dovecot-2.3.21.1/0000755000000000000000000000000014656633642010367 500000000000000dovecot-2.3.21.1/dovecot-version.h0000644000000000000000000000034714656633633013612 00000000000000#ifndef DOVECOT_VERSION_H #define DOVECOT_VERSION_H #define DOVECOT_REVISION "d492236fa0" #define DOVECOT_VERSION_FULL VERSION" ("DOVECOT_REVISION")" #define DOVECOT_BUILD_INFO DOVECOT_VERSION_FULL #endif /* DOVECOT_VERSION_H */ dovecot-2.3.21.1/COPYING0000644000000000000000000000757614656633576011367 00000000000000See AUTHORS file for list of copyright holders. Everything in src/lib/, src/auth/, and src/lib-sql/ is under MIT license (see COPYING.MIT) unless otherwise mentioned at the beginning of the file. Everything else is LGPLv2.1 (see COPYING.LGPL) unless otherwise mentioned at the beginning of the file. Current exceptions are: src/lib/md5.c : Public Domain src/lib/sha1.c and sha2.c: Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. Copyright (C) 2005, 2007 Olivier Gay All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. src/lib/UnicodeData.txt: Copyright (C) 1991-2007 Unicode, Inc. All rights reserved. Distributed under the Terms of Use in http://www.unicode.org/copyright.html. Permission is hereby granted, free of charge, to any person obtaining a copy of the Unicode data files and any associated documentation (the "Data Files") or Unicode software and any associated documentation (the "Software") to deal in the Data Files or Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Data Files or Software, and to permit persons to whom the Data Files or Software are furnished to do so, provided that (a) the above copyright notice(s) and this permission notice appear with all copies of the Data Files or Software, (b) both the above copyright notice(s) and this permission notice appear in associated documentation, and (c) there is clear notice in each modified Data File or in the Software as well as in the documentation associated with the Data File(s) or Software that the data or software has been modified. THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR SOFTWARE. Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in these Data Files or Software without prior written authorization of the copyright holder. dovecot-2.3.21.1/README0000644000000000000000000001157014656633635011175 00000000000000Installation See INSTALL.md file. Configuration See doc/documentation.txt or http://wiki2.dovecot.org/ RFCs conformed email - RFC822 - Standard for ARPA Internet Text Messages - RFC2822 - Internet Message Format (updated RFC822) - RFC2045 - Multipurpose Internet Mail Extensions (MIME) (part 1) - RFC2046 - Multipurpose Internet Mail Extensions (MIME) (part 2) - RFC2047 - Multipurpose Internet Mail Extensions (MIME) (part 3) - RFC2048 - Multipurpose Internet Mail Extensions (MIME) (part 4) - RFC2049 - Multipurpose Internet Mail Extensions (MIME) (part 5) Auth - RFC2245 - Anonymous SASL Mechanism. - RFC2595 - Using TLS with IMAP, POP3 and ACAP - RFC2831 - Using Digest Authentication as a SASL Mechanism (DIGEST-MD5) - RFC5802 - Salted Challenge Response Authentication Mechanism (SCRAM) - SASL and GSS-API Mechanisms - RFC7628 - A Set of Simple Authentication and Security Layer (SASL) Mechanisms for OAuth - Google XOAUTH2 protocol POP3 - RFC1939 - Post Office Protocol - Version 3 - RFC2449 - POP3 Extension Mechanism - RFC2595 - Using TLS with IMAP, POP3 and ACAP - RFC3206 - The SYS and AUTH POP Response Codes - RFC5034 - The Post Office Protocol (POP3) - Simple Authentication and Security Layer (SASL) Authentication Mechanism IMAP base - RFC3501 - IMAP4rev1 - RFC2180 - IMAP4 Multi-Accessed Mailbox Practice - RFC2595 - Using TLS with IMAP, POP3 and ACAP - RFC2683 - IMAP4 Implementation Recommendations IMAP extensions - RFC2087 - IMAP4 QUOTA extension - RFC2088 - IMAP4 non-synchronizing literals (LITERAL+) - RFC2177 - IMAP4 IDLE command - RFC2221 - IMAP4 Login Referrals - RFC2342 - IMAP4 Namespace - RFC2971 - IMAP4 ID extension - RFC3348 - IMAP4 Child Mailbox Extension - RFC3502 - IMAP4 MULTIAPPEND Extension - RFC3516 - IMAP4 Binary Content Extension - RFC3691 - IMAP4 UNSELECT command - RFC4314 - IMAP4 Access Control List (ACL) Extension - RFC4315 - IMAP UIDPLUS extension - RFC4467 - IMAP URLAUTH Extension - RFC4469 - IMAP CATENATE Extension - RFC4551 - IMAP Extension for Conditional STORE Operation or Quick Flag Changes Resynchronization - RFC4731 - IMAP4 Extension to SEARCH Command for Controlling What Kind of Information Is Returned - RFC4959 - IMAP Extension for Simple Authentication and Security Layer (SASL) Initial Client Response - RFC4978 - The IMAP COMPRESS Extension - RFC5032 - WITHIN Search Extension to the IMAP Protocol - RFC5161 - The IMAP ENABLE Extension - RFC5162 - IMAP4 Extensions for Quick Mailbox Resynchronization - RFC5182 - IMAP Extension for Referencing the Last SEARCH Result - RFC5255 - IMAP Internationalization (I18NLEVEL=1 only) - RFC5256 - IMAP SORT and THREAD Extensions - RFC5258 - IMAP4 - LIST Command Extensions - RFC5267 - Contexts for IMAP4 (ESORT and CONTEXT=SEARCH only) - RFC5464 - The IMAP METADATA Extension - RFC5465 - The IMAP NOTIFY Extension - RFC5524 - Extended URLFETCH for Binary and Converted Parts - RFC5530 - IMAP Response Codes - RFC5819 - IMAP4 Extension for Returning STATUS Information in Extended LIST - RFC5957 - Display-Based Address Sorting for the IMAP4 SORT Extension - RFC6154 - IMAP LIST Extension for Special-Use Mailboxes (SPECIAL-USE only) - RFC6203 - IMAP4 Extension for Fuzzy Search - RFC6785 - Support for IMAP Events in Sieve (via Pigeonhole plugin) - RFC6851 - Internet Message Access Protocol (IMAP) - MOVE Extension - RFC7162 - IMAP Extensions: Quick Flag Changes Resynchronization (CONDSTORE) and Quick Mailbox Resynchronization (QRESYNC) (updated RFC4551 and RFC5162) - RFC7888 - IMAP4 Non-synchronizing Literals (updated RFC2088) - RFC8457 - IMAP "$Important" Keyword and "\Important" Special-Use Attribute - RFC8970 - IMAP4 Extension: Message Preview Generation SMTP/LMTP base - RFC821 - Simple Mail Transfer Protocol - RFC2821 - Simple Mail Transfer Protocol (updated RFC821) - RFC5321 - Simple Mail Transfer Protocol (updated RFC2821) - RFC2033 - Local Mail Transfer Protocol - RFC6409 - Message Submission for Mail SMTP/LMTP extensions - RFC1870 - SMTP Service Extension for Message Size Declaration - RFC2034 - SMTP Service Extension for Returning Enhanced Error Codes - RFC2920 - SMTP Service Extension for Command Pipelining - RFC3030 - SMTP Service Extensions for Transmission of Large and Binary MIME Messages - RFC3207 - SMTP Service Extension for Secure SMTP over Transport Layer Security - RFC4468 - Message Submission BURL Extension - RFC4954 - SMTP Service Extension for Authentication - RFC6152 - SMTP Service Extension for 8-bit MIME Transport Contact info Timo Sirainen tss@iki.fi, http://www.dovecot.org/ Please use the Dovecot mailing list dovecot@dovecot.org for questions about Dovecot. You can post to the list without subscribing, the mail then waits in a moderator queue for a while. See http://dovecot.org/mailinglists.html dovecot-2.3.21.1/install-sh0000755000000000000000000003577614656633607012336 00000000000000#!/bin/sh # install - install a program, script, or datafile scriptversion=2020-11-14.01; # UTC # This originates from X11R5 (mit/util/scripts/install.sh), which was # later released in X11R6 (xc/config/util/install.sh) with the # following copyright and license. # # Copyright (C) 1994 X Consortium # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- # TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # Except as contained in this notice, the name of the X Consortium shall not # be used in advertising or otherwise to promote the sale, use or other deal- # ings in this Software without prior written authorization from the X Consor- # tium. # # # FSF changes to this file are in the public domain. # # Calling this script install-sh is preferred over install.sh, to prevent # 'make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. tab=' ' nl=' ' IFS=" $tab$nl" # Set DOITPROG to "echo" to test this script. doit=${DOITPROG-} doit_exec=${doit:-exec} # Put in absolute file names if you don't have them in your path; # or use environment vars. chgrpprog=${CHGRPPROG-chgrp} chmodprog=${CHMODPROG-chmod} chownprog=${CHOWNPROG-chown} cmpprog=${CMPPROG-cmp} cpprog=${CPPROG-cp} mkdirprog=${MKDIRPROG-mkdir} mvprog=${MVPROG-mv} rmprog=${RMPROG-rm} stripprog=${STRIPPROG-strip} posix_mkdir= # Desired mode of installed file. mode=0755 # Create dirs (including intermediate dirs) using mode 755. # This is like GNU 'install' as of coreutils 8.32 (2020). mkdir_umask=22 backupsuffix= chgrpcmd= chmodcmd=$chmodprog chowncmd= mvcmd=$mvprog rmcmd="$rmprog -f" stripcmd= src= dst= dir_arg= dst_arg= copy_on_change=false is_target_a_directory=possibly usage="\ Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE or: $0 [OPTION]... SRCFILES... DIRECTORY or: $0 [OPTION]... -t DIRECTORY SRCFILES... or: $0 [OPTION]... -d DIRECTORIES... In the 1st form, copy SRCFILE to DSTFILE. In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. In the 4th, create DIRECTORIES. Options: --help display this help and exit. --version display version info and exit. -c (ignored) -C install only if different (preserve data modification time) -d create directories instead of installing files. -g GROUP $chgrpprog installed files to GROUP. -m MODE $chmodprog installed files to MODE. -o USER $chownprog installed files to USER. -p pass -p to $cpprog. -s $stripprog installed files. -S SUFFIX attempt to back up existing files, with suffix SUFFIX. -t DIRECTORY install into DIRECTORY. -T report an error if DSTFILE is a directory. Environment variables override the default commands: CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG RMPROG STRIPPROG By default, rm is invoked with -f; when overridden with RMPROG, it's up to you to specify -f if you want it. If -S is not specified, no backups are attempted. Email bug reports to bug-automake@gnu.org. Automake home page: https://www.gnu.org/software/automake/ " while test $# -ne 0; do case $1 in -c) ;; -C) copy_on_change=true;; -d) dir_arg=true;; -g) chgrpcmd="$chgrpprog $2" shift;; --help) echo "$usage"; exit $?;; -m) mode=$2 case $mode in *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*) echo "$0: invalid mode: $mode" >&2 exit 1;; esac shift;; -o) chowncmd="$chownprog $2" shift;; -p) cpprog="$cpprog -p";; -s) stripcmd=$stripprog;; -S) backupsuffix="$2" shift;; -t) is_target_a_directory=always dst_arg=$2 # Protect names problematic for 'test' and other utilities. case $dst_arg in -* | [=\(\)!]) dst_arg=./$dst_arg;; esac shift;; -T) is_target_a_directory=never;; --version) echo "$0 $scriptversion"; exit $?;; --) shift break;; -*) echo "$0: invalid option: $1" >&2 exit 1;; *) break;; esac shift done # We allow the use of options -d and -T together, by making -d # take the precedence; this is for compatibility with GNU install. if test -n "$dir_arg"; then if test -n "$dst_arg"; then echo "$0: target directory not allowed when installing a directory." >&2 exit 1 fi fi if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then # When -d is used, all remaining arguments are directories to create. # When -t is used, the destination is already specified. # Otherwise, the last argument is the destination. Remove it from $@. for arg do if test -n "$dst_arg"; then # $@ is not empty: it contains at least $arg. set fnord "$@" "$dst_arg" shift # fnord fi shift # arg dst_arg=$arg # Protect names problematic for 'test' and other utilities. case $dst_arg in -* | [=\(\)!]) dst_arg=./$dst_arg;; esac done fi if test $# -eq 0; then if test -z "$dir_arg"; then echo "$0: no input file specified." >&2 exit 1 fi # It's OK to call 'install-sh -d' without argument. # This can happen when creating conditional directories. exit 0 fi if test -z "$dir_arg"; then if test $# -gt 1 || test "$is_target_a_directory" = always; then if test ! -d "$dst_arg"; then echo "$0: $dst_arg: Is not a directory." >&2 exit 1 fi fi fi if test -z "$dir_arg"; then do_exit='(exit $ret); exit $ret' trap "ret=129; $do_exit" 1 trap "ret=130; $do_exit" 2 trap "ret=141; $do_exit" 13 trap "ret=143; $do_exit" 15 # Set umask so as not to create temps with too-generous modes. # However, 'strip' requires both read and write access to temps. case $mode in # Optimize common cases. *644) cp_umask=133;; *755) cp_umask=22;; *[0-7]) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw='% 200' fi cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; *) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw=,u+rw fi cp_umask=$mode$u_plus_rw;; esac fi for src do # Protect names problematic for 'test' and other utilities. case $src in -* | [=\(\)!]) src=./$src;; esac if test -n "$dir_arg"; then dst=$src dstdir=$dst test -d "$dstdir" dstdir_status=$? # Don't chown directories that already exist. if test $dstdir_status = 0; then chowncmd="" fi else # Waiting for this to be detected by the "$cpprog $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if test ! -f "$src" && test ! -d "$src"; then echo "$0: $src does not exist." >&2 exit 1 fi if test -z "$dst_arg"; then echo "$0: no destination specified." >&2 exit 1 fi dst=$dst_arg # If destination is a directory, append the input filename. if test -d "$dst"; then if test "$is_target_a_directory" = never; then echo "$0: $dst_arg: Is a directory" >&2 exit 1 fi dstdir=$dst dstbase=`basename "$src"` case $dst in */) dst=$dst$dstbase;; *) dst=$dst/$dstbase;; esac dstdir_status=0 else dstdir=`dirname "$dst"` test -d "$dstdir" dstdir_status=$? fi fi case $dstdir in */) dstdirslash=$dstdir;; *) dstdirslash=$dstdir/;; esac obsolete_mkdir_used=false if test $dstdir_status != 0; then case $posix_mkdir in '') # With -d, create the new directory with the user-specified mode. # Otherwise, rely on $mkdir_umask. if test -n "$dir_arg"; then mkdir_mode=-m$mode else mkdir_mode= fi posix_mkdir=false # The $RANDOM variable is not portable (e.g., dash). Use it # here however when possible just to lower collision chance. tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ trap ' ret=$? rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null exit $ret ' 0 # Because "mkdir -p" follows existing symlinks and we likely work # directly in world-writeable /tmp, make sure that the '$tmpdir' # directory is successfully created first before we actually test # 'mkdir -p'. if (umask $mkdir_umask && $mkdirprog $mkdir_mode "$tmpdir" && exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 then if test -z "$dir_arg" || { # Check for POSIX incompatibilities with -m. # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or # other-writable bit of parent directory when it shouldn't. # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. test_tmpdir="$tmpdir/a" ls_ld_tmpdir=`ls -ld "$test_tmpdir"` case $ls_ld_tmpdir in d????-?r-*) different_mode=700;; d????-?--*) different_mode=755;; *) false;; esac && $mkdirprog -m$different_mode -p -- "$test_tmpdir" && { ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"` test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" } } then posix_mkdir=: fi rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" else # Remove any dirs left behind by ancient mkdir implementations. rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null fi trap '' 0;; esac if $posix_mkdir && ( umask $mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" ) then : else # mkdir does not conform to POSIX, # or it failed possibly due to a race condition. Create the # directory the slow way, step by step, checking for races as we go. case $dstdir in /*) prefix='/';; [-=\(\)!]*) prefix='./';; *) prefix='';; esac oIFS=$IFS IFS=/ set -f set fnord $dstdir shift set +f IFS=$oIFS prefixes= for d do test X"$d" = X && continue prefix=$prefix$d if test -d "$prefix"; then prefixes= else if $posix_mkdir; then (umask $mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break # Don't fail if two instances are running concurrently. test -d "$prefix" || exit 1 else case $prefix in *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; *) qprefix=$prefix;; esac prefixes="$prefixes '$qprefix'" fi fi prefix=$prefix/ done if test -n "$prefixes"; then # Don't fail if two instances are running concurrently. (umask $mkdir_umask && eval "\$doit_exec \$mkdirprog $prefixes") || test -d "$dstdir" || exit 1 obsolete_mkdir_used=true fi fi fi if test -n "$dir_arg"; then { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 else # Make a couple of temp file names in the proper directory. dsttmp=${dstdirslash}_inst.$$_ rmtmp=${dstdirslash}_rm.$$_ # Trap to clean up those temp files at exit. trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 # Copy the file name to the temp name. (umask $cp_umask && { test -z "$stripcmd" || { # Create $dsttmp read-write so that cp doesn't create it read-only, # which would cause strip to fail. if test -z "$doit"; then : >"$dsttmp" # No need to fork-exec 'touch'. else $doit touch "$dsttmp" fi } } && $doit_exec $cpprog "$src" "$dsttmp") && # and set any options; do chmod last to preserve setuid bits. # # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $cpprog $src $dsttmp" command. # { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && # If -C, don't bother to copy if it wouldn't change the file. if $copy_on_change && old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && set -f && set X $old && old=:$2:$4:$5:$6 && set X $new && new=:$2:$4:$5:$6 && set +f && test "$old" = "$new" && $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 then rm -f "$dsttmp" else # If $backupsuffix is set, and the file being installed # already exists, attempt a backup. Don't worry if it fails, # e.g., if mv doesn't support -f. if test -n "$backupsuffix" && test -f "$dst"; then $doit $mvcmd -f "$dst" "$dst$backupsuffix" 2>/dev/null fi # Rename the file to the real destination. $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || # The rename failed, perhaps because mv can't rename something else # to itself, or perhaps because mv is so ancient that it does not # support -f. { # Now remove or move aside any old file at destination location. # We try this two ways since rm can't unlink itself on some # systems and the destination file might be busy for other # reasons. In this case, the final cleanup might fail but the new # file should still install successfully. { test ! -f "$dst" || $doit $rmcmd "$dst" 2>/dev/null || { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && { $doit $rmcmd "$rmtmp" 2>/dev/null; :; } } || { echo "$0: cannot unlink or rename $dst" >&2 (exit 1); exit 1 } } && # Now rename the file to the real destination. $doit $mvcmd "$dsttmp" "$dst" } fi || exit 1 trap '' 0 fi done # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: dovecot-2.3.21.1/src/0000755000000000000000000000000014656633641011155 500000000000000dovecot-2.3.21.1/src/old-stats/0000755000000000000000000000000014656633641013067 500000000000000dovecot-2.3.21.1/src/old-stats/mail-command.h0000644000000000000000000000072714656633576015533 00000000000000#ifndef MAIL_COMMAND_H #define MAIL_COMMAND_H struct mail_command; extern struct mail_command *stable_mail_commands_head; extern struct mail_command *stable_mail_commands_tail; int mail_command_update_parse(const char *const *args, const char **error_r); void mail_command_ref(struct mail_command *cmd); void mail_command_unref(struct mail_command **cmd); void mail_commands_free_memory(void); void mail_commands_init(void); void mail_commands_deinit(void); #endif dovecot-2.3.21.1/src/old-stats/client-export.c0000644000000000000000000004366514656633576015775 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "net.h" #include "ostream.h" #include "str.h" #include "strescape.h" #include "wildcard-match.h" #include "mail-stats.h" #include "mail-command.h" #include "mail-session.h" #include "mail-user.h" #include "mail-domain.h" #include "mail-ip.h" #include "client.h" #include "client-export.h" enum mail_export_level { MAIL_EXPORT_LEVEL_COMMAND, MAIL_EXPORT_LEVEL_SESSION, MAIL_EXPORT_LEVEL_USER, MAIL_EXPORT_LEVEL_DOMAIN, MAIL_EXPORT_LEVEL_IP, MAIL_EXPORT_LEVEL_GLOBAL }; static const char *mail_export_level_names[] = { "command", "session", "user", "domain", "ip", "global" }; struct mail_export_filter { const char *user, *domain, *session; struct ip_addr ip; unsigned int ip_bits; time_t since; bool connected; }; struct client_export_cmd { enum mail_export_level level; struct mail_export_filter filter; string_t *str; int (*export_iter)(struct client *client); bool header_sent; }; static int mail_export_level_parse(const char *str, enum mail_export_level *level_r) { unsigned int i; for (i = 0; i < N_ELEMENTS(mail_export_level_names); i++) { if (strcmp(mail_export_level_names[i], str) == 0) { *level_r = (enum mail_export_level)i; return 0; } } return -1; } static int mail_export_parse_filter(const char *const *args, pool_t pool, struct mail_export_filter *filter_r, const char **error_r) { unsigned long l; /* filters: user= | domain= | session= ip=[/] since= connected */ i_zero(filter_r); for (; *args != NULL; args++) { if (str_begins(*args, "user=")) filter_r->user = p_strdup(pool, *args + 5); else if (str_begins(*args, "domain=")) filter_r->domain = p_strdup(pool, *args + 7); else if (str_begins(*args, "session=")) filter_r->session = p_strdup(pool, *args + 8); else if (str_begins(*args, "ip=")) { if (net_parse_range(*args + 3, &filter_r->ip, &filter_r->ip_bits) < 0) { *error_r = "Invalid ip filter"; return -1; } } else if (str_begins(*args, "since=")) { if (str_to_ulong(*args + 6, &l) < 0) { *error_r = "Invalid since filter"; return -1; } filter_r->since = (time_t)l; } else if (strcmp(*args, "connected") == 0) { filter_r->connected = TRUE; } } return 0; } static void client_export_stats_headers(struct client *client) { unsigned int i, count = stats_field_count(); string_t *str = t_str_new(128); i_assert(count > 0); str_append(str, stats_field_name(0)); for (i = 1; i < count; i++) { str_append_c(str, '\t'); str_append(str, stats_field_name(i)); } str_append_c(str, '\n'); o_stream_nsend(client->output, str_data(str), str_len(str)); } static void client_export_stats(string_t *str, const struct stats *stats) { unsigned int i, count = stats_field_count(); i_assert(count > 0); stats_field_value(str, stats, 0); for (i = 1; i < count; i++) { str_append_c(str, '\t'); stats_field_value(str, stats, i); } } static bool mail_export_filter_match_session(const struct mail_export_filter *filter, const struct mail_session *session) { if (filter->connected && session->disconnected) return FALSE; if (filter->since > session->last_update.tv_sec) return FALSE; if (filter->session != NULL && strcmp(session->id, filter->session) != 0) return FALSE; if (filter->user != NULL && !wildcard_match(session->user->name, filter->user)) return FALSE; if (filter->domain != NULL && !wildcard_match(session->user->domain->name, filter->domain)) return FALSE; if (filter->ip_bits > 0 && !net_is_in_network(&session->ip->ip, &filter->ip, filter->ip_bits)) return FALSE; return TRUE; } static bool mail_export_filter_match_user_common(const struct mail_export_filter *filter, const struct mail_user *user) { struct mail_session *s; bool connected = FALSE, ip_ok = FALSE; if (filter->user != NULL && !wildcard_match(user->name, filter->user)) return FALSE; if (filter->connected || filter->ip_bits > 0) { for (s = user->sessions; s != NULL; s = s->user_next) { if (!s->disconnected) connected = TRUE; if (filter->ip_bits > 0 && net_is_in_network(&s->ip->ip, &filter->ip, filter->ip_bits)) ip_ok = TRUE; } if (filter->connected && !connected) return FALSE; if (filter->ip_bits > 0 && !ip_ok) return FALSE; } return TRUE; } static bool mail_export_filter_match_user(const struct mail_export_filter *filter, const struct mail_user *user) { if (filter->since > user->last_update.tv_sec) return FALSE; if (filter->domain != NULL && !wildcard_match(user->domain->name, filter->domain)) return FALSE; return mail_export_filter_match_user_common(filter, user); } static bool mail_export_filter_match_domain(const struct mail_export_filter *filter, const struct mail_domain *domain) { struct mail_user *user; if (filter->since > domain->last_update.tv_sec) return FALSE; if (filter->domain != NULL && !wildcard_match(domain->name, filter->domain)) return FALSE; if (filter->user != NULL || filter->connected || filter->ip_bits > 0) { for (user = domain->users; user != NULL; user = user->domain_next) { if (mail_export_filter_match_user_common(filter, user)) break; } if (user == NULL) return FALSE; } return TRUE; } static bool mail_export_filter_match_ip(const struct mail_export_filter *filter, const struct mail_ip *ip) { struct mail_session *s; bool connected = FALSE, user_ok = FALSE, domain_ok = FALSE; if (filter->connected || filter->ip_bits > 0) { for (s = ip->sessions; s != NULL; s = s->ip_next) { if (!s->disconnected) connected = TRUE; if (filter->user != NULL && wildcard_match(s->user->name, filter->user)) user_ok = TRUE; if (filter->domain != NULL && wildcard_match(s->user->domain->name, filter->domain)) domain_ok = TRUE; } if (filter->connected && !connected) return FALSE; if (filter->user != NULL && !user_ok) return FALSE; if (filter->domain != NULL && !domain_ok) return FALSE; } if (filter->since > ip->last_update.tv_sec) return FALSE; if (filter->ip_bits > 0 && !net_is_in_network(&ip->ip, &filter->ip, filter->ip_bits)) return FALSE; return TRUE; } static void client_export_timeval(string_t *str, const struct timeval *tv) { str_printfa(str, "\t%ld.%06u", (long)tv->tv_sec, (unsigned int)tv->tv_usec); } static int client_export_iter_command(struct client *client) { struct client_export_cmd *cmd = client->cmd_export; struct mail_command *command = client->mail_cmd_iter; i_assert(cmd->level == MAIL_EXPORT_LEVEL_COMMAND); mail_command_unref(&client->mail_cmd_iter); if (!cmd->header_sent) { o_stream_nsend_str(client->output, "cmd\targs\tsession\tuser\tlast_update\t"); client_export_stats_headers(client); cmd->header_sent = TRUE; } for (; command != NULL; command = command->stable_next) { if (client_is_busy(client)) break; if (!mail_export_filter_match_session(&cmd->filter, command->session)) continue; str_truncate(cmd->str, 0); str_append_tabescaped(cmd->str, command->name); str_append_c(cmd->str, '\t'); str_append_tabescaped(cmd->str, command->args); str_append_c(cmd->str, '\t'); str_append(cmd->str, command->session->id); str_append_c(cmd->str, '\t'); str_append_tabescaped(cmd->str, command->session->user->name); client_export_timeval(cmd->str, &command->last_update); str_append_c(cmd->str, '\t'); client_export_stats(cmd->str, command->stats); str_append_c(cmd->str, '\n'); o_stream_nsend(client->output, str_data(cmd->str), str_len(cmd->str)); } if (command != NULL) { client->mail_cmd_iter = command; mail_command_ref(command); return 0; } return 1; } static int client_export_iter_session(struct client *client) { struct client_export_cmd *cmd = client->cmd_export; struct mail_session *session = client->mail_session_iter; i_assert(cmd->level == MAIL_EXPORT_LEVEL_SESSION); mail_session_unref(&client->mail_session_iter); if (!cmd->header_sent) { o_stream_nsend_str(client->output, "session\tuser\tip\tservice\tpid\tconnected" "\tlast_update\tnum_cmds\t"); client_export_stats_headers(client); cmd->header_sent = TRUE; } for (; session != NULL; session = session->stable_next) { if (client_is_busy(client)) break; if (!mail_export_filter_match_session(&cmd->filter, session)) continue; str_truncate(cmd->str, 0); str_append(cmd->str, session->id); str_append_c(cmd->str, '\t'); str_append_tabescaped(cmd->str, session->user->name); str_append_c(cmd->str, '\t'); if (session->ip != NULL) T_BEGIN { str_append(cmd->str, net_ip2addr(&session->ip->ip)); } T_END; str_append_c(cmd->str, '\t'); str_append_tabescaped(cmd->str, session->service); str_printfa(cmd->str, "\t%ld", (long)session->pid); str_printfa(cmd->str, "\t%d", !session->disconnected); client_export_timeval(cmd->str, &session->last_update); str_printfa(cmd->str, "\t%u\t", session->num_cmds); client_export_stats(cmd->str, session->stats); str_append_c(cmd->str, '\n'); o_stream_nsend(client->output, str_data(cmd->str), str_len(cmd->str)); } if (session != NULL) { client->mail_session_iter = session; mail_session_ref(session); return 0; } return 1; } static int client_export_iter_user(struct client *client) { struct client_export_cmd *cmd = client->cmd_export; struct mail_user *user = client->mail_user_iter; i_assert(cmd->level == MAIL_EXPORT_LEVEL_USER); mail_user_unref(&client->mail_user_iter); if (!cmd->header_sent) { o_stream_nsend_str(client->output, "user\treset_timestamp\tlast_update" "\tnum_logins\tnum_cmds\t"); client_export_stats_headers(client); cmd->header_sent = TRUE; } for (; user != NULL; user = user->stable_next) { if (client_is_busy(client)) break; if (!mail_export_filter_match_user(&cmd->filter, user)) continue; str_truncate(cmd->str, 0); str_append_tabescaped(cmd->str, user->name); str_printfa(cmd->str, "\t%ld", (long)user->reset_timestamp); client_export_timeval(cmd->str, &user->last_update); str_printfa(cmd->str, "\t%u\t%u\t", user->num_logins, user->num_cmds); client_export_stats(cmd->str, user->stats); str_append_c(cmd->str, '\n'); o_stream_nsend(client->output, str_data(cmd->str), str_len(cmd->str)); } if (user != NULL) { client->mail_user_iter = user; mail_user_ref(user); return 0; } return 1; } static int client_export_iter_domain(struct client *client) { struct client_export_cmd *cmd = client->cmd_export; struct mail_domain *domain = client->mail_domain_iter; i_assert(cmd->level == MAIL_EXPORT_LEVEL_DOMAIN); mail_domain_unref(&client->mail_domain_iter); if (!cmd->header_sent) { o_stream_nsend_str(client->output, "domain\treset_timestamp\tlast_update" "\tnum_logins\tnum_cmds\tnum_connected_sessions\t"); client_export_stats_headers(client); cmd->header_sent = TRUE; } for (; domain != NULL; domain = domain->stable_next) { if (client_is_busy(client)) break; if (!mail_export_filter_match_domain(&cmd->filter, domain)) continue; str_truncate(cmd->str, 0); str_append_tabescaped(cmd->str, domain->name); str_printfa(cmd->str, "\t%ld", (long)domain->reset_timestamp); client_export_timeval(cmd->str, &domain->last_update); str_printfa(cmd->str, "\t%u\t%u\t%u\t", domain->num_logins, domain->num_cmds, domain->num_connected_sessions); client_export_stats(cmd->str, domain->stats); str_append_c(cmd->str, '\n'); o_stream_nsend(client->output, str_data(cmd->str), str_len(cmd->str)); } if (domain != NULL) { client->mail_domain_iter = domain; mail_domain_ref(domain); return 0; } return 1; } static int client_export_iter_ip(struct client *client) { struct client_export_cmd *cmd = client->cmd_export; struct mail_ip *ip = client->mail_ip_iter; i_assert(cmd->level == MAIL_EXPORT_LEVEL_IP); mail_ip_unref(&client->mail_ip_iter); if (!cmd->header_sent) { o_stream_nsend_str(client->output, "ip\treset_timestamp\tlast_update" "\tnum_logins\tnum_cmds\tnum_connected_sessions\t"); client_export_stats_headers(client); cmd->header_sent = TRUE; } for (; ip != NULL; ip = ip->stable_next) { if (client_is_busy(client)) break; if (!mail_export_filter_match_ip(&cmd->filter, ip)) continue; str_truncate(cmd->str, 0); T_BEGIN { str_append(cmd->str, net_ip2addr(&ip->ip)); } T_END; str_printfa(cmd->str, "\t%ld", (long)ip->reset_timestamp); client_export_timeval(cmd->str, &ip->last_update); str_printfa(cmd->str, "\t%u\t%u\t%u\t", ip->num_logins, ip->num_cmds, ip->num_connected_sessions); client_export_stats(cmd->str, ip->stats); str_append_c(cmd->str, '\n'); o_stream_nsend(client->output, str_data(cmd->str), str_len(cmd->str)); } if (ip != NULL) { client->mail_ip_iter = ip; mail_ip_ref(ip); return 0; } return 1; } static int client_export_iter_global(struct client *client) { struct client_export_cmd *cmd = client->cmd_export; struct mail_global *g = &mail_global_stats; i_assert(cmd->level == MAIL_EXPORT_LEVEL_GLOBAL); if (!cmd->header_sent) { o_stream_nsend_str(client->output, "reset_timestamp\tlast_update" "\tnum_logins\tnum_cmds\tnum_connected_sessions\t"); client_export_stats_headers(client); cmd->header_sent = TRUE; } str_truncate(cmd->str, 0); str_printfa(cmd->str, "%ld", (long)g->reset_timestamp); client_export_timeval(cmd->str, &g->last_update); str_printfa(cmd->str, "\t%u\t%u\t%u\t", g->num_logins, g->num_cmds, g->num_connected_sessions); client_export_stats(cmd->str, g->stats); str_append_c(cmd->str, '\n'); o_stream_nsend(client->output, str_data(cmd->str), str_len(cmd->str)); return 1; } static int client_export_more(struct client *client) { if (client->cmd_export->export_iter(client) == 0) return 0; o_stream_nsend_str(client->output, "\n"); return 1; } static bool client_export_iter_init(struct client *client) { struct client_export_cmd *cmd = client->cmd_export; if (cmd->filter.user != NULL && strchr(cmd->filter.user, '*') == NULL && (cmd->level == MAIL_EXPORT_LEVEL_USER || cmd->level == MAIL_EXPORT_LEVEL_SESSION)) { /* exact user */ struct mail_user *user = mail_user_lookup(cmd->filter.user); if (user == NULL) return FALSE; if (cmd->level == MAIL_EXPORT_LEVEL_SESSION) { client->mail_session_iter = user->sessions; if (client->mail_session_iter == NULL) return FALSE; mail_session_ref(client->mail_session_iter); cmd->export_iter = client_export_iter_session; } else { client->mail_user_iter = user; mail_user_ref(user); cmd->export_iter = client_export_iter_user; } return TRUE; } if (cmd->filter.ip_bits == IPADDR_BITS(&cmd->filter.ip) && (cmd->level == MAIL_EXPORT_LEVEL_IP || cmd->level == MAIL_EXPORT_LEVEL_SESSION)) { /* exact IP address */ struct mail_ip *ip = mail_ip_lookup(&cmd->filter.ip); if (ip == NULL) return FALSE; if (cmd->level == MAIL_EXPORT_LEVEL_SESSION) { client->mail_session_iter = ip->sessions; if (client->mail_session_iter == NULL) return FALSE; mail_session_ref(client->mail_session_iter); cmd->export_iter = client_export_iter_session; } else { client->mail_ip_iter = ip; mail_ip_ref(ip); cmd->export_iter = client_export_iter_ip; } return TRUE; } if (cmd->filter.domain != NULL && strchr(cmd->filter.domain, '*') == NULL && (cmd->level == MAIL_EXPORT_LEVEL_DOMAIN || cmd->level == MAIL_EXPORT_LEVEL_USER)) { /* exact domain */ struct mail_domain *domain = mail_domain_lookup(cmd->filter.domain); if (domain == NULL) return FALSE; if (cmd->level == MAIL_EXPORT_LEVEL_USER) { client->mail_user_iter = domain->users; mail_user_ref(client->mail_user_iter); cmd->export_iter = client_export_iter_user; } else { client->mail_domain_iter = domain; mail_domain_ref(domain); cmd->export_iter = client_export_iter_domain; } return TRUE; } switch (cmd->level) { case MAIL_EXPORT_LEVEL_COMMAND: client->mail_cmd_iter = stable_mail_commands_head; if (client->mail_cmd_iter == NULL) return FALSE; mail_command_ref(client->mail_cmd_iter); cmd->export_iter = client_export_iter_command; break; case MAIL_EXPORT_LEVEL_SESSION: client->mail_session_iter = stable_mail_sessions; if (client->mail_session_iter == NULL) return FALSE; mail_session_ref(client->mail_session_iter); cmd->export_iter = client_export_iter_session; break; case MAIL_EXPORT_LEVEL_USER: client->mail_user_iter = stable_mail_users; if (client->mail_user_iter == NULL) return FALSE; mail_user_ref(client->mail_user_iter); cmd->export_iter = client_export_iter_user; break; case MAIL_EXPORT_LEVEL_DOMAIN: client->mail_domain_iter = stable_mail_domains; if (client->mail_domain_iter == NULL) return FALSE; mail_domain_ref(client->mail_domain_iter); cmd->export_iter = client_export_iter_domain; break; case MAIL_EXPORT_LEVEL_IP: client->mail_ip_iter = stable_mail_ips; if (client->mail_ip_iter == NULL) return FALSE; mail_ip_ref(client->mail_ip_iter); cmd->export_iter = client_export_iter_ip; break; case MAIL_EXPORT_LEVEL_GLOBAL: cmd->export_iter = client_export_iter_global; break; } i_assert(cmd->export_iter != NULL); return TRUE; } int client_export(struct client *client, const char *const *args, const char **error_r) { const char *level_str = args[0]; struct client_export_cmd *cmd; p_clear(client->cmd_pool); cmd = p_new(client->cmd_pool, struct client_export_cmd, 1); cmd->str = str_new(client->cmd_pool, 256); if (level_str == NULL) { *error_r = "Missing level parameter"; return -1; } if (mail_export_level_parse(level_str, &cmd->level) < 0) { *error_r = "Invalid level"; return -1; } if (mail_export_parse_filter(args + 1, client->cmd_pool, &cmd->filter, error_r) < 0) return -1; client->cmd_export = cmd; if (!client_export_iter_init(client)) { /* nothing to export */ o_stream_nsend_str(client->output, "\n"); return 1; } client->cmd_more = client_export_more; return client_export_more(client); } dovecot-2.3.21.1/src/old-stats/mail-stats.h0000644000000000000000000000507514656633576015254 00000000000000#ifndef MAIL_STATS_H #define MAIL_STATS_H #include #include "net.h" #include "guid.h" #include "stats.h" struct stats_send_ctx; struct mail_command { struct mail_command *stable_prev, *stable_next; struct mail_command *session_prev, *session_next; struct mail_session *session; char *name, *args; /* non-zero id means the command is still running */ unsigned int id; struct timeval last_update; struct stats *stats; int refcount; }; struct mail_session { struct mail_session *stable_prev, *stable_next; struct mail_session *sorted_prev, *sorted_next; struct mail_session *user_prev, *user_next; struct mail_session *ip_prev, *ip_next; /* if id="", the session no longer exists */ char *id; struct mail_user *user; const char *service; pid_t pid; /* ip address may be NULL if there's none */ struct mail_ip *ip; struct timeout *to_idle; struct stats *stats; struct timeval last_update; unsigned int num_cmds; bool disconnected; unsigned int highest_cmd_id; int refcount; struct mail_command *commands; }; struct mail_user { struct mail_user *stable_prev, *stable_next; struct mail_user *sorted_prev, *sorted_next; struct mail_user *domain_prev, *domain_next; char *name; struct mail_domain *domain; time_t reset_timestamp; struct timeval last_update; struct stats *stats; unsigned int num_logins; unsigned int num_cmds; int refcount; struct mail_session *sessions; }; struct mail_domain { struct mail_domain *stable_prev, *stable_next; struct mail_domain *sorted_prev, *sorted_next; char *name; time_t reset_timestamp; struct timeval last_update; struct stats *stats; unsigned int num_logins; unsigned int num_cmds; unsigned int num_connected_sessions; int refcount; struct mail_user *users; }; struct mail_ip { struct mail_ip *stable_prev, *stable_next; struct mail_ip *sorted_prev, *sorted_next; struct ip_addr ip; time_t reset_timestamp; struct timeval last_update; struct stats *stats; unsigned int num_logins; unsigned int num_cmds; unsigned int num_connected_sessions; int refcount; struct mail_session *sessions; }; struct mail_global { time_t reset_timestamp; struct timeval last_update; struct stats *stats; unsigned int num_logins; unsigned int num_cmds; unsigned int num_connected_sessions; struct timeout *to_stats_send; struct stats_send_ctx *stats_send_ctx; }; extern struct mail_global mail_global_stats; void mail_global_init(void); void mail_global_deinit(void); void mail_global_login(void); void mail_global_disconnected(void); void mail_global_refresh(const struct stats *diff_stats); #endif dovecot-2.3.21.1/src/old-stats/stats-carbon.h0000644000000000000000000000042314656633576015566 00000000000000#ifndef STATS_CARBON #define STATS_CARBON 1 struct stats_send_ctx; int stats_carbon_send(const char *endpoint, const char *data, void (*callback)(void *), void *cb_ctx, struct stats_send_ctx **ctx_r); void stats_carbon_destroy(struct stats_send_ctx **ctx); #endif dovecot-2.3.21.1/src/old-stats/mail-command.c0000644000000000000000000001362314656633576015525 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "base64.h" #include "ioloop.h" #include "llist.h" #include "global-memory.h" #include "stats-settings.h" #include "mail-stats.h" #include "mail-session.h" #include "mail-command.h" #define MAIL_COMMAND_TIMEOUT_SECS (60*15) /* commands are sorted by their last_update timestamp, oldest first */ struct mail_command *stable_mail_commands_head; struct mail_command *stable_mail_commands_tail; static size_t mail_command_memsize(const struct mail_command *cmd) { return sizeof(*cmd) + strlen(cmd->name) + 1 + strlen(cmd->args) + 1; } static struct mail_command * mail_command_find(struct mail_session *session, unsigned int id) { struct mail_command *cmd; i_assert(id != 0); if (id > session->highest_cmd_id) { /* fast path for new commands */ return NULL; } for (cmd = session->commands; cmd != NULL; cmd = cmd->session_next) { if (cmd->id == id) return cmd; } /* expired */ return NULL; } static struct mail_command * mail_command_add(struct mail_session *session, const char *name, const char *args) { struct mail_command *cmd; cmd = i_malloc(MALLOC_ADD(sizeof(struct mail_command), stats_alloc_size())); cmd->stats = (void *)(cmd + 1); cmd->refcount = 1; /* unrefed at "done" */ cmd->session = session; cmd->name = i_strdup(name); cmd->args = i_strdup(args); cmd->last_update = ioloop_timeval; DLLIST2_APPEND_FULL(&stable_mail_commands_head, &stable_mail_commands_tail, cmd, stable_prev, stable_next); DLLIST_PREPEND_FULL(&session->commands, cmd, session_prev, session_next); mail_session_ref(cmd->session); global_memory_alloc(mail_command_memsize(cmd)); return cmd; } static void mail_command_free(struct mail_command *cmd) { i_assert(cmd->refcount == 0); global_memory_free(mail_command_memsize(cmd)); DLLIST2_REMOVE_FULL(&stable_mail_commands_head, &stable_mail_commands_tail, cmd, stable_prev, stable_next); DLLIST_REMOVE_FULL(&cmd->session->commands, cmd, session_prev, session_next); mail_session_unref(&cmd->session); i_free(cmd->name); i_free(cmd->args); i_free(cmd); } void mail_command_ref(struct mail_command *cmd) { cmd->refcount++; } void mail_command_unref(struct mail_command **_cmd) { struct mail_command *cmd = *_cmd; i_assert(cmd->refcount > 0); cmd->refcount--; *_cmd = NULL; } int mail_command_update_parse(const char *const *args, const char **error_r) { struct mail_session *session; struct mail_command *cmd; struct stats *new_stats, *diff_stats; buffer_t *buf; const char *error; unsigned int i, cmd_id; bool done = FALSE, continued = FALSE; /* [d] c[d] */ if (str_array_length(args) < 3) { *error_r = "UPDATE-CMD: Too few parameters"; return -1; } if (mail_session_get(args[0], &session, error_r) < 0) return -1; if (str_to_uint(args[1], &cmd_id) < 0 || cmd_id == 0) { *error_r = "UPDATE-CMD: Invalid command id"; return -1; } for (i = 0; args[2][i] != '\0'; i++) { switch (args[2][i]) { case 'd': done = TRUE; break; case 'c': continued = TRUE; break; default: *error_r = "UPDATE-CMD: Invalid flags parameter"; return -1; } } cmd = mail_command_find(session, cmd_id); if (!continued) { /* new command */ if (cmd != NULL) { *error_r = "UPDATE-CMD: Duplicate new command id"; return -1; } if (str_array_length(args) < 5) { *error_r = "UPDATE-CMD: Too few parameters"; return -1; } cmd = mail_command_add(session, args[3], args[4]); cmd->id = cmd_id; session->highest_cmd_id = I_MAX(session->highest_cmd_id, cmd_id); session->num_cmds++; session->user->num_cmds++; session->user->domain->num_cmds++; if (session->ip != NULL) session->ip->num_cmds++; mail_global_stats.num_cmds++; args += 5; } else { if (cmd == NULL) { /* already expired command, ignore */ i_warning("UPDATE-CMD: Already expired"); return 0; } args += 3; cmd->last_update = ioloop_timeval; } buf = t_buffer_create(256); if (args[0] == NULL || base64_decode(args[0], strlen(args[0]), NULL, buf) < 0) { *error_r = t_strdup_printf("UPDATE-CMD: Invalid base64 input"); return -1; } new_stats = stats_alloc(pool_datastack_create()); diff_stats = stats_alloc(pool_datastack_create()); if (!stats_import(buf->data, buf->used, cmd->stats, new_stats, &error)) { *error_r = t_strdup_printf("UPDATE-CMD: %s", error); return -1; } if (!stats_diff(cmd->stats, new_stats, diff_stats, &error)) { *error_r = t_strdup_printf("UPDATE-CMD: stats shrank: %s", error); return -1; } stats_add(cmd->stats, diff_stats); if (done) { cmd->id = 0; mail_command_unref(&cmd); } mail_session_refresh(session, NULL); return 0; } static bool mail_command_is_timed_out(struct mail_command *cmd) { /* some commands like IDLE can run forever */ return ioloop_time - cmd->last_update.tv_sec > MAIL_COMMAND_TIMEOUT_SECS; } void mail_commands_free_memory(void) { unsigned int diff; while (stable_mail_commands_head != NULL) { struct mail_command *cmd = stable_mail_commands_head; if (cmd->refcount == 0) i_assert(cmd->id == 0); else if (cmd->refcount == 1 && (cmd->session->disconnected || mail_command_is_timed_out(cmd))) { /* session was probably lost */ mail_command_unref(&cmd); } else { break; } mail_command_free(stable_mail_commands_head); if (global_used_memory < stats_settings->memory_limit || stable_mail_commands_head == NULL) break; diff = ioloop_time - stable_mail_commands_head->last_update.tv_sec; if (diff < stats_settings->command_min_time) break; } } void mail_commands_init(void) { } void mail_commands_deinit(void) { while (stable_mail_commands_head != NULL) { struct mail_command *cmd = stable_mail_commands_head; if (cmd->id != 0) mail_command_unref(&cmd); mail_command_free(stable_mail_commands_head); } } dovecot-2.3.21.1/src/old-stats/Makefile.in0000644000000000000000000007067514656633611015070 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ pkglibexec_PROGRAMS = old-stats$(EXEEXT) subdir = src/old-stats ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(pkglibexecdir)" PROGRAMS = $(pkglibexec_PROGRAMS) am_old_stats_OBJECTS = client.$(OBJEXT) client-export.$(OBJEXT) \ client-reset.$(OBJEXT) fifo-input-connection.$(OBJEXT) \ global-memory.$(OBJEXT) mail-command.$(OBJEXT) \ mail-domain.$(OBJEXT) mail-ip.$(OBJEXT) mail-session.$(OBJEXT) \ mail-stats.$(OBJEXT) mail-user.$(OBJEXT) main.$(OBJEXT) \ stats-carbon.$(OBJEXT) stats-settings.$(OBJEXT) old_stats_OBJECTS = $(am_old_stats_OBJECTS) am__DEPENDENCIES_1 = AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/client-export.Po \ ./$(DEPDIR)/client-reset.Po ./$(DEPDIR)/client.Po \ ./$(DEPDIR)/fifo-input-connection.Po \ ./$(DEPDIR)/global-memory.Po ./$(DEPDIR)/mail-command.Po \ ./$(DEPDIR)/mail-domain.Po ./$(DEPDIR)/mail-ip.Po \ ./$(DEPDIR)/mail-session.Po ./$(DEPDIR)/mail-stats.Po \ ./$(DEPDIR)/mail-user.Po ./$(DEPDIR)/main.Po \ ./$(DEPDIR)/stats-carbon.Po ./$(DEPDIR)/stats-settings.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(old_stats_SOURCES) DIST_SOURCES = $(old_stats_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac HEADERS = $(noinst_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) pkglibexecdir = $(libexecdir)/dovecot ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ old_stats_moduledir = $(moduledir)/old-stats AM_CPPFLAGS = \ -DSTATS_MODULE_DIR=\""$(old_stats_moduledir)"\" \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-old-stats \ $(BINARY_CFLAGS) old_stats_LDADD = $(LIBDOVECOT) \ $(BINARY_LDFLAGS) old_stats_DEPENDENCIES = $(LIBDOVECOT_DEPS) old_stats_SOURCES = \ client.c \ client-export.c \ client-reset.c \ fifo-input-connection.c \ global-memory.c \ mail-command.c \ mail-domain.c \ mail-ip.c \ mail-session.c \ mail-stats.c \ mail-user.c \ main.c \ stats-carbon.c \ stats-settings.c noinst_HEADERS = \ client.h \ client-export.h \ client-reset.h \ fifo-input-connection.h \ global-memory.h \ mail-command.h \ mail-domain.h \ mail-ip.h \ mail-session.h \ mail-stats.h \ mail-user.h \ stats-carbon.h \ stats-settings.h all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/old-stats/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/old-stats/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files clean-pkglibexecPROGRAMS: @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list old-stats$(EXEEXT): $(old_stats_OBJECTS) $(old_stats_DEPENDENCIES) $(EXTRA_old_stats_DEPENDENCIES) @rm -f old-stats$(EXEEXT) $(AM_V_CCLD)$(LINK) $(old_stats_OBJECTS) $(old_stats_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client-export.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client-reset.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fifo-input-connection.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/global-memory.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-command.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-domain.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-ip.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-session.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-stats.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-user.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stats-carbon.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stats-settings.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am check: check-am all-am: Makefile $(PROGRAMS) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkglibexecdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-pkglibexecPROGRAMS \ mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/client-export.Po -rm -f ./$(DEPDIR)/client-reset.Po -rm -f ./$(DEPDIR)/client.Po -rm -f ./$(DEPDIR)/fifo-input-connection.Po -rm -f ./$(DEPDIR)/global-memory.Po -rm -f ./$(DEPDIR)/mail-command.Po -rm -f ./$(DEPDIR)/mail-domain.Po -rm -f ./$(DEPDIR)/mail-ip.Po -rm -f ./$(DEPDIR)/mail-session.Po -rm -f ./$(DEPDIR)/mail-stats.Po -rm -f ./$(DEPDIR)/mail-user.Po -rm -f ./$(DEPDIR)/main.Po -rm -f ./$(DEPDIR)/stats-carbon.Po -rm -f ./$(DEPDIR)/stats-settings.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-pkglibexecPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/client-export.Po -rm -f ./$(DEPDIR)/client-reset.Po -rm -f ./$(DEPDIR)/client.Po -rm -f ./$(DEPDIR)/fifo-input-connection.Po -rm -f ./$(DEPDIR)/global-memory.Po -rm -f ./$(DEPDIR)/mail-command.Po -rm -f ./$(DEPDIR)/mail-domain.Po -rm -f ./$(DEPDIR)/mail-ip.Po -rm -f ./$(DEPDIR)/mail-session.Po -rm -f ./$(DEPDIR)/mail-stats.Po -rm -f ./$(DEPDIR)/mail-user.Po -rm -f ./$(DEPDIR)/main.Po -rm -f ./$(DEPDIR)/stats-carbon.Po -rm -f ./$(DEPDIR)/stats-settings.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkglibexecPROGRAMS .MAKE: install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ clean-generic clean-libtool clean-pkglibexecPROGRAMS \ cscopelist-am ctags ctags-am distclean distclean-compile \ distclean-generic distclean-libtool distclean-tags distdir dvi \ dvi-am html html-am info info-am install install-am \ install-data install-data-am install-dvi install-dvi-am \ install-exec install-exec-am install-html install-html-am \ install-info install-info-am install-man install-pdf \ install-pdf-am install-pkglibexecPROGRAMS install-ps \ install-ps-am install-strip installcheck installcheck-am \ installdirs maintainer-clean maintainer-clean-generic \ mostlyclean mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ uninstall-am uninstall-pkglibexecPROGRAMS .PRECIOUS: Makefile # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/old-stats/fifo-input-connection.c0000644000000000000000000000510314656633576017376 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "llist.h" #include "strescape.h" #include "istream.h" #include "ostream.h" #include "master-service.h" #include "mail-session.h" #include "mail-user.h" #include "mail-command.h" #include "fifo-input-connection.h" #include #define MAX_INBUF_SIZE (PIPE_BUF*2) struct fifo_input_connection { struct fifo_input_connection *prev, *next; int fd; struct istream *input; struct io *io; }; static struct fifo_input_connection *fifo_conns = NULL; static int fifo_input_connection_request(const char *const *args, const char **error_r) { const char *cmd = args[0]; if (cmd == NULL) { *error_r = "Missing command"; return -1; } args++; if (strcmp(cmd, "CONNECT") == 0) return mail_session_connect_parse(args, error_r); if (strcmp(cmd, "DISCONNECT") == 0) return mail_session_disconnect_parse(args, error_r); if (strcmp(cmd, "UPDATE-SESSION") == 0) return mail_session_update_parse(args, error_r); if (strcmp(cmd, "ADD-USER") == 0) return mail_user_add_parse(args, error_r); if (strcmp(cmd, "UPDATE-CMD") == 0) return mail_command_update_parse(args, error_r); *error_r = "Unknown command"; return -1; } static void fifo_input_connection_input(struct fifo_input_connection *conn) { const char *line, *const *args, *error; switch (i_stream_read(conn->input)) { case -2: i_error("BUG: Mail server sent too much data"); fifo_input_connection_destroy(&conn); return; case -1: fifo_input_connection_destroy(&conn); return; } while ((line = i_stream_next_line(conn->input)) != NULL) T_BEGIN { args = t_strsplit_tabescaped(line); if (fifo_input_connection_request(args, &error) < 0) i_error("FIFO input error: %s", error); } T_END; } struct fifo_input_connection *fifo_input_connection_create(int fd) { struct fifo_input_connection *conn; conn = i_new(struct fifo_input_connection, 1); conn->fd = fd; conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE); conn->io = io_add(fd, IO_READ, fifo_input_connection_input, conn); DLLIST_PREPEND(&fifo_conns, conn); return conn; } void fifo_input_connection_destroy(struct fifo_input_connection **_conn) { struct fifo_input_connection *conn = *_conn; *_conn = NULL; DLLIST_REMOVE(&fifo_conns, conn); io_remove(&conn->io); i_stream_destroy(&conn->input); if (close(conn->fd) < 0) i_error("close(conn) failed: %m"); i_free(conn); } void fifo_input_connections_destroy_all(void) { while (fifo_conns != NULL) { struct fifo_input_connection *conn = fifo_conns; fifo_input_connection_destroy(&conn); } } dovecot-2.3.21.1/src/old-stats/mail-user.h0000644000000000000000000000117614656633576015072 00000000000000#ifndef MAIL_USER_H #define MAIL_USER_H struct stats; extern struct mail_user *stable_mail_users; struct mail_user *mail_user_login(const char *username); void mail_user_disconnected(struct mail_user *user); struct mail_user *mail_user_lookup(const char *username); void mail_user_refresh(struct mail_user *user, const struct stats *diff_stats) ATTR_NULL(2); int mail_user_add_parse(const char *const *args, const char **error_r); void mail_user_ref(struct mail_user *user); void mail_user_unref(struct mail_user **user); void mail_users_free_memory(void); void mail_users_init(void); void mail_users_deinit(void); #endif dovecot-2.3.21.1/src/old-stats/client-reset.h0000644000000000000000000000025014656633576015562 00000000000000#ifndef CLIENT_RESET_H #define CLIENT_RESET_H struct client; int client_stats_reset(struct client *client, const char *const *args, const char **error_r); #endif dovecot-2.3.21.1/src/old-stats/mail-ip.h0000644000000000000000000000100214656633576014510 00000000000000#ifndef MAIL_IP_H #define MAIL_IP_H extern struct mail_ip *stable_mail_ips; struct mail_ip *mail_ip_login(const struct ip_addr *ip_addr); void mail_ip_disconnected(struct mail_ip *ip); struct mail_ip *mail_ip_lookup(const struct ip_addr *ip_addr); void mail_ip_refresh(struct mail_ip *ip, const struct stats *diff_stats) ATTR_NULL(2); void mail_ip_ref(struct mail_ip *ip); void mail_ip_unref(struct mail_ip **ip); void mail_ips_free_memory(void); void mail_ips_init(void); void mail_ips_deinit(void); #endif dovecot-2.3.21.1/src/old-stats/mail-ip.c0000644000000000000000000000603314656633576014514 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "hash.h" #include "llist.h" #include "global-memory.h" #include "stats-settings.h" #include "mail-stats.h" #include "mail-ip.h" static HASH_TABLE(struct ip_addr *, struct mail_ip *) mail_ips_hash; /* ips are sorted by their last_update timestamp, oldest first */ static struct mail_ip *mail_ips_head, *mail_ips_tail; struct mail_ip *stable_mail_ips; static size_t mail_ip_memsize(const struct mail_ip *ip) { return sizeof(*ip); } struct mail_ip *mail_ip_login(const struct ip_addr *ip_addr) { struct mail_ip *ip; ip = hash_table_lookup(mail_ips_hash, ip_addr); if (ip != NULL) { ip->num_logins++; ip->num_connected_sessions++; mail_ip_refresh(ip, NULL); return ip; } ip = i_malloc(MALLOC_ADD(sizeof(struct mail_ip), stats_alloc_size())); ip->stats = (void *)(ip + 1); ip->ip = *ip_addr; ip->reset_timestamp = ioloop_time; hash_table_insert(mail_ips_hash, &ip->ip, ip); DLLIST_PREPEND_FULL(&stable_mail_ips, ip, stable_prev, stable_next); DLLIST2_APPEND_FULL(&mail_ips_head, &mail_ips_tail, ip, sorted_prev, sorted_next); ip->num_logins++; ip->num_connected_sessions++; ip->last_update = ioloop_timeval; global_memory_alloc(mail_ip_memsize(ip)); return ip; } void mail_ip_disconnected(struct mail_ip *ip) { i_assert(ip->num_connected_sessions > 0); ip->num_connected_sessions--; } struct mail_ip *mail_ip_lookup(const struct ip_addr *ip_addr) { return hash_table_lookup(mail_ips_hash, ip_addr); } void mail_ip_ref(struct mail_ip *ip) { ip->refcount++; } void mail_ip_unref(struct mail_ip **_ip) { struct mail_ip *ip = *_ip; i_assert(ip->refcount > 0); ip->refcount--; *_ip = NULL; } static void mail_ip_free(struct mail_ip *ip) { i_assert(ip->refcount == 0); i_assert(ip->sessions == NULL); global_memory_free(mail_ip_memsize(ip)); hash_table_remove(mail_ips_hash, &ip->ip); DLLIST_REMOVE_FULL(&stable_mail_ips, ip, stable_prev, stable_next); DLLIST2_REMOVE_FULL(&mail_ips_head, &mail_ips_tail, ip, sorted_prev, sorted_next); i_free(ip); } void mail_ip_refresh(struct mail_ip *ip, const struct stats *diff_stats) { if (diff_stats != NULL) stats_add(ip->stats, diff_stats); ip->last_update = ioloop_timeval; DLLIST2_REMOVE_FULL(&mail_ips_head, &mail_ips_tail, ip, sorted_prev, sorted_next); DLLIST2_APPEND_FULL(&mail_ips_head, &mail_ips_tail, ip, sorted_prev, sorted_next); } void mail_ips_free_memory(void) { unsigned int diff; while (mail_ips_head != NULL && mail_ips_head->refcount == 0) { mail_ip_free(mail_ips_head); if (global_used_memory < stats_settings->memory_limit || mail_ips_head == NULL) break; diff = ioloop_time - mail_ips_head->last_update.tv_sec; if (diff < stats_settings->ip_min_time) break; } } void mail_ips_init(void) { hash_table_create(&mail_ips_hash, default_pool, 0, net_ip_hash, net_ip_cmp); } void mail_ips_deinit(void) { while (mail_ips_head != NULL) mail_ip_free(mail_ips_head); hash_table_destroy(&mail_ips_hash); } dovecot-2.3.21.1/src/old-stats/client.h0000644000000000000000000000150614656633576014447 00000000000000#ifndef CLIENT_H #define CLIENT_H struct client { struct client *prev, *next; int fd; struct io *io; struct istream *input; struct ostream *output; struct timeout *to_pending; pool_t cmd_pool; struct client_export_cmd *cmd_export; int (*cmd_more)(struct client *client); /* command iterators. while non-NULL, they've increased the struct's refcount so it won't be deleted during iteration */ unsigned int iter_count; struct mail_command *mail_cmd_iter; struct mail_session *mail_session_iter; struct mail_user *mail_user_iter; struct mail_domain *mail_domain_iter; struct mail_ip *mail_ip_iter; }; struct client *client_create(int fd); void client_destroy(struct client **client); bool client_is_busy(struct client *client); void client_enable_io(struct client *client); void clients_destroy_all(void); #endif dovecot-2.3.21.1/src/old-stats/mail-domain.h0000644000000000000000000000122114656633576015352 00000000000000#ifndef MAIL_DOMAIN_H #define MAIL_DOMAIN_H struct stats; extern struct mail_domain *stable_mail_domains; struct mail_domain *mail_domain_login_create(const char *name); void mail_domain_login(struct mail_domain *domain); void mail_domain_disconnected(struct mail_domain *domain); struct mail_domain *mail_domain_lookup(const char *name); void mail_domain_refresh(struct mail_domain *domain, const struct stats *diff_stats) ATTR_NULL(2); void mail_domain_ref(struct mail_domain *domain); void mail_domain_unref(struct mail_domain **domain); void mail_domains_free_memory(void); void mail_domains_init(void); void mail_domains_deinit(void); #endif dovecot-2.3.21.1/src/old-stats/mail-session.c0000644000000000000000000002357514656633576015601 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "base64.h" #include "ioloop.h" #include "hash.h" #include "llist.h" #include "str-table.h" #include "global-memory.h" #include "stats-settings.h" #include "mail-stats.h" #include "mail-user.h" #include "mail-ip.h" #include "mail-session.h" #include "mail-domain.h" /* If session doesn't receive any updates for this long, assume that the process associated with it has crashed, and forcibly disconnect the session. Must be larger than SESSION_STATS_FORCE_REFRESH_SECS in stats plugin */ #define MAIL_SESSION_IDLE_TIMEOUT_MSECS (1000*60*15) /* If stats process crashes/restarts, existing processes keep sending status updates to it, but this process doesn't know their session IDs. If these missing IDs are found within this many seconds of starting the stats process, don't log a warning about them. (On a larger installation this avoids flooding the error log with hundreds of warnings.) */ #define SESSION_ID_WARN_HIDE_SECS (60*5) static HASH_TABLE(char *, struct mail_session *) mail_sessions_hash; /* sessions are sorted by their last_update timestamp, oldest first */ static struct mail_session *mail_sessions_head, *mail_sessions_tail; static time_t session_id_warn_hide_until; static bool session_id_hide_warned = FALSE; static struct str_table *services; struct mail_session *stable_mail_sessions; static size_t mail_session_memsize(const struct mail_session *session) { return sizeof(*session) + strlen(session->id) + 1; } static void mail_session_disconnect(struct mail_session *session) { i_assert(!session->disconnected); mail_user_disconnected(session->user); if (session->ip != NULL) mail_ip_disconnected(session->ip); hash_table_remove(mail_sessions_hash, session->id); session->disconnected = TRUE; timeout_remove(&session->to_idle); mail_session_unref(&session); } static void mail_session_idle_timeout(struct mail_session *session) { /* user="" service="" pid=0 is used for incoming sessions that were received after we detected a stats process crash/restart. there's no point in logging anything about them, since they contain no useful information. */ if (session->user->name[0] == '\0' && session->service[0] != '\0' && session->pid == 0) { i_warning("Session %s (user %s, service %s) " "appears to have crashed, disconnecting it", session->id, session->user->name, session->service); } mail_session_disconnect(session); } int mail_session_connect_parse(const char *const *args, const char **error_r) { struct mail_session *session; const char *session_id; pid_t pid; struct ip_addr ip; unsigned int i; /* [key=value ..] */ if (str_array_length(args) < 4) { *error_r = "CONNECT: Too few parameters"; return -1; } session_id = args[0]; if (str_to_pid(args[3], &pid) < 0) { *error_r = t_strdup_printf("CONNECT: Invalid pid %s for session ID %s", args[3], session_id); return -1; } session = hash_table_lookup(mail_sessions_hash, session_id); if (session != NULL) { *error_r = t_strdup_printf( "CONNECT: Duplicate session ID %s for user %s service %s (old PID %ld, new PID %ld)", session_id, args[1], args[2], (long)session->pid, (long)pid); return -1; } session = i_malloc(MALLOC_ADD(sizeof(struct mail_session), stats_alloc_size())); session->stats = (void *)(session + 1); session->refcount = 1; /* unrefed at disconnect */ session->id = i_strdup(session_id); session->service = str_table_ref(services, args[2]); session->pid = pid; session->last_update = ioloop_timeval; session->to_idle = timeout_add(MAIL_SESSION_IDLE_TIMEOUT_MSECS, mail_session_idle_timeout, session); session->user = mail_user_login(args[1]); session->user->num_logins++; mail_domain_login(session->user->domain); for (i = 3; args[i] != NULL; i++) { if (str_begins(args[i], "rip=") && net_addr2ip(args[i] + 4, &ip) == 0) session->ip = mail_ip_login(&ip); } hash_table_insert(mail_sessions_hash, session->id, session); DLLIST_PREPEND_FULL(&stable_mail_sessions, session, stable_prev, stable_next); DLLIST2_APPEND_FULL(&mail_sessions_head, &mail_sessions_tail, session, sorted_prev, sorted_next); DLLIST_PREPEND_FULL(&session->user->sessions, session, user_prev, user_next); mail_user_ref(session->user); if (session->ip != NULL) { DLLIST_PREPEND_FULL(&session->ip->sessions, session, ip_prev, ip_next); mail_ip_ref(session->ip); } global_memory_alloc(mail_session_memsize(session)); mail_global_login(); return 0; } void mail_session_ref(struct mail_session *session) { session->refcount++; } void mail_session_unref(struct mail_session **_session) { struct mail_session *session = *_session; i_assert(session->refcount > 0); session->refcount--; *_session = NULL; } static void mail_session_free(struct mail_session *session) { i_assert(session->refcount == 0); global_memory_free(mail_session_memsize(session)); timeout_remove(&session->to_idle); if (!session->disconnected) hash_table_remove(mail_sessions_hash, session->id); DLLIST_REMOVE_FULL(&stable_mail_sessions, session, stable_prev, stable_next); DLLIST2_REMOVE_FULL(&mail_sessions_head, &mail_sessions_tail, session, sorted_prev, sorted_next); DLLIST_REMOVE_FULL(&session->user->sessions, session, user_prev, user_next); mail_user_unref(&session->user); if (session->ip != NULL) { DLLIST_REMOVE_FULL(&session->ip->sessions, session, ip_prev, ip_next); mail_ip_unref(&session->ip); } str_table_unref(services, &session->service); i_free(session->id); i_free(session); } static void mail_session_id_lost(const char *session_id) { if (ioloop_time < session_id_warn_hide_until) { if (session_id_hide_warned) return; session_id_hide_warned = TRUE; i_warning("stats process appears to have crashed/restarted, " "hiding missing session ID warnings for %d seconds", (int)(session_id_warn_hide_until - ioloop_time)); return; } i_warning("Couldn't find session ID: %s", session_id); } int mail_session_lookup(const char *id, struct mail_session **session_r, const char **error_r) { if (id == NULL) { *error_r = "Too few parameters"; return -1; } *session_r = hash_table_lookup(mail_sessions_hash, id); if (*session_r == NULL) { mail_session_id_lost(id); return 0; } return 1; } int mail_session_get(const char *id, struct mail_session **session_r, const char **error_r) { const char *new_args[5]; int ret; if ((ret = mail_session_lookup(id, session_r, error_r)) != 0) return ret; /* Create a new dummy session to avoid repeated warnings */ new_args[0] = id; new_args[1] = ""; /* username */ new_args[2] = ""; /* service */ new_args[3] = "0"; /* pid */ new_args[4] = NULL; if (mail_session_connect_parse(new_args, error_r) < 0) i_unreached(); if (mail_session_lookup(id, session_r, error_r) != 1) i_unreached(); return 0; } int mail_session_disconnect_parse(const char *const *args, const char **error_r) { struct mail_session *session; int ret; /* */ if ((ret = mail_session_lookup(args[0], &session, error_r)) <= 0) return ret; if (!session->disconnected) mail_session_disconnect(session); return 0; } void mail_session_refresh(struct mail_session *session, const struct stats *diff_stats) { timeout_reset(session->to_idle); if (diff_stats != NULL) stats_add(session->stats, diff_stats); session->last_update = ioloop_timeval; DLLIST2_REMOVE_FULL(&mail_sessions_head, &mail_sessions_tail, session, sorted_prev, sorted_next); DLLIST2_APPEND_FULL(&mail_sessions_head, &mail_sessions_tail, session, sorted_prev, sorted_next); mail_user_refresh(session->user, diff_stats); if (session->ip != NULL) mail_ip_refresh(session->ip, diff_stats); } int mail_session_update_parse(const char *const *args, const char **error_r) { struct mail_session *session; struct stats *new_stats, *diff_stats; buffer_t *buf; const char *error; /* */ if (mail_session_get(args[0], &session, error_r) < 0) return -1; buf = t_buffer_create(256); if (args[1] == NULL || base64_decode(args[1], strlen(args[1]), NULL, buf) < 0) { *error_r = t_strdup_printf("UPDATE-SESSION %s %s %s: Invalid base64 input", session->user->name, session->service, session->id); return -1; } new_stats = stats_alloc(pool_datastack_create()); diff_stats = stats_alloc(pool_datastack_create()); if (!stats_import(buf->data, buf->used, session->stats, new_stats, &error)) { *error_r = t_strdup_printf("UPDATE-SESSION %s %s %s: %s", session->user->name, session->service, session->id, error); return -1; } if (!stats_diff(session->stats, new_stats, diff_stats, &error)) { *error_r = t_strdup_printf("UPDATE-SESSION %s %s %s: stats shrank: %s", session->user->name, session->service, session->id, error); return -1; } mail_session_refresh(session, diff_stats); return 0; } void mail_sessions_free_memory(void) { unsigned int diff; while (mail_sessions_head != NULL && mail_sessions_head->refcount == 0) { i_assert(mail_sessions_head->disconnected); mail_session_free(mail_sessions_head); if (global_used_memory < stats_settings->memory_limit || mail_sessions_head == NULL) break; diff = ioloop_time - mail_sessions_head->last_update.tv_sec; if (diff < stats_settings->session_min_time) break; } } void mail_sessions_init(void) { session_id_warn_hide_until = ioloop_time + SESSION_ID_WARN_HIDE_SECS; hash_table_create(&mail_sessions_hash, default_pool, 0, str_hash, strcmp); services = str_table_init(); } void mail_sessions_deinit(void) { while (mail_sessions_head != NULL) { struct mail_session *session = mail_sessions_head; if (!session->disconnected) mail_session_unref(&session); mail_session_free(mail_sessions_head); } hash_table_destroy(&mail_sessions_hash); str_table_deinit(&services); } dovecot-2.3.21.1/src/old-stats/client-export.h0000644000000000000000000000024614656633576015766 00000000000000#ifndef CLIENT_EXPORT_H #define CLIENT_EXPORT_H struct client; int client_export(struct client *client, const char *const *args, const char **error_r); #endif dovecot-2.3.21.1/src/old-stats/stats-settings.c0000644000000000000000000000546514656633576016170 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "settings-parser.h" #include "service-settings.h" #include "stats-settings.h" /* */ static struct file_listener_settings old_stats_unix_listeners_array[] = { { "old-stats", 0600, "", "" } }; static struct file_listener_settings *old_stats_unix_listeners[] = { &old_stats_unix_listeners_array[0] }; static buffer_t old_stats_unix_listeners_buf = { { { old_stats_unix_listeners, sizeof(old_stats_unix_listeners) } } }; static struct file_listener_settings old_stats_fifo_listeners_array[] = { { "old-stats-mail", 0600, "", "" }, { "old-stats-user", 0600, "", "" } }; static struct file_listener_settings *old_stats_fifo_listeners[] = { &old_stats_fifo_listeners_array[0], &old_stats_fifo_listeners_array[1] }; static buffer_t old_stats_fifo_listeners_buf = { { { old_stats_fifo_listeners, sizeof(old_stats_fifo_listeners) } } }; /* */ struct service_settings old_stats_service_settings = { .name = "old-stats", .protocol = "", .type = "", .executable = "old-stats", .user = "$default_internal_user", .group = "", .privileged_group = "", .extra_groups = "", .chroot = "empty", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 1, .client_limit = 0, .service_count = 0, .idle_kill = UINT_MAX, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &old_stats_unix_listeners_buf, sizeof(old_stats_unix_listeners[0]) } }, .fifo_listeners = { { &old_stats_fifo_listeners_buf, sizeof(old_stats_fifo_listeners[0]) } }, .inet_listeners = ARRAY_INIT, .process_limit_1 = TRUE }; /* we're kind of kludging here to avoid "stats_" prefix in the struct fields */ #undef DEF #define DEF(type, name) \ SETTING_DEFINE_STRUCT_##type("old_stats_"#name, name, struct old_stats_settings) static const struct setting_define old_stats_setting_defines[] = { DEF(SIZE, memory_limit), DEF(TIME, command_min_time), DEF(TIME, session_min_time), DEF(TIME, user_min_time), DEF(TIME, domain_min_time), DEF(TIME, ip_min_time), DEF(STR, carbon_server), DEF(TIME, carbon_interval), DEF(STR, carbon_name), SETTING_DEFINE_LIST_END }; const struct old_stats_settings old_stats_default_settings = { .memory_limit = 1024*1024*16, .command_min_time = 60, .session_min_time = 60*15, .user_min_time = 60*60, .domain_min_time = 60*60*12, .ip_min_time = 60*60*12, .carbon_interval = 30, .carbon_server = "", .carbon_name = "" }; const struct setting_parser_info old_stats_setting_parser_info = { .module_name = "stats", .defines = old_stats_setting_defines, .defaults = &old_stats_default_settings, .type_offset = SIZE_MAX, .struct_size = sizeof(struct old_stats_settings), .parent_offset = SIZE_MAX }; const struct old_stats_settings *stats_settings; dovecot-2.3.21.1/src/old-stats/client-reset.c0000644000000000000000000000105614656633576015562 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ostream.h" #include "strescape.h" #include "mail-stats.h" #include "client.h" #include "client-reset.h" int client_stats_reset(struct client *client, const char *const *args ATTR_UNUSED, const char **error_r ATTR_UNUSED) { struct mail_global *g = &mail_global_stats; stats_reset(g->stats); g->num_logins = 0; g->num_cmds = 0; g->reset_timestamp = ioloop_time; i_zero(&g->last_update); o_stream_nsend_str(client->output, "OK\n"); return 0; } dovecot-2.3.21.1/src/old-stats/fifo-input-connection.h0000644000000000000000000000040614656633576017404 00000000000000#ifndef FIFO_INPUT_CONNECTION_H #define FIFO_INPUT_CONNECTION_H struct fifo_input_connection *fifo_input_connection_create(int fd); void fifo_input_connection_destroy(struct fifo_input_connection **conn); void fifo_input_connections_destroy_all(void); #endif dovecot-2.3.21.1/src/old-stats/global-memory.h0000644000000000000000000000025114656633576015733 00000000000000#ifndef GLOBAL_MEMORY_H #define GLOBAL_MEMORY_H extern size_t global_used_memory; void global_memory_alloc(size_t size); void global_memory_free(size_t size); #endif dovecot-2.3.21.1/src/old-stats/mail-session.h0000644000000000000000000000173314656633576015576 00000000000000#ifndef MAIL_SESSION_H #define MAIL_SESSION_H struct stats; struct mail_session; extern struct mail_session *stable_mail_sessions; int mail_session_connect_parse(const char *const *args, const char **error_r); int mail_session_disconnect_parse(const char *const *args, const char **error_r); int mail_session_update_parse(const char *const *args, const char **error_r); int mail_session_cmd_update_parse(const char *const *args, const char **error_r); void mail_session_ref(struct mail_session *session); void mail_session_unref(struct mail_session **session); int mail_session_lookup(const char *guid, struct mail_session **session_r, const char **error_r); int mail_session_get(const char *guid, struct mail_session **session_r, const char **error_r); void mail_session_refresh(struct mail_session *session, const struct stats *diff_stats) ATTR_NULL(2); void mail_sessions_free_memory(void); void mail_sessions_init(void); void mail_sessions_deinit(void); #endif dovecot-2.3.21.1/src/old-stats/stats-carbon.c0000644000000000000000000000551314656633576015566 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "time-util.h" #include "stats-settings.h" #include "mail-stats.h" #include "istream.h" #include "ostream.h" #include "net.h" #include "str.h" #include "write-full.h" #include "stats-carbon.h" #define CARBON_SERVER_DEFAULT_PORT 2003 struct stats_send_ctx { pool_t pool; int fd; unsigned long to_msecs; const char *endpoint; const char *str; struct io *io; struct timeout *to; void (*callback)(void *); void *ctx; }; void stats_carbon_destroy(struct stats_send_ctx **_ctx) { struct stats_send_ctx *ctx = *_ctx; *_ctx = NULL; io_remove(&ctx->io); timeout_remove(&ctx->to); i_close_fd(&ctx->fd); pool_unref(&ctx->pool); } static void stats_carbon_callback(struct stats_send_ctx *ctx) { i_assert(ctx->callback != NULL); void (*callback)(void *) = ctx->callback; ctx->callback = NULL; callback(ctx->ctx); } static void stats_carbon_timeout(struct stats_send_ctx *ctx) { i_error("Stats submit(%s) failed: endpoint timeout after %lu msecs", ctx->endpoint, ctx->to_msecs); stats_carbon_callback(ctx); } static void stats_carbon_connected(struct stats_send_ctx *ctx) { io_remove(&ctx->io); if ((errno = net_geterror(ctx->fd)) != 0) { i_error("connect(%s) failed: %m", ctx->endpoint); stats_carbon_callback(ctx); return; } if (write_full(ctx->fd, ctx->str, strlen(ctx->str)) < 0) i_error("write(%s) failed: %m", ctx->endpoint); stats_carbon_callback(ctx); } int stats_carbon_send(const char *endpoint, const char *data, void (*callback)(void *), void *cb_ctx, struct stats_send_ctx **ctx_r) { const char *host; in_port_t port; struct ip_addr ip; if (net_str2hostport(endpoint, CARBON_SERVER_DEFAULT_PORT, &host, &port) < 0 || net_addr2ip(host, &ip) < 0) { i_error("stats_submit: Cannot parse endpoint '%s'", endpoint); return -1; } pool_t pool = pool_alloconly_create("stats carbon send", 1024); struct stats_send_ctx *ctx = p_new(pool, struct stats_send_ctx, 1); ctx->pool = pool; ctx->str = p_strdup(ctx->pool, data); ctx->fd = net_connect_ip(&ip, port, NULL); if (ctx->fd < 0) { i_error("connect(%s) failed: %m", endpoint); stats_carbon_callback(ctx); return -1; } ctx->io = io_add(ctx->fd, IO_WRITE, stats_carbon_connected, ctx); /* give time for almost until next update this is to ensure we leave a little pause between attempts. Multiplier 800 gives us 20% window, and ensures the number stays positive. */ ctx->to_msecs = stats_settings->carbon_interval*800; ctx->to = timeout_add(ctx->to_msecs, stats_carbon_timeout, ctx); if (net_ipport2str(&ip, port, &host) < 0) i_unreached(); ctx->endpoint = p_strdup(ctx->pool, host); ctx->callback = callback; ctx->ctx = cb_ctx; *ctx_r = ctx; return 0; } dovecot-2.3.21.1/src/old-stats/mail-user.c0000644000000000000000000001102214656633576015054 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "ioloop.h" #include "hash.h" #include "llist.h" #include "base64.h" #include "global-memory.h" #include "stats-settings.h" #include "mail-stats.h" #include "mail-domain.h" #include "mail-user.h" static HASH_TABLE(char *, struct mail_user *) mail_users_hash; /* users are sorted by their last_update timestamp, oldest first */ static struct mail_user *mail_users_head, *mail_users_tail; struct mail_user *stable_mail_users; static size_t mail_user_memsize(const struct mail_user *user) { return sizeof(*user) + strlen(user->name) + 1; } struct mail_user *mail_user_login(const char *username) { struct mail_user *user; const char *domain; user = hash_table_lookup(mail_users_hash, username); if (user != NULL) { mail_user_refresh(user, NULL); return user; } domain = i_strchr_to_next(username, '@'); if (domain == NULL) domain = ""; user = i_malloc(MALLOC_ADD(sizeof(struct mail_user), stats_alloc_size())); user->stats = (void *)(user + 1); user->name = i_strdup(username); user->reset_timestamp = ioloop_time; user->domain = mail_domain_login_create(domain); hash_table_insert(mail_users_hash, user->name, user); DLLIST_PREPEND_FULL(&stable_mail_users, user, stable_prev, stable_next); DLLIST2_APPEND_FULL(&mail_users_head, &mail_users_tail, user, sorted_prev, sorted_next); DLLIST_PREPEND_FULL(&user->domain->users, user, domain_prev, domain_next); mail_domain_ref(user->domain); user->last_update = ioloop_timeval; global_memory_alloc(mail_user_memsize(user)); return user; } void mail_user_disconnected(struct mail_user *user) { mail_domain_disconnected(user->domain); } struct mail_user *mail_user_lookup(const char *username) { return hash_table_lookup(mail_users_hash, username); } void mail_user_ref(struct mail_user *user) { user->refcount++; } void mail_user_unref(struct mail_user **_user) { struct mail_user *user = *_user; i_assert(user->refcount > 0); user->refcount--; *_user = NULL; } static void mail_user_free(struct mail_user *user) { i_assert(user->refcount == 0); i_assert(user->sessions == NULL); global_memory_free(mail_user_memsize(user)); hash_table_remove(mail_users_hash, user->name); DLLIST_REMOVE_FULL(&stable_mail_users, user, stable_prev, stable_next); DLLIST2_REMOVE_FULL(&mail_users_head, &mail_users_tail, user, sorted_prev, sorted_next); DLLIST_REMOVE_FULL(&user->domain->users, user, domain_prev, domain_next); mail_domain_unref(&user->domain); i_free(user->name); i_free(user); } void mail_user_refresh(struct mail_user *user, const struct stats *diff_stats) { if (diff_stats != NULL) stats_add(user->stats, diff_stats); user->last_update = ioloop_timeval; DLLIST2_REMOVE_FULL(&mail_users_head, &mail_users_tail, user, sorted_prev, sorted_next); DLLIST2_APPEND_FULL(&mail_users_head, &mail_users_tail, user, sorted_prev, sorted_next); mail_domain_refresh(user->domain, diff_stats); } int mail_user_add_parse(const char *const *args, const char **error_r) { struct mail_user *user; struct stats *empty_stats, *diff_stats; buffer_t *buf; const char *service, *error; /* */ if (str_array_length(args) < 3) { *error_r = "ADD-USER: Too few parameters"; return -1; } user = mail_user_login(args[0]); service = args[1]; buf = t_buffer_create(256); if (base64_decode(args[2], strlen(args[2]), NULL, buf) < 0) { *error_r = t_strdup_printf("ADD-USER %s %s: Invalid base64 input", user->name, service); return -1; } empty_stats = stats_alloc(pool_datastack_create()); diff_stats = stats_alloc(pool_datastack_create()); if (!stats_import(buf->data, buf->used, empty_stats, diff_stats, &error)) { *error_r = t_strdup_printf("ADD-USER %s %s: %s", user->name, service, error); return -1; } mail_user_refresh(user, diff_stats); return 0; } void mail_users_free_memory(void) { unsigned int diff; while (mail_users_head != NULL && mail_users_head->refcount == 0) { mail_user_free(mail_users_head); if (global_used_memory < stats_settings->memory_limit || mail_users_head == NULL) break; diff = ioloop_time - mail_users_head->last_update.tv_sec; if (diff < stats_settings->user_min_time) break; } } void mail_users_init(void) { hash_table_create(&mail_users_hash, default_pool, 0, str_hash, strcmp); } void mail_users_deinit(void) { while (mail_users_head != NULL) mail_user_free(mail_users_head); hash_table_destroy(&mail_users_hash); } dovecot-2.3.21.1/src/old-stats/main.c0000644000000000000000000000461114656633576014110 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "module-dir.h" #include "restrict-access.h" #include "master-service.h" #include "master-service-settings.h" #include "global-memory.h" #include "stats-settings.h" #include "fifo-input-connection.h" #include "mail-command.h" #include "mail-session.h" #include "mail-user.h" #include "mail-domain.h" #include "mail-ip.h" #include "mail-stats.h" #include "client.h" static struct module *modules = NULL; static void client_connected(struct master_service_connection *conn) { if (conn->fifo) (void)fifo_input_connection_create(conn->fd); else (void)client_create(conn->fd); master_service_client_connection_accept(conn); } static void main_preinit(void) { struct module_dir_load_settings mod_set; i_zero(&mod_set); mod_set.abi_version = DOVECOT_ABI_VERSION; mod_set.require_init_funcs = TRUE; modules = module_dir_load(STATS_MODULE_DIR, NULL, &mod_set); module_dir_init(modules); restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL); restrict_access_allow_coredumps(TRUE); } int main(int argc, char *argv[]) { const struct setting_parser_info *set_roots[] = { &old_stats_setting_parser_info, NULL }; const enum master_service_flags service_flags = MASTER_SERVICE_FLAG_DONT_SEND_STATS | MASTER_SERVICE_FLAG_NO_IDLE_DIE | MASTER_SERVICE_FLAG_UPDATE_PROCTITLE; const char *error; void **sets; master_service = master_service_init("stats", service_flags, &argc, &argv, ""); if (master_getopt(master_service) > 0) return FATAL_DEFAULT; if (master_service_settings_read_simple(master_service, set_roots, &error) < 0) i_fatal("Error reading configuration: %s", error); master_service_init_log(master_service); main_preinit(); sets = master_service_settings_get_others(master_service); stats_settings = sets[0]; mail_commands_init(); mail_sessions_init(); mail_users_init(); mail_domains_init(); mail_ips_init(); mail_global_init(); master_service_init_finish(master_service); master_service_run(master_service, client_connected); clients_destroy_all(); fifo_input_connections_destroy_all(); mail_commands_deinit(); mail_sessions_deinit(); mail_users_deinit(); mail_domains_deinit(); mail_ips_deinit(); mail_global_deinit(); module_dir_unload(&modules); i_assert(global_used_memory == 0); master_service_deinit(&master_service); return 0; } dovecot-2.3.21.1/src/old-stats/global-memory.c0000644000000000000000000000221514656633576015730 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "mail-command.h" #include "mail-session.h" #include "mail-user.h" #include "mail-domain.h" #include "mail-ip.h" #include "stats-settings.h" #include "global-memory.h" size_t global_used_memory = 0; static bool global_memory_free_something(void) { size_t orig_used_memory = global_used_memory; mail_commands_free_memory(); if (global_used_memory > stats_settings->memory_limit) mail_sessions_free_memory(); if (global_used_memory > stats_settings->memory_limit) mail_users_free_memory(); if (global_used_memory > stats_settings->memory_limit) mail_ips_free_memory(); if (global_used_memory > stats_settings->memory_limit) mail_domains_free_memory(); return global_used_memory < orig_used_memory; } void global_memory_alloc(size_t size) { i_assert(size < SIZE_MAX - global_used_memory); global_used_memory += size; while (global_used_memory > stats_settings->memory_limit) { if (!global_memory_free_something()) break; } } void global_memory_free(size_t size) { i_assert(size <= global_used_memory); global_used_memory -= size; } dovecot-2.3.21.1/src/old-stats/Makefile.am0000644000000000000000000000166014656633576015055 00000000000000old_stats_moduledir = $(moduledir)/old-stats pkglibexecdir = $(libexecdir)/dovecot pkglibexec_PROGRAMS = old-stats AM_CPPFLAGS = \ -DSTATS_MODULE_DIR=\""$(old_stats_moduledir)"\" \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-old-stats \ $(BINARY_CFLAGS) old_stats_LDADD = $(LIBDOVECOT) \ $(BINARY_LDFLAGS) old_stats_DEPENDENCIES = $(LIBDOVECOT_DEPS) old_stats_SOURCES = \ client.c \ client-export.c \ client-reset.c \ fifo-input-connection.c \ global-memory.c \ mail-command.c \ mail-domain.c \ mail-ip.c \ mail-session.c \ mail-stats.c \ mail-user.c \ main.c \ stats-carbon.c \ stats-settings.c noinst_HEADERS = \ client.h \ client-export.h \ client-reset.h \ fifo-input-connection.h \ global-memory.h \ mail-command.h \ mail-domain.h \ mail-ip.h \ mail-session.h \ mail-stats.h \ mail-user.h \ stats-carbon.h \ stats-settings.h dovecot-2.3.21.1/src/old-stats/mail-stats.c0000644000000000000000000000477214656633576015252 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "mail-stats.h" #include "stats-carbon.h" #include "stats-settings.h" #include "str.h" struct mail_global mail_global_stats; static void mail_global_stats_sent(void *ctx) { struct mail_global *stats = ctx; stats_carbon_destroy(&stats->stats_send_ctx); } static void mail_global_stats_send(void *u0 ATTR_UNUSED) { time_t ts = ioloop_time; if (*stats_settings->carbon_name != '\0' && *stats_settings->carbon_server != '\0') { string_t *str = t_str_new(256); const char *prefix = t_strdup_printf("dovecot.%s.global", stats_settings->carbon_name); str_printfa(str, "%s.logins %u %"PRIdTIME_T"\r\n", prefix, mail_global_stats.num_logins, ts); str_printfa(str, "%s.cmds %u %"PRIdTIME_T"\r\n", prefix, mail_global_stats.num_cmds, ts); str_printfa(str, "%s.connected_sessions %u %"PRIdTIME_T"\r\n", prefix, mail_global_stats.num_connected_sessions, ts); str_printfa(str, "%s.last_reset %"PRIdTIME_T" %"PRIdTIME_T"\r\n", prefix, mail_global_stats.reset_timestamp, ts); /* then export rest of the stats */ for(size_t i = 0; i < stats_field_count(); i++) { str_printfa(str, "%s.%s ", prefix, stats_field_name(i)); stats_field_value(str, mail_global_stats.stats, i); str_printfa(str, " %"PRIdTIME_T"\r\n", ts); } /* and send them along */ (void)stats_carbon_send(stats_settings->carbon_server, str_c(str), mail_global_stats_sent, &mail_global_stats, &mail_global_stats.stats_send_ctx); } } void mail_global_init(void) { mail_global_stats.reset_timestamp = ioloop_time; mail_global_stats.stats = stats_alloc(default_pool); mail_global_stats.to_stats_send = timeout_add(stats_settings->carbon_interval*1000, mail_global_stats_send, NULL); } void mail_global_deinit(void) { if (mail_global_stats.stats_send_ctx != NULL) stats_carbon_destroy(&mail_global_stats.stats_send_ctx); timeout_remove(&mail_global_stats.to_stats_send); i_free(mail_global_stats.stats); } void mail_global_login(void) { mail_global_stats.num_logins++; mail_global_stats.num_connected_sessions++; } void mail_global_disconnected(void) { i_assert(mail_global_stats.num_connected_sessions > 0); mail_global_stats.num_connected_sessions--; } void mail_global_refresh(const struct stats *diff_stats) { if (diff_stats != NULL) stats_add(mail_global_stats.stats, diff_stats); mail_global_stats.last_update = ioloop_timeval; } dovecot-2.3.21.1/src/old-stats/client.c0000644000000000000000000001063714656633576014447 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "llist.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "strescape.h" #include "master-service.h" #include "mail-command.h" #include "mail-session.h" #include "mail-user.h" #include "mail-domain.h" #include "mail-ip.h" #include "client-export.h" #include "client-reset.h" #include "client.h" #include #define CLIENT_MAX_SIMULTANEOUS_ITER_COUNT 1000 #define MAX_INBUF_SIZE 1024 #define OUTBUF_THROTTLE_SIZE (1024*64) static struct client *clients; bool client_is_busy(struct client *client) { client->iter_count++; if (client->iter_count % CLIENT_MAX_SIMULTANEOUS_ITER_COUNT == 0) return TRUE; if (o_stream_get_buffer_used_size(client->output) < OUTBUF_THROTTLE_SIZE) return FALSE; if (o_stream_flush(client->output) < 0) return TRUE; return o_stream_get_buffer_used_size(client->output) >= OUTBUF_THROTTLE_SIZE; } static int client_handle_request(struct client *client, const char *const *args, const char **error_r) { const char *cmd = args[0]; if (cmd == NULL) { *error_r = "Missing command"; return -1; } args++; if (strcmp(cmd, "EXPORT") == 0) return client_export(client, args, error_r); if (strcmp(cmd, "RESET") == 0) return client_stats_reset(client, args, error_r); *error_r = "Unknown command"; return -1; } static const char *const* client_read_next_line(struct client *client) { const char *line; line = i_stream_next_line(client->input); if (line == NULL) return NULL; return t_strsplit_tabescaped(line); } static void client_input(struct client *client) { const char *const *args, *error; int ret; timeout_remove(&client->to_pending); switch (i_stream_read(client->input)) { case -2: i_error("BUG: Stats client sent too much data"); client_destroy(&client); return; case -1: client_destroy(&client); return; } o_stream_cork(client->output); while ((args = client_read_next_line(client)) != NULL) { ret = client_handle_request(client, args, &error); if (ret < 0) { i_error("Stats client input error: %s", error); client_destroy(&client); return; } if (ret == 0) { o_stream_set_flush_pending(client->output, TRUE); io_remove(&client->io); break; } client->cmd_more = NULL; } o_stream_uncork(client->output); } static int client_output(struct client *client) { int ret = 1; if (o_stream_flush(client->output) < 0) { client_destroy(&client); return 1; } if (client->cmd_more != NULL) ret = client->cmd_more(client); if (ret > 0) { client->cmd_more = NULL; if (client->io == NULL) client_enable_io(client); } return ret; } void client_enable_io(struct client *client) { i_assert(client->io == NULL); client->io = io_add(client->fd, IO_READ, client_input, client); if (client->to_pending == NULL) client->to_pending = timeout_add(0, client_input, client); } struct client *client_create(int fd) { struct client *client; client = i_new(struct client, 1); client->fd = fd; client->io = io_add(fd, IO_READ, client_input, client); client->input = i_stream_create_fd(fd, MAX_INBUF_SIZE); client->output = o_stream_create_fd(fd, SIZE_MAX); o_stream_set_no_error_handling(client->output, TRUE); o_stream_set_flush_callback(client->output, client_output, client); client->cmd_pool = pool_alloconly_create("cmd pool", 1024); DLLIST_PREPEND(&clients, client); return client; } static void client_unref_iters(struct client *client) { if (client->mail_cmd_iter != NULL) mail_command_unref(&client->mail_cmd_iter); if (client->mail_session_iter != NULL) mail_session_unref(&client->mail_session_iter); if (client->mail_user_iter != NULL) mail_user_unref(&client->mail_user_iter); if (client->mail_domain_iter != NULL) mail_domain_unref(&client->mail_domain_iter); if (client->mail_ip_iter != NULL) mail_ip_unref(&client->mail_ip_iter); } void client_destroy(struct client **_client) { struct client *client = *_client; *_client = NULL; DLLIST_REMOVE(&clients, client); io_remove(&client->io); i_stream_destroy(&client->input); o_stream_destroy(&client->output); if (close(client->fd) < 0) i_error("close(client) failed: %m"); client_unref_iters(client); pool_unref(&client->cmd_pool); i_free(client); master_service_client_connection_destroyed(master_service); } void clients_destroy_all(void) { while (clients != NULL) { struct client *client = clients; client_destroy(&client); } } dovecot-2.3.21.1/src/old-stats/mail-domain.c0000644000000000000000000000661614656633576015362 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "hash.h" #include "llist.h" #include "global-memory.h" #include "stats-settings.h" #include "mail-stats.h" #include "mail-domain.h" static HASH_TABLE(char *, struct mail_domain *) mail_domains_hash; /* domains are sorted by their last_update timestamp, oldest first */ static struct mail_domain *mail_domains_head, *mail_domains_tail; struct mail_domain *stable_mail_domains; static size_t mail_domain_memsize(const struct mail_domain *domain) { return sizeof(*domain) + strlen(domain->name) + 1; } struct mail_domain *mail_domain_login_create(const char *name) { struct mail_domain *domain; domain = hash_table_lookup(mail_domains_hash, name); if (domain != NULL) { return domain; } domain = i_malloc(MALLOC_ADD(sizeof(struct mail_domain), stats_alloc_size())); domain->stats = (void *)(domain + 1); domain->name = i_strdup(name); domain->reset_timestamp = ioloop_time; hash_table_insert(mail_domains_hash, domain->name, domain); DLLIST_PREPEND_FULL(&stable_mail_domains, domain, stable_prev, stable_next); global_memory_alloc(mail_domain_memsize(domain)); return domain; } void mail_domain_login(struct mail_domain *domain) { domain->num_logins++; domain->num_connected_sessions++; mail_domain_refresh(domain, NULL); } void mail_domain_disconnected(struct mail_domain *domain) { i_assert(domain->num_connected_sessions > 0); domain->num_connected_sessions--; mail_global_disconnected(); } struct mail_domain *mail_domain_lookup(const char *name) { return hash_table_lookup(mail_domains_hash, name); } void mail_domain_ref(struct mail_domain *domain) { domain->refcount++; } void mail_domain_unref(struct mail_domain **_domain) { struct mail_domain *domain = *_domain; i_assert(domain->refcount > 0); domain->refcount--; *_domain = NULL; } static void mail_domain_free(struct mail_domain *domain) { i_assert(domain->refcount == 0); i_assert(domain->users == NULL); global_memory_free(mail_domain_memsize(domain)); hash_table_remove(mail_domains_hash, domain->name); DLLIST_REMOVE_FULL(&stable_mail_domains, domain, stable_prev, stable_next); DLLIST2_REMOVE_FULL(&mail_domains_head, &mail_domains_tail, domain, sorted_prev, sorted_next); i_free(domain->name); i_free(domain); } void mail_domain_refresh(struct mail_domain *domain, const struct stats *diff_stats) { if (diff_stats != NULL) stats_add(domain->stats, diff_stats); domain->last_update = ioloop_timeval; DLLIST2_REMOVE_FULL(&mail_domains_head, &mail_domains_tail, domain, sorted_prev, sorted_next); DLLIST2_APPEND_FULL(&mail_domains_head, &mail_domains_tail, domain, sorted_prev, sorted_next); mail_global_refresh(diff_stats); } void mail_domains_free_memory(void) { unsigned int diff; while (mail_domains_head != NULL && mail_domains_head->refcount == 0) { mail_domain_free(mail_domains_head); if (global_used_memory < stats_settings->memory_limit || mail_domains_head == NULL) break; diff = ioloop_time - mail_domains_head->last_update.tv_sec; if (diff < stats_settings->domain_min_time) break; } } void mail_domains_init(void) { hash_table_create(&mail_domains_hash, default_pool, 0, str_hash, strcmp); } void mail_domains_deinit(void) { while (mail_domains_head != NULL) mail_domain_free(mail_domains_head); hash_table_destroy(&mail_domains_hash); } dovecot-2.3.21.1/src/old-stats/stats-settings.h0000644000000000000000000000073714656633576016172 00000000000000#ifndef STATS_SETTINGS_H #define STATS_SETTINGS_H struct old_stats_settings { uoff_t memory_limit; unsigned int command_min_time; unsigned int session_min_time; unsigned int user_min_time; unsigned int domain_min_time; unsigned int ip_min_time; unsigned int carbon_interval; const char *carbon_server; const char *carbon_name; }; extern const struct setting_parser_info old_stats_setting_parser_info; extern const struct old_stats_settings *stats_settings; #endif dovecot-2.3.21.1/src/lib-dict-backend/0000755000000000000000000000000014656633637014236 500000000000000dovecot-2.3.21.1/src/lib-dict-backend/dict-sql.h0000644000000000000000000000015514656633576016052 00000000000000#ifndef DICT_SQL_H #define DICT_SQL_H void dict_sql_register(void); void dict_sql_unregister(void); #endif dovecot-2.3.21.1/src/lib-dict-backend/dict-ldap-settings.c0000644000000000000000000001756114656633576020035 00000000000000/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #if defined(BUILTIN_LDAP) || defined(PLUGIN_BUILD) #include "array.h" #include "str.h" #include "settings.h" #include "dict-ldap-settings.h" #include static const char *dict_ldap_commonName = "cn"; static const char *dict_ldap_empty_filter = ""; enum section_type { SECTION_ROOT = 0, SECTION_MAP, SECTION_FIELDS }; struct dict_ldap_map_attribute { const char *name; const char *variable; }; struct setting_parser_ctx { pool_t pool; struct dict_ldap_settings *set; enum section_type type; struct dict_ldap_map cur_map; ARRAY(struct dict_ldap_map_attribute) cur_attributes; }; #undef DEF_STR #undef DEF_BOOL #undef DEF_UINT #define DEF_STR(name) DEF_STRUCT_STR(name, dict_ldap_map) #define DEF_BOOL(name) DEF_STRUCT_BOOL(name, dict_ldap_map) #define DEF_UINT(name) DEF_STRUCT_UINT(name ,dict_ldap_map) static const struct setting_def dict_ldap_map_setting_defs[] = { DEF_STR(pattern), DEF_STR(filter), DEF_STR(filter_iter), DEF_STR(username_attribute), DEF_STR(value_attribute), DEF_STR(base_dn), DEF_STR(scope), { 0, NULL, 0 } }; static const char *pattern_read_name(const char **pattern) { const char *p = *pattern, *name; if (*p == '{') { /* ${name} */ name = ++p; p = strchr(p, '}'); if (p == NULL) { /* error, but allow anyway */ *pattern += strlen(*pattern); return ""; } *pattern = p + 1; } else { /* $name - ends at the first non-alnum_ character */ name = p; for (; *p != '\0'; p++) { if (!i_isalnum(*p) && *p != '_') break; } *pattern = p; } name = t_strdup_until(name, p); return name; } static const char *dict_ldap_attributes_map(struct setting_parser_ctx *ctx) { struct dict_ldap_map_attribute *attributes; string_t *pattern; const char *p, *name; unsigned int i, count; /* go through the variables in the pattern, replace them with plain '$' character and add its ldap attribute */ pattern = t_str_new(strlen(ctx->cur_map.pattern) + 1); attributes = array_get_modifiable(&ctx->cur_attributes, &count); p_array_init(&ctx->cur_map.ldap_attributes, ctx->pool, count); for (p = ctx->cur_map.pattern; *p != '\0';) { if (*p != '$') { str_append_c(pattern, *p); p++; continue; } p++; str_append_c(pattern, '$'); name = pattern_read_name(&p); for (i = 0; i < count; i++) { if (attributes[i].variable != NULL && strcmp(attributes[i].variable, name) == 0) break; } if (i == count) { return t_strconcat("Missing LDAP attribute for variable: ", name, NULL); } /* mark this attribute as used */ attributes[i].variable = NULL; array_push_back(&ctx->cur_map.ldap_attributes, &attributes[i].name); } /* make sure there aren't any unused attributes */ for (i = 0; i < count; i++) { if (attributes[i].variable != NULL) { return t_strconcat("Unused variable: ", attributes[i].variable, NULL); } } if (ctx->set->max_attribute_count < count) ctx->set->max_attribute_count = count; ctx->cur_map.pattern = p_strdup(ctx->pool, str_c(pattern)); return NULL; } static const char *dict_ldap_map_finish(struct setting_parser_ctx *ctx) { if (ctx->cur_map.pattern == NULL) return "Missing setting: pattern"; if (ctx->cur_map.filter == NULL) ctx->cur_map.filter = dict_ldap_empty_filter; if (*ctx->cur_map.filter != '\0') { const char *ptr = ctx->cur_map.filter; if (*ptr != '(') return "Filter must start with ("; while(*ptr != '\0') ptr++; ptr--; if (*ptr != ')') return "Filter must end with )"; } if (ctx->cur_map.value_attribute == NULL) return "Missing setting: value_attribute"; if (ctx->cur_map.username_attribute == NULL) { /* default to commonName */ ctx->cur_map.username_attribute = dict_ldap_commonName; } if (ctx->cur_map.scope == NULL) { ctx->cur_map.scope_val = 2; /* subtree */ } else { if (strcasecmp(ctx->cur_map.scope, "one") == 0) ctx->cur_map.scope_val = 1; else if (strcasecmp(ctx->cur_map.scope, "base") == 0) ctx->cur_map.scope_val = 0; else if (strcasecmp(ctx->cur_map.scope, "subtree") == 0) ctx->cur_map.scope_val = 2; else return "Scope must be one, base or subtree"; } if (!array_is_created(&ctx->cur_map.ldap_attributes)) { /* no attributes besides value. allocate the array anyway. */ p_array_init(&ctx->cur_map.ldap_attributes, ctx->pool, 1); if (strchr(ctx->cur_map.pattern, '$') != NULL) return "Missing attributes for pattern variables"; } array_push_back(&ctx->set->maps, &ctx->cur_map); i_zero(&ctx->cur_map); return NULL; } static const char * parse_setting(const char *key, const char *value, struct setting_parser_ctx *ctx) { struct dict_ldap_map_attribute *attribute; switch (ctx->type) { case SECTION_ROOT: if (strcmp(key, "uri") == 0) { ctx->set->uri = p_strdup(ctx->pool, value); return NULL; } if (strcmp(key, "bind_dn") == 0) { ctx->set->bind_dn = p_strdup(ctx->pool, value); return NULL; } if (strcmp(key, "password") == 0) { ctx->set->password = p_strdup(ctx->pool, value); return NULL; } if (strcmp(key, "timeout") == 0) { if (str_to_uint(value, &ctx->set->timeout) != 0) { return "Invalid timeout value"; } return NULL; } if (strcmp(key, "max_idle_time") == 0) { if (str_to_uint(value, &ctx->set->max_idle_time) != 0) { return "Invalid max_idle_time value"; } return NULL; } if (strcmp(key, "debug") == 0) { if (str_to_uint(value, &ctx->set->debug) != 0) { return "invalid debug value"; } return NULL; } if (strcmp(key, "tls") == 0) { if (strcasecmp(value, "yes") == 0) { ctx->set->require_ssl = TRUE; ctx->set->start_tls = TRUE; } else if (strcasecmp(value, "no") == 0) { ctx->set->require_ssl = FALSE; ctx->set->start_tls = FALSE; } else if (strcasecmp(value, "try") == 0) { ctx->set->require_ssl = FALSE; ctx->set->start_tls = TRUE; } else { return "tls must be yes, try or no"; } return NULL; } break; case SECTION_MAP: return parse_setting_from_defs(ctx->pool, dict_ldap_map_setting_defs, &ctx->cur_map, key, value); case SECTION_FIELDS: if (*value != '$') { return t_strconcat("Value is missing '$' for attribute: ", key, NULL); } attribute = array_append_space(&ctx->cur_attributes); attribute->name = p_strdup(ctx->pool, key); attribute->variable = p_strdup(ctx->pool, value + 1); return NULL; } return t_strconcat("Unknown setting: ", key, NULL); } static bool parse_section(const char *type, const char *name ATTR_UNUSED, struct setting_parser_ctx *ctx, const char **error_r) { switch (ctx->type) { case SECTION_ROOT: if (type == NULL) return FALSE; if (strcmp(type, "map") == 0) { array_clear(&ctx->cur_attributes); ctx->type = SECTION_MAP; return TRUE; } break; case SECTION_MAP: if (type == NULL) { ctx->type = SECTION_ROOT; *error_r = dict_ldap_map_finish(ctx); return FALSE; } if (strcmp(type, "fields") == 0) { ctx->type = SECTION_FIELDS; return TRUE; } break; case SECTION_FIELDS: if (type == NULL) { ctx->type = SECTION_MAP; *error_r = dict_ldap_attributes_map(ctx); return FALSE; } break; } *error_r = t_strconcat("Unknown section: ", type, NULL); return FALSE; } struct dict_ldap_settings * dict_ldap_settings_read(pool_t pool, const char *path, const char **error_r) { struct setting_parser_ctx ctx; i_zero(&ctx); ctx.pool = pool; ctx.set = p_new(pool, struct dict_ldap_settings, 1); t_array_init(&ctx.cur_attributes, 16); p_array_init(&ctx.set->maps, pool, 8); ctx.set->timeout = 30; /* default timeout */ ctx.set->require_ssl = FALSE; /* try to start SSL */ ctx.set->start_tls = TRUE; if (!settings_read(path, NULL, parse_setting, parse_section, &ctx, error_r)) return NULL; if (ctx.set->uri == NULL) { *error_r = t_strdup_printf("Error in configuration file %s: " "Missing ldap uri", path); return NULL; } return ctx.set; } #endif dovecot-2.3.21.1/src/lib-dict-backend/dict.conf0000644000000000000000000000131414656633576015751 00000000000000connect = host=localhost map { pattern = shared/dictmap/$key1/$key2 table = table value_field = value value_type = string fields { a = $key1 b = $key2 } } map { pattern = shared/counters/$class/$name table = counters value_field = value value_type = uint fields { class = $class name = $name } } map { pattern = priv/quota/bytes table = quota username_field = username value_field = bytes value_type = uint } map { pattern = priv/quota/count table = quota username_field = username value_field = count value_type = uint } map { pattern = priv/quota/folders table = quota username_field = username value_field = folders value_type = uint } dovecot-2.3.21.1/src/lib-dict-backend/Makefile.in0000644000000000000000000011207114656633610016214 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ pkglibexecdir = $(libexecdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ @HAVE_LDAP_TRUE@@LDAP_PLUGIN_FALSE@am__append_1 = $(LIBDOVECOT_LDAP) @HAVE_LDAP_TRUE@@LDAP_PLUGIN_FALSE@am__append_2 = ldap noinst_PROGRAMS = $(am__EXEEXT_1) subdir = src/lib-dict-backend ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__EXEEXT_1 = test-dict-sql$(EXEEXT) PROGRAMS = $(noinst_PROGRAMS) am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ *) f=$$p;; \ esac; am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; am__install_max = 40 am__nobase_strip_setup = \ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` am__nobase_strip = \ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" am__nobase_list = $(am__nobase_strip_setup); \ for p in $$list; do echo "$$p $$p"; done | \ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ if (++n[$$2] == $(am__install_max)) \ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ END { for (dir in files) print dir, files[dir] }' am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' am__uninstall_files_from_dir = { \ test -z "$$files" \ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ $(am__cd) "$$dir" && rm -f $$files; }; \ } am__installdirs = "$(DESTDIR)$(module_dictdir)" LTLIBRARIES = $(module_dict_LTLIBRARIES) $(noinst_LTLIBRARIES) am__DEPENDENCIES_1 = @HAVE_LDAP_TRUE@@LDAP_PLUGIN_FALSE@am__DEPENDENCIES_2 = \ @HAVE_LDAP_TRUE@@LDAP_PLUGIN_FALSE@ $(am__DEPENDENCIES_1) libdict_backend_la_DEPENDENCIES = $(am__DEPENDENCIES_2) am__objects_1 = dict-ldap.lo dict-ldap-settings.lo am_libdict_backend_la_OBJECTS = dict-cdb.lo dict-sql.lo \ dict-sql-settings.lo $(am__objects_1) nodist_libdict_backend_la_OBJECTS = dict-drivers-register.lo libdict_backend_la_OBJECTS = $(am_libdict_backend_la_OBJECTS) \ $(nodist_libdict_backend_la_OBJECTS) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = am__libdict_ldap_la_SOURCES_DIST = dict-ldap.c dict-ldap-settings.c am__objects_2 = libdict_ldap_la-dict-ldap.lo \ libdict_ldap_la-dict-ldap-settings.lo @LDAP_PLUGIN_TRUE@am_libdict_ldap_la_OBJECTS = $(am__objects_2) libdict_ldap_la_OBJECTS = $(am_libdict_ldap_la_OBJECTS) libdict_ldap_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(libdict_ldap_la_LDFLAGS) $(LDFLAGS) \ -o $@ @LDAP_PLUGIN_TRUE@am_libdict_ldap_la_rpath = -rpath $(module_dictdir) am_test_dict_sql_OBJECTS = test_dict_sql-test-dict-sql.$(OBJEXT) test_dict_sql_OBJECTS = $(am_test_dict_sql_OBJECTS) test_dict_sql_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(test_dict_sql_CFLAGS) \ $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/dict-cdb.Plo \ ./$(DEPDIR)/dict-drivers-register.Plo \ ./$(DEPDIR)/dict-ldap-settings.Plo ./$(DEPDIR)/dict-ldap.Plo \ ./$(DEPDIR)/dict-sql-settings.Plo ./$(DEPDIR)/dict-sql.Plo \ ./$(DEPDIR)/libdict_ldap_la-dict-ldap-settings.Plo \ ./$(DEPDIR)/libdict_ldap_la-dict-ldap.Plo \ ./$(DEPDIR)/test_dict_sql-test-dict-sql.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(libdict_backend_la_SOURCES) \ $(nodist_libdict_backend_la_SOURCES) \ $(libdict_ldap_la_SOURCES) $(test_dict_sql_SOURCES) DIST_SOURCES = $(libdict_backend_la_SOURCES) \ $(am__libdict_ldap_la_SOURCES_DIST) $(test_dict_sql_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac HEADERS = $(noinst_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ $(am__append_2) docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ noinst_LTLIBRARIES = libdict_backend.la module_dictdir = $(moduledir)/dict AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-dict \ -I$(top_srcdir)/src/lib-ldap \ -I$(top_srcdir)/src/lib-sql \ -I$(top_srcdir)/src/lib-settings \ $(SQL_CFLAGS) ldap_sources = \ dict-ldap.c \ dict-ldap-settings.c libdict_backend_la_SOURCES = \ dict-cdb.c \ dict-sql.c \ dict-sql-settings.c \ $(ldap_sources) libdict_backend_la_LIBADD = $(am__append_1) nodist_libdict_backend_la_SOURCES = \ dict-drivers-register.c noinst_HEADERS = \ dict-ldap-settings.h \ dict-sql.h \ dict-sql-private.h \ dict-sql-settings.h @LDAP_PLUGIN_TRUE@LIBDICT_LDAP = libdict_ldap.la @LDAP_PLUGIN_TRUE@libdict_ldap_la_DEPENDENCIES = $(LIBDOVECOT_LDAP) $(LIBDOVECOT_DEPS) @LDAP_PLUGIN_TRUE@libdict_ldap_la_LDFLAGS = -module -avoid-version @LDAP_PLUGIN_TRUE@libdict_ldap_la_LIBADD = $(LIBDOVECOT_LDAP) $(LIBDOVECOT) @LDAP_PLUGIN_TRUE@libdict_ldap_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD @LDAP_PLUGIN_TRUE@libdict_ldap_la_SOURCES = $(ldap_sources) module_dict_LTLIBRARIES = \ $(LIBDICT_LDAP) EXTRA_DIST = dict.conf test_programs = \ test-dict-sql test_dict_sql_CFLAGS = $(AM_CPPFLAGS) -DDICT_SRC_DIR=\"$(top_srcdir)/src/lib-dict-backend\" test_dict_sql_SOURCES = \ test-dict-sql.c test_dict_sql_LDADD = \ $(noinst_LTLIBRARIES) \ $(DICT_LIBS) \ ../lib-sql/libdriver_test.la \ ../lib-sql/libsql.la \ ../lib-dovecot/libdovecot.la test_dict_sql_DEPENDENCIES = \ $(noinst_LTLIBRARIES) \ ../lib-sql/libdriver_test.la \ ../lib-sql/libsql.la \ ../lib-dovecot/libdovecot.la all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-dict-backend/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/lib-dict-backend/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): clean-noinstPROGRAMS: @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list install-module_dictLTLIBRARIES: $(module_dict_LTLIBRARIES) @$(NORMAL_INSTALL) @list='$(module_dict_LTLIBRARIES)'; test -n "$(module_dictdir)" || list=; \ list2=; for p in $$list; do \ if test -f $$p; then \ list2="$$list2 $$p"; \ else :; fi; \ done; \ test -z "$$list2" || { \ echo " $(MKDIR_P) '$(DESTDIR)$(module_dictdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(module_dictdir)" || exit 1; \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(module_dictdir)'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(module_dictdir)"; \ } uninstall-module_dictLTLIBRARIES: @$(NORMAL_UNINSTALL) @list='$(module_dict_LTLIBRARIES)'; test -n "$(module_dictdir)" || list=; \ for p in $$list; do \ $(am__strip_dir) \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(module_dictdir)/$$f'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(module_dictdir)/$$f"; \ done clean-module_dictLTLIBRARIES: -test -z "$(module_dict_LTLIBRARIES)" || rm -f $(module_dict_LTLIBRARIES) @list='$(module_dict_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } clean-noinstLTLIBRARIES: -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) @list='$(noinst_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } libdict_backend.la: $(libdict_backend_la_OBJECTS) $(libdict_backend_la_DEPENDENCIES) $(EXTRA_libdict_backend_la_DEPENDENCIES) $(AM_V_CCLD)$(LINK) $(libdict_backend_la_OBJECTS) $(libdict_backend_la_LIBADD) $(LIBS) libdict_ldap.la: $(libdict_ldap_la_OBJECTS) $(libdict_ldap_la_DEPENDENCIES) $(EXTRA_libdict_ldap_la_DEPENDENCIES) $(AM_V_CCLD)$(libdict_ldap_la_LINK) $(am_libdict_ldap_la_rpath) $(libdict_ldap_la_OBJECTS) $(libdict_ldap_la_LIBADD) $(LIBS) test-dict-sql$(EXEEXT): $(test_dict_sql_OBJECTS) $(test_dict_sql_DEPENDENCIES) $(EXTRA_test_dict_sql_DEPENDENCIES) @rm -f test-dict-sql$(EXEEXT) $(AM_V_CCLD)$(test_dict_sql_LINK) $(test_dict_sql_OBJECTS) $(test_dict_sql_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-cdb.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-drivers-register.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-ldap-settings.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-ldap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-sql-settings.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-sql.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdict_ldap_la-dict-ldap-settings.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdict_ldap_la-dict-ldap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_dict_sql-test-dict-sql.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< libdict_ldap_la-dict-ldap.lo: dict-ldap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdict_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdict_ldap_la-dict-ldap.lo -MD -MP -MF $(DEPDIR)/libdict_ldap_la-dict-ldap.Tpo -c -o libdict_ldap_la-dict-ldap.lo `test -f 'dict-ldap.c' || echo '$(srcdir)/'`dict-ldap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdict_ldap_la-dict-ldap.Tpo $(DEPDIR)/libdict_ldap_la-dict-ldap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dict-ldap.c' object='libdict_ldap_la-dict-ldap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdict_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdict_ldap_la-dict-ldap.lo `test -f 'dict-ldap.c' || echo '$(srcdir)/'`dict-ldap.c libdict_ldap_la-dict-ldap-settings.lo: dict-ldap-settings.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdict_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdict_ldap_la-dict-ldap-settings.lo -MD -MP -MF $(DEPDIR)/libdict_ldap_la-dict-ldap-settings.Tpo -c -o libdict_ldap_la-dict-ldap-settings.lo `test -f 'dict-ldap-settings.c' || echo '$(srcdir)/'`dict-ldap-settings.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdict_ldap_la-dict-ldap-settings.Tpo $(DEPDIR)/libdict_ldap_la-dict-ldap-settings.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dict-ldap-settings.c' object='libdict_ldap_la-dict-ldap-settings.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdict_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdict_ldap_la-dict-ldap-settings.lo `test -f 'dict-ldap-settings.c' || echo '$(srcdir)/'`dict-ldap-settings.c test_dict_sql-test-dict-sql.o: test-dict-sql.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_dict_sql_CFLAGS) $(CFLAGS) -MT test_dict_sql-test-dict-sql.o -MD -MP -MF $(DEPDIR)/test_dict_sql-test-dict-sql.Tpo -c -o test_dict_sql-test-dict-sql.o `test -f 'test-dict-sql.c' || echo '$(srcdir)/'`test-dict-sql.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_dict_sql-test-dict-sql.Tpo $(DEPDIR)/test_dict_sql-test-dict-sql.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-dict-sql.c' object='test_dict_sql-test-dict-sql.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_dict_sql_CFLAGS) $(CFLAGS) -c -o test_dict_sql-test-dict-sql.o `test -f 'test-dict-sql.c' || echo '$(srcdir)/'`test-dict-sql.c test_dict_sql-test-dict-sql.obj: test-dict-sql.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_dict_sql_CFLAGS) $(CFLAGS) -MT test_dict_sql-test-dict-sql.obj -MD -MP -MF $(DEPDIR)/test_dict_sql-test-dict-sql.Tpo -c -o test_dict_sql-test-dict-sql.obj `if test -f 'test-dict-sql.c'; then $(CYGPATH_W) 'test-dict-sql.c'; else $(CYGPATH_W) '$(srcdir)/test-dict-sql.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_dict_sql-test-dict-sql.Tpo $(DEPDIR)/test_dict_sql-test-dict-sql.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-dict-sql.c' object='test_dict_sql-test-dict-sql.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_dict_sql_CFLAGS) $(CFLAGS) -c -o test_dict_sql-test-dict-sql.obj `if test -f 'test-dict-sql.c'; then $(CYGPATH_W) 'test-dict-sql.c'; else $(CYGPATH_W) '$(srcdir)/test-dict-sql.c'; fi` mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am $(MAKE) $(AM_MAKEFLAGS) check-local check: check-am all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(module_dictdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-module_dictLTLIBRARIES \ clean-noinstLTLIBRARIES clean-noinstPROGRAMS mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/dict-cdb.Plo -rm -f ./$(DEPDIR)/dict-drivers-register.Plo -rm -f ./$(DEPDIR)/dict-ldap-settings.Plo -rm -f ./$(DEPDIR)/dict-ldap.Plo -rm -f ./$(DEPDIR)/dict-sql-settings.Plo -rm -f ./$(DEPDIR)/dict-sql.Plo -rm -f ./$(DEPDIR)/libdict_ldap_la-dict-ldap-settings.Plo -rm -f ./$(DEPDIR)/libdict_ldap_la-dict-ldap.Plo -rm -f ./$(DEPDIR)/test_dict_sql-test-dict-sql.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-module_dictLTLIBRARIES install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/dict-cdb.Plo -rm -f ./$(DEPDIR)/dict-drivers-register.Plo -rm -f ./$(DEPDIR)/dict-ldap-settings.Plo -rm -f ./$(DEPDIR)/dict-ldap.Plo -rm -f ./$(DEPDIR)/dict-sql-settings.Plo -rm -f ./$(DEPDIR)/dict-sql.Plo -rm -f ./$(DEPDIR)/libdict_ldap_la-dict-ldap-settings.Plo -rm -f ./$(DEPDIR)/libdict_ldap_la-dict-ldap.Plo -rm -f ./$(DEPDIR)/test_dict_sql-test-dict-sql.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-module_dictLTLIBRARIES .MAKE: check-am install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ check-local clean clean-generic clean-libtool \ clean-module_dictLTLIBRARIES clean-noinstLTLIBRARIES \ clean-noinstPROGRAMS cscopelist-am ctags ctags-am distclean \ distclean-compile distclean-generic distclean-libtool \ distclean-tags distdir dvi dvi-am html html-am info info-am \ install install-am install-data install-data-am install-dvi \ install-dvi-am install-exec install-exec-am install-html \ install-html-am install-info install-info-am install-man \ install-module_dictLTLIBRARIES install-pdf install-pdf-am \ install-ps install-ps-am install-strip installcheck \ installcheck-am installdirs maintainer-clean \ maintainer-clean-generic mostlyclean mostlyclean-compile \ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ tags tags-am uninstall uninstall-am \ uninstall-module_dictLTLIBRARIES .PRECIOUS: Makefile dict-drivers-register.c: Makefile $(top_builddir)/config.h rm -f $@ echo '/* this file automatically generated by Makefile */' >$@ echo '#include "lib.h"' >>$@ echo '#include "dict.h"' >>$@ echo '#include "ldap-client.h"' >>$@ echo '#include "dict-sql.h"' >>$@ for i in $(dict_drivers) null; do \ if [ "$${i}" != "null" ]; then \ echo "extern struct dict dict_driver_$${i};" >>$@ ; \ fi; \ done echo 'void dict_drivers_register_all(void) {' >>$@ echo 'dict_drivers_register_builtin();' >>$@ echo 'dict_sql_register();' >>$@ for i in $(dict_drivers) null; do \ if [ "$${i}" != "null" ]; then \ echo "dict_driver_register(&dict_driver_$${i});" >>$@ ; \ fi; \ done echo '}' >>$@ echo 'void dict_drivers_unregister_all(void) {' >>$@ echo '#ifdef BUILTIN_LDAP' >>$@ echo 'ldap_clients_cleanup();' >>$@ echo '#endif' >>$@ echo 'dict_drivers_unregister_builtin();' >>$@ echo 'dict_sql_unregister();' >>$@ for i in $(dict_drivers) null; do \ if [ "$${i}" != "null" ]; then \ echo "dict_driver_unregister(&dict_driver_$${i});" >>$@ ; \ fi; \ done echo '}' >>$@ distclean-generic: rm -f Makefile dict-drivers-register.c check-local: for bin in $(test_programs) $(check_PROGRAMS); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/lib-dict-backend/dict-sql-private.h0000644000000000000000000000026214656633576017521 00000000000000#ifndef DICT_SQL_PRIVATE_H #define DICT_SQL_PRIVATE_H 1 struct sql_dict { struct dict dict; pool_t pool; struct sql_db *db; const struct dict_sql_settings *set; }; #endif dovecot-2.3.21.1/src/lib-dict-backend/dict-sql-settings.h0000644000000000000000000000203714656633576017711 00000000000000#ifndef DICT_SQL_SETTINGS_H #define DICT_SQL_SETTINGS_H enum dict_sql_type { DICT_SQL_TYPE_STRING = 0, DICT_SQL_TYPE_INT, DICT_SQL_TYPE_UINT, DICT_SQL_TYPE_HEXBLOB }; struct dict_sql_field { const char *name; enum dict_sql_type value_type; }; struct dict_sql_map { /* pattern is in simplified form: all variables are stored as simple '$' character. fields array is sorted by the variable index. */ const char *pattern; const char *table; const char *username_field; const char *value_field; const char *value_type; bool value_hexblob; /* SQL field names, one for each $ variable in the pattern */ ARRAY(struct dict_sql_field) pattern_fields; /* generated: */ unsigned int values_count; const char *const *value_fields; const enum dict_sql_type *value_types; }; struct dict_sql_settings { const char *connect; unsigned int max_pattern_fields_count; ARRAY(struct dict_sql_map) maps; }; struct dict_sql_settings * dict_sql_settings_read(const char *path, const char **error_r); void dict_sql_settings_deinit(void); #endif dovecot-2.3.21.1/src/lib-dict-backend/dict-cdb.c0000644000000000000000000001365514656633576016007 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #ifdef BUILD_CDB #include "dict-private.h" #include #include #include #include #define CDB_WITH_NULL 1 #define CDB_WITHOUT_NULL 2 struct cdb_dict { struct dict dict; struct cdb cdb; char *path; int fd, flag; }; struct cdb_dict_iterate_context { struct dict_iterate_context ctx; enum dict_iterate_flags flags; buffer_t *buffer; const char *values[2]; char *path; unsigned cptr; char *error; }; static void cdb_dict_deinit(struct dict *_dict); static int cdb_dict_init(struct dict *driver, const char *uri, const struct dict_settings *set ATTR_UNUSED, struct dict **dict_r, const char **error_r) { struct cdb_dict *dict; dict = i_new(struct cdb_dict, 1); dict->dict = *driver; dict->path = i_strdup(uri); dict->flag = CDB_WITH_NULL | CDB_WITHOUT_NULL; /* initialize cdb to 0 (unallocated) */ i_zero(&dict->cdb); dict->fd = open(dict->path, O_RDONLY); if (dict->fd == -1) { *error_r = t_strdup_printf("open(%s) failed: %m", dict->path); cdb_dict_deinit(&dict->dict); return -1; } #ifdef TINYCDB_VERSION if (cdb_init(&dict->cdb, dict->fd) < 0) { *error_r = t_strdup_printf("cdb_init(%s) failed: %m", dict->path); cdb_dict_deinit(&dict->dict); return -1; } #else cdb_init(&dict->cdb, dict->fd); #endif *dict_r = &dict->dict; return 0; } static void cdb_dict_deinit(struct dict *_dict) { struct cdb_dict *dict = (struct cdb_dict *)_dict; /* we can safely deinit unallocated cdb */ cdb_free(&dict->cdb); i_close_fd_path(&dict->fd, dict->path); i_free(dict->path); i_free(dict); } static int cdb_dict_lookup(struct dict *_dict, const struct dict_op_settings *set ATTR_UNUSED, pool_t pool, const char *key, const char **value_r, const char **error_r) { struct cdb_dict *dict = (struct cdb_dict *)_dict; unsigned datalen; int ret = 0; char *data; /* keys and values may be null terminated... */ if ((dict->flag & CDB_WITH_NULL) != 0) { ret = cdb_find(&dict->cdb, key, (unsigned)strlen(key)+1); if (ret > 0) dict->flag &= ENUM_NEGATE(CDB_WITHOUT_NULL); } /* ...or not */ if (ret == 0 && (dict->flag & CDB_WITHOUT_NULL) != 0) { ret = cdb_find(&dict->cdb, key, (unsigned)strlen(key)); if (ret > 0) dict->flag &= ENUM_NEGATE(CDB_WITH_NULL); } if (ret <= 0) { *value_r = NULL; /* something bad with db */ if (ret < 0) { *error_r = t_strdup_printf("cdb_find(%s) failed: %m", dict->path); return -1; } /* found nothing */ return 0; } datalen = cdb_datalen(&dict->cdb); data = p_malloc(pool, datalen + 1); if (cdb_read(&dict->cdb, data, datalen, cdb_datapos(&dict->cdb)) < 0) { *error_r = t_strdup_printf("cdb_read(%s) failed: %m", dict->path); return -1; } *value_r = data; return 1; } static struct dict_iterate_context * cdb_dict_iterate_init(struct dict *_dict, const struct dict_op_settings *set ATTR_UNUSED, const char *path, enum dict_iterate_flags flags) { struct cdb_dict_iterate_context *ctx = i_new(struct cdb_dict_iterate_context, 1); struct cdb_dict *dict = (struct cdb_dict *)_dict; ctx->ctx.dict = &dict->dict; ctx->path = i_strdup(path); ctx->flags = flags; ctx->buffer = buffer_create_dynamic(default_pool, 256); cdb_seqinit(&ctx->cptr, &dict->cdb); return &ctx->ctx; } static bool cdb_dict_next(struct cdb_dict_iterate_context *ctx, const char **key_r) { struct cdb_dict *dict = (struct cdb_dict *)ctx->ctx.dict; char *data; unsigned datalen; int ret; if ((ret = cdb_seqnext(&ctx->cptr, &dict->cdb)) < 1) { if (ret < 0) ctx->error = i_strdup_printf("cdb_seqnext(%s) failed: %m", dict->path); return FALSE; } buffer_set_used_size(ctx->buffer, 0); datalen = cdb_keylen(&dict->cdb); data = buffer_append_space_unsafe(ctx->buffer, datalen + 1); if (cdb_read(&dict->cdb, data, datalen, cdb_keypos(&dict->cdb)) < 0) { ctx->error = i_strdup_printf("cdb_read(%s) failed: %m", dict->path); return FALSE; } data[datalen] = '\0'; *key_r = data; return TRUE; } static bool cdb_dict_iterate(struct dict_iterate_context *_ctx, const char **key_r, const char *const **values_r) { struct cdb_dict_iterate_context *ctx = (struct cdb_dict_iterate_context *)_ctx; struct cdb_dict *dict = (struct cdb_dict *)_ctx->dict; const char *key; bool match = FALSE; char *data; unsigned datalen; if (ctx->error != NULL) return FALSE; while(!match && cdb_dict_next(ctx, &key)) { if (((ctx->flags & DICT_ITERATE_FLAG_EXACT_KEY) != 0 && strcmp(key, ctx->path) == 0) || ((ctx->flags & DICT_ITERATE_FLAG_RECURSE) != 0 && str_begins(key, ctx->path)) || ((ctx->flags & DICT_ITERATE_FLAG_RECURSE) == 0 && str_begins(key, ctx->path) && strchr(key + strlen(ctx->path), '/') == NULL)) { match = TRUE; break; } } if (!match) return FALSE; *key_r = key; if ((ctx->flags & DICT_ITERATE_FLAG_NO_VALUE) != 0) return TRUE; datalen = cdb_datalen(&dict->cdb); data = buffer_append_space_unsafe(ctx->buffer, datalen + 1); if (cdb_read(&dict->cdb, data, datalen, cdb_datapos(&dict->cdb)) < 0) { ctx->error = i_strdup_printf("cdb_read(%s) failed: %m", dict->path); return FALSE; } data[datalen] = '\0'; ctx->values[0] = data; *values_r = ctx->values; return TRUE; } static int cdb_dict_iterate_deinit(struct dict_iterate_context *_ctx, const char **error_r) { int ret = 0; struct cdb_dict_iterate_context *ctx = (struct cdb_dict_iterate_context *)_ctx; if (ctx->error != NULL) { *error_r = t_strdup(ctx->error); ret = -1; } buffer_free(&ctx->buffer); i_free(ctx->error); i_free(ctx->path); i_free(ctx); return ret; } struct dict dict_driver_cdb = { .name = "cdb", { .init = cdb_dict_init, .deinit = cdb_dict_deinit, .lookup = cdb_dict_lookup, .iterate_init = cdb_dict_iterate_init, .iterate = cdb_dict_iterate, .iterate_deinit = cdb_dict_iterate_deinit, } }; #endif dovecot-2.3.21.1/src/lib-dict-backend/dict-sql-settings.c0000644000000000000000000002161014656633576017702 00000000000000/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "hash.h" #include "settings.h" #include "dict-sql-settings.h" #include enum section_type { SECTION_ROOT = 0, SECTION_MAP, SECTION_FIELDS }; struct dict_sql_map_field { struct dict_sql_field sql_field; const char *variable; }; struct setting_parser_ctx { pool_t pool; struct dict_sql_settings *set; enum section_type type; struct dict_sql_map cur_map; ARRAY(struct dict_sql_map_field) cur_fields; }; #define DEF_STR(name) DEF_STRUCT_STR(name, dict_sql_map) #define DEF_BOOL(name) DEF_STRUCT_BOOL(name, dict_sql_map) static const struct setting_def dict_sql_map_setting_defs[] = { DEF_STR(pattern), DEF_STR(table), DEF_STR(username_field), DEF_STR(value_field), DEF_STR(value_type), DEF_BOOL(value_hexblob), { 0, NULL, 0 } }; struct dict_sql_settings_cache { pool_t pool; const char *path; struct dict_sql_settings *set; }; static HASH_TABLE(const char *, struct dict_sql_settings_cache *) dict_sql_settings_cache; static const char *pattern_read_name(const char **pattern) { const char *p = *pattern, *name; if (*p == '{') { /* ${name} */ name = ++p; p = strchr(p, '}'); if (p == NULL) { /* error, but allow anyway */ *pattern += strlen(*pattern); return ""; } *pattern = p + 1; } else { /* $name - ends at the first non-alnum_ character */ name = p; for (; *p != '\0'; p++) { if (!i_isalnum(*p) && *p != '_') break; } *pattern = p; } name = t_strdup_until(name, p); return name; } static const char *dict_sql_fields_map(struct setting_parser_ctx *ctx) { struct dict_sql_map_field *fields; string_t *pattern; const char *p, *name; unsigned int i, count; /* go through the variables in the pattern, replace them with plain '$' character and add its sql field */ pattern = t_str_new(strlen(ctx->cur_map.pattern) + 1); fields = array_get_modifiable(&ctx->cur_fields, &count); p_array_init(&ctx->cur_map.pattern_fields, ctx->pool, count); for (p = ctx->cur_map.pattern; *p != '\0';) { if (*p != '$') { str_append_c(pattern, *p); p++; continue; } p++; str_append_c(pattern, '$'); name = pattern_read_name(&p); for (i = 0; i < count; i++) { if (fields[i].variable != NULL && strcmp(fields[i].variable, name) == 0) break; } if (i == count) { return t_strconcat("Missing SQL field for variable: ", name, NULL); } /* mark this field as used */ fields[i].variable = NULL; array_push_back(&ctx->cur_map.pattern_fields, &fields[i].sql_field); } /* make sure there aren't any unused fields */ for (i = 0; i < count; i++) { if (fields[i].variable != NULL) { return t_strconcat("Unused variable: ", fields[i].variable, NULL); } } if (ctx->set->max_pattern_fields_count < count) ctx->set->max_pattern_fields_count = count; ctx->cur_map.pattern = p_strdup(ctx->pool, str_c(pattern)); return NULL; } static bool dict_sql_value_type_parse(const char *value_type, enum dict_sql_type *type_r) { if (strcmp(value_type, "string") == 0) *type_r = DICT_SQL_TYPE_STRING; else if (strcmp(value_type, "hexblob") == 0) *type_r = DICT_SQL_TYPE_HEXBLOB; else if (strcmp(value_type, "int") == 0) *type_r = DICT_SQL_TYPE_INT; else if (strcmp(value_type, "uint") == 0) *type_r = DICT_SQL_TYPE_UINT; else return FALSE; return TRUE; } static const char *dict_sql_map_finish(struct setting_parser_ctx *ctx) { unsigned int i; if (ctx->cur_map.pattern == NULL) return "Missing setting: pattern"; if (ctx->cur_map.table == NULL) return "Missing setting: table"; if (ctx->cur_map.value_field == NULL) return "Missing setting: value_field"; ctx->cur_map.value_fields = (const char *const *) p_strsplit_spaces(ctx->pool, ctx->cur_map.value_field, ","); ctx->cur_map.values_count = str_array_length(ctx->cur_map.value_fields); enum dict_sql_type *value_types = p_new(ctx->pool, enum dict_sql_type, ctx->cur_map.values_count); if (ctx->cur_map.value_type != NULL) { const char *const *types = t_strsplit_spaces(ctx->cur_map.value_type, ","); if (str_array_length(types) != ctx->cur_map.values_count) return "Number of fields in value_fields doesn't match value_type"; for (i = 0; i < ctx->cur_map.values_count; i++) { if (!dict_sql_value_type_parse(types[i], &value_types[i])) return "Invalid value in value_type"; } } else { for (i = 0; i < ctx->cur_map.values_count; i++) { value_types[i] = ctx->cur_map.value_hexblob ? DICT_SQL_TYPE_HEXBLOB : DICT_SQL_TYPE_STRING; } } ctx->cur_map.value_types = value_types; if (ctx->cur_map.username_field == NULL) { /* not all queries require this */ ctx->cur_map.username_field = "'username_field not set'"; } if (!array_is_created(&ctx->cur_map.pattern_fields)) { /* no fields besides value. allocate the array anyway. */ p_array_init(&ctx->cur_map.pattern_fields, ctx->pool, 1); if (strchr(ctx->cur_map.pattern, '$') != NULL) return "Missing fields for pattern variables"; } array_push_back(&ctx->set->maps, &ctx->cur_map); i_zero(&ctx->cur_map); return NULL; } static const char * parse_setting(const char *key, const char *value, struct setting_parser_ctx *ctx) { struct dict_sql_map_field *field; size_t value_len; switch (ctx->type) { case SECTION_ROOT: if (strcmp(key, "connect") == 0) { ctx->set->connect = p_strdup(ctx->pool, value); return NULL; } break; case SECTION_MAP: return parse_setting_from_defs(ctx->pool, dict_sql_map_setting_defs, &ctx->cur_map, key, value); case SECTION_FIELDS: if (*value != '$') { return t_strconcat("Value is missing '$' for field: ", key, NULL); } field = array_append_space(&ctx->cur_fields); field->sql_field.name = p_strdup(ctx->pool, key); value_len = strlen(value); if (str_begins(value, "${hexblob:") && value[value_len-1] == '}') { field->variable = p_strndup(ctx->pool, value + 10, value_len-10-1); field->sql_field.value_type = DICT_SQL_TYPE_HEXBLOB; } else if (str_begins(value, "${int:") && value[value_len-1] == '}') { field->variable = p_strndup(ctx->pool, value + 6, value_len-6-1); field->sql_field.value_type = DICT_SQL_TYPE_INT; } else if (str_begins(value, "${uint:") && value[value_len-1] == '}') { field->variable = p_strndup(ctx->pool, value + 7, value_len-7-1); field->sql_field.value_type = DICT_SQL_TYPE_UINT; } else { field->variable = p_strdup(ctx->pool, value + 1); } return NULL; } return t_strconcat("Unknown setting: ", key, NULL); } static bool parse_section(const char *type, const char *name ATTR_UNUSED, struct setting_parser_ctx *ctx, const char **error_r) { switch (ctx->type) { case SECTION_ROOT: if (type == NULL) return FALSE; if (strcmp(type, "map") == 0) { array_clear(&ctx->cur_fields); ctx->type = SECTION_MAP; return TRUE; } break; case SECTION_MAP: if (type == NULL) { ctx->type = SECTION_ROOT; *error_r = dict_sql_map_finish(ctx); return FALSE; } if (strcmp(type, "fields") == 0) { ctx->type = SECTION_FIELDS; return TRUE; } break; case SECTION_FIELDS: if (type == NULL) { ctx->type = SECTION_MAP; *error_r = dict_sql_fields_map(ctx); return FALSE; } break; } *error_r = t_strconcat("Unknown section: ", type, NULL); return FALSE; } struct dict_sql_settings * dict_sql_settings_read(const char *path, const char **error_r) { struct setting_parser_ctx ctx; struct dict_sql_settings_cache *cache; pool_t pool; if (!hash_table_is_created(dict_sql_settings_cache)) { hash_table_create(&dict_sql_settings_cache, default_pool, 0, str_hash, strcmp); } cache = hash_table_lookup(dict_sql_settings_cache, path); if (cache != NULL) return cache->set; i_zero(&ctx); pool = pool_alloconly_create("dict sql settings", 1024); ctx.pool = pool; ctx.set = p_new(pool, struct dict_sql_settings, 1); t_array_init(&ctx.cur_fields, 16); p_array_init(&ctx.set->maps, pool, 8); if (!settings_read(path, NULL, parse_setting, parse_section, &ctx, error_r)) { pool_unref(&pool); return NULL; } if (ctx.set->connect == NULL) { *error_r = t_strdup_printf("Error in configuration file %s: " "Missing connect setting", path); pool_unref(&pool); return NULL; } cache = p_new(pool, struct dict_sql_settings_cache, 1); cache->pool = pool; cache->path = p_strdup(pool, path); cache->set = ctx.set; hash_table_insert(dict_sql_settings_cache, cache->path, cache); return ctx.set; } void dict_sql_settings_deinit(void) { struct hash_iterate_context *iter; struct dict_sql_settings_cache *cache; const char *key; if (!hash_table_is_created(dict_sql_settings_cache)) return; iter = hash_table_iterate_init(dict_sql_settings_cache); while (hash_table_iterate(iter, dict_sql_settings_cache, &key, &cache)) pool_unref(&cache->pool); hash_table_iterate_deinit(&iter); hash_table_destroy(&dict_sql_settings_cache); } dovecot-2.3.21.1/src/lib-dict-backend/dict-ldap.c0000644000000000000000000003054714656633576016176 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING memcached */ #include "lib.h" #if defined(BUILTIN_LDAP) || defined(PLUGIN_BUILD) #include "array.h" #include "module-dir.h" #include "str.h" #include "istream.h" #include "ostream.h" #include "var-expand.h" #include "connection.h" #include "llist.h" #include "ldap-client.h" #include "dict.h" #include "dict-private.h" #include "dict-ldap-settings.h" static const char *LDAP_ESCAPE_CHARS = "*,\\#+<>;\"()= "; struct ldap_dict; struct dict_ldap_op { struct ldap_dict *dict; const struct dict_ldap_map *map; pool_t pool; unsigned long txid; struct dict_lookup_result res; dict_lookup_callback_t *callback; void *callback_ctx; }; struct ldap_dict { struct dict dict; struct dict_ldap_settings *set; const char *uri; const char *base_dn; enum ldap_scope scope; pool_t pool; struct ldap_client *client; unsigned long last_txid; unsigned int pending; struct ldap_dict *prev,*next; }; static void ldap_dict_lookup_async(struct dict *dict, const struct dict_op_settings *set, const char *key, dict_lookup_callback_t *callback, void *context); static bool dict_ldap_map_match(const struct dict_ldap_map *map, const char *path, ARRAY_TYPE(const_string) *values, size_t *pat_len_r, size_t *path_len_r, bool partial_ok, bool recurse) { const char *path_start = path; const char *pat, *attribute, *p; size_t len; array_clear(values); pat = map->pattern; while (*pat != '\0' && *path != '\0') { if (*pat == '$') { /* variable */ pat++; if (*pat == '\0') { /* pattern ended with this variable, it'll match the rest of the path */ len = strlen(path); if (partial_ok) { /* iterating - the last field never matches fully. if there's a trailing '/', drop it. */ pat--; if (path[len-1] == '/') { attribute = t_strndup(path, len-1); array_push_back(values, &attribute); } else { array_push_back(values, &path); } } else { array_push_back(values, &path); path += len; } *path_len_r = path - path_start; *pat_len_r = pat - map->pattern; return TRUE; } /* pattern matches until the next '/' in path */ p = strchr(path, '/'); if (p != NULL) { attribute = t_strdup_until(path, p); array_push_back(values, &attribute); path = p; } else { /* no '/' anymore, but it'll still match a partial */ array_push_back(values, &path); path += strlen(path); pat++; } } else if (*pat == *path) { pat++; path++; } else { return FALSE; } } *path_len_r = path - path_start; *pat_len_r = pat - map->pattern; if (*pat == '\0') return *path == '\0'; else if (!partial_ok) return FALSE; else { /* partial matches must end with '/'. */ if (pat != map->pattern && pat[-1] != '/') return FALSE; /* if we're not recursing, there should be only one $variable left. */ if (recurse) return TRUE; return pat[0] == '$' && strchr(pat, '/') == NULL; } } static const struct dict_ldap_map * ldap_dict_find_map(struct ldap_dict *dict, const char *path, ARRAY_TYPE(const_string) *values) { const struct dict_ldap_map *maps; unsigned int i, count; size_t len; t_array_init(values, dict->set->max_attribute_count); maps = array_get(&dict->set->maps, &count); for (i = 0; i < count; i++) { if (dict_ldap_map_match(&maps[i], path, values, &len, &len, FALSE, FALSE)) return &maps[i]; } return NULL; } static int dict_ldap_connect(struct ldap_dict *dict, const char **error_r) { struct ldap_client_settings set; i_zero(&set); set.uri = dict->set->uri; set.bind_dn = dict->set->bind_dn; set.password = dict->set->password; set.timeout_secs = dict->set->timeout; set.max_idle_time_secs = dict->set->max_idle_time; set.debug = dict->set->debug; set.require_ssl = dict->set->require_ssl; set.start_tls = dict->set->start_tls; return ldap_client_init(&set, &dict->client, error_r); } #define IS_LDAP_ESCAPED_CHAR(c) \ ((((unsigned char)(c)) & 0x80) != 0 || strchr(LDAP_ESCAPE_CHARS, (c)) != NULL) static const char *ldap_escape(const char *str) { string_t *ret = NULL; for (const char *p = str; *p != '\0'; p++) { if (IS_LDAP_ESCAPED_CHAR(*p)) { if (ret == NULL) { ret = t_str_new((size_t) (p - str) + 64); str_append_data(ret, str, (size_t) (p - str)); } str_printfa(ret, "\\%02X", (unsigned char)*p); } else if (ret != NULL) str_append_c(ret, *p); } return ret == NULL ? str : str_c(ret); } static bool ldap_dict_build_query(const struct dict_op_settings *set, const struct dict_ldap_map *map, ARRAY_TYPE(const_string) *values, bool priv, string_t *query_r, const char **error_r) { const char *template, *error; ARRAY(struct var_expand_table) exp; struct var_expand_table entry; t_array_init(&exp, 8); entry.key = '\0'; entry.value = ldap_escape(set->username); entry.long_key = "username"; array_push_back(&exp, &entry); if (priv) { template = t_strdup_printf("(&(%s=%s)%s)", map->username_attribute, "%{username}", map->filter); } else { template = map->filter; } for(size_t i = 0; i < array_count(values) && i < array_count(&map->ldap_attributes); i++) { struct var_expand_table entry; const char *value = array_idx_elem(values, i); const char *long_key = array_idx_elem(&map->ldap_attributes, i); entry.value = ldap_escape(value); entry.long_key = long_key; array_push_back(&exp, &entry); } array_append_zero(&exp); if (var_expand(query_r, template, array_front(&exp), &error) <= 0) { *error_r = t_strdup_printf("Failed to expand %s: %s", template, error); return FALSE; } return TRUE; } static int ldap_dict_init(struct dict *dict_driver, const char *uri, const struct dict_settings *set ATTR_UNUSED, struct dict **dict_r, const char **error_r) { pool_t pool = pool_alloconly_create("ldap dict", 2048); struct ldap_dict *dict = p_new(pool, struct ldap_dict, 1); dict->pool = pool; dict->dict = *dict_driver; dict->uri = p_strdup(pool, uri); dict->set = dict_ldap_settings_read(pool, uri, error_r); if (dict->set == NULL) { pool_unref(&pool); return -1; } if (dict_ldap_connect(dict, error_r) < 0) { pool_unref(&pool); return -1; } *dict_r = (struct dict*)dict; *error_r = NULL; return 0; } static void ldap_dict_deinit(struct dict *dict) { struct ldap_dict *ctx = (struct ldap_dict *)dict; ldap_client_deinit(&ctx->client); pool_unref(&ctx->pool); } static void ldap_dict_wait(struct dict *dict) { struct ldap_dict *ctx = (struct ldap_dict *)dict; i_assert(ctx->dict.ioloop == NULL); ctx->dict.prev_ioloop = current_ioloop; ctx->dict.ioloop = io_loop_create(); dict_switch_ioloop(dict); do { io_loop_run(current_ioloop); } while (ctx->pending > 0); io_loop_set_current(ctx->dict.prev_ioloop); dict_switch_ioloop(dict); io_loop_set_current(ctx->dict.ioloop); io_loop_destroy(&ctx->dict.ioloop); ctx->dict.prev_ioloop = NULL; } static bool ldap_dict_switch_ioloop(struct dict *dict) { struct ldap_dict *ctx = (struct ldap_dict *)dict; ldap_client_switch_ioloop(ctx->client); return ctx->pending > 0; } static void ldap_dict_lookup_done(const struct dict_lookup_result *result, void *ctx) { struct dict_lookup_result *res = ctx; res->ret = result->ret; res->value = t_strdup(result->value); res->error = t_strdup(result->error); } static void ldap_dict_lookup_callback(struct ldap_result *result, struct dict_ldap_op *op) { pool_t pool = op->pool; struct ldap_search_iterator *iter; const struct ldap_entry *entry; op->dict->pending--; if (ldap_result_has_failed(result)) { op->res.ret = -1; op->res.error = ldap_result_get_error(result); } else { iter = ldap_search_iterator_init(result); entry = ldap_search_iterator_next(iter); if (entry != NULL) { if (op->dict->set->debug > 0) i_debug("ldap_dict_lookup_callback got dn %s", ldap_entry_dn(entry)); /* try extract value */ const char *const *values = ldap_entry_get_attribute(entry, op->map->value_attribute); if (values != NULL) { const char **new_values; if (op->dict->set->debug > 0) i_debug("ldap_dict_lookup_callback got attribute %s", op->map->value_attribute); op->res.ret = 1; new_values = p_new(op->pool, const char *, 2); new_values[0] = p_strdup(op->pool, values[0]); op->res.values = new_values; op->res.value = op->res.values[0]; } else { if (op->dict->set->debug > 0) i_debug("ldap_dict_lookup_callback dit not get attribute %s", op->map->value_attribute); op->res.value = NULL; } } ldap_search_iterator_deinit(&iter); } if (op->dict->dict.prev_ioloop != NULL) io_loop_set_current(op->dict->dict.prev_ioloop); op->callback(&op->res, op->callback_ctx); if (op->dict->dict.prev_ioloop != NULL) { io_loop_set_current(op->dict->dict.ioloop); io_loop_stop(op->dict->dict.ioloop); } pool_unref(&pool); } static int ldap_dict_lookup(struct dict *dict, const struct dict_op_settings *set, pool_t pool, const char *key, const char **value_r, const char **error_r) { struct dict_lookup_result res; ldap_dict_lookup_async(dict, set, key, ldap_dict_lookup_done, &res); ldap_dict_wait(dict); if (res.ret < 0) { *error_r = res.error; return -1; } if (res.ret > 0) *value_r = p_strdup(pool, res.value); return res.ret; } /* static struct dict_iterate_context *ldap_dict_iterate_init(struct dict *dict, const char *const *paths, enum dict_iterate_flags flags) { return NULL; } static bool ldap_dict_iterate(struct dict_iterate_context *ctx, const char **key_r, const char **value_r) { return FALSE; } static int ldap_dict_iterate_deinit(struct dict_iterate_context *ctx) { return -1; } static struct dict_transaction_context ldap_dict_transaction_init(struct dict *dict); static int ldap_dict_transaction_commit(struct dict_transaction_context *ctx, bool async, dict_transaction_commit_callback_t *callback, void *context); static void ldap_dict_transaction_rollback(struct dict_transaction_context *ctx); static void ldap_dict_set(struct dict_transaction_context *ctx, const char *key, const char *value); static void ldap_dict_unset(struct dict_transaction_context *ctx, const char *key); static void ldap_dict_atomic_inc(struct dict_transaction_context *ctx, const char *key, long long diff); */ static void ldap_dict_lookup_async(struct dict *dict, const struct dict_op_settings *set, const char *key, dict_lookup_callback_t *callback, void *context) { struct ldap_search_input input; struct ldap_dict *ctx = (struct ldap_dict*)dict; struct dict_ldap_op *op; const char *error; pool_t oppool = pool_alloconly_create("ldap dict lookup", 64); string_t *query = str_new(oppool, 64); op = p_new(oppool, struct dict_ldap_op, 1); op->pool = oppool; op->dict = ctx; op->callback = callback; op->callback_ctx = context; op->txid = ctx->last_txid++; /* key needs to be transformed into something else */ ARRAY_TYPE(const_string) values; const char *attributes[2] = {0, 0}; t_array_init(&values, 8); const struct dict_ldap_map *map = ldap_dict_find_map(ctx, key, &values); if (map != NULL) { op->map = map; attributes[0] = map->value_attribute; /* build lookup */ i_zero(&input); input.base_dn = map->base_dn; input.scope = map->scope_val; if (!ldap_dict_build_query(set, map, &values, strncmp(key, DICT_PATH_PRIVATE, strlen(DICT_PATH_PRIVATE))==0, query, &error)) { op->res.error = error; callback(&op->res, context); pool_unref(&oppool); return; } input.filter = str_c(query); input.attributes = attributes; input.timeout_secs = ctx->set->timeout; ctx->pending++; ldap_search_start(ctx->client, &input, ldap_dict_lookup_callback, op); } else { op->res.error = "no such key"; callback(&op->res, context); pool_unref(&oppool); } } struct dict dict_driver_ldap = { .name = "ldap", { .init = ldap_dict_init, .deinit = ldap_dict_deinit, .wait = ldap_dict_wait, .lookup = ldap_dict_lookup, .lookup_async = ldap_dict_lookup_async, .switch_ioloop = ldap_dict_switch_ioloop, } }; #ifndef BUILTIN_LDAP /* Building a plugin */ void dict_ldap_init(struct module *module ATTR_UNUSED); void dict_ldap_deinit(void); void dict_ldap_init(struct module *module ATTR_UNUSED) { dict_driver_register(&dict_driver_ldap); } void dict_ldap_deinit(void) { ldap_clients_cleanup(); dict_driver_unregister(&dict_driver_ldap); } const char *dict_ldap_plugin_dependencies[] = { NULL }; #endif #endif dovecot-2.3.21.1/src/lib-dict-backend/test-dict-sql.c0000644000000000000000000002111014656633576017014 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "test-lib.h" #include "sql-api.h" #include "dict.h" #include "dict-private.h" #include "dict-sql.h" #include "dict-sql-private.h" #include "driver-test.h" struct dict_op_settings dict_op_settings = { .username = "testuser", }; static void test_setup(struct dict **dict_r) { const char *error = NULL; struct dict_settings set = { .base_dir = "." }; struct dict *dict = NULL; if (dict_init("mysql:" DICT_SRC_DIR "/dict.conf", &set, &dict, &error) < 0) i_fatal("cannot initialize dict: %s", error); *dict_r = dict; } static void test_teardown(struct dict **_dict) { struct dict *dict = *_dict; *_dict = NULL; if (dict != NULL) { dict_deinit(&dict); } } static void test_set_expected(struct dict *_dict, const struct test_driver_result *result) { struct sql_dict *dict = (struct sql_dict *)_dict; sql_driver_test_add_expected_result(dict->db, result); } static void test_lookup_one(void) { const char *value = NULL, *error = NULL; struct test_driver_result_set rset = { .rows = 1, .cols = 1, .col_names = (const char *[]){"value", NULL}, .row_data = (const char **[]){(const char*[]){"one", NULL}}, }; struct test_driver_result res = { .nqueries = 1, .queries = (const char *[]){"SELECT value FROM table WHERE a = 'hello' AND b = 'world'", NULL}, .result = &rset, }; const struct dict_op_settings set = { .username = "testuser", }; struct dict *dict; pool_t pool = pool_datastack_create(); test_begin("dict lookup one"); test_setup(&dict); test_set_expected(dict, &res); test_assert(dict_lookup(dict, &set, pool, "shared/dictmap/hello/world", &value, &error) == 1); test_assert_strcmp(value, "one"); if (error != NULL) i_error("dict_lookup failed: %s", error); test_teardown(&dict); test_end(); } static void test_atomic_inc(void) { const char *error; struct test_driver_result res = { .nqueries = 3, .queries = (const char *[]){ "UPDATE counters SET value=value+128 WHERE class = 'global' AND name = 'counter'", "UPDATE quota SET bytes=bytes+128,count=count+1 WHERE username = 'testuser'", "UPDATE quota SET bytes=bytes+128,count=count+1,folders=folders+123 WHERE username = 'testuser'", NULL}, .result = NULL, }; struct dict_op_settings set = { .username = "testuser", }; struct dict *dict; test_begin("dict atomic inc"); test_setup(&dict); test_set_expected(dict, &res); /* 1 field */ struct dict_transaction_context *ctx = dict_transaction_begin(dict, &set); dict_atomic_inc(ctx, "shared/counters/global/counter", 128); test_assert(dict_transaction_commit(&ctx, &error) == 0); if (error != NULL) i_error("dict_transaction_commit failed: %s", error); error = NULL; /* 2 fields */ ctx = dict_transaction_begin(dict, &set); dict_atomic_inc(ctx, "priv/quota/bytes", 128); dict_atomic_inc(ctx, "priv/quota/count", 1); test_assert(dict_transaction_commit(&ctx, &error) == 0); if (error != NULL) i_error("dict_transaction_commit failed: %s", error); error = NULL; /* 3 fields */ ctx = dict_transaction_begin(dict, &set); dict_atomic_inc(ctx, "priv/quota/bytes", 128); dict_atomic_inc(ctx, "priv/quota/count", 1); dict_atomic_inc(ctx, "priv/quota/folders", 123); test_assert(dict_transaction_commit(&ctx, &error) == 0); if (error != NULL) i_error("dict_transaction_commit failed: %s", error); test_teardown(&dict); test_end(); } static void test_set(void) { const char *error; struct test_driver_result res = { .affected_rows = 1, .nqueries = 3, .queries = (const char *[]){ "INSERT INTO counters (value,class,name) VALUES (128,'global','counter') ON DUPLICATE KEY UPDATE value=128", "INSERT INTO quota (bytes,count,username) VALUES (128,1,'testuser') ON DUPLICATE KEY UPDATE bytes=128,count=1", "INSERT INTO quota (bytes,count,folders,username) VALUES (128,1,123,'testuser') ON DUPLICATE KEY UPDATE bytes=128,count=1,folders=123", NULL}, .result = NULL, }; struct dict *dict; test_begin("dict set"); test_setup(&dict); test_set_expected(dict, &res); /* 1 field */ struct dict_transaction_context *ctx = dict_transaction_begin(dict, &dict_op_settings); dict_set(ctx, "shared/counters/global/counter", "128"); test_assert(dict_transaction_commit(&ctx, &error) == 1); if (error != NULL) i_error("dict_transaction_commit failed: %s", error); error = NULL; /* 2 fields */ ctx = dict_transaction_begin(dict, &dict_op_settings); dict_set(ctx, "priv/quota/bytes", "128"); dict_set(ctx, "priv/quota/count", "1"); test_assert(dict_transaction_commit(&ctx, &error) == 1); if (error != NULL) i_error("dict_transaction_commit failed: %s", error); error = NULL; /* 3 fields */ ctx = dict_transaction_begin(dict, &dict_op_settings); dict_set(ctx, "priv/quota/bytes", "128"); dict_set(ctx, "priv/quota/count", "1"); dict_set(ctx, "priv/quota/folders", "123"); test_assert(dict_transaction_commit(&ctx, &error) == 1); if (error != NULL) i_error("dict_transaction_commit failed: %s", error); test_teardown(&dict); test_end(); } static void test_unset(void) { const char *error; struct test_driver_result res = { .affected_rows = 1, .nqueries = 3, .queries = (const char *[]){ "DELETE FROM counters WHERE class = 'global' AND name = 'counter'", "DELETE FROM quota WHERE username = 'testuser'", "DELETE FROM quota WHERE username = 'testuser'", NULL}, .result = NULL, }; struct dict *dict; test_begin("dict unset"); test_setup(&dict); test_set_expected(dict, &res); struct dict_transaction_context *ctx = dict_transaction_begin(dict, &dict_op_settings); dict_unset(ctx, "shared/counters/global/counter"); test_assert(dict_transaction_commit(&ctx, &error) == 1); if (error != NULL) i_error("dict_transaction_commit failed: %s", error); error = NULL; ctx = dict_transaction_begin(dict, &dict_op_settings); dict_unset(ctx, "priv/quota/bytes"); dict_unset(ctx, "priv/quota/count"); test_assert(dict_transaction_commit(&ctx, &error) == 1); if (error != NULL) i_error("dict_transaction_commit failed: %s", error); test_teardown(&dict); test_end(); } static void test_iterate(void) { const char *key = NULL, *value = NULL, *error; struct test_driver_result_set rset = { .rows = 5, .cols = 2, .col_names = (const char *[]){"value", "name", NULL}, .row_data = (const char **[]){ (const char*[]){"one", "counter", NULL}, (const char*[]){"two", "counter", NULL}, (const char*[]){"three", "counter", NULL}, (const char*[]){"four", "counter", NULL}, (const char*[]){"five", "counter", NULL}, }, }; struct test_driver_result res = { .nqueries = 1, .queries = (const char *[]){ "SELECT value,name FROM counters WHERE class = 'global' AND name = 'counter'", NULL}, .result = &rset, }; struct dict *dict; test_begin("dict iterate"); test_setup(&dict); test_set_expected(dict, &res); struct dict_iterate_context *iter = dict_iterate_init(dict, &dict_op_settings, "shared/counters/global/counter", DICT_ITERATE_FLAG_EXACT_KEY); size_t idx = 0; while(dict_iterate(iter, &key, &value)) { i_assert(idx < rset.rows); test_assert_strcmp_idx(key, "shared/counters/global/counter", idx); test_assert_strcmp_idx(value, rset.row_data[idx][0], idx); idx++; } test_assert(idx == rset.rows); test_assert(dict_iterate_deinit(&iter, &error) == 0); if (error != NULL) i_error("dict_iterate_deinit failed: %s", error); error = NULL; res.queries = (const char*[]){ "SELECT value,name FROM counters WHERE class = 'global' AND name LIKE '%' AND name NOT LIKE '%/%'", NULL }; res.cur = 0; res.result->cur = 0; test_set_expected(dict, &res); iter = dict_iterate_init(dict, &dict_op_settings, "shared/counters/global/", 0); idx = 0; while(dict_iterate(iter, &key, &value)) { i_assert(idx < rset.rows); test_assert_strcmp_idx(key, "shared/counters/global/counter", idx); test_assert_strcmp_idx(value, rset.row_data[idx][0], idx); idx++; } test_assert(idx == rset.rows); test_assert(dict_iterate_deinit(&iter, &error) == 0); if (error != NULL) i_error("dict_iterate_deinit failed: %s", error); test_teardown(&dict); test_end(); } int main(void) { sql_drivers_init(); sql_driver_test_register(); dict_sql_register(); static void (*const test_functions[])(void) = { test_lookup_one, test_atomic_inc, test_set, test_unset, test_iterate, NULL }; int ret = test_run(test_functions); dict_sql_unregister(); sql_driver_test_unregister(); sql_drivers_deinit(); return ret; } dovecot-2.3.21.1/src/lib-dict-backend/Makefile.am0000644000000000000000000000565414656633576016226 00000000000000noinst_LTLIBRARIES = libdict_backend.la module_dictdir = $(moduledir)/dict dict_drivers = @dict_drivers@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-dict \ -I$(top_srcdir)/src/lib-ldap \ -I$(top_srcdir)/src/lib-sql \ -I$(top_srcdir)/src/lib-settings \ $(SQL_CFLAGS) NOPLUGIN_LDFLAGS = ldap_sources = \ dict-ldap.c \ dict-ldap-settings.c libdict_backend_la_SOURCES = \ dict-cdb.c \ dict-sql.c \ dict-sql-settings.c \ $(ldap_sources) libdict_backend_la_LIBADD = nodist_libdict_backend_la_SOURCES = \ dict-drivers-register.c noinst_HEADERS = \ dict-ldap-settings.h \ dict-sql.h \ dict-sql-private.h \ dict-sql-settings.h if LDAP_PLUGIN LIBDICT_LDAP = libdict_ldap.la libdict_ldap_la_DEPENDENCIES = $(LIBDOVECOT_LDAP) $(LIBDOVECOT_DEPS) libdict_ldap_la_LDFLAGS = -module -avoid-version libdict_ldap_la_LIBADD = $(LIBDOVECOT_LDAP) $(LIBDOVECOT) libdict_ldap_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD libdict_ldap_la_SOURCES = $(ldap_sources) else if HAVE_LDAP libdict_backend_la_LIBADD += $(LIBDOVECOT_LDAP) dict_drivers += ldap endif endif module_dict_LTLIBRARIES = \ $(LIBDICT_LDAP) EXTRA_DIST = dict.conf dict-drivers-register.c: Makefile $(top_builddir)/config.h rm -f $@ echo '/* this file automatically generated by Makefile */' >$@ echo '#include "lib.h"' >>$@ echo '#include "dict.h"' >>$@ echo '#include "ldap-client.h"' >>$@ echo '#include "dict-sql.h"' >>$@ for i in $(dict_drivers) null; do \ if [ "$${i}" != "null" ]; then \ echo "extern struct dict dict_driver_$${i};" >>$@ ; \ fi; \ done echo 'void dict_drivers_register_all(void) {' >>$@ echo 'dict_drivers_register_builtin();' >>$@ echo 'dict_sql_register();' >>$@ for i in $(dict_drivers) null; do \ if [ "$${i}" != "null" ]; then \ echo "dict_driver_register(&dict_driver_$${i});" >>$@ ; \ fi; \ done echo '}' >>$@ echo 'void dict_drivers_unregister_all(void) {' >>$@ echo '#ifdef BUILTIN_LDAP' >>$@ echo 'ldap_clients_cleanup();' >>$@ echo '#endif' >>$@ echo 'dict_drivers_unregister_builtin();' >>$@ echo 'dict_sql_unregister();' >>$@ for i in $(dict_drivers) null; do \ if [ "$${i}" != "null" ]; then \ echo "dict_driver_unregister(&dict_driver_$${i});" >>$@ ; \ fi; \ done echo '}' >>$@ distclean-generic: rm -f Makefile dict-drivers-register.c test_programs = \ test-dict-sql noinst_PROGRAMS = $(test_programs) test_dict_sql_CFLAGS = $(AM_CPPFLAGS) -DDICT_SRC_DIR=\"$(top_srcdir)/src/lib-dict-backend\" test_dict_sql_SOURCES = \ test-dict-sql.c test_dict_sql_LDADD = \ $(noinst_LTLIBRARIES) \ $(DICT_LIBS) \ ../lib-sql/libdriver_test.la \ ../lib-sql/libsql.la \ ../lib-dovecot/libdovecot.la test_dict_sql_DEPENDENCIES = \ $(noinst_LTLIBRARIES) \ ../lib-sql/libdriver_test.la \ ../lib-sql/libsql.la \ ../lib-dovecot/libdovecot.la check-local: for bin in $(test_programs) $(check_PROGRAMS); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done dovecot-2.3.21.1/src/lib-dict-backend/dict-sql.c0000644000000000000000000012727514656633576016062 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "istream.h" #include "hex-binary.h" #include "hash.h" #include "str.h" #include "sql-api-private.h" #include "sql-db-cache.h" #include "dict-private.h" #include "dict-sql-settings.h" #include "dict-sql.h" #include "dict-sql-private.h" #include #include #define DICT_SQL_MAX_UNUSED_CONNECTIONS 10 enum sql_recurse_type { SQL_DICT_RECURSE_NONE, SQL_DICT_RECURSE_ONE, SQL_DICT_RECURSE_FULL }; struct sql_dict_param { enum dict_sql_type value_type; const char *value_str; int64_t value_int64; const void *value_binary; size_t value_binary_size; }; ARRAY_DEFINE_TYPE(sql_dict_param, struct sql_dict_param); struct sql_dict_iterate_context { struct dict_iterate_context ctx; pool_t pool; enum dict_iterate_flags flags; const char *path; struct sql_result *result; string_t *key; const struct dict_sql_map *map; size_t key_prefix_len, pattern_prefix_len; unsigned int sql_fields_start_idx, next_map_idx; bool destroyed; bool synchronous_result; bool iter_query_sent; bool allow_null_map; /* allow next map to be NULL */ const char *error; }; struct sql_dict_inc_row { struct sql_dict_inc_row *prev; unsigned int rows; }; struct sql_dict_prev { const struct dict_sql_map *map; char *key; union { char *str; long long diff; } value; }; struct sql_dict_transaction_context { struct dict_transaction_context ctx; struct sql_transaction_context *sql_ctx; pool_t inc_row_pool; struct sql_dict_inc_row *inc_row; ARRAY(struct sql_dict_prev) prev_inc; ARRAY(struct sql_dict_prev) prev_set; dict_transaction_commit_callback_t *async_callback; void *async_context; char *error; }; static struct sql_db_cache *dict_sql_db_cache; static void sql_dict_prev_inc_flush(struct sql_dict_transaction_context *ctx); static void sql_dict_prev_set_flush(struct sql_dict_transaction_context *ctx); static void sql_dict_prev_inc_free(struct sql_dict_transaction_context *ctx); static void sql_dict_prev_set_free(struct sql_dict_transaction_context *ctx); static int sql_dict_init(struct dict *driver, const char *uri, const struct dict_settings *set, struct dict **dict_r, const char **error_r) { struct sql_settings sql_set; struct sql_dict *dict; pool_t pool; pool = pool_alloconly_create("sql dict", 2048); dict = p_new(pool, struct sql_dict, 1); dict->pool = pool; dict->dict = *driver; dict->set = dict_sql_settings_read(uri, error_r); if (dict->set == NULL) { pool_unref(&pool); return -1; } i_zero(&sql_set); sql_set.driver = driver->name; sql_set.connect_string = dict->set->connect; sql_set.event_parent = set->event_parent; if (sql_db_cache_new(dict_sql_db_cache, &sql_set, &dict->db, error_r) < 0) { pool_unref(&pool); return -1; } *dict_r = &dict->dict; return 0; } static void sql_dict_deinit(struct dict *_dict) { struct sql_dict *dict = (struct sql_dict *)_dict; sql_unref(&dict->db); pool_unref(&dict->pool); } static void sql_dict_wait(struct dict *_dict) { struct sql_dict *dict = (struct sql_dict *)_dict; sql_wait(dict->db); } /* Try to match path to map->pattern. For example pattern="shared/x/$/$/y" and path="shared/x/1/2/y", this is match and pattern_values=[1, 2]. */ static bool dict_sql_map_match(const struct dict_sql_map *map, const char *path, ARRAY_TYPE(const_string) *pattern_values, size_t *pat_len_r, size_t *path_len_r, bool partial_ok, bool recurse) { const char *path_start = path; const char *pat, *field, *p; size_t len; array_clear(pattern_values); pat = map->pattern; while (*pat != '\0' && *path != '\0') { if (*pat == '$') { /* variable */ pat++; if (*pat == '\0') { /* pattern ended with this variable, it'll match the rest of the path */ len = strlen(path); if (partial_ok) { /* iterating - the last field never matches fully. if there's a trailing '/', drop it. */ pat--; if (path[len-1] == '/') { field = t_strndup(path, len-1); array_push_back(pattern_values, &field); } else { array_push_back(pattern_values, &path); } } else { array_push_back(pattern_values, &path); path += len; } *path_len_r = path - path_start; *pat_len_r = pat - map->pattern; return TRUE; } /* pattern matches until the next '/' in path */ p = strchr(path, '/'); if (p != NULL) { field = t_strdup_until(path, p); array_push_back(pattern_values, &field); path = p; } else { /* no '/' anymore, but it'll still match a partial */ array_push_back(pattern_values, &path); path += strlen(path); pat++; } } else if (*pat == *path) { pat++; path++; } else { return FALSE; } } *path_len_r = path - path_start; *pat_len_r = pat - map->pattern; if (*pat == '\0') return *path == '\0'; else if (!partial_ok) return FALSE; else { /* partial matches must end with '/'. */ if (pat != map->pattern && pat[-1] != '/') return FALSE; /* if we're not recursing, there should be only one $variable left. */ if (recurse) return TRUE; return pat[0] == '$' && strchr(pat, '/') == NULL; } } static const struct dict_sql_map * sql_dict_find_map(struct sql_dict *dict, const char *path, ARRAY_TYPE(const_string) *pattern_values) { const struct dict_sql_map *maps; unsigned int i, count; size_t len; t_array_init(pattern_values, dict->set->max_pattern_fields_count); maps = array_get(&dict->set->maps, &count); for (i = 0; i < count; i++) { if (dict_sql_map_match(&maps[i], path, pattern_values, &len, &len, FALSE, FALSE)) return &maps[i]; } return NULL; } static void sql_dict_statement_bind(struct sql_statement *stmt, unsigned int column_idx, const struct sql_dict_param *param) { switch (param->value_type) { case DICT_SQL_TYPE_STRING: sql_statement_bind_str(stmt, column_idx, param->value_str); break; case DICT_SQL_TYPE_INT: case DICT_SQL_TYPE_UINT: sql_statement_bind_int64(stmt, column_idx, param->value_int64); break; case DICT_SQL_TYPE_HEXBLOB: sql_statement_bind_binary(stmt, column_idx, param->value_binary, param->value_binary_size); break; } } static struct sql_statement * sql_dict_statement_init(struct sql_dict *dict, const char *query, const ARRAY_TYPE(sql_dict_param) *params) { struct sql_statement *stmt; struct sql_prepared_statement *prep_stmt; const struct sql_dict_param *param; if ((sql_get_flags(dict->db) & SQL_DB_FLAG_PREP_STATEMENTS) != 0) { prep_stmt = sql_prepared_statement_init(dict->db, query); stmt = sql_statement_init_prepared(prep_stmt); sql_prepared_statement_unref(&prep_stmt); } else { /* Prepared statements not supported by the backend. Just use regular statements to avoid wasting memory. */ stmt = sql_statement_init(dict->db, query); } array_foreach(params, param) { sql_dict_statement_bind(stmt, array_foreach_idx(params, param), param); } return stmt; } static int sql_dict_value_get(const struct dict_sql_map *map, enum dict_sql_type value_type, const char *field_name, const char *value, const char *value_suffix, ARRAY_TYPE(sql_dict_param) *params, const char **error_r) { struct sql_dict_param *param; buffer_t *buf; param = array_append_space(params); param->value_type = value_type; switch (value_type) { case DICT_SQL_TYPE_STRING: if (value_suffix[0] != '\0') value = t_strconcat(value, value_suffix, NULL); param->value_str = value; return 0; case DICT_SQL_TYPE_INT: if (value_suffix[0] != '\0' || str_to_int64(value, ¶m->value_int64) < 0) { *error_r = t_strdup_printf( "%s field's value isn't 64bit signed integer: %s%s (in pattern: %s)", field_name, value, value_suffix, map->pattern); return -1; } return 0; case DICT_SQL_TYPE_UINT: if (value_suffix[0] != '\0' || value[0] == '-' || str_to_int64(value, ¶m->value_int64) < 0) { *error_r = t_strdup_printf( "%s field's value isn't 64bit unsigned integer: %s%s (in pattern: %s)", field_name, value, value_suffix, map->pattern); return -1; } return 0; case DICT_SQL_TYPE_HEXBLOB: break; } buf = t_buffer_create(strlen(value)/2); if (hex_to_binary(value, buf) < 0) { /* we shouldn't get untrusted input here. it's also a bit annoying to handle this error. */ *error_r = t_strdup_printf("%s field's value isn't hexblob: %s (in pattern: %s)", field_name, value, map->pattern); return -1; } str_append(buf, value_suffix); param->value_binary = buf->data; param->value_binary_size = buf->used; return 0; } static int sql_dict_field_get_value(const struct dict_sql_map *map, const struct dict_sql_field *field, const char *value, const char *value_suffix, ARRAY_TYPE(sql_dict_param) *params, const char **error_r) { return sql_dict_value_get(map, field->value_type, field->name, value, value_suffix, params, error_r); } static int sql_dict_where_build(const char *username, const struct dict_sql_map *map, const ARRAY_TYPE(const_string) *values_arr, bool add_username, enum sql_recurse_type recurse_type, string_t *query, ARRAY_TYPE(sql_dict_param) *params, const char **error_r) { const struct dict_sql_field *pattern_fields; const char *const *pattern_values; unsigned int i, count, count2, exact_count; pattern_fields = array_get(&map->pattern_fields, &count); pattern_values = array_get(values_arr, &count2); /* if we came here from iteration code there may be fewer pattern_values */ i_assert(count2 <= count); if (count2 == 0 && !add_username) { /* we want everything */ return 0; } str_append(query, " WHERE"); exact_count = count == count2 && recurse_type != SQL_DICT_RECURSE_NONE ? count2-1 : count2; if (exact_count != array_count(values_arr)) { *error_r = t_strdup_printf("Key continues past the matched pattern %s", map->pattern); return -1; } for (i = 0; i < exact_count; i++) { if (i > 0) str_append(query, " AND"); str_printfa(query, " %s = ?", pattern_fields[i].name); if (sql_dict_field_get_value(map, &pattern_fields[i], pattern_values[i], "", params, error_r) < 0) return -1; } switch (recurse_type) { case SQL_DICT_RECURSE_NONE: break; case SQL_DICT_RECURSE_ONE: if (i > 0) str_append(query, " AND"); if (i < count2) { str_printfa(query, " %s LIKE ?", pattern_fields[i].name); if (sql_dict_field_get_value(map, &pattern_fields[i], pattern_values[i], "/%", params, error_r) < 0) return -1; str_printfa(query, " AND %s NOT LIKE ?", pattern_fields[i].name); if (sql_dict_field_get_value(map, &pattern_fields[i], pattern_values[i], "/%/%", params, error_r) < 0) return -1; } else { str_printfa(query, " %s LIKE '%%' AND " "%s NOT LIKE '%%/%%'", pattern_fields[i].name, pattern_fields[i].name); } break; case SQL_DICT_RECURSE_FULL: if (i < count2) { if (i > 0) str_append(query, " AND"); str_printfa(query, " %s LIKE ", pattern_fields[i].name); if (sql_dict_field_get_value(map, &pattern_fields[i], pattern_values[i], "/%", params, error_r) < 0) return -1; } break; } if (add_username) { struct sql_dict_param *param = array_append_space(params); if (count2 > 0) str_append(query, " AND"); str_printfa(query, " %s = ?", map->username_field); param->value_type = DICT_SQL_TYPE_STRING; param->value_str = t_strdup(username); } return 0; } static int sql_lookup_get_query(struct sql_dict *dict, const struct dict_op_settings *set, const char *key, const struct dict_sql_map **map_r, struct sql_statement **stmt_r, const char **error_r) { const struct dict_sql_map *map; ARRAY_TYPE(const_string) pattern_values; const char *error; map = *map_r = sql_dict_find_map(dict, key, &pattern_values); if (map == NULL) { *error_r = t_strdup_printf( "sql dict lookup: Invalid/unmapped key: %s", key); return -1; } string_t *query = t_str_new(256); ARRAY_TYPE(sql_dict_param) params; t_array_init(¶ms, 4); str_printfa(query, "SELECT %s FROM %s", map->value_field, map->table); if (sql_dict_where_build(set->username, map, &pattern_values, key[0] == DICT_PATH_PRIVATE[0], SQL_DICT_RECURSE_NONE, query, ¶ms, &error) < 0) { *error_r = t_strdup_printf( "sql dict lookup: Failed to lookup key %s: %s", key, error); return -1; } *stmt_r = sql_dict_statement_init(dict, str_c(query), ¶ms); return 0; } static const char * sql_dict_result_unescape(enum dict_sql_type type, pool_t pool, struct sql_result *result, unsigned int result_idx) { const unsigned char *data; size_t size; const char *value; string_t *str; switch (type) { case DICT_SQL_TYPE_STRING: case DICT_SQL_TYPE_INT: case DICT_SQL_TYPE_UINT: value = sql_result_get_field_value(result, result_idx); return value == NULL ? "" : p_strdup(pool, value); case DICT_SQL_TYPE_HEXBLOB: break; } data = sql_result_get_field_value_binary(result, result_idx, &size); str = str_new(pool, size*2 + 1); binary_to_hex_append(str, data, size); return str_c(str); } static const char * sql_dict_result_unescape_value(const struct dict_sql_map *map, pool_t pool, struct sql_result *result) { return sql_dict_result_unescape(map->value_types[0], pool, result, 0); } static const char *const * sql_dict_result_unescape_values(const struct dict_sql_map *map, pool_t pool, struct sql_result *result) { const char **values; unsigned int i; values = p_new(pool, const char *, map->values_count + 1); for (i = 0; i < map->values_count; i++) { values[i] = sql_dict_result_unescape(map->value_types[i], pool, result, i); } return values; } static const char * sql_dict_result_unescape_field(const struct dict_sql_map *map, pool_t pool, struct sql_result *result, unsigned int result_idx, unsigned int sql_field_idx) { const struct dict_sql_field *sql_field; sql_field = array_idx(&map->pattern_fields, sql_field_idx); return sql_dict_result_unescape(sql_field->value_type, pool, result, result_idx); } static int sql_dict_lookup(struct dict *_dict, const struct dict_op_settings *set, pool_t pool, const char *key, const char **value_r, const char **error_r) { struct sql_dict *dict = (struct sql_dict *)_dict; const struct dict_sql_map *map; struct sql_statement *stmt; struct sql_result *result = NULL; int ret; *value_r = NULL; if (sql_lookup_get_query(dict, set, key, &map, &stmt, error_r) < 0) return -1; result = sql_statement_query_s(&stmt); ret = sql_result_next_row(result); if (ret < 0) { *error_r = t_strdup_printf("dict sql lookup failed: %s", sql_result_get_error(result)); } else if (ret > 0) { *value_r = sql_dict_result_unescape_value(map, pool, result); } sql_result_unref(result); return ret; } struct sql_dict_lookup_context { const struct dict_sql_map *map; dict_lookup_callback_t *callback; void *context; }; static void sql_dict_lookup_async_callback(struct sql_result *sql_result, struct sql_dict_lookup_context *ctx) { struct dict_lookup_result result; i_zero(&result); result.ret = sql_result_next_row(sql_result); if (result.ret < 0) result.error = sql_result_get_error(sql_result); else if (result.ret > 0) { result.values = sql_dict_result_unescape_values(ctx->map, pool_datastack_create(), sql_result); result.value = result.values[0]; if (result.value == NULL) { /* NULL value returned. we'll treat this as "not found", which is probably what is usually wanted. */ result.ret = 0; } } ctx->callback(&result, ctx->context); i_free(ctx); } static void sql_dict_lookup_async(struct dict *_dict, const struct dict_op_settings *set, const char *key, dict_lookup_callback_t *callback, void *context) { struct sql_dict *dict = (struct sql_dict *)_dict; const struct dict_sql_map *map; struct sql_dict_lookup_context *ctx; struct sql_statement *stmt; const char *error; if (sql_lookup_get_query(dict, set, key, &map, &stmt, &error) < 0) { struct dict_lookup_result result; i_zero(&result); result.ret = -1; result.error = error; callback(&result, context); } else { ctx = i_new(struct sql_dict_lookup_context, 1); ctx->callback = callback; ctx->context = context; ctx->map = map; sql_statement_query(&stmt, sql_dict_lookup_async_callback, ctx); } } static const struct dict_sql_map * sql_dict_iterate_find_next_map(struct sql_dict_iterate_context *ctx, ARRAY_TYPE(const_string) *pattern_values) { struct sql_dict *dict = (struct sql_dict *)ctx->ctx.dict; const struct dict_sql_map *maps; unsigned int i, count; size_t pat_len, path_len; bool recurse = (ctx->flags & DICT_ITERATE_FLAG_RECURSE) != 0; t_array_init(pattern_values, dict->set->max_pattern_fields_count); maps = array_get(&dict->set->maps, &count); for (i = ctx->next_map_idx; i < count; i++) { if (dict_sql_map_match(&maps[i], ctx->path, pattern_values, &pat_len, &path_len, TRUE, recurse) && (recurse || array_count(pattern_values)+1 >= array_count(&maps[i].pattern_fields))) { ctx->key_prefix_len = path_len; ctx->pattern_prefix_len = pat_len; ctx->next_map_idx = i + 1; str_truncate(ctx->key, 0); str_append(ctx->key, ctx->path); return &maps[i]; } } return NULL; } static int sql_dict_iterate_build_next_query(struct sql_dict_iterate_context *ctx, struct sql_statement **stmt_r, const char **error_r) { struct sql_dict *dict = (struct sql_dict *)ctx->ctx.dict; const struct dict_op_settings_private *set = &ctx->ctx.set; const struct dict_sql_map *map; ARRAY_TYPE(const_string) pattern_values; const struct dict_sql_field *pattern_fields; enum sql_recurse_type recurse_type; unsigned int i, count; map = sql_dict_iterate_find_next_map(ctx, &pattern_values); /* NULL map is allowed if we have already done some lookups */ if (map == NULL) { if (!ctx->allow_null_map) { *error_r = "Invalid/unmapped path"; return -1; } return 0; } if (ctx->result != NULL) { sql_result_unref(ctx->result); ctx->result = NULL; } string_t *query = t_str_new(256); str_append(query, "SELECT "); if ((ctx->flags & DICT_ITERATE_FLAG_NO_VALUE) == 0) str_printfa(query, "%s,", map->value_field); /* get all missing fields */ pattern_fields = array_get(&map->pattern_fields, &count); i = array_count(&pattern_values); if (i == count) { /* we always want to know the last field since we're iterating its children */ i_assert(i > 0); i--; } ctx->sql_fields_start_idx = i; for (; i < count; i++) str_printfa(query, "%s,", pattern_fields[i].name); str_truncate(query, str_len(query)-1); str_printfa(query, " FROM %s", map->table); if ((ctx->flags & DICT_ITERATE_FLAG_RECURSE) != 0) recurse_type = SQL_DICT_RECURSE_FULL; else if ((ctx->flags & DICT_ITERATE_FLAG_EXACT_KEY) != 0) recurse_type = SQL_DICT_RECURSE_NONE; else recurse_type = SQL_DICT_RECURSE_ONE; ARRAY_TYPE(sql_dict_param) params; t_array_init(¶ms, 4); bool add_username = (ctx->path[0] == DICT_PATH_PRIVATE[0]); if (sql_dict_where_build(set->username, map, &pattern_values, add_username, recurse_type, query, ¶ms, error_r) < 0) return -1; if ((ctx->flags & DICT_ITERATE_FLAG_SORT_BY_KEY) != 0) { str_append(query, " ORDER BY "); for (i = 0; i < count; i++) { str_printfa(query, "%s", pattern_fields[i].name); if (i < count-1) str_append_c(query, ','); } } else if ((ctx->flags & DICT_ITERATE_FLAG_SORT_BY_VALUE) != 0) str_printfa(query, " ORDER BY %s", map->value_field); if (ctx->ctx.max_rows > 0) { i_assert(ctx->ctx.row_count < ctx->ctx.max_rows); str_printfa(query, " LIMIT %"PRIu64, ctx->ctx.max_rows - ctx->ctx.row_count); } *stmt_r = sql_dict_statement_init(dict, str_c(query), ¶ms); ctx->map = map; return 1; } static void sql_dict_iterate_callback(struct sql_result *result, struct sql_dict_iterate_context *ctx) { if (!ctx->destroyed) { sql_result_ref(result); ctx->result = result; if (ctx->ctx.async_callback != NULL && !ctx->synchronous_result) ctx->ctx.async_callback(ctx->ctx.async_context); } pool_t pool_copy = ctx->pool; pool_unref(&pool_copy); } static int sql_dict_iterate_next_query(struct sql_dict_iterate_context *ctx) { struct sql_statement *stmt; const char *error; int ret; ret = sql_dict_iterate_build_next_query(ctx, &stmt, &error); if (ret <= 0) { /* this is expected error */ if (ret == 0) return ret; /* failed */ ctx->error = p_strdup_printf(ctx->pool, "sql dict iterate failed for %s: %s", ctx->path, error); return -1; } if ((ctx->flags & DICT_ITERATE_FLAG_ASYNC) == 0) { ctx->result = sql_statement_query_s(&stmt); } else { i_assert(ctx->result == NULL); ctx->synchronous_result = TRUE; pool_ref(ctx->pool); sql_statement_query(&stmt, sql_dict_iterate_callback, ctx); ctx->synchronous_result = FALSE; } return ret; } static struct dict_iterate_context * sql_dict_iterate_init(struct dict *_dict, const struct dict_op_settings *set ATTR_UNUSED, const char *path, enum dict_iterate_flags flags) { struct sql_dict_iterate_context *ctx; pool_t pool; pool = pool_alloconly_create("sql dict iterate", 512); ctx = p_new(pool, struct sql_dict_iterate_context, 1); ctx->ctx.dict = _dict; ctx->pool = pool; ctx->flags = flags; ctx->path = p_strdup(pool, path); ctx->key = str_new(pool, 256); return &ctx->ctx; } static bool sql_dict_iterate(struct dict_iterate_context *_ctx, const char **key_r, const char *const **values_r) { struct sql_dict_iterate_context *ctx = (struct sql_dict_iterate_context *)_ctx; const char *p, *value; unsigned int i, sql_field_i, count; int ret; _ctx->has_more = FALSE; if (ctx->error != NULL) return FALSE; if (!ctx->iter_query_sent) { ctx->iter_query_sent = TRUE; if (sql_dict_iterate_next_query(ctx) <= 0) return FALSE; } if (ctx->result == NULL) { /* wait for async lookup to finish */ i_assert((ctx->flags & DICT_ITERATE_FLAG_ASYNC) != 0); _ctx->has_more = TRUE; return FALSE; } ret = sql_result_next_row(ctx->result); while (ret == SQL_RESULT_NEXT_MORE) { if ((ctx->flags & DICT_ITERATE_FLAG_ASYNC) == 0) sql_result_more_s(&ctx->result); else { /* get more results asynchronously */ ctx->synchronous_result = TRUE; pool_ref(ctx->pool); sql_result_more(&ctx->result, sql_dict_iterate_callback, ctx); ctx->synchronous_result = FALSE; if (ctx->result == NULL) { _ctx->has_more = TRUE; return FALSE; } } ret = sql_result_next_row(ctx->result); } if (ret == 0) { /* see if there are more results in the next map. don't do it if we're looking for an exact match, since we already should have handled it. */ if ((ctx->flags & DICT_ITERATE_FLAG_EXACT_KEY) != 0) return FALSE; ctx->iter_query_sent = FALSE; /* we have gotten *SOME* results, so can allow unmapped next key now. */ ctx->allow_null_map = TRUE; return sql_dict_iterate(_ctx, key_r, values_r); } if (ret < 0) { ctx->error = p_strdup_printf(ctx->pool, "dict sql iterate failed: %s", sql_result_get_error(ctx->result)); return FALSE; } /* convert fetched row to dict key */ str_truncate(ctx->key, ctx->key_prefix_len); if (ctx->key_prefix_len > 0 && str_c(ctx->key)[ctx->key_prefix_len-1] != '/') str_append_c(ctx->key, '/'); count = sql_result_get_fields_count(ctx->result); i = (ctx->flags & DICT_ITERATE_FLAG_NO_VALUE) != 0 ? 0 : ctx->map->values_count; sql_field_i = ctx->sql_fields_start_idx; for (p = ctx->map->pattern + ctx->pattern_prefix_len; *p != '\0'; p++) { if (*p != '$') str_append_c(ctx->key, *p); else { i_assert(i < count); value = sql_dict_result_unescape_field(ctx->map, pool_datastack_create(), ctx->result, i, sql_field_i); if (value != NULL) str_append(ctx->key, value); i++; sql_field_i++; } } *key_r = str_c(ctx->key); if ((ctx->flags & DICT_ITERATE_FLAG_NO_VALUE) == 0) { *values_r = sql_dict_result_unescape_values(ctx->map, pool_datastack_create(), ctx->result); } return TRUE; } static int sql_dict_iterate_deinit(struct dict_iterate_context *_ctx, const char **error_r) { struct sql_dict_iterate_context *ctx = (struct sql_dict_iterate_context *)_ctx; int ret = ctx->error != NULL ? -1 : 0; *error_r = t_strdup(ctx->error); if (ctx->result != NULL) sql_result_unref(ctx->result); ctx->destroyed = TRUE; pool_t pool_copy = ctx->pool; pool_unref(&pool_copy); return ret; } static struct dict_transaction_context * sql_dict_transaction_init(struct dict *_dict) { struct sql_dict *dict = (struct sql_dict *)_dict; struct sql_dict_transaction_context *ctx; ctx = i_new(struct sql_dict_transaction_context, 1); ctx->ctx.dict = _dict; ctx->sql_ctx = sql_transaction_begin(dict->db); return &ctx->ctx; } static void sql_dict_transaction_free(struct sql_dict_transaction_context *ctx) { if (array_is_created(&ctx->prev_inc)) sql_dict_prev_inc_free(ctx); if (array_is_created(&ctx->prev_set)) sql_dict_prev_set_free(ctx); pool_unref(&ctx->inc_row_pool); i_free(ctx->error); i_free(ctx); } static bool sql_dict_transaction_has_nonexistent(struct sql_dict_transaction_context *ctx) { struct sql_dict_inc_row *inc_row; for (inc_row = ctx->inc_row; inc_row != NULL; inc_row = inc_row->prev) { i_assert(inc_row->rows != UINT_MAX); if (inc_row->rows == 0) return TRUE; } return FALSE; } static void sql_dict_transaction_commit_callback(const struct sql_commit_result *sql_result, struct sql_dict_transaction_context *ctx) { struct dict_commit_result result; i_zero(&result); if (sql_result->error == NULL) result.ret = sql_dict_transaction_has_nonexistent(ctx) ? DICT_COMMIT_RET_NOTFOUND : DICT_COMMIT_RET_OK; else { result.error = t_strdup_printf("sql dict: commit failed: %s", sql_result->error); switch (sql_result->error_type) { case SQL_RESULT_ERROR_TYPE_UNKNOWN: default: result.ret = DICT_COMMIT_RET_FAILED; break; case SQL_RESULT_ERROR_TYPE_WRITE_UNCERTAIN: result.ret = DICT_COMMIT_RET_WRITE_UNCERTAIN; break; } } if (ctx->async_callback != NULL) ctx->async_callback(&result, ctx->async_context); else if (result.ret < 0) i_error("%s", result.error); sql_dict_transaction_free(ctx); } static void sql_dict_transaction_commit(struct dict_transaction_context *_ctx, bool async, dict_transaction_commit_callback_t *callback, void *context) { struct sql_dict_transaction_context *ctx = (struct sql_dict_transaction_context *)_ctx; const char *error; struct dict_commit_result result; /* flush any pending set/inc */ if (array_is_created(&ctx->prev_inc)) sql_dict_prev_inc_flush(ctx); if (array_is_created(&ctx->prev_set)) sql_dict_prev_set_flush(ctx); /* note that the above calls might still set ctx->error */ i_zero(&result); result.ret = DICT_COMMIT_RET_FAILED; result.error = t_strdup(ctx->error); if (ctx->error != NULL) { sql_transaction_rollback(&ctx->sql_ctx); } else if (!_ctx->changed) { /* nothing changed, no need to commit */ sql_transaction_rollback(&ctx->sql_ctx); result.ret = DICT_COMMIT_RET_OK; } else if (async) { ctx->async_callback = callback; ctx->async_context = context; sql_transaction_commit(&ctx->sql_ctx, sql_dict_transaction_commit_callback, ctx); return; } else if (sql_transaction_commit_s(&ctx->sql_ctx, &error) < 0) { result.error = t_strdup_printf( "sql dict: commit failed: %s", error); } else { if (sql_dict_transaction_has_nonexistent(ctx)) result.ret = DICT_COMMIT_RET_NOTFOUND; else result.ret = DICT_COMMIT_RET_OK; } sql_dict_transaction_free(ctx); callback(&result, context); } static void sql_dict_transaction_rollback(struct dict_transaction_context *_ctx) { struct sql_dict_transaction_context *ctx = (struct sql_dict_transaction_context *)_ctx; sql_transaction_rollback(&ctx->sql_ctx); sql_dict_transaction_free(ctx); } static struct sql_statement * sql_dict_transaction_stmt_init(struct sql_dict_transaction_context *ctx, const char *query, const ARRAY_TYPE(sql_dict_param) *params) { struct sql_dict *dict = (struct sql_dict *)ctx->ctx.dict; struct sql_statement *stmt = sql_dict_statement_init(dict, query, params); if (ctx->ctx.timestamp.tv_sec != 0) sql_statement_set_timestamp(stmt, &ctx->ctx.timestamp); if (ctx->ctx.set.hide_log_values) sql_statement_set_no_log_expanded_values(stmt, ctx->ctx.set.hide_log_values); return stmt; } struct dict_sql_build_query_field { const struct dict_sql_map *map; const char *value; }; struct dict_sql_build_query { struct sql_dict *dict; ARRAY(struct dict_sql_build_query_field) fields; const ARRAY_TYPE(const_string) *pattern_values; bool add_username; }; static int sql_dict_set_query(struct sql_dict_transaction_context *ctx, const struct dict_sql_build_query *build, struct sql_statement **stmt_r, const char **error_r) { struct sql_dict *dict = build->dict; const struct dict_sql_build_query_field *fields; const struct dict_sql_field *pattern_fields; ARRAY_TYPE(sql_dict_param) params; const char *const *pattern_values; unsigned int i, field_count, count, count2; string_t *prefix, *suffix; fields = array_get(&build->fields, &field_count); i_assert(field_count > 0); t_array_init(¶ms, 4); prefix = t_str_new(64); suffix = t_str_new(256); /* SQL table is guaranteed to be the same for all fields. Build all the SQL field names into prefix and '?' placeholders for each value into the suffix. The actual field values will be added into params[]. */ str_printfa(prefix, "INSERT INTO %s", fields[0].map->table); str_append(prefix, " ("); str_append(suffix, ") VALUES ("); for (i = 0; i < field_count; i++) { if (i > 0) { str_append_c(prefix, ','); str_append_c(suffix, ','); } str_append(prefix, t_strcut(fields[i].map->value_field, ',')); enum dict_sql_type value_type = fields[i].map->value_types[0]; str_append_c(suffix, '?'); if (sql_dict_value_get(fields[i].map, value_type, "value", fields[i].value, "", ¶ms, error_r) < 0) return -1; } if (build->add_username) { struct sql_dict_param *param = array_append_space(¶ms); str_printfa(prefix, ",%s", fields[0].map->username_field); str_append(suffix, ",?"); param->value_type = DICT_SQL_TYPE_STRING; param->value_str = ctx->ctx.set.username; } /* add the variable fields that were parsed from the path */ pattern_fields = array_get(&fields[0].map->pattern_fields, &count); pattern_values = array_get(build->pattern_values, &count2); i_assert(count == count2); for (i = 0; i < count; i++) { str_printfa(prefix, ",%s", pattern_fields[i].name); str_append(suffix, ",?"); if (sql_dict_field_get_value(fields[0].map, &pattern_fields[i], pattern_values[i], "", ¶ms, error_r) < 0) return -1; } str_append_str(prefix, suffix); str_append_c(prefix, ')'); enum sql_db_flags flags = sql_get_flags(dict->db); if ((flags & SQL_DB_FLAG_ON_DUPLICATE_KEY) != 0) str_append(prefix, " ON DUPLICATE KEY UPDATE "); else if ((flags & SQL_DB_FLAG_ON_CONFLICT_DO) != 0) { str_append(prefix, " ON CONFLICT ("); for (i = 0; i < count; i++) { if (i > 0) str_append_c(prefix, ','); str_append(prefix, pattern_fields[i].name); } if (build->add_username) { if (count > 0) str_append_c(prefix, ','); str_append(prefix, fields[0].map->username_field); } str_append(prefix, ") DO UPDATE SET "); } else { *stmt_r = sql_dict_transaction_stmt_init(ctx, str_c(prefix), ¶ms); return 0; } /* If the row already exists, UPDATE it instead. The pattern_values don't need to be updated here, because they are expected to be part of the row's primary key. */ for (i = 0; i < field_count; i++) { const char *first_value_field = t_strcut(fields[i].map->value_field, ','); if (i > 0) str_append_c(prefix, ','); str_append(prefix, first_value_field); str_append_c(prefix, '='); enum dict_sql_type value_type = fields[i].map->value_types[0]; str_append_c(prefix, '?'); if (sql_dict_value_get(fields[i].map, value_type, "value", fields[i].value, "", ¶ms, error_r) < 0) return -1; } *stmt_r = sql_dict_transaction_stmt_init(ctx, str_c(prefix), ¶ms); return 0; } static int sql_dict_update_query(const struct dict_sql_build_query *build, const struct dict_op_settings_private *set, const char **query_r, ARRAY_TYPE(sql_dict_param) *params, const char **error_r) { const struct dict_sql_build_query_field *fields; unsigned int i, field_count; string_t *query; fields = array_get(&build->fields, &field_count); i_assert(field_count > 0); query = t_str_new(64); str_printfa(query, "UPDATE %s SET ", fields[0].map->table); for (i = 0; i < field_count; i++) { const char *first_value_field = t_strcut(fields[i].map->value_field, ','); if (i > 0) str_append_c(query, ','); str_printfa(query, "%s=%s+?", first_value_field, first_value_field); } if (sql_dict_where_build(set->username, fields[0].map, build->pattern_values, build->add_username, SQL_DICT_RECURSE_NONE, query, params, error_r) < 0) return -1; *query_r = str_c(query); return 0; } static void sql_dict_prev_set_free(struct sql_dict_transaction_context *ctx) { struct sql_dict_prev *prev_set; array_foreach_modifiable(&ctx->prev_set, prev_set) { i_free(prev_set->value.str); i_free(prev_set->key); } array_free(&ctx->prev_set); } static void sql_dict_prev_set_flush(struct sql_dict_transaction_context *ctx) { struct sql_dict *dict = (struct sql_dict *)ctx->ctx.dict; const struct sql_dict_prev *prev_sets; unsigned int count; struct sql_statement *stmt; ARRAY_TYPE(const_string) pattern_values; struct dict_sql_build_query build; struct dict_sql_build_query_field *field; const char *error; i_assert(array_is_created(&ctx->prev_set)); if (ctx->error != NULL) { sql_dict_prev_set_free(ctx); return; } prev_sets = array_get(&ctx->prev_set, &count); i_assert(count > 0); /* Get the variable values from the dict path. We already verified that these are all exactly the same for everything in prev_sets. */ if (sql_dict_find_map(dict, prev_sets[0].key, &pattern_values) == NULL) i_unreached(); /* this was already checked */ i_zero(&build); build.dict = dict; build.pattern_values = &pattern_values; build.add_username = (prev_sets[0].key[0] == DICT_PATH_PRIVATE[0]); /* build.fields[] is used to get the map { value_field } for the SQL field names, as well as the values for them. Example: INSERT INTO ... (build.fields[0].map->value_field, ...[1], ...) VALUES (build.fields[0].value, ...[1], ...) */ t_array_init(&build.fields, count); for (unsigned int i = 0; i < count; i++) { i_assert(build.add_username == (prev_sets[i].key[0] == DICT_PATH_PRIVATE[0])); field = array_append_space(&build.fields); field->map = prev_sets[i].map; field->value = prev_sets[i].value.str; } if (sql_dict_set_query(ctx, &build, &stmt, &error) < 0) { ctx->error = i_strdup_printf( "dict-sql: Failed to set %u fields (first %s): %s", count, prev_sets[0].key, error); } else { sql_update_stmt(ctx->sql_ctx, &stmt); } sql_dict_prev_set_free(ctx); } static void sql_dict_unset(struct dict_transaction_context *_ctx, const char *key) { struct sql_dict_transaction_context *ctx = (struct sql_dict_transaction_context *)_ctx; struct sql_dict *dict = (struct sql_dict *)_ctx->dict; const struct dict_op_settings_private *set = &_ctx->set; const struct dict_sql_map *map; ARRAY_TYPE(const_string) pattern_values; string_t *query = t_str_new(256); ARRAY_TYPE(sql_dict_param) params; const char *error; if (ctx->error != NULL) return; /* In theory we could unset one of the previous set/incs in this same transaction, so flush them first. */ if (array_is_created(&ctx->prev_inc)) sql_dict_prev_inc_flush(ctx); if (array_is_created(&ctx->prev_set)) sql_dict_prev_set_flush(ctx); map = sql_dict_find_map(dict, key, &pattern_values); if (map == NULL) { ctx->error = i_strdup_printf("dict-sql: Invalid/unmapped key: %s", key); return; } str_printfa(query, "DELETE FROM %s", map->table); t_array_init(¶ms, 4); if (sql_dict_where_build(set->username, map, &pattern_values, key[0] == DICT_PATH_PRIVATE[0], SQL_DICT_RECURSE_NONE, query, ¶ms, &error) < 0) { ctx->error = i_strdup_printf( "dict-sql: Failed to delete %s: %s", key, error); } else { struct sql_statement *stmt = sql_dict_transaction_stmt_init(ctx, str_c(query), ¶ms); sql_update_stmt(ctx->sql_ctx, &stmt); } } static unsigned int * sql_dict_next_inc_row(struct sql_dict_transaction_context *ctx) { struct sql_dict_inc_row *row; if (ctx->inc_row_pool == NULL) { ctx->inc_row_pool = pool_alloconly_create("sql dict inc rows", 128); } row = p_new(ctx->inc_row_pool, struct sql_dict_inc_row, 1); row->prev = ctx->inc_row; row->rows = UINT_MAX; ctx->inc_row = row; return &row->rows; } static void sql_dict_prev_inc_free(struct sql_dict_transaction_context *ctx) { struct sql_dict_prev *prev_inc; array_foreach_modifiable(&ctx->prev_inc, prev_inc) i_free(prev_inc->key); array_free(&ctx->prev_inc); } static void sql_dict_prev_inc_flush(struct sql_dict_transaction_context *ctx) { struct sql_dict *dict = (struct sql_dict *)ctx->ctx.dict; const struct dict_op_settings_private *set = &ctx->ctx.set; const struct sql_dict_prev *prev_incs; unsigned int count; ARRAY_TYPE(const_string) pattern_values; struct dict_sql_build_query build; struct dict_sql_build_query_field *field; ARRAY_TYPE(sql_dict_param) params; struct sql_dict_param *param; const char *query, *error; i_assert(array_is_created(&ctx->prev_inc)); if (ctx->error != NULL) { sql_dict_prev_inc_free(ctx); return; } prev_incs = array_get(&ctx->prev_inc, &count); i_assert(count > 0); /* Get the variable values from the dict path. We already verified that these are all exactly the same for everything in prev_incs. */ if (sql_dict_find_map(dict, prev_incs[0].key, &pattern_values) == NULL) i_unreached(); /* this was already checked */ i_zero(&build); build.dict = dict; build.pattern_values = &pattern_values; build.add_username = (prev_incs[0].key[0] == DICT_PATH_PRIVATE[0]); /* build.fields[] is an array of maps, which are used to get the map { value_field } for the SQL field names. params[] specifies the list of values to use for each field. Example: UPDATE .. SET build.fields[0].map->value_field = ...->value_field + params[0]->value_int64, ...[1]... */ t_array_init(&build.fields, count); t_array_init(¶ms, count); for (unsigned int i = 0; i < count; i++) { i_assert(build.add_username == (prev_incs[i].key[0] == DICT_PATH_PRIVATE[0])); field = array_append_space(&build.fields); field->map = prev_incs[i].map; field->value = NULL; /* unused */ param = array_append_space(¶ms); param->value_type = DICT_SQL_TYPE_INT; param->value_int64 = prev_incs[i].value.diff; } if (sql_dict_update_query(&build, set, &query, ¶ms, &error) < 0) { ctx->error = i_strdup_printf( "dict-sql: Failed to increase %u fields (first %s): %s", count, prev_incs[0].key, error); } else { struct sql_statement *stmt = sql_dict_transaction_stmt_init(ctx, query, ¶ms); sql_update_stmt_get_rows(ctx->sql_ctx, &stmt, sql_dict_next_inc_row(ctx)); } sql_dict_prev_inc_free(ctx); } static bool sql_dict_maps_are_mergeable(struct sql_dict *dict, const struct sql_dict_prev *prev1, const struct dict_sql_map *map2, const char *map2_key, const ARRAY_TYPE(const_string) *map2_pattern_values) { const struct dict_sql_map *map3; ARRAY_TYPE(const_string) map1_pattern_values; /* sql table names must equal */ if (strcmp(prev1->map->table, map2->table) != 0) return FALSE; /* private vs shared prefix must equal */ if (prev1->key[0] != map2_key[0]) return FALSE; if (prev1->key[0] == DICT_PATH_PRIVATE[0]) { /* for private keys, username must equal */ if (strcmp(prev1->map->username_field, map2->username_field) != 0) return FALSE; } /* variable values in the paths must equal exactly */ map3 = sql_dict_find_map(dict, prev1->key, &map1_pattern_values); i_assert(map3 == prev1->map); return array_equal_fn(&map1_pattern_values, map2_pattern_values, i_strcmp_p); } static void sql_dict_set(struct dict_transaction_context *_ctx, const char *key, const char *value) { struct sql_dict_transaction_context *ctx = (struct sql_dict_transaction_context *)_ctx; struct sql_dict *dict = (struct sql_dict *)_ctx->dict; const struct dict_sql_map *map; ARRAY_TYPE(const_string) pattern_values; if (ctx->error != NULL) return; /* In theory we could set the previous inc in this same transaction, so flush it first. */ if (array_is_created(&ctx->prev_inc)) sql_dict_prev_inc_flush(ctx); map = sql_dict_find_map(dict, key, &pattern_values); if (map == NULL) { ctx->error = i_strdup_printf( "sql dict set: Invalid/unmapped key: %s", key); return; } if (array_is_created(&ctx->prev_set) && !sql_dict_maps_are_mergeable(dict, array_front(&ctx->prev_set), map, key, &pattern_values)) { /* couldn't merge to the previous set - flush it */ sql_dict_prev_set_flush(ctx); } if (!array_is_created(&ctx->prev_set)) i_array_init(&ctx->prev_set, 4); /* Either this is the first set, or this can be merged with the previous set. */ struct sql_dict_prev *prev_set = array_append_space(&ctx->prev_set); prev_set->map = map; prev_set->key = i_strdup(key); prev_set->value.str = i_strdup(value); } static void sql_dict_atomic_inc(struct dict_transaction_context *_ctx, const char *key, long long diff) { struct sql_dict_transaction_context *ctx = (struct sql_dict_transaction_context *)_ctx; struct sql_dict *dict = (struct sql_dict *)_ctx->dict; const struct dict_sql_map *map; ARRAY_TYPE(const_string) pattern_values; if (ctx->error != NULL) return; /* In theory we could inc the previous set in this same transaction, so flush it first. */ if (array_is_created(&ctx->prev_set)) sql_dict_prev_set_flush(ctx); map = sql_dict_find_map(dict, key, &pattern_values); if (map == NULL) { ctx->error = i_strdup_printf( "sql dict atomic inc: Invalid/unmapped key: %s", key); return; } if (array_is_created(&ctx->prev_inc) && !sql_dict_maps_are_mergeable(dict, array_front(&ctx->prev_inc), map, key, &pattern_values)) { /* couldn't merge to the previous inc - flush it */ sql_dict_prev_inc_flush(ctx); } if (!array_is_created(&ctx->prev_inc)) i_array_init(&ctx->prev_inc, 4); /* Either this is the first inc, or this can be merged with the previous inc. */ struct sql_dict_prev *prev_inc = array_append_space(&ctx->prev_inc); prev_inc->map = map; prev_inc->key = i_strdup(key); prev_inc->value.diff = diff; } static struct dict sql_dict = { .name = "sql", { .init = sql_dict_init, .deinit = sql_dict_deinit, .wait = sql_dict_wait, .lookup = sql_dict_lookup, .iterate_init = sql_dict_iterate_init, .iterate = sql_dict_iterate, .iterate_deinit = sql_dict_iterate_deinit, .transaction_init = sql_dict_transaction_init, .transaction_commit = sql_dict_transaction_commit, .transaction_rollback = sql_dict_transaction_rollback, .set = sql_dict_set, .unset = sql_dict_unset, .atomic_inc = sql_dict_atomic_inc, .lookup_async = sql_dict_lookup_async, } }; static struct dict *dict_sql_drivers; void dict_sql_register(void) { const struct sql_db *const *drivers; unsigned int i, count; dict_sql_db_cache = sql_db_cache_init(DICT_SQL_MAX_UNUSED_CONNECTIONS); /* @UNSAFE */ drivers = array_get(&sql_drivers, &count); dict_sql_drivers = i_new(struct dict, count + 1); for (i = 0; i < count; i++) { dict_sql_drivers[i] = sql_dict; dict_sql_drivers[i].name = drivers[i]->name; dict_driver_register(&dict_sql_drivers[i]); } } void dict_sql_unregister(void) { int i; for (i = 0; dict_sql_drivers[i].name != NULL; i++) dict_driver_unregister(&dict_sql_drivers[i]); i_free(dict_sql_drivers); sql_db_cache_deinit(&dict_sql_db_cache); dict_sql_settings_deinit(); } dovecot-2.3.21.1/src/lib-dict-backend/dict-ldap-settings.h0000644000000000000000000000154514656633576020035 00000000000000#ifndef DICT_LDAP_SETTINGS_H #define DICT_LDAP_SETTINGS_H struct dict_ldap_map { /* pattern is in simplified form: all variables are stored as simple '$' character. fields array is sorted by the variable index. */ const char *pattern; const char *filter; const char *filter_iter; const char *username_attribute; const char *value_attribute; const char *base_dn; const char *scope; int scope_val; unsigned int timeout; ARRAY_TYPE(const_string) ldap_attributes; }; struct dict_ldap_settings { const char *uri; const char *bind_dn; const char *password; unsigned int timeout; unsigned int max_idle_time; unsigned int debug; unsigned int max_attribute_count; bool require_ssl; bool start_tls; ARRAY(struct dict_ldap_map) maps; }; struct dict_ldap_settings * dict_ldap_settings_read(pool_t pool, const char *path, const char **error_r); #endif dovecot-2.3.21.1/src/submission/0000755000000000000000000000000014656633640013347 500000000000000dovecot-2.3.21.1/src/submission/submission-common.h0000644000000000000000000000306714656633576017137 00000000000000#ifndef SUBMISSION_COMMON_H #define SUBMISSION_COMMON_H #include "lib.h" #include "array.h" #include "ioloop.h" #include "smtp-reply.h" #include "smtp-server.h" #include "submission-client.h" #include "submission-settings.h" #define URL_HOST_ALLOW_ANY "*" /* Maximum number of bytes added to a relayed message. This is used to calculate the SIZE capability based on what the backend server states. */ #define SUBMISSION_MAX_ADDITIONAL_MAIL_SIZE 1024 #define SUBMISSION_MAIL_DATA_MAX_INMEMORY_SIZE (1024*128) /* Maximum time to wait for QUIT reply from relay server */ #define SUBMISSION_MAX_WAIT_QUIT_REPLY_MSECS 2000 #define SUBMISSION_SUPPORTED_SMTP_CAPABILITIES \ (SMTP_CAPABILITY_AUTH | SMTP_CAPABILITY_PIPELINING | \ SMTP_CAPABILITY_SIZE | SMTP_CAPABILITY_ENHANCEDSTATUSCODES | \ SMTP_CAPABILITY_8BITMIME | SMTP_CAPABILITY_CHUNKING | \ SMTP_CAPABILITY_BINARYMIME | SMTP_CAPABILITY_BURL | \ SMTP_CAPABILITY_DSN | SMTP_CAPABILITY_VRFY) typedef void submission_client_created_func_t(struct client **client); extern submission_client_created_func_t *hook_client_created; extern bool submission_debug; extern struct smtp_server *smtp_server; extern struct smtp_client *smtp_client; /* Sets the hook_client_created and returns the previous hook, which the new_hook should call if it's non-NULL. */ submission_client_created_func_t * submission_client_created_hook_set(submission_client_created_func_t *new_hook); void submission_refresh_proctitle(void); void client_handshake(struct client *client); #endif dovecot-2.3.21.1/src/submission/submission-settings.c0000644000000000000000000001356614656633576017507 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "hostpid.h" #include "buffer.h" #include "settings-parser.h" #include "service-settings.h" #include "mail-storage-settings.h" #include "submission-settings.h" #include #include static bool submission_settings_verify(void *_set, pool_t pool, const char **error_r); /* */ static struct file_listener_settings submission_unix_listeners_array[] = { { "login/submission", 0666, "", "" } }; static struct file_listener_settings *submission_unix_listeners[] = { &submission_unix_listeners_array[0] }; static buffer_t submission_unix_listeners_buf = { { { submission_unix_listeners, sizeof(submission_unix_listeners) } } }; /* */ struct service_settings submission_service_settings = { .name = "submission", .protocol = "submission", .type = "", .executable = "submission", .user = "", .group = "", .privileged_group = "", .extra_groups = "$default_internal_group", .chroot = "", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 1024, .client_limit = 1, .service_count = 1, .idle_kill = 0, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &submission_unix_listeners_buf, sizeof(submission_unix_listeners[0]) } }, .fifo_listeners = ARRAY_INIT, .inet_listeners = ARRAY_INIT }; #undef DEF #define DEF(type, name) \ SETTING_DEFINE_STRUCT_##type(#name, name, struct submission_settings) static const struct setting_define submission_setting_defines[] = { DEF(BOOL, verbose_proctitle), DEF(STR_VARS, rawlog_dir), DEF(STR, hostname), DEF(STR, login_greeting), DEF(STR, login_trusted_networks), DEF(STR, recipient_delimiter), DEF(SIZE, submission_max_mail_size), DEF(UINT, submission_max_recipients), DEF(STR, submission_client_workarounds), DEF(STR, submission_logout_format), DEF(STR, submission_backend_capabilities), DEF(STR, submission_relay_host), DEF(IN_PORT, submission_relay_port), DEF(BOOL, submission_relay_trusted), DEF(STR, submission_relay_user), DEF(STR, submission_relay_master_user), DEF(STR, submission_relay_password), DEF(ENUM, submission_relay_ssl), DEF(BOOL, submission_relay_ssl_verify), DEF(STR_VARS, submission_relay_rawlog_dir), DEF(TIME, submission_relay_max_idle_time), DEF(TIME_MSECS, submission_relay_connect_timeout), DEF(TIME_MSECS, submission_relay_command_timeout), DEF(STR, imap_urlauth_host), DEF(IN_PORT, imap_urlauth_port), SETTING_DEFINE_LIST_END }; static const struct submission_settings submission_default_settings = { .verbose_proctitle = FALSE, .rawlog_dir = "", .hostname = "", .login_greeting = PACKAGE_NAME" ready.", .login_trusted_networks = "", .recipient_delimiter = "+", .submission_max_mail_size = 40*1024*1024, .submission_max_recipients = 0, .submission_client_workarounds = "", .submission_logout_format = "in=%i out=%o", .submission_backend_capabilities = NULL, .submission_relay_host = "", .submission_relay_port = 25, .submission_relay_trusted = FALSE, .submission_relay_user = "", .submission_relay_master_user = "", .submission_relay_password = "", .submission_relay_ssl = "no:smtps:starttls", .submission_relay_ssl_verify = TRUE, .submission_relay_rawlog_dir = "", .submission_relay_max_idle_time = 60*29, .submission_relay_connect_timeout = 30*1000, .submission_relay_command_timeout = 60*5*1000, .imap_urlauth_host = "", .imap_urlauth_port = 143, }; static const struct setting_parser_info *submission_setting_dependencies[] = { &mail_user_setting_parser_info, NULL }; const struct setting_parser_info submission_setting_parser_info = { .module_name = "submission", .defines = submission_setting_defines, .defaults = &submission_default_settings, .type_offset = SIZE_MAX, .struct_size = sizeof(struct submission_settings), .parent_offset = SIZE_MAX, .check_func = submission_settings_verify, .dependencies = submission_setting_dependencies }; /* */ struct submission_client_workaround_list { const char *name; enum submission_client_workarounds num; }; /* These definitions need to be kept in sync with equivalent definitions present in src/submission-login/submission-login-settings.c. Workarounds that are not relevant to the submission service are defined as 0 here to prevent "Unknown workaround" errors below. */ static const struct submission_client_workaround_list submission_client_workaround_list[] = { { "whitespace-before-path", SUBMISSION_WORKAROUND_WHITESPACE_BEFORE_PATH }, { "mailbox-for-path", SUBMISSION_WORKAROUND_MAILBOX_FOR_PATH }, { "implicit-auth-external", 0 }, { "exotic-backend", 0 }, { NULL, 0 } }; static int submission_settings_parse_workarounds(struct submission_settings *set, const char **error_r) { enum submission_client_workarounds client_workarounds = 0; const struct submission_client_workaround_list *list; const char *const *str; str = t_strsplit_spaces(set->submission_client_workarounds, " ,"); for (; *str != NULL; str++) { list = submission_client_workaround_list; for (; list->name != NULL; list++) { if (strcasecmp(*str, list->name) == 0) { client_workarounds |= list->num; break; } } if (list->name == NULL) { *error_r = t_strdup_printf( "submission_client_workarounds: " "Unknown workaround: %s", *str); return -1; } } set->parsed_workarounds = client_workarounds; return 0; } static bool submission_settings_verify(void *_set, pool_t pool ATTR_UNUSED, const char **error_r) { struct submission_settings *set = _set; if (submission_settings_parse_workarounds(set, error_r) < 0) return FALSE; #ifndef CONFIG_BINARY if (set->submission_relay_max_idle_time == 0) { *error_r = "submission_relay_max_idle_time must not be 0"; return FALSE; } if (*set->hostname == '\0') set->hostname = p_strdup(pool, my_hostdomain()); #endif return TRUE; } /* */ dovecot-2.3.21.1/src/submission/submission-recipient.h0000644000000000000000000000136714656633576017632 00000000000000#ifndef SUBMISSION_RECIPIENT_H #define SUBMISSION_RECIPIENT_H struct submission_backend; struct client; struct submission_recipient { struct smtp_server_recipient *rcpt; struct submission_backend *backend; void *backend_context; /* Module-specific contexts. */ ARRAY(union submission_recipient_module_context *) module_contexts; bool anonymous_allowed:1; }; struct submission_recipient_module_register { unsigned int id; }; union submission_recipient_module_context { struct submission_recipient_module_register *reg; }; extern struct submission_recipient_module_register submission_recipient_module_register; struct submission_recipient * submission_recipient_create(struct client *client, struct smtp_server_recipient *rcpt); #endif dovecot-2.3.21.1/src/submission/submission-settings.h0000644000000000000000000000267714656633576017515 00000000000000#ifndef SUBMISSION_SETTINGS_H #define SUBMISSION_SETTINGS_H #include "smtp-server.h" /* */ enum submission_client_workarounds { SUBMISSION_WORKAROUND_WHITESPACE_BEFORE_PATH = BIT(0), SUBMISSION_WORKAROUND_MAILBOX_FOR_PATH = BIT(1), }; /* */ struct submission_settings { bool verbose_proctitle; const char *rawlog_dir; const char *hostname; const char *login_greeting; const char *login_trusted_networks; const char *recipient_delimiter; /* submission: */ uoff_t submission_max_mail_size; unsigned int submission_max_recipients; const char *submission_client_workarounds; const char *submission_logout_format; /* submission backend: */ const char *submission_backend_capabilities; /* submission relay: */ const char *submission_relay_host; in_port_t submission_relay_port; bool submission_relay_trusted; const char *submission_relay_user; const char *submission_relay_master_user; const char *submission_relay_password; const char *submission_relay_ssl; bool submission_relay_ssl_verify; const char *submission_relay_rawlog_dir; unsigned int submission_relay_max_idle_time; unsigned int submission_relay_connect_timeout; unsigned int submission_relay_command_timeout; /* imap urlauth: */ const char *imap_urlauth_host; in_port_t imap_urlauth_port; enum submission_client_workarounds parsed_workarounds; }; extern const struct setting_parser_info submission_setting_parser_info; #endif dovecot-2.3.21.1/src/submission/Makefile.in0000644000000000000000000007363414656633613015351 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ pkglibexec_PROGRAMS = submission$(EXEEXT) subdir = src/submission ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \ $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(pkglibexecdir)" \ "$(DESTDIR)$(pkginc_libdir)" PROGRAMS = $(pkglibexec_PROGRAMS) am_submission_OBJECTS = main.$(OBJEXT) submission-backend.$(OBJEXT) \ submission-backend-relay.$(OBJEXT) \ submission-recipient.$(OBJEXT) submission-client.$(OBJEXT) \ submission-commands.$(OBJEXT) submission-settings.$(OBJEXT) submission_OBJECTS = $(am_submission_OBJECTS) am__DEPENDENCIES_1 = AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = submission_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(submission_LDFLAGS) $(LDFLAGS) -o $@ AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/main.Po \ ./$(DEPDIR)/submission-backend-relay.Po \ ./$(DEPDIR)/submission-backend.Po \ ./$(DEPDIR)/submission-client.Po \ ./$(DEPDIR)/submission-commands.Po \ ./$(DEPDIR)/submission-recipient.Po \ ./$(DEPDIR)/submission-settings.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(submission_SOURCES) DIST_SOURCES = $(submission_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ *) f=$$p;; \ esac; am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; am__install_max = 40 am__nobase_strip_setup = \ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` am__nobase_strip = \ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" am__nobase_list = $(am__nobase_strip_setup); \ for p in $$list; do echo "$$p $$p"; done | \ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ if (++n[$$2] == $(am__install_max)) \ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ END { for (dir in files) print dir, files[dir] }' am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' am__uninstall_files_from_dir = { \ test -z "$$files" \ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ $(am__cd) "$$dir" && rm -f $$files; }; \ } HEADERS = $(pkginc_lib_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) pkglibexecdir = $(libexecdir)/dovecot ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-ssl-iostream \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-imap-storage \ -I$(top_srcdir)/src/lib-imap-urlauth \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-storage \ -I$(top_srcdir)/src/lib-storage/index \ -I$(top_srcdir)/src/lib-storage/index/raw \ -I$(top_srcdir)/src/lib-smtp urlauth_libs = \ $(top_builddir)/src/lib-imap-urlauth/libimap-urlauth.la submission_LDFLAGS = -export-dynamic submission_LDADD = \ $(urlauth_libs) \ $(LIBDOVECOT_STORAGE) \ $(LIBDOVECOT) \ $(MODULE_LIBS) submission_DEPENDENCIES = \ $(urlauth_libs) \ $(LIBDOVECOT_STORAGE_DEPS) \ $(LIBDOVECOT_DEPS) submission_SOURCES = \ main.c \ submission-backend.c \ submission-backend-relay.c \ submission-recipient.c \ submission-client.c \ submission-commands.c \ submission-settings.c headers = \ submission-common.h \ submission-backend.h \ submission-backend-relay.h \ submission-commands.h \ submission-recipient.h \ submission-client.h \ submission-settings.h pkginc_libdir = $(pkgincludedir) pkginc_lib_HEADERS = $(headers) all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/submission/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/submission/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files clean-pkglibexecPROGRAMS: @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list submission$(EXEEXT): $(submission_OBJECTS) $(submission_DEPENDENCIES) $(EXTRA_submission_DEPENDENCIES) @rm -f submission$(EXEEXT) $(AM_V_CCLD)$(submission_LINK) $(submission_OBJECTS) $(submission_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/submission-backend-relay.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/submission-backend.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/submission-client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/submission-commands.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/submission-recipient.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/submission-settings.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) @$(NORMAL_INSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ done uninstall-pkginc_libHEADERS: @$(NORMAL_UNINSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am check: check-am all-am: Makefile $(PROGRAMS) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(pkginc_libdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-pkglibexecPROGRAMS \ mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/main.Po -rm -f ./$(DEPDIR)/submission-backend-relay.Po -rm -f ./$(DEPDIR)/submission-backend.Po -rm -f ./$(DEPDIR)/submission-client.Po -rm -f ./$(DEPDIR)/submission-commands.Po -rm -f ./$(DEPDIR)/submission-recipient.Po -rm -f ./$(DEPDIR)/submission-settings.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-pkginc_libHEADERS install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-pkglibexecPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/main.Po -rm -f ./$(DEPDIR)/submission-backend-relay.Po -rm -f ./$(DEPDIR)/submission-backend.Po -rm -f ./$(DEPDIR)/submission-client.Po -rm -f ./$(DEPDIR)/submission-commands.Po -rm -f ./$(DEPDIR)/submission-recipient.Po -rm -f ./$(DEPDIR)/submission-settings.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkginc_libHEADERS uninstall-pkglibexecPROGRAMS .MAKE: install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ clean-generic clean-libtool clean-pkglibexecPROGRAMS \ cscopelist-am ctags ctags-am distclean distclean-compile \ distclean-generic distclean-libtool distclean-tags distdir dvi \ dvi-am html html-am info info-am install install-am \ install-data install-data-am install-dvi install-dvi-am \ install-exec install-exec-am install-html install-html-am \ install-info install-info-am install-man install-pdf \ install-pdf-am install-pkginc_libHEADERS \ install-pkglibexecPROGRAMS install-ps install-ps-am \ install-strip installcheck installcheck-am installdirs \ maintainer-clean maintainer-clean-generic mostlyclean \ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ uninstall-pkginc_libHEADERS uninstall-pkglibexecPROGRAMS .PRECIOUS: Makefile # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/submission/submission-backend.h0000644000000000000000000001437514656633576017242 00000000000000#ifndef SUBMISSION_BACKEND_H #define SUBMISSION_BACKEND_H struct submission_recipient; struct submission_backend; union submission_backend_module_context; struct submission_backend_vfuncs { void (*destroy)(struct submission_backend *backend); void (*start)(struct submission_backend *backend); void (*ready)(struct submission_backend *backend, enum smtp_capability caps); void (*fail)(struct submission_backend *backend, const char *enh_code, const char *reason); void (*client_input_pre)(struct submission_backend *backend); void (*client_input_post)(struct submission_backend *backend); uoff_t (*get_max_mail_size)(struct submission_backend *backend); void (*trans_start)(struct submission_backend *backend, struct smtp_server_transaction *trans, const struct smtp_address *path, const struct smtp_params_mail *params); void (*trans_free)(struct submission_backend *backend, struct smtp_server_transaction *trans); /* Command handlers: These implement the behavior of the various core SMTP commands. SMTP commands are handled asynchronously, which means that the command is not necessarily finished when these handlers end. A command is finished either when 1 is returned or a reply is submitted for it. When a handler returns 0, the command implementation is waiting for an external event and when it returns -1 an error occurred. When 1 is returned, a default success reply is submitted implicitly. Not submitting an error reply when -1 is returned causes an assert fail. See src/lib-smtp/smtp-server.h for details. When overriding these handler vfuncs, the base implementation should usually be called at some point. When it is called immediately, its result can be returned as normal. When the override returns 0, the base implementation would be called at a later time when some external state is achieved. Note that the overriding function then assumes the responsibility to submit the default reply when none is submitted and the base implementation returns 1. Also note that only the default backend actually triggers all of these command callbacks. Secondary backends only get called for transaction commands and only when that backend is tied to the transaction somehow; e.g., as the primary transaction backend or when it is tied to one of the approved recipients. */ int (*cmd_helo)(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_helo *data); int (*cmd_mail)(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_mail *data); int (*cmd_rcpt)(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, struct submission_recipient *srcpt); int (*cmd_rset)(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd); int (*cmd_data)(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans, struct istream *data_input, uoff_t data_size); int (*cmd_vrfy)(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, const char *param); int (*cmd_noop)(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd); int (*cmd_quit)(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd); }; struct submission_backend { pool_t pool; struct client *client; struct submission_backend *prev, *next; struct submission_backend_vfuncs v; struct istream *data_input; uoff_t data_size; char *fail_enh_code; char *fail_reason; /* Module-specific contexts. */ ARRAY(union submission_backend_module_context *) module_contexts; bool started:1; bool ready:1; bool trans_started:1; }; struct submission_backend_module_register { unsigned int id; }; union submission_backend_module_context { struct submission_backend_module_register *reg; }; extern struct submission_backend_module_register submission_backend_module_register; void submission_backend_init(struct submission_backend *backend, pool_t pool, struct client *client, const struct submission_backend_vfuncs *vfunc); void submission_backends_destroy_all(struct client *client); void submission_backend_start(struct submission_backend *backend); void submission_backend_started(struct submission_backend *backend, enum smtp_capability caps); void submission_backend_fail(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, const char *enh_code, const char *reason) ATTR_NULL(2); void submission_backends_client_input_pre(struct client *client); void submission_backends_client_input_post(struct client *client); uoff_t submission_backend_get_max_mail_size(struct submission_backend *backend); void submission_backend_trans_start(struct submission_backend *backend, struct smtp_server_transaction *trans); void submission_backends_trans_start(struct client *client, struct smtp_server_transaction *trans); void submission_backends_trans_free(struct client *client, struct smtp_server_transaction *trans); void submission_backend_helo_reply_submit(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_helo *data); int submission_backend_cmd_helo(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_helo *data); int submission_backend_cmd_mail(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_mail *data); int submission_backend_cmd_rcpt(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, struct submission_recipient *srcpt); int submission_backend_cmd_rset(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd); int submission_backends_cmd_data(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans, struct istream *data_input, uoff_t data_size); int submission_backend_cmd_vrfy(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, const char *param); int submission_backend_cmd_noop(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd); int submission_backend_cmd_quit(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd); #endif dovecot-2.3.21.1/src/submission/submission-commands.h0000644000000000000000000000443514656633576017450 00000000000000#ifndef SUBMISSION_COMMANDS_H #define SUBMISSION_COMMANDS_H /* * HELO command */ void submission_helo_reply_submit(struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_helo *data); int cmd_helo(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_helo *data); int client_default_cmd_helo(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_helo *data); /* * MAIL command */ int cmd_mail(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_mail *data); int client_default_cmd_mail(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_mail *data); /* * RCPT command */ int cmd_rcpt(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_recipient *rcpt); int client_default_cmd_rcpt(struct client *client ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd, struct submission_recipient *srcpt); /* * RSET command */ int cmd_rset(void *conn_ctx, struct smtp_server_cmd_ctx *cmd); int client_default_cmd_rset(struct client *client, struct smtp_server_cmd_ctx *cmd); /* * DATA/BDAT commands */ int cmd_data_begin(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans, struct istream *data_input); int cmd_data_continue(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans); int client_default_cmd_data(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans, struct istream *data_input, uoff_t data_size); /* * BURL command */ void cmd_burl(struct smtp_server_cmd_ctx *cmd, const char *params); /* * VRFY command */ int cmd_vrfy(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, const char *param); int client_default_cmd_vrfy(struct client *client, struct smtp_server_cmd_ctx *cmd, const char *param); /* * NOOP command */ int cmd_noop(void *conn_ctx, struct smtp_server_cmd_ctx *cmd); int client_default_cmd_noop(struct client *client, struct smtp_server_cmd_ctx *cmd); /* * QUIT command */ int cmd_quit(void *conn_ctx, struct smtp_server_cmd_ctx *cmd); int client_default_cmd_quit(struct client *client, struct smtp_server_cmd_ctx *cmd); #endif dovecot-2.3.21.1/src/submission/submission-client.h0000644000000000000000000001204414656633576017120 00000000000000#ifndef CLIENT_H #define CLIENT_H #include "net.h" struct smtp_reply; struct submission_recipient; struct submission_backend; struct submission_backend_relay; struct client; struct client_state { pool_t pool; enum smtp_server_state state; char *args; struct submission_backend *backend; struct istream *data_input; uoff_t data_size; bool anonymous_allowed:1; }; struct client_extra_capability { const char *capability; const char *params; }; struct submission_client_vfuncs { void (*destroy)(struct client *client); void (*trans_start)(struct client *client, struct smtp_server_transaction *trans); void (*trans_free)(struct client *client, struct smtp_server_transaction *trans); /* Command handlers: These implement the behavior of the various core SMTP commands. SMTP commands are handled asynchronously, which means that the command is not necessarily finished when these handlers end. A command is finished either when 1 is returned or a reply is submitted for it. When a handler returns 0, the command implementation is waiting for an external event and when it returns -1 an error occurred. When 1 is returned, a default success reply is submitted implicitly. Not submitting an error reply when -1 is returned causes an assert fail. See src/lib-smtp/smtp-server.h for details. When overriding these handler vfuncs, the base implementation should usually be called at some point. When it is called immediately, its result can be returned as normal. When the override returns 0, the base implementation would be called at a later time when some external state is achieved. Note that the overriding function then assumes the responsibility to submit the default reply when none is submitted and the base implementation returns 1. */ int (*cmd_helo)(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_helo *data); int (*cmd_mail)(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_mail *data); int (*cmd_rcpt)(struct client *client, struct smtp_server_cmd_ctx *cmd, struct submission_recipient *srcpt); int (*cmd_rset)(struct client *client, struct smtp_server_cmd_ctx *cmd); int (*cmd_data)(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans, struct istream *data_input, uoff_t data_size); int (*cmd_vrfy)(struct client *client, struct smtp_server_cmd_ctx *cmd, const char *param); int (*cmd_noop)(struct client *client, struct smtp_server_cmd_ctx *cmd); int (*cmd_quit)(struct client *client, struct smtp_server_cmd_ctx *cmd); }; struct client { struct client *prev, *next; pool_t pool; struct submission_client_vfuncs v; const struct setting_parser_info *user_set_info; const struct submission_settings *set; struct smtp_server_connection *conn; struct client_state state; ARRAY(struct submission_backend *) pending_backends; ARRAY(struct submission_recipient *) rcpt_to; ARRAY(struct submission_backend *) rcpt_backends; struct mail_storage_service_user *service_user; struct mail_user *user; /* IMAP URLAUTH context (RFC4467) for BURL (RFC4468) */ struct imap_urlauth_context *urlauth_ctx; struct timeout *to_quit; enum smtp_capability backend_capabilities; struct submission_backend *backend_default; struct submission_backend_relay *backend_default_relay; struct submission_backend *backends; unsigned int backends_count; /* Extra (non-standard) capabilities */ ARRAY(struct client_extra_capability) extra_capabilities; /* Module-specific contexts. */ ARRAY(union submission_module_context *) module_contexts; bool standalone:1; bool disconnected:1; bool destroyed:1; bool anvil_sent:1; bool backend_capabilities_configured:1; bool anonymous_allowed:1; }; struct submission_module_register { unsigned int id; }; union submission_module_context { struct submission_client_vfuncs super; struct submission_module_register *reg; }; extern struct submission_module_register submission_module_register; extern struct client *submission_clients; extern unsigned int submission_client_count; struct client * client_create(int fd_in, int fd_out, struct mail_user *user, struct mail_storage_service_user *service_user, const struct submission_settings *set, const char *helo, const struct smtp_proxy_data *proxy_data, const unsigned char *pdata, unsigned int pdata_len, bool no_greeting); void client_destroy(struct client **client, const char *prefix, const char *reason) ATTR_NULL(2, 3); typedef void (*client_input_callback_t)(struct client *context); void client_apply_backend_capabilities(struct client *client); void client_default_backend_started(struct client *client, enum smtp_capability caps); uoff_t client_get_max_mail_size(struct client *client); void client_add_extra_capability(struct client *client, const char *capability, const char *params) ATTR_NULL(2); int client_input_read(struct client *client); int client_handle_input(struct client *client); void clients_destroy_all(void); #endif dovecot-2.3.21.1/src/submission/submission-backend-relay.c0000644000000000000000000010546114656633576020344 00000000000000/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ #include "submission-common.h" #include "str.h" #include "str-sanitize.h" #include "mail-user.h" #include "iostream-ssl.h" #include "smtp-client.h" #include "smtp-client-connection.h" #include "smtp-client-transaction.h" #include "smtp-client-command.h" #include "submission-recipient.h" #include "submission-backend-relay.h" struct submission_backend_relay { struct submission_backend backend; struct smtp_client_connection *conn; struct smtp_client_transaction *trans; bool trans_started:1; bool trusted:1; bool quit_confirmed:1; }; static struct submission_backend_vfuncs backend_relay_vfuncs; /* * Common */ /* The command handling of the submission relay service aims to follow the following rules: - Attempt to keep pipelined commands pipelined when relaying them to the actual relay service. - Don't forward commands if they're known to fail at the relay server. Errors can still occur if pipelined commands fail. Abort subsequent pending commands if such failures affect those commands. - Keep predictable errors consistent as much as possible; send our own reply if the error condition is clear (e.g. missing MAIL, RCPT). */ static bool backend_relay_handle_relay_reply(struct submission_backend_relay *backend, struct smtp_server_cmd_ctx *cmd, const struct smtp_reply *reply, struct smtp_reply *reply_r) ATTR_NULL(2) { struct client *client = backend->backend.client; struct mail_user *user = client->user; const char *enh_code, *msg, *log_msg = NULL; const char *const *reply_lines; bool result = TRUE; *reply_r = *reply; switch (reply->status) { case SMTP_CLIENT_COMMAND_ERROR_ABORTED: return FALSE; case SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED: case SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED: case SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED: enh_code = "4.4.0"; msg = "Failed to connect to relay server"; result = FALSE; break; case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_CLOSED: enh_code = smtp_reply_get_enh_code(reply); log_msg = "Lost connection to relay server"; reply_lines = smtp_reply_get_text_lines_omit_prefix(reply); msg = t_strconcat("Lost connection to relay server:\n", t_strarray_join(reply_lines, "\n"), NULL); result = FALSE; break; case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST: case SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY: case SMTP_CLIENT_COMMAND_ERROR_TIMED_OUT: enh_code = "4.4.0"; log_msg = msg = "Lost connection to relay server"; result = FALSE; break; /* RFC 4954, Section 6: 530 5.7.0 Authentication required This response SHOULD be returned by any command other than AUTH, EHLO, HELO, NOOP, RSET, or QUIT when server policy requires authentication in order to perform the requested action and authentication is not currently in force. */ case 530: log_msg = "Relay server requires authentication"; enh_code = "4.3.5", msg = "Internal error occurred. " "Refer to server log for more information."; result = FALSE; break; default: break; } if (!result) { const char *detail = "", *reason; i_assert(msg != NULL); switch (reply->status) { case SMTP_CLIENT_COMMAND_ERROR_ABORTED: i_unreached(); case SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED: detail = " (DNS lookup)"; break; case SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED: case SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED: detail = " (connect)"; break; case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST: case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_CLOSED: if (backend->quit_confirmed) return FALSE; detail = " (connection lost)"; break; case SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY: detail = " (bad reply)"; break; case SMTP_CLIENT_COMMAND_ERROR_TIMED_OUT: detail = " (timed out)"; break; default: break; } reason = t_strdup_printf("%s%s", msg, detail); smtp_client_transaction_destroy(&backend->trans); if (log_msg != NULL) { if (smtp_reply_is_remote(reply)) { i_error("%s: %s", log_msg, smtp_reply_log(reply)); } else if (user->mail_debug) { i_debug("%s: %s", log_msg, smtp_reply_log(reply)); } } submission_backend_fail(&backend->backend, cmd, enh_code, reason); return FALSE; } if (!smtp_reply_has_enhanced_code(reply)) { reply_r->enhanced_code = SMTP_REPLY_ENH_CODE(reply->status / 100, 0, 0); } return TRUE; } /* * Mail transaction */ static void backend_relay_trans_finished(struct submission_backend_relay *backend) { backend->trans = NULL; } static void backend_relay_trans_start_callback( const struct smtp_reply *relay_reply ATTR_UNUSED, struct submission_backend_relay *backend ATTR_UNUSED) { /* nothing to do */ } static void backend_relay_trans_start(struct submission_backend *_backend, struct smtp_server_transaction *trans ATTR_UNUSED, const struct smtp_address *path, const struct smtp_params_mail *params) { struct submission_backend_relay *backend = (struct submission_backend_relay *)_backend; if (backend->trans == NULL) { backend->trans_started = TRUE; backend->trans = smtp_client_transaction_create( backend->conn, path, params, 0, backend_relay_trans_finished, backend); smtp_client_transaction_set_immediate(backend->trans, TRUE); smtp_client_transaction_start( backend->trans, backend_relay_trans_start_callback, backend); } else if (!backend->trans_started) { backend->trans_started = TRUE; smtp_client_transaction_start_empty( backend->trans, path, params, backend_relay_trans_start_callback, backend); } } static void backend_relay_trans_free(struct submission_backend *_backend, struct smtp_server_transaction *trans ATTR_UNUSED) { struct submission_backend_relay *backend = (struct submission_backend_relay *)_backend; backend->trans_started = FALSE; if (backend->trans == NULL) return; smtp_client_transaction_destroy(&backend->trans); } struct smtp_client_transaction * submission_backend_relay_init_transaction( struct submission_backend_relay *backend, enum smtp_client_transaction_flags flags) { i_assert(backend->trans == NULL); backend->trans = smtp_client_transaction_create_empty( backend->conn, flags, backend_relay_trans_finished, backend); smtp_client_transaction_set_immediate(backend->trans, TRUE); return backend->trans; } /* * EHLO, HELO commands */ struct relay_cmd_helo_context { struct submission_backend_relay *backend; struct smtp_server_cmd_ctx *cmd; struct smtp_server_cmd_helo *data; struct smtp_client_command *cmd_relayed; }; static void relay_cmd_helo_destroy(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct relay_cmd_helo_context *helo_cmd) { i_assert(helo_cmd != NULL); if (helo_cmd->cmd_relayed != NULL) smtp_client_command_abort(&helo_cmd->cmd_relayed); } static void relay_cmd_helo_update_xclient(struct submission_backend_relay *backend, struct smtp_server_cmd_helo *data) { struct smtp_proxy_data proxy_data; if (!backend->trusted) return; i_zero(&proxy_data); proxy_data.helo = data->helo.domain; smtp_client_connection_update_proxy_data(backend->conn, &proxy_data); } static void relay_cmd_helo_reply(struct smtp_server_cmd_ctx *cmd, struct relay_cmd_helo_context *helo_cmd) { struct submission_backend_relay *backend = helo_cmd->backend; if (helo_cmd->data->changed) relay_cmd_helo_update_xclient(backend, helo_cmd->data); T_BEGIN { submission_backend_helo_reply_submit(&backend->backend, cmd, helo_cmd->data); } T_END; } static void relay_cmd_helo_callback(const struct smtp_reply *relay_reply, struct relay_cmd_helo_context *helo_cmd) { i_assert(helo_cmd != NULL); struct smtp_server_cmd_ctx *cmd = helo_cmd->cmd; struct submission_backend_relay *backend = helo_cmd->backend; struct smtp_reply reply; /* finished relaying EHLO command to relay server */ helo_cmd->cmd_relayed = NULL; if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, &reply)) return; if (smtp_reply_is_success(&reply)) { relay_cmd_helo_reply(cmd, helo_cmd); } else { /* RFC 2034, Section 4: These codes must appear in all 2xx, 4xx, and 5xx response lines other than initial greeting and any response to HELO or EHLO. */ reply.enhanced_code = SMTP_REPLY_ENH_CODE_NONE; smtp_server_reply_forward(cmd, &reply); } } static void relay_cmd_helo_start(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct relay_cmd_helo_context *helo_cmd) { struct submission_backend_relay *backend = helo_cmd->backend; if (helo_cmd->data->changed) relay_cmd_helo_update_xclient(backend, helo_cmd->data); } static int backend_relay_cmd_helo(struct submission_backend *_backend, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_helo *data) { struct submission_backend_relay *backend = (struct submission_backend_relay *)_backend; struct relay_cmd_helo_context *helo_cmd; helo_cmd = p_new(cmd->pool, struct relay_cmd_helo_context, 1); helo_cmd->backend = backend; helo_cmd->cmd = cmd; helo_cmd->data = data; /* This is not the first HELO/EHLO; just relay a RSET command */ smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_NEXT, relay_cmd_helo_start, helo_cmd); smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_DESTROY, relay_cmd_helo_destroy, helo_cmd); helo_cmd->cmd_relayed = smtp_client_command_rset_submit( backend->conn, 0, relay_cmd_helo_callback, helo_cmd); return 0; } /* * MAIL command */ struct relay_cmd_mail_context { struct submission_backend_relay *backend; struct smtp_server_cmd_ctx *cmd; struct smtp_server_cmd_mail *data; struct smtp_client_transaction_mail *relay_mail; }; static void relay_cmd_mail_replied(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct relay_cmd_mail_context *mail_cmd) { if (mail_cmd->relay_mail != NULL) smtp_client_transaction_mail_abort(&mail_cmd->relay_mail); } static void relay_cmd_mail_callback(const struct smtp_reply *relay_reply, struct relay_cmd_mail_context *mail_cmd) { i_assert(mail_cmd != NULL); struct smtp_server_cmd_ctx *cmd = mail_cmd->cmd; struct submission_backend_relay *backend = mail_cmd->backend; struct smtp_reply reply; /* finished relaying MAIL command to relay server */ mail_cmd->relay_mail = NULL; if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, &reply)) return; if (smtp_reply_is_success(relay_reply)) { /* if relay accepts it, we accept it too */ /* the default 2.0.0 code won't do */ if (!smtp_reply_has_enhanced_code(relay_reply)) reply.enhanced_code = SMTP_REPLY_ENH_CODE(2, 1, 0); } /* forward reply */ smtp_server_reply_forward(cmd, &reply); } static int relay_cmd_mail_parameter_auth(struct submission_backend_relay *backend, struct smtp_server_cmd_ctx *cmd, enum smtp_capability relay_caps, struct smtp_server_cmd_mail *data) { struct client *client = backend->backend.client; struct smtp_params_mail *params = &data->params; struct smtp_address *auth_addr; const char *error; if ((relay_caps & SMTP_CAPABILITY_AUTH) == 0) return 0; auth_addr = NULL; if (smtp_address_parse_username(cmd->pool, client->user->username, &auth_addr, &error) < 0) { i_warning("Username `%s' is not a valid SMTP address: %s", client->user->username, error); } params->auth = auth_addr; return 0; } static int relay_cmd_mail_parameter_size(struct submission_backend_relay *backend, struct smtp_server_cmd_ctx *cmd, enum smtp_capability relay_caps, struct smtp_server_cmd_mail *data) { struct client *client = backend->backend.client; uoff_t max_size; /* SIZE=: RFC 1870 */ if (data->params.size == 0 || (relay_caps & SMTP_CAPABILITY_SIZE) == 0) return 0; /* determine actual size limit (account for our additions) */ max_size = client_get_max_mail_size(client); if (max_size > 0 && data->params.size > max_size) { smtp_server_reply( cmd, 552, "5.3.4", "Message size exceeds fixed maximum message size"); return -1; } /* relay the SIZE parameter (account for additional size) */ data->params.size += SUBMISSION_MAX_ADDITIONAL_MAIL_SIZE; return 0; } static int backend_relay_cmd_mail(struct submission_backend *_backend, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_mail *data) { struct submission_backend_relay *backend = (struct submission_backend_relay *)_backend; enum smtp_capability relay_caps = smtp_client_connection_get_capabilities(backend->conn); struct relay_cmd_mail_context *mail_cmd; /* check and adjust parameters where necessary */ if (relay_cmd_mail_parameter_auth(backend, cmd, relay_caps, data) < 0) return -1; if (relay_cmd_mail_parameter_size(backend, cmd, relay_caps, data) < 0) return -1; /* queue command (pipeline) */ mail_cmd = p_new(cmd->pool, struct relay_cmd_mail_context, 1); mail_cmd->backend = backend; mail_cmd->cmd = cmd; mail_cmd->data = data; smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_REPLIED, relay_cmd_mail_replied, mail_cmd); if (backend->trans == NULL) { /* start client transaction */ backend->trans_started = TRUE; backend->trans = smtp_client_transaction_create( backend->conn, data->path, &data->params, 0, backend_relay_trans_finished, backend); smtp_client_transaction_set_immediate(backend->trans, TRUE); smtp_client_transaction_start( backend->trans, relay_cmd_mail_callback, mail_cmd); } else { /* forward pipelined MAIL command */ i_assert(backend->trans_started); mail_cmd->relay_mail = smtp_client_transaction_add_mail( backend->trans, data->path, &data->params, relay_cmd_mail_callback, mail_cmd); } return 0; } /* * RCPT command */ struct relay_cmd_rcpt_context { struct submission_backend_relay *backend; struct submission_recipient *rcpt; struct smtp_server_cmd_ctx *cmd; struct smtp_client_transaction_rcpt *relay_rcpt; }; static void relay_cmd_rcpt_replied(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct relay_cmd_rcpt_context *rcpt_cmd) { if (rcpt_cmd->relay_rcpt != NULL) smtp_client_transaction_rcpt_abort(&rcpt_cmd->relay_rcpt); } static void relay_cmd_rcpt_callback(const struct smtp_reply *relay_reply, struct relay_cmd_rcpt_context *rcpt_cmd) { i_assert(rcpt_cmd != NULL); struct smtp_server_cmd_ctx *cmd = rcpt_cmd->cmd; struct submission_backend_relay *backend = rcpt_cmd->backend; struct submission_recipient *srcpt = rcpt_cmd->rcpt; struct smtp_server_recipient *rcpt = srcpt->rcpt; struct smtp_client_transaction_rcpt *relay_rcpt = rcpt_cmd->relay_rcpt; struct smtp_reply reply; /* finished relaying RCPT command to relay server */ rcpt_cmd->relay_rcpt = NULL; if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, &reply)) return; if (smtp_reply_is_success(&reply)) { /* the default 2.0.0 code won't do */ if (!smtp_reply_has_enhanced_code(&reply)) reply.enhanced_code = SMTP_REPLY_ENH_CODE(2, 1, 5); i_assert(relay_rcpt != NULL); srcpt->backend_context = relay_rcpt; } /* forward reply */ smtp_server_recipient_reply_forward(rcpt, &reply); } static int backend_relay_cmd_rcpt(struct submission_backend *_backend, struct smtp_server_cmd_ctx *cmd, struct submission_recipient *srcpt) { struct submission_backend_relay *backend = (struct submission_backend_relay *)_backend; struct smtp_server_recipient *rcpt = srcpt->rcpt; struct relay_cmd_rcpt_context *rcpt_cmd; /* queue command (pipeline) */ rcpt_cmd = p_new(cmd->pool, struct relay_cmd_rcpt_context, 1); rcpt_cmd->backend = backend; rcpt_cmd->cmd = cmd; rcpt_cmd->rcpt = srcpt; smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_REPLIED, relay_cmd_rcpt_replied, rcpt_cmd); if (backend->trans == NULL) (void)submission_backend_relay_init_transaction(backend, 0); rcpt_cmd->relay_rcpt = smtp_client_transaction_add_pool_rcpt( backend->trans, rcpt->pool, rcpt->path, &rcpt->params, relay_cmd_rcpt_callback, rcpt_cmd); return 0; } /* * RSET command */ struct relay_cmd_rset_context { struct submission_backend_relay *backend; struct smtp_server_cmd_ctx *cmd; struct smtp_client_command *cmd_relayed; }; static void relay_cmd_rset_destroy(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct relay_cmd_rset_context *rset_cmd) { i_assert(rset_cmd != NULL); if (rset_cmd->cmd_relayed != NULL) smtp_client_command_abort(&rset_cmd->cmd_relayed); } static void relay_cmd_rset_callback(const struct smtp_reply *relay_reply, struct relay_cmd_rset_context *rset_cmd) { i_assert(rset_cmd != NULL); struct smtp_server_cmd_ctx *cmd = rset_cmd->cmd; struct submission_backend_relay *backend = rset_cmd->backend; struct smtp_reply reply; /* finished relaying MAIL command to relay server */ rset_cmd->cmd_relayed = NULL; if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, &reply)) return; /* forward reply */ smtp_server_reply_forward(cmd, &reply); } static int backend_relay_cmd_rset(struct submission_backend *_backend, struct smtp_server_cmd_ctx *cmd) { struct submission_backend_relay *backend = (struct submission_backend_relay *)_backend; struct relay_cmd_rset_context *rset_cmd; rset_cmd = p_new(cmd->pool, struct relay_cmd_rset_context, 1); rset_cmd->backend = backend; rset_cmd->cmd = cmd; if (backend->trans != NULL) { /* RSET pipelined after MAIL */ smtp_client_transaction_reset(backend->trans, relay_cmd_rset_callback, rset_cmd); } else { /* RSET alone */ smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_DESTROY, relay_cmd_rset_destroy, rset_cmd); rset_cmd->cmd_relayed = smtp_client_command_rset_submit( backend->conn, 0, relay_cmd_rset_callback, rset_cmd); } return 0; } /* * DATA/BDAT commands */ struct relay_cmd_data_context { struct submission_backend_relay *backend; struct smtp_server_cmd_ctx *cmd; struct smtp_server_transaction *trans; }; static void relay_cmd_data_rcpt_callback(const struct smtp_reply *relay_reply, struct submission_recipient *srcpt) { struct smtp_server_recipient *rcpt = srcpt->rcpt; struct smtp_server_cmd_ctx *cmd = rcpt->cmd; struct submission_backend_relay *backend = (struct submission_backend_relay *)srcpt->backend; struct client *client = srcpt->backend->client; struct smtp_server_transaction *trans = smtp_server_connection_get_transaction(client->conn); struct smtp_reply reply; i_assert(HAS_ALL_BITS(trans->flags, SMTP_SERVER_TRANSACTION_FLAG_REPLY_PER_RCPT)); /* check for fatal problems */ if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, &reply)) return; if (smtp_reply_is_success(&reply)) { i_info("Successfully relayed message: " "from=<%s>, to=<%s>, size=%"PRIuUOFF_T", " "id=%s, rcpt=%u/%u, reply=`%s'", smtp_address_encode(trans->mail_from), smtp_address_encode(rcpt->path), client->state.data_size, trans->id, rcpt->index, array_count(&trans->rcpt_to), str_sanitize(smtp_reply_log(&reply), 128)); } else { i_info("Failed to relay message: " "from=<%s>, to=<%s>, size=%"PRIuUOFF_T", " "rcpt=%u/%u, reply=`%s'", smtp_address_encode(trans->mail_from), smtp_address_encode(rcpt->path), client->state.data_size, rcpt->index, array_count(&trans->rcpt_to), str_sanitize(smtp_reply_log(&reply), 128)); } smtp_server_recipient_reply_forward(rcpt, &reply); } static void relay_cmd_data_callback(const struct smtp_reply *relay_reply, struct relay_cmd_data_context *data_ctx) { i_assert(data_ctx != NULL); struct smtp_server_cmd_ctx *cmd = data_ctx->cmd; struct smtp_server_transaction *trans = data_ctx->trans; struct submission_backend_relay *backend = data_ctx->backend; struct client *client = backend->backend.client; struct smtp_reply reply; /* finished relaying message to relay server */ if (HAS_ALL_BITS(trans->flags, SMTP_SERVER_TRANSACTION_FLAG_REPLY_PER_RCPT)) { /* handled recipient replies individually */ return; } /* check for fatal problems */ if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, &reply)) return; if (smtp_reply_is_success(&reply)) { i_info("Successfully relayed message: " "from=<%s>, size=%"PRIuUOFF_T", " "id=%s, nrcpt=%u, reply=`%s'", smtp_address_encode(trans->mail_from), client->state.data_size, trans->id, array_count(&trans->rcpt_to), str_sanitize(smtp_reply_log(&reply), 128)); } else { i_info("Failed to relay message: " "from=<%s>, size=%"PRIuUOFF_T", nrcpt=%u, reply=`%s'", smtp_address_encode(trans->mail_from), client->state.data_size, array_count(&trans->rcpt_to), str_sanitize(smtp_reply_log(&reply), 128)); } smtp_server_reply_forward(cmd, &reply); } static void backend_relay_cmd_data_init_callbacks(struct submission_backend_relay *backend, struct smtp_server_transaction *trans) { struct client *client = backend->backend.client; struct submission_recipient *rcpt; if (!HAS_ALL_BITS(trans->flags, SMTP_SERVER_TRANSACTION_FLAG_REPLY_PER_RCPT)) return; array_foreach_elem(&client->rcpt_to, rcpt) { struct smtp_client_transaction_rcpt *relay_rcpt = rcpt->backend_context; smtp_client_transaction_rcpt_set_data_callback( relay_rcpt, relay_cmd_data_rcpt_callback, rcpt); } } static int backend_relay_cmd_data(struct submission_backend *_backend, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans, struct istream *data_input, uoff_t data_size ATTR_UNUSED) { struct submission_backend_relay *backend = (struct submission_backend_relay *)_backend; struct relay_cmd_data_context *data_ctx; /* start relaying to relay server */ data_ctx = p_new(trans->pool, struct relay_cmd_data_context, 1); data_ctx->backend = backend; data_ctx->cmd = cmd; data_ctx->trans = trans; trans->context = (void*)data_ctx; i_assert(backend->trans != NULL); backend_relay_cmd_data_init_callbacks(backend, trans); smtp_client_transaction_send(backend->trans, data_input, relay_cmd_data_callback, data_ctx); return 0; } /* * VRFY command */ struct relay_cmd_vrfy_context { struct submission_backend_relay *backend; struct smtp_server_cmd_ctx *cmd; struct smtp_client_command *cmd_relayed; }; static void relay_cmd_vrfy_destroy(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct relay_cmd_vrfy_context *vrfy_cmd) { i_assert(vrfy_cmd != NULL); if (vrfy_cmd->cmd_relayed != NULL) smtp_client_command_abort(&vrfy_cmd->cmd_relayed); } static void relay_cmd_vrfy_callback(const struct smtp_reply *relay_reply, struct relay_cmd_vrfy_context *vrfy_cmd) { i_assert(vrfy_cmd != NULL); struct smtp_server_cmd_ctx *cmd = vrfy_cmd->cmd; struct submission_backend_relay *backend = vrfy_cmd->backend; struct smtp_reply reply; /* finished relaying VRFY command to relay server */ vrfy_cmd->cmd_relayed = NULL; if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, &reply)) return; /* RFC 5321, Section 3.5.3: A server MUST NOT return a 250 code in response to a VRFY or EXPN command unless it has actually verified the address. In particular, a server MUST NOT return 250 if all it has done is to verify that the syntax given is valid. In that case, 502 (Command not implemented) or 500 (Syntax error, command unrecognized) SHOULD be returned. As stated elsewhere, implementation (in the sense of actually validating addresses and returning information) of VRFY and EXPN are strongly recommended. Hence, implementations that return 500 or 502 for VRFY are not in full compliance with this specification. */ if (reply.status == 500 || reply.status == 502) { smtp_server_cmd_vrfy_reply_default(cmd); return; } if (!smtp_reply_has_enhanced_code(&reply)) { switch (relay_reply->status) { case 250: case 251: case 252: reply.enhanced_code = SMTP_REPLY_ENH_CODE(2, 5, 0); break; default: break; } } smtp_server_reply_forward(cmd, &reply); } static int backend_relay_cmd_vrfy(struct submission_backend *_backend, struct smtp_server_cmd_ctx *cmd, const char *param) { struct submission_backend_relay *backend = (struct submission_backend_relay *)_backend; struct relay_cmd_vrfy_context *vrfy_cmd; vrfy_cmd = p_new(cmd->pool, struct relay_cmd_vrfy_context, 1); vrfy_cmd->backend = backend; vrfy_cmd->cmd = cmd; smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_DESTROY, relay_cmd_vrfy_destroy, vrfy_cmd); vrfy_cmd->cmd_relayed = smtp_client_command_vrfy_submit( backend->conn, 0, param, relay_cmd_vrfy_callback, vrfy_cmd); return 0; } /* * NOOP command */ struct relay_cmd_noop_context { struct submission_backend_relay *backend; struct smtp_server_cmd_ctx *cmd; struct smtp_client_command *cmd_relayed; }; static void relay_cmd_noop_destroy(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct relay_cmd_noop_context *noop_cmd) { i_assert(noop_cmd != NULL); if (noop_cmd->cmd_relayed != NULL) smtp_client_command_abort(&noop_cmd->cmd_relayed); } static void relay_cmd_noop_callback(const struct smtp_reply *relay_reply, struct relay_cmd_noop_context *noop_cmd) { i_assert(noop_cmd != NULL); struct smtp_server_cmd_ctx *cmd = noop_cmd->cmd; struct submission_backend_relay *backend = noop_cmd->backend; struct smtp_reply reply; /* finished relaying NOOP command to relay server */ noop_cmd->cmd_relayed = NULL; if (!backend_relay_handle_relay_reply(backend, cmd, relay_reply, &reply)) return; if (smtp_reply_is_success(&reply)) smtp_server_cmd_noop_reply_success(cmd); else smtp_server_reply_forward(cmd, &reply); } static int backend_relay_cmd_noop(struct submission_backend *_backend, struct smtp_server_cmd_ctx *cmd) { struct submission_backend_relay *backend = (struct submission_backend_relay *)_backend; struct relay_cmd_noop_context *noop_cmd; noop_cmd = p_new(cmd->pool, struct relay_cmd_noop_context, 1); noop_cmd->backend = backend; noop_cmd->cmd = cmd; smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_DESTROY, relay_cmd_noop_destroy, noop_cmd); noop_cmd->cmd_relayed = smtp_client_command_noop_submit( backend->conn, 0, relay_cmd_noop_callback, noop_cmd); return 0; } /* * QUIT command */ struct relay_cmd_quit_context { struct submission_backend_relay *backend; struct smtp_server_cmd_ctx *cmd; struct smtp_client_command *cmd_relayed; }; static void relay_cmd_quit_destroy(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct relay_cmd_quit_context *quit_cmd) { i_assert(quit_cmd != NULL); if (quit_cmd->cmd_relayed != NULL) smtp_client_command_abort(&quit_cmd->cmd_relayed); } static void relay_cmd_quit_relayed_destroy(void *context) { struct relay_cmd_quit_context *quit_cmd = context; i_assert(quit_cmd != NULL); quit_cmd->cmd_relayed = NULL; } static void relay_cmd_quit_replied(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct relay_cmd_quit_context *quit_cmd) { if (quit_cmd->cmd_relayed != NULL) smtp_client_command_abort(&quit_cmd->cmd_relayed); } static void relay_cmd_quit_finish(struct relay_cmd_quit_context *quit_cmd) { struct smtp_server_cmd_ctx *cmd = quit_cmd->cmd; quit_cmd->backend->quit_confirmed = TRUE; if (quit_cmd->cmd_relayed != NULL) smtp_client_command_abort(&quit_cmd->cmd_relayed); smtp_server_reply_quit(cmd); } static void relay_cmd_quit_callback(const struct smtp_reply *relay_reply ATTR_UNUSED, struct relay_cmd_quit_context *quit_cmd) { i_assert(quit_cmd != NULL); quit_cmd->cmd_relayed = NULL; relay_cmd_quit_finish(quit_cmd); } static void relay_cmd_quit_relay(struct relay_cmd_quit_context *quit_cmd) { struct submission_backend_relay *backend = quit_cmd->backend; struct smtp_server_cmd_ctx *cmd = quit_cmd->cmd; if (quit_cmd->cmd_relayed != NULL) return; if (smtp_client_connection_get_state(backend->conn) < SMTP_CLIENT_CONNECTION_STATE_READY) { /* Don't bother relaying QUIT command when relay is not fully initialized. */ quit_cmd->backend->quit_confirmed = TRUE; smtp_server_reply_quit(cmd); return; } /* RFC 5321, Section 4.1.1.10: The sender MUST NOT intentionally close the transmission channel until it sends a QUIT command, and it SHOULD wait until it receives the reply (even if there was an error response to a previous command). */ quit_cmd->cmd_relayed = smtp_client_command_new(backend->conn, 0, relay_cmd_quit_callback, quit_cmd); smtp_client_command_write(quit_cmd->cmd_relayed, "QUIT"); smtp_client_command_set_abort_callback( quit_cmd->cmd_relayed, relay_cmd_quit_relayed_destroy, quit_cmd); smtp_client_command_submit(quit_cmd->cmd_relayed); } static void relay_cmd_quit_next(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct relay_cmd_quit_context *quit_cmd) { /* QUIT command is next to reply */ relay_cmd_quit_relay(quit_cmd); } static int backend_relay_cmd_quit(struct submission_backend *_backend, struct smtp_server_cmd_ctx *cmd) { struct submission_backend_relay *backend = (struct submission_backend_relay *)_backend; struct relay_cmd_quit_context *quit_cmd; quit_cmd = p_new(cmd->pool, struct relay_cmd_quit_context, 1); quit_cmd->backend = backend; quit_cmd->cmd = cmd; smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_NEXT, relay_cmd_quit_next, quit_cmd); smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_REPLIED, relay_cmd_quit_replied, quit_cmd); smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_DESTROY, relay_cmd_quit_destroy, quit_cmd); if (smtp_client_connection_get_state(backend->conn) >= SMTP_CLIENT_CONNECTION_STATE_READY) relay_cmd_quit_relay(quit_cmd); return 0; } /* * Relay backend */ struct submission_backend_relay * submission_backend_relay_create( struct client *client, const struct submision_backend_relay_settings *set) { struct submission_backend_relay *backend; struct mail_user *user = client->user; struct ssl_iostream_settings ssl_set; struct smtp_client_settings smtp_set; pool_t pool; pool = pool_alloconly_create("submission relay backend", 1024); backend = p_new(pool, struct submission_backend_relay, 1); submission_backend_init(&backend->backend, pool, client, &backend_relay_vfuncs); mail_user_init_ssl_client_settings(user, &ssl_set); if (set->ssl_verify) ssl_set.verbose_invalid_cert = TRUE; else ssl_set.allow_invalid_cert = TRUE; /* make relay connection */ i_zero(&smtp_set); smtp_set.my_hostname = set->my_hostname; smtp_set.extra_capabilities = set->extra_capabilities; smtp_set.ssl = &ssl_set; smtp_set.debug = user->mail_debug; if (set->rawlog_dir != NULL) { smtp_set.rawlog_dir = mail_user_home_expand(user, set->rawlog_dir); } if (set->trusted) { backend->trusted = TRUE; smtp_set.peer_trusted = TRUE; smtp_server_connection_get_proxy_data(client->conn, &smtp_set.proxy_data); if (user->conn.remote_ip != NULL) { smtp_set.proxy_data.source_ip = *user->conn.remote_ip; smtp_set.proxy_data.source_port = user->conn.remote_port; } smtp_set.proxy_data.login = user->username; smtp_set.xclient_defer = TRUE; } smtp_set.username = set->user; smtp_set.master_user = set->master_user; smtp_set.password = set->password; smtp_set.sasl_mech = set->sasl_mech; smtp_set.connect_timeout_msecs = set->connect_timeout_msecs; smtp_set.command_timeout_msecs = set->command_timeout_msecs; if (set->path != NULL) { backend->conn = smtp_client_connection_create_unix( smtp_client, set->protocol, set->path, &smtp_set); } else if (set->ip.family == 0) { backend->conn = smtp_client_connection_create( smtp_client, set->protocol, set->host, set->port, set->ssl_mode, &smtp_set); } else { backend->conn = smtp_client_connection_create_ip( smtp_client, set->protocol, &set->ip, set->port, set->host, set->ssl_mode, &smtp_set); } return backend; } struct submission_backend * submission_backend_relay_get(struct submission_backend_relay *backend) { return &backend->backend; } struct smtp_client_connection * submission_backend_relay_get_connection( struct submission_backend_relay *backend) { return backend->conn; } struct smtp_client_transaction * submission_backend_relay_get_transaction( struct submission_backend_relay *backend) { return backend->trans; } static void backend_relay_destroy(struct submission_backend *_backend) { struct submission_backend_relay *backend = (struct submission_backend_relay *)_backend; if (backend->trans != NULL) smtp_client_transaction_destroy(&backend->trans); if (backend->conn != NULL) smtp_client_connection_close(&backend->conn); } static void backend_relay_ready_cb(const struct smtp_reply *reply, void *context) { struct submission_backend_relay *backend = context; struct smtp_reply dummy; /* check relay status */ if (!backend_relay_handle_relay_reply(backend, NULL, reply, &dummy)) return; if (!smtp_reply_is_success(reply)) { i_error("Failed to establish relay connection: %s", smtp_reply_log(reply)); submission_backend_fail( &backend->backend, NULL, "4.4.0", "Failed to establish relay connection"); return; } /* notify the backend API about the fact that we're ready and propagate our capabilities */ submission_backend_started(&backend->backend, smtp_client_connection_get_capabilities(backend->conn)); } static void backend_relay_start(struct submission_backend *_backend) { struct submission_backend_relay *backend = (struct submission_backend_relay *)_backend; smtp_client_connection_connect(backend->conn, backend_relay_ready_cb, backend); } /* try to proxy pipelined commands in a similarly pipelined fashion */ static void backend_relay_client_input_pre(struct submission_backend *_backend) { struct submission_backend_relay *backend = (struct submission_backend_relay *)_backend; if (backend->conn != NULL) smtp_client_connection_cork(backend->conn); } static void backend_relay_client_input_post(struct submission_backend *_backend) { struct submission_backend_relay *backend = (struct submission_backend_relay *)_backend; if (backend->conn != NULL) smtp_client_connection_uncork(backend->conn); } static uoff_t backend_relay_get_max_mail_size(struct submission_backend *_backend) { struct submission_backend_relay *backend = (struct submission_backend_relay *)_backend; return smtp_client_connection_get_size_capability(backend->conn); } static struct submission_backend_vfuncs backend_relay_vfuncs = { .destroy = backend_relay_destroy, .start = backend_relay_start, .client_input_pre = backend_relay_client_input_pre, .client_input_post = backend_relay_client_input_post, .get_max_mail_size = backend_relay_get_max_mail_size, .trans_start = backend_relay_trans_start, .trans_free = backend_relay_trans_free, .cmd_helo = backend_relay_cmd_helo, .cmd_mail = backend_relay_cmd_mail, .cmd_rcpt = backend_relay_cmd_rcpt, .cmd_rset = backend_relay_cmd_rset, .cmd_data = backend_relay_cmd_data, .cmd_vrfy = backend_relay_cmd_vrfy, .cmd_noop = backend_relay_cmd_noop, .cmd_quit = backend_relay_cmd_quit, }; dovecot-2.3.21.1/src/submission/submission-client.c0000644000000000000000000003722314656633576017121 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "submission-common.h" #include "array.h" #include "ioloop.h" #include "base64.h" #include "str.h" #include "llist.h" #include "net.h" #include "istream.h" #include "ostream.h" #include "hostpid.h" #include "var-expand.h" #include "settings-parser.h" #include "master-service.h" #include "master-service-settings.h" #include "mail-namespace.h" #include "mail-storage.h" #include "mail-storage-service.h" #include "raw-storage.h" #include "imap-urlauth.h" #include "smtp-syntax.h" #include "smtp-client-connection.h" #include "submission-backend-relay.h" #include "submission-recipient.h" #include "submission-commands.h" #include "submission-settings.h" #include /* max. length of input command line */ #define MAX_INBUF_SIZE 4096 /* Stop reading input when output buffer has this many bytes. Once the buffer size has dropped to half of it, start reading input again. */ #define OUTBUF_THROTTLE_SIZE 4096 /* Disconnect client when it sends too many bad commands in a row */ #define CLIENT_MAX_BAD_COMMANDS 20 /* Disconnect client after idling this many milliseconds */ #define CLIENT_IDLE_TIMEOUT_MSECS (10*60*1000) static const struct smtp_server_callbacks smtp_callbacks; static const struct submission_client_vfuncs submission_client_vfuncs; struct submission_module_register submission_module_register = { 0 }; struct client *submission_clients; unsigned int submission_client_count; static void client_input_pre(void *context) { struct client *client = context; submission_backends_client_input_pre(client); } static void client_input_post(void *context) { struct client *client = context; submission_backends_client_input_post(client); } static void client_parse_backend_capabilities(struct client *client) { const struct submission_settings *set = client->set; const char *const *str; client->backend_capabilities = SMTP_CAPABILITY_NONE; if (set->submission_backend_capabilities == NULL) return; str = t_strsplit_spaces(set->submission_backend_capabilities, " ,"); for (; *str != NULL; str++) { enum smtp_capability cap = smtp_capability_find_by_name(*str); if (cap == SMTP_CAPABILITY_NONE) { i_warning("Unknown SMTP capability in submission_backend_capabilities: " "%s", *str); continue; } client->backend_capabilities |= cap; } /* Make sure CHUNKING support is always enabled when BINARYMIME is enabled by explicit configuration. */ if (HAS_ALL_BITS(client->backend_capabilities, SMTP_CAPABILITY_BINARYMIME)) { client->backend_capabilities |= SMTP_CAPABILITY_CHUNKING; } client->backend_capabilities_configured = TRUE; } void client_apply_backend_capabilities(struct client *client) { enum smtp_capability caps = client->backend_capabilities; /* propagate capabilities */ caps |= SMTP_CAPABILITY_AUTH | SMTP_CAPABILITY_PIPELINING | SMTP_CAPABILITY_SIZE | SMTP_CAPABILITY_ENHANCEDSTATUSCODES | SMTP_CAPABILITY_CHUNKING | SMTP_CAPABILITY_BURL; caps &= SUBMISSION_SUPPORTED_SMTP_CAPABILITIES; smtp_server_connection_set_capabilities(client->conn, caps); } void client_default_backend_started(struct client *client, enum smtp_capability caps) { /* propagate capabilities from backend to frontend */ if (!client->backend_capabilities_configured) { client->backend_capabilities = caps; client_apply_backend_capabilities(client); /* resume the server now that we have the backend capabilities */ smtp_server_connection_resume(client->conn); } } static void client_create_backend_default(struct client *client, const struct submission_settings *set) { struct submision_backend_relay_settings relay_set; i_zero(&relay_set); relay_set.my_hostname = set->hostname; relay_set.protocol = SMTP_PROTOCOL_SMTP; relay_set.host = set->submission_relay_host; relay_set.port = set->submission_relay_port; relay_set.user = set->submission_relay_user; relay_set.master_user = set->submission_relay_master_user; relay_set.password = set->submission_relay_password; relay_set.rawlog_dir = set->submission_relay_rawlog_dir; relay_set.max_idle_time = set->submission_relay_max_idle_time; relay_set.connect_timeout_msecs = set->submission_relay_connect_timeout; relay_set.command_timeout_msecs = set->submission_relay_command_timeout; relay_set.trusted = set->submission_relay_trusted; if (strcmp(set->submission_relay_ssl, "smtps") == 0) relay_set.ssl_mode = SMTP_CLIENT_SSL_MODE_IMMEDIATE; else if (strcmp(set->submission_relay_ssl, "starttls") == 0) relay_set.ssl_mode = SMTP_CLIENT_SSL_MODE_STARTTLS; else relay_set.ssl_mode = SMTP_CLIENT_SSL_MODE_NONE; relay_set.ssl_verify = set->submission_relay_ssl_verify; client->backend_default_relay = submission_backend_relay_create(client, &relay_set); client->backend_default = submission_backend_relay_get(client->backend_default_relay); } static void client_init_urlauth(struct client *client) { static const char *access_apps[] = { "submit+", NULL }; struct imap_urlauth_config config; i_zero(&config); config.url_host = client->set->imap_urlauth_host; config.url_port = client->set->imap_urlauth_port; config.socket_path = t_strconcat(client->user->set->base_dir, "/"IMAP_URLAUTH_SOCKET_NAME, NULL); config.session_id = client->user->session_id; config.access_anonymous = client->user->anonymous; config.access_user = client->user->username; config.access_service = "submission"; config.access_applications = access_apps; client->urlauth_ctx = imap_urlauth_init(client->user, &config); } struct client * client_create(int fd_in, int fd_out, struct mail_user *user, struct mail_storage_service_user *service_user, const struct submission_settings *set, const char *helo, const struct smtp_proxy_data *proxy_data, const unsigned char *pdata, unsigned int pdata_len, bool no_greeting) { enum submission_client_workarounds workarounds = set->parsed_workarounds; const struct mail_storage_settings *mail_set; struct smtp_server_settings smtp_set; const char *ident; struct client *client; pool_t pool; /* always use nonblocking I/O */ net_set_nonblock(fd_in, TRUE); net_set_nonblock(fd_out, TRUE); pool = pool_alloconly_create("submission client", 2048); client = p_new(pool, struct client, 1); client->pool = pool; client->v = submission_client_vfuncs; client->user = user; client->service_user = service_user; client->set = set; i_array_init(&client->pending_backends, 4); i_array_init(&client->rcpt_to, 8); i_array_init(&client->rcpt_backends, 8); i_zero(&smtp_set); smtp_set.hostname = set->hostname; smtp_set.login_greeting = set->login_greeting; smtp_set.max_recipients = set->submission_max_recipients; smtp_set.max_client_idle_time_msecs = CLIENT_IDLE_TIMEOUT_MSECS; smtp_set.max_message_size = set->submission_max_mail_size; smtp_set.rawlog_dir = set->rawlog_dir; smtp_set.no_greeting = no_greeting; smtp_set.debug = user->mail_debug; if ((workarounds & SUBMISSION_WORKAROUND_WHITESPACE_BEFORE_PATH) != 0) { smtp_set.workarounds |= SMTP_SERVER_WORKAROUND_WHITESPACE_BEFORE_PATH; } if ((workarounds & SUBMISSION_WORKAROUND_MAILBOX_FOR_PATH) != 0) { smtp_set.workarounds |= SMTP_SERVER_WORKAROUND_MAILBOX_FOR_PATH; } client_parse_backend_capabilities(client); p_array_init(&client->module_contexts, client->pool, 5); client->conn = smtp_server_connection_create(smtp_server, fd_in, fd_out, user->conn.remote_ip, user->conn.remote_port, FALSE, &smtp_set, &smtp_callbacks, client); smtp_server_connection_set_proxy_data(client->conn, proxy_data); smtp_server_connection_login(client->conn, client->user->username, helo, pdata, pdata_len, user->conn.ssl_secured); client_create_backend_default(client, set); mail_set = mail_user_set_get_storage_set(user); if (*set->imap_urlauth_host != '\0' && *mail_set->mail_attribute_dict != '\0') { /* Enable BURL capability only when urlauth dict is configured correctly */ client_init_urlauth(client); } submission_client_count++; DLLIST_PREPEND(&submission_clients, client); ident = mail_user_get_anvil_userip_ident(client->user); if (ident != NULL) { master_service_anvil_send( master_service, t_strconcat( "CONNECT\t", my_pid, "\tsubmission/", ident, "\n", NULL)); client->anvil_sent = TRUE; } if (hook_client_created != NULL) hook_client_created(&client); if (user->anonymous && !client->anonymous_allowed) { smtp_server_connection_abort( &client->conn, 534, "5.7.9", "Anonymous login is not allowed for submission"); } else if (client->backend_capabilities_configured) { client_apply_backend_capabilities(client); smtp_server_connection_start(client->conn); } else { submission_backend_start(client->backend_default); smtp_server_connection_start_pending(client->conn); } submission_refresh_proctitle(); return client; } static void client_state_reset(struct client *client) { i_free(client->state.args); i_stream_unref(&client->state.data_input); pool_unref(&client->state.pool); i_zero(&client->state); } void client_destroy(struct client **_client, const char *prefix, const char *reason) { struct client *client = *_client; struct smtp_server_connection *conn = client->conn; *_client = NULL; smtp_server_connection_terminate( &conn, (prefix == NULL ? "4.0.0" : prefix), reason); } static void client_default_destroy(struct client *client) { i_assert(client->disconnected); if (client->destroyed) return; client->destroyed = TRUE; submission_backends_destroy_all(client); array_free(&client->pending_backends); array_free(&client->rcpt_to); array_free(&client->rcpt_backends); submission_client_count--; DLLIST_REMOVE(&submission_clients, client); if (client->anvil_sent) { master_service_anvil_send( master_service, t_strconcat( "DISCONNECT\t", my_pid, "\tsubmission/", mail_user_get_anvil_userip_ident(client->user), "\n", NULL)); } if (client->urlauth_ctx != NULL) imap_urlauth_deinit(&client->urlauth_ctx); mail_user_deinit(&client->user); mail_storage_service_user_unref(&client->service_user); client_state_reset(client); pool_unref(&client->pool); master_service_client_connection_destroyed(master_service); submission_refresh_proctitle(); } static void client_connection_trans_start(void *context, struct smtp_server_transaction *trans) { struct client *client = context; client->state.pool = pool_alloconly_create("submission client state", 1024); client->v.trans_start(client, trans); } static void client_default_trans_start(struct client *client, struct smtp_server_transaction *trans) { submission_backends_trans_start(client, trans); } static void client_connection_trans_free(void *context, struct smtp_server_transaction *trans) { struct client *client = context; client->v.trans_free(client, trans); } static void client_default_trans_free(struct client *client, struct smtp_server_transaction *trans) { array_clear(&client->rcpt_to); submission_backends_trans_free(client, trans); client_state_reset(client); } static void client_connection_state_changed(void *context ATTR_UNUSED, enum smtp_server_state new_state, const char *new_args) { struct client *client = context; i_free(client->state.args); client->state.state = new_state; client->state.args = i_strdup(new_args); if (submission_client_count == 1) submission_refresh_proctitle(); } static const char *client_stats(struct client *client) { const struct smtp_server_stats *stats = smtp_server_connection_get_stats(client->conn); const char *trans_id = smtp_server_connection_get_transaction_id(client->conn); const struct var_expand_table logout_tab[] = { { 'i', dec2str(stats->input), "input" }, { 'o', dec2str(stats->output), "output" }, { '\0', dec2str(stats->command_count), "command_count" }, { '\0', dec2str(stats->reply_count), "reply_count" }, { '\0', trans_id, "transaction_id" }, { '\0', NULL, NULL } }; const struct var_expand_table *user_tab = mail_user_var_expand_table(client->user); const struct var_expand_table *tab = t_var_expand_merge_tables(logout_tab, user_tab); string_t *str; const char *error; str = t_str_new(128); if (var_expand_with_funcs(str, client->set->submission_logout_format, tab, mail_user_var_expand_func_table, client->user, &error) < 0) { i_error("Failed to expand submission_logout_format=%s: %s", client->set->submission_logout_format, error); } return str_c(str); } static void client_connection_disconnect(void *context, const char *reason) { struct client *client = context; const char *log_reason; if (client->disconnected) return; client->disconnected = TRUE; timeout_remove(&client->to_quit); submission_backends_destroy_all(client); if (array_is_created(&client->rcpt_to)) array_clear(&client->rcpt_to); if (reason == NULL) log_reason = "Connection closed"; else log_reason = t_str_oneline(reason); i_info("Disconnected: %s %s", log_reason, client_stats(client)); } static void client_connection_free(void *context) { struct client *client = context; client->v.destroy(client); } uoff_t client_get_max_mail_size(struct client *client) { struct submission_backend *backend; uoff_t max_size, limit; /* Account for backend SIZE limits and calculate our own relative to those. */ max_size = client->set->submission_max_mail_size; if (max_size == 0) max_size = UOFF_T_MAX; for (backend = client->backends; backend != NULL; backend = backend->next) { limit = submission_backend_get_max_mail_size(backend); if (limit <= SUBMISSION_MAX_ADDITIONAL_MAIL_SIZE) continue; limit -= SUBMISSION_MAX_ADDITIONAL_MAIL_SIZE; if (limit < max_size) max_size = limit; } return max_size; } void client_add_extra_capability(struct client *client, const char *capability, const char *params) { struct client_extra_capability cap; /* Don't add capabilties handled by lib-smtp here */ i_assert(smtp_capability_find_by_name(capability) == SMTP_CAPABILITY_NONE); /* Avoid committing protocol errors */ i_assert(smtp_ehlo_keyword_is_valid(capability)); i_assert(params == NULL || smtp_ehlo_params_str_is_valid(params)); i_zero(&cap); cap.capability = p_strdup(client->pool, capability); cap.params = p_strdup(client->pool, params); if (!array_is_created(&client->extra_capabilities)) p_array_init(&client->extra_capabilities, client->pool, 5); array_push_back(&client->extra_capabilities, &cap); } void clients_destroy_all(void) { while (submission_clients != NULL) { struct client *client = submission_clients; mail_storage_service_io_activate_user(client->service_user); client_destroy(&client, "4.3.2", "Shutting down"); } } static const struct smtp_server_callbacks smtp_callbacks = { .conn_cmd_helo = cmd_helo, .conn_cmd_mail = cmd_mail, .conn_cmd_rcpt = cmd_rcpt, .conn_cmd_rset = cmd_rset, .conn_cmd_data_begin = cmd_data_begin, .conn_cmd_data_continue = cmd_data_continue, .conn_cmd_vrfy = cmd_vrfy, .conn_cmd_noop = cmd_noop, .conn_cmd_quit = cmd_quit, .conn_cmd_input_pre = client_input_pre, .conn_cmd_input_post = client_input_post, .conn_trans_start = client_connection_trans_start, .conn_trans_free = client_connection_trans_free, .conn_state_changed = client_connection_state_changed, .conn_disconnect = client_connection_disconnect, .conn_free = client_connection_free, }; static const struct submission_client_vfuncs submission_client_vfuncs = { client_default_destroy, .trans_start = client_default_trans_start, .trans_free = client_default_trans_free, .cmd_helo = client_default_cmd_helo, .cmd_mail = client_default_cmd_mail, .cmd_rcpt = client_default_cmd_rcpt, .cmd_rset = client_default_cmd_rset, .cmd_data = client_default_cmd_data, .cmd_vrfy = client_default_cmd_vrfy, .cmd_noop = client_default_cmd_noop, .cmd_quit = client_default_cmd_quit, }; dovecot-2.3.21.1/src/submission/submission-commands.c0000644000000000000000000003751514656633576017450 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "submission-common.h" #include "str.h" #include "istream.h" #include "istream-concat.h" #include "istream-seekable.h" #include "mail-storage.h" #include "imap-url.h" #include "imap-msgpart.h" #include "imap-msgpart-url.h" #include "imap-urlauth.h" #include "imap-urlauth-fetch.h" #include "submission-recipient.h" #include "submission-commands.h" #include "submission-backend-relay.h" /* * EHLO, HELO commands */ static void submission_helo_reply_add_extra(struct client *client, struct smtp_server_reply *reply) { const struct client_extra_capability *cap; if (!array_is_created(&client->extra_capabilities)) return; array_foreach(&client->extra_capabilities, cap) { if (cap->params == NULL) { smtp_server_reply_ehlo_add(reply, cap->capability); } else { smtp_server_reply_ehlo_add_param(reply, cap->capability, "%s", cap->params); } } } void submission_helo_reply_submit(struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_helo *data) { struct client *client = smtp_server_connection_get_context(cmd->conn); enum smtp_capability backend_caps = client->backend_capabilities; struct smtp_server_reply *reply; uoff_t cap_size; reply = smtp_server_reply_create_ehlo(cmd->cmd); if (!data->helo.old_smtp) { string_t *burl_params = t_str_new(256); str_append(burl_params, "imap"); if (*client->set->imap_urlauth_host == '\0' || strcmp(client->set->imap_urlauth_host, URL_HOST_ALLOW_ANY) == 0) { str_printfa(burl_params, " imap://%s", client->set->hostname); } else { str_printfa(burl_params, " imap://%s", client->set->imap_urlauth_host); } if (client->set->imap_urlauth_port != 143) { str_printfa(burl_params, ":%u", client->set->imap_urlauth_port); } if ((backend_caps & SMTP_CAPABILITY_8BITMIME) != 0) smtp_server_reply_ehlo_add(reply, "8BITMIME"); smtp_server_reply_ehlo_add(reply, "AUTH"); if ((backend_caps & SMTP_CAPABILITY_BINARYMIME) != 0 && (backend_caps & SMTP_CAPABILITY_CHUNKING) != 0) smtp_server_reply_ehlo_add(reply, "BINARYMIME"); smtp_server_reply_ehlo_add_param(reply, "BURL", "%s", str_c(burl_params)); smtp_server_reply_ehlo_add(reply, "CHUNKING"); if ((backend_caps & SMTP_CAPABILITY_DSN) != 0) smtp_server_reply_ehlo_add(reply, "DSN"); smtp_server_reply_ehlo_add(reply, "ENHANCEDSTATUSCODES"); smtp_server_reply_ehlo_add(reply, "PIPELINING"); cap_size = client_get_max_mail_size(client); if (cap_size > 0) { smtp_server_reply_ehlo_add_param(reply, "SIZE", "%"PRIuUOFF_T, cap_size); } else { smtp_server_reply_ehlo_add(reply, "SIZE"); } if ((backend_caps & SMTP_CAPABILITY_VRFY) != 0) smtp_server_reply_ehlo_add(reply, "VRFY"); submission_helo_reply_add_extra(client, reply); } smtp_server_reply_submit(reply); } int cmd_helo(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_helo *data) { struct client *client = conn_ctx; if (!data->first || smtp_server_connection_get_state(client->conn, NULL) >= SMTP_SERVER_STATE_READY) return client->v.cmd_helo(client, cmd, data); /* respond right away */ submission_helo_reply_submit(cmd, data); return 1; } int client_default_cmd_helo(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_helo *data) { return submission_backend_cmd_helo(client->backend_default, cmd, data); } /* * MAIL command */ int cmd_mail(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_mail *data) { struct client *client = conn_ctx; client->state.backend = client->backend_default; return client->v.cmd_mail(client, cmd, data); } int client_default_cmd_mail(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_mail *data) { if (client->user->anonymous && !client->state.anonymous_allowed) { /* NOTE: may need to allow anonymous BURL access in the future, but while that is not supported, deny all anonymous access explicitly. */ smtp_server_reply(cmd, 554, "5.7.1", "Access denied (anonymous user)"); return -1; } return submission_backend_cmd_mail(client->state.backend, cmd, data); } /* * RCPT command */ int cmd_rcpt(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_recipient *rcpt) { struct client *client = conn_ctx; struct submission_recipient *srcpt; srcpt = submission_recipient_create(client, rcpt); return client->v.cmd_rcpt(client, cmd, srcpt); } int client_default_cmd_rcpt(struct client *client ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd, struct submission_recipient *srcpt) { if (client->user->anonymous && !srcpt->anonymous_allowed) { /* NOTE: may need to allow anonymous BURL access in the future, but while that is not supported, deny all anonymous access explicitly. */ smtp_server_recipient_reply( srcpt->rcpt, 554, "5.7.1", "Access denied (anonymous user)"); return -1; } return submission_backend_cmd_rcpt(srcpt->backend, cmd, srcpt); } /* * RSET command */ int cmd_rset(void *conn_ctx, struct smtp_server_cmd_ctx *cmd) { struct client *client = conn_ctx; return client->v.cmd_rset(client, cmd); } int client_default_cmd_rset(struct client *client, struct smtp_server_cmd_ctx *cmd) { struct submission_backend *backend = client->state.backend; if (backend == NULL) backend = client->backend_default; /* all backends will also be notified through trans_free(), but that doesn't allow changing the RSET command response. */ return submission_backend_cmd_rset(backend, cmd); } /* * DATA/BDAT commands */ int cmd_data_continue(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans) { struct client *client = conn_ctx; struct istream *data_input = client->state.data_input; uoff_t data_size; struct istream *inputs[3]; string_t *added_headers; const unsigned char *data; size_t size; int ret; while ((ret = i_stream_read_more(data_input, &data, &size)) > 0) { i_stream_skip(data_input, size); if (!smtp_server_cmd_data_check_size(cmd)) return -1; } if (ret == 0) return 0; if (ret < 0 && data_input->stream_errno != 0) return -1; /* Done reading DATA stream; remove it from state and continue with local variable. */ client->state.data_input = NULL; /* Current data stream position is the data size */ client->state.data_size = data_input->v_offset; /* prepend our own headers */ added_headers = t_str_new(200); smtp_server_transaction_write_trace_record( added_headers, trans, SMTP_SERVER_TRACE_RCPT_TO_ADDRESS_FINAL); i_stream_seek(data_input, 0); inputs[0] = i_stream_create_copy_from_data( str_data(added_headers), str_len(added_headers)); inputs[1] = data_input; inputs[2] = NULL; data_input = i_stream_create_concat(inputs); i_stream_set_name(data_input, ""); data_size = client->state.data_size + str_len(added_headers); i_stream_unref(&inputs[0]); i_stream_unref(&inputs[1]); ret = client->v.cmd_data(client, cmd, trans, data_input, data_size); i_stream_unref(&data_input); return ret; } int cmd_data_begin(void *conn_ctx, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct smtp_server_transaction *trans ATTR_UNUSED, struct istream *data_input) { struct client *client = conn_ctx; struct istream *inputs[2]; string_t *path; if (client->user->anonymous && !client->state.anonymous_allowed) { smtp_server_reply(cmd, 554, "5.7.1", "Access denied (anonymous user)"); return -1; } inputs[0] = data_input; inputs[1] = NULL; path = t_str_new(256); mail_user_set_get_temp_prefix(path, client->user->set); client->state.data_input = i_stream_create_seekable_path(inputs, SUBMISSION_MAIL_DATA_MAX_INMEMORY_SIZE, str_c(path)); return 0; } int client_default_cmd_data(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans, struct istream *data_input, uoff_t data_size) { return submission_backends_cmd_data(client, cmd, trans, data_input, data_size); } /* * BURL command */ /* FIXME: RFC 4468 If the URL argument to BURL refers to binary data, then the submit server MAY refuse the command or down convert as described in Binary SMTP. */ struct cmd_burl_context { struct client *client; struct smtp_server_cmd_ctx *cmd; struct imap_urlauth_fetch *urlauth_fetch; struct imap_msgpart_url *url_fetch; bool chunk_last:1; }; static void cmd_burl_destroy(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct cmd_burl_context *burl_cmd) { if (burl_cmd->urlauth_fetch != NULL) imap_urlauth_fetch_deinit(&burl_cmd->urlauth_fetch); if (burl_cmd->url_fetch != NULL) imap_msgpart_url_free(&burl_cmd->url_fetch); } static int cmd_burl_fetch_cb(struct imap_urlauth_fetch_reply *reply, bool last, void *context) { struct cmd_burl_context *burl_cmd = context; struct smtp_server_cmd_ctx *cmd = burl_cmd->cmd; int ret; i_assert(last); if (reply == NULL) { /* fatal failure */ // FIXME: make this an internal error smtp_server_reply(cmd, 554, "5.6.6", "IMAP URLAUTH resolution failed"); return -1; } if (!reply->succeeded) { /* URL fetch failed */ if (reply->error != NULL) { smtp_server_reply(cmd, 554, "5.6.6", "IMAP URLAUTH resolution failed: %s", reply->error); } else { smtp_server_reply(cmd, 554, "5.6.6", "IMAP URLAUTH resolution failed"); } return 1; } /* URL fetch succeeded */ ret = smtp_server_connection_data_chunk_add(cmd, reply->input, reply->size, burl_cmd->chunk_last, FALSE); if (ret < 0) return -1; /* Command is likely not yet complete at this point, so return 0 */ return 0; } static int cmd_burl_fetch_trusted(struct cmd_burl_context *burl_cmd, struct imap_url *imap_url) { struct smtp_server_cmd_ctx *cmd = burl_cmd->cmd; struct client *client = burl_cmd->client; const char *host_name = client->set->imap_urlauth_host; in_port_t host_port = client->set->imap_urlauth_port; struct imap_msgpart_open_result result; const char *error; /* validate host */ if (imap_url->host.name == NULL || (strcmp(host_name, URL_HOST_ALLOW_ANY) != 0 && strcmp(imap_url->host.name, host_name) != 0)) { smtp_server_reply(cmd, 554, "5.6.6", "IMAP URL resolution failed: " "Inappropriate or missing host name"); return -1; } /* validate port */ if ((imap_url->port == 0 && host_port != 143) || (imap_url->port != 0 && host_port != imap_url->port)) { smtp_server_reply(cmd, 554, "5.6.6", "IMAP URL resolution failed: " "Inappropriate server port"); return -1; } /* retrieve URL */ if (imap_msgpart_url_create (client->user, imap_url, &burl_cmd->url_fetch, &error) < 0) { smtp_server_reply(cmd, 554, "5.6.6", "IMAP URL resolution failed: %s", error); return -1; } if (imap_msgpart_url_read_part(burl_cmd->url_fetch, &result, &error) <= 0) { smtp_server_reply(cmd, 554, "5.6.6", "IMAP URL resolution failed: %s", error); return -1; } return smtp_server_connection_data_chunk_add(cmd, result.input, result.size, burl_cmd->chunk_last, FALSE); } static int cmd_burl_fetch(struct cmd_burl_context *burl_cmd, const char *url, struct imap_url *imap_url) { struct smtp_server_cmd_ctx *cmd = burl_cmd->cmd; struct client *client = burl_cmd->client; if (client->urlauth_ctx == NULL) { /* RFC5248, Section 2.4: 554 5.7.14 Trust relationship required The submission server requires a configured trust relationship with a third-party server in order to access the message content. This value replaces the prior use of X.7.8 for this error condition, thereby updating [RFC4468]. */ smtp_server_reply(cmd, 554, "5.7.14", "No IMAP URLAUTH access available"); return -1; } /* urlauth */ burl_cmd->urlauth_fetch = imap_urlauth_fetch_init(client->urlauth_ctx, cmd_burl_fetch_cb, burl_cmd); if (imap_urlauth_fetch_url_parsed(burl_cmd->urlauth_fetch, url, imap_url, IMAP_URLAUTH_FETCH_FLAG_BODY) == 0) { /* wait for URL fetch */ return 0; } return 1; } void cmd_burl(struct smtp_server_cmd_ctx *cmd, const char *params) { struct smtp_server_connection *conn = cmd->conn; struct client *client = smtp_server_connection_get_context(conn); struct cmd_burl_context *burl_cmd; const char *const *argv; enum imap_url_parse_flags url_parse_flags = IMAP_URL_PARSE_ALLOW_URLAUTH; struct imap_url *imap_url; const char *url, *error; bool chunk_last = FALSE; int ret = 1; smtp_server_connection_data_chunk_init(cmd); /* burl-cmd = "BURL" SP absolute-URI [SP end-marker] CRLF end-marker = "LAST" */ argv = t_strsplit(params, " "); url = argv[0]; if (url == NULL) { smtp_server_reply(cmd, 501, "5.5.4", "Missing chunk URL parameter"); ret = -1; } else if (imap_url_parse(url, NULL, url_parse_flags, &imap_url, &error) < 0) { smtp_server_reply(cmd, 501, "5.5.4", "Invalid chunk URL: %s", error); ret = -1; } else if (argv[1] != NULL) { if (strcasecmp(argv[1], "LAST") != 0) { smtp_server_reply(cmd, 501, "5.5.4", "Invalid end marker parameter"); ret = -1; } else if (argv[2] != NULL) { smtp_server_reply(cmd, 501, "5.5.4", "Invalid parameters"); ret = -1; } else { chunk_last = TRUE; } } if (ret < 0 || !smtp_server_connection_data_check_state(cmd)) return; if (client->user->anonymous) { smtp_server_reply(cmd, 554, "5.7.1", "Access denied (anonymous user)"); return; } burl_cmd = p_new(cmd->pool, struct cmd_burl_context, 1); burl_cmd->client = client; burl_cmd->cmd = cmd; burl_cmd->chunk_last = chunk_last; smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_DESTROY, cmd_burl_destroy, burl_cmd); if (imap_url->uauth_rumpurl == NULL) { /* direct local url */ ret = cmd_burl_fetch_trusted(burl_cmd, imap_url); } else { ret = cmd_burl_fetch(burl_cmd, url, imap_url); } if (ret == 0 && chunk_last) smtp_server_command_input_lock(cmd); } /* * VRFY command */ int cmd_vrfy(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, const char *param) { struct client *client = conn_ctx; if (client->user->anonymous) { smtp_server_reply(cmd, 550, "5.7.1", "Access denied (anonymous user)"); return -1; } return client->v.cmd_vrfy(client, cmd, param); } int client_default_cmd_vrfy(struct client *client, struct smtp_server_cmd_ctx *cmd, const char *param) { return submission_backend_cmd_vrfy(client->backend_default, cmd, param); } /* * NOOP command */ int cmd_noop(void *conn_ctx, struct smtp_server_cmd_ctx *cmd) { struct client *client = conn_ctx; return client->v.cmd_noop(client, cmd); } int client_default_cmd_noop(struct client *client, struct smtp_server_cmd_ctx *cmd) { return submission_backend_cmd_noop(client->backend_default, cmd); } /* * QUIT command */ struct cmd_quit_context { struct client *client; struct smtp_server_cmd_ctx *cmd; }; static void cmd_quit_finish(struct cmd_quit_context *quit_cmd) { struct client *client = quit_cmd->client; struct smtp_server_cmd_ctx *cmd = quit_cmd->cmd; timeout_remove(&client->to_quit); smtp_server_reply_quit(cmd); } static void cmd_quit_next(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct cmd_quit_context *quit_cmd) { struct client *client = quit_cmd->client; /* give backend a brief interval to generate a quit reply */ client->to_quit = timeout_add(SUBMISSION_MAX_WAIT_QUIT_REPLY_MSECS, cmd_quit_finish, quit_cmd); } int cmd_quit(void *conn_ctx, struct smtp_server_cmd_ctx *cmd) { struct client *client = conn_ctx; struct cmd_quit_context *quit_cmd; quit_cmd = p_new(cmd->pool, struct cmd_quit_context, 1); quit_cmd->client = client; quit_cmd->cmd = cmd; smtp_server_command_add_hook(cmd->cmd, SMTP_SERVER_COMMAND_HOOK_NEXT, cmd_quit_next, quit_cmd); return client->v.cmd_quit(client, cmd); } int client_default_cmd_quit(struct client *client, struct smtp_server_cmd_ctx *cmd) { return submission_backend_cmd_quit(client->backend_default, cmd); } dovecot-2.3.21.1/src/submission/submission-backend.c0000644000000000000000000002723714656633576017236 00000000000000/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ #include "submission-common.h" #include "llist.h" #include "istream.h" #include "istream-sized.h" #include "submission-recipient.h" #include "submission-client.h" #include "submission-commands.h" #include "submission-backend.h" struct submission_backend_module_register submission_backend_module_register = { 0 }; void submission_backend_init(struct submission_backend *backend, pool_t pool, struct client *client, const struct submission_backend_vfuncs *vfunc) { backend->pool = pool; backend->client = client; backend->v = *vfunc; p_array_init(&backend->module_contexts, pool, 5); client->backends_count++; DLLIST_PREPEND(&client->backends, backend); } static void submission_backend_destroy(struct submission_backend *backend) { struct client *client = backend->client; i_stream_unref(&backend->data_input); i_free(backend->fail_enh_code); i_free(backend->fail_reason); DLLIST_REMOVE(&client->backends, backend); backend->v.destroy(backend); pool_unref(&backend->pool); } void submission_backends_destroy_all(struct client *client) { while (client->backends != NULL) submission_backend_destroy(client->backends); array_clear(&client->rcpt_backends); client->state.backend = NULL; } void submission_backend_start(struct submission_backend *backend) { if (backend->started) return; if (backend->fail_reason != NULL) { /* Don't restart until failure is reset at transaction end */ return; } backend->started = TRUE; backend->v.start(backend); } void submission_backend_started(struct submission_backend *backend, enum smtp_capability caps) { struct client *client = backend->client; if (backend == client->backend_default) client_default_backend_started(client, caps); backend->ready = TRUE; if (backend->v.ready != NULL) backend->v.ready(backend, caps); } static void submission_backend_fail_rcpts(struct submission_backend *backend) { struct client *client = backend->client; struct submission_recipient *srcpt; const char *enh_code = backend->fail_enh_code; const char *reason = backend->fail_reason; i_assert(array_count(&client->rcpt_to) > 0); i_assert(reason != NULL); if (enh_code == NULL) enh_code = "4.0.0"; array_foreach_elem(&client->rcpt_to, srcpt) { struct smtp_server_recipient *rcpt = srcpt->rcpt; if (srcpt->backend != backend) continue; if (rcpt->cmd == NULL) continue; smtp_server_recipient_reply(rcpt, 451, enh_code, "%s", reason); } } static inline void submission_backend_reply_failure(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd) { const char *enh_code = backend->fail_enh_code; const char *reason = backend->fail_reason; if (enh_code == NULL) enh_code = "4.0.0"; i_assert(smtp_server_command_get_reply_count(cmd->cmd) == 1); smtp_server_reply(cmd, 451, enh_code, "%s", reason); } static inline bool submission_backend_handle_failure(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd) { if (backend->fail_reason == NULL) return TRUE; /* already failed */ submission_backend_reply_failure(backend, cmd); return TRUE; } void submission_backend_fail(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, const char *enh_code, const char *reason) { struct client *client = backend->client; bool failed_before = (backend->fail_reason != NULL); /* Can be called several times */ if (backend == client->backend_default) { /* default backend: fail the whole client */ client_destroy(&client, enh_code, reason); return; } /* Non-default backend for this transaction (maybe even for only some of the approved recipients): fail only the affected transaction and/or specific recipients. */ /* Remember the failure only once */ if (!failed_before) { backend->fail_enh_code = i_strdup(enh_code); backend->fail_reason = i_strdup(reason); } if (cmd == NULL) { /* Called outside command context: just remember the failure */ } else if (smtp_server_command_get_reply_count(cmd->cmd) > 1) { /* Fail DATA/BDAT/BURL command expecting several replies */ submission_backend_fail_rcpts(backend); } else { /* Single command */ submission_backend_reply_failure(backend, cmd); } /* Call the fail vfunc only once */ if (!failed_before && backend->v.fail != NULL) backend->v.fail(backend, enh_code, reason); backend->started = FALSE; backend->ready = FALSE; } void submission_backends_client_input_pre(struct client *client) { struct submission_backend *backend; for (backend = client->backends; backend != NULL; backend = backend->next) { if (!backend->started) continue; if (backend->v.client_input_pre != NULL) backend->v.client_input_pre(backend); } } void submission_backends_client_input_post(struct client *client) { struct submission_backend *backend; for (backend = client->backends; backend != NULL; backend = backend->next) { if (!backend->started) continue; if (backend->v.client_input_post != NULL) backend->v.client_input_post(backend); } } uoff_t submission_backend_get_max_mail_size(struct submission_backend *backend) { if (backend->v.get_max_mail_size != NULL) return backend->v.get_max_mail_size(backend); return UOFF_T_MAX; } void submission_backend_trans_start(struct submission_backend *backend, struct smtp_server_transaction *trans) { submission_backend_start(backend); if (backend->trans_started) return; backend->trans_started = TRUE; if (backend->v.trans_start != NULL) { backend->v.trans_start(backend, trans, trans->mail_from, &trans->params); } } static void submission_backend_trans_free(struct submission_backend *backend, struct smtp_server_transaction *trans) { i_stream_unref(&backend->data_input); if (backend->v.trans_free != NULL) backend->v.trans_free(backend, trans); backend->trans_started = FALSE; i_free(backend->fail_enh_code); i_free(backend->fail_reason); } void submission_backends_trans_start(struct client *client, struct smtp_server_transaction *trans) { struct submission_backend *backend; i_assert(client->state.backend != NULL); submission_backend_trans_start(client->state.backend, trans); array_foreach_elem(&client->pending_backends, backend) submission_backend_trans_start(backend, trans); array_clear(&client->pending_backends); } void submission_backends_trans_free(struct client *client, struct smtp_server_transaction *trans) { struct submission_backend *backend; i_assert(client->state.backend != NULL || array_count(&client->rcpt_backends) == 0); array_foreach_elem(&client->rcpt_backends, backend) submission_backend_trans_free(backend, trans); array_clear(&client->pending_backends); array_clear(&client->rcpt_backends); client->state.backend = NULL; } int submission_backend_cmd_helo(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_helo *data) { /* failure on default backend closes the client connection */ i_assert(backend->fail_reason == NULL); if (!backend->started || backend->v.cmd_helo == NULL) { /* default backend is not interested, respond right away */ submission_helo_reply_submit(cmd, data); return 1; } return backend->v.cmd_helo(backend, cmd, data); } void submission_backend_helo_reply_submit( struct submission_backend *backend ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_helo *data) { submission_helo_reply_submit(cmd, data); } int submission_backend_cmd_mail(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_mail *data) { if (!submission_backend_handle_failure(backend, cmd)) return -1; submission_backend_start(backend); if (backend->v.cmd_mail == NULL) { /* mail backend is not interested, respond right away */ return 1; } return backend->v.cmd_mail(backend, cmd, data); } static void submission_backend_add_pending(struct submission_backend *backend) { struct client *client = backend->client; struct submission_backend *pending_backend; array_foreach_elem(&client->pending_backends, pending_backend) { if (backend == pending_backend) return; } array_push_back(&client->pending_backends, &backend); } int submission_backend_cmd_rcpt(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, struct submission_recipient *srcpt) { struct smtp_server_transaction *trans; if (!submission_backend_handle_failure(backend, cmd)) return -1; i_assert(backend->started); if (backend->v.cmd_rcpt == NULL) { /* backend is not interested, respond right away */ return 1; } trans = smtp_server_connection_get_transaction(cmd->conn); if (trans != NULL) submission_backend_trans_start(srcpt->backend, trans); else submission_backend_add_pending(srcpt->backend); return backend->v.cmd_rcpt(backend, cmd, srcpt); } int submission_backend_cmd_rset(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd) { if (!submission_backend_handle_failure(backend, cmd)) return -1; submission_backend_start(backend); if (backend->v.cmd_rset == NULL) { /* backend is not interested, respond right away */ return 1; } return backend->v.cmd_rset(backend, cmd); } static int submission_backend_cmd_data(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans) { if (backend->fail_reason != NULL) { submission_backend_fail_rcpts(backend); return 0; } i_assert(backend->started); return backend->v.cmd_data(backend, cmd, trans, backend->data_input, backend->data_size); } int submission_backends_cmd_data(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans, struct istream *data_input, uoff_t data_size) { struct submission_backend *backend; int ret = 0; i_assert(array_count(&client->rcpt_backends) > 0); /* Make sure data input stream is at the beginning (plugins may have messed with it. */ i_stream_seek(data_input, 0); /* create the data_input streams first */ array_foreach_elem(&client->rcpt_backends, backend) { backend->data_input = i_stream_create_sized(data_input, data_size); backend->data_size = data_size; } /* now that all the streams are created, start reading them (reading them earlier could have caused the data_input parent's offset to change) */ array_foreach_elem(&client->rcpt_backends, backend) { ret = submission_backend_cmd_data(backend, cmd, trans); if (ret < 0) break; } return ret; } int submission_backend_cmd_vrfy(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd, const char *param) { /* failure on default backend closes the client connection */ i_assert(backend->fail_reason == NULL); submission_backend_start(backend); if (backend->v.cmd_vrfy == NULL) { /* backend is not interested, respond right away */ return 1; } return backend->v.cmd_vrfy(backend, cmd, param); } int submission_backend_cmd_noop(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd) { /* failure on default backend closes the client connection */ i_assert(backend->fail_reason == NULL); submission_backend_start(backend); if (backend->v.cmd_noop == NULL) { /* backend is not interested, respond right away */ return 1; } return backend->v.cmd_noop(backend, cmd); } int submission_backend_cmd_quit(struct submission_backend *backend, struct smtp_server_cmd_ctx *cmd) { /* failure on default backend closes the client connection */ i_assert(backend->fail_reason == NULL); if (!backend->started) { /* quit before backend even started */ return 1; } if (backend->v.cmd_quit == NULL) { /* backend is not interested, respond right away */ return 1; } return backend->v.cmd_quit(backend, cmd); } dovecot-2.3.21.1/src/submission/submission-backend-relay.h0000644000000000000000000000334314656633576020345 00000000000000#ifndef SUBMISSION_BACKEND_RELAY_H #define SUBMISSION_BACKEND_RELAY_H #include "smtp-client-connection.h" #include "smtp-client-transaction.h" #include "submission-backend.h" struct client; struct submission_backend_relay; struct submision_backend_relay_settings { const char *my_hostname; enum smtp_protocol protocol; const char *path, *host; struct ip_addr ip; /* if empty, resolve host */ in_port_t port; const char *const *extra_capabilities; const char *user, *master_user; const char *password; const struct dsasl_client_mech *sasl_mech; enum smtp_client_connection_ssl_mode ssl_mode; const char *rawlog_dir; unsigned int max_idle_time; unsigned int connect_timeout_msecs; unsigned int command_timeout_msecs; bool ssl_verify:1; bool trusted:1; }; struct submission_backend_relay * submission_backend_relay_create( struct client *client, const struct submision_backend_relay_settings *set); /* Returns the base backend object for this relay backend */ struct submission_backend * submission_backend_relay_get(struct submission_backend_relay *backend) ATTR_PURE; /* Returns the client connection for this relay backend */ struct smtp_client_connection * submission_backend_relay_get_connection( struct submission_backend_relay *backend) ATTR_PURE; /* Returns the current client transaction for this relay backend */ struct smtp_client_transaction * submission_backend_relay_get_transaction( struct submission_backend_relay *backend) ATTR_PURE; /* Initializes the client transaction manually, which allows providing alternative transaction flags. */ struct smtp_client_transaction * submission_backend_relay_init_transaction( struct submission_backend_relay *backend, enum smtp_client_transaction_flags flags); #endif dovecot-2.3.21.1/src/submission/main.c0000644000000000000000000003002714656633576014371 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "submission-common.h" #include "buffer.h" #include "str.h" #include "istream.h" #include "ostream.h" #include "array.h" #include "base64.h" #include "hostpid.h" #include "path-util.h" #include "process-title.h" #include "restrict-access.h" #include "fd-util.h" #include "settings-parser.h" #include "master-service.h" #include "master-login.h" #include "master-service-settings.h" #include "master-interface.h" #include "var-expand.h" #include "mail-error.h" #include "mail-user.h" #include "mail-storage-service.h" #include "smtp-server.h" #include "smtp-client.h" #include "submission-commands.h" #include #include #define DNS_CLIENT_SOCKET_PATH "dns-client" #define LMTP_MASTER_FIRST_LISTEN_FD 3 #define IS_STANDALONE() \ (getenv(MASTER_IS_PARENT_ENV) == NULL) struct smtp_server *smtp_server = NULL; struct smtp_client *smtp_client = NULL; static bool verbose_proctitle = FALSE; static struct mail_storage_service_ctx *storage_service; static struct master_login *master_login = NULL; submission_client_created_func_t *hook_client_created = NULL; bool submission_debug = FALSE; submission_client_created_func_t * submission_client_created_hook_set(submission_client_created_func_t *new_hook) { submission_client_created_func_t *old_hook = hook_client_created; hook_client_created = new_hook; return old_hook; } void submission_refresh_proctitle(void) { struct client *client; string_t *title = t_str_new(128); if (!verbose_proctitle) return; str_append_c(title, '['); switch (submission_client_count) { case 0: str_append(title, "idling"); break; case 1: client = submission_clients; str_append(title, client->user->username); if (client->user->conn.remote_ip != NULL) { str_append_c(title, ' '); str_append(title, net_ip2addr(client->user->conn.remote_ip)); } str_append_c(title, ' '); str_append(title, smtp_server_state_names[client->state.state]); if (client->state.args != NULL && *client->state.args != '\0') { str_append_c(title, ' '); str_append(title, client->state.args); } break; default: str_printfa(title, "%u connections", submission_client_count); break; } str_append_c(title, ']'); process_title_set(str_c(title)); } static void submission_die(void) { /* do nothing. submission connections typically die pretty quick anyway. */ } static void send_error(int fd_out, const char *hostname, const char *error_code, const char *error_msg) { const char *msg; msg = t_strdup_printf("451 %s %s\r\n" "421 4.3.2 %s Shutting down due to fatal error\r\n", error_code, error_msg, hostname); if (write(fd_out, msg, strlen(msg)) < 0) { if (errno != EAGAIN && errno != EPIPE && errno != ECONNRESET) i_error("write(client) failed: %m"); } } static bool extract_input_data_field(const unsigned char **data, size_t *data_len, const char **value_r) { size_t value_len = 0; if (*data_len == 0) return FALSE; if (**data == '\0') { value_len = 1; } else { *value_r = t_strndup(*data, *data_len); value_len = strlen(*value_r) + 1; } if (value_len > *data_len) { *data = &uchar_nul; *data_len = 0; } else { *data = *data + value_len; *data_len = *data_len - value_len; } return TRUE; } static int client_create_from_input(const struct mail_storage_service_input *input, enum mail_auth_request_flags login_flags, int fd_in, int fd_out, const buffer_t *input_buf, const char **error_r) { struct mail_storage_service_user *user; struct mail_user *mail_user; struct submission_settings *set; bool no_greeting = HAS_ALL_BITS(login_flags, MAIL_AUTH_REQUEST_FLAG_IMPLICIT); const char *errstr; const char *helo = NULL; struct smtp_proxy_data proxy_data; const unsigned char *data; size_t data_len; if (mail_storage_service_lookup_next(storage_service, input, &user, &mail_user, error_r) <= 0) { send_error(fd_out, my_hostname, "4.7.0", MAIL_ERRSTR_CRITICAL_MSG); return -1; } restrict_access_allow_coredumps(TRUE); set = mail_storage_service_user_get_set(user)[1]; if (set->verbose_proctitle) verbose_proctitle = TRUE; if (settings_var_expand(&submission_setting_parser_info, set, mail_user->pool, mail_user_var_expand_table(mail_user), &errstr) <= 0) { *error_r = t_strdup_printf("Failed to expand settings: %s", errstr); send_error(fd_out, set->hostname, "4.3.5", MAIL_ERRSTR_CRITICAL_MSG); mail_user_deinit(&mail_user); mail_storage_service_user_unref(&user); return -1; } if (set->submission_relay_host == NULL || *set->submission_relay_host == '\0') { *error_r = "No relay host configured for submission proxy " "(submission_relay_host is unset)"; send_error(fd_out, set->hostname, "4.3.5", MAIL_ERRSTR_CRITICAL_MSG); mail_user_deinit(&mail_user); mail_storage_service_user_unref(&user); return -1; } /* parse input data */ data = NULL; data_len = 0; i_zero(&proxy_data); if (input_buf != NULL && input_buf->used > 0) { data = input_buf->data; data_len = input_buf->used; if (extract_input_data_field(&data, &data_len, &helo) && extract_input_data_field(&data, &data_len, &proxy_data.helo)) { /* nothing to do */ } /* NOTE: actually, pipelining the AUTH command is stricly speaking not allowed, but we support it anyway. */ } (void)client_create(fd_in, fd_out, mail_user, user, set, helo, &proxy_data, data, data_len, no_greeting); return 0; } static void main_stdio_run(const char *username) { struct mail_storage_service_input input; buffer_t *input_buf; const char *value, *error, *input_base64; i_zero(&input); input.module = input.service = "submission"; input.username = username != NULL ? username : getenv("USER"); if (input.username == NULL && IS_STANDALONE()) input.username = getlogin(); if (input.username == NULL) i_fatal("USER environment missing"); if ((value = getenv("IP")) != NULL) (void)net_addr2ip(value, &input.remote_ip); if ((value = getenv("LOCAL_IP")) != NULL) (void)net_addr2ip(value, &input.local_ip); input_base64 = getenv("CLIENT_INPUT"); input_buf = input_base64 == NULL ? NULL : t_base64_decode_str(input_base64); if (client_create_from_input(&input, 0, STDIN_FILENO, STDOUT_FILENO, input_buf, &error) < 0) i_fatal("%s", error); } static void login_client_connected(const struct master_login_client *login_client, const char *username, const char *const *extra_fields) { struct mail_storage_service_input input; enum mail_auth_request_flags flags = login_client->auth_req.flags; const char *error; buffer_t input_buf; i_zero(&input); input.module = input.service = "submission"; input.local_ip = login_client->auth_req.local_ip; input.remote_ip = login_client->auth_req.remote_ip; input.local_port = login_client->auth_req.local_port; input.remote_port = login_client->auth_req.remote_port; input.username = username; input.userdb_fields = extra_fields; input.session_id = login_client->session_id; if ((flags & MAIL_AUTH_REQUEST_FLAG_CONN_SECURED) != 0) input.conn_secured = TRUE; if ((flags & MAIL_AUTH_REQUEST_FLAG_CONN_SSL_SECURED) != 0) input.conn_ssl_secured = TRUE; buffer_create_from_const_data(&input_buf, login_client->data, login_client->auth_req.data_size); if (client_create_from_input(&input, flags, login_client->fd, login_client->fd, &input_buf, &error) < 0) { int fd = login_client->fd; i_error("%s", error); i_close_fd(&fd); master_service_client_connection_destroyed(master_service); } } static void login_client_failed(const struct master_login_client *client, const char *errormsg) { const char *msg; msg = t_strdup_printf("451 4.7.0 %s\r\n" "421 4.3.2 %s Shutting down due to fatal error\r\n", errormsg, my_hostname); if (write(client->fd, msg, strlen(msg)) < 0) { /* ignored */ } } static void client_connected(struct master_service_connection *conn) { /* when running standalone, we shouldn't even get here */ i_assert(master_login != NULL); master_service_client_connection_accept(conn); master_login_add(master_login, conn->fd); } int main(int argc, char *argv[]) { static const struct setting_parser_info *set_roots[] = { &submission_setting_parser_info, NULL }; struct master_login_settings login_set; enum master_service_flags service_flags = 0; enum mail_storage_service_flags storage_service_flags = 0; struct smtp_server_settings smtp_server_set; struct smtp_client_settings smtp_client_set; const char *username = NULL, *auth_socket_path = "auth-master"; const char *tmp_socket_path; const char *error; int c; i_zero(&login_set); login_set.postlogin_timeout_secs = MASTER_POSTLOGIN_TIMEOUT_DEFAULT; login_set.request_auth_token = TRUE; if (IS_STANDALONE() && getuid() == 0 && net_getpeername(1, NULL, NULL) == 0) { printf("421 5.3.5 The submission binary must not be started " "from inetd, use submission-login instead.\r\n"); return 1; } if (IS_STANDALONE()) { service_flags |= MASTER_SERVICE_FLAG_STANDALONE | MASTER_SERVICE_FLAG_STD_CLIENT; } else { service_flags |= MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN; } master_service = master_service_init("submission", service_flags, &argc, &argv, "a:Dt:u:"); while ((c = master_getopt(master_service)) > 0) { switch (c) { case 'a': auth_socket_path = optarg; break; case 't': if (str_to_uint(optarg, &login_set.postlogin_timeout_secs) < 0 || login_set.postlogin_timeout_secs == 0) i_fatal("Invalid -t parameter: %s", optarg); break; case 'u': storage_service_flags |= MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP; username = optarg; break; case 'D': submission_debug = TRUE; break; default: return FATAL_DEFAULT; } } if (t_abspath(auth_socket_path, &login_set.auth_socket_path, &error) < 0) { i_fatal("t_abspath(%s) failed: %s", auth_socket_path, error); } if (argv[optind] != NULL) { if (t_abspath(argv[optind], &login_set.postlogin_socket_path, &error) < 0) { i_fatal("t_abspath(%s) failed: %s", argv[optind], error); } } login_set.callback = login_client_connected; login_set.failure_callback = login_client_failed; login_set.update_proctitle = getenv(MASTER_VERBOSE_PROCTITLE_ENV) != NULL && master_service_get_client_limit(master_service) == 1; master_service_set_die_callback(master_service, submission_die); storage_service = mail_storage_service_init(master_service, set_roots, storage_service_flags); /* initialize SMTP server */ i_zero(&smtp_server_set); smtp_server_set.capabilities = SMTP_CAPABILITY_DSN; smtp_server_set.protocol = SMTP_PROTOCOL_SMTP; smtp_server_set.max_pipelined_commands = 5; smtp_server_set.debug = submission_debug; smtp_server_set.reason_code_module = "submission"; smtp_server = smtp_server_init(&smtp_server_set); smtp_server_command_register(smtp_server, "BURL", cmd_burl, 0); if (t_abspath(DNS_CLIENT_SOCKET_PATH, &tmp_socket_path, &error) < 0) i_fatal("t_abspath(%s) failed: %s", DNS_CLIENT_SOCKET_PATH, error); /* initialize SMTP client */ i_zero(&smtp_client_set); smtp_client_set.my_hostname = my_hostdomain(); smtp_client_set.debug = submission_debug; smtp_client_set.dns_client_socket_path = tmp_socket_path; smtp_client = smtp_client_init(&smtp_client_set); if (!IS_STANDALONE()) master_login = master_login_init(master_service, &login_set); master_service_init_finish(master_service); /* NOTE: login_set.*_socket_path are now invalid due to data stack having been freed */ /* fake that we're running, so we know if client was destroyed while handling its initial input */ io_loop_set_running(current_ioloop); if (IS_STANDALONE()) { T_BEGIN { main_stdio_run(username); } T_END; } else { io_loop_set_running(current_ioloop); } if (io_loop_is_running(current_ioloop)) master_service_run(master_service, client_connected); clients_destroy_all(); smtp_client_deinit(&smtp_client); smtp_server_deinit(&smtp_server); if (master_login != NULL) master_login_deinit(&master_login); mail_storage_service_deinit(&storage_service); master_service_deinit(&master_service); return 0; } dovecot-2.3.21.1/src/submission/Makefile.am0000644000000000000000000000246314656633576015340 00000000000000pkglibexecdir = $(libexecdir)/dovecot pkglibexec_PROGRAMS = submission AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-ssl-iostream \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-imap-storage \ -I$(top_srcdir)/src/lib-imap-urlauth \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-storage \ -I$(top_srcdir)/src/lib-storage/index \ -I$(top_srcdir)/src/lib-storage/index/raw \ -I$(top_srcdir)/src/lib-smtp urlauth_libs = \ $(top_builddir)/src/lib-imap-urlauth/libimap-urlauth.la submission_LDFLAGS = -export-dynamic submission_LDADD = \ $(urlauth_libs) \ $(LIBDOVECOT_STORAGE) \ $(LIBDOVECOT) \ $(MODULE_LIBS) submission_DEPENDENCIES = \ $(urlauth_libs) \ $(LIBDOVECOT_STORAGE_DEPS) \ $(LIBDOVECOT_DEPS) submission_SOURCES = \ main.c \ submission-backend.c \ submission-backend-relay.c \ submission-recipient.c \ submission-client.c \ submission-commands.c \ submission-settings.c headers = \ submission-common.h \ submission-backend.h \ submission-backend-relay.h \ submission-commands.h \ submission-recipient.h \ submission-client.h \ submission-settings.h pkginc_libdir=$(pkgincludedir) pkginc_lib_HEADERS = $(headers) dovecot-2.3.21.1/src/submission/submission-recipient.c0000644000000000000000000000267314656633576017626 00000000000000/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ #include "submission-common.h" #include "submission-backend.h" #include "submission-recipient.h" struct submission_recipient_module_register submission_recipient_module_register = { 0 }; static void submission_recipient_approved(struct smtp_server_recipient *rcpt ATTR_UNUSED, struct submission_recipient *srcpt); struct submission_recipient * submission_recipient_create(struct client *client, struct smtp_server_recipient *rcpt) { struct submission_recipient *srcpt; srcpt = p_new(rcpt->pool, struct submission_recipient, 1); srcpt->rcpt = rcpt; srcpt->backend = client->state.backend; rcpt->context = srcpt; p_array_init(&srcpt->module_contexts, rcpt->pool, 5); smtp_server_recipient_add_hook( rcpt, SMTP_SERVER_RECIPIENT_HOOK_APPROVED, submission_recipient_approved, srcpt); return srcpt; } static void submission_recipient_approved(struct smtp_server_recipient *rcpt ATTR_UNUSED, struct submission_recipient *srcpt) { struct submission_backend *backend = srcpt->backend; struct client *client = backend->client; struct submission_backend *rcpt_backend; bool backend_found = FALSE; array_push_back(&client->rcpt_to, &srcpt); array_foreach_elem(&client->rcpt_backends, rcpt_backend) { if (rcpt_backend == backend) { backend_found = TRUE; break; } } if (!backend_found) array_push_back(&client->rcpt_backends, &backend); } dovecot-2.3.21.1/src/stats/0000755000000000000000000000000014656633641012313 500000000000000dovecot-2.3.21.1/src/stats/client-reader.c0000644000000000000000000001567214656633576015137 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "stats-common.h" #include "array.h" #include "str.h" #include "stats-dist.h" #include "strescape.h" #include "connection.h" #include "ostream.h" #include "master-service.h" #include "stats-metrics.h" #include "stats-settings.h" #include "client-reader.h" #include "client-writer.h" struct reader_client { struct connection conn; }; static struct connection_list *reader_clients = NULL; void client_reader_create(int fd) { struct reader_client *client; client = i_new(struct reader_client, 1); connection_init_server(reader_clients, &client->conn, "stats-reader", fd, fd); } static void reader_client_destroy(struct connection *conn) { connection_deinit(conn); i_free(conn); master_service_client_connection_destroyed(master_service); } static void reader_client_dump_stats(string_t *str, struct stats_dist *stats, const char *const *fields) { for (unsigned int i = 0; fields[i] != NULL; i++) { const char *field = fields[i]; str_append_c(str, '\t'); if (strcmp(field, "count") == 0) str_printfa(str, "%u", stats_dist_get_count(stats)); else if (strcmp(field, "sum") == 0) str_printfa(str, "%"PRIu64, stats_dist_get_sum(stats)); else if (strcmp(field, "min") == 0) str_printfa(str, "%"PRIu64, stats_dist_get_min(stats)); else if (strcmp(field, "max") == 0) str_printfa(str, "%"PRIu64, stats_dist_get_max(stats)); else if (strcmp(field, "avg") == 0) str_printfa(str, "%.02f", stats_dist_get_avg(stats)); else if (strcmp(field, "median") == 0) str_printfa(str, "%"PRIu64, stats_dist_get_median(stats)); else if (strcmp(field, "variance") == 0) str_printfa(str, "%.02f", stats_dist_get_variance(stats)); else if (field[0] == '%') { str_printfa(str, "%"PRIu64, stats_dist_get_percentile(stats, strtod(field+1, NULL)/100.0)); } else { /* return unknown fields as empty */ } } } static void reader_client_dump_metric(string_t *str, const struct metric *metric, const char *const *fields) { reader_client_dump_stats(str, metric->duration_stats, fields); for (unsigned int i = 0; i < metric->fields_count; i++) { str_append_c(str, '\t'); str_append_tabescaped(str, metric->fields[i].field_key); reader_client_dump_stats(str, metric->fields[i].stats, fields); } str_append_c(str, '\n'); } static void reader_client_append_sub_name(string_t *str, const char *sub_name) { for (; *sub_name != '\0'; sub_name++) { switch (*sub_name) { case '\t': case '\n': case '\r': case ' ': str_append_c(str, '_'); break; default: str_append_c(str, *sub_name); } } } static void reader_client_dump_sub_metrics(struct ostream *output, const struct metric *metric, const char *sub_name, const char *const *fields) { size_t root_pos, name_pos; struct metric *const *sub_metrics; if (!array_is_created(&metric->sub_metrics)) return; string_t *str = t_str_new(128); reader_client_append_sub_name(str, sub_name); str_append_c(str, '_'); root_pos = str->used; array_foreach(&metric->sub_metrics, sub_metrics) { str_truncate(str, root_pos); reader_client_append_sub_name(str, (*sub_metrics)->sub_name); name_pos = str->used; reader_client_dump_metric(str, *sub_metrics, fields); o_stream_nsend(output, str_data(str), str_len(str)); str_truncate(str, name_pos); reader_client_dump_sub_metrics(output, *sub_metrics, str_c(str), fields); } } static int reader_client_input_dump(struct reader_client *client, const char *const *args) { struct stats_metrics_iter *iter; const struct metric *metric; o_stream_cork(client->conn.output); iter = stats_metrics_iterate_init(stats_metrics); while ((metric = stats_metrics_iterate(iter)) != NULL) T_BEGIN { string_t *str = t_str_new(128); str_append_tabescaped(str, metric->name); reader_client_dump_metric(str, metric, args); o_stream_nsend(client->conn.output, str_data(str), str_len(str)); reader_client_dump_sub_metrics(client->conn.output, metric, metric->name, args); } T_END; o_stream_nsend(client->conn.output, "\n", 1); stats_metrics_iterate_deinit(&iter); o_stream_uncork(client->conn.output); return 1; } static int reader_client_input_dump_reset(struct reader_client *client, const char *const *args) { (void)reader_client_input_dump(client, args); stats_metrics_reset(stats_metrics); return 1; } static int reader_client_input_metrics_add(struct reader_client *client, const char *const *args) { const char *error; if (str_array_length(args) < 7) { e_error(client->conn.event, "METRICS-ADD: Not enough parameters"); return -1; } struct stats_metric_settings set = { .metric_name = args[0], .description = args[1], .fields = args[2], .group_by = args[3], .filter = args[4], .exporter = args[5], .exporter_include = args[6], }; o_stream_cork(client->conn.output); if (stats_metrics_add_dynamic(stats_metrics, &set, &error)) { client_writer_update_connections(); o_stream_nsend(client->conn.output, "+", 1); } else { o_stream_nsend(client->conn.output, "-", 1); o_stream_nsend_str(client->conn.output, "METRICS-ADD: "); o_stream_nsend_str(client->conn.output, error); } o_stream_nsend(client->conn.output, "\n", 1); o_stream_uncork(client->conn.output); return 1; } static int reader_client_input_metrics_remove(struct reader_client *client, const char *const *args) { if (str_array_length(args) < 1) { e_error(client->conn.event, "METRICS-REMOVE: Not enough parameters"); return -1; } if (stats_metrics_remove_dynamic(stats_metrics, args[0])) { client_writer_update_connections(); o_stream_nsend(client->conn.output, "+\n", 2); } else { o_stream_nsend_str(client->conn.output, t_strdup_printf("-metrics '%s' not found\n", args[0])); } return 1; } static int reader_client_input_args(struct connection *conn, const char *const *args) { struct reader_client *client = (struct reader_client *)conn; const char *cmd = args[0]; if (cmd == NULL) { i_error("Client sent empty line"); return 1; } args++; if (strcmp(cmd, "DUMP") == 0) return reader_client_input_dump(client, args); else if (strcmp(cmd, "METRICS-ADD") == 0) return reader_client_input_metrics_add(client, args); else if (strcmp(cmd, "METRICS-REMOVE") == 0) return reader_client_input_metrics_remove(client, args); else if (strcmp(cmd, "DUMP-RESET") == 0) return reader_client_input_dump_reset(client, args); return 1; } static struct connection_settings client_set = { .service_name_in = "stats-reader-client", .service_name_out = "stats-reader-server", .major_version = 2, .minor_version = 0, .input_max_size = 1024, .output_max_size = SIZE_MAX, .client = FALSE, }; static const struct connection_vfuncs client_vfuncs = { .destroy = reader_client_destroy, .input_args = reader_client_input_args, }; void client_readers_init(void) { reader_clients = connection_list_init(&client_set, &client_vfuncs); } void client_readers_deinit(void) { connection_list_deinit(&reader_clients); } dovecot-2.3.21.1/src/stats/client-reader.h0000644000000000000000000000026614656633576015135 00000000000000#ifndef CLIENT_READER_H #define CLIENT_READER_H struct stats_metrics; void client_reader_create(int fd); void client_readers_init(void); void client_readers_deinit(void); #endif dovecot-2.3.21.1/src/stats/test-stats-metrics.c0000644000000000000000000002772414656633576016201 00000000000000/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ #include "test-stats-common.h" #include "array.h" bool test_stats_callback(struct event *event, enum event_callback_type type ATTR_UNUSED, struct failure_context *ctx, const char *fmt ATTR_UNUSED, va_list args ATTR_UNUSED) { if (stats_metrics != NULL) { stats_metrics_event(stats_metrics, event, ctx); struct event_filter *filter = stats_metrics_get_event_filter(stats_metrics); return !event_filter_match(filter, event, ctx); } return TRUE; } static const char *settings_blob_1 = "metric=test\n" "metric/test/metric_name=test\n" "metric/test/filter=event=test\n" "\n"; static void test_stats_metrics(void) { test_begin("stats metrics (event counting)"); /* register some stats */ test_init(settings_blob_1); /* push event in */ struct event *event = event_create(NULL); event_add_category(event, &test_category); event_set_name(event, "test"); test_event_send(event); event_unref(&event); test_assert(get_stats_dist_field("test", STATS_DIST_COUNT) == 1); test_assert(get_stats_dist_field("test", STATS_DIST_SUM) > 0); test_deinit(); test_end(); } static const char *settings_blob_2 = "metric=test\n" "metric/test/metric_name=test\n" "metric/test/filter=(event=test AND test_field=value)\n" "\n"; static void test_stats_metrics_filter(void) { test_begin("stats metrics (filter)"); test_init(settings_blob_2); /* check filter */ struct event_filter *filter = stats_metrics_get_event_filter(stats_metrics); string_t *str_filter = t_str_new(64); event_filter_export(filter, str_filter); test_assert_strcmp("((event=\"test\" AND \"test_field\"=\"value\"))", str_c(str_filter)); /* send event */ struct event *event = event_create(NULL); event_add_category(event, &test_category); event_set_name(event, "test"); event_add_str(event, "test_field", "value"); test_event_send(event); event_unref(&event); test_assert(get_stats_dist_field("test", STATS_DIST_COUNT) == 1); test_assert(get_stats_dist_field("test", STATS_DIST_SUM) > 0); /* send another event */ event = event_create(NULL); event_add_category(event, &test_category); event_set_name(event, "test"); event_add_str(event, "test_field", "nother value"); e_debug(event, "test"); event_unref(&event); test_assert(get_stats_dist_field("test", STATS_DIST_COUNT) == 1); test_assert(get_stats_dist_field("test", STATS_DIST_SUM) > 0); test_deinit(); test_end(); } static void test_stats_metrics_group_by_check_one(const struct metric *metric, const char *sub_name, unsigned int total_count, unsigned int submetric_count, unsigned int group_by_count, enum stats_metric_group_by_func group_by_func, const char *group_by_field, enum metric_value_type value_type) { test_assert_strcmp(metric->name, "test"); if (sub_name != NULL) test_assert_strcmp(metric->sub_name, sub_name); else test_assert(metric->sub_name == NULL); test_assert(stats_dist_get_count(metric->duration_stats) == total_count); if (submetric_count > 0) { test_assert(array_is_created(&metric->sub_metrics)); test_assert(array_count(&metric->sub_metrics) == submetric_count); } else { test_assert(!array_is_created(&metric->sub_metrics)); } if (group_by_count > 0) { test_assert(metric->group_by_count == group_by_count); i_assert(metric->group_by != NULL); test_assert(metric->group_by[0].func == group_by_func); test_assert_strcmp(metric->group_by[0].field, group_by_field); } else { test_assert(metric->group_by_count == 0); test_assert(metric->group_by == NULL); } test_assert(metric->group_value.type == value_type); } #define DISCRETE_TEST_VAL_COUNT 3 struct discrete_test { const char *settings_blob; unsigned int num_values; const char *values_first[DISCRETE_TEST_VAL_COUNT]; const char *values_second[DISCRETE_TEST_VAL_COUNT]; }; static const struct discrete_test discrete_tests[] = { { "test_name sub_name", 3, { "eta", "kappa", "nu", }, { "upsilon", "pi", "epsilon", }, }, { "test_name:discrete sub_name:discrete", 3, { "apple", "bannana", "orange", }, { "pie", "yoghurt", "cobbler", }, }, { "test_name sub_name:discrete", 3, { "apollo", "gaia", "hermes", }, { "thor", "odin", "loki", }, }, }; static void test_stats_metrics_group_by_discrete_real(const struct discrete_test *test) { struct event *event; unsigned int i, j; test_begin(t_strdup_printf("stats metrics (discrete group by) - %s", test->settings_blob)); test_init(t_strdup_printf("metric=test\n" "metric/test/metric_name=test\n" "metric/test/filter=event=test\n" "metric/test/group_by=%s\n" "\n", test->settings_blob)); for (i = 0; i < test->num_values; i++) { for (j = 0; j < test->num_values; j++) { event = event_create(NULL); event_add_category(event, &test_category); event_set_name(event, "test"); event_add_str(event, "test_name", test->values_first[i]); event_add_str(event, "sub_name", test->values_second[j]); test_event_send(event); event_unref(&event); } } /* check total number of events */ test_assert(get_stats_dist_field("test", STATS_DIST_COUNT) == test->num_values * test->num_values); /* analyze the structure */ struct stats_metrics_iter *iter = stats_metrics_iterate_init(stats_metrics); const struct metric *root_metric = stats_metrics_iterate(iter); stats_metrics_iterate_deinit(&iter); test_stats_metrics_group_by_check_one(root_metric, NULL, test->num_values * test->num_values, test->num_values, 2, STATS_METRIC_GROUPBY_DISCRETE, "test_name", 0); struct metric *const *first = array_idx(&root_metric->sub_metrics, 0); /* examime each sub-metric */ for (i = 0; i < test->num_values; i++) { test_stats_metrics_group_by_check_one(first[i], test->values_first[i], test->num_values, test->num_values, 1, STATS_METRIC_GROUPBY_DISCRETE, "sub_name", METRIC_VALUE_TYPE_STR); struct metric *const *second = array_idx(&first[i]->sub_metrics, 0); /* examine each sub-sub-metric */ for (j = 0; j < test->num_values; j++) { test_stats_metrics_group_by_check_one(second[j], test->values_second[j], 1, 0, 0, 0, NULL, METRIC_VALUE_TYPE_STR); } } test_deinit(); test_end(); } static void test_stats_metrics_group_by_discrete(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(discrete_tests); i++) test_stats_metrics_group_by_discrete_real(&discrete_tests[i]); } #define QUANTIZED_TEST_VAL_COUNT 15 struct quantized_test { const char *settings_blob; unsigned int num_inputs; intmax_t input_vals[QUANTIZED_TEST_VAL_COUNT]; unsigned int num_sub_metrics; unsigned int num_ranges; struct { struct stats_metric_settings_bucket_range range; intmax_t count; } ranges[QUANTIZED_TEST_VAL_COUNT]; }; static const struct quantized_test quantized_tests[] = { { "linear:100:1000:100", 13, { 0, 50, 100, 101, 200, 201, 250, 301, 900, 901, 1000, 1001, 2000 }, 7, 11, { { { INTMAX_MIN, 100 }, 3 }, { { 100, 200 }, 2 }, { { 200, 300 }, 2 }, { { 300, 400 }, 1 }, { { 400, 500 }, 0 }, { { 500, 600 }, 0 }, { { 600, 700 }, 0 }, { { 700, 800 }, 0 }, { { 800, 900 }, 1 }, { { 900, 1000 }, 2 }, { { 1000, INTMAX_MAX }, 2 }, } }, { /* start at 0 */ "exponential:0:6:10", 12, { 0, 5, 10, 11, 100, 101, 500, 1000, 1001, 1000000, 1000001, 2000000 }, 7, 8, { { { INTMAX_MIN, 1 }, 1 }, { { 1, 10 }, 2 }, { { 10, 100 }, 2 }, { { 100, 1000 }, 3 }, { { 1000, 10000 }, 1 }, { { 10000, 100000 }, 0 }, { { 100000, 1000000 }, 1 }, { { 1000000, INTMAX_MAX }, 2 }, } }, { /* start at 0 */ "exponential:0:6:2", 9, { 0, 1, 2, 4, 5, 20, 64, 65, 100 }, 7, 8, { { { INTMAX_MIN, 1 }, 2 }, { { 1, 2 }, 1 }, { { 2, 4 }, 1 }, { { 4, 8 }, 1 }, { { 8, 16 }, 0 }, { { 16, 32 }, 1 }, { { 32, 64 }, 1 }, { { 64, INTMAX_MAX }, 2 }, } }, { /* start at >0 */ "exponential:2:6:10", 12, { 0, 5, 10, 11, 100, 101, 500, 1000, 1001, 1000000, 1000001, 2000000 }, 5, 6, { { { INTMAX_MIN, 100 }, 5 }, { { 100, 1000 }, 3 }, { { 1000, 10000 }, 1 }, { { 10000, 100000 }, 0 }, { { 100000, 1000000 }, 1 }, { { 1000000, INTMAX_MAX }, 2 }, } }, { /* start at >0 */ "exponential:2:6:2", 9, { 0, 1, 2, 4, 5, 20, 64, 65, 100 }, 5, 6, { { { INTMAX_MIN, 4 }, 4 }, { { 4, 8 }, 1 }, { { 8, 16 }, 0 }, { { 16, 32 }, 1 }, { { 32, 64 }, 1 }, { { 64, INTMAX_MAX }, 2 }, } }, }; static void test_stats_metrics_group_by_quantized_real(const struct quantized_test *test) { unsigned int i; test_begin(t_strdup_printf("stats metrics (quantized group by) - %s", test->settings_blob)); test_init(t_strdup_printf("metric=test\n" "metric/test/metric_name=test\n" "metric/test/filter=event=test\n" "metric/test/group_by=test_name foobar:%s\n" "\n", test->settings_blob)); struct event *event; for (i = 0; i < test->num_inputs; i++) { event = event_create(NULL); event_add_category(event, &test_category); event_set_name(event, "test"); event_add_str(event, "test_name", "alpha"); event_add_int(event, "foobar", test->input_vals[i]); test_event_send(event); event_unref(&event); } /* check total number of events */ test_assert(get_stats_dist_field("test", STATS_DIST_COUNT) == test->num_inputs); /* analyze the structure */ struct stats_metrics_iter *iter = stats_metrics_iterate_init(stats_metrics); const struct metric *root_metric = stats_metrics_iterate(iter); stats_metrics_iterate_deinit(&iter); test_stats_metrics_group_by_check_one(root_metric, NULL, test->num_inputs, 1, 2, STATS_METRIC_GROUPBY_DISCRETE, "test_name", 0); /* examine first level sub-metric */ struct metric *const *first = array_idx(&root_metric->sub_metrics, 0); test_stats_metrics_group_by_check_one(first[0], "alpha", test->num_inputs, test->num_sub_metrics, 1, STATS_METRIC_GROUPBY_QUANTIZED, "foobar", METRIC_VALUE_TYPE_STR); /* check the ranges */ test_assert(first[0]->group_by[0].num_ranges == test->num_ranges); for (i = 0; i < test->num_ranges; i++) { test_assert(first[0]->group_by[0].ranges[i].min == test->ranges[i].range.min); test_assert(first[0]->group_by[0].ranges[i].max == test->ranges[i].range.max); } /* examine second level sub-metrics */ struct metric *const *second = array_idx(&first[0]->sub_metrics, 0); for (i = 0; i < test->num_sub_metrics; i++) { const char *sub_name; intmax_t range_idx; /* we check these first, before we use the value below */ test_assert(second[i]->group_value.type == METRIC_VALUE_TYPE_BUCKET_INDEX); test_assert(second[i]->group_value.intmax < test->num_ranges); range_idx = second[i]->group_value.intmax; /* construct the expected sub-metric name */ if (test->ranges[range_idx].range.min == INTMAX_MIN) { sub_name = t_strdup_printf("foobar_ninf_%jd", test->ranges[range_idx].range.max); } else if (test->ranges[range_idx].range.max == INTMAX_MAX) { sub_name = t_strdup_printf("foobar_%jd_inf", test->ranges[range_idx].range.min + 1); } else { sub_name = t_strdup_printf("foobar_%jd_%jd", test->ranges[range_idx].range.min + 1, test->ranges[range_idx].range.max); } test_stats_metrics_group_by_check_one(second[i], sub_name, test->ranges[second[i]->group_value.intmax].count, 0, 0, 0, NULL, METRIC_VALUE_TYPE_BUCKET_INDEX); } test_deinit(); test_end(); } static void test_stats_metrics_group_by_quantized(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(quantized_tests); i++) test_stats_metrics_group_by_quantized_real(&quantized_tests[i]); } int main(void) { void (*const test_functions[])(void) = { test_stats_metrics, test_stats_metrics_filter, test_stats_metrics_group_by_discrete, test_stats_metrics_group_by_quantized, NULL }; int ret = test_run(test_functions); return ret; } dovecot-2.3.21.1/src/stats/test-stats-common.h0000644000000000000000000000161214656633576016014 00000000000000#ifndef TEST_STATS_COMMON #define TEST_STATS_COMMON 1 #include "stats-common.h" #include "event-filter.h" #include "istream.h" #include "settings-parser.h" #include "str.h" #include "test-common.h" #include "lib-event-private.h" #include "stats-dist.h" #include "stats-event-category.h" #include "stats-metrics.h" extern struct event_category test_category; extern struct event_category child_test_category; extern pool_t test_pool; bool test_stats_callback(struct event *event, enum event_callback_type type ATTR_UNUSED, struct failure_context *ctx, const char *fmt ATTR_UNUSED, va_list args ATTR_UNUSED); void test_init(const char *settings_blob); void test_deinit(void); void test_event_send(struct event *event); enum stats_dist_field { STATS_DIST_COUNT, STATS_DIST_SUM, }; uint64_t get_stats_dist_field(const char *metric_name, enum stats_dist_field field); #endif dovecot-2.3.21.1/src/stats/stats-metrics.h0000644000000000000000000000661314656633576015223 00000000000000#ifndef STATS_METRICS_H #define STATS_METRICS_H #include "stats-settings.h" #include "sha1.h" #define STATS_EVENT_FIELD_NAME_DURATION "duration" struct metric; struct stats_metrics; struct exporter { const char *name; /* * serialization format options * * the "how do we encode the event before sending it" knobs */ enum event_exporter_time_fmt time_format; /* Max length for string field values */ size_t format_max_field_len; /* function to serialize the event */ void (*format)(const struct metric *, struct event *, buffer_t *); /* mime type for the format */ const char *format_mime_type; /* * transport options * * the "how do we get the event to the external location" knobs */ const char *transport_args; unsigned int transport_timeout; /* function to send the event */ void (*transport)(const struct exporter *, const buffer_t *); }; struct metric_export_info { const struct exporter *exporter; enum event_exporter_includes { EVENT_EXPORTER_INCL_NONE = 0, EVENT_EXPORTER_INCL_NAME = 0x01, EVENT_EXPORTER_INCL_HOSTNAME = 0x02, EVENT_EXPORTER_INCL_TIMESTAMPS = 0x04, EVENT_EXPORTER_INCL_CATEGORIES = 0x08, EVENT_EXPORTER_INCL_FIELDS = 0x10, } include; }; struct metric_field { const char *field_key; struct stats_dist *stats; }; enum metric_value_type { METRIC_VALUE_TYPE_STR, METRIC_VALUE_TYPE_INT, METRIC_VALUE_TYPE_BUCKET_INDEX, }; struct metric_value { enum metric_value_type type; unsigned char hash[SHA1_RESULTLEN]; intmax_t intmax; }; struct metric { const struct stats_metric_settings *set; const char *name; /* When this metric is a sub-metric, then this is the suffix for name and any sub_names before it. So if we have struct metric imap_command { event_name = imap_command_finished group_by = cmd_name } The metric.name will always be imap_command and for each sub-metric metric.sub_name will be whatever the cmd_name is, such as 'select'. This is a display name and does not guarantee uniqueness. */ const char *sub_name; /* Timing for how long the event existed */ struct stats_dist *duration_stats; unsigned int fields_count; struct metric_field *fields; unsigned int group_by_count; const struct stats_metric_settings_group_by *group_by; struct metric_value group_value; ARRAY(struct metric *) sub_metrics; struct metric_export_info export_info; }; bool stats_metrics_add_dynamic(struct stats_metrics *metrics, struct stats_metric_settings *set, const char **error_r); bool stats_metrics_remove_dynamic(struct stats_metrics *metrics, const char *name); struct stats_metrics *stats_metrics_init(const struct stats_settings *set); void stats_metrics_deinit(struct stats_metrics **metrics); /* Reset all metrics */ void stats_metrics_reset(struct stats_metrics *metrics); /* Returns event filter created from the stats_settings. */ struct event_filter * stats_metrics_get_event_filter(struct stats_metrics *metrics); /* Update metrics with given event. */ void stats_metrics_event(struct stats_metrics *metrics, struct event *event, const struct failure_context *ctx); /* Iterate through all the tracked metrics. */ struct stats_metrics_iter * stats_metrics_iterate_init(struct stats_metrics *metrics); const struct metric *stats_metrics_iterate(struct stats_metrics_iter *iter); void stats_metrics_iterate_deinit(struct stats_metrics_iter **iter); #endif dovecot-2.3.21.1/src/stats/event-exporter-fmt.c0000644000000000000000000000372414656633576016167 00000000000000/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "hash.h" #include "ioloop.h" #include "event-exporter.h" void event_export_helper_fmt_unix_time(string_t *dest, const struct timeval *time) { str_printfa(dest, "%"PRIdTIME_T".%06u", time->tv_sec, (unsigned int) time->tv_usec); } void event_export_helper_fmt_rfc3339_time(string_t *dest, const struct timeval *time) { const struct tm *tm; tm = gmtime(&time->tv_sec); str_printfa(dest, "%04d-%02d-%02dT%02d:%02d:%02d.%06luZ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, time->tv_usec); } HASH_TABLE_DEFINE_TYPE(category_set, void *, const struct event_category *); static void insert_category(HASH_TABLE_TYPE(category_set) hash, const struct event_category * const cat) { /* insert this category (key == the unique internal pointer) */ hash_table_update(hash, cat->internal, cat); /* insert parent's categories */ if (cat->parent != NULL) insert_category(hash, cat->parent); } void event_export_helper_fmt_categories(string_t *dest, struct event_category * const *cats, unsigned int count, void (*append)(string_t *, const char *), const char *separator) { HASH_TABLE_TYPE(category_set) hash; struct hash_iterate_context *iter; const struct event_category *cat; void *key ATTR_UNUSED; unsigned int i; bool first = TRUE; if (count == 0) return; hash_table_create_direct(&hash, pool_datastack_create(), 3 * count /* estimate */); /* insert all the categories into the hash table */ for (i = 0; i < count; i++) insert_category(hash, cats[i]); /* output each category from hash table */ iter = hash_table_iterate_init(hash); while (hash_table_iterate(iter, hash, &key, &cat)) { if (!first) str_append(dest, separator); append(dest, cat->name); first = FALSE; } hash_table_iterate_deinit(&iter); hash_table_destroy(&hash); } dovecot-2.3.21.1/src/stats/event-exporter-transport-drop.c0000644000000000000000000000035714656633576020376 00000000000000/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "event-exporter.h" void event_export_transport_drop(const struct exporter *exporter ATTR_UNUSED, const buffer_t *buf ATTR_UNUSED) { } dovecot-2.3.21.1/src/stats/stats-service-private.h0000644000000000000000000000022014656633576016651 00000000000000#ifndef STATS_SERVICE_PRIVATE_H #define STATS_SERVICE_PRIVATE_H #include "stats-service.h" void stats_service_openmetrics_init(void); #endif dovecot-2.3.21.1/src/stats/test-client-writer.c0000644000000000000000000000722514656633576016161 00000000000000/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ #include "test-stats-common.h" #include "master-service-private.h" #include "client-writer.h" #include "connection.h" #include "ostream.h" static struct event *last_sent_event = NULL; static bool recurse_back = FALSE; static struct connection_list *conn_list; static void test_writer_server_destroy(struct connection *conn) { io_loop_stop(conn->ioloop); } static int test_writer_server_input_args(struct connection *conn, const char *const *args ATTR_UNUSED) { /* check filter */ test_assert_strcmp(args[0], "FILTER"); test_assert_strcmp(args[1], "(event=\"test\")"); /* send commands now */ string_t *send_buf = t_str_new(128); o_stream_nsend_str(conn->output, "CATEGORY\ttest\n"); str_printfa(send_buf, "BEGIN\t%"PRIu64"\t0\t0\t", last_sent_event->id); event_export(last_sent_event, send_buf); str_append_c(send_buf, '\n'); o_stream_nsend(conn->output, str_data(send_buf), str_len(send_buf)); str_truncate(send_buf, 0); str_printfa(send_buf, "END\t%"PRIu64"\n", last_sent_event->id); o_stream_nsend(conn->output, str_data(send_buf), str_len(send_buf)); /* disconnect immediately */ return -1; } static struct connection_settings client_set = { .service_name_in = "stats-server", .service_name_out = "stats-client", .major_version = 4, .minor_version = 0, .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, .client = TRUE, }; static const struct connection_vfuncs client_vfuncs = { .input_args = test_writer_server_input_args, .destroy = test_writer_server_destroy, }; static void test_write_one(struct event *event ATTR_UNUSED) { int fds[2]; test_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0); struct connection *conn = i_new(struct connection, 1); struct ioloop *loop = io_loop_create(); client_writer_create(fds[1]); connection_init_client_fd(conn_list, conn, "stats", fds[0], fds[0]); last_sent_event = event; io_loop_run(loop); last_sent_event = NULL; connection_deinit(conn); i_free(conn); /* client-writer needs two loops to deinit */ io_loop_set_running(loop); io_loop_handler_run(loop); io_loop_set_running(loop); io_loop_handler_run(loop); io_loop_destroy(&loop); } bool test_stats_callback(struct event *event, enum event_callback_type type ATTR_UNUSED, struct failure_context *ctx ATTR_UNUSED, const char *fmt ATTR_UNUSED, va_list args ATTR_UNUSED) { if (recurse_back) return TRUE; recurse_back = TRUE; if (stats_metrics != NULL) { test_write_one(event); } recurse_back = FALSE; return TRUE; } static const char *settings_blob_1 = "metric=test\n" "metric/test/metric_name=test\n" "metric/test/filter=event=test\n" "\n"; static void test_client_writer(void) { test_begin("client writer"); /* register some stats */ test_init(settings_blob_1); client_writers_init(); conn_list = connection_list_init(&client_set, &client_vfuncs); /* push event in */ struct event *event = event_create(NULL); event_add_category(event, &test_category); event_set_name(event, "test"); test_event_send(event); event_unref(&event); test_assert(get_stats_dist_field("test", STATS_DIST_COUNT) == 1); test_assert(get_stats_dist_field("test", STATS_DIST_SUM) > 0); test_deinit(); client_writers_deinit(); connection_list_deinit(&conn_list); test_end(); } int main(void) { /* fake master service to pretend destroying connections. */ struct master_service local_master_service = { .stopping = TRUE, .total_available_count = 100, .service_count_left = 100, }; void (*const test_functions[])(void) = { test_client_writer, NULL }; master_service = &local_master_service; int ret = test_run(test_functions); return ret; } dovecot-2.3.21.1/src/stats/Makefile.in0000644000000000000000000010470514656633613014306 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ pkglibexec_PROGRAMS = stats$(EXEEXT) noinst_PROGRAMS = $(am__EXEEXT_1) subdir = src/stats ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__EXEEXT_1 = test-stats-metrics$(EXEEXT) test-client-writer$(EXEEXT) \ test-client-reader$(EXEEXT) am__installdirs = "$(DESTDIR)$(pkglibexecdir)" PROGRAMS = $(noinst_PROGRAMS) $(pkglibexec_PROGRAMS) LTLIBRARIES = $(noinst_LTLIBRARIES) libstats_local_la_LIBADD = am__objects_1 = stats-service-openmetrics.lo am_libstats_local_la_OBJECTS = client-reader.lo client-writer.lo \ client-http.lo event-exporter-fmt.lo \ event-exporter-fmt-json.lo event-exporter-fmt-none.lo \ event-exporter-fmt-tab-text.lo \ event-exporter-transport-drop.lo \ event-exporter-transport-http-post.lo \ event-exporter-transport-log.lo $(am__objects_1) \ stats-service.lo stats-event-category.lo stats-metrics.lo \ stats-settings.lo libstats_local_la_OBJECTS = $(am_libstats_local_la_OBJECTS) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = am_stats_OBJECTS = main.$(OBJEXT) stats_OBJECTS = $(am_stats_OBJECTS) am__DEPENDENCIES_1 = am_test_client_reader_OBJECTS = test-client-reader.$(OBJEXT) \ test-stats-common.$(OBJEXT) test_client_reader_OBJECTS = $(am_test_client_reader_OBJECTS) am__DEPENDENCIES_2 = $(noinst_LTLIBRARIES) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) am_test_client_writer_OBJECTS = test-client-writer.$(OBJEXT) \ test-stats-common.$(OBJEXT) test_client_writer_OBJECTS = $(am_test_client_writer_OBJECTS) am_test_stats_metrics_OBJECTS = test-stats-metrics.$(OBJEXT) \ test-stats-common.$(OBJEXT) test_stats_metrics_OBJECTS = $(am_test_stats_metrics_OBJECTS) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/client-http.Plo \ ./$(DEPDIR)/client-reader.Plo ./$(DEPDIR)/client-writer.Plo \ ./$(DEPDIR)/event-exporter-fmt-json.Plo \ ./$(DEPDIR)/event-exporter-fmt-none.Plo \ ./$(DEPDIR)/event-exporter-fmt-tab-text.Plo \ ./$(DEPDIR)/event-exporter-fmt.Plo \ ./$(DEPDIR)/event-exporter-transport-drop.Plo \ ./$(DEPDIR)/event-exporter-transport-http-post.Plo \ ./$(DEPDIR)/event-exporter-transport-log.Plo \ ./$(DEPDIR)/main.Po ./$(DEPDIR)/stats-event-category.Plo \ ./$(DEPDIR)/stats-metrics.Plo \ ./$(DEPDIR)/stats-service-openmetrics.Plo \ ./$(DEPDIR)/stats-service.Plo ./$(DEPDIR)/stats-settings.Plo \ ./$(DEPDIR)/test-client-reader.Po \ ./$(DEPDIR)/test-client-writer.Po \ ./$(DEPDIR)/test-stats-common.Po \ ./$(DEPDIR)/test-stats-metrics.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(libstats_local_la_SOURCES) $(stats_SOURCES) \ $(test_client_reader_SOURCES) $(test_client_writer_SOURCES) \ $(test_stats_metrics_SOURCES) DIST_SOURCES = $(libstats_local_la_SOURCES) $(stats_SOURCES) \ $(test_client_reader_SOURCES) $(test_client_writer_SOURCES) \ $(test_stats_metrics_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac HEADERS = $(noinst_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) pkglibexecdir = $(libexecdir)/dovecot ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ noinst_LTLIBRARIES = libstats_local.la AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-http \ -I$(top_srcdir)/src/lib-ssl-iostream \ -I$(top_srcdir)/src/lib-test \ $(BINARY_CFLAGS) stats_LDADD = \ $(noinst_LTLIBRARIES) \ $(LIBDOVECOT) \ $(DOVECOT_SSL_LIBS) \ $(BINARY_LDFLAGS) \ -lm stats_DEPENDENCIES = \ $(noinst_LTLIBRARIES) \ $(DOVECOT_SSL_LIBS) \ $(LIBDOVECOT_DEPS) stats_services = \ stats-service-openmetrics.c stats_SOURCES = \ main.c libstats_local_la_SOURCES = \ client-reader.c \ client-writer.c \ client-http.c \ event-exporter-fmt.c \ event-exporter-fmt-json.c \ event-exporter-fmt-none.c \ event-exporter-fmt-tab-text.c \ event-exporter-transport-drop.c \ event-exporter-transport-http-post.c \ event-exporter-transport-log.c \ $(stats_services) \ stats-service.c \ stats-event-category.c \ stats-metrics.c \ stats-settings.c noinst_HEADERS = \ stats-common.h \ client-reader.h \ client-writer.h \ client-http.h\ event-exporter.h \ stats-service.h \ stats-service-private.h \ stats-event-category.h \ stats-metrics.h \ stats-settings.h \ test-stats-common.h test_libs = \ $(noinst_LTLIBRARIES) \ $(DOVECOT_SSL_LIBS) \ $(LIBDOVECOT) \ $(BINARY_LDFLAGS) \ -lm test_deps = \ $(noinst_LTLIBRARIES) \ $(DOVECOT_SSL_LIBS) \ $(LIBDOVECOT_DEPS) test_stats_metrics_SOURCES = test-stats-metrics.c test-stats-common.c test_stats_metrics_LDADD = $(test_libs) test_stats_metrics_DEPENDENCIES = $(test_deps) test_client_writer_SOURCES = test-client-writer.c test-stats-common.c test_client_writer_LDADD = $(test_libs) test_client_writer_DEPENDENCIES = $(test_deps) test_client_reader_SOURCES = test-client-reader.c test-stats-common.c test_client_reader_LDADD = $(test_libs) test_client_reader_DEPENDENCIES = $(test_deps) test_programs = test-stats-metrics test-client-writer test-client-reader LIBDOVECOT_TEST_DEPS = \ ../lib-ssl-iostream/libssl_iostream.la \ ../lib-test/libtest.la \ ../lib/liblib.la LIBDOVECOT_TEST = \ $(LIBDOVECOT_TEST_DEPS) \ $(MODULE_LIBS) all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/stats/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/stats/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): clean-noinstPROGRAMS: @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files clean-pkglibexecPROGRAMS: @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list clean-noinstLTLIBRARIES: -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) @list='$(noinst_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } libstats_local.la: $(libstats_local_la_OBJECTS) $(libstats_local_la_DEPENDENCIES) $(EXTRA_libstats_local_la_DEPENDENCIES) $(AM_V_CCLD)$(LINK) $(libstats_local_la_OBJECTS) $(libstats_local_la_LIBADD) $(LIBS) stats$(EXEEXT): $(stats_OBJECTS) $(stats_DEPENDENCIES) $(EXTRA_stats_DEPENDENCIES) @rm -f stats$(EXEEXT) $(AM_V_CCLD)$(LINK) $(stats_OBJECTS) $(stats_LDADD) $(LIBS) test-client-reader$(EXEEXT): $(test_client_reader_OBJECTS) $(test_client_reader_DEPENDENCIES) $(EXTRA_test_client_reader_DEPENDENCIES) @rm -f test-client-reader$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_client_reader_OBJECTS) $(test_client_reader_LDADD) $(LIBS) test-client-writer$(EXEEXT): $(test_client_writer_OBJECTS) $(test_client_writer_DEPENDENCIES) $(EXTRA_test_client_writer_DEPENDENCIES) @rm -f test-client-writer$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_client_writer_OBJECTS) $(test_client_writer_LDADD) $(LIBS) test-stats-metrics$(EXEEXT): $(test_stats_metrics_OBJECTS) $(test_stats_metrics_DEPENDENCIES) $(EXTRA_test_stats_metrics_DEPENDENCIES) @rm -f test-stats-metrics$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_stats_metrics_OBJECTS) $(test_stats_metrics_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client-http.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client-reader.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client-writer.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/event-exporter-fmt-json.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/event-exporter-fmt-none.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/event-exporter-fmt-tab-text.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/event-exporter-fmt.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/event-exporter-transport-drop.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/event-exporter-transport-http-post.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/event-exporter-transport-log.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stats-event-category.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stats-metrics.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stats-service-openmetrics.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stats-service.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stats-settings.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-client-reader.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-client-writer.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-stats-common.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-stats-metrics.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am $(MAKE) $(AM_MAKEFLAGS) check-local check: check-am all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkglibexecdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ clean-noinstPROGRAMS clean-pkglibexecPROGRAMS mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/client-http.Plo -rm -f ./$(DEPDIR)/client-reader.Plo -rm -f ./$(DEPDIR)/client-writer.Plo -rm -f ./$(DEPDIR)/event-exporter-fmt-json.Plo -rm -f ./$(DEPDIR)/event-exporter-fmt-none.Plo -rm -f ./$(DEPDIR)/event-exporter-fmt-tab-text.Plo -rm -f ./$(DEPDIR)/event-exporter-fmt.Plo -rm -f ./$(DEPDIR)/event-exporter-transport-drop.Plo -rm -f ./$(DEPDIR)/event-exporter-transport-http-post.Plo -rm -f ./$(DEPDIR)/event-exporter-transport-log.Plo -rm -f ./$(DEPDIR)/main.Po -rm -f ./$(DEPDIR)/stats-event-category.Plo -rm -f ./$(DEPDIR)/stats-metrics.Plo -rm -f ./$(DEPDIR)/stats-service-openmetrics.Plo -rm -f ./$(DEPDIR)/stats-service.Plo -rm -f ./$(DEPDIR)/stats-settings.Plo -rm -f ./$(DEPDIR)/test-client-reader.Po -rm -f ./$(DEPDIR)/test-client-writer.Po -rm -f ./$(DEPDIR)/test-stats-common.Po -rm -f ./$(DEPDIR)/test-stats-metrics.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-pkglibexecPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/client-http.Plo -rm -f ./$(DEPDIR)/client-reader.Plo -rm -f ./$(DEPDIR)/client-writer.Plo -rm -f ./$(DEPDIR)/event-exporter-fmt-json.Plo -rm -f ./$(DEPDIR)/event-exporter-fmt-none.Plo -rm -f ./$(DEPDIR)/event-exporter-fmt-tab-text.Plo -rm -f ./$(DEPDIR)/event-exporter-fmt.Plo -rm -f ./$(DEPDIR)/event-exporter-transport-drop.Plo -rm -f ./$(DEPDIR)/event-exporter-transport-http-post.Plo -rm -f ./$(DEPDIR)/event-exporter-transport-log.Plo -rm -f ./$(DEPDIR)/main.Po -rm -f ./$(DEPDIR)/stats-event-category.Plo -rm -f ./$(DEPDIR)/stats-metrics.Plo -rm -f ./$(DEPDIR)/stats-service-openmetrics.Plo -rm -f ./$(DEPDIR)/stats-service.Plo -rm -f ./$(DEPDIR)/stats-settings.Plo -rm -f ./$(DEPDIR)/test-client-reader.Po -rm -f ./$(DEPDIR)/test-client-writer.Po -rm -f ./$(DEPDIR)/test-stats-common.Po -rm -f ./$(DEPDIR)/test-stats-metrics.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkglibexecPROGRAMS .MAKE: check-am install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ check-local clean clean-generic clean-libtool \ clean-noinstLTLIBRARIES clean-noinstPROGRAMS \ clean-pkglibexecPROGRAMS cscopelist-am ctags ctags-am \ distclean distclean-compile distclean-generic \ distclean-libtool distclean-tags distdir dvi dvi-am html \ html-am info info-am install install-am install-data \ install-data-am install-dvi install-dvi-am install-exec \ install-exec-am install-html install-html-am install-info \ install-info-am install-man install-pdf install-pdf-am \ install-pkglibexecPROGRAMS install-ps install-ps-am \ install-strip installcheck installcheck-am installdirs \ maintainer-clean maintainer-clean-generic mostlyclean \ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ uninstall-pkglibexecPROGRAMS .PRECIOUS: Makefile check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/stats/event-exporter-fmt-json.c0000644000000000000000000001377114656633576017141 00000000000000/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "array.h" #include "lib-event-private.h" #include "event-exporter.h" #include "str.h" #include "json-parser.h" #include "hostpid.h" static void append_str(string_t *dest, const char *str) { str_append_c(dest, '"'); json_append_escaped(dest, str); str_append_c(dest, '"'); } static void append_str_max_len(string_t *dest, const char *str, const struct metric_export_info *info) { str_append_c(dest, '"'); if (info->exporter->format_max_field_len == 0) json_append_escaped(dest, str); else { size_t len = strlen(str); json_append_escaped_data(dest, (const unsigned char *)str, I_MIN(len, info->exporter->format_max_field_len)); if (len > info->exporter->format_max_field_len) str_append(dest, "..."); } str_append_c(dest, '"'); } static void append_strlist(string_t *dest, const ARRAY_TYPE(const_string) *strlist, const struct metric_export_info *info) { const char *value; bool first = TRUE; str_append_c(dest, '['); array_foreach_elem(strlist, value) { if (first) first = FALSE; else str_append_c(dest, ','); append_str_max_len(dest, value, info); } str_append_c(dest, ']'); } static void append_int(string_t *dest, intmax_t val) { str_printfa(dest, "%jd", val); } static void append_time(string_t *dest, const struct timeval *time, enum event_exporter_time_fmt fmt) { switch (fmt) { case EVENT_EXPORTER_TIME_FMT_NATIVE: i_panic("JSON does not have a native date/time type"); case EVENT_EXPORTER_TIME_FMT_UNIX: event_export_helper_fmt_unix_time(dest, time); break; case EVENT_EXPORTER_TIME_FMT_RFC3339: str_append_c(dest, '"'); event_export_helper_fmt_rfc3339_time(dest, time); str_append_c(dest, '"'); break; } } static void append_field_value(string_t *dest, const struct event_field *field, const struct metric_export_info *info) { switch (field->value_type) { case EVENT_FIELD_VALUE_TYPE_STR: append_str_max_len(dest, field->value.str, info); break; case EVENT_FIELD_VALUE_TYPE_INTMAX: append_int(dest, field->value.intmax); break; case EVENT_FIELD_VALUE_TYPE_TIMEVAL: append_time(dest, &field->value.timeval, info->exporter->time_format); break; case EVENT_FIELD_VALUE_TYPE_STRLIST: append_strlist(dest, &field->value.strlist, info); break; } } static void json_export_name(string_t *dest, struct event *event, const struct metric_export_info *info) { if ((info->include & EVENT_EXPORTER_INCL_NAME) == 0) return; append_str(dest, "event"); str_append_c(dest, ':'); append_str(dest, event->sending_name); str_append_c(dest, ','); } static void json_export_hostname(string_t *dest, const struct metric_export_info *info) { if ((info->include & EVENT_EXPORTER_INCL_HOSTNAME) == 0) return; append_str(dest, "hostname"); str_append_c(dest, ':'); append_str(dest, my_hostname); str_append_c(dest, ','); } static void json_export_timestamps(string_t *dest, struct event *event, const struct metric_export_info *info) { if ((info->include & EVENT_EXPORTER_INCL_TIMESTAMPS) == 0) return; append_str(dest, "start_time"); str_append_c(dest, ':'); append_time(dest, &event->tv_created, info->exporter->time_format); str_append_c(dest, ','); append_str(dest, "end_time"); str_append_c(dest, ':'); append_time(dest, &ioloop_timeval, info->exporter->time_format); str_append_c(dest, ','); } static void json_export_categories(string_t *dest, struct event *event, const struct metric_export_info *info) { struct event_category *const *cats; unsigned int count; if ((info->include & EVENT_EXPORTER_INCL_CATEGORIES) == 0) return; append_str(dest, "categories"); str_append(dest, ":["); cats = event_get_categories(event, &count); event_export_helper_fmt_categories(dest, cats, count, append_str, ","); str_append(dest, "],"); } static void json_export_fields(string_t *dest, struct event *event, const struct metric_export_info *info, const unsigned int fields_count, const struct metric_field *fields) { bool appended = FALSE; if ((info->include & EVENT_EXPORTER_INCL_FIELDS) == 0) return; append_str(dest, "fields"); str_append(dest, ":{"); if (fields_count == 0) { /* include all fields */ const struct event_field *fields; unsigned int count; fields = event_get_fields(event, &count); for (unsigned int i = 0; i < count; i++) { const struct event_field *field = &fields[i]; append_str(dest, field->key); str_append_c(dest, ':'); append_field_value(dest, field, info); str_append_c(dest, ','); appended = TRUE; } } else { for (unsigned int i = 0; i < fields_count; i++) { const char *name = fields[i].field_key; const struct event_field *field; field = event_find_field_recursive(event, name); if (field == NULL) continue; /* doesn't exist, skip it */ append_str(dest, name); str_append_c(dest, ':'); append_field_value(dest, field, info); str_append_c(dest, ','); appended = TRUE; } } /* remove trailing comma */ if (appended) str_truncate(dest, str_len(dest) - 1); str_append(dest, "},"); } /* * Serialize the event as: * * { * "name": , * "hostname": , * "start_time": , * "end_time": , * "categories": [ , ... ], * "fields": { * : , * ... * } * } * */ void event_export_fmt_json(const struct metric *metric, struct event *event, buffer_t *dest) { const struct metric_export_info *info = &metric->export_info; if (info->include == EVENT_EXPORTER_INCL_NONE) { str_append(dest, "{}"); return; } str_append_c(dest, '{'); json_export_name(dest, event, info); json_export_hostname(dest, info); json_export_timestamps(dest, event, info); json_export_categories(dest, event, info); json_export_fields(dest, event, info, metric->fields_count, metric->fields); /* remove trailing comma */ str_truncate(dest, str_len(dest) - 1); str_append_c(dest, '}'); } dovecot-2.3.21.1/src/stats/stats-metrics.c0000644000000000000000000005225514656633576015221 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "stats-common.h" #include "array.h" #include "str.h" #include "str-sanitize.h" #include "stats-dist.h" #include "time-util.h" #include "event-filter.h" #include "event-exporter.h" #include "stats-settings.h" #include "stats-metrics.h" #include "settings-parser.h" #include #define LOG_EXPORTER_LONG_FIELD_TRUNCATE_LEN 1000 struct stats_metrics { pool_t pool; struct event_filter *filter; /* stats & export */ ARRAY(struct exporter *) exporters; ARRAY(struct metric *) metrics; }; static void stats_metric_event(struct metric *metric, struct event *event, pool_t pool); static struct metric * stats_metric_sub_metric_alloc(struct metric *metric, const char *name, pool_t pool); static void stats_metric_free(struct metric *metric); static void stats_exporters_add_set(struct stats_metrics *metrics, const struct stats_exporter_settings *set) { struct exporter *exporter; exporter = p_new(metrics->pool, struct exporter, 1); exporter->name = p_strdup(metrics->pool, set->name); exporter->transport_args = p_strdup(metrics->pool, set->transport_args); exporter->transport_timeout = set->transport_timeout; exporter->time_format = set->parsed_time_format; /* TODO: The following should be plugable. * * Note: Make sure to mirror any changes to the below code in * stats_exporter_settings_check(). */ if (strcmp(set->format, "none") == 0) { exporter->format = event_export_fmt_none; exporter->format_mime_type = "application/octet-stream"; } else if (strcmp(set->format, "json") == 0) { exporter->format = event_export_fmt_json; exporter->format_mime_type = "application/json"; } else if (strcmp(set->format, "tab-text") == 0) { exporter->format = event_export_fmt_tabescaped_text; exporter->format_mime_type = "text/plain"; } else { i_unreached(); } /* TODO: The following should be plugable. * * Note: Make sure to mirror any changes to the below code in * stats_exporter_settings_check(). */ if (strcmp(set->transport, "drop") == 0) { exporter->transport = event_export_transport_drop; } else if (strcmp(set->transport, "http-post") == 0) { exporter->transport = event_export_transport_http_post; } else if (strcmp(set->transport, "log") == 0) { exporter->transport = event_export_transport_log; exporter->format_max_field_len = LOG_EXPORTER_LONG_FIELD_TRUNCATE_LEN; } else { i_unreached(); } exporter->transport_args = set->transport_args; array_push_back(&metrics->exporters, &exporter); } static struct metric * stats_metric_alloc(pool_t pool, const char *name, const struct stats_metric_settings *set, const char *const *fields) { struct metric *metric = p_new(pool, struct metric, 1); metric->name = p_strdup(pool, name); metric->set = set; metric->duration_stats = stats_dist_init(); metric->fields_count = str_array_length(fields); if (metric->fields_count > 0) { metric->fields = p_new(pool, struct metric_field, metric->fields_count); for (unsigned int i = 0; i < metric->fields_count; i++) { metric->fields[i].field_key = p_strdup(pool, fields[i]); metric->fields[i].stats = stats_dist_init(); } } return metric; } static void stats_metrics_add_set(struct stats_metrics *metrics, const struct stats_metric_settings *set) { struct exporter *exporter; struct metric *metric; const char *const *fields; const char *const *tmp; fields = t_strsplit_spaces(set->fields, " "); metric = stats_metric_alloc(metrics->pool, set->metric_name, set, fields); if (array_is_created(&set->parsed_group_by)) metric->group_by = array_get(&set->parsed_group_by, &metric->group_by_count); array_push_back(&metrics->metrics, &metric); event_filter_merge_with_context(metrics->filter, set->parsed_filter, metric); /* * Metrics may also be exported - make sure exporter info is set */ if (set->exporter[0] == '\0') return; /* not exported */ array_foreach_elem(&metrics->exporters, exporter) { if (strcmp(set->exporter, exporter->name) == 0) { metric->export_info.exporter = exporter; break; } } if (metric->export_info.exporter == NULL) i_panic("Could not find exporter (%s) for metric (%s)", set->exporter, set->metric_name); /* Defaults */ metric->export_info.include = EVENT_EXPORTER_INCL_NONE; tmp = t_strsplit_spaces(set->exporter_include, " "); for (; *tmp != NULL; tmp++) { if (strcmp(*tmp, "name") == 0) metric->export_info.include |= EVENT_EXPORTER_INCL_NAME; else if (strcmp(*tmp, "hostname") == 0) metric->export_info.include |= EVENT_EXPORTER_INCL_HOSTNAME; else if (strcmp(*tmp, "timestamps") == 0) metric->export_info.include |= EVENT_EXPORTER_INCL_TIMESTAMPS; else if (strcmp(*tmp, "categories") == 0) metric->export_info.include |= EVENT_EXPORTER_INCL_CATEGORIES; else if (strcmp(*tmp, "fields") == 0) metric->export_info.include |= EVENT_EXPORTER_INCL_FIELDS; else i_warning("Ignoring unknown exporter include '%s'", *tmp); } } static struct stats_metric_settings * stats_metric_settings_dup(pool_t pool, const struct stats_metric_settings *src) { struct stats_metric_settings *set = p_new(pool, struct stats_metric_settings, 1); set->metric_name = p_strdup(pool, src->metric_name); set->description = p_strdup(pool, src->description); set->fields = p_strdup(pool, src->fields); set->group_by = p_strdup(pool, src->group_by); set->filter = p_strdup(pool, src->filter); set->exporter = p_strdup(pool, src->exporter); set->exporter_include = p_strdup(pool, src->exporter_include); return set; } static struct metric * stats_metrics_find(struct stats_metrics *metrics, const char *name, unsigned int *idx_r) { struct metric *const *m; array_foreach(&metrics->metrics, m) { if (strcmp((*m)->name, name) == 0) { *idx_r = array_foreach_idx(&metrics->metrics, m); return *m; } } return NULL; } static bool stats_metrics_check_for_exporter(struct stats_metrics *metrics, const char *name) { struct exporter *exporter; /* Allow registering metrics with empty/missing exporters. */ if (name[0] == '\0') return TRUE; if (!array_is_created(&metrics->exporters)) return FALSE; bool is_found = FALSE; array_foreach_elem(&metrics->exporters, exporter) { if (strcmp(exporter->name, name) == 0) { is_found = TRUE; break; } } return is_found; } bool stats_metrics_add_dynamic(struct stats_metrics *metrics, struct stats_metric_settings *set, const char **error_r) { unsigned int existing_idx ATTR_UNUSED; if (stats_metrics_find(metrics, set->metric_name, &existing_idx) != NULL) { *error_r = "Metric already exists"; return FALSE; } struct stats_metric_settings *_set = stats_metric_settings_dup(metrics->pool, set); if (!stats_metric_setting_parser_info.check_func(_set, metrics->pool, error_r)) return FALSE; if (!stats_metrics_check_for_exporter(metrics, set->exporter)) { *error_r = t_strdup_printf("Exporter '%s' does not exist.", set->exporter); return FALSE; } stats_metrics_add_set(metrics, _set); return TRUE; } bool stats_metrics_remove_dynamic(struct stats_metrics *metrics, const char *name) { unsigned int m_idx; bool ret = FALSE; struct metric *m = stats_metrics_find(metrics, name, &m_idx); if (m != NULL) { array_delete(&metrics->metrics, m_idx, 1); ret = event_filter_remove_queries_with_context(metrics->filter, m); stats_metric_free(m); } return ret; } static void stats_metrics_add_from_settings(struct stats_metrics *metrics, const struct stats_settings *set) { /* add all the exporters first */ if (!array_is_created(&set->exporters)) { p_array_init(&metrics->exporters, metrics->pool, 0); } else { struct stats_exporter_settings *exporter_set; p_array_init(&metrics->exporters, metrics->pool, array_count(&set->exporters)); array_foreach_elem(&set->exporters, exporter_set) stats_exporters_add_set(metrics, exporter_set); } /* then add all the metrics */ if (!array_is_created(&set->metrics)) { p_array_init(&metrics->metrics, metrics->pool, 0); } else { struct stats_metric_settings *metric_set; p_array_init(&metrics->metrics, metrics->pool, array_count(&set->metrics)); array_foreach_elem(&set->metrics, metric_set) T_BEGIN { stats_metrics_add_set(metrics, metric_set); } T_END; } } struct stats_metrics *stats_metrics_init(const struct stats_settings *set) { struct stats_metrics *metrics; pool_t pool = pool_alloconly_create("stats metrics", 1024); metrics = p_new(pool, struct stats_metrics, 1); metrics->pool = pool; metrics->filter = event_filter_create(); stats_metrics_add_from_settings(metrics, set); return metrics; } static void stats_metric_free(struct metric *metric) { struct metric *sub_metric; stats_dist_deinit(&metric->duration_stats); for (unsigned int i = 0; i < metric->fields_count; i++) stats_dist_deinit(&metric->fields[i].stats); if (!array_is_created(&metric->sub_metrics)) return; array_foreach_elem(&metric->sub_metrics, sub_metric) stats_metric_free(sub_metric); } static void stats_export_deinit(void) { /* no need for event_export_transport_drop_deinit() - no-op */ event_export_transport_http_post_deinit(); /* no need for event_export_transport_log_deinit() - no-op */ } void stats_metrics_deinit(struct stats_metrics **_metrics) { struct stats_metrics *metrics = *_metrics; struct metric *metric; *_metrics = NULL; stats_export_deinit(); array_foreach_elem(&metrics->metrics, metric) stats_metric_free(metric); event_filter_unref(&metrics->filter); pool_unref(&metrics->pool); } static void stats_metric_reset(struct metric *metric) { struct metric *sub_metric; stats_dist_reset(metric->duration_stats); for (unsigned int i = 0; i < metric->fields_count; i++) stats_dist_reset(metric->fields[i].stats); if (!array_is_created(&metric->sub_metrics)) return; array_foreach_elem(&metric->sub_metrics, sub_metric) stats_metric_reset(sub_metric); } void stats_metrics_reset(struct stats_metrics *metrics) { struct metric *metric; array_foreach_elem(&metrics->metrics, metric) stats_metric_reset(metric); } struct event_filter * stats_metrics_get_event_filter(struct stats_metrics *metrics) { return metrics->filter; } static struct metric * stats_metric_find_sub_metric(struct metric *metric, const struct metric_value *value) { struct metric *sub_metrics; /* lookup sub-metric */ array_foreach_elem(&metric->sub_metrics, sub_metrics) { switch (sub_metrics->group_value.type) { case METRIC_VALUE_TYPE_STR: if (memcmp(sub_metrics->group_value.hash, value->hash, SHA1_RESULTLEN) == 0) return sub_metrics; break; case METRIC_VALUE_TYPE_INT: if (sub_metrics->group_value.intmax == value->intmax) return sub_metrics; break; case METRIC_VALUE_TYPE_BUCKET_INDEX: if (sub_metrics->group_value.intmax == value->intmax) return sub_metrics; break; } } return NULL; } static struct metric * stats_metric_sub_metric_alloc(struct metric *metric, const char *name, pool_t pool) { struct metric *sub_metric; ARRAY_TYPE(const_string) fields; t_array_init(&fields, metric->fields_count); for (unsigned int i = 0; i < metric->fields_count; i++) array_append(&fields, &metric->fields[i].field_key, 1); array_append_zero(&fields); sub_metric = stats_metric_alloc(pool, metric->name, metric->set, array_idx(&fields, 0)); sub_metric->sub_name = p_strdup(pool, str_sanitize_utf8(name, 32)); array_append(&metric->sub_metrics, &sub_metric, 1); return sub_metric; } static bool stats_metric_group_by_discrete(const struct event_field *field, struct metric_value *value_r) { switch (field->value_type) { case EVENT_FIELD_VALUE_TYPE_STR: value_r->type = METRIC_VALUE_TYPE_STR; /* use sha1 of value to avoid excessive memory usage in case the actual value is quite long */ sha1_get_digest(field->value.str, strlen(field->value.str), value_r->hash); return TRUE; case EVENT_FIELD_VALUE_TYPE_INTMAX: value_r->type = METRIC_VALUE_TYPE_INT; value_r->intmax = field->value.intmax; return TRUE; case EVENT_FIELD_VALUE_TYPE_TIMEVAL: return FALSE; case EVENT_FIELD_VALUE_TYPE_STRLIST: return FALSE; } i_unreached(); } /* convert the value to a bucket index */ static bool stats_metric_group_by_quantized(const struct event_field *field, struct metric_value *value_r, const struct stats_metric_settings_group_by *group_by) { switch (field->value_type) { case EVENT_FIELD_VALUE_TYPE_STR: case EVENT_FIELD_VALUE_TYPE_TIMEVAL: case EVENT_FIELD_VALUE_TYPE_STRLIST: return FALSE; case EVENT_FIELD_VALUE_TYPE_INTMAX: break; } value_r->type = METRIC_VALUE_TYPE_BUCKET_INDEX; for (unsigned int i = 0; i < group_by->num_ranges; i++) { if ((field->value.intmax <= group_by->ranges[i].min) || (field->value.intmax > group_by->ranges[i].max)) continue; value_r->intmax = i; return TRUE; } i_panic("failed to find a matching bucket for '%s'=%jd", group_by->field, field->value.intmax); } /* convert value to a bucket label */ static const char * stats_metric_group_by_quantized_label(const struct event_field *field, const struct stats_metric_settings_group_by *group_by, const size_t bucket_index) { const struct stats_metric_settings_bucket_range *range = &group_by->ranges[bucket_index]; const char *name = group_by->field; const char *label; switch (field->value_type) { case EVENT_FIELD_VALUE_TYPE_STR: case EVENT_FIELD_VALUE_TYPE_TIMEVAL: case EVENT_FIELD_VALUE_TYPE_STRLIST: i_unreached(); case EVENT_FIELD_VALUE_TYPE_INTMAX: break; } if (range->min == INTMAX_MIN) label = t_strdup_printf("%s_ninf_%jd", name, range->max); else if (range->max == INTMAX_MAX) label = t_strdup_printf("%s_%jd_inf", name, range->min + 1); else label = t_strdup_printf("%s_%jd_%jd", name, range->min + 1, range->max); return label; } static bool stats_metric_group_by_get_value(const struct event_field *field, const struct stats_metric_settings_group_by *group_by, struct metric_value *value_r) { switch (group_by->func) { case STATS_METRIC_GROUPBY_DISCRETE: if (!stats_metric_group_by_discrete(field, value_r)) return FALSE; return TRUE; case STATS_METRIC_GROUPBY_QUANTIZED: if (!stats_metric_group_by_quantized(field, value_r, group_by)) return FALSE; return TRUE; } i_panic("unknown group-by function %d", group_by->func); } static const char * stats_metric_group_by_get_label(const struct event_field *field, const struct stats_metric_settings_group_by *group_by, const struct metric_value *value) { switch (group_by->func) { case STATS_METRIC_GROUPBY_DISCRETE: i_unreached(); case STATS_METRIC_GROUPBY_QUANTIZED: return stats_metric_group_by_quantized_label(field, group_by, value->intmax); } i_panic("unknown group-by function %d", group_by->func); } static const char * stats_metric_group_by_value_label(const struct event_field *field, const struct stats_metric_settings_group_by *group_by, const struct metric_value *value) { switch (value->type) { case METRIC_VALUE_TYPE_STR: return field->value.str; case METRIC_VALUE_TYPE_INT: return dec2str(field->value.intmax); case METRIC_VALUE_TYPE_BUCKET_INDEX: return stats_metric_group_by_get_label(field, group_by, value); } i_unreached(); } static struct metric * stats_metric_get_sub_metric(struct metric *metric, const struct event_field *field, const struct metric_value *value, pool_t pool) { struct metric *sub_metric; sub_metric = stats_metric_find_sub_metric(metric, value); if (sub_metric != NULL) return sub_metric; T_BEGIN { const char *value_label = stats_metric_group_by_value_label(field, &metric->group_by[0], value); sub_metric = stats_metric_sub_metric_alloc(metric, value_label, pool); } T_END; if (metric->group_by_count > 1) { sub_metric->group_by_count = metric->group_by_count - 1; sub_metric->group_by = &metric->group_by[1]; } sub_metric->group_value.type = value->type; sub_metric->group_value.intmax = value->intmax; memcpy(sub_metric->group_value.hash, value->hash, SHA1_RESULTLEN); return sub_metric; } static void stats_metric_group_by_field(struct metric *metric, struct event *event, const struct event_field *field, pool_t pool) { struct metric *sub_metric; struct metric_value value; if (!stats_metric_group_by_get_value(field, &metric->group_by[0], &value)) return; if (!array_is_created(&metric->sub_metrics)) p_array_init(&metric->sub_metrics, pool, 8); sub_metric = stats_metric_get_sub_metric(metric, field, &value, pool); /* sub-metrics are recursive, so each sub-metric can have additional sub-metrics. */ stats_metric_event(sub_metric, event, pool); } static void stats_event_get_strlist(struct event *event, const char *name, ARRAY_TYPE(const_string) *strings) { if (event == NULL) return; const struct event_field *field = event_find_field_nonrecursive(event, name); if (field != NULL) { const char *str; array_foreach_elem(&field->value.strlist, str) array_push_back(strings, &str); } stats_event_get_strlist(event_get_parent(event), name, strings); } static void stats_metric_group_by(struct metric *metric, struct event *event, pool_t pool) { const struct event_field *field = event_find_field_recursive(event, metric->group_by[0].field); /* ignore missing field */ if (field == NULL) return; if (field->value_type != EVENT_FIELD_VALUE_TYPE_STRLIST) stats_metric_group_by_field(metric, event, field, pool); else { /* Handle each string in strlist separately. The strlist needs to be combined from the event and its parents, as well as the global event and its parents. */ ARRAY_TYPE(const_string) strings; t_array_init(&strings, 8); stats_event_get_strlist(event, metric->group_by[0].field, &strings); stats_event_get_strlist(event_get_global(), metric->group_by[0].field, &strings); struct event_field str_field = { .value_type = EVENT_FIELD_VALUE_TYPE_STR, }; const char *str; /* sort strings so duplicates can be easily skipped */ array_sort(&strings, i_strcmp_p); array_foreach_elem(&strings, str) { if (str_field.value.str == NULL || strcmp(str_field.value.str, str) != 0) { str_field.value.str = str; stats_metric_group_by_field(metric, event, &str_field, pool); } } } } static void stats_metric_event_field(struct event *event, const char *fieldname, struct stats_dist *stats) { const struct event_field *field = event_find_field_recursive(event, fieldname); intmax_t num = 0; if (field == NULL) return; switch (field->value_type) { case EVENT_FIELD_VALUE_TYPE_STR: case EVENT_FIELD_VALUE_TYPE_STRLIST: break; case EVENT_FIELD_VALUE_TYPE_INTMAX: num = field->value.intmax; break; case EVENT_FIELD_VALUE_TYPE_TIMEVAL: num = field->value.timeval.tv_sec * 1000000ULL + field->value.timeval.tv_usec; break; } stats_dist_add(stats, num); } static void stats_metric_event(struct metric *metric, struct event *event, pool_t pool) { /* duration is special - we always add it */ stats_metric_event_field(event, STATS_EVENT_FIELD_NAME_DURATION, metric->duration_stats); for (unsigned int i = 0; i < metric->fields_count; i++) stats_metric_event_field(event, metric->fields[i].field_key, metric->fields[i].stats); if (metric->group_by != NULL) stats_metric_group_by(metric, event, pool); } static void stats_export_event(struct metric *metric, struct event *oldevent) { const struct metric_export_info *info = &metric->export_info; const struct exporter *exporter = info->exporter; struct event *event; i_assert(exporter != NULL); event = event_flatten(oldevent); T_BEGIN { buffer_t *buf; buf = t_buffer_create(128); exporter->format(metric, event, buf); exporter->transport(exporter, buf); } T_END; event_unref(&event); } void stats_metrics_event(struct stats_metrics *metrics, struct event *event, const struct failure_context *ctx) { struct event_filter_match_iter *iter; struct metric *metric; uintmax_t duration; /* Note: Adding the field here means that it will get exported below. This is necessary to allow group-by functions to quantize based on the event duration. */ event_get_last_duration(event, &duration); event_add_int(event, STATS_EVENT_FIELD_NAME_DURATION, duration); /* process stats & exports */ iter = event_filter_match_iter_init(metrics->filter, event, ctx); while ((metric = event_filter_match_iter_next(iter)) != NULL) T_BEGIN { /* every metric is fed into stats */ stats_metric_event(metric, event, metrics->pool); /* some metrics are exported */ if (metric->export_info.exporter != NULL) stats_export_event(metric, event); } T_END; event_filter_match_iter_deinit(&iter); } struct stats_metrics_iter { struct stats_metrics *metrics; unsigned int idx; }; struct stats_metrics_iter * stats_metrics_iterate_init(struct stats_metrics *metrics) { struct stats_metrics_iter *iter; iter = i_new(struct stats_metrics_iter, 1); iter->metrics = metrics; return iter; } const struct metric *stats_metrics_iterate(struct stats_metrics_iter *iter) { struct metric *const *metrics; unsigned int count; metrics = array_get(&iter->metrics->metrics, &count); if (iter->idx >= count) return NULL; return metrics[iter->idx++]; } void stats_metrics_iterate_deinit(struct stats_metrics_iter **_iter) { struct stats_metrics_iter *iter = *_iter; *_iter = NULL; i_free(iter); } dovecot-2.3.21.1/src/stats/event-exporter-transport-http-post.c0000644000000000000000000000416014656633576021370 00000000000000/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "str.h" #include "event-exporter.h" #include "http-client.h" #include "iostream-ssl.h" #include "master-service.h" #include "master-service-ssl-settings.h" /* the http client used to export all events with exporter=http-post */ static struct http_client *exporter_http_client; void event_export_transport_http_post_deinit(void) { if (exporter_http_client != NULL) http_client_deinit(&exporter_http_client); } static void response_fxn(const struct http_response *response, void *context ATTR_UNUSED) { static time_t last_log; static unsigned suppressed; if (http_response_is_success(response)) return; if (last_log == ioloop_time) { suppressed++; return; /* don't spam the log */ } if (suppressed == 0) i_error("Failed to export event via HTTP POST: %d %s", response->status, response->reason); else i_error("Failed to export event via HTTP POST: %d %s (%u more errors suppressed)", response->status, response->reason, suppressed); last_log = ioloop_time; suppressed = 0; } void event_export_transport_http_post(const struct exporter *exporter, const buffer_t *buf) { struct http_client_request *req; if (exporter_http_client == NULL) { const struct master_service_ssl_settings *master_ssl_set = master_service_ssl_settings_get(master_service); struct ssl_iostream_settings ssl_set; struct http_client_settings set = { .dns_client_socket_path = "dns-client", }; if (master_ssl_set != NULL) { master_service_ssl_client_settings_to_iostream_set( master_ssl_set, pool_datastack_create(), &ssl_set); set.ssl = &ssl_set; } exporter_http_client = http_client_init(&set); } req = http_client_request_url_str(exporter_http_client, "POST", exporter->transport_args, response_fxn, NULL); http_client_request_add_header(req, "Content-Type", exporter->format_mime_type); http_client_request_set_payload_data(req, buf->data, buf->used); http_client_request_set_timeout_msecs(req, exporter->transport_timeout); http_client_request_submit(req); } dovecot-2.3.21.1/src/stats/event-exporter-transport-log.c0000644000000000000000000000050014656633576020201 00000000000000/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "str.h" #include "event-exporter.h" void event_export_transport_log(const struct exporter *exporter ATTR_UNUSED, const buffer_t *buf) { i_info("%.*s", (int)buf->used, (const char *)buf->data); } dovecot-2.3.21.1/src/stats/client-http.h0000644000000000000000000000151214656633576014645 00000000000000#ifndef CLIENT_HTTP_H #define CLIENT_HTTP_H struct master_service_connection; struct http_server_request; typedef void (stats_http_resource_callback_t)(void *context, struct http_server_request *req, const char *sub_path); void client_http_create(struct master_service_connection *conn); void stats_http_resource_add(const char *path, const char *title, stats_http_resource_callback_t *callback, void *context); #define stats_http_resource_add(path, title, callback, context) \ stats_http_resource_add(path, title, \ (stats_http_resource_callback_t *)callback, \ (TRUE ? context : \ CALLBACK_TYPECHECK(callback, void (*)( \ typeof(context), struct http_server_request *req, \ const char *sub_path)))) void client_http_init(const struct stats_settings *set); void client_http_deinit(void); #endif dovecot-2.3.21.1/src/stats/client-writer.h0000644000000000000000000000034414656633576015204 00000000000000#ifndef CLIENT_WRITER_H #define CLIENT_WRITER_H struct stats_metrics; void client_writer_create(int fd); void client_writer_update_connections(void); void client_writers_init(void); void client_writers_deinit(void); #endif dovecot-2.3.21.1/src/stats/stats-service.h0000644000000000000000000000017314656633576015210 00000000000000#ifndef STATS_SERVICE_H #define STATS_SERVICE_H void stats_services_init(void); void stats_services_deinit(void); #endif dovecot-2.3.21.1/src/stats/stats-settings.c0000644000000000000000000003512714656633576015412 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "stats-common.h" #include "buffer.h" #include "settings-parser.h" #include "service-settings.h" #include "stats-settings.h" #include "array.h" /* */ #include "event-filter.h" #include /* */ static bool stats_metric_settings_check(void *_set, pool_t pool, const char **error_r); static bool stats_exporter_settings_check(void *_set, pool_t pool, const char **error_r); static bool stats_settings_check(void *_set, pool_t pool, const char **error_r); /* */ static struct file_listener_settings stats_unix_listeners_array[] = { { "stats-reader", 0600, "", "" }, { "stats-writer", 0660, "", "$default_internal_group" }, { "login/stats-writer", 0600, "$default_login_user", "" }, }; static struct file_listener_settings *stats_unix_listeners[] = { &stats_unix_listeners_array[0], &stats_unix_listeners_array[1], &stats_unix_listeners_array[2], }; static buffer_t stats_unix_listeners_buf = { { { stats_unix_listeners, sizeof(stats_unix_listeners) } } }; /* */ struct service_settings stats_service_settings = { .name = "stats", .protocol = "", .type = "", .executable = "stats", .user = "$default_internal_user", .group = "", .privileged_group = "", .extra_groups = "", .chroot = "", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 1, .client_limit = 0, .service_count = 0, .idle_kill = UINT_MAX, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &stats_unix_listeners_buf, sizeof(stats_unix_listeners[0]) } }, .inet_listeners = ARRAY_INIT, }; /* * event_exporter { } block settings */ #undef DEF #define DEF(type, name) \ SETTING_DEFINE_STRUCT_##type(#name, name, struct stats_exporter_settings) static const struct setting_define stats_exporter_setting_defines[] = { DEF(STR, name), DEF(STR, transport), DEF(STR, transport_args), DEF(TIME_MSECS, transport_timeout), DEF(STR, format), DEF(STR, format_args), SETTING_DEFINE_LIST_END }; static const struct stats_exporter_settings stats_exporter_default_settings = { .name = "", .transport = "", .transport_args = "", .transport_timeout = 250, /* ms */ .format = "", .format_args = "", }; const struct setting_parser_info stats_exporter_setting_parser_info = { .defines = stats_exporter_setting_defines, .defaults = &stats_exporter_default_settings, .type_offset = offsetof(struct stats_exporter_settings, name), .struct_size = sizeof(struct stats_exporter_settings), .parent_offset = SIZE_MAX, .check_func = stats_exporter_settings_check, }; /* * metric { } block settings */ #undef DEF #define DEF(type, name) \ SETTING_DEFINE_STRUCT_##type(#name, name, struct stats_metric_settings) static const struct setting_define stats_metric_setting_defines[] = { DEF(STR, metric_name), DEF(STR, fields), DEF(STR, group_by), DEF(STR, filter), DEF(STR, exporter), DEF(STR, exporter_include), DEF(STR, description), SETTING_DEFINE_LIST_END }; static const struct stats_metric_settings stats_metric_default_settings = { .metric_name = "", .fields = "", .filter = "", .exporter = "", .group_by = "", .exporter_include = STATS_METRIC_SETTINGS_DEFAULT_EXPORTER_INCLUDE, .description = "", }; const struct setting_parser_info stats_metric_setting_parser_info = { .defines = stats_metric_setting_defines, .defaults = &stats_metric_default_settings, .type_offset = offsetof(struct stats_metric_settings, metric_name), .struct_size = sizeof(struct stats_metric_settings), .parent_offset = SIZE_MAX, .check_func = stats_metric_settings_check, }; /* * top-level settings */ #undef DEF #define DEF(type, name) \ SETTING_DEFINE_STRUCT_##type(#name, name, struct stats_settings) #undef DEFLIST_UNIQUE #define DEFLIST_UNIQUE(field, name, defines) \ { .type = SET_DEFLIST_UNIQUE, .key = name, \ .offset = offsetof(struct stats_settings, field), \ .list_info = defines } static const struct setting_define stats_setting_defines[] = { DEF(STR, stats_http_rawlog_dir), DEFLIST_UNIQUE(metrics, "metric", &stats_metric_setting_parser_info), DEFLIST_UNIQUE(exporters, "event_exporter", &stats_exporter_setting_parser_info), SETTING_DEFINE_LIST_END }; const struct stats_settings stats_default_settings = { .stats_http_rawlog_dir = "", .metrics = ARRAY_INIT, .exporters = ARRAY_INIT, }; const struct setting_parser_info stats_setting_parser_info = { .module_name = "stats", .defines = stats_setting_defines, .defaults = &stats_default_settings, .type_offset = SIZE_MAX, .struct_size = sizeof(struct stats_settings), .parent_offset = SIZE_MAX, .check_func = stats_settings_check, }; /* */ static bool parse_format_args_set_time(struct stats_exporter_settings *set, enum event_exporter_time_fmt fmt, const char **error_r) { if ((set->parsed_time_format != EVENT_EXPORTER_TIME_FMT_NATIVE) && (set->parsed_time_format != fmt)) { *error_r = t_strdup_printf("Exporter '%s' specifies multiple " "time format args", set->name); return FALSE; } set->parsed_time_format = fmt; return TRUE; } static bool parse_format_args(struct stats_exporter_settings *set, const char **error_r) { const char *const *tmp; /* Defaults */ set->parsed_time_format = EVENT_EXPORTER_TIME_FMT_NATIVE; tmp = t_strsplit_spaces(set->format_args, " "); /* * If the config contains multiple types of the same type (e.g., * both time-rfc3339 and time-unix) we fail the config check. * * Note: At the moment, we have only time-* tokens. In the future * when we have other tokens, they should be parsed here. */ for (; *tmp != NULL; tmp++) { enum event_exporter_time_fmt fmt; if (strcmp(*tmp, "time-rfc3339") == 0) { fmt = EVENT_EXPORTER_TIME_FMT_RFC3339; } else if (strcmp(*tmp, "time-unix") == 0) { fmt = EVENT_EXPORTER_TIME_FMT_UNIX; } else { *error_r = t_strdup_printf("Unknown exporter format " "arg: %s", *tmp); return FALSE; } if (!parse_format_args_set_time(set, fmt, error_r)) return FALSE; } return TRUE; } static bool stats_exporter_settings_check(void *_set, pool_t pool ATTR_UNUSED, const char **error_r) { struct stats_exporter_settings *set = _set; bool time_fmt_required; if (set->name[0] == '\0') { *error_r = "Exporter name can't be empty"; return FALSE; } /* TODO: The following should be plugable. * * Note: Make sure to mirror any changes to the below code in * stats_exporters_add_set(). */ if (set->format[0] == '\0') { *error_r = "Exporter format name can't be empty"; return FALSE; } else if (strcmp(set->format, "none") == 0) { time_fmt_required = FALSE; } else if (strcmp(set->format, "json") == 0) { time_fmt_required = TRUE; } else if (strcmp(set->format, "tab-text") == 0) { time_fmt_required = TRUE; } else { *error_r = t_strdup_printf("Unknown exporter format '%s'", set->format); return FALSE; } /* TODO: The following should be plugable. * * Note: Make sure to mirror any changes to the below code in * stats_exporters_add_set(). */ if (set->transport[0] == '\0') { *error_r = "Exporter transport name can't be empty"; return FALSE; } else if (strcmp(set->transport, "drop") == 0 || strcmp(set->transport, "http-post") == 0 || strcmp(set->transport, "log") == 0) { /* no-op */ } else { *error_r = t_strdup_printf("Unknown transport type '%s'", set->transport); return FALSE; } if (!parse_format_args(set, error_r)) return FALSE; /* Some formats don't have a native way of serializing time stamps */ if (time_fmt_required && set->parsed_time_format == EVENT_EXPORTER_TIME_FMT_NATIVE) { *error_r = t_strdup_printf("%s exporter format requires a " "time-* argument", set->format); return FALSE; } return TRUE; } static bool parse_metric_group_by_common(const char *func, const char *const *params, intmax_t *min_r, intmax_t *max_r, intmax_t *other_r, const char **error_r) { intmax_t min, max, other; if ((str_array_length(params) != 3) || (str_to_intmax(params[0], &min) < 0) || (str_to_intmax(params[1], &max) < 0) || (str_to_intmax(params[2], &other) < 0)) { *error_r = t_strdup_printf("group_by '%s' aggregate function takes " "3 int args", func); return FALSE; } if ((min < 0) || (max < 0) || (other < 0)) { *error_r = t_strdup_printf("group_by '%s' aggregate function " "arguments must be >= 0", func); return FALSE; } if (min >= max) { *error_r = t_strdup_printf("group_by '%s' aggregate function " "min must be < max (%ju must be < %ju)", func, min, max); return FALSE; } *min_r = min; *max_r = max; *other_r = other; return TRUE; } static bool parse_metric_group_by_exp(pool_t pool, struct stats_metric_settings_group_by *group_by, const char *const *params, const char **error_r) { intmax_t min, max, base; if (!parse_metric_group_by_common("exponential", params, &min, &max, &base, error_r)) return FALSE; if ((base != 2) && (base != 10)) { *error_r = t_strdup_printf("group_by 'exponential' aggregate function " "base must be one of: 2, 10 (base=%ju)", base); return FALSE; } group_by->func = STATS_METRIC_GROUPBY_QUANTIZED; /* * Allocate the bucket range array and fill it in * * The first bucket is special - it contains everything less than or * equal to 'base^min'. The last bucket is also special - it * contains everything greater than 'base^max'. * * The second bucket begins at 'base^min + 1', the third bucket * begins at 'base^(min + 1) + 1', and so on. */ group_by->num_ranges = max - min + 2; group_by->ranges = p_new(pool, struct stats_metric_settings_bucket_range, group_by->num_ranges); /* set up min & max buckets */ group_by->ranges[0].min = INTMAX_MIN; group_by->ranges[0].max = pow(base, min); group_by->ranges[group_by->num_ranges - 1].min = pow(base, max); group_by->ranges[group_by->num_ranges - 1].max = INTMAX_MAX; /* remaining buckets */ for (unsigned int i = 1; i < group_by->num_ranges - 1; i++) { group_by->ranges[i].min = pow(base, min + (i - 1)); group_by->ranges[i].max = pow(base, min + i); } return TRUE; } static bool parse_metric_group_by_lin(pool_t pool, struct stats_metric_settings_group_by *group_by, const char *const *params, const char **error_r) { intmax_t min, max, step; if (!parse_metric_group_by_common("linear", params, &min, &max, &step, error_r)) return FALSE; if ((min + step) > max) { *error_r = t_strdup_printf("group_by 'linear' aggregate function " "min+step must be <= max (%ju must be <= %ju)", min + step, max); return FALSE; } group_by->func = STATS_METRIC_GROUPBY_QUANTIZED; /* * Allocate the bucket range array and fill it in * * The first bucket is special - it contains everything less than or * equal to 'min'. The last bucket is also special - it contains * everything greater than 'max'. * * The second bucket begins at 'min + 1', the third bucket begins at * 'min + 1 * step + 1', the fourth at 'min + 2 * step + 1', and so on. */ group_by->num_ranges = (max - min) / step + 2; group_by->ranges = p_new(pool, struct stats_metric_settings_bucket_range, group_by->num_ranges); /* set up min & max buckets */ group_by->ranges[0].min = INTMAX_MIN; group_by->ranges[0].max = min; group_by->ranges[group_by->num_ranges - 1].min = max; group_by->ranges[group_by->num_ranges - 1].max = INTMAX_MAX; /* remaining buckets */ for (unsigned int i = 1; i < group_by->num_ranges - 1; i++) { group_by->ranges[i].min = min + (i - 1) * step; group_by->ranges[i].max = min + i * step; } return TRUE; } static bool parse_metric_group_by(struct stats_metric_settings *set, pool_t pool, const char **error_r) { const char *const *tmp = t_strsplit_spaces(set->group_by, " "); if (tmp[0] == NULL) return TRUE; p_array_init(&set->parsed_group_by, pool, str_array_length(tmp)); /* For each group_by field */ for (; *tmp != NULL; tmp++) { struct stats_metric_settings_group_by group_by; const char *const *params; i_zero(&group_by); /* :... */ params = t_strsplit(*tmp, ":"); if (params[1] == NULL) { /* - alias for :discrete */ group_by.func = STATS_METRIC_GROUPBY_DISCRETE; } else if (strcmp(params[1], "discrete") == 0) { /* :discrete */ group_by.func = STATS_METRIC_GROUPBY_DISCRETE; if (params[2] != NULL) { *error_r = "group_by 'discrete' aggregate function " "does not take any args"; return FALSE; } } else if (strcmp(params[1], "exponential") == 0) { /* :exponential::: */ if (!parse_metric_group_by_exp(pool, &group_by, ¶ms[2], error_r)) return FALSE; } else if (strcmp(params[1], "linear") == 0) { /* :linear::: */ if (!parse_metric_group_by_lin(pool, &group_by, ¶ms[2], error_r)) return FALSE; } else { *error_r = t_strdup_printf("unknown aggregation function " "'%s' on field '%s'", params[1], params[0]); return FALSE; } group_by.field = p_strdup(pool, params[0]); array_push_back(&set->parsed_group_by, &group_by); } return TRUE; } static bool stats_metric_settings_check(void *_set, pool_t pool, const char **error_r) { struct stats_metric_settings *set = _set; if (set->metric_name[0] == '\0') { *error_r = "Metric name can't be empty"; return FALSE; } if (set->filter[0] == '\0') { *error_r = t_strdup_printf("metric %s { filter } is empty - " "will not match anything", set->metric_name); return FALSE; } set->parsed_filter = event_filter_create_fragment(pool); if (event_filter_parse(set->filter, set->parsed_filter, error_r) < 0) return FALSE; if (!parse_metric_group_by(set, pool, error_r)) return FALSE; return TRUE; } static bool stats_settings_check(void *_set, pool_t pool ATTR_UNUSED, const char **error_r) { struct stats_settings *set = _set; struct stats_exporter_settings *exporter; struct stats_metric_settings *metric; if (!array_is_created(&set->metrics) || !array_is_created(&set->exporters)) return TRUE; /* check that all metrics refer to exporters that exist */ array_foreach_elem(&set->metrics, metric) { bool found = FALSE; if (metric->exporter[0] == '\0') continue; /* metric not exported */ array_foreach_elem(&set->exporters, exporter) { if (strcmp(metric->exporter, exporter->name) == 0) { found = TRUE; break; } } if (!found) { *error_r = t_strdup_printf("metric %s refers to " "non-existent exporter '%s'", metric->metric_name, metric->exporter); return FALSE; } } return TRUE; } /* */ dovecot-2.3.21.1/src/stats/stats-service.c0000644000000000000000000000044014656633576015200 00000000000000/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ #include "stats-common.h" #include "http-server.h" #include "stats-service-private.h" void stats_services_init(void) { stats_service_openmetrics_init(); } void stats_services_deinit(void) { /* Nothing yet */ } dovecot-2.3.21.1/src/stats/stats-common.h0000644000000000000000000000026314656633576015040 00000000000000#ifndef STATS_COMMON_H #define STATS_COMMON_H #include "lib.h" #include "stats-settings.h" extern struct stats_metrics *stats_metrics; extern time_t stats_startup_time; #endif dovecot-2.3.21.1/src/stats/event-exporter.h0000644000000000000000000000273114656633576015405 00000000000000#ifndef EVENT_EXPORTER_H #define EVENT_EXPORTER_H #include "stats-metrics.h" /* fmt functions */ void event_export_fmt_json(const struct metric *metric, struct event *event, buffer_t *dest); void event_export_fmt_none(const struct metric *metric, struct event *event, buffer_t *dest); void event_export_fmt_tabescaped_text(const struct metric *metric, struct event *event, buffer_t *dest); /* transport functions */ void event_export_transport_drop(const struct exporter *exporter, const buffer_t *buf); void event_export_transport_http_post(const struct exporter *exporter, const buffer_t *buf); void event_export_transport_http_post_deinit(void); void event_export_transport_log(const struct exporter *exporter, const buffer_t *buf); /* append a microsecond resolution RFC3339 UTC timestamp */ void event_export_helper_fmt_rfc3339_time(string_t *dest, const struct timeval *time); /* append a microsecond resolution unix timestamp in seconds (i.e., %u.%06u) */ void event_export_helper_fmt_unix_time(string_t *dest, const struct timeval *time); /* append category names using 'append' function pointer, separated by 'separator' arg The result has no duplicates regardless of if the array has any or if any of the categories' ancestors are implictly or explicitly duplicated. */ void event_export_helper_fmt_categories(string_t *dest, struct event_category *const *cats, unsigned int count, void (*append)(string_t *, const char *), const char *separator); #endif dovecot-2.3.21.1/src/stats/client-writer.c0000644000000000000000000002340314656633576015200 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "stats-common.h" #include "array.h" #include "llist.h" #include "hash.h" #include "str.h" #include "strescape.h" #include "lib-event-private.h" #include "event-filter.h" #include "ostream.h" #include "connection.h" #include "master-service.h" #include "stats-event-category.h" #include "stats-metrics.h" #include "stats-settings.h" #include "client-writer.h" #define STATS_UPDATE_CLIENTS_DELAY_MSECS 1000 struct stats_event { struct stats_event *prev, *next; uint64_t id; struct event *event; }; struct writer_client { struct connection conn; struct stats_event *events; HASH_TABLE(struct stats_event *, struct stats_event *) events_hash; }; static struct timeout *to_update_clients; static struct connection_list *writer_clients = NULL; static void client_writer_send_handshake(struct writer_client *client) { string_t *filter = t_str_new(128); string_t *str = t_str_new(128); event_filter_export(stats_metrics_get_event_filter(stats_metrics), filter); str_append(str, "FILTER\t"); str_append_tabescaped(str, str_c(filter)); str_append_c(str, '\n'); o_stream_nsend(client->conn.output, str_data(str), str_len(str)); } static unsigned int stats_event_hash(const struct stats_event *event) { return (unsigned int)event->id; } static int stats_event_cmp(const struct stats_event *event1, const struct stats_event *event2) { return event1->id == event2->id ? 0 : 1; } void client_writer_create(int fd) { struct writer_client *client; client = i_new(struct writer_client, 1); hash_table_create(&client->events_hash, default_pool, 0, stats_event_hash, stats_event_cmp); connection_init_server(writer_clients, &client->conn, "stats", fd, fd); client_writer_send_handshake(client); } static void writer_client_destroy(struct connection *conn) { struct writer_client *client = (struct writer_client *)conn; struct stats_event *event, *next; for (event = client->events; event != NULL; event = next) { next = event->next; event_unref(&event->event); i_free(event); } hash_table_destroy(&client->events_hash); connection_deinit(conn); i_free(conn); master_service_client_connection_destroyed(master_service); } static struct stats_event * writer_client_find_event(struct writer_client *client, uint64_t event_id) { struct stats_event lookup_event = { .id = event_id }; return hash_table_lookup(client->events_hash, &lookup_event); } static bool writer_client_run_event(struct writer_client *client, uint64_t parent_event_id, const char *const *args, struct event **event_r, const char **error_r) { struct event *parent_event; unsigned int log_type; if (parent_event_id == 0) parent_event = NULL; else { struct stats_event *stats_parent_event = writer_client_find_event(client, parent_event_id); if (stats_parent_event == NULL) { *error_r = "Unknown parent event ID"; return FALSE; } parent_event = stats_parent_event->event; } if (args[0] == NULL || str_to_uint(args[0], &log_type) < 0 || log_type >= LOG_TYPE_COUNT) { *error_r = "Invalid log type"; return FALSE; } const struct failure_context ctx = { .type = (enum log_type)log_type }; args++; struct event *event = event_create(parent_event); if (!event_import_unescaped(event, args, error_r)) { event_unref(&event); return FALSE; } stats_metrics_event(stats_metrics, event, &ctx); *event_r = event; return TRUE; } static bool writer_client_input_event(struct writer_client *client, const char *const *args, const char **error_r) { struct event *event, *global_event = NULL; uint64_t parent_event_id, global_event_id; bool ret; if (args[1] == NULL || str_to_uint64(args[0], &global_event_id) < 0) { *error_r = "Invalid global event ID"; return FALSE; } if (args[1] == NULL || str_to_uint64(args[1], &parent_event_id) < 0) { *error_r = "Invalid parent ID"; return FALSE; } if (global_event_id != 0) { struct stats_event *stats_global_event = writer_client_find_event(client, global_event_id); if (stats_global_event == NULL) { *error_r = "Unknown global event ID"; return FALSE; } global_event = stats_global_event->event; event_push_global(global_event); } ret = writer_client_run_event(client, parent_event_id, args+2, &event, error_r); if (global_event != NULL) event_pop_global(global_event); if (!ret) return FALSE; event_unref(&event); return TRUE; } static bool writer_client_input_event_begin(struct writer_client *client, const char *const *args, const char **error_r) { struct event *event; struct stats_event *stats_event; uint64_t event_id, parent_event_id; if (args[0] == NULL || args[1] == NULL || str_to_uint64(args[0], &event_id) < 0 || str_to_uint64(args[1], &parent_event_id) < 0) { *error_r = "Invalid event IDs"; return FALSE; } if (writer_client_find_event(client, event_id) != NULL) { *error_r = "Duplicate event ID"; return FALSE; } if (!writer_client_run_event(client, parent_event_id, args+2, &event, error_r)) return FALSE; stats_event = i_new(struct stats_event, 1); stats_event->id = event_id; stats_event->event = event; DLLIST_PREPEND(&client->events, stats_event); hash_table_insert(client->events_hash, stats_event, stats_event); return TRUE; } static bool writer_client_input_event_update(struct writer_client *client, const char *const *args, const char **error_r) { struct stats_event *stats_event, *parent_stats_event; struct event *parent_event; uint64_t event_id, parent_event_id; if (args[0] == NULL || args[1] == NULL || str_to_uint64(args[0], &event_id) < 0 || str_to_uint64(args[1], &parent_event_id) < 0) { *error_r = "Invalid event IDs"; return FALSE; } stats_event = writer_client_find_event(client, event_id); if (stats_event == NULL) { *error_r = "Unknown event ID"; return FALSE; } parent_stats_event = parent_event_id == 0 ? NULL : writer_client_find_event(client, parent_event_id); parent_event = parent_stats_event == NULL ? NULL : parent_stats_event->event; if (stats_event->event->parent != parent_event) { *error_r = "Event unexpectedly changed parent"; return FALSE; } return event_import_unescaped(stats_event->event, args+2, error_r); } static bool writer_client_input_event_end(struct writer_client *client, const char *const *args, const char **error_r) { struct stats_event *stats_event; uint64_t event_id; if (args[0] == NULL || str_to_uint64(args[0], &event_id) < 0) { *error_r = "Invalid event ID"; return FALSE; } stats_event = writer_client_find_event(client, event_id); if (stats_event == NULL) { *error_r = "Unknown event ID"; return FALSE; } DLLIST_REMOVE(&client->events, stats_event); hash_table_remove(client->events_hash, stats_event); event_unref(&stats_event->event); i_free(stats_event); return TRUE; } static bool writer_client_input_category(struct writer_client *client ATTR_UNUSED, const char *const *args, const char **error_r) { struct event_category *category, *parent; if (args[0] == NULL) { *error_r = "Missing category name"; return FALSE; } if (args[1] == NULL) parent = NULL; else if ((parent = event_category_find_registered(args[1])) == NULL) { *error_r = "Unknown parent category"; return FALSE; } category = event_category_find_registered(args[0]); if (category == NULL) { /* new category - create */ stats_event_category_register(args[0], parent); } else if (category->parent != parent) { *error_r = t_strdup_printf( "Category parent '%s' changed to '%s'", category->parent == NULL ? "" : category->parent->name, parent == NULL ? "" : parent->name); return FALSE; } else { /* duplicate - ignore */ return TRUE; } return TRUE; } static int writer_client_input_args(struct connection *conn, const char *const *args) { struct writer_client *client = (struct writer_client *)conn; const char *error, *cmd = args[0]; bool ret; if (cmd == NULL) { i_error("Client sent empty line"); return 1; } if (strcmp(cmd, "EVENT") == 0) ret = writer_client_input_event(client, args+1, &error); else if (strcmp(cmd, "BEGIN") == 0) ret = writer_client_input_event_begin(client, args+1, &error); else if (strcmp(cmd, "UPDATE") == 0) ret = writer_client_input_event_update(client, args+1, &error); else if (strcmp(cmd, "END") == 0) ret = writer_client_input_event_end(client, args+1, &error); else if (strcmp(cmd, "CATEGORY") == 0) ret = writer_client_input_category(client, args+1, &error); else { error = "Unknown command"; ret = FALSE; } if (!ret) { i_error("Client sent invalid input for %s: %s (input: %s)", cmd, error, t_strarray_join(args, "\t")); return -1; } return 1; } static struct connection_settings client_set = { .service_name_in = "stats-client", .service_name_out = "stats-server", .major_version = 4, .minor_version = 0, .input_max_size = 1024*128, /* "big enough" */ .output_max_size = SIZE_MAX, .client = FALSE, }; static const struct connection_vfuncs client_vfuncs = { .destroy = writer_client_destroy, .input_args = writer_client_input_args, }; static void client_writer_update_connections_internal(void *context ATTR_UNUSED) { struct connection *conn; for (conn = writer_clients->connections; conn != NULL; conn = conn->next) { struct writer_client *client = container_of(conn, struct writer_client, conn); client_writer_send_handshake(client); } timeout_remove(&to_update_clients); } void client_writer_update_connections(void) { if (to_update_clients != NULL) return; to_update_clients = timeout_add(STATS_UPDATE_CLIENTS_DELAY_MSECS, client_writer_update_connections_internal, NULL); } void client_writers_init(void) { writer_clients = connection_list_init(&client_set, &client_vfuncs); } void client_writers_deinit(void) { timeout_remove(&to_update_clients); connection_list_deinit(&writer_clients); } dovecot-2.3.21.1/src/stats/test-stats-common.c0000644000000000000000000000522014656633576016006 00000000000000/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ #include "test-stats-common.h" #include #include struct event_category test_category = { .name = "test", }; struct event_category child_test_category = { .name = "child", .parent = &test_category, }; pool_t test_pool; struct stats_metrics *stats_metrics = NULL; time_t stats_startup_time; static bool callback_added = FALSE; static struct stats_settings *read_settings(const char *settings) { struct istream *is = test_istream_create(settings); const char *error; struct setting_parser_context *ctx = settings_parser_init(test_pool, &stats_setting_parser_info, 0); if (settings_parse_stream_read(ctx, is) < 0) i_fatal("Failed to parse settings: %s", settings_parser_get_error(ctx)); if (!settings_parser_check(ctx, test_pool, &error)) i_fatal("Failed to parse settings: %s", error); struct stats_settings *set = settings_parser_get(ctx); settings_parser_deinit(&ctx); i_stream_unref(&is); return set; } void test_init(const char *settings_blob) { if (!callback_added) { event_register_callback(test_stats_callback); callback_added = TRUE; } stats_event_categories_init(); test_pool = pool_alloconly_create(MEMPOOL_GROWING"test pool", 2048); stats_startup_time = time(NULL); /* register test categories */ stats_event_category_register(test_category.name, NULL); stats_event_category_register(child_test_category.name, &test_category); struct stats_settings *set = read_settings(settings_blob); stats_metrics = stats_metrics_init(set); } void test_deinit(void) { stats_metrics_deinit(&stats_metrics); stats_event_categories_deinit(); pool_unref(&test_pool); } void test_event_send(struct event *event) { struct failure_context ctx = { .type = LOG_TYPE_DEBUG, }; usleep(1); /* make sure duration>0 always */ event_send(event, &ctx, "hello"); } uint64_t get_stats_dist_field(const char *metric_name, enum stats_dist_field field) { struct stats_metrics_iter *iter = stats_metrics_iterate_init(stats_metrics); const struct metric *metric; while((metric = stats_metrics_iterate(iter)) != NULL) if (strcmp(metric->name, metric_name) == 0) break; /* bug in test if not found */ i_assert(metric != NULL); stats_metrics_iterate_deinit(&iter); switch(field) { case STATS_DIST_COUNT: return stats_dist_get_count(metric->duration_stats); case STATS_DIST_SUM: return stats_dist_get_sum(metric->duration_stats); default: i_unreached(); } } dovecot-2.3.21.1/src/stats/main.c0000644000000000000000000000550014656633576013332 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "stats-common.h" #include "restrict-access.h" #include "ioloop.h" #include "master-service.h" #include "master-service-settings.h" #include "stats-settings.h" #include "stats-event-category.h" #include "stats-metrics.h" #include "stats-service.h" #include "client-writer.h" #include "client-reader.h" #include "client-http.h" struct stats_metrics *stats_metrics; time_t stats_startup_time; static const struct stats_settings *stats_settings; static bool client_is_writer(const char *path) { const char *name, *suffix; name = strrchr(path, '/'); if (name == NULL) name = path; else name++; suffix = strrchr(name, '-'); if (suffix == NULL) suffix = name; else suffix++; return strcmp(suffix, "writer") == 0; } static void client_connected(struct master_service_connection *conn) { if (strcmp(conn->name, "http") == 0) client_http_create(conn); else if (client_is_writer(conn->name)) client_writer_create(conn->fd); else client_reader_create(conn->fd); master_service_client_connection_accept(conn); } static void stats_die(void) { /* just wait for existing stats clients to disconnect from us */ } static void main_preinit(void) { restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL); restrict_access_allow_coredumps(TRUE); } static void main_init(void) { void **sets = master_service_settings_get_others(master_service); stats_settings = sets[0]; stats_startup_time = ioloop_time; stats_metrics = stats_metrics_init(stats_settings); stats_event_categories_init(); client_readers_init(); client_writers_init(); client_http_init(stats_settings); stats_services_init(); } static void main_deinit(void) { stats_services_deinit(); client_readers_deinit(); client_writers_deinit(); client_http_deinit(); stats_event_categories_deinit(); stats_metrics_deinit(&stats_metrics); } int main(int argc, char *argv[]) { const struct setting_parser_info *set_roots[] = { &stats_setting_parser_info, NULL }; const enum master_service_flags service_flags = MASTER_SERVICE_FLAG_NO_SSL_INIT | MASTER_SERVICE_FLAG_DONT_SEND_STATS | MASTER_SERVICE_FLAG_NO_IDLE_DIE | MASTER_SERVICE_FLAG_UPDATE_PROCTITLE; const char *error; master_service = master_service_init("stats", service_flags, &argc, &argv, ""); if (master_getopt(master_service) > 0) return FATAL_DEFAULT; if (master_service_settings_read_simple(master_service, set_roots, &error) < 0) i_fatal("Error reading configuration: %s", error); master_service_init_log(master_service); master_service_set_die_callback(master_service, stats_die); main_preinit(); main_init(); master_service_init_finish(master_service); master_service_run(master_service, client_connected); main_deinit(); master_service_deinit(&master_service); return 0; } dovecot-2.3.21.1/src/stats/Makefile.am0000644000000000000000000000442614656633576014304 00000000000000pkglibexecdir = $(libexecdir)/dovecot pkglibexec_PROGRAMS = stats noinst_LTLIBRARIES = libstats_local.la AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-http \ -I$(top_srcdir)/src/lib-ssl-iostream \ -I$(top_srcdir)/src/lib-test \ $(BINARY_CFLAGS) stats_LDADD = \ $(noinst_LTLIBRARIES) \ $(LIBDOVECOT) \ $(DOVECOT_SSL_LIBS) \ $(BINARY_LDFLAGS) \ -lm stats_DEPENDENCIES = \ $(noinst_LTLIBRARIES) \ $(DOVECOT_SSL_LIBS) \ $(LIBDOVECOT_DEPS) stats_services = \ stats-service-openmetrics.c stats_SOURCES = \ main.c libstats_local_la_SOURCES = \ client-reader.c \ client-writer.c \ client-http.c \ event-exporter-fmt.c \ event-exporter-fmt-json.c \ event-exporter-fmt-none.c \ event-exporter-fmt-tab-text.c \ event-exporter-transport-drop.c \ event-exporter-transport-http-post.c \ event-exporter-transport-log.c \ $(stats_services) \ stats-service.c \ stats-event-category.c \ stats-metrics.c \ stats-settings.c noinst_HEADERS = \ stats-common.h \ client-reader.h \ client-writer.h \ client-http.h\ event-exporter.h \ stats-service.h \ stats-service-private.h \ stats-event-category.h \ stats-metrics.h \ stats-settings.h \ test-stats-common.h test_libs = \ $(noinst_LTLIBRARIES) \ $(DOVECOT_SSL_LIBS) \ $(LIBDOVECOT) \ $(BINARY_LDFLAGS) \ -lm test_deps = \ $(noinst_LTLIBRARIES) \ $(DOVECOT_SSL_LIBS) \ $(LIBDOVECOT_DEPS) test_stats_metrics_SOURCES = test-stats-metrics.c test-stats-common.c test_stats_metrics_LDADD = $(test_libs) test_stats_metrics_DEPENDENCIES = $(test_deps) test_client_writer_SOURCES = test-client-writer.c test-stats-common.c test_client_writer_LDADD = $(test_libs) test_client_writer_DEPENDENCIES = $(test_deps) test_client_reader_SOURCES = test-client-reader.c test-stats-common.c test_client_reader_LDADD = $(test_libs) test_client_reader_DEPENDENCIES = $(test_deps) test_programs = test-stats-metrics test-client-writer test-client-reader noinst_PROGRAMS = $(test_programs) check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done LIBDOVECOT_TEST_DEPS = \ ../lib-ssl-iostream/libssl_iostream.la \ ../lib-test/libtest.la \ ../lib/liblib.la LIBDOVECOT_TEST = \ $(LIBDOVECOT_TEST_DEPS) \ $(MODULE_LIBS) dovecot-2.3.21.1/src/stats/stats-event-category.c0000644000000000000000000000160514656633576016500 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "stats-event-category.h" static pool_t categories_pool; void stats_event_category_register(const char *name, struct event_category *parent) { struct event_category *category = p_new(categories_pool, struct event_category, 1); category->parent = parent; category->name = p_strdup(categories_pool, name); /* Create a temporary event to register the category. A bit slower than necessary, but this code won't be called often. */ struct event *event = event_create(NULL); struct event_category *categories[] = { category, NULL }; event_add_categories(event, categories); event_unref(&event); } void stats_event_categories_init(void) { categories_pool = pool_alloconly_create("categories", 1024); } void stats_event_categories_deinit(void) { pool_unref(&categories_pool); } dovecot-2.3.21.1/src/stats/client-http.c0000644000000000000000000001274414656633576014651 00000000000000/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ #include "stats-common.h" #include "str.h" #include "array.h" #include "strescape.h" #include "connection.h" #include "ostream.h" #include "master-service.h" #include "http-server.h" #include "http-url.h" #include "stats-metrics.h" #include "stats-service.h" #include "client-http.h" struct stats_http_client; struct stats_http_client { struct http_server_connection *http_conn; }; struct stats_http_resource { pool_t pool; const char *title; struct http_server_resource *resource; stats_http_resource_callback_t *callback; void *context; }; static struct http_server *stats_http_server; static ARRAY(struct stats_http_resource *) stats_http_resources; /* * Request */ static void stats_http_server_handle_request(void *context ATTR_UNUSED, struct http_server_request *http_sreq) { http_server_request_fail(http_sreq, 404, "Path Not Found"); } /* * Connection */ static void stats_http_server_connection_destroy(void *context, const char *reason); static const struct http_server_callbacks stats_http_callbacks = { .connection_destroy = stats_http_server_connection_destroy, .handle_request = stats_http_server_handle_request }; void client_http_create(struct master_service_connection *conn) { struct stats_http_client *client; client = i_new(struct stats_http_client, 1); client->http_conn = http_server_connection_create( stats_http_server, conn->fd, conn->fd, conn->ssl, &stats_http_callbacks, client); } static void stats_http_client_destroy(struct stats_http_client *client) { i_free(client); master_service_client_connection_destroyed(master_service); } static void stats_http_server_connection_destroy(void *context, const char *reason ATTR_UNUSED) { struct stats_http_client *client = context; if (client->http_conn == NULL) { /* Already destroying client directly */ return; } /* HTTP connection is destroyed already now */ client->http_conn = NULL; /* Destroy the connection itself */ stats_http_client_destroy(client); } /* * Resources */ /* Registry */ static void stats_http_resource_callback(struct stats_http_resource *res, struct http_server_request *req, const char *sub_path) { res->callback(res->context, req, sub_path); } #undef stats_http_resource_add void stats_http_resource_add(const char *path, const char *title, stats_http_resource_callback_t *callback, void *context) { struct stats_http_resource *res; pool_t pool; pool = pool_alloconly_create("stats http resource", 2048); res = p_new(pool, struct stats_http_resource, 1); res->pool = pool; res->title = p_strdup(pool, title); res->callback = callback; res->context = context; res->resource = http_server_resource_create( stats_http_server, pool, stats_http_resource_callback, res); http_server_resource_add_location(res->resource, path); pool_unref(&pool); array_append(&stats_http_resources, &res, 1); } /* Root */ static void stats_http_resource_root_make_response(struct http_server_response *resp, const struct http_request *hreq) { struct stats_http_resource *res; struct http_url url; string_t *msg; http_url_init_authority_from(&url, hreq->target.url); msg = t_str_new(1024); str_append(msg, "\n"); str_append(msg, "\n"); str_append(msg, "\n"); str_append(msg, "\n"); str_append(msg, "\n"); str_append(msg, "Dovecot Stats\n"); str_append(msg, "\n"); str_append(msg, "\n"); str_append(msg, "\n"); str_append(msg, "

Dovecot Stats:

\n"); str_append(msg, "

    \n"); array_foreach_elem(&stats_http_resources, res) { if (res->title == NULL) continue; /* List the resource at its primary location. */ url.path = http_server_resource_get_path(res->resource); str_append(msg, "
  • "); str_append(msg, res->title); str_append(msg, "
  • \n"); } str_append(msg, "

\n"); str_append(msg, "\n"); str_append(msg, "\n"); str_append(msg, "\n"); http_server_response_set_payload_data( resp, str_data(msg), str_len(msg)); } static void stats_http_resource_root_request(void *context ATTR_UNUSED, struct http_server_request *req, const char *sub_path) { const struct http_request *hreq = http_server_request_get(req); struct http_server_response *resp; if (strcmp(hreq->method, "OPTIONS") == 0) { resp = http_server_response_create(req, 200, "OK"); http_server_response_add_header(resp, "Allow", "GET"); http_server_response_submit(resp); return; } if (strcmp(hreq->method, "GET") != 0) { http_server_request_fail_bad_method(req, "GET"); return; } if (*sub_path != '\0') { http_server_request_fail(req, 404, "Not Found"); return; } resp = http_server_response_create(req, 200, "OK"); http_server_response_add_header(resp, "Content-Type", "text/html; charset=utf-8"); stats_http_resource_root_make_response(resp, hreq); http_server_response_submit(resp); } /* * Server */ void client_http_init(const struct stats_settings *set) { struct http_server_settings http_set = { .rawlog_dir = set->stats_http_rawlog_dir, }; i_array_init(&stats_http_resources, 8); stats_http_server = http_server_init(&http_set); stats_http_resource_add("/", NULL, stats_http_resource_root_request, NULL); } void client_http_deinit(void) { http_server_deinit(&stats_http_server); array_free(&stats_http_resources); } dovecot-2.3.21.1/src/stats/event-exporter-fmt-none.c0000644000000000000000000000046114656633576017117 00000000000000/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "event-exporter.h" void event_export_fmt_none(const struct metric *metric ATTR_UNUSED, struct event *event ATTR_UNUSED, buffer_t *dest ATTR_UNUSED) { /* nothing to do */ } dovecot-2.3.21.1/src/stats/stats-service-openmetrics.c0000644000000000000000000005525414656633576017543 00000000000000/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ #include "stats-common.h" #include "dovecot-version.h" #include "str.h" #include "array.h" #include "json-parser.h" #include "ioloop.h" #include "ostream.h" #include "stats-dist.h" #include "http-server.h" #include "client-http.h" #include "stats-settings.h" #include "stats-metrics.h" #include "stats-service-private.h" #define OPENMETRICS_CONTENT_VERSION "0.0.1" #ifdef DOVECOT_REVISION #define OPENMETRICS_BUILD_INFO \ "version=\""DOVECOT_VERSION"\"," \ "revision=\""DOVECOT_REVISION"\"" #else #define OPENMETRICS_BUILD_INFO \ "version=\""DOVECOT_VERSION"\"" #endif enum openmetrics_metric_type { OPENMETRICS_METRIC_TYPE_COUNT, OPENMETRICS_METRIC_TYPE_DURATION, OPENMETRICS_METRIC_TYPE_FIELD, OPENMETRICS_METRIC_TYPE_HISTOGRAM, }; enum openmetrics_request_state { OPENMETRICS_REQUEST_STATE_INIT = 0, OPENMETRICS_REQUEST_STATE_METRIC, OPENMETRICS_REQUEST_STATE_METRIC_HEADER, OPENMETRICS_REQUEST_STATE_SUB_METRICS, OPENMETRICS_REQUEST_STATE_METRIC_BODY, OPENMETRICS_REQUEST_STATE_FINISHED, }; struct openmetrics_request_sub_metric { size_t labels_pos; const struct metric *metric; unsigned int sub_index; }; struct openmetrics_request { struct ostream *output; enum openmetrics_request_state state; struct stats_metrics_iter *stats_iter; const struct metric *metric; enum openmetrics_metric_type metric_type; string_t *labels; size_t labels_pos; unsigned int field_pos; ARRAY(struct openmetrics_request_sub_metric) sub_metric_stack; bool has_submetric:1; }; /* https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels: Every time series is uniquely identified by its metric name and optional key-value pairs called labels. The metric name specifies the general feature of a system that is measured (e.g. http_requests_total - the total number of HTTP requests received). It may contain ASCII letters and digits, as well as underscores and colons. It must match the regex [a-zA-Z_:][a-zA-Z0-9_:]*. */ static bool openmetrics_check_name(const char *name) { const unsigned char *p, *pend; p = (const unsigned char *)name; pend = p + strlen(name); if (p == pend) return FALSE; if (!(*p >= 'a' && *p <= 'z') && !(*p >= 'A' && *p <= 'Z') && *p != '_' && *p != ':') return FALSE; p++; while (p < pend) { if (!(*p >= 'a' && *p <= 'z') && !(*p >= 'A' && *p <= 'Z') && !(*p >= '0' && *p <= '9') && *p != '_' && *p != ':') return FALSE; p++; } return TRUE; } static void openmetrics_export_dovecot(string_t *out) { i_assert(stats_startup_time <= ioloop_time); str_append(out, "# HELP process_start_time_seconds " "Timestamp of service start\n"); str_append(out, "# TYPE process_start_time_seconds gauge\n"); str_printfa(out, "process_start_time_seconds %"PRIdTIME_T"\n", stats_startup_time); str_append(out, "# HELP dovecot_build " "Dovecot build information\n"); str_append(out, "# TYPE dovecot_build info\n"); str_append(out, "dovecot_build_info{"OPENMETRICS_BUILD_INFO"} 1\n"); } static void openmetrics_export_eof(string_t *out) { str_append(out, "# EOF\n"); } static void openmetrics_export_metric_value(struct openmetrics_request *req, string_t *out, const struct metric *metric) { const struct metric_field *field; /* Metric name */ str_append(out, "dovecot_"); str_append(out, req->metric->name); switch (req->metric_type) { case OPENMETRICS_METRIC_TYPE_COUNT: if (req->metric->group_by != NULL && str_len(req->labels) == 0) str_append(out, "_count"); else str_append(out, "_total"); break; case OPENMETRICS_METRIC_TYPE_DURATION: if (req->metric->group_by != NULL && str_len(req->labels) == 0) str_append(out, "_duration_seconds_sum"); else str_append(out, "_duration_seconds_total"); break; case OPENMETRICS_METRIC_TYPE_FIELD: field = &metric->fields[req->field_pos]; if (req->metric->group_by != NULL && str_len(req->labels) == 0) str_printfa(out, "_%s_sum", field->field_key); else str_printfa(out, "_%s_total", field->field_key); break; case OPENMETRICS_METRIC_TYPE_HISTOGRAM: i_unreached(); } /* Labels */ if (str_len(req->labels) > 0) { str_append_c(out, '{'); str_append_str(out, req->labels); str_append_c(out, '}'); } /* Value */ switch (req->metric_type) { case OPENMETRICS_METRIC_TYPE_COUNT: str_printfa(out, " %u\n", stats_dist_get_count(metric->duration_stats)); break; case OPENMETRICS_METRIC_TYPE_DURATION: /* Convert from microseconds to seconds */ str_printfa(out, " %.6f\n", stats_dist_get_sum(metric->duration_stats)/1e6F); break; case OPENMETRICS_METRIC_TYPE_FIELD: field = &metric->fields[req->field_pos]; str_printfa(out, " %"PRIu64"\n", stats_dist_get_sum(field->stats)); break; case OPENMETRICS_METRIC_TYPE_HISTOGRAM: i_unreached(); } } static const struct metric * openmetrics_find_histogram_bucket(const struct metric *metric, unsigned int index) { struct metric *sub_metric; if (!array_is_created(&metric->sub_metrics)) return NULL; array_foreach_elem(&metric->sub_metrics, sub_metric) { if (sub_metric->group_value.type != METRIC_VALUE_TYPE_BUCKET_INDEX) continue; if (sub_metric->group_value.intmax == index) return sub_metric; } return NULL; } static void openmetrics_export_histogram_bucket(struct openmetrics_request *req, string_t *out, const struct metric *metric, intmax_t bucket_limit, int64_t count) { /* Metric name */ str_append(out, "dovecot_"); str_append(out, metric->name); str_append(out, "_bucket"); /* Labels */ str_append_c(out, '{'); if (str_len(req->labels) > 0) { str_append_str(out, req->labels); str_append_c(out, ','); } if (bucket_limit == INTMAX_MAX) str_append(out, "le=\"+Inf\""); else if (strcmp(metric->group_by->field, STATS_EVENT_FIELD_NAME_DURATION) == 0) { /* Convert from microseconds to seconds */ str_printfa(out, "le=\"%.6f\"", bucket_limit/1e6F); } else { str_printfa(out, "le=\"%jd\"", bucket_limit); } str_printfa(out, "} %"PRIu64"\n", count); } static void openmetrics_export_histogram(struct openmetrics_request *req, string_t *out, const struct metric *metric) { const struct stats_metric_settings_group_by *group_by = metric->group_by; float sum = 0; uint64_t count = 0; /* Buckets */ for (unsigned int i = 0; i < group_by->num_ranges; i++) { const struct metric *sub_metric = openmetrics_find_histogram_bucket(metric, i); if (sub_metric != NULL) { sum += stats_dist_get_sum(sub_metric->duration_stats); count += stats_dist_get_count(sub_metric->duration_stats); } openmetrics_export_histogram_bucket(req, out, metric, group_by->ranges[i].max, count); } /* There is either no data in histogram, which adding the optional sum and count metrics doesn't add any new information or these have already been exported for submetrics. */ if (count == 0) return; /* Sum */ str_append(out, "dovecot_"); str_append(out, metric->name); str_append(out, "_sum"); /* Labels */ if (str_len(req->labels) > 0) { str_append_c(out, '{'); str_append_str(out, req->labels); str_append_c(out, '}'); } if (strcmp(metric->group_by->field, STATS_EVENT_FIELD_NAME_DURATION) == 0) { /* Convert from microseconds to seconds */ sum /= 1e6F; } str_printfa(out, " %.6f\n", sum); /* Count */ str_append(out, "dovecot_"); str_append(out, metric->name); str_append(out, "_count"); /* Labels */ if (str_len(req->labels) > 0) { str_append_c(out, '{'); str_append_str(out, req->labels); str_append_c(out, '}'); } str_printfa(out, " %"PRIu64"\n", count); } static void openmetrics_export_metric_header(struct openmetrics_request *req, string_t *out) { const struct metric *metric = req->metric; const struct metric_field *field; /* Description */ str_append(out, "# HELP dovecot_"); str_append(out, metric->name); switch (req->metric_type) { case OPENMETRICS_METRIC_TYPE_COUNT: str_append(out, " Total number of all events of this kind"); break; case OPENMETRICS_METRIC_TYPE_DURATION: str_append(out, "_duration_seconds Total duration of all events of this kind"); break; case OPENMETRICS_METRIC_TYPE_FIELD: field = &metric->fields[req->field_pos]; str_printfa(out, "_%s Total of field value for events of this kind", field->field_key); break; case OPENMETRICS_METRIC_TYPE_HISTOGRAM: str_append(out, " Histogram"); break; } if (*metric->set->description != '\0') { str_append(out, " of "); str_append(out, metric->set->description); } str_append_c(out, '\n'); /* Type */ str_append(out, "# TYPE dovecot_"); str_append(out, metric->name); switch (req->metric_type) { case OPENMETRICS_METRIC_TYPE_COUNT: str_append(out, " counter\n"); break; case OPENMETRICS_METRIC_TYPE_DURATION: str_append(out, "_duration_seconds counter\n"); break; case OPENMETRICS_METRIC_TYPE_FIELD: field = &metric->fields[req->field_pos]; str_printfa(out, "_%s counter\n", field->field_key); break; case OPENMETRICS_METRIC_TYPE_HISTOGRAM: str_append(out, " histogram\n"); break; } } static void openmetrics_export_submetric(struct openmetrics_request *req, string_t *out, const struct metric *metric) { /* This metric may be a submetric and therefore have a label associated with it. */ if (metric->sub_name != NULL) { str_append_c(req->labels, '"'); json_append_escaped(req->labels, metric->sub_name); str_append_c(req->labels, '"'); } if (req->metric_type == OPENMETRICS_METRIC_TYPE_HISTOGRAM) { if (metric->group_by == NULL || metric->group_by[0].func != STATS_METRIC_GROUPBY_QUANTIZED) return; openmetrics_export_histogram(req, out, metric); return; } openmetrics_export_metric_value(req, out, metric); req->has_submetric = TRUE; } static const struct metric * openmetrics_export_sub_metric_get(struct openmetrics_request_sub_metric *reqsm) { if (reqsm->sub_index >= array_count(&reqsm->metric->sub_metrics)) return NULL; return array_idx_elem(&reqsm->metric->sub_metrics, reqsm->sub_index); } static const struct metric * openmetrics_export_sub_metric_get_next( struct openmetrics_request_sub_metric *reqsm) { /* Get the next valid sub-metric */ reqsm->sub_index++; return openmetrics_export_sub_metric_get(reqsm); } static struct openmetrics_request_sub_metric * openmetrics_export_sub_metric_down(struct openmetrics_request *req) { struct openmetrics_request_sub_metric *reqsm = array_back_modifiable(&req->sub_metric_stack); const struct metric *sub_metric; /* Descend further into sub-metric tree */ if (reqsm->metric->group_by == NULL || !openmetrics_check_name(reqsm->metric->group_by->field) || !array_is_created(&reqsm->metric->sub_metrics) || array_count(&reqsm->metric->sub_metrics) == 0) return NULL; if (reqsm->metric->group_by[0].func == STATS_METRIC_GROUPBY_QUANTIZED) { /* Never descend into quantized group_by sub-metrics. Histograms are exported as a single blob. */ return NULL; } /* Find sub-metric to descend into */ sub_metric = openmetrics_export_sub_metric_get(reqsm); if (sub_metric == NULL) { /* None valid */ return NULL; } if (str_len(req->labels) > 0) str_append_c(req->labels, ','); str_append(req->labels, reqsm->metric->group_by->field); str_append_c(req->labels, '='); reqsm->labels_pos = str_len(req->labels); /* Descend */ reqsm = array_append_space(&req->sub_metric_stack); reqsm->metric = sub_metric; return reqsm; } static struct openmetrics_request_sub_metric * openmetrics_export_sub_metric_up_next(struct openmetrics_request *req) { struct openmetrics_request_sub_metric *reqsm; const struct metric *sub_metric = NULL; /* Ascend to next sub-metric of an ancestor */ while (array_count(&req->sub_metric_stack) > 1) { /* Ascend */ array_pop_back(&req->sub_metric_stack); reqsm = array_back_modifiable(&req->sub_metric_stack); str_truncate(req->labels, reqsm->labels_pos); /* Find next sub-metric */ sub_metric = openmetrics_export_sub_metric_get_next(reqsm); if (sub_metric != NULL) { /* None valid */ break; } } if (sub_metric == NULL) { /* End of sub-metric tree */ return NULL; } /* Descend */ reqsm = array_append_space(&req->sub_metric_stack); reqsm->metric = sub_metric; return reqsm; } static struct openmetrics_request_sub_metric * openmetrics_export_sub_metric_current(struct openmetrics_request *req) { struct openmetrics_request_sub_metric *reqsm; /* Get state for current sub-metric */ if (!array_is_created(&req->sub_metric_stack)) i_array_init(&req->sub_metric_stack, 8); if (array_count(&req->sub_metric_stack) >= 2) { /* Already walking the sub-metric tree */ return array_back_modifiable(&req->sub_metric_stack); } /* Start tree walking */ reqsm = array_append_space(&req->sub_metric_stack); reqsm->metric = req->metric; reqsm->labels_pos = str_len(req->labels); return openmetrics_export_sub_metric_down(req); } static bool openmetrics_export_sub_metrics(struct openmetrics_request *req, string_t *out) { struct openmetrics_request_sub_metric *reqsm = NULL; if (!array_is_created(&req->metric->sub_metrics)) return TRUE; reqsm = openmetrics_export_sub_metric_current(req); if (reqsm == NULL) { /* No valid sub-metrics to export */ return TRUE; } openmetrics_export_submetric(req, out, reqsm->metric); /* Try do descend into sub-metrics tree for next sub-metric to export. */ reqsm = openmetrics_export_sub_metric_down(req); if (reqsm == NULL) { /* Sub-metrics of this metric exhausted; ascend to the next parent sub-metric. */ reqsm = openmetrics_export_sub_metric_up_next(req); } if (reqsm == NULL) { /* Finished */ array_clear(&req->sub_metric_stack); return TRUE; } return FALSE; } static void openmetrics_export_metric_body(struct openmetrics_request *req, string_t *out) { openmetrics_export_metric_value(req, out, req->metric); } static int openmetrics_send_buffer(struct openmetrics_request *req, buffer_t *buffer) { ssize_t sent; if (buffer->used == 0) return 1; sent = o_stream_send(req->output, buffer->data, buffer->used); if (sent < 0) return -1; /* Max buffer size is enormous */ i_assert((size_t)sent == buffer->used); if (o_stream_get_buffer_used_size(req->output) >= IO_BLOCK_SIZE) return 0; return 1; } static bool openmetrics_export_has_histogram(struct openmetrics_request *req) { const struct metric *metric = req->metric; unsigned int i; if (metric->group_by_count == 0) { /* No group_by */ return FALSE; } /* We can only support quantized group_by when it is the last group item. */ for (i = 0; i < (metric->group_by_count - 1); i++) { if (metric->group_by[i].func == STATS_METRIC_GROUPBY_QUANTIZED) return FALSE; } return (metric->group_by[metric->group_by_count - 1].func == STATS_METRIC_GROUPBY_QUANTIZED); } static void openmetrics_export_next(struct openmetrics_request *req) { /* Determine what to export next. */ switch (req->metric_type) { case OPENMETRICS_METRIC_TYPE_COUNT: /* Continue with duration output for this metric. */ req->metric_type = OPENMETRICS_METRIC_TYPE_DURATION; req->state = OPENMETRICS_REQUEST_STATE_METRIC_HEADER; break; case OPENMETRICS_METRIC_TYPE_DURATION: if (openmetrics_export_has_histogram(req)) { /* Continue with histogram output for this metric. */ req->metric_type = OPENMETRICS_METRIC_TYPE_HISTOGRAM; req->state = OPENMETRICS_REQUEST_STATE_METRIC_HEADER; } else if (req->metric->fields_count > 0) { req->field_pos = 0; req->metric_type = OPENMETRICS_METRIC_TYPE_FIELD; req->state = OPENMETRICS_REQUEST_STATE_METRIC_HEADER; } else { /* No histogram; continue with next metric */ req->state = OPENMETRICS_REQUEST_STATE_METRIC; } break; case OPENMETRICS_METRIC_TYPE_FIELD: req->field_pos++; if (req->field_pos < req->metric->fields_count) { req->metric_type = OPENMETRICS_METRIC_TYPE_FIELD; req->state = OPENMETRICS_REQUEST_STATE_METRIC_HEADER; } else { /* all fields consumed */ req->state = OPENMETRICS_REQUEST_STATE_METRIC; } break; case OPENMETRICS_METRIC_TYPE_HISTOGRAM: if (req->metric->fields_count > 0) { /* Continue with fields */ req->field_pos = 0; req->metric_type = OPENMETRICS_METRIC_TYPE_FIELD; req->state = OPENMETRICS_REQUEST_STATE_METRIC_HEADER; } else { /* Continue with next metric */ req->state = OPENMETRICS_REQUEST_STATE_METRIC; } break; } } static void openmetrics_export_continue(struct openmetrics_request *req, string_t *out) { switch (req->state) { case OPENMETRICS_REQUEST_STATE_INIT: /* Export the Dovecot base metrics. */ i_assert(req->stats_iter == NULL); req->stats_iter = stats_metrics_iterate_init(stats_metrics); openmetrics_export_dovecot(out); req->state = OPENMETRICS_REQUEST_STATE_METRIC; break; case OPENMETRICS_REQUEST_STATE_METRIC: /* Export the next metric. */ i_assert(req->stats_iter != NULL); do { req->metric = stats_metrics_iterate(req->stats_iter); } while (req->metric != NULL && !openmetrics_check_name(req->metric->name)); if (req->metric == NULL) { /* Finished exporting metrics. */ req->state = OPENMETRICS_REQUEST_STATE_FINISHED; break; } if (req->labels == NULL) req->labels = str_new(default_pool, 32); else str_truncate(req->labels, 0); req->labels_pos = 0; /* Start with count output for this metric if the type is not histogram. If the metric is of type histogram, start with quantiles. */ if (openmetrics_export_has_histogram(req)) req->metric_type = OPENMETRICS_METRIC_TYPE_HISTOGRAM; else req->metric_type = OPENMETRICS_METRIC_TYPE_COUNT; req->state = OPENMETRICS_REQUEST_STATE_METRIC_HEADER; /* Fall through */ case OPENMETRICS_REQUEST_STATE_METRIC_HEADER: /* Export the HELP/TYPE header for the current metric */ str_truncate(req->labels, req->labels_pos); req->has_submetric = FALSE; if (array_is_created(&req->sub_metric_stack)) array_clear(&req->sub_metric_stack); openmetrics_export_metric_header(req, out); req->state = OPENMETRICS_REQUEST_STATE_SUB_METRICS; break; case OPENMETRICS_REQUEST_STATE_SUB_METRICS: /* Export the sub-metrics for the current metric. This will return for each sub-metric, so that the out string buffer stays small. */ if (!openmetrics_export_sub_metrics(req, out)) break; /* All sub-metrics written. */ req->state = OPENMETRICS_REQUEST_STATE_METRIC_BODY; break; case OPENMETRICS_REQUEST_STATE_METRIC_BODY: /* Export the body of the current metric. */ str_truncate(req->labels, req->labels_pos); if (req->metric_type == OPENMETRICS_METRIC_TYPE_HISTOGRAM) openmetrics_export_histogram(req, out, req->metric); else openmetrics_export_metric_body(req, out); openmetrics_export_next(req); break; case OPENMETRICS_REQUEST_STATE_FINISHED: i_unreached(); } } static void openmetrics_handle_write_error(struct openmetrics_request *req) { i_info("openmetrics: write(%s) failed: %s", o_stream_get_name(req->output), o_stream_get_error(req->output)); o_stream_destroy(&req->output); } static void openmetrics_request_deinit(struct openmetrics_request *req) { stats_metrics_iterate_deinit(&req->stats_iter); str_free(&req->labels); array_free(&req->sub_metric_stack); } static int openmetrics_export(struct openmetrics_request *req) { string_t *out; int ret; ret = o_stream_flush(req->output); if (ret < 0) { openmetrics_handle_write_error(req); return -1; } if (ret == 0) { /* Output stream buffer needs to be flushed further */ return 0; } if (req->state == OPENMETRICS_REQUEST_STATE_FINISHED) { /* All metrics were exported already, so we can finish the HTTP request now. */ o_stream_destroy(&req->output); return 1; } /* Export metrics into a string buffer and write that buffer to the output stream after each (sub-)metric, so that the string buffer stays small. The output stream buffer can grow bigger, but writing is stopped for later resumption when the output stream buffer has grown beyond an optimal size. */ out = t_str_new(1024); for (;;) { str_truncate(out, 0); openmetrics_export_continue(req, out); ret = openmetrics_send_buffer(req, out); if (ret < 0) { openmetrics_handle_write_error(req); return -1; } if (req->state == OPENMETRICS_REQUEST_STATE_FINISHED) { /* Finished export of metrics, but the output stream buffer may still contain data. */ break; } if (ret == 0) { /* Output stream buffer is filled up beyond the optimal size; wait until we can write more. */ return ret; } } /* Send EOF */ str_truncate(out, 0); openmetrics_export_eof(out); ret = openmetrics_send_buffer(req, out); if (ret < 0) { openmetrics_handle_write_error(req); return -1; } /* Cleanup everything except the output stream */ openmetrics_request_deinit(req); /* Finished; flush output */ ret = o_stream_finish(req->output); if (ret < 0) { openmetrics_handle_write_error(req); return -1; } return ret; } static void openmetrics_request_destroy(struct openmetrics_request *req) { o_stream_destroy(&req->output); openmetrics_request_deinit(req); } static void stats_service_openmetrics_request(void *context ATTR_UNUSED, struct http_server_request *hsreq, const char *sub_path) { const struct http_request *hreq = http_server_request_get(hsreq); struct http_server_response *hsresp; struct openmetrics_request *req; pool_t pool; if (strcmp(hreq->method, "OPTIONS") == 0) { hsresp = http_server_response_create(hsreq, 200, "OK"); http_server_response_add_header(hsresp, "Allow", "GET"); http_server_response_submit(hsresp); return; } if (strcmp(hreq->method, "GET") != 0) { http_server_request_fail_bad_method(hsreq, "GET"); return; } if (*sub_path != '\0') { http_server_request_fail(hsreq, 404, "Not Found"); return; } pool = http_server_request_get_pool(hsreq); req = p_new(pool, struct openmetrics_request, 1); http_server_request_set_destroy_callback( hsreq, openmetrics_request_destroy, req); hsresp = http_server_response_create(hsreq, 200, "OK"); http_server_response_add_header( hsresp, "Content-Type", "application/openmetrics-text; version="OPENMETRICS_CONTENT_VERSION"; " "charset=utf-8"); req->output = http_server_response_get_payload_output( hsresp, SIZE_MAX, FALSE); o_stream_set_flush_callback(req->output, openmetrics_export, req); o_stream_set_flush_pending(req->output, TRUE); } void stats_service_openmetrics_init(void) { struct stats_metrics_iter *iter; const struct metric *metric; iter = stats_metrics_iterate_init(stats_metrics); while ((metric = stats_metrics_iterate(iter)) != NULL) { if (!openmetrics_check_name(metric->name)) { i_warning( "stats: openmetrics: " "Metric `%s' is not valid for OpenMetrics" "(invalid metric name; skipped)", metric->name); } } stats_metrics_iterate_deinit(&iter); stats_http_resource_add("/metrics", "OpenMetrics", stats_service_openmetrics_request, NULL); } dovecot-2.3.21.1/src/stats/event-exporter-fmt-tab-text.c0000644000000000000000000001337614656633576017721 00000000000000/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "array.h" #include "lib-event-private.h" #include "event-exporter.h" #include "str.h" #include "strescape.h" #include "hostpid.h" static void append_strlist(string_t *dest, const ARRAY_TYPE(const_string) *strlist) { string_t *str = t_str_new(64); const char *value; bool first = TRUE; /* append the strings first escaped into a temporary string */ array_foreach_elem(strlist, value) { if (first) first = FALSE; else str_append_c(str, '\t'); str_append_tabescaped(str, value); } /* append the temporary string (double-)escaped as the value */ str_append_tabescaped(dest, str_c(str)); } static void append_int(string_t *dest, intmax_t val) { str_printfa(dest, "%jd", val); } static void append_time(string_t *dest, const struct timeval *time, enum event_exporter_time_fmt fmt) { switch (fmt) { case EVENT_EXPORTER_TIME_FMT_NATIVE: i_panic("tab-text format does not have a native date/time type"); case EVENT_EXPORTER_TIME_FMT_UNIX: event_export_helper_fmt_unix_time(dest, time); break; case EVENT_EXPORTER_TIME_FMT_RFC3339: event_export_helper_fmt_rfc3339_time(dest, time); break; } } static void append_field_str(string_t *dest, const char *str, const struct metric_export_info *info) { if (info->exporter->format_max_field_len == 0) str_append_tabescaped(dest, str); else { size_t len = strlen(str); str_append_tabescaped_n(dest, (const unsigned char *)str, I_MIN(len, info->exporter->format_max_field_len)); if (len > info->exporter->format_max_field_len) str_append(dest, "..."); } } static void append_field_value(string_t *dest, const struct event_field *field, const struct metric_export_info *info) { switch (field->value_type) { case EVENT_FIELD_VALUE_TYPE_STR: append_field_str(dest, field->value.str, info); break; case EVENT_FIELD_VALUE_TYPE_INTMAX: append_int(dest, field->value.intmax); break; case EVENT_FIELD_VALUE_TYPE_TIMEVAL: append_time(dest, &field->value.timeval, info->exporter->time_format); break; case EVENT_FIELD_VALUE_TYPE_STRLIST: append_strlist(dest, &field->value.strlist); break; } } static void tabtext_export_name(string_t *dest, struct event *event, const struct metric_export_info *info) { if ((info->include & EVENT_EXPORTER_INCL_NAME) == 0) return; str_append(dest, "event:"); str_append_tabescaped(dest, event->sending_name); str_append_c(dest, '\t'); } static void tabtext_export_hostname(string_t *dest, const struct metric_export_info *info) { if ((info->include & EVENT_EXPORTER_INCL_HOSTNAME) == 0) return; str_append(dest, "hostname:"); str_append_tabescaped(dest, my_hostname); str_append_c(dest, '\t'); } static void tabtext_export_timestamps(string_t *dest, struct event *event, const struct metric_export_info *info) { if ((info->include & EVENT_EXPORTER_INCL_TIMESTAMPS) == 0) return; str_append(dest, "start_time:"); append_time(dest, &event->tv_created, info->exporter->time_format); str_append(dest, "\tend_time:"); append_time(dest, &ioloop_timeval, info->exporter->time_format); str_append_c(dest, '\t'); } static void append_category(string_t *dest, const char *cat) { str_append(dest, "category:"); str_append_tabescaped(dest, cat); } static void tabtext_export_categories(string_t *dest, struct event *event, const struct metric_export_info *info) { struct event_category *const *cats; unsigned int count; if ((info->include & EVENT_EXPORTER_INCL_CATEGORIES) == 0) return; cats = event_get_categories(event, &count); event_export_helper_fmt_categories(dest, cats, count, append_category, "\t"); str_append_c(dest, '\t'); /* extra \t to have something to remove later */ } static void tabtext_export_fields(string_t *dest, struct event *event, const struct metric_export_info *info, const unsigned int fields_count, const struct metric_field *fields) { if ((info->include & EVENT_EXPORTER_INCL_FIELDS) == 0) return; if (fields_count == 0) { /* include all fields */ const struct event_field *fields; unsigned int count; fields = event_get_fields(event, &count); for (unsigned int i = 0; i < count; i++) { const struct event_field *field = &fields[i]; str_append(dest, "field:"); str_append_tabescaped(dest, field->key); str_append_c(dest, '='); append_field_value(dest, field, info); str_append_c(dest, '\t'); } } else { for (unsigned int i = 0; i < fields_count; i++) { const char *name = fields[i].field_key; const struct event_field *field; field = event_find_field_recursive(event, name); if (field == NULL) continue; /* doesn't exist, skip it */ str_append(dest, "field:"); str_append_tabescaped(dest, name); str_append_c(dest, '='); append_field_value(dest, field, info); str_append_c(dest, '\t'); } } } /* * Serialize the event as tab delimited collection of the following: * * event: * hostname: * start_time: * end_time: * category: * field:= * * Note: cat and field can occur multiple times. */ void event_export_fmt_tabescaped_text(const struct metric *metric, struct event *event, buffer_t *dest) { const struct metric_export_info *info = &metric->export_info; if (info->include == EVENT_EXPORTER_INCL_NONE) return; tabtext_export_name(dest, event, info); tabtext_export_hostname(dest, info); tabtext_export_timestamps(dest, event, info); tabtext_export_categories(dest, event, info); tabtext_export_fields(dest, event, info, metric->fields_count, metric->fields); /* remove trailing tab */ str_truncate(dest, str_len(dest) - 1); } dovecot-2.3.21.1/src/stats/stats-event-category.h0000644000000000000000000000051614656633576016505 00000000000000#ifndef STATS_EVENT_CATEGORY_H #define STATS_EVENT_CATEGORY_H /* Register a new event category if it doesn't already exist. parent may be NULL. */ void stats_event_category_register(const char *name, struct event_category *parent); void stats_event_categories_init(void); void stats_event_categories_deinit(void); #endif dovecot-2.3.21.1/src/stats/test-client-reader.c0000644000000000000000000001247314656633576016110 00000000000000/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ #include "test-stats-common.h" #include "master-service-private.h" #include "client-reader.h" #include "connection.h" #include "ostream.h" static struct connection_list *conn_list; struct test_connection { struct connection conn; unsigned int row_count; }; static void test_reader_server_destroy(struct connection *conn) { io_loop_stop(conn->ioloop); } static struct connection_settings client_set = { .service_name_in = "stats-reader-server", .service_name_out = "stats-reader-client", .major_version = 2, .minor_version = 0, .allow_empty_args_input = TRUE, .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, .client = TRUE, }; bool test_stats_callback(struct event *event, enum event_callback_type type ATTR_UNUSED, struct failure_context *ctx, const char *fmt ATTR_UNUSED, va_list args ATTR_UNUSED) { if (stats_metrics != NULL) { stats_metrics_event(stats_metrics, event, ctx); struct event_filter *filter = stats_metrics_get_event_filter(stats_metrics); return !event_filter_match(filter, event, ctx); } return TRUE; } static const char *settings_blob_1 = "metric=test\n" "metric/test/metric_name=test\n" "metric/test/filter=event=test\n" "\n"; static int test_reader_server_input_args(struct connection *conn ATTR_UNUSED, const char *const *args) { if (args[0] == NULL) return -1; test_assert_strcmp(args[0], "test"); test_assert_strcmp(args[1], "1"); return 1; } static void test_dump_metrics(void) { int fds[2]; test_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0); struct connection *conn = i_new(struct connection, 1); struct ioloop *loop = io_loop_create(); client_reader_create(fds[1]); connection_init_client_fd(conn_list, conn, "stats", fds[0], fds[0]); o_stream_nsend_str(conn->output, "DUMP\tcount\n"); io_loop_run(loop); connection_deinit(conn); i_free(conn); /* allow client-reader to finish up */ io_loop_set_running(loop); io_loop_handler_run(loop); io_loop_destroy(&loop); } static void test_client_reader(void) { const struct connection_vfuncs client_vfuncs = { .input_args = test_reader_server_input_args, .destroy = test_reader_server_destroy, }; test_begin("client reader"); /* register some stats */ test_init(settings_blob_1); client_readers_init(); conn_list = connection_list_init(&client_set, &client_vfuncs); /* push event in */ struct event *event = event_create(NULL); event_add_category(event, &test_category); event_set_name(event, "test"); test_event_send(event); event_unref(&event); test_assert(get_stats_dist_field("test", STATS_DIST_COUNT) == 1); test_assert(get_stats_dist_field("test", STATS_DIST_SUM) > 0); /* check output from reader */ test_dump_metrics(); test_deinit(); client_readers_deinit(); connection_list_deinit(&conn_list); test_end(); } static const char *settings_blob_2 = "metric=test\n" "metric/test/metric_name=test\n" "metric/test/filter=event=test\n" "metric/test/group_by=test_name\n" "\n"; static int test_reader_server_input_args_group_by(struct connection *conn, const char *const *args) { struct test_connection *tconn = container_of(conn, struct test_connection, conn); if (args[0] == NULL) return -1; tconn->row_count++; if (tconn->row_count == 1) { test_assert_strcmp(args[0], "test"); test_assert_strcmp(args[1], "1"); } else if (tconn->row_count == 2) { test_assert_strcmp(args[0], "test_alpha"); test_assert_strcmp(args[1], "1"); } return 1; } static void test_dump_metrics_group_by(void) { int fds[2]; test_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0); struct test_connection *conn = i_new(struct test_connection, 1); struct ioloop *loop = io_loop_create(); client_reader_create(fds[1]); connection_init_client_fd(conn_list, &conn->conn, "stats", fds[0], fds[0]); o_stream_nsend_str(conn->conn.output, "DUMP\tcount\n"); io_loop_run(loop); connection_deinit(&conn->conn); i_free(conn); io_loop_set_running(loop); io_loop_handler_run(loop); io_loop_destroy(&loop); } static void test_client_reader_group_by(void) { const struct connection_vfuncs client_vfuncs = { .input_args = test_reader_server_input_args_group_by, .destroy = test_reader_server_destroy, }; test_begin("client reader (group by)"); /* register some stats */ test_init(settings_blob_2); client_readers_init(); conn_list = connection_list_init(&client_set, &client_vfuncs); /* push event in */ struct event *event = event_create(NULL); event_add_category(event, &test_category); event_set_name(event, "test"); event_add_str(event, "event_name", "alpha"); test_event_send(event); event_unref(&event); test_assert(get_stats_dist_field("test", STATS_DIST_COUNT) == 1); test_assert(get_stats_dist_field("test", STATS_DIST_SUM) > 0); /* check output from reader */ test_dump_metrics_group_by(); test_deinit(); client_readers_deinit(); connection_list_deinit(&conn_list); test_end(); } int main(void) { /* fake master service to pretend destroying connections. */ struct master_service local_master_service = { .stopping = TRUE, .total_available_count = 100, .service_count_left = 100, }; void (*const test_functions[])(void) = { test_client_reader, test_client_reader_group_by, NULL }; master_service = &local_master_service; int ret = test_run(test_functions); return ret; } dovecot-2.3.21.1/src/stats/stats-settings.h0000644000000000000000000001001514656633576015404 00000000000000#ifndef STATS_SETTINGS_H #define STATS_SETTINGS_H #define STATS_METRIC_SETTINGS_DEFAULT_EXPORTER_INCLUDE \ "name hostname timestamps categories fields" /* */ /* * We allow a selection of a timestamp format. * * The 'time-unix' format generates a number with the number of seconds * since 1970-01-01 00:00 UTC. * * The 'time-rfc3339' format uses the YYYY-MM-DDTHH:MM:SS.uuuuuuZ format as * defined by RFC 3339. * * The special native format (not explicitly selectable in the config, but * default if no time-* token is used) uses the format's native timestamp * format. Note that not all formats have a timestamp data format. * * The native format and the rules below try to address the question: can a * parser that doesn't have any knowledge of fields' values' types losslessly * reconstruct the fields? * * For example, JSON only has strings and numbers, so it cannot represent a * timestamp in a "context-free lossless" way. Therefore, when making a * JSON blob, we need to decide which way to serialize timestamps. No * matter how we do it, we incur some loss. If a decoder sees 1557232304 in * a field, it cannot be certain if the field is an integer that just * happens to be a reasonable timestamp, or if it actually is a timestamp. * Same goes with RFC3339 - it could just be that the user supplied a string * that looks like a timestamp, and that string made it into an event field. * * Other common serialization formats, such as CBOR, have a lossless way of * encoding timestamps. * * Note that there are two concepts at play: native and default. * * The rules for how the format's timestamp formats are used: * * 1. The default time format is the native format. * 2. The native time format may or may not exist for a given format (e.g., * in JSON) * 3. If the native format doesn't exist and no time format was specified in * the config, it is a config error. * * We went with these rules because: * * 1. It prevents type information loss by default. * 2. It completely isolates the policy from the algorithm. * 3. It defers the decision whether each format without a native timestamp * type should have a default acting as native until after we've had some * operational experience. * 4. A future decision to add a default (via 3. point) will be 100% compatible. */ enum event_exporter_time_fmt { EVENT_EXPORTER_TIME_FMT_NATIVE = 0, EVENT_EXPORTER_TIME_FMT_UNIX, EVENT_EXPORTER_TIME_FMT_RFC3339, }; /* */ struct stats_exporter_settings { const char *name; const char *transport; const char *transport_args; unsigned int transport_timeout; const char *format; const char *format_args; /* parsed values */ enum event_exporter_time_fmt parsed_time_format; }; /* */ enum stats_metric_group_by_func { STATS_METRIC_GROUPBY_DISCRETE = 0, STATS_METRIC_GROUPBY_QUANTIZED, }; /* * A range covering a stats bucket. The the interval is half closed - the * minimum is excluded and the maximum is included. In other words: (min, max]. * Because we don't have a +Inf and -Inf, we use INTMAX_MIN and INTMAX_MAX * respectively. */ struct stats_metric_settings_bucket_range { intmax_t min; intmax_t max; }; struct stats_metric_settings_group_by { const char *field; enum stats_metric_group_by_func func; unsigned int num_ranges; struct stats_metric_settings_bucket_range *ranges; }; /* */ struct stats_metric_settings { const char *metric_name; const char *description; const char *fields; const char *group_by; const char *filter; ARRAY(struct stats_metric_settings_group_by) parsed_group_by; struct event_filter *parsed_filter; /* exporter related fields */ const char *exporter; const char *exporter_include; }; struct stats_settings { const char *stats_http_rawlog_dir; ARRAY(struct stats_exporter_settings *) exporters; ARRAY(struct stats_metric_settings *) metrics; }; extern const struct setting_parser_info stats_setting_parser_info; extern const struct setting_parser_info stats_metric_setting_parser_info; #endif dovecot-2.3.21.1/src/dns/0000755000000000000000000000000014656633640011740 500000000000000dovecot-2.3.21.1/src/dns/Makefile.in0000644000000000000000000006244114656633607013737 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ pkglibexec_PROGRAMS = dns-client$(EXEEXT) subdir = src/dns ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(pkglibexecdir)" PROGRAMS = $(pkglibexec_PROGRAMS) am_dns_client_OBJECTS = dns-client.$(OBJEXT) \ dns-client-settings.$(OBJEXT) dns_client_OBJECTS = $(am_dns_client_OBJECTS) am__DEPENDENCIES_1 = AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/dns-client-settings.Po \ ./$(DEPDIR)/dns-client.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(dns_client_SOURCES) DIST_SOURCES = $(dns_client_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) pkglibexecdir = $(libexecdir)/dovecot ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-settings \ $(BINARY_CFLAGS) dns_client_LDADD = $(LIBDOVECOT) \ $(BINARY_LDFLAGS) dns_client_DEPENDENCIES = $(LIBDOVECOT_DEPS) dns_client_SOURCES = \ dns-client.c \ dns-client-settings.c all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/dns/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/dns/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files clean-pkglibexecPROGRAMS: @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list dns-client$(EXEEXT): $(dns_client_OBJECTS) $(dns_client_DEPENDENCIES) $(EXTRA_dns_client_DEPENDENCIES) @rm -f dns-client$(EXEEXT) $(AM_V_CCLD)$(LINK) $(dns_client_OBJECTS) $(dns_client_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dns-client-settings.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dns-client.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am check: check-am all-am: Makefile $(PROGRAMS) installdirs: for dir in "$(DESTDIR)$(pkglibexecdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-pkglibexecPROGRAMS \ mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/dns-client-settings.Po -rm -f ./$(DEPDIR)/dns-client.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-pkglibexecPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/dns-client-settings.Po -rm -f ./$(DEPDIR)/dns-client.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkglibexecPROGRAMS .MAKE: install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ clean-generic clean-libtool clean-pkglibexecPROGRAMS \ cscopelist-am ctags ctags-am distclean distclean-compile \ distclean-generic distclean-libtool distclean-tags distdir dvi \ dvi-am html html-am info info-am install install-am \ install-data install-data-am install-dvi install-dvi-am \ install-exec install-exec-am install-html install-html-am \ install-info install-info-am install-man install-pdf \ install-pdf-am install-pkglibexecPROGRAMS install-ps \ install-ps-am install-strip installcheck installcheck-am \ installdirs maintainer-clean maintainer-clean-generic \ mostlyclean mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ uninstall-am uninstall-pkglibexecPROGRAMS .PRECIOUS: Makefile # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/dns/dns-client.c0000644000000000000000000001122714656633576014077 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "istream.h" #include "ostream.h" #include "array.h" #include "strfuncs.h" #include "connection.h" #include "restrict-access.h" #include "master-service.h" #include static struct event_category event_category_dns = { .name = "dns-worker" }; static struct connection_list *dns_clients = NULL; static int dns_client_input_args(struct connection *client, const char *const *args) { struct ip_addr *ips, ip; const char *name; struct event *event; unsigned int i, ips_count; int ret; struct event_passthrough *e; if (strcmp(args[0], "QUIT") == 0) { return -1; } else if (args[1] == NULL) { e_error(client->event, "Got empty request"); return -1; } event = event_create(client->event); event_set_append_log_prefix(event, t_strconcat(args[1], ": ", NULL)); e = event_create_passthrough(event)-> set_name("dns_worker_request_started")-> add_str("name", args[1]); e_debug(e->event(), "Resolving"); e = event_create_passthrough(event)-> set_name("dns_worker_request_finished")-> add_str("name", args[1]); if (strcmp(args[0], "IP") == 0) { ret = net_gethostbyname(args[1], &ips, &ips_count); if (ret == 0 && ips_count == 0) { /* shouldn't happen, but fix it anyway.. */ ret = EAI_NONAME; } /* update timestamp after hostname lookup so the event duration field gets set correctly */ io_loop_time_refresh(); if (ret != 0) { const char *err = net_gethosterror(ret); e->add_int("error_code", ret); e->add_str("error", err); e_debug(e->event(), "Resolve failed: %s", err); o_stream_nsend_str(client->output, t_strdup_printf("%d\t%s\n", ret, err)); } else { ARRAY_TYPE(const_string) tmp; t_array_init(&tmp, ips_count); o_stream_nsend_str(client->output, "0\t"); for (i = 0; i < ips_count; i++) { const char *ip = net_ip2addr(&ips[i]); array_push_back(&tmp, &ip); } array_append_zero(&tmp); e_debug(e->event(), "Resolve success: %s", t_strarray_join(array_front(&tmp), ", ")); o_stream_nsend_str(client->output, t_strarray_join(array_front(&tmp), "\t")); o_stream_nsend_str(client->output, "\n"); } } else if (strcmp(args[0], "NAME") == 0) { if (net_addr2ip(args[1], &ip) < 0) { e->add_int("error_code", EAI_FAIL); e->add_str("error", "Not an IP"); e_debug(e->event(), "Resolve failed: Not an IP"); o_stream_nsend_str(client->output, "-1\tNot an IP\n"); } else if ((ret = net_gethostbyaddr(&ip, &name)) != 0) { const char *err = net_gethosterror(ret); e->add_int("error_code", ret); e->add_str("error", err); e_debug(e->event(), "Resolve failed: %s", err); o_stream_nsend_str(client->output, t_strdup_printf("%d\t%s\n", ret, err)); } else { e_debug(e->event(), "Resolve success: %s", name); o_stream_nsend_str(client->output, t_strdup_printf("0\t%s\n", name)); } } else { e->add_str("error", "Unknown command"); e_error(e->event(), "Unknown command '%s'", args[0]); o_stream_nsend_str(client->output, "-1\tUnknown command\n"); } event_unref(&event); return 1; } static void dns_client_destroy(struct connection *client) { connection_deinit(client); event_unref(&client->event); i_free(client); master_service_client_connection_destroyed(master_service); } static const struct connection_vfuncs dns_client_vfuncs = { .input_args = dns_client_input_args, .destroy = dns_client_destroy }; static const struct connection_settings dns_client_set = { .service_name_in = "dns-client", .service_name_out = "dns", .major_version = 1, .minor_version = 0, .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX }; static void client_connected(struct master_service_connection *master_conn) { struct connection *conn = i_new(struct connection, 1); master_service_client_connection_accept(master_conn); connection_init_server(dns_clients, conn, master_conn->name, master_conn->fd, master_conn->fd); event_add_category(conn->event, &event_category_dns); } int main(int argc, char *argv[]) { master_service = master_service_init("dns-client", 0, &argc, &argv, ""); if (master_getopt(master_service) > 0) return FATAL_DEFAULT; master_service_init_log(master_service); restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL); restrict_access_allow_coredumps(TRUE); /* setup connection list */ dns_clients = connection_list_init(&dns_client_set, &dns_client_vfuncs); master_service_init_finish(master_service); master_service_run(master_service, client_connected); /* disconnect all clients */ connection_list_deinit(&dns_clients); master_service_deinit(&master_service); return 0; } dovecot-2.3.21.1/src/dns/Makefile.am0000644000000000000000000000055714656633576013733 00000000000000pkglibexecdir = $(libexecdir)/dovecot pkglibexec_PROGRAMS = dns-client AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-settings \ $(BINARY_CFLAGS) dns_client_LDADD = $(LIBDOVECOT) \ $(BINARY_LDFLAGS) dns_client_DEPENDENCIES = $(LIBDOVECOT_DEPS) dns_client_SOURCES = \ dns-client.c \ dns-client-settings.c dovecot-2.3.21.1/src/dns/dns-client-settings.c0000644000000000000000000000235314656633576015735 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "settings-parser.h" #include "service-settings.h" #include /* */ static struct file_listener_settings dns_client_unix_listeners_array[] = { { "dns-client", 0666, "", "" }, { "login/dns-client", 0666, "", "" }, }; static struct file_listener_settings *dns_client_unix_listeners[] = { &dns_client_unix_listeners_array[0], &dns_client_unix_listeners_array[1], }; static buffer_t dns_client_unix_listeners_buf = { { { dns_client_unix_listeners, sizeof(dns_client_unix_listeners) } } }; /* */ struct service_settings dns_client_service_settings = { .name = "dns-client", .protocol = "", .type = "", .executable = "dns-client", .user = "$default_internal_user", .group = "", .privileged_group = "", .extra_groups = "", .chroot = "", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 0, .client_limit = 1, .service_count = 0, .idle_kill = 0, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &dns_client_unix_listeners_buf, sizeof(dns_client_unix_listeners[0]) } }, .fifo_listeners = ARRAY_INIT, .inet_listeners = ARRAY_INIT }; dovecot-2.3.21.1/src/lda/0000755000000000000000000000000014656633640011714 500000000000000dovecot-2.3.21.1/src/lda/Makefile.in0000644000000000000000000006342014656633610013703 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ pkglibexec_PROGRAMS = dovecot-lda$(EXEEXT) subdir = src/lda ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(pkglibexecdir)" PROGRAMS = $(pkglibexec_PROGRAMS) am_dovecot_lda_OBJECTS = main.$(OBJEXT) dovecot_lda_OBJECTS = $(am_dovecot_lda_OBJECTS) am__DEPENDENCIES_1 = AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = dovecot_lda_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(dovecot_lda_LDFLAGS) $(LDFLAGS) -o $@ AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/main.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(dovecot_lda_SOURCES) DIST_SOURCES = $(dovecot_lda_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) pkglibexecdir = $(libexecdir)/dovecot ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-smtp \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-smtp \ -I$(top_srcdir)/src/lib-lda \ -I$(top_srcdir)/src/lib-storage \ -I$(top_srcdir)/src/lib-storage/index \ -I$(top_srcdir)/src/lib-storage/index/raw \ $(BINARY_CFLAGS) dovecot_lda_LDFLAGS = -export-dynamic dovecot_lda_LDADD = \ $(LIBDOVECOT_LDA) \ $(LIBDOVECOT_STORAGE) \ $(LIBDOVECOT) \ $(BINARY_LDFLAGS) dovecot_lda_DEPENDENCIES = \ $(LIBDOVECOT_LDA) \ $(LIBDOVECOT_STORAGE_DEPS) \ $(LIBDOVECOT_DEPS) dovecot_lda_SOURCES = \ main.c all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lda/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/lda/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files clean-pkglibexecPROGRAMS: @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list dovecot-lda$(EXEEXT): $(dovecot_lda_OBJECTS) $(dovecot_lda_DEPENDENCIES) $(EXTRA_dovecot_lda_DEPENDENCIES) @rm -f dovecot-lda$(EXEEXT) $(AM_V_CCLD)$(dovecot_lda_LINK) $(dovecot_lda_OBJECTS) $(dovecot_lda_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am check: check-am all-am: Makefile $(PROGRAMS) installdirs: for dir in "$(DESTDIR)$(pkglibexecdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-pkglibexecPROGRAMS \ mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/main.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-exec-local install-pkglibexecPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/main.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkglibexecPROGRAMS .MAKE: install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ clean-generic clean-libtool clean-pkglibexecPROGRAMS \ cscopelist-am ctags ctags-am distclean distclean-compile \ distclean-generic distclean-libtool distclean-tags distdir dvi \ dvi-am html html-am info info-am install install-am \ install-data install-data-am install-dvi install-dvi-am \ install-exec install-exec-am install-exec-local install-html \ install-html-am install-info install-info-am install-man \ install-pdf install-pdf-am install-pkglibexecPROGRAMS \ install-ps install-ps-am install-strip installcheck \ installcheck-am installdirs maintainer-clean \ maintainer-clean-generic mostlyclean mostlyclean-compile \ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ tags tags-am uninstall uninstall-am \ uninstall-pkglibexecPROGRAMS .PRECIOUS: Makefile install-exec-local: rm -f $(DESTDIR)$(pkglibexecdir)/deliver $(LN_S) dovecot-lda $(DESTDIR)$(pkglibexecdir)/deliver # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/lda/main.c0000644000000000000000000003764414656633576012752 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "lib-signals.h" #include "ioloop.h" #include "env-util.h" #include "istream.h" #include "istream-seekable.h" #include "path-util.h" #include "safe-mkstemp.h" #include "eacces-error.h" #include "ipwd.h" #include "str.h" #include "str-sanitize.h" #include "strescape.h" #include "unichar.h" #include "rfc822-parser.h" #include "message-address.h" #include "smtp-address.h" #include "settings-parser.h" #include "master-service.h" #include "master-service-settings.h" #include "mail-storage-service.h" #include "mail-namespace.h" #include "raw-storage.h" #include "mail-deliver.h" #include "mail-send.h" #include "mbox-from.h" #include "smtp-submit-settings.h" #include "lda-settings.h" #include #include const struct smtp_address default_envelope_sender = { .localpart = "MAILER-DAEMON", }; /* After buffer grows larger than this, create a temporary file to /tmp where to read the mail. */ #define MAIL_MAX_MEMORY_BUFFER (1024*128) struct event_category event_category_lda = { .name = "lda", }; static const char *wanted_headers[] = { "From", "To", "Message-ID", "Subject", "Return-Path", NULL }; static int seekable_fd_callback(const char **path_r, void *context) { struct mail_deliver_input *dinput = context; string_t *path; int fd; path = t_str_new(128); mail_user_set_get_temp_prefix(path, dinput->rcpt_user->set); fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1); if (fd == -1) { i_error("safe_mkstemp(%s) failed: %m", str_c(path)); return -1; } /* we just want the fd, unlink it */ if (i_unlink(str_c(path)) < 0) { /* shouldn't happen.. */ i_close_fd(&fd); return -1; } *path_r = str_c(path); return fd; } static struct istream * create_raw_stream(struct mail_deliver_input *dinput, int fd, time_t *mtime_r) { struct istream *input, *input2, *input_list[2]; const unsigned char *data; const char *error; char *sender = NULL; size_t i, size; int ret, tz; *mtime_r = (time_t)-1; fd_set_nonblock(fd, FALSE); input = i_stream_create_fd(fd, 4096); input->blocking = TRUE; /* If input begins with a From-line, drop it */ ret = i_stream_read_bytes(input, &data, &size, 5); if (ret > 0 && memcmp(data, "From ", 5) == 0) { /* skip until the first LF */ i_stream_skip(input, 5); while (i_stream_read_more(input, &data, &size) > 0) { for (i = 0; i < size; i++) { if (data[i] == '\n') break; } if (i != size) { (void)mbox_from_parse(data, i, mtime_r, &tz, &sender); i_stream_skip(input, i + 1); break; } i_stream_skip(input, size); } } if (sender != NULL && dinput->mail_from == NULL) { struct smtp_address *mail_from = NULL; /* use the envelope sender from From_-line, but only if it hasn't been specified with -f already. */ if (smtp_address_parse_mailbox(pool_datastack_create(), sender, 0, &mail_from, &error) < 0) { i_warning("Failed to parse address from `From_'-line: %s", error); } dinput->mail_from = mail_from; } i_free(sender); if (input->v_offset == 0) { input2 = input; i_stream_ref(input2); } else { input2 = i_stream_create_limit(input, UOFF_T_MAX); } i_stream_unref(&input); input_list[0] = input2; input_list[1] = NULL; input = i_stream_create_seekable(input_list, MAIL_MAX_MEMORY_BUFFER, seekable_fd_callback, dinput); i_stream_unref(&input2); return input; } static struct mail * lda_raw_mail_open(struct mail_deliver_input *dinput, const char *path) { struct mail_user *raw_mail_user; struct mailbox *box; struct mailbox_transaction_context *t; struct mail *mail; struct mailbox_header_lookup_ctx *headers_ctx; const struct smtp_address *mail_from; struct istream *input; void **sets; time_t mtime; int ret; sets = master_service_settings_get_others(master_service); raw_mail_user = raw_storage_create_from_set(dinput->rcpt_user->set_info, sets[0]); mail_from = (dinput->mail_from != NULL ? dinput->mail_from : &default_envelope_sender); if (path == NULL) { input = create_raw_stream(dinput, 0, &mtime); i_stream_set_name(input, "stdin"); ret = raw_mailbox_alloc_stream(raw_mail_user, input, mtime, smtp_address_encode(mail_from), &box); i_stream_unref(&input); } else { ret = raw_mailbox_alloc_path(raw_mail_user, path, (time_t)-1, smtp_address_encode(mail_from), &box); } if (ret < 0) { i_fatal("Can't open delivery mail as raw: %s", mailbox_get_last_internal_error(box, NULL)); } mail_user_unref(&raw_mail_user); t = mailbox_transaction_begin(box, 0, __func__); headers_ctx = mailbox_header_lookup_init(box, wanted_headers); mail = mail_alloc(t, 0, headers_ctx); mailbox_header_lookup_unref(&headers_ctx); mail_set_seq(mail, 1); return mail; } static void lda_set_rcpt_to(struct mail_deliver_input *dinput, const struct smtp_address *rcpt_to, const char *user, const char *rcpt_to_source) { const char *error; if (rcpt_to == NULL && *dinput->set->lda_original_recipient_header != '\0') { rcpt_to = mail_deliver_get_address( dinput->src_mail, dinput->set->lda_original_recipient_header); rcpt_to_source = t_strconcat( dinput->set->lda_original_recipient_header, " header", NULL); } if (rcpt_to == NULL) { struct smtp_address *user_addr; if (smtp_address_parse_username(pool_datastack_create(), user, &user_addr, &error) < 0) { i_fatal_status(EX_USAGE, "Cannot obtain SMTP address from username `%s': %s", user, error); } if (user_addr->domain == NULL) user_addr->domain = dinput->set->hostname; rcpt_to = user_addr; rcpt_to_source = "user@hostname"; } dinput->rcpt_params.orcpt.addr = rcpt_to; if (dinput->rcpt_to == NULL) dinput->rcpt_to = rcpt_to; e_debug(dinput->rcpt_user->event, "Destination address: %s (source: %s)", smtp_address_encode_path(rcpt_to), rcpt_to_source); } static int lda_do_deliver(struct mail_deliver_context *ctx, bool stderr_rejection) { enum mail_deliver_error error_code; const char *error; int ret; if (mail_deliver(ctx, &error_code, &error) >= 0) return EX_OK; if (error_code == MAIL_DELIVER_ERROR_INTERNAL) { /* This shouldn't happen */ return EX_TEMPFAIL; } if (stderr_rejection) { /* write to stderr also for tempfails so that MTA can log the reason if it wants to. */ fprintf(stderr, "%s\n", error); } switch (error_code) { case MAIL_DELIVER_ERROR_NONE: i_unreached(); case MAIL_DELIVER_ERROR_TEMPORARY: return EX_TEMPFAIL; case MAIL_DELIVER_ERROR_REJECTED: break; case MAIL_DELIVER_ERROR_NOQUOTA: if (ctx->set->quota_full_tempfail) return EX_TEMPFAIL; ctx->mailbox_full = TRUE; break; case MAIL_DELIVER_ERROR_INTERNAL: i_unreached(); } /* Rejected */ ctx->dsn = TRUE; /* we'll have to reply with permanent failure */ mail_deliver_log(ctx, "rejected: %s", str_sanitize(error, 512)); if (stderr_rejection) return EX_NOPERM; ret = mail_send_rejection(ctx, ctx->rcpt_to, error); if (ret != 0) return ret < 0 ? EX_TEMPFAIL : ret; /* ok, rejection sent */ return EX_OK; } static int lda_deliver(struct mail_deliver_input *dinput, struct mail_storage_service_user *service_user, const char *user, const char *path, struct smtp_address *rcpt_to, const char *rcpt_to_source, bool stderr_rejection) { struct mail_deliver_context ctx; const struct var_expand_table *var_table; struct lda_settings *lda_set; struct smtp_submit_settings *smtp_set; const char *errstr; int ret; var_table = mail_user_var_expand_table(dinput->rcpt_user); smtp_set = mail_storage_service_user_get_set(service_user)[1]; lda_set = mail_storage_service_user_get_set(service_user)[2]; ret = settings_var_expand( &lda_setting_parser_info, lda_set, dinput->rcpt_user->pool, var_table, &errstr); if (ret > 0) { ret = settings_var_expand( &smtp_submit_setting_parser_info, smtp_set, dinput->rcpt_user->pool, var_table, &errstr); } if (ret <= 0) i_fatal("Failed to expand settings: %s", errstr); dinput->set = lda_set; dinput->smtp_set = smtp_set; dinput->src_mail = lda_raw_mail_open(dinput, path); lda_set_rcpt_to(dinput, rcpt_to, user, rcpt_to_source); mail_deliver_init(&ctx, dinput); ret = lda_do_deliver(&ctx, stderr_rejection); mail_deliver_deinit(&ctx); return ret; } static void failure_exit_callback(int *status) { /* we want all our exit codes to be sysexits.h compatible. if we failed because of a logging related error, we most likely aren't writing to stderr, so try writing there to give some kind of a clue what's wrong. FATAL_LOGOPEN failure already wrote to stderr, so don't duplicate it. */ switch (*status) { case FATAL_LOGWRITE: fputs("Failed to write to log file", stderr); break; case FATAL_LOGERROR: fputs("Internal logging error", stderr); break; case FATAL_LOGOPEN: case FATAL_OUTOFMEM: case FATAL_EXEC: case FATAL_DEFAULT: break; default: return; } *status = EX_TEMPFAIL; } static void print_help(void) { printf( "Usage: dovecot-lda [-c ] [-d ] [-p ]\n" " [-m ] [-e] [-k] [-f ]\n" " [-a ]\n" " [-r ] \n"); } int main(int argc, char *argv[]) { const struct setting_parser_info *set_roots[] = { &smtp_submit_setting_parser_info, &lda_setting_parser_info, NULL }; struct mail_deliver_input dinput; enum mail_storage_service_flags service_flags = 0; const char *user, *errstr, *path; struct smtp_address *rcpt_to, *final_rcpt_to, *mail_from; struct mail_storage_service_ctx *storage_service; struct mail_storage_service_user *service_user; struct mail_storage_service_input service_input; struct event *event; const char *user_source = "", *rcpt_to_source = "", *mail_from_error; uid_t process_euid; bool stderr_rejection = FALSE; int ret, c; if (getuid() != geteuid() && geteuid() == 0) { /* running setuid - don't allow this if the binary is executable by anyone */ struct stat st; if (stat(argv[0], &st) < 0) { fprintf(stderr, "stat(%s) failed: %s\n", argv[0], strerror(errno)); return EX_TEMPFAIL; } else if ((st.st_mode & 1) != 0 && (st.st_mode & 04000) != 0) { fprintf(stderr, "%s must not be both world-executable " "and setuid-root. This allows root exploits. " "See http://wiki2.dovecot.org/LDA#multipleuids\n", argv[0]); return EX_TEMPFAIL; } } i_set_failure_exit_callback(failure_exit_callback); master_service = master_service_init("lda", MASTER_SERVICE_FLAG_STANDALONE | MASTER_SERVICE_FLAG_DONT_LOG_TO_STDERR | MASTER_SERVICE_FLAG_NO_INIT_DATASTACK_FRAME, &argc, &argv, "a:d:ef:m:p:r:"); event = event_create(NULL); event_add_category(event, &event_category_lda); i_zero(&dinput); dinput.session = mail_deliver_session_init(); dinput.rcpt_default_mailbox = "INBOX"; path = NULL; user = getenv("USER"); mail_from = final_rcpt_to = rcpt_to = NULL; mail_from_error = NULL; while ((c = master_getopt(master_service)) > 0) { switch (c) { case 'a': /* original recipient address */ if (smtp_address_parse_path( pool_datastack_create(), optarg, SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART | SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL, &rcpt_to, &errstr) < 0) { i_fatal_status(EX_USAGE, "Invalid -a parameter: %s", errstr); } rcpt_to_source = "-a parameter"; break; case 'd': /* destination user */ user = optarg; service_flags |= MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP; break; case 'e': stderr_rejection = TRUE; break; case 'f': /* envelope sender address */ ret = smtp_address_parse_path( pool_datastack_create(), optarg, SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL | SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART | SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY | SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN | SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW, &mail_from, &errstr); if (ret < 0 && !smtp_address_is_broken(mail_from)) { i_fatal_status(EX_USAGE, "Invalid -f parameter: %s", errstr); } if (ret < 0) mail_from_error = errstr; break; case 'm': /* destination mailbox. Ignore -m "". This allows doing -m ${extension} in Postfix to handle user+mailbox */ if (*optarg != '\0') T_BEGIN { if (!uni_utf8_str_is_valid(optarg)) { i_fatal("Mailbox name not UTF-8: %s", optarg); } dinput.rcpt_default_mailbox = optarg; } T_END; break; case 'p': /* input path */ if (t_abspath(optarg, &path, &errstr) < 0) { i_fatal("t_abspath(%s) failed: %s", optarg, errstr); } break; case 'r': /* final recipient address */ if (smtp_address_parse_path( pool_datastack_create(), optarg, SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART | SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL, &final_rcpt_to, &errstr) < 0) { i_fatal_status(EX_USAGE, "Invalid -r parameter: %s", errstr); } break; default: print_help(); return EX_USAGE; } } if (optind != argc) { print_help(); i_fatal_status(EX_USAGE, "Unknown argument: %s", argv[optind]); } process_euid = geteuid(); if ((service_flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) != 0) ; else if (process_euid != 0) { /* we're non-root. get our username and possibly our home. */ struct passwd pw; const char *home; home = getenv("HOME"); if (user != NULL && home != NULL) { /* no need for a pw lookup */ user_source = "USER environment"; } else if ((ret = i_getpwuid(process_euid, &pw)) > 0) { user = t_strdup(pw.pw_name); if (home == NULL) env_put("HOME", pw.pw_dir); user_source = "passwd lookup for process euid"; } else if (ret < 0) { /* temporary failure */ i_fatal("getpwuid() failed: %m"); } else if (user == NULL) { i_fatal_status(EX_USAGE, "Couldn't lookup our username (uid=%s)", dec2str(process_euid)); } } else { i_fatal_status(EX_USAGE, "destination user parameter (-d user) not given"); } master_service_init_finish(master_service); dinput.mail_from = mail_from; dinput.rcpt_to = final_rcpt_to; event_add_str(event, "protocol", "lda"); event_add_str(event, "user", user); if (mail_from != NULL) { event_add_str(event, "mail_from", smtp_address_encode(mail_from)); } if (final_rcpt_to != NULL) { event_add_str(event, "rcpt_to", smtp_address_encode(final_rcpt_to)); } dinput.event_parent = event; i_zero(&service_input); service_input.module = "lda"; service_input.service = "lda"; service_input.username = user; service_input.event_parent = event; service_flags |= MAIL_STORAGE_SERVICE_FLAG_USE_SYSEXITS; storage_service = mail_storage_service_init(master_service, set_roots, service_flags); mail_deliver_hooks_init(); /* set before looking up the user (or ideally we'd do this between _lookup() and _next(), but don't bother) */ dinput.delivery_time_started = ioloop_timeval; ret = mail_storage_service_lookup_next(storage_service, &service_input, &service_user, &dinput.rcpt_user, &errstr); if (ret <= 0) { if (ret < 0) i_fatal("%s", errstr); ret = EX_NOUSER; } else { #ifdef SIGXFSZ lib_signals_ignore(SIGXFSZ, TRUE); #endif if (*user_source != '\0') { e_debug(dinput.rcpt_user->event, "userdb lookup skipped, username taken from %s", user_source); } if (mail_from_error != NULL) { e_debug(event, "Broken -f parameter: %s " "(proceeding with <> as sender)", mail_from_error); } ret = lda_deliver(&dinput, service_user, user, path, rcpt_to, rcpt_to_source, stderr_rejection); struct mailbox_transaction_context *t = dinput.src_mail->transaction; struct mailbox *box = dinput.src_mail->box; mail_free(&dinput.src_mail); mailbox_transaction_rollback(&t); mailbox_free(&box); mail_user_deinit(&dinput.rcpt_user); mail_storage_service_user_unref(&service_user); } mail_deliver_session_deinit(&dinput.session); mail_storage_service_deinit(&storage_service); event_unref(&event); master_service_deinit(&master_service); return ret; } dovecot-2.3.21.1/src/lda/Makefile.am0000644000000000000000000000162314656633576013702 00000000000000pkglibexecdir = $(libexecdir)/dovecot pkglibexec_PROGRAMS = dovecot-lda AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-smtp \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-smtp \ -I$(top_srcdir)/src/lib-lda \ -I$(top_srcdir)/src/lib-storage \ -I$(top_srcdir)/src/lib-storage/index \ -I$(top_srcdir)/src/lib-storage/index/raw \ $(BINARY_CFLAGS) dovecot_lda_LDFLAGS = -export-dynamic dovecot_lda_LDADD = \ $(LIBDOVECOT_LDA) \ $(LIBDOVECOT_STORAGE) \ $(LIBDOVECOT) \ $(BINARY_LDFLAGS) dovecot_lda_DEPENDENCIES = \ $(LIBDOVECOT_LDA) \ $(LIBDOVECOT_STORAGE_DEPS) \ $(LIBDOVECOT_DEPS) dovecot_lda_SOURCES = \ main.c install-exec-local: rm -f $(DESTDIR)$(pkglibexecdir)/deliver $(LN_S) dovecot-lda $(DESTDIR)$(pkglibexecdir)/deliver dovecot-2.3.21.1/src/lib-sql/0000755000000000000000000000000014656633637012525 500000000000000dovecot-2.3.21.1/src/lib-sql/sql-api.c0000644000000000000000000004743114656633576014172 00000000000000/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "ioloop.h" #include "hash.h" #include "str.h" #include "time-util.h" #include "sql-api-private.h" #include struct event_category event_category_sql = { .name = "sql", }; struct sql_db_module_register sql_db_module_register = { 0 }; ARRAY_TYPE(sql_drivers) sql_drivers; void sql_drivers_init(void) { i_array_init(&sql_drivers, 8); } void sql_drivers_deinit(void) { array_free(&sql_drivers); } static const struct sql_db *sql_driver_lookup(const char *name) { const struct sql_db *const *drivers; unsigned int i, count; drivers = array_get(&sql_drivers, &count); for (i = 0; i < count; i++) { if (strcmp(drivers[i]->name, name) == 0) return drivers[i]; } return NULL; } void sql_driver_register(const struct sql_db *driver) { if (sql_driver_lookup(driver->name) != NULL) { i_fatal("sql_driver_register(%s): Already registered", driver->name); } array_push_back(&sql_drivers, &driver); } void sql_driver_unregister(const struct sql_db *driver) { const struct sql_db *const *drivers; unsigned int i, count; drivers = array_get(&sql_drivers, &count); for (i = 0; i < count; i++) { if (drivers[i] == driver) { array_delete(&sql_drivers, i, 1); break; } } } struct sql_db *sql_init(const char *db_driver, const char *connect_string) { const char *error; struct sql_db *db; struct sql_settings set = { .driver = db_driver, .connect_string = connect_string, }; if (sql_init_full(&set, &db, &error) < 0) i_fatal("%s", error); return db; } int sql_init_full(const struct sql_settings *set, struct sql_db **db_r, const char **error_r) { const struct sql_db *driver; struct sql_db *db; int ret = 0; i_assert(set->connect_string != NULL); driver = sql_driver_lookup(set->driver); if (driver == NULL) { *error_r = t_strdup_printf("Unknown database driver '%s'", set->driver); return -1; } if ((driver->flags & SQL_DB_FLAG_POOLED) == 0) { if (driver->v.init_full == NULL) { db = driver->v.init(set->connect_string); } else ret = driver->v.init_full(set, &db, error_r); } else ret = driver_sqlpool_init_full(set, driver, &db, error_r); if (ret < 0) return -1; sql_init_common(db); *db_r = db; return 0; } void sql_init_common(struct sql_db *db) { db->refcount = 1; i_array_init(&db->module_contexts, 5); hash_table_create(&db->prepared_stmt_hash, default_pool, 0, str_hash, strcmp); } void sql_ref(struct sql_db *db) { i_assert(db->refcount > 0); db->refcount++; } static void default_sql_prepared_statement_deinit(struct sql_prepared_statement *prep_stmt) { i_free(prep_stmt->query_template); i_free(prep_stmt); } static void sql_prepared_statements_free(struct sql_db *db) { struct hash_iterate_context *iter; struct sql_prepared_statement *prep_stmt; char *query; iter = hash_table_iterate_init(db->prepared_stmt_hash); while (hash_table_iterate(iter, db->prepared_stmt_hash, &query, &prep_stmt)) { i_assert(prep_stmt->refcount == 0); if (prep_stmt->db->v.prepared_statement_deinit != NULL) prep_stmt->db->v.prepared_statement_deinit(prep_stmt); else default_sql_prepared_statement_deinit(prep_stmt); } hash_table_iterate_deinit(&iter); hash_table_clear(db->prepared_stmt_hash, TRUE); } void sql_unref(struct sql_db **_db) { struct sql_db *db = *_db; *_db = NULL; i_assert(db->refcount > 0); if (db->v.unref != NULL) db->v.unref(db); if (--db->refcount > 0) return; timeout_remove(&db->to_reconnect); sql_prepared_statements_free(db); hash_table_destroy(&db->prepared_stmt_hash); db->v.deinit(db); } enum sql_db_flags sql_get_flags(struct sql_db *db) { if (db->v.get_flags != NULL) return db->v.get_flags(db); else return db->flags; } int sql_connect(struct sql_db *db) { time_t now; switch (db->state) { case SQL_DB_STATE_DISCONNECTED: break; case SQL_DB_STATE_CONNECTING: return 0; default: return 1; } /* don't try reconnecting more than once a second */ now = time(NULL); if (db->last_connect_try + (time_t)db->connect_delay > now) return -1; db->last_connect_try = now; return db->v.connect(db); } void sql_disconnect(struct sql_db *db) { timeout_remove(&db->to_reconnect); db->v.disconnect(db); } const char *sql_escape_string(struct sql_db *db, const char *string) { return db->v.escape_string(db, string); } const char *sql_escape_blob(struct sql_db *db, const unsigned char *data, size_t size) { return db->v.escape_blob(db, data, size); } void sql_exec(struct sql_db *db, const char *query) { db->v.exec(db, query); } #undef sql_query void sql_query(struct sql_db *db, const char *query, sql_query_callback_t *callback, void *context) { db->v.query(db, query, callback, context); } struct sql_result *sql_query_s(struct sql_db *db, const char *query) { return db->v.query_s(db, query); } static struct sql_prepared_statement * default_sql_prepared_statement_init(struct sql_db *db, const char *query_template) { struct sql_prepared_statement *prep_stmt; prep_stmt = i_new(struct sql_prepared_statement, 1); prep_stmt->db = db; prep_stmt->refcount = 1; prep_stmt->query_template = i_strdup(query_template); return prep_stmt; } static struct sql_statement * default_sql_statement_init_prepared(struct sql_prepared_statement *stmt) { return sql_statement_init(stmt->db, stmt->query_template); } const char *sql_statement_get_log_query(struct sql_statement *stmt) { if (stmt->no_log_expanded_values) return stmt->query_template; return sql_statement_get_query(stmt); } const char *sql_statement_get_query(struct sql_statement *stmt) { string_t *query = t_str_new(128); const char *const *args; unsigned int i, args_count, arg_pos = 0; args = array_get(&stmt->args, &args_count); for (i = 0; stmt->query_template[i] != '\0'; i++) { if (stmt->query_template[i] == '?') { if (arg_pos >= args_count || args[arg_pos] == NULL) { i_panic("lib-sql: Missing bind for arg #%u in statement: %s", arg_pos, stmt->query_template); } str_append(query, args[arg_pos++]); } else { str_append_c(query, stmt->query_template[i]); } } if (arg_pos != args_count) { i_panic("lib-sql: Too many bind args (%u) for statement: %s", args_count, stmt->query_template); } return str_c(query); } static void default_sql_statement_query(struct sql_statement *stmt, sql_query_callback_t *callback, void *context) { sql_query(stmt->db, sql_statement_get_query(stmt), callback, context); pool_unref(&stmt->pool); } static struct sql_result * default_sql_statement_query_s(struct sql_statement *stmt) { struct sql_result *result = sql_query_s(stmt->db, sql_statement_get_query(stmt)); pool_unref(&stmt->pool); return result; } static void default_sql_update_stmt(struct sql_transaction_context *ctx, struct sql_statement *stmt, unsigned int *affected_rows) { ctx->db->v.update(ctx, sql_statement_get_query(stmt), affected_rows); pool_unref(&stmt->pool); } struct sql_prepared_statement * sql_prepared_statement_init(struct sql_db *db, const char *query_template) { struct sql_prepared_statement *stmt; stmt = hash_table_lookup(db->prepared_stmt_hash, query_template); if (stmt != NULL) { stmt->refcount++; return stmt; } if (db->v.prepared_statement_init != NULL) stmt = db->v.prepared_statement_init(db, query_template); else stmt = default_sql_prepared_statement_init(db, query_template); hash_table_insert(db->prepared_stmt_hash, stmt->query_template, stmt); return stmt; } void sql_prepared_statement_unref(struct sql_prepared_statement **_prep_stmt) { struct sql_prepared_statement *prep_stmt = *_prep_stmt; *_prep_stmt = NULL; i_assert(prep_stmt->refcount > 0); prep_stmt->refcount--; } static void sql_statement_init_fields(struct sql_statement *stmt, struct sql_db *db) { stmt->db = db; p_array_init(&stmt->args, stmt->pool, 8); } struct sql_statement * sql_statement_init(struct sql_db *db, const char *query_template) { struct sql_statement *stmt; if (db->v.statement_init != NULL) stmt = db->v.statement_init(db, query_template); else { pool_t pool = pool_alloconly_create("sql statement", 1024); stmt = p_new(pool, struct sql_statement, 1); stmt->pool = pool; } stmt->query_template = p_strdup(stmt->pool, query_template); sql_statement_init_fields(stmt, db); return stmt; } struct sql_statement * sql_statement_init_prepared(struct sql_prepared_statement *prep_stmt) { struct sql_statement *stmt; if (prep_stmt->db->v.statement_init_prepared == NULL) return default_sql_statement_init_prepared(prep_stmt); stmt = prep_stmt->db->v.statement_init_prepared(prep_stmt); sql_statement_init_fields(stmt, prep_stmt->db); return stmt; } void sql_statement_abort(struct sql_statement **_stmt) { struct sql_statement *stmt = *_stmt; *_stmt = NULL; if (stmt->db->v.statement_abort != NULL) stmt->db->v.statement_abort(stmt); pool_unref(&stmt->pool); } void sql_statement_set_timestamp(struct sql_statement *stmt, const struct timespec *ts) { if (stmt->db->v.statement_set_timestamp != NULL) stmt->db->v.statement_set_timestamp(stmt, ts); } void sql_statement_set_no_log_expanded_values(struct sql_statement *stmt, bool no_expand) { stmt->no_log_expanded_values = no_expand; } void sql_statement_bind_str(struct sql_statement *stmt, unsigned int column_idx, const char *value) { const char *escaped_value = p_strdup_printf(stmt->pool, "'%s'", sql_escape_string(stmt->db, value)); array_idx_set(&stmt->args, column_idx, &escaped_value); if (stmt->db->v.statement_bind_str != NULL) stmt->db->v.statement_bind_str(stmt, column_idx, value); } void sql_statement_bind_binary(struct sql_statement *stmt, unsigned int column_idx, const void *value, size_t value_size) { const char *value_str = p_strdup_printf(stmt->pool, "%s", sql_escape_blob(stmt->db, value, value_size)); array_idx_set(&stmt->args, column_idx, &value_str); if (stmt->db->v.statement_bind_binary != NULL) { stmt->db->v.statement_bind_binary(stmt, column_idx, value, value_size); } } void sql_statement_bind_int64(struct sql_statement *stmt, unsigned int column_idx, int64_t value) { const char *value_str = p_strdup_printf(stmt->pool, "%"PRId64, value); array_idx_set(&stmt->args, column_idx, &value_str); if (stmt->db->v.statement_bind_int64 != NULL) stmt->db->v.statement_bind_int64(stmt, column_idx, value); } #undef sql_statement_query void sql_statement_query(struct sql_statement **_stmt, sql_query_callback_t *callback, void *context) { struct sql_statement *stmt = *_stmt; *_stmt = NULL; if (stmt->db->v.statement_query != NULL) stmt->db->v.statement_query(stmt, callback, context); else default_sql_statement_query(stmt, callback, context); } struct sql_result *sql_statement_query_s(struct sql_statement **_stmt) { struct sql_statement *stmt = *_stmt; *_stmt = NULL; if (stmt->db->v.statement_query_s != NULL) return stmt->db->v.statement_query_s(stmt); else return default_sql_statement_query_s(stmt); } void sql_result_ref(struct sql_result *result) { result->refcount++; } void sql_result_unref(struct sql_result *result) { i_assert(result->refcount > 0); if (--result->refcount > 0) return; i_free(result->map); result->v.free(result); } static const struct sql_field_def * sql_field_def_find(const struct sql_field_def *fields, const char *name) { unsigned int i; for (i = 0; fields[i].name != NULL; i++) { if (strcasecmp(fields[i].name, name) == 0) return &fields[i]; } return NULL; } static void sql_result_build_map(struct sql_result *result, const struct sql_field_def *fields, size_t dest_size) { const struct sql_field_def *def; const char *name; unsigned int i, count, field_size = 0; count = sql_result_get_fields_count(result); result->map_size = count; result->map = i_new(struct sql_field_map, result->map_size); for (i = 0; i < count; i++) { name = sql_result_get_field_name(result, i); def = sql_field_def_find(fields, name); if (def != NULL) { result->map[i].type = def->type; result->map[i].offset = def->offset; switch (def->type) { case SQL_TYPE_STR: field_size = sizeof(const char *); break; case SQL_TYPE_UINT: field_size = sizeof(unsigned int); break; case SQL_TYPE_ULLONG: field_size = sizeof(unsigned long long); break; case SQL_TYPE_BOOL: field_size = sizeof(bool); break; } i_assert(def->offset + field_size <= dest_size); } else { result->map[i].offset = SIZE_MAX; } } } void sql_result_setup_fetch(struct sql_result *result, const struct sql_field_def *fields, void *dest, size_t dest_size) { if (result->map == NULL) sql_result_build_map(result, fields, dest_size); result->fetch_dest = dest; result->fetch_dest_size = dest_size; } static void sql_result_fetch(struct sql_result *result) { unsigned int i, count; const char *value; void *ptr; memset(result->fetch_dest, 0, result->fetch_dest_size); count = result->map_size; for (i = 0; i < count; i++) { if (result->map[i].offset == SIZE_MAX) continue; value = sql_result_get_field_value(result, i); ptr = STRUCT_MEMBER_P(result->fetch_dest, result->map[i].offset); switch (result->map[i].type) { case SQL_TYPE_STR: { *((const char **)ptr) = value; break; } case SQL_TYPE_UINT: { if (value != NULL && str_to_uint(value, (unsigned int *)ptr) < 0) i_error("sql: Value not uint: %s", value); break; } case SQL_TYPE_ULLONG: { if (value != NULL && str_to_ullong(value, (unsigned long long *)ptr) < 0) i_error("sql: Value not ullong: %s", value); break; } case SQL_TYPE_BOOL: { if (value != NULL && (*value == 't' || *value == '1')) *((bool *)ptr) = TRUE; break; } } } } int sql_result_next_row(struct sql_result *result) { int ret; if ((ret = result->v.next_row(result)) <= 0) return ret; if (result->fetch_dest != NULL) sql_result_fetch(result); return 1; } #undef sql_result_more void sql_result_more(struct sql_result **result, sql_query_callback_t *callback, void *context) { i_assert((*result)->v.more != NULL); (*result)->v.more(result, TRUE, callback, context); } static void sql_result_more_sync_callback(struct sql_result *result, void *context) { struct sql_result **dest_result = context; *dest_result = result; } void sql_result_more_s(struct sql_result **result) { i_assert((*result)->v.more != NULL); (*result)->v.more(result, FALSE, sql_result_more_sync_callback, result); /* the callback must have been called */ i_assert(*result != NULL); } unsigned int sql_result_get_fields_count(struct sql_result *result) { return result->v.get_fields_count(result); } const char *sql_result_get_field_name(struct sql_result *result, unsigned int idx) { return result->v.get_field_name(result, idx); } int sql_result_find_field(struct sql_result *result, const char *field_name) { return result->v.find_field(result, field_name); } const char *sql_result_get_field_value(struct sql_result *result, unsigned int idx) { return result->v.get_field_value(result, idx); } const unsigned char * sql_result_get_field_value_binary(struct sql_result *result, unsigned int idx, size_t *size_r) { return result->v.get_field_value_binary(result, idx, size_r); } const char *sql_result_find_field_value(struct sql_result *result, const char *field_name) { return result->v.find_field_value(result, field_name); } const char *const *sql_result_get_values(struct sql_result *result) { return result->v.get_values(result); } const char *sql_result_get_error(struct sql_result *result) { return result->v.get_error(result); } enum sql_result_error_type sql_result_get_error_type(struct sql_result *result) { return result->error_type; } static void sql_result_not_connected_free(struct sql_result *result ATTR_UNUSED) { } static int sql_result_not_connected_next_row(struct sql_result *result ATTR_UNUSED) { return -1; } static const char * sql_result_not_connected_get_error(struct sql_result *result ATTR_UNUSED) { return SQL_ERRSTR_NOT_CONNECTED; } struct sql_transaction_context *sql_transaction_begin(struct sql_db *db) { return db->v.transaction_begin(db); } #undef sql_transaction_commit void sql_transaction_commit(struct sql_transaction_context **_ctx, sql_commit_callback_t *callback, void *context) { struct sql_transaction_context *ctx = *_ctx; *_ctx = NULL; ctx->db->v.transaction_commit(ctx, callback, context); } int sql_transaction_commit_s(struct sql_transaction_context **_ctx, const char **error_r) { struct sql_transaction_context *ctx = *_ctx; *_ctx = NULL; return ctx->db->v.transaction_commit_s(ctx, error_r); } void sql_transaction_rollback(struct sql_transaction_context **_ctx) { struct sql_transaction_context *ctx = *_ctx; *_ctx = NULL; ctx->db->v.transaction_rollback(ctx); } void sql_update(struct sql_transaction_context *ctx, const char *query) { ctx->db->v.update(ctx, query, NULL); } void sql_update_stmt(struct sql_transaction_context *ctx, struct sql_statement **_stmt) { struct sql_statement *stmt = *_stmt; *_stmt = NULL; if (ctx->db->v.update_stmt != NULL) ctx->db->v.update_stmt(ctx, stmt, NULL); else default_sql_update_stmt(ctx, stmt, NULL); } void sql_update_get_rows(struct sql_transaction_context *ctx, const char *query, unsigned int *affected_rows) { ctx->db->v.update(ctx, query, affected_rows); } void sql_update_stmt_get_rows(struct sql_transaction_context *ctx, struct sql_statement **_stmt, unsigned int *affected_rows) { struct sql_statement *stmt = *_stmt; *_stmt = NULL; if (ctx->db->v.update_stmt != NULL) ctx->db->v.update_stmt(ctx, stmt, affected_rows); else default_sql_update_stmt(ctx, stmt, affected_rows); } void sql_db_set_state(struct sql_db *db, enum sql_db_state state) { enum sql_db_state old_state = db->state; if (db->state == state) return; db->state = state; if (db->state_change_callback != NULL) { db->state_change_callback(db, old_state, db->state_change_context); } } void sql_transaction_add_query(struct sql_transaction_context *ctx, pool_t pool, const char *query, unsigned int *affected_rows) { struct sql_transaction_query *tquery; tquery = p_new(pool, struct sql_transaction_query, 1); tquery->trans = ctx; tquery->query = p_strdup(pool, query); tquery->affected_rows = affected_rows; if (ctx->head == NULL) ctx->head = tquery; else ctx->tail->next = tquery; ctx->tail = tquery; } void sql_connection_log_finished(struct sql_db *db) { struct event_passthrough *e = event_create_passthrough(db->event)-> set_name(SQL_CONNECTION_FINISHED); e_debug(e->event(), "Connection finished (queries=%"PRIu64", slow queries=%"PRIu64")", db->succeeded_queries + db->failed_queries, db->slow_queries); } struct event_passthrough * sql_query_finished_event(struct sql_db *db, struct event *event, const char *query, bool success, int *duration_r) { int diff; struct timeval tv; event_get_create_time(event, &tv); struct event_passthrough *e = event_create_passthrough(event)-> set_name(SQL_QUERY_FINISHED)-> add_str("query_first_word", t_strcut(query, ' ')); diff = timeval_diff_msecs(&ioloop_timeval, &tv); if (!success) { db->failed_queries++; } else { db->succeeded_queries++; } if (diff >= SQL_SLOW_QUERY_MSEC) { e->add_str("slow_query", "y"); db->slow_queries++; } if (duration_r != NULL) *duration_r = diff; return e; } struct event_passthrough *sql_transaction_finished_event(struct sql_transaction_context *ctx) { return event_create_passthrough(ctx->event)-> set_name(SQL_TRANSACTION_FINISHED); } void sql_wait(struct sql_db *db) { if (db->v.wait != NULL) db->v.wait(db); } struct sql_result sql_not_connected_result = { .v = { sql_result_not_connected_free, sql_result_not_connected_next_row, NULL, NULL, NULL, NULL, NULL, NULL, NULL, sql_result_not_connected_get_error, NULL, }, .failed_try_retry = TRUE }; dovecot-2.3.21.1/src/lib-sql/driver-test.c0000644000000000000000000003466614656633576015102 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "test-lib.h" #include "str.h" #include "buffer.h" #include "sql-api-private.h" #include "driver-test.h" #include "array.h" #include "hex-binary.h" struct test_sql_db { struct sql_db api; pool_t pool; ARRAY(struct test_driver_result) expected; const char *error; bool failed:1; }; struct test_sql_result { struct sql_result api; struct test_driver_result *result; const char *error; }; static struct sql_db *driver_test_mysql_init(const char *connect_string); static struct sql_db *driver_test_cassandra_init(const char *connect_string); static struct sql_db *driver_test_sqlite_init(const char *connect_string); static void driver_test_deinit(struct sql_db *_db); static int driver_test_connect(struct sql_db *_db); static void driver_test_disconnect(struct sql_db *_db); static const char * driver_test_mysql_escape_string(struct sql_db *_db, const char *string); static const char * driver_test_escape_string(struct sql_db *_db, const char *string); static void driver_test_exec(struct sql_db *_db, const char *query); static void driver_test_query(struct sql_db *_db, const char *query, sql_query_callback_t *callback, void *context); static struct sql_result * driver_test_query_s(struct sql_db *_db, const char *query); static struct sql_transaction_context * driver_test_transaction_begin(struct sql_db *_db); static void driver_test_transaction_commit(struct sql_transaction_context *ctx, sql_commit_callback_t *callback, void *context); static int driver_test_transaction_commit_s(struct sql_transaction_context *ctx, const char **error_r); static void driver_test_transaction_rollback(struct sql_transaction_context *ctx); static void driver_test_update(struct sql_transaction_context *ctx, const char *query, unsigned int *affected_rows); static const char * driver_test_mysql_escape_blob(struct sql_db *_db, const unsigned char *data, size_t size); static const char * driver_test_escape_blob(struct sql_db *_db, const unsigned char *data, size_t size); static void driver_test_result_free(struct sql_result *result); static int driver_test_result_next_row(struct sql_result *result); static unsigned int driver_test_result_get_fields_count(struct sql_result *result); static const char * driver_test_result_get_field_name(struct sql_result *result, unsigned int idx); static int driver_test_result_find_field(struct sql_result *result, const char *field_name); static const char * driver_test_result_get_field_value(struct sql_result *result, unsigned int idx); static const unsigned char * driver_test_result_get_field_value_binary(struct sql_result *result, unsigned int idx, size_t *size_r); static const char * driver_test_result_find_field_value(struct sql_result *result, const char *field_name); static const char *const * driver_test_result_get_values(struct sql_result *result); const char *driver_test_result_get_error(struct sql_result *result); const struct sql_db driver_test_mysql_db = { .name = "mysql", .flags = SQL_DB_FLAG_BLOCKING | SQL_DB_FLAG_ON_DUPLICATE_KEY, .v = { .init = driver_test_mysql_init, .deinit = driver_test_deinit, .connect = driver_test_connect, .disconnect = driver_test_disconnect, .escape_string = driver_test_mysql_escape_string, .exec = driver_test_exec, .query = driver_test_query, .query_s = driver_test_query_s, .transaction_begin = driver_test_transaction_begin, .transaction_commit = driver_test_transaction_commit, .transaction_commit_s = driver_test_transaction_commit_s, .transaction_rollback = driver_test_transaction_rollback, .update = driver_test_update, .escape_blob = driver_test_mysql_escape_blob, } }; const struct sql_db driver_test_cassandra_db = { .name = "cassandra", .v = { .init = driver_test_cassandra_init, .deinit = driver_test_deinit, .connect = driver_test_connect, .disconnect = driver_test_disconnect, .escape_string = driver_test_escape_string, .exec = driver_test_exec, .query = driver_test_query, .query_s = driver_test_query_s, .transaction_begin = driver_test_transaction_begin, .transaction_commit = driver_test_transaction_commit, .transaction_commit_s = driver_test_transaction_commit_s, .transaction_rollback = driver_test_transaction_rollback, .update = driver_test_update, .escape_blob = driver_test_escape_blob, } }; const struct sql_db driver_test_sqlite_db = { .name = "sqlite", .flags = SQL_DB_FLAG_ON_CONFLICT_DO | SQL_DB_FLAG_BLOCKING, .v = { .init = driver_test_sqlite_init, .deinit = driver_test_deinit, .connect = driver_test_connect, .disconnect = driver_test_disconnect, .escape_string = driver_test_escape_string, .exec = driver_test_exec, .query = driver_test_query, .query_s = driver_test_query_s, .transaction_begin = driver_test_transaction_begin, .transaction_commit = driver_test_transaction_commit, .transaction_commit_s = driver_test_transaction_commit_s, .transaction_rollback = driver_test_transaction_rollback, .update = driver_test_update, .escape_blob = driver_test_escape_blob, } }; const struct sql_result driver_test_result = { .v = { .free = driver_test_result_free, .next_row = driver_test_result_next_row, .get_fields_count = driver_test_result_get_fields_count, .get_field_name = driver_test_result_get_field_name, .find_field = driver_test_result_find_field, .get_field_value = driver_test_result_get_field_value, .get_field_value_binary = driver_test_result_get_field_value_binary, .find_field_value = driver_test_result_find_field_value, .get_values = driver_test_result_get_values, .get_error = driver_test_result_get_error, } }; void sql_driver_test_register(void) { sql_driver_register(&driver_test_mysql_db); sql_driver_register(&driver_test_cassandra_db); sql_driver_register(&driver_test_sqlite_db); } void sql_driver_test_unregister(void) { sql_driver_unregister(&driver_test_mysql_db); sql_driver_unregister(&driver_test_cassandra_db); sql_driver_unregister(&driver_test_sqlite_db); } static struct sql_db *driver_test_init(const struct sql_db *driver, const char *connect_string ATTR_UNUSED) { pool_t pool = pool_alloconly_create(MEMPOOL_GROWING" test sql driver", 2048); struct test_sql_db *ret = p_new(pool, struct test_sql_db, 1); ret->pool = pool; ret->api = *driver; p_array_init(&ret->expected, pool, 8); return &ret->api; } static struct sql_db *driver_test_mysql_init(const char *connect_string) { return driver_test_init(&driver_test_mysql_db, connect_string); } static struct sql_db *driver_test_cassandra_init(const char *connect_string) { return driver_test_init(&driver_test_cassandra_db, connect_string); } static struct sql_db *driver_test_sqlite_init(const char *connect_string) { return driver_test_init(&driver_test_sqlite_db, connect_string); } static void driver_test_deinit(struct sql_db *_db ATTR_UNUSED) { struct test_sql_db *db = (struct test_sql_db*)_db; array_free(&_db->module_contexts); pool_unref(&db->pool); } static int driver_test_connect(struct sql_db *_db ATTR_UNUSED) { /* nix */ return 0; } static void driver_test_disconnect(struct sql_db *_db ATTR_UNUSED) { } static const char * driver_test_mysql_escape_string(struct sql_db *_db ATTR_UNUSED, const char *string) { string_t *esc = t_str_new(strlen(string)); for(const char *ptr = string; *ptr != '\0'; ptr++) { if (*ptr == '\n' || *ptr == '\r' || *ptr == '\\' || *ptr == '\'' || *ptr == '\"' || *ptr == '\x1a') str_append_c(esc, '\\'); str_append_c(esc, *ptr); } return str_c(esc); } static const char * driver_test_escape_string(struct sql_db *_db ATTR_UNUSED, const char *string) { return string; } static void driver_test_exec(struct sql_db *_db, const char *query) { struct test_sql_db *db = (struct test_sql_db*)_db; struct test_driver_result *result = array_front_modifiable(&db->expected); i_assert(result->cur < result->nqueries); /* i_debug("DUMMY EXECUTE: %s", query); i_debug("DUMMY EXPECT : %s", result->queries[result->cur]); */ test_assert_strcmp(result->queries[result->cur], query); if (strcmp(result->queries[result->cur], query) != 0) { db->error = "Invalid query"; db->failed = TRUE; } result->cur++; } static void driver_test_query(struct sql_db *_db, const char *query, sql_query_callback_t *callback, void *context) { struct sql_result *result = driver_test_query_s(_db, query); if (callback != NULL) callback(result, context); } static struct sql_result * driver_test_query_s(struct sql_db *_db, const char *query) { struct test_sql_db *db = (struct test_sql_db*)_db; struct test_driver_result *result = array_front_modifiable(&db->expected); struct test_sql_result *res = i_new(struct test_sql_result, 1); driver_test_exec(_db, query); if (db->failed) { res->api.failed = TRUE; } res->api.v = driver_test_result.v; res->api.db = _db; if (result->result != NULL) { res->result = i_new(struct test_driver_result, 1); memcpy(res->result, result, sizeof(*result)); } res->api.refcount = 1; /* drop it from array if it's used up */ if (result->cur == result->nqueries) array_pop_front(&db->expected); return &res->api; } static struct sql_transaction_context * driver_test_transaction_begin(struct sql_db *_db) { struct sql_transaction_context *ctx = i_new(struct sql_transaction_context, 1); ctx->db = _db; return ctx; } static void driver_test_transaction_commit(struct sql_transaction_context *ctx, sql_commit_callback_t *callback, void *context) { struct sql_commit_result res; res.error_type = driver_test_transaction_commit_s(ctx, &res.error); callback(&res, context); } static int driver_test_transaction_commit_s(struct sql_transaction_context *ctx, const char **error_r) { struct test_sql_db *db = (struct test_sql_db*)ctx->db; int ret = 0; if (db->error != NULL) { *error_r = db->error; ret = -1; } i_free(ctx); db->error = NULL; db->failed = FALSE; return ret; } static void driver_test_transaction_rollback(struct sql_transaction_context *ctx) { struct test_sql_db *db = (struct test_sql_db*)ctx->db; i_free(ctx); db->error = NULL; db->failed = FALSE; } static void driver_test_update(struct sql_transaction_context *ctx, const char *query, unsigned int *affected_rows) { struct test_sql_db *db= (struct test_sql_db*)ctx->db; struct test_driver_result *result = array_front_modifiable(&db->expected); driver_test_exec(ctx->db, query); if (affected_rows != NULL) *affected_rows = result->affected_rows; /* drop it from array if it's used up */ if (result->cur == result->nqueries) array_pop_front(&db->expected); } static const char * driver_test_mysql_escape_blob(struct sql_db *_db ATTR_UNUSED, const unsigned char *data, size_t size) { return t_strdup_printf("X'%s'", binary_to_hex(data,size)); } static const char * driver_test_escape_blob(struct sql_db *_db ATTR_UNUSED, const unsigned char *data, size_t size) { return t_strdup_printf("X'%s'", binary_to_hex(data,size)); } static void driver_test_result_free(struct sql_result *result) { struct test_sql_result *tsr = (struct test_sql_result *)result; if (tsr->result != NULL) i_free(tsr->result); i_free(result); } static int driver_test_result_next_row(struct sql_result *result) { struct test_sql_result *tsr = (struct test_sql_result *)result; struct test_driver_result *r = tsr->result; if (r == NULL) return 0; struct test_driver_result_set *rs = &(r->result[r->cur-1]); if (rs->cur <= rs->rows) { rs->cur++; } return rs->cur <= rs->rows ? 1 : 0; } static unsigned int driver_test_result_get_fields_count(struct sql_result *result) { struct test_sql_result *tsr = (struct test_sql_result *)result; struct test_driver_result *r = tsr->result; struct test_driver_result_set *rs = &(r->result[r->cur-1]); return rs->cols; } static const char * driver_test_result_get_field_name(struct sql_result *result, unsigned int idx) { struct test_sql_result *tsr = (struct test_sql_result *)result; struct test_driver_result *r = tsr->result; struct test_driver_result_set *rs = &(r->result[r->cur-1]); i_assert(idx < rs->cols); return rs->col_names[idx]; } static int driver_test_result_find_field(struct sql_result *result, const char *field_name) { struct test_sql_result *tsr = (struct test_sql_result *)result; struct test_driver_result *r = tsr->result; struct test_driver_result_set *rs = &(r->result[r->cur-1]); for(size_t i = 0; i < rs->cols; i++) { if (strcmp(field_name, rs->col_names[i])==0) return i; } return -1; } static const char * driver_test_result_get_field_value(struct sql_result *result, unsigned int idx) { struct test_sql_result *tsr = (struct test_sql_result *)result; struct test_driver_result *r = tsr->result; struct test_driver_result_set *rs = &(r->result[r->cur-1]); i_assert(idx < rs->cols); i_assert(rs->cur <= rs->rows); return rs->row_data[rs->cur-1][idx]; } static const unsigned char * driver_test_result_get_field_value_binary(struct sql_result *result, unsigned int idx, size_t *size_r) { buffer_t *buf = t_buffer_create(64); const char *value = driver_test_result_get_field_value(result, idx); /* expect it hex encoded */ if (hex_to_binary(value, buf) < 0) { *size_r = 0; return NULL; } *size_r = buf->used; return buf->data; } static const char * driver_test_result_find_field_value(struct sql_result *result, const char *field_name) { int idx = driver_test_result_find_field(result, field_name); if (idx < 0) return NULL; return driver_test_result_get_field_value(result, idx); } static const char *const * driver_test_result_get_values(struct sql_result *result) { struct test_sql_result *tsr = (struct test_sql_result *)result; struct test_driver_result *r = tsr->result; struct test_driver_result_set *rs = &(r->result[r->cur-1]); i_assert(rs->cur <= rs->rows); return rs->row_data[rs->cur-1]; } const char *driver_test_result_get_error(struct sql_result *result) { struct test_sql_result *tsr = (struct test_sql_result *)result; return tsr->error; } void sql_driver_test_add_expected_result(struct sql_db *_db, const struct test_driver_result *result) { struct test_sql_db *db = (struct test_sql_db*)_db; array_push_back(&db->expected, result); } void sql_driver_test_clear_expected_results(struct sql_db *_db) { struct test_sql_db *db = (struct test_sql_db*)_db; array_clear(&db->expected); } dovecot-2.3.21.1/src/lib-sql/driver-sqlite.c0000644000000000000000000003354614656633576015420 00000000000000/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "ioloop.h" #include "str.h" #include "hex-binary.h" #include "sql-api-private.h" #ifdef BUILD_SQLITE #include /* retry time if db is busy (in ms) */ static const int sqlite_busy_timeout = 1000; struct sqlite_db { struct sql_db api; pool_t pool; const char *dbfile; sqlite3 *sqlite; bool connected:1; int rc; }; struct sqlite_result { struct sql_result api; sqlite3_stmt *stmt; unsigned int cols; const char **row; }; struct sqlite_transaction_context { struct sql_transaction_context ctx; bool failed:1; }; extern const struct sql_db driver_sqlite_db; extern const struct sql_result driver_sqlite_result; extern const struct sql_result driver_sqlite_error_result; static struct event_category event_category_sqlite = { .parent = &event_category_sql, .name = "sqlite" }; static int driver_sqlite_connect(struct sql_db *_db) { struct sqlite_db *db = (struct sqlite_db *)_db; if (db->connected) return 1; db->rc = sqlite3_open(db->dbfile, &db->sqlite); if (db->rc == SQLITE_OK) { db->connected = TRUE; sqlite3_busy_timeout(db->sqlite, sqlite_busy_timeout); return 1; } else { e_error(_db->event, "open(%s) failed: %s", db->dbfile, sqlite3_errmsg(db->sqlite)); sqlite3_close(db->sqlite); db->sqlite = NULL; return -1; } } static void driver_sqlite_disconnect(struct sql_db *_db) { struct sqlite_db *db = (struct sqlite_db *)_db; sqlite3_close(db->sqlite); db->sqlite = NULL; } static int driver_sqlite_init_full_v(const struct sql_settings *set, struct sql_db **db_r, const char **error_r ATTR_UNUSED) { struct sqlite_db *db; pool_t pool; pool = pool_alloconly_create("sqlite driver", 512); db = p_new(pool, struct sqlite_db, 1); db->pool = pool; db->api = driver_sqlite_db; db->dbfile = p_strdup(db->pool, set->connect_string); db->connected = FALSE; db->api.event = event_create(set->event_parent); event_add_category(db->api.event, &event_category_sqlite); event_set_append_log_prefix(db->api.event, "sqlite: "); *db_r = &db->api; return 0; } static void driver_sqlite_deinit_v(struct sql_db *_db) { struct sqlite_db *db = (struct sqlite_db *)_db; _db->no_reconnect = TRUE; sql_db_set_state(&db->api, SQL_DB_STATE_DISCONNECTED); sqlite3_close(db->sqlite); sql_connection_log_finished(_db); event_unref(&_db->event); array_free(&_db->module_contexts); pool_unref(&db->pool); } static const char * driver_sqlite_escape_string(struct sql_db *_db ATTR_UNUSED, const char *string) { const char *p; char *dest, *destbegin; /* find the first ' */ for (p = string; *p != '\''; p++) { if (*p == '\0') return t_strdup_noconst(string); } /* @UNSAFE: escape ' with '' */ dest = destbegin = t_buffer_get((p - string) + strlen(string) * 2 + 1); memcpy(dest, string, p - string); dest += p - string; for (; *p != '\0'; p++) { *dest++ = *p; if (*p == '\'') *dest++ = *p; } *dest++ = '\0'; t_buffer_alloc(dest - destbegin); return destbegin; } static void driver_sqlite_result_log(const struct sql_result *result, const char *query) { struct sqlite_db *db = (struct sqlite_db *)result->db; bool success = db->connected && db->rc == SQLITE_OK; int duration; const char *suffix = ""; struct event_passthrough *e = sql_query_finished_event(&db->api, result->event, query, success, &duration); io_loop_time_refresh(); if (!db->connected) { suffix = ": Cannot connect to database"; e->add_str("error", "Cannot connect to database"); } else if (db->rc != SQLITE_OK) { suffix = t_strdup_printf(": %s (%d)", sqlite3_errmsg(db->sqlite), db->rc); e->add_str("error", sqlite3_errmsg(db->sqlite)); e->add_int("error_code", db->rc); } e_debug(e->event(), SQL_QUERY_FINISHED_FMT"%s", query, duration, suffix); } static void driver_sqlite_exec(struct sql_db *_db, const char *query) { struct sqlite_db *db = (struct sqlite_db *)_db; struct sql_result result; i_zero(&result); result.db = _db; result.event = event_create(_db->event); /* Other drivers do not include time spent connecting but this simplifies error logging, so we include it here. */ if (driver_sqlite_connect(_db) < 0) { driver_sqlite_result_log(&result, query); } else { db->rc = sqlite3_exec(db->sqlite, query, NULL, NULL, NULL); driver_sqlite_result_log(&result, query); } event_unref(&result.event); } static void driver_sqlite_query(struct sql_db *db, const char *query, sql_query_callback_t *callback, void *context) { struct sql_result *result; result = sql_query_s(db, query); result->callback = TRUE; callback(result, context); result->callback = FALSE; sql_result_unref(result); } static struct sql_result * driver_sqlite_query_s(struct sql_db *_db, const char *query) { struct sqlite_db *db = (struct sqlite_db *)_db; struct sqlite_result *result; struct event *event; result = i_new(struct sqlite_result, 1); result->api.db = _db; /* Temporarily store the event since result->api gets * overwritten later here and we need to reset it. */ event = event_create(_db->event); result->api.event = event; if (driver_sqlite_connect(_db) < 0) { driver_sqlite_result_log(&result->api, query); result->api = driver_sqlite_error_result; result->stmt = NULL; result->cols = 0; } else { db->rc = sqlite3_prepare(db->sqlite, query, -1, &result->stmt, NULL); driver_sqlite_result_log(&result->api, query); if (db->rc == SQLITE_OK) { result->api = driver_sqlite_result; result->cols = sqlite3_column_count(result->stmt); result->row = i_new(const char *, result->cols); } else { result->api = driver_sqlite_error_result; result->stmt = NULL; result->cols = 0; } } result->api.db = _db; result->api.refcount = 1; result->api.event = event; return &result->api; } static void driver_sqlite_result_free(struct sql_result *_result) { struct sqlite_result *result = (struct sqlite_result *)_result; struct sqlite_db *db = (struct sqlite_db *) result->api.db; int rc; if (_result->callback) return; if (result->stmt != NULL) { if ((rc = sqlite3_finalize(result->stmt)) != SQLITE_OK) { e_warning(_result->event, "finalize failed: %s (%d)", sqlite3_errmsg(db->sqlite), rc); } i_free(result->row); } event_unref(&result->api.event); i_free(result); } static int driver_sqlite_result_next_row(struct sql_result *_result) { struct sqlite_result *result = (struct sqlite_result *)_result; switch (sqlite3_step(result->stmt)) { case SQLITE_ROW: return 1; case SQLITE_DONE: return 0; default: return -1; } } static unsigned int driver_sqlite_result_get_fields_count(struct sql_result *_result) { struct sqlite_result *result = (struct sqlite_result *)_result; return result->cols; } static const char * driver_sqlite_result_get_field_name(struct sql_result *_result, unsigned int idx) { struct sqlite_result *result = (struct sqlite_result *)_result; return sqlite3_column_name(result->stmt, idx); } static int driver_sqlite_result_find_field(struct sql_result *_result, const char *field_name) { struct sqlite_result *result = (struct sqlite_result *)_result; unsigned int i; for (i = 0; i < result->cols; ++i) { const char *col = sqlite3_column_name(result->stmt, i); if (strcmp(col, field_name) == 0) return i; } return -1; } static const char * driver_sqlite_result_get_field_value(struct sql_result *_result, unsigned int idx) { struct sqlite_result *result = (struct sqlite_result *)_result; return (const char*)sqlite3_column_text(result->stmt, idx); } static const unsigned char * driver_sqlite_result_get_field_value_binary(struct sql_result *_result, unsigned int idx, size_t *size_r) { struct sqlite_result *result = (struct sqlite_result *)_result; *size_r = sqlite3_column_bytes(result->stmt, idx); return sqlite3_column_blob(result->stmt, idx); } static const char * driver_sqlite_result_find_field_value(struct sql_result *result, const char *field_name) { int idx; idx = driver_sqlite_result_find_field(result, field_name); if (idx < 0) return NULL; return driver_sqlite_result_get_field_value(result, idx); } static const char *const * driver_sqlite_result_get_values(struct sql_result *_result) { struct sqlite_result *result = (struct sqlite_result *)_result; unsigned int i; for (i = 0; i < result->cols; ++i) { result->row[i] = driver_sqlite_result_get_field_value(_result, i); } return (const char *const *)result->row; } static const char *driver_sqlite_result_get_error(struct sql_result *_result) { struct sqlite_result *result = (struct sqlite_result *)_result; struct sqlite_db *db = (struct sqlite_db *)result->api.db; if (db->connected) return sqlite3_errmsg(db->sqlite); else return "Cannot connect to database"; } static struct sql_transaction_context * driver_sqlite_transaction_begin(struct sql_db *_db) { struct sqlite_transaction_context *ctx; struct sqlite_db *db = (struct sqlite_db *)_db; ctx = i_new(struct sqlite_transaction_context, 1); ctx->ctx.db = _db; ctx->ctx.event = event_create(_db->event); sql_exec(_db, "BEGIN TRANSACTION"); if (db->rc != SQLITE_OK) ctx->failed = TRUE; return &ctx->ctx; } static void driver_sqlite_transaction_rollback(struct sql_transaction_context *_ctx) { struct sqlite_transaction_context *ctx = (struct sqlite_transaction_context *)_ctx; if (!ctx->failed) { e_debug(sql_transaction_finished_event(_ctx)-> add_str("error", "Rolled back")->event(), "Transaction rolled back"); } sql_exec(_ctx->db, "ROLLBACK"); event_unref(&_ctx->event); i_free(ctx); } static void driver_sqlite_transaction_commit(struct sql_transaction_context *_ctx, sql_commit_callback_t *callback, void *context) { struct sqlite_transaction_context *ctx = (struct sqlite_transaction_context *)_ctx; struct sqlite_db *db = (struct sqlite_db *)ctx->ctx.db; struct sql_commit_result commit_result; if (!ctx->failed) { sql_exec(_ctx->db, "COMMIT"); if (db->rc != SQLITE_OK) ctx->failed = TRUE; } i_zero(&commit_result); if (ctx->failed) { commit_result.error = sqlite3_errmsg(db->sqlite); callback(&commit_result, context); e_debug(sql_transaction_finished_event(_ctx)-> add_str("error", commit_result.error)->event(), "Transaction failed"); /* From SQLite manual: It is recommended that applications respond to the errors listed above by explicitly issuing a ROLLBACK command. If the transaction has already been rolled back automatically by the error response, then the ROLLBACK command will fail with an error, but no harm is caused by this. */ driver_sqlite_transaction_rollback(_ctx); } else { e_debug(sql_transaction_finished_event(_ctx)->event(), "Transaction committed"); callback(&commit_result, context); event_unref(&_ctx->event); i_free(ctx); } } static int driver_sqlite_transaction_commit_s(struct sql_transaction_context *_ctx, const char **error_r) { struct sqlite_transaction_context *ctx = (struct sqlite_transaction_context *)_ctx; struct sqlite_db *db = (struct sqlite_db *) ctx->ctx.db; if (ctx->failed) { /* also does i_free(ctx) */ driver_sqlite_transaction_rollback(_ctx); return -1; } sql_exec(_ctx->db, "COMMIT"); *error_r = sqlite3_errmsg(db->sqlite); i_free(ctx); return 0; } static void driver_sqlite_update(struct sql_transaction_context *_ctx, const char *query, unsigned int *affected_rows) { struct sqlite_transaction_context *ctx = (struct sqlite_transaction_context *)_ctx; struct sqlite_db *db = (struct sqlite_db *)ctx->ctx.db; if (ctx->failed) return; sql_exec(_ctx->db, query); if (db->rc != SQLITE_OK) ctx->failed = TRUE; else if (affected_rows != NULL) *affected_rows = sqlite3_changes(db->sqlite); } static const char * driver_sqlite_escape_blob(struct sql_db *_db ATTR_UNUSED, const unsigned char *data, size_t size) { string_t *str = t_str_new(128); str_append(str, "x'"); binary_to_hex_append(str, data, size); str_append_c(str, '\''); return str_c(str); } const struct sql_db driver_sqlite_db = { .name = "sqlite", .flags = #if SQLITE_VERSION_NUMBER >= 3024000 SQL_DB_FLAG_ON_CONFLICT_DO | #endif SQL_DB_FLAG_BLOCKING, .v = { .init_full = driver_sqlite_init_full_v, .deinit = driver_sqlite_deinit_v, .connect = driver_sqlite_connect, .disconnect = driver_sqlite_disconnect, .escape_string = driver_sqlite_escape_string, .exec = driver_sqlite_exec, .query = driver_sqlite_query, .query_s = driver_sqlite_query_s, .transaction_begin = driver_sqlite_transaction_begin, .transaction_commit = driver_sqlite_transaction_commit, .transaction_commit_s = driver_sqlite_transaction_commit_s, .transaction_rollback = driver_sqlite_transaction_rollback, .update = driver_sqlite_update, .escape_blob = driver_sqlite_escape_blob, } }; const struct sql_result driver_sqlite_result = { .v = { .free = driver_sqlite_result_free, .next_row = driver_sqlite_result_next_row, .get_fields_count = driver_sqlite_result_get_fields_count, .get_field_name = driver_sqlite_result_get_field_name, .find_field = driver_sqlite_result_find_field, .get_field_value = driver_sqlite_result_get_field_value, .get_field_value_binary = driver_sqlite_result_get_field_value_binary, .find_field_value = driver_sqlite_result_find_field_value, .get_values = driver_sqlite_result_get_values, .get_error = driver_sqlite_result_get_error, } }; static int driver_sqlite_result_error_next_row(struct sql_result *result ATTR_UNUSED) { return -1; } const struct sql_result driver_sqlite_error_result = { .v = { .free = driver_sqlite_result_free, .next_row = driver_sqlite_result_error_next_row, .get_error = driver_sqlite_result_get_error, } }; const char *driver_sqlite_version = DOVECOT_ABI_VERSION; void driver_sqlite_init(void); void driver_sqlite_deinit(void); void driver_sqlite_init(void) { sql_driver_register(&driver_sqlite_db); } void driver_sqlite_deinit(void) { sql_driver_unregister(&driver_sqlite_db); } #endif dovecot-2.3.21.1/src/lib-sql/sql-db-cache.h0000644000000000000000000000061214656633576015042 00000000000000#ifndef SQL_DB_CACHE_H #define SQL_DB_CACHE_H struct sql_db_cache; /* Like sql_init(), but use a connection pool. */ int sql_db_cache_new(struct sql_db_cache *cache, const struct sql_settings *set, struct sql_db **db_r, const char **error_r); struct sql_db_cache *sql_db_cache_init(unsigned int max_unused_connections); void sql_db_cache_deinit(struct sql_db_cache **cache); #endif dovecot-2.3.21.1/src/lib-sql/driver-pgsql.c0000644000000000000000000010175414656633576015242 00000000000000/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "ioloop.h" #include "hex-binary.h" #include "str.h" #include "time-util.h" #include "sql-api-private.h" #include "llist.h" #ifdef BUILD_PGSQL #include #define PGSQL_DNS_WARN_MSECS 500 struct pgsql_db { struct sql_db api; pool_t pool; char *connect_string; char *host; PGconn *pg; struct io *io; struct timeout *to_connect; enum io_condition io_dir; struct pgsql_result *pending_results; struct pgsql_result *cur_result; struct ioloop *ioloop, *orig_ioloop; struct sql_result *sync_result; bool (*next_callback)(void *); void *next_context; char *error; const char *connect_state; bool fatal_error:1; }; struct pgsql_binary_value { unsigned char *value; size_t size; }; struct pgsql_result { struct sql_result api; struct pgsql_result *prev, *next; PGresult *pgres; struct timeout *to; unsigned int rownum, rows; unsigned int fields_count; const char **fields; const char **values; char *query; ARRAY(struct pgsql_binary_value) binary_values; sql_query_callback_t *callback; void *context; bool timeout:1; }; struct pgsql_transaction_context { struct sql_transaction_context ctx; int refcount; sql_commit_callback_t *callback; void *context; pool_t query_pool; const char *error; bool failed:1; }; extern const struct sql_db driver_pgsql_db; extern const struct sql_result driver_pgsql_result; static void result_finish(struct pgsql_result *result); static void transaction_update_callback(struct sql_result *result, struct sql_transaction_query *query); static struct event_category event_category_pgsql = { .parent = &event_category_sql, .name = "pgsql" }; static void driver_pgsql_set_state(struct pgsql_db *db, enum sql_db_state state) { i_assert(state == SQL_DB_STATE_BUSY || db->cur_result == NULL); /* switch back to original ioloop in case the caller wants to add/remove timeouts */ if (db->ioloop != NULL) io_loop_set_current(db->orig_ioloop); sql_db_set_state(&db->api, state); if (db->ioloop != NULL) io_loop_set_current(db->ioloop); } static bool driver_pgsql_next_callback(struct pgsql_db *db) { bool (*next_callback)(void *) = db->next_callback; void *next_context = db->next_context; if (next_callback == NULL) return FALSE; db->next_callback = NULL; db->next_context = NULL; return next_callback(next_context); } static void driver_pgsql_stop_io(struct pgsql_db *db) { if (db->io != NULL) { io_remove(&db->io); db->io_dir = 0; } } static void driver_pgsql_close(struct pgsql_db *db) { db->io_dir = 0; db->fatal_error = FALSE; driver_pgsql_stop_io(db); PQfinish(db->pg); db->pg = NULL; timeout_remove(&db->to_connect); driver_pgsql_set_state(db, SQL_DB_STATE_DISCONNECTED); if (db->ioloop != NULL) { /* running a sync query, stop it */ io_loop_stop(db->ioloop); } driver_pgsql_next_callback(db); } static const char *last_error(struct pgsql_db *db) { const char *msg; size_t len; msg = PQerrorMessage(db->pg); if (msg == NULL) return "(no error set)"; /* Error message should contain trailing \n, we don't want it */ len = strlen(msg); return len == 0 || msg[len-1] != '\n' ? msg : t_strndup(msg, len-1); } static void connect_callback(struct pgsql_db *db) { enum io_condition io_dir = 0; int ret; driver_pgsql_stop_io(db); while ((ret = PQconnectPoll(db->pg)) == PGRES_POLLING_ACTIVE) ; switch (ret) { case PGRES_POLLING_READING: db->connect_state = "wait for input"; io_dir = IO_READ; break; case PGRES_POLLING_WRITING: db->connect_state = "wait for output"; io_dir = IO_WRITE; break; case PGRES_POLLING_OK: break; case PGRES_POLLING_FAILED: e_error(db->api.event, "Connect failed to database %s: %s (state: %s)", PQdb(db->pg), last_error(db), db->connect_state); driver_pgsql_close(db); return; } if (io_dir != 0) { db->io = io_add(PQsocket(db->pg), io_dir, connect_callback, db); db->io_dir = io_dir; } if (io_dir == 0) { db->connect_state = "connected"; timeout_remove(&db->to_connect); if (PQserverVersion(db->pg) >= 90500) { /* v9.5+ */ db->api.flags |= SQL_DB_FLAG_ON_CONFLICT_DO; } driver_pgsql_set_state(db, SQL_DB_STATE_IDLE); if (db->ioloop != NULL) { /* driver_pgsql_sync_init() waiting for connection to finish */ io_loop_stop(db->ioloop); } } } static void driver_pgsql_connect_timeout(struct pgsql_db *db) { unsigned int secs = ioloop_time - db->api.last_connect_try; e_error(db->api.event, "Connect failed: Timeout after %u seconds (state: %s)", secs, db->connect_state); driver_pgsql_close(db); } static int driver_pgsql_connect(struct sql_db *_db) { struct pgsql_db *db = (struct pgsql_db *)_db; struct timeval tv_start; int msecs; i_assert(db->api.state == SQL_DB_STATE_DISCONNECTED); io_loop_time_refresh(); tv_start = ioloop_timeval; db->pg = PQconnectStart(db->connect_string); if (db->pg == NULL) { i_fatal("pgsql: PQconnectStart() failed (out of memory)"); } if (PQstatus(db->pg) == CONNECTION_BAD) { e_error(_db->event, "Connect failed to database %s: %s", PQdb(db->pg), last_error(db)); driver_pgsql_close(db); return -1; } /* PQconnectStart() blocks on host name resolving. Log a warning if it takes too long. Also don't include time spent on that in the connect timeout (by refreshing ioloop time). */ io_loop_time_refresh(); msecs = timeval_diff_msecs(&ioloop_timeval, &tv_start); if (msecs > PGSQL_DNS_WARN_MSECS) { e_warning(_db->event, "DNS lookup took %d.%03d s", msecs/1000, msecs % 1000); } /* nonblocking connecting begins. */ if (PQsetnonblocking(db->pg, 1) < 0) e_error(_db->event, "PQsetnonblocking() failed"); i_assert(db->to_connect == NULL); db->to_connect = timeout_add(SQL_CONNECT_TIMEOUT_SECS * 1000, driver_pgsql_connect_timeout, db); db->connect_state = "connecting"; db->io = io_add(PQsocket(db->pg), IO_WRITE, connect_callback, db); db->io_dir = IO_WRITE; driver_pgsql_set_state(db, SQL_DB_STATE_CONNECTING); return 0; } static void driver_pgsql_disconnect(struct sql_db *_db) { struct pgsql_db *db = (struct pgsql_db *)_db; if (db->cur_result != NULL && db->cur_result->to != NULL) { driver_pgsql_stop_io(db); result_finish(db->cur_result); } _db->no_reconnect = TRUE; driver_pgsql_close(db); _db->no_reconnect = FALSE; } static void driver_pgsql_free(struct pgsql_db **_db) { struct pgsql_db *db = *_db; *_db = NULL; event_unref(&db->api.event); i_free(db->connect_string); i_free(db->host); i_free(db->error); array_free(&db->api.module_contexts); i_free(db); } static enum sql_db_flags driver_pgsql_get_flags(struct sql_db *db) { switch (db->state) { case SQL_DB_STATE_DISCONNECTED: if (sql_connect(db) < 0) break; /* fall through */ case SQL_DB_STATE_CONNECTING: /* Wait for connection to finish, so we can get the flags reliably. */ sql_wait(db); break; case SQL_DB_STATE_IDLE: case SQL_DB_STATE_BUSY: break; } return db->flags; } static int driver_pgsql_init_full_v(const struct sql_settings *set, struct sql_db **db_r, const char **error_r ATTR_UNUSED) { struct pgsql_db *db; db = i_new(struct pgsql_db, 1); db->connect_string = i_strdup(set->connect_string); db->api = driver_pgsql_db; db->api.event = event_create(set->event_parent); event_add_category(db->api.event, &event_category_pgsql); /* NOTE: Connection string will be parsed by pgsql itself We only pick the host part here */ T_BEGIN { const char *const *arg = t_strsplit(db->connect_string, " "); for (; *arg != NULL; arg++) { if (str_begins(*arg, "host=")) db->host = i_strdup(*arg + 5); } } T_END; event_set_append_log_prefix(db->api.event, t_strdup_printf("pgsql(%s): ", db->host)); *db_r = &db->api; return 0; } static void driver_pgsql_deinit_v(struct sql_db *_db) { struct pgsql_db *db = (struct pgsql_db *)_db; driver_pgsql_disconnect(_db); driver_pgsql_free(&db); } static void driver_pgsql_set_idle(struct pgsql_db *db) { i_assert(db->api.state == SQL_DB_STATE_BUSY); if (db->fatal_error) driver_pgsql_close(db); else if (!driver_pgsql_next_callback(db)) driver_pgsql_set_state(db, SQL_DB_STATE_IDLE); } static void consume_results(struct pgsql_db *db) { PGresult *pgres; driver_pgsql_stop_io(db); while (PQconsumeInput(db->pg) != 0) { if (PQisBusy(db->pg) != 0) { db->io = io_add(PQsocket(db->pg), IO_READ, consume_results, db); db->io_dir = IO_READ; return; } pgres = PQgetResult(db->pg); if (pgres == NULL) break; PQclear(pgres); } if (PQstatus(db->pg) == CONNECTION_BAD) driver_pgsql_close(db); else driver_pgsql_set_idle(db); } static void driver_pgsql_result_free(struct sql_result *_result) { struct pgsql_db *db = (struct pgsql_db *)_result->db; struct pgsql_result *result = (struct pgsql_result *)_result; bool success; i_assert(!result->api.callback); i_assert(db->cur_result == result); i_assert(result->callback == NULL); if (_result == db->sync_result) db->sync_result = NULL; db->cur_result = NULL; success = result->pgres != NULL && !db->fatal_error; if (result->pgres != NULL) { PQclear(result->pgres); result->pgres = NULL; } if (success) { /* we'll have to read the rest of the results as well */ i_assert(db->io == NULL); consume_results(db); } else { driver_pgsql_set_idle(db); } if (array_is_created(&result->binary_values)) { struct pgsql_binary_value *value; array_foreach_modifiable(&result->binary_values, value) PQfreemem(value->value); array_free(&result->binary_values); } event_unref(&result->api.event); i_free(result->query); i_free(result->fields); i_free(result->values); i_free(result); } static void result_finish(struct pgsql_result *result) { struct pgsql_db *db = (struct pgsql_db *)result->api.db; bool free_result = TRUE; int duration; i_assert(db->io == NULL); timeout_remove(&result->to); DLLIST_REMOVE(&db->pending_results, result); /* if connection to server was lost, we don't yet see that the connection is bad. we only see the fatal error, so assume it also means disconnection. */ if (PQstatus(db->pg) == CONNECTION_BAD || result->pgres == NULL || PQresultStatus(result->pgres) == PGRES_FATAL_ERROR) db->fatal_error = TRUE; if (db->fatal_error) { result->api.failed = TRUE; result->api.failed_try_retry = TRUE; } /* emit event */ if (result->api.failed) { const char *error = result->timeout ? "Timed out" : last_error(db); struct event_passthrough *e = sql_query_finished_event(&db->api, result->api.event, result->query, TRUE, &duration); e->add_str("error", error); e_debug(e->event(), SQL_QUERY_FINISHED_FMT": %s", result->query, duration, error); } else { e_debug(sql_query_finished_event(&db->api, result->api.event, result->query, FALSE, &duration)-> event(), SQL_QUERY_FINISHED_FMT, result->query, duration); } result->api.callback = TRUE; T_BEGIN { if (result->callback != NULL) result->callback(&result->api, result->context); } T_END; result->api.callback = FALSE; free_result = db->sync_result != &result->api; if (db->ioloop != NULL) io_loop_stop(db->ioloop); i_assert(!free_result || result->api.refcount > 0); result->callback = NULL; if (free_result) sql_result_unref(&result->api); } static void get_result(struct pgsql_result *result) { struct pgsql_db *db = (struct pgsql_db *)result->api.db; driver_pgsql_stop_io(db); if (PQconsumeInput(db->pg) == 0) { result_finish(result); return; } if (PQisBusy(db->pg) != 0) { db->io = io_add(PQsocket(db->pg), IO_READ, get_result, result); db->io_dir = IO_READ; return; } result->pgres = PQgetResult(db->pg); result_finish(result); } static void flush_callback(struct pgsql_result *result) { struct pgsql_db *db = (struct pgsql_db *)result->api.db; int ret; driver_pgsql_stop_io(db); ret = PQflush(db->pg); if (ret > 0) { db->io = io_add(PQsocket(db->pg), IO_WRITE, flush_callback, result); db->io_dir = IO_WRITE; return; } if (ret < 0) { result_finish(result); } else { /* all flushed */ get_result(result); } } static void query_timeout(struct pgsql_result *result) { struct pgsql_db *db = (struct pgsql_db *)result->api.db; driver_pgsql_stop_io(db); result->timeout = TRUE; result_finish(result); } static void do_query(struct pgsql_result *result, const char *query) { struct pgsql_db *db = (struct pgsql_db *)result->api.db; int ret; i_assert(SQL_DB_IS_READY(&db->api)); i_assert(db->cur_result == NULL); i_assert(db->io == NULL); driver_pgsql_set_state(db, SQL_DB_STATE_BUSY); db->cur_result = result; DLLIST_PREPEND(&db->pending_results, result); result->to = timeout_add(SQL_QUERY_TIMEOUT_SECS * 1000, query_timeout, result); result->query = i_strdup(query); if (PQsendQuery(db->pg, query) == 0 || (ret = PQflush(db->pg)) < 0) { /* failed to send query */ result_finish(result); return; } if (ret > 0) { /* write blocks */ db->io = io_add(PQsocket(db->pg), IO_WRITE, flush_callback, result); db->io_dir = IO_WRITE; } else { get_result(result); } } static const char * driver_pgsql_escape_string(struct sql_db *_db, const char *string) { struct pgsql_db *db = (struct pgsql_db *)_db; size_t len = strlen(string); char *to; #ifdef HAVE_PQESCAPE_STRING_CONN if (db->api.state == SQL_DB_STATE_DISCONNECTED) { /* try connecting again */ (void)sql_connect(&db->api); } if (db->api.state != SQL_DB_STATE_DISCONNECTED) { int error; to = t_buffer_get(len * 2 + 1); len = PQescapeStringConn(db->pg, to, string, len, &error); } else #endif { to = t_buffer_get(len * 2 + 1); len = PQescapeString(to, string, len); } t_buffer_alloc(len + 1); return to; } static void exec_callback(struct sql_result *_result, void *context ATTR_UNUSED) { struct pgsql_result *result = (struct pgsql_result*)_result; result_finish(result); } static void driver_pgsql_exec(struct sql_db *db, const char *query) { struct pgsql_result *result; result = i_new(struct pgsql_result, 1); result->api = driver_pgsql_result; result->api.db = db; result->api.refcount = 1; result->api.event = event_create(db->event); result->callback = exec_callback; do_query(result, query); } static void driver_pgsql_query(struct sql_db *db, const char *query, sql_query_callback_t *callback, void *context) { struct pgsql_result *result; result = i_new(struct pgsql_result, 1); result->api = driver_pgsql_result; result->api.db = db; result->api.refcount = 1; result->api.event = event_create(db->event); result->callback = callback; result->context = context; do_query(result, query); } static void pgsql_query_s_callback(struct sql_result *result, void *context) { struct pgsql_db *db = context; db->sync_result = result; } static void driver_pgsql_sync_init(struct pgsql_db *db) { bool add_to_connect; db->orig_ioloop = current_ioloop; if (db->io == NULL) { db->ioloop = io_loop_create(); return; } i_assert(db->api.state == SQL_DB_STATE_CONNECTING); /* have to move our existing I/O and timeout handlers to new I/O loop */ io_remove(&db->io); add_to_connect = (db->to_connect != NULL); timeout_remove(&db->to_connect); db->ioloop = io_loop_create(); if (add_to_connect) { db->to_connect = timeout_add(SQL_CONNECT_TIMEOUT_SECS * 1000, driver_pgsql_connect_timeout, db); } db->io = io_add(PQsocket(db->pg), db->io_dir, connect_callback, db); /* wait for connecting to finish */ io_loop_run(db->ioloop); } static void driver_pgsql_sync_deinit(struct pgsql_db *db) { io_loop_destroy(&db->ioloop); } static struct sql_result * driver_pgsql_sync_query(struct pgsql_db *db, const char *query) { struct sql_result *result; i_assert(db->sync_result == NULL); switch (db->api.state) { case SQL_DB_STATE_CONNECTING: case SQL_DB_STATE_BUSY: i_unreached(); case SQL_DB_STATE_DISCONNECTED: sql_not_connected_result.refcount++; return &sql_not_connected_result; case SQL_DB_STATE_IDLE: break; } driver_pgsql_query(&db->api, query, pgsql_query_s_callback, db); if (db->sync_result == NULL) io_loop_run(db->ioloop); i_assert(db->io == NULL); result = db->sync_result; if (result == &sql_not_connected_result) { /* we don't end up in pgsql's free function, so sync_result won't be set to NULL if we don't do it here. */ db->sync_result = NULL; } else if (result == NULL) { result = &sql_not_connected_result; result->refcount++; } i_assert(db->io == NULL); return result; } static struct sql_result * driver_pgsql_query_s(struct sql_db *_db, const char *query) { struct pgsql_db *db = (struct pgsql_db *)_db; struct sql_result *result; driver_pgsql_sync_init(db); result = driver_pgsql_sync_query(db, query); driver_pgsql_sync_deinit(db); return result; } static int driver_pgsql_result_next_row(struct sql_result *_result) { struct pgsql_result *result = (struct pgsql_result *)_result; struct pgsql_db *db = (struct pgsql_db *)_result->db; if (result->rows != 0) { /* second time we're here */ if (++result->rownum < result->rows) return 1; /* end of this packet. see if there's more. FIXME: this may block, but the current API doesn't provide a non-blocking way to do this.. */ PQclear(result->pgres); result->pgres = PQgetResult(db->pg); if (result->pgres == NULL) return 0; } if (result->pgres == NULL) { _result->failed = TRUE; return -1; } switch (PQresultStatus(result->pgres)) { case PGRES_COMMAND_OK: /* no rows returned */ return 0; case PGRES_TUPLES_OK: result->rows = PQntuples(result->pgres); return result->rows > 0 ? 1 : 0; case PGRES_EMPTY_QUERY: case PGRES_NONFATAL_ERROR: /* nonfatal error */ _result->failed = TRUE; return -1; default: /* treat as fatal error */ _result->failed = TRUE; db->fatal_error = TRUE; return -1; } } static void driver_pgsql_result_fetch_fields(struct pgsql_result *result) { unsigned int i; if (result->fields != NULL) return; /* @UNSAFE */ result->fields_count = PQnfields(result->pgres); result->fields = i_new(const char *, result->fields_count); for (i = 0; i < result->fields_count; i++) result->fields[i] = PQfname(result->pgres, i); } static unsigned int driver_pgsql_result_get_fields_count(struct sql_result *_result) { struct pgsql_result *result = (struct pgsql_result *)_result; driver_pgsql_result_fetch_fields(result); return result->fields_count; } static const char * driver_pgsql_result_get_field_name(struct sql_result *_result, unsigned int idx) { struct pgsql_result *result = (struct pgsql_result *)_result; driver_pgsql_result_fetch_fields(result); i_assert(idx < result->fields_count); return result->fields[idx]; } static int driver_pgsql_result_find_field(struct sql_result *_result, const char *field_name) { struct pgsql_result *result = (struct pgsql_result *)_result; unsigned int i; driver_pgsql_result_fetch_fields(result); for (i = 0; i < result->fields_count; i++) { if (strcmp(result->fields[i], field_name) == 0) return i; } return -1; } static const char * driver_pgsql_result_get_field_value(struct sql_result *_result, unsigned int idx) { struct pgsql_result *result = (struct pgsql_result *)_result; if (PQgetisnull(result->pgres, result->rownum, idx) != 0) return NULL; return PQgetvalue(result->pgres, result->rownum, idx); } static const unsigned char * driver_pgsql_result_get_field_value_binary(struct sql_result *_result, unsigned int idx, size_t *size_r) { struct pgsql_result *result = (struct pgsql_result *)_result; const char *value; struct pgsql_binary_value *binary_value; if (PQgetisnull(result->pgres, result->rownum, idx) != 0) { *size_r = 0; return NULL; } value = PQgetvalue(result->pgres, result->rownum, idx); if (!array_is_created(&result->binary_values)) i_array_init(&result->binary_values, idx + 1); binary_value = array_idx_get_space(&result->binary_values, idx); if (binary_value->value == NULL) { binary_value->value = PQunescapeBytea((const unsigned char *)value, &binary_value->size); } *size_r = binary_value->size; return binary_value->value; } static const char * driver_pgsql_result_find_field_value(struct sql_result *result, const char *field_name) { int idx; idx = driver_pgsql_result_find_field(result, field_name); if (idx < 0) return NULL; return driver_pgsql_result_get_field_value(result, idx); } static const char *const * driver_pgsql_result_get_values(struct sql_result *_result) { struct pgsql_result *result = (struct pgsql_result *)_result; unsigned int i; if (result->values == NULL) { driver_pgsql_result_fetch_fields(result); result->values = i_new(const char *, result->fields_count); } /* @UNSAFE */ for (i = 0; i < result->fields_count; i++) { result->values[i] = driver_pgsql_result_get_field_value(_result, i); } return result->values; } static const char *driver_pgsql_result_get_error(struct sql_result *_result) { struct pgsql_result *result = (struct pgsql_result *)_result; struct pgsql_db *db = (struct pgsql_db *)_result->db; const char *msg; size_t len; i_free_and_null(db->error); if (result->timeout) { db->error = i_strdup("Query timed out"); } else if (result->pgres == NULL) { /* connection error */ db->error = i_strdup(last_error(db)); } else { msg = PQresultErrorMessage(result->pgres); if (msg == NULL) return "(no error set)"; /* Error message should contain trailing \n, we don't want it */ len = strlen(msg); db->error = len == 0 || msg[len-1] != '\n' ? i_strdup(msg) : i_strndup(msg, len-1); } return db->error; } static struct sql_transaction_context * driver_pgsql_transaction_begin(struct sql_db *db) { struct pgsql_transaction_context *ctx; ctx = i_new(struct pgsql_transaction_context, 1); ctx->ctx.db = db; ctx->ctx.event = event_create(db->event); /* we need to be able to handle multiple open transactions, so at least for now just keep them in memory until commit time. */ ctx->query_pool = pool_alloconly_create("pgsql transaction", 1024); return &ctx->ctx; } static void driver_pgsql_transaction_free(struct pgsql_transaction_context *ctx) { pool_unref(&ctx->query_pool); event_unref(&ctx->ctx.event); i_free(ctx); } static void transaction_commit_callback(struct sql_result *result, struct pgsql_transaction_context *ctx) { struct sql_commit_result commit_result; i_zero(&commit_result); if (sql_result_next_row(result) < 0) { commit_result.error = sql_result_get_error(result); commit_result.error_type = sql_result_get_error_type(result); } ctx->callback(&commit_result, ctx->context); driver_pgsql_transaction_free(ctx); } static bool transaction_send_next(void *context) { struct pgsql_transaction_context *ctx = context; i_assert(!ctx->failed); if (ctx->ctx.db->state == SQL_DB_STATE_BUSY) { /* kludgy.. */ ctx->ctx.db->state = SQL_DB_STATE_IDLE; } else if (!SQL_DB_IS_READY(ctx->ctx.db)) { struct sql_commit_result commit_result = { .error = "Not connected" }; ctx->callback(&commit_result, ctx->context); return FALSE; } if (ctx->ctx.head != NULL) { struct sql_transaction_query *query = ctx->ctx.head; ctx->ctx.head = ctx->ctx.head->next; sql_query(ctx->ctx.db, query->query, transaction_update_callback, query); } else { sql_query(ctx->ctx.db, "COMMIT", transaction_commit_callback, ctx); } return TRUE; } static void transaction_commit_error_callback(struct pgsql_transaction_context *ctx, struct sql_result *result) { struct sql_commit_result commit_result; i_zero(&commit_result); commit_result.error = sql_result_get_error(result); commit_result.error_type = sql_result_get_error_type(result); e_debug(sql_transaction_finished_event(&ctx->ctx)-> add_str("error", commit_result.error)->event(), "Transaction failed: %s", commit_result.error); ctx->callback(&commit_result, ctx->context); } static void transaction_begin_callback(struct sql_result *result, struct pgsql_transaction_context *ctx) { struct pgsql_db *db = (struct pgsql_db *)result->db; i_assert(result->db == ctx->ctx.db); if (sql_result_next_row(result) < 0) { transaction_commit_error_callback(ctx, result); driver_pgsql_transaction_free(ctx); return; } i_assert(db->next_callback == NULL); db->next_callback = transaction_send_next; db->next_context = ctx; } static void transaction_update_callback(struct sql_result *result, struct sql_transaction_query *query) { struct pgsql_transaction_context *ctx = (struct pgsql_transaction_context *)query->trans; struct pgsql_db *db = (struct pgsql_db *)result->db; if (sql_result_next_row(result) < 0) { transaction_commit_error_callback(ctx, result); driver_pgsql_transaction_free(ctx); return; } if (query->affected_rows != NULL) { struct pgsql_result *pg_result = (struct pgsql_result *)result; if (str_to_uint(PQcmdTuples(pg_result->pgres), query->affected_rows) < 0) i_unreached(); } i_assert(db->next_callback == NULL); db->next_callback = transaction_send_next; db->next_context = ctx; } static void transaction_trans_query_callback(struct sql_result *result, struct sql_transaction_query *query) { struct pgsql_transaction_context *ctx = (struct pgsql_transaction_context *)query->trans; struct sql_commit_result commit_result; if (sql_result_next_row(result) < 0) { transaction_commit_error_callback(ctx, result); driver_pgsql_transaction_free(ctx); return; } if (query->affected_rows != NULL) { struct pgsql_result *pg_result = (struct pgsql_result *)result; if (str_to_uint(PQcmdTuples(pg_result->pgres), query->affected_rows) < 0) i_unreached(); } e_debug(sql_transaction_finished_event(&ctx->ctx)->event(), "Transaction committed"); i_zero(&commit_result); ctx->callback(&commit_result, ctx->context); driver_pgsql_transaction_free(ctx); } static void driver_pgsql_transaction_commit(struct sql_transaction_context *_ctx, sql_commit_callback_t *callback, void *context) { struct pgsql_transaction_context *ctx = (struct pgsql_transaction_context *)_ctx; struct sql_commit_result result; i_zero(&result); ctx->callback = callback; ctx->context = context; if (ctx->failed || _ctx->head == NULL) { if (ctx->failed) { result.error = ctx->error; e_debug(sql_transaction_finished_event(_ctx)-> add_str("error", ctx->error)->event(), "Transaction failed: %s", ctx->error); } else { e_debug(sql_transaction_finished_event(_ctx)->event(), "Transaction committed"); } callback(&result, context); driver_pgsql_transaction_free(ctx); } else if (_ctx->head->next == NULL) { /* just a single query, send it */ sql_query(_ctx->db, _ctx->head->query, transaction_trans_query_callback, _ctx->head); } else { /* multiple queries, use a transaction */ i_assert(_ctx->db->v.query == driver_pgsql_query); sql_query(_ctx->db, "BEGIN", transaction_begin_callback, ctx); } } static void commit_multi_fail(struct pgsql_transaction_context *ctx, struct sql_result *result, const char *query) { ctx->failed = TRUE; ctx->error = t_strdup_printf("%s (query: %s)", sql_result_get_error(result), query); sql_result_unref(result); } static struct sql_result * driver_pgsql_transaction_commit_multi(struct pgsql_transaction_context *ctx) { struct pgsql_db *db = (struct pgsql_db *)ctx->ctx.db; struct sql_result *result; struct sql_transaction_query *query; result = driver_pgsql_sync_query(db, "BEGIN"); if (sql_result_next_row(result) < 0) { commit_multi_fail(ctx, result, "BEGIN"); return NULL; } sql_result_unref(result); /* send queries */ for (query = ctx->ctx.head; query != NULL; query = query->next) { result = driver_pgsql_sync_query(db, query->query); if (sql_result_next_row(result) < 0) { commit_multi_fail(ctx, result, query->query); break; } if (query->affected_rows != NULL) { struct pgsql_result *pg_result = (struct pgsql_result *)result; if (str_to_uint(PQcmdTuples(pg_result->pgres), query->affected_rows) < 0) i_unreached(); } sql_result_unref(result); } return driver_pgsql_sync_query(db, ctx->failed ? "ROLLBACK" : "COMMIT"); } static void driver_pgsql_try_commit_s(struct pgsql_transaction_context *ctx, const char **error_r) { struct sql_transaction_context *_ctx = &ctx->ctx; struct pgsql_db *db = (struct pgsql_db *)_ctx->db; struct sql_transaction_query *single_query = NULL; struct sql_result *result; if (_ctx->head->next == NULL) { /* just a single query, send it */ single_query = _ctx->head; result = sql_query_s(_ctx->db, single_query->query); } else { /* multiple queries, use a transaction */ driver_pgsql_sync_init(db); result = driver_pgsql_transaction_commit_multi(ctx); driver_pgsql_sync_deinit(db); } if (ctx->failed) { i_assert(ctx->error != NULL); e_debug(sql_transaction_finished_event(_ctx)-> add_str("error", ctx->error)->event(), "Transaction failed: %s", ctx->error); *error_r = ctx->error; } else if (result != NULL) { if (sql_result_next_row(result) < 0) *error_r = sql_result_get_error(result); else if (single_query != NULL && single_query->affected_rows != NULL) { struct pgsql_result *pg_result = (struct pgsql_result *)result; if (str_to_uint(PQcmdTuples(pg_result->pgres), single_query->affected_rows) < 0) i_unreached(); } } if (!ctx->failed) { e_debug(sql_transaction_finished_event(_ctx)->event(), "Transaction committed"); } if (result != NULL) sql_result_unref(result); } static int driver_pgsql_transaction_commit_s(struct sql_transaction_context *_ctx, const char **error_r) { struct pgsql_transaction_context *ctx = (struct pgsql_transaction_context *)_ctx; struct pgsql_db *db = (struct pgsql_db *)_ctx->db; *error_r = NULL; if (_ctx->head != NULL) { driver_pgsql_try_commit_s(ctx, error_r); if (_ctx->db->state == SQL_DB_STATE_DISCONNECTED) { *error_r = t_strdup(*error_r); e_info(db->api.event, "Disconnected from database, " "retrying commit"); if (sql_connect(_ctx->db) >= 0) { ctx->failed = FALSE; *error_r = NULL; driver_pgsql_try_commit_s(ctx, error_r); } } } driver_pgsql_transaction_free(ctx); return *error_r == NULL ? 0 : -1; } static void driver_pgsql_transaction_rollback(struct sql_transaction_context *_ctx) { struct pgsql_transaction_context *ctx = (struct pgsql_transaction_context *)_ctx; e_debug(sql_transaction_finished_event(_ctx)-> add_str("error", "Rolled back")->event(), "Transaction rolled back"); driver_pgsql_transaction_free(ctx); } static void driver_pgsql_update(struct sql_transaction_context *_ctx, const char *query, unsigned int *affected_rows) { struct pgsql_transaction_context *ctx = (struct pgsql_transaction_context *)_ctx; sql_transaction_add_query(_ctx, ctx->query_pool, query, affected_rows); } static const char * driver_pgsql_escape_blob(struct sql_db *_db ATTR_UNUSED, const unsigned char *data, size_t size) { string_t *str = t_str_new(128); str_append(str, "E'\\\\x"); binary_to_hex_append(str, data, size); str_append_c(str, '\''); return str_c(str); } static bool driver_pgsql_have_work(struct pgsql_db *db) { return db->next_callback != NULL || db->pending_results != NULL || db->api.state == SQL_DB_STATE_CONNECTING; } static void driver_pgsql_wait(struct sql_db *_db) { struct pgsql_db *db = (struct pgsql_db *)_db; if (!driver_pgsql_have_work(db)) return; db->orig_ioloop = current_ioloop; db->ioloop = io_loop_create(); db->io = io_loop_move_io(&db->io); while (driver_pgsql_have_work(db)) io_loop_run(db->ioloop); io_loop_set_current(db->orig_ioloop); db->io = io_loop_move_io(&db->io); io_loop_set_current(db->ioloop); io_loop_destroy(&db->ioloop); } const struct sql_db driver_pgsql_db = { .name = "pgsql", .flags = SQL_DB_FLAG_POOLED, .v = { .get_flags = driver_pgsql_get_flags, .init_full = driver_pgsql_init_full_v, .deinit = driver_pgsql_deinit_v, .connect = driver_pgsql_connect, .disconnect = driver_pgsql_disconnect, .escape_string = driver_pgsql_escape_string, .exec = driver_pgsql_exec, .query = driver_pgsql_query, .query_s = driver_pgsql_query_s, .wait = driver_pgsql_wait, .transaction_begin = driver_pgsql_transaction_begin, .transaction_commit = driver_pgsql_transaction_commit, .transaction_commit_s = driver_pgsql_transaction_commit_s, .transaction_rollback = driver_pgsql_transaction_rollback, .update = driver_pgsql_update, .escape_blob = driver_pgsql_escape_blob, } }; const struct sql_result driver_pgsql_result = { .v = { .free = driver_pgsql_result_free, .next_row = driver_pgsql_result_next_row, .get_fields_count = driver_pgsql_result_get_fields_count, .get_field_name = driver_pgsql_result_get_field_name, .find_field = driver_pgsql_result_find_field, .get_field_value = driver_pgsql_result_get_field_value, .get_field_value_binary = driver_pgsql_result_get_field_value_binary, .find_field_value = driver_pgsql_result_find_field_value, .get_values = driver_pgsql_result_get_values, .get_error = driver_pgsql_result_get_error, } }; const char *driver_pgsql_version = DOVECOT_ABI_VERSION; void driver_pgsql_init(void); void driver_pgsql_deinit(void); void driver_pgsql_init(void) { sql_driver_register(&driver_pgsql_db); } void driver_pgsql_deinit(void) { sql_driver_unregister(&driver_pgsql_db); } #endif dovecot-2.3.21.1/src/lib-sql/driver-cassandra.c0000644000000000000000000022567314656633576016062 00000000000000/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "istream.h" #include "array.h" #include "hostpid.h" #include "hex-binary.h" #include "str.h" #include "ioloop.h" #include "net.h" #include "write-full.h" #include "time-util.h" #include "var-expand.h" #include "safe-memset.h" #include "settings-parser.h" #include "sql-api-private.h" #ifdef BUILD_CASSANDRA #include #include #include #include #include #define IS_CONNECTED(db) \ ((db)->api.state != SQL_DB_STATE_DISCONNECTED && \ (db)->api.state != SQL_DB_STATE_CONNECTING) #define CASSANDRA_FALLBACK_WARN_INTERVAL_SECS 60 #define CASSANDRA_FALLBACK_FIRST_RETRY_MSECS 50 #define CASSANDRA_FALLBACK_MAX_RETRY_MSECS (1000*60) #define CASS_QUERY_DEFAULT_WARN_TIMEOUT_MSECS (5*1000) typedef void driver_cassandra_callback_t(CassFuture *future, void *context); enum cassandra_counter_type { CASSANDRA_COUNTER_TYPE_QUERY_SENT, CASSANDRA_COUNTER_TYPE_QUERY_RECV_OK, CASSANDRA_COUNTER_TYPE_QUERY_RECV_ERR_NO_HOSTS, CASSANDRA_COUNTER_TYPE_QUERY_RECV_ERR_QUEUE_FULL, CASSANDRA_COUNTER_TYPE_QUERY_RECV_ERR_CLIENT_TIMEOUT, CASSANDRA_COUNTER_TYPE_QUERY_RECV_ERR_SERVER_TIMEOUT, CASSANDRA_COUNTER_TYPE_QUERY_RECV_ERR_SERVER_UNAVAILABLE, CASSANDRA_COUNTER_TYPE_QUERY_RECV_ERR_OTHER, CASSANDRA_COUNTER_TYPE_QUERY_SLOW, CASSANDRA_COUNTER_COUNT }; static const char *counter_names[CASSANDRA_COUNTER_COUNT] = { "sent", "recv_ok", "recv_err_no_hosts", "recv_err_queue_full", "recv_err_client_timeout", "recv_err_server_timeout", "recv_err_server_unavailable", "recv_err_other", "slow", }; enum cassandra_query_type { CASSANDRA_QUERY_TYPE_READ, CASSANDRA_QUERY_TYPE_READ_MORE, CASSANDRA_QUERY_TYPE_WRITE, CASSANDRA_QUERY_TYPE_DELETE, CASSANDRA_QUERY_TYPE_COUNT }; static const char *cassandra_query_type_names[CASSANDRA_QUERY_TYPE_COUNT] = { "read", "read-more", "write", "delete" }; struct cassandra_callback { unsigned int id; struct timeout *to; CassFuture *future; struct cassandra_db *db; driver_cassandra_callback_t *callback; void *context; }; struct cassandra_db { struct sql_db api; char *hosts, *keyspace, *user, *password; CassConsistency read_consistency, write_consistency, delete_consistency; CassConsistency read_fallback_consistency, write_fallback_consistency; CassConsistency delete_fallback_consistency; CassLogLevel log_level; bool debug_queries; bool latency_aware_routing; bool init_ssl; unsigned int protocol_version; unsigned int num_threads; unsigned int connect_timeout_msecs, request_timeout_msecs; unsigned int warn_timeout_msecs; unsigned int heartbeat_interval_secs, idle_timeout_secs; unsigned int execution_retry_interval_msecs, execution_retry_times; unsigned int page_size; in_port_t port; CassCluster *cluster; CassSession *session; CassTimestampGen *timestamp_gen; CassSsl *ssl; int fd_pipe[2]; struct io *io_pipe; ARRAY(struct cassandra_sql_prepared_statement *) pending_prepares; ARRAY(struct cassandra_callback *) callbacks; ARRAY(struct cassandra_result *) results; unsigned int callback_ids; char *metrics_path; char *ssl_ca_file; char *ssl_cert_file; char *ssl_private_key_file; char *ssl_private_key_password; CassSslVerifyFlags ssl_verify_flags; struct timeout *to_metrics; uint64_t counters[CASSANDRA_COUNTER_COUNT]; struct timeval primary_query_last_sent[CASSANDRA_QUERY_TYPE_COUNT]; time_t last_fallback_warning[CASSANDRA_QUERY_TYPE_COUNT]; unsigned int fallback_failures[CASSANDRA_QUERY_TYPE_COUNT]; /* for synchronous queries: */ struct ioloop *ioloop, *orig_ioloop; struct sql_result *sync_result; char *error; }; struct cassandra_result { struct sql_result api; CassStatement *statement; const CassResult *result; CassIterator *iterator; char *log_query; char *error; CassConsistency consistency, fallback_consistency; enum cassandra_query_type query_type; struct timeval page0_start_time, start_time, finish_time; unsigned int row_count, total_row_count, page_num; cass_int64_t timestamp; pool_t row_pool; ARRAY_TYPE(const_string) fields; ARRAY(size_t) field_sizes; sql_query_callback_t *callback; void *context; bool is_prepared:1; bool query_sent:1; bool finished:1; bool paging_continues:1; }; struct cassandra_transaction_context { struct sql_transaction_context ctx; int refcount; sql_commit_callback_t *callback; void *context; struct cassandra_sql_statement *stmt; char *query; char *log_query; cass_int64_t query_timestamp; char *error; bool begin_succeeded:1; bool begin_failed:1; bool failed:1; }; struct cassandra_sql_arg { unsigned int column_idx; char *value_str; const unsigned char *value_binary; size_t value_binary_size; int64_t value_int64; }; struct cassandra_sql_statement { struct sql_statement stmt; struct cassandra_sql_prepared_statement *prep; CassStatement *cass_stmt; ARRAY(struct cassandra_sql_arg) pending_args; cass_int64_t timestamp; struct cassandra_result *result; }; struct cassandra_sql_prepared_statement { struct sql_prepared_statement prep_stmt; /* NULL, until the prepare is asynchronously finished */ const CassPrepared *prepared; /* statements waiting for prepare to finish */ ARRAY(struct cassandra_sql_statement *) pending_statements; /* an error here will cause the prepare to be retried on the next execution attempt. */ char *error; bool pending; }; extern const struct sql_db driver_cassandra_db; extern const struct sql_result driver_cassandra_result; static struct { CassConsistency consistency; const char *name; } cass_consistency_names[] = { { CASS_CONSISTENCY_ANY, "any" }, { CASS_CONSISTENCY_ONE, "one" }, { CASS_CONSISTENCY_TWO, "two" }, { CASS_CONSISTENCY_THREE, "three" }, { CASS_CONSISTENCY_QUORUM, "quorum" }, { CASS_CONSISTENCY_ALL, "all" }, { CASS_CONSISTENCY_LOCAL_QUORUM, "local-quorum" }, { CASS_CONSISTENCY_EACH_QUORUM, "each-quorum" }, { CASS_CONSISTENCY_SERIAL, "serial" }, { CASS_CONSISTENCY_LOCAL_SERIAL, "local-serial" }, { CASS_CONSISTENCY_LOCAL_ONE, "local-one" } }; static struct { CassLogLevel log_level; const char *name; } cass_log_level_names[] = { { CASS_LOG_CRITICAL, "critical" }, { CASS_LOG_ERROR, "error" }, { CASS_LOG_WARN, "warn" }, { CASS_LOG_INFO, "info" }, { CASS_LOG_DEBUG, "debug" }, { CASS_LOG_TRACE, "trace" } }; static struct event_category event_category_cassandra = { .parent = &event_category_sql, .name = "cassandra" }; static pthread_t main_thread_id; static bool main_thread_id_set; static void driver_cassandra_prepare_pending(struct cassandra_db *db); static void prepare_finish_pending_statements(struct cassandra_sql_prepared_statement *prep_stmt); static void driver_cassandra_result_send_query(struct cassandra_result *result); static void driver_cassandra_send_queries(struct cassandra_db *db); static void result_finish(struct cassandra_result *result); static void log_one_line(const CassLogMessage *message, enum log_type log_type, const char *log_level_str, const char *text, size_t text_len) { /* NOTE: We may not be in the main thread. We can't use the standard Dovecot functions that may use data stack. That's why we can't use i_log_type() in here, but have to re-implement the internal logging protocol. Otherwise preserve Cassandra's own logging format. */ fprintf(stderr, "\001%c%s %u.%03u %s(%s:%d:%s): %.*s\n", log_type+1, my_pid, (unsigned int)(message->time_ms / 1000), (unsigned int)(message->time_ms % 1000), log_level_str, message->file, message->line, message->function, (int)text_len, text); } static void driver_cassandra_log_handler(const CassLogMessage* message, void *data ATTR_UNUSED) { enum log_type log_type = LOG_TYPE_ERROR; const char *log_level_str = ""; switch (message->severity) { case CASS_LOG_DISABLED: case CASS_LOG_LAST_ENTRY: i_unreached(); case CASS_LOG_CRITICAL: log_type = LOG_TYPE_PANIC; break; case CASS_LOG_ERROR: log_type = LOG_TYPE_ERROR; break; case CASS_LOG_WARN: log_type = LOG_TYPE_WARNING; break; case CASS_LOG_INFO: log_type = LOG_TYPE_INFO; break; case CASS_LOG_TRACE: log_level_str = "[TRACE] "; /* fall through */ case CASS_LOG_DEBUG: log_type = LOG_TYPE_DEBUG; break; } /* Log message may contain LFs, so log each line separately. */ const char *p, *line = message->message; while ((p = strchr(line, '\n')) != NULL) { log_one_line(message, log_type, log_level_str, line, p - line); line = p+1; } log_one_line(message, log_type, log_level_str, line, strlen(line)); } static void driver_cassandra_init_log(void) { failure_callback_t *fatal_callback, *error_callback; failure_callback_t *info_callback, *debug_callback; i_get_failure_handlers(&fatal_callback, &error_callback, &info_callback, &debug_callback); if (i_failure_handler_is_internal(debug_callback)) { /* Using internal logging protocol. Use it ourself to set log levels correctly. */ cass_log_set_callback(driver_cassandra_log_handler, NULL); } } static int consistency_parse(const char *str, CassConsistency *consistency_r) { unsigned int i; for (i = 0; i < N_ELEMENTS(cass_consistency_names); i++) { if (strcmp(cass_consistency_names[i].name, str) == 0) { *consistency_r = cass_consistency_names[i].consistency; return 0; } } return -1; } static int log_level_parse(const char *str, CassLogLevel *log_level_r) { unsigned int i; for (i = 0; i < N_ELEMENTS(cass_log_level_names); i++) { if (strcmp(cass_log_level_names[i].name, str) == 0) { *log_level_r = cass_log_level_names[i].log_level; return 0; } } return -1; } static void driver_cassandra_set_state(struct cassandra_db *db, enum sql_db_state state) { /* switch back to original ioloop in case the caller wants to add/remove timeouts */ if (db->ioloop != NULL) io_loop_set_current(db->orig_ioloop); sql_db_set_state(&db->api, state); if (db->ioloop != NULL) io_loop_set_current(db->ioloop); } static void driver_cassandra_close(struct cassandra_db *db, const char *error) { struct cassandra_sql_prepared_statement *prep_stmt; struct cassandra_result *const *resultp; io_remove(&db->io_pipe); if (db->fd_pipe[0] != -1) { i_close_fd(&db->fd_pipe[0]); i_close_fd(&db->fd_pipe[1]); } driver_cassandra_set_state(db, SQL_DB_STATE_DISCONNECTED); array_foreach_elem(&db->pending_prepares, prep_stmt) { prep_stmt->pending = FALSE; prep_stmt->error = i_strdup(error); prepare_finish_pending_statements(prep_stmt); } array_clear(&db->pending_prepares); while (array_count(&db->results) > 0) { resultp = array_front(&db->results); if ((*resultp)->error == NULL) (*resultp)->error = i_strdup(error); result_finish(*resultp); } if (db->ioloop != NULL) { /* running a sync query, stop it */ io_loop_stop(db->ioloop); } } static void driver_cassandra_log_error(struct cassandra_db *db, CassFuture *future, const char *str) { const char *message; size_t size; cass_future_error_message(future, &message, &size); e_error(db->api.event, "%s: %.*s", str, (int)size, message); } static struct cassandra_callback * cassandra_callback_detach(struct cassandra_db *db, unsigned int id) { struct cassandra_callback *cb, *const *cbp; /* usually there are only a few callbacks, so don't bother with using a hash table */ array_foreach(&db->callbacks, cbp) { cb = *cbp; if (cb->id == id) { array_delete(&db->callbacks, array_foreach_idx(&db->callbacks, cbp), 1); return cb; } } return NULL; } static void cassandra_callback_run(struct cassandra_callback *cb) { timeout_remove(&cb->to); cb->callback(cb->future, cb->context); cass_future_free(cb->future); i_free(cb); } static void driver_cassandra_future_callback(CassFuture *future ATTR_UNUSED, void *context) { struct cassandra_callback *cb = context; if (pthread_equal(pthread_self(), main_thread_id) != 0) { /* called immediately from the main thread. */ cassandra_callback_detach(cb->db, cb->id); cb->to = timeout_add_short(0, cassandra_callback_run, cb); return; } /* this isn't the main thread - communicate with main thread by writing the callback id to the pipe. note that we must not use almost any dovecot functions here because most of them are using data-stack, which isn't thread-safe. especially don't use i_error() here. */ if (write_full(cb->db->fd_pipe[1], &cb->id, sizeof(cb->id)) < 0) { const char *str = t_strdup_printf( "cassandra: write(pipe) failed: %s\n", strerror(errno)); (void)write_full(STDERR_FILENO, str, strlen(str)); } } static void driver_cassandra_input_id(struct cassandra_db *db, unsigned int id) { struct cassandra_callback *cb; cb = cassandra_callback_detach(db, id); if (cb == NULL) i_panic("cassandra: Received unknown ID %u", id); cassandra_callback_run(cb); } static void driver_cassandra_input(struct cassandra_db *db) { unsigned int ids[1024]; ssize_t ret; ret = read(db->fd_pipe[0], ids, sizeof(ids)); if (ret < 0) e_error(db->api.event, "read(pipe) failed: %m"); else if (ret == 0) e_error(db->api.event, "read(pipe) failed: EOF"); else if (ret % sizeof(ids[0]) != 0) e_error(db->api.event, "read(pipe) returned wrong amount of data"); else { /* success */ unsigned int i, count = ret / sizeof(ids[0]); for (i = 0; i < count && db->api.state != SQL_DB_STATE_DISCONNECTED; i++) driver_cassandra_input_id(db, ids[i]); return; } driver_cassandra_close(db, "IPC pipe closed"); } static void driver_cassandra_set_callback(CassFuture *future, struct cassandra_db *db, driver_cassandra_callback_t *callback, void *context) { struct cassandra_callback *cb; i_assert(callback != NULL); cb = i_new(struct cassandra_callback, 1); cb->future = future; cb->callback = callback; cb->context = context; cb->db = db; array_push_back(&db->callbacks, &cb); cb->id = ++db->callback_ids; if (cb->id == 0) cb->id = ++db->callback_ids; /* NOTE: The callback may be called immediately by this same thread. This is checked within the callback. It may also be called at any time after this call by another thread. So we must not access "cb" again after this call. */ cass_future_set_callback(future, driver_cassandra_future_callback, cb); } static void connect_callback(CassFuture *future, void *context) { struct cassandra_db *db = context; if (cass_future_error_code(future) != CASS_OK) { driver_cassandra_log_error(db, future, "Couldn't connect to Cassandra"); driver_cassandra_close(db, "Couldn't connect to Cassandra"); return; } driver_cassandra_set_state(db, SQL_DB_STATE_IDLE); if (db->ioloop != NULL) { /* driver_cassandra_sync_init() waiting for connection to finish */ io_loop_stop(db->ioloop); } driver_cassandra_prepare_pending(db); driver_cassandra_send_queries(db); } static int driver_cassandra_connect(struct sql_db *_db) { struct cassandra_db *db = (struct cassandra_db *)_db; CassFuture *future; i_assert(db->api.state == SQL_DB_STATE_DISCONNECTED); if (pipe(db->fd_pipe) < 0) { e_error(_db->event, "pipe() failed: %m"); return -1; } db->io_pipe = io_add(db->fd_pipe[0], IO_READ, driver_cassandra_input, db); driver_cassandra_set_state(db, SQL_DB_STATE_CONNECTING); future = cass_session_connect_keyspace(db->session, db->cluster, db->keyspace); driver_cassandra_set_callback(future, db, connect_callback, db); return 0; } static void driver_cassandra_disconnect(struct sql_db *_db) { struct cassandra_db *db = (struct cassandra_db *)_db; driver_cassandra_close(db, "Disconnected"); } static const char * driver_cassandra_escape_string(struct sql_db *db ATTR_UNUSED, const char *string) { string_t *escaped; unsigned int i; if (strchr(string, '\'') == NULL) return string; escaped = t_str_new(strlen(string)+10); for (i = 0; string[i] != '\0'; i++) { if (string[i] == '\'') str_append_c(escaped, '\''); str_append_c(escaped, string[i]); } return str_c(escaped); } static int driver_cassandra_parse_connect_string(struct cassandra_db *db, const char *connect_string, const char **error_r) { const char *const *args, *key, *value, *error; string_t *hosts = t_str_new(64); bool read_fallback_set = FALSE, write_fallback_set = FALSE; bool delete_fallback_set = FALSE; db->log_level = CASS_LOG_WARN; db->read_consistency = CASS_CONSISTENCY_LOCAL_QUORUM; db->write_consistency = CASS_CONSISTENCY_LOCAL_QUORUM; db->delete_consistency = CASS_CONSISTENCY_LOCAL_QUORUM; db->connect_timeout_msecs = SQL_CONNECT_TIMEOUT_SECS*1000; db->request_timeout_msecs = SQL_QUERY_TIMEOUT_SECS*1000; db->warn_timeout_msecs = CASS_QUERY_DEFAULT_WARN_TIMEOUT_MSECS; args = t_strsplit_spaces(connect_string, " "); for (; *args != NULL; args++) { value = strchr(*args, '='); if (value == NULL) { *error_r = t_strdup_printf( "Missing value in connect string: %s", *args); return -1; } key = t_strdup_until(*args, value++); if (str_begins(key, "ssl_")) db->init_ssl = TRUE; if (strcmp(key, "host") == 0) { if (str_len(hosts) > 0) str_append_c(hosts, ','); str_append(hosts, value); } else if (strcmp(key, "port") == 0) { if (net_str2port(value, &db->port) < 0) { *error_r = t_strdup_printf( "Invalid port: %s", value); return -1; } } else if (strcmp(key, "dbname") == 0 || strcmp(key, "keyspace") == 0) { i_free(db->keyspace); db->keyspace = i_strdup(value); } else if (strcmp(key, "user") == 0) { i_free(db->user); db->user = i_strdup(value); } else if (strcmp(key, "password") == 0) { i_free(db->password); db->password = i_strdup(value); } else if (strcmp(key, "read_consistency") == 0) { if (consistency_parse(value, &db->read_consistency) < 0) { *error_r = t_strdup_printf( "Unknown read_consistency: %s", value); return -1; } } else if (strcmp(key, "read_fallback_consistency") == 0) { if (consistency_parse(value, &db->read_fallback_consistency) < 0) { *error_r = t_strdup_printf( "Unknown read_fallback_consistency: %s", value); return -1; } read_fallback_set = TRUE; } else if (strcmp(key, "write_consistency") == 0) { if (consistency_parse(value, &db->write_consistency) < 0) { *error_r = t_strdup_printf( "Unknown write_consistency: %s", value); return -1; } } else if (strcmp(key, "write_fallback_consistency") == 0) { if (consistency_parse(value, &db->write_fallback_consistency) < 0) { *error_r = t_strdup_printf( "Unknown write_fallback_consistency: %s", value); return -1; } write_fallback_set = TRUE; } else if (strcmp(key, "delete_consistency") == 0) { if (consistency_parse(value, &db->delete_consistency) < 0) { *error_r = t_strdup_printf( "Unknown delete_consistency: %s", value); return -1; } } else if (strcmp(key, "delete_fallback_consistency") == 0) { if (consistency_parse(value, &db->delete_fallback_consistency) < 0) { *error_r = t_strdup_printf( "Unknown delete_fallback_consistency: %s", value); return -1; } delete_fallback_set = TRUE; } else if (strcmp(key, "log_level") == 0) { if (log_level_parse(value, &db->log_level) < 0) { *error_r = t_strdup_printf( "Unknown log_level: %s", value); return -1; } } else if (strcmp(key, "debug_queries") == 0) { db->debug_queries = TRUE; } else if (strcmp(key, "latency_aware_routing") == 0) { db->latency_aware_routing = TRUE; } else if (strcmp(key, "version") == 0) { if (str_to_uint(value, &db->protocol_version) < 0) { *error_r = t_strdup_printf( "Invalid version: %s", value); return -1; } } else if (strcmp(key, "num_threads") == 0) { if (str_to_uint(value, &db->num_threads) < 0) { *error_r = t_strdup_printf( "Invalid num_threads: %s", value); return -1; } } else if (strcmp(key, "heartbeat_interval") == 0) { if (settings_get_time(value, &db->heartbeat_interval_secs, &error) < 0) { *error_r = t_strdup_printf( "Invalid heartbeat_interval '%s': %s", value, error); return -1; } } else if (strcmp(key, "idle_timeout") == 0) { if (settings_get_time(value, &db->idle_timeout_secs, &error) < 0) { *error_r = t_strdup_printf( "Invalid idle_timeout '%s': %s", value, error); return -1; } } else if (strcmp(key, "connect_timeout") == 0) { if (settings_get_time_msecs(value, &db->connect_timeout_msecs, &error) < 0) { *error_r = t_strdup_printf( "Invalid connect_timeout '%s': %s", value, error); return -1; } } else if (strcmp(key, "request_timeout") == 0) { if (settings_get_time_msecs(value, &db->request_timeout_msecs, &error) < 0) { *error_r = t_strdup_printf( "Invalid request_timeout '%s': %s", value, error); return -1; } } else if (strcmp(key, "warn_timeout") == 0) { if (settings_get_time_msecs(value, &db->warn_timeout_msecs, &error) < 0) { *error_r = t_strdup_printf( "Invalid warn_timeout '%s': %s", value, error); return -1; } } else if (strcmp(key, "metrics") == 0) { i_free(db->metrics_path); db->metrics_path = i_strdup(value); } else if (strcmp(key, "execution_retry_interval") == 0) { if (settings_get_time_msecs(value, &db->execution_retry_interval_msecs, &error) < 0) { *error_r = t_strdup_printf( "Invalid execution_retry_interval '%s': %s", value, error); return -1; } #ifndef HAVE_CASSANDRA_SPECULATIVE_POLICY *error_r = t_strdup_printf( "This cassandra version does not support execution_retry_interval"); return -1; #endif } else if (strcmp(key, "execution_retry_times") == 0) { if (str_to_uint(value, &db->execution_retry_times) < 0) { *error_r = t_strdup_printf( "Invalid execution_retry_times %s", value); return -1; } #ifndef HAVE_CASSANDRA_SPECULATIVE_POLICY *error_r = t_strdup_printf( "This cassandra version does not support execution_retry_times"); return -1; #endif } else if (strcmp(key, "page_size") == 0) { if (str_to_uint(value, &db->page_size) < 0) { *error_r = t_strdup_printf( "Invalid page_size: %s", value); return -1; } } else if (strcmp(key, "ssl_ca") == 0) { db->ssl_ca_file = i_strdup(value); } else if (strcmp(key, "ssl_cert_file") == 0) { db->ssl_cert_file = i_strdup(value); } else if (strcmp(key, "ssl_private_key_file") == 0) { db->ssl_private_key_file = i_strdup(value); } else if (strcmp(key, "ssl_private_key_password") == 0) { db->ssl_private_key_password = i_strdup(value); } else if (strcmp(key, "ssl_verify") == 0) { if (strcmp(value, "none") == 0) { db->ssl_verify_flags = CASS_SSL_VERIFY_NONE; } else if (strcmp(value, "cert") == 0) { db->ssl_verify_flags = CASS_SSL_VERIFY_PEER_CERT; } else if (strcmp(value, "cert-ip") == 0) { db->ssl_verify_flags = CASS_SSL_VERIFY_PEER_CERT | CASS_SSL_VERIFY_PEER_IDENTITY; #if HAVE_DECL_CASS_SSL_VERIFY_PEER_IDENTITY_DNS == 1 } else if (strcmp(value, "cert-dns") == 0) { db->ssl_verify_flags = CASS_SSL_VERIFY_PEER_CERT | CASS_SSL_VERIFY_PEER_IDENTITY_DNS; #endif } else { *error_r = t_strdup_printf( "Unsupported ssl_verify flags: '%s'", value); return -1; } } else { *error_r = t_strdup_printf( "Unknown connect string: %s", key); return -1; } } if (!read_fallback_set) db->read_fallback_consistency = db->read_consistency; if (!write_fallback_set) db->write_fallback_consistency = db->write_consistency; if (!delete_fallback_set) db->delete_fallback_consistency = db->delete_consistency; if (str_len(hosts) == 0) { *error_r = t_strdup_printf("No hosts given in connect string"); return -1; } if (db->keyspace == NULL) { *error_r = t_strdup_printf("No dbname given in connect string"); return -1; } if ((db->ssl_cert_file != NULL && db->ssl_private_key_file == NULL) || (db->ssl_cert_file == NULL && db->ssl_private_key_file != NULL)) { *error_r = "ssl_cert_file and ssl_private_key_file need to be both set"; return -1; } db->hosts = i_strdup(str_c(hosts)); return 0; } static void driver_cassandra_get_metrics_json(struct cassandra_db *db, string_t *dest) { #define ADD_UINT64(_struct, _field) \ str_printfa(dest, "\""#_field"\": %llu,", \ (unsigned long long)metrics._struct._field); #define ADD_DOUBLE(_struct, _field) \ str_printfa(dest, "\""#_field"\": %02lf,", metrics._struct._field); CassMetrics metrics; cass_session_get_metrics(db->session, &metrics); str_append(dest, "{ \"requests\": {"); ADD_UINT64(requests, min); ADD_UINT64(requests, max); ADD_UINT64(requests, mean); ADD_UINT64(requests, stddev); ADD_UINT64(requests, median); ADD_UINT64(requests, percentile_75th); ADD_UINT64(requests, percentile_95th); ADD_UINT64(requests, percentile_98th); ADD_UINT64(requests, percentile_99th); ADD_UINT64(requests, percentile_999th); ADD_DOUBLE(requests, mean_rate); ADD_DOUBLE(requests, one_minute_rate); ADD_DOUBLE(requests, five_minute_rate); ADD_DOUBLE(requests, fifteen_minute_rate); str_truncate(dest, str_len(dest)-1); str_append(dest, "}, \"stats\": {"); ADD_UINT64(stats, total_connections); ADD_UINT64(stats, available_connections); ADD_UINT64(stats, exceeded_pending_requests_water_mark); ADD_UINT64(stats, exceeded_write_bytes_water_mark); str_truncate(dest, str_len(dest)-1); str_append(dest, "}, \"errors\": {"); ADD_UINT64(errors, connection_timeouts); ADD_UINT64(errors, pending_request_timeouts); ADD_UINT64(errors, request_timeouts); str_truncate(dest, str_len(dest)-1); str_append(dest, "}, \"queries\": {"); for (unsigned int i = 0; i < CASSANDRA_COUNTER_COUNT; i++) { str_printfa(dest, "\"%s\": %"PRIu64",", counter_names[i], db->counters[i]); } str_truncate(dest, str_len(dest)-1); str_append(dest, "}}"); } static void driver_cassandra_metrics_write(struct cassandra_db *db) { struct var_expand_table tab[] = { { '\0', NULL, NULL } }; string_t *path = t_str_new(64); string_t *data; const char *error; int fd; if (var_expand(path, db->metrics_path, tab, &error) <= 0) { e_error(db->api.event, "Failed to expand metrics_path=%s: %s", db->metrics_path, error); return; } fd = open(str_c(path), O_WRONLY | O_CREAT | O_TRUNC | O_NONBLOCK, 0600); if (fd == -1) { e_error(db->api.event, "creat(%s) failed: %m", str_c(path)); return; } data = t_str_new(1024); driver_cassandra_get_metrics_json(db, data); if (write_full(fd, str_data(data), str_len(data)) < 0) e_error(db->api.event, "write(%s) failed: %m", str_c(path)); i_close_fd(&fd); } static void driver_cassandra_free(struct cassandra_db **_db) { struct cassandra_db *db = *_db; *_db = NULL; event_unref(&db->api.event); i_free(db->metrics_path); i_free(db->hosts); i_free(db->error); i_free(db->keyspace); i_free(db->user); i_free(db->password); i_free(db->ssl_ca_file); i_free(db->ssl_cert_file); i_free(db->ssl_private_key_file); i_free_and_null(db->ssl_private_key_password); array_free(&db->api.module_contexts); if (db->ssl != NULL) cass_ssl_free(db->ssl); i_free(db); } static int driver_cassandra_init_ssl(struct cassandra_db *db, const char **error_r) { buffer_t *buf = t_buffer_create(512); CassError c_err; db->ssl = cass_ssl_new(); i_assert(db->ssl != NULL); if (db->ssl_ca_file != NULL) { if (buffer_append_full_file(buf, db->ssl_ca_file, SIZE_MAX, error_r) < 0) return -1; if ((c_err = cass_ssl_add_trusted_cert(db->ssl, str_c(buf))) != CASS_OK) { *error_r = cass_error_desc(c_err); return -1; } } if (db->ssl_private_key_file != NULL && db->ssl_cert_file != NULL) { buffer_set_used_size(buf, 0); if (buffer_append_full_file(buf, db->ssl_private_key_file, SIZE_MAX, error_r) < 0) return -1; c_err = cass_ssl_set_private_key(db->ssl, str_c(buf), db->ssl_private_key_password); safe_memset(buffer_get_modifiable_data(buf, NULL), 0, buf->used); if (c_err != CASS_OK) { *error_r = cass_error_desc(c_err); return -1; } buffer_set_used_size(buf, 0); if (buffer_append_full_file(buf, db->ssl_cert_file, SIZE_MAX, error_r) < 0) return -1; if ((c_err = cass_ssl_set_cert(db->ssl, str_c(buf))) != CASS_OK) { *error_r = cass_error_desc(c_err); return -1; } } cass_ssl_set_verify_flags(db->ssl, db->ssl_verify_flags); return 0; } static int driver_cassandra_init_full_v(const struct sql_settings *set, struct sql_db **db_r, const char **error_r) { struct cassandra_db *db; int ret; db = i_new(struct cassandra_db, 1); db->api = driver_cassandra_db; db->fd_pipe[0] = db->fd_pipe[1] = -1; db->api.event = event_create(set->event_parent); event_add_category(db->api.event, &event_category_cassandra); event_set_append_log_prefix(db->api.event, "cassandra: "); T_BEGIN { ret = driver_cassandra_parse_connect_string(db, set->connect_string, error_r); } T_END_PASS_STR_IF(ret < 0, error_r); if (ret < 0) { driver_cassandra_free(&db); return -1; } if (db->init_ssl && driver_cassandra_init_ssl(db, error_r) < 0) { driver_cassandra_free(&db); return -1; } driver_cassandra_init_log(); cass_log_set_level(db->log_level); if (db->log_level >= CASS_LOG_DEBUG) event_set_forced_debug(db->api.event, TRUE); if (db->protocol_version > 0 && db->protocol_version < 4) { /* binding with column indexes requires v4 */ db->api.v.prepared_statement_init = NULL; db->api.v.prepared_statement_deinit = NULL; db->api.v.statement_init_prepared = NULL; } db->timestamp_gen = cass_timestamp_gen_monotonic_new(); db->cluster = cass_cluster_new(); #ifdef HAVE_CASS_CLUSTER_SET_USE_HOSTNAME_RESOLUTION if ((db->ssl_verify_flags & CASS_SSL_VERIFY_PEER_IDENTITY_DNS) != 0) { CassError c_err; if ((c_err = cass_cluster_set_use_hostname_resolution( db->cluster, cass_true)) != CASS_OK) { *error_r = cass_error_desc(c_err); driver_cassandra_free(&db); return -1; } } #endif cass_cluster_set_ssl(db->cluster, db->ssl); cass_cluster_set_timestamp_gen(db->cluster, db->timestamp_gen); cass_cluster_set_connect_timeout(db->cluster, db->connect_timeout_msecs); cass_cluster_set_request_timeout(db->cluster, db->request_timeout_msecs); cass_cluster_set_contact_points(db->cluster, db->hosts); if (db->user != NULL && db->password != NULL) cass_cluster_set_credentials(db->cluster, db->user, db->password); if (db->port != 0) cass_cluster_set_port(db->cluster, db->port); if (db->protocol_version != 0) cass_cluster_set_protocol_version(db->cluster, db->protocol_version); if (db->num_threads != 0) cass_cluster_set_num_threads_io(db->cluster, db->num_threads); if (db->latency_aware_routing) cass_cluster_set_latency_aware_routing(db->cluster, cass_true); if (db->heartbeat_interval_secs != 0) cass_cluster_set_connection_heartbeat_interval(db->cluster, db->heartbeat_interval_secs); if (db->idle_timeout_secs != 0) cass_cluster_set_connection_idle_timeout(db->cluster, db->idle_timeout_secs); #ifdef HAVE_CASSANDRA_SPECULATIVE_POLICY if (db->execution_retry_times > 0 && db->execution_retry_interval_msecs > 0) cass_cluster_set_constant_speculative_execution_policy( db->cluster, db->execution_retry_interval_msecs, db->execution_retry_times); #endif if (db->ssl != NULL) { e_debug(db->api.event, "Enabling TLS for cluster"); cass_cluster_set_ssl(db->cluster, db->ssl); } db->session = cass_session_new(); if (db->metrics_path != NULL) db->to_metrics = timeout_add(1000, driver_cassandra_metrics_write, db); i_array_init(&db->results, 16); i_array_init(&db->callbacks, 16); i_array_init(&db->pending_prepares, 16); if (!main_thread_id_set) { main_thread_id = pthread_self(); main_thread_id_set = TRUE; } *db_r = &db->api; return 0; } static void driver_cassandra_deinit_v(struct sql_db *_db) { struct cassandra_db *db = (struct cassandra_db *)_db; driver_cassandra_close(db, "Deinitialized"); i_assert(array_count(&db->callbacks) == 0); array_free(&db->callbacks); i_assert(array_count(&db->results) == 0); array_free(&db->results); i_assert(array_count(&db->pending_prepares) == 0); array_free(&db->pending_prepares); cass_session_free(db->session); cass_cluster_free(db->cluster); cass_timestamp_gen_free(db->timestamp_gen); timeout_remove(&db->to_metrics); sql_connection_log_finished(_db); driver_cassandra_free(&db); } static void driver_cassandra_result_unlink(struct cassandra_db *db, struct cassandra_result *result) { struct cassandra_result *const *results; unsigned int i, count; results = array_get(&db->results, &count); for (i = 0; i < count; i++) { if (results[i] == result) { array_delete(&db->results, i, 1); return; } } i_unreached(); } static void driver_cassandra_log_result(struct cassandra_result *result, bool all_pages, long long reply_usecs) { struct cassandra_db *db = (struct cassandra_db *)result->api.db; struct timeval now; unsigned int row_count; i_gettimeofday(&now); string_t *str = t_str_new(128); str_printfa(str, "Finished %squery '%s' (", result->is_prepared ? "prepared " : "", result->log_query); if (result->timestamp != 0) str_printfa(str, "timestamp=%"PRId64", ", result->timestamp); if (all_pages) { str_printfa(str, "%u pages in total, ", result->page_num); row_count = result->total_row_count; } else { if (result->page_num > 0 || result->paging_continues) str_printfa(str, "page %u, ", result->page_num); row_count = result->row_count; } str_printfa(str, "%u rows, %lld+%lld us): %s", row_count, reply_usecs, timeval_diff_usecs(&now, &result->finish_time), result->error != NULL ? result->error : "success"); struct event_passthrough *e = sql_query_finished_event(&db->api, result->api.event, result->log_query, result->error == NULL, NULL); if (result->error != NULL) e->add_str("error", result->error); struct event *event = e->event(); if (db->debug_queries) event_set_forced_debug(event, TRUE); if (reply_usecs/1000 >= db->warn_timeout_msecs) { db->counters[CASSANDRA_COUNTER_TYPE_QUERY_SLOW]++; e_warning(event, "%s", str_c(str)); } else { e_debug(event, "%s", str_c(str)); } } static void driver_cassandra_result_free(struct sql_result *_result) { struct cassandra_db *db = (struct cassandra_db *)_result->db; struct cassandra_result *result = (struct cassandra_result *)_result; long long reply_usecs; i_assert(!result->api.callback); i_assert(result->callback == NULL); if (_result == db->sync_result) db->sync_result = NULL; reply_usecs = timeval_diff_usecs(&result->finish_time, &result->start_time); driver_cassandra_log_result(result, FALSE, reply_usecs); if (result->page_num > 0 && !result->paging_continues) { /* Multi-page query finishes now. Log a debug/warning summary message about it separate from the per-page messages. */ reply_usecs = timeval_diff_usecs(&result->finish_time, &result->page0_start_time); driver_cassandra_log_result(result, TRUE, reply_usecs); } if (result->result != NULL) cass_result_free(result->result); if (result->iterator != NULL) cass_iterator_free(result->iterator); if (result->statement != NULL) cass_statement_free(result->statement); pool_unref(&result->row_pool); event_unref(&result->api.event); i_free(result->log_query); i_free(result->error); i_free(result); } static void result_finish(struct cassandra_result *result) { struct cassandra_db *db = (struct cassandra_db *)result->api.db; bool free_result = TRUE; result->finished = TRUE; result->finish_time = ioloop_timeval; driver_cassandra_result_unlink(db, result); i_assert((result->error != NULL) == (result->iterator == NULL)); result->api.callback = TRUE; T_BEGIN { result->callback(&result->api, result->context); } T_END; result->api.callback = FALSE; free_result = db->sync_result != &result->api; if (db->ioloop != NULL) io_loop_stop(db->ioloop); i_assert(!free_result || result->api.refcount > 0); result->callback = NULL; if (free_result) sql_result_unref(&result->api); } static void query_resend_with_fallback(struct cassandra_result *result) { struct cassandra_db *db = (struct cassandra_db *)result->api.db; time_t last_warning = ioloop_time - db->last_fallback_warning[result->query_type]; if (last_warning >= CASSANDRA_FALLBACK_WARN_INTERVAL_SECS) { e_warning(db->api.event, "%s - retrying future %s queries with consistency %s (instead of %s)", result->error, cassandra_query_type_names[result->query_type], cass_consistency_string(result->fallback_consistency), cass_consistency_string(result->consistency)); db->last_fallback_warning[result->query_type] = ioloop_time; } i_free_and_null(result->error); db->fallback_failures[result->query_type]++; result->consistency = result->fallback_consistency; driver_cassandra_result_send_query(result); } static void counters_inc_error(struct cassandra_db *db, CassError error) { switch (error) { case CASS_ERROR_LIB_NO_HOSTS_AVAILABLE: db->counters[CASSANDRA_COUNTER_TYPE_QUERY_RECV_ERR_NO_HOSTS]++; break; case CASS_ERROR_LIB_REQUEST_QUEUE_FULL: db->counters[CASSANDRA_COUNTER_TYPE_QUERY_RECV_ERR_QUEUE_FULL]++; break; case CASS_ERROR_LIB_REQUEST_TIMED_OUT: db->counters[CASSANDRA_COUNTER_TYPE_QUERY_RECV_ERR_CLIENT_TIMEOUT]++; break; case CASS_ERROR_SERVER_WRITE_TIMEOUT: db->counters[CASSANDRA_COUNTER_TYPE_QUERY_RECV_ERR_SERVER_TIMEOUT]++; break; case CASS_ERROR_SERVER_UNAVAILABLE: db->counters[CASSANDRA_COUNTER_TYPE_QUERY_RECV_ERR_SERVER_UNAVAILABLE]++; break; default: db->counters[CASSANDRA_COUNTER_TYPE_QUERY_RECV_ERR_OTHER]++; break; } } static bool query_error_want_fallback(CassError error) { switch (error) { case CASS_ERROR_LIB_WRITE_ERROR: case CASS_ERROR_LIB_REQUEST_TIMED_OUT: /* Communication problems on client side. Maybe it will work with fallback consistency? */ return TRUE; case CASS_ERROR_LIB_NO_HOSTS_AVAILABLE: /* The client library couldn't connect to enough Cassandra nodes. The error message text is the same as for CASS_ERROR_SERVER_UNAVAILABLE. */ return TRUE; case CASS_ERROR_SERVER_SERVER_ERROR: case CASS_ERROR_SERVER_OVERLOADED: case CASS_ERROR_SERVER_IS_BOOTSTRAPPING: case CASS_ERROR_SERVER_READ_TIMEOUT: case CASS_ERROR_SERVER_READ_FAILURE: case CASS_ERROR_SERVER_WRITE_FAILURE: /* Servers are having trouble. Maybe with fallback consistency we can reach non-troubled servers? */ return TRUE; case CASS_ERROR_SERVER_UNAVAILABLE: /* Cassandra server knows that there aren't enough nodes available. "All hosts in current policy attempted and were either unavailable or failed". */ return TRUE; case CASS_ERROR_SERVER_WRITE_TIMEOUT: /* Cassandra server couldn't reach all the needed nodes. This may be because it hasn't yet detected that the servers are down, or because the servers are just too busy. We'll try the fallback consistency to avoid unnecessary temporary errors. */ return TRUE; default: return FALSE; } } static enum sql_result_error_type driver_cassandra_error_is_uncertain(CassError error) { switch (error) { case CASS_ERROR_SERVER_WRITE_FAILURE: /* This happens when some of the replicas that were contacted * by the coordinator replied with an error. */ case CASS_ERROR_SERVER_WRITE_TIMEOUT: /* A Cassandra timeout during a write query. */ case CASS_ERROR_SERVER_UNAVAILABLE: /* The coordinator knows there are not enough replicas alive * to perform a query with the requested consistency level. */ case CASS_ERROR_LIB_REQUEST_TIMED_OUT: /* A request sent from the driver has timed out. */ case CASS_ERROR_LIB_WRITE_ERROR: /* A write error occured. */ return SQL_RESULT_ERROR_TYPE_WRITE_UNCERTAIN; default: return SQL_RESULT_ERROR_TYPE_UNKNOWN; } } static void query_callback(CassFuture *future, void *context) { struct cassandra_result *result = context; struct cassandra_db *db = (struct cassandra_db *)result->api.db; CassError error = cass_future_error_code(future); if (error != CASS_OK) { const char *errmsg; size_t errsize; int msecs; cass_future_error_message(future, &errmsg, &errsize); i_free(result->error); msecs = timeval_diff_msecs(&ioloop_timeval, &result->start_time); counters_inc_error(db, error); /* Timeouts bring uncertainty whether the query succeeded or not. Also _SERVER_UNAVAILABLE could have actually written enough copies of the data for the query to succeed. */ result->api.error_type = driver_cassandra_error_is_uncertain(error); result->error = i_strdup_printf( "Query '%s' failed: %.*s (in %u.%03u secs%s)", result->log_query, (int)errsize, errmsg, msecs/1000, msecs%1000, result->page_num == 0 ? "" : t_strdup_printf(", page %u", result->page_num)); if (query_error_want_fallback(error) && result->fallback_consistency != result->consistency) { /* retry with fallback consistency */ query_resend_with_fallback(result); return; } result_finish(result); return; } db->counters[CASSANDRA_COUNTER_TYPE_QUERY_RECV_OK]++; if (result->fallback_consistency != result->consistency) { /* non-fallback query finished successfully. if there had been any fallbacks, reset them. */ db->fallback_failures[result->query_type] = 0; } result->result = cass_future_get_result(future); result->iterator = cass_iterator_from_result(result->result); result_finish(result); } static void driver_cassandra_init_statement(struct cassandra_result *result) { struct cassandra_db *db = (struct cassandra_db *)result->api.db; cass_statement_set_consistency(result->statement, result->consistency); #ifdef HAVE_CASSANDRA_SPECULATIVE_POLICY cass_statement_set_is_idempotent(result->statement, cass_true); #endif if (db->page_size > 0) cass_statement_set_paging_size(result->statement, db->page_size); } static void driver_cassandra_result_send_query(struct cassandra_result *result) { struct cassandra_db *db = (struct cassandra_db *)result->api.db; CassFuture *future; i_assert(result->statement != NULL); db->counters[CASSANDRA_COUNTER_TYPE_QUERY_SENT]++; if (result->query_type != CASSANDRA_QUERY_TYPE_READ_MORE) driver_cassandra_init_statement(result); future = cass_session_execute(db->session, result->statement); driver_cassandra_set_callback(future, db, query_callback, result); } static bool driver_cassandra_want_fallback_query(struct cassandra_result *result) { struct cassandra_db *db = (struct cassandra_db *)result->api.db; unsigned int failure_count = db->fallback_failures[result->query_type]; unsigned int i, msecs = CASSANDRA_FALLBACK_FIRST_RETRY_MSECS; struct timeval tv; if (failure_count == 0) return FALSE; /* double the retries every time. */ for (i = 1; i < failure_count; i++) { msecs *= 2; if (msecs >= CASSANDRA_FALLBACK_MAX_RETRY_MSECS) { msecs = CASSANDRA_FALLBACK_MAX_RETRY_MSECS; break; } } /* If last primary query sent timestamp + msecs is older than current time, we need to retry the primary query. Note that this practically prevents multiple primary queries from being attempted simultaneously, because the caller updates primary_query_last_sent immediately when returning. The only time when multiple primary queries can be running in parallel is when the earlier query is being slow and hasn't finished early enough. This could even be a wanted feature, since while the first query might have to wait for a timeout, Cassandra could have been fixed in the meantime and the second query finishes successfully. */ tv = db->primary_query_last_sent[result->query_type]; timeval_add_msecs(&tv, msecs); return timeval_cmp(&ioloop_timeval, &tv) < 0; } static int driver_cassandra_send_query(struct cassandra_result *result) { struct cassandra_db *db = (struct cassandra_db *)result->api.db; int ret; if (!SQL_DB_IS_READY(&db->api)) { if ((ret = sql_connect(&db->api)) <= 0) { if (ret < 0) driver_cassandra_close(db, "Couldn't connect to Cassandra"); return ret; } } if (result->page0_start_time.tv_sec == 0) result->page0_start_time = ioloop_timeval; result->start_time = ioloop_timeval; result->row_pool = pool_alloconly_create("cassandra result", 512); switch (result->query_type) { case CASSANDRA_QUERY_TYPE_READ: result->consistency = db->read_consistency; result->fallback_consistency = db->read_fallback_consistency; break; case CASSANDRA_QUERY_TYPE_READ_MORE: /* consistency is already set and we don't want to fallback at this point anymore. */ result->fallback_consistency = result->consistency; break; case CASSANDRA_QUERY_TYPE_WRITE: result->consistency = db->write_consistency; result->fallback_consistency = db->write_fallback_consistency; break; case CASSANDRA_QUERY_TYPE_DELETE: result->consistency = db->delete_consistency; result->fallback_consistency = db->delete_fallback_consistency; break; case CASSANDRA_QUERY_TYPE_COUNT: i_unreached(); } if (driver_cassandra_want_fallback_query(result)) result->consistency = result->fallback_consistency; else db->primary_query_last_sent[result->query_type] = ioloop_timeval; driver_cassandra_result_send_query(result); result->query_sent = TRUE; return 1; } static void driver_cassandra_send_queries(struct cassandra_db *db) { struct cassandra_result *const *results; unsigned int i, count; results = array_get(&db->results, &count); for (i = 0; i < count; i++) { if (!results[i]->query_sent && results[i]->statement != NULL) { if (driver_cassandra_send_query(results[i]) <= 0) break; } } } static void exec_callback(struct sql_result *_result ATTR_UNUSED, void *context ATTR_UNUSED) { } static struct cassandra_result * driver_cassandra_query_init(struct cassandra_db *db, const char *log_query, enum cassandra_query_type query_type, bool is_prepared, sql_query_callback_t *callback, void *context) { struct cassandra_result *result; result = i_new(struct cassandra_result, 1); result->api = driver_cassandra_result; result->api.db = &db->api; result->api.refcount = 1; result->callback = callback; result->context = context; result->query_type = query_type; result->log_query = i_strdup(log_query); result->is_prepared = is_prepared; result->api.event = event_create(db->api.event); array_push_back(&db->results, &result); return result; } static void driver_cassandra_query_full(struct sql_db *_db, const char *query, enum cassandra_query_type query_type, sql_query_callback_t *callback, void *context) { struct cassandra_db *db = (struct cassandra_db *)_db; struct cassandra_result *result; result = driver_cassandra_query_init(db, query, query_type, FALSE, callback, context); result->statement = cass_statement_new(query, 0); (void)driver_cassandra_send_query(result); } static void driver_cassandra_exec(struct sql_db *db, const char *query) { driver_cassandra_query_full(db, query, CASSANDRA_QUERY_TYPE_WRITE, exec_callback, NULL); } static void driver_cassandra_query(struct sql_db *db, const char *query, sql_query_callback_t *callback, void *context) { driver_cassandra_query_full(db, query, CASSANDRA_QUERY_TYPE_READ, callback, context); } static void cassandra_query_s_callback(struct sql_result *result, void *context) { struct cassandra_db *db = context; db->sync_result = result; } static void driver_cassandra_sync_init(struct cassandra_db *db) { if (sql_connect(&db->api) < 0) return; db->orig_ioloop = current_ioloop; db->ioloop = io_loop_create(); if (IS_CONNECTED(db)) return; i_assert(db->api.state == SQL_DB_STATE_CONNECTING); db->io_pipe = io_loop_move_io(&db->io_pipe); /* wait for connecting to finish */ io_loop_run(db->ioloop); } static void driver_cassandra_sync_deinit(struct cassandra_db *db) { if (db->orig_ioloop == NULL) return; if (db->io_pipe != NULL) { io_loop_set_current(db->orig_ioloop); db->io_pipe = io_loop_move_io(&db->io_pipe); io_loop_set_current(db->ioloop); } io_loop_destroy(&db->ioloop); } static struct sql_result * driver_cassandra_sync_query(struct cassandra_db *db, const char *query, enum cassandra_query_type query_type) { struct sql_result *result; i_assert(db->sync_result == NULL); switch (db->api.state) { case SQL_DB_STATE_CONNECTING: case SQL_DB_STATE_BUSY: i_unreached(); case SQL_DB_STATE_DISCONNECTED: sql_not_connected_result.refcount++; return &sql_not_connected_result; case SQL_DB_STATE_IDLE: break; } driver_cassandra_query_full(&db->api, query, query_type, cassandra_query_s_callback, db); if (db->sync_result == NULL) { db->io_pipe = io_loop_move_io(&db->io_pipe); io_loop_run(db->ioloop); } result = db->sync_result; if (result == &sql_not_connected_result) { /* we don't end up in cassandra's free function, so sync_result won't be set to NULL if we don't do it here. */ db->sync_result = NULL; } else if (result == NULL) { result = &sql_not_connected_result; result->refcount++; } return result; } static struct sql_result * driver_cassandra_query_s(struct sql_db *_db, const char *query) { struct cassandra_db *db = (struct cassandra_db *)_db; struct sql_result *result; driver_cassandra_sync_init(db); result = driver_cassandra_sync_query(db, query, CASSANDRA_QUERY_TYPE_READ); driver_cassandra_sync_deinit(db); return result; } static int driver_cassandra_get_value(struct cassandra_result *result, const CassValue *value, const char **str_r, size_t *len_r) { const unsigned char *output; void *output_dup; size_t output_size; CassError rc; const char *type; if (cass_value_is_null(value) != 0) { *str_r = NULL; *len_r = 0; return 0; } switch (cass_data_type_type(cass_value_data_type(value))) { case CASS_VALUE_TYPE_INT: { cass_int32_t num; rc = cass_value_get_int32(value, &num); if (rc == CASS_OK) { const char *str = t_strdup_printf("%d", num); output_size = strlen(str); output = (const void *)str; } type = "int32"; break; } case CASS_VALUE_TYPE_TIMESTAMP: case CASS_VALUE_TYPE_BIGINT: { cass_int64_t num; rc = cass_value_get_int64(value, &num); if (rc == CASS_OK) { const char *str = t_strdup_printf("%lld", (long long)num); output_size = strlen(str); output = (const void *)str; } type = "int64"; break; } default: rc = cass_value_get_bytes(value, &output, &output_size); type = "bytes"; break; } if (rc != CASS_OK) { i_free(result->error); result->error = i_strdup_printf("Couldn't get value as %s: %s", type, cass_error_desc(rc)); return -1; } output_dup = p_malloc(result->row_pool, output_size + 1); memcpy(output_dup, output, output_size); *str_r = output_dup; *len_r = output_size; return 0; } static int driver_cassandra_result_next_page(struct cassandra_result *result) { struct cassandra_db *db = (struct cassandra_db *)result->api.db; if (db->page_size == 0) { /* no paging */ return 0; } if (cass_result_has_more_pages(result->result) == cass_false) return 0; /* callers that don't support sql_query_more() will still get a useful error message. */ i_free(result->error); result->error = i_strdup( "Paged query has more results, but not supported by the caller"); return SQL_RESULT_NEXT_MORE; } static int driver_cassandra_result_next_row(struct sql_result *_result) { struct cassandra_result *result = (struct cassandra_result *)_result; const CassRow *row; const CassValue *value; const char *str; size_t size; unsigned int i; int ret = 1; if (result->iterator == NULL) return -1; if (cass_iterator_next(result->iterator) == 0) return driver_cassandra_result_next_page(result); result->row_count++; result->total_row_count++; p_clear(result->row_pool); p_array_init(&result->fields, result->row_pool, 8); p_array_init(&result->field_sizes, result->row_pool, 8); row = cass_iterator_get_row(result->iterator); for (i = 0; (value = cass_row_get_column(row, i)) != NULL; i++) { if (driver_cassandra_get_value(result, value, &str, &size) < 0) { ret = -1; break; } array_push_back(&result->fields, &str); array_push_back(&result->field_sizes, &size); } return ret; } static void driver_cassandra_result_more(struct sql_result **_result, bool async, sql_query_callback_t *callback, void *context) { struct cassandra_db *db = (struct cassandra_db *)(*_result)->db; struct cassandra_result *new_result; struct cassandra_result *old_result = (struct cassandra_result *)*_result; /* Initialize the next page as a new sql_result */ new_result = driver_cassandra_query_init(db, old_result->log_query, CASSANDRA_QUERY_TYPE_READ_MORE, old_result->is_prepared, callback, context); /* Preserve the statement and update its paging state */ new_result->statement = old_result->statement; old_result->statement = NULL; cass_statement_set_paging_state(new_result->statement, old_result->result); old_result->paging_continues = TRUE; /* The caller did support paging. Clear out the "...not supported by the caller" error text, so it won't be in the debug log output. */ i_free_and_null(old_result->error); new_result->timestamp = old_result->timestamp; new_result->consistency = old_result->consistency; new_result->page_num = old_result->page_num + 1; new_result->page0_start_time = old_result->page0_start_time; new_result->total_row_count = old_result->total_row_count; sql_result_unref(*_result); *_result = NULL; if (async) (void)driver_cassandra_send_query(new_result); else { i_assert(db->api.state == SQL_DB_STATE_IDLE); driver_cassandra_sync_init(db); (void)driver_cassandra_send_query(new_result); if (new_result->result == NULL) { db->io_pipe = io_loop_move_io(&db->io_pipe); io_loop_run(db->ioloop); } driver_cassandra_sync_deinit(db); callback(&new_result->api, context); } } static unsigned int driver_cassandra_result_get_fields_count(struct sql_result *_result) { struct cassandra_result *result = (struct cassandra_result *)_result; return array_count(&result->fields); } static const char * driver_cassandra_result_get_field_name(struct sql_result *_result ATTR_UNUSED, unsigned int idx ATTR_UNUSED) { i_unreached(); } static int driver_cassandra_result_find_field(struct sql_result *_result ATTR_UNUSED, const char *field_name ATTR_UNUSED) { i_unreached(); } static const char * driver_cassandra_result_get_field_value(struct sql_result *_result, unsigned int idx) { struct cassandra_result *result = (struct cassandra_result *)_result; return array_idx_elem(&result->fields, idx); } static const unsigned char * driver_cassandra_result_get_field_value_binary(struct sql_result *_result ATTR_UNUSED, unsigned int idx ATTR_UNUSED, size_t *size_r ATTR_UNUSED) { struct cassandra_result *result = (struct cassandra_result *)_result; const char *str; const size_t *sizep; str = array_idx_elem(&result->fields, idx); sizep = array_idx(&result->field_sizes, idx); *size_r = *sizep; return (const void *)str; } static const char * driver_cassandra_result_find_field_value(struct sql_result *result ATTR_UNUSED, const char *field_name ATTR_UNUSED) { i_unreached(); } static const char *const * driver_cassandra_result_get_values(struct sql_result *_result) { struct cassandra_result *result = (struct cassandra_result *)_result; return array_front(&result->fields); } static const char *driver_cassandra_result_get_error(struct sql_result *_result) { struct cassandra_result *result = (struct cassandra_result *)_result; if (result->error != NULL) return result->error; return "FIXME"; } static struct sql_transaction_context * driver_cassandra_transaction_begin(struct sql_db *db) { struct cassandra_transaction_context *ctx; ctx = i_new(struct cassandra_transaction_context, 1); ctx->ctx.db = db; ctx->ctx.event = event_create(db->event); ctx->refcount = 1; return &ctx->ctx; } static void driver_cassandra_transaction_unref(struct cassandra_transaction_context **_ctx) { struct cassandra_transaction_context *ctx = *_ctx; *_ctx = NULL; i_assert(ctx->refcount > 0); if (--ctx->refcount > 0) return; event_unref(&ctx->ctx.event); i_free(ctx->log_query); i_free(ctx->query); i_free(ctx->error); i_free(ctx); } static void transaction_set_failed(struct cassandra_transaction_context *ctx, const char *error) { if (ctx->failed) { i_assert(ctx->error != NULL); } else { i_assert(ctx->error == NULL); ctx->failed = TRUE; ctx->error = i_strdup(error); } } static void transaction_commit_callback(struct sql_result *result, void *context) { struct cassandra_transaction_context *ctx = context; struct sql_commit_result commit_result; i_zero(&commit_result); if (sql_result_next_row(result) < 0) { commit_result.error = sql_result_get_error(result); commit_result.error_type = sql_result_get_error_type(result); e_debug(sql_transaction_finished_event(&ctx->ctx)-> add_str("error", commit_result.error)->event(), "Transaction failed"); } else { e_debug(sql_transaction_finished_event(&ctx->ctx)->event(), "Transaction committed"); } ctx->callback(&commit_result, ctx->context); driver_cassandra_transaction_unref(&ctx); } static void driver_cassandra_transaction_commit(struct sql_transaction_context *_ctx, sql_commit_callback_t *callback, void *context) { struct cassandra_transaction_context *ctx = (struct cassandra_transaction_context *)_ctx; struct cassandra_db *db = (struct cassandra_db *)_ctx->db; enum cassandra_query_type query_type; struct sql_commit_result result; i_zero(&result); ctx->callback = callback; ctx->context = context; if (ctx->failed || (ctx->query == NULL && ctx->stmt == NULL)) { if (ctx->failed) result.error = ctx->error; e_debug(sql_transaction_finished_event(_ctx)-> add_str("error", "Rolled back")->event(), "Transaction rolled back"); callback(&result, context); driver_cassandra_transaction_unref(&ctx); return; } /* just a single query, send it */ const char *query = ctx->query != NULL ? ctx->query : sql_statement_get_query(&ctx->stmt->stmt); if (strncasecmp(query, "DELETE ", 7) == 0) query_type = CASSANDRA_QUERY_TYPE_DELETE; else query_type = CASSANDRA_QUERY_TYPE_WRITE; if (ctx->query != NULL) { struct cassandra_result *cass_result; cass_result = driver_cassandra_query_init(db, ctx->log_query, query_type, FALSE, transaction_commit_callback, ctx); cass_result->statement = cass_statement_new(query, 0); if (ctx->query_timestamp != 0) { cass_result->timestamp = ctx->query_timestamp; cass_statement_set_timestamp(cass_result->statement, ctx->query_timestamp); } (void)driver_cassandra_send_query(cass_result); } else { ctx->stmt->result = driver_cassandra_query_init(db, sql_statement_get_log_query(&ctx->stmt->stmt), query_type, TRUE, transaction_commit_callback, ctx); if (ctx->stmt->cass_stmt == NULL) { /* wait for prepare to finish */ } else { ctx->stmt->result->statement = ctx->stmt->cass_stmt; ctx->stmt->result->timestamp = ctx->stmt->timestamp; (void)driver_cassandra_send_query(ctx->stmt->result); pool_unref(&ctx->stmt->stmt.pool); } } } static void driver_cassandra_try_commit_s(struct cassandra_transaction_context *ctx) { struct sql_transaction_context *_ctx = &ctx->ctx; struct cassandra_db *db = (struct cassandra_db *)_ctx->db; struct sql_result *result = NULL; enum cassandra_query_type query_type; /* just a single query, send it */ if (strncasecmp(ctx->query, "DELETE ", 7) == 0) query_type = CASSANDRA_QUERY_TYPE_DELETE; else query_type = CASSANDRA_QUERY_TYPE_WRITE; driver_cassandra_sync_init(db); result = driver_cassandra_sync_query(db, ctx->query, query_type); driver_cassandra_sync_deinit(db); if (sql_result_next_row(result) < 0) transaction_set_failed(ctx, sql_result_get_error(result)); sql_result_unref(result); } static int driver_cassandra_transaction_commit_s(struct sql_transaction_context *_ctx, const char **error_r) { struct cassandra_transaction_context *ctx = (struct cassandra_transaction_context *)_ctx; if (ctx->stmt != NULL) { /* nothing should be using this - don't bother implementing */ i_panic("cassandra: sql_transaction_commit_s() not supported for prepared statements"); } if (ctx->query != NULL && !ctx->failed) driver_cassandra_try_commit_s(ctx); *error_r = t_strdup(ctx->error); i_assert(ctx->refcount == 1); i_assert((*error_r != NULL) == ctx->failed); driver_cassandra_transaction_unref(&ctx); return *error_r == NULL ? 0 : -1; } static void driver_cassandra_transaction_rollback(struct sql_transaction_context *_ctx) { struct cassandra_transaction_context *ctx = (struct cassandra_transaction_context *)_ctx; i_assert(ctx->refcount == 1); driver_cassandra_transaction_unref(&ctx); } static void driver_cassandra_update(struct sql_transaction_context *_ctx, const char *query, unsigned int *affected_rows) { struct cassandra_transaction_context *ctx = (struct cassandra_transaction_context *)_ctx; i_assert(affected_rows == NULL); if (ctx->query != NULL || ctx->stmt != NULL) { transaction_set_failed(ctx, "Multiple changes in transaction not supported"); return; } ctx->query = i_strdup(query); /* When log_query is set here it can contain expanded values even if stmt->no_log_expanded_values is set. */ ctx->log_query = i_strdup(query); } static const char * driver_cassandra_escape_blob(struct sql_db *_db ATTR_UNUSED, const unsigned char *data, size_t size) { string_t *str = t_str_new(128); str_append(str, "0x"); binary_to_hex_append(str, data, size); return str_c(str); } static CassError driver_cassandra_bind_int(struct cassandra_sql_statement *stmt, unsigned int column_idx, int64_t value) { const CassDataType *data_type; CassValueType value_type; i_assert(stmt->prep != NULL); /* statements require exactly correct value type */ data_type = cass_prepared_parameter_data_type(stmt->prep->prepared, column_idx); value_type = cass_data_type_type(data_type); switch (value_type) { case CASS_VALUE_TYPE_INT: if (value < INT32_MIN || value > INT32_MAX) return CASS_ERROR_LIB_INVALID_VALUE_TYPE; return cass_statement_bind_int32(stmt->cass_stmt, column_idx, value); case CASS_VALUE_TYPE_TIMESTAMP: case CASS_VALUE_TYPE_BIGINT: return cass_statement_bind_int64(stmt->cass_stmt, column_idx, value); case CASS_VALUE_TYPE_SMALL_INT: if (value < INT16_MIN || value > INT16_MAX) return CASS_ERROR_LIB_INVALID_VALUE_TYPE; return cass_statement_bind_int16(stmt->cass_stmt, column_idx, value); case CASS_VALUE_TYPE_TINY_INT: if (value < INT8_MIN || value > INT8_MAX) return CASS_ERROR_LIB_INVALID_VALUE_TYPE; return cass_statement_bind_int8(stmt->cass_stmt, column_idx, value); default: return CASS_ERROR_LIB_INVALID_VALUE_TYPE; } } static void prepare_finish_arg(struct cassandra_sql_statement *stmt, const struct cassandra_sql_arg *arg) { CassError rc; if (arg->value_str != NULL) { rc = cass_statement_bind_string(stmt->cass_stmt, arg->column_idx, arg->value_str); } else if (arg->value_binary != NULL) { rc = cass_statement_bind_bytes(stmt->cass_stmt, arg->column_idx, arg->value_binary, arg->value_binary_size); } else { rc = driver_cassandra_bind_int(stmt, arg->column_idx, arg->value_int64); } if (rc != CASS_OK) { e_error(stmt->stmt.db->event, "Statement '%s': Failed to bind column %u: %s", stmt->stmt.query_template, arg->column_idx, cass_error_desc(rc)); } } static void prepare_finish_statement(struct cassandra_sql_statement *stmt) { const struct cassandra_sql_arg *arg; if (stmt->prep->prepared == NULL) { i_assert(stmt->prep->error != NULL); if (stmt->result != NULL) { stmt->result->error = i_strdup(stmt->prep->error); result_finish(stmt->result); } pool_unref(&stmt->stmt.pool); return; } stmt->cass_stmt = cass_prepared_bind(stmt->prep->prepared); if (stmt->timestamp != 0) cass_statement_set_timestamp(stmt->cass_stmt, stmt->timestamp); if (array_is_created(&stmt->pending_args)) { array_foreach(&stmt->pending_args, arg) prepare_finish_arg(stmt, arg); } if (stmt->result != NULL) { stmt->result->statement = stmt->cass_stmt; stmt->result->timestamp = stmt->timestamp; (void)driver_cassandra_send_query(stmt->result); pool_unref(&stmt->stmt.pool); } } static void prepare_finish_pending_statements(struct cassandra_sql_prepared_statement *prep_stmt) { struct cassandra_sql_statement *stmt; array_foreach_elem(&prep_stmt->pending_statements, stmt) prepare_finish_statement(stmt); array_clear(&prep_stmt->pending_statements); } static void prepare_callback(CassFuture *future, void *context) { struct cassandra_sql_prepared_statement *prep_stmt = context; CassError error = cass_future_error_code(future); if (error != CASS_OK) { const char *errmsg; size_t errsize; cass_future_error_message(future, &errmsg, &errsize); i_free(prep_stmt->error); prep_stmt->error = i_strndup(errmsg, errsize); } else { prep_stmt->prepared = cass_future_get_prepared(future); } prepare_finish_pending_statements(prep_stmt); } static void prepare_start(struct cassandra_sql_prepared_statement *prep_stmt) { struct cassandra_db *db = (struct cassandra_db *)prep_stmt->prep_stmt.db; CassFuture *future; if (!SQL_DB_IS_READY(&db->api)) { if (!prep_stmt->pending) { prep_stmt->pending = TRUE; array_push_back(&db->pending_prepares, &prep_stmt); if (sql_connect(&db->api) < 0) i_unreached(); } return; } /* clear the current error in case we're retrying */ i_free_and_null(prep_stmt->error); future = cass_session_prepare(db->session, prep_stmt->prep_stmt.query_template); driver_cassandra_set_callback(future, db, prepare_callback, prep_stmt); } static void driver_cassandra_prepare_pending(struct cassandra_db *db) { struct cassandra_sql_prepared_statement *prep_stmt; i_assert(SQL_DB_IS_READY(&db->api)); array_foreach_elem(&db->pending_prepares, prep_stmt) { prep_stmt->pending = FALSE; prepare_start(prep_stmt); } array_clear(&db->pending_prepares); } static struct sql_prepared_statement * driver_cassandra_prepared_statement_init(struct sql_db *db, const char *query_template) { struct cassandra_sql_prepared_statement *prep_stmt = i_new(struct cassandra_sql_prepared_statement, 1); prep_stmt->prep_stmt.db = db; prep_stmt->prep_stmt.refcount = 1; prep_stmt->prep_stmt.query_template = i_strdup(query_template); i_array_init(&prep_stmt->pending_statements, 4); prepare_start(prep_stmt); return &prep_stmt->prep_stmt; } static void driver_cassandra_prepared_statement_deinit(struct sql_prepared_statement *_prep_stmt) { struct cassandra_sql_prepared_statement *prep_stmt = (struct cassandra_sql_prepared_statement *)_prep_stmt; i_assert(array_count(&prep_stmt->pending_statements) == 0); if (prep_stmt->prepared != NULL) cass_prepared_free(prep_stmt->prepared); array_free(&prep_stmt->pending_statements); i_free(prep_stmt->error); i_free(prep_stmt->prep_stmt.query_template); i_free(prep_stmt); } static struct sql_statement * driver_cassandra_statement_init(struct sql_db *db ATTR_UNUSED, const char *query_template ATTR_UNUSED) { pool_t pool = pool_alloconly_create("cassandra sql statement", 1024); struct cassandra_sql_statement *stmt = p_new(pool, struct cassandra_sql_statement, 1); stmt->stmt.pool = pool; return &stmt->stmt; } static struct sql_statement * driver_cassandra_statement_init_prepared(struct sql_prepared_statement *_prep_stmt) { struct cassandra_sql_prepared_statement *prep_stmt = (struct cassandra_sql_prepared_statement *)_prep_stmt; pool_t pool = pool_alloconly_create("cassandra prepared sql statement", 1024); struct cassandra_sql_statement *stmt = p_new(pool, struct cassandra_sql_statement, 1); stmt->stmt.pool = pool; stmt->stmt.query_template = p_strdup(stmt->stmt.pool, prep_stmt->prep_stmt.query_template); stmt->prep = prep_stmt; if (prep_stmt->prepared != NULL) { /* statement is already prepared. we can use it immediately. */ stmt->cass_stmt = cass_prepared_bind(prep_stmt->prepared); } else { if (prep_stmt->error != NULL) prepare_start(prep_stmt); /* need to wait until prepare is finished */ array_push_back(&prep_stmt->pending_statements, &stmt); } return &stmt->stmt; } static void driver_cassandra_statement_abort(struct sql_statement *_stmt) { struct cassandra_sql_statement *stmt = (struct cassandra_sql_statement *)_stmt; if (stmt->cass_stmt != NULL) cass_statement_free(stmt->cass_stmt); } static void driver_cassandra_statement_set_timestamp(struct sql_statement *_stmt, const struct timespec *ts) { struct cassandra_sql_statement *stmt = (struct cassandra_sql_statement *)_stmt; cass_int64_t ts_usecs = (cass_int64_t)ts->tv_sec * 1000000ULL + ts->tv_nsec / 1000; i_assert(stmt->result == NULL); if (stmt->cass_stmt != NULL) cass_statement_set_timestamp(stmt->cass_stmt, ts_usecs); stmt->timestamp = ts_usecs; } static struct cassandra_sql_arg * driver_cassandra_add_pending_arg(struct cassandra_sql_statement *stmt, unsigned int column_idx) { struct cassandra_sql_arg *arg; if (!array_is_created(&stmt->pending_args)) p_array_init(&stmt->pending_args, stmt->stmt.pool, 8); arg = array_append_space(&stmt->pending_args); arg->column_idx = column_idx; return arg; } static void driver_cassandra_statement_bind_str(struct sql_statement *_stmt, unsigned int column_idx, const char *value) { struct cassandra_sql_statement *stmt = (struct cassandra_sql_statement *)_stmt; if (stmt->cass_stmt != NULL) cass_statement_bind_string(stmt->cass_stmt, column_idx, value); else if (stmt->prep != NULL) { struct cassandra_sql_arg *arg = driver_cassandra_add_pending_arg(stmt, column_idx); arg->value_str = p_strdup(_stmt->pool, value); } } static void driver_cassandra_statement_bind_binary(struct sql_statement *_stmt, unsigned int column_idx, const void *value, size_t value_size) { struct cassandra_sql_statement *stmt = (struct cassandra_sql_statement *)_stmt; if (stmt->cass_stmt != NULL) { cass_statement_bind_bytes(stmt->cass_stmt, column_idx, value, value_size); } else if (stmt->prep != NULL) { struct cassandra_sql_arg *arg = driver_cassandra_add_pending_arg(stmt, column_idx); arg->value_binary = value_size == 0 ? &uchar_nul : p_memdup(_stmt->pool, value, value_size); arg->value_binary_size = value_size; } } static void driver_cassandra_statement_bind_int64(struct sql_statement *_stmt, unsigned int column_idx, int64_t value) { struct cassandra_sql_statement *stmt = (struct cassandra_sql_statement *)_stmt; if (stmt->cass_stmt != NULL) driver_cassandra_bind_int(stmt, column_idx, value); else if (stmt->prep != NULL) { struct cassandra_sql_arg *arg = driver_cassandra_add_pending_arg(stmt, column_idx); arg->value_int64 = value; } } static void driver_cassandra_statement_query(struct sql_statement *_stmt, sql_query_callback_t *callback, void *context) { struct cassandra_sql_statement *stmt = (struct cassandra_sql_statement *)_stmt; struct cassandra_db *db = (struct cassandra_db *)_stmt->db; const char *query = sql_statement_get_query(_stmt); bool is_prepared = stmt->cass_stmt != NULL || stmt->prep != NULL; stmt->result = driver_cassandra_query_init(db, sql_statement_get_log_query(_stmt), CASSANDRA_QUERY_TYPE_READ, is_prepared, callback, context); if (stmt->cass_stmt != NULL) { stmt->result->statement = stmt->cass_stmt; stmt->result->timestamp = stmt->timestamp; } else if (stmt->prep != NULL) { /* wait for prepare to finish */ return; } else { stmt->result->statement = cass_statement_new(query, 0); stmt->result->timestamp = stmt->timestamp; if (stmt->timestamp != 0) { cass_statement_set_timestamp(stmt->result->statement, stmt->timestamp); } } (void)driver_cassandra_send_query(stmt->result); pool_unref(&_stmt->pool); } static struct sql_result * driver_cassandra_statement_query_s(struct sql_statement *_stmt ATTR_UNUSED) { i_panic("cassandra: sql_statement_query_s() not supported"); } static void driver_cassandra_update_stmt(struct sql_transaction_context *_ctx, struct sql_statement *_stmt, unsigned int *affected_rows) { struct cassandra_transaction_context *ctx = (struct cassandra_transaction_context *)_ctx; struct cassandra_sql_statement *stmt = (struct cassandra_sql_statement *)_stmt; i_assert(affected_rows == NULL); if (ctx->query != NULL || ctx->stmt != NULL) { transaction_set_failed(ctx, "Multiple changes in transaction not supported"); return; } if (stmt->prep != NULL) ctx->stmt = stmt; else { ctx->query = i_strdup(sql_statement_get_query(_stmt)); ctx->log_query = i_strdup(sql_statement_get_log_query(_stmt)); ctx->query_timestamp = stmt->timestamp; pool_unref(&_stmt->pool); } } static bool driver_cassandra_have_work(struct cassandra_db *db) { return array_not_empty(&db->pending_prepares) || array_not_empty(&db->callbacks) || array_not_empty(&db->results); } static void driver_cassandra_wait(struct sql_db *_db) { struct cassandra_db *db = (struct cassandra_db *)_db; if (!driver_cassandra_have_work(db)) return; struct ioloop *prev_ioloop = current_ioloop; db->ioloop = io_loop_create(); db->io_pipe = io_loop_move_io(&db->io_pipe); while (driver_cassandra_have_work(db)) io_loop_run(db->ioloop); io_loop_set_current(prev_ioloop); db->io_pipe = io_loop_move_io(&db->io_pipe); io_loop_set_current(db->ioloop); io_loop_destroy(&db->ioloop); } const struct sql_db driver_cassandra_db = { .name = "cassandra", .flags = SQL_DB_FLAG_PREP_STATEMENTS, .v = { .init_full = driver_cassandra_init_full_v, .deinit = driver_cassandra_deinit_v, .connect = driver_cassandra_connect, .disconnect = driver_cassandra_disconnect, .escape_string = driver_cassandra_escape_string, .exec = driver_cassandra_exec, .query = driver_cassandra_query, .query_s = driver_cassandra_query_s, .wait = driver_cassandra_wait, .transaction_begin = driver_cassandra_transaction_begin, .transaction_commit = driver_cassandra_transaction_commit, .transaction_commit_s = driver_cassandra_transaction_commit_s, .transaction_rollback = driver_cassandra_transaction_rollback, .update = driver_cassandra_update, .escape_blob = driver_cassandra_escape_blob, .prepared_statement_init = driver_cassandra_prepared_statement_init, .prepared_statement_deinit = driver_cassandra_prepared_statement_deinit, .statement_init = driver_cassandra_statement_init, .statement_init_prepared = driver_cassandra_statement_init_prepared, .statement_abort = driver_cassandra_statement_abort, .statement_set_timestamp = driver_cassandra_statement_set_timestamp, .statement_bind_str = driver_cassandra_statement_bind_str, .statement_bind_binary = driver_cassandra_statement_bind_binary, .statement_bind_int64 = driver_cassandra_statement_bind_int64, .statement_query = driver_cassandra_statement_query, .statement_query_s = driver_cassandra_statement_query_s, .update_stmt = driver_cassandra_update_stmt, } }; const struct sql_result driver_cassandra_result = { .v = { driver_cassandra_result_free, driver_cassandra_result_next_row, driver_cassandra_result_get_fields_count, driver_cassandra_result_get_field_name, driver_cassandra_result_find_field, driver_cassandra_result_get_field_value, driver_cassandra_result_get_field_value_binary, driver_cassandra_result_find_field_value, driver_cassandra_result_get_values, driver_cassandra_result_get_error, driver_cassandra_result_more, } }; const char *driver_cassandra_version = DOVECOT_ABI_VERSION; void driver_cassandra_init(void); void driver_cassandra_deinit(void); void driver_cassandra_init(void) { sql_driver_register(&driver_cassandra_db); } void driver_cassandra_deinit(void) { sql_driver_unregister(&driver_cassandra_db); } #endif dovecot-2.3.21.1/src/lib-sql/driver-sqlpool.c0000644000000000000000000006117314656633576015605 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "llist.h" #include "ioloop.h" #include "sql-api-private.h" #include #define QUERY_TIMEOUT_SECS 6 /* sqlpool events are separate from category:sql, because they are usually not very interesting, and would only make logging too noisy. They can be enabled explicitly. */ static struct event_category event_category_sqlpool = { .name = "sqlpool", }; struct sqlpool_host { char *connect_string; unsigned int connection_count; }; struct sqlpool_connection { struct sql_db *db; unsigned int host_idx; }; struct sqlpool_db { struct sql_db api; pool_t pool; const struct sql_db *driver; unsigned int connection_limit; ARRAY(struct sqlpool_host) hosts; /* all connections from all hosts */ ARRAY(struct sqlpool_connection) all_connections; /* index of last connection in all_connections that was used to send a query. */ unsigned int last_query_conn_idx; /* queued requests */ struct sqlpool_request *requests_head, *requests_tail; struct timeout *request_to; }; struct sqlpool_request { struct sqlpool_request *prev, *next; struct sqlpool_db *db; time_t created; unsigned int host_idx; unsigned int retry_count; struct event *event; /* requests are a) queries */ char *query; sql_query_callback_t *callback; void *context; /* b) transaction waiters */ struct sqlpool_transaction_context *trans; }; struct sqlpool_transaction_context { struct sql_transaction_context ctx; sql_commit_callback_t *callback; void *context; pool_t query_pool; struct sqlpool_request *commit_request; }; extern struct sql_db driver_sqlpool_db; static struct sqlpool_connection * sqlpool_add_connection(struct sqlpool_db *db, struct sqlpool_host *host, unsigned int host_idx); static void driver_sqlpool_query_callback(struct sql_result *result, struct sqlpool_request *request); static void driver_sqlpool_commit_callback(const struct sql_commit_result *result, struct sqlpool_transaction_context *ctx); static void driver_sqlpool_deinit(struct sql_db *_db); static struct sqlpool_request * ATTR_NULL(2) sqlpool_request_new(struct sqlpool_db *db, const char *query) { struct sqlpool_request *request; request = i_new(struct sqlpool_request, 1); request->db = db; request->created = time(NULL); request->query = i_strdup(query); request->event = event_create(db->api.event); return request; } static void sqlpool_request_free(struct sqlpool_request **_request) { struct sqlpool_request *request = *_request; *_request = NULL; i_assert(request->prev == NULL && request->next == NULL); event_unref(&request->event); i_free(request->query); i_free(request); } static void sqlpool_request_abort(struct sqlpool_request **_request) { struct sqlpool_request *request = *_request; *_request = NULL; if (request->callback != NULL) request->callback(&sql_not_connected_result, request->context); i_assert(request->prev != NULL || request->db->requests_head == request); DLLIST2_REMOVE(&request->db->requests_head, &request->db->requests_tail, request); sqlpool_request_free(&request); } static struct sql_transaction_context * driver_sqlpool_new_conn_trans(struct sqlpool_transaction_context *trans, struct sql_db *conndb) { struct sql_transaction_context *conn_trans; struct sql_transaction_query *query; conn_trans = sql_transaction_begin(conndb); /* backend will use our queries list (we might still append more queries to the list) */ conn_trans->head = trans->ctx.head; conn_trans->tail = trans->ctx.tail; for (query = conn_trans->head; query != NULL; query = query->next) query->trans = conn_trans; return conn_trans; } static void sqlpool_request_handle_transaction(struct sql_db *conndb, struct sqlpool_transaction_context *trans) { struct sql_transaction_context *conn_trans; sqlpool_request_free(&trans->commit_request); conn_trans = driver_sqlpool_new_conn_trans(trans, conndb); sql_transaction_commit(&conn_trans, driver_sqlpool_commit_callback, trans); } static void sqlpool_request_send_next(struct sqlpool_db *db, struct sql_db *conndb) { struct sqlpool_request *request; if (db->requests_head == NULL || !SQL_DB_IS_READY(conndb)) return; request = db->requests_head; DLLIST2_REMOVE(&db->requests_head, &db->requests_tail, request); timeout_reset(db->request_to); if (request->query != NULL) { sql_query(conndb, request->query, driver_sqlpool_query_callback, request); } else if (request->trans != NULL) { sqlpool_request_handle_transaction(conndb, request->trans); } else { i_unreached(); } } static void sqlpool_reconnect(struct sql_db *conndb) { timeout_remove(&conndb->to_reconnect); (void)sql_connect(conndb); } static struct sqlpool_host * sqlpool_find_host_with_least_connections(struct sqlpool_db *db, unsigned int *host_idx_r) { struct sqlpool_host *hosts, *min = NULL; unsigned int i, count; hosts = array_get_modifiable(&db->hosts, &count); i_assert(count > 0); min = &hosts[0]; *host_idx_r = 0; for (i = 1; i < count; i++) { if (min->connection_count > hosts[i].connection_count) { min = &hosts[i]; *host_idx_r = i; } } return min; } static bool sqlpool_have_successful_connections(struct sqlpool_db *db) { const struct sqlpool_connection *conn; array_foreach(&db->all_connections, conn) { if (conn->db->state >= SQL_DB_STATE_IDLE) return TRUE; } return FALSE; } static void sqlpool_handle_connect_failed(struct sqlpool_db *db, struct sql_db *conndb) { struct sqlpool_host *host; unsigned int host_idx; if (conndb->connect_failure_count > 0) { /* increase delay between reconnections to this server */ conndb->connect_delay *= 5; if (conndb->connect_delay > SQL_CONNECT_MAX_DELAY) conndb->connect_delay = SQL_CONNECT_MAX_DELAY; } conndb->connect_failure_count++; /* reconnect after the delay */ timeout_remove(&conndb->to_reconnect); conndb->to_reconnect = timeout_add(conndb->connect_delay * 1000, sqlpool_reconnect, conndb); /* if we have zero successful hosts and there still are hosts without connections, connect to one of them. */ if (!sqlpool_have_successful_connections(db)) { host = sqlpool_find_host_with_least_connections(db, &host_idx); if (host->connection_count == 0) (void)sqlpool_add_connection(db, host, host_idx); } } static void sqlpool_state_changed(struct sql_db *conndb, enum sql_db_state prev_state, void *context) { struct sqlpool_db *db = context; if (conndb->state == SQL_DB_STATE_IDLE) { conndb->connect_failure_count = 0; conndb->connect_delay = SQL_CONNECT_MIN_DELAY; sqlpool_request_send_next(db, conndb); } if (prev_state == SQL_DB_STATE_CONNECTING && conndb->state == SQL_DB_STATE_DISCONNECTED && !conndb->no_reconnect) sqlpool_handle_connect_failed(db, conndb); } static struct sqlpool_connection * sqlpool_add_connection(struct sqlpool_db *db, struct sqlpool_host *host, unsigned int host_idx) { struct sql_db *conndb; struct sqlpool_connection *conn; const char *error; int ret = 0; host->connection_count++; e_debug(db->api.event, "Creating new connection"); if (db->driver->v.init_full == NULL) { conndb = db->driver->v.init(host->connect_string); } else { struct sql_settings set = { .connect_string = host->connect_string, .event_parent = event_get_parent(db->api.event), }; ret = db->driver->v.init_full(&set, &conndb, &error); } if (ret < 0) i_fatal("sqlpool: %s", error); sql_init_common(conndb); conndb->state_change_callback = sqlpool_state_changed; conndb->state_change_context = db; conndb->connect_delay = SQL_CONNECT_MIN_DELAY; conn = array_append_space(&db->all_connections); conn->host_idx = host_idx; conn->db = conndb; return conn; } static struct sqlpool_connection * sqlpool_add_new_connection(struct sqlpool_db *db) { struct sqlpool_host *host; unsigned int host_idx; host = sqlpool_find_host_with_least_connections(db, &host_idx); if (host->connection_count >= db->connection_limit) return NULL; else return sqlpool_add_connection(db, host, host_idx); } static const struct sqlpool_connection * sqlpool_find_available_connection(struct sqlpool_db *db, unsigned int unwanted_host_idx, bool *all_disconnected_r) { const struct sqlpool_connection *conns; unsigned int i, count; *all_disconnected_r = TRUE; conns = array_get(&db->all_connections, &count); for (i = 0; i < count; i++) { unsigned int idx = (i + db->last_query_conn_idx + 1) % count; struct sql_db *conndb = conns[idx].db; if (conns[idx].host_idx == unwanted_host_idx) continue; if (!SQL_DB_IS_READY(conndb) && conndb->to_reconnect == NULL) { /* see if we could reconnect to it immediately */ (void)sql_connect(conndb); } if (SQL_DB_IS_READY(conndb)) { db->last_query_conn_idx = idx; *all_disconnected_r = FALSE; return &conns[idx]; } if (conndb->state != SQL_DB_STATE_DISCONNECTED) *all_disconnected_r = FALSE; } return NULL; } static bool driver_sqlpool_get_connection(struct sqlpool_db *db, unsigned int unwanted_host_idx, const struct sqlpool_connection **conn_r) { const struct sqlpool_connection *conn, *conns; unsigned int i, count; bool all_disconnected; conn = sqlpool_find_available_connection(db, unwanted_host_idx, &all_disconnected); if (conn == NULL && unwanted_host_idx != UINT_MAX) { /* maybe there are no wanted hosts. use any of them. */ conn = sqlpool_find_available_connection(db, UINT_MAX, &all_disconnected); } if (conn == NULL && all_disconnected) { /* no connected connections. connect_delays may have gotten too high, reset all of them to see if some are still alive. */ conns = array_get(&db->all_connections, &count); for (i = 0; i < count; i++) { struct sql_db *conndb = conns[i].db; if (conndb->connect_delay > SQL_CONNECT_RESET_DELAY) conndb->connect_delay = SQL_CONNECT_RESET_DELAY; } conn = sqlpool_find_available_connection(db, UINT_MAX, &all_disconnected); } if (conn == NULL) { /* still nothing. try creating new connections */ conn = sqlpool_add_new_connection(db); if (conn != NULL) (void)sql_connect(conn->db); if (conn == NULL || !SQL_DB_IS_READY(conn->db)) return FALSE; } *conn_r = conn; return TRUE; } static bool driver_sqlpool_get_sync_connection(struct sqlpool_db *db, const struct sqlpool_connection **conn_r) { const struct sqlpool_connection *conns; unsigned int i, count; if (driver_sqlpool_get_connection(db, UINT_MAX, conn_r)) return TRUE; /* no idling connections, but maybe we can find one that's trying to connect to server, and we can use it once it's finished */ conns = array_get(&db->all_connections, &count); for (i = 0; i < count; i++) { if (conns[i].db->state == SQL_DB_STATE_CONNECTING) { *conn_r = &conns[i]; return TRUE; } } return FALSE; } static bool driver_sqlpool_get_connected_flags(struct sqlpool_db *db, enum sql_db_flags *flags_r) { const struct sqlpool_connection *conn; array_foreach(&db->all_connections, conn) { if (conn->db->state > SQL_DB_STATE_CONNECTING) { *flags_r = sql_get_flags(conn->db); return TRUE; } } return FALSE; } static enum sql_db_flags driver_sqlpool_get_flags(struct sql_db *_db) { struct sqlpool_db *db = (struct sqlpool_db *)_db; const struct sqlpool_connection *conn; enum sql_db_flags flags; /* try to use a connected db */ if (driver_sqlpool_get_connected_flags(db, &flags)) return flags; if (!driver_sqlpool_get_sync_connection(db, &conn)) { /* Failed to connect to database. Just use the first connection. */ conn = array_idx(&db->all_connections, 0); } return sql_get_flags(conn->db); } static int driver_sqlpool_parse_hosts(struct sqlpool_db *db, const char *connect_string, const char **error_r) { const char *const *args, *key, *value, *hostname; struct sqlpool_host *host; ARRAY_TYPE(const_string) hostnames, connect_args; t_array_init(&hostnames, 8); t_array_init(&connect_args, 32); /* connect string is a space separated list. it may contain backend-specific strings which we'll pass as-is. we'll only care about our own settings, plus the host settings. */ args = t_strsplit_spaces(connect_string, " "); for (; *args != NULL; args++) { value = strchr(*args, '='); if (value == NULL) { key = *args; value = ""; } else { key = t_strdup_until(*args, value); value++; } if (strcmp(key, "maxconns") == 0) { if (str_to_uint(value, &db->connection_limit) < 0) { *error_r = t_strdup_printf("Invalid value for maxconns: %s", value); return -1; } } else if (strcmp(key, "host") == 0) { array_push_back(&hostnames, &value); } else { array_push_back(&connect_args, args); } } /* build a new connect string without our settings or hosts */ array_append_zero(&connect_args); connect_string = t_strarray_join(array_front(&connect_args), " "); if (array_count(&hostnames) == 0) { /* no hosts specified. create a default one. */ host = array_append_space(&db->hosts); host->connect_string = i_strdup(connect_string); } else { if (*connect_string == '\0') connect_string = NULL; array_foreach_elem(&hostnames, hostname) { host = array_append_space(&db->hosts); host->connect_string = i_strconcat("host=", hostname, " ", connect_string, NULL); } } if (db->connection_limit == 0) db->connection_limit = SQL_DEFAULT_CONNECTION_LIMIT; return 0; } static void sqlpool_add_all_once(struct sqlpool_db *db) { struct sqlpool_host *host; unsigned int host_idx; for (;;) { host = sqlpool_find_host_with_least_connections(db, &host_idx); if (host->connection_count > 0) break; (void)sqlpool_add_connection(db, host, host_idx); } } int driver_sqlpool_init_full(const struct sql_settings *set, const struct sql_db *driver, struct sql_db **db_r, const char **error_r) { struct sqlpool_db *db; int ret; db = i_new(struct sqlpool_db, 1); db->driver = driver; db->api = driver_sqlpool_db; db->api.flags = driver->flags; db->api.event = event_create(set->event_parent); event_add_category(db->api.event, &event_category_sqlpool); event_set_append_log_prefix(db->api.event, t_strdup_printf("sqlpool(%s): ", driver->name)); i_array_init(&db->hosts, 8); T_BEGIN { ret = driver_sqlpool_parse_hosts(db, set->connect_string, error_r); } T_END_PASS_STR_IF(ret < 0, error_r); if (ret < 0) { driver_sqlpool_deinit(&db->api); return ret; } i_array_init(&db->all_connections, 16); /* connect to all databases so we can do load balancing immediately */ sqlpool_add_all_once(db); *db_r = &db->api; return 0; } static void driver_sqlpool_abort_requests(struct sqlpool_db *db) { while (db->requests_head != NULL) { struct sqlpool_request *request = db->requests_head; sqlpool_request_abort(&request); } timeout_remove(&db->request_to); } static void driver_sqlpool_deinit(struct sql_db *_db) { struct sqlpool_db *db = (struct sqlpool_db *)_db; struct sqlpool_host *host; struct sqlpool_connection *conn; array_foreach_modifiable(&db->all_connections, conn) sql_unref(&conn->db); array_clear(&db->all_connections); driver_sqlpool_abort_requests(db); array_foreach_modifiable(&db->hosts, host) i_free(host->connect_string); i_assert(array_count(&db->all_connections) == 0); array_free(&db->hosts); array_free(&db->all_connections); array_free(&_db->module_contexts); event_unref(&_db->event); i_free(db); } static int driver_sqlpool_connect(struct sql_db *_db) { struct sqlpool_db *db = (struct sqlpool_db *)_db; const struct sqlpool_connection *conn; int ret = -1, ret2; array_foreach(&db->all_connections, conn) { ret2 = conn->db->to_reconnect != NULL ? -1 : sql_connect(conn->db); if (ret2 > 0) ret = 1; else if (ret2 == 0 && ret < 0) ret = 0; } return ret; } static void driver_sqlpool_disconnect(struct sql_db *_db) { struct sqlpool_db *db = (struct sqlpool_db *)_db; const struct sqlpool_connection *conn; array_foreach(&db->all_connections, conn) sql_disconnect(conn->db); driver_sqlpool_abort_requests(db); } static const char * driver_sqlpool_escape_string(struct sql_db *_db, const char *string) { struct sqlpool_db *db = (struct sqlpool_db *)_db; const struct sqlpool_connection *conns; unsigned int i, count; /* use the first ready connection */ conns = array_get(&db->all_connections, &count); for (i = 0; i < count; i++) { if (SQL_DB_IS_READY(conns[i].db)) return sql_escape_string(conns[i].db, string); } /* no ready connections. just use the first one (we're guaranteed to always have one) */ return sql_escape_string(conns[0].db, string); } static void driver_sqlpool_timeout(struct sqlpool_db *db) { int duration; while (db->requests_head != NULL) { struct sqlpool_request *request = db->requests_head; if (request->created + SQL_QUERY_TIMEOUT_SECS > ioloop_time) break; if (request->query != NULL) { e_error(sql_query_finished_event(&db->api, request->event, request->query, FALSE, &duration)-> add_str("error", "Query timed out")-> event(), SQL_QUERY_FINISHED_FMT": Query timed out " "(no free connections for %u secs)", request->query, duration, (unsigned int)(ioloop_time - request->created)); } else { e_error(event_create_passthrough(request->event)-> add_str("error", "Timed out")-> set_name(SQL_TRANSACTION_FINISHED)->event(), "Transaction timed out " "(no free connections for %u secs)", (unsigned int)(ioloop_time - request->created)); } sqlpool_request_abort(&request); } if (db->requests_head == NULL) timeout_remove(&db->request_to); } static void driver_sqlpool_prepend_request(struct sqlpool_db *db, struct sqlpool_request *request) { DLLIST2_PREPEND(&db->requests_head, &db->requests_tail, request); if (db->request_to == NULL) { db->request_to = timeout_add(SQL_QUERY_TIMEOUT_SECS * 1000, driver_sqlpool_timeout, db); } } static void driver_sqlpool_append_request(struct sqlpool_db *db, struct sqlpool_request *request) { DLLIST2_APPEND(&db->requests_head, &db->requests_tail, request); if (db->request_to == NULL) { db->request_to = timeout_add(SQL_QUERY_TIMEOUT_SECS * 1000, driver_sqlpool_timeout, db); } } static void driver_sqlpool_query_callback(struct sql_result *result, struct sqlpool_request *request) { struct sqlpool_db *db = request->db; const struct sqlpool_connection *conn = NULL; struct sql_db *conndb; if (result->failed_try_retry && request->retry_count < array_count(&db->hosts)) { e_warning(db->api.event, "Query failed, retrying: %s", sql_result_get_error(result)); request->retry_count++; driver_sqlpool_prepend_request(db, request); if (driver_sqlpool_get_connection(request->db, request->host_idx, &conn)) { request->host_idx = conn->host_idx; sqlpool_request_send_next(db, conn->db); } } else { if (result->failed) { e_error(db->api.event, "Query failed, aborting: %s", request->query); } conndb = result->db; if (request->callback != NULL) request->callback(result, request->context); sqlpool_request_free(&request); sqlpool_request_send_next(db, conndb); } } static void ATTR_NULL(3, 4) driver_sqlpool_query(struct sql_db *_db, const char *query, sql_query_callback_t *callback, void *context) { struct sqlpool_db *db = (struct sqlpool_db *)_db; struct sqlpool_request *request; const struct sqlpool_connection *conn; request = sqlpool_request_new(db, query); request->callback = callback; request->context = context; if (!driver_sqlpool_get_connection(db, UINT_MAX, &conn)) driver_sqlpool_append_request(db, request); else { request->host_idx = conn->host_idx; sql_query(conn->db, query, driver_sqlpool_query_callback, request); } } static void driver_sqlpool_exec(struct sql_db *_db, const char *query) { driver_sqlpool_query(_db, query, NULL, NULL); } static struct sql_result * driver_sqlpool_query_s(struct sql_db *_db, const char *query) { struct sqlpool_db *db = (struct sqlpool_db *)_db; const struct sqlpool_connection *conn; struct sql_result *result; if (!driver_sqlpool_get_sync_connection(db, &conn)) { sql_not_connected_result.refcount++; return &sql_not_connected_result; } result = sql_query_s(conn->db, query); if (result->failed_try_retry) { if (!driver_sqlpool_get_sync_connection(db, &conn)) return result; sql_result_unref(result); result = sql_query_s(conn->db, query); } return result; } static struct sql_transaction_context * driver_sqlpool_transaction_begin(struct sql_db *_db) { struct sqlpool_transaction_context *ctx; ctx = i_new(struct sqlpool_transaction_context, 1); ctx->ctx.db = _db; /* queue changes until commit. even if we did have a free connection now, don't use it or multiple open transactions could tie up all connections. */ ctx->query_pool = pool_alloconly_create("sqlpool transaction", 1024); return &ctx->ctx; } static void driver_sqlpool_transaction_free(struct sqlpool_transaction_context *ctx) { if (ctx->commit_request != NULL) sqlpool_request_abort(&ctx->commit_request); pool_unref(&ctx->query_pool); i_free(ctx); } static void driver_sqlpool_commit_callback(const struct sql_commit_result *result, struct sqlpool_transaction_context *ctx) { ctx->callback(result, ctx->context); driver_sqlpool_transaction_free(ctx); } static void driver_sqlpool_transaction_commit(struct sql_transaction_context *_ctx, sql_commit_callback_t *callback, void *context) { struct sqlpool_transaction_context *ctx = (struct sqlpool_transaction_context *)_ctx; struct sqlpool_db *db = (struct sqlpool_db *)_ctx->db; const struct sqlpool_connection *conn; ctx->callback = callback; ctx->context = context; ctx->commit_request = sqlpool_request_new(db, NULL); ctx->commit_request->trans = ctx; if (driver_sqlpool_get_connection(db, UINT_MAX, &conn)) sqlpool_request_handle_transaction(conn->db, ctx); else driver_sqlpool_append_request(db, ctx->commit_request); } static int driver_sqlpool_transaction_commit_s(struct sql_transaction_context *_ctx, const char **error_r) { struct sqlpool_transaction_context *ctx = (struct sqlpool_transaction_context *)_ctx; struct sqlpool_db *db = (struct sqlpool_db *)_ctx->db; const struct sqlpool_connection *conn; struct sql_transaction_context *conn_trans; int ret; *error_r = NULL; if (!driver_sqlpool_get_sync_connection(db, &conn)) { *error_r = SQL_ERRSTR_NOT_CONNECTED; driver_sqlpool_transaction_free(ctx); return -1; } conn_trans = driver_sqlpool_new_conn_trans(ctx, conn->db); ret = sql_transaction_commit_s(&conn_trans, error_r); driver_sqlpool_transaction_free(ctx); return ret; } static void driver_sqlpool_transaction_rollback(struct sql_transaction_context *_ctx) { struct sqlpool_transaction_context *ctx = (struct sqlpool_transaction_context *)_ctx; driver_sqlpool_transaction_free(ctx); } static void driver_sqlpool_update(struct sql_transaction_context *_ctx, const char *query, unsigned int *affected_rows) { struct sqlpool_transaction_context *ctx = (struct sqlpool_transaction_context *)_ctx; /* we didn't get a connection for transaction immediately. queue updates until commit transfers all of these */ sql_transaction_add_query(&ctx->ctx, ctx->query_pool, query, affected_rows); } static const char * driver_sqlpool_escape_blob(struct sql_db *_db, const unsigned char *data, size_t size) { struct sqlpool_db *db = (struct sqlpool_db *)_db; const struct sqlpool_connection *conns; unsigned int i, count; /* use the first ready connection */ conns = array_get(&db->all_connections, &count); for (i = 0; i < count; i++) { if (SQL_DB_IS_READY(conns[i].db)) return sql_escape_blob(conns[i].db, data, size); } /* no ready connections. just use the first one (we're guaranteed to always have one) */ return sql_escape_blob(conns[0].db, data, size); } static void driver_sqlpool_wait(struct sql_db *_db) { struct sqlpool_db *db = (struct sqlpool_db *)_db; const struct sqlpool_connection *conn; array_foreach(&db->all_connections, conn) sql_wait(conn->db); } struct sql_db driver_sqlpool_db = { "", .v = { .get_flags = driver_sqlpool_get_flags, .deinit = driver_sqlpool_deinit, .connect = driver_sqlpool_connect, .disconnect = driver_sqlpool_disconnect, .escape_string = driver_sqlpool_escape_string, .exec = driver_sqlpool_exec, .query = driver_sqlpool_query, .query_s = driver_sqlpool_query_s, .wait = driver_sqlpool_wait, .transaction_begin = driver_sqlpool_transaction_begin, .transaction_commit = driver_sqlpool_transaction_commit, .transaction_commit_s = driver_sqlpool_transaction_commit_s, .transaction_rollback = driver_sqlpool_transaction_rollback, .update = driver_sqlpool_update, .escape_blob = driver_sqlpool_escape_blob, } }; dovecot-2.3.21.1/src/lib-sql/Makefile.in0000644000000000000000000013546714656633611014522 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ pkglibexecdir = $(libexecdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ @BUILD_MYSQL_TRUE@@SQL_PLUGINS_TRUE@am__append_1 = mysql @BUILD_PGSQL_TRUE@@SQL_PLUGINS_TRUE@am__append_2 = pgsql @BUILD_SQLITE_TRUE@@SQL_PLUGINS_TRUE@am__append_3 = sqlite @BUILD_CASSANDRA_TRUE@@SQL_PLUGINS_TRUE@am__append_4 = cassandra subdir = src/lib-sql ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(pkginc_lib_HEADERS) $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ *) f=$$p;; \ esac; am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; am__install_max = 40 am__nobase_strip_setup = \ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` am__nobase_strip = \ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" am__nobase_list = $(am__nobase_strip_setup); \ for p in $$list; do echo "$$p $$p"; done | \ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ if (++n[$$2] == $(am__install_max)) \ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ END { for (dir in files) print dir, files[dir] }' am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' am__uninstall_files_from_dir = { \ test -z "$$files" \ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ $(am__cd) "$$dir" && rm -f $$files; }; \ } am__installdirs = "$(DESTDIR)$(pkglibdir)" \ "$(DESTDIR)$(sql_moduledir)" "$(DESTDIR)$(pkginc_libdir)" LTLIBRARIES = $(noinst_LTLIBRARIES) $(pkglib_LTLIBRARIES) \ $(sql_module_LTLIBRARIES) am_libdovecot_sql_la_OBJECTS = libdovecot_sql_la_OBJECTS = $(am_libdovecot_sql_la_OBJECTS) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = libdovecot_sql_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(libdovecot_sql_la_LDFLAGS) $(LDFLAGS) \ -o $@ am__DEPENDENCIES_1 = @SQL_PLUGINS_TRUE@libdriver_cassandra_la_DEPENDENCIES = \ @SQL_PLUGINS_TRUE@ $(am__DEPENDENCIES_1) am__libdriver_cassandra_la_SOURCES_DIST = driver-cassandra.c @SQL_PLUGINS_TRUE@am_libdriver_cassandra_la_OBJECTS = \ @SQL_PLUGINS_TRUE@ libdriver_cassandra_la-driver-cassandra.lo libdriver_cassandra_la_OBJECTS = $(am_libdriver_cassandra_la_OBJECTS) libdriver_cassandra_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(libdriver_cassandra_la_LDFLAGS) \ $(LDFLAGS) -o $@ @BUILD_CASSANDRA_TRUE@@SQL_PLUGINS_TRUE@am_libdriver_cassandra_la_rpath = \ @BUILD_CASSANDRA_TRUE@@SQL_PLUGINS_TRUE@ -rpath \ @BUILD_CASSANDRA_TRUE@@SQL_PLUGINS_TRUE@ $(sql_moduledir) @SQL_PLUGINS_TRUE@libdriver_mysql_la_DEPENDENCIES = \ @SQL_PLUGINS_TRUE@ $(am__DEPENDENCIES_1) am__libdriver_mysql_la_SOURCES_DIST = driver-mysql.c @SQL_PLUGINS_TRUE@am_libdriver_mysql_la_OBJECTS = \ @SQL_PLUGINS_TRUE@ libdriver_mysql_la-driver-mysql.lo libdriver_mysql_la_OBJECTS = $(am_libdriver_mysql_la_OBJECTS) libdriver_mysql_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(libdriver_mysql_la_LDFLAGS) \ $(LDFLAGS) -o $@ @BUILD_MYSQL_TRUE@@SQL_PLUGINS_TRUE@am_libdriver_mysql_la_rpath = \ @BUILD_MYSQL_TRUE@@SQL_PLUGINS_TRUE@ -rpath $(sql_moduledir) @SQL_PLUGINS_TRUE@libdriver_pgsql_la_DEPENDENCIES = \ @SQL_PLUGINS_TRUE@ $(am__DEPENDENCIES_1) am__libdriver_pgsql_la_SOURCES_DIST = driver-pgsql.c @SQL_PLUGINS_TRUE@am_libdriver_pgsql_la_OBJECTS = \ @SQL_PLUGINS_TRUE@ libdriver_pgsql_la-driver-pgsql.lo libdriver_pgsql_la_OBJECTS = $(am_libdriver_pgsql_la_OBJECTS) libdriver_pgsql_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(libdriver_pgsql_la_LDFLAGS) \ $(LDFLAGS) -o $@ @BUILD_PGSQL_TRUE@@SQL_PLUGINS_TRUE@am_libdriver_pgsql_la_rpath = \ @BUILD_PGSQL_TRUE@@SQL_PLUGINS_TRUE@ -rpath $(sql_moduledir) @SQL_PLUGINS_TRUE@libdriver_sqlite_la_DEPENDENCIES = \ @SQL_PLUGINS_TRUE@ $(am__DEPENDENCIES_1) am__libdriver_sqlite_la_SOURCES_DIST = driver-sqlite.c @SQL_PLUGINS_TRUE@am_libdriver_sqlite_la_OBJECTS = \ @SQL_PLUGINS_TRUE@ libdriver_sqlite_la-driver-sqlite.lo libdriver_sqlite_la_OBJECTS = $(am_libdriver_sqlite_la_OBJECTS) libdriver_sqlite_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(libdriver_sqlite_la_LDFLAGS) \ $(LDFLAGS) -o $@ @BUILD_SQLITE_TRUE@@SQL_PLUGINS_TRUE@am_libdriver_sqlite_la_rpath = \ @BUILD_SQLITE_TRUE@@SQL_PLUGINS_TRUE@ -rpath $(sql_moduledir) libdriver_test_la_LIBADD = am_libdriver_test_la_OBJECTS = libdriver_test_la-driver-test.lo libdriver_test_la_OBJECTS = $(am_libdriver_test_la_OBJECTS) libdriver_test_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(libdriver_test_la_LDFLAGS) $(LDFLAGS) \ -o $@ libsql_la_DEPENDENCIES = $(am__DEPENDENCIES_1) am__libsql_la_SOURCES_DIST = sql-api.c sql-db-cache.c driver-mysql.c \ driver-pgsql.c driver-sqlite.c driver-cassandra.c \ driver-sqlpool.c am__objects_1 = sql-api.lo sql-db-cache.lo @SQL_PLUGINS_FALSE@am__objects_2 = driver-mysql.lo driver-pgsql.lo \ @SQL_PLUGINS_FALSE@ driver-sqlite.lo driver-cassandra.lo am_libsql_la_OBJECTS = $(am__objects_1) $(am__objects_2) \ driver-sqlpool.lo nodist_libsql_la_OBJECTS = sql-drivers-register.lo libsql_la_OBJECTS = $(am_libsql_la_OBJECTS) \ $(nodist_libsql_la_OBJECTS) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/driver-cassandra.Plo \ ./$(DEPDIR)/driver-mysql.Plo ./$(DEPDIR)/driver-pgsql.Plo \ ./$(DEPDIR)/driver-sqlite.Plo ./$(DEPDIR)/driver-sqlpool.Plo \ ./$(DEPDIR)/libdriver_cassandra_la-driver-cassandra.Plo \ ./$(DEPDIR)/libdriver_mysql_la-driver-mysql.Plo \ ./$(DEPDIR)/libdriver_pgsql_la-driver-pgsql.Plo \ ./$(DEPDIR)/libdriver_sqlite_la-driver-sqlite.Plo \ ./$(DEPDIR)/libdriver_test_la-driver-test.Plo \ ./$(DEPDIR)/sql-api.Plo ./$(DEPDIR)/sql-db-cache.Plo \ ./$(DEPDIR)/sql-drivers-register.Plo am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(libdovecot_sql_la_SOURCES) \ $(libdriver_cassandra_la_SOURCES) \ $(libdriver_mysql_la_SOURCES) $(libdriver_pgsql_la_SOURCES) \ $(libdriver_sqlite_la_SOURCES) $(libdriver_test_la_SOURCES) \ $(libsql_la_SOURCES) $(nodist_libsql_la_SOURCES) DIST_SOURCES = $(libdovecot_sql_la_SOURCES) \ $(am__libdriver_cassandra_la_SOURCES_DIST) \ $(am__libdriver_mysql_la_SOURCES_DIST) \ $(am__libdriver_pgsql_la_SOURCES_DIST) \ $(am__libdriver_sqlite_la_SOURCES_DIST) \ $(libdriver_test_la_SOURCES) $(am__libsql_la_SOURCES_DIST) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ # automake seems to force making this unconditional.. NOPLUGIN_LDFLAGS = OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ noinst_LTLIBRARIES = libsql.la libdriver_test.la SQL_DRIVER_PLUGINS = $(am__append_1) $(am__append_2) $(am__append_3) \ $(am__append_4) @BUILD_MYSQL_TRUE@@SQL_PLUGINS_TRUE@MYSQL_LIB = libdriver_mysql.la @BUILD_PGSQL_TRUE@@SQL_PLUGINS_TRUE@PGSQL_LIB = libdriver_pgsql.la @BUILD_SQLITE_TRUE@@SQL_PLUGINS_TRUE@SQLITE_LIB = libdriver_sqlite.la @BUILD_CASSANDRA_TRUE@@SQL_PLUGINS_TRUE@CASSANDRA_LIB = libdriver_cassandra.la @SQL_PLUGINS_TRUE@sql_module_LTLIBRARIES = \ @SQL_PLUGINS_TRUE@ $(MYSQL_LIB) \ @SQL_PLUGINS_TRUE@ $(PGSQL_LIB) \ @SQL_PLUGINS_TRUE@ $(SQLITE_LIB) \ @SQL_PLUGINS_TRUE@ $(CASSANDRA_LIB) @SQL_PLUGINS_TRUE@sql_moduledir = $(moduledir) AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ $(SQL_CFLAGS) dist_sources = \ sql-api.c \ sql-db-cache.c @SQL_PLUGINS_FALSE@driver_sources = \ @SQL_PLUGINS_FALSE@ driver-mysql.c \ @SQL_PLUGINS_FALSE@ driver-pgsql.c \ @SQL_PLUGINS_FALSE@ driver-sqlite.c \ @SQL_PLUGINS_FALSE@ driver-cassandra.c libsql_la_SOURCES = \ $(dist_sources) \ $(driver_sources) \ driver-sqlpool.c libsql_la_LIBADD = $(SQL_LIBS) nodist_libsql_la_SOURCES = sql-drivers-register.c deplibs = \ ../lib-dovecot/libdovecot.la @SQL_PLUGINS_TRUE@libdriver_mysql_la_LDFLAGS = -module -avoid-version @SQL_PLUGINS_TRUE@libdriver_mysql_la_LIBADD = $(MYSQL_LIBS) @SQL_PLUGINS_TRUE@libdriver_mysql_la_CPPFLAGS = $(AM_CPPFLAGS) $(MYSQL_CFLAGS) @SQL_PLUGINS_TRUE@libdriver_mysql_la_SOURCES = driver-mysql.c @SQL_PLUGINS_TRUE@libdriver_pgsql_la_LDFLAGS = -module -avoid-version @SQL_PLUGINS_TRUE@libdriver_pgsql_la_LIBADD = $(PGSQL_LIBS) @SQL_PLUGINS_TRUE@libdriver_pgsql_la_CPPFLAGS = $(AM_CPPFLAGS) $(PGSQL_CFLAGS) @SQL_PLUGINS_TRUE@libdriver_pgsql_la_SOURCES = driver-pgsql.c @SQL_PLUGINS_TRUE@libdriver_sqlite_la_LDFLAGS = -module -avoid-version @SQL_PLUGINS_TRUE@libdriver_sqlite_la_LIBADD = $(SQLITE_LIBS) @SQL_PLUGINS_TRUE@libdriver_sqlite_la_CPPFLAGS = $(AM_CPPFLAGS) $(SQLITE_CFLAGS) @SQL_PLUGINS_TRUE@libdriver_sqlite_la_SOURCES = driver-sqlite.c @SQL_PLUGINS_TRUE@libdriver_cassandra_la_LDFLAGS = -module -avoid-version @SQL_PLUGINS_TRUE@libdriver_cassandra_la_LIBADD = $(CASSANDRA_LIBS) @SQL_PLUGINS_TRUE@libdriver_cassandra_la_CPPFLAGS = $(AM_CPPFLAGS) $(CASSANDRA_CFLAGS) @SQL_PLUGINS_TRUE@libdriver_cassandra_la_SOURCES = driver-cassandra.c libdriver_test_la_LDFLAGS = -avoid-version libdriver_test_la_CPPFLAGS = $(AM_CPPFLAGS) \ -I$(top_srcdir)/src/lib-test libdriver_test_la_SOURCES = driver-test.c noinst_HEADERS = driver-test.h pkglib_LTLIBRARIES = libdovecot-sql.la libdovecot_sql_la_SOURCES = libdovecot_sql_la_LIBADD = libsql.la $(deplibs) libdovecot_sql_la_DEPENDENCIES = libsql.la libdovecot_sql_la_LDFLAGS = -export-dynamic headers = \ sql-api.h \ sql-api-private.h \ sql-db-cache.h pkginc_libdir = $(pkgincludedir) pkginc_lib_HEADERS = $(headers) all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sql/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/lib-sql/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): clean-noinstLTLIBRARIES: -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) @list='$(noinst_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } install-pkglibLTLIBRARIES: $(pkglib_LTLIBRARIES) @$(NORMAL_INSTALL) @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ list2=; for p in $$list; do \ if test -f $$p; then \ list2="$$list2 $$p"; \ else :; fi; \ done; \ test -z "$$list2" || { \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibdir)" || exit 1; \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(pkglibdir)'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(pkglibdir)"; \ } uninstall-pkglibLTLIBRARIES: @$(NORMAL_UNINSTALL) @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ for p in $$list; do \ $(am__strip_dir) \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(pkglibdir)/$$f'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(pkglibdir)/$$f"; \ done clean-pkglibLTLIBRARIES: -test -z "$(pkglib_LTLIBRARIES)" || rm -f $(pkglib_LTLIBRARIES) @list='$(pkglib_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } install-sql_moduleLTLIBRARIES: $(sql_module_LTLIBRARIES) @$(NORMAL_INSTALL) @list='$(sql_module_LTLIBRARIES)'; test -n "$(sql_moduledir)" || list=; \ list2=; for p in $$list; do \ if test -f $$p; then \ list2="$$list2 $$p"; \ else :; fi; \ done; \ test -z "$$list2" || { \ echo " $(MKDIR_P) '$(DESTDIR)$(sql_moduledir)'"; \ $(MKDIR_P) "$(DESTDIR)$(sql_moduledir)" || exit 1; \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(sql_moduledir)'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(sql_moduledir)"; \ } uninstall-sql_moduleLTLIBRARIES: @$(NORMAL_UNINSTALL) @list='$(sql_module_LTLIBRARIES)'; test -n "$(sql_moduledir)" || list=; \ for p in $$list; do \ $(am__strip_dir) \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(sql_moduledir)/$$f'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(sql_moduledir)/$$f"; \ done clean-sql_moduleLTLIBRARIES: -test -z "$(sql_module_LTLIBRARIES)" || rm -f $(sql_module_LTLIBRARIES) @list='$(sql_module_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } libdovecot-sql.la: $(libdovecot_sql_la_OBJECTS) $(libdovecot_sql_la_DEPENDENCIES) $(EXTRA_libdovecot_sql_la_DEPENDENCIES) $(AM_V_CCLD)$(libdovecot_sql_la_LINK) -rpath $(pkglibdir) $(libdovecot_sql_la_OBJECTS) $(libdovecot_sql_la_LIBADD) $(LIBS) libdriver_cassandra.la: $(libdriver_cassandra_la_OBJECTS) $(libdriver_cassandra_la_DEPENDENCIES) $(EXTRA_libdriver_cassandra_la_DEPENDENCIES) $(AM_V_CCLD)$(libdriver_cassandra_la_LINK) $(am_libdriver_cassandra_la_rpath) $(libdriver_cassandra_la_OBJECTS) $(libdriver_cassandra_la_LIBADD) $(LIBS) libdriver_mysql.la: $(libdriver_mysql_la_OBJECTS) $(libdriver_mysql_la_DEPENDENCIES) $(EXTRA_libdriver_mysql_la_DEPENDENCIES) $(AM_V_CCLD)$(libdriver_mysql_la_LINK) $(am_libdriver_mysql_la_rpath) $(libdriver_mysql_la_OBJECTS) $(libdriver_mysql_la_LIBADD) $(LIBS) libdriver_pgsql.la: $(libdriver_pgsql_la_OBJECTS) $(libdriver_pgsql_la_DEPENDENCIES) $(EXTRA_libdriver_pgsql_la_DEPENDENCIES) $(AM_V_CCLD)$(libdriver_pgsql_la_LINK) $(am_libdriver_pgsql_la_rpath) $(libdriver_pgsql_la_OBJECTS) $(libdriver_pgsql_la_LIBADD) $(LIBS) libdriver_sqlite.la: $(libdriver_sqlite_la_OBJECTS) $(libdriver_sqlite_la_DEPENDENCIES) $(EXTRA_libdriver_sqlite_la_DEPENDENCIES) $(AM_V_CCLD)$(libdriver_sqlite_la_LINK) $(am_libdriver_sqlite_la_rpath) $(libdriver_sqlite_la_OBJECTS) $(libdriver_sqlite_la_LIBADD) $(LIBS) libdriver_test.la: $(libdriver_test_la_OBJECTS) $(libdriver_test_la_DEPENDENCIES) $(EXTRA_libdriver_test_la_DEPENDENCIES) $(AM_V_CCLD)$(libdriver_test_la_LINK) $(libdriver_test_la_OBJECTS) $(libdriver_test_la_LIBADD) $(LIBS) libsql.la: $(libsql_la_OBJECTS) $(libsql_la_DEPENDENCIES) $(EXTRA_libsql_la_DEPENDENCIES) $(AM_V_CCLD)$(LINK) $(libsql_la_OBJECTS) $(libsql_la_LIBADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/driver-cassandra.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/driver-mysql.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/driver-pgsql.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/driver-sqlite.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/driver-sqlpool.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdriver_cassandra_la-driver-cassandra.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdriver_mysql_la-driver-mysql.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdriver_pgsql_la-driver-pgsql.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdriver_sqlite_la-driver-sqlite.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdriver_test_la-driver-test.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sql-api.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sql-db-cache.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sql-drivers-register.Plo@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< libdriver_cassandra_la-driver-cassandra.lo: driver-cassandra.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdriver_cassandra_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdriver_cassandra_la-driver-cassandra.lo -MD -MP -MF $(DEPDIR)/libdriver_cassandra_la-driver-cassandra.Tpo -c -o libdriver_cassandra_la-driver-cassandra.lo `test -f 'driver-cassandra.c' || echo '$(srcdir)/'`driver-cassandra.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdriver_cassandra_la-driver-cassandra.Tpo $(DEPDIR)/libdriver_cassandra_la-driver-cassandra.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='driver-cassandra.c' object='libdriver_cassandra_la-driver-cassandra.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdriver_cassandra_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdriver_cassandra_la-driver-cassandra.lo `test -f 'driver-cassandra.c' || echo '$(srcdir)/'`driver-cassandra.c libdriver_mysql_la-driver-mysql.lo: driver-mysql.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdriver_mysql_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdriver_mysql_la-driver-mysql.lo -MD -MP -MF $(DEPDIR)/libdriver_mysql_la-driver-mysql.Tpo -c -o libdriver_mysql_la-driver-mysql.lo `test -f 'driver-mysql.c' || echo '$(srcdir)/'`driver-mysql.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdriver_mysql_la-driver-mysql.Tpo $(DEPDIR)/libdriver_mysql_la-driver-mysql.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='driver-mysql.c' object='libdriver_mysql_la-driver-mysql.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdriver_mysql_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdriver_mysql_la-driver-mysql.lo `test -f 'driver-mysql.c' || echo '$(srcdir)/'`driver-mysql.c libdriver_pgsql_la-driver-pgsql.lo: driver-pgsql.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdriver_pgsql_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdriver_pgsql_la-driver-pgsql.lo -MD -MP -MF $(DEPDIR)/libdriver_pgsql_la-driver-pgsql.Tpo -c -o libdriver_pgsql_la-driver-pgsql.lo `test -f 'driver-pgsql.c' || echo '$(srcdir)/'`driver-pgsql.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdriver_pgsql_la-driver-pgsql.Tpo $(DEPDIR)/libdriver_pgsql_la-driver-pgsql.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='driver-pgsql.c' object='libdriver_pgsql_la-driver-pgsql.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdriver_pgsql_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdriver_pgsql_la-driver-pgsql.lo `test -f 'driver-pgsql.c' || echo '$(srcdir)/'`driver-pgsql.c libdriver_sqlite_la-driver-sqlite.lo: driver-sqlite.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdriver_sqlite_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdriver_sqlite_la-driver-sqlite.lo -MD -MP -MF $(DEPDIR)/libdriver_sqlite_la-driver-sqlite.Tpo -c -o libdriver_sqlite_la-driver-sqlite.lo `test -f 'driver-sqlite.c' || echo '$(srcdir)/'`driver-sqlite.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdriver_sqlite_la-driver-sqlite.Tpo $(DEPDIR)/libdriver_sqlite_la-driver-sqlite.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='driver-sqlite.c' object='libdriver_sqlite_la-driver-sqlite.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdriver_sqlite_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdriver_sqlite_la-driver-sqlite.lo `test -f 'driver-sqlite.c' || echo '$(srcdir)/'`driver-sqlite.c libdriver_test_la-driver-test.lo: driver-test.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdriver_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdriver_test_la-driver-test.lo -MD -MP -MF $(DEPDIR)/libdriver_test_la-driver-test.Tpo -c -o libdriver_test_la-driver-test.lo `test -f 'driver-test.c' || echo '$(srcdir)/'`driver-test.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdriver_test_la-driver-test.Tpo $(DEPDIR)/libdriver_test_la-driver-test.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='driver-test.c' object='libdriver_test_la-driver-test.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdriver_test_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdriver_test_la-driver-test.lo `test -f 'driver-test.c' || echo '$(srcdir)/'`driver-test.c mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) @$(NORMAL_INSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ done uninstall-pkginc_libHEADERS: @$(NORMAL_UNINSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am check: check-am all-am: Makefile $(LTLIBRARIES) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkglibdir)" "$(DESTDIR)$(sql_moduledir)" "$(DESTDIR)$(pkginc_libdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." @SQL_PLUGINS_FALSE@install-exec-local: clean: clean-am clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ clean-pkglibLTLIBRARIES clean-sql_moduleLTLIBRARIES \ mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/driver-cassandra.Plo -rm -f ./$(DEPDIR)/driver-mysql.Plo -rm -f ./$(DEPDIR)/driver-pgsql.Plo -rm -f ./$(DEPDIR)/driver-sqlite.Plo -rm -f ./$(DEPDIR)/driver-sqlpool.Plo -rm -f ./$(DEPDIR)/libdriver_cassandra_la-driver-cassandra.Plo -rm -f ./$(DEPDIR)/libdriver_mysql_la-driver-mysql.Plo -rm -f ./$(DEPDIR)/libdriver_pgsql_la-driver-pgsql.Plo -rm -f ./$(DEPDIR)/libdriver_sqlite_la-driver-sqlite.Plo -rm -f ./$(DEPDIR)/libdriver_test_la-driver-test.Plo -rm -f ./$(DEPDIR)/sql-api.Plo -rm -f ./$(DEPDIR)/sql-db-cache.Plo -rm -f ./$(DEPDIR)/sql-drivers-register.Plo -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-pkginc_libHEADERS \ install-sql_moduleLTLIBRARIES install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-exec-local install-pkglibLTLIBRARIES install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/driver-cassandra.Plo -rm -f ./$(DEPDIR)/driver-mysql.Plo -rm -f ./$(DEPDIR)/driver-pgsql.Plo -rm -f ./$(DEPDIR)/driver-sqlite.Plo -rm -f ./$(DEPDIR)/driver-sqlpool.Plo -rm -f ./$(DEPDIR)/libdriver_cassandra_la-driver-cassandra.Plo -rm -f ./$(DEPDIR)/libdriver_mysql_la-driver-mysql.Plo -rm -f ./$(DEPDIR)/libdriver_pgsql_la-driver-pgsql.Plo -rm -f ./$(DEPDIR)/libdriver_sqlite_la-driver-sqlite.Plo -rm -f ./$(DEPDIR)/libdriver_test_la-driver-test.Plo -rm -f ./$(DEPDIR)/sql-api.Plo -rm -f ./$(DEPDIR)/sql-db-cache.Plo -rm -f ./$(DEPDIR)/sql-drivers-register.Plo -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkginc_libHEADERS uninstall-pkglibLTLIBRARIES \ uninstall-sql_moduleLTLIBRARIES .MAKE: install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ clean-generic clean-libtool clean-noinstLTLIBRARIES \ clean-pkglibLTLIBRARIES clean-sql_moduleLTLIBRARIES \ cscopelist-am ctags ctags-am distclean distclean-compile \ distclean-generic distclean-libtool distclean-tags distdir dvi \ dvi-am html html-am info info-am install install-am \ install-data install-data-am install-dvi install-dvi-am \ install-exec install-exec-am install-exec-local install-html \ install-html-am install-info install-info-am install-man \ install-pdf install-pdf-am install-pkginc_libHEADERS \ install-pkglibLTLIBRARIES install-ps install-ps-am \ install-sql_moduleLTLIBRARIES install-strip installcheck \ installcheck-am installdirs maintainer-clean \ maintainer-clean-generic mostlyclean mostlyclean-compile \ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ tags tags-am uninstall uninstall-am \ uninstall-pkginc_libHEADERS uninstall-pkglibLTLIBRARIES \ uninstall-sql_moduleLTLIBRARIES .PRECIOUS: Makefile sql-drivers-register.c: Makefile rm -f $@ echo '/* this file automatically generated by Makefile */' >$@ echo '#include "lib.h"' >>$@ echo '#include "sql-api.h"' >>$@ @SQL_PLUGINS_FALSE@ for i in $(sql_drivers) null; do \ @SQL_PLUGINS_FALSE@ if [ "$${i}" != "null" ]; then \ @SQL_PLUGINS_FALSE@ echo "extern struct sql_db driver_$${i}_db;" >>$@ ; \ @SQL_PLUGINS_FALSE@ fi; \ @SQL_PLUGINS_FALSE@ done echo 'void sql_drivers_register_all(void) {' >>$@ @SQL_PLUGINS_FALSE@ for i in $(sql_drivers) null; do \ @SQL_PLUGINS_FALSE@ if [ "$${i}" != "null" ]; then \ @SQL_PLUGINS_FALSE@ echo "sql_driver_register(&driver_$${i}_db);" >>$@ ; \ @SQL_PLUGINS_FALSE@ fi; \ @SQL_PLUGINS_FALSE@ done echo '}' >>$@ @SQL_PLUGINS_TRUE@install-exec-local: @SQL_PLUGINS_TRUE@ for d in auth dict; do \ @SQL_PLUGINS_TRUE@ $(mkdir_p) $(DESTDIR)$(moduledir)/$$d; \ @SQL_PLUGINS_TRUE@ for driver in $(SQL_DRIVER_PLUGINS); do \ @SQL_PLUGINS_TRUE@ rm -f $(DESTDIR)$(moduledir)/$$d/libdriver_$$driver.so; \ @SQL_PLUGINS_TRUE@ $(LN_S) ../libdriver_$$driver.so $(DESTDIR)$(moduledir)/$$d; \ @SQL_PLUGINS_TRUE@ done; \ @SQL_PLUGINS_TRUE@ done distclean-generic: rm -f Makefile sql-drivers-register.c # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/lib-sql/driver-test.h0000644000000000000000000000120614656633576015067 00000000000000#ifndef DRIVER_TEST_H #define DRIVER_TEST_H 1 struct test_driver_result_set { size_t rows, cols, cur; const char *const *col_names; const char ***row_data; }; struct test_driver_result { /* expected queries */ size_t nqueries; size_t cur; unsigned int affected_rows; const char *const *queries; /* test result, rows and columns */ struct test_driver_result_set *result; }; void sql_driver_test_register(void); void sql_driver_test_unregister(void); void sql_driver_test_add_expected_result(struct sql_db *_db, const struct test_driver_result *result); void sql_driver_test_clear_expected_results(struct sql_db *_db); #endif dovecot-2.3.21.1/src/lib-sql/sql-api-private.h0000644000000000000000000001766414656633576015654 00000000000000#ifndef SQL_API_PRIVATE_H #define SQL_API_PRIVATE_H #include "sql-api.h" #include "module-context.h" enum sql_db_state { /* not connected to database */ SQL_DB_STATE_DISCONNECTED, /* waiting for connection attempt to succeed or fail */ SQL_DB_STATE_CONNECTING, /* connected, allowing more queries */ SQL_DB_STATE_IDLE, /* connected, no more queries allowed */ SQL_DB_STATE_BUSY }; /* Minimum delay between reconnecting to same server */ #define SQL_CONNECT_MIN_DELAY 1 /* Maximum time to avoiding reconnecting to same server */ #define SQL_CONNECT_MAX_DELAY (60*30) /* If no servers are connected but a query is requested, try reconnecting to next server which has been disconnected longer than this (with a single server setup this is really the "max delay" and the SQL_CONNECT_MAX_DELAY is never used). */ #define SQL_CONNECT_RESET_DELAY 15 /* Abort connect() if it can't connect within this time. */ #define SQL_CONNECT_TIMEOUT_SECS 5 /* Abort queries after this many seconds */ #define SQL_QUERY_TIMEOUT_SECS 60 /* Default max. number of connections to create per host */ #define SQL_DEFAULT_CONNECTION_LIMIT 5 #define SQL_DB_IS_READY(db) \ ((db)->state == SQL_DB_STATE_IDLE) #define SQL_ERRSTR_NOT_CONNECTED "Not connected to database" /* What is considered slow query */ #define SQL_SLOW_QUERY_MSEC 1000 #define SQL_QUERY_FINISHED "sql_query_finished" #define SQL_CONNECTION_FINISHED "sql_connection_finished" #define SQL_TRANSACTION_FINISHED "sql_transaction_finished" #define SQL_QUERY_FINISHED_FMT "Finished query '%s' in %u msecs" struct sql_db_module_register { unsigned int id; }; union sql_db_module_context { struct sql_db_module_register *reg; }; extern struct sql_db_module_register sql_db_module_register; extern struct event_category event_category_sql; struct sql_transaction_query { struct sql_transaction_query *next; struct sql_transaction_context *trans; const char *query; unsigned int *affected_rows; }; struct sql_db_vfuncs { struct sql_db *(*init)(const char *connect_string); int (*init_full)(const struct sql_settings *set, struct sql_db **db_r, const char **error); void (*deinit)(struct sql_db *db); void (*unref)(struct sql_db *db); void (*wait) (struct sql_db *db); enum sql_db_flags (*get_flags)(struct sql_db *db); int (*connect)(struct sql_db *db); void (*disconnect)(struct sql_db *db); const char *(*escape_string)(struct sql_db *db, const char *string); void (*exec)(struct sql_db *db, const char *query); void (*query)(struct sql_db *db, const char *query, sql_query_callback_t *callback, void *context); struct sql_result *(*query_s)(struct sql_db *db, const char *query); struct sql_transaction_context *(*transaction_begin)(struct sql_db *db); void (*transaction_commit)(struct sql_transaction_context *ctx, sql_commit_callback_t *callback, void *context); int (*transaction_commit_s)(struct sql_transaction_context *ctx, const char **error_r); void (*transaction_rollback)(struct sql_transaction_context *ctx); void (*update)(struct sql_transaction_context *ctx, const char *query, unsigned int *affected_rows); const char *(*escape_blob)(struct sql_db *db, const unsigned char *data, size_t size); struct sql_prepared_statement * (*prepared_statement_init)(struct sql_db *db, const char *query_template); void (*prepared_statement_deinit)(struct sql_prepared_statement *prep_stmt); struct sql_statement * (*statement_init)(struct sql_db *db, const char *query_template); struct sql_statement * (*statement_init_prepared)(struct sql_prepared_statement *prep_stmt); void (*statement_abort)(struct sql_statement *stmt); void (*statement_set_timestamp)(struct sql_statement *stmt, const struct timespec *ts); void (*statement_bind_str)(struct sql_statement *stmt, unsigned int column_idx, const char *value); void (*statement_bind_binary)(struct sql_statement *stmt, unsigned int column_idx, const void *value, size_t value_size); void (*statement_bind_int64)(struct sql_statement *stmt, unsigned int column_idx, int64_t value); void (*statement_query)(struct sql_statement *stmt, sql_query_callback_t *callback, void *context); struct sql_result *(*statement_query_s)(struct sql_statement *stmt); void (*update_stmt)(struct sql_transaction_context *ctx, struct sql_statement *stmt, unsigned int *affected_rows); }; struct sql_db { const char *name; enum sql_db_flags flags; int refcount; struct sql_db_vfuncs v; ARRAY(union sql_db_module_context *) module_contexts; void (*state_change_callback)(struct sql_db *db, enum sql_db_state prev_state, void *context); void *state_change_context; struct event *event; HASH_TABLE(char *, struct sql_prepared_statement *) prepared_stmt_hash; enum sql_db_state state; /* last time we started connecting to this server (which may or may not have succeeded) */ time_t last_connect_try; unsigned int connect_delay; unsigned int connect_failure_count; struct timeout *to_reconnect; uint64_t succeeded_queries; uint64_t failed_queries; /* includes both succeeded and failed */ uint64_t slow_queries; bool no_reconnect:1; }; struct sql_result_vfuncs { void (*free)(struct sql_result *result); int (*next_row)(struct sql_result *result); unsigned int (*get_fields_count)(struct sql_result *result); const char *(*get_field_name)(struct sql_result *result, unsigned int idx); int (*find_field)(struct sql_result *result, const char *field_name); const char *(*get_field_value)(struct sql_result *result, unsigned int idx); const unsigned char * (*get_field_value_binary)(struct sql_result *result, unsigned int idx, size_t *size_r); const char *(*find_field_value)(struct sql_result *result, const char *field_name); const char *const *(*get_values)(struct sql_result *result); const char *(*get_error)(struct sql_result *result); void (*more)(struct sql_result **result, bool async, sql_query_callback_t *callback, void *context); }; struct sql_prepared_statement { struct sql_db *db; int refcount; char *query_template; }; struct sql_statement { struct sql_db *db; pool_t pool; const char *query_template; ARRAY_TYPE(const_string) args; /* Tell the driver to not log this query with expanded values. */ bool no_log_expanded_values; }; struct sql_field_map { enum sql_field_type type; size_t offset; }; struct sql_result { struct sql_result_vfuncs v; int refcount; struct sql_db *db; const struct sql_field_def *fields; unsigned int map_size; struct sql_field_map *map; void *fetch_dest; struct event *event; size_t fetch_dest_size; enum sql_result_error_type error_type; bool failed:1; bool failed_try_retry:1; bool callback:1; }; struct sql_transaction_context { struct sql_db *db; struct event *event; /* commit() must use this query list if head is non-NULL. */ struct sql_transaction_query *head, *tail; }; ARRAY_DEFINE_TYPE(sql_drivers, const struct sql_db *); extern ARRAY_TYPE(sql_drivers) sql_drivers; extern struct sql_result sql_not_connected_result; void sql_init_common(struct sql_db *db); struct sql_db * driver_sqlpool_init(const char *connect_string, const struct sql_db *driver); int driver_sqlpool_init_full(const struct sql_settings *set, const struct sql_db *driver, struct sql_db **db_r, const char **error_r); void sql_db_set_state(struct sql_db *db, enum sql_db_state state); void sql_transaction_add_query(struct sql_transaction_context *ctx, pool_t pool, const char *query, unsigned int *affected_rows); const char *sql_statement_get_log_query(struct sql_statement *stmt); const char *sql_statement_get_query(struct sql_statement *stmt); void sql_connection_log_finished(struct sql_db *db); struct event_passthrough * sql_query_finished_event(struct sql_db *db, struct event *event, const char *query, bool success, int *duration_r); struct event_passthrough *sql_transaction_finished_event(struct sql_transaction_context *ctx); #endif dovecot-2.3.21.1/src/lib-sql/Makefile.am0000644000000000000000000000660714656633576014514 00000000000000noinst_LTLIBRARIES = libsql.la libdriver_test.la SQL_DRIVER_PLUGINS = # automake seems to force making this unconditional.. NOPLUGIN_LDFLAGS = if SQL_PLUGINS if BUILD_MYSQL MYSQL_LIB = libdriver_mysql.la SQL_DRIVER_PLUGINS += mysql endif if BUILD_PGSQL PGSQL_LIB = libdriver_pgsql.la SQL_DRIVER_PLUGINS += pgsql endif if BUILD_SQLITE SQLITE_LIB = libdriver_sqlite.la SQL_DRIVER_PLUGINS += sqlite endif if BUILD_CASSANDRA CASSANDRA_LIB = libdriver_cassandra.la SQL_DRIVER_PLUGINS += cassandra endif sql_module_LTLIBRARIES = \ $(MYSQL_LIB) \ $(PGSQL_LIB) \ $(SQLITE_LIB) \ $(CASSANDRA_LIB) sql_moduledir = $(moduledir) endif sql_drivers = @sql_drivers@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ $(SQL_CFLAGS) dist_sources = \ sql-api.c \ sql-db-cache.c if ! SQL_PLUGINS driver_sources = \ driver-mysql.c \ driver-pgsql.c \ driver-sqlite.c \ driver-cassandra.c endif libsql_la_SOURCES = \ $(dist_sources) \ $(driver_sources) \ driver-sqlpool.c libsql_la_LIBADD = $(SQL_LIBS) nodist_libsql_la_SOURCES = sql-drivers-register.c deplibs = \ ../lib-dovecot/libdovecot.la if SQL_PLUGINS libdriver_mysql_la_LDFLAGS = -module -avoid-version libdriver_mysql_la_LIBADD = $(MYSQL_LIBS) libdriver_mysql_la_CPPFLAGS = $(AM_CPPFLAGS) $(MYSQL_CFLAGS) libdriver_mysql_la_SOURCES = driver-mysql.c libdriver_pgsql_la_LDFLAGS = -module -avoid-version libdriver_pgsql_la_LIBADD = $(PGSQL_LIBS) libdriver_pgsql_la_CPPFLAGS = $(AM_CPPFLAGS) $(PGSQL_CFLAGS) libdriver_pgsql_la_SOURCES = driver-pgsql.c libdriver_sqlite_la_LDFLAGS = -module -avoid-version libdriver_sqlite_la_LIBADD = $(SQLITE_LIBS) libdriver_sqlite_la_CPPFLAGS = $(AM_CPPFLAGS) $(SQLITE_CFLAGS) libdriver_sqlite_la_SOURCES = driver-sqlite.c libdriver_cassandra_la_LDFLAGS = -module -avoid-version libdriver_cassandra_la_LIBADD = $(CASSANDRA_LIBS) libdriver_cassandra_la_CPPFLAGS = $(AM_CPPFLAGS) $(CASSANDRA_CFLAGS) libdriver_cassandra_la_SOURCES = driver-cassandra.c else endif libdriver_test_la_LDFLAGS = -avoid-version libdriver_test_la_CPPFLAGS = $(AM_CPPFLAGS) \ -I$(top_srcdir)/src/lib-test libdriver_test_la_SOURCES = driver-test.c noinst_HEADERS = driver-test.h pkglib_LTLIBRARIES = libdovecot-sql.la libdovecot_sql_la_SOURCES = libdovecot_sql_la_LIBADD = libsql.la $(deplibs) libdovecot_sql_la_DEPENDENCIES = libsql.la libdovecot_sql_la_LDFLAGS = -export-dynamic headers = \ sql-api.h \ sql-api-private.h \ sql-db-cache.h pkginc_libdir=$(pkgincludedir) pkginc_lib_HEADERS = $(headers) sql-drivers-register.c: Makefile rm -f $@ echo '/* this file automatically generated by Makefile */' >$@ echo '#include "lib.h"' >>$@ echo '#include "sql-api.h"' >>$@ if ! SQL_PLUGINS for i in $(sql_drivers) null; do \ if [ "$${i}" != "null" ]; then \ echo "extern struct sql_db driver_$${i}_db;" >>$@ ; \ fi; \ done endif echo 'void sql_drivers_register_all(void) {' >>$@ if ! SQL_PLUGINS for i in $(sql_drivers) null; do \ if [ "$${i}" != "null" ]; then \ echo "sql_driver_register(&driver_$${i}_db);" >>$@ ; \ fi; \ done endif echo '}' >>$@ if SQL_PLUGINS install-exec-local: for d in auth dict; do \ $(mkdir_p) $(DESTDIR)$(moduledir)/$$d; \ for driver in $(SQL_DRIVER_PLUGINS); do \ rm -f $(DESTDIR)$(moduledir)/$$d/libdriver_$$driver.so; \ $(LN_S) ../libdriver_$$driver.so $(DESTDIR)$(moduledir)/$$d; \ done; \ done endif distclean-generic: rm -f Makefile sql-drivers-register.c dovecot-2.3.21.1/src/lib-sql/sql-db-cache.c0000644000000000000000000000717214656633576015045 00000000000000/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "hash.h" #include "sql-api-private.h" #include "sql-db-cache.h" #define SQL_DB_CACHE_CONTEXT(obj) \ MODULE_CONTEXT_REQUIRE(obj, sql_db_cache_module) struct sql_db_cache_context { union sql_db_module_context module_ctx; struct sql_db *prev, *next; /* These are set while refcount=0 */ struct sql_db_cache *cache; int refcount; char *key; void (*orig_deinit)(struct sql_db *db); }; struct sql_db_cache { HASH_TABLE(char *, struct sql_db *) dbs; unsigned int unused_count, max_unused_connections; struct sql_db *unused_tail, *unused_head; }; static MODULE_CONTEXT_DEFINE_INIT(sql_db_cache_module, &sql_db_module_register); static void sql_db_cache_db_unref(struct sql_db *db) { struct sql_db_cache_context *ctx = SQL_DB_CACHE_CONTEXT(db); struct sql_db_cache_context *head_ctx; if (--ctx->refcount > 0) return; i_assert(db->refcount == 2); ctx->cache->unused_count++; if (ctx->cache->unused_tail == NULL) ctx->cache->unused_tail = db; else { head_ctx = SQL_DB_CACHE_CONTEXT(ctx->cache->unused_head); head_ctx->next = db; } ctx->prev = ctx->cache->unused_head; ctx->cache->unused_head = db; } static void sql_db_cache_unlink(struct sql_db_cache_context *ctx) { struct sql_db_cache_context *prev_ctx, *next_ctx; i_assert(ctx->refcount == 0); if (ctx->prev == NULL) ctx->cache->unused_tail = ctx->next; else { prev_ctx = SQL_DB_CACHE_CONTEXT(ctx->prev); prev_ctx->next = ctx->next; } if (ctx->next == NULL) ctx->cache->unused_head = ctx->prev; else { next_ctx = SQL_DB_CACHE_CONTEXT(ctx->next); next_ctx->prev = ctx->prev; } ctx->cache->unused_count--; } static void sql_db_cache_free_tail(struct sql_db_cache *cache) { struct sql_db *db; struct sql_db_cache_context *ctx; db = cache->unused_tail; i_assert(db->refcount == 1); ctx = SQL_DB_CACHE_CONTEXT(db); sql_db_cache_unlink(ctx); hash_table_remove(cache->dbs, ctx->key); i_free(ctx->key); i_free(ctx); db->v.unref = NULL; sql_unref(&db); } static void sql_db_cache_drop_oldest(struct sql_db_cache *cache) { while (cache->unused_count >= cache->max_unused_connections) sql_db_cache_free_tail(cache); } int sql_db_cache_new(struct sql_db_cache *cache, const struct sql_settings *set, struct sql_db **db_r, const char **error_r) { struct sql_db_cache_context *ctx; struct sql_db *db; char *key; key = i_strdup_printf("%s\t%s", set->driver, set->connect_string); db = hash_table_lookup(cache->dbs, key); if (db != NULL) { ctx = SQL_DB_CACHE_CONTEXT(db); if (ctx->refcount == 0) { sql_db_cache_unlink(ctx); ctx->prev = ctx->next = NULL; } i_free(key); } else { sql_db_cache_drop_oldest(cache); if (sql_init_full(set, &db, error_r) < 0) { i_free(key); return -1; } ctx = i_new(struct sql_db_cache_context, 1); ctx->cache = cache; ctx->key = key; ctx->orig_deinit = db->v.deinit; db->v.unref = sql_db_cache_db_unref; MODULE_CONTEXT_SET(db, sql_db_cache_module, ctx); hash_table_insert(cache->dbs, ctx->key, db); } ctx->refcount++; sql_ref(db); *db_r = db; return 0; } struct sql_db_cache *sql_db_cache_init(unsigned int max_unused_connections) { struct sql_db_cache *cache; cache = i_new(struct sql_db_cache, 1); hash_table_create(&cache->dbs, default_pool, 0, str_hash, strcmp); cache->max_unused_connections = max_unused_connections; return cache; } void sql_db_cache_deinit(struct sql_db_cache **_cache) { struct sql_db_cache *cache = *_cache; *_cache = NULL; while (cache->unused_tail != NULL) sql_db_cache_free_tail(cache); hash_table_destroy(&cache->dbs); i_free(cache); } dovecot-2.3.21.1/src/lib-sql/driver-mysql.c0000644000000000000000000005425614656633576015265 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "array.h" #include "hex-binary.h" #include "str.h" #include "net.h" #include "time-util.h" #include "sql-api-private.h" #ifdef BUILD_MYSQL #include #include #ifdef HAVE_ATTR_NULL /* ugly way to tell clang that mysql.h is a system header and we don't want to enable nonnull attributes for it by default.. */ # 4 "driver-mysql.c" 3 #endif #include #ifdef HAVE_ATTR_NULL # 4 "driver-mysql.c" 3 # line 20 #endif #include #define MYSQL_DEFAULT_READ_TIMEOUT_SECS 30 #define MYSQL_DEFAULT_WRITE_TIMEOUT_SECS 30 struct mysql_db { struct sql_db api; pool_t pool; const char *user, *password, *dbname, *host, *unix_socket; const char *ssl_cert, *ssl_key, *ssl_ca, *ssl_ca_path, *ssl_cipher; int ssl_verify_server_cert; const char *option_file, *option_group; in_port_t port; unsigned int client_flags; unsigned int connect_timeout, read_timeout, write_timeout; time_t last_success; MYSQL *mysql; unsigned int next_query_connection; bool ssl_set:1; }; struct mysql_result { struct sql_result api; MYSQL_RES *result; MYSQL_ROW row; MYSQL_FIELD *fields; unsigned int fields_count; my_ulonglong affected_rows; }; struct mysql_transaction_context { struct sql_transaction_context ctx; pool_t query_pool; const char *error; bool failed:1; bool committed:1; bool commit_started:1; }; extern const struct sql_db driver_mysql_db; extern const struct sql_result driver_mysql_result; extern const struct sql_result driver_mysql_error_result; static struct event_category event_category_mysql = { .parent = &event_category_sql, .name = "mysql" }; static int driver_mysql_connect(struct sql_db *_db) { struct mysql_db *db = container_of(_db, struct mysql_db, api); const char *unix_socket, *host; unsigned long client_flags = db->client_flags; unsigned int secs_used; time_t start_time; bool failed; i_assert(db->api.state == SQL_DB_STATE_DISCONNECTED); sql_db_set_state(&db->api, SQL_DB_STATE_CONNECTING); if (db->host == NULL) { /* assume option_file overrides the host, or if not we'll just connect to localhost */ unix_socket = NULL; host = NULL; } else if (*db->host == '/') { unix_socket = db->host; host = NULL; } else { unix_socket = NULL; host = db->host; } if (db->option_file != NULL) { mysql_options(db->mysql, MYSQL_READ_DEFAULT_FILE, db->option_file); } if (db->host != NULL) event_set_append_log_prefix(_db->event, t_strdup_printf("mysql(%s): ", db->host)); e_debug(_db->event, "Connecting"); mysql_options(db->mysql, MYSQL_OPT_CONNECT_TIMEOUT, &db->connect_timeout); mysql_options(db->mysql, MYSQL_OPT_READ_TIMEOUT, &db->read_timeout); mysql_options(db->mysql, MYSQL_OPT_WRITE_TIMEOUT, &db->write_timeout); mysql_options(db->mysql, MYSQL_READ_DEFAULT_GROUP, db->option_group != NULL ? db->option_group : "client"); if (!db->ssl_set && (db->ssl_ca != NULL || db->ssl_ca_path != NULL)) { #ifdef HAVE_MYSQL_SSL mysql_ssl_set(db->mysql, db->ssl_key, db->ssl_cert, db->ssl_ca, db->ssl_ca_path #ifdef HAVE_MYSQL_SSL_CIPHER , db->ssl_cipher #endif ); #ifdef HAVE_MYSQL_SSL_VERIFY_SERVER_CERT mysql_options(db->mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (void *)&db->ssl_verify_server_cert); #endif db->ssl_set = TRUE; #else i_fatal("mysql: SSL support not compiled in " "(remove ssl_ca and ssl_ca_path settings)"); #endif } #ifdef CLIENT_MULTI_RESULTS client_flags |= CLIENT_MULTI_RESULTS; #endif /* CLIENT_MULTI_RESULTS allows the use of stored procedures */ start_time = time(NULL); failed = mysql_real_connect(db->mysql, host, db->user, db->password, db->dbname, db->port, unix_socket, client_flags) == NULL; secs_used = time(NULL) - start_time; if (failed) { /* connecting could have taken a while. make sure that any timeouts that get added soon will get a refreshed timestamp. */ io_loop_time_refresh(); if (db->api.connect_delay < secs_used) db->api.connect_delay = secs_used; sql_db_set_state(&db->api, SQL_DB_STATE_DISCONNECTED); e_error(_db->event, "Connect failed to database (%s): %s - " "waiting for %u seconds before retry", db->dbname, mysql_error(db->mysql), db->api.connect_delay); sql_disconnect(&db->api); return -1; } else { db->last_success = ioloop_time; sql_db_set_state(&db->api, SQL_DB_STATE_IDLE); return 1; } } static void driver_mysql_disconnect(struct sql_db *_db) { struct mysql_db *db = container_of(_db, struct mysql_db, api); if (db->mysql != NULL) mysql_close(db->mysql); } static int driver_mysql_parse_connect_string(struct mysql_db *db, const char *connect_string, const char **error_r) { const char *const *args, *name, *value; const char **field; db->ssl_cipher = "HIGH"; db->ssl_verify_server_cert = 1; db->connect_timeout = SQL_CONNECT_TIMEOUT_SECS; db->read_timeout = MYSQL_DEFAULT_READ_TIMEOUT_SECS; db->write_timeout = MYSQL_DEFAULT_WRITE_TIMEOUT_SECS; args = t_strsplit_spaces(connect_string, " "); for (; *args != NULL; args++) { value = strchr(*args, '='); if (value == NULL) { *error_r = t_strdup_printf("Missing value in connect string: %s", *args); return -1; } name = t_strdup_until(*args, value); value++; field = NULL; if (strcmp(name, "host") == 0 || strcmp(name, "hostaddr") == 0) field = &db->host; else if (strcmp(name, "user") == 0) field = &db->user; else if (strcmp(name, "password") == 0) field = &db->password; else if (strcmp(name, "dbname") == 0) field = &db->dbname; else if (strcmp(name, "port") == 0) { if (net_str2port(value, &db->port) < 0) { *error_r = t_strdup_printf("Invalid port number: %s", value); return -1; } } else if (strcmp(name, "client_flags") == 0) { if (str_to_uint(value, &db->client_flags) < 0) { *error_r = t_strdup_printf("Invalid client flags: %s", value); return -1; } } else if (strcmp(name, "connect_timeout") == 0) { if (str_to_uint(value, &db->connect_timeout) < 0) { *error_r = t_strdup_printf("Invalid read_timeout: %s", value); return -1; } } else if (strcmp(name, "read_timeout") == 0) { if (str_to_uint(value, &db->read_timeout) < 0) { *error_r = t_strdup_printf("Invalid read_timeout: %s", value); return -1; } } else if (strcmp(name, "write_timeout") == 0) { if (str_to_uint(value, &db->write_timeout) < 0) { *error_r = t_strdup_printf("Invalid read_timeout: %s", value); return -1; } } else if (strcmp(name, "ssl_cert") == 0) field = &db->ssl_cert; else if (strcmp(name, "ssl_key") == 0) field = &db->ssl_key; else if (strcmp(name, "ssl_ca") == 0) field = &db->ssl_ca; else if (strcmp(name, "ssl_ca_path") == 0) field = &db->ssl_ca_path; else if (strcmp(name, "ssl_cipher") == 0) field = &db->ssl_cipher; else if (strcmp(name, "ssl_verify_server_cert") == 0) { if (strcmp(value, "yes") == 0) db->ssl_verify_server_cert = 1; else if (strcmp(value, "no") == 0) db->ssl_verify_server_cert = 0; else { *error_r = t_strdup_printf("Invalid boolean: %s", value); return -1; } } else if (strcmp(name, "option_file") == 0) field = &db->option_file; else if (strcmp(name, "option_group") == 0) field = &db->option_group; else { *error_r = t_strdup_printf("Unknown connect string: %s", name); return -1; } if (field != NULL) *field = p_strdup(db->pool, value); } if (db->host == NULL && db->option_file == NULL) { *error_r = "No hosts given in connect string"; return -1; } if (db->mysql == NULL) { db->mysql = p_new(db->pool, MYSQL, 1); MYSQL *ptr = mysql_init(db->mysql); if (ptr == NULL) i_fatal_status(FATAL_OUTOFMEM, "mysql_init() failed"); } return 0; } static int driver_mysql_init_full_v(const struct sql_settings *set, struct sql_db **db_r, const char **error_r) { struct mysql_db *db; const char *error = NULL; pool_t pool; int ret; pool = pool_alloconly_create("mysql driver", 1024); db = p_new(pool, struct mysql_db, 1); db->pool = pool; db->api = driver_mysql_db; db->api.event = event_create(set->event_parent); event_add_category(db->api.event, &event_category_mysql); event_set_append_log_prefix(db->api.event, "mysql: "); T_BEGIN { ret = driver_mysql_parse_connect_string(db, set->connect_string, &error); error = p_strdup(db->pool, error); } T_END; if (ret < 0) { *error_r = t_strdup(error); pool_unref(&db->pool); return ret; } *db_r = &db->api; return 0; } static void driver_mysql_deinit_v(struct sql_db *_db) { struct mysql_db *db = container_of(_db, struct mysql_db, api); _db->no_reconnect = TRUE; sql_db_set_state(&db->api, SQL_DB_STATE_DISCONNECTED); driver_mysql_disconnect(_db); sql_connection_log_finished(_db); event_unref(&_db->event); array_free(&_db->module_contexts); pool_unref(&db->pool); } static int driver_mysql_do_query(struct mysql_db *db, const char *query, struct event *event) { int ret, diff; struct event_passthrough *e; ret = mysql_query(db->mysql, query); io_loop_time_refresh(); e = sql_query_finished_event(&db->api, event, query, ret == 0, &diff); if (ret != 0) { e->add_int("error_code", mysql_errno(db->mysql)); e->add_str("error", mysql_error(db->mysql)); e_debug(e->event(), SQL_QUERY_FINISHED_FMT": %s", query, diff, mysql_error(db->mysql)); } else e_debug(e->event(), SQL_QUERY_FINISHED_FMT, query, diff); if (ret == 0) return 0; /* failed */ switch (mysql_errno(db->mysql)) { case CR_SERVER_GONE_ERROR: case CR_SERVER_LOST: sql_db_set_state(&db->api, SQL_DB_STATE_DISCONNECTED); break; default: break; } return -1; } static const char * driver_mysql_escape_string(struct sql_db *_db, const char *string) { struct mysql_db *db = container_of(_db, struct mysql_db, api); size_t len = strlen(string); char *to; if (_db->state == SQL_DB_STATE_DISCONNECTED) { /* try connecting */ (void)sql_connect(&db->api); } if (_db->state == SQL_DB_STATE_DISCONNECTED) { /* FIXME: we don't have a valid connection, so fallback to using default escaping. the next query will most likely fail anyway so it shouldn't matter that much what we return here.. Anyway, this API needs changing so that the escaping function could already fail the query reliably. */ to = t_buffer_get(len * 2 + 1); len = mysql_escape_string(to, string, len); t_buffer_alloc(len + 1); return to; } to = t_buffer_get(len * 2 + 1); len = mysql_real_escape_string(db->mysql, to, string, len); t_buffer_alloc(len + 1); return to; } static void driver_mysql_exec(struct sql_db *_db, const char *query) { struct mysql_db *db = container_of(_db, struct mysql_db, api); struct event *event = event_create(_db->event); (void)driver_mysql_do_query(db, query, event); event_unref(&event); } static void driver_mysql_query(struct sql_db *db, const char *query, sql_query_callback_t *callback, void *context) { struct sql_result *result; result = sql_query_s(db, query); result->callback = TRUE; callback(result, context); result->callback = FALSE; sql_result_unref(result); } static struct sql_result * driver_mysql_query_s(struct sql_db *_db, const char *query) { struct mysql_db *db = container_of(_db, struct mysql_db, api); struct mysql_result *result; struct event *event; int ret; result = i_new(struct mysql_result, 1); result->api = driver_mysql_result; event = event_create(_db->event); if (driver_mysql_do_query(db, query, event) < 0) result->api = driver_mysql_error_result; else { /* query ok */ result->affected_rows = mysql_affected_rows(db->mysql); result->result = mysql_store_result(db->mysql); #ifdef CLIENT_MULTI_RESULTS /* Because we've enabled CLIENT_MULTI_RESULTS, we need to read (ignore) extra results - there should not be any. ret is: -1 = done, >0 = error, 0 = more results. */ while ((ret = mysql_next_result(db->mysql)) == 0) ; #else ret = -1; #endif if (ret < 0 && (result->result != NULL || mysql_errno(db->mysql) == 0)) { /* ok */ } else { /* failed */ if (result->result != NULL) mysql_free_result(result->result); result->api = driver_mysql_error_result; } } result->api.db = _db; result->api.refcount = 1; result->api.event = event; return &result->api; } static void driver_mysql_result_free(struct sql_result *_result) { struct mysql_result *result = container_of(_result, struct mysql_result, api); i_assert(_result != &sql_not_connected_result); if (_result->callback) return; if (result->result != NULL) mysql_free_result(result->result); event_unref(&_result->event); i_free(result); } static int driver_mysql_result_next_row(struct sql_result *_result) { struct mysql_result *result = container_of(_result, struct mysql_result, api); struct mysql_db *db = container_of(_result->db, struct mysql_db, api); int ret; if (result->result == NULL) { /* no results */ return 0; } result->row = mysql_fetch_row(result->result); if (result->row != NULL) ret = 1; else { if (mysql_errno(db->mysql) != 0) return -1; ret = 0; } db->last_success = ioloop_time; return ret; } static void driver_mysql_result_fetch_fields(struct mysql_result *result) { if (result->fields != NULL) return; result->fields_count = mysql_num_fields(result->result); result->fields = mysql_fetch_fields(result->result); } static unsigned int driver_mysql_result_get_fields_count(struct sql_result *_result) { struct mysql_result *result = container_of(_result, struct mysql_result, api); driver_mysql_result_fetch_fields(result); return result->fields_count; } static const char * driver_mysql_result_get_field_name(struct sql_result *_result, unsigned int idx) { struct mysql_result *result = container_of(_result, struct mysql_result, api); driver_mysql_result_fetch_fields(result); i_assert(idx < result->fields_count); return result->fields[idx].name; } static int driver_mysql_result_find_field(struct sql_result *_result, const char *field_name) { struct mysql_result *result = container_of(_result, struct mysql_result, api); unsigned int i; driver_mysql_result_fetch_fields(result); for (i = 0; i < result->fields_count; i++) { if (strcmp(result->fields[i].name, field_name) == 0) return i; } return -1; } static const char * driver_mysql_result_get_field_value(struct sql_result *_result, unsigned int idx) { struct mysql_result *result = container_of(_result, struct mysql_result, api); return (const char *)result->row[idx]; } static const unsigned char * driver_mysql_result_get_field_value_binary(struct sql_result *_result, unsigned int idx, size_t *size_r) { struct mysql_result *result = container_of(_result, struct mysql_result, api); unsigned long *lengths; lengths = mysql_fetch_lengths(result->result); *size_r = lengths[idx]; return (const void *)result->row[idx]; } static const char * driver_mysql_result_find_field_value(struct sql_result *result, const char *field_name) { int idx; idx = driver_mysql_result_find_field(result, field_name); if (idx < 0) return NULL; return driver_mysql_result_get_field_value(result, idx); } static const char *const * driver_mysql_result_get_values(struct sql_result *_result) { struct mysql_result *result = container_of(_result, struct mysql_result, api); return (const char *const *)result->row; } static const char *driver_mysql_result_get_error(struct sql_result *_result) { struct mysql_db *db = container_of(_result->db, struct mysql_db, api); const char *errstr; unsigned int idle_time; int err; err = mysql_errno(db->mysql); errstr = mysql_error(db->mysql); if ((err == CR_SERVER_GONE_ERROR || err == CR_SERVER_LOST) && db->last_success != 0) { idle_time = ioloop_time - db->last_success; errstr = t_strdup_printf("%s (idled for %u secs)", errstr, idle_time); } return errstr; } static struct sql_transaction_context * driver_mysql_transaction_begin(struct sql_db *db) { struct mysql_transaction_context *ctx; ctx = i_new(struct mysql_transaction_context, 1); ctx->ctx.db = db; ctx->query_pool = pool_alloconly_create("mysql transaction", 1024); ctx->ctx.event = event_create(db->event); return &ctx->ctx; } static void driver_mysql_transaction_commit(struct sql_transaction_context *ctx, sql_commit_callback_t *callback, void *context) { struct sql_commit_result result; const char *error; i_zero(&result); if (sql_transaction_commit_s(&ctx, &error) < 0) result.error = error; callback(&result, context); } static int ATTR_NULL(3) transaction_send_query(struct mysql_transaction_context *ctx, const char *query, unsigned int *affected_rows_r) { struct sql_result *_result; int ret = 0; if (ctx->failed) return -1; _result = sql_query_s(ctx->ctx.db, query); if (sql_result_next_row(_result) < 0) { ctx->error = sql_result_get_error(_result); ctx->failed = TRUE; ret = -1; } else if (affected_rows_r != NULL) { struct mysql_result *result = container_of(_result, struct mysql_result, api); i_assert(result->affected_rows != (my_ulonglong)-1); *affected_rows_r = result->affected_rows; } sql_result_unref(_result); return ret; } static int driver_mysql_try_commit_s(struct mysql_transaction_context *ctx) { struct sql_transaction_context *_ctx = &ctx->ctx; bool multi = _ctx->head != NULL && _ctx->head->next != NULL; /* wrap in BEGIN/COMMIT only if transaction has mutiple statements. */ if (multi && transaction_send_query(ctx, "BEGIN", NULL) < 0) { if (_ctx->db->state != SQL_DB_STATE_DISCONNECTED) return -1; /* we got disconnected, retry */ return 0; } else if (multi) { ctx->commit_started = TRUE; } while (_ctx->head != NULL) { if (transaction_send_query(ctx, _ctx->head->query, _ctx->head->affected_rows) < 0) return -1; _ctx->head = _ctx->head->next; } if (multi && transaction_send_query(ctx, "COMMIT", NULL) < 0) return -1; return 1; } static int driver_mysql_transaction_commit_s(struct sql_transaction_context *_ctx, const char **error_r) { struct mysql_transaction_context *ctx = container_of(_ctx, struct mysql_transaction_context, ctx); struct mysql_db *db = container_of(_ctx->db, struct mysql_db, api); int ret = 1; *error_r = NULL; if (_ctx->head != NULL) { ret = driver_mysql_try_commit_s(ctx); *error_r = t_strdup(ctx->error); if (ret == 0) { e_info(db->api.event, "Disconnected from database, " "retrying commit"); if (sql_connect(_ctx->db) >= 0) { ctx->failed = FALSE; ret = driver_mysql_try_commit_s(ctx); } } } if (ret > 0) ctx->committed = TRUE; sql_transaction_rollback(&_ctx); return ret <= 0 ? -1 : 0; } static void driver_mysql_transaction_rollback(struct sql_transaction_context *_ctx) { struct mysql_transaction_context *ctx = container_of(_ctx, struct mysql_transaction_context, ctx); if (ctx->failed) { bool rolledback = FALSE; const char *orig_error = t_strdup(ctx->error); if (ctx->commit_started) { /* reset failed flag so ROLLBACK is actually sent. otherwise, transaction_send_query() will return without trying to send the query. */ ctx->failed = FALSE; if (transaction_send_query(ctx, "ROLLBACK", NULL) < 0) e_debug(event_create_passthrough(_ctx->event)-> add_str("error", ctx->error)->event(), "Rollback failed: %s", ctx->error); else rolledback = TRUE; } e_debug(sql_transaction_finished_event(_ctx)-> add_str("error", orig_error)->event(), "Transaction failed: %s%s", orig_error, rolledback ? " - Rolled back" : ""); } else if (ctx->committed) e_debug(sql_transaction_finished_event(_ctx)->event(), "Transaction committed"); else e_debug(sql_transaction_finished_event(_ctx)-> add_str("error", "Rolled back")->event(), "Transaction rolled back"); event_unref(&ctx->ctx.event); pool_unref(&ctx->query_pool); i_free(ctx); } static void driver_mysql_update(struct sql_transaction_context *_ctx, const char *query, unsigned int *affected_rows) { struct mysql_transaction_context *ctx = container_of(_ctx, struct mysql_transaction_context, ctx); sql_transaction_add_query(&ctx->ctx, ctx->query_pool, query, affected_rows); } static const char * driver_mysql_escape_blob(struct sql_db *_db ATTR_UNUSED, const unsigned char *data, size_t size) { string_t *str = t_str_new(128); str_append(str, "X'"); binary_to_hex_append(str, data, size); str_append_c(str, '\''); return str_c(str); } const struct sql_db driver_mysql_db = { .name = "mysql", .flags = SQL_DB_FLAG_BLOCKING | SQL_DB_FLAG_POOLED | SQL_DB_FLAG_ON_DUPLICATE_KEY, .v = { .init_full = driver_mysql_init_full_v, .deinit = driver_mysql_deinit_v, .connect = driver_mysql_connect, .disconnect = driver_mysql_disconnect, .escape_string = driver_mysql_escape_string, .exec = driver_mysql_exec, .query = driver_mysql_query, .query_s = driver_mysql_query_s, .transaction_begin = driver_mysql_transaction_begin, .transaction_commit = driver_mysql_transaction_commit, .transaction_commit_s = driver_mysql_transaction_commit_s, .transaction_rollback = driver_mysql_transaction_rollback, .update = driver_mysql_update, .escape_blob = driver_mysql_escape_blob, } }; const struct sql_result driver_mysql_result = { .v = { .free = driver_mysql_result_free, .next_row = driver_mysql_result_next_row, .get_fields_count = driver_mysql_result_get_fields_count, .get_field_name = driver_mysql_result_get_field_name, .find_field = driver_mysql_result_find_field, .get_field_value = driver_mysql_result_get_field_value, .get_field_value_binary = driver_mysql_result_get_field_value_binary, .find_field_value = driver_mysql_result_find_field_value, .get_values = driver_mysql_result_get_values, .get_error = driver_mysql_result_get_error, } }; static int driver_mysql_result_error_next_row(struct sql_result *result ATTR_UNUSED) { return -1; } const struct sql_result driver_mysql_error_result = { .v = { .free = driver_mysql_result_free, .next_row = driver_mysql_result_error_next_row, .get_error = driver_mysql_result_get_error, }, .failed_try_retry = TRUE }; const char *driver_mysql_version = DOVECOT_ABI_VERSION; void driver_mysql_init(void); void driver_mysql_deinit(void); void driver_mysql_init(void) { sql_driver_register(&driver_mysql_db); } void driver_mysql_deinit(void) { sql_driver_unregister(&driver_mysql_db); } #endif dovecot-2.3.21.1/src/lib-sql/sql-api.h0000644000000000000000000002354314656633576014175 00000000000000#ifndef SQL_API_H #define SQL_API_H struct timespec; /* This SQL API is designed to work asynchronously. The underlying drivers however may not. */ enum sql_db_flags { /* Set if queries are not executed asynchronously */ SQL_DB_FLAG_BLOCKING = 0x01, /* Set if database wants to use connection pooling */ SQL_DB_FLAG_POOLED = 0x02, /* Prepared statements are supported by the database. If they aren't, the functions can still be used, but they're just internally convered into regular statements. */ SQL_DB_FLAG_PREP_STATEMENTS = 0x04, /* Database supports INSERT .. ON DUPLICATE KEY syntax. */ SQL_DB_FLAG_ON_DUPLICATE_KEY = 0x08, /* Database supports INSERT .. ON CONFLICT DO UPDATE syntax. */ SQL_DB_FLAG_ON_CONFLICT_DO = 0x10, }; enum sql_field_type { SQL_TYPE_STR, SQL_TYPE_UINT, SQL_TYPE_ULLONG, SQL_TYPE_BOOL }; struct sql_field_def { enum sql_field_type type; const char *name; size_t offset; }; enum sql_result_error_type { SQL_RESULT_ERROR_TYPE_UNKNOWN = 0, /* It's unknown whether write succeeded or not. This could be due to a timeout or a disconnection from server. */ SQL_RESULT_ERROR_TYPE_WRITE_UNCERTAIN }; enum sql_result_next { /* Row was returned */ SQL_RESULT_NEXT_OK = 1, /* There are no more rows */ SQL_RESULT_NEXT_LAST = 0, /* Error occurred - see sql_result_get_error*() */ SQL_RESULT_NEXT_ERROR = -1, /* There are more results - call sql_result_more() */ SQL_RESULT_NEXT_MORE = -99 }; #define SQL_DEF_STRUCT(name, struct_name, type, c_type) \ { (type) + COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE( \ ((struct struct_name *)0)->name, c_type), \ #name, offsetof(struct struct_name, name) } #define SQL_DEF_STRUCT_STR(name, struct_name) \ SQL_DEF_STRUCT(name, struct_name, SQL_TYPE_STR, const char *) #define SQL_DEF_STRUCT_UINT(name, struct_name) \ SQL_DEF_STRUCT(name, struct_name, SQL_TYPE_UINT, unsigned int) #define SQL_DEF_STRUCT_ULLONG(name, struct_name) \ SQL_DEF_STRUCT(name, struct_name, SQL_TYPE_ULLONG, unsigned long long) #define SQL_DEF_STRUCT_BOOL(name, struct_name) \ SQL_DEF_STRUCT(name, struct_name, SQL_TYPE_BOOL, bool) struct sql_db; struct sql_result; struct sql_commit_result { const char *error; enum sql_result_error_type error_type; }; struct sql_settings { const char *driver; const char *connect_string; struct event *event_parent; }; typedef void sql_query_callback_t(struct sql_result *result, void *context); typedef void sql_commit_callback_t(const struct sql_commit_result *result, void *context); void sql_drivers_init(void); void sql_drivers_deinit(void); /* register all built-in SQL drivers */ void sql_drivers_register_all(void); void sql_driver_register(const struct sql_db *driver); void sql_driver_unregister(const struct sql_db *driver); /* Initialize database connections. db_driver is the database driver name, eg. "mysql" or "pgsql". connect_string is driver-specific. */ struct sql_db *sql_init(const char *db_driver, const char *connect_string); int sql_init_full(const struct sql_settings *set, struct sql_db **db_r, const char **error_r); void sql_ref(struct sql_db *db); void sql_unref(struct sql_db **db); /* Returns SQL database state flags. */ enum sql_db_flags sql_get_flags(struct sql_db *db); /* Explicitly connect to the database. It's not required to call this function though. Returns -1 if we're not connected, 0 if we started connecting or 1 if we are fully connected now. */ int sql_connect(struct sql_db *db); /* Explicitly disconnect from database and abort pending auth requests. */ void sql_disconnect(struct sql_db *db); /* Escape the given string if needed and return it. */ const char *sql_escape_string(struct sql_db *db, const char *string); /* Escape the given data as a string. */ const char *sql_escape_blob(struct sql_db *db, const unsigned char *data, size_t size); /* Execute SQL query without waiting for results. */ void sql_exec(struct sql_db *db, const char *query); /* Execute SQL query and return result in callback. If fields list is given, the returned fields are validated to be of correct type, and you can use sql_result_next_row_get() */ void sql_query(struct sql_db *db, const char *query, sql_query_callback_t *callback, void *context); #define sql_query(db, query, callback, context) \ sql_query(db, query - \ CALLBACK_TYPECHECK(callback, void (*)( \ struct sql_result *, typeof(context))), \ (sql_query_callback_t *)callback, context) /* Execute blocking SQL query and return result. */ struct sql_result *sql_query_s(struct sql_db *db, const char *query); struct sql_prepared_statement * sql_prepared_statement_init(struct sql_db *db, const char *query_template); void sql_prepared_statement_unref(struct sql_prepared_statement **prep_stmt); struct sql_statement * sql_statement_init(struct sql_db *db, const char *query_template); struct sql_statement * sql_statement_init_prepared(struct sql_prepared_statement *prep_stmt); void sql_statement_abort(struct sql_statement **stmt); void sql_statement_set_timestamp(struct sql_statement *stmt, const struct timespec *ts); void sql_statement_set_no_log_expanded_values(struct sql_statement *stmt, bool no_expand); void sql_statement_bind_str(struct sql_statement *stmt, unsigned int column_idx, const char *value); void sql_statement_bind_binary(struct sql_statement *stmt, unsigned int column_idx, const void *value, size_t value_size); void sql_statement_bind_int64(struct sql_statement *stmt, unsigned int column_idx, int64_t value); void sql_statement_query(struct sql_statement **stmt, sql_query_callback_t *callback, void *context); #define sql_statement_query(stmt, callback, context) \ sql_statement_query(stmt, \ (sql_query_callback_t *)callback, TRUE ? context : \ CALLBACK_TYPECHECK(callback, void (*)( \ struct sql_result *, typeof(context)))) struct sql_result *sql_statement_query_s(struct sql_statement **stmt); void sql_result_setup_fetch(struct sql_result *result, const struct sql_field_def *fields, void *dest, size_t dest_size); /* Go to next row. See enum sql_result_next. */ int sql_result_next_row(struct sql_result *result); /* If sql_result_next_row() returned SQL_RESULT_NEXT_MORE, this can be called to continue returning more results. The result is freed with this call, so it must not be accesed anymore until the callback is finished. */ void sql_result_more(struct sql_result **result, sql_query_callback_t *callback, void *context); #define sql_result_more(result, callback, context) \ sql_result_more(result - \ CALLBACK_TYPECHECK(callback, void (*)( \ struct sql_result *, typeof(context))), \ (sql_query_callback_t *)callback, context) /* Synchronous version of sql_result_more(). The result will be replaced with the new result. */ void sql_result_more_s(struct sql_result **result); void sql_result_ref(struct sql_result *result); /* Needs to be called only with sql_query_s() or when result has been explicitly referenced. */ void sql_result_unref(struct sql_result *result); /* Return number of fields in result. */ unsigned int sql_result_get_fields_count(struct sql_result *result); /* Return name of the given field index. */ const char *sql_result_get_field_name(struct sql_result *result, unsigned int idx); /* Return field index for given name, or -1 if not found. */ int sql_result_find_field(struct sql_result *result, const char *field_name); /* Returns value of given field as string. Note that it can be NULL. */ const char *sql_result_get_field_value(struct sql_result *result, unsigned int idx); /* Returns a binary value. Note that a NULL is returned as NULL with size=0, while empty string returns non-NULL with size=0. */ const unsigned char * sql_result_get_field_value_binary(struct sql_result *result, unsigned int idx, size_t *size_r); /* Find the field and return its value. NULL return value can mean that either the field didn't exist or that its value is NULL. */ const char *sql_result_find_field_value(struct sql_result *result, const char *field_name); /* Return all values of current row. Note that this array is not NULL-terminated - you must use sql_result_get_fields_count() to find out the array's length. It's also possible that some of the values inside the array are NULL. */ const char *const *sql_result_get_values(struct sql_result *result); /* Return last error message in result. */ const char *sql_result_get_error(struct sql_result *result); enum sql_result_error_type sql_result_get_error_type(struct sql_result *result); /* Begin a new transaction. Currently you're limited to only one open transaction at a time. */ struct sql_transaction_context *sql_transaction_begin(struct sql_db *db); /* Commit transaction. */ void sql_transaction_commit(struct sql_transaction_context **ctx, sql_commit_callback_t *callback, void *context); #define sql_transaction_commit(ctx, callback, context) \ sql_transaction_commit(ctx - \ CALLBACK_TYPECHECK(callback, void (*)( \ const struct sql_commit_result *, typeof(context))), \ (sql_commit_callback_t *)callback, context) /* Synchronous commit. Returns 0 if ok, -1 if error. */ int sql_transaction_commit_s(struct sql_transaction_context **ctx, const char **error_r); void sql_transaction_rollback(struct sql_transaction_context **ctx); /* Execute query in given transaction. */ void sql_update(struct sql_transaction_context *ctx, const char *query); void sql_update_stmt(struct sql_transaction_context *ctx, struct sql_statement **stmt); /* Save the number of rows updated by this query. The value is set before commit callback is called. */ void sql_update_get_rows(struct sql_transaction_context *ctx, const char *query, unsigned int *affected_rows); void sql_update_stmt_get_rows(struct sql_transaction_context *ctx, struct sql_statement **stmt, unsigned int *affected_rows); /* Wait for SQL query results. */ void sql_wait(struct sql_db *db); #endif dovecot-2.3.21.1/src/lmtp/0000755000000000000000000000000014656633640012130 500000000000000dovecot-2.3.21.1/src/lmtp/lmtp-recipient.h0000644000000000000000000000214614656633576015170 00000000000000#ifndef LMTP_RECIPIENT_H #define LMTP_RECIPIENT_H struct smtp_address; struct smtp_server_cmd_ctx; struct smtp_server_cmd_rcpt; struct smtp_server_recipient; union lmtp_recipient_module_context; struct client; enum lmtp_recipient_type { LMTP_RECIPIENT_TYPE_LOCAL, LMTP_RECIPIENT_TYPE_PROXY, }; struct lmtp_recipient { struct client *client; struct smtp_server_recipient *rcpt; enum lmtp_recipient_type type; void *backend_context; const char *session_id; const char *forward_fields; /* Module-specific contexts. */ ARRAY(union lmtp_recipient_module_context *) module_contexts; }; struct lmtp_recipient_module_register { unsigned int id; }; union lmtp_recipient_module_context { struct lmtp_recipient_module_register *reg; }; extern struct lmtp_recipient_module_register lmtp_recipient_module_register; struct lmtp_recipient * lmtp_recipient_create(struct client *client, struct smtp_server_transaction *trans, struct smtp_server_recipient *rcpt); struct lmtp_recipient * lmtp_recipient_find_duplicate(struct lmtp_recipient *lrcpt, struct smtp_server_transaction *trans); #endif dovecot-2.3.21.1/src/lmtp/lmtp-local.c0000644000000000000000000005465114656633576014303 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lmtp-common.h" #include "str.h" #include "istream.h" #include "strescape.h" #include "time-util.h" #include "hostpid.h" #include "var-expand.h" #include "restrict-access.h" #include "anvil-client.h" #include "settings-parser.h" #include "mail-storage.h" #include "mail-storage-service.h" #include "mail-namespace.h" #include "mail-deliver.h" #include "mail-autoexpunge.h" #include "index/raw/raw-storage.h" #include "smtp-common.h" #include "smtp-params.h" #include "smtp-address.h" #include "smtp-submit-settings.h" #include "lda-settings.h" #include "lmtp-settings.h" #include "lmtp-recipient.h" #include "lmtp-local.h" struct lmtp_local_recipient { struct lmtp_recipient *rcpt; char *detail; struct mail_storage_service_user *service_user; struct anvil_query *anvil_query; struct lmtp_local_recipient *duplicate; bool anvil_connect_sent:1; }; struct lmtp_local { struct client *client; ARRAY(struct lmtp_local_recipient *) rcpt_to; struct mail *raw_mail, *first_saved_mail; struct mail_user *rcpt_user; }; /* * LMTP local */ static struct lmtp_local * lmtp_local_init(struct client *client) { struct lmtp_local *local; local = i_new(struct lmtp_local, 1); local->client = client; i_array_init(&local->rcpt_to, 8); return local; } void lmtp_local_deinit(struct lmtp_local **_local) { struct lmtp_local *local = *_local; *_local = NULL; if (array_is_created(&local->rcpt_to)) array_free(&local->rcpt_to); if (local->raw_mail != NULL) { struct mailbox_transaction_context *raw_trans = local->raw_mail->transaction; struct mailbox *raw_box = local->raw_mail->box; mail_free(&local->raw_mail); mailbox_transaction_rollback(&raw_trans); mailbox_free(&raw_box); } i_free(local); } /* * Recipient */ static void lmtp_local_rcpt_anvil_disconnect(struct lmtp_local_recipient *llrcpt) { const struct mail_storage_service_input *input; if (!llrcpt->anvil_connect_sent) return; llrcpt->anvil_connect_sent = FALSE; input = mail_storage_service_user_get_input(llrcpt->service_user); master_service_anvil_send(master_service, t_strconcat( "DISCONNECT\t", my_pid, "\t", master_service_get_name(master_service), "/", input->username, "\n", NULL)); } static void lmtp_local_rcpt_destroy(struct smtp_server_recipient *rcpt ATTR_UNUSED, struct lmtp_local_recipient *llrcpt) { if (llrcpt->anvil_query != NULL) anvil_client_query_abort(anvil, &llrcpt->anvil_query); lmtp_local_rcpt_anvil_disconnect(llrcpt); mail_storage_service_user_unref(&llrcpt->service_user); } static void lmtp_local_rcpt_reply_overquota(struct lmtp_local_recipient *llrcpt, const char *error) { struct smtp_server_recipient *rcpt = llrcpt->rcpt->rcpt; struct lda_settings *lda_set = mail_storage_service_user_get_set(llrcpt->service_user)[2]; if (lda_set->quota_full_tempfail) smtp_server_recipient_reply(rcpt, 452, "4.2.2", "%s", error); else smtp_server_recipient_reply(rcpt, 552, "5.2.2", "%s", error); } static void ATTR_FORMAT(4,5) lmtp_local_rcpt_fail_all(struct lmtp_local *local, unsigned int status, const char *enh_code, const char *fmt, ...) { struct lmtp_local_recipient *const *llrcpts; const char *msg; unsigned int count, i; va_list args; va_start(args, fmt); msg = t_strdup_vprintf(fmt, args); va_end(args); llrcpts = array_get(&local->rcpt_to, &count); for (i = 0; i < count; i++) { struct smtp_server_recipient *rcpt = llrcpts[i]->rcpt->rcpt; smtp_server_recipient_reply(rcpt, status, enh_code, "%s", msg); } } /* * RCPT command */ static int lmtp_local_rcpt_check_quota(struct lmtp_local_recipient *llrcpt) { struct client *client = llrcpt->rcpt->client; struct smtp_server_recipient *rcpt = llrcpt->rcpt->rcpt; struct smtp_address *address = rcpt->path; struct mail_user *user; struct mail_namespace *ns; struct mailbox *box; struct mailbox_status status; enum mail_error mail_error; const char *error; int ret; if (!client->lmtp_set->lmtp_rcpt_check_quota) return 0; /* mail user will be created second time when mail is saved, so it's session_id needs to be different, but second time session_id needs to be the same as rcpt session_id and mail user session id for the first rcpt should not overlap with session id of the second recipient, so add custom ":quota" suffix to the session_id without session_id counter increment, so next time mail user will get the same session id as rcpt */ ret = mail_storage_service_next_with_session_suffix(storage_service, llrcpt->service_user, "quota", &user, &error); if (ret < 0) { e_error(rcpt->event, "Failed to initialize user %s: %s", smtp_address_encode(address), error); ret = -1; } else { /* Set the log prefix for the user. The default log prefix is automatically restored later when user context gets deactivated. */ i_set_failure_prefix("%s", mail_storage_service_user_get_log_prefix(llrcpt->service_user)); ns = mail_namespace_find_inbox(user->namespaces); box = mailbox_alloc(ns->list, "INBOX", 0); ret = mailbox_get_status(box, STATUS_CHECK_OVER_QUOTA, &status); if (ret < 0) { error = mailbox_get_last_error(box, &mail_error); if (mail_error == MAIL_ERROR_NOQUOTA) { lmtp_local_rcpt_reply_overquota(llrcpt, error); } else { e_error(rcpt->event, "mailbox_get_status(%s, STATUS_CHECK_OVER_QUOTA) " "failed: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, NULL)); } ret = -1; } mailbox_free(&box); mail_user_deinit(&user); mail_storage_service_io_deactivate_user(llrcpt->service_user); } if (ret < 0 && !smtp_server_recipient_is_replied(rcpt)) { smtp_server_recipient_reply(rcpt, 451, "4.3.0", "Temporary internal error"); } return ret; } static void lmtp_local_rcpt_approved(struct smtp_server_recipient *rcpt, struct lmtp_local_recipient *llrcpt) { struct client *client = llrcpt->rcpt->client; struct lmtp_recipient *drcpt; /* resolve duplicate recipient */ drcpt = lmtp_recipient_find_duplicate(llrcpt->rcpt, rcpt->trans); if (drcpt != NULL) { i_assert(drcpt->type == LMTP_RECIPIENT_TYPE_LOCAL); llrcpt->duplicate = drcpt->backend_context; i_assert(llrcpt->duplicate->duplicate == NULL); } /* add to local recipients */ array_push_back(&client->local->rcpt_to, &llrcpt); } static bool lmtp_local_rcpt_anvil_finish(struct lmtp_local_recipient *llrcpt) { struct smtp_server_recipient *rcpt = llrcpt->rcpt->rcpt; struct smtp_server_cmd_ctx *cmd = rcpt->cmd; if (lmtp_local_rcpt_check_quota(llrcpt) < 0) return FALSE; smtp_server_cmd_rcpt_reply_success(cmd); return TRUE; } static void lmtp_local_rcpt_anvil_cb(const char *reply, void *context) { struct lmtp_local_recipient *llrcpt = (struct lmtp_local_recipient *)context; struct client *client = llrcpt->rcpt->client; struct smtp_server_recipient *rcpt = llrcpt->rcpt->rcpt; const struct mail_storage_service_input *input; unsigned int parallel_count = 0; llrcpt->anvil_query = NULL; if (reply == NULL) { /* lookup failed */ } else if (str_to_uint(reply, ¶llel_count) < 0) { e_error(rcpt->event, "Invalid reply from anvil: %s", reply); } if (parallel_count >= client->lmtp_set->lmtp_user_concurrency_limit) { smtp_server_recipient_reply( rcpt, 451, "4.3.0", "Too many concurrent deliveries for user"); } else if (lmtp_local_rcpt_anvil_finish(llrcpt)) { llrcpt->anvil_connect_sent = TRUE; input = mail_storage_service_user_get_input(llrcpt->service_user); master_service_anvil_send(master_service, t_strconcat( "CONNECT\t", my_pid, "\t", master_service_get_name(master_service), "/", input->username, "\n", NULL)); } } int lmtp_local_rcpt(struct client *client, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct lmtp_recipient *lrcpt, const char *username, const char *detail) { struct smtp_server_recipient *rcpt = lrcpt->rcpt; struct lmtp_local_recipient *llrcpt; struct mail_storage_service_input input; struct mail_storage_service_user *service_user; const char *error = NULL; int ret = 0; i_zero(&input); input.module = input.service = "lmtp"; input.username = username; input.local_ip = client->local_ip; input.remote_ip = client->remote_ip; input.local_port = client->local_port; input.remote_port = client->remote_port; input.session_id = lrcpt->session_id; input.conn_ssl_secured = smtp_server_connection_is_ssl_secured(client->conn); input.conn_secured = input.conn_ssl_secured || smtp_server_connection_is_trusted(client->conn); input.forward_fields = lrcpt->forward_fields; input.event_parent = rcpt->event; ret = mail_storage_service_lookup(storage_service, &input, &service_user, &error); if (ret < 0) { e_error(rcpt->event, "Failed to lookup user %s: %s", username, error); smtp_server_recipient_reply(rcpt, 451, "4.3.0", "Temporary internal error"); return -1; } if (ret == 0) { smtp_server_recipient_reply(rcpt, 550, "5.1.1", "User doesn't exist: %s", username); return -1; } if (client->local == NULL) client->local = lmtp_local_init(client); llrcpt = p_new(rcpt->pool, struct lmtp_local_recipient, 1); llrcpt->rcpt = lrcpt; llrcpt->detail = p_strdup(rcpt->pool, detail); llrcpt->service_user = service_user; lrcpt->type = LMTP_RECIPIENT_TYPE_LOCAL; lrcpt->backend_context = llrcpt; smtp_server_recipient_add_hook( rcpt, SMTP_SERVER_RECIPIENT_HOOK_DESTROY, lmtp_local_rcpt_destroy, llrcpt); smtp_server_recipient_add_hook( rcpt, SMTP_SERVER_RECIPIENT_HOOK_APPROVED, lmtp_local_rcpt_approved, llrcpt); if (client->lmtp_set->lmtp_user_concurrency_limit == 0) { (void)lmtp_local_rcpt_anvil_finish(llrcpt); } else { /* NOTE: username may change as the result of the userdb lookup. Look up the new one via service_user. */ const struct mail_storage_service_input *input = mail_storage_service_user_get_input(llrcpt->service_user); const char *query = t_strconcat("LOOKUP\t", master_service_get_name(master_service), "/", str_tabescape(input->username), NULL); llrcpt->anvil_query = anvil_client_query(anvil, query, lmtp_local_rcpt_anvil_cb, llrcpt); return 0; } return 1; } /* * DATA command */ void lmtp_local_add_headers(struct lmtp_local *local, struct smtp_server_transaction *trans, string_t *headers) { struct client *client = local->client; const struct lmtp_settings *lmtp_set = client->lmtp_set; struct lmtp_local_recipient *const *llrcpts; const struct smtp_address *rcpt_to = NULL; unsigned int count; str_printfa(headers, "Return-Path: <%s>\r\n", smtp_address_encode(trans->mail_from)); llrcpts = array_get(&local->rcpt_to, &count); if (count == 1) { struct smtp_server_recipient *rcpt = llrcpts[0]->rcpt->rcpt; switch (lmtp_set->parsed_lmtp_hdr_delivery_address) { case LMTP_HDR_DELIVERY_ADDRESS_NONE: break; case LMTP_HDR_DELIVERY_ADDRESS_FINAL: rcpt_to = rcpt->path; break; case LMTP_HDR_DELIVERY_ADDRESS_ORIGINAL: rcpt_to = rcpt->params.orcpt.addr; break; } } if (rcpt_to != NULL) { str_printfa(headers, "Delivered-To: %s\r\n", smtp_address_encode(rcpt_to)); } } static int lmtp_local_deliver(struct lmtp_local *local, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct smtp_server_transaction *trans, struct lmtp_local_recipient *llrcpt, struct mail *src_mail, struct mail_deliver_session *session) { struct client *client = local->client; struct lmtp_recipient *lrcpt = llrcpt->rcpt; struct smtp_server_recipient *rcpt = lrcpt->rcpt; struct mail_storage_service_user *service_user = llrcpt->service_user; struct lmtp_local_deliver_context lldctx; struct mail_user *rcpt_user; const struct mail_storage_service_input *input; const struct mail_storage_settings *mail_set; struct smtp_submit_settings *smtp_set; struct smtp_proxy_data proxy_data; struct lda_settings *lda_set; struct mail_namespace *ns; struct setting_parser_context *set_parser; const struct var_expand_table *var_table; void **sets; const char *line, *error, *username; int ret; input = mail_storage_service_user_get_input(service_user); username = t_strdup(input->username); mail_set = mail_storage_service_user_get_mail_set(service_user); set_parser = mail_storage_service_user_get_settings_parser(service_user); smtp_server_connection_get_proxy_data (client->conn, &proxy_data); if (proxy_data.timeout_secs > 0 && (mail_set->mail_max_lock_timeout == 0 || mail_set->mail_max_lock_timeout > proxy_data.timeout_secs)) { /* set lock timeout waits to be less than when proxy has advertised that it's going to timeout the connection. this avoids duplicate deliveries in case the delivery succeeds after the proxy has already disconnected from us. */ line = t_strdup_printf("mail_max_lock_timeout=%us", proxy_data.timeout_secs <= 1 ? 1 : proxy_data.timeout_secs-1); if (settings_parse_line(set_parser, line) < 0) i_unreached(); } i_zero(&lldctx); lldctx.session_id = lrcpt->session_id; lldctx.src_mail = src_mail; lldctx.session = session; /* get the timestamp before user is created, since it starts the I/O */ io_loop_time_refresh(); lldctx.delivery_time_started = ioloop_timeval; client_update_data_state(client, username); if (mail_storage_service_next(storage_service, service_user, &rcpt_user, &error) < 0) { e_error(rcpt->event, "Failed to initialize user: %s", error); smtp_server_recipient_reply(rcpt, 451, "4.3.0", "Temporary internal error"); return -1; } local->rcpt_user = rcpt_user; sets = mail_storage_service_user_get_set(service_user); var_table = mail_user_var_expand_table(rcpt_user); smtp_set = sets[1]; lda_set = sets[2]; ret = settings_var_expand( &smtp_submit_setting_parser_info, smtp_set, client->pool, var_table, &error); if (ret > 0) { ret = settings_var_expand( &lda_setting_parser_info, lda_set, client->pool, var_table, &error); } if (ret <= 0) { e_error(rcpt->event, "Failed to expand settings: %s", error); smtp_server_recipient_reply(rcpt, 451, "4.3.0", "Temporary internal error"); return -1; } /* Set the log prefix for the user. The default log prefix is automatically restored later when user context gets deactivated. */ i_set_failure_prefix("%s", mail_storage_service_user_get_log_prefix(service_user)); lldctx.rcpt_user = rcpt_user; lldctx.smtp_set = smtp_set; lldctx.lda_set = lda_set; if (*llrcpt->detail == '\0' || !client->lmtp_set->lmtp_save_to_detail_mailbox) lldctx.rcpt_default_mailbox = "INBOX"; else { ns = mail_namespace_find_inbox(rcpt_user->namespaces); lldctx.rcpt_default_mailbox = t_strconcat(ns->prefix, llrcpt->detail, NULL); } ret = client->v.local_deliver(client, lrcpt, cmd, trans, &lldctx); lmtp_local_rcpt_anvil_disconnect(llrcpt); return ret; } static int lmtp_local_default_do_deliver(struct lmtp_local *local, struct lmtp_local_recipient *llrcpt, struct lmtp_local_deliver_context *lldctx, struct mail_deliver_context *dctx) { struct smtp_server_recipient *rcpt = llrcpt->rcpt->rcpt; enum mail_deliver_error error_code; const char *error; if (mail_deliver(dctx, &error_code, &error) == 0) { if (dctx->dest_mail != NULL) { i_assert(local->first_saved_mail == NULL); local->first_saved_mail = dctx->dest_mail; } smtp_server_recipient_reply(rcpt, 250, "2.0.0", "%s Saved", lldctx->session_id); return 0; } switch (error_code) { case MAIL_DELIVER_ERROR_NONE: i_unreached(); case MAIL_DELIVER_ERROR_TEMPORARY: smtp_server_recipient_reply(rcpt, 451, "4.2.0", "%s", error); break; case MAIL_DELIVER_ERROR_REJECTED: smtp_server_recipient_reply(rcpt, 552, "5.2.0", "%s", error); break; case MAIL_DELIVER_ERROR_NOQUOTA: lmtp_local_rcpt_reply_overquota(llrcpt, error); break; case MAIL_DELIVER_ERROR_INTERNAL: /* This shouldn't happen */ smtp_server_recipient_reply(rcpt, 451, "4.3.0", "%s", error); break; } return -1; } int lmtp_local_default_deliver(struct client *client, struct lmtp_recipient *lrcpt, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct smtp_server_transaction *trans, struct lmtp_local_deliver_context *lldctx) { struct lmtp_local *local = client->local; struct lmtp_local_recipient *llrcpt = lrcpt->backend_context; struct smtp_server_recipient *rcpt = lrcpt->rcpt; struct smtp_address *rcpt_to = rcpt->path; struct mail_deliver_input dinput; struct mail_deliver_context dctx; struct event *event; int ret; event = event_create(rcpt->event); event_drop_parent_log_prefixes(event, 3); i_zero(&dinput); dinput.session = lldctx->session; dinput.set = lldctx->lda_set; dinput.smtp_set = lldctx->smtp_set; dinput.session_id = lldctx->session_id; dinput.event_parent = event; dinput.src_mail = lldctx->src_mail; /* MAIL FROM */ dinput.mail_from = trans->mail_from; dinput.mail_params = trans->params; /* RCPT TO */ dinput.rcpt_user = lldctx->rcpt_user; dinput.rcpt_params = rcpt->params; if (dinput.rcpt_params.orcpt.addr == NULL && *dinput.set->lda_original_recipient_header != '\0') { dinput.rcpt_params.orcpt.addr = mail_deliver_get_address( lldctx->src_mail, dinput.set->lda_original_recipient_header); } if (dinput.rcpt_params.orcpt.addr == NULL) dinput.rcpt_params.orcpt.addr = rcpt_to; dinput.rcpt_to = rcpt_to; dinput.rcpt_default_mailbox = lldctx->rcpt_default_mailbox; dinput.save_dest_mail = array_count(&trans->rcpt_to) > 1 && local->first_saved_mail == NULL; dinput.session_time_msecs = timeval_diff_msecs(&client->state.data_end_timeval, &trans->timestamp); dinput.delivery_time_started = lldctx->delivery_time_started; mail_deliver_init(&dctx, &dinput); ret = lmtp_local_default_do_deliver(local, llrcpt, lldctx, &dctx); mail_deliver_deinit(&dctx); event_unref(&event); return ret; } static uid_t lmtp_local_deliver_to_rcpts(struct lmtp_local *local, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans, struct mail_deliver_session *session) { struct client *client = local->client; uid_t first_uid = (uid_t)-1; struct mail *src_mail; struct lmtp_local_recipient *const *llrcpts; unsigned int count, i; int ret; src_mail = local->raw_mail; llrcpts = array_get(&local->rcpt_to, &count); for (i = 0; i < count; i++) { struct lmtp_local_recipient *llrcpt = llrcpts[i]; struct smtp_server_recipient *rcpt = llrcpt->rcpt->rcpt; if (llrcpt->duplicate != NULL) { struct smtp_server_recipient *drcpt = llrcpt->duplicate->rcpt->rcpt; /* don't deliver more than once to the same recipient */ smtp_server_reply_submit_duplicate(cmd, rcpt->index, drcpt->index); continue; } ret = lmtp_local_deliver(local, cmd, trans, llrcpt, src_mail, session); client_update_data_state(client, NULL); /* succeeded and mail_user is not saved in first_saved_mail */ if ((ret == 0 && (local->first_saved_mail == NULL || local->first_saved_mail == src_mail)) || /* failed. try the next one. */ (ret != 0 && local->rcpt_user != NULL)) { if (i == (count - 1)) mail_user_autoexpunge(local->rcpt_user); mail_storage_service_io_deactivate_user(local->rcpt_user->_service_user); mail_user_deinit(&local->rcpt_user); } else if (ret == 0) { /* use the first saved message to save it elsewhere too. this might allow hard linking the files. mail_user is saved in first_saved_mail, will be unreferenced later on */ mail_storage_service_io_deactivate_user(local->rcpt_user->_service_user); local->rcpt_user = NULL; src_mail = local->first_saved_mail; first_uid = geteuid(); i_assert(first_uid != 0); } else if (local->rcpt_user != NULL) { mail_storage_service_io_deactivate_user(local->rcpt_user->_service_user); } } return first_uid; } static int lmtp_local_open_raw_mail(struct lmtp_local *local, struct smtp_server_transaction *trans, struct istream *input) { static const char *wanted_headers[] = { "From", "To", "Message-ID", "Subject", "Return-Path", NULL }; struct client *client = local->client; struct mailbox *box; struct mailbox_transaction_context *mtrans; struct mailbox_header_lookup_ctx *headers_ctx; enum mail_error error; if (raw_mailbox_alloc_stream(client->raw_mail_user, input, (time_t)-1, smtp_address_encode(trans->mail_from), &box) < 0) { e_error(client->event, "Can't open delivery mail as raw: %s", mailbox_get_last_internal_error(box, &error)); mailbox_free(&box); lmtp_local_rcpt_fail_all(local, 451, "4.3.0", "Temporary internal error"); return -1; } mtrans = mailbox_transaction_begin(box, 0, __func__); headers_ctx = mailbox_header_lookup_init(box, wanted_headers); local->raw_mail = mail_alloc(mtrans, 0, headers_ctx); mailbox_header_lookup_unref(&headers_ctx); mail_set_seq(local->raw_mail, 1); return 0; } void lmtp_local_data(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans, struct istream *input) { struct lmtp_local *local = client->local; struct mail_deliver_session *session; uid_t old_uid, first_uid; if (lmtp_local_open_raw_mail(local, trans, input) < 0) return; session = mail_deliver_session_init(); old_uid = geteuid(); first_uid = lmtp_local_deliver_to_rcpts(local, cmd, trans, session); mail_deliver_session_deinit(&session); if (local->first_saved_mail != NULL) { struct mail *mail = local->first_saved_mail; struct mailbox_transaction_context *trans = mail->transaction; struct mailbox *box = trans->box; struct mail_user *user = box->storage->user; /* just in case these functions are going to write anything, change uid back to user's own one */ if (first_uid != old_uid) { if (seteuid(0) < 0) i_fatal("seteuid(0) failed: %m"); if (seteuid(first_uid) < 0) i_fatal("seteuid() failed: %m"); } mail_storage_service_io_activate_user(user->_service_user); mail_free(&mail); mailbox_transaction_rollback(&trans); mailbox_free(&box); mail_user_autoexpunge(user); mail_storage_service_io_deactivate_user(user->_service_user); mail_user_deinit(&user); } if (old_uid == 0) { /* switch back to running as root, since that's what we're practically doing anyway. it's also important in case we lose e.g. config connection and need to reconnect to it. */ if (seteuid(0) < 0) i_fatal("seteuid(0) failed: %m"); /* enable core dumping again. we need to chdir also to root-owned directory to get core dumps. */ restrict_access_allow_coredumps(TRUE); if (chdir(base_dir) < 0) { e_error(client->event, "chdir(%s) failed: %m", base_dir); } } } dovecot-2.3.21.1/src/lmtp/Makefile.in0000644000000000000000000007267114656633611014130 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ pkglibexec_PROGRAMS = lmtp$(EXEEXT) subdir = src/lmtp ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(pkginc_lib_HEADERS) $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(pkglibexecdir)" \ "$(DESTDIR)$(pkginc_libdir)" PROGRAMS = $(pkglibexec_PROGRAMS) am_lmtp_OBJECTS = main.$(OBJEXT) lmtp-client.$(OBJEXT) \ lmtp-commands.$(OBJEXT) lmtp-recipient.$(OBJEXT) \ lmtp-local.$(OBJEXT) lmtp-proxy.$(OBJEXT) \ lmtp-settings.$(OBJEXT) lmtp_OBJECTS = $(am_lmtp_OBJECTS) am__DEPENDENCIES_1 = AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = lmtp_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(lmtp_LDFLAGS) $(LDFLAGS) -o $@ AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/lmtp-client.Po \ ./$(DEPDIR)/lmtp-commands.Po ./$(DEPDIR)/lmtp-local.Po \ ./$(DEPDIR)/lmtp-proxy.Po ./$(DEPDIR)/lmtp-recipient.Po \ ./$(DEPDIR)/lmtp-settings.Po ./$(DEPDIR)/main.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(lmtp_SOURCES) DIST_SOURCES = $(lmtp_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ *) f=$$p;; \ esac; am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; am__install_max = 40 am__nobase_strip_setup = \ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` am__nobase_strip = \ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" am__nobase_list = $(am__nobase_strip_setup); \ for p in $$list; do echo "$$p $$p"; done | \ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ if (++n[$$2] == $(am__install_max)) \ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ END { for (dir in files) print dir, files[dir] }' am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' am__uninstall_files_from_dir = { \ test -z "$$files" \ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ $(am__cd) "$$dir" && rm -f $$files; }; \ } HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) pkglibexecdir = $(libexecdir)/dovecot ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-smtp \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-lda \ -I$(top_srcdir)/src/lib-ssl-iostream \ -I$(top_srcdir)/src/lib-storage \ -I$(top_srcdir)/src/lib-storage/index \ -I$(top_srcdir)/src/lib-storage/index/raw \ -DMODULEDIR=\""$(moduledir)"\" \ $(BINARY_CFLAGS) lmtp_LDFLAGS = -export-dynamic \ $(BINARY_LDFLAGS) lmtp_LDADD = \ $(LIBDOVECOT_LDA) \ $(LIBDOVECOT_STORAGE) \ $(LIBDOVECOT) lmtp_DEPENDENCIES = \ $(LIBDOVECOT_LDA) \ $(LIBDOVECOT_STORAGE_DEPS) \ $(LIBDOVECOT_DEPS) lmtp_SOURCES = \ main.c \ lmtp-client.c \ lmtp-commands.c \ lmtp-recipient.c \ lmtp-local.c \ lmtp-proxy.c \ lmtp-settings.c noinst_HEADERS = \ lmtp-local.h \ lmtp-proxy.h headers = \ lmtp-common.h \ lmtp-commands.h \ lmtp-recipient.h \ lmtp-client.h \ lmtp-settings.h pkginc_libdir = $(pkgincludedir) pkginc_lib_HEADERS = $(headers) all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lmtp/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/lmtp/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files clean-pkglibexecPROGRAMS: @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list lmtp$(EXEEXT): $(lmtp_OBJECTS) $(lmtp_DEPENDENCIES) $(EXTRA_lmtp_DEPENDENCIES) @rm -f lmtp$(EXEEXT) $(AM_V_CCLD)$(lmtp_LINK) $(lmtp_OBJECTS) $(lmtp_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmtp-client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmtp-commands.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmtp-local.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmtp-proxy.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmtp-recipient.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmtp-settings.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) @$(NORMAL_INSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ done uninstall-pkginc_libHEADERS: @$(NORMAL_UNINSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am check: check-am all-am: Makefile $(PROGRAMS) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(pkginc_libdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-pkglibexecPROGRAMS \ mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/lmtp-client.Po -rm -f ./$(DEPDIR)/lmtp-commands.Po -rm -f ./$(DEPDIR)/lmtp-local.Po -rm -f ./$(DEPDIR)/lmtp-proxy.Po -rm -f ./$(DEPDIR)/lmtp-recipient.Po -rm -f ./$(DEPDIR)/lmtp-settings.Po -rm -f ./$(DEPDIR)/main.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-pkginc_libHEADERS install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-pkglibexecPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/lmtp-client.Po -rm -f ./$(DEPDIR)/lmtp-commands.Po -rm -f ./$(DEPDIR)/lmtp-local.Po -rm -f ./$(DEPDIR)/lmtp-proxy.Po -rm -f ./$(DEPDIR)/lmtp-recipient.Po -rm -f ./$(DEPDIR)/lmtp-settings.Po -rm -f ./$(DEPDIR)/main.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkginc_libHEADERS uninstall-pkglibexecPROGRAMS .MAKE: install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ clean-generic clean-libtool clean-pkglibexecPROGRAMS \ cscopelist-am ctags ctags-am distclean distclean-compile \ distclean-generic distclean-libtool distclean-tags distdir dvi \ dvi-am html html-am info info-am install install-am \ install-data install-data-am install-dvi install-dvi-am \ install-exec install-exec-am install-html install-html-am \ install-info install-info-am install-man install-pdf \ install-pdf-am install-pkginc_libHEADERS \ install-pkglibexecPROGRAMS install-ps install-ps-am \ install-strip installcheck installcheck-am installdirs \ maintainer-clean maintainer-clean-generic mostlyclean \ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ uninstall-pkginc_libHEADERS uninstall-pkglibexecPROGRAMS .PRECIOUS: Makefile # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/lmtp/lmtp-local.h0000644000000000000000000000165514656633576014304 00000000000000#ifndef LMTP_LOCAL_H #define LMTP_LOCAL_H #include "net.h" struct mail_deliver_session; struct smtp_server_cmd_ctx; struct smtp_server_cmd_rcpt; struct lmtp_local; struct client; void lmtp_local_deinit(struct lmtp_local **_local); int lmtp_local_rcpt(struct client *client, struct smtp_server_cmd_ctx *cmd, struct lmtp_recipient *lrcpt, const char *username, const char *detail); void lmtp_local_add_headers(struct lmtp_local *local, struct smtp_server_transaction *trans, string_t *headers); int lmtp_local_default_deliver(struct client *client, struct lmtp_recipient *lrcpt, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans, struct lmtp_local_deliver_context *lldctx); void lmtp_local_data(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans, struct istream *input); #endif dovecot-2.3.21.1/src/lmtp/lmtp-commands.c0000644000000000000000000002237114656633576015004 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lmtp-common.h" #include "str.h" #include "istream.h" #include "istream-concat.h" #include "ostream.h" #include "iostream-temp.h" #include "master-service.h" #include "settings-parser.h" #include "lda-settings.h" #include "mail-user.h" #include "smtp-address.h" #include "mail-deliver.h" #include "mail-error.h" #include "lmtp-recipient.h" #include "lmtp-proxy.h" #include "lmtp-local.h" #include "lmtp-commands.h" /* * MAIL command */ int cmd_mail(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_mail *data) { struct client *client = (struct client *)conn_ctx; return client->v.cmd_mail(client, cmd, data); } int client_default_cmd_mail(struct client *client, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct smtp_server_cmd_mail *data ATTR_UNUSED) { if (client->lmtp_set->lmtp_user_concurrency_limit > 0) { /* Connect to anvil before dropping privileges */ lmtp_anvil_init(); } return 1; } /* * RCPT command */ static int cmd_rcpt_handle_forward_fields(struct smtp_server_cmd_ctx *cmd, struct lmtp_recipient *lrcpt) { struct smtp_server_recipient *rcpt = lrcpt->rcpt; string_t *xforward; const char *error; int ret; ret = smtp_params_rcpt_decode_extra(&rcpt->params, LMTP_RCPT_FORWARD_PARAMETER, &xforward, FALSE, &error); if (ret < 0) { smtp_server_reply(cmd, 501, "5.5.4", "Invalid "LMTP_RCPT_FORWARD_PARAMETER"= " "parameter: %s", error); return -1; } if (ret == 0) return 0; /* Drop the parameter */ (void)smtp_params_rcpt_drop_extra(&rcpt->params, LMTP_RCPT_FORWARD_PARAMETER, NULL); /* Check the real IP rather than the proxied client IP, since XCLIENT command will update that, thereby making it untrusted. Unlike the XCLIENT command, the RCPT forward parameter needs to be used after the XCLIENT is first issued. */ if (!smtp_server_connection_is_trusted(rcpt->conn)) { smtp_server_reply(cmd, 550, "5.7.14", "Unacceptable "LMTP_RCPT_FORWARD_PARAMETER"= " "parameter: You are not from trusted IP"); return -1; } lrcpt->forward_fields = p_strdup(rcpt->pool, str_c(xforward)); return 0; } int cmd_rcpt(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_recipient *rcpt) { struct client *client = (struct client *)conn_ctx; struct smtp_server_transaction *trans; struct lmtp_recipient *lrcpt; trans = smtp_server_connection_get_transaction(rcpt->conn); i_assert(trans != NULL); /* MAIL command is synchronous */ lrcpt = lmtp_recipient_create(client, trans, rcpt); if (cmd_rcpt_handle_forward_fields(cmd, lrcpt) < 0) return -1; return client->v.cmd_rcpt(client, cmd, lrcpt); } int client_default_cmd_rcpt(struct client *client, struct smtp_server_cmd_ctx *cmd, struct lmtp_recipient *lrcpt) { struct smtp_server_recipient *rcpt = lrcpt->rcpt; const char *username, *detail; char delim = '\0'; int ret; i_assert(!smtp_address_isnull(rcpt->path)); if (*rcpt->path->localpart == '\0' && rcpt->path->domain == NULL) { smtp_server_recipient_reply( rcpt, 550, "5.1.1", "Unacceptable TO: Empty path not allowed"); return -1; } smtp_address_detail_parse_temp( client->unexpanded_lda_set->recipient_delimiter, rcpt->path, &username, &delim, &detail); i_assert(*username != '\0'); /* Make user name and detail available in the recipient event. The mail_user event (for local delivery) also adds the user field, but adding it here makes it available to the recipient event in general. Additionally, the auth lookups performed for local and proxy delivery can further override the "user" recipient event when the auth service returns a different user name. In any case, we provide the initial value here. */ event_add_str(rcpt->event, "user", username); if (detail[0] != '\0') event_add_str(rcpt->event, "detail", detail); if (client->lmtp_set->lmtp_proxy) { /* proxied? */ if ((ret=lmtp_proxy_rcpt(client, cmd, lrcpt, username, detail, delim)) != 0) return (ret < 0 ? -1 : 0); /* no */ } /* local delivery */ return lmtp_local_rcpt(client, cmd, lrcpt, username, detail); } /* * DATA command */ static void cmd_data_create_added_headers(struct client *client, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct smtp_server_transaction *trans) { size_t proxy_offset = 0; string_t *str; str = t_str_new(512); /* Headers for local deliveries only */ if (client->local != NULL) lmtp_local_add_headers(client->local, trans, str); /* Headers for local and proxied messages */ proxy_offset = str_len(str); if (client->lmtp_set->lmtp_add_received_header) { const struct lmtp_settings *lmtp_set = client->lmtp_set; enum smtp_server_trace_rcpt_to_address rcpt_to_address = SMTP_SERVER_TRACE_RCPT_TO_ADDRESS_FINAL; switch (lmtp_set->parsed_lmtp_hdr_delivery_address) { case LMTP_HDR_DELIVERY_ADDRESS_NONE: rcpt_to_address = SMTP_SERVER_TRACE_RCPT_TO_ADDRESS_NONE; break; case LMTP_HDR_DELIVERY_ADDRESS_FINAL: rcpt_to_address = SMTP_SERVER_TRACE_RCPT_TO_ADDRESS_FINAL; break; case LMTP_HDR_DELIVERY_ADDRESS_ORIGINAL: rcpt_to_address = SMTP_SERVER_TRACE_RCPT_TO_ADDRESS_ORIGINAL; break; } smtp_server_transaction_write_trace_record( str, trans, rcpt_to_address); } client->state.added_headers_local = p_strdup(client->state_pool, str_c(str)); client->state.added_headers_proxy = client->state.added_headers_local + proxy_offset; } static int cmd_data_finish(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans) { struct client_state *state = &client->state; struct istream *input_msg; int ret; i_assert(HAS_ALL_BITS(trans->flags, SMTP_SERVER_TRANSACTION_FLAG_REPLY_PER_RCPT)); client->state.data_end_timeval = ioloop_timeval; /* finish the message */ input_msg = iostream_temp_finish(&state->mail_data_output, IO_BLOCK_SIZE); ret = client->v.cmd_data(client, cmd, trans, input_msg, client->state.data_size); i_stream_unref(&input_msg); return ret; } int cmd_data_continue(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans) { struct client *client = (struct client *)conn_ctx; struct client_state *state = &client->state; struct istream *data_input = state->data_input; const unsigned char *data; size_t size; ssize_t ret; i_assert(state->mail_data_output != NULL); while ((ret = i_stream_read(data_input)) > 0 || ret == -2) { data = i_stream_get_data(data_input, &size); if (o_stream_send(state->mail_data_output, data, size) != (ssize_t)size) { e_error(client->event, "write(%s) failed: %s", o_stream_get_name(state->mail_data_output), o_stream_get_error(state->mail_data_output)); smtp_server_reply(cmd, 451, "4.3.0", "Temporary internal failure"); return -1; } i_stream_skip(data_input, size); if (!smtp_server_cmd_data_check_size(cmd)) return -1; } if (ret == 0) return 0; if (ret < 0 && data_input->stream_errno != 0) { /* Client probably disconnected */ return -1; } /* Current data stream position is the data size */ client->state.data_size = data_input->v_offset; /* The ending "." line was seen. finish delivery. */ return cmd_data_finish(client, cmd, trans); } int cmd_data_begin(void *conn_ctx, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct smtp_server_transaction *trans ATTR_UNUSED, struct istream *data_input) { struct client *client = (struct client *)conn_ctx; string_t *path; i_assert(client->state.mail_data_output == NULL); path = t_str_new(256); mail_user_set_get_temp_prefix(path, client->raw_mail_user->set); client->state.mail_data_output = iostream_temp_create_named(str_c(path), 0, "(lmtp data)"); client->state.data_input = data_input; return 0; } int client_default_cmd_data(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans, struct istream *data_input, uoff_t data_size ATTR_UNUSED) { struct client_state *state = &client->state; struct istream *input_local, *input_proxy; struct istream *inputs[3]; /* Formulate prepended headers for both local and proxy delivery */ cmd_data_create_added_headers(client, cmd, trans); /* Construct message streams for local and proxy delivery */ input_local = input_proxy = NULL; if (client->local != NULL) { inputs[0] = i_stream_create_from_data( state->added_headers_local, strlen(state->added_headers_local)); inputs[1] = data_input; inputs[2] = NULL; input_local = i_stream_create_concat(inputs); i_stream_set_name(input_local, ""); i_stream_unref(&inputs[0]); } if (client->proxy != NULL) { inputs[0] = i_stream_create_from_data( state->added_headers_proxy, strlen(state->added_headers_proxy)); inputs[1] = data_input; inputs[2] = NULL; input_proxy = i_stream_create_concat(inputs); i_stream_set_name(input_proxy, ""); i_stream_unref(&inputs[0]); } /* local delivery */ if (client->local != NULL) { lmtp_local_data(client, cmd, trans, input_local); i_stream_unref(&input_local); } /* proxy delivery */ if (client->proxy != NULL) { lmtp_proxy_data(client, cmd, trans, input_proxy); i_stream_unref(&input_proxy); } return 0; } dovecot-2.3.21.1/src/lmtp/lmtp-proxy.c0000644000000000000000000006053514656633576014370 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lmtp-common.h" #include "istream.h" #include "istream-sized.h" #include "ostream.h" #include "iostream-ssl.h" #include "str.h" #include "strescape.h" #include "time-util.h" #include "smtp-common.h" #include "smtp-params.h" #include "smtp-address.h" #include "smtp-client.h" #include "smtp-client-connection.h" #include "smtp-client-transaction.h" #include "auth-master.h" #include "master-service-ssl-settings.h" #include "mail-storage-service.h" #include "lda-settings.h" #include "lmtp-recipient.h" #include "lmtp-proxy.h" #define LMTP_MAX_REPLY_SIZE 4096 #define LMTP_PROXY_DEFAULT_TIMEOUT_MSECS (1000*125) enum lmtp_proxy_ssl_flags { /* Use SSL/TLS enabled */ PROXY_SSL_FLAG_YES = 0x01, /* Don't do SSL handshake immediately after connected */ PROXY_SSL_FLAG_STARTTLS = 0x02, /* Don't require that the received certificate is valid */ PROXY_SSL_FLAG_ANY_CERT = 0x04 }; struct lmtp_proxy_rcpt_settings { enum smtp_protocol protocol; const char *host; struct ip_addr hostip, source_ip; in_port_t port; enum lmtp_proxy_ssl_flags ssl_flags; unsigned int timeout_msecs; struct smtp_params_rcpt params; bool proxy_not_trusted:1; }; struct lmtp_proxy_recipient { struct lmtp_recipient *rcpt; struct lmtp_proxy_connection *conn; struct smtp_address *address; const unsigned char *forward_fields; size_t forward_fields_size; bool rcpt_to_failed:1; bool data_reply_received:1; }; struct lmtp_proxy_connection { struct lmtp_proxy *proxy; struct lmtp_proxy_rcpt_settings set; char *host; struct smtp_client_connection *lmtp_conn; struct smtp_client_transaction *lmtp_trans; struct istream *data_input; struct timeout *to; bool finished:1; bool failed:1; }; struct lmtp_proxy { struct client *client; struct smtp_server_transaction *trans; struct smtp_client *lmtp_client; ARRAY(struct lmtp_proxy_connection *) connections; ARRAY(struct lmtp_proxy_recipient *) rcpt_to; unsigned int next_data_reply_idx; struct timeout *to_finish; struct istream *data_input; unsigned int max_timeout_msecs; unsigned int proxy_session_seq; bool finished:1; }; static void lmtp_proxy_data_cb(const struct smtp_reply *reply, struct lmtp_proxy_recipient *lprcpt); /* * LMTP proxy */ static struct lmtp_proxy * lmtp_proxy_init(struct client *client, struct smtp_server_transaction *trans) { const char *extra_capabilities[] = { LMTP_RCPT_FORWARD_CAPABILITY, NULL }; struct smtp_client_settings lmtp_set; struct lmtp_proxy *proxy; proxy = i_new(struct lmtp_proxy, 1); proxy->client = client; proxy->trans = trans; i_array_init(&proxy->rcpt_to, 32); i_array_init(&proxy->connections, 32); i_zero(&lmtp_set); lmtp_set.my_hostname = client->my_domain; lmtp_set.extra_capabilities = extra_capabilities; lmtp_set.dns_client_socket_path = dns_client_socket_path; lmtp_set.max_reply_size = LMTP_MAX_REPLY_SIZE; lmtp_set.rawlog_dir = client->lmtp_set->lmtp_proxy_rawlog_dir; smtp_server_connection_get_proxy_data(client->conn, &lmtp_set.proxy_data); lmtp_set.proxy_data.source_ip = client->remote_ip; lmtp_set.proxy_data.source_port = client->remote_port; /* This initial session_id is used only locally by lib-smtp. Each LMTP proxy connection gets a more specific updated session_id. */ lmtp_set.proxy_data.session = trans->id; if (lmtp_set.proxy_data.ttl_plus_1 == 0) lmtp_set.proxy_data.ttl_plus_1 = LMTP_PROXY_DEFAULT_TTL + 1; else lmtp_set.proxy_data.ttl_plus_1--; lmtp_set.event_parent = client->event; proxy->lmtp_client = smtp_client_init(&lmtp_set); return proxy; } static void lmtp_proxy_connection_deinit(struct lmtp_proxy_connection *conn) { if (conn->lmtp_trans != NULL) smtp_client_transaction_destroy(&conn->lmtp_trans); if (conn->lmtp_conn != NULL) smtp_client_connection_close(&conn->lmtp_conn); timeout_remove(&conn->to); i_stream_unref(&conn->data_input); i_free(conn->host); i_free(conn); } void lmtp_proxy_deinit(struct lmtp_proxy **_proxy) { struct lmtp_proxy *proxy = *_proxy; struct lmtp_proxy_connection *conn; *_proxy = NULL; array_foreach_elem(&proxy->connections, conn) lmtp_proxy_connection_deinit(conn); smtp_client_deinit(&proxy->lmtp_client); i_stream_unref(&proxy->data_input); timeout_remove(&proxy->to_finish); array_free(&proxy->rcpt_to); array_free(&proxy->connections); i_free(proxy); } static void lmtp_proxy_mail_cb(const struct smtp_reply *proxy_reply ATTR_UNUSED, struct lmtp_proxy_connection *conn ATTR_UNUSED) { /* nothing */ } static void lmtp_proxy_connection_finish(struct lmtp_proxy_connection *conn) { conn->finished = TRUE; conn->lmtp_trans = NULL; } static void lmtp_proxy_connection_init_ssl(struct lmtp_proxy_connection *conn, struct ssl_iostream_settings *ssl_set_r, enum smtp_client_connection_ssl_mode *ssl_mode_r) { const struct master_service_ssl_settings *master_ssl_set; *ssl_mode_r = SMTP_CLIENT_SSL_MODE_NONE; if ((conn->set.ssl_flags & PROXY_SSL_FLAG_YES) == 0) { i_zero(ssl_set_r); return; } master_ssl_set = master_service_ssl_settings_get(master_service); master_service_ssl_client_settings_to_iostream_set( master_ssl_set, pool_datastack_create(), ssl_set_r); if ((conn->set.ssl_flags & PROXY_SSL_FLAG_ANY_CERT) != 0) ssl_set_r->allow_invalid_cert = TRUE; if ((conn->set.ssl_flags & PROXY_SSL_FLAG_STARTTLS) == 0) *ssl_mode_r = SMTP_CLIENT_SSL_MODE_IMMEDIATE; else *ssl_mode_r = SMTP_CLIENT_SSL_MODE_STARTTLS; } static bool lmtp_proxy_connection_has_rcpt_forward(struct lmtp_proxy_connection *conn) { const struct smtp_capability_extra *cap_extra = smtp_client_connection_get_extra_capability( conn->lmtp_conn, LMTP_RCPT_FORWARD_CAPABILITY); return (cap_extra != NULL); } static struct lmtp_proxy_connection * lmtp_proxy_get_connection(struct lmtp_proxy *proxy, const struct lmtp_proxy_rcpt_settings *set) { static const char *rcpt_param_extensions[] = { LMTP_RCPT_FORWARD_PARAMETER, NULL }; static const struct smtp_client_capability_extra cap_rcpt_forward = { .name = LMTP_RCPT_FORWARD_CAPABILITY, .rcpt_param_extensions = rcpt_param_extensions, }; struct smtp_client_settings lmtp_set; struct smtp_server_transaction *trans = proxy->trans; struct client *client = proxy->client; struct lmtp_proxy_connection *conn; enum smtp_client_connection_ssl_mode ssl_mode; struct ssl_iostream_settings ssl_set; i_assert(set->timeout_msecs > 0); array_foreach_elem(&proxy->connections, conn) { if (conn->set.protocol == set->protocol && conn->set.port == set->port && strcmp(conn->set.host, set->host) == 0 && net_ip_compare(&conn->set.hostip, &set->hostip) && net_ip_compare(&conn->set.source_ip, &set->source_ip) && conn->set.ssl_flags == set->ssl_flags) return conn; } conn = i_new(struct lmtp_proxy_connection, 1); conn->proxy = proxy; conn->set.protocol = set->protocol; conn->set.hostip = set->hostip; conn->host = i_strdup(set->host); conn->set.host = conn->host; conn->set.source_ip = set->source_ip; conn->set.port = set->port; conn->set.ssl_flags = set->ssl_flags; conn->set.timeout_msecs = set->timeout_msecs; array_push_back(&proxy->connections, &conn); lmtp_proxy_connection_init_ssl(conn, &ssl_set, &ssl_mode); i_zero(&lmtp_set); lmtp_set.my_ip = conn->set.source_ip; lmtp_set.ssl = &ssl_set; lmtp_set.peer_trusted = !conn->set.proxy_not_trusted; lmtp_set.forced_capabilities = SMTP_CAPABILITY__ORCPT; lmtp_set.mail_send_broken_path = TRUE; lmtp_set.verbose_user_errors = client->lmtp_set->lmtp_verbose_replies; if (conn->set.hostip.family != 0) { conn->lmtp_conn = smtp_client_connection_create_ip( proxy->lmtp_client, set->protocol, &conn->set.hostip, conn->set.port, conn->set.host, ssl_mode, &lmtp_set); } else { conn->lmtp_conn = smtp_client_connection_create( proxy->lmtp_client, set->protocol, conn->set.host, conn->set.port, ssl_mode, &lmtp_set); } struct smtp_proxy_data proxy_data = { .session = t_strdup_printf("%s:P%u", proxy->trans->id, ++proxy->proxy_session_seq), }; smtp_client_connection_update_proxy_data(conn->lmtp_conn, &proxy_data); smtp_client_connection_accept_extra_capability(conn->lmtp_conn, &cap_rcpt_forward); smtp_client_connection_connect(conn->lmtp_conn, NULL, NULL); conn->lmtp_trans = smtp_client_transaction_create( conn->lmtp_conn, trans->mail_from, &trans->params, 0, lmtp_proxy_connection_finish, conn); smtp_client_transaction_start(conn->lmtp_trans, lmtp_proxy_mail_cb, conn); if (proxy->max_timeout_msecs < set->timeout_msecs) proxy->max_timeout_msecs = set->timeout_msecs; return conn; } static void lmtp_proxy_handle_connection_error(struct lmtp_proxy_recipient *lprcpt, const struct smtp_reply *reply) { struct lmtp_recipient *lrcpt = lprcpt->rcpt; struct client *client = lrcpt->client; struct smtp_server_recipient *rcpt = lrcpt->rcpt; const char *detail = ""; if (client->lmtp_set->lmtp_verbose_replies) { smtp_server_command_fail(rcpt->cmd->cmd, 451, "4.4.0", "Proxy failed: %s (session=%s)", smtp_reply_log(reply), lrcpt->session_id); return; } switch (reply->status) { case SMTP_CLIENT_COMMAND_ERROR_ABORTED: break; case SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED: detail = "DNS lookup, "; break; case SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED: case SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED: detail = "connect, "; break; case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST: case SMTP_CLIENT_COMMAND_ERROR_CONNECTION_CLOSED: detail = "connection lost, "; break; case SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY: detail = "bad reply, "; break; case SMTP_CLIENT_COMMAND_ERROR_TIMED_OUT: detail = "timed out, "; break; default: break; } smtp_server_command_fail(rcpt->cmd->cmd, 451, "4.4.0", "Proxy failed (%ssession=%s)", detail, lrcpt->session_id); } static bool lmtp_proxy_handle_reply(struct lmtp_proxy_recipient *lprcpt, const struct smtp_reply *reply, struct smtp_reply *reply_r) { *reply_r = *reply; if (!smtp_reply_is_remote(reply) || reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECTION_CLOSED) { lmtp_proxy_handle_connection_error(lprcpt, reply); return FALSE; } if (!smtp_reply_has_enhanced_code(reply)) { reply_r->enhanced_code = SMTP_REPLY_ENH_CODE(reply->status / 100, 0, 0); } return TRUE; } /* * RCPT command */ static bool lmtp_proxy_rcpt_parse_fields(struct lmtp_recipient *lrcpt, struct lmtp_proxy_rcpt_settings *set, const char *const *args, const char **address) { struct smtp_server_recipient *rcpt = lrcpt->rcpt; const char *p, *key, *value; bool proxying = FALSE, port_set = FALSE; for (; *args != NULL; args++) { p = strchr(*args, '='); if (p == NULL) { key = *args; value = ""; } else { key = t_strdup_until(*args, p); value = p + 1; } if (strcmp(key, "proxy") == 0) proxying = TRUE; else if (strcmp(key, "host") == 0) set->host = value; else if (strcmp(key, "hostip") == 0) { if (net_addr2ip(value, &set->hostip) < 0) { e_error(rcpt->event, "proxy: Invalid hostip %s", value); return FALSE; } } else if (strcmp(key, "source_ip") == 0) { if (net_addr2ip(value, &set->source_ip) < 0) { e_error(rcpt->event, "proxy: Invalid source_ip %s", value); return FALSE; } } else if (strcmp(key, "port") == 0) { if (net_str2port(value, &set->port) < 0) { e_error(rcpt->event, "proxy: Invalid port number %s", value); return FALSE; } port_set = TRUE; } else if (strcmp(key, "proxy_timeout") == 0) { if (str_to_uint(value, &set->timeout_msecs) < 0) { e_error(rcpt->event,"proxy: " "Invalid proxy_timeout value %s", value); return FALSE; } set->timeout_msecs *= 1000; } else if (strcmp(key, "proxy_not_trusted") == 0) { set->proxy_not_trusted = TRUE; } else if (strcmp(key, "protocol") == 0) { if (strcmp(value, "lmtp") == 0) { set->protocol = SMTP_PROTOCOL_LMTP; if (!port_set) set->port = 24; } else if (strcmp(value, "smtp") == 0) { set->protocol = SMTP_PROTOCOL_SMTP; if (!port_set) set->port = 25; } else { e_error(rcpt->event, "proxy: Unknown protocol %s", value); return FALSE; } } else if (strcmp(key, "ssl") == 0) { set->ssl_flags |= PROXY_SSL_FLAG_YES; if (strcmp(value, "any-cert") == 0) set->ssl_flags |= PROXY_SSL_FLAG_ANY_CERT; } else if (strcmp(key, "starttls") == 0) { set->ssl_flags |= PROXY_SSL_FLAG_YES | PROXY_SSL_FLAG_STARTTLS; if (strcmp(value, "any-cert") == 0) set->ssl_flags |= PROXY_SSL_FLAG_ANY_CERT; } else if (strcmp(key, "user") == 0 || strcmp(key, "destuser") == 0) { /* Changing the username */ *address = value; } else { /* Just ignore it */ } } if (proxying && set->host == NULL) { e_error(rcpt->event, "proxy: host not given"); return FALSE; } return proxying; } static bool lmtp_proxy_is_ourself(const struct client *client, const struct lmtp_proxy_rcpt_settings *set) { struct ip_addr ip; if (set->port != client->local_port) return FALSE; if (set->hostip.family != 0) ip = set->hostip; else { if (net_addr2ip(set->host, &ip) < 0) return FALSE; } if (!net_ip_compare(&ip, &client->local_ip)) return FALSE; return TRUE; } static void lmtp_proxy_rcpt_approved(struct smtp_server_recipient *rcpt ATTR_UNUSED, struct lmtp_proxy_recipient *lprcpt) { struct client *client = lprcpt->rcpt->client; /* Add to proxy recipients */ array_push_back(&client->proxy->rcpt_to, &lprcpt); } static void lmtp_proxy_rcpt_cb(const struct smtp_reply *proxy_reply, struct lmtp_proxy_recipient *lprcpt) { struct smtp_server_recipient *rcpt = lprcpt->rcpt->rcpt; struct smtp_reply reply; if (!lmtp_proxy_handle_reply(lprcpt, proxy_reply, &reply)) return; if (smtp_reply_is_success(proxy_reply)) { /* If backend accepts it, we accept it too */ /* The default 2.0.0 code won't do */ if (!smtp_reply_has_enhanced_code(proxy_reply)) reply.enhanced_code = SMTP_REPLY_ENH_CODE(2, 1, 0); } /* Forward reply */ smtp_server_recipient_reply_forward(rcpt, &reply); } static void lmtp_proxy_rcpt_login_cb(const struct smtp_reply *proxy_reply, void *context) { struct lmtp_proxy_recipient *lprcpt = context; struct lmtp_recipient *lrcpt = lprcpt->rcpt; struct lmtp_proxy_connection *conn = lprcpt->conn; struct smtp_server_recipient *rcpt = lrcpt->rcpt; struct smtp_reply reply; struct smtp_client_transaction_rcpt *relay_rcpt; struct smtp_params_rcpt *rcpt_params = &rcpt->params; bool add_orcpt_param = FALSE, add_xrcptforward_param = FALSE; pool_t param_pool; if (!lmtp_proxy_handle_reply(lprcpt, proxy_reply, &reply)) return; if (!smtp_reply_is_success(proxy_reply)) { smtp_server_recipient_reply_forward(rcpt, &reply); return; } /* Add an ORCPT parameter when passdb changed the username (and therefore the RCPT address changed) and there is no ORCPT parameter yet. */ if (!smtp_params_rcpt_has_orcpt(rcpt_params) && !smtp_address_equals(lprcpt->address, rcpt->path)) add_orcpt_param = TRUE; /* Add forward fields parameter when passdb returned forward_* fields */ if (lprcpt->forward_fields != NULL && lmtp_proxy_connection_has_rcpt_forward(conn)) add_xrcptforward_param = TRUE; /* Copy params when changes are pending */ param_pool = NULL; if (add_orcpt_param || add_xrcptforward_param) { param_pool = pool_datastack_create(); rcpt_params = p_new(param_pool, struct smtp_params_rcpt, 1); smtp_params_rcpt_copy(param_pool, rcpt_params, &rcpt->params); } /* Add ORCPT */ if (add_orcpt_param) { smtp_params_rcpt_set_orcpt(rcpt_params, param_pool, rcpt->path); } /* Add forward fields parameter */ if (add_xrcptforward_param) { smtp_params_rcpt_encode_extra( rcpt_params, param_pool, LMTP_RCPT_FORWARD_PARAMETER, lprcpt->forward_fields, lprcpt->forward_fields_size); } smtp_server_recipient_add_hook( rcpt, SMTP_SERVER_RECIPIENT_HOOK_APPROVED, lmtp_proxy_rcpt_approved, lprcpt); relay_rcpt = smtp_client_transaction_add_pool_rcpt( conn->lmtp_trans, rcpt->pool, lprcpt->address, rcpt_params, lmtp_proxy_rcpt_cb, lprcpt); smtp_client_transaction_rcpt_set_data_callback( relay_rcpt, lmtp_proxy_data_cb, lprcpt); } int lmtp_proxy_rcpt(struct client *client, struct smtp_server_cmd_ctx *cmd, struct lmtp_recipient *lrcpt, const char *username, const char *detail, char delim) { struct auth_master_connection *auth_conn; struct lmtp_proxy_rcpt_settings set; struct lmtp_proxy_connection *conn; struct smtp_server_recipient *rcpt = lrcpt->rcpt; struct lmtp_proxy_recipient *lprcpt; struct smtp_server_transaction *trans; struct smtp_address *address = rcpt->path; struct auth_user_info info; struct mail_storage_service_input input; const char *const *fields, *errstr, *orig_username = username; struct smtp_proxy_data proxy_data; struct smtp_address *user; string_t *fwfields; pool_t auth_pool; int ret; trans = smtp_server_connection_get_transaction(cmd->conn); i_assert(trans != NULL); /* MAIL command is synchronous */ i_zero(&input); input.module = input.service = "lmtp"; mail_storage_service_init_settings(storage_service, &input); i_zero(&info); info.service = master_service_get_name(master_service); info.local_ip = client->local_ip; info.real_local_ip = client->real_local_ip; info.remote_ip = client->remote_ip; info.real_remote_ip = client->real_remote_ip; info.local_port = client->local_port; info.real_local_port = client->real_local_port; info.remote_port = client->remote_port; info.real_remote_port = client->real_remote_port; info.forward_fields = lrcpt->forward_fields; // FIXME: make this async auth_pool = pool_alloconly_create("auth lookup", 1024); auth_conn = mail_storage_service_get_auth_conn(storage_service); ret = auth_master_pass_lookup(auth_conn, username, &info, auth_pool, &fields); if (ret <= 0) { errstr = (ret < 0 && fields[0] != NULL ? t_strdup(fields[0]) : "Temporary user lookup failure"); pool_unref(&auth_pool); if (ret < 0) { smtp_server_recipient_reply(rcpt, 451, "4.3.0", "%s", errstr); return -1; } else { /* User not found from passdb: revert to local delivery. */ return 0; } } i_zero(&set); set.port = client->local_port; set.protocol = SMTP_PROTOCOL_LMTP; set.timeout_msecs = LMTP_PROXY_DEFAULT_TIMEOUT_MSECS; if (!lmtp_proxy_rcpt_parse_fields(lrcpt, &set, fields, &username)) { /* Not proxying this user */ pool_unref(&auth_pool); return 0; } if (strcmp(username, orig_username) != 0) { /* The existing "user" event field is overridden with the new user name, while old username is available as "orig_user" */ event_add_str(rcpt->event, "user", username); event_add_str(rcpt->event, "original_user", orig_username); if (smtp_address_parse_username(pool_datastack_create(), username, &user, &errstr) < 0) { e_error(rcpt->event, "%s: " "Username `%s' returned by passdb lookup is not a valid SMTP address", orig_username, username); smtp_server_recipient_reply( rcpt, 550, "5.3.5", "Internal user lookup failure"); pool_unref(&auth_pool); return -1; } /* Username changed. change the address as well */ if (*detail == '\0') { address = user; } else { address = smtp_address_add_detail_temp( user, detail, delim); } } else if (lmtp_proxy_is_ourself(client, &set)) { e_error(rcpt->event, "Proxying to <%s> loops to itself", username); smtp_server_recipient_reply(rcpt, 554, "5.4.6", "Proxying loops to itself"); pool_unref(&auth_pool); return -1; } smtp_server_connection_get_proxy_data(cmd->conn, &proxy_data); if (proxy_data.ttl_plus_1 == 1) { e_error(rcpt->event, "Proxying to <%s> appears to be looping (TTL=0)", username); smtp_server_recipient_reply(rcpt, 554, "5.4.6", "Proxying appears to be looping " "(TTL=0)"); pool_unref(&auth_pool); return -1; } if (client->proxy == NULL) client->proxy = lmtp_proxy_init(client, trans); conn = lmtp_proxy_get_connection(client->proxy, &set); lprcpt = p_new(rcpt->pool, struct lmtp_proxy_recipient, 1); lprcpt->rcpt = lrcpt; lprcpt->address = smtp_address_clone(rcpt->pool, address); lprcpt->conn = conn; lrcpt->type = LMTP_RECIPIENT_TYPE_PROXY; lrcpt->backend_context = lprcpt; /* Copy forward fields returned from passdb */ fwfields = NULL; for (const char *const *ptr = fields; *ptr != NULL; ptr++) { if (strncasecmp(*ptr, "forward_", 8) != 0) continue; if (fwfields == NULL) fwfields = t_str_new(128); else str_append_c(fwfields, '\t'); str_append_tabescaped(fwfields, (*ptr) + 8); } if (fwfields != NULL) { lprcpt->forward_fields = p_memdup( rcpt->pool, str_data(fwfields), str_len(fwfields)); lprcpt->forward_fields_size = str_len(fwfields); } pool_unref(&auth_pool); smtp_client_connection_connect(conn->lmtp_conn, lmtp_proxy_rcpt_login_cb, lprcpt); return 1; } /* * DATA command */ static void lmtp_proxy_data_cb(const struct smtp_reply *proxy_reply, struct lmtp_proxy_recipient *lprcpt) { struct lmtp_proxy_connection *conn = lprcpt->conn; struct lmtp_recipient *lrcpt = lprcpt->rcpt; struct smtp_server_recipient *rcpt = lrcpt->rcpt; struct lmtp_proxy *proxy = conn->proxy; struct smtp_server_transaction *trans = proxy->trans; struct smtp_address *address = lprcpt->address; const struct smtp_client_transaction_times *times = smtp_client_transaction_get_times(conn->lmtp_trans); unsigned int rcpt_index = rcpt->index; struct smtp_reply reply; string_t *msg; /* Compose log message */ msg = t_str_new(128); str_printfa(msg, "<%s>: ", lrcpt->session_id); if (smtp_reply_is_success(proxy_reply)) str_append(msg, "Sent message to"); else str_append(msg, "Failed to send message to"); str_printfa(msg, " <%s> at %s:%u: %s (%u/%u at %u ms)", smtp_address_encode(address), conn->set.host, conn->set.port, smtp_reply_log(proxy_reply), rcpt_index + 1, array_count(&trans->rcpt_to), timeval_diff_msecs(&ioloop_timeval, ×->started)); /* Handle reply */ if (smtp_reply_is_success(proxy_reply)) { /* If backend accepts it, we accept it too */ e_info(rcpt->event, "%s", str_c(msg)); /* Substitute our own success message */ smtp_reply_printf(&reply, 250, "%s Saved", lrcpt->session_id); /* Do let the enhanced code through */ if (!smtp_reply_has_enhanced_code(proxy_reply)) reply.enhanced_code = SMTP_REPLY_ENH_CODE(2, 0, 0); else reply.enhanced_code = proxy_reply->enhanced_code; } else { if (smtp_reply_is_remote(proxy_reply)) { /* The problem isn't with the proxy, it's with the remote side. so the remote side will log an error, while for us this is just an info event */ e_info(rcpt->event, "%s", str_c(msg)); } else { e_error(rcpt->event, "%s", str_c(msg)); } if (!lmtp_proxy_handle_reply(lprcpt, proxy_reply, &reply)) return; } /* Forward reply */ smtp_server_recipient_reply_forward(rcpt, &reply); } static void lmtp_proxy_data_dummy_cb(const struct smtp_reply *proxy_reply ATTR_UNUSED, struct lmtp_proxy_connection *conn ATTR_UNUSED) { /* nothing */ } void lmtp_proxy_data(struct client *client, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct smtp_server_transaction *trans ATTR_UNUSED, struct istream *data_input) { struct lmtp_proxy *proxy = client->proxy; struct lmtp_proxy_connection *conn; uoff_t size; i_assert(data_input->seekable); i_assert(proxy->data_input == NULL); client_update_data_state(client, "proxying"); proxy->data_input = data_input; i_stream_ref(proxy->data_input); if (i_stream_get_size(proxy->data_input, TRUE, &size) < 0) { e_error(client->event, "i_stream_get_size(data_input) failed: %s", i_stream_get_error(proxy->data_input)); size = UOFF_T_MAX; } /* Create the data_input streams first */ array_foreach_elem(&proxy->connections, conn) { if (conn->finished) { /* This connection had already failed */ continue; } if (size == UOFF_T_MAX) { conn->data_input = i_stream_create_limit(data_input, UOFF_T_MAX); } else { conn->data_input = i_stream_create_sized(data_input, size); } } /* Now that all the streams are created, start reading them (reading them earlier could have caused the data_input parent's offset to change) */ array_foreach_elem(&proxy->connections, conn) { if (conn->finished) { /* This connection had already failed */ continue; } smtp_client_transaction_set_timeout(conn->lmtp_trans, proxy->max_timeout_msecs); smtp_client_transaction_send(conn->lmtp_trans, conn->data_input, lmtp_proxy_data_dummy_cb, conn); } } dovecot-2.3.21.1/src/lmtp/lmtp-client.c0000644000000000000000000002715714656633576014470 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lmtp-common.h" #include "base64.h" #include "str.h" #include "llist.h" #include "iostream.h" #include "istream.h" #include "ostream.h" #include "hostpid.h" #include "process-title.h" #include "var-expand.h" #include "module-dir.h" #include "master-service-ssl.h" #include "master-service-settings.h" #include "iostream-ssl.h" #include "mail-namespace.h" #include "mail-storage.h" #include "mail-storage-service.h" #include "raw-storage.h" #include "lda-settings.h" #include "lmtp-local.h" #include "lmtp-proxy.h" #include "lmtp-commands.h" #include #define CLIENT_IDLE_TIMEOUT_MSECS (1000*60*5) static const struct smtp_server_callbacks lmtp_callbacks; static const struct lmtp_client_vfuncs lmtp_client_vfuncs; struct lmtp_module_register lmtp_module_register = { 0 }; static struct client *clients = NULL; static unsigned int clients_count = 0; static bool verbose_proctitle = FALSE; static const char *client_remote_id(struct client *client) { const char *addr; addr = net_ip2addr(&client->remote_ip); if (addr[0] == '\0') addr = "local"; return addr; } static void refresh_proctitle(void) { struct client *client; string_t *title; if (!verbose_proctitle) return; title = t_str_new(128); str_append_c(title, '['); switch (clients_count) { case 0: str_append(title, "idling"); break; case 1: client = clients; str_append(title, client_remote_id(client)); str_append_c(title, ' '); str_append(title, smtp_server_state_names[client->state.state]); if (client->state.args != NULL && *client->state.args != '\0') { str_append_c(title, ' '); str_append(title, client->state.args); } break; default: str_printfa(title, "%u connections", clients_count); break; } str_append_c(title, ']'); process_title_set(str_c(title)); } static void client_load_modules(struct client *client) { struct module_dir_load_settings mod_set; i_zero(&mod_set); mod_set.abi_version = DOVECOT_ABI_VERSION; mod_set.require_init_funcs = TRUE; mod_set.binary_name = "lmtp"; /* pre-load all configured mail plugins */ mail_storage_service_modules = module_dir_load_missing(mail_storage_service_modules, client->lmtp_set->mail_plugin_dir, client->lmtp_set->mail_plugins, &mod_set); module_dir_init(mail_storage_service_modules); } static void client_raw_user_create(struct client *client) { void **sets; sets = master_service_settings_get_others(master_service); client->raw_mail_user = raw_storage_create_from_set(client->user_set_info, sets[0]); } static void client_read_settings(struct client *client, bool ssl) { struct mail_storage_service_input input; const struct setting_parser_context *set_parser; struct mail_user_settings *user_set; struct lmtp_settings *lmtp_set; struct lda_settings *lda_set; const char *error; i_zero(&input); input.module = input.service = "lmtp"; input.local_ip = client->local_ip; input.remote_ip = client->remote_ip; input.local_port = client->local_port; input.remote_port = client->remote_port; input.conn_secured = ssl; input.conn_ssl_secured = ssl; input.username = ""; if (mail_storage_service_read_settings(storage_service, &input, client->pool, &client->user_set_info, &set_parser, &error) < 0) i_fatal("%s", error); lmtp_settings_dup(set_parser, client->pool, &user_set, &lmtp_set, &lda_set); const struct var_expand_table *tab = mail_storage_service_get_var_expand_table(storage_service, &input); if (settings_var_expand(&lmtp_setting_parser_info, lmtp_set, client->pool, tab, &error) <= 0) i_fatal("Failed to expand settings: %s", error); client->service_set = master_service_settings_get(master_service); client->user_set = user_set; client->lmtp_set = lmtp_set; client->unexpanded_lda_set = lda_set; } struct client *client_create(int fd_in, int fd_out, const struct master_service_connection *conn) { static const char *rcpt_param_extensions[] = { LMTP_RCPT_FORWARD_PARAMETER, NULL }; static const struct smtp_capability_extra cap_rcpt_forward = { .name = LMTP_RCPT_FORWARD_CAPABILITY }; enum lmtp_client_workarounds workarounds; struct smtp_server_settings lmtp_set; struct client *client; pool_t pool; pool = pool_alloconly_create("lmtp client", 2048); client = p_new(pool, struct client, 1); client->pool = pool; client->v = lmtp_client_vfuncs; client->remote_ip = conn->remote_ip; client->remote_port = conn->remote_port; client->local_ip = conn->local_ip; client->local_port = conn->local_port; client->real_local_ip = conn->real_local_ip; client->real_local_port = conn->real_local_port; client->real_remote_ip = conn->real_remote_ip; client->real_remote_port = conn->real_remote_port; client->state_pool = pool_alloconly_create("client state", 4096); client->event = event_create(NULL); event_add_category(client->event, &event_category_lmtp); client_read_settings(client, conn->ssl); client_raw_user_create(client); client_load_modules(client); client->my_domain = client->unexpanded_lda_set->hostname; if (client->service_set->verbose_proctitle) verbose_proctitle = TRUE; p_array_init(&client->module_contexts, client->pool, 5); i_zero(&lmtp_set); lmtp_set.capabilities = SMTP_CAPABILITY_PIPELINING | SMTP_CAPABILITY_ENHANCEDSTATUSCODES | SMTP_CAPABILITY_8BITMIME | SMTP_CAPABILITY_CHUNKING | SMTP_CAPABILITY_XCLIENT | SMTP_CAPABILITY__ORCPT; if (!conn->ssl && master_service_ssl_is_enabled(master_service)) lmtp_set.capabilities |= SMTP_CAPABILITY_STARTTLS; lmtp_set.hostname = client->unexpanded_lda_set->hostname; lmtp_set.login_greeting = client->lmtp_set->login_greeting; lmtp_set.max_message_size = UOFF_T_MAX; lmtp_set.rcpt_param_extensions = rcpt_param_extensions; lmtp_set.rcpt_domain_optional = TRUE; lmtp_set.max_client_idle_time_msecs = CLIENT_IDLE_TIMEOUT_MSECS; lmtp_set.rawlog_dir = client->lmtp_set->lmtp_rawlog_dir; lmtp_set.event_parent = client->event; workarounds = client->lmtp_set->parsed_workarounds; if ((workarounds & LMTP_WORKAROUND_WHITESPACE_BEFORE_PATH) != 0) { lmtp_set.workarounds |= SMTP_SERVER_WORKAROUND_WHITESPACE_BEFORE_PATH; } if ((workarounds & LMTP_WORKAROUND_MAILBOX_FOR_PATH) != 0) { lmtp_set.workarounds |= SMTP_SERVER_WORKAROUND_MAILBOX_FOR_PATH; } client->conn = smtp_server_connection_create( lmtp_server, fd_in, fd_out, &conn->remote_ip, conn->remote_port, conn->ssl, &lmtp_set, &lmtp_callbacks, client); if (smtp_server_connection_is_trusted(client->conn)) { smtp_server_connection_add_extra_capability( client->conn, &cap_rcpt_forward); } DLLIST_PREPEND(&clients, client); clients_count++; e_info(client->event, "Connect from %s", client_remote_id(client)); if (hook_client_created != NULL) hook_client_created(&client); smtp_server_connection_start(client->conn); refresh_proctitle(); return client; } void client_state_reset(struct client *client) { i_free(client->state.args); if (client->local != NULL) lmtp_local_deinit(&client->local); if (client->proxy != NULL) lmtp_proxy_deinit(&client->proxy); o_stream_unref(&client->state.mail_data_output); i_zero(&client->state); p_clear(client->state_pool); } void client_destroy(struct client **_client, const char *enh_code, const char *reason) { struct client *client = *_client; *_client = NULL; smtp_server_connection_terminate(&client->conn, (enh_code == NULL ? "4.0.0" : enh_code), reason); } static void client_default_destroy(struct client *client) { if (client->destroyed) return; client->destroyed = TRUE; clients_count--; DLLIST_REMOVE(&clients, client); if (client->raw_mail_user != NULL) mail_user_deinit(&client->raw_mail_user); client_state_reset(client); event_unref(&client->event); pool_unref(&client->state_pool); pool_unref(&client->pool); master_service_client_connection_destroyed(master_service); } static void client_connection_trans_start(void *context, struct smtp_server_transaction *trans) { struct client *client = context; client->v.trans_start(client, trans); } static void client_default_trans_start(struct client *client ATTR_UNUSED, struct smtp_server_transaction *trans ATTR_UNUSED) { /* nothing */ } static void client_connection_trans_free(void *context, struct smtp_server_transaction *trans) { struct client *client = (struct client *)context; client->v.trans_free(client, trans); } static void client_default_trans_free(struct client *client, struct smtp_server_transaction *trans ATTR_UNUSED) { client_state_reset(client); } static void client_connection_state_changed(void *context, enum smtp_server_state new_state, const char *new_args) { struct client *client = (struct client *)context; i_free(client->state.args); client->state.state = new_state; client->state.args = i_strdup(new_args); if (clients_count == 1) refresh_proctitle(); } void client_update_data_state(struct client *client, const char *new_args) { i_assert(client->state.state == SMTP_SERVER_STATE_DATA); i_free(client->state.args); client->state.args = i_strdup(new_args); if (clients_count == 1) refresh_proctitle(); } static void client_connection_proxy_data_updated(void *context, const struct smtp_proxy_data *data) { struct client *client = (struct client *)context; client->remote_ip = data->source_ip; client->remote_port = data->source_port; if (clients_count == 1) refresh_proctitle(); } static void client_connection_disconnect(void *context, const char *reason) { struct client *client = (struct client *)context; if (client->disconnected) return; client->disconnected = TRUE; if (reason == NULL) reason = "Connection closed"; e_info(client->event, "Disconnect from %s: %s", client_remote_id(client), reason); } static void client_connection_free(void *context) { struct client *client = (struct client *)context; client->v.destroy(client); } static bool client_connection_is_trusted(void *context) { struct client *client = (struct client *)context; const char *const *net; struct ip_addr net_ip; unsigned int bits; if (client->lmtp_set->login_trusted_networks == NULL) return FALSE; net = t_strsplit_spaces(client->lmtp_set->login_trusted_networks, ", "); for (; *net != NULL; net++) { if (net_parse_range(*net, &net_ip, &bits) < 0) { e_error(client->event, "login_trusted_networks: " "Invalid network '%s'", *net); break; } if (net_is_in_network(&client->real_remote_ip, &net_ip, bits)) return TRUE; } return FALSE; } void clients_destroy(void) { while (clients != NULL) { struct client *client = clients; client_destroy(&client, "4.3.2", "Shutting down"); } } static const struct smtp_server_callbacks lmtp_callbacks = { .conn_cmd_mail = cmd_mail, .conn_cmd_rcpt = cmd_rcpt, .conn_cmd_data_begin = cmd_data_begin, .conn_cmd_data_continue = cmd_data_continue, .conn_trans_start = client_connection_trans_start, .conn_trans_free = client_connection_trans_free, .conn_state_changed = client_connection_state_changed, .conn_proxy_data_updated = client_connection_proxy_data_updated, .conn_disconnect = client_connection_disconnect, .conn_free = client_connection_free, .conn_is_trusted = client_connection_is_trusted }; static const struct lmtp_client_vfuncs lmtp_client_vfuncs = { .destroy = client_default_destroy, .trans_start = client_default_trans_start, .trans_free = client_default_trans_free, .cmd_mail = client_default_cmd_mail, .cmd_rcpt = client_default_cmd_rcpt, .cmd_data = client_default_cmd_data, .local_deliver = lmtp_local_default_deliver, }; dovecot-2.3.21.1/src/lmtp/lmtp-common.h0000644000000000000000000000171414656633576014476 00000000000000#ifndef LMTP_COMMON_H #define LMTP_COMMON_H #include "lib.h" #include "array.h" #include "ioloop.h" #include "settings-parser.h" #include "master-service.h" #include "smtp-reply.h" #include "smtp-server.h" #include "lmtp-client.h" #include "lmtp-settings.h" #define LMTP_RCPT_FORWARD_CAPABILITY "XRCPTFORWARD" #define LMTP_RCPT_FORWARD_PARAMETER "XRCPTFORWARD" typedef void lmtp_client_created_func_t(struct client **client); extern lmtp_client_created_func_t *hook_client_created; extern struct event_category event_category_lmtp; extern char *dns_client_socket_path, *base_dir; extern struct mail_storage_service_ctx *storage_service; extern struct anvil_client *anvil; extern struct smtp_server *lmtp_server; /* Sets the hook_client_created and returns the previous hook, which the new_hook should call if it's non-NULL. */ lmtp_client_created_func_t * lmtp_client_created_hook_set(lmtp_client_created_func_t *new_hook); void lmtp_anvil_init(void); #endif dovecot-2.3.21.1/src/lmtp/lmtp-settings.h0000644000000000000000000000247714656633576015055 00000000000000#ifndef LMTP_SETTINGS_H #define LMTP_SETTINGS_H struct mail_user_settings; struct lda_settings; struct lmtp_settings; /* */ enum lmtp_hdr_delivery_address { LMTP_HDR_DELIVERY_ADDRESS_NONE, LMTP_HDR_DELIVERY_ADDRESS_FINAL, LMTP_HDR_DELIVERY_ADDRESS_ORIGINAL }; enum lmtp_client_workarounds { LMTP_WORKAROUND_WHITESPACE_BEFORE_PATH = BIT(0), LMTP_WORKAROUND_MAILBOX_FOR_PATH = BIT(1), }; /* */ struct lmtp_settings { bool lmtp_proxy; bool lmtp_save_to_detail_mailbox; bool lmtp_rcpt_check_quota; bool lmtp_add_received_header; bool lmtp_verbose_replies; unsigned int lmtp_user_concurrency_limit; const char *lmtp_hdr_delivery_address; const char *lmtp_rawlog_dir; const char *lmtp_proxy_rawlog_dir; const char *lmtp_client_workarounds; const char *login_greeting; const char *login_trusted_networks; const char *mail_plugins; const char *mail_plugin_dir; enum lmtp_hdr_delivery_address parsed_lmtp_hdr_delivery_address; enum lmtp_client_workarounds parsed_workarounds; }; extern const struct setting_parser_info lmtp_setting_parser_info; void lmtp_settings_dup(const struct setting_parser_context *set_parser, pool_t pool, struct mail_user_settings **user_set_r, struct lmtp_settings **lmtp_set_r, struct lda_settings **lda_set_r); #endif dovecot-2.3.21.1/src/lmtp/lmtp-client.h0000644000000000000000000000620514656633576014464 00000000000000#ifndef CLIENT_H #define CLIENT_H #include "net.h" #include "smtp-server.h" #define CLIENT_MAIL_DATA_MAX_INMEMORY_SIZE (1024*128) struct mail_storage; struct mail_deliver_context; union lmtp_module_context; struct lmtp_recipient; struct client; struct lmtp_local_deliver_context { struct mail *src_mail; const char *session_id; struct timeval delivery_time_started; struct mail_user *rcpt_user; const char *rcpt_default_mailbox; const struct mail_storage_settings *mail_set; const struct smtp_submit_settings *smtp_set; const struct lda_settings *lda_set; struct mail_deliver_session *session; }; struct client_state { enum smtp_server_state state; char *args; unsigned int session_id_seq; struct istream *data_input; uoff_t data_size; struct timeval data_end_timeval; struct ostream *mail_data_output; const char *added_headers_local; const char *added_headers_proxy; }; struct lmtp_client_vfuncs { void (*destroy)(struct client *client); void (*trans_start)(struct client *client, struct smtp_server_transaction *trans); void (*trans_free)(struct client *client, struct smtp_server_transaction *trans); int (*cmd_mail)(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_mail *data); int (*cmd_rcpt)(struct client *client, struct smtp_server_cmd_ctx *cmd, struct lmtp_recipient *lrcpt); int (*cmd_data)(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans, struct istream *data_input, uoff_t data_size); int (*local_deliver)(struct client *client, struct lmtp_recipient *lrcpt, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans, struct lmtp_local_deliver_context *lldctx); }; struct client { struct client *prev, *next; pool_t pool; struct lmtp_client_vfuncs v; struct event *event; const struct setting_parser_info *user_set_info; const struct mail_user_settings *user_set; const struct lda_settings *unexpanded_lda_set; const struct lmtp_settings *lmtp_set; const struct master_service_settings *service_set; struct smtp_server_connection *conn; struct ip_addr remote_ip, local_ip, real_local_ip, real_remote_ip; in_port_t remote_port, local_port, real_local_port, real_remote_port; struct mail_user *raw_mail_user; const char *my_domain; pool_t state_pool; struct client_state state; struct istream *dot_input; struct lmtp_local *local; struct lmtp_proxy *proxy; /* Module-specific contexts. */ ARRAY(union lmtp_module_context *) module_contexts; bool disconnected:1; bool destroyed:1; }; struct lmtp_module_register { unsigned int id; }; union lmtp_module_context { struct lmtp_client_vfuncs super; struct lmtp_module_register *reg; }; extern struct lmtp_module_register lmtp_module_register; struct client *client_create(int fd_in, int fd_out, const struct master_service_connection *conn); void client_destroy(struct client **client, const char *enh_code, const char *reason) ATTR_NULL(2, 3); void client_state_reset(struct client *client); void client_update_data_state(struct client *client, const char *new_args); void clients_destroy(void); #endif dovecot-2.3.21.1/src/lmtp/lmtp-proxy.h0000644000000000000000000000125014656633576014362 00000000000000#ifndef LMTP_PROXY_H #define LMTP_PROXY_H #include "net.h" #include "smtp-common.h" #include "smtp-params.h" #include "smtp-client.h" #define LMTP_PROXY_DEFAULT_TTL 5 struct smtp_server_cmd_ctx; struct smtp_server_cmd_rcpt; struct lmtp_proxy; struct client; void lmtp_proxy_deinit(struct lmtp_proxy **proxy); int lmtp_proxy_rcpt(struct client *client, struct smtp_server_cmd_ctx *cmd, struct lmtp_recipient *rcpt, const char *username, const char *detail, char delim); void lmtp_proxy_data(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans ATTR_UNUSED, struct istream *data_input); #endif dovecot-2.3.21.1/src/lmtp/main.c0000644000000000000000000001074214656633576013154 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lmtp-common.h" #include "ioloop.h" #include "path-util.h" #include "restrict-access.h" #include "anvil-client.h" #include "master-service.h" #include "master-service-settings.h" #include "master-interface.h" #include "mail-deliver.h" #include "mail-storage-service.h" #include "smtp-submit-settings.h" #include "lda-settings.h" #include #define DNS_CLIENT_SOCKET_PATH "dns-client" #define LMTP_MASTER_FIRST_LISTEN_FD 3 #define IS_STANDALONE() \ (getenv(MASTER_IS_PARENT_ENV) == NULL) struct smtp_server *lmtp_server = NULL; char *dns_client_socket_path, *base_dir; struct mail_storage_service_ctx *storage_service; struct anvil_client *anvil; lmtp_client_created_func_t *hook_client_created = NULL; struct event_category event_category_lmtp = { .name = "lmtp", }; lmtp_client_created_func_t * lmtp_client_created_hook_set(lmtp_client_created_func_t *new_hook) { lmtp_client_created_func_t *old_hook = hook_client_created; hook_client_created = new_hook; return old_hook; } void lmtp_anvil_init(void) { if (anvil == NULL) { const char *path = t_strdup_printf("%s/anvil", base_dir); anvil = anvil_client_init(path, NULL, 0); } } static void client_connected(struct master_service_connection *conn) { master_service_client_connection_accept(conn); (void)client_create(conn->fd, conn->fd, conn); } static void drop_privileges(void) { struct restrict_access_settings set; const char *error; /* by default we don't drop any privileges, but keep running as root. */ restrict_access_get_env(&set); /* open config connection before dropping privileges */ struct master_service_settings_input input; struct master_service_settings_output output; i_zero(&input); input.module = "lmtp"; input.service = "lmtp"; if (master_service_settings_read(master_service, &input, &output, &error) < 0) i_fatal("Error reading configuration: %s", error); restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL); } static void main_init(void) { struct master_service_connection conn; struct smtp_server_settings lmtp_set; i_zero(&lmtp_set); lmtp_set.protocol = SMTP_PROTOCOL_LMTP; lmtp_set.auth_optional = TRUE; lmtp_set.rcpt_domain_optional = TRUE; lmtp_set.mail_path_allow_broken = TRUE; lmtp_set.reason_code_module = "lmtp"; lmtp_server = smtp_server_init(&lmtp_set); if (IS_STANDALONE()) { i_zero(&conn); (void)client_create(STDIN_FILENO, STDOUT_FILENO, &conn); } const char *error, *tmp_socket_path; if (t_abspath(DNS_CLIENT_SOCKET_PATH, &tmp_socket_path, &error) < 0) { i_fatal("t_abspath(%s) failed: %s", DNS_CLIENT_SOCKET_PATH, error); } dns_client_socket_path = i_strdup(tmp_socket_path); mail_deliver_hooks_init(); } static void main_deinit(void) { clients_destroy(); if (anvil != NULL) anvil_client_deinit(&anvil); i_free(dns_client_socket_path); i_free(base_dir); smtp_server_deinit(&lmtp_server); } int main(int argc, char *argv[]) { const struct setting_parser_info *set_roots[] = { &smtp_submit_setting_parser_info, &lda_setting_parser_info, &lmtp_setting_parser_info, NULL }; enum master_service_flags service_flags = MASTER_SERVICE_FLAG_HAVE_STARTTLS; enum mail_storage_service_flags storage_service_flags = MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP | MAIL_STORAGE_SERVICE_FLAG_TEMP_PRIV_DROP | MAIL_STORAGE_SERVICE_FLAG_NO_IDLE_TIMEOUT; const char *tmp_base_dir; int c; if (IS_STANDALONE()) { service_flags |= MASTER_SERVICE_FLAG_STANDALONE | MASTER_SERVICE_FLAG_STD_CLIENT; } else { service_flags |= MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN ; } master_service = master_service_init("lmtp", service_flags, &argc, &argv, "D"); while ((c = master_getopt(master_service)) > 0) { switch (c) { case 'D': storage_service_flags |= MAIL_STORAGE_SERVICE_FLAG_ENABLE_CORE_DUMPS; break; default: return FATAL_DEFAULT; } } const char *error; if (t_get_working_dir(&tmp_base_dir, &error) < 0) i_fatal("Could not get working directory: %s", error); base_dir = i_strdup(tmp_base_dir); drop_privileges(); master_service_init_log_with_pid(master_service); storage_service = mail_storage_service_init(master_service, set_roots, storage_service_flags); restrict_access_allow_coredumps(TRUE); main_init(); master_service_init_finish(master_service); master_service_run(master_service, client_connected); main_deinit(); mail_storage_service_deinit(&storage_service); master_service_deinit(&master_service); return 0; } dovecot-2.3.21.1/src/lmtp/Makefile.am0000644000000000000000000000220614656633576014114 00000000000000pkglibexecdir = $(libexecdir)/dovecot pkglibexec_PROGRAMS = lmtp AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-smtp \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-lda \ -I$(top_srcdir)/src/lib-ssl-iostream \ -I$(top_srcdir)/src/lib-storage \ -I$(top_srcdir)/src/lib-storage/index \ -I$(top_srcdir)/src/lib-storage/index/raw \ -DMODULEDIR=\""$(moduledir)"\" \ $(BINARY_CFLAGS) lmtp_LDFLAGS = -export-dynamic \ $(BINARY_LDFLAGS) lmtp_LDADD = \ $(LIBDOVECOT_LDA) \ $(LIBDOVECOT_STORAGE) \ $(LIBDOVECOT) lmtp_DEPENDENCIES = \ $(LIBDOVECOT_LDA) \ $(LIBDOVECOT_STORAGE_DEPS) \ $(LIBDOVECOT_DEPS) lmtp_SOURCES = \ main.c \ lmtp-client.c \ lmtp-commands.c \ lmtp-recipient.c \ lmtp-local.c \ lmtp-proxy.c \ lmtp-settings.c noinst_HEADERS = \ lmtp-local.h \ lmtp-proxy.h headers = \ lmtp-common.h \ lmtp-commands.h \ lmtp-recipient.h \ lmtp-client.h \ lmtp-settings.h pkginc_libdir=$(pkgincludedir) pkginc_lib_HEADERS = $(headers) dovecot-2.3.21.1/src/lmtp/lmtp-settings.c0000644000000000000000000001271214656633576015041 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "var-expand.h" #include "settings-parser.h" #include "service-settings.h" #include "master-service.h" #include "master-service-settings.h" #include "lda-settings.h" #include "lmtp-settings.h" #include "mail-storage-settings.h" #include #include static bool lmtp_settings_check(void *_set, pool_t pool, const char **error_r); /* */ static struct file_listener_settings lmtp_unix_listeners_array[] = { { "lmtp", 0666, "", "" } }; static struct file_listener_settings *lmtp_unix_listeners[] = { &lmtp_unix_listeners_array[0] }; static buffer_t lmtp_unix_listeners_buf = { { { lmtp_unix_listeners, sizeof(lmtp_unix_listeners) } } }; /* */ struct service_settings lmtp_service_settings = { .name = "lmtp", .protocol = "lmtp", .type = "", .executable = "lmtp", .user = "", .group = "", .privileged_group = "", .extra_groups = "$default_internal_group", .chroot = "", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 0, .client_limit = 1, .service_count = 0, .idle_kill = 0, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &lmtp_unix_listeners_buf, sizeof(lmtp_unix_listeners[0]) } }, .fifo_listeners = ARRAY_INIT, .inet_listeners = ARRAY_INIT }; #undef DEF #define DEF(type, name) \ SETTING_DEFINE_STRUCT_##type(#name, name, struct lmtp_settings) static const struct setting_define lmtp_setting_defines[] = { DEF(BOOL, lmtp_proxy), DEF(BOOL, lmtp_save_to_detail_mailbox), DEF(BOOL, lmtp_rcpt_check_quota), DEF(BOOL, lmtp_add_received_header), DEF(BOOL, lmtp_verbose_replies), DEF(UINT, lmtp_user_concurrency_limit), DEF(ENUM, lmtp_hdr_delivery_address), DEF(STR_VARS, lmtp_rawlog_dir), DEF(STR_VARS, lmtp_proxy_rawlog_dir), DEF(STR, lmtp_client_workarounds), DEF(STR_VARS, login_greeting), DEF(STR, login_trusted_networks), DEF(STR, mail_plugins), DEF(STR, mail_plugin_dir), SETTING_DEFINE_LIST_END }; static const struct lmtp_settings lmtp_default_settings = { .lmtp_proxy = FALSE, .lmtp_save_to_detail_mailbox = FALSE, .lmtp_rcpt_check_quota = FALSE, .lmtp_add_received_header = TRUE, .lmtp_verbose_replies = FALSE, .lmtp_user_concurrency_limit = 0, .lmtp_hdr_delivery_address = "final:none:original", .lmtp_rawlog_dir = "", .lmtp_proxy_rawlog_dir = "", .lmtp_client_workarounds = "", .login_greeting = PACKAGE_NAME" ready.", .login_trusted_networks = "", .mail_plugins = "", .mail_plugin_dir = MODULEDIR, }; static const struct setting_parser_info *lmtp_setting_dependencies[] = { &lda_setting_parser_info, NULL }; const struct setting_parser_info lmtp_setting_parser_info = { .module_name = "lmtp", .defines = lmtp_setting_defines, .defaults = &lmtp_default_settings, .type_offset = SIZE_MAX, .struct_size = sizeof(struct lmtp_settings), .parent_offset = SIZE_MAX, .check_func = lmtp_settings_check, .dependencies = lmtp_setting_dependencies }; /* */ struct lmtp_client_workaround_list { const char *name; enum lmtp_client_workarounds num; }; static const struct lmtp_client_workaround_list lmtp_client_workaround_list[] = { { "whitespace-before-path", LMTP_WORKAROUND_WHITESPACE_BEFORE_PATH }, { "mailbox-for-path", LMTP_WORKAROUND_MAILBOX_FOR_PATH }, { NULL, 0 } }; static int lmtp_settings_parse_workarounds(struct lmtp_settings *set, const char **error_r) { enum lmtp_client_workarounds client_workarounds = 0; const struct lmtp_client_workaround_list *list; const char *const *str; str = t_strsplit_spaces(set->lmtp_client_workarounds, " ,"); for (; *str != NULL; str++) { list = lmtp_client_workaround_list; for (; list->name != NULL; list++) { if (strcasecmp(*str, list->name) == 0) { client_workarounds |= list->num; break; } } if (list->name == NULL) { *error_r = t_strdup_printf( "lmtp_client_workarounds: " "Unknown workaround: %s", *str); return -1; } } set->parsed_workarounds = client_workarounds; return 0; } static bool lmtp_settings_check(void *_set, pool_t pool ATTR_UNUSED, const char **error_r) { struct lmtp_settings *set = _set; if (lmtp_settings_parse_workarounds(set, error_r) < 0) return FALSE; if (strcmp(set->lmtp_hdr_delivery_address, "none") == 0) { set->parsed_lmtp_hdr_delivery_address = LMTP_HDR_DELIVERY_ADDRESS_NONE; } else if (strcmp(set->lmtp_hdr_delivery_address, "final") == 0) { set->parsed_lmtp_hdr_delivery_address = LMTP_HDR_DELIVERY_ADDRESS_FINAL; } else if (strcmp(set->lmtp_hdr_delivery_address, "original") == 0) { set->parsed_lmtp_hdr_delivery_address = LMTP_HDR_DELIVERY_ADDRESS_ORIGINAL; } else { *error_r = t_strdup_printf("Unknown lmtp_hdr_delivery_address: %s", set->lmtp_hdr_delivery_address); return FALSE; } return TRUE; } /* */ void lmtp_settings_dup(const struct setting_parser_context *set_parser, pool_t pool, struct mail_user_settings **user_set_r, struct lmtp_settings **lmtp_set_r, struct lda_settings **lda_set_r) { const char *error; void **sets; sets = master_service_settings_parser_get_others(master_service, set_parser); *user_set_r = settings_dup(&mail_user_setting_parser_info, sets[0], pool); *lda_set_r = settings_dup(&lda_setting_parser_info, sets[2], pool); *lmtp_set_r = settings_dup(&lmtp_setting_parser_info, sets[3], pool); if (!lmtp_settings_check(*lmtp_set_r, pool, &error)) i_unreached(); } dovecot-2.3.21.1/src/lmtp/lmtp-recipient.c0000644000000000000000000000275314656633576015167 00000000000000/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ #include "lmtp-common.h" #include "array.h" #include "smtp-server.h" #include "lmtp-recipient.h" struct lmtp_recipient_module_register lmtp_recipient_module_register = { 0 }; struct lmtp_recipient * lmtp_recipient_create(struct client *client, struct smtp_server_transaction *trans, struct smtp_server_recipient *rcpt) { struct lmtp_recipient *lrcpt; lrcpt = p_new(rcpt->pool, struct lmtp_recipient, 1); lrcpt->rcpt = rcpt; lrcpt->client = client; rcpt->context = lrcpt; p_array_init(&lrcpt->module_contexts, rcpt->pool, 5); /* Use a unique session_id for each mail delivery. This is especially important for stats process to not see duplicate sessions. */ if (client->state.session_id_seq++ == 0) lrcpt->session_id = trans->id; else { lrcpt->session_id = p_strdup_printf(rcpt->pool, "%s:R%u", trans->id, client->state.session_id_seq); } event_add_str(rcpt->event, "session", lrcpt->session_id); return lrcpt; } struct lmtp_recipient * lmtp_recipient_find_duplicate(struct lmtp_recipient *lrcpt, struct smtp_server_transaction *trans) { struct smtp_server_recipient *drcpt; struct lmtp_recipient *dup_lrcpt; i_assert(lrcpt->rcpt != NULL); drcpt = smtp_server_transaction_find_rcpt_duplicate(trans, lrcpt->rcpt); if (drcpt == NULL) return NULL; dup_lrcpt = drcpt->context; i_assert(dup_lrcpt->rcpt == drcpt); i_assert(dup_lrcpt->type == lrcpt->type); return dup_lrcpt; } dovecot-2.3.21.1/src/lmtp/lmtp-commands.h0000644000000000000000000000221114656633576015000 00000000000000#ifndef COMMANDS_H #define COMMANDS_H struct client; struct smtp_server_cmd_ctx; struct smtp_server_cmd_helo; /* * MAIL command */ int cmd_mail(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_mail *data); int client_default_cmd_mail(struct client *client, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct smtp_server_cmd_mail *data ATTR_UNUSED); /* * RCPT command */ int cmd_rcpt(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_recipient *rcpt); int client_default_cmd_rcpt(struct client *client, struct smtp_server_cmd_ctx *cmd, struct lmtp_recipient *lrcpt); /* * DATA command */ int cmd_data_continue(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans); int cmd_data_begin(void *conn_ctx, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct smtp_server_transaction *trans, struct istream *data_input); int client_default_cmd_data(struct client *client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_transaction *trans, struct istream *data_input, uoff_t data_size); #endif dovecot-2.3.21.1/src/lib-oauth2/0000755000000000000000000000000014656633636013127 500000000000000dovecot-2.3.21.1/src/lib-oauth2/test-oauth2-jwt.c0000644000000000000000000006123514656633576016206 00000000000000/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "str.h" #include "ostream.h" #include "hmac.h" #include "sha2.h" #include "base64.h" #include "randgen.h" #include "array.h" #include "json-parser.h" #include "iso8601-date.h" #include "oauth2.h" #include "oauth2-private.h" #include "dcrypt.h" #include "dict.h" #include "dict-private.h" #include "test-common.h" #include "unlink-directory.h" #include #include #define base64url_encode_str(str, dest) \ base64url_encode(BASE64_ENCODE_FLAG_NO_PADDING, SIZE_MAX, (str), \ strlen((str)), (dest)) /** * Test keypair used only for this test. */ static const char *rsa_public_key = "-----BEGIN PUBLIC KEY-----\n" "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv\n" "vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc\n" "aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy\n" "tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0\n" "e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb\n" "V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9\n" "MwIDAQAB\n" "-----END PUBLIC KEY-----"; static const char *rsa_private_key = "-----BEGIN PRIVATE KEY-----\n" "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCfPKKzVmN80HRs\n" "GAoUxK++RO3CW8GxomrtLnAD6TN5U5WlVbCRZ1WFrizfxcz+lr/Kvjtq/v7PdVOa\n" "8NHIAdxpP3bCFEQWku/1yPmVN4lKJvKv8yub9i2MJlVaBo5giHCtfAouo+v/XWKd\n" "awCR8jK28dZPFlgRxcuABcW5S5pLe4X2ASI1DDMZNTW/QWqSpMGvgHydbccI3jtd\n" "S7S3xjR76V/izg7FBrBYPv0n3/l3dHLS9tXcCbUW0YmIm87BGwh9UKEOlhK1NwdM\n" "Iyq29ZtXovXUFaSnMZdJbge/jepr4ZJg4PZBTrwxvn2hKTY4H4G04ukmh+ZsYQaC\n" "+bDIIj0zAgMBAAECggEAKIBGrbCSW2O1yOyQW9nvDUkA5EdsS58Q7US7bvM4iWpu\n" "DIBwCXur7/VuKnhn/HUhURLzj/JNozynSChqYyG+CvL+ZLy82LUE3ZIBkSdv/vFL\n" "Ft+VvvRtf1EcsmoqenkZl7aN7HD7DJeXBoz5tyVQKuH17WW0fsi9StGtCcUl+H6K\n" "zV9Gif0Kj0uLQbCg3THRvKuueBTwCTdjoP0PwaNADgSWb3hJPeLMm/yII4tIMGbO\n" "w+xd9wJRl+ZN9nkNtQMxszFGdKjedB6goYLQuP0WRZx+YtykaVJdM75bDUvsQar4\n" "9Pc21Fp7UVk/CN11DX/hX3TmTJAUtqYADliVKkTbCQKBgQDLU48tBxm3g1CdDM/P\n" "ZIEmpA3Y/m7e9eX7M1Uo/zDh4G/S9a4kkX6GQY2dLFdCtOS8M4hR11Io7MceBKDi\n" "djorTZ5zJPQ8+b9Rm+1GlaucGNwRW0cQk2ltT2ksPmJnQn2xvM9T8vE+a4A/YGzw\n" "mZOfpoVGykWs/tbSzU2aTaOybQKBgQDIfRf6OmirGPh59l+RSuDkZtISF/51mCV/\n" "S1M4DltWDwhjC2Y2T+meIsb/Mjtz4aVNz0EHB8yvn0TMGr94Uwjv4uBdpVSwz+xL\n" "hHL7J4rpInH+i0gxa0N+rGwsPwI8wJG95wLY+Kni5KCuXQw55uX1cqnnsahpRZFZ\n" "EerBXhjqHwKBgBmEjiaHipm2eEqNjhMoOPFBi59dJ0sCL2/cXGa9yEPA6Cfgv49F\n" "V0zAM2azZuwvSbm4+fXTgTMzrDW/PPXPArPmlOk8jQ6OBY3XdOrz48q+b/gZrYyO\n" "A6A9ZCSyW6U7+gxxds/BYLeFxF2v21xC2f0iZ/2faykv/oQMUh34en/tAoGACqVZ\n" "2JexZyR0TUWf3X80YexzyzIq+OOTWicNzDQ29WLm9xtr2gZ0SUlfd72bGpQoyvDu\n" "awkm/UxfwtbIxALkvpg1gcN9s8XWrkviLyPyZF7H3tRWiQlBFEDjnZXa8I7pLkRO\n" "Cmdp3fp17cxTEeAI5feovfzZDH39MdWZuZrdh9ECgYBTEv8S7nK8wrxIC390kroV\n" "52eBwzckQU2mWa0thUtaGQiU1EYPCSDcjkrLXwB72ft0dW57KyWtvrB6rt1ORgOL\n" "eI5hFbwdGQhCHTrAR1vG3SyFPMAm+8JB+sGOD/fvjtZKx//MFNweKFNEF0C/o6Z2\n" "FXj90PlgF8sCQut36ZfuIQ==\n" "-----END PRIVATE KEY-----"; static buffer_t *hs_sign_key = NULL; static struct dict *keys_dict = NULL; static bool skip_dcrypt = FALSE; static struct oauth2_validation_key_cache *key_cache = NULL; static int parse_jwt_token(struct oauth2_request *req, const char *token, bool *is_jwt_r, const char **error_r) { struct oauth2_settings set; i_zero(&set); set.key_dict = keys_dict; set.key_cache = key_cache; i_zero(req); req->pool = pool_datastack_create(); req->set = &set; t_array_init(&req->fields, 8); return oauth2_try_parse_jwt(&set, token, &req->fields, is_jwt_r, error_r); } static void test_jwt_token(const char *token) { /* then see what the parser likes it */ struct oauth2_request req; const char *error = NULL; bool is_jwt; test_assert(parse_jwt_token(&req, token, &is_jwt, &error) == 0); test_assert(is_jwt == TRUE); test_assert(error == NULL); /* check fields */ test_assert(array_is_created(&req.fields)); if (array_is_created(&req.fields)) { const struct oauth2_field *field; bool got_sub = FALSE; array_foreach(&req.fields, field) { if (strcmp(field->name, "sub") == 0) { test_assert_strcmp(field->value, "testuser"); got_sub = TRUE; } } test_assert(got_sub == TRUE); } if (error != NULL) i_error("%s", error); } static buffer_t *create_jwt_token_kid(const char *algo, const char *kid) { /* make a token */ buffer_t *tokenbuf = t_buffer_create(64); /* header */ base64url_encode_str( t_strdup_printf( "{\"alg\":\"%s\",\"typ\":\"JWT\",\"kid\":\"%s\"}", algo, kid), tokenbuf); buffer_append(tokenbuf, ".", 1); /* body */ base64url_encode_str( t_strdup_printf("{\"sub\":\"testuser\","\ "\"iat\":%"PRIdTIME_T"," "\"exp\":%"PRIdTIME_T"}", time(NULL), time(NULL)+600), tokenbuf); return tokenbuf; } static buffer_t *create_jwt_token(const char *algo) { /* make a token */ buffer_t *tokenbuf = t_buffer_create(64); /* header */ base64url_encode_str( t_strdup_printf("{\"alg\":\"%s\",\"typ\":\"JWT\"}", algo), tokenbuf); buffer_append(tokenbuf, ".", 1); /* body */ base64url_encode_str( t_strdup_printf("{\"sub\":\"testuser\","\ "\"iat\":%"PRIdTIME_T"," "\"exp\":%"PRIdTIME_T"}", time(NULL), time(NULL)+600), tokenbuf); return tokenbuf; } static void append_key_value(string_t *dest, const char *key, const char *value, bool str) { str_append_c(dest, '"'); json_append_escaped(dest, key); str_append(dest, "\":"); if (str) str_append_c(dest, '"'); json_append_escaped(dest, value); if (str) str_append_c(dest, '"'); } #define create_jwt_token_fields(algo, exp, iat, nbf, fields) \ create_jwt_token_fields_kid(algo, "default", exp, iat, nbf, fields) static buffer_t * create_jwt_token_fields_kid(const char *algo, const char *kid, time_t exp, time_t iat, time_t nbf, ARRAY_TYPE(oauth2_field) *fields) { const struct oauth2_field *field; buffer_t *tokenbuf = t_buffer_create(64); string_t *hdr = t_str_new(32); str_printfa(hdr, "{\"alg\":\"%s\",\"typ\":\"JWT\"", algo); if (kid != NULL && *kid != '\0') { str_append(hdr, ",\"kid\":\""); json_append_escaped(hdr, kid); str_append_c(hdr, '"'); } str_append(hdr, "}"); base64url_encode_str(str_c(hdr), tokenbuf); buffer_append(tokenbuf, ".", 1); string_t *bodybuf = t_str_new(64); str_append_c(bodybuf, '{'); if (exp > 0) { append_key_value(bodybuf, "exp", dec2str(exp), FALSE); } if (iat > 0) { if (exp > 0) str_append_c(bodybuf, ','); append_key_value(bodybuf, "iat", dec2str(iat), FALSE); } if (nbf > 0) { if (exp > 0 || iat > 0) str_append_c(bodybuf, ','); append_key_value(bodybuf, "nbf", dec2str(nbf), FALSE); } array_foreach(fields, field) { if (str_data(bodybuf)[bodybuf->used-1] != '{') str_append_c(bodybuf, ','); append_key_value(bodybuf, field->name, field->value, TRUE); } str_append_c(bodybuf, '}'); base64url_encode_str(str_c(bodybuf), tokenbuf); return tokenbuf; } #define save_key(algo, key) save_key_to(algo, "default", (key)) #define save_key_to(algo, name, key) save_key_azp_to(algo, "default", name, (key)) static void save_key_azp_to(const char *algo, const char *azp, const char *name, const char *keydata) { const char *error; struct dict_op_settings set = { .username = "testuser", }; struct dict_transaction_context *ctx = dict_transaction_begin(keys_dict, &set); algo = t_str_ucase(algo); dict_set(ctx, t_strconcat(DICT_PATH_SHARED, azp, "/", algo, "/", name, NULL), keydata); if (dict_transaction_commit(&ctx, &error) < 0) i_error("dict_set(%s) failed: %s", name, error); } static void sign_jwt_token_hs256(buffer_t *tokenbuf, buffer_t *key) { i_assert(key != NULL); buffer_t *sig = t_hmac_buffer(&hash_method_sha256, key->data, key->used, tokenbuf); buffer_append(tokenbuf, ".", 1); base64url_encode(BASE64_ENCODE_FLAG_NO_PADDING, SIZE_MAX, sig->data, sig->used, tokenbuf); } static void sign_jwt_token_hs384(buffer_t *tokenbuf, buffer_t *key) { i_assert(key != NULL); buffer_t *sig = t_hmac_buffer(&hash_method_sha384, key->data, key->used, tokenbuf); buffer_append(tokenbuf, ".", 1); base64url_encode(BASE64_ENCODE_FLAG_NO_PADDING, SIZE_MAX, sig->data, sig->used, tokenbuf); } static void sign_jwt_token_hs512(buffer_t *tokenbuf, buffer_t *key) { i_assert(key != NULL); buffer_t *sig = t_hmac_buffer(&hash_method_sha512, key->data, key->used, tokenbuf); buffer_append(tokenbuf, ".", 1); base64url_encode(BASE64_ENCODE_FLAG_NO_PADDING, SIZE_MAX, sig->data, sig->used, tokenbuf); } static void test_jwt_hs_token(void) { test_begin("JWT HMAC token"); buffer_t *sign_key_384 = t_buffer_create(384/8); void *ptr = buffer_append_space_unsafe(sign_key_384, 384/8); random_fill(ptr, 384/8); buffer_t *b64_key = t_base64_encode(0, SIZE_MAX, sign_key_384->data, sign_key_384->used); save_key_to("HS384", "default", str_c(b64_key)); buffer_t *sign_key_512 = t_buffer_create(512/8); ptr = buffer_append_space_unsafe(sign_key_512, 512/8); random_fill(ptr, 512/8); b64_key = t_base64_encode(0, SIZE_MAX, sign_key_512->data, sign_key_512->used); save_key_to("HS512", "default", str_c(b64_key)); /* make a token */ buffer_t *tokenbuf = create_jwt_token("HS256"); /* sign it */ sign_jwt_token_hs256(tokenbuf, hs_sign_key); test_jwt_token(str_c(tokenbuf)); tokenbuf = create_jwt_token("HS384"); sign_jwt_token_hs384(tokenbuf, sign_key_384); test_jwt_token(str_c(tokenbuf)); tokenbuf = create_jwt_token("HS512"); sign_jwt_token_hs512(tokenbuf, sign_key_512); test_jwt_token(str_c(tokenbuf)); test_end(); } static void test_jwt_token_escape(void) { struct test_case { const char *azp; const char *alg; const char *kid; const char *esc_azp; const char *esc_kid; } test_cases[] = { { "", "hs256", "", "default", "default" }, { "", "hs256", "test", "default", "test" }, { "test", "hs256", "test", "test", "test" }, { "http://test.unit/local%key", "hs256", "http://test.unit/local%key", "http:%2f%2ftest.unit%2flocal%25key", "http:%2f%2ftest.unit%2flocal%25key" }, { "../", "hs256", "../", "..%2f", "..%2f" }, }; test_begin("JWT token escaping"); buffer_t *b64_key = t_base64_encode(0, SIZE_MAX, hs_sign_key->data, hs_sign_key->used); ARRAY_TYPE(oauth2_field) fields; t_array_init(&fields, 8); for (size_t i = 0; i < N_ELEMENTS(test_cases); i++) { const struct test_case *test_case = &test_cases[i]; array_clear(&fields); struct oauth2_field *field = array_append_space(&fields); field->name = "sub"; field->value = "testuser"; if (*test_case->azp != '\0') { field = array_append_space(&fields); field->name = "azp"; field->value = test_case->azp; } if (*test_case->kid != '\0') { field = array_append_space(&fields); field->name = "kid"; field->value = test_case->kid; } save_key_azp_to(test_case->alg, test_case->esc_azp, test_case->esc_kid, str_c(b64_key)); buffer_t *token = create_jwt_token_fields_kid(test_case->alg, test_case->kid, time(NULL)+500, time(NULL)-500, 0, &fields); sign_jwt_token_hs256(token, hs_sign_key); test_jwt_token(str_c(token)); } test_end(); } static void test_jwt_broken_token(void) { struct test_cases { const char *token; bool is_jwt; } test_cases[] = { { /* empty token */ .token = "", .is_jwt = FALSE }, { /* not base64 */ .token = "{\"alg\":\"HS256\":\"typ\":\"JWT\"}", .is_jwt = FALSE }, { /* not jwt */ .token = "aGVsbG8sIHdvcmxkCg", .is_jwt = FALSE }, { /* no alg field */ .token = "eyJ0eXAiOiAiSldUIn0.e30.e30", .is_jwt = FALSE }, { /* typ field is wrong */ .token = "e3R5cDogamtzLCBhbGc6IEhTMjU2fQ." "eyJhbGdvIjogIldURiIsICJ0eXAiOiAiSldUIn0." "q2wwwWWJVJxqw-J3uQ0DdlIyWfoZ7Z0QrdzvMW_B-jo", .is_jwt = FALSE }, { /* unknown algorithm */ .token = "eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJXVEYifQ." "eyJhbGdvIjogIldURiIsICJ0eXAiOiAiSldUIn0." "q2wwwWWJVJxqw-J3uQ0DdlIyWfoZ7Z0QrdzvMW_B-jo", .is_jwt = TRUE }, { /* truncated base64 */ .token = "yJhbGciOiJIUzI1NiIsInR5", .is_jwt = FALSE }, { /* missing body and signature */ .token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9", .is_jwt = FALSE }, { /* empty body and signature */ .token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..", .is_jwt = TRUE }, { /* empty signature */ .token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." "eyJleHAiOjE1ODEzMzA3OTN9.", .is_jwt = TRUE }, { /* bad signature */ .token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." "eyJleHAiOjE1ODEzMzA3OTN9." "q2wwwWWJVJxqw-J3uQ0DdlIyWfoZ7Z0QrdzvMW_B-jo", .is_jwt = TRUE }, { /* algorithm is 'none' */ .token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0." "eyJleHAiOjE1ODEzMzA3OTN9.", .is_jwt = TRUE } }; test_begin("JWT broken tokens"); for (size_t i = 0; i < N_ELEMENTS(test_cases); i++) T_BEGIN { struct test_cases *test_case = &test_cases[i]; struct oauth2_request req; const char *error = NULL; bool is_jwt; test_assert_idx(parse_jwt_token(&req, test_case->token, &is_jwt, &error) != 0, i); test_assert_idx(test_case->is_jwt == is_jwt, i); test_assert_idx(error != NULL, i); } T_END; test_end(); } static void test_jwt_bad_valid_token(void) { test_begin("JWT bad token tests"); time_t now = time(NULL); struct test_cases { time_t exp; time_t iat; time_t nbf; const char *key_values[20]; const char *error; } test_cases[] = { { /* "empty" token */ .exp = 0, .iat = 0, .nbf = 0, .key_values = { NULL }, .error = "Missing 'sub' field", }, { /* missing sub field */ .exp = now+500, .iat = 0, .nbf = 0, .key_values = { NULL }, .error = "Missing 'sub' field", }, { /* no expiration */ .key_values = { "sub", "testuser", NULL }, .error = "Missing 'exp' field", }, { /* non-ISO date as iat */ .exp = now+500, .iat = 0, .nbf = 0, .key_values = { "sub", "testuser", "iat", "1.1.2019 16:00", NULL }, .error = "Malformed 'iat' field" }, { /* expired token */ .exp = now-500, .iat = 0, .nbf = 0, .key_values = { "sub", "testuser", NULL }, .error = "Token has expired", }, { /* future token */ .exp = now+1000, .iat = now+500, .nbf = 0, .key_values = { "sub", "testuser", NULL }, .error = "Token is issued in future", }, { /* token not valid yet */ .exp = now+500, .iat = now, .nbf = now+250, .key_values = { "sub", "testuser", NULL }, .error = "Token is not valid yet", }, }; for (size_t i = 0; i < N_ELEMENTS(test_cases); i++) T_BEGIN { const struct test_cases *test_case = &test_cases[i]; const char *key = NULL; ARRAY_TYPE(oauth2_field) fields; t_array_init(&fields, 8); for (const char *const *value = test_case->key_values; *value != NULL; value++) { if (key == NULL) { key = *value; } else { struct oauth2_field *field = array_append_space(&fields); field->name = key; field->value = *value; key = NULL; } } buffer_t *tokenbuf = create_jwt_token_fields("HS256", test_case->exp, test_case->iat, test_case->nbf, &fields); sign_jwt_token_hs256(tokenbuf, hs_sign_key); struct oauth2_request req; const char *error = NULL; bool is_jwt; test_assert_idx(parse_jwt_token(&req, str_c(tokenbuf), &is_jwt, &error) != 0, i); test_assert_idx(is_jwt == TRUE, i); if (test_case->error != NULL) { test_assert_strcmp(test_case->error, error); } test_assert(error != NULL); } T_END; test_end(); } static void test_jwt_valid_token(void) { test_begin("JWT valid token tests"); time_t now = time(NULL); struct test_cases { time_t exp; time_t iat; time_t nbf; const char *key_values[20]; } test_cases[] = { { /* valid token */ .exp = now + 500, .key_values = { "sub", "testuser", NULL }, }, { .exp = now + 500, .nbf = now - 500, .iat = now - 250, .key_values = { "sub", "testuser", NULL }, }, { /* token issued in advance */ .exp = now + 500, .nbf = now - 500, .iat = now - 3600, .key_values = { "sub", "testuser", NULL, }, }, }; for (size_t i = 0; i < N_ELEMENTS(test_cases); i++) T_BEGIN { const struct test_cases *test_case = &test_cases[i]; ARRAY_TYPE(oauth2_field) fields; t_array_init(&fields, 8); for (unsigned int i = 0; test_case->key_values[i] != NULL; i += 2) { struct oauth2_field *field = array_append_space(&fields); field->name = test_case->key_values[i]; field->value = test_case->key_values[i+1]; } buffer_t *tokenbuf = create_jwt_token_fields("HS256", test_case->exp, test_case->iat, test_case->nbf, &fields); sign_jwt_token_hs256(tokenbuf, hs_sign_key); struct oauth2_request req; const char *error = NULL; bool is_jwt; test_assert_idx(parse_jwt_token(&req, str_c(tokenbuf), &is_jwt, &error) == 0, i); test_assert_idx(is_jwt == TRUE, i); test_assert_idx(error == NULL, i); if (error != NULL) i_error("JWT validation error: %s", error); } T_END; test_end(); } static void test_jwt_dates(void) { test_begin("JWT Token dates"); /* simple check to make sure ISO8601 dates work too */ ARRAY_TYPE(oauth2_field) fields; t_array_init(&fields, 8); struct oauth2_field *field; struct tm tm_b; struct tm *tm; time_t now = time(NULL); time_t exp = now+500; time_t nbf = now-250; time_t iat = now-500; field = array_append_space(&fields); field->name = "sub"; field->value = "testuser"; field = array_append_space(&fields); field->name = "exp"; tm = gmtime_r(&exp, &tm_b); field->value = iso8601_date_create_tm(tm, INT_MAX); field = array_append_space(&fields); field->name = "nbf"; tm = gmtime_r(&nbf, &tm_b); field->value = iso8601_date_create_tm(tm, INT_MAX); field = array_append_space(&fields); field->name = "iat"; tm = gmtime_r(&iat, &tm_b); field->value = iso8601_date_create_tm(tm, INT_MAX); buffer_t *tokenbuf = create_jwt_token_fields("HS256", 0, 0, 0, &fields); sign_jwt_token_hs256(tokenbuf, hs_sign_key); test_jwt_token(str_c(tokenbuf)); str_truncate(tokenbuf, 0); base64url_encode_str("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", tokenbuf); str_append_c(tokenbuf, '.'); base64url_encode_str(t_strdup_printf("{\"sub\":\"testuser\"," "\"exp\":%"PRIdTIME_T"," "\"nbf\":0,\"iat\":%"PRIdTIME_T"}", exp, iat), tokenbuf); sign_jwt_token_hs256(tokenbuf, hs_sign_key); test_jwt_token(str_c(tokenbuf)); test_end(); } static void test_jwt_key_files(void) { test_begin("JWT key id"); /* write HMAC secrets */ struct oauth2_request req; bool is_jwt; const char *error = NULL; buffer_t *secret = t_buffer_create(32); void *ptr = buffer_append_space_unsafe(secret, 32); random_fill(ptr, 32); buffer_t *b64_key = t_base64_encode(0, SIZE_MAX, secret->data, secret->used); save_key_to("HS256", "first", str_c(b64_key)); buffer_t *secret2 = t_buffer_create(32); ptr = buffer_append_space_unsafe(secret2, 32); random_fill(ptr, 32); b64_key = t_base64_encode(0, SIZE_MAX, secret2->data, secret2->used); save_key_to("HS256", "second", str_c(b64_key)); /* create and sign token */ buffer_t *token_1 = create_jwt_token_kid("HS256", "first"); buffer_t *token_2 = create_jwt_token_kid("HS256", "second"); buffer_t *token_3 = create_jwt_token_kid("HS256", "missing"); buffer_t *token_4 = create_jwt_token_kid("HS256", ""); sign_jwt_token_hs256(token_1, secret); sign_jwt_token_hs256(token_2, secret2); sign_jwt_token_hs256(token_3, secret); sign_jwt_token_hs256(token_4, secret); test_jwt_token(str_c(token_1)); test_jwt_token(str_c(token_2)); test_assert(parse_jwt_token(&req, str_c(token_3), &is_jwt, &error) != 0); test_assert(is_jwt == TRUE); test_assert_strcmp(error, "HS256 key 'missing' not found"); test_assert(parse_jwt_token(&req, str_c(token_4), &is_jwt, &error) != 0); test_assert(is_jwt == TRUE); test_assert_strcmp(error, "'kid' field is empty"); test_end(); } static void test_jwt_kid_escape(void) { test_begin("JWT kid escape"); /* save a token */ buffer_t *secret = t_buffer_create(32); void *ptr = buffer_append_space_unsafe(secret, 32); random_fill(ptr, 32); buffer_t *b64_key = t_base64_encode(0, SIZE_MAX, secret->data, secret->used); save_key_to("HS256", "hello.world%2f%25", str_c(b64_key)); /* make a token */ buffer_t *tokenbuf = create_jwt_token_kid("HS256", "hello.world/%"); /* sign it */ sign_jwt_token_hs256(tokenbuf, secret); test_jwt_token(str_c(tokenbuf)); test_end(); } static void test_jwt_rs_token(void) { const char *error; if (skip_dcrypt) return; test_begin("JWT RSA token"); /* write public key to file */ oauth2_validation_key_cache_evict(key_cache, "default"); save_key("RS256", rsa_public_key); buffer_t *tokenbuf = create_jwt_token("RS256"); /* sign token */ buffer_t *sig = t_buffer_create(64); struct dcrypt_private_key *key; if (!dcrypt_key_load_private(&key, rsa_private_key, NULL, NULL, &error) || !dcrypt_sign(key, "sha256", DCRYPT_SIGNATURE_FORMAT_DSS, tokenbuf->data, tokenbuf->used, sig, DCRYPT_PADDING_RSA_PKCS1, &error)) { i_error("dcrypt signing failed: %s", error); lib_exit(1); } dcrypt_key_unref_private(&key); /* convert to base64 */ buffer_append(tokenbuf, ".", 1); base64url_encode(BASE64_ENCODE_FLAG_NO_PADDING, SIZE_MAX, sig->data, sig->used, tokenbuf); test_jwt_token(str_c(tokenbuf)); test_end(); } static void test_jwt_ps_token(void) { const char *error; if (skip_dcrypt) return; test_begin("JWT RSAPSS token"); /* write public key to file */ oauth2_validation_key_cache_evict(key_cache, "default"); save_key("PS256", rsa_public_key); buffer_t *tokenbuf = create_jwt_token("PS256"); /* sign token */ buffer_t *sig = t_buffer_create(64); struct dcrypt_private_key *key; if (!dcrypt_key_load_private(&key, rsa_private_key, NULL, NULL, &error) || !dcrypt_sign(key, "sha256", DCRYPT_SIGNATURE_FORMAT_DSS, tokenbuf->data, tokenbuf->used, sig, DCRYPT_PADDING_RSA_PKCS1_PSS, &error)) { i_error("dcrypt signing failed: %s", error); lib_exit(1); } dcrypt_key_unref_private(&key); /* convert to base64 */ buffer_append(tokenbuf, ".", 1); base64url_encode(BASE64_ENCODE_FLAG_NO_PADDING, SIZE_MAX, sig->data, sig->used, tokenbuf); test_jwt_token(str_c(tokenbuf)); test_end(); } static void test_jwt_ec_token(void) { const char *error; if (skip_dcrypt) return; test_begin("JWT ECDSA token"); struct dcrypt_keypair pair; i_zero(&pair); if (!dcrypt_keypair_generate(&pair, DCRYPT_KEY_EC, 0, "prime256v1", &error)) { i_error("dcrypt keypair generate failed: %s", error); lib_exit(1); } /* export public key */ buffer_t *keybuf = t_buffer_create(256); if (!dcrypt_key_store_public(pair.pub, DCRYPT_FORMAT_PEM, keybuf, &error)) { i_error("dcrypt key store failed: %s", error); lib_exit(1); } oauth2_validation_key_cache_evict(key_cache, "default"); save_key("ES256", str_c(keybuf)); buffer_t *tokenbuf = create_jwt_token("ES256"); /* sign token */ buffer_t *sig = t_buffer_create(64); if (!dcrypt_sign(pair.priv, "sha256", DCRYPT_SIGNATURE_FORMAT_X962, tokenbuf->data, tokenbuf->used, sig, DCRYPT_PADDING_DEFAULT, &error)) { i_error("dcrypt signing failed: %s", error); lib_exit(1); } dcrypt_keypair_unref(&pair); /* convert to base64 */ buffer_append(tokenbuf, ".", 1); base64url_encode(BASE64_ENCODE_FLAG_NO_PADDING, SIZE_MAX, sig->data, sig->used, tokenbuf); test_jwt_token(str_c(tokenbuf)); test_end(); } static void test_do_init(void) { const char *error; struct dcrypt_settings dcrypt_set = { .module_dir = "../lib-dcrypt/.libs", }; struct dict_settings dict_set = { .base_dir = ".", }; i_unlink_if_exists(".keys"); dict_driver_register(&dict_driver_file); if (dict_init("file:.keys", &dict_set, &keys_dict, &error) < 0) i_fatal("dict_init(file:.keys): %s", error); if (!dcrypt_initialize(NULL, &dcrypt_set, &error)) { i_error("No functional dcrypt backend found - " "skipping some tests: %s", error); skip_dcrypt = TRUE; } key_cache = oauth2_validation_key_cache_init(); /* write HMAC secret */ hs_sign_key =buffer_create_dynamic(default_pool, 32); void *ptr = buffer_append_space_unsafe(hs_sign_key, 32); random_fill(ptr, 32); buffer_t *b64_key = t_base64_encode(0, SIZE_MAX, hs_sign_key->data, hs_sign_key->used); save_key("HS256", str_c(b64_key)); } static void test_do_deinit(void) { dict_deinit(&keys_dict); dict_driver_unregister(&dict_driver_file); oauth2_validation_key_cache_deinit(&key_cache); i_unlink(".keys"); buffer_free(&hs_sign_key); dcrypt_deinitialize(); } int main(void) { static void (*test_functions[])(void) = { test_do_init, test_jwt_hs_token, test_jwt_token_escape, test_jwt_valid_token, test_jwt_bad_valid_token, test_jwt_broken_token, test_jwt_dates, test_jwt_key_files, test_jwt_kid_escape, test_jwt_rs_token, test_jwt_ps_token, test_jwt_ec_token, test_do_deinit, NULL }; int ret; ret = test_run(test_functions); return ret; } dovecot-2.3.21.1/src/lib-oauth2/oauth2-key-cache.c0000644000000000000000000001023414656633576016247 00000000000000/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "llist.h" #include "buffer.h" #include "hash.h" #include "dcrypt.h" #include "oauth2.h" #include "oauth2-private.h" struct oauth2_key_cache_entry { const char *key_id; struct dcrypt_public_key *pubkey; buffer_t *hmac_key; struct oauth2_key_cache_entry *prev, *next; }; HASH_TABLE_DEFINE_TYPE(oauth2_key_cache, const char *, struct oauth2_key_cache_entry *); struct oauth2_validation_key_cache { pool_t pool; HASH_TABLE_TYPE(oauth2_key_cache) keys; struct oauth2_key_cache_entry *list_start; }; struct oauth2_validation_key_cache *oauth2_validation_key_cache_init(void) { pool_t pool = pool_alloconly_create( MEMPOOL_GROWING"oauth2 key cache", 128); struct oauth2_validation_key_cache *cache = p_new(pool, struct oauth2_validation_key_cache, 1); cache->pool = pool; hash_table_create(&cache->keys, pool, 8, str_hash, strcmp); return cache; } void oauth2_validation_key_cache_deinit( struct oauth2_validation_key_cache **_cache) { struct oauth2_validation_key_cache *cache = *_cache; *_cache = NULL; if (cache == NULL) return; /* free resources */ struct oauth2_key_cache_entry *entry = cache->list_start; while (entry != NULL) { if (entry->pubkey != NULL) dcrypt_key_unref_public(&entry->pubkey); entry = entry->next; } hash_table_destroy(&cache->keys); pool_unref(&cache->pool); } int oauth2_validation_key_cache_lookup_pubkey( struct oauth2_validation_key_cache *cache, const char *key_id, struct dcrypt_public_key **pubkey_r) { if (cache == NULL) return -1; struct oauth2_key_cache_entry *entry = hash_table_lookup(cache->keys, key_id); if (entry == NULL || entry->pubkey == NULL) return -1; *pubkey_r = entry->pubkey; return 0; } int oauth2_validation_key_cache_lookup_hmac_key( struct oauth2_validation_key_cache *cache, const char *key_id, const buffer_t **hmac_key_r) { if (cache == NULL) return -1; struct oauth2_key_cache_entry *entry = hash_table_lookup(cache->keys, key_id); if (entry == NULL || entry->hmac_key == NULL || entry->hmac_key->used == 0) return -1; *hmac_key_r = entry->hmac_key; return 0; } void oauth2_validation_key_cache_insert_pubkey( struct oauth2_validation_key_cache *cache, const char *key_id, struct dcrypt_public_key *pubkey) { if (cache == NULL) return; struct oauth2_key_cache_entry *entry = hash_table_lookup(cache->keys, key_id); if (entry != NULL) { dcrypt_key_unref_public(&entry->pubkey); entry->pubkey = pubkey; if (entry->hmac_key != NULL) buffer_set_used_size(entry->hmac_key, 0); return; } entry = p_new(cache->pool, struct oauth2_key_cache_entry, 1); entry->key_id = p_strdup(cache->pool, key_id); entry->pubkey = pubkey; DLLIST_PREPEND(&cache->list_start, entry); hash_table_insert(cache->keys, entry->key_id, entry); } void oauth2_validation_key_cache_insert_hmac_key( struct oauth2_validation_key_cache *cache, const char *key_id, const buffer_t *hmac_key) { if (cache == NULL) return; struct oauth2_key_cache_entry *entry = hash_table_lookup(cache->keys, key_id); if (entry != NULL) { dcrypt_key_unref_public(&entry->pubkey); if (entry->hmac_key == NULL) { entry->hmac_key = buffer_create_dynamic( cache->pool, hmac_key->used); } else { buffer_set_used_size(entry->hmac_key, 0); } buffer_append(entry->hmac_key, hmac_key->data, hmac_key->used); return; } entry = p_new(cache->pool, struct oauth2_key_cache_entry, 1); entry->key_id = p_strdup(cache->pool, key_id); entry->hmac_key = buffer_create_dynamic(cache->pool, hmac_key->used); buffer_append(entry->hmac_key, hmac_key->data, hmac_key->used); DLLIST_PREPEND(&cache->list_start, entry); hash_table_insert(cache->keys, entry->key_id, entry); } int oauth2_validation_key_cache_evict(struct oauth2_validation_key_cache *cache, const char *key_id) { if (cache == NULL) return -1; struct oauth2_key_cache_entry *entry = hash_table_lookup(cache->keys, key_id); if (entry == NULL) return -1; if (entry->pubkey != NULL) dcrypt_key_unref_public(&entry->pubkey); DLLIST_REMOVE(&cache->list_start, entry); hash_table_remove(cache->keys, key_id); return 0; } dovecot-2.3.21.1/src/lib-oauth2/test-oauth2-json.c0000644000000000000000000000516714656633576016355 00000000000000/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "json-parser.h" #include "oauth2.h" #include "oauth2-private.h" #include "test-common.h" static void test_oauth_json_valid_parsed(struct oauth2_request *req ATTR_UNUSED, const char *error) { test_assert(error == NULL); } static void test_oauth2_json_valid(void) { static const char *test_input = "{\"access_token\":\"9a2dea3c-f8be-4271-b9c8-5b37da4f2f7e\"," "\"grant_type\":\"authorization_code\"," "\"openid\":\"\"," "\"scope\":[\"openid\",\"profile\",\"email\"]," "\"profile\":\"\"," "\"realm\":\"/employees\"," "\"token_type\":\"Bearer\"," "\"expires_in\":2377," "\"client_id\":\"mosaic\"," "\"email\":\"\"," "\"extensions\":" "{\"algorithm\":\"cuttlefish\"," "\"tentacles\":8" "}" "}"; static const struct oauth2_field fields[] = { { .name = "access_token", .value = "9a2dea3c-f8be-4271-b9c8-5b37da4f2f7e" }, { .name = "grant_type", .value = "authorization_code" }, { .name = "openid", .value = "" }, { .name = "profile", .value = "" }, { .name = "realm", .value = "/employees" }, { .name = "token_type", .value = "Bearer" }, { .name = "expires_in", .value = "2377" }, { .name = "client_id", .value = "mosaic" }, { .name = "email", .value = "" }, }; static const unsigned int fields_count = N_ELEMENTS(fields); struct oauth2_request *req; const struct oauth2_field *pfields; unsigned int count, i; pool_t pool; size_t pos; test_begin("oauth json skip"); /* Create mock request */ pool = pool_alloconly_create_clean("oauth2 json test", 1024); req = p_new(pool, struct oauth2_request, 1); req->pool = pool; p_array_init(&req->fields, req->pool, 1); req->is = test_istream_create_data(test_input, strlen(test_input)); req->parser = json_parser_init(req->is); req->json_parsed_cb = test_oauth_json_valid_parsed; /* Parse the JSON response */ for (pos = 0; pos <= strlen(test_input); pos +=2) { test_istream_set_size(req->is, pos); oauth2_request_parse_json(req); if (req->is == NULL) break; } /* Verify the parsed fields */ pfields = array_get(&req->fields, &count); test_assert(count == fields_count); if (count > fields_count) count = fields_count; for (i = 0; i < count; i++) { test_assert(strcmp(pfields[i].name, fields[i].name) == 0); test_assert(strcmp(pfields[i].value, fields[i].value) == 0); } /* Clean up */ pool_unref(&req->pool); test_end(); } int main(void) { static void (*const test_functions[])(void) = { test_oauth2_json_valid, NULL }; return test_run(test_functions); } dovecot-2.3.21.1/src/lib-oauth2/Makefile.in0000644000000000000000000007324214656633610015114 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ pkglibexecdir = $(libexecdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ noinst_PROGRAMS = $(am__EXEEXT_1) subdir = src/lib-oauth2 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(pkginc_lib_HEADERS) $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__EXEEXT_1 = test-oauth2-json$(EXEEXT) test-oauth2-jwt$(EXEEXT) PROGRAMS = $(noinst_PROGRAMS) LTLIBRARIES = $(noinst_LTLIBRARIES) liboauth2_la_LIBADD = am_liboauth2_la_OBJECTS = oauth2.lo oauth2-request.lo oauth2-jwt.lo \ oauth2-key-cache.lo liboauth2_la_OBJECTS = $(am_liboauth2_la_OBJECTS) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = am_test_oauth2_json_OBJECTS = test-oauth2-json.$(OBJEXT) test_oauth2_json_OBJECTS = $(am_test_oauth2_json_OBJECTS) am__DEPENDENCIES_1 = am__DEPENDENCIES_2 = $(noinst_LTLIBRARIES) ../lib-dcrypt/libdcrypt.la \ ../lib-http/libhttp.la ../lib-dns/libdns.la \ ../lib-ssl-iostream/libssl_iostream.la \ ../lib-master/libmaster.la ../lib-auth/libauth.la \ ../lib-dict/libdict.la ../lib-settings/libsettings.la \ ../lib-test/libtest.la ../lib/liblib.la $(am__DEPENDENCIES_1) am_test_oauth2_jwt_OBJECTS = test-oauth2-jwt.$(OBJEXT) test_oauth2_jwt_OBJECTS = $(am_test_oauth2_jwt_OBJECTS) test_oauth2_jwt_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(test_oauth2_jwt_LDFLAGS) $(LDFLAGS) \ -o $@ AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/oauth2-jwt.Plo \ ./$(DEPDIR)/oauth2-key-cache.Plo \ ./$(DEPDIR)/oauth2-request.Plo ./$(DEPDIR)/oauth2.Plo \ ./$(DEPDIR)/test-oauth2-json.Po ./$(DEPDIR)/test-oauth2-jwt.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(liboauth2_la_SOURCES) $(test_oauth2_json_SOURCES) \ $(test_oauth2_jwt_SOURCES) DIST_SOURCES = $(liboauth2_la_SOURCES) $(test_oauth2_json_SOURCES) \ $(test_oauth2_jwt_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ *) f=$$p;; \ esac; am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; am__install_max = 40 am__nobase_strip_setup = \ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` am__nobase_strip = \ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" am__nobase_list = $(am__nobase_strip_setup); \ for p in $$list; do echo "$$p $$p"; done | \ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ if (++n[$$2] == $(am__install_max)) \ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ END { for (dir in files) print dir, files[dir] }' am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' am__uninstall_files_from_dir = { \ test -z "$$files" \ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ $(am__cd) "$$dir" && rm -f $$files; }; \ } am__installdirs = "$(DESTDIR)$(pkginc_libdir)" HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-http \ -I$(top_srcdir)/src/lib-dcrypt \ -I$(top_srcdir)/src/lib-dict \ -I$(top_srcdir)/src/lib-settings noinst_LTLIBRARIES = liboauth2.la pkginc_libdir = $(pkgincludedir) pkginc_lib_HEADERS = \ oauth2.h noinst_HEADERS = \ oauth2-private.h liboauth2_la_SOURCES = \ oauth2.c \ oauth2-request.c \ oauth2-jwt.c \ oauth2-key-cache.c test_programs = \ test-oauth2-json \ test-oauth2-jwt test_libs = \ $(noinst_LTLIBRARIES) \ ../lib-dcrypt/libdcrypt.la \ ../lib-http/libhttp.la \ ../lib-dns/libdns.la \ ../lib-ssl-iostream/libssl_iostream.la \ ../lib-master/libmaster.la \ ../lib-auth/libauth.la \ ../lib-dict/libdict.la \ ../lib-settings/libsettings.la \ ../lib-test/libtest.la \ ../lib/liblib.la \ $(MODULE_LIBS) test_deps = \ $(noinst_LTLIBRARIES) \ ../lib-dcrypt/libdcrypt.la \ ../lib-http/libhttp.la \ ../lib-dns/libdns.la \ ../lib-ssl-iostream/libssl_iostream.la \ ../lib-master/libmaster.la \ ../lib-auth/libauth.la \ ../lib-dict/libdict.la \ ../lib-settings/libsettings.la \ ../lib-test/libtest.la \ ../lib/liblib.la test_oauth2_json_SOURCES = test-oauth2-json.c test_oauth2_json_LDADD = $(test_libs) test_oauth2_json_DEPENDENCIES = $(test_deps) test_oauth2_jwt_SOURCES = test-oauth2-jwt.c test_oauth2_jwt_LDADD = $(test_libs) @HAVE_WHOLE_ARCHIVE_TRUE@test_oauth2_jwt_LDFLAGS = -Wl,$(LD_WHOLE_ARCHIVE),../lib-ssl-iostream/.libs/libssl_iostream.a,$(LD_NO_WHOLE_ARCHIVE) test_oauth2_jwt_DEPENDENCIES = $(test_deps) all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-oauth2/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/lib-oauth2/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): clean-noinstPROGRAMS: @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list clean-noinstLTLIBRARIES: -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) @list='$(noinst_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } liboauth2.la: $(liboauth2_la_OBJECTS) $(liboauth2_la_DEPENDENCIES) $(EXTRA_liboauth2_la_DEPENDENCIES) $(AM_V_CCLD)$(LINK) $(liboauth2_la_OBJECTS) $(liboauth2_la_LIBADD) $(LIBS) test-oauth2-json$(EXEEXT): $(test_oauth2_json_OBJECTS) $(test_oauth2_json_DEPENDENCIES) $(EXTRA_test_oauth2_json_DEPENDENCIES) @rm -f test-oauth2-json$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_oauth2_json_OBJECTS) $(test_oauth2_json_LDADD) $(LIBS) test-oauth2-jwt$(EXEEXT): $(test_oauth2_jwt_OBJECTS) $(test_oauth2_jwt_DEPENDENCIES) $(EXTRA_test_oauth2_jwt_DEPENDENCIES) @rm -f test-oauth2-jwt$(EXEEXT) $(AM_V_CCLD)$(test_oauth2_jwt_LINK) $(test_oauth2_jwt_OBJECTS) $(test_oauth2_jwt_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/oauth2-jwt.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/oauth2-key-cache.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/oauth2-request.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/oauth2.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-oauth2-json.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-oauth2-jwt.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) @$(NORMAL_INSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ done uninstall-pkginc_libHEADERS: @$(NORMAL_UNINSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am $(MAKE) $(AM_MAKEFLAGS) check-local check: check-am all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkginc_libdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ clean-noinstPROGRAMS mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/oauth2-jwt.Plo -rm -f ./$(DEPDIR)/oauth2-key-cache.Plo -rm -f ./$(DEPDIR)/oauth2-request.Plo -rm -f ./$(DEPDIR)/oauth2.Plo -rm -f ./$(DEPDIR)/test-oauth2-json.Po -rm -f ./$(DEPDIR)/test-oauth2-jwt.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-pkginc_libHEADERS install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/oauth2-jwt.Plo -rm -f ./$(DEPDIR)/oauth2-key-cache.Plo -rm -f ./$(DEPDIR)/oauth2-request.Plo -rm -f ./$(DEPDIR)/oauth2.Plo -rm -f ./$(DEPDIR)/test-oauth2-json.Po -rm -f ./$(DEPDIR)/test-oauth2-jwt.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkginc_libHEADERS .MAKE: check-am install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ check-local clean clean-generic clean-libtool \ clean-noinstLTLIBRARIES clean-noinstPROGRAMS cscopelist-am \ ctags ctags-am distclean distclean-compile distclean-generic \ distclean-libtool distclean-tags distdir dvi dvi-am html \ html-am info info-am install install-am install-data \ install-data-am install-dvi install-dvi-am install-exec \ install-exec-am install-html install-html-am install-info \ install-info-am install-man install-pdf install-pdf-am \ install-pkginc_libHEADERS install-ps install-ps-am \ install-strip installcheck installcheck-am installdirs \ maintainer-clean maintainer-clean-generic mostlyclean \ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ uninstall-pkginc_libHEADERS .PRECIOUS: Makefile check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/lib-oauth2/oauth2-jwt.c0000644000000000000000000003444014656633576015227 00000000000000/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "str.h" #include "strescape.h" #include "hmac.h" #include "array.h" #include "hash-method.h" #include "istream.h" #include "iso8601-date.h" #include "json-tree.h" #include "array.h" #include "base64.h" #include "str-sanitize.h" #include "dcrypt.h" #include "var-expand.h" #include "oauth2.h" #include "oauth2-private.h" #include "dict.h" #include static const char *get_field(const struct json_tree *tree, const char *key) { const struct json_tree_node *root = json_tree_root(tree); const struct json_tree_node *value_node = json_tree_find_key(root, key); if (value_node == NULL || value_node->value_type == JSON_TYPE_OBJECT || value_node->value_type == JSON_TYPE_ARRAY) return NULL; return json_tree_get_value_str(value_node); } static const char *get_field_multiple(const struct json_tree *tree, const char *key) { const struct json_tree_node *root = json_tree_root(tree); const struct json_tree_node *value_node = json_tree_find_key(root, key); if (value_node == NULL || value_node->value_type == JSON_TYPE_OBJECT) return NULL; if (value_node->value_type != JSON_TYPE_ARRAY) return json_tree_get_value_str(value_node); const struct json_tree_node *entry_node = json_tree_get_child(value_node); string_t *values = t_str_new(64); for (; entry_node != NULL; entry_node = entry_node->next) { if (entry_node->value_type == JSON_TYPE_OBJECT || entry_node->value_type == JSON_TYPE_ARRAY) continue; const char *value_str = json_tree_get_value_str(entry_node); if (str_len(values) > 0) str_append_c(values, '\t'); str_append_tabescaped(values, value_str); } return str_c(values); } static int get_time_field(const struct json_tree *tree, const char *key, int64_t *value_r) { time_t tvalue; const char *value = get_field(tree, key); int tz_offset ATTR_UNUSED; if (value == NULL) return 0; if (str_to_int64(value, value_r) == 0) { if (*value_r < 0) return -1; return 1; } else if (iso8601_date_parse((const unsigned char*)value, strlen(value), &tvalue, &tz_offset)) { if (tvalue < 0) return -1; *value_r = tvalue; return 1; } return -1; } /* Escapes '/' and '%' in identifier to %hex */ static const char *escape_identifier(const char *identifier) { size_t pos = strcspn(identifier, "/%"); /* nothing to escape */ if (identifier[pos] == '\0') return identifier; size_t len = strlen(identifier); string_t *new_id = t_str_new(len); str_append_data(new_id, identifier, pos); for (size_t i = pos; i < len; i++) { switch (identifier[i]) { case '/': str_append(new_id, "%2f"); break; case '%': str_append(new_id, "%25"); break; default: str_append_c(new_id, identifier[i]); break; } } return str_c(new_id); } static int oauth2_lookup_hmac_key(const struct oauth2_settings *set, const char *azp, const char *alg, const char *key_id, const buffer_t **hmac_key_r, const char **error_r) { const char *base64_key; const char *cache_key_id, *lookup_key; int ret; cache_key_id = t_strconcat(azp, ".", alg, ".", key_id, NULL); if (oauth2_validation_key_cache_lookup_hmac_key( set->key_cache, cache_key_id, hmac_key_r) == 0) return 0; /* do a synchronous dict lookup */ lookup_key = t_strconcat(DICT_PATH_SHARED, azp, "/", alg, "/", key_id, NULL); struct dict_op_settings dict_set = { .username = NULL, }; if ((ret = dict_lookup(set->key_dict, &dict_set, pool_datastack_create(), lookup_key, &base64_key, error_r)) < 0) { return -1; } else if (ret == 0) { *error_r = t_strdup_printf("%s key '%s' not found", alg, key_id); return -1; } /* decode key */ buffer_t *key = t_base64_decode_str(base64_key); if (key->used == 0) { *error_r = "Invalid base64 encoded key"; return -1; } oauth2_validation_key_cache_insert_hmac_key(set->key_cache, cache_key_id, key); *hmac_key_r = key; return 0; } static int oauth2_validate_hmac(const struct oauth2_settings *set, const char *azp, const char *alg, const char *key_id, const char *const *blobs, const char **error_r) { const struct hash_method *method; if (strcmp(alg, "HS256") == 0) method = hash_method_lookup("sha256"); else if (strcmp(alg, "HS384") == 0) method = hash_method_lookup("sha384"); else if (strcmp(alg, "HS512") == 0) method = hash_method_lookup("sha512"); else { *error_r = t_strdup_printf("unsupported algorithm '%s'", alg); return -1; } const buffer_t *key; if (oauth2_lookup_hmac_key(set, azp, alg, key_id, &key, error_r) < 0) return -1; struct hmac_context ctx; hmac_init(&ctx, key->data, key->used, method); hmac_update(&ctx, blobs[0], strlen(blobs[0])); hmac_update(&ctx, ".", 1); hmac_update(&ctx, blobs[1], strlen(blobs[1])); unsigned char digest[method->digest_size]; hmac_final(&ctx, digest); buffer_t *their_digest = t_base64url_decode_str(BASE64_DECODE_FLAG_NO_PADDING, blobs[2]); if (method->digest_size != their_digest->used || !mem_equals_timing_safe(digest, their_digest->data, method->digest_size)) { *error_r = "Incorrect JWT signature"; return -1; } return 0; } static int oauth2_lookup_pubkey(const struct oauth2_settings *set, const char *azp, const char *alg, const char *key_id, struct dcrypt_public_key **key_r, const char **error_r) { const char *key_str; const char *cache_key_id, *lookup_key; int ret; cache_key_id = t_strconcat(azp, ".", alg, ".", key_id, NULL); if (oauth2_validation_key_cache_lookup_pubkey( set->key_cache, cache_key_id, key_r) == 0) return 0; /* do a synchronous dict lookup */ lookup_key = t_strconcat(DICT_PATH_SHARED, azp, "/", alg, "/", key_id, NULL); struct dict_op_settings dict_set = { .username = NULL, }; if ((ret = dict_lookup(set->key_dict, &dict_set, pool_datastack_create(), lookup_key, &key_str, error_r)) < 0) { return -1; } else if (ret == 0) { *error_r = t_strdup_printf("%s key '%s' not found", alg, key_id); return -1; } /* try to load key */ struct dcrypt_public_key *pubkey; const char *error; if (!dcrypt_key_load_public(&pubkey, key_str, &error)) { *error_r = t_strdup_printf("Cannot load key: %s", error); return -1; } /* cache key */ oauth2_validation_key_cache_insert_pubkey(set->key_cache, cache_key_id, pubkey); *key_r = pubkey; return 0; } static int oauth2_validate_rsa_ecdsa(const struct oauth2_settings *set, const char *azp, const char *alg, const char *key_id, const char *const *blobs, const char **error_r) { const char *method; enum dcrypt_padding padding; enum dcrypt_signature_format sig_format; if (!dcrypt_is_initialized()) { *error_r = "No crypto library loaded"; return -1; } if (str_begins(alg, "RS")) { padding = DCRYPT_PADDING_RSA_PKCS1; sig_format = DCRYPT_SIGNATURE_FORMAT_DSS; } else if (str_begins(alg, "PS")) { padding = DCRYPT_PADDING_RSA_PKCS1_PSS; sig_format = DCRYPT_SIGNATURE_FORMAT_DSS; } else if (str_begins(alg, "ES")) { padding = DCRYPT_PADDING_DEFAULT; sig_format = DCRYPT_SIGNATURE_FORMAT_X962; } else { /* this should be checked by caller */ i_unreached(); } if (strcmp(alg+2, "256") == 0) { method = "sha256"; } else if (strcmp(alg+2, "384") == 0) { method = "sha384"; } else if (strcmp(alg+2, "512") == 0) { method = "sha512"; } else { *error_r = t_strdup_printf("Unsupported algorithm '%s'", alg); return -1; } buffer_t *signature = t_base64url_decode_str(BASE64_DECODE_FLAG_NO_PADDING, blobs[2]); struct dcrypt_public_key *pubkey; if (oauth2_lookup_pubkey(set, azp, alg, key_id, &pubkey, error_r) < 0) return -1; /* data to verify */ const char *data = t_strconcat(blobs[0], ".", blobs[1], NULL); /* verify signature */ bool valid; if (!dcrypt_verify(pubkey, method, sig_format, data, strlen(data), signature->data, signature->used, &valid, padding, error_r)) { valid = FALSE; } else if (!valid) { *error_r = "Bad signature"; } return valid ? 0 : -1; } static int oauth2_validate_signature(const struct oauth2_settings *set, const char *azp, const char *alg, const char *key_id, const char *const *blobs, const char **error_r) { if (str_begins(alg, "HS")) { return oauth2_validate_hmac(set, azp, alg, key_id, blobs, error_r); } else if (str_begins(alg, "RS") || str_begins(alg, "PS") || str_begins(alg, "ES")) { return oauth2_validate_rsa_ecdsa(set, azp, alg, key_id, blobs, error_r); } *error_r = t_strdup_printf("Unsupported algorithm '%s'", alg); return -1; } static void oauth2_jwt_copy_fields(ARRAY_TYPE(oauth2_field) *fields, struct json_tree *tree) { pool_t pool = array_get_pool(fields); ARRAY(const struct json_tree_node*) nodes; const struct json_tree_node *root = json_tree_root(tree); t_array_init(&nodes, 1); array_push_back(&nodes, &root); while (array_count(&nodes) > 0) { const struct json_tree_node *const *pnode = array_front(&nodes); const struct json_tree_node *node = *pnode; array_pop_front(&nodes); while (node != NULL) { if (node->value_type == JSON_TYPE_OBJECT) { root = node->value.child; array_push_back(&nodes, &root); } else if (node->key != NULL) { struct oauth2_field *field = array_append_space(fields); field->name = p_strdup(pool, node->key); field->value = p_strdup( pool, json_tree_get_value_str(node)); } node = node->next; } } } static int oauth2_jwt_header_process(struct json_tree *tree, const char **alg_r, const char **kid_r, const char **error_r) { const char *alg = get_field(tree, "alg"); const char *kid = get_field(tree, "kid"); if (alg == NULL) { *error_r = "Cannot find 'alg' field"; return -1; } /* These are lost when tree is deinitialized. Make sure algorithm is uppercased. */ *alg_r = t_str_ucase(alg); *kid_r = t_strdup(kid); return 0; } static bool check_scope(const char *req, const char *got) { const char *const *scope_req = t_strsplit_spaces(req, " ,"); const char *const *scope_got = t_strsplit_spaces(got, " ,"); for (; *scope_req != NULL; scope_req++) if (!str_array_icase_find(scope_got, *scope_req)) return FALSE; return TRUE; } static int oauth2_jwt_body_process(const struct oauth2_settings *set, const char *alg, const char *kid, ARRAY_TYPE(oauth2_field) *fields, struct json_tree *tree, const char *const *blobs, const char **error_r) { const char *sub = get_field(tree, "sub"); int ret; int64_t t0 = time(NULL); /* default IAT and NBF to now */ int64_t iat, nbf, exp; int tz_offset ATTR_UNUSED; if (sub == NULL) { *error_r = "Missing 'sub' field"; return -1; } if ((ret = get_time_field(tree, "exp", &exp)) < 1) { *error_r = t_strdup_printf("%s 'exp' field", ret == 0 ? "Missing" : "Malformed"); return -1; } if ((ret = get_time_field(tree, "nbf", &nbf)) < 0) { *error_r = "Malformed 'nbf' field"; return -1; } else if (ret == 0 || nbf == 0) nbf = t0; if ((ret = get_time_field(tree, "iat", &iat)) < 0) { *error_r = "Malformed 'iat' field"; return -1; } else if (ret == 0 || iat == 0) iat = t0; if (nbf > t0) { *error_r = "Token is not valid yet"; return -1; } if (iat > t0) { *error_r = "Token is issued in future"; return -1; } if (exp < t0) { *error_r = "Token has expired"; return -1; } /* ensure token dates are not conflicting */ if (exp < iat || exp < nbf) { *error_r = "Token time values are conflicting"; return -1; } const char *iss = get_field(tree, "iss"); if (set->issuers != NULL && *set->issuers != NULL) { if (iss == NULL) { *error_r = "Token is missing 'iss' field"; return -1; } if (!str_array_find(set->issuers, iss)) { *error_r = t_strdup_printf("Issuer '%s' is not allowed", str_sanitize_utf8(iss, 128)); return -1; } } const char *aud = get_field_multiple(tree, "aud"); /* if there is client_id configured, then aud should be present */ if (set->client_id != NULL && *set->client_id != '\0') { if (aud == NULL) { *error_r = "client_id set but aud is missing"; return -1; } const char *const *auds = t_strsplit_tabescaped(aud); if (!str_array_find(auds, set->client_id)) { *error_r = "client_id not found in aud field"; return -1; } } const char *got_scope = get_field(tree, "scope"); const char *req_scope = set->scope; if (req_scope != NULL && *req_scope != '\0') { if (got_scope == NULL) { *error_r = "scope set but not found in token"; return -1; } if (!check_scope(req_scope, got_scope)) { *error_r = t_strdup_printf("configured scope '%s' missing from token scope '%s'", req_scope, got_scope); return -1; } } /* see if there is azp */ const char *azp = get_field(tree, "azp"); if (azp == NULL) azp = "default"; else azp = escape_identifier(azp); if (oauth2_validate_signature(set, azp, alg, kid, blobs, error_r) < 0) return -1; oauth2_jwt_copy_fields(fields, tree); return 0; } int oauth2_try_parse_jwt(const struct oauth2_settings *set, const char *token, ARRAY_TYPE(oauth2_field) *fields, bool *is_jwt_r, const char **error_r) { const char *const *blobs = t_strsplit(token, "."); int ret; i_assert(set->key_dict != NULL); /* we don't know if it's JWT token yet */ *is_jwt_r = FALSE; if (str_array_length(blobs) != 3) { *error_r = "Not a JWT token"; return -1; } /* attempt to decode header */ buffer_t *header = t_base64url_decode_str(BASE64_DECODE_FLAG_NO_PADDING, blobs[0]); if (header->used == 0) { *error_r = "Not a JWT token"; return -1; } struct json_tree *header_tree; if (oauth2_json_tree_build(header, &header_tree, error_r) < 0) return -1; const char *alg, *kid; ret = oauth2_jwt_header_process(header_tree, &alg, &kid, error_r); json_tree_deinit(&header_tree); if (ret < 0) return -1; /* it is now assumed to be a JWT token */ *is_jwt_r = TRUE; if (kid == NULL) kid = "default"; else if (*kid == '\0') { *error_r = "'kid' field is empty"; return -1; } else { kid = escape_identifier(kid); } /* parse body */ struct json_tree *body_tree; buffer_t *body = t_base64url_decode_str(BASE64_DECODE_FLAG_NO_PADDING, blobs[1]); if (oauth2_json_tree_build(body, &body_tree, error_r) == -1) return -1; ret = oauth2_jwt_body_process(set, alg, kid, fields, body_tree, blobs, error_r); json_tree_deinit(&body_tree); return ret; } dovecot-2.3.21.1/src/lib-oauth2/oauth2.h0000644000000000000000000001140414656633576014425 00000000000000#ifndef OAUTH2_H #define OAUTH2_H #include "net.h" struct dict; struct oauth2_request; struct oauth2_validation_key_cache; struct oauth2_field { const char *name; const char *value; }; ARRAY_DEFINE_TYPE(oauth2_field, struct oauth2_field); struct oauth2_settings { struct http_client *client; /* GET tokeninfo from this URL, token is appended to URL http://some.host/path?access_token= */ const char *tokeninfo_url; /* POST grant password here, needs user credentials and client_* settings */ const char *grant_url; /* GET more information from this URL, uses Bearer authentication */ const char *introspection_url; /* POST refresh here, needs refresh token and client_* settings */ const char *refresh_url; /* client identificator for oauth2 server */ const char *client_id; /* client secret for oauth2 server */ const char *client_secret; /* access request scope for oauth2 server (optional) */ const char *scope; /* key dict for looking up validation keys */ struct dict *key_dict; /* cache for validation keys */ struct oauth2_validation_key_cache *key_cache; /* valid issuer names */ const char *const *issuers; enum { INTROSPECTION_MODE_GET_AUTH, INTROSPECTION_MODE_GET, INTROSPECTION_MODE_POST, INTROSPECTION_MODE_LOCAL, } introspection_mode; unsigned int timeout_msecs; /* Should X-Dovecot-Auth-* headers be sent */ bool send_auth_headers; /* Should use grant password mechanism for authentication */ bool use_grant_password; }; struct oauth2_request_result { /* Oauth2 server response fields */ ARRAY_TYPE(oauth2_field) *fields; /* Non-NULL if there was an unexpected internal error. */ const char *error; /* timestamp token expires at */ time_t expires_at; /* User authenticated successfully. Implies that error==NULL. */ bool valid:1; }; struct oauth2_request_input { const char *token; const char *service; struct ip_addr local_ip, real_local_ip, remote_ip, real_remote_ip; in_port_t local_port, real_local_port, remote_port, real_remote_port; }; typedef void oauth2_request_callback_t(struct oauth2_request_result*, void*); bool oauth2_valid_token(const char *token); struct oauth2_request* oauth2_passwd_grant_start(const struct oauth2_settings *set, const struct oauth2_request_input *input, const char *username, const char *password, oauth2_request_callback_t *callback, void *context); #define oauth2_passwd_grant_start(set, input, username, password, callback, \ context) \ oauth2_passwd_grant_start( \ set, input - CALLBACK_TYPECHECK( \ callback, void(*)(struct oauth2_request_result*, \ typeof(context))), \ username, password, \ (oauth2_request_callback_t*)callback, (void*)context); struct oauth2_request* oauth2_token_validation_start(const struct oauth2_settings *set, const struct oauth2_request_input *input, oauth2_request_callback_t *callback, void *context); #define oauth2_token_validation_start(set, input, callback, context) \ oauth2_token_validation_start( \ set, input - CALLBACK_TYPECHECK( \ callback, void(*)(struct oauth2_request_result*, \ typeof(context))), \ (oauth2_request_callback_t*)callback, (void*)context); struct oauth2_request* oauth2_introspection_start(const struct oauth2_settings *set, const struct oauth2_request_input *input, oauth2_request_callback_t *callback, void *context); #define oauth2_introspection_start(set, input, callback, context) \ oauth2_introspection_start( \ set, input - CALLBACK_TYPECHECK( \ callback, void(*)(struct oauth2_request_result*, \ typeof(context))), \ (oauth2_request_callback_t*)callback, (void*)context); struct oauth2_request * oauth2_refresh_start(const struct oauth2_settings *set, const struct oauth2_request_input *input, oauth2_request_callback_t *callback, void *context); #define oauth2_refresh_start(set, input, callback, context) \ oauth2_refresh_start( \ set, input - CALLBACK_TYPECHECK( \ callback, void(*)(struct oauth2_request_result*, \ typeof(context))), \ (oauth2_request_callback_t*)callback, (void*)context); /* Abort without calling callback, use this to cancel the request */ void oauth2_request_abort(struct oauth2_request **); int oauth2_try_parse_jwt(const struct oauth2_settings *set, const char *token, ARRAY_TYPE(oauth2_field) *fields, bool *is_jwt_r, const char **error_r); /* Initialize validation key cache */ struct oauth2_validation_key_cache *oauth2_validation_key_cache_init(void); /* Evict given key ID from cache, returns 0 on successful eviction */ int oauth2_validation_key_cache_evict(struct oauth2_validation_key_cache *cache, const char *key_id); /* Deinitialize validation key cache */ void oauth2_validation_key_cache_deinit( struct oauth2_validation_key_cache **_cache); #endif dovecot-2.3.21.1/src/lib-oauth2/oauth2-request.c0000644000000000000000000002377114656633576016120 00000000000000/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "istream.h" #include "str.h" #include "http-client.h" #include "http-url.h" #include "json-parser.h" #include "oauth2.h" #include "oauth2-private.h" static void oauth2_request_free(struct oauth2_request *req) { timeout_remove(&req->to_delayed_error); pool_unref(&req->pool); } static void oauth2_request_callback(struct oauth2_request *req, struct oauth2_request_result *res) { i_assert(req->req_callback != NULL); oauth2_request_callback_t *callback = req->req_callback; req->req_callback = NULL; callback(res, req->req_context); oauth2_request_free(req); } static bool oauth2_request_field_parse(const struct oauth2_field *field, struct oauth2_request_result *res) { if (strcasecmp(field->name, "expires_in") == 0) { uint32_t expires_in = 0; if (str_to_uint32(field->value, &expires_in) < 0) { res->error = t_strdup_printf( "Malformed number '%s' in expires_in", field->value); return FALSE; } else { res->expires_at = ioloop_time + expires_in; } } else if (strcasecmp(field->name, "token_type") == 0) { if (strcasecmp(field->value, "bearer") != 0) { res->error = t_strdup_printf( "Expected Bearer token, got '%s'", field->value); return FALSE; } } return TRUE; } static void oauth2_request_continue(struct oauth2_request *req, const char *error) { struct oauth2_request_result res; i_zero(&res); unsigned int status_hi = req->response_status/100; i_assert(status_hi == 2 || status_hi == 4); if (error != NULL) res.error = error; else { const struct oauth2_field *field; /* see if we can figure out when it expires */ array_foreach(&req->fields, field) { if (!oauth2_request_field_parse(field, &res)) break; } res.valid = (status_hi == 2) && res.error == NULL; } res.fields = &req->fields; oauth2_request_callback(req, &res); } void oauth2_request_parse_json(struct oauth2_request *req) { enum json_type type; const char *token, *error; int ret; while((ret = json_parse_next(req->parser, &type, &token)) > 0) { if (req->field_name == NULL) { if (type != JSON_TYPE_OBJECT_KEY) break; /* cannot use t_strdup because we might have to read more */ req->field_name = p_strdup(req->pool, token); } else if (type < JSON_TYPE_STRING) { /* this should be last allocation */ p_free(req->pool, req->field_name); json_parse_skip(req->parser); } else { if (!array_is_created(&req->fields)) p_array_init(&req->fields, req->pool, 4); struct oauth2_field *field = array_append_space(&req->fields); field->name = req->field_name; req->field_name = NULL; field->value = p_strdup(req->pool, token); } } /* read more */ if (ret == 0) return; io_remove(&req->io); if (ret > 0) { (void)json_parser_deinit(&req->parser, &error); error = "Invalid response data"; } else if (i_stream_read_eof(req->is) && req->is->v_offset == 0 && req->is->stream_errno == 0) { /* discard error, empty response is OK. */ (void)json_parser_deinit(&req->parser, &error); error = NULL; } else if (json_parser_deinit(&req->parser, &error) == 0) { error = NULL; } else { i_assert(error != NULL); } i_stream_unref(&req->is); req->json_parsed_cb(req, error); } static void oauth2_request_response(const struct http_response *response, struct oauth2_request *req) { req->response_status = response->status; unsigned int status_hi = req->response_status/100; if (status_hi != 2 && status_hi != 4) { /* Unexpected internal error */ struct oauth2_request_result res = { .error = http_response_get_message(response), }; oauth2_request_callback(req, &res); return; } if (response->payload != NULL) { req->is = response->payload; i_stream_ref(req->is); } else { req->is = i_stream_create_from_data("", 0); } p_array_init(&req->fields, req->pool, 1); req->parser = json_parser_init(req->is); req->json_parsed_cb = oauth2_request_continue; req->io = io_add_istream(req->is, oauth2_request_parse_json, req); oauth2_request_parse_json(req); } static void oauth2_request_fail(struct oauth2_request *req) { struct oauth2_request_result res = { .error = "No token provided", .valid = FALSE, }; oauth2_request_callback(req, &res); } static void oauth2_request_set_headers(struct oauth2_request *req, const struct oauth2_request_input *input) { if (!req->set->send_auth_headers) return; if (input->service != NULL) { http_client_request_add_header( req->req, "X-Dovecot-Auth-Service", input->service); } if (input->local_ip.family != 0) { const char *addr; if (net_ipport2str(&input->local_ip, input->local_port, &addr) == 0) { http_client_request_add_header( req->req, "X-Dovecot-Auth-Local", addr); } } if (input->remote_ip.family != 0) { const char *addr; if (net_ipport2str(&input->remote_ip, input->remote_port, &addr) == 0) { http_client_request_add_header( req->req, "X-Dovecot-Auth-Remote", addr); } } } static struct oauth2_request * oauth2_request_start(const struct oauth2_settings *set, const struct oauth2_request_input *input, oauth2_request_callback_t *callback, void *context, pool_t p, const char *method, const char *url, const string_t *payload, bool add_auth_bearer) { pool_t pool = (p == NULL) ? pool_alloconly_create_clean("oauth2 request", 1024) : p; struct oauth2_request *req = p_new(pool, struct oauth2_request, 1); req->pool = pool; req->set = set; req->req_callback = callback; req->req_context = context; if (!oauth2_valid_token(input->token)) { req->to_delayed_error = timeout_add_short(0, oauth2_request_fail, req); return req; } req->req = http_client_request_url_str(req->set->client, method, url, oauth2_request_response, req); oauth2_request_set_headers(req, input); if (payload != NULL && strcmp(method, "POST") == 0) { struct istream *is = i_stream_create_from_string(payload); http_client_request_add_header( req->req, "Content-Type", "application/x-www-form-urlencoded"); http_client_request_set_payload(req->req, is, FALSE); i_stream_unref(&is); } if (add_auth_bearer && http_client_request_get_origin_url(req->req)->user == NULL && set->introspection_mode == INTROSPECTION_MODE_GET_AUTH) { http_client_request_add_header(req->req, "Authorization", t_strdup_printf("Bearer %s", input->token)); } http_client_request_set_timeout_msecs(req->req, req->set->timeout_msecs); http_client_request_submit(req->req); return req; } #undef oauth2_refresh_start struct oauth2_request * oauth2_refresh_start(const struct oauth2_settings *set, const struct oauth2_request_input *input, oauth2_request_callback_t *callback, void *context) { string_t *payload = t_str_new(128); str_append(payload, "grant_type=refresh_token&refresh_token="); http_url_escape_param(payload, input->token); return oauth2_request_start(set, input, callback, context, NULL, "POST", set->refresh_url, NULL, FALSE); } #undef oauth2_introspection_start struct oauth2_request * oauth2_introspection_start(const struct oauth2_settings *set, const struct oauth2_request_input *input, oauth2_request_callback_t *callback, void *context) { string_t *enc; const char *url; const char *method; string_t *payload = NULL; pool_t p = NULL; switch (set->introspection_mode) { case INTROSPECTION_MODE_GET: enc = t_str_new(64); str_append(enc, set->introspection_url); http_url_escape_param(enc, input->token); if (*set->client_id != '\0') { str_append(enc, "&client_id="); http_url_escape_param(enc, set->client_id); } if (*set->client_secret != '\0') { str_append(enc, "&client_secret="); http_url_escape_param(enc, set->client_secret); } url = str_c(enc); method = "GET"; break; case INTROSPECTION_MODE_GET_AUTH: url = set->introspection_url; method = "GET"; break; case INTROSPECTION_MODE_POST: p = pool_alloconly_create_clean("oauth2 request", 1024); payload = str_new(p, strlen(input->token)+6); str_append(payload, "token="); http_url_escape_param(payload, input->token); url = set->introspection_url; method = "POST"; break; default: i_unreached(); break; } return oauth2_request_start(set, input, callback, context, p, method, url, payload, TRUE); } #undef oauth2_token_validation_start struct oauth2_request * oauth2_token_validation_start(const struct oauth2_settings *set, const struct oauth2_request_input *input, oauth2_request_callback_t *callback, void *context) { string_t *enc = t_str_new(64); str_append(enc, set->tokeninfo_url); http_url_escape_param(enc, input->token); return oauth2_request_start(set, input, callback, context, NULL, "GET", str_c(enc), NULL, TRUE); } #undef oauth2_passwd_grant_start struct oauth2_request * oauth2_passwd_grant_start(const struct oauth2_settings *set, const struct oauth2_request_input *input, const char *username, const char *password, oauth2_request_callback_t *callback, void *context) { pool_t pool = pool_alloconly_create_clean("oauth2 request", 1024); string_t *payload = str_new(pool, 128); /* add token */ str_append(payload, "grant_type=password&username="); http_url_escape_param(payload, username); str_append(payload, "&password="); http_url_escape_param(payload, password); if (*set->client_id != '\0') { str_append(payload, "&client_id="); http_url_escape_param(payload, set->client_id); } if (*set->client_secret != '\0') { str_append(payload, "&client_secret="); http_url_escape_param(payload, set->client_secret); } if (set->scope[0] != '\0') { str_append(payload, "&scope="); http_url_escape_param(payload, set->scope); } return oauth2_request_start(set, input, callback, context, pool, "POST", set->grant_url, payload, FALSE); } void oauth2_request_abort(struct oauth2_request **_req) { struct oauth2_request *req = *_req; *_req = NULL; http_client_request_abort(&req->req); oauth2_request_free(req); } dovecot-2.3.21.1/src/lib-oauth2/oauth2-private.h0000644000000000000000000000260314656633576016076 00000000000000#ifndef OAUTH2_PRIVATE_H #define OAUTH2_PRIVATE_H struct json_tree; struct dcrypt_public_key; struct oauth2_request { pool_t pool; const struct oauth2_settings *set; struct http_client_request *req; struct json_parser *parser; struct istream *is; struct io *io; const char *delayed_error; struct timeout *to_delayed_error; const char *username; const char *key_file_template; void (*json_parsed_cb)(struct oauth2_request *, const char *error); ARRAY_TYPE(oauth2_field) fields; char *field_name; oauth2_request_callback_t *req_callback; void *req_context; /* indicates whether token is valid */ unsigned int response_status; }; void oauth2_request_parse_json(struct oauth2_request *req); int oauth2_json_tree_build(const buffer_t *json, struct json_tree **tree_r, const char **error_r); int oauth2_validation_key_cache_lookup_pubkey( struct oauth2_validation_key_cache *cache, const char *key_id, struct dcrypt_public_key **pubkey_r); int oauth2_validation_key_cache_lookup_hmac_key( struct oauth2_validation_key_cache *cache, const char *key_id, const buffer_t **hmac_key_r); void oauth2_validation_key_cache_insert_pubkey( struct oauth2_validation_key_cache *cache, const char *key_id, struct dcrypt_public_key *pubkey); void oauth2_validation_key_cache_insert_hmac_key( struct oauth2_validation_key_cache *cache, const char *key_id, const buffer_t *hmac_key); #endif dovecot-2.3.21.1/src/lib-oauth2/oauth2.c0000644000000000000000000000203314656633576014416 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "istream.h" #include "json-tree.h" #include "oauth2.h" #include "oauth2-private.h" int oauth2_json_tree_build(const buffer_t *json, struct json_tree **tree_r, const char **error_r) { struct istream *is = i_stream_create_from_buffer(json); struct json_parser *parser = json_parser_init(is); struct json_tree *tree = json_tree_init(); enum json_type type; const char *value; int ret; while ((ret = json_parse_next(parser, &type, &value)) > 0) { /* this is safe to reuse here because it gets rewritten in while loop */ ret = json_tree_append(tree, type, value); i_assert(ret == 0); } i_assert(ret != 0); ret = json_parser_deinit(&parser, error_r); i_stream_unref(&is); if (ret != 0) json_tree_deinit(&tree); else *tree_r = tree; return ret; } bool oauth2_valid_token(const char *token) { if (token == NULL || *token == '\0' || strpbrk(token, "\r\n") != NULL) return FALSE; return TRUE; } dovecot-2.3.21.1/src/lib-oauth2/Makefile.am0000644000000000000000000000323714656633576015113 00000000000000AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-http \ -I$(top_srcdir)/src/lib-dcrypt \ -I$(top_srcdir)/src/lib-dict \ -I$(top_srcdir)/src/lib-settings noinst_LTLIBRARIES=liboauth2.la pkginc_libdir=$(pkgincludedir) pkginc_lib_HEADERS = \ oauth2.h noinst_HEADERS = \ oauth2-private.h liboauth2_la_SOURCES = \ oauth2.c \ oauth2-request.c \ oauth2-jwt.c \ oauth2-key-cache.c test_programs = \ test-oauth2-json \ test-oauth2-jwt noinst_PROGRAMS = $(test_programs) test_libs = \ $(noinst_LTLIBRARIES) \ ../lib-dcrypt/libdcrypt.la \ ../lib-http/libhttp.la \ ../lib-dns/libdns.la \ ../lib-ssl-iostream/libssl_iostream.la \ ../lib-master/libmaster.la \ ../lib-auth/libauth.la \ ../lib-dict/libdict.la \ ../lib-settings/libsettings.la \ ../lib-test/libtest.la \ ../lib/liblib.la \ $(MODULE_LIBS) test_deps = \ $(noinst_LTLIBRARIES) \ ../lib-dcrypt/libdcrypt.la \ ../lib-http/libhttp.la \ ../lib-dns/libdns.la \ ../lib-ssl-iostream/libssl_iostream.la \ ../lib-master/libmaster.la \ ../lib-auth/libauth.la \ ../lib-dict/libdict.la \ ../lib-settings/libsettings.la \ ../lib-test/libtest.la \ ../lib/liblib.la test_oauth2_json_SOURCES = test-oauth2-json.c test_oauth2_json_LDADD = $(test_libs) test_oauth2_json_DEPENDENCIES = $(test_deps) test_oauth2_jwt_SOURCES = test-oauth2-jwt.c test_oauth2_jwt_LDADD = $(test_libs) if HAVE_WHOLE_ARCHIVE test_oauth2_jwt_LDFLAGS = -Wl,$(LD_WHOLE_ARCHIVE),../lib-ssl-iostream/.libs/libssl_iostream.a,$(LD_NO_WHOLE_ARCHIVE) endif test_oauth2_jwt_DEPENDENCIES = $(test_deps) check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done dovecot-2.3.21.1/src/pop3-login/0000755000000000000000000000000014656633640013143 500000000000000dovecot-2.3.21.1/src/pop3-login/pop3-proxy.h0000644000000000000000000000055314656633576015307 00000000000000#ifndef POP3_PROXY_H #define POP3_PROXY_H void pop3_proxy_reset(struct client *client); int pop3_proxy_parse_line(struct client *client, const char *line); void pop3_proxy_failed(struct client *client, enum login_proxy_failure_type type, const char *reason, bool reconnecting); const char *pop3_proxy_get_state(struct client *client); #endif dovecot-2.3.21.1/src/pop3-login/pop3-login-settings.c0000644000000000000000000000365214656633576017072 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "settings-parser.h" #include "service-settings.h" #include "login-settings.h" #include "pop3-login-settings.h" #include /* */ static struct inet_listener_settings pop3_login_inet_listeners_array[] = { { .name = "pop3", .address = "", .port = 110 }, { .name = "pop3s", .address = "", .port = 995, .ssl = TRUE } }; static struct inet_listener_settings *pop3_login_inet_listeners[] = { &pop3_login_inet_listeners_array[0], &pop3_login_inet_listeners_array[1] }; static buffer_t pop3_login_inet_listeners_buf = { { { pop3_login_inet_listeners, sizeof(pop3_login_inet_listeners) } } }; /* */ struct service_settings pop3_login_service_settings = { .name = "pop3-login", .protocol = "pop3", .type = "login", .executable = "pop3-login", .user = "$default_login_user", .group = "", .privileged_group = "", .extra_groups = "", .chroot = "login", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 0, .client_limit = 0, .service_count = 1, .idle_kill = 0, .vsz_limit = UOFF_T_MAX, .unix_listeners = ARRAY_INIT, .fifo_listeners = ARRAY_INIT, .inet_listeners = { { &pop3_login_inet_listeners_buf, sizeof(pop3_login_inet_listeners[0]) } } }; static const struct setting_define pop3_login_setting_defines[] = { SETTING_DEFINE_LIST_END }; static const struct setting_parser_info *pop3_login_setting_dependencies[] = { &login_setting_parser_info, NULL }; const struct setting_parser_info pop3_login_setting_parser_info = { .module_name = "pop3-login", .defines = pop3_login_setting_defines, .type_offset = SIZE_MAX, .parent_offset = SIZE_MAX, .dependencies = pop3_login_setting_dependencies }; const struct setting_parser_info *pop3_login_setting_roots[] = { &login_setting_parser_info, &pop3_login_setting_parser_info, NULL }; dovecot-2.3.21.1/src/pop3-login/client-authenticate.c0000644000000000000000000001637614656633576017206 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "login-common.h" #include "base64.h" #include "buffer.h" #include "hex-binary.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "safe-memset.h" #include "str.h" #include "str-sanitize.h" #include "auth-client.h" #include "../pop3/pop3-capability.h" #include "client.h" #include "client-authenticate.h" #include "pop3-proxy.h" static const char *capability_string = POP3_CAPABILITY_REPLY; bool cmd_capa(struct pop3_client *client, const char *args ATTR_UNUSED) { const struct auth_mech_desc *mech; unsigned int i, count; string_t *str; str = t_str_new(128); str_append(str, "+OK\r\n"); str_append(str, capability_string); if (client_is_tls_enabled(&client->common) && !client->common.tls) str_append(str, "STLS\r\n"); if (!client->common.set->disable_plaintext_auth || client->common.secured) str_append(str, "USER\r\n"); str_append(str, "SASL"); mech = sasl_server_get_advertised_mechs(&client->common, &count); for (i = 0; i < count; i++) { str_append_c(str, ' '); str_append(str, mech[i].name); } str_append(str, "\r\n.\r\n"); client_send_raw(&client->common, str_c(str)); return TRUE; } void pop3_client_auth_result(struct client *client, enum client_auth_result result, const struct client_auth_reply *reply ATTR_UNUSED, const char *text) { switch (result) { case CLIENT_AUTH_RESULT_SUCCESS: /* nothing to be done for POP3 */ break; case CLIENT_AUTH_RESULT_TEMPFAIL: client_send_reply(client, POP3_CMD_REPLY_TEMPFAIL, text); break; case CLIENT_AUTH_RESULT_AUTHFAILED: case CLIENT_AUTH_RESULT_AUTHFAILED_REASON: case CLIENT_AUTH_RESULT_AUTHZFAILED: case CLIENT_AUTH_RESULT_PASS_EXPIRED: case CLIENT_AUTH_RESULT_SSL_REQUIRED: case CLIENT_AUTH_RESULT_LOGIN_DISABLED: case CLIENT_AUTH_RESULT_MECH_INVALID: case CLIENT_AUTH_RESULT_MECH_SSL_REQUIRED: case CLIENT_AUTH_RESULT_INVALID_BASE64: client_send_reply(client, POP3_CMD_REPLY_AUTH_ERROR, text); break; default: client_send_reply(client, POP3_CMD_REPLY_ERROR, text); break; } } int cmd_auth(struct pop3_client *pop3_client) { /* NOTE: This command's input is handled specially because the SASL-IR can be large. */ struct client *client = &pop3_client->common; const unsigned char *data; size_t i, size; int ret; /* [] */ if (!pop3_client->auth_mech_name_parsed) { data = i_stream_get_data(client->input, &size); for (i = 0; i < size; i++) { if (data[i] == ' ' || data[i] == '\r' || data[i] == '\n') break; } if (i == size) return 0; if (i == 0) { /* Old-style SASL discovery, used by MS Outlook */ unsigned int i, count; const struct auth_mech_desc *mech; client_send_raw(client, "+OK\r\n"); mech = sasl_server_get_advertised_mechs(client, &count); for (i = 0; i < count; i++) { client_send_raw(client, mech[i].name); client_send_raw(client, "\r\n"); } client_send_raw(client, ".\r\n"); (void)i_stream_read_next_line(client->input); return 1; } i_free(client->auth_mech_name); client->auth_mech_name = i_strndup(data, i); pop3_client->auth_mech_name_parsed = TRUE; if (data[i] == ' ') i++; i_stream_skip(client->input, i); } /* get SASL-IR, if any */ if ((ret = client_auth_read_line(client)) <= 0) return ret; const char *ir = NULL; if (client->auth_response->used > 0) ir = t_strdup(str_c(client->auth_response)); pop3_client->auth_mech_name_parsed = FALSE; /* The whole AUTH line command is parsed now. The rest of the SASL protocol exchange happens in login-common code. We can free the current command here already, because no pop3-login code is called until the authentication is finished. Also, there's currently no single location that is called in pop3-login code after the authentication is finished. For example it could be an auth failure or it could be a successful authentication with a proxying failure. */ i_free(pop3_client->current_cmd); return client_auth_begin(client, t_strdup(client->auth_mech_name), ir); } bool cmd_user(struct pop3_client *pop3_client, const char *args) { if (!client_check_plaintext_auth(&pop3_client->common, FALSE)) { if (pop3_client->common.virtual_user == NULL) pop3_client->common.virtual_user = i_strdup(args); return TRUE; } i_free(pop3_client->last_user); pop3_client->last_user = i_strdup(args); client_send_raw(&pop3_client->common, "+OK\r\n"); return TRUE; } bool cmd_pass(struct pop3_client *pop3_client, const char *args) { struct client *client = &pop3_client->common; string_t *plain_login, *base64; if (pop3_client->last_user == NULL) { /* client may ignore the USER reply and only display the error message from PASS */ if (!client_check_plaintext_auth(client, TRUE)) return TRUE; client_send_reply(client, POP3_CMD_REPLY_ERROR, "No username given."); return TRUE; } /* authorization ID \0 authentication ID \0 pass */ plain_login = t_str_new(128); str_append_c(plain_login, '\0'); str_append(plain_login, pop3_client->last_user); str_append_c(plain_login, '\0'); str_append(plain_login, args); i_free_and_null(pop3_client->last_user); base64 = t_buffer_create(MAX_BASE64_ENCODED_SIZE(plain_login->used)); base64_encode(plain_login->data, plain_login->used, base64); (void)client_auth_begin(client, "PLAIN", str_c(base64)); return TRUE; } bool cmd_apop(struct pop3_client *pop3_client, const char *args) { struct client *client = &pop3_client->common; buffer_t *apop_data, *base64; const char *p; unsigned int server_pid, connect_uid; if (pop3_client->apop_challenge == NULL) { if (client->set->auth_verbose) e_info(client->event, "APOP failed: APOP not enabled"); client_send_reply(client, POP3_CMD_REPLY_ERROR, "APOP not enabled."); return TRUE; } /* */ p = strchr(args, ' '); if (p == NULL || strlen(p+1) != 32) { if (client->set->auth_verbose) e_info(client->event, "APOP failed: Invalid parameters"); client_send_reply(client, POP3_CMD_REPLY_ERROR, "Invalid parameters."); return TRUE; } /* APOP challenge \0 username \0 APOP response */ apop_data = t_buffer_create(128); buffer_append(apop_data, pop3_client->apop_challenge, strlen(pop3_client->apop_challenge)+1); buffer_append(apop_data, args, (size_t)(p-args)); buffer_append_c(apop_data, '\0'); if (hex_to_binary(p+1, apop_data) < 0) { if (client->set->auth_verbose) { e_info(client->event, "APOP failed: " "Invalid characters in MD5 response"); } client_send_reply(client, POP3_CMD_REPLY_ERROR, "Invalid characters in MD5 response."); return TRUE; } base64 = t_buffer_create(MAX_BASE64_ENCODED_SIZE(apop_data->used)); base64_encode(apop_data->data, apop_data->used, base64); auth_client_get_connect_id(auth_client, &server_pid, &connect_uid); if (pop3_client->apop_server_pid != server_pid || pop3_client->apop_connect_uid != connect_uid) { /* we reconnected to auth server and can't authenticate with APOP in this session anymore. disconnecting the user is probably the best solution now. */ client_destroy(client, "Reconnected to auth server, can't do APOP"); return TRUE; } (void)client_auth_begin_private(client, "APOP", str_c(base64)); return TRUE; } dovecot-2.3.21.1/src/pop3-login/Makefile.in0000644000000000000000000006414014656633613015135 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ pkglibexec_PROGRAMS = pop3-login$(EXEEXT) subdir = src/pop3-login ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(pkglibexecdir)" PROGRAMS = $(pkglibexec_PROGRAMS) am_pop3_login_OBJECTS = client.$(OBJEXT) client-authenticate.$(OBJEXT) \ pop3-login-settings.$(OBJEXT) pop3-proxy.$(OBJEXT) pop3_login_OBJECTS = $(am_pop3_login_OBJECTS) am__DEPENDENCIES_1 = AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/client-authenticate.Po \ ./$(DEPDIR)/client.Po ./$(DEPDIR)/pop3-login-settings.Po \ ./$(DEPDIR)/pop3-proxy.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(pop3_login_SOURCES) DIST_SOURCES = $(pop3_login_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac HEADERS = $(noinst_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) pkglibexecdir = $(libexecdir)/dovecot ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-sasl \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/login-common \ $(BINARY_CFLAGS) pop3_login_LDADD = \ $(LIBDOVECOT_LOGIN) \ $(LIBDOVECOT) \ $(SSL_LIBS) \ $(BINARY_LDFLAGS) pop3_login_DEPENDENCIES = \ $(LIBDOVECOT_LOGIN) \ $(LIBDOVECOT_DEPS) pop3_login_SOURCES = \ client.c \ client-authenticate.c \ pop3-login-settings.c \ pop3-proxy.c noinst_HEADERS = \ client.h \ client-authenticate.h \ pop3-login-settings.h \ pop3-proxy.h all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/pop3-login/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/pop3-login/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files clean-pkglibexecPROGRAMS: @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list pop3-login$(EXEEXT): $(pop3_login_OBJECTS) $(pop3_login_DEPENDENCIES) $(EXTRA_pop3_login_DEPENDENCIES) @rm -f pop3-login$(EXEEXT) $(AM_V_CCLD)$(LINK) $(pop3_login_OBJECTS) $(pop3_login_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client-authenticate.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pop3-login-settings.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pop3-proxy.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am check: check-am all-am: Makefile $(PROGRAMS) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkglibexecdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-pkglibexecPROGRAMS \ mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/client-authenticate.Po -rm -f ./$(DEPDIR)/client.Po -rm -f ./$(DEPDIR)/pop3-login-settings.Po -rm -f ./$(DEPDIR)/pop3-proxy.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-pkglibexecPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/client-authenticate.Po -rm -f ./$(DEPDIR)/client.Po -rm -f ./$(DEPDIR)/pop3-login-settings.Po -rm -f ./$(DEPDIR)/pop3-proxy.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkglibexecPROGRAMS .MAKE: install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ clean-generic clean-libtool clean-pkglibexecPROGRAMS \ cscopelist-am ctags ctags-am distclean distclean-compile \ distclean-generic distclean-libtool distclean-tags distdir dvi \ dvi-am html html-am info info-am install install-am \ install-data install-data-am install-dvi install-dvi-am \ install-exec install-exec-am install-html install-html-am \ install-info install-info-am install-man install-pdf \ install-pdf-am install-pkglibexecPROGRAMS install-ps \ install-ps-am install-strip installcheck installcheck-am \ installdirs maintainer-clean maintainer-clean-generic \ mostlyclean mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ uninstall-am uninstall-pkglibexecPROGRAMS .PRECIOUS: Makefile # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/pop3-login/client.h0000644000000000000000000000134514656633576014525 00000000000000#ifndef CLIENT_H #define CLIENT_H #include "net.h" #include "client-common.h" #include "auth-client.h" enum pop3_proxy_state { POP3_PROXY_BANNER = 0, POP3_PROXY_STARTTLS, POP3_PROXY_XCLIENT, POP3_PROXY_LOGIN1, POP3_PROXY_LOGIN2, POP3_PROXY_STATE_COUNT }; struct pop3_client { struct client common; char *current_cmd; char *last_user; char *apop_challenge; unsigned int apop_server_pid, apop_connect_uid; enum pop3_proxy_state proxy_state; bool proxy_xclient; bool auth_mech_name_parsed; }; enum pop3_cmd_reply { POP3_CMD_REPLY_OK, POP3_CMD_REPLY_ERROR, POP3_CMD_REPLY_AUTH_ERROR, POP3_CMD_REPLY_TEMPFAIL }; void client_send_reply(struct client *client, enum pop3_cmd_reply reply, const char *text); #endif dovecot-2.3.21.1/src/pop3-login/client-authenticate.h0000644000000000000000000000101214656633576017170 00000000000000#ifndef CLIENT_AUTHENTICATE_H #define CLIENT_AUTHENTICATE_H void pop3_client_auth_result(struct client *client, enum client_auth_result result, const struct client_auth_reply *reply, const char *text); bool cmd_capa(struct pop3_client *client, const char *args); bool cmd_user(struct pop3_client *client, const char *args); bool cmd_pass(struct pop3_client *client, const char *args); int cmd_auth(struct pop3_client *client); bool cmd_apop(struct pop3_client *client, const char *args); #endif dovecot-2.3.21.1/src/pop3-login/Makefile.am0000644000000000000000000000123414656633576015127 00000000000000pkglibexecdir = $(libexecdir)/dovecot pkglibexec_PROGRAMS = pop3-login AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-sasl \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/login-common \ $(BINARY_CFLAGS) pop3_login_LDADD = \ $(LIBDOVECOT_LOGIN) \ $(LIBDOVECOT) \ $(SSL_LIBS) \ $(BINARY_LDFLAGS) pop3_login_DEPENDENCIES = \ $(LIBDOVECOT_LOGIN) \ $(LIBDOVECOT_DEPS) pop3_login_SOURCES = \ client.c \ client-authenticate.c \ pop3-login-settings.c \ pop3-proxy.c noinst_HEADERS = \ client.h \ client-authenticate.h \ pop3-login-settings.h \ pop3-proxy.h dovecot-2.3.21.1/src/pop3-login/pop3-proxy.c0000644000000000000000000002334714656633576015310 00000000000000/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ #include "login-common.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "base64.h" #include "safe-memset.h" #include "str.h" #include "str-sanitize.h" #include "strescape.h" #include "dsasl-client.h" #include "client.h" #include "pop3-proxy.h" static const char *pop3_proxy_state_names[POP3_PROXY_STATE_COUNT] = { "banner", "starttls", "xclient", "login1", "login2" }; static int proxy_send_login(struct pop3_client *client, struct ostream *output) { struct dsasl_client_settings sasl_set; const unsigned char *sasl_output; size_t len; const char *mech_name, *error; string_t *str = t_str_new(128); i_assert(client->common.proxy_ttl > 1); if (client->proxy_xclient && !client->common.proxy_not_trusted) { string_t *fwd = t_str_new(128); for(const char *const *ptr = client->common.auth_passdb_args;*ptr != NULL; ptr++) { if (strncasecmp(*ptr, "forward_", 8) == 0) { if (str_len(fwd) > 0) str_append_c(fwd, '\t'); str_append_tabescaped(fwd, (*ptr)+8); } } str_printfa(str, "XCLIENT ADDR=%s PORT=%u SESSION=%s TTL=%u", net_ip2addr(&client->common.ip), client->common.remote_port, client_get_session_id(&client->common), client->common.proxy_ttl - 1); if (str_len(fwd) > 0) { str_append(str, " FORWARD="); base64_encode(str_data(fwd), str_len(fwd), str); } str_append(str, "\r\n"); /* remote supports XCLIENT, send it */ o_stream_nsend(output, str_data(str), str_len(str)); client->proxy_state = POP3_PROXY_XCLIENT; } else { client->proxy_state = POP3_PROXY_LOGIN1; } str_truncate(str, 0); if (client->common.proxy_mech == NULL) { /* send USER command */ str_append(str, "USER "); str_append(str, client->common.proxy_user); str_append(str, "\r\n"); o_stream_nsend(output, str_data(str), str_len(str)); return 0; } i_assert(client->common.proxy_sasl_client == NULL); i_zero(&sasl_set); sasl_set.authid = client->common.proxy_master_user != NULL ? client->common.proxy_master_user : client->common.proxy_user; sasl_set.authzid = client->common.proxy_user; sasl_set.password = client->common.proxy_password; client->common.proxy_sasl_client = dsasl_client_new(client->common.proxy_mech, &sasl_set); mech_name = dsasl_client_mech_get_name(client->common.proxy_mech); str_printfa(str, "AUTH %s ", mech_name); if (dsasl_client_output(client->common.proxy_sasl_client, &sasl_output, &len, &error) < 0) { const char *reason = t_strdup_printf( "SASL mechanism %s init failed: %s", mech_name, error); login_proxy_failed(client->common.login_proxy, login_proxy_get_event(client->common.login_proxy), LOGIN_PROXY_FAILURE_TYPE_INTERNAL, reason); return -1; } if (len == 0) str_append_c(str, '='); else base64_encode(sasl_output, len, str); str_append(str, "\r\n"); o_stream_nsend(output, str_data(str), str_len(str)); if (client->proxy_state != POP3_PROXY_XCLIENT) client->proxy_state = POP3_PROXY_LOGIN2; return 0; } static int pop3_proxy_continue_sasl_auth(struct client *client, struct ostream *output, const char *line) { string_t *str; const unsigned char *data; size_t data_len; const char *error; int ret; str = t_str_new(128); if (base64_decode(line, strlen(line), NULL, str) < 0) { const char *reason = t_strdup_printf( "Invalid base64 data in AUTH response"); login_proxy_failed(client->login_proxy, login_proxy_get_event(client->login_proxy), LOGIN_PROXY_FAILURE_TYPE_PROTOCOL, reason); return -1; } ret = dsasl_client_input(client->proxy_sasl_client, str_data(str), str_len(str), &error); if (ret == 0) { ret = dsasl_client_output(client->proxy_sasl_client, &data, &data_len, &error); } if (ret < 0) { const char *reason = t_strdup_printf( "Invalid authentication data: %s", error); login_proxy_failed(client->login_proxy, login_proxy_get_event(client->login_proxy), LOGIN_PROXY_FAILURE_TYPE_PROTOCOL, reason); return -1; } i_assert(ret == 0); str_truncate(str, 0); base64_encode(data, data_len, str); str_append(str, "\r\n"); o_stream_nsend(output, str_data(str), str_len(str)); return 0; } int pop3_proxy_parse_line(struct client *client, const char *line) { struct pop3_client *pop3_client = (struct pop3_client *)client; struct ostream *output; enum login_proxy_ssl_flags ssl_flags; i_assert(!client->destroyed); output = login_proxy_get_ostream(client->login_proxy); switch (pop3_client->proxy_state) { case POP3_PROXY_BANNER: /* this is a banner */ if (!str_begins(line, "+OK")) { const char *reason = t_strdup_printf( "Invalid banner: %s", str_sanitize(line, 160)); login_proxy_failed(client->login_proxy, login_proxy_get_event(client->login_proxy), LOGIN_PROXY_FAILURE_TYPE_PROTOCOL, reason); return -1; } pop3_client->proxy_xclient = str_begins(line+3, " [XCLIENT]"); ssl_flags = login_proxy_get_ssl_flags(client->login_proxy); if ((ssl_flags & PROXY_SSL_FLAG_STARTTLS) == 0) { if (proxy_send_login(pop3_client, output) < 0) return -1; } else { o_stream_nsend_str(output, "STLS\r\n"); pop3_client->proxy_state = POP3_PROXY_STARTTLS; } return 0; case POP3_PROXY_STARTTLS: if (!str_begins(line, "+OK")) { const char *reason = t_strdup_printf( "STLS failed: %s", str_sanitize(line, 160)); login_proxy_failed(client->login_proxy, login_proxy_get_event(client->login_proxy), LOGIN_PROXY_FAILURE_TYPE_REMOTE, reason); return -1; } if (login_proxy_starttls(client->login_proxy) < 0) return -1; /* i/ostreams changed. */ output = login_proxy_get_ostream(client->login_proxy); if (proxy_send_login(pop3_client, output) < 0) return -1; return 1; case POP3_PROXY_XCLIENT: if (!str_begins(line, "+OK")) { const char *reason = t_strdup_printf( "XCLIENT failed: %s", str_sanitize(line, 160)); login_proxy_failed(client->login_proxy, login_proxy_get_event(client->login_proxy), LOGIN_PROXY_FAILURE_TYPE_REMOTE, reason); return -1; } pop3_client->proxy_state = client->proxy_sasl_client == NULL ? POP3_PROXY_LOGIN1 : POP3_PROXY_LOGIN2; return 0; case POP3_PROXY_LOGIN1: i_assert(client->proxy_sasl_client == NULL); if (!str_begins(line, "+OK")) break; /* USER successful, send PASS */ o_stream_nsend_str(output, t_strdup_printf( "PASS %s\r\n", client->proxy_password)); pop3_client->proxy_state = POP3_PROXY_LOGIN2; return 0; case POP3_PROXY_LOGIN2: if (str_begins(line, "+ ") && client->proxy_sasl_client != NULL) { /* continue SASL authentication */ if (pop3_proxy_continue_sasl_auth(client, output, line+2) < 0) return -1; return 0; } if (!str_begins(line, "+OK")) break; /* Login successful. Send this line to client. */ line = t_strconcat(line, "\r\n", NULL); o_stream_nsend_str(client->output, line); client_proxy_finish_destroy_client(client); return 1; case POP3_PROXY_STATE_COUNT: i_unreached(); } /* Login failed. Pass through the error message to client. If the backend server isn't Dovecot, the error message may be different from Dovecot's "user doesn't exist" error. This would allow an attacker to find out what users exist in the system. The optimal way to handle this would be to replace the backend's "password failed" error message with Dovecot's AUTH_FAILED_MSG, but this would require a new setting and the sysadmin to actually bother setting it properly. So for now we'll just forward the error message. This shouldn't be a real problem since of course everyone will be using only Dovecot as their backend :) */ enum login_proxy_failure_type failure_type = LOGIN_PROXY_FAILURE_TYPE_AUTH; if (!str_begins(line, "-ERR ")) { client_send_reply(client, POP3_CMD_REPLY_ERROR, AUTH_FAILED_MSG); } else if (str_begins(line, "-ERR [SYS/TEMP]")) { /* delay sending the reply until we know if we reconnect */ failure_type = LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL; line += 5; } else { client_send_raw(client, t_strconcat(line, "\r\n", NULL)); line += 5; } login_proxy_failed(client->login_proxy, login_proxy_get_event(client->login_proxy), failure_type, line); return -1; } void pop3_proxy_reset(struct client *client) { struct pop3_client *pop3_client = (struct pop3_client *)client; pop3_client->proxy_state = POP3_PROXY_BANNER; } static void pop3_proxy_send_failure_reply(struct client *client, enum login_proxy_failure_type type, const char *reason) { switch (type) { case LOGIN_PROXY_FAILURE_TYPE_CONNECT: case LOGIN_PROXY_FAILURE_TYPE_INTERNAL: case LOGIN_PROXY_FAILURE_TYPE_REMOTE: case LOGIN_PROXY_FAILURE_TYPE_PROTOCOL: client_send_reply(client, POP3_CMD_REPLY_TEMPFAIL, LOGIN_PROXY_FAILURE_MSG); break; case LOGIN_PROXY_FAILURE_TYPE_INTERNAL_CONFIG: case LOGIN_PROXY_FAILURE_TYPE_REMOTE_CONFIG: client_send_reply(client, POP3_CMD_REPLY_ERROR, LOGIN_PROXY_FAILURE_MSG); break; case LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL: /* [SYS/TEMP] prefix is already in the reason string */ client_send_reply(client, POP3_CMD_REPLY_ERROR, reason); break; case LOGIN_PROXY_FAILURE_TYPE_AUTH: /* reply was already sent */ break; } } void pop3_proxy_failed(struct client *client, enum login_proxy_failure_type type, const char *reason, bool reconnecting) { if (!reconnecting) pop3_proxy_send_failure_reply(client, type, reason); client_common_proxy_failed(client, type, reason, reconnecting); } const char *pop3_proxy_get_state(struct client *client) { struct pop3_client *pop3_client = (struct pop3_client *)client; return pop3_proxy_state_names[pop3_client->proxy_state]; } dovecot-2.3.21.1/src/pop3-login/client.c0000644000000000000000000002454114656633576014523 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "login-common.h" #include "base64.h" #include "buffer.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "randgen.h" #include "hostpid.h" #include "safe-memset.h" #include "str.h" #include "strescape.h" #include "master-service.h" #include "client.h" #include "client-authenticate.h" #include "auth-client.h" #include "pop3-proxy.h" #include "pop3-login-settings.h" #include /* Disconnect client when it sends too many bad commands */ #define CLIENT_MAX_BAD_COMMANDS 3 #define CLIENT_MAX_CMD_LEN 8 static bool cmd_stls(struct pop3_client *client) { client_cmd_starttls(&client->common); return TRUE; } static bool cmd_quit(struct pop3_client *client) { client_send_reply(&client->common, POP3_CMD_REPLY_OK, "Logging out"); client_destroy(&client->common, CLIENT_UNAUTHENTICATED_LOGOUT_MSG); return TRUE; } static bool cmd_xclient(struct pop3_client *client, const char *args) { const char *const *tmp; in_port_t remote_port; bool args_ok = TRUE; if (!client->common.trusted) { client_send_reply(&client->common, POP3_CMD_REPLY_OK, "You are not from trusted IP - ignoring"); return TRUE; } for (tmp = t_strsplit(args, " "); *tmp != NULL; tmp++) { if (strncasecmp(*tmp, "ADDR=", 5) == 0) { if (net_addr2ip(*tmp + 5, &client->common.ip) < 0) args_ok = FALSE; } else if (strncasecmp(*tmp, "PORT=", 5) == 0) { if (net_str2port(*tmp + 5, &remote_port) < 0) args_ok = FALSE; else client->common.remote_port = remote_port; } else if (strncasecmp(*tmp, "SESSION=", 8) == 0) { const char *value = *tmp + 8; if (strlen(value) <= LOGIN_MAX_SESSION_ID_LEN) { client->common.session_id = p_strdup(client->common.pool, value); } } else if (strncasecmp(*tmp, "TTL=", 4) == 0) { if (str_to_uint(*tmp + 4, &client->common.proxy_ttl) < 0) args_ok = FALSE; } else if (strncasecmp(*tmp, "FORWARD=", 8) == 0) { size_t value_len = strlen((*tmp)+8); client->common.forward_fields = str_new(client->common.preproxy_pool, MAX_BASE64_DECODED_SIZE(value_len)); if (base64_decode((*tmp)+8, value_len, NULL, client->common.forward_fields) < 0) args_ok = FALSE; } } if (!args_ok) { client_send_reply(&client->common, POP3_CMD_REPLY_ERROR, "Invalid parameters"); return TRUE; } /* args ok, set them and reset the state */ client_send_reply(&client->common, POP3_CMD_REPLY_OK, "Updated"); return TRUE; } static bool client_command_execute(struct pop3_client *client, const char *cmd, const char *args) { if (strcmp(cmd, "CAPA") == 0) return cmd_capa(client, args); if (strcmp(cmd, "USER") == 0) return cmd_user(client, args); if (strcmp(cmd, "PASS") == 0) return cmd_pass(client, args); if (strcmp(cmd, "APOP") == 0) return cmd_apop(client, args); if (strcmp(cmd, "STLS") == 0) return cmd_stls(client); if (strcmp(cmd, "QUIT") == 0) return cmd_quit(client); if (strcmp(cmd, "XCLIENT") == 0) return cmd_xclient(client, args); if (strcmp(cmd, "XOIP") == 0) { /* Compatibility with Zimbra's patched nginx */ return cmd_xclient(client, t_strconcat("ADDR=", args, NULL)); } client_send_reply(&client->common, POP3_CMD_REPLY_ERROR, "Unknown command."); return FALSE; } static void pop3_client_input(struct client *client) { i_assert(!client->authenticating); if (!client_read(client)) return; client_ref(client); o_stream_cork(client->output); /* if a command starts an authentication, stop processing further commands until the authentication is finished. */ while (!client->output->closed && !client->authenticating && auth_client_is_connected(auth_client)) { if (!client->v.input_next_cmd(client)) break; } if (auth_client != NULL && !auth_client_is_connected(auth_client)) client->input_blocked = TRUE; o_stream_uncork(client->output); client_unref(&client); } static bool client_read_cmd_name(struct client *client, const char **cmd_r) { const unsigned char *data; size_t size, i; string_t *cmd = t_str_new(CLIENT_MAX_CMD_LEN); if (i_stream_read_more(client->input, &data, &size) <= 0) return FALSE; for(i = 0; i < size; i++) { if (data[i] == '\r') continue; if (data[i] == ' ' || data[i] == '\n' || data[i] == '\0' || i >= CLIENT_MAX_CMD_LEN) { *cmd_r = str_c(cmd); /* only skip ws */ i_stream_skip(client->input, i + (data[i] == ' ' ? 1 : 0)); return TRUE; } str_append_c(cmd, i_toupper(data[i])); } return FALSE; } static bool pop3_client_input_next_cmd(struct client *client) { struct pop3_client *pop3_client = (struct pop3_client *)client; const char *cmd, *args; if (pop3_client->current_cmd == NULL) { if (!client_read_cmd_name(client, &cmd)) return FALSE; pop3_client->current_cmd = i_strdup(cmd); } if (strcmp(pop3_client->current_cmd, "AUTH") == 0) { if (cmd_auth(pop3_client) <= 0) { /* Need more input / destroyed. We also get here when SASL authentication is actually started. */ return FALSE; } /* AUTH command finished already (SASL probe or ERR reply) */ i_free(pop3_client->current_cmd); return TRUE; } if ((args = i_stream_next_line(client->input)) == NULL) return FALSE; if (client_command_execute(pop3_client, pop3_client->current_cmd, args)) client->bad_counter = 0; else if (++client->bad_counter >= CLIENT_MAX_BAD_COMMANDS) { client_send_reply(client, POP3_CMD_REPLY_ERROR, "Too many invalid bad commands."); client_destroy(client, "Disconnected: Too many bad commands"); return FALSE; } i_free(pop3_client->current_cmd); return TRUE; } static struct client *pop3_client_alloc(pool_t pool) { struct pop3_client *pop3_client; pop3_client = p_new(pool, struct pop3_client, 1); return &pop3_client->common; } static void pop3_client_create(struct client *client ATTR_UNUSED, void **other_sets ATTR_UNUSED) { } static void pop3_client_destroy(struct client *client) { struct pop3_client *pop3_client = (struct pop3_client *)client; i_free_and_null(pop3_client->current_cmd); i_free_and_null(pop3_client->last_user); i_free_and_null(pop3_client->apop_challenge); } static char *get_apop_challenge(struct pop3_client *client) { unsigned char buffer[16]; unsigned char buffer_base64[MAX_BASE64_ENCODED_SIZE(sizeof(buffer)) + 1]; buffer_t buf; if (sasl_server_find_available_mech(&client->common, "APOP") == NULL) { /* disabled, no need to present the challenge */ return NULL; } auth_client_get_connect_id(auth_client, &client->apop_server_pid, &client->apop_connect_uid); random_fill(buffer, sizeof(buffer)); buffer_create_from_data(&buf, buffer_base64, sizeof(buffer_base64)); base64_encode(buffer, sizeof(buffer), &buf); buffer_append_c(&buf, '\0'); return i_strdup_printf("<%x.%x.%lx.%s@%s>", client->apop_server_pid, client->apop_connect_uid, (unsigned long)ioloop_time, (const char *)buf.data, my_hostname); } static void pop3_client_notify_auth_ready(struct client *client) { struct pop3_client *pop3_client = (struct pop3_client *)client; string_t *str; client->io = io_add_istream(client->input, client_input, client); str = t_str_new(128); if (client->trusted) { /* Dovecot extension to avoid extra roundtrip for CAPA */ str_append(str, "[XCLIENT] "); } str_append(str, client->set->login_greeting); pop3_client->apop_challenge = get_apop_challenge(pop3_client); if (pop3_client->apop_challenge != NULL) str_printfa(str, " %s", pop3_client->apop_challenge); client_send_reply(client, POP3_CMD_REPLY_OK, str_c(str)); client->banner_sent = TRUE; } static void pop3_client_notify_starttls(struct client *client, bool success, const char *text) { if (success) client_send_reply(client, POP3_CMD_REPLY_OK, text); else client_send_reply(client, POP3_CMD_REPLY_ERROR, text); } static void pop3_client_starttls(struct client *client ATTR_UNUSED) { } void client_send_reply(struct client *client, enum pop3_cmd_reply reply, const char *text) { const char *prefix = "-ERR"; switch (reply) { case POP3_CMD_REPLY_OK: prefix = "+OK"; break; case POP3_CMD_REPLY_TEMPFAIL: prefix = "-ERR [SYS/TEMP]"; break; case POP3_CMD_REPLY_AUTH_ERROR: if (text[0] == '[') prefix = "-ERR"; else prefix = "-ERR [AUTH]"; break; case POP3_CMD_REPLY_ERROR: break; } T_BEGIN { string_t *line = t_str_new(256); str_append(line, prefix); str_append_c(line, ' '); str_append(line, text); str_append(line, "\r\n"); client_send_raw_data(client, str_data(line), str_len(line)); } T_END; } static void pop3_client_notify_disconnect(struct client *client, enum client_disconnect_reason reason, const char *text) { if (reason == CLIENT_DISCONNECT_INTERNAL_ERROR) client_send_reply(client, POP3_CMD_REPLY_TEMPFAIL, text); else client_send_reply(client, POP3_CMD_REPLY_ERROR, text); } static void pop3_login_die(void) { /* do nothing. pop3 connections typically die pretty quick anyway. */ } static void pop3_login_preinit(void) { login_set_roots = pop3_login_setting_roots; } static void pop3_login_init(void) { /* override the default login_die() */ master_service_set_die_callback(master_service, pop3_login_die); } static void pop3_login_deinit(void) { clients_destroy_all(); } static struct client_vfuncs pop3_client_vfuncs = { .alloc = pop3_client_alloc, .create = pop3_client_create, .destroy = pop3_client_destroy, .notify_auth_ready = pop3_client_notify_auth_ready, .notify_disconnect = pop3_client_notify_disconnect, .notify_starttls = pop3_client_notify_starttls, .starttls = pop3_client_starttls, .input = pop3_client_input, .auth_result = pop3_client_auth_result, .proxy_reset = pop3_proxy_reset, .proxy_parse_line = pop3_proxy_parse_line, .proxy_failed = pop3_proxy_failed, .proxy_get_state = pop3_proxy_get_state, .send_raw_data = client_common_send_raw_data, .input_next_cmd = pop3_client_input_next_cmd, .free = client_common_default_free, }; static struct login_binary pop3_login_binary = { .protocol = "pop3", .process_name = "pop3-login", .default_port = 110, .default_ssl_port = 995, .event_category = { .name = "pop3", }, .client_vfuncs = &pop3_client_vfuncs, .preinit = pop3_login_preinit, .init = pop3_login_init, .deinit = pop3_login_deinit, .sasl_support_final_reply = FALSE, .anonymous_login_acceptable = TRUE, }; int main(int argc, char *argv[]) { return login_binary_run(&pop3_login_binary, argc, argv); } dovecot-2.3.21.1/src/pop3-login/pop3-login-settings.h0000644000000000000000000000021214656633576017064 00000000000000#ifndef POP3_LOGIN_SETTINGS_H #define POP3_LOGIN_SETTINGS_H extern const struct setting_parser_info *pop3_login_setting_roots[]; #endif dovecot-2.3.21.1/src/imap-urlauth/0000755000000000000000000000000014656633640013564 500000000000000dovecot-2.3.21.1/src/imap-urlauth/imap-urlauth.c0000644000000000000000000002113514656633576016272 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ /* The imap-urlauth service provides URLAUTH access between different accounts. If user A has an URLAUTH that references a mail from user B, it makes a connection to the imap-urlauth service to access user B's mail store to retrieve the mail. The authentication and authorization of the URLAUTH is performed within this service. Because access to the mailbox and the associated mailbox keys is necessary to retrieve the message and for verification of the URLAUTH, the urlauth services need root privileges. To mitigate security concerns, the retrieval and verification of the URLs is performed in a worker service that drops root privileges and acts as target user B. The imap-urlauth service thus consists of three separate stages: - imap-urlauth-login: This is the login service which operates identical to imap-login and pop3-login equivalents, except for the fact that only token authentication is allowed. It verifies that the connecting client is an IMAP service acting on behaf of an authenticated user. - imap-urlauth: Once the client is authenticated, the connection gets passed to the imap-urlauth service (as implemented here). The goal of this stage is to prevent the need for re-authenticating to the imap-urlauth service when the clients wants to switch to a different target user. It normally runs as $default_internal_user and starts workers to perform the actual work. To start a worker, the imap-urlauth service establishes a control connection to the imap-urlauth-worker service. In the handshake phase of the control protocol, the connection of the client is passed to the worker. Once the worker finishes, a new worker is started and the client connection is transfered to it, unless the client is disconnected. - imap-urlauth-worker: The worker handles the URLAUTH requests from the client, so this is where the mail store of the target user is accessed. The worker starts as root. In the protocol interaction the client first indicates what the target user is. The worker then performs a userdb lookup and drops privileges. The client can then submit URLAUTH requests, which are limited to that user. Once the client wants to access a different user, the worker terminates and the imap-urlauth service starts a new worker for the next target user. */ #include "imap-urlauth-common.h" #include "lib-signals.h" #include "ioloop.h" #include "buffer.h" #include "array.h" #include "istream.h" #include "ostream.h" #include "path-util.h" #include "base64.h" #include "str.h" #include "process-title.h" #include "auth-master.h" #include "master-service.h" #include "master-service-settings.h" #include "master-login.h" #include "master-interface.h" #include "var-expand.h" #include #include #define IS_STANDALONE() \ (getenv(MASTER_IS_PARENT_ENV) == NULL) bool verbose_proctitle = FALSE; static struct master_login *master_login = NULL; static const struct imap_urlauth_settings *imap_urlauth_settings; void imap_urlauth_refresh_proctitle(void) { struct client *client; string_t *title = t_str_new(128); if (!verbose_proctitle) return; str_append_c(title, '['); switch (imap_urlauth_client_count) { case 0: str_append(title, "idling"); break; case 1: client = imap_urlauth_clients; str_append(title, client->username); break; default: str_printfa(title, "%u connections", imap_urlauth_client_count); break; } str_append_c(title, ']'); process_title_set(str_c(title)); } static void imap_urlauth_die(void) { /* do nothing. imap_urlauth connections typically die pretty quick anyway. */ } static int client_create_from_input(const char *service, const char *username, int fd_in, int fd_out) { struct client *client; if (client_create(service, username, fd_in, fd_out, imap_urlauth_settings, &client) < 0) return -1; if (!IS_STANDALONE()) client_send_line(client, "OK"); return 0; } static void main_stdio_run(const char *username) { username = username != NULL ? username : getenv("USER"); if (username == NULL && IS_STANDALONE()) username = getlogin(); if (username == NULL) i_fatal("USER environment missing"); (void)client_create_from_input("", username, STDIN_FILENO, STDOUT_FILENO); } static void login_client_connected(const struct master_login_client *client, const char *username, const char *const *extra_fields) { const char *msg = "NO\n"; struct auth_user_reply reply; struct net_unix_cred cred; const char *const *fields; const char *service = NULL; unsigned int count, i; auth_user_fields_parse(extra_fields, pool_datastack_create(), &reply); /* check peer credentials if possible */ if (reply.uid != (uid_t)-1 && net_getunixcred(client->fd, &cred) == 0 && reply.uid != cred.uid) { i_error("Peer's credentials (uid=%ld) do not match " "the user that logged in (uid=%ld).", (long)cred.uid, (long)reply.uid); if (write(client->fd, msg, strlen(msg)) < 0) { /* ignored */ } net_disconnect(client->fd); return; } fields = array_get(&reply.extra_fields, &count); for (i = 0; i < count; i++) { if (str_begins(fields[i], "client_service=")) { service = fields[i] + 15; break; } } if (service == NULL) { i_error("Auth did not yield required client_service field (BUG)."); if (write(client->fd, msg, strlen(msg)) < 0) { /* ignored */ } net_disconnect(client->fd); return; } if (reply.anonymous) username = NULL; if (client_create_from_input(service, username, client->fd, client->fd) < 0) net_disconnect(client->fd); } static void login_client_failed(const struct master_login_client *client, const char *errormsg ATTR_UNUSED) { const char *msg = "NO\n"; if (write(client->fd, msg, strlen(msg)) < 0) { /* ignored */ } } static void client_connected(struct master_service_connection *conn) { /* when running standalone, we shouldn't even get here */ i_assert(master_login != NULL); master_service_client_connection_accept(conn); master_login_add(master_login, conn->fd); } int main(int argc, char *argv[]) { static const struct setting_parser_info *set_roots[] = { &imap_urlauth_setting_parser_info, NULL }; struct master_login_settings login_set; struct master_service_settings_input input; struct master_service_settings_output output; void **sets; enum master_service_flags service_flags = 0; const char *error = NULL, *username = NULL; const char *auth_socket_path = "auth-master"; int c; i_zero(&login_set); login_set.postlogin_timeout_secs = MASTER_POSTLOGIN_TIMEOUT_DEFAULT; if (IS_STANDALONE() && getuid() == 0 && net_getpeername(1, NULL, NULL) == 0) { printf("NO imap_urlauth binary must not be started from " "inetd, use imap-urlauth-login instead.\n"); return 1; } if (IS_STANDALONE()) { service_flags |= MASTER_SERVICE_FLAG_STANDALONE | MASTER_SERVICE_FLAG_STD_CLIENT; } else { service_flags |= MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN; } master_service = master_service_init("imap-urlauth", service_flags, &argc, &argv, "a:"); while ((c = master_getopt(master_service)) > 0) { switch (c) { case 'a': auth_socket_path = optarg; break; default: return FATAL_DEFAULT; } } master_service_init_log(master_service); i_zero(&input); input.roots = set_roots; input.module = "imap-urlauth"; input.service = "imap-urlauth"; if (master_service_settings_read(master_service, &input, &output, &error) < 0) i_fatal("Error reading configuration: %s", error); sets = master_service_settings_get_others(master_service); imap_urlauth_settings = sets[0]; if (imap_urlauth_settings->verbose_proctitle) verbose_proctitle = TRUE; if (t_abspath(auth_socket_path, &login_set.auth_socket_path, &error) < 0) { i_fatal("t_abspath(%s) failed: %s", auth_socket_path, error); } login_set.callback = login_client_connected; login_set.failure_callback = login_client_failed; login_set.update_proctitle = verbose_proctitle && master_service_get_client_limit(master_service) == 1; master_service_init_finish(master_service); master_service_set_die_callback(master_service, imap_urlauth_die); /* fake that we're running, so we know if client was destroyed while handling its initial input */ io_loop_set_running(current_ioloop); if (IS_STANDALONE()) { T_BEGIN { main_stdio_run(username); } T_END; } else { master_login = master_login_init(master_service, &login_set); io_loop_set_running(current_ioloop); } if (io_loop_is_running(current_ioloop)) master_service_run(master_service, client_connected); clients_destroy_all(); if (master_login != NULL) master_login_deinit(&master_login); master_service_deinit(&master_service); return 0; } dovecot-2.3.21.1/src/imap-urlauth/imap-urlauth-worker-settings.c0000644000000000000000000000457714656633576021452 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "settings-parser.h" #include "service-settings.h" #include "mail-storage-settings.h" #include "imap-urlauth-worker-settings.h" #include #include /* */ static struct file_listener_settings imap_urlauth_worker_unix_listeners_array[] = { { "imap-urlauth-worker", 0600, "$default_internal_user", "" } }; static struct file_listener_settings *imap_urlauth_worker_unix_listeners[] = { &imap_urlauth_worker_unix_listeners_array[0] }; static buffer_t imap_urlauth_worker_unix_listeners_buf = { { { imap_urlauth_worker_unix_listeners, sizeof(imap_urlauth_worker_unix_listeners) } } }; /* */ struct service_settings imap_urlauth_worker_service_settings = { .name = "imap-urlauth-worker", .protocol = "imap", .type = "", .executable = "imap-urlauth-worker", .user = "", .group = "", .privileged_group = "", .extra_groups = "$default_internal_group", .chroot = "", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 1024, .client_limit = 1, .service_count = 1, .idle_kill = 0, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &imap_urlauth_worker_unix_listeners_buf, sizeof(imap_urlauth_worker_unix_listeners[0]) } }, .fifo_listeners = ARRAY_INIT, .inet_listeners = ARRAY_INIT }; #undef DEF #define DEF(type, name) \ SETTING_DEFINE_STRUCT_##type(#name, name, struct imap_urlauth_worker_settings) static const struct setting_define imap_urlauth_worker_setting_defines[] = { DEF(BOOL, verbose_proctitle), DEF(STR, imap_urlauth_host), DEF(IN_PORT, imap_urlauth_port), SETTING_DEFINE_LIST_END }; const struct imap_urlauth_worker_settings imap_urlauth_worker_default_settings = { .verbose_proctitle = FALSE, .imap_urlauth_host = "", .imap_urlauth_port = 143 }; static const struct setting_parser_info *imap_urlauth_worker_setting_dependencies[] = { &mail_user_setting_parser_info, NULL }; const struct setting_parser_info imap_urlauth_worker_setting_parser_info = { .module_name = "imap-urlauth-worker", .defines = imap_urlauth_worker_setting_defines, .defaults = &imap_urlauth_worker_default_settings, .type_offset = SIZE_MAX, .struct_size = sizeof(struct imap_urlauth_worker_settings), .parent_offset = SIZE_MAX, .dependencies = imap_urlauth_worker_setting_dependencies }; dovecot-2.3.21.1/src/imap-urlauth/imap-urlauth-client.c0000644000000000000000000002273414656633576017554 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "imap-urlauth-common.h" #include "array.h" #include "ioloop.h" #include "net.h" #include "fdpass.h" #include "istream.h" #include "ostream.h" #include "str.h" #include "strescape.h" #include "eacces-error.h" #include "llist.h" #include "hostpid.h" #include "execv-const.h" #include "env-util.h" #include "var-expand.h" #include "restrict-access.h" #include "master-service.h" #include "master-interface.h" #include #include #define IMAP_URLAUTH_PROTOCOL_MAJOR_VERSION 1 #define IMAP_URLAUTH_PROTOCOL_MINOR_VERSION 0 #define IMAP_URLAUTH_WORKER_SOCKET "imap-urlauth-worker" /* max. length of input lines (URLs) */ #define MAX_INBUF_SIZE 2048 /* Disconnect client after idling this many milliseconds */ #define CLIENT_IDLE_TIMEOUT_MSECS (10*60*1000) #define USER_EXECUTABLE "imap-urlauth-worker" #define IS_STANDALONE() \ (getenv(MASTER_IS_PARENT_ENV) == NULL) static struct event_category event_category_urlauth = { .name = "imap-urlauth", }; struct client *imap_urlauth_clients; unsigned int imap_urlauth_client_count; static int client_worker_connect(struct client *client); static void client_worker_disconnect(struct client *client); static void client_worker_input(struct client *client); int client_create(const char *service, const char *username, int fd_in, int fd_out, const struct imap_urlauth_settings *set, struct client **client_r) { struct client *client; const char *app; /* always use nonblocking I/O */ net_set_nonblock(fd_in, TRUE); net_set_nonblock(fd_out, TRUE); client = i_new(struct client, 1); client->fd_in = fd_in; client->fd_out = fd_out; client->fd_ctrl = -1; client->set = set; client->event = event_create(NULL); event_set_forced_debug(client->event, set->mail_debug); event_add_category(client->event, &event_category_urlauth); event_set_append_log_prefix(client->event, t_strdup_printf( "user %s: ", username)); if (client_worker_connect(client) < 0) { event_unref(&client->event); i_free(client); return -1; } /* determine user's special privileges */ i_array_init(&client->access_apps, 4); if (username != NULL) { if (set->imap_urlauth_submit_user != NULL && strcmp(set->imap_urlauth_submit_user, username) == 0) { e_debug(client->event, "User has URLAUTH submit access"); app = "submit+"; array_push_back(&client->access_apps, &app); } if (set->imap_urlauth_stream_user != NULL && strcmp(set->imap_urlauth_stream_user, username) == 0) { e_debug(client->event, "User has URLAUTH stream access"); app = "stream"; array_push_back(&client->access_apps, &app); } } client->username = i_strdup(username); client->service = i_strdup(service); client->output = o_stream_create_fd(fd_out, SIZE_MAX); imap_urlauth_client_count++; DLLIST_PREPEND(&imap_urlauth_clients, client); imap_urlauth_refresh_proctitle(); *client_r = client; return 0; } void client_send_line(struct client *client, const char *fmt, ...) { va_list va; ssize_t ret; if (client->output->closed) return; va_start(va, fmt); T_BEGIN { string_t *str; str = t_str_new(256); str_vprintfa(str, fmt, va); str_append(str, "\n"); ret = o_stream_send(client->output, str_data(str), str_len(str)); i_assert(ret < 0 || (size_t)ret == str_len(str)); } T_END; va_end(va); } static int client_worker_connect(struct client *client) { static const char handshake[] = "VERSION\timap-urlauth-worker\t2\t0\n"; const char *socket_path; ssize_t ret; unsigned char data; socket_path = t_strconcat(client->set->base_dir, "/"IMAP_URLAUTH_WORKER_SOCKET, NULL); e_debug(client->event, "Connecting to worker socket %s", socket_path); client->fd_ctrl = net_connect_unix_with_retries(socket_path, 1000); if (client->fd_ctrl < 0) { if (errno == EACCES) { e_error(client->event, "imap-urlauth-client: %s", eacces_error_get("net_connect_unix", socket_path)); } else { e_error(client->event, "imap-urlauth-client: " "net_connect_unix(%s) failed: %m", socket_path); } return -1; } /* transfer one or two fds */ data = (client->fd_in == client->fd_out ? '0' : '1'); ret = fd_send(client->fd_ctrl, client->fd_in, &data, sizeof(data)); if (ret > 0 && client->fd_in != client->fd_out) { data = '0'; ret = fd_send(client->fd_ctrl, client->fd_out, &data, sizeof(data)); } if (ret <= 0) { if (ret < 0) { e_error(client->event, "fd_send(%s, %d) failed: %m", socket_path, client->fd_ctrl); } else { e_error(client->event, "fd_send(%s, %d) failed to send byte", socket_path, client->fd_ctrl); } client_worker_disconnect(client); return -1; } client->ctrl_output = o_stream_create_fd(client->fd_ctrl, SIZE_MAX); /* send protocol version handshake */ if (o_stream_send_str(client->ctrl_output, handshake) < 0) { e_error(client->event, "Error sending handshake to imap-urlauth worker: %m"); client_worker_disconnect(client); return -1; } client->ctrl_input = i_stream_create_fd(client->fd_ctrl, MAX_INBUF_SIZE); client->ctrl_io = io_add(client->fd_ctrl, IO_READ, client_worker_input, client); return 0; } void client_worker_disconnect(struct client *client) { client->worker_state = IMAP_URLAUTH_WORKER_STATE_INACTIVE; io_remove(&client->ctrl_io); o_stream_destroy(&client->ctrl_output); i_stream_destroy(&client->ctrl_input); if (client->fd_ctrl >= 0) { net_disconnect(client->fd_ctrl); client->fd_ctrl = -1; } } static int client_worker_input_line(struct client *client, const char *response) { const char *const *apps; unsigned int count, i; bool restart; string_t *str; int ret; switch (client->worker_state) { case IMAP_URLAUTH_WORKER_STATE_INACTIVE: if (strcasecmp(response, "OK") != 0) { client_disconnect(client, "Worker handshake failed"); return -1; } client->worker_state = IMAP_URLAUTH_WORKER_STATE_CONNECTED; str = t_str_new(256); str_append(str, "ACCESS\t"); if (client->username != NULL) str_append_tabescaped(str, client->username); str_append(str, "\t"); str_append_tabescaped(str, client->service); if (client->set->mail_debug) str_append(str, "\tdebug"); if (array_count(&client->access_apps) > 0) { str_append(str, "\tapps="); apps = array_get(&client->access_apps, &count); str_append(str, apps[0]); for (i = 1; i < count; i++) { str_append_c(str, ','); str_append_tabescaped(str, apps[i]); } } str_append(str, "\n"); ret = o_stream_send(client->ctrl_output, str_data(str), str_len(str)); i_assert(ret < 0 || (size_t)ret == str_len(str)); if (ret < 0) { client_disconnect(client, "Failed to send ACCESS control command to worker"); return -1; } break; case IMAP_URLAUTH_WORKER_STATE_CONNECTED: if (strcasecmp(response, "OK") != 0) { client_disconnect(client, "Failed to negotiate access parameters"); return -1; } client->worker_state = IMAP_URLAUTH_WORKER_STATE_ACTIVE; break; case IMAP_URLAUTH_WORKER_STATE_ACTIVE: restart = TRUE; if (strcasecmp(response, "DISCONNECTED") == 0) { /* worker detected client disconnect */ restart = FALSE; } else if (strcasecmp(response, "FINISHED") != 0) { /* unknown response */ client_disconnect(client, "Worker finished with unknown response"); return -1; } e_debug(client->event, "Worker finished successfully"); if (restart) { /* connect to new worker for accessing different user */ client_worker_disconnect(client); if (client_worker_connect(client) < 0) { client_disconnect(client, "Failed to connect to new worker"); return -1; } /* indicate success of "END" command */ client_send_line(client, "OK"); } else { client_disconnect(client, "Client disconnected"); } return -1; default: i_unreached(); } return 0; } void client_worker_input(struct client *client) { struct istream *input = client->ctrl_input; const char *line; if (input->closed) { /* disconnected */ client_disconnect(client, "Worker disconnected unexpectedly"); return; } switch (i_stream_read(input)) { case -1: /* disconnected */ client_disconnect(client, "Worker disconnected unexpectedly"); return; case -2: /* input buffer full */ client_disconnect(client, "Worker sent too large input"); return; } while ((line = i_stream_next_line(input)) != NULL) { if (client_worker_input_line(client, line) < 0) return; } } void client_destroy(struct client *client, const char *reason) { i_assert(reason != NULL || client->disconnected); if (!client->disconnected) e_info(client->event, "Disconnected: %s", reason); imap_urlauth_client_count--; DLLIST_REMOVE(&imap_urlauth_clients, client); timeout_remove(&client->to_idle); client_worker_disconnect(client); o_stream_destroy(&client->output); fd_close_maybe_stdio(&client->fd_in, &client->fd_out); event_unref(&client->event); i_free(client->username); i_free(client->service); array_free(&client->access_apps); i_free(client); master_service_client_connection_destroyed(master_service); imap_urlauth_refresh_proctitle(); } static void client_destroy_timeout(struct client *client) { client_destroy(client, NULL); } void client_disconnect(struct client *client, const char *reason) { if (client->disconnected) return; client->disconnected = TRUE; e_info(client->event, "Disconnected: %s", reason); client->to_idle = timeout_add(0, client_destroy_timeout, client); } void clients_destroy_all(void) { while (imap_urlauth_clients != NULL) client_destroy(imap_urlauth_clients, "Server shutting down."); } dovecot-2.3.21.1/src/imap-urlauth/imap-urlauth-login-settings.h0000644000000000000000000000024214656633576021237 00000000000000#ifndef IMAP_URLAUTH_LOGIN_SETTINGS_H #define IMAP_URLAUTH_LOGIN_SETTINGS_H extern const struct setting_parser_info *imap_urlauth_login_setting_roots[]; #endif dovecot-2.3.21.1/src/imap-urlauth/imap-urlauth-client.h0000644000000000000000000000222714656633576017554 00000000000000#ifndef IMAP_URLAUTH_CLIENT_H #define IMAP_URLAUTH_CLIENT_H struct client; struct mail_storage; enum imap_urlauth_worker_state { IMAP_URLAUTH_WORKER_STATE_INACTIVE = 0, IMAP_URLAUTH_WORKER_STATE_CONNECTED, IMAP_URLAUTH_WORKER_STATE_ACTIVE, }; struct client { struct client *prev, *next; int fd_in, fd_out, fd_ctrl; struct io *ctrl_io; struct ostream *output, *ctrl_output; struct istream *ctrl_input; struct timeout *to_idle; struct event *event; char *username, *service; ARRAY_TYPE(const_string) access_apps; /* settings: */ const struct imap_urlauth_settings *set; enum imap_urlauth_worker_state worker_state; bool disconnected:1; }; extern struct client *imap_urlauth_clients; extern unsigned int imap_urlauth_client_count; int client_create(const char *service, const char *username, int fd_in, int fd_out, const struct imap_urlauth_settings *set, struct client **client_r); void client_destroy(struct client *client, const char *reason); void client_send_line(struct client *client, const char *fmt, ...) ATTR_FORMAT(2, 3); void client_disconnect(struct client *client, const char *reason); void clients_destroy_all(void); #endif dovecot-2.3.21.1/src/imap-urlauth/Makefile.in0000644000000000000000000013435614656633607015570 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ pkglibexec_PROGRAMS = imap-urlauth-login$(EXEEXT) \ imap-urlauth$(EXEEXT) imap-urlauth-worker$(EXEEXT) subdir = src/imap-urlauth ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(pkglibexecdir)" PROGRAMS = $(pkglibexec_PROGRAMS) am_imap_urlauth_OBJECTS = imap_urlauth-imap-urlauth.$(OBJEXT) \ imap_urlauth-imap-urlauth-client.$(OBJEXT) \ imap_urlauth-imap-urlauth-settings.$(OBJEXT) imap_urlauth_OBJECTS = $(am_imap_urlauth_OBJECTS) am__DEPENDENCIES_1 = AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = imap_urlauth_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(imap_urlauth_LDFLAGS) $(LDFLAGS) -o $@ am_imap_urlauth_login_OBJECTS = \ imap_urlauth_login-imap-urlauth-login.$(OBJEXT) \ imap_urlauth_login-imap-urlauth-login-settings.$(OBJEXT) imap_urlauth_login_OBJECTS = $(am_imap_urlauth_login_OBJECTS) am_imap_urlauth_worker_OBJECTS = \ imap_urlauth_worker-imap-urlauth-worker.$(OBJEXT) \ imap_urlauth_worker-imap-urlauth-worker-settings.$(OBJEXT) imap_urlauth_worker_OBJECTS = $(am_imap_urlauth_worker_OBJECTS) imap_urlauth_worker_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(imap_urlauth_worker_LDFLAGS) \ $(LDFLAGS) -o $@ AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/imap_urlauth-imap-urlauth-client.Po \ ./$(DEPDIR)/imap_urlauth-imap-urlauth-settings.Po \ ./$(DEPDIR)/imap_urlauth-imap-urlauth.Po \ ./$(DEPDIR)/imap_urlauth_login-imap-urlauth-login-settings.Po \ ./$(DEPDIR)/imap_urlauth_login-imap-urlauth-login.Po \ ./$(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker-settings.Po \ ./$(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(imap_urlauth_SOURCES) $(imap_urlauth_login_SOURCES) \ $(imap_urlauth_worker_SOURCES) DIST_SOURCES = $(imap_urlauth_SOURCES) $(imap_urlauth_login_SOURCES) \ $(imap_urlauth_worker_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac HEADERS = $(noinst_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) pkglibexecdir = $(libexecdir)/dovecot ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-master \ $(BINARY_CFLAGS) # imap-urlauth-login imap_urlauth_login_CPPFLAGS = \ $(AM_CPPFLAGS) \ -I$(top_srcdir)/src/login-common imap_urlauth_login_LDADD = \ $(LIBDOVECOT_LOGIN) \ $(LIBDOVECOT) \ $(SSL_LIBS) \ $(BINARY_LDFLAGS) imap_urlauth_login_DEPENDENCIES = \ $(LIBDOVECOT_LOGIN_DEPS) \ $(LIBDOVECOT_DEPS) imap_urlauth_login_SOURCES = \ imap-urlauth-login.c \ imap-urlauth-login-settings.c # imap-urlauth imap_urlauth_CPPFLAGS = \ $(AM_CPPFLAGS) \ -I$(top_srcdir)/src/lib-dict \ -DPKG_RUNDIR=\""$(rundir)"\" imap_urlauth_LDFLAGS = -export-dynamic imap_urlauth_LDADD = $(LIBDOVECOT) \ $(BINARY_LDFLAGS) imap_urlauth_DEPENDENCIES = $(LIBDOVECOT_DEPS) imap_urlauth_SOURCES = \ imap-urlauth.c \ imap-urlauth-client.c \ imap-urlauth-settings.c # imap-urlauth-worker imap_urlauth_worker_CPPFLAGS = \ $(AM_CPPFLAGS) \ -I$(top_srcdir)/src/lib-dict \ -I$(top_srcdir)/src/imap \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-imap-storage \ -I$(top_srcdir)/src/lib-imap-urlauth \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-storage \ -I$(top_srcdir)/src/login-common imap_urlauth_worker_LDFLAGS = -export-dynamic \ $(BINARY_LDFLAGS) urlauth_libs = \ $(top_builddir)/src/lib-imap-urlauth/libimap-urlauth.la imap_urlauth_worker_LDADD = $(urlauth_libs) $(LIBDOVECOT_STORAGE) $(LIBDOVECOT) imap_urlauth_worker_DEPENDENCIES = $(urlauth_libs) $(LIBDOVECOT_STORAGE_DEPS) $(LIBDOVECOT_DEPS) imap_urlauth_worker_SOURCES = \ imap-urlauth-worker.c \ imap-urlauth-worker-settings.c noinst_HEADERS = \ imap-urlauth-client.h \ imap-urlauth-common.h \ imap-urlauth-settings.h \ imap-urlauth-login-settings.h \ imap-urlauth-worker-settings.h all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/imap-urlauth/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/imap-urlauth/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files clean-pkglibexecPROGRAMS: @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list imap-urlauth$(EXEEXT): $(imap_urlauth_OBJECTS) $(imap_urlauth_DEPENDENCIES) $(EXTRA_imap_urlauth_DEPENDENCIES) @rm -f imap-urlauth$(EXEEXT) $(AM_V_CCLD)$(imap_urlauth_LINK) $(imap_urlauth_OBJECTS) $(imap_urlauth_LDADD) $(LIBS) imap-urlauth-login$(EXEEXT): $(imap_urlauth_login_OBJECTS) $(imap_urlauth_login_DEPENDENCIES) $(EXTRA_imap_urlauth_login_DEPENDENCIES) @rm -f imap-urlauth-login$(EXEEXT) $(AM_V_CCLD)$(LINK) $(imap_urlauth_login_OBJECTS) $(imap_urlauth_login_LDADD) $(LIBS) imap-urlauth-worker$(EXEEXT): $(imap_urlauth_worker_OBJECTS) $(imap_urlauth_worker_DEPENDENCIES) $(EXTRA_imap_urlauth_worker_DEPENDENCIES) @rm -f imap-urlauth-worker$(EXEEXT) $(AM_V_CCLD)$(imap_urlauth_worker_LINK) $(imap_urlauth_worker_OBJECTS) $(imap_urlauth_worker_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap_urlauth-imap-urlauth-client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap_urlauth-imap-urlauth-settings.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap_urlauth-imap-urlauth.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap_urlauth_login-imap-urlauth-login-settings.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap_urlauth_login-imap-urlauth-login.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker-settings.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< imap_urlauth-imap-urlauth.o: imap-urlauth.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT imap_urlauth-imap-urlauth.o -MD -MP -MF $(DEPDIR)/imap_urlauth-imap-urlauth.Tpo -c -o imap_urlauth-imap-urlauth.o `test -f 'imap-urlauth.c' || echo '$(srcdir)/'`imap-urlauth.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/imap_urlauth-imap-urlauth.Tpo $(DEPDIR)/imap_urlauth-imap-urlauth.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='imap-urlauth.c' object='imap_urlauth-imap-urlauth.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o imap_urlauth-imap-urlauth.o `test -f 'imap-urlauth.c' || echo '$(srcdir)/'`imap-urlauth.c imap_urlauth-imap-urlauth.obj: imap-urlauth.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT imap_urlauth-imap-urlauth.obj -MD -MP -MF $(DEPDIR)/imap_urlauth-imap-urlauth.Tpo -c -o imap_urlauth-imap-urlauth.obj `if test -f 'imap-urlauth.c'; then $(CYGPATH_W) 'imap-urlauth.c'; else $(CYGPATH_W) '$(srcdir)/imap-urlauth.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/imap_urlauth-imap-urlauth.Tpo $(DEPDIR)/imap_urlauth-imap-urlauth.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='imap-urlauth.c' object='imap_urlauth-imap-urlauth.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o imap_urlauth-imap-urlauth.obj `if test -f 'imap-urlauth.c'; then $(CYGPATH_W) 'imap-urlauth.c'; else $(CYGPATH_W) '$(srcdir)/imap-urlauth.c'; fi` imap_urlauth-imap-urlauth-client.o: imap-urlauth-client.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT imap_urlauth-imap-urlauth-client.o -MD -MP -MF $(DEPDIR)/imap_urlauth-imap-urlauth-client.Tpo -c -o imap_urlauth-imap-urlauth-client.o `test -f 'imap-urlauth-client.c' || echo '$(srcdir)/'`imap-urlauth-client.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/imap_urlauth-imap-urlauth-client.Tpo $(DEPDIR)/imap_urlauth-imap-urlauth-client.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='imap-urlauth-client.c' object='imap_urlauth-imap-urlauth-client.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o imap_urlauth-imap-urlauth-client.o `test -f 'imap-urlauth-client.c' || echo '$(srcdir)/'`imap-urlauth-client.c imap_urlauth-imap-urlauth-client.obj: imap-urlauth-client.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT imap_urlauth-imap-urlauth-client.obj -MD -MP -MF $(DEPDIR)/imap_urlauth-imap-urlauth-client.Tpo -c -o imap_urlauth-imap-urlauth-client.obj `if test -f 'imap-urlauth-client.c'; then $(CYGPATH_W) 'imap-urlauth-client.c'; else $(CYGPATH_W) '$(srcdir)/imap-urlauth-client.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/imap_urlauth-imap-urlauth-client.Tpo $(DEPDIR)/imap_urlauth-imap-urlauth-client.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='imap-urlauth-client.c' object='imap_urlauth-imap-urlauth-client.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o imap_urlauth-imap-urlauth-client.obj `if test -f 'imap-urlauth-client.c'; then $(CYGPATH_W) 'imap-urlauth-client.c'; else $(CYGPATH_W) '$(srcdir)/imap-urlauth-client.c'; fi` imap_urlauth-imap-urlauth-settings.o: imap-urlauth-settings.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT imap_urlauth-imap-urlauth-settings.o -MD -MP -MF $(DEPDIR)/imap_urlauth-imap-urlauth-settings.Tpo -c -o imap_urlauth-imap-urlauth-settings.o `test -f 'imap-urlauth-settings.c' || echo '$(srcdir)/'`imap-urlauth-settings.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/imap_urlauth-imap-urlauth-settings.Tpo $(DEPDIR)/imap_urlauth-imap-urlauth-settings.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='imap-urlauth-settings.c' object='imap_urlauth-imap-urlauth-settings.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o imap_urlauth-imap-urlauth-settings.o `test -f 'imap-urlauth-settings.c' || echo '$(srcdir)/'`imap-urlauth-settings.c imap_urlauth-imap-urlauth-settings.obj: imap-urlauth-settings.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT imap_urlauth-imap-urlauth-settings.obj -MD -MP -MF $(DEPDIR)/imap_urlauth-imap-urlauth-settings.Tpo -c -o imap_urlauth-imap-urlauth-settings.obj `if test -f 'imap-urlauth-settings.c'; then $(CYGPATH_W) 'imap-urlauth-settings.c'; else $(CYGPATH_W) '$(srcdir)/imap-urlauth-settings.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/imap_urlauth-imap-urlauth-settings.Tpo $(DEPDIR)/imap_urlauth-imap-urlauth-settings.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='imap-urlauth-settings.c' object='imap_urlauth-imap-urlauth-settings.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o imap_urlauth-imap-urlauth-settings.obj `if test -f 'imap-urlauth-settings.c'; then $(CYGPATH_W) 'imap-urlauth-settings.c'; else $(CYGPATH_W) '$(srcdir)/imap-urlauth-settings.c'; fi` imap_urlauth_login-imap-urlauth-login.o: imap-urlauth-login.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_login_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT imap_urlauth_login-imap-urlauth-login.o -MD -MP -MF $(DEPDIR)/imap_urlauth_login-imap-urlauth-login.Tpo -c -o imap_urlauth_login-imap-urlauth-login.o `test -f 'imap-urlauth-login.c' || echo '$(srcdir)/'`imap-urlauth-login.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/imap_urlauth_login-imap-urlauth-login.Tpo $(DEPDIR)/imap_urlauth_login-imap-urlauth-login.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='imap-urlauth-login.c' object='imap_urlauth_login-imap-urlauth-login.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_login_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o imap_urlauth_login-imap-urlauth-login.o `test -f 'imap-urlauth-login.c' || echo '$(srcdir)/'`imap-urlauth-login.c imap_urlauth_login-imap-urlauth-login.obj: imap-urlauth-login.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_login_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT imap_urlauth_login-imap-urlauth-login.obj -MD -MP -MF $(DEPDIR)/imap_urlauth_login-imap-urlauth-login.Tpo -c -o imap_urlauth_login-imap-urlauth-login.obj `if test -f 'imap-urlauth-login.c'; then $(CYGPATH_W) 'imap-urlauth-login.c'; else $(CYGPATH_W) '$(srcdir)/imap-urlauth-login.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/imap_urlauth_login-imap-urlauth-login.Tpo $(DEPDIR)/imap_urlauth_login-imap-urlauth-login.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='imap-urlauth-login.c' object='imap_urlauth_login-imap-urlauth-login.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_login_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o imap_urlauth_login-imap-urlauth-login.obj `if test -f 'imap-urlauth-login.c'; then $(CYGPATH_W) 'imap-urlauth-login.c'; else $(CYGPATH_W) '$(srcdir)/imap-urlauth-login.c'; fi` imap_urlauth_login-imap-urlauth-login-settings.o: imap-urlauth-login-settings.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_login_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT imap_urlauth_login-imap-urlauth-login-settings.o -MD -MP -MF $(DEPDIR)/imap_urlauth_login-imap-urlauth-login-settings.Tpo -c -o imap_urlauth_login-imap-urlauth-login-settings.o `test -f 'imap-urlauth-login-settings.c' || echo '$(srcdir)/'`imap-urlauth-login-settings.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/imap_urlauth_login-imap-urlauth-login-settings.Tpo $(DEPDIR)/imap_urlauth_login-imap-urlauth-login-settings.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='imap-urlauth-login-settings.c' object='imap_urlauth_login-imap-urlauth-login-settings.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_login_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o imap_urlauth_login-imap-urlauth-login-settings.o `test -f 'imap-urlauth-login-settings.c' || echo '$(srcdir)/'`imap-urlauth-login-settings.c imap_urlauth_login-imap-urlauth-login-settings.obj: imap-urlauth-login-settings.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_login_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT imap_urlauth_login-imap-urlauth-login-settings.obj -MD -MP -MF $(DEPDIR)/imap_urlauth_login-imap-urlauth-login-settings.Tpo -c -o imap_urlauth_login-imap-urlauth-login-settings.obj `if test -f 'imap-urlauth-login-settings.c'; then $(CYGPATH_W) 'imap-urlauth-login-settings.c'; else $(CYGPATH_W) '$(srcdir)/imap-urlauth-login-settings.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/imap_urlauth_login-imap-urlauth-login-settings.Tpo $(DEPDIR)/imap_urlauth_login-imap-urlauth-login-settings.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='imap-urlauth-login-settings.c' object='imap_urlauth_login-imap-urlauth-login-settings.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_login_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o imap_urlauth_login-imap-urlauth-login-settings.obj `if test -f 'imap-urlauth-login-settings.c'; then $(CYGPATH_W) 'imap-urlauth-login-settings.c'; else $(CYGPATH_W) '$(srcdir)/imap-urlauth-login-settings.c'; fi` imap_urlauth_worker-imap-urlauth-worker.o: imap-urlauth-worker.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_worker_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT imap_urlauth_worker-imap-urlauth-worker.o -MD -MP -MF $(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker.Tpo -c -o imap_urlauth_worker-imap-urlauth-worker.o `test -f 'imap-urlauth-worker.c' || echo '$(srcdir)/'`imap-urlauth-worker.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker.Tpo $(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='imap-urlauth-worker.c' object='imap_urlauth_worker-imap-urlauth-worker.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_worker_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o imap_urlauth_worker-imap-urlauth-worker.o `test -f 'imap-urlauth-worker.c' || echo '$(srcdir)/'`imap-urlauth-worker.c imap_urlauth_worker-imap-urlauth-worker.obj: imap-urlauth-worker.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_worker_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT imap_urlauth_worker-imap-urlauth-worker.obj -MD -MP -MF $(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker.Tpo -c -o imap_urlauth_worker-imap-urlauth-worker.obj `if test -f 'imap-urlauth-worker.c'; then $(CYGPATH_W) 'imap-urlauth-worker.c'; else $(CYGPATH_W) '$(srcdir)/imap-urlauth-worker.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker.Tpo $(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='imap-urlauth-worker.c' object='imap_urlauth_worker-imap-urlauth-worker.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_worker_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o imap_urlauth_worker-imap-urlauth-worker.obj `if test -f 'imap-urlauth-worker.c'; then $(CYGPATH_W) 'imap-urlauth-worker.c'; else $(CYGPATH_W) '$(srcdir)/imap-urlauth-worker.c'; fi` imap_urlauth_worker-imap-urlauth-worker-settings.o: imap-urlauth-worker-settings.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_worker_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT imap_urlauth_worker-imap-urlauth-worker-settings.o -MD -MP -MF $(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker-settings.Tpo -c -o imap_urlauth_worker-imap-urlauth-worker-settings.o `test -f 'imap-urlauth-worker-settings.c' || echo '$(srcdir)/'`imap-urlauth-worker-settings.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker-settings.Tpo $(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker-settings.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='imap-urlauth-worker-settings.c' object='imap_urlauth_worker-imap-urlauth-worker-settings.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_worker_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o imap_urlauth_worker-imap-urlauth-worker-settings.o `test -f 'imap-urlauth-worker-settings.c' || echo '$(srcdir)/'`imap-urlauth-worker-settings.c imap_urlauth_worker-imap-urlauth-worker-settings.obj: imap-urlauth-worker-settings.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_worker_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT imap_urlauth_worker-imap-urlauth-worker-settings.obj -MD -MP -MF $(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker-settings.Tpo -c -o imap_urlauth_worker-imap-urlauth-worker-settings.obj `if test -f 'imap-urlauth-worker-settings.c'; then $(CYGPATH_W) 'imap-urlauth-worker-settings.c'; else $(CYGPATH_W) '$(srcdir)/imap-urlauth-worker-settings.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker-settings.Tpo $(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker-settings.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='imap-urlauth-worker-settings.c' object='imap_urlauth_worker-imap-urlauth-worker-settings.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(imap_urlauth_worker_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o imap_urlauth_worker-imap-urlauth-worker-settings.obj `if test -f 'imap-urlauth-worker-settings.c'; then $(CYGPATH_W) 'imap-urlauth-worker-settings.c'; else $(CYGPATH_W) '$(srcdir)/imap-urlauth-worker-settings.c'; fi` mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am check: check-am all-am: Makefile $(PROGRAMS) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkglibexecdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-pkglibexecPROGRAMS \ mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/imap_urlauth-imap-urlauth-client.Po -rm -f ./$(DEPDIR)/imap_urlauth-imap-urlauth-settings.Po -rm -f ./$(DEPDIR)/imap_urlauth-imap-urlauth.Po -rm -f ./$(DEPDIR)/imap_urlauth_login-imap-urlauth-login-settings.Po -rm -f ./$(DEPDIR)/imap_urlauth_login-imap-urlauth-login.Po -rm -f ./$(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker-settings.Po -rm -f ./$(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-pkglibexecPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/imap_urlauth-imap-urlauth-client.Po -rm -f ./$(DEPDIR)/imap_urlauth-imap-urlauth-settings.Po -rm -f ./$(DEPDIR)/imap_urlauth-imap-urlauth.Po -rm -f ./$(DEPDIR)/imap_urlauth_login-imap-urlauth-login-settings.Po -rm -f ./$(DEPDIR)/imap_urlauth_login-imap-urlauth-login.Po -rm -f ./$(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker-settings.Po -rm -f ./$(DEPDIR)/imap_urlauth_worker-imap-urlauth-worker.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkglibexecPROGRAMS .MAKE: install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ clean-generic clean-libtool clean-pkglibexecPROGRAMS \ cscopelist-am ctags ctags-am distclean distclean-compile \ distclean-generic distclean-libtool distclean-tags distdir dvi \ dvi-am html html-am info info-am install install-am \ install-data install-data-am install-dvi install-dvi-am \ install-exec install-exec-am install-html install-html-am \ install-info install-info-am install-man install-pdf \ install-pdf-am install-pkglibexecPROGRAMS install-ps \ install-ps-am install-strip installcheck installcheck-am \ installdirs maintainer-clean maintainer-clean-generic \ mostlyclean mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ uninstall-am uninstall-pkglibexecPROGRAMS .PRECIOUS: Makefile # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/imap-urlauth/imap-urlauth-settings.c0000644000000000000000000000452414656633576020133 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "settings-parser.h" #include "service-settings.h" #include "imap-urlauth-settings.h" #include #include /* */ static struct file_listener_settings imap_urlauth_unix_listeners_array[] = { { "token-login/imap-urlauth", 0666, "", "" } }; static struct file_listener_settings *imap_urlauth_unix_listeners[] = { &imap_urlauth_unix_listeners_array[0] }; static buffer_t imap_urlauth_unix_listeners_buf = { { { imap_urlauth_unix_listeners, sizeof(imap_urlauth_unix_listeners) } } }; /* */ struct service_settings imap_urlauth_service_settings = { .name = "imap-urlauth", .protocol = "imap", .type = "", .executable = "imap-urlauth", .user = "$default_internal_user", .group = "", .privileged_group = "", .extra_groups = "", .chroot = "", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 1024, .client_limit = 1, .service_count = 1, .idle_kill = 0, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &imap_urlauth_unix_listeners_buf, sizeof(imap_urlauth_unix_listeners[0]) } }, .fifo_listeners = ARRAY_INIT, .inet_listeners = ARRAY_INIT }; #undef DEF #define DEF(type, name) \ SETTING_DEFINE_STRUCT_##type(#name, name, struct imap_urlauth_settings) static const struct setting_define imap_urlauth_setting_defines[] = { DEF(STR, base_dir), DEF(BOOL, mail_debug), DEF(BOOL, verbose_proctitle), DEF(STR, imap_urlauth_logout_format), DEF(STR, imap_urlauth_submit_user), DEF(STR, imap_urlauth_stream_user), SETTING_DEFINE_LIST_END }; const struct imap_urlauth_settings imap_urlauth_default_settings = { .base_dir = PKG_RUNDIR, .mail_debug = FALSE, .verbose_proctitle = FALSE, .imap_urlauth_logout_format = "in=%i out=%o", .imap_urlauth_submit_user = NULL, .imap_urlauth_stream_user = NULL }; static const struct setting_parser_info *imap_urlauth_setting_dependencies[] = { NULL }; const struct setting_parser_info imap_urlauth_setting_parser_info = { .module_name = "imap-urlauth", .defines = imap_urlauth_setting_defines, .defaults = &imap_urlauth_default_settings, .type_offset = SIZE_MAX, .struct_size = sizeof(struct imap_urlauth_settings), .parent_offset = SIZE_MAX, .dependencies = imap_urlauth_setting_dependencies }; dovecot-2.3.21.1/src/imap-urlauth/imap-urlauth-worker-settings.h0000644000000000000000000000064514656633576021447 00000000000000#ifndef IMAP_URLAUTH_SETTINGS_H #define IMAP_URLAUTH_SETTINGS_H struct mail_user_settings; struct imap_urlauth_worker_settings { bool verbose_proctitle; /* imap_urlauth: */ const char *imap_urlauth_host; in_port_t imap_urlauth_port; }; extern const struct imap_urlauth_worker_settings imap_urlauth_worker_default_settings; extern const struct setting_parser_info imap_urlauth_worker_setting_parser_info; #endif dovecot-2.3.21.1/src/imap-urlauth/imap-urlauth-settings.h0000644000000000000000000000075614656633576020143 00000000000000#ifndef IMAP_URLAUTH_SETTINGS_H #define IMAP_URLAUTH_SETTINGS_H struct mail_user_settings; struct imap_urlauth_settings { const char *base_dir; bool mail_debug; bool verbose_proctitle; /* imap_urlauth: */ const char *imap_urlauth_logout_format; const char *imap_urlauth_submit_user; const char *imap_urlauth_stream_user; }; extern const struct imap_urlauth_settings imap_urlauth_default_settings; extern const struct setting_parser_info imap_urlauth_setting_parser_info; #endif dovecot-2.3.21.1/src/imap-urlauth/imap-urlauth-worker.c0000644000000000000000000006363214656633576017611 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "ioloop.h" #include "net.h" #include "fdpass.h" #include "istream.h" #include "ostream.h" #include "str.h" #include "str-sanitize.h" #include "strescape.h" #include "llist.h" #include "hostpid.h" #include "var-expand.h" #include "process-title.h" #include "randgen.h" #include "restrict-access.h" #include "settings-parser.h" #include "master-service.h" #include "master-interface.h" #include "mail-storage.h" #include "mail-storage-service.h" #include "mail-namespace.h" #include "imap-url.h" #include "imap-msgpart-url.h" #include "imap-urlauth.h" #include "imap-urlauth-fetch.h" #include "imap-urlauth-worker-settings.h" #include #include #define MAX_CTRL_HANDSHAKE 255 /* max. length of input lines (URLs) */ #define MAX_INBUF_SIZE 2048 /* Disconnect client after idling this many milliseconds */ #define CLIENT_IDLE_TIMEOUT_MSECS (10*60*1000) #define IS_STANDALONE() \ (getenv(MASTER_IS_PARENT_ENV) == NULL) #define IMAP_URLAUTH_WORKER_PROTOCOL_MAJOR_VERSION 2 #define IMAP_URLAUTH_WORKER_PROTOCOL_MINOR_VERSION 0 struct client { struct client *prev, *next; int fd_in, fd_out, fd_ctrl; struct io *io, *ctrl_io; struct istream *input, *ctrl_input; struct ostream *output, *ctrl_output; struct timeout *to_idle; char *access_user, *access_service; ARRAY_TYPE(string) access_apps; struct mail_storage_service_user *service_user; struct mail_user *mail_user; struct imap_urlauth_context *urlauth_ctx; struct imap_msgpart_url *url; struct istream *msg_part_input; uoff_t msg_part_size; /* settings: */ const struct imap_urlauth_worker_settings *set; const struct mail_storage_settings *mail_set; bool debug:1; bool finished:1; bool waiting_input:1; bool version_received:1; bool access_received:1; bool access_anonymous:1; }; static bool verbose_proctitle = FALSE; static struct mail_storage_service_ctx *storage_service; struct client *imap_urlauth_worker_clients; unsigned int imap_urlauth_worker_client_count; static void client_destroy(struct client *client); static void client_abort(struct client *client, const char *reason); static int client_run_url(struct client *client); static void client_input(struct client *client); static bool client_handle_input(struct client *client); static int client_output(struct client *client); static void client_ctrl_input(struct client *client); static void imap_urlauth_worker_refresh_proctitle(void) { struct client *client = imap_urlauth_worker_clients; string_t *title; if (!verbose_proctitle) return; title = t_str_new(128); str_append_c(title, '['); switch (imap_urlauth_worker_client_count) { case 0: str_append(title, "idling"); break; case 1: if (client->mail_user == NULL) str_append(title, client->access_user); else { str_append(title, client->access_user); str_append(title, "->"); str_append(title, client->mail_user->username); } break; default: str_printfa(title, "%u connections", imap_urlauth_worker_client_count); break; } str_append_c(title, ']'); process_title_set(str_c(title)); } static void client_idle_timeout(struct client *client) { if (client->url != NULL) { client_abort(client, "Session closed for inactivity in reading our output"); } else { client_destroy(client); } } static struct client *client_create(int fd) { struct client *client; /* always use nonblocking I/O */ net_set_nonblock(fd, TRUE); client = i_new(struct client, 1); i_array_init(&client->access_apps, 16); client->fd_in = -1; client->fd_out = -1; client->fd_ctrl = fd; client->access_anonymous = TRUE; /* default until overridden */ client->ctrl_io = io_add(fd, IO_READ, client_ctrl_input, client); client->to_idle = timeout_add(CLIENT_IDLE_TIMEOUT_MSECS, client_idle_timeout, client); imap_urlauth_worker_client_count++; DLLIST_PREPEND(&imap_urlauth_worker_clients, client); imap_urlauth_worker_refresh_proctitle(); return client; } static struct client * client_create_standalone(const char *access_user, const char *const *access_applications, int fd_in, int fd_out, bool debug) { struct client *client; /* always use nonblocking I/O */ net_set_nonblock(fd_in, TRUE); net_set_nonblock(fd_out, TRUE); client = i_new(struct client, 1); i_array_init(&client->access_apps, 16); client->fd_in = fd_in; client->fd_out = fd_out; client->fd_ctrl = -1; if (access_user != NULL && *access_user != '\0') client->access_user = i_strdup(access_user); else { client->access_user = i_strdup("anonymous"); client->access_anonymous = TRUE; } if (access_applications != NULL) { const char *const *apps = access_applications; for (; *apps != NULL; apps++) { char *app = i_strdup(*apps); array_push_back(&client->access_apps, &app); } } client->debug = debug; client->input = i_stream_create_fd(fd_in, MAX_INBUF_SIZE); client->output = o_stream_create_fd(fd_out, SIZE_MAX); client->io = io_add(fd_in, IO_READ, client_input, client); client->to_idle = timeout_add(CLIENT_IDLE_TIMEOUT_MSECS, client_idle_timeout, client); o_stream_set_no_error_handling(client->output, TRUE); o_stream_set_flush_callback(client->output, client_output, client); imap_urlauth_worker_client_count++; DLLIST_PREPEND(&imap_urlauth_worker_clients, client); i_set_failure_prefix("imap-urlauth[%s](%s): ", my_pid, client->access_user); return client; } static void client_abort(struct client *client, const char *reason) { i_error("%s", reason); client_destroy(client); } static void client_destroy(struct client *client) { char *app; i_set_failure_prefix("imap-urlauth[%s](%s): ", my_pid, client->access_user); if (client->url != NULL) { /* deinitialize url */ i_stream_close(client->input); o_stream_close(client->output); (void)client_run_url(client); i_assert(client->url == NULL); } imap_urlauth_worker_client_count--; DLLIST_REMOVE(&imap_urlauth_worker_clients, client); if (client->urlauth_ctx != NULL) imap_urlauth_deinit(&client->urlauth_ctx); if (client->mail_user != NULL) mail_user_deinit(&client->mail_user); io_remove(&client->io); io_remove(&client->ctrl_io); timeout_remove(&client->to_idle); i_stream_destroy(&client->input); o_stream_destroy(&client->output); i_stream_destroy(&client->ctrl_input); o_stream_destroy(&client->ctrl_output); fd_close_maybe_stdio(&client->fd_in, &client->fd_out); if (client->fd_ctrl >= 0) net_disconnect(client->fd_ctrl); if (client->service_user != NULL) mail_storage_service_user_unref(&client->service_user); i_free(client->access_user); i_free(client->access_service); array_foreach_elem(&client->access_apps, app) i_free(app); array_free(&client->access_apps); i_free(client); imap_urlauth_worker_refresh_proctitle(); master_service_client_connection_destroyed(master_service); } static int client_run_url(struct client *client) { const unsigned char *data; size_t size; ssize_t ret = 0; while (i_stream_read_more(client->msg_part_input, &data, &size) > 0) { if ((ret = o_stream_send(client->output, data, size)) < 0) break; i_stream_skip(client->msg_part_input, ret); if (o_stream_get_buffer_used_size(client->output) >= 4096) { if ((ret = o_stream_flush(client->output)) < 0) break; if (ret == 0) return 0; } } if (client->output->closed || ret < 0) { imap_msgpart_url_free(&client->url); return -1; } if (client->msg_part_input->eof) { o_stream_nsend(client->output, "\n", 1); imap_msgpart_url_free(&client->url); return 1; } return 0; } static void clients_destroy_all(void) { while (imap_urlauth_worker_clients != NULL) client_destroy(imap_urlauth_worker_clients); } static void ATTR_FORMAT(2, 3) client_send_line(struct client *client, const char *fmt, ...) { va_list va; if (client->output->closed) return; va_start(va, fmt); T_BEGIN { string_t *str; str = t_str_new(256); str_vprintfa(str, fmt, va); str_append(str, "\n"); o_stream_nsend(client->output, str_data(str), str_len(str)); } T_END; va_end(va); } static int client_fetch_urlpart(struct client *client, const char *url, enum imap_urlauth_fetch_flags url_flags, const char **bpstruct_r, bool *binary_with_nuls_r, const char **errormsg_r) { const char *error; struct imap_msgpart_open_result mpresult; enum mail_error error_code; int ret; *bpstruct_r = NULL; *errormsg_r = NULL; *binary_with_nuls_r = FALSE; ret = imap_urlauth_fetch(client->urlauth_ctx, url, &client->url, &error_code, &error); if (ret <= 0) { if (ret < 0) return -1; error = t_strdup_printf("Failed to fetch URLAUTH \"%s\": %s", url, error); if (client->debug) i_debug("%s", error); /* don't leak info about existence/accessibility of mailboxes */ if (error_code == MAIL_ERROR_PARAMS) *errormsg_r = error; return 0; } if ((url_flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0) imap_msgpart_url_set_decode_to_binary(client->url); if ((url_flags & IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE) != 0) { ret = imap_msgpart_url_get_bodypartstructure(client->url, bpstruct_r, &error); if (ret <= 0) { *errormsg_r = t_strdup_printf( "Failed to read URLAUTH \"%s\": %s", url, error); if (client->debug) i_debug("%s", *errormsg_r); return ret; } } /* if requested, read the message part the URL points to */ if ((url_flags & IMAP_URLAUTH_FETCH_FLAG_BODY) != 0 || (url_flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0) { ret = imap_msgpart_url_read_part(client->url, &mpresult, &error); if (ret <= 0) { *errormsg_r = t_strdup_printf( "Failed to read URLAUTH \"%s\": %s", url, error); if (client->debug) i_debug("%s", *errormsg_r); return ret; } client->msg_part_size = mpresult.size; client->msg_part_input = mpresult.input; *binary_with_nuls_r = mpresult.binary_decoded_input_has_nuls; } return 1; } static int client_fetch_url(struct client *client, const char *url, enum imap_urlauth_fetch_flags url_flags) { string_t *response; const char *bpstruct, *errormsg; bool binary_with_nuls; int ret; i_assert(client->url == NULL); client->msg_part_size = 0; client->msg_part_input = NULL; if (client->debug) i_debug("Fetching URLAUTH %s", url); /* fetch URL */ ret = client_fetch_urlpart(client, url, url_flags, &bpstruct, &binary_with_nuls, &errormsg); if (ret <= 0) { /* fetch failed */ if (client->url != NULL) imap_msgpart_url_free(&client->url); /* don't send error details to anonymous users: just to be sure that no information about the target user account is unduly leaked. */ if (client->access_anonymous || errormsg == NULL) client_send_line(client, "NO"); else { client_send_line(client, "NO\terror=%s", str_tabescape(errormsg)); } if (ret < 0) { /* fetch failed badly */ client_abort(client, "Session aborted: Fatal failure while fetching URL"); } return 0; } response = t_str_new(256); str_append(response, "OK"); if (binary_with_nuls) str_append(response, "\thasnuls"); if (bpstruct != NULL) { str_append(response, "\tbpstruct="); str_append(response, str_tabescape(bpstruct)); if (client->debug) { i_debug("Fetched URLAUTH yielded BODYPARTSTRUCTURE (%s)", bpstruct); } } /* return content */ o_stream_cork(client->output); if (client->msg_part_size == 0 || client->msg_part_input == NULL) { /* empty */ str_append(response, "\t0"); client_send_line(client, "%s", str_c(response)); imap_msgpart_url_free(&client->url); client->url = NULL; if (client->debug) i_debug("Fetched URLAUTH yielded empty result"); } else { /* actual content */ str_printfa(response, "\t%"PRIuUOFF_T, client->msg_part_size); client_send_line(client, "%s", str_c(response)); if (client->debug) { i_debug("Fetched URLAUTH yielded %"PRIuUOFF_T" bytes " "of %smessage data", client->msg_part_size, (binary_with_nuls ? "binary " : "")); } if (client_run_url(client) < 0) { client_abort(client, "Session aborted: Fatal failure while transferring URL"); return 0; } } if (client->url != NULL) { /* URL not finished */ o_stream_set_flush_pending(client->output, TRUE); client->waiting_input = TRUE; } o_stream_uncork(client->output); return client->url != NULL ? 0 : 1; } static int client_handle_command(struct client *client, const char *cmd, const char *const *args, const char **error_r) { int ret; *error_r = NULL; /* "URL"["\tbody"]["\tbinary"]["\tbpstruct"]"\t": fetch URL (meta)data */ if (strcmp(cmd, "URL") == 0) { enum imap_urlauth_fetch_flags url_flags = 0; const char *url; if (*args == NULL) { *error_r = "URL: Missing URL parameter"; return -1; } url = *args; args++; while (*args != NULL) { if (strcasecmp(*args, "body") == 0) url_flags |= IMAP_URLAUTH_FETCH_FLAG_BODY; else if (strcasecmp(*args, "binary") == 0) url_flags |= IMAP_URLAUTH_FETCH_FLAG_BINARY; else if (strcasecmp(*args, "bpstruct") == 0) url_flags |= IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE; args++; } if (url_flags == 0) url_flags = IMAP_URLAUTH_FETCH_FLAG_BODY; T_BEGIN { ret = client_fetch_url(client, url, url_flags); } T_END; return ret; } /* "END": unselect current user (closes worker) */ if (strcmp(cmd, "END") == 0) { if (args[0] != NULL) { *error_r = "END: Invalid number of parameters"; return -1; } client->finished = TRUE; if (client->ctrl_output != NULL) o_stream_nsend_str(client->ctrl_output, "FINISHED\n"); client_destroy(client); return 0; } *error_r = t_strconcat("Unknown or inappropriate command: ", cmd, NULL); return -1; } static int client_handle_user_command(struct client *client, const char *cmd, const char *const *args, const char **error_r) { struct mail_storage_service_input input; struct imap_urlauth_worker_settings *set; struct mail_storage_service_user *user; struct imap_urlauth_config config; struct mail_user *mail_user; const char *error; unsigned int count; int ret; /* "USER\t" */ *error_r = NULL; /* check command syntax */ if (strcmp(cmd, "USER") != 0) { *error_r = t_strconcat("Unknown or inappropriate command: ", cmd, NULL); return -1; } if (args[0] == NULL || args[1] != NULL) { *error_r = "USER: Invalid number of parameters"; return -1; } /* lookup user */ i_zero(&input); input.module = "imap-urlauth-worker"; input.service = "imap-urlauth-worker"; input.username = args[0]; if (client->debug) i_debug("Looking up user %s", input.username); ret = mail_storage_service_lookup_next(storage_service, &input, &user, &mail_user, &error); if (ret < 0) { i_error("Failed to lookup user %s: %s", input.username, error); client_abort(client, "Session aborted: Failed to lookup user"); return 0; } else if (ret == 0) { if (client->debug) i_debug("User %s doesn't exist", input.username); client_send_line(client, "NO"); return 1; } client->debug = mail_user->mail_debug = client->debug || mail_user->mail_debug; /* drop privileges */ restrict_access_allow_coredumps(TRUE); set = mail_storage_service_user_get_set(user)[1]; if (settings_var_expand(&imap_urlauth_worker_setting_parser_info, set, mail_user->pool, mail_user_var_expand_table(mail_user), &error) <= 0) { client_send_line(client, "NO"); client_abort(client, t_strdup_printf( "Session aborted: Failed to expand settings: %s", error)); return 0; } if (set->verbose_proctitle) { verbose_proctitle = TRUE; imap_urlauth_worker_refresh_proctitle(); } client->service_user = user; client->mail_user = mail_user; client->set = set; if (client->debug) { i_debug("Found user account `%s' on behalf of user `%s'", mail_user->username, client->access_user); } /* initialize urlauth context */ if (*set->imap_urlauth_host == '\0') { i_error("imap_urlauth_host setting is not configured for user %s", mail_user->username); client_send_line(client, "NO"); client_abort(client, "Session aborted: URLAUTH not configured"); return 0; } i_zero(&config); config.url_host = set->imap_urlauth_host; config.url_port = set->imap_urlauth_port; config.access_user = client->access_user; config.access_service = client->access_service; config.access_anonymous = client->access_anonymous; config.access_applications = (const void *)array_get(&client->access_apps, &count); client->urlauth_ctx = imap_urlauth_init(client->mail_user, &config); if (client->debug) { i_debug("Providing access to user account `%s' on behalf of user `%s' " "using service `%s'", mail_user->username, client->access_user, client->access_service); } i_set_failure_prefix("imap-urlauth[%s](%s->%s): ", my_pid, client->access_user, mail_user->username); client_send_line(client, "OK"); return 1; } static bool client_handle_input(struct client *client) { const char *line, *cmd, *error; int ret; if (client->url != NULL) { /* we're still processing a URL. wait until it's finished. */ io_remove(&client->io); client->io = NULL; client->waiting_input = TRUE; return TRUE; } if (client->io == NULL) { client->io = io_add(client->fd_in, IO_READ, client_input, client); } client->waiting_input = FALSE; timeout_reset(client->to_idle); switch (i_stream_read(client->input)) { case -1: /* disconnected */ if (client->ctrl_output != NULL) o_stream_nsend_str(client->ctrl_output, "DISCONNECTED\n"); client_destroy(client); return FALSE; case -2: /* line too long, kill it */ client_abort(client, "Session aborted: Input line too long"); return FALSE; } while ((line = i_stream_next_line(client->input)) != NULL) { const char *const *args = t_strsplit_tabescaped(line); if (args[0] == NULL) continue; cmd = args[0]; args++; if (client->mail_user == NULL) ret = client_handle_user_command(client, cmd, args, &error); else ret = client_handle_command(client, cmd, args, &error); if (ret <= 0) { if (ret == 0) break; i_error("Client input error: %s", error); client_abort(client, "Session aborted: Unexpected input"); return FALSE; } } return TRUE; } static void client_input(struct client *client) { (void)client_handle_input(client); } static int client_output(struct client *client) { if (o_stream_flush(client->output) < 0) { if (client->ctrl_output != NULL) o_stream_nsend_str(client->ctrl_output, "DISCONNECTED\n"); client_destroy(client); return 1; } timeout_reset(client->to_idle); if (client->url != NULL) { if (client_run_url(client) < 0) { client_destroy(client); return 1; } if (client->url == NULL && client->waiting_input) { if (!client_handle_input(client)) { /* client got destroyed */ return 1; } } } if (client->url != NULL) { /* url not finished yet */ return 0; } else if (client->io == NULL) { /* data still in output buffer, get back here to add IO */ return 0; } else { return 1; } } static int client_ctrl_read_fds(struct client *client) { unsigned char data = 0; ssize_t ret = 1; if (client->fd_in == -1) { ret = fd_read(client->fd_ctrl, &data, sizeof(data), &client->fd_in); if (ret > 0 && data == '0') client->fd_out = client->fd_in; } if (ret > 0 && client->fd_out == -1) { ret = fd_read(client->fd_ctrl, &data, sizeof(data), &client->fd_out); } if (ret == 0) { /* unexpectedly disconnected */ client_destroy(client); return 0; } else if (ret < 0) { if (errno == EAGAIN) return 0; i_error("fd_read() failed: %m"); return -1; } else if (data != '0') { i_error("fd_read() returned invalid byte 0x%2x", data); return -1; } if (client->fd_in == -1 || client->fd_out == -1) { i_error("Handshake is missing a file descriptor"); return -1; } client->ctrl_input = i_stream_create_fd(client->fd_ctrl, MAX_INBUF_SIZE); client->ctrl_output = o_stream_create_fd(client->fd_ctrl, SIZE_MAX); o_stream_set_no_error_handling(client->ctrl_output, TRUE); return 1; } static void client_ctrl_input(struct client *client) { const char *const *args; const char *line; int ret; timeout_reset(client->to_idle); if (client->fd_in == -1 || client->fd_out == -1) { if ((ret = client_ctrl_read_fds(client)) <= 0) { if (ret < 0) client_abort(client, "FD Transfer failed"); return; } } switch (i_stream_read(client->ctrl_input)) { case -1: /* disconnected */ client_destroy(client); return; case -2: /* line too long, kill it */ client_abort(client, "Control session aborted: Input line too long"); return; } if (!client->version_received) { if ((line = i_stream_next_line(client->ctrl_input)) == NULL) return; if (!version_string_verify(line, "imap-urlauth-worker", IMAP_URLAUTH_WORKER_PROTOCOL_MAJOR_VERSION)) { i_error("imap-urlauth-worker client not compatible with this server " "(mixed old and new binaries?) %s", line); client_abort(client, "Control session aborted: Version mismatch"); return; } client->version_received = TRUE; if (o_stream_send_str(client->ctrl_output, "OK\n") < 0) { client_destroy(client); return; } } if (client->access_received) { client_abort(client, "Control session aborted: Unexpected input"); return; } if ((line = i_stream_next_line(client->ctrl_input)) == NULL) return; args = t_strsplit_tabescaped(line); if (*args == NULL || strcmp(*args, "ACCESS") != 0) { i_error("Invalid control command: %s", str_sanitize(line, 80)); client_abort(client, "Control session aborted: Invalid command"); return; } args++; if (args[0] == NULL || args[1] == NULL) { i_error("Invalid ACCESS command: %s", str_sanitize(line, 80)); client_abort(client, "Control session aborted: Invalid command"); return; } i_assert(client->access_user == NULL); i_assert(client->access_service == NULL); if (**args != '\0') { client->access_user = i_strdup(*args); client->access_anonymous = FALSE; } else { client->access_user = i_strdup("anonymous"); client->access_anonymous = TRUE; } args++; client->access_service = i_strdup(*args); i_set_failure_prefix("imap-urlauth[%s](%s): ", my_pid, client->access_user); args++; while (*args != NULL) { /* debug */ if (strcasecmp(*args, "debug") == 0) { client->debug = TRUE; /* apps=[,access_apps, &app); if (client->debug) { i_debug("User %s has URLAUTH %s access", client->access_user, app); } apps++; } } else { i_error("Invalid ACCESS parameter: %s", str_sanitize(*args, 80)); client_abort(client, "Control session aborted: Invalid command"); return; } args++; } client->access_received = TRUE; if (o_stream_send_str(client->ctrl_output, "OK\n") < 0) { client_destroy(client); return; } client->input = i_stream_create_fd(client->fd_in, MAX_INBUF_SIZE); client->output = o_stream_create_fd(client->fd_out, SIZE_MAX); client->io = io_add(client->fd_in, IO_READ, client_input, client); o_stream_set_no_error_handling(client->output, TRUE); o_stream_set_flush_callback(client->output, client_output, client); if (client->debug) { i_debug("Worker activated for access by user `%s' using service `%s'", client->access_user, client->access_service); } } static void imap_urlauth_worker_die(void) { /* do nothing */ } static void main_stdio_run(const char *access_user, const char *const *access_applications) { bool debug; debug = getenv("DEBUG") != NULL; access_user = access_user != NULL ? access_user : getenv("USER"); if (access_user == NULL && IS_STANDALONE()) access_user = getlogin(); if (access_user == NULL) i_fatal("USER environment missing"); (void)client_create_standalone(access_user, access_applications, STDIN_FILENO, STDOUT_FILENO, debug); } static void client_connected(struct master_service_connection *conn) { master_service_client_connection_accept(conn); (void)client_create(conn->fd); } int main(int argc, char *argv[]) { static const struct setting_parser_info *set_roots[] = { &imap_urlauth_worker_setting_parser_info, NULL }; enum master_service_flags service_flags = 0; enum mail_storage_service_flags storage_service_flags = MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP | MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT; ARRAY_TYPE (const_string) access_apps; const char *access_user = NULL; int c; if (IS_STANDALONE()) { service_flags |= MASTER_SERVICE_FLAG_STANDALONE | MASTER_SERVICE_FLAG_STD_CLIENT; } else { service_flags |= MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN; } master_service = master_service_init("imap-urlauth-worker", service_flags, &argc, &argv, "a:"); t_array_init(&access_apps, 4); while ((c = master_getopt(master_service)) > 0) { switch (c) { case 'a': { const char *app = t_strdup(optarg); array_push_back(&access_apps, &app); break; } default: return FATAL_DEFAULT; } } if ( optind < argc ) { access_user = argv[optind++]; } if (optind != argc) { i_fatal_status(EX_USAGE, "Unknown argument: %s", argv[optind]); } master_service_init_log_with_pid(master_service); master_service_set_die_callback(master_service, imap_urlauth_worker_die); storage_service = mail_storage_service_init(master_service, set_roots, storage_service_flags); master_service_init_finish(master_service); /* fake that we're running, so we know if client was destroyed while handling its initial input */ io_loop_set_running(current_ioloop); if (IS_STANDALONE()) { T_BEGIN { if (array_count(&access_apps) > 0) { (void)array_append_space(&access_apps); main_stdio_run(access_user, array_front(&access_apps)); } else { main_stdio_run(access_user, NULL); } } T_END; } else { io_loop_set_running(current_ioloop); } if (io_loop_is_running(current_ioloop)) master_service_run(master_service, client_connected); clients_destroy_all(); mail_storage_service_deinit(&storage_service); master_service_deinit(&master_service); return 0; } dovecot-2.3.21.1/src/imap-urlauth/Makefile.am0000644000000000000000000000400714656633576015551 00000000000000pkglibexecdir = $(libexecdir)/dovecot # Refer to comment in imap-urlauth.c for info on what these binaries are for. pkglibexec_PROGRAMS = imap-urlauth-login imap-urlauth imap-urlauth-worker AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-master \ $(BINARY_CFLAGS) # imap-urlauth-login imap_urlauth_login_CPPFLAGS = \ $(AM_CPPFLAGS) \ -I$(top_srcdir)/src/login-common imap_urlauth_login_LDADD = \ $(LIBDOVECOT_LOGIN) \ $(LIBDOVECOT) \ $(SSL_LIBS) \ $(BINARY_LDFLAGS) imap_urlauth_login_DEPENDENCIES = \ $(LIBDOVECOT_LOGIN_DEPS) \ $(LIBDOVECOT_DEPS) imap_urlauth_login_SOURCES = \ imap-urlauth-login.c \ imap-urlauth-login-settings.c # imap-urlauth imap_urlauth_CPPFLAGS = \ $(AM_CPPFLAGS) \ -I$(top_srcdir)/src/lib-dict \ -DPKG_RUNDIR=\""$(rundir)"\" imap_urlauth_LDFLAGS = -export-dynamic imap_urlauth_LDADD = $(LIBDOVECOT) \ $(BINARY_LDFLAGS) imap_urlauth_DEPENDENCIES = $(LIBDOVECOT_DEPS) imap_urlauth_SOURCES = \ imap-urlauth.c \ imap-urlauth-client.c \ imap-urlauth-settings.c # imap-urlauth-worker imap_urlauth_worker_CPPFLAGS = \ $(AM_CPPFLAGS) \ -I$(top_srcdir)/src/lib-dict \ -I$(top_srcdir)/src/imap \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-imap-storage \ -I$(top_srcdir)/src/lib-imap-urlauth \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-storage \ -I$(top_srcdir)/src/login-common imap_urlauth_worker_LDFLAGS = -export-dynamic \ $(BINARY_LDFLAGS) urlauth_libs = \ $(top_builddir)/src/lib-imap-urlauth/libimap-urlauth.la imap_urlauth_worker_LDADD = $(urlauth_libs) $(LIBDOVECOT_STORAGE) $(LIBDOVECOT) imap_urlauth_worker_DEPENDENCIES = $(urlauth_libs) $(LIBDOVECOT_STORAGE_DEPS) $(LIBDOVECOT_DEPS) imap_urlauth_worker_SOURCES = \ imap-urlauth-worker.c \ imap-urlauth-worker-settings.c noinst_HEADERS = \ imap-urlauth-client.h \ imap-urlauth-common.h \ imap-urlauth-settings.h \ imap-urlauth-login-settings.h \ imap-urlauth-worker-settings.h dovecot-2.3.21.1/src/imap-urlauth/imap-urlauth-common.h0000644000000000000000000000043714656633576017567 00000000000000#ifndef IMAP_URLAUTH_COMMON_H #define IMAP_URLAUTH_COMMON_H #include "lib.h" #include "imap-urlauth-client.h" #include "imap-urlauth-settings.h" extern bool verbose_proctitle; extern struct mail_storage_service_ctx *storage_service; void imap_urlauth_refresh_proctitle(void); #endif dovecot-2.3.21.1/src/imap-urlauth/imap-urlauth-login-settings.c0000644000000000000000000000374214656633576021242 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "settings-parser.h" #include "service-settings.h" #include "login-settings.h" #include "imap-urlauth-login-settings.h" #include /* */ static struct file_listener_settings imap_urlauth_login_unix_listeners_array[] = { { "imap-urlauth", 0666, "", "" } }; static struct file_listener_settings *imap_urlauth_login_unix_listeners[] = { &imap_urlauth_login_unix_listeners_array[0] }; static buffer_t imap_urlauth_login_unix_listeners_buf = { { { imap_urlauth_login_unix_listeners, sizeof(imap_urlauth_login_unix_listeners) } } }; /* */ struct service_settings imap_urlauth_login_service_settings = { .name = "imap-urlauth-login", .protocol = "imap", .type = "login", .executable = "imap-urlauth-login", .user = "$default_login_user", .group = "", .privileged_group = "", .extra_groups = "", .chroot = "token-login", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 0, .client_limit = 0, .service_count = 1, .idle_kill = 0, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &imap_urlauth_login_unix_listeners_buf, sizeof(imap_urlauth_login_unix_listeners[0]) } }, .fifo_listeners = ARRAY_INIT, .inet_listeners = ARRAY_INIT }; static const struct setting_define imap_urlauth_login_setting_defines[] = { SETTING_DEFINE_LIST_END }; static const struct setting_parser_info *imap_urlauth_login_setting_dependencies[] = { &login_setting_parser_info, NULL }; const struct setting_parser_info imap_urlauth_login_setting_parser_info = { .module_name = "imap-urlauth-login", .defines = imap_urlauth_login_setting_defines, .type_offset = SIZE_MAX, .parent_offset = SIZE_MAX, .dependencies = imap_urlauth_login_setting_dependencies }; const struct setting_parser_info *imap_urlauth_login_setting_roots[] = { &login_setting_parser_info, &imap_urlauth_login_setting_parser_info, NULL }; dovecot-2.3.21.1/src/imap-urlauth/imap-urlauth-login.c0000644000000000000000000001236314656633576017403 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "login-common.h" #include "str.h" #include "strescape.h" #include "base64.h" #include "net.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "master-service.h" #include "auth-client.h" #include "client-common.h" #include "imap-urlauth-login-settings.h" #define IMAP_URLAUTH_PROTOCOL_MAJOR_VERSION 2 #define IMAP_URLAUTH_PROTOCOL_MINOR_VERSION 0 struct imap_urlauth_client { struct client common; const struct imap_urlauth_login_settings *set; bool version_received:1; }; static void imap_urlauth_client_auth_result(struct client *client, enum client_auth_result result, const struct client_auth_reply *reply ATTR_UNUSED, const char *text ATTR_UNUSED) { if (result != CLIENT_AUTH_RESULT_SUCCESS) { /* failed or otherwise invalid status */ client_send_raw(client, "FAILED\n"); client_destroy(client, "Authentication failed"); } else { /* authentication succeeded */ } } static void imap_urlauth_client_handle_input(struct client *client) { #define AUTH_ARG_COUNT 6 struct imap_urlauth_client *uauth_client = (struct imap_urlauth_client *)client; struct net_unix_cred cred; const char *line; const char *const *args; pid_t pid; if (!uauth_client->version_received) { if ((line = i_stream_next_line(client->input)) == NULL) return; if (!version_string_verify(line, "imap-urlauth", IMAP_URLAUTH_PROTOCOL_MAJOR_VERSION)) { e_error(client->event, "IMAP URLAUTH client not compatible with this server " "(mixed old and new binaries?) %s", line); client_destroy(client, "Version mismatch"); return; } uauth_client->version_received = TRUE; } if ((line = i_stream_next_line(client->input)) == NULL) return; /* read authentication info from input; "AUTH"\t\t\t\t\t */ args = t_strsplit_tabescaped(line); if (str_array_length(args) < AUTH_ARG_COUNT || strcmp(args[0], "AUTH") != 0 || str_to_pid(args[2], &pid) < 0) { e_error(client->event, "IMAP URLAUTH client sent unexpected AUTH input: %s", line); client_destroy(client, "Unexpected input"); return; } /* only imap and submission have direct access to urlauth service */ if (strcmp(args[1], "imap") != 0 && strcmp(args[1], "submission") != 0) { e_error(client->event, "IMAP URLAUTH accessed from inappropriate service: %s", args[1]); client_destroy(client, "Unexpected input"); return; } /* verify session pid if possible */ if (net_getunixcred(client->fd, &cred) == 0 && cred.pid != (pid_t)-1 && pid != cred.pid) { e_error(client->event, "IMAP URLAUTH client sent invalid session pid %ld in AUTH request: " "it did not match peer credentials (pid=%ld, uid=%ld)", (long)pid, (long)cred.pid, (long)cred.uid); client_destroy(client, "Invalid AUTH request"); return; } T_BEGIN { string_t *auth_data = t_str_new(128); string_t *init_resp; unsigned int i; str_append(auth_data, args[1]); for (i = 2; i < AUTH_ARG_COUNT; i++) { str_append_c(auth_data, '\0'); str_append(auth_data, args[i]); } init_resp = t_str_new(256); base64_encode(str_data(auth_data), str_len(auth_data), init_resp); (void)client_auth_begin_private(client, "DOVECOT-TOKEN", str_c(init_resp)); } T_END; } static void imap_urlauth_client_input(struct client *client) { if (!client_read(client)) return; client_ref(client); o_stream_cork(client->output); if (!auth_client_is_connected(auth_client)) { /* we're not currently connected to auth process - don't allow any commands */ timeout_remove(&client->to_auth_waiting); client->input_blocked = TRUE; } else { imap_urlauth_client_handle_input(client); } o_stream_uncork(client->output); client_unref(&client); } static struct client *imap_urlauth_client_alloc(pool_t pool) { struct imap_urlauth_client *uauth_client; uauth_client = p_new(pool, struct imap_urlauth_client, 1); return &uauth_client->common; } static void imap_urlauth_client_create (struct client *client, void **other_sets) { struct imap_urlauth_client *uauth_client = (struct imap_urlauth_client *)client; uauth_client->set = other_sets[0]; client->io = io_add_istream(client->input, client_input, client); } static void imap_urlauth_login_preinit(void) { login_set_roots = imap_urlauth_login_setting_roots; } static void imap_urlauth_login_init(void) { } static void imap_urlauth_login_deinit(void) { clients_destroy_all(); } static struct client_vfuncs imap_urlauth_vfuncs = { .alloc = imap_urlauth_client_alloc, .create = imap_urlauth_client_create, .input = imap_urlauth_client_input, .auth_result = imap_urlauth_client_auth_result, .send_raw_data = client_common_send_raw_data, .free = client_common_default_free, }; static struct login_binary imap_urlauth_login_binary = { .protocol = "imap-urlauth", .process_name = "imap-urlauth-login", .default_login_socket = LOGIN_TOKEN_DEFAULT_SOCKET, .event_category = { .name = "imap", }, .client_vfuncs = &imap_urlauth_vfuncs, .preinit = imap_urlauth_login_preinit, .init = imap_urlauth_login_init, .deinit = imap_urlauth_login_deinit, .anonymous_login_acceptable = TRUE, }; int main(int argc, char *argv[]) { return login_binary_run(&imap_urlauth_login_binary, argc, argv); } dovecot-2.3.21.1/src/lib-imap-client/0000755000000000000000000000000014656633637014130 500000000000000dovecot-2.3.21.1/src/lib-imap-client/imapc-connection.c0000644000000000000000000021602314656633576017450 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "net.h" #include "istream.h" #include "ostream.h" #include "base64.h" #include "write-full.h" #include "str.h" #include "time-util.h" #include "dns-lookup.h" #include "dsasl-client.h" #include "iostream-rawlog.h" #include "iostream-ssl.h" #include "imap-quote.h" #include "imap-util.h" #include "imap-parser.h" #include "imapc-client-private.h" #include "imapc-connection.h" #include #include #define IMAPC_COMMAND_STATE_AUTHENTICATE_CONTINUE 10000 #define IMAPC_MAX_INLINE_LITERAL_SIZE (1024*32) /* If LOGOUT reply takes longer than this, disconnect. */ #define IMAPC_LOGOUT_TIMEOUT_MSECS 5000 enum imapc_input_state { IMAPC_INPUT_STATE_NONE = 0, IMAPC_INPUT_STATE_PLUS, IMAPC_INPUT_STATE_UNTAGGED, IMAPC_INPUT_STATE_UNTAGGED_NUM, IMAPC_INPUT_STATE_TAGGED }; struct imapc_command_stream { unsigned int pos; uoff_t size; struct istream *input; }; struct imapc_command { pool_t pool; buffer_t *data; unsigned int send_pos; unsigned int tag; enum imapc_command_flags flags; struct imapc_connection *conn; /* If non-NULL, points to the mailbox where this command should be executed */ struct imapc_client_mailbox *box; ARRAY(struct imapc_command_stream) streams; imapc_command_callback_t *callback; void *context; /* This is the AUTHENTICATE command */ bool authenticate:1; /* This is the IDLE command */ bool idle:1; /* Waiting for '+' literal reply before we can continue */ bool wait_for_literal:1; /* Command is fully sent to server */ bool sent:1; }; ARRAY_DEFINE_TYPE(imapc_command, struct imapc_command *); struct imapc_connection_literal { char *temp_path; int fd; uoff_t bytes_left; const struct imap_arg *parent_arg; unsigned int list_idx; }; struct imapc_connection { struct imapc_client *client; char *name; int refcount; int fd; struct io *io; struct istream *input, *raw_input; struct ostream *output, *raw_output; struct imap_parser *parser; struct timeout *to; struct timeout *to_output; struct dns_lookup *dns_lookup; struct dsasl_client *sasl_client; struct ssl_iostream *ssl_iostream; int (*input_callback)(struct imapc_connection *conn); enum imapc_input_state input_state; unsigned int cur_tag; uint32_t cur_num; struct timeval last_connect; unsigned int reconnect_count; /* If QRESYNC isn't used, this is set immediately after issuing SELECT/EXAMINE. We could differentiate better whether a mailbox is "being selected" vs "fully selected", but that code is already in the imapc-storage side so it would have to be moved or duplicated here. And since nothing actually cares about this distinction (yet), don't bother with it for now. This is set to NULL when the mailbox is closed from imapc-storage point of view, even if the server is still in selected state (see selected_on_server). */ struct imapc_client_mailbox *selected_box; /* If QRESYNC is used, this is set when SELECT/EXAMINE is issued. If the server is already in selected state, the selected_box is most likely already NULL at this point, because imapc-storage has closed it. */ struct imapc_client_mailbox *qresync_selecting_box; enum imapc_connection_state state; char *disconnect_reason; enum imapc_capability capabilities; char **capabilities_list; imapc_command_callback_t *login_callback; void *login_context; /* commands pending in queue to be sent */ ARRAY_TYPE(imapc_command) cmd_send_queue; /* commands that have been sent, waiting for their tagged reply */ ARRAY_TYPE(imapc_command) cmd_wait_list; /* commands that were already sent, but were aborted since (due to unselecting mailbox). */ ARRAY_TYPE(seq_range) aborted_cmd_tags; unsigned int reconnect_command_count; unsigned int ips_count, prev_connect_idx; struct ip_addr *ips; struct imapc_connection_literal literal; ARRAY(struct imapc_arg_file) literal_files; unsigned int throttle_msecs; unsigned int throttle_shrink_msecs; unsigned int last_successful_throttle_msecs; bool throttle_pending; struct timeval throttle_end_timeval; struct timeout *to_throttle, *to_throttle_shrink; bool reconnecting:1; bool reconnect_waiting:1; bool reconnect_ok:1; bool idling:1; bool idle_stopping:1; bool idle_plus_waiting:1; bool select_waiting_reply:1; /* TRUE if IMAP server is in SELECTED state. select_box may be NULL though, if we already closed the mailbox from client point of view. */ bool selected_on_server:1; }; static void imapc_connection_capability_cb(const struct imapc_command_reply *reply, void *context); static int imapc_connection_output(struct imapc_connection *conn); static int imapc_connection_ssl_init(struct imapc_connection *conn); static void imapc_command_free(struct imapc_command *cmd); static void imapc_command_send_more(struct imapc_connection *conn); static void imapc_login_callback(struct imapc_connection *conn, const struct imapc_command_reply *reply); static void imapc_auth_ok(struct imapc_connection *conn) { if (conn->client->set.debug) i_debug("imapc(%s): Authenticated successfully", conn->name); if (conn->client->state_change_callback == NULL) return; conn->client->state_change_callback(conn->client->state_change_context, IMAPC_STATE_CHANGE_AUTH_OK, NULL); } static void imapc_auth_failed(struct imapc_connection *conn, const struct imapc_command_reply *_reply, const char *error) { struct imapc_command_reply reply = *_reply; reply.text_without_resp = reply.text_full = t_strdup_printf("Authentication failed: %s", error); if (reply.state != IMAPC_COMMAND_STATE_DISCONNECTED) { reply.state = IMAPC_COMMAND_STATE_AUTH_FAILED; i_error("imapc(%s): %s", conn->name, reply.text_full); } imapc_login_callback(conn, &reply); if (conn->client->state_change_callback == NULL) return; conn->client->state_change_callback(conn->client->state_change_context, IMAPC_STATE_CHANGE_AUTH_FAILED, error); } struct imapc_connection * imapc_connection_init(struct imapc_client *client, imapc_command_callback_t *login_callback, void *login_context) { struct imapc_connection *conn; conn = i_new(struct imapc_connection, 1); conn->refcount = 1; conn->client = client; conn->login_callback = login_callback; conn->login_context = login_context; conn->fd = -1; conn->name = i_strdup_printf("%s:%u", client->set.host, client->set.port); conn->literal.fd = -1; conn->reconnect_ok = (client->set.connect_retry_count>0); i_array_init(&conn->cmd_send_queue, 8); i_array_init(&conn->cmd_wait_list, 32); i_array_init(&conn->literal_files, 4); i_array_init(&conn->aborted_cmd_tags, 8); if (client->set.debug) i_debug("imapc(%s): Created new connection", conn->name); imapc_client_ref(client); return conn; } static void imapc_connection_ref(struct imapc_connection *conn) { i_assert(conn->refcount > 0); conn->refcount++; } static void imapc_connection_unref(struct imapc_connection **_conn) { struct imapc_connection *conn = *_conn; i_assert(conn->refcount > 0); *_conn = NULL; if (--conn->refcount > 0) return; i_assert(conn->disconnect_reason == NULL); if (conn->capabilities_list != NULL) p_strsplit_free(default_pool, conn->capabilities_list); array_free(&conn->cmd_send_queue); array_free(&conn->cmd_wait_list); array_free(&conn->literal_files); array_free(&conn->aborted_cmd_tags); imapc_client_unref(&conn->client); i_free(conn->ips); i_free(conn->name); i_free(conn); } void imapc_connection_deinit(struct imapc_connection **_conn) { imapc_connection_disconnect(*_conn); imapc_connection_unref(_conn); } void imapc_connection_ioloop_changed(struct imapc_connection *conn) { if (conn->io != NULL) conn->io = io_loop_move_io(&conn->io); if (conn->to != NULL) conn->to = io_loop_move_timeout(&conn->to); if (conn->to_throttle != NULL) conn->to_throttle = io_loop_move_timeout(&conn->to_throttle); if (conn->to_throttle_shrink != NULL) conn->to_throttle_shrink = io_loop_move_timeout(&conn->to_throttle_shrink); if (conn->output != NULL) o_stream_switch_ioloop(conn->output); if (conn->dns_lookup != NULL) dns_lookup_switch_ioloop(conn->dns_lookup); if (conn->client->ioloop == NULL && conn->to_output != NULL) { /* we're only once moving the to_output to the main ioloop, since timeout moves currently also reset the timeout. (the rest of the times this is a no-op) */ conn->to_output = io_loop_move_timeout(&conn->to_output); } } static const char *imapc_command_get_readable(struct imapc_command *cmd) { string_t *str = t_str_new(256); const unsigned char *data = cmd->data->data; unsigned int i; for (i = 0; i < cmd->data->used; i++) { if (data[i] != '\r' && data[i] != '\n') str_append_c(str, data[i]); } return str_c(str); } static void imapc_connection_abort_commands_array(ARRAY_TYPE(imapc_command) *cmd_array, ARRAY_TYPE(imapc_command) *dest_array, struct imapc_client_mailbox *only_box, bool keep_retriable) { struct imapc_command *cmd; unsigned int i; for (i = 0; i < array_count(cmd_array); ) { cmd = array_idx_elem(cmd_array, i); if (cmd->box != only_box && only_box != NULL) i++; else if (keep_retriable && (cmd->flags & IMAPC_COMMAND_FLAG_RETRIABLE) != 0) { cmd->send_pos = 0; cmd->wait_for_literal = 0; cmd->flags |= IMAPC_COMMAND_FLAG_RECONNECTED; i++; } else { array_delete(cmd_array, i, 1); array_push_back(dest_array, &cmd); } } } void imapc_connection_abort_commands(struct imapc_connection *conn, struct imapc_client_mailbox *only_box, bool keep_retriable) { struct imapc_command *cmd; ARRAY_TYPE(imapc_command) tmp_array; struct imapc_command_reply reply; t_array_init(&tmp_array, 8); imapc_connection_abort_commands_array(&conn->cmd_wait_list, &tmp_array, only_box, keep_retriable); imapc_connection_abort_commands_array(&conn->cmd_send_queue, &tmp_array, only_box, keep_retriable); if (array_count(&conn->cmd_wait_list) > 0 && only_box == NULL) { /* need to move all the waiting commands to send queue */ array_append_array(&conn->cmd_wait_list, &conn->cmd_send_queue); array_clear(&conn->cmd_send_queue); array_append_array(&conn->cmd_send_queue, &conn->cmd_wait_list); array_clear(&conn->cmd_wait_list); } /* abort the commands. we'll do it here later so that if the callback recurses us back here we don't crash */ i_zero(&reply); reply.state = IMAPC_COMMAND_STATE_DISCONNECTED; if (only_box != NULL) { reply.text_without_resp = reply.text_full = "Unselecting mailbox"; } else { reply.text_without_resp = reply.text_full = "Disconnected from server"; } array_foreach_elem(&tmp_array, cmd) { if (cmd->sent && conn->state == IMAPC_CONNECTION_STATE_DONE) { /* We're not disconnected, so the reply will still come. Remember that it needs to be ignored. */ seq_range_array_add(&conn->aborted_cmd_tags, cmd->tag); } cmd->callback(&reply, cmd->context); imapc_command_free(cmd); } if (array_count(&conn->cmd_wait_list) == 0) timeout_remove(&conn->to); } static void imapc_login_callback(struct imapc_connection *conn, const struct imapc_command_reply *reply) { if (conn->login_callback != NULL) conn->login_callback(reply, conn->login_context); } static void imapc_connection_set_state(struct imapc_connection *conn, enum imapc_connection_state state) { struct imapc_command_reply reply; conn->state = state; switch (state) { case IMAPC_CONNECTION_STATE_DISCONNECTED: i_zero(&reply); reply.state = IMAPC_COMMAND_STATE_DISCONNECTED; reply.text_full = "Disconnected from server"; if (conn->disconnect_reason != NULL) { reply.text_full = t_strdup_printf("%s: %s", reply.text_full, conn->disconnect_reason); i_free_and_null(conn->disconnect_reason); } reply.text_without_resp = reply.text_full; if (!conn->reconnecting) { imapc_login_callback(conn, &reply); i_free(conn->ips); conn->ips_count = 0; } array_clear(&conn->aborted_cmd_tags); conn->idling = FALSE; conn->idle_plus_waiting = FALSE; conn->idle_stopping = FALSE; conn->select_waiting_reply = FALSE; conn->qresync_selecting_box = NULL; conn->selected_box = NULL; conn->selected_on_server = FALSE; /* fall through */ case IMAPC_CONNECTION_STATE_DONE: /* if we came from imapc_client_get_capabilities(), stop so it can finish up and not just hang indefinitely. */ if (conn->client->stop_on_state_finish && !conn->reconnecting) imapc_client_stop(conn->client); break; default: break; } } static void imapc_connection_lfiles_free(struct imapc_connection *conn) { struct imapc_arg_file *lfile; array_foreach_modifiable(&conn->literal_files, lfile) { if (close(lfile->fd) < 0) i_error("imapc: close(literal file) failed: %m"); } array_clear(&conn->literal_files); } static void imapc_connection_literal_reset(struct imapc_connection_literal *literal) { i_close_fd_path(&literal->fd, literal->temp_path); i_free_and_null(literal->temp_path); i_zero(literal); literal->fd = -1; } void imapc_connection_disconnect_full(struct imapc_connection *conn, bool reconnecting) { /* timeout may be set also in disconnected state */ timeout_remove(&conn->to); conn->reconnecting = reconnecting; if (conn->state == IMAPC_CONNECTION_STATE_DISCONNECTED) { i_assert(array_count(&conn->cmd_wait_list) == 0); if (conn->reconnect_command_count == 0) imapc_connection_abort_commands(conn, NULL, reconnecting); return; } if (conn->client->set.debug) i_debug("imapc(%s): Disconnected", conn->name); if (conn->dns_lookup != NULL) dns_lookup_abort(&conn->dns_lookup); imapc_connection_lfiles_free(conn); imapc_connection_literal_reset(&conn->literal); timeout_remove(&conn->to_output); timeout_remove(&conn->to_throttle); timeout_remove(&conn->to_throttle_shrink); if (conn->parser != NULL) imap_parser_unref(&conn->parser); io_remove(&conn->io); ssl_iostream_destroy(&conn->ssl_iostream); if (conn->fd != -1) { i_stream_destroy(&conn->input); o_stream_destroy(&conn->output); net_disconnect(conn->fd); conn->fd = -1; } /* get capabilities again after reconnection. this is especially important because post-login capabilities often do not contain AUTH= capabilities. */ conn->capabilities = 0; if (conn->capabilities_list != NULL) { p_strsplit_free(default_pool, conn->capabilities_list); conn->capabilities_list = NULL; } imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_DISCONNECTED); imapc_connection_abort_commands(conn, NULL, reconnecting); if (!reconnecting) { imapc_client_try_stop(conn->client); } } void imapc_connection_set_no_reconnect(struct imapc_connection *conn) { conn->reconnect_ok = FALSE; } void imapc_connection_disconnect(struct imapc_connection *conn) { imapc_connection_disconnect_full(conn, FALSE); } static void imapc_connection_set_disconnected(struct imapc_connection *conn) { imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_DISCONNECTED); imapc_connection_abort_commands(conn, NULL, FALSE); } static bool imapc_connection_can_reconnect(struct imapc_connection *conn) { if (conn->client->logging_out) return FALSE; if (conn->client->set.connect_retry_count == 0 || (conn->client->set.connect_retry_count < UINT_MAX && conn->reconnect_count >= conn->client->set.connect_retry_count)) return FALSE; if (conn->selected_box != NULL) return imapc_client_mailbox_can_reconnect(conn->selected_box); else { return conn->reconnect_command_count == 0 && conn->reconnect_ok; } } static void imapc_connection_reconnect(struct imapc_connection *conn) { conn->reconnect_ok = FALSE; conn->reconnect_waiting = FALSE; if (conn->selected_box != NULL) { i_assert(!conn->selected_box->reconnecting); conn->selected_box->reconnecting = TRUE; /* if we fail again, avoid reconnecting immediately. if the server is broken we could just get into an infinitely failing reconnection loop. */ conn->selected_box->reconnect_ok = FALSE; } imapc_connection_disconnect_full(conn, TRUE); imapc_connection_connect(conn); } void imapc_connection_try_reconnect(struct imapc_connection *conn, const char *errstr, unsigned int delay_msecs, bool connect_error) { /* Try the next IP address only for connect() problems. */ if (conn->prev_connect_idx + 1 < conn->ips_count && connect_error) { i_warning("imapc(%s): %s - trying the next IP", conn->name, errstr); conn->reconnect_ok = TRUE; imapc_connection_disconnect_full(conn, TRUE); imapc_connection_connect(conn); return; } if (!imapc_connection_can_reconnect(conn)) { i_error("imapc(%s): %s - disconnecting", conn->name, errstr); imapc_connection_disconnect(conn); } else { conn->reconnecting = TRUE; i_warning("imapc(%s): %s - reconnecting (delay %u ms)", conn->name, errstr, delay_msecs); if (delay_msecs == 0) imapc_connection_reconnect(conn); else { imapc_connection_disconnect_full(conn, TRUE); conn->to = timeout_add(delay_msecs, imapc_connection_reconnect, conn); conn->reconnect_count++; conn->reconnect_waiting = TRUE; } } } static void ATTR_FORMAT(2, 3) imapc_connection_input_error(struct imapc_connection *conn, const char *fmt, ...) { va_list va; va_start(va, fmt); i_error("imapc(%s): Server sent invalid input: %s", conn->name, t_strdup_vprintf(fmt, va)); imapc_connection_disconnect(conn); va_end(va); } static bool last_arg_is_fetch_body(const struct imap_arg *args, const struct imap_arg **parent_arg_r, unsigned int *idx_r) { const struct imap_arg *list; const char *name; unsigned int count; if (args[0].type == IMAP_ARG_ATOM && imap_arg_atom_equals(&args[1], "FETCH") && imap_arg_get_list_full(&args[2], &list, &count) && count >= 2 && list[count].type == IMAP_ARG_LITERAL_SIZE && imap_arg_get_atom(&list[count-1], &name) && strncasecmp(name, "BODY[", 5) == 0) { *parent_arg_r = &args[2]; *idx_r = count; return TRUE; } return FALSE; } static int imapc_connection_read_literal_init(struct imapc_connection *conn, uoff_t size, const struct imap_arg *args) { const char *path; const struct imap_arg *parent_arg; unsigned int idx; i_assert(conn->literal.fd == -1); if (size <= IMAPC_MAX_INLINE_LITERAL_SIZE || !last_arg_is_fetch_body(args, &parent_arg, &idx)) { /* read the literal directly into parser */ return 0; } conn->literal.fd = imapc_client_create_temp_fd(conn->client, &path); if (conn->literal.fd == -1) return -1; conn->literal.temp_path = i_strdup(path); conn->literal.bytes_left = size; conn->literal.parent_arg = parent_arg; conn->literal.list_idx = idx; return 1; } static int imapc_connection_read_literal(struct imapc_connection *conn) { struct imapc_arg_file *lfile; const unsigned char *data; size_t size; if (conn->literal.bytes_left == 0) return 1; data = i_stream_get_data(conn->input, &size); if (size > conn->literal.bytes_left) size = conn->literal.bytes_left; if (size > 0) { if (write_full(conn->literal.fd, data, size) < 0) { i_error("imapc(%s): write(%s) failed: %m", conn->name, conn->literal.temp_path); imapc_connection_disconnect(conn); return -1; } i_stream_skip(conn->input, size); conn->literal.bytes_left -= size; } if (conn->literal.bytes_left > 0) return 0; /* finished */ lfile = array_append_space(&conn->literal_files); lfile->fd = conn->literal.fd; lfile->parent_arg = conn->literal.parent_arg; lfile->list_idx = conn->literal.list_idx; conn->literal.fd = -1; imapc_connection_literal_reset(&conn->literal); return 1; } static int imapc_connection_read_line_more(struct imapc_connection *conn, const struct imap_arg **imap_args_r) { uoff_t literal_size; int ret; if ((ret = imapc_connection_read_literal(conn)) <= 0) return ret; ret = imap_parser_read_args(conn->parser, 0, IMAP_PARSE_FLAG_LITERAL_SIZE | IMAP_PARSE_FLAG_ATOM_ALLCHARS | IMAP_PARSE_FLAG_LITERAL8 | IMAP_PARSE_FLAG_SERVER_TEXT, imap_args_r); if (ret == -2) { /* need more data */ return 0; } if (ret < 0) { enum imap_parser_error parser_error; const char *err_msg = imap_parser_get_error(conn->parser, &parser_error); if (parser_error != IMAP_PARSE_ERROR_BAD_SYNTAX) imapc_connection_input_error(conn, "Error parsing input: %s", err_msg); else i_error("Error parsing input: %s", err_msg); return -1; } if (imap_parser_get_literal_size(conn->parser, &literal_size)) { if (imapc_connection_read_literal_init(conn, literal_size, *imap_args_r) <= 0) { imap_parser_read_last_literal(conn->parser); return 2; } return imapc_connection_read_line_more(conn, imap_args_r); } return 1; } static int imapc_connection_read_line(struct imapc_connection *conn, const struct imap_arg **imap_args_r) { const unsigned char *data; size_t size; int ret; while ((ret = imapc_connection_read_line_more(conn, imap_args_r)) == 2) ; if (ret > 0) { data = i_stream_get_data(conn->input, &size); if (size >= 2 && data[0] == '\r' && data[1] == '\n') i_stream_skip(conn->input, 2); else if (size >= 1 && data[0] == '\n') i_stream_skip(conn->input, 1); else i_panic("imapc: Missing LF from input line"); } else if (ret < 0) { data = i_stream_get_data(conn->input, &size); unsigned char *lf = memchr(data, '\n', size); if (lf != NULL) i_stream_skip(conn->input, (lf - data) + 1); } return ret; } static int imapc_connection_parse_capability(struct imapc_connection *conn, const char *value) { const char *const *tmp; unsigned int i; if (conn->client->set.debug) { i_debug("imapc(%s): Server capabilities: %s", conn->name, value); } conn->capabilities = 0; if (conn->capabilities_list != NULL) p_strsplit_free(default_pool, conn->capabilities_list); conn->capabilities_list = p_strsplit(default_pool, value, " "); for (tmp = t_strsplit(value, " "); *tmp != NULL; tmp++) { for (i = 0; imapc_capability_names[i].name != NULL; i++) { const struct imapc_capability_name *cap = &imapc_capability_names[i]; if (strcasecmp(*tmp, cap->name) == 0) { conn->capabilities |= cap->capability; break; } } } if ((conn->capabilities & IMAPC_CAPABILITY_IMAP4REV1) == 0) { imapc_connection_input_error(conn, "CAPABILITY list is missing IMAP4REV1"); return -1; } return 0; } static int imapc_connection_handle_resp_text_code(struct imapc_connection *conn, const char *key, const char *value) { if (strcasecmp(key, "CAPABILITY") == 0) { if (imapc_connection_parse_capability(conn, value) < 0) return -1; } if (strcasecmp(key, "CLOSED") == 0) { /* QRESYNC: SELECTing another mailbox */ if (conn->qresync_selecting_box != NULL) { conn->selected_box = conn->qresync_selecting_box; conn->qresync_selecting_box = NULL; } else { conn->selected_on_server = FALSE; } } return 0; } static int imapc_connection_handle_resp_text(struct imapc_connection *conn, const char *text, const char **key_r, const char **value_r) { const char *p, *value; i_assert(text[0] == '['); p = strchr(text, ']'); if (p == NULL) { imapc_connection_input_error(conn, "Missing ']' in resp-text"); return -1; } text = t_strdup_until(text + 1, p); value = strchr(text, ' '); if (value != NULL) { *key_r = t_strdup_until(text, value); *value_r = value + 1; } else { *key_r = text; *value_r = ""; } return imapc_connection_handle_resp_text_code(conn, *key_r, *value_r); } static int imapc_connection_handle_imap_resp_text(struct imapc_connection *conn, const struct imap_arg *args, const char **key_r, const char **value_r) { const char *text; if (args->type != IMAP_ARG_ATOM) return 0; text = imap_args_to_str(args); if (*text != '[') { if (*text == '\0') { imapc_connection_input_error(conn, "Missing text in resp-text"); return -1; } return 0; } return imapc_connection_handle_resp_text(conn, text, key_r, value_r); } static bool need_literal(const char *str) { unsigned int i; for (i = 0; str[i] != '\0'; i++) { unsigned char c = str[i]; if ((c & 0x80) != 0 || c == '\r' || c == '\n') return TRUE; } return FALSE; } static void imapc_connection_input_reset(struct imapc_connection *conn) { conn->input_state = IMAPC_INPUT_STATE_NONE; conn->cur_tag = 0; conn->cur_num = 0; if (conn->parser != NULL) imap_parser_reset(conn->parser); imapc_connection_lfiles_free(conn); } static void imapc_connection_auth_finish(struct imapc_connection *conn, const struct imapc_command_reply *reply) { if (reply->state != IMAPC_COMMAND_STATE_OK) { imapc_auth_failed(conn, reply, reply->text_full); imapc_connection_disconnect(conn); return; } imapc_auth_ok(conn); i_assert(array_count(&conn->cmd_wait_list) == 0); timeout_remove(&conn->to); imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_DONE); imapc_login_callback(conn, reply); imapc_command_send_more(conn); } static void imapc_connection_login_cb(const struct imapc_command_reply *reply, void *context) { struct imapc_connection *conn = context; imapc_connection_auth_finish(conn, reply); } static void imapc_connection_proxyauth_login_cb(const struct imapc_command_reply *reply, void *context) { struct imapc_connection *conn = context; const struct imapc_client_settings *set = &conn->client->set; struct imapc_command *cmd; if (reply->state == IMAPC_COMMAND_STATE_OK) { cmd = imapc_connection_cmd(conn, imapc_connection_login_cb, conn); imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN); imapc_command_sendf(cmd, "PROXYAUTH %s", set->username); imapc_command_send_more(conn); } else { imapc_connection_auth_finish(conn, reply); } } static void imapc_connection_authenticate_cb(const struct imapc_command_reply *reply, void *context) { struct imapc_connection *conn = context; const unsigned char *sasl_output; size_t input_len, sasl_output_len; buffer_t *buf; const char *error; if ((int)reply->state != IMAPC_COMMAND_STATE_AUTHENTICATE_CONTINUE) { dsasl_client_free(&conn->sasl_client); imapc_connection_auth_finish(conn, reply); return; } input_len = strlen(reply->text_full); buf = t_buffer_create(MAX_BASE64_DECODED_SIZE(input_len)); if (base64_decode(reply->text_full, input_len, NULL, buf) < 0) { imapc_auth_failed(conn, reply, t_strdup_printf("Server sent non-base64 input for AUTHENTICATE: %s", reply->text_full)); } else if (dsasl_client_input(conn->sasl_client, buf->data, buf->used, &error) < 0) { imapc_auth_failed(conn, reply, error); } else if (dsasl_client_output(conn->sasl_client, &sasl_output, &sasl_output_len, &error) < 0) { imapc_auth_failed(conn, reply, error); } else { string_t *imap_output = t_str_new(MAX_BASE64_ENCODED_SIZE(sasl_output_len)+2); base64_encode(sasl_output, sasl_output_len, imap_output); str_append(imap_output, "\r\n"); o_stream_nsend(conn->output, str_data(imap_output), str_len(imap_output)); return; } imapc_connection_disconnect(conn); } static bool imapc_connection_have_auth(struct imapc_connection *conn, const char *mech_name) { char *const *capa; for (capa = conn->capabilities_list; *capa != NULL; capa++) { if (strncasecmp(*capa, "AUTH=", 5) == 0 && strcasecmp((*capa)+5, mech_name) == 0) return TRUE; } return FALSE; } static int imapc_connection_get_sasl_mech(struct imapc_connection *conn, const struct dsasl_client_mech **mech_r, const char **error_r) { const struct imapc_client_settings *set = &conn->client->set; const char *const *mechanisms = t_strsplit_spaces(set->sasl_mechanisms, ", "); /* find one of the specified SASL mechanisms */ for (; *mechanisms != NULL; mechanisms++) { if (imapc_connection_have_auth(conn, *mechanisms)) { *mech_r = dsasl_client_mech_find(*mechanisms); if (*mech_r != NULL) return 0; *error_r = t_strdup_printf( "Support for SASL method '%s' is missing", *mechanisms); return -1; } } *error_r = t_strdup_printf("IMAP server doesn't support any of the requested SASL mechanisms: %s", set->sasl_mechanisms); return -1; } static void imapc_connection_authenticate(struct imapc_connection *conn) { const struct imapc_client_settings *set = &conn->client->set; struct imapc_command *cmd; struct dsasl_client_settings sasl_set; const struct dsasl_client_mech *sasl_mech = NULL; const char *error; if (conn->client->set.debug) { if (set->master_user == NULL) { i_debug("imapc(%s): Authenticating as %s", conn->name, set->username); } else { i_debug("imapc(%s): Authenticating as %s for user %s", conn->name, set->master_user, set->username); } } if (set->sasl_mechanisms != NULL && set->sasl_mechanisms[0] != '\0') { if (imapc_connection_get_sasl_mech(conn, &sasl_mech, &error) < 0) { struct imapc_command_reply reply; i_zero(&reply); reply.state = IMAPC_COMMAND_STATE_DISCONNECTED; reply.text_full = ""; imapc_auth_failed(conn, &reply, error); imapc_connection_disconnect(conn); return; } } if (set->use_proxyauth && set->master_user != NULL) { /* We can use LOGIN command */ cmd = imapc_connection_cmd(conn, imapc_connection_proxyauth_login_cb, conn); imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN); imapc_command_sendf(cmd, "LOGIN %s %s", set->master_user, set->password); return; } if (sasl_mech == NULL && ((set->master_user == NULL && !need_literal(set->username) && !need_literal(set->password)) || (conn->capabilities & IMAPC_CAPABILITY_AUTH_PLAIN) == 0)) { /* We can use LOGIN command */ cmd = imapc_connection_cmd(conn, imapc_connection_login_cb, conn); imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN); imapc_command_sendf(cmd, "LOGIN %s %s", set->username, set->password); return; } i_zero(&sasl_set); if (set->master_user == NULL) sasl_set.authid = set->username; else { sasl_set.authid = set->master_user; sasl_set.authzid = set->username; } sasl_set.password = set->password; if (sasl_mech == NULL) sasl_mech = &dsasl_client_mech_plain; conn->sasl_client = dsasl_client_new(sasl_mech, &sasl_set); cmd = imapc_connection_cmd(conn, imapc_connection_authenticate_cb, conn); cmd->authenticate = TRUE; imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN); if ((conn->capabilities & IMAPC_CAPABILITY_SASL_IR) != 0) { const unsigned char *sasl_output; size_t sasl_output_len; string_t *sasl_output_base64; const char *error; if (dsasl_client_output(conn->sasl_client, &sasl_output, &sasl_output_len, &error) < 0) { i_error("imapc(%s): Failed to create initial SASL reply: %s", conn->name, error); imapc_connection_disconnect(conn); return; } sasl_output_base64 = t_str_new(MAX_BASE64_ENCODED_SIZE(sasl_output_len)); base64_encode(sasl_output, sasl_output_len, sasl_output_base64); imapc_command_sendf(cmd, "AUTHENTICATE %1s %1s", dsasl_client_mech_get_name(sasl_mech), str_c(sasl_output_base64)); } else { imapc_command_sendf(cmd, "AUTHENTICATE %1s", dsasl_client_mech_get_name(sasl_mech)); } } static void imapc_connection_starttls_cb(const struct imapc_command_reply *reply, void *context) { struct imapc_connection *conn = context; struct imapc_command *cmd; if (reply->state != IMAPC_COMMAND_STATE_OK) { imapc_connection_input_error(conn, "STARTTLS failed: %s", reply->text_full); return; } if (imapc_connection_ssl_init(conn) < 0) imapc_connection_disconnect(conn); else { /* get updated capabilities */ cmd = imapc_connection_cmd(conn, imapc_connection_capability_cb, conn); imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN); imapc_command_send(cmd, "CAPABILITY"); } } static void imapc_connection_id_callback(const struct imapc_command_reply *reply ATTR_UNUSED, void *context ATTR_UNUSED) { } static void imapc_connection_send_id(struct imapc_connection *conn) { static unsigned int global_id_counter = 0; struct imapc_command *cmd; if ((conn->capabilities & IMAPC_CAPABILITY_ID) == 0 || conn->client->set.session_id_prefix == NULL) return; cmd = imapc_connection_cmd(conn, imapc_connection_id_callback, conn); imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN); imapc_command_send(cmd, t_strdup_printf( "ID (\"name\" \"Dovecot\" \"x-session-ext-id\" \"%s-%u\")", conn->client->set.session_id_prefix, ++global_id_counter)); } static void imapc_connection_starttls(struct imapc_connection *conn) { struct imapc_command *cmd; if (conn->client->set.ssl_mode == IMAPC_CLIENT_SSL_MODE_STARTTLS && conn->ssl_iostream == NULL) { if ((conn->capabilities & IMAPC_CAPABILITY_STARTTLS) == 0) { i_error("imapc(%s): Requested STARTTLS, " "but server doesn't support it", conn->name); imapc_connection_disconnect(conn); return; } cmd = imapc_connection_cmd(conn, imapc_connection_starttls_cb, conn); imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN); imapc_command_send(cmd, "STARTTLS"); return; } imapc_connection_send_id(conn); imapc_connection_authenticate(conn); } static void imapc_connection_capability_cb(const struct imapc_command_reply *reply, void *context) { struct imapc_connection *conn = context; if (reply->state != IMAPC_COMMAND_STATE_OK) { imapc_connection_input_error(conn, "Failed to get capabilities: %s", reply->text_full); } else if (conn->capabilities == 0) { imapc_connection_input_error(conn, "Capabilities not returned by server"); } else { imapc_connection_starttls(conn); } } static int imapc_connection_input_banner(struct imapc_connection *conn) { const struct imap_arg *imap_args; const char *key, *value; struct imapc_command *cmd; int ret; if ((ret = imapc_connection_read_line(conn, &imap_args)) <= 0) return ret; /* we already verified that the banner beigns with OK */ i_assert(imap_arg_atom_equals(imap_args, "OK")); imap_args++; if (imapc_connection_handle_imap_resp_text(conn, imap_args, &key, &value) < 0) return -1; imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_AUTHENTICATING); if (conn->capabilities == 0) { /* capabilities weren't sent in the banner. ask for them. */ cmd = imapc_connection_cmd(conn, imapc_connection_capability_cb, conn); imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN); imapc_command_send(cmd, "CAPABILITY"); } else { imapc_connection_starttls(conn); } conn->input_callback = NULL; imapc_connection_input_reset(conn); return 1; } static int imapc_connection_input_untagged(struct imapc_connection *conn) { const struct imap_arg *imap_args; const unsigned char *data; size_t size; const char *name, *value; struct imap_parser *parser; struct imapc_untagged_reply reply; int ret; if (conn->state == IMAPC_CONNECTION_STATE_CONNECTING) { /* input banner */ data = i_stream_get_data(conn->input, &size); if (size < 3 && memchr(data, '\n', size) == NULL) return 0; if (i_memcasecmp(data, "OK ", 3) != 0) { imapc_connection_input_error(conn, "Banner doesn't begin with OK: %s", t_strcut(t_strndup(data, size), '\n')); return -1; } conn->input_callback = imapc_connection_input_banner; return 1; } if ((ret = imapc_connection_read_line(conn, &imap_args)) == 0) return 0; else if (ret < 0) { imapc_connection_input_reset(conn); return 1; } if (!imap_arg_get_atom(&imap_args[0], &name)) { imapc_connection_input_error(conn, "Invalid untagged reply"); return -1; } imap_args++; if (conn->input_state == IMAPC_INPUT_STATE_UNTAGGED && str_to_uint32(name, &conn->cur_num) == 0) { /* */ conn->input_state = IMAPC_INPUT_STATE_UNTAGGED_NUM; if (!imap_arg_get_atom(&imap_args[0], &name)) { imapc_connection_input_error(conn, "Invalid untagged reply"); return -1; } imap_args++; } i_zero(&reply); if (strcasecmp(name, "OK") == 0) { if (imapc_connection_handle_imap_resp_text(conn, imap_args, &reply.resp_text_key, &reply.resp_text_value) < 0) return -1; } else if (strcasecmp(name, "CAPABILITY") == 0) { value = imap_args_to_str(imap_args); if (imapc_connection_parse_capability(conn, value) < 0) return -1; } else if (strcasecmp(name, "BYE") == 0) { i_free(conn->disconnect_reason); conn->disconnect_reason = i_strdup(imap_args_to_str(imap_args)); } reply.name = name; reply.num = conn->cur_num; reply.args = imap_args; reply.file_args = array_get(&conn->literal_files, &reply.file_args_count); if (conn->selected_box != NULL) { reply.untagged_box_context = conn->selected_box->untagged_box_context; } /* the callback may disconnect and destroy the parser */ parser = conn->parser; imap_parser_ref(parser); conn->client->untagged_callback(&reply, conn->client->untagged_context); imap_parser_unref(&parser); imapc_connection_input_reset(conn); return 1; } static int imapc_connection_input_plus(struct imapc_connection *conn) { struct imapc_command *const *cmds; unsigned int cmds_count; const char *line; if ((line = i_stream_next_line(conn->input)) == NULL) return 0; cmds = array_get(&conn->cmd_send_queue, &cmds_count); if (conn->idle_plus_waiting) { /* "+ idling" reply for IDLE command */ conn->idle_plus_waiting = FALSE; conn->idling = TRUE; /* no timing out while IDLEing */ if (conn->to != NULL && !conn->idle_stopping) timeout_remove(&conn->to); } else if (cmds_count > 0 && cmds[0]->wait_for_literal) { /* reply for literal */ cmds[0]->wait_for_literal = FALSE; imapc_command_send_more(conn); } else { cmds = array_get(&conn->cmd_wait_list, &cmds_count); if (cmds_count > 0 && cmds[0]->authenticate) { /* continue AUTHENTICATE */ struct imapc_command_reply reply; i_zero(&reply); reply.state = (enum imapc_command_state)IMAPC_COMMAND_STATE_AUTHENTICATE_CONTINUE; reply.text_full = line; cmds[0]->callback(&reply, cmds[0]->context); } else { imapc_connection_input_error(conn, "Unexpected '+': %s", line); return -1; } } imapc_connection_input_reset(conn); return 1; } static void imapc_connection_throttle_shrink_timeout(struct imapc_connection *conn) { if (conn->throttle_msecs <= 1) conn->throttle_msecs = 0; else conn->throttle_msecs = conn->throttle_msecs*3 / 4; if (conn->throttle_shrink_msecs <= conn->client->set.throttle_set.shrink_min_msecs) conn->throttle_shrink_msecs = 0; else conn->throttle_shrink_msecs = conn->throttle_shrink_msecs*3 / 4; timeout_remove(&conn->to_throttle_shrink); if (conn->throttle_shrink_msecs > 0) { conn->to_throttle_shrink = timeout_add(conn->throttle_shrink_msecs, imapc_connection_throttle_shrink_timeout, conn); } } static void imapc_connection_throttle(struct imapc_connection *conn, const struct imapc_command_reply *reply) { timeout_remove(&conn->to_throttle); /* If GMail returns [THROTTLED], start slowing down commands. Unfortunately this isn't a nice resp-text-code, but just appended at the end of the line (although we kind of support it as resp-text-code also in here if it's uppercased). */ if (strstr(reply->text_full, "[THROTTLED]") != NULL) { if (conn->throttle_msecs == 0) conn->throttle_msecs = conn->client->set.throttle_set.init_msecs; else if (conn->throttle_msecs < conn->last_successful_throttle_msecs) conn->throttle_msecs = conn->last_successful_throttle_msecs; else { conn->throttle_msecs *= 2; if (conn->throttle_msecs > conn->client->set.throttle_set.max_msecs) conn->throttle_msecs = conn->client->set.throttle_set.max_msecs; } if (conn->throttle_shrink_msecs == 0) conn->throttle_shrink_msecs = conn->client->set.throttle_set.shrink_min_msecs; else conn->throttle_shrink_msecs *= 2; if (conn->to_throttle_shrink != NULL) timeout_reset(conn->to_throttle_shrink); } else { if (conn->throttle_shrink_msecs > 0 && conn->to_throttle_shrink == NULL) { conn->to_throttle_shrink = timeout_add(conn->throttle_shrink_msecs, imapc_connection_throttle_shrink_timeout, conn); } conn->last_successful_throttle_msecs = conn->throttle_msecs; } if (conn->throttle_msecs > 0) { conn->throttle_end_timeval = ioloop_timeval; timeval_add_msecs(&conn->throttle_end_timeval, conn->throttle_msecs); conn->throttle_pending = TRUE; } } static void imapc_command_reply_free(struct imapc_command *cmd, const struct imapc_command_reply *reply) { cmd->callback(reply, cmd->context); imapc_command_free(cmd); } static int imapc_connection_input_tagged(struct imapc_connection *conn) { struct imapc_command *const *cmds, *cmd = NULL; unsigned int i, count; char *line, *linep; const char *p; struct imapc_command_reply reply; line = i_stream_next_line(conn->input); if (line == NULL) return 0; /* make sure reply texts stays valid if input stream gets freed */ line = t_strdup_noconst(line); i_zero(&reply); linep = strchr(line, ' '); if (linep == NULL) reply.text_full = ""; else { *linep = '\0'; reply.text_full = linep + 1; } if (strcasecmp(line, "ok") == 0) reply.state = IMAPC_COMMAND_STATE_OK; else if (strcasecmp(line, "no") == 0) reply.state = IMAPC_COMMAND_STATE_NO; else if (strcasecmp(line, "bad") == 0) reply.state = IMAPC_COMMAND_STATE_BAD; else { imapc_connection_input_error(conn, "Invalid state in tagged reply: %u %s %s", conn->cur_tag, line, reply.text_full); return -1; } if (reply.text_full[0] == '[') { /* get resp-text */ if (imapc_connection_handle_resp_text(conn, reply.text_full, &reply.resp_text_key, &reply.resp_text_value) < 0) return -1; p = i_strchr_to_next(reply.text_full, ']'); i_assert(p != NULL); reply.text_without_resp = p; if (reply.text_without_resp[0] == ' ') reply.text_without_resp++; } else { reply.text_without_resp = reply.text_full; } /* if we've pipelined multiple commands, handle [THROTTLED] reply from only one of them */ if (!conn->throttle_pending) imapc_connection_throttle(conn, &reply); /* find the command. it's either the first command in send queue (literal failed) or somewhere in wait list. */ cmds = array_get(&conn->cmd_send_queue, &count); if (count > 0 && cmds[0]->tag == conn->cur_tag) { cmd = cmds[0]; array_pop_front(&conn->cmd_send_queue); } else { cmds = array_get(&conn->cmd_wait_list, &count); for (i = 0; i < count; i++) { if (cmds[i]->tag == conn->cur_tag) { cmd = cmds[i]; array_delete(&conn->cmd_wait_list, i, 1); break; } } } if (array_count(&conn->cmd_wait_list) == 0 && array_count(&conn->cmd_send_queue) == 0 && conn->state == IMAPC_CONNECTION_STATE_DONE && conn->to != NULL) timeout_remove(&conn->to); if (cmd == NULL) { if (seq_range_exists(&conn->aborted_cmd_tags, conn->cur_tag)) { /* sent command was already aborted - ignore it */ seq_range_array_remove(&conn->aborted_cmd_tags, conn->cur_tag); imapc_connection_input_reset(conn); return 1; } imapc_connection_input_error(conn, "Unknown tag in a reply: %u %s %s", conn->cur_tag, line, reply.text_full); return -1; } if ((cmd->flags & IMAPC_COMMAND_FLAG_SELECT) != 0) conn->select_waiting_reply = FALSE; if (reply.state == IMAPC_COMMAND_STATE_BAD) { i_error("imapc(%s): Command '%s' failed with BAD: %u %s", conn->name, imapc_command_get_readable(cmd), conn->cur_tag, reply.text_full); imapc_connection_disconnect(conn); } if (reply.state == IMAPC_COMMAND_STATE_NO && (cmd->flags & IMAPC_COMMAND_FLAG_SELECT) != 0 && conn->selected_box != NULL) { /* EXAMINE/SELECT failed: mailbox is no longer selected */ imapc_connection_unselect(conn->selected_box, TRUE); } if (conn->reconnect_command_count > 0 && (cmd->flags & IMAPC_COMMAND_FLAG_RECONNECTED) != 0) { i_assert(conn->reconnect_command_count > 0); if (--conn->reconnect_command_count == 0) { /* we've received replies for all the commands started before reconnection. if we get disconnected now, we can safely reconnect without worrying about infinite reconnect loops. */ if (conn->selected_box != NULL) conn->selected_box->reconnect_ok = TRUE; } } if (conn->reconnect_command_count == 0) { /* we've successfully received replies to some commands. */ conn->reconnect_ok = TRUE; } imapc_connection_input_reset(conn); imapc_command_reply_free(cmd, &reply); imapc_command_send_more(conn); return 1; } static int imapc_connection_input_one(struct imapc_connection *conn) { const char *tag; int ret = -1; if (conn->input_callback != NULL) return conn->input_callback(conn); switch (conn->input_state) { case IMAPC_INPUT_STATE_NONE: tag = imap_parser_read_word(conn->parser); if (tag == NULL) return 0; if (strcmp(tag, "*") == 0) { conn->input_state = IMAPC_INPUT_STATE_UNTAGGED; conn->cur_num = 0; ret = imapc_connection_input_untagged(conn); } else if (strcmp(tag, "+") == 0) { conn->input_state = IMAPC_INPUT_STATE_PLUS; ret = imapc_connection_input_plus(conn); } else { conn->input_state = IMAPC_INPUT_STATE_TAGGED; if (str_to_uint(tag, &conn->cur_tag) < 0 || conn->cur_tag == 0) { imapc_connection_input_error(conn, "Invalid command tag: %s", tag); ret = -1; } else { ret = imapc_connection_input_tagged(conn); } } break; case IMAPC_INPUT_STATE_PLUS: ret = imapc_connection_input_plus(conn); break; case IMAPC_INPUT_STATE_UNTAGGED: case IMAPC_INPUT_STATE_UNTAGGED_NUM: ret = imapc_connection_input_untagged(conn); break; case IMAPC_INPUT_STATE_TAGGED: ret = imapc_connection_input_tagged(conn); break; } return ret; } static void imapc_connection_input(struct imapc_connection *conn) { const char *errstr; string_t *str; ssize_t ret = 0; /* we need to read as much as we can with SSL streams to avoid hanging */ imapc_connection_ref(conn); while (conn->input != NULL && (ret = i_stream_read(conn->input)) > 0) imapc_connection_input_pending(conn); if (ret < 0 && conn->client->logging_out && conn->disconnect_reason != NULL) { /* expected disconnection */ imapc_connection_disconnect(conn); } else if (ret < 0) { /* disconnected or buffer full */ str = t_str_new(128); if (conn->disconnect_reason != NULL) { str_printfa(str, "Server disconnected with message: %s", conn->disconnect_reason); } else if (ret == -2) { str_printfa(str, "Server sent too large input " "(buffer full at %zu)", i_stream_get_data_size(conn->input)); } else if (conn->ssl_iostream == NULL) { errstr = conn->input->stream_errno == 0 ? "EOF" : i_stream_get_error(conn->input); str_printfa(str, "Server disconnected unexpectedly: %s", errstr); } else { errstr = ssl_iostream_get_last_error(conn->ssl_iostream); if (errstr == NULL) { errstr = conn->input->stream_errno == 0 ? "EOF" : i_stream_get_error(conn->input); } str_printfa(str, "Server disconnected unexpectedly: %s", errstr); } imapc_connection_try_reconnect(conn, str_c(str), 0, FALSE); } imapc_connection_unref(&conn); } static int imapc_connection_ssl_handshaked(const char **error_r, void *context) { struct imapc_connection *conn = context; const char *error; if (ssl_iostream_check_cert_validity(conn->ssl_iostream, conn->client->set.host, &error) == 0) { if (conn->client->set.debug) { i_debug("imapc(%s): SSL handshake successful", conn->name); } return 0; } else if (conn->client->set.ssl_set.allow_invalid_cert) { if (conn->client->set.debug) { i_debug("imapc(%s): SSL handshake successful, " "ignoring invalid certificate: %s", conn->name, error); } return 0; } else { *error_r = error; return -1; } } static int imapc_connection_ssl_init(struct imapc_connection *conn) { const char *error; if (conn->client->ssl_ctx == NULL) { i_error("imapc(%s): No SSL context", conn->name); return -1; } if (conn->client->set.debug) i_debug("imapc(%s): Starting SSL handshake", conn->name); if (conn->raw_input != conn->input) { /* recreate rawlog after STARTTLS */ i_stream_ref(conn->raw_input); o_stream_ref(conn->raw_output); i_stream_destroy(&conn->input); o_stream_destroy(&conn->output); conn->input = conn->raw_input; conn->output = conn->raw_output; } io_remove(&conn->io); if (io_stream_create_ssl_client(conn->client->ssl_ctx, conn->client->set.host, &conn->client->set.ssl_set, &conn->input, &conn->output, &conn->ssl_iostream, &error) < 0) { i_error("imapc(%s): Couldn't initialize SSL client: %s", conn->name, error); return -1; } conn->io = io_add_istream(conn->input, imapc_connection_input, conn); ssl_iostream_set_handshake_callback(conn->ssl_iostream, imapc_connection_ssl_handshaked, conn); if (ssl_iostream_handshake(conn->ssl_iostream) < 0) { i_error("imapc(%s): SSL handshake failed: %s", conn->name, ssl_iostream_get_last_error(conn->ssl_iostream)); return -1; } if (*conn->client->set.rawlog_dir != '\0') { iostream_rawlog_create(conn->client->set.rawlog_dir, &conn->input, &conn->output); } imap_parser_set_streams(conn->parser, conn->input, NULL); return 0; } static int imapc_connection_connected(struct imapc_connection *conn) { const struct ip_addr *ip = &conn->ips[conn->prev_connect_idx]; struct ip_addr local_ip; in_port_t local_port; int err; i_assert(conn->io == NULL); err = net_geterror(conn->fd); if (err != 0) { imapc_connection_try_reconnect(conn, t_strdup_printf( "connect(%s, %u) failed: %s", net_ip2addr(ip), conn->client->set.port, strerror(err)), conn->client->set.connect_retry_interval_msecs, TRUE); return -1; } if (net_getsockname(conn->fd, &local_ip, &local_port) < 0) local_port = 0; i_info("imapc(%s): Connected to %s:%u (local %s:%u)", conn->name, net_ip2addr(ip), conn->client->set.port, net_ip2addr(&local_ip), local_port); conn->io = io_add(conn->fd, IO_READ, imapc_connection_input, conn); o_stream_set_flush_callback(conn->output, imapc_connection_output, conn); if (conn->client->set.ssl_mode == IMAPC_CLIENT_SSL_MODE_IMMEDIATE) { if (imapc_connection_ssl_init(conn) < 0) imapc_connection_disconnect(conn); } return imapc_connection_output(conn); } static void imapc_connection_timeout(struct imapc_connection *conn) { const struct ip_addr *ip = &conn->ips[conn->prev_connect_idx]; const char *errstr; bool connect_error = FALSE; switch (conn->state) { case IMAPC_CONNECTION_STATE_CONNECTING: errstr = t_strdup_printf("connect(%s, %u) timed out after %u seconds", net_ip2addr(ip), conn->client->set.port, conn->client->set.connect_timeout_msecs/1000); connect_error = TRUE; break; case IMAPC_CONNECTION_STATE_AUTHENTICATING: errstr = t_strdup_printf("Authentication timed out after %u seconds", conn->client->set.connect_timeout_msecs/1000); break; default: i_unreached(); } imapc_connection_try_reconnect(conn, errstr, 0, connect_error); } static void imapc_noop_callback(const struct imapc_command_reply *reply ATTR_UNUSED, void *context ATTR_UNUSED) { } static void imapc_reidle_callback(const struct imapc_command_reply *reply ATTR_UNUSED, void *context) { struct imapc_connection *conn = context; imapc_connection_idle(conn); } static void imapc_connection_reset_idle(struct imapc_connection *conn) { struct imapc_command *cmd; if (conn->idling) cmd = imapc_connection_cmd(conn, imapc_reidle_callback, conn); else if (array_count(&conn->cmd_wait_list) == 0) cmd = imapc_connection_cmd(conn, imapc_noop_callback, NULL); else { /* IMAP command reply is taking a long time */ return; } imapc_command_send(cmd, "NOOP"); } static void imapc_connection_connect_next_ip(struct imapc_connection *conn) { const struct ip_addr *ip = NULL; unsigned int i; int fd; i_assert(conn->client->set.max_idle_time > 0); for (i = 0; iips_count;) { conn->prev_connect_idx = (conn->prev_connect_idx+1) % conn->ips_count; ip = &conn->ips[conn->prev_connect_idx]; fd = net_connect_ip(ip, conn->client->set.port, NULL); if (fd != -1) break; /* failed to connect to one of the IPs immediately (e.g. IPv6 address without connectivity). try all IPs before failing completely. */ i_error("net_connect_ip(%s:%u) failed: %m", net_ip2addr(ip), conn->client->set.port); if (conn->prev_connect_idx+1 == conn->ips_count) { imapc_connection_try_reconnect(conn, "No more IP address(es) to try", conn->client->set.connect_retry_interval_msecs, TRUE); return; } } i_assert(ip != NULL); conn->fd = fd; conn->input = conn->raw_input = i_stream_create_fd(fd, conn->client->set.max_line_length); conn->output = conn->raw_output = o_stream_create_fd(fd, SIZE_MAX); o_stream_set_no_error_handling(conn->output, TRUE); if (*conn->client->set.rawlog_dir != '\0' && conn->client->set.ssl_mode != IMAPC_CLIENT_SSL_MODE_IMMEDIATE) { iostream_rawlog_create(conn->client->set.rawlog_dir, &conn->input, &conn->output); } o_stream_set_flush_pending(conn->output, TRUE); o_stream_set_flush_callback(conn->output, imapc_connection_connected, conn); conn->parser = imap_parser_create(conn->input, NULL, conn->client->set.max_line_length); conn->to = timeout_add(conn->client->set.connect_timeout_msecs, imapc_connection_timeout, conn); conn->to_output = timeout_add(conn->client->set.max_idle_time*1000, imapc_connection_reset_idle, conn); if (conn->client->set.debug) { i_debug("imapc(%s): Connecting to %s:%u", conn->name, net_ip2addr(ip), conn->client->set.port); } } static void imapc_connection_dns_callback(const struct dns_lookup_result *result, struct imapc_connection *conn) { conn->dns_lookup = NULL; if (result->ret != 0) { i_error("imapc(%s): dns_lookup(%s) failed: %s", conn->name, conn->client->set.host, result->error); imapc_connection_set_disconnected(conn); return; } i_assert(result->ips_count > 0); conn->ips_count = result->ips_count; conn->ips = i_new(struct ip_addr, conn->ips_count); memcpy(conn->ips, result->ips, sizeof(*conn->ips) * conn->ips_count); conn->prev_connect_idx = conn->ips_count - 1; imapc_connection_connect_next_ip(conn); } void imapc_connection_connect(struct imapc_connection *conn) { struct dns_lookup_settings dns_set; struct ip_addr ip, *ips; unsigned int ips_count; int ret; if (conn->fd != -1 || conn->dns_lookup != NULL) return; if (conn->reconnect_waiting) { /* wait for the reconnection delay to finish before doing anything. */ return; } conn->reconnecting = FALSE; /* if we get disconnected before we've finished all the pending commands, don't reconnect */ conn->reconnect_command_count = array_count(&conn->cmd_wait_list) + array_count(&conn->cmd_send_queue); imapc_connection_input_reset(conn); conn->last_connect = ioloop_timeval; if (conn->client->set.debug) { i_debug("imapc(%s): Looking up IP address " "(reconnect_ok=%s, last_connect=%ld)", conn->name, (conn->reconnect_ok ? "true" : "false"), (long)conn->last_connect.tv_sec); } i_zero(&dns_set); dns_set.dns_client_socket_path = conn->client->set.dns_client_socket_path; dns_set.timeout_msecs = conn->client->set.connect_timeout_msecs; dns_set.event_parent = conn->client->event; imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_CONNECTING); if (conn->ips_count > 0) { /* do nothing */ } else if (net_addr2ip(conn->client->set.host, &ip) == 0) { conn->ips_count = 1; conn->ips = i_new(struct ip_addr, conn->ips_count); conn->ips[0] = ip; } else if (*dns_set.dns_client_socket_path == '\0') { ret = net_gethostbyname(conn->client->set.host, &ips, &ips_count); if (ret != 0) { i_error("imapc(%s): net_gethostbyname(%s) failed: %s", conn->name, conn->client->set.host, net_gethosterror(ret)); imapc_connection_set_disconnected(conn); return; } conn->ips_count = ips_count; conn->ips = i_new(struct ip_addr, ips_count); memcpy(conn->ips, ips, ips_count * sizeof(*ips)); } else { (void)dns_lookup(conn->client->set.host, &dns_set, imapc_connection_dns_callback, conn, &conn->dns_lookup); return; } imapc_connection_connect_next_ip(conn); } void imapc_connection_input_pending(struct imapc_connection *conn) { int ret = 1; if (conn->input == NULL) return; if (conn->to != NULL && !conn->idle_stopping) timeout_reset(conn->to); o_stream_cork(conn->output); while (ret > 0 && conn->input != NULL) { T_BEGIN { ret = imapc_connection_input_one(conn); } T_END; } if (conn->output != NULL) o_stream_uncork(conn->output); } static struct imapc_command * imapc_command_begin(imapc_command_callback_t *callback, void *context) { struct imapc_command *cmd; pool_t pool; i_assert(callback != NULL); pool = pool_alloconly_create("imapc command", 2048); cmd = p_new(pool, struct imapc_command, 1); cmd->pool = pool; cmd->callback = callback; cmd->context = context; /* use a globally unique tag counter so looking at rawlogs is somewhat easier */ if (++imapc_client_cmd_tag_counter == 0) imapc_client_cmd_tag_counter++; cmd->tag = imapc_client_cmd_tag_counter; return cmd; } static void imapc_command_free(struct imapc_command *cmd) { struct imapc_command_stream *stream; if (array_is_created(&cmd->streams)) { array_foreach_modifiable(&cmd->streams, stream) i_stream_unref(&stream->input); } pool_unref(&cmd->pool); } const char *imapc_command_get_tag(struct imapc_command *cmd) { return t_strdup_printf("%u", cmd->tag); } void imapc_command_abort(struct imapc_command **_cmd) { struct imapc_command *cmd = *_cmd; *_cmd = NULL; imapc_command_free(cmd); } static void imapc_command_timeout(struct imapc_connection *conn) { struct imapc_command *const *cmds; unsigned int count; cmds = array_get(&conn->cmd_wait_list, &count); i_assert(count > 0); imapc_connection_try_reconnect(conn, t_strdup_printf( "Command '%s' timed out", imapc_command_get_readable(cmds[0])), 0, FALSE); } static bool parse_sync_literal(const unsigned char *data, unsigned int pos, unsigned int *value_r) { unsigned int value = 0, mul = 1; /* data should contain "{size}\r\n" and pos points after \n */ if (pos <= 4 || data[pos-1] != '\n' || data[pos-2] != '\r' || data[pos-3] != '}' || !i_isdigit(data[pos-4])) return FALSE; pos -= 4; do { value += (data[pos] - '0') * mul; mul = mul*10; pos--; } while (pos > 0 && i_isdigit(data[pos])); if (pos == 0 || data[pos] != '{') return FALSE; *value_r = value; return TRUE; } static void imapc_command_send_finished(struct imapc_connection *conn, struct imapc_command *cmd) { struct imapc_command *const *cmdp; i_assert(conn->to != NULL); if (cmd->idle) conn->idle_plus_waiting = TRUE; cmd->sent = TRUE; /* everything sent. move command to wait list. */ cmdp = array_front(&conn->cmd_send_queue); i_assert(*cmdp == cmd); array_pop_front(&conn->cmd_send_queue); array_push_back(&conn->cmd_wait_list, &cmd); /* send the next command in queue */ imapc_command_send_more(conn); } static struct imapc_command_stream * imapc_command_get_sending_stream(struct imapc_command *cmd) { struct imapc_command_stream *stream; if (!array_is_created(&cmd->streams) || array_count(&cmd->streams) == 0) return NULL; stream = array_front_modifiable(&cmd->streams); if (stream->pos != cmd->send_pos) return NULL; return stream; } static int imapc_command_try_send_stream(struct imapc_connection *conn, struct imapc_command *cmd) { struct imapc_command_stream *stream; enum ostream_send_istream_result res; stream = imapc_command_get_sending_stream(cmd); if (stream == NULL) return -2; /* we're sending the stream now */ o_stream_set_max_buffer_size(conn->output, 0); res = o_stream_send_istream(conn->output, stream->input); o_stream_set_max_buffer_size(conn->output, SIZE_MAX); switch (res) { case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: break; case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: i_unreached(); case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: i_assert(stream->input->v_offset < stream->size); return 0; case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: i_error("imapc: read(%s) failed: %s", i_stream_get_name(stream->input), i_stream_get_error(stream->input)); return -1; case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: /* disconnected */ return -1; } i_assert(stream->input->v_offset == stream->size); /* finished with the stream */ i_stream_unref(&stream->input); array_pop_front(&cmd->streams); i_assert(cmd->send_pos != cmd->data->used); return 1; } static void imapc_connection_set_selecting(struct imapc_client_mailbox *box) { struct imapc_connection *conn = box->conn; i_assert(conn->qresync_selecting_box == NULL); if (conn->selected_on_server && (conn->capabilities & IMAPC_CAPABILITY_QRESYNC) != 0) { /* server will send a [CLOSED] once selected mailbox is closed */ conn->qresync_selecting_box = box; } else { /* we'll have to assume that all the future untagged messages are for the mailbox we're selecting */ conn->selected_box = box; conn->selected_on_server = TRUE; } conn->select_waiting_reply = TRUE; } static bool imapc_connection_is_throttled(struct imapc_connection *conn) { timeout_remove(&conn->to_throttle); if (conn->throttle_msecs == 0) { /* we haven't received [THROTTLED] recently */ return FALSE; } if (array_count(&conn->cmd_wait_list) > 0) { /* wait until we have received the existing commands' tagged replies to see if we're still throttled */ return TRUE; } if (timeval_cmp(&ioloop_timeval, &conn->throttle_end_timeval) >= 0) { /* we reached the throttle timeout - send the next command */ conn->throttle_pending = FALSE; return FALSE; } /* we're still being throttled - wait for it to end */ conn->to_throttle = timeout_add_absolute(&conn->throttle_end_timeval, imapc_command_send_more, conn); return TRUE; } static void imapc_command_send_more(struct imapc_connection *conn) { struct imapc_command *const *cmds, *cmd; struct imapc_command_reply reply; const unsigned char *p, *data; unsigned int count, size; size_t seek_pos, start_pos, end_pos; int ret; if (imapc_connection_is_throttled(conn)) return; cmds = array_get(&conn->cmd_send_queue, &count); if (count == 0) return; cmd = cmds[0]; if ((cmd->flags & IMAPC_COMMAND_FLAG_PRELOGIN) == 0 && conn->state != IMAPC_CONNECTION_STATE_DONE) { /* wait until we're fully connected */ return; } if ((cmd->flags & IMAPC_COMMAND_FLAG_LOGOUT) != 0 && array_count(&conn->cmd_wait_list) > 0) { /* wait until existing commands have finished */ return; } if (conn->select_waiting_reply) { /* wait for SELECT to finish */ return; } if (cmd->wait_for_literal) { /* wait until we received '+' */ return; } i_assert(cmd->send_pos < cmd->data->used); if (cmd->box == NULL) { /* non-mailbox command */ } else if (cmd->send_pos == 0 && (cmd->flags & IMAPC_COMMAND_FLAG_SELECT) != 0) { /* SELECT/EXAMINE command */ imapc_connection_set_selecting(cmd->box); } else if (!imapc_client_mailbox_is_opened(cmd->box)) { if (cmd->box->reconnecting) { /* wait for SELECT/EXAMINE */ return; } /* shouldn't normally happen */ i_zero(&reply); reply.text_without_resp = reply.text_full = "Mailbox not open"; reply.state = IMAPC_COMMAND_STATE_DISCONNECTED; array_pop_front(&conn->cmd_send_queue); imapc_command_reply_free(cmd, &reply); imapc_command_send_more(conn); return; } /* add timeout for commands if there's not one yet (pre-login has its own timeout) */ if ((cmd->flags & IMAPC_COMMAND_FLAG_LOGOUT) != 0) { /* LOGOUT has a shorter timeout */ timeout_remove(&conn->to); conn->to = timeout_add(IMAPC_LOGOUT_TIMEOUT_MSECS, imapc_command_timeout, conn); } else if (conn->to == NULL) { conn->to = timeout_add(conn->client->set.cmd_timeout_msecs, imapc_command_timeout, conn); } timeout_reset(conn->to_output); if ((ret = imapc_command_try_send_stream(conn, cmd)) == 0) return; if (ret == -1) { i_zero(&reply); reply.text_without_resp = reply.text_full = "Mailbox not open"; reply.state = IMAPC_COMMAND_STATE_DISCONNECTED; array_pop_front(&conn->cmd_send_queue); imapc_command_reply_free(cmd, &reply); imapc_command_send_more(conn); return; } seek_pos = cmd->send_pos; if (seek_pos != 0 && ret == -2) { /* skip over the literal. we can also get here from AUTHENTICATE command, which doesn't use a literal */ if (parse_sync_literal(cmd->data->data, seek_pos, &size)) { seek_pos += size; i_assert(seek_pos <= cmd->data->used); } } do { start_pos = seek_pos; p = memchr(CONST_PTR_OFFSET(cmd->data->data, seek_pos), '\n', cmd->data->used - seek_pos); i_assert(p != NULL); seek_pos = p - (const unsigned char *)cmd->data->data + 1; /* keep going for LITERAL+ command */ } while (start_pos + 3 < seek_pos && p[-1] == '\r' && p[-2] == '}' && p[-3] == '+'); end_pos = seek_pos; data = CONST_PTR_OFFSET(cmd->data->data, cmd->send_pos); size = end_pos - cmd->send_pos; o_stream_nsend(conn->output, data, size); cmd->send_pos = end_pos; if (cmd->send_pos == cmd->data->used) { i_assert(!array_is_created(&cmd->streams) || array_count(&cmd->streams) == 0); imapc_command_send_finished(conn, cmd); } else { cmd->wait_for_literal = TRUE; } } static void imapc_connection_send_idle_done(struct imapc_connection *conn) { if ((conn->idling || conn->idle_plus_waiting) && !conn->idle_stopping) { conn->idle_stopping = TRUE; o_stream_nsend_str(conn->output, "DONE\r\n"); if (conn->to == NULL) { conn->to = timeout_add(conn->client->set.cmd_timeout_msecs, imapc_command_timeout, conn); } } } static void imapc_connection_cmd_send(struct imapc_command *cmd) { struct imapc_connection *conn = cmd->conn; struct imapc_command *const *cmds; unsigned int i, count; imapc_connection_send_idle_done(conn); i_assert((cmd->flags & IMAPC_COMMAND_FLAG_RECONNECTED) == 0); if ((cmd->flags & IMAPC_COMMAND_FLAG_PRELOGIN) != 0 && conn->state == IMAPC_CONNECTION_STATE_AUTHENTICATING) { /* pre-login commands get inserted before everything else */ array_push_front(&conn->cmd_send_queue, &cmd); imapc_command_send_more(conn); return; } /* add the command just before retried commands */ cmds = array_get(&conn->cmd_send_queue, &count); for (i = count; i > 0; i--) { if ((cmds[i-1]->flags & IMAPC_COMMAND_FLAG_RECONNECTED) == 0) break; } array_insert(&conn->cmd_send_queue, i, &cmd, 1); imapc_command_send_more(conn); } static int imapc_connection_output(struct imapc_connection *conn) { struct imapc_command *const *cmds; unsigned int count; int ret; if (conn->to != NULL) timeout_reset(conn->to); if ((ret = o_stream_flush(conn->output)) < 0) return 1; imapc_connection_ref(conn); cmds = array_get(&conn->cmd_send_queue, &count); if (count > 0) { if (imapc_command_get_sending_stream(cmds[0]) != NULL && !cmds[0]->wait_for_literal) { /* we're sending a stream. send more. */ imapc_command_send_more(conn); } } imapc_connection_unref(&conn); return ret; } struct imapc_command * imapc_connection_cmd(struct imapc_connection *conn, imapc_command_callback_t *callback, void *context) { struct imapc_command *cmd; cmd = imapc_command_begin(callback, context); cmd->conn = conn; return cmd; } void imapc_command_set_flags(struct imapc_command *cmd, enum imapc_command_flags flags) { cmd->flags = flags; } void imapc_command_set_mailbox(struct imapc_command *cmd, struct imapc_client_mailbox *box) { cmd->box = box; } bool imapc_command_connection_is_selected(struct imapc_command *cmd) { return cmd->conn->selected_box != NULL || cmd->conn->qresync_selecting_box != NULL; } void imapc_command_send(struct imapc_command *cmd, const char *cmd_str) { size_t len = strlen(cmd_str); cmd->data = str_new(cmd->pool, 6 + len + 2); str_printfa(cmd->data, "%u %s\r\n", cmd->tag, cmd_str); imapc_connection_cmd_send(cmd); } void imapc_command_sendf(struct imapc_command *cmd, const char *cmd_fmt, ...) { va_list args; va_start(args, cmd_fmt); imapc_command_sendvf(cmd, cmd_fmt, args); va_end(args); } void imapc_command_sendvf(struct imapc_command *cmd, const char *cmd_fmt, va_list args) { unsigned int i; cmd->data = str_new(cmd->pool, 128); str_printfa(cmd->data, "%u ", cmd->tag); for (i = 0; cmd_fmt[i] != '\0'; i++) { if (cmd_fmt[i] != '%') { str_append_c(cmd->data, cmd_fmt[i]); continue; } switch (cmd_fmt[++i]) { case '\0': i_unreached(); case 'u': { unsigned int arg = va_arg(args, unsigned int); str_printfa(cmd->data, "%u", arg); break; } case 'p': { struct istream *input = va_arg(args, struct istream *); struct imapc_command_stream *s; uoff_t size; if (!array_is_created(&cmd->streams)) p_array_init(&cmd->streams, cmd->pool, 2); if (i_stream_get_size(input, TRUE, &size) < 0) size = 0; str_printfa(cmd->data, "{%"PRIuUOFF_T"}\r\n", size); s = array_append_space(&cmd->streams); s->pos = str_len(cmd->data); s->size = size; s->input = input; i_stream_ref(input); break; } case 's': { const char *arg = va_arg(args, const char *); if (!need_literal(arg)) imap_append_quoted(cmd->data, arg); else if ((cmd->conn->capabilities & IMAPC_CAPABILITY_LITERALPLUS) != 0) { str_printfa(cmd->data, "{%zu+}\r\n%s", strlen(arg), arg); } else { str_printfa(cmd->data, "{%zu}\r\n%s", strlen(arg), arg); } break; } case '1': { /* %1s - no quoting */ const char *arg = va_arg(args, const char *); i++; i_assert(cmd_fmt[i] == 's'); str_append(cmd->data, arg); break; } } } str_append(cmd->data, "\r\n"); imapc_connection_cmd_send(cmd); } enum imapc_connection_state imapc_connection_get_state(struct imapc_connection *conn) { return conn->state; } enum imapc_capability imapc_connection_get_capabilities(struct imapc_connection *conn) { return conn->capabilities; } void imapc_connection_unselect(struct imapc_client_mailbox *box, bool via_tagged_reply) { struct imapc_connection *conn = box->conn; if (conn->select_waiting_reply) { /* Mailbox closing was requested before SELECT/EXAMINE replied. The connection state is now unknown and shouldn't be used anymore. */ imapc_connection_disconnect(conn); } else if (conn->qresync_selecting_box == NULL && conn->selected_box == NULL) { /* There is no mailbox selected currently. */ i_assert(!via_tagged_reply); } else { /* Mailbox was closed in a known state. Either due to SELECT/EXAMINE failing (via_tagged_reply) or by imapc-storage after the mailbox was already fully selected. */ i_assert(conn->qresync_selecting_box == box || conn->selected_box == box); conn->qresync_selecting_box = NULL; conn->selected_box = NULL; if (via_tagged_reply) conn->selected_on_server = FALSE; else { /* We didn't actually send UNSELECT command, so don't touch selected_on_server state. */ } } imapc_connection_send_idle_done(conn); imapc_connection_abort_commands(conn, box, FALSE); } struct imapc_client_mailbox * imapc_connection_get_mailbox(struct imapc_connection *conn) { if (conn->qresync_selecting_box != NULL) return conn->qresync_selecting_box; return conn->selected_box; } static void imapc_connection_idle_callback(const struct imapc_command_reply *reply ATTR_UNUSED, void *context) { struct imapc_connection *conn = context; conn->idling = FALSE; conn->idle_plus_waiting = FALSE; conn->idle_stopping = FALSE; } void imapc_connection_idle(struct imapc_connection *conn) { struct imapc_command *cmd; if (array_count(&conn->cmd_send_queue) != 0 || array_count(&conn->cmd_wait_list) != 0 || conn->idling || conn->idle_plus_waiting || (conn->capabilities & IMAPC_CAPABILITY_IDLE) == 0) return; cmd = imapc_connection_cmd(conn, imapc_connection_idle_callback, conn); cmd->idle = TRUE; imapc_command_send(cmd, "IDLE"); } dovecot-2.3.21.1/src/lib-imap-client/imapc-client.h0000644000000000000000000002060714656633576016575 00000000000000#ifndef IMAPC_CLIENT_H #define IMAPC_CLIENT_H #include "net.h" #include "iostream-ssl.h" /* IMAP RFC defines this to be at least 30 minutes. */ #define IMAPC_DEFAULT_MAX_IDLE_TIME (60*29) enum imapc_command_state { IMAPC_COMMAND_STATE_OK = 0, IMAPC_COMMAND_STATE_NO, IMAPC_COMMAND_STATE_BAD, /* Authentication to IMAP server failed (NO or BAD) */ IMAPC_COMMAND_STATE_AUTH_FAILED, /* Client was unexpectedly disconnected. */ IMAPC_COMMAND_STATE_DISCONNECTED }; extern const char *imapc_command_state_names[]; enum imapc_capability { IMAPC_CAPABILITY_SASL_IR = 0x01, IMAPC_CAPABILITY_LITERALPLUS = 0x02, IMAPC_CAPABILITY_QRESYNC = 0x04, IMAPC_CAPABILITY_IDLE = 0x08, IMAPC_CAPABILITY_UIDPLUS = 0x10, IMAPC_CAPABILITY_AUTH_PLAIN = 0x20, IMAPC_CAPABILITY_STARTTLS = 0x40, IMAPC_CAPABILITY_X_GM_EXT_1 = 0x80, IMAPC_CAPABILITY_CONDSTORE = 0x100, IMAPC_CAPABILITY_NAMESPACE = 0x200, IMAPC_CAPABILITY_UNSELECT = 0x400, IMAPC_CAPABILITY_ESEARCH = 0x800, IMAPC_CAPABILITY_WITHIN = 0x1000, IMAPC_CAPABILITY_QUOTA = 0x2000, IMAPC_CAPABILITY_ID = 0x4000, IMAPC_CAPABILITY_SAVEDATE = 0x8000, IMAPC_CAPABILITY_IMAP4REV1 = 0x40000000 }; struct imapc_capability_name { const char *name; enum imapc_capability capability; }; extern const struct imapc_capability_name imapc_capability_names[]; enum imapc_command_flags { /* The command changes the selected mailbox (SELECT, EXAMINE) */ IMAPC_COMMAND_FLAG_SELECT = 0x01, /* The command is sent to server before login (or is the login command itself). Non-prelogin commands will be queued until login is successful. */ IMAPC_COMMAND_FLAG_PRELOGIN = 0x02, /* Allow command to be automatically retried if disconnected before it finishes. */ IMAPC_COMMAND_FLAG_RETRIABLE = 0x04, /* This is the LOGOUT command. Use a small timeout for it. */ IMAPC_COMMAND_FLAG_LOGOUT = 0x08, /* Command is being resent after a reconnection. */ IMAPC_COMMAND_FLAG_RECONNECTED = 0x10 }; enum imapc_client_ssl_mode { IMAPC_CLIENT_SSL_MODE_NONE, IMAPC_CLIENT_SSL_MODE_IMMEDIATE, IMAPC_CLIENT_SSL_MODE_STARTTLS }; #define IMAPC_DEFAULT_CONNECT_TIMEOUT_MSECS (1000*30) #define IMAPC_DEFAULT_COMMAND_TIMEOUT_MSECS (1000*60*5) #define IMAPC_DEFAULT_MAX_LINE_LENGTH (SIZE_MAX) struct imapc_throttling_settings { unsigned int init_msecs; unsigned int max_msecs; unsigned int shrink_min_msecs; }; struct imapc_client_settings { const char *host; in_port_t port; const char *master_user; const char *username; const char *password; /* Space-separated list of SASL mechanisms to try (in the specified order). The default is to use only LOGIN command or SASL PLAIN. */ const char *sasl_mechanisms; bool use_proxyauth; /* Use Sun/Oracle PROXYAUTH command */ unsigned int max_idle_time; /* If ID capability is advertised, send a unique "x-session-ext-id", which begins with this prefix. */ const char *session_id_prefix; const char *dns_client_socket_path; const char *temp_path_prefix; struct ssl_iostream_settings ssl_set; enum imapc_client_ssl_mode ssl_mode; const char *rawlog_dir; bool debug; /* Timeout for logging in. 0 = default. */ unsigned int connect_timeout_msecs; /* Number of retries, -1 = infinity */ unsigned int connect_retry_count; /* Interval between retries, must be > 0 if retries > 0 */ unsigned int connect_retry_interval_msecs; /* Timeout for IMAP commands. Reset every time more data is being sent or received. 0 = default. */ unsigned int cmd_timeout_msecs; /* Maximum allowed line length (not including literals read as streams). 0 = unlimited. */ size_t max_line_length; struct imapc_throttling_settings throttle_set; }; struct imapc_command_reply { enum imapc_command_state state; /* "[RESP TEXT]" produces key=RESP, value=TEXT. "[RESP]" produces key=RESP, value=NULL otherwise both are NULL */ const char *resp_text_key, *resp_text_value; /* The full tagged reply, including [RESP TEXT]. */ const char *text_full; /* Tagged reply text without [RESP TEXT] */ const char *text_without_resp; }; struct imapc_arg_file { /* file descriptor containing the value */ int fd; /* parent_arg.list[list_idx] points to the IMAP_ARG_LITERAL_SIZE argument */ const struct imap_arg *parent_arg; unsigned int list_idx; }; struct imapc_untagged_reply { /* name of the untagged reply, e.g. EXISTS */ const char *name; /* number at the beginning of the reply, or 0 if there wasn't any. Set for EXISTS, EXPUNGE, etc. */ uint32_t num; /* the rest of the reply can be read from these args. */ const struct imap_arg *args; /* arguments whose contents are stored into files. only "FETCH (BODY[" arguments can be here. */ const struct imapc_arg_file *file_args; unsigned int file_args_count; /* "* OK [RESP TEXT]" produces key=RESP, value=TEXT. "* OK [RESP]" produces key=RESP, value=NULL otherwise both are NULL */ const char *resp_text_key, *resp_text_value; /* If this reply occurred while a mailbox was selected, this contains the mailbox's untagged_context. */ void *untagged_box_context; }; enum imapc_state_change_event { IMAPC_STATE_CHANGE_AUTH_OK, IMAPC_STATE_CHANGE_AUTH_FAILED, }; /* Called when tagged reply is received for command. */ typedef void imapc_command_callback_t(const struct imapc_command_reply *reply, void *context); /* Called each time untagged input is received. */ typedef void imapc_untagged_callback_t(const struct imapc_untagged_reply *reply, void *context); typedef void imapc_state_change_callback_t(void *context, enum imapc_state_change_event event, const char *error); struct imapc_client * imapc_client_init(const struct imapc_client_settings *set, struct event *event_parent); void imapc_client_disconnect(struct imapc_client *client); void imapc_client_deinit(struct imapc_client **client); /* Set login callback, must be set before calling other commands. This is called only for the first login, not for any reconnects or if there are multiple connections created. */ void imapc_client_set_login_callback(struct imapc_client *client, imapc_command_callback_t *callback, void *context); /* Explicitly login to server (also done automatically). */ void imapc_client_login(struct imapc_client *client); /* Send a LOGOUT and wait for disconnection. */ void imapc_client_logout(struct imapc_client *client); struct imapc_command * imapc_client_cmd(struct imapc_client *client, imapc_command_callback_t *callback, void *context); void imapc_command_set_flags(struct imapc_command *cmd, enum imapc_command_flags flags); bool imapc_command_connection_is_selected(struct imapc_command *cmd); void imapc_command_send(struct imapc_command *cmd, const char *cmd_str); void imapc_command_sendf(struct imapc_command *cmd, const char *cmd_fmt, ...) ATTR_FORMAT(2, 3); void imapc_command_sendvf(struct imapc_command *cmd, const char *cmd_fmt, va_list args) ATTR_FORMAT(2, 0); const char *imapc_command_get_tag(struct imapc_command *cmd); void imapc_command_abort(struct imapc_command **cmd); void imapc_client_register_untagged(struct imapc_client *client, imapc_untagged_callback_t *callback, void *context); void imapc_client_run(struct imapc_client *client); void imapc_client_stop(struct imapc_client *client); bool imapc_client_is_running(struct imapc_client *client); struct imapc_client_mailbox * imapc_client_mailbox_open(struct imapc_client *client, void *untagged_box_context); void imapc_client_mailbox_set_reopen_cb(struct imapc_client_mailbox *box, void (*callback)(void *context), void *context); void imapc_client_mailbox_close(struct imapc_client_mailbox **box); bool imapc_client_mailbox_can_reconnect(struct imapc_client_mailbox *box); void imapc_client_mailbox_reconnect(struct imapc_client_mailbox *box, const char *errmsg); struct imapc_command * imapc_client_mailbox_cmd(struct imapc_client_mailbox *box, imapc_command_callback_t *callback, void *context); struct imapc_msgmap * imapc_client_mailbox_get_msgmap(struct imapc_client_mailbox *box); void imapc_client_mailbox_idle(struct imapc_client_mailbox *box); bool imapc_client_mailbox_is_opened(struct imapc_client_mailbox *box); int imapc_client_get_capabilities(struct imapc_client *client, enum imapc_capability *capabilities_r); int imapc_client_create_temp_fd(struct imapc_client *client, const char **path_r); void imapc_client_register_state_change_callback(struct imapc_client *client, imapc_state_change_callback_t *cb, void *context); #endif dovecot-2.3.21.1/src/lib-imap-client/test-imapc-client.c0000644000000000000000000006054214656633576017547 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "hostpid.h" #include "net.h" #include "istream.h" #include "ostream.h" #include "ioloop.h" #include "unlink-directory.h" #include "sleep.h" #include "test-common.h" #include "test-subprocess.h" #include "imapc-client-private.h" #include #include #define SERVER_KILL_TIMEOUT_SECS 20 #define IMAPC_COMMAND_STATE_INVALID (enum imapc_command_state)-1 typedef void test_server_init_t(void); typedef void test_client_init_t(void); struct test_server { in_port_t port; pid_t pid; int fd_listen, fd; struct istream *input; struct ostream *output; }; static struct ip_addr bind_ip; static struct test_server server; static struct imapc_client *imapc_client; static enum imapc_command_state imapc_login_last_reply; static ARRAY(enum imapc_command_state) imapc_cmd_last_replies; static bool debug = FALSE; static void main_deinit(void); /* * Test client */ static struct imapc_client_settings test_imapc_default_settings = { .host = "127.0.0.1", .username = "testuser", .password = "testpass", .dns_client_socket_path = "", .temp_path_prefix = ".test-tmp/", .rawlog_dir = "", .connect_timeout_msecs = 5000, .connect_retry_count = 3, .connect_retry_interval_msecs = 10, .max_idle_time = 10000, }; static enum imapc_command_state test_imapc_cmd_last_reply_pop(void) { const enum imapc_command_state *replies; enum imapc_command_state reply; unsigned int count; replies = array_get(&imapc_cmd_last_replies, &count); if (count == 0) return IMAPC_COMMAND_STATE_INVALID; reply = replies[0]; array_pop_front(&imapc_cmd_last_replies); return reply; } static bool test_imapc_cmd_last_reply_expect(enum imapc_command_state state) { if (array_count(&imapc_cmd_last_replies) == 0) imapc_client_run(imapc_client); return test_imapc_cmd_last_reply_pop() == state; } static void imapc_login_callback(const struct imapc_command_reply *reply, void *context ATTR_UNUSED) { if (debug) { i_debug("Login reply: %s %s", imapc_command_state_names[reply->state], reply->text_full); } imapc_login_last_reply = reply->state; imapc_client_stop(imapc_client); } static void imapc_command_callback(const struct imapc_command_reply *reply, void *context ATTR_UNUSED) { if (debug) { i_debug("Command reply: %s %s", imapc_command_state_names[reply->state], reply->text_full); } array_push_back(&imapc_cmd_last_replies, &reply->state); imapc_client_stop(imapc_client); } static void imapc_reopen_callback(void *context) { struct imapc_client_mailbox *box = context; struct imapc_command *cmd; cmd = imapc_client_mailbox_cmd(box, imapc_command_callback, NULL); imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_SELECT); imapc_command_send(cmd, "SELECT"); } /* * Test server */ static bool test_imapc_server_expect_full(struct test_server *server, const char *expected_line) { const char *line = i_stream_read_next_line(server->input); if (debug) i_debug("Received: %s", (line == NULL ? "" : line)); if (line == NULL) { printf("imapc client disconnected unexpectedly: %s\n", i_stream_get_error(server->input)); return FALSE; } else if (strcmp(line, expected_line) != 0) { printf("imapc client sent '%s' when expecting '%s'\n", line, expected_line); return FALSE; } else { return TRUE; } } static bool test_imapc_server_expect(const char *expected_line) { return test_imapc_server_expect_full(&server, expected_line); } static void test_server_wait_connection(struct test_server *server, bool send_banner) { if (debug) i_debug("Waiting for connection"); server->fd = net_accept(server->fd_listen, NULL, NULL); i_assert(server->fd >= 0); if (debug) i_debug("Client connected"); fd_set_nonblock(server->fd, FALSE); server->input = i_stream_create_fd(server->fd, SIZE_MAX); server->output = o_stream_create_fd(server->fd, SIZE_MAX); o_stream_set_no_error_handling(server->output, TRUE); if (send_banner) { o_stream_nsend_str(server->output, "* OK [CAPABILITY IMAP4rev1 UNSELECT QUOTA] ready\r\n"); } } static void test_server_disconnect(struct test_server *server) { if (debug) i_debug("Disconnecting client"); i_stream_unref(&server->input); o_stream_unref(&server->output); i_close_fd(&server->fd); } static void test_server_disconnect_and_wait(bool send_banner) { test_server_disconnect(&server); test_server_wait_connection(&server, send_banner); } /* * Test processes */ static int test_open_server_fd(in_port_t *bind_port) { int fd = net_listen(&bind_ip, bind_port, 128); if (debug) i_debug("server listening on %u", *bind_port); if (fd == -1) { i_fatal("listen(%s:%u) failed: %m", net_ip2addr(&bind_ip), *bind_port); } fd_set_nonblock(fd, FALSE); return fd; } static int test_run_server(test_server_init_t *server_test) { struct ioloop *ioloop; i_set_failure_prefix("SERVER: "); if (debug) i_debug("PID=%s", my_pid); ioloop = io_loop_create(); if (server_test != NULL) server_test(); test_server_disconnect(&server); io_loop_destroy(&ioloop); if (debug) i_debug("Terminated"); i_close_fd(&server.fd_listen); main_deinit(); return 0; } static void test_run_client(const struct imapc_client_settings *client_set, test_client_init_t *client_test) { struct ioloop *ioloop; i_set_failure_prefix("CLIENT: "); if (debug) i_debug("PID=%s", my_pid); i_sleep_msecs(100); /* wait a little for server setup */ ioloop = io_loop_create(); imapc_client = imapc_client_init(client_set, NULL); client_test(); imapc_client_logout(imapc_client); test_assert(array_count(&imapc_cmd_last_replies) == 0); if (imapc_client != NULL) imapc_client_deinit(&imapc_client); io_loop_destroy(&ioloop); if (debug) i_debug("Terminated"); } static void test_run_client_server(const struct imapc_client_settings *client_set, test_client_init_t *client_test, test_server_init_t *server_test) { struct imapc_client_settings client_set_copy = *client_set; const char *error; imapc_client_cmd_tag_counter = 0; imapc_login_last_reply = IMAPC_COMMAND_STATE_INVALID; t_array_init(&imapc_cmd_last_replies, 4); i_zero(&server); server.pid = (pid_t)-1; server.fd = -1; server.fd_listen = test_open_server_fd(&server.port); client_set_copy.port = server.port; if (mkdir(client_set->temp_path_prefix, 0700) < 0 && errno != EEXIST) i_fatal("mkdir(%s) failed: %m", client_set->temp_path_prefix); if (server_test != NULL) { /* Fork server */ test_subprocess_fork(test_run_server, server_test, TRUE); } i_close_fd(&server.fd_listen); /* Run client */ test_run_client(&client_set_copy, client_test); i_unset_failure_prefix(); test_subprocess_kill_all(SERVER_KILL_TIMEOUT_SECS); if (unlink_directory(client_set->temp_path_prefix, UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0) i_fatal("%s", error); } /* * imapc connect failed */ static void test_imapc_connect_failed_client(void) { imapc_client_set_login_callback(imapc_client, imapc_login_callback, NULL); imapc_client_login(imapc_client); /* connection refused & one reconnect */ test_expect_errors(2); imapc_client_run(imapc_client); test_expect_no_more_errors(); test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_DISCONNECTED); } static void test_imapc_connect_failed(void) { struct imapc_client_settings set = test_imapc_default_settings; test_begin("imapc connect failed"); test_run_client_server(&set, test_imapc_connect_failed_client, NULL); test_end(); } /* * imapc banner hang */ static void test_imapc_banner_hangs_client(void) { imapc_client_set_login_callback(imapc_client, imapc_login_callback, NULL); imapc_client_login(imapc_client); test_expect_errors(2); imapc_client_run(imapc_client); test_expect_no_more_errors(); test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_DISCONNECTED); } static void test_imapc_banner_hangs_server(void) { struct test_server server2 = { .fd_listen = server.fd_listen }; test_server_wait_connection(&server, FALSE); test_server_wait_connection(&server2, FALSE); test_assert(i_stream_read_next_line(server2.input) == NULL); test_server_disconnect(&server2); } static void test_imapc_banner_hangs(void) { struct imapc_client_settings set = test_imapc_default_settings; set.connect_timeout_msecs = 500; test_begin("imapc banner hangs"); test_run_client_server(&set, test_imapc_banner_hangs_client, test_imapc_banner_hangs_server); test_end(); } /* * imapc login hangs */ static void test_imapc_login_hangs_client(void) { imapc_client_set_login_callback(imapc_client, imapc_login_callback, NULL); imapc_client_login(imapc_client); /* run the first login */ test_expect_error_string("Authentication timed out"); imapc_client_run(imapc_client); test_expect_no_more_errors(); /* imapc_login_callback() has stopped us. run the second reconnect login. */ test_expect_error_string("Authentication timed out"); imapc_client_run(imapc_client); test_expect_no_more_errors(); test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_DISCONNECTED); } static void test_imapc_login_hangs_server(void) { struct test_server server2 = { .fd_listen = server.fd_listen }; test_server_wait_connection(&server, TRUE); test_assert(test_imapc_server_expect( "1 LOGIN \"testuser\" \"testpass\"")); test_server_wait_connection(&server2, TRUE); test_assert(test_imapc_server_expect_full( &server2, "2 LOGIN \"testuser\" \"testpass\"")); test_assert(i_stream_read_next_line(server2.input) == NULL); test_server_disconnect(&server2); } static void test_imapc_login_hangs(void) { struct imapc_client_settings set = test_imapc_default_settings; set.connect_timeout_msecs = 500; test_begin("imapc login hangs"); test_run_client_server(&set, test_imapc_login_hangs_client, test_imapc_login_hangs_server); test_end(); } /* * imapc login fails */ static void test_imapc_login_fails_client(void) { imapc_client_set_login_callback(imapc_client, imapc_login_callback, NULL); imapc_client_login(imapc_client); test_expect_error_string("Authentication failed: Test login failed"); imapc_client_run(imapc_client); test_expect_no_more_errors(); test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_AUTH_FAILED); } static void test_imapc_login_fails_server(void) { test_server_wait_connection(&server, TRUE); test_assert(test_imapc_server_expect( "1 LOGIN \"testuser\" \"testpass\"")); o_stream_nsend_str(server.output, "1 NO Test login failed\r\n"); } static void test_imapc_login_fails(void) { struct imapc_client_settings set = test_imapc_default_settings; test_begin("imapc login fails"); test_run_client_server(&set, test_imapc_login_fails_client, test_imapc_login_fails_server); test_end(); } /* * imapc reconnect */ static void test_imapc_reconnect_client(void) { struct imapc_command *cmd; /* login to server */ imapc_client_set_login_callback(imapc_client, imapc_login_callback, NULL); imapc_client_login(imapc_client); imapc_client_run(imapc_client); test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_OK); imapc_login_last_reply = IMAPC_COMMAND_STATE_INVALID; /* disconnect */ cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL); imapc_command_send(cmd, "DISCONNECT"); test_expect_error_string("reconnecting"); imapc_client_run(imapc_client); test_expect_no_more_errors(); test_assert(test_imapc_cmd_last_reply_pop() == IMAPC_COMMAND_STATE_DISCONNECTED); /* we should be reconnected now. try a command. */ cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL); imapc_command_send(cmd, "NOOP"); imapc_client_run(imapc_client); test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_INVALID); test_assert(test_imapc_cmd_last_reply_pop() == IMAPC_COMMAND_STATE_OK); } static void test_imapc_reconnect_server(void) { test_server_wait_connection(&server, TRUE); test_assert(test_imapc_server_expect( "1 LOGIN \"testuser\" \"testpass\"")); o_stream_nsend_str(server.output, "1 OK \r\n"); test_assert(test_imapc_server_expect("2 DISCONNECT")); test_server_disconnect_and_wait(TRUE); test_assert(test_imapc_server_expect( "4 LOGIN \"testuser\" \"testpass\"")); o_stream_nsend_str(server.output, "4 OK \r\n"); test_assert(test_imapc_server_expect("3 NOOP")); o_stream_nsend_str(server.output, "3 OK \r\n"); test_assert(test_imapc_server_expect("5 LOGOUT")); o_stream_nsend_str(server.output, "5 OK \r\n"); test_assert(i_stream_read_next_line(server.input) == NULL); } static void test_imapc_reconnect(void) { struct imapc_client_settings set = test_imapc_default_settings; test_begin("imapc reconnect"); test_run_client_server(&set, test_imapc_reconnect_client, test_imapc_reconnect_server); test_end(); } /* * imapc reconnect resend commands */ static void test_imapc_reconnect_resend_cmds_client(void) { struct imapc_command *cmd; /* login to server */ imapc_client_set_login_callback(imapc_client, imapc_login_callback, NULL); imapc_client_login(imapc_client); imapc_client_run(imapc_client); test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_OK); imapc_login_last_reply = IMAPC_COMMAND_STATE_INVALID; /* send two commands */ cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL); imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE); imapc_command_send(cmd, "RETRY1"); cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL); imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE); imapc_command_send(cmd, "RETRY2"); /* disconnect & reconnect automatically */ cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL); imapc_command_send(cmd, "DISCONNECT"); test_expect_error_string("reconnecting"); imapc_client_run(imapc_client); test_expect_no_more_errors(); test_assert(test_imapc_cmd_last_reply_expect( IMAPC_COMMAND_STATE_DISCONNECTED)); /* continue reconnection */ test_assert(test_imapc_cmd_last_reply_expect(IMAPC_COMMAND_STATE_OK)); test_assert(test_imapc_cmd_last_reply_expect(IMAPC_COMMAND_STATE_OK)); } static void test_imapc_reconnect_resend_cmds_server(void) { test_server_wait_connection(&server, TRUE); test_assert(test_imapc_server_expect( "1 LOGIN \"testuser\" \"testpass\"")); o_stream_nsend_str(server.output, "1 OK \r\n"); test_assert(test_imapc_server_expect("2 RETRY1")); test_assert(test_imapc_server_expect("3 RETRY2")); test_assert(test_imapc_server_expect("4 DISCONNECT")); test_server_disconnect_and_wait(TRUE); test_assert(test_imapc_server_expect( "5 LOGIN \"testuser\" \"testpass\"")); o_stream_nsend_str(server.output, "5 OK \r\n"); test_assert(test_imapc_server_expect("2 RETRY1")); o_stream_nsend_str(server.output, "2 OK \r\n"); test_assert(test_imapc_server_expect("3 RETRY2")); o_stream_nsend_str(server.output, "3 OK \r\n"); test_assert(test_imapc_server_expect("6 LOGOUT")); o_stream_nsend_str(server.output, "6 OK \r\n"); test_assert(i_stream_read_next_line(server.input) == NULL); } static void test_imapc_reconnect_resend_commands(void) { struct imapc_client_settings set = test_imapc_default_settings; test_begin("imapc reconnect resend commands"); test_run_client_server(&set, test_imapc_reconnect_resend_cmds_client, test_imapc_reconnect_resend_cmds_server); test_end(); } /* * imapc reconnect resend commands failed */ static void test_imapc_reconnect_resend_cmds_failed_client(void) { struct imapc_command *cmd; /* login to server */ imapc_client_set_login_callback(imapc_client, imapc_login_callback, NULL); imapc_client_login(imapc_client); imapc_client_run(imapc_client); test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_OK); imapc_login_last_reply = IMAPC_COMMAND_STATE_INVALID; /* send two commands */ cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL); imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE); imapc_command_send(cmd, "RETRY1"); cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL); imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE); imapc_command_send(cmd, "RETRY2"); /* disconnect & try to reconnect automatically */ cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL); imapc_command_send(cmd, "DISCONNECT"); test_expect_error_string("reconnecting"); imapc_client_run(imapc_client); test_expect_no_more_errors(); test_assert(test_imapc_cmd_last_reply_expect( IMAPC_COMMAND_STATE_DISCONNECTED)); test_expect_error_string("timed out"); test_assert(test_imapc_cmd_last_reply_expect( IMAPC_COMMAND_STATE_DISCONNECTED)); test_expect_no_more_errors(); test_assert(test_imapc_cmd_last_reply_expect( IMAPC_COMMAND_STATE_DISCONNECTED)); } static void test_imapc_reconnect_resend_cmds_failed_server(void) { test_server_wait_connection(&server, TRUE); test_assert(test_imapc_server_expect( "1 LOGIN \"testuser\" \"testpass\"")); o_stream_nsend_str(server.output, "1 OK \r\n"); test_assert(test_imapc_server_expect("2 RETRY1")); test_assert(test_imapc_server_expect("3 RETRY2")); test_assert(test_imapc_server_expect("4 DISCONNECT")); test_server_disconnect(&server); i_sleep_intr_secs(60); } static void test_imapc_reconnect_resend_commands_failed(void) { struct imapc_client_settings set = test_imapc_default_settings; set.connect_timeout_msecs = 500; test_begin("imapc reconnect resend commands failed"); test_run_client_server(&set, test_imapc_reconnect_resend_cmds_failed_client, test_imapc_reconnect_resend_cmds_failed_server); test_end(); } /* * imapc reconnect mailbox */ static void test_imapc_reconnect_mailbox_client(void) { struct imapc_command *cmd; struct imapc_client_mailbox *box; /* login to server */ imapc_client_set_login_callback(imapc_client, imapc_login_callback, NULL); imapc_client_login(imapc_client); imapc_client_run(imapc_client); test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_OK); imapc_login_last_reply = IMAPC_COMMAND_STATE_INVALID; /* select a mailbox */ box = imapc_client_mailbox_open(imapc_client, NULL); imapc_client_mailbox_set_reopen_cb(box, imapc_reopen_callback, box); cmd = imapc_client_mailbox_cmd(box, imapc_command_callback, NULL); imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_SELECT); imapc_command_send(cmd, "SELECT"); imapc_client_run(imapc_client); test_assert(test_imapc_cmd_last_reply_expect(IMAPC_COMMAND_STATE_OK)); /* send a command */ cmd = imapc_client_mailbox_cmd(box, imapc_command_callback, NULL); imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE); imapc_command_send(cmd, "RETRY"); /* disconnect & reconnect automatically */ cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL); imapc_command_send(cmd, "DISCONNECT"); test_expect_error_string("reconnecting"); imapc_client_run(imapc_client); test_expect_no_more_errors(); test_assert(test_imapc_cmd_last_reply_expect( IMAPC_COMMAND_STATE_DISCONNECTED)); /* continue reconnection */ test_assert(test_imapc_cmd_last_reply_expect(IMAPC_COMMAND_STATE_OK)); test_assert(test_imapc_cmd_last_reply_expect(IMAPC_COMMAND_STATE_OK)); imapc_client_mailbox_close(&box); } static void test_imapc_reconnect_mailbox_server(void) { test_server_wait_connection(&server, TRUE); test_assert(test_imapc_server_expect( "1 LOGIN \"testuser\" \"testpass\"")); o_stream_nsend_str(server.output, "1 OK \r\n"); test_assert(test_imapc_server_expect("2 SELECT")); o_stream_nsend_str(server.output, "2 OK \r\n"); test_assert(test_imapc_server_expect("3 RETRY")); test_assert(test_imapc_server_expect("4 DISCONNECT")); test_server_disconnect_and_wait(TRUE); test_assert(test_imapc_server_expect( "5 LOGIN \"testuser\" \"testpass\"")); o_stream_nsend_str(server.output, "5 OK \r\n"); test_assert(test_imapc_server_expect("6 SELECT")); o_stream_nsend_str(server.output, "6 OK \r\n"); test_assert(test_imapc_server_expect("3 RETRY")); o_stream_nsend_str(server.output, "3 OK \r\n"); test_assert(test_imapc_server_expect("7 LOGOUT")); o_stream_nsend_str(server.output, "7 OK \r\n"); test_assert(i_stream_read_next_line(server.input) == NULL); } static void test_imapc_reconnect_mailbox(void) { struct imapc_client_settings set = test_imapc_default_settings; test_begin("imapc reconnect mailbox"); test_run_client_server(&set, test_imapc_reconnect_mailbox_client, test_imapc_reconnect_mailbox_server); test_end(); } /* * imapc_client_get_capabilities() */ static void test_imapc_client_get_capabilities_client(void) { enum imapc_capability capabilities; test_assert(imapc_client_get_capabilities(imapc_client, &capabilities) == 0); test_assert(capabilities == (IMAPC_CAPABILITY_IMAP4REV1 | IMAPC_CAPABILITY_UNSELECT | IMAPC_CAPABILITY_QUOTA)); } static void test_imapc_client_get_capabilities_server(void) { test_server_wait_connection(&server, TRUE); test_assert(test_imapc_server_expect( "1 LOGIN \"testuser\" \"testpass\"")); o_stream_nsend_str(server.output, "1 OK \r\n"); test_assert(test_imapc_server_expect("2 LOGOUT")); o_stream_nsend_str(server.output, "2 OK \r\n"); test_assert(i_stream_read_next_line(server.input) == NULL); } static void test_imapc_client_get_capabilities(void) { struct imapc_client_settings set = test_imapc_default_settings; test_begin("imapc_client_get_capabilities()"); test_run_client_server(&set, test_imapc_client_get_capabilities_client, test_imapc_client_get_capabilities_server); test_end(); } /* * imapc_client_get_capabilities() reconnected */ static void test_imapc_client_get_capabilities_reconnected_client(void) { enum imapc_capability capabilities; test_expect_error_string("Server disconnected unexpectedly"); test_assert(imapc_client_get_capabilities(imapc_client, &capabilities) == 0); test_assert(capabilities == (IMAPC_CAPABILITY_IMAP4REV1 | IMAPC_CAPABILITY_UNSELECT | IMAPC_CAPABILITY_QUOTA)); test_expect_no_more_errors(); } static void test_imapc_client_get_capabilities_reconnected_server(void) { test_server_wait_connection(&server, TRUE); test_server_disconnect_and_wait(TRUE); test_assert(test_imapc_server_expect( "2 LOGIN \"testuser\" \"testpass\"")); o_stream_nsend_str(server.output, "2 OK \r\n"); test_assert(test_imapc_server_expect("3 LOGOUT")); o_stream_nsend_str(server.output, "3 OK \r\n"); test_assert(i_stream_read_next_line(server.input) == NULL); } static void test_imapc_client_get_capabilities_reconnected(void) { struct imapc_client_settings set = test_imapc_default_settings; test_begin("imapc_client_get_capabilities() reconnected"); test_run_client_server( &set, test_imapc_client_get_capabilities_reconnected_client, test_imapc_client_get_capabilities_reconnected_server); test_end(); } /* * imapc_client_get_capabilities() disconnected */ static void test_imapc_client_get_capabilities_disconnected_client(void) { enum imapc_capability capabilities; test_expect_errors(2); test_assert(imapc_client_get_capabilities(imapc_client, &capabilities) < 0); test_expect_no_more_errors(); } static void test_imapc_client_get_capabilities_disconnected_server(void) { test_server_wait_connection(&server, TRUE); test_server_disconnect_and_wait(TRUE); } static void test_imapc_client_get_capabilities_disconnected(void) { struct imapc_client_settings set = test_imapc_default_settings; test_begin("imapc_client_get_capabilities() disconnected"); test_run_client_server( &set, test_imapc_client_get_capabilities_disconnected_client, test_imapc_client_get_capabilities_disconnected_server); test_end(); } /* * Main */ static void main_init(void) { /* nothing yet */ } static void main_deinit(void) { /* nothing yet; also called from sub-processes */ } int main(int argc ATTR_UNUSED, char *argv[]) { int c; int ret; static void (*const test_functions[])(void) = { test_imapc_connect_failed, test_imapc_banner_hangs, test_imapc_login_hangs, test_imapc_login_fails, test_imapc_reconnect, test_imapc_reconnect_resend_commands, test_imapc_reconnect_resend_commands_failed, test_imapc_reconnect_mailbox, test_imapc_client_get_capabilities, test_imapc_client_get_capabilities_reconnected, test_imapc_client_get_capabilities_disconnected, NULL }; lib_init(); main_init(); while ((c = getopt(argc, argv, "D")) > 0) { switch (c) { case 'D': debug = TRUE; break; default: i_fatal("Usage: %s [-D]", argv[0]); } } test_subprocesses_init(debug); test_imapc_default_settings.debug = debug; /* listen on localhost */ i_zero(&bind_ip); bind_ip.family = AF_INET; bind_ip.u.ip4.s_addr = htonl(INADDR_LOOPBACK); ret = test_run(test_functions); test_subprocesses_deinit(); main_deinit(); lib_deinit(); return ret; } dovecot-2.3.21.1/src/lib-imap-client/imapc-client-private.h0000644000000000000000000000257414656633576020250 00000000000000#ifndef IMAPC_CLIENT_PRIVATE_H #define IMAPC_CLIENT_PRIVATE_H #include "imapc-client.h" #define IMAPC_CLIENT_IDLE_SEND_DELAY_MSECS 100 struct imapc_client_connection { struct imapc_connection *conn; struct imapc_client *client; struct imapc_client_mailbox *box; }; struct imapc_client { pool_t pool; int refcount; struct event *event; struct imapc_client_settings set; struct ssl_iostream_context *ssl_ctx; imapc_untagged_callback_t *untagged_callback; void *untagged_context; imapc_state_change_callback_t *state_change_callback; void *state_change_context; imapc_command_callback_t *login_callback; void *login_context; ARRAY(struct imapc_client_connection *) conns; bool logging_out; struct ioloop *ioloop; bool stop_on_state_finish; }; struct imapc_client_mailbox { struct imapc_client *client; struct imapc_connection *conn; struct imapc_msgmap *msgmap; struct timeout *to_send_idle; void (*reopen_callback)(void *context); void *reopen_context; void *untagged_box_context; bool reconnect_ok; bool reconnecting; bool closing; }; extern unsigned int imapc_client_cmd_tag_counter; void imapc_client_ref(struct imapc_client *client); void imapc_client_unref(struct imapc_client **client); void imapc_command_set_mailbox(struct imapc_command *cmd, struct imapc_client_mailbox *box); void imapc_client_try_stop(struct imapc_client *client); #endif dovecot-2.3.21.1/src/lib-imap-client/Makefile.in0000644000000000000000000006762714656633610016126 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ pkglibexecdir = $(libexecdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ noinst_PROGRAMS = $(am__EXEEXT_1) subdir = src/lib-imap-client ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \ $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__EXEEXT_1 = test-imapc-client$(EXEEXT) PROGRAMS = $(noinst_PROGRAMS) LTLIBRARIES = $(noinst_LTLIBRARIES) libimap_client_la_LIBADD = am_libimap_client_la_OBJECTS = imapc-client.lo imapc-connection.lo \ imapc-msgmap.lo libimap_client_la_OBJECTS = $(am_libimap_client_la_OBJECTS) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = am_test_imapc_client_OBJECTS = test-imapc-client.$(OBJEXT) test_imapc_client_OBJECTS = $(am_test_imapc_client_OBJECTS) am__DEPENDENCIES_1 = am__DEPENDENCIES_2 = $(test_deps) $(am__DEPENDENCIES_1) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/imapc-client.Plo \ ./$(DEPDIR)/imapc-connection.Plo ./$(DEPDIR)/imapc-msgmap.Plo \ ./$(DEPDIR)/test-imapc-client.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(libimap_client_la_SOURCES) $(test_imapc_client_SOURCES) DIST_SOURCES = $(libimap_client_la_SOURCES) \ $(test_imapc_client_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ *) f=$$p;; \ esac; am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; am__install_max = 40 am__nobase_strip_setup = \ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` am__nobase_strip = \ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" am__nobase_list = $(am__nobase_strip_setup); \ for p in $$list; do echo "$$p $$p"; done | \ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ if (++n[$$2] == $(am__install_max)) \ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ END { for (dir in files) print dir, files[dir] }' am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' am__uninstall_files_from_dir = { \ test -z "$$files" \ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ $(am__cd) "$$dir" && rm -f $$files; }; \ } am__installdirs = "$(DESTDIR)$(pkginc_libdir)" HEADERS = $(pkginc_lib_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ noinst_LTLIBRARIES = libimap_client.la AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-dns \ -I$(top_srcdir)/src/lib-sasl \ -I$(top_srcdir)/src/lib-ssl-iostream \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-imap libimap_client_la_SOURCES = \ imapc-client.c \ imapc-connection.c \ imapc-msgmap.c headers = \ imapc-client.h \ imapc-client-private.h \ imapc-connection.h \ imapc-msgmap.h pkginc_libdir = $(pkgincludedir) pkginc_lib_HEADERS = $(headers) test_programs = \ test-imapc-client test_deps = \ $(noinst_LTLIBRARIES) \ ../lib-ssl-iostream/libssl_iostream.la \ ../lib-sasl/libsasl.la \ ../lib-imap/libimap.la \ ../lib-mail/libmail.la \ ../lib-charset/libcharset.la \ ../lib-dns/libdns.la \ ../lib-test/libtest.la \ ../lib/liblib.la test_libs = \ $(test_deps) \ $(MODULE_LIBS) test_imapc_client_SOURCES = test-imapc-client.c test_imapc_client_LDADD = $(test_libs) test_imapc_client_DEPENDENCIES = $(test_deps) all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-imap-client/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/lib-imap-client/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): clean-noinstPROGRAMS: @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list clean-noinstLTLIBRARIES: -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) @list='$(noinst_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } libimap_client.la: $(libimap_client_la_OBJECTS) $(libimap_client_la_DEPENDENCIES) $(EXTRA_libimap_client_la_DEPENDENCIES) $(AM_V_CCLD)$(LINK) $(libimap_client_la_OBJECTS) $(libimap_client_la_LIBADD) $(LIBS) test-imapc-client$(EXEEXT): $(test_imapc_client_OBJECTS) $(test_imapc_client_DEPENDENCIES) $(EXTRA_test_imapc_client_DEPENDENCIES) @rm -f test-imapc-client$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_imapc_client_OBJECTS) $(test_imapc_client_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imapc-client.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imapc-connection.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imapc-msgmap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imapc-client.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) @$(NORMAL_INSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ done uninstall-pkginc_libHEADERS: @$(NORMAL_UNINSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am $(MAKE) $(AM_MAKEFLAGS) check-local check: check-am all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkginc_libdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ clean-noinstPROGRAMS mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/imapc-client.Plo -rm -f ./$(DEPDIR)/imapc-connection.Plo -rm -f ./$(DEPDIR)/imapc-msgmap.Plo -rm -f ./$(DEPDIR)/test-imapc-client.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-pkginc_libHEADERS install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/imapc-client.Plo -rm -f ./$(DEPDIR)/imapc-connection.Plo -rm -f ./$(DEPDIR)/imapc-msgmap.Plo -rm -f ./$(DEPDIR)/test-imapc-client.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkginc_libHEADERS .MAKE: check-am install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ check-local clean clean-generic clean-libtool \ clean-noinstLTLIBRARIES clean-noinstPROGRAMS cscopelist-am \ ctags ctags-am distclean distclean-compile distclean-generic \ distclean-libtool distclean-tags distdir dvi dvi-am html \ html-am info info-am install install-am install-data \ install-data-am install-dvi install-dvi-am install-exec \ install-exec-am install-html install-html-am install-info \ install-info-am install-man install-pdf install-pdf-am \ install-pkginc_libHEADERS install-ps install-ps-am \ install-strip installcheck installcheck-am installdirs \ maintainer-clean maintainer-clean-generic mostlyclean \ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ uninstall-pkginc_libHEADERS .PRECIOUS: Makefile check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/lib-imap-client/imapc-client.c0000644000000000000000000003753714656633576016602 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "ioloop.h" #include "safe-mkstemp.h" #include "iostream-ssl.h" #include "imapc-msgmap.h" #include "imapc-connection.h" #include "imapc-client-private.h" #include const char *imapc_command_state_names[] = { "OK", "NO", "BAD", "(auth failed)", "(disconnected)" }; const struct imapc_capability_name imapc_capability_names[] = { { "SASL-IR", IMAPC_CAPABILITY_SASL_IR }, { "LITERAL+", IMAPC_CAPABILITY_LITERALPLUS }, { "QRESYNC", IMAPC_CAPABILITY_QRESYNC }, { "IDLE", IMAPC_CAPABILITY_IDLE }, { "UIDPLUS", IMAPC_CAPABILITY_UIDPLUS }, { "AUTH=PLAIN", IMAPC_CAPABILITY_AUTH_PLAIN }, { "STARTTLS", IMAPC_CAPABILITY_STARTTLS }, { "X-GM-EXT-1", IMAPC_CAPABILITY_X_GM_EXT_1 }, { "CONDSTORE", IMAPC_CAPABILITY_CONDSTORE }, { "NAMESPACE", IMAPC_CAPABILITY_NAMESPACE }, { "UNSELECT", IMAPC_CAPABILITY_UNSELECT }, { "ESEARCH", IMAPC_CAPABILITY_ESEARCH }, { "WITHIN", IMAPC_CAPABILITY_WITHIN }, { "QUOTA", IMAPC_CAPABILITY_QUOTA }, { "ID", IMAPC_CAPABILITY_ID }, { "SAVEDATE", IMAPC_CAPABILITY_SAVEDATE }, { "IMAP4REV1", IMAPC_CAPABILITY_IMAP4REV1 }, { NULL, 0 } }; unsigned int imapc_client_cmd_tag_counter = 0; static void default_untagged_callback(const struct imapc_untagged_reply *reply ATTR_UNUSED, void *context ATTR_UNUSED) { } struct imapc_client * imapc_client_init(const struct imapc_client_settings *set, struct event *event_parent) { struct imapc_client *client; const char *error; pool_t pool; i_assert(set->connect_retry_count == 0 || set->connect_retry_interval_msecs > 0); pool = pool_alloconly_create("imapc client", 1024); client = p_new(pool, struct imapc_client, 1); client->pool = pool; client->refcount = 1; client->event = event_create(event_parent); client->set.debug = set->debug; client->set.host = p_strdup(pool, set->host); client->set.port = set->port; client->set.master_user = p_strdup_empty(pool, set->master_user); client->set.username = p_strdup(pool, set->username); client->set.password = p_strdup(pool, set->password); client->set.sasl_mechanisms = p_strdup(pool, set->sasl_mechanisms); client->set.session_id_prefix = p_strdup(pool, set->session_id_prefix); client->set.use_proxyauth = set->use_proxyauth; client->set.dns_client_socket_path = p_strdup(pool, set->dns_client_socket_path); client->set.temp_path_prefix = p_strdup(pool, set->temp_path_prefix); client->set.rawlog_dir = p_strdup(pool, set->rawlog_dir); client->set.max_idle_time = set->max_idle_time; client->set.connect_timeout_msecs = set->connect_timeout_msecs != 0 ? set->connect_timeout_msecs : IMAPC_DEFAULT_CONNECT_TIMEOUT_MSECS; client->set.connect_retry_count = set->connect_retry_count; client->set.connect_retry_interval_msecs = set->connect_retry_interval_msecs; client->set.cmd_timeout_msecs = set->cmd_timeout_msecs != 0 ? set->cmd_timeout_msecs : IMAPC_DEFAULT_COMMAND_TIMEOUT_MSECS; client->set.max_line_length = set->max_line_length != 0 ? set->max_line_length : IMAPC_DEFAULT_MAX_LINE_LENGTH; client->set.throttle_set = set->throttle_set; if (client->set.throttle_set.init_msecs == 0) client->set.throttle_set.init_msecs = IMAPC_THROTTLE_DEFAULT_INIT_MSECS; if (client->set.throttle_set.max_msecs == 0) client->set.throttle_set.max_msecs = IMAPC_THROTTLE_DEFAULT_MAX_MSECS; if (client->set.throttle_set.shrink_min_msecs == 0) client->set.throttle_set.shrink_min_msecs = IMAPC_THROTTLE_DEFAULT_SHRINK_MIN_MSECS; if (set->ssl_mode != IMAPC_CLIENT_SSL_MODE_NONE) { client->set.ssl_mode = set->ssl_mode; ssl_iostream_settings_init_from(pool, &client->set.ssl_set, &set->ssl_set); client->set.ssl_set.verbose_invalid_cert = !client->set.ssl_set.allow_invalid_cert; if (ssl_iostream_client_context_cache_get(&client->set.ssl_set, &client->ssl_ctx, &error) < 0) { i_error("imapc(%s:%u): Couldn't initialize SSL context: %s", set->host, set->port, error); } } client->untagged_callback = default_untagged_callback; p_array_init(&client->conns, pool, 8); return client; } void imapc_client_ref(struct imapc_client *client) { i_assert(client->refcount > 0); client->refcount++; } void imapc_client_unref(struct imapc_client **_client) { struct imapc_client *client = *_client; *_client = NULL; i_assert(client->refcount > 0); if (--client->refcount > 0) return; if (client->ssl_ctx != NULL) ssl_iostream_context_unref(&client->ssl_ctx); event_unref(&client->event); pool_unref(&client->pool); } void imapc_client_disconnect(struct imapc_client *client) { struct imapc_client_connection *const *conns, *conn; unsigned int i, count; conns = array_get(&client->conns, &count); for (i = count; i > 0; i--) { conn = conns[i-1]; array_delete(&client->conns, i-1, 1); i_assert(imapc_connection_get_mailbox(conn->conn) == NULL); imapc_connection_deinit(&conn->conn); i_free(conn); } } void imapc_client_deinit(struct imapc_client **_client) { struct imapc_client *client = *_client; imapc_client_disconnect(client); imapc_client_unref(_client); } void imapc_client_register_untagged(struct imapc_client *client, imapc_untagged_callback_t *callback, void *context) { client->untagged_callback = callback; client->untagged_context = context; } static void imapc_client_run_pre(struct imapc_client *client) { struct imapc_client_connection *conn; struct ioloop *prev_ioloop = current_ioloop; i_assert(client->ioloop == NULL); client->ioloop = io_loop_create(); io_loop_set_running(client->ioloop); array_foreach_elem(&client->conns, conn) { imapc_connection_ioloop_changed(conn->conn); if (imapc_connection_get_state(conn->conn) == IMAPC_CONNECTION_STATE_DISCONNECTED) imapc_connection_connect(conn->conn); } if (io_loop_is_running(client->ioloop)) io_loop_run(client->ioloop); io_loop_set_current(prev_ioloop); } static void imapc_client_run_post(struct imapc_client *client) { struct imapc_client_connection *conn; struct ioloop *ioloop = client->ioloop; client->ioloop = NULL; array_foreach_elem(&client->conns, conn) imapc_connection_ioloop_changed(conn->conn); io_loop_set_current(ioloop); io_loop_destroy(&ioloop); } void imapc_client_run(struct imapc_client *client) { imapc_client_run_pre(client); imapc_client_run_post(client); } void imapc_client_stop(struct imapc_client *client) { if (client->ioloop != NULL) io_loop_stop(client->ioloop); } void imapc_client_try_stop(struct imapc_client *client) { struct imapc_client_connection *conn; array_foreach_elem(&client->conns, conn) if (imapc_connection_get_state(conn->conn) != IMAPC_CONNECTION_STATE_DISCONNECTED) return; imapc_client_stop(client); } bool imapc_client_is_running(struct imapc_client *client) { return client->ioloop != NULL; } static void imapc_client_login_callback(const struct imapc_command_reply *reply, void *context) { struct imapc_client_connection *conn = context; struct imapc_client *client = conn->client; struct imapc_client_mailbox *box = conn->box; if (box != NULL && box->reconnecting) { box->reconnecting = FALSE; if (reply->state == IMAPC_COMMAND_STATE_OK) { /* reopen the mailbox */ box->reopen_callback(box->reopen_context); } else { imapc_connection_abort_commands(box->conn, NULL, FALSE); } } /* call the login callback only once */ if (client->login_callback != NULL) { imapc_command_callback_t *callback = client->login_callback; void *context = client->login_context; client->login_callback = NULL; client->login_context = NULL; callback(reply, context); } } static struct imapc_client_connection * imapc_client_add_connection(struct imapc_client *client) { struct imapc_client_connection *conn; conn = i_new(struct imapc_client_connection, 1); conn->client = client; conn->conn = imapc_connection_init(client, imapc_client_login_callback, conn); array_push_back(&client->conns, &conn); return conn; } static struct imapc_connection * imapc_client_find_connection(struct imapc_client *client) { struct imapc_client_connection *const *connp; /* FIXME: stupid algorithm */ if (array_count(&client->conns) == 0) return imapc_client_add_connection(client)->conn; connp = array_front(&client->conns); return (*connp)->conn; } struct imapc_command * imapc_client_cmd(struct imapc_client *client, imapc_command_callback_t *callback, void *context) { struct imapc_connection *conn; conn = imapc_client_find_connection(client); return imapc_connection_cmd(conn, callback, context); } static struct imapc_client_connection * imapc_client_get_unboxed_connection(struct imapc_client *client) { struct imapc_client_connection *const *conns; unsigned int i, count; conns = array_get(&client->conns, &count); for (i = 0; i < count; i++) { if (conns[i]->box == NULL) return conns[i]; } return imapc_client_add_connection(client); } void imapc_client_login(struct imapc_client *client) { struct imapc_client_connection *conn; i_assert(client->login_callback != NULL); i_assert(array_count(&client->conns) == 0); conn = imapc_client_add_connection(client); imapc_connection_connect(conn->conn); } struct imapc_logout_ctx { struct imapc_client *client; unsigned int logout_count; }; static void imapc_client_logout_callback(const struct imapc_command_reply *reply ATTR_UNUSED, void *context) { struct imapc_logout_ctx *ctx = context; i_assert(ctx->logout_count > 0); if (--ctx->logout_count == 0) imapc_client_stop(ctx->client); } void imapc_client_logout(struct imapc_client *client) { struct imapc_logout_ctx ctx = { .client = client }; struct imapc_client_connection *conn; struct imapc_command *cmd; client->logging_out = TRUE; /* send LOGOUT to all connections */ array_foreach_elem(&client->conns, conn) { if (imapc_connection_get_state(conn->conn) == IMAPC_CONNECTION_STATE_DISCONNECTED) continue; imapc_connection_set_no_reconnect(conn->conn); ctx.logout_count++; cmd = imapc_connection_cmd(conn->conn, imapc_client_logout_callback, &ctx); imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN | IMAPC_COMMAND_FLAG_LOGOUT); imapc_command_send(cmd, "LOGOUT"); } /* wait for LOGOUT to finish */ while (ctx.logout_count > 0) imapc_client_run(client); /* we should have disconnected all clients already, but if there were any timeouts there may be some clients left. */ imapc_client_disconnect(client); } struct imapc_client_mailbox * imapc_client_mailbox_open(struct imapc_client *client, void *untagged_box_context) { struct imapc_client_mailbox *box; struct imapc_client_connection *conn; box = i_new(struct imapc_client_mailbox, 1); box->client = client; box->untagged_box_context = untagged_box_context; conn = imapc_client_get_unboxed_connection(client); conn->box = box; box->conn = conn->conn; box->msgmap = imapc_msgmap_init(); /* if we get disconnected before the SELECT is finished, allow one reconnect retry. */ box->reconnect_ok = TRUE; return box; } void imapc_client_mailbox_set_reopen_cb(struct imapc_client_mailbox *box, void (*callback)(void *context), void *context) { box->reopen_callback = callback; box->reopen_context = context; } bool imapc_client_mailbox_can_reconnect(struct imapc_client_mailbox *box) { /* the reconnect_ok flag attempts to avoid infinite reconnection loops to a server that keeps disconnecting us (e.g. some of the commands we send keeps crashing it always) */ return box->reopen_callback != NULL && box->reconnect_ok; } void imapc_client_mailbox_reconnect(struct imapc_client_mailbox *box, const char *errmsg) { imapc_connection_try_reconnect(box->conn, errmsg, 0, FALSE); } void imapc_client_mailbox_close(struct imapc_client_mailbox **_box) { struct imapc_client_mailbox *box = *_box; struct imapc_client_connection *conn; box->closing = TRUE; /* cancel any pending commands */ imapc_connection_unselect(box, FALSE); if (box->reconnecting) { /* need to abort the reconnection so it won't try to access the box */ imapc_connection_disconnect(box->conn); } /* set this only after unselect, which may cancel some commands that reference this box */ *_box = NULL; array_foreach_elem(&box->client->conns, conn) { if (conn->box == box) { conn->box = NULL; break; } } imapc_msgmap_deinit(&box->msgmap); timeout_remove(&box->to_send_idle); i_free(box); } struct imapc_command * imapc_client_mailbox_cmd(struct imapc_client_mailbox *box, imapc_command_callback_t *callback, void *context) { struct imapc_command *cmd; i_assert(!box->closing); cmd = imapc_connection_cmd(box->conn, callback, context); imapc_command_set_mailbox(cmd, box); return cmd; } struct imapc_msgmap * imapc_client_mailbox_get_msgmap(struct imapc_client_mailbox *box) { return box->msgmap; } static void imapc_client_mailbox_idle_send(struct imapc_client_mailbox *box) { timeout_remove(&box->to_send_idle); if (imapc_client_mailbox_is_opened(box)) imapc_connection_idle(box->conn); } void imapc_client_mailbox_idle(struct imapc_client_mailbox *box) { /* send the IDLE with a delay to avoid unnecessary IDLEs that are immediately aborted */ if (box->to_send_idle == NULL && imapc_client_mailbox_is_opened(box)) { box->to_send_idle = timeout_add_short(IMAPC_CLIENT_IDLE_SEND_DELAY_MSECS, imapc_client_mailbox_idle_send, box); } /* we're done with all work at this point. */ box->reconnect_ok = TRUE; } bool imapc_client_mailbox_is_opened(struct imapc_client_mailbox *box) { struct imapc_client_mailbox *selected_box; if (box->closing || imapc_connection_get_state(box->conn) != IMAPC_CONNECTION_STATE_DONE) return FALSE; selected_box = imapc_connection_get_mailbox(box->conn); if (selected_box != box) { if (selected_box != NULL) i_error("imapc: Selected mailbox changed unexpectedly"); return FALSE; } return TRUE; } static bool imapc_client_get_any_capabilities(struct imapc_client *client, enum imapc_capability *capabilities_r) { struct imapc_client_connection *conn; array_foreach_elem(&client->conns, conn) { if (imapc_connection_get_state(conn->conn) == IMAPC_CONNECTION_STATE_DONE) { *capabilities_r = imapc_connection_get_capabilities(conn->conn); return TRUE; } } return FALSE; } int imapc_client_get_capabilities(struct imapc_client *client, enum imapc_capability *capabilities_r) { /* try to find a connection that is already logged in */ if (imapc_client_get_any_capabilities(client, capabilities_r)) return 0; /* if there are no connections yet, create one */ if (array_count(&client->conns) == 0) (void)imapc_client_add_connection(client); /* wait for any of the connections to login */ client->stop_on_state_finish = TRUE; imapc_client_run(client); client->stop_on_state_finish = FALSE; if (imapc_client_get_any_capabilities(client, capabilities_r)) return 0; /* failed */ return -1; } int imapc_client_create_temp_fd(struct imapc_client *client, const char **path_r) { string_t *path; int fd; if (client->set.temp_path_prefix == NULL) { i_error("imapc: temp_path_prefix not set, " "can't create temp file"); return -1; } path = t_str_new(128); str_append(path, client->set.temp_path_prefix); fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1); if (fd == -1) { i_error("safe_mkstemp(%s) failed: %m", str_c(path)); return -1; } /* we just want the fd, unlink it */ if (i_unlink(str_c(path)) < 0) { /* shouldn't happen.. */ i_close_fd(&fd); return -1; } *path_r = str_c(path); return fd; } void imapc_client_register_state_change_callback(struct imapc_client *client, imapc_state_change_callback_t *cb, void *context) { i_assert(client->state_change_callback == NULL); i_assert(client->state_change_context == NULL); client->state_change_callback = cb; client->state_change_context = context; } void imapc_client_set_login_callback(struct imapc_client *client, imapc_command_callback_t *callback, void *context) { client->login_callback = callback; client->login_context = context; } dovecot-2.3.21.1/src/lib-imap-client/imapc-msgmap.c0000644000000000000000000000342114656633576016571 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "imapc-msgmap.h" #include "sort.h" struct imapc_msgmap { ARRAY_TYPE(uint32_t) uids; uint32_t uid_next; }; struct imapc_msgmap *imapc_msgmap_init(void) { struct imapc_msgmap *msgmap; msgmap = i_new(struct imapc_msgmap, 1); i_array_init(&msgmap->uids, 128); msgmap->uid_next = 1; return msgmap; } void imapc_msgmap_deinit(struct imapc_msgmap **_msgmap) { struct imapc_msgmap *msgmap = *_msgmap; *_msgmap = NULL; array_free(&msgmap->uids); i_free(msgmap); } uint32_t imapc_msgmap_count(struct imapc_msgmap *msgmap) { return array_count(&msgmap->uids); } uint32_t imapc_msgmap_uidnext(struct imapc_msgmap *msgmap) { return msgmap->uid_next; } uint32_t imapc_msgmap_rseq_to_uid(struct imapc_msgmap *msgmap, uint32_t rseq) { const uint32_t *uidp; uidp = array_idx(&msgmap->uids, rseq-1); return *uidp; } bool imapc_msgmap_uid_to_rseq(struct imapc_msgmap *msgmap, uint32_t uid, uint32_t *rseq_r) { const uint32_t *p, *first; p = array_bsearch(&msgmap->uids, &uid, uint32_cmp); if (p == NULL) { *rseq_r = 0; return FALSE; } first = array_front(&msgmap->uids); *rseq_r = (p - first) + 1; return TRUE; } void imapc_msgmap_append(struct imapc_msgmap *msgmap, uint32_t rseq, uint32_t uid) { i_assert(rseq == imapc_msgmap_count(msgmap) + 1); i_assert(uid >= msgmap->uid_next); msgmap->uid_next = uid + 1; array_push_back(&msgmap->uids, &uid); } void imapc_msgmap_expunge(struct imapc_msgmap *msgmap, uint32_t rseq) { i_assert(rseq > 0); i_assert(rseq <= imapc_msgmap_count(msgmap)); array_delete(&msgmap->uids, rseq-1, 1); } void imapc_msgmap_reset(struct imapc_msgmap *msgmap) { array_clear(&msgmap->uids); msgmap->uid_next = 1; } dovecot-2.3.21.1/src/lib-imap-client/imapc-connection.h0000644000000000000000000000421214656633576017450 00000000000000#ifndef IMAPC_CONNECTION_H #define IMAPC_CONNECTION_H #include "imapc-client.h" /* [THROTTLED] handling behavior */ #define IMAPC_THROTTLE_DEFAULT_INIT_MSECS 50 #define IMAPC_THROTTLE_DEFAULT_MAX_MSECS (16*1000) #define IMAPC_THROTTLE_DEFAULT_SHRINK_MIN_MSECS 500 struct imapc_client; struct imapc_connection; enum imapc_connection_state { /* No connection */ IMAPC_CONNECTION_STATE_DISCONNECTED = 0, /* Trying to connect */ IMAPC_CONNECTION_STATE_CONNECTING, /* Connected, trying to authenticate */ IMAPC_CONNECTION_STATE_AUTHENTICATING, /* Authenticated, ready to accept commands */ IMAPC_CONNECTION_STATE_DONE }; struct imapc_connection * imapc_connection_init(struct imapc_client *client, imapc_command_callback_t *login_callback, void *login_context); void imapc_connection_deinit(struct imapc_connection **conn); void imapc_connection_connect(struct imapc_connection *conn); void imapc_connection_set_no_reconnect(struct imapc_connection *conn); void imapc_connection_disconnect(struct imapc_connection *conn); void imapc_connection_disconnect_full(struct imapc_connection *conn, bool reconnecting); void imapc_connection_try_reconnect(struct imapc_connection *conn, const char *errstr, unsigned int delay_msecs, bool connect_error); void imapc_connection_abort_commands(struct imapc_connection *conn, struct imapc_client_mailbox *only_box, bool keep_retriable) ATTR_NULL(2); void imapc_connection_ioloop_changed(struct imapc_connection *conn); void imapc_connection_input_pending(struct imapc_connection *conn); struct imapc_command * imapc_connection_cmd(struct imapc_connection *conn, imapc_command_callback_t *callback, void *context) ATTR_NULL(3); void imapc_connection_unselect(struct imapc_client_mailbox *box, bool via_tagged_reply); enum imapc_connection_state imapc_connection_get_state(struct imapc_connection *conn); enum imapc_capability imapc_connection_get_capabilities(struct imapc_connection *conn); struct imapc_client_mailbox * imapc_connection_get_mailbox(struct imapc_connection *conn); void imapc_connection_idle(struct imapc_connection *conn); #endif dovecot-2.3.21.1/src/lib-imap-client/imapc-msgmap.h0000644000000000000000000000123614656633576016600 00000000000000#ifndef IMAPC_MSGMAP_H #define IMAPC_MSGMAP_H struct imapc_msgmap *imapc_msgmap_init(void); void imapc_msgmap_deinit(struct imapc_msgmap **msgmap); uint32_t imapc_msgmap_count(struct imapc_msgmap *msgmap); uint32_t imapc_msgmap_uidnext(struct imapc_msgmap *msgmap); uint32_t imapc_msgmap_rseq_to_uid(struct imapc_msgmap *msgmap, uint32_t rseq); bool imapc_msgmap_uid_to_rseq(struct imapc_msgmap *msgmap, uint32_t uid, uint32_t *rseq_r); void imapc_msgmap_append(struct imapc_msgmap *msgmap, uint32_t rseq, uint32_t uid); void imapc_msgmap_expunge(struct imapc_msgmap *msgmap, uint32_t rseq); void imapc_msgmap_reset(struct imapc_msgmap *msgmap); #endif dovecot-2.3.21.1/src/lib-imap-client/Makefile.am0000644000000000000000000000217014656633576016106 00000000000000noinst_LTLIBRARIES = libimap_client.la AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-dns \ -I$(top_srcdir)/src/lib-sasl \ -I$(top_srcdir)/src/lib-ssl-iostream \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-imap libimap_client_la_SOURCES = \ imapc-client.c \ imapc-connection.c \ imapc-msgmap.c headers = \ imapc-client.h \ imapc-client-private.h \ imapc-connection.h \ imapc-msgmap.h pkginc_libdir=$(pkgincludedir) pkginc_lib_HEADERS = $(headers) test_programs = \ test-imapc-client noinst_PROGRAMS = $(test_programs) test_deps = \ $(noinst_LTLIBRARIES) \ ../lib-ssl-iostream/libssl_iostream.la \ ../lib-sasl/libsasl.la \ ../lib-imap/libimap.la \ ../lib-mail/libmail.la \ ../lib-charset/libcharset.la \ ../lib-dns/libdns.la \ ../lib-test/libtest.la \ ../lib/liblib.la test_libs = \ $(test_deps) \ $(MODULE_LIBS) test_imapc_client_SOURCES = test-imapc-client.c test_imapc_client_LDADD = $(test_libs) test_imapc_client_DEPENDENCIES = $(test_deps) check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done dovecot-2.3.21.1/src/pop3/0000755000000000000000000000000014656633640012035 500000000000000dovecot-2.3.21.1/src/pop3/pop3-commands.h0000644000000000000000000000051014656633576014612 00000000000000#ifndef POP3_COMMANDS_H #define POP3_COMMANDS_H struct pop3_command { const char *name; int (*func)(struct client *client, const char *args); }; const struct pop3_command *pop3_command_find(const char *name); int client_command_execute(struct client *client, const struct pop3_command *cmd, const char *args); #endif dovecot-2.3.21.1/src/pop3/pop3-client.c0000644000000000000000000005773414656633576014306 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "pop3-common.h" #include "array.h" #include "ioloop.h" #include "net.h" #include "iostream.h" #include "istream.h" #include "ostream.h" #include "iostream-rawlog.h" #include "crc32.h" #include "str.h" #include "llist.h" #include "hostpid.h" #include "file-dotlock.h" #include "var-expand.h" #include "master-service.h" #include "mail-storage.h" #include "mail-storage-service.h" #include "mail-autoexpunge.h" #include "pop3-commands.h" #include "mail-search-build.h" #include "mail-namespace.h" #include /* max. length of input command line (spec says 512) */ #define MAX_INBUF_SIZE 2048 /* Disconnect client when it sends too many bad commands in a row */ #define CLIENT_MAX_BAD_COMMANDS 20 /* Disconnect client after idling this many milliseconds */ #define CLIENT_IDLE_TIMEOUT_MSECS (10*60*1000) /* If client starts idling for this many milliseconds, commit the current transaction. This allows the mailbox to become unlocked. */ #define CLIENT_COMMIT_TIMEOUT_MSECS (10*1000) #define POP3_LOCK_FNAME "dovecot-pop3-session.lock" #define POP3_SESSION_DOTLOCK_STALE_TIMEOUT_SECS (60*5) extern struct pop3_client_vfuncs pop3_client_vfuncs; struct pop3_module_register pop3_module_register = { 0 }; struct client *pop3_clients; unsigned int pop3_client_count; static enum mail_sort_type pop3_sort_program[] = { MAIL_SORT_POP3_ORDER, MAIL_SORT_END }; static const struct dotlock_settings session_dotlock_set = { .timeout = 10, .stale_timeout = POP3_SESSION_DOTLOCK_STALE_TIMEOUT_SECS, .lock_suffix = "", .use_io_notify = TRUE }; static void client_input(struct client *client); static int client_output(struct client *client); static void client_commit_timeout(struct client *client) { if (client->cmd != NULL) { /* Can't commit while commands are running */ return; } (void)mailbox_transaction_commit(&client->trans); client->trans = mailbox_transaction_begin(client->mailbox, 0, __func__); } static void client_idle_timeout(struct client *client) { if (client->cmd != NULL) { client_destroy(client, t_strdup_printf( "Client has not read server output for for %"PRIdTIME_T" secs", ioloop_time - client->last_output)); } else { client_send_line(client, "-ERR Disconnected for inactivity."); client_destroy(client, t_strdup_printf( "Inactivity - no input for %"PRIdTIME_T" secs", ioloop_time - client->last_input)); } } static int pop3_mail_get_size(struct client *client, struct mail *mail, uoff_t *size_r) { int ret; if (!client->set->pop3_fast_size_lookups) return mail_get_virtual_size(mail, size_r); /* first try to get the virtual size */ mail->lookup_abort = MAIL_LOOKUP_ABORT_READ_MAIL; ret = mail_get_virtual_size(mail, size_r); mail->lookup_abort = MAIL_LOOKUP_ABORT_NEVER; if (ret == 0) return 0; if (mailbox_get_last_mail_error(mail->box) != MAIL_ERROR_LOOKUP_ABORTED) return -1; /* virtual size not available with a fast lookup. fallback to trying the physical size */ mail->lookup_abort = MAIL_LOOKUP_ABORT_READ_MAIL; ret = mail_get_physical_size(mail, size_r); mail->lookup_abort = MAIL_LOOKUP_ABORT_NEVER; if (ret == 0) return 0; if (mailbox_get_last_mail_error(mail->box) != MAIL_ERROR_LOOKUP_ABORTED) return -1; /* no way to quickly get the size. fallback to doing a slow virtual size lookup */ return mail_get_virtual_size(mail, size_r); } static void msgnum_to_seq_map_add(ARRAY_TYPE(uint32_t) *msgnum_to_seq_map, struct client *client, struct mail *mail, unsigned int msgnum) { uint32_t seq; if (mail->seq == msgnum+1) return; if (!array_is_created(msgnum_to_seq_map)) i_array_init(msgnum_to_seq_map, client->messages_count); /* add any messages between this and the previous one that had a POP3 order defined */ seq = array_count(msgnum_to_seq_map) + 1; for (; seq <= msgnum; seq++) array_push_back(msgnum_to_seq_map, &seq); array_push_back(msgnum_to_seq_map, &mail->seq); } static int read_mailbox(struct client *client, uint32_t *failed_uid_r) { struct mailbox_status status; struct mailbox_transaction_context *t; struct mail_search_args *search_args; struct mail_search_arg *sarg; struct mail_search_context *ctx; struct mail *mail; uoff_t size; ARRAY(uoff_t) message_sizes; ARRAY_TYPE(uint32_t) msgnum_to_seq_map = ARRAY_INIT; unsigned int msgnum; int ret = 1; *failed_uid_r = 0; mailbox_get_open_status(client->mailbox, STATUS_UIDVALIDITY, &status); client->uid_validity = status.uidvalidity; client->messages_count = status.messages; t = mailbox_transaction_begin(client->mailbox, 0, __func__); search_args = mail_search_build_init(); if (client->deleted_kw != NULL) { sarg = mail_search_build_add(search_args, SEARCH_KEYWORDS); sarg->match_not = TRUE; sarg->value.str = p_strdup(search_args->pool, client->set->pop3_deleted_flag); i_array_init(&client->all_seqs, 32); } else { mail_search_build_add_all(search_args); } mail_search_args_init(search_args, client->mailbox, TRUE, NULL); ctx = mailbox_search_init(t, search_args, pop3_sort_program, client->set->pop3_fast_size_lookups ? 0 : MAIL_FETCH_VIRTUAL_SIZE, NULL); mail_search_args_unref(&search_args); client->last_seen_pop3_msn = 0; client->total_size = 0; i_array_init(&message_sizes, client->messages_count); msgnum = 0; while (mailbox_search_next(ctx, &mail)) { if (pop3_mail_get_size(client, mail, &size) < 0) { ret = mail->expunged ? 0 : -1; *failed_uid_r = mail->uid; break; } if (array_is_created(&client->all_seqs)) seq_range_array_add(&client->all_seqs, mail->seq); msgnum_to_seq_map_add(&msgnum_to_seq_map, client, mail, msgnum); if ((mail_get_flags(mail) & MAIL_SEEN) != 0) client->last_seen_pop3_msn = msgnum + 1; client->total_size += size; if (client->highest_seq < mail->seq) client->highest_seq = mail->seq; array_push_back(&message_sizes, &size); msgnum++; } if (mailbox_search_deinit(&ctx) < 0) ret = -1; if (ret <= 0) { /* commit the transaction instead of rolling back to make sure we don't lose data (virtual sizes) added to cache file */ (void)mailbox_transaction_commit(&t); array_free(&message_sizes); if (array_is_created(&msgnum_to_seq_map)) array_free(&msgnum_to_seq_map); return ret; } i_assert(msgnum <= client->messages_count); client->messages_count = msgnum; if (!array_is_created(&client->all_seqs)) { i_array_init(&client->all_seqs, 1); seq_range_array_add_range(&client->all_seqs, 1, msgnum); } client->trans = t; client->message_sizes = array_free_without_data(&message_sizes); if (array_is_created(&msgnum_to_seq_map)) { client->msgnum_to_seq_map_count = array_count(&msgnum_to_seq_map); client->msgnum_to_seq_map = array_free_without_data(&msgnum_to_seq_map); } return 1; } static int init_pop3_deleted_flag(struct client *client, const char **error_r) { const char *deleted_keywords[2]; if (client->set->pop3_deleted_flag[0] == '\0') return 0; deleted_keywords[0] = client->set->pop3_deleted_flag; deleted_keywords[1] = NULL; if (mailbox_keywords_create(client->mailbox, deleted_keywords, &client->deleted_kw) < 0) { *error_r = t_strdup_printf( "pop3_deleted_flags: Invalid keyword '%s': %s", client->set->pop3_deleted_flag, mailbox_get_last_internal_error(client->mailbox, NULL)); return -1; } return 0; } static int init_mailbox(struct client *client, const char **error_r) { uint32_t failed_uid = 0, last_failed_uid = 0; int i, ret = -1; for (i = 0;; i++) { if (mailbox_sync(client->mailbox, MAILBOX_SYNC_FLAG_FULL_READ) < 0) { ret = -1; break; } ret = read_mailbox(client, &failed_uid); if (ret > 0) return 0; if (i == 2) break; /* well, sync and try again. maybe it works the second time. */ last_failed_uid = failed_uid; failed_uid = 0; } if (ret < 0) { *error_r = mailbox_get_last_internal_error(client->mailbox, NULL); client_send_storage_error(client); } else { if (failed_uid == last_failed_uid && failed_uid != 0) { /* failed twice in same message */ *error_r = t_strdup_printf( "Getting size of message UID=%u failed", failed_uid); } else { *error_r = "Can't sync mailbox: " "Messages keep getting expunged"; } client_send_line(client, "-ERR [SYS/TEMP] Couldn't sync mailbox."); } return -1; } static enum uidl_keys parse_uidl_keymask(const char *format) { enum uidl_keys mask = 0; for (; *format != '\0'; format++) { if (format[0] == '%' && format[1] != '\0') { switch (var_get_key(++format)) { case 'v': mask |= UIDL_UIDVALIDITY; break; case 'u': mask |= UIDL_UID; break; case 'm': mask |= UIDL_MD5; break; case 'f': mask |= UIDL_FILE_NAME; break; case 'g': mask |= UIDL_GUID; break; } } } return mask; } static void pop3_lock_session_refresh(struct client *client) { if (file_dotlock_touch(client->session_dotlock) < 0) { client_send_line(client, "-ERR [SYS/TEMP] Couldn't update POP3 session lock."); client_destroy(client, "Couldn't lock POP3 session"); } } int pop3_lock_session(struct client *client) { const struct mail_storage_settings *mail_set = mail_storage_service_user_get_mail_set(client->service_user); struct dotlock_settings dotlock_set; enum mailbox_list_path_type type; const char *dir, *path; int ret; if (mailbox_list_get_root_path(client->inbox_ns->list, MAILBOX_LIST_PATH_TYPE_INDEX, &dir)) { type = MAILBOX_LIST_PATH_TYPE_INDEX; } else if (mailbox_list_get_root_path(client->inbox_ns->list, MAILBOX_LIST_PATH_TYPE_DIR, &dir)) { type = MAILBOX_LIST_PATH_TYPE_DIR; } else { i_error("pop3_lock_session: Storage has no root/index directory, " "can't create a POP3 session lock file"); return -1; } if (mailbox_list_mkdir_root(client->inbox_ns->list, dir, type) < 0) { i_error("pop3_lock_session: Couldn't create root directory %s: %s", dir, mailbox_list_get_last_internal_error(client->inbox_ns->list, NULL)); return -1; } path = t_strdup_printf("%s/"POP3_LOCK_FNAME, dir); dotlock_set = session_dotlock_set; dotlock_set.use_excl_lock = mail_set->dotlock_use_excl; dotlock_set.nfs_flush = mail_set->mail_nfs_storage; ret = file_dotlock_create(&dotlock_set, path, 0, &client->session_dotlock); if (ret < 0) i_error("file_dotlock_create(%s) failed: %m", path); else if (ret > 0) { client->to_session_dotlock_refresh = timeout_add(POP3_SESSION_DOTLOCK_STALE_TIMEOUT_SECS*1000, pop3_lock_session_refresh, client); } return ret; } struct client *client_create(int fd_in, int fd_out, struct mail_user *user, struct mail_storage_service_user *service_user, const struct pop3_settings *set) { struct client *client; pool_t pool; /* always use nonblocking I/O */ net_set_nonblock(fd_in, TRUE); net_set_nonblock(fd_out, TRUE); pool = pool_alloconly_create("pop3 client", 256); client = p_new(pool, struct client, 1); client->pool = pool; client->service_user = service_user; client->v = pop3_client_vfuncs; client->set = set; client->fd_in = fd_in; client->fd_out = fd_out; client->input = i_stream_create_fd(fd_in, MAX_INBUF_SIZE); client->output = o_stream_create_fd(fd_out, SIZE_MAX); o_stream_set_no_error_handling(client->output, TRUE); o_stream_set_flush_callback(client->output, client_output, client); p_array_init(&client->module_contexts, client->pool, 5); client->last_input = ioloop_time; client->to_idle = timeout_add(CLIENT_IDLE_TIMEOUT_MSECS, client_idle_timeout, client); client->to_commit = timeout_add(CLIENT_COMMIT_TIMEOUT_MSECS, client_commit_timeout, client); client->user = user; client->mail_set = mail_user_set_get_storage_set(user); client->uidl_keymask = parse_uidl_keymask(client->mail_set->pop3_uidl_format); if (client->uidl_keymask == 0) i_fatal("Invalid pop3_uidl_format"); if (var_has_key(set->pop3_logout_format, 'u', "uidl_change")) { /* logging uidl_change. we need hashes of the UIDLs */ client->message_uidls_save = TRUE; } else if (strcmp(set->pop3_uidl_duplicates, "allow") != 0) { /* UIDL duplicates aren't allowed, so we'll need to keep track of them */ client->message_uidls_save = TRUE; } pop3_client_count++; DLLIST_PREPEND(&pop3_clients, client); if (hook_client_created != NULL) hook_client_created(&client); return client; } void client_create_finish(struct client *client) { if (client->set->rawlog_dir[0] != '\0') { (void)iostream_rawlog_create(client->set->rawlog_dir, &client->input, &client->output); } client->io = io_add_istream(client->input, client_input, client); } int client_init_mailbox(struct client *client, const char **error_r) { enum mailbox_flags flags; const char *ident, *errmsg; /* refresh proctitle before a potentially long-running init_mailbox() */ pop3_refresh_proctitle(); flags = MAILBOX_FLAG_POP3_SESSION; if (!client->set->pop3_no_flag_updates) flags |= MAILBOX_FLAG_DROP_RECENT; client->mailbox = mailbox_alloc(client->inbox_ns->list, "INBOX", flags); if (mailbox_open(client->mailbox) < 0) { *error_r = t_strdup_printf("Couldn't open INBOX: %s", mailbox_get_last_internal_error(client->mailbox, NULL)); client_send_storage_error(client); return -1; } if (init_pop3_deleted_flag(client, &errmsg) < 0 || init_mailbox(client, &errmsg) < 0) { *error_r = t_strdup_printf("Couldn't init INBOX: %s", errmsg); return -1; } if (!client->set->pop3_no_flag_updates && client->messages_count > 0) client->seen_bitmask = i_malloc(MSGS_BITMASK_SIZE(client)); ident = mail_user_get_anvil_userip_ident(client->user); if (ident != NULL) { master_service_anvil_send(master_service, t_strconcat( "CONNECT\t", my_pid, "\tpop3/", ident, "\n", NULL)); client->anvil_sent = TRUE; } return 0; } static const char *client_build_uidl_change_string(struct client *client) { uint32_t i, old_hash, new_hash; unsigned int old_msg_count, new_msg_count; if (client->message_uidls == NULL) { /* UIDL command not given */ return ""; } /* 1..new-1 were probably left to mailbox by previous POP3 session */ old_msg_count = client->lowest_retr_pop3_msn > 0 ? client->lowest_retr_pop3_msn - 1 : client->messages_count; for (i = 0, old_hash = 0; i < old_msg_count; i++) old_hash ^= crc32_str(client->message_uidls[i]); /* assume all except deleted messages were sent to POP3 client */ if (!client->deleted) { for (i = 0, new_hash = 0; i < client->messages_count; i++) new_hash ^= crc32_str(client->message_uidls[i]); } else { for (i = 0, new_hash = 0; i < client->messages_count; i++) { if ((client->deleted_bitmask[i / CHAR_BIT] & (1 << (i % CHAR_BIT))) != 0) continue; new_hash ^= crc32_str(client->message_uidls[i]); } } new_msg_count = client->messages_count - client->deleted_count; if (old_hash == new_hash && old_msg_count == new_msg_count) return t_strdup_printf("%u/%08x", old_msg_count, old_hash); else { return t_strdup_printf("%u/%08x -> %u/%08x", old_msg_count, old_hash, new_msg_count, new_hash); } } static const char *client_stats(struct client *client) { const char *uidl_change = ""; if (var_has_key(client->set->pop3_logout_format, 'o', "uidl_change")) uidl_change = client_build_uidl_change_string(client); const struct var_expand_table logout_tab[] = { { 'p', dec2str(client->top_bytes), "top_bytes" }, { 't', dec2str(client->top_count), "top_count" }, { 'b', dec2str(client->retr_bytes), "retr_bytes" }, { 'r', dec2str(client->retr_count), "retr_count" }, { 'd', !client->delete_success ? "0" : dec2str(client->deleted_count), "deleted_count" }, { 'm', dec2str(client->messages_count), "message_count" }, { 's', dec2str(client->total_size), "message_bytes" }, { 'i', dec2str(client->input->v_offset), "input" }, { 'o', dec2str(client->output->offset), "output" }, { 'u', uidl_change, "uidl_change" }, { '\0', !client->delete_success ? "0" : dec2str(client->deleted_size), "deleted_bytes" }, { '\0', NULL, NULL } }; const struct var_expand_table *user_tab = mail_user_var_expand_table(client->user); const struct var_expand_table *tab = t_var_expand_merge_tables(logout_tab, user_tab); string_t *str; const char *error; str = t_str_new(128); if (var_expand_with_funcs(str, client->set->pop3_logout_format, tab, mail_user_var_expand_func_table, client->user, &error) < 0) { i_error("Failed to expand pop3_logout_format=%s: %s", client->set->pop3_logout_format, error); } return str_c(str); } void client_destroy(struct client *client, const char *reason) { client->v.destroy(client, reason); } static void client_default_destroy(struct client *client, const char *reason) { i_assert(!client->destroyed); client->destroyed = TRUE; if (client->seen_change_count > 0) (void)client_update_mails(client); if (!client->disconnected) { if (reason == NULL) { reason = io_stream_get_disconnect_reason(client->input, client->output); } i_info("Disconnected: %s %s", reason, client_stats(client)); } if (client->cmd != NULL) { /* deinitialize command */ i_stream_close(client->input); o_stream_close(client->output); client->cmd(client); i_assert(client->cmd == NULL); } if (client->trans != NULL) { /* client didn't QUIT, but we still want to save any changes done in this transaction. especially the cached virtual message sizes. */ (void)mailbox_transaction_commit(&client->trans); } if (array_is_created(&client->all_seqs)) array_free(&client->all_seqs); if (client->deleted_kw != NULL) mailbox_keywords_unref(&client->deleted_kw); if (client->mailbox != NULL) mailbox_free(&client->mailbox); if (client->anvil_sent) { master_service_anvil_send(master_service, t_strconcat( "DISCONNECT\t", my_pid, "\tpop3/", mail_user_get_anvil_userip_ident(client->user), "\n", NULL)); } if (client->session_dotlock != NULL) file_dotlock_delete(&client->session_dotlock); timeout_remove(&client->to_session_dotlock_refresh); pool_unref(&client->uidl_pool); i_free(client->message_sizes); i_free(client->deleted_bitmask); i_free(client->seen_bitmask); i_free(client->msgnum_to_seq_map); io_remove(&client->io); timeout_remove(&client->to_idle); timeout_remove(&client->to_commit); i_stream_destroy(&client->input); o_stream_destroy(&client->output); fd_close_maybe_stdio(&client->fd_in, &client->fd_out); /* Autoexpunging might run for a long time. Disconnect the client before it starts, and refresh proctitle so it's clear that it's doing autoexpunging. We've also sent DISCONNECT to anvil already, because this is background work and shouldn't really be counted as an active POP3 session for the user. */ pop3_refresh_proctitle(); mail_user_autoexpunge(client->user); mail_user_deinit(&client->user); mail_storage_service_user_unref(&client->service_user); pop3_client_count--; DLLIST_REMOVE(&pop3_clients, client); pool_unref(&client->pool); master_service_client_connection_destroyed(master_service); pop3_refresh_proctitle(); } static void client_destroy_timeout(struct client *client) { client_destroy(client, NULL); } void client_disconnect(struct client *client, const char *reason) { if (client->disconnected) return; client->disconnected = TRUE; i_info("Disconnected: %s %s", reason, client_stats(client)); (void)o_stream_flush(client->output); i_stream_close(client->input); o_stream_close(client->output); timeout_remove(&client->to_idle); client->to_idle = timeout_add(0, client_destroy_timeout, client); } void client_send_line(struct client *client, const char *fmt, ...) { va_list va; ssize_t ret; if (client->output->closed) return; va_start(va, fmt); T_BEGIN { string_t *str; str = t_str_new(256); str_vprintfa(str, fmt, va); str_append(str, "\r\n"); ret = o_stream_send(client->output, str_data(str), str_len(str)); i_assert(ret < 0 || (size_t)ret == str_len(str)); } T_END; if (ret >= 0) { if (!POP3_CLIENT_OUTPUT_FULL(client)) client->last_output = ioloop_time; else if (client->io != NULL) { /* no more input until client has read our output */ io_remove(&client->io); /* If someone happens to flush output, we want to get our IO handler back in flush callback */ o_stream_set_flush_pending(client->output, TRUE); } } va_end(va); } void client_send_storage_error(struct client *client) { const char *errstr; enum mail_error error; if (mailbox_is_inconsistent(client->mailbox)) { client_send_line(client, "-ERR [SYS/TEMP] Mailbox is in inconsistent " "state, please relogin."); client_disconnect(client, "Mailbox is in inconsistent state."); return; } errstr = mailbox_get_last_error(client->mailbox, &error); switch (error) { case MAIL_ERROR_TEMP: case MAIL_ERROR_NOQUOTA: case MAIL_ERROR_INUSE: client_send_line(client, "-ERR [SYS/TEMP] %s", errstr); break; default: client_send_line(client, "-ERR [SYS/PERM] %s", errstr); break; } } bool client_handle_input(struct client *client) { char *line, *args; int ret; o_stream_cork(client->output); while (!client->output->closed && (line = i_stream_next_line(client->input)) != NULL) { args = strchr(line, ' '); if (args != NULL) *args++ = '\0'; const struct pop3_command *cmd = pop3_command_find(line); if (cmd == NULL) { client_send_line(client, "-ERR Unknown command: %s", line); ret = -1; } else T_BEGIN { const char *reason_code = event_reason_code_prefix("pop3", "cmd_", cmd->name); struct event_reason *reason = event_reason_begin(reason_code); ret = client_command_execute(client, cmd, args != NULL ? args : ""); event_reason_end(&reason); } T_END; if (ret >= 0) { client->bad_counter = 0; if (client->cmd != NULL) { o_stream_set_flush_pending(client->output, TRUE); client->waiting_input = TRUE; break; } } else if (++client->bad_counter > CLIENT_MAX_BAD_COMMANDS) { client_send_line(client, "-ERR Too many bad commands."); client_disconnect(client, "Too many bad commands."); } } o_stream_uncork(client->output); if (client->output->closed) { client_destroy(client, NULL); return FALSE; } return TRUE; } static void client_input(struct client *client) { if (client->cmd != NULL) { /* we're still processing a command. wait until it's finished. */ io_remove(&client->io); client->waiting_input = TRUE; return; } client->waiting_input = FALSE; client->last_input = ioloop_time; timeout_reset(client->to_idle); if (client->to_commit != NULL) timeout_reset(client->to_commit); switch (i_stream_read(client->input)) { case -1: /* disconnected */ client_destroy(client, NULL); return; case -2: /* line too long, kill it */ client_send_line(client, "-ERR Input line too long."); client_destroy(client, "Input line too long"); return; } (void)client_handle_input(client); } static int client_output(struct client *client) { if (o_stream_flush(client->output) < 0) { client_destroy(client, NULL); return 1; } client->last_output = ioloop_time; timeout_reset(client->to_idle); if (client->to_commit != NULL) timeout_reset(client->to_commit); if (client->cmd != NULL) client->cmd(client); if (client->cmd == NULL) { if (o_stream_get_buffer_used_size(client->output) < POP3_OUTBUF_THROTTLE_SIZE/2 && client->io == NULL && !client->input->closed) { /* enable input again */ client->io = io_add_istream(client->input, client_input, client); } if (client->io != NULL && client->waiting_input) { if (!client_handle_input(client)) { /* client got destroyed */ return 1; } } } if (client->cmd != NULL) { /* command not finished yet */ return 0; } else if (client->io == NULL) { /* data still in output buffer, get back here to add IO */ return 0; } else { return 1; } } void clients_destroy_all(void) { while (pop3_clients != NULL) { mail_storage_service_io_activate_user(pop3_clients->service_user); if (pop3_clients->cmd == NULL) { client_send_line(pop3_clients, "-ERR [SYS/TEMP] Server shutting down."); } client_destroy(pop3_clients, "Server shutting down."); } } struct pop3_client_vfuncs pop3_client_vfuncs = { client_default_destroy }; dovecot-2.3.21.1/src/pop3/pop3-commands.c0000644000000000000000000006030014656633576014610 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "pop3-common.h" #include "array.h" #include "istream.h" #include "ostream.h" #include "hash.h" #include "str.h" #include "var-expand.h" #include "message-size.h" #include "mail-storage.h" #include "mail-storage-settings.h" #include "mail-search-build.h" #include "pop3-capability.h" #include "pop3-commands.h" static enum mail_sort_type pop3_sort_program[] = { MAIL_SORT_POP3_ORDER, MAIL_SORT_END }; static uint32_t msgnum_to_seq(struct client *client, uint32_t msgnum) { return msgnum < client->msgnum_to_seq_map_count ? client->msgnum_to_seq_map[msgnum] : msgnum+1; } static const char *get_msgnum(struct client *client, const char *args, unsigned int *msgnum, bool thenspace) { unsigned int num; if (*args < '0' || *args > '9') { client_send_line(client, "-ERR Invalid message number: %s", args); return NULL; } if (str_parse_uint(args, &num, &args) < 0) { client_send_line(client, "-ERR Message number too large: %s", args); return NULL; } if (*args != (thenspace ? ' ' : '\0')) { client_send_line(client, "-ERR Noise after message number: %s", args); return NULL; } if (num == 0 || num > client->messages_count) { client_send_line(client, "-ERR There's no message %u.", num); return NULL; } num--; if (client->deleted) { if ((client->deleted_bitmask[num / CHAR_BIT] & (1 << (num % CHAR_BIT))) != 0) { client_send_line(client, "-ERR Message is deleted."); return NULL; } } while (*args == ' ') args++; *msgnum = num; return args; } static const char *get_size(struct client *client, const char *args, uoff_t *size, bool thenspace) { uoff_t num; if (*args < '0' || *args > '9') { client_send_line(client, "-ERR Invalid size: %s", args); return NULL; } if (str_parse_uoff(args, &num, &args) < 0) { client_send_line(client, "-ERR Size too large: %s", args); return NULL; } if (*args != (thenspace ? ' ' : '\0')) { client_send_line(client, "-ERR Noise after size: %s", args); return NULL; } while (*args == ' ') args++; *size = num; return args; } static int cmd_capa(struct client *client, const char *args ATTR_UNUSED) { client_send_line(client, "+OK\r\n"POP3_CAPABILITY_REPLY"."); return 1; } static int cmd_dele(struct client *client, const char *args) { unsigned int msgnum; if (get_msgnum(client, args, &msgnum, FALSE) == NULL) return -1; if (!client->deleted) { client->deleted_bitmask = i_malloc(MSGS_BITMASK_SIZE(client)); client->deleted = TRUE; } client->deleted_bitmask[msgnum / CHAR_BIT] |= 1 << (msgnum % CHAR_BIT); client->deleted_count++; client->deleted_size += client->message_sizes[msgnum]; client_send_line(client, "+OK Marked to be deleted."); return 1; } struct cmd_list_context { unsigned int msgnum; }; static void cmd_list_callback(struct client *client) { struct cmd_list_context *ctx = client->cmd_context; for (; ctx->msgnum != client->messages_count; ctx->msgnum++) { if (client->output->closed) break; if (POP3_CLIENT_OUTPUT_FULL(client)) { /* buffer full */ return; } if (client->deleted) { if ((client->deleted_bitmask[ctx->msgnum / CHAR_BIT] & (1 << (ctx->msgnum % CHAR_BIT))) != 0) continue; } client_send_line(client, "%u %"PRIuUOFF_T, ctx->msgnum+1, client->message_sizes[ctx->msgnum]); } client_send_line(client, "."); i_free(ctx); client->cmd = NULL; } static int cmd_list(struct client *client, const char *args) { struct cmd_list_context *ctx; if (*args == '\0') { ctx = i_new(struct cmd_list_context, 1); client_send_line(client, "+OK %u messages:", client->messages_count - client->deleted_count); client->cmd = cmd_list_callback; client->cmd_context = ctx; cmd_list_callback(client); } else { unsigned int msgnum; if (get_msgnum(client, args, &msgnum, FALSE) == NULL) return -1; client_send_line(client, "+OK %u %"PRIuUOFF_T, msgnum+1, client->message_sizes[msgnum]); } return 1; } static int cmd_last(struct client *client, const char *args ATTR_UNUSED) { if (client->set->pop3_enable_last) client_send_line(client, "+OK %u", client->last_seen_pop3_msn); else client_send_line(client, "-ERR LAST command not enabled"); return 1; } static int cmd_noop(struct client *client, const char *args ATTR_UNUSED) { client_send_line(client, "+OK"); return 1; } static struct mail_search_args * pop3_search_build_seqset(ARRAY_TYPE(seq_range) *seqset) { struct mail_search_args *search_args; struct mail_search_arg *sarg; search_args = mail_search_build_init(); sarg = mail_search_build_add(search_args, SEARCH_SEQSET); sarg->value.seqset = *seqset; return search_args; } static struct mail_search_args * pop3_search_build(struct client *client, uint32_t seq) { struct mail_search_args *search_args; if (seq == 0) return pop3_search_build_seqset(&client->all_seqs); search_args = mail_search_build_init(); mail_search_build_add_seqset(search_args, seq, seq); return search_args; } static int client_verify_ordering(struct client *client, struct mail *mail, uint32_t msgnum) { uint32_t seq; seq = msgnum_to_seq(client, msgnum); if (seq != mail->seq) { i_error("Message ordering changed unexpectedly " "(msg #%u: storage seq %u -> %u)", msgnum+1, seq, mail->seq); return -1; } return 0; } static void client_expunge(struct client *client, struct mail *mail) { switch (client->set->parsed_delete_type) { case POP3_DELETE_TYPE_EXPUNGE: mail_expunge(mail); break; case POP3_DELETE_TYPE_FLAG: i_assert(client->deleted_kw != NULL); mail_update_keywords(mail, MODIFY_ADD, client->deleted_kw); break; } } bool client_update_mails(struct client *client) { struct mail_search_args *search_args; struct mail_search_context *ctx; struct mail *mail; ARRAY_TYPE(seq_range) deleted_msgs, seen_msgs; uint32_t msgnum, bit; bool ret = TRUE; if (mailbox_is_readonly(client->mailbox)) { /* silently ignore */ return TRUE; } /* translate msgnums to sequences (in case POP3 ordering is different) */ t_array_init(&deleted_msgs, 8); if (client->deleted_bitmask != NULL && client->quit_seen) { for (msgnum = 0; msgnum < client->messages_count; msgnum++) { bit = 1 << (msgnum % CHAR_BIT); if ((client->deleted_bitmask[msgnum / CHAR_BIT] & bit) != 0) seq_range_array_add(&deleted_msgs, msgnum_to_seq(client, msgnum)); } } t_array_init(&seen_msgs, 8); if (client->seen_bitmask != NULL) { for (msgnum = 0; msgnum < client->messages_count; msgnum++) { bit = 1 << (msgnum % CHAR_BIT); if ((client->seen_bitmask[msgnum / CHAR_BIT] & bit) != 0) seq_range_array_add(&seen_msgs, msgnum_to_seq(client, msgnum)); } } if (array_count(&deleted_msgs) > 0) { /* expunge DELEted mails */ search_args = pop3_search_build_seqset(&deleted_msgs); ctx = mailbox_search_init(client->trans, search_args, NULL, 0, NULL); mail_search_args_unref(&search_args); while (mailbox_search_next(ctx, &mail)) client_expunge(client, mail); if (mailbox_search_deinit(&ctx) < 0) ret = FALSE; /* don't bother setting \Seen flags for deleted messages */ seq_range_array_invert(&deleted_msgs, 1, client->highest_seq); seq_range_array_intersect(&seen_msgs, &deleted_msgs); } if (array_count(&seen_msgs) > 0) { /* add \Seen flags for RETRed mails */ search_args = pop3_search_build_seqset(&seen_msgs); ctx = mailbox_search_init(client->trans, search_args, NULL, 0, NULL); mail_search_args_unref(&search_args); while (mailbox_search_next(ctx, &mail)) mail_update_flags(mail, MODIFY_ADD, MAIL_SEEN); if (mailbox_search_deinit(&ctx) < 0) ret = FALSE; } client->seen_change_count = 0; return ret; } static int cmd_quit(struct client *client, const char *args ATTR_UNUSED) { client->quit_seen = TRUE; if (client->deleted || client->seen_bitmask != NULL) { if (!client_update_mails(client)) { client_send_storage_error(client); client_disconnect(client, "Storage error during logout."); return 1; } } if (mailbox_transaction_commit(&client->trans) < 0 || mailbox_sync(client->mailbox, MAILBOX_SYNC_FLAG_FULL_WRITE) < 0) { client_send_storage_error(client); client_disconnect(client, "Storage error during logout."); return 1; } else { client->delete_success = TRUE; } if (!client->deleted) client_send_line(client, "+OK Logging out."); else client_send_line(client, "+OK Logging out, messages deleted."); client_disconnect(client, "Logged out"); return 1; } struct fetch_context { struct mail *mail; struct istream *stream; uoff_t body_lines; uoff_t *byte_counter; uoff_t byte_counter_offset; unsigned char last; bool cr_skipped, in_body; }; static void fetch_deinit(struct fetch_context *ctx) { mail_free(&ctx->mail); i_free(ctx); } static void fetch_callback(struct client *client) { struct fetch_context *ctx = client->cmd_context; const unsigned char *data; unsigned char add; size_t i, size; int ret; while ((ctx->body_lines > 0 || !ctx->in_body) && i_stream_read_more(ctx->stream, &data, &size) > 0) { if (size > 4096) size = 4096; add = '\0'; for (i = 0; i < size; i++) { if ((data[i] == '\r' || data[i] == '\n') && !ctx->in_body) { if (i == 0 && (ctx->last == '\0' || ctx->last == '\n')) ctx->in_body = TRUE; else if (i > 0 && data[i-1] == '\n') ctx->in_body = TRUE; } if (data[i] == '\n') { if ((i == 0 && ctx->last != '\r') || (i > 0 && data[i-1] != '\r')) { /* missing CR */ add = '\r'; break; } if (ctx->in_body) { if (--ctx->body_lines == 0) { i++; break; } } } else if (data[i] == '.' && ((i == 0 && ctx->last == '\n') || (i > 0 && data[i-1] == '\n'))) { /* escape the dot */ add = '.'; break; } else if (data[i] == '\0' && (client->set->parsed_workarounds & WORKAROUND_OUTLOOK_NO_NULS) != 0) { add = 0x80; break; } } if (i > 0) { if (o_stream_send(client->output, data, i) < 0) break; ctx->last = data[i-1]; i_stream_skip(ctx->stream, i); } if (o_stream_get_buffer_used_size(client->output) >= 4096) { if ((ret = o_stream_flush(client->output)) < 0) break; if (ret == 0) { /* continue later */ return; } } if (add != '\0') { if (o_stream_send(client->output, &add, 1) < 0) break; ctx->last = add; if (add == 0x80) i_stream_skip(ctx->stream, 1); } } if (ctx->last != '\n') { /* didn't end with CRLF */ o_stream_nsend(client->output, "\r\n", 2); } if (!ctx->in_body && (client->set->parsed_workarounds & WORKAROUND_OE_NS_EOH) != 0) { /* Add the missing end of headers line. */ o_stream_nsend(client->output, "\r\n", 2); } *ctx->byte_counter += client->output->offset - ctx->byte_counter_offset; client_send_line(client, "."); fetch_deinit(ctx); client->cmd = NULL; } static int client_reply_msg_expunged(struct client *client, unsigned int msgnum) { client_send_line(client, "-ERR Message %u expunged.", msgnum + 1); if (msgnum <= client->highest_expunged_fetch_msgnum) { /* client tried to fetch an expunged message again. treat this as error so we'll eventually disconnect the client instead of letting it loop forever. */ return -1; } client->highest_expunged_fetch_msgnum = msgnum; return 1; } static int fetch(struct client *client, unsigned int msgnum, uoff_t body_lines, const char *reason, uoff_t *byte_counter) { struct fetch_context *ctx; int ret; ctx = i_new(struct fetch_context, 1); ctx->byte_counter = byte_counter; ctx->byte_counter_offset = client->output->offset; ctx->mail = mail_alloc(client->trans, MAIL_FETCH_STREAM_HEADER | MAIL_FETCH_STREAM_BODY, NULL); mail_set_seq(ctx->mail, msgnum_to_seq(client, msgnum)); if (mail_get_stream_because(ctx->mail, NULL, NULL, reason, &ctx->stream) < 0) { ret = client_reply_msg_expunged(client, msgnum); fetch_deinit(ctx); return ret; } if (body_lines == UOFF_T_MAX && client->seen_bitmask != NULL) { if ((mail_get_flags(ctx->mail) & MAIL_SEEN) == 0) { /* mark the message seen with RETR command */ client->seen_bitmask[msgnum / CHAR_BIT] |= 1 << (msgnum % CHAR_BIT); client->seen_change_count++; } } ctx->body_lines = body_lines; if (body_lines == UOFF_T_MAX) { client_send_line(client, "+OK %"PRIuUOFF_T" octets", client->message_sizes[msgnum]); } else { client_send_line(client, "+OK"); ctx->body_lines++; /* internally we count the empty line too */ } client->cmd = fetch_callback; client->cmd_context = ctx; fetch_callback(client); return 1; } static int cmd_retr(struct client *client, const char *args) { unsigned int msgnum; if (get_msgnum(client, args, &msgnum, FALSE) == NULL) return -1; if (client->lowest_retr_pop3_msn > msgnum+1 || client->lowest_retr_pop3_msn == 0) client->lowest_retr_pop3_msn = msgnum+1; if (client->last_seen_pop3_msn < msgnum+1) client->last_seen_pop3_msn = msgnum+1; client->retr_count++; return fetch(client, msgnum, UOFF_T_MAX, "RETR", &client->retr_bytes); } static int cmd_rset(struct client *client, const char *args ATTR_UNUSED) { struct mail_search_context *search_ctx; struct mail *mail; struct mail_search_args *search_args; client->last_seen_pop3_msn = 0; if (client->deleted) { client->deleted = FALSE; memset(client->deleted_bitmask, 0, MSGS_BITMASK_SIZE(client)); client->deleted_count = 0; client->deleted_size = 0; } if (client->seen_change_count > 0) { memset(client->seen_bitmask, 0, MSGS_BITMASK_SIZE(client)); client->seen_change_count = 0; } if (client->set->pop3_enable_last) { /* remove all \Seen flags (as specified by RFC 1460) */ search_args = pop3_search_build(client, 0); search_ctx = mailbox_search_init(client->trans, search_args, NULL, 0, NULL); mail_search_args_unref(&search_args); while (mailbox_search_next(search_ctx, &mail)) mail_update_flags(mail, MODIFY_REMOVE, MAIL_SEEN); (void)mailbox_search_deinit(&search_ctx); (void)mailbox_transaction_commit(&client->trans); client->trans = mailbox_transaction_begin(client->mailbox, 0, __func__); } client_send_line(client, "+OK"); return 1; } static int cmd_stat(struct client *client, const char *args ATTR_UNUSED) { client_send_line(client, "+OK %u %"PRIuUOFF_T, client->messages_count - client->deleted_count, client->total_size - client->deleted_size); return 1; } static int cmd_top(struct client *client, const char *args) { unsigned int msgnum; uoff_t max_lines; args = get_msgnum(client, args, &msgnum, TRUE); if (args == NULL) return -1; if (get_size(client, args, &max_lines, FALSE) == NULL) return -1; client->top_count++; return fetch(client, msgnum, max_lines, "TOP", &client->top_bytes); } struct cmd_uidl_context { struct mail_search_context *search_ctx; struct mail *mail; uint32_t msgnum; bool list_all; }; static int pop3_get_uid(struct client *client, struct mail *mail, string_t *str, bool *permanent_uidl_r) { char uid_str[MAX_INT_STRLEN] = { 0 }; const char *uidl; const char *hdr_md5 = NULL, *filename = NULL, *guid = NULL; if (mail_get_special(mail, MAIL_FETCH_UIDL_BACKEND, &uidl) == 0 && *uidl != '\0') { str_append(str, uidl); /* UIDL is already permanent */ *permanent_uidl_r = TRUE; return 0; } *permanent_uidl_r = FALSE; if (client->set->pop3_reuse_xuidl && mail_get_first_header(mail, "X-UIDL", &uidl) > 0) { str_append(str, uidl); return 0; } if ((client->uidl_keymask & UIDL_UID) != 0) { if (i_snprintf(uid_str, sizeof(uid_str), "%u", mail->uid) < 0) i_unreached(); } if ((client->uidl_keymask & UIDL_MD5) != 0) { if (mail_get_special(mail, MAIL_FETCH_HEADER_MD5, &hdr_md5) < 0) { i_error("UIDL: Header MD5 lookup failed: %s", mailbox_get_last_internal_error(mail->box, NULL)); return -1; } else if (hdr_md5[0] == '\0') { i_error("UIDL: Header MD5 not found " "(pop3_uidl_format=%%m not supported by storage?)"); return -1; } } if ((client->uidl_keymask & UIDL_FILE_NAME) != 0) { if (mail_get_special(mail, MAIL_FETCH_STORAGE_ID, &filename) < 0) { i_error("UIDL: File name lookup failed: %s", mailbox_get_last_internal_error(mail->box, NULL)); return -1; } else if (filename[0] == '\0') { i_error("UIDL: File name not found " "(pop3_uidl_format=%%f not supported by storage?)"); return -1; } } if ((client->uidl_keymask & UIDL_GUID) != 0) { if (mail_get_special(mail, MAIL_FETCH_GUID, &guid) < 0) { i_error("UIDL: Message GUID lookup failed: %s", mailbox_get_last_internal_error(mail->box, NULL)); return -1; } else if (guid[0] == '\0') { i_error("UIDL: Message GUID not found " "(pop3_uidl_format=%%g not supported by storage?)"); return -1; } } const struct var_expand_table tab[] = { { 'v', dec2str(client->uid_validity), "uidvalidity" }, { 'u', uid_str, "uid" }, { 'm', hdr_md5, "md5" }, { 'f', filename, "filename" }, { 'g', guid, "guid" }, { '\0', NULL, NULL } }; const char *error; if (var_expand(str, client->mail_set->pop3_uidl_format, tab, &error) <= 0) { i_error("UIDL: Failed to expand pop3_uidl_format=%s: %s", client->mail_set->pop3_uidl_format, error); return -1; } return 0; } static bool list_uidls_saved_iter(struct client *client, struct cmd_uidl_context *ctx) { bool found = FALSE; while (ctx->msgnum < client->messages_count) { uint32_t msgnum = ctx->msgnum++; if (client->deleted) { if ((client->deleted_bitmask[msgnum / CHAR_BIT] & (1 << (msgnum % CHAR_BIT))) != 0) continue; } found = TRUE; client_send_line(client, ctx->list_all ? "%u %s" : "+OK %u %s", msgnum+1, client->message_uidls[msgnum]); if (client->output->closed || !ctx->list_all) break; if (POP3_CLIENT_OUTPUT_FULL(client)) { /* output is being buffered, continue when there's more space */ return FALSE; } } /* finished */ client->cmd = NULL; if (ctx->list_all) client_send_line(client, "."); i_free(ctx); return found; } static bool list_uids_iter(struct client *client, struct cmd_uidl_context *ctx) { string_t *str; bool permanent_uidl, found = FALSE; bool failed = FALSE; if (client->message_uidls != NULL) return list_uidls_saved_iter(client, ctx); str = t_str_new(128); while (mailbox_search_next(ctx->search_ctx, &ctx->mail)) { uint32_t msgnum = ctx->msgnum++; if (client_verify_ordering(client, ctx->mail, msgnum) < 0) { failed = TRUE; break; } if (client->deleted) { if ((client->deleted_bitmask[msgnum / CHAR_BIT] & (1 << (msgnum % CHAR_BIT))) != 0) continue; } found = TRUE; str_truncate(str, 0); if (pop3_get_uid(client, ctx->mail, str, &permanent_uidl) < 0) { failed = TRUE; break; } if (client->set->pop3_save_uidl && !permanent_uidl) mail_update_pop3_uidl(ctx->mail, str_c(str)); client_send_line(client, ctx->list_all ? "%u %s" : "+OK %u %s", msgnum+1, str_c(str)); if (client->output->closed) break; if (POP3_CLIENT_OUTPUT_FULL(client) && ctx->list_all) { /* output is being buffered, continue when there's more space */ return FALSE; } } /* finished */ (void)mailbox_search_deinit(&ctx->search_ctx); client->cmd = NULL; if (ctx->list_all && !failed) client_send_line(client, "."); i_free(ctx); if (failed) client_disconnect(client, "POP3 UIDLs couldn't be listed"); return found || failed; } static void cmd_uidl_callback(struct client *client) { struct cmd_uidl_context *ctx = client->cmd_context; (void)list_uids_iter(client, ctx); } HASH_TABLE_DEFINE_TYPE(uidl_counter, char *, void *); static void uidl_rename_duplicate(string_t *uidl, HASH_TABLE_TYPE(uidl_counter) prev_uidls) { char *key; void *value; unsigned int counter; while (hash_table_lookup_full(prev_uidls, str_c(uidl), &key, &value)) { /* duplicate. the value contains the number of duplicates. */ counter = POINTER_CAST_TO(value, unsigned int) + 1; hash_table_update(prev_uidls, key, POINTER_CAST(counter)); str_printfa(uidl, "-%u", counter); /* the second lookup really should return NULL, but just in case of some weird UIDLs do this as many times as needed */ } } static void client_uidls_save(struct client *client) { struct mail_search_context *search_ctx; struct mail_search_args *search_args; struct mail *mail; HASH_TABLE_TYPE(uidl_counter) prev_uidls; const char **seq_uidls; string_t *str; char *uidl; enum mail_fetch_field wanted_fields; uint32_t msgnum; bool permanent_uidl, uidl_duplicates_rename, failed = FALSE; i_assert(client->message_uidls == NULL); search_args = pop3_search_build(client, 0); wanted_fields = 0; if ((client->uidl_keymask & UIDL_MD5) != 0) wanted_fields |= MAIL_FETCH_HEADER_MD5; search_ctx = mailbox_search_init(client->trans, search_args, NULL, wanted_fields, NULL); mail_search_args_unref(&search_args); uidl_duplicates_rename = strcmp(client->set->pop3_uidl_duplicates, "rename") == 0; if (uidl_duplicates_rename) hash_table_create(&prev_uidls, default_pool, 0, str_hash, strcmp); client->uidl_pool = pool_alloconly_create("message uidls", 1024); /* first read all the UIDLs into a temporary [seq] array */ seq_uidls = i_new(const char *, client->highest_seq); str = t_str_new(128); while (mailbox_search_next(search_ctx, &mail)) { str_truncate(str, 0); if (pop3_get_uid(client, mail, str, &permanent_uidl) < 0) { failed = TRUE; break; } if (uidl_duplicates_rename) uidl_rename_duplicate(str, prev_uidls); uidl = p_strdup(client->uidl_pool, str_c(str)); if (client->set->pop3_save_uidl && !permanent_uidl) mail_update_pop3_uidl(mail, uidl); i_assert(mail->seq <= client->highest_seq); seq_uidls[mail->seq-1] = uidl; if (uidl_duplicates_rename) hash_table_update(prev_uidls, uidl, POINTER_CAST(1)); } (void)mailbox_search_deinit(&search_ctx); if (uidl_duplicates_rename) hash_table_destroy(&prev_uidls); if (failed) { pool_unref(&client->uidl_pool); i_free(seq_uidls); return; } /* map UIDLs to msgnums (in case POP3 sort ordering is different) */ client->message_uidls = p_new(client->uidl_pool, const char *, MALLOC_ADD(client->messages_count, 1)); for (msgnum = 0; msgnum < client->messages_count; msgnum++) { client->message_uidls[msgnum] = seq_uidls[msgnum_to_seq(client, msgnum) - 1]; } i_free(seq_uidls); } static struct cmd_uidl_context * cmd_uidl_init(struct client *client, uint32_t seq) { struct cmd_uidl_context *ctx; struct mail_search_args *search_args; enum mail_fetch_field wanted_fields; if (client->message_uidls_save && client->message_uidls == NULL && client->messages_count > 0) client_uidls_save(client); ctx = i_new(struct cmd_uidl_context, 1); ctx->list_all = seq == 0; if (client->message_uidls == NULL) { wanted_fields = 0; if ((client->uidl_keymask & UIDL_MD5) != 0) wanted_fields |= MAIL_FETCH_HEADER_MD5; search_args = pop3_search_build(client, seq); ctx->search_ctx = mailbox_search_init(client->trans, search_args, pop3_sort_program, wanted_fields, NULL); mail_search_args_unref(&search_args); } if (seq == 0) { client->cmd = cmd_uidl_callback; client->cmd_context = ctx; } return ctx; } static int cmd_uidl(struct client *client, const char *args) { struct cmd_uidl_context *ctx; uint32_t seq; if (*args == '\0') { client_send_line(client, "+OK"); ctx = cmd_uidl_init(client, 0); (void)list_uids_iter(client, ctx); } else { unsigned int msgnum; if (get_msgnum(client, args, &msgnum, FALSE) == NULL) return -1; seq = msgnum_to_seq(client, msgnum); ctx = cmd_uidl_init(client, seq); ctx->msgnum = msgnum; if (!list_uids_iter(client, ctx)) return client_reply_msg_expunged(client, msgnum); } return 1; } static const struct pop3_command pop3_commands[] = { { "capa", cmd_capa }, { "dele", cmd_dele }, { "list", cmd_list }, { "last", cmd_last }, { "noop", cmd_noop }, { "quit", cmd_quit }, { "retr", cmd_retr }, { "rset", cmd_rset }, { "stat", cmd_stat }, { "top", cmd_top }, { "uidl", cmd_uidl }, }; const struct pop3_command *pop3_command_find(const char *name) { for (unsigned int i = 0; i < N_ELEMENTS(pop3_commands); i++) { if (strcasecmp(pop3_commands[i].name, name) == 0) return &pop3_commands[i]; } return NULL; } int client_command_execute(struct client *client, const struct pop3_command *cmd, const char *args) { while (*args == ' ') args++; return cmd->func(client, args); } dovecot-2.3.21.1/src/pop3/pop3-client.h0000644000000000000000000000753714656633576014307 00000000000000#ifndef POP3_CLIENT_H #define POP3_CLIENT_H #include "seq-range-array.h" struct client; struct mail_storage; struct mail_storage_service_ctx; typedef void command_func_t(struct client *client); #define MSGS_BITMASK_SIZE(client) \ (MALLOC_ADD((client)->messages_count, (CHAR_BIT-1)) / CHAR_BIT) /* Stop reading input when output buffer has this many bytes. Once the buffer size has dropped to half of it, start reading input again. */ #define POP3_OUTBUF_THROTTLE_SIZE 4096 #define POP3_CLIENT_OUTPUT_FULL(client) \ (o_stream_get_buffer_used_size((client)->output) >= POP3_OUTBUF_THROTTLE_SIZE) struct pop3_client_vfuncs { void (*destroy)(struct client *client, const char *reason); }; /* pop3_msn = 1..n in the POP3 protocol msgnum = 0..n-1 = pop3_msn-1 seq = 1..n = mail's sequence number in lib-storage. when pop3 sort ordering is used, msgnum_to_seq_map[] can be used for translation. */ struct client { struct client *prev, *next; struct pop3_client_vfuncs v; int fd_in, fd_out; struct io *io; struct istream *input; struct ostream *output; struct timeout *to_idle, *to_commit; command_func_t *cmd; void *cmd_context; pool_t pool; struct mail_storage_service_user *service_user; struct mail_user *user; struct mail_namespace *inbox_ns; struct mailbox *mailbox; struct mailbox_transaction_context *trans; struct mail_keywords *deleted_kw; struct timeout *to_session_dotlock_refresh; struct dotlock *session_dotlock; time_t last_input, last_output; unsigned int bad_counter; unsigned int highest_expunged_fetch_msgnum; unsigned int uid_validity; unsigned int messages_count; unsigned int deleted_count, seen_change_count; uoff_t total_size; uoff_t deleted_size; uint32_t last_seen_pop3_msn, lowest_retr_pop3_msn; /* All sequences currently visible in the mailbox. */ ARRAY_TYPE(seq_range) all_seqs; uint32_t highest_seq; /* [msgnum] contains mail seq. anything after it has seq = msgnum+1 */ uint32_t *msgnum_to_seq_map; uint32_t msgnum_to_seq_map_count; uoff_t top_bytes; uoff_t retr_bytes; unsigned int top_count; unsigned int retr_count; /* [msgnum] */ const char **message_uidls; uoff_t *message_sizes; /* [msgnum/8] & msgnum%8 */ unsigned char *deleted_bitmask; unsigned char *seen_bitmask; /* settings: */ const struct pop3_settings *set; const struct mail_storage_settings *mail_set; pool_t uidl_pool; enum uidl_keys uidl_keymask; /* Module-specific contexts. */ ARRAY(union pop3_module_context *) module_contexts; bool destroyed:1; bool disconnected:1; bool deleted:1; bool waiting_input:1; bool anvil_sent:1; bool message_uidls_save:1; bool delete_success:1; bool quit_seen:1; }; struct pop3_module_register { unsigned int id; }; union pop3_module_context { struct pop3_client_vfuncs super; struct pop3_module_register *reg; }; extern struct pop3_module_register pop3_module_register; extern struct client *pop3_clients; extern unsigned int pop3_client_count; /* Create new client with specified input/output handles. socket specifies if the handle is a socket. */ struct client *client_create(int fd_in, int fd_out, struct mail_user *user, struct mail_storage_service_user *service_user, const struct pop3_settings *set); void client_create_finish(struct client *client); int client_init_mailbox(struct client *client, const char **error_r); void client_destroy(struct client *client, const char *reason) ATTR_NULL(2); /* Disconnect client connection */ void client_disconnect(struct client *client, const char *reason); /* Send a line of data to client */ void client_send_line(struct client *client, const char *fmt, ...) ATTR_FORMAT(2, 3); void client_send_storage_error(struct client *client); bool client_handle_input(struct client *client); bool client_update_mails(struct client *client); void clients_destroy_all(void); int pop3_lock_session(struct client *client); #endif dovecot-2.3.21.1/src/pop3/Makefile.in0000644000000000000000000007056114656633613014033 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ pkglibexec_PROGRAMS = pop3$(EXEEXT) subdir = src/pop3 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \ $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(pkglibexecdir)" \ "$(DESTDIR)$(pkginc_libdir)" PROGRAMS = $(pkglibexec_PROGRAMS) am_pop3_OBJECTS = main.$(OBJEXT) pop3-client.$(OBJEXT) \ pop3-commands.$(OBJEXT) pop3-settings.$(OBJEXT) pop3_OBJECTS = $(am_pop3_OBJECTS) am__DEPENDENCIES_1 = AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = pop3_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(pop3_LDFLAGS) $(LDFLAGS) -o $@ AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/main.Po ./$(DEPDIR)/pop3-client.Po \ ./$(DEPDIR)/pop3-commands.Po ./$(DEPDIR)/pop3-settings.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(pop3_SOURCES) DIST_SOURCES = $(pop3_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ *) f=$$p;; \ esac; am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; am__install_max = 40 am__nobase_strip_setup = \ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` am__nobase_strip = \ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" am__nobase_list = $(am__nobase_strip_setup); \ for p in $$list; do echo "$$p $$p"; done | \ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ if (++n[$$2] == $(am__install_max)) \ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ END { for (dir in files) print dir, files[dir] }' am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' am__uninstall_files_from_dir = { \ test -z "$$files" \ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ $(am__cd) "$$dir" && rm -f $$files; }; \ } HEADERS = $(pkginc_lib_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) pkglibexecdir = $(libexecdir)/dovecot ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-dict \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-storage \ $(BINARY_CFLAGS) pop3_LDFLAGS = -export-dynamic \ $(BINARY_LDFLAGS) pop3_LDADD = \ $(LIBDOVECOT_STORAGE) \ $(LIBDOVECOT) pop3_DEPENDENCIES = \ $(LIBDOVECOT_STORAGE_DEPS) \ $(LIBDOVECOT_DEPS) pop3_SOURCES = \ main.c \ pop3-client.c \ pop3-commands.c \ pop3-settings.c headers = \ pop3-capability.h \ pop3-client.h \ pop3-commands.h \ pop3-common.h \ pop3-settings.h pkginc_libdir = $(pkgincludedir) pkginc_lib_HEADERS = $(headers) all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/pop3/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/pop3/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files clean-pkglibexecPROGRAMS: @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list pop3$(EXEEXT): $(pop3_OBJECTS) $(pop3_DEPENDENCIES) $(EXTRA_pop3_DEPENDENCIES) @rm -f pop3$(EXEEXT) $(AM_V_CCLD)$(pop3_LINK) $(pop3_OBJECTS) $(pop3_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pop3-client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pop3-commands.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pop3-settings.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) @$(NORMAL_INSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ done uninstall-pkginc_libHEADERS: @$(NORMAL_UNINSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am check: check-am all-am: Makefile $(PROGRAMS) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(pkginc_libdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-pkglibexecPROGRAMS \ mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/main.Po -rm -f ./$(DEPDIR)/pop3-client.Po -rm -f ./$(DEPDIR)/pop3-commands.Po -rm -f ./$(DEPDIR)/pop3-settings.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-pkginc_libHEADERS install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-pkglibexecPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/main.Po -rm -f ./$(DEPDIR)/pop3-client.Po -rm -f ./$(DEPDIR)/pop3-commands.Po -rm -f ./$(DEPDIR)/pop3-settings.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkginc_libHEADERS uninstall-pkglibexecPROGRAMS .MAKE: install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ clean-generic clean-libtool clean-pkglibexecPROGRAMS \ cscopelist-am ctags ctags-am distclean distclean-compile \ distclean-generic distclean-libtool distclean-tags distdir dvi \ dvi-am html html-am info info-am install install-am \ install-data install-data-am install-dvi install-dvi-am \ install-exec install-exec-am install-html install-html-am \ install-info install-info-am install-man install-pdf \ install-pdf-am install-pkginc_libHEADERS \ install-pkglibexecPROGRAMS install-ps install-ps-am \ install-strip installcheck installcheck-am installdirs \ maintainer-clean maintainer-clean-generic mostlyclean \ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ uninstall-pkginc_libHEADERS uninstall-pkglibexecPROGRAMS .PRECIOUS: Makefile # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/pop3/pop3-capability.h0000644000000000000000000000032214656633576015133 00000000000000#ifndef POP3_CAPABILITY_H #define POP3_CAPABILITY_H #define POP3_CAPABILITY_REPLY \ "CAPA\r\n" \ "TOP\r\n" \ "UIDL\r\n" \ "RESP-CODES\r\n" \ "PIPELINING\r\n" \ "AUTH-RESP-CODE\r\n" /* + SASL */ #endif dovecot-2.3.21.1/src/pop3/pop3-settings.h0000644000000000000000000000157414656633576014664 00000000000000#ifndef POP3_SETTINGS_H #define POP3_SETTINGS_H struct mail_user_settings; /* */ enum pop3_client_workarounds { WORKAROUND_OUTLOOK_NO_NULS = 0x01, WORKAROUND_OE_NS_EOH = 0x02 }; enum pop3_delete_type { POP3_DELETE_TYPE_EXPUNGE = 0, POP3_DELETE_TYPE_FLAG, }; /* */ struct pop3_settings { bool verbose_proctitle; const char *rawlog_dir; /* pop3: */ bool pop3_no_flag_updates; bool pop3_enable_last; bool pop3_reuse_xuidl; bool pop3_save_uidl; bool pop3_lock_session; bool pop3_fast_size_lookups; const char *pop3_client_workarounds; const char *pop3_logout_format; const char *pop3_uidl_duplicates; const char *pop3_deleted_flag; const char *pop3_delete_type; enum pop3_client_workarounds parsed_workarounds; enum pop3_delete_type parsed_delete_type; }; extern const struct setting_parser_info pop3_setting_parser_info; #endif dovecot-2.3.21.1/src/pop3/pop3-settings.c0000644000000000000000000001160014656633576014646 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "settings-parser.h" #include "service-settings.h" #include "mail-storage-settings.h" #include "pop3-settings.h" #include #include static bool pop3_settings_verify(void *_set, pool_t pool, const char **error_r); /* */ static struct file_listener_settings pop3_unix_listeners_array[] = { { "login/pop3", 0666, "", "" } }; static struct file_listener_settings *pop3_unix_listeners[] = { &pop3_unix_listeners_array[0] }; static buffer_t pop3_unix_listeners_buf = { { { pop3_unix_listeners, sizeof(pop3_unix_listeners) } } }; /* */ struct service_settings pop3_service_settings = { .name = "pop3", .protocol = "pop3", .type = "", .executable = "pop3", .user = "", .group = "", .privileged_group = "", .extra_groups = "$default_internal_group", .chroot = "", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 1024, .client_limit = 1, .service_count = 1, .idle_kill = 0, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &pop3_unix_listeners_buf, sizeof(pop3_unix_listeners[0]) } }, .fifo_listeners = ARRAY_INIT, .inet_listeners = ARRAY_INIT }; #undef DEF #undef DEFLIST #define DEF(type, name) \ SETTING_DEFINE_STRUCT_##type(#name, name, struct pop3_settings) #define DEFLIST(field, name, defines) \ { .type = SET_DEFLIST, .key = name, \ .offset = offsetof(struct pop3_settings, field), \ .list_info = defines } static const struct setting_define pop3_setting_defines[] = { DEF(BOOL, verbose_proctitle), DEF(STR_VARS, rawlog_dir), DEF(BOOL, pop3_no_flag_updates), DEF(BOOL, pop3_enable_last), DEF(BOOL, pop3_reuse_xuidl), DEF(BOOL, pop3_save_uidl), DEF(BOOL, pop3_lock_session), DEF(BOOL, pop3_fast_size_lookups), DEF(STR, pop3_client_workarounds), DEF(STR, pop3_logout_format), DEF(ENUM, pop3_uidl_duplicates), DEF(STR, pop3_deleted_flag), DEF(ENUM, pop3_delete_type), SETTING_DEFINE_LIST_END }; static const struct pop3_settings pop3_default_settings = { .verbose_proctitle = FALSE, .rawlog_dir = "", .pop3_no_flag_updates = FALSE, .pop3_enable_last = FALSE, .pop3_reuse_xuidl = FALSE, .pop3_save_uidl = FALSE, .pop3_lock_session = FALSE, .pop3_fast_size_lookups = FALSE, .pop3_client_workarounds = "", .pop3_logout_format = "top=%t/%p, retr=%r/%b, del=%d/%m, size=%s", .pop3_uidl_duplicates = "allow:rename", .pop3_deleted_flag = "", .pop3_delete_type = "default:expunge:flag" }; static const struct setting_parser_info *pop3_setting_dependencies[] = { &mail_user_setting_parser_info, NULL }; const struct setting_parser_info pop3_setting_parser_info = { .module_name = "pop3", .defines = pop3_setting_defines, .defaults = &pop3_default_settings, .type_offset = SIZE_MAX, .struct_size = sizeof(struct pop3_settings), .parent_offset = SIZE_MAX, .check_func = pop3_settings_verify, .dependencies = pop3_setting_dependencies }; /* */ struct pop3_client_workaround_list { const char *name; enum pop3_client_workarounds num; }; static const struct pop3_client_workaround_list pop3_client_workaround_list[] = { { "outlook-no-nuls", WORKAROUND_OUTLOOK_NO_NULS }, { "oe-ns-eoh", WORKAROUND_OE_NS_EOH }, { NULL, 0 } }; static int pop3_settings_parse_workarounds(struct pop3_settings *set, const char **error_r) { enum pop3_client_workarounds client_workarounds = 0; const struct pop3_client_workaround_list *list; const char *const *str; str = t_strsplit_spaces(set->pop3_client_workarounds, " ,"); for (; *str != NULL; str++) { list = pop3_client_workaround_list; for (; list->name != NULL; list++) { if (strcasecmp(*str, list->name) == 0) { client_workarounds |= list->num; break; } } if (list->name == NULL) { *error_r = t_strdup_printf("pop3_client_workarounds: " "Unknown workaround: %s", *str); return -1; } } set->parsed_workarounds = client_workarounds; return 0; } static bool pop3_settings_verify(void *_set, pool_t pool ATTR_UNUSED, const char **error_r) { struct pop3_settings *set = _set; if (pop3_settings_parse_workarounds(set, error_r) < 0) return FALSE; if (strcmp(set->pop3_delete_type, "default") == 0) { if (set->pop3_deleted_flag[0] == '\0') set->parsed_delete_type = POP3_DELETE_TYPE_EXPUNGE; else set->parsed_delete_type = POP3_DELETE_TYPE_FLAG; } else if (strcmp(set->pop3_delete_type, "expunge") == 0) { set->parsed_delete_type = POP3_DELETE_TYPE_EXPUNGE; } else if (strcmp(set->pop3_delete_type, "flag") == 0) { if (set->pop3_deleted_flag[0] == '\0') { *error_r = "pop3_delete_type=flag, but pop3_deleted_flag not set"; return FALSE; } set->parsed_delete_type = POP3_DELETE_TYPE_FLAG; } else { *error_r = t_strdup_printf("pop3_delete_type: Unknown value '%s'", set->pop3_delete_type); return FALSE; } return TRUE; } /* */ dovecot-2.3.21.1/src/pop3/main.c0000644000000000000000000003001114656633576013050 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "pop3-common.h" #include "ioloop.h" #include "buffer.h" #include "istream.h" #include "istream-concat.h" #include "ostream.h" #include "path-util.h" #include "base64.h" #include "str.h" #include "process-title.h" #include "restrict-access.h" #include "settings-parser.h" #include "master-service.h" #include "master-login.h" #include "master-interface.h" #include "var-expand.h" #include "mail-error.h" #include "mail-user.h" #include "mail-namespace.h" #include "mail-storage-service.h" #include #include #define IS_STANDALONE() \ (getenv(MASTER_IS_PARENT_ENV) == NULL) static bool verbose_proctitle = FALSE; static struct mail_storage_service_ctx *storage_service; static struct master_login *master_login = NULL; pop3_client_created_func_t *hook_client_created = NULL; pop3_client_created_func_t * pop3_client_created_hook_set(pop3_client_created_func_t *new_hook) { pop3_client_created_func_t *old_hook = hook_client_created; hook_client_created = new_hook; return old_hook; } void pop3_refresh_proctitle(void) { struct client *client; string_t *title = t_str_new(128); if (!verbose_proctitle) return; str_append_c(title, '['); switch (pop3_client_count) { case 0: str_append(title, "idling"); break; case 1: client = pop3_clients; str_append(title, client->user->username); if (client->user->conn.remote_ip != NULL) { str_append_c(title, ' '); str_append(title, net_ip2addr(client->user->conn.remote_ip)); } if (client->destroyed) str_append(title, " (deinit)"); break; default: str_printfa(title, "%u connections", pop3_client_count); break; } str_append_c(title, ']'); process_title_set(str_c(title)); } static void pop3_die(void) { /* do nothing. pop3 connections typically die pretty quick anyway. */ } static void client_add_input(struct client *client, const buffer_t *buf) { struct ostream *output; if (buf != NULL && buf->used > 0) { struct istream *inputs[] = { i_stream_create_copy_from_data(buf->data, buf->used), client->input, NULL }; client->input = i_stream_create_concat(inputs); i_stream_copy_fd(client->input, inputs[1]); i_stream_unref(&inputs[0]); i_stream_unref(&inputs[1]); i_stream_set_input_pending(client->input, TRUE); } output = client->output; o_stream_ref(output); o_stream_cork(output); (void)client_handle_input(client); o_stream_uncork(output); o_stream_unref(&output); } static int client_create_from_input(const struct mail_storage_service_input *input, int fd_in, int fd_out, struct client **client_r, const char **error_r) { const char *lookup_error_str = "-ERR [SYS/TEMP] "MAIL_ERRSTR_CRITICAL_MSG"\r\n"; struct mail_storage_service_user *user; struct mail_user *mail_user; struct pop3_settings *set; const char *errstr; if (mail_storage_service_lookup_next(storage_service, input, &user, &mail_user, error_r) <= 0) { if (write(fd_out, lookup_error_str, strlen(lookup_error_str)) < 0) { /* ignored */ } return -1; } restrict_access_allow_coredumps(TRUE); set = mail_storage_service_user_get_set(user)[1]; if (set->verbose_proctitle) verbose_proctitle = TRUE; if (settings_var_expand(&pop3_setting_parser_info, set, mail_user->pool, mail_user_var_expand_table(mail_user), &errstr) <= 0) { *error_r = t_strdup_printf("Failed to expand settings: %s", errstr); mail_user_deinit(&mail_user); mail_storage_service_user_unref(&user); return -1; } *client_r = client_create(fd_in, fd_out, mail_user, user, set); return 0; } static int lock_session(struct client *client) { int ret; i_assert(client->user->namespaces != NULL); i_assert(client->set->pop3_lock_session); if ((ret = pop3_lock_session(client)) <= 0) { client_send_line(client, ret < 0 ? "-ERR [SYS/TEMP] Failed to create POP3 session lock." : "-ERR [IN-USE] Mailbox is locked by another POP3 session."); client_destroy(client, "Couldn't lock POP3 session"); return -1; } return 0; } #define MSG_BYE_INTERNAL_ERROR "-ERR "MAIL_ERRSTR_CRITICAL_MSG static int init_namespaces(struct client *client, bool already_logged_in) { const char *error; /* finish initializing the user (see comment in main()) */ if (mail_namespaces_init(client->user, &error) < 0) { if (!already_logged_in) client_send_line(client, MSG_BYE_INTERNAL_ERROR); i_error("%s", error); client_destroy(client, error); return -1; } i_assert(client->inbox_ns == NULL); client->inbox_ns = mail_namespace_find_inbox(client->user->namespaces); i_assert(client->inbox_ns != NULL); return 0; } static void client_init_session(struct client *client) { const char *error; /* * RFC 1939 requires that the session lock gets acquired before the * positive response is sent to the client indicating a transition * to the TRANSACTION state. * * Since the session lock is stored under the INBOX's storage * directory, the locking code requires that the namespaces are * initialized first. * * If the system administrator configured dovecot to not use session * locks, we can send back the positive response before the * potentially long-running namespace initialization occurs. This * avoids the client possibly timing out during authentication due * to storage initialization taking too long. */ if (client->set->pop3_lock_session) { if (init_namespaces(client, FALSE) < 0) return; /* no need to propagate an error */ if (lock_session(client) < 0) return; /* no need to propagate an error */ if (!IS_STANDALONE()) client_send_line(client, "+OK Logged in."); } else { if (!IS_STANDALONE()) client_send_line(client, "+OK Logged in."); if (init_namespaces(client, TRUE) < 0) return; /* no need to propagate an error */ } struct event_reason *reason = event_reason_begin("pop3:initialize"); int ret = client_init_mailbox(client, &error); event_reason_end(&reason); if (ret < 0) { i_error("%s", error); client_destroy(client, error); } } static void main_stdio_run(const char *username) { struct client *client; struct mail_storage_service_input input; buffer_t *input_buf; const char *value, *error, *input_base64; i_zero(&input); input.module = input.service = "pop3"; input.username = username != NULL ? username : getenv("USER"); if (input.username == NULL && IS_STANDALONE()) input.username = getlogin(); if (input.username == NULL) i_fatal("USER environment missing"); if ((value = getenv("IP")) != NULL) (void)net_addr2ip(value, &input.remote_ip); if ((value = getenv("LOCAL_IP")) != NULL) (void)net_addr2ip(value, &input.local_ip); input_base64 = getenv("CLIENT_INPUT"); input_buf = input_base64 == NULL ? NULL : t_base64_decode_str(input_base64); if (client_create_from_input(&input, STDIN_FILENO, STDOUT_FILENO, &client, &error) < 0) i_fatal("%s", error); client_add_input(client, input_buf); client_create_finish(client); client_init_session(client); /* client may be destroyed now */ } static void login_client_connected(const struct master_login_client *login_client, const char *username, const char *const *extra_fields) { struct client *client; struct mail_storage_service_input input; enum mail_auth_request_flags flags = login_client->auth_req.flags; const char *error; buffer_t input_buf; i_zero(&input); input.module = input.service = "pop3"; input.local_ip = login_client->auth_req.local_ip; input.remote_ip = login_client->auth_req.remote_ip; input.local_port = login_client->auth_req.local_port; input.remote_port = login_client->auth_req.remote_port; input.username = username; input.userdb_fields = extra_fields; input.session_id = login_client->session_id; if ((flags & MAIL_AUTH_REQUEST_FLAG_CONN_SECURED) != 0) input.conn_secured = TRUE; if ((flags & MAIL_AUTH_REQUEST_FLAG_CONN_SSL_SECURED) != 0) input.conn_ssl_secured = TRUE; buffer_create_from_const_data(&input_buf, login_client->data, login_client->auth_req.data_size); if (client_create_from_input(&input, login_client->fd, login_client->fd, &client, &error) < 0) { int fd = login_client->fd; i_error("%s", error); i_close_fd(&fd); master_service_client_connection_destroyed(master_service); return; } client_add_input(client, &input_buf); client_create_finish(client); client_init_session(client); /* client may be destroyed now */ } static void login_client_failed(const struct master_login_client *client, const char *errormsg) { const char *msg; msg = t_strdup_printf("-ERR [SYS/TEMP] %s\r\n", errormsg); if (write(client->fd, msg, strlen(msg)) < 0) { /* ignored */ } } static void client_connected(struct master_service_connection *conn) { /* when running standalone, we shouldn't even get here */ i_assert(master_login != NULL); master_service_client_connection_accept(conn); master_login_add(master_login, conn->fd); } int main(int argc, char *argv[]) { static const struct setting_parser_info *set_roots[] = { &pop3_setting_parser_info, NULL }; struct master_login_settings login_set; enum master_service_flags service_flags = 0; enum mail_storage_service_flags storage_service_flags = MAIL_STORAGE_SERVICE_FLAG_NO_SSL_CA; const char *username = NULL, *auth_socket_path = "auth-master"; int c; i_zero(&login_set); login_set.postlogin_timeout_secs = MASTER_POSTLOGIN_TIMEOUT_DEFAULT; if (IS_STANDALONE() && getuid() == 0 && net_getpeername(1, NULL, NULL) == 0) { printf("-ERR [SYS/PERM] pop3 binary must not be started from " "inetd, use pop3-login instead.\n"); return 1; } if (IS_STANDALONE()) { service_flags |= MASTER_SERVICE_FLAG_STANDALONE | MASTER_SERVICE_FLAG_STD_CLIENT; } else { service_flags |= MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN; } /* * We include MAIL_STORAGE_SERVICE_FLAG_NO_NAMESPACES so that the * mail_user initialization is fast and we can quickly send back the * OK response to LOGIN/AUTHENTICATE. Otherwise we risk a very slow * namespace initialization to cause client timeouts on login. */ storage_service_flags |= MAIL_STORAGE_SERVICE_FLAG_NO_NAMESPACES; master_service = master_service_init("pop3", service_flags, &argc, &argv, "a:t:u:"); while ((c = master_getopt(master_service)) > 0) { switch (c) { case 'a': auth_socket_path = optarg; break; case 't': if (str_to_uint(optarg, &login_set.postlogin_timeout_secs) < 0 || login_set.postlogin_timeout_secs == 0) i_fatal("Invalid -t parameter: %s", optarg); break; case 'u': storage_service_flags |= MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP; username = optarg; break; default: return FATAL_DEFAULT; } } const char *error; if (t_abspath(auth_socket_path, &login_set.auth_socket_path, &error) < 0) { i_fatal("t_abspath(%s) failed: %s", auth_socket_path, error); } if (argv[optind] != NULL) { if (t_abspath(argv[optind], &login_set.postlogin_socket_path, &error) < 0) { i_fatal("t_abspath(%s) failed: %s", argv[optind], error); } } login_set.callback = login_client_connected; login_set.failure_callback = login_client_failed; login_set.update_proctitle = getenv(MASTER_VERBOSE_PROCTITLE_ENV) != NULL && master_service_get_client_limit(master_service) == 1; if (!IS_STANDALONE()) master_login = master_login_init(master_service, &login_set); master_service_set_die_callback(master_service, pop3_die); storage_service = mail_storage_service_init(master_service, set_roots, storage_service_flags); master_service_init_finish(master_service); /* NOTE: login_set.*_socket_path are now invalid due to data stack having been freed */ /* fake that we're running, so we know if client was destroyed while handling its initial input */ io_loop_set_running(current_ioloop); if (IS_STANDALONE()) { T_BEGIN { main_stdio_run(username); } T_END; } else { io_loop_set_running(current_ioloop); } if (io_loop_is_running(current_ioloop)) master_service_run(master_service, client_connected); clients_destroy_all(); if (master_login != NULL) master_login_deinit(&master_login); mail_storage_service_deinit(&storage_service); master_service_deinit(&master_service); return 0; } dovecot-2.3.21.1/src/pop3/Makefile.am0000644000000000000000000000137014656633576014022 00000000000000pkglibexecdir = $(libexecdir)/dovecot pkglibexec_PROGRAMS = pop3 AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-dict \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-storage \ $(BINARY_CFLAGS) pop3_LDFLAGS = -export-dynamic \ $(BINARY_LDFLAGS) pop3_LDADD = \ $(LIBDOVECOT_STORAGE) \ $(LIBDOVECOT) pop3_DEPENDENCIES = \ $(LIBDOVECOT_STORAGE_DEPS) \ $(LIBDOVECOT_DEPS) pop3_SOURCES = \ main.c \ pop3-client.c \ pop3-commands.c \ pop3-settings.c headers = \ pop3-capability.h \ pop3-client.h \ pop3-commands.h \ pop3-common.h \ pop3-settings.h pkginc_libdir=$(pkgincludedir) pkginc_lib_HEADERS = $(headers) dovecot-2.3.21.1/src/pop3/pop3-common.h0000644000000000000000000000116214656633576014305 00000000000000#ifndef POP3_COMMON_H #define POP3_COMMON_H enum uidl_keys { UIDL_UIDVALIDITY = 0x01, UIDL_UID = 0x02, UIDL_MD5 = 0x04, UIDL_FILE_NAME = 0x08, UIDL_GUID = 0x10 }; #include "lib.h" #include "pop3-client.h" #include "pop3-settings.h" typedef void pop3_client_created_func_t(struct client **client); extern pop3_client_created_func_t *hook_client_created; /* Sets the hook_client_created and returns the previous hook, which the new_hook should call if it's non-NULL. */ pop3_client_created_func_t * pop3_client_created_hook_set(pop3_client_created_func_t *new_hook); void pop3_refresh_proctitle(void); #endif dovecot-2.3.21.1/src/lib/0000755000000000000000000000000014656633635011726 500000000000000dovecot-2.3.21.1/src/lib/ostream-rawlog.c0000644000000000000000000000467014656633576014770 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "iostream-rawlog-private.h" #include "ostream-private.h" #include "ostream-rawlog.h" struct rawlog_ostream { struct ostream_private ostream; struct rawlog_iostream riostream; }; static void o_stream_rawlog_close(struct iostream_private *stream, bool close_parent) { struct rawlog_ostream *rstream = container_of(stream, struct rawlog_ostream, ostream.iostream); iostream_rawlog_close(&rstream->riostream); if (close_parent) o_stream_close(rstream->ostream.parent); } static ssize_t o_stream_rawlog_sendv(struct ostream_private *stream, const struct const_iovec *iov, unsigned int iov_count) { struct rawlog_ostream *rstream = container_of(stream, struct rawlog_ostream, ostream); unsigned int i; ssize_t ret, bytes; if ((ret = o_stream_sendv(stream->parent, iov, iov_count)) < 0) { o_stream_copy_error_from_parent(stream); return -1; } bytes = ret; for (i = 0; i < iov_count && bytes > 0; i++) { if (iov[i].iov_len < (size_t)bytes) { iostream_rawlog_write(&rstream->riostream, iov[i].iov_base, iov[i].iov_len); bytes -= iov[i].iov_len; } else { iostream_rawlog_write(&rstream->riostream, iov[i].iov_base, bytes); break; } } stream->ostream.offset += ret; return ret; } struct ostream * o_stream_create_rawlog(struct ostream *output, const char *rawlog_path, int rawlog_fd, enum iostream_rawlog_flags flags) { struct ostream *rawlog_output; bool autoclose_fd = (flags & IOSTREAM_RAWLOG_FLAG_AUTOCLOSE) != 0; i_assert(rawlog_path != NULL); i_assert(rawlog_fd != -1); rawlog_output = autoclose_fd ? o_stream_create_fd_autoclose(&rawlog_fd, 0): o_stream_create_fd(rawlog_fd, 0); o_stream_set_name(rawlog_output, t_strdup_printf("rawlog(%s)", rawlog_path)); return o_stream_create_rawlog_from_stream(output, rawlog_output, flags); } struct ostream * o_stream_create_rawlog_from_stream(struct ostream *output, struct ostream *rawlog_output, enum iostream_rawlog_flags flags) { struct rawlog_ostream *rstream; rstream = i_new(struct rawlog_ostream, 1); rstream->ostream.sendv = o_stream_rawlog_sendv; rstream->ostream.iostream.close = o_stream_rawlog_close; rstream->riostream.rawlog_output = rawlog_output; iostream_rawlog_init(&rstream->riostream, flags, FALSE); return o_stream_create(&rstream->ostream, output, o_stream_get_fd(output)); } dovecot-2.3.21.1/src/lib/file-create-locked.h0000644000000000000000000000304014656633576015437 00000000000000#ifndef FILE_CREATE_LOCKED_H #define FILE_CREATE_LOCKED_H #include "file-lock.h" struct file_create_settings { /* 0 = try locking without waiting */ unsigned int lock_timeout_secs; struct file_lock_settings lock_settings; /* 0 = 0600 */ int mode; /* 0 = default */ uid_t uid; /* 0 = default */ gid_t gid; const char *gid_origin; /* If parent directory doesn't exist, mkdir() it with this mode. 0 = don't mkdir(). The parent directories are assumed to be potentially rmdir() simultaneously, so the mkdir()+locking may be attempted multiple times. */ int mkdir_mode; /* 0 = default */ uid_t mkdir_uid; /* 0 = default */ gid_t mkdir_gid; const char *mkdir_gid_origin; }; /* Either open an existing file and lock it, or create the file locked. The creation is done by creating a temp file and link()ing it to path. If link() fails, opening is retried again. Returns fd on success, -1 on error. errno is preserved for the last failed syscall, so most importantly ENOENT could mean that the directory doesn't exist and EAGAIN means locking timed out. If this function is used to create lock files, file_lock_set_unlink_on_free() should be used for the resulting lock. It attempts to avoid unlinking the file if there are already other processes using the lock. That can help to avoid "Creating a locked file ... keeps failing" errors */ int file_create_locked(const char *path, const struct file_create_settings *set, struct file_lock **lock_r, bool *created_r, const char **error_r); #endif dovecot-2.3.21.1/src/lib/llist.h0000644000000000000000000000533114656633576013154 00000000000000#ifndef LLIST_H #define LLIST_H /* Doubly linked list */ #define DLLIST_PREPEND_FULL(list, item, prev, next) STMT_START { \ (item)->prev = NULL; \ (item)->next = *(list); \ if (*(list) != NULL) (*(list))->prev = (item); \ *(list) = (item); \ } STMT_END #define DLLIST_PREPEND(list, item) \ DLLIST_PREPEND_FULL(list, item, prev, next) #define DLLIST_REMOVE_FULL(list, item, prev, next) STMT_START { \ if ((item)->prev != NULL) \ (item)->prev->next = (item)->next; \ else if ((*list) == item) \ *(list) = (item)->next; \ if ((item)->next != NULL) { \ (item)->next->prev = (item)->prev; \ (item)->next = NULL; \ } \ (item)->prev = NULL; \ } STMT_END #define DLLIST_REMOVE(list, item) \ DLLIST_REMOVE_FULL(list, item, prev, next) /* Doubly linked list with head and tail */ #define DLLIST2_PREPEND_FULL(head, tail, item, prev, next) STMT_START { \ (item)->prev = NULL; \ (item)->next = *(head); \ if (*(head) != NULL) (*(head))->prev = (item); else (*tail) = (item); \ *(head) = (item); \ } STMT_END #define DLLIST2_PREPEND(head, tail, item) \ DLLIST2_PREPEND_FULL(head, tail, item, prev, next) #define DLLIST2_APPEND_FULL(head, tail, item, prev, next) STMT_START { \ (item)->prev = *(tail); \ (item)->next = NULL; \ if (*(tail) != NULL) (*(tail))->next = (item); else (*head) = (item); \ *(tail) = (item); \ } STMT_END #define DLLIST2_APPEND(head, tail, item) \ DLLIST2_APPEND_FULL(head, tail, item, prev, next) #define DLLIST2_INSERT_AFTER_FULL(head, tail, after, item, prev, next) \ STMT_START { \ (item)->prev = (after); \ (item)->next = (after)->next; \ if ((after)->next != NULL) \ (after)->next->prev = (item); \ (after)->next = (item); \ if (*(tail) == (after)) \ *(tail) = (item); \ } STMT_END #define DLLIST2_INSERT_AFTER(head, tail, after, item) \ DLLIST2_INSERT_AFTER_FULL(head, tail, after, item, prev, next) #define DLLIST2_REMOVE_FULL(head, tail, item, prev, next) STMT_START { \ if ((item)->prev != NULL) \ (item)->prev->next = (item)->next; \ else if (*(head) == item) \ *(head) = (item)->next; \ if ((item)->next != NULL) { \ (item)->next->prev = (item)->prev; \ (item)->next = NULL; \ } else if ((*tail) == item) \ *(tail) = (item)->prev; \ (item)->prev = NULL; \ } STMT_END #define DLLIST2_REMOVE(head, tail, item) \ DLLIST2_REMOVE_FULL(head, tail, item, prev, next) #define DLLIST2_JOIN_FULL(head1, tail1, head2, tail2, prev, next) STMT_START { \ if (*(head1) == NULL) { \ *(head1) = *(head2); \ *(tail1) = *(tail2); \ } else if (*(head2) != NULL) { \ (*(tail1))->next = *(head2); \ (*(head2))->prev = *(tail1); \ (*tail1) = (*tail2); \ } \ } STMT_END #define DLLIST2_JOIN(head1, tail1, head2, tail2) \ DLLIST2_JOIN_FULL(head1, tail1, head2, tail2, prev, next) #endif dovecot-2.3.21.1/src/lib/test-hash.c0000644000000000000000000000221414656633576013715 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "hash.h" static void test_hash_random_pool(pool_t pool) { #define KEYMAX 100000 HASH_TABLE(void *, void *) hash; unsigned int *keys; unsigned int i, key, keyidx, delidx; keys = i_new(unsigned int, KEYMAX); keyidx = 0; hash_table_create_direct(&hash, pool, 0); for (i = 0; i < KEYMAX; i++) { key = (i_rand_limit(KEYMAX)) + 1; if (i_rand_limit(5) > 0) { if (hash_table_lookup(hash, POINTER_CAST(key)) == NULL) { hash_table_insert(hash, POINTER_CAST(key), POINTER_CAST(1)); keys[keyidx++] = key; } } else if (keyidx > 0) { delidx = i_rand_limit(keyidx); hash_table_remove(hash, POINTER_CAST(keys[delidx])); memmove(&keys[delidx], &keys[delidx+1], (keyidx-delidx-1) * sizeof(*keys)); keyidx--; } } for (i = 0; i < keyidx; i++) hash_table_remove(hash, POINTER_CAST(keys[i])); hash_table_destroy(&hash); i_free(keys); } void test_hash(void) { pool_t pool; test_hash_random_pool(default_pool); pool = pool_alloconly_create("test hash", 1024); test_hash_random_pool(pool); pool_unref(&pool); } dovecot-2.3.21.1/src/lib/test-pkcs5.c0000644000000000000000000000375514656633576014032 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str.h" #include "buffer.h" #include "hash-method.h" #include "pkcs5.h" struct test_vector { const char *prf; unsigned char *p; size_t pLen; unsigned char *s; size_t sLen; unsigned int i; unsigned char *dk; size_t dkLen; }; #define TEST_BUF(x) (unsigned char*)x, sizeof(x)-1 /* RFC 6070 test vectors */ static const struct test_vector test_vectors_v2[] = { { "sha1", TEST_BUF("password"), TEST_BUF("salt"), 1, TEST_BUF("\x0c\x60\xc8\x0f\x96\x1f\x0e\x71\xf3\xa9\xb5\x24\xaf\x60\x12\x06\x2f\xe0\x37\xa6") }, { "sha1", TEST_BUF("password"), TEST_BUF("salt"), 2, TEST_BUF("\xea\x6c\x01\x4d\xc7\x2d\x6f\x8c\xcd\x1e\xd9\x2a\xce\x1d\x41\xf0\xd8\xde\x89\x57") }, { "sha1", TEST_BUF("password"), TEST_BUF("salt"), 4096, TEST_BUF("\x4b\x00\x79\x01\xb7\x65\x48\x9a\xbe\xad\x49\xd9\x26\xf7\x21\xd0\x65\xa4\x29\xc1") }, /* enable the next test only when you need it, it takes quite long time */ /* { "sha1", TEST_BUF("password"), TEST_BUF("salt"), 16777216, TEST_BUF("\xee\xfe\x3d\x61\xcd\x4d\xa4\xe4\xe9\x94\x5b\x3d\x6b\xa2\x15\x8c\x26\x34\xe9\x84") }, */ { "sha1", TEST_BUF("passwordPASSWORDpassword"), TEST_BUF("saltSALTsaltSALTsaltSALTsaltSALTsalt"), 4096, TEST_BUF("\x3d\x2e\xec\x4f\xe4\x1c\x84\x9b\x80\xc8\xd8\x36\x62\xc0\xe4\x4a\x8b\x29\x1a\x96\x4c\xf2\xf0\x70\x38") }, { "sha1", TEST_BUF("pass\0word"), TEST_BUF("sa\0lt"), 4096, TEST_BUF("\x56\xfa\x6a\xa7\x55\x48\x09\x9d\xcc\x37\xd7\xf0\x34\x25\xe0\xc3") } }; void test_pkcs5_pbkdf2(void) { buffer_t *res = buffer_create_dynamic(default_pool, 25); test_begin("pkcs5_pbkdf2"); for(size_t i = 0; i < N_ELEMENTS(test_vectors_v2); i++) { buffer_set_used_size(res, 0); const struct test_vector *vec = &(test_vectors_v2[i]); pkcs5_pbkdf(PKCS5_PBKDF2, hash_method_lookup(vec->prf), vec->p, vec->pLen, vec->s, vec->sLen, vec->i, vec->dkLen, res); test_assert_idx(memcmp(res->data, vec->dk, vec->dkLen) == 0, i); } buffer_free(&res); test_end(); } dovecot-2.3.21.1/src/lib/fdatasync-path.c0000644000000000000000000000122114656633576014720 00000000000000/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "fdatasync-path.h" #include #include int fdatasync_path(const char *path) { int fd, ret = 0; /* Directories need to be opened as read-only. fsync() doesn't appear to care about it. */ fd = open(path, O_RDONLY); if (fd == -1) return -1; if (fdatasync(fd) < 0) { /* Some OSes/FSes don't allow fsyncing directories. Silently ignore the problem. */ if (errno == EBADF) { /* e.g. NetBSD */ } else if (errno == EINVAL) { /* e.g. Linux+CIFS */ } else { ret = -1; } } i_close_fd(&fd); return ret; } dovecot-2.3.21.1/src/lib/failures.h0000644000000000000000000001317714656633576013646 00000000000000#ifndef FAILURES_H #define FAILURES_H struct ip_addr; /* Default exit status codes that we could use. */ enum fatal_exit_status { FATAL_LOGOPEN = 80, /* Can't open log file */ FATAL_LOGWRITE = 81, /* Can't write to log file */ FATAL_LOGERROR = 82, /* Internal logging error */ FATAL_OUTOFMEM = 83, /* Out of memory */ FATAL_EXEC = 84, /* exec() failed */ FATAL_DEFAULT = 89 }; enum log_type { LOG_TYPE_DEBUG, LOG_TYPE_INFO, LOG_TYPE_WARNING, LOG_TYPE_ERROR, LOG_TYPE_FATAL, LOG_TYPE_PANIC, LOG_TYPE_COUNT, /* special case */ LOG_TYPE_OPTION }; struct failure_line { pid_t pid; enum log_type log_type; /* If non-zero, the first log_prefix_len bytes in text indicate the log prefix. This implies disable_log_prefix=TRUE. */ unsigned int log_prefix_len; /* Disable the global log prefix. */ bool disable_log_prefix; const char *text; }; struct failure_context { enum log_type type; int exit_status; /* for LOG_TYPE_FATAL */ const struct tm *timestamp; /* NULL = use time() + localtime() */ unsigned int timestamp_usecs; const char *log_prefix; /* override the default log prefix */ /* If non-0, insert the log type text (e.g. "Info: ") at this position in the log_prefix instead of appending it. */ unsigned int log_prefix_type_pos; }; #define DEFAULT_FAILURE_STAMP_FORMAT "%b %d %H:%M:%S " typedef void failure_callback_t(const struct failure_context *ctx, const char *format, va_list args); extern const char *failure_log_type_prefixes[]; extern const char *failure_log_type_names[]; void i_log_type(const struct failure_context *ctx, const char *format, ...) ATTR_FORMAT(2, 3); void i_log_typev(const struct failure_context *ctx, const char *format, va_list args) ATTR_FORMAT(2, 0); void i_panic(const char *format, ...) ATTR_FORMAT(1, 2) ATTR_NORETURN ATTR_COLD; void i_unreached(const char *source_filename, int source_linenum) ATTR_NORETURN ATTR_COLD; #define i_unreached() \ i_unreached(__FILE__, __LINE__) void i_fatal(const char *format, ...) ATTR_FORMAT(1, 2) ATTR_NORETURN ATTR_COLD; void i_error(const char *format, ...) ATTR_FORMAT(1, 2) ATTR_COLD; void i_warning(const char *format, ...) ATTR_FORMAT(1, 2); void i_info(const char *format, ...) ATTR_FORMAT(1, 2); void i_debug(const char *format, ...) ATTR_FORMAT(1, 2); void i_fatal_status(int status, const char *format, ...) ATTR_FORMAT(2, 3) ATTR_NORETURN ATTR_COLD; /* Change failure handlers. */ #ifndef __cplusplus void i_set_fatal_handler(failure_callback_t *callback ATTR_NORETURN); #else /* Older g++ doesn't like attributes in parameters */ void i_set_fatal_handler(failure_callback_t *callback); #endif void i_set_error_handler(failure_callback_t *callback); void i_set_info_handler(failure_callback_t *callback); void i_set_debug_handler(failure_callback_t *callback); void i_get_failure_handlers(failure_callback_t **fatal_callback_r, failure_callback_t **error_callback_r, failure_callback_t **info_callback_r, failure_callback_t **debug_callback_r); /* Send failures to file. */ void default_fatal_handler(const struct failure_context *ctx, const char *format, va_list args) ATTR_NORETURN ATTR_FORMAT(2, 0); void default_error_handler(const struct failure_context *ctx, const char *format, va_list args) ATTR_FORMAT(2, 0); /* Send failures to syslog() */ void i_syslog_fatal_handler(const struct failure_context *ctx, const char *format, va_list args) ATTR_NORETURN ATTR_FORMAT(2, 0); void i_syslog_error_handler(const struct failure_context *ctx, const char *format, va_list args) ATTR_FORMAT(2, 0); /* Open syslog and set failure/info/debug handlers to use it. */ void i_set_failure_syslog(const char *ident, int options, int facility); /* Send failures to specified log file instead of stderr. */ void i_set_failure_file(const char *path, const char *prefix); /* Send errors to stderr using internal error protocol. */ void i_set_failure_internal(void); /* Returns TRUE if the given callback handler was set via i_set_failure_internal(). */ bool i_failure_handler_is_internal(failure_callback_t *const callback); /* If writing to log fails, ignore it instead of existing with FATAL_LOGWRITE or FATAL_LOGERROR. */ void i_set_failure_ignore_errors(bool ignore); /* Send informational messages to specified log file. i_set_failure_*() functions modify the info file too, so call this function after them. */ void i_set_info_file(const char *path); /* Send debug-level message to the given log file. The i_set_info_file() function modifies also the debug log file, so call this function after it. */ void i_set_debug_file(const char *path); /* Set the failure prefix. */ void i_set_failure_prefix(const char *prefix_fmt, ...) ATTR_FORMAT(1, 2); /* Set prefix to "". */ void i_unset_failure_prefix(void); /* Returns the current failure prefix (never NULL). */ const char *i_get_failure_prefix(void); /* Prefix failures with a timestamp. fmt is in strftime() format. */ void i_set_failure_timestamp_format(const char *fmt); /* When logging with internal error protocol, update the process's current IP address / log prefix by sending it to log process. This is mainly used to improve the error message if the process crashes. */ void i_set_failure_send_ip(const struct ip_addr *ip); void i_set_failure_send_prefix(const char *prefix); /* Call the callback before exit()ing. The callback may update the status. */ void i_set_failure_exit_callback(void (*callback)(int *status)); /* Call the exit callback and exit() */ void failure_exit(int status) ATTR_NORETURN ATTR_COLD; /* Parse a line logged using internal failure handler */ void i_failure_parse_line(const char *line, struct failure_line *failure); void failures_deinit(void); #endif dovecot-2.3.21.1/src/lib/home-expand.c0000644000000000000000000000234514656633576014227 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ipwd.h" #include "home-expand.h" int home_try_expand(const char **_path) { const char *path = *_path; const char *name, *home, *p; struct passwd pw; if (path == NULL || *path != '~') return 0; path++; if (*path == '/' || *path == '\0') { home = getenv("HOME"); if (*path != '\0') path++; } else { p = strchr(path, '/'); if (p == NULL) { name = path; path = ""; } else { name = t_strdup_until(path, p); path = p+1; } switch (i_getpwnam(name, &pw)) { case -1: i_error("getpwnam(%s) failed: %m", name); home = NULL; break; case 0: home = NULL; break; default: home = pw.pw_dir; break; } } if (home == NULL) return -1; if (*path == '\0') *_path = t_strdup(home); else *_path = t_strconcat(home, "/", path, NULL); return 0; } const char *home_expand(const char *path) { (void)home_try_expand(&path); return path; } const char *home_expand_tilde(const char *path, const char *home) { if (path == NULL || *path != '~') return path; if (path[1] == '\0') return home; if (path[1] != '/') return path; /* ~/ used */ return t_strconcat(home, path + 1, NULL); } dovecot-2.3.21.1/src/lib/imem.h0000644000000000000000000000300114656633576012744 00000000000000#ifndef IMEM_H #define IMEM_H /* For easy allocation of memory from default memory pool. */ extern pool_t default_pool; #define i_new(type, count) p_new(default_pool, type, count) #define i_realloc_type(mem, type, old_count, new_count) \ p_realloc_type(default_pool, mem, type, old_count, new_count) void *i_malloc(size_t size) ATTR_MALLOC ATTR_RETURNS_NONNULL; void *i_realloc(void *mem, size_t old_size, size_t new_size) ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL; /* i_free() and i_free_and_null() are now guaranteed to both set mem=NULL, so either one of them can be used. */ #ifndef STATIC_CHECKER # define i_free(mem) p_free_and_null(default_pool, mem) #else # define i_free(mem) \ STMT_START { \ pool_system_free(default_pool, mem); \ (mem) = NULL; \ } STMT_END #endif #define i_free_and_null(mem) i_free(mem) /* string functions */ char *i_strdup(const char *str) ATTR_MALLOC; void *i_memdup(const void *data, size_t size) ATTR_MALLOC; /* like i_strdup(), but if str == "", return NULL */ char *i_strdup_empty(const char *str) ATTR_MALLOC; /* *end isn't included */ char *i_strdup_until(const void *str, const void *end) ATTR_MALLOC ATTR_RETURNS_NONNULL; char *i_strndup(const void *str, size_t max_chars) ATTR_MALLOC; char *i_strdup_printf(const char *format, ...) ATTR_FORMAT(1, 2) ATTR_MALLOC ATTR_RETURNS_NONNULL; char *i_strdup_vprintf(const char *format, va_list args) ATTR_FORMAT(1, 0) ATTR_MALLOC ATTR_RETURNS_NONNULL; char *i_strconcat(const char *str1, ...) ATTR_SENTINEL ATTR_MALLOC; #endif dovecot-2.3.21.1/src/lib/safe-mkdir.h0000644000000000000000000000057314656633576014052 00000000000000#ifndef SAFE_MKDIR_H #define SAFE_MKDIR_H /* Either create a directory or make sure that it already exists with given permissions. If anything fails, the i_fatal() is called. Returns 1 if directory was created, 2 if it already existed with correct permissions, 0 if we changed permissions. */ int safe_mkdir(const char *dir, mode_t mode, uid_t uid, gid_t gid); #endif dovecot-2.3.21.1/src/lib/net.c0000644000000000000000000006452014656633576012613 00000000000000/* Copyright (c) 1999-2018 Dovecot authors, see the included COPYING file */ #define _GNU_SOURCE /* For Linux's struct ucred */ #include "lib.h" #include "time-util.h" #include "net.h" #include #include #include #include #include #if defined(HAVE_UCRED_H) # include /* for getpeerucred() */ #elif defined(HAVE_SYS_UCRED_H) # include /* for FreeBSD struct xucred */ #endif union sockaddr_union { struct sockaddr sa; struct sockaddr_in sin; struct sockaddr_in6 sin6; }; union sockaddr_union_unix { struct sockaddr sa; struct sockaddr_un un; }; #define SIZEOF_SOCKADDR(so) ((so).sa.sa_family == AF_INET6 ? \ sizeof(so.sin6) : sizeof(so.sin)) #if !defined(HAVE_GETPEEREID) && !defined(SO_PEERCRED) && !defined(HAVE_GETPEERUCRED) && defined(MSG_WAITALL) && defined(LOCAL_CREDS) # define NEEDS_LOCAL_CREDS 1 #else # undef NEEDS_LOCAL_CREDS #endif /* If connect() fails with EADDRNOTAVAIL (or some others on FreeBSD), retry it this many times. This is needed on busy systems kernel may assign the same source port to two sockets at bind() stage, which is what we generally want to allow more than 64k outgoing connections to different destinations. However, at bind() stage the kernel doesn't know the destination yet. So it's possible that it assigns the same source port to two (or more) sockets that have the same destination IP+port as well. In this case connect() will fail with EADDRNOTAVAIL. We'll need to retry this and hope that the next attempt won't conflict. */ #define MAX_CONNECT_RETRIES 20 bool net_ip_compare(const struct ip_addr *ip1, const struct ip_addr *ip2) { return net_ip_cmp(ip1, ip2) == 0; } int net_ip_cmp(const struct ip_addr *ip1, const struct ip_addr *ip2) { if (ip1->family != ip2->family) return ip1->family - ip2->family; switch (ip1->family) { case AF_INET6: return memcmp(&ip1->u.ip6, &ip2->u.ip6, sizeof(ip1->u.ip6)); case AF_INET: return memcmp(&ip1->u.ip4, &ip2->u.ip4, sizeof(ip1->u.ip4)); default: break; } return 0; } unsigned int net_ip_hash(const struct ip_addr *ip) { const unsigned char *p; unsigned int len, g, h = 0; if (ip->family == AF_INET6) { p = ip->u.ip6.s6_addr; len = sizeof(ip->u.ip6); } else { return ip->u.ip4.s_addr; } for (; len > 0; len--, p++) { h = (h << 4) + *p; if ((g = h & 0xf0000000UL) != 0) { h = h ^ (g >> 24); h = h ^ g; } } return h; } /* copy IP to sockaddr */ static inline void sin_set_ip(union sockaddr_union *so, const struct ip_addr *ip) { if (ip == NULL) { so->sin6.sin6_family = AF_INET6; so->sin6.sin6_addr = in6addr_any; return; } so->sin.sin_family = ip->family; if (ip->family == AF_INET6) memcpy(&so->sin6.sin6_addr, &ip->u.ip6, sizeof(ip->u.ip6)); else memcpy(&so->sin.sin_addr, &ip->u.ip4, sizeof(ip->u.ip4)); } static inline void sin_get_ip(const union sockaddr_union *so, struct ip_addr *ip) { /* IP structs may be sent across processes. Clear the whole struct first to make sure it won't leak any data across processes. */ i_zero(ip); ip->family = so->sin.sin_family; if (ip->family == AF_INET6) memcpy(&ip->u.ip6, &so->sin6.sin6_addr, sizeof(ip->u.ip6)); else if (ip->family == AF_INET) memcpy(&ip->u.ip4, &so->sin.sin_addr, sizeof(ip->u.ip4)); else i_zero(&ip->u); } static inline void sin_set_port(union sockaddr_union *so, in_port_t port) { if (so->sin.sin_family == AF_INET6) so->sin6.sin6_port = htons(port); else so->sin.sin_port = htons(port); } static inline in_port_t sin_get_port(union sockaddr_union *so) { if (so->sin.sin_family == AF_INET6) return ntohs(so->sin6.sin6_port); if (so->sin.sin_family == AF_INET) return ntohs(so->sin.sin_port); return 0; } static int net_connect_ip_once(const struct ip_addr *ip, in_port_t port, const struct ip_addr *my_ip, int sock_type, bool blocking) { union sockaddr_union so; int fd, ret, opt = 1; if (my_ip != NULL && ip->family != my_ip->family) { i_warning("net_connect_ip(): ip->family != my_ip->family"); my_ip = NULL; } /* create the socket */ i_zero(&so); so.sin.sin_family = ip->family; fd = socket(ip->family, sock_type, 0); if (fd == -1) { i_error("socket() failed: %m"); return -1; } /* set socket options */ (void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); if (sock_type == SOCK_STREAM) (void)setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt)); if (!blocking) net_set_nonblock(fd, TRUE); /* set our own address */ if (my_ip != NULL) { sin_set_ip(&so, my_ip); if (bind(fd, &so.sa, SIZEOF_SOCKADDR(so)) == -1) { i_error("bind(%s) failed: %m", net_ip2addr(my_ip)); i_close_fd(&fd); return -1; } } /* connect */ sin_set_ip(&so, ip); sin_set_port(&so, port); ret = connect(fd, &so.sa, SIZEOF_SOCKADDR(so)); #ifndef WIN32 if (ret < 0 && errno != EINPROGRESS) #else if (ret < 0 && WSAGetLastError() != WSAEWOULDBLOCK) #endif { i_close_fd(&fd); return -1; } return fd; } static int net_connect_ip_full(const struct ip_addr *ip, in_port_t port, const struct ip_addr *my_ip, int sock_type, bool blocking) { int fd, try; for (try = 0;;) { fd = net_connect_ip_once(ip, port, my_ip, sock_type, blocking); if (fd != -1 || try++ >= MAX_CONNECT_RETRIES || (errno != EADDRNOTAVAIL #ifdef __FreeBSD__ /* busy */ && errno != EADDRINUSE /* pf may cause this if another connection used the same port recently */ && errno != EACCES #endif )) break; } return fd; } int net_connect_ip(const struct ip_addr *ip, in_port_t port, const struct ip_addr *my_ip) { return net_connect_ip_full(ip, port, my_ip, SOCK_STREAM, FALSE); } int net_connect_ip_blocking(const struct ip_addr *ip, in_port_t port, const struct ip_addr *my_ip) { return net_connect_ip_full(ip, port, my_ip, SOCK_STREAM, TRUE); } int net_connect_udp(const struct ip_addr *ip, in_port_t port, const struct ip_addr *my_ip) { return net_connect_ip_full(ip, port, my_ip, SOCK_DGRAM, FALSE); } int net_try_bind(const struct ip_addr *ip) { union sockaddr_union so; int fd; /* create the socket */ i_zero(&so); so.sin.sin_family = ip->family; fd = socket(ip->family, SOCK_STREAM, 0); if (fd == -1) { i_error("socket() failed: %m"); return -1; } sin_set_ip(&so, ip); if (bind(fd, &so.sa, SIZEOF_SOCKADDR(so)) == -1) { i_close_fd(&fd); return -1; } i_close_fd(&fd); return 0; } int net_connect_unix(const char *path) { union sockaddr_union_unix sa; int fd, ret; i_zero(&sa); sa.un.sun_family = AF_UNIX; if (i_strocpy(sa.un.sun_path, path, sizeof(sa.un.sun_path)) < 0) { /* too long path */ #ifdef ENAMETOOLONG errno = ENAMETOOLONG; #else errno = EOVERFLOW; #endif return -1; } /* create the socket */ fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd == -1) { i_error("socket(%s) failed: %m", path); return -1; } net_set_nonblock(fd, TRUE); /* connect */ ret = connect(fd, &sa.sa, sizeof(sa)); if (ret < 0 && errno != EINPROGRESS) { i_close_fd(&fd); return -1; } #ifdef NEEDS_LOCAL_CREDS { int on = 1; if (setsockopt(fd, 0, LOCAL_CREDS, &on, sizeof on)) { i_error("setsockopt(LOCAL_CREDS) failed: %m"); return -1; } } #endif return fd; } int net_connect_unix_with_retries(const char *path, unsigned int msecs) { struct timeval start, now; int fd; i_gettimeofday(&start); do { fd = net_connect_unix(path); if (fd != -1 || (errno != EAGAIN && errno != ECONNREFUSED)) break; /* busy. wait for a while. */ usleep(i_rand_minmax(1, 10) * 10000); i_gettimeofday(&now); } while (timeval_diff_msecs(&now, &start) < (int)msecs); return fd; } void net_disconnect(int fd) { /* FreeBSD's close() fails with ECONNRESET if socket still has unsent data in transmit buffer. We don't care. */ if (close(fd) < 0 && errno != ECONNRESET) i_error("net_disconnect() failed: %m"); } void net_set_nonblock(int fd, bool nonblock) { fd_set_nonblock(fd, nonblock); } int net_set_cork(int fd ATTR_UNUSED, bool cork ATTR_UNUSED) { #ifdef TCP_CORK int val = cork; return setsockopt(fd, IPPROTO_TCP, TCP_CORK, &val, sizeof(val)); #else errno = ENOPROTOOPT; return -1; #endif } int net_set_tcp_nodelay(int fd, bool nodelay) { int val = nodelay; return setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)); } int net_set_tcp_quickack(int fd ATTR_UNUSED, bool quickack ATTR_UNUSED) { #ifdef TCP_QUICKACK int val = quickack; return setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &val, sizeof(val)); #else errno = ENOPROTOOPT; return -1; #endif } int net_set_send_buffer_size(int fd, size_t size) { int opt; if (size > INT_MAX) { errno = EINVAL; return -1; } opt = (int)size; return setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)); } int net_set_recv_buffer_size(int fd, size_t size) { int opt; if (size > INT_MAX) { errno = EINVAL; return -1; } opt = (int)size; return setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)); } const struct ip_addr net_ip4_any = { .family = AF_INET, .u.ip4.s_addr = INADDR_ANY }; const struct ip_addr net_ip6_any = { .family = AF_INET6, .u.ip6 = IN6ADDR_ANY_INIT }; const struct ip_addr net_ip4_loopback = { .family = AF_INET, .u.ip4.s_addr = INADDR_LOOPBACK }; const struct ip_addr net_ip6_loopback = { .family = AF_INET6, .u.ip6 = IN6ADDR_LOOPBACK_INIT }; int net_listen(const struct ip_addr *my_ip, in_port_t *port, int backlog) { enum net_listen_flags flags = 0; return net_listen_full(my_ip, port, &flags, backlog); } int net_listen_full(const struct ip_addr *my_ip, in_port_t *port, enum net_listen_flags *flags, int backlog) { union sockaddr_union so; int ret, fd, opt = 1; socklen_t len; i_zero(&so); sin_set_port(&so, *port); sin_set_ip(&so, my_ip); /* create the socket */ fd = socket(so.sin.sin_family, SOCK_STREAM, 0); if (fd == -1 && my_ip == NULL && (errno == EINVAL || errno == EAFNOSUPPORT)) { /* IPv6 is not supported by OS */ so.sin.sin_family = AF_INET; so.sin.sin_addr.s_addr = INADDR_ANY; fd = socket(AF_INET, SOCK_STREAM, 0); } if (fd == -1) { i_error("socket() failed: %m"); return -1; } /* set socket options */ (void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); (void)setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt)); if ((*flags & NET_LISTEN_FLAG_REUSEPORT) != 0) { #ifdef SO_REUSEPORT if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)) < 0) #endif *flags &= ENUM_NEGATE(NET_LISTEN_FLAG_REUSEPORT); } /* If using IPv6, bind only to IPv6 if possible. This avoids ambiguities with IPv4-mapped IPv6 addresses. */ #ifdef IPV6_V6ONLY if (so.sin.sin_family == AF_INET6) { opt = 1; (void)setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); } #endif /* specify the address/port we want to listen in */ ret = bind(fd, &so.sa, SIZEOF_SOCKADDR(so)); if (ret < 0) { if (errno != EADDRINUSE) { i_error("bind(%s, %u) failed: %m", my_ip == NULL ? "" : net_ip2addr(my_ip), *port); } } else { /* get the actual port we started listen */ len = SIZEOF_SOCKADDR(so); ret = getsockname(fd, &so.sa, &len); if (ret >= 0) { *port = sin_get_port(&so); /* start listening */ if (listen(fd, backlog) >= 0) return fd; if (errno != EADDRINUSE) i_error("listen() failed: %m"); } } /* error */ i_close_fd(&fd); return -1; } int net_listen_unix(const char *path, int backlog) { union { struct sockaddr sa; struct sockaddr_un un; } sa; int fd; i_zero(&sa); sa.un.sun_family = AF_UNIX; if (i_strocpy(sa.un.sun_path, path, sizeof(sa.un.sun_path)) < 0) { /* too long path */ #ifdef ENAMETOOLONG errno = ENAMETOOLONG; #else errno = EOVERFLOW; #endif return -1; } /* create the socket */ fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd == -1) { i_error("socket() failed: %m"); return -1; } #ifdef NEEDS_LOCAL_CREDS { int on = 1; if (setsockopt(fd, 0, LOCAL_CREDS, &on, sizeof on)) { i_error("setsockopt(LOCAL_CREDS) failed: %m"); return -1; } } #endif /* bind */ if (bind(fd, &sa.sa, sizeof(sa)) < 0) { if (errno != EADDRINUSE) i_error("bind(%s) failed: %m", path); } else { /* start listening */ if (listen(fd, backlog) == 0) return fd; if (errno != EADDRINUSE) i_error("listen() failed: %m"); } i_close_fd(&fd); return -1; } int net_listen_unix_unlink_stale(const char *path, int backlog) { unsigned int i = 0; int fd; while ((fd = net_listen_unix(path, backlog)) == -1) { if (errno != EADDRINUSE || ++i == 2) return -1; /* see if it really exists */ fd = net_connect_unix(path); if (fd != -1 || errno != ECONNREFUSED) { i_close_fd(&fd); errno = EADDRINUSE; return -1; } /* delete and try again */ if (i_unlink_if_exists(path) < 0) { errno = EADDRINUSE; return -1; } } return fd; } int net_accept(int fd, struct ip_addr *addr_r, in_port_t *port_r) { union sockaddr_union so; int ret; socklen_t addrlen; i_assert(fd >= 0); i_zero(&so); addrlen = sizeof(so); ret = accept(fd, &so.sa, &addrlen); if (ret < 0) { if (errno == EAGAIN || errno == ECONNABORTED) return -1; else return -2; } if (so.sin.sin_family == AF_UNIX) { if (addr_r != NULL) i_zero(addr_r); if (port_r != NULL) *port_r = 0; } else { if (addr_r != NULL) sin_get_ip(&so, addr_r); if (port_r != NULL) *port_r = sin_get_port(&so); } return ret; } ssize_t net_receive(int fd, void *buf, size_t len) { ssize_t ret; i_assert(fd >= 0); i_assert(len <= SSIZE_T_MAX); ret = read(fd, buf, len); if (ret == 0) { /* disconnected */ errno = 0; return -2; } if (unlikely(ret < 0)) { if (errno == EINTR || errno == EAGAIN) return 0; if (errno == ECONNRESET || errno == ETIMEDOUT) { /* treat as disconnection */ return -2; } } return ret; } int net_gethostbyname(const char *addr, struct ip_addr **ips, unsigned int *ips_count) { /* @UNSAFE */ union sockaddr_union *so; struct addrinfo hints, *ai, *origai; struct ip_addr ip; int host_error; int count; *ips = NULL; *ips_count = 0; /* support [ipv6] style addresses here so they work globally */ if (addr[0] == '[' && net_addr2ip(addr, &ip) == 0) { *ips_count = 1; *ips = t_new(struct ip_addr, 1); **ips = ip; return 0; } i_zero(&hints); hints.ai_socktype = SOCK_STREAM; /* save error to host_error for later use */ host_error = getaddrinfo(addr, NULL, &hints, &ai); if (host_error != 0) return host_error; /* get number of IPs */ origai = ai; for (count = 0; ai != NULL; ai = ai->ai_next) count++; *ips_count = count; *ips = t_new(struct ip_addr, count); count = 0; for (ai = origai; ai != NULL; ai = ai->ai_next, count++) { so = (union sockaddr_union *) ai->ai_addr; sin_get_ip(so, &(*ips)[count]); } freeaddrinfo(origai); return 0; } int net_gethostbyaddr(const struct ip_addr *ip, const char **name_r) { union sockaddr_union so; socklen_t addrlen = sizeof(so); char hbuf[NI_MAXHOST]; int ret; i_zero(&so); sin_set_ip(&so, ip); ret = getnameinfo(&so.sa, addrlen, hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD); if (ret != 0) return ret; *name_r = t_strdup(hbuf); return 0; } int net_getsockname(int fd, struct ip_addr *addr, in_port_t *port) { union sockaddr_union so; socklen_t addrlen; i_assert(fd >= 0); i_zero(&so); addrlen = sizeof(so); if (getsockname(fd, &so.sa, &addrlen) == -1) return -1; if (so.sin.sin_family == AF_UNIX) { if (addr != NULL) i_zero(addr); if (port != NULL) *port = 0; } else { if (addr != NULL) sin_get_ip(&so, addr); if (port != NULL) *port = sin_get_port(&so); } return 0; } int net_getpeername(int fd, struct ip_addr *addr, in_port_t *port) { union sockaddr_union so; socklen_t addrlen; i_assert(fd >= 0); i_zero(&so); addrlen = sizeof(so); if (getpeername(fd, &so.sa, &addrlen) == -1) return -1; if (so.sin.sin_family == AF_UNIX) { if (addr != NULL) i_zero(addr); if (port != NULL) *port = 0; } else { if (addr != NULL) sin_get_ip(&so, addr); if (port != NULL) *port = sin_get_port(&so); } return 0; } int net_getunixname(int fd, const char **name_r) { union sockaddr_union_unix so; socklen_t addrlen = sizeof(so); i_zero(&so); if (getsockname(fd, &so.sa, &addrlen) < 0) return -1; if (so.un.sun_family != AF_UNIX) { errno = ENOTSOCK; return -1; } *name_r = t_strdup(so.un.sun_path); return 0; } int net_getunixcred(int fd, struct net_unix_cred *cred_r) { #if defined(SO_PEERCRED) # if defined(HAVE_STRUCT_SOCKPEERCRED) /* OpenBSD (may also provide getpeereid, but we also want pid) */ struct sockpeercred ucred; # else /* Linux */ struct ucred ucred; # endif socklen_t len = sizeof(ucred); if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) < 0) { i_error("getsockopt(SO_PEERCRED) failed: %m"); return -1; } cred_r->uid = ucred.uid; cred_r->gid = ucred.gid; cred_r->pid = ucred.pid; return 0; #elif defined(LOCAL_PEEREID) /* NetBSD (may also provide getpeereid, but we also want pid) */ struct unpcbid ucred; socklen_t len = sizeof(ucred); if (getsockopt(fd, 0, LOCAL_PEEREID, &ucred, &len) < 0) { i_error("getsockopt(LOCAL_PEEREID) failed: %m"); return -1; } cred_r->uid = ucred.unp_euid; cred_r->gid = ucred.unp_egid; cred_r->pid = ucred.unp_pid; return 0; #elif defined(HAVE_GETPEEREID) /* OSX 10.4+, FreeBSD 4.6+, OpenBSD 3.0+, NetBSD 5.0+ */ if (getpeereid(fd, &cred_r->uid, &cred_r->gid) < 0) { i_error("getpeereid() failed: %m"); return -1; } cred_r->pid = (pid_t)-1; return 0; #elif defined(LOCAL_PEERCRED) /* Older FreeBSD */ struct xucred ucred; socklen_t len = sizeof(ucred); if (getsockopt(fd, 0, LOCAL_PEERCRED, &ucred, &len) < 0) { i_error("getsockopt(LOCAL_PEERCRED) failed: %m"); return -1; } if (ucred.cr_version != XUCRED_VERSION) { errno = EINVAL; return -1; } cred_r->uid = ucred.cr_uid; cred_r->gid = ucred.cr_gid; cred_r->pid = (pid_t)-1; return 0; #elif defined(HAVE_GETPEERUCRED) /* Solaris */ ucred_t *ucred = NULL; if (getpeerucred(fd, &ucred) < 0) { i_error("getpeerucred() failed: %m"); return -1; } cred_r->uid = ucred_geteuid(ucred); cred_r->gid = ucred_getrgid(ucred); cred_r->pid = ucred_getpid(ucred); ucred_free(ucred); if (cred_r->uid == (uid_t)-1 || cred_r->gid == (gid_t)-1) { errno = EINVAL; return -1; } return 0; #elif defined(NEEDS_LOCAL_CREDS) /* NetBSD < 5 */ int i, n, on; struct iovec iov; struct msghdr msg; struct { struct cmsghdr ch; char buf[110]; } cdata; struct sockcred *sc; iov.iov_base = (char *)&on; iov.iov_len = 1; sc = (struct sockcred *)cdata.buf; sc->sc_uid = sc->sc_euid = sc->sc_gid = sc->sc_egid = -1; i_zero(&cdata.ch); i_zero(&msg); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = &cdata; msg.msg_controllen = sizeof(cdata.ch) + sizeof(cdata.buf); for (i = 0; i < 10; i++) { n = recvmsg(fd, &msg, MSG_WAITALL | MSG_PEEK); if (n >= 0 || errno != EAGAIN) break; usleep(100); } if (n < 0) { i_error("recvmsg() failed: %m"); return -1; } cred_r->uid = sc->sc_euid; cred_r->gid = sc->sc_egid; cred_r->pid = (pid_t)-1; return 0; #else errno = EINVAL; return -1; #endif } const char *net_ip2addr(const struct ip_addr *ip) { char *addr = t_malloc_no0(MAX_IP_LEN+1); if (inet_ntop(ip->family, &ip->u.ip6, addr, MAX_IP_LEN) == NULL) return ""; return addr; } static bool net_addr2ip_inet4_fast(const char *addr, struct ip_addr *ip) { uint8_t *saddr = (void *)&ip->u.ip4.s_addr; unsigned int i, num; if (str_parse_uint(addr, &num, &addr) < 0) return FALSE; if (*addr == '\0' && num <= 0xffffffff) { /* single-number IPv4 address */ ip->u.ip4.s_addr = htonl(num); ip->family = AF_INET; return TRUE; } /* try to parse as a.b.c.d */ i = 0; for (;;) { if (num >= 256) return FALSE; saddr[i] = num; if (i == 3) break; i++; if (*addr != '.') return FALSE; addr++; if (str_parse_uint(addr, &num, &addr) < 0) return FALSE; } if (*addr != '\0') return FALSE; ip->family = AF_INET; return TRUE; } int net_addr2ip(const char *addr, struct ip_addr *ip) { int ret; if (net_addr2ip_inet4_fast(addr, ip)) return 0; if (strchr(addr, ':') != NULL) { /* IPv6 */ T_BEGIN { if (addr[0] == '[') { /* allow [ipv6 addr] */ size_t len = strlen(addr); if (addr[len-1] == ']') addr = t_strndup(addr+1, len-2); } ret = inet_pton(AF_INET6, addr, &ip->u.ip6); } T_END; if (ret == 0) return -1; ip->family = AF_INET6; } else { /* IPv4 */ if (inet_aton(addr, &ip->u.ip4) == 0) return -1; ip->family = AF_INET; } return 0; } int net_str2port(const char *str, in_port_t *port_r) { uintmax_t l; if (str_to_uintmax(str, &l) < 0) return -1; if (l == 0 || l > (in_port_t)-1) return -1; *port_r = (in_port_t)l; return 0; } int net_str2port_zero(const char *str, in_port_t *port_r) { uintmax_t l; if (str_to_uintmax(str, &l) < 0) return -1; if (l > (in_port_t)-1) return -1; *port_r = (in_port_t)l; return 0; } int net_str2hostport(const char *str, in_port_t default_port, const char **host_r, in_port_t *port_r) { const char *p, *host; in_port_t port; if (str[0] == '[') { /* [IPv6] address, possibly followed by :port */ p = strchr(str, ']'); if (p == NULL) return -1; host = t_strdup_until(str+1, p++); } else { p = strchr(str, ':'); if (p == NULL || strchr(p+1, ':') != NULL) { /* host or IPv6 address */ *host_r = str; *port_r = default_port; return 0; } host = t_strdup_until(str, p); } if (p[0] == '\0') { *host_r = host; *port_r = default_port; return 0; } if (p[0] != ':') return -1; if (net_str2port(p+1, &port) < 0) return -1; *host_r = host; *port_r = port; return 0; } int net_ipport2str(const struct ip_addr *ip, in_port_t port, const char **str_r) { if (!IPADDR_IS_V4(ip) && !IPADDR_IS_V6(ip)) return -1; *str_r = t_strdup_printf("%s%s%s:%u", IPADDR_IS_V6(ip) ? "[" : "", net_ip2addr(ip), IPADDR_IS_V6(ip) ? "]" : "", port); return 0; } int net_ipv6_mapped_ipv4_convert(const struct ip_addr *src, struct ip_addr *dest) { static uint8_t v4_prefix[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff }; if (!IPADDR_IS_V6(src)) return -1; if (memcmp(src->u.ip6.s6_addr, v4_prefix, sizeof(v4_prefix)) != 0) return -1; i_zero(dest); dest->family = AF_INET; memcpy(&dest->u.ip6, &src->u.ip6.s6_addr[3*4], 4); return 0; } int net_geterror(int fd) { int data; socklen_t len = sizeof(data); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &data, &len) == -1) { /* we're now really returning the getsockopt()'s error code instead of the socket's, but normally we should never get here anyway. */ return errno; } return data; } const char *net_gethosterror(int error) { i_assert(error != 0); return gai_strerror(error); } enum net_hosterror_type net_get_hosterror_type(int error) { const struct { int error; enum net_hosterror_type type; } error_map[] = { #ifdef EAI_ADDRFAMILY /* Obsoleted by RFC 2553bis-02 */ { EAI_ADDRFAMILY, NET_HOSTERROR_TYPE_NOT_FOUND }, #endif { EAI_AGAIN, NET_HOSTERROR_TYPE_NAMESERVER }, { EAI_BADFLAGS, NET_HOSTERROR_TYPE_INTERNAL_ERROR }, { EAI_FAIL, NET_HOSTERROR_TYPE_NAMESERVER }, { EAI_FAMILY, NET_HOSTERROR_TYPE_INTERNAL_ERROR }, { EAI_MEMORY, NET_HOSTERROR_TYPE_INTERNAL_ERROR }, #ifdef EAI_NODATA /* Obsoleted by RFC 2553bis-02 */ { EAI_NODATA, NET_HOSTERROR_TYPE_NOT_FOUND }, #endif { EAI_NONAME, NET_HOSTERROR_TYPE_NOT_FOUND }, { EAI_SERVICE, NET_HOSTERROR_TYPE_INTERNAL_ERROR }, { EAI_SOCKTYPE, NET_HOSTERROR_TYPE_INTERNAL_ERROR }, { EAI_SYSTEM, NET_HOSTERROR_TYPE_INTERNAL_ERROR }, }; for (unsigned int i = 0; i < N_ELEMENTS(error_map); i++) { if (error_map[i].error == error) return error_map[i].type; } /* shouldn't happen? assume internal error */ return NET_HOSTERROR_TYPE_INTERNAL_ERROR; } int net_hosterror_notfound(int error) { #ifdef EAI_NODATA /* NODATA is depricated */ return (error != 1 && (error == EAI_NONAME || error == EAI_NODATA)) ? 1 : 0; #else return (error != 1 && (error == EAI_NONAME)) ? 1 : 0; #endif } const char *net_getservbyport(in_port_t port) { struct servent *entry; entry = getservbyport(htons(port), "tcp"); return entry == NULL ? NULL : entry->s_name; } bool is_ipv4_address(const char *addr) { while (*addr != '\0') { if (*addr != '.' && !i_isdigit(*addr)) return FALSE; addr++; } return TRUE; } bool is_ipv6_address(const char *addr) { bool have_prefix = FALSE; if (*addr == '[') { have_prefix = TRUE; addr++; } while (*addr != '\0') { if (*addr != ':' && !i_isxdigit(*addr)) { if (have_prefix && *addr == ']' && addr[1] == '\0') break; return FALSE; } addr++; } return TRUE; } int net_parse_range(const char *network, struct ip_addr *ip_r, unsigned int *bits_r) { const char *p; unsigned int bits, max_bits; p = strchr(network, '/'); if (p != NULL) network = t_strdup_until(network, p++); if (net_addr2ip(network, ip_r) < 0) return -1; max_bits = IPADDR_BITS(ip_r); if (p == NULL) { /* full IP address must match */ bits = max_bits; } else { /* get the network mask */ if (str_to_uint(p, &bits) < 0 || bits > max_bits) return -1; } *bits_r = bits; return 0; } bool net_is_in_network(const struct ip_addr *ip, const struct ip_addr *net_ip, unsigned int bits) { struct ip_addr tmp_ip; const uint32_t *ip1, *ip2; uint32_t mask, i1, i2; unsigned int pos, i; if (net_ipv6_mapped_ipv4_convert(ip, &tmp_ip) == 0) { /* IPv4 address mapped disguised as IPv6 address */ ip = &tmp_ip; } if (ip->family == 0 || net_ip->family == 0) { /* non-IPv4/IPv6 address (e.g. UNIX socket) never matches anything */ return FALSE; } if (IPADDR_IS_V4(ip) != IPADDR_IS_V4(net_ip)) { /* one is IPv6 and one is IPv4 */ return FALSE; } i_assert(IPADDR_IS_V6(ip) == IPADDR_IS_V6(net_ip)); if (IPADDR_IS_V4(ip)) { ip1 = &ip->u.ip4.s_addr; ip2 = &net_ip->u.ip4.s_addr; } else { ip1 = (const void *)&ip->u.ip6; ip2 = (const void *)&net_ip->u.ip6; } /* check first the full 32bit ints */ for (pos = 0, i = 0; pos + 32 <= bits; pos += 32, i++) { if (ip1[i] != ip2[i]) return FALSE; } i1 = htonl(ip1[i]); i2 = htonl(ip2[i]); /* check the last full bytes */ for (mask = 0xff000000; pos + 8 <= bits; pos += 8, mask >>= 8) { if ((i1 & mask) != (i2 & mask)) return FALSE; } /* check the last bits, they're reversed in bytes */ bits -= pos; for (mask = 0x80000000 >> (pos % 32); bits > 0; bits--, mask >>= 1) { if ((i1 & mask) != (i2 & mask)) return FALSE; } return TRUE; } dovecot-2.3.21.1/src/lib/backtrace-string.c0000644000000000000000000000666614656633576015257 00000000000000/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "backtrace-string.h" #define MAX_STACK_SIZE 30 #define BACKTRACE_SKIP_PREFIX "backtrace_" #if defined(HAVE_LIBUNWIND) #include static int backtrace_append_unwind(string_t *str) { size_t str_orig_size = str_len(str); char proc_name[256]; int ret; unsigned int fp = 0; unw_cursor_t c; unw_context_t ctx; unw_proc_info_t pip; bool success = FALSE; if ((ret = unw_getcontext(&ctx)) != 0) { str_printfa(str, "unw_getcontext() failed: %d", ret); return -1; } if ((ret = unw_init_local(&c, &ctx)) != 0) { str_printfa(str, "unw_init_local() failed: %d", ret); return -1; } do { str_printfa(str, "#%d ", fp); if ((ret = unw_get_proc_info(&c, &pip)) != 0) { str_printfa(str, "[unw_get_proc_info_failed(): %d]", ret); } else if (pip.start_ip == 0 || pip.end_ip == 0) { str_append(str, "[no start/end information]"); } else if ((ret = unw_get_proc_name(&c, proc_name, sizeof(proc_name), 0)) != 0 && ret != UNW_ENOMEM) { str_printfa(str, "[unw_get_proc_name() failed: %d]", ret); } else if (!success && str_begins(proc_name, BACKTRACE_SKIP_PREFIX)) { str_truncate(str, str_orig_size); continue; } else { str_append_max(str, proc_name, sizeof(proc_name)); str_printfa(str, "[0x%08zx]", pip.start_ip); success = TRUE; } str_append(str, " -> "); fp++; } while ((ret = unw_step(&c)) > 0); /* remove ' -> ' */ if (str->used > 4) str_truncate(str, str->used - 4); return ret == 0 && success ? 0 : -1; } #endif #if defined(HAVE_BACKTRACE_SYMBOLS) && defined(HAVE_EXECINFO_H) /* Linux */ #include static int backtrace_append_libc(string_t *str) { size_t str_orig_size = str_len(str); void *stack[MAX_STACK_SIZE]; char **strings; int ret, i; ret = backtrace(stack, N_ELEMENTS(stack)); if (ret <= 0) return -1; strings = backtrace_symbols(stack, ret); for (i = 0; i < ret; i++) { if (str_len(str) > str_orig_size) str_append(str, " -> "); if (strings == NULL) { /* out of memory case */ str_printfa(str, "0x%p", stack[i]); } else if (str_len(str) != str_orig_size || !str_begins(strings[i], BACKTRACE_SKIP_PREFIX)) str_append(str, strings[i]); } free(strings); return 0; } #elif defined(HAVE_WALKCONTEXT) && defined(HAVE_UCONTEXT_H) /* Solaris */ #include struct walk_context { string_t *str; unsigned int pos; }; static int walk_callback(uintptr_t ptr, int signo ATTR_UNUSED, void *context) { struct walk_context *ctx = context; if (ctx->pos > 0) str_append(ctx->str, " -> "); str_printfa(ctx->str, "0x%p", (void *)ptr); ctx->pos++; return 0; } static int backtrace_append_libc(string_t *str) { ucontext_t uc; struct walk_context ctx; if (getcontext(&uc) < 0) return -1; ctx.str = str; ctx.pos = 0; walkcontext(&uc, walk_callback, &ctx); return 0; } #else static int backtrace_append_libc(string_t *str ATTR_UNUSED) { return -1; } #endif int backtrace_append(string_t *str) { #if defined(HAVE_LIBUNWIND) size_t orig_len = str_len(str); if (backtrace_append_unwind(str) == 0) return 0; /* failed to get useful backtrace. libc's own method is likely better. */ str_truncate(str, orig_len); #endif return backtrace_append_libc(str); } int backtrace_get(const char **backtrace_r) { string_t *str; str = t_str_new(512); if (backtrace_append(str) < 0) return -1; *backtrace_r = str_c(str); return 0; } dovecot-2.3.21.1/src/lib/ioloop.c0000644000000000000000000010672414656633576013331 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "backtrace-string.h" #include "llist.h" #include "time-util.h" #include "istream-private.h" #include "ioloop-private.h" #include /* Dovecot attempts to detect also when time suddenly jumps forwards. This is done by getting the minimum timeout wait in epoll() (or similar) and then seeing if the current time after epoll() is past the timeout. This can't be very exact, so likely the difference is always at least 1 microsecond. In high load situations it can be somewhat higher. Dovecot generally doesn't have very important short timeouts, so to avoid logging many warnings about this, use a rather high value. */ #define IOLOOP_TIME_MOVED_FORWARDS_MIN_USECS (100000) time_t ioloop_time = 0; struct timeval ioloop_timeval; struct ioloop *current_ioloop = NULL; uint64_t ioloop_global_wait_usecs = 0; static ARRAY(io_switch_callback_t *) io_switch_callbacks = ARRAY_INIT; static ARRAY(io_destroy_callback_t *) io_destroy_callbacks = ARRAY_INIT; static bool panic_on_leak = FALSE, panic_on_leak_set = FALSE; static time_t data_stack_last_free_unused = 0; static void io_loop_initialize_handler(struct ioloop *ioloop) { unsigned int initial_fd_count; initial_fd_count = ioloop->max_fd_count > 0 && ioloop->max_fd_count < IOLOOP_INITIAL_FD_COUNT ? ioloop->max_fd_count : IOLOOP_INITIAL_FD_COUNT; io_loop_handler_init(ioloop, initial_fd_count); } static struct io_file * io_add_file(struct ioloop *ioloop, int fd, enum io_condition condition, const char *source_filename, unsigned int source_linenum, io_callback_t *callback, void *context) { struct io_file *io; i_assert(callback != NULL); i_assert((condition & IO_NOTIFY) == 0); io = i_new(struct io_file, 1); io->io.condition = condition; io->io.callback = callback; io->io.context = context; io->io.ioloop = ioloop; io->io.source_filename = source_filename; io->io.source_linenum = source_linenum; io->refcount = 1; io->fd = fd; if (io->io.ioloop->cur_ctx != NULL) { io->io.ctx = io->io.ioloop->cur_ctx; io_loop_context_ref(io->io.ctx); } if (io->io.ioloop->handler_context == NULL) io_loop_initialize_handler(io->io.ioloop); if (fd != -1) io_loop_handle_add(io); else { /* we're adding an istream whose only way to get notified is to call i_stream_set_input_pending() */ } if (io->io.ioloop->io_files != NULL) { io->io.ioloop->io_files->prev = io; io->next = io->io.ioloop->io_files; } io->io.ioloop->io_files = io; return io; } #undef io_add_to struct io *io_add_to(struct ioloop *ioloop, int fd, enum io_condition condition, const char *source_filename, unsigned int source_linenum, io_callback_t *callback, void *context) { struct io_file *io; i_assert(fd >= 0); io = io_add_file(ioloop, fd, condition, source_filename, source_linenum, callback, context); return &io->io; } #undef io_add struct io *io_add(int fd, enum io_condition condition, const char *source_filename, unsigned int source_linenum, io_callback_t *callback, void *context) { return io_add_to(current_ioloop, fd, condition, source_filename, source_linenum, callback, context); } #undef io_add_istream_to struct io *io_add_istream_to(struct ioloop *ioloop, struct istream *input, const char *source_filename, unsigned int source_linenum, io_callback_t *callback, void *context) { struct io_file *io; io = io_add_file(ioloop, i_stream_get_fd(input), IO_READ, source_filename, source_linenum, callback, context); io->istream = input; i_stream_ref(io->istream); i_stream_set_io(io->istream, &io->io); return &io->io; } #undef io_add_istream struct io *io_add_istream(struct istream *input, const char *source_filename, unsigned int source_linenum, io_callback_t *callback, void *context) { return io_add_istream_to(current_ioloop, input, source_filename, source_linenum, callback, context); } static void io_file_unlink(struct io_file *io) { if (io->prev != NULL) io->prev->next = io->next; else io->io.ioloop->io_files = io->next; if (io->next != NULL) io->next->prev = io->prev; /* if we got here from an I/O handler callback, make sure we don't try to handle this one next. */ if (io->io.ioloop->next_io_file == io) io->io.ioloop->next_io_file = io->next; } static void io_remove_full(struct io **_io, bool closed) { struct io *io = *_io; i_assert(io->callback != NULL); *_io = NULL; /* make sure the callback doesn't get called anymore. kqueue code relies on this. */ io->callback = NULL; if (io->pending) { i_assert(io->ioloop->io_pending_count > 0); io->ioloop->io_pending_count--; } if (io->ctx != NULL) io_loop_context_unref(&io->ctx); if ((io->condition & IO_NOTIFY) != 0) io_loop_notify_remove(io); else { struct io_file *io_file = (struct io_file *)io; struct istream *istream = io_file->istream; if (istream != NULL) { /* remove io before it's freed */ i_stream_unset_io(istream, io); } io_file_unlink(io_file); if (io_file->fd != -1) io_loop_handle_remove(io_file, closed); else i_free(io); /* remove io from the ioloop before unreferencing the istream, because a destroyed istream may automatically close the fd. */ i_stream_unref(&istream); } } void io_remove(struct io **io) { if (*io == NULL) return; io_remove_full(io, FALSE); } void io_remove_closed(struct io **io) { if (*io == NULL) return; i_assert(((*io)->condition & IO_NOTIFY) == 0); io_remove_full(io, TRUE); } void io_set_pending(struct io *io) { i_assert((io->condition & IO_NOTIFY) == 0); if (!io->pending) { io->pending = TRUE; io->ioloop->io_pending_count++; } } bool io_is_pending(struct io *io) { return io->pending; } void io_set_never_wait_alone(struct io *io, bool set) { io->never_wait_alone = set; } static void timeout_update_next(struct timeout *timeout, struct timeval *tv_now) { if (tv_now == NULL) i_gettimeofday(&timeout->next_run); else { timeout->next_run.tv_sec = tv_now->tv_sec; timeout->next_run.tv_usec = tv_now->tv_usec; } /* we don't want microsecond accuracy or this function will be called all the time - millisecond is more than enough */ timeout->next_run.tv_usec -= timeout->next_run.tv_usec % 1000; timeout->next_run.tv_sec += timeout->msecs/1000; timeout->next_run.tv_usec += (timeout->msecs%1000)*1000; if (timeout->next_run.tv_usec >= 1000000) { timeout->next_run.tv_sec++; timeout->next_run.tv_usec -= 1000000; } } static struct timeout * timeout_add_common(struct ioloop *ioloop, const char *source_filename, unsigned int source_linenum, timeout_callback_t *callback, void *context) { struct timeout *timeout; timeout = i_new(struct timeout, 1); timeout->item.idx = UINT_MAX; timeout->source_filename = source_filename; timeout->source_linenum = source_linenum; timeout->ioloop = ioloop; timeout->callback = callback; timeout->context = context; if (timeout->ioloop->cur_ctx != NULL) { timeout->ctx = timeout->ioloop->cur_ctx; io_loop_context_ref(timeout->ctx); } return timeout; } #undef timeout_add_to struct timeout *timeout_add_to(struct ioloop *ioloop, unsigned int msecs, const char *source_filename, unsigned int source_linenum, timeout_callback_t *callback, void *context) { struct timeout *timeout; timeout = timeout_add_common(ioloop, source_filename, source_linenum, callback, context); timeout->msecs = msecs; if (msecs > 0) { /* start this timeout in the next run cycle */ array_push_back(&timeout->ioloop->timeouts_new, &timeout); } else { /* Trigger zero timeouts as soon as possible. When ioloop is running, refresh the timestamp to prevent infinite loops in case a timeout callback keeps recreating the 0-timeout. */ timeout_update_next(timeout, timeout->ioloop->running ? NULL : &ioloop_timeval); priorityq_add(timeout->ioloop->timeouts, &timeout->item); } return timeout; } #undef timeout_add struct timeout *timeout_add(unsigned int msecs, const char *source_filename, unsigned int source_linenum, timeout_callback_t *callback, void *context) { return timeout_add_to(current_ioloop, msecs, source_filename, source_linenum, callback, context); } #undef timeout_add_short_to struct timeout * timeout_add_short_to(struct ioloop *ioloop, unsigned int msecs, const char *source_filename, unsigned int source_linenum, timeout_callback_t *callback, void *context) { return timeout_add_to(ioloop, msecs, source_filename, source_linenum, callback, context); } #undef timeout_add_short struct timeout * timeout_add_short(unsigned int msecs, const char *source_filename, unsigned int source_linenum, timeout_callback_t *callback, void *context) { return timeout_add(msecs, source_filename, source_linenum, callback, context); } #undef timeout_add_absolute_to struct timeout * timeout_add_absolute_to(struct ioloop *ioloop, const struct timeval *time, const char *source_filename, unsigned int source_linenum, timeout_callback_t *callback, void *context) { struct timeout *timeout; timeout = timeout_add_common(ioloop, source_filename, source_linenum, callback, context); timeout->one_shot = TRUE; timeout->next_run = *time; priorityq_add(timeout->ioloop->timeouts, &timeout->item); return timeout; } #undef timeout_add_absolute struct timeout * timeout_add_absolute(const struct timeval *time, const char *source_filename, unsigned int source_linenum, timeout_callback_t *callback, void *context) { return timeout_add_absolute_to(current_ioloop, time, source_filename, source_linenum, callback, context); } static struct timeout * timeout_copy(const struct timeout *old_to, struct ioloop *ioloop) { struct timeout *new_to; new_to = timeout_add_common(ioloop, old_to->source_filename, old_to->source_linenum, old_to->callback, old_to->context); new_to->one_shot = old_to->one_shot; new_to->msecs = old_to->msecs; new_to->next_run = old_to->next_run; if (old_to->item.idx != UINT_MAX) priorityq_add(new_to->ioloop->timeouts, &new_to->item); else if (!new_to->one_shot) { i_assert(new_to->msecs > 0); array_push_back(&new_to->ioloop->timeouts_new, &new_to); } return new_to; } static void timeout_free(struct timeout *timeout) { if (timeout->ctx != NULL) io_loop_context_unref(&timeout->ctx); i_free(timeout); } void timeout_remove(struct timeout **_timeout) { struct timeout *timeout = *_timeout; struct ioloop *ioloop; if (timeout == NULL) return; ioloop = timeout->ioloop; *_timeout = NULL; if (timeout->item.idx != UINT_MAX) priorityq_remove(timeout->ioloop->timeouts, &timeout->item); else if (!timeout->one_shot && timeout->msecs > 0) { struct timeout *const *to_idx; array_foreach(&ioloop->timeouts_new, to_idx) { if (*to_idx == timeout) { array_delete(&ioloop->timeouts_new, array_foreach_idx(&ioloop->timeouts_new, to_idx), 1); break; } } } timeout_free(timeout); } static void ATTR_NULL(2) timeout_reset_timeval(struct timeout *timeout, struct timeval *tv_now) { if (timeout->item.idx == UINT_MAX) return; timeout_update_next(timeout, tv_now); /* If we came here from io_loop_handle_timeouts_real(), next_run must be larger than tv_now or it can go to infinite loop. This would mainly happen with 0 ms timeouts. Avoid this by making sure next_run is at least 1 us higher than tv_now. Note that some callers (like master process's process_min_avail preforking timeout) really do want the 0 ms timeout to trigger multiple times as rapidly as it can (but in separate ioloop runs). So don't increase it more than by 1 us. */ if (tv_now != NULL && timeval_cmp(&timeout->next_run, tv_now) <= 0) { timeout->next_run = *tv_now; timeval_add_usecs(&timeout->next_run, 1); } priorityq_remove(timeout->ioloop->timeouts, &timeout->item); priorityq_add(timeout->ioloop->timeouts, &timeout->item); } void timeout_reset(struct timeout *timeout) { i_assert(!timeout->one_shot); timeout_reset_timeval(timeout, NULL); } static int timeout_get_wait_time(struct timeout *timeout, struct timeval *tv_r, struct timeval *tv_now, bool in_timeout_loop) { int ret; if (tv_now->tv_sec == 0) i_gettimeofday(tv_now); tv_r->tv_sec = tv_now->tv_sec; tv_r->tv_usec = tv_now->tv_usec; i_assert(tv_r->tv_sec > 0); i_assert(timeout->next_run.tv_sec > 0); tv_r->tv_sec = timeout->next_run.tv_sec - tv_r->tv_sec; tv_r->tv_usec = timeout->next_run.tv_usec - tv_r->tv_usec; if (tv_r->tv_usec < 0) { tv_r->tv_sec--; tv_r->tv_usec += 1000000; } if (tv_r->tv_sec < 0) { /* The timeout should have been called already */ tv_r->tv_sec = 0; tv_r->tv_usec = 0; return 0; } if (tv_r->tv_sec == 0 && tv_r->tv_usec == 1 && !in_timeout_loop) { /* Possibly 0 ms timeout. Don't wait for a full millisecond for it to trigger. */ tv_r->tv_usec = 0; return 0; } if (tv_r->tv_sec > INT_MAX/1000-1) tv_r->tv_sec = INT_MAX/1000-1; /* round wait times up to next millisecond */ ret = tv_r->tv_sec * 1000 + (tv_r->tv_usec + 999) / 1000; i_assert(ret >= 0 && tv_r->tv_sec >= 0 && tv_r->tv_usec >= 0); return ret; } static int io_loop_get_wait_time(struct ioloop *ioloop, struct timeval *tv_r) { struct timeval tv_now; struct priorityq_item *item; struct timeout *timeout; int msecs; item = priorityq_peek(ioloop->timeouts); timeout = (struct timeout *)item; /* we need to see if there are pending IO waiting, if there is, we set msecs = 0 to ensure they are processed without delay */ if (timeout == NULL && ioloop->io_pending_count == 0) { /* no timeouts. use INT_MAX msecs for timeval and return -1 for poll/epoll infinity. */ tv_r->tv_sec = INT_MAX / 1000; tv_r->tv_usec = 0; ioloop->next_max_time.tv_sec = (1ULL << (TIME_T_MAX_BITS-1)) - 1; ioloop->next_max_time.tv_usec = 0; return -1; } if (ioloop->io_pending_count > 0) { i_gettimeofday(&tv_now); msecs = 0; tv_r->tv_sec = 0; tv_r->tv_usec = 0; } else { tv_now.tv_sec = 0; msecs = timeout_get_wait_time(timeout, tv_r, &tv_now, FALSE); } ioloop->next_max_time = tv_now; timeval_add_msecs(&ioloop->next_max_time, msecs); /* update ioloop_timeval - this is meant for io_loop_handle_timeouts()'s ioloop_wait_usecs calculation. normally after this we go to the ioloop and after that we update ioloop_timeval immediately again. */ ioloop_timeval = tv_now; ioloop_time = tv_now.tv_sec; i_assert(msecs == 0 || timeout->msecs > 0 || timeout->one_shot); return msecs; } static bool io_loop_have_waitable_io_files(struct ioloop *ioloop) { struct io_file *io; for (io = ioloop->io_files; io != NULL; io = io->next) { if (io->io.callback != NULL && !io->io.never_wait_alone) return TRUE; } return FALSE; } int io_loop_run_get_wait_time(struct ioloop *ioloop, struct timeval *tv_r) { int msecs = io_loop_get_wait_time(ioloop, tv_r); if (msecs < 0 && !io_loop_have_waitable_io_files(ioloop)) i_panic("BUG: No IOs or timeouts set. Not waiting for infinity."); return msecs; } static int timeout_cmp(const void *p1, const void *p2) { const struct timeout *to1 = p1, *to2 = p2; return timeval_cmp(&to1->next_run, &to2->next_run); } static void io_loop_default_time_moved(const struct timeval *old_time, const struct timeval *new_time) { long long diff = timeval_diff_usecs(old_time, new_time); if (diff > 0) { i_warning("Time moved backwards by %lld.%06lld seconds.", diff / 1000000, diff % 1000000); } } static void io_loop_timeouts_start_new(struct ioloop *ioloop) { struct timeout *timeout; if (array_count(&ioloop->timeouts_new) == 0) return; io_loop_time_refresh(); array_foreach_elem(&ioloop->timeouts_new, timeout) { i_assert(timeout->next_run.tv_sec == 0 && timeout->next_run.tv_usec == 0); i_assert(!timeout->one_shot); i_assert(timeout->msecs > 0); timeout_update_next(timeout, &ioloop_timeval); priorityq_add(ioloop->timeouts, &timeout->item); } array_clear(&ioloop->timeouts_new); } static void io_loop_timeouts_update(struct ioloop *ioloop, long long diff_usecs) { struct priorityq_item *const *items; unsigned int i, count; count = priorityq_count(ioloop->timeouts); items = priorityq_items(ioloop->timeouts); for (i = 0; i < count; i++) { struct timeout *to = (struct timeout *)items[i]; if (diff_usecs > 0) timeval_add_usecs(&to->next_run, diff_usecs); else timeval_sub_usecs(&to->next_run, -diff_usecs); } } static void io_loops_timeouts_update(long long diff_usecs) { struct ioloop *ioloop; for (ioloop = current_ioloop; ioloop != NULL; ioloop = ioloop->prev) io_loop_timeouts_update(ioloop, diff_usecs); } static void ioloop_add_wait_time(struct ioloop *ioloop) { struct io_wait_timer *timer; long long diff; diff = timeval_diff_usecs(&ioloop_timeval, &ioloop->wait_started); if (diff < 0) { /* time moved backwards */ diff = 0; } ioloop->ioloop_wait_usecs += diff; ioloop_global_wait_usecs += diff; for (timer = ioloop->wait_timers; timer != NULL; timer = timer->next) timer->usecs += diff; } static void io_loop_handle_timeouts_real(struct ioloop *ioloop) { struct priorityq_item *item; struct timeval tv_old, tv, tv_call; long long diff_usecs; data_stack_frame_t t_id; tv_old = ioloop_timeval; i_gettimeofday(&ioloop_timeval); diff_usecs = timeval_diff_usecs(&ioloop_timeval, &tv_old); if (unlikely(diff_usecs < 0)) { /* time moved backwards */ io_loops_timeouts_update(diff_usecs); ioloop->time_moved_callback(&tv_old, &ioloop_timeval); i_assert(ioloop == current_ioloop); /* the callback may have slept, so check the time again. */ i_gettimeofday(&ioloop_timeval); } else { diff_usecs = timeval_diff_usecs(&ioloop->next_max_time, &ioloop_timeval); if (unlikely(-diff_usecs >= IOLOOP_TIME_MOVED_FORWARDS_MIN_USECS)) { io_loops_timeouts_update(-diff_usecs); /* time moved forwards */ ioloop->time_moved_callback(&ioloop->next_max_time, &ioloop_timeval); i_assert(ioloop == current_ioloop); } ioloop_add_wait_time(ioloop); } ioloop_time = ioloop_timeval.tv_sec; tv_call = ioloop_timeval; while (ioloop->running && (item = priorityq_peek(ioloop->timeouts)) != NULL) { struct timeout *timeout = (struct timeout *)item; /* use tv_call to make sure we don't get to infinite loop in case callbacks update ioloop_timeval. */ if (timeout_get_wait_time(timeout, &tv, &tv_call, TRUE) > 0) break; if (timeout->one_shot) { /* remove timeout from queue */ priorityq_remove(timeout->ioloop->timeouts, &timeout->item); } else { /* update timeout's next_run and reposition it in the queue */ timeout_reset_timeval(timeout, &tv_call); } if (timeout->ctx != NULL) io_loop_context_activate(timeout->ctx); t_id = t_push_named("ioloop timeout handler %p", (void *)timeout->callback); timeout->callback(timeout->context); if (!t_pop(&t_id)) { i_panic("Leaked a t_pop() call in timeout handler %p", (void *)timeout->callback); } if (ioloop->cur_ctx != NULL) io_loop_context_deactivate(ioloop->cur_ctx); i_assert(ioloop == current_ioloop); } } void io_loop_handle_timeouts(struct ioloop *ioloop) { T_BEGIN { io_loop_handle_timeouts_real(ioloop); } T_END; /* Free the unused memory in data stack once per second. This way if the data stack has grown excessively large temporarily, it won't permanently waste memory. And if the data stack grows back to the same large size, re-allocating it once per second doesn't cause performance problems. */ if (data_stack_last_free_unused != ioloop_time) { if (data_stack_last_free_unused != 0) data_stack_free_unused(); data_stack_last_free_unused = ioloop_time; } } void io_loop_call_io(struct io *io) { struct ioloop *ioloop = io->ioloop; data_stack_frame_t t_id; if (io->pending) { i_assert(ioloop->io_pending_count > 0); ioloop->io_pending_count--; io->pending = FALSE; } if (io->ctx != NULL) io_loop_context_activate(io->ctx); t_id = t_push_named("ioloop handler %p", (void *)io->callback); io->callback(io->context); if (!t_pop(&t_id)) { i_panic("Leaked a t_pop() call in I/O handler %p", (void *)io->callback); } if (ioloop->cur_ctx != NULL) io_loop_context_deactivate(ioloop->cur_ctx); i_assert(ioloop == current_ioloop); } void io_loop_run(struct ioloop *ioloop) { if (ioloop->handler_context == NULL) io_loop_initialize_handler(ioloop); if (ioloop->cur_ctx != NULL) io_loop_context_deactivate(ioloop->cur_ctx); /* recursive io_loop_run() isn't allowed for the same ioloop. it can break backends. */ i_assert(!ioloop->iolooping); ioloop->iolooping = TRUE; ioloop->running = TRUE; while (ioloop->running) io_loop_handler_run(ioloop); ioloop->iolooping = FALSE; } static void io_loop_call_pending(struct ioloop *ioloop) { struct io_file *io; while (ioloop->io_pending_count > 0) { io = ioloop->io_files; do { ioloop->next_io_file = io->next; if (io->io.pending) io_loop_call_io(&io->io); if (ioloop->io_pending_count == 0) break; io = ioloop->next_io_file; } while (io != NULL); } } void io_loop_handler_run(struct ioloop *ioloop) { i_assert(ioloop == current_ioloop); io_loop_timeouts_start_new(ioloop); ioloop->wait_started = ioloop_timeval; io_loop_handler_run_internal(ioloop); io_loop_call_pending(ioloop); if (ioloop->stop_after_run_loop) io_loop_stop(ioloop); i_assert(ioloop == current_ioloop); } void io_loop_stop(struct ioloop *ioloop) { ioloop->running = FALSE; ioloop->stop_after_run_loop = FALSE; } void io_loop_stop_delayed(struct ioloop *ioloop) { ioloop->stop_after_run_loop = TRUE; } void io_loop_set_running(struct ioloop *ioloop) { ioloop->running = TRUE; } void io_loop_set_max_fd_count(struct ioloop *ioloop, unsigned int max_fds) { ioloop->max_fd_count = max_fds; } bool io_loop_is_running(struct ioloop *ioloop) { return ioloop->running; } void io_loop_time_refresh(void) { i_gettimeofday(&ioloop_timeval); ioloop_time = ioloop_timeval.tv_sec; } struct ioloop *io_loop_create(void) { struct ioloop *ioloop; if (!panic_on_leak_set) { panic_on_leak_set = TRUE; panic_on_leak = getenv("CORE_IO_LEAK") != NULL; } /* initialize time */ i_gettimeofday(&ioloop_timeval); ioloop_time = ioloop_timeval.tv_sec; ioloop = i_new(struct ioloop, 1); ioloop->timeouts = priorityq_init(timeout_cmp, 32); i_array_init(&ioloop->timeouts_new, 8); ioloop->time_moved_callback = current_ioloop != NULL ? current_ioloop->time_moved_callback : io_loop_default_time_moved; ioloop->prev = current_ioloop; io_loop_set_current(ioloop); return ioloop; } void io_loop_destroy(struct ioloop **_ioloop) { struct ioloop *ioloop = *_ioloop; struct timeout *to; struct priorityq_item *item; bool leaks = FALSE; *_ioloop = NULL; /* ->prev won't work unless loops are destroyed in create order */ i_assert(ioloop == current_ioloop); if (array_is_created(&io_destroy_callbacks)) { io_destroy_callback_t *callback; array_foreach_elem(&io_destroy_callbacks, callback) T_BEGIN { callback(current_ioloop); } T_END; } io_loop_set_current(current_ioloop->prev); if (ioloop->notify_handler_context != NULL) io_loop_notify_handler_deinit(ioloop); while (ioloop->io_files != NULL) { struct io_file *io = ioloop->io_files; struct io *_io = &io->io; const char *error = t_strdup_printf( "I/O leak: %p (%s:%u, fd %d)", (void *)io->io.callback, io->io.source_filename, io->io.source_linenum, io->fd); if (panic_on_leak) i_panic("%s", error); else i_warning("%s", error); io_remove(&_io); leaks = TRUE; } i_assert(ioloop->io_pending_count == 0); array_foreach_elem(&ioloop->timeouts_new, to) { const char *error = t_strdup_printf( "Timeout leak: %p (%s:%u)", (void *)to->callback, to->source_filename, to->source_linenum); if (panic_on_leak) i_panic("%s", error); else i_warning("%s", error); timeout_free(to); leaks = TRUE; } array_free(&ioloop->timeouts_new); while ((item = priorityq_pop(ioloop->timeouts)) != NULL) { struct timeout *to = (struct timeout *)item; const char *error = t_strdup_printf( "Timeout leak: %p (%s:%u)", (void *)to->callback, to->source_filename, to->source_linenum); if (panic_on_leak) i_panic("%s", error); else i_warning("%s", error); timeout_free(to); leaks = TRUE; } priorityq_deinit(&ioloop->timeouts); while (ioloop->wait_timers != NULL) { struct io_wait_timer *timer = ioloop->wait_timers; const char *error = t_strdup_printf( "IO wait timer leak: %s:%u", timer->source_filename, timer->source_linenum); if (panic_on_leak) i_panic("%s", error); else i_warning("%s", error); io_wait_timer_remove(&timer); leaks = TRUE; } if (leaks) { const char *backtrace; if (backtrace_get(&backtrace) == 0) i_warning("Raw backtrace for leaks: %s", backtrace); } if (ioloop->handler_context != NULL) io_loop_handler_deinit(ioloop); if (ioloop->cur_ctx != NULL) io_loop_context_unref(&ioloop->cur_ctx); i_free(ioloop); } void io_loop_set_time_moved_callback(struct ioloop *ioloop, io_loop_time_moved_callback_t *callback) { ioloop->time_moved_callback = callback; } static void io_switch_callbacks_free(void) { array_free(&io_switch_callbacks); } static void io_destroy_callbacks_free(void) { array_free(&io_destroy_callbacks); } void io_loop_set_current(struct ioloop *ioloop) { io_switch_callback_t *callback; struct ioloop *prev_ioloop = current_ioloop; if (ioloop == current_ioloop) return; current_ioloop = ioloop; if (array_is_created(&io_switch_callbacks)) { array_foreach_elem(&io_switch_callbacks, callback) T_BEGIN { callback(prev_ioloop); } T_END; } } struct ioloop *io_loop_get_root(void) { struct ioloop *ioloop = current_ioloop; while (ioloop->prev != NULL) ioloop = ioloop->prev; return ioloop; } void io_loop_add_switch_callback(io_switch_callback_t *callback) { if (!array_is_created(&io_switch_callbacks)) { i_array_init(&io_switch_callbacks, 4); lib_atexit_priority(io_switch_callbacks_free, LIB_ATEXIT_PRIORITY_LOW); } array_push_back(&io_switch_callbacks, &callback); } void io_loop_remove_switch_callback(io_switch_callback_t *callback) { io_switch_callback_t *const *callbackp; unsigned int idx; array_foreach(&io_switch_callbacks, callbackp) { if (*callbackp == callback) { idx = array_foreach_idx(&io_switch_callbacks, callbackp); array_delete(&io_switch_callbacks, idx, 1); return; } } i_unreached(); } void io_loop_add_destroy_callback(io_destroy_callback_t *callback) { if (!array_is_created(&io_destroy_callbacks)) { i_array_init(&io_destroy_callbacks, 4); lib_atexit_priority(io_destroy_callbacks_free, LIB_ATEXIT_PRIORITY_LOW); } array_push_back(&io_destroy_callbacks, &callback); } void io_loop_remove_destroy_callback(io_destroy_callback_t *callback) { io_destroy_callback_t *const *callbackp; unsigned int idx; array_foreach(&io_destroy_callbacks, callbackp) { if (*callbackp == callback) { idx = array_foreach_idx(&io_destroy_callbacks, callbackp); array_delete(&io_destroy_callbacks, idx, 1); return; } } i_unreached(); } struct ioloop_context *io_loop_context_new(struct ioloop *ioloop) { struct ioloop_context *ctx; ctx = i_new(struct ioloop_context, 1); ctx->refcount = 1; ctx->ioloop = ioloop; i_array_init(&ctx->callbacks, 4); return ctx; } void io_loop_context_ref(struct ioloop_context *ctx) { i_assert(ctx->refcount > 0); ctx->refcount++; } void io_loop_context_unref(struct ioloop_context **_ctx) { struct ioloop_context *ctx = *_ctx; *_ctx = NULL; i_assert(ctx->refcount > 0); if (--ctx->refcount > 0) return; /* cur_ctx itself keeps a reference */ i_assert(ctx->ioloop->cur_ctx != ctx); array_free(&ctx->callbacks); array_free(&ctx->global_event_stack); i_free(ctx); } #undef io_loop_context_add_callbacks void io_loop_context_add_callbacks(struct ioloop_context *ctx, io_callback_t *activate, io_callback_t *deactivate, void *context) { struct ioloop_context_callback cb; i_zero(&cb); cb.activate = activate; cb.deactivate = deactivate; cb.context = context; array_push_back(&ctx->callbacks, &cb); } #undef io_loop_context_remove_callbacks void io_loop_context_remove_callbacks(struct ioloop_context *ctx, io_callback_t *activate, io_callback_t *deactivate, void *context) { struct ioloop_context_callback *cb; array_foreach_modifiable(&ctx->callbacks, cb) { if (cb->context == context && cb->activate == activate && cb->deactivate == deactivate) { /* simply mark it as deleted, since we could get here from activate/deactivate loop */ cb->activate = NULL; cb->deactivate = NULL; cb->context = NULL; return; } } i_panic("io_loop_context_remove_callbacks() context not found"); } static void io_loop_context_remove_deleted_callbacks(struct ioloop_context *ctx) { const struct ioloop_context_callback *cbs; unsigned int i, count; cbs = array_get(&ctx->callbacks, &count); for (i = 0; i < count; ) { if (cbs[i].activate != NULL) i++; else { array_delete(&ctx->callbacks, i, 1); cbs = array_get(&ctx->callbacks, &count); } } } static void io_loop_context_push_global_events(struct ioloop_context *ctx) { struct event *const *events; unsigned int i, count; ctx->root_global_event = event_get_global(); if (!array_is_created(&ctx->global_event_stack)) return; /* push the global events from stack in reverse order */ events = array_get(&ctx->global_event_stack, &count); if (count == 0) return; /* Remember the oldest global event. We're going to pop until that event when deactivating the context. */ for (i = count; i > 0; i--) event_push_global(events[i-1]); array_clear(&ctx->global_event_stack); } static void io_loop_context_pop_global_events(struct ioloop_context *ctx) { struct event *event; /* ioloop context is always global, so we can't push one ioloop context on top of another one. We'll need to rewind the global event stack until we've reached the event that started this context. We'll push these global events back when the ioloop context is activated again. (We'll assert-crash if the root event is freed before these global events have been popped.) */ while ((event = event_get_global()) != ctx->root_global_event) { i_assert(event != NULL); if (!array_is_created(&ctx->global_event_stack)) i_array_init(&ctx->global_event_stack, 4); array_push_back(&ctx->global_event_stack, &event); event_pop_global(event); } ctx->root_global_event = NULL; } void io_loop_context_activate(struct ioloop_context *ctx) { struct ioloop_context_callback *cb; i_assert(ctx->ioloop->cur_ctx == NULL); ctx->ioloop->cur_ctx = ctx; io_loop_context_push_global_events(ctx); io_loop_context_ref(ctx); array_foreach_modifiable(&ctx->callbacks, cb) { i_assert(!cb->activated); if (cb->activate != NULL) T_BEGIN { cb->activate(cb->context); } T_END; cb->activated = TRUE; } } void io_loop_context_deactivate(struct ioloop_context *ctx) { struct ioloop_context_callback *cb; i_assert(ctx->ioloop->cur_ctx == ctx); array_foreach_modifiable(&ctx->callbacks, cb) { if (!cb->activated) { /* we just added this callback. don't deactivate it before it gets first activated. */ } else { if (cb->deactivate != NULL) T_BEGIN { cb->deactivate(cb->context); } T_END; cb->activated = FALSE; } } ctx->ioloop->cur_ctx = NULL; io_loop_context_pop_global_events(ctx); io_loop_context_remove_deleted_callbacks(ctx); io_loop_context_unref(&ctx); } void io_loop_context_switch(struct ioloop_context *ctx) { if (ctx->ioloop->cur_ctx != NULL) { if (ctx->ioloop->cur_ctx == ctx) return; io_loop_context_deactivate(ctx->ioloop->cur_ctx); /* deactivation may remove the cur_ctx */ if (ctx->ioloop->cur_ctx != NULL) io_loop_context_unref(&ctx->ioloop->cur_ctx); } io_loop_context_activate(ctx); } struct ioloop_context *io_loop_get_current_context(struct ioloop *ioloop) { return ioloop->cur_ctx; } struct io *io_loop_move_io_to(struct ioloop *ioloop, struct io **_io) { struct io *old_io = *_io; struct io_file *old_io_file, *new_io_file; if (old_io == NULL) return NULL; i_assert((old_io->condition & IO_NOTIFY) == 0); if (old_io->ioloop == ioloop) return old_io; old_io_file = (struct io_file *)old_io; new_io_file = io_add_file(ioloop, old_io_file->fd, old_io->condition, old_io->source_filename, old_io->source_linenum, old_io->callback, old_io->context); if (old_io_file->istream != NULL) { /* reference before io_remove() */ new_io_file->istream = old_io_file->istream; i_stream_ref(new_io_file->istream); } if (old_io->pending) io_set_pending(&new_io_file->io); io_remove(_io); if (new_io_file->istream != NULL) { /* update istream io after it was removed with io_remove() */ i_stream_set_io(new_io_file->istream, &new_io_file->io); } return &new_io_file->io; } struct io *io_loop_move_io(struct io **_io) { return io_loop_move_io_to(current_ioloop, _io); } struct timeout *io_loop_move_timeout_to(struct ioloop *ioloop, struct timeout **_timeout) { struct timeout *new_to, *old_to = *_timeout; if (old_to == NULL || old_to->ioloop == ioloop) return old_to; new_to = timeout_copy(old_to, ioloop); timeout_remove(_timeout); return new_to; } struct timeout *io_loop_move_timeout(struct timeout **_timeout) { return io_loop_move_timeout_to(current_ioloop, _timeout); } bool io_loop_have_ios(struct ioloop *ioloop) { return ioloop->io_files != NULL; } bool io_loop_have_immediate_timeouts(struct ioloop *ioloop) { struct timeval tv; return io_loop_get_wait_time(ioloop, &tv) == 0; } bool io_loop_is_empty(struct ioloop *ioloop) { return ioloop->io_files == NULL && priorityq_count(ioloop->timeouts) == 0 && array_count(&ioloop->timeouts_new) == 0; } uint64_t io_loop_get_wait_usecs(struct ioloop *ioloop) { return ioloop->ioloop_wait_usecs; } enum io_condition io_loop_find_fd_conditions(struct ioloop *ioloop, int fd) { enum io_condition conditions = 0; struct io_file *io; i_assert(fd >= 0); for (io = ioloop->io_files; io != NULL; io = io->next) { if (io->fd == fd) conditions |= io->io.condition; } return conditions; } #undef io_wait_timer_add_to struct io_wait_timer * io_wait_timer_add_to(struct ioloop *ioloop, const char *source_filename, unsigned int source_linenum) { struct io_wait_timer *timer; timer = i_new(struct io_wait_timer, 1); timer->ioloop = ioloop; timer->source_filename = source_filename; timer->source_linenum = source_linenum; DLLIST_PREPEND(&ioloop->wait_timers, timer); return timer; } #undef io_wait_timer_add struct io_wait_timer * io_wait_timer_add(const char *source_filename, unsigned int source_linenum) { return io_wait_timer_add_to(current_ioloop, source_filename, source_linenum); } struct io_wait_timer *io_wait_timer_move_to(struct io_wait_timer **_timer, struct ioloop *ioloop) { struct io_wait_timer *timer = *_timer; *_timer = NULL; DLLIST_REMOVE(&timer->ioloop->wait_timers, timer); DLLIST_PREPEND(&ioloop->wait_timers, timer); timer->ioloop = ioloop; return timer; } struct io_wait_timer *io_wait_timer_move(struct io_wait_timer **_timer) { return io_wait_timer_move_to(_timer, current_ioloop); } void io_wait_timer_remove(struct io_wait_timer **_timer) { struct io_wait_timer *timer = *_timer; *_timer = NULL; DLLIST_REMOVE(&timer->ioloop->wait_timers, timer); i_free(timer); } uint64_t io_wait_timer_get_usecs(struct io_wait_timer *timer) { return timer->usecs; } struct event *io_loop_get_active_global_root(void) { if (current_ioloop == NULL) return NULL; if (current_ioloop->cur_ctx == NULL) return NULL; return current_ioloop->cur_ctx->root_global_event; } dovecot-2.3.21.1/src/lib/imem.c0000644000000000000000000000265414656633576012754 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" pool_t default_pool = &static_system_pool; void *i_malloc(size_t size) { return p_malloc(default_pool, size); } void *i_realloc(void *mem, size_t old_size, size_t new_size) { return p_realloc(default_pool, mem, old_size, new_size); } char *i_strdup(const char *str) { return p_strdup(default_pool, str); } void *i_memdup(const void *data, size_t size) { return p_memdup(default_pool, data, size); } char *i_strdup_empty(const char *str) { return p_strdup_empty(default_pool, str); } char *i_strdup_until(const void *str, const void *end) { return p_strdup_until(default_pool, str, end); } char *i_strndup(const void *str, size_t max_chars) { i_assert(str != NULL); return p_strndup(default_pool, str, max_chars); } char *i_strdup_printf(const char *format, ...) { va_list args; char *ret; va_start(args, format); ret = p_strdup_vprintf(default_pool, format, args); va_end(args); return ret; } char *i_strdup_vprintf(const char *format, va_list args) { return p_strdup_vprintf(default_pool, format, args); } char *i_strconcat(const char *str1, ...) { va_list args; char *ret; size_t len; i_assert(str1 != NULL); va_start(args, str1); T_BEGIN { const char *temp = vstrconcat(str1, args, &len); t_buffer_alloc(len); ret = p_malloc(default_pool, len); memcpy(ret, temp, len); } T_END; va_end(args); return ret; } dovecot-2.3.21.1/src/lib/test-lib.h0000644000000000000000000000030314656633576013542 00000000000000#ifndef TEST_LIB #define TEST_LIB #include "lib.h" #include "test-common.h" #define TEST(x) TEST_DECL(x) #define FATAL(x) FATAL_DECL(x) #include "test-lib.inc" #undef TEST #undef FATAL #endif dovecot-2.3.21.1/src/lib/ostream-hash.c0000644000000000000000000000263714656633576014421 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "hash-method.h" #include "ostream-private.h" #include "ostream-hash.h" struct hash_ostream { struct ostream_private ostream; const struct hash_method *method; void *hash_context; }; static ssize_t o_stream_hash_sendv(struct ostream_private *stream, const struct const_iovec *iov, unsigned int iov_count) { struct hash_ostream *hstream = container_of(stream, struct hash_ostream, ostream); unsigned int i; size_t bytes_left, block_len; ssize_t ret; if ((ret = o_stream_sendv(stream->parent, iov, iov_count)) < 0) { o_stream_copy_error_from_parent(stream); return -1; } if (ret > 0) { bytes_left = ret; for (i = 0; i < iov_count && bytes_left > 0; i++) { block_len = iov[i].iov_len <= bytes_left ? iov[i].iov_len : bytes_left; hstream->method->loop(hstream->hash_context, iov[i].iov_base, block_len); bytes_left -= block_len; } } stream->ostream.offset += ret; return ret; } struct ostream * o_stream_create_hash(struct ostream *output, const struct hash_method *method, void *hash_context) { struct hash_ostream *hstream; hstream = i_new(struct hash_ostream, 1); hstream->ostream.sendv = o_stream_hash_sendv; hstream->method = method; hstream->hash_context = hash_context; return o_stream_create(&hstream->ostream, output, o_stream_get_fd(output)); } dovecot-2.3.21.1/src/lib/istream-base64-decoder.c0000644000000000000000000001076614656633576016161 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "base64.h" #include "hex-binary.h" #include "istream-private.h" #include "istream-base64.h" struct base64_decoder_istream { struct istream_private istream; struct base64_decoder decoder; }; static int i_stream_read_parent(struct istream_private *stream) { size_t size; ssize_t ret; size = i_stream_get_data_size(stream->parent); if (size >= 4) return 1; /* we have less than one base64 block. see if there is more data available. */ ret = i_stream_read_memarea(stream->parent); if (ret <= 0) { stream->istream.stream_errno = stream->parent->stream_errno; return ret; } size = i_stream_get_data_size(stream->parent); i_assert(size != 0); return 1; } static int i_stream_base64_try_decode_block(struct base64_decoder_istream *bstream) { struct istream_private *stream = &bstream->istream; const unsigned char *data; size_t size, avail, pos; buffer_t buf; data = i_stream_get_data(stream->parent, &size); if (size == 0) return 0; if (!i_stream_try_alloc(stream, (size+3)/4*3, &avail)) return -2; buffer_create_from_data(&buf, stream->w_buffer + stream->pos, avail); if (base64_decode_more(&bstream->decoder, data, size, &pos, &buf) < 0) { io_stream_set_error(&stream->iostream, "Invalid base64 data: 0x%s", binary_to_hex(data+pos, I_MIN(size-pos, 8))); stream->istream.stream_errno = EINVAL; return -1; } stream->pos += buf.used; i_stream_skip(stream->parent, pos); return pos > 0 ? 1 : 0; } static void i_stream_base64_finish_decode(struct base64_decoder_istream *bstream) { struct istream_private *stream = &bstream->istream; i_assert(i_stream_get_data_size(stream->parent) == 0); if (base64_decode_finish(&bstream->decoder) < 0) { io_stream_set_error(&stream->iostream, "Base64 data ends prematurely"); stream->istream.stream_errno = EPIPE; } } static ssize_t i_stream_base64_decoder_read(struct istream_private *stream) { struct base64_decoder_istream *bstream = container_of(stream, struct base64_decoder_istream, istream); size_t pre_count, post_count; int ret; if (base64_decode_is_finished(&bstream->decoder)) { stream->istream.eof = TRUE; return -1; } do { ret = i_stream_read_parent(stream); if (ret == 0) return 0; if (ret < 0 && ret != -2) { if (stream->istream.stream_errno != 0) return -1; if (i_stream_get_data_size(stream->parent) == 0) { i_stream_base64_finish_decode(bstream); stream->istream.eof = TRUE; return -1; } } /* encode as many blocks as fits into destination buffer */ pre_count = stream->pos - stream->skip; while ((ret = i_stream_base64_try_decode_block(bstream)) > 0) ; post_count = stream->pos - stream->skip; } while (ret == 0 && pre_count == post_count); if (ret < 0 && pre_count == post_count) return ret; i_assert(post_count > pre_count); return post_count - pre_count; } static void i_stream_base64_decoder_seek(struct istream_private *stream, uoff_t v_offset, bool mark) { struct base64_decoder_istream *bstream = container_of(stream, struct base64_decoder_istream, istream); if (v_offset < stream->istream.v_offset) { /* seeking backwards - go back to beginning and seek forward from there. */ stream->parent_expected_offset = stream->parent_start_offset; stream->skip = stream->pos = 0; stream->istream.v_offset = 0; i_stream_seek(stream->parent, 0); base64_decode_reset(&bstream->decoder); } i_stream_default_seek_nonseekable(stream, v_offset, mark); } static struct istream * i_stream_create_base64_decoder_common(const struct base64_scheme *b64, struct istream *input) { struct base64_decoder_istream *bstream; bstream = i_new(struct base64_decoder_istream, 1); bstream->istream.max_buffer_size = input->real_stream->max_buffer_size; bstream->istream.read = i_stream_base64_decoder_read; bstream->istream.seek = i_stream_base64_decoder_seek; bstream->istream.istream.readable_fd = FALSE; bstream->istream.istream.blocking = input->blocking; bstream->istream.istream.seekable = input->seekable; base64_decode_init(&bstream->decoder, b64, 0); return i_stream_create(&bstream->istream, input, i_stream_get_fd(input), 0); } struct istream * i_stream_create_base64_decoder(struct istream *input) { return i_stream_create_base64_decoder_common(&base64_scheme, input); } struct istream * i_stream_create_base64url_decoder(struct istream *input) { return i_stream_create_base64_decoder_common(&base64url_scheme, input); } dovecot-2.3.21.1/src/lib/guid.h0000644000000000000000000000326414656633576012760 00000000000000#ifndef GUID_H #define GUID_H #define GUID_128_SIZE 16 typedef uint8_t guid_128_t[GUID_128_SIZE]; #define GUID_128_HOST_HASH_SIZE 4 ARRAY_DEFINE_TYPE(guid_128_t, guid_128_t); enum uuid_format { FORMAT_RECORD, FORMAT_COMPACT, FORMAT_MICROSOFT, }; /* Generate a GUID (contains host name) */ const char *guid_generate(void); /* Generate 128 bit GUID */ void guid_128_generate(guid_128_t guid_r); /* Returns TRUE if GUID is empty (not set / unknown). */ bool guid_128_is_empty(const guid_128_t guid) ATTR_PURE; static inline void guid_128_empty(guid_128_t guid) { memset(guid, 0, GUID_128_SIZE); } /* Returns TRUE if two GUIDs are equal. */ bool guid_128_equals(const guid_128_t guid1, const guid_128_t guid2) ATTR_PURE; /* Copy GUID */ static inline void guid_128_copy(guid_128_t dest, const guid_128_t src) { memcpy(dest, src, GUID_128_SIZE); } /* Returns GUID as a hex string. */ const char *guid_128_to_string(const guid_128_t guid); /* Parse GUID from a string. Returns 0 if ok, -1 if GUID isn't valid. */ int guid_128_from_string(const char *str, guid_128_t guid_r); /* Returns GUID as a UUID hex string. */ const char *guid_128_to_uuid_string(const guid_128_t guid, enum uuid_format format); /* Parse GUID from a UUID string. Returns 0 if ok, -1 if UUID isn't valid. */ int guid_128_from_uuid_string(const char *str, guid_128_t guid_r); /* guid_128 hash/cmp functions for hash.h */ unsigned int guid_128_hash(const guid_128_t guid) ATTR_PURE; int guid_128_cmp(const guid_128_t guid1, const guid_128_t guid2) ATTR_PURE; /* Return the hash of host used by guid_128_generate(). */ void guid_128_host_hash_get(const char *host, unsigned char hash_r[STATIC_ARRAY GUID_128_HOST_HASH_SIZE]); #endif dovecot-2.3.21.1/src/lib/str-find.h0000644000000000000000000000140414656633576013550 00000000000000#ifndef STR_FIND_H #define STR_FIND_H struct str_find_context; struct str_find_context *str_find_init(pool_t pool, const char *key); void str_find_deinit(struct str_find_context **ctx); /* Returns TRUE if key is found. It's possible to send the data in arbitrary blocks and have the key still match. */ bool str_find_more(struct str_find_context *ctx, const unsigned char *data, size_t size); /* After str_find_more() has returned TRUE, this function returns the end position in the previous data block where the key had matched. */ size_t str_find_get_match_end_pos(struct str_find_context *ctx); /* Reset input data. The next str_find_more() call won't try to match the key to earlier data. */ void str_find_reset(struct str_find_context *ctx); #endif dovecot-2.3.21.1/src/lib/test-base32.c0000644000000000000000000001127514656633576014060 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str.h" #include "base32.h" static void test_base32_encode(void) { static const char *input[] = { "toedeledokie!!", "bye bye world", "hoeveel onzin kun je testen?????", "c'est pas vrai! ", "dit is het einde van deze test" }; static const char *output[] = { "ORXWKZDFNRSWI33LNFSSCII=", "MJ4WKIDCPFSSA53POJWGI===", "NBXWK5TFMVWCA33OPJUW4IDLOVXCA2TFEB2GK43UMVXD6PZ7H47Q====", "MMTWK43UEBYGC4ZAOZZGC2JBEA======", "MRUXIIDJOMQGQZLUEBSWS3TEMUQHMYLOEBSGK6TFEB2GK43U" }; string_t *str; unsigned int i; test_begin("base32_encode() with padding"); str = t_str_new(256); for (i = 0; i < N_ELEMENTS(input); i++) { str_truncate(str, 0); base32_encode(TRUE, input[i], strlen(input[i]), str); test_assert(strcmp(output[i], str_c(str)) == 0); } test_end(); test_begin("base32_encode() no padding"); str = t_str_new(256); for (i = 0; i < N_ELEMENTS(input); i++) { const char *p = strchr(output[i], '='); size_t len; if (p == NULL) len = strlen(output[i]); else len = (size_t)(p - output[i]); str_truncate(str, 0); base32_encode(FALSE, input[i], strlen(input[i]), str); test_assert(strncmp(output[i], str_c(str), len) == 0); } test_end(); } static void test_base32hex_encode(void) { static const char *input[] = { "toedeledokie!!", "bye bye world", "hoeveel onzin kun je testen?????", "c'est pas vrai! ", "dit is het einde van deze test" }; static const char *output[] = { "EHNMAP35DHIM8RRBD5II288=", "C9SMA832F5II0TRFE9M68===", "D1NMATJ5CLM20RREF9KMS83BELN20QJ541Q6ASRKCLN3UFPV7SVG====", "CCJMASRK41O62SP0EPP62Q9140======", "CHKN8839ECG6GPBK41IMIRJ4CKG7COBE41I6AUJ541Q6ASRK" }; string_t *str; unsigned int i; test_begin("base32hex_encode() with padding"); str = t_str_new(256); for (i = 0; i < N_ELEMENTS(input); i++) { str_truncate(str, 0); base32hex_encode(TRUE, input[i], strlen(input[i]), str); test_assert(strcmp(output[i], str_c(str)) == 0); } test_end(); test_begin("base32hex_encode() no padding"); str = t_str_new(256); for (i = 0; i < N_ELEMENTS(input); i++) { const char *p = strchr(output[i], '='); size_t len; if (p == NULL) len = strlen(output[i]); else len = (size_t)(p - output[i]); str_truncate(str, 0); base32hex_encode(FALSE, input[i], strlen(input[i]), str); test_assert(strncmp(output[i], str_c(str), len) == 0); } test_end(); } struct test_base32_decode_output { const char *text; int ret; unsigned int src_pos; }; static void test_base32_decode(void) { static const char *input[] = { "ORXWKZDFNRSWI33LNFSSCII=", "MJ4WKIDCPFSSA53POJWGI===", "NBXWK5TFMVWCA33OPJUW4IDLOVXCA2TFEB2GK43UMVXD6PZ7H47Q====", "MMTWK43UEBYGC4ZAOZZGC2JBEA======", "MRUXIIDJOMQGQZLUEBSWS3TEMUQHMYLOEBSGK6TFEB2GK43U" }; static const struct test_base32_decode_output output[] = { { "toedeledokie!!", 0, 24 }, { "bye bye world", 0, 24 }, { "hoeveel onzin kun je testen?????", 0, 56 }, { "c'est pas vrai! ", 0, 32 }, { "dit is het einde van deze test", 1, 48 }, }; string_t *str; unsigned int i; size_t src_pos; int ret; test_begin("base32_decode()"); str = t_str_new(256); for (i = 0; i < N_ELEMENTS(input); i++) { str_truncate(str, 0); src_pos = 0; ret = base32_decode(input[i], strlen(input[i]), &src_pos, str); test_assert(output[i].ret == ret && strcmp(output[i].text, str_c(str)) == 0 && (src_pos == output[i].src_pos || (output[i].src_pos == UINT_MAX && src_pos == strlen(input[i])))); } test_end(); } static void test_base32_random(void) { string_t *str, *dest; unsigned char buf[10]; unsigned int i, j, max; str = t_str_new(256); dest = t_str_new(256); test_begin("padded base32 encode/decode with random input"); for (i = 0; i < 1000; i++) { max = i_rand_limit(sizeof(buf)); for (j = 0; j < max; j++) buf[j] = i_rand_uchar(); str_truncate(str, 0); str_truncate(dest, 0); base32_encode(TRUE, buf, max, str); test_assert(base32_decode(str_data(str), str_len(str), NULL, dest) >= 0); test_assert(str_len(dest) == max && memcmp(buf, str_data(dest), max) == 0); } test_end(); test_begin("padded base32hex encode/decode with random input"); for (i = 0; i < 1000; i++) { max = i_rand_limit(sizeof(buf)); for (j = 0; j < max; j++) buf[j] = i_rand_uchar(); str_truncate(str, 0); str_truncate(dest, 0); base32hex_encode(TRUE, buf, max, str); test_assert(base32hex_decode(str_data(str), str_len(str), NULL, dest) >= 0); test_assert(str_len(dest) == max && memcmp(buf, str_data(dest), max) == 0); } test_end(); } void test_base32(void) { test_base32_encode(); test_base32hex_encode(); test_base32_decode(); test_base32_random(); } dovecot-2.3.21.1/src/lib/failures.c0000644000000000000000000006147114656633576013641 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "str.h" #include "hostpid.h" #include "net.h" #include "process-title.h" #include "lib-signals.h" #include "backtrace-string.h" #include "printf-format-fix.h" #include "write-full.h" #include "time-util.h" #include "failures-private.h" #include #include #include #include #include #define LOG_TYPE_FLAG_PREFIX_LEN 0x40 #define LOG_TYPE_FLAG_DISABLE_LOG_PREFIX 0x80 const char *failure_log_type_prefixes[LOG_TYPE_COUNT] = { "Debug: ", "Info: ", "Warning: ", "Error: ", "Fatal: ", "Panic: " }; const char *failure_log_type_names[LOG_TYPE_COUNT] = { "debug", "info", "warning", "error", "fatal", "panic" }; static int log_fd_write(int fd, const unsigned char *data, size_t len); static void error_handler_real(const struct failure_context *ctx, const char *format, va_list args); /* Initialize working defaults */ static failure_callback_t *fatal_handler ATTR_NORETURN = default_fatal_handler; static failure_callback_t *error_handler = default_error_handler; static failure_callback_t *info_handler = default_error_handler; static failure_callback_t *debug_handler = default_error_handler; static void (*failure_exit_callback)(int *) = NULL; static struct failure_context failure_ctx_debug = { .type = LOG_TYPE_DEBUG }; static struct failure_context failure_ctx_info = { .type = LOG_TYPE_INFO }; static struct failure_context failure_ctx_warning = { .type = LOG_TYPE_WARNING }; static struct failure_context failure_ctx_error = { .type = LOG_TYPE_ERROR }; static int log_fd = STDERR_FILENO, log_info_fd = STDERR_FILENO, log_debug_fd = STDERR_FILENO; static char *log_prefix = NULL; static char *log_stamp_format = NULL, *log_stamp_format_suffix = NULL; static bool failure_ignore_errors = FALSE, log_prefix_sent = FALSE; static bool coredump_on_error = FALSE; static void log_timestamp_add(const struct failure_context *ctx, string_t *str); static void log_prefix_add(const struct failure_context *ctx, string_t *str); static int i_failure_send_option_forced(const char *key, const char *value); static int internal_send_split(string_t *full_str, size_t prefix_len); static string_t * ATTR_FORMAT(3, 0) default_format(const struct failure_context *ctx, size_t *prefix_len_r ATTR_UNUSED, const char *format, va_list args) { string_t *str = t_str_new(256); log_timestamp_add(ctx, str); log_prefix_add(ctx, str); /* make sure there's no %n in there and fix %m */ str_vprintfa(str, printf_format_fix(format), args); return str; } static int default_write(enum log_type type, string_t *data, size_t prefix_len ATTR_UNUSED) { int fd; switch (type) { case LOG_TYPE_DEBUG: fd = log_debug_fd; break; case LOG_TYPE_INFO: fd = log_info_fd; break; default: fd = log_fd; break; } str_append_c(data, '\n'); return log_fd_write(fd, str_data(data), str_len(data)); } static void default_on_handler_failure(const struct failure_context *ctx) { const char *log_type = "info"; switch (ctx->type) { case LOG_TYPE_DEBUG: log_type = "debug"; /* fall through */ case LOG_TYPE_INFO: /* we failed to log to info/debug log, try to log the write error to error log - maybe that'll work. */ i_fatal_status(FATAL_LOGWRITE, "write() failed to %s log: %m", log_type); break; default: failure_exit(FATAL_LOGWRITE); break; } } static void default_post_handler(const struct failure_context *ctx) { if (ctx->type == LOG_TYPE_ERROR && coredump_on_error) abort(); } static string_t * ATTR_FORMAT(3, 0) syslog_format(const struct failure_context *ctx, size_t *prefix_len_r ATTR_UNUSED, const char *format, va_list args) { string_t *str = t_str_new(128); if (ctx->type == LOG_TYPE_INFO) { if (ctx->log_prefix != NULL) str_append(str, ctx->log_prefix); else if (log_prefix != NULL) str_append(str, log_prefix); } else { log_prefix_add(ctx, str); } str_vprintfa(str, format, args); return str; } static int syslog_write(enum log_type type, string_t *data, size_t prefix_len ATTR_UNUSED) { int level = LOG_ERR; switch (type) { case LOG_TYPE_DEBUG: level = LOG_DEBUG; break; case LOG_TYPE_INFO: level = LOG_INFO; break; case LOG_TYPE_WARNING: level = LOG_WARNING; break; case LOG_TYPE_ERROR: level = LOG_ERR; break; case LOG_TYPE_FATAL: case LOG_TYPE_PANIC: level = LOG_CRIT; break; case LOG_TYPE_COUNT: case LOG_TYPE_OPTION: i_unreached(); } syslog(level, "%s", str_c(data)); return 0; } static void syslog_on_handler_failure(const struct failure_context *ctx ATTR_UNUSED) { failure_exit(FATAL_LOGERROR); } static void syslog_post_handler(const struct failure_context *ctx ATTR_UNUSED) { } static string_t * ATTR_FORMAT(3, 0) internal_format(const struct failure_context *ctx, size_t *prefix_len_r, const char *format, va_list args) { string_t *str; unsigned char log_type = ctx->type + 1; if (ctx->log_prefix != NULL) { log_type |= LOG_TYPE_FLAG_DISABLE_LOG_PREFIX; if (ctx->log_prefix_type_pos != 0) log_type |= LOG_TYPE_FLAG_PREFIX_LEN; } else if (!log_prefix_sent && log_prefix != NULL) { if (i_failure_send_option_forced("prefix", log_prefix) < 0) { /* Failed to write log prefix. The log message writing would likely fail as well, but don't even try since the log prefix would be wrong. */ return NULL; } log_prefix_sent = TRUE; } str = t_str_new(128); str_printfa(str, "\001%c%s ", log_type, my_pid); if ((log_type & LOG_TYPE_FLAG_PREFIX_LEN) != 0) str_printfa(str, "%u ", ctx->log_prefix_type_pos); if (ctx->log_prefix != NULL) str_append(str, ctx->log_prefix); *prefix_len_r = str_len(str); str_vprintfa(str, format, args); return str; } static int internal_write(enum log_type type ATTR_UNUSED, string_t *data, size_t prefix_len) { if (str_len(data)+1 <= PIPE_BUF) { str_append_c(data, '\n'); return log_fd_write(STDERR_FILENO, str_data(data), str_len(data)); } return internal_send_split(data, prefix_len); } static void internal_on_handler_failure(const struct failure_context *ctx ATTR_UNUSED) { failure_exit(FATAL_LOGERROR); } static void internal_post_handler(const struct failure_context *ctx ATTR_UNUSED) { } static struct failure_handler_vfuncs default_handler_vfuncs = { .write = &default_write, .format = &default_format, .on_handler_failure = &default_on_handler_failure, .post_handler = &default_post_handler }; static struct failure_handler_vfuncs syslog_handler_vfuncs = { .write = &syslog_write, .format = &syslog_format, .on_handler_failure = &syslog_on_handler_failure, .post_handler = &syslog_post_handler }; static struct failure_handler_vfuncs internal_handler_vfuncs = { .write = &internal_write, .format = &internal_format, .on_handler_failure = &internal_on_handler_failure, .post_handler = &internal_post_handler }; struct failure_handler_config failure_handler = { .fatal_err_reset = FATAL_LOGWRITE, .v = &default_handler_vfuncs }; static int common_handler(const struct failure_context *ctx, const char *format, va_list args) { static int recursed = 0; int ret; size_t prefix_len = 0; if (recursed >= 2) { /* we're being called from some signal handler or we ran out of memory */ return -1; } recursed++; T_BEGIN { string_t *str = failure_handler.v->format(ctx, &prefix_len, format, args); ret = str == NULL ? -1 : failure_handler.v->write(ctx->type, str, prefix_len); } T_END; if (ret < 0 && failure_ignore_errors) ret = 0; recursed--; return ret; } static void error_handler_real(const struct failure_context *ctx, const char *format, va_list args) { if (common_handler(ctx, format, args) < 0) failure_handler.v->on_handler_failure(ctx); failure_handler.v->post_handler(ctx); } static void ATTR_FORMAT(2, 0) i_internal_error_handler(const struct failure_context *ctx, const char *format, va_list args); /* kludgy .. we want to trust log_stamp_format with -Wformat-nonliteral */ static const char * get_log_stamp_format(const char *format_arg, unsigned int timestamp_usecs) ATTR_FORMAT_ARG(1); static const char *get_log_stamp_format(const char *format_arg ATTR_UNUSED, unsigned int timestamp_usecs) { if (log_stamp_format_suffix == NULL) return log_stamp_format; return t_strdup_printf("%s%06u%s", log_stamp_format, timestamp_usecs, log_stamp_format_suffix); } void failure_exit(int status) { static bool recursed = FALSE; if (failure_exit_callback != NULL && !recursed) { recursed = TRUE; failure_exit_callback(&status); } lib_exit(status); } static void log_timestamp_add(const struct failure_context *ctx, string_t *str) { const struct tm *tm = ctx->timestamp; char buf[256]; struct timeval now; if (log_stamp_format != NULL) { if (tm == NULL) { i_gettimeofday(&now); tm = localtime(&now.tv_sec); } else { now.tv_usec = ctx->timestamp_usecs; } if (strftime(buf, sizeof(buf), get_log_stamp_format("unused", now.tv_usec), tm) > 0) str_append(str, buf); } } static void log_prefix_add(const struct failure_context *ctx, string_t *str) { if (ctx->log_prefix == NULL) { /* use global log prefix */ if (log_prefix != NULL) str_append(str, log_prefix); str_append(str, failure_log_type_prefixes[ctx->type]); } else if (ctx->log_prefix_type_pos == 0) { str_append(str, ctx->log_prefix); str_append(str, failure_log_type_prefixes[ctx->type]); } else { i_assert(ctx->log_prefix_type_pos <= strlen(ctx->log_prefix)); str_append_data(str, ctx->log_prefix, ctx->log_prefix_type_pos); str_append(str, failure_log_type_prefixes[ctx->type]); str_append(str, ctx->log_prefix + ctx->log_prefix_type_pos); } } static void fd_wait_writable(int fd) { struct pollfd pfd = { .fd = fd, .events = POLLOUT | POLLERR | POLLHUP | POLLNVAL, }; /* Use poll() instead of ioloop, because we don't want to recurse back to log writing in case something fails. */ if (poll(&pfd, 1, -1) < 0 && errno != EINTR) { /* Unexpected error. We're already blocking on log writes, so we can't log it. */ abort(); } } static int log_fd_write(int fd, const unsigned char *data, size_t len) { ssize_t ret; unsigned int prev_signal_term_counter = signal_term_counter; unsigned int terminal_eintr_count = 0; const char *old_title = NULL; bool failed = FALSE, process_title_changed = FALSE; while (!failed && (ret = write(fd, data, len)) != (ssize_t)len) { if (ret > 0) { /* some was written, continue.. */ data += ret; len -= ret; continue; } if (ret == 0) { /* out of disk space? */ errno = ENOSPC; failed = TRUE; break; } switch (errno) { case EAGAIN: { /* Log fd is nonblocking - wait until we can write more. Indicate in process title that the process is waiting because it's waiting on the log. Remember that the log fd is shared across processes, which also means the log fd flags are shared. So if one process changes the O_NONBLOCK flag for a log fd, all the processes see the change. To avoid problems, we'll wait using poll() instead of changing the O_NONBLOCK flag. */ if (!process_title_changed) { const char *title; process_title_changed = TRUE; old_title = t_strdup(process_title_get()); if (old_title == NULL) title = "[blocking on log write]"; else title = t_strdup_printf("%s - [blocking on log write]", old_title); process_title_set(title); } fd_wait_writable(fd); break; } case EINTR: if (prev_signal_term_counter == signal_term_counter) { /* non-terminal signal. ignore. */ } else if (terminal_eintr_count++ == 0) { /* we'd rather not die in the middle of writing to log. try again once more */ } else { /* received two terminal signals. someone wants us dead. */ failed = TRUE; break; } break; default: failed = TRUE; break; } prev_signal_term_counter = signal_term_counter; } if (process_title_changed) process_title_set(old_title); return failed ? -1 : 0; } static void ATTR_NORETURN default_fatal_finish(enum log_type type, int status) { const char *backtrace; static int recursed = 0; recursed++; if ((type == LOG_TYPE_PANIC || status == FATAL_OUTOFMEM) && recursed == 1) { if (backtrace_get(&backtrace) == 0) i_error("Raw backtrace: %s", backtrace); } recursed--; if (type == LOG_TYPE_PANIC || getenv("CORE_ERROR") != NULL || (status == FATAL_OUTOFMEM && getenv("CORE_OUTOFMEM") != NULL)) abort(); else failure_exit(status); } static void ATTR_NORETURN fatal_handler_real(const struct failure_context *ctx, const char *format, va_list args) { int status = ctx->exit_status; if (common_handler(ctx, format, args) < 0 && status == FATAL_DEFAULT) status = failure_handler.fatal_err_reset; default_fatal_finish(ctx->type, status); } void default_fatal_handler(const struct failure_context *ctx, const char *format, va_list args) { failure_handler.v = &default_handler_vfuncs; failure_handler.fatal_err_reset = FATAL_LOGWRITE; fatal_handler_real(ctx, format, args); } void default_error_handler(const struct failure_context *ctx, const char *format, va_list args) { failure_handler.v = &default_handler_vfuncs; failure_handler.fatal_err_reset = FATAL_LOGWRITE; error_handler_real(ctx, format, args); } void i_log_type(const struct failure_context *ctx, const char *format, ...) { va_list args; va_start(args, format); i_log_typev(ctx, format, args); va_end(args); } void i_log_typev(const struct failure_context *ctx, const char *format, va_list args) { switch (ctx->type) { case LOG_TYPE_DEBUG: debug_handler(ctx, format, args); break; case LOG_TYPE_INFO: info_handler(ctx, format, args); break; default: error_handler(ctx, format, args); } } void i_panic(const char *format, ...) { struct failure_context ctx; va_list args; lib_set_clean_exit(TRUE); i_zero(&ctx); ctx.type = LOG_TYPE_PANIC; va_start(args, format); fatal_handler(&ctx, format, args); i_unreached(); /*va_end(args);*/ } void i_fatal(const char *format, ...) { struct failure_context ctx; va_list args; lib_set_clean_exit(TRUE); i_zero(&ctx); ctx.type = LOG_TYPE_FATAL; ctx.exit_status = FATAL_DEFAULT; va_start(args, format); fatal_handler(&ctx, format, args); i_unreached(); /*va_end(args);*/ } void i_fatal_status(int status, const char *format, ...) { struct failure_context ctx; va_list args; lib_set_clean_exit(TRUE); i_zero(&ctx); ctx.type = LOG_TYPE_FATAL; ctx.exit_status = status; va_start(args, format); fatal_handler(&ctx, format, args); i_unreached(); /*va_end(args);*/ } void i_error(const char *format, ...) { int old_errno = errno; va_list args; va_start(args, format); error_handler(&failure_ctx_error, format, args); va_end(args); errno = old_errno; } void i_warning(const char *format, ...) { int old_errno = errno; va_list args; va_start(args, format); error_handler(&failure_ctx_warning, format, args); va_end(args); errno = old_errno; } void i_info(const char *format, ...) { int old_errno = errno; va_list args; va_start(args, format); info_handler(&failure_ctx_info, format, args); va_end(args); errno = old_errno; } void i_debug(const char *format, ...) { int old_errno = errno; va_list args; va_start(args, format); debug_handler(&failure_ctx_debug, format, args); va_end(args); errno = old_errno; } void i_set_fatal_handler(failure_callback_t *callback ATTR_NORETURN) { fatal_handler = callback; } void i_set_error_handler(failure_callback_t *callback) { coredump_on_error = getenv("CORE_ERROR") != NULL; error_handler = callback; } void i_set_info_handler(failure_callback_t *callback) { info_handler = callback; } void i_set_debug_handler(failure_callback_t *callback) { debug_handler = callback; } void i_get_failure_handlers(failure_callback_t **fatal_callback_r, failure_callback_t **error_callback_r, failure_callback_t **info_callback_r, failure_callback_t **debug_callback_r) { *fatal_callback_r = fatal_handler; *error_callback_r = error_handler; *info_callback_r = info_handler; *debug_callback_r = debug_handler; } void i_syslog_fatal_handler(const struct failure_context *ctx, const char *format, va_list args) { failure_handler.v = &syslog_handler_vfuncs; failure_handler.fatal_err_reset = FATAL_LOGERROR; fatal_handler_real(ctx, format, args); } void i_syslog_error_handler(const struct failure_context *ctx, const char *format, va_list args) { failure_handler.v = &syslog_handler_vfuncs; failure_handler.fatal_err_reset = FATAL_LOGERROR; error_handler_real(ctx, format, args); } void i_set_failure_syslog(const char *ident, int options, int facility) { openlog(ident, options, facility); i_set_fatal_handler(i_syslog_fatal_handler); i_set_error_handler(i_syslog_error_handler); i_set_info_handler(i_syslog_error_handler); i_set_debug_handler(i_syslog_error_handler); } static void open_log_file(int *fd, const char *path) { const char *str; if (*fd != STDERR_FILENO) { if (close(*fd) < 0) { str = t_strdup_printf("close(%d) failed: %m\n", *fd); (void)write_full(STDERR_FILENO, str, strlen(str)); } } if (path == NULL || strcmp(path, "/dev/stderr") == 0) *fd = STDERR_FILENO; else { *fd = open(path, O_CREAT | O_APPEND | O_WRONLY, 0600); if (*fd == -1) { *fd = STDERR_FILENO; str = t_strdup_printf("Can't open log file %s: %m\n", path); (void)write_full(STDERR_FILENO, str, strlen(str)); if (fd == &log_fd) failure_exit(FATAL_LOGOPEN); else i_fatal_status(FATAL_LOGOPEN, "%s", str); } fd_close_on_exec(*fd, TRUE); } } void i_set_failure_file(const char *path, const char *prefix) { i_set_failure_prefix("%s", prefix); if (log_info_fd != STDERR_FILENO && log_info_fd != log_fd) { if (close(log_info_fd) < 0) i_error("close(%d) failed: %m", log_info_fd); } if (log_debug_fd != STDERR_FILENO && log_debug_fd != log_info_fd && log_debug_fd != log_fd) { if (close(log_debug_fd) < 0) i_error("close(%d) failed: %m", log_debug_fd); } open_log_file(&log_fd, path); /* if info/debug logs are elsewhere, i_set_info/debug_file() overrides these later. */ log_info_fd = log_fd; log_debug_fd = log_fd; i_set_fatal_handler(default_fatal_handler); i_set_error_handler(default_error_handler); i_set_info_handler(default_error_handler); i_set_debug_handler(default_error_handler); } static int i_failure_send_option_forced(const char *key, const char *value) { const char *str; str = t_strdup_printf("\001%c%s %s=%s\n", LOG_TYPE_OPTION+1, my_pid, key, value); return log_fd_write(STDERR_FILENO, (const unsigned char *)str, strlen(str)); } static void i_failure_send_option(const char *key, const char *value) { if (error_handler == i_internal_error_handler) (void)i_failure_send_option_forced(key, value); } void i_set_failure_prefix(const char *prefix_fmt, ...) { va_list args; va_start(args, prefix_fmt); i_free(log_prefix); log_prefix = i_strdup_vprintf(prefix_fmt, args); va_end(args); log_prefix_sent = FALSE; } void i_unset_failure_prefix(void) { i_free(log_prefix); log_prefix = i_strdup(""); log_prefix_sent = FALSE; } const char *i_get_failure_prefix(void) { return log_prefix != NULL ? log_prefix : ""; } static int internal_send_split(string_t *full_str, size_t prefix_len) { /* This function splits the log line into PIPE_BUF sized blocks, so the log process doesn't see partial lines. The log prefix is repeated for each sent line. However, if the log prefix is excessively long, we're still going to send the log lines even if they are longer than PIPE_BUF. LINE_MIN_TEXT_LEN controls the minimum number of bytes we're going to send of the actual log line regardless of the log prefix length. (Alternative solution could be to just forcibly split the line to PIPE_BUF length blocks without repeating the log prefix for subsequent lines.) */ #define LINE_MIN_TEXT_LEN 128 #if LINE_MIN_TEXT_LEN >= PIPE_BUF # error LINE_MIN_TEXT_LEN too large #endif string_t *str; size_t max_text_len, pos = prefix_len; str = t_str_new(PIPE_BUF); str_append_data(str, str_data(full_str), prefix_len); if (prefix_len < PIPE_BUF) { max_text_len = I_MAX(PIPE_BUF - prefix_len - 1, LINE_MIN_TEXT_LEN); } else { max_text_len = LINE_MIN_TEXT_LEN; } while (pos < str_len(full_str)) { str_truncate(str, prefix_len); str_append_max(str, str_c(full_str) + pos, max_text_len); str_append_c(str, '\n'); if (log_fd_write(STDERR_FILENO, str_data(str), str_len(str)) < 0) return -1; pos += max_text_len; } return 0; } static bool line_parse_prefix(const char *line, enum log_type *log_type_r, bool *replace_prefix_r, bool *have_prefix_len_r) { if (*line != 1) return FALSE; unsigned char log_type = (line[1] & 0x3f); if (log_type == '\0') { i_warning("Broken log line follows (type=NUL)"); return FALSE; } log_type--; if (log_type > LOG_TYPE_OPTION) { i_warning("Broken log line follows (type=%d)", log_type); return FALSE; } *log_type_r = log_type; *replace_prefix_r = (line[1] & LOG_TYPE_FLAG_DISABLE_LOG_PREFIX) != 0; *have_prefix_len_r = (line[1] & LOG_TYPE_FLAG_PREFIX_LEN) != 0; return TRUE; } void i_failure_parse_line(const char *line, struct failure_line *failure) { bool have_prefix_len = FALSE; i_zero(failure); if (!line_parse_prefix(line, &failure->log_type, &failure->disable_log_prefix, &have_prefix_len)) { failure->log_type = LOG_TYPE_ERROR; failure->text = line; return; } line += 2; failure->text = line; while (*line >= '0' && *line <= '9') { failure->pid = failure->pid*10 + (*line - '0'); line++; } if (*line != ' ') { /* some old protocol? */ failure->pid = 0; return; } line++; if (have_prefix_len) { if (str_parse_uint(line, &failure->log_prefix_len, &line) < 0 || line[0] != ' ') { /* unexpected, but ignore */ } else { line++; if (failure->log_prefix_len > strlen(line)) { /* invalid */ failure->log_prefix_len = 0; } } } failure->text = line; } static void ATTR_NORETURN ATTR_FORMAT(2, 0) i_internal_fatal_handler(const struct failure_context *ctx, const char *format, va_list args) { failure_handler.v = &internal_handler_vfuncs; failure_handler.fatal_err_reset = FATAL_LOGERROR; fatal_handler_real(ctx, format, args); } static void i_internal_error_handler(const struct failure_context *ctx, const char *format, va_list args) { failure_handler.v = &internal_handler_vfuncs; failure_handler.fatal_err_reset = FATAL_LOGERROR; error_handler_real(ctx, format, args); } void i_set_failure_internal(void) { fd_set_nonblock(STDERR_FILENO, TRUE); i_set_fatal_handler(i_internal_fatal_handler); i_set_error_handler(i_internal_error_handler); i_set_info_handler(i_internal_error_handler); i_set_debug_handler(i_internal_error_handler); } bool i_failure_handler_is_internal(failure_callback_t *const callback) { return callback == i_internal_fatal_handler || callback == i_internal_error_handler; } void i_set_failure_ignore_errors(bool ignore) { failure_ignore_errors = ignore; } void i_set_info_file(const char *path) { if (log_info_fd == log_fd) log_info_fd = STDERR_FILENO; open_log_file(&log_info_fd, path); info_handler = default_error_handler; /* write debug-level messages to the info_log_path, until i_set_debug_file() was called */ log_debug_fd = log_info_fd; i_set_debug_handler(default_error_handler); } void i_set_debug_file(const char *path) { if (log_debug_fd == log_fd || log_debug_fd == log_info_fd) log_debug_fd = STDERR_FILENO; open_log_file(&log_debug_fd, path); debug_handler = default_error_handler; } void i_set_failure_timestamp_format(const char *fmt) { const char *p; i_free(log_stamp_format); i_free_and_null(log_stamp_format_suffix); p = strstr(fmt, "%{usecs}"); if (p == NULL) log_stamp_format = i_strdup(fmt); else { log_stamp_format = i_strdup_until(fmt, p); log_stamp_format_suffix = i_strdup(p + 8); } } void i_set_failure_send_ip(const struct ip_addr *ip) { i_failure_send_option("ip", net_ip2addr(ip)); } void i_set_failure_send_prefix(const char *prefix) { i_failure_send_option("prefix", prefix); } void i_set_failure_exit_callback(void (*callback)(int *status)) { failure_exit_callback = callback; } void failures_deinit(void) { if (log_debug_fd == log_info_fd || log_debug_fd == log_fd) log_debug_fd = STDERR_FILENO; if (log_info_fd == log_fd) log_info_fd = STDERR_FILENO; if (log_fd != STDERR_FILENO) { i_close_fd(&log_fd); log_fd = STDERR_FILENO; } if (log_info_fd != STDERR_FILENO) { i_close_fd(&log_info_fd); log_info_fd = STDERR_FILENO; } if (log_debug_fd != STDERR_FILENO) { i_close_fd(&log_debug_fd); log_debug_fd = STDERR_FILENO; } i_free_and_null(log_prefix); i_free_and_null(log_stamp_format); i_free_and_null(log_stamp_format_suffix); } #undef i_unreached void i_unreached(const char *source_filename, int source_linenum) { i_panic("file %s: line %d: unreached", source_filename, source_linenum); } dovecot-2.3.21.1/src/lib/bits.h0000644000000000000000000001337314656633576012773 00000000000000#ifndef BITS_H #define BITS_H #define UINT64_SUM_OVERFLOWS(a, b) \ (a > (uint64_t)-1 - b) #define BIT(n) (1u << (n)) /* These expressions make it easy to ensure that bit test expressions are boolean in order to satisfy the in-house -Wstrict-bool. */ /* ((val & bits) == 0) is very common */ #define HAS_NO_BITS(val,bits) (((val) & (bits)) == 0) /* ((val & bits) != 0) is even more common */ /* Note - illogical behaviour if bits==0, fixing that requires potential multiple evaluation, but it's a corner case that should never occur. */ #define HAS_ANY_BITS(val,bits) (((val) & (bits)) != 0) /* ((val & bits) == bits) is uncommon */ #define HAS_ALL_BITS(val,bits) ((~(val) & (bits)) == 0) /* negation implemented without using the subtraction operator ~(x - 1) = 1 + ~x these are equivalent by -(-x) == ~(~(x)) == x */ #define UNSIGNED_MINUS(x) (1 + ~(x)) /* Returns x, such that x is the smallest power of 2 >= num. */ size_t nearest_power(size_t num) ATTR_CONST; #if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) /* Returns TRUE if 2^x=num, i.e. if num has only a single bit set to 1. */ static inline bool ATTR_CONST bits_is_power_of_two(uint64_t num) { return __builtin_popcountll(num) == 1; } static inline unsigned int ATTR_CONST bits_required32(uint32_t num) { return num == 0 ? 0 : 32 - __builtin_clz(num); } static inline unsigned int ATTR_CONST bits_required8(uint8_t num) { return bits_required32(num); } static inline unsigned int ATTR_CONST bits_required16(uint16_t num) { return bits_required32(num); } static inline unsigned int ATTR_CONST bits_required64(uint64_t num) { return num == 0 ? 0 : 64 - __builtin_clzll(num); } #else /* Returns TRUE if 2^x=num, i.e. if num has only a single bit set to 1. */ static inline bool ATTR_CONST bits_is_power_of_two(uint64_t num) { return num != 0 && (num & (num + ~0ULL)) == 0; } unsigned int bits_required8(uint8_t num) ATTR_CONST; static inline unsigned int bits_required16(uint16_t num) { return (num <= 0xff) ? bits_required8(num) : 8 + bits_required8(num >> 8); } static inline unsigned int bits_required32(uint32_t num) { return (num <= 0xffff) ? bits_required16(num) : 16 + bits_required16(num >> 16); } static inline unsigned int bits_required64(uint64_t num) { return (num <= 0xffffffff) ? bits_required32(num) : 32 + bits_required32(num >> 32); } #endif static inline uint64_t ATTR_NO_SANITIZE_INTEGER ATTR_NO_SANITIZE_IMPLICIT_CONVERSION bits_rotl64(uint64_t num, unsigned int count) { const unsigned int mask = CHAR_BIT*sizeof(num) - 1; count &= mask; return (num << count) | (num >> (UNSIGNED_MINUS(count) & mask)); } static inline uint32_t ATTR_NO_SANITIZE_INTEGER ATTR_NO_SANITIZE_IMPLICIT_CONVERSION bits_rotl32(uint32_t num, unsigned int count) { const unsigned int mask = CHAR_BIT*sizeof(num) - 1; count &= mask; return (num << count) | (num >> (UNSIGNED_MINUS(count) & mask)); } static inline uint64_t ATTR_NO_SANITIZE_INTEGER ATTR_NO_SANITIZE_IMPLICIT_CONVERSION bits_rotr64(uint64_t num, unsigned int count) { const unsigned int mask = CHAR_BIT*sizeof(num) - 1; count &= mask; return (num >> count) | (num << (UNSIGNED_MINUS(count) & mask)); } static inline uint32_t ATTR_NO_SANITIZE_INTEGER ATTR_NO_SANITIZE_IMPLICIT_CONVERSION bits_rotr32(uint32_t num, unsigned int count) { const unsigned int mask = CHAR_BIT*sizeof(num) - 1; count &= mask; return (num >> count) | (num << (UNSIGNED_MINUS(count) & mask)); } /* These functions look too big to be inline, but in almost all expected uses, 'fracbits' will be a compile-time constant, and most of the expressions will simplify greatly. */ /* Perform a piecewise-linear approximation to a log2, with fracbits "fractional" bits. Best explained with examples: With 2 fractional bits splitting each power of 2 into 4 bands: 00, 01, 10, 11 -> 00, 01, 10, 11 (small corner cases) 100, 101, 110, 111 -> 100, 101, 110, 111 ([4-8) split into 4 bands) 1000, 1001, 1010, 1011 -> 1000, 1000, 1001, 1001 ([8-15) split ... 1100, 1101, 1110, 1111 -> 1010, 1010, 1011, 1011 ... into 4 bands) [16..31) -> 11bb [32..63) -> 100bb [64..127) -> 101bb [128..255) -> 110bb e.g. 236 = 11101100 -> ((8-2)<<2 == 11000) + (111.....>> 5 == 111) - 100 == 11011 */ static inline unsigned int ATTR_CONST bits_fraclog(unsigned int val, unsigned int fracbits) { unsigned bits = bits_required32(val); if (bits <= fracbits + 1) return val; unsigned int bandnum = bits - fracbits; unsigned int bandstart = bandnum << fracbits; unsigned int fracoffsbad = val >> (bandnum - 1); /* has leading 1 still */ unsigned int bucket = bandstart + fracoffsbad - BIT(fracbits); return bucket; } static inline unsigned int ATTR_CONST bits_fraclog_bucket_start(unsigned int bucket, unsigned int fracbits) { unsigned int bandnum = bucket >> fracbits; if (bandnum <= 1) return bucket; if (fracbits == 0) return BIT(bucket - 1); unsigned int fracoffs = bucket & (BIT(fracbits)-1); unsigned int fracoffs1 = BIT(fracbits) + fracoffs; unsigned int bandstart = fracoffs1 << (bandnum - 1); return bandstart; } static inline unsigned int ATTR_CONST ATTR_NO_SANITIZE_INTEGER ATTR_NO_SANITIZE_IMPLICIT_CONVERSION bits_fraclog_bucket_end(unsigned int bucket, unsigned int fracbits) { unsigned int bandnum = bucket >> fracbits; if (bandnum <= 1) return bucket; if (fracbits == 0) return BIT(bucket - 1) * 2 - 1; unsigned int fracoffs = bucket & (BIT(fracbits)-1); unsigned int nextfracoffs1 = 1 + BIT(fracbits) + fracoffs; unsigned int nextbandstart = nextfracoffs1 << (bandnum - 1); return nextbandstart - 1; } /* UNSAFE: multiple use of parameter (but expecting a constant in reality). But a macro as it's most likely to be used to declare an array size. */ #define BITS_FRACLOG_BUCKETS(bits) ((33u - (bits)) << (bits)) #endif dovecot-2.3.21.1/src/lib/test-strescape.c0000644000000000000000000001437214656633576014773 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str.h" #include "strescape.h" struct strinput { const char *input; const char *output; }; static const char tabescaped_input[] = "\0011\001t\001r\001nplip\001n"; static const char tabescaped_input_with_nul[] = "\0011\001t\001r\001nplip\001n\0010"; static const char tabunescaped_input[] = "\001\t\r\nplip\n"; static const char *wrong_tabescaped_input = "a\001\001b\001\nc\0011\001t\001r\001nplip\001n"; static const char *wrong_tabescaped_output = "a\001b\nc\001\t\r\nplip\n"; static struct { const char *input; const char *const *output; } strsplit_tests[] = { { /*tabescaped_input3*/NULL, (const char *const []) { tabunescaped_input, tabunescaped_input, tabunescaped_input, "", NULL } }, { "", (const char *const []) { NULL } }, { "\t", (const char *const []) { "", "", NULL } }, { tabescaped_input, (const char *const []) { tabunescaped_input, NULL } }, }; static void test_str_escape(void) { static const struct strinput unesc[] = { { "foo", "foo" }, { "\\\\\\\\\\\"\\\"\\\'\\\'", "\\\\\"\"\'\'" }, { "\\a\\n\\r\\", "anr" } }; static const struct strinput tabesc[] = { { "foo", "foo" }, { "\001", "\0011" }, { "\t", "\001t" }, { "\r", "\001r" }, { "\n", "\001n" }, { "\001\001\t\t\r\r\n\n", "\0011\0011\001t\001t\001r\001r\001n\001n" } }; unsigned char buf[1 << CHAR_BIT]; const char *escaped, *tabstr, *unesc_str; string_t *str; unsigned int i; test_begin("str_escape"); for (i = 1; i < sizeof(buf); i++) buf[i-1] = i; buf[i-1] = '\0'; escaped = str_escape((char *)buf); test_assert(strlen(escaped) == (1 << CHAR_BIT) - 1 + 3); test_assert(escaped['\"'-1] == '\\'); /* 34 */ test_assert(escaped['\"'] == '\"'); test_assert(escaped['\''+1-1] == '\\'); /* 39 */ test_assert(escaped['\''+1] == '\''); test_assert(escaped['\\'+2-1] == '\\'); /* 92 */ test_assert(escaped['\\'+2] == '\\'); test_assert(strcmp(str_escape("\\\\\"\"\'\'"), "\\\\\\\\\\\"\\\"\\\'\\\'") == 0); test_end(); test_begin("str_nescape"); escaped = str_nescape("\"escape only first but not 'this'", 10); test_assert(strcmp(escaped, "\\\"escape on") == 0); escaped = str_nescape("\"hello\"\0\"world\"", 15); test_assert(memcmp(escaped, "\\\"hello\\\"\0\\\"world\\\"", 19) == 0); test_end(); str = t_str_new(256); test_begin("str_unescape"); for (i = 0; i < N_ELEMENTS(unesc); i++) { test_assert(strcmp(str_unescape(t_strdup_noconst(unesc[i].input)), unesc[i].output) == 0); str_truncate(str, 0); str_append_unescaped(str, unesc[i].input, strlen(unesc[i].input)); test_assert(strcmp(str_c(str), unesc[i].output) == 0); } test_end(); test_begin("str_unescape_next"); escaped = "foo\"bar\\\"b\\\\az\"plop"; test_assert(str_unescape_next(&escaped, &unesc_str) == 0); test_assert(strcmp(unesc_str, "foo") == 0); test_assert(str_unescape_next(&escaped, &unesc_str) == 0); test_assert(strcmp(unesc_str, "bar\"b\\az") == 0); test_assert(str_unescape_next(&escaped, &unesc_str) == -1); escaped = "foo\\"; test_assert(str_unescape_next(&escaped, &unesc_str) == -1); test_end(); test_begin("str_tabescape"); for (i = 0; i < N_ELEMENTS(tabesc); i++) { test_assert(strcmp(t_str_tabunescape(tabesc[i].output), tabesc[i].input) == 0); test_assert(strcmp(str_tabunescape(t_strdup_noconst(tabesc[i].output)), tabesc[i].input) == 0); test_assert(strcmp(str_tabescape(tabesc[i].input), tabesc[i].output) == 0); str_truncate(str, 0); str_append_tabunescaped(str, tabesc[i].output, strlen(tabesc[i].output)); test_assert(strcmp(str_c(str), tabesc[i].input) == 0); } str_truncate(str, 0); tabstr = "\0012\001l\001"; str_append_tabunescaped(str, tabstr, strlen(tabstr)); test_assert(strcmp(str_c(str), "2l") == 0); test_assert(strcmp(str_c(str), str_tabunescape(t_strdup_noconst(tabstr))) == 0); test_end(); } static void test_tabescape(void) { string_t *str = t_str_new(128); test_begin("string tabescaping"); test_assert(strcmp(str_tabescape(tabunescaped_input), tabescaped_input) == 0); str_append_tabescaped(str, tabunescaped_input); test_assert(strcmp(str_c(str), tabescaped_input) == 0); /* test escaping the trailing NUL as well */ str_truncate(str, 0); str_append_tabescaped_n(str, (const unsigned char *)tabunescaped_input, strlen(tabunescaped_input)+1); test_assert_strcmp(str_c(str), tabescaped_input_with_nul); /* unescaping */ str_truncate(str, 0); str_append_tabunescaped(str, tabescaped_input, strlen(tabescaped_input)); test_assert(strcmp(str_c(str), tabunescaped_input) == 0); test_assert(strcmp(str_tabunescape(t_strdup_noconst(tabescaped_input)), tabunescaped_input) == 0); test_assert(strcmp(t_str_tabunescape(tabescaped_input), tabunescaped_input) == 0); /* unescaping with wrongly written tabescape-input */ str_truncate(str, 0); str_append_tabunescaped(str, wrong_tabescaped_input, strlen(wrong_tabescaped_input)); test_assert(strcmp(str_c(str), wrong_tabescaped_output) == 0); test_assert(strcmp(str_tabunescape(t_strdup_noconst(wrong_tabescaped_input)), wrong_tabescaped_output) == 0); test_assert(strcmp(t_str_tabunescape(wrong_tabescaped_input), wrong_tabescaped_output) == 0); test_end(); } static void test_strsplit_tabescaped(void) { const char *const *args; test_begin("*_strsplit_tabescaped()"); for (unsigned int i = 0; i < N_ELEMENTS(strsplit_tests); i++) { args = t_strsplit_tabescaped(strsplit_tests[i].input); for (unsigned int j = 0; strsplit_tests[i].output[j] != NULL; j++) test_assert_idx(null_strcmp(strsplit_tests[i].output[j], args[j]) == 0, i); } test_end(); } static void test_strsplit_tabescaped_inplace(void) { const char *const *args; test_begin("*_strsplit_tabescaped_inplace()"); for (unsigned int i = 0; i < N_ELEMENTS(strsplit_tests); i++) { char *input = t_strdup_noconst(strsplit_tests[i].input); args = t_strsplit_tabescaped_inplace(input); for (unsigned int j = 0; strsplit_tests[i].output[j] != NULL; j++) test_assert_idx(null_strcmp(strsplit_tests[i].output[j], args[j]) == 0, i); } test_end(); } void test_strescape(void) { strsplit_tests[0].input = t_strdup_printf("%s\t%s\t%s\t", tabescaped_input, tabescaped_input, tabescaped_input); test_str_escape(); test_tabescape(); test_strsplit_tabescaped(); test_strsplit_tabescaped_inplace(); } dovecot-2.3.21.1/src/lib/test-base64.c0000644000000000000000000010260014656633576014056 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str.h" #include "base64.h" static void test_base64_encode(void) { const struct { const char *input; const char *output; } tests[] = { { "hello world", "aGVsbG8gd29ybGQ=" }, { "foo barits", "Zm9vIGJhcml0cw==" }, { "just niin", "anVzdCBuaWlu" }, { "\xe7\x8c\xbf\xe3\x82\x82\xe6\x9c\xa8\xe3\x81\x8b" "\xe3\x82\x89\xe8\x90\xbd\xe3\x81\xa1\xe3\x82\x8b", "54y/44KC5pyo44GL44KJ6JC944Gh44KL" }, { "\xe8\xa7\x92\xe3\x82\x92\xe7\x9f\xaf\xe3\x82\x81\xe3\x81" "\xa6\xe7\x89\x9b\xe3\x82\x92\xe6\xae\xba\xe3\x81\x99", "6KeS44KS55+v44KB44Gm54mb44KS5q6644GZ" }, }; string_t *str; unsigned int i; test_begin("base64_encode()"); str = t_str_new(256); for (i = 0; i < N_ELEMENTS(tests); i++) { str_truncate(str, 0); base64_encode(tests[i].input, strlen(tests[i].input), str); test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i); test_assert_idx( str_len(str) == MAX_BASE64_ENCODED_SIZE( strlen(tests[i].input)), i); } test_end(); } struct test_base64_decode { const char *input; const char *output; int ret; }; static void test_base64_decode(void) { static const struct test_base64_decode tests[] = { { "", "", 0 }, { "\taGVsbG8gd29ybGQ=", "hello world", 0 }, { "\nZm9v\n \tIGJh \t\ncml0cw==", "foo barits", 0 }, { " anVzdCBuaWlu \n", "just niin", 0 }, { "aGVsb", "hel", -1 }, { "aGVsb!!!!!", "hel", -1 }, { "aGVs!!!!!", "hel", -1 }, { "0JPQvtCy0L7RgNGPzIHRgiwg0YfRgt" "C+INC60YPRgCDQtNC+0Y/MgdGCLg==", "\xd0\x93\xd0\xbe\xd0\xb2\xd0\xbe\xd1\x80\xd1\x8f\xcc" "\x81\xd1\x82\x2c\x20\xd1\x87\xd1\x82\xd0\xbe\x20\xd0" "\xba\xd1\x83\xd1\x80\x20\xd0\xb4\xd0\xbe\xd1\x8f\xcc" "\x81\xd1\x82\x2e", 0 }, }; string_t *str; buffer_t buf; unsigned int i; int ret; test_begin("base64_decode()"); for (i = 0; i < N_ELEMENTS(tests); i++) { /* Some of the base64_decode() callers use fixed size buffers. Use a fixed size buffer here as well to test that base64_decode() can't allocate any extra space even temporarily. */ size_t max_decoded_size = MAX_BASE64_DECODED_SIZE(strlen(tests[i].input)); buffer_create_from_data(&buf, (max_decoded_size == 0 ? "" : t_malloc0(max_decoded_size)), max_decoded_size); str = &buf; ret = base64_decode(tests[i].input, strlen(tests[i].input), NULL, str); test_assert_idx(tests[i].ret == ret, i); test_assert_idx(strlen(tests[i].output) == str_len(str) && memcmp(tests[i].output, str_data(str), str_len(str)) == 0, i); if (ret >= 0) { test_assert_idx( str_len(str) <= MAX_BASE64_DECODED_SIZE( strlen(tests[i].input)), i); } } test_end(); } static void test_base64_random(void) { string_t *str, *dest; unsigned char buf[10]; unsigned int i, j, max; str = t_str_new(256); dest = t_str_new(256); test_begin("base64 encode/decode with random input"); for (i = 0; i < 1000; i++) { max = i_rand_limit(sizeof(buf)); for (j = 0; j < max; j++) buf[j] = i_rand_uchar(); str_truncate(str, 0); str_truncate(dest, 0); base64_encode(buf, max, str); test_assert_idx(base64_decode(str_data(str), str_len(str), NULL, dest) >= 0, i); test_assert_idx(str_len(dest) == max && memcmp(buf, str_data(dest), max) == 0, i); } test_end(); } static void test_base64url_encode(void) { const struct { const char *input; const char *output; } tests[] = { { "", "" }, { "hello world", "aGVsbG8gd29ybGQ=" }, { "foo barits", "Zm9vIGJhcml0cw==" }, { "just niin", "anVzdCBuaWlu" }, { "\xe7\x8c\xbf\xe3\x82\x82\xe6\x9c\xa8\xe3\x81\x8b" "\xe3\x82\x89\xe8\x90\xbd\xe3\x81\xa1\xe3\x82\x8b", "54y_44KC5pyo44GL44KJ6JC944Gh44KL" }, { "\xe8\xa7\x92\xe3\x82\x92\xe7\x9f\xaf\xe3\x82\x81\xe3\x81" "\xa6\xe7\x89\x9b\xe3\x82\x92\xe6\xae\xba\xe3\x81\x99", "6KeS44KS55-v44KB44Gm54mb44KS5q6644GZ" }, }; string_t *str; unsigned int i; test_begin("base64url_encode()"); str = t_str_new(256); for (i = 0; i < N_ELEMENTS(tests); i++) { str_truncate(str, 0); base64url_encode(0, 0, tests[i].input, strlen(tests[i].input), str); test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i); test_assert_idx( str_len(str) == MAX_BASE64_ENCODED_SIZE( strlen(tests[i].input)), i); } test_end(); } struct test_base64url_decode { const char *input; const char *output; int ret; }; static void test_base64url_decode(void) { static const struct test_base64url_decode tests[] = { { "", "", 0 }, { "\taGVsbG8gd29ybGQ=", "hello world", 0 }, { "\nZm9v\n \tIGJh \t\ncml0cw==", "foo barits", 0 }, { " anVzdCBuaWlu \n", "just niin", 0 }, { "aGVsb", "hel", -1 }, { "aGVsb!!!!!", "hel", -1 }, { "aGVs!!!!!", "hel", -1 }, { "0JPQvtCy0L7RgNGPzIHRgiwg0YfRgt" "C-INC60YPRgCDQtNC-0Y_MgdGCLg==", "\xd0\x93\xd0\xbe\xd0\xb2\xd0\xbe\xd1\x80\xd1\x8f\xcc" "\x81\xd1\x82\x2c\x20\xd1\x87\xd1\x82\xd0\xbe\x20\xd0" "\xba\xd1\x83\xd1\x80\x20\xd0\xb4\xd0\xbe\xd1\x8f\xcc" "\x81\xd1\x82\x2e", 0 }, }; string_t *str; buffer_t buf; unsigned int i; int ret; test_begin("base64url_decode()"); for (i = 0; i < N_ELEMENTS(tests); i++) { /* Some of the base64_decode() callers use fixed size buffers. Use a fixed size buffer here as well to test that base64_decode() can't allocate any extra space even temporarily. */ size_t max_decoded_size = MAX_BASE64_DECODED_SIZE(strlen(tests[i].input)); buffer_create_from_data(&buf, (max_decoded_size == 0 ? "" : t_malloc0(max_decoded_size)), max_decoded_size); str = &buf; ret = base64url_decode(0, tests[i].input, strlen(tests[i].input), str); test_assert_idx(tests[i].ret == ret, i); test_assert_idx(strlen(tests[i].output) == str_len(str) && memcmp(tests[i].output, str_data(str), str_len(str)) == 0, i); if (ret >= 0) { test_assert_idx( str_len(str) <= MAX_BASE64_DECODED_SIZE( strlen(tests[i].input)), i); } } test_end(); } static void test_base64url_random(void) { string_t *str, *dest; unsigned char buf[10]; unsigned int i, j, max; str = t_str_new(256); dest = t_str_new(256); test_begin("base64url encode/decode with random input"); for (i = 0; i < 1000; i++) { max = i_rand_limit(sizeof(buf)); for (j = 0; j < max; j++) buf[j] = i_rand_uchar(); str_truncate(str, 0); str_truncate(dest, 0); base64url_encode(0, 0, buf, max, str); test_assert_idx(base64url_decode(0, str_data(str), str_len(str), dest) >= 0, i); test_assert_idx(str_len(dest) == max && memcmp(buf, str_data(dest), max) == 0, i); } test_end(); } struct test_base64_encode_lowlevel { const struct base64_scheme *scheme; enum base64_encode_flags flags; size_t max_line_len; const char *input; const char *output; }; static const struct test_base64_encode_lowlevel tests_base64_encode_lowlevel[] = { { .scheme = &base64_scheme, .input = "", .output = "", }, { .scheme = &base64_scheme, .max_line_len = 2, .input = "", .output = "", }, { .scheme = &base64_scheme, .flags = BASE64_ENCODE_FLAG_CRLF, .max_line_len = 2, .input = "", .output = "", }, { .scheme = &base64_scheme, .flags = BASE64_ENCODE_FLAG_NO_PADDING, .input = "", .output = "", }, { .scheme = &base64_scheme, .input = "hello world", .output = "aGVsbG8gd29ybGQ=", }, { .scheme = &base64url_scheme, .input = "hello world", .output = "aGVsbG8gd29ybGQ=", }, { .scheme = &base64_scheme, .flags = BASE64_ENCODE_FLAG_NO_PADDING, .input = "hello world", .output = "aGVsbG8gd29ybGQ", }, { .scheme = &base64_scheme, .input = "foo barits", .output = "Zm9vIGJhcml0cw==", }, { .scheme = &base64url_scheme, .input = "foo barits", .output = "Zm9vIGJhcml0cw==", }, { .scheme = &base64_scheme, .flags = BASE64_ENCODE_FLAG_NO_PADDING, .input = "foo barits", .output = "Zm9vIGJhcml0cw", }, { .scheme = &base64_scheme, .input = "just niin", .output = "anVzdCBuaWlu", }, { .scheme = &base64url_scheme, .input = "just niin", .output = "anVzdCBuaWlu", }, { .scheme = &base64_scheme, .flags = BASE64_ENCODE_FLAG_NO_PADDING, .input = "just niin", .output = "anVzdCBuaWlu", }, { .scheme = &base64_scheme, .input = "\xe7\x8c\xbf\xe3\x82\x82\xe6\x9c\xa8\xe3\x81\x8b" "\xe3\x82\x89\xe8\x90\xbd\xe3\x81\xa1\xe3\x82\x8b", .output = "54y/44KC5pyo44GL44KJ6JC944Gh44KL", }, { .scheme = &base64url_scheme, .input = "\xe7\x8c\xbf\xe3\x82\x82\xe6\x9c\xa8\xe3\x81\x8b" "\xe3\x82\x89\xe8\x90\xbd\xe3\x81\xa1\xe3\x82\x8b", .output = "54y_44KC5pyo44GL44KJ6JC944Gh44KL", }, { .scheme = &base64_scheme, .input = "\xe8\xa7\x92\xe3\x82\x92\xe7\x9f\xaf\xe3\x82\x81\xe3" "\x81\xa6\xe7\x89\x9b\xe3\x82\x92\xe6\xae\xba\xe3\x81" "\x99", .output = "6KeS44KS55+v44KB44Gm54mb44KS5q6644GZ", }, { .scheme = &base64url_scheme, .input = "\xe8\xa7\x92\xe3\x82\x92\xe7\x9f\xaf\xe3\x82\x81\xe3" "\x81\xa6\xe7\x89\x9b\xe3\x82\x92\xe6\xae\xba\xe3\x81" "\x99", .output = "6KeS44KS55-v44KB44Gm54mb44KS5q6644GZ", }, { .scheme = &base64_scheme, .flags = BASE64_ENCODE_FLAG_CRLF, .input = "just niin", .output = "anVzdCBuaWlu", }, { .scheme = &base64_scheme, .flags = BASE64_ENCODE_FLAG_CRLF, .max_line_len = 80, .input = "just niin", .output = "anVzdCBuaWlu", }, { .scheme = &base64_scheme, .flags = BASE64_ENCODE_FLAG_CRLF, .max_line_len = 48, .input = "Passer, deliciae meae puellae,\n" "quicum ludere, quem in sinu tenere,\n" "cui primum digitum dare appetenti\n" "et acris solet incitare morsus,\n" "cum desiderio meo nitenti\n" "carum nescio quid lubet iocari,\n" "credo ut, cum gravis acquiescet ardor,\n" "sit solaciolum sui doloris,\n" "tecum ludere sicut ipsa possem\n" "et tristis animi levare curas!\n", .output = "UGFzc2VyLCBkZWxpY2lhZSBtZWFlIHB1ZWxsYWUsCnF1aWN1\r\n" "bSBsdWRlcmUsIHF1ZW0gaW4gc2ludSB0ZW5lcmUsCmN1aSBw\r\n" "cmltdW0gZGlnaXR1bSBkYXJlIGFwcGV0ZW50aQpldCBhY3Jp\r\n" "cyBzb2xldCBpbmNpdGFyZSBtb3JzdXMsCmN1bSBkZXNpZGVy\r\n" "aW8gbWVvIG5pdGVudGkKY2FydW0gbmVzY2lvIHF1aWQgbHVi\r\n" "ZXQgaW9jYXJpLApjcmVkbyB1dCwgY3VtIGdyYXZpcyBhY3F1\r\n" "aWVzY2V0IGFyZG9yLApzaXQgc29sYWNpb2x1bSBzdWkgZG9s\r\n" "b3JpcywKdGVjdW0gbHVkZXJlIHNpY3V0IGlwc2EgcG9zc2Vt\r\n" "CmV0IHRyaXN0aXMgYW5pbWkgbGV2YXJlIGN1cmFzIQo=", }, { .scheme = &base64_scheme, .max_line_len = 48, .input = "Lugete, o Veneres Cupidinesque,\n" "et quantum est hominum venustiorum:\n" "passer mortuus est meae puellae, \n" "passer, deliciae meae puellae\n" "quem plus amat illa oculis suis amabat.\n" "Nam mellitus erat suamque norat\n" "ipsam tam bene quam puella matrem,\n" "nec sese a gremio illius movebat,\n" "sed circumsiliens modo huc modo illuc\n" "ad solam dominam usque pipiabat;\n" "qui nunc it per iter tenebricosum\n" "illuc, unde negant redire quemquam.\n" "At vobis male sint, malae tenebrae\n" "Orci, quae omnia bella devoratis:\n" "tam bellum mihi passerem abstulistis.\n" "O factum male! O miselle passer!\n" "Tua nunc opera meae puellae\n" "flendo turgiduli rubent ocelli.\n", .output = "THVnZXRlLCBvIFZlbmVyZXMgQ3VwaWRpbmVzcXVlLApldCBx\n" "dWFudHVtIGVzdCBob21pbnVtIHZlbnVzdGlvcnVtOgpwYXNz\n" "ZXIgbW9ydHV1cyBlc3QgbWVhZSBwdWVsbGFlLCAKcGFzc2Vy\n" "LCBkZWxpY2lhZSBtZWFlIHB1ZWxsYWUKcXVlbSBwbHVzIGFt\n" "YXQgaWxsYSBvY3VsaXMgc3VpcyBhbWFiYXQuCk5hbSBtZWxs\n" "aXR1cyBlcmF0IHN1YW1xdWUgbm9yYXQKaXBzYW0gdGFtIGJl\n" "bmUgcXVhbSBwdWVsbGEgbWF0cmVtLApuZWMgc2VzZSBhIGdy\n" "ZW1pbyBpbGxpdXMgbW92ZWJhdCwKc2VkIGNpcmN1bXNpbGll\n" "bnMgbW9kbyBodWMgbW9kbyBpbGx1YwphZCBzb2xhbSBkb21p\n" "bmFtIHVzcXVlIHBpcGlhYmF0OwpxdWkgbnVuYyBpdCBwZXIg\n" "aXRlciB0ZW5lYnJpY29zdW0KaWxsdWMsIHVuZGUgbmVnYW50\n" "IHJlZGlyZSBxdWVtcXVhbS4KQXQgdm9iaXMgbWFsZSBzaW50\n" "LCBtYWxhZSB0ZW5lYnJhZQpPcmNpLCBxdWFlIG9tbmlhIGJl\n" "bGxhIGRldm9yYXRpczoKdGFtIGJlbGx1bSBtaWhpIHBhc3Nl\n" "cmVtIGFic3R1bGlzdGlzLgpPIGZhY3R1bSBtYWxlISBPIG1p\n" "c2VsbGUgcGFzc2VyIQpUdWEgbnVuYyBvcGVyYSBtZWFlIHB1\n" "ZWxsYWUKZmxlbmRvIHR1cmdpZHVsaSBydWJlbnQgb2NlbGxp\n" "Lgo=", }, }; static void test_base64_encode_lowlevel(void) { string_t *str; unsigned int i; test_begin("base64 encode low-level"); str = t_str_new(256); for (i = 0; i < N_ELEMENTS(tests_base64_encode_lowlevel); i++) { const struct test_base64_encode_lowlevel *test = &tests_base64_encode_lowlevel[i]; struct base64_encoder enc; uoff_t out_size; str_truncate(str, 0); base64_encode_init(&enc, test->scheme, test->flags, test->max_line_len); out_size = base64_get_full_encoded_size( &enc, strlen(test->input)); test_assert_idx(base64_encode_more(&enc, test->input, strlen(test->input), NULL, str), i); test_assert_idx(base64_encode_finish(&enc, str), i); test_assert_idx(strcmp(test->output, str_c(str)) == 0, i); test_assert_idx(test->flags != 0 || test->max_line_len != 0 || str_len(str) == MAX_BASE64_ENCODED_SIZE( strlen(test->input)), i); test_assert_idx(str_len(str) == out_size, i); } test_end(); } struct test_base64_decode_lowlevel { const struct base64_scheme *scheme; enum base64_decode_flags flags; const char *input; const char *output; int ret; unsigned int src_pos; }; static const struct test_base64_decode_lowlevel tests_base64_decode_lowlevel[] = { { .scheme = &base64_scheme, .input = "", .output = "", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64_scheme, .input = " ", .output = "", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64_scheme, .input = "", .output = "", .ret = 0, .src_pos = UINT_MAX, .flags = BASE64_DECODE_FLAG_EXPECT_BOUNDARY, }, { .scheme = &base64_scheme, .input = "", .output = "", .ret = 0, .src_pos = UINT_MAX, .flags = BASE64_DECODE_FLAG_NO_WHITESPACE, }, { .scheme = &base64_scheme, .input = " ", .output = "", .ret = -1, .src_pos = 0, .flags = BASE64_DECODE_FLAG_NO_WHITESPACE, }, { .scheme = &base64_scheme, .input = "", .output = "", .ret = 0, .src_pos = UINT_MAX, .flags = BASE64_DECODE_FLAG_NO_PADDING, }, { .scheme = &base64_scheme, .input = "", .output = "", .ret = 0, .src_pos = UINT_MAX, .flags = BASE64_DECODE_FLAG_IGNORE_PADDING, }, { .scheme = &base64_scheme, .input = "\taGVsbG8gd29ybGQ=", .output = "hello world", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64url_scheme, .input = "\taGVsbG8gd29ybGQ=", .output = "hello world", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64_scheme, .input = "aGVsbG8gd29ybGQ=\t", .output = "hello world", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64_scheme, .input = "\taGVsbG8gd29ybGQ=\t", .output = "hello world", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64_scheme, .input = "aGVsbG8gd29ybGQ=:frop", .output = "hello world", .ret = 0, .src_pos = 16, .flags = BASE64_DECODE_FLAG_EXPECT_BOUNDARY, }, { .scheme = &base64_scheme, .input = "\taGVsbG8gd29ybGQ=\t:frop", .output = "hello world", .ret = 0, .src_pos = 18, .flags = BASE64_DECODE_FLAG_EXPECT_BOUNDARY, }, { .scheme = &base64_scheme, .input = "aGVsbG8gd29ybGQ=\t", .output = "hello world", .ret = -1, .src_pos = 16, .flags = BASE64_DECODE_FLAG_NO_WHITESPACE, }, { .scheme = &base64_scheme, .input = "\taGVsbG8gd29ybGQ=\t", .output = "", .ret = -1, .src_pos = 0, .flags = BASE64_DECODE_FLAG_NO_WHITESPACE, }, { .scheme = &base64_scheme, .flags = BASE64_DECODE_FLAG_NO_PADDING, .input = "\taGVsbG8gd29ybGQ=", .output = "hello world", .ret = -1, .src_pos = 16, }, { .scheme = &base64_scheme, .flags = BASE64_DECODE_FLAG_NO_PADDING, .input = "\taGVsbG8gd29ybGQ", .output = "hello world", .ret = 0, .src_pos = 16, }, { .scheme = &base64_scheme, .flags = BASE64_DECODE_FLAG_IGNORE_PADDING, .input = "\taGVsbG8gd29ybGQ=", .output = "hello world", .ret = 0, .src_pos = 17, }, { .scheme = &base64_scheme, .flags = BASE64_DECODE_FLAG_IGNORE_PADDING, .input = "\taGVsbG8gd29ybGQ", .output = "hello world", .ret = 0, .src_pos = 16, }, { .scheme = &base64_scheme, .input = "\nZm9v\n \tIGJh \t\ncml0cw==", .output = "foo barits", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64url_scheme, .input = "\nZm9v\n \tIGJh \t\ncml0cw==", .output = "foo barits", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64_scheme, .input = "\nZm9v\n \tIGJh \t\ncml0cw==\n ", .output = "foo barits", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64_scheme, .input = "\nZm9v\n \tIGJh \t\ncml0cw= =\n ", .output = "foo barits", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64_scheme, .input = "\nZm9v\n \tIGJh \t\ncml0cw\n= =\n ", .output = "foo barits", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64_scheme, .flags = BASE64_DECODE_FLAG_NO_PADDING, .input = "\nZm9v\n \tIGJh \t\ncml0cw==", .output = "foo barits", .ret = -1, .src_pos = 22, }, { .scheme = &base64_scheme, .flags = BASE64_DECODE_FLAG_NO_PADDING, .input = "\nZm9v\n \tIGJh \t\ncml0cw", .output = "foo barits", .ret = 0, .src_pos = 22, }, { .scheme = &base64_scheme, .flags = BASE64_DECODE_FLAG_IGNORE_PADDING, .input = "\nZm9v\n \tIGJh \t\ncml0cw==", .output = "foo barits", .ret = 0, .src_pos = 24, }, { .scheme = &base64_scheme, .flags = BASE64_DECODE_FLAG_IGNORE_PADDING, .input = "\nZm9v\n \tIGJh \t\ncml0cw", .output = "foo barits", .ret = 0, .src_pos = 22, }, { .scheme = &base64_scheme, .input = " anVzdCBuaWlu \n", .output = "just niin", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64url_scheme, .input = " anVzdCBuaWlu \n", .output = "just niin", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64_scheme, .flags = BASE64_DECODE_FLAG_NO_PADDING, .input = " anVzdCBuaWlu \n", .output = "just niin", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64_scheme, .flags = BASE64_DECODE_FLAG_IGNORE_PADDING, .input = " anVzdCBuaWlu \n", .output = "just niin", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64_scheme, .input = "aGVsb", .output = "hel", .ret = -1, .src_pos = 5, }, { .scheme = &base64url_scheme, .input = "aGVsb", .output = "hel", .ret = -1, .src_pos = 5, }, { .scheme = &base64_scheme, .flags = BASE64_DECODE_FLAG_NO_PADDING, .input = "aGVsb", .output = "hel", .ret = 0, .src_pos = 5, }, { .scheme = &base64_scheme, .flags = BASE64_DECODE_FLAG_IGNORE_PADDING, .input = "aGVsb", .output = "hel", .ret = 0, .src_pos = 5, }, { .scheme = &base64_scheme, .input = "aGVsb!!!!!", .output = "hel", .ret = -1, .src_pos = 5, }, { .scheme = &base64url_scheme, .input = "aGVsb!!!!!", .output = "hel", .ret = -1, .src_pos = 5, }, { .scheme = &base64_scheme, .input = "aGVs!!!!!", .output = "hel", .ret = -1, .src_pos = 4, }, { .scheme = &base64url_scheme, .input = "aGVs!!!!!", .output = "hel", .ret = -1, .src_pos = 4, }, { .scheme = &base64_scheme, .input = "0JPQvtCy0L7RgNGPzIHRgiwg0YfRgt" "C+INC60YPRgCDQtNC+0Y/MgdGCLg==", .output = "\xd0\x93\xd0\xbe\xd0\xb2\xd0\xbe\xd1\x80\xd1\x8f\xcc" "\x81\xd1\x82\x2c\x20\xd1\x87\xd1\x82\xd0\xbe\x20\xd0" "\xba\xd1\x83\xd1\x80\x20\xd0\xb4\xd0\xbe\xd1\x8f\xcc" "\x81\xd1\x82\x2e", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64url_scheme, .input = "0JPQvtCy0L7RgNGPzIHRgiwg0YfRgt" "C-INC60YPRgCDQtNC-0Y_MgdGCLg==", .output = "\xd0\x93\xd0\xbe\xd0\xb2\xd0\xbe\xd1\x80\xd1\x8f\xcc" "\x81\xd1\x82\x2c\x20\xd1\x87\xd1\x82\xd0\xbe\x20\xd0" "\xba\xd1\x83\xd1\x80\x20\xd0\xb4\xd0\xbe\xd1\x8f\xcc" "\x81\xd1\x82\x2e", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64_scheme, .flags = BASE64_DECODE_FLAG_NO_PADDING, .input = "0JPQvtCy0L7RgNGPzIHRgiwg0YfRgt" "C+INC60YPRgCDQtNC+0Y/MgdGCLg", .output = "\xd0\x93\xd0\xbe\xd0\xb2\xd0\xbe\xd1\x80\xd1\x8f\xcc" "\x81\xd1\x82\x2c\x20\xd1\x87\xd1\x82\xd0\xbe\x20\xd0" "\xba\xd1\x83\xd1\x80\x20\xd0\xb4\xd0\xbe\xd1\x8f\xcc" "\x81\xd1\x82\x2e", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64_scheme, .flags = BASE64_DECODE_FLAG_IGNORE_PADDING, .input = "0JPQvtCy0L7RgNGPzIHRgiwg0YfRgt" "C+INC60YPRgCDQtNC+0Y/MgdGCLg", .output = "\xd0\x93\xd0\xbe\xd0\xb2\xd0\xbe\xd1\x80\xd1\x8f\xcc" "\x81\xd1\x82\x2c\x20\xd1\x87\xd1\x82\xd0\xbe\x20\xd0" "\xba\xd1\x83\xd1\x80\x20\xd0\xb4\xd0\xbe\xd1\x8f\xcc" "\x81\xd1\x82\x2e", .ret = 0, .src_pos = UINT_MAX, }, { .scheme = &base64_scheme, .flags = BASE64_DECODE_FLAG_IGNORE_PADDING, .input = "0JPQvtCy0L7RgNGPzIHRgiwg0YfRgt" "C+INC60YPRgCDQtNC+0Y/MgdGCLg==", .output = "\xd0\x93\xd0\xbe\xd0\xb2\xd0\xbe\xd1\x80\xd1\x8f\xcc" "\x81\xd1\x82\x2c\x20\xd1\x87\xd1\x82\xd0\xbe\x20\xd0" "\xba\xd1\x83\xd1\x80\x20\xd0\xb4\xd0\xbe\xd1\x8f\xcc" "\x81\xd1\x82\x2e", .ret = 0, .src_pos = UINT_MAX, }, }; static void test_base64_decode_lowlevel(void) { string_t *str; buffer_t buf; unsigned int i; test_begin("base64 decode low-level"); for (i = 0; i < N_ELEMENTS(tests_base64_decode_lowlevel); i++) { const struct test_base64_decode_lowlevel *test = &tests_base64_decode_lowlevel[i]; struct base64_decoder dec; size_t src_pos; int ret; /* Some of the base64_decode() callers use fixed size buffers. Use a fixed size buffer here as well to test that base64_decode() can't allocate any extra space even temporarily. */ size_t max_decoded_size = MAX_BASE64_DECODED_SIZE(strlen(test->input)); buffer_create_from_data(&buf, (max_decoded_size == 0 ? "" : t_malloc0(max_decoded_size)), max_decoded_size); str = &buf; base64_decode_init(&dec, test->scheme, test->flags); ret = base64_decode_more(&dec, test->input, strlen(test->input), &src_pos, str); if (ret >= 0) ret = base64_decode_finish(&dec); test_assert_idx(ret == test->ret, i); test_assert_idx(strlen(test->output) == str_len(str) && memcmp(test->output, str_data(str), str_len(str)) == 0, i); test_assert_idx(src_pos == test->src_pos || (test->src_pos == UINT_MAX && src_pos == strlen(test->input)), i); if (ret >= 0) { test_assert_idx( str_len(str) <= MAX_BASE64_DECODED_SIZE( strlen(test->input)), i); } } test_end(); } static void test_base64_random_lowlevel_one_block(const struct base64_scheme *b64, enum base64_encode_flags enc_flags, enum base64_decode_flags dec_flags, size_t max_line_len, unsigned int test_idx, const unsigned char *in_buf, size_t in_buf_size, buffer_t *buf1, buffer_t *buf2) { struct base64_encoder enc; struct base64_decoder dec; void *space; size_t enc_size; buffer_t buf; int ret; buffer_set_used_size(buf1, 0); buffer_set_used_size(buf2, 0); base64_encode_init(&enc, b64, enc_flags, max_line_len); enc_size = base64_get_full_encoded_size(&enc, in_buf_size); space = buffer_append_space_unsafe(buf1, enc_size); buffer_create_from_data(&buf, space, enc_size); if (!base64_encode_more(&enc, in_buf, in_buf_size, NULL, &buf)) test_assert_idx(FALSE, test_idx); if (!base64_encode_finish(&enc, &buf)) test_assert_idx(FALSE, test_idx); test_assert(base64_get_full_encoded_size(&enc, in_buf_size) == buf1->used); base64_decode_init(&dec, b64, dec_flags); space = buffer_append_space_unsafe(buf2, in_buf_size); buffer_create_from_data(&buf, space, in_buf_size); ret = base64_decode_more(&dec, buf1->data, buf1->used, NULL, &buf); if (ret >= 0) ret = base64_decode_finish(&dec); test_assert_idx(ret >= 0, test_idx); test_assert_idx(buf2->used == in_buf_size && memcmp(in_buf, buf2->data, in_buf_size) == 0, test_idx); } static void test_base64_random_lowlevel_stream(const struct base64_scheme *b64, enum base64_encode_flags enc_flags, enum base64_decode_flags dec_flags, size_t max_line_len, unsigned int test_idx, const unsigned char *in_buf, size_t in_buf_size, buffer_t *buf1, buffer_t *buf2, size_t chunk_size) { struct base64_encoder enc; struct base64_decoder dec; const unsigned char *buf_p, *buf_begin, *buf_end; int ret; size_t out_space, out_full_size; void *out_data; buffer_t out; /* Encode */ buffer_set_used_size(buf1, 0); buf_begin = in_buf; buf_end = buf_begin + in_buf_size; base64_encode_init(&enc, b64, enc_flags, max_line_len); out_full_size = base64_get_full_encoded_size(&enc, in_buf_size); out_space = 0; for (buf_p = buf_begin; buf_p < buf_end; ) { size_t buf_ch, out_ch; size_t left = (buf_end - buf_p); size_t used = buf1->used; size_t src_pos, out_size, src_full_space; bool eres; if (chunk_size == 0) { buf_ch = i_rand_limit(32); out_ch = i_rand_limit(32); } else { buf_ch = chunk_size; out_ch = chunk_size; } out_space += out_ch; out_data = buffer_append_space_unsafe(buf1, out_space); buffer_create_from_data(&out, out_data, out_space); if (buf_ch > left) buf_ch = left; src_full_space = base64_encode_get_full_space( &enc, out_full_size - used); test_assert_idx(src_full_space >= (size_t)(buf_end - buf_p), test_idx); out_size = base64_encode_get_size(&enc, buf_ch); eres = base64_encode_more(&enc, buf_p, buf_ch, &src_pos, &out); test_assert_idx((eres && src_pos == buf_ch) || (!eres && src_pos < buf_ch), test_idx); test_assert_idx(out.used <= out_size, test_idx); buf_p += src_pos; i_assert(out_space >= out.used); out_space -= out.used; buffer_set_used_size(buf1, used + out.used); } test_assert_idx(base64_encode_finish(&enc, buf1), test_idx); /* Verify encode */ test_assert(out_full_size == buf1->used); buffer_set_used_size(buf2, 0); base64_encode_init(&enc, b64, enc_flags, max_line_len); test_assert_idx(base64_encode_more(&enc, in_buf, in_buf_size, NULL, buf2), test_idx); test_assert_idx(base64_encode_finish(&enc, buf2), test_idx); test_assert_idx(buffer_cmp(buf1, buf2), test_idx); /* Decode */ buffer_set_used_size(buf2, 0); buf_begin = buf1->data; buf_end = buf_begin + buf1->used; base64_decode_init(&dec, b64, dec_flags); ret = 1; out_space = 0; for (buf_p = buf_begin; buf_p < buf_end; ) { size_t buf_ch, out_ch; size_t left = (buf_end - buf_p); size_t used = buf2->used; size_t src_pos; if (chunk_size == 0) { buf_ch = i_rand_limit(32); out_ch = i_rand_limit(32); } else { buf_ch = chunk_size; out_ch = chunk_size; } out_space += out_ch; out_data = buffer_append_space_unsafe(buf2, out_space); buffer_create_from_data(&out, out_data, out_space); if (buf_ch > left) buf_ch = left; ret = base64_decode_more(&dec, buf_p, buf_ch, &src_pos, &out); test_assert_idx(ret >= 0, test_idx); if (ret < 0) { break; } buf_p += src_pos; i_assert(out_space >= out.used); out_space -= out.used; buffer_set_used_size(buf2, used + out.used); } test_assert_idx(ret >= 0, test_idx); /* Verify decode */ if (ret > 0) { ret = base64_decode_finish(&dec); test_assert_idx(ret == 0, test_idx); test_assert_idx(buf2->used == in_buf_size && memcmp(in_buf, buf2->data, in_buf_size) == 0, test_idx); } } static void test_base64_random_lowlevel_case(const struct base64_scheme *b64, enum base64_encode_flags enc_flags, enum base64_decode_flags dec_flags, size_t max_line_len) { unsigned char in_buf[512]; size_t in_buf_size; buffer_t *buf1, *buf2; unsigned int i, j; if (test_has_failed()) return; buf1 = t_buffer_create(MAX_BASE64_ENCODED_SIZE(sizeof(in_buf))); buf2 = t_buffer_create(MAX_BASE64_ENCODED_SIZE(sizeof(in_buf))); /* one block */ for (i = 0; i < 1000; i++) { in_buf_size = i_rand_limit(sizeof(in_buf)); for (j = 0; j < in_buf_size; j++) in_buf[j] = i_rand_uchar(); test_base64_random_lowlevel_one_block(b64, enc_flags, dec_flags, max_line_len, i, in_buf, in_buf_size, buf1, buf2); if (test_has_failed()) { i_info("One block test failed (" "enc_flags=%02x dec_flags=%02x " "max_line_len=%zu size=%zu)", enc_flags, dec_flags, max_line_len, in_buf_size); return; } } /* streaming; single-byte trickle */ for (i = 0; i < 1000; i++) { in_buf_size = i_rand_limit(sizeof(in_buf)); for (j = 0; j < in_buf_size; j++) in_buf[j] = i_rand_uchar(); test_base64_random_lowlevel_stream(b64, enc_flags, dec_flags, max_line_len, i, in_buf, in_buf_size, buf1, buf2, 1); if (test_has_failed()) { i_info("Streaming single-byte trickle test failed (" "enc_flags=%02x dec_flags=%02x " "max_line_len=%zu size=%zu)", enc_flags, dec_flags, max_line_len, in_buf_size); return; } } /* streaming; random chunks */ for (i = 0; i < 1000; i++) { in_buf_size = i_rand_limit(sizeof(in_buf)); for (j = 0; j < in_buf_size; j++) in_buf[j] = i_rand_uchar(); test_base64_random_lowlevel_stream(b64, enc_flags, dec_flags, max_line_len, i, in_buf, in_buf_size, buf1, buf2, 0); if (test_has_failed()) { i_info("Streaming random chunks test failed (" "enc_flags=%02x dec_flags=%02x " "max_line_len=%zu size=%zu)", enc_flags, dec_flags, max_line_len, in_buf_size); return; } } } static void test_base64_random_lowlevel(void) { test_begin("base64 encode/decode low-level with random input"); test_base64_random_lowlevel_case(&base64_scheme, 0, 0, 0); test_base64_random_lowlevel_case(&base64url_scheme, 0, 0, 0); test_base64_random_lowlevel_case(&base64_scheme, 0, BASE64_DECODE_FLAG_EXPECT_BOUNDARY, 0); test_base64_random_lowlevel_case(&base64url_scheme, 0, BASE64_DECODE_FLAG_EXPECT_BOUNDARY, 0); test_base64_random_lowlevel_case(&base64_scheme, 0, BASE64_DECODE_FLAG_NO_WHITESPACE, 0); test_base64_random_lowlevel_case(&base64url_scheme, 0, BASE64_DECODE_FLAG_NO_WHITESPACE, 0); test_base64_random_lowlevel_case(&base64_scheme, 0, 0, 10); test_base64_random_lowlevel_case(&base64url_scheme, 0, 0, 10); test_base64_random_lowlevel_case(&base64_scheme, BASE64_ENCODE_FLAG_CRLF, 0, 10); test_base64_random_lowlevel_case(&base64url_scheme, BASE64_ENCODE_FLAG_CRLF, 0, 10); test_base64_random_lowlevel_case(&base64_scheme, BASE64_ENCODE_FLAG_NO_PADDING, BASE64_DECODE_FLAG_NO_PADDING, 0); test_base64_random_lowlevel_case(&base64url_scheme, BASE64_ENCODE_FLAG_NO_PADDING, BASE64_DECODE_FLAG_NO_PADDING, 0); test_base64_random_lowlevel_case(&base64_scheme, BASE64_ENCODE_FLAG_NO_PADDING | BASE64_ENCODE_FLAG_CRLF, BASE64_DECODE_FLAG_NO_PADDING, 15); test_base64_random_lowlevel_case(&base64url_scheme, BASE64_ENCODE_FLAG_NO_PADDING | BASE64_ENCODE_FLAG_CRLF, BASE64_DECODE_FLAG_NO_PADDING, 15); test_base64_random_lowlevel_case(&base64_scheme, BASE64_ENCODE_FLAG_NO_PADDING | BASE64_ENCODE_FLAG_CRLF, BASE64_DECODE_FLAG_NO_PADDING, 1); test_end(); } static void _add_lines(const char *in, size_t max_line_len, bool crlf, string_t *out) { size_t in_len = strlen(in); while (max_line_len > 0 && in_len > max_line_len) { str_append_data(out, in, max_line_len); if (crlf) str_append(out, "\r\n"); else str_append_c(out, '\n'); in += max_line_len; in_len -= max_line_len; } str_append_data(out, in, in_len); } static void test_base64_encode_lines(void) { static const char *input[] = { "Passer, deliciae meae puellae,\n" "quicum ludere, quem in sinu tenere,\n" "cui primum digitum dare appetenti\n" "et acris solet incitare morsus,\n" "cum desiderio meo nitenti\n" "carum nescio quid lubet iocari,\n" "credo ut, cum gravis acquiescet ardor,\n" "sit solaciolum sui doloris,\n" "tecum ludere sicut ipsa possem\n" "et tristis animi levare curas!\n" }; static const char *output[] = { "UGFzc2VyLCBkZWxpY2lhZSBtZWFlIHB1ZWxsYWUsCnF1aWN1" "bSBsdWRlcmUsIHF1ZW0gaW4gc2ludSB0ZW5lcmUsCmN1aSBw" "cmltdW0gZGlnaXR1bSBkYXJlIGFwcGV0ZW50aQpldCBhY3Jp" "cyBzb2xldCBpbmNpdGFyZSBtb3JzdXMsCmN1bSBkZXNpZGVy" "aW8gbWVvIG5pdGVudGkKY2FydW0gbmVzY2lvIHF1aWQgbHVi" "ZXQgaW9jYXJpLApjcmVkbyB1dCwgY3VtIGdyYXZpcyBhY3F1" "aWVzY2V0IGFyZG9yLApzaXQgc29sYWNpb2x1bSBzdWkgZG9s" "b3JpcywKdGVjdW0gbHVkZXJlIHNpY3V0IGlwc2EgcG9zc2Vt" "CmV0IHRyaXN0aXMgYW5pbWkgbGV2YXJlIGN1cmFzIQo=", }; string_t *out_test, *out_ref; unsigned int i, n; out_test = t_str_new(256); out_ref = t_str_new(256); test_begin("base64 encode lines (LF)"); for (i = 0; i < N_ELEMENTS(input); i++) { struct base64_encoder b64enc; for (n = 0; n <= 80; n++) { str_truncate(out_test, 0); base64_encode_init(&b64enc, &base64_scheme, 0, n); base64_encode_more(&b64enc, input[i], strlen(input[i]), NULL, out_test); base64_encode_finish(&b64enc, out_test); str_truncate(out_ref, 0); _add_lines(output[i], n, FALSE, out_ref); test_assert(strcmp(str_c(out_ref), str_c(out_test)) == 0); } } test_end(); test_begin("base64 encode lines (CRLF)"); for (i = 0; i < N_ELEMENTS(input); i++) { struct base64_encoder b64enc; for (n = 0; n <= 80; n++) { str_truncate(out_test, 0); base64_encode_init(&b64enc, &base64_scheme, BASE64_ENCODE_FLAG_CRLF, n); base64_encode_more(&b64enc, input[i], strlen(input[i]), NULL, out_test); base64_encode_finish(&b64enc, out_test); str_truncate(out_ref, 0); _add_lines(output[i], n, TRUE, out_ref); test_assert(strcmp(str_c(out_ref), str_c(out_test)) == 0); } } test_end(); } void test_base64(void) { test_base64_encode(); test_base64_decode(); test_base64_random(); test_base64url_encode(); test_base64url_decode(); test_base64url_random(); test_base64_encode_lowlevel(); test_base64_decode_lowlevel(); test_base64_random_lowlevel(); test_base64_encode_lines(); } dovecot-2.3.21.1/src/lib/test-iso8601-date.c0000644000000000000000000000757014656633576015030 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "test-common.h" #include "iso8601-date.h" #include struct iso8601_date_test { const char *date_in; const char *date_out; struct tm tm; int zone_offset; }; /* Valid date tests */ struct iso8601_date_test valid_date_tests[] = { { .date_in = "2007-11-07T23:05:34+00:00", .tm = { .tm_year = 107, .tm_mon = 10, .tm_mday = 7, .tm_hour = 23, .tm_min = 5, .tm_sec = 34 }, },{ .date_in = "2011-01-07T21:03:31+00:30", .tm = { .tm_year = 111, .tm_mon = 0, .tm_mday = 7, .tm_hour = 21, .tm_min = 3, .tm_sec = 31 }, .zone_offset = 30 },{ .date_in = "2006-05-09T18:04:12+05:30", .tm = { .tm_year = 106, .tm_mon = 4, .tm_mday = 9, .tm_hour = 18, .tm_min = 4, .tm_sec = 12 }, .zone_offset = 5*60+30 },{ .date_in = "1975-10-30T06:33:29Z", .date_out = "1975-10-30T06:33:29+00:00", .tm = { .tm_year = 75, .tm_mon = 9, .tm_mday = 30, .tm_hour = 6, .tm_min = 33, .tm_sec = 29 }, },{ .date_in = "1988-04-24t15:02:12z", .date_out = "1988-04-24T15:02:12+00:00", .tm = { .tm_year = 88, .tm_mon = 3, .tm_mday = 24, .tm_hour = 15, .tm_min = 2, .tm_sec = 12 }, },{ .date_in = "2012-02-29T08:12:34.23198Z", .date_out = "2012-02-29T08:12:34+00:00", .tm = { .tm_year = 112, .tm_mon = 1, .tm_mday = 29, .tm_hour = 8, .tm_min = 12, .tm_sec = 34 }, } }; unsigned int valid_date_test_count = N_ELEMENTS(valid_date_tests); static void test_iso8601_date_valid(void) { unsigned int i; for (i = 0; i < valid_date_test_count; i++) T_BEGIN { const char *date_in, *date_out, *pdate_out; struct tm *tm = &valid_date_tests[i].tm, ptm; int zone_offset = valid_date_tests[i].zone_offset, pzone_offset; bool result; date_in = valid_date_tests[i].date_in; date_out = valid_date_tests[i].date_out == NULL ? date_in : valid_date_tests[i].date_out; test_begin(t_strdup_printf("iso8601 date valid [%d]", i)); result = iso8601_date_parse_tm ((const unsigned char *)date_in, strlen(date_in), &ptm, &pzone_offset); test_out(t_strdup_printf("parse %s", date_in), result); if (result) { bool equal = tm->tm_year == ptm.tm_year && tm->tm_mon == ptm.tm_mon && tm->tm_mday == ptm.tm_mday && tm->tm_hour == ptm.tm_hour && tm->tm_min == ptm.tm_min && tm->tm_sec == ptm.tm_sec; test_out("valid timestamp", equal); test_out_reason("valid timezone", zone_offset == pzone_offset, t_strdup_printf("%d", pzone_offset)); pdate_out = iso8601_date_create_tm(tm, zone_offset); test_out_reason("valid create", strcmp(date_out, pdate_out) == 0, pdate_out); } test_end(); } T_END; } /* Invalid date tests */ const char *invalid_date_tests[] = { "200-11-17T23:05:34+00:00", "2007:11-17T23:05:34+00:00", "2007-11?17T23:05:34+00:00", "2007-49-17T23:05:34+00:00", "2007-11-77T23:05:34+00:00", "2007-11-17K23:05:34+00:00", "2007-11-13T59:05:34+00:00", "2007-112-13T12:15:34+00:00", "2007-11-133T12:15:34+00:00", "2007-11-13T12J15:34+00:00", "2007-11-13T12:15*34+00:00", "2007-11-13T12:15:34/00:00", "2007-11-13T12:15:34+00-00", "2007-11-13T123:15:34+00:00", "2007-11-13T12:157:34+00:00", "2007-11-13T12:15:342+00:00", "2007-11-13T12:15:34+001:00", "2007-11-13T12:15:32+00:006", "2007-02-29T15:13:21Z" }; unsigned int invalid_date_test_count = N_ELEMENTS(invalid_date_tests); static void test_iso8601_date_invalid(void) { unsigned int i; for (i = 0; i < invalid_date_test_count; i++) T_BEGIN { const char *date_in; struct tm tm; int tz; bool result; date_in = invalid_date_tests[i]; test_begin(t_strdup_printf("iso8601 date invalid [%d]", i)); result = iso8601_date_parse_tm ((const unsigned char *)date_in, strlen(date_in), &tm, &tz); test_out(t_strdup_printf("parse %s", date_in), !result); test_end(); } T_END; } void test_iso8601_date(void) { test_iso8601_date_valid(); test_iso8601_date_invalid(); } dovecot-2.3.21.1/src/lib/test-istream-tee.c0000644000000000000000000001017414656633576015215 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str.h" #include "istream-private.h" #include "istream-tee.h" #define TEST_BUF_SIZE I_STREAM_MIN_SIZE #define TEST_STR_LEN (TEST_BUF_SIZE*3) #define CHILD_COUNT 5 static void test_istream_tee_tailing(const char *str) { struct istream *test_input, *child_input[CHILD_COUNT]; struct tee_istream *tee; unsigned int i, len, delta; test_input = test_istream_create(str); test_istream_set_max_buffer_size(test_input, TEST_BUF_SIZE); test_begin("istream tee tailing"); tee = tee_i_stream_create(test_input); for (i = 0; i < CHILD_COUNT; i++) child_input[i] = tee_i_stream_create_child(tee); test_istream_set_allow_eof(test_input, FALSE); delta = 1; for (len = 1; len < TEST_BUF_SIZE; len += delta) { test_istream_set_size(test_input, len); for (i = 0; i < CHILD_COUNT; i++) { test_assert_idx(i_stream_read(child_input[i]) == (int)delta, len); test_assert_idx(!tee_i_stream_child_is_waiting(child_input[i]), len); test_assert_idx(i_stream_read(child_input[i]) == 0, len); test_assert_idx(!tee_i_stream_child_is_waiting(child_input[i]), len); } delta = i_rand_limit(32); /* may stand still */ if(delta > TEST_BUF_SIZE - len) delta = 1; } test_istream_set_size(test_input, len); for (i = 0; i < CHILD_COUNT; i++) { test_assert(i_stream_read(child_input[i]) == (int)delta); test_assert(i_stream_read(child_input[i]) == -2); test_assert(!tee_i_stream_child_is_waiting(child_input[i])); } delta = 1; while ((len += delta) <= TEST_STR_LEN) { unsigned int lagger = i_rand_limit(CHILD_COUNT); test_istream_set_size(test_input, len); for (i = 0; i < CHILD_COUNT; i++) { test_assert(i_stream_read(child_input[i]) == -2); test_assert(!tee_i_stream_child_is_waiting(child_input[i])); } for (i = 0; i < CHILD_COUNT; i++) { if (i == lagger) continue; i_stream_skip(child_input[i], delta); test_assert(i_stream_read(child_input[i]) == 0); test_assert(tee_i_stream_child_is_waiting(child_input[i])); } i_stream_skip(child_input[lagger], delta); for (i = 0; i < CHILD_COUNT; i++) { test_assert(i_stream_read(child_input[i]) == (int)delta); test_assert(i_stream_read(child_input[i]) == -2); test_assert(!tee_i_stream_child_is_waiting(child_input[i])); } delta = i_rand_minmax(1, 31); /* mustn't stand still */ if(delta > TEST_STR_LEN - len) delta = 1; } for (i = 0; i < CHILD_COUNT-1; i++) { i_stream_skip(child_input[i], 1); test_assert(i_stream_read(child_input[i]) == 0); test_assert(tee_i_stream_child_is_waiting(child_input[i])); } i_stream_skip(child_input[i], 1); test_assert(i_stream_read(child_input[i]) == 0); test_assert(!tee_i_stream_child_is_waiting(child_input[i])); test_istream_set_allow_eof(test_input, TRUE); for (i = 0; i < CHILD_COUNT; i++) { test_assert(i_stream_read(child_input[i]) == -1); i_stream_unref(&child_input[i]); } i_stream_unref(&test_input); test_end(); } static void test_istream_tee_blocks(const char *str) { struct istream *test_input, *child_input[CHILD_COUNT]; struct tee_istream *tee; unsigned int i, j; test_input = test_istream_create(str); test_istream_set_max_buffer_size(test_input, TEST_BUF_SIZE); test_begin("istream tee blocks"); tee = tee_i_stream_create(test_input); for (i = 0; i < CHILD_COUNT; i++) child_input[i] = tee_i_stream_create_child(tee); test_istream_set_allow_eof(test_input, FALSE); for (j = 1; j <= 3; j++) { test_istream_set_size(test_input, TEST_BUF_SIZE*j); for (i = 0; i < CHILD_COUNT; i++) { test_assert(i_stream_read(child_input[i]) == TEST_BUF_SIZE); i_stream_skip(child_input[i], TEST_BUF_SIZE); } } test_istream_set_allow_eof(test_input, TRUE); for (i = 0; i < CHILD_COUNT; i++) { test_assert(i_stream_read(child_input[i]) == -1); i_stream_unref(&child_input[i]); } i_stream_unref(&test_input); test_end(); } void test_istream_tee(void) { string_t *str; unsigned int i; str = str_new(default_pool, TEST_STR_LEN); for (i = 0; i < TEST_STR_LEN; i++) str_append_c(str, 'a' + i%26); test_istream_tee_tailing(str_c(str)); test_istream_tee_blocks(str_c(str)); str_free(&str); } dovecot-2.3.21.1/src/lib/istream-hash.h0000644000000000000000000000054314656633576014412 00000000000000#ifndef ISTREAM_HASH_H #define ISTREAM_HASH_H struct hash_method; /* hash_context must be allocated and initialized by caller. This istream will simply call method->loop() for all the data going through the istream. */ struct istream * i_stream_create_hash(struct istream *input, const struct hash_method *method, void *hash_context); #endif dovecot-2.3.21.1/src/lib/time-util.h0000644000000000000000000000612314656633576013736 00000000000000#ifndef TIME_UTIL_H #define TIME_UTIL_H #include /* for struct timeval */ /* Same as gettimeofday(), but call i_fatal() if the call fails. */ void i_gettimeofday(struct timeval *tv_r); /* Return nanoseconds since UNIX epoch (1970-01-01). */ uint64_t i_nanoseconds(void); /* Return microseconds since UNIX epoch (1970-01-01). */ static inline uint64_t i_microseconds(void) { return i_nanoseconds() / 1000; } /* Returns -1 if tv1tv2, 0 if they're equal. */ int timeval_cmp(const struct timeval *tv1, const struct timeval *tv2); /* Same as timeval_cmp, but tv->usecs must differ by at least usec_margin */ int timeval_cmp_margin(const struct timeval *tv1, const struct timeval *tv2, unsigned int usec_margin); /* Returns tv1-tv2 in milliseconds. */ int timeval_diff_msecs(const struct timeval *tv1, const struct timeval *tv2); /* Returns tv1-tv2 in microseconds. */ long long timeval_diff_usecs(const struct timeval *tv1, const struct timeval *tv2); static inline void timeval_add_usecs(struct timeval *tv, long long usecs) { i_assert(usecs >= 0); tv->tv_sec += usecs / 1000000; tv->tv_usec += (usecs % 1000000); if (tv->tv_usec >= 1000000) { tv->tv_sec++; tv->tv_usec -= 1000000; } } static inline void timeval_sub_usecs(struct timeval *tv, long long usecs) { i_assert(usecs >= 0); tv->tv_sec -= usecs / 1000000; tv->tv_usec -= (usecs % 1000000); if (tv->tv_usec < 0) { tv->tv_sec--; tv->tv_usec += 1000000; } } static inline void timeval_add_msecs(struct timeval *tv, unsigned int msecs) { tv->tv_sec += msecs / 1000; tv->tv_usec += (msecs % 1000) * 1000; if (tv->tv_usec >= 1000000) { tv->tv_sec++; tv->tv_usec -= 1000000; } } static inline void timeval_sub_msecs(struct timeval *tv, unsigned int msecs) { tv->tv_sec -= msecs / 1000; tv->tv_usec -= (msecs % 1000) * 1000; if (tv->tv_usec < 0) { tv->tv_sec--; tv->tv_usec += 1000000; } } static inline unsigned long long timeval_to_usecs(const struct timeval *tv) { return (tv->tv_sec * 1000000ULL + tv->tv_usec); } static inline void timeval_add(struct timeval *tv, const struct timeval *val) { i_assert(val->tv_usec < 1000000); tv->tv_sec += val->tv_sec; tv->tv_usec += val->tv_usec; if (tv->tv_usec >= 1000000) { tv->tv_sec++; tv->tv_usec -= 1000000; } } static inline time_t timeval_round(struct timeval *tv) { return (tv->tv_usec < 500000 ? tv->tv_sec : tv->tv_sec + 1); } /* Convert t to local time and return timestamp on that day at 00:00:00. */ time_t time_to_local_day_start(time_t t); /* Wrappers to strftime() */ const char *t_strftime(const char *fmt, const struct tm *tm) ATTR_STRFTIME(1); const char *t_strflocaltime(const char *fmt, time_t t) ATTR_STRFTIME(1); const char *t_strfgmtime(const char *fmt, time_t t) ATTR_STRFTIME(1); /* Parse string as [.] into timeval. must not have higher precision time, i.e. a maximum of 6 digits is allowed. Note that ".1" is handled as ".1000000" so the string should have been written using "%06u" printf format. */ int str_to_timeval(const char *str, struct timeval *tv_r) ATTR_WARN_UNUSED_RESULT; #endif dovecot-2.3.21.1/src/lib/eacces-error.c0000644000000000000000000001667514656633576014407 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "path-util.h" #include "ipwd.h" #include "restrict-access.h" #include "eacces-error.h" #include #include static bool is_in_group(gid_t gid) { const gid_t *gids; unsigned int i, count; if (getegid() == gid) return TRUE; gids = restrict_get_groups_list(&count); for (i = 0; i < count; i++) { if (gids[i] == gid) return TRUE; } return FALSE; } static void write_eacces_error(string_t *errmsg, const char *path, int mode) { char c; switch (mode) { case R_OK: c = 'r'; break; case W_OK: c = 'w'; break; case X_OK: c = 'x'; break; default: i_unreached(); } str_printfa(errmsg, " missing +%c perm: %s", c, path); } static int test_manual_access(const char *path, int access_mode, bool write_eacces, string_t *errmsg) { const struct group *group; bool user_not_in_group = FALSE; struct stat st; int mode; if (stat(path, &st) < 0) { str_printfa(errmsg, " stat(%s) failed: %m", path); return -1; } switch (access_mode) { case R_OK: mode = 04; break; case W_OK: mode = 02; break; case X_OK: mode = 01; break; default: i_unreached(); } if (st.st_uid == geteuid()) st.st_mode = (st.st_mode & 0700) >> 6; else if (is_in_group(st.st_gid)) st.st_mode = (st.st_mode & 0070) >> 3; else { if ((((st.st_mode & 0070) >> 3) & mode) != 0) user_not_in_group = TRUE; st.st_mode = (st.st_mode & 0007); } if ((st.st_mode & mode) != 0) return 0; if (write_eacces) write_eacces_error(errmsg, path, access_mode); if (user_not_in_group) { /* group would have had enough permissions, but we don't belong to the group */ str_printfa(errmsg, ", we're not in group %s", dec2str(st.st_gid)); group = getgrgid(st.st_gid); if (group != NULL) str_printfa(errmsg, "(%s)", group->gr_name); } errno = EACCES; return -1; } static int test_access(const char *path, int access_mode, string_t *errmsg) { struct stat st; if (getuid() == geteuid()) { if (access(path, access_mode) == 0) return 0; if (errno == EACCES) { write_eacces_error(errmsg, path, access_mode); if (test_manual_access(path, access_mode, FALSE, errmsg) == 0) { str_append(errmsg, ", UNIX perms appear ok " "(ACL/MAC wrong?)"); } errno = EACCES; } else { str_printfa(errmsg, ", access(%s, %d) failed: %m", path, access_mode); } return -1; } /* access() uses real uid, not effective uid. we'll have to do these checks manually. */ switch (access_mode) { case X_OK: if (stat(t_strconcat(path, "/test", NULL), &st) == 0) return 0; if (errno == ENOENT || errno == ENOTDIR) return 0; if (errno == EACCES) write_eacces_error(errmsg, path, access_mode); else str_printfa(errmsg, ", stat(%s/test) failed: %m", path); return -1; case R_OK: case W_OK: break; default: i_unreached(); } return test_manual_access(path, access_mode, TRUE, errmsg); } static const char * eacces_error_get_full(const char *func, const char *path, bool creating) { const char *prev_path, *dir = NULL, *p; const char *pw_name = NULL, *gr_name = NULL; struct passwd pw; struct group group; string_t *errmsg; struct stat st; int orig_errno, ret, missing_mode = 0; orig_errno = errno; errmsg = t_str_new(256); str_printfa(errmsg, "%s(%s)", func, path); if (*path != '/') { const char *error; if (t_get_working_dir(&dir, &error) < 0) { i_error("eacces_error_get_full: %s", error); str_printfa(errmsg, " in an unknown directory"); } else { str_printfa(errmsg, " in directory %s", dir); path = t_strconcat(dir, "/", path, NULL); } } str_printfa(errmsg, " failed: Permission denied (euid=%s", dec2str(geteuid())); switch (i_getpwuid(geteuid(), &pw)) { case -1: str_append(errmsg, "()"); break; case 0: str_append(errmsg, "()"); break; default: pw_name = t_strdup(pw.pw_name); str_printfa(errmsg, "(%s)", pw_name); break; } str_printfa(errmsg, " egid=%s", dec2str(getegid())); switch (i_getgrgid(getegid(), &group)) { case -1: str_append(errmsg, "()"); break; case 0: str_append(errmsg, "()"); break; default: gr_name = t_strdup(group.gr_name); str_printfa(errmsg, "(%s)", gr_name); break; } prev_path = path; ret = -1; while (strcmp(prev_path, "/") != 0) { if ((p = strrchr(prev_path, '/')) == NULL) break; dir = t_strdup_until(prev_path, p); ret = stat(dir, &st); if (ret == 0) break; if (errno == EACCES && strcmp(dir, "/") != 0) { /* see if we have access to parent directory */ } else if (errno == ENOENT && creating && strcmp(dir, "/") != 0) { /* probably mkdir_parents() failed here, find the first parent directory we couldn't create */ } else { /* some other error, can't handle it */ str_printfa(errmsg, " stat(%s) failed: %m", dir); break; } prev_path = dir; } if (ret == 0) { /* dir is the first parent directory we can stat() */ if (test_access(dir, X_OK, errmsg) < 0) { if (errno == EACCES) missing_mode = 1; } else if (creating && test_access(dir, W_OK, errmsg) < 0) { if (errno == EACCES) missing_mode = 2; } else if (prev_path == path && test_access(path, R_OK, errmsg) < 0) { } else if (!creating && test_access(path, W_OK, errmsg) < 0) { /* this produces a wrong error if the operation didn't actually need write permissions, but we don't know it here.. */ if (errno == EACCES) missing_mode = 4; } else { str_append(errmsg, " UNIX perms appear ok " "(ACL/MAC wrong?)"); } } if (ret < 0) ; else if (st.st_uid != geteuid()) { if (pw_name != NULL && i_getpwuid(st.st_uid, &pw) > 0 && strcmp(pw.pw_name, pw_name) == 0) { str_printfa(errmsg, ", conflicting dir uid=%s(%s)", dec2str(st.st_uid), pw_name); } else { str_printfa(errmsg, ", dir owned by %s:%s mode=0%o", dec2str(st.st_uid), dec2str(st.st_gid), (unsigned int)(st.st_mode & 0777)); } } else if (missing_mode != 0 && (((st.st_mode & 0700) >> 6) & missing_mode) == 0) { str_append(errmsg, ", dir owner missing perms"); } if (ret == 0 && gr_name != NULL && st.st_gid != getegid()) { if (i_getgrgid(st.st_gid, &group) > 0 && strcmp(group.gr_name, gr_name) == 0) { str_printfa(errmsg, ", conflicting dir gid=%s(%s)", dec2str(st.st_gid), gr_name); } } str_append_c(errmsg, ')'); errno = orig_errno; return str_c(errmsg); } const char *eacces_error_get(const char *func, const char *path) { return eacces_error_get_full(func, path, FALSE); } const char *eacces_error_get_creating(const char *func, const char *path) { return eacces_error_get_full(func, path, TRUE); } const char *eperm_error_get_chgrp(const char *func, const char *path, gid_t gid, const char *gid_origin) { string_t *errmsg; const struct group *group; int orig_errno = errno; errmsg = t_str_new(256); str_printfa(errmsg, "%s(%s, group=%s", func, path, dec2str(gid)); group = getgrgid(gid); if (group != NULL) str_printfa(errmsg, "(%s)", group->gr_name); str_printfa(errmsg, ") failed: Operation not permitted (egid=%s", dec2str(getegid())); group = getgrgid(getegid()); if (group != NULL) str_printfa(errmsg, "(%s)", group->gr_name); if (gid_origin != NULL) str_printfa(errmsg, ", group based on %s", gid_origin); str_append(errmsg, " - see http://wiki2.dovecot.org/Errors/ChgrpNoPerm)"); errno = orig_errno; return str_c(errmsg); } dovecot-2.3.21.1/src/lib/str-table.h0000644000000000000000000000123114656633576013715 00000000000000#ifndef STR_TABLE_H #define STR_TABLE_H /* Hash table containing string -> refcount. */ struct str_table *str_table_init(void); void str_table_deinit(struct str_table **table); /* Returns TRUE if there are no referenced strings in the table. */ bool str_table_is_empty(struct str_table *table); /* Return string allocated from the strtable and increase its reference count. */ const char *str_table_ref(struct str_table *table, const char *str); /* Decrease string's reference count, freeing it if it reaches zero. The str pointer must have been returned by the str_table_ref(). */ void str_table_unref(struct str_table *table, const char **str); #endif dovecot-2.3.21.1/src/lib/module-context.h0000644000000000000000000000724714656633576015004 00000000000000#ifndef MODULE_CONTEXT_H #define MODULE_CONTEXT_H #include "array.h" /* This is a bit complex to use, but it prevents using wrong module IDs in module_contexts arrays. --------- The main structure is implemented like this: struct STRUCT_NAME_module_register { unsigned int id; }; union STRUCT_NAME_module_context { struct STRUCT_NAME_module_register *reg; // it's allowed to have some structure here so it won't waste space. // for example: struct STRUCT_NAME_vfuncs super; }; struct STRUCT_NAME { ARRAY(union STRUCT_NAME_module_context *) module_contexts; }; extern struct STRUCT_NAME_module_register STRUCT_NAME_module_register; --------- The usage in modules goes like: static MODULE_CONTEXT_DEFINE(mymodule_STRUCT_NAME_module, &STRUCT_NAME_module_register); struct mymodule_STRUCT_NAME { union STRUCT_NAME_module_context module_ctx; // module-specific data }; struct mymodule_STRUCT_NAME *ctx = i_new(...); MODULE_CONTEXT_SET(obj, mymodule_STRUCT_NAME_module, ctx); struct mymodule_STRUCT_NAME *ctx = MODULE_CONTEXT(obj, mymodule_STRUCT_NAME_module); */ #define OBJ_REGISTER(obj) \ ((**(obj)->module_contexts.v)->reg) #define OBJ_REGISTER_COMPATIBLE(obj, id_ctx) \ COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE(OBJ_REGISTER(obj), (id_ctx).reg) #define MODULE_CONTEXT(obj, id_ctx) \ (module_get_context_id(&(id_ctx).id) < array_count(&(obj)->module_contexts) ? \ (*((void **)array_idx_modifiable(&(obj)->module_contexts, \ module_get_context_id(&(id_ctx).id)) + \ OBJ_REGISTER_COMPATIBLE(obj, id_ctx))) : NULL) /* Will crash if context is missing. This is mainly used to simplify code and keep static analyzers happy. This syntax discards result of i_panic and returns NULL instead to keep compilers happy. */ #define MODULE_CONTEXT_REQUIRE(obj, id_ctx) \ (module_get_context_id(&(id_ctx).id) < array_count(&(obj)->module_contexts) ? \ (*((void **)array_idx_modifiable(&(obj)->module_contexts, \ module_get_context_id(&(id_ctx).id)) + \ OBJ_REGISTER_COMPATIBLE(obj, id_ctx))) : (i_panic("Module context " #id_ctx " missing"), NULL)) #ifdef HAVE_TYPEOF # define MODULE_CONTEXT_DEFINE(_name, _reg) \ struct _name { \ struct module_context_id id; \ typeof(_reg) reg; \ } _name # define MODULE_CONTEXT_INIT(_reg) \ { { &(_reg)->id, 0, FALSE }, NULL } #else # define MODULE_CONTEXT_DEFINE(_name, _reg) \ struct _name { \ struct module_context_id id; \ } _name # define MODULE_CONTEXT_INIT(_reg) \ { { &(_reg)->id, 0, FALSE } } #endif #define MODULE_CONTEXT_DEFINE_INIT(_name, _reg) \ MODULE_CONTEXT_DEFINE(_name, _reg) = MODULE_CONTEXT_INIT(_reg) struct module_context_id { unsigned int *module_id_register; unsigned int module_id; bool module_id_set; }; static inline unsigned int module_get_context_id(struct module_context_id *id) { if (!id->module_id_set) { id->module_id = *id->module_id_register; id->module_id_set = TRUE; *id->module_id_register += 1; } return id->module_id; } #define MODULE_CONTEXT_SET_FULL(obj, id_ctx, ctx, module_ctx) STMT_START { \ (void)COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE(module_ctx, \ (**(obj)->module_contexts.v)); \ (void)OBJ_REGISTER_COMPATIBLE(obj, id_ctx); \ void *_module_tmp = ctx; \ array_idx_set_i(&(obj)->module_contexts.arr, \ module_get_context_id(&(id_ctx).id), &_module_tmp); \ } STMT_END #define MODULE_CONTEXT_SET(obj, id_ctx, context) \ MODULE_CONTEXT_SET_FULL(obj, id_ctx, context, &(context)->module_ctx) #define MODULE_CONTEXT_SET_SELF(obj, id_ctx, context) \ MODULE_CONTEXT_SET_FULL(obj, id_ctx, context, context) #define MODULE_CONTEXT_UNSET(obj, id_ctx) \ array_idx_clear(&(obj)->module_contexts, (id_ctx).id.module_id) #endif dovecot-2.3.21.1/src/lib/lib-signals.c0000644000000000000000000004175314656633576014234 00000000000000/* Copyright (c) 2001-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "ioloop.h" #include "write-full.h" #include "llist.h" #include "lib-signals.h" #include #include #include #define MAX_SIGNAL_VALUE 63 #define SIGNAL_IS_TERMINAL(signo) \ ((signo) == SIGINT || (signo) == SIGQUIT || (signo) == SIGTERM) #if !defined(SA_SIGINFO) && !defined(SI_NOINFO) /* without SA_SIGINFO we don't know what the real code is. we need SI_NOINFO to make sure lib_signal_code_to_str() returns "". */ # define SI_NOINFO -1 #endif struct signal_ioloop { struct signal_ioloop *prev, *next; int refcount; struct ioloop *ioloop; struct io *io; }; struct signal_handler { signal_handler_t *handler; void *context; enum libsig_flags flags; struct signal_handler *next; struct signal_ioloop *sig_ioloop; bool expected:1; bool shadowed:1; }; volatile unsigned int signal_term_counter = 0; /* Remember that these are accessed inside signal handler which may be called even while we're initializing/deinitializing. Try hard to keep everything in consistent state. */ static struct signal_handler *signal_handlers[MAX_SIGNAL_VALUE+1] = { NULL, }; static int sig_pipe_fd[2] = { -1, -1 }; static bool signals_initialized = FALSE; static unsigned int signals_expected = 0; static struct signal_ioloop *signal_ioloops = NULL; static siginfo_t pending_signals[MAX_SIGNAL_VALUE+1]; static ARRAY(siginfo_t) pending_shadowed_signals; static bool have_pending_signals = FALSE; static bool have_missing_ioloops = FALSE; static bool ioloop_switched = FALSE; static void signal_read(void *context); const char *lib_signal_code_to_str(int signo, int sicode) { /* common */ switch (sicode) { #ifdef SI_NOINFO case SI_NOINFO: return ""; #endif case SI_USER: return "kill"; #ifdef SI_KERNEL case SI_KERNEL: return "kernel"; #endif case SI_TIMER: return "timer"; } /* If SEGV_MAPERR is supported, the rest of them must be too. FreeBSD 6 at least doesn't support these. */ #ifdef SEGV_MAPERR switch (signo) { case SIGSEGV: switch (sicode) { case SEGV_MAPERR: return "address not mapped"; case SEGV_ACCERR: return "invalid permissions"; } break; case SIGBUS: switch (sicode) { case BUS_ADRALN: return "invalid address alignment"; #ifdef BUS_ADRERR /* for OSX 10.3 */ case BUS_ADRERR: return "nonexistent physical address"; #endif #ifdef BUS_OBJERR /* for OSX 10.3 */ case BUS_OBJERR: return "object-specific hardware error"; #endif } } #endif return t_strdup_printf("unknown %d", sicode); } #ifdef SA_SIGINFO static void sig_handler(int signo, siginfo_t *si, void *context ATTR_UNUSED) #else static void sig_handler(int signo) #endif { struct signal_handler *h; int saved_errno; char c = 0; #if defined(SI_NOINFO) || !defined(SA_SIGINFO) #ifndef SA_SIGINFO siginfo_t *si = NULL; #endif siginfo_t tmp_si; if (si == NULL) { /* Solaris can leave this to NULL */ i_zero(&tmp_si); tmp_si.si_signo = signo; tmp_si.si_code = SI_NOINFO; si = &tmp_si; } #endif if (signo < 0 || signo > MAX_SIGNAL_VALUE) return; if (SIGNAL_IS_TERMINAL(signo)) signal_term_counter++; /* remember that we're inside a signal handler which might have been called at any time. don't do anything that's unsafe. we might also get interrupted by another signal while inside this handler. */ saved_errno = errno; for (h = signal_handlers[signo]; h != NULL; h = h->next) { if ((h->flags & LIBSIG_FLAG_DELAYED) == 0) h->handler(si, h->context); else if (pending_signals[signo].si_signo == 0) { pending_signals[signo] = *si; if (!have_pending_signals) { if (write(sig_pipe_fd[1], &c, 1) != 1) { lib_signals_syscall_error( "signal: write(sigpipe) failed: "); } have_pending_signals = TRUE; } } } errno = saved_errno; } #ifdef SA_SIGINFO static void sig_ignore(int signo ATTR_UNUSED, siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED) #else static void sig_ignore(int signo ATTR_UNUSED) #endif { /* if we used SIG_IGN instead of this function, the system call might be restarted */ } static struct signal_ioloop * lib_signals_ioloop_find(struct ioloop *ioloop) { struct signal_ioloop *l; for (l = signal_ioloops; l != NULL; l = l->next) { if (l->ioloop == ioloop) break; } return l; } static void lib_signals_init_io(struct signal_ioloop *l) { i_assert(sig_pipe_fd[0] != -1); l->io = io_add_to(l->ioloop, sig_pipe_fd[0], IO_READ, signal_read, NULL); io_set_never_wait_alone(l->io, signals_expected == 0); } static struct signal_ioloop * lib_signals_ioloop_ref(struct ioloop *ioloop) { struct signal_ioloop *l; l = lib_signals_ioloop_find(ioloop); if (l == NULL) { l = i_new(struct signal_ioloop, 1); l->ioloop = ioloop; lib_signals_init_io(l); DLLIST_PREPEND(&signal_ioloops, l); } l->refcount++; return l; } static void lib_signals_ioloop_unref(struct signal_ioloop **_sig_ioloop) { struct signal_ioloop *sig_ioloop = *_sig_ioloop; *_sig_ioloop = NULL; if (sig_ioloop == NULL) return; i_assert(sig_ioloop->refcount > 0); if (--sig_ioloop->refcount > 0) return; io_remove(&sig_ioloop->io); DLLIST_REMOVE(&signal_ioloops, sig_ioloop); i_free(sig_ioloop); } static void signal_handler_switch_ioloop(struct signal_handler *h) { lib_signals_ioloop_unref(&h->sig_ioloop); if (current_ioloop != NULL) h->sig_ioloop = lib_signals_ioloop_ref(current_ioloop); else have_missing_ioloops = TRUE; } static void signal_handler_free(struct signal_handler *h) { lib_signals_ioloop_unref(&h->sig_ioloop); i_free(h); } static void signal_handle_shadowed(void) { const siginfo_t *sis; unsigned int count, i; if (!array_is_created(&pending_shadowed_signals) || array_count(&pending_shadowed_signals) == 0) return; sis = array_get(&pending_shadowed_signals, &count); for (i = 0; i < count; i++) { struct signal_handler *h; bool shadowed = FALSE; i_assert(sis[i].si_signo > 0); for (h = signal_handlers[sis[i].si_signo]; h != NULL; h = h->next) { i_assert(h->sig_ioloop != NULL); if ((h->flags & LIBSIG_FLAG_DELAYED) == 0 || (h->flags & LIBSIG_FLAG_IOLOOP_AUTOMOVE) != 0) continue; if (h->shadowed && h->sig_ioloop->ioloop != current_ioloop) { shadowed = TRUE; continue; } /* handler can be called now */ h->shadowed = FALSE; h->handler(&sis[i], h->context); } if (!shadowed) { /* no handlers are shadowed anymore; delete the signal info */ array_delete(&pending_shadowed_signals, i, 1); sis = array_get(&pending_shadowed_signals, &count); } } } static void signal_check_shadowed(void) { struct signal_ioloop *sig_ioloop; if (!array_is_created(&pending_shadowed_signals) || array_count(&pending_shadowed_signals) == 0) return; sig_ioloop = lib_signals_ioloop_find(current_ioloop); if (sig_ioloop != NULL) io_set_pending(sig_ioloop->io); } static void signal_shadow(int signo, const siginfo_t *si) { const siginfo_t *sis; unsigned int count, i; /* remember last signal info for handlers that cannot run in current ioloop */ if (!array_is_created(&pending_shadowed_signals)) i_array_init(&pending_shadowed_signals, 4); sis = array_get(&pending_shadowed_signals, &count); for (i = 0; i < count; i++) { i_assert(sis[i].si_signo != 0); if (sis[i].si_signo == signo) break; } array_idx_set(&pending_shadowed_signals, i, si); } static void ATTR_NULL(1) signal_read(void *context ATTR_UNUSED) { siginfo_t signals[MAX_SIGNAL_VALUE+1]; sigset_t fullset, oldset; struct signal_handler *h; char buf[64]; int signo; ssize_t ret; if (ioloop_switched) { ioloop_switched = FALSE; /* handle any delayed signal handlers that emerged from the shadow */ signal_handle_shadowed(); } if (sigfillset(&fullset) < 0) i_fatal("sigfillset() failed: %m"); if (sigprocmask(SIG_BLOCK, &fullset, &oldset) < 0) i_fatal("sigprocmask() failed: %m"); /* typically we should read only a single byte, but if a signal is sent while signal handler is running we might get more. */ ret = read(sig_pipe_fd[0], buf, sizeof(buf)); if (ret > 0) { memcpy(signals, pending_signals, sizeof(signals)); memset(pending_signals, 0, sizeof(pending_signals)); have_pending_signals = FALSE; } else if (ret < 0) { if (errno != EAGAIN) i_fatal("read(sigpipe) failed: %m"); } else { i_fatal("read(sigpipe) failed: EOF"); } if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0) i_fatal("sigprocmask() failed: %m"); if (ret < 0) return; /* call the delayed handlers after signals are copied and unblocked */ for (signo = 0; signo < MAX_SIGNAL_VALUE; signo++) { bool shadowed = FALSE; if (signals[signo].si_signo == 0) continue; for (h = signal_handlers[signo]; h != NULL; h = h->next) { i_assert(h->sig_ioloop != NULL); if ((h->flags & LIBSIG_FLAG_DELAYED) == 0) { /* handler already called immediately in signal context */ continue; } if ((h->flags & LIBSIG_FLAG_IOLOOP_AUTOMOVE) == 0 && h->sig_ioloop->ioloop != current_ioloop) { /* cannot run handler in current ioloop (shadowed) */ h->shadowed = TRUE; shadowed = TRUE; continue; } /* handler can be called now */ h->handler(&signals[signo], h->context); } if (shadowed) { /* remember last signal info for handlers that cannot run in current ioloop (shadowed) */ signal_shadow(signo, &signals[signo]); } } } static void lib_signals_update_expected_signals(bool expected) { struct signal_ioloop *sig_ioloop; if (expected) signals_expected++; else { i_assert(signals_expected > 0); signals_expected--; } sig_ioloop = signal_ioloops; for (; sig_ioloop != NULL; sig_ioloop = sig_ioloop->next) { if (sig_ioloop->io != NULL) { io_set_never_wait_alone(sig_ioloop->io, signals_expected == 0); } } } static void lib_signals_ioloop_switch(void) { struct signal_handler *h; if (current_ioloop == NULL || sig_pipe_fd[0] <= 0) return; /* initialize current_ioloop for signal handlers created before the first ioloop. */ for (int signo = 0; signo < MAX_SIGNAL_VALUE; signo++) { for (h = signal_handlers[signo]; h != NULL; h = h->next) { if ((h->flags & LIBSIG_FLAG_IOLOOP_AUTOMOVE) != 0) lib_signals_ioloop_unref(&h->sig_ioloop); if (h->sig_ioloop == NULL) h->sig_ioloop = lib_signals_ioloop_ref(current_ioloop); } } have_missing_ioloops = FALSE; } static void lib_signals_ioloop_switched(struct ioloop *prev_ioloop ATTR_UNUSED) { ioloop_switched = TRUE; lib_signals_ioloop_switch(); /* check whether we can now handle any shadowed delayed signals */ signal_check_shadowed(); } static void lib_signals_ioloop_destroyed(struct ioloop *ioloop) { struct signal_ioloop *sig_ioloop; sig_ioloop = lib_signals_ioloop_find(ioloop); if (sig_ioloop != NULL) { io_remove(&sig_ioloop->io); sig_ioloop->ioloop = NULL; } } void lib_signals_ioloop_detach(void) { struct signal_handler *h; for (int signo = 0; signo < MAX_SIGNAL_VALUE; signo++) { for (h = signal_handlers[signo]; h != NULL; h = h->next) { if (h->sig_ioloop != NULL) { lib_signals_ioloop_unref(&h->sig_ioloop); have_missing_ioloops = TRUE; } } } } void lib_signals_ioloop_attach(void) { if (have_missing_ioloops) lib_signals_ioloop_switch(); } static void lib_signals_set(int signo, enum libsig_flags flags) { struct sigaction act; if (sigemptyset(&act.sa_mask) < 0) i_fatal("sigemptyset(): %m"); #ifdef SA_SIGINFO act.sa_flags = SA_SIGINFO; act.sa_sigaction = sig_handler; #else act.sa_flags = 0; act.sa_handler = sig_handler; #endif if ((flags & LIBSIG_FLAG_RESTART) != 0) act.sa_flags |= SA_RESTART; if (sigaction(signo, &act, NULL) < 0) i_fatal("sigaction(%d): %m", signo); } void lib_signals_set_handler(int signo, enum libsig_flags flags, signal_handler_t *handler, void *context) { struct signal_handler *h; i_assert(handler != NULL); if (signo < 0 || signo > MAX_SIGNAL_VALUE) { i_panic("Trying to set signal %d handler, but max is %d", signo, MAX_SIGNAL_VALUE); } if (signal_handlers[signo] == NULL && signals_initialized) lib_signals_set(signo, flags); h = i_new(struct signal_handler, 1); h->handler = handler; h->context = context; h->flags = flags; /* atomically set to signal_handlers[] list */ h->next = signal_handlers[signo]; signal_handlers[signo] = h; if ((flags & LIBSIG_FLAG_DELAYED) != 0 && sig_pipe_fd[0] == -1) { /* first delayed handler */ if (pipe(sig_pipe_fd) < 0) i_fatal("pipe() failed: %m"); fd_set_nonblock(sig_pipe_fd[0], TRUE); fd_set_nonblock(sig_pipe_fd[1], TRUE); fd_close_on_exec(sig_pipe_fd[0], TRUE); fd_close_on_exec(sig_pipe_fd[1], TRUE); } signal_handler_switch_ioloop(h); } static void lib_signals_ignore_forced(int signo, bool restart_syscalls) { struct sigaction act; if (sigemptyset(&act.sa_mask) < 0) i_fatal("sigemptyset(): %m"); if (restart_syscalls) { act.sa_flags = SA_RESTART; act.sa_handler = SIG_IGN; } else { #ifdef SA_SIGINFO act.sa_flags = SA_SIGINFO; act.sa_sigaction = sig_ignore; #else act.sa_flags = 0; act.sa_handler = sig_ignore; #endif } if (sigaction(signo, &act, NULL) < 0) i_fatal("sigaction(%d): %m", signo); } void lib_signals_ignore(int signo, bool restart_syscalls) { if (signo < 0 || signo > MAX_SIGNAL_VALUE) { i_panic("Trying to ignore signal %d, but max is %d", signo, MAX_SIGNAL_VALUE); } i_assert(signal_handlers[signo] == NULL); lib_signals_ignore_forced(signo, restart_syscalls); } void lib_signals_clear_handlers_and_ignore(int signo) { struct signal_handler *h; if (signal_handlers[signo] == NULL) return; lib_signals_ignore_forced(signo, TRUE); h = signal_handlers[signo]; signal_handlers[signo] = NULL; while (h != NULL) { struct signal_handler *h_next = h->next; if (h->expected) signals_expected--; signal_handler_free(h); h = h_next; } } void lib_signals_unset_handler(int signo, signal_handler_t *handler, void *context) { struct signal_handler *h, **p; for (p = &signal_handlers[signo]; *p != NULL; p = &(*p)->next) { if ((*p)->handler == handler && (*p)->context == context) { if (p == &signal_handlers[signo] && (*p)->next == NULL) { /* last handler is to be removed */ lib_signals_ignore_forced(signo, TRUE); } h = *p; *p = h->next; if (h->expected) lib_signals_update_expected_signals(FALSE); signal_handler_free(h); return; } } i_panic("lib_signals_unset_handler(%d, %p, %p): handler not found", signo, (void *)handler, context); } void lib_signals_set_expected(int signo, bool expected, signal_handler_t *handler, void *context) { struct signal_handler *h; for (h = signal_handlers[signo]; h != NULL; h = h->next) { if (h->handler == handler && h->context == context) { if (h->expected == expected) return; h->expected = expected; lib_signals_update_expected_signals(expected); return; } } i_panic("lib_signals_set_expected(%d, %p, %p): handler not found", signo, (void *)handler, context); } void lib_signals_switch_ioloop(int signo, signal_handler_t *handler, void *context) { struct signal_handler *h; for (h = signal_handlers[signo]; h != NULL; h = h->next) { if (h->handler == handler && h->context == context) { i_assert((h->flags & LIBSIG_FLAG_DELAYED) != 0); i_assert((h->flags & LIBSIG_FLAG_IOLOOP_AUTOMOVE) == 0); signal_handler_switch_ioloop(h); /* check whether we can now handle any shadowed delayed signals */ signal_check_shadowed(); return; } } i_panic("lib_signals_switch_ioloop(%d, %p, %p): handler not found", signo, (void *)handler, context); } void lib_signals_syscall_error(const char *prefix) { /* @UNSAFE: We're in a signal handler. It's very limited what is allowed in here. Especially strerror() isn't at least officially allowed. */ char errno_buf[MAX_INT_STRLEN], *errno_str; errno_str = dec2str_buf(errno_buf, errno); size_t prefix_len = strlen(prefix); size_t errno_str_len = strlen(errno_str); char buf[prefix_len + errno_str_len + 1]; memcpy(buf, prefix, prefix_len); memcpy(buf + prefix_len, errno_str, errno_str_len); buf[prefix_len + errno_str_len] = '\n'; if (write_full(STDERR_FILENO, buf, prefix_len + errno_str_len + 1) < 0) { /* can't really do anything */ } } void lib_signals_init(void) { int i; signals_initialized = TRUE; io_loop_add_switch_callback(lib_signals_ioloop_switched); io_loop_add_destroy_callback(lib_signals_ioloop_destroyed); /* add signals that were already registered */ for (i = 0; i < MAX_SIGNAL_VALUE; i++) { if (signal_handlers[i] != NULL) lib_signals_set(i, signal_handlers[i]->flags); } } void lib_signals_deinit(void) { int i; for (i = 0; i < MAX_SIGNAL_VALUE; i++) { if (signal_handlers[i] != NULL) lib_signals_clear_handlers_and_ignore(i); } i_assert(signals_expected == 0); if (sig_pipe_fd[0] != -1) { if (close(sig_pipe_fd[0]) < 0) i_error("close(sigpipe) failed: %m"); if (close(sig_pipe_fd[1]) < 0) i_error("close(sigpipe) failed: %m"); sig_pipe_fd[0] = sig_pipe_fd[1] = -1; } if (array_is_created(&pending_shadowed_signals)) array_free(&pending_shadowed_signals); i_assert(signal_ioloops == NULL); } dovecot-2.3.21.1/src/lib/base64.h0000644000000000000000000003044614656633576013116 00000000000000#ifndef BASE64_H #define BASE64_H /* * Common Base64 */ /* max. buffer size required for base64_encode() */ #define MAX_BASE64_ENCODED_SIZE(size) \ ((((size) + 2) / 3) * 4) /* max. buffer size required for base64_decode() */ #define MAX_BASE64_DECODED_SIZE(size) \ (((size) + 3) / 4 * 3) struct base64_scheme { const char encmap[64]; const unsigned char decmap[256]; }; /* * Low-level Base64 encoder */ enum base64_encode_flags { /* Use CRLF instead of the default LF as line ending. */ BASE64_ENCODE_FLAG_CRLF = BIT(0), /* Encode no padding at the end of the data. */ BASE64_ENCODE_FLAG_NO_PADDING = BIT(1), }; struct base64_encoder { const struct base64_scheme *b64; enum base64_encode_flags flags; size_t max_line_len; /* state */ unsigned int sub_pos; unsigned char buf; size_t cur_line_len; unsigned char w_buf[10]; unsigned int w_buf_len; bool pending_lf:1; bool finishing:1; bool finished:1; }; /* Returns TRUE when base64_encode_finish() was called on this encoder. */ static inline bool base64_encode_is_finished(struct base64_encoder *enc) { return enc->finished; } /* Initialize the Base64 encoder. The b64 parameter is the definition of the particular Base64 encoding scheme that is used. */ static inline void base64_encode_init(struct base64_encoder *enc, const struct base64_scheme *b64, enum base64_encode_flags flags, size_t max_line_len) { i_zero(enc); enc->b64 = b64; enc->flags = flags; enc->max_line_len = (max_line_len == 0 ? SIZE_MAX : max_line_len); } /* Reset the Base64 encoder to its initial state. */ static inline void base64_encode_reset(struct base64_encoder *enc) { const struct base64_scheme *b64 = enc->b64; enum base64_encode_flags flags = enc->flags; size_t max_line_len = enc->max_line_len; base64_encode_init(enc, b64, flags, max_line_len); } /* Translate the size of the full encoder input to the size of the encoder output. */ uoff_t base64_get_full_encoded_size(struct base64_encoder *enc, uoff_t src_size); /* Translate the size of the next input to the size of the output once encoded. This yields the amount of data appended to the dest buffer by base64_encode_more() with the indicated src_size. */ size_t base64_encode_get_size(struct base64_encoder *enc, size_t src_size); /* Translate the space in the destination buffer to the number of bytes that can be encoded at most to complete the full base64 encoding, including padding and newlines if configured. */ size_t base64_encode_get_full_space(struct base64_encoder *enc, size_t dst_space); /* Translates binary data into some form of Base64. The src must not point to dest buffer. Returns TRUE when all the provided data is encoded. Returns FALSE when the space in the provided buffer is insufficient. The return value may be ignored. If src_pos_r is non-NULL, it's updated to first non-translated character in src. */ bool ATTR_NOWARN_UNUSED_RESULT base64_encode_more(struct base64_encoder *enc, const void *src, size_t src_size, size_t *src_pos_r, buffer_t *dest) ATTR_NULL(4); /* Finishes Base64 encoding. Returns TRUE when all the provided data is encoded. Returns FALSE when the space in the provided buffer is insufficient. The return value may be ignored. */ bool ATTR_NOWARN_UNUSED_RESULT base64_encode_finish(struct base64_encoder *enc, buffer_t *dest) ATTR_NULL(2); /* * Low-level Base64 decoder */ enum base64_decode_flags { /* Decode input until a boundary is reached. This boundary is a non-Base64 input sequence that would normally trigger a decode error; e.g., Base64 data followed by a ':'. With this flag, it is possible to decode such a Base64 prefix. The base64_decode_finish() function will still check that the Base64 data ends properly (padding). */ BASE64_DECODE_FLAG_EXPECT_BOUNDARY = BIT(0), /* Prohibit whitespace in the input. */ BASE64_DECODE_FLAG_NO_WHITESPACE = BIT(1), /* Require absence of padding at the end of the input. */ BASE64_DECODE_FLAG_NO_PADDING = BIT(2), /* Ignore padding at the end of the input. This flag is ignored when BASE64_DECODE_FLAG_NO_PADDING is also set. If both of these flags are absent, padding is required (the default). */ BASE64_DECODE_FLAG_IGNORE_PADDING = BIT(3), }; struct base64_decoder { const struct base64_scheme *b64; enum base64_decode_flags flags; /* state */ unsigned int sub_pos; unsigned char buf; bool seen_padding:1; bool seen_end:1; bool seen_boundary:1; bool finished:1; bool failed:1; }; /* Returns TRUE when base64_decode_finish() was called on this decoder. */ static inline bool base64_decode_is_finished(struct base64_decoder *dec) { return dec->finished; } /* Initialize the Base64 decoder. The b64 parameter is the definition of the particular Base64 encoding scheme that is expected. */ static inline void base64_decode_init(struct base64_decoder *dec, const struct base64_scheme *b64, enum base64_decode_flags flags) { i_zero(dec); dec->b64 = b64; dec->flags = flags; } /* Reset the Base64 decoder to its initial state. */ static inline void base64_decode_reset(struct base64_decoder *dec) { const struct base64_scheme *b64 = dec->b64; enum base64_decode_flags flags = dec->flags; base64_decode_init(dec, b64, flags); } /* Translates some form of Base64 data into binary and appends it to dest buffer. dest may point to same buffer as src. Returns 1 if all ok, 0 if end of base64 data found, -1 if data is invalid. By default, any CR, LF characters are ignored, as well as any whitespace. This can be overridden using the BASE64_DECODE_FLAG_NO_WHITESPACE flag. If src_pos is non-NULL, it's updated to first non-translated character in src. */ int base64_decode_more(struct base64_decoder *dec, const void *src, size_t src_size, size_t *src_pos_r, buffer_t *dest) ATTR_NULL(4); /* Finishes Base64 decoding. This function checks whether the encoded data ends in the proper padding. Returns 0 if all ok, and -1 if data is invalid. */ int base64_decode_finish(struct base64_decoder *dec); /* * Generic Base64 API */ /* Translates binary data into some variant of Base64. The src must not point to dest buffer. The b64 parameter is the definition of the particular Base 64 encoding scheme that is used. See below for specific functions. */ static inline void base64_scheme_encode(const struct base64_scheme *b64, enum base64_encode_flags flags, size_t max_line_len, const void *src, size_t src_size, buffer_t *dest) { struct base64_encoder enc; base64_encode_init(&enc, b64, flags, max_line_len); base64_encode_more(&enc, src, src_size, NULL, dest); base64_encode_finish(&enc, dest); } buffer_t *t_base64_scheme_encode(const struct base64_scheme *b64, enum base64_encode_flags flags, size_t max_line_len, const void *src, size_t src_size); static inline buffer_t * t_base64_scheme_encode_str(const struct base64_scheme *b64, enum base64_encode_flags flags, size_t max_line_len, const char *src) { return t_base64_scheme_encode(b64, flags, max_line_len, src, strlen(src)); } /* Translates some variant of Base64 data into binary and appends it to dest buffer. dest may point to same buffer as src. Returns 1 if all ok, 0 if end of Base64 data found, -1 if data is invalid. The b64 parameter is the definition of the particular Base 64 encoding scheme that is expected. See below for specific functions. Any CR, LF characters are ignored, as well as whitespace at beginning or end of line. */ int base64_scheme_decode(const struct base64_scheme *b64, enum base64_decode_flags flags, const void *src, size_t src_size, buffer_t *dest); /* Decode given data to a buffer allocated from data stack. The b64 parameter is the definition of the particular Base 64 encoding scheme that is expected. See below for specific functions. */ buffer_t *t_base64_scheme_decode(const struct base64_scheme *b64, enum base64_decode_flags flags, const void *src, size_t src_size); /* Decode given string to a buffer allocated from data stack. The b64 parameter is the definition of the particular Base 64 encoding scheme that is expected. See below for specific functions. */ static inline buffer_t * t_base64_scheme_decode_str(const struct base64_scheme *b64, enum base64_decode_flags flags, const char *str) { return t_base64_scheme_decode(b64, flags, str, strlen(str)); } /* Returns TRUE if c is a valid encoding character (excluding '=') for the provided base64 mapping table */ static inline bool base64_scheme_is_valid_char(const struct base64_scheme *b64, char c) { return b64->decmap[(uint8_t)c] != 0xff; } /* * "base64" encoding scheme (RFC 4648, Section 4) */ extern struct base64_scheme base64_scheme; /* Translates binary data into base64. See base64_scheme_encode(). */ static inline void base64_encode(const void *src, size_t src_size, buffer_t *dest) { base64_scheme_encode(&base64_scheme, 0, 0, src, src_size, dest); } static inline buffer_t * t_base64_encode(enum base64_encode_flags flags, size_t max_line_len, const void *src, size_t src_size) { return t_base64_scheme_encode(&base64_scheme, flags, max_line_len, src, src_size); } static inline buffer_t * t_base64_encode_str(enum base64_encode_flags flags, size_t max_line_len, const char *src) { return t_base64_scheme_encode(&base64_scheme, flags, max_line_len, src, strlen(src)); } /* Translates base64 data into binary and appends it to dest buffer. See base64_scheme_decode(). The src_pos_r parameter is deprecated and MUST be NULL. */ static inline int base64_decode(const void *src, size_t src_size, size_t *src_pos_r ATTR_UNUSED, buffer_t *dest) ATTR_NULL(3) { // NOTE: src_pos_r is deprecated here; to be removed in v2.4 */ i_assert(src_pos_r == NULL); return base64_scheme_decode(&base64_scheme, 0, src, src_size, dest); } /* Decode given data to a buffer allocated from data stack. */ static inline buffer_t * t_base64_decode(enum base64_decode_flags flags, const void *src, size_t src_size) { return t_base64_scheme_decode(&base64_scheme, flags, src, src_size); } /* Decode given string to a buffer allocated from data stack. */ static inline buffer_t *t_base64_decode_str(const char *str) { return t_base64_scheme_decode_str(&base64_scheme, 0, str); } /* Returns TRUE if c is a valid base64 encoding character (excluding '=') */ static inline bool base64_is_valid_char(char c) { return base64_scheme_is_valid_char(&base64_scheme, c); } /* * "base64url" encoding scheme (RFC 4648, Section 5) */ extern struct base64_scheme base64url_scheme; /* Translates binary data into base64url. See base64_scheme_encode(). */ static inline void base64url_encode(enum base64_encode_flags flags, size_t max_line_len, const void *src, size_t src_size, buffer_t *dest) { base64_scheme_encode(&base64url_scheme, flags, max_line_len, src, src_size, dest); } static inline buffer_t * t_base64url_encode(enum base64_encode_flags flags, size_t max_line_len, const void *src, size_t src_size) { return t_base64_scheme_encode(&base64url_scheme, flags, max_line_len, src, src_size); } static inline buffer_t * t_base64url_encode_str(enum base64_encode_flags flags, size_t max_line_len, const char *src) { return t_base64_scheme_encode(&base64url_scheme, flags, max_line_len, src, strlen(src)); } /* Translates base64url data into binary and appends it to dest buffer. See base64_scheme_decode(). */ static inline int base64url_decode(enum base64_decode_flags flags, const void *src, size_t src_size, buffer_t *dest) { return base64_scheme_decode(&base64url_scheme, flags, src, src_size, dest); } /* Decode given data to a buffer allocated from data stack. */ static inline buffer_t * t_base64url_decode(enum base64_decode_flags flags, const void *src, size_t src_size) { return t_base64_scheme_decode(&base64url_scheme, flags, src, src_size); } /* Decode given string to a buffer allocated from data stack. */ static inline buffer_t * t_base64url_decode_str(enum base64_decode_flags flags, const char *str) { return t_base64_scheme_decode_str(&base64url_scheme, flags, str); } /* Returns TRUE if c is a valid base64url encoding character (excluding '=') */ static inline bool base64url_is_valid_char(char c) { return base64_scheme_is_valid_char(&base64url_scheme, c); } #endif dovecot-2.3.21.1/src/lib/test-str-sanitize.c0000644000000000000000000000744414656633576015440 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str.h" #include "str-sanitize.h" struct str_sanitize_test { const char *str; unsigned int max_len; const char *sanitized; /* NULL for no change */ }; static void test_str_sanitize_max_bytes(void) { static const struct str_sanitize_test tests[] = { { NULL, 2, NULL }, { "", 2, NULL }, { "a", 2, NULL }, { "ab", 2, NULL }, { "abc", 2, "..." }, { "abcd", 3, "..." }, { "abcde", 4, "a..." }, { "\xD1\x81", 1, "..." }, { "\xD1\x81", 2, "\xD1\x81" }, { "\xD1\x81", 3, NULL }, { "\xC3\xA4\xC3\xA4zyxa", 1, "..." }, { "\xC3\xA4\xC3\xA4zyxa", 2, "..." }, { "\xC3\xA4\xC3\xA4zyxa", 3, "..." }, { "\xC3\xA4\xC3\xA4zyxa", 4, "..." }, { "\xC3\xA4\xC3\xA4zyxa", 5, "\xC3\xA4..." }, { "\xC3\xA4\xC3\xA4zyxa", 6, "\xC3\xA4..." }, { "\xC3\xA4\xC3\xA4zyxa", 7, "\xC3\xA4\xC3\xA4..." }, { "\xC3\xA4\xC3\xA4zyxa", 8, "\xC3\xA4\xC3\xA4zyxa" }, { "\001x\x1fy\x81", 10, "?x?y?" } }; const char *str; string_t *str2; unsigned int i; test_begin("str_sanitize"); for (i = 0; i < N_ELEMENTS(tests); i++) { str = str_sanitize(tests[i].str, tests[i].max_len); if (tests[i].sanitized != NULL) test_assert_idx(null_strcmp(str, tests[i].sanitized) == 0, i); else test_assert_idx(str == tests[i].str, i); } test_end(); test_begin("str_sanitize_append"); str2 = t_str_new(128); for (i = 0; i < N_ELEMENTS(tests); i++) { if (tests[i].str == NULL) continue; str_truncate(str2, 0); str_append(str2, "1234567890"); str_sanitize_append(str2, tests[i].str, tests[i].max_len); test_assert_idx(str_begins(str_c(str2), "1234567890"), i); if (tests[i].sanitized != NULL) test_assert_idx(strcmp(str_c(str2)+10, tests[i].sanitized) == 0, i); else test_assert_idx(strcmp(str_c(str2)+10, tests[i].str) == 0, i); } test_end(); } static void test_str_sanitize_max_codepoints(void) { static const struct str_sanitize_test tests[] = { { NULL, 2, NULL }, { "", 2, NULL }, { "a", 2, NULL }, { "ab", 2, NULL }, { "abc", 2, "a\xE2\x80\xA6" }, { "abcd", 3, "ab\xE2\x80\xA6" }, { "abcde", 4, "abc\xE2\x80\xA6" }, { "\xD1\x81", 1, "\xD1\x81" }, { "\xD1\x81", 2, "\xD1\x81" }, { "\xD1\x81", 3, NULL }, { "\xC3\xA4\xC3\xA4zyxa", 1, "\xE2\x80\xA6" }, { "\xC3\xA4\xC3\xA4zyxa", 2, "\xC3\xA4\xE2\x80\xA6" }, { "\xC3\xA4\xC3\xA4zyxa", 3, "\xC3\xA4\xC3\xA4\xE2\x80\xA6" }, { "\xC3\xA4\xC3\xA4zyxa", 4, "\xC3\xA4\xC3\xA4z\xE2\x80\xA6" }, { "\xC3\xA4\xC3\xA4zyxa", 5, "\xC3\xA4\xC3\xA4zy\xE2\x80\xA6" }, { "\xC3\xA4\xC3\xA4zyxa", 6, "\xC3\xA4\xC3\xA4zyxa" }, { "\xC3\xA4\xC3\xA4zyxa", 7, "\xC3\xA4\xC3\xA4zyxa" }, { "\xC3\xA4\xC3\xA4zyxa", 8, "\xC3\xA4\xC3\xA4zyxa" }, { "\001x\x1fy\x81", 10, "\xEF\xBF\xBDx\xEF\xBF\xBDy\xEF\xBF\xBD" } }; const char *str; string_t *str2; unsigned int i; test_begin("str_sanitize_utf8"); for (i = 0; i < N_ELEMENTS(tests); i++) { str = str_sanitize_utf8(tests[i].str, tests[i].max_len); if (tests[i].sanitized != NULL) test_assert_idx(null_strcmp(str, tests[i].sanitized) == 0, i); else test_assert_idx(str == tests[i].str, i); } test_end(); test_begin("str_sanitize_append_utf8"); str2 = t_str_new(128); for (i = 0; i < N_ELEMENTS(tests); i++) { if (tests[i].str == NULL) continue; str_truncate(str2, 0); str_append(str2, "1234567890"); str_sanitize_append_utf8(str2, tests[i].str, tests[i].max_len); test_assert_idx(strncmp(str_c(str2), "1234567890", 10) == 0, i); if (tests[i].sanitized != NULL) test_assert_idx(strcmp(str_c(str2)+10, tests[i].sanitized) == 0, i); else test_assert_idx(strcmp(str_c(str2)+10, tests[i].str) == 0, i); } test_end(); } void test_str_sanitize(void) { test_str_sanitize_max_bytes(); test_str_sanitize_max_codepoints(); } dovecot-2.3.21.1/src/lib/istream-crlf.c0000644000000000000000000001145214656633576014411 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "istream-private.h" #include "istream-crlf.h" struct crlf_istream { struct istream_private istream; bool pending_cr:1; bool last_cr:1; }; static int i_stream_crlf_read_common(struct crlf_istream *cstream) { struct istream_private *stream = &cstream->istream; size_t size, avail; ssize_t ret; size = i_stream_get_data_size(stream->parent); if (size == 0) { ret = i_stream_read_memarea(stream->parent); if (ret <= 0) { i_assert(ret != -2); /* 0 sized buffer can't be full */ stream->istream.stream_errno = stream->parent->stream_errno; stream->istream.eof = stream->parent->eof; return ret; } size = i_stream_get_data_size(stream->parent); i_assert(size != 0); } if (!i_stream_try_alloc(stream, size, &avail)) return -2; return 1; } static ssize_t i_stream_crlf_read_crlf(struct istream_private *stream) { struct crlf_istream *cstream = container_of(stream, struct crlf_istream, istream); const unsigned char *data, *ptr, *src, *src_end; unsigned char *dest, *dest_end; size_t size, copy_len; ssize_t ret; ret = i_stream_crlf_read_common(cstream); if (ret <= 0) return ret; /* at least one byte was read */ data = i_stream_get_data(stream->parent, &size); dest = stream->w_buffer + stream->pos; dest_end = stream->w_buffer + stream->buffer_size; src = data; src_end = data + size; /* @UNSAFE: add missing CRs */ if (*src == '\n') { if (!cstream->last_cr && dest < dest_end) *dest++ = '\r'; if (dest < dest_end) { *dest++ = '\n'; src++; } } while (dest < dest_end) { i_assert(src <= src_end); ptr = memchr(src, '\n', src_end - src); if (ptr == NULL) ptr = src_end; /* copy data up to LF */ copy_len = ptr - src; if (dest + copy_len > dest_end) copy_len = dest_end - dest; if (copy_len > 0) { memcpy(dest, src, copy_len); dest += copy_len; src += copy_len; } i_assert(dest <= dest_end && src <= src_end); if (dest == dest_end || src == src_end) break; /* add the CR if necessary and copy the LF. (src >= data+1, because data[0]=='\n' was handled before this loop) */ if (src[-1] != '\r') *dest++ = '\r'; if (dest == dest_end) break; *dest++ = '\n'; src++; i_assert(src == ptr + 1); } i_assert(dest != stream->w_buffer); cstream->last_cr = dest[-1] == '\r'; i_stream_skip(stream->parent, src - data); ret = (dest - stream->w_buffer) - stream->pos; i_assert(ret > 0); stream->pos = dest - stream->w_buffer; return ret; } static ssize_t i_stream_crlf_read_lf(struct istream_private *stream) { struct crlf_istream *cstream = container_of(stream, struct crlf_istream, istream); const unsigned char *data, *p; size_t i, dest, size, max; ssize_t ret; bool pending_cr; ret = i_stream_crlf_read_common(cstream); if (ret <= 0) return ret; data = i_stream_get_data(stream->parent, &size); /* @UNSAFE */ /* \r\n -> \n \r -> \r \r\r\n -> \r\n */ dest = stream->pos; pending_cr = cstream->pending_cr; for (i = 0; i < size && dest < stream->buffer_size; ) { if (data[i] == '\r') { if (pending_cr) { /* \r\r */ stream->w_buffer[dest++] = '\r'; } else { pending_cr = TRUE; } i++; } else if (data[i] == '\n') { /* [\r]\n */ pending_cr = FALSE; stream->w_buffer[dest++] = '\n'; i++; } else if (pending_cr) { /* \r */ pending_cr = FALSE; stream->w_buffer[dest++] = '\r'; } else { /* copy everything until the next \r */ max = I_MIN(size - i, stream->buffer_size - dest); p = memchr(data + i, '\r', max); if (p != NULL) max = p - (data+i); memcpy(stream->w_buffer + dest, data + i, max); dest += max; i += max; } } i_assert(i <= size); i_assert(dest <= stream->buffer_size); cstream->pending_cr = pending_cr; i_stream_skip(stream->parent, i); ret = dest - stream->pos; if (ret == 0) { i_assert(cstream->pending_cr && size == 1); return i_stream_crlf_read_lf(stream); } i_assert(ret > 0); stream->pos = dest; return ret; } static struct istream * i_stream_create_crlf_full(struct istream *input, bool crlf) { struct crlf_istream *cstream; cstream = i_new(struct crlf_istream, 1); cstream->istream.max_buffer_size = input->real_stream->max_buffer_size; cstream->istream.read = crlf ? i_stream_crlf_read_crlf : i_stream_crlf_read_lf; cstream->istream.istream.readable_fd = FALSE; cstream->istream.istream.blocking = input->blocking; cstream->istream.istream.seekable = FALSE; return i_stream_create(&cstream->istream, input, i_stream_get_fd(input), 0); } struct istream *i_stream_create_crlf(struct istream *input) { return i_stream_create_crlf_full(input, TRUE); } struct istream *i_stream_create_lf(struct istream *input) { return i_stream_create_crlf_full(input, FALSE); } dovecot-2.3.21.1/src/lib/json-tree.c0000644000000000000000000001043014656633576013722 00000000000000/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "json-tree.h" struct json_tree { pool_t pool; struct json_tree_node *root, *cur, *cur_child; }; struct json_tree * json_tree_init_type(enum json_type container) { struct json_tree *tree; pool_t pool; pool = pool_alloconly_create("json tree", 1024); tree = p_new(pool, struct json_tree, 1); tree->pool = pool; tree->root = tree->cur = p_new(tree->pool, struct json_tree_node, 1); tree->cur->value_type = container == JSON_TYPE_ARRAY ? container : JSON_TYPE_OBJECT; return tree; } void json_tree_deinit(struct json_tree **_tree) { struct json_tree *tree = *_tree; *_tree = NULL; pool_unref(&tree->pool); } static void json_tree_append_child(struct json_tree *tree, enum json_type type, const char *value) { struct json_tree_node *node; node = p_new(tree->pool, struct json_tree_node, 1); node->parent = tree->cur; node->value_type = type; node->value.str = p_strdup(tree->pool, value); if (tree->cur_child == NULL) tree->cur->value.child = node; else tree->cur_child->next = node; tree->cur_child = node; } static void json_tree_set_cur(struct json_tree *tree, struct json_tree_node *node) { tree->cur = node; tree->cur_child = tree->cur->value.child; if (tree->cur_child != NULL) { while (tree->cur_child->next != NULL) tree->cur_child = tree->cur_child->next; } } static int json_tree_append_value(struct json_tree *tree, enum json_type type, const char *value) { switch (tree->cur->value_type) { case JSON_TYPE_OBJECT_KEY: /* "key": value - we already added the node and set its key, so now just set the value */ tree->cur->value_type = type; tree->cur->value.str = p_strdup(tree->pool, value); json_tree_set_cur(tree, tree->cur->parent); break; case JSON_TYPE_ARRAY: /* element in array - add a new node */ json_tree_append_child(tree, type, value); break; default: return -1; } return 0; } int json_tree_append(struct json_tree *tree, enum json_type type, const char *value) { switch (type) { case JSON_TYPE_OBJECT_KEY: if (tree->cur->value_type != JSON_TYPE_OBJECT) return -1; json_tree_append_child(tree, type, NULL); json_tree_set_cur(tree, tree->cur_child); tree->cur->key = p_strdup(tree->pool, value); break; case JSON_TYPE_ARRAY: if (json_tree_append_value(tree, type, NULL) < 0) return -1; json_tree_set_cur(tree, tree->cur_child); break; case JSON_TYPE_OBJECT: if (tree->cur->value_type == JSON_TYPE_OBJECT_KEY) tree->cur->value_type = JSON_TYPE_OBJECT; else if (tree->cur->value_type == JSON_TYPE_ARRAY) { json_tree_append_child(tree, type, NULL); json_tree_set_cur(tree, tree->cur_child); } else { return -1; } break; case JSON_TYPE_OBJECT_END: if (tree->cur->parent == NULL || tree->cur->value_type != JSON_TYPE_OBJECT) return -1; json_tree_set_cur(tree, tree->cur->parent); break; case JSON_TYPE_ARRAY_END: if (tree->cur->parent == NULL || tree->cur->value_type != JSON_TYPE_ARRAY) return -1; json_tree_set_cur(tree, tree->cur->parent); break; case JSON_TYPE_STRING: case JSON_TYPE_NUMBER: case JSON_TYPE_TRUE: case JSON_TYPE_FALSE: case JSON_TYPE_NULL: if (json_tree_append_value(tree, type, value) < 0) return -1; break; } return 0; } const struct json_tree_node * json_tree_root(const struct json_tree *tree) { return tree->root; } const struct json_tree_node * json_tree_find_key(const struct json_tree_node *node, const char *key) { i_assert(node->value_type == JSON_TYPE_OBJECT); node = json_tree_get_child(node); for (; node != NULL; node = node->next) { if (node->key != NULL && strcmp(node->key, key) == 0) return node; } return NULL; } const struct json_tree_node * json_tree_find_child_with(const struct json_tree_node *node, const char *key, const char *value) { const struct json_tree_node *child; i_assert(node->value_type == JSON_TYPE_OBJECT || node->value_type == JSON_TYPE_ARRAY); for (node = json_tree_get_child(node); node != NULL; node = node->next) { if (node->value_type != JSON_TYPE_OBJECT) continue; child = json_tree_find_key(node, key); if (child != NULL && json_tree_get_value_str(child) != NULL && strcmp(json_tree_get_value_str(child), value) == 0) return node; } return NULL; } dovecot-2.3.21.1/src/lib/test-connection.c0000644000000000000000000004511714656633576015142 00000000000000/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "ioloop.h" #include "connection.h" #include "istream.h" #include "ostream.h" #include "strnum.h" #include "strescape.h" #include static const struct connection_settings client_set = { .service_name_in = "TEST-S", .service_name_out = "TEST-C", .major_version = 1, .minor_version = 0, .client = TRUE, .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, }; static const struct connection_settings server_set = { .service_name_in = "TEST-C", .service_name_out = "TEST-S", .major_version = 1, .minor_version = 0, .client = FALSE, .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, }; static bool received_quit = FALSE; static bool was_resumed = FALSE; static bool was_idle_killed = FALSE; static int received_count = 0; static void test_connection_run(const struct connection_settings *set_s, const struct connection_settings *set_c, const struct connection_vfuncs *v_s, const struct connection_vfuncs *v_c, unsigned int iter_count) { int fds[2]; struct ioloop *loop = io_loop_create(); struct connection_list *clients = connection_list_init(set_c, v_c); struct connection_list *servers = connection_list_init(set_s, v_s); struct connection *conn_c = i_new(struct connection, 1); struct connection *conn_s = i_new(struct connection, 1); conn_s->ioloop = loop; conn_c->ioloop = loop; for(unsigned int iters = 0; iters < iter_count; iters++) { test_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0); fd_set_nonblock(fds[0], TRUE); fd_set_nonblock(fds[1], TRUE); connection_init_server(servers, conn_s, "client", fds[1], fds[1]); connection_init_client_fd(clients, conn_c, "server", fds[0], fds[0]); io_loop_run(loop); connection_deinit(conn_c); connection_deinit(conn_s); } i_free(conn_c); i_free(conn_s); connection_list_deinit(&clients); connection_list_deinit(&servers); io_loop_destroy(&loop); } /* BEGIN SIMPLE TEST */ static void test_connection_simple_client_connected(struct connection *conn, bool success) { if (conn->list->set.client) o_stream_nsend_str(conn->output, "QUIT\n"); test_assert(success); }; static int test_connection_simple_input_args(struct connection *conn, const char *const *args) { if (strcmp(args[0], "QUIT") == 0) { received_quit = TRUE; connection_disconnect(conn); return 0; } i_error("invalid input"); return -1; } static void test_connection_simple_destroy(struct connection *conn) { io_loop_stop(conn->ioloop); connection_disconnect(conn); } static const struct connection_vfuncs simple_v = { .client_connected = test_connection_simple_client_connected, .input_args = test_connection_simple_input_args, .destroy = test_connection_simple_destroy, }; static void test_connection_simple(void) { test_begin("connection simple"); test_connection_run(&server_set, &client_set, &simple_v, &simple_v, 10); test_assert(received_quit); received_quit = FALSE; test_end(); } /* BEGIN NO INPUT TEST */ static const struct connection_settings no_input_client_set = { .service_name_in = "TEST-S", .service_name_out = "TEST-C", .major_version = 1, .minor_version = 0, .client = TRUE, .input_max_size = 0, .output_max_size = SIZE_MAX, }; static const struct connection_settings no_input_server_set = { .service_name_in = "TEST-C", .service_name_out = "TEST-S", .major_version = 1, .minor_version = 0, .client = FALSE, .input_max_size = 0, .output_max_size = SIZE_MAX, }; static void test_connection_no_input_input(struct connection *conn) { const char *input; struct istream *is = i_stream_create_fd(conn->fd_in, SIZE_MAX); i_stream_set_blocking(is, FALSE); while ((input = i_stream_read_next_line(is)) != NULL) { const char *const *args = t_strsplit_tabescaped(input); if (!conn->handshake_received) { if (connection_handshake_args_default(conn, args) > -1) conn->handshake_received = TRUE; continue; } if (strcmp(args[0], "QUIT") == 0) { received_quit = TRUE; io_loop_stop(conn->ioloop); break; } } i_stream_unref(&is); } static const struct connection_vfuncs no_input_v = { .client_connected = test_connection_simple_client_connected, .input = test_connection_no_input_input, .destroy = test_connection_simple_destroy, }; static void test_connection_no_input(void) { test_begin("connection no input stream"); test_connection_run(&no_input_server_set, &no_input_client_set, &no_input_v, &no_input_v, 1); test_assert(received_quit); received_quit = FALSE; test_end(); } /* BEGIN HANDSHAKE TEST */ static void test_connection_custom_handshake_client_connected(struct connection *conn, bool success) { if (conn->list->set.client) o_stream_nsend_str(conn->output, "HANDSHAKE\tFRIEND\n"); test_assert(success); }; static int test_connection_custom_handshake_args(struct connection *conn, const char *const *args) { if (!conn->version_received) { if (connection_handshake_args_default(conn, args) < 0) return -1; return 0; } if (!conn->handshake_received) { if (strcmp(args[0], "HANDSHAKE") == 0 && strcmp(args[1], "FRIEND") == 0) { if (!conn->list->set.client) o_stream_nsend_str(conn->output, "HANDSHAKE\tFRIEND\n"); else o_stream_nsend_str(conn->output, "QUIT\n"); return 1; } return -1; } return 1; } static const struct connection_vfuncs custom_handshake_v = { .client_connected = test_connection_custom_handshake_client_connected, .input_args = test_connection_simple_input_args, .handshake_args = test_connection_custom_handshake_args, .destroy = test_connection_simple_destroy, }; static void test_connection_custom_handshake(void) { test_begin("connection custom handshake"); test_connection_run(&server_set, &client_set, &custom_handshake_v, &custom_handshake_v, 10); test_assert(received_quit); received_quit = FALSE; test_end(); } /* BEGIN PING PONG TEST */ static int test_connection_ping_pong_input_args(struct connection *conn, const char *const *args) { unsigned int n; test_assert(args[0] != NULL && args[1] != NULL); if (args[0] == NULL || args[1] == NULL) return -1; if (str_to_uint(args[1], &n) < 0) return -1; if (n > 10) o_stream_nsend_str(conn->output, "QUIT\t0\n"); else if (strcmp(args[0], "QUIT") == 0) connection_disconnect(conn); else if (strcmp(args[0], "PING") == 0) { received_count++; o_stream_nsend_str(conn->output, t_strdup_printf("PONG\t%u\n", n+1)); } else if (strcmp(args[0], "PONG") == 0) o_stream_nsend_str(conn->output, t_strdup_printf("PING\t%u\n", n)); else return -1; return 1; } static void test_connection_ping_pong_client_connected(struct connection *conn, bool success) { o_stream_nsend_str(conn->output, "PING\t1\n"); test_assert(success); }; static const struct connection_vfuncs ping_pong_v = { .client_connected = test_connection_ping_pong_client_connected, .input_args = test_connection_ping_pong_input_args, .destroy = test_connection_simple_destroy, }; static void test_connection_ping_pong(void) { test_begin("connection ping pong"); test_connection_run(&server_set, &client_set, &ping_pong_v, &ping_pong_v, 10); test_assert(received_count == 100); test_end(); } /* BEGIN INPUT FULL TEST */ static const struct connection_settings input_full_client_set = { .service_name_in = "TEST-S", .service_name_out = "TEST-C", .major_version = 1, .minor_version = 0, .client = TRUE, .input_max_size = 100, .output_max_size = SIZE_MAX, }; static int test_connection_input_full_input_args(struct connection *conn, const char *const *args ATTR_UNUSED) { /* send a long line */ for (unsigned int i = 0; i < 200; i++) o_stream_nsend(conn->output, "c", 1); return 1; } static void test_connection_input_full_destroy(struct connection *conn) { test_assert(conn->disconnect_reason == CONNECTION_DISCONNECT_BUFFER_FULL || conn->list->set.client == FALSE); test_connection_simple_destroy(conn); } static const struct connection_vfuncs input_full_v = { .client_connected = test_connection_simple_client_connected, .input_args = test_connection_input_full_input_args, .destroy = test_connection_input_full_destroy, }; static void test_connection_input_full(void) { test_begin("connection input full"); test_connection_run(&server_set, &input_full_client_set, &input_full_v, &simple_v, 10); test_end(); } /* BEGIN RESUME TEST */ static struct timeout *to_send_quit = NULL; static struct timeout *to_resume = NULL; static void test_connection_resume_client_connected(struct connection *conn, bool success) { test_assert(success); o_stream_nsend_str(conn->output, "BEGIN\n"); } static void test_connection_resume_continue(struct connection *conn) { timeout_remove(&to_resume); /* ensure QUIT wasn't received early */ was_resumed = !received_quit; connection_input_resume(conn); } static void test_connection_resume_send_quit(struct connection *conn) { timeout_remove(&to_send_quit); o_stream_nsend_str(conn->output, "QUIT\n"); } static int test_connection_resume_input_args(struct connection *conn, const char *const *args) { test_assert(args[0] != NULL); if (args[0] == NULL) return -1; if (strcmp(args[0], "BEGIN") == 0) { o_stream_nsend_str(conn->output, "HALT\n"); to_send_quit = timeout_add_short(10, test_connection_resume_send_quit, conn); } else if (strcmp(args[0], "HALT") == 0) { connection_input_halt(conn); to_resume = timeout_add_short(100, test_connection_resume_continue, conn); } else if (strcmp(args[0], "QUIT") == 0) { received_quit = TRUE; connection_disconnect(conn); } return 1; } static const struct connection_vfuncs resume_v = { .client_connected = test_connection_resume_client_connected, .input_args = test_connection_resume_input_args, .destroy = test_connection_simple_destroy, }; static void test_connection_resume(void) { test_begin("connection resume"); was_resumed = received_quit = FALSE; test_connection_run(&server_set, &client_set, &resume_v, &resume_v, 1); test_assert(was_resumed); test_assert(received_quit); was_resumed = received_quit = FALSE; test_end(); } /* BEGIN RESUME PIPELINED TEST */ static int test_connection_resume_pipelined_input_args(struct connection *conn, const char *const *args) { test_assert(args[0] != NULL); if (args[0] == NULL) return -1; if (strcmp(args[0], "BEGIN") == 0) { o_stream_nsend_str(conn->output, "HALT\nQUIT\n"); } else if (strcmp(args[0], "HALT") == 0) { connection_input_halt(conn); to_resume = timeout_add_short(100, test_connection_resume_continue, conn); return 0; } else if (strcmp(args[0], "QUIT") == 0) { received_quit = TRUE; connection_disconnect(conn); } return 1; } static const struct connection_vfuncs resume_pipelined_v = { .client_connected = test_connection_resume_client_connected, .input_args = test_connection_resume_pipelined_input_args, .destroy = test_connection_simple_destroy, }; static void test_connection_resume_pipelined(void) { test_begin("connection resume pipelined"); was_resumed = received_quit = FALSE; test_connection_run(&server_set, &client_set, &resume_pipelined_v, &resume_pipelined_v, 1); test_assert(was_resumed); test_assert(received_quit); was_resumed = received_quit = FALSE; test_end(); } /* BEGIN IDLE KILL TEST */ static void test_connection_idle_kill_client_connected(struct connection *conn ATTR_UNUSED, bool success) { test_assert(success); }; static const struct connection_settings idle_kill_server_set = { .service_name_in = "TEST-C", .service_name_out = "TEST-S", .major_version = 1, .minor_version = 0, .client = FALSE, .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, .input_idle_timeout_secs = 1, }; static void test_connection_idle_kill_timeout(struct connection *conn) { was_idle_killed = TRUE; o_stream_nsend_str(conn->output, "QUIT\n"); } static const struct connection_vfuncs idle_kill_v = { .client_connected = test_connection_idle_kill_client_connected, .input_args = test_connection_simple_input_args, .destroy = test_connection_simple_destroy, .idle_timeout = test_connection_idle_kill_timeout, }; static void test_connection_idle_kill(void) { test_begin("connection idle kill"); was_idle_killed = received_quit = FALSE; test_connection_run(&idle_kill_server_set, &client_set, &idle_kill_v, &idle_kill_v, 1); test_assert(received_quit); test_assert(was_idle_killed); was_idle_killed = received_quit = FALSE; test_end(); } /* BEGIN HANDSHAKE FAILED TEST (version) */ static void test_connection_handshake_failed_destroy(struct connection *conn) { test_assert(conn->disconnect_reason == CONNECTION_DISCONNECT_HANDSHAKE_FAILED); test_connection_simple_destroy(conn); } static const struct connection_vfuncs handshake_failed_version_v = { .client_connected = test_connection_simple_client_connected, .input_args = test_connection_simple_input_args, .destroy = test_connection_handshake_failed_destroy, }; static void test_connection_handshake_failed_version(void) { static const struct connection_settings client_sets[] = { { .service_name_in = "TEST-S", .service_name_out = "TEST-S", .major_version = 1, .minor_version = 0, .client = TRUE, .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, }, { .service_name_in = "TEST-C", .service_name_out = "TEST-C", .major_version = 1, .minor_version = 0, .client = TRUE, .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, }, { .service_name_in = "TEST-S", .service_name_out = "TEST-C", .major_version = 2, .minor_version = 0, .client = TRUE, .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, } }; static const struct connection_settings client_set_minor = { .service_name_in = "TEST-S", .service_name_out = "TEST-C", .major_version = 1, .minor_version = 2, .client = TRUE, .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, }; test_begin("connection handshake failed (version)"); test_expect_errors(N_ELEMENTS(client_sets)); /* this should stay FALSE during the version mismatch sets */ received_quit = FALSE; for (size_t i = 0; i < N_ELEMENTS(client_sets); i++) { test_connection_run(&server_set, &client_sets[i], &simple_v, &handshake_failed_version_v, 1); test_assert(!received_quit); } received_quit = FALSE; test_connection_run(&server_set, &client_set_minor, &simple_v, &simple_v, 1); test_assert(received_quit); received_quit = FALSE; test_end(); } /* BEGIN HANDSHAKE FAILED TEST (args) */ static int test_connection_handshake_failed_1_args(struct connection *conn ATTR_UNUSED, const char *const *args ATTR_UNUSED) { /* just fail */ return -1; } static const struct connection_vfuncs handshake_failed_1_v = { .client_connected = test_connection_simple_client_connected, .input_args = test_connection_simple_input_args, .handshake_args = test_connection_handshake_failed_1_args, .destroy = test_connection_handshake_failed_destroy, }; static void test_connection_handshake_failed_args(void) { test_begin("connection handshake failed (handshake_args)"); test_connection_run(&server_set, &client_set, &simple_v, &handshake_failed_1_v, 10); test_end(); } /* BEGIN HANDSHAKE FAILED TEST (handshake_line) */ static int test_connection_handshake_failed_2_line(struct connection *conn ATTR_UNUSED, const char *line ATTR_UNUSED) { return -1; } static const struct connection_vfuncs handshake_failed_2_v = { .client_connected = test_connection_simple_client_connected, .input_args = test_connection_simple_input_args, .handshake_line = test_connection_handshake_failed_2_line, .destroy = test_connection_handshake_failed_destroy, }; static void test_connection_handshake_failed_line(void) { test_begin("connection handshake failed (handshake_line)"); test_connection_run(&server_set, &client_set, &simple_v, &handshake_failed_2_v, 10); test_end(); } /* BEGIN HANDSHAKE FAILED TEST (handshake) */ static int test_connection_handshake_failed_3(struct connection *conn ATTR_UNUSED) { return -1; } static const struct connection_vfuncs handshake_failed_3_v = { .client_connected = test_connection_simple_client_connected, .input_args = test_connection_simple_input_args, .handshake = test_connection_handshake_failed_3, .destroy = test_connection_handshake_failed_destroy, }; static void test_connection_handshake_failed_input(void) { test_begin("connection handshake failed (handshake)"); test_connection_run(&server_set, &client_set, &simple_v, &handshake_failed_3_v, 10); test_end(); } /* BEGIN CONNECTION ERRORED TEST (ensure correct error) */ static void test_connection_errored_client_connected(struct connection *conn, bool success) { test_assert(success); o_stream_nsend_str(conn->output, "HELLO\n"); } static void test_connection_errored_destroy(struct connection *conn) { test_assert(conn->disconnect_reason == CONNECTION_DISCONNECT_DEINIT); test_connection_simple_destroy(conn); } static int test_connection_errored_input_line(struct connection *conn ATTR_UNUSED, const char *line) { if (str_begins(line, "VERSION")) return 1; return -1; } static const struct connection_vfuncs test_connection_errored_1_v = { .client_connected = test_connection_errored_client_connected, .input_line = test_connection_errored_input_line, .destroy = test_connection_errored_destroy, }; static void test_connection_input_error_reason(void) { test_begin("connection input error (correct disconnect reason)"); test_connection_run(&server_set, &client_set, &test_connection_errored_1_v, &test_connection_errored_1_v, 10); test_end(); } /* END CONNECTION ERRORED TEST */ /* BEGIN NO VERSION TEST */ static const struct connection_settings no_version_client_set = { .major_version = 0, .minor_version = 0, .client = TRUE, .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, .dont_send_version = TRUE, }; static const struct connection_settings no_version_server_set = { .major_version = 0, .minor_version = 0, .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, .dont_send_version = TRUE, }; static void test_connection_no_version(void) { test_begin("connection no version sent"); test_connection_run(&no_version_server_set, &no_version_client_set, &simple_v, &simple_v, 10); test_end(); } /* END NO VERSION TEST */ void test_connection(void) { test_connection_simple(); test_connection_no_input(); test_connection_custom_handshake(); test_connection_ping_pong(); test_connection_input_full(); test_connection_resume(); test_connection_resume_pipelined(); test_connection_idle_kill(); test_connection_handshake_failed_version(); test_connection_handshake_failed_args(); test_connection_handshake_failed_line(); test_connection_handshake_failed_input(); test_connection_input_error_reason(); test_connection_no_version(); } dovecot-2.3.21.1/src/lib/strescape.h0000644000000000000000000000321014656633576014010 00000000000000#ifndef STRESCAPE_H #define STRESCAPE_H #define IS_ESCAPED_CHAR(c) ((c) == '"' || (c) == '\\' || (c) == '\'') /* escape all '\', '"' and "'" characters, this is nul safe */ const char *str_nescape(const void *str, size_t len); /* escape string */ static inline const char *str_escape(const char *str) { return str_nescape(str, strlen(str)); } void str_append_escaped(string_t *dest, const void *src, size_t src_size); /* remove all '\' characters, append to given string */ void str_append_unescaped(string_t *dest, const void *src, size_t src_size); /* remove all '\' characters */ char *str_unescape(char *str); /* Remove all '\' chars from str until '"' is reached and return the unescaped string. *str is updated to point to the character after the '"'. Returns 0 if ok, -1 if '"' wasn't found. */ int str_unescape_next(const char **str, const char **unescaped_r); /* For Dovecot's internal protocols: Escape \001, \t, \r and \n characters using \001. */ const char *str_tabescape(const char *str); void str_append_tabescaped(string_t *dest, const char *src); void str_append_tabescaped_n(string_t *dest, const unsigned char *src, size_t src_size); void str_append_tabunescaped(string_t *dest, const void *src, size_t src_size); char *str_tabunescape(char *str); const char *t_str_tabunescape(const char *str); char **p_strsplit_tabescaped(pool_t pool, const char *str); const char *const *t_strsplit_tabescaped(const char *str); /* Same as t_strsplit_tabescaped(), but the input string is modified and the returned pointers inside the array point to the original string. */ const char *const *t_strsplit_tabescaped_inplace(char *str); #endif dovecot-2.3.21.1/src/lib/ostream-unix.h0000644000000000000000000000053214656633576014456 00000000000000#ifndef OSTREAM_UNIX_H #define OSTREAM_UNIX_H struct ostream *o_stream_create_unix(int fd, size_t max_buffer_size); /* Write fd to UNIX socket along with the next outgoing data block. Returns TRUE if fd is accepted, and FALSE if a previous fd still needs to be sent. */ bool o_stream_unix_write_fd(struct ostream *output, int fd); #endif dovecot-2.3.21.1/src/lib/test-malloc-overflow.c0000644000000000000000000000621114656633576016103 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" static void test_malloc_overflow_multiply(void) { static const struct { size_t a, b; } tests[] = { { 0, SIZE_MAX }, { 1, SIZE_MAX }, { SIZE_MAX/2, 2 }, }; test_begin("MALLOC_MULTIPLY()"); for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { test_assert_idx(MALLOC_MULTIPLY(tests[i].a, tests[i].b) == tests[i].a * tests[i].b, i); test_assert_idx(MALLOC_MULTIPLY(tests[i].b, tests[i].a) == tests[i].b * tests[i].a, i); } test_end(); } static void test_malloc_overflow_add(void) { static const struct { size_t a, b; } tests[] = { { 0, SIZE_MAX }, { 1, SIZE_MAX-1 }, { SIZE_MAX/2+1, SIZE_MAX/2 }, }; unsigned short n = 2; test_begin("MALLOC_ADD()"); /* check that no compiler warning is given */ test_assert(MALLOC_ADD(2, n) == 2U+n); for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { test_assert_idx(MALLOC_ADD(tests[i].a, tests[i].b) == tests[i].a + tests[i].b, i); test_assert_idx(MALLOC_ADD(tests[i].b, tests[i].a) == tests[i].b + tests[i].a, i); } test_end(); } void test_malloc_overflow(void) { test_malloc_overflow_multiply(); test_malloc_overflow_add(); } static enum fatal_test_state fatal_malloc_overflow_multiply(unsigned int *stage) { const struct { size_t a, b; } mul_tests[] = { { SIZE_MAX/2+1, 2 }, }; unsigned int i; test_expect_fatal_string("memory allocation overflow"); switch (*stage) { case 0: test_begin("MALLOC_MULTIPLY() overflows"); i_error("%zu", MALLOC_MULTIPLY((size_t)SIZE_MAX/2, (uint8_t)3)); break; case 1: i_error("%zu", MALLOC_MULTIPLY((uint8_t)3, (size_t)SIZE_MAX/2)); break; } *stage -= 2; if (*stage >= N_ELEMENTS(mul_tests)*2) { *stage -= N_ELEMENTS(mul_tests)*2; if (*stage == 0) test_end(); test_expect_fatal_string(NULL); return FATAL_TEST_FINISHED; } i = *stage / 2; if (*stage % 2 == 0) i_error("%zu", MALLOC_MULTIPLY(mul_tests[i].a, mul_tests[i].b)); else i_error("%zu", MALLOC_MULTIPLY(mul_tests[i].b, mul_tests[i].a)); return FATAL_TEST_FAILURE; } static enum fatal_test_state fatal_malloc_overflow_add(unsigned int *stage) { const struct { size_t a, b; } add_tests[] = { { SIZE_MAX, 1 }, { SIZE_MAX/2+1, SIZE_MAX/2+1 }, }; unsigned int i; test_expect_fatal_string("memory allocation overflow"); switch (*stage) { case 0: test_begin("MALLOC_ADD() overflows"); i_error("%zu", MALLOC_ADD((size_t)SIZE_MAX, (uint8_t)1)); break; case 1: i_error("%zu", MALLOC_ADD((uint8_t)1, (size_t)SIZE_MAX)); break; } *stage -= 2; if (*stage >= N_ELEMENTS(add_tests)*2) { *stage -= N_ELEMENTS(add_tests)*2; if (*stage == 0) test_end(); test_expect_fatal_string(NULL); return FATAL_TEST_FINISHED; } i = *stage / 2; if (*stage % 2 == 0) i_error("%zu", MALLOC_ADD(add_tests[i].a, add_tests[i].b)); else i_error("%zu", MALLOC_ADD(add_tests[i].b, add_tests[i].a)); return FATAL_TEST_FAILURE; } enum fatal_test_state fatal_malloc_overflow(unsigned int stage) { enum fatal_test_state state; state = fatal_malloc_overflow_multiply(&stage); if (state != FATAL_TEST_FINISHED) return state; return fatal_malloc_overflow_add(&stage); } dovecot-2.3.21.1/src/lib/mkdir-parents.c0000644000000000000000000001003014656633576014570 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "eacces-error.h" #include "mkdir-parents.h" #include "ipwd.h" #include #include #include static int ATTR_NULL(5) mkdir_chown_full(const char *path, mode_t mode, uid_t uid, gid_t gid, const char *gid_origin) { string_t *str; mode_t old_mask; unsigned int i; int ret, fd = -1, orig_errno; for (i = 0;; i++) { old_mask = umask(0); ret = mkdir(path, mode); umask(old_mask); if (ret < 0) break; fd = open(path, O_RDONLY); if (fd != -1) break; if (errno != ENOENT || i == 3) { i_error("open(%s) failed: %m", path); return -1; } /* it was just rmdir()ed by someone else? retry */ } if (ret < 0) { if (errno == EISDIR || errno == ENOSYS) { /* EISDIR check is for BSD/OS which returns it if path contains '/' at the end and it exists. ENOSYS check is for NFS mount points. */ errno = EEXIST; } i_assert(fd == -1); return -1; } if (fchown(fd, uid, gid) < 0) { i_close_fd(&fd); orig_errno = errno; if (rmdir(path) < 0 && errno != ENOENT) i_error("rmdir(%s) failed: %m", path); errno = orig_errno; if (errno == EPERM && uid == (uid_t)-1) { i_error("%s", eperm_error_get_chgrp("fchown", path, gid, gid_origin)); return -1; } str = t_str_new(256); str_printfa(str, "fchown(%s, %ld", path, uid == (uid_t)-1 ? -1L : (long)uid); if (uid != (uid_t)-1) { struct passwd pw; if (i_getpwuid(uid, &pw) > 0) str_printfa(str, "(%s)", pw.pw_name); } str_printfa(str, ", %ld", gid == (gid_t)-1 ? -1L : (long)gid); if (gid != (gid_t)-1) { struct group gr; if (i_getgrgid(uid, &gr) > 0) str_printfa(str, "(%s)", gr.gr_name); } errno = orig_errno; i_error("%s) failed: %m", str_c(str)); return -1; } if (gid != (gid_t)-1 && (mode & S_ISGID) == 0) { /* make sure the directory doesn't have setgid bit enabled (in case its parent had) */ if (fchmod(fd, mode) < 0) { orig_errno = errno; if (rmdir(path) < 0 && errno != ENOENT) i_error("rmdir(%s) failed: %m", path); errno = orig_errno; i_error("fchmod(%s) failed: %m", path); i_close_fd(&fd); return -1; } } i_close_fd(&fd); return 0; } int mkdir_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { return mkdir_chown_full(path, mode, uid, gid, NULL); } int mkdir_chgrp(const char *path, mode_t mode, gid_t gid, const char *gid_origin) { return mkdir_chown_full(path, mode, (uid_t)-1, gid, gid_origin); } static int ATTR_NULL(5) mkdir_parents_chown_full(const char *path, mode_t mode, uid_t uid, gid_t gid, const char *gid_origin) { const char *p; int ret; if (mkdir_chown_full(path, mode, uid, gid, gid_origin) < 0) { if (errno != ENOENT) return -1; /* doesn't exist, try recursively creating our parent dir */ p = strrchr(path, '/'); if (p == NULL || p == path) return -1; /* shouldn't happen */ T_BEGIN { ret = mkdir_parents_chown_full(t_strdup_until(path, p), mode, uid, gid, gid_origin); } T_END; if (ret < 0 && errno != EEXIST) return -1; /* should work now */ if (mkdir_chown_full(path, mode, uid, gid, gid_origin) < 0) return -1; } return 0; } int mkdir_parents_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { return mkdir_parents_chown_full(path, mode, uid, gid, NULL); } int mkdir_parents_chgrp(const char *path, mode_t mode, gid_t gid, const char *gid_origin) { return mkdir_parents_chown_full(path, mode, (uid_t)-1, gid, gid_origin); } int mkdir_parents(const char *path, mode_t mode) { return mkdir_parents_chown(path, mode, (uid_t)-1, (gid_t)-1); } int stat_first_parent(const char *path, const char **root_dir_r, struct stat *st_r) { const char *p; while (stat(path, st_r) < 0) { if (errno != ENOENT || strcmp(path, "/") == 0) { *root_dir_r = path; return -1; } p = strrchr(path, '/'); if (p == NULL) path = "/"; else path = t_strdup_until(path, p); } *root_dir_r = path; return 0; } dovecot-2.3.21.1/src/lib/hmac-cram-md5.c0000644000000000000000000000313114656633576014327 00000000000000/* * CRAM-MD5 (RFC 2195) compatibility code * Copyright (c) 2003 Joshua Goodall * * This software is released under the MIT license. */ #include "lib.h" #include "md5.h" #include "hmac-cram-md5.h" void hmac_md5_get_cram_context(struct hmac_context *_hmac_ctx, unsigned char context_digest[CRAM_MD5_CONTEXTLEN]) { struct hmac_context_priv *hmac_ctx = &_hmac_ctx->u.priv; unsigned char *cdp; struct md5_context *ctx = (void*)hmac_ctx->ctx; struct md5_context *ctxo = (void*)hmac_ctx->ctxo; #define CDPUT(p, c) STMT_START { \ *(p)++ = (c) & 0xff; \ *(p)++ = (c) >> 8 & 0xff; \ *(p)++ = (c) >> 16 & 0xff; \ *(p)++ = (c) >> 24 & 0xff; \ } STMT_END cdp = context_digest; CDPUT(cdp, ctxo->a); CDPUT(cdp, ctxo->b); CDPUT(cdp, ctxo->c); CDPUT(cdp, ctxo->d); CDPUT(cdp, ctx->a); CDPUT(cdp, ctx->b); CDPUT(cdp, ctx->c); CDPUT(cdp, ctx->d); } void hmac_md5_set_cram_context(struct hmac_context *_hmac_ctx, const unsigned char context_digest[CRAM_MD5_CONTEXTLEN]) { struct hmac_context_priv *hmac_ctx = &_hmac_ctx->u.priv; const unsigned char *cdp; struct md5_context *ctx = (void*)hmac_ctx->ctx; struct md5_context *ctxo = (void*)hmac_ctx->ctxo; #define CDGET(p, c) STMT_START { \ (c) = (*p++); \ (c) += (*p++ << 8); \ (c) += (*p++ << 16); \ (c) += ((uint32_t)(*p++) << 24); \ } STMT_END cdp = context_digest; CDGET(cdp, ctxo->a); CDGET(cdp, ctxo->b); CDGET(cdp, ctxo->c); CDGET(cdp, ctxo->d); CDGET(cdp, ctx->a); CDGET(cdp, ctx->b); CDGET(cdp, ctx->c); CDGET(cdp, ctx->d); ctxo->lo = ctx->lo = 64; ctxo->hi = ctx->hi = 0; } dovecot-2.3.21.1/src/lib/test-buffer.c0000644000000000000000000002625714656633576014260 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "buffer.h" static void test_buffer_random(void) { #define BUF_TEST_SIZE (1024*2) #define BUF_TEST_COUNT 1000 buffer_t *buf; unsigned char *p, testdata[BUF_TEST_SIZE], shadowbuf[BUF_TEST_SIZE]; unsigned int i, shadowbuf_size; size_t pos, pos2, size, size2; int test = -1; bool zero; buf = buffer_create_dynamic(default_pool, 1); for (i = 0; i < BUF_TEST_SIZE; i++) testdata[i] = i_rand_uchar(); memset(shadowbuf, 0, sizeof(shadowbuf)); shadowbuf_size = 0; for (i = 0; i < BUF_TEST_COUNT; i++) { if (buf->used == BUF_TEST_SIZE) { size = shadowbuf_size = i_rand_limit(buf->used - 1); buffer_set_used_size(buf, size); memset(shadowbuf + shadowbuf_size, 0, BUF_TEST_SIZE - shadowbuf_size); i_assert(buf->used < BUF_TEST_SIZE); } test = i_rand_limit(7); zero = i_rand_limit(10) == 0; switch (test) { case 0: pos = i_rand_limit(BUF_TEST_SIZE - 1); size = i_rand_limit(BUF_TEST_SIZE - pos); if (!zero) { buffer_write(buf, pos, testdata, size); memcpy(shadowbuf + pos, testdata, size); } else { buffer_write_zero(buf, pos, size); memset(shadowbuf + pos, 0, size); } if (pos + size > shadowbuf_size) shadowbuf_size = pos + size; break; case 1: size = i_rand_limit(BUF_TEST_SIZE - buf->used); if (!zero) { buffer_append(buf, testdata, size); memcpy(shadowbuf + shadowbuf_size, testdata, size); } else { buffer_append_zero(buf, size); memset(shadowbuf + shadowbuf_size, 0, size); } shadowbuf_size += size; break; case 2: pos = i_rand_limit(BUF_TEST_SIZE - 1); size = i_rand_limit(BUF_TEST_SIZE - I_MAX(buf->used, pos)); if (!zero) { buffer_insert(buf, pos, testdata, size); memmove(shadowbuf + pos + size, shadowbuf + pos, BUF_TEST_SIZE - (pos + size)); memcpy(shadowbuf + pos, testdata, size); } else { buffer_insert_zero(buf, pos, size); memmove(shadowbuf + pos + size, shadowbuf + pos, BUF_TEST_SIZE - (pos + size)); memset(shadowbuf + pos, 0, size); } if (pos < shadowbuf_size) shadowbuf_size += size; else shadowbuf_size = pos + size; break; case 3: pos = i_rand_limit(BUF_TEST_SIZE - 1); size = i_rand_limit(BUF_TEST_SIZE - pos); buffer_delete(buf, pos, size); if (pos < shadowbuf_size) { if (pos + size > shadowbuf_size) size = shadowbuf_size - pos; memmove(shadowbuf + pos, shadowbuf + pos + size, BUF_TEST_SIZE - (pos + size)); shadowbuf_size -= size; memset(shadowbuf + shadowbuf_size, 0, BUF_TEST_SIZE - shadowbuf_size); } break; case 4: pos = i_rand_limit(BUF_TEST_SIZE - 1); size = i_rand_limit(BUF_TEST_SIZE - pos); size2 = i_rand_limit(BUF_TEST_SIZE - I_MAX(buf->used, pos)); buffer_replace(buf, pos, size, testdata, size2); if (pos < shadowbuf_size) { if (pos + size > shadowbuf_size) size = shadowbuf_size - pos; memmove(shadowbuf + pos, shadowbuf + pos + size, BUF_TEST_SIZE - (pos + size)); shadowbuf_size -= size; memset(shadowbuf + shadowbuf_size, 0, BUF_TEST_SIZE - shadowbuf_size); } memmove(shadowbuf + pos + size2, shadowbuf + pos, BUF_TEST_SIZE - (pos + size2)); memcpy(shadowbuf + pos, testdata, size2); if (pos < shadowbuf_size) shadowbuf_size += size2; else shadowbuf_size = pos + size2; break; case 5: if (shadowbuf_size <= 1) break; pos = i_rand_limit(shadowbuf_size - 1); /* dest */ pos2 = i_rand_limit(shadowbuf_size - 1); /* source */ size = i_rand_limit(shadowbuf_size - I_MAX(pos, pos2)); buffer_copy(buf, pos, buf, pos2, size); memmove(shadowbuf + pos, shadowbuf + pos2, size); if (pos > pos2 && pos + size > shadowbuf_size) shadowbuf_size = pos + size; break; case 6: pos = i_rand_limit(BUF_TEST_SIZE - 1); size = i_rand_limit(BUF_TEST_SIZE - pos); p = buffer_get_space_unsafe(buf, pos, size); memcpy(p, testdata, size); memcpy(shadowbuf + pos, testdata, size); if (pos + size > shadowbuf_size) shadowbuf_size = pos + size; break; } i_assert(shadowbuf_size <= BUF_TEST_SIZE); if (buf->used != shadowbuf_size || memcmp(buf->data, shadowbuf, buf->used) != 0) break; } if (i == BUF_TEST_COUNT) test_out("buffer", TRUE); else { test_out_reason("buffer", FALSE, t_strdup_printf("round %u test %d failed", i, test)); } buffer_free(&buf); } static void test_buffer_write(void) { buffer_t *buf; test_begin("buffer_write"); buf = t_buffer_create(8); buffer_write(buf, 5, buf, 0); test_assert(buf->used == 5); test_end(); } static void test_buffer_set_used_size(void) { buffer_t *buf; test_begin("buffer_set_used_size"); buf = t_buffer_create(8); memset(buffer_append_space_unsafe(buf, 7), 'a', 7); buffer_set_used_size(buf, 4); test_assert(memcmp(buffer_get_space_unsafe(buf, 0, 7), "aaaa\0\0\0", 7) == 0); memset(buffer_get_space_unsafe(buf, 4, 7), 'b', 7); buffer_set_used_size(buf, 10); test_assert(memcmp(buffer_append_space_unsafe(buf, 1), "\0", 1) == 0); buffer_set_used_size(buf, 11); test_assert(memcmp(buffer_get_space_unsafe(buf, 0, 11), "aaaabbbbbb\0", 11) == 0); test_end(); } #if 0 /* this code is left here to produce the output found in * buffer.h should it be needed for debugging purposes */ #include "str.h" #include "hex-binary.h" static const char *binary_to_10(const unsigned char *data, size_t size) { string_t *str = t_str_new(size*8); for (size_t i = 0; i < size; i++) { for (int j = 0; j < 8; j++) { if ((data[i] & (1 << (7-j))) != 0) str_append_c(str, '1'); else str_append_c(str, '0'); } } return str_c(str); } static void test_foo(void) { buffer_t *buf = buffer_create_dynamic(default_pool, 100); for (int i = 1; i <= 24; i++) { buffer_set_used_size(buf, 0); buffer_append_c(buf, 0xff); buffer_append_c(buf, 0xff); buffer_append_c(buf, 0xff); buffer_truncate_rshift_bits(buf, i); printf("%2d bits: %24s %s\n", i, binary_to_hex(buf->data, buf->used), binary_to_10(buf->data, buf->used)); } } #endif static void test_buffer_truncate_bits(void) { buffer_t *buf; test_begin("buffer_test_truncate_bits"); struct { buffer_t input; size_t bits; buffer_t output; } test_cases[] = { { { { { "\xff\xff\xff", 3 } } }, 0, { { { "", 0 } } } }, { { { { "\xff\xff\xff", 3 } } }, 1, { { { "\x01", 1 } } } }, { { { { "\xff\xff\xff", 3 } } }, 2, { { { "\x03", 1 } } } }, { { { { "\xff\xff\xff", 3 } } }, 3, { { { "\x07", 1 } } } }, { { { { "\xff\xff\xff", 3 } } }, 4, { { { "\x0f", 1 } } } }, { { { { "\xff\xff\xff", 3 } } }, 5, { { { "\x1f", 1 } } } }, { { { { "\xff\xff\xff", 3 } } }, 6, { { { "\x3f", 1 } } } }, { { { { "\xff\xff\xff", 3 } } }, 7, { { { "\x7f", 1 } } } }, { { { { "\xff\xff\xff", 3 } } }, 8, { { { "\xff", 1 } } } }, { { { { "\xff\xff\xff", 3 } } }, 9, { { { "\x01\xff", 2 } } } }, { { { { "\xff\xff\xff", 3 } } }, 10, { { { "\x03\xff", 2 } } } }, { { { { "\xff\xff\xff", 3 } } }, 11, { { { "\x07\xff", 2 } } } }, { { { { "\xff\xff\xff", 3 } } }, 12, { { { "\x0f\xff", 2 } } } }, { { { { "\xff\xff\xff", 3 } } }, 13, { { { "\x1f\xff", 2 } } } }, { { { { "\xff\xff\xff", 3 } } }, 14, { { { "\x3f\xff", 2 } } } }, { { { { "\xff\xff\xff", 3 } } }, 15, { { { "\x7f\xff", 2 } } } }, { { { { "0123456789", 10 } } }, 16, { { { "01", 2 } } } }, { { { { "0123456789", 10 } } }, 24, { { { "012", 3 } } } }, { { { { "0123456789", 10 } } }, 32, { { { "0123", 4 } } } }, { { { { "0123456789", 10 } } }, 40, { { { "01234", 5 } } } }, { { { { "0123456789", 10 } } }, 48, { { { "012345", 6 } } } }, { { { { "0123456789", 10 } } }, 56, { { { "0123456", 7 } } } }, { { { { "0123456789", 10 } } }, 64, { { { "01234567", 8 } } } }, { { { { "0123456789", 10 } } }, 72, { { { "012345678", 9 } } } }, { { { { "0123456789", 10 } } }, 80, { { { "0123456789", 10 } } } }, { { { { "\x58\x11\xed\x02\x4d\x87\x4a\xe2\x5c\xb2\xfa\x69\xf0\xa9\x46\x2e\x04\xca\x5d\x82", 20 } } }, 13, { { { "\x0b\x02", 2 } } } }, /* special test cases for auth policy */ { { { { "\x34\x40\xc8\xc9\x3a\xb6\xe7\xc4\x3f\xc1\xc3\x4d\xd5\x56\xa3\xea\xfb\x5a\x33\x57\xac\x11\x39\x2c\x71\xcb\xee\xbb\xc8\x66\x2f\x64", 32 } } }, 12, { { { "\x03\x44", 2 } } } }, { { { { "\x49\xe5\x8a\x88\x76\xd3\x25\x68\xc9\x89\x4a\xe0\x64\xe4\x04\xf4\xf9\x13\xec\x88\x97\x47\x30\x7f\x3f\xcd\x8f\x74\x4f\x40\xd1\x25", 32 } } }, 12, { { { "\x04\x9e", 2 } } } }, { { { { "\x08\x3c\xdc\x14\x61\x80\x1c\xe8\x43\x81\x98\xfa\xc0\x64\x04\x7a\xa2\x73\x25\x6e\xe6\x4b\x85\x42\xd0\xe2\x78\xd7\x91\xb4\x89\x3f", 32 } } }, 12, { { { "\x00\x83", 2 } } } }, }; buf = t_buffer_create(10); for(size_t i = 0; i < N_ELEMENTS(test_cases); i++) { buffer_set_used_size(buf, 0); buffer_copy(buf, 0, &test_cases[i].input, 0, SIZE_MAX); buffer_truncate_rshift_bits(buf, test_cases[i].bits); test_assert_idx(buffer_cmp(buf, &test_cases[i].output) == TRUE, i); } test_end(); } static void test_buffer_replace(void) { const char orig_input[] = "123456789"; const char data[] = "abcdefghij"; buffer_t *buf, *buf2; unsigned int init_size, pos, size, data_size; test_begin("buffer_replace()"); for (init_size = 0; init_size <= sizeof(orig_input)-1; init_size++) { for (pos = 0; pos < sizeof(orig_input)+1; pos++) { for (size = 0; size < sizeof(orig_input)+1; size++) { for (data_size = 0; data_size <= sizeof(data)-1; data_size++) T_BEGIN { buf = buffer_create_dynamic(pool_datastack_create(), 4); buf2 = buffer_create_dynamic(pool_datastack_create(), 4); buffer_append(buf, orig_input, init_size); buffer_append(buf2, orig_input, init_size); buffer_replace(buf, pos, size, data, data_size); buffer_delete(buf2, pos, size); buffer_insert(buf2, pos, data, data_size); test_assert(buf->used == buf2->used && memcmp(buf->data, buf2->data, buf->used) == 0); } T_END; } } } test_end(); } void test_buffer(void) { test_buffer_random(); test_buffer_write(); test_buffer_set_used_size(); test_buffer_truncate_bits(); test_buffer_replace(); } static void fatal_buffer_free(buffer_t *buf) { buffer_free(&buf); } enum fatal_test_state fatal_buffer(unsigned int stage) { buffer_t *buf; switch (stage) { case 0: test_begin("fatal buffer_create_dynamic_max()"); buf = buffer_create_dynamic_max(default_pool, 1, 5); buffer_append(buf, "12345", 5); test_expect_fatal_string("Buffer write out of range"); test_fatal_set_callback(fatal_buffer_free, buf); buffer_append_c(buf, 'x'); return FATAL_TEST_FAILURE; case 1: buf = buffer_create_dynamic_max(default_pool, 1, 5); test_expect_fatal_string("Buffer write out of range"); test_fatal_set_callback(fatal_buffer_free, buf); buffer_append(buf, "123456", 6); return FATAL_TEST_FAILURE; default: test_end(); return FATAL_TEST_FINISHED; } } dovecot-2.3.21.1/src/lib/test-json-parser.c0000644000000000000000000003066314656633576015246 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str.h" #include "istream-private.h" #include "json-parser.h" #define TYPE_SKIP 100 #define TYPE_STREAM 101 static const char json_input[] = "{\n" "\t\"key\"\t:\t\t\"string\"," " \"key2\" : 1234, \n" "\"key3\":true," "\"key4\":false," "\"skip1\": \"jsifjaisfjiasji\"," "\"skip2\": { \"x\":{ \"y\":123}, \"z\":[5,[6],{\"k\":0},3]}," "\"key5\":null," "\"key6\": {}," "\"key7\": {" " \"sub1\":\"value\"" "}," "\"key8\": {" " \"sub2\":-12.456,\n" " \"sub3\":12.456e9,\n" " \"sub4\":0.456e-789" "}," "\"key9\": \"foo\\\\\\\"\\b\\f\\n\\r\\t\\u0001\\u10ff\"," "\"key10\": \"foo\\\\\\\"\\b\\f\\n\\r\\t\\u0001\\u10ff\"," "\"key11\": []," "\"key12\": [ \"foo\" , 5.24,[true],{\"aobj\":[]}]," "\"key13\": \"\\ud801\\udc37\"," "\"key14\": \"\xd8\xb3\xd9\x84\xd8\xa7\xd9\x85\"," "\"key15\": \"\\u10000\"" "}\n"; static const struct { enum json_type type; const char *value; } json_output[] = { { JSON_TYPE_OBJECT_KEY, "key" }, { JSON_TYPE_STRING, "string" }, { JSON_TYPE_OBJECT_KEY, "key2" }, { JSON_TYPE_NUMBER, "1234" }, { JSON_TYPE_OBJECT_KEY, "key3" }, { JSON_TYPE_TRUE, "true" }, { JSON_TYPE_OBJECT_KEY, "key4" }, { JSON_TYPE_FALSE, "false" }, { JSON_TYPE_OBJECT_KEY, "skip1" }, { TYPE_SKIP, NULL }, { JSON_TYPE_OBJECT_KEY, "skip2" }, { TYPE_SKIP, NULL }, { JSON_TYPE_OBJECT_KEY, "key5" }, { JSON_TYPE_NULL, NULL }, { JSON_TYPE_OBJECT_KEY, "key6" }, { JSON_TYPE_OBJECT, NULL }, { JSON_TYPE_OBJECT_END, NULL }, { JSON_TYPE_OBJECT_KEY, "key7" }, { JSON_TYPE_OBJECT, NULL }, { JSON_TYPE_OBJECT_KEY, "sub1" }, { JSON_TYPE_STRING, "value" }, { JSON_TYPE_OBJECT_END, NULL }, { JSON_TYPE_OBJECT_KEY, "key8" }, { JSON_TYPE_OBJECT, NULL }, { JSON_TYPE_OBJECT_KEY, "sub2" }, { JSON_TYPE_NUMBER, "-12.456" }, { JSON_TYPE_OBJECT_KEY, "sub3" }, { JSON_TYPE_NUMBER, "12.456e9" }, { JSON_TYPE_OBJECT_KEY, "sub4" }, { JSON_TYPE_NUMBER, "0.456e-789" }, { JSON_TYPE_OBJECT_END, NULL }, { JSON_TYPE_OBJECT_KEY, "key9" }, { JSON_TYPE_STRING, "foo\\\"\b\f\n\r\t\001\xe1\x83\xbf" }, { JSON_TYPE_OBJECT_KEY, "key10" }, { TYPE_STREAM, "foo\\\"\b\f\n\r\t\001\xe1\x83\xbf" }, { JSON_TYPE_OBJECT_KEY, "key11" }, { JSON_TYPE_ARRAY, NULL }, { JSON_TYPE_ARRAY_END, NULL }, { JSON_TYPE_OBJECT_KEY, "key12" }, { JSON_TYPE_ARRAY, NULL }, { JSON_TYPE_STRING, "foo" }, { JSON_TYPE_NUMBER, "5.24" }, { JSON_TYPE_ARRAY, NULL }, { JSON_TYPE_TRUE, "true" }, { JSON_TYPE_ARRAY_END, NULL }, { JSON_TYPE_OBJECT, NULL }, { JSON_TYPE_OBJECT_KEY, "aobj" }, { JSON_TYPE_ARRAY, NULL }, { JSON_TYPE_ARRAY_END, NULL }, { JSON_TYPE_OBJECT_END, NULL }, { JSON_TYPE_ARRAY_END, NULL }, { JSON_TYPE_OBJECT_KEY, "key13" }, { JSON_TYPE_STRING, "\xf0\x90\x90\xb7" }, { JSON_TYPE_OBJECT_KEY, "key14" }, { JSON_TYPE_STRING, "\xd8\xb3\xd9\x84\xd8\xa7\xd9\x85" }, { JSON_TYPE_OBJECT_KEY, "key15" }, { JSON_TYPE_STRING, "\xe1\x80\x80""0" }, }; static int stream_read_value(struct istream **input, const char **value_r) { const unsigned char *data; size_t size; ssize_t ret; while ((ret = i_stream_read(*input)) > 0) ; if (ret == 0) return 0; i_assert(ret == -1); if ((*input)->stream_errno != 0) return -1; data = i_stream_get_data(*input, &size); *value_r = t_strndup(data, size); i_stream_unref(input); return 1; } static void test_json_parser_success(bool full_size) { struct json_parser *parser; struct istream *input, *jsoninput = NULL; enum json_type type; const char *value, *error; unsigned int i, pos, json_input_len = strlen(json_input); int ret = 0; test_begin(full_size ? "json parser" : "json parser (nonblocking)"); input = test_istream_create_data(json_input, json_input_len); test_istream_set_allow_eof(input, FALSE); parser = json_parser_init(input); i = full_size ? json_input_len : 0; for (pos = 0; i <= json_input_len; i++) { test_istream_set_size(input, i); for (;;) { value = NULL; if (pos < N_ELEMENTS(json_output) && json_output[pos].type == (enum json_type)TYPE_SKIP) { json_parse_skip_next(parser); pos++; continue; } else if (pos == N_ELEMENTS(json_output) || json_output[pos].type != (enum json_type)TYPE_STREAM) { ret = json_parse_next(parser, &type, &value); } else { ret = jsoninput != NULL ? 1 : json_parse_next_stream(parser, &jsoninput); if (ret > 0 && jsoninput != NULL) ret = stream_read_value(&jsoninput, &value); type = TYPE_STREAM; } if (ret <= 0) break; i_assert(pos < N_ELEMENTS(json_output)); test_assert_idx(json_output[pos].type == type, pos); test_assert_idx(null_strcmp(json_output[pos].value, value) == 0, pos); pos++; } test_assert_idx(ret == 0, pos); } test_assert(pos == N_ELEMENTS(json_output)); test_istream_set_allow_eof(input, TRUE); test_assert(json_parse_next(parser, &type, &value) == -1); i_stream_unref(&input); test_assert(json_parser_deinit(&parser, &error) == 0); test_end(); } static void test_json_parser_skip_array(void) { static const char *test_input = "[ 1, {\"foo\": 1 }, 2, \"bar\", 3, 1.234, 4, [], 5, [[]], 6, true ]"; struct json_parser *parser; struct istream *input; enum json_type type; const char *value, *error; int i; test_begin("json parser skip array"); input = test_istream_create_data(test_input, strlen(test_input)); parser = json_parser_init_flags(input, JSON_PARSER_NO_ROOT_OBJECT); test_assert(json_parse_next(parser, &type, &value) > 0 && type == JSON_TYPE_ARRAY); for (i = 1; i <= 6; i++) { test_assert(json_parse_next(parser, &type, &value) > 0 && type == JSON_TYPE_NUMBER && atoi(value) == i); json_parse_skip_next(parser); } test_assert(json_parse_next(parser, &type, &value) > 0 && type == JSON_TYPE_ARRAY_END); test_assert(json_parser_deinit(&parser, &error) == 0); i_stream_unref(&input); test_end(); } static void test_json_parser_skip_object_fields(void) { static const char *test_input = "{\"access_token\":\"9a2dea3c-f8be-4271-b9c8-5b37da4f2f7e\"," "\"grant_type\":\"authorization_code\"," "\"openid\":\"\"," "\"scope\":[\"openid\",\"profile\",\"email\"]," "\"profile\":\"\"," "\"realm\":\"/employees\"," "\"token_type\":\"Bearer\"," "\"expires_in\":2377," "\"client_i\\u0064\":\"mosaic\\u0064\"," "\"email\":\"\"," "\"extensions\":" "{\"algorithm\":\"cuttlefish\"," "\"tentacles\":8" "}" "}"; static const char *const keys[] = { "access_token", "grant_type", "openid", "scope", "profile", "realm", "token_type", "expires_in", "client_id", "email", "extensions" }; static const unsigned int keys_count = N_ELEMENTS(keys); struct json_parser *parser; struct istream *input; enum json_type type; const char *value, *error; unsigned int i; size_t pos; int ret; test_begin("json parser skip object fields (by key)"); input = test_istream_create_data(test_input, strlen(test_input)); parser = json_parser_init(input); for (i = 0; i < keys_count; i++) { ret = json_parse_next(parser, &type, &value); if (ret < 0) break; test_assert(ret > 0 && type == JSON_TYPE_OBJECT_KEY); test_assert(strcmp(value, keys[i]) == 0); json_parse_skip_next(parser); } test_assert(i == keys_count); test_assert(json_parser_deinit(&parser, &error) == 0); i_stream_unref(&input); i = 0; input = test_istream_create_data(test_input, strlen(test_input)); parser = json_parser_init(input); for (pos = 0; pos <= strlen(test_input)*2; pos++) { test_istream_set_size(input, pos/2); ret = json_parse_next(parser, &type, &value); if (ret == 0) continue; if (ret < 0) break; i_assert(i < keys_count); test_assert(ret > 0 && type == JSON_TYPE_OBJECT_KEY); test_assert(strcmp(value, keys[i]) == 0); json_parse_skip_next(parser); i++; } test_assert(i == keys_count); test_assert(json_parser_deinit(&parser, &error) == 0); i_stream_unref(&input); test_end(); test_begin("json parser skip object fields (by value type)"); input = test_istream_create_data(test_input, strlen(test_input)); parser = json_parser_init(input); for (i = 0; i < keys_count; i++) { ret = json_parse_next(parser, &type, &value); if (ret < 0) break; test_assert(ret > 0 && type == JSON_TYPE_OBJECT_KEY); test_assert(strcmp(value, keys[i]) == 0); ret = json_parse_next(parser, &type, &value); test_assert(ret > 0 && type != JSON_TYPE_OBJECT_KEY); json_parse_skip(parser); } test_assert(i == keys_count); test_assert(json_parser_deinit(&parser, &error) == 0); i_stream_unref(&input); i = 0; input = test_istream_create_data(test_input, strlen(test_input)); parser = json_parser_init(input); for (pos = 0; pos <= strlen(test_input)*2; pos++) { test_istream_set_size(input, pos/2); ret = json_parse_next(parser, &type, &value); if (ret < 0) break; if (ret == 0) continue; test_assert(ret > 0); if (type == JSON_TYPE_OBJECT_KEY) { i_assert(i < keys_count); test_assert(strcmp(value, keys[i]) == 0); i++; } else { json_parse_skip(parser); } } test_assert(i == keys_count); test_assert(json_parser_deinit(&parser, &error) == 0); i_stream_unref(&input); test_end(); } static int test_json_parse_input(const void *test_input, size_t test_input_size, enum json_parser_flags flags) { struct json_parser *parser; struct istream *input; enum json_type type; const char *value, *error; int ret = 0; input = test_istream_create_data(test_input, test_input_size); parser = json_parser_init_flags(input, flags); while (json_parse_next(parser, &type, &value) > 0) ret++; if (json_parser_deinit(&parser, &error) < 0) ret = -1; i_stream_unref(&input); return ret; } static void test_json_parser_primitive_values(void) { static const struct { const char *str; int ret; } test_inputs[] = { { "\"hello\"", 1 }, { "null", 1 }, { "1234", 1 }, { "1234.1234", 1 }, { "{}", 2 }, { "[]", 2 }, { "true", 1 }, { "false", 1 } }; unsigned int i; test_begin("json_parser (primitives)"); for (i = 0; i < N_ELEMENTS(test_inputs); i++) test_assert_idx(test_json_parse_input(test_inputs[i].str, strlen(test_inputs[i].str), JSON_PARSER_NO_ROOT_OBJECT) == test_inputs[i].ret, i); test_end(); } static void test_json_parser_errors(void) { static const char *test_inputs[] = { "{", "{:}", "{\"foo\":}", "{\"foo\" []}", "{\"foo\": [1}", "{\"foo\": [1,]}", "{\"foo\": [1,]}", "{\"foo\": 1,}", "{\"foo\": 1.}}", "{\"foo\": 1},{}", "{\"foo\": \"\\ud808\"}", "{\"foo\": \"\\udfff\"}", "{\"foo\": \"\\uyyyy\"}", }; unsigned int i; test_begin("json parser error handling"); for (i = 0; i < N_ELEMENTS(test_inputs); i++) test_assert_idx(test_json_parse_input(test_inputs[i], strlen(test_inputs[i]), 0) < 0, i); test_end(); } static void test_json_parser_nuls_in_string(void) { static const unsigned char test_input[] = { '{', '"', 'k', '"', ':', '"', '\0', '"', '}' }; static const unsigned char test_input2[] = { '{', '"', 'k', '"', ':', '"', '\\', '\0', '"', '}' }; static const unsigned char test_input3[] = { '{', '"', 'k', '"', ':', '"', '\\', 'u', '0', '0', '0', '0', '"', '}' }; test_begin("json parser nuls in string"); test_assert(test_json_parse_input(test_input, sizeof(test_input), 0) < 0); test_assert(test_json_parse_input(test_input2, sizeof(test_input2), 0) < 0); test_assert(test_json_parse_input(test_input3, sizeof(test_input3), 0) < 0); test_end(); } static void test_json_append_escaped(void) { string_t *str = t_str_new(32); test_begin("json_append_escaped()"); json_append_escaped(str, "\b\f\r\n\t\"\\\001\002-\xC3\xA4\xf0\x90\x90\xb7\xe2\x80\xa8\xe2\x80\xa9\xff"); test_assert(strcmp(str_c(str), "\\b\\f\\r\\n\\t\\\"\\\\\\u0001\\u0002-\xC3\xA4\xf0\x90\x90\xb7\\u2028\\u2029" UNICODE_REPLACEMENT_CHAR_UTF8) == 0); test_end(); } static void test_json_append_escaped_data(void) { static const unsigned char test_input[] = "\b\f\r\n\t\"\\\000\001\002-\xC3\xA4\xf0\x90\x90\xb7\xe2\x80\xa8\xe2\x80\xa9\xff"; string_t *str = t_str_new(32); test_begin("json_append_escaped_data()"); json_append_escaped_data(str, test_input, sizeof(test_input)-1); test_assert(strcmp(str_c(str), "\\b\\f\\r\\n\\t\\\"\\\\\\u0000\\u0001\\u0002-\xC3\xA4\xf0\x90\x90\xb7\\u2028\\u2029" UNICODE_REPLACEMENT_CHAR_UTF8) == 0); test_end(); } void test_json_parser(void) { test_json_parser_success(TRUE); test_json_parser_success(FALSE); test_json_parser_skip_array(); test_json_parser_skip_object_fields(); test_json_parser_primitive_values(); test_json_parser_errors(); test_json_parser_nuls_in_string(); test_json_append_escaped(); test_json_append_escaped_data(); } dovecot-2.3.21.1/src/lib/test-ioloop.c0000644000000000000000000002273714656633576014307 00000000000000/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "net.h" #include "time-util.h" #include "ioloop.h" #include "istream.h" #include struct test_ctx { bool got_left; bool got_right; bool got_to; }; static void timeout_callback(struct timeval *tv) { i_gettimeofday(tv); io_loop_stop(current_ioloop); } static void test_ioloop_fd_cb_left(struct test_ctx *ctx) { ctx->got_left = TRUE; if (ctx->got_left && ctx->got_right) io_loop_stop(current_ioloop); } static void test_ioloop_fd_cb_right(struct test_ctx *ctx) { ctx->got_right = TRUE; if (ctx->got_left && ctx->got_right) io_loop_stop(current_ioloop); } static void test_ioloop_fd_to(struct test_ctx *ctx) { ctx->got_to = TRUE; io_loop_stop(current_ioloop); } static void test_ioloop_fd(void) { test_begin("ioloop fd"); struct test_ctx test_ctx; int fds[2]; int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fds); test_assert(ret == 0); if (ret < 0) { i_error("socketpair() failed: %m"); test_end(); return; } i_zero(&test_ctx); struct ioloop *ioloop = io_loop_create(); struct io *io_left = io_add(fds[0], IO_READ, test_ioloop_fd_cb_left, &test_ctx); struct io *io_right = io_add(fds[1], IO_READ, test_ioloop_fd_cb_right, &test_ctx); struct timeout *to = timeout_add(2000, test_ioloop_fd_to, &test_ctx); if (write(fds[0], "ltr", 3) != 3 || write(fds[1], "rtl", 3) != 3) i_fatal("write() failed: %m"); io_loop_run(ioloop); timeout_remove(&to); io_remove(&io_left); io_remove(&io_right); test_assert(test_ctx.got_to == FALSE); test_assert(test_ctx.got_left == TRUE); test_assert(test_ctx.got_right == TRUE); io_loop_destroy(&ioloop); i_close_fd(&fds[0]); i_close_fd(&fds[1]); test_end(); } static void test_ioloop_timeout(void) { struct ioloop *ioloop, *ioloop2; struct timeout *to, *to2; struct timeval tv_start, tv_callback; test_begin("ioloop timeout"); ioloop = io_loop_create(); /* add a timeout by moving it from another ioloop */ ioloop2 = io_loop_create(); test_assert(io_loop_is_empty(ioloop)); test_assert(io_loop_is_empty(ioloop2)); to2 = timeout_add(1000, timeout_callback, &tv_callback); test_assert(io_loop_is_empty(ioloop)); test_assert(!io_loop_is_empty(ioloop2)); io_loop_set_current(ioloop); to2 = io_loop_move_timeout(&to2); test_assert(!io_loop_is_empty(ioloop)); test_assert(io_loop_is_empty(ioloop2)); io_loop_set_current(ioloop2); io_loop_destroy(&ioloop2); sleep(1); /* add & remove immediately */ to = timeout_add(1000, timeout_callback, &tv_callback); timeout_remove(&to); /* add the timeout we're actually testing below */ to = timeout_add(1000, timeout_callback, &tv_callback); i_gettimeofday(&tv_start); io_loop_run(ioloop); test_assert(timeval_diff_msecs(&tv_callback, &tv_start) >= 500); timeout_remove(&to); timeout_remove(&to2); test_assert(io_loop_is_empty(ioloop)); io_loop_destroy(&ioloop); test_end(); } static void zero_timeout_callback(unsigned int *counter) { *counter += 1; } static void test_ioloop_zero_timeout(void) { struct ioloop *ioloop; struct timeout *to; struct io *io; unsigned int counter = 0; int fd[2]; test_begin("ioloop zero timeout"); if (pipe(fd) < 0) i_fatal("pipe() failed: %m"); switch (fork()) { case (pid_t)-1: i_fatal("fork() failed: %m"); case 0: sleep(1); char c = 0; if (write(fd[1], &c, 1) < 0) i_fatal("write(pipe) failed: %m"); test_exit(0); default: break; } ioloop = io_loop_create(); to = timeout_add_short(0, zero_timeout_callback, &counter); io = io_add(fd[0], IO_READ, io_loop_stop, ioloop); io_loop_run(ioloop); test_assert_ucmp(counter, >, 1000); timeout_remove(&to); io_remove(&io); io_loop_destroy(&ioloop); test_end(); } struct zero_timeout_recreate_ctx { struct timeout *to; unsigned int counter; }; static void zero_timeout_recreate_callback(struct zero_timeout_recreate_ctx *ctx) { timeout_remove(&ctx->to); ctx->to = timeout_add_short(0, zero_timeout_recreate_callback, ctx); ctx->counter++; } static void test_ioloop_zero_timeout_recreate(void) { struct ioloop *ioloop; struct io *io; struct zero_timeout_recreate_ctx ctx = { .counter = 0 }; int fd[2]; test_begin("ioloop zero timeout recreate"); if (pipe(fd) < 0) i_fatal("pipe() failed: %m"); switch (fork()) { case (pid_t)-1: i_fatal("fork() failed: %m"); case 0: sleep(1); char c = 0; if (write(fd[1], &c, 1) < 0) i_fatal("write(pipe) failed: %m"); test_exit(0); default: break; } ioloop = io_loop_create(); ctx.to = timeout_add_short(0, zero_timeout_recreate_callback, &ctx); io = io_add(fd[0], IO_READ, io_loop_stop, ioloop); io_loop_run(ioloop); test_assert_ucmp(ctx.counter, >, 1000); timeout_remove(&ctx.to); io_remove(&io); io_loop_destroy(&ioloop); test_end(); } static void io_callback(void *context ATTR_UNUSED) { } static void test_ioloop_find_fd_conditions(void) { static struct { enum io_condition condition; int fd[2]; struct io *io; } tests[] = { { IO_ERROR, { -1, -1 }, NULL }, { IO_READ, { -1, -1 }, NULL }, { IO_WRITE, { -1, -1 }, NULL }, { IO_READ | IO_WRITE, { -1, -1 }, NULL }, { IO_READ, { -1, -1 }, NULL } /* read+write as separate ios */ }; struct ioloop *ioloop; struct io *io; unsigned int i; test_begin("ioloop find fd conditions"); ioloop = io_loop_create(); for (i = 0; i < N_ELEMENTS(tests); i++) { if (socketpair(AF_UNIX, SOCK_STREAM, 0, tests[i].fd) < 0) i_fatal("socketpair() failed: %m"); tests[i].io = io_add(tests[i].fd[0], tests[i].condition, io_callback, NULL); } io = io_add(tests[i-1].fd[0], IO_WRITE, io_callback, NULL); tests[i-1].condition |= IO_WRITE; for (i = 0; i < N_ELEMENTS(tests); i++) test_assert_idx(io_loop_find_fd_conditions(ioloop, tests[i].fd[0]) == tests[i].condition, i); io_remove(&io); for (i = 0; i < N_ELEMENTS(tests); i++) { io_remove(&tests[i].io); i_close_fd(&tests[i].fd[0]); i_close_fd(&tests[i].fd[1]); } io_loop_destroy(&ioloop); test_end(); } static void io_callback_pending_io(void *context ATTR_UNUSED) { io_loop_stop(current_ioloop); } static void test_ioloop_pending_io(void) { test_begin("ioloop pending io"); struct istream *is = i_stream_create_from_data("data", 4); struct ioloop *ioloop = io_loop_create(); test_assert(io_loop_is_empty(ioloop)); struct io *io = io_add_istream(is, io_callback_pending_io, NULL); test_assert(!io_loop_is_empty(ioloop)); io_loop_set_current(ioloop); io_set_pending(io); io_loop_run(ioloop); io_remove(&io); i_stream_unref(&is); io_loop_destroy(&ioloop); test_end(); } static void test_ioloop_context_callback(struct ioloop_context *ctx) { test_assert(io_loop_get_current_context(current_ioloop) == ctx); io_loop_stop(current_ioloop); } static void test_ioloop_context(void) { test_begin("ioloop context"); struct ioloop *ioloop = io_loop_create(); struct ioloop_context *ctx = io_loop_context_new(ioloop); test_assert(io_loop_get_current_context(current_ioloop) == NULL); io_loop_context_activate(ctx); test_assert(io_loop_get_current_context(current_ioloop) == ctx); struct timeout *to = timeout_add(0, test_ioloop_context_callback, ctx); io_loop_run(ioloop); test_assert(io_loop_get_current_context(current_ioloop) == NULL); /* test that we don't crash at deinit if we leave the context active */ io_loop_context_activate(ctx); test_assert(io_loop_get_current_context(current_ioloop) == ctx); timeout_remove(&to); io_loop_context_unref(&ctx); io_loop_destroy(&ioloop); test_end(); } static void test_ioloop_context_events_run(struct event *root_event) { struct ioloop *ioloop = io_loop_create(); struct ioloop_context *ctx1, *ctx2; /* create context 1 */ ctx1 = io_loop_context_new(ioloop); io_loop_context_switch(ctx1); struct event *ctx1_event1 = event_create(NULL); event_push_global(ctx1_event1); struct event *ctx1_event2 = event_create(NULL); event_push_global(ctx1_event2); io_loop_context_deactivate(ctx1); test_assert(event_get_global() == root_event); /* create context 2 */ ctx2 = io_loop_context_new(ioloop); io_loop_context_switch(ctx2); struct event *ctx2_event1 = event_create(NULL); event_push_global(ctx2_event1); io_loop_context_deactivate(ctx2); test_assert(event_get_global() == root_event); /* test switching contexts */ io_loop_context_switch(ctx1); test_assert(event_get_global() == ctx1_event2); io_loop_context_switch(ctx2); test_assert(event_get_global() == ctx2_event1); /* test popping away events */ io_loop_context_switch(ctx1); event_pop_global(ctx1_event2); io_loop_context_switch(ctx2); event_pop_global(ctx2_event1); io_loop_context_switch(ctx1); test_assert(event_get_global() == ctx1_event1); io_loop_context_switch(ctx2); test_assert(event_get_global() == root_event); io_loop_context_deactivate(ctx2); io_loop_context_unref(&ctx1); io_loop_context_unref(&ctx2); io_loop_destroy(&ioloop); event_unref(&ctx1_event1); event_unref(&ctx1_event2); event_unref(&ctx2_event1); } static void test_ioloop_context_events(void) { test_begin("ioloop context - no root event"); test_ioloop_context_events_run(NULL); test_end(); test_begin("ioloop context - with root event"); struct event *root_event = event_create(NULL); event_push_global(root_event); test_ioloop_context_events_run(root_event); event_pop_global(root_event); event_unref(&root_event); test_end(); } void test_ioloop(void) { test_ioloop_timeout(); test_ioloop_zero_timeout(); test_ioloop_zero_timeout_recreate(); test_ioloop_find_fd_conditions(); test_ioloop_pending_io(); test_ioloop_fd(); test_ioloop_context(); test_ioloop_context_events(); } dovecot-2.3.21.1/src/lib/iostream-pump.h0000644000000000000000000000464714656633576014640 00000000000000#ifndef IOSTREAM_PUMP_H #define IOSTREAM_PUMP_H /* iostream-pump ============= This construct pumps data from istream to ostream asynchronously. The pump requires you to provide completion callback. The completion callback is called with success parameter to indicate whether it ended with error. The istream and ostream are reffed on creation and unreffed on unref. */ struct istream; struct ostream; struct ioloop; struct iostream_pump; enum iostream_pump_status { /* pump succeeded - EOF received from istream and all output was written successfully to ostream. */ IOSTREAM_PUMP_STATUS_INPUT_EOF, /* pump failed - istream returned an error */ IOSTREAM_PUMP_STATUS_INPUT_ERROR, /* pump failed - ostream returned an error */ IOSTREAM_PUMP_STATUS_OUTPUT_ERROR, }; /* The callback is called once when the pump succeeds or fails due to iostreams. (It's not called if pump is destroyed.) */ typedef void iostream_pump_callback_t(enum iostream_pump_status status, void *context); struct iostream_pump * iostream_pump_create(struct istream *input, struct ostream *output); struct istream *iostream_pump_get_input(struct iostream_pump *pump); struct ostream *iostream_pump_get_output(struct iostream_pump *pump); void iostream_pump_start(struct iostream_pump *pump); void iostream_pump_stop(struct iostream_pump *pump); void iostream_pump_ref(struct iostream_pump *pump); void iostream_pump_unref(struct iostream_pump **_pump); void iostream_pump_destroy(struct iostream_pump **_pump); void iostream_pump_set_completion_callback(struct iostream_pump *pump, iostream_pump_callback_t *callback, void *context); #define iostream_pump_set_completion_callback(pump, callback, context) \ iostream_pump_set_completion_callback(pump, \ (iostream_pump_callback_t *)callback, TRUE ? context : \ CALLBACK_TYPECHECK(callback, \ void (*)(enum iostream_pump_status, typeof(context)))) /* Returns TRUE if the pump is currently only writing to the ostream. The input listener has been removed either because the ostream buffer is full or because the istream already returned EOF. This function can also be called from the completion callback in error conditions. */ bool iostream_pump_is_waiting_output(struct iostream_pump *pump); void iostream_pump_switch_ioloop_to(struct iostream_pump *pump, struct ioloop *ioloop); void iostream_pump_switch_ioloop(struct iostream_pump *pump); #endif dovecot-2.3.21.1/src/lib/iostream-rawlog.h0000644000000000000000000000203514656633576015137 00000000000000#ifndef IOSTREAM_RAWLOG_H #define IOSTREAM_RAWLOG_H enum iostream_rawlog_flags { IOSTREAM_RAWLOG_FLAG_AUTOCLOSE = 0x01, IOSTREAM_RAWLOG_FLAG_BUFFERED = 0x02, IOSTREAM_RAWLOG_FLAG_TIMESTAMP = 0x04 }; /* Create rawlog *.in and *.out files to the given directory. */ int ATTR_NOWARN_UNUSED_RESULT iostream_rawlog_create(const char *dir, struct istream **input, struct ostream **output); /* Create rawlog prefix.in and prefix.out files. */ int ATTR_NOWARN_UNUSED_RESULT iostream_rawlog_create_prefix(const char *prefix, struct istream **input, struct ostream **output); /* Create rawlog path, writing both input and output to the same file. */ int ATTR_NOWARN_UNUSED_RESULT iostream_rawlog_create_path(const char *path, struct istream **input, struct ostream **output); /* Create rawlog that appends to the given rawlog_output. Both input and output are written to the same stream. */ void iostream_rawlog_create_from_stream(struct ostream *rawlog_output, struct istream **input, struct ostream **output); #endif dovecot-2.3.21.1/src/lib/hostpid.h0000644000000000000000000000104214656633576013472 00000000000000#ifndef HOSTPID_H #define HOSTPID_H /* These environments override the hostname/hostdomain if they're set. Master process normally sets these to child processes. */ #define MY_HOSTNAME_ENV "DOVECOT_HOSTNAME" #define MY_HOSTDOMAIN_ENV "DOVECOT_HOSTDOMAIN" extern const char *my_hostname; extern const char *my_pid; /* Initializes my_hostname and my_pid. */ void hostpid_init(void); void hostpid_deinit(void); /* Returns the current host+domain, or if it fails fallback to returning hostname. */ const char *my_hostdomain(void); #endif dovecot-2.3.21.1/src/lib/test-istream.c0000644000000000000000000002541714656633576014450 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "istream.h" #include "istream-crlf.h" static void test_istream_children(void) { struct istream *parent, *child1, *child2; const unsigned char *data; size_t size; test_begin("istream children"); parent = test_istream_create_data("123456789", 9); test_istream_set_max_buffer_size(parent, 3); child1 = i_stream_create_limit(parent, UOFF_T_MAX); child2 = i_stream_create_limit(parent, UOFF_T_MAX); /* child1 read beginning */ test_assert(i_stream_read(child1) == 3); data = i_stream_get_data(child1, &size); test_assert(size == 3 && memcmp(data, "123", 3) == 0); i_stream_skip(child1, 3); /* child1 read middle.. */ test_assert(i_stream_read(child1) == 3); data = i_stream_get_data(child1, &size); test_assert(size == 3 && memcmp(data, "456", 3) == 0); /* child2 read beginning.. */ test_assert(i_stream_read(child2) == 3); data = i_stream_get_data(child2, &size); test_assert(size == 3 && memcmp(data, "123", 3) == 0); /* child1 check middle again.. the parent has been modified, so it can't return the original data (without some code changes). */ test_assert(i_stream_get_data_size(child1) == 0); i_stream_skip(child1, 3); /* child1 read end */ test_assert(i_stream_read(child1) == 3); data = i_stream_get_data(child1, &size); test_assert(size == 3 && memcmp(data, "789", 3) == 0); i_stream_skip(child1, 3); test_assert(i_stream_read(child1) == -1); /* child2 check beginning again.. */ test_assert(i_stream_get_data_size(child1) == 0); i_stream_skip(child2, 3); /* child2 read middle */ test_assert(i_stream_read(child2) == 3); data = i_stream_get_data(child2, &size); test_assert(size == 3 && memcmp(data, "456", 3) == 0); i_stream_skip(child2, 3); i_stream_destroy(&child1); i_stream_destroy(&child2); i_stream_destroy(&parent); test_end(); } static void test_istream_next_line_expect(struct istream *is, const char *expect, unsigned int i) { const char *line = i_stream_next_line(is); test_assert_strcmp_idx(line, expect, i); } static void test_istream_next_line(void) { /* single line cases */ #define TEST_CASE(a, s, b) { \ .input = (const unsigned char*)((a)), .input_len = sizeof((a)), \ .skip = s, \ .output = b } const struct test_case_sl { const unsigned char *input; size_t input_len; size_t skip; const char *output; } test_cases_sl[] = { TEST_CASE("", 0, NULL), TEST_CASE("a\n", 1, ""), TEST_CASE("a\r\n", 0, "a"), TEST_CASE("a\r\n", 1, ""), TEST_CASE("a\r\n", 2, ""), TEST_CASE("hello\nworld\n", 6, "world"), TEST_CASE("hello\nworld", 6, NULL), TEST_CASE("hello\n\n\n\n", 6, ""), TEST_CASE("wrong\n\r\n\n", 0, "wrong"), TEST_CASE("wrong\n\r\r\n", 6, "\r"), TEST_CASE("wrong\n\r\r\n", 7, ""), }; test_begin("i_stream_next_line"); for(unsigned int i = 0; i < N_ELEMENTS(test_cases_sl); i++) { const struct test_case_sl *test_case = &test_cases_sl[i]; struct istream *input = i_stream_create_copy_from_data(test_case->input, test_case->input_len); test_assert_idx(i_stream_read(input) >= 0 || (input->stream_errno == 0 && input->eof), i); i_stream_skip(input, test_case->skip); test_assert_strcmp_idx(i_stream_next_line(input), test_case->output, i); test_assert_idx(input->stream_errno == 0, i); i_stream_unref(&input); input = test_istream_create_data(test_case->input, test_case->input_len); test_assert_idx(i_stream_read(input) >= 0 || (input->stream_errno == 0 && input->eof), i); i_stream_skip(input, test_case->skip); test_assert_strcmp_idx(i_stream_next_line(input), test_case->output, i); test_assert_idx(input->stream_errno == 0, i); i_stream_unref(&input); } #undef TEST_CASE #define TEST_CASE(a) test_istream_create_data((a), sizeof(a)) /* multiline tests */ struct istream *is = TEST_CASE("\n\n\n\n\n\n"); size_t i; test_assert(i_stream_read(is) >= 0 || (is->stream_errno == 0 && is->eof)); for(i = 0; i < 6; i++) test_istream_next_line_expect(is, "", i); test_assert(is->stream_errno == 0); i_stream_unref(&is); is = TEST_CASE( "simple\r\n" "multiline\n" "test with\0" "some exciting\n" "things\r\n\0"); test_assert(i_stream_read(is) >= 0 || (is->stream_errno == 0 && is->eof)); test_istream_next_line_expect(is, "simple", 0); test_istream_next_line_expect(is, "multiline", 1); test_istream_next_line_expect(is, "test with", 2); test_istream_next_line_expect(is, "things", 3); test_istream_next_line_expect(is, NULL, 4); test_assert(is->stream_errno == 0); i_stream_unref(&is); is = TEST_CASE( "NUL\0" "test\n"); test_assert(i_stream_read(is) >= 0 || (is->stream_errno == 0 && is->eof)); test_istream_next_line_expect(is, "NUL", 0); test_istream_next_line_expect(is, NULL, 1); test_assert(is->stream_errno == 0); i_stream_unref(&is); const char test_data_1[] = "this is some data\n" "written like this\n" "to attempt and induce\n" "errors or flaws\n"; is = TEST_CASE(test_data_1); size_t n = 0; const char *const *lines = t_strsplit(test_data_1, "\n"); for(i = 0; i < sizeof(test_data_1); i++) { test_istream_set_size(is, i); test_assert(i_stream_read(is) >= 0); const char *line = i_stream_next_line(is); if (line != NULL) { test_assert_strcmp_idx(lines[n], line, n); n++; } } test_assert(n == 4); test_assert(is->stream_errno == 0); i_stream_unref(&is); const char test_data_2[] = "this is some data\n" "written like this\n" "to attempt and induce\n" "errors or flaws"; is = TEST_CASE(test_data_2); lines = t_strsplit(test_data_2, "\n"); i_stream_set_return_partial_line(is, TRUE); n = 0; /* requires one extra read to get the last line */ for(i = 0; i < sizeof(test_data_1) + 1; i++) { test_istream_set_size(is, I_MIN(sizeof(test_data_1), i)); (void)i_stream_read(is); const char *line = i_stream_next_line(is); if (line != NULL) { test_assert_strcmp_idx(lines[n], line, n); n++; } (void)i_stream_read(is); } test_assert(n == 4); test_assert(is->stream_errno == 0); i_stream_unref(&is); const char test_data_3[] = "this is some data\r\n" "written like this\r\n" "to attempt and induce\r\n" "errors or flaws\r\n"; struct istream *is_1 = TEST_CASE(test_data_3); is = i_stream_create_crlf(is_1); i_stream_unref(&is_1); lines = t_strsplit_spaces(test_data_3, "\r\n"); n = 0; for(i = 0; i < sizeof(test_data_3); i++) { test_istream_set_size(is, i); test_assert(i_stream_read(is) >= 0); const char *line = i_stream_next_line(is); if (line != NULL) { test_assert_strcmp_idx(lines[n], line, n); n++; } } test_assert(n == 4); test_assert(is->stream_errno == 0); i_stream_unref(&is); test_end(); } static void test_istream_read_next_line(void) { /* single line cases */ #undef TEST_CASE #define TEST_CASE(a, s, b) { \ .input = (const unsigned char*)((a)), .input_len = sizeof((a)), \ .skip = s, \ .output = b } const struct test_case_sl { const unsigned char *input; size_t input_len; size_t skip; const char *output; } test_cases_sl[] = { TEST_CASE("", 0, NULL), TEST_CASE("a\n", 1, ""), TEST_CASE("a\r\n", 0, "a"), TEST_CASE("a\r\n", 1, ""), TEST_CASE("a\r\n", 2, ""), TEST_CASE("hello\nworld\n", 6, "world"), TEST_CASE("hello\nworld", 6, NULL), TEST_CASE("hello\n\n\n\n", 6, ""), TEST_CASE("wrong\n\r\n\n", 0, "wrong"), TEST_CASE("wrong\n\r\r\n", 6, "\r"), TEST_CASE("wrong\n\r\r\n", 7, ""), }; test_begin("i_stream_read_next_line"); for(unsigned int i = 0; i < N_ELEMENTS(test_cases_sl); i++) { const struct test_case_sl *test_case = &test_cases_sl[i]; struct istream *input = i_stream_create_copy_from_data(test_case->input, test_case->input_len); i_stream_skip(input, test_case->skip); test_assert_strcmp_idx(i_stream_read_next_line(input), test_case->output, i); test_assert_idx(input->stream_errno == 0, i); i_stream_unref(&input); input = test_istream_create_data(test_case->input, test_case->input_len); i_stream_skip(input, test_case->skip); test_assert_strcmp_idx(i_stream_read_next_line(input), test_case->output, i); test_assert_idx(input->stream_errno == 0, i); i_stream_unref(&input); } const char test_data_1[] = "this is some data\n" "written like this\n" "to attempt and induce\n" "errors or flaws\n"; #undef TEST_CASE #define TEST_CASE(a) test_istream_create_data((a), sizeof(a)) /* multiline tests */ struct istream *is = TEST_CASE("\n\n\n\n\n\n"); size_t i; for(i = 0; i < 6; i++) test_assert_strcmp_idx(i_stream_read_next_line(is), "", i); test_assert(is->stream_errno == 0); i_stream_unref(&is); is = TEST_CASE( "simple\r\n" "multiline\n" "test with\0" "some exciting\n" "things\r\n\0"); test_assert_strcmp_idx(i_stream_read_next_line(is), "simple", 0); test_assert_strcmp_idx(i_stream_read_next_line(is), "multiline", 1); test_assert_strcmp_idx(i_stream_read_next_line(is), "test with", 2); test_assert_strcmp_idx(i_stream_read_next_line(is), "things", 3); test_assert_strcmp_idx(i_stream_read_next_line(is), NULL, 4); test_assert(is->stream_errno == 0); i_stream_unref(&is); is = TEST_CASE( "NUL\0" "test\n"); test_assert_strcmp_idx(i_stream_read_next_line(is), "NUL", 0); test_assert_strcmp_idx(i_stream_read_next_line(is), NULL, 1); test_assert(is->stream_errno == 0); i_stream_unref(&is); is = TEST_CASE(test_data_1); size_t n = 0; const char *const *lines = t_strsplit(test_data_1, "\n"); for(i = 0; i < sizeof(test_data_1); i++) { test_istream_set_size(is, i); const char *line = i_stream_read_next_line(is); if (line != NULL) { test_assert_strcmp_idx(lines[n], line, n); n++; } } test_assert(n == 4); test_assert(is->stream_errno == 0); i_stream_unref(&is); const char test_data_2[] = "this is some data\n" "written like this\n" "to attempt and induce\n" "errors or flaws"; is = TEST_CASE(test_data_2); lines = t_strsplit(test_data_2, "\n"); i_stream_set_return_partial_line(is, TRUE); n = 0; for(i = 0; i < sizeof(test_data_1); i++) { test_istream_set_size(is, i); const char *line = i_stream_read_next_line(is); if (line != NULL) { test_assert_strcmp_idx(lines[n], line, n); n++; } } test_assert(n == 4); test_assert(is->stream_errno == 0); i_stream_unref(&is); const char test_data_3[] = "this is some data\r\n" "written like this\r\n" "to attempt and induce\r\n" "errors or flaws\r\n"; struct istream *is_1 = TEST_CASE(test_data_3); is = i_stream_create_crlf(is_1); i_stream_unref(&is_1); lines = t_strsplit_spaces(test_data_3, "\r\n"); n = 0; for(i = 0; i < sizeof(test_data_3); i++) { test_istream_set_size(is, i); const char *line = i_stream_read_next_line(is); if (line != NULL) { test_assert_strcmp_idx(lines[n], line, n); n++; } } test_assert(n == 4); test_assert(is->stream_errno == 0); i_stream_unref(&is); test_end(); } void test_istream(void) { test_istream_children(); test_istream_next_line(); test_istream_read_next_line(); } dovecot-2.3.21.1/src/lib/sendfile-util.h0000644000000000000000000000124514656633576014571 00000000000000#ifndef SENDFILE_UTIL_H #define SENDFILE_UTIL_H /* Wrapper for various sendfile()-like calls. Read a maximum of count bytes from the given offset in in_fd and write them to out_fd. The offset is updated after the call. Note the call assert-crashes if count is 0. Returns: >0 number of bytes successfully written (maybe less than count) 0 if offset points to the input's EOF or past it -1, errno=EINVAL if it isn't supported for some reason (out_fd isn't a socket or there simply is no sendfile()). -1, errno=EAGAIN if non-blocking write couldn't send anything */ ssize_t safe_sendfile(int out_fd, int in_fd, uoff_t *offset, size_t count); #endif dovecot-2.3.21.1/src/lib/event-filter-lexer.l0000644000000000000000000000705314656633576015555 00000000000000/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ %option nounput %option noinput %option noyywrap %option noyyalloc noyyrealloc noyyfree %option reentrant %option bison-bridge %option never-interactive %option prefix="event_filter_parser_" %{ #include "lib.h" #include "str.h" #include "event-filter-private.h" #include "event-filter-parser.h" #define YY_FATAL_ERROR(msg) { i_fatal("event filter parsing: %s", (msg)); } /* mimic renaming done by bison's api.prefix %define */ #define YYSTYPE EVENT_FILTER_PARSER_STYPE #define YY_INPUT(buf, result, max_size) \ result = event_filter_parser_input_proc(buf, max_size, yyscanner) static size_t event_filter_parser_input_proc(char *buf, size_t size, yyscan_t scanner); #pragma GCC diagnostic push /* ignore strict bool warnings in generated code */ #ifdef HAVE_STRICT_BOOL # pragma GCC diagnostic ignored "-Wstrict-bool" #endif /* ignore sign comparison errors (buggy flex) */ #pragma GCC diagnostic ignored "-Wsign-compare" /* ignore unused functions */ #pragma GCC diagnostic ignored "-Wunused-function" /* ignore unused parameters */ #pragma GCC diagnostic ignored "-Wunused-parameter" %} %x string %% string_t *str_buf = NULL; \" { BEGIN(string); str_buf = t_str_new(128); } \" { yylval->str = str_c(str_buf); BEGIN(INITIAL); return STRING; } /* Note: these have to match the event_filter_append_escaped() behavior */ [^\\"]+ { str_append(str_buf, yytext); } \\\\ { str_append_c(str_buf, '\\'); } \\\" { str_append_c(str_buf, '"'); } \\. { str_append(str_buf, yytext); } [Aa][Nn][Dd] { return AND; } [Oo][Rr] { return OR; } [Nn][Oo][Tt] { return NOT; } [<>=()] { return *yytext; } [A-Za-z0-9:.*?_-]+ { yylval->str = t_strdup(yytext); return TOKEN; } [ \t\n\r] { /* ignore */ } . { /* * We simply return the char to the * and let the grammar error out * with a syntax error. * * Note: The cast is significant * since utf-8 bytes >=128 will * otherwise result in sign * extension and a negative int * getting returned on some * platforms (e.g., x86) which in * turn confuses the parser. E.g., * if: * *yytext = '\x80' * we get: * *yytext -> -128 * (int) *yytext -> -128 * which is wrong. With the * unsigned char cast, we get: * (u.c.) *yytext -> 128 * (int)(u.c.) *yytext -> 128 * which is correct. */ return (unsigned char) *yytext; } %% #pragma GCC diagnostic pop void *yyalloc(size_t bytes, void* yyscanner ATTR_UNUSED) { void *ptr = calloc(1, bytes); if (ptr == NULL) i_fatal_status(FATAL_OUTOFMEM, "calloc(1, %zu): Out of memory", bytes); return ptr; } void *yyrealloc (void *ptr, size_t bytes, void *yyscanner ATTR_UNUSED) { void *nptr = realloc(ptr, bytes); if (nptr == NULL) i_fatal_status(FATAL_OUTOFMEM, "realloc(ptr, %zu): Out of memory", bytes); return nptr; } void yyfree(void *ptr, void *yyscanner ATTR_UNUSED) { if (ptr == NULL) return; free(ptr); } static size_t event_filter_parser_input_proc(char *buf, size_t size, yyscan_t scanner) { struct event_filter_parser_state *state; size_t num_bytes; state = event_filter_parser_get_extra(scanner); if (state->len == state->pos) return 0; i_assert(state->len > state->pos); num_bytes = I_MIN(state->len - state->pos, size); memcpy(buf, state->input + state->pos, num_bytes); state->pos += num_bytes; return num_bytes; } dovecot-2.3.21.1/src/lib/stats-dist.c0000644000000000000000000001052714656633576014122 00000000000000/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "stats-dist.h" #include "sort.h" /* In order to have a vaguely accurate 95th percentile, you need way more than 20 in your subsample. */ #define TIMING_DEFAULT_SUBSAMPLING_BUFFER (20*24) /* 20*24 fits in a page */ struct stats_dist { unsigned int sample_count; unsigned int count; bool sorted; uint64_t min; uint64_t max; uint64_t sum; uint64_t samples[]; }; struct stats_dist *stats_dist_init(void) { return stats_dist_init_with_size(TIMING_DEFAULT_SUBSAMPLING_BUFFER); } struct stats_dist *stats_dist_init_with_size(unsigned int sample_count) { i_assert(sample_count > 0); struct stats_dist *stats = i_malloc(sizeof(struct stats_dist) + sizeof(uint64_t) * sample_count); stats->sample_count = sample_count; return stats; } void stats_dist_deinit(struct stats_dist **_stats) { i_free_and_null(*_stats); } void stats_dist_reset(struct stats_dist *stats) { unsigned int sample_count = stats->sample_count; i_zero(stats); stats->sample_count = sample_count; } void stats_dist_add(struct stats_dist *stats, uint64_t value) { if (stats->count < stats->sample_count) { stats->samples[stats->count] = value; if (stats->count == 0) stats->min = stats->max = value; } else { unsigned int idx = i_rand_limit(stats->count); if (idx < stats->sample_count) stats->samples[idx] = value; } stats->count++; stats->sum += value; if (stats->max < value) stats->max = value; if (stats->min > value) stats->min = value; stats->sorted = FALSE; } unsigned int stats_dist_get_count(const struct stats_dist *stats) { return stats->count; } uint64_t stats_dist_get_sum(const struct stats_dist *stats) { return stats->sum; } uint64_t stats_dist_get_min(const struct stats_dist *stats) { return stats->min; } uint64_t stats_dist_get_max(const struct stats_dist *stats) { return stats->max; } double stats_dist_get_avg(const struct stats_dist *stats) { if (stats->count == 0) return 0; return (double)stats->sum / stats->count; } static void stats_dist_ensure_sorted(struct stats_dist *stats) { if (stats->sorted) return; unsigned int count = (stats->count < stats->sample_count) ? stats->count : stats->sample_count; i_qsort(stats->samples, count, sizeof(*stats->samples), uint64_cmp); stats->sorted = TRUE; } uint64_t stats_dist_get_median(struct stats_dist *stats) { if (stats->count == 0) return 0; /* cast-away const - reading requires sorting */ stats_dist_ensure_sorted(stats); unsigned int count = (stats->count < stats->sample_count) ? stats->count : stats->sample_count; unsigned int idx1 = (count-1)/2, idx2 = count/2; return (stats->samples[idx1] + stats->samples[idx2]) / 2; } double stats_dist_get_variance(const struct stats_dist *stats) { double sum = 0; if (stats->count == 0) return 0; double avg = stats_dist_get_avg(stats); double count = (stats->count < stats->sample_count) ? stats->count : stats->sample_count; for(unsigned int i = 0; i < count; i++) { sum += (stats->samples[i] - avg)*(stats->samples[i] - avg); } return sum / count; } /* This is independent of the stats framework, useful for any selection task */ static unsigned int stats_dist_get_index(unsigned int range, double fraction) { /* With out of range fractions, we can give the caller what they probably want rather than just crashing. */ if (fraction >= 1.) return range - 1; if (fraction <= 0.) return 0; double idx_float = range * fraction; unsigned int idx = idx_float; /* C defaults to rounding down */ idx_float -= idx; /* Exact boundaries belong to the open range below them. As FP isn't exact, and ratios may be specified inexactly, include a small amount of fuzz around the exact boundary. */ if (idx_float < 1e-8*range) idx--; return idx; } uint64_t stats_dist_get_percentile(struct stats_dist *stats, double fraction) { if (stats->count == 0) return 0; stats_dist_ensure_sorted(stats); unsigned int count = (stats->count < stats->sample_count) ? stats->count : stats->sample_count; unsigned int idx = stats_dist_get_index(count, fraction); return stats->samples[idx]; } const uint64_t *stats_dist_get_samples(const struct stats_dist *stats, unsigned int *count_r) { *count_r = (stats->count < stats->sample_count) ? stats->count : stats->sample_count; return stats->samples; } dovecot-2.3.21.1/src/lib/write-full.h0000644000000000000000000000060014656633576014111 00000000000000#ifndef WRITE_FULL_H #define WRITE_FULL_H /* Write data into file. Returns -1 if error occurred, or 0 if all was ok. If there's not enough space in device, -1 with ENOSPC is returned, and it's unspecified how much data was actually written. */ int write_full(int fd, const void *data, size_t size); int pwrite_full(int fd, const void *data, size_t size, off_t offset); #endif dovecot-2.3.21.1/src/lib/ostream-file-private.h0000644000000000000000000000225014656633576016061 00000000000000#ifndef OSTREAM_FILE_PRIVATE_H #define OSTREAM_FILE_PRIVATE_H #include "ostream-private.h" struct file_ostream { struct ostream_private ostream; ssize_t (*writev)(struct file_ostream *fstream, const struct const_iovec *iov, unsigned int iov_count); int fd; struct io *io; uoff_t buffer_offset; uoff_t real_offset; unsigned char *buffer; /* ring-buffer */ size_t buffer_size, optimal_block_size; size_t head, tail; /* first unsent/unused byte */ bool full:1; /* if head == tail, is buffer empty or full? */ bool file:1; bool flush_pending:1; bool socket_cork_set:1; bool no_socket_cork:1; bool no_socket_nodelay:1; bool no_socket_quickack:1; bool no_sendfile:1; bool autoclose_fd:1; }; struct ostream * o_stream_create_file_common(struct file_ostream *fstream, int fd, size_t max_buffer_size, bool autoclose_fd); ssize_t o_stream_file_writev(struct file_ostream *fstream, const struct const_iovec *iov, unsigned int iov_size); ssize_t o_stream_file_sendv(struct ostream_private *stream, const struct const_iovec *iov, unsigned int iov_count); void o_stream_file_close(struct iostream_private *stream, bool close_parent); #endif dovecot-2.3.21.1/src/lib/istream-unix.c0000644000000000000000000000523714656633576014452 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "fdpass.h" #include "istream-file-private.h" #include "istream-unix.h" struct unix_istream { struct file_istream fstream; bool next_read_fd; int read_fd; }; static void i_stream_unix_close(struct iostream_private *stream, bool close_parent) { struct unix_istream *ustream = container_of(stream, struct unix_istream, fstream.istream.iostream); i_close_fd(&ustream->read_fd); i_stream_file_close(stream, close_parent); } static ssize_t i_stream_unix_read(struct istream_private *stream) { struct unix_istream *ustream = container_of(stream, struct unix_istream, fstream.istream); size_t size; ssize_t ret; if (!ustream->next_read_fd) return i_stream_file_read(stream); i_assert(ustream->read_fd == -1); i_assert(ustream->fstream.skip_left == 0); /* not supported here.. */ if (!i_stream_try_alloc(stream, 1, &size)) return -2; ret = fd_read(stream->fd, stream->w_buffer + stream->pos, size, &ustream->read_fd); if (ustream->read_fd != -1) ustream->next_read_fd = FALSE; if (ret == 0) { /* EOF */ stream->istream.eof = TRUE; ustream->fstream.seen_eof = TRUE; return -1; } if (unlikely(ret < 0)) { if ((errno == EINTR || errno == EAGAIN) && !stream->istream.blocking) { return 0; } else { i_assert(errno != 0); /* if we get EBADF for a valid fd, it means something's really wrong and we'd better just crash. */ i_assert(errno != EBADF); stream->istream.stream_errno = errno; return -1; } } stream->pos += ret; return ret; } struct istream *i_stream_create_unix(int fd, size_t max_buffer_size) { struct unix_istream *ustream; struct istream *input; i_assert(fd != -1); ustream = i_new(struct unix_istream, 1); ustream->read_fd = -1; input = i_stream_create_file_common(&ustream->fstream, fd, NULL, max_buffer_size, FALSE); input->real_stream->iostream.close = i_stream_unix_close; input->real_stream->read = i_stream_unix_read; return input; } void i_stream_unix_set_read_fd(struct istream *input) { struct unix_istream *ustream = container_of(input->real_stream, struct unix_istream, fstream.istream); ustream->next_read_fd = TRUE; } void i_stream_unix_unset_read_fd(struct istream *input) { struct unix_istream *ustream = container_of(input->real_stream, struct unix_istream, fstream.istream); ustream->next_read_fd = FALSE; } int i_stream_unix_get_read_fd(struct istream *input) { struct unix_istream *ustream = container_of(input->real_stream, struct unix_istream, fstream.istream); int fd; fd = ustream->read_fd; ustream->read_fd = -1; return fd; } dovecot-2.3.21.1/src/lib/hash-format.c0000644000000000000000000001317514656633576014236 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "base64.h" #include "hex-binary.h" #include "str.h" #include "hash-method.h" #include "hash-format.h" enum hash_encoding { HASH_ENCODING_HEX, HASH_ENCODING_HEX_SHORT, HASH_ENCODING_BASE64 }; struct hash_format_list { struct hash_format_list *next; const struct hash_method *method; void *context; unsigned int bits; enum hash_encoding encoding; }; struct hash_format { pool_t pool; const char *str; struct hash_format_list *list, **pos; unsigned char *digest; }; static int hash_format_parse(const char *str, unsigned int *idxp, const struct hash_method **method_r, unsigned int *bits_r, const char **error_r) { const char *name, *end, *bitsp; unsigned int bits, i = *idxp; /* we should have "hash_name}" or "hash_name:bits}" */ end = strchr(str+i, '}'); if (end == NULL) { *error_r = "Missing '}'"; return -1; } *idxp = end - str; name = t_strdup_until(str+i, end); bitsp = strchr(name, ':'); if (bitsp != NULL) name = t_strdup_until(name, bitsp++); *method_r = hash_method_lookup(name); if (*method_r == NULL) { *error_r = t_strconcat("Unknown hash method: ", name, NULL); return -1; } bits = (*method_r)->digest_size * 8; if (bitsp != NULL) { if (str_to_uint(bitsp, &bits) < 0 || bits == 0 || bits > (*method_r)->digest_size*8) { *error_r = t_strconcat("Invalid :bits number: ", bitsp, NULL); return -1; } if ((bits % 8) != 0) { *error_r = t_strconcat( "Currently :bits must be divisible by 8: ", bitsp, NULL); return -1; } } *bits_r = bits; return 0; } static int hash_format_string_analyze(struct hash_format *format, const char *str, const char **error_r) { struct hash_format_list *list; unsigned int i; for (i = 0; str[i] != '\0'; i++) { if (str[i] != '%') continue; i++; list = p_new(format->pool, struct hash_format_list, 1); list->encoding = HASH_ENCODING_HEX; *format->pos = list; format->pos = &list->next; if (str[i] == 'B') { list->encoding = HASH_ENCODING_BASE64; i++; } else if (str[i] == 'X') { list->encoding = HASH_ENCODING_HEX_SHORT; i++; } if (str[i++] != '{') { *error_r = "No '{' after '%'"; return -1; } if (hash_format_parse(str, &i, &list->method, &list->bits, error_r) < 0) return -1; list->context = p_malloc(format->pool, list->method->context_size); list->method->init(list->context); } return 0; } int hash_format_init(const char *format_string, struct hash_format **format_r, const char **error_r) { struct hash_format *format; pool_t pool; int ret; pool = pool_alloconly_create("hash format", 1024); format = p_new(pool, struct hash_format, 1); format->pool = pool; format->str = p_strdup(pool, format_string); format->pos = &format->list; T_BEGIN { ret = hash_format_string_analyze(format, format_string, error_r); if (ret < 0) *error_r = p_strdup(format->pool, *error_r); } T_END; if (ret < 0) { *error_r = t_strdup(*error_r); pool_unref(&pool); return -1; } *format_r = format; return 0; } void hash_format_loop(struct hash_format *format, const void *data, size_t size) { struct hash_format_list *list; for (list = format->list; list != NULL; list = list->next) list->method->loop(list->context, data, size); } void hash_format_reset(struct hash_format *format) { struct hash_format_list *list; for (list = format->list; list != NULL; list = list->next) { memset(list->context, 0, list->method->context_size); list->method->init(list->context); } } static void hash_format_digest(string_t *dest, const struct hash_format_list *list, const unsigned char *digest) { unsigned int i, orig_len, size = list->bits / 8; i_assert(list->bits % 8 == 0); switch (list->encoding) { case HASH_ENCODING_HEX: binary_to_hex_append(dest, digest, size); break; case HASH_ENCODING_HEX_SHORT: orig_len = str_len(dest); binary_to_hex_append(dest, digest, size); /* drop leading zeros, except if it's the only one */ for (i = orig_len; i < str_len(dest); i++) { if (str_data(dest)[i] != '0') break; } if (i == str_len(dest)) i--; str_delete(dest, orig_len, i-orig_len); break; case HASH_ENCODING_BASE64: orig_len = str_len(dest); base64_encode(digest, size, dest); /* drop trailing '=' chars */ while (str_len(dest) > orig_len && str_data(dest)[str_len(dest)-1] == '=') str_truncate(dest, str_len(dest)-1); break; } } void hash_format_write(struct hash_format *format, string_t *dest) { struct hash_format_list *list; const char *p; unsigned int i, max_digest_size = 0; for (list = format->list; list != NULL; list = list->next) { if (max_digest_size < list->method->digest_size) max_digest_size = list->method->digest_size; } if (format->digest == NULL) format->digest = p_malloc(format->pool, max_digest_size); list = format->list; for (i = 0; format->str[i] != '\0'; i++) { if (format->str[i] != '%') { str_append_c(dest, format->str[i]); continue; } /* we already verified that the string is ok */ i_assert(list != NULL); list->method->result(list->context, format->digest); hash_format_digest(dest, list, format->digest); list = list->next; p = strchr(format->str+i, '}'); i_assert(p != NULL); i = p - format->str; } } void hash_format_deinit(struct hash_format **_format, string_t *dest) { struct hash_format *format = *_format; *_format = NULL; hash_format_write(format, dest); pool_unref(&format->pool); } void hash_format_deinit_free(struct hash_format **_format) { struct hash_format *format = *_format; *_format = NULL; pool_unref(&format->pool); } dovecot-2.3.21.1/src/lib/cpu-limit.c0000644000000000000000000001225414656633576013725 00000000000000/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "lib-signals.h" #include "time-util.h" #include "cpu-limit.h" #include #include struct cpu_limit { struct cpu_limit *parent; enum cpu_limit_type type; unsigned int cpu_limit_secs; struct rusage initial_usage; bool limit_reached; }; static struct cpu_limit *cpu_limit; static struct rlimit orig_limit, last_set_rlimit; static volatile sig_atomic_t xcpu_signal_counter; static sig_atomic_t checked_signal_counter; static unsigned int rlim_cur_adjust_secs; static void cpu_limit_handler(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED) { xcpu_signal_counter++; } static unsigned int cpu_limit_get_usage_msecs_with(struct cpu_limit *climit, enum cpu_limit_type type, const struct rusage *rusage) { struct timeval cpu_usage = { 0, 0 }; int usage_diff; if ((type & CPU_LIMIT_TYPE_USER) != 0) timeval_add(&cpu_usage, &rusage->ru_utime); if ((type & CPU_LIMIT_TYPE_SYSTEM) != 0) timeval_add(&cpu_usage, &rusage->ru_stime); struct timeval initial_total = { 0, 0 }; if ((type & CPU_LIMIT_TYPE_USER) != 0) timeval_add(&initial_total, &climit->initial_usage.ru_utime); if ((type & CPU_LIMIT_TYPE_SYSTEM) != 0) timeval_add(&initial_total, &climit->initial_usage.ru_stime); usage_diff = timeval_diff_msecs(&cpu_usage, &initial_total); i_assert(usage_diff >= 0); return (unsigned int)usage_diff; } unsigned int cpu_limit_get_usage_msecs(struct cpu_limit *climit, enum cpu_limit_type type) { struct rusage rusage; /* Query cpu usage so far */ if (getrusage(RUSAGE_SELF, &rusage) < 0) i_fatal("getrusage() failed: %m"); return cpu_limit_get_usage_msecs_with(climit, type, &rusage); } static bool cpu_limit_update_recursive(struct cpu_limit *climit, const struct rusage *rusage, unsigned int *max_wait_secs) { if (climit == NULL) return FALSE; if (cpu_limit_update_recursive(climit->parent, rusage, max_wait_secs)) { /* parent's limit reached */ climit->limit_reached = TRUE; return TRUE; } unsigned int secs_used = cpu_limit_get_usage_msecs_with(climit, climit->type, rusage)/1000; if (secs_used >= climit->cpu_limit_secs) { climit->limit_reached = TRUE; return TRUE; } unsigned int secs_left = climit->cpu_limit_secs - secs_used; if (*max_wait_secs > secs_left) *max_wait_secs = secs_left; return FALSE; } static void cpu_limit_update_rlimit(void) { struct rusage rusage; struct rlimit rlimit; unsigned int max_wait_secs = UINT_MAX; if (getrusage(RUSAGE_SELF, &rusage) < 0) i_fatal("getrusage() failed: %m"); (void)cpu_limit_update_recursive(cpu_limit, &rusage, &max_wait_secs); if (max_wait_secs == UINT_MAX) { /* All the limits have reached now. Restore the original limits. */ rlimit = orig_limit; } else { struct timeval tv_limit = rusage.ru_utime; timeval_add(&tv_limit, &rusage.ru_stime); i_zero(&rlimit); /* Add +1 second to round up. */ rlimit.rlim_cur = tv_limit.tv_sec + max_wait_secs + 1 + rlim_cur_adjust_secs; rlimit.rlim_max = orig_limit.rlim_max; } if (last_set_rlimit.rlim_cur != rlimit.rlim_cur) { last_set_rlimit = rlimit; if (setrlimit(RLIMIT_CPU, &rlimit) < 0) i_fatal("setrlimit() failed: %m"); } } struct cpu_limit * cpu_limit_init(unsigned int cpu_limit_secs, enum cpu_limit_type type) { struct cpu_limit *climit; struct rusage rusage; i_assert(cpu_limit_secs > 0); i_assert(type != 0); climit = i_new(struct cpu_limit, 1); climit->parent = cpu_limit; climit->type = type; climit->cpu_limit_secs = cpu_limit_secs; /* Query current limit */ if (climit->parent == NULL) { if (getrlimit(RLIMIT_CPU, &orig_limit) < 0) i_fatal("getrlimit() failed: %m"); } /* Query cpu usage so far */ if (getrusage(RUSAGE_SELF, &rusage) < 0) i_fatal("getrusage() failed: %m"); climit->initial_usage = rusage; if (climit->parent == NULL) { lib_signals_set_handler(SIGXCPU, LIBSIG_FLAG_RESTART, cpu_limit_handler, NULL); } cpu_limit = climit; cpu_limit_update_rlimit(); return climit; } void cpu_limit_deinit(struct cpu_limit **_climit) { struct cpu_limit *climit = *_climit; *_climit = NULL; if (climit == NULL) return; i_assert(climit == cpu_limit); cpu_limit = climit->parent; cpu_limit_update_rlimit(); if (climit->parent == NULL) lib_signals_unset_handler(SIGXCPU, cpu_limit_handler, NULL); i_free(climit); } bool cpu_limit_exceeded(struct cpu_limit *climit) { static struct timeval tv_last = { 0, 0 }; struct timeval tv_now; if (checked_signal_counter != xcpu_signal_counter) { i_gettimeofday(&tv_now); if (tv_last.tv_sec != 0 && timeval_diff_msecs(&tv_now, &tv_last) < 1000) { /* Additional sanity check: We're getting here more rapidly than once per second. This isn't expected to happen, but at least in theory it could happen because rlim_cur isn't clearly calculated from just the user+system CPU usage. So in case rlim_cur is too low and keeps firing XCPU signal, try to increase rlim_cur by 1 second. Eventually it should become large enough. */ rlim_cur_adjust_secs++; } checked_signal_counter = xcpu_signal_counter; cpu_limit_update_rlimit(); } return climit->limit_reached; } dovecot-2.3.21.1/src/lib/test-ostream-file.c0000644000000000000000000001235114656633576015364 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "net.h" #include "str.h" #include "safe-mkstemp.h" #include "randgen.h" #include "istream.h" #include "ostream.h" #include #include #define MAX_BUFSIZE 256 static void test_ostream_file_random_once(void) { struct ostream *output; string_t *path = t_str_new(128); char buf[MAX_BUFSIZE*4], buf2[MAX_BUFSIZE*4], randbuf[MAX_BUFSIZE]; unsigned int i, offset, size; ssize_t ret; int fd; memset(buf, 0, sizeof(buf)); fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1); if (fd == -1) i_fatal("safe_mkstemp(%s) failed: %m", str_c(path)); i_unlink(str_c(path)); output = o_stream_create_fd(fd, MAX_BUFSIZE); o_stream_cork(output); size = i_rand_minmax(1, MAX_BUFSIZE); random_fill(randbuf, size); memcpy(buf, randbuf, size); test_assert(o_stream_send(output, buf, size) > 0); for (i = 0; i < 10; i++) { offset = i_rand_limit(MAX_BUFSIZE * 3); size = i_rand_minmax(1, MAX_BUFSIZE); random_fill(randbuf, size); memcpy(buf + offset, randbuf, size); test_assert(o_stream_pwrite(output, randbuf, size, offset) == 0); if (i_rand_limit(10) == 0) test_assert(o_stream_flush(output) > 0); } o_stream_uncork(output); test_assert(o_stream_finish(output) > 0); ret = pread(fd, buf2, sizeof(buf2), 0); if (ret < 0) i_fatal("pread() failed: %m"); else { i_assert(ret > 0); test_assert(memcmp(buf, buf2, ret) == 0); } o_stream_unref(&output); i_close_fd(&fd); } static void test_ostream_file_random(void) { unsigned int i; test_begin("ostream pwrite random"); for (i = 0; i < 100; i++) T_BEGIN { test_ostream_file_random_once(); } T_END; test_end(); } static void test_ostream_file_send_istream_file(void) { struct istream *input, *input2; struct ostream *output; char buf[10]; int fd; test_begin("ostream file send istream file"); /* temp file istream */ fd = open(".temp.istream", O_RDWR | O_CREAT | O_TRUNC, 0600); if (fd == -1) i_fatal("creat(.temp.istream) failed: %m"); test_assert(write(fd, "1234567890", 10) == 10); test_assert(lseek(fd, 0, SEEK_SET) == 0); input = i_stream_create_fd_autoclose(&fd, 1024); /* temp file ostream */ fd = open(".temp.ostream", O_RDWR | O_CREAT | O_TRUNC, 0600); if (fd == -1) i_fatal("creat(.temp.ostream) failed: %m"); output = o_stream_create_fd(fd, 0); /* test that writing works between two files */ i_stream_seek(input, 3); input2 = i_stream_create_limit(input, 4); test_assert(o_stream_send_istream(output, input2) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED); test_assert(output->offset == 4); test_assert(pread(fd, buf, sizeof(buf), 0) == 4 && memcmp(buf, "4567", 4) == 0); i_stream_unref(&input2); /* test that writing works within the same file */ i_stream_destroy(&input); input = i_stream_create_fd(fd, 1024); /* forwards: 4567 -> 4677 */ o_stream_seek(output, 1); i_stream_seek(input, 2); input2 = i_stream_create_limit(input, 2); test_assert(o_stream_send_istream(output, input2) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED); test_assert(output->offset == 3); test_assert(pread(fd, buf, sizeof(buf), 0) == 4 && memcmp(buf, "4677", 4) == 0); i_stream_destroy(&input2); i_stream_destroy(&input); /* backwards: 1234 -> 11234 */ memcpy(buf, "1234", 4); test_assert(pwrite(fd, buf, 4, 0) == 4); input = i_stream_create_fd(fd, 1024); o_stream_seek(output, 1); test_assert(o_stream_send_istream(output, input) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED); test_assert(output->offset == 5); test_assert(pread(fd, buf, sizeof(buf), 0) == 5 && memcmp(buf, "11234", 5) == 0); i_stream_destroy(&input); o_stream_destroy(&output); i_close_fd(&fd); i_unlink(".temp.istream"); i_unlink(".temp.ostream"); test_end(); } static void test_ostream_file_send_istream_sendfile(void) { struct istream *input, *input2; struct ostream *output; char buf[10]; int fd, sock_fd[2]; test_begin("ostream file send istream sendfile()"); /* temp file istream */ fd = open(".temp.istream", O_RDWR | O_CREAT | O_TRUNC, 0600); if (fd == -1) i_fatal("creat(.temp.istream) failed: %m"); test_assert(write(fd, "abcdefghij", 10) == 10); test_assert(lseek(fd, 0, SEEK_SET) == 0); input = i_stream_create_fd_autoclose(&fd, 1024); /* temp socket ostream */ i_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fd) == 0); output = o_stream_create_fd_autoclose(sock_fd, 0); /* test that sendfile() works */ i_stream_seek(input, 3); input2 = i_stream_create_limit(input, 4); test_assert(o_stream_send_istream(output, input2) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED); test_assert(output->offset == 4); test_assert(read(sock_fd[1], buf, sizeof(buf)) == 4 && memcmp(buf, "defg", 4) == 0); i_stream_unref(&input2); /* test reading past EOF */ i_stream_seek(input, 0); input2 = i_stream_create_limit(input, 20); test_assert(o_stream_send_istream(output, input2) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED); test_assert(input2->v_offset == 10); test_assert(output->offset == 14); i_stream_unref(&input2); i_stream_unref(&input); o_stream_destroy(&output); i_close_fd(&sock_fd[1]); i_unlink(".temp.istream"); test_end(); } void test_ostream_file(void) { test_ostream_file_random(); test_ostream_file_send_istream_file(); test_ostream_file_send_istream_sendfile(); } dovecot-2.3.21.1/src/lib/hash.c0000644000000000000000000003022214656633576012740 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ /* @UNSAFE: whole file */ #include "lib.h" #include "hash.h" #include "primes.h" #include #define HASH_TABLE_MIN_SIZE 67 #undef hash_table_create #undef hash_table_create_direct #undef hash_table_destroy #undef hash_table_clear #undef hash_table_lookup #undef hash_table_lookup_full #undef hash_table_insert #undef hash_table_update #undef hash_table_try_remove #undef hash_table_count #undef hash_table_iterate_init #undef hash_table_iterate #undef hash_table_freeze #undef hash_table_thaw #undef hash_table_copy struct hash_node { struct hash_node *next; void *key; void *value; }; struct hash_table { pool_t node_pool; int frozen; unsigned int initial_size, nodes_count, removed_count; unsigned int size; struct hash_node *nodes; struct hash_node *free_nodes; hash_callback_t *hash_cb; hash_cmp_callback_t *key_compare_cb; }; struct hash_iterate_context { struct hash_table *table; struct hash_node *next; unsigned int pos; }; enum hash_table_operation{ HASH_TABLE_OP_INSERT, HASH_TABLE_OP_UPDATE, HASH_TABLE_OP_RESIZE }; static bool hash_table_resize(struct hash_table *table, bool grow); void hash_table_create(struct hash_table **table_r, pool_t node_pool, unsigned int initial_size, hash_callback_t *hash_cb, hash_cmp_callback_t *key_compare_cb) { struct hash_table *table; pool_ref(node_pool); table = i_new(struct hash_table, 1); table->node_pool = node_pool; table->initial_size = I_MAX(primes_closest(initial_size), HASH_TABLE_MIN_SIZE); table->hash_cb = hash_cb; table->key_compare_cb = key_compare_cb; table->size = table->initial_size; table->nodes = i_new(struct hash_node, table->size); *table_r = table; } static unsigned int direct_hash(const void *p) { /* NOTE: may truncate the value, but that doesn't matter. */ return POINTER_CAST_TO(p, unsigned int); } static int direct_cmp(const void *p1, const void *p2) { return p1 == p2 ? 0 : 1; } void hash_table_create_direct(struct hash_table **table_r, pool_t node_pool, unsigned int initial_size) { hash_table_create(table_r, node_pool, initial_size, direct_hash, direct_cmp); } static void free_node(struct hash_table *table, struct hash_node *node) { if (!table->node_pool->alloconly_pool) p_free(table->node_pool, node); else { node->next = table->free_nodes; table->free_nodes = node; } } static void destroy_node_list(struct hash_table *table, struct hash_node *node) { struct hash_node *next; while (node != NULL) { next = node->next; p_free(table->node_pool, node); node = next; } } static void hash_table_destroy_nodes(struct hash_table *table) { unsigned int i; for (i = 0; i < table->size; i++) { if (table->nodes[i].next != NULL) destroy_node_list(table, table->nodes[i].next); } } void hash_table_destroy(struct hash_table **_table) { struct hash_table *table = *_table; if (table == NULL) return; *_table = NULL; i_assert(table->frozen == 0); if (!table->node_pool->alloconly_pool) { hash_table_destroy_nodes(table); destroy_node_list(table, table->free_nodes); } pool_unref(&table->node_pool); i_free(table->nodes); i_free(table); } void hash_table_clear(struct hash_table *table, bool free_nodes) { i_assert(table->frozen == 0); if (!table->node_pool->alloconly_pool) hash_table_destroy_nodes(table); if (free_nodes) { if (!table->node_pool->alloconly_pool) destroy_node_list(table, table->free_nodes); table->free_nodes = NULL; } memset(table->nodes, 0, sizeof(struct hash_node) * table->size); table->nodes_count = 0; table->removed_count = 0; } static struct hash_node * hash_table_lookup_node(const struct hash_table *table, const void *key, unsigned int hash) { struct hash_node *node; node = &table->nodes[hash % table->size]; do { if (node->key != NULL) { if (table->key_compare_cb(node->key, key) == 0) return node; } node = node->next; } while (node != NULL); return NULL; } void *hash_table_lookup(const struct hash_table *table, const void *key) { struct hash_node *node; node = hash_table_lookup_node(table, key, table->hash_cb(key)); return node != NULL ? node->value : NULL; } bool hash_table_lookup_full(const struct hash_table *table, const void *lookup_key, void **orig_key, void **value) { struct hash_node *node; node = hash_table_lookup_node(table, lookup_key, table->hash_cb(lookup_key)); if (node == NULL) return FALSE; *orig_key = node->key; *value = node->value; return TRUE; } static void hash_table_insert_node(struct hash_table *table, void *key, void *value, enum hash_table_operation opcode) { struct hash_node *node, *prev; unsigned int hash; bool check_existing = TRUE; i_assert(table->nodes_count < UINT_MAX); i_assert(key != NULL); if(opcode == HASH_TABLE_OP_RESIZE) check_existing = FALSE; hash = table->hash_cb(key); if (check_existing && table->removed_count > 0) { /* there may be holes, have to check everything */ node = hash_table_lookup_node(table, key, hash); if (node != NULL) { i_assert(opcode == HASH_TABLE_OP_UPDATE); node->value = value; return; } check_existing = FALSE; } /* a) primary node */ node = &table->nodes[hash % table->size]; if (node->key == NULL) { table->nodes_count++; node->key = key; node->value = value; return; } if (check_existing) { if (table->key_compare_cb(node->key, key) == 0) { i_assert(opcode == HASH_TABLE_OP_UPDATE); node->value = value; return; } } /* b) collisions list */ prev = node; node = node->next; while (node != NULL) { if (node->key == NULL) break; if (check_existing) { if (table->key_compare_cb(node->key, key) == 0) { i_assert(opcode == HASH_TABLE_OP_UPDATE); node->value = value; return; } } prev = node; node = node->next; } if (node == NULL) { if (table->frozen == 0 && hash_table_resize(table, TRUE)) { /* resized table, try again */ hash_table_insert_node(table, key, value, HASH_TABLE_OP_RESIZE); return; } if (table->free_nodes == NULL) node = p_new(table->node_pool, struct hash_node, 1); else { node = table->free_nodes; table->free_nodes = node->next; node->next = NULL; } prev->next = node; } node->key = key; node->value = value; table->nodes_count++; } void hash_table_insert(struct hash_table *table, void *key, void *value) { hash_table_insert_node(table, key, value, HASH_TABLE_OP_INSERT); } void hash_table_update(struct hash_table *table, void *key, void *value) { hash_table_insert_node(table, key, value, HASH_TABLE_OP_UPDATE); } static void hash_table_compress(struct hash_table *table, struct hash_node *root) { struct hash_node *node, *next; i_assert(table->frozen == 0); /* remove deleted nodes from the list */ for (node = root; node->next != NULL; ) { next = node->next; if (next->key == NULL) { node->next = next->next; free_node(table, next); } else { node = next; } } /* update root */ if (root->key == NULL && root->next != NULL) { next = root->next; *root = *next; free_node(table, next); } } static void hash_table_compress_removed(struct hash_table *table) { unsigned int i; for (i = 0; i < table->size; i++) hash_table_compress(table, &table->nodes[i]); table->removed_count = 0; } bool hash_table_try_remove(struct hash_table *table, const void *key) { struct hash_node *node; unsigned int hash; hash = table->hash_cb(key); node = hash_table_lookup_node(table, key, hash); if (unlikely(node == NULL)) return FALSE; node->key = NULL; table->nodes_count--; if (table->frozen != 0) table->removed_count++; else if (!hash_table_resize(table, FALSE)) hash_table_compress(table, &table->nodes[hash % table->size]); return TRUE; } unsigned int hash_table_count(const struct hash_table *table) { return table->nodes_count; } struct hash_iterate_context *hash_table_iterate_init(struct hash_table *table) { struct hash_iterate_context *ctx; hash_table_freeze(table); ctx = i_new(struct hash_iterate_context, 1); ctx->table = table; ctx->next = &table->nodes[0]; return ctx; } static struct hash_node * hash_table_iterate_next(struct hash_iterate_context *ctx, struct hash_node *node) { do { node = node->next; if (node == NULL) { if (++ctx->pos == ctx->table->size) { ctx->pos--; return NULL; } node = &ctx->table->nodes[ctx->pos]; } } while (node->key == NULL); return node; } bool hash_table_iterate(struct hash_iterate_context *ctx, void **key_r, void **value_r) { struct hash_node *node; node = ctx->next; if (node != NULL && node->key == NULL) node = hash_table_iterate_next(ctx, node); if (node == NULL) { *key_r = *value_r = NULL; return FALSE; } *key_r = node->key; *value_r = node->value; ctx->next = hash_table_iterate_next(ctx, node); return TRUE; } void hash_table_iterate_deinit(struct hash_iterate_context **_ctx) { struct hash_iterate_context *ctx = *_ctx; if (ctx == NULL) return; *_ctx = NULL; hash_table_thaw(ctx->table); i_free(ctx); } void hash_table_freeze(struct hash_table *table) { table->frozen++; } void hash_table_thaw(struct hash_table *table) { i_assert(table->frozen > 0); if (--table->frozen > 0) return; if (table->removed_count > 0) { if (!hash_table_resize(table, FALSE)) hash_table_compress_removed(table); } } static bool hash_table_resize(struct hash_table *table, bool grow) { struct hash_node *old_nodes, *node, *next; unsigned int next_size, old_size, i; float nodes_per_list; i_assert(table->frozen == 0); nodes_per_list = (float) table->nodes_count / (float) table->size; if (nodes_per_list > 0.3 && nodes_per_list < 2.0) return FALSE; next_size = I_MAX(primes_closest(table->nodes_count+1), table->initial_size); if (next_size == table->size) return FALSE; if (grow && table->size >= next_size) return FALSE; /* recreate primary table */ old_size = table->size; old_nodes = table->nodes; table->size = I_MAX(next_size, HASH_TABLE_MIN_SIZE); table->nodes = i_new(struct hash_node, table->size); table->nodes_count = 0; table->removed_count = 0; table->frozen++; /* move the data */ for (i = 0; i < old_size; i++) { node = &old_nodes[i]; if (node->key != NULL) { hash_table_insert_node(table, node->key, node->value, HASH_TABLE_OP_RESIZE); } for (node = node->next; node != NULL; node = next) { next = node->next; if (node->key != NULL) { hash_table_insert_node(table, node->key, node->value, HASH_TABLE_OP_RESIZE); } free_node(table, node); } } table->frozen--; i_free(old_nodes); return TRUE; } void hash_table_copy(struct hash_table *dest, struct hash_table *src) { struct hash_iterate_context *iter; void *key, *value; hash_table_freeze(dest); iter = hash_table_iterate_init(src); while (hash_table_iterate(iter, &key, &value)) hash_table_insert(dest, key, value); hash_table_iterate_deinit(&iter); hash_table_thaw(dest); } /* a char* hash function from ASU -- from glib */ unsigned int ATTR_NO_SANITIZE_INTEGER str_hash(const char *p) { const unsigned char *s = (const unsigned char *)p; unsigned int g, h = 0; while (*s != '\0') { h = (h << 4) + *s; if ((g = h & 0xf0000000UL) != 0) { h = h ^ (g >> 24); h = h ^ g; } s++; } return h; } /* a char* hash function from ASU -- from glib */ unsigned int ATTR_NO_SANITIZE_INTEGER strcase_hash(const char *p) { const unsigned char *s = (const unsigned char *)p; unsigned int g, h = 0; while (*s != '\0') { h = (h << 4) + i_toupper(*s); if ((g = h & 0xf0000000UL) != 0) { h = h ^ (g >> 24); h = h ^ g; } s++; } return h; } unsigned int ATTR_NO_SANITIZE_INTEGER mem_hash(const void *p, unsigned int size) { const unsigned char *s = p; unsigned int i, g, h = 0; for (i = 0; i < size; i++) { h = (h << 4) + *s; if ((g = h & 0xf0000000UL) != 0) { h = h ^ (g >> 24); h = h ^ g; } s++; } return h; } unsigned int ATTR_NO_SANITIZE_INTEGER strfastcase_hash(const char *p) { const unsigned char *s = (const unsigned char *)p; unsigned int g, h = 0; while (*s != '\0') { h = (h << 4) + ((*s) & ~0x20U); if ((g = h & 0xf0000000UL) != 0) { h = h ^ (g >> 24); h = h ^ g; } s++; } return h; } dovecot-2.3.21.1/src/lib/istream-chain.h0000644000000000000000000000160614656633576014552 00000000000000#ifndef ISTREAM_CHAIN_H #define ISTREAM_CHAIN_H struct istream_chain; /* Flexibly couple input streams into a single chain stream. Input streams can be added after creation of the chain stream, and the chain stream will not signal EOF until all streams are read to EOF and the last stream added was NULL. Streams that were finished to EOF are unreferenced. The chain stream is obviously not seekable and it has no determinable size. The chain_r argument returns a pointer to the chain object. */ struct istream *i_stream_create_chain(struct istream_chain **chain_r, size_t max_buffer_size); /* Append an input stream to the chain. */ void i_stream_chain_append(struct istream_chain *chain, struct istream *stream); /* Mark the end of the chain. Only then reads from the chain stream can return EOF. */ void i_stream_chain_append_eof(struct istream_chain *chain); #endif dovecot-2.3.21.1/src/lib/istream-tee.c0000644000000000000000000001602214656633576014236 00000000000000/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "istream-private.h" #include "istream-tee.h" struct tee_istream { struct istream *input; struct tee_child_istream *children; uoff_t max_read_offset; }; struct tee_child_istream { struct istream_private istream; struct tee_istream *tee; struct tee_child_istream *next; bool last_read_waiting:1; }; static void tee_streams_update_buffer(struct tee_istream *tee) { struct tee_child_istream *tstream = tee->children; const unsigned char *data; size_t size, old_used; data = i_stream_get_data(tee->input, &size); for (; tstream != NULL; tstream = tstream->next) { if (tstream->istream.istream.closed) { tstream->istream.skip = tstream->istream.pos = 0; continue; } old_used = tstream->istream.pos - tstream->istream.skip; tstream->istream.buffer = data; i_assert(tstream->istream.istream.v_offset >= tee->input->v_offset); tstream->istream.skip = tstream->istream.istream.v_offset - tee->input->v_offset; i_assert(tstream->istream.skip + old_used <= size); tstream->istream.pos = tstream->istream.skip + old_used; tstream->istream.parent_expected_offset = tee->input->v_offset; tstream->istream.access_counter = tee->input->real_stream->access_counter; } } static void tee_streams_skip(struct tee_istream *tee) { struct tee_child_istream *tstream = tee->children; size_t min_skip; min_skip = SIZE_MAX; for (; tstream != NULL; tstream = tstream->next) { if (tstream->istream.skip < min_skip && !tstream->istream.istream.closed) min_skip = tstream->istream.skip; } if (min_skip > 0 && min_skip != SIZE_MAX) { i_stream_skip(tee->input, min_skip); tee_streams_update_buffer(tee); } } static void i_stream_tee_close(struct iostream_private *stream, bool close_parent ATTR_UNUSED) { struct tee_child_istream *tstream = container_of(stream, struct tee_child_istream, istream.iostream); tee_streams_skip(tstream->tee); } static void i_stream_tee_destroy(struct iostream_private *stream) { struct tee_child_istream *tstream = container_of(stream, struct tee_child_istream, istream.iostream); struct tee_istream *tee = tstream->tee; struct tee_child_istream **p; if (tstream->istream.istream.v_offset > tee->max_read_offset) tee->max_read_offset = tstream->istream.istream.v_offset; for (p = &tee->children; *p != NULL; p = &(*p)->next) { if (*p == tstream) { *p = tstream->next; break; } } if (tee->children == NULL) { /* last child. the tee is now destroyed */ i_assert(tee->input->v_offset <= tee->max_read_offset); i_stream_skip(tee->input, tee->max_read_offset - tee->input->v_offset); i_stream_unref(&tee->input); i_free(tee); } else { tee_streams_skip(tstream->tee); } /* i_stream_unref() shouldn't unref the parent */ tstream->istream.parent = NULL; } static void i_stream_tee_set_max_buffer_size(struct iostream_private *stream, size_t max_size) { struct tee_child_istream *tstream = container_of(stream, struct tee_child_istream, istream.iostream); tstream->istream.max_buffer_size = max_size; i_stream_set_max_buffer_size(tstream->tee->input, max_size); } static ssize_t i_stream_tee_read(struct istream_private *stream) { struct tee_child_istream *tstream = container_of(stream, struct tee_child_istream, istream); struct istream *input = tstream->tee->input; const unsigned char *data; size_t size; uoff_t last_high_offset; ssize_t ret; tstream->last_read_waiting = FALSE; if (stream->buffer == NULL) { /* initial read */ tee_streams_update_buffer(tstream->tee); } data = i_stream_get_data(input, &size); /* last_high_offset contains how far we have read this child tee stream so far. input->v_offset + size contains how much is available in the parent stream without having to read more. */ last_high_offset = stream->istream.v_offset + (stream->pos - stream->skip); if (stream->pos == size) { /* we've read everything, need to read more */ i_assert(last_high_offset == input->v_offset + size); tee_streams_skip(tstream->tee); ret = i_stream_read(input); if (ret <= 0) { size = i_stream_get_data_size(input); if (ret == -2 && stream->skip != 0) { /* someone else is holding the data, wait for it */ tstream->last_read_waiting = TRUE; return 0; } stream->istream.stream_errno = input->stream_errno; stream->istream.eof = input->eof; return ret; } tee_streams_update_buffer(tstream->tee); data = i_stream_get_data(input, &size); } else { /* there's still some data available from parent */ i_assert(last_high_offset < input->v_offset + size); tee_streams_update_buffer(tstream->tee); i_assert(stream->pos < size); } i_assert(stream->buffer == data); ret = size - stream->pos; i_assert(ret > 0); stream->pos = size; i_assert(stream->istream.v_offset + (stream->pos - stream->skip) == input->v_offset + size); return ret; } static int i_stream_tee_stat(struct istream_private *stream, bool exact) { struct tee_child_istream *tstream = container_of(stream, struct tee_child_istream, istream); const struct stat *st; if (i_stream_stat(tstream->tee->input, exact, &st) < 0) return -1; stream->statbuf = *st; return 0; } static void i_stream_tee_sync(struct istream_private *stream) { struct tee_child_istream *tstream = container_of(stream, struct tee_child_istream, istream); tee_streams_skip(tstream->tee); if (i_stream_get_data_size(tstream->tee->input) != 0) { i_panic("tee-istream: i_stream_sync() called " "with data still buffered"); } i_stream_sync(tstream->tee->input); } struct tee_istream *tee_i_stream_create(struct istream *input) { struct tee_istream *tee; tee = i_new(struct tee_istream, 1); if (input->v_offset == 0) { i_stream_ref(input); tee->input = input; } else { tee->input = i_stream_create_limit(input, UOFF_T_MAX); } return tee; } struct istream *tee_i_stream_create_child(struct tee_istream *tee) { struct tee_child_istream *tstream; struct istream *ret, *input = tee->input; tstream = i_new(struct tee_child_istream, 1); tstream->tee = tee; tstream->istream.max_buffer_size = input->real_stream->max_buffer_size; tstream->istream.iostream.close = i_stream_tee_close; tstream->istream.iostream.destroy = i_stream_tee_destroy; tstream->istream.iostream.set_max_buffer_size = i_stream_tee_set_max_buffer_size; tstream->istream.read = i_stream_tee_read; tstream->istream.stat = i_stream_tee_stat; tstream->istream.sync = i_stream_tee_sync; tstream->next = tee->children; tee->children = tstream; ret = i_stream_create(&tstream->istream, input, i_stream_get_fd(input), ISTREAM_CREATE_FLAG_NOOP_SNAPSHOT); i_stream_set_name(&tstream->istream.istream, i_stream_get_name(input)); /* we keep the reference in tee stream, no need for extra references */ i_stream_unref(&input); return ret; } bool tee_i_stream_child_is_waiting(struct istream *input) { struct tee_child_istream *tstream = container_of(input->real_stream, struct tee_child_istream, istream); return tstream->last_read_waiting; } dovecot-2.3.21.1/src/lib/child-wait.h0000644000000000000000000000170614656633576014054 00000000000000#ifndef CHILD_WAIT_H #define CHILD_WAIT_H struct child_wait_status { struct child_wait *wait; pid_t pid; int status; }; typedef void child_wait_callback_t(const struct child_wait_status *status, void *context); struct child_wait * child_wait_new_with_pid(pid_t pid, child_wait_callback_t *callback, void *context) ATTR_NULL(3); #define child_wait_new_with_pid(pid, callback, context) \ child_wait_new_with_pid(pid - \ CALLBACK_TYPECHECK(callback, void (*)( \ const struct child_wait_status *status, typeof(context))), \ (child_wait_callback_t *)callback, context) #define child_wait_new(callback, context) \ child_wait_new_with_pid((pid_t)-1, callback, context) void child_wait_free(struct child_wait **wait); void child_wait_add_pid(struct child_wait *wait, pid_t pid); void child_wait_remove_pid(struct child_wait *wait, pid_t pid); void child_wait_switch_ioloop(void); void child_wait_init(void); void child_wait_deinit(void); #endif dovecot-2.3.21.1/src/lib/test-lib.inc0000644000000000000000000000517614656633576014101 00000000000000/* This file may be multiply-included, with different definitions of 'TEST()' macro. This is sometimes called "the X trick" (as the macro is often imaginatively called X(). */ TEST(test_aqueue) TEST(test_array) FATAL(fatal_array) TEST(test_backtrace) TEST(test_base32) TEST(test_base64) TEST(test_bits) TEST(test_bsearch_insert_pos) TEST(test_buffer) TEST(test_buffer_append_full) FATAL(fatal_buffer) TEST(test_byteorder) TEST(test_connection) TEST(test_crc32) TEST(test_cpu_limit) TEST(test_data_stack) FATAL(fatal_data_stack) TEST(test_env_util) FATAL(fatal_env_util) TEST(test_event_category_register) FATAL(fatal_event_category_register) TEST(test_lib_event) FATAL(fatal_lib_event) TEST(test_event_filter) TEST(test_event_filter_expr) TEST(test_event_filter_merge) TEST(test_event_filter_parser) TEST(test_event_flatten) TEST(test_event_log) TEST(test_failures) TEST(test_file_cache) TEST(test_file_create_locked) TEST(test_guid) TEST(test_hash) TEST(test_hash_format) TEST(test_hash_method) TEST(test_hmac) TEST(test_hex_binary) FATAL(fatal_i_close) TEST(test_imem) TEST(test_ioloop) TEST(test_iso8601_date) TEST(test_iostream_pump) TEST(test_iostream_proxy) TEST(test_iostream_temp) TEST(test_istream) TEST(test_istream_base64_decoder) TEST(test_istream_base64_encoder) TEST(test_istream_chain) TEST(test_istream_concat) TEST(test_istream_crlf) TEST(test_istream_failure_at) TEST(test_istream_jsonstr) TEST(test_istream_multiplex) TEST(test_istream_seekable) TEST(test_istream_sized) TEST(test_istream_tee) TEST(test_istream_try) TEST(test_istream_unix) TEST(test_json_parser) TEST(test_json_tree) TEST(test_lib_event) TEST(test_lib_signals) TEST(test_llist) TEST(test_log_throttle) TEST(test_macros) TEST(test_malloc_overflow) FATAL(fatal_malloc_overflow) TEST(test_memarea) TEST(test_mempool) FATAL(fatal_mempool) TEST(test_mempool_alloconly) FATAL(fatal_mempool_alloconly) TEST(test_mempool_allocfree) FATAL(fatal_mempool_allocfree) TEST(test_net) TEST(test_numpack) TEST(test_ostream_buffer) TEST(test_ostream_failure_at) TEST(test_ostream_file) TEST(test_ostream_multiplex) TEST(test_multiplex) TEST(test_path_util) TEST(test_pkcs5_pbkdf2) TEST(test_primes) TEST(test_printf_format_fix) FATAL(fatal_printf_format_fix) TEST(test_priorityq) TEST(test_random) FATAL(fatal_random) TEST(test_seq_range_array) FATAL(fatal_seq_range_array) TEST(test_seq_set_builder) TEST(test_stats_dist) TEST(test_str) TEST(test_strescape) TEST(test_strfuncs) FATAL(fatal_strfuncs) TEST(test_strnum) TEST(test_str_find) TEST(test_str_sanitize) TEST(test_str_table) TEST(test_time_util) TEST(test_unichar) TEST(test_uri) TEST(test_utc_mktime) TEST(test_var_expand) TEST(test_wildcard_match) dovecot-2.3.21.1/src/lib/ostream-multiplex.c0000644000000000000000000002533514656633576015521 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "array.h" #include "ostream-private.h" #include "ostream-multiplex.h" /* all multiplex packets are [1 byte cid][4 byte length][data] */ struct multiplex_ostream; struct multiplex_ochannel { struct ostream_private ostream; struct multiplex_ostream *mstream; uint8_t cid; buffer_t *buf; uint64_t last_sent_counter; bool closed:1; bool corked:1; }; struct multiplex_ostream { struct ostream *parent; stream_flush_callback_t *old_flush_callback; void *old_flush_context; /* channel 0 is main channel */ uint8_t cur_channel; unsigned int remain; size_t bufsize; uint64_t send_counter; ARRAY(struct multiplex_ochannel *) channels; bool destroyed:1; }; static struct multiplex_ochannel * get_channel(struct multiplex_ostream *mstream, uint8_t cid) { struct multiplex_ochannel *channel; i_assert(mstream != NULL); array_foreach_elem(&mstream->channels, channel) { if (channel != NULL && channel->cid == cid) return channel; } return NULL; } static void propagate_error(struct multiplex_ostream *mstream, int stream_errno) { struct multiplex_ochannel *channel; array_foreach_elem(&mstream->channels, channel) if (channel != NULL) channel->ostream.ostream.stream_errno = stream_errno; } static struct multiplex_ochannel *get_next_channel(struct multiplex_ostream *mstream) { struct multiplex_ochannel *oldest_channel = NULL; struct multiplex_ochannel *channel; uint64_t last_counter = mstream->send_counter; array_foreach_elem(&mstream->channels, channel) { if (channel != NULL && channel->last_sent_counter <= last_counter && channel->buf->used > 0) { last_counter = channel->last_sent_counter; oldest_channel = channel; } } return oldest_channel; } static bool o_stream_multiplex_sendv(struct multiplex_ostream *mstream) { struct multiplex_ochannel *channel; ssize_t ret = 0; bool all_sent = TRUE; while((channel = get_next_channel(mstream)) != NULL) { if (channel->buf->used == 0) continue; if (o_stream_get_buffer_avail_size(mstream->parent) < 6) { all_sent = FALSE; break; } /* check parent stream capacity */ size_t tmp = o_stream_get_buffer_avail_size(mstream->parent) - 5; /* ensure it fits into 32 bit int */ size_t amt = I_MIN(UINT_MAX, I_MIN(tmp, channel->buf->used)); /* ensure amt fits */ if (tmp == 0) break; /* delay corking here now that we are going to send something */ if (!o_stream_is_corked(mstream->parent)) o_stream_cork(mstream->parent); uint32_t len = cpu32_to_be(amt); const struct const_iovec vec[] = { { &channel->cid, 1 }, { &len, 4 }, { channel->buf->data, amt } }; if ((ret = o_stream_sendv(mstream->parent, vec, N_ELEMENTS(vec))) < 0) { propagate_error(mstream, mstream->parent->stream_errno); break; } i_assert((size_t)ret == 1 + 4 + amt); buffer_delete(channel->buf, 0, amt); channel->last_sent_counter = ++mstream->send_counter; } if (o_stream_is_corked(mstream->parent)) o_stream_uncork(mstream->parent); return all_sent; } static int o_stream_multiplex_flush(struct multiplex_ostream *mstream) { int ret = o_stream_flush(mstream->parent); if (ret >= 0) { if (!o_stream_multiplex_sendv(mstream)) return 0; } /* a) Everything is flushed. See if one of the callbacks' flush callbacks wants to write more data. b) ostream failed. Notify the callbacks in case they need to know. */ struct multiplex_ochannel *channel; bool unfinished = FALSE; bool failed = FALSE; array_foreach_elem(&mstream->channels, channel) { if (channel != NULL && channel->ostream.callback != NULL) { ret = channel->ostream.callback(channel->ostream.context); if (ret < 0) failed = TRUE; else if (ret == 0) unfinished = TRUE; } } return failed ? -1 : (unfinished ? 0 : 1); } static int o_stream_multiplex_ochannel_flush(struct ostream_private *stream) { ssize_t ret; struct multiplex_ochannel *channel = container_of(stream, struct multiplex_ochannel, ostream); struct multiplex_ostream *mstream = channel->mstream; /* flush parent stream always, so there is room for more. */ if ((ret = o_stream_flush(mstream->parent)) <= 0) { if (ret == -1) propagate_error(mstream, mstream->parent->stream_errno); return ret; } /* send all channels */ o_stream_multiplex_sendv(mstream); if (channel->buf->used > 0) return 0; return 1; } static void o_stream_multiplex_ochannel_cork(struct ostream_private *stream, bool set) { struct multiplex_ochannel *channel = container_of(stream, struct multiplex_ochannel, ostream); if (channel->corked != set && !set) { /* flush */ (void)o_stream_multiplex_ochannel_flush(stream); } channel->corked = set; } static ssize_t o_stream_multiplex_ochannel_sendv(struct ostream_private *stream, const struct const_iovec *iov, unsigned int iov_count) { struct multiplex_ochannel *channel = container_of(stream, struct multiplex_ochannel, ostream); size_t total = 0, avail = o_stream_get_buffer_avail_size(&stream->ostream); size_t optimal_size = I_MIN(IO_BLOCK_SIZE, avail); for (unsigned int i = 0; i < iov_count; i++) total += iov[i].iov_len; if (avail < total) { o_stream_multiplex_sendv(channel->mstream); avail = o_stream_get_buffer_avail_size(&stream->ostream); if (avail == 0) return 0; } total = 0; for (unsigned int i = 0; i < iov_count; i++) { /* copy data to buffer */ size_t tmp = avail - total; if (tmp == 0) break; buffer_append(channel->buf, iov[i].iov_base, I_MIN(tmp, iov[i].iov_len)); total += I_MIN(tmp, iov[i].iov_len); } stream->ostream.offset += total; /* will send later */ if (channel->corked && channel->buf->used < optimal_size) return total; o_stream_multiplex_sendv(channel->mstream); return total; } static void o_stream_multiplex_ochannel_set_flush_callback(struct ostream_private *stream, stream_flush_callback_t *callback, void *context) { /* We have overwritten our parent's flush-callback. Don't change it. */ stream->callback = callback; stream->context = context; } static size_t o_stream_multiplex_ochannel_get_buffer_used_size(const struct ostream_private *stream) { const struct multiplex_ochannel *channel = container_of(stream, const struct multiplex_ochannel, ostream); return channel->buf->used + o_stream_get_buffer_used_size(channel->mstream->parent); } static size_t o_stream_multiplex_ochannel_get_buffer_avail_size(const struct ostream_private *stream) { const struct multiplex_ochannel *channel = container_of(stream, const struct multiplex_ochannel, ostream); size_t max_avail = I_MIN(channel->mstream->bufsize, o_stream_get_buffer_avail_size(stream->parent)); /* There is 5-byte overhead per message, so take that into account */ return max_avail <= (channel->buf->used + 5) ? 0 : max_avail - (channel->buf->used + 5); } static void o_stream_multiplex_ochannel_close(struct iostream_private *stream, bool close_parent) { struct multiplex_ochannel *arr_channel; struct multiplex_ochannel *channel = container_of(stream, struct multiplex_ochannel, ostream.iostream); channel->closed = TRUE; if (close_parent) { array_foreach_elem(&channel->mstream->channels, arr_channel) if (arr_channel != NULL && !arr_channel->closed) return; o_stream_close(channel->mstream->parent); } } static void o_stream_multiplex_try_destroy(struct multiplex_ostream *mstream) { struct multiplex_ochannel *channel; /* can't do anything until they are all closed */ array_foreach_elem(&mstream->channels, channel) if (channel != NULL) return; i_assert(mstream->parent->real_stream->callback == (stream_flush_callback_t *)o_stream_multiplex_flush); o_stream_set_flush_callback(mstream->parent, *mstream->old_flush_callback, mstream->old_flush_context); o_stream_unref(&mstream->parent); array_free(&mstream->channels); i_free(mstream); } static void o_stream_multiplex_ochannel_destroy(struct iostream_private *stream) { struct multiplex_ochannel **channelp; struct multiplex_ochannel *channel = container_of(stream, struct multiplex_ochannel, ostream.iostream); o_stream_unref(&channel->ostream.parent); if (channel->buf != NULL) buffer_free(&channel->buf); /* delete the channel */ array_foreach_modifiable(&channel->mstream->channels, channelp) { if (*channelp != NULL && (*channelp)->cid == channel->cid) { *channelp = NULL; break; } } o_stream_multiplex_try_destroy(channel->mstream); } static struct ostream * o_stream_add_channel_real(struct multiplex_ostream *mstream, uint8_t cid) { struct multiplex_ochannel *channel = i_new(struct multiplex_ochannel, 1); channel->cid = cid; channel->buf = buffer_create_dynamic(default_pool, 256); channel->mstream = mstream; channel->ostream.cork = o_stream_multiplex_ochannel_cork; channel->ostream.flush = o_stream_multiplex_ochannel_flush; channel->ostream.sendv = o_stream_multiplex_ochannel_sendv; channel->ostream.set_flush_callback = o_stream_multiplex_ochannel_set_flush_callback; channel->ostream.get_buffer_used_size = o_stream_multiplex_ochannel_get_buffer_used_size; channel->ostream.get_buffer_avail_size = o_stream_multiplex_ochannel_get_buffer_avail_size; channel->ostream.iostream.close = o_stream_multiplex_ochannel_close; channel->ostream.iostream.destroy = o_stream_multiplex_ochannel_destroy; channel->ostream.fd = o_stream_get_fd(mstream->parent); array_push_back(&channel->mstream->channels, &channel); (void)o_stream_create(&channel->ostream, mstream->parent, -1); /* o_stream_create() defaults the flush_callback to parent's callback. Here it points to o_stream_multiplex_flush(), which just causes infinite looping. */ channel->ostream.callback = NULL; channel->ostream.context = NULL; return &channel->ostream.ostream; } struct ostream *o_stream_multiplex_add_channel(struct ostream *stream, uint8_t cid) { struct multiplex_ochannel *chan = container_of(stream->real_stream, struct multiplex_ochannel, ostream); i_assert(get_channel(chan->mstream, cid) == NULL); return o_stream_add_channel_real(chan->mstream, cid); } struct ostream *o_stream_create_multiplex(struct ostream *parent, size_t bufsize) { struct multiplex_ostream *mstream; mstream = i_new(struct multiplex_ostream, 1); mstream->parent = parent; mstream->bufsize = bufsize; mstream->old_flush_callback = parent->real_stream->callback; mstream->old_flush_context = parent->real_stream->context; o_stream_set_flush_callback(parent, o_stream_multiplex_flush, mstream); i_array_init(&mstream->channels, 8); o_stream_ref(parent); return o_stream_add_channel_real(mstream, 0); } uint8_t o_stream_multiplex_get_channel_id(struct ostream *stream) { struct multiplex_ochannel *channel = container_of(stream->real_stream, struct multiplex_ochannel, ostream); return channel->cid; } dovecot-2.3.21.1/src/lib/ioloop-iolist.c0000644000000000000000000000225514656633576014624 00000000000000/* * Copyright (c) 2004 Andrey Panin * * This software is released under the MIT license. */ #include "lib.h" #include "ioloop-private.h" #include "ioloop-iolist.h" bool ioloop_iolist_add(struct io_list *list, struct io_file *io) { int i, idx; if ((io->io.condition & IO_READ) != 0) idx = IOLOOP_IOLIST_INPUT; else if ((io->io.condition & IO_WRITE) != 0) idx = IOLOOP_IOLIST_OUTPUT; else if ((io->io.condition & IO_ERROR) != 0) idx = IOLOOP_IOLIST_ERROR; else { i_unreached(); } if (list->ios[idx] != NULL) { i_panic("io_add(0x%x) called twice fd=%d, callback=%p -> %p", io->io.condition, io->fd, list->ios[idx]->io.callback, io->io.callback); } i_assert(list->ios[idx] == NULL); list->ios[idx] = io; /* check if this was the first one */ for (i = 0; i < IOLOOP_IOLIST_IOS_PER_FD; i++) { if (i != idx && list->ios[i] != NULL) return FALSE; } return TRUE; } bool ioloop_iolist_del(struct io_list *list, struct io_file *io) { bool last = TRUE; int i; for (i = 0; i < IOLOOP_IOLIST_IOS_PER_FD; i++) { if (list->ios[i] != NULL) { if (list->ios[i] == io) list->ios[i] = NULL; else last = FALSE; } } return last; } dovecot-2.3.21.1/src/lib/test-iostream-temp.c0000644000000000000000000001163614656633576015570 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "istream.h" #include "ostream.h" #include "iostream-temp.h" #include #include static void test_iostream_temp_create_sized_memory(void) { struct ostream *output; test_begin("iostream_temp_create_sized() memory"); output = iostream_temp_create_sized(".intentional-nonexistent-error/", 0, "test", 4); test_assert(o_stream_send(output, "123", 3) == 3); test_assert(output->offset == 3); test_assert(o_stream_send(output, "4", 1) == 1); test_assert(output->offset == 4); test_assert(o_stream_get_fd(output) == -1); /* now we'll try to switch to writing to a file, but it'll fail */ test_expect_error_string("safe_mkstemp"); test_assert(o_stream_send(output, "5", 1) == 1); test_expect_no_more_errors(); test_assert(o_stream_get_fd(output) == -1); o_stream_destroy(&output); test_end(); } static void test_iostream_temp_create_sized_disk(void) { struct ostream *output; test_begin("iostream_temp_create_sized() disk"); output = iostream_temp_create_sized(".", 0, "test", 4); test_assert(o_stream_send(output, "123", 3) == 3); test_assert(output->offset == 3); test_assert(o_stream_send(output, "4", 1) == 1); test_assert(output->offset == 4); test_assert(o_stream_get_fd(output) == -1); test_assert(o_stream_send(output, "5", 1) == 1); test_assert(output->offset == 5); test_assert(o_stream_get_fd(output) != -1); o_stream_destroy(&output); test_end(); } static void test_iostream_temp_create_write_error(void) { struct ostream *output; test_begin("iostream_temp_create_sized() write error"); output = iostream_temp_create_sized(".", 0, "test", 1); test_assert(o_stream_send(output, "123", 3) == 3); test_assert(o_stream_get_fd(output) != -1); test_assert(output->offset == 3); test_assert(o_stream_temp_move_to_memory(output) == 0); test_assert(o_stream_get_fd(output) == -1); test_assert(o_stream_send(output, "45", 2) == 2); test_assert(output->offset == 5); const unsigned char *data; size_t size; struct istream *input = iostream_temp_finish(&output, 128); test_assert(i_stream_read_bytes(input, &data, &size, 5) == 1 && memcmp(data, "12345", 5) == 0); i_stream_destroy(&input); test_end(); } static void test_iostream_temp_istream(void) { struct istream *input, *input2, *temp_input; struct ostream *output; int fd; test_begin("iostream_temp istream"); fd = open(".temp.istream", O_RDWR | O_CREAT | O_TRUNC, 0600); if (fd == -1) i_fatal("create(.temp.istream) failed: %m"); test_assert(write(fd, "foobar", 6) == 6); test_assert(lseek(fd, 0, SEEK_SET) == 0); input = i_stream_create_fd_autoclose(&fd, 1024); /* a working fd-dup */ output = iostream_temp_create_sized(".nonexistent/", IOSTREAM_TEMP_FLAG_TRY_FD_DUP, "test", 1); test_assert(o_stream_send_istream(output, input) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED); test_assert(output->offset == 6); temp_input = iostream_temp_finish(&output, 128); test_assert(i_stream_read(temp_input) == 6); i_stream_destroy(&temp_input); /* non-working fd-dup: write data before sending istream */ i_stream_seek(input, 0); output = iostream_temp_create_sized(".intentional-nonexistent-error/", IOSTREAM_TEMP_FLAG_TRY_FD_DUP, "test", 4); test_assert(o_stream_send(output, "1234", 4) == 4); test_assert(output->offset == 4); test_expect_error_string("safe_mkstemp"); test_assert(o_stream_send_istream(output, input) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED); test_assert(output->offset == 10); test_expect_no_more_errors(); o_stream_destroy(&output); /* non-working fd-dup: write data after sending istream */ i_stream_seek(input, 0); output = iostream_temp_create_sized(".intentional-nonexistent-error/", IOSTREAM_TEMP_FLAG_TRY_FD_DUP, "test", 4); test_assert(o_stream_send_istream(output, input) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED); test_assert(output->offset == 6); test_expect_error_string("safe_mkstemp"); test_assert(o_stream_send(output, "1", 1) == 1); test_assert(output->offset == 7); test_expect_no_more_errors(); o_stream_destroy(&output); /* non-working fd-dup: send two istreams */ i_stream_seek(input, 0); input2 = i_stream_create_limit(input, UOFF_T_MAX); output = iostream_temp_create_sized(".intentional-nonexistent-error/", IOSTREAM_TEMP_FLAG_TRY_FD_DUP, "test", 4); test_assert(o_stream_send_istream(output, input) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED); test_assert(output->offset == 6); test_expect_error_string("safe_mkstemp"); test_assert(o_stream_send_istream(output, input2) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED); test_assert(output->offset == 12); test_expect_no_more_errors(); o_stream_destroy(&output); i_stream_unref(&input2); i_stream_destroy(&input); i_unlink(".temp.istream"); test_end(); } void test_iostream_temp(void) { test_iostream_temp_create_sized_memory(); test_iostream_temp_create_sized_disk(); test_iostream_temp_create_write_error(); test_iostream_temp_istream(); } dovecot-2.3.21.1/src/lib/unicodemap.pl0000755000000000000000000001046114656633576014340 00000000000000#!/usr/bin/env perl use strict; my (%titlecase8, %uni8_decomp); my (@titlecase16_keys, @titlecase16_values); my (@titlecase32_keys, @titlecase32_values); my (@uni16_decomp_keys, @uni16_decomp_values); my (@uni32_decomp_keys, @uni32_decomp_values); my (@multidecomp_keys, @multidecomp_offsets, @multidecomp_values); while (<>) { chomp $_; my @arr = split(";"); my $code = eval("0x".$arr[0]); my $decomp = $arr[5]; my $titlecode = $arr[14]; if ($titlecode ne "") { # titlecase mapping my $value = eval("0x$titlecode"); if ($value == $code) { # the same character, ignore } elsif ($code <= 0xff) { die "Error: We've assumed 8bit keys have max. 16bit values" if ($value > 0xffff); $titlecase8{$code} = $value; } elsif ($code <= 0xffff) { die "Error: We've assumed 16bit keys have max. 16bit values" if ($value > 0xffff); push @titlecase16_keys, $code; push @titlecase16_values, $value; } else { push @titlecase32_keys, $code; push @titlecase32_values, $value; } } elsif ($decomp =~ /(?:\<[^>]*> )?(.+)/) { # decompositions my $decomp_codes = $1; if ($decomp_codes =~ /^([0-9A-Z]*)$/i) { # unicharacter decomposition. use separate lists for this my $value = eval("0x$1"); if ($value > 0xffffffff) { print STDERR "Error: We've assumed decomposition codes are max. 32bit\n"; exit 1; } if ($code <= 0xff) { $uni8_decomp{$code} = $value; } elsif ($code <= 0xffff) { push @uni16_decomp_keys, $code; push @uni16_decomp_values, $value; } else { push @uni32_decomp_keys, $code; push @uni32_decomp_values, $value; } } else { # multicharacter decomposition. if ($code > 0xffffffff) { print STDERR "Error: We've assumed multi-decomposition key codes are max. 32bit\n"; exit 1; } push @multidecomp_keys, $code; push @multidecomp_offsets, scalar(@multidecomp_values); foreach my $dcode (split(" ", $decomp_codes)) { my $value = eval("0x$dcode"); if ($value > 0xffffffff) { print STDERR "Error: We've assumed decomposition codes are max. 32bit\n"; exit 1; } push @multidecomp_values, $value; } push @multidecomp_values, 0; } } } sub print_list { my @list = @{$_[0]}; my $last = $#list; my $n = 0; foreach my $key (@list) { printf("0x%05x", $key); last if ($n == $last); print ","; $n++; if (($n % 8) == 0) { print "\n\t"; } else { print " "; } } } print "/* This file is automatically generated by unicodemap.pl from UnicodeData.txt NOTE: decompositions for characters having titlecase characters are not included, because we first translate everything to titlecase */\n"; sub print_map8 { my %map = %{$_[0]}; my @list; for (my $i = 0; $i <= 0xff; $i++) { if (defined($map{$i})) { push @list, $map{$i}; } else { push @list, $i; } } print_list(\@list); } print "static const uint16_t titlecase8_map[256] = {\n\t"; print_map8(\%titlecase8); print "\n};\n"; print "static const uint16_t titlecase16_keys[] = {\n\t"; print_list(\@titlecase16_keys); print "\n};\n"; print "static const uint16_t titlecase16_values[] = {\n\t"; print_list(\@titlecase16_values); print "\n};\n"; print "static const uint32_t titlecase32_keys[] = {\n\t"; print_list(\@titlecase32_keys); print "\n};\n"; print "static const uint32_t titlecase32_values[] = {\n\t"; print_list(\@titlecase32_values); print "\n};\n"; print "static const uint16_t uni8_decomp_map[256] = {\n\t"; print_map8(\%uni8_decomp); print "\n};\n"; print "static const uint16_t uni16_decomp_keys[] = {\n\t"; print_list(\@uni16_decomp_keys); print "\n};\n"; print "static const uint32_t uni16_decomp_values[] = {\n\t"; print_list(\@uni16_decomp_values); print "\n};\n"; print "static const uint32_t uni32_decomp_keys[] = {\n\t"; print_list(\@uni32_decomp_keys); print "\n};\n"; print "static const uint32_t uni32_decomp_values[] = {\n\t"; print_list(\@uni32_decomp_values); print "\n};\n"; print "static const uint32_t multidecomp_keys[] = {\n\t"; print_list(\@multidecomp_keys); print "\n};\n"; print "static const uint16_t multidecomp_offsets[] = {\n\t"; print_list(\@multidecomp_offsets); print "\n};\n"; print "static const uint32_t multidecomp_values[] = {\n\t"; print_list(\@multidecomp_values); print "\n};\n"; dovecot-2.3.21.1/src/lib/md4.h0000644000000000000000000000152114656633576012506 00000000000000/* * This is an OpenSSL-compatible implementation of the RSA Data Security, * Inc. MD4 Message-Digest Algorithm. * * Written by Solar Designer in 2001, and placed in * the public domain. See md4.c for more information. */ #ifndef MD4_H #define MD4_H #include "hash-method.h" #define MD4_RESULTLEN (128/8) struct md4_context { uint_fast32_t lo, hi; uint_fast32_t a, b, c, d; unsigned char buffer[64]; uint_fast32_t block[MD4_RESULTLEN]; }; void md4_init(struct md4_context *ctx); void md4_update(struct md4_context *ctx, const void *data, size_t size); void md4_final(struct md4_context *ctx, unsigned char result[STATIC_ARRAY MD4_RESULTLEN]); void md4_get_digest(const void *data, size_t size, unsigned char result[STATIC_ARRAY MD4_RESULTLEN]); extern const struct hash_method hash_method_md4; #endif dovecot-2.3.21.1/src/lib/printf-format-fix.c0000644000000000000000000001166614656633576015404 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "printf-format-fix.h" static const char * fix_format_real(const char *fmt, const char *p, size_t *len_r) { const char *errstr; char *buf; size_t len1, len2, len3; i_assert((size_t)(p - fmt) < INT_MAX); i_assert(p[0] == '%' && p[1] == 'm'); errstr = strerror(errno); /* we'll assume that there's only one %m in the format string. this simplifies the code and there's really no good reason to have it multiple times. Callers can trap this case themselves. */ len1 = p - fmt; len2 = strlen(errstr); len3 = strlen(p + 2); /* @UNSAFE */ buf = t_buffer_get(len1 + len2 + len3 + 1); memcpy(buf, fmt, len1); memcpy(buf + len1, errstr, len2); memcpy(buf + len1 + len2, p + 2, len3 + 1); *len_r = len1 + len2 + len3; return buf; } static bool verify_length(const char **p) { if (**p == '*') { /* We don't bother supporting "*m$" - it's not used anywhere and seems a bit dangerous. */ *p += 1; } else if (**p >= '0' && **p <= '9') { /* Limit to 4 digits - we'll never want more than that. Some implementations might not handle long digits correctly, or maybe even could be used for DoS due to using too much CPU. If you want to express '99' as '00099', then you lose in this function. */ unsigned int i = 0; do { *p += 1; if (++i > 4) return FALSE; } while (**p >= '0' && **p <= '9'); } return TRUE; } static const char * printf_format_fix_noalloc(const char *format, size_t *len_r) { /* NOTE: This function is overly strict in what it accepts. Some format strings that are valid (and safe) in C99 will cause a panic here. This is because we don't really need to support the weirdest special cases, and we're also being extra careful not to pass anything to the underlying libc printf, which might treat the string differently than us and unexpectedly handling it as %n. For example "%**%n" with glibc. */ /* Allow only the standard C99 flags. There are also <'> and flags, but we don't really need them. And at worst if they're not supported by the underlying printf, they could potentially be used to work around our restrictions. */ const char printf_flags[] = "#0- +"; /* As a tiny optimization keep the most commonly used conversion specifiers first, so strchr() stops early. */ static const char *printf_specifiers = "sudcixXpoeEfFgGaA"; const char *ret, *p, *p2; char *flag; p = ret = format; while ((p2 = strchr(p, '%')) != NULL) { const unsigned int start_pos = p2 - format; p = p2+1; if (*p == '%') { /* we'll be strict and allow %% only when there are no optinal flags or modifiers. */ p++; continue; } /* 1) zero or more flags. We'll add a further restriction that each flag can be used only once, since there's no need to use them more than once, and some implementations might add their own limits. */ bool printf_flags_seen[N_ELEMENTS(printf_flags)] = { FALSE, }; while (*p != '\0' && (flag = strchr(printf_flags, *p)) != NULL) { unsigned int flag_idx = flag - printf_flags; if (printf_flags_seen[flag_idx]) { i_panic("Duplicate %% flag '%c' starting at #%u in '%s'", *p, start_pos, format); } printf_flags_seen[flag_idx] = TRUE; p++; } /* 2) Optional minimum field width */ if (!verify_length(&p)) { i_panic("Too large minimum field width starting at #%u in '%s'", start_pos, format); } /* 3) Optional precision */ if (*p == '.') { p++; if (!verify_length(&p)) { i_panic("Too large precision starting at #%u in '%s'", start_pos, format); } } /* 4) Optional length modifier */ switch (*p) { case 'h': if (*++p == 'h') p++; break; case 'l': if (*++p == 'l') p++; break; case 'L': case 'j': case 'z': case 't': p++; break; } /* 5) conversion specifier */ if (*p == '\0' || strchr(printf_specifiers, *p) == NULL) { switch (*p) { case 'n': i_panic("%%n modifier used"); case 'm': if (ret != format) i_panic("%%m used twice"); ret = fix_format_real(format, p-1, len_r); break; case '\0': i_panic("Missing %% specifier starting at #%u in '%s'", start_pos, format); default: i_panic("Unsupported 0x%02x specifier starting at #%u in '%s'", *p, start_pos, format); } } p++; } if (ret == format) *len_r = p - format + strlen(p); return ret; } const char *printf_format_fix_get_len(const char *format, size_t *len_r) { const char *ret; ret = printf_format_fix_noalloc(format, len_r); if (ret != format) t_buffer_alloc(*len_r + 1); return ret; } const char *printf_format_fix(const char *format) { const char *ret; size_t len; ret = printf_format_fix_noalloc(format, &len); if (ret != format) t_buffer_alloc(len + 1); return ret; } const char *printf_format_fix_unsafe(const char *format) { size_t len; return printf_format_fix_noalloc(format, &len); } dovecot-2.3.21.1/src/lib/test-bsearch-insert-pos.c0000644000000000000000000000206114656633576016502 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "bsearch-insert-pos.h" static int cmp_uint(const unsigned int *i1, const unsigned int *i2) { return (int)*i1 - (int)*i2; } void test_bsearch_insert_pos(void) { static const unsigned int input[] = { 1, 5, 9, 15, 16, UINT_MAX, 1, 5, 9, 15, 16, 17, UINT_MAX, UINT_MAX }; static const unsigned int max_key = 18; const unsigned int *cur; unsigned int key, len, i, idx; bool success; cur = input; for (i = 0; cur[0] != UINT_MAX; i++) { for (len = 0; cur[len] != UINT_MAX; len++) ; for (key = 0; key < max_key; key++) { if (bsearch_insert_pos(&key, cur, len, sizeof(*cur), cmp_uint, &idx)) success = cur[idx] == key; else if (idx == 0) success = cur[0] > key; else if (idx == len) success = cur[len-1] < key; else { success = cur[idx-1] < key && cur[idx+1] > key; } if (!success) break; } cur += len + 1; test_out(t_strdup_printf("bsearch_insert_pos(%d,%d)", i, key), success); } } dovecot-2.3.21.1/src/lib/test-istream-base64-decoder.c0000644000000000000000000002333114656633576017126 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str.h" #include "istream-private.h" #include "istream-base64.h" #include "istream-sized.h" #include "base64.h" struct base64_istream_test { const char *input; const char *output; int stream_errno; }; static const struct base64_istream_test base64_tests[] = { { "", "", 0 }, { "aGVsbG8gd29ybGQ=", "hello world", 0 }, { "\naGVs\nbG8g\nd29y\nbGQ=\n", "hello world", 0 }, { " aGVs \r\n bG8g \r\n d29y \t \r\n bGQ= \r\n\r\n", "hello world", 0 }, { "0JPQvtCy0L7RgNGPzIHRgiwg0YfRgtC+INC60YPRgCDQtNC+0Y/MgdGCLg==", "\xd0\x93\xd0\xbe\xd0\xb2\xd0\xbe\xd1\x80\xd1\x8f\xcc" "\x81\xd1\x82\x2c\x20\xd1\x87\xd1\x82\xd0\xbe\x20\xd0" "\xba\xd1\x83\xd1\x80\x20\xd0\xb4\xd0\xbe\xd1\x8f\xcc" "\x81\xd1\x82\x2e", 0 }, { "\r", "", 0 }, { "\n", "", 0 }, { "\r\n", "", 0 }, { " ", "", 0 }, { "foo", "\x7e\x8a", EPIPE }, { "foo ","\x7e\x8a", EPIPE }, { "Zm9vC", "foo", EPIPE }, { "Zm9v!", "foo", EINVAL }, { "Zm9!v", "fo", EINVAL }, { "Zm9 v", "foo", 0 }, { "Zm 9v", "foo", 0 }, { "Z m9v", "foo", 0 }, }; static const struct base64_istream_test base64url_tests[] = { { "", "", 0 }, { "aGVsbG8gd29ybGQ=", "hello world", 0 }, { "\naGVs\nbG8g\nd29y\nbGQ=\n", "hello world", 0 }, { " aGVs \r\n bG8g \r\n d29y \t \r\n bGQ= \r\n\r\n", "hello world", 0 }, { "0JPQvtCy0L7RgNGPzIHRgiwg0YfRgtC-INC60YPRgCDQtNC-0Y_MgdGCLg==", "\xd0\x93\xd0\xbe\xd0\xb2\xd0\xbe\xd1\x80\xd1\x8f\xcc" "\x81\xd1\x82\x2c\x20\xd1\x87\xd1\x82\xd0\xbe\x20\xd0" "\xba\xd1\x83\xd1\x80\x20\xd0\xb4\xd0\xbe\xd1\x8f\xcc" "\x81\xd1\x82\x2e", 0 }, { "\r", "", 0 }, { "\n", "", 0 }, { "\r\n", "", 0 }, { " ", "", 0 }, { "foo", "\x7e\x8a", EPIPE }, { "foo ","\x7e\x8a", EPIPE }, { "Zm9vC", "foo", EPIPE }, { "Zm9v!", "foo", EINVAL }, { "Zm9!v", "fo", EINVAL }, { "Zm9 v", "foo", 0 }, { "Zm 9v", "foo", 0 }, { "Z m9v", "foo", 0 }, }; static void decode_test(unsigned int base64_input_len, struct istream *input_data, struct istream *input, const char *output, int stream_errno) { const unsigned char *data; size_t i, size; int ret = 0; for (i = 1; i <= base64_input_len; i++) { test_istream_set_size(input_data, i); while ((ret = i_stream_read(input)) > 0) ; if (ret == -1 && stream_errno != 0) break; test_assert(ret == 0); } if (ret == 0) { test_istream_set_allow_eof(input_data, TRUE); while ((ret = i_stream_read(input)) > 0) ; } test_assert(ret == -1); test_assert(input->stream_errno == stream_errno); data = i_stream_get_data(input, &size); test_assert(size == strlen(output)); if (size > 0) test_assert(memcmp(data, output, size) == 0); } static void decode_base64_test(const char *base64_input, const char *output, int stream_errno) { unsigned int base64_input_len = strlen(base64_input); struct istream *input_data, *input; input_data = test_istream_create_data(base64_input, base64_input_len); test_istream_set_allow_eof(input_data, FALSE); input = i_stream_create_base64_decoder(input_data); decode_test(base64_input_len, input_data, input, output, stream_errno); i_stream_unref(&input); i_stream_unref(&input_data); } static void decode_base64url_test(const char *base64_input, const char *output, int stream_errno) { unsigned int base64_input_len = strlen(base64_input); struct istream *input_data, *input; input_data = test_istream_create_data(base64_input, base64_input_len); test_istream_set_allow_eof(input_data, FALSE); input = i_stream_create_base64url_decoder(input_data); decode_test(base64_input_len, input_data, input, output, stream_errno); i_stream_unref(&input); i_stream_unref(&input_data); } static void test_istream_base64_io_random(void) { unsigned char in_buf[2048]; size_t in_buf_size; buffer_t *out_buf; unsigned int i, j; int ret; out_buf = t_buffer_create(sizeof(in_buf)); test_begin("istream base64 random I/O"); for (i = 0; !test_has_failed() && i < 4000; i++) { struct istream *input1, *input2, *input3, *input4, *input5; struct istream *sinput1, *sinput2, *sinput3, *sinput4; struct istream *top_input; const unsigned char *data; unsigned int chpl1, chpl2; unsigned int sized_streams; unsigned int crlf_encode; size_t size; struct base64_encoder b64enc; /* Initialize test data */ in_buf_size = i_rand_limit(sizeof(in_buf)); for (j = 0; j < in_buf_size; j++) in_buf[j] = i_rand_uchar(); /* Reset final output buffer */ buffer_set_used_size(out_buf, 0); /* Determine line lengths */ chpl1 = i_rand_limit(30)*4; chpl2 = i_rand_limit(30)*4; /* Create stream for test data */ input1 = i_stream_create_from_data(in_buf, in_buf_size); i_stream_set_name(input1, "[data]"); /* Determine which stages have sized streams */ sized_streams = i_rand_minmax(0x00, 0x0f); /* Determine which stages use CRLF */ crlf_encode = i_rand_minmax(0x00, 0x03); /* Create first encoder stream */ input2 = i_stream_create_base64_encoder( input1, chpl1, HAS_ALL_BITS(crlf_encode, BIT(0))); i_stream_set_name(input2, "[base64_encoder #1]"); if (HAS_ALL_BITS(sized_streams, BIT(0))) { /* Wrap the first encoder stream in a sized stream to check size and trigger any buffer overflow problems */ base64_encode_init(&b64enc, &base64_scheme, (HAS_ALL_BITS(crlf_encode, BIT(0)) ? BASE64_ENCODE_FLAG_CRLF : 0), chpl1); sinput1 = i_stream_create_sized(input2, base64_get_full_encoded_size(&b64enc, in_buf_size)); i_stream_set_name(sinput1, "[sized #1]"); } else { sinput1 = input2; i_stream_ref(sinput1); } /* Create first decoder stream */ input3 = i_stream_create_base64_decoder(sinput1); i_stream_set_name(input3, "[base64_decoder #1]"); if (HAS_ALL_BITS(sized_streams, BIT(1))) { /* Wrap the first decoder stream in a sized stream to check size and trigger any buffer overflow problems */ sinput2 = i_stream_create_sized(input3, in_buf_size); i_stream_set_name(sinput2, "[sized #2]"); } else { sinput2 = input3; i_stream_ref(sinput2); } /* Create second encoder stream */ input4 = i_stream_create_base64_encoder( sinput2, chpl2, HAS_ALL_BITS(crlf_encode, BIT(1))); i_stream_set_name(input4, "[base64_encoder #2]"); if (HAS_ALL_BITS(sized_streams, BIT(2))) { /* Wrap the second encoder stream in a sized stream to check size and trigger any buffer overflow problems */ base64_encode_init(&b64enc, &base64_scheme, (HAS_ALL_BITS(crlf_encode, BIT(1)) ? BASE64_ENCODE_FLAG_CRLF : 0), chpl2); sinput3 = i_stream_create_sized(input4, base64_get_full_encoded_size(&b64enc, in_buf_size)); i_stream_set_name(sinput3, "[sized #3]"); } else { sinput3 = input4; i_stream_ref(sinput3); } /* Create second deoder stream */ input5 = i_stream_create_base64_decoder(sinput3); i_stream_set_name(input5, "[base64_decoder #2]"); if (HAS_ALL_BITS(sized_streams, BIT(3))) { /* Wrap the second decoder stream in a sized stream to check size and trigger any buffer overflow problems */ sinput4 = i_stream_create_sized(input5, in_buf_size); i_stream_set_name(sinput4, "[sized #4]"); } else { sinput4 = input5; i_stream_ref(sinput4); } /* Assign random buffer sizes */ i_stream_set_max_buffer_size(input5, i_rand_minmax(1, 512)); i_stream_set_max_buffer_size(input4, i_rand_minmax(1, 512)); i_stream_set_max_buffer_size(input3, i_rand_minmax(1, 512)); i_stream_set_max_buffer_size(input2, i_rand_minmax(1, 512)); /* Read the outer stream in full with random increments. */ top_input = sinput4; while ((ret = i_stream_read_more( top_input, &data, &size)) > 0) { size_t ch = i_rand_limit(512); size = I_MIN(size, ch); buffer_append(out_buf, data, size); i_stream_skip(top_input, size); } if (ret < 0 && top_input->stream_errno == 0) { data = i_stream_get_data(top_input, &size); if (size > 0) { buffer_append(out_buf, data, size); i_stream_skip(top_input, size); } } /* Assert stream status */ test_assert_idx(ret < 0 && top_input->stream_errno == 0, i); /* Assert input/output equality */ test_assert_idx(out_buf->used == in_buf_size && memcmp(in_buf, out_buf->data, in_buf_size) == 0, i); if (top_input->stream_errno != 0) { i_error("%s: %s", i_stream_get_name(input1), i_stream_get_error(input1)); i_error("%s: %s", i_stream_get_name(input2), i_stream_get_error(input2)); i_error("%s: %s", i_stream_get_name(input3), i_stream_get_error(input3)); i_error("%s: %s", i_stream_get_name(input4), i_stream_get_error(input4)); i_error("%s: %s", i_stream_get_name(input5), i_stream_get_error(input5)); } if (test_has_failed()) { i_info("Test parameters: size=%zu " "line_length_1=%u line_length_2=%u", in_buf_size, chpl1, chpl2); } /* Clean up */ i_stream_unref(&input1); i_stream_unref(&input2); i_stream_unref(&input3); i_stream_unref(&input4); i_stream_unref(&input5); i_stream_unref(&sinput1); i_stream_unref(&sinput2); i_stream_unref(&sinput3); i_stream_unref(&sinput4); } test_end(); } void test_istream_base64_decoder(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(base64_tests); i++) { const struct base64_istream_test *test = &base64_tests[i]; test_begin(t_strdup_printf("istream base64 decoder %u", i+1)); decode_base64_test(test->input, test->output, test->stream_errno); test_end(); } for (i = 0; i < N_ELEMENTS(base64url_tests); i++) { const struct base64_istream_test *test = &base64url_tests[i]; test_begin(t_strdup_printf("istream base64url decoder %u", i+1)); decode_base64url_test(test->input, test->output, test->stream_errno); test_end(); } test_istream_base64_io_random(); } dovecot-2.3.21.1/src/lib/ostream-file.c0000644000000000000000000007434514656633576014422 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ /* @UNSAFE: whole file */ #include "lib.h" #include "ioloop.h" #include "write-full.h" #include "net.h" #include "sendfile-util.h" #include "istream.h" #include "istream-private.h" #include "ostream-file-private.h" #include #include #ifdef HAVE_SYS_UIO_H # include #endif #include /* try to keep the buffer size within 4k..128k. ReiserFS may actually return 128k as optimal size. */ #define DEFAULT_OPTIMAL_BLOCK_SIZE IO_BLOCK_SIZE #define MAX_OPTIMAL_BLOCK_SIZE (128*1024) #define IS_STREAM_EMPTY(fstream) \ ((fstream)->head == (fstream)->tail && !(fstream)->full) #define MAX_SSIZE_T(size) \ ((size) < SSIZE_T_MAX ? (size_t)(size) : SSIZE_T_MAX) static void stream_send_io(struct file_ostream *fstream); static struct ostream * o_stream_create_fd_common(int fd, size_t max_buffer_size, bool autoclose_fd); static void stream_closed(struct file_ostream *fstream) { io_remove(&fstream->io); if (fstream->autoclose_fd && fstream->fd != -1) { /* Ignore ECONNRESET because we don't really care about it here, as we are closing the socket down in any case. There might be unsent data but nothing we can do about that. */ if (unlikely(close(fstream->fd) < 0 && errno != ECONNRESET)) { i_error("file_ostream.close(%s) failed: %m", o_stream_get_name(&fstream->ostream.ostream)); } } fstream->fd = -1; fstream->ostream.ostream.closed = TRUE; } void o_stream_file_close(struct iostream_private *stream, bool close_parent ATTR_UNUSED) { struct file_ostream *fstream = container_of(stream, struct file_ostream, ostream.iostream); stream_closed(fstream); } static void o_stream_file_destroy(struct iostream_private *stream) { struct file_ostream *fstream = container_of(stream, struct file_ostream, ostream.iostream); i_free(fstream->buffer); } static size_t file_buffer_get_used_size(struct file_ostream *fstream) { if (fstream->head == fstream->tail) return fstream->full ? fstream->buffer_size : 0; else if (fstream->head < fstream->tail) { /* ...HXXXT... */ return fstream->tail - fstream->head; } else { /* XXXT...HXXX */ return fstream->tail + (fstream->buffer_size - fstream->head); } } static void update_buffer(struct file_ostream *fstream, size_t size) { size_t used; if (IS_STREAM_EMPTY(fstream) || size == 0) return; if (fstream->head < fstream->tail) { /* ...HXXXT... */ used = fstream->tail - fstream->head; i_assert(size <= used); fstream->head += size; } else { /* XXXT...HXXX */ used = fstream->buffer_size - fstream->head; if (size > used) { size -= used; i_assert(size <= fstream->tail); fstream->head = size; } else { fstream->head += size; } fstream->full = FALSE; } if (fstream->head == fstream->tail) fstream->head = fstream->tail = 0; if (fstream->head == fstream->buffer_size) fstream->head = 0; } static void o_stream_socket_cork(struct file_ostream *fstream) { if (fstream->ostream.corked && !fstream->socket_cork_set) { if (!fstream->no_socket_cork) { if (net_set_cork(fstream->fd, TRUE) < 0) fstream->no_socket_cork = TRUE; else fstream->socket_cork_set = TRUE; } } } static int o_stream_lseek(struct file_ostream *fstream) { off_t ret; if (fstream->real_offset == fstream->buffer_offset) return 0; ret = lseek(fstream->fd, (off_t)fstream->buffer_offset, SEEK_SET); if (ret < 0) { io_stream_set_error(&fstream->ostream.iostream, "lseek() failed: %m"); fstream->ostream.ostream.stream_errno = errno; return -1; } if (ret != (off_t)fstream->buffer_offset) { io_stream_set_error(&fstream->ostream.iostream, "lseek() returned wrong value"); fstream->ostream.ostream.stream_errno = EINVAL; return -1; } fstream->real_offset = fstream->buffer_offset; return 0; } ssize_t o_stream_file_writev(struct file_ostream *fstream, const struct const_iovec *iov, unsigned int iov_count) { ssize_t ret; size_t size, sent; unsigned int i; if (iov_count == 1) { i_assert(iov->iov_len > 0); if (!fstream->file || fstream->real_offset == fstream->buffer_offset) { ret = write(fstream->fd, iov->iov_base, iov->iov_len); if (ret > 0) fstream->real_offset += ret; } else { ret = pwrite(fstream->fd, iov->iov_base, iov->iov_len, fstream->buffer_offset); } } else { if (o_stream_lseek(fstream) < 0) return -1; sent = 0; while (iov_count > IOV_MAX) { size = 0; for (i = 0; i < IOV_MAX; i++) size += iov[i].iov_len; ret = writev(fstream->fd, (const struct iovec *)iov, IOV_MAX); if (ret != (ssize_t)size) { break; } fstream->real_offset += ret; fstream->buffer_offset += ret; sent += ret; iov += IOV_MAX; iov_count -= IOV_MAX; } if (iov_count <= IOV_MAX) { size = 0; for (i = 0; i < iov_count; i++) size += iov[i].iov_len; ret = writev(fstream->fd, (const struct iovec *)iov, iov_count); } if (ret > 0) { fstream->real_offset += ret; ret += sent; } else if (!fstream->file && sent > 0) { /* return what we managed to get sent */ ret = sent; } } return ret; } static ssize_t o_stream_file_writev_full(struct file_ostream *fstream, const struct const_iovec *iov, unsigned int iov_count) { ssize_t ret, ret2; size_t size, total_size; bool partial; unsigned int i; for (i = 0, total_size = 0; i < iov_count; i++) total_size += iov[i].iov_len; o_stream_socket_cork(fstream); ret = fstream->writev(fstream, iov, iov_count); partial = ret != (ssize_t)total_size; if (ret < 0) { if (fstream->file) { if (errno == EINTR) { /* automatically retry */ return o_stream_file_writev_full(fstream, iov, iov_count); } } else if (errno == EAGAIN || errno == EINTR) { /* try again later */ return 0; } fstream->ostream.ostream.stream_errno = errno; stream_closed(fstream); return -1; } if (unlikely(ret == 0 && fstream->file)) { /* assume out of disk space */ fstream->ostream.ostream.stream_errno = ENOSPC; stream_closed(fstream); return -1; } fstream->buffer_offset += ret; if (partial && fstream->file) { /* we failed to write everything to a file. either we ran out of disk space or we're writing to NFS. try to write the rest to resolve this. */ size = ret; while (iov_count > 0 && size >= iov->iov_len) { size -= iov->iov_len; iov++; iov_count--; } i_assert(iov_count > 0); if (size == 0) ret2 = o_stream_file_writev_full(fstream, iov, iov_count); else { /* write the first iov separately */ struct const_iovec new_iov; new_iov.iov_base = CONST_PTR_OFFSET(iov->iov_base, size); new_iov.iov_len = iov->iov_len - size; ret2 = o_stream_file_writev_full(fstream, &new_iov, 1); if (ret2 > 0) { i_assert((size_t)ret2 == new_iov.iov_len); /* write the rest */ if (iov_count > 1) { ret += ret2; ret2 = o_stream_file_writev_full(fstream, iov + 1, iov_count - 1); } } } i_assert(ret2 != 0); if (ret2 < 0) ret = ret2; else ret += ret2; } i_assert(ret < 0 || !fstream->file || (size_t)ret == total_size); return ret; } /* returns how much of vector was used */ static int o_stream_fill_iovec(struct file_ostream *fstream, struct const_iovec iov[2]) { if (IS_STREAM_EMPTY(fstream)) return 0; if (fstream->head < fstream->tail) { iov[0].iov_base = fstream->buffer + fstream->head; iov[0].iov_len = fstream->tail - fstream->head; return 1; } else { iov[0].iov_base = fstream->buffer + fstream->head; iov[0].iov_len = fstream->buffer_size - fstream->head; if (fstream->tail == 0) return 1; else { iov[1].iov_base = fstream->buffer; iov[1].iov_len = fstream->tail; return 2; } } } static int buffer_flush(struct file_ostream *fstream) { struct const_iovec iov[2]; int iov_len; ssize_t ret; iov_len = o_stream_fill_iovec(fstream, iov); if (iov_len > 0) { ret = o_stream_file_writev_full(fstream, iov, iov_len); if (ret < 0) return -1; update_buffer(fstream, ret); } return IS_STREAM_EMPTY(fstream) ? 1 : 0; } static void o_stream_tcp_flush_via_nodelay(struct file_ostream *fstream) { if (net_set_tcp_nodelay(fstream->fd, TRUE) < 0) { /* Don't bother logging errors. There are quite a lot of different errors that need to be ignored, and it differs between OSes. At least: Linux: ENOTSUP, ENOTSOCK, ENOPROTOOPT FreeBSD: EINVAL, ECONNRESET */ fstream->no_socket_nodelay = TRUE; } else if (net_set_tcp_nodelay(fstream->fd, FALSE) < 0) { /* We already successfully enabled TCP_NODELAY, so there shouldn't really be errors. Except ECONNRESET can possibly still happen between these two calls, so again don't log errors. */ fstream->no_socket_nodelay = TRUE; } } static void o_stream_file_cork(struct ostream_private *stream, bool set) { struct file_ostream *fstream = container_of(stream, struct file_ostream, ostream); struct iostream_private *iostream = &fstream->ostream.iostream; int ret; if (stream->corked != set && !stream->ostream.closed) { if (set && fstream->io != NULL) io_remove(&fstream->io); else if (!set) { /* buffer flushing might close the stream */ ret = buffer_flush(fstream); stream->last_errors_not_checked = TRUE; if (fstream->io == NULL && (ret == 0 || fstream->flush_pending) && !stream->ostream.closed) { fstream->io = io_add_to( io_stream_get_ioloop(iostream), fstream->fd, IO_WRITE, stream_send_io, fstream); } } if (stream->ostream.closed) { /* flushing may have closed the stream already */ return; } if (fstream->socket_cork_set) { i_assert(!set); if (net_set_cork(fstream->fd, FALSE) < 0) fstream->no_socket_cork = TRUE; fstream->socket_cork_set = FALSE; } if (!set && !fstream->no_socket_nodelay) { /* Uncorking - send all the pending data immediately. Remove nodelay immediately afterwards, so if any output is sent outside corking it may get delayed. */ o_stream_tcp_flush_via_nodelay(fstream); } if (!set && !fstream->no_socket_quickack) { /* Uncorking - disable delayed ACKs to reduce latency. Note that this needs to be set repeatedly. */ if (net_set_tcp_quickack(fstream->fd, TRUE) < 0) fstream->no_socket_quickack = TRUE; } stream->corked = set; } } static int o_stream_file_flush(struct ostream_private *stream) { struct file_ostream *fstream = container_of(stream, struct file_ostream, ostream); return buffer_flush(fstream); } static void o_stream_file_flush_pending(struct ostream_private *stream, bool set) { struct file_ostream *fstream = container_of(stream, struct file_ostream, ostream); struct iostream_private *iostream = &fstream->ostream.iostream; fstream->flush_pending = set; if (set && !stream->corked && fstream->io == NULL) { fstream->io = io_add_to(io_stream_get_ioloop(iostream), fstream->fd, IO_WRITE, stream_send_io, fstream); } } static size_t get_unused_space(const struct file_ostream *fstream) { if (fstream->head > fstream->tail) { /* XXXT...HXXX */ return fstream->head - fstream->tail; } else if (fstream->head < fstream->tail) { /* ...HXXXT... */ return (fstream->buffer_size - fstream->tail) + fstream->head; } else { /* either fully unused or fully used */ return fstream->full ? 0 : fstream->buffer_size; } } static size_t o_stream_file_get_buffer_used_size(const struct ostream_private *stream) { const struct file_ostream *fstream = container_of(stream, const struct file_ostream, ostream); return fstream->buffer_size - get_unused_space(fstream); } static int o_stream_file_seek(struct ostream_private *stream, uoff_t offset) { struct file_ostream *fstream = container_of(stream, struct file_ostream, ostream); if (offset > OFF_T_MAX) { stream->ostream.stream_errno = EINVAL; return -1; } if (!fstream->file) { stream->ostream.stream_errno = ESPIPE; return -1; } if (buffer_flush(fstream) < 0) return -1; stream->ostream.offset = offset; fstream->buffer_offset = offset; return 1; } static void o_stream_grow_buffer(struct file_ostream *fstream, size_t bytes) { size_t size, new_size, end_size; size = nearest_power(fstream->buffer_size + bytes); if (size > fstream->ostream.max_buffer_size) { /* limit the size */ size = fstream->ostream.max_buffer_size; } else if (fstream->ostream.corked) { /* try to use optimal buffer size with corking */ new_size = I_MIN(fstream->optimal_block_size, fstream->ostream.max_buffer_size); if (new_size > size) size = new_size; } if (size <= fstream->buffer_size) return; fstream->buffer = i_realloc(fstream->buffer, fstream->buffer_size, size); if (fstream->tail <= fstream->head && !IS_STREAM_EMPTY(fstream)) { /* move head forward to end of buffer */ end_size = fstream->buffer_size - fstream->head; memmove(fstream->buffer + size - end_size, fstream->buffer + fstream->head, end_size); fstream->head = size - end_size; } fstream->full = FALSE; fstream->buffer_size = size; } static void stream_send_io(struct file_ostream *fstream) { struct ostream *ostream = &fstream->ostream.ostream; struct iostream_private *iostream = &fstream->ostream.iostream; bool use_cork = !fstream->ostream.corked; int ret; /* Set flush_pending = FALSE first before calling the flush callback, and change it to TRUE only if callback returns 0. That way the callback can call o_stream_set_flush_pending() again and we don't forget it even if flush callback returns 1. */ fstream->flush_pending = FALSE; o_stream_ref(ostream); if (use_cork) o_stream_cork(ostream); if (fstream->ostream.callback != NULL) ret = fstream->ostream.callback(fstream->ostream.context); else ret = o_stream_file_flush(&fstream->ostream); if (use_cork) o_stream_uncork(ostream); if (ret == 0) fstream->flush_pending = TRUE; if (!fstream->flush_pending && IS_STREAM_EMPTY(fstream)) { io_remove(&fstream->io); } else if (!fstream->ostream.ostream.closed) { /* Add the IO handler if it's not there already. Callback might have just returned 0 without there being any data to be sent. */ if (fstream->io == NULL) { fstream->io = io_add_to(io_stream_get_ioloop(iostream), fstream->fd, IO_WRITE, stream_send_io, fstream); } } o_stream_unref(&ostream); } static size_t o_stream_add(struct file_ostream *fstream, const void *data, size_t size) { struct iostream_private *iostream = &fstream->ostream.iostream; size_t unused, sent; int i; unused = get_unused_space(fstream); if (unused < size) o_stream_grow_buffer(fstream, size-unused); sent = 0; for (i = 0; i < 2 && sent < size && !fstream->full; i++) { unused = fstream->tail >= fstream->head ? fstream->buffer_size - fstream->tail : fstream->head - fstream->tail; if (unused > size-sent) unused = size-sent; memcpy(fstream->buffer + fstream->tail, CONST_PTR_OFFSET(data, sent), unused); sent += unused; fstream->tail += unused; if (fstream->tail == fstream->buffer_size) fstream->tail = 0; if (fstream->head == fstream->tail && fstream->buffer_size > 0) fstream->full = TRUE; } if (sent != 0 && fstream->io == NULL && !fstream->ostream.corked && !fstream->file) { fstream->io = io_add_to(io_stream_get_ioloop(iostream), fstream->fd, IO_WRITE, stream_send_io, fstream); } return sent; } ssize_t o_stream_file_sendv(struct ostream_private *stream, const struct const_iovec *iov, unsigned int iov_count) { struct file_ostream *fstream = container_of(stream, struct file_ostream, ostream); size_t size, total_size, added, optimal_size; unsigned int i; ssize_t ret = 0; for (i = 0, size = 0; i < iov_count; i++) size += iov[i].iov_len; total_size = size; if (size > get_unused_space(fstream) && !IS_STREAM_EMPTY(fstream)) { if (o_stream_file_flush(stream) < 0) return -1; } optimal_size = I_MIN(fstream->optimal_block_size, fstream->ostream.max_buffer_size); if (IS_STREAM_EMPTY(fstream) && (!stream->corked || size >= optimal_size)) { /* send immediately */ ret = o_stream_file_writev_full(fstream, iov, iov_count); if (ret < 0) return -1; size = ret; while (size > 0 && iov_count > 0 && size >= iov[0].iov_len) { size -= iov[0].iov_len; iov++; iov_count--; } if (iov_count == 0) i_assert(size == 0); else { added = o_stream_add(fstream, CONST_PTR_OFFSET(iov[0].iov_base, size), iov[0].iov_len - size); ret += added; if (added != iov[0].iov_len - size) { /* buffer full */ stream->ostream.offset += ret; return ret; } iov++; iov_count--; } } /* buffer it, at least partly */ for (i = 0; i < iov_count; i++) { added = o_stream_add(fstream, iov[i].iov_base, iov[i].iov_len); ret += added; if (added != iov[i].iov_len) break; } stream->ostream.offset += ret; i_assert((size_t)ret <= total_size); i_assert((size_t)ret == total_size || !fstream->file); return ret; } static size_t o_stream_file_update_buffer(struct file_ostream *fstream, const void *data, size_t size, size_t pos) { size_t avail, copy_size; if (fstream->head < fstream->tail) { /* ...HXXXT... */ i_assert(pos < fstream->tail); avail = fstream->tail - pos; } else { /* XXXT...HXXX */ avail = fstream->buffer_size - pos; } copy_size = I_MIN(size, avail); memcpy(fstream->buffer + pos, data, copy_size); data = CONST_PTR_OFFSET(data, copy_size); size -= copy_size; if (size > 0 && fstream->head >= fstream->tail) { /* wraps to beginning of the buffer */ copy_size = I_MIN(size, fstream->tail); memcpy(fstream->buffer, data, copy_size); size -= copy_size; } return size; } static int o_stream_file_write_at(struct ostream_private *stream, const void *data, size_t size, uoff_t offset) { struct file_ostream *fstream = container_of(stream, struct file_ostream, ostream); size_t used, pos, skip, left; /* update buffer if the write overlaps it */ used = file_buffer_get_used_size(fstream); if (used > 0 && fstream->buffer_offset < offset + size && fstream->buffer_offset + used > offset) { if (fstream->buffer_offset <= offset) { /* updating from the beginning */ skip = 0; } else { skip = fstream->buffer_offset - offset; } pos = (fstream->head + offset + skip - fstream->buffer_offset) % fstream->buffer_size; left = o_stream_file_update_buffer(fstream, CONST_PTR_OFFSET(data, skip), size - skip, pos); if (left > 0) { /* didn't write all of it */ if (skip > 0) { /* we also have to write a prefix. don't bother with two syscalls, just write all of it in one pwrite(). */ } else { /* write only the suffix */ size_t update_count = size - left; data = CONST_PTR_OFFSET(data, update_count); size -= update_count; offset += update_count; } } else if (skip == 0) { /* everything done */ return 0; } else { /* still have to write prefix */ size = skip; } } /* we couldn't write everything to the buffer. flush the buffer and pwrite() the rest. */ if (o_stream_file_flush(stream) < 0) return -1; if (pwrite_full(fstream->fd, data, size, offset) < 0) { stream->ostream.stream_errno = errno; stream_closed(fstream); return -1; } return 0; } static bool io_stream_sendfile(struct ostream_private *outstream, struct istream *instream, int in_fd, enum ostream_send_istream_result *res_r) { struct file_ostream *foutstream = container_of(outstream, struct file_ostream, ostream); uoff_t in_size, offset, send_size, v_offset, abs_start_offset; ssize_t ret; bool sendfile_not_supported = FALSE; if ((ret = i_stream_get_size(instream, TRUE, &in_size)) < 0) { *res_r = OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT; return TRUE; } if (ret == 0) { /* size unknown. we can't use sendfile(). */ return FALSE; } o_stream_socket_cork(foutstream); /* flush out any data in buffer */ if ((ret = buffer_flush(foutstream)) < 0) { *res_r = OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT; return TRUE; } else if (ret == 0) { *res_r = OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT; return TRUE; } if (o_stream_lseek(foutstream) < 0) { *res_r = OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT; return TRUE; } v_offset = instream->v_offset; abs_start_offset = i_stream_get_absolute_offset(instream) - v_offset; while (v_offset < in_size) { offset = abs_start_offset + v_offset; send_size = in_size - v_offset; ret = safe_sendfile(foutstream->fd, in_fd, &offset, MAX_SSIZE_T(send_size)); if (ret <= 0) { if (ret == 0) { /* Unexpectedly early EOF at input */ i_stream_seek(instream, v_offset); instream->eof = TRUE; *res_r = OSTREAM_SEND_ISTREAM_RESULT_FINISHED; return TRUE; } if (foutstream->file) { if (errno == EINTR) { /* automatically retry */ continue; } } else { if (errno == EINTR || errno == EAGAIN) { ret = 0; break; } } if (errno == EINVAL) sendfile_not_supported = TRUE; else { io_stream_set_error(&outstream->iostream, "sendfile() failed: %m"); outstream->ostream.stream_errno = errno; /* close only if error wasn't because sendfile() isn't supported */ stream_closed(foutstream); } break; } v_offset += ret; foutstream->real_offset += ret; foutstream->buffer_offset += ret; outstream->ostream.offset += ret; } i_stream_seek(instream, v_offset); if (v_offset == in_size) { instream->eof = TRUE; *res_r = OSTREAM_SEND_ISTREAM_RESULT_FINISHED; return TRUE; } i_assert(ret <= 0); if (sendfile_not_supported) return FALSE; if (ret < 0) *res_r = OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT; else *res_r = OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT; return TRUE; } static enum ostream_send_istream_result io_stream_copy_backwards(struct ostream_private *outstream, struct istream *instream, uoff_t in_size) { struct file_ostream *foutstream = container_of(outstream, struct file_ostream, ostream); uoff_t in_start_offset, in_offset, in_limit, out_offset; const unsigned char *data; size_t buffer_size, size, read_size; ssize_t ret; i_assert(IS_STREAM_EMPTY(foutstream)); /* figure out optimal buffer size */ buffer_size = instream->real_stream->buffer_size; if (buffer_size == 0 || buffer_size > foutstream->buffer_size) { if (foutstream->optimal_block_size > foutstream->buffer_size) { o_stream_grow_buffer(foutstream, foutstream->optimal_block_size - foutstream->buffer_size); } buffer_size = foutstream->buffer_size; } in_start_offset = instream->v_offset; in_offset = in_limit = in_size; out_offset = outstream->ostream.offset + (in_offset - in_start_offset); while (in_offset > in_start_offset) { if (in_offset - in_start_offset <= buffer_size) read_size = in_offset - in_start_offset; else read_size = buffer_size; in_offset -= read_size; out_offset -= read_size; for (;;) { i_assert(in_offset <= in_limit); i_stream_seek(instream, in_offset); read_size = in_limit - in_offset; /* FIXME: something's wrong here */ if (i_stream_read_bytes(instream, &data, &size, read_size) == 0) i_unreached(); if (size >= read_size) { size = read_size; if (instream->mmaped) { /* we'll have to write it through buffer or the file gets corrupted */ i_assert(size <= foutstream->buffer_size); memcpy(foutstream->buffer, data, size); data = foutstream->buffer; } break; } /* buffer too large probably, try with smaller */ read_size -= size; in_offset += read_size; out_offset += read_size; buffer_size -= read_size; } in_limit -= size; ret = pwrite_full(foutstream->fd, data, size, out_offset); if (ret < 0) { /* error */ outstream->ostream.stream_errno = errno; return OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT; } i_stream_skip(instream, size); } /* make it visible that we're at instream's EOF */ i_stream_seek(instream, in_size); instream->eof = TRUE; outstream->ostream.offset += in_size - in_start_offset; return OSTREAM_SEND_ISTREAM_RESULT_FINISHED; } static enum ostream_send_istream_result io_stream_copy_same_stream(struct ostream_private *outstream, struct istream *instream) { uoff_t in_size; off_t in_abs_offset, ret = 0; /* copying data within same fd. we'll have to be careful with seeks and overlapping writes. */ if ((ret = i_stream_get_size(instream, TRUE, &in_size)) < 0) return OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT; if (ret == 0) { /* if we couldn't find out the size, it means that instream isn't a regular file_istream. we can be reasonably sure that we can copy it safely the regular way. (there's really no other possibility, other than failing completely.) */ return io_stream_copy(&outstream->ostream, instream); } i_assert(instream->v_offset <= in_size); in_abs_offset = i_stream_get_absolute_offset(instream); ret = (off_t)outstream->ostream.offset - in_abs_offset; if (ret == 0) { /* copying data over itself. we don't really need to do that, just fake it. */ return OSTREAM_SEND_ISTREAM_RESULT_FINISHED; } if (ret > 0 && in_size > (uoff_t)ret) { /* overlapping */ i_assert(instream->seekable); return io_stream_copy_backwards(outstream, instream, in_size); } else { /* non-overlapping */ return io_stream_copy(&outstream->ostream, instream); } } static enum ostream_send_istream_result o_stream_file_send_istream(struct ostream_private *outstream, struct istream *instream) { struct file_ostream *foutstream = container_of(outstream, struct file_ostream, ostream); bool same_stream; int in_fd; enum ostream_send_istream_result res; in_fd = !instream->readable_fd ? -1 : i_stream_get_fd(instream); if (!foutstream->no_sendfile && in_fd != -1 && in_fd != foutstream->fd && instream->seekable) { if (io_stream_sendfile(outstream, instream, in_fd, &res)) return res; /* sendfile() not supported (with this fd), fallback to regular sending. */ foutstream->no_sendfile = TRUE; } same_stream = i_stream_get_fd(instream) == foutstream->fd && foutstream->fd != -1; if (!same_stream) return io_stream_copy(&outstream->ostream, instream); return io_stream_copy_same_stream(outstream, instream); } static void o_stream_file_switch_ioloop_to(struct ostream_private *stream, struct ioloop *ioloop) { struct file_ostream *fstream = container_of(stream, struct file_ostream, ostream); if (fstream->io != NULL) fstream->io = io_loop_move_io_to(ioloop, &fstream->io); } struct ostream * o_stream_create_file_common(struct file_ostream *fstream, int fd, size_t max_buffer_size, bool autoclose_fd) { struct ostream *ostream; fstream->fd = fd; fstream->autoclose_fd = autoclose_fd; fstream->optimal_block_size = DEFAULT_OPTIMAL_BLOCK_SIZE; fstream->ostream.iostream.close = o_stream_file_close; fstream->ostream.iostream.destroy = o_stream_file_destroy; fstream->ostream.cork = o_stream_file_cork; fstream->ostream.flush = o_stream_file_flush; fstream->ostream.flush_pending = o_stream_file_flush_pending; fstream->ostream.get_buffer_used_size = o_stream_file_get_buffer_used_size; fstream->ostream.seek = o_stream_file_seek; fstream->ostream.sendv = o_stream_file_sendv; fstream->ostream.write_at = o_stream_file_write_at; fstream->ostream.send_istream = o_stream_file_send_istream; fstream->ostream.switch_ioloop_to = o_stream_file_switch_ioloop_to; fstream->writev = o_stream_file_writev; fstream->ostream.max_buffer_size = max_buffer_size; ostream = o_stream_create(&fstream->ostream, NULL, fd); if (max_buffer_size == 0) fstream->ostream.max_buffer_size = fstream->optimal_block_size; return ostream; } static void fstream_init_file(struct file_ostream *fstream) { struct stat st; fstream->no_sendfile = TRUE; if (fstat(fstream->fd, &st) < 0) return; if ((uoff_t)st.st_blksize > fstream->optimal_block_size) { /* use the optimal block size, but with a reasonable limit */ fstream->optimal_block_size = I_MIN(st.st_blksize, MAX_OPTIMAL_BLOCK_SIZE); } if (S_ISREG(st.st_mode)) { fstream->no_socket_cork = TRUE; fstream->no_socket_nodelay = TRUE; fstream->no_socket_quickack = TRUE; fstream->file = TRUE; } } static struct ostream * o_stream_create_fd_common(int fd, size_t max_buffer_size, bool autoclose_fd) { struct file_ostream *fstream; struct ostream *ostream; off_t offset; fstream = i_new(struct file_ostream, 1); ostream = o_stream_create_file_common (fstream, fd, max_buffer_size, autoclose_fd); offset = lseek(fd, 0, SEEK_CUR); if (offset >= 0) { ostream->offset = offset; fstream->real_offset = offset; fstream->buffer_offset = offset; fstream_init_file(fstream); } else { struct ip_addr local_ip; if (net_getsockname(fd, &local_ip, NULL) < 0) { /* not a socket */ fstream->no_sendfile = TRUE; fstream->no_socket_cork = TRUE; fstream->no_socket_nodelay = TRUE; fstream->no_socket_quickack = TRUE; } else if (local_ip.family == 0) { /* UNIX domain socket */ fstream->no_socket_cork = TRUE; fstream->no_socket_nodelay = TRUE; fstream->no_socket_quickack = TRUE; } } return ostream; } struct ostream * o_stream_create_fd(int fd, size_t max_buffer_size) { return o_stream_create_fd_common(fd, max_buffer_size, FALSE); } struct ostream * o_stream_create_fd_autoclose(int *fd, size_t max_buffer_size) { struct ostream *ostream = o_stream_create_fd_common(*fd, max_buffer_size, TRUE); *fd = -1; return ostream; } struct ostream * o_stream_create_fd_file(int fd, uoff_t offset, bool autoclose_fd) { struct file_ostream *fstream; struct ostream *ostream; if (offset == UOFF_T_MAX) offset = lseek(fd, 0, SEEK_CUR); fstream = i_new(struct file_ostream, 1); ostream = o_stream_create_file_common(fstream, fd, 0, autoclose_fd); fstream_init_file(fstream); fstream->real_offset = offset; fstream->buffer_offset = offset; ostream->blocking = fstream->file; ostream->offset = offset; return ostream; } struct ostream *o_stream_create_fd_file_autoclose(int *fd, uoff_t offset) { struct ostream *output; output = o_stream_create_fd_file(*fd, offset, TRUE); *fd = -1; return output; } struct ostream *o_stream_create_file(const char *path, uoff_t offset, mode_t mode, enum ostream_create_file_flags flags) { int fd; int open_flags = O_WRONLY|O_CREAT; if (HAS_ANY_BITS(flags, OSTREAM_CREATE_FILE_FLAG_APPEND)) open_flags |= O_APPEND; else open_flags |= O_TRUNC; if ((fd = open(path, open_flags, mode)) < 0) return o_stream_create_error(errno); return o_stream_create_fd_file_autoclose(&fd, offset); } dovecot-2.3.21.1/src/lib/istream-base64-encoder.c0000644000000000000000000001356514656633576016173 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "base64.h" #include "istream-private.h" #include "istream-base64.h" struct base64_encoder_istream { struct istream_private istream; struct base64_encoder encoder; }; static int i_stream_read_parent(struct istream_private *stream) { size_t size; ssize_t ret; size = i_stream_get_data_size(stream->parent); if (size > 0) return 1; ret = i_stream_read_memarea(stream->parent); if (ret <= 0) { stream->istream.stream_errno = stream->parent->stream_errno; return ret; } size = i_stream_get_data_size(stream->parent); i_assert(size != 0); return 1; } static int i_stream_base64_try_encode(struct base64_encoder_istream *bstream) { struct istream_private *stream = &bstream->istream; struct base64_encoder *b64enc = &bstream->encoder; const unsigned char *data; size_t size, pos, out_size, avail; buffer_t buf; data = i_stream_get_data(stream->parent, &size); if (size == 0) return 0; out_size = base64_encode_get_size(b64enc, size); if (!i_stream_try_alloc(stream, out_size, &avail)) return -2; buffer_create_from_data(&buf, stream->w_buffer + stream->pos, avail); base64_encode_more(b64enc, data, size, &pos, &buf); i_assert(buf.used > 0); stream->pos += buf.used; i_stream_skip(stream->parent, pos); return 1; } static int i_stream_base64_finish_encode(struct base64_encoder_istream *bstream) { struct istream_private *stream = &bstream->istream; struct base64_encoder *b64enc = &bstream->encoder; size_t out_size, buffer_avail; buffer_t buf; out_size = base64_encode_get_size(b64enc, 0); if (out_size == 0) { if (base64_encode_finish(b64enc, NULL)) stream->istream.eof = TRUE; return 1; } if (!i_stream_try_alloc(stream, out_size, &buffer_avail)) return -2; buffer_create_from_data(&buf, stream->w_buffer + stream->pos, buffer_avail); if (base64_encode_finish(b64enc, &buf)) stream->istream.eof = TRUE; i_assert(buf.used > 0); stream->pos += buf.used; return 1; } static ssize_t i_stream_base64_encoder_read(struct istream_private *stream) { struct base64_encoder_istream *bstream = container_of(stream, struct base64_encoder_istream, istream); size_t pre_count, post_count; int ret; if (base64_encode_is_finished(&bstream->encoder)) { stream->istream.eof = TRUE; return -1; } pre_count = post_count = 0; do { ret = i_stream_read_parent(stream); if (ret == 0) return 0; if (ret < 0) { if (stream->istream.stream_errno != 0) return -1; if (i_stream_get_data_size(stream->parent) == 0) break; /* add the final partial block */ } /* encode as many lines as fits into destination buffer */ pre_count = stream->pos - stream->skip; while ((ret = i_stream_base64_try_encode(bstream)) > 0) ; post_count = stream->pos - stream->skip; } while (ret == 0 && pre_count == post_count); if (ret == -2) { if (pre_count == post_count) return -2; } else if (ret < 0) { if (i_stream_get_data_size(stream->parent) == 0) { i_assert(post_count == pre_count); pre_count = stream->pos - stream->skip; ret = i_stream_base64_finish_encode(bstream); post_count = stream->pos - stream->skip; if (ret <= 0) return ret; } if (pre_count == post_count) { stream->istream.eof = TRUE; return -1; } } i_assert(post_count > pre_count); return post_count - pre_count; } static void i_stream_base64_encoder_seek(struct istream_private *stream, uoff_t v_offset, bool mark) { struct base64_encoder_istream *bstream = container_of(stream, struct base64_encoder_istream, istream); if (v_offset < stream->istream.v_offset) { /* seeking backwards - go back to beginning and seek forward from there. */ stream->parent_expected_offset = stream->parent_start_offset; stream->skip = stream->pos = 0; stream->istream.v_offset = 0; i_stream_seek(stream->parent, 0); base64_encode_reset(&bstream->encoder); } i_stream_default_seek_nonseekable(stream, v_offset, mark); } static int i_stream_base64_encoder_stat(struct istream_private *stream, bool exact ATTR_UNUSED) { struct base64_encoder_istream *bstream = container_of(stream, struct base64_encoder_istream, istream); const struct stat *st; if (i_stream_stat(stream->parent, exact, &st) < 0) { stream->istream.stream_errno = stream->parent->stream_errno; return -1; } stream->statbuf = *st; if (st->st_size == 0) return 0; stream->statbuf.st_size = base64_get_full_encoded_size(&bstream->encoder, st->st_size); return 0; } static struct istream * i_stream_create_base64_encoder_common(const struct base64_scheme *b64, struct istream *input, unsigned int chars_per_line, bool crlf) { struct base64_encoder_istream *bstream; enum base64_encode_flags b64_flags = 0; i_assert(chars_per_line % 4 == 0); bstream = i_new(struct base64_encoder_istream, 1); bstream->istream.max_buffer_size = input->real_stream->max_buffer_size; bstream->istream.read = i_stream_base64_encoder_read; bstream->istream.seek = i_stream_base64_encoder_seek; bstream->istream.stat = i_stream_base64_encoder_stat; bstream->istream.istream.readable_fd = FALSE; bstream->istream.istream.blocking = input->blocking; bstream->istream.istream.seekable = input->seekable; if (crlf) b64_flags |= BASE64_ENCODE_FLAG_CRLF; base64_encode_init(&bstream->encoder, b64, b64_flags, chars_per_line); return i_stream_create(&bstream->istream, input, i_stream_get_fd(input), 0); } struct istream * i_stream_create_base64_encoder(struct istream *input, unsigned int chars_per_line, bool crlf) { return i_stream_create_base64_encoder_common(&base64_scheme, input, chars_per_line, crlf); } struct istream * i_stream_create_base64url_encoder(struct istream *input, unsigned int chars_per_line, bool crlf) { return i_stream_create_base64_encoder_common(&base64url_scheme, input, chars_per_line, crlf); } dovecot-2.3.21.1/src/lib/hex-binary.c0000644000000000000000000000352714656633576014073 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "hex-binary.h" static void binary_to_hex_case(unsigned char *dest, const unsigned char *data, size_t size, bool ucase) { unsigned char *p; char base_char; size_t i; int value; /* @UNSAFE */ base_char = ucase ? 'A' : 'a'; p = dest; for (i = 0; i < size; i++) { value = data[i] >> 4; *p++ = value < 10 ? value + '0' : value - 10 + base_char; value = data[i] & 0x0f; *p++ = value < 10 ? value + '0' : value - 10 + base_char; } } const char *binary_to_hex(const unsigned char *data, size_t size) { unsigned char *dest = t_malloc_no0(MALLOC_MULTIPLY(size, 2) + 1); binary_to_hex_case(dest, data, size, FALSE); dest[size*2] = '\0'; return (char *)dest; } const char *binary_to_hex_ucase(const unsigned char *data, size_t size) { unsigned char *dest = t_malloc_no0(MALLOC_MULTIPLY(size, 2) + 1); binary_to_hex_case(dest, data, size, TRUE); dest[size*2] = '\0'; return (char *)dest; } void binary_to_hex_append(string_t *dest, const unsigned char *data, size_t size) { unsigned char *buf; buf = buffer_append_space_unsafe(dest, size * 2); binary_to_hex_case(buf, data, size, FALSE); } int hex_to_binary(const char *data, buffer_t *dest) { int value; while (*data != '\0') { if (*data >= '0' && *data <= '9') value = (*data - '0') << 4; else if (*data >= 'a' && *data <= 'f') value = (*data - 'a' + 10) << 4; else if (*data >= 'A' && *data <= 'F') value = (*data - 'A' + 10) << 4; else return -1; data++; if (*data >= '0' && *data <= '9') value |= *data - '0'; else if (*data >= 'a' && *data <= 'f') value |= *data - 'a' + 10; else if (*data >= 'A' && *data <= 'F') value |= *data - 'A' + 10; else return -1; buffer_append_c(dest, value); data++; } return 0; } dovecot-2.3.21.1/src/lib/json-tree.h0000644000000000000000000000373214656633576013736 00000000000000#ifndef JSON_TREE_H #define JSON_TREE_H #include "json-parser.h" /* Direct access to this structure is not encouraged, use the inline function accessors where possible, so that the implementation details can remain fluid, or, even better, hidden. */ struct json_tree_node { /* object key, or NULL if we're in a list */ const char *key; struct json_tree_node *parent, *next; enum json_type value_type; struct { /* for JSON_TYPE_OBJECT and JSON_TYPE_ARRAY */ struct json_tree_node *child; /* for other types */ const char *str; } value; }; static inline ATTR_PURE const struct json_tree_node *json_tree_get_child(const struct json_tree_node *node) { return node->value.child; } static inline ATTR_PURE const char *json_tree_get_value_str(const struct json_tree_node *node) { return node->value.str; } /* You can build a list or an object, nothing else. */ struct json_tree *json_tree_init_type(enum json_type container); static inline struct json_tree *json_tree_init(void) { return json_tree_init_type(JSON_TYPE_OBJECT); } static inline struct json_tree *json_tree_init_array(void) { return json_tree_init_type(JSON_TYPE_ARRAY); } void json_tree_deinit(struct json_tree **tree); /* Append data to a tree. The type/value should normally come from json-parser. Returns 0 on success, -1 if the input was invalid (which should never happen if it's coming from json-parser). */ int json_tree_append(struct json_tree *tree, enum json_type type, const char *value); /* Return the root node. */ const struct json_tree_node * json_tree_root(const struct json_tree *tree); /* Find a node with the specified key from an OBJECT node */ const struct json_tree_node * json_tree_find_key(const struct json_tree_node *node, const char *key); /* Find an object node (from an array), which contains the specified key=value in its values. */ const struct json_tree_node * json_tree_find_child_with(const struct json_tree_node *node, const char *key, const char *value); #endif dovecot-2.3.21.1/src/lib/istream-hash.c0000644000000000000000000000471614656633576014413 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "hash-method.h" #include "istream-private.h" #include "istream-hash.h" struct hash_istream { struct istream_private istream; const struct hash_method *method; void *hash_context; uoff_t high_offset; }; static ssize_t i_stream_hash_read(struct istream_private *stream) { struct hash_istream *hstream = container_of(stream, struct hash_istream, istream); const unsigned char *data; size_t size; uoff_t skip; ssize_t ret; i_stream_seek(stream->parent, stream->parent_start_offset + stream->istream.v_offset); ret = i_stream_read_copy_from_parent(&stream->istream); if (ret > 0 && hstream->hash_context != NULL) { data = i_stream_get_data(&stream->istream, &size); i_assert((size_t)ret <= size); i_assert(stream->istream.v_offset <= hstream->high_offset); skip = hstream->high_offset - stream->istream.v_offset; if (skip < (size_t)size) { hstream->high_offset += (size-skip); hstream->method->loop(hstream->hash_context, data+skip, size-skip); } } else if (ret < 0) { /* we finished hashing it. don't access it anymore, because the memory pointed by the hash may be freed before the istream itself */ hstream->hash_context = NULL; } return ret; } static void i_stream_hash_seek(struct istream_private *stream, uoff_t v_offset, bool mark ATTR_UNUSED) { struct hash_istream *hstream = container_of(stream, struct hash_istream, istream); if (hstream->hash_context != NULL) { io_stream_set_error(&stream->iostream, "Seeking not supported before hashing is finished"); stream->istream.stream_errno = ESPIPE; } stream->istream.v_offset = v_offset; stream->skip = stream->pos = 0; } struct istream * i_stream_create_hash(struct istream *input, const struct hash_method *method, void *hash_context) { struct hash_istream *hstream; hstream = i_new(struct hash_istream, 1); hstream->istream.max_buffer_size = input->real_stream->max_buffer_size; hstream->istream.stream_size_passthrough = TRUE; hstream->istream.read = i_stream_hash_read; hstream->istream.seek = i_stream_hash_seek; hstream->istream.istream.readable_fd = input->readable_fd; hstream->istream.istream.blocking = input->blocking; hstream->istream.istream.seekable = input->seekable; hstream->method = method; hstream->hash_context = hash_context; return i_stream_create(&hstream->istream, input, i_stream_get_fd(input), 0); } dovecot-2.3.21.1/src/lib/istream-sized.c0000644000000000000000000001506514656633576014605 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "istream-private.h" #include "istream-sized.h" struct sized_istream { struct istream_private istream; istream_sized_callback_t *error_callback; void *error_context; uoff_t size; bool min_size_only; }; static void i_stream_sized_destroy(struct iostream_private *stream) { struct sized_istream *sstream = container_of(stream, struct sized_istream, istream.iostream); uoff_t v_offset; v_offset = sstream->istream.parent_start_offset + sstream->istream.istream.v_offset; if (sstream->istream.parent->seekable || v_offset > sstream->istream.parent->v_offset) { /* get to same position in parent stream */ i_stream_seek(sstream->istream.parent, v_offset); } } static const char * i_stream_create_sized_default_error_callback( const struct istream_sized_error_data *data, void *context ATTR_UNUSED) { if (data->v_offset + data->new_bytes < data->wanted_size) { return t_strdup_printf("Stream is smaller than expected " "(%"PRIuUOFF_T" < %"PRIuUOFF_T")", data->v_offset + data->new_bytes, data->wanted_size); } else { return t_strdup_printf("Stream is larger than expected " "(%"PRIuUOFF_T" > %"PRIuUOFF_T", eof=%d)", data->v_offset + data->new_bytes, data->wanted_size, data->eof ? 1 : 0); } } static ssize_t i_stream_sized_parent_read(struct istream_private *stream, size_t *pos_r) { ssize_t ret; do { ret = i_stream_read_memarea(stream->parent); stream->istream.stream_errno = stream->parent->stream_errno; stream->istream.eof = stream->parent->eof; stream->buffer = i_stream_get_data(stream->parent, pos_r); } while (*pos_r <= stream->pos && ret > 0); return ret; } static ssize_t i_stream_sized_read(struct istream_private *stream) { struct sized_istream *sstream = container_of(stream, struct sized_istream, istream); struct istream_sized_error_data data; const char *error; uoff_t left; ssize_t ret; size_t pos; i_stream_seek(stream->parent, sstream->istream.parent_start_offset + stream->istream.v_offset); stream->pos -= stream->skip; stream->skip = 0; stream->buffer = i_stream_get_data(stream->parent, &pos); if (pos > stream->pos) ret = 0; else { if ((ret = i_stream_sized_parent_read(stream, &pos)) == -2) return -2; } left = sstream->size - stream->istream.v_offset; if (pos == left && ret != -1) { /* we have exactly the wanted amount of data left, but we don't know yet if there is more data in parent. */ ret = i_stream_sized_parent_read(stream, &pos); } i_zero(&data); data.v_offset = stream->istream.v_offset; data.new_bytes = pos; data.wanted_size = sstream->size; data.eof = stream->istream.eof; if (pos == left) { /* we may or may not be finished, depending on whether parent is at EOF. */ } else if (pos > left) { /* parent has more data available than expected */ if (!sstream->min_size_only) { error = sstream->error_callback(&data, sstream->error_context); io_stream_set_error(&stream->iostream, "%s", error); stream->istream.stream_errno = EINVAL; return -1; } pos = left; if (pos <= stream->pos) { stream->istream.eof = TRUE; ret = -1; } } else if (!stream->istream.eof) { /* still more to read */ } else if (stream->istream.stream_errno == ENOENT) { /* lost the file */ } else { /* EOF before we reached the wanted size */ error = sstream->error_callback(&data, sstream->error_context); io_stream_set_error(&stream->iostream, "%s", error); stream->istream.stream_errno = EPIPE; } ret = pos > stream->pos ? (ssize_t)(pos - stream->pos) : (ret == 0 ? 0 : -1); stream->pos = pos; i_assert(ret != -1 || stream->istream.eof || stream->istream.stream_errno != 0); return ret; } static int i_stream_sized_stat(struct istream_private *stream, bool exact ATTR_UNUSED) { struct sized_istream *sstream = container_of(stream, struct sized_istream, istream); const struct stat *st; /* parent stream may be base64-decoder. don't waste time decoding the entire stream, since we already know what the size is supposed to be. */ if (i_stream_stat(stream->parent, FALSE, &st) < 0) { stream->istream.stream_errno = stream->parent->stream_errno; return -1; } stream->statbuf = *st; stream->statbuf.st_size = sstream->size; return 0; } static struct sized_istream * i_stream_create_sized_common(struct istream *input, uoff_t size) { struct sized_istream *sstream; sstream = i_new(struct sized_istream, 1); sstream->size = size; sstream->istream.max_buffer_size = input->real_stream->max_buffer_size; sstream->istream.iostream.destroy = i_stream_sized_destroy; sstream->istream.read = i_stream_sized_read; sstream->istream.stat = i_stream_sized_stat; sstream->istream.istream.readable_fd = input->readable_fd; sstream->istream.istream.blocking = input->blocking; sstream->istream.istream.seekable = input->seekable; (void)i_stream_create(&sstream->istream, input, i_stream_get_fd(input), 0); return sstream; } struct istream *i_stream_create_sized(struct istream *input, uoff_t size) { struct sized_istream *sstream; sstream = i_stream_create_sized_common(input, size); sstream->error_callback = i_stream_create_sized_default_error_callback; sstream->error_context = sstream; return &sstream->istream.istream; } struct istream *i_stream_create_sized_range(struct istream *input, uoff_t offset, uoff_t size) { uoff_t orig_offset = input->v_offset; struct istream *ret; input->v_offset = offset; ret = i_stream_create_sized(input, size); input->v_offset = orig_offset; return ret; } struct istream *i_stream_create_min_sized(struct istream *input, uoff_t min_size) { struct istream *ret; ret= i_stream_create_sized(input, min_size); struct sized_istream *ret_sstream = container_of(ret->real_stream, struct sized_istream, istream); ret_sstream->min_size_only = TRUE; return ret; } struct istream *i_stream_create_min_sized_range(struct istream *input, uoff_t offset, uoff_t min_size) { struct istream *ret; ret = i_stream_create_sized_range(input, offset, min_size); struct sized_istream *ret_sstream = container_of(ret->real_stream, struct sized_istream, istream); ret_sstream->min_size_only = TRUE; return ret; } #undef i_stream_create_sized_with_callback struct istream * i_stream_create_sized_with_callback(struct istream *input, uoff_t size, istream_sized_callback_t *error_callback, void *context) { struct sized_istream *sstream; sstream = i_stream_create_sized_common(input, size); sstream->error_callback = error_callback; sstream->error_context = context; return &sstream->istream.istream; } dovecot-2.3.21.1/src/lib/event-filter.h0000644000000000000000000000516714656633576014440 00000000000000#ifndef EVENT_FILTER_H #define EVENT_FILTER_H struct event; struct event_filter_field { const char *key; const char *value; }; struct event_filter *event_filter_create(void); struct event_filter *event_filter_create_fragment(pool_t pool); void event_filter_ref(struct event_filter *filter); void event_filter_unref(struct event_filter **filter); /* Add queries from source filter to destination filter. */ void event_filter_merge(struct event_filter *dest, const struct event_filter *src); /* Add queries from source filter to destination filter, but with supplied context overriding whatever context source queries had. */ void event_filter_merge_with_context(struct event_filter *dest, const struct event_filter *src, void *new_context); /* Remove query with given context from filter. Returns TRUE if query was removed, otherwise FALSE. */ bool event_filter_remove_queries_with_context(struct event_filter *filter, void *context); /* Export the filter into a string. The context pointers aren't exported. */ void event_filter_export(struct event_filter *filter, string_t *dest); /* Add queries to the filter from the given string. The string is expected to be generated by event_filter_export(). Returns TRUE on success, FALSE on invalid string. */ #define event_filter_import(filter, str, error_r) \ (event_filter_parse((str), (filter), (error_r)) == 0) /* Parse a string-ified query, filling the passed in filter */ int event_filter_parse(const char *str, struct event_filter *filter, const char **error_r); /* Returns TRUE if the event matches the event filter. */ bool event_filter_match(struct event_filter *filter, struct event *event, const struct failure_context *ctx); /* Same as event_filter_match(), but use the given source filename:linenum instead of taking it from the event. */ bool event_filter_match_source(struct event_filter *filter, struct event *event, const char *source_filename, unsigned int source_linenum, const struct failure_context *ctx); /* Iterate through all queries with non-NULL context that match the event. */ struct event_filter_match_iter * event_filter_match_iter_init(struct event_filter *filter, struct event *event, const struct failure_context *ctx); /* Return context for the query that matched, or NULL when there are no more matches. Note: This skips over any queries that have NULL context. */ void *event_filter_match_iter_next(struct event_filter_match_iter *iter); void event_filter_match_iter_deinit(struct event_filter_match_iter **iter); void event_filter_init(void); void event_filter_deinit(void); #endif dovecot-2.3.21.1/src/lib/istream-try.h0000644000000000000000000000242314656633576014304 00000000000000#ifndef ISTREAM_TRY_H #define ISTREAM_TRY_H /* Read from the first input stream that doesn't fail with EINVAL. If any of the streams fail with non-EINVAL, it's treated as a fatal failure and the error is immediately returned. If a stream returns 0, more data is waited for before continuing to the next stream. This allows the last stream to be a fallback stream that always succeeds. Once the stream is detected, all the other streams are unreferenced. The streams should usually be children of the same parent tee-istream. Detecting whether istream-tee buffer is full or not is a bit tricky. There's no visible difference between non-blocking istream returning 0 and istream-tee buffer being full. To work around this, we treat used buffer sizes <= min_buffer_full_size as being non-blocking istreams, while buffer sizes > min_buffer_full_size are assumed to be due to istream-tee max buffer size being reached. Practically this means that min_buffer_full_size must be smaller than the smallest of the istreams' maximum buffer sizes, but large enough that all the istreams would have returned EINVAL on invalid input by that position. */ struct istream *istream_try_create(struct istream *const input[], size_t min_buffer_full_size); #endif dovecot-2.3.21.1/src/lib/istream-failure-at.c0000644000000000000000000000574514656633576015524 00000000000000/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "istream-private.h" #include "istream-failure-at.h" struct failure_at_istream { struct istream_private istream; int error_code; char *error_string; uoff_t failure_offset; }; static void i_stream_failure_at_destroy(struct iostream_private *stream) { struct failure_at_istream *fstream = container_of(stream, struct failure_at_istream, istream.iostream); i_free(fstream->error_string); } static ssize_t i_stream_failure_at_read(struct istream_private *stream) { struct failure_at_istream *fstream = container_of(stream, struct failure_at_istream, istream); uoff_t new_offset; ssize_t ret; i_stream_seek(stream->parent, stream->parent_start_offset + stream->istream.v_offset); ret = i_stream_read_copy_from_parent(&stream->istream); new_offset = stream->istream.v_offset + (stream->pos - stream->skip); if (ret >= 0 && new_offset >= fstream->failure_offset) { if (stream->istream.v_offset >= fstream->failure_offset) { /* we already passed the wanted failure offset, return error immediately. */ stream->pos = stream->skip; stream->istream.stream_errno = errno = fstream->error_code; io_stream_set_error(&stream->iostream, "%s", fstream->error_string); ret = -1; } else { /* return data up to the wanted failure offset and on the next read() call return failure */ size_t new_pos = fstream->failure_offset - stream->istream.v_offset + stream->skip; i_assert(new_pos >= stream->skip && stream->pos >= new_pos); ret -= stream->pos - new_pos; stream->pos = new_pos; } } else if (ret < 0 && stream->istream.stream_errno == 0 && fstream->failure_offset == UOFF_T_MAX) { /* failure at EOF */ stream->istream.stream_errno = errno = fstream->error_code; io_stream_set_error(&stream->iostream, "%s", fstream->error_string); } return ret; } struct istream * i_stream_create_failure_at(struct istream *input, uoff_t failure_offset, int stream_errno, const char *error_string) { struct failure_at_istream *fstream; fstream = i_new(struct failure_at_istream, 1); fstream->istream.max_buffer_size = input->real_stream->max_buffer_size; fstream->istream.stream_size_passthrough = TRUE; fstream->istream.read = i_stream_failure_at_read; fstream->istream.iostream.destroy = i_stream_failure_at_destroy; fstream->istream.istream.readable_fd = input->readable_fd; fstream->istream.istream.blocking = input->blocking; fstream->istream.istream.seekable = input->seekable; fstream->error_code = stream_errno; fstream->error_string = i_strdup(error_string); fstream->failure_offset = failure_offset; return i_stream_create(&fstream->istream, input, i_stream_get_fd(input), 0); } struct istream * i_stream_create_failure_at_eof(struct istream *input, int stream_errno, const char *error_string) { return i_stream_create_failure_at(input, UOFF_T_MAX, stream_errno, error_string); } dovecot-2.3.21.1/src/lib/strnum.c0000644000000000000000000003060414656633576013351 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "strnum.h" bool str_is_numeric(const char *str, char end_char) { if (*str == '\0' || *str == end_char) return FALSE; while (*str != '\0' && *str != end_char) { if (*str < '0' || *str > '9') return FALSE; str++; } return TRUE; } bool str_is_float(const char *str, char end_char) { bool dot_seen = FALSE; bool num_seen = FALSE; if (*str == '\0' || *str == end_char) return FALSE; while (*str != '\0' && *str != end_char) { if (*str == '.') { if (dot_seen || !num_seen) return FALSE; dot_seen = TRUE; num_seen = FALSE; str++; /* enforce that number follows dot */ continue; } if (*str < '0' || *str > '9') return FALSE; num_seen = TRUE; str++; } return num_seen; } /* * Unsigned decimal */ #define STR_PARSE_U__TEMPLATE(name, type) \ int name(const char *str, type *num_r, const char **endp_r) \ { \ uintmax_t l; \ if (str_parse_uintmax(str, &l, endp_r) < 0 || l > (type)-1) \ return -1; \ *num_r = (type)l; \ return 0; \ } STR_PARSE_U__TEMPLATE(str_parse_uint, unsigned int) STR_PARSE_U__TEMPLATE(str_parse_ulong, unsigned long) STR_PARSE_U__TEMPLATE(str_parse_ullong, unsigned long long) STR_PARSE_U__TEMPLATE(str_parse_uint32, uint32_t) STR_PARSE_U__TEMPLATE(str_parse_uint64, uint64_t) #define STR_TO_U__TEMPLATE(name, type) \ int name(const char *str, type *num_r) \ { \ uintmax_t l; \ if (str_to_uintmax(str, &l) < 0 || l > (type)-1) \ return -1; \ *num_r = (type)l; \ return 0; \ } STR_TO_U__TEMPLATE(str_to_uint, unsigned int) STR_TO_U__TEMPLATE(str_to_ulong, unsigned long) STR_TO_U__TEMPLATE(str_to_ullong, unsigned long long) STR_TO_U__TEMPLATE(str_to_uint32, uint32_t) STR_TO_U__TEMPLATE(str_to_uint64, uint64_t) int str_parse_uintmax(const char *str, uintmax_t *num_r, const char **endp_r) { uintmax_t n = 0; if (*str < '0' || *str > '9') return -1; do { if (n >= ((uintmax_t)-1 / 10)) { if (n > (uintmax_t)-1 / 10) return -1; if ((uintmax_t)(*str - '0') > ((uintmax_t)-1 % 10)) return -1; } n = n * 10 + (*str - '0'); str++; } while (*str >= '0' && *str <= '9'); if (endp_r != NULL) *endp_r = str; *num_r = n; return 0; } int str_to_uintmax(const char *str, uintmax_t *num_r) { const char *endp; uintmax_t n; int ret = str_parse_uintmax(str, &n, &endp); if ((ret != 0) || (*endp != '\0')) return -1; *num_r = n; return 0; } bool str_uint_equals(const char *str, uintmax_t num) { uintmax_t l; if (str_to_uintmax(str, &l) < 0) return FALSE; return l == num; } /* * Unsigned hexadecimal */ #define STR_PARSE_UHEX__TEMPLATE(name, type) \ int name(const char *str, type *num_r, const char **endp_r) \ { \ uintmax_t l; \ if (str_parse_uintmax_hex(str, &l, endp_r) < 0 || l > (type)-1) \ return -1; \ *num_r = (type)l; \ return 0; \ } STR_PARSE_UHEX__TEMPLATE(str_parse_uint_hex, unsigned int) STR_PARSE_UHEX__TEMPLATE(str_parse_ulong_hex, unsigned long) STR_PARSE_UHEX__TEMPLATE(str_parse_ullong_hex, unsigned long long) STR_PARSE_UHEX__TEMPLATE(str_parse_uint32_hex, uint32_t) STR_PARSE_UHEX__TEMPLATE(str_parse_uint64_hex, uint64_t) #define STR_TO_UHEX__TEMPLATE(name, type) \ int name(const char *str, type *num_r) \ { \ uintmax_t l; \ if (str_to_uintmax_hex(str, &l) < 0 || l > (type)-1) \ return -1; \ *num_r = (type)l; \ return 0; \ } STR_TO_UHEX__TEMPLATE(str_to_uint_hex, unsigned int) STR_TO_UHEX__TEMPLATE(str_to_ulong_hex, unsigned long) STR_TO_UHEX__TEMPLATE(str_to_ullong_hex, unsigned long long) STR_TO_UHEX__TEMPLATE(str_to_uint32_hex, uint32_t) STR_TO_UHEX__TEMPLATE(str_to_uint64_hex, uint64_t) static inline int _str_parse_hex(const char ch, unsigned int *hex_r) { switch (ch) { case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': *hex_r = (unsigned int)(ch - 'a' + 10); return 0; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': *hex_r = (unsigned int)(ch - 'A' + 10); return 0; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': *hex_r = (unsigned int)(ch - '0'); return 0; default: break; } return -1; } int str_parse_uintmax_hex(const char *str, uintmax_t *num_r, const char **endp_r) { unsigned int hex; uintmax_t n = 0; if (_str_parse_hex(*str, &hex) < 0) return -1; do { if (n > (uintmax_t)-1 >> 4) return -1; n = (n << 4) + hex; str++; } while (_str_parse_hex(*str, &hex) >= 0); if (endp_r != NULL) *endp_r = str; *num_r = n; return 0; } int str_to_uintmax_hex(const char *str, uintmax_t *num_r) { const char *endp; uintmax_t n; int ret = str_parse_uintmax_hex(str, &n, &endp); if ((ret != 0) || (*endp != '\0')) return -1; *num_r = n; return 0; } /* * Unsigned octal */ #define STR_PARSE_UOCT__TEMPLATE(name, type) \ int name(const char *str, type *num_r, const char **endp_r) \ { \ uintmax_t l; \ if (str_parse_uintmax_oct(str, &l, endp_r) < 0 || l > (type)-1) \ return -1; \ *num_r = (type)l; \ return 0; \ } STR_PARSE_UOCT__TEMPLATE(str_parse_uint_oct, unsigned int) STR_PARSE_UOCT__TEMPLATE(str_parse_ulong_oct, unsigned long) STR_PARSE_UOCT__TEMPLATE(str_parse_ullong_oct, unsigned long long) STR_PARSE_UOCT__TEMPLATE(str_parse_uint32_oct, uint32_t) STR_PARSE_UOCT__TEMPLATE(str_parse_uint64_oct, uint64_t) #define STR_TO_UOCT__TEMPLATE(name, type) \ int name(const char *str, type *num_r) \ { \ uintmax_t l; \ if (str_to_uintmax_oct(str, &l) < 0 || l > (type)-1) \ return -1; \ *num_r = (type)l; \ return 0; \ } STR_TO_UOCT__TEMPLATE(str_to_uint_oct, unsigned int) STR_TO_UOCT__TEMPLATE(str_to_ulong_oct, unsigned long) STR_TO_UOCT__TEMPLATE(str_to_ullong_oct, unsigned long long) STR_TO_UOCT__TEMPLATE(str_to_uint32_oct, uint32_t) STR_TO_UOCT__TEMPLATE(str_to_uint64_oct, uint64_t) int str_parse_uintmax_oct(const char *str, uintmax_t *num_r, const char **endp_r) { uintmax_t n = 0; if (*str < '0' || *str > '7') return -1; for (; *str >= '0' && *str <= '7'; str++) { if (n > (uintmax_t)-1 >> 3) return -1; n = (n << 3) + (*str - '0'); } if (endp_r != NULL) *endp_r = str; *num_r = n; return 0; } int str_to_uintmax_oct(const char *str, uintmax_t *num_r) { const char *endp; uintmax_t n; int ret = str_parse_uintmax_oct(str, &n, &endp); if ((ret != 0) || (*endp != '\0')) return -1; *num_r = n; return 0; } /* * Signed */ #define STR_PARSE_S__TEMPLATE(name, type, int_min, int_max) \ int name(const char *str, type *num_r, const char **endp_r) \ { \ intmax_t l; \ if (str_parse_intmax(str, &l, endp_r) < 0) \ return -1; \ if (l < int_min || l > int_max) \ return -1; \ *num_r = (type)l; \ return 0; \ } STR_PARSE_S__TEMPLATE(str_parse_int, int, INT_MIN, INT_MAX) STR_PARSE_S__TEMPLATE(str_parse_long, long, LONG_MIN, LONG_MAX) STR_PARSE_S__TEMPLATE(str_parse_llong, long long, LLONG_MIN, LLONG_MAX) STR_PARSE_S__TEMPLATE(str_parse_int32, int32_t, INT32_MIN, INT32_MAX) STR_PARSE_S__TEMPLATE(str_parse_int64, int64_t, INT64_MIN, INT64_MAX) #define STR_TO_S__TEMPLATE(name, type, int_min, int_max) \ int name(const char *str, type *num_r) \ { \ intmax_t l; \ if (str_to_intmax(str, &l) < 0) \ return -1; \ if (l < int_min || l > int_max) \ return -1; \ *num_r = (type)l; \ return 0; \ } STR_TO_S__TEMPLATE(str_to_int, int, INT_MIN, INT_MAX) STR_TO_S__TEMPLATE(str_to_long, long, LONG_MIN, LONG_MAX) STR_TO_S__TEMPLATE(str_to_llong, long long, LLONG_MIN, LLONG_MAX) STR_TO_S__TEMPLATE(str_to_int32, int32_t, INT32_MIN, INT32_MAX) STR_TO_S__TEMPLATE(str_to_int64, int64_t, INT64_MIN, INT64_MAX) int ATTR_NO_SANITIZE_IMPLICIT_CONVERSION ATTR_NO_SANITIZE_INTEGER str_parse_intmax(const char *str, intmax_t *num_r, const char **endp_r) { bool neg = FALSE; uintmax_t l; if (*str == '-') { neg = TRUE; str++; } if (str_parse_uintmax(str, &l, endp_r) < 0) return -1; if (!neg) { if (l > INTMAX_MAX) return -1; *num_r = (intmax_t)l; } else { if (l > UINTMAX_MAX - (UINTMAX_MAX + INTMAX_MIN)) return -1; *num_r = (intmax_t) UNSIGNED_MINUS(l); } return 0; } int str_to_intmax(const char *str, intmax_t *num_r) { const char *endp; intmax_t n; int ret = str_parse_intmax(str, &n, &endp); if ((ret != 0) || (*endp != '\0')) return -1; *num_r = n; return 0; } /* * Special numeric types */ static int verify_xid(uintmax_t l, unsigned int result_size) { unsigned int result_bits; /* we assume that result is a signed type, but that it can never be negative */ result_bits = result_size*CHAR_BIT - 1; if ((l >> result_bits) != 0) return -1; return 0; } int str_to_uid(const char *str, uid_t *num_r) { uintmax_t l; if (str_to_uintmax(str, &l) < 0) return -1; if (verify_xid(l, sizeof(*num_r)) < 0) return -1; *num_r = (uid_t)l; return 0; } int str_to_gid(const char *str, gid_t *num_r) { uintmax_t l; if (str_to_uintmax(str, &l) < 0) return -1; /* OS X uses negative GIDs */ #ifndef __APPLE__ if (verify_xid(l, sizeof(*num_r)) < 0) return -1; #endif *num_r = (gid_t)l; return 0; } int str_to_pid(const char *str, pid_t *num_r) { uintmax_t l; if (str_to_uintmax(str, &l) < 0) return -1; if (verify_xid(l, sizeof(*num_r)) < 0) return -1; *num_r = (pid_t)l; return 0; } int str_to_ino(const char *str, ino_t *num_r) { uintmax_t l; if (str_to_uintmax(str, &l) < 0) return -1; if (verify_xid(l, sizeof(*num_r)) < 0) return -1; *num_r = (ino_t)l; return 0; } int str_to_uoff(const char *str, uoff_t *num_r) { uintmax_t l; if (str_to_uintmax(str, &l) < 0) return -1; if (l > UOFF_T_MAX) return -1; *num_r = (uoff_t)l; return 0; } int str_to_time(const char *str, time_t *num_r) { intmax_t l; if (str_to_intmax(str, &l) < 0) return -1; *num_r = (time_t)l; return 0; } STR_PARSE_U__TEMPLATE(str_parse_uoff, uoff_t) /* * Error handling */ const char *str_num_error(const char *str) { if (*str == '-') { if (!str_is_numeric(str + 1, '\0')) return "Not a valid number"; return "Number too small"; } else { if (!str_is_numeric(str, '\0')) return "Not a valid number"; return "Number too large"; } } dovecot-2.3.21.1/src/lib/safe-mkstemp.h0000644000000000000000000000115714656633576014423 00000000000000#ifndef SAFE_MKSTEMP_H #define SAFE_MKSTEMP_H /* Create a new file with a given prefix. The string is updated to contain the created filename. uid and gid can be (uid_t)-1 and (gid_t)-1 to use the defaults. */ int safe_mkstemp(string_t *prefix, mode_t mode, uid_t uid, gid_t gid); int safe_mkstemp_group(string_t *prefix, mode_t mode, gid_t gid, const char *gid_origin); /* Append host and PID to the prefix. */ int safe_mkstemp_hostpid(string_t *prefix, mode_t mode, uid_t uid, gid_t gid); int safe_mkstemp_hostpid_group(string_t *prefix, mode_t mode, gid_t gid, const char *gid_origin); #endif dovecot-2.3.21.1/src/lib/test-event-flatten.c0000644000000000000000000002105314656633576015550 00000000000000/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "ioloop.h" #include "time-util.h" #include "lib-event-private.h" #include "failures-private.h" #include "array.h" #include "str.h" #define CHECK_FLATTEN_SAME(e) \ check_event_same(event_flatten(e), (e)) #define CHECK_FLATTEN_DIFF(e, c, nc, f, nf) \ check_event_diff(event_flatten(e), (e), \ (c), (nc), \ (f), (nf)) static struct event_category cats[] = { { .name = "cat0", }, { .name = "cat1", }, }; static void check_event_diff_cats(struct event_category *const *got, unsigned int ngot, const char **exp, unsigned int nexp) { unsigned int i; test_assert(ngot == nexp); for (i = 0; i < nexp; i++) test_assert(strcmp(got[i]->name, exp[i]) == 0); } static void check_event_diff_fields(const struct event_field *got, unsigned int ngot, const struct event_field *exp, unsigned int nexp) { unsigned int i; const char *got_str; test_assert(ngot == nexp); for (i = 0; i < nexp; i++) { if (got[i].value_type != exp[i].value_type) { test_assert(FALSE); continue; } switch (exp[i].value_type) { case EVENT_FIELD_VALUE_TYPE_STR: test_assert(strcmp(exp[i].value.str, got[i].value.str) == 0); break; case EVENT_FIELD_VALUE_TYPE_INTMAX: test_assert(exp[i].value.intmax == got[i].value.intmax); break; case EVENT_FIELD_VALUE_TYPE_TIMEVAL: test_assert(timeval_cmp(&exp[i].value.timeval, &got[i].value.timeval) == 0); break; case EVENT_FIELD_VALUE_TYPE_STRLIST: got_str = t_array_const_string_join(&got[i].value.strlist, ","); test_assert_strcmp(exp[i].value.str, got_str); break; } } } static void check_event_diff(struct event *e, struct event *orig, const char **expected_cats, unsigned int num_expected_cats, const struct event_field *expected_fields, unsigned int num_expected_fields) { struct event_category *const *cats; const struct event_field *fields; unsigned int num_cats; unsigned int num_fields; test_assert(e != orig); test_assert(e->parent == NULL); /* different pointers implies different ids */ test_assert(e->id != orig->id); /* TODO: does this make sense? */ test_assert(timeval_cmp(&e->tv_created_ioloop, &orig->tv_created_ioloop) == 0); test_assert(timeval_cmp(&e->tv_created, &orig->tv_created) == 0); test_assert(timeval_cmp(&e->tv_last_sent, &orig->tv_last_sent) == 0); test_assert(strcmp(e->source_filename, orig->source_filename) == 0); test_assert(e->source_linenum == orig->source_linenum); /* FIXME: check sending name? */ cats = event_get_categories(e, &num_cats); check_event_diff_cats(cats, num_cats, expected_cats, num_expected_cats); fields = event_get_fields(e, &num_fields); check_event_diff_fields(fields, num_fields, expected_fields, num_expected_fields); event_unref(&e); } static void check_event_same(struct event *e, struct event *orig) { test_assert(e == orig); /* the pointers are the same; nothing can possibly differ */ event_unref(&e); } static void test_event_flatten_no_parent(void) { struct event *e; test_begin("event flatten: no parent"); e = event_create(NULL); CHECK_FLATTEN_SAME(e); event_add_int(e, "abc", 4); CHECK_FLATTEN_SAME(e); event_add_int(e, "def", 2); CHECK_FLATTEN_SAME(e); event_add_str(e, "abc", "foo"); CHECK_FLATTEN_SAME(e); event_add_category(e, &cats[0]); CHECK_FLATTEN_SAME(e); event_unref(&e); test_end(); } static void test_event_flatten_one_parent(void) { static const char *exp_1cat[] = { "cat0", }; static const char *exp_2cat[] = { "cat1", "cat0", }; static struct event_field exp_int = { .key = "abc", .value_type = EVENT_FIELD_VALUE_TYPE_INTMAX, .value = { .str = NULL, .intmax = 42, .timeval = {0,0}, } }; static struct event_field exp_2int[2] = { { .key = "abc", .value_type = EVENT_FIELD_VALUE_TYPE_INTMAX, .value = { .intmax = 42, .str = NULL, .timeval = {0,0}, } }, { .key = "def", .value_type = EVENT_FIELD_VALUE_TYPE_INTMAX, .value = { .intmax = 49, .str = NULL, .timeval = {0,0}, } }, }; static struct event_field exp_1str1int[2] = { { .key = "abc", .value_type = EVENT_FIELD_VALUE_TYPE_STR, .value = { .str = "foo", .intmax = 0, .timeval = {0,0}, } }, { .key = "def", .value_type = EVENT_FIELD_VALUE_TYPE_INTMAX, .value = { .intmax = 49, .str = NULL, .timeval = {0,0}, } }, }; static struct event_field exp_1str1int1strlist[3] = { { .key = "abc", .value_type = EVENT_FIELD_VALUE_TYPE_STR, .value = { .str = "foo", .intmax = 0, .timeval = {0,0}, } }, { .key = "def", .value_type = EVENT_FIELD_VALUE_TYPE_INTMAX, .value = { .intmax = 49, .str = NULL, .timeval = {0,0}, } }, { .key = "cba", .value_type = EVENT_FIELD_VALUE_TYPE_STRLIST, .value = { .str = "one,two,three", }, }, }; struct event *parent; struct event *e; test_begin("event flatten: one parent"); t_array_init(&exp_1str1int1strlist[0].value.strlist, 3); const char *str = "one"; array_push_back(&exp_1str1int1strlist[0].value.strlist, &str); str = "two"; array_push_back(&exp_1str1int1strlist[0].value.strlist, &str); str = "three"; array_push_back(&exp_1str1int1strlist[0].value.strlist, &str); parent = event_create(NULL); e = event_create(parent); CHECK_FLATTEN_DIFF(e, NULL, 0, NULL, 0); event_add_int(e, "abc", 42); CHECK_FLATTEN_DIFF(e, NULL, 0, &exp_int, 1); event_add_int(e, "def", 49); CHECK_FLATTEN_DIFF(e, NULL, 0, exp_2int, 2); event_add_str(e, "abc", "foo"); CHECK_FLATTEN_DIFF(e, NULL, 0, exp_1str1int, 2); event_add_category(e, &cats[0]); CHECK_FLATTEN_DIFF(e, exp_1cat, 1, exp_1str1int, 2); event_add_category(e, &cats[1]); CHECK_FLATTEN_DIFF(e, exp_2cat, 2, exp_1str1int, 2); event_strlist_append(e, "cba", "one"); event_strlist_append(e, "cba", "two"); event_strlist_append(e, "cba", "three"); CHECK_FLATTEN_DIFF(e, exp_2cat, 2, exp_1str1int1strlist, 3); event_unref(&e); event_unref(&parent); test_end(); } static void test_event_flatten_override_parent_field(void) { static struct event_field exp_int = { .key = "abc", .value_type = EVENT_FIELD_VALUE_TYPE_INTMAX, .value = { .intmax = 42, .str = NULL, .timeval = {0,0}, } }; static struct event_field exp_str = { .key = "abc", .value_type = EVENT_FIELD_VALUE_TYPE_STR, .value = { .str = "def", .intmax = 0, .timeval = {0,0}, } }; static struct event_field exp_2str[2] = { { .key = "abc", .value_type = EVENT_FIELD_VALUE_TYPE_STR, .value = { .str = "def", .intmax = 0, .timeval = {0,0}, } }, { .key = "foo", .value_type = EVENT_FIELD_VALUE_TYPE_STR, .value = { .str = "bar", .intmax = 0, .timeval = {0,0}, } }, }; struct event *parent; struct event *e; test_begin("event flatten: override parent field"); parent = event_create(NULL); event_add_int(parent, "abc", 5); e = event_create(parent); event_add_int(e, "abc", 42); CHECK_FLATTEN_DIFF(e, NULL, 0, &exp_int, 1); event_add_str(e, "abc", "def"); CHECK_FLATTEN_DIFF(e, NULL, 0, &exp_str, 1); event_add_str(parent, "foo", "bar"); CHECK_FLATTEN_DIFF(e, NULL, 0, exp_2str, 2); event_unref(&e); event_unref(&parent); test_end(); } static void test_event_strlist_flatten(void) { test_begin("event flatten: strlist"); struct event *l1 = event_create(NULL); event_strlist_append(l1, "test", "l3"); struct event *l2 = event_create(l1); event_strlist_append(l2, "test", "l1"); struct event *l3 = event_create(l2); unsigned int line = __LINE__ - 1; event_strlist_append(l3, "test", "l2"); string_t *dest = t_str_new(32); struct event *event = event_flatten(l3); event_export(event, dest); /* see if it matches .. */ const char *reference = t_strdup_printf("%"PRIdTIME_T"\t%u" "\ts"__FILE__ "\t%u\tLtest\t3\tl3\tl1\tl2", event->tv_created.tv_sec, (unsigned int)event->tv_created.tv_usec, line); test_assert_strcmp(str_c(dest), reference); /* these should not end up duplicated */ event_strlist_append(event, "test", "l1"); event_strlist_append(event, "test", "l2"); event_strlist_append(event, "test", "l3"); /* and export should look the same */ str_truncate(dest, 0); event_export(event, dest); test_assert_strcmp(str_c(dest), reference); event_unref(&event); /* export event */ event_unref(&l3); event_unref(&l2); event_unref(&l1); test_end(); } void test_event_flatten(void) { test_event_flatten_no_parent(); test_event_flatten_one_parent(); test_event_flatten_override_parent_field(); test_event_strlist_flatten(); } dovecot-2.3.21.1/src/lib/sort.h0000644000000000000000000000176714656633576013025 00000000000000#ifndef SORT_H #define SORT_H #define INTEGER_CMP(name, type) \ static inline int name(const type *i1, const type *i2) \ { \ if (*i1 < *i2) \ return -1; \ else if (*i1 > *i2) \ return 1; \ else \ return 0; \ } INTEGER_CMP(uint64_cmp, uint64_t) INTEGER_CMP(uint32_cmp, uint32_t) #define i_qsort(base, nmemb, size, cmp) \ qsort(base, nmemb, size - \ CALLBACK_TYPECHECK(cmp, int (*)(typeof(const typeof(*base) *), \ typeof(const typeof(*base) *))), \ (int (*)(const void *, const void *))cmp) #define i_bsearch(key, base, nmemb, size, cmp) \ bsearch(key, base, nmemb, size - \ CALLBACK_TYPECHECK(cmp, int (*)(typeof(const typeof(*key) *), \ typeof(const typeof(*base) *))), \ (int (*)(const void *, const void *))cmp) int bsearch_strcmp(const char *key, const char *const *member) ATTR_PURE; int bsearch_strcasecmp(const char *key, const char *const *member) ATTR_PURE; #endif dovecot-2.3.21.1/src/lib/test-hmac.c0000644000000000000000000005046214656633576013712 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "hash-method.h" #include "hmac.h" #include "sha-common.h" #include "buffer.h" #include "hex-binary.h" struct test_vector { const char *prf; const unsigned char *key; size_t key_len; const unsigned char *data; size_t data_len; const unsigned char *res; size_t res_len; }; #define TEST_BUF(x) (const unsigned char*)x, sizeof(x)-1 /* RFC 4231 test vectors */ static const struct test_vector test_vectors[] = { /* Test Case 1 */ { "sha256", TEST_BUF("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"), TEST_BUF("Hi There"), TEST_BUF("\xb0\x34\x4c\x61\xd8\xdb\x38\x53\x5c\xa8\xaf\xce\xaf\x0b\xf1\x2b\x88\x1d\xc2\x00\xc9\x83\x3d\xa7\x26\xe9\x37\x6c\x2e\x32\xcf\xf7") }, /* Test Case 2 */ { "sha256", TEST_BUF("\x4a\x65\x66\x65"), /* "Jefe" */ TEST_BUF("what do ya want for nothing?"), TEST_BUF("\x5b\xdc\xc1\x46\xbf\x60\x75\x4e\x6a\x04\x24\x26\x08\x95\x75\xc7\x5a\x00\x3f\x08\x9d\x27\x39\x83\x9d\xec\x58\xb9\x64\xec\x38\x43") }, /* Test Case 3 */ { "sha256", TEST_BUF("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"), TEST_BUF("\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"), TEST_BUF("\x77\x3e\xa9\x1e\x36\x80\x0e\x46\x85\x4d\xb8\xeb\xd0\x91\x81\xa7\x29\x59\x09\x8b\x3e\xf8\xc1\x22\xd9\x63\x55\x14\xce\xd5\x65\xfe") }, /* Test Case 4 */ { "sha256", TEST_BUF("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19"), TEST_BUF("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"), TEST_BUF("\x82\x55\x8a\x38\x9a\x44\x3c\x0e\xa4\xcc\x81\x98\x99\xf2\x08\x3a\x85\xf0\xfa\xa3\xe5\x78\xf8\x07\x7a\x2e\x3f\xf4\x67\x29\x66\x5b") }, /* Test Case 5 */ { "sha256", TEST_BUF("\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"), TEST_BUF("\x54\x65\x73\x74\x20\x57\x69\x74\x68\x20\x54\x72\x75\x6e\x63\x61\x74\x69\x6f\x6e"), /* "Test With Truncation" */ TEST_BUF("\xa3\xb6\x16\x74\x73\x10\x0e\xe0\x6e\x0c\x79\x6c\x29\x55\x55\x2b") }, /* Test Case 6 */ { "sha256", TEST_BUF("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"), TEST_BUF("\x54\x65\x73\x74\x20\x55\x73\x69\x6e\x67\x20\x4c\x61\x72\x67\x65\x72\x20\x54\x68\x61\x6e\x20\x42\x6c\x6f\x63\x6b\x2d\x53\x69\x7a\x65\x20\x4b\x65\x79\x20\x2d\x20\x48\x61\x73\x68\x20\x4b\x65\x79\x20\x46\x69\x72\x73\x74"), /* "Test Using Larger Than Block-Size Key - Hash Key First" */ TEST_BUF("\x60\xe4\x31\x59\x1e\xe0\xb6\x7f\x0d\x8a\x26\xaa\xcb\xf5\xb7\x7f\x8e\x0b\xc6\x21\x37\x28\xc5\x14\x05\x46\x04\x0f\x0e\xe3\x7f\x54") }, /* Test Case 7 */ { "sha256", TEST_BUF("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"), TEST_BUF("\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20\x74\x65\x73\x74\x20\x75\x73\x69\x6e\x67\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b\x2d\x73\x69\x7a\x65\x20\x6b\x65\x79\x20\x61\x6e\x64\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b\x2d\x73\x69\x7a\x65\x20\x64\x61\x74\x61\x2e\x20\x54\x68\x65\x20\x6b\x65\x79\x20\x6e\x65\x65\x64\x73\x20\x74\x6f\x20\x62\x65\x20\x68\x61\x73\x68\x65\x64\x20\x62\x65\x66\x6f\x72\x65\x20\x62\x65\x69\x6e\x67\x20\x75\x73\x65\x64\x20\x62\x79\x20\x74\x68\x65\x20\x48\x4d\x41\x43\x20\x61\x6c\x67\x6f\x72\x69\x74\x68\x6d\x2e"), /* "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm." */ TEST_BUF("\x9b\x09\xff\xa7\x1b\x94\x2f\xcb\x27\x63\x5f\xbc\xd5\xb0\xe9\x44\xbf\xdc\x63\x64\x4f\x07\x13\x93\x8a\x7f\x51\x53\x5c\x3a\x35\xe2") } }; static const struct test_vector test_vectors_hmac384[] = { /* Test Case 1 */ { "sha384", TEST_BUF("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"), TEST_BUF("Hi There"), TEST_BUF("\xaf\xd0\x39\x44\xd8\x48\x95\x62\x6b\x08\x25\xf4\xab\x46\x90\x7f\x15\xf9\xda\xdb\xe4\x10\x1e\xc6\x82\xaa\x03\x4c\x7c\xeb\xc5\x9c\xfa\xea\x9e\xa9\x07\x6e\xde\x7f\x4a\xf1\x52\xe8\xb2\xfa\x9c\xb6"), }, /* Test Case 2 */ { "sha384", TEST_BUF("\x4a\x65\x66\x65"), /* "Jefe" */ TEST_BUF("what do ya want for nothing?"), TEST_BUF("\xaf\x45\xd2\xe3\x76\x48\x40\x31\x61\x7f\x78\xd2\xb5\x8a\x6b\x1b\x9c\x7e\xf4\x64\xf5\xa0\x1b\x47\xe4\x2e\xc3\x73\x63\x22\x44\x5e\x8e\x22\x40\xca\x5e\x69\xe2\xc7\x8b\x32\x39\xec\xfa\xb2\x16\x49"), }, /* Test Case 3 */ { "sha384", TEST_BUF("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"), TEST_BUF("\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"), TEST_BUF("\x88\x06\x26\x08\xd3\xe6\xad\x8a\x0a\xa2\xac\xe0\x14\xc8\xa8\x6f\x0a\xa6\x35\xd9\x47\xac\x9f\xeb\xe8\x3e\xf4\xe5\x59\x66\x14\x4b\x2a\x5a\xb3\x9d\xc1\x38\x14\xb9\x4e\x3a\xb6\xe1\x01\xa3\x4f\x27"), }, /* Test Case 4 */ { "sha384", TEST_BUF("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19"), TEST_BUF("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"), TEST_BUF("\x3e\x8a\x69\xb7\x78\x3c\x25\x85\x19\x33\xab\x62\x90\xaf\x6c\xa7\x7a\x99\x81\x48\x08\x50\x00\x9c\xc5\x57\x7c\x6e\x1f\x57\x3b\x4e\x68\x01\xdd\x23\xc4\xa7\xd6\x79\xcc\xf8\xa3\x86\xc6\x74\xcf\xfb"), }, /* Test Case 5 */ { "sha384", TEST_BUF("\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"), TEST_BUF("\x54\x65\x73\x74\x20\x57\x69\x74\x68\x20\x54\x72\x75\x6e\x63\x61\x74\x69\x6f\x6e"), /* "Test With Truncation" */ TEST_BUF("\x3a\xbf\x34\xc3\x50\x3b\x2a\x23\xa4\x6e\xfc\x61\x9b\xae\xf8\x97"), }, /* Test Case 6 */ { "sha384", TEST_BUF("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"), TEST_BUF("\x54\x65\x73\x74\x20\x55\x73\x69\x6e\x67\x20\x4c\x61\x72\x67\x65\x72\x20\x54\x68\x61\x6e\x20\x42\x6c\x6f\x63\x6b\x2d\x53\x69\x7a\x65\x20\x4b\x65\x79\x20\x2d\x20\x48\x61\x73\x68\x20\x4b\x65\x79\x20\x46\x69\x72\x73\x74"), /* "Test Using Larger Than Block-Size Key - Hash Key First" */ TEST_BUF("\x4e\xce\x08\x44\x85\x81\x3e\x90\x88\xd2\xc6\x3a\x04\x1b\xc5\xb4\x4f\x9e\xf1\x01\x2a\x2b\x58\x8f\x3c\xd1\x1f\x05\x03\x3a\xc4\xc6\x0c\x2e\xf6\xab\x40\x30\xfe\x82\x96\x24\x8d\xf1\x63\xf4\x49\x52"), }, /* Test Case 7 */ { "sha384", TEST_BUF("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"), TEST_BUF("\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20\x74\x65\x73\x74\x20\x75\x73\x69\x6e\x67\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b\x2d\x73\x69\x7a\x65\x20\x6b\x65\x79\x20\x61\x6e\x64\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b\x2d\x73\x69\x7a\x65\x20\x64\x61\x74\x61\x2e\x20\x54\x68\x65\x20\x6b\x65\x79\x20\x6e\x65\x65\x64\x73\x20\x74\x6f\x20\x62\x65\x20\x68\x61\x73\x68\x65\x64\x20\x62\x65\x66\x6f\x72\x65\x20\x62\x65\x69\x6e\x67\x20\x75\x73\x65\x64\x20\x62\x79\x20\x74\x68\x65\x20\x48\x4d\x41\x43\x20\x61\x6c\x67\x6f\x72\x69\x74\x68\x6d\x2e"), /* "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm." */ TEST_BUF("\x66\x17\x17\x8e\x94\x1f\x02\x0d\x35\x1e\x2f\x25\x4e\x8f\xd3\x2c\x60\x24\x20\xfe\xb0\xb8\xfb\x9a\xdc\xce\xbb\x82\x46\x1e\x99\xc5\xa6\x78\xcc\x31\xe7\x99\x17\x6d\x38\x60\xe6\x11\x0c\x46\x52\x3e"), } }; static const struct test_vector test_vectors_hmac512[] = { /* Test Case 1 */ { "sha512", TEST_BUF("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"), TEST_BUF("Hi There"), TEST_BUF("\x87\xaa\x7c\xde\xa5\xef\x61\x9d\x4f\xf0\xb4\x24\x1a\x1d\x6c\xb0\x23\x79\xf4\xe2\xce\x4e\xc2\x78\x7a\xd0\xb3\x05\x45\xe1\x7c\xde\xda\xa8\x33\xb7\xd6\xb8\xa7\x02\x03\x8b\x27\x4e\xae\xa3\xf4\xe4\xbe\x9d\x91\x4e\xeb\x61\xf1\x70\x2e\x69\x6c\x20\x3a\x12\x68\x54") }, /* Test Case 2 */ { "sha512", TEST_BUF("\x4a\x65\x66\x65"), /* "Jefe" */ TEST_BUF("what do ya want for nothing?"), TEST_BUF("\x16\x4b\x7a\x7b\xfc\xf8\x19\xe2\xe3\x95\xfb\xe7\x3b\x56\xe0\xa3\x87\xbd\x64\x22\x2e\x83\x1f\xd6\x10\x27\x0c\xd7\xea\x25\x05\x54\x97\x58\xbf\x75\xc0\x5a\x99\x4a\x6d\x03\x4f\x65\xf8\xf0\xe6\xfd\xca\xea\xb1\xa3\x4d\x4a\x6b\x4b\x63\x6e\x07\x0a\x38\xbc\xe7\x37") }, /* Test Case 3 */ { "sha512", TEST_BUF("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"), TEST_BUF("\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"), TEST_BUF("\xfa\x73\xb0\x08\x9d\x56\xa2\x84\xef\xb0\xf0\x75\x6c\x89\x0b\xe9\xb1\xb5\xdb\xdd\x8e\xe8\x1a\x36\x55\xf8\x3e\x33\xb2\x27\x9d\x39\xbf\x3e\x84\x82\x79\xa7\x22\xc8\x06\xb4\x85\xa4\x7e\x67\xc8\x07\xb9\x46\xa3\x37\xbe\xe8\x94\x26\x74\x27\x88\x59\xe1\x32\x92\xfb") }, /* Test Case 4 */ { "sha512", TEST_BUF("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19"), TEST_BUF("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"), TEST_BUF("\xb0\xba\x46\x56\x37\x45\x8c\x69\x90\xe5\xa8\xc5\xf6\x1d\x4a\xf7\xe5\x76\xd9\x7f\xf9\x4b\x87\x2d\xe7\x6f\x80\x50\x36\x1e\xe3\xdb\xa9\x1c\xa5\xc1\x1a\xa2\x5e\xb4\xd6\x79\x27\x5c\xc5\x78\x80\x63\xa5\xf1\x97\x41\x12\x0c\x4f\x2d\xe2\xad\xeb\xeb\x10\xa2\x98\xdd") }, /* Test Case 5 */ { "sha512", TEST_BUF("\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"), TEST_BUF("\x54\x65\x73\x74\x20\x57\x69\x74\x68\x20\x54\x72\x75\x6e\x63\x61\x74\x69\x6f\x6e"), /* "Test With Truncation" */ TEST_BUF("\x41\x5f\xad\x62\x71\x58\x0a\x53\x1d\x41\x79\xbc\x89\x1d\x87\xa6") }, /* Test Case 6 */ { "sha512", TEST_BUF("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"), TEST_BUF("\x54\x65\x73\x74\x20\x55\x73\x69\x6e\x67\x20\x4c\x61\x72\x67\x65\x72\x20\x54\x68\x61\x6e\x20\x42\x6c\x6f\x63\x6b\x2d\x53\x69\x7a\x65\x20\x4b\x65\x79\x20\x2d\x20\x48\x61\x73\x68\x20\x4b\x65\x79\x20\x46\x69\x72\x73\x74"), /* "Test Using Larger Than Block-Size Key - Hash Key First" */ TEST_BUF("\x80\xb2\x42\x63\xc7\xc1\xa3\xeb\xb7\x14\x93\xc1\xdd\x7b\xe8\xb4\x9b\x46\xd1\xf4\x1b\x4a\xee\xc1\x12\x1b\x01\x37\x83\xf8\xf3\x52\x6b\x56\xd0\x37\xe0\x5f\x25\x98\xbd\x0f\xd2\x21\x5d\x6a\x1e\x52\x95\xe6\x4f\x73\xf6\x3f\x0a\xec\x8b\x91\x5a\x98\x5d\x78\x65\x98") }, /* Test Case 7 */ { "sha512", TEST_BUF("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"), TEST_BUF("\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20\x74\x65\x73\x74\x20\x75\x73\x69\x6e\x67\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b\x2d\x73\x69\x7a\x65\x20\x6b\x65\x79\x20\x61\x6e\x64\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b\x2d\x73\x69\x7a\x65\x20\x64\x61\x74\x61\x2e\x20\x54\x68\x65\x20\x6b\x65\x79\x20\x6e\x65\x65\x64\x73\x20\x74\x6f\x20\x62\x65\x20\x68\x61\x73\x68\x65\x64\x20\x62\x65\x66\x6f\x72\x65\x20\x62\x65\x69\x6e\x67\x20\x75\x73\x65\x64\x20\x62\x79\x20\x74\x68\x65\x20\x48\x4d\x41\x43\x20\x61\x6c\x67\x6f\x72\x69\x74\x68\x6d\x2e"), /* "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm." */ TEST_BUF("\xe3\x7b\x6a\x77\x5d\xc8\x7d\xba\xa4\xdf\xa9\xf9\x6e\x5e\x3f\xfd\xde\xbd\x71\xf8\x86\x72\x89\x86\x5d\xf5\xa3\x2d\x20\xcd\xc9\x44\xb6\x02\x2c\xac\x3c\x49\x82\xb1\x0d\x5e\xeb\x55\xc3\xe4\xde\x15\x13\x46\x76\xfb\x6d\xe0\x44\x60\x65\xc9\x74\x40\xfa\x8c\x6a\x58") } }; /* RFC 5869 test vectors */ static const struct test_vector_5869 { const char *prf; const unsigned char *ikm; size_t ikm_len; const unsigned char *salt; size_t salt_len; const unsigned char *info; size_t info_len; const unsigned char *okm; size_t okm_len; } test_vectors_5869[] = { { "sha256", TEST_BUF("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"), TEST_BUF("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c"), TEST_BUF("\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9"), TEST_BUF("\x3c\xb2\x5f\x25\xfa\xac\xd5\x7a\x90\x43\x4f\x64\xd0\x36\x2f\x2a\x2d\x2d\x0a\x90\xcf\x1a\x5a\x4c\x5d\xb0\x2d\x56\xec\xc4\xc5\xbf\x34\x00\x72\x08\xd5\xb8\x87\x18\x58\x65") }, { "sha256", TEST_BUF("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"), TEST_BUF("\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"), TEST_BUF("\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"), TEST_BUF("\xb1\x1e\x39\x8d\xc8\x03\x27\xa1\xc8\xe7\xf7\x8c\x59\x6a\x49\x34\x4f\x01\x2e\xda\x2d\x4e\xfa\xd8\xa0\x50\xcc\x4c\x19\xaf\xa9\x7c\x59\x04\x5a\x99\xca\xc7\x82\x72\x71\xcb\x41\xc6\x5e\x59\x0e\x09\xda\x32\x75\x60\x0c\x2f\x09\xb8\x36\x77\x93\xa9\xac\xa3\xdb\x71\xcc\x30\xc5\x81\x79\xec\x3e\x87\xc1\x4c\x01\xd5\xc1\xf3\x43\x4f\x1d\x87") }, { "sha256", TEST_BUF("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"), TEST_BUF(""), TEST_BUF(""), TEST_BUF("\x8d\xa4\xe7\x75\xa5\x63\xc1\x8f\x71\x5f\x80\x2a\x06\x3c\x5a\x31\xb8\xa1\x1f\x5c\x5e\xe1\x87\x9e\xc3\x45\x4e\x5f\x3c\x73\x8d\x2d\x9d\x20\x13\x95\xfa\xa4\xb6\x1a\x96\xc8") }, /* should be equal to above */ { "sha256", TEST_BUF("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"), NULL, 0, NULL, 0, TEST_BUF("\x8d\xa4\xe7\x75\xa5\x63\xc1\x8f\x71\x5f\x80\x2a\x06\x3c\x5a\x31\xb8\xa1\x1f\x5c\x5e\xe1\x87\x9e\xc3\x45\x4e\x5f\x3c\x73\x8d\x2d\x9d\x20\x13\x95\xfa\xa4\xb6\x1a\x96\xc8") }, }; static void test_hmac_rfc(void) { test_begin("hmac sha256 rfc4231 vectors"); for(size_t i = 0; i < N_ELEMENTS(test_vectors); i++) { const struct test_vector *vec = &(test_vectors[i]); struct hmac_context ctx; hmac_init(&ctx, vec->key, vec->key_len, hash_method_lookup(vec->prf)); hmac_update(&ctx, vec->data, vec->data_len); unsigned char res[SHA256_RESULTLEN]; hmac_final(&ctx, res); test_assert_idx(memcmp(res, vec->res, vec->res_len) == 0, i); } test_end(); } static void test_hmac384_rfc(void) { test_begin("hmac sha384 rfc4231 vectors"); for (size_t i = 0; i < N_ELEMENTS(test_vectors_hmac384); i++) { const struct test_vector *vec = &(test_vectors_hmac384[i]); struct hmac_context ctx; hmac_init(&ctx, vec->key, vec->key_len, hash_method_lookup(vec->prf)); hmac_update(&ctx, vec->data, vec->data_len); unsigned char res[SHA384_RESULTLEN]; hmac_final(&ctx, res); test_assert_idx(memcmp(res, vec->res, vec->res_len) == 0, i); } test_end(); } static void test_hmac512_rfc(void) { test_begin("hmac sha512 rfc4231 vectors"); for (size_t i = 0; i < N_ELEMENTS(test_vectors_hmac512); i++) { const struct test_vector *vec = &(test_vectors_hmac512[i]); struct hmac_context ctx; hmac_init(&ctx, vec->key, vec->key_len, hash_method_lookup(vec->prf)); hmac_update(&ctx, vec->data, vec->data_len); unsigned char res[SHA512_RESULTLEN]; hmac_final(&ctx, res); test_assert_idx(memcmp(res, vec->res, vec->res_len) == 0, i); } test_end(); } static void test_hmac_buffer(void) { const struct test_vector *vec = &(test_vectors[0]); test_begin("hmac temporary buffer"); buffer_t *tmp; tmp = t_hmac_data(hash_method_lookup(vec->prf), vec->key, vec->key_len, vec->data, vec->data_len); test_assert(tmp->used == vec->res_len && memcmp(tmp->data, vec->res, vec->res_len) == 0); test_end(); } static void test_hkdf_rfc(void) { test_begin("hkdf sha256 rfc5869 vectors"); buffer_t *res = t_buffer_create(82); for(size_t i = 0; i < N_ELEMENTS(test_vectors_5869); i++) { buffer_set_used_size(res, 0); const struct test_vector_5869 *vec = &(test_vectors_5869[i]); const struct hash_method *m = hash_method_lookup(vec->prf); hmac_hkdf(m, vec->salt, vec->salt_len, vec->ikm, vec->ikm_len, vec->info, vec->info_len, res, vec->okm_len); test_assert_idx(memcmp(res->data, vec->okm, vec->okm_len) == 0, i); } test_end(); } static void test_hkdf_buffer(void) { test_begin("hkdf temporary buffer"); const struct test_vector_5869 *vec = &(test_vectors_5869[0]); const struct hash_method *m = hash_method_lookup(vec->prf); buffer_t *tmp = t_hmac_hkdf(m, vec->salt, vec->salt_len, vec->ikm, vec->ikm_len, vec->info, vec->info_len, vec->okm_len); test_assert(tmp->used == vec->okm_len && memcmp(tmp->data, vec->okm, vec->okm_len) == 0); test_end(); } void test_hmac(void) { test_hmac_rfc(); test_hmac384_rfc(); test_hmac512_rfc(); test_hmac_buffer(); test_hkdf_rfc(); test_hkdf_buffer(); } dovecot-2.3.21.1/src/lib/fd-util.h0000644000000000000000000000204514656633576013370 00000000000000#ifndef FD_UTIL_H #define FD_UTIL_H /* Change close-on-exec flag of fd. */ void fd_close_on_exec(int fd, bool set); /* Verify that fds in given range don't exist. */ void fd_debug_verify_leaks(int first_fd, int last_fd); /* Set file descriptor to blocking/nonblocking state */ void fd_set_nonblock(int fd, bool nonblock); /* Close fd_in and fd_out, unless they're already -1. They can point to the same fd, in which case they're closed only once. If they point to stdin or stdout, they're replaced with /dev/null. */ void fd_close_maybe_stdio(int *fd_in, int *fd_out); /* Close the fd and set it to -1. This assert-crashes if fd == 0, and is a no-op if fd == -1. Normally fd == 0 would happen only if an uninitialized fd is attempted to be closed, which is a bug. */ void i_close_fd_path(int *fd, const char *path, const char *arg, const char *func, const char *file, int line); #define i_close_fd_path(fd, path) i_close_fd_path((fd), (path), #fd, __func__, __FILE__, __LINE__) #define i_close_fd(fd) i_close_fd_path((fd), NULL) #endif dovecot-2.3.21.1/src/lib/test-iostream-pump.c0000644000000000000000000001774614656633576015614 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "istream.h" #include "ostream.h" #include "buffer.h" #include "str.h" #include "ioloop.h" #include "iostream-pump.h" #include "istream-failure-at.h" #include "ostream-failure-at.h" #include #include #include struct nonblock_ctx { struct istream *in; struct ostream *out; uoff_t pos, max_size; }; static unsigned char data[] = "hello, world"; static void completed(enum iostream_pump_status status, int *u0) { /* to somehow discern between error and success .. */ (*u0) -= (status == IOSTREAM_PUMP_STATUS_INPUT_EOF ? 1 : 2); io_loop_stop(current_ioloop); } static void failed(int *u0) { *u0 = -1; /* ensure failure */ io_loop_stop(current_ioloop); } static void pump_nonblocking_timeout(struct nonblock_ctx *ctx) { switch (ctx->pos % 4) { case 0: break; case 1: /* allow more input */ if (ctx->in->blocking) break; if (ctx->pos/4 == ctx->max_size+1) test_istream_set_allow_eof(ctx->in, TRUE); else test_istream_set_size(ctx->in, ctx->pos/4); i_stream_set_input_pending(ctx->in, TRUE); break; case 2: break; case 3: { /* allow more output. give always one byte less than the input size so there's something in internal buffer. */ if (ctx->out->blocking) break; size_t size = ctx->pos/4; if (size > 0) test_ostream_set_max_output_size(ctx->out, size-1); break; } } ctx->pos++; } static const char * run_pump(struct istream *in, struct ostream *out, int *counter, buffer_t *out_buffer) { struct iostream_pump *pump; struct ioloop *ioloop = io_loop_create(); io_loop_set_current(ioloop); struct nonblock_ctx ctx = { in, out, 0, 0 }; struct timeout *to2 = NULL; if (!in->blocking) { test_assert(i_stream_get_size(in, TRUE, &ctx.max_size) > 0); test_istream_set_size(in, 0); test_istream_set_allow_eof(in, FALSE); } if (!out->blocking) { test_ostream_set_max_output_size(out, 0); } if (!in->blocking || !out->blocking) { to2 = timeout_add_short(0, pump_nonblocking_timeout, &ctx); } pump = iostream_pump_create(in, out); i_stream_unref(&in); o_stream_unref(&out); iostream_pump_set_completion_callback(pump, completed, counter); iostream_pump_start(pump); alarm(5); struct timeout *to = timeout_add(3000, failed, counter); io_loop_run(current_ioloop); timeout_remove(&to); timeout_remove(&to2); alarm(0); test_assert(*counter == 0); if (!ctx.out->blocking && ctx.in->stream_errno != 0 && ctx.out->stream_errno == 0) { /* input failed, finish flushing output */ test_ostream_set_max_output_size(ctx.out, SIZE_MAX); test_assert(o_stream_flush(ctx.out) > 0); } else { test_assert(o_stream_flush(ctx.out) != 0); } const char *ret = t_strdup(str_c(out_buffer)); iostream_pump_unref(&pump); io_loop_destroy(&ioloop); return ret; } static void test_iostream_setup(bool in_block, bool out_block, struct istream **in_r, struct ostream **out_r, buffer_t **out_buffer_r) { *out_buffer_r = t_buffer_create(128); *in_r = test_istream_create_data(data, sizeof(data)); (*in_r)->blocking = in_block; if (out_block) *out_r = test_ostream_create(*out_buffer_r); else *out_r = test_ostream_create_nonblocking(*out_buffer_r, 1); } static void test_iostream_pump_simple(bool in_block, bool out_block) { int counter; struct istream *in; struct ostream *out; buffer_t *buffer; test_begin(t_strdup_printf("iostream_pump " "(in=%sblocking, out=%sblocking)", (in_block ? "" : "non-"), (out_block ? "" : "non-"))); test_iostream_setup(in_block, out_block, &in, &out, &buffer); counter = 1; test_assert(strcmp(run_pump(in, out, &counter, buffer), "hello, world") == 0); test_end(); } static void test_iostream_pump_failure_start_read(bool in_block, bool out_block) { int counter; struct istream *in, *in_2; struct ostream *out; buffer_t *buffer; test_begin(t_strdup_printf("iostream_pump failure start-read " "(in=%sblocking, out=%sblocking)", (in_block ? "" : "non-"), (out_block ? "" : "non-"))); test_iostream_setup(in_block, out_block, &in_2, &out, &buffer); in = i_stream_create_failure_at(in_2, 0, EIO, "test pump fail"); i_stream_unref(&in_2); counter = 2; test_assert(strcmp(run_pump(in, out, &counter, buffer), "") == 0); test_end(); } static void test_iostream_pump_failure_mid_read(bool in_block, bool out_block) { int counter; struct istream *in, *in_2; struct ostream *out; buffer_t *buffer; test_begin(t_strdup_printf("iostream_pump failure mid-read " "(in=%sblocking, out=%sblocking)", (in_block ? "" : "non-"), (out_block ? "" : "non-"))); test_iostream_setup(in_block, out_block, &in_2, &out, &buffer); in = i_stream_create_failure_at(in_2, 4, EIO, "test pump fail"); i_stream_unref(&in_2); counter = 2; test_assert(strcmp(run_pump(in, out, &counter, buffer), "hell") == 0); test_end(); } static void test_iostream_pump_failure_end_read(bool in_block, bool out_block) { int counter; struct istream *in, *in_2; struct ostream *out; buffer_t *buffer; test_begin(t_strdup_printf("iostream_pump failure mid-read " "(in=%sblocking, out=%sblocking)", (in_block ? "" : "non-"), (out_block ? "" : "non-"))); test_iostream_setup(in_block, out_block, &in_2, &out, &buffer); in = i_stream_create_failure_at_eof(in_2, EIO, "test pump fail"); i_stream_unref(&in_2); counter = 2; test_assert(strcmp(run_pump(in, out, &counter, buffer), "hello, world") == 0); test_end(); } static void test_iostream_pump_failure_start_write(bool in_block, bool out_block) { int counter; struct istream *in; struct ostream *out, *out_2; buffer_t *buffer; test_begin(t_strdup_printf("iostream_pump failure start-write " "(in=%sblocking, out=%sblocking)", (in_block ? "" : "non-"), (out_block ? "" : "non-"))); test_iostream_setup(in_block, out_block, &in, &out_2, &buffer); out = o_stream_create_failure_at(out_2, 0, "test pump fail"); o_stream_unref(&out_2); counter = 2; test_assert(strcmp(run_pump(in, out, &counter, buffer), "") == 0); test_end(); } static void test_iostream_pump_failure_mid_write(bool in_block, bool out_block) { int counter; struct istream *in; struct ostream *out, *out_2; buffer_t *buffer; test_begin(t_strdup_printf("iostream_pump failure mid-write " "(in=%sblocking, out=%sblocking)", (in_block ? "" : "non-"), (out_block ? "" : "non-"))); test_iostream_setup(in_block, out_block, &in, &out_2, &buffer); out = o_stream_create_failure_at(out_2, 4, "test pump fail"); o_stream_unref(&out_2); counter = 2; /* "hel" because the last byte is only in internal buffer */ test_assert(strcmp(run_pump(in, out, &counter, buffer), (out_block ? (in_block ? "" : "hell") : "hel")) == 0); test_end(); } static void test_iostream_pump_failure_end_write(bool in_block, bool out_block) { int counter; struct istream *in; struct ostream *out, *out_2; buffer_t *buffer; if (!out_block || !in_block) { /* we'll get flushes constantly */ return; } test_begin("iostream_pump failure end-write (blocking)"); test_iostream_setup(in_block, out_block, &in, &out_2, &buffer); out = o_stream_create_failure_at_flush(out_2, "test pump fail"); o_stream_unref(&out_2); counter = 2; test_assert(strcmp(run_pump(in, out, &counter, buffer), "hello, world") == 0); test_end(); } static void test_iostream_pump_real(void) { for(int i = 0; i < 3; i++) { bool in_block = ((i & BIT(0)) != 0); bool out_block = ((i & BIT(1)) != 0); test_iostream_pump_simple(in_block, out_block); test_iostream_pump_failure_start_read(in_block, out_block); test_iostream_pump_failure_mid_read(in_block, out_block); test_iostream_pump_failure_end_read(in_block, out_block); test_iostream_pump_failure_start_write(in_block, out_block); test_iostream_pump_failure_mid_write(in_block, out_block); test_iostream_pump_failure_end_write(in_block, out_block); } } void test_iostream_pump(void) { T_BEGIN { test_iostream_pump_real(); } T_END; } dovecot-2.3.21.1/src/lib/priorityq.h0000644000000000000000000000301614656633576014065 00000000000000#ifndef PRIORITYQ_H #define PRIORITYQ_H /* Priority queue implementation using heap. The items you add to the queue must begin with a struct priorityq_item. This is necessary for priorityq_remove() to work fast. */ struct priorityq_item { /* Current index in the queue array, updated automatically. */ unsigned int idx; /* [your own data] */ }; /* Returns <0, 0 or >0 */ typedef int priorityq_cmp_callback_t(const void *p1, const void *p2); /* Create a new priority queue. Callback is used to compare added items. */ struct priorityq * priorityq_init(priorityq_cmp_callback_t *cmp_callback, unsigned int init_size); void priorityq_deinit(struct priorityq **pq); /* Return number of items in the queue. */ unsigned int priorityq_count(const struct priorityq *pq) ATTR_PURE; /* Add a new item to the queue. */ void priorityq_add(struct priorityq *pq, struct priorityq_item *item); /* Remove the specified item from the queue. */ void priorityq_remove(struct priorityq *pq, struct priorityq_item *item); /* Return the item with the highest priority. Returns NULL if queue is empty. */ struct priorityq_item *priorityq_peek(struct priorityq *pq); /* Like priorityq_peek(), but also remove the returned item from the queue. */ struct priorityq_item *priorityq_pop(struct priorityq *pq); /* Returns array containing all the priorityq_items. Only the first item is guaranteed to be the highest priority item, the rest can't be assumed to be in any order. */ struct priorityq_item *const *priorityq_items(struct priorityq *pq); #endif dovecot-2.3.21.1/src/lib/file-lock.c0000644000000000000000000003133014656633576013663 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "istream.h" #include "file-lock.h" #include "file-dotlock.h" #include "time-util.h" #include #include #ifdef HAVE_FLOCK # include #endif struct file_lock { struct file_lock_settings set; int fd; char *path; struct dotlock *dotlock; struct timeval locked_time; int lock_type; }; static struct timeval lock_wait_start; static uint64_t file_lock_wait_usecs = 0; static long long file_lock_slow_warning_usecs = -1; static void file_lock_log_warning_if_slow(struct file_lock *lock); bool file_lock_method_parse(const char *name, enum file_lock_method *method_r) { if (strcasecmp(name, "fcntl") == 0) *method_r = FILE_LOCK_METHOD_FCNTL; else if (strcasecmp(name, "flock") == 0) *method_r = FILE_LOCK_METHOD_FLOCK; else if (strcasecmp(name, "dotlock") == 0) *method_r = FILE_LOCK_METHOD_DOTLOCK; else return FALSE; return TRUE; } const char *file_lock_method_to_str(enum file_lock_method method) { switch (method) { case FILE_LOCK_METHOD_FCNTL: return "fcntl"; case FILE_LOCK_METHOD_FLOCK: return "flock"; case FILE_LOCK_METHOD_DOTLOCK: return "dotlock"; } i_unreached(); } int file_try_lock(int fd, const char *path, int lock_type, const struct file_lock_settings *set, struct file_lock **lock_r, const char **error_r) { return file_wait_lock(fd, path, lock_type, set, 0, lock_r, error_r); } static const char * file_lock_find_fcntl(int lock_fd, int lock_type) { struct flock fl; i_zero(&fl); fl.l_type = lock_type; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; if (fcntl(lock_fd, F_GETLK, &fl) < 0 || fl.l_type == F_UNLCK || fl.l_pid == -1 || fl.l_pid == 0) return ""; return t_strdup_printf(" (%s lock held by pid %ld)", fl.l_type == F_RDLCK ? "READ" : "WRITE", (long)fl.l_pid); } static const char * file_lock_find_proc_locks(int lock_fd ATTR_UNUSED) { /* do anything except Linux support this? don't bother trying it for OSes we don't know about. */ #ifdef __linux__ static bool have_proc_locks = TRUE; struct stat st; char node_buf[MAX_INT_STRLEN * 3 + 2]; struct istream *input; const char *line, *lock_type = ""; pid_t pid = 0; int fd; if (!have_proc_locks) return NULL; if (fstat(lock_fd, &st) < 0) return ""; i_snprintf(node_buf, sizeof(node_buf), "%02x:%02x:%llu", major(st.st_dev), minor(st.st_dev), (unsigned long long)st.st_ino); fd = open("/proc/locks", O_RDONLY); if (fd == -1) { have_proc_locks = FALSE; return ""; } input = i_stream_create_fd_autoclose(&fd, 512); while (pid == 0 && (line = i_stream_read_next_line(input)) != NULL) T_BEGIN { const char *const *args = t_strsplit_spaces(line, " "); /* number: FLOCK/POSIX ADVISORY READ/WRITE pid major:minor:inode region-start region-end */ if (str_array_length(args) < 8) { ; /* don't continue from within a T_BEGIN {...} T_END */ } else if (strcmp(args[5], node_buf) == 0) { lock_type = strcmp(args[3], "READ") == 0 ? "READ" : "WRITE"; if (str_to_pid(args[4], &pid) < 0) pid = 0; } } T_END; i_stream_destroy(&input); if (pid == 0) { /* not found */ return ""; } if (pid == getpid()) return " (BUG: lock is held by our own process)"; return t_strdup_printf(" (%s lock held by pid %ld)", lock_type, (long)pid); #else return ""; #endif } const char *file_lock_find(int lock_fd, enum file_lock_method lock_method, int lock_type) { const char *ret; if (lock_method == FILE_LOCK_METHOD_FCNTL) { ret = file_lock_find_fcntl(lock_fd, lock_type); if (ret[0] != '\0') return ret; } return file_lock_find_proc_locks(lock_fd); } static bool err_is_lock_timeout(time_t started, unsigned int timeout_secs) { /* if EINTR took at least timeout_secs-1 number of seconds, assume it was the alarm. otherwise log EINTR failure. (We most likely don't want to retry EINTR since a signal means somebody wants us to stop blocking). */ return errno == EINTR && (unsigned long)(time(NULL) - started + 1) >= timeout_secs; } static int file_lock_do(int fd, const char *path, int lock_type, const struct file_lock_settings *set, unsigned int timeout_secs, const char **error_r) { const char *lock_type_str; time_t started = time(NULL); int ret; i_assert(fd != -1); if (timeout_secs != 0) { alarm(timeout_secs); file_lock_wait_start(); } lock_type_str = lock_type == F_UNLCK ? "unlock" : (lock_type == F_RDLCK ? "read-lock" : "write-lock"); switch (set->lock_method) { case FILE_LOCK_METHOD_FCNTL: { #ifndef HAVE_FCNTL *error_r = t_strdup_printf( "Can't lock file %s: fcntl() locks not supported", path); return -1; #else struct flock fl; fl.l_type = lock_type; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; ret = fcntl(fd, timeout_secs != 0 ? F_SETLKW : F_SETLK, &fl); if (timeout_secs != 0) { alarm(0); file_lock_wait_end(path); } if (ret == 0) break; if (timeout_secs == 0 && (errno == EACCES || errno == EAGAIN)) { /* locked by another process */ *error_r = t_strdup_printf( "fcntl(%s, %s, F_SETLK) locking failed: %m " "(File is already locked)", path, lock_type_str); return 0; } if (err_is_lock_timeout(started, timeout_secs)) { errno = EAGAIN; *error_r = t_strdup_printf( "fcntl(%s, %s, F_SETLKW) locking failed: " "Timed out after %u seconds%s", path, lock_type_str, timeout_secs, file_lock_find(fd, set->lock_method, lock_type)); return 0; } *error_r = t_strdup_printf("fcntl(%s, %s, %s) locking failed: %m", path, lock_type_str, timeout_secs == 0 ? "F_SETLK" : "F_SETLKW"); if (errno == EDEADLK && !set->allow_deadlock) { i_panic("%s%s", *error_r, file_lock_find(fd, set->lock_method, lock_type)); } return -1; #endif } case FILE_LOCK_METHOD_FLOCK: { #ifndef HAVE_FLOCK *error_r = t_strdup_printf( "Can't lock file %s: flock() not supported", path); return -1; #else int operation = timeout_secs != 0 ? 0 : LOCK_NB; switch (lock_type) { case F_RDLCK: operation |= LOCK_SH; break; case F_WRLCK: operation |= LOCK_EX; break; case F_UNLCK: operation |= LOCK_UN; break; } ret = flock(fd, operation); if (timeout_secs != 0) { alarm(0); file_lock_wait_end(path); } if (ret == 0) break; if (timeout_secs == 0 && errno == EWOULDBLOCK) { /* locked by another process */ *error_r = t_strdup_printf( "flock(%s, %s) failed: %m " "(File is already locked)", path, lock_type_str); return 0; } if (err_is_lock_timeout(started, timeout_secs)) { errno = EAGAIN; *error_r = t_strdup_printf("flock(%s, %s) failed: " "Timed out after %u seconds%s", path, lock_type_str, timeout_secs, file_lock_find(fd, set->lock_method, lock_type)); return 0; } *error_r = t_strdup_printf("flock(%s, %s) failed: %m", path, lock_type_str); if (errno == EDEADLK && !set->allow_deadlock) { i_panic("%s%s", *error_r, file_lock_find(fd, set->lock_method, lock_type)); } return -1; #endif } case FILE_LOCK_METHOD_DOTLOCK: /* we shouldn't get here */ i_unreached(); } return 1; } int file_wait_lock(int fd, const char *path, int lock_type, const struct file_lock_settings *set, unsigned int timeout_secs, struct file_lock **lock_r, const char **error_r) { struct file_lock *lock; int ret; ret = file_lock_do(fd, path, lock_type, set, timeout_secs, error_r); if (ret <= 0) return ret; lock = i_new(struct file_lock, 1); lock->set = *set; lock->fd = fd; lock->path = i_strdup(path); lock->lock_type = lock_type; i_gettimeofday(&lock->locked_time); *lock_r = lock; return 1; } int file_lock_try_update(struct file_lock *lock, int lock_type) { const char *error; int ret; ret = file_lock_do(lock->fd, lock->path, lock_type, &lock->set, 0, &error); if (ret <= 0) return ret; file_lock_log_warning_if_slow(lock); lock->lock_type = lock_type; return 1; } void file_lock_set_unlink_on_free(struct file_lock *lock, bool set) { lock->set.unlink_on_free = set; } void file_lock_set_close_on_free(struct file_lock *lock, bool set) { lock->set.close_on_free = set; } struct file_lock *file_lock_from_dotlock(struct dotlock **dotlock) { struct file_lock *lock; lock = i_new(struct file_lock, 1); lock->set.lock_method = FILE_LOCK_METHOD_DOTLOCK; lock->fd = -1; lock->path = i_strdup(file_dotlock_get_lock_path(*dotlock)); lock->lock_type = F_WRLCK; i_gettimeofday(&lock->locked_time); lock->dotlock = *dotlock; *dotlock = NULL; return lock; } static void file_unlock_real(struct file_lock *lock) { const char *error; if (file_lock_do(lock->fd, lock->path, F_UNLCK, &lock->set, 0, &error) == 0) { /* this shouldn't happen */ i_error("file_unlock(%s) failed: %m", lock->path); } } void file_unlock(struct file_lock **_lock) { struct file_lock *lock = *_lock; *_lock = NULL; /* unlocking is unnecessary when the file is unlinked. or alternatively the unlink() must be done before unlocking, because otherwise it could be deleting the new lock. */ i_assert(!lock->set.unlink_on_free); if (lock->dotlock == NULL) file_unlock_real(lock); file_lock_free(&lock); } static void file_try_unlink_locked(struct file_lock *lock) { struct file_lock *temp_lock = NULL; struct file_lock_settings temp_set = lock->set; struct stat st1, st2; const char *error; int ret; temp_set.close_on_free = FALSE; temp_set.unlink_on_free = FALSE; file_unlock_real(lock); ret = file_try_lock(lock->fd, lock->path, F_WRLCK, &temp_set, &temp_lock, &error); if (ret < 0) { i_error("file_lock_free(): Unexpectedly failed to retry locking %s: %s", lock->path, error); } else if (ret == 0) { /* already locked by someone else */ } else if (fstat(lock->fd, &st1) < 0) { /* not expected to happen */ i_error("file_lock_free(): fstat(%s) failed: %m", lock->path); } else if (stat(lock->path, &st2) < 0) { if (errno != ENOENT) i_error("file_lock_free(): stat(%s) failed: %m", lock->path); } else if (st1.st_ino != st2.st_ino || !CMP_DEV_T(st1.st_dev, st2.st_dev)) { /* lock file was recreated already - don't delete it */ } else { /* nobody was waiting on the lock - unlink it */ i_unlink(lock->path); } file_lock_free(&temp_lock); } void file_lock_free(struct file_lock **_lock) { struct file_lock *lock = *_lock; if (lock == NULL) return; *_lock = NULL; if (lock->dotlock != NULL) file_dotlock_delete(&lock->dotlock); if (lock->set.unlink_on_free) file_try_unlink_locked(lock); if (lock->set.close_on_free) i_close_fd(&lock->fd); file_lock_log_warning_if_slow(lock); i_free(lock->path); i_free(lock); } const char *file_lock_get_path(struct file_lock *lock) { return lock->path; } void file_lock_set_path(struct file_lock *lock, const char *path) { if (path != lock->path) { i_free(lock->path); lock->path = i_strdup(path); } } void file_lock_wait_start(void) { i_assert(lock_wait_start.tv_sec == 0); i_gettimeofday(&lock_wait_start); } static void file_lock_wait_init_warning(void) { const char *value; i_assert(file_lock_slow_warning_usecs == -1); value = getenv("FILE_LOCK_SLOW_WARNING_MSECS"); if (value == NULL) file_lock_slow_warning_usecs = LLONG_MAX; else if (str_to_llong(value, &file_lock_slow_warning_usecs) == 0 && file_lock_slow_warning_usecs > 0) { file_lock_slow_warning_usecs *= 1000; } else { i_error("FILE_LOCK_SLOW_WARNING_MSECS: " "Invalid value '%s' - ignoring", value); file_lock_slow_warning_usecs = LLONG_MAX; } } static void file_lock_log_warning_if_slow(struct file_lock *lock) { if (file_lock_slow_warning_usecs < 0) file_lock_wait_init_warning(); if (file_lock_slow_warning_usecs == LLONG_MAX) { /* slowness checking is disabled */ return; } if (lock->lock_type != F_WRLCK) { /* some shared locks can legitimately be kept for a long time. don't warn about them. */ return; } struct timeval now; i_gettimeofday(&now); int diff = timeval_diff_msecs(&now, &lock->locked_time); if (diff > file_lock_slow_warning_usecs/1000) { i_warning("Lock %s kept for %d.%03d secs", lock->path, diff / 1000, diff % 1000); } } void file_lock_wait_end(const char *lock_name) { struct timeval now; i_assert(lock_wait_start.tv_sec != 0); i_gettimeofday(&now); long long diff = timeval_diff_usecs(&now, &lock_wait_start); if (diff < 0) { /* time moved backwards */ diff = 0; } if (diff > file_lock_slow_warning_usecs) { if (file_lock_slow_warning_usecs < 0) file_lock_wait_init_warning(); if (diff > file_lock_slow_warning_usecs) { int diff_msecs = (diff + 999) / 1000; i_warning("Locking %s took %d.%03d secs", lock_name, diff_msecs / 1000, diff_msecs % 1000); } } file_lock_wait_usecs += diff; lock_wait_start.tv_sec = 0; } uint64_t file_lock_wait_get_total_usecs(void) { return file_lock_wait_usecs; } dovecot-2.3.21.1/src/lib/test-priorityq.c0000644000000000000000000000535114656633576015041 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "priorityq.h" struct pq_test_item { struct priorityq_item item; int num; }; static int cmp_int(const void *p1, const void *p2) { const struct pq_test_item *i1 = p1, *i2 = p2; return i1->num - i2->num; } void test_priorityq(void) { #define PQ_MAX_ITEMS 100 static const int input[] = { 1, 2, 3, 4, 5, 6, 7, 8, -1, 8, 7, 6, 5, 4, 3, 2, 1, -1, 8, 7, 5, 6, 1, 3, 4, 2, -1, -1 }; static const int output[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; struct pq_test_item *item, items[PQ_MAX_ITEMS]; struct priorityq_item *const *all_items; unsigned int i, j; struct priorityq *pq; pool_t pool; int prev; pool = pool_alloconly_create("priorityq items", 1024); /* simple tests with popping only */ test_begin("priorityq"); for (i = 0; input[i] != -1; i++) { p_clear(pool); pq = priorityq_init(cmp_int, 1); for (j = 0; input[i] != -1; i++, j++) { test_assert(priorityq_count(pq) == j); item = p_new(pool, struct pq_test_item, 1); item->num = input[i]; priorityq_add(pq, &item->item); } all_items = priorityq_items(pq); test_assert(priorityq_count(pq) == N_ELEMENTS(output)); item = (struct pq_test_item *)all_items[0]; test_assert(item->num == output[0]); for (j = 1; j < N_ELEMENTS(output); j++) { item = (struct pq_test_item *)all_items[j]; test_assert(item->num > output[0]); test_assert(item->num <= output[N_ELEMENTS(output)-1]); } for (j = 0; j < N_ELEMENTS(output); j++) { test_assert(priorityq_count(pq) == N_ELEMENTS(output) - j); item = (struct pq_test_item *)priorityq_peek(pq); i_assert(item != NULL); test_assert(output[j] == item->num); item = (struct pq_test_item *)priorityq_pop(pq); i_assert(item != NULL); test_assert(output[j] == item->num); } test_assert(priorityq_count(pq) == 0); test_assert(priorityq_peek(pq) == NULL); test_assert(priorityq_pop(pq) == NULL); priorityq_deinit(&pq); } test_end(); /* randomized tests, remove elements */ test_begin("priorityq randomized"); for (i = 0; i < 100; i++) { pq = priorityq_init(cmp_int, 1); for (j = 0; j < PQ_MAX_ITEMS; j++) { items[j].num = i_rand_limit(INT_MAX); priorityq_add(pq, &items[j].item); } for (j = 0; j < PQ_MAX_ITEMS; j++) { if (i_rand_limit(3) == 0) { priorityq_remove(pq, &items[j].item); items[j].num = -1; } } prev = 0; while (priorityq_count(pq) > 0) { item = (struct pq_test_item *)priorityq_pop(pq); i_assert(item != NULL); test_assert(item->num >= 0 && prev <= item->num); prev = item->num; item->num = -1; } for (j = 0; j < PQ_MAX_ITEMS; j++) { test_assert(items[j].num == -1); } priorityq_deinit(&pq); } test_end(); pool_unref(&pool); } dovecot-2.3.21.1/src/lib/event-log.c0000644000000000000000000003033314656633576013720 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "event-filter.h" #include "lib-event-private.h" unsigned int event_filter_replace_counter = 1; static struct event_filter *global_debug_log_filter = NULL; static struct event_filter *global_debug_send_filter = NULL; static struct event_filter *global_core_log_filter = NULL; #undef e_error void e_error(struct event *event, const char *source_filename, unsigned int source_linenum, const char *fmt, ...) { struct event_log_params params = { .log_type = LOG_TYPE_ERROR, .source_filename = source_filename, .source_linenum = source_linenum, }; va_list args; va_start(args, fmt); T_BEGIN { event_logv(event, ¶ms, fmt, args); } T_END; va_end(args); } #undef e_warning void e_warning(struct event *event, const char *source_filename, unsigned int source_linenum, const char *fmt, ...) { struct event_log_params params = { .log_type = LOG_TYPE_WARNING, .source_filename = source_filename, .source_linenum = source_linenum, }; va_list args; va_start(args, fmt); T_BEGIN { event_logv(event, ¶ms, fmt, args); } T_END; va_end(args); } #undef e_info void e_info(struct event *event, const char *source_filename, unsigned int source_linenum, const char *fmt, ...) { struct event_log_params params = { .log_type = LOG_TYPE_INFO, .source_filename = source_filename, .source_linenum = source_linenum, }; va_list args; va_start(args, fmt); T_BEGIN { event_logv(event, ¶ms, fmt, args); } T_END; va_end(args); } #undef e_debug void e_debug(struct event *event, const char *source_filename, unsigned int source_linenum, const char *fmt, ...) { struct event_log_params params = { .log_type = LOG_TYPE_DEBUG, .source_filename = source_filename, .source_linenum = source_linenum, }; va_list args; va_start(args, fmt); T_BEGIN { event_logv(event, ¶ms, fmt, args); } T_END; va_end(args); } #undef e_log void e_log(struct event *event, enum log_type level, const char *source_filename, unsigned int source_linenum, const char *fmt, ...) { struct event_log_params params = { .log_type = level, .source_filename = source_filename, .source_linenum = source_linenum, }; va_list args; va_start(args, fmt); T_BEGIN { event_logv(event, ¶ms, fmt, args); } T_END; va_end(args); } struct event_get_log_message_context { const struct event_log_params *params; string_t *log_prefix; const char *message; unsigned int type_pos; bool replace_prefix:1; bool str_out_done:1; }; static inline void ATTR_FORMAT(2, 0) event_get_log_message_str_out(struct event_get_log_message_context *glmctx, const char *fmt, va_list args) { const struct event_log_params *params = glmctx->params; string_t *str_out = params->base_str_out; /* The message is appended once in full, rather than incremental during the recursion. */ if (glmctx->str_out_done || str_out == NULL) return; /* append the current log prefix to the string buffer */ if (params->base_str_prefix != NULL && !glmctx->replace_prefix) str_append(str_out, params->base_str_prefix); str_append_str(str_out, glmctx->log_prefix); if (glmctx->message != NULL) { /* a child event already constructed a message */ str_append(str_out, glmctx->message); } else { va_list args_copy; /* construct message from format and arguments */ VA_COPY(args_copy, args); str_vprintfa(str_out, fmt, args_copy); va_end(args_copy); } /* finished with the string buffer */ glmctx->str_out_done = TRUE; } static bool ATTR_FORMAT(4, 0) event_get_log_message(struct event *event, struct event_get_log_message_context *glmctx, unsigned int prefixes_dropped, const char *fmt, va_list args) { const struct event_log_params *params = glmctx->params; const char *prefix = event->log_prefix; bool ret = FALSE; /* Reached the base event? */ if (event == params->base_event) { /* Append the message to the provided string buffer. */ event_get_log_message_str_out(glmctx, fmt, args); /* Insert the base send prefix */ if (params->base_send_prefix != NULL) { str_insert(glmctx->log_prefix, 0, params->base_send_prefix); ret = TRUE; } } /* Call the message amendment callback for this event if there is one. */ if (event->log_message_callback != NULL) { const char *in_message; /* construct the log message composed by children and arguments */ if (glmctx->message == NULL) { str_vprintfa(glmctx->log_prefix, fmt, args); in_message = str_c(glmctx->log_prefix); } else if (str_len(glmctx->log_prefix) == 0) { in_message = glmctx->message; } else { str_append(glmctx->log_prefix, glmctx->message); in_message = str_c(glmctx->log_prefix); } /* reformat the log message */ glmctx->message = event->log_message_callback( event->log_message_callback_context, glmctx->params->log_type, in_message); /* continue with a cleared prefix buffer (as prefix is now part of *message_r). */ str_truncate(glmctx->log_prefix, 0); ret = TRUE; } if (event->log_prefix_callback != NULL) { prefix = event->log_prefix_callback( event->log_prefix_callback_context); } if (event->log_prefix_replace) { /* this event replaces all parent log prefixes */ glmctx->replace_prefix = TRUE; glmctx->type_pos = (prefix == NULL ? 0 : strlen(prefix)); event_get_log_message_str_out(glmctx, fmt, args); } if (prefix != NULL) { if (event->log_prefix_replace || prefixes_dropped == 0) { str_insert(glmctx->log_prefix, 0, prefix); ret = TRUE; } else if (prefixes_dropped > 0) { prefixes_dropped--; } } if (event->parent == NULL) { event_get_log_message_str_out(glmctx, fmt, args); if (params->base_event == NULL && params->base_send_prefix != NULL && !glmctx->replace_prefix) { str_insert(glmctx->log_prefix, 0, params->base_send_prefix); ret = TRUE; } } else if (!event->log_prefix_replace && (!params->no_send || !glmctx->str_out_done)) { prefixes_dropped += event->log_prefixes_dropped; if (event_get_log_message(event->parent, glmctx, prefixes_dropped, fmt, args)) ret = TRUE; } return ret; } void event_log(struct event *event, const struct event_log_params *params, const char *fmt, ...) { va_list args; va_start(args, fmt); event_logv(event, params, fmt, args); va_end(args); } #undef event_want_log_level bool event_want_log_level(struct event *event, enum log_type level, const char *source_filename, unsigned int source_linenum) { struct failure_context ctx = { .type = LOG_TYPE_DEBUG }; if (level >= event->min_log_level) { /* Always log when level is at least this high */ return TRUE; } if (event->debug_level_checked_filter_counter == event_filter_replace_counter) { /* Log filters haven't changed since we last checked this, so we can rely on the last cached value. FIXME: this doesn't work correctly if event changes and the change affects whether the filters would match. */ return event->sending_debug_log; } event->debug_level_checked_filter_counter = event_filter_replace_counter; if (event->forced_debug) { /* Debugging is forced for this event (and its children) */ event->sending_debug_log = TRUE; } else if (global_debug_log_filter != NULL && event_filter_match_source(global_debug_log_filter, event, source_filename, source_linenum, &ctx)) { /* log_debug filter matched */ event->sending_debug_log = TRUE; } else if (global_core_log_filter != NULL && event_filter_match_source(global_core_log_filter, event, source_filename, source_linenum, &ctx)) { /* log_core_filter matched */ event->sending_debug_log = TRUE; } else { event->sending_debug_log = FALSE; } return event->sending_debug_log; } #undef event_want_level bool event_want_level(struct event *event, enum log_type level, const char *source_filename, unsigned int source_linenum) { if (event_want_log_level(event, level, source_filename, source_linenum)) return TRUE; /* see if debug send filtering matches */ if (global_debug_send_filter != NULL) { struct failure_context ctx = { .type = LOG_TYPE_DEBUG }; if (event_filter_match_source(global_debug_send_filter, event, source_filename, source_linenum, &ctx)) return TRUE; } return FALSE; } static void ATTR_FORMAT(3, 0) event_logv_params(struct event *event, const struct event_log_params *params, const char *fmt, va_list args) { struct event_get_log_message_context glmctx; struct failure_context ctx = { .type = params->log_type, }; bool abort_after_event = FALSE; i_assert(!params->no_send || params->base_str_out != NULL); if (global_core_log_filter != NULL && event_filter_match_source(global_core_log_filter, event, event->source_filename, event->source_linenum, &ctx)) abort_after_event = TRUE; i_zero(&glmctx); glmctx.params = params; glmctx.log_prefix = t_str_new(64); if (!event_get_log_message(event, &glmctx, 0, fmt, args)) { /* keep log prefix as it is */ if (params->base_str_out != NULL && !glmctx.str_out_done) { va_list args_copy; VA_COPY(args_copy, args); str_vprintfa(params->base_str_out, fmt, args_copy); va_end(args_copy); } if (!params->no_send) event_vsend(event, &ctx, fmt, args); } else if (params->no_send) { /* don't send the event */ } else if (glmctx.replace_prefix) { /* event overrides the log prefix (even if it's "") */ ctx.log_prefix = str_c(glmctx.log_prefix); ctx.log_prefix_type_pos = glmctx.type_pos; if (glmctx.message != NULL) event_send(event, &ctx, "%s", glmctx.message); else event_vsend(event, &ctx, fmt, args); } else { /* append to log prefix, but don't fully replace it */ if (glmctx.message != NULL) str_append(glmctx.log_prefix, glmctx.message); else str_vprintfa(glmctx.log_prefix, fmt, args); event_send(event, &ctx, "%s", str_c(glmctx.log_prefix)); } if (abort_after_event) abort(); } void event_logv(struct event *event, const struct event_log_params *params, const char *fmt, va_list args) { const char *orig_source_filename = event->source_filename; unsigned int orig_source_linenum = event->source_linenum; int old_errno = errno; if (params->source_filename != NULL) { event_set_source(event, params->source_filename, params->source_linenum, TRUE); } (void)event_want_log_level(event, params->log_type, event->source_filename, event->source_linenum); event_ref(event); event_logv_params(event, params, fmt, args); event_set_source(event, orig_source_filename, orig_source_linenum, TRUE); event_unref(&event); errno = old_errno; } struct event *event_set_forced_debug(struct event *event, bool force) { if (force) event->forced_debug = TRUE; event_recalculate_debug_level(event); return event; } struct event *event_unset_forced_debug(struct event *event) { event->forced_debug = FALSE; event_recalculate_debug_level(event); return event; } void event_set_global_debug_log_filter(struct event_filter *filter) { event_unset_global_debug_log_filter(); global_debug_log_filter = filter; event_filter_ref(global_debug_log_filter); event_filter_replace_counter++; } struct event_filter *event_get_global_debug_log_filter(void) { return global_debug_log_filter; } void event_unset_global_debug_log_filter(void) { event_filter_unref(&global_debug_log_filter); event_filter_replace_counter++; } void event_set_global_debug_send_filter(struct event_filter *filter) { event_unset_global_debug_send_filter(); global_debug_send_filter = filter; event_filter_ref(global_debug_send_filter); event_filter_replace_counter++; } struct event_filter *event_get_global_debug_send_filter(void) { return global_debug_send_filter; } void event_unset_global_debug_send_filter(void) { event_filter_unref(&global_debug_send_filter); event_filter_replace_counter++; } void event_set_global_core_log_filter(struct event_filter *filter) { event_unset_global_core_log_filter(); global_core_log_filter = filter; event_filter_ref(global_core_log_filter); event_filter_replace_counter++; } struct event_filter *event_get_global_core_log_filter(void) { return global_core_log_filter; } void event_unset_global_core_log_filter(void) { event_filter_unref(&global_core_log_filter); event_filter_replace_counter++; } dovecot-2.3.21.1/src/lib/unlink-directory.c0000644000000000000000000001633314656633576015326 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ /* There's a bit tricky race condition with recursive deletion. Suppose this happens: lstat(dir, ..) -> OK, it's a directory // attacker deletes dir, replaces it with symlink to / opendir(dir) -> it actually opens / Most portable solution is to lstat() the dir, chdir() there, then check that "." points to same device/inode as we originally lstat()ed. This assumes that the device has usable inodes, most should except for some NFS implementations. Filesystems may also reassign a deleted inode to another file immediately after it's deleted. That in theory makes it possible to exploit this race to delete the new directory. However, the new inode is quite unlikely to be any important directory, and attacker is quite unlikely to find out which directory even got the inode. Maybe with some setuid program or daemon interaction something could come out of it though. Another less portable solution is to fchdir(open(dir, O_NOFOLLOW)). This should be completely safe. The actual file deletion also has to be done relative to current directory, to make sure that the whole directory structure isn't replaced with another one while we're deleting it. Going back to parent directory isn't too easy either - safest (and easiest) way again is to open() the directory and fchdir() back there. */ #define _GNU_SOURCE /* for O_NOFOLLOW with Linux */ #include "lib.h" #include "path-util.h" #include "unlink-directory.h" #include #include #include #include #define ERROR_FORMAT "%s(%s) failed: %m" #define ERROR_FORMAT_DNAME "%s(%s/%s) failed: %m" static void ATTR_FORMAT(3,4) unlink_directory_error(const char **error, int *first_errno, const char *fmt, ...) { va_list args; va_start(args, fmt); const char *err = t_strdup_vprintf(fmt, args); if (*error == NULL) { if (first_errno != NULL) *first_errno = errno; *error = err; } else i_error("%s", err); va_end(args); } static int unlink_directory_r(const char *dir, enum unlink_directory_flags flags, const char **error) { DIR *dirp; struct dirent *d; struct stat st; int dir_fd, old_errno; #ifdef O_NOFOLLOW dir_fd = open(dir, O_RDONLY | O_NOFOLLOW); if (dir_fd == -1) { unlink_directory_error(error, NULL, "open(%s, O_RDONLY | O_NOFOLLOW) failed: %m", dir); return -1; } #else struct stat st2; if (lstat(dir, &st) < 0) { unlink_directory_error(error_r, NULL, ERROR_FORMAT, "lstat", dir); return -1; } if (!S_ISDIR(st.st_mode)) { if ((st.st_mode & S_IFMT) != S_IFLNK) { unlink_directory_error(error_r, NULL, "%s is not a directory: %s", dir); errno = ENOTDIR; } else { /* be compatible with O_NOFOLLOW */ errno = ELOOP; unlink_directory_error(error_r, NULL, "%s is a symlink, not a directory: %s", dir); } return -1; } dir_fd = open(dir, O_RDONLY); if (dir_fd == -1) { unlink_directory_error(error_r, NULL, "open(%s, O_RDONLY) failed: %m", dir); return -1; } if (fstat(dir_fd, &st2) < 0) { i_close_fd(&dir_fd); unlink_directory_error(error_r, NULL, ERROR_FORMAT, "fstat", dir); return -1; } if (st.st_ino != st2.st_ino || !CMP_DEV_T(st.st_dev, st2.st_dev)) { /* directory was just replaced with something else. */ i_close_fd(&dir_fd); errno = ENOTDIR; unlink_directory_error(error_r, NULL, "%s race condition: directory was just replaced", dir); return -1; } #endif if (fchdir(dir_fd) < 0) { i_close_fd(&dir_fd); unlink_directory_error(error, NULL, ERROR_FORMAT, "fchdir", dir); return -1; } dirp = opendir("."); if (dirp == NULL) { i_close_fd(&dir_fd); unlink_directory_error(error, NULL, "opendir(.) (in %s) failed: %m", dir); return -1; } int first_errno = 0; for (;;) { errno = 0; d = readdir(dirp); if (d == NULL) { if (errno != 0) { unlink_directory_error(error, &first_errno, ERROR_FORMAT, "readdir", dir); } break; } if (d->d_name[0] == '.') { if ((d->d_name[1] == '\0' || (d->d_name[1] == '.' && d->d_name[2] == '\0'))) { /* skip . and .. */ continue; } if ((flags & UNLINK_DIRECTORY_FLAG_SKIP_DOTFILES) != 0) continue; } if (unlink(d->d_name) < 0 && errno != ENOENT) { old_errno = errno; if (lstat(d->d_name, &st) < 0) { if (errno != ENOENT) { unlink_directory_error(error, &first_errno, ERROR_FORMAT, "lstat", dir); break; } } else if (S_ISDIR(st.st_mode) && (flags & UNLINK_DIRECTORY_FLAG_FILES_ONLY) == 0) { if (unlink_directory_r(d->d_name, flags, error) < 0) { if (first_errno == 0) first_errno = errno; if (errno != ENOENT) break; } if (fchdir(dir_fd) < 0) { unlink_directory_error(error, &first_errno, ERROR_FORMAT, "fchdir", dir); break; } if (rmdir(d->d_name) < 0 && errno != ENOENT) { if (errno == EEXIST) /* standardize errno */ errno = ENOTEMPTY; unlink_directory_error(error, &first_errno, ERROR_FORMAT, "rmdir", dir); break; } } else if (S_ISDIR(st.st_mode) && (flags & UNLINK_DIRECTORY_FLAG_FILES_ONLY) != 0) { /* skip directory */ } else if (old_errno == EBUSY && str_begins(d->d_name, ".nfs")) { /* can't delete NFS files that are still in use. let the caller decide if this error is worth logging about */ break; } else /* so it wasn't a directory */ unlink_directory_error(error, &first_errno, ERROR_FORMAT_DNAME, "unlink", dir, d->d_name); } } i_close_fd(&dir_fd); if (closedir(dirp) < 0) unlink_directory_error(error, &first_errno, ERROR_FORMAT, "closedir", dir); if (*error != NULL) { errno = first_errno; return -1; } return 0; } int unlink_directory(const char *dir, enum unlink_directory_flags flags, const char **error_r) { const char *orig_dir, *error; int fd, ret, old_errno; if (t_get_working_dir(&orig_dir, &error) < 0) { i_warning("Could not get working directory in unlink_directory(): %s", error); orig_dir = "."; } fd = open(".", O_RDONLY); if (fd == -1) { *error_r = t_strdup_printf( "Can't preserve current directory %s: " "open(.) failed: %m", orig_dir); return -1; } /* Cannot set error_r to NULL inside of unlink_directory_r() because of recursion */ *error_r = NULL; ret = unlink_directory_r(dir, flags, error_r); old_errno = errno; if (fchdir(fd) < 0) { i_fatal("unlink_directory(%s): " "Can't fchdir() back to our original dir %s: %m", dir, orig_dir); } i_close_fd(&fd); if (ret < 0) { errno = old_errno; return errno == ENOENT ? 0 : -1; } if ((flags & UNLINK_DIRECTORY_FLAG_RMDIR) != 0) { if (rmdir(dir) < 0 && errno != ENOENT) { *error_r = t_strdup_printf("rmdir(%s) failed: %m", dir); if (errno == EEXIST) { /* standardize errno */ errno = ENOTEMPTY; } return errno == ENOENT ? 0 : 1; } } return 1; } dovecot-2.3.21.1/src/lib/file-copy.h0000644000000000000000000000067114656633576013716 00000000000000#ifndef FILE_COPY_H #define FILE_COPY_H /* Copy file atomically. First try hardlinking, then fallback to creating a temporary file (destpath.tmp) and rename()ing it over srcpath. If the destination file already exists, it may or may not be overwritten, so that shouldn't be relied on. Returns -1 = error, 0 = source file not found, 1 = ok */ int file_copy(const char *srcpath, const char *destpath, bool try_hardlink); #endif dovecot-2.3.21.1/src/lib/wildcard-match.c0000644000000000000000000000556014656633576014707 00000000000000/* * This code would not have been possible without the prior work and * suggestions of various sourced. Special thanks to Robey for * all his time/help tracking down bugs and his ever-helpful advice. * * 04/09: Fixed the "*\*" against "*a" bug (caused an endless loop) * * Chris Fuller (aka Fred1@IRC & Fwitz@IRC) * crf@cfox.bchs.uh.edu * * I hereby release this code into the public domain * */ #include "lib.h" #include "wildcard-match.h" #include #define WILDS '*' /* matches 0 or more characters (including spaces) */ #define WILDQ '?' /* matches exactly one character */ #define NOMATCH 0 #define MATCH (match+sofar) static int wildcard_match_int(const char *data, const char *mask, bool icase) { const char *ma = mask, *na = data, *lsm = NULL, *lsn = NULL; int match = 1; int sofar = 0; if (na[0] == '\0') { /* empty string can match only "*" wildcard(s) */ while (ma[0] == '*') ma++; return ma[0] == '\0' ? MATCH : NOMATCH; } /* find the end of each string */ while (*(mask++) != '\0'); mask-=2; while (*(data++) != '\0'); data-=2; while (data >= na) { /* If the mask runs out of chars before the string, fall back on * a wildcard or fail. */ if (mask < ma) { if (lsm != NULL) { data = --lsn; mask = lsm; if (data < na) lsm = NULL; sofar = 0; } else return NOMATCH; } switch (*mask) { case WILDS: /* Matches anything */ do mask--; /* Zap redundant wilds */ while ((mask >= ma) && (*mask == WILDS)); lsm = mask; lsn = data; match += sofar; sofar = 0; /* Update fallback pos */ if (mask < ma) return MATCH; continue; /* Next char, please */ case WILDQ: mask--; data--; continue; /* '?' always matches */ } if (icase ? (i_toupper(*mask) == i_toupper(*data)) : (*mask == *data)) { /* If matching char */ mask--; data--; sofar++; /* Tally the match */ continue; /* Next char, please */ } if (lsm != NULL) { /* To to fallback on '*' */ data = --lsn; mask = lsm; if (data < na) lsm = NULL; /* Rewind to saved pos */ sofar = 0; continue; /* Next char, please */ } return NOMATCH; /* No fallback=No match */ } while ((mask >= ma) && (*mask == WILDS)) mask--; /* Zap leftover %s & *s */ return (mask >= ma) ? NOMATCH : MATCH; /* Start of both = match */ } bool wildcard_match(const char *data, const char *mask) { return wildcard_match_int(data, mask, FALSE) != 0; } bool wildcard_match_icase(const char *data, const char *mask) { return wildcard_match_int(data, mask, TRUE) != 0; } dovecot-2.3.21.1/src/lib/str.c0000644000000000000000000000714014656633576012630 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "printf-format-fix.h" #include "unichar.h" #include "str.h" #include string_t *str_new(pool_t pool, size_t initial_size) { /* never allocate a 0 byte size buffer. this is especially important when str_c() is called on an empty string from a different stack frame (see the comment in buffer.c about this). */ return buffer_create_dynamic(pool, I_MAX(initial_size, 1)); } string_t *str_new_const(pool_t pool, const char *str, size_t len) { string_t *ret; i_assert(str[len] == '\0'); ret = p_new(pool, buffer_t, 1); buffer_create_from_const_data(ret, str, len + 1); str_truncate(ret, len); return ret; } string_t *t_str_new(size_t initial_size) { return str_new(pool_datastack_create(), initial_size); } string_t *t_str_new_const(const char *str, size_t len) { return str_new_const(pool_datastack_create(), str, len); } void str_free(string_t **str) { if (str == NULL || *str == NULL) return; buffer_free(str); } static void str_add_nul(string_t *str) { const unsigned char *data = str_data(str); size_t len = str_len(str); size_t alloc = buffer_get_size(str); if (len == alloc || data[len] != '\0') { buffer_write(str, len, "", 1); /* remove the \0 - we don't want to keep it */ buffer_set_used_size(str, len); } } char *str_free_without_data(string_t **str) { str_add_nul(*str); return buffer_free_without_data(str); } const char *str_c(string_t *str) { str_add_nul(str); return str->data; } char *str_c_modifiable(string_t *str) { str_add_nul(str); return buffer_get_modifiable_data(str, NULL); } bool str_equals(const string_t *str1, const string_t *str2) { if (str1->used != str2->used) return FALSE; return memcmp(str1->data, str2->data, str1->used) == 0; } void str_append_max(string_t *str, const char *cstr, size_t max_len) { const char *p; size_t len; p = memchr(cstr, '\0', max_len); if (p == NULL) len = max_len; else len = p - (const char *)cstr; buffer_append(str, cstr, len); } void str_printfa(string_t *str, const char *fmt, ...) { va_list args; va_start(args, fmt); str_vprintfa(str, fmt, args); va_end(args); } void str_vprintfa(string_t *str, const char *fmt, va_list args) { #define SNPRINTF_INITIAL_EXTRA_SIZE 128 va_list args2; char *tmp; size_t init_size; size_t pos = str->used; int ret, ret2; VA_COPY(args2, args); /* the format string is modified only if %m exists in it. it happens only in error conditions, so don't try to t_push() here since it'll just slow down the normal code path. */ fmt = printf_format_fix_get_len(fmt, &init_size); init_size += SNPRINTF_INITIAL_EXTRA_SIZE; /* @UNSAFE */ if (pos+init_size > buffer_get_writable_size(str) && pos < buffer_get_writable_size(str)) { /* avoid growing buffer larger if possible. this is also required if buffer isn't dynamically growing. */ init_size = buffer_get_writable_size(str)-pos; } tmp = buffer_get_space_unsafe(str, pos, init_size); ret = vsnprintf(tmp, init_size, fmt, args); i_assert(ret >= 0); if ((unsigned int)ret >= init_size) { /* didn't fit with the first guess. now we know the size, so try again. */ tmp = buffer_get_space_unsafe(str, pos, ret + 1); ret2 = vsnprintf(tmp, ret + 1, fmt, args2); i_assert(ret2 == ret); } va_end(args2); /* drop the unused data, including terminating NUL */ buffer_set_used_size(str, pos + ret); } void str_truncate_utf8(string_t *str, size_t len) { size_t size = str_len(str); if (size <= len) return; str_truncate(str, uni_utf8_data_truncate(str_data(str), size, len)); } dovecot-2.3.21.1/src/lib/event-log.h0000644000000000000000000001436714656633576013736 00000000000000#ifndef EVENT_LOG_H #define EVENT_LOG_H struct event_filter; #include "lib-event.h" struct event_log_params { enum log_type log_type; const char *source_filename; unsigned int source_linenum; /* Base event used as a reference for base_* parameters (see below) */ struct event *base_event; /* Append the event message to base_str_out in addition to emitting the event as normal. The message appended to the string buffer includes prefixes and message callback modifications by parent events up until the base_event. The event is otherwise sent as normal with the full prefixes and all modifications up to the root event (unless no_send=TRUE). This is primarily useful to mimic (part of) event logging in parallel logs that are visible to users. */ string_t *base_str_out; /* Prefix inserted at the base_event for the sent log message. */ const char *base_send_prefix; /* Prefix inserted at the base_event for the log message appended to the string buffer. */ const char *base_str_prefix; /* Don't actually send the event; only append to the provided string buffer (base_str_out must not be NULL). */ bool no_send:1; }; /* Increased every time global event filters have changed. */ extern unsigned int event_filter_replace_counter; void e_error(struct event *event, const char *source_filename, unsigned int source_linenum, const char *fmt, ...) ATTR_FORMAT(4, 5); #define e_error(_event, ...) STMT_START { \ struct event *_tmp_event = (_event); \ if (event_want_level(_tmp_event, LOG_TYPE_ERROR)) \ e_error(_tmp_event, __FILE__, __LINE__, __VA_ARGS__); \ else \ event_send_abort(_tmp_event); \ } STMT_END void e_warning(struct event *event, const char *source_filename, unsigned int source_linenum, const char *fmt, ...) ATTR_FORMAT(4, 5); #define e_warning(_event, ...) STMT_START { \ struct event *_tmp_event = (_event); \ if (event_want_level(_tmp_event, LOG_TYPE_WARNING)) \ e_warning(_tmp_event, __FILE__, __LINE__, __VA_ARGS__); \ else \ event_send_abort(_tmp_event); \ } STMT_END void e_info(struct event *event, const char *source_filename, unsigned int source_linenum, const char *fmt, ...) ATTR_FORMAT(4, 5); #define e_info(_event, ...) STMT_START { \ struct event *_tmp_event = (_event); \ if (event_want_level(_tmp_event, LOG_TYPE_INFO)) \ e_info(_tmp_event, __FILE__, __LINE__, __VA_ARGS__); \ else \ event_send_abort(_tmp_event); \ } STMT_END void e_debug(struct event *event, const char *source_filename, unsigned int source_linenum, const char *fmt, ...) ATTR_FORMAT(4, 5); #define e_debug(_event, ...) STMT_START { \ struct event *_tmp_event = (_event); \ if (event_want_debug(_tmp_event)) \ e_debug(_tmp_event, __FILE__, __LINE__, __VA_ARGS__); \ else \ event_send_abort(_tmp_event); \ } STMT_END void e_log(struct event *event, enum log_type level, const char *source_filename, unsigned int source_linenum, const char *fmt, ...) ATTR_FORMAT(5, 6); #define e_log(_event, level, ...) STMT_START { \ struct event *_tmp_event = (_event); \ if (event_want_level(_tmp_event, level)) \ e_log(_tmp_event, level, __FILE__, __LINE__, __VA_ARGS__); \ else \ event_send_abort(_tmp_event); \ } STMT_END /* Returns TRUE if event should be logged. Typically event_want_debug_log() could be used in deciding whether to build an expensive debug log message (e.g. requires extra disk IO). Note that if this is used, the actual event being sent won't be matched against event filters because it's never called. The result of the check is cached in the event, so repeated calls are efficient. */ bool event_want_log_level(struct event *event, enum log_type level, const char *source_filename, unsigned int source_linenum); #define event_want_log_level(_event, level) event_want_log_level((_event), (level), __FILE__, __LINE__) #define event_want_debug_log(_event) event_want_log_level((_event), LOG_TYPE_DEBUG) /* Returns TRUE if event should be processed (for logging or sending to stats). The logging is checked with event_want_log_level() with the same caching behavior. */ bool event_want_level(struct event *event, enum log_type level, const char *source_filename, unsigned int source_linenum); #define event_want_level(_event, level) event_want_level((_event), (level), __FILE__, __LINE__) #define event_want_debug(_event) event_want_level((_event), LOG_TYPE_DEBUG) void event_log(struct event *event, const struct event_log_params *params, const char *fmt, ...) ATTR_FORMAT(3, 4); void event_logv(struct event *event, const struct event_log_params *params, const char *fmt, va_list args) ATTR_FORMAT(3, 0); /* If debugging is forced, the global debug log filter is ignored. Changing this applies only to this event and any child event that is created afterwards. It doesn't apply to existing child events (mainly for performance reasons). Note that event_set_forced_debug(event, FALSE) is a no-op. To disable forced-debug, use event_unset_forced_debug(event). */ struct event *event_set_forced_debug(struct event *event, bool force); /* Set the forced-debug to FALSE */ struct event *event_unset_forced_debug(struct event *event); /* Set the global filter to logging debug events. */ void event_set_global_debug_log_filter(struct event_filter *filter); /* Return the current global debug log event filter. */ struct event_filter *event_get_global_debug_log_filter(void); /* Unset global debug log filter, if one exists. */ void event_unset_global_debug_log_filter(void); /* Set the global filter to sending debug events. The debug events are also sent if they match the global debug log filter. */ void event_set_global_debug_send_filter(struct event_filter *filter); /* Return the current global debug send event filter. */ struct event_filter *event_get_global_debug_send_filter(void); /* Unset global debug send filter, if one exists. */ void event_unset_global_debug_send_filter(void); /* Set/replace the global core filter, which abort()s on matching events. */ void event_set_global_core_log_filter(struct event_filter *filter); /* Return the current global core filter. */ struct event_filter *event_get_global_core_log_filter(void); /* Unset the global core filter, if one exists. */ void event_unset_global_core_log_filter(void); #endif dovecot-2.3.21.1/src/lib/crc32.h0000644000000000000000000000045214656633576012740 00000000000000#ifndef CRC32_H #define CRC32_H uint32_t crc32_data(const void *data, size_t size) ATTR_PURE; uint32_t crc32_str(const char *str) ATTR_PURE; uint32_t crc32_data_more(uint32_t crc, const void *data, size_t size) ATTR_PURE; uint32_t crc32_str_more(uint32_t crc, const char *str) ATTR_PURE; #endif dovecot-2.3.21.1/src/lib/sha2.c0000644000000000000000000004235114656633576012660 00000000000000/* * FIPS 180-2 SHA-224/256/384/512 implementation * Last update: 02/02/2007 * Issue date: 04/30/2005 * * Copyright (C) 2005, 2007 Olivier Gay * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "lib.h" #include "sha2.h" #define SHFR(x, n) (x >> n) #define ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n))) #define ROTL(x, n) ((x << n) | (x >> ((sizeof(x) << 3) - n))) #define CH(x, y, z) ((x & y) ^ (~x & z)) #define MAJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z)) #define SHA256_F1(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) #define SHA256_F2(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) #define SHA256_F3(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHFR(x, 3)) #define SHA256_F4(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHFR(x, 10)) #define SHA384_F1(x) (ROTR(x, 28) ^ ROTR(x, 34) ^ ROTR(x, 39)) #define SHA384_F2(x) (ROTR(x, 14) ^ ROTR(x, 18) ^ ROTR(x, 41)) #define SHA384_F3(x) (ROTR(x, 1) ^ ROTR(x, 8) ^ SHFR(x, 7)) #define SHA384_F4(x) (ROTR(x, 19) ^ ROTR(x, 61) ^ SHFR(x, 6)) #define SHA512_F1(x) (ROTR(x, 28) ^ ROTR(x, 34) ^ ROTR(x, 39)) #define SHA512_F2(x) (ROTR(x, 14) ^ ROTR(x, 18) ^ ROTR(x, 41)) #define SHA512_F3(x) (ROTR(x, 1) ^ ROTR(x, 8) ^ SHFR(x, 7)) #define SHA512_F4(x) (ROTR(x, 19) ^ ROTR(x, 61) ^ SHFR(x, 6)) #define UNPACK32(x, str) \ { \ *((str) + 3) = (uint8_t) ((x) ); \ *((str) + 2) = (uint8_t) ((x) >> 8); \ *((str) + 1) = (uint8_t) ((x) >> 16); \ *((str) + 0) = (uint8_t) ((x) >> 24); \ } #define PACK32(str, x) \ { \ *(x) = ((uint32_t) *((str) + 3) ) \ | ((uint32_t) *((str) + 2) << 8) \ | ((uint32_t) *((str) + 1) << 16) \ | ((uint32_t) *((str) + 0) << 24); \ } #define UNPACK64(x, str) \ { \ *((str) + 7) = (uint8_t) ((x) ); \ *((str) + 6) = (uint8_t) ((x) >> 8); \ *((str) + 5) = (uint8_t) ((x) >> 16); \ *((str) + 4) = (uint8_t) ((x) >> 24); \ *((str) + 3) = (uint8_t) ((x) >> 32); \ *((str) + 2) = (uint8_t) ((x) >> 40); \ *((str) + 1) = (uint8_t) ((x) >> 48); \ *((str) + 0) = (uint8_t) ((x) >> 56); \ } #define PACK64(str, x) \ { \ *(x) = ((uint64_t) *((str) + 7) ) \ | ((uint64_t) *((str) + 6) << 8) \ | ((uint64_t) *((str) + 5) << 16) \ | ((uint64_t) *((str) + 4) << 24) \ | ((uint64_t) *((str) + 3) << 32) \ | ((uint64_t) *((str) + 2) << 40) \ | ((uint64_t) *((str) + 1) << 48) \ | ((uint64_t) *((str) + 0) << 56); \ } #define SHA256_SCR(i) \ { \ w[i] = SHA256_F4(w[i - 2]) + w[i - 7] \ + SHA256_F3(w[i - 15]) + w[i - 16]; \ } #define SHA384_SCR(i) \ { \ w[i] = SHA512_F4(w[i - 2]) + w[i - 7] \ + SHA512_F3(w[i - 15]) + w[i - 16]; \ } #define SHA512_SCR(i) \ { \ w[i] = SHA512_F4(w[i - 2]) + w[i - 7] \ + SHA512_F3(w[i - 15]) + w[i - 16]; \ } static const uint32_t sha256_h0[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; static const uint64_t sha384_h0[8] = {0xcbbb9d5dc1059ed8ULL, 0x629a292a367cd507ULL, 0x9159015a3070dd17ULL, 0x152fecd8f70e5939ULL, 0x67332667ffc00b31ULL, 0x8eb44a8768581511ULL, 0xdb0c2e0d64f98fa7ULL, 0x47b5481dbefa4fa4ULL}; static const uint64_t sha512_h0[8] = {0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, 0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL, 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL, 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL}; static const uint32_t sha256_k[64] = {0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; static const uint64_t sha512_k[80] = {0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL}; /* SHA-256 functions */ static void ATTR_UNSIGNED_WRAPS sha256_transf(struct sha256_ctx *ctx, const unsigned char *data, size_t block_nb) { uint32_t w[64]; uint32_t wv[8]; uint32_t t1, t2; const unsigned char *sub_block; int i,j; for (i = 0; i < (int) block_nb; i++) { sub_block = data + (i << 6); for (j = 0; j < 16; j++) { PACK32(&sub_block[j << 2], &w[j]); } for (j = 16; j < 64; j++) { SHA256_SCR(j); } for (j = 0; j < 8; j++) { wv[j] = ctx->h[j]; } for (j = 0; j < 64; j++) { t1 = wv[7] + SHA256_F2(wv[4]) + CH(wv[4], wv[5], wv[6]) + sha256_k[j] + w[j]; t2 = SHA256_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]); wv[7] = wv[6]; wv[6] = wv[5]; wv[5] = wv[4]; wv[4] = wv[3] + t1; wv[3] = wv[2]; wv[2] = wv[1]; wv[1] = wv[0]; wv[0] = t1 + t2; } for (j = 0; j < 8; j++) { ctx->h[j] += wv[j]; } } } void sha256_init(struct sha256_ctx *ctx) { int i; for (i = 0; i < 8; i++) { ctx->h[i] = sha256_h0[i]; } ctx->len = 0; ctx->tot_len = 0; } void sha256_loop(struct sha256_ctx *ctx, const void *data, size_t len) { const unsigned char *shifted_message; size_t block_nb; size_t new_len, rem_len, tmp_len; tmp_len = SHA256_BLOCK_SIZE - ctx->len; rem_len = len < tmp_len ? len : tmp_len; memcpy(&ctx->block[ctx->len], data, rem_len); if (ctx->len + len < SHA256_BLOCK_SIZE) { ctx->len += len; return; } new_len = len - rem_len; block_nb = new_len / SHA256_BLOCK_SIZE; shifted_message = CONST_PTR_OFFSET(data, rem_len); sha256_transf(ctx, ctx->block, 1); sha256_transf(ctx, shifted_message, block_nb); rem_len = new_len % SHA256_BLOCK_SIZE; memcpy(ctx->block, &shifted_message[block_nb << 6], rem_len); ctx->len = rem_len; ctx->tot_len += (block_nb + 1) << 6; } void sha256_result(struct sha256_ctx *ctx, unsigned char digest[STATIC_ARRAY SHA256_RESULTLEN]) { size_t block_nb; size_t pm_len; uint64_t len_b; int i; block_nb = (1 + ((SHA256_BLOCK_SIZE - 9) < (ctx->len % SHA256_BLOCK_SIZE))); len_b = (ctx->tot_len + ctx->len) << 3; pm_len = block_nb << 6; memset(ctx->block + ctx->len, 0, pm_len - ctx->len); ctx->block[ctx->len] = 0x80; UNPACK64(len_b, ctx->block + pm_len - 8); sha256_transf(ctx, ctx->block, block_nb); for (i = 0 ; i < 8; i++) { UNPACK32(ctx->h[i], &digest[i << 2]); } } void sha256_get_digest(const void *data, size_t size, unsigned char digest[STATIC_ARRAY SHA256_RESULTLEN]) { struct sha256_ctx ctx; sha256_init(&ctx); sha256_loop(&ctx, data, size); sha256_result(&ctx, digest); } /* SHA-384 functions */ static void ATTR_UNSIGNED_WRAPS sha384_transf(struct sha384_ctx *ctx, const unsigned char *data, size_t block_nb) { uint64_t w[80]; uint64_t wv[8]; uint64_t t1, t2; const unsigned char *sub_block; int i, j; for (i = 0; i < (int) block_nb; i++) { sub_block = data + (i << 7); for (j = 0; j < 16; j++) { PACK64(&sub_block[j << 3], &w[j]); } for (j = 16; j < 80; j++) { SHA384_SCR(j); } for (j = 0; j < 8; j++) { wv[j] = ctx->h[j]; } for (j = 0; j < 80; j++) { /* sha384_k is same as sha512_k */ t1 = wv[7] + SHA384_F2(wv[4]) + CH(wv[4], wv[5], wv[6]) + sha512_k[j] + w[j]; t2 = SHA384_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]); wv[7] = wv[6]; wv[6] = wv[5]; wv[5] = wv[4]; wv[4] = wv[3] + t1; wv[3] = wv[2]; wv[2] = wv[1]; wv[1] = wv[0]; wv[0] = t1 + t2; } for (j = 0; j < 8; j++) { ctx->h[j] += wv[j]; } } } void sha384_init(struct sha384_ctx *ctx) { int i; for (i = 0; i < 8; i++) { ctx->h[i] = sha384_h0[i]; } ctx->len = 0; ctx->tot_len = 0; } void sha384_loop(struct sha384_ctx *ctx, const void *data, size_t len) { const unsigned char *shifted_message; size_t block_nb; size_t new_len, rem_len, tmp_len; tmp_len = SHA384_BLOCK_SIZE - ctx->len; rem_len = len < tmp_len ? len : tmp_len; memcpy(&ctx->block[ctx->len], data, rem_len); if (ctx->len + len < SHA384_BLOCK_SIZE) { ctx->len += len; return; } new_len = len - rem_len; block_nb = new_len / SHA384_BLOCK_SIZE; shifted_message = CONST_PTR_OFFSET(data, rem_len); sha384_transf(ctx, ctx->block, 1); sha384_transf(ctx, shifted_message, block_nb); rem_len = new_len % SHA384_BLOCK_SIZE; memcpy(ctx->block, &shifted_message[block_nb << 7], rem_len); ctx->len = rem_len; ctx->tot_len += (block_nb + 1) << 7; } void sha384_result(struct sha384_ctx *ctx, unsigned char digest[STATIC_ARRAY SHA384_RESULTLEN]) { unsigned int block_nb; unsigned int pm_len; uint64_t len_b; int i; block_nb = 1 + ((SHA384_BLOCK_SIZE - 17) < (ctx->len % SHA384_BLOCK_SIZE)); len_b = (ctx->tot_len + ctx->len) << 3; pm_len = block_nb << 7; memset(ctx->block + ctx->len, 0, pm_len - ctx->len); ctx->block[ctx->len] = 0x80; UNPACK64(len_b, ctx->block + pm_len - 8); sha384_transf(ctx, ctx->block, block_nb); for (i = 0 ; i < 6; i++) { UNPACK64(ctx->h[i], &digest[i << 3]); } } void sha384_get_digest(const void *data, size_t size, unsigned char digest[STATIC_ARRAY SHA384_RESULTLEN]) { struct sha384_ctx ctx; sha384_init(&ctx); sha384_loop(&ctx, data, size); sha384_result(&ctx, digest); } /* SHA-512 functions */ static void ATTR_UNSIGNED_WRAPS sha512_transf(struct sha512_ctx *ctx, const unsigned char *data, size_t block_nb) { uint64_t w[80]; uint64_t wv[8]; uint64_t t1, t2; const unsigned char *sub_block; int i, j; for (i = 0; i < (int) block_nb; i++) { sub_block = data + (i << 7); for (j = 0; j < 16; j++) { PACK64(&sub_block[j << 3], &w[j]); } for (j = 16; j < 80; j++) { SHA512_SCR(j); } for (j = 0; j < 8; j++) { wv[j] = ctx->h[j]; } for (j = 0; j < 80; j++) { t1 = wv[7] + SHA512_F2(wv[4]) + CH(wv[4], wv[5], wv[6]) + sha512_k[j] + w[j]; t2 = SHA512_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]); wv[7] = wv[6]; wv[6] = wv[5]; wv[5] = wv[4]; wv[4] = wv[3] + t1; wv[3] = wv[2]; wv[2] = wv[1]; wv[1] = wv[0]; wv[0] = t1 + t2; } for (j = 0; j < 8; j++) { ctx->h[j] += wv[j]; } } } void sha512_init(struct sha512_ctx *ctx) { int i; for (i = 0; i < 8; i++) { ctx->h[i] = sha512_h0[i]; } ctx->len = 0; ctx->tot_len = 0; } void sha512_loop(struct sha512_ctx *ctx, const void *data, size_t len) { const unsigned char *shifted_message; size_t block_nb; size_t new_len, rem_len, tmp_len; tmp_len = SHA512_BLOCK_SIZE - ctx->len; rem_len = len < tmp_len ? len : tmp_len; memcpy(&ctx->block[ctx->len], data, rem_len); if (ctx->len + len < SHA512_BLOCK_SIZE) { ctx->len += len; return; } new_len = len - rem_len; block_nb = new_len / SHA512_BLOCK_SIZE; shifted_message = CONST_PTR_OFFSET(data, rem_len); sha512_transf(ctx, ctx->block, 1); sha512_transf(ctx, shifted_message, block_nb); rem_len = new_len % SHA512_BLOCK_SIZE; memcpy(ctx->block, &shifted_message[block_nb << 7], rem_len); ctx->len = rem_len; ctx->tot_len += (block_nb + 1) << 7; } void sha512_result(struct sha512_ctx *ctx, unsigned char digest[STATIC_ARRAY SHA512_RESULTLEN]) { unsigned int block_nb; unsigned int pm_len; uint64_t len_b; int i; block_nb = 1 + ((SHA512_BLOCK_SIZE - 17) < (ctx->len % SHA512_BLOCK_SIZE)); len_b = (ctx->tot_len + ctx->len) << 3; pm_len = block_nb << 7; memset(ctx->block + ctx->len, 0, pm_len - ctx->len); ctx->block[ctx->len] = 0x80; UNPACK64(len_b, ctx->block + pm_len - 8); sha512_transf(ctx, ctx->block, block_nb); for (i = 0 ; i < 8; i++) { UNPACK64(ctx->h[i], &digest[i << 3]); } } void sha512_get_digest(const void *data, size_t size, unsigned char digest[STATIC_ARRAY SHA512_RESULTLEN]) { struct sha512_ctx ctx; sha512_init(&ctx); sha512_loop(&ctx, data, size); sha512_result(&ctx, digest); } static void hash_method_init_sha256(void *context) { sha256_init(context); } static void hash_method_loop_sha256(void *context, const void *data, size_t size) { sha256_loop(context, data, size); } static void hash_method_result_sha256(void *context, unsigned char *result_r) { sha256_result(context, result_r); } const struct hash_method hash_method_sha256 = { .name = "sha256", .block_size = SHA256_BLOCK_SIZE, .context_size = sizeof(struct sha256_ctx), .digest_size = SHA256_RESULTLEN, .init = hash_method_init_sha256, .loop = hash_method_loop_sha256, .result = hash_method_result_sha256, }; static void hash_method_init_sha384(void *context) { sha384_init(context); } static void hash_method_loop_sha384(void *context, const void *data, size_t size) { sha384_loop(context, data, size); } static void hash_method_result_sha384(void *context, unsigned char *result_r) { sha384_result(context, result_r); } const struct hash_method hash_method_sha384 = { .name = "sha384", .block_size = SHA384_BLOCK_SIZE, .context_size = sizeof(struct sha384_ctx), .digest_size = SHA384_RESULTLEN, .init = hash_method_init_sha384, .loop = hash_method_loop_sha384, .result = hash_method_result_sha384, }; static void hash_method_init_sha512(void *context) { sha512_init(context); } static void hash_method_loop_sha512(void *context, const void *data, size_t size) { sha512_loop(context, data, size); } static void hash_method_result_sha512(void *context, unsigned char *result_r) { sha512_result(context, result_r); } const struct hash_method hash_method_sha512 = { .name = "sha512", .block_size = SHA512_BLOCK_SIZE, .context_size = sizeof(struct sha512_ctx), .digest_size = SHA512_RESULTLEN, .init = hash_method_init_sha512, .loop = hash_method_loop_sha512, .result = hash_method_result_sha512, }; dovecot-2.3.21.1/src/lib/test-istream-multiplex.c0000644000000000000000000002422714656633576016467 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "ioloop.h" #include "str.h" #include "crc32.h" #include "randgen.h" #include "istream-private.h" #include "istream-multiplex.h" #include "ostream.h" #include static void test_istream_multiplex_simple(void) { test_begin("istream multiplex (simple)"); static const char data[] = "\x00\x00\x00\x00\x06Hello\x00" "\x01\x00\x00\x00\x03Wor" "\x00\x00\x00\x00\x00" "\x01\x00\x00\x00\x03ld\x00"; static const size_t data_len = sizeof(data)-1; struct istream *input = test_istream_create_data(data, data_len); size_t siz; struct istream *chan0 = i_stream_create_multiplex(input, SIZE_MAX); struct istream *chan1 = i_stream_multiplex_add_channel(chan0, 1); /* nothing to read until the first byte */ for (size_t i = 0; i <= 1+4; i++) { test_istream_set_size(input, i); test_assert(i_stream_read(chan0) == 0); test_assert(i_stream_read(chan1) == 0); } /* partial read of the first packet */ size_t input_max = 1+4+3; test_istream_set_size(input, input_max); test_assert(i_stream_read(chan0) == 3); test_assert(memcmp(i_stream_get_data(chan0, &siz), "Hel", 3) == 0 && siz == 3); test_assert(i_stream_read(chan1) == 0); /* read the rest of the first packet and the second packet. read chan1 before chan0 to see that it works. */ input_max += 3 + 1+4+3; test_istream_set_size(input, input_max); test_assert(i_stream_read(chan1) == 3); test_assert(i_stream_read(chan0) == 3); test_assert(memcmp(i_stream_get_data(chan0, &siz), "Hello\0", 6) == 0 && siz == 6); test_assert(memcmp(i_stream_get_data(chan1, &siz), "Wor", 3) == 0 && siz == 3); /* 0-sized packet is ignored */ input_max += 1+4; test_istream_set_size(input, input_max); test_assert(i_stream_read(chan0) == 0); test_assert(i_stream_read(chan1) == 0); /* read the final packet */ input_max += 1+4+3; i_assert(input_max == data_len); test_istream_set_size(input, input_max); test_assert(i_stream_read(chan0) == 0); test_assert(i_stream_read(chan1) == 3); /* we should have the final data in all channels now */ test_assert(memcmp(i_stream_get_data(chan0, &siz), "Hello\0", 6) == 0 && siz == 6); test_assert(memcmp(i_stream_get_data(chan1, &siz), "World\0", 6) == 0 && siz == 6); /* all channels should return EOF */ test_assert(i_stream_read(chan0) == -1 && chan0->stream_errno == 0); i_stream_unref(&chan0); test_assert(i_stream_read(chan1) == -1 && chan1->stream_errno == 0); i_stream_unref(&chan1); i_stream_unref(&input); test_end(); } static void test_istream_multiplex_maxbuf(void) { test_begin("istream multiplex (maxbuf)"); static const char data[] = "\x00\x00\x00\x00\x06Hello\x00" "\x01\x00\x00\x00\x06World\x00"; static const size_t data_len = sizeof(data)-1; struct istream *input = test_istream_create_data(data, data_len); size_t siz; struct istream *chan0 = i_stream_create_multiplex(input, 5); struct istream *chan1 = i_stream_multiplex_add_channel(chan0, 1); /* we get data for channel 0 and congest */ test_assert(i_stream_read(chan1) == 0); /* we read data for channel 0 */ test_assert(i_stream_read(chan0) == 5); /* and now it's congested */ test_assert(i_stream_read(chan0) == -2); test_assert(memcmp(i_stream_get_data(chan0, &siz), "Hello", 5) == 0 && siz == 5); /* consume data */ i_stream_skip(chan0, 5); /* we read data for channel 1 */ test_assert(i_stream_read(chan1) == 5); test_assert(memcmp(i_stream_get_data(chan1, &siz), "World", 5) == 0 && siz == 5); /* consume data */ i_stream_skip(chan1, 5); /* read last byte */ test_assert(i_stream_read(chan0) == 1); /* now we get byte for channel 1 */ test_assert(i_stream_read(chan0) == 0); /* now we read byte for channel 1 */ test_assert(i_stream_read(chan1) == 1); /* and everything should return EOF now */ test_assert(i_stream_read(chan1) == -1); test_assert(i_stream_read(chan0) == -1); i_stream_unref(&chan0); i_stream_unref(&chan1); i_stream_unref(&input); test_end(); } static void test_istream_multiplex_random(void) { const unsigned int max_channel = 6; const unsigned int packets_count = 30; test_begin("istream multiplex (random)"); unsigned int i; uoff_t bytes_written = 0, bytes_read = 0; buffer_t *buf = buffer_create_dynamic(default_pool, 10240); uint32_t input_crc[max_channel]; uint32_t output_crc[max_channel]; memset(input_crc, 0, sizeof(input_crc)); memset(output_crc, 0, sizeof(output_crc)); for (i = 0; i < packets_count; i++) { unsigned int len = i_rand_limit(1024+1); unsigned char packet_data[len]; uint32_t len_be = cpu32_to_be(len); unsigned int channel = i_rand_limit(max_channel); random_fill(packet_data, len); input_crc[channel] = crc32_data_more(input_crc[channel], packet_data, len); buffer_append_c(buf, channel); buffer_append(buf, &len_be, sizeof(len_be)); buffer_append(buf, packet_data, len); bytes_written += len; } struct istream *input = test_istream_create_data(buf->data, buf->used); struct istream *chan[max_channel]; chan[0] = i_stream_create_multiplex(input, 1024/4); for (i = 1; i < max_channel; i++) chan[i] = i_stream_multiplex_add_channel(chan[0], i); test_istream_set_size(input, 0); /* read from each stream, 1 byte at a time */ size_t input_size = 0; int max_ret = -3; unsigned int read_max_channel = max_channel/2; bool something_read = FALSE; for (i = 0;;) { ssize_t ret = i_stream_read(chan[i]); if (max_ret < ret) max_ret = ret; if (ret > 0) { size_t size; const unsigned char *data = i_stream_get_data(chan[i], &size); output_crc[i] = crc32_data_more(output_crc[i], data, size); bytes_read += size; test_assert((size_t)ret == size); i_stream_skip(chan[i], size); something_read = TRUE; } if (++i < read_max_channel) ; else if (max_ret == 0 && !something_read && read_max_channel < max_channel) { read_max_channel++; } else { if (max_ret <= -1) { test_assert(max_ret == -1); break; } if (max_ret == 0) test_istream_set_size(input, ++input_size); i = 0; max_ret = -3; something_read = FALSE; read_max_channel = max_channel/2; } } test_assert(bytes_read == bytes_written); for (i = 0; i < max_channel; i++) { test_assert_idx(input_crc[i] == output_crc[i], i); test_assert_idx(i_stream_read(chan[i]) == -1 && chan[i]->stream_errno == 0, i); i_stream_unref(&chan[i]); } i_stream_unref(&input); buffer_free(&buf); test_end(); } static unsigned int channel_counter[2] = {0, 0}; static const char *msgs[] = { "", "a", "bb", "ccc", "dddd", "eeeee", "ffffff" }; static void test_istream_multiplex_stream_read(struct istream *channel) { uint8_t cid = i_stream_multiplex_get_channel_id(channel); const char *line; size_t siz; if (i_stream_read(channel) < 0) return; while((line = i_stream_next_line(channel)) != NULL) { siz = strlen(line); test_assert_idx(siz > 0 && siz < N_ELEMENTS(msgs), channel_counter[cid]); if (siz > 0 && siz < N_ELEMENTS(msgs)) { test_assert_idx(strcmp(line, msgs[siz]) == 0, channel_counter[cid]); } channel_counter[cid]++; } if (channel_counter[0] > 100 && channel_counter[1] > 100) io_loop_stop(current_ioloop); } static void test_send_msg(struct ostream *os, uint8_t cid, const char *msg) { uint32_t len = cpu32_to_be(strlen(msg) + 1); const struct const_iovec iov[] = { { &cid, sizeof(cid) }, { &len, sizeof(len) }, { msg, strlen(msg) }, { "\n", 1 } /* newline added for i_stream_next_line */ }; o_stream_nsendv(os, iov, N_ELEMENTS(iov)); } static void test_istream_multiplex_stream_write(struct ostream *channel) { size_t rounds = i_rand_limit(10); for(size_t i = 0; i < rounds; i++) { uint8_t cid = i_rand_limit(2); test_send_msg(channel, cid, msgs[1 + i_rand_limit(N_ELEMENTS(msgs) - 1)]); } } static void test_istream_multiplex_stream(void) { test_begin("istream multiplex (stream)"); struct ioloop *ioloop = io_loop_create(); io_loop_set_current(ioloop); int fds[2]; test_assert(pipe(fds) == 0); fd_set_nonblock(fds[0], TRUE); fd_set_nonblock(fds[1], TRUE); struct ostream *os = o_stream_create_fd(fds[1], SIZE_MAX); struct istream *is = i_stream_create_fd(fds[0], 10 + i_rand_limit(10)); struct istream *chan0 = i_stream_create_multiplex(is, SIZE_MAX); struct istream *chan1 = i_stream_multiplex_add_channel(chan0, 1); struct io *io0 = io_add_istream(chan0, test_istream_multiplex_stream_read, chan0); struct io *io1 = io_add_istream(chan1, test_istream_multiplex_stream_read, chan1); struct io *io2 = io_add(fds[1], IO_WRITE, test_istream_multiplex_stream_write, os); io_loop_run(current_ioloop); io_remove(&io0); io_remove(&io1); io_remove(&io2); i_stream_unref(&chan1); i_stream_unref(&chan0); i_stream_unref(&is); test_assert(o_stream_finish(os) > 0); o_stream_unref(&os); io_loop_destroy(&ioloop); i_close_fd(&fds[0]); i_close_fd(&fds[1]); test_end(); } static void test_istream_multiplex_close_channel(void) { test_begin("istream multiplex (close channel)"); static const char *data = "\x00\x00\x00\x00\x06Hello\x00" "\x01\x00\x00\x00\x06World\x00"; static const size_t data_len = 22; struct istream *input = test_istream_create_data(data, data_len); size_t siz; struct istream *chan0 = i_stream_create_multiplex(input, SIZE_MAX); struct istream *chan1 = i_stream_multiplex_add_channel(chan0, 1); i_stream_unref(&chan1); test_assert(i_stream_read(chan0) == 6); test_assert(memcmp(i_stream_get_data(chan0, &siz), "Hello\0", 6) == 0 && siz == 6); i_stream_unref(&chan0); i_stream_unref(&input); input = test_istream_create_data(data, data_len); chan0 = i_stream_create_multiplex(input, SIZE_MAX); chan1 = i_stream_multiplex_add_channel(chan0, 1); /* this is needed to populate chan1 data */ (void)i_stream_read(chan0); i_stream_unref(&chan0); test_assert(i_stream_read(chan1) == 6); test_assert(memcmp(i_stream_get_data(chan1, &siz), "World\0", 6) == 0 && siz == 6); i_stream_unref(&chan1); i_stream_unref(&input); test_end(); } void test_istream_multiplex(void) { test_istream_multiplex_simple(); test_istream_multiplex_maxbuf(); test_istream_multiplex_random(); test_istream_multiplex_stream(); test_istream_multiplex_close_channel(); } dovecot-2.3.21.1/src/lib/restrict-process-size.h0000644000000000000000000000153214656633576016307 00000000000000#ifndef RESTRICT_PROCESS_SIZE_H #define RESTRICT_PROCESS_SIZE_H #include #ifdef HAVE_SYS_RESOURCE_H # include #endif /* Restrict max. process size. */ void restrict_process_size(rlim_t bytes); /* Restrict max. number of processes. */ void restrict_process_count(rlim_t count); /* Set fd limit to count. */ void restrict_fd_limit(rlim_t count); /* Get the core dump size limit. Returns 0 if ok, -1 if lookup failed. */ int restrict_get_core_limit(rlim_t *limit_r); /* Get the process VSZ size limit. Returns 0 if ok, -1 if lookup failed. */ int restrict_get_process_size(rlim_t *limit_r); /* Get the process count limit. Returns 0 if ok, -1 if lookup failed. */ int restrict_get_process_limit(rlim_t *limit_r); /* Get the fd limit. Returns 0 if ok, -1 if lookup failed. */ int restrict_get_fd_limit(rlim_t *limit_r); #endif dovecot-2.3.21.1/src/lib/hash-method.h0000644000000000000000000000260214656633576014224 00000000000000#ifndef HASH_METHOD_H #define HASH_METHOD_H #include "buffer.h" struct hash_method { const char *name; /* Block size for the algorithm */ unsigned int block_size; /* Number of bytes that must be allocated for context */ unsigned int context_size; /* Number of bytes that must be allocated for result()'s digest */ unsigned int digest_size; void (*init)(void *context); void (*loop)(void *context, const void *data, size_t size); void (*result)(void *context, unsigned char *digest_r); }; const struct hash_method *hash_method_lookup(const char *name); /* NULL-terminated list of all hash methods */ extern const struct hash_method *hash_methods[]; void hash_method_get_digest(const struct hash_method *meth, const void *data, size_t data_len, unsigned char *result_r); /** Simple datastack helpers for digesting (hashing) * USAGE: buffer_t *result = t_hash_str(hash_method_lookup("sha256"), "hello world"); const char *hex = binary_to_hex(result->data, result->used); */ buffer_t *t_hash_data(const struct hash_method *meth, const void *data, size_t data_len); static inline buffer_t *t_hash_buffer(const struct hash_method *meth, const buffer_t *data) { return t_hash_data(meth, data->data, data->used); } static inline buffer_t *t_hash_str(const struct hash_method *meth, const char *data) { return t_hash_data(meth, data, strlen(data)); } #endif dovecot-2.3.21.1/src/lib/test-lib.c0000644000000000000000000000121314656633576013536 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" int main(int argc, char **argv) { const char *match = ""; if (argc > 2 && strcmp(argv[1], "--match") == 0) match = argv[2]; static const struct named_test test_functions[] = { #define TEST(x) TEST_NAMED(x) #define FATAL(x) #include "test-lib.inc" #undef TEST #undef FATAL { NULL, NULL } }; static const struct named_fatal fatal_functions[] = { #define TEST(x) #define FATAL(x) FATAL_NAMED(x) #include "test-lib.inc" #undef TEST #undef FATAL { NULL, NULL } }; return test_run_named_with_fatals(match, test_functions, fatal_functions); } dovecot-2.3.21.1/src/lib/test-macros.c0000644000000000000000000000273614656633576014267 00000000000000/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ #include "test-lib.h" struct parent { unsigned int a; }; struct child { unsigned int b; struct parent p; }; static void test_container_of(void) { struct child child; struct parent *parent = &child.p; test_begin("container_of()"); struct child *ptr_child = container_of(parent, struct child, p); test_assert(ptr_child == &child); test_end(); } static void test_pointer_cast(void) { #define TEST_POINTER_CAST(type, prefix, value) \ type prefix ## _num = value; \ void *prefix ## _ptr = POINTER_CAST(prefix ## _num); \ test_assert(POINTER_CAST_TO(prefix ## _ptr, type) == prefix ## _num); test_begin("POINTER_CAST"); TEST_POINTER_CAST(unsigned int, uint, 0x87654321); TEST_POINTER_CAST(uint32_t, uint32, 0xf00dabcd); TEST_POINTER_CAST(uint16_t, uint16, 0x9876); TEST_POINTER_CAST(uint8_t, uint8, 0xf8); #if SIZEOF_VOID_P == 8 TEST_POINTER_CAST(unsigned long, ulong, 0xfedcba9876543210); TEST_POINTER_CAST(size_t, size, 0xfedcba9876543210); #else TEST_POINTER_CAST(unsigned long, ulong, 0xfedcba98); TEST_POINTER_CAST(size_t, size, 0xfedcba98); #endif test_end(); } static void test_ptr_offset(void) { uint32_t foo[] = { 1, 2, 3 }; const uint32_t foo2[] = { 1, 2, 3 }; test_begin("PTR_OFFSET"); test_assert(PTR_OFFSET(foo, 4) == &foo[1]); test_assert(CONST_PTR_OFFSET(foo2, 8) == &foo2[2]); test_end(); } void test_macros(void) { test_container_of(); test_pointer_cast(); test_ptr_offset(); } dovecot-2.3.21.1/src/lib/mmap-util.h0000644000000000000000000000202614656633576013730 00000000000000#ifndef MMAP_UTIL_H #define MMAP_UTIL_H #include #ifdef HAVE_LINUX_MREMAP # define __USE_GNU /* for MREMAP_MAYMOVE */ #endif #include #undef __USE_GNU #if !defined (MREMAP_MAYMOVE) && !defined (HAVE_LINUX_MREMAP) # define MREMAP_MAYMOVE 1 #endif #define madvise my_madvise int my_madvise(void *start, size_t length, int advice); #ifndef HAVE_MADVISE # ifndef MADV_NORMAL # define MADV_NORMAL 0 # define MADV_RANDOM 0 # define MADV_SEQUENTIAL 0 # define MADV_WILLNEED 0 # define MADV_DONTNEED 0 # endif #endif void *mmap_file(int fd, size_t *length, int prot); void *mmap_ro_file(int fd, size_t *length); void *mmap_rw_file(int fd, size_t *length); /* for allocating anonymous mmap()s, with portable mremap(). these must not be mixed with any standard mmap calls. */ void *mmap_anon(size_t length); void *mremap_anon(void *old_address, size_t old_size, size_t new_size, unsigned long flags); int munmap_anon(void *start, size_t length); size_t mmap_get_page_size(void) ATTR_CONST; #endif dovecot-2.3.21.1/src/lib/test-data-stack.c0000644000000000000000000003204614656633576015014 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "lib-event-private.h" #include "event-filter.h" #include "data-stack.h" static int ds_grow_event_count = 0; static bool test_ds_grow_event_callback(struct event *event, enum event_callback_type type, struct failure_context *ctx, const char *fmt ATTR_UNUSED, va_list args ATTR_UNUSED) { const struct event_field *field; if (type != EVENT_CALLBACK_TYPE_SEND) return TRUE; ds_grow_event_count++; test_assert(ctx->type == LOG_TYPE_DEBUG); field = event_find_field_nonrecursive(event, "alloc_size"); test_assert(field != NULL && field->value_type == EVENT_FIELD_VALUE_TYPE_INTMAX && field->value.intmax >= 1024 * (5 + 100)); field = event_find_field_nonrecursive(event, "used_size"); test_assert(field != NULL && field->value_type == EVENT_FIELD_VALUE_TYPE_INTMAX && field->value.intmax >= 1024 * (5 + 100)); field = event_find_field_nonrecursive(event, "last_alloc_size"); test_assert(field != NULL && field->value_type == EVENT_FIELD_VALUE_TYPE_INTMAX && field->value.intmax >= 1024 * 100); field = event_find_field_nonrecursive(event, "frame_marker"); test_assert(field != NULL && field->value_type == EVENT_FIELD_VALUE_TYPE_STR && strstr(field->value.str, "data-stack.c") != NULL); return TRUE; } static void test_ds_grow_event(void) { const char *error; test_begin("data-stack grow event"); event_register_callback(test_ds_grow_event_callback); i_assert(event_get_global_debug_log_filter() == NULL); struct event_filter *filter = event_filter_create(); test_assert(event_filter_parse("event=data_stack_grow", filter, &error) == 0); event_set_global_debug_log_filter(filter); event_filter_unref(&filter); /* make sure the test won't fail due to earlier data stack allocations. */ data_stack_free_unused(); T_BEGIN { (void)t_malloc0(1024*5); test_assert(ds_grow_event_count == 0); (void)t_malloc0(1024*100); test_assert(ds_grow_event_count == 1); } T_END; event_unset_global_debug_log_filter(); event_unregister_callback(test_ds_grow_event_callback); test_end(); } static void test_ds_get_used_size(void) { test_begin("data-stack data_stack_get_used_size()"); size_t size1 = data_stack_get_used_size(); (void)t_malloc0(500); size_t size2 = data_stack_get_used_size(); test_assert(size1 + 500 <= size2); T_BEGIN { (void)t_malloc0(300); size_t sub_size1 = data_stack_get_used_size(); T_BEGIN { (void)t_malloc0(300); } T_END; test_assert_cmp(sub_size1, ==, data_stack_get_used_size()); } T_END; test_assert_cmp(size2, ==, data_stack_get_used_size()); test_end(); } static void test_ds_get_bytes_available(void) { test_begin("data-stack t_get_bytes_available()"); for (unsigned int i = 0; i < 32; i++) { size_t orig_avail = t_get_bytes_available(); size_t avail1; T_BEGIN { if (i > 0) t_malloc_no0(i); avail1 = t_get_bytes_available(); t_malloc_no0(avail1); test_assert_idx(t_get_bytes_available() == 0, i); t_malloc_no0(1); test_assert_idx(t_get_bytes_available() > 0, i); } T_END; T_BEGIN { if (i > 0) t_malloc_no0(i); size_t avail2 = t_get_bytes_available(); test_assert_idx(avail1 == avail2, i); t_malloc_no0(avail2 + 1); test_assert_idx(t_get_bytes_available() > 0, i); } T_END; test_assert_idx(t_get_bytes_available() == orig_avail, i); } test_end(); } static void ATTR_FORMAT(2, 0) test_ds_growing_debug(const struct failure_context *ctx ATTR_UNUSED, const char *format, va_list args) { ds_grow_event_count++; (void)t_strdup_vprintf(format, args); } static void test_ds_grow_in_event(void) { size_t i, alloc1 = 8096; unsigned char *buf; const char *error; test_begin("data-stack grow in event"); struct event_filter *filter = event_filter_create(); event_set_global_debug_log_filter(filter); test_assert(event_filter_parse("event=data_stack_grow", filter, &error) == 0); event_filter_unref(&filter); i_set_debug_handler(test_ds_growing_debug); buf = t_buffer_get(alloc1); for (i = 0; i < alloc1; i++) buf[i] = i & 0xff; test_assert(ds_grow_event_count == 0); buf = t_buffer_reget(buf, 65536); test_assert(ds_grow_event_count == 1); for (i = 0; i < alloc1; i++) { if (buf[i] != (unsigned char)i) break; } test_assert(i == alloc1); i_set_debug_handler(default_error_handler); event_unset_global_debug_log_filter(); test_end(); } static void test_ds_buffers(void) { test_begin("data-stack buffer growth"); T_BEGIN { size_t i; unsigned char *p; size_t left = t_get_bytes_available(); while (left < 10000) { t_malloc_no0(left+1); /* force a new block */ left = t_get_bytes_available(); } left -= 64; /* make room for the sentry if DEBUG */ p = t_buffer_get(1); p[0] = 1; for (i = 2; i <= left; i++) { /* grow it */ unsigned char *p2 = t_buffer_get(i); test_assert_idx(p == p2, i); p[i-1] = i & 0xff; test_assert_idx(p[i-2] == (unsigned char)(i-1), i); } /* now fix it permanently */ t_buffer_alloc_last_full(); test_assert(t_get_bytes_available() < 64 + MEM_ALIGN(1)); } T_END; test_end(); test_begin("data-stack buffer interruption"); T_BEGIN { void *b = t_buffer_get(1000); void *a = t_malloc_no0(1); void *b2 = t_buffer_get(1001); test_assert(a == b); /* expected, not guaranteed */ test_assert(b2 != b); } T_END; test_end(); test_begin("data-stack buffer with reallocs"); T_BEGIN { size_t bigleft = t_get_bytes_available(); size_t i; /* with DEBUG: the stack frame allocation takes 96 bytes and malloc takes extra 40 bytes + alignment, so don't let "i" be too high. */ for (i = 1; i < bigleft-96-40-16; i += i_rand_limit(32)) T_BEGIN { unsigned char *p, *p2; size_t left; t_malloc_no0(i); left = t_get_bytes_available(); /* The most useful idx for the assert is 'left' */ test_assert_idx(left <= bigleft-i, left); p = t_buffer_get(left/2); p[0] = 'Z'; p[left/2 - 1] = 'Z'; p2 = t_buffer_get(left + left/2); test_assert_idx(p != p2, left); test_assert_idx(p[0] == 'Z', left); test_assert_idx(p[left/2 -1] == 'Z', left); } T_END; } T_END; test_end(); } static void test_ds_realloc() { test_begin("data-stack realloc"); T_BEGIN { size_t i; unsigned char *p; size_t left = t_get_bytes_available(); while (left < 10000) { t_malloc_no0(left+1); /* force a new block */ left = t_get_bytes_available(); } left -= 64; /* make room for the sentry if DEBUG */ p = t_malloc_no0(1); p[0] = 1; for (i = 2; i <= left; i++) { /* grow it */ test_assert_idx(t_try_realloc(p, i), i); p[i-1] = i & 0xff; test_assert_idx(p[i-2] == (unsigned char)(i-1), i); } test_assert(t_get_bytes_available() < 64 + MEM_ALIGN(1)); } T_END; test_end(); } static void test_ds_recurse(int depth, int number, size_t size) { int i; char **ps; char tag[2] = { depth+1, '\0' }; int try_fails = 0; data_stack_frame_t t_id = t_push_named("test_ds_recurse[%i]", depth); ps = t_buffer_get(sizeof(char *) * number); i_assert(ps != NULL); t_buffer_alloc(sizeof(char *) * number); for (i = 0; i < number; i++) { ps[i] = t_malloc_no0(size/2); bool re = t_try_realloc(ps[i], size); i_assert(ps[i] != NULL); if (!re) { try_fails++; ps[i] = t_malloc_no0(size); } /* drop our own canaries */ memset(ps[i], tag[0], size); ps[i][size-2] = 0; } /* Do not expect a high failure rate from t_try_realloc */ test_assert_idx(try_fails <= number / 20, depth); /* Now recurse... */ if(depth>0) test_ds_recurse(depth-1, number, size); /* Test our canaries are still intact */ for (i = 0; i < number; i++) { test_assert_idx(strspn(ps[i], tag) == size - 2, i); test_assert_idx(ps[i][size-1] == tag[0], i); } test_assert_idx(t_pop(&t_id), depth); } static void test_ds_recursive(void) { int count = 20, depth = 80; int i; test_begin("data-stack recursive"); size_t init_size = data_stack_get_used_size(); for(i = 0; i < count; i++) T_BEGIN { int number=i_rand_limit(100)+50; int size=i_rand_limit(100)+50; test_ds_recurse(depth, number, size); } T_END; test_assert_cmp(init_size, ==, data_stack_get_used_size()); test_end(); } static void test_ds_pass_str(void) { data_stack_frame_t frames[32*2 + 1]; /* BLOCK_FRAME_COUNT*2 + 1 */ const char *strings[N_ELEMENTS(frames)]; test_begin("data-stack pass string"); for (unsigned int frame = 0; frame < N_ELEMENTS(frames); frame++) { frames[frame] = t_push("test"); if (frame % 10 == 5) { /* increase block counts */ (void)t_malloc_no0(1024*30); (void)t_malloc_no0(1024*30); } strings[frame] = t_strdup_printf("frame %d", frame); for (unsigned int i = 0; i <= frame; i++) { test_assert_idx(data_stack_frame_contains(&frames[frame], strings[i]) == (i == frame), frame * 100 + i); } } const char *last_str = strings[N_ELEMENTS(frames)-1]; for (unsigned int frame = N_ELEMENTS(frames); frame > 0; ) { frame--; test_assert(t_pop_pass_str(&frames[frame], &last_str)); } test_assert_strcmp(last_str, "frame 64"); /* make sure the pass_condition works properly */ const char *error, *orig_error, *orig2_error; T_BEGIN { (void)t_strdup("qwertyuiop"); error = orig_error = t_strdup("123456"); } T_END_PASS_STR_IF(TRUE, &error); orig2_error = orig_error; T_BEGIN { (void)t_strdup("abcdefghijklmnopqrstuvwxyz"); } T_END_PASS_STR_IF(FALSE, &orig2_error); /* orig_error and orig2_error both point to freed data stack frame */ test_assert(orig_error == orig2_error); /* the passed error is still valid though */ test_assert_strcmp(error, "123456"); test_end(); } void test_data_stack(void) { void (*tests[])(void) = { test_ds_grow_event, test_ds_get_used_size, test_ds_get_bytes_available, test_ds_grow_in_event, test_ds_buffers, test_ds_realloc, test_ds_recursive, test_ds_pass_str, }; for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { ds_grow_event_count = 0; data_stack_free_unused(); T_BEGIN { tests[i](); } T_END; } } enum fatal_test_state fatal_data_stack(unsigned int stage) { #ifdef DEBUG #define NONEXISTENT_STACK_FRAME_ID (data_stack_frame_t)999999999 /* If we abort, then we'll be left with a dangling t_push() keep a record of our temporary stack id, so we can clean up. */ static data_stack_frame_t t_id = NONEXISTENT_STACK_FRAME_ID; static unsigned char *undo_ptr = NULL; static unsigned char undo_data; static bool things_are_messed_up = FALSE; if (stage != 0) { /* Presume that we need to clean up from the prior test: undo the evil write, then we will be able to t_pop cleanly, and finally we can end the test stanza. */ if (things_are_messed_up || undo_ptr == NULL) return FATAL_TEST_ABORT; /* abort, things are messed up with t_pop */ *undo_ptr = undo_data; undo_ptr = NULL; /* t_pop mustn't abort, that would cause recursion */ things_are_messed_up = TRUE; if (t_id != NONEXISTENT_STACK_FRAME_ID && !t_pop(&t_id)) return FATAL_TEST_ABORT; /* abort, things are messed up with us */ things_are_messed_up = FALSE; t_id = NONEXISTENT_STACK_FRAME_ID; test_end(); } switch(stage) { case 0: { unsigned char *p; test_begin("fatal data-stack underrun"); t_id = t_push_named("fatal_data_stack underrun"); size_t left = t_get_bytes_available(); p = t_malloc_no0(left-80); /* will fit */ p = t_malloc_no0(100); /* won't fit, will get new block */ int seek = 0; /* Seek back for the canary, don't assume endianness */ while(seek > -60 && ((p[seek+1] != 0xDB) || ((p[seek] != 0xBA || p[seek+2] != 0xAD) && (p[seek+2] != 0xBA || p[seek] != 0xAD)))) seek--; if (seek <= -60) return FATAL_TEST_ABORT; /* abort, couldn't find header */ undo_ptr = p + seek; undo_data = *undo_ptr; *undo_ptr = '*'; /* t_malloc_no0 will panic block header corruption */ test_expect_fatal_string("Corrupted data stack canary"); (void)t_malloc_no0(10); return FATAL_TEST_FAILURE; } case 1: case 2: { test_begin(stage == 1 ? "fatal t_malloc_no0 overrun near" : "fatal t_malloc_no0 overrun far"); t_id = t_push_named(stage == 1 ? "fatal t_malloc_no0 overrun first" : "fatal t_malloc_no0 overrun far"); unsigned char *p = t_malloc_no0(10); undo_ptr = p + 10 + (stage == 1 ? 0 : 8*4-1); /* presumes sentry size */ undo_data = *undo_ptr; *undo_ptr = '*'; /* t_pop will now fail */ test_expect_fatal_string("buffer overflow"); (void)t_pop(&t_id); t_id = NONEXISTENT_STACK_FRAME_ID; /* We're FUBAR, mustn't pop next entry */ return FATAL_TEST_FAILURE; } case 3: case 4: { test_begin(stage == 3 ? "fatal t_buffer_get overrun near" : "fatal t_buffer_get overrun far"); t_id = t_push_named(stage == 3 ? "fatal t_buffer overrun near" : "fatal t_buffer_get overrun far"); unsigned char *p = t_buffer_get(10); undo_ptr = p + 10 + (stage == 3 ? 0 : 8*4-1); undo_data = *undo_ptr; *undo_ptr = '*'; /* t_pop will now fail */ test_expect_fatal_string("buffer overflow"); (void)t_pop(&t_id); t_id = NONEXISTENT_STACK_FRAME_ID; /* We're FUBAR, mustn't pop next entry */ return FATAL_TEST_FAILURE; } default: things_are_messed_up = TRUE; return FATAL_TEST_FINISHED; } #else return stage == 0 ? FATAL_TEST_FINISHED : FATAL_TEST_ABORT; #endif } dovecot-2.3.21.1/src/lib/ioloop-epoll.c0000644000000000000000000001331714656633576014435 00000000000000/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "sleep.h" #include "ioloop-private.h" #include "ioloop-iolist.h" #ifdef IOLOOP_EPOLL #include #include struct ioloop_handler_context { int epfd; unsigned int deleted_count; ARRAY(struct io_list *) fd_index; ARRAY(struct epoll_event) events; }; void io_loop_handler_init(struct ioloop *ioloop, unsigned int initial_fd_count) { struct ioloop_handler_context *ctx; ioloop->handler_context = ctx = i_new(struct ioloop_handler_context, 1); i_array_init(&ctx->events, initial_fd_count); i_array_init(&ctx->fd_index, initial_fd_count); ctx->epfd = epoll_create(initial_fd_count); if (ctx->epfd < 0) { if (errno != EMFILE) i_fatal("epoll_create(): %m"); else { i_fatal("epoll_create(): %m (you may need to increase " "/proc/sys/fs/epoll/max_user_instances)"); } } fd_close_on_exec(ctx->epfd, TRUE); } void io_loop_handler_deinit(struct ioloop *ioloop) { struct ioloop_handler_context *ctx = ioloop->handler_context; struct io_list **list; unsigned int i, count; list = array_get_modifiable(&ctx->fd_index, &count); for (i = 0; i < count; i++) i_free(list[i]); if (close(ctx->epfd) < 0) i_error("close(epoll) failed: %m"); array_free(&ioloop->handler_context->fd_index); array_free(&ioloop->handler_context->events); i_free(ioloop->handler_context); } #define IO_EPOLL_ERROR (EPOLLERR | EPOLLHUP) #define IO_EPOLL_INPUT (EPOLLIN | EPOLLPRI | IO_EPOLL_ERROR) #define IO_EPOLL_OUTPUT (EPOLLOUT | IO_EPOLL_ERROR) static int epoll_event_mask(struct io_list *list) { int events = 0, i; struct io_file *io; for (i = 0; i < IOLOOP_IOLIST_IOS_PER_FD; i++) { io = list->ios[i]; if (io == NULL) continue; if ((io->io.condition & IO_READ) != 0) events |= IO_EPOLL_INPUT; if ((io->io.condition & IO_WRITE) != 0) events |= IO_EPOLL_OUTPUT; if ((io->io.condition & IO_ERROR) != 0) events |= IO_EPOLL_ERROR; } return events; } void io_loop_handle_add(struct io_file *io) { struct ioloop_handler_context *ctx = io->io.ioloop->handler_context; struct io_list **list; struct epoll_event event; int op; bool first; list = array_idx_get_space(&ctx->fd_index, io->fd); if (*list == NULL) *list = i_new(struct io_list, 1); first = ioloop_iolist_add(*list, io); i_zero(&event); event.data.ptr = *list; event.events = epoll_event_mask(*list); op = first ? EPOLL_CTL_ADD : EPOLL_CTL_MOD; if (epoll_ctl(ctx->epfd, op, io->fd, &event) < 0) { if (errno == EPERM && op == EPOLL_CTL_ADD) { i_panic("epoll_ctl(add, %d) failed: %m " "(fd doesn't support epoll%s)", io->fd, io->fd != STDIN_FILENO ? "" : " - instead of 'fd); } if (first) { /* allow epoll_wait() to return the maximum number of events by keeping space allocated for each file descriptor */ if (ctx->deleted_count > 0) ctx->deleted_count--; else array_append_zero(&ctx->events); } } void io_loop_handle_remove(struct io_file *io, bool closed) { struct ioloop_handler_context *ctx = io->io.ioloop->handler_context; struct io_list **list; struct epoll_event event; int op; bool last; list = array_idx_modifiable(&ctx->fd_index, io->fd); last = ioloop_iolist_del(*list, io); if (!closed) { i_zero(&event); event.data.ptr = *list; event.events = epoll_event_mask(*list); op = last ? EPOLL_CTL_DEL : EPOLL_CTL_MOD; if (epoll_ctl(ctx->epfd, op, io->fd, &event) < 0) { const char *errstr = t_strdup_printf( "epoll_ctl(%s, %d) failed: %m", op == EPOLL_CTL_DEL ? "del" : "mod", io->fd); if (errno != ENOSPC && errno != ENOMEM) i_panic("%s", errstr); else i_error("%s", errstr); } } if (last) { /* since we're not freeing memory in any case, just increase deleted counter so next handle_add() can just decrease it instead of appending to the events array */ ctx->deleted_count++; } i_free(io); } void io_loop_handler_run_internal(struct ioloop *ioloop) { struct ioloop_handler_context *ctx = ioloop->handler_context; struct epoll_event *events; const struct epoll_event *event; struct io_list *list; struct io_file *io; struct timeval tv; unsigned int events_count; int msecs, ret, i, j; bool call; i_assert(ctx != NULL); /* get the time left for next timeout task */ msecs = io_loop_run_get_wait_time(ioloop, &tv); events = array_get_modifiable(&ctx->events, &events_count); if (ioloop->io_files != NULL && events_count > ctx->deleted_count) { ret = epoll_wait(ctx->epfd, events, events_count, msecs); if (ret < 0 && errno != EINTR) i_fatal("epoll_wait(): %m"); } else { /* no I/Os, but we should have some timeouts. just wait for them. */ i_assert(msecs >= 0); i_sleep_intr_msecs(msecs); ret = 0; } /* execute timeout handlers */ io_loop_handle_timeouts(ioloop); if (!ioloop->running) return; for (i = 0; i < ret; i++) { /* io_loop_handle_add() may cause events array reallocation, so we have use array_idx() */ event = array_idx(&ctx->events, i); list = event->data.ptr; for (j = 0; j < IOLOOP_IOLIST_IOS_PER_FD; j++) { io = list->ios[j]; if (io == NULL) continue; call = FALSE; if ((event->events & (EPOLLHUP | EPOLLERR)) != 0) call = TRUE; else if ((io->io.condition & IO_READ) != 0) call = (event->events & EPOLLIN) != 0; else if ((io->io.condition & IO_WRITE) != 0) call = (event->events & EPOLLOUT) != 0; else if ((io->io.condition & IO_ERROR) != 0) call = (event->events & IO_EPOLL_ERROR) != 0; if (call) { io_loop_call_io(&io->io); if (!ioloop->running) return; } } } } #endif /* IOLOOP_EPOLL */ dovecot-2.3.21.1/src/lib/aqueue.c0000644000000000000000000000606214656633576013307 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "aqueue.h" struct aqueue *aqueue_init(struct array *array) { struct aqueue *aqueue; aqueue = i_new(struct aqueue, 1); aqueue->arr = array; aqueue->area_size = buffer_get_writable_size(aqueue->arr->buffer) / aqueue->arr->element_size; i_assert(aqueue->area_size > 0); return aqueue; } void aqueue_deinit(struct aqueue **_aqueue) { struct aqueue *aqueue = *_aqueue; *_aqueue = NULL; i_free(aqueue); } static void aqueue_grow(struct aqueue *aqueue) { unsigned int orig_area_size, count; i_assert(aqueue->full && aqueue->head == aqueue->tail); orig_area_size = aqueue->area_size; (void)array_append_space_i(aqueue->arr); aqueue->area_size = buffer_get_writable_size(aqueue->arr->buffer) / aqueue->arr->element_size; i_assert(orig_area_size < aqueue->area_size); count = I_MIN(aqueue->area_size - orig_area_size, aqueue->head); array_copy(aqueue->arr, orig_area_size, aqueue->arr, 0, count); if (count < aqueue->area_size - orig_area_size) aqueue->head = orig_area_size + count; else { array_copy(aqueue->arr, 0, aqueue->arr, count, aqueue->head - count); aqueue->head -= count; } i_assert(aqueue->head != aqueue->tail); aqueue->full = FALSE; } void aqueue_append(struct aqueue *aqueue, const void *data) { if (aqueue->full) { aqueue_grow(aqueue); i_assert(!aqueue->full); } array_idx_set_i(aqueue->arr, aqueue->head, data); aqueue->head = (aqueue->head + 1) % aqueue->area_size; aqueue->full = aqueue->head == aqueue->tail; } void aqueue_delete(struct aqueue *aqueue, unsigned int n) { unsigned int idx, count = aqueue_count(aqueue); i_assert(n < count); aqueue->full = FALSE; if (n == 0) { /* optimized deletion from tail */ aqueue->tail = (aqueue->tail + 1) % aqueue->area_size; return; } if (n == count-1) { /* optimized deletion from head */ aqueue->head = (aqueue->head + aqueue->area_size - 1) % aqueue->area_size; return; } idx = aqueue_idx(aqueue, n); if ((n < count/2 || idx > aqueue->head) && idx > aqueue->tail) { /* move tail forward. ..tail##idx##head.. or ##head..tail##idx## */ array_copy(aqueue->arr, aqueue->tail + 1, aqueue->arr, aqueue->tail, idx - aqueue->tail); aqueue->tail++; i_assert(aqueue->tail < aqueue->area_size); } else { /* move head backward. ..tail##idx##head.. or ##idx##head..tail## */ i_assert(idx < aqueue->head); array_copy(aqueue->arr, idx, aqueue->arr, idx + 1, aqueue->head - idx); aqueue->head = (aqueue->head + aqueue->area_size - 1) % aqueue->area_size; } i_assert(aqueue->head < aqueue->area_size && aqueue->head != aqueue->tail); } void aqueue_delete_tail(struct aqueue *aqueue) { aqueue_delete(aqueue, 0); } void aqueue_clear(struct aqueue *aqueue) { aqueue->head = aqueue->tail = 0; aqueue->full = FALSE; } unsigned int aqueue_count(const struct aqueue *aqueue) { unsigned int area_size = aqueue->area_size; return aqueue->full ? area_size : (area_size - aqueue->tail + aqueue->head) % area_size; } dovecot-2.3.21.1/src/lib/pkcs5.c0000644000000000000000000000506114656633576013045 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "hash-method.h" #include "hmac.h" #include "pkcs5.h" #include #include static int pkcs5_pbkdf1(const struct hash_method *hash, const unsigned char *password, size_t password_len, const unsigned char *salt, size_t salt_len, unsigned int iter, uint32_t length, buffer_t *result) { if (length < 1 || length > hash->digest_size) return -1; if (iter < 1) return -1; unsigned char dk[hash->digest_size]; unsigned char ctx[hash->context_size]; hash->init(ctx); hash->loop(ctx, password, password_len); hash->loop(ctx, salt, salt_len); hash->result(ctx, dk); length--; for(;length>0;length--) { hash->init(ctx); hash->loop(ctx, dk, hash->digest_size); hash->result(ctx, dk); } buffer_append(result, dk, hash->digest_size); return 0; } static int pkcs5_pbkdf2(const struct hash_method *hash, const unsigned char *password, size_t password_len, const unsigned char *salt, size_t salt_len, unsigned int iter, uint32_t length, buffer_t *result) { if (length < 1 || iter < 1) return -1; size_t l = (length + hash->digest_size - 1)/hash->digest_size; /* same as ceil(length/hash->digest_size) */ unsigned char dk[l * hash->digest_size]; unsigned char *block; struct hmac_context hctx; unsigned int c,i,t; unsigned char U_c[hash->digest_size]; for(t = 0; t < l; t++) { block = &(dk[t*hash->digest_size]); /* U_1 = PRF(Password, Salt|| INT_BE32(Block_Number)) */ c = htonl(t+1); hmac_init(&hctx, password, password_len, hash); hmac_update(&hctx, salt, salt_len); hmac_update(&hctx, &c, sizeof(c)); hmac_final(&hctx, U_c); /* block = U_1 ^ .. ^ U_iter */ memcpy(block, U_c, hash->digest_size); /* U_c = PRF(Password, U_c-1) */ for(c = 1; c < iter; c++) { hmac_init(&hctx, password, password_len, hash); hmac_update(&hctx, U_c, hash->digest_size); hmac_final(&hctx, U_c); for(i = 0; i < hash->digest_size; i++) block[i] ^= U_c[i]; } } buffer_append(result, dk, length); return 0; } int pkcs5_pbkdf(enum pkcs5_pbkdf_mode mode, const struct hash_method *hash, const unsigned char *password, size_t password_len, const unsigned char *salt, size_t salt_len, unsigned int iterations, uint32_t dk_len, buffer_t *result) { if (mode == PKCS5_PBKDF1) return pkcs5_pbkdf1(hash,password,password_len, salt,salt_len,iterations,dk_len,result); else if (mode == PKCS5_PBKDF2) return pkcs5_pbkdf2(hash,password,password_len, salt,salt_len,iterations,dk_len,result); i_unreached(); } dovecot-2.3.21.1/src/lib/sort.c0000644000000000000000000000054714656633576013013 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "sort.h" #include #include int bsearch_strcmp(const char *key, const char *const *member) { return strcmp(key, *member); } int bsearch_strcasecmp(const char *key, const char *const *member) { return strcasecmp(key, *member); } dovecot-2.3.21.1/src/lib/ostream-multiplex.h0000644000000000000000000000045214656633576015517 00000000000000#ifndef OSTREAM_MULTIPLEX #define OSTREAM_MULTIPLEX 1 struct ostream *o_stream_create_multiplex(struct ostream *parent, size_t bufsize); struct ostream *o_stream_multiplex_add_channel(struct ostream *stream, uint8_t cid); uint8_t o_stream_multiplex_get_channel_id(struct ostream *stream); #endif dovecot-2.3.21.1/src/lib/test-ostream-failure-at.c0000644000000000000000000000355014656633576016477 00000000000000/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "buffer.h" #include "ostream.h" #include "ostream-failure-at.h" #define TEST_DATA_LENGTH 128 #define TEST_ERRMSG "test-ostream-failure-at error triggered" void test_ostream_failure_at(void) { unsigned char test_data[TEST_DATA_LENGTH]; struct ostream *output, *buf_output; buffer_t *buf = t_buffer_create(256); unsigned int i; test_begin("ostream failure at"); for (i = 0; i < sizeof(test_data); i++) test_data[i] = i; for (i = 0; i < TEST_DATA_LENGTH; i++) { buf_output = o_stream_create_buffer(buf); output = o_stream_create_failure_at(buf_output, i, TEST_ERRMSG); if (i > 0) test_assert(o_stream_send(output, test_data, sizeof(test_data)) == (int)i); test_assert_idx(o_stream_send(output, test_data, sizeof(test_data)) == -1 && output->offset == i && output->stream_errno == EIO && strcmp(o_stream_get_error(output), TEST_ERRMSG) == 0, i); o_stream_destroy(&output); o_stream_destroy(&buf_output); } /* shouldn't fail */ buf_output = o_stream_create_buffer(buf); output = o_stream_create_failure_at(buf_output, TEST_DATA_LENGTH, TEST_ERRMSG); test_assert(o_stream_send(output, test_data, sizeof(test_data)) == TEST_DATA_LENGTH); test_assert(o_stream_flush(output) > 0 && output->offset == TEST_DATA_LENGTH && output->stream_errno == 0); o_stream_destroy(&output); o_stream_destroy(&buf_output); /* fail at flush */ buf_output = o_stream_create_buffer(buf); output = o_stream_create_failure_at_flush(buf_output, TEST_ERRMSG); test_assert(o_stream_send(output, test_data, sizeof(test_data)) == TEST_DATA_LENGTH); test_assert(o_stream_flush(output) < 0 && output->stream_errno == EIO && strcmp(o_stream_get_error(output), TEST_ERRMSG) == 0); o_stream_destroy(&output); o_stream_destroy(&buf_output); test_end(); } dovecot-2.3.21.1/src/lib/unichar.c0000644000000000000000000002334414656633576013455 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "bsearch-insert-pos.h" #include "unichar.h" #include "unicodemap.c" #define HANGUL_FIRST 0xac00 #define HANGUL_LAST 0xd7a3 const unsigned char utf8_replacement_char[UTF8_REPLACEMENT_CHAR_LEN] = { 0xef, 0xbf, 0xbd }; /* 0xfffd */ static const uint8_t utf8_non1_bytes[256 - 192 - 2] = { 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1 }; const uint8_t *const uni_utf8_non1_bytes = utf8_non1_bytes; unsigned int uni_strlen(const unichar_t *str) { unsigned int len = 0; for (len = 0; str[len] != 0; len++) ; return len; } int uni_utf8_get_char(const char *input, unichar_t *chr_r) { return uni_utf8_get_char_n((const unsigned char *)input, SIZE_MAX, chr_r); } int uni_utf8_get_char_n(const void *_input, size_t max_len, unichar_t *chr_r) { static unichar_t lowest_valid_chr_table[] = { 0, 0, 0x80, 0x800, 0x10000, 0x200000, 0x4000000 }; const unsigned char *input = _input; unichar_t chr, lowest_valid_chr; unsigned int i, len; int ret; i_assert(max_len > 0); if (*input < 0x80) { *chr_r = *input; return 1; } /* first byte has len highest bits set, followed by zero bit. the rest of the bits are used as the highest bits of the value. */ chr = *input; len = uni_utf8_char_bytes(*input); switch (len) { case 2: chr &= 0x1f; break; case 3: chr &= 0x0f; break; case 4: chr &= 0x07; break; case 5: chr &= 0x03; break; case 6: chr &= 0x01; break; default: /* only 7bit chars should have len==1 */ i_assert(len == 1); return -1; } if (len <= max_len) { lowest_valid_chr = lowest_valid_chr_table[len]; ret = len; } else { /* check first if the input is invalid before returning 0 */ lowest_valid_chr = 0; ret = 0; len = max_len; } /* the following bytes must all be 10xxxxxx */ for (i = 1; i < len; i++) { if ((input[i] & 0xc0) != 0x80) return input[i] == '\0' ? 0 : -1; chr <<= 6; chr |= input[i] & 0x3f; } /* these are specified as invalid encodings by standards see RFC3629 */ if (!uni_is_valid_ucs4(chr)) return -1; if (chr < lowest_valid_chr) { /* overlong encoding */ return -1; } *chr_r = chr; return ret; } int uni_utf8_to_ucs4(const char *input, ARRAY_TYPE(unichars) *output) { unichar_t chr; while (*input != '\0') { int len = uni_utf8_get_char(input, &chr); if (len <= 0) { /* invalid input */ return -1; } input += len; array_push_back(output, &chr); } return 0; } int uni_utf8_to_ucs4_n(const unsigned char *input, size_t size, ARRAY_TYPE(unichars) *output) { unichar_t chr; while (size > 0) { int len = uni_utf8_get_char_n(input, size, &chr); if (len <= 0) return -1; /* invalid input */ input += len; size -= len; array_push_back(output, &chr); } return 0; } void uni_ucs4_to_utf8(const unichar_t *input, size_t len, buffer_t *output) { for (; len > 0 && *input != '\0'; input++, len--) uni_ucs4_to_utf8_c(*input, output); } void uni_ucs4_to_utf8_c(unichar_t chr, buffer_t *output) { unsigned char first; int bitpos; if (chr < 0x80) { buffer_append_c(output, chr); return; } i_assert(uni_is_valid_ucs4(chr)); if (chr < (1 << (6 + 5))) { /* 110xxxxx */ bitpos = 6; first = 0x80 | 0x40; } else if (chr < (1 << ((2*6) + 4))) { /* 1110xxxx */ bitpos = 2*6; first = 0x80 | 0x40 | 0x20; } else if (chr < (1 << ((3*6) + 3))) { /* 11110xxx */ bitpos = 3*6; first = 0x80 | 0x40 | 0x20 | 0x10; } else if (chr < (1 << ((4*6) + 2))) { /* 111110xx */ bitpos = 4*6; first = 0x80 | 0x40 | 0x20 | 0x10 | 0x08; } else { /* 1111110x */ bitpos = 5*6; first = 0x80 | 0x40 | 0x20 | 0x10 | 0x08 | 0x04; } buffer_append_c(output, first | (chr >> bitpos)); do { bitpos -= 6; buffer_append_c(output, 0x80 | ((chr >> bitpos) & 0x3f)); } while (bitpos > 0); } unsigned int uni_utf8_strlen(const char *input) { return uni_utf8_strlen_n(input, strlen(input)); } unsigned int uni_utf8_strlen_n(const void *input, size_t size) { size_t partial_pos; return uni_utf8_partial_strlen_n(input, size, &partial_pos); } unsigned int uni_utf8_partial_strlen_n(const void *_input, size_t size, size_t *partial_pos_r) { const unsigned char *input = _input; unsigned int count, len = 0; size_t i; for (i = 0; i < size; ) { count = uni_utf8_char_bytes(input[i]); if (i + count > size) break; i += count; len++; } *partial_pos_r = i; return len; } static bool uint16_find(const uint16_t *data, unsigned int count, uint16_t value, unsigned int *idx_r) { BINARY_NUMBER_SEARCH(data, count, value, idx_r); } static bool uint32_find(const uint32_t *data, unsigned int count, uint32_t value, unsigned int *idx_r) { BINARY_NUMBER_SEARCH(data, count, value, idx_r); } unichar_t uni_ucs4_to_titlecase(unichar_t chr) { unsigned int idx; if (chr <= 0xff) return titlecase8_map[chr]; else if (chr <= 0xffff) { if (!uint16_find(titlecase16_keys, N_ELEMENTS(titlecase16_keys), chr, &idx)) return chr; else return titlecase16_values[idx]; } else { if (!uint32_find(titlecase32_keys, N_ELEMENTS(titlecase32_keys), chr, &idx)) return chr; else return titlecase32_values[idx]; } } static bool uni_ucs4_decompose_uni(unichar_t *chr) { unsigned int idx; if (*chr <= 0xff) { if (uni8_decomp_map[*chr] == *chr) return FALSE; *chr = uni8_decomp_map[*chr]; } else if (*chr <= 0xffff) { if (*chr < uni16_decomp_keys[0]) return FALSE; if (!uint16_find(uni16_decomp_keys, N_ELEMENTS(uni16_decomp_keys), *chr, &idx)) return FALSE; *chr = uni16_decomp_values[idx]; } else { if (!uint32_find(uni32_decomp_keys, N_ELEMENTS(uni32_decomp_keys), *chr, &idx)) return FALSE; *chr = uni32_decomp_values[idx]; } return TRUE; } static void uni_ucs4_decompose_hangul_utf8(unichar_t chr, buffer_t *output) { #define SBase HANGUL_FIRST #define LBase 0x1100 #define VBase 0x1161 #define TBase 0x11A7 #define LCount 19 #define VCount 21 #define TCount 28 #define NCount (VCount * TCount) unsigned int SIndex = chr - SBase; unichar_t L = LBase + SIndex / NCount; unichar_t V = VBase + (SIndex % NCount) / TCount; unichar_t T = TBase + SIndex % TCount; uni_ucs4_to_utf8_c(L, output); uni_ucs4_to_utf8_c(V, output); if (T != TBase) uni_ucs4_to_utf8_c(T, output); } static bool uni_ucs4_decompose_multi_utf8(unichar_t chr, buffer_t *output) { const uint32_t *value; unsigned int idx; if (chr < multidecomp_keys[0] || chr > 0xffff) return FALSE; if (!uint32_find(multidecomp_keys, N_ELEMENTS(multidecomp_keys), chr, &idx)) return FALSE; value = &multidecomp_values[multidecomp_offsets[idx]]; for (; *value != 0; value++) uni_ucs4_to_utf8_c(*value, output); return TRUE; } static void output_add_replacement_char(buffer_t *output) { if (output->used >= UTF8_REPLACEMENT_CHAR_LEN && memcmp(CONST_PTR_OFFSET(output->data, output->used - UTF8_REPLACEMENT_CHAR_LEN), utf8_replacement_char, UTF8_REPLACEMENT_CHAR_LEN) == 0) { /* don't add the replacement char multiple times */ return; } buffer_append(output, utf8_replacement_char, UTF8_REPLACEMENT_CHAR_LEN); } int uni_utf8_to_decomposed_titlecase(const void *_input, size_t size, buffer_t *output) { const unsigned char *input = _input; unichar_t chr; int ret = 0; while (size > 0) { int bytes = uni_utf8_get_char_n(input, size, &chr); if (bytes <= 0) { /* invalid input. try the next byte. */ ret = -1; input++; size--; output_add_replacement_char(output); continue; } input += bytes; size -= bytes; chr = uni_ucs4_to_titlecase(chr); if (chr >= HANGUL_FIRST && chr <= HANGUL_LAST) uni_ucs4_decompose_hangul_utf8(chr, output); else if (uni_ucs4_decompose_uni(&chr) || !uni_ucs4_decompose_multi_utf8(chr, output)) uni_ucs4_to_utf8_c(chr, output); } return ret; } static inline unsigned int is_valid_utf8_seq(const unsigned char *input, unsigned int size) { unichar_t chr; int len = uni_utf8_get_char_n(input, size, &chr); return len <= 0 ? 0 : len; } static int uni_utf8_find_invalid_pos(const unsigned char *input, size_t size, size_t *pos_r) { size_t i, len; /* find the first invalid utf8 sequence */ for (i = 0; i < size;) { if (input[i] < 0x80) i++; else { len = is_valid_utf8_seq(input + i, size-i); if (unlikely(len == 0)) { *pos_r = i; return -1; } i += len; } } return 0; } bool uni_utf8_get_valid_data(const unsigned char *input, size_t size, buffer_t *buf) { size_t i, len; if (uni_utf8_find_invalid_pos(input, size, &i) == 0) return TRUE; /* broken utf-8 input - skip the broken characters */ buffer_append(buf, input, i++); output_add_replacement_char(buf); while (i < size) { if (input[i] < 0x80) { buffer_append_c(buf, input[i++]); continue; } len = is_valid_utf8_seq(input + i, size-i); if (len == 0) { i++; output_add_replacement_char(buf); continue; } buffer_append(buf, input + i, len); i += len; } return FALSE; } bool uni_utf8_str_is_valid(const char *str) { size_t i; return uni_utf8_find_invalid_pos((const unsigned char *)str, strlen(str), &i) == 0; } bool uni_utf8_data_is_valid(const unsigned char *data, size_t size) { size_t i; return uni_utf8_find_invalid_pos(data, size, &i) == 0; } size_t uni_utf8_data_truncate(const unsigned char *data, size_t old_size, size_t max_new_size) { if (max_new_size >= old_size) return old_size; if (max_new_size == 0) return 0; if ((data[max_new_size] & 0x80) == 0) return max_new_size; while (max_new_size > 0 && (data[max_new_size-1] & 0xc0) == 0x80) max_new_size--; if (max_new_size > 0 && (data[max_new_size-1] & 0xc0) == 0xc0) max_new_size--; return max_new_size; } dovecot-2.3.21.1/src/lib/sleep.c0000644000000000000000000000306614656633576013133 00000000000000/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "sleep.h" #include static bool ATTR_NOWARN_UNUSED_RESULT sleep_timespec(const struct timespec *ts_sleep, bool interruptible) { struct timespec ts_remain = *ts_sleep; while (nanosleep(&ts_remain, &ts_remain) < 0) { if (errno != EINTR) i_fatal("nanosleep(): %m"); if (interruptible) return FALSE; } return TRUE; } void i_sleep_usecs(unsigned long long usecs) { struct timespec ts_sleep; ts_sleep.tv_sec = (time_t)(usecs / 1000000); ts_sleep.tv_nsec = (long)(usecs % 1000000) * 1000; sleep_timespec(&ts_sleep, FALSE); } void i_sleep_msecs(unsigned int msecs) { struct timespec ts_sleep; ts_sleep.tv_sec = (time_t)(msecs / 1000); ts_sleep.tv_nsec = (long)(msecs % 1000) * 1000000; sleep_timespec(&ts_sleep, FALSE); } void i_sleep_secs(time_t secs) { struct timespec ts_sleep; ts_sleep.tv_sec = secs; ts_sleep.tv_nsec = 0; sleep_timespec(&ts_sleep, FALSE); } bool i_sleep_intr_usecs(unsigned long long usecs) { struct timespec ts_sleep; ts_sleep.tv_sec = (time_t)(usecs / 1000000); ts_sleep.tv_nsec = (long)(usecs % 1000000) * 1000; return sleep_timespec(&ts_sleep, TRUE); } bool i_sleep_intr_msecs(unsigned int msecs) { struct timespec ts_sleep; ts_sleep.tv_sec = (time_t)(msecs / 1000); ts_sleep.tv_nsec = (long)(msecs % 1000) * 1000000; return sleep_timespec(&ts_sleep, TRUE); } bool i_sleep_intr_secs(time_t secs) { struct timespec ts_sleep; ts_sleep.tv_sec = secs; ts_sleep.tv_nsec = 0; return sleep_timespec(&ts_sleep, TRUE); } dovecot-2.3.21.1/src/lib/test-file-cache.c0000644000000000000000000002151514656633576014757 00000000000000/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "istream.h" #include "ostream.h" #include "file-cache.h" #include #include #include #include #include #include #define TEST_FILENAME ".test_file_cache" static void test_file_cache_read(void) { test_begin("file_cache_read"); /* create a file */ struct ostream *os = o_stream_create_file(TEST_FILENAME, 0, 0600, 0); o_stream_nsend_str(os, "initial data\n"); test_assert(o_stream_finish(os) == 1); o_stream_destroy(&os); int fd = open(TEST_FILENAME, O_RDONLY); i_assert(fd > -1); struct file_cache *cache = file_cache_new_path(fd, TEST_FILENAME); /* this should be 0 before read */ size_t size; const unsigned char *map = file_cache_get_map(cache, &size); test_assert(map == NULL); test_assert(size == 0); test_assert(map == NULL); test_assert(file_cache_read(cache, 0, 13) == 13); map = file_cache_get_map(cache, &size); test_assert(map != NULL && size == 13 && memcmp(map, "initial data\n", 13) == 0); file_cache_free(&cache); i_close_fd(&fd); i_unlink(TEST_FILENAME); test_end(); } static void test_file_cache_write_read(void) { test_begin("file_cache_write_read"); /* create a file */ struct ostream *os = o_stream_create_file(TEST_FILENAME, 0, 0600, 0); o_stream_nsend_str(os, "initial data\n"); test_assert(o_stream_finish(os) == 1); o_stream_destroy(&os); int fd = open(TEST_FILENAME, O_RDONLY); i_assert(fd > -1); struct file_cache *cache = file_cache_new_path(fd, TEST_FILENAME); /* this should be 0 before read */ size_t size; const unsigned char *map = file_cache_get_map(cache, &size); test_assert(map == NULL); test_assert(size == 0); test_assert(map == NULL); test_assert(file_cache_read(cache, 0, 13) == 13); file_cache_write(cache, "updated data\n", 13, 0); map = file_cache_get_map(cache, &size); test_assert(map != NULL && size == 13 && memcmp(map, "updated data\n", 13) == 0); file_cache_free(&cache); i_close_fd(&fd); struct istream *is = i_stream_create_file(TEST_FILENAME, SIZE_MAX); const unsigned char *data; test_assert(i_stream_read_more(is, &data, &size) > 0 && size == 13); test_assert(map != NULL && size == 13 && memcmp(data, "initial data\n", 13) == 0); i_stream_destroy(&is); i_unlink(TEST_FILENAME); test_end(); } static void test_file_cache_read_invalidate(void) { test_begin("file_cache_read_invalidate"); /* create a file */ struct ostream *os = o_stream_create_file(TEST_FILENAME, 0, 0600, 0); o_stream_nsend_str(os, "initial data\n"); test_assert(o_stream_finish(os) == 1); o_stream_destroy(&os); int fd = open(TEST_FILENAME, O_RDONLY); i_assert(fd > -1); struct file_cache *cache = file_cache_new_path(fd, TEST_FILENAME); /* this should be 0 before read */ size_t size; test_assert(file_cache_read(cache, 0, 13) == 13); const unsigned char *map = file_cache_get_map(cache, &size); test_assert(map != NULL && size == 13 && memcmp(map, "initial data\n", 13) == 0); /* update file */ os = o_stream_create_file(TEST_FILENAME, 0, 0600, 0); o_stream_nsend_str(os, "updated data\n"); test_assert(o_stream_finish(os) == 1); o_stream_destroy(&os); map = file_cache_get_map(cache, &size); test_assert(map != NULL && size == 13 && memcmp(map, "initial data\n", 13) == 0); /* invalidate cache */ file_cache_invalidate(cache, 0, size); test_assert(file_cache_read(cache, 0, 13) == 13); map = file_cache_get_map(cache, &size); test_assert(size == 13); test_assert(map != NULL && size == 13 && memcmp(map, "updated data\n", 13) == 0); file_cache_free(&cache); i_close_fd(&fd); i_unlink(TEST_FILENAME); test_end(); } static void test_file_cache_multipage(void) { test_begin("file_cache_multipage"); size_t page_size = getpagesize(); struct ostream *os = o_stream_create_file(TEST_FILENAME, 0, 0600, 0); size_t total_size = 0; for (size_t i = 0; i < page_size * 3 + 100; i += 12) { o_stream_nsend_str(os, "initial data"); total_size += 12; } test_assert(o_stream_finish(os) == 1); o_stream_destroy(&os); int fd = open(TEST_FILENAME, O_RDONLY); i_assert(fd > -1); struct file_cache *cache = file_cache_new_path(fd, TEST_FILENAME); /* read everything to memory page at a time */ test_assert(file_cache_read(cache, 0, page_size) == (ssize_t)page_size); test_assert(file_cache_read(cache, page_size, page_size) == (ssize_t)page_size); test_assert(file_cache_read(cache, page_size*2, page_size) == (ssize_t)page_size); test_assert(file_cache_read(cache, page_size*3, page_size) == (ssize_t)total_size-(ssize_t)page_size*3); size_t size; const unsigned char *map = file_cache_get_map(cache, &size); test_assert(size == total_size); test_assert(map != NULL); /* write-read-invalidate-read */ for(size_t i = 0; i < page_size * 3; i+= page_size / 3) { char orig[13]; const char *ptr = CONST_PTR_OFFSET(map, i); memcpy(orig, ptr, 12); orig[12] = '\0'; file_cache_write(cache, "updated data", 12, i); map = file_cache_get_map(cache, &size); ptr = CONST_PTR_OFFSET(map, i); test_assert(strncmp(ptr, "updated data", 12) == 0); /* invalidate cache */ file_cache_invalidate(cache, i, 12); /* check that it's back what it was */ test_assert(file_cache_read(cache, i, 12) == 12); map = file_cache_get_map(cache, &size); ptr = CONST_PTR_OFFSET(map, i); test_assert(strncmp(ptr, orig, 12) == 0); } file_cache_free(&cache); i_close_fd(&fd); i_unlink(TEST_FILENAME); test_end(); } static void test_file_cache_anon(void) { /* file-cache should work as anonymous cache for small files */ test_begin("file_cache_anon"); test_assert(access(TEST_FILENAME, F_OK) == -1 && errno == ENOENT); struct file_cache *cache = file_cache_new_path(-1, TEST_FILENAME); test_assert(file_cache_set_size(cache, 1024) == 0); file_cache_write(cache, "initial data", 12, 0); size_t size; const unsigned char *map = file_cache_get_map(cache, &size); test_assert(map != NULL && size == 12 && memcmp(map, "initial data", 12) == 0); file_cache_free(&cache); i_unlink_if_exists(TEST_FILENAME); test_end(); } static void test_file_cache_switch_fd(void) { test_begin("file_cache_switch_fd"); test_assert(access(TEST_FILENAME, F_OK) == -1 && errno == ENOENT); struct file_cache *cache = file_cache_new_path(-1, TEST_FILENAME); test_assert(file_cache_set_size(cache, 13) == 0); file_cache_write(cache, "initial data\n", 13, 0); /* create a file */ struct ostream *os = o_stream_create_file(TEST_FILENAME, 0, 0600, 0); o_stream_nsend_str(os, "updated data\n"); test_assert(o_stream_finish(os) == 1); o_stream_destroy(&os); int fd = open(TEST_FILENAME, O_RDONLY); i_assert(fd > -1); /* map should be invalidated and updated data read from given file */ file_cache_set_fd(cache, fd); test_assert(file_cache_read(cache, 0, 13) == 13); size_t size; const unsigned char *map = file_cache_get_map(cache, &size); test_assert(map != NULL && size == 13 && memcmp(map, "updated data\n", 13) == 0); file_cache_free(&cache); i_close_fd(&fd); i_unlink(TEST_FILENAME); test_end(); } static void test_file_cache_errors(void) { test_begin("file_cache_errors"); size_t page_size = getpagesize(); test_assert(access(TEST_FILENAME, F_OK) == -1 && errno == ENOENT); int fd = open(TEST_FILENAME, O_RDONLY); struct file_cache *cache = file_cache_new_path(fd, TEST_FILENAME); size_t size; /* file does not exist and we try large enough mapping */ test_expect_error_string("fstat(.test_file_cache) failed: " "Bad file descriptor"); test_assert(file_cache_read(cache, 0, 2*1024*1024) == -1); const unsigned char *map = file_cache_get_map(cache, &size); test_assert(size == 0); test_assert(map == NULL); /* temporarily set a small memory limit to make mmap attempt fail */ struct rlimit rl_cur; test_assert(getrlimit(RLIMIT_AS, &rl_cur) == 0); struct rlimit rl_new = { .rlim_cur = 1, .rlim_max = rl_cur.rlim_max }; const char *errstr = t_strdup_printf("mmap_anon(.test_file_cache, %zu) failed: " "Cannot allocate memory", page_size); test_assert(setrlimit(RLIMIT_AS, &rl_new) == 0); test_expect_error_string(errstr); test_assert(file_cache_set_size(cache, 1024) == -1); test_assert(setrlimit(RLIMIT_AS, &rl_cur) == 0); /* same for mremap */ errstr = t_strdup_printf("mremap_anon(.test_file_cache, %zu) failed: " "Cannot allocate memory", page_size*2); test_assert(file_cache_set_size(cache, 1) == 0); test_assert(setrlimit(RLIMIT_AS, &rl_new) == 0); test_expect_error_string(errstr); test_assert(file_cache_set_size(cache, page_size*2) == -1); test_assert(setrlimit(RLIMIT_AS, &rl_cur) == 0); file_cache_free(&cache); i_close_fd(&fd); i_unlink_if_exists(TEST_FILENAME); test_end(); } void test_file_cache(void) { test_file_cache_read(); test_file_cache_write_read(); test_file_cache_read_invalidate(); test_file_cache_multipage(); test_file_cache_anon(); test_file_cache_switch_fd(); test_file_cache_errors(); } dovecot-2.3.21.1/src/lib/hostpid.c0000644000000000000000000000300314656633576013464 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "hostpid.h" #include #include #define HOSTNAME_DISALLOWED_CHARS "/\r\n\t" const char *my_hostname = NULL; const char *my_pid = NULL; static char *my_hostname_dup = NULL; static char *my_domain = NULL; void hostpid_init(void) { static char pid[MAX_INT_STRLEN]; char hostname[256]; const char *value; /* allow calling hostpid_init() multiple times to reset hostname */ i_free_and_null(my_hostname_dup); i_free_and_null(my_domain); value = getenv(MY_HOSTNAME_ENV); if (value == NULL) { if (gethostname(hostname, sizeof(hostname)-1) < 0) i_fatal("gethostname() failed: %m"); hostname[sizeof(hostname)-1] = '\0'; value = hostname; } if (value[0] == '\0' || strcspn(value, HOSTNAME_DISALLOWED_CHARS) != strlen(value)) i_fatal("Invalid system hostname: '%s'", value); my_hostname_dup = i_strdup(value); my_hostname = my_hostname_dup; i_snprintf(pid, sizeof(pid), "%lld", (long long)getpid()); my_pid = pid; } void hostpid_deinit(void) { i_free(my_hostname_dup); i_free(my_domain); } const char *my_hostdomain(void) { struct hostent *hent; const char *name; if (my_domain == NULL) { name = getenv(MY_HOSTDOMAIN_ENV); if (name == NULL) { hent = gethostbyname(my_hostname); name = hent != NULL ? hent->h_name : NULL; if (name == NULL) { /* failed, use just the hostname */ name = my_hostname; } } my_domain = i_strdup(name); } return my_domain; } dovecot-2.3.21.1/src/lib/json-parser.c0000644000000000000000000005053314656633576014267 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "istream.h" #include "hex-dec.h" #include "unichar.h" #include "istream-jsonstr.h" #include "json-parser.h" enum json_state { JSON_STATE_ROOT = 0, JSON_STATE_OBJECT_OPEN, JSON_STATE_OBJECT_KEY, JSON_STATE_OBJECT_COLON, JSON_STATE_OBJECT_VALUE, JSON_STATE_OBJECT_SKIP_STRING, JSON_STATE_OBJECT_NEXT, JSON_STATE_ARRAY_OPEN, JSON_STATE_ARRAY_VALUE, JSON_STATE_ARRAY_SKIP_STRING, JSON_STATE_ARRAY_NEXT, JSON_STATE_ARRAY_NEXT_SKIP, JSON_STATE_VALUE, JSON_STATE_DONE }; struct json_parser { pool_t pool; struct istream *input; uoff_t highwater_offset; enum json_parser_flags flags; const unsigned char *start, *end, *data; const char *error; string_t *value; struct istream *strinput; enum json_state state; ARRAY(enum json_state) nesting; unsigned int nested_skip_count; bool skipping; bool seen_eof; }; static int json_parser_read_more(struct json_parser *parser) { uoff_t cur_highwater = parser->input->v_offset + i_stream_get_data_size(parser->input); size_t size; ssize_t ret; i_assert(parser->highwater_offset <= cur_highwater); if (parser->error != NULL) return -1; if (parser->highwater_offset == cur_highwater) { ret = i_stream_read(parser->input); if (ret == -2) { parser->error = "Token too large"; return -1; } if (ret < 0 && !parser->seen_eof && i_stream_get_data_size(parser->input) > 0 && parser->input->stream_errno == 0) { /* call it once more to finish any pending number */ parser->seen_eof = TRUE; } else if (ret <= 0) { return ret; } else { cur_highwater = parser->input->v_offset + i_stream_get_data_size(parser->input); i_assert(parser->highwater_offset < cur_highwater); parser->highwater_offset = cur_highwater; } } parser->start = parser->data = i_stream_get_data(parser->input, &size); parser->end = parser->start + size; i_assert(size > 0); return 1; } static void json_parser_update_input_pos(struct json_parser *parser) { size_t size; if (parser->data == parser->start) return; i_stream_skip(parser->input, parser->data - parser->start); parser->start = parser->data = i_stream_get_data(parser->input, &size); parser->end = parser->start + size; if (size > 0) { /* we skipped over some data and there's still data left. no need to read() the next time. */ parser->highwater_offset = 0; } else { parser->highwater_offset = parser->input->v_offset; } } struct json_parser *json_parser_init(struct istream *input) { return json_parser_init_flags(input, 0); } struct json_parser *json_parser_init_flags(struct istream *input, enum json_parser_flags flags) { struct json_parser *parser; pool_t pool = pool_alloconly_create("json parser", sizeof(struct json_parser)+64); parser = p_new(pool, struct json_parser, 1); parser->pool = pool; parser->input = input; parser->flags = flags; parser->value = str_new(default_pool, 128); i_array_init(&parser->nesting, 8); i_stream_ref(input); if ((flags & JSON_PARSER_NO_ROOT_OBJECT) != 0) parser->state = JSON_STATE_VALUE; return parser; } int json_parser_deinit(struct json_parser **_parser, const char **error_r) { struct json_parser *parser = *_parser; *_parser = NULL; if (parser->error != NULL) { /* actual parser error */ *error_r = t_strdup(parser->error); } else if (parser->input->stream_errno != 0) { *error_r = t_strdup_printf("read(%s) failed: %s", i_stream_get_name(parser->input), i_stream_get_error(parser->input)); } else if (parser->data == parser->end && !i_stream_have_bytes_left(parser->input) && parser->state != JSON_STATE_DONE) { *error_r = "Missing '}'"; } else { *error_r = NULL; } i_stream_unref(&parser->input); array_free(&parser->nesting); str_free(&parser->value); pool_unref(&parser->pool); return *error_r != NULL ? -1 : 0; } static bool json_parse_whitespace(struct json_parser *parser) { for (; parser->data != parser->end; parser->data++) { switch (*parser->data) { case ' ': case '\t': case '\r': case '\n': break; default: json_parser_update_input_pos(parser); return TRUE; } } json_parser_update_input_pos(parser); return FALSE; } static int json_skip_string(struct json_parser *parser) { for (; parser->data != parser->end; parser->data++) { if (*parser->data == '"') { parser->data++; json_parser_update_input_pos(parser); return 1; } if (*parser->data == '\\') { parser->data++; if (parser->data == parser->end) break; switch (*parser->data) { case '"': case '\\': case '/': case 'b': case 'f': case 'n': case 'r': case 't': break; case 'u': if (parser->end - parser->data < 4) { parser->data = parser->end; return -1; } parser->data += 3; break; default: parser->error = "Invalid escape string"; return -1; } } } json_parser_update_input_pos(parser); return 0; } static int json_parse_unicode_escape(struct json_parser *parser) { char chbuf[5] = {0}; unichar_t chr, hi_surg; parser->data++; if (parser->end - parser->data < 4) { /* wait for more data */ parser->data = parser->end; return 0; } memcpy(chbuf, parser->data, 4); if (str_to_uint32_hex(chbuf, &chr) < 0) { parser->error = "Invalid unicode escape seen"; return -1; } if (UTF16_VALID_HIGH_SURROGATE(chr)) { /* possible surrogate pair */ hi_surg = chr; chr = 0; parser->data += 4; if (parser->data >= parser->end) { /* wait for more data */ parser->data = parser->end; return 0; } if ((parser->end - parser->data) < 2) { if (parser->data[0] == '\\') { /* wait for more data */ parser->data = parser->end; return 0; } /* error */ } if ((parser->end - parser->data) < 6) { if (parser->data[0] == '\\' && parser->data[1] == 'u') { /* wait for more data */ parser->data = parser->end; return 0; } /* error */ } else { memcpy(chbuf, &parser->data[2], 4); if (str_to_uint32_hex(chbuf, &chr) < 0) { parser->error = "Invalid unicode escape seen"; return -1; } } if (parser->data[0] != '\\' || parser->data[1] != 'u' || !UTF16_VALID_LOW_SURROGATE(chr)) { parser->error = p_strdup_printf(parser->pool, "High surrogate 0x%04x seen, " "but not followed by low surrogate", hi_surg); return -1; } chr = uni_join_surrogate(hi_surg, chr); parser->data += 2; } if (!uni_is_valid_ucs4(chr)) { parser->error = p_strdup_printf(parser->pool, "Invalid unicode character U+%04x", chr); return -1; } if (chr == 0) { parser->error = "\\u0000 not supported in strings"; return -1; } uni_ucs4_to_utf8_c(chr, parser->value); parser->data += 3; return 1; } static int json_parse_string(struct json_parser *parser, bool allow_skip, const char **value_r) { int ret; if (*parser->data != '"') return -1; parser->data++; if (parser->skipping && allow_skip) { *value_r = NULL; return json_skip_string(parser); } str_truncate(parser->value, 0); for (; parser->data != parser->end; parser->data++) { if (*parser->data == '"') { parser->data++; *value_r = str_c(parser->value); return 1; } switch (*parser->data) { case '\\': if (++parser->data == parser->end) return 0; switch (*parser->data) { case '"': case '\\': case '/': str_append_c(parser->value, *parser->data); break; case 'b': str_append_c(parser->value, '\b'); break; case 'f': str_append_c(parser->value, '\f'); break; case 'n': str_append_c(parser->value, '\n'); break; case 'r': str_append_c(parser->value, '\r'); break; case 't': str_append_c(parser->value, '\t'); break; case 'u': if ((ret=json_parse_unicode_escape(parser)) <= 0) return ret; break; default: parser->error = "Invalid escape string"; return -1; } break; case '\0': parser->error = "NULs not supported in strings"; return -1; default: str_append_c(parser->value, *parser->data); break; } } return 0; } static int json_parse_digits(struct json_parser *parser) { if (parser->data == parser->end) return 0; if (*parser->data < '0' || *parser->data > '9') return -1; while (parser->data != parser->end && *parser->data >= '0' && *parser->data <= '9') str_append_c(parser->value, *parser->data++); return 1; } static int json_parse_int(struct json_parser *parser) { int ret; if (*parser->data == '-') { str_append_c(parser->value, *parser->data++); if (parser->data == parser->end) return 0; } if (*parser->data == '0') str_append_c(parser->value, *parser->data++); else { if ((ret = json_parse_digits(parser)) <= 0) return ret; } return 1; } static int json_parse_number(struct json_parser *parser, const char **value_r) { int ret; str_truncate(parser->value, 0); if ((ret = json_parse_int(parser)) <= 0) return ret; if (parser->data != parser->end && *parser->data == '.') { /* frac */ str_append_c(parser->value, *parser->data++); if ((ret = json_parse_digits(parser)) <= 0) return ret; } if (parser->data != parser->end && (*parser->data == 'e' || *parser->data == 'E')) { /* exp */ str_append_c(parser->value, *parser->data++); if (parser->data == parser->end) return 0; if (*parser->data == '+' || *parser->data == '-') str_append_c(parser->value, *parser->data++); if ((ret = json_parse_digits(parser)) <= 0) return ret; } if (parser->data == parser->end && !parser->input->eof) return 0; *value_r = str_c(parser->value); return 1; } static int json_parse_atom(struct json_parser *parser, const char *atom) { size_t avail, len = strlen(atom); avail = parser->end - parser->data; if (avail < len) { if (memcmp(parser->data, atom, avail) != 0) return -1; /* everything matches so far, but we need more data */ parser->data += avail; return 0; } if (memcmp(parser->data, atom, len) != 0) return -1; parser->data += len; return 1; } static int json_parse_denest(struct json_parser *parser) { const enum json_state *nested_states; unsigned count; parser->data++; json_parser_update_input_pos(parser); nested_states = array_get(&parser->nesting, &count); i_assert(count > 0); if (count == 1) { /* closing root */ parser->state = JSON_STATE_DONE; if ((parser->flags & JSON_PARSER_NO_ROOT_OBJECT) == 0) return 0; /* we want to return the ending "]" or "}" to caller */ return 1; } /* closing a nested object */ parser->state = nested_states[count-2] == JSON_STATE_OBJECT_OPEN ? JSON_STATE_OBJECT_NEXT : JSON_STATE_ARRAY_NEXT; array_delete(&parser->nesting, count-1, 1); if (parser->nested_skip_count > 0) { parser->nested_skip_count--; return 0; } return 1; } static int json_parse_close_object(struct json_parser *parser, enum json_type *type_r) { if (json_parse_denest(parser) == 0) return 0; *type_r = JSON_TYPE_OBJECT_END; return 1; } static int json_parse_close_array(struct json_parser *parser, enum json_type *type_r) { if (json_parse_denest(parser) == 0) return 0; *type_r = JSON_TYPE_ARRAY_END; return 1; } static void json_parser_object_open(struct json_parser *parser) { parser->data++; parser->state = JSON_STATE_OBJECT_OPEN; array_push_back(&parser->nesting, &parser->state); json_parser_update_input_pos(parser); } static int json_try_parse_next(struct json_parser *parser, enum json_type *type_r, const char **value_r) { bool skipping = parser->skipping; int ret; if (!json_parse_whitespace(parser)) return -1; switch (parser->state) { case JSON_STATE_ROOT: if (*parser->data != '{') { parser->error = "Object doesn't begin with '{'"; return -1; } json_parser_object_open(parser); return 0; case JSON_STATE_OBJECT_VALUE: case JSON_STATE_ARRAY_VALUE: case JSON_STATE_VALUE: if (*parser->data == '{') { json_parser_object_open(parser); if (parser->skipping) { parser->nested_skip_count++; return 0; } *type_r = JSON_TYPE_OBJECT; return 1; } else if (*parser->data == '[') { parser->data++; parser->state = JSON_STATE_ARRAY_OPEN; array_push_back(&parser->nesting, &parser->state); json_parser_update_input_pos(parser); if (parser->skipping) { parser->nested_skip_count++; return 0; } *type_r = JSON_TYPE_ARRAY; return 1; } if ((ret = json_parse_string(parser, TRUE, value_r)) >= 0) { *type_r = JSON_TYPE_STRING; } else if ((ret = json_parse_number(parser, value_r)) >= 0) { *type_r = JSON_TYPE_NUMBER; } else if ((ret = json_parse_atom(parser, "true")) >= 0) { *type_r = JSON_TYPE_TRUE; *value_r = "true"; } else if ((ret = json_parse_atom(parser, "false")) >= 0) { *type_r = JSON_TYPE_FALSE; *value_r = "false"; } else if ((ret = json_parse_atom(parser, "null")) >= 0) { *type_r = JSON_TYPE_NULL; *value_r = NULL; } else { if (parser->error == NULL) parser->error = "Invalid data as value"; return -1; } if (ret == 0) { i_assert(parser->data == parser->end); if (parser->skipping && *type_r == JSON_TYPE_STRING) { /* a large string that we want to skip over. */ json_parser_update_input_pos(parser); parser->state = parser->state == JSON_STATE_OBJECT_VALUE ? JSON_STATE_OBJECT_SKIP_STRING : JSON_STATE_ARRAY_SKIP_STRING; return 0; } return -1; } switch (parser->state) { case JSON_STATE_OBJECT_VALUE: parser->state = JSON_STATE_OBJECT_NEXT; break; case JSON_STATE_ARRAY_VALUE: parser->state = JSON_STATE_ARRAY_NEXT; break; case JSON_STATE_VALUE: parser->state = JSON_STATE_DONE; break; default: i_unreached(); } break; case JSON_STATE_OBJECT_OPEN: if (*parser->data == '}') return json_parse_close_object(parser, type_r); parser->state = JSON_STATE_OBJECT_KEY; /* fall through */ case JSON_STATE_OBJECT_KEY: if (json_parse_string(parser, FALSE, value_r) <= 0) { parser->error = "Expected string as object key"; return -1; } *type_r = JSON_TYPE_OBJECT_KEY; parser->state = JSON_STATE_OBJECT_COLON; break; case JSON_STATE_OBJECT_COLON: if (*parser->data != ':') { parser->error = "Expected ':' after key"; return -1; } parser->data++; parser->state = JSON_STATE_OBJECT_VALUE; json_parser_update_input_pos(parser); return 0; case JSON_STATE_OBJECT_NEXT: if (parser->skipping && parser->nested_skip_count == 0) { /* we skipped over the previous value */ parser->skipping = FALSE; } if (*parser->data == '}') return json_parse_close_object(parser, type_r); if (*parser->data != ',') { parser->error = "Expected ',' or '}' after object value"; return -1; } parser->state = JSON_STATE_OBJECT_KEY; parser->data++; json_parser_update_input_pos(parser); return 0; case JSON_STATE_ARRAY_OPEN: if (*parser->data == ']') return json_parse_close_array(parser, type_r); parser->state = JSON_STATE_ARRAY_VALUE; return 0; case JSON_STATE_ARRAY_NEXT: if (parser->skipping && parser->nested_skip_count == 0) { /* we skipped over the previous value */ parser->skipping = FALSE; } /* fall through */ case JSON_STATE_ARRAY_NEXT_SKIP: if (*parser->data == ']') return json_parse_close_array(parser, type_r); if (*parser->data != ',') { parser->error = "Expected ',' or '}' after array value"; return -1; } parser->state = JSON_STATE_ARRAY_VALUE; parser->data++; json_parser_update_input_pos(parser); return 0; case JSON_STATE_OBJECT_SKIP_STRING: case JSON_STATE_ARRAY_SKIP_STRING: if (json_skip_string(parser) <= 0) return -1; parser->state = parser->state == JSON_STATE_OBJECT_SKIP_STRING ? JSON_STATE_OBJECT_NEXT : JSON_STATE_ARRAY_NEXT; return 0; case JSON_STATE_DONE: parser->error = "Unexpected data at the end"; return -1; } json_parser_update_input_pos(parser); return skipping ? 0 : 1; } int json_parse_next(struct json_parser *parser, enum json_type *type_r, const char **value_r) { int ret; i_assert(parser->strinput == NULL); *value_r = NULL; while ((ret = json_parser_read_more(parser)) > 0) { while ((ret = json_try_parse_next(parser, type_r, value_r)) == 0) ; if (ret > 0) break; if (parser->data != parser->end) return -1; /* parsing probably failed because there wasn't enough input. reset the error and try reading more. */ parser->error = NULL; parser->highwater_offset = parser->input->v_offset + i_stream_get_data_size(parser->input); } return ret; } void json_parse_skip_next(struct json_parser *parser) { i_assert(!parser->skipping); i_assert(parser->strinput == NULL); i_assert(parser->state == JSON_STATE_OBJECT_COLON || parser->state == JSON_STATE_OBJECT_VALUE || parser->state == JSON_STATE_ARRAY_VALUE || parser->state == JSON_STATE_ARRAY_NEXT); parser->skipping = TRUE; if (parser->state == JSON_STATE_ARRAY_NEXT) parser->state = JSON_STATE_ARRAY_NEXT_SKIP; } void json_parse_skip(struct json_parser *parser) { i_assert(!parser->skipping); i_assert(parser->strinput == NULL); i_assert(parser->state == JSON_STATE_OBJECT_NEXT || parser->state == JSON_STATE_OBJECT_OPEN || parser->state == JSON_STATE_ARRAY_NEXT || parser->state == JSON_STATE_ARRAY_OPEN); if (parser->state == JSON_STATE_OBJECT_OPEN || parser->state == JSON_STATE_ARRAY_OPEN) parser->nested_skip_count++; parser->skipping = TRUE; if (parser->state == JSON_STATE_ARRAY_NEXT) parser->state = JSON_STATE_ARRAY_NEXT_SKIP; } static void json_strinput_destroyed(struct json_parser *parser) { i_assert(parser->strinput != NULL); parser->strinput = NULL; } static int json_try_parse_stream_start(struct json_parser *parser, struct istream **input_r) { if (!json_parse_whitespace(parser)) return -1; if (parser->state == JSON_STATE_OBJECT_COLON) { if (*parser->data != ':') { parser->error = "Expected ':' after key"; return -1; } parser->data++; parser->state = JSON_STATE_OBJECT_VALUE; if (!json_parse_whitespace(parser)) return -1; } if (*parser->data != '"') return -1; parser->data++; json_parser_update_input_pos(parser); parser->state = parser->state == JSON_STATE_OBJECT_VALUE ? JSON_STATE_OBJECT_SKIP_STRING : JSON_STATE_ARRAY_SKIP_STRING; parser->strinput = i_stream_create_jsonstr(parser->input); i_stream_add_destroy_callback(parser->strinput, json_strinput_destroyed, parser); *input_r = parser->strinput; return 0; } int json_parse_next_stream(struct json_parser *parser, struct istream **input_r) { int ret; i_assert(!parser->skipping); i_assert(parser->strinput == NULL); i_assert(parser->state == JSON_STATE_OBJECT_COLON || parser->state == JSON_STATE_OBJECT_VALUE || parser->state == JSON_STATE_ARRAY_VALUE); *input_r = NULL; while ((ret = json_parser_read_more(parser)) > 0) { if (json_try_parse_stream_start(parser, input_r) == 0) break; if (parser->data != parser->end) return -1; /* parsing probably failed because there wasn't enough input. reset the error and try reading more. */ parser->error = NULL; parser->highwater_offset = parser->input->v_offset + i_stream_get_data_size(parser->input); } return ret; } static void json_append_escaped_char(string_t *dest, unsigned char src) { switch (src) { case '\b': str_append(dest, "\\b"); break; case '\f': str_append(dest, "\\f"); break; case '\n': str_append(dest, "\\n"); break; case '\r': str_append(dest, "\\r"); break; case '\t': str_append(dest, "\\t"); break; case '"': str_append(dest, "\\\""); break; case '\\': str_append(dest, "\\\\"); break; default: if (src < 0x20 || src >= 0x80) str_printfa(dest, "\\u%04x", src); else str_append_c(dest, src); break; } } void json_append_escaped_ucs4(string_t *dest, unichar_t chr) { if (chr < 0x80) json_append_escaped_char(dest, (unsigned char)chr); else if (chr == 0x2028 || chr == 0x2029) str_printfa(dest, "\\u%04x", chr); else uni_ucs4_to_utf8_c(chr, dest); } void ostream_escaped_json_format(string_t *dest, unsigned char src) { json_append_escaped_char(dest, src); } void json_append_escaped(string_t *dest, const char *src) { json_append_escaped_data(dest, (const unsigned char*)src, strlen(src)); } void json_append_escaped_data(string_t *dest, const unsigned char *src, size_t size) { size_t i; int bytes = 0; unichar_t chr; for (i = 0; i < size;) { bytes = uni_utf8_get_char_n(src+i, size-i, &chr); if (bytes > 0 && uni_is_valid_ucs4(chr)) { json_append_escaped_ucs4(dest, chr); i += bytes; } else { str_append_data(dest, UNICODE_REPLACEMENT_CHAR_UTF8, UTF8_REPLACEMENT_CHAR_LEN); i++; } } } dovecot-2.3.21.1/src/lib/lib.h0000644000000000000000000000722314656633576012575 00000000000000#ifndef LIB_H #define LIB_H /* default lib includes */ #ifdef HAVE_CONFIG_H # include "config.h" #endif /* default system includes - keep these at minimum.. */ #include /* Solaris defines NULL wrong unless this is used */ #include #include /* strcmp() etc. */ #ifdef HAVE_STRINGS_H # include /* strcasecmp() etc. */ #endif #include /* va_list is used everywhere */ #include /* INT_MAX, etc. */ #include /* error checking is good */ #include /* many other includes want this */ #include /* PRI* macros */ #ifdef HAVE_STDINT_H # include /* C99 int types, we mostly need uintmax_t */ #endif #include "compat.h" #include "macros.h" #include "failures.h" #include "malloc-overflow.h" #include "data-stack.h" #include "mempool.h" #include "imem.h" #include "byteorder.h" #include "fd-util.h" typedef struct buffer buffer_t; typedef struct buffer string_t; struct istream; struct ostream; typedef void lib_atexit_callback_t(void); #include "array-decl.h" /* ARRAY*()s may exist in any header */ #include "bits.h" #include "hash-decl.h" /* HASH_TABLE*()s may exist in any header */ #include "strfuncs.h" #include "strnum.h" #include "event-log.h" #define LIB_ATEXIT_PRIORITY_HIGH -10 #define LIB_ATEXIT_PRIORITY_DEFAULT 0 #define LIB_ATEXIT_PRIORITY_LOW 10 /* /dev/null opened as O_WRONLY. Opened at lib_init(), so it can be accessed also inside chroots. */ extern int dev_null_fd; /* Call unlink(). If it fails, log an error including the source filename and line number. */ int i_unlink(const char *path, const char *source_fname, unsigned int source_linenum); #define i_unlink(path) i_unlink(path, __FILE__, __LINE__) /* Same as i_unlink(), but don't log an error if errno=ENOENT. Returns 1 on unlink() success, 0 if errno=ENOENT, -1 on other errors. */ int i_unlink_if_exists(const char *path, const char *source_fname, unsigned int source_linenum); #define i_unlink_if_exists(path) i_unlink_if_exists(path, __FILE__, __LINE__) /* Reset getopt() so it can be used for the next args. */ void i_getopt_reset(void); /* Call the given callback at the beginning of lib_deinit(). The main difference to atexit() is that liblib's memory allocation and logging functions are still available. Also if lib_atexit() is called multiple times to the same callback, it's added only once. */ void lib_atexit(lib_atexit_callback_t *callback); /* Specify the order in which the callback is called. Lowest numbered priorities are called first. lib_atexit() is called with priority=0. */ void lib_atexit_priority(lib_atexit_callback_t *callback, int priority); /* Manually run the atexit callbacks. lib_deinit() also does this if not explicitly called. */ void lib_atexit_run(void); /* Unless this or lib_deinit() is called, any unexpected exit() will result in abort(). This can be helpful in catching unexpected exits. */ void lib_set_clean_exit(bool set); /* Same as lib_set_clean_exit(TRUE) followed by exit(status). */ void lib_exit(int status) ATTR_NORETURN; void lib_init(void); bool lib_is_initialized(void); void lib_deinit(void); uint32_t i_rand(void); /* Returns a random integer < upper_bound. */ uint32_t i_rand_limit(uint32_t upper_bound); static inline unsigned short i_rand_ushort(void) { return i_rand_limit(USHRT_MAX + 1); } static inline unsigned char i_rand_uchar(void) { return i_rand_limit(UCHAR_MAX + 1); } /* Returns a random integer >= min_val, and <= max_val. */ static inline uint32_t i_rand_minmax(uint32_t min_val, uint32_t max_val) { i_assert(min_val <= max_val); return min_val + i_rand_limit(max_val - min_val + 1); } #endif dovecot-2.3.21.1/src/lib/istream-crlf.h0000644000000000000000000000035714656633576014420 00000000000000#ifndef ISTREAM_CRLF_H #define ISTREAM_CRLF_H /* Read all linefeeds as CRLF */ struct istream *i_stream_create_crlf(struct istream *input); /* Read all linefeeds as LF */ struct istream *i_stream_create_lf(struct istream *input); #endif dovecot-2.3.21.1/src/lib/test-istream-concat.c0000644000000000000000000001604514656633576015712 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "istream-private.h" #include "istream-concat.h" #include #include #define TEST_MAX_ISTREAM_COUNT 10 #define TEST_MAX_ISTREAM_SIZE 1024 #define TEST_MAX_BUFFER_SIZE 128 static void test_istream_concat_one(unsigned int buffer_size) { static const char *input_string = "xyz"; #define STREAM_COUNT 5 #define STREAM_BYTES 3 struct istream *streams[STREAM_COUNT+1]; struct istream *input; const unsigned char *data; size_t size; unsigned int i, j; for (i = 0; i < STREAM_COUNT; i++) { streams[i] = test_istream_create(input_string); test_istream_set_allow_eof(streams[i], TRUE); test_istream_set_size(streams[i], 0); } streams[i] = NULL; input = i_stream_create_concat(streams); for (i = 0; i/STREAM_BYTES < STREAM_COUNT; i++) { test_istream_set_size(streams[i/STREAM_BYTES], (i%STREAM_BYTES) + 1); test_assert(i_stream_read(input) == 1); if (i < buffer_size) { data = i_stream_get_data(input, &size); test_assert(size == i+1); } else { i_stream_skip(input, 1); data = i_stream_get_data(input, &size); test_assert(size == buffer_size); } for (j = 0; j < size; j++) { test_assert((char)data[j] == input_string[(input->v_offset + j) % STREAM_BYTES]); } test_assert(i_stream_read(input) <= 0); } test_assert(i_stream_read(input) == -1); i_stream_skip(input, i_stream_get_data_size(input)); i_stream_unref(&input); for (i = 0; i < STREAM_COUNT; i++) { test_assert(streams[i]->eof && streams[i]->stream_errno == 0); i_stream_unref(&streams[i]); } } static bool test_istream_concat_random(void) { struct istream **streams, *concat, **limits = NULL; const unsigned char *data; unsigned char *w_data; size_t size = 0; unsigned int i, j, offset, stream_count, data_len, simult; stream_count = i_rand_minmax(2, TEST_MAX_ISTREAM_COUNT + 2 - 1); streams = t_new(struct istream *, stream_count + 1); for (i = 0, offset = 0; i < stream_count; i++) { data_len = i_rand_minmax(1, TEST_MAX_ISTREAM_SIZE); w_data = t_malloc_no0(data_len); for (j = 0; j < data_len; j++) w_data[j] = (offset++) & 0xff; streams[i] = test_istream_create_data(w_data, data_len); test_istream_set_allow_eof(streams[i], TRUE); } streams[i] = NULL; i_assert(offset > 0); concat = i_stream_create_concat(streams); i_stream_set_max_buffer_size(concat, TEST_MAX_BUFFER_SIZE); simult = i_rand_limit(TEST_MAX_ISTREAM_COUNT); if (simult > 0) { limits = t_new(struct istream *, simult); for (i = 0; i < simult; i++) limits[i] = i_stream_create_limit(concat, UOFF_T_MAX); } for (i = 0; i < 1000; i++) { struct istream *input = (simult == 0) ? concat : limits[i_rand_limit(simult)]; if (i_rand_limit(3) == 0) { i_stream_seek(input, i_rand_limit(offset)); } else { ssize_t ret = i_stream_read(input); size = i_stream_get_data_size(input); if (ret == -2) { test_assert(size >= TEST_MAX_BUFFER_SIZE); } else if (input->v_offset + size != offset) { test_assert(ret > 0); test_assert(input->v_offset + ret <= offset); i_stream_skip(input, i_rand_limit(ret)); data = i_stream_get_data(input, &size); for (j = 0; j < size; j++) { test_assert(data[j] == (input->v_offset + j) % 256); } } } if (test_has_failed()) break; } for (i = 0; i < stream_count; i++) i_stream_unref(&streams[i]); for (i = 0; i < simult; i++) i_stream_unref(&limits[i]); i_stream_unref(&concat); return !test_has_failed(); } static void test_istream_concat_seek_end(void) { test_begin("istream concat seek end"); struct istream *streams[] = { test_istream_create("s1"), test_istream_create("s2"), NULL }; struct istream *input = i_stream_create_concat(streams); i_stream_unref(&streams[0]); i_stream_unref(&streams[1]); i_stream_seek(input, 4); test_assert(i_stream_read(input) == -1); i_stream_unref(&input); test_end(); } static void test_istream_concat_early_end(void) { struct istream *input, *streams[2]; test_begin("istream concat early end"); streams[0] = test_istream_create("stream"); test_istream_set_size(streams[0], 3); test_istream_set_allow_eof(streams[0], FALSE); streams[1] = NULL; input = i_stream_create_concat(streams); test_assert(i_stream_read(input) == 3); test_istream_set_size(streams[0], 5); test_assert(i_stream_read(input) == 2); i_stream_skip(input, 5); i_stream_unref(&input); test_assert(streams[0]->v_offset == 5); i_stream_unref(&streams[0]); test_end(); } static void test_istream_concat_snapshot(void) { struct istream *input; const unsigned char *data; size_t size; test_begin("istream concat snapshot"); struct istream *test_istreams[] = { test_istream_create("abcdefghijklmnopqrst"), test_istream_create("ABCDEFGHIJKLMNOPQRSTUVWXY"), test_istream_create("!\"#$%&'()*+,-./01234567890:;<="), NULL }; input = i_stream_create_concat(test_istreams); for (unsigned int i = 0; test_istreams[i] != NULL; i++) { struct istream *tmp_istream = test_istreams[i]; i_stream_unref(&tmp_istream); } test_istream_set_size(test_istreams[0], 20); test_istream_set_size(test_istreams[1], 0); test_istream_set_size(test_istreams[2], 0); /* first stream */ test_istream_set_allow_eof(test_istreams[0], FALSE); test_assert(i_stream_read_data(input, &data, &size, 0) == 1); test_assert(size == 20); test_assert(memcmp(data, "abcdefghijklmnopqrst", 20) == 0); /* partially skip */ i_stream_skip(input, 12); /* second stream */ test_assert(i_stream_read_data(input, &data, &size, 10) == 0); test_assert(size == 8); test_istream_set_allow_eof(test_istreams[0], TRUE); test_istream_set_size(test_istreams[0], 0); test_assert(i_stream_read_data(input, &data, &size, 10) == 0); test_assert(size == 8); test_istream_set_size(test_istreams[1], 10); test_assert(i_stream_read_data(input, &data, &size, 10) == 1); test_assert(size == 18); test_istream_set_allow_eof(test_istreams[1], FALSE); test_assert(i_stream_read(input) == 0); test_istream_set_size(test_istreams[1], 25); test_istream_set_allow_eof(test_istreams[1], TRUE); test_assert(i_stream_read_data(input, &data, &size, 30) == 1); test_assert(size == 33); test_assert(memcmp(data, "mnopqrst" "ABCDEFGHIJKLMNOPQRSTUVWXY", 33) == 0); /* partially skip */ i_stream_skip(input, 12); /* third stream */ test_istream_set_size(test_istreams[2], 0); test_assert(i_stream_read(input) == 0); test_istream_set_size(test_istreams[2], 30); test_assert(i_stream_read_data(input, &data, &size, 25) == 1); test_assert(size == 51); test_assert(memcmp(data, "EFGHIJKLMNOPQRSTUVWXY" "!\"#$%&'()*+,-./01234567890:;<=", 51) == 0); i_stream_unref(&input); test_end(); } void test_istream_concat(void) { unsigned int i; test_begin("istream concat"); for (i = 1; i < STREAM_BYTES*STREAM_COUNT; i++) { test_istream_concat_one(i); } test_end(); test_begin("istream concat random"); for (i = 0; i < 100; i++) T_BEGIN { if(!test_istream_concat_random()) i = 101; /* don't break a T_BEGIN */ } T_END; test_end(); test_istream_concat_seek_end(); test_istream_concat_early_end(); test_istream_concat_snapshot(); } dovecot-2.3.21.1/src/lib/test-printf-format-fix.c0000644000000000000000000001006614656633576016352 00000000000000/* Copyright (c) 2001-2018 Dovecot authors, see the included COPYING file */ /* Unit tests for printf-format-fix helper */ #include "test-lib.h" #include "printf-format-fix.h" #include struct format_fix_rewrites { const char *input; const char *output; size_t length; }; static void test_unchanged() { static const char *tests[] = { "Hello world", "Embedded %%, %u, %f, %s, etc. are OK", "Allow %#0- +s flags", "duplicate flags in different args %0-123s %0-123s", "Minimum length %9999s", "Minimum length parameter %*s", "Precision %.9999s", "Precision %1.9999s", "Precision parameter %1.*s %.*s", "Floating precisions such as %.0f %0.4f %-4.0f", "Length modifiers %hd %hhd %ld %lld %Lg %jd %zd %td", "Specifiers %s %u %d %c %i %x %X %p %o %e %E %f %F %g %G %a %A", "%%doesn't cause confusion in %%m and %%n", }; unsigned int i; test_begin("printf_format_fix(safe)"); for (i = 0; i < N_ELEMENTS(tests); ++i) { size_t len; T_BEGIN { test_assert_idx(printf_format_fix(tests[i]) == tests[i], i); test_assert_idx(printf_format_fix_get_len(tests[i], &len) == tests[i], i); test_assert_idx(len == strlen(tests[i]), i); } T_END; } test_end(); } static void test_ok_changes() { static const char *tests[] = { "OK to have a trailing %m", "%m can appear at the start too", "Even %m in the middle with a confusing %%m elsewhere is OK", }; unsigned int i; const char *needle; unsigned int needlen; int old_errno = errno; test_begin("printf_format_fix(rewrites)"); errno = EINVAL; needle = strerror(errno); i_assert(needle != NULL); needlen = strlen(needle); for (i = 0; i < N_ELEMENTS(tests); ++i) { size_t len; char const *chgd; char const *insert; unsigned int offs; T_BEGIN { chgd = printf_format_fix(tests[i]); test_assert_idx(chgd != tests[i], i); insert = strstr(chgd, needle); test_assert_idx(insert != NULL, i); offs = insert - chgd; test_assert_idx(memcmp(chgd, tests[i], offs) == 0, i); test_assert_idx(memcmp(chgd+offs, needle, needlen) == 0, i); test_assert_idx(strcmp(chgd+offs+needlen, tests[i]+offs+2) == 0, i); chgd = printf_format_fix_get_len(tests[i], &len); test_assert_idx(chgd != tests[i], i); test_assert_idx(len == strlen(chgd), i); insert = strstr(chgd, needle); test_assert_idx(insert != NULL, i); offs = insert - chgd; test_assert_idx(memcmp(chgd, tests[i], offs) == 0, i); test_assert_idx(memcmp(chgd+offs, needle, needlen) == 0, i); test_assert_idx(memcmp(chgd+offs+needlen, tests[i]+offs+2, len-needlen-offs) == 0, i); } T_END; } errno = old_errno; test_end(); } void test_printf_format_fix() { test_unchanged(); test_ok_changes(); } /* Want to test the panics too? go for it! */ enum fatal_test_state fatal_printf_format_fix(unsigned int stage) { static const struct { const char *format; const char *expected_fatal; } fatals[] = { { "no no no %n's", "%n modifier used" }, { "no no no %-1234567890123n's with extra stuff", "Too large minimum field width" }, { "%m allowed once, but not twice: %m", "%m used twice" }, { "%m must not obscure a later %n", "%n modifier used" }, { "definitely can't have a tailing %", "Missing % specifier" }, { "Evil %**%n", "Unsupported 0x2a specifier" }, { "Evil %*#%99999$s", "Unsupported 0x23 specifier" }, { "No weird %% with %0%", "Unsupported 0x25 specifier" }, { "No duplicate modifiers %00s", "Duplicate % flag '0'" }, { "Minimum length can't be too long %10000s", "Too large minimum field width" }, { "Minimum length doesn't support %*1$s", "Unsupported 0x31 specifier" }, { "Precision can't be too long %.10000s", "Too large precision" }, { "Precision can't be too long %1.10000s", "Too large precision" }, { "Precision doesn't support %1.-1s", "Unsupported 0x2d specifier" }, }; if(stage >= N_ELEMENTS(fatals)) { test_end(); return FATAL_TEST_FINISHED; } if(stage == 0) test_begin("fatal_printf_format_fix"); /* let's crash! */ test_expect_fatal_string(fatals[stage].expected_fatal); (void)printf_format_fix(fatals[stage].format); return FATAL_TEST_FAILURE; } dovecot-2.3.21.1/src/lib/test-utc-mktime.c0000644000000000000000000000325014656633576015052 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "utc-mktime.h" struct test_utc_mktime { int year, month, day, hour, min, sec; time_t out; }; void test_utc_mktime(void) { static const struct test_utc_mktime tests[] = { #ifdef TIME_T_SIGNED { 1969, 12, 31, 23, 59, 59, -1 }, { 1901, 12, 13, 20, 45, 53, -2147483647 }, #endif #if (TIME_T_MAX_BITS > 32 || !defined(TIME_T_SIGNED)) { 2106, 2, 7, 6, 28, 15, 4294967295 }, { 2038, 1, 19, 3, 14, 8, 2147483648 }, #endif { 2007, 11, 7, 1, 7, 20, 1194397640 }, { 1970, 1, 1, 0, 0, 0, 0 }, { 2038, 1, 19, 3, 14, 7, 2147483647 }, { INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, -1 }, #if TIME_T_MAX_BITS > 32 { 2106, 2, 7, 6, 28, 16, 4294967296 }, #endif /* June leap second */ { 2015, 6, 30, 23, 59, 59, 1435708799 }, { 2015, 6, 30, 23, 59, 60, 1435708799 }, { 2015, 7, 1, 0, 0, 0, 1435708800 }, /* Invalid leap second */ { 2017, 1, 24, 16, 40, 60, 1485276059 }, /* Dec leap second */ { 2016, 12, 31, 23, 59, 59, 1483228799 }, { 2016, 12, 31, 23, 59, 60, 1483228799 }, { 2017, 1, 1, 0, 0, 0, 1483228800 }, }; struct tm tm; unsigned int i; time_t t; bool success; for (i = 0; i < N_ELEMENTS(tests); i++) { const struct test_utc_mktime *test = &tests[i]; i_zero(&tm); tm.tm_year = test->year - 1900; tm.tm_mon = test->month - 1; tm.tm_mday = test->day; tm.tm_hour = test->hour; tm.tm_min = test->min; tm.tm_sec = test->sec; t = utc_mktime(&tm); success = t == test->out; test_out_reason(t_strdup_printf("utc_mktime(%d)", i), success, success ? NULL : t_strdup_printf("%ld != %ld", (long)t, (long)test->out)); } } dovecot-2.3.21.1/src/lib/randgen.h0000644000000000000000000000063514656633576013445 00000000000000#ifndef RANDGEN_H #define RANDGEN_H /* Fill given buffer with semi-strong randomness */ void random_fill(void *buf, size_t size); /* may be called multiple times, and are called by default in lib_init */ void random_init(void); void random_deinit(void); #ifdef DEBUG /* Debug helper to make random tests reproduceable. 0=got seed, -1=failure. */ int rand_get_last_seed(unsigned int *seed_r); #endif #endif dovecot-2.3.21.1/src/lib/test-env-util.c0000644000000000000000000000507114656633576014541 00000000000000/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "env-util.h" void test_env_util(void) { test_begin("env util"); env_put("ENVUTIL_BACKUP", "saved"); struct env_backup *backup = env_backup_save(); /* test env_clean() */ env_clean(); char ***env = env_get_environ_p(); test_assert(*env == NULL || **env == NULL); test_assert(getenv("ENVUTIL_BACKUP") == NULL); /* test env_put_array() */ const char *add_env[] = { "a=1", "b=1", "c=1", "d=1", NULL }; env_put_array(add_env); test_assert_strcmp(getenv("a"), "1"); test_assert_strcmp(getenv("b"), "1"); test_assert_strcmp(getenv("c"), "1"); test_assert_strcmp(getenv("d"), "1"); test_assert(getenv("e") == NULL); const char *add_env2[] = { "b=", "e=2", NULL }; env_put_array(add_env2); test_assert_strcmp(getenv("a"), "1"); test_assert_strcmp(getenv("b"), ""); test_assert_strcmp(getenv("c"), "1"); test_assert_strcmp(getenv("d"), "1"); test_assert_strcmp(getenv("e"), "2"); /* test env_clean_except() */ const char *preserve_env[] = { "a", "c", NULL }; env_clean_except(preserve_env); test_assert_strcmp(getenv("a"), "1"); test_assert(getenv("b") == NULL); test_assert_strcmp(getenv("c"), "1"); test_assert(getenv("d") == NULL); test_assert(*env != NULL && (null_strcmp((*env)[0], "a=1") == 0 || null_strcmp((*env)[0], "c=1") == 0)); test_assert(*env != NULL && (null_strcmp((*env)[1], "a=1") == 0 || null_strcmp((*env)[1], "c=1") == 0)); /* test env_remove() */ env_remove("a"); test_assert(getenv("a") == NULL); test_assert(getenv("c") != NULL); env_remove("a"); test_assert(getenv("a") == NULL); test_assert(getenv("c") != NULL); env_remove("c"); test_assert(getenv("c") == NULL); test_assert(*env == NULL || **env == NULL); /* test restoring */ env_backup_restore(backup); test_assert_strcmp(getenv("ENVUTIL_BACKUP"), "saved"); env_put("ENVUTIL_BACKUP", "overwrite"); test_assert_strcmp(getenv("ENVUTIL_BACKUP"), "overwrite"); /* test restoring again */ env_backup_restore(backup); test_assert_strcmp(getenv("ENVUTIL_BACKUP"), "saved"); env_backup_free(&backup); test_end(); } enum fatal_test_state fatal_env_util(unsigned int stage) { switch (stage) { case 0: test_begin("env util fatals"); test_expect_fatal_string("strchr(name, '=') == NULL"); env_put("key=bad", "value"); return FATAL_TEST_FAILURE; case 1: test_expect_fatal_string("value != NULL"); const char *const envs[] = { "key", NULL }; env_put_array(envs); return FATAL_TEST_FAILURE; default: test_end(); return FATAL_TEST_FINISHED; } } dovecot-2.3.21.1/src/lib/process-title.c0000644000000000000000000001147714656633576014625 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "env-util.h" #include "process-title.h" #ifdef HAVE_LIBBSD #include #else #include /* FreeBSD */ #endif static char *process_name = NULL; static char *current_process_title; static unsigned int process_title_counter = 0; #ifdef HAVE_SETPROCTITLE # undef PROCTITLE_HACK #endif #ifdef PROCTITLE_HACK #ifdef DEBUG /* if there are problems with this approach, try to make sure we notice it */ # define PROCTITLE_CLEAR_CHAR 0xab #else /* There are always race conditions when updating the process title. ps might read a partially written title. Try to at least minimize this by using NUL as the fill character, so ps won't show a large number of 0xab chars. */ # define PROCTITLE_CLEAR_CHAR 0 #endif static char *process_title; static size_t process_title_len, process_title_clean_pos; static void *argv_memblock, *environ_memblock; static void proctitle_hack_init(char *argv[], char *env[]) { char *last; unsigned int i; bool clear_env; i_assert(argv[0] != NULL); /* find the last argv or environment string. it should always be the last string in environ, but don't rely on it. this is what openssh does, so hopefully it's safe enough. */ last = argv[0] + strlen(argv[0]) + 1; for (i = 1; argv[i] != NULL; i++) { if (argv[i] == last) last = argv[i] + strlen(argv[i]) + 1; } if (env[0] == NULL) clear_env = FALSE; else { clear_env = last == env[0]; for (i = 0; env[i] != NULL; i++) { if (env[i] == last) last = env[i] + strlen(env[i]) + 1; } } process_title = argv[0]; process_title_len = last - argv[0]; if (clear_env) { memset(env[0], PROCTITLE_CLEAR_CHAR, last - env[0]); process_title_clean_pos = env[0] - process_title; } else { process_title_clean_pos = 0; } } static char **argv_dup(char *old_argv[], void **memblock_r) { /* @UNSAFE */ void *memblock, *memblock_end; char **new_argv; unsigned int i, count; size_t len, memblock_len = 0; for (count = 0; old_argv[count] != NULL; count++) memblock_len += strlen(old_argv[count]) + 1; memblock_len += sizeof(char *) * (count + 1); memblock = malloc(memblock_len); if (memblock == NULL) i_fatal_status(FATAL_OUTOFMEM, "malloc() failed: %m"); *memblock_r = memblock; memblock_end = PTR_OFFSET(memblock, memblock_len); new_argv = memblock; memblock = PTR_OFFSET(memblock, sizeof(char *) * (count + 1)); for (i = 0; i < count; i++) { new_argv[i] = memblock; len = strlen(old_argv[i]) + 1; memcpy(memblock, old_argv[i], len); memblock = PTR_OFFSET(memblock, len); } i_assert(memblock == memblock_end); new_argv[i] = NULL; return new_argv; } static void proctitle_hack_set(const char *title) { size_t len = strlen(title); /* OS X wants two NULs */ if (len >= process_title_len-1) len = process_title_len - 2; memcpy(process_title, title, len); process_title[len++] = '\0'; process_title[len++] = '\0'; if (len < process_title_clean_pos) { memset(process_title + len, PROCTITLE_CLEAR_CHAR, process_title_clean_pos - len); process_title_clean_pos = len; } else if (process_title_clean_pos != 0) { process_title_clean_pos = len; } } #endif void process_title_init(int argc ATTR_UNUSED, char **argv[]) { #ifdef PROCTITLE_HACK char ***environ_p = env_get_environ_p(); char **orig_argv = *argv; char **orig_environ = *environ_p; *argv = argv_dup(orig_argv, &argv_memblock); *environ_p = argv_dup(orig_environ, &environ_memblock); proctitle_hack_init(orig_argv, orig_environ); #endif #ifdef HAVE_LIBBSD setproctitle_init(argc, *argv, *env_get_environ_p()); #endif process_name = (*argv)[0]; } void process_title_set(const char *title) { i_assert(process_name != NULL); process_title_counter++; i_free(current_process_title); current_process_title = i_strdup(title); #ifdef HAVE_SETPROCTITLE if (title == NULL) setproctitle(NULL); else setproctitle("%s", title); #elif defined(PROCTITLE_HACK) T_BEGIN { proctitle_hack_set(t_strconcat(process_name, " ", title, NULL)); } T_END; #endif } const char *process_title_get(void) { return current_process_title; } unsigned int process_title_get_counter(void) { return process_title_counter; } void process_title_deinit(void) { #ifdef PROCTITLE_HACK char ***environ_p = env_get_environ_p(); free(argv_memblock); free(environ_memblock); /* Environment is no longer usable. Make sure we won't crash in case some library's deinit function still calls getenv(). This code was mainly added because of GNUTLS where we don't really care about the getenv() call. Alternatively we could remove the free() calls above, but that would annoy memory leak checking tools. Also we could attempt to restore the environ_p to its original state, but that's a bit complicated. */ *environ_p = NULL; #endif i_free(current_process_title); } dovecot-2.3.21.1/src/lib/istream-concat.c0000644000000000000000000002757514656633576014747 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "memarea.h" #include "istream-private.h" #include "istream-concat.h" struct concat_istream { struct istream_private istream; struct istream **input, *cur_input; uoff_t *input_size; unsigned int input_count; unsigned int cur_idx, unknown_size_idx; size_t prev_stream_left, prev_stream_skip, prev_skip; }; static void i_stream_concat_skip(struct concat_istream *cstream); static void i_stream_concat_close(struct iostream_private *stream, bool close_parent) { struct concat_istream *cstream = container_of(stream, struct concat_istream, istream.iostream); i_assert(cstream->cur_input == cstream->input[cstream->cur_idx]); unsigned int i; if (cstream->istream.istream.stream_errno == 0) { /* get the parent streams to the wanted offset */ (void)i_stream_concat_skip(cstream); } if (close_parent) { for (i = 0; i < cstream->input_count; i++) i_stream_close(cstream->input[i]); } } static void i_stream_concat_destroy(struct iostream_private *stream) { struct concat_istream *cstream = container_of(stream, struct concat_istream, istream.iostream); i_assert(cstream->cur_input == cstream->input[cstream->cur_idx]); unsigned int i; for (i = 0; i < cstream->input_count; i++) i_stream_unref(&cstream->input[i]); i_free(cstream->input); i_free(cstream->input_size); i_stream_free_buffer(&cstream->istream); } static void i_stream_concat_set_max_buffer_size(struct iostream_private *stream, size_t max_size) { struct concat_istream *cstream = container_of(stream, struct concat_istream, istream.iostream); i_assert(cstream->cur_input == cstream->input[cstream->cur_idx]); unsigned int i; cstream->istream.max_buffer_size = max_size; for (i = 0; i < cstream->input_count; i++) i_stream_set_max_buffer_size(cstream->input[i], max_size); } static void i_stream_concat_read_next(struct concat_istream *cstream) { struct istream *prev_input = cstream->cur_input; const unsigned char *data; size_t data_size, size; i_assert(cstream->cur_input->eof); if (cstream->prev_stream_skip != 0) { i_stream_skip(cstream->input[cstream->cur_idx-1], cstream->prev_stream_skip); cstream->prev_stream_skip = 0; } data = i_stream_get_data(cstream->cur_input, &data_size); cstream->cur_idx++; cstream->cur_input = cstream->input[cstream->cur_idx]; i_stream_seek(cstream->cur_input, 0); if (cstream->prev_stream_left > 0 || cstream->istream.pos == 0) { /* all the pending data is already in w_buffer */ cstream->prev_stream_skip = data_size; cstream->prev_stream_left += data_size; i_assert(cstream->prev_stream_left == cstream->istream.pos - cstream->istream.skip); return; } i_assert(cstream->prev_stream_skip == 0); /* we already verified that the data size is less than the maximum buffer size */ cstream->istream.skip = 0; cstream->istream.pos = 0; if (data_size > 0) { if (cstream->istream.memarea != NULL && memarea_get_refcount(cstream->istream.memarea) > 1) i_stream_memarea_detach(&cstream->istream); if (!i_stream_try_alloc(&cstream->istream, data_size, &size)) i_unreached(); i_assert(size >= data_size); } cstream->prev_stream_left = data_size; memcpy(cstream->istream.w_buffer, data, data_size); i_stream_skip(prev_input, data_size); cstream->istream.skip = 0; cstream->istream.pos = data_size; } static void i_stream_concat_skip(struct concat_istream *cstream) { struct istream_private *stream = &cstream->istream; size_t bytes_skipped; i_assert(stream->skip >= cstream->prev_skip); bytes_skipped = stream->skip - cstream->prev_skip; if (cstream->prev_stream_left == 0) { /* no need to worry about buffers, skip everything */ } else if (bytes_skipped < cstream->prev_stream_left) { /* we're still skipping inside buffer */ cstream->prev_stream_left -= bytes_skipped; bytes_skipped = 0; } else { /* done with the buffer */ i_stream_skip(cstream->input[cstream->cur_idx-1], cstream->prev_stream_skip); cstream->prev_stream_skip = 0; bytes_skipped -= cstream->prev_stream_left; cstream->prev_stream_left = 0; } if (bytes_skipped > 0) { i_assert(stream->buffer != NULL); stream->pos -= bytes_skipped; stream->skip -= bytes_skipped; stream->buffer += bytes_skipped; } cstream->prev_skip = stream->skip; i_stream_skip(cstream->cur_input, bytes_skipped); } static ssize_t i_stream_concat_read(struct istream_private *stream) { struct concat_istream *cstream = container_of(stream, struct concat_istream, istream); i_assert(cstream->cur_input == cstream->input[cstream->cur_idx]); const unsigned char *data; size_t size, data_size, cur_data_pos, new_pos; size_t new_bytes_count; ssize_t ret; bool last_stream; i_assert(cstream->cur_input != NULL); i_stream_concat_skip(cstream); i_assert(stream->pos >= stream->skip + cstream->prev_stream_left); cur_data_pos = stream->pos - (stream->skip + cstream->prev_stream_left); data = i_stream_get_data(cstream->cur_input, &data_size); if (data_size > cur_data_pos) ret = 0; else { /* need to read more - NOTE: Can't use i_stream_read_memarea() here, because our stream->buffer may point to the parent istream. Implementing explicit snapshot function to avoid this isn't easy for seekable concat-istreams, because due to seeking it's not necessarily the cur_input that needs to be snapshotted. */ i_assert(cur_data_pos == data_size); ret = i_stream_read(cstream->cur_input); if (ret == -2 || ret == 0) return ret; if (ret == -1 && cstream->cur_input->stream_errno != 0) { io_stream_set_error(&cstream->istream.iostream, "read(%s) failed: %s", i_stream_get_name(cstream->cur_input), i_stream_get_error(cstream->cur_input)); stream->istream.stream_errno = cstream->cur_input->stream_errno; return -1; } /* we either read something or we're at EOF */ last_stream = cstream->cur_idx+1 >= cstream->input_count; if (ret == -1 && !last_stream) { if (stream->pos - stream->skip >= i_stream_get_max_buffer_size(&stream->istream)) return -2; i_stream_concat_read_next(cstream); cstream->prev_skip = stream->skip; return i_stream_concat_read(stream); } stream->istream.eof = cstream->cur_input->eof && last_stream; i_assert(ret != -1 || stream->istream.eof); data = i_stream_get_data(cstream->cur_input, &data_size); } if (data_size == cur_data_pos) { /* nothing new read - preserve the buffer as it was */ i_assert(ret == 0 || ret == -1); return ret; } if (cstream->prev_stream_left == 0) { /* we can point directly to the current stream's buffers */ stream->buffer = data; stream->pos -= stream->skip; stream->skip = 0; new_pos = data_size; } else { /* we still have some of the previous stream left. merge the new data with it. */ i_assert(data_size > cur_data_pos); new_bytes_count = data_size - cur_data_pos; if (!i_stream_try_alloc(stream, new_bytes_count, &size)) { stream->buffer = stream->w_buffer; return -2; } stream->buffer = stream->w_buffer; /* we'll copy all the new input to w_buffer. if we skip over prev_stream_left bytes, the next read will switch to pointing to cur_input's data directly. */ if (new_bytes_count > size) new_bytes_count = size; memcpy(stream->w_buffer + stream->pos, data + cur_data_pos, new_bytes_count); new_pos = stream->pos + new_bytes_count; } i_assert(new_pos > stream->pos); ret = (ssize_t)(new_pos - stream->pos); stream->pos = new_pos; cstream->prev_skip = stream->skip; return ret; } static int find_v_offset(struct concat_istream *cstream, uoff_t *v_offset, unsigned int *idx_r) { const struct stat *st; unsigned int i; for (i = 0; i < cstream->input_count; i++) { if (*v_offset == 0) { /* seek to beginning of this stream */ break; } if (i == cstream->unknown_size_idx) { /* we'll need to figure out this stream's size */ if (i_stream_stat(cstream->input[i], TRUE, &st) < 0) { io_stream_set_error(&cstream->istream.iostream, "stat(%s) failed: %s", i_stream_get_name(cstream->input[i]), i_stream_get_error(cstream->input[i])); i_error("istream-concat: stat(%s) failed: %s", i_stream_get_name(cstream->input[i]), i_stream_get_error(cstream->input[i])); cstream->istream.istream.stream_errno = cstream->input[i]->stream_errno; return -1; } /* @UNSAFE */ cstream->input_size[i] = st->st_size; cstream->unknown_size_idx = i + 1; } if (*v_offset < cstream->input_size[i]) break; *v_offset -= cstream->input_size[i]; } *idx_r = i; return 0; } static void i_stream_concat_seek(struct istream_private *stream, uoff_t v_offset, bool mark ATTR_UNUSED) { struct concat_istream *cstream = container_of(stream, struct concat_istream, istream); i_assert(cstream->cur_input == cstream->input[cstream->cur_idx]); stream->istream.v_offset = v_offset; stream->skip = stream->pos = 0; cstream->prev_stream_left = 0; cstream->prev_stream_skip = 0; cstream->prev_skip = 0; if (find_v_offset(cstream, &v_offset, &cstream->cur_idx) < 0) { /* failed */ stream->istream.stream_errno = EINVAL; return; } if (cstream->cur_idx < cstream->input_count) cstream->cur_input = cstream->input[cstream->cur_idx]; else { /* we allow seeking to EOF, but not past it. */ if (v_offset != 0) { io_stream_set_error(&cstream->istream.iostream, "Seeking past EOF by %"PRIuUOFF_T" bytes", v_offset); cstream->istream.istream.stream_errno = EINVAL; return; } i_assert(cstream->cur_idx > 0); /* Position ourselves at the EOF of the last actual stream. */ cstream->cur_idx--; cstream->cur_input = cstream->input[cstream->cur_idx]; v_offset = cstream->input_size[cstream->cur_idx]; } i_stream_seek(cstream->cur_input, v_offset); } static int i_stream_concat_stat(struct istream_private *stream, bool exact ATTR_UNUSED) { struct concat_istream *cstream = container_of(stream, struct concat_istream, istream); i_assert(cstream->cur_input == cstream->input[cstream->cur_idx]); uoff_t v_offset = UOFF_T_MAX; unsigned int i, cur_idx; /* make sure we have all sizes */ if (find_v_offset(cstream, &v_offset, &cur_idx) < 0) return -1; stream->statbuf.st_size = 0; for (i = 0; i < cstream->unknown_size_idx; i++) stream->statbuf.st_size += cstream->input_size[i]; return 0; } struct istream *i_stream_create_concat(struct istream *input[]) { struct concat_istream *cstream; unsigned int count; size_t max_buffer_size = 0; bool blocking = TRUE, seekable = TRUE; /* if any of the streams isn't blocking or seekable, set ourself also nonblocking/nonseekable */ for (count = 0; input[count] != NULL; count++) { size_t cur_max = i_stream_get_max_buffer_size(input[count]); i_assert(cur_max != 0); if (cur_max != SIZE_MAX && cur_max > max_buffer_size) max_buffer_size = cur_max; if (!input[count]->blocking) blocking = FALSE; if (!input[count]->seekable) seekable = FALSE; i_stream_ref(input[count]); } i_assert(count != 0); if (max_buffer_size == 0) max_buffer_size = SIZE_MAX; if (max_buffer_size < I_STREAM_MIN_SIZE) max_buffer_size = I_STREAM_MIN_SIZE; cstream = i_new(struct concat_istream, 1); cstream->input_count = count; cstream->input = p_memdup(default_pool, input, sizeof(*input) * count); cstream->input_size = i_new(uoff_t, count); cstream->cur_input = cstream->input[0]; i_stream_seek(cstream->cur_input, 0); cstream->istream.iostream.close = i_stream_concat_close; cstream->istream.iostream.destroy = i_stream_concat_destroy; cstream->istream.iostream.set_max_buffer_size = i_stream_concat_set_max_buffer_size; cstream->istream.max_buffer_size = max_buffer_size; cstream->istream.read = i_stream_concat_read; cstream->istream.seek = i_stream_concat_seek; cstream->istream.stat = i_stream_concat_stat; cstream->istream.istream.readable_fd = FALSE; cstream->istream.istream.blocking = blocking; cstream->istream.istream.seekable = seekable; return i_stream_create(&cstream->istream, NULL, -1, 0); } dovecot-2.3.21.1/src/lib/sha3.h0000644000000000000000000000600514656633576012662 00000000000000/* * FIPS 180-2 SHA-224/256/384/512 implementation * Last update: 02/02/2007 * Issue date: 04/30/2005 * * Copyright (C) 2005, 2007 Olivier Gay * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef SHA3_H #define SHA3_H #include "hash-method.h" #include "sha-common.h" #define SHA3_KECCAK_SPONGE_WORDS \ (((1600)/8/*bits to byte*/)/sizeof(uint64_t)) struct sha3_ctx { uint64_t saved; /* the portion of the input message that we * didn't consume yet */ union { /* Keccak's state */ uint64_t s[SHA3_KECCAK_SPONGE_WORDS]; uint8_t sb[SHA3_KECCAK_SPONGE_WORDS * 8]; }; unsigned byteIndex; /* 0..7--the next byte after the set one * (starts from 0; 0--none are buffered) */ unsigned wordIndex; /* 0..24--the next word to integrate input * (starts from 0) */ unsigned capacityWords; /* the double size of the hash output in * words (e.g. 16 for Keccak 512) */ }; void sha3_256_init(void *context); void sha3_256_result(void *context, unsigned char digest[STATIC_ARRAY SHA256_RESULTLEN]); void sha3_256_get_digest(const void *data, size_t size, unsigned char digest[STATIC_ARRAY SHA256_RESULTLEN]); void sha3_512_init(void *context); void sha3_512_result(void *context, unsigned char digest[STATIC_ARRAY SHA512_RESULTLEN]); void sha3_512_get_digest(const void *data, size_t size, unsigned char digest[STATIC_ARRAY SHA512_RESULTLEN]); void sha3_loop(void *context, const void *data, size_t len); extern const struct hash_method hash_method_sha3_256; extern const struct hash_method hash_method_sha3_512; #endif dovecot-2.3.21.1/src/lib/uri-util.c0000644000000000000000000007735614656633576013612 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "net.h" #include "uri-util.h" #include /* [URI-GEN] RFC3986 Appendix A: URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] absolute-URI = scheme ":" hier-part [ "?" query ] scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) URI-reference = URI / relative-ref relative-ref = relative-part [ "?" query ] [ "#" fragment ] relative-part = "//" authority path-abempty / path-absolute / path-noscheme / path-empty hier-part = "//" authority path-abempty / path-absolute / path-rootless / path-empty authority = [ userinfo "@" ] host [ ":" port ] userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) host = IP-literal / IPv4address / reg-name port = *DIGIT IP-literal = "[" ( IPv6address / IPvFuture ) "]" IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) IPv6address = 6( h16 ":" ) ls32 / "::" 5( h16 ":" ) ls32 / [ h16 ] "::" 4( h16 ":" ) ls32 / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 / [ *4( h16 ":" ) h16 ] "::" ls32 / [ *5( h16 ":" ) h16 ] "::" h16 / [ *6( h16 ":" ) h16 ] "::" h16 = 1*4HEXDIG ls32 = ( h16 ":" h16 ) / IPv4address IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet dec-octet = DIGIT ; 0-9 / %x31-39 DIGIT ; 10-99 / "1" 2DIGIT ; 100-199 / "2" %x30-34 DIGIT ; 200-249 / "25" %x30-35 ; 250-255 reg-name = *( unreserved / pct-encoded / sub-delims ) path = path-abempty ; begins with "/" or is empty / path-absolute ; begins with "/" but not "//" / path-noscheme ; begins with a non-colon segment / path-rootless ; begins with a segment / path-empty ; zero characters path-abempty = *( "/" segment ) path-absolute = "/" [ segment-nz *( "/" segment ) ] path-noscheme = segment-nz-nc *( "/" segment ) path-rootless = segment-nz *( "/" segment ) path-empty = 0 segment = *pchar segment-nz = 1*pchar segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) ; non-zero-length segment without any colon ":" pchar = unreserved / pct-encoded / sub-delims / ":" / "@" query = *( pchar / "/" / "?" ) fragment = *( pchar / "/" / "?" ) pct-encoded = "%" HEXDIG HEXDIG unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" reserved = gen-delims / sub-delims gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" */ #define URI_MAX_SCHEME_NAME_LEN 64 /* Character lookup table * * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" [bit0] * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" * / "*" / "+" / "," / ";" / "=" [bit1] * gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" [bit2] * pchar = unreserved / sub-delims / ":" / "@" [bit0|bit1|bit3] * 'pfchar' = unreserved / sub-delims / ":" / "@" / "/" * [bit0|bit1|bit3|bit5] * 'uchar' = unreserved / sub-delims / ":" [bit0|bit1|bit4] * 'qchar' = pchar / "/" / "?" [bit0|bit1|bit3|bit5|bit6] * */ #define CHAR_MASK_UNRESERVED (1<<0) #define CHAR_MASK_SUB_DELIMS (1<<1) #define CHAR_MASK_PCHAR ((1<<0)|(1<<1)|(1<<3)) #define CHAR_MASK_PFCHAR ((1<<0)|(1<<1)|(1<<3)|(1<<5)) #define CHAR_MASK_UCHAR ((1<<0)|(1<<1)|(1<<4)) #define CHAR_MASK_QCHAR ((1<<0)|(1<<1)|(1<<3)|(1<<5)|(1<<6)) #define CHAR_MASK_UNRESERVED_PATH ((1<<0)|(1<<5)) static unsigned const char _uri_char_lookup[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10 0, 2, 0, 4, 2, 0, 2, 2, 2, 2, 2, 2, 2, 1, 1, 36, // 20 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 2, 0, 2, 0, 68, // 30 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 40 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 0, 4, 0, 1, // 50 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 60 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, // 70 }; static inline int _decode_hex_digit(const unsigned char digit) { switch (digit) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return digit - '0'; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': return digit - 'a' + 0x0a; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': return digit - 'A' + 0x0A; } return -1; } static int uri_parse_pct_encoded_data(struct uri_parser *parser, const unsigned char **p, const unsigned char *pend, unsigned char *ch_r) ATTR_NULL(3) { int value; if (**p != '%' || (pend != NULL && *p >= pend)) return 0; *p += 1; if (**p == 0 || *(*p+1) == 0 || (pend != NULL && *p+1 >= pend)) { parser->error = "Unexpected URI boundary after '%'"; return -1; } if ((value = _decode_hex_digit(**p)) < 0) { parser->error = p_strdup_printf(parser->pool, "Expecting hex digit after '%%', but found '%c'", **p); return -1; } *ch_r = (value & 0x0f) << 4; *p += 1; if ((value = _decode_hex_digit(**p)) < 0) { parser->error = p_strdup_printf(parser->pool, "Expecting hex digit after '%%%c', but found '%c'", *((*p)-1), **p); return -1; } *ch_r |= (value & 0x0f); *p += 1; if (!parser->allow_pct_nul && *ch_r == '\0') { parser->error = "Percent encoding is not allowed to encode NUL character"; return -1; } return 1; } int uri_parse_pct_encoded(struct uri_parser *parser, unsigned char *ch_r) { return uri_parse_pct_encoded_data (parser, &parser->cur, parser->end, ch_r); } static int uri_parse_unreserved_char(struct uri_parser *parser, unsigned char *ch_r) { if ((*parser->cur & 0x80) != 0) return 0; if ((_uri_char_lookup[*parser->cur] & CHAR_MASK_UNRESERVED) != 0) { *ch_r = *parser->cur; parser->cur++; return 1; } return 0; } int uri_parse_unreserved(struct uri_parser *parser, string_t *part) { int len = 0; while (parser->cur < parser->end) { int ret; unsigned char ch = 0; if ((ret = uri_parse_unreserved_char(parser, &ch)) < 0) return -1; if (ret == 0) break; if (part != NULL) str_append_c(part, ch); len++; } return len > 0 ? 1 : 0; } int uri_parse_unreserved_pct(struct uri_parser *parser, string_t *part) { int len = 0; while (parser->cur < parser->end) { int ret; unsigned char ch = 0; if ((ret=uri_parse_pct_encoded(parser, &ch)) < 0) return -1; else if (ret == 0 && (ret=uri_parse_unreserved_char(parser, &ch)) < 0) return -1; if (ret == 0) break; if (part != NULL) str_append_c(part, ch); len++; } return len > 0 ? 1 : 0; } bool uri_data_decode(struct uri_parser *parser, const char *data, const char *until, const char **decoded_r) { const unsigned char *p = (const unsigned char *)data; const unsigned char *pend = (const unsigned char *)until; string_t *decoded; int ret; if (pend == NULL) { /* NULL means unlimited; solely rely on '\0' */ pend = (const unsigned char *)SIZE_MAX; } if (p >= pend || *p == '\0') { if (decoded_r != NULL) *decoded_r = ""; return TRUE; } decoded = uri_parser_get_tmpbuf(parser, 256); while (p < pend && *p != '\0') { unsigned char ch; if ((ret=uri_parse_pct_encoded_data (parser, &p, NULL, &ch)) != 0) { if (ret < 0) return FALSE; str_append_c(decoded, ch); } else { str_append_c(decoded, *p); p++; } } if (decoded_r != NULL) *decoded_r = p_strdup(parser->pool, str_c(decoded)); return TRUE; } int uri_parse_scheme(struct uri_parser *parser, const char **scheme_r) { const unsigned char *first = parser->cur; size_t len = 1; /* RFC 3968: * scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) */ if (parser->cur >= parser->end || !i_isalpha(*parser->cur)) return 0; parser->cur++; while (len < URI_MAX_SCHEME_NAME_LEN && parser->cur < parser->end) { if (!i_isalnum(*parser->cur) && *parser->cur != '+' && *parser->cur != '-' && *parser->cur != '.') break; parser->cur++; len++; } if (parser->cur >= parser->end || *parser->cur != ':') { parser->error = "Invalid URI scheme"; return -1; } if (scheme_r != NULL) *scheme_r = t_strndup(first, parser->cur - first); parser->cur++; return 1; } int uri_cut_scheme(const char **uri_p, const char **scheme_r) { struct uri_parser parser; uri_parser_init(&parser, NULL, *uri_p); if (uri_parse_scheme(&parser, scheme_r) <= 0) return -1; *uri_p = (const char *)parser.cur; return 0; } static int uri_parse_dec_octet(struct uri_parser *parser, string_t *literal, uint8_t *octet_r) ATTR_NULL(2) { unsigned int octet = 0; int count = 0; /* RFC 3986: * * dec-octet = DIGIT ; 0-9 * / %x31-39 DIGIT ; 10-99 * / "1" 2DIGIT ; 100-199 * / "2" %x30-34 DIGIT ; 200-249 * / "25" %x30-35 ; 250-255 */ while (parser->cur < parser->end && i_isdigit(*parser->cur)) { octet = octet * 10 + (parser->cur[0] - '0'); if (octet > 255) return -1; if (literal != NULL) str_append_c(literal, *parser->cur); parser->cur++; count++; } if (count > 0) { *octet_r = octet; return 1; } return 0; } static int uri_parse_ipv4address(struct uri_parser *parser, string_t *literal, struct in_addr *ip4_r) ATTR_NULL(2,3) { uint8_t octet; uint32_t ip = 0; int ret; int i; /* RFC 3986: * * IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet */ if ((ret = uri_parse_dec_octet(parser, literal, &octet)) <= 0) return ret; ip = octet; for (i = 0; i < 3 && parser->cur < parser->end; i++) { if (*parser->cur != '.') return -1; if (literal != NULL) str_append_c(literal, '.'); parser->cur++; if (uri_parse_dec_octet(parser, literal, &octet) <= 0) return -1; ip = (ip << 8) + octet; } if (ip4_r != NULL) ip4_r->s_addr = htonl(ip); return 1; } static int uri_do_parse_reg_name(struct uri_parser *parser, string_t *reg_name) ATTR_NULL(2) { /* RFC 3986: * * reg-name = *( unreserved / pct-encoded / sub-delims ) */ while (parser->cur < parser->end) { int ret; unsigned char c; /* unreserved / pct-encoded */ if ((ret=uri_parse_pct_encoded(parser, &c)) < 0) return -1; else if (ret == 0 && (ret=uri_parse_unreserved_char(parser, &c)) < 0) return -1; if (ret > 0) { if (reg_name != NULL) str_append_c(reg_name, c); continue; } /* sub-delims */ c = *parser->cur; if ((c & 0x80) == 0 && (_uri_char_lookup[c] & CHAR_MASK_SUB_DELIMS) != 0) { if (reg_name != NULL) str_append_c(reg_name, *parser->cur); parser->cur++; continue; } break; } return 0; } int uri_parse_reg_name(struct uri_parser *parser, const char **reg_name_r) { string_t *reg_name = NULL; int ret; if (reg_name_r != NULL) reg_name = uri_parser_get_tmpbuf(parser, 256); if ((ret=uri_do_parse_reg_name(parser, reg_name)) <= 0) return ret; if (reg_name_r != NULL) *reg_name_r = str_c(reg_name); return 1; } static int uri_do_parse_host_name(struct uri_parser *parser, string_t *host_name) ATTR_NULL(2) { const unsigned char *first, *part; int ret; /* RFC 3986, Section 3.2.2: A registered name intended for lookup in the DNS uses the syntax defined in Section 3.5 of [RFC1034] and Section 2.1 of [RFC1123]. Such a name consists of a sequence of domain labels separated by ".", each domain label starting and ending with an alphanumeric character and possibly also containing "-" characters. The rightmost domain label of a fully qualified domain name in DNS may be followed by a single "." and should be if it is necessary to distinguish between the complete domain name and some local domain. RFC 2396, Section 3.2.2 (old URI specification): hostname = *( domainlabel "." ) toplabel [ "." ] domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum toplabel = alpha | alpha *( alphanum | "-" ) alphanum The description in RFC 3986 is more liberal, so: hostname = *( domainlabel "." ) domainlabel [ "." ] domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum We also support percent encoding in spirit of the generic reg-name, even though this should explicitly not be used according to the RFC. It is, however, not strictly forbidden (unlike older RFC), so we support it. */ first = part = parser->cur; for (;;) { const unsigned char *offset; unsigned char ch, pch; /* alphanum */ offset = parser->cur; ch = pch = *parser->cur; if (parser->cur >= parser->end) break; if ((ret=uri_parse_pct_encoded(parser, &ch)) < 0) { return -1; } else if (ret > 0) { if (!i_isalnum(ch)) return -1; if (host_name != NULL) str_append_c(host_name, ch); part = parser->cur; } else { if (!i_isalnum(*parser->cur)) break; parser->cur++; } if (parser->cur < parser->end) { /* *( alphanum | "-" ) alphanum */ do { offset = parser->cur; if ((ret=uri_parse_pct_encoded(parser, &ch)) < 0) { return -1; } else if (ret > 0) { if (!i_isalnum(ch) && ch != '-') break; if (host_name != NULL) { if (offset > part) str_append_data(host_name, part, offset - part); str_append_c(host_name, ch); } part = parser->cur; } else { ch = *parser->cur; if (!i_isalnum(ch) && ch != '-') break; parser->cur++; } pch = ch; } while (parser->cur < parser->end); if (!i_isalnum(pch)) { parser->error = "Invalid domain label in hostname"; return -1; } } if (host_name != NULL && parser->cur > part) str_append_data(host_name, part, parser->cur - part); /* "." */ if (parser->cur >= parser->end || ch != '.') break; if (host_name != NULL) str_append_c(host_name, '.'); if (parser->cur == offset) parser->cur++; part = parser->cur; } if (parser->cur == first) return 0; /* remove trailing '.' */ if (host_name != NULL) { const char *name = str_c(host_name); i_assert(str_len(host_name) > 0); if (name[str_len(host_name)-1] == '.') str_truncate(host_name, str_len(host_name)-1); } return 1; } int uri_parse_host_name(struct uri_parser *parser, const char **host_name_r) { string_t *host_name = NULL; int ret; if (host_name_r != NULL) host_name = uri_parser_get_tmpbuf(parser, 256); if ((ret=uri_do_parse_host_name(parser, host_name)) <= 0) return ret; if (host_name_r != NULL) *host_name_r = str_c(host_name); return 1; } static int uri_parse_ip_literal(struct uri_parser *parser, string_t *literal, struct in6_addr *ip6_r) ATTR_NULL(2,3) { const unsigned char *p; const char *address; struct in6_addr ip6; /* IP-literal = "[" ( IPv6address / IPvFuture ) "]" * IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) * IPv6address = ; Syntax not relevant: parsed using inet_pton() */ /* "[" already verified */ /* Scan for end of address */ for (p = parser->cur+1; p < parser->end; p++) { if (*p == ']') break; } if (p >= parser->end || *p != ']') { parser->error = "Expecting ']' at end of IP-literal"; return -1; } if (literal != NULL) str_append_data(literal, parser->cur, p-parser->cur+1); address = t_strdup_until(parser->cur+1, p); parser->cur = p + 1; if (*address == '\0') { parser->error = "Empty IPv6 host address"; return -1; } if (*address == 'v') { parser->error = p_strdup_printf(parser->pool, "Future IP host address '%s' not supported", address); return -1; } if (inet_pton(AF_INET6, address, &ip6) <= 0) { parser->error = p_strdup_printf(parser->pool, "Invalid IPv6 host address '%s'", address); return -1; } if (ip6_r != NULL) *ip6_r = ip6; return 1; } static int uri_do_parse_host(struct uri_parser *parser, struct uri_host *host, bool host_name) ATTR_NULL(2) { const unsigned char *preserve; struct in_addr ip4; struct in6_addr ip6; string_t *literal = NULL; int ret; /* RFC 3986: * * host = IP-literal / IPv4address / reg-name */ if (host != NULL) i_zero(host); literal = uri_parser_get_tmpbuf(parser, 256); /* IP-literal / */ if (parser->cur < parser->end && *parser->cur == '[') { if (uri_parse_ip_literal(parser, literal, &ip6) <= 0) return -1; if (host != NULL) { host->name = p_strdup(parser->pool, str_c(literal));; host->ip.family = AF_INET6; host->ip.u.ip6 = ip6; } return 1; } /* IPv4address / * * If it fails to parse, we try to parse it as a reg-name */ preserve = parser->cur; if ((ret = uri_parse_ipv4address(parser, literal, &ip4)) > 0) { if (host != NULL) { host->name = p_strdup(parser->pool, str_c(literal)); host->ip.family = AF_INET; host->ip.u.ip4 = ip4; } return ret; } parser->cur = preserve; str_truncate(literal, 0); /* reg-name */ if (host_name) { if (uri_do_parse_host_name(parser, literal) < 0) return -1; } else if (uri_do_parse_reg_name(parser, literal) < 0) return -1; if (host != NULL) host->name = p_strdup(parser->pool, str_c(literal)); return 0; } int uri_parse_host(struct uri_parser *parser, struct uri_host *host) { return uri_do_parse_host(parser, host, TRUE); } static int uri_parse_port(struct uri_parser *parser, struct uri_authority *auth) ATTR_NULL(2) { const unsigned char *first; in_port_t port; /* RFC 3986: * * port = *DIGIT */ first = parser->cur; while (parser->cur < parser->end && i_isdigit(*parser->cur)) parser->cur++; if (parser->cur == first) return 0; if (net_str2port(t_strdup_until(first, parser->cur), &port) < 0) { parser->error = "Invalid port number"; return -1; } if (auth != NULL) auth->port = port; return 1; } static int uri_do_parse_authority(struct uri_parser *parser, struct uri_authority *auth, bool host_name) ATTR_NULL(2) { const unsigned char *p; int ret; /* * authority = [ userinfo "@" ] host [ ":" port ] */ if (auth != NULL) i_zero(auth); /* Scan ahead to check whether there is a [userinfo "@"] uri component */ for (p = parser->cur; p < parser->end; p++){ /* refuse 8bit characters */ if ((*p & 0x80) != 0) break; /* break at first delimiter */ if (*p != '%' && (_uri_char_lookup[*p] & CHAR_MASK_UCHAR) == 0) break; } /* Extract userinfo */ if (p < parser->end && *p == '@') { if (auth != NULL) auth->enc_userinfo = p_strdup_until(parser->pool, parser->cur, p); parser->cur = p+1; } /* host */ if (uri_do_parse_host(parser, (auth == NULL ? NULL : &auth->host), host_name) < 0) return -1; if (parser->cur == parser->end) return 1; switch (*parser->cur) { case ':': case '/': case '?': case '#': break; default: parser->error = "Invalid host identifier"; return -1; } /* [":" port] */ if (*parser->cur == ':') { parser->cur++; if ((ret = uri_parse_port(parser, auth)) < 0) return ret; if (parser->cur == parser->end) return 1; switch (*parser->cur) { case '/': case '?': case '#': break; default: parser->error = "Invalid host port"; return -1; } } return 1; } static int uri_do_parse_slashslash_authority(struct uri_parser *parser, struct uri_authority *auth, bool host_name) ATTR_NULL(2) { /* "//" authority */ if ((parser->end - parser->cur) <= 2 || parser->cur[0] != '/' || parser->cur[1] != '/') return 0; parser->cur += 2; return uri_do_parse_authority(parser, auth, host_name); } int uri_parse_authority(struct uri_parser *parser, struct uri_authority *auth) { return uri_do_parse_authority(parser, auth, FALSE); } int uri_parse_slashslash_authority(struct uri_parser *parser, struct uri_authority *auth) { return uri_do_parse_slashslash_authority(parser, auth, FALSE); } int uri_parse_host_authority(struct uri_parser *parser, struct uri_authority *auth) { return uri_do_parse_authority(parser, auth, TRUE); } int uri_parse_slashslash_host_authority(struct uri_parser *parser, struct uri_authority *auth) { return uri_do_parse_slashslash_authority(parser, auth, TRUE); } int uri_parse_path_segment(struct uri_parser *parser, const char **segment_r) { const unsigned char *first = parser->cur; int ret; while (parser->cur < parser->end) { if (*parser->cur == '%') { unsigned char ch = 0; if ((ret=uri_parse_pct_encoded(parser, &ch)) < 0) return -1; if (ret > 0) continue; } if ((*parser->cur & 0x80) != 0 || (_uri_char_lookup[*parser->cur] & CHAR_MASK_PCHAR) == 0) break; parser->cur++; } if (parser->cur < parser->end && *parser->cur != '/' && *parser->cur != '?' && *parser->cur != '#' ) { parser->error = "Path component contains invalid character"; return -1; } if (first == parser->cur) return 0; if (segment_r != NULL) *segment_r = p_strdup_until(parser->pool, first, parser->cur); return 1; } int uri_parse_path(struct uri_parser *parser, int *relative_r, const char *const **path_r) { const unsigned char *pbegin = parser->cur; ARRAY_TYPE(const_string) segments; const char *segment = NULL; unsigned int count; int relative = 1; int ret; count = 0; if (path_r != NULL) p_array_init(&segments, parser->pool, 16); else i_zero(&segments); /* check for a leading '/' and indicate absolute path when it is present */ if (parser->cur < parser->end && *parser->cur == '/') { parser->cur++; relative = 0; } /* parse first segment */ if ((ret = uri_parse_path_segment(parser, &segment)) < 0) return -1; for (;;) { if (ret > 0) { /* strip dot segments */ if (segment[0] == '.') { if (segment[1] == '.') { if (segment[2] == '\0') { /* '..' -> skip and... */ segment = NULL; /* ... pop last segment (if any) */ if (count > 0) { if (path_r != NULL) { i_assert(count == array_count(&segments)); array_delete(&segments, count-1, 1); } count--; } else if ( relative > 0 ) { relative++; } } } else if (segment[1] == '\0') { /* '.' -> skip */ segment = NULL; } } } else { segment = ""; } if (segment != NULL) { if (path_r != NULL) array_push_back(&segments, &segment); count++; } if (parser->cur >= parser->end || *parser->cur != '/') break; parser->cur++; /* parse next path segment */ if ((ret = uri_parse_path_segment(parser, &segment)) < 0) return -1; } if (relative_r != NULL) *relative_r = relative; if (path_r != NULL) *path_r = NULL; if (parser->cur == pbegin) { /* path part of URI is empty */ return 0; } if (path_r != NULL) { /* special treatment for a trailing '..' or '.' */ if (segment == NULL) { segment = ""; array_push_back(&segments, &segment); } array_append_zero(&segments); *path_r = array_get(&segments, &count); } if (parser->cur < parser->end && *parser->cur != '?' && *parser->cur != '#') { parser->error = "Path component contains invalid character"; return -1; } return 1; } int uri_parse_query(struct uri_parser *parser, const char **query_r) { const unsigned char *first = parser->cur; int ret; /* RFC 3986: * * URI = { ... } [ "?" query ] { ... } * query = *( pchar / "/" / "?" ) * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" */ if (parser->cur >= parser->end || *parser->cur != '?') return 0; parser->cur++; while (parser->cur < parser->end) { if (*parser->cur == '%') { unsigned char ch = 0; if ((ret=uri_parse_pct_encoded(parser, &ch)) < 0) return -1; if (ret > 0) continue; } if ((*parser->cur & 0x80) != 0 || (_uri_char_lookup[*parser->cur] & CHAR_MASK_QCHAR) == 0) break; parser->cur++; } if (parser->cur < parser->end && *parser->cur != '#') { parser->error = "Query component contains invalid character"; return -1; } if (query_r != NULL) *query_r = p_strdup_until(parser->pool, first+1, parser->cur); return 1; } int uri_parse_fragment(struct uri_parser *parser, const char **fragment_r) { const unsigned char *first = parser->cur; int ret; /* RFC 3986: * * URI = { ... } [ "#" fragment ] * fragment = *( pchar / "/" / "?" ) * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" */ if (parser->cur >= parser->end || *parser->cur != '#') return 0; parser->cur++; while (parser->cur < parser->end) { if (*parser->cur == '%') { unsigned char ch = 0; if ((ret=uri_parse_pct_encoded(parser, &ch)) < 0) return -1; if (ret > 0) continue; } if ((*parser->cur & 0x80) != 0 || (_uri_char_lookup[*parser->cur] & CHAR_MASK_QCHAR) == 0) break; parser->cur++; } if (parser->cur < parser->end) { parser->error = "Fragment component contains invalid character"; return -1; } if (fragment_r != NULL) *fragment_r = p_strdup_until(parser->pool, first+1, parser->cur); return 1; } void uri_parser_init_data(struct uri_parser *parser, pool_t pool, const unsigned char *data, size_t size) { i_zero(parser); parser->pool = pool; parser->begin = parser->cur = data; parser->end = data + size; } void uri_parser_init(struct uri_parser *parser, pool_t pool, const char *uri) { uri_parser_init_data (parser, pool, (const unsigned char *)uri, strlen(uri)); } string_t *uri_parser_get_tmpbuf(struct uri_parser *parser, size_t size) { if (parser->tmpbuf == NULL) parser->tmpbuf = str_new(parser->pool, size); else str_truncate(parser->tmpbuf, 0); return parser->tmpbuf; } int uri_parse_absolute_generic(struct uri_parser *parser, enum uri_parse_flags flags) { int relative, aret, ret = 0; /* URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] hier-part = "//" authority path-abempty / path-absolute / path-rootless / path-empty path-abempty = *( "/" segment ) path-absolute = "/" [ segment-nz *( "/" segment ) ] path-rootless = segment-nz *( "/" segment ) path-empty = 0 segment = *pchar segment-nz = 1*pchar */ /* scheme ":" */ if ((flags & URI_PARSE_SCHEME_EXTERNAL) == 0 && (ret=uri_parse_scheme(parser, NULL)) <= 0) { if (ret == 0) parser->error = "Missing scheme"; return -1; } /* "//" authority */ if ((aret=uri_parse_slashslash_authority (parser, NULL)) < 0) return -1; /* path-absolute / path-rootless / path-empty */ if (aret == 0) { ret = uri_parse_path(parser, &relative, NULL); /* path-abempty */ } else if (parser->cur < parser->end && *parser->cur == '/') { ret = uri_parse_path(parser, &relative, NULL); i_assert(ret <= 0 || relative == 0); } if (ret < 0) return -1; /* [ "?" query ] */ if (uri_parse_query(parser, NULL) < 0) return -1; /* [ "#" fragment ] */ if ((ret=uri_parse_fragment(parser, NULL)) < 0) return ret; if (ret > 0 && (flags & URI_PARSE_ALLOW_FRAGMENT_PART) == 0) { parser->error = "Fragment part not allowed"; return -1; } i_assert(parser->cur == parser->end); return 0; } /* * Generic URI manipulation */ void uri_host_copy(pool_t pool, struct uri_host *dest, const struct uri_host *src) { const char *host_name = src->name; /* create host name literal if caller is lazy */ if (host_name == NULL && src->ip.family != 0) { host_name = net_ip2addr(&src->ip); i_assert(*host_name != '\0'); } *dest = *src; dest->name = p_strdup(pool, host_name); } /* * Check generic URI */ int uri_check_data(const unsigned char *data, size_t size, enum uri_parse_flags flags, const char **error_r) { struct uri_parser parser; int ret; i_zero(&parser); parser.pool = pool_datastack_create(); parser.begin = parser.cur = data; parser.end = data + size; ret = uri_parse_absolute_generic(&parser, flags); *error_r = parser.error; return ret; } int uri_check(const char *uri, enum uri_parse_flags flags, const char **error_r) { return uri_check_data ((const unsigned char *)uri, strlen(uri), flags, error_r); } /* * Generic URI construction */ void uri_data_encode(string_t *out, const unsigned char esc_table[256], unsigned char esc_mask, const char *esc_extra, const char *data) { const unsigned char *pbegin, *p; pbegin = p = (const unsigned char *)data; while (*p != '\0') { if ((*p & 0x80) != 0 || (esc_table[*p] & esc_mask) == 0 || (esc_extra != NULL && strchr(esc_extra, (char)*p) != NULL)) { if ((p - pbegin) > 0) str_append_data(out, pbegin, p - pbegin); str_printfa(out, "%%%02x", *p); p++; pbegin = p; } else { p++; } } if ((p - pbegin) > 0) str_append_data(out, pbegin, p - pbegin); } void uri_append_scheme(string_t *out, const char *scheme) { str_append(out, scheme); str_append_c(out, ':'); } void uri_append_user_data(string_t *out, const char *esc, const char *data) { uri_data_encode(out, _uri_char_lookup, CHAR_MASK_UCHAR, esc, data); } void uri_append_userinfo(string_t *out, const char *userinfo) { uri_append_user_data(out, NULL, userinfo); str_append_c(out, '@'); } void uri_append_host_name(string_t *out, const char *name) { uri_data_encode(out, _uri_char_lookup, CHAR_MASK_UNRESERVED | CHAR_MASK_SUB_DELIMS, NULL, name); } void uri_append_host_ip(string_t *out, const struct ip_addr *host_ip) { const char *addr = net_ip2addr(host_ip); i_assert(host_ip->family != 0); if (host_ip->family == AF_INET) { str_append(out, addr); return; } i_assert(host_ip->family == AF_INET6); str_append_c(out, '['); str_append(out, addr); str_append_c(out, ']'); } void uri_append_host(string_t *out, const struct uri_host *host) { if (host->name != NULL) { /* assume IPv6 literal if starts with '['; avoid encoding */ if (*host->name == '[') str_append(out, host->name); else uri_append_host_name(out, host->name); } else uri_append_host_ip(out, &host->ip); } void uri_append_port(string_t *out, in_port_t port) { if (port != 0) str_printfa(out, ":%u", port); } void uri_append_path_segment_data(string_t *out, const char *esc, const char *data) { uri_data_encode(out, _uri_char_lookup, CHAR_MASK_PCHAR, esc, data); } void uri_append_path_segment(string_t *out, const char *segment) { str_append_c(out, '/'); if (*segment != '\0') uri_append_path_data(out, NULL, segment); } void uri_append_path_data(string_t *out, const char *esc, const char *data) { uri_data_encode(out, _uri_char_lookup, CHAR_MASK_PFCHAR, esc, data); } void uri_append_path(string_t *out, const char *path) { str_append_c(out, '/'); if (*path != '\0') uri_append_path_data(out, NULL, path); } void uri_append_query_data(string_t *out, const char *esc, const char *data) { uri_data_encode(out, _uri_char_lookup, CHAR_MASK_QCHAR, esc, data); } void uri_append_query(string_t *out, const char *query) { str_append_c(out, '?'); if (*query != '\0') uri_append_query_data(out, NULL, query); } void uri_append_fragment_data(string_t *out, const char *esc, const char *data) { uri_data_encode(out, _uri_char_lookup, CHAR_MASK_QCHAR, esc, data); } void uri_append_fragment(string_t *out, const char *fragment) { str_append_c(out, '#'); if (*fragment != '\0') uri_append_fragment_data(out, NULL, fragment); } void uri_append_unreserved(string_t *out, const char *data) { uri_data_encode(out, _uri_char_lookup, CHAR_MASK_UNRESERVED, NULL, data); } void uri_append_unreserved_path(string_t *out, const char *data) { uri_data_encode(out, _uri_char_lookup, CHAR_MASK_UNRESERVED_PATH, NULL, data); } dovecot-2.3.21.1/src/lib/process-stat.c0000644000000000000000000001567114656633576014457 00000000000000/* Copyright (c) 2008-2021 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "process-stat.h" #include "time-util.h" #include #include #include #include #include #include #include #define PROC_STAT_PATH "/proc/self/stat" #define PROC_STATUS_PATH "/proc/self/status" #define PROC_IO_PATH "/proc/self/io" static const uint64_t stat_undefined = 0xFFFFFFFFFFFFFFFF; struct key_val { const char *key; uint64_t *value; unsigned int idx; }; static int parse_field(const char *line, struct key_val *field) { if (str_begins(line, field->key)) return str_to_uint64(line + strlen(field->key), field->value); return -1; } static void buffer_parse(const char *buf, struct key_val *fields) { const char *const *tmp; tmp = t_strsplit(buf, "\n"); unsigned int tmp_count = str_array_length(tmp); for (; fields->key != NULL; fields++) { if (fields->idx >= tmp_count || parse_field(tmp[fields->idx], fields) < 0) *fields->value = stat_undefined; } } static int open_fd(const char *path, struct event *event) { int fd; uid_t uid; fd = open(path, O_RDONLY); if (fd == -1 && errno == EACCES) { uid = geteuid(); /* kludge: if we're running with permissions temporarily dropped, get them temporarily back so we can open /proc/self/io. */ if (seteuid(0) == 0) { fd = open(path, O_RDONLY); if (seteuid(uid) < 0) i_fatal("seteuid(%s) failed", dec2str(uid)); } errno = EACCES; } if (fd == -1) { if (errno == ENOENT || errno == EACCES) e_debug(event, "open(%s) failed: %m", path); else e_error(event, "open(%s) failed: %m", path); } return fd; } static int read_file(int fd, const char *path, char *buf_r, size_t buf_size, struct event *event) { ssize_t ret; ret = read(fd, buf_r, buf_size); if (ret <= 0) { if (ret == -1) e_error(event, "read(%s) failed: %m", path); else e_error(event, "read(%s) returned EOF", path); } else if (ret == (ssize_t)buf_size) { e_error(event, "%s is larger than expected", path); buf_r[buf_size - 1] = '\0'; } else { buf_r[ret] = '\0'; } i_close_fd(&fd); return ret <= 0 ? -1 : 0; } static int parse_key_val_file(const char *path, struct key_val *fields, struct event *event) { char buf[2048]; int fd; fd = open_fd(path, event); if (fd == -1 || read_file(fd, path, buf, sizeof(buf), event) < 0) { for (; fields->key != NULL; fields++) *fields->value = stat_undefined; return -1; } buffer_parse(buf, fields); return 0; } static int parse_proc_io(struct process_stat *stat_r, struct event *event) { struct key_val fields[] = { { "rchar: ", &stat_r->rchar, 0 }, { "wchar: ", &stat_r->wchar, 1 }, { "syscr: ", &stat_r->syscr, 2 }, { "syscw: ", &stat_r->syscw, 3 }, { NULL, NULL, 0 }, }; if (stat_r->proc_io_failed || parse_key_val_file(PROC_IO_PATH, fields, event) < 0) { stat_r->proc_io_failed = TRUE; return -1; } return 0; } static int parse_proc_status(struct process_stat *stat_r, struct event *event) { struct key_val fields [] = { { "voluntary_ctxt_switches:\t", &stat_r->vol_cs, 53 }, { "nonvoluntary_ctxt_switches:\t", &stat_r->invol_cs, 54 }, { NULL, NULL, 0 }, }; if (stat_r->proc_status_failed || parse_key_val_file(PROC_STATUS_PATH, fields, event) < 0) { stat_r->proc_status_failed = TRUE; return -1; } return 0; } static int stat_get_rusage(struct process_stat *stat_r) { struct rusage usage; if (getrusage(RUSAGE_SELF, &usage) < 0) return -1; stat_r->utime = timeval_to_usecs(&usage.ru_utime); stat_r->stime = timeval_to_usecs(&usage.ru_stime); stat_r->minor_faults = usage.ru_minflt; stat_r->major_faults = usage.ru_majflt; stat_r->vol_cs = usage.ru_nvcsw; stat_r->invol_cs = usage.ru_nivcsw; return 0; } static int parse_stat_file(struct process_stat *stat_r, struct event *event) { int fd = -1; char buf[1024]; unsigned int i; const char *const *tmp; struct { uint64_t *value; unsigned int idx; } fields[] = { { &stat_r->minor_faults, 9 }, { &stat_r->major_faults, 11 }, { &stat_r->utime, 13 }, { &stat_r->stime, 14 }, { &stat_r->vsz, 22 }, { &stat_r->rss, 23 }, }; if (!stat_r->proc_stat_failed) fd = open_fd(PROC_STAT_PATH, event); if (fd == -1) { stat_r->proc_stat_failed = TRUE; /* vsz and rss are not provided by getrusage(), setting to undefined */ stat_r->vsz = stat_undefined; stat_r->rss = stat_undefined; if (stat_r->rusage_failed) return -1; if (stat_get_rusage(stat_r) < 0) { e_error(event, "getrusage() failed: %m"); stat_r->rusage_failed = TRUE; return -1; } return 0; } if (read_file(fd, PROC_STAT_PATH, buf, sizeof(buf), event) < 0) { stat_r->proc_stat_failed = TRUE; return -1; } tmp = t_strsplit(buf, " "); unsigned int tmp_count = str_array_length(tmp); for (i = 0; i < N_ELEMENTS(fields); i++) { if (fields[i].idx >= tmp_count || str_to_uint64(tmp[fields[i].idx], fields[i].value) < 0) *fields[i].value = stat_undefined; } /* rss is provided in pages, convert to bytes */ stat_r->rss *= sysconf(_SC_PAGESIZE); return 0; } static int parse_all_stats(struct process_stat *stat_r, struct event *event) { bool has_fields = FALSE; if (parse_stat_file(stat_r, event) == 0) has_fields = TRUE; if (parse_proc_io(stat_r, event) == 0) has_fields = TRUE; if ((!stat_r->proc_stat_failed || stat_r->rusage_failed) && parse_proc_status(stat_r, event) == 0) has_fields = TRUE; if (has_fields) return 0; return -1; } void process_stat_read_start(struct process_stat *stat_r, struct event *event) { i_zero(stat_r); (void)parse_all_stats(stat_r, event); } void process_stat_read_finish(struct process_stat *stat, struct event *event) { unsigned int i; struct process_stat new_stat; i_zero(&new_stat); new_stat.proc_io_failed = stat->proc_io_failed; new_stat.proc_status_failed = stat->proc_status_failed; new_stat.proc_stat_failed = stat->proc_stat_failed; new_stat.rusage_failed = stat->rusage_failed; if (parse_all_stats(&new_stat, event) < 0) { i_zero(stat); return; } stat->vsz = new_stat.vsz == stat_undefined ? 0 : new_stat.vsz; stat->rss = new_stat.rss == stat_undefined ? 0 : new_stat.rss; unsigned int cumulative_field_offsets[] = { offsetof(struct process_stat, utime), offsetof(struct process_stat, stime), offsetof(struct process_stat, minor_faults), offsetof(struct process_stat, major_faults), offsetof(struct process_stat, vol_cs), offsetof(struct process_stat, invol_cs), offsetof(struct process_stat, rchar), offsetof(struct process_stat, wchar), offsetof(struct process_stat, syscr), offsetof(struct process_stat, syscw), }; for (i = 0; i < N_ELEMENTS(cumulative_field_offsets); i++) { uint64_t *old_value = PTR_OFFSET(stat, cumulative_field_offsets[i]); uint64_t *new_value = PTR_OFFSET(&new_stat, cumulative_field_offsets[i]); if (*old_value == stat_undefined || *new_value == stat_undefined) *old_value = 0; else *old_value = *new_value > *old_value ? (*new_value - *old_value) : 0; } } dovecot-2.3.21.1/src/lib/hook-build.h0000644000000000000000000000120414656633576014055 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #ifndef HOOK_BUILD_H #define HOOK_BUILD_H 1 struct hook_build_context; struct hook_stack; /* Initialize new hook building context, vfuncs should point to the functions table that is being manipulated, and size should be the size of this table. */ struct hook_build_context *hook_build_init(void (**vfuncs)(), size_t size); /* This is called after a hook may have updated vfuncs */ void hook_build_update(struct hook_build_context *ctx, void *_vlast); /* Free memory used by build context */ void hook_build_deinit(struct hook_build_context **_ctx); #endif dovecot-2.3.21.1/src/lib/ostream-rawlog.h0000644000000000000000000000061114656633576014764 00000000000000#ifndef OSTREAM_RAWLOG_H #define OSTREAM_RAWLOG_H #include "iostream-rawlog.h" struct ostream * o_stream_create_rawlog(struct ostream *output, const char *rawlog_path, int rawlog_fd, enum iostream_rawlog_flags flags); struct ostream * o_stream_create_rawlog_from_stream(struct ostream *output, struct ostream *rawlog_output, enum iostream_rawlog_flags flags); #endif dovecot-2.3.21.1/src/lib/mmap-util.c0000644000000000000000000000233614656633576013727 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "mmap-util.h" #include void *mmap_file(int fd, size_t *length, int prot) { struct stat st; if (fstat(fd, &st) < 0) return MAP_FAILED; #if OFF_T_MAX > SSIZE_T_MAX if (st.st_size > SSIZE_T_MAX) { /* too large file to map into memory */ errno = EFBIG; return MAP_FAILED; } #endif *length = (size_t)st.st_size; if (*length == 0) return NULL; i_assert(*length > 0 && *length < SSIZE_T_MAX); return mmap(NULL, *length, prot, MAP_SHARED, fd, 0); } void *mmap_ro_file(int fd, size_t *length) { return mmap_file(fd, length, PROT_READ); } void *mmap_rw_file(int fd, size_t *length) { return mmap_file(fd, length, PROT_READ | PROT_WRITE); } #undef madvise int my_madvise(void *start ATTR_UNUSED, size_t length ATTR_UNUSED, int advice ATTR_UNUSED) { #ifdef HAVE_MADVISE /* Ignore ENOSYS errors, which happen if the kernel hasn't implemented the syscall even if libc has. */ if (madvise(start, length, advice) < 0 && errno != ENOSYS) return -1; #endif return 0; } size_t mmap_get_page_size(void) { static size_t size = 0; if (size != 0) return size; size = getpagesize(); return size; } dovecot-2.3.21.1/src/lib/eacces-error.h0000644000000000000000000000107314656633576014376 00000000000000#ifndef EACCES_ERROR_H #define EACCES_ERROR_H /* Return a user-friendly error message for EACCES failures. */ const char *eacces_error_get(const char *func, const char *path); const char *eacces_error_get_creating(const char *func, const char *path); /* Return a user-friendly error message for fchown() or chown() EPERM failures when only the group is being changed. gid_origin specifies why exactly this group is being used. */ const char *eperm_error_get_chgrp(const char *func, const char *path, gid_t gid, const char *gid_origin) ATTR_NULL(4); #endif dovecot-2.3.21.1/src/lib/istream-rawlog.c0000644000000000000000000000714414656633576014761 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ostream.h" #include "iostream-rawlog-private.h" #include "istream-private.h" #include "istream-rawlog.h" struct rawlog_istream { struct istream_private istream; struct rawlog_iostream riostream; }; static void i_stream_rawlog_close(struct iostream_private *stream, bool close_parent) { struct rawlog_istream *rstream = container_of(stream, struct rawlog_istream, istream.iostream); iostream_rawlog_close(&rstream->riostream); if (close_parent) i_stream_close(rstream->istream.parent); } static void i_stream_rawlog_destroy(struct iostream_private *stream) { struct rawlog_istream *rstream = container_of(stream, struct rawlog_istream, istream.iostream); uoff_t v_offset; v_offset = rstream->istream.parent_start_offset + rstream->istream.istream.v_offset; if (rstream->istream.parent->seekable || v_offset > rstream->istream.parent->v_offset) { /* get to same position in parent stream */ i_stream_seek(rstream->istream.parent, v_offset); } } static ssize_t i_stream_rawlog_read(struct istream_private *stream) { struct rawlog_istream *rstream = container_of(stream, struct rawlog_istream, istream); ssize_t ret; size_t pos; i_stream_seek(stream->parent, rstream->istream.parent_start_offset + stream->istream.v_offset); stream->pos -= stream->skip; stream->skip = 0; stream->buffer = i_stream_get_data(stream->parent, &pos); if (pos > stream->pos) ret = 0; else do { ret = i_stream_read_memarea(stream->parent); stream->istream.stream_errno = stream->parent->stream_errno; stream->istream.eof = stream->parent->eof; stream->buffer = i_stream_get_data(stream->parent, &pos); } while (pos <= stream->pos && ret > 0); if (ret == -2) return -2; if (pos <= stream->pos) ret = ret == 0 ? 0 : -1; else { ret = (ssize_t)(pos - stream->pos); iostream_rawlog_write(&rstream->riostream, stream->buffer + stream->pos, ret); } stream->pos = pos; i_assert(ret != -1 || stream->istream.eof || stream->istream.stream_errno != 0); return ret; } struct istream * i_stream_create_rawlog(struct istream *input, const char *rawlog_path, int rawlog_fd, enum iostream_rawlog_flags flags) { struct ostream *rawlog_output; bool autoclose_fd = (flags & IOSTREAM_RAWLOG_FLAG_AUTOCLOSE) != 0; i_assert(rawlog_path != NULL); i_assert(rawlog_fd != -1); rawlog_output = autoclose_fd ? o_stream_create_fd_autoclose(&rawlog_fd, 0) : o_stream_create_fd(rawlog_fd, 0); o_stream_set_name(rawlog_output, t_strdup_printf("rawlog(%s)", rawlog_path)); return i_stream_create_rawlog_from_stream(input, rawlog_output, flags); } struct istream * i_stream_create_rawlog_from_stream(struct istream *input, struct ostream *rawlog_output, enum iostream_rawlog_flags flags) { struct rawlog_istream *rstream; rstream = i_new(struct rawlog_istream, 1); rstream->istream.max_buffer_size = input->real_stream->max_buffer_size; rstream->istream.stream_size_passthrough = TRUE; rstream->riostream.rawlog_output = rawlog_output; iostream_rawlog_init(&rstream->riostream, flags, TRUE); rstream->istream.read = i_stream_rawlog_read; rstream->istream.iostream.close = i_stream_rawlog_close; rstream->istream.iostream.destroy = i_stream_rawlog_destroy; rstream->istream.istream.readable_fd = input->readable_fd; rstream->istream.istream.blocking = input->blocking; rstream->istream.istream.seekable = input->seekable; return i_stream_create(&rstream->istream, input, i_stream_get_fd(input), 0); } dovecot-2.3.21.1/src/lib/ostream-private.h0000644000000000000000000000456514656633576015157 00000000000000#ifndef OSTREAM_PRIVATE_H #define OSTREAM_PRIVATE_H #include "ostream.h" #include "iostream-private.h" struct ostream_private { /* inheritance: */ struct iostream_private iostream; /* methods: */ void (*cork)(struct ostream_private *stream, bool set); int (*flush)(struct ostream_private *stream); void (*set_flush_callback)(struct ostream_private *stream, stream_flush_callback_t *callback, void *context); void (*flush_pending)(struct ostream_private *stream, bool set); size_t (*get_buffer_used_size)(const struct ostream_private *stream); size_t (*get_buffer_avail_size)(const struct ostream_private *stream); int (*seek)(struct ostream_private *stream, uoff_t offset); ssize_t (*sendv)(struct ostream_private *stream, const struct const_iovec *iov, unsigned int iov_count); int (*write_at)(struct ostream_private *stream, const void *data, size_t size, uoff_t offset); enum ostream_send_istream_result (*send_istream)(struct ostream_private *outstream, struct istream *instream); void (*switch_ioloop_to)(struct ostream_private *stream, struct ioloop *ioloop); /* data: */ struct ostream ostream; size_t max_buffer_size; struct ostream *parent; /* for filter streams */ int fd; struct timeval last_write_timeval; stream_flush_callback_t *callback; void *context; bool corked:1; bool finished:1; bool closing:1; bool last_errors_not_checked:1; bool error_handling_disabled:1; bool noverflow:1; bool finish_also_parent:1; bool finish_via_child:1; }; struct ostream * o_stream_create(struct ostream_private *_stream, struct ostream *parent, int fd) ATTR_NULL(2); enum ostream_send_istream_result io_stream_copy(struct ostream *outstream, struct istream *instream); void o_stream_copy_error_from_parent(struct ostream_private *_stream); /* This should be called before sending data to parent stream. It makes sure that the parent stream's output buffer doesn't become too large. Returns 1 if more data can be safely added, 0 if not, -1 if error. */ int o_stream_flush_parent_if_needed(struct ostream_private *_stream); /* Call this in flush() handler to flush the parent stream. It will call either o_stream_flush() or o_stream_finish() depending on whether this stream is already finished. If the parent fails, its error will be also copied to this stream. */ int o_stream_flush_parent(struct ostream_private *_stream); #endif dovecot-2.3.21.1/src/lib/istream-chain.c0000644000000000000000000002405114656633576014544 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "llist.h" #include "memarea.h" #include "istream-private.h" #include "istream-chain.h" struct chain_istream; struct istream_chain_link { struct istream_chain_link *prev, *next; struct istream *stream; bool eof; }; struct istream_chain { struct istream_chain_link *head, *tail; struct chain_istream *stream; }; struct chain_istream { struct istream_private istream; /* how much of the previous link's stream still exists at the beginning of our buffer. skipping through this should point to the beginning of the current link's stream. */ size_t prev_stream_left; size_t prev_skip; struct istream_chain chain; }; static void ATTR_NULL(2) i_stream_chain_append_internal(struct istream_chain *chain, struct istream *stream) { struct istream_chain_link *link; if (stream == NULL && chain->tail != NULL && chain->tail->stream == NULL) return; link = i_new(struct istream_chain_link, 1); link->stream = stream; link->eof = stream == NULL; if (stream != NULL) i_stream_ref(stream); if (chain->head == NULL && stream != NULL) { i_stream_set_max_buffer_size(stream, chain->stream->istream.max_buffer_size); } DLLIST2_APPEND(&chain->head, &chain->tail, link); /* if io_add_istream() has been added to this chain stream, notify the callback that we have more data available. */ if (stream != NULL) i_stream_set_input_pending(stream, TRUE); } void i_stream_chain_append(struct istream_chain *chain, struct istream *stream) { i_stream_chain_append_internal(chain, stream); } void i_stream_chain_append_eof(struct istream_chain *chain) { i_stream_chain_append_internal(chain, NULL); } static void i_stream_chain_set_max_buffer_size(struct iostream_private *stream, size_t max_size) { struct chain_istream *cstream = container_of(stream, struct chain_istream, istream.iostream); struct istream_chain_link *link = cstream->chain.head; cstream->istream.max_buffer_size = max_size; while (link != NULL) { if (link->stream != NULL) i_stream_set_max_buffer_size(link->stream, max_size); link = link->next; } } static void i_stream_chain_destroy(struct iostream_private *stream) { struct chain_istream *cstream = container_of(stream, struct chain_istream, istream.iostream); struct istream_chain_link *link = cstream->chain.head; while (link != NULL) { struct istream_chain_link *next = link->next; i_stream_unref(&link->stream); i_free(link); link = next; } i_stream_free_buffer(&cstream->istream); } static void i_stream_chain_read_next(struct chain_istream *cstream) { struct istream_chain_link *link = cstream->chain.head; struct istream *prev_input; const unsigned char *data; size_t data_size, cur_data_pos; i_assert(link != NULL && link->stream != NULL); i_assert(link->stream->eof); prev_input = link->stream; data = i_stream_get_data(prev_input, &data_size); DLLIST2_REMOVE(&cstream->chain.head, &cstream->chain.tail, link); i_free(link); /* a) we have more streams, b) we have EOF, c) we need to wait for more streams */ link = cstream->chain.head; if (link != NULL && link->stream != NULL) i_stream_seek(link->stream, 0); if (cstream->prev_stream_left > 0) { /* we've already buffered some of the prev_input. continue appending the rest to it. if it's already at EOF, there's nothing more to append. */ cur_data_pos = cstream->istream.pos - (cstream->istream.skip + cstream->prev_stream_left); i_assert(cur_data_pos <= data_size); data += cur_data_pos; data_size -= cur_data_pos; /* the stream has now become "previous", so its contents in buffer are now part of prev_stream_left. */ cstream->prev_stream_left += cur_data_pos; } else { cstream->istream.pos = 0; cstream->istream.skip = 0; cstream->prev_stream_left = 0; } if (data_size > 0) { if (cstream->istream.memarea != NULL && memarea_get_refcount(cstream->istream.memarea) > 1) i_stream_memarea_detach(&cstream->istream); memcpy(i_stream_alloc(&cstream->istream, data_size), data, data_size); cstream->istream.pos += data_size; cstream->prev_stream_left += data_size; } i_stream_skip(prev_input, i_stream_get_data_size(prev_input)); i_stream_unref(&prev_input); } static bool i_stream_chain_skip(struct chain_istream *cstream) { struct istream_private *stream = &cstream->istream; struct istream_chain_link *link = cstream->chain.head; size_t bytes_skipped; i_assert(stream->skip >= cstream->prev_skip); bytes_skipped = stream->skip - cstream->prev_skip; if (cstream->prev_stream_left == 0) { /* no need to worry about buffers, skip everything */ } else if (bytes_skipped < cstream->prev_stream_left) { /* we're still skipping inside buffer */ cstream->prev_stream_left -= bytes_skipped; bytes_skipped = 0; } else { /* done with the buffer */ bytes_skipped -= cstream->prev_stream_left; cstream->prev_stream_left = 0; } if (bytes_skipped > 0) { i_assert(stream->buffer != NULL); stream->pos -= bytes_skipped; stream->skip -= bytes_skipped; stream->buffer += bytes_skipped; } cstream->prev_skip = stream->skip; if (link == NULL || link->eof) { i_assert(bytes_skipped == 0); return FALSE; } i_stream_skip(link->stream, bytes_skipped); return TRUE; } static ssize_t i_stream_chain_read(struct istream_private *stream) { struct chain_istream *cstream = container_of(stream, struct chain_istream, istream); struct istream_chain_link *link = cstream->chain.head; const unsigned char *data; size_t data_size, cur_data_pos, new_pos; size_t new_bytes_count; ssize_t ret; if (link != NULL && link->eof) { stream->istream.eof = TRUE; return -1; } if (!i_stream_chain_skip(cstream)) return 0; i_assert(link != NULL); i_assert(stream->pos >= stream->skip + cstream->prev_stream_left); cur_data_pos = stream->pos - (stream->skip + cstream->prev_stream_left); data = i_stream_get_data(link->stream, &data_size); if (data_size > cur_data_pos) ret = 0; else { /* need to read more */ i_assert(cur_data_pos == data_size); ret = i_stream_read_memarea(link->stream); if (ret == -2 || ret == 0) return ret; if (ret == -1) { if (link->stream->stream_errno != 0) { io_stream_set_error(&stream->iostream, "read(%s) failed: %s", i_stream_get_name(link->stream), i_stream_get_error(link->stream)); stream->istream.stream_errno = link->stream->stream_errno; return -1; } /* EOF of this stream, go to next stream */ i_stream_chain_read_next(cstream); cstream->prev_skip = stream->skip; return i_stream_chain_read(stream); } /* we read something */ data = i_stream_get_data(link->stream, &data_size); } if (data_size == cur_data_pos) { /* nothing new read - preserve the buffer as it was */ i_assert(ret == 0 || ret == -1); return ret; } if (cstream->prev_stream_left == 0) { /* we can point directly to the current stream's buffers */ stream->buffer = data; stream->pos -= stream->skip; stream->skip = 0; new_pos = data_size; } else { /* we still have some of the previous stream left. merge the new data with it. */ i_assert(data_size > cur_data_pos); new_bytes_count = data_size - cur_data_pos; memcpy(i_stream_alloc(stream, new_bytes_count), data + cur_data_pos, new_bytes_count); stream->buffer = stream->w_buffer; new_pos = stream->pos + new_bytes_count; } i_assert(new_pos > stream->pos); ret = (ssize_t)(new_pos - stream->pos); stream->pos = new_pos; cstream->prev_skip = stream->skip; return ret; } static void i_stream_chain_close(struct iostream_private *stream, bool close_parent) { struct chain_istream *cstream = container_of(stream, struct chain_istream, istream.iostream); /* seek to the correct position in parent stream in case it didn't end with EOF */ (void)i_stream_chain_skip(cstream); if (close_parent) { struct istream_chain_link *link = cstream->chain.head; while (link != NULL) { i_stream_close(link->stream); link = link->next; } } } static struct istream_snapshot * i_stream_chain_snapshot(struct istream_private *stream, struct istream_snapshot *prev_snapshot) { if (stream->buffer == stream->w_buffer) { /* Two or more istreams have been combined. Snapshot the w_buffer's contents that contains their data. */ i_assert(stream->memarea != NULL); return i_stream_default_snapshot(stream, prev_snapshot); } /* Individual istreams are being read. Snapshot the istream directly. */ struct chain_istream *cstream = container_of(stream, struct chain_istream, istream); struct istream_chain_link *link = cstream->chain.head; if (link == NULL || link->stream == NULL) return prev_snapshot; struct istream_private *_link_stream = link->stream->real_stream; struct istream_snapshot *snapshot = i_new(struct istream_snapshot, 1); snapshot->prev_snapshot = _link_stream->snapshot(_link_stream, prev_snapshot); if (snapshot->prev_snapshot == prev_snapshot) { /* The link stream didn't implement snapshotting in any way. This could cause trouble if the link stream is freed while it's still referred to in this snapshot. Fix this by referencing the link istream. Normally avoid doing this, since the extra references can cause unexpected problems. */ snapshot->istream = link->stream; i_stream_ref(snapshot->istream); } return snapshot; } struct istream *i_stream_create_chain(struct istream_chain **chain_r, size_t max_buffer_size) { struct chain_istream *cstream; cstream = i_new(struct chain_istream, 1); cstream->chain.stream = cstream; cstream->istream.max_buffer_size = max_buffer_size; cstream->istream.iostream.close = i_stream_chain_close; cstream->istream.iostream.destroy = i_stream_chain_destroy; cstream->istream.iostream.set_max_buffer_size = i_stream_chain_set_max_buffer_size; cstream->istream.read = i_stream_chain_read; cstream->istream.snapshot = i_stream_chain_snapshot; cstream->istream.istream.readable_fd = FALSE; cstream->istream.istream.blocking = FALSE; cstream->istream.istream.seekable = FALSE; *chain_r = &cstream->chain; return i_stream_create(&cstream->istream, NULL, -1, 0); } dovecot-2.3.21.1/src/lib/test-byteorder.c0000644000000000000000000001352614656633576015001 00000000000000/* * Copyright (c) 2016-2017 Josef 'Jeff' Sipek * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "test-lib.h" #include "byteorder.h" struct bswap_run { uint64_t in; uint8_t out8; uint16_t out16; uint32_t out32; uint64_t out64; }; static const struct bswap_run runs[] = { { .in = 0, .out8 = 0, .out16 = 0, .out32 = 0, .out64 = 0, }, { .in = 0xffffffffffffffff, .out8 = 0xff, .out16 = 0xffff, .out32 = 0xffffffff, .out64 = 0xffffffffffffffff, }, { .in = 0x123456789abcdef0, .out8 = 0xf0, .out16 = 0xf0de, .out32 = 0xf0debc9a, .out64 = 0xf0debc9a78563412, }, { .in = 0x8080808080808080, .out8 = 0x80, .out16 = 0x8080, .out32 = 0x80808080, .out64 = 0x8080808080808080, }, }; #define CHECK(iter, size, in, exp) \ do { \ uint##size##_t got = i_bswap_##size(in); \ \ test_begin(t_strdup_printf("byteorder - bswap " \ "(size:%-2u iter:%u)", \ size, iter)); \ test_assert(got == exp); \ test_end(); \ } while (0) static void __test(int iter, const struct bswap_run *run) { CHECK(iter, 8, run->in & 0xff, run->out8); CHECK(iter, 16, run->in & 0xffff, run->out16); CHECK(iter, 32, run->in & 0xffffffff, run->out32); CHECK(iter, 64, run->in, run->out64); } static void test_bswap(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(runs) ; i++) __test(i, &runs[i]); } struct unaligned_run { uint8_t in[8]; /* outputs */ uint8_t be8; uint16_t be16; uint32_t be32; uint64_t be64; uint8_t le8; uint16_t le16; uint32_t le32; uint64_t le64; #ifdef WORDS_BIGENDIAN #define cpu8 be8 #define cpu16 be16 #define cpu32 be32 #define cpu64 be64 #else #define cpu8 le8 #define cpu16 le16 #define cpu32 le32 #define cpu64 le64 #endif }; static const struct unaligned_run uruns[] = { { .in = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, .be8 = 0, .be16 = 0, .be32 = 0, .be64 = 0, .le8 = 0, .le16 = 0, .le32 = 0, .le64 = 0, }, { .in = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }, .be8 = 0xff, .be16 = 0xffff, .be32 = 0xffffffff, .be64 = 0xffffffffffffffff, .le8 = 0xff, .le16 = 0xffff, .le32 = 0xffffffff, .le64 = 0xffffffffffffffff, }, { .in = { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, }, .be8 = 0x12, .be16 = 0x1234, .be32 = 0x12345678, .be64 = 0x123456789abcdef0, .le8 = 0x12, .le16 = 0x3412, .le32 = 0x78563412, .le64 = 0xf0debc9a78563412, }, { .in = { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, }, .be8 = 0x80, .be16 = 0x8080, .be32 = 0x80808080, .be64 = 0x8080808080808080, .le8 = 0x80, .le16 = 0x8080, .le32 = 0x80808080, .le64 = 0x8080808080808080, }, }; #define __CHECK_READ(iter, size, pfx, in, fxn, exp) \ do { \ uint##size##_t got = fxn(in); \ \ test_begin(t_strdup_printf("byteorder - unaligned read "\ "(%-3s size:%-2u iter:%u)", \ pfx, size, iter)); \ test_assert(got == exp); \ test_end(); \ } while (0) #define CHECK_READ(iter, size, in, be_exp, le_exp, cpu_exp) \ do { \ __CHECK_READ(iter, size, "BE", in, \ be##size##_to_cpu_unaligned, be_exp); \ __CHECK_READ(iter, size, "LE", in, \ le##size##_to_cpu_unaligned, le_exp); \ __CHECK_READ(iter, size, "CPU", in, \ cpu##size##_to_cpu_unaligned, cpu_exp); \ } while (0) static void __test_read(int iter, const struct unaligned_run *run) { CHECK_READ(iter, 8, run->in, run->be8, run->le8, run->cpu8); CHECK_READ(iter, 16, run->in, run->be16, run->le16, run->cpu16); CHECK_READ(iter, 32, run->in, run->be32, run->le32, run->cpu32); CHECK_READ(iter, 64, run->in, run->be64, run->le64, run->cpu64); } #define __CHECK_WRITE(iter, size, pfx, in, fxn, exp) \ do { \ uint8_t got[size / 8]; \ \ fxn(in, got); \ \ test_begin(t_strdup_printf("byteorder - unaligned write "\ "(%-3s size:%-2u iter:%u)", \ pfx, size, iter)); \ test_assert(memcmp(got, exp, sizeof(got)) == 0); \ test_end(); \ } while (0) #define CHECK_WRITE(iter, size, out, be_in, le_in) \ do { \ __CHECK_WRITE(iter, size, "BE", be_in, \ cpu##size##_to_be_unaligned, out); \ __CHECK_WRITE(iter, size, "LE", le_in, \ cpu##size##_to_le_unaligned, out); \ } while (0) static void __test_write(int iter, const struct unaligned_run *run) { CHECK_WRITE(iter, 8, run->in, run->be8, run->le8); CHECK_WRITE(iter, 16, run->in, run->be16, run->le16); CHECK_WRITE(iter, 32, run->in, run->be32, run->le32); CHECK_WRITE(iter, 64, run->in, run->be64, run->le64); } static void test_unaligned(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(uruns) ; i++) __test_read(i, &uruns[i]); for (i = 0; i < N_ELEMENTS(uruns) ; i++) __test_write(i, &uruns[i]); } void test_byteorder(void) { test_bswap(); test_unaligned(); } dovecot-2.3.21.1/src/lib/log-throttle.h0000644000000000000000000000225714656633576014455 00000000000000#ifndef LOG_THROTTLE_H #define LOG_THROTTLE_H struct log_throttle_settings { /* Start throttling after we reach this many log events/interval. */ unsigned int throttle_at_max_per_interval; /* Throttling continues until there's only this many or below log events/interval. */ unsigned int unthrottle_at_max_per_interval; /* Interval unit in milliseconds. The throttled-callback is also called at this interval. Default (0) is 1000 milliseconds. */ unsigned int interval_msecs; }; typedef void log_throttle_callback_t(unsigned int new_events_count, void *context); struct log_throttle * log_throttle_init(const struct log_throttle_settings *set, log_throttle_callback_t *callback, void *context); #define log_throttle_init(set, callback, context) \ log_throttle_init(set - \ CALLBACK_TYPECHECK(callback, void (*)(unsigned int, typeof(context))), \ (log_throttle_callback_t *)callback, context) void log_throttle_deinit(struct log_throttle **throttle); /* Increase event count. Returns TRUE if the event should be logged, FALSE if it's throttled. ioloop_timeval is used to determine the current time. */ bool log_throttle_accept(struct log_throttle *throttle); #endif dovecot-2.3.21.1/src/lib/test-lib-signals.c0000644000000000000000000001272014656633576015201 00000000000000/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "time-util.h" #include "ioloop.h" #include "lib-signals.h" #include #include struct test_context_delayed { bool timed_out:1; bool signal_handled:1; }; static void kill_timeout(struct test_context_delayed *tctx ATTR_UNUSED) { if (kill(getpid(), SIGALRM) < 0) i_fatal("Failed to send signal: %m"); } static void test_timeout(struct test_context_delayed *tctx) { tctx->timed_out = TRUE; io_loop_stop(current_ioloop); } static void signal_handler_delayed(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED) { struct test_context_delayed *tctx = (struct test_context_delayed *)context; tctx->signal_handled = TRUE; io_loop_stop(current_ioloop); } static void test_lib_signals_delayed(void) { struct test_context_delayed tctx; struct timeout *to_kill, *to_test; struct ioloop *ioloop; test_begin("lib-signals delayed - init lib-signals first"); i_zero(&tctx); lib_signals_init(); lib_signals_set_handler(SIGALRM, LIBSIG_FLAGS_SAFE | LIBSIG_FLAG_IOLOOP_AUTOMOVE, signal_handler_delayed, &tctx); ioloop = io_loop_create(); to_kill = timeout_add_short(200, kill_timeout, &tctx); to_test = timeout_add_short(400, test_timeout, &tctx); io_loop_run(ioloop); timeout_remove(&to_kill); timeout_remove(&to_test); io_loop_destroy(&ioloop); lib_signals_deinit(); test_assert(!tctx.timed_out); test_assert(tctx.signal_handled); test_end(); test_begin("lib-signals delayed - init ioloop first"); i_zero(&tctx); ioloop = io_loop_create(); lib_signals_init(); lib_signals_set_handler(SIGALRM, LIBSIG_FLAGS_SAFE | LIBSIG_FLAG_IOLOOP_AUTOMOVE, signal_handler_delayed, &tctx); to_kill = timeout_add_short(200, kill_timeout, &tctx); to_test = timeout_add_short(400, test_timeout, &tctx); io_loop_run(ioloop); timeout_remove(&to_kill); timeout_remove(&to_test); lib_signals_deinit(); io_loop_destroy(&ioloop); test_assert(!tctx.timed_out); test_assert(tctx.signal_handled); test_end(); } static void test_lib_signals_delayed_nested_ioloop(void) { struct test_context_delayed tctx; struct timeout *to_kill, *to_test; struct ioloop *ioloop1, *ioloop2; test_begin("lib-signals delayed in nested ioloop"); i_zero(&tctx); lib_signals_init(); lib_signals_set_handler(SIGALRM, LIBSIG_FLAGS_SAFE | LIBSIG_FLAG_IOLOOP_AUTOMOVE, signal_handler_delayed, &tctx); /* briefly run outer ioloop */ ioloop1 = io_loop_create(); to_test = timeout_add_short(100, test_timeout, &tctx); io_loop_run(ioloop1); timeout_remove(&to_test); test_assert(tctx.timed_out); test_assert(!tctx.signal_handled); tctx.timed_out = FALSE; /* run inner ioloop, which triggers the signal */ ioloop2 = io_loop_create(); to_kill = timeout_add_short(200, kill_timeout, &tctx); to_test = timeout_add_short(400, test_timeout, &tctx); io_loop_run(ioloop2); timeout_remove(&to_kill); timeout_remove(&to_test); io_loop_destroy(&ioloop2); io_loop_destroy(&ioloop1); lib_signals_deinit(); test_assert(!tctx.timed_out); test_assert(tctx.signal_handled); test_end(); } static void test_lib_signals_delayed_no_ioloop_automove(void) { struct test_context_delayed tctx; struct timeout *to_kill, *to_test; struct ioloop *ioloop1, *ioloop2; test_begin("lib-signals delayed with NO_IOLOOP_AUTOMOVE - unmoved"); i_zero(&tctx); ioloop1 = io_loop_create(); lib_signals_init(); lib_signals_set_handler(SIGALRM, LIBSIG_FLAGS_SAFE, signal_handler_delayed, &tctx); /* briefly run outer ioloop */ to_test = timeout_add_short(100, test_timeout, &tctx); io_loop_run(ioloop1); timeout_remove(&to_test); test_assert(tctx.timed_out); test_assert(!tctx.signal_handled); tctx.timed_out = FALSE; /* run inner ioloop, which triggers the signal but musn't handle it */ ioloop2 = io_loop_create(); to_kill = timeout_add_short(200, kill_timeout, &tctx); to_test = timeout_add_short(400, test_timeout, &tctx); io_loop_run(ioloop2); test_assert(tctx.timed_out); test_assert(!tctx.signal_handled); tctx.timed_out = FALSE; timeout_remove(&to_kill); timeout_remove(&to_test); io_loop_destroy(&ioloop2); /* run outer ioloop once more */ to_test = timeout_add_short(100, test_timeout, &tctx); io_loop_run(ioloop1); timeout_remove(&to_test); lib_signals_deinit(); io_loop_destroy(&ioloop1); test_assert(!tctx.timed_out); test_assert(tctx.signal_handled); test_end(); test_begin("lib-signals delayed with NO_IOLOOP_AUTOMOVE - moved"); i_zero(&tctx); ioloop1 = io_loop_create(); lib_signals_init(); lib_signals_set_handler(SIGALRM, LIBSIG_FLAGS_SAFE, signal_handler_delayed, &tctx); /* briefly run outer ioloop */ to_test = timeout_add_short(100, test_timeout, &tctx); io_loop_run(ioloop1); timeout_remove(&to_test); test_assert(tctx.timed_out); test_assert(!tctx.signal_handled); tctx.timed_out = FALSE; /* run inner ioloop, which triggers the signal */ ioloop2 = io_loop_create(); lib_signals_switch_ioloop(SIGALRM, signal_handler_delayed, &tctx); to_kill = timeout_add_short(200, kill_timeout, &tctx); to_test = timeout_add_short(400, test_timeout, &tctx); io_loop_run(ioloop2); test_assert(!tctx.timed_out); test_assert(tctx.signal_handled); timeout_remove(&to_kill); timeout_remove(&to_test); io_loop_destroy(&ioloop2); lib_signals_deinit(); io_loop_destroy(&ioloop1); test_end(); } void test_lib_signals(void) { test_lib_signals_delayed(); test_lib_signals_delayed_nested_ioloop(); test_lib_signals_delayed_no_ioloop_automove(); } dovecot-2.3.21.1/src/lib/sendfile-util.c0000644000000000000000000000620014656633576014560 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ /* kludge a bit to remove _FILE_OFFSET_BITS definition from config.h. It's required to be able to include sys/sendfile.h with Linux. */ #include "config.h" #undef HAVE_CONFIG_H #ifdef HAVE_LINUX_SENDFILE # undef _FILE_OFFSET_BITS #endif #include "lib.h" #include "sendfile-util.h" #ifdef HAVE_LINUX_SENDFILE #include ssize_t safe_sendfile(int out_fd, int in_fd, uoff_t *offset, size_t count) { /* REMEMBER: uoff_t and off_t may not be of same size. */ off_t safe_offset; ssize_t ret; i_assert(count > 0); /* make sure given offset fits into off_t */ if (sizeof(off_t) * CHAR_BIT == 32) { /* 32bit off_t */ if (*offset >= 2147483647L) { errno = EINVAL; return -1; } if (count > 2147483647L - *offset) count = 2147483647L - *offset; } else { /* they're most likely the same size. if not, fix this code later */ i_assert(sizeof(off_t) == sizeof(uoff_t)); if (*offset >= OFF_T_MAX) { errno = EINVAL; return -1; } if (count > OFF_T_MAX - *offset) count = OFF_T_MAX - *offset; } safe_offset = (off_t)*offset; ret = sendfile(out_fd, in_fd, &safe_offset, count); /* ret=0 : trying to read past EOF */ *offset = (uoff_t)safe_offset; return ret; } #elif defined(HAVE_FREEBSD_SENDFILE) #include #include ssize_t safe_sendfile(int out_fd, int in_fd, uoff_t *offset, size_t count) { struct sf_hdtr hdtr; off_t sbytes; int ret; /* if count=0 is passed to sendfile(), it sends everything from in_fd until EOF. We don't want that. */ i_assert(count > 0); i_assert(count <= SSIZE_T_MAX); i_zero(&hdtr); ret = sendfile(in_fd, out_fd, *offset, count, &hdtr, &sbytes, 0); *offset += sbytes; if (ret == 0 || (ret < 0 && errno == EAGAIN && sbytes > 0)) return (ssize_t)sbytes; else { if (errno == ENOTSOCK) { /* out_fd wasn't a socket. behave as if sendfile() wasn't supported at all. */ errno = EINVAL; } return -1; } } #elif defined (HAVE_SOLARIS_SENDFILE) #include #include "net.h" ssize_t safe_sendfile(int out_fd, int in_fd, uoff_t *offset, size_t count) { ssize_t ret; off_t s_offset; i_assert(count > 0); i_assert(count <= SSIZE_T_MAX); /* NOTE: if outfd is not a socket, some Solaris versions will kernel panic */ s_offset = (off_t)*offset; ret = sendfile(out_fd, in_fd, &s_offset, count); if (ret < 0) { /* if remote is gone, EPIPE is returned */ if (errno == EINVAL) { /* most likely trying to read past EOF */ ret = 0; } else if (errno == EAFNOSUPPORT || errno == EOPNOTSUPP) { /* not supported, return Linux-like EINVAL so caller sees only consistent errnos. */ errno = EINVAL; } else if (s_offset != (off_t)*offset) { /* some data was sent, return it */ i_assert(s_offset > (off_t)*offset); ret = s_offset - (off_t)*offset; } } *offset = (uoff_t)s_offset; i_assert(ret < 0 || (size_t)ret <= count); return ret; } #else ssize_t safe_sendfile(int out_fd ATTR_UNUSED, int in_fd ATTR_UNUSED, uoff_t *offset ATTR_UNUSED, size_t count ATTR_UNUSED) { errno = EINVAL; return -1; } #endif dovecot-2.3.21.1/src/lib/istream-private.h0000644000000000000000000001236714656633576015150 00000000000000#ifndef ISTREAM_PRIVATE_H #define ISTREAM_PRIVATE_H #include "istream.h" #include "iostream-private.h" #define I_STREAM_MIN_SIZE IO_BLOCK_SIZE struct io; struct istream_private { /* inheritance: */ struct iostream_private iostream; /* methods: */ ssize_t (*read)(struct istream_private *stream); void (*seek)(struct istream_private *stream, uoff_t v_offset, bool mark); void (*sync)(struct istream_private *stream); int (*stat)(struct istream_private *stream, bool exact); int (*get_size)(struct istream_private *stream, bool exact, uoff_t *size_r); void (*switch_ioloop_to)(struct istream_private *stream, struct ioloop *ioloop); struct istream_snapshot * (*snapshot)(struct istream_private *stream, struct istream_snapshot *prev_snapshot); /* data: */ struct istream istream; int fd; uoff_t start_offset; struct stat statbuf; /* added by io_add_istream() -> i_stream_set_io() */ struct io *io; const unsigned char *buffer; unsigned char *w_buffer; /* may be NULL */ size_t buffer_size, max_buffer_size, init_buffer_size, data_limit; size_t skip, pos; /* If seeking backwards within the buffer, the next read() will return again pos..high_pos */ size_t high_pos; struct istream *parent; /* for filter streams */ uoff_t parent_start_offset; /* Initially UOFF_T_MAX. Otherwise it's the exact known stream size, which can be used by stat() / get_size(). */ uoff_t cached_stream_size; /* parent stream's expected offset is kept here. i_stream_read() always seeks parent stream to here before calling read(). */ uoff_t parent_expected_offset; struct memarea *memarea; struct istream_snapshot *prev_snapshot; /* increased every time the stream is changed (e.g. seek, read). this way streams can check if their parent streams have been accessed behind them. */ unsigned int access_counter; /* Timestamp when read() last returned >0 */ struct timeval last_read_timeval; string_t *line_str; /* for i_stream_next_line() if w_buffer == NULL */ bool line_crlf:1; bool return_nolf_line:1; bool stream_size_passthrough:1; /* stream is parent's size */ bool nonpersistent_buffers:1; bool io_pending:1; }; struct istream_snapshot { struct istream_snapshot *prev_snapshot; struct memarea *old_memarea; struct istream *istream; void (*free)(struct istream_snapshot *snapshot); }; enum istream_create_flag { /* The stream guarantees that the buffer pointer stays valid when it returns <= 0. */ ISTREAM_CREATE_FLAG_NOOP_SNAPSHOT = 0x01, }; struct istream * ATTR_NOWARN_UNUSED_RESULT i_stream_create(struct istream_private *stream, struct istream *parent, int fd, enum istream_create_flag flags) ATTR_NULL(2); /* Initialize parent lazily after i_stream_create() has already been called. */ void i_stream_init_parent(struct istream_private *_stream, struct istream *parent); void i_stream_compress(struct istream_private *stream); void i_stream_grow_buffer(struct istream_private *stream, size_t bytes); bool ATTR_NOWARN_UNUSED_RESULT i_stream_try_alloc(struct istream_private *stream, size_t wanted_size, size_t *size_r); /* Like i_stream_try_alloc(), but compress only if it's the only way to get more space. This can be useful when stream is marked with i_stream_seek_mark() */ bool ATTR_NOWARN_UNUSED_RESULT i_stream_try_alloc_avoid_compress(struct istream_private *stream, size_t wanted_size, size_t *size_r); void *i_stream_alloc(struct istream_private *stream, size_t size); /* Detach istream from its current memarea. This unreferences the memarea and resets the w_buffer to empty. This can be used to make sure i_stream_*alloc() won't return a pointer to memory referenced to in a snapshot. */ void i_stream_memarea_detach(struct istream_private *stream); /* Free memory allocated by i_stream_*alloc() */ void i_stream_free_buffer(struct istream_private *stream); ssize_t i_stream_read_copy_from_parent(struct istream *istream); void i_stream_default_seek_nonseekable(struct istream_private *stream, uoff_t v_offset, bool mark); /* Returns FALSE if seeking must be done by starting from the beginning. The caller is then expected to reset the stream and call this function again, which should work then. If TRUE is returned, the seek was either successfully done or stream_errno is set. */ bool i_stream_nonseekable_try_seek(struct istream_private *stream, uoff_t v_offset); /* Default snapshot handling: use memarea if it exists, otherwise snapshot parent stream. */ struct istream_snapshot * i_stream_default_snapshot(struct istream_private *stream, struct istream_snapshot *prev_snapshot); void i_stream_snapshot_free(struct istream_snapshot **snapshot); struct istream *i_stream_get_root_io(struct istream *stream); void i_stream_set_io(struct istream *stream, struct io *io); void i_stream_unset_io(struct istream *stream, struct io *io); /* Filter istreams should be calling this instead of i_stream_read() to avoid unnecessarily referencing memareas. After this call any pointers to the parent istream's content must be considered as potentially invalid and have to be updated, even if the return value is <=0. */ ssize_t i_stream_read_memarea(struct istream *stream); int i_stream_read_more_memarea(struct istream *stream, const unsigned char **data_r, size_t *size_r); #endif dovecot-2.3.21.1/src/lib/test-istream-base64-encoder.c0000644000000000000000000001522214656633576017140 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str.h" #include "istream-private.h" #include "istream-base64.h" struct base64_istream_test { const char *input; unsigned int chars_per_line; bool crlf; const char *output; }; static const struct base64_istream_test base64_tests[] = { { "", 80, FALSE, "" }, { "1", 80, FALSE, "MQ==" }, { "12", 80, FALSE, "MTI=" }, { "123", 80, FALSE, "MTIz" }, { "1234", 80, FALSE, "MTIzNA==" }, { "12345", 80, FALSE, "MTIzNDU=" }, { "hello world", 80, FALSE, "aGVsbG8gd29ybGQ=" }, { "hello world", 4, FALSE, "aGVs\nbG8g\nd29y\nbGQ=" }, { "hello world", 4, TRUE, "aGVs\r\nbG8g\r\nd29y\r\nbGQ=" }, { "hello worlds", 80, FALSE, "aGVsbG8gd29ybGRz" }, { "hello worlds", 4, FALSE, "aGVs\nbG8g\nd29y\nbGRz" }, { "hello worlds", 4, TRUE, "aGVs\r\nbG8g\r\nd29y\r\nbGRz" }, { "hell world", 80, FALSE, "aGVsbCB3b3JsZA==" }, { "hell world", 4, FALSE, "aGVs\nbCB3\nb3Js\nZA==" }, { "hell world", 4, TRUE, "aGVs\r\nbCB3\r\nb3Js\r\nZA==" }, { "hello to the world!!", 80, FALSE, "aGVsbG8gdG8gdGhlIHdvcmxkISE=" }, { "hello to the world!!", 8, FALSE, "aGVsbG8g\ndG8gdGhl\nIHdvcmxk\nISE=" }, { "hello to the world!!", 8, TRUE, "aGVsbG8g\r\ndG8gdGhl\r\nIHdvcmxk\r\nISE=" }, { "\xd0\x93\xd0\xbe\xd0\xb2\xd0\xbe\xd1\x80\xd1\x8f\xcc" "\x81\xd1\x82\x2c\x20\xd1\x87\xd1\x82\xd0\xbe\x20\xd0" "\xba\xd1\x83\xd1\x80\x20\xd0\xb4\xd0\xbe\xd1\x8f\xcc" "\x81\xd1\x82\x2e", 80, FALSE, "0JPQvtCy0L7RgNGPzIHRgiwg0YfRgtC+INC60YPRgCDQtNC+0Y/MgdGCLg==" }, }; static const struct base64_istream_test base64url_tests[] = { { "", 80, FALSE, "" }, { "1", 80, FALSE, "MQ==" }, { "12", 80, FALSE, "MTI=" }, { "123", 80, FALSE, "MTIz" }, { "1234", 80, FALSE, "MTIzNA==" }, { "12345", 80, FALSE, "MTIzNDU=" }, { "hello world", 80, FALSE, "aGVsbG8gd29ybGQ=" }, { "hello world", 4, FALSE, "aGVs\nbG8g\nd29y\nbGQ=" }, { "hello world", 4, TRUE, "aGVs\r\nbG8g\r\nd29y\r\nbGQ=" }, { "hello worlds", 80, FALSE, "aGVsbG8gd29ybGRz" }, { "hello worlds", 4, FALSE, "aGVs\nbG8g\nd29y\nbGRz" }, { "hello worlds", 4, TRUE, "aGVs\r\nbG8g\r\nd29y\r\nbGRz" }, { "hell world", 80, FALSE, "aGVsbCB3b3JsZA==" }, { "hell world", 4, FALSE, "aGVs\nbCB3\nb3Js\nZA==" }, { "hell world", 4, TRUE, "aGVs\r\nbCB3\r\nb3Js\r\nZA==" }, { "hello to the world!!", 80, FALSE, "aGVsbG8gdG8gdGhlIHdvcmxkISE=" }, { "hello to the world!!", 8, FALSE, "aGVsbG8g\ndG8gdGhl\nIHdvcmxk\nISE=" }, { "hello to the world!!", 8, TRUE, "aGVsbG8g\r\ndG8gdGhl\r\nIHdvcmxk\r\nISE=" }, { "\xd0\x93\xd0\xbe\xd0\xb2\xd0\xbe\xd1\x80\xd1\x8f\xcc" "\x81\xd1\x82\x2c\x20\xd1\x87\xd1\x82\xd0\xbe\x20\xd0" "\xba\xd1\x83\xd1\x80\x20\xd0\xb4\xd0\xbe\xd1\x8f\xcc" "\x81\xd1\x82\x2e", 80, FALSE, "0JPQvtCy0L7RgNGPzIHRgiwg0YfRgtC-INC60YPRgCDQtNC-0Y_MgdGCLg==" }, }; static const char *hello = "hello world"; static void encode_test(unsigned int text_len, struct istream *input, struct istream *input_data, const char *output) { unsigned int i; const unsigned char *data; uoff_t stream_size; size_t size; ssize_t ret; for (i = 1; i <= text_len; i++) { test_istream_set_size(input_data, i); while ((ret = i_stream_read(input)) > 0) ; test_assert(ret == 0); } test_istream_set_allow_eof(input_data, TRUE); while ((ret = i_stream_read(input)) > 0) ; test_assert(ret == -1); data = i_stream_get_data(input, &size); test_assert(size == strlen(output) && memcmp(data, output, size) == 0); ret = i_stream_get_size(input, TRUE, &stream_size); test_assert(ret > 0); test_assert(size == stream_size); } static void encode_base64_test(const char *text, unsigned int chars_per_line, bool crlf, const char *output) { unsigned int text_len = strlen(text); struct istream *input, *input_data; input_data = test_istream_create_data(text, text_len); test_istream_set_allow_eof(input_data, FALSE); input = i_stream_create_base64_encoder(input_data, chars_per_line, crlf); encode_test(text_len, input, input_data, output); i_stream_unref(&input); i_stream_unref(&input_data); } static void encode_base64url_test(const char *text, unsigned int chars_per_line, bool crlf, const char *output) { unsigned int text_len = strlen(text); struct istream *input, *input_data; input_data = test_istream_create_data(text, text_len); test_istream_set_allow_eof(input_data, FALSE); input = i_stream_create_base64url_encoder(input_data, chars_per_line, crlf); encode_test(text_len, input, input_data, output); i_stream_unref(&input); i_stream_unref(&input_data); } static void test_encoder_seek(struct istream *input, const char *textout) { unsigned int offset, len = strlen(textout); const unsigned char *data; size_t size; ssize_t ret; while (i_stream_read(input) > 0) ; i_stream_skip(input, i_stream_get_data_size(input)); for (offset = 0; offset < len; offset++) { i_stream_seek(input, offset); while ((ret = i_stream_read(input)) > 0) ; test_assert(ret == -1); data = i_stream_get_data(input, &size); test_assert(size == len-offset); test_assert(memcmp(data, textout+offset, size) == 0); i_stream_skip(input, size); } } static void test_istream_base64_encoder_seek(const char *textin, const char *textout) { struct istream *input, *input_data; input_data = i_stream_create_from_data(textin, strlen(textin)); input = i_stream_create_base64_encoder(input_data, 4, TRUE); test_encoder_seek(input, textout); i_stream_unref(&input); i_stream_unref(&input_data); } static void test_istream_base64url_encoder_seek(const char *textin, const char *textout) { struct istream *input, *input_data; input_data = i_stream_create_from_data(textin, strlen(textin)); input = i_stream_create_base64url_encoder(input_data, 4, TRUE); test_encoder_seek(input, textout); i_stream_unref(&input); i_stream_unref(&input_data); } void test_istream_base64_encoder(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(base64_tests); i++) { const struct base64_istream_test *test = &base64_tests[i]; test_begin(t_strdup_printf( "istream base64 encoder %u", i+1)); encode_base64_test(test->input, test->chars_per_line, test->crlf, test->output); test_end(); } for (i = 0; i < N_ELEMENTS(base64url_tests); i++) { const struct base64_istream_test *test = &base64url_tests[i]; test_begin(t_strdup_printf( "istream base64url encoder %u", i+1)); encode_base64url_test(test->input, test->chars_per_line, test->crlf, test->output); test_end(); } test_begin("istream base64 encoder seek"); test_istream_base64_encoder_seek( hello, "aGVs\r\nbG8g\r\nd29y\r\nbGQ="); test_end(); test_begin("istream base64url encoder seek"); test_istream_base64url_encoder_seek( hello, "aGVs\r\nbG8g\r\nd29y\r\nbGQ="); test_end(); } dovecot-2.3.21.1/src/lib/istream-multiplex.c0000644000000000000000000002036514656633576015511 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "array.h" #include "istream-private.h" #include "istream-multiplex.h" /* all multiplex packets are [1 byte cid][4 byte length][data] */ struct multiplex_istream; struct multiplex_ichannel { struct istream_private istream; struct multiplex_istream *mstream; uint8_t cid; size_t pending_pos; bool closed:1; }; struct multiplex_istream { struct istream *parent; /* channel 0 is main channel */ uint8_t cur_channel; unsigned int remain; size_t bufsize; ARRAY(struct multiplex_ichannel *) channels; bool blocking:1; }; static ssize_t i_stream_multiplex_ichannel_read(struct istream_private *stream); static struct multiplex_ichannel * get_channel(struct multiplex_istream *mstream, uint8_t cid) { struct multiplex_ichannel *channel; i_assert(mstream != NULL); array_foreach_elem(&mstream->channels, channel) { if (channel != NULL && channel->cid == cid) return channel; } return NULL; } static void propagate_error(struct multiplex_istream *mstream, int stream_errno) { struct multiplex_ichannel *channel; array_foreach_elem(&mstream->channels, channel) if (channel != NULL) channel->istream.istream.stream_errno = stream_errno; } static void propagate_eof(struct multiplex_istream *mstream) { struct multiplex_ichannel *channel; array_foreach_elem(&mstream->channels, channel) { if (channel == NULL) continue; channel->istream.istream.eof = TRUE; if (mstream->remain > 0) { channel->istream.istream.stream_errno = EPIPE; io_stream_set_error(&channel->istream.iostream, "Unexpected EOF - %u bytes remaining in packet", mstream->remain); } } } static ssize_t i_stream_multiplex_read(struct multiplex_istream *mstream, struct multiplex_ichannel *req_channel) { const unsigned char *data; size_t len = 0, used, wanted, avail; ssize_t ret, got = 0; if (mstream->parent == NULL) { req_channel->istream.istream.eof = TRUE; return -1; } (void)i_stream_get_data(mstream->parent, &len); if (len == 0 && mstream->parent->closed) { req_channel->istream.istream.eof = TRUE; return -1; } if (((mstream->remain > 0 && len == 0) || (mstream->remain == 0 && len < 5)) && (ret = i_stream_read_memarea(mstream->parent)) <= 0) { propagate_error(mstream, mstream->parent->stream_errno); if (mstream->parent->eof) propagate_eof(mstream); return ret; } for(;;) { data = i_stream_get_data(mstream->parent, &len); if (len == 0) { if (got == 0 && mstream->blocking) { /* can't return 0 with blocking istreams, so try again from the beginning. */ return i_stream_multiplex_read(mstream, req_channel); } break; } if (mstream->remain > 0) { struct multiplex_ichannel *channel = get_channel(mstream, mstream->cur_channel); wanted = I_MIN(len, mstream->remain); /* is it open? */ if (channel != NULL && !channel->closed) { struct istream_private *stream = &channel->istream; stream->pos += channel->pending_pos; bool alloc_ret = i_stream_try_alloc(stream, wanted, &avail); stream->pos -= channel->pending_pos; if (!alloc_ret) { i_stream_set_input_pending(&stream->istream, TRUE); if (channel->cid != req_channel->cid) return 0; if (got > 0) break; return -2; } used = I_MIN(wanted, avail); /* dump into buffer */ if (channel->cid != req_channel->cid) { i_assert(stream->pos + channel->pending_pos + used <= stream->buffer_size); memcpy(stream->w_buffer + stream->pos + channel->pending_pos, data, used); channel->pending_pos += used; i_stream_set_input_pending(&stream->istream, TRUE); } else { i_assert(stream->pos + used <= stream->buffer_size); memcpy(stream->w_buffer + stream->pos, data, used); stream->pos += used; got += used; } } else { used = wanted; } mstream->remain -= used; i_stream_skip(mstream->parent, used); /* see if there is more to read */ continue; } if (mstream->remain == 0) { /* need more data */ if (len < 5) { ret = i_stream_multiplex_ichannel_read(&req_channel->istream); if (ret > 0) got += ret; break; } /* channel ID */ mstream->cur_channel = data[0]; /* data length */ mstream->remain = be32_to_cpu_unaligned(data+1); i_stream_skip(mstream->parent, 5); } } propagate_error(mstream, mstream->parent->stream_errno); if (mstream->parent->eof) propagate_eof(mstream); return got; } static ssize_t i_stream_multiplex_ichannel_read(struct istream_private *stream) { struct multiplex_ichannel *channel = container_of(stream, struct multiplex_ichannel, istream); /* if previous multiplex read dumped data for us actually serve it here. */ if (channel->pending_pos > 0) { ssize_t ret = channel->pending_pos; stream->pos += channel->pending_pos; channel->pending_pos = 0; return ret; } return i_stream_multiplex_read(channel->mstream, channel); } static void i_stream_multiplex_ichannel_switch_ioloop_to(struct istream_private *stream, struct ioloop *ioloop) { struct multiplex_ichannel *channel = container_of(stream, struct multiplex_ichannel, istream); i_stream_switch_ioloop_to(channel->mstream->parent, ioloop); } static void i_stream_multiplex_ichannel_close(struct iostream_private *stream, bool close_parent) { struct multiplex_ichannel *arr_channel; struct multiplex_ichannel *channel = container_of(stream, struct multiplex_ichannel, istream.iostream); channel->closed = TRUE; if (close_parent) { array_foreach_elem(&channel->mstream->channels, arr_channel) if (arr_channel != NULL && !arr_channel->closed) return; i_stream_close(channel->mstream->parent); } } static void i_stream_multiplex_try_destroy(struct multiplex_istream *mstream) { struct multiplex_ichannel *channel; /* can't do anything until they are all closed */ array_foreach_elem(&mstream->channels, channel) if (channel != NULL) return; i_stream_unref(&mstream->parent); array_free(&mstream->channels); i_free(mstream); } static void i_stream_multiplex_ichannel_destroy(struct iostream_private *stream) { struct multiplex_ichannel **channelp; struct multiplex_ichannel *channel = container_of(stream, struct multiplex_ichannel, istream.iostream); i_stream_multiplex_ichannel_close(stream, TRUE); i_stream_free_buffer(&channel->istream); array_foreach_modifiable(&channel->mstream->channels, channelp) { if (*channelp == channel) { *channelp = NULL; break; } } i_stream_multiplex_try_destroy(channel->mstream); } static struct istream * i_stream_add_channel_real(struct multiplex_istream *mstream, uint8_t cid) { struct multiplex_ichannel *channel = i_new(struct multiplex_ichannel, 1); channel->cid = cid; channel->mstream = mstream; channel->istream.read = i_stream_multiplex_ichannel_read; channel->istream.switch_ioloop_to = i_stream_multiplex_ichannel_switch_ioloop_to; channel->istream.iostream.close = i_stream_multiplex_ichannel_close; channel->istream.iostream.destroy = i_stream_multiplex_ichannel_destroy; channel->istream.max_buffer_size = mstream->bufsize; channel->istream.istream.blocking = mstream->blocking; if (cid == 0) channel->istream.fd = i_stream_get_fd(mstream->parent); else channel->istream.fd = -1; array_push_back(&channel->mstream->channels, &channel); return i_stream_create(&channel->istream, NULL, channel->istream.fd, 0); } struct istream *i_stream_multiplex_add_channel(struct istream *stream, uint8_t cid) { struct multiplex_ichannel *chan = container_of(stream->real_stream, struct multiplex_ichannel, istream); i_assert(get_channel(chan->mstream, cid) == NULL); return i_stream_add_channel_real(chan->mstream, cid); } struct istream *i_stream_create_multiplex(struct istream *parent, size_t bufsize) { struct multiplex_istream *mstream; mstream = i_new(struct multiplex_istream, 1); mstream->parent = parent; mstream->bufsize = bufsize; mstream->blocking = parent->blocking; i_array_init(&mstream->channels, 8); i_stream_ref(parent); return i_stream_add_channel_real(mstream, 0); } uint8_t i_stream_multiplex_get_channel_id(struct istream *stream) { struct multiplex_ichannel *channel = container_of(stream->real_stream, struct multiplex_ichannel, istream); return channel->cid; } dovecot-2.3.21.1/src/lib/Makefile.in0000644000000000000000000107002014656633611013705 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ pkglibexecdir = $(libexecdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ noinst_PROGRAMS = $(am__EXEEXT_1) subdir = src/lib ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(pkginc_lib_HEADERS) $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__EXEEXT_1 = test-lib$(EXEEXT) PROGRAMS = $(noinst_PROGRAMS) LTLIBRARIES = $(noinst_LTLIBRARIES) am__DEPENDENCIES_1 = liblib_la_DEPENDENCIES = $(am__DEPENDENCIES_1) am_liblib_la_OBJECTS = array.lo aqueue.lo askpass.lo \ backtrace-string.lo base32.lo base64.lo bits.lo \ bsearch-insert-pos.lo buffer.lo buffer-istream.lo \ child-wait.lo compat.lo connection.lo cpu-limit.lo crc32.lo \ data-stack.lo eacces-error.lo env-util.lo event-filter.lo \ event-filter-lexer.lo event-filter-parser.lo event-log.lo \ execv-const.lo failures.lo fd-util.lo fdatasync-path.lo \ fdpass.lo file-cache.lo file-create-locked.lo file-copy.lo \ file-dotlock.lo file-lock.lo file-set-size.lo guid.lo hash.lo \ hash-format.lo hash-method.lo hash2.lo hex-binary.lo \ hex-dec.lo hmac.lo hmac-cram-md5.lo home-expand.lo \ hook-build.lo hostpid.lo imem.lo ipwd.lo iostream.lo \ iostream-pump.lo iostream-proxy.lo iostream-rawlog.lo \ iostream-temp.lo iso8601-date.lo istream.lo \ istream-base64-decoder.lo istream-base64-encoder.lo \ istream-callback.lo istream-chain.lo istream-concat.lo \ istream-crlf.lo istream-data.lo istream-failure-at.lo \ istream-file.lo istream-hash.lo istream-jsonstr.lo \ istream-limit.lo istream-multiplex.lo istream-rawlog.lo \ istream-seekable.lo istream-sized.lo istream-tee.lo \ istream-try.lo istream-timeout.lo istream-unix.lo ioloop.lo \ ioloop-iolist.lo ioloop-notify-none.lo ioloop-notify-fd.lo \ ioloop-notify-inotify.lo ioloop-notify-kqueue.lo \ ioloop-poll.lo ioloop-select.lo ioloop-epoll.lo \ ioloop-kqueue.lo json-parser.lo json-tree.lo lib.lo \ lib-event.lo lib-signals.lo log-throttle.lo md4.lo md5.lo \ memarea.lo mempool.lo mempool-allocfree.lo \ mempool-alloconly.lo mempool-datastack.lo mempool-system.lo \ mempool-unsafe-datastack.lo mkdir-parents.lo mmap-anon.lo \ mmap-util.lo module-dir.lo mountpoint.lo net.lo \ nfs-workarounds.lo numpack.lo ostream.lo ostream-buffer.lo \ ostream-failure-at.lo ostream-file.lo ostream-hash.lo \ ostream-multiplex.lo ostream-null.lo ostream-rawlog.lo \ ostream-unix.lo ostream-wrapper.lo path-util.lo pkcs5.lo \ primes.lo printf-format-fix.lo process-stat.lo \ process-title.lo priorityq.lo randgen.lo rand.lo read-full.lo \ restrict-access.lo restrict-process-size.lo safe-memset.lo \ safe-mkdir.lo safe-mkstemp.lo sendfile-util.lo \ seq-range-array.lo seq-set-builder.lo sha1.lo sha2.lo sha3.lo \ sleep.lo sort.lo stats-dist.lo str.lo str-find.lo \ str-sanitize.lo str-table.lo strescape.lo strfuncs.lo \ strnum.lo time-util.lo unix-socket-create.lo \ unlink-directory.lo unlink-old-files.lo unichar.lo uri-util.lo \ utc-offset.lo utc-mktime.lo var-expand.lo var-expand-if.lo \ wildcard-match.lo write-full.lo liblib_la_OBJECTS = $(am_liblib_la_OBJECTS) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = am_test_lib_OBJECTS = test_lib-test-lib.$(OBJEXT) \ test_lib-test-array.$(OBJEXT) test_lib-test-aqueue.$(OBJEXT) \ test_lib-test-backtrace.$(OBJEXT) \ test_lib-test-base32.$(OBJEXT) test_lib-test-base64.$(OBJEXT) \ test_lib-test-bits.$(OBJEXT) \ test_lib-test-bsearch-insert-pos.$(OBJEXT) \ test_lib-test-buffer.$(OBJEXT) \ test_lib-test-buffer-istream.$(OBJEXT) \ test_lib-test-byteorder.$(OBJEXT) \ test_lib-test-connection.$(OBJEXT) \ test_lib-test-crc32.$(OBJEXT) \ test_lib-test-cpu-limit.$(OBJEXT) \ test_lib-test-data-stack.$(OBJEXT) \ test_lib-test-env-util.$(OBJEXT) \ test_lib-test-event-category-register.$(OBJEXT) \ test_lib-test-event-filter.$(OBJEXT) \ test_lib-test-event-filter-expr.$(OBJEXT) \ test_lib-test-event-filter-merge.$(OBJEXT) \ test_lib-test-event-filter-parser.$(OBJEXT) \ test_lib-test-event-flatten.$(OBJEXT) \ test_lib-test-event-log.$(OBJEXT) \ test_lib-test-failures.$(OBJEXT) \ test_lib-test-fd-util.$(OBJEXT) \ test_lib-test-file-cache.$(OBJEXT) \ test_lib-test-file-create-locked.$(OBJEXT) \ test_lib-test-guid.$(OBJEXT) test_lib-test-hash.$(OBJEXT) \ test_lib-test-hash-format.$(OBJEXT) \ test_lib-test-hash-method.$(OBJEXT) \ test_lib-test-hmac.$(OBJEXT) \ test_lib-test-hex-binary.$(OBJEXT) \ test_lib-test-imem.$(OBJEXT) test_lib-test-ioloop.$(OBJEXT) \ test_lib-test-iso8601-date.$(OBJEXT) \ test_lib-test-iostream-pump.$(OBJEXT) \ test_lib-test-iostream-proxy.$(OBJEXT) \ test_lib-test-iostream-temp.$(OBJEXT) \ test_lib-test-istream.$(OBJEXT) \ test_lib-test-istream-base64-decoder.$(OBJEXT) \ test_lib-test-istream-base64-encoder.$(OBJEXT) \ test_lib-test-istream-chain.$(OBJEXT) \ test_lib-test-istream-concat.$(OBJEXT) \ test_lib-test-istream-crlf.$(OBJEXT) \ test_lib-test-istream-failure-at.$(OBJEXT) \ test_lib-test-istream-jsonstr.$(OBJEXT) \ test_lib-test-istream-multiplex.$(OBJEXT) \ test_lib-test-istream-seekable.$(OBJEXT) \ test_lib-test-istream-sized.$(OBJEXT) \ test_lib-test-istream-tee.$(OBJEXT) \ test_lib-test-istream-try.$(OBJEXT) \ test_lib-test-istream-unix.$(OBJEXT) \ test_lib-test-json-parser.$(OBJEXT) \ test_lib-test-json-tree.$(OBJEXT) \ test_lib-test-lib-event.$(OBJEXT) \ test_lib-test-lib-signals.$(OBJEXT) \ test_lib-test-llist.$(OBJEXT) \ test_lib-test-log-throttle.$(OBJEXT) \ test_lib-test-macros.$(OBJEXT) \ test_lib-test-malloc-overflow.$(OBJEXT) \ test_lib-test-memarea.$(OBJEXT) \ test_lib-test-mempool.$(OBJEXT) \ test_lib-test-mempool-allocfree.$(OBJEXT) \ test_lib-test-mempool-alloconly.$(OBJEXT) \ test_lib-test-pkcs5.$(OBJEXT) test_lib-test-net.$(OBJEXT) \ test_lib-test-numpack.$(OBJEXT) \ test_lib-test-ostream-buffer.$(OBJEXT) \ test_lib-test-ostream-failure-at.$(OBJEXT) \ test_lib-test-ostream-file.$(OBJEXT) \ test_lib-test-ostream-multiplex.$(OBJEXT) \ test_lib-test-multiplex.$(OBJEXT) \ test_lib-test-path-util.$(OBJEXT) \ test_lib-test-primes.$(OBJEXT) \ test_lib-test-printf-format-fix.$(OBJEXT) \ test_lib-test-priorityq.$(OBJEXT) \ test_lib-test-random.$(OBJEXT) \ test_lib-test-seq-range-array.$(OBJEXT) \ test_lib-test-seq-set-builder.$(OBJEXT) \ test_lib-test-stats-dist.$(OBJEXT) test_lib-test-str.$(OBJEXT) \ test_lib-test-strescape.$(OBJEXT) \ test_lib-test-strfuncs.$(OBJEXT) \ test_lib-test-strnum.$(OBJEXT) \ test_lib-test-str-find.$(OBJEXT) \ test_lib-test-str-sanitize.$(OBJEXT) \ test_lib-test-str-table.$(OBJEXT) \ test_lib-test-time-util.$(OBJEXT) \ test_lib-test-unichar.$(OBJEXT) \ test_lib-test-utc-mktime.$(OBJEXT) test_lib-test-uri.$(OBJEXT) \ test_lib-test-var-expand.$(OBJEXT) \ test_lib-test-wildcard-match.$(OBJEXT) test_lib_OBJECTS = $(am_test_lib_OBJECTS) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/aqueue.Plo ./$(DEPDIR)/array.Plo \ ./$(DEPDIR)/askpass.Plo ./$(DEPDIR)/backtrace-string.Plo \ ./$(DEPDIR)/base32.Plo ./$(DEPDIR)/base64.Plo \ ./$(DEPDIR)/bits.Plo ./$(DEPDIR)/bsearch-insert-pos.Plo \ ./$(DEPDIR)/buffer-istream.Plo ./$(DEPDIR)/buffer.Plo \ ./$(DEPDIR)/child-wait.Plo ./$(DEPDIR)/compat.Plo \ ./$(DEPDIR)/connection.Plo ./$(DEPDIR)/cpu-limit.Plo \ ./$(DEPDIR)/crc32.Plo ./$(DEPDIR)/data-stack.Plo \ ./$(DEPDIR)/eacces-error.Plo ./$(DEPDIR)/env-util.Plo \ ./$(DEPDIR)/event-filter-lexer.Plo \ ./$(DEPDIR)/event-filter-parser.Plo \ ./$(DEPDIR)/event-filter.Plo ./$(DEPDIR)/event-log.Plo \ ./$(DEPDIR)/execv-const.Plo ./$(DEPDIR)/failures.Plo \ ./$(DEPDIR)/fd-util.Plo ./$(DEPDIR)/fdatasync-path.Plo \ ./$(DEPDIR)/fdpass.Plo ./$(DEPDIR)/file-cache.Plo \ ./$(DEPDIR)/file-copy.Plo ./$(DEPDIR)/file-create-locked.Plo \ ./$(DEPDIR)/file-dotlock.Plo ./$(DEPDIR)/file-lock.Plo \ ./$(DEPDIR)/file-set-size.Plo ./$(DEPDIR)/guid.Plo \ ./$(DEPDIR)/hash-format.Plo ./$(DEPDIR)/hash-method.Plo \ ./$(DEPDIR)/hash.Plo ./$(DEPDIR)/hash2.Plo \ ./$(DEPDIR)/hex-binary.Plo ./$(DEPDIR)/hex-dec.Plo \ ./$(DEPDIR)/hmac-cram-md5.Plo ./$(DEPDIR)/hmac.Plo \ ./$(DEPDIR)/home-expand.Plo ./$(DEPDIR)/hook-build.Plo \ ./$(DEPDIR)/hostpid.Plo ./$(DEPDIR)/imem.Plo \ ./$(DEPDIR)/ioloop-epoll.Plo ./$(DEPDIR)/ioloop-iolist.Plo \ ./$(DEPDIR)/ioloop-kqueue.Plo ./$(DEPDIR)/ioloop-notify-fd.Plo \ ./$(DEPDIR)/ioloop-notify-inotify.Plo \ ./$(DEPDIR)/ioloop-notify-kqueue.Plo \ ./$(DEPDIR)/ioloop-notify-none.Plo ./$(DEPDIR)/ioloop-poll.Plo \ ./$(DEPDIR)/ioloop-select.Plo ./$(DEPDIR)/ioloop.Plo \ ./$(DEPDIR)/iostream-proxy.Plo ./$(DEPDIR)/iostream-pump.Plo \ ./$(DEPDIR)/iostream-rawlog.Plo ./$(DEPDIR)/iostream-temp.Plo \ ./$(DEPDIR)/iostream.Plo ./$(DEPDIR)/ipwd.Plo \ ./$(DEPDIR)/iso8601-date.Plo \ ./$(DEPDIR)/istream-base64-decoder.Plo \ ./$(DEPDIR)/istream-base64-encoder.Plo \ ./$(DEPDIR)/istream-callback.Plo ./$(DEPDIR)/istream-chain.Plo \ ./$(DEPDIR)/istream-concat.Plo ./$(DEPDIR)/istream-crlf.Plo \ ./$(DEPDIR)/istream-data.Plo \ ./$(DEPDIR)/istream-failure-at.Plo \ ./$(DEPDIR)/istream-file.Plo ./$(DEPDIR)/istream-hash.Plo \ ./$(DEPDIR)/istream-jsonstr.Plo ./$(DEPDIR)/istream-limit.Plo \ ./$(DEPDIR)/istream-multiplex.Plo \ ./$(DEPDIR)/istream-rawlog.Plo \ ./$(DEPDIR)/istream-seekable.Plo ./$(DEPDIR)/istream-sized.Plo \ ./$(DEPDIR)/istream-tee.Plo ./$(DEPDIR)/istream-timeout.Plo \ ./$(DEPDIR)/istream-try.Plo ./$(DEPDIR)/istream-unix.Plo \ ./$(DEPDIR)/istream.Plo ./$(DEPDIR)/json-parser.Plo \ ./$(DEPDIR)/json-tree.Plo ./$(DEPDIR)/lib-event.Plo \ ./$(DEPDIR)/lib-signals.Plo ./$(DEPDIR)/lib.Plo \ ./$(DEPDIR)/log-throttle.Plo ./$(DEPDIR)/md4.Plo \ ./$(DEPDIR)/md5.Plo ./$(DEPDIR)/memarea.Plo \ ./$(DEPDIR)/mempool-allocfree.Plo \ ./$(DEPDIR)/mempool-alloconly.Plo \ ./$(DEPDIR)/mempool-datastack.Plo \ ./$(DEPDIR)/mempool-system.Plo \ ./$(DEPDIR)/mempool-unsafe-datastack.Plo \ ./$(DEPDIR)/mempool.Plo ./$(DEPDIR)/mkdir-parents.Plo \ ./$(DEPDIR)/mmap-anon.Plo ./$(DEPDIR)/mmap-util.Plo \ ./$(DEPDIR)/module-dir.Plo ./$(DEPDIR)/mountpoint.Plo \ ./$(DEPDIR)/net.Plo ./$(DEPDIR)/nfs-workarounds.Plo \ ./$(DEPDIR)/numpack.Plo ./$(DEPDIR)/ostream-buffer.Plo \ ./$(DEPDIR)/ostream-failure-at.Plo \ ./$(DEPDIR)/ostream-file.Plo ./$(DEPDIR)/ostream-hash.Plo \ ./$(DEPDIR)/ostream-multiplex.Plo ./$(DEPDIR)/ostream-null.Plo \ ./$(DEPDIR)/ostream-rawlog.Plo ./$(DEPDIR)/ostream-unix.Plo \ ./$(DEPDIR)/ostream-wrapper.Plo ./$(DEPDIR)/ostream.Plo \ ./$(DEPDIR)/path-util.Plo ./$(DEPDIR)/pkcs5.Plo \ ./$(DEPDIR)/primes.Plo ./$(DEPDIR)/printf-format-fix.Plo \ ./$(DEPDIR)/priorityq.Plo ./$(DEPDIR)/process-stat.Plo \ ./$(DEPDIR)/process-title.Plo ./$(DEPDIR)/rand.Plo \ ./$(DEPDIR)/randgen.Plo ./$(DEPDIR)/read-full.Plo \ ./$(DEPDIR)/restrict-access.Plo \ ./$(DEPDIR)/restrict-process-size.Plo \ ./$(DEPDIR)/safe-memset.Plo ./$(DEPDIR)/safe-mkdir.Plo \ ./$(DEPDIR)/safe-mkstemp.Plo ./$(DEPDIR)/sendfile-util.Plo \ ./$(DEPDIR)/seq-range-array.Plo \ ./$(DEPDIR)/seq-set-builder.Plo ./$(DEPDIR)/sha1.Plo \ ./$(DEPDIR)/sha2.Plo ./$(DEPDIR)/sha3.Plo \ ./$(DEPDIR)/sleep.Plo ./$(DEPDIR)/sort.Plo \ ./$(DEPDIR)/stats-dist.Plo ./$(DEPDIR)/str-find.Plo \ ./$(DEPDIR)/str-sanitize.Plo ./$(DEPDIR)/str-table.Plo \ ./$(DEPDIR)/str.Plo ./$(DEPDIR)/strescape.Plo \ ./$(DEPDIR)/strfuncs.Plo ./$(DEPDIR)/strnum.Plo \ ./$(DEPDIR)/test_lib-test-aqueue.Po \ ./$(DEPDIR)/test_lib-test-array.Po \ ./$(DEPDIR)/test_lib-test-backtrace.Po \ ./$(DEPDIR)/test_lib-test-base32.Po \ ./$(DEPDIR)/test_lib-test-base64.Po \ ./$(DEPDIR)/test_lib-test-bits.Po \ ./$(DEPDIR)/test_lib-test-bsearch-insert-pos.Po \ ./$(DEPDIR)/test_lib-test-buffer-istream.Po \ ./$(DEPDIR)/test_lib-test-buffer.Po \ ./$(DEPDIR)/test_lib-test-byteorder.Po \ ./$(DEPDIR)/test_lib-test-connection.Po \ ./$(DEPDIR)/test_lib-test-cpu-limit.Po \ ./$(DEPDIR)/test_lib-test-crc32.Po \ ./$(DEPDIR)/test_lib-test-data-stack.Po \ ./$(DEPDIR)/test_lib-test-env-util.Po \ ./$(DEPDIR)/test_lib-test-event-category-register.Po \ ./$(DEPDIR)/test_lib-test-event-filter-expr.Po \ ./$(DEPDIR)/test_lib-test-event-filter-merge.Po \ ./$(DEPDIR)/test_lib-test-event-filter-parser.Po \ ./$(DEPDIR)/test_lib-test-event-filter.Po \ ./$(DEPDIR)/test_lib-test-event-flatten.Po \ ./$(DEPDIR)/test_lib-test-event-log.Po \ ./$(DEPDIR)/test_lib-test-failures.Po \ ./$(DEPDIR)/test_lib-test-fd-util.Po \ ./$(DEPDIR)/test_lib-test-file-cache.Po \ ./$(DEPDIR)/test_lib-test-file-create-locked.Po \ ./$(DEPDIR)/test_lib-test-guid.Po \ ./$(DEPDIR)/test_lib-test-hash-format.Po \ ./$(DEPDIR)/test_lib-test-hash-method.Po \ ./$(DEPDIR)/test_lib-test-hash.Po \ ./$(DEPDIR)/test_lib-test-hex-binary.Po \ ./$(DEPDIR)/test_lib-test-hmac.Po \ ./$(DEPDIR)/test_lib-test-imem.Po \ ./$(DEPDIR)/test_lib-test-ioloop.Po \ ./$(DEPDIR)/test_lib-test-iostream-proxy.Po \ ./$(DEPDIR)/test_lib-test-iostream-pump.Po \ ./$(DEPDIR)/test_lib-test-iostream-temp.Po \ ./$(DEPDIR)/test_lib-test-iso8601-date.Po \ ./$(DEPDIR)/test_lib-test-istream-base64-decoder.Po \ ./$(DEPDIR)/test_lib-test-istream-base64-encoder.Po \ ./$(DEPDIR)/test_lib-test-istream-chain.Po \ ./$(DEPDIR)/test_lib-test-istream-concat.Po \ ./$(DEPDIR)/test_lib-test-istream-crlf.Po \ ./$(DEPDIR)/test_lib-test-istream-failure-at.Po \ ./$(DEPDIR)/test_lib-test-istream-jsonstr.Po \ ./$(DEPDIR)/test_lib-test-istream-multiplex.Po \ ./$(DEPDIR)/test_lib-test-istream-seekable.Po \ ./$(DEPDIR)/test_lib-test-istream-sized.Po \ ./$(DEPDIR)/test_lib-test-istream-tee.Po \ ./$(DEPDIR)/test_lib-test-istream-try.Po \ ./$(DEPDIR)/test_lib-test-istream-unix.Po \ ./$(DEPDIR)/test_lib-test-istream.Po \ ./$(DEPDIR)/test_lib-test-json-parser.Po \ ./$(DEPDIR)/test_lib-test-json-tree.Po \ ./$(DEPDIR)/test_lib-test-lib-event.Po \ ./$(DEPDIR)/test_lib-test-lib-signals.Po \ ./$(DEPDIR)/test_lib-test-lib.Po \ ./$(DEPDIR)/test_lib-test-llist.Po \ ./$(DEPDIR)/test_lib-test-log-throttle.Po \ ./$(DEPDIR)/test_lib-test-macros.Po \ ./$(DEPDIR)/test_lib-test-malloc-overflow.Po \ ./$(DEPDIR)/test_lib-test-memarea.Po \ ./$(DEPDIR)/test_lib-test-mempool-allocfree.Po \ ./$(DEPDIR)/test_lib-test-mempool-alloconly.Po \ ./$(DEPDIR)/test_lib-test-mempool.Po \ ./$(DEPDIR)/test_lib-test-multiplex.Po \ ./$(DEPDIR)/test_lib-test-net.Po \ ./$(DEPDIR)/test_lib-test-numpack.Po \ ./$(DEPDIR)/test_lib-test-ostream-buffer.Po \ ./$(DEPDIR)/test_lib-test-ostream-failure-at.Po \ ./$(DEPDIR)/test_lib-test-ostream-file.Po \ ./$(DEPDIR)/test_lib-test-ostream-multiplex.Po \ ./$(DEPDIR)/test_lib-test-path-util.Po \ ./$(DEPDIR)/test_lib-test-pkcs5.Po \ ./$(DEPDIR)/test_lib-test-primes.Po \ ./$(DEPDIR)/test_lib-test-printf-format-fix.Po \ ./$(DEPDIR)/test_lib-test-priorityq.Po \ ./$(DEPDIR)/test_lib-test-random.Po \ ./$(DEPDIR)/test_lib-test-seq-range-array.Po \ ./$(DEPDIR)/test_lib-test-seq-set-builder.Po \ ./$(DEPDIR)/test_lib-test-stats-dist.Po \ ./$(DEPDIR)/test_lib-test-str-find.Po \ ./$(DEPDIR)/test_lib-test-str-sanitize.Po \ ./$(DEPDIR)/test_lib-test-str-table.Po \ ./$(DEPDIR)/test_lib-test-str.Po \ ./$(DEPDIR)/test_lib-test-strescape.Po \ ./$(DEPDIR)/test_lib-test-strfuncs.Po \ ./$(DEPDIR)/test_lib-test-strnum.Po \ ./$(DEPDIR)/test_lib-test-time-util.Po \ ./$(DEPDIR)/test_lib-test-unichar.Po \ ./$(DEPDIR)/test_lib-test-uri.Po \ ./$(DEPDIR)/test_lib-test-utc-mktime.Po \ ./$(DEPDIR)/test_lib-test-var-expand.Po \ ./$(DEPDIR)/test_lib-test-wildcard-match.Po \ ./$(DEPDIR)/time-util.Plo ./$(DEPDIR)/unichar.Plo \ ./$(DEPDIR)/unix-socket-create.Plo \ ./$(DEPDIR)/unlink-directory.Plo \ ./$(DEPDIR)/unlink-old-files.Plo ./$(DEPDIR)/uri-util.Plo \ ./$(DEPDIR)/utc-mktime.Plo ./$(DEPDIR)/utc-offset.Plo \ ./$(DEPDIR)/var-expand-if.Plo ./$(DEPDIR)/var-expand.Plo \ ./$(DEPDIR)/wildcard-match.Plo ./$(DEPDIR)/write-full.Plo am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = @MAINTAINER_MODE_FALSE@am__skiplex = test -f $@ || LEXCOMPILE = $(LEX) $(AM_LFLAGS) $(LFLAGS) LTLEXCOMPILE = $(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(LEX) $(AM_LFLAGS) $(LFLAGS) AM_V_LEX = $(am__v_LEX_@AM_V@) am__v_LEX_ = $(am__v_LEX_@AM_DEFAULT_V@) am__v_LEX_0 = @echo " LEX " $@; am__v_LEX_1 = YLWRAP = $(top_srcdir)/ylwrap @MAINTAINER_MODE_FALSE@am__skipyacc = test -f $@ || am__yacc_c2h = sed -e s/cc$$/hh/ -e s/cpp$$/hpp/ -e s/cxx$$/hxx/ \ -e s/c++$$/h++/ -e s/c$$/h/ YACCCOMPILE = $(YACC) $(AM_YFLAGS) $(YFLAGS) LTYACCCOMPILE = $(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(YACC) $(AM_YFLAGS) $(YFLAGS) AM_V_YACC = $(am__v_YACC_@AM_V@) am__v_YACC_ = $(am__v_YACC_@AM_DEFAULT_V@) am__v_YACC_0 = @echo " YACC " $@; am__v_YACC_1 = SOURCES = $(liblib_la_SOURCES) $(test_lib_SOURCES) DIST_SOURCES = $(liblib_la_SOURCES) $(test_lib_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ *) f=$$p;; \ esac; am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; am__install_max = 40 am__nobase_strip_setup = \ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` am__nobase_strip = \ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" am__nobase_list = $(am__nobase_strip_setup); \ for p in $$list; do echo "$$p $$p"; done | \ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ if (++n[$$2] == $(am__install_max)) \ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ END { for (dir in files) print dir, files[dir] }' am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' am__uninstall_files_from_dir = { \ test -z "$$files" \ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ $(am__cd) "$$dir" && rm -f $$files; }; \ } am__installdirs = "$(DESTDIR)$(pkginc_libdir)" HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp \ $(top_srcdir)/ylwrap event-filter-lexer.c \ event-filter-parser.c DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CPPFLAGS = \ $(LIBUNWIND_CFLAGS) noinst_LTLIBRARIES = liblib.la BUILT_SOURCES = $(srcdir)/unicodemap.c \ event-filter-lexer.c \ event-filter-parser.c \ event-filter-parser.h EXTRA_DIST = unicodemap.c unicodemap.pl UnicodeData.txt # Squelch autoconf error about using .[ly] sources but not defining $(LEX) # and $(YACC). Using false here avoids accidental use. LEX = /bin/false YACC = /bin/false liblib_la_LIBADD = $(LIBUNWIND_LIBS) liblib_la_SOURCES = \ array.c \ aqueue.c \ askpass.c \ backtrace-string.c \ base32.c \ base64.c \ bits.c \ bsearch-insert-pos.c \ buffer.c \ buffer-istream.c \ child-wait.c \ compat.c \ connection.c \ cpu-limit.c \ crc32.c \ data-stack.c \ eacces-error.c \ env-util.c \ event-filter.c \ event-filter-lexer.l \ event-filter-parser.y \ event-log.c \ execv-const.c \ failures.c \ fd-util.c \ fdatasync-path.c \ fdpass.c \ file-cache.c \ file-create-locked.c \ file-copy.c \ file-dotlock.c \ file-lock.c \ file-set-size.c \ guid.c \ hash.c \ hash-format.c \ hash-method.c \ hash2.c \ hex-binary.c \ hex-dec.c \ hmac.c \ hmac-cram-md5.c \ home-expand.c \ hook-build.c \ hostpid.c \ imem.c \ ipwd.c \ iostream.c \ iostream-pump.c \ iostream-proxy.c \ iostream-rawlog.c \ iostream-temp.c \ iso8601-date.c \ istream.c \ istream-base64-decoder.c \ istream-base64-encoder.c \ istream-callback.c \ istream-chain.c \ istream-concat.c \ istream-crlf.c \ istream-data.c \ istream-failure-at.c \ istream-file.c \ istream-hash.c \ istream-jsonstr.c \ istream-limit.c \ istream-multiplex.c \ istream-rawlog.c \ istream-seekable.c \ istream-sized.c \ istream-tee.c \ istream-try.c \ istream-timeout.c \ istream-unix.c \ ioloop.c \ ioloop-iolist.c \ ioloop-notify-none.c \ ioloop-notify-fd.c \ ioloop-notify-inotify.c \ ioloop-notify-kqueue.c \ ioloop-poll.c \ ioloop-select.c \ ioloop-epoll.c \ ioloop-kqueue.c \ json-parser.c \ json-tree.c \ lib.c \ lib-event.c \ lib-signals.c \ log-throttle.c \ md4.c \ md5.c \ memarea.c \ mempool.c \ mempool-allocfree.c \ mempool-alloconly.c \ mempool-datastack.c \ mempool-system.c \ mempool-unsafe-datastack.c \ mkdir-parents.c \ mmap-anon.c \ mmap-util.c \ module-dir.c \ mountpoint.c \ net.c \ nfs-workarounds.c \ numpack.c \ ostream.c \ ostream-buffer.c \ ostream-failure-at.c \ ostream-file.c \ ostream-hash.c \ ostream-multiplex.c \ ostream-null.c \ ostream-rawlog.c \ ostream-unix.c \ ostream-wrapper.c \ path-util.c \ pkcs5.c \ primes.c \ printf-format-fix.c \ process-stat.c \ process-title.c \ priorityq.c \ randgen.c \ rand.c \ read-full.c \ restrict-access.c \ restrict-process-size.c \ safe-memset.c \ safe-mkdir.c \ safe-mkstemp.c \ sendfile-util.c \ seq-range-array.c \ seq-set-builder.c \ sha1.c \ sha2.c \ sha3.c \ sleep.c \ sort.c \ stats-dist.c \ str.c \ str-find.c \ str-sanitize.c \ str-table.c \ strescape.c \ strfuncs.c \ strnum.c \ time-util.c \ unix-socket-create.c \ unlink-directory.c \ unlink-old-files.c \ unichar.c \ uri-util.c \ utc-offset.c \ utc-mktime.c \ var-expand.c \ var-expand-if.c \ wildcard-match.c \ write-full.c headers = \ aqueue.h \ array.h \ array-decl.h \ askpass.h \ backtrace-string.h \ base32.h \ base64.h \ bits.h \ bsearch-insert-pos.h \ buffer.h \ byteorder.h \ child-wait.h \ compat.h \ connection.h \ cpu-limit.h \ crc32.h \ data-stack.h \ eacces-error.h \ env-util.h \ event-filter.h \ event-filter-parser.h \ event-filter-private.h \ event-log.h \ execv-const.h \ failures.h \ failures-private.h \ fd-util.h \ fdatasync-path.h \ fdpass.h \ file-cache.h \ file-create-locked.h \ file-copy.h \ file-dotlock.h \ file-lock.h \ file-set-size.h \ fsync-mode.h \ guid.h \ hash.h \ hash-decl.h \ hash-format.h \ hash-method.h \ hash2.h \ hex-binary.h \ hex-dec.h \ hmac.h \ hmac-cram-md5.h \ home-expand.h \ hook-build.h \ hostpid.h \ imem.h \ ipwd.h \ iostream.h \ iostream-private.h \ iostream-pump.h \ iostream-proxy.h \ iostream-rawlog.h \ iostream-rawlog-private.h \ iostream-temp.h \ iso8601-date.h \ istream.h \ istream-base64.h \ istream-callback.h \ istream-chain.h \ istream-concat.h \ istream-crlf.h \ istream-failure-at.h \ istream-file-private.h \ istream-hash.h \ istream-jsonstr.h \ istream-multiplex.h \ istream-private.h \ istream-rawlog.h \ istream-seekable.h \ istream-sized.h \ istream-tee.h \ istream-try.h \ istream-timeout.h \ istream-unix.h \ ioloop.h \ ioloop-iolist.h \ ioloop-private.h \ ioloop-notify-fd.h \ json-parser.h \ json-tree.h \ lib.h \ lib-event.h \ lib-event-private.h \ lib-signals.h \ llist.h \ log-throttle.h \ macros.h \ md4.h \ md5.h \ malloc-overflow.h \ memarea.h \ mempool.h \ mkdir-parents.h \ mmap-util.h \ module-context.h \ module-dir.h \ mountpoint.h \ net.h \ nfs-workarounds.h \ numpack.h \ ostream.h \ ostream-failure-at.h \ ostream-file-private.h \ ostream-hash.h \ ostream-multiplex.h \ ostream-private.h \ ostream-null.h \ ostream-rawlog.h \ ostream-unix.h \ ostream-wrapper.h \ path-util.h \ pkcs5.h \ primes.h \ printf-format-fix.h \ process-stat.h \ process-title.h \ priorityq.h \ randgen.h \ read-full.h \ restrict-access.h \ restrict-process-size.h \ safe-memset.h \ safe-mkdir.h \ safe-mkstemp.h \ sendfile-util.h \ seq-range-array.h \ seq-set-builder.h \ sha-common.h \ sha1.h \ sha2.h \ sha3.h \ sleep.h \ sort.h \ stats-dist.h \ str.h \ str-find.h \ str-sanitize.h \ str-table.h \ strescape.h \ strfuncs.h \ strnum.h \ time-util.h \ unix-socket-create.h \ unlink-directory.h \ unlink-old-files.h \ unichar.h \ uri-util.h \ utc-offset.h \ utc-mktime.h \ var-expand.h \ var-expand-private.h \ wildcard-match.h \ write-full.h test_programs = test-lib test_lib_CPPFLAGS = \ -I$(top_srcdir)/src/lib-test test_libs = \ ../lib-test/libtest.la \ liblib.la test_lib_SOURCES = \ test-lib.c \ test-array.c \ test-aqueue.c \ test-backtrace.c \ test-base32.c \ test-base64.c \ test-bits.c \ test-bsearch-insert-pos.c \ test-buffer.c \ test-buffer-istream.c \ test-byteorder.c \ test-connection.c \ test-crc32.c \ test-cpu-limit.c \ test-data-stack.c \ test-env-util.c \ test-event-category-register.c \ test-event-filter.c \ test-event-filter-expr.c \ test-event-filter-merge.c \ test-event-filter-parser.c \ test-event-flatten.c \ test-event-log.c \ test-failures.c \ test-fd-util.c \ test-file-cache.c \ test-file-create-locked.c \ test-guid.c \ test-hash.c \ test-hash-format.c \ test-hash-method.c \ test-hmac.c \ test-hex-binary.c \ test-imem.c \ test-ioloop.c \ test-iso8601-date.c \ test-iostream-pump.c \ test-iostream-proxy.c \ test-iostream-temp.c \ test-istream.c \ test-istream-base64-decoder.c \ test-istream-base64-encoder.c \ test-istream-chain.c \ test-istream-concat.c \ test-istream-crlf.c \ test-istream-failure-at.c \ test-istream-jsonstr.c \ test-istream-multiplex.c \ test-istream-seekable.c \ test-istream-sized.c \ test-istream-tee.c \ test-istream-try.c \ test-istream-unix.c \ test-json-parser.c \ test-json-tree.c \ test-lib-event.c \ test-lib-signals.c \ test-llist.c \ test-log-throttle.c \ test-macros.c \ test-malloc-overflow.c \ test-memarea.c \ test-mempool.c \ test-mempool-allocfree.c \ test-mempool-alloconly.c \ test-pkcs5.c \ test-net.c \ test-numpack.c \ test-ostream-buffer.c \ test-ostream-failure-at.c \ test-ostream-file.c \ test-ostream-multiplex.c \ test-multiplex.c \ test-path-util.c \ test-primes.c \ test-printf-format-fix.c \ test-priorityq.c \ test-random.c \ test-seq-range-array.c \ test-seq-set-builder.c \ test-stats-dist.c \ test-str.c \ test-strescape.c \ test-strfuncs.c \ test-strnum.c \ test-str-find.c \ test-str-sanitize.c \ test-str-table.c \ test-time-util.c \ test-unichar.c \ test-utc-mktime.c \ test-uri.c \ test-var-expand.c \ test-wildcard-match.c test_headers = \ test-lib.h \ test-lib.inc test_lib_LDADD = $(test_libs) -lm test_lib_DEPENDENCIES = $(test_libs) pkginc_libdir = $(pkgincludedir) pkginc_lib_HEADERS = $(headers) noinst_HEADERS = $(test_headers) all: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) all-am .SUFFIXES: .SUFFIXES: .c .l .lo .o .obj .y $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/lib/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): clean-noinstPROGRAMS: @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list clean-noinstLTLIBRARIES: -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) @list='$(noinst_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } liblib.la: $(liblib_la_OBJECTS) $(liblib_la_DEPENDENCIES) $(EXTRA_liblib_la_DEPENDENCIES) $(AM_V_CCLD)$(LINK) $(liblib_la_OBJECTS) $(liblib_la_LIBADD) $(LIBS) test-lib$(EXEEXT): $(test_lib_OBJECTS) $(test_lib_DEPENDENCIES) $(EXTRA_test_lib_DEPENDENCIES) @rm -f test-lib$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_lib_OBJECTS) $(test_lib_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/aqueue.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/array.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/askpass.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/backtrace-string.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/base32.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/base64.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bits.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsearch-insert-pos.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/buffer-istream.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/buffer.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/child-wait.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/compat.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/connection.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cpu-limit.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc32.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data-stack.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eacces-error.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/env-util.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/event-filter-lexer.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/event-filter-parser.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/event-filter.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/event-log.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/execv-const.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/failures.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fd-util.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdatasync-path.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdpass.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-cache.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-copy.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-create-locked.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-dotlock.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-lock.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file-set-size.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/guid.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hash-format.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hash-method.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hash.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hash2.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hex-binary.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hex-dec.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hmac-cram-md5.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hmac.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/home-expand.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hook-build.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hostpid.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imem.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ioloop-epoll.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ioloop-iolist.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ioloop-kqueue.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ioloop-notify-fd.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ioloop-notify-inotify.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ioloop-notify-kqueue.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ioloop-notify-none.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ioloop-poll.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ioloop-select.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ioloop.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iostream-proxy.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iostream-pump.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iostream-rawlog.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iostream-temp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iostream.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipwd.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iso8601-date.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-base64-decoder.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-base64-encoder.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-callback.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-chain.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-concat.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-crlf.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-data.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-failure-at.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-file.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-hash.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-jsonstr.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-limit.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-multiplex.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-rawlog.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-seekable.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-sized.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-tee.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-timeout.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-try.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-unix.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/json-parser.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/json-tree.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lib-event.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lib-signals.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lib.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/log-throttle.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md4.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/memarea.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mempool-allocfree.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mempool-alloconly.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mempool-datastack.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mempool-system.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mempool-unsafe-datastack.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mempool.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mkdir-parents.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mmap-anon.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mmap-util.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/module-dir.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mountpoint.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/net.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nfs-workarounds.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/numpack.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream-buffer.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream-failure-at.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream-file.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream-hash.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream-multiplex.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream-null.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream-rawlog.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream-unix.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream-wrapper.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/path-util.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pkcs5.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/primes.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/printf-format-fix.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/priorityq.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/process-stat.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/process-title.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rand.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/randgen.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/read-full.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/restrict-access.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/restrict-process-size.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/safe-memset.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/safe-mkdir.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/safe-mkstemp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sendfile-util.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/seq-range-array.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/seq-set-builder.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sha1.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sha2.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sha3.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sleep.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sort.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stats-dist.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/str-find.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/str-sanitize.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/str-table.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/str.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strescape.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strfuncs.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strnum.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-aqueue.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-array.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-backtrace.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-base32.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-base64.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-bits.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-bsearch-insert-pos.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-buffer-istream.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-buffer.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-byteorder.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-connection.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-cpu-limit.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-crc32.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-data-stack.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-env-util.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-event-category-register.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-event-filter-expr.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-event-filter-merge.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-event-filter-parser.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-event-filter.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-event-flatten.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-event-log.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-failures.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-fd-util.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-file-cache.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-file-create-locked.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-guid.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-hash-format.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-hash-method.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-hash.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-hex-binary.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-hmac.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-imem.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-ioloop.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-iostream-proxy.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-iostream-pump.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-iostream-temp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-iso8601-date.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-istream-base64-decoder.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-istream-base64-encoder.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-istream-chain.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-istream-concat.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-istream-crlf.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-istream-failure-at.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-istream-jsonstr.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-istream-multiplex.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-istream-seekable.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-istream-sized.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-istream-tee.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-istream-try.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-istream-unix.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-istream.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-json-parser.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-json-tree.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-lib-event.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-lib-signals.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-lib.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-llist.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-log-throttle.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-macros.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-malloc-overflow.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-memarea.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-mempool-allocfree.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-mempool-alloconly.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-mempool.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-multiplex.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-net.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-numpack.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-ostream-buffer.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-ostream-failure-at.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-ostream-file.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-ostream-multiplex.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-path-util.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-pkcs5.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-primes.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-printf-format-fix.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-priorityq.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-random.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-seq-range-array.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-seq-set-builder.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-stats-dist.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-str-find.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-str-sanitize.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-str-table.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-str.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-strescape.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-strfuncs.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-strnum.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-time-util.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-unichar.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-uri.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-utc-mktime.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-var-expand.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lib-test-wildcard-match.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/time-util.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unichar.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unix-socket-create.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unlink-directory.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unlink-old-files.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/uri-util.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utc-mktime.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utc-offset.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/var-expand-if.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/var-expand.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/wildcard-match.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/write-full.Plo@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< test_lib-test-lib.o: test-lib.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-lib.o -MD -MP -MF $(DEPDIR)/test_lib-test-lib.Tpo -c -o test_lib-test-lib.o `test -f 'test-lib.c' || echo '$(srcdir)/'`test-lib.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-lib.Tpo $(DEPDIR)/test_lib-test-lib.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-lib.c' object='test_lib-test-lib.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-lib.o `test -f 'test-lib.c' || echo '$(srcdir)/'`test-lib.c test_lib-test-lib.obj: test-lib.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-lib.obj -MD -MP -MF $(DEPDIR)/test_lib-test-lib.Tpo -c -o test_lib-test-lib.obj `if test -f 'test-lib.c'; then $(CYGPATH_W) 'test-lib.c'; else $(CYGPATH_W) '$(srcdir)/test-lib.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-lib.Tpo $(DEPDIR)/test_lib-test-lib.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-lib.c' object='test_lib-test-lib.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-lib.obj `if test -f 'test-lib.c'; then $(CYGPATH_W) 'test-lib.c'; else $(CYGPATH_W) '$(srcdir)/test-lib.c'; fi` test_lib-test-array.o: test-array.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-array.o -MD -MP -MF $(DEPDIR)/test_lib-test-array.Tpo -c -o test_lib-test-array.o `test -f 'test-array.c' || echo '$(srcdir)/'`test-array.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-array.Tpo $(DEPDIR)/test_lib-test-array.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-array.c' object='test_lib-test-array.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-array.o `test -f 'test-array.c' || echo '$(srcdir)/'`test-array.c test_lib-test-array.obj: test-array.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-array.obj -MD -MP -MF $(DEPDIR)/test_lib-test-array.Tpo -c -o test_lib-test-array.obj `if test -f 'test-array.c'; then $(CYGPATH_W) 'test-array.c'; else $(CYGPATH_W) '$(srcdir)/test-array.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-array.Tpo $(DEPDIR)/test_lib-test-array.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-array.c' object='test_lib-test-array.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-array.obj `if test -f 'test-array.c'; then $(CYGPATH_W) 'test-array.c'; else $(CYGPATH_W) '$(srcdir)/test-array.c'; fi` test_lib-test-aqueue.o: test-aqueue.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-aqueue.o -MD -MP -MF $(DEPDIR)/test_lib-test-aqueue.Tpo -c -o test_lib-test-aqueue.o `test -f 'test-aqueue.c' || echo '$(srcdir)/'`test-aqueue.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-aqueue.Tpo $(DEPDIR)/test_lib-test-aqueue.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-aqueue.c' object='test_lib-test-aqueue.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-aqueue.o `test -f 'test-aqueue.c' || echo '$(srcdir)/'`test-aqueue.c test_lib-test-aqueue.obj: test-aqueue.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-aqueue.obj -MD -MP -MF $(DEPDIR)/test_lib-test-aqueue.Tpo -c -o test_lib-test-aqueue.obj `if test -f 'test-aqueue.c'; then $(CYGPATH_W) 'test-aqueue.c'; else $(CYGPATH_W) '$(srcdir)/test-aqueue.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-aqueue.Tpo $(DEPDIR)/test_lib-test-aqueue.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-aqueue.c' object='test_lib-test-aqueue.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-aqueue.obj `if test -f 'test-aqueue.c'; then $(CYGPATH_W) 'test-aqueue.c'; else $(CYGPATH_W) '$(srcdir)/test-aqueue.c'; fi` test_lib-test-backtrace.o: test-backtrace.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-backtrace.o -MD -MP -MF $(DEPDIR)/test_lib-test-backtrace.Tpo -c -o test_lib-test-backtrace.o `test -f 'test-backtrace.c' || echo '$(srcdir)/'`test-backtrace.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-backtrace.Tpo $(DEPDIR)/test_lib-test-backtrace.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-backtrace.c' object='test_lib-test-backtrace.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-backtrace.o `test -f 'test-backtrace.c' || echo '$(srcdir)/'`test-backtrace.c test_lib-test-backtrace.obj: test-backtrace.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-backtrace.obj -MD -MP -MF $(DEPDIR)/test_lib-test-backtrace.Tpo -c -o test_lib-test-backtrace.obj `if test -f 'test-backtrace.c'; then $(CYGPATH_W) 'test-backtrace.c'; else $(CYGPATH_W) '$(srcdir)/test-backtrace.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-backtrace.Tpo $(DEPDIR)/test_lib-test-backtrace.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-backtrace.c' object='test_lib-test-backtrace.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-backtrace.obj `if test -f 'test-backtrace.c'; then $(CYGPATH_W) 'test-backtrace.c'; else $(CYGPATH_W) '$(srcdir)/test-backtrace.c'; fi` test_lib-test-base32.o: test-base32.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-base32.o -MD -MP -MF $(DEPDIR)/test_lib-test-base32.Tpo -c -o test_lib-test-base32.o `test -f 'test-base32.c' || echo '$(srcdir)/'`test-base32.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-base32.Tpo $(DEPDIR)/test_lib-test-base32.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-base32.c' object='test_lib-test-base32.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-base32.o `test -f 'test-base32.c' || echo '$(srcdir)/'`test-base32.c test_lib-test-base32.obj: test-base32.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-base32.obj -MD -MP -MF $(DEPDIR)/test_lib-test-base32.Tpo -c -o test_lib-test-base32.obj `if test -f 'test-base32.c'; then $(CYGPATH_W) 'test-base32.c'; else $(CYGPATH_W) '$(srcdir)/test-base32.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-base32.Tpo $(DEPDIR)/test_lib-test-base32.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-base32.c' object='test_lib-test-base32.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-base32.obj `if test -f 'test-base32.c'; then $(CYGPATH_W) 'test-base32.c'; else $(CYGPATH_W) '$(srcdir)/test-base32.c'; fi` test_lib-test-base64.o: test-base64.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-base64.o -MD -MP -MF $(DEPDIR)/test_lib-test-base64.Tpo -c -o test_lib-test-base64.o `test -f 'test-base64.c' || echo '$(srcdir)/'`test-base64.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-base64.Tpo $(DEPDIR)/test_lib-test-base64.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-base64.c' object='test_lib-test-base64.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-base64.o `test -f 'test-base64.c' || echo '$(srcdir)/'`test-base64.c test_lib-test-base64.obj: test-base64.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-base64.obj -MD -MP -MF $(DEPDIR)/test_lib-test-base64.Tpo -c -o test_lib-test-base64.obj `if test -f 'test-base64.c'; then $(CYGPATH_W) 'test-base64.c'; else $(CYGPATH_W) '$(srcdir)/test-base64.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-base64.Tpo $(DEPDIR)/test_lib-test-base64.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-base64.c' object='test_lib-test-base64.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-base64.obj `if test -f 'test-base64.c'; then $(CYGPATH_W) 'test-base64.c'; else $(CYGPATH_W) '$(srcdir)/test-base64.c'; fi` test_lib-test-bits.o: test-bits.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-bits.o -MD -MP -MF $(DEPDIR)/test_lib-test-bits.Tpo -c -o test_lib-test-bits.o `test -f 'test-bits.c' || echo '$(srcdir)/'`test-bits.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-bits.Tpo $(DEPDIR)/test_lib-test-bits.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-bits.c' object='test_lib-test-bits.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-bits.o `test -f 'test-bits.c' || echo '$(srcdir)/'`test-bits.c test_lib-test-bits.obj: test-bits.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-bits.obj -MD -MP -MF $(DEPDIR)/test_lib-test-bits.Tpo -c -o test_lib-test-bits.obj `if test -f 'test-bits.c'; then $(CYGPATH_W) 'test-bits.c'; else $(CYGPATH_W) '$(srcdir)/test-bits.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-bits.Tpo $(DEPDIR)/test_lib-test-bits.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-bits.c' object='test_lib-test-bits.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-bits.obj `if test -f 'test-bits.c'; then $(CYGPATH_W) 'test-bits.c'; else $(CYGPATH_W) '$(srcdir)/test-bits.c'; fi` test_lib-test-bsearch-insert-pos.o: test-bsearch-insert-pos.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-bsearch-insert-pos.o -MD -MP -MF $(DEPDIR)/test_lib-test-bsearch-insert-pos.Tpo -c -o test_lib-test-bsearch-insert-pos.o `test -f 'test-bsearch-insert-pos.c' || echo '$(srcdir)/'`test-bsearch-insert-pos.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-bsearch-insert-pos.Tpo $(DEPDIR)/test_lib-test-bsearch-insert-pos.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-bsearch-insert-pos.c' object='test_lib-test-bsearch-insert-pos.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-bsearch-insert-pos.o `test -f 'test-bsearch-insert-pos.c' || echo '$(srcdir)/'`test-bsearch-insert-pos.c test_lib-test-bsearch-insert-pos.obj: test-bsearch-insert-pos.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-bsearch-insert-pos.obj -MD -MP -MF $(DEPDIR)/test_lib-test-bsearch-insert-pos.Tpo -c -o test_lib-test-bsearch-insert-pos.obj `if test -f 'test-bsearch-insert-pos.c'; then $(CYGPATH_W) 'test-bsearch-insert-pos.c'; else $(CYGPATH_W) '$(srcdir)/test-bsearch-insert-pos.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-bsearch-insert-pos.Tpo $(DEPDIR)/test_lib-test-bsearch-insert-pos.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-bsearch-insert-pos.c' object='test_lib-test-bsearch-insert-pos.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-bsearch-insert-pos.obj `if test -f 'test-bsearch-insert-pos.c'; then $(CYGPATH_W) 'test-bsearch-insert-pos.c'; else $(CYGPATH_W) '$(srcdir)/test-bsearch-insert-pos.c'; fi` test_lib-test-buffer.o: test-buffer.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-buffer.o -MD -MP -MF $(DEPDIR)/test_lib-test-buffer.Tpo -c -o test_lib-test-buffer.o `test -f 'test-buffer.c' || echo '$(srcdir)/'`test-buffer.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-buffer.Tpo $(DEPDIR)/test_lib-test-buffer.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-buffer.c' object='test_lib-test-buffer.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-buffer.o `test -f 'test-buffer.c' || echo '$(srcdir)/'`test-buffer.c test_lib-test-buffer.obj: test-buffer.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-buffer.obj -MD -MP -MF $(DEPDIR)/test_lib-test-buffer.Tpo -c -o test_lib-test-buffer.obj `if test -f 'test-buffer.c'; then $(CYGPATH_W) 'test-buffer.c'; else $(CYGPATH_W) '$(srcdir)/test-buffer.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-buffer.Tpo $(DEPDIR)/test_lib-test-buffer.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-buffer.c' object='test_lib-test-buffer.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-buffer.obj `if test -f 'test-buffer.c'; then $(CYGPATH_W) 'test-buffer.c'; else $(CYGPATH_W) '$(srcdir)/test-buffer.c'; fi` test_lib-test-buffer-istream.o: test-buffer-istream.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-buffer-istream.o -MD -MP -MF $(DEPDIR)/test_lib-test-buffer-istream.Tpo -c -o test_lib-test-buffer-istream.o `test -f 'test-buffer-istream.c' || echo '$(srcdir)/'`test-buffer-istream.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-buffer-istream.Tpo $(DEPDIR)/test_lib-test-buffer-istream.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-buffer-istream.c' object='test_lib-test-buffer-istream.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-buffer-istream.o `test -f 'test-buffer-istream.c' || echo '$(srcdir)/'`test-buffer-istream.c test_lib-test-buffer-istream.obj: test-buffer-istream.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-buffer-istream.obj -MD -MP -MF $(DEPDIR)/test_lib-test-buffer-istream.Tpo -c -o test_lib-test-buffer-istream.obj `if test -f 'test-buffer-istream.c'; then $(CYGPATH_W) 'test-buffer-istream.c'; else $(CYGPATH_W) '$(srcdir)/test-buffer-istream.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-buffer-istream.Tpo $(DEPDIR)/test_lib-test-buffer-istream.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-buffer-istream.c' object='test_lib-test-buffer-istream.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-buffer-istream.obj `if test -f 'test-buffer-istream.c'; then $(CYGPATH_W) 'test-buffer-istream.c'; else $(CYGPATH_W) '$(srcdir)/test-buffer-istream.c'; fi` test_lib-test-byteorder.o: test-byteorder.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-byteorder.o -MD -MP -MF $(DEPDIR)/test_lib-test-byteorder.Tpo -c -o test_lib-test-byteorder.o `test -f 'test-byteorder.c' || echo '$(srcdir)/'`test-byteorder.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-byteorder.Tpo $(DEPDIR)/test_lib-test-byteorder.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-byteorder.c' object='test_lib-test-byteorder.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-byteorder.o `test -f 'test-byteorder.c' || echo '$(srcdir)/'`test-byteorder.c test_lib-test-byteorder.obj: test-byteorder.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-byteorder.obj -MD -MP -MF $(DEPDIR)/test_lib-test-byteorder.Tpo -c -o test_lib-test-byteorder.obj `if test -f 'test-byteorder.c'; then $(CYGPATH_W) 'test-byteorder.c'; else $(CYGPATH_W) '$(srcdir)/test-byteorder.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-byteorder.Tpo $(DEPDIR)/test_lib-test-byteorder.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-byteorder.c' object='test_lib-test-byteorder.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-byteorder.obj `if test -f 'test-byteorder.c'; then $(CYGPATH_W) 'test-byteorder.c'; else $(CYGPATH_W) '$(srcdir)/test-byteorder.c'; fi` test_lib-test-connection.o: test-connection.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-connection.o -MD -MP -MF $(DEPDIR)/test_lib-test-connection.Tpo -c -o test_lib-test-connection.o `test -f 'test-connection.c' || echo '$(srcdir)/'`test-connection.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-connection.Tpo $(DEPDIR)/test_lib-test-connection.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-connection.c' object='test_lib-test-connection.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-connection.o `test -f 'test-connection.c' || echo '$(srcdir)/'`test-connection.c test_lib-test-connection.obj: test-connection.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-connection.obj -MD -MP -MF $(DEPDIR)/test_lib-test-connection.Tpo -c -o test_lib-test-connection.obj `if test -f 'test-connection.c'; then $(CYGPATH_W) 'test-connection.c'; else $(CYGPATH_W) '$(srcdir)/test-connection.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-connection.Tpo $(DEPDIR)/test_lib-test-connection.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-connection.c' object='test_lib-test-connection.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-connection.obj `if test -f 'test-connection.c'; then $(CYGPATH_W) 'test-connection.c'; else $(CYGPATH_W) '$(srcdir)/test-connection.c'; fi` test_lib-test-crc32.o: test-crc32.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-crc32.o -MD -MP -MF $(DEPDIR)/test_lib-test-crc32.Tpo -c -o test_lib-test-crc32.o `test -f 'test-crc32.c' || echo '$(srcdir)/'`test-crc32.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-crc32.Tpo $(DEPDIR)/test_lib-test-crc32.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-crc32.c' object='test_lib-test-crc32.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-crc32.o `test -f 'test-crc32.c' || echo '$(srcdir)/'`test-crc32.c test_lib-test-crc32.obj: test-crc32.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-crc32.obj -MD -MP -MF $(DEPDIR)/test_lib-test-crc32.Tpo -c -o test_lib-test-crc32.obj `if test -f 'test-crc32.c'; then $(CYGPATH_W) 'test-crc32.c'; else $(CYGPATH_W) '$(srcdir)/test-crc32.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-crc32.Tpo $(DEPDIR)/test_lib-test-crc32.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-crc32.c' object='test_lib-test-crc32.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-crc32.obj `if test -f 'test-crc32.c'; then $(CYGPATH_W) 'test-crc32.c'; else $(CYGPATH_W) '$(srcdir)/test-crc32.c'; fi` test_lib-test-cpu-limit.o: test-cpu-limit.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-cpu-limit.o -MD -MP -MF $(DEPDIR)/test_lib-test-cpu-limit.Tpo -c -o test_lib-test-cpu-limit.o `test -f 'test-cpu-limit.c' || echo '$(srcdir)/'`test-cpu-limit.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-cpu-limit.Tpo $(DEPDIR)/test_lib-test-cpu-limit.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-cpu-limit.c' object='test_lib-test-cpu-limit.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-cpu-limit.o `test -f 'test-cpu-limit.c' || echo '$(srcdir)/'`test-cpu-limit.c test_lib-test-cpu-limit.obj: test-cpu-limit.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-cpu-limit.obj -MD -MP -MF $(DEPDIR)/test_lib-test-cpu-limit.Tpo -c -o test_lib-test-cpu-limit.obj `if test -f 'test-cpu-limit.c'; then $(CYGPATH_W) 'test-cpu-limit.c'; else $(CYGPATH_W) '$(srcdir)/test-cpu-limit.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-cpu-limit.Tpo $(DEPDIR)/test_lib-test-cpu-limit.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-cpu-limit.c' object='test_lib-test-cpu-limit.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-cpu-limit.obj `if test -f 'test-cpu-limit.c'; then $(CYGPATH_W) 'test-cpu-limit.c'; else $(CYGPATH_W) '$(srcdir)/test-cpu-limit.c'; fi` test_lib-test-data-stack.o: test-data-stack.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-data-stack.o -MD -MP -MF $(DEPDIR)/test_lib-test-data-stack.Tpo -c -o test_lib-test-data-stack.o `test -f 'test-data-stack.c' || echo '$(srcdir)/'`test-data-stack.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-data-stack.Tpo $(DEPDIR)/test_lib-test-data-stack.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-data-stack.c' object='test_lib-test-data-stack.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-data-stack.o `test -f 'test-data-stack.c' || echo '$(srcdir)/'`test-data-stack.c test_lib-test-data-stack.obj: test-data-stack.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-data-stack.obj -MD -MP -MF $(DEPDIR)/test_lib-test-data-stack.Tpo -c -o test_lib-test-data-stack.obj `if test -f 'test-data-stack.c'; then $(CYGPATH_W) 'test-data-stack.c'; else $(CYGPATH_W) '$(srcdir)/test-data-stack.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-data-stack.Tpo $(DEPDIR)/test_lib-test-data-stack.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-data-stack.c' object='test_lib-test-data-stack.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-data-stack.obj `if test -f 'test-data-stack.c'; then $(CYGPATH_W) 'test-data-stack.c'; else $(CYGPATH_W) '$(srcdir)/test-data-stack.c'; fi` test_lib-test-env-util.o: test-env-util.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-env-util.o -MD -MP -MF $(DEPDIR)/test_lib-test-env-util.Tpo -c -o test_lib-test-env-util.o `test -f 'test-env-util.c' || echo '$(srcdir)/'`test-env-util.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-env-util.Tpo $(DEPDIR)/test_lib-test-env-util.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-env-util.c' object='test_lib-test-env-util.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-env-util.o `test -f 'test-env-util.c' || echo '$(srcdir)/'`test-env-util.c test_lib-test-env-util.obj: test-env-util.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-env-util.obj -MD -MP -MF $(DEPDIR)/test_lib-test-env-util.Tpo -c -o test_lib-test-env-util.obj `if test -f 'test-env-util.c'; then $(CYGPATH_W) 'test-env-util.c'; else $(CYGPATH_W) '$(srcdir)/test-env-util.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-env-util.Tpo $(DEPDIR)/test_lib-test-env-util.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-env-util.c' object='test_lib-test-env-util.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-env-util.obj `if test -f 'test-env-util.c'; then $(CYGPATH_W) 'test-env-util.c'; else $(CYGPATH_W) '$(srcdir)/test-env-util.c'; fi` test_lib-test-event-category-register.o: test-event-category-register.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-event-category-register.o -MD -MP -MF $(DEPDIR)/test_lib-test-event-category-register.Tpo -c -o test_lib-test-event-category-register.o `test -f 'test-event-category-register.c' || echo '$(srcdir)/'`test-event-category-register.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-event-category-register.Tpo $(DEPDIR)/test_lib-test-event-category-register.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-event-category-register.c' object='test_lib-test-event-category-register.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-event-category-register.o `test -f 'test-event-category-register.c' || echo '$(srcdir)/'`test-event-category-register.c test_lib-test-event-category-register.obj: test-event-category-register.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-event-category-register.obj -MD -MP -MF $(DEPDIR)/test_lib-test-event-category-register.Tpo -c -o test_lib-test-event-category-register.obj `if test -f 'test-event-category-register.c'; then $(CYGPATH_W) 'test-event-category-register.c'; else $(CYGPATH_W) '$(srcdir)/test-event-category-register.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-event-category-register.Tpo $(DEPDIR)/test_lib-test-event-category-register.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-event-category-register.c' object='test_lib-test-event-category-register.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-event-category-register.obj `if test -f 'test-event-category-register.c'; then $(CYGPATH_W) 'test-event-category-register.c'; else $(CYGPATH_W) '$(srcdir)/test-event-category-register.c'; fi` test_lib-test-event-filter.o: test-event-filter.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-event-filter.o -MD -MP -MF $(DEPDIR)/test_lib-test-event-filter.Tpo -c -o test_lib-test-event-filter.o `test -f 'test-event-filter.c' || echo '$(srcdir)/'`test-event-filter.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-event-filter.Tpo $(DEPDIR)/test_lib-test-event-filter.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-event-filter.c' object='test_lib-test-event-filter.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-event-filter.o `test -f 'test-event-filter.c' || echo '$(srcdir)/'`test-event-filter.c test_lib-test-event-filter.obj: test-event-filter.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-event-filter.obj -MD -MP -MF $(DEPDIR)/test_lib-test-event-filter.Tpo -c -o test_lib-test-event-filter.obj `if test -f 'test-event-filter.c'; then $(CYGPATH_W) 'test-event-filter.c'; else $(CYGPATH_W) '$(srcdir)/test-event-filter.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-event-filter.Tpo $(DEPDIR)/test_lib-test-event-filter.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-event-filter.c' object='test_lib-test-event-filter.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-event-filter.obj `if test -f 'test-event-filter.c'; then $(CYGPATH_W) 'test-event-filter.c'; else $(CYGPATH_W) '$(srcdir)/test-event-filter.c'; fi` test_lib-test-event-filter-expr.o: test-event-filter-expr.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-event-filter-expr.o -MD -MP -MF $(DEPDIR)/test_lib-test-event-filter-expr.Tpo -c -o test_lib-test-event-filter-expr.o `test -f 'test-event-filter-expr.c' || echo '$(srcdir)/'`test-event-filter-expr.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-event-filter-expr.Tpo $(DEPDIR)/test_lib-test-event-filter-expr.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-event-filter-expr.c' object='test_lib-test-event-filter-expr.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-event-filter-expr.o `test -f 'test-event-filter-expr.c' || echo '$(srcdir)/'`test-event-filter-expr.c test_lib-test-event-filter-expr.obj: test-event-filter-expr.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-event-filter-expr.obj -MD -MP -MF $(DEPDIR)/test_lib-test-event-filter-expr.Tpo -c -o test_lib-test-event-filter-expr.obj `if test -f 'test-event-filter-expr.c'; then $(CYGPATH_W) 'test-event-filter-expr.c'; else $(CYGPATH_W) '$(srcdir)/test-event-filter-expr.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-event-filter-expr.Tpo $(DEPDIR)/test_lib-test-event-filter-expr.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-event-filter-expr.c' object='test_lib-test-event-filter-expr.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-event-filter-expr.obj `if test -f 'test-event-filter-expr.c'; then $(CYGPATH_W) 'test-event-filter-expr.c'; else $(CYGPATH_W) '$(srcdir)/test-event-filter-expr.c'; fi` test_lib-test-event-filter-merge.o: test-event-filter-merge.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-event-filter-merge.o -MD -MP -MF $(DEPDIR)/test_lib-test-event-filter-merge.Tpo -c -o test_lib-test-event-filter-merge.o `test -f 'test-event-filter-merge.c' || echo '$(srcdir)/'`test-event-filter-merge.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-event-filter-merge.Tpo $(DEPDIR)/test_lib-test-event-filter-merge.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-event-filter-merge.c' object='test_lib-test-event-filter-merge.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-event-filter-merge.o `test -f 'test-event-filter-merge.c' || echo '$(srcdir)/'`test-event-filter-merge.c test_lib-test-event-filter-merge.obj: test-event-filter-merge.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-event-filter-merge.obj -MD -MP -MF $(DEPDIR)/test_lib-test-event-filter-merge.Tpo -c -o test_lib-test-event-filter-merge.obj `if test -f 'test-event-filter-merge.c'; then $(CYGPATH_W) 'test-event-filter-merge.c'; else $(CYGPATH_W) '$(srcdir)/test-event-filter-merge.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-event-filter-merge.Tpo $(DEPDIR)/test_lib-test-event-filter-merge.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-event-filter-merge.c' object='test_lib-test-event-filter-merge.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-event-filter-merge.obj `if test -f 'test-event-filter-merge.c'; then $(CYGPATH_W) 'test-event-filter-merge.c'; else $(CYGPATH_W) '$(srcdir)/test-event-filter-merge.c'; fi` test_lib-test-event-filter-parser.o: test-event-filter-parser.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-event-filter-parser.o -MD -MP -MF $(DEPDIR)/test_lib-test-event-filter-parser.Tpo -c -o test_lib-test-event-filter-parser.o `test -f 'test-event-filter-parser.c' || echo '$(srcdir)/'`test-event-filter-parser.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-event-filter-parser.Tpo $(DEPDIR)/test_lib-test-event-filter-parser.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-event-filter-parser.c' object='test_lib-test-event-filter-parser.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-event-filter-parser.o `test -f 'test-event-filter-parser.c' || echo '$(srcdir)/'`test-event-filter-parser.c test_lib-test-event-filter-parser.obj: test-event-filter-parser.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-event-filter-parser.obj -MD -MP -MF $(DEPDIR)/test_lib-test-event-filter-parser.Tpo -c -o test_lib-test-event-filter-parser.obj `if test -f 'test-event-filter-parser.c'; then $(CYGPATH_W) 'test-event-filter-parser.c'; else $(CYGPATH_W) '$(srcdir)/test-event-filter-parser.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-event-filter-parser.Tpo $(DEPDIR)/test_lib-test-event-filter-parser.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-event-filter-parser.c' object='test_lib-test-event-filter-parser.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-event-filter-parser.obj `if test -f 'test-event-filter-parser.c'; then $(CYGPATH_W) 'test-event-filter-parser.c'; else $(CYGPATH_W) '$(srcdir)/test-event-filter-parser.c'; fi` test_lib-test-event-flatten.o: test-event-flatten.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-event-flatten.o -MD -MP -MF $(DEPDIR)/test_lib-test-event-flatten.Tpo -c -o test_lib-test-event-flatten.o `test -f 'test-event-flatten.c' || echo '$(srcdir)/'`test-event-flatten.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-event-flatten.Tpo $(DEPDIR)/test_lib-test-event-flatten.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-event-flatten.c' object='test_lib-test-event-flatten.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-event-flatten.o `test -f 'test-event-flatten.c' || echo '$(srcdir)/'`test-event-flatten.c test_lib-test-event-flatten.obj: test-event-flatten.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-event-flatten.obj -MD -MP -MF $(DEPDIR)/test_lib-test-event-flatten.Tpo -c -o test_lib-test-event-flatten.obj `if test -f 'test-event-flatten.c'; then $(CYGPATH_W) 'test-event-flatten.c'; else $(CYGPATH_W) '$(srcdir)/test-event-flatten.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-event-flatten.Tpo $(DEPDIR)/test_lib-test-event-flatten.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-event-flatten.c' object='test_lib-test-event-flatten.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-event-flatten.obj `if test -f 'test-event-flatten.c'; then $(CYGPATH_W) 'test-event-flatten.c'; else $(CYGPATH_W) '$(srcdir)/test-event-flatten.c'; fi` test_lib-test-event-log.o: test-event-log.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-event-log.o -MD -MP -MF $(DEPDIR)/test_lib-test-event-log.Tpo -c -o test_lib-test-event-log.o `test -f 'test-event-log.c' || echo '$(srcdir)/'`test-event-log.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-event-log.Tpo $(DEPDIR)/test_lib-test-event-log.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-event-log.c' object='test_lib-test-event-log.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-event-log.o `test -f 'test-event-log.c' || echo '$(srcdir)/'`test-event-log.c test_lib-test-event-log.obj: test-event-log.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-event-log.obj -MD -MP -MF $(DEPDIR)/test_lib-test-event-log.Tpo -c -o test_lib-test-event-log.obj `if test -f 'test-event-log.c'; then $(CYGPATH_W) 'test-event-log.c'; else $(CYGPATH_W) '$(srcdir)/test-event-log.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-event-log.Tpo $(DEPDIR)/test_lib-test-event-log.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-event-log.c' object='test_lib-test-event-log.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-event-log.obj `if test -f 'test-event-log.c'; then $(CYGPATH_W) 'test-event-log.c'; else $(CYGPATH_W) '$(srcdir)/test-event-log.c'; fi` test_lib-test-failures.o: test-failures.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-failures.o -MD -MP -MF $(DEPDIR)/test_lib-test-failures.Tpo -c -o test_lib-test-failures.o `test -f 'test-failures.c' || echo '$(srcdir)/'`test-failures.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-failures.Tpo $(DEPDIR)/test_lib-test-failures.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-failures.c' object='test_lib-test-failures.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-failures.o `test -f 'test-failures.c' || echo '$(srcdir)/'`test-failures.c test_lib-test-failures.obj: test-failures.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-failures.obj -MD -MP -MF $(DEPDIR)/test_lib-test-failures.Tpo -c -o test_lib-test-failures.obj `if test -f 'test-failures.c'; then $(CYGPATH_W) 'test-failures.c'; else $(CYGPATH_W) '$(srcdir)/test-failures.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-failures.Tpo $(DEPDIR)/test_lib-test-failures.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-failures.c' object='test_lib-test-failures.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-failures.obj `if test -f 'test-failures.c'; then $(CYGPATH_W) 'test-failures.c'; else $(CYGPATH_W) '$(srcdir)/test-failures.c'; fi` test_lib-test-fd-util.o: test-fd-util.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-fd-util.o -MD -MP -MF $(DEPDIR)/test_lib-test-fd-util.Tpo -c -o test_lib-test-fd-util.o `test -f 'test-fd-util.c' || echo '$(srcdir)/'`test-fd-util.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-fd-util.Tpo $(DEPDIR)/test_lib-test-fd-util.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-fd-util.c' object='test_lib-test-fd-util.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-fd-util.o `test -f 'test-fd-util.c' || echo '$(srcdir)/'`test-fd-util.c test_lib-test-fd-util.obj: test-fd-util.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-fd-util.obj -MD -MP -MF $(DEPDIR)/test_lib-test-fd-util.Tpo -c -o test_lib-test-fd-util.obj `if test -f 'test-fd-util.c'; then $(CYGPATH_W) 'test-fd-util.c'; else $(CYGPATH_W) '$(srcdir)/test-fd-util.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-fd-util.Tpo $(DEPDIR)/test_lib-test-fd-util.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-fd-util.c' object='test_lib-test-fd-util.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-fd-util.obj `if test -f 'test-fd-util.c'; then $(CYGPATH_W) 'test-fd-util.c'; else $(CYGPATH_W) '$(srcdir)/test-fd-util.c'; fi` test_lib-test-file-cache.o: test-file-cache.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-file-cache.o -MD -MP -MF $(DEPDIR)/test_lib-test-file-cache.Tpo -c -o test_lib-test-file-cache.o `test -f 'test-file-cache.c' || echo '$(srcdir)/'`test-file-cache.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-file-cache.Tpo $(DEPDIR)/test_lib-test-file-cache.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-file-cache.c' object='test_lib-test-file-cache.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-file-cache.o `test -f 'test-file-cache.c' || echo '$(srcdir)/'`test-file-cache.c test_lib-test-file-cache.obj: test-file-cache.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-file-cache.obj -MD -MP -MF $(DEPDIR)/test_lib-test-file-cache.Tpo -c -o test_lib-test-file-cache.obj `if test -f 'test-file-cache.c'; then $(CYGPATH_W) 'test-file-cache.c'; else $(CYGPATH_W) '$(srcdir)/test-file-cache.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-file-cache.Tpo $(DEPDIR)/test_lib-test-file-cache.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-file-cache.c' object='test_lib-test-file-cache.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-file-cache.obj `if test -f 'test-file-cache.c'; then $(CYGPATH_W) 'test-file-cache.c'; else $(CYGPATH_W) '$(srcdir)/test-file-cache.c'; fi` test_lib-test-file-create-locked.o: test-file-create-locked.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-file-create-locked.o -MD -MP -MF $(DEPDIR)/test_lib-test-file-create-locked.Tpo -c -o test_lib-test-file-create-locked.o `test -f 'test-file-create-locked.c' || echo '$(srcdir)/'`test-file-create-locked.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-file-create-locked.Tpo $(DEPDIR)/test_lib-test-file-create-locked.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-file-create-locked.c' object='test_lib-test-file-create-locked.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-file-create-locked.o `test -f 'test-file-create-locked.c' || echo '$(srcdir)/'`test-file-create-locked.c test_lib-test-file-create-locked.obj: test-file-create-locked.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-file-create-locked.obj -MD -MP -MF $(DEPDIR)/test_lib-test-file-create-locked.Tpo -c -o test_lib-test-file-create-locked.obj `if test -f 'test-file-create-locked.c'; then $(CYGPATH_W) 'test-file-create-locked.c'; else $(CYGPATH_W) '$(srcdir)/test-file-create-locked.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-file-create-locked.Tpo $(DEPDIR)/test_lib-test-file-create-locked.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-file-create-locked.c' object='test_lib-test-file-create-locked.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-file-create-locked.obj `if test -f 'test-file-create-locked.c'; then $(CYGPATH_W) 'test-file-create-locked.c'; else $(CYGPATH_W) '$(srcdir)/test-file-create-locked.c'; fi` test_lib-test-guid.o: test-guid.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-guid.o -MD -MP -MF $(DEPDIR)/test_lib-test-guid.Tpo -c -o test_lib-test-guid.o `test -f 'test-guid.c' || echo '$(srcdir)/'`test-guid.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-guid.Tpo $(DEPDIR)/test_lib-test-guid.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-guid.c' object='test_lib-test-guid.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-guid.o `test -f 'test-guid.c' || echo '$(srcdir)/'`test-guid.c test_lib-test-guid.obj: test-guid.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-guid.obj -MD -MP -MF $(DEPDIR)/test_lib-test-guid.Tpo -c -o test_lib-test-guid.obj `if test -f 'test-guid.c'; then $(CYGPATH_W) 'test-guid.c'; else $(CYGPATH_W) '$(srcdir)/test-guid.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-guid.Tpo $(DEPDIR)/test_lib-test-guid.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-guid.c' object='test_lib-test-guid.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-guid.obj `if test -f 'test-guid.c'; then $(CYGPATH_W) 'test-guid.c'; else $(CYGPATH_W) '$(srcdir)/test-guid.c'; fi` test_lib-test-hash.o: test-hash.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-hash.o -MD -MP -MF $(DEPDIR)/test_lib-test-hash.Tpo -c -o test_lib-test-hash.o `test -f 'test-hash.c' || echo '$(srcdir)/'`test-hash.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-hash.Tpo $(DEPDIR)/test_lib-test-hash.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-hash.c' object='test_lib-test-hash.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-hash.o `test -f 'test-hash.c' || echo '$(srcdir)/'`test-hash.c test_lib-test-hash.obj: test-hash.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-hash.obj -MD -MP -MF $(DEPDIR)/test_lib-test-hash.Tpo -c -o test_lib-test-hash.obj `if test -f 'test-hash.c'; then $(CYGPATH_W) 'test-hash.c'; else $(CYGPATH_W) '$(srcdir)/test-hash.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-hash.Tpo $(DEPDIR)/test_lib-test-hash.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-hash.c' object='test_lib-test-hash.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-hash.obj `if test -f 'test-hash.c'; then $(CYGPATH_W) 'test-hash.c'; else $(CYGPATH_W) '$(srcdir)/test-hash.c'; fi` test_lib-test-hash-format.o: test-hash-format.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-hash-format.o -MD -MP -MF $(DEPDIR)/test_lib-test-hash-format.Tpo -c -o test_lib-test-hash-format.o `test -f 'test-hash-format.c' || echo '$(srcdir)/'`test-hash-format.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-hash-format.Tpo $(DEPDIR)/test_lib-test-hash-format.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-hash-format.c' object='test_lib-test-hash-format.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-hash-format.o `test -f 'test-hash-format.c' || echo '$(srcdir)/'`test-hash-format.c test_lib-test-hash-format.obj: test-hash-format.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-hash-format.obj -MD -MP -MF $(DEPDIR)/test_lib-test-hash-format.Tpo -c -o test_lib-test-hash-format.obj `if test -f 'test-hash-format.c'; then $(CYGPATH_W) 'test-hash-format.c'; else $(CYGPATH_W) '$(srcdir)/test-hash-format.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-hash-format.Tpo $(DEPDIR)/test_lib-test-hash-format.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-hash-format.c' object='test_lib-test-hash-format.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-hash-format.obj `if test -f 'test-hash-format.c'; then $(CYGPATH_W) 'test-hash-format.c'; else $(CYGPATH_W) '$(srcdir)/test-hash-format.c'; fi` test_lib-test-hash-method.o: test-hash-method.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-hash-method.o -MD -MP -MF $(DEPDIR)/test_lib-test-hash-method.Tpo -c -o test_lib-test-hash-method.o `test -f 'test-hash-method.c' || echo '$(srcdir)/'`test-hash-method.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-hash-method.Tpo $(DEPDIR)/test_lib-test-hash-method.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-hash-method.c' object='test_lib-test-hash-method.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-hash-method.o `test -f 'test-hash-method.c' || echo '$(srcdir)/'`test-hash-method.c test_lib-test-hash-method.obj: test-hash-method.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-hash-method.obj -MD -MP -MF $(DEPDIR)/test_lib-test-hash-method.Tpo -c -o test_lib-test-hash-method.obj `if test -f 'test-hash-method.c'; then $(CYGPATH_W) 'test-hash-method.c'; else $(CYGPATH_W) '$(srcdir)/test-hash-method.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-hash-method.Tpo $(DEPDIR)/test_lib-test-hash-method.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-hash-method.c' object='test_lib-test-hash-method.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-hash-method.obj `if test -f 'test-hash-method.c'; then $(CYGPATH_W) 'test-hash-method.c'; else $(CYGPATH_W) '$(srcdir)/test-hash-method.c'; fi` test_lib-test-hmac.o: test-hmac.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-hmac.o -MD -MP -MF $(DEPDIR)/test_lib-test-hmac.Tpo -c -o test_lib-test-hmac.o `test -f 'test-hmac.c' || echo '$(srcdir)/'`test-hmac.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-hmac.Tpo $(DEPDIR)/test_lib-test-hmac.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-hmac.c' object='test_lib-test-hmac.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-hmac.o `test -f 'test-hmac.c' || echo '$(srcdir)/'`test-hmac.c test_lib-test-hmac.obj: test-hmac.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-hmac.obj -MD -MP -MF $(DEPDIR)/test_lib-test-hmac.Tpo -c -o test_lib-test-hmac.obj `if test -f 'test-hmac.c'; then $(CYGPATH_W) 'test-hmac.c'; else $(CYGPATH_W) '$(srcdir)/test-hmac.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-hmac.Tpo $(DEPDIR)/test_lib-test-hmac.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-hmac.c' object='test_lib-test-hmac.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-hmac.obj `if test -f 'test-hmac.c'; then $(CYGPATH_W) 'test-hmac.c'; else $(CYGPATH_W) '$(srcdir)/test-hmac.c'; fi` test_lib-test-hex-binary.o: test-hex-binary.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-hex-binary.o -MD -MP -MF $(DEPDIR)/test_lib-test-hex-binary.Tpo -c -o test_lib-test-hex-binary.o `test -f 'test-hex-binary.c' || echo '$(srcdir)/'`test-hex-binary.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-hex-binary.Tpo $(DEPDIR)/test_lib-test-hex-binary.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-hex-binary.c' object='test_lib-test-hex-binary.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-hex-binary.o `test -f 'test-hex-binary.c' || echo '$(srcdir)/'`test-hex-binary.c test_lib-test-hex-binary.obj: test-hex-binary.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-hex-binary.obj -MD -MP -MF $(DEPDIR)/test_lib-test-hex-binary.Tpo -c -o test_lib-test-hex-binary.obj `if test -f 'test-hex-binary.c'; then $(CYGPATH_W) 'test-hex-binary.c'; else $(CYGPATH_W) '$(srcdir)/test-hex-binary.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-hex-binary.Tpo $(DEPDIR)/test_lib-test-hex-binary.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-hex-binary.c' object='test_lib-test-hex-binary.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-hex-binary.obj `if test -f 'test-hex-binary.c'; then $(CYGPATH_W) 'test-hex-binary.c'; else $(CYGPATH_W) '$(srcdir)/test-hex-binary.c'; fi` test_lib-test-imem.o: test-imem.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-imem.o -MD -MP -MF $(DEPDIR)/test_lib-test-imem.Tpo -c -o test_lib-test-imem.o `test -f 'test-imem.c' || echo '$(srcdir)/'`test-imem.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-imem.Tpo $(DEPDIR)/test_lib-test-imem.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-imem.c' object='test_lib-test-imem.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-imem.o `test -f 'test-imem.c' || echo '$(srcdir)/'`test-imem.c test_lib-test-imem.obj: test-imem.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-imem.obj -MD -MP -MF $(DEPDIR)/test_lib-test-imem.Tpo -c -o test_lib-test-imem.obj `if test -f 'test-imem.c'; then $(CYGPATH_W) 'test-imem.c'; else $(CYGPATH_W) '$(srcdir)/test-imem.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-imem.Tpo $(DEPDIR)/test_lib-test-imem.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-imem.c' object='test_lib-test-imem.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-imem.obj `if test -f 'test-imem.c'; then $(CYGPATH_W) 'test-imem.c'; else $(CYGPATH_W) '$(srcdir)/test-imem.c'; fi` test_lib-test-ioloop.o: test-ioloop.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-ioloop.o -MD -MP -MF $(DEPDIR)/test_lib-test-ioloop.Tpo -c -o test_lib-test-ioloop.o `test -f 'test-ioloop.c' || echo '$(srcdir)/'`test-ioloop.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-ioloop.Tpo $(DEPDIR)/test_lib-test-ioloop.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-ioloop.c' object='test_lib-test-ioloop.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-ioloop.o `test -f 'test-ioloop.c' || echo '$(srcdir)/'`test-ioloop.c test_lib-test-ioloop.obj: test-ioloop.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-ioloop.obj -MD -MP -MF $(DEPDIR)/test_lib-test-ioloop.Tpo -c -o test_lib-test-ioloop.obj `if test -f 'test-ioloop.c'; then $(CYGPATH_W) 'test-ioloop.c'; else $(CYGPATH_W) '$(srcdir)/test-ioloop.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-ioloop.Tpo $(DEPDIR)/test_lib-test-ioloop.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-ioloop.c' object='test_lib-test-ioloop.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-ioloop.obj `if test -f 'test-ioloop.c'; then $(CYGPATH_W) 'test-ioloop.c'; else $(CYGPATH_W) '$(srcdir)/test-ioloop.c'; fi` test_lib-test-iso8601-date.o: test-iso8601-date.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-iso8601-date.o -MD -MP -MF $(DEPDIR)/test_lib-test-iso8601-date.Tpo -c -o test_lib-test-iso8601-date.o `test -f 'test-iso8601-date.c' || echo '$(srcdir)/'`test-iso8601-date.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-iso8601-date.Tpo $(DEPDIR)/test_lib-test-iso8601-date.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-iso8601-date.c' object='test_lib-test-iso8601-date.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-iso8601-date.o `test -f 'test-iso8601-date.c' || echo '$(srcdir)/'`test-iso8601-date.c test_lib-test-iso8601-date.obj: test-iso8601-date.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-iso8601-date.obj -MD -MP -MF $(DEPDIR)/test_lib-test-iso8601-date.Tpo -c -o test_lib-test-iso8601-date.obj `if test -f 'test-iso8601-date.c'; then $(CYGPATH_W) 'test-iso8601-date.c'; else $(CYGPATH_W) '$(srcdir)/test-iso8601-date.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-iso8601-date.Tpo $(DEPDIR)/test_lib-test-iso8601-date.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-iso8601-date.c' object='test_lib-test-iso8601-date.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-iso8601-date.obj `if test -f 'test-iso8601-date.c'; then $(CYGPATH_W) 'test-iso8601-date.c'; else $(CYGPATH_W) '$(srcdir)/test-iso8601-date.c'; fi` test_lib-test-iostream-pump.o: test-iostream-pump.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-iostream-pump.o -MD -MP -MF $(DEPDIR)/test_lib-test-iostream-pump.Tpo -c -o test_lib-test-iostream-pump.o `test -f 'test-iostream-pump.c' || echo '$(srcdir)/'`test-iostream-pump.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-iostream-pump.Tpo $(DEPDIR)/test_lib-test-iostream-pump.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-iostream-pump.c' object='test_lib-test-iostream-pump.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-iostream-pump.o `test -f 'test-iostream-pump.c' || echo '$(srcdir)/'`test-iostream-pump.c test_lib-test-iostream-pump.obj: test-iostream-pump.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-iostream-pump.obj -MD -MP -MF $(DEPDIR)/test_lib-test-iostream-pump.Tpo -c -o test_lib-test-iostream-pump.obj `if test -f 'test-iostream-pump.c'; then $(CYGPATH_W) 'test-iostream-pump.c'; else $(CYGPATH_W) '$(srcdir)/test-iostream-pump.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-iostream-pump.Tpo $(DEPDIR)/test_lib-test-iostream-pump.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-iostream-pump.c' object='test_lib-test-iostream-pump.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-iostream-pump.obj `if test -f 'test-iostream-pump.c'; then $(CYGPATH_W) 'test-iostream-pump.c'; else $(CYGPATH_W) '$(srcdir)/test-iostream-pump.c'; fi` test_lib-test-iostream-proxy.o: test-iostream-proxy.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-iostream-proxy.o -MD -MP -MF $(DEPDIR)/test_lib-test-iostream-proxy.Tpo -c -o test_lib-test-iostream-proxy.o `test -f 'test-iostream-proxy.c' || echo '$(srcdir)/'`test-iostream-proxy.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-iostream-proxy.Tpo $(DEPDIR)/test_lib-test-iostream-proxy.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-iostream-proxy.c' object='test_lib-test-iostream-proxy.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-iostream-proxy.o `test -f 'test-iostream-proxy.c' || echo '$(srcdir)/'`test-iostream-proxy.c test_lib-test-iostream-proxy.obj: test-iostream-proxy.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-iostream-proxy.obj -MD -MP -MF $(DEPDIR)/test_lib-test-iostream-proxy.Tpo -c -o test_lib-test-iostream-proxy.obj `if test -f 'test-iostream-proxy.c'; then $(CYGPATH_W) 'test-iostream-proxy.c'; else $(CYGPATH_W) '$(srcdir)/test-iostream-proxy.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-iostream-proxy.Tpo $(DEPDIR)/test_lib-test-iostream-proxy.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-iostream-proxy.c' object='test_lib-test-iostream-proxy.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-iostream-proxy.obj `if test -f 'test-iostream-proxy.c'; then $(CYGPATH_W) 'test-iostream-proxy.c'; else $(CYGPATH_W) '$(srcdir)/test-iostream-proxy.c'; fi` test_lib-test-iostream-temp.o: test-iostream-temp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-iostream-temp.o -MD -MP -MF $(DEPDIR)/test_lib-test-iostream-temp.Tpo -c -o test_lib-test-iostream-temp.o `test -f 'test-iostream-temp.c' || echo '$(srcdir)/'`test-iostream-temp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-iostream-temp.Tpo $(DEPDIR)/test_lib-test-iostream-temp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-iostream-temp.c' object='test_lib-test-iostream-temp.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-iostream-temp.o `test -f 'test-iostream-temp.c' || echo '$(srcdir)/'`test-iostream-temp.c test_lib-test-iostream-temp.obj: test-iostream-temp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-iostream-temp.obj -MD -MP -MF $(DEPDIR)/test_lib-test-iostream-temp.Tpo -c -o test_lib-test-iostream-temp.obj `if test -f 'test-iostream-temp.c'; then $(CYGPATH_W) 'test-iostream-temp.c'; else $(CYGPATH_W) '$(srcdir)/test-iostream-temp.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-iostream-temp.Tpo $(DEPDIR)/test_lib-test-iostream-temp.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-iostream-temp.c' object='test_lib-test-iostream-temp.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-iostream-temp.obj `if test -f 'test-iostream-temp.c'; then $(CYGPATH_W) 'test-iostream-temp.c'; else $(CYGPATH_W) '$(srcdir)/test-iostream-temp.c'; fi` test_lib-test-istream.o: test-istream.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream.o -MD -MP -MF $(DEPDIR)/test_lib-test-istream.Tpo -c -o test_lib-test-istream.o `test -f 'test-istream.c' || echo '$(srcdir)/'`test-istream.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream.Tpo $(DEPDIR)/test_lib-test-istream.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream.c' object='test_lib-test-istream.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream.o `test -f 'test-istream.c' || echo '$(srcdir)/'`test-istream.c test_lib-test-istream.obj: test-istream.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream.obj -MD -MP -MF $(DEPDIR)/test_lib-test-istream.Tpo -c -o test_lib-test-istream.obj `if test -f 'test-istream.c'; then $(CYGPATH_W) 'test-istream.c'; else $(CYGPATH_W) '$(srcdir)/test-istream.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream.Tpo $(DEPDIR)/test_lib-test-istream.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream.c' object='test_lib-test-istream.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream.obj `if test -f 'test-istream.c'; then $(CYGPATH_W) 'test-istream.c'; else $(CYGPATH_W) '$(srcdir)/test-istream.c'; fi` test_lib-test-istream-base64-decoder.o: test-istream-base64-decoder.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-base64-decoder.o -MD -MP -MF $(DEPDIR)/test_lib-test-istream-base64-decoder.Tpo -c -o test_lib-test-istream-base64-decoder.o `test -f 'test-istream-base64-decoder.c' || echo '$(srcdir)/'`test-istream-base64-decoder.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-base64-decoder.Tpo $(DEPDIR)/test_lib-test-istream-base64-decoder.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-base64-decoder.c' object='test_lib-test-istream-base64-decoder.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-base64-decoder.o `test -f 'test-istream-base64-decoder.c' || echo '$(srcdir)/'`test-istream-base64-decoder.c test_lib-test-istream-base64-decoder.obj: test-istream-base64-decoder.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-base64-decoder.obj -MD -MP -MF $(DEPDIR)/test_lib-test-istream-base64-decoder.Tpo -c -o test_lib-test-istream-base64-decoder.obj `if test -f 'test-istream-base64-decoder.c'; then $(CYGPATH_W) 'test-istream-base64-decoder.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-base64-decoder.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-base64-decoder.Tpo $(DEPDIR)/test_lib-test-istream-base64-decoder.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-base64-decoder.c' object='test_lib-test-istream-base64-decoder.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-base64-decoder.obj `if test -f 'test-istream-base64-decoder.c'; then $(CYGPATH_W) 'test-istream-base64-decoder.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-base64-decoder.c'; fi` test_lib-test-istream-base64-encoder.o: test-istream-base64-encoder.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-base64-encoder.o -MD -MP -MF $(DEPDIR)/test_lib-test-istream-base64-encoder.Tpo -c -o test_lib-test-istream-base64-encoder.o `test -f 'test-istream-base64-encoder.c' || echo '$(srcdir)/'`test-istream-base64-encoder.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-base64-encoder.Tpo $(DEPDIR)/test_lib-test-istream-base64-encoder.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-base64-encoder.c' object='test_lib-test-istream-base64-encoder.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-base64-encoder.o `test -f 'test-istream-base64-encoder.c' || echo '$(srcdir)/'`test-istream-base64-encoder.c test_lib-test-istream-base64-encoder.obj: test-istream-base64-encoder.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-base64-encoder.obj -MD -MP -MF $(DEPDIR)/test_lib-test-istream-base64-encoder.Tpo -c -o test_lib-test-istream-base64-encoder.obj `if test -f 'test-istream-base64-encoder.c'; then $(CYGPATH_W) 'test-istream-base64-encoder.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-base64-encoder.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-base64-encoder.Tpo $(DEPDIR)/test_lib-test-istream-base64-encoder.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-base64-encoder.c' object='test_lib-test-istream-base64-encoder.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-base64-encoder.obj `if test -f 'test-istream-base64-encoder.c'; then $(CYGPATH_W) 'test-istream-base64-encoder.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-base64-encoder.c'; fi` test_lib-test-istream-chain.o: test-istream-chain.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-chain.o -MD -MP -MF $(DEPDIR)/test_lib-test-istream-chain.Tpo -c -o test_lib-test-istream-chain.o `test -f 'test-istream-chain.c' || echo '$(srcdir)/'`test-istream-chain.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-chain.Tpo $(DEPDIR)/test_lib-test-istream-chain.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-chain.c' object='test_lib-test-istream-chain.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-chain.o `test -f 'test-istream-chain.c' || echo '$(srcdir)/'`test-istream-chain.c test_lib-test-istream-chain.obj: test-istream-chain.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-chain.obj -MD -MP -MF $(DEPDIR)/test_lib-test-istream-chain.Tpo -c -o test_lib-test-istream-chain.obj `if test -f 'test-istream-chain.c'; then $(CYGPATH_W) 'test-istream-chain.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-chain.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-chain.Tpo $(DEPDIR)/test_lib-test-istream-chain.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-chain.c' object='test_lib-test-istream-chain.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-chain.obj `if test -f 'test-istream-chain.c'; then $(CYGPATH_W) 'test-istream-chain.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-chain.c'; fi` test_lib-test-istream-concat.o: test-istream-concat.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-concat.o -MD -MP -MF $(DEPDIR)/test_lib-test-istream-concat.Tpo -c -o test_lib-test-istream-concat.o `test -f 'test-istream-concat.c' || echo '$(srcdir)/'`test-istream-concat.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-concat.Tpo $(DEPDIR)/test_lib-test-istream-concat.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-concat.c' object='test_lib-test-istream-concat.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-concat.o `test -f 'test-istream-concat.c' || echo '$(srcdir)/'`test-istream-concat.c test_lib-test-istream-concat.obj: test-istream-concat.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-concat.obj -MD -MP -MF $(DEPDIR)/test_lib-test-istream-concat.Tpo -c -o test_lib-test-istream-concat.obj `if test -f 'test-istream-concat.c'; then $(CYGPATH_W) 'test-istream-concat.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-concat.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-concat.Tpo $(DEPDIR)/test_lib-test-istream-concat.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-concat.c' object='test_lib-test-istream-concat.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-concat.obj `if test -f 'test-istream-concat.c'; then $(CYGPATH_W) 'test-istream-concat.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-concat.c'; fi` test_lib-test-istream-crlf.o: test-istream-crlf.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-crlf.o -MD -MP -MF $(DEPDIR)/test_lib-test-istream-crlf.Tpo -c -o test_lib-test-istream-crlf.o `test -f 'test-istream-crlf.c' || echo '$(srcdir)/'`test-istream-crlf.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-crlf.Tpo $(DEPDIR)/test_lib-test-istream-crlf.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-crlf.c' object='test_lib-test-istream-crlf.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-crlf.o `test -f 'test-istream-crlf.c' || echo '$(srcdir)/'`test-istream-crlf.c test_lib-test-istream-crlf.obj: test-istream-crlf.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-crlf.obj -MD -MP -MF $(DEPDIR)/test_lib-test-istream-crlf.Tpo -c -o test_lib-test-istream-crlf.obj `if test -f 'test-istream-crlf.c'; then $(CYGPATH_W) 'test-istream-crlf.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-crlf.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-crlf.Tpo $(DEPDIR)/test_lib-test-istream-crlf.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-crlf.c' object='test_lib-test-istream-crlf.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-crlf.obj `if test -f 'test-istream-crlf.c'; then $(CYGPATH_W) 'test-istream-crlf.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-crlf.c'; fi` test_lib-test-istream-failure-at.o: test-istream-failure-at.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-failure-at.o -MD -MP -MF $(DEPDIR)/test_lib-test-istream-failure-at.Tpo -c -o test_lib-test-istream-failure-at.o `test -f 'test-istream-failure-at.c' || echo '$(srcdir)/'`test-istream-failure-at.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-failure-at.Tpo $(DEPDIR)/test_lib-test-istream-failure-at.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-failure-at.c' object='test_lib-test-istream-failure-at.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-failure-at.o `test -f 'test-istream-failure-at.c' || echo '$(srcdir)/'`test-istream-failure-at.c test_lib-test-istream-failure-at.obj: test-istream-failure-at.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-failure-at.obj -MD -MP -MF $(DEPDIR)/test_lib-test-istream-failure-at.Tpo -c -o test_lib-test-istream-failure-at.obj `if test -f 'test-istream-failure-at.c'; then $(CYGPATH_W) 'test-istream-failure-at.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-failure-at.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-failure-at.Tpo $(DEPDIR)/test_lib-test-istream-failure-at.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-failure-at.c' object='test_lib-test-istream-failure-at.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-failure-at.obj `if test -f 'test-istream-failure-at.c'; then $(CYGPATH_W) 'test-istream-failure-at.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-failure-at.c'; fi` test_lib-test-istream-jsonstr.o: test-istream-jsonstr.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-jsonstr.o -MD -MP -MF $(DEPDIR)/test_lib-test-istream-jsonstr.Tpo -c -o test_lib-test-istream-jsonstr.o `test -f 'test-istream-jsonstr.c' || echo '$(srcdir)/'`test-istream-jsonstr.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-jsonstr.Tpo $(DEPDIR)/test_lib-test-istream-jsonstr.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-jsonstr.c' object='test_lib-test-istream-jsonstr.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-jsonstr.o `test -f 'test-istream-jsonstr.c' || echo '$(srcdir)/'`test-istream-jsonstr.c test_lib-test-istream-jsonstr.obj: test-istream-jsonstr.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-jsonstr.obj -MD -MP -MF $(DEPDIR)/test_lib-test-istream-jsonstr.Tpo -c -o test_lib-test-istream-jsonstr.obj `if test -f 'test-istream-jsonstr.c'; then $(CYGPATH_W) 'test-istream-jsonstr.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-jsonstr.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-jsonstr.Tpo $(DEPDIR)/test_lib-test-istream-jsonstr.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-jsonstr.c' object='test_lib-test-istream-jsonstr.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-jsonstr.obj `if test -f 'test-istream-jsonstr.c'; then $(CYGPATH_W) 'test-istream-jsonstr.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-jsonstr.c'; fi` test_lib-test-istream-multiplex.o: test-istream-multiplex.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-multiplex.o -MD -MP -MF $(DEPDIR)/test_lib-test-istream-multiplex.Tpo -c -o test_lib-test-istream-multiplex.o `test -f 'test-istream-multiplex.c' || echo '$(srcdir)/'`test-istream-multiplex.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-multiplex.Tpo $(DEPDIR)/test_lib-test-istream-multiplex.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-multiplex.c' object='test_lib-test-istream-multiplex.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-multiplex.o `test -f 'test-istream-multiplex.c' || echo '$(srcdir)/'`test-istream-multiplex.c test_lib-test-istream-multiplex.obj: test-istream-multiplex.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-multiplex.obj -MD -MP -MF $(DEPDIR)/test_lib-test-istream-multiplex.Tpo -c -o test_lib-test-istream-multiplex.obj `if test -f 'test-istream-multiplex.c'; then $(CYGPATH_W) 'test-istream-multiplex.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-multiplex.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-multiplex.Tpo $(DEPDIR)/test_lib-test-istream-multiplex.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-multiplex.c' object='test_lib-test-istream-multiplex.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-multiplex.obj `if test -f 'test-istream-multiplex.c'; then $(CYGPATH_W) 'test-istream-multiplex.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-multiplex.c'; fi` test_lib-test-istream-seekable.o: test-istream-seekable.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-seekable.o -MD -MP -MF $(DEPDIR)/test_lib-test-istream-seekable.Tpo -c -o test_lib-test-istream-seekable.o `test -f 'test-istream-seekable.c' || echo '$(srcdir)/'`test-istream-seekable.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-seekable.Tpo $(DEPDIR)/test_lib-test-istream-seekable.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-seekable.c' object='test_lib-test-istream-seekable.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-seekable.o `test -f 'test-istream-seekable.c' || echo '$(srcdir)/'`test-istream-seekable.c test_lib-test-istream-seekable.obj: test-istream-seekable.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-seekable.obj -MD -MP -MF $(DEPDIR)/test_lib-test-istream-seekable.Tpo -c -o test_lib-test-istream-seekable.obj `if test -f 'test-istream-seekable.c'; then $(CYGPATH_W) 'test-istream-seekable.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-seekable.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-seekable.Tpo $(DEPDIR)/test_lib-test-istream-seekable.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-seekable.c' object='test_lib-test-istream-seekable.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-seekable.obj `if test -f 'test-istream-seekable.c'; then $(CYGPATH_W) 'test-istream-seekable.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-seekable.c'; fi` test_lib-test-istream-sized.o: test-istream-sized.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-sized.o -MD -MP -MF $(DEPDIR)/test_lib-test-istream-sized.Tpo -c -o test_lib-test-istream-sized.o `test -f 'test-istream-sized.c' || echo '$(srcdir)/'`test-istream-sized.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-sized.Tpo $(DEPDIR)/test_lib-test-istream-sized.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-sized.c' object='test_lib-test-istream-sized.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-sized.o `test -f 'test-istream-sized.c' || echo '$(srcdir)/'`test-istream-sized.c test_lib-test-istream-sized.obj: test-istream-sized.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-sized.obj -MD -MP -MF $(DEPDIR)/test_lib-test-istream-sized.Tpo -c -o test_lib-test-istream-sized.obj `if test -f 'test-istream-sized.c'; then $(CYGPATH_W) 'test-istream-sized.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-sized.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-sized.Tpo $(DEPDIR)/test_lib-test-istream-sized.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-sized.c' object='test_lib-test-istream-sized.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-sized.obj `if test -f 'test-istream-sized.c'; then $(CYGPATH_W) 'test-istream-sized.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-sized.c'; fi` test_lib-test-istream-tee.o: test-istream-tee.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-tee.o -MD -MP -MF $(DEPDIR)/test_lib-test-istream-tee.Tpo -c -o test_lib-test-istream-tee.o `test -f 'test-istream-tee.c' || echo '$(srcdir)/'`test-istream-tee.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-tee.Tpo $(DEPDIR)/test_lib-test-istream-tee.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-tee.c' object='test_lib-test-istream-tee.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-tee.o `test -f 'test-istream-tee.c' || echo '$(srcdir)/'`test-istream-tee.c test_lib-test-istream-tee.obj: test-istream-tee.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-tee.obj -MD -MP -MF $(DEPDIR)/test_lib-test-istream-tee.Tpo -c -o test_lib-test-istream-tee.obj `if test -f 'test-istream-tee.c'; then $(CYGPATH_W) 'test-istream-tee.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-tee.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-tee.Tpo $(DEPDIR)/test_lib-test-istream-tee.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-tee.c' object='test_lib-test-istream-tee.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-tee.obj `if test -f 'test-istream-tee.c'; then $(CYGPATH_W) 'test-istream-tee.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-tee.c'; fi` test_lib-test-istream-try.o: test-istream-try.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-try.o -MD -MP -MF $(DEPDIR)/test_lib-test-istream-try.Tpo -c -o test_lib-test-istream-try.o `test -f 'test-istream-try.c' || echo '$(srcdir)/'`test-istream-try.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-try.Tpo $(DEPDIR)/test_lib-test-istream-try.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-try.c' object='test_lib-test-istream-try.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-try.o `test -f 'test-istream-try.c' || echo '$(srcdir)/'`test-istream-try.c test_lib-test-istream-try.obj: test-istream-try.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-try.obj -MD -MP -MF $(DEPDIR)/test_lib-test-istream-try.Tpo -c -o test_lib-test-istream-try.obj `if test -f 'test-istream-try.c'; then $(CYGPATH_W) 'test-istream-try.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-try.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-try.Tpo $(DEPDIR)/test_lib-test-istream-try.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-try.c' object='test_lib-test-istream-try.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-try.obj `if test -f 'test-istream-try.c'; then $(CYGPATH_W) 'test-istream-try.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-try.c'; fi` test_lib-test-istream-unix.o: test-istream-unix.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-unix.o -MD -MP -MF $(DEPDIR)/test_lib-test-istream-unix.Tpo -c -o test_lib-test-istream-unix.o `test -f 'test-istream-unix.c' || echo '$(srcdir)/'`test-istream-unix.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-unix.Tpo $(DEPDIR)/test_lib-test-istream-unix.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-unix.c' object='test_lib-test-istream-unix.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-unix.o `test -f 'test-istream-unix.c' || echo '$(srcdir)/'`test-istream-unix.c test_lib-test-istream-unix.obj: test-istream-unix.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-istream-unix.obj -MD -MP -MF $(DEPDIR)/test_lib-test-istream-unix.Tpo -c -o test_lib-test-istream-unix.obj `if test -f 'test-istream-unix.c'; then $(CYGPATH_W) 'test-istream-unix.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-unix.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-istream-unix.Tpo $(DEPDIR)/test_lib-test-istream-unix.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-istream-unix.c' object='test_lib-test-istream-unix.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-istream-unix.obj `if test -f 'test-istream-unix.c'; then $(CYGPATH_W) 'test-istream-unix.c'; else $(CYGPATH_W) '$(srcdir)/test-istream-unix.c'; fi` test_lib-test-json-parser.o: test-json-parser.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-json-parser.o -MD -MP -MF $(DEPDIR)/test_lib-test-json-parser.Tpo -c -o test_lib-test-json-parser.o `test -f 'test-json-parser.c' || echo '$(srcdir)/'`test-json-parser.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-json-parser.Tpo $(DEPDIR)/test_lib-test-json-parser.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-json-parser.c' object='test_lib-test-json-parser.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-json-parser.o `test -f 'test-json-parser.c' || echo '$(srcdir)/'`test-json-parser.c test_lib-test-json-parser.obj: test-json-parser.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-json-parser.obj -MD -MP -MF $(DEPDIR)/test_lib-test-json-parser.Tpo -c -o test_lib-test-json-parser.obj `if test -f 'test-json-parser.c'; then $(CYGPATH_W) 'test-json-parser.c'; else $(CYGPATH_W) '$(srcdir)/test-json-parser.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-json-parser.Tpo $(DEPDIR)/test_lib-test-json-parser.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-json-parser.c' object='test_lib-test-json-parser.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-json-parser.obj `if test -f 'test-json-parser.c'; then $(CYGPATH_W) 'test-json-parser.c'; else $(CYGPATH_W) '$(srcdir)/test-json-parser.c'; fi` test_lib-test-json-tree.o: test-json-tree.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-json-tree.o -MD -MP -MF $(DEPDIR)/test_lib-test-json-tree.Tpo -c -o test_lib-test-json-tree.o `test -f 'test-json-tree.c' || echo '$(srcdir)/'`test-json-tree.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-json-tree.Tpo $(DEPDIR)/test_lib-test-json-tree.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-json-tree.c' object='test_lib-test-json-tree.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-json-tree.o `test -f 'test-json-tree.c' || echo '$(srcdir)/'`test-json-tree.c test_lib-test-json-tree.obj: test-json-tree.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-json-tree.obj -MD -MP -MF $(DEPDIR)/test_lib-test-json-tree.Tpo -c -o test_lib-test-json-tree.obj `if test -f 'test-json-tree.c'; then $(CYGPATH_W) 'test-json-tree.c'; else $(CYGPATH_W) '$(srcdir)/test-json-tree.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-json-tree.Tpo $(DEPDIR)/test_lib-test-json-tree.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-json-tree.c' object='test_lib-test-json-tree.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-json-tree.obj `if test -f 'test-json-tree.c'; then $(CYGPATH_W) 'test-json-tree.c'; else $(CYGPATH_W) '$(srcdir)/test-json-tree.c'; fi` test_lib-test-lib-event.o: test-lib-event.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-lib-event.o -MD -MP -MF $(DEPDIR)/test_lib-test-lib-event.Tpo -c -o test_lib-test-lib-event.o `test -f 'test-lib-event.c' || echo '$(srcdir)/'`test-lib-event.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-lib-event.Tpo $(DEPDIR)/test_lib-test-lib-event.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-lib-event.c' object='test_lib-test-lib-event.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-lib-event.o `test -f 'test-lib-event.c' || echo '$(srcdir)/'`test-lib-event.c test_lib-test-lib-event.obj: test-lib-event.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-lib-event.obj -MD -MP -MF $(DEPDIR)/test_lib-test-lib-event.Tpo -c -o test_lib-test-lib-event.obj `if test -f 'test-lib-event.c'; then $(CYGPATH_W) 'test-lib-event.c'; else $(CYGPATH_W) '$(srcdir)/test-lib-event.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-lib-event.Tpo $(DEPDIR)/test_lib-test-lib-event.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-lib-event.c' object='test_lib-test-lib-event.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-lib-event.obj `if test -f 'test-lib-event.c'; then $(CYGPATH_W) 'test-lib-event.c'; else $(CYGPATH_W) '$(srcdir)/test-lib-event.c'; fi` test_lib-test-lib-signals.o: test-lib-signals.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-lib-signals.o -MD -MP -MF $(DEPDIR)/test_lib-test-lib-signals.Tpo -c -o test_lib-test-lib-signals.o `test -f 'test-lib-signals.c' || echo '$(srcdir)/'`test-lib-signals.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-lib-signals.Tpo $(DEPDIR)/test_lib-test-lib-signals.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-lib-signals.c' object='test_lib-test-lib-signals.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-lib-signals.o `test -f 'test-lib-signals.c' || echo '$(srcdir)/'`test-lib-signals.c test_lib-test-lib-signals.obj: test-lib-signals.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-lib-signals.obj -MD -MP -MF $(DEPDIR)/test_lib-test-lib-signals.Tpo -c -o test_lib-test-lib-signals.obj `if test -f 'test-lib-signals.c'; then $(CYGPATH_W) 'test-lib-signals.c'; else $(CYGPATH_W) '$(srcdir)/test-lib-signals.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-lib-signals.Tpo $(DEPDIR)/test_lib-test-lib-signals.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-lib-signals.c' object='test_lib-test-lib-signals.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-lib-signals.obj `if test -f 'test-lib-signals.c'; then $(CYGPATH_W) 'test-lib-signals.c'; else $(CYGPATH_W) '$(srcdir)/test-lib-signals.c'; fi` test_lib-test-llist.o: test-llist.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-llist.o -MD -MP -MF $(DEPDIR)/test_lib-test-llist.Tpo -c -o test_lib-test-llist.o `test -f 'test-llist.c' || echo '$(srcdir)/'`test-llist.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-llist.Tpo $(DEPDIR)/test_lib-test-llist.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-llist.c' object='test_lib-test-llist.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-llist.o `test -f 'test-llist.c' || echo '$(srcdir)/'`test-llist.c test_lib-test-llist.obj: test-llist.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-llist.obj -MD -MP -MF $(DEPDIR)/test_lib-test-llist.Tpo -c -o test_lib-test-llist.obj `if test -f 'test-llist.c'; then $(CYGPATH_W) 'test-llist.c'; else $(CYGPATH_W) '$(srcdir)/test-llist.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-llist.Tpo $(DEPDIR)/test_lib-test-llist.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-llist.c' object='test_lib-test-llist.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-llist.obj `if test -f 'test-llist.c'; then $(CYGPATH_W) 'test-llist.c'; else $(CYGPATH_W) '$(srcdir)/test-llist.c'; fi` test_lib-test-log-throttle.o: test-log-throttle.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-log-throttle.o -MD -MP -MF $(DEPDIR)/test_lib-test-log-throttle.Tpo -c -o test_lib-test-log-throttle.o `test -f 'test-log-throttle.c' || echo '$(srcdir)/'`test-log-throttle.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-log-throttle.Tpo $(DEPDIR)/test_lib-test-log-throttle.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-log-throttle.c' object='test_lib-test-log-throttle.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-log-throttle.o `test -f 'test-log-throttle.c' || echo '$(srcdir)/'`test-log-throttle.c test_lib-test-log-throttle.obj: test-log-throttle.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-log-throttle.obj -MD -MP -MF $(DEPDIR)/test_lib-test-log-throttle.Tpo -c -o test_lib-test-log-throttle.obj `if test -f 'test-log-throttle.c'; then $(CYGPATH_W) 'test-log-throttle.c'; else $(CYGPATH_W) '$(srcdir)/test-log-throttle.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-log-throttle.Tpo $(DEPDIR)/test_lib-test-log-throttle.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-log-throttle.c' object='test_lib-test-log-throttle.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-log-throttle.obj `if test -f 'test-log-throttle.c'; then $(CYGPATH_W) 'test-log-throttle.c'; else $(CYGPATH_W) '$(srcdir)/test-log-throttle.c'; fi` test_lib-test-macros.o: test-macros.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-macros.o -MD -MP -MF $(DEPDIR)/test_lib-test-macros.Tpo -c -o test_lib-test-macros.o `test -f 'test-macros.c' || echo '$(srcdir)/'`test-macros.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-macros.Tpo $(DEPDIR)/test_lib-test-macros.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-macros.c' object='test_lib-test-macros.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-macros.o `test -f 'test-macros.c' || echo '$(srcdir)/'`test-macros.c test_lib-test-macros.obj: test-macros.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-macros.obj -MD -MP -MF $(DEPDIR)/test_lib-test-macros.Tpo -c -o test_lib-test-macros.obj `if test -f 'test-macros.c'; then $(CYGPATH_W) 'test-macros.c'; else $(CYGPATH_W) '$(srcdir)/test-macros.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-macros.Tpo $(DEPDIR)/test_lib-test-macros.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-macros.c' object='test_lib-test-macros.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-macros.obj `if test -f 'test-macros.c'; then $(CYGPATH_W) 'test-macros.c'; else $(CYGPATH_W) '$(srcdir)/test-macros.c'; fi` test_lib-test-malloc-overflow.o: test-malloc-overflow.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-malloc-overflow.o -MD -MP -MF $(DEPDIR)/test_lib-test-malloc-overflow.Tpo -c -o test_lib-test-malloc-overflow.o `test -f 'test-malloc-overflow.c' || echo '$(srcdir)/'`test-malloc-overflow.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-malloc-overflow.Tpo $(DEPDIR)/test_lib-test-malloc-overflow.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-malloc-overflow.c' object='test_lib-test-malloc-overflow.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-malloc-overflow.o `test -f 'test-malloc-overflow.c' || echo '$(srcdir)/'`test-malloc-overflow.c test_lib-test-malloc-overflow.obj: test-malloc-overflow.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-malloc-overflow.obj -MD -MP -MF $(DEPDIR)/test_lib-test-malloc-overflow.Tpo -c -o test_lib-test-malloc-overflow.obj `if test -f 'test-malloc-overflow.c'; then $(CYGPATH_W) 'test-malloc-overflow.c'; else $(CYGPATH_W) '$(srcdir)/test-malloc-overflow.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-malloc-overflow.Tpo $(DEPDIR)/test_lib-test-malloc-overflow.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-malloc-overflow.c' object='test_lib-test-malloc-overflow.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-malloc-overflow.obj `if test -f 'test-malloc-overflow.c'; then $(CYGPATH_W) 'test-malloc-overflow.c'; else $(CYGPATH_W) '$(srcdir)/test-malloc-overflow.c'; fi` test_lib-test-memarea.o: test-memarea.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-memarea.o -MD -MP -MF $(DEPDIR)/test_lib-test-memarea.Tpo -c -o test_lib-test-memarea.o `test -f 'test-memarea.c' || echo '$(srcdir)/'`test-memarea.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-memarea.Tpo $(DEPDIR)/test_lib-test-memarea.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-memarea.c' object='test_lib-test-memarea.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-memarea.o `test -f 'test-memarea.c' || echo '$(srcdir)/'`test-memarea.c test_lib-test-memarea.obj: test-memarea.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-memarea.obj -MD -MP -MF $(DEPDIR)/test_lib-test-memarea.Tpo -c -o test_lib-test-memarea.obj `if test -f 'test-memarea.c'; then $(CYGPATH_W) 'test-memarea.c'; else $(CYGPATH_W) '$(srcdir)/test-memarea.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-memarea.Tpo $(DEPDIR)/test_lib-test-memarea.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-memarea.c' object='test_lib-test-memarea.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-memarea.obj `if test -f 'test-memarea.c'; then $(CYGPATH_W) 'test-memarea.c'; else $(CYGPATH_W) '$(srcdir)/test-memarea.c'; fi` test_lib-test-mempool.o: test-mempool.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-mempool.o -MD -MP -MF $(DEPDIR)/test_lib-test-mempool.Tpo -c -o test_lib-test-mempool.o `test -f 'test-mempool.c' || echo '$(srcdir)/'`test-mempool.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-mempool.Tpo $(DEPDIR)/test_lib-test-mempool.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-mempool.c' object='test_lib-test-mempool.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-mempool.o `test -f 'test-mempool.c' || echo '$(srcdir)/'`test-mempool.c test_lib-test-mempool.obj: test-mempool.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-mempool.obj -MD -MP -MF $(DEPDIR)/test_lib-test-mempool.Tpo -c -o test_lib-test-mempool.obj `if test -f 'test-mempool.c'; then $(CYGPATH_W) 'test-mempool.c'; else $(CYGPATH_W) '$(srcdir)/test-mempool.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-mempool.Tpo $(DEPDIR)/test_lib-test-mempool.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-mempool.c' object='test_lib-test-mempool.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-mempool.obj `if test -f 'test-mempool.c'; then $(CYGPATH_W) 'test-mempool.c'; else $(CYGPATH_W) '$(srcdir)/test-mempool.c'; fi` test_lib-test-mempool-allocfree.o: test-mempool-allocfree.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-mempool-allocfree.o -MD -MP -MF $(DEPDIR)/test_lib-test-mempool-allocfree.Tpo -c -o test_lib-test-mempool-allocfree.o `test -f 'test-mempool-allocfree.c' || echo '$(srcdir)/'`test-mempool-allocfree.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-mempool-allocfree.Tpo $(DEPDIR)/test_lib-test-mempool-allocfree.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-mempool-allocfree.c' object='test_lib-test-mempool-allocfree.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-mempool-allocfree.o `test -f 'test-mempool-allocfree.c' || echo '$(srcdir)/'`test-mempool-allocfree.c test_lib-test-mempool-allocfree.obj: test-mempool-allocfree.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-mempool-allocfree.obj -MD -MP -MF $(DEPDIR)/test_lib-test-mempool-allocfree.Tpo -c -o test_lib-test-mempool-allocfree.obj `if test -f 'test-mempool-allocfree.c'; then $(CYGPATH_W) 'test-mempool-allocfree.c'; else $(CYGPATH_W) '$(srcdir)/test-mempool-allocfree.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-mempool-allocfree.Tpo $(DEPDIR)/test_lib-test-mempool-allocfree.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-mempool-allocfree.c' object='test_lib-test-mempool-allocfree.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-mempool-allocfree.obj `if test -f 'test-mempool-allocfree.c'; then $(CYGPATH_W) 'test-mempool-allocfree.c'; else $(CYGPATH_W) '$(srcdir)/test-mempool-allocfree.c'; fi` test_lib-test-mempool-alloconly.o: test-mempool-alloconly.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-mempool-alloconly.o -MD -MP -MF $(DEPDIR)/test_lib-test-mempool-alloconly.Tpo -c -o test_lib-test-mempool-alloconly.o `test -f 'test-mempool-alloconly.c' || echo '$(srcdir)/'`test-mempool-alloconly.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-mempool-alloconly.Tpo $(DEPDIR)/test_lib-test-mempool-alloconly.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-mempool-alloconly.c' object='test_lib-test-mempool-alloconly.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-mempool-alloconly.o `test -f 'test-mempool-alloconly.c' || echo '$(srcdir)/'`test-mempool-alloconly.c test_lib-test-mempool-alloconly.obj: test-mempool-alloconly.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-mempool-alloconly.obj -MD -MP -MF $(DEPDIR)/test_lib-test-mempool-alloconly.Tpo -c -o test_lib-test-mempool-alloconly.obj `if test -f 'test-mempool-alloconly.c'; then $(CYGPATH_W) 'test-mempool-alloconly.c'; else $(CYGPATH_W) '$(srcdir)/test-mempool-alloconly.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-mempool-alloconly.Tpo $(DEPDIR)/test_lib-test-mempool-alloconly.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-mempool-alloconly.c' object='test_lib-test-mempool-alloconly.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-mempool-alloconly.obj `if test -f 'test-mempool-alloconly.c'; then $(CYGPATH_W) 'test-mempool-alloconly.c'; else $(CYGPATH_W) '$(srcdir)/test-mempool-alloconly.c'; fi` test_lib-test-pkcs5.o: test-pkcs5.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-pkcs5.o -MD -MP -MF $(DEPDIR)/test_lib-test-pkcs5.Tpo -c -o test_lib-test-pkcs5.o `test -f 'test-pkcs5.c' || echo '$(srcdir)/'`test-pkcs5.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-pkcs5.Tpo $(DEPDIR)/test_lib-test-pkcs5.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-pkcs5.c' object='test_lib-test-pkcs5.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-pkcs5.o `test -f 'test-pkcs5.c' || echo '$(srcdir)/'`test-pkcs5.c test_lib-test-pkcs5.obj: test-pkcs5.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-pkcs5.obj -MD -MP -MF $(DEPDIR)/test_lib-test-pkcs5.Tpo -c -o test_lib-test-pkcs5.obj `if test -f 'test-pkcs5.c'; then $(CYGPATH_W) 'test-pkcs5.c'; else $(CYGPATH_W) '$(srcdir)/test-pkcs5.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-pkcs5.Tpo $(DEPDIR)/test_lib-test-pkcs5.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-pkcs5.c' object='test_lib-test-pkcs5.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-pkcs5.obj `if test -f 'test-pkcs5.c'; then $(CYGPATH_W) 'test-pkcs5.c'; else $(CYGPATH_W) '$(srcdir)/test-pkcs5.c'; fi` test_lib-test-net.o: test-net.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-net.o -MD -MP -MF $(DEPDIR)/test_lib-test-net.Tpo -c -o test_lib-test-net.o `test -f 'test-net.c' || echo '$(srcdir)/'`test-net.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-net.Tpo $(DEPDIR)/test_lib-test-net.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-net.c' object='test_lib-test-net.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-net.o `test -f 'test-net.c' || echo '$(srcdir)/'`test-net.c test_lib-test-net.obj: test-net.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-net.obj -MD -MP -MF $(DEPDIR)/test_lib-test-net.Tpo -c -o test_lib-test-net.obj `if test -f 'test-net.c'; then $(CYGPATH_W) 'test-net.c'; else $(CYGPATH_W) '$(srcdir)/test-net.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-net.Tpo $(DEPDIR)/test_lib-test-net.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-net.c' object='test_lib-test-net.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-net.obj `if test -f 'test-net.c'; then $(CYGPATH_W) 'test-net.c'; else $(CYGPATH_W) '$(srcdir)/test-net.c'; fi` test_lib-test-numpack.o: test-numpack.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-numpack.o -MD -MP -MF $(DEPDIR)/test_lib-test-numpack.Tpo -c -o test_lib-test-numpack.o `test -f 'test-numpack.c' || echo '$(srcdir)/'`test-numpack.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-numpack.Tpo $(DEPDIR)/test_lib-test-numpack.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-numpack.c' object='test_lib-test-numpack.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-numpack.o `test -f 'test-numpack.c' || echo '$(srcdir)/'`test-numpack.c test_lib-test-numpack.obj: test-numpack.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-numpack.obj -MD -MP -MF $(DEPDIR)/test_lib-test-numpack.Tpo -c -o test_lib-test-numpack.obj `if test -f 'test-numpack.c'; then $(CYGPATH_W) 'test-numpack.c'; else $(CYGPATH_W) '$(srcdir)/test-numpack.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-numpack.Tpo $(DEPDIR)/test_lib-test-numpack.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-numpack.c' object='test_lib-test-numpack.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-numpack.obj `if test -f 'test-numpack.c'; then $(CYGPATH_W) 'test-numpack.c'; else $(CYGPATH_W) '$(srcdir)/test-numpack.c'; fi` test_lib-test-ostream-buffer.o: test-ostream-buffer.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-ostream-buffer.o -MD -MP -MF $(DEPDIR)/test_lib-test-ostream-buffer.Tpo -c -o test_lib-test-ostream-buffer.o `test -f 'test-ostream-buffer.c' || echo '$(srcdir)/'`test-ostream-buffer.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-ostream-buffer.Tpo $(DEPDIR)/test_lib-test-ostream-buffer.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-ostream-buffer.c' object='test_lib-test-ostream-buffer.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-ostream-buffer.o `test -f 'test-ostream-buffer.c' || echo '$(srcdir)/'`test-ostream-buffer.c test_lib-test-ostream-buffer.obj: test-ostream-buffer.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-ostream-buffer.obj -MD -MP -MF $(DEPDIR)/test_lib-test-ostream-buffer.Tpo -c -o test_lib-test-ostream-buffer.obj `if test -f 'test-ostream-buffer.c'; then $(CYGPATH_W) 'test-ostream-buffer.c'; else $(CYGPATH_W) '$(srcdir)/test-ostream-buffer.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-ostream-buffer.Tpo $(DEPDIR)/test_lib-test-ostream-buffer.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-ostream-buffer.c' object='test_lib-test-ostream-buffer.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-ostream-buffer.obj `if test -f 'test-ostream-buffer.c'; then $(CYGPATH_W) 'test-ostream-buffer.c'; else $(CYGPATH_W) '$(srcdir)/test-ostream-buffer.c'; fi` test_lib-test-ostream-failure-at.o: test-ostream-failure-at.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-ostream-failure-at.o -MD -MP -MF $(DEPDIR)/test_lib-test-ostream-failure-at.Tpo -c -o test_lib-test-ostream-failure-at.o `test -f 'test-ostream-failure-at.c' || echo '$(srcdir)/'`test-ostream-failure-at.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-ostream-failure-at.Tpo $(DEPDIR)/test_lib-test-ostream-failure-at.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-ostream-failure-at.c' object='test_lib-test-ostream-failure-at.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-ostream-failure-at.o `test -f 'test-ostream-failure-at.c' || echo '$(srcdir)/'`test-ostream-failure-at.c test_lib-test-ostream-failure-at.obj: test-ostream-failure-at.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-ostream-failure-at.obj -MD -MP -MF $(DEPDIR)/test_lib-test-ostream-failure-at.Tpo -c -o test_lib-test-ostream-failure-at.obj `if test -f 'test-ostream-failure-at.c'; then $(CYGPATH_W) 'test-ostream-failure-at.c'; else $(CYGPATH_W) '$(srcdir)/test-ostream-failure-at.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-ostream-failure-at.Tpo $(DEPDIR)/test_lib-test-ostream-failure-at.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-ostream-failure-at.c' object='test_lib-test-ostream-failure-at.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-ostream-failure-at.obj `if test -f 'test-ostream-failure-at.c'; then $(CYGPATH_W) 'test-ostream-failure-at.c'; else $(CYGPATH_W) '$(srcdir)/test-ostream-failure-at.c'; fi` test_lib-test-ostream-file.o: test-ostream-file.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-ostream-file.o -MD -MP -MF $(DEPDIR)/test_lib-test-ostream-file.Tpo -c -o test_lib-test-ostream-file.o `test -f 'test-ostream-file.c' || echo '$(srcdir)/'`test-ostream-file.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-ostream-file.Tpo $(DEPDIR)/test_lib-test-ostream-file.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-ostream-file.c' object='test_lib-test-ostream-file.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-ostream-file.o `test -f 'test-ostream-file.c' || echo '$(srcdir)/'`test-ostream-file.c test_lib-test-ostream-file.obj: test-ostream-file.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-ostream-file.obj -MD -MP -MF $(DEPDIR)/test_lib-test-ostream-file.Tpo -c -o test_lib-test-ostream-file.obj `if test -f 'test-ostream-file.c'; then $(CYGPATH_W) 'test-ostream-file.c'; else $(CYGPATH_W) '$(srcdir)/test-ostream-file.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-ostream-file.Tpo $(DEPDIR)/test_lib-test-ostream-file.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-ostream-file.c' object='test_lib-test-ostream-file.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-ostream-file.obj `if test -f 'test-ostream-file.c'; then $(CYGPATH_W) 'test-ostream-file.c'; else $(CYGPATH_W) '$(srcdir)/test-ostream-file.c'; fi` test_lib-test-ostream-multiplex.o: test-ostream-multiplex.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-ostream-multiplex.o -MD -MP -MF $(DEPDIR)/test_lib-test-ostream-multiplex.Tpo -c -o test_lib-test-ostream-multiplex.o `test -f 'test-ostream-multiplex.c' || echo '$(srcdir)/'`test-ostream-multiplex.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-ostream-multiplex.Tpo $(DEPDIR)/test_lib-test-ostream-multiplex.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-ostream-multiplex.c' object='test_lib-test-ostream-multiplex.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-ostream-multiplex.o `test -f 'test-ostream-multiplex.c' || echo '$(srcdir)/'`test-ostream-multiplex.c test_lib-test-ostream-multiplex.obj: test-ostream-multiplex.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-ostream-multiplex.obj -MD -MP -MF $(DEPDIR)/test_lib-test-ostream-multiplex.Tpo -c -o test_lib-test-ostream-multiplex.obj `if test -f 'test-ostream-multiplex.c'; then $(CYGPATH_W) 'test-ostream-multiplex.c'; else $(CYGPATH_W) '$(srcdir)/test-ostream-multiplex.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-ostream-multiplex.Tpo $(DEPDIR)/test_lib-test-ostream-multiplex.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-ostream-multiplex.c' object='test_lib-test-ostream-multiplex.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-ostream-multiplex.obj `if test -f 'test-ostream-multiplex.c'; then $(CYGPATH_W) 'test-ostream-multiplex.c'; else $(CYGPATH_W) '$(srcdir)/test-ostream-multiplex.c'; fi` test_lib-test-multiplex.o: test-multiplex.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-multiplex.o -MD -MP -MF $(DEPDIR)/test_lib-test-multiplex.Tpo -c -o test_lib-test-multiplex.o `test -f 'test-multiplex.c' || echo '$(srcdir)/'`test-multiplex.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-multiplex.Tpo $(DEPDIR)/test_lib-test-multiplex.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-multiplex.c' object='test_lib-test-multiplex.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-multiplex.o `test -f 'test-multiplex.c' || echo '$(srcdir)/'`test-multiplex.c test_lib-test-multiplex.obj: test-multiplex.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-multiplex.obj -MD -MP -MF $(DEPDIR)/test_lib-test-multiplex.Tpo -c -o test_lib-test-multiplex.obj `if test -f 'test-multiplex.c'; then $(CYGPATH_W) 'test-multiplex.c'; else $(CYGPATH_W) '$(srcdir)/test-multiplex.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-multiplex.Tpo $(DEPDIR)/test_lib-test-multiplex.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-multiplex.c' object='test_lib-test-multiplex.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-multiplex.obj `if test -f 'test-multiplex.c'; then $(CYGPATH_W) 'test-multiplex.c'; else $(CYGPATH_W) '$(srcdir)/test-multiplex.c'; fi` test_lib-test-path-util.o: test-path-util.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-path-util.o -MD -MP -MF $(DEPDIR)/test_lib-test-path-util.Tpo -c -o test_lib-test-path-util.o `test -f 'test-path-util.c' || echo '$(srcdir)/'`test-path-util.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-path-util.Tpo $(DEPDIR)/test_lib-test-path-util.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-path-util.c' object='test_lib-test-path-util.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-path-util.o `test -f 'test-path-util.c' || echo '$(srcdir)/'`test-path-util.c test_lib-test-path-util.obj: test-path-util.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-path-util.obj -MD -MP -MF $(DEPDIR)/test_lib-test-path-util.Tpo -c -o test_lib-test-path-util.obj `if test -f 'test-path-util.c'; then $(CYGPATH_W) 'test-path-util.c'; else $(CYGPATH_W) '$(srcdir)/test-path-util.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-path-util.Tpo $(DEPDIR)/test_lib-test-path-util.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-path-util.c' object='test_lib-test-path-util.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-path-util.obj `if test -f 'test-path-util.c'; then $(CYGPATH_W) 'test-path-util.c'; else $(CYGPATH_W) '$(srcdir)/test-path-util.c'; fi` test_lib-test-primes.o: test-primes.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-primes.o -MD -MP -MF $(DEPDIR)/test_lib-test-primes.Tpo -c -o test_lib-test-primes.o `test -f 'test-primes.c' || echo '$(srcdir)/'`test-primes.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-primes.Tpo $(DEPDIR)/test_lib-test-primes.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-primes.c' object='test_lib-test-primes.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-primes.o `test -f 'test-primes.c' || echo '$(srcdir)/'`test-primes.c test_lib-test-primes.obj: test-primes.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-primes.obj -MD -MP -MF $(DEPDIR)/test_lib-test-primes.Tpo -c -o test_lib-test-primes.obj `if test -f 'test-primes.c'; then $(CYGPATH_W) 'test-primes.c'; else $(CYGPATH_W) '$(srcdir)/test-primes.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-primes.Tpo $(DEPDIR)/test_lib-test-primes.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-primes.c' object='test_lib-test-primes.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-primes.obj `if test -f 'test-primes.c'; then $(CYGPATH_W) 'test-primes.c'; else $(CYGPATH_W) '$(srcdir)/test-primes.c'; fi` test_lib-test-printf-format-fix.o: test-printf-format-fix.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-printf-format-fix.o -MD -MP -MF $(DEPDIR)/test_lib-test-printf-format-fix.Tpo -c -o test_lib-test-printf-format-fix.o `test -f 'test-printf-format-fix.c' || echo '$(srcdir)/'`test-printf-format-fix.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-printf-format-fix.Tpo $(DEPDIR)/test_lib-test-printf-format-fix.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-printf-format-fix.c' object='test_lib-test-printf-format-fix.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-printf-format-fix.o `test -f 'test-printf-format-fix.c' || echo '$(srcdir)/'`test-printf-format-fix.c test_lib-test-printf-format-fix.obj: test-printf-format-fix.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-printf-format-fix.obj -MD -MP -MF $(DEPDIR)/test_lib-test-printf-format-fix.Tpo -c -o test_lib-test-printf-format-fix.obj `if test -f 'test-printf-format-fix.c'; then $(CYGPATH_W) 'test-printf-format-fix.c'; else $(CYGPATH_W) '$(srcdir)/test-printf-format-fix.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-printf-format-fix.Tpo $(DEPDIR)/test_lib-test-printf-format-fix.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-printf-format-fix.c' object='test_lib-test-printf-format-fix.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-printf-format-fix.obj `if test -f 'test-printf-format-fix.c'; then $(CYGPATH_W) 'test-printf-format-fix.c'; else $(CYGPATH_W) '$(srcdir)/test-printf-format-fix.c'; fi` test_lib-test-priorityq.o: test-priorityq.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-priorityq.o -MD -MP -MF $(DEPDIR)/test_lib-test-priorityq.Tpo -c -o test_lib-test-priorityq.o `test -f 'test-priorityq.c' || echo '$(srcdir)/'`test-priorityq.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-priorityq.Tpo $(DEPDIR)/test_lib-test-priorityq.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-priorityq.c' object='test_lib-test-priorityq.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-priorityq.o `test -f 'test-priorityq.c' || echo '$(srcdir)/'`test-priorityq.c test_lib-test-priorityq.obj: test-priorityq.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-priorityq.obj -MD -MP -MF $(DEPDIR)/test_lib-test-priorityq.Tpo -c -o test_lib-test-priorityq.obj `if test -f 'test-priorityq.c'; then $(CYGPATH_W) 'test-priorityq.c'; else $(CYGPATH_W) '$(srcdir)/test-priorityq.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-priorityq.Tpo $(DEPDIR)/test_lib-test-priorityq.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-priorityq.c' object='test_lib-test-priorityq.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-priorityq.obj `if test -f 'test-priorityq.c'; then $(CYGPATH_W) 'test-priorityq.c'; else $(CYGPATH_W) '$(srcdir)/test-priorityq.c'; fi` test_lib-test-random.o: test-random.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-random.o -MD -MP -MF $(DEPDIR)/test_lib-test-random.Tpo -c -o test_lib-test-random.o `test -f 'test-random.c' || echo '$(srcdir)/'`test-random.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-random.Tpo $(DEPDIR)/test_lib-test-random.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-random.c' object='test_lib-test-random.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-random.o `test -f 'test-random.c' || echo '$(srcdir)/'`test-random.c test_lib-test-random.obj: test-random.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-random.obj -MD -MP -MF $(DEPDIR)/test_lib-test-random.Tpo -c -o test_lib-test-random.obj `if test -f 'test-random.c'; then $(CYGPATH_W) 'test-random.c'; else $(CYGPATH_W) '$(srcdir)/test-random.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-random.Tpo $(DEPDIR)/test_lib-test-random.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-random.c' object='test_lib-test-random.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-random.obj `if test -f 'test-random.c'; then $(CYGPATH_W) 'test-random.c'; else $(CYGPATH_W) '$(srcdir)/test-random.c'; fi` test_lib-test-seq-range-array.o: test-seq-range-array.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-seq-range-array.o -MD -MP -MF $(DEPDIR)/test_lib-test-seq-range-array.Tpo -c -o test_lib-test-seq-range-array.o `test -f 'test-seq-range-array.c' || echo '$(srcdir)/'`test-seq-range-array.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-seq-range-array.Tpo $(DEPDIR)/test_lib-test-seq-range-array.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-seq-range-array.c' object='test_lib-test-seq-range-array.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-seq-range-array.o `test -f 'test-seq-range-array.c' || echo '$(srcdir)/'`test-seq-range-array.c test_lib-test-seq-range-array.obj: test-seq-range-array.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-seq-range-array.obj -MD -MP -MF $(DEPDIR)/test_lib-test-seq-range-array.Tpo -c -o test_lib-test-seq-range-array.obj `if test -f 'test-seq-range-array.c'; then $(CYGPATH_W) 'test-seq-range-array.c'; else $(CYGPATH_W) '$(srcdir)/test-seq-range-array.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-seq-range-array.Tpo $(DEPDIR)/test_lib-test-seq-range-array.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-seq-range-array.c' object='test_lib-test-seq-range-array.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-seq-range-array.obj `if test -f 'test-seq-range-array.c'; then $(CYGPATH_W) 'test-seq-range-array.c'; else $(CYGPATH_W) '$(srcdir)/test-seq-range-array.c'; fi` test_lib-test-seq-set-builder.o: test-seq-set-builder.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-seq-set-builder.o -MD -MP -MF $(DEPDIR)/test_lib-test-seq-set-builder.Tpo -c -o test_lib-test-seq-set-builder.o `test -f 'test-seq-set-builder.c' || echo '$(srcdir)/'`test-seq-set-builder.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-seq-set-builder.Tpo $(DEPDIR)/test_lib-test-seq-set-builder.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-seq-set-builder.c' object='test_lib-test-seq-set-builder.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-seq-set-builder.o `test -f 'test-seq-set-builder.c' || echo '$(srcdir)/'`test-seq-set-builder.c test_lib-test-seq-set-builder.obj: test-seq-set-builder.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-seq-set-builder.obj -MD -MP -MF $(DEPDIR)/test_lib-test-seq-set-builder.Tpo -c -o test_lib-test-seq-set-builder.obj `if test -f 'test-seq-set-builder.c'; then $(CYGPATH_W) 'test-seq-set-builder.c'; else $(CYGPATH_W) '$(srcdir)/test-seq-set-builder.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-seq-set-builder.Tpo $(DEPDIR)/test_lib-test-seq-set-builder.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-seq-set-builder.c' object='test_lib-test-seq-set-builder.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-seq-set-builder.obj `if test -f 'test-seq-set-builder.c'; then $(CYGPATH_W) 'test-seq-set-builder.c'; else $(CYGPATH_W) '$(srcdir)/test-seq-set-builder.c'; fi` test_lib-test-stats-dist.o: test-stats-dist.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-stats-dist.o -MD -MP -MF $(DEPDIR)/test_lib-test-stats-dist.Tpo -c -o test_lib-test-stats-dist.o `test -f 'test-stats-dist.c' || echo '$(srcdir)/'`test-stats-dist.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-stats-dist.Tpo $(DEPDIR)/test_lib-test-stats-dist.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-stats-dist.c' object='test_lib-test-stats-dist.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-stats-dist.o `test -f 'test-stats-dist.c' || echo '$(srcdir)/'`test-stats-dist.c test_lib-test-stats-dist.obj: test-stats-dist.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-stats-dist.obj -MD -MP -MF $(DEPDIR)/test_lib-test-stats-dist.Tpo -c -o test_lib-test-stats-dist.obj `if test -f 'test-stats-dist.c'; then $(CYGPATH_W) 'test-stats-dist.c'; else $(CYGPATH_W) '$(srcdir)/test-stats-dist.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-stats-dist.Tpo $(DEPDIR)/test_lib-test-stats-dist.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-stats-dist.c' object='test_lib-test-stats-dist.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-stats-dist.obj `if test -f 'test-stats-dist.c'; then $(CYGPATH_W) 'test-stats-dist.c'; else $(CYGPATH_W) '$(srcdir)/test-stats-dist.c'; fi` test_lib-test-str.o: test-str.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-str.o -MD -MP -MF $(DEPDIR)/test_lib-test-str.Tpo -c -o test_lib-test-str.o `test -f 'test-str.c' || echo '$(srcdir)/'`test-str.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-str.Tpo $(DEPDIR)/test_lib-test-str.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-str.c' object='test_lib-test-str.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-str.o `test -f 'test-str.c' || echo '$(srcdir)/'`test-str.c test_lib-test-str.obj: test-str.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-str.obj -MD -MP -MF $(DEPDIR)/test_lib-test-str.Tpo -c -o test_lib-test-str.obj `if test -f 'test-str.c'; then $(CYGPATH_W) 'test-str.c'; else $(CYGPATH_W) '$(srcdir)/test-str.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-str.Tpo $(DEPDIR)/test_lib-test-str.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-str.c' object='test_lib-test-str.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-str.obj `if test -f 'test-str.c'; then $(CYGPATH_W) 'test-str.c'; else $(CYGPATH_W) '$(srcdir)/test-str.c'; fi` test_lib-test-strescape.o: test-strescape.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-strescape.o -MD -MP -MF $(DEPDIR)/test_lib-test-strescape.Tpo -c -o test_lib-test-strescape.o `test -f 'test-strescape.c' || echo '$(srcdir)/'`test-strescape.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-strescape.Tpo $(DEPDIR)/test_lib-test-strescape.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-strescape.c' object='test_lib-test-strescape.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-strescape.o `test -f 'test-strescape.c' || echo '$(srcdir)/'`test-strescape.c test_lib-test-strescape.obj: test-strescape.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-strescape.obj -MD -MP -MF $(DEPDIR)/test_lib-test-strescape.Tpo -c -o test_lib-test-strescape.obj `if test -f 'test-strescape.c'; then $(CYGPATH_W) 'test-strescape.c'; else $(CYGPATH_W) '$(srcdir)/test-strescape.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-strescape.Tpo $(DEPDIR)/test_lib-test-strescape.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-strescape.c' object='test_lib-test-strescape.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-strescape.obj `if test -f 'test-strescape.c'; then $(CYGPATH_W) 'test-strescape.c'; else $(CYGPATH_W) '$(srcdir)/test-strescape.c'; fi` test_lib-test-strfuncs.o: test-strfuncs.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-strfuncs.o -MD -MP -MF $(DEPDIR)/test_lib-test-strfuncs.Tpo -c -o test_lib-test-strfuncs.o `test -f 'test-strfuncs.c' || echo '$(srcdir)/'`test-strfuncs.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-strfuncs.Tpo $(DEPDIR)/test_lib-test-strfuncs.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-strfuncs.c' object='test_lib-test-strfuncs.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-strfuncs.o `test -f 'test-strfuncs.c' || echo '$(srcdir)/'`test-strfuncs.c test_lib-test-strfuncs.obj: test-strfuncs.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-strfuncs.obj -MD -MP -MF $(DEPDIR)/test_lib-test-strfuncs.Tpo -c -o test_lib-test-strfuncs.obj `if test -f 'test-strfuncs.c'; then $(CYGPATH_W) 'test-strfuncs.c'; else $(CYGPATH_W) '$(srcdir)/test-strfuncs.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-strfuncs.Tpo $(DEPDIR)/test_lib-test-strfuncs.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-strfuncs.c' object='test_lib-test-strfuncs.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-strfuncs.obj `if test -f 'test-strfuncs.c'; then $(CYGPATH_W) 'test-strfuncs.c'; else $(CYGPATH_W) '$(srcdir)/test-strfuncs.c'; fi` test_lib-test-strnum.o: test-strnum.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-strnum.o -MD -MP -MF $(DEPDIR)/test_lib-test-strnum.Tpo -c -o test_lib-test-strnum.o `test -f 'test-strnum.c' || echo '$(srcdir)/'`test-strnum.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-strnum.Tpo $(DEPDIR)/test_lib-test-strnum.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-strnum.c' object='test_lib-test-strnum.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-strnum.o `test -f 'test-strnum.c' || echo '$(srcdir)/'`test-strnum.c test_lib-test-strnum.obj: test-strnum.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-strnum.obj -MD -MP -MF $(DEPDIR)/test_lib-test-strnum.Tpo -c -o test_lib-test-strnum.obj `if test -f 'test-strnum.c'; then $(CYGPATH_W) 'test-strnum.c'; else $(CYGPATH_W) '$(srcdir)/test-strnum.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-strnum.Tpo $(DEPDIR)/test_lib-test-strnum.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-strnum.c' object='test_lib-test-strnum.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-strnum.obj `if test -f 'test-strnum.c'; then $(CYGPATH_W) 'test-strnum.c'; else $(CYGPATH_W) '$(srcdir)/test-strnum.c'; fi` test_lib-test-str-find.o: test-str-find.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-str-find.o -MD -MP -MF $(DEPDIR)/test_lib-test-str-find.Tpo -c -o test_lib-test-str-find.o `test -f 'test-str-find.c' || echo '$(srcdir)/'`test-str-find.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-str-find.Tpo $(DEPDIR)/test_lib-test-str-find.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-str-find.c' object='test_lib-test-str-find.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-str-find.o `test -f 'test-str-find.c' || echo '$(srcdir)/'`test-str-find.c test_lib-test-str-find.obj: test-str-find.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-str-find.obj -MD -MP -MF $(DEPDIR)/test_lib-test-str-find.Tpo -c -o test_lib-test-str-find.obj `if test -f 'test-str-find.c'; then $(CYGPATH_W) 'test-str-find.c'; else $(CYGPATH_W) '$(srcdir)/test-str-find.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-str-find.Tpo $(DEPDIR)/test_lib-test-str-find.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-str-find.c' object='test_lib-test-str-find.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-str-find.obj `if test -f 'test-str-find.c'; then $(CYGPATH_W) 'test-str-find.c'; else $(CYGPATH_W) '$(srcdir)/test-str-find.c'; fi` test_lib-test-str-sanitize.o: test-str-sanitize.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-str-sanitize.o -MD -MP -MF $(DEPDIR)/test_lib-test-str-sanitize.Tpo -c -o test_lib-test-str-sanitize.o `test -f 'test-str-sanitize.c' || echo '$(srcdir)/'`test-str-sanitize.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-str-sanitize.Tpo $(DEPDIR)/test_lib-test-str-sanitize.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-str-sanitize.c' object='test_lib-test-str-sanitize.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-str-sanitize.o `test -f 'test-str-sanitize.c' || echo '$(srcdir)/'`test-str-sanitize.c test_lib-test-str-sanitize.obj: test-str-sanitize.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-str-sanitize.obj -MD -MP -MF $(DEPDIR)/test_lib-test-str-sanitize.Tpo -c -o test_lib-test-str-sanitize.obj `if test -f 'test-str-sanitize.c'; then $(CYGPATH_W) 'test-str-sanitize.c'; else $(CYGPATH_W) '$(srcdir)/test-str-sanitize.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-str-sanitize.Tpo $(DEPDIR)/test_lib-test-str-sanitize.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-str-sanitize.c' object='test_lib-test-str-sanitize.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-str-sanitize.obj `if test -f 'test-str-sanitize.c'; then $(CYGPATH_W) 'test-str-sanitize.c'; else $(CYGPATH_W) '$(srcdir)/test-str-sanitize.c'; fi` test_lib-test-str-table.o: test-str-table.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-str-table.o -MD -MP -MF $(DEPDIR)/test_lib-test-str-table.Tpo -c -o test_lib-test-str-table.o `test -f 'test-str-table.c' || echo '$(srcdir)/'`test-str-table.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-str-table.Tpo $(DEPDIR)/test_lib-test-str-table.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-str-table.c' object='test_lib-test-str-table.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-str-table.o `test -f 'test-str-table.c' || echo '$(srcdir)/'`test-str-table.c test_lib-test-str-table.obj: test-str-table.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-str-table.obj -MD -MP -MF $(DEPDIR)/test_lib-test-str-table.Tpo -c -o test_lib-test-str-table.obj `if test -f 'test-str-table.c'; then $(CYGPATH_W) 'test-str-table.c'; else $(CYGPATH_W) '$(srcdir)/test-str-table.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-str-table.Tpo $(DEPDIR)/test_lib-test-str-table.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-str-table.c' object='test_lib-test-str-table.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-str-table.obj `if test -f 'test-str-table.c'; then $(CYGPATH_W) 'test-str-table.c'; else $(CYGPATH_W) '$(srcdir)/test-str-table.c'; fi` test_lib-test-time-util.o: test-time-util.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-time-util.o -MD -MP -MF $(DEPDIR)/test_lib-test-time-util.Tpo -c -o test_lib-test-time-util.o `test -f 'test-time-util.c' || echo '$(srcdir)/'`test-time-util.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-time-util.Tpo $(DEPDIR)/test_lib-test-time-util.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-time-util.c' object='test_lib-test-time-util.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-time-util.o `test -f 'test-time-util.c' || echo '$(srcdir)/'`test-time-util.c test_lib-test-time-util.obj: test-time-util.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-time-util.obj -MD -MP -MF $(DEPDIR)/test_lib-test-time-util.Tpo -c -o test_lib-test-time-util.obj `if test -f 'test-time-util.c'; then $(CYGPATH_W) 'test-time-util.c'; else $(CYGPATH_W) '$(srcdir)/test-time-util.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-time-util.Tpo $(DEPDIR)/test_lib-test-time-util.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-time-util.c' object='test_lib-test-time-util.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-time-util.obj `if test -f 'test-time-util.c'; then $(CYGPATH_W) 'test-time-util.c'; else $(CYGPATH_W) '$(srcdir)/test-time-util.c'; fi` test_lib-test-unichar.o: test-unichar.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-unichar.o -MD -MP -MF $(DEPDIR)/test_lib-test-unichar.Tpo -c -o test_lib-test-unichar.o `test -f 'test-unichar.c' || echo '$(srcdir)/'`test-unichar.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-unichar.Tpo $(DEPDIR)/test_lib-test-unichar.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-unichar.c' object='test_lib-test-unichar.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-unichar.o `test -f 'test-unichar.c' || echo '$(srcdir)/'`test-unichar.c test_lib-test-unichar.obj: test-unichar.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-unichar.obj -MD -MP -MF $(DEPDIR)/test_lib-test-unichar.Tpo -c -o test_lib-test-unichar.obj `if test -f 'test-unichar.c'; then $(CYGPATH_W) 'test-unichar.c'; else $(CYGPATH_W) '$(srcdir)/test-unichar.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-unichar.Tpo $(DEPDIR)/test_lib-test-unichar.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-unichar.c' object='test_lib-test-unichar.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-unichar.obj `if test -f 'test-unichar.c'; then $(CYGPATH_W) 'test-unichar.c'; else $(CYGPATH_W) '$(srcdir)/test-unichar.c'; fi` test_lib-test-utc-mktime.o: test-utc-mktime.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-utc-mktime.o -MD -MP -MF $(DEPDIR)/test_lib-test-utc-mktime.Tpo -c -o test_lib-test-utc-mktime.o `test -f 'test-utc-mktime.c' || echo '$(srcdir)/'`test-utc-mktime.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-utc-mktime.Tpo $(DEPDIR)/test_lib-test-utc-mktime.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-utc-mktime.c' object='test_lib-test-utc-mktime.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-utc-mktime.o `test -f 'test-utc-mktime.c' || echo '$(srcdir)/'`test-utc-mktime.c test_lib-test-utc-mktime.obj: test-utc-mktime.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-utc-mktime.obj -MD -MP -MF $(DEPDIR)/test_lib-test-utc-mktime.Tpo -c -o test_lib-test-utc-mktime.obj `if test -f 'test-utc-mktime.c'; then $(CYGPATH_W) 'test-utc-mktime.c'; else $(CYGPATH_W) '$(srcdir)/test-utc-mktime.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-utc-mktime.Tpo $(DEPDIR)/test_lib-test-utc-mktime.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-utc-mktime.c' object='test_lib-test-utc-mktime.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-utc-mktime.obj `if test -f 'test-utc-mktime.c'; then $(CYGPATH_W) 'test-utc-mktime.c'; else $(CYGPATH_W) '$(srcdir)/test-utc-mktime.c'; fi` test_lib-test-uri.o: test-uri.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-uri.o -MD -MP -MF $(DEPDIR)/test_lib-test-uri.Tpo -c -o test_lib-test-uri.o `test -f 'test-uri.c' || echo '$(srcdir)/'`test-uri.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-uri.Tpo $(DEPDIR)/test_lib-test-uri.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-uri.c' object='test_lib-test-uri.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-uri.o `test -f 'test-uri.c' || echo '$(srcdir)/'`test-uri.c test_lib-test-uri.obj: test-uri.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-uri.obj -MD -MP -MF $(DEPDIR)/test_lib-test-uri.Tpo -c -o test_lib-test-uri.obj `if test -f 'test-uri.c'; then $(CYGPATH_W) 'test-uri.c'; else $(CYGPATH_W) '$(srcdir)/test-uri.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-uri.Tpo $(DEPDIR)/test_lib-test-uri.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-uri.c' object='test_lib-test-uri.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-uri.obj `if test -f 'test-uri.c'; then $(CYGPATH_W) 'test-uri.c'; else $(CYGPATH_W) '$(srcdir)/test-uri.c'; fi` test_lib-test-var-expand.o: test-var-expand.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-var-expand.o -MD -MP -MF $(DEPDIR)/test_lib-test-var-expand.Tpo -c -o test_lib-test-var-expand.o `test -f 'test-var-expand.c' || echo '$(srcdir)/'`test-var-expand.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-var-expand.Tpo $(DEPDIR)/test_lib-test-var-expand.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-var-expand.c' object='test_lib-test-var-expand.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-var-expand.o `test -f 'test-var-expand.c' || echo '$(srcdir)/'`test-var-expand.c test_lib-test-var-expand.obj: test-var-expand.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-var-expand.obj -MD -MP -MF $(DEPDIR)/test_lib-test-var-expand.Tpo -c -o test_lib-test-var-expand.obj `if test -f 'test-var-expand.c'; then $(CYGPATH_W) 'test-var-expand.c'; else $(CYGPATH_W) '$(srcdir)/test-var-expand.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-var-expand.Tpo $(DEPDIR)/test_lib-test-var-expand.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-var-expand.c' object='test_lib-test-var-expand.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-var-expand.obj `if test -f 'test-var-expand.c'; then $(CYGPATH_W) 'test-var-expand.c'; else $(CYGPATH_W) '$(srcdir)/test-var-expand.c'; fi` test_lib-test-wildcard-match.o: test-wildcard-match.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-wildcard-match.o -MD -MP -MF $(DEPDIR)/test_lib-test-wildcard-match.Tpo -c -o test_lib-test-wildcard-match.o `test -f 'test-wildcard-match.c' || echo '$(srcdir)/'`test-wildcard-match.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-wildcard-match.Tpo $(DEPDIR)/test_lib-test-wildcard-match.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-wildcard-match.c' object='test_lib-test-wildcard-match.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-wildcard-match.o `test -f 'test-wildcard-match.c' || echo '$(srcdir)/'`test-wildcard-match.c test_lib-test-wildcard-match.obj: test-wildcard-match.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_lib-test-wildcard-match.obj -MD -MP -MF $(DEPDIR)/test_lib-test-wildcard-match.Tpo -c -o test_lib-test-wildcard-match.obj `if test -f 'test-wildcard-match.c'; then $(CYGPATH_W) 'test-wildcard-match.c'; else $(CYGPATH_W) '$(srcdir)/test-wildcard-match.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_lib-test-wildcard-match.Tpo $(DEPDIR)/test_lib-test-wildcard-match.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-wildcard-match.c' object='test_lib-test-wildcard-match.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_lib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_lib-test-wildcard-match.obj `if test -f 'test-wildcard-match.c'; then $(CYGPATH_W) 'test-wildcard-match.c'; else $(CYGPATH_W) '$(srcdir)/test-wildcard-match.c'; fi` mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) @$(NORMAL_INSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ done uninstall-pkginc_libHEADERS: @$(NORMAL_UNINSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am $(MAKE) $(AM_MAKEFLAGS) check-local check: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) check-am all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkginc_libdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) install-am install-exec: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." -rm -f event-filter-lexer.c -rm -f event-filter-parser.c -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) clean: clean-am clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ clean-noinstPROGRAMS mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/aqueue.Plo -rm -f ./$(DEPDIR)/array.Plo -rm -f ./$(DEPDIR)/askpass.Plo -rm -f ./$(DEPDIR)/backtrace-string.Plo -rm -f ./$(DEPDIR)/base32.Plo -rm -f ./$(DEPDIR)/base64.Plo -rm -f ./$(DEPDIR)/bits.Plo -rm -f ./$(DEPDIR)/bsearch-insert-pos.Plo -rm -f ./$(DEPDIR)/buffer-istream.Plo -rm -f ./$(DEPDIR)/buffer.Plo -rm -f ./$(DEPDIR)/child-wait.Plo -rm -f ./$(DEPDIR)/compat.Plo -rm -f ./$(DEPDIR)/connection.Plo -rm -f ./$(DEPDIR)/cpu-limit.Plo -rm -f ./$(DEPDIR)/crc32.Plo -rm -f ./$(DEPDIR)/data-stack.Plo -rm -f ./$(DEPDIR)/eacces-error.Plo -rm -f ./$(DEPDIR)/env-util.Plo -rm -f ./$(DEPDIR)/event-filter-lexer.Plo -rm -f ./$(DEPDIR)/event-filter-parser.Plo -rm -f ./$(DEPDIR)/event-filter.Plo -rm -f ./$(DEPDIR)/event-log.Plo -rm -f ./$(DEPDIR)/execv-const.Plo -rm -f ./$(DEPDIR)/failures.Plo -rm -f ./$(DEPDIR)/fd-util.Plo -rm -f ./$(DEPDIR)/fdatasync-path.Plo -rm -f ./$(DEPDIR)/fdpass.Plo -rm -f ./$(DEPDIR)/file-cache.Plo -rm -f ./$(DEPDIR)/file-copy.Plo -rm -f ./$(DEPDIR)/file-create-locked.Plo -rm -f ./$(DEPDIR)/file-dotlock.Plo -rm -f ./$(DEPDIR)/file-lock.Plo -rm -f ./$(DEPDIR)/file-set-size.Plo -rm -f ./$(DEPDIR)/guid.Plo -rm -f ./$(DEPDIR)/hash-format.Plo -rm -f ./$(DEPDIR)/hash-method.Plo -rm -f ./$(DEPDIR)/hash.Plo -rm -f ./$(DEPDIR)/hash2.Plo -rm -f ./$(DEPDIR)/hex-binary.Plo -rm -f ./$(DEPDIR)/hex-dec.Plo -rm -f ./$(DEPDIR)/hmac-cram-md5.Plo -rm -f ./$(DEPDIR)/hmac.Plo -rm -f ./$(DEPDIR)/home-expand.Plo -rm -f ./$(DEPDIR)/hook-build.Plo -rm -f ./$(DEPDIR)/hostpid.Plo -rm -f ./$(DEPDIR)/imem.Plo -rm -f ./$(DEPDIR)/ioloop-epoll.Plo -rm -f ./$(DEPDIR)/ioloop-iolist.Plo -rm -f ./$(DEPDIR)/ioloop-kqueue.Plo -rm -f ./$(DEPDIR)/ioloop-notify-fd.Plo -rm -f ./$(DEPDIR)/ioloop-notify-inotify.Plo -rm -f ./$(DEPDIR)/ioloop-notify-kqueue.Plo -rm -f ./$(DEPDIR)/ioloop-notify-none.Plo -rm -f ./$(DEPDIR)/ioloop-poll.Plo -rm -f ./$(DEPDIR)/ioloop-select.Plo -rm -f ./$(DEPDIR)/ioloop.Plo -rm -f ./$(DEPDIR)/iostream-proxy.Plo -rm -f ./$(DEPDIR)/iostream-pump.Plo -rm -f ./$(DEPDIR)/iostream-rawlog.Plo -rm -f ./$(DEPDIR)/iostream-temp.Plo -rm -f ./$(DEPDIR)/iostream.Plo -rm -f ./$(DEPDIR)/ipwd.Plo -rm -f ./$(DEPDIR)/iso8601-date.Plo -rm -f ./$(DEPDIR)/istream-base64-decoder.Plo -rm -f ./$(DEPDIR)/istream-base64-encoder.Plo -rm -f ./$(DEPDIR)/istream-callback.Plo -rm -f ./$(DEPDIR)/istream-chain.Plo -rm -f ./$(DEPDIR)/istream-concat.Plo -rm -f ./$(DEPDIR)/istream-crlf.Plo -rm -f ./$(DEPDIR)/istream-data.Plo -rm -f ./$(DEPDIR)/istream-failure-at.Plo -rm -f ./$(DEPDIR)/istream-file.Plo -rm -f ./$(DEPDIR)/istream-hash.Plo -rm -f ./$(DEPDIR)/istream-jsonstr.Plo -rm -f ./$(DEPDIR)/istream-limit.Plo -rm -f ./$(DEPDIR)/istream-multiplex.Plo -rm -f ./$(DEPDIR)/istream-rawlog.Plo -rm -f ./$(DEPDIR)/istream-seekable.Plo -rm -f ./$(DEPDIR)/istream-sized.Plo -rm -f ./$(DEPDIR)/istream-tee.Plo -rm -f ./$(DEPDIR)/istream-timeout.Plo -rm -f ./$(DEPDIR)/istream-try.Plo -rm -f ./$(DEPDIR)/istream-unix.Plo -rm -f ./$(DEPDIR)/istream.Plo -rm -f ./$(DEPDIR)/json-parser.Plo -rm -f ./$(DEPDIR)/json-tree.Plo -rm -f ./$(DEPDIR)/lib-event.Plo -rm -f ./$(DEPDIR)/lib-signals.Plo -rm -f ./$(DEPDIR)/lib.Plo -rm -f ./$(DEPDIR)/log-throttle.Plo -rm -f ./$(DEPDIR)/md4.Plo -rm -f ./$(DEPDIR)/md5.Plo -rm -f ./$(DEPDIR)/memarea.Plo -rm -f ./$(DEPDIR)/mempool-allocfree.Plo -rm -f ./$(DEPDIR)/mempool-alloconly.Plo -rm -f ./$(DEPDIR)/mempool-datastack.Plo -rm -f ./$(DEPDIR)/mempool-system.Plo -rm -f ./$(DEPDIR)/mempool-unsafe-datastack.Plo -rm -f ./$(DEPDIR)/mempool.Plo -rm -f ./$(DEPDIR)/mkdir-parents.Plo -rm -f ./$(DEPDIR)/mmap-anon.Plo -rm -f ./$(DEPDIR)/mmap-util.Plo -rm -f ./$(DEPDIR)/module-dir.Plo -rm -f ./$(DEPDIR)/mountpoint.Plo -rm -f ./$(DEPDIR)/net.Plo -rm -f ./$(DEPDIR)/nfs-workarounds.Plo -rm -f ./$(DEPDIR)/numpack.Plo -rm -f ./$(DEPDIR)/ostream-buffer.Plo -rm -f ./$(DEPDIR)/ostream-failure-at.Plo -rm -f ./$(DEPDIR)/ostream-file.Plo -rm -f ./$(DEPDIR)/ostream-hash.Plo -rm -f ./$(DEPDIR)/ostream-multiplex.Plo -rm -f ./$(DEPDIR)/ostream-null.Plo -rm -f ./$(DEPDIR)/ostream-rawlog.Plo -rm -f ./$(DEPDIR)/ostream-unix.Plo -rm -f ./$(DEPDIR)/ostream-wrapper.Plo -rm -f ./$(DEPDIR)/ostream.Plo -rm -f ./$(DEPDIR)/path-util.Plo -rm -f ./$(DEPDIR)/pkcs5.Plo -rm -f ./$(DEPDIR)/primes.Plo -rm -f ./$(DEPDIR)/printf-format-fix.Plo -rm -f ./$(DEPDIR)/priorityq.Plo -rm -f ./$(DEPDIR)/process-stat.Plo -rm -f ./$(DEPDIR)/process-title.Plo -rm -f ./$(DEPDIR)/rand.Plo -rm -f ./$(DEPDIR)/randgen.Plo -rm -f ./$(DEPDIR)/read-full.Plo -rm -f ./$(DEPDIR)/restrict-access.Plo -rm -f ./$(DEPDIR)/restrict-process-size.Plo -rm -f ./$(DEPDIR)/safe-memset.Plo -rm -f ./$(DEPDIR)/safe-mkdir.Plo -rm -f ./$(DEPDIR)/safe-mkstemp.Plo -rm -f ./$(DEPDIR)/sendfile-util.Plo -rm -f ./$(DEPDIR)/seq-range-array.Plo -rm -f ./$(DEPDIR)/seq-set-builder.Plo -rm -f ./$(DEPDIR)/sha1.Plo -rm -f ./$(DEPDIR)/sha2.Plo -rm -f ./$(DEPDIR)/sha3.Plo -rm -f ./$(DEPDIR)/sleep.Plo -rm -f ./$(DEPDIR)/sort.Plo -rm -f ./$(DEPDIR)/stats-dist.Plo -rm -f ./$(DEPDIR)/str-find.Plo -rm -f ./$(DEPDIR)/str-sanitize.Plo -rm -f ./$(DEPDIR)/str-table.Plo -rm -f ./$(DEPDIR)/str.Plo -rm -f ./$(DEPDIR)/strescape.Plo -rm -f ./$(DEPDIR)/strfuncs.Plo -rm -f ./$(DEPDIR)/strnum.Plo -rm -f ./$(DEPDIR)/test_lib-test-aqueue.Po -rm -f ./$(DEPDIR)/test_lib-test-array.Po -rm -f ./$(DEPDIR)/test_lib-test-backtrace.Po -rm -f ./$(DEPDIR)/test_lib-test-base32.Po -rm -f ./$(DEPDIR)/test_lib-test-base64.Po -rm -f ./$(DEPDIR)/test_lib-test-bits.Po -rm -f ./$(DEPDIR)/test_lib-test-bsearch-insert-pos.Po -rm -f ./$(DEPDIR)/test_lib-test-buffer-istream.Po -rm -f ./$(DEPDIR)/test_lib-test-buffer.Po -rm -f ./$(DEPDIR)/test_lib-test-byteorder.Po -rm -f ./$(DEPDIR)/test_lib-test-connection.Po -rm -f ./$(DEPDIR)/test_lib-test-cpu-limit.Po -rm -f ./$(DEPDIR)/test_lib-test-crc32.Po -rm -f ./$(DEPDIR)/test_lib-test-data-stack.Po -rm -f ./$(DEPDIR)/test_lib-test-env-util.Po -rm -f ./$(DEPDIR)/test_lib-test-event-category-register.Po -rm -f ./$(DEPDIR)/test_lib-test-event-filter-expr.Po -rm -f ./$(DEPDIR)/test_lib-test-event-filter-merge.Po -rm -f ./$(DEPDIR)/test_lib-test-event-filter-parser.Po -rm -f ./$(DEPDIR)/test_lib-test-event-filter.Po -rm -f ./$(DEPDIR)/test_lib-test-event-flatten.Po -rm -f ./$(DEPDIR)/test_lib-test-event-log.Po -rm -f ./$(DEPDIR)/test_lib-test-failures.Po -rm -f ./$(DEPDIR)/test_lib-test-fd-util.Po -rm -f ./$(DEPDIR)/test_lib-test-file-cache.Po -rm -f ./$(DEPDIR)/test_lib-test-file-create-locked.Po -rm -f ./$(DEPDIR)/test_lib-test-guid.Po -rm -f ./$(DEPDIR)/test_lib-test-hash-format.Po -rm -f ./$(DEPDIR)/test_lib-test-hash-method.Po -rm -f ./$(DEPDIR)/test_lib-test-hash.Po -rm -f ./$(DEPDIR)/test_lib-test-hex-binary.Po -rm -f ./$(DEPDIR)/test_lib-test-hmac.Po -rm -f ./$(DEPDIR)/test_lib-test-imem.Po -rm -f ./$(DEPDIR)/test_lib-test-ioloop.Po -rm -f ./$(DEPDIR)/test_lib-test-iostream-proxy.Po -rm -f ./$(DEPDIR)/test_lib-test-iostream-pump.Po -rm -f ./$(DEPDIR)/test_lib-test-iostream-temp.Po -rm -f ./$(DEPDIR)/test_lib-test-iso8601-date.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-base64-decoder.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-base64-encoder.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-chain.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-concat.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-crlf.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-failure-at.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-jsonstr.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-multiplex.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-seekable.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-sized.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-tee.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-try.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-unix.Po -rm -f ./$(DEPDIR)/test_lib-test-istream.Po -rm -f ./$(DEPDIR)/test_lib-test-json-parser.Po -rm -f ./$(DEPDIR)/test_lib-test-json-tree.Po -rm -f ./$(DEPDIR)/test_lib-test-lib-event.Po -rm -f ./$(DEPDIR)/test_lib-test-lib-signals.Po -rm -f ./$(DEPDIR)/test_lib-test-lib.Po -rm -f ./$(DEPDIR)/test_lib-test-llist.Po -rm -f ./$(DEPDIR)/test_lib-test-log-throttle.Po -rm -f ./$(DEPDIR)/test_lib-test-macros.Po -rm -f ./$(DEPDIR)/test_lib-test-malloc-overflow.Po -rm -f ./$(DEPDIR)/test_lib-test-memarea.Po -rm -f ./$(DEPDIR)/test_lib-test-mempool-allocfree.Po -rm -f ./$(DEPDIR)/test_lib-test-mempool-alloconly.Po -rm -f ./$(DEPDIR)/test_lib-test-mempool.Po -rm -f ./$(DEPDIR)/test_lib-test-multiplex.Po -rm -f ./$(DEPDIR)/test_lib-test-net.Po -rm -f ./$(DEPDIR)/test_lib-test-numpack.Po -rm -f ./$(DEPDIR)/test_lib-test-ostream-buffer.Po -rm -f ./$(DEPDIR)/test_lib-test-ostream-failure-at.Po -rm -f ./$(DEPDIR)/test_lib-test-ostream-file.Po -rm -f ./$(DEPDIR)/test_lib-test-ostream-multiplex.Po -rm -f ./$(DEPDIR)/test_lib-test-path-util.Po -rm -f ./$(DEPDIR)/test_lib-test-pkcs5.Po -rm -f ./$(DEPDIR)/test_lib-test-primes.Po -rm -f ./$(DEPDIR)/test_lib-test-printf-format-fix.Po -rm -f ./$(DEPDIR)/test_lib-test-priorityq.Po -rm -f ./$(DEPDIR)/test_lib-test-random.Po -rm -f ./$(DEPDIR)/test_lib-test-seq-range-array.Po -rm -f ./$(DEPDIR)/test_lib-test-seq-set-builder.Po -rm -f ./$(DEPDIR)/test_lib-test-stats-dist.Po -rm -f ./$(DEPDIR)/test_lib-test-str-find.Po -rm -f ./$(DEPDIR)/test_lib-test-str-sanitize.Po -rm -f ./$(DEPDIR)/test_lib-test-str-table.Po -rm -f ./$(DEPDIR)/test_lib-test-str.Po -rm -f ./$(DEPDIR)/test_lib-test-strescape.Po -rm -f ./$(DEPDIR)/test_lib-test-strfuncs.Po -rm -f ./$(DEPDIR)/test_lib-test-strnum.Po -rm -f ./$(DEPDIR)/test_lib-test-time-util.Po -rm -f ./$(DEPDIR)/test_lib-test-unichar.Po -rm -f ./$(DEPDIR)/test_lib-test-uri.Po -rm -f ./$(DEPDIR)/test_lib-test-utc-mktime.Po -rm -f ./$(DEPDIR)/test_lib-test-var-expand.Po -rm -f ./$(DEPDIR)/test_lib-test-wildcard-match.Po -rm -f ./$(DEPDIR)/time-util.Plo -rm -f ./$(DEPDIR)/unichar.Plo -rm -f ./$(DEPDIR)/unix-socket-create.Plo -rm -f ./$(DEPDIR)/unlink-directory.Plo -rm -f ./$(DEPDIR)/unlink-old-files.Plo -rm -f ./$(DEPDIR)/uri-util.Plo -rm -f ./$(DEPDIR)/utc-mktime.Plo -rm -f ./$(DEPDIR)/utc-offset.Plo -rm -f ./$(DEPDIR)/var-expand-if.Plo -rm -f ./$(DEPDIR)/var-expand.Plo -rm -f ./$(DEPDIR)/wildcard-match.Plo -rm -f ./$(DEPDIR)/write-full.Plo -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-pkginc_libHEADERS install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/aqueue.Plo -rm -f ./$(DEPDIR)/array.Plo -rm -f ./$(DEPDIR)/askpass.Plo -rm -f ./$(DEPDIR)/backtrace-string.Plo -rm -f ./$(DEPDIR)/base32.Plo -rm -f ./$(DEPDIR)/base64.Plo -rm -f ./$(DEPDIR)/bits.Plo -rm -f ./$(DEPDIR)/bsearch-insert-pos.Plo -rm -f ./$(DEPDIR)/buffer-istream.Plo -rm -f ./$(DEPDIR)/buffer.Plo -rm -f ./$(DEPDIR)/child-wait.Plo -rm -f ./$(DEPDIR)/compat.Plo -rm -f ./$(DEPDIR)/connection.Plo -rm -f ./$(DEPDIR)/cpu-limit.Plo -rm -f ./$(DEPDIR)/crc32.Plo -rm -f ./$(DEPDIR)/data-stack.Plo -rm -f ./$(DEPDIR)/eacces-error.Plo -rm -f ./$(DEPDIR)/env-util.Plo -rm -f ./$(DEPDIR)/event-filter-lexer.Plo -rm -f ./$(DEPDIR)/event-filter-parser.Plo -rm -f ./$(DEPDIR)/event-filter.Plo -rm -f ./$(DEPDIR)/event-log.Plo -rm -f ./$(DEPDIR)/execv-const.Plo -rm -f ./$(DEPDIR)/failures.Plo -rm -f ./$(DEPDIR)/fd-util.Plo -rm -f ./$(DEPDIR)/fdatasync-path.Plo -rm -f ./$(DEPDIR)/fdpass.Plo -rm -f ./$(DEPDIR)/file-cache.Plo -rm -f ./$(DEPDIR)/file-copy.Plo -rm -f ./$(DEPDIR)/file-create-locked.Plo -rm -f ./$(DEPDIR)/file-dotlock.Plo -rm -f ./$(DEPDIR)/file-lock.Plo -rm -f ./$(DEPDIR)/file-set-size.Plo -rm -f ./$(DEPDIR)/guid.Plo -rm -f ./$(DEPDIR)/hash-format.Plo -rm -f ./$(DEPDIR)/hash-method.Plo -rm -f ./$(DEPDIR)/hash.Plo -rm -f ./$(DEPDIR)/hash2.Plo -rm -f ./$(DEPDIR)/hex-binary.Plo -rm -f ./$(DEPDIR)/hex-dec.Plo -rm -f ./$(DEPDIR)/hmac-cram-md5.Plo -rm -f ./$(DEPDIR)/hmac.Plo -rm -f ./$(DEPDIR)/home-expand.Plo -rm -f ./$(DEPDIR)/hook-build.Plo -rm -f ./$(DEPDIR)/hostpid.Plo -rm -f ./$(DEPDIR)/imem.Plo -rm -f ./$(DEPDIR)/ioloop-epoll.Plo -rm -f ./$(DEPDIR)/ioloop-iolist.Plo -rm -f ./$(DEPDIR)/ioloop-kqueue.Plo -rm -f ./$(DEPDIR)/ioloop-notify-fd.Plo -rm -f ./$(DEPDIR)/ioloop-notify-inotify.Plo -rm -f ./$(DEPDIR)/ioloop-notify-kqueue.Plo -rm -f ./$(DEPDIR)/ioloop-notify-none.Plo -rm -f ./$(DEPDIR)/ioloop-poll.Plo -rm -f ./$(DEPDIR)/ioloop-select.Plo -rm -f ./$(DEPDIR)/ioloop.Plo -rm -f ./$(DEPDIR)/iostream-proxy.Plo -rm -f ./$(DEPDIR)/iostream-pump.Plo -rm -f ./$(DEPDIR)/iostream-rawlog.Plo -rm -f ./$(DEPDIR)/iostream-temp.Plo -rm -f ./$(DEPDIR)/iostream.Plo -rm -f ./$(DEPDIR)/ipwd.Plo -rm -f ./$(DEPDIR)/iso8601-date.Plo -rm -f ./$(DEPDIR)/istream-base64-decoder.Plo -rm -f ./$(DEPDIR)/istream-base64-encoder.Plo -rm -f ./$(DEPDIR)/istream-callback.Plo -rm -f ./$(DEPDIR)/istream-chain.Plo -rm -f ./$(DEPDIR)/istream-concat.Plo -rm -f ./$(DEPDIR)/istream-crlf.Plo -rm -f ./$(DEPDIR)/istream-data.Plo -rm -f ./$(DEPDIR)/istream-failure-at.Plo -rm -f ./$(DEPDIR)/istream-file.Plo -rm -f ./$(DEPDIR)/istream-hash.Plo -rm -f ./$(DEPDIR)/istream-jsonstr.Plo -rm -f ./$(DEPDIR)/istream-limit.Plo -rm -f ./$(DEPDIR)/istream-multiplex.Plo -rm -f ./$(DEPDIR)/istream-rawlog.Plo -rm -f ./$(DEPDIR)/istream-seekable.Plo -rm -f ./$(DEPDIR)/istream-sized.Plo -rm -f ./$(DEPDIR)/istream-tee.Plo -rm -f ./$(DEPDIR)/istream-timeout.Plo -rm -f ./$(DEPDIR)/istream-try.Plo -rm -f ./$(DEPDIR)/istream-unix.Plo -rm -f ./$(DEPDIR)/istream.Plo -rm -f ./$(DEPDIR)/json-parser.Plo -rm -f ./$(DEPDIR)/json-tree.Plo -rm -f ./$(DEPDIR)/lib-event.Plo -rm -f ./$(DEPDIR)/lib-signals.Plo -rm -f ./$(DEPDIR)/lib.Plo -rm -f ./$(DEPDIR)/log-throttle.Plo -rm -f ./$(DEPDIR)/md4.Plo -rm -f ./$(DEPDIR)/md5.Plo -rm -f ./$(DEPDIR)/memarea.Plo -rm -f ./$(DEPDIR)/mempool-allocfree.Plo -rm -f ./$(DEPDIR)/mempool-alloconly.Plo -rm -f ./$(DEPDIR)/mempool-datastack.Plo -rm -f ./$(DEPDIR)/mempool-system.Plo -rm -f ./$(DEPDIR)/mempool-unsafe-datastack.Plo -rm -f ./$(DEPDIR)/mempool.Plo -rm -f ./$(DEPDIR)/mkdir-parents.Plo -rm -f ./$(DEPDIR)/mmap-anon.Plo -rm -f ./$(DEPDIR)/mmap-util.Plo -rm -f ./$(DEPDIR)/module-dir.Plo -rm -f ./$(DEPDIR)/mountpoint.Plo -rm -f ./$(DEPDIR)/net.Plo -rm -f ./$(DEPDIR)/nfs-workarounds.Plo -rm -f ./$(DEPDIR)/numpack.Plo -rm -f ./$(DEPDIR)/ostream-buffer.Plo -rm -f ./$(DEPDIR)/ostream-failure-at.Plo -rm -f ./$(DEPDIR)/ostream-file.Plo -rm -f ./$(DEPDIR)/ostream-hash.Plo -rm -f ./$(DEPDIR)/ostream-multiplex.Plo -rm -f ./$(DEPDIR)/ostream-null.Plo -rm -f ./$(DEPDIR)/ostream-rawlog.Plo -rm -f ./$(DEPDIR)/ostream-unix.Plo -rm -f ./$(DEPDIR)/ostream-wrapper.Plo -rm -f ./$(DEPDIR)/ostream.Plo -rm -f ./$(DEPDIR)/path-util.Plo -rm -f ./$(DEPDIR)/pkcs5.Plo -rm -f ./$(DEPDIR)/primes.Plo -rm -f ./$(DEPDIR)/printf-format-fix.Plo -rm -f ./$(DEPDIR)/priorityq.Plo -rm -f ./$(DEPDIR)/process-stat.Plo -rm -f ./$(DEPDIR)/process-title.Plo -rm -f ./$(DEPDIR)/rand.Plo -rm -f ./$(DEPDIR)/randgen.Plo -rm -f ./$(DEPDIR)/read-full.Plo -rm -f ./$(DEPDIR)/restrict-access.Plo -rm -f ./$(DEPDIR)/restrict-process-size.Plo -rm -f ./$(DEPDIR)/safe-memset.Plo -rm -f ./$(DEPDIR)/safe-mkdir.Plo -rm -f ./$(DEPDIR)/safe-mkstemp.Plo -rm -f ./$(DEPDIR)/sendfile-util.Plo -rm -f ./$(DEPDIR)/seq-range-array.Plo -rm -f ./$(DEPDIR)/seq-set-builder.Plo -rm -f ./$(DEPDIR)/sha1.Plo -rm -f ./$(DEPDIR)/sha2.Plo -rm -f ./$(DEPDIR)/sha3.Plo -rm -f ./$(DEPDIR)/sleep.Plo -rm -f ./$(DEPDIR)/sort.Plo -rm -f ./$(DEPDIR)/stats-dist.Plo -rm -f ./$(DEPDIR)/str-find.Plo -rm -f ./$(DEPDIR)/str-sanitize.Plo -rm -f ./$(DEPDIR)/str-table.Plo -rm -f ./$(DEPDIR)/str.Plo -rm -f ./$(DEPDIR)/strescape.Plo -rm -f ./$(DEPDIR)/strfuncs.Plo -rm -f ./$(DEPDIR)/strnum.Plo -rm -f ./$(DEPDIR)/test_lib-test-aqueue.Po -rm -f ./$(DEPDIR)/test_lib-test-array.Po -rm -f ./$(DEPDIR)/test_lib-test-backtrace.Po -rm -f ./$(DEPDIR)/test_lib-test-base32.Po -rm -f ./$(DEPDIR)/test_lib-test-base64.Po -rm -f ./$(DEPDIR)/test_lib-test-bits.Po -rm -f ./$(DEPDIR)/test_lib-test-bsearch-insert-pos.Po -rm -f ./$(DEPDIR)/test_lib-test-buffer-istream.Po -rm -f ./$(DEPDIR)/test_lib-test-buffer.Po -rm -f ./$(DEPDIR)/test_lib-test-byteorder.Po -rm -f ./$(DEPDIR)/test_lib-test-connection.Po -rm -f ./$(DEPDIR)/test_lib-test-cpu-limit.Po -rm -f ./$(DEPDIR)/test_lib-test-crc32.Po -rm -f ./$(DEPDIR)/test_lib-test-data-stack.Po -rm -f ./$(DEPDIR)/test_lib-test-env-util.Po -rm -f ./$(DEPDIR)/test_lib-test-event-category-register.Po -rm -f ./$(DEPDIR)/test_lib-test-event-filter-expr.Po -rm -f ./$(DEPDIR)/test_lib-test-event-filter-merge.Po -rm -f ./$(DEPDIR)/test_lib-test-event-filter-parser.Po -rm -f ./$(DEPDIR)/test_lib-test-event-filter.Po -rm -f ./$(DEPDIR)/test_lib-test-event-flatten.Po -rm -f ./$(DEPDIR)/test_lib-test-event-log.Po -rm -f ./$(DEPDIR)/test_lib-test-failures.Po -rm -f ./$(DEPDIR)/test_lib-test-fd-util.Po -rm -f ./$(DEPDIR)/test_lib-test-file-cache.Po -rm -f ./$(DEPDIR)/test_lib-test-file-create-locked.Po -rm -f ./$(DEPDIR)/test_lib-test-guid.Po -rm -f ./$(DEPDIR)/test_lib-test-hash-format.Po -rm -f ./$(DEPDIR)/test_lib-test-hash-method.Po -rm -f ./$(DEPDIR)/test_lib-test-hash.Po -rm -f ./$(DEPDIR)/test_lib-test-hex-binary.Po -rm -f ./$(DEPDIR)/test_lib-test-hmac.Po -rm -f ./$(DEPDIR)/test_lib-test-imem.Po -rm -f ./$(DEPDIR)/test_lib-test-ioloop.Po -rm -f ./$(DEPDIR)/test_lib-test-iostream-proxy.Po -rm -f ./$(DEPDIR)/test_lib-test-iostream-pump.Po -rm -f ./$(DEPDIR)/test_lib-test-iostream-temp.Po -rm -f ./$(DEPDIR)/test_lib-test-iso8601-date.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-base64-decoder.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-base64-encoder.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-chain.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-concat.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-crlf.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-failure-at.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-jsonstr.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-multiplex.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-seekable.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-sized.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-tee.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-try.Po -rm -f ./$(DEPDIR)/test_lib-test-istream-unix.Po -rm -f ./$(DEPDIR)/test_lib-test-istream.Po -rm -f ./$(DEPDIR)/test_lib-test-json-parser.Po -rm -f ./$(DEPDIR)/test_lib-test-json-tree.Po -rm -f ./$(DEPDIR)/test_lib-test-lib-event.Po -rm -f ./$(DEPDIR)/test_lib-test-lib-signals.Po -rm -f ./$(DEPDIR)/test_lib-test-lib.Po -rm -f ./$(DEPDIR)/test_lib-test-llist.Po -rm -f ./$(DEPDIR)/test_lib-test-log-throttle.Po -rm -f ./$(DEPDIR)/test_lib-test-macros.Po -rm -f ./$(DEPDIR)/test_lib-test-malloc-overflow.Po -rm -f ./$(DEPDIR)/test_lib-test-memarea.Po -rm -f ./$(DEPDIR)/test_lib-test-mempool-allocfree.Po -rm -f ./$(DEPDIR)/test_lib-test-mempool-alloconly.Po -rm -f ./$(DEPDIR)/test_lib-test-mempool.Po -rm -f ./$(DEPDIR)/test_lib-test-multiplex.Po -rm -f ./$(DEPDIR)/test_lib-test-net.Po -rm -f ./$(DEPDIR)/test_lib-test-numpack.Po -rm -f ./$(DEPDIR)/test_lib-test-ostream-buffer.Po -rm -f ./$(DEPDIR)/test_lib-test-ostream-failure-at.Po -rm -f ./$(DEPDIR)/test_lib-test-ostream-file.Po -rm -f ./$(DEPDIR)/test_lib-test-ostream-multiplex.Po -rm -f ./$(DEPDIR)/test_lib-test-path-util.Po -rm -f ./$(DEPDIR)/test_lib-test-pkcs5.Po -rm -f ./$(DEPDIR)/test_lib-test-primes.Po -rm -f ./$(DEPDIR)/test_lib-test-printf-format-fix.Po -rm -f ./$(DEPDIR)/test_lib-test-priorityq.Po -rm -f ./$(DEPDIR)/test_lib-test-random.Po -rm -f ./$(DEPDIR)/test_lib-test-seq-range-array.Po -rm -f ./$(DEPDIR)/test_lib-test-seq-set-builder.Po -rm -f ./$(DEPDIR)/test_lib-test-stats-dist.Po -rm -f ./$(DEPDIR)/test_lib-test-str-find.Po -rm -f ./$(DEPDIR)/test_lib-test-str-sanitize.Po -rm -f ./$(DEPDIR)/test_lib-test-str-table.Po -rm -f ./$(DEPDIR)/test_lib-test-str.Po -rm -f ./$(DEPDIR)/test_lib-test-strescape.Po -rm -f ./$(DEPDIR)/test_lib-test-strfuncs.Po -rm -f ./$(DEPDIR)/test_lib-test-strnum.Po -rm -f ./$(DEPDIR)/test_lib-test-time-util.Po -rm -f ./$(DEPDIR)/test_lib-test-unichar.Po -rm -f ./$(DEPDIR)/test_lib-test-uri.Po -rm -f ./$(DEPDIR)/test_lib-test-utc-mktime.Po -rm -f ./$(DEPDIR)/test_lib-test-var-expand.Po -rm -f ./$(DEPDIR)/test_lib-test-wildcard-match.Po -rm -f ./$(DEPDIR)/time-util.Plo -rm -f ./$(DEPDIR)/unichar.Plo -rm -f ./$(DEPDIR)/unix-socket-create.Plo -rm -f ./$(DEPDIR)/unlink-directory.Plo -rm -f ./$(DEPDIR)/unlink-old-files.Plo -rm -f ./$(DEPDIR)/uri-util.Plo -rm -f ./$(DEPDIR)/utc-mktime.Plo -rm -f ./$(DEPDIR)/utc-offset.Plo -rm -f ./$(DEPDIR)/var-expand-if.Plo -rm -f ./$(DEPDIR)/var-expand.Plo -rm -f ./$(DEPDIR)/wildcard-match.Plo -rm -f ./$(DEPDIR)/write-full.Plo -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkginc_libHEADERS .MAKE: all check check-am install install-am install-exec \ install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ check-local clean clean-generic clean-libtool \ clean-noinstLTLIBRARIES clean-noinstPROGRAMS cscopelist-am \ ctags ctags-am distclean distclean-compile distclean-generic \ distclean-libtool distclean-tags distdir dvi dvi-am html \ html-am info info-am install install-am install-data \ install-data-am install-dvi install-dvi-am install-exec \ install-exec-am install-html install-html-am install-info \ install-info-am install-man install-pdf install-pdf-am \ install-pkginc_libHEADERS install-ps install-ps-am \ install-strip installcheck installcheck-am installdirs \ maintainer-clean maintainer-clean-generic mostlyclean \ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ uninstall-pkginc_libHEADERS .PRECIOUS: Makefile # We use custom rules here because we want to use flex and bison instead # of lex and yacc (or bison in yacc-compatibility mode). Both flex and # bison can handle properly naming the generated files, and it is simpler # and cleaner to make this rule ourselves instead of working around ylwrap # and yywrap's antiquated notion of what is hapenning. .l.c: $(AM_V_GEN)$(FLEX) -o $@ $< .y.c: $(AM_V_GEN)$(BISON) -o $@ $< # Bison generates both a header and a .c file. Without the following # dependency, anything including the header will race the bison process. event-filter-parser.h: event-filter-parser.c $(srcdir)/UnicodeData.txt: test -f $@ || wget -O $@ https://dovecot.org/res/UnicodeData.txt $(srcdir)/unicodemap.c: $(srcdir)/unicodemap.pl $(srcdir)/UnicodeData.txt perl $(srcdir)/unicodemap.pl < $(srcdir)/UnicodeData.txt > $@ check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/lib/array.c0000644000000000000000000001061314656633576013135 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" void * array_idx_modifiable_i(const struct array *array, unsigned int idx) { i_assert(idx < array->buffer->used / array->element_size); return PTR_OFFSET(array->buffer->data, idx * array->element_size); } void *array_idx_get_space_i(struct array *array, unsigned int idx) { return buffer_get_space_unsafe(array->buffer, idx * array->element_size, array->element_size); } void array_idx_set_i(struct array *array, unsigned int idx, const void *data) { buffer_write(array->buffer, idx * array->element_size, data, array->element_size); } void array_idx_clear_i(struct array *array, unsigned int idx) { buffer_write_zero(array->buffer, idx * array->element_size, array->element_size); } void *array_insert_space_i(struct array *array, unsigned int idx) { void *data; size_t pos; pos = idx * array->element_size; buffer_copy(array->buffer, pos + array->element_size, array->buffer, pos, SIZE_MAX); data = buffer_get_space_unsafe(array->buffer, pos, array->element_size); memset(data, 0, array->element_size); return data; } bool array_cmp_i(const struct array *array1, const struct array *array2) { if (!array_is_created_i(array1) || array1->buffer->used == 0) return !array_is_created_i(array2) || array2->buffer->used == 0; if (!array_is_created_i(array2)) return FALSE; return buffer_cmp(array1->buffer, array2->buffer); } bool array_equal_fn_i(const struct array *array1, const struct array *array2, int (*cmp)(const void *, const void*)) { unsigned int count1, count2, i; size_t size; if (!array_is_created_i(array1) || array1->buffer->used == 0) return !array_is_created_i(array2) || array2->buffer->used == 0; if (!array_is_created_i(array2)) return FALSE; count1 = array_count_i(array1); count2 = array_count_i(array2); if (count1 != count2) return FALSE; size = array1->element_size; i_assert(size == array2->element_size); for (i = 0; i < count1; i++) { if (cmp(CONST_PTR_OFFSET(array1->buffer->data, i * size), CONST_PTR_OFFSET(array2->buffer->data, i * size)) != 0) return FALSE; } return TRUE; } bool array_equal_fn_ctx_i(const struct array *array1, const struct array *array2, int (*cmp)(const void *, const void *, const void *), const void *context) { unsigned int count1, count2, i; size_t size; if (!array_is_created_i(array1) || array1->buffer->used == 0) return !array_is_created_i(array2) || array2->buffer->used == 0; if (!array_is_created_i(array2)) return FALSE; count1 = array_count_i(array1); count2 = array_count_i(array2); if (count1 != count2) return FALSE; size = array1->element_size; i_assert(size == array2->element_size); for (i = 0; i < count1; i++) { if (cmp(CONST_PTR_OFFSET(array1->buffer->data, i * size), CONST_PTR_OFFSET(array2->buffer->data, i * size), context) != 0) return FALSE; } return TRUE; } void array_reverse_i(struct array *array) { const size_t element_size = array->element_size; unsigned int i, count = array_count_i(array); size_t size; void *data, *tmp; data = buffer_get_modifiable_data(array->buffer, &size); tmp = t_buffer_get(array->element_size); for (i = 0; i+1 < count; i++, count--) { memcpy(tmp, PTR_OFFSET(data, i * element_size), element_size); memcpy(PTR_OFFSET(data, i * element_size), PTR_OFFSET(data, (count-1) * element_size), element_size); memcpy(PTR_OFFSET(data, (count-1) * element_size), tmp, element_size); } } void array_sort_i(struct array *array, int (*cmp)(const void *, const void *)) { unsigned int count; count = array_count_i(array); if (count == 0) return; qsort(buffer_get_modifiable_data(array->buffer, NULL), count, array->element_size, cmp); } void *array_bsearch_i(struct array *array, const void *key, int (*cmp)(const void *, const void *)) { unsigned int count; count = array_count_i(array); return bsearch(key, array->buffer->data, count, array->element_size, cmp); } const void *array_lsearch_i(const struct array *array, const void *key, int (*cmp)(const void *, const void *)) { const void * const data = array->buffer->data; const size_t s = array->element_size; unsigned int idx; for (idx = 0; idx < array_count_i(array); idx++) { if (cmp(key, CONST_PTR_OFFSET(data, idx * s)) == 0) { return PTR_OFFSET(data, idx * s); } } return NULL; } dovecot-2.3.21.1/src/lib/file-cache.c0000644000000000000000000002043214656633576013777 00000000000000/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "mmap-util.h" #include "file-cache.h" #include struct file_cache { int fd; char *path; buffer_t *page_bitmask; void *mmap_base; size_t mmap_length; size_t read_highwater; }; struct file_cache *file_cache_new(int fd) { return file_cache_new_path(fd, ""); } struct file_cache *file_cache_new_path(int fd, const char *path) { struct file_cache *cache; cache = i_new(struct file_cache, 1); cache->fd = fd; cache->path = i_strdup(path); cache->page_bitmask = buffer_create_dynamic(default_pool, 128); return cache; } void file_cache_free(struct file_cache **_cache) { struct file_cache *cache = *_cache; *_cache = NULL; if (cache->mmap_base != NULL) { if (munmap_anon(cache->mmap_base, cache->mmap_length) < 0) i_error("munmap_anon(%s) failed: %m", cache->path); } buffer_free(&cache->page_bitmask); i_free(cache->path); i_free(cache); } void file_cache_set_fd(struct file_cache *cache, int fd) { cache->fd = fd; file_cache_invalidate(cache, 0, cache->mmap_length); } int file_cache_set_size(struct file_cache *cache, uoff_t size) { size_t page_size = mmap_get_page_size(); uoff_t diff; void *new_base; i_assert(page_size > 0); diff = size % page_size; if (diff != 0) size += page_size - diff; i_assert((size % page_size) == 0); if (size <= cache->mmap_length) return 0; if (size > SIZE_MAX) { i_error("file_cache_set_size(%s, %"PRIuUOFF_T"): size too large", cache->path, size); return -1; } /* grow mmaping */ if (cache->mmap_base == NULL) { cache->mmap_base = mmap_anon(size); if (cache->mmap_base == MAP_FAILED) { i_error("mmap_anon(%s, %"PRIuUOFF_T") failed: %m", cache->path, size); cache->mmap_base = NULL; cache->mmap_length = 0; return -1; } } else { new_base = mremap_anon(cache->mmap_base, cache->mmap_length, size, MREMAP_MAYMOVE); if (new_base == MAP_FAILED) { i_error("mremap_anon(%s, %"PRIuUOFF_T") failed: %m", cache->path, size); return -1; } cache->mmap_base = new_base; } cache->mmap_length = size; return 0; } ssize_t file_cache_read(struct file_cache *cache, uoff_t offset, size_t size) { size_t page_size = mmap_get_page_size(); size_t poffset, psize, dest_offset, dest_size; unsigned char *bits, *dest; ssize_t ret; i_assert(page_size > 0); if (size > SSIZE_T_MAX) { /* make sure our calculations won't overflow. most likely we'll be reading less data, but allow it anyway so caller doesn't have to deal with any extra checks. */ size = SSIZE_T_MAX; } if (offset >= UOFF_T_MAX - size) size = UOFF_T_MAX - offset; if (offset + size > cache->mmap_length && offset + size - cache->mmap_length > 1024*1024) { /* growing more than a megabyte, make sure that the file is large enough so we don't allocate memory more than needed */ struct stat st; if (fstat(cache->fd, &st) < 0) { if (errno != ESTALE) i_error("fstat(%s) failed: %m", cache->path); return -1; } if (offset + size > (uoff_t)st.st_size) { if (offset >= (uoff_t)st.st_size) return 0; size = (uoff_t)st.st_size - offset; } } if (file_cache_set_size(cache, offset + size) < 0) return -1; poffset = offset / page_size; psize = (offset + size + page_size-1) / page_size - poffset; i_assert(psize > 0); bits = buffer_get_space_unsafe(cache->page_bitmask, 0, (poffset + psize + CHAR_BIT - 1) / CHAR_BIT); dest_offset = poffset * page_size; dest = PTR_OFFSET(cache->mmap_base, dest_offset); dest_size = page_size; while (psize > 0) { if ((bits[poffset / CHAR_BIT] & (1 << (poffset % CHAR_BIT))) != 0) { /* page is already in cache */ dest_offset += page_size; if (dest_offset <= cache->read_highwater) { psize--; poffset++; dest += page_size; continue; } /* this is the last partially cached block. use the caching only if we don't want to read past read_highwater */ if (offset + size <= cache->read_highwater) { i_assert(psize == 1); break; } /* mark the block noncached again and read it */ bits[poffset / CHAR_BIT] &= ~(1 << (poffset % CHAR_BIT)); dest_offset -= page_size; } ret = pread(cache->fd, dest, dest_size, dest_offset); if (ret <= 0) { if (ret < 0) return -1; /* EOF. mark the last block as cached even if it isn't completely. read_highwater tells us how far we've actually made. */ if (dest_offset == cache->read_highwater) { i_assert(poffset == cache->read_highwater / page_size); bits[poffset / CHAR_BIT] |= 1 << (poffset % CHAR_BIT); } return dest_offset <= offset ? 0 : dest_offset - offset < size ? dest_offset - offset : size; } dest += ret; dest_offset += ret; if (cache->read_highwater < dest_offset) { unsigned int high_poffset = cache->read_highwater / page_size; /* read_highwater needs to be updated. if we didn't just read that block, we can't trust anymore that we have it cached */ bits[high_poffset / CHAR_BIT] &= ~(1 << (high_poffset % CHAR_BIT)); cache->read_highwater = dest_offset; } if ((size_t)ret != dest_size) { /* partial read - probably EOF but make sure. */ dest_size -= ret; continue; } bits[poffset / CHAR_BIT] |= 1 << (poffset % CHAR_BIT); dest_size = page_size; psize--; poffset++; } return size; } const void *file_cache_get_map(struct file_cache *cache, size_t *size_r) { *size_r = cache->read_highwater; return cache->mmap_base; } void file_cache_write(struct file_cache *cache, const void *data, size_t size, uoff_t offset) { size_t page_size = mmap_get_page_size(); unsigned char *bits; unsigned int first_page, last_page; i_assert(page_size > 0); i_assert(UOFF_T_MAX - offset > size); if (file_cache_set_size(cache, offset + size) < 0) { /* couldn't grow mapping. just make sure the written memory area is invalidated then. */ file_cache_invalidate(cache, offset, size); return; } memcpy(PTR_OFFSET(cache->mmap_base, offset), data, size); if (cache->read_highwater < offset + size) { unsigned int page = cache->read_highwater / page_size; bits = buffer_get_space_unsafe(cache->page_bitmask, page / CHAR_BIT, 1); *bits &= ~(1 << (page % CHAR_BIT)); cache->read_highwater = offset + size; } /* mark fully written pages cached */ if (size >= page_size) { first_page = offset / page_size; last_page = (offset + size) / page_size; if ((offset % page_size) != 0) first_page++; bits = buffer_get_space_unsafe(cache->page_bitmask, 0, last_page / CHAR_BIT + 1); for (; first_page < last_page; first_page++) { bits[first_page / CHAR_BIT] |= 1 << (first_page % CHAR_BIT); } } } void file_cache_invalidate(struct file_cache *cache, uoff_t offset, uoff_t size) { size_t page_size = mmap_get_page_size(); unsigned char *bits, mask; unsigned int i; if (offset >= cache->read_highwater || size == 0) return; i_assert(page_size > 0); if (size > cache->read_highwater - offset) { /* ignore anything after read highwater */ size = cache->read_highwater - offset; } if (size >= cache->read_highwater) { /* we're invalidating everything up to read highwater. drop the highwater position. */ cache->read_highwater = offset & ~(page_size-1); } size = (offset + size + page_size-1) / page_size; offset /= page_size; i_assert(size > offset); size -= offset; if (size != 1) { /* tell operating system that we don't need the memory anymore and it may free it. don't bother to do it for single pages, there's a good chance that they get re-read back immediately. */ (void)madvise(PTR_OFFSET(cache->mmap_base, offset * page_size), size * page_size, MADV_DONTNEED); } bits = buffer_get_space_unsafe(cache->page_bitmask, offset / CHAR_BIT, 1 + (size + CHAR_BIT - 1) / CHAR_BIT); /* set the first byte */ for (i = offset % CHAR_BIT, mask = 0; i < CHAR_BIT && size > 0; i++) { mask |= 1 << i; size--; } *bits++ &= ~mask; /* set the middle bytes */ memset(bits, 0, size / CHAR_BIT); bits += size / CHAR_BIT; size %= CHAR_BIT; /* set the last byte */ if (size > 0) { for (i = 0, mask = 0; i < size; i++) mask |= 1 << i; *bits &= ~mask; } } dovecot-2.3.21.1/src/lib/test-istream-seekable.c0000644000000000000000000002041614656633576016213 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "sha2.h" #include "istream-private.h" #include "istream-sized.h" #include "istream-hash.h" #include "istream-seekable.h" #include #include static int fd_callback_fd = -1; static int fd_callback(const char **path_r, void *context ATTR_UNUSED) { int fd; *path_r = "test-lib.tmp"; fd = open(*path_r, O_RDWR | O_CREAT | O_TRUNC, 0600); if (fd == -1) i_error("creat(%s) failed: %m", *path_r); else i_unlink(*path_r); fd_callback_fd = fd; return fd; } static void test_istream_seekable_one(unsigned int buffer_size) { static const char *input_string = "xyz"; #define STREAM_COUNT 5 #define STREAM_BYTES 3 struct istream *streams[STREAM_COUNT+1]; struct istream *input; const unsigned char *data; size_t size; unsigned int i, j; for (i = 0; i < STREAM_COUNT; i++) { streams[i] = test_istream_create(input_string); streams[i]->seekable = FALSE; test_istream_set_allow_eof(streams[i], TRUE); test_istream_set_size(streams[i], 0); } streams[i] = NULL; input = i_stream_create_seekable(streams, buffer_size, fd_callback, NULL); test_assert(!input->blocking); for (i = 0; i/STREAM_BYTES < STREAM_COUNT; i++) { test_istream_set_size(streams[i/STREAM_BYTES], (i%STREAM_BYTES) + 1); if (i < buffer_size) { test_assert(i_stream_read(input) == 1); data = i_stream_get_data(input, &size); test_assert(size == i+1); } else { test_assert(i_stream_read(input) == -2); i_stream_skip(input, 1); test_assert(i_stream_read(input) == 1); data = i_stream_get_data(input, &size); test_assert(size == buffer_size); } for (j = 0; j < size; j++) { test_assert((char)data[j] == input_string[(input->v_offset + j) % STREAM_BYTES]); } } test_assert(!input->blocking); test_assert(i_stream_read(input) == -1); test_assert(input->blocking); for (i = 0; i < STREAM_COUNT; i++) { test_assert(streams[i]->eof && streams[i]->stream_errno == 0); i_stream_unref(&streams[i]); } i_stream_unref(&input); } static void test_istream_seekable_random(void) { struct istream **streams, *input; const unsigned char *data; unsigned char *w_data; size_t size; unsigned int i, j, offset, stream_count, data_len, buffer_size; stream_count = i_rand_minmax(2, 10 + 2 - 1); streams = t_new(struct istream *, stream_count + 1); for (i = 0, offset = 0; i < stream_count; i++) { data_len = i_rand_minmax(1, 100); w_data = t_malloc_no0(data_len); for (j = 0; j < data_len; j++) w_data[j] = (offset++) & 0xff; streams[i] = test_istream_create_data(w_data, data_len); streams[i]->seekable = FALSE; test_istream_set_allow_eof(streams[i], TRUE); } streams[i] = NULL; i_assert(offset > 0); buffer_size = i_rand_minmax(1, 100); size = 0; input = i_stream_create_seekable(streams, buffer_size, fd_callback, NULL); test_assert(!input->blocking); /* first read it through */ while (i_stream_read(input) > 0) { size = i_stream_get_data_size(input); i_stream_skip(input, size); } test_assert(input->blocking); i_stream_seek(input, 0); for (i = 0; i < 100; i++) { if (i_rand_limit(3) == 0) { i_stream_seek(input, i_rand_limit(offset)); } else { ssize_t ret = i_stream_read(input); if (input->v_offset + size == offset) test_assert(ret < 0); else if (ret == -2) { test_assert(size == buffer_size); } else { test_assert(ret > 0); test_assert(input->v_offset + ret <= offset); i_stream_skip(input, i_rand_limit(ret + 1)); data = i_stream_get_data(input, &size); for (j = 0; j < size; j++) { test_assert(data[j] == (input->v_offset + j) % 256); } } } size = i_stream_get_data_size(input); } for (i = 0; i < stream_count; i++) { test_assert(streams[i]->eof && streams[i]->stream_errno == 0); i_stream_unref(&streams[i]); } i_stream_unref(&input); } static void test_istream_seekable_eof(void) { static const char *in_str = "foo"; unsigned int in_str_len = strlen(in_str); struct istream *streams[2], *input; const unsigned char *data; size_t size; test_begin("istream seekable eof"); streams[0] = i_stream_create_from_data(in_str, in_str_len); streams[0]->seekable = FALSE; streams[1] = NULL; input = i_stream_create_seekable(streams, in_str_len, fd_callback, NULL); test_assert(i_stream_read(input) == (ssize_t)in_str_len); data = i_stream_get_data(input, &size); test_assert(size == in_str_len); test_assert(memcmp(data, in_str, in_str_len) == 0); test_assert(i_stream_read(input) == -1); data = i_stream_get_data(input, &size); test_assert(size == in_str_len); test_assert(memcmp(data, in_str, in_str_len) == 0); i_stream_seek(input, size); i_stream_unref(&input); test_assert(streams[0]->v_offset == in_str_len); test_assert(streams[0]->eof); i_stream_unref(&streams[0]); test_end(); } static void test_istream_seekable_early_end(void) { struct istream *input, *streams[2]; test_begin("istream seekable early end"); streams[0] = test_istream_create("stream"); test_istream_set_size(streams[0], 3); test_istream_set_allow_eof(streams[0], FALSE); streams[0]->seekable = FALSE; streams[1] = NULL; input = i_stream_create_seekable(streams, 1000, fd_callback, NULL); test_assert(i_stream_read(input) == 3); test_istream_set_size(streams[0], 5); test_assert(i_stream_read(input) == 2); i_stream_skip(input, 5); i_stream_unref(&input); test_assert(streams[0]->v_offset == 5); i_stream_unref(&streams[0]); test_end(); } static void test_istream_seekable_invalid_read(void) { test_begin("istream seekable + other streams causing invalid read"); struct sha256_ctx hash_ctx; sha256_init(&hash_ctx); struct istream *str_input = test_istream_create("123456"); str_input->seekable = FALSE; struct istream *seek_inputs[] = { str_input, NULL }; struct istream *seek_input = i_stream_create_seekable(seek_inputs, 3, fd_callback, NULL); struct istream *sized_input = i_stream_create_sized(seek_input, 3); struct istream *input = i_stream_create_hash(sized_input, &hash_method_sha256, &hash_ctx); test_assert(i_stream_read(input) == 3); test_assert(i_stream_read(input) == -2); i_stream_skip(input, 3); test_assert(i_stream_read(input) == -1); i_stream_unref(&input); i_stream_unref(&sized_input); i_stream_unref(&seek_input); i_stream_unref(&str_input); test_end(); } static void test_istream_seekable_get_size(void) { test_begin("istream seekable get size"); struct istream *str_input = test_istream_create("123456"); str_input->seekable = FALSE; struct istream *seek_inputs[] = { str_input, NULL }; struct istream *input = i_stream_create_seekable(seek_inputs, 32, fd_callback, NULL); uoff_t size; test_assert(i_stream_read(input) == 6); test_assert(i_stream_read(input) == -1); test_assert(i_stream_get_size(input, TRUE, &size) == 1 && size == 6); i_stream_unref(&input); i_stream_unref(&str_input); test_end(); } static void test_istream_seekable_failed_writes(void) { struct istream *input, *streams[2]; test_begin("istream seekable failed write"); streams[0] = test_istream_create("stream"); test_istream_set_size(streams[0], 3); test_istream_set_allow_eof(streams[0], FALSE); streams[0]->seekable = FALSE; streams[1] = NULL; input = i_stream_create_seekable(streams, 2, fd_callback, NULL); i_stream_set_name(input, "test seekable"); test_assert(i_stream_read(input) == 2); i_stream_skip(input, 2); test_assert(i_stream_read(input) == 1); i_close_fd(&fd_callback_fd); test_istream_set_size(streams[0], 5); test_expect_error_string("istream-seekable: write_full(test-lib.tmp) failed: Bad file descriptor"); test_assert(i_stream_read(input) == -1); test_expect_no_more_errors(); test_expect_error_string("file_istream.close((seekable temp-istream for: test seekable)) failed: Bad file descriptor"); i_stream_unref(&input); test_expect_no_more_errors(); i_stream_unref(&streams[0]); test_end(); } void test_istream_seekable(void) { unsigned int i; test_begin("istream seekable"); for (i = 1; i <= STREAM_BYTES*STREAM_COUNT; i++) test_istream_seekable_one(i); test_end(); test_begin("istream seekable random"); for (i = 0; i < 100; i++) T_BEGIN { test_istream_seekable_random(); } T_END; test_end(); test_istream_seekable_eof(); test_istream_seekable_early_end(); test_istream_seekable_invalid_read(); test_istream_seekable_get_size(); test_istream_seekable_failed_writes(); } dovecot-2.3.21.1/src/lib/utc-mktime.c0000644000000000000000000000345114656633576014100 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "utc-mktime.h" static int tm_cmp(const struct tm *tm1, const struct tm *tm2) { int diff; if ((diff = tm1->tm_year - tm2->tm_year) != 0) return diff; if ((diff = tm1->tm_mon - tm2->tm_mon) != 0) return diff; if ((diff = tm1->tm_mday - tm2->tm_mday) != 0) return diff; if ((diff = tm1->tm_hour - tm2->tm_hour) != 0) return diff; if ((diff = tm1->tm_min - tm2->tm_min) != 0) return diff; return tm1->tm_sec - tm2->tm_sec; } static inline void adjust_leap_second(struct tm *tm) { if (tm->tm_sec == 60) tm->tm_sec = 59; } #ifdef HAVE_TIMEGM /* Normalization done by timegm is considered a failure here, since it means * the timestamp is not valid as-is. Leap second 60 is adjusted to 59 before * this though. */ time_t utc_mktime(const struct tm *tm) { struct tm leap_adj_tm = *tm; adjust_leap_second(&leap_adj_tm); struct tm tmp = leap_adj_tm; time_t t; t = timegm(&tmp); if (tm_cmp(&leap_adj_tm, &tmp) != 0) return (time_t)-1; return t; } #else time_t utc_mktime(const struct tm *tm) { struct tm leap_adj_tm = *tm; adjust_leap_second(&leap_adj_tm); const struct tm *try_tm; time_t t; int bits, dir; /* we'll do a binary search across the entire valid time_t range. when gmtime()'s output matches the tm parameter, we've found the correct time_t value. this also means that if tm contains invalid values, -1 is returned. */ #ifdef TIME_T_SIGNED t = 0; #else t = (time_t)1 << (TIME_T_MAX_BITS - 1); #endif for (bits = TIME_T_MAX_BITS - 2;; bits--) { try_tm = gmtime(&t); dir = tm_cmp(&leap_adj_tm, try_tm); if (dir == 0) return t; if (bits < 0) break; if (dir < 0) t -= (time_t)1 << bits; else t += (time_t)1 << bits; } return (time_t)-1; } #endif dovecot-2.3.21.1/src/lib/base64.c0000644000000000000000000005633414656633576013115 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "base64.h" #include "buffer.h" /* * Low-level Base64 encoder */ uoff_t base64_get_full_encoded_size(struct base64_encoder *enc, uoff_t src_size) { bool crlf = HAS_ALL_BITS(enc->flags, BASE64_ENCODE_FLAG_CRLF); bool no_padding = HAS_ALL_BITS(enc->flags, BASE64_ENCODE_FLAG_NO_PADDING); uoff_t out_size; uoff_t newlines; if (src_size == 0) return 0; /* calculate size of encoded data */ out_size = MAX_BASE64_ENCODED_SIZE(src_size); if (no_padding) { switch (src_size % 3) { case 0: break; case 1: i_assert(out_size > 2); out_size -= 2; break; case 2: i_assert(out_size > 1); out_size -= 1; break; } } if (out_size > enc->max_line_len) { /* newline between each full line */ i_assert(enc->max_line_len > 0); newlines = (out_size / enc->max_line_len) - 1; /* an extra newline to separate the partial last line from the previous full line */ if ((out_size % enc->max_line_len) != 0) newlines++; /* update size with added newlines */ out_size += newlines * (crlf ? 2 : 1); } return out_size; } static size_t base64_encode_get_out_size(struct base64_encoder *enc, size_t src_size) { size_t res_size = enc->w_buf_len; i_assert(enc->w_buf_len <= sizeof(enc->w_buf)); if (src_size == 0) return res_size; /* Handle sub-position */ switch (enc->sub_pos) { case 0: break; case 1: res_size++; src_size--; if (src_size == 0) return res_size; /* fall through */ case 2: res_size += 2; src_size--; break; default: i_unreached(); } /* We're now at a 3-byte boundary */ if (src_size == 0) return res_size; /* Calculate size we can append to the output from remaining input */ res_size += ((src_size) / 3) * 4; switch (src_size % 3) { case 0: break; case 1: res_size += 1; break; case 2: res_size += 2; break; } return res_size; } size_t base64_encode_get_size(struct base64_encoder *enc, size_t src_size) { bool crlf = HAS_ALL_BITS(enc->flags, BASE64_ENCODE_FLAG_CRLF); size_t out_size = base64_encode_get_out_size(enc, src_size); if (src_size == 0) { /* last block */ switch (enc->sub_pos) { case 0: break; case 1: out_size += 3; break; case 2: out_size += 2; break; default: i_unreached(); } } if (enc->max_line_len < SIZE_MAX) { size_t line_part, lines; /* Calculate how many line endings must be added */ i_assert(enc->max_line_len > 0); lines = out_size / enc->max_line_len; line_part = out_size % enc->max_line_len; if (enc->cur_line_len > (enc->max_line_len - line_part)) lines++; out_size += lines * (crlf ? 2 : 1); } if (enc->pending_lf) out_size++; return out_size; } size_t base64_encode_get_full_space(struct base64_encoder *enc, size_t dst_space) { bool crlf = HAS_ALL_BITS(enc->flags, BASE64_ENCODE_FLAG_CRLF); bool no_padding = HAS_ALL_BITS(enc->flags, BASE64_ENCODE_FLAG_NO_PADDING); size_t src_space = 0; i_assert(enc->w_buf_len <= sizeof(enc->w_buf)); if (enc->max_line_len < SIZE_MAX) { size_t max_line_space, lines, nl_space; /* Calculate how many line endings must be added if all space were used. */ i_assert(enc->max_line_len < SIZE_MAX-2); max_line_space = enc->max_line_len + (crlf ? 2 : 1); lines = dst_space / max_line_space; /* Calculate how much space is used by newline characters and subtract this from the available space. */ nl_space = lines * (crlf ? 2 : 1); if (dst_space <= nl_space) return 0; dst_space -= nl_space; } if (dst_space <= enc->w_buf_len) return 0; dst_space -= enc->w_buf_len; if (enc->pending_lf) dst_space--; if (dst_space == 0) return 0; /* Handle sub-position */ switch (enc->sub_pos) { case 0: break; case 1: dst_space--; src_space++; /* fall through */ case 2: if (dst_space < 2) return src_space; dst_space -= 2; src_space++; break; default: i_unreached(); } if (dst_space == 0) return src_space; src_space += dst_space / 4 * 3; if (no_padding) { switch (dst_space % 4) { case 0: case 1: break; case 2: src_space += 1; break; case 3: src_space += 2; break; } } return src_space; } static void base64_encode_more_data(struct base64_encoder *enc, const unsigned char *src_c, size_t src_size, size_t *src_pos_r, size_t dst_avail, buffer_t *dest) { const struct base64_scheme *b64 = enc->b64; const char *b64enc = b64->encmap; size_t res_size; unsigned char *start, *ptr, *end; size_t src_pos; i_assert(!enc->pending_lf); /* determine how much we can write in destination buffer */ if (dst_avail == 0) { *src_pos_r = 0; return; } /* pre-allocate space in the destination buffer */ res_size = base64_encode_get_out_size(enc, src_size); if (res_size > dst_avail) res_size = dst_avail; start = buffer_append_space_unsafe(dest, res_size); end = start + res_size; ptr = start; /* write bytes not written in previous call */ i_assert(enc->w_buf_len <= sizeof(enc->w_buf)); if (enc->w_buf_len > res_size) { memcpy(ptr, enc->w_buf, res_size); ptr += res_size; enc->w_buf_len -= res_size; memmove(enc->w_buf, enc->w_buf + res_size, enc->w_buf_len); } else if (enc->w_buf_len > 0) { memcpy(ptr, enc->w_buf, enc->w_buf_len); ptr += enc->w_buf_len; enc->w_buf_len = 0; } if (ptr == end) { *src_pos_r = 0; return; } i_assert(enc->w_buf_len == 0); i_assert(src_size != 0); /* Handle sub-position */ src_pos = 0; switch (enc->sub_pos) { case 0: break; case 1: i_assert(ptr < end); ptr[0] = b64enc[enc->buf | (src_c[src_pos] >> 4)]; ptr++; enc->buf = (src_c[src_pos] & 0x0f) << 2; src_pos++; if (src_pos == src_size || ptr == end) { enc->sub_pos = 2; *src_pos_r = src_pos; return; } /* fall through */ case 2: ptr[0] = b64enc[enc->buf | ((src_c[src_pos] & 0xc0) >> 6)]; enc->w_buf[0] = b64enc[src_c[src_pos] & 0x3f]; ptr++; src_pos++; if (ptr < end) { ptr[0] = enc->w_buf[0]; ptr++; enc->w_buf_len = 0; } else { enc->sub_pos = 0; enc->w_buf_len = 1; *src_pos_r = src_pos; return; } break; default: i_unreached(); } enc->sub_pos = 0; /* We're now at a 3-byte boundary */ if (src_pos == src_size) { i_assert(ptr == end); *src_pos_r = src_pos; return; } /* Convert the bulk */ for (; src_size - src_pos > 2 && &ptr[3] < end; src_pos += 3, ptr += 4) { ptr[0] = b64enc[src_c[src_pos] >> 2]; ptr[1] = b64enc[((src_c[src_pos] & 0x03) << 4) | (src_c[src_pos+1] >> 4)]; ptr[2] = b64enc[((src_c[src_pos+1] & 0x0f) << 2) | ((src_c[src_pos+2] & 0xc0) >> 6)]; ptr[3] = b64enc[src_c[src_pos+2] & 0x3f]; } /* Convert the bytes beyond the last 3-byte boundary and update state for next call */ switch (src_size - src_pos) { case 0: enc->sub_pos = 0; enc->buf = 0; break; case 1: enc->sub_pos = 1; enc->w_buf[0] = b64enc[src_c[src_pos] >> 2]; enc->w_buf_len = 1; enc->buf = (src_c[src_pos] & 0x03) << 4; src_pos++; break; case 2: enc->sub_pos = 2; enc->w_buf[0] = b64enc[src_c[src_pos] >> 2]; enc->w_buf[1] = b64enc[((src_c[src_pos] & 0x03) << 4) | (src_c[src_pos+1] >> 4)]; enc->w_buf_len = 2; enc->buf = (src_c[src_pos+1] & 0x0f) << 2; src_pos += 2; break; default: /* hit the end of the destination buffer */ enc->sub_pos = 0; enc->w_buf[0] = b64enc[src_c[src_pos] >> 2]; enc->w_buf[1] = b64enc[((src_c[src_pos] & 0x03) << 4) | (src_c[src_pos+1] >> 4)]; enc->w_buf[2] = b64enc[((src_c[src_pos+1] & 0x0f) << 2) | ((src_c[src_pos+2] & 0xc0) >> 6)]; enc->w_buf[3] = b64enc[src_c[src_pos+2] & 0x3f]; enc->w_buf_len = 4; enc->buf = 0; src_pos += 3; } /* fill the remaining allocated space */ i_assert(ptr <= end); res_size = end - ptr; i_assert(enc->w_buf_len <= sizeof(enc->w_buf)); if (enc->w_buf_len > res_size) { memcpy(ptr, enc->w_buf, res_size); ptr += res_size; enc->w_buf_len -= res_size; memmove(enc->w_buf, enc->w_buf + res_size, enc->w_buf_len); } else if (enc->w_buf_len > 0) { memcpy(ptr, enc->w_buf, enc->w_buf_len); ptr += enc->w_buf_len; enc->w_buf_len = 0; } i_assert(ptr == end); *src_pos_r = src_pos; } bool base64_encode_more(struct base64_encoder *enc, const void *src, size_t src_size, size_t *src_pos_r, buffer_t *dest) { bool crlf = HAS_ALL_BITS(enc->flags, BASE64_ENCODE_FLAG_CRLF); const unsigned char *src_c, *src_p; size_t src_pos, src_left; i_assert(!enc->finishing); i_assert(!enc->finished); src_p = src_c = src; src_left = src_size; while (src_left > 0) { size_t dst_avail, dst_pos, line_avail, written; /* determine how much we can write in destination buffer */ dst_avail = buffer_get_avail_size(dest); if (dst_avail == 0) break; /* Emit pending newline immediately */ if (enc->pending_lf) { i_assert(crlf); buffer_append_c(dest, '\n'); enc->pending_lf = FALSE; dst_avail--; if (dst_avail == 0) break; } i_assert(enc->max_line_len > 0); i_assert(enc->cur_line_len <= enc->max_line_len); line_avail = I_MIN(enc->max_line_len - enc->cur_line_len, dst_avail); if (line_avail > 0) { dst_pos = dest->used; base64_encode_more_data(enc, src_p, src_left, &src_pos, line_avail, dest); i_assert(src_pos <= src_left); src_p += src_pos; src_left -= src_pos; i_assert(dest->used >= dst_pos); written = dest->used - dst_pos; i_assert(written <= line_avail); i_assert(written <= enc->max_line_len); i_assert(enc->cur_line_len <= (enc->max_line_len - written)); enc->cur_line_len += written; dst_avail -= written; } if (dst_avail == 0) break; if (src_left > 0 && enc->cur_line_len == enc->max_line_len) { if (crlf) { if (dst_avail >= 2) { /* emit the full CRLF sequence */ buffer_append(dest, "\r\n", 2); } else { /* emit CR */ buffer_append_c(dest, '\r'); /* remember the LF */ enc->pending_lf = TRUE; } } else { buffer_append_c(dest, '\n'); } enc->cur_line_len = 0; } } i_assert(src_p >= src_c); src_pos = (src_p - src_c); if (src_pos_r != NULL) *src_pos_r = src_pos; return (src_pos == src_size); } bool base64_encode_finish(struct base64_encoder *enc, buffer_t *dest) { const struct base64_scheme *b64 = enc->b64; const char *b64enc = b64->encmap; bool crlf = HAS_ALL_BITS(enc->flags, BASE64_ENCODE_FLAG_CRLF); bool padding = HAS_NO_BITS(enc->flags, BASE64_ENCODE_FLAG_NO_PADDING); unsigned char *ptr, *end; size_t dst_avail, line_avail, write_full, write; unsigned int w_buf_pos = 0; i_assert(!enc->finished); enc->finishing = TRUE; dst_avail = 0; if (dest != NULL) dst_avail = buffer_get_avail_size(dest); if (enc->w_buf_len > 0 || enc->pending_lf) { if (dst_avail == 0) return FALSE; i_assert(enc->w_buf_len <= sizeof(enc->w_buf)); } i_assert(enc->max_line_len > 0); i_assert(enc->cur_line_len <= enc->max_line_len); line_avail = enc->max_line_len - enc->cur_line_len; switch (enc->sub_pos) { case 0: break; case 1: i_assert(enc->w_buf_len <= (sizeof(enc->w_buf) - 3)); enc->w_buf[enc->w_buf_len] = b64enc[enc->buf]; enc->w_buf_len++; if (padding) { enc->w_buf[enc->w_buf_len + 0] = '='; enc->w_buf[enc->w_buf_len + 1] = '='; enc->w_buf_len += 2; } break; case 2: i_assert(enc->w_buf_len <= (sizeof(enc->w_buf) - 2)); enc->w_buf[enc->w_buf_len] = b64enc[enc->buf]; enc->w_buf_len++; if (padding) { enc->w_buf[enc->w_buf_len + 0] = '='; enc->w_buf_len++; } break; default: i_unreached(); } enc->sub_pos = 0; write_full = write = enc->w_buf_len; if (enc->pending_lf) write_full++; if (enc->max_line_len < SIZE_MAX && line_avail < write) { unsigned int lines; lines = I_MAX((write - line_avail) / enc->max_line_len, 1); write_full += lines * (crlf ? 2 : 1); } else { line_avail = write; } if (write_full == 0) { enc->finished = TRUE; return TRUE; } i_assert(dest != NULL); if (write_full > dst_avail) write_full = dst_avail; ptr = buffer_append_space_unsafe(dest, write_full); end = ptr + write_full; if (enc->pending_lf) { ptr[0] = '\n'; dst_avail--; ptr++; enc->pending_lf = FALSE; } if (line_avail > dst_avail) line_avail = dst_avail; if (line_avail > 0) { memcpy(ptr, enc->w_buf, line_avail); ptr += line_avail; w_buf_pos += line_avail; } while (ptr < end && w_buf_pos < enc->w_buf_len) { enc->cur_line_len = 0; if (crlf) { ptr[0] = '\r'; ptr++; if (ptr == end) { enc->pending_lf = TRUE; break; } } ptr[0] = '\n'; ptr++; if (ptr == end) break; write = I_MIN(enc->w_buf_len - w_buf_pos, enc->max_line_len); write = I_MIN(write, (size_t)(end - ptr)); memcpy(ptr, &enc->w_buf[w_buf_pos], write); ptr += write; w_buf_pos += write; enc->cur_line_len += write; i_assert(ptr <= end); } i_assert(ptr == end); if (w_buf_pos < enc->w_buf_len) { enc->w_buf_len -= w_buf_pos; memmove(enc->w_buf, enc->w_buf + w_buf_pos, enc->w_buf_len); return FALSE; } if (enc->pending_lf) return FALSE; enc->finished = TRUE; return TRUE; } /* * Low-level Base64 decoder */ #define IS_EMPTY(c) \ ((c) == '\n' || (c) == '\r' || (c) == ' ' || (c) == '\t') static inline void base64_skip_whitespace(struct base64_decoder *dec, const unsigned char *src_c, size_t src_size, size_t *src_pos) { if (HAS_ALL_BITS(dec->flags, BASE64_DECODE_FLAG_NO_WHITESPACE)) return; /* skip any whitespace in the padding */ while ((*src_pos) < src_size && IS_EMPTY(src_c[(*src_pos)])) (*src_pos)++; } int base64_decode_more(struct base64_decoder *dec, const void *src, size_t src_size, size_t *src_pos_r, buffer_t *dest) { const struct base64_scheme *b64 = dec->b64; const unsigned char *src_c = src; bool expect_boundary = HAS_ALL_BITS( dec->flags, BASE64_DECODE_FLAG_EXPECT_BOUNDARY); bool no_whitespace = HAS_ALL_BITS( dec->flags, BASE64_DECODE_FLAG_NO_WHITESPACE); bool no_padding = HAS_ALL_BITS( dec->flags, BASE64_DECODE_FLAG_NO_PADDING); size_t src_pos, dst_avail; int ret = 1; i_assert(!dec->finished); i_assert(!dec->failed); if (dec->seen_boundary) { /* already seen the boundary/end of base64 data */ if (src_pos_r != NULL) *src_pos_r = 0; dec->failed = TRUE; return -1; } src_pos = 0; if (dec->seen_end) { /* skip any whitespace at the end */ base64_skip_whitespace(dec, src_c, src_size, &src_pos); if (src_pos_r != NULL) *src_pos_r = src_pos; if (src_pos < src_size) { if (!expect_boundary) { dec->failed = TRUE; return -1; } dec->seen_boundary = TRUE; return 0; } if (no_whitespace) { dec->seen_boundary = TRUE; return 0; } /* more whitespace may follow */ return 1; } if (src_size == 0) { if (src_pos_r != NULL) *src_pos_r = 0; return 1; } dst_avail = buffer_get_avail_size(dest); if (dst_avail == 0) { i_assert(src_pos_r != NULL); *src_pos_r = 0; return 1; } for (; !dec->seen_padding && src_pos < src_size; src_pos++) { unsigned char in = src_c[src_pos]; unsigned char dm = b64->decmap[in]; if (dm == 0xff) { if (no_whitespace) { ret = -1; break; } if (unlikely(!IS_EMPTY(in))) { ret = -1; break; } continue; } if (dst_avail == 0) { i_assert(src_pos_r != NULL); *src_pos_r = src_pos; return 1; } switch (dec->sub_pos) { case 0: dec->buf = dm; dec->sub_pos++; break; case 1: dec->buf = (dec->buf << 2) | (dm >> 4); buffer_append_c(dest, dec->buf); dst_avail--; dec->buf = dm; dec->sub_pos++; break; case 2: dec->buf = ((dec->buf << 4) & 0xff) | (dm >> 2); buffer_append_c(dest, dec->buf); dst_avail--; dec->buf = dm; dec->sub_pos++; break; case 3: dec->buf = ((dec->buf << 6) & 0xc0) | dm; buffer_append_c(dest, dec->buf); dst_avail--; dec->buf = 0; dec->sub_pos = 0; break; default: i_unreached(); } } if (dec->seen_padding) { /* skip any whitespace in or after the padding */ base64_skip_whitespace(dec, src_c, src_size, &src_pos); if (src_pos == src_size) { if (src_pos_r != NULL) *src_pos_r = src_pos; return 1; } } if (dec->seen_padding || ret < 0) { /* try to parse the end (padding) of the base64 input */ i_assert(src_pos < src_size); if (no_padding) { /* no padding allowed */ i_assert(!dec->seen_padding); ret = -1; } else { switch (dec->sub_pos) { case 0: case 1: /* no padding expected */ ret = -1; break; case 2: if (unlikely(src_c[src_pos] != '=')) { /* invalid character */ ret = -1; break; } dec->seen_padding = TRUE; dec->sub_pos++; src_pos++; if (src_pos == src_size) { ret = 1; break; } /* skip any whitespace in the padding */ base64_skip_whitespace(dec, src_c, src_size, &src_pos); if (src_pos == src_size) { ret = 1; break; } /* fall through */ case 3: if (unlikely(src_c[src_pos] != '=')) { /* invalid character */ ret = -1; break; } dec->seen_padding = TRUE; dec->seen_end = TRUE; dec->sub_pos = 0; src_pos++; /* skip any trailing whitespace */ base64_skip_whitespace(dec, src_c, src_size, &src_pos); if (src_pos < src_size) { ret = -1; break; } if (no_whitespace) { dec->seen_boundary = TRUE; ret = 0; } else { /* more whitespace may follow */ ret = 1; } break; } } } if (ret < 0) { if (!expect_boundary) { dec->failed = TRUE; } else { dec->seen_boundary = TRUE; ret = 0; } } if (src_pos_r != NULL) *src_pos_r = src_pos; return ret; } int base64_decode_finish(struct base64_decoder *dec) { i_assert(!dec->finished); dec->finished = TRUE; if (dec->failed) return -1; if (HAS_ALL_BITS(dec->flags, BASE64_DECODE_FLAG_NO_PADDING)) { i_assert(!dec->seen_padding); return 0; } if (HAS_ALL_BITS(dec->flags, BASE64_DECODE_FLAG_IGNORE_PADDING)) return 0; return (dec->sub_pos == 0 ? 0 : -1); } /* * Generic Base64 API */ buffer_t *t_base64_scheme_encode(const struct base64_scheme *b64, enum base64_encode_flags flags, size_t max_line_len, const void *src, size_t src_size) { buffer_t *buf; buf = t_buffer_create(MAX_BASE64_ENCODED_SIZE(src_size)); base64_scheme_encode(b64, flags, max_line_len, src, src_size, buf); return buf; } int base64_scheme_decode(const struct base64_scheme *b64, enum base64_decode_flags flags, const void *src, size_t src_size, buffer_t *dest) { struct base64_decoder dec; int ret; base64_decode_init(&dec, b64, flags); ret = base64_decode_more(&dec, src, src_size, NULL, dest); if (ret >= 0) ret = base64_decode_finish(&dec); return ret; } buffer_t *t_base64_scheme_decode(const struct base64_scheme *b64, enum base64_decode_flags flags, const void *src, size_t src_size) { buffer_t *buf; buf = t_buffer_create(MAX_BASE64_DECODED_SIZE(src_size)); (void)base64_scheme_decode(b64, flags, src, src_size, buf); return buf; } /* * "base64" encoding scheme (RFC 4648, Section 4) */ struct base64_scheme base64_scheme = { .encmap = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', }, .decmap = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0-7 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 8-15 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 16-23 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 24-31 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 32-39 */ 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, /* 40-47 */ 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, /* 48-55 */ 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 56-63 */ 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* 64-71 */ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, /* 72-79 */ 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, /* 80-87 */ 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, /* 88-95 */ 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, /* 96-103 */ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, /* 104-111 */ 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, /* 112-119 */ 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, /* 120-127 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 128-255 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }, }; /* * "base64url" encoding scheme (RFC 4648, Section 5) */ struct base64_scheme base64url_scheme = { .encmap = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', }, .decmap = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0-7 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 8-15 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 16-23 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 24-31 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 32-39 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, /* 40-47 */ 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, /* 48-55 */ 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 56-63 */ 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* 64-71 */ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, /* 72-79 */ 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, /* 80-87 */ 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0x3f, /* 88-95 */ 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, /* 96-103 */ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, /* 104-111 */ 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, /* 112-119 */ 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, /* 120-127 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 128-255 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }, }; dovecot-2.3.21.1/src/lib/ostream-wrapper.h0000644000000000000000000001700014656633576015151 00000000000000#ifndef OSTREAM_WRAPPER_H #define OSTREAM_WRAPPER_H #include "ostream-private.h" /* The wrapper output stream allows turning any form* of activity involving data output into a standard Dovecot output stream. The wrapper output stream can operate both in blocking and non-blocking mode. When the wrapped activity is non-blocking, a blocking wrapper output stream will implicitly run its own ioloop. It is possible to have the wrapper output stream object available even before the data can be written anywhere, even before any form of output object (a connection) exists. In that case, any data written to the wrapper stream is buffered until the buffer is full. Once that happens, the stream will block or refuse writes until the underlying output becomes available. The wrapper output stream is not meant to be used directly. Instead, it is to be used as part of the implementation of an application-specific output stream. The wrapper output stream serves as the means to prevent code duplication between similar output stream implementations. It defines several methods that need to be implemented by the application-specific output stream. * Currently, the wrapper stream still expects an output stream object when data is to be written somewhere, but that should be easily circumvented once such behavior is needed (FIXME). */ struct wrapper_ostream { struct ostream_private ostream; struct event *event; /* Called when the implementation should start making the parent output stream available, e.g. connect to the server. This happens when data was written to the wrapper ostream (when it is corked this only happens when the wrapper ostream buffer is full or the wrapper ostream is finished). */ void (*output_start)(struct wrapper_ostream *wostream); /* Returns TRUE when the output is ready for data. */ bool (*output_ready)(struct wrapper_ostream *wostream); /* Called when an error occurred while writing to the output stream. */ void (*output_error)(struct wrapper_ostream *wostream); /* Called when the wrapper ostream was finished using o_stream_finish() and the wrapper ostream buffer is empty. Also, the parent output was flushed successfully. */ int (*output_finish)(struct wrapper_ostream *wostream); /* Called when the wrapper ostream does not need write to parent output stream. This is will e.g. drop the parent output's flush callback or equivalent notification mechanism. */ void (*output_halt)(struct wrapper_ostream *wostream); /* Called when the wrapper ostream has data available for the parent output and wants wrapper_ostream_continue() to be called when the parent stream is writeable. */ void (*output_resume)(struct wrapper_ostream *wostream); /* Update the timeouts. The sender_blocking parameter indicates which side of the data transfer is blocking, so whether a timeout needs to be set for limiting the time other side is not doing anything. */ void (*output_update_timeouts)(struct wrapper_ostream *wostream, bool sender_blocking); /* Called before and after running ioloop for performing blocking I/O wait. Use these vfuncs to switch to and from the temporary ioloop. */ struct ioloop *(*wait_begin)(struct wrapper_ostream *wostream, struct ioloop *ioloop); void (*wait_end)(struct wrapper_ostream *wostream, struct ioloop *prev_ioloop); /* Called before and after running the flush callback for the ostream. */ void (*callback_pre)(struct wrapper_ostream *wostream); void (*callback_post)(struct wrapper_ostream *wostream); /* Called when the ostream is switched to a different ioloop. */ void (*switch_ioloop_to)(struct wrapper_ostream *wostream, struct ioloop *ioloop); /* Called when the wrapper ostream is forcibly closed using o_stream_close() (or indirectly through e.g. o_stream_destroy()). */ void (*close)(struct wrapper_ostream *wostream); /* Called when the ostream is destroyed. */ void (*destroy)(struct wrapper_ostream *wostream); buffer_t *buffer; // FIXME: use a ringbuffer instead (file_ostream) /* The (parent) output stream. */ struct ostream *output; /* The ioloop used while flushing/sending output for when the wrapper ostream is blocking. */ struct ioloop *flush_ioloop; /* Error set using wrapper_ostream_return_error(). This is returned to the application once it continues using the wrapper ostream. */ char *pending_error; int pending_errno; /* Timeout for delayed execution of wrapper_ostream_continue(). */ struct timeout *to_event; /* Output was started (output_start() vfunc was called). */ bool output_started:1; /* Output was finished (output_finish() vfunc was called). */ bool output_finished:1; /* Output was was closed somehow. This means that the output is no longer available. This is not the same as the ostream close flag. */ bool output_closed:1; /* Output was closed directly or indirectly by the application action. */ bool output_closed_api:1; bool flush_pending:1; bool flush_waiting:1; bool flushing:1; bool continuing:1; bool returned_error:1; }; /* Create the wrapper output stream. This function calls o_stream_create() internally. The initial maximum buffer size is set to max_buffer_size. When blocking is TRUE, a blocking output stream will be created. The provided event is used internally for debug logging. */ struct ostream * wrapper_ostream_create(struct wrapper_ostream *wostream, size_t max_buffer_size, bool blocking, struct event *event) ATTR_NULL(4); /* Continue sending output. Returns 1 if all buffered data is sent so far, 0 if not, and -1 if an error occurred. */ int wrapper_ostream_continue(struct wrapper_ostream *wostream); /* Trigger an (asynchronous) flush on the output stream. */ void wrapper_ostream_trigger_flush(struct wrapper_ostream *wostream); /* This function returns the size of the data buffered in the wrapper stream, but only when the output stream is finished using o_stream_finish(). When the output stream is finished, the data is complete and this function returns TRUE and size_r is set to the size. If it is not complete, this function returns FALSE and size_r is not assigned. This function is meant to be called just before sending the first block of data internally for deciding between sending the data using a chunked transfer encoding or, when it is already complete, as a single blob with known size. E.g., for HTTP this is the choice between sending the message using the Transfer-Encoding: chunked header or the Content-Length header. */ bool wrapper_ostream_get_buffered_size(struct wrapper_ostream *wostream, uoff_t *size_r); /* Call this when the underlying output stream first becomes available. */ void wrapper_ostream_output_available(struct wrapper_ostream *wostream, struct ostream *output); /* Call this to notify the wrapper that the underlying output is destroyed and no more data can be written ever. */ void wrapper_ostream_output_destroyed(struct wrapper_ostream *wostream); /* Call this to notify the wrapper that an error has occurred. It will be returned as such for the next stream write/flush and subsequent o_stream_get_error(). */ void wrapper_ostream_set_error(struct wrapper_ostream *wostream, int stream_errno, const char *stream_error); /* Notify the application immediately about any error condition set earlier using wrapper_ostream_set_error() by calling the ostream flush callback right now. */ void wrapper_ostream_notify_error(struct wrapper_ostream *wostream); #endif dovecot-2.3.21.1/src/lib/askpass.c0000644000000000000000000000262714656633576013472 00000000000000/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "str.h" #include "askpass.h" #include #include #include #include static void askpass_str(const char *prompt, buffer_t *pass) { struct termios old_tio, tio; bool tty, restore_tio = FALSE; char ch; int fd; tty = isatty(STDIN_FILENO) != 0; if (tty) { fputs(prompt, stderr); fflush(stderr); fd = open("/dev/tty", O_RDONLY); if (fd < 0) i_fatal("open(/dev/tty) failed: %m"); /* turn off echo */ if (tcgetattr(fd, &old_tio) == 0) { restore_tio = TRUE; tio = old_tio; tio.c_lflag &= ENUM_NEGATE(ECHO | ECHONL); (void)tcsetattr(fd, TCSAFLUSH, &tio); } } else { /* read it from stdin without showing a prompt */ fd = STDIN_FILENO; } /* read the password */ while (read(fd, &ch, 1) > 0) { if (ch == '\n' || ch == '\r') break; buffer_append_c(pass, ch); } if (tty) { if (restore_tio) (void)tcsetattr(fd, TCSAFLUSH, &old_tio); fputs("\n", stderr); fflush(stderr); i_close_fd(&fd); } } void askpass(const char *prompt, char *buf, size_t buf_size) { buffer_t str; buffer_create_from_data(&str, buf, buf_size); askpass_str(prompt, &str); buffer_append_c(&str, '\0'); } const char *t_askpass(const char *prompt) { string_t *str = t_str_new(32); askpass_str(prompt, str); return str_c(str); } dovecot-2.3.21.1/src/lib/iostream.c0000644000000000000000000000712314656633576013644 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "istream.h" #include "ostream.h" #include "iostream-private.h" static void io_stream_default_close(struct iostream_private *stream ATTR_UNUSED, bool close_parent ATTR_UNUSED) { } static void io_stream_default_destroy(struct iostream_private *stream ATTR_UNUSED) { } void io_stream_init(struct iostream_private *stream) { if (stream->close == NULL) stream->close = io_stream_default_close; if (stream->destroy == NULL) stream->destroy = io_stream_default_destroy; stream->ioloop = current_ioloop; stream->refcount = 1; } void io_stream_ref(struct iostream_private *stream) { i_assert(stream->refcount > 0); stream->refcount++; } bool io_stream_unref(struct iostream_private *stream) { i_assert(stream->refcount > 0); if (--stream->refcount != 0) return TRUE; stream->close(stream, FALSE); stream->destroy(stream); return FALSE; } void io_stream_free(struct iostream_private *stream) { const struct iostream_destroy_callback *dc; if (array_is_created(&stream->destroy_callbacks)) { array_foreach(&stream->destroy_callbacks, dc) dc->callback(dc->context); array_free(&stream->destroy_callbacks); } i_free(stream->error); i_free(stream->name); i_free(stream); } void io_stream_close(struct iostream_private *stream, bool close_parent) { stream->close(stream, close_parent); } void io_stream_set_max_buffer_size(struct iostream_private *stream, size_t max_size) { stream->set_max_buffer_size(stream, max_size); } void io_stream_add_destroy_callback(struct iostream_private *stream, void (*callback)(void *), void *context) { struct iostream_destroy_callback *dc; if (!array_is_created(&stream->destroy_callbacks)) i_array_init(&stream->destroy_callbacks, 2); dc = array_append_space(&stream->destroy_callbacks); dc->callback = callback; dc->context = context; } void io_stream_remove_destroy_callback(struct iostream_private *stream, void (*callback)(void *)) { const struct iostream_destroy_callback *dcs; unsigned int i, count; dcs = array_get(&stream->destroy_callbacks, &count); for (i = 0; i < count; i++) { if (dcs[i].callback == callback) { array_delete(&stream->destroy_callbacks, i, 1); return; } } i_unreached(); } void io_stream_set_error(struct iostream_private *stream, const char *fmt, ...) { va_list args; va_start(args, fmt); io_stream_set_verror(stream, fmt, args); va_end(args); } void io_stream_set_verror(struct iostream_private *stream, const char *fmt, va_list args) { /* one of the parameters may be the old stream->error, so don't free it before the new error is created. */ char *error = i_strdup_vprintf(fmt, args); i_free(stream->error); stream->error = error; } const char *io_stream_get_disconnect_reason(struct istream *input, struct ostream *output) { const char *errstr; if (input != NULL && input->stream_errno != 0) { errno = input->stream_errno; errstr = i_stream_get_error(input); } else if (output != NULL && output->stream_errno != 0) { errno = output->stream_errno; errstr = o_stream_get_error(output); } else { errno = 0; errstr = ""; } if (errno == 0 || errno == EPIPE) return "Connection closed"; else return t_strdup_printf("Connection closed: %s", errstr); } void io_stream_switch_ioloop_to(struct iostream_private *stream, struct ioloop *ioloop) { stream->ioloop = ioloop; } struct ioloop *io_stream_get_ioloop(struct iostream_private *stream) { return (stream->ioloop == NULL ? current_ioloop : stream->ioloop); } dovecot-2.3.21.1/src/lib/test-crc32.c0000644000000000000000000000047414656633576013714 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "crc32.h" void test_crc32(void) { const char str[] = "foo\0bar"; test_begin("crc32"); test_assert(crc32_str(str) == 0x8c736521); test_assert(crc32_data(str, sizeof(str)) == 0x32c9723d); test_end(); } dovecot-2.3.21.1/src/lib/test-event-log.c0000644000000000000000000022551614656633576014706 00000000000000/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "ioloop.h" #include "str.h" #include "failures-private.h" #include enum test_log_event_type { TYPE_END, TYPE_PREFIX_APPEND, TYPE_PREFIX_REPLACE, TYPE_PREFIX_APPEND_CB, TYPE_PREFIX_REPLACE_CB, TYPE_MESSAGE_AMEND, TYPE_SKIP, }; enum test_log_event_flag { FLAG_BASE_EVENT = BIT(0), FLAG_DROP_PREFIXES_1 = BIT(1), FLAG_DROP_PREFIXES_2 = BIT(2), FLAG_DROP_PREFIXES_4 = BIT(3), }; enum test_log_flag { FLAG_NO_SEND = BIT(0), }; struct test_log_event { enum test_log_event_type type; const char *str; enum test_log_event_flag flags; }; struct test_log { const struct test_log_event *prefixes; const char *global_log_prefix; const char *base_send_prefix; const char *base_str_prefix; const char *result; const char *result_str_out; enum test_log_flag flags; }; static char *test_output = NULL; static void ATTR_FORMAT(2, 0) info_handler(const struct failure_context *ctx, const char *format, va_list args) { size_t prefix_len; i_assert(ctx->type == LOG_TYPE_INFO); i_free(test_output); T_BEGIN { string_t *str = failure_handler.v->format(ctx, &prefix_len, format, args); test_output = i_strdup(str_c(str)); } T_END; } static void ATTR_FORMAT(2, 0) error_handler(const struct failure_context *ctx, const char *format, va_list args) { size_t prefix_len; i_assert(ctx->type == LOG_TYPE_WARNING || ctx->type == LOG_TYPE_ERROR); i_free(test_output); T_BEGIN { string_t *str = failure_handler.v->format(ctx, &prefix_len, format, args); test_output = i_strdup(str_c(str)); } T_END; } static const char * test_event_log_prefix_cb(char *prefix) { return t_strdup_printf("callback(%s)", prefix); } static const char * test_event_log_message_cb(char *prefix, enum log_type log_type ATTR_UNUSED, const char *message) { return t_strdup_printf("[%s%s]", prefix, message); } static void test_event_log_message(void) { struct test_log tests[] = { { .prefixes = (const struct test_log_event []) { { .type = TYPE_END } }, .global_log_prefix = "global1.", .result = "global1.Info: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { .type = TYPE_END } }, .result = "replaced1,Info: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { .type = TYPE_END } }, .result = "replaced2.Info: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { .type = TYPE_END } }, .result = "replaced1,Info: appended2.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { .type = TYPE_END } }, .result = "replaced1,Info: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global2.", .result = "global2.Info: appended1,TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: appended1,appended2.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { TYPE_PREFIX_APPEND, "appended3.", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: " "appended1,appended2.appended3.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { .type = TYPE_END } }, .result = "replaced2.Info: appended3#TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { .type = TYPE_END } }, .result = "replaced4;Info: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { TYPE_PREFIX_APPEND, "appended5-", 0 }, { .type = TYPE_END } }, .result = "replaced4;Info: appended5-TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND_CB, "appended1-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: callback(appended1-)TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced1.", 0 }, { TYPE_PREFIX_REPLACE_CB, "replaced2-", 0 }, { .type = TYPE_END } }, .result = "callback(replaced2-)Info: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE_CB, "replaced1.", 0 }, { TYPE_PREFIX_APPEND, "appended1,", 0 }, { .type = TYPE_END } }, .result = "callback(replaced1.)Info: appended1,TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE_CB, "replaced1.", 0 }, { TYPE_PREFIX_REPLACE, "replaced2-", 0 }, { .type = TYPE_END } }, .result = "replaced2-Info: TEXT", }, /* Tests involving event_set_log_message_callback() */ { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-" , 0}, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: [amended1-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: [amended1-[amended2-TEXT]]", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended1-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: [amended1-appended1-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: appended1-[amended1-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: " "appended1-[amended1-appended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { TYPE_PREFIX_APPEND, "appended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: [amended1-appended1-" "[amended2-appended2-TEXT]]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { .type = TYPE_END } }, .result = "replaced1,Info: [amended1-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_REPLACE, "replaced2,", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { .type = TYPE_END } }, .result = "replaced2,Info: [amended2-TEXT]", }, /* Tests with params->base_str_out != NULL */ { .prefixes = (const struct test_log_event []) { { .type = TYPE_END } }, .global_log_prefix = "global1.", .result = "global1.Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { .type = TYPE_END } }, .result = "replaced1,Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .result = "replaced1,Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { .type = TYPE_END } }, .result = "replaced2.Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { .type = TYPE_END } }, .result = "replaced2.Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .result = "replaced2.Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { .type = TYPE_END } }, .result = "replaced1,Info: appended2.TEXT", .result_str_out = "appended2.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { .type = TYPE_END } }, .result = "replaced1,Info: appended2.TEXT", .result_str_out = "appended2.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .result = "replaced1,Info: appended2.TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { .type = TYPE_END } }, .result = "replaced1,Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { .type = TYPE_END } }, .result = "replaced1,Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced1,", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .result = "replaced1,Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global2.", .result = "global2.Info: appended1,TEXT", .result_str_out = "appended1,TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .global_log_prefix = "global2.", .result = "global2.Info: appended1,TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: appended1,appended2.TEXT", .result_str_out = "appended1,appended2.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: appended1,appended2.TEXT", .result_str_out = "appended2.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: appended1,appended2.TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { TYPE_PREFIX_APPEND, "appended3.", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: " "appended1,appended2.appended3.TEXT", .result_str_out = "appended1,appended2.appended3.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { TYPE_PREFIX_APPEND, "appended3.", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: " "appended1,appended2.appended3.TEXT", .result_str_out = "appended2.appended3.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended3.", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: " "appended1,appended2.appended3.TEXT", .result_str_out = "appended3.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { TYPE_PREFIX_APPEND, "appended3.", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: " "appended1,appended2.appended3.TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { .type = TYPE_END } }, .result = "replaced2.Info: appended3#TEXT", .result_str_out = "appended3#TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { .type = TYPE_END } }, .result = "replaced2.Info: appended3#TEXT", .result_str_out = "appended3#TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { .type = TYPE_END } }, .result = "replaced2.Info: appended3#TEXT", .result_str_out = "appended3#TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .result = "replaced2.Info: appended3#TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { .type = TYPE_END } }, .result = "replaced4;Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { .type = TYPE_END } }, .result = "replaced4;Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { .type = TYPE_END } }, .result = "replaced4;Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { .type = TYPE_END } }, .result = "replaced4;Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .result = "replaced4;Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { TYPE_PREFIX_APPEND, "appended5-", 0 }, { .type = TYPE_END } }, .result = "replaced4;Info: appended5-TEXT", .result_str_out = "appended5-TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { TYPE_PREFIX_APPEND, "appended5-", 0 }, { .type = TYPE_END } }, .result = "replaced4;Info: appended5-TEXT", .result_str_out = "appended5-TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { TYPE_PREFIX_APPEND, "appended5-", 0 }, { .type = TYPE_END } }, .result = "replaced4;Info: appended5-TEXT", .result_str_out = "appended5-TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { TYPE_PREFIX_APPEND, "appended5-", 0 }, { .type = TYPE_END } }, .result = "replaced4;Info: appended5-TEXT", .result_str_out = "appended5-TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended5-", 0 }, { .type = TYPE_END } }, .result = "replaced4;Info: appended5-TEXT", .result_str_out = "appended5-TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { TYPE_PREFIX_APPEND, "appended5-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .result = "replaced4;Info: appended5-TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND_CB, "appended1-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: callback(appended1-)TEXT", .result_str_out = "callback(appended1-)TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND_CB, "appended1-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: callback(appended1-)TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced1.", 0 }, { TYPE_PREFIX_REPLACE_CB, "replaced2-", 0 }, { .type = TYPE_END } }, .result = "callback(replaced2-)Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced1.", 0 }, { TYPE_PREFIX_REPLACE_CB, "replaced2-", 0 }, { .type = TYPE_END } }, .result = "callback(replaced2-)Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced1.", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE_CB, "replaced2-", 0 }, { .type = TYPE_END } }, .result = "callback(replaced2-)Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced1.", 0 }, { TYPE_PREFIX_REPLACE_CB, "replaced2-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .result = "callback(replaced2-)Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE_CB, "replaced1.", 0 }, { TYPE_PREFIX_APPEND, "appended1,", 0 }, { .type = TYPE_END } }, .result = "callback(replaced1.)Info: appended1,TEXT", .result_str_out = "appended1,TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE_CB, "replaced1.", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended1,", 0 }, { .type = TYPE_END } }, .result = "callback(replaced1.)Info: appended1,TEXT", .result_str_out = "appended1,TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE_CB, "replaced1.", 0 }, { TYPE_PREFIX_APPEND, "appended1,", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .result = "callback(replaced1.)Info: appended1,TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE_CB, "replaced1.", 0 }, { TYPE_PREFIX_REPLACE, "replaced2-", 0 }, { .type = TYPE_END } }, .result = "replaced2-Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE_CB, "replaced1.", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced2-", 0 }, { .type = TYPE_END } }, .result = "replaced2-Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE_CB, "replaced1.", 0 }, { TYPE_PREFIX_REPLACE, "replaced2-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .result = "replaced2-Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-" , 0}, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: [amended1-TEXT]", .result_str_out = "[amended1-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-" , FLAG_BASE_EVENT}, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: [amended1-TEXT]", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: [amended1-[amended2-TEXT]]", .result_str_out = "[amended1-[amended2-TEXT]]", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", FLAG_BASE_EVENT }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: [amended1-[amended2-TEXT]]", .result_str_out = "[amended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: [amended1-[amended2-TEXT]]", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended1-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: [amended1-appended1-TEXT]", .result_str_out = "[amended1-appended1-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended1-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: [amended1-appended1-TEXT]", .result_str_out = "appended1-TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended1-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: [amended1-appended1-TEXT]", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: appended1-[amended1-TEXT]", .result_str_out = "appended1-[amended1-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1-", FLAG_BASE_EVENT }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: appended1-[amended1-TEXT]", .result_str_out = "[amended1-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: appended1-[amended1-TEXT]", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: " "appended1-[amended1-appended2-TEXT]", .result_str_out = "appended1-[amended1-appended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1-", FLAG_BASE_EVENT }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: " "appended1-[amended1-appended2-TEXT]", .result_str_out = "[amended1-appended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: " "appended1-[amended1-appended2-TEXT]", .result_str_out = "appended2-TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended2-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: " "appended1-[amended1-appended2-TEXT]", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { TYPE_PREFIX_APPEND, "appended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: [amended1-appended1-" "[amended2-appended2-TEXT]]", .result_str_out = "[amended1-appended1-" "[amended2-appended2-TEXT]]", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { TYPE_PREFIX_APPEND, "appended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: [amended1-appended1-" "[amended2-appended2-TEXT]]", .result_str_out = "appended1-[amended2-appended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended1-", FLAG_BASE_EVENT }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { TYPE_PREFIX_APPEND, "appended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: [amended1-appended1-" "[amended2-appended2-TEXT]]", .result_str_out = "[amended2-appended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: [amended1-appended1-" "[amended2-appended2-TEXT]]", .result_str_out = "appended2-TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { TYPE_PREFIX_APPEND, "appended2-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = "global4.Info: [amended1-appended1-" "[amended2-appended2-TEXT]]", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { .type = TYPE_END } }, .result = "replaced1,Info: [amended1-TEXT]", .result_str_out = "[amended1-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", FLAG_BASE_EVENT }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { .type = TYPE_END } }, .result = "replaced1,Info: [amended1-TEXT]", .result_str_out = "[amended1-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .result = "replaced1,Info: [amended1-TEXT]", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_REPLACE, "replaced2,", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { .type = TYPE_END } }, .result = "replaced2,Info: [amended2-TEXT]", .result_str_out = "[amended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", FLAG_BASE_EVENT }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_REPLACE, "replaced2,", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { .type = TYPE_END } }, .result = "replaced2,Info: [amended2-TEXT]", .result_str_out = "[amended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced2,", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { .type = TYPE_END } }, .result = "replaced2,Info: [amended2-TEXT]", .result_str_out = "[amended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_REPLACE, "replaced2,", FLAG_BASE_EVENT }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { .type = TYPE_END } }, .result = "replaced2,Info: [amended2-TEXT]", .result_str_out = "[amended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_REPLACE, "replaced2,", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .result = "replaced2,Info: [amended2-TEXT]", .result_str_out = "TEXT", }, /* Tests involving params->no_send */ { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended3.", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = NULL, .result_str_out = "appended3.TEXT", .flags = FLAG_NO_SEND, }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended1-", FLAG_BASE_EVENT }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { TYPE_PREFIX_APPEND, "appended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .result = NULL, .result_str_out = "[amended2-appended2-TEXT]", .flags = FLAG_NO_SEND, }, /* Tests with params->base_*_prefix assigned */ { .prefixes = (const struct test_log_event []) { { .type = TYPE_END } }, .global_log_prefix = "global1.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global1.Info: PREFIX: TEXT", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced1,Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced1,Info: PREFIX: TEXT", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced2.Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced2.Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced2.Info: PREFIX: TEXT", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced1,Info: appended2.TEXT", .result_str_out = "appended2.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced1,Info: PREFIX: appended2.TEXT", .result_str_out = "STR_PREFIX: appended2.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced1,Info: appended2.PREFIX: TEXT", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced1,Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced1,Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced1,", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced1,Info: PREFIX: TEXT", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global2.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global2.Info: PREFIX: appended1,TEXT", .result_str_out = "STR_PREFIX: appended1,TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .global_log_prefix = "global2.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global2.Info: appended1,PREFIX: TEXT", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global3.Info: PREFIX: " "appended1,appended2.TEXT", .result_str_out = "STR_PREFIX: " "appended1,appended2.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global3.Info: appended1,PREFIX: " "appended2.TEXT", .result_str_out = "STR_PREFIX: appended2.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global3.Info: appended1,appended2." "PREFIX: TEXT", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { TYPE_PREFIX_APPEND, "appended3.", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global3.Info: PREFIX: " "appended1,appended2.appended3.TEXT", .result_str_out = "STR_PREFIX: " "appended1,appended2.appended3.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { TYPE_PREFIX_APPEND, "appended3.", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global3.Info: appended1,PREFIX: " "appended2.appended3.TEXT", .result_str_out = "STR_PREFIX: " "appended2.appended3.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended3.", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global3.Info: appended1,appended2.PREFIX: " "appended3.TEXT", .result_str_out = "STR_PREFIX: appended3.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { TYPE_PREFIX_APPEND, "appended3.", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global3.Info: " "appended1,appended2.appended3.PREFIX: TEXT", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced2.Info: appended3#TEXT", .result_str_out = "appended3#TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced2.Info: appended3#TEXT", .result_str_out = "appended3#TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced2.Info: PREFIX: appended3#TEXT", .result_str_out = "STR_PREFIX: appended3#TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced2.Info: appended3#PREFIX: TEXT", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced4;Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced4;Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced4;Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced4;Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced4;Info: PREFIX: TEXT", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { TYPE_PREFIX_APPEND, "appended5-", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced4;Info: appended5-TEXT", .result_str_out = "appended5-TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { TYPE_PREFIX_APPEND, "appended5-", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced4;Info: appended5-TEXT", .result_str_out = "appended5-TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { TYPE_PREFIX_APPEND, "appended5-", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced4;Info: appended5-TEXT", .result_str_out = "appended5-TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { TYPE_PREFIX_APPEND, "appended5-", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced4;Info: appended5-TEXT", .result_str_out = "appended5-TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended5-", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced4;Info: PREFIX: appended5-TEXT", .result_str_out = "STR_PREFIX: appended5-TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced2.", 0 }, { TYPE_PREFIX_APPEND, "appended3#", 0 }, { TYPE_PREFIX_REPLACE, "replaced4;", 0 }, { TYPE_PREFIX_APPEND, "appended5-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced4;Info: appended5-PREFIX: TEXT", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND_CB, "appended1-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global3.Info: PREFIX: " "callback(appended1-)TEXT", .result_str_out = "STR_PREFIX: " "callback(appended1-)TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND_CB, "appended1-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global3.Info: callback(appended1-)PREFIX: " "TEXT", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced1.", 0 }, { TYPE_PREFIX_REPLACE_CB, "replaced2-", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "callback(replaced2-)Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced1.", 0 }, { TYPE_PREFIX_REPLACE_CB, "replaced2-", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "callback(replaced2-)Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced1.", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE_CB, "replaced2-", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "callback(replaced2-)Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_REPLACE, "replaced1.", 0 }, { TYPE_PREFIX_REPLACE_CB, "replaced2-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "callback(replaced2-)Info: PREFIX: TEXT", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE_CB, "replaced1.", 0 }, { TYPE_PREFIX_APPEND, "appended1,", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "callback(replaced1.)Info: appended1,TEXT", .result_str_out = "appended1,TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE_CB, "replaced1.", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended1,", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "callback(replaced1.)Info: PREFIX: " "appended1,TEXT", .result_str_out = "STR_PREFIX: appended1,TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE_CB, "replaced1.", 0 }, { TYPE_PREFIX_APPEND, "appended1,", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "callback(replaced1.)Info: appended1,PREFIX: " "TEXT", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE_CB, "replaced1.", 0 }, { TYPE_PREFIX_REPLACE, "replaced2-", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced2-Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE_CB, "replaced1.", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced2-", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced2-Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE_CB, "replaced1.", 0 }, { TYPE_PREFIX_REPLACE, "replaced2-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced2-Info: PREFIX: TEXT", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-" , 0}, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: PREFIX: [amended1-TEXT]", .result_str_out = "STR_PREFIX: [amended1-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-" , FLAG_BASE_EVENT}, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: [amended1-PREFIX: TEXT]", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: PREFIX: " "[amended1-[amended2-TEXT]]", .result_str_out = "STR_PREFIX: " "[amended1-[amended2-TEXT]]", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", FLAG_BASE_EVENT }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: [amended1-PREFIX: " "[amended2-TEXT]]", .result_str_out = "STR_PREFIX: [amended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: [amended1-[amended2-PREFIX: " "TEXT]]", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended1-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: PREFIX: " "[amended1-appended1-TEXT]", .result_str_out = "STR_PREFIX: " "[amended1-appended1-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended1-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: [amended1-PREFIX: " "appended1-TEXT]", .result_str_out = "STR_PREFIX: appended1-TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended1-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: [amended1-appended1-PREFIX: " "TEXT]", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: PREFIX: " "appended1-[amended1-TEXT]", .result_str_out = "STR_PREFIX: " "appended1-[amended1-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1-", FLAG_BASE_EVENT }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: appended1-PREFIX: " "[amended1-TEXT]", .result_str_out = "STR_PREFIX: [amended1-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: appended1-[amended1-PREFIX: " "TEXT]", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: PREFIX: " "appended1-[amended1-appended2-TEXT]", .result_str_out = "STR_PREFIX: " "appended1-[amended1-appended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1-", FLAG_BASE_EVENT }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: appended1-PREFIX: " "[amended1-appended2-TEXT]", .result_str_out = "STR_PREFIX: " "[amended1-appended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: appended1-[amended1-PREFIX: " "appended2-TEXT]", .result_str_out = "STR_PREFIX: appended2-TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended2-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: " "appended1-[amended1-appended2-PREFIX: TEXT]", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { TYPE_PREFIX_APPEND, "appended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: PREFIX: [amended1-appended1-" "[amended2-appended2-TEXT]]", .result_str_out = "STR_PREFIX: [amended1-appended1-" "[amended2-appended2-TEXT]]", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { TYPE_PREFIX_APPEND, "appended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: [amended1-PREFIX: appended1-" "[amended2-appended2-TEXT]]", .result_str_out = "STR_PREFIX: " "appended1-[amended2-appended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended1-", FLAG_BASE_EVENT }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { TYPE_PREFIX_APPEND, "appended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: [amended1-appended1-PREFIX: " "[amended2-appended2-TEXT]]", .result_str_out = "STR_PREFIX: " "[amended2-appended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", FLAG_BASE_EVENT }, { TYPE_PREFIX_APPEND, "appended2-", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: [amended1-appended1-" "[amended2-PREFIX: appended2-TEXT]]", .result_str_out = "STR_PREFIX: appended2-TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_APPEND, "appended1-", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { TYPE_PREFIX_APPEND, "appended2-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .global_log_prefix = "global4.", .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "global4.Info: [amended1-appended1-" "[amended2-appended2-PREFIX: TEXT]]", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced1,Info: [amended1-TEXT]", .result_str_out = "[amended1-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", FLAG_BASE_EVENT }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced1,Info: PREFIX: [amended1-TEXT]", .result_str_out = "STR_PREFIX: [amended1-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced1,Info: [amended1-PREFIX: TEXT]", .result_str_out = "STR_PREFIX: TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_REPLACE, "replaced2,", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced2,Info: [amended2-TEXT]", .result_str_out = "[amended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", FLAG_BASE_EVENT }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_REPLACE, "replaced2,", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced2,Info: [amended2-TEXT]", .result_str_out = "[amended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", FLAG_BASE_EVENT }, { TYPE_PREFIX_REPLACE, "replaced2,", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced2,Info: [amended2-TEXT]", .result_str_out = "[amended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_REPLACE, "replaced2,", FLAG_BASE_EVENT }, { TYPE_MESSAGE_AMEND, "amended2-", 0 }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced2,Info: PREFIX: [amended2-TEXT]", .result_str_out = "STR_PREFIX: [amended2-TEXT]", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_REPLACE, "replaced1,", 0 }, { TYPE_MESSAGE_AMEND, "amended1-", 0 }, { TYPE_PREFIX_REPLACE, "replaced2,", 0 }, { TYPE_MESSAGE_AMEND, "amended2-", FLAG_BASE_EVENT }, { .type = TYPE_END } }, .base_send_prefix = "PREFIX: ", .base_str_prefix = "STR_PREFIX: ", .result = "replaced2,Info: [amended2-PREFIX: TEXT]", .result_str_out = "STR_PREFIX: TEXT", }, /* Tests in which parent log prefixes are dropped by an event lower in the hierarchy. */ { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { TYPE_PREFIX_APPEND, "appended3.", 0 }, { TYPE_PREFIX_APPEND, "appended4.", 0 }, { TYPE_PREFIX_APPEND, "appended5.", 0 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: " "appended1,appended2.appended3." "appended4.appended5.TEXT", .result_str_out = "appended1,appended2.appended3." "appended4.appended5.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { TYPE_PREFIX_APPEND, "appended3.", 0 }, { TYPE_PREFIX_APPEND, "appended4.", 0 }, { TYPE_PREFIX_APPEND, "appended5.", FLAG_DROP_PREFIXES_1 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: " "appended1,appended2.appended3." "appended5.TEXT", .result_str_out = "appended1,appended2.appended3." "appended5.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { TYPE_PREFIX_APPEND, "appended3.", 0 }, { TYPE_PREFIX_APPEND, "appended4.", 0 }, { TYPE_PREFIX_APPEND, "appended5.", 0 }, { TYPE_SKIP, NULL, FLAG_DROP_PREFIXES_1 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: " "appended1,appended2.appended3." "appended4.TEXT", .result_str_out = "appended1,appended2.appended3." "appended4.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { TYPE_PREFIX_APPEND, "appended3.", 0 }, { TYPE_PREFIX_APPEND, "appended4.", 0 }, { TYPE_PREFIX_APPEND, "appended5.", FLAG_DROP_PREFIXES_2 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: " "appended1,appended2.appended5.TEXT", .result_str_out = "appended1,appended2.appended5.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { TYPE_PREFIX_APPEND, "appended3.", 0 }, { TYPE_PREFIX_APPEND, "appended4.", 0 }, { TYPE_PREFIX_APPEND, "appended5.", (FLAG_DROP_PREFIXES_1 | FLAG_DROP_PREFIXES_2) }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: appended1,appended5.TEXT", .result_str_out = "appended1,appended5.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { TYPE_PREFIX_APPEND, "appended3.", 0 }, { TYPE_PREFIX_APPEND, "appended4.", 0 }, { TYPE_PREFIX_APPEND, "appended5.", FLAG_DROP_PREFIXES_4 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: appended5.TEXT", .result_str_out = "appended5.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { TYPE_PREFIX_APPEND, "appended3.", 0 }, { TYPE_PREFIX_APPEND, "appended4.", 0 }, { TYPE_PREFIX_APPEND, "appended5.", (FLAG_DROP_PREFIXES_1 | FLAG_DROP_PREFIXES_2 | FLAG_DROP_PREFIXES_4) }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: appended5.TEXT", .result_str_out = "appended5.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { TYPE_PREFIX_APPEND, "appended3.", 0 }, { TYPE_PREFIX_APPEND, "appended4.", 0 }, { TYPE_PREFIX_APPEND, "appended5.", 0 }, { TYPE_SKIP, NULL, (FLAG_DROP_PREFIXES_1 | FLAG_DROP_PREFIXES_2 | FLAG_DROP_PREFIXES_4) }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: TEXT", .result_str_out = "TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", FLAG_DROP_PREFIXES_1 }, { TYPE_PREFIX_APPEND, "appended3.", 0 }, { TYPE_PREFIX_APPEND, "appended4.", 0 }, { TYPE_PREFIX_APPEND, "appended5.", FLAG_DROP_PREFIXES_1 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: " "appended2.appended3.appended5.TEXT", .result_str_out = "appended2.appended3.appended5.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", FLAG_DROP_PREFIXES_1 }, { TYPE_PREFIX_APPEND, "appended2.", FLAG_DROP_PREFIXES_1 }, { TYPE_PREFIX_APPEND, "appended3.", FLAG_DROP_PREFIXES_1 }, { TYPE_PREFIX_APPEND, "appended4.", FLAG_DROP_PREFIXES_1 }, { TYPE_PREFIX_APPEND, "appended5.", FLAG_DROP_PREFIXES_1 }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: appended5.TEXT", .result_str_out = "appended5.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { .type = TYPE_SKIP }, { TYPE_PREFIX_APPEND, "appended2.", FLAG_DROP_PREFIXES_1 }, { .type = TYPE_SKIP }, { TYPE_PREFIX_APPEND, "appended3.", 0 }, { .type = TYPE_SKIP }, { TYPE_PREFIX_APPEND, "appended4.", 0 }, { .type = TYPE_SKIP }, { TYPE_PREFIX_APPEND, "appended5.", FLAG_DROP_PREFIXES_1 }, { .type = TYPE_SKIP }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: " "appended2.appended3.appended5.TEXT", .result_str_out = "appended2.appended3.appended5.TEXT", }, { .prefixes = (const struct test_log_event []) { { TYPE_PREFIX_APPEND, "appended1,", 0 }, { TYPE_PREFIX_APPEND, "appended2.", 0 }, { TYPE_PREFIX_APPEND, "appended3.", FLAG_DROP_PREFIXES_1 }, { TYPE_PREFIX_APPEND, "appended4.", 0 }, { TYPE_PREFIX_APPEND, "appended5.", (FLAG_DROP_PREFIXES_1 | FLAG_DROP_PREFIXES_2) }, { .type = TYPE_END } }, .global_log_prefix = "global3.", .result = "global3.Info: appended5.TEXT", .result_str_out = "appended5.TEXT", }, }; test_begin("event log message"); failure_callback_t *orig_fatal, *orig_error, *orig_info, *orig_debug; i_get_failure_handlers(&orig_fatal, &orig_error, &orig_info, &orig_debug); i_set_info_handler(info_handler); for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) T_BEGIN { const struct test_log *test = &tests[i]; struct event_log_params params = { .log_type = LOG_TYPE_INFO, .base_send_prefix = test->base_send_prefix, .base_str_prefix = test->base_str_prefix, .no_send = ((test->flags & FLAG_NO_SEND) != 0), }; i_free(test_output); if (test->global_log_prefix != NULL) i_set_failure_prefix("%s", test->global_log_prefix); else i_set_failure_prefix("UNEXPECTED GLOBAL PREFIX"); struct event *event, *parent; event = parent = event_create(NULL); for (unsigned int j = 0; test->prefixes[j].type != TYPE_END; j++) { unsigned int drop_prefixes = 0; if (event == NULL) { struct event *child = event_create(parent); event_unref(&parent); event = parent = child; } if ((test->prefixes[j].flags & FLAG_BASE_EVENT) != 0) { i_assert(params.base_event == NULL); params.base_event = event; } if ((test->prefixes[j].flags & FLAG_DROP_PREFIXES_1) != 0) drop_prefixes += 1; if ((test->prefixes[j].flags & FLAG_DROP_PREFIXES_2) != 0) drop_prefixes += 2; if ((test->prefixes[j].flags & FLAG_DROP_PREFIXES_4) != 0) drop_prefixes += 4; event_drop_parent_log_prefixes(event, drop_prefixes); switch (test->prefixes[j].type) { case TYPE_END: i_unreached(); case TYPE_PREFIX_APPEND: event_set_append_log_prefix(event, test->prefixes[j].str); break; case TYPE_PREFIX_REPLACE: event_replace_log_prefix(event, test->prefixes[j].str); break; case TYPE_PREFIX_APPEND_CB: event_set_log_prefix_callback(event, FALSE, test_event_log_prefix_cb, (char*)test->prefixes[j].str); break; case TYPE_PREFIX_REPLACE_CB: event_set_log_prefix_callback(event, TRUE, test_event_log_prefix_cb, (char*)test->prefixes[j].str); break; case TYPE_MESSAGE_AMEND: event_set_log_message_callback(event, test_event_log_message_cb, (char*)test->prefixes[j].str); break; case TYPE_SKIP: break; } event = NULL; } event = parent; if (test->result_str_out != NULL) { /* Use small value so buffer size grows. This way the unit test fails if anyone attempts to add data-stack frame to event_log(). */ params.base_str_out = t_str_new(1); } event_log(event, ¶ms, "TEXT"); test_assert_strcmp(test->result, test_output); if (test->result_str_out != NULL) { test_assert_strcmp(test->result_str_out, str_c(params.base_str_out)); } event_unref(&event); } T_END; i_set_info_handler(orig_info); i_unset_failure_prefix(); i_free(test_output); test_end(); } static void test_event_duration() { uintmax_t duration; test_begin("event duration"); struct event *e = event_create(NULL); usleep(10); e_info(e, "Submit event"); event_get_last_duration(e, &duration); test_assert(duration > 0); event_unref(&e); test_end(); } static void test_event_log_level(void) { test_begin("event log level"); failure_callback_t *orig_fatal, *orig_error, *orig_info, *orig_debug; i_get_failure_handlers(&orig_fatal, &orig_error, &orig_info, &orig_debug); i_set_info_handler(info_handler); i_set_error_handler(error_handler); struct event *event = event_create(NULL); event_set_min_log_level(event, LOG_TYPE_WARNING); errno = EACCES; e_info(event, "Info event"); test_assert(test_output == NULL); e_warning(event, "Warning event"); test_assert_strcmp(test_output, "Warning: Warning event"); event_unref(&event); i_set_info_handler(orig_info); i_set_error_handler(orig_error); i_free(test_output); test_assert(errno == EACCES); test_end(); } void test_event_log(void) { test_event_log_message(); test_event_duration(); test_event_log_level(); } dovecot-2.3.21.1/src/lib/test-aqueue.c0000644000000000000000000000351014656633576014257 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "array.h" #include "aqueue.h" static bool aqueue_is_ok(struct aqueue *aqueue, unsigned int deleted_n) { const unsigned int *p; unsigned int n, i, count; count = aqueue_count(aqueue); for (i = 0, n = 1; i < count; i++, n++) { p = array_idx_i(aqueue->arr, aqueue_idx(aqueue, i)); if (i == deleted_n) n++; if (*p != n) return FALSE; } return TRUE; } static const unsigned int aqueue_input[] = { 1, 2, 3, 4, 5, 6 }; static const char *test_aqueue2(unsigned int initial_size) { ARRAY(unsigned int) aqueue_array; unsigned int i, j, k; for (i = 0; i < N_ELEMENTS(aqueue_input); i++) { for (k = 0; k < N_ELEMENTS(aqueue_input); k++) { struct aqueue *aqueue; t_array_init(&aqueue_array, initial_size); aqueue = aqueue_init(&aqueue_array.arr); aqueue->head = aqueue->tail = initial_size - 1; for (j = 0; j < k; j++) { aqueue_append(aqueue, &aqueue_input[j]); if (aqueue_count(aqueue) != j + 1) { return t_strdup_printf("Wrong count after append %u vs %u)", aqueue_count(aqueue), j + 1); } if (!aqueue_is_ok(aqueue, UINT_MAX)) return "Invalid data after append"; } if (k != 0 && i < k) { aqueue_delete(aqueue, i); if (aqueue_count(aqueue) != k - 1) return "Wrong count after delete"; if (!aqueue_is_ok(aqueue, i)) return "Invalid data after delete"; } aqueue_clear(aqueue); if (aqueue_count(aqueue) != 0) return "aqueue_clear() broken"; aqueue_deinit(&aqueue); } } return NULL; } void test_aqueue(void) { unsigned int i; const char *reason = NULL; for (i = 1; i <= N_ELEMENTS(aqueue_input) + 1 && reason == NULL; i++) { T_BEGIN { reason = test_aqueue2(i); } T_END; } test_out_reason("aqueue", reason == NULL, reason); } dovecot-2.3.21.1/src/lib/mountpoint.h0000644000000000000000000000133614656633576014242 00000000000000#ifndef MOUNTPOINT_H #define MOUNTPOINT_H struct mountpoint { char *device_path; char *mount_path; char *type; dev_t dev; unsigned int block_size; /* may not be set for iteration */ }; /* Returns 1 = found, 0 = not found (from mount tabs, or the path itself), -1 = error */ int mountpoint_get(const char *path, pool_t pool, struct mountpoint *point_r); /* Iterate through mountpoints */ struct mountpoint_iter *mountpoint_iter_init(void); /* Returns the next mountpoint or NULL if there are no more. */ const struct mountpoint *mountpoint_iter_next(struct mountpoint_iter *iter); /* Returns 0 if mountpoints were iterated successfully, -1 if it failed. */ int mountpoint_iter_deinit(struct mountpoint_iter **iter); #endif dovecot-2.3.21.1/src/lib/hash-decl.h0000644000000000000000000000101714656633576013652 00000000000000#ifndef HASH_DECL_H #define HASH_DECL_H #define HASH_TABLE_UNION(key_type, value_type) { \ struct hash_table *_table; \ key_type _key; \ key_type *_keyp; \ const key_type _const_key; \ value_type _value; \ value_type *_valuep; \ } #define HASH_TABLE_DEFINE_TYPE(name, key_type, value_type) \ union hash ## __ ## name HASH_TABLE_UNION(key_type, value_type) #define HASH_TABLE(key_type, value_type) \ union HASH_TABLE_UNION(key_type, value_type) #define HASH_TABLE_TYPE(name) \ union hash ## __ ## name #endif dovecot-2.3.21.1/src/lib/hex-dec.c0000644000000000000000000000147714656633576013344 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "hex-dec.h" void dec2hex(unsigned char *hexstr, uintmax_t dec, unsigned int hexstr_size) { unsigned int i; for (i = 0; i < hexstr_size; i++) { unsigned int value = dec & 0x0f; if (value < 10) hexstr[hexstr_size-i-1] = value + '0'; else hexstr[hexstr_size-i-1] = value - 10 + 'A'; dec >>= 4; } } uintmax_t hex2dec(const unsigned char *data, unsigned int len) { unsigned int i; uintmax_t value = 0; for (i = 0; i < len; i++) { value = value*0x10; if (data[i] >= '0' && data[i] <= '9') value += data[i]-'0'; else if (data[i] >= 'A' && data[i] <= 'F') value += data[i]-'A' + 10; else if (data[i] >= 'a' && data[i] <= 'f') value += data[i]-'a' + 10; else return 0; } return value; } dovecot-2.3.21.1/src/lib/iostream-temp.c0000644000000000000000000002741314656633576014613 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "str.h" #include "safe-mkstemp.h" #include "write-full.h" #include "istream-private.h" #include "ostream-private.h" #include "iostream-temp.h" #include #define IOSTREAM_TEMP_MAX_BUF_SIZE_DEFAULT (1024*128) struct temp_ostream { struct ostream_private ostream; char *temp_path_prefix; enum iostream_temp_flags flags; size_t max_mem_size; struct istream *dupstream; uoff_t dupstream_offset, dupstream_start_offset; char *name; buffer_t *buf; int fd; bool fd_tried; uoff_t fd_size; }; static bool o_stream_temp_dup_cancel(struct temp_ostream *tstream, enum ostream_send_istream_result *res_r); static void o_stream_temp_close(struct iostream_private *stream, bool close_parent ATTR_UNUSED) { struct temp_ostream *tstream = container_of(stream, struct temp_ostream, ostream.iostream); i_close_fd(&tstream->fd); buffer_free(&tstream->buf); i_free(tstream->temp_path_prefix); i_free(tstream->name); } static int o_stream_temp_move_to_fd(struct temp_ostream *tstream) { string_t *path; if (tstream->fd_tried) return -1; tstream->fd_tried = TRUE; path = t_str_new(128); str_append(path, tstream->temp_path_prefix); tstream->fd = safe_mkstemp_hostpid(path, 0600, (uid_t)-1, (gid_t)-1); if (tstream->fd == -1) { i_error("safe_mkstemp(%s) failed: %m", str_c(path)); return -1; } if (i_unlink(str_c(path)) < 0) { i_close_fd(&tstream->fd); return -1; } if (write_full(tstream->fd, tstream->buf->data, tstream->buf->used) < 0) { i_error("write(%s) failed: %m", str_c(path)); i_close_fd(&tstream->fd); return -1; } /* make the fd available also to o_stream_get_fd(), e.g. for unit tests */ tstream->ostream.fd = tstream->fd; tstream->fd_size = tstream->buf->used; buffer_free(&tstream->buf); return 0; } int o_stream_temp_move_to_memory(struct ostream *output) { struct temp_ostream *tstream = container_of(output->real_stream, struct temp_ostream, ostream); unsigned char buf[IO_BLOCK_SIZE]; uoff_t offset = 0; ssize_t ret = 0; i_assert(tstream->buf == NULL); tstream->buf = buffer_create_dynamic(default_pool, 8192); while (offset < tstream->ostream.ostream.offset && (ret = pread(tstream->fd, buf, sizeof(buf), offset)) > 0) { if ((size_t)ret > tstream->ostream.ostream.offset - offset) ret = tstream->ostream.ostream.offset - offset; buffer_append(tstream->buf, buf, ret); offset += ret; } if (ret < 0) { /* not really expecting this to happen */ i_error("iostream-temp %s: read(%s*) failed: %m", o_stream_get_name(&tstream->ostream.ostream), tstream->temp_path_prefix); tstream->ostream.ostream.stream_errno = EIO; return -1; } i_close_fd(&tstream->fd); tstream->ostream.fd = -1; return 0; } static ssize_t o_stream_temp_fd_sendv(struct temp_ostream *tstream, const struct const_iovec *iov, unsigned int iov_count) { size_t bytes = 0; unsigned int i; for (i = 0; i < iov_count; i++) { if (write_full(tstream->fd, iov[i].iov_base, iov[i].iov_len) < 0) { i_error("iostream-temp %s: write(%s*) failed: %m - moving to memory", o_stream_get_name(&tstream->ostream.ostream), tstream->temp_path_prefix); if (o_stream_temp_move_to_memory(&tstream->ostream.ostream) < 0) return -1; for (; i < iov_count; i++) { buffer_append(tstream->buf, iov[i].iov_base, iov[i].iov_len); bytes += iov[i].iov_len; tstream->ostream.ostream.offset += iov[i].iov_len; } i_assert(tstream->fd_tried); return bytes; } bytes += iov[i].iov_len; tstream->ostream.ostream.offset += iov[i].iov_len; } tstream->fd_size += bytes; return bytes; } static ssize_t o_stream_temp_sendv(struct ostream_private *stream, const struct const_iovec *iov, unsigned int iov_count) { struct temp_ostream *tstream = container_of(stream, struct temp_ostream, ostream); ssize_t ret = 0; unsigned int i; enum ostream_send_istream_result res; tstream->flags &= ENUM_NEGATE(IOSTREAM_TEMP_FLAG_TRY_FD_DUP); if (tstream->dupstream != NULL) { if (o_stream_temp_dup_cancel(tstream, &res)) return -1; } if (tstream->fd != -1) return o_stream_temp_fd_sendv(tstream, iov, iov_count); for (i = 0; i < iov_count; i++) { if (tstream->buf->used + iov[i].iov_len > tstream->max_mem_size) { if (o_stream_temp_move_to_fd(tstream) == 0) { i_assert(tstream->fd != -1); return o_stream_temp_fd_sendv(tstream, iov+i, iov_count-i); } /* failed to move to temp fd, just keep it in memory */ } buffer_append(tstream->buf, iov[i].iov_base, iov[i].iov_len); ret += iov[i].iov_len; stream->ostream.offset += iov[i].iov_len; } return ret; } static bool o_stream_temp_dup_cancel(struct temp_ostream *tstream, enum ostream_send_istream_result *res_r) { struct istream *input; uoff_t size = tstream->dupstream_offset - tstream->dupstream_start_offset; bool ret = TRUE; /* use res_r to return error */ i_stream_seek(tstream->dupstream, tstream->dupstream_start_offset); tstream->ostream.ostream.offset = 0; input = i_stream_create_limit(tstream->dupstream, size); i_stream_unref(&tstream->dupstream); *res_r = io_stream_copy(&tstream->ostream.ostream, input); switch (*res_r) { case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: /* everything copied */ ret = FALSE; break; case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: i_unreached(); case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: tstream->ostream.ostream.stream_errno = input->stream_errno; io_stream_set_error(&tstream->ostream.iostream, "iostream-temp: read(%s) failed: %s", i_stream_get_name(input), i_stream_get_error(input)); break; case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: break; } i_stream_destroy(&input); return ret; } static bool o_stream_temp_dup_istream(struct temp_ostream *outstream, struct istream *instream, enum ostream_send_istream_result *res_r) { uoff_t in_size; if (!instream->readable_fd || i_stream_get_fd(instream) == -1) return FALSE; if (i_stream_get_size(instream, TRUE, &in_size) <= 0) { if (outstream->dupstream != NULL) return o_stream_temp_dup_cancel(outstream, res_r); return FALSE; } i_assert(instream->v_offset <= in_size); if (outstream->dupstream == NULL) { outstream->dupstream = instream; outstream->dupstream_start_offset = instream->v_offset; i_stream_ref(outstream->dupstream); } else { if (outstream->dupstream != instream || outstream->dupstream_offset != instream->v_offset || outstream->dupstream_offset > in_size) return o_stream_temp_dup_cancel(outstream, res_r); } i_stream_seek(instream, in_size); /* we should be at EOF now. o_stream_send_istream() asserts if eof isn't set. */ instream->eof = TRUE; outstream->dupstream_offset = instream->v_offset; outstream->ostream.ostream.offset = outstream->dupstream_offset - outstream->dupstream_start_offset; *res_r = OSTREAM_SEND_ISTREAM_RESULT_FINISHED; return TRUE; } static enum ostream_send_istream_result o_stream_temp_send_istream(struct ostream_private *_outstream, struct istream *instream) { struct temp_ostream *outstream = container_of(_outstream, struct temp_ostream, ostream); enum ostream_send_istream_result res; if ((outstream->flags & IOSTREAM_TEMP_FLAG_TRY_FD_DUP) != 0) { if (o_stream_temp_dup_istream(outstream, instream, &res)) return res; outstream->flags &= ENUM_NEGATE(IOSTREAM_TEMP_FLAG_TRY_FD_DUP); } return io_stream_copy(&outstream->ostream.ostream, instream); } static int o_stream_temp_write_at(struct ostream_private *stream, const void *data, size_t size, uoff_t offset) { struct temp_ostream *tstream = container_of(stream, struct temp_ostream, ostream); if (tstream->fd == -1) { i_assert(stream->ostream.offset == tstream->buf->used); buffer_write(tstream->buf, offset, data, size); stream->ostream.offset = tstream->buf->used; } else { if (pwrite_full(tstream->fd, data, size, offset) < 0) { stream->ostream.stream_errno = errno; i_close_fd(&tstream->fd); return -1; } if (tstream->fd_size < offset + size) tstream->fd_size = offset + size; } return 0; } static int o_stream_temp_seek(struct ostream_private *_stream, uoff_t offset) { _stream->ostream.offset = offset; return 0; } struct ostream *iostream_temp_create(const char *temp_path_prefix, enum iostream_temp_flags flags) { return iostream_temp_create_named(temp_path_prefix, flags, ""); } struct ostream *iostream_temp_create_named(const char *temp_path_prefix, enum iostream_temp_flags flags, const char *name) { return iostream_temp_create_sized(temp_path_prefix, flags, name, IOSTREAM_TEMP_MAX_BUF_SIZE_DEFAULT); } struct ostream *iostream_temp_create_sized(const char *temp_path_prefix, enum iostream_temp_flags flags, const char *name, size_t max_mem_size) { struct temp_ostream *tstream; struct ostream *output; tstream = i_new(struct temp_ostream, 1); tstream->ostream.ostream.blocking = TRUE; tstream->ostream.sendv = o_stream_temp_sendv; tstream->ostream.send_istream = o_stream_temp_send_istream; tstream->ostream.write_at = o_stream_temp_write_at; tstream->ostream.seek = o_stream_temp_seek; tstream->ostream.iostream.close = o_stream_temp_close; tstream->temp_path_prefix = i_strdup(temp_path_prefix); tstream->flags = flags; tstream->max_mem_size = max_mem_size; tstream->buf = buffer_create_dynamic(default_pool, 8192); tstream->fd = -1; output = o_stream_create(&tstream->ostream, NULL, -1); tstream->name = i_strdup(name); if (name[0] == '\0') { o_stream_set_name(output, t_strdup_printf( "(temp iostream in %s)", temp_path_prefix)); } else { o_stream_set_name(output, t_strdup_printf( "(temp iostream in %s for %s)", temp_path_prefix, name)); } return output; } static void iostream_temp_buf_destroyed(buffer_t *buf) { buffer_free(&buf); } struct istream *iostream_temp_finish(struct ostream **output, size_t max_buffer_size) { struct temp_ostream *tstream = container_of((*output)->real_stream, struct temp_ostream, ostream); struct istream *input, *input2; uoff_t abs_offset, size; const char *for_path; int fd; if (tstream->name[0] == '\0') for_path = ""; else for_path = t_strdup_printf(" for %s", tstream->name); if (tstream->dupstream != NULL && !tstream->dupstream->closed) { abs_offset = i_stream_get_absolute_offset(tstream->dupstream) - tstream->dupstream->v_offset + tstream->dupstream_start_offset; size = tstream->dupstream_offset - tstream->dupstream_start_offset; fd = dup(i_stream_get_fd(tstream->dupstream)); if (fd == -1) input = i_stream_create_error_str(errno, "dup() failed: %m"); else { input2 = i_stream_create_fd_autoclose(&fd, max_buffer_size); i_stream_seek(input2, abs_offset); input = i_stream_create_limit(input2, size); i_stream_unref(&input2); } i_stream_set_name(input, t_strdup_printf( "(Temp file in %s%s, from %s)", tstream->temp_path_prefix, for_path, i_stream_get_name(tstream->dupstream))); i_stream_unref(&tstream->dupstream); } else if (tstream->dupstream != NULL) { /* return the original failed stream. */ input = tstream->dupstream; } else if (tstream->fd != -1) { int fd = tstream->fd; input = i_stream_create_fd_autoclose(&tstream->fd, max_buffer_size); i_stream_set_name(input, t_strdup_printf( "(Temp file fd %d in %s%s, %"PRIuUOFF_T" bytes)", fd, tstream->temp_path_prefix, for_path, tstream->fd_size)); } else { input = i_stream_create_from_data(tstream->buf->data, tstream->buf->used); i_stream_set_name(input, t_strdup_printf( "(Temp buffer in %s%s, %zu bytes)", tstream->temp_path_prefix, for_path, tstream->buf->used)); i_stream_add_destroy_callback(input, iostream_temp_buf_destroyed, tstream->buf); tstream->buf = NULL; } o_stream_destroy(output); return input; } dovecot-2.3.21.1/src/lib/path-util.h0000644000000000000000000000604614656633576013740 00000000000000#ifndef PATH_UTIL_H #define PATH_UTIL_H /* Returns path as the normalized absolute path, which means that './' * and '../' components are resolved, and that duplicate and trailing * slashes are removed. If it's not already the absolute path, it's * assumed to be relative to the current working directory. * * NOTE: Be careful with this function. The resolution of '../' components * with the parent component as if it were a normal directory is not valid * if the path contains symbolic links. * * Returns 0 on success, and -1 on failure. errno and error_r are set on * failure, and error_r cannot be NULL. */ int t_normpath(const char *path, const char **npath_r, const char **error_r); /* Like t_normpath(), but path is relative to given root. */ int t_normpath_to(const char *path, const char *root, const char **npath_r, const char **error_r); /* Returns path as the real normalized absolute path, which means that all * symbolic links in the path are resolved, that './' and '../' components * are resolved, and that duplicate and trailing slashes are removed. If it's * not already the absolute path, it's assumed to be relative to the current * working directory. * * NOTE: This function calls stat() for each path component and more when * there are symbolic links (just like POSIX realpath()). * * Returns 0 on success, and -1 on failure. errno and error_r are set on * failure, and error_r cannot be NULL. */ int t_realpath(const char *path, const char **npath_r, const char **error_r); /* Like t_realpath(), but path is relative to given root. */ int t_realpath_to(const char *path, const char *root, const char **npath_r, const char **error_r); /* Returns path as absolute path. If it's not already absolute path, * it's assumed to be relative to current working directory. * * In the t_abspath functions, the returned paths are not normalized. This * means that './' and '../' are not resolved, but they left in the returned * path as given in the parameters. Symbolic links are not resolved either. * * Returns 0 on success, and -1 on failure. error_r is set on failure, and * cannot be NULL. */ int t_abspath(const char *path, const char **abspath_r, const char **error_r); /* Like t_abspath(), but path is relative to given root. */ const char *t_abspath_to(const char *path, const char *root); /* Get current working directory allocated from data stack. Returns 0 on * success and 1 on failure. error_r is set on failure and cannot be NULL. */ int t_get_working_dir(const char **dir_r, const char **error_r); /* Get symlink destination allocated from data stack. Returns 0 on success and * -1 on failure. error_r is set on failure and cannot be NULL. */ int t_readlink(const char *path, const char **dest_r, const char **error_r); /* Update binpath to be absolute: * a) begins with '/' -> no change * b) contains '/' -> assume relative to working directory * c) set to first executable that's found from $PATH * * error_r is set on failure, and cannot be NULL. */ bool t_binary_abspath(const char **binpath, const char **error_r); #endif dovecot-2.3.21.1/src/lib/iostream-rawlog-private.h0000644000000000000000000000115014656633576016604 00000000000000#ifndef IOSTREAM_RAWLOG_PRIVATE_H #define IOSTREAM_RAWLOG_PRIVATE_H #include "iostream-rawlog.h" #define IOSTREAM_RAWLOG_MAX_PREFIX_LEN 3 struct rawlog_iostream { struct iostream_private *iostream; enum iostream_rawlog_flags flags; struct ostream *rawlog_output; buffer_t *buffer; bool input; bool line_continued; }; void iostream_rawlog_init(struct rawlog_iostream *rstream, enum iostream_rawlog_flags flags, bool input); void iostream_rawlog_write(struct rawlog_iostream *rstream, const unsigned char *data, size_t size); void iostream_rawlog_close(struct rawlog_iostream *rstream); #endif dovecot-2.3.21.1/src/lib/iostream.h0000644000000000000000000000053714656633576013653 00000000000000#ifndef IOSTREAM_H #define IOSTREAM_H /* Returns human-readable reason for why iostream was disconnected. The output is either "Connection closed" for clean disconnections or "Connection closed: " for unclean disconnections. */ const char *io_stream_get_disconnect_reason(struct istream *input, struct ostream *output); #endif dovecot-2.3.21.1/src/lib/test-llist.c0000644000000000000000000001271214656633576014125 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "llist.h" struct dllist { struct dllist *prev, *next; }; static void test_dllist(void) { struct dllist *head = NULL, *l4, *l3, *l2, *l1; struct dllist empty = { NULL, NULL }; l4 = t_new(struct dllist, 1); l3 = t_new(struct dllist, 1); l2 = t_new(struct dllist, 1); l1 = t_new(struct dllist, 1); test_begin("dllist"); DLLIST_PREPEND(&head, l4); test_assert(head == l4); test_assert(l4->prev == NULL && l4->next == NULL); DLLIST_PREPEND(&head, l3); test_assert(head == l3); test_assert(l3->prev == NULL && l3->next == l4); test_assert(l4->prev == l3 && l4->next == NULL); DLLIST_PREPEND(&head, l2); DLLIST_PREPEND(&head, l1); /* remove from middle */ DLLIST_REMOVE(&head, l2); test_assert(l2->prev == NULL && l2->next == NULL); test_assert(head == l1); test_assert(l1->prev == NULL && l1->next == l3); test_assert(l3->prev == l1 && l3->next == l4); test_assert(l4->prev == l3 && l4->next == NULL); /* remove from head */ DLLIST_REMOVE(&head, l1); test_assert(l1->prev == NULL && l1->next == NULL); test_assert(head == l3); test_assert(l3->prev == NULL && l3->next == l4); test_assert(l4->prev == l3 && l4->next == NULL); /* remove from tail */ DLLIST_PREPEND(&head, l1); DLLIST_REMOVE(&head, l4); test_assert(l4->prev == NULL && l4->next == NULL); test_assert(head == l1); test_assert(l1->prev == NULL && l1->next == l3); test_assert(l3->prev == l1 && l3->next == NULL); /* removal of an entry not in the list shouldn't cause the list to break */ DLLIST_REMOVE(&head, &empty); test_assert(head == l1); test_assert(l1->prev == NULL && l1->next == l3); test_assert(l3->prev == l1 && l3->next == NULL); /* remove last two */ DLLIST_REMOVE(&head, l1); DLLIST_REMOVE(&head, l3); test_assert(l3->prev == NULL && l3->next == NULL); test_assert(head == NULL); test_end(); } static void test_dllist2(void) { struct dllist *head = NULL, *tail = NULL, *l4, *l3, *l2, *l1; struct dllist empty = { NULL, NULL }; l4 = t_new(struct dllist, 1); l3 = t_new(struct dllist, 1); l2 = t_new(struct dllist, 1); l1 = t_new(struct dllist, 1); test_begin("dllist2"); /* prepend to empty */ DLLIST2_PREPEND(&head, &tail, l3); test_assert(head == l3 && tail == l3); test_assert(l3->next == NULL && l3->prev == NULL); /* remove last */ DLLIST2_REMOVE(&head, &tail, l3); test_assert(head == NULL && tail == NULL); test_assert(l3->next == NULL && l3->prev == NULL); /* append to empty */ DLLIST2_APPEND(&head, &tail, l3); test_assert(head == l3 && tail == l3); test_assert(l3->next == NULL && l3->prev == NULL); /* prepend */ DLLIST2_PREPEND(&head, &tail, l2); test_assert(head == l2 && tail == l3); test_assert(l2->prev == NULL && l2->next == l3); test_assert(l3->prev == l2 && l3->next == NULL); /* append */ DLLIST2_APPEND(&head, &tail, l4); test_assert(head == l2 && tail == l4); test_assert(l2->prev == NULL && l2->next == l3); test_assert(l3->prev == l2 && l3->next == l4); test_assert(l4->prev == l3 && l4->next == NULL); DLLIST2_PREPEND(&head, &tail, l1); /* remove from middle */ DLLIST2_REMOVE(&head, &tail, l2); test_assert(l2->prev == NULL && l2->next == NULL); test_assert(head == l1 && tail == l4); test_assert(l1->prev == NULL && l1->next == l3); test_assert(l3->prev == l1 && l3->next == l4); test_assert(l4->prev == l3 && l4->next == NULL); /* remove from head */ DLLIST2_REMOVE(&head, &tail, l1); test_assert(l1->prev == NULL && l1->next == NULL); test_assert(head == l3 && tail == l4); test_assert(l3->prev == NULL && l3->next == l4); test_assert(l4->prev == l3 && l4->next == NULL); /* remove from tail */ DLLIST2_PREPEND(&head, &tail, l1); DLLIST2_REMOVE(&head, &tail, l4); test_assert(l4->prev == NULL && l4->next == NULL); test_assert(head == l1 && tail == l3); test_assert(l1->prev == NULL && l1->next == l3); test_assert(l3->prev == l1 && l3->next == NULL); /* removal of an entry not in the list shouldn't cause the list to break */ DLLIST2_REMOVE(&head, &tail, &empty); test_assert(head == l1); test_assert(head == l1 && tail == l3); test_assert(l1->prev == NULL && l1->next == l3); test_assert(l3->prev == l1 && l3->next == NULL); /* remove last two */ DLLIST2_REMOVE(&head, &tail, l1); DLLIST2_REMOVE(&head, &tail, l3); test_assert(l3->prev == NULL && l3->next == NULL); test_assert(head == NULL && tail == NULL); test_end(); } static void test_dllist2_join(void) { struct dllist *head, *tail, *elem[4]; struct dllist *head2, *tail2, *elem2[N_ELEMENTS(elem)]; test_begin("dllist2 join"); for (unsigned int i = 0; i < N_ELEMENTS(elem); i++) { elem[i] = t_new(struct dllist, 1); elem2[i] = t_new(struct dllist, 1); } for (unsigned int i = 0; i < N_ELEMENTS(elem); i++) { for (unsigned int j = 0; j < N_ELEMENTS(elem2); j++) { head = tail = head2 = tail2 = NULL; for (unsigned int n = 0; n < i; n++) DLLIST2_APPEND(&head, &tail, elem[n]); for (unsigned int n = 0; n < j; n++) DLLIST2_APPEND(&head2, &tail2, elem2[n]); DLLIST2_JOIN(&head, &tail, &head2, &tail2); /* verify */ struct dllist *tmp = head, *last = NULL; for (unsigned int n = 0; n < i; n++) { test_assert(tmp == elem[n]); last = tmp; tmp = tmp->next; } for (unsigned int n = 0; n < j; n++) { test_assert(tmp == elem2[n]); last = tmp; tmp = tmp->next; } test_assert(tmp == NULL); test_assert(tail == last); } } test_end(); } void test_llist(void) { test_dllist(); test_dllist2(); test_dllist2_join(); } dovecot-2.3.21.1/src/lib/restrict-process-size.c0000644000000000000000000000416014656633576016302 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "restrict-process-size.h" #include void restrict_process_size(rlim_t bytes) { struct rlimit rlim; rlim.rlim_max = rlim.rlim_cur = bytes; if (setrlimit(RLIMIT_DATA, &rlim) < 0) { i_fatal("setrlimit(RLIMIT_DATA, %llu): %m", (unsigned long long)bytes); } #ifdef HAVE_RLIMIT_AS if (setrlimit(RLIMIT_AS, &rlim) < 0) { i_fatal("setrlimit(RLIMIT_AS, %llu): %m", (unsigned long long)bytes); } #endif } void restrict_process_count(rlim_t count ATTR_UNUSED) { #ifdef HAVE_RLIMIT_NPROC struct rlimit rlim; rlim.rlim_max = rlim.rlim_cur = count; if (setrlimit(RLIMIT_NPROC, &rlim) < 0) { i_error("setrlimit(RLIMIT_NPROC, %llu): %m", (unsigned long long)count); } #endif } void restrict_fd_limit(rlim_t count) { #ifdef HAVE_SETRLIMIT struct rlimit rlim; rlim.rlim_cur = rlim.rlim_max = count; if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) { i_error("setrlimit(RLIMIT_NOFILE, %llu): %m", (unsigned long long)count); } #endif } int restrict_get_process_size(rlim_t *limit_r) { struct rlimit rlim; #ifdef HAVE_RLIMIT_AS if (getrlimit(RLIMIT_AS, &rlim) < 0) { i_error("getrlimit(RLIMIT_AS): %m"); return -1; } #else if (getrlimit(RLIMIT_DATA, &rlim) < 0) { i_error("getrlimit(RLIMIT_DATA): %m"); return -1; } #endif *limit_r = rlim.rlim_cur; return 0; } int restrict_get_core_limit(rlim_t *limit_r) { #ifdef HAVE_RLIMIT_CORE struct rlimit rlim; if (getrlimit(RLIMIT_CORE, &rlim) < 0) { i_error("getrlimit(RLIMIT_CORE) failed: %m"); return -1; } *limit_r = rlim.rlim_cur; return 0; #else return -1; #endif } int restrict_get_process_limit(rlim_t *limit_r) { #ifdef HAVE_RLIMIT_NPROC struct rlimit rlim; if (getrlimit(RLIMIT_NPROC, &rlim) < 0) { i_error("getrlimit(RLIMIT_NPROC) failed: %m"); return -1; } *limit_r = rlim.rlim_cur; return 0; #else return -1; #endif } int restrict_get_fd_limit(rlim_t *limit_r) { struct rlimit rlim; if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) { i_error("getrlimit(RLIMIT_NOFILE) failed: %m"); return -1; } *limit_r = rlim.rlim_cur; return 0; } dovecot-2.3.21.1/src/lib/env-util.h0000644000000000000000000000213314656633576013565 00000000000000#ifndef ENV_UTIL_H #define ENV_UTIL_H /* Add a new environment variable or replace an existing one. Wrapper to setenv(). Note that setenv() often doesn't free memory used by replaced environment, so don't keep repeatedly changing values in environment. */ void env_put(const char *name, const char *value); /* env_put() NULL-terminated array of name=value strings */ void env_put_array(const char *const *envs); /* Remove a single environment. */ void env_remove(const char *name); /* Clear all environment variables. */ void env_clean(void); /* Clear all environment variables except what's listed in preserve_envs[] */ void env_clean_except(const char *const preserve_envs[]); /* Save a copy of the current environment. */ struct env_backup *env_backup_save(void); /* Clear the current environment and restore the backup. */ void env_backup_restore(struct env_backup *env); /* Free the memory used by environment backup. */ void env_backup_free(struct env_backup **env); /* Returns the value of "&environ". This is more portable than using it directly. */ char ***env_get_environ_p(void); #endif dovecot-2.3.21.1/src/lib/test-file-create-locked.c0000644000000000000000000000641514656633576016420 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "unlink-directory.h" #include "file-create-locked.h" #include "sleep.h" #include #include #include static void create_file(const char *path) { int fd; fd = creat(path, 0600); if (fd == -1) i_fatal("creat(%s) failed: %m", path); i_close_fd(&fd); } static bool wait_for_file(pid_t pid, const char *path) { struct stat st; for (unsigned int i = 0; i < 1000; i++) { if (stat(path, &st) == 0) return TRUE; if (errno != ENOENT) i_fatal("stat(%s) failed: %m", path); if (kill(pid, 0) < 0) { if (errno == ESRCH) return FALSE; i_fatal("kill(SIGSRCH) failed: %m"); } i_sleep_msecs(10); } i_error("%s isn't being created", path); return FALSE; } static void test_file_create_locked_basic(void) { struct file_create_settings set = { .lock_timeout_secs = 0, .lock_settings = { .lock_method = FILE_LOCK_METHOD_FCNTL, }, }; const char *path = ".test-file-create-locked"; struct file_lock *lock; const char *error; bool created; pid_t pid; int fd; test_begin("file_create_locked()"); i_unlink_if_exists(path); i_unlink_if_exists(".test-temp-file-create-locked-child"); pid = fork(); switch (pid) { case (pid_t)-1: i_error("fork() failed: %m"); break; case 0: /* child */ fd = file_create_locked(path, &set, &lock, &created, &error); test_assert(fd > 0); test_assert(created); if (test_has_failed()) lib_exit(1); create_file(".test-temp-file-create-locked-child"); sleep(60); i_close_fd(&fd); lib_exit(0); default: /* parent */ test_assert(wait_for_file(pid, ".test-temp-file-create-locked-child")); if (test_has_failed()) break; test_assert(file_create_locked(path, &set, &lock, &created, &error) == -1); test_assert(errno == EAGAIN); if (kill(pid, SIGKILL) < 0) i_error("kill(SIGKILL) failed: %m"); break; } i_unlink_if_exists(".test-temp-file-create-locked-child"); i_unlink_if_exists(path); test_end(); } static void test_file_create_locked_mkdir(void) { struct file_create_settings set = { .lock_timeout_secs = 0, .lock_settings = { .lock_method = FILE_LOCK_METHOD_FCNTL, }, }; const char *path; struct file_lock *lock; const char *error, *dir; bool created; int fd; test_begin("file_create_locked() with mkdir"); dir = ".test-temp-file-create-locked-dir"; if (unlink_directory(dir, UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0) i_fatal("unlink_directory(%s) failed: %s", dir, error); path = t_strconcat(dir, "/lockfile", NULL); /* try without mkdir enabled */ test_assert(file_create_locked(path, &set, &lock, &created, &error) == -1); test_assert(errno == ENOENT); /* try with mkdir enabled */ set.mkdir_mode = 0700; fd = file_create_locked(path, &set, &lock, &created, &error); test_assert(fd > 0); test_assert(created); i_close_fd(&fd); struct stat st; if (stat(dir, &st) < 0) i_error("stat(%s) failed: %m", dir); test_assert((st.st_mode & 0777) == 0700); i_unlink(path); file_lock_free(&lock); if (unlink_directory(dir, UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0) i_fatal("unlink_directory(%s) failed: %s", dir, error); test_end(); } void test_file_create_locked(void) { test_file_create_locked_basic(); test_file_create_locked_mkdir(); } dovecot-2.3.21.1/src/lib/ostream-hash.h0000644000000000000000000000054414656633576014421 00000000000000#ifndef OSTREAM_HASH_H #define OSTREAM_HASH_H struct hash_method; /* hash_context must be allocated and initialized by caller. This ostream will simply call method->loop() for all the data going through the ostream. */ struct ostream * o_stream_create_hash(struct ostream *output, const struct hash_method *method, void *hash_context); #endif dovecot-2.3.21.1/src/lib/test-event-category-register.c0000644000000000000000000002352414656633576017557 00000000000000/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "ioloop.h" #include "time-util.h" #include "lib-event-private.h" #include "failures-private.h" /* we call a generic "unregister category function; to tell it what exact * behavior it should expect from lib-lib, we pass in one of the following * values */ enum unreg_expectation { UNREG_NOT_LAST, UNREG_LAST, UNREG_NOP, }; #define CAT_NAME_PREFIX "test-category" /* pointer to a category we expect to be registered/unregistered */ static struct event_category *expected_callback_cat; static bool callback_called; static struct event *dummy_event; static void check_category(struct event_category *cat) { callback_called = TRUE; /* lib-lib called a callback with a NULL? (useless and a bug) */ test_assert(cat != NULL); /* callback called, but didn't expect to be called? */ test_assert(expected_callback_cat != NULL); /* test_assert() doesn't terminate, so avoid NULL ptr derefs later on */ if ((cat == NULL) || (expected_callback_cat == NULL)) return; /* check that the categories have the same values */ test_assert(strcmp(cat->name, expected_callback_cat->name) == 0); test_assert(cat->internal == expected_callback_cat->internal); } static void check_cat_registered(const char *name, bool should_exist) { struct event_category *cat; callback_called = FALSE; cat = event_category_find_registered(name); test_assert(callback_called == FALSE); test_assert((cat != NULL) == should_exist); } static void register_cat(struct event_category *newcat, struct event_category *expcat) { /* start with a known state - no regs expected */ expected_callback_cat = NULL; callback_called = FALSE; dummy_event = event_create(NULL); test_assert(callback_called == FALSE); /* we expect a registration only when adding a cat */ expected_callback_cat = (expcat); event_add_category(dummy_event, (newcat)); expected_callback_cat = NULL; /* check that all went well */ test_assert(callback_called == (expcat != NULL)); test_assert((newcat)->internal != NULL); test_assert(event_category_find_registered((newcat)->name) != NULL); /* clean up */ event_unref(&dummy_event); } static void unregister_cat(struct event_category *cat, enum unreg_expectation expectation) { /* sanity check that cat is set up as expected */ switch (expectation) { case UNREG_NOT_LAST: /* must be registered to unregister */ test_assert(event_category_find_registered((cat)->name) != NULL); expected_callback_cat = NULL; break; case UNREG_LAST: /* must be registered to unregister */ test_assert(event_category_find_registered((cat)->name) != NULL); expected_callback_cat = cat; break; case UNREG_NOP: /* must not be registered for no-op */ /* event_category_find_registered(cat->name) should return NULL, but since we don't actually unregister this lookup would fail. Therefore, we skip it. */ expected_callback_cat = NULL; break; } /* Note: We don't actually have a way to unregister categories. We keep the above checks and the calls to this function as a form of documentation of how unregistering should work. */ } static void test_event_category_1ptr_null(void) { #define CAT_NAME_0 CAT_NAME_PREFIX "-1ptr-null" static struct event_category cat = { .name = CAT_NAME_0 }; test_begin("event category rereg: same ptr, NULL parent"); check_cat_registered(CAT_NAME_0, FALSE); register_cat(&cat, &cat); register_cat(&cat, NULL); check_cat_registered(CAT_NAME_0, TRUE); unregister_cat(&cat, UNREG_LAST); unregister_cat(&cat, UNREG_NOP); test_end(); #undef CAT_NAME_0 } static void test_event_category_1ptr_nonnull(void) { #define CAT_NAME_0 CAT_NAME_PREFIX "-1ptr-nonnull-0" #define CAT_NAME_1 CAT_NAME_PREFIX "-1ptr-nonnull-1" static struct event_category cat = { .name = CAT_NAME_0 }; static struct event_category cat_with_parent = { .name = CAT_NAME_1, .parent = &cat }; test_begin("event category rereg: same ptr, non-NULL parent"); check_cat_registered(CAT_NAME_0, FALSE); check_cat_registered(CAT_NAME_1, FALSE); register_cat(&cat, &cat); register_cat(&cat_with_parent, &cat_with_parent); register_cat(&cat_with_parent, NULL); check_cat_registered(CAT_NAME_0, TRUE); check_cat_registered(CAT_NAME_1, TRUE); unregister_cat(&cat_with_parent, UNREG_LAST); unregister_cat(&cat_with_parent, UNREG_NOP); /* NOTE: we must unreg children before parent cats */ unregister_cat(&cat, UNREG_LAST); unregister_cat(&cat, UNREG_NOP); test_end(); #undef CAT_NAME_0 #undef CAT_NAME_1 } static void test_event_category_2ptr_null(void) { #define CAT_NAME_0 CAT_NAME_PREFIX "-2ptr-null" static struct event_category cat0 = { .name = CAT_NAME_0 }; static struct event_category cat1 = { .name = CAT_NAME_0 }; test_begin("event category rereg: different ptr, NULL parent"); check_cat_registered(CAT_NAME_0, FALSE); register_cat(&cat0, &cat0); register_cat(&cat1, NULL); check_cat_registered(CAT_NAME_0, TRUE); unregister_cat(&cat0, UNREG_NOT_LAST); unregister_cat(&cat1, UNREG_LAST); unregister_cat(&cat0, UNREG_NOP); unregister_cat(&cat1, UNREG_NOP); test_end(); #undef CAT_NAME_0 } static void test_event_category_2ptr_nonnull_same(void) { #define CAT_NAME_0 CAT_NAME_PREFIX "-2ptr-nonnull-same-0" #define CAT_NAME_1 CAT_NAME_PREFIX "-2ptr-nonnull-same-1" static struct event_category cat = { .name = CAT_NAME_0 }; static struct event_category cat_with_parent0 = { .name = CAT_NAME_1, .parent = &cat }; static struct event_category cat_with_parent1 = { .name = CAT_NAME_1, .parent = &cat }; test_begin("event category rereg: different ptr, same non-NULL parent"); check_cat_registered(CAT_NAME_0, FALSE); check_cat_registered(CAT_NAME_1, FALSE); register_cat(&cat, &cat); register_cat(&cat_with_parent0, &cat_with_parent0); register_cat(&cat_with_parent1, NULL); check_cat_registered(CAT_NAME_0, TRUE); check_cat_registered(CAT_NAME_1, TRUE); unregister_cat(&cat_with_parent0, UNREG_NOT_LAST); unregister_cat(&cat_with_parent1, UNREG_LAST); unregister_cat(&cat_with_parent0, UNREG_NOP); unregister_cat(&cat_with_parent1, UNREG_NOP); /* NOTE: we must unreg children before parent cats */ unregister_cat(&cat, UNREG_LAST); unregister_cat(&cat, UNREG_NOP); test_end(); #undef CAT_NAME_0 #undef CAT_NAME_1 } static void test_event_category_2ptr_nonnull_similar(void) { #define CAT_NAME_0 CAT_NAME_PREFIX "-2ptr-nonnull-similar-0" #define CAT_NAME_1 CAT_NAME_PREFIX "-2ptr-nonnull-similar-1" static struct event_category cat0 = { .name = CAT_NAME_0 }; static struct event_category cat1 = { .name = CAT_NAME_0 }; static struct event_category cat_with_parent0 = { .name = CAT_NAME_1, .parent = &cat0 }; static struct event_category cat_with_parent1 = { .name = CAT_NAME_1, .parent = &cat1 }; test_begin("event category rereg: different ptr, similar non-NULL parent"); check_cat_registered(CAT_NAME_0, FALSE); check_cat_registered(CAT_NAME_1, FALSE); register_cat(&cat0, &cat0); register_cat(&cat1, NULL); register_cat(&cat_with_parent0, &cat_with_parent0); register_cat(&cat_with_parent1, NULL); check_cat_registered(CAT_NAME_0, TRUE); check_cat_registered(CAT_NAME_1, TRUE); unregister_cat(&cat_with_parent0, UNREG_NOT_LAST); unregister_cat(&cat_with_parent1, UNREG_LAST); unregister_cat(&cat_with_parent0, UNREG_NOP); unregister_cat(&cat_with_parent1, UNREG_NOP); /* NOTE: we must unreg children before parent cats */ unregister_cat(&cat0, UNREG_NOT_LAST); unregister_cat(&cat1, UNREG_LAST); unregister_cat(&cat0, UNREG_NOP); unregister_cat(&cat1, UNREG_NOP); test_end(); #undef CAT_NAME_0 #undef CAT_NAME_1 } void test_event_category_register(void) { event_category_register_callback(check_category); /* * registering/unregistering the same exact category struct (i.e., * the pointer is the same) is a no-op after the first call */ test_event_category_1ptr_null(); test_event_category_1ptr_nonnull(); /* * registering/unregistering two different category structs (i.e., * the pointers are different) is a almost a no-op */ test_event_category_2ptr_null(); test_event_category_2ptr_nonnull_same(); test_event_category_2ptr_nonnull_similar(); event_category_unregister_callback(check_category); } enum fatal_test_state fatal_event_category_register(unsigned int stage) { #define CAT_NAME_0 CAT_NAME_PREFIX "-2ptr-nonnull-different-0" #define CAT_NAME_1 CAT_NAME_PREFIX "-2ptr-nonnull-different-1" #define CAT_NAME_2 CAT_NAME_PREFIX "-2ptr-nonnull-different-2" static struct event_category cat_no_parent0 = { .name = CAT_NAME_0 }; static struct event_category cat_parent0 = { .name = CAT_NAME_1, .parent = &cat_no_parent0 }; static struct event_category cat_other = { .name = CAT_NAME_2 }; static struct event_category cat_other_parent = { .name = CAT_NAME_1, .parent = &cat_other }; /* we have only one fatal stage at this point */ switch (stage) { case 0: event_category_register_callback(check_category); test_begin("event category rereg: different ptr, different non-NULL parent"); check_cat_registered(CAT_NAME_0, FALSE); check_cat_registered(CAT_NAME_1, FALSE); check_cat_registered(CAT_NAME_2, FALSE); register_cat(&cat_no_parent0, &cat_no_parent0); register_cat(&cat_other, &cat_other); register_cat(&cat_parent0, &cat_parent0); test_expect_fatal_string("event category parent mismatch detected"); register_cat(&cat_other_parent, NULL); /* expected panic */ return FATAL_TEST_FAILURE; case 1: event_unref(&dummy_event); unregister_cat(&cat_parent0, UNREG_LAST); unregister_cat(&cat_parent0, UNREG_NOP); unregister_cat(&cat_other, UNREG_LAST); unregister_cat(&cat_other, UNREG_NOP); /* NOTE: we must unreg children before parent cats */ unregister_cat(&cat_no_parent0, UNREG_LAST); unregister_cat(&cat_no_parent0, UNREG_NOP); test_end(); event_category_unregister_callback(check_category); return FATAL_TEST_FINISHED; default: return FATAL_TEST_ABORT; } #undef CAT_NAME_0 #undef CAT_NAME_1 #undef CAT_NAME_2 } dovecot-2.3.21.1/src/lib/test-istream-failure-at.c0000644000000000000000000000321714656633576016471 00000000000000/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "istream.h" #include "istream-failure-at.h" #define TEST_DATA_LENGTH 128 #define TEST_ERRMSG "test-istream-failure-at error triggered" void test_istream_failure_at(void) { struct istream *input, *data_input; unsigned char test_data[TEST_DATA_LENGTH]; unsigned int i; ssize_t ret; test_begin("istream failure at"); for (i = 0; i < sizeof(test_data); i++) test_data[i] = i; data_input = i_stream_create_from_data(test_data, sizeof(test_data)); for (i = 0; i < TEST_DATA_LENGTH; i++) { i_stream_seek(data_input, 0); input = i_stream_create_failure_at(data_input, i, EIO, TEST_ERRMSG); while ((ret = i_stream_read(input)) > 0) i_stream_skip(input, ret); test_assert_idx(ret == -1 && input->v_offset == i && input->stream_errno == EIO && strcmp(i_stream_get_error(input), TEST_ERRMSG) == 0, i); i_stream_destroy(&input); } /* shouldn't fail */ i_stream_seek(data_input, 0); input = i_stream_create_failure_at(data_input, TEST_DATA_LENGTH, EIO, TEST_ERRMSG); while ((ret = i_stream_read(input)) > 0) i_stream_skip(input, ret); test_assert(ret == -1 && input->stream_errno == 0); i_stream_destroy(&input); /* fail at EOF */ i_stream_seek(data_input, 0); input = i_stream_create_failure_at_eof(data_input, EIO, TEST_ERRMSG); while ((ret = i_stream_read(input)) > 0) i_stream_skip(input, ret); test_assert_idx(ret == -1 && input->v_offset == TEST_DATA_LENGTH && input->stream_errno == EIO && strcmp(i_stream_get_error(input), TEST_ERRMSG) == 0, i); i_stream_destroy(&input); i_stream_destroy(&data_input); test_end(); } dovecot-2.3.21.1/src/lib/buffer.c0000644000000000000000000003136414656633576013276 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ /* @UNSAFE: whole file */ #include "lib.h" #include "buffer.h" struct real_buffer { union { struct buffer buf; struct { /* public: */ const void *r_buffer; size_t used; /* private: */ unsigned char *w_buffer; size_t dirty, alloc, writable_size, max_size; pool_t pool; bool alloced:1; bool dynamic:1; }; }; }; typedef int buffer_check_sizes[COMPILE_ERROR_IF_TRUE(sizeof(struct real_buffer) > sizeof(buffer_t)) ?1:1]; static void buffer_alloc(struct real_buffer *buf, size_t size) { i_assert(buf->w_buffer == NULL || buf->alloced); if (size == buf->alloc) return; i_assert(size > buf->alloc); if (buf->w_buffer == NULL) buf->w_buffer = p_malloc(buf->pool, size); else buf->w_buffer = p_realloc(buf->pool, buf->w_buffer, buf->alloc, size); buf->alloc = size; buf->writable_size = size-1; /* -1 for str_c() NUL */ buf->r_buffer = buf->w_buffer; buf->alloced = TRUE; } static inline void buffer_check_limits(struct real_buffer *buf, size_t pos, size_t data_size) { size_t new_size; if (unlikely(buf->max_size - pos < data_size)) i_panic("Buffer write out of range (%zu + %zu)", pos, data_size); new_size = pos + data_size; if (new_size > buf->used && buf->used < buf->dirty) { /* clear used..dirty area */ size_t max = I_MIN(I_MIN(buf->alloc, buf->dirty), new_size); memset(buf->w_buffer + buf->used, 0, max - buf->used); } /* Use buf->writable_size instead of buf->alloc to always keep +1 byte available in case str_c() is called for this buffer. This is mainly for cases where the buffer is allocated from data stack, and str_c() is called in a separate stack frame. */ if (new_size > buf->writable_size) { if (unlikely(!buf->dynamic)) { i_panic("Buffer full (%zu > %zu, pool %s)", pos + data_size, buf->alloc, buf->pool == NULL ? "" : pool_get_name(buf->pool)); } size_t new_alloc_size = pool_get_exp_grown_size(buf->pool, buf->alloc, new_size + 1); if (new_alloc_size > buf->max_size) { /* limit to max_size, but do include +1 for str_c() NUL */ new_alloc_size = buf->max_size + 1; } buffer_alloc(buf, new_alloc_size); } #if 0 else if (new_size > buf->used && buf->alloced && !buf->pool->alloconly_pool && !buf->pool->datastack_pool) { void *new_buf; /* buffer's size increased: move the buffer's memory elsewhere. this should help catch bugs where old pointers are tried to be used to access the buffer's memory */ new_buf = p_malloc(buf->pool, buf->alloc); memcpy(new_buf, buf->w_buffer, buf->alloc); p_free(buf->pool, buf->w_buffer); buf->w_buffer = new_buf; buf->r_buffer = new_buf; } #endif if (new_size > buf->used) buf->used = new_size; i_assert(buf->used <= buf->alloc); i_assert(buf->w_buffer != NULL); } static inline void buffer_check_append_limits(struct real_buffer *buf, size_t data_size) { /* Fast path: See if data to be appended fits into allocated buffer. If it does, we don't even need to memset() the dirty buffer since it's going to be filled with the newly appended data. */ if (buf->writable_size - buf->used < data_size) buffer_check_limits(buf, buf->used, data_size); else buf->used += data_size; } #undef buffer_create_from_data void buffer_create_from_data(buffer_t *buffer, void *data, size_t size) { struct real_buffer *buf; i_assert(sizeof(*buffer) >= sizeof(struct real_buffer)); buf = container_of(buffer, struct real_buffer, buf); i_zero(buf); buf->alloc = buf->writable_size = buf->max_size = size; buf->r_buffer = buf->w_buffer = data; /* clear the whole memory area. unnecessary usually, but if the buffer is used by e.g. str_c() it tries to access uninitialized memory */ memset(data, 0, size); } #undef buffer_create_from_const_data void buffer_create_from_const_data(buffer_t *buffer, const void *data, size_t size) { struct real_buffer *buf; i_assert(sizeof(*buffer) >= sizeof(struct real_buffer)); buf = container_of(buffer, struct real_buffer, buf); i_zero(buf); buf->used = buf->alloc = buf->writable_size = buf->max_size = size; buf->r_buffer = data; i_assert(buf->w_buffer == NULL); } buffer_t *buffer_create_dynamic(pool_t pool, size_t init_size) { return buffer_create_dynamic_max(pool, init_size, SIZE_MAX); } buffer_t *buffer_create_dynamic_max(pool_t pool, size_t init_size, size_t max_size) { struct real_buffer *buf; #ifdef DEBUG /* we increment this by 1 later on, so if it's SIZE_MAX it turns into 0 and hides a potential bug. Too scary to use in production for now, though. This can change in future. */ i_assert(init_size < SIZE_MAX); #endif buf = p_new(pool, struct real_buffer, 1); buf->pool = pool; buf->dynamic = TRUE; buf->max_size = max_size; /* buffer_alloc() reserves +1 for str_c() NIL, so add +1 here to init_size so we can actually write that much to the buffer without realloc */ buffer_alloc(buf, init_size+1); return &buf->buf; } void buffer_free(buffer_t **_buf) { if (*_buf == NULL) return; struct real_buffer *buf = container_of(*_buf, struct real_buffer, buf); *_buf = NULL; if (buf->alloced) p_free(buf->pool, buf->w_buffer); if (buf->pool != NULL) p_free(buf->pool, buf); } void *buffer_free_without_data(buffer_t **_buf) { struct real_buffer *buf = container_of(*_buf, struct real_buffer, buf); void *data; *_buf = NULL; data = buf->w_buffer; p_free(buf->pool, buf); return data; } pool_t buffer_get_pool(const buffer_t *_buf) { const struct real_buffer *buf = container_of(_buf, const struct real_buffer, buf); return buf->pool; } void buffer_write(buffer_t *_buf, size_t pos, const void *data, size_t data_size) { struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); buffer_check_limits(buf, pos, data_size); if (data_size > 0) memcpy(buf->w_buffer + pos, data, data_size); } void buffer_append(buffer_t *_buf, const void *data, size_t data_size) { struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); if (data_size > 0) { size_t pos = buf->used; buffer_check_append_limits(buf, data_size); memcpy(buf->w_buffer + pos, data, data_size); } } void buffer_append_c(buffer_t *_buf, unsigned char chr) { struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); size_t pos = buf->used; buffer_check_append_limits(buf, 1); buf->w_buffer[pos] = chr; } void buffer_insert(buffer_t *_buf, size_t pos, const void *data, size_t data_size) { struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); if (pos >= buf->used) buffer_write(_buf, pos, data, data_size); else { buffer_copy(_buf, pos + data_size, _buf, pos, SIZE_MAX); memcpy(buf->w_buffer + pos, data, data_size); } } void buffer_delete(buffer_t *_buf, size_t pos, size_t size) { struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); size_t end_size; if (pos >= buf->used) return; end_size = buf->used - pos; if (size < end_size) { /* delete from between */ end_size -= size; memmove(buf->w_buffer + pos, buf->w_buffer + pos + size, end_size); } else { /* delete the rest of the buffer */ end_size = 0; } buffer_set_used_size(_buf, pos + end_size); } void buffer_replace(buffer_t *_buf, size_t pos, size_t size, const void *data, size_t data_size) { struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); size_t end_size; if (pos >= buf->used) { buffer_write(_buf, pos, data, data_size); return; } end_size = buf->used - pos; if (size < end_size) { end_size -= size; if (data_size == 0) { /* delete from between */ memmove(buf->w_buffer + pos, buf->w_buffer + pos + size, end_size); } else { /* insert */ buffer_copy(_buf, pos + data_size, _buf, pos + size, SIZE_MAX); memcpy(buf->w_buffer + pos, data, data_size); } } else { /* overwrite the end */ end_size = 0; buffer_write(_buf, pos, data, data_size); } buffer_set_used_size(_buf, pos + data_size + end_size); } void buffer_write_zero(buffer_t *_buf, size_t pos, size_t data_size) { struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); buffer_check_limits(buf, pos, data_size); memset(buf->w_buffer + pos, 0, data_size); } void buffer_append_zero(buffer_t *_buf, size_t data_size) { struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); /* NOTE: When appending it's enough to check that the limits are valid, because the data is already guaranteed to be zero-filled. */ buffer_check_limits(buf, buf->used, data_size); } void buffer_insert_zero(buffer_t *_buf, size_t pos, size_t data_size) { struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); if (pos >= buf->used) buffer_write_zero(_buf, pos, data_size); else { buffer_copy(_buf, pos + data_size, _buf, pos, SIZE_MAX); memset(buf->w_buffer + pos, 0, data_size); } } void buffer_copy(buffer_t *_dest, size_t dest_pos, const buffer_t *_src, size_t src_pos, size_t copy_size) { struct real_buffer *dest = container_of(_dest, struct real_buffer, buf); const struct real_buffer *src = container_of(_src, const struct real_buffer, buf); size_t max_size; i_assert(src_pos <= src->used); max_size = src->used - src_pos; if (copy_size > max_size) copy_size = max_size; buffer_check_limits(dest, dest_pos, copy_size); i_assert(src->r_buffer != NULL); if (src == dest) { memmove(dest->w_buffer + dest_pos, CONST_PTR_OFFSET(src->r_buffer, src_pos), copy_size); } else { memcpy(dest->w_buffer + dest_pos, CONST_PTR_OFFSET(src->r_buffer, src_pos), copy_size); } } void buffer_append_buf(buffer_t *dest, const buffer_t *src, size_t src_pos, size_t copy_size) { buffer_copy(dest, dest->used, src, src_pos, copy_size); } void *buffer_get_space_unsafe(buffer_t *_buf, size_t pos, size_t size) { struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); buffer_check_limits(buf, pos, size); return buf->w_buffer + pos; } void *buffer_append_space_unsafe(buffer_t *buf, size_t size) { /* NOTE: can't use buffer_check_append_limits() here because it doesn't guarantee that the buffer is zero-filled. */ return buffer_get_space_unsafe(buf, buf->used, size); } void *buffer_get_modifiable_data(const buffer_t *_buf, size_t *used_size_r) { const struct real_buffer *buf = container_of(_buf, const struct real_buffer, buf); if (used_size_r != NULL) *used_size_r = buf->used; i_assert(buf->used == 0 || buf->w_buffer != NULL); return buf->w_buffer; } void buffer_set_used_size(buffer_t *_buf, size_t used_size) { struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); i_assert(used_size <= buf->alloc); if (buf->used > buf->dirty) buf->dirty = buf->used; buf->used = used_size; } size_t buffer_get_size(const buffer_t *_buf) { const struct real_buffer *buf = container_of(_buf, const struct real_buffer, buf); return buf->alloc; } size_t buffer_get_writable_size(const buffer_t *_buf) { const struct real_buffer *buf = container_of(_buf, const struct real_buffer, buf); /* Use buf->writable_size instead of buf->alloc to reserve +1 for str_c() NUL in buffer_check_limits(). Otherwise the caller might increase the buffer's alloc size unnecessarily when it just wants to access the entire buffer. */ return buf->writable_size; } size_t buffer_get_avail_size(const buffer_t *_buf) { const struct real_buffer *buf = container_of(_buf, const struct real_buffer, buf); i_assert(buf->alloc >= buf->used); return ((buf->dynamic ? SIZE_MAX : buf->alloc) - buf->used); } bool buffer_cmp(const buffer_t *buf1, const buffer_t *buf2) { if (buf1->used != buf2->used) return FALSE; if (buf1->used == 0) return TRUE; return memcmp(buf1->data, buf2->data, buf1->used) == 0; } void buffer_verify_pool(buffer_t *_buf) { const struct real_buffer *buf = container_of(_buf, struct real_buffer, buf); void *ret; if (buf->pool != NULL && buf->pool->datastack_pool && buf->alloc > 0) { /* this doesn't really do anything except verify the stack frame */ ret = p_realloc(buf->pool, buf->w_buffer, buf->alloc, buf->alloc); i_assert(ret == buf->w_buffer); } } void ATTR_NO_SANITIZE_IMPLICIT_CONVERSION ATTR_NO_SANITIZE_INTEGER buffer_truncate_rshift_bits(buffer_t *buf, size_t bits) { /* no-op if it's shorten than bits in any case.. */ if (buf->used * 8 < bits) return; if (bits > 0) { /* truncate it to closest byte boundary */ size_t bytes = ((bits + 7) & ~(size_t)7) / 8; /* remaining bits */ bits = bits % 8; buffer_set_used_size(buf, I_MIN(bytes, buf->used)); unsigned char *ptr = buffer_get_modifiable_data(buf, &bytes); /* right shift over byte array */ if (bits > 0) { for(size_t i=bytes-1;i>0;i--) ptr[i] = (ptr[i]>>(8-bits)) + ((ptr[i-1]&(0xff>>(bits)))<>(8-bits); } } else { buffer_set_used_size(buf, 0); } } dovecot-2.3.21.1/src/lib/ostream.c0000644000000000000000000005163614656633576013503 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "istream.h" #include "ostream-private.h" void o_stream_set_name(struct ostream *stream, const char *name) { i_free(stream->real_stream->iostream.name); stream->real_stream->iostream.name = i_strdup(name); } const char *o_stream_get_name(struct ostream *stream) { while (stream->real_stream->iostream.name == NULL) { stream = stream->real_stream->parent; if (stream == NULL) return ""; } return stream->real_stream->iostream.name; } int o_stream_get_fd(struct ostream *stream) { return stream->real_stream->fd; } const char *o_stream_get_error(struct ostream *stream) { struct ostream *s; /* we'll only return errors for streams that have stream_errno set. we might be returning unintended error otherwise. */ if (stream->stream_errno == 0) return ""; for (s = stream; s != NULL; s = s->real_stream->parent) { if (s->stream_errno == 0) break; if (s->real_stream->iostream.error != NULL) return s->real_stream->iostream.error; } return strerror(stream->stream_errno); } const char *o_stream_get_disconnect_reason(struct ostream *stream) { return io_stream_get_disconnect_reason(NULL, stream); } static void o_stream_close_full(struct ostream *stream, bool close_parents) { /* Ideally o_stream_finish() would be called for all non-failed ostreams, but strictly requiring it would cause unnecessary complexity for many callers. Just require that at this point after flushing there isn't anything in the output buffer or that we're ignoring all errors. */ if (o_stream_flush(stream) == 0) i_assert(stream->real_stream->error_handling_disabled); if (!stream->closed && !stream->real_stream->closing) { /* first mark the stream as being closed so the o_stream_copy_error_from_parent() won't recurse us back here. but don't immediately mark the stream closed, because we may still want to write something to it. */ stream->real_stream->closing = TRUE; io_stream_close(&stream->real_stream->iostream, close_parents); stream->closed = TRUE; } if (stream->stream_errno == 0) stream->stream_errno = EPIPE; } void o_stream_destroy(struct ostream **_stream) { struct ostream *stream = *_stream; if (stream == NULL) return; *_stream = NULL; o_stream_close_full(stream, FALSE); o_stream_unref(&stream); } void o_stream_ref(struct ostream *stream) { io_stream_ref(&stream->real_stream->iostream); } void o_stream_unref(struct ostream **_stream) { struct ostream *stream; if (*_stream == NULL) return; stream = *_stream; if (stream->real_stream->last_errors_not_checked && !stream->real_stream->error_handling_disabled && stream->real_stream->iostream.refcount == 1) { i_panic("output stream %s is missing error handling", o_stream_get_name(stream)); } if (!io_stream_unref(&stream->real_stream->iostream)) io_stream_free(&stream->real_stream->iostream); *_stream = NULL; } #undef o_stream_add_destroy_callback void o_stream_add_destroy_callback(struct ostream *stream, ostream_callback_t *callback, void *context) { io_stream_add_destroy_callback(&stream->real_stream->iostream, callback, context); } void o_stream_remove_destroy_callback(struct ostream *stream, void (*callback)()) { io_stream_remove_destroy_callback(&stream->real_stream->iostream, callback); } void o_stream_close(struct ostream *stream) { if (stream != NULL) o_stream_close_full(stream, TRUE); } #undef o_stream_set_flush_callback void o_stream_set_flush_callback(struct ostream *stream, stream_flush_callback_t *callback, void *context) { struct ostream_private *_stream = stream->real_stream; _stream->set_flush_callback(_stream, callback, context); } void o_stream_unset_flush_callback(struct ostream *stream) { struct ostream_private *_stream = stream->real_stream; _stream->set_flush_callback(_stream, NULL, NULL); } void o_stream_set_max_buffer_size(struct ostream *stream, size_t max_size) { io_stream_set_max_buffer_size(&stream->real_stream->iostream, max_size); } size_t o_stream_get_max_buffer_size(struct ostream *stream) { return stream->real_stream->max_buffer_size; } void o_stream_cork(struct ostream *stream) { struct ostream_private *_stream = stream->real_stream; if (unlikely(stream->closed || stream->stream_errno != 0)) return; _stream->cork(_stream, TRUE); } void o_stream_uncork(struct ostream *stream) { struct ostream_private *_stream = stream->real_stream; if (unlikely(stream->closed || stream->stream_errno != 0)) return; _stream->cork(_stream, FALSE); } bool o_stream_is_corked(struct ostream *stream) { struct ostream_private *_stream = stream->real_stream; return _stream->corked; } int o_stream_flush(struct ostream *stream) { struct ostream_private *_stream = stream->real_stream; int ret = 1; o_stream_ignore_last_errors(stream); if (unlikely(stream->closed || stream->stream_errno != 0)) { errno = stream->stream_errno; return -1; } if (unlikely(_stream->noverflow)) { io_stream_set_error(&_stream->iostream, "Output stream buffer was full (%zu bytes)", o_stream_get_max_buffer_size(stream)); errno = stream->stream_errno = ENOBUFS; return -1; } if (unlikely((ret = _stream->flush(_stream)) < 0)) { i_assert(stream->stream_errno != 0); errno = stream->stream_errno; } return ret; } void o_stream_set_flush_pending(struct ostream *stream, bool set) { struct ostream_private *_stream = stream->real_stream; if (unlikely(stream->closed || stream->stream_errno != 0)) return; _stream->flush_pending(_stream, set); } size_t o_stream_get_buffer_used_size(const struct ostream *stream) { const struct ostream_private *_stream = stream->real_stream; return _stream->get_buffer_used_size(_stream); } size_t o_stream_get_buffer_avail_size(const struct ostream *stream) { const struct ostream_private *_stream = stream->real_stream; return _stream->get_buffer_avail_size(_stream); } int o_stream_seek(struct ostream *stream, uoff_t offset) { struct ostream_private *_stream = stream->real_stream; if (unlikely(stream->closed || stream->stream_errno != 0)) { errno = stream->stream_errno; return -1; } if (unlikely(_stream->seek(_stream, offset) < 0)) { i_assert(stream->stream_errno != 0); errno = stream->stream_errno; return -1; } return 1; } ssize_t o_stream_send(struct ostream *stream, const void *data, size_t size) { struct const_iovec iov; i_zero(&iov); iov.iov_base = data; iov.iov_len = size; return o_stream_sendv(stream, &iov, 1); } static ssize_t o_stream_sendv_int(struct ostream *stream, const struct const_iovec *iov, unsigned int iov_count, bool *overflow_r) { struct ostream_private *_stream = stream->real_stream; unsigned int i; size_t total_size; ssize_t ret; *overflow_r = FALSE; for (i = 0, total_size = 0; i < iov_count; i++) total_size += iov[i].iov_len; if (total_size == 0) return 0; i_assert(!_stream->finished); ret = _stream->sendv(_stream, iov, iov_count); if (ret > 0) stream->real_stream->last_write_timeval = ioloop_timeval; if (unlikely(ret != (ssize_t)total_size)) { if (ret < 0) { i_assert(stream->stream_errno != 0); errno = stream->stream_errno; } else { i_assert(!stream->blocking); stream->overflow = TRUE; *overflow_r = TRUE; } } return ret; } ssize_t o_stream_sendv(struct ostream *stream, const struct const_iovec *iov, unsigned int iov_count) { bool overflow; if (unlikely(stream->closed || stream->stream_errno != 0)) { errno = stream->stream_errno; return -1; } return o_stream_sendv_int(stream, iov, iov_count, &overflow); } ssize_t o_stream_send_str(struct ostream *stream, const char *str) { return o_stream_send(stream, str, strlen(str)); } void o_stream_nsend(struct ostream *stream, const void *data, size_t size) { struct const_iovec iov; i_zero(&iov); iov.iov_base = data; iov.iov_len = size; o_stream_nsendv(stream, &iov, 1); } void o_stream_nsendv(struct ostream *stream, const struct const_iovec *iov, unsigned int iov_count) { bool overflow; if (unlikely(stream->closed || stream->stream_errno != 0 || stream->real_stream->noverflow)) return; (void)o_stream_sendv_int(stream, iov, iov_count, &overflow); if (overflow) stream->real_stream->noverflow = TRUE; stream->real_stream->last_errors_not_checked = TRUE; } void o_stream_nsend_str(struct ostream *stream, const char *str) { o_stream_nsend(stream, str, strlen(str)); } int o_stream_finish(struct ostream *stream) { stream->real_stream->finished = TRUE; return o_stream_flush(stream); } void o_stream_set_finish_also_parent(struct ostream *stream, bool set) { stream->real_stream->finish_also_parent = set; } void o_stream_set_finish_via_child(struct ostream *stream, bool set) { stream->real_stream->finish_via_child = set; } void o_stream_ignore_last_errors(struct ostream *stream) { while (stream != NULL) { stream->real_stream->last_errors_not_checked = FALSE; stream = stream->real_stream->parent; } } void o_stream_abort(struct ostream *stream) { o_stream_ignore_last_errors(stream); if (stream->stream_errno != 0) return; io_stream_set_error(&stream->real_stream->iostream, "aborted writing"); stream->stream_errno = EPIPE; } void o_stream_set_no_error_handling(struct ostream *stream, bool set) { stream->real_stream->error_handling_disabled = set; } enum ostream_send_istream_result o_stream_send_istream(struct ostream *outstream, struct istream *instream) { struct ostream_private *_outstream = outstream->real_stream; uoff_t old_outstream_offset = outstream->offset; uoff_t old_instream_offset = instream->v_offset; enum ostream_send_istream_result res; if (unlikely(instream->closed || instream->stream_errno != 0)) { errno = instream->stream_errno; return OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT; } if (unlikely(outstream->closed || outstream->stream_errno != 0)) { errno = outstream->stream_errno; return OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT; } i_assert(!_outstream->finished); res = _outstream->send_istream(_outstream, instream); switch (res) { case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: i_assert(instream->stream_errno == 0); i_assert(outstream->stream_errno == 0); i_assert(!i_stream_have_bytes_left(instream)); break; case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: i_assert(!instream->blocking); break; case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: i_assert(!outstream->blocking); o_stream_set_flush_pending(outstream, TRUE); break; case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: i_assert(instream->stream_errno != 0); return res; case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: i_assert(outstream->stream_errno != 0); return res; } /* non-failure - make sure stream offsets match */ i_assert((outstream->offset - old_outstream_offset) == (instream->v_offset - old_instream_offset)); if (outstream->offset != old_outstream_offset) outstream->real_stream->last_write_timeval = ioloop_timeval; return res; } void o_stream_nsend_istream(struct ostream *outstream, struct istream *instream) { i_assert(instream->blocking); switch (o_stream_send_istream(outstream, instream)) { case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: break; case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: i_unreached(); case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: outstream->real_stream->noverflow = TRUE; break; case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: outstream->stream_errno = instream->stream_errno; io_stream_set_error(&outstream->real_stream->iostream, "nsend-istream: read(%s) failed: %s", i_stream_get_name(instream), i_stream_get_error(instream)); break; case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: break; } outstream->real_stream->last_errors_not_checked = TRUE; } int o_stream_pwrite(struct ostream *stream, const void *data, size_t size, uoff_t offset) { int ret; if (unlikely(stream->closed || stream->stream_errno != 0)) { errno = stream->stream_errno; return -1; } i_assert(!stream->real_stream->finished); ret = stream->real_stream->write_at(stream->real_stream, data, size, offset); if (ret > 0) stream->real_stream->last_write_timeval = ioloop_timeval; else if (unlikely(ret < 0)) { i_assert(stream->stream_errno != 0); errno = stream->stream_errno; } return ret; } void o_stream_get_last_write_time(struct ostream *stream, struct timeval *tv_r) { *tv_r = stream->real_stream->last_write_timeval; } enum ostream_send_istream_result io_stream_copy(struct ostream *outstream, struct istream *instream) { struct const_iovec iov; const unsigned char *data; ssize_t ret; while (i_stream_read_more(instream, &data, &iov.iov_len) > 0) { iov.iov_base = data; if ((ret = o_stream_sendv(outstream, &iov, 1)) < 0) return OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT; else if (ret == 0) return OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT; i_stream_skip(instream, ret); } if (instream->stream_errno != 0) return OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT; if (i_stream_have_bytes_left(instream)) return OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT; return OSTREAM_SEND_ISTREAM_RESULT_FINISHED; } void o_stream_switch_ioloop_to(struct ostream *stream, struct ioloop *ioloop) { struct ostream_private *_stream = stream->real_stream; io_stream_switch_ioloop_to(&_stream->iostream, ioloop); _stream->switch_ioloop_to(_stream, ioloop); } void o_stream_switch_ioloop(struct ostream *stream) { o_stream_switch_ioloop_to(stream, current_ioloop); } static void o_stream_default_close(struct iostream_private *stream, bool close_parent) { struct ostream_private *_stream = container_of(stream, struct ostream_private, iostream); (void)o_stream_flush(&_stream->ostream); if (close_parent) o_stream_close(_stream->parent); } static void o_stream_default_destroy(struct iostream_private *stream) { struct ostream_private *_stream = container_of(stream, struct ostream_private, iostream); o_stream_unref(&_stream->parent); } static void o_stream_default_set_max_buffer_size(struct iostream_private *stream, size_t max_size) { struct ostream_private *_stream = container_of(stream, struct ostream_private, iostream); if (_stream->parent != NULL) o_stream_set_max_buffer_size(_stream->parent, max_size); _stream->max_buffer_size = max_size; } static void o_stream_default_cork(struct ostream_private *_stream, bool set) { _stream->corked = set; if (set) { if (_stream->parent != NULL) o_stream_cork(_stream->parent); } else { (void)o_stream_flush(&_stream->ostream); _stream->last_errors_not_checked = TRUE; if (_stream->parent != NULL) o_stream_uncork(_stream->parent); } } void o_stream_copy_error_from_parent(struct ostream_private *_stream) { struct ostream *src = _stream->parent; struct ostream *dest = &_stream->ostream; i_assert(src->stream_errno != 0); dest->stream_errno = src->stream_errno; dest->overflow = src->overflow; if (src->closed) o_stream_close(dest); } int o_stream_flush_parent_if_needed(struct ostream_private *_stream) { if (o_stream_get_buffer_used_size(_stream->parent) >= IO_BLOCK_SIZE) { /* we already have quite a lot of data in parent stream. unless we can flush it, don't add any more to it or we could keep wasting memory by just increasing the buffer size all the time. */ if (o_stream_flush(_stream->parent) < 0) { o_stream_copy_error_from_parent(_stream); return -1; } if (o_stream_get_buffer_used_size(_stream->parent) >= IO_BLOCK_SIZE) return 0; } return 1; } int o_stream_flush_parent(struct ostream_private *_stream) { int ret; i_assert(_stream->parent != NULL); if (!_stream->finished || !_stream->finish_also_parent || !_stream->parent->real_stream->finish_via_child) ret = o_stream_flush(_stream->parent); else ret = o_stream_finish(_stream->parent); if (ret < 0) o_stream_copy_error_from_parent(_stream); return ret; } static int o_stream_default_flush(struct ostream_private *_stream) { if (_stream->parent == NULL) return 1; return o_stream_flush_parent(_stream); } static void o_stream_default_set_flush_callback(struct ostream_private *_stream, stream_flush_callback_t *callback, void *context) { if (_stream->parent != NULL) o_stream_set_flush_callback(_stream->parent, callback, context); _stream->callback = callback; _stream->context = context; } static void o_stream_default_set_flush_pending(struct ostream_private *_stream, bool set) { if (_stream->parent != NULL) o_stream_set_flush_pending(_stream->parent, set); } static size_t o_stream_default_get_buffer_used_size(const struct ostream_private *_stream) { if (_stream->parent == NULL) return 0; else return o_stream_get_buffer_used_size(_stream->parent); } static size_t o_stream_default_get_buffer_avail_size(const struct ostream_private *_stream) { /* This default implementation assumes that the returned buffer size is between 0..max_buffer_size. There's no assert though, in case the max_buffer_size changes. */ size_t used = o_stream_get_buffer_used_size(&_stream->ostream); return _stream->max_buffer_size <= used ? 0 : _stream->max_buffer_size - used; } static int o_stream_default_seek(struct ostream_private *_stream, uoff_t offset ATTR_UNUSED) { _stream->ostream.stream_errno = ESPIPE; return -1; } static ssize_t o_stream_default_sendv(struct ostream_private *stream, const struct const_iovec *iov, unsigned int iov_count) { ssize_t ret; if ((ret = o_stream_sendv(stream->parent, iov, iov_count)) < 0) { o_stream_copy_error_from_parent(stream); return -1; } stream->ostream.offset += ret; return ret; } static int o_stream_default_write_at(struct ostream_private *_stream, const void *data ATTR_UNUSED, size_t size ATTR_UNUSED, uoff_t offset ATTR_UNUSED) { _stream->ostream.stream_errno = ESPIPE; return -1; } static enum ostream_send_istream_result o_stream_default_send_istream(struct ostream_private *outstream, struct istream *instream) { return io_stream_copy(&outstream->ostream, instream); } static void o_stream_default_switch_ioloop_to(struct ostream_private *_stream, struct ioloop *ioloop) { if (_stream->parent != NULL) o_stream_switch_ioloop_to(_stream->parent, ioloop); } struct ostream * o_stream_create(struct ostream_private *_stream, struct ostream *parent, int fd) { _stream->finish_also_parent = TRUE; _stream->finish_via_child = TRUE; _stream->fd = fd; _stream->ostream.real_stream = _stream; if (parent != NULL) { _stream->ostream.blocking = parent->blocking; _stream->parent = parent; o_stream_ref(parent); _stream->callback = parent->real_stream->callback; _stream->context = parent->real_stream->context; _stream->max_buffer_size = parent->real_stream->max_buffer_size; _stream->error_handling_disabled = parent->real_stream->error_handling_disabled; } if (_stream->iostream.close == NULL) _stream->iostream.close = o_stream_default_close; if (_stream->iostream.destroy == NULL) _stream->iostream.destroy = o_stream_default_destroy; if (_stream->iostream.set_max_buffer_size == NULL) { _stream->iostream.set_max_buffer_size = o_stream_default_set_max_buffer_size; } if (_stream->cork == NULL) _stream->cork = o_stream_default_cork; if (_stream->flush == NULL) _stream->flush = o_stream_default_flush; if (_stream->set_flush_callback == NULL) { _stream->set_flush_callback = o_stream_default_set_flush_callback; } if (_stream->flush_pending == NULL) _stream->flush_pending = o_stream_default_set_flush_pending; if (_stream->get_buffer_used_size == NULL) _stream->get_buffer_used_size = o_stream_default_get_buffer_used_size; if (_stream->get_buffer_avail_size == NULL) { _stream->get_buffer_avail_size = o_stream_default_get_buffer_avail_size; } if (_stream->seek == NULL) _stream->seek = o_stream_default_seek; if (_stream->sendv == NULL) _stream->sendv = o_stream_default_sendv; if (_stream->write_at == NULL) _stream->write_at = o_stream_default_write_at; if (_stream->send_istream == NULL) _stream->send_istream = o_stream_default_send_istream; if (_stream->switch_ioloop_to == NULL) _stream->switch_ioloop_to = o_stream_default_switch_ioloop_to; io_stream_init(&_stream->iostream); return &_stream->ostream; } struct ostream *o_stream_create_error(int stream_errno) { struct ostream_private *stream; struct ostream *output; stream = i_new(struct ostream_private, 1); stream->ostream.blocking = TRUE; stream->ostream.closed = TRUE; stream->ostream.stream_errno = stream_errno; output = o_stream_create(stream, NULL, -1); o_stream_set_no_error_handling(output, TRUE); o_stream_set_name(output, "(error)"); return output; } struct ostream * o_stream_create_error_str(int stream_errno, const char *fmt, ...) { struct ostream *output; va_list args; va_start(args, fmt); output = o_stream_create_error(stream_errno); io_stream_set_verror(&output->real_stream->iostream, fmt, args); va_end(args); return output; } struct ostream *o_stream_create_passthrough(struct ostream *output) { struct ostream_private *stream; stream = i_new(struct ostream_private, 1); return o_stream_create(stream, output, o_stream_get_fd(output)); } dovecot-2.3.21.1/src/lib/test-hex-binary.c0000644000000000000000000000310514656633576015040 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "buffer.h" #include "str.h" #include "hex-binary.h" static void test_binary_to_hex(void) { static unsigned char input[] = { 0xff, 0x00, 0x01, 0xb3 }; static char *output_lcase = "ff0001b3"; static char *output_ucase = "FF0001B3"; string_t *str; test_begin("binary to hex"); test_assert(strcmp(binary_to_hex(input, sizeof(input)), output_lcase) == 0); test_end(); test_begin("binary to hex ucase"); test_assert(strcmp(binary_to_hex_ucase(input, sizeof(input)), output_ucase) == 0); test_end(); test_begin("binary to hex ucase"); str = t_str_new(32); str_append_c(str, '<'); binary_to_hex_append(str, input, sizeof(input)); str_append_c(str, '>'); test_assert(strcmp(str_c(str), t_strconcat("<", output_lcase, ">", NULL)) == 0); test_end(); } static void test_hex_to_binary(void) { static const char *ok_input = "0001fEFf"; static unsigned char ok_output[] = { 0x00, 0x01, 0xfe, 0xff }; static const char *error_input[] = { "00 01", "0x01", "0g" }; buffer_t *buf = t_buffer_create(10); unsigned int i; test_begin("hex to binary"); test_assert(hex_to_binary("", buf) == 0); test_assert(buf->used == 0); test_assert(hex_to_binary(ok_input, buf) == 0); test_assert(buf->used == N_ELEMENTS(ok_output)); test_assert(memcmp(buf->data, ok_output, buf->used) == 0); for (i = 0; i < N_ELEMENTS(error_input); i++) test_assert(hex_to_binary(error_input[i], buf) == -1); test_end(); } void test_hex_binary(void) { test_binary_to_hex(); test_hex_to_binary(); } dovecot-2.3.21.1/src/lib/execv-const.c0000644000000000000000000000147614656633576014264 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "execv-const.h" #include static char **argv_drop_const(const char *const argv[]) { char **ret; unsigned int i, count; for (count = 0; argv[count] != NULL; count++) ; ret = t_new(char *, count + 1); for (i = 0; i < count; i++) ret[i] = t_strdup_noconst(argv[i]); return ret; } void execv_const(const char *path, const char *const argv[]) { (void)execv(path, argv_drop_const(argv)); i_fatal_status(errno == ENOMEM ? FATAL_OUTOFMEM : FATAL_EXEC, "execv(%s) failed: %m", path); } void execvp_const(const char *file, const char *const argv[]) { (void)execvp(file, argv_drop_const(argv)); i_fatal_status(errno == ENOMEM ? FATAL_OUTOFMEM : FATAL_EXEC, "execvp(%s) failed: %m", file); } dovecot-2.3.21.1/src/lib/lib-signals.h0000644000000000000000000000536114656633576014234 00000000000000#ifndef LIB_SIGNALS_H #define LIB_SIGNALS_H #include enum libsig_flags { /* Signal handler will be called later from IO loop when it's safe to do any kind of work */ LIBSIG_FLAG_DELAYED = 0x01, /* Restart syscalls instead of having them fail with EINTR */ LIBSIG_FLAG_RESTART = 0x02, /* Automatically shift delayed signal handling for this signal to a newly started ioloop. */ LIBSIG_FLAG_IOLOOP_AUTOMOVE = 0x04, }; #define LIBSIG_FLAGS_SAFE (LIBSIG_FLAG_DELAYED | LIBSIG_FLAG_RESTART) typedef void signal_handler_t(const siginfo_t *si, void *context); /* Number of times a "termination signal" has been received. These signals are SIGINT, SIGQUIT and SIGTERM. Callers can compare this to their saved previous value to see if a syscall returning EINTR should be treated as someone wanting to end the process or just some internal signal that should be ignored, such as SIGCHLD. This is marked as volatile so that compiler won't optimize away its comparisons. It may not work perfectly everywhere, such as when accessing it isn't atomic, so you shouldn't heavily rely on its actual value. */ extern volatile unsigned int signal_term_counter; /* Convert si_code to string */ const char *lib_signal_code_to_str(int signo, int sicode); /* Detach IOs from all ioloops. This isn't normally necessary, except when forking a process. */ void lib_signals_ioloop_detach(void); void lib_signals_ioloop_attach(void); /* Set signal handler for specific signal. */ void lib_signals_set_handler(int signo, enum libsig_flags flags, signal_handler_t *handler, void *context) ATTR_NULL(4); /* Ignore given signal. */ void lib_signals_ignore(int signo, bool restart_syscalls); /* Clear all signal handlers for a specific signal and set the signal to be ignored. */ void lib_signals_clear_handlers_and_ignore(int signo); /* Unset specific signal handler for specific signal. */ void lib_signals_unset_handler(int signo, signal_handler_t *handler, void *context) ATTR_NULL(3); /* Indicate whether signals are expected for the indicated delayed handler. When signals are expected, the io for delayed handlers will be allowed to wait alone on the ioloop. */ void lib_signals_set_expected(int signo, bool expected, signal_handler_t *handler, void *context); ATTR_NULL(4); /* Switch ioloop for a specific signal handler created with LIBSIG_FLAG_NO_IOLOOP_AUTOMOVE. */ void lib_signals_switch_ioloop(int signo, signal_handler_t *handler, void *context); /* Log a syscall error inside a (non-delayed) signal handler where i_error() is unsafe. errno number will be appended to the prefix. */ void lib_signals_syscall_error(const char *prefix); void lib_signals_init(void); void lib_signals_deinit(void); #endif dovecot-2.3.21.1/src/lib/test-istream-sized.c0000644000000000000000000000575414656633576015566 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "istream.h" #include "istream-sized.h" static const struct { const char *input; uoff_t size; int stream_errno; } tests[] = { { "", 0, 0 }, { "", 1, EPIPE }, { "a", 1, 0 }, { "ab", 1, EINVAL }, { "ab", 0, EINVAL }, { "ab", UOFF_T_MAX, EPIPE }, }; static void run_test(const char *sized_input, uoff_t sized_size, int stream_errno) { unsigned int sized_input_len = strlen(sized_input); struct istream *input_data, *input; const unsigned char *data; size_t i, size; int ret = 0; input_data = test_istream_create_data(sized_input, sized_input_len); test_istream_set_allow_eof(input_data, FALSE); input = i_stream_create_sized(input_data, sized_size); for (i = 1; i < sized_input_len; i++) { test_istream_set_size(input_data, i); while ((ret = i_stream_read(input)) > 0) ; if (ret == -1 && stream_errno != 0) break; test_assert(ret == 0); } if (ret == 0) { test_istream_set_allow_eof(input_data, TRUE); test_istream_set_size(input_data, i); while ((ret = i_stream_read(input)) > 0) ; } test_assert(ret == -1); test_assert(input->stream_errno == stream_errno); data = i_stream_get_data(input, &size); test_assert(size == I_MIN(sized_input_len, sized_size)); if (size > 0) test_assert(memcmp(data, sized_input, size) == 0); i_stream_unref(&input); i_stream_unref(&input_data); } static void test_istream_sized_full(bool exact) { const unsigned char test_data[10] = "1234567890"; struct istream *test_input, *input; unsigned int i, j; int expected_errno; for (i = 1; i < sizeof(test_data)*2; i++) { test_input = test_istream_create_data(test_data, sizeof(test_data)); test_istream_set_allow_eof(test_input, FALSE); test_istream_set_size(test_input, 0); if (exact) input = i_stream_create_sized(test_input, i); else input = i_stream_create_min_sized(test_input, i); for (j = 1; j <= I_MIN(i, sizeof(test_data)); j++) { test_assert_idx(i_stream_read(input) == 0, j); test_istream_set_size(test_input, j); test_assert_idx(i_stream_read(input) == 1, j); } test_assert_idx(i_stream_read(input) == 0, i); if (j <= sizeof(test_data)) test_istream_set_size(test_input, j); else test_istream_set_allow_eof(test_input, TRUE); test_assert_idx(i_stream_read(input) == -1 && input->eof, i); if (i > sizeof(test_data)) expected_errno = EPIPE; else if (i < sizeof(test_data) && exact) expected_errno = EINVAL; else expected_errno = 0; test_assert_idx(input->stream_errno == expected_errno, i); i_stream_unref(&input); i_stream_unref(&test_input); } } void test_istream_sized(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(tests); i++) { test_begin(t_strdup_printf("istream sized %u", i+1)); run_test(tests[i].input, tests[i].size, tests[i].stream_errno); test_end(); } test_begin("istream sized"); test_istream_sized_full(TRUE); test_end(); test_begin("istream sized min"); test_istream_sized_full(FALSE); test_end(); } dovecot-2.3.21.1/src/lib/ioloop-select.c0000644000000000000000000000736614656633576014610 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop-private.h" #ifdef IOLOOP_SELECT #ifdef HAVE_SYS_SELECT_H # include /* According to POSIX 1003.1-2001 */ #endif #include #include struct ioloop_handler_context { int highest_fd; fd_set read_fds, write_fds, except_fds; fd_set tmp_read_fds, tmp_write_fds, tmp_except_fds; }; static void update_highest_fd(struct ioloop *ioloop) { struct ioloop_handler_context *ctx = ioloop->handler_context; struct io_file *io; int max_highest_fd; max_highest_fd = ctx->highest_fd-1; ctx->highest_fd = -1; for (io = ioloop->io_files; io != NULL; io = io->next) { if (io->fd <= ctx->highest_fd) continue; ctx->highest_fd = io->fd; if (ctx->highest_fd == max_highest_fd) break; } } void io_loop_handler_init(struct ioloop *ioloop, unsigned int initial_fd_count ATTR_UNUSED) { struct ioloop_handler_context *ctx; ioloop->handler_context = ctx = i_new(struct ioloop_handler_context, 1); ctx->highest_fd = -1; FD_ZERO(&ctx->read_fds); FD_ZERO(&ctx->write_fds); FD_ZERO(&ctx->except_fds); } void io_loop_handler_deinit(struct ioloop *ioloop) { i_free(ioloop->handler_context); } void io_loop_handle_add(struct io_file *io) { struct ioloop_handler_context *ctx = io->io.ioloop->handler_context; enum io_condition condition = io->io.condition; int fd = io->fd; i_assert(fd >= 0); if (fd >= FD_SETSIZE) i_fatal("fd %d too large for select()", fd); if ((condition & (IO_READ | IO_ERROR)) != 0) FD_SET(fd, &ctx->read_fds); if ((condition & IO_WRITE) != 0) FD_SET(fd, &ctx->write_fds); FD_SET(fd, &ctx->except_fds); if (io->fd > ctx->highest_fd) ctx->highest_fd = io->fd; } void io_loop_handle_remove(struct io_file *io, bool closed ATTR_UNUSED) { struct ioloop_handler_context *ctx = io->io.ioloop->handler_context; enum io_condition condition = io->io.condition; int fd = io->fd; i_assert(fd >= 0 && fd < FD_SETSIZE); if ((condition & (IO_READ | IO_ERROR)) != 0) FD_CLR(fd, &ctx->read_fds); if ((condition & IO_WRITE) != 0) FD_CLR(fd, &ctx->write_fds); if (!FD_ISSET(fd, &ctx->read_fds) && !FD_ISSET(fd, &ctx->write_fds)) { FD_CLR(fd, &ctx->except_fds); /* check if we removed the highest fd */ if (io->fd == ctx->highest_fd) update_highest_fd(io->io.ioloop); } i_free(io); } #define io_check_condition(ctx, fd, cond) \ ((FD_ISSET((fd), &(ctx)->tmp_read_fds) && ((cond) & (IO_READ|IO_ERROR)) != 0) || \ (FD_ISSET((fd), &(ctx)->tmp_write_fds) && ((cond) & IO_WRITE) != 0) || \ (FD_ISSET((fd), &(ctx)->tmp_except_fds))) void io_loop_handler_run_internal(struct ioloop *ioloop) { struct ioloop_handler_context *ctx = ioloop->handler_context; struct timeval tv; struct io_file *io; int ret; /* get the time left for next timeout task */ io_loop_run_get_wait_time(ioloop, &tv); memcpy(&ctx->tmp_read_fds, &ctx->read_fds, sizeof(fd_set)); memcpy(&ctx->tmp_write_fds, &ctx->write_fds, sizeof(fd_set)); memcpy(&ctx->tmp_except_fds, &ctx->except_fds, sizeof(fd_set)); ret = select(ctx->highest_fd + 1, &ctx->tmp_read_fds, &ctx->tmp_write_fds, &ctx->tmp_except_fds, &tv); if (ret < 0 && errno != EINTR) i_warning("select() : %m"); /* execute timeout handlers */ io_loop_handle_timeouts(ioloop); if (ret <= 0 || !ioloop->running) { /* no I/O events */ return; } io = ioloop->io_files; for (; io != NULL && ret > 0; io = ioloop->next_io_file) { ioloop->next_io_file = io->next; if (io->fd == -1) { /* io_add_istream() without fd */ } else if (io_check_condition(ctx, io->fd, io->io.condition)) { ret--; io_loop_call_io(&io->io); if (!ioloop->running) break; } } } #endif dovecot-2.3.21.1/src/lib/var-expand.c0000644000000000000000000004434414656633576014074 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "md5.h" #include "hash.h" #include "hex-binary.h" #include "base64.h" #include "hostpid.h" #include "hmac.h" #include "pkcs5.h" #include "hash-method.h" #include "str.h" #include "strescape.h" #include "var-expand.h" #include "var-expand-private.h" #include #include #define TABLE_LAST(t) \ ((t)->key == '\0' && (t)->long_key == NULL) struct var_expand_modifier { char key; const char *(*func)(const char *, struct var_expand_context *); }; static ARRAY(struct var_expand_extension_func_table) var_expand_extensions; static const char * m_str_lcase(const char *str, struct var_expand_context *ctx ATTR_UNUSED) { return t_str_lcase(str); } static const char * m_str_ucase(const char *str, struct var_expand_context *ctx ATTR_UNUSED) { return t_str_ucase(str); } static const char * m_str_escape(const char *str, struct var_expand_context *ctx ATTR_UNUSED) { return str_escape(str); } static const char * m_str_hex(const char *str, struct var_expand_context *ctx ATTR_UNUSED) { unsigned long long l; if (str_to_ullong(str, &l) < 0) l = 0; return t_strdup_printf("%llx", l); } static const char * m_str_reverse(const char *str, struct var_expand_context *ctx ATTR_UNUSED) { size_t len = strlen(str); char *p, *rev; rev = t_malloc_no0(len + 1); rev[len] = '\0'; for (p = rev + len - 1; *str != '\0'; str++) *p-- = *str; return rev; } static const char *m_str_hash(const char *str, struct var_expand_context *ctx) { unsigned int value = str_hash(str); string_t *hash = t_str_new(20); if (ctx->width != 0) { value %= ctx->width; ctx->width = 0; } str_printfa(hash, "%x", value); while ((int)str_len(hash) < ctx->offset) str_insert(hash, 0, "0"); ctx->offset = 0; return str_c(hash); } static const char * m_str_newhash(const char *str, struct var_expand_context *ctx) { string_t *hash = t_str_new(20); unsigned char result[MD5_RESULTLEN]; unsigned int i; uint64_t value = 0; md5_get_digest(str, strlen(str), result); for (i = 0; i < sizeof(value); i++) { value <<= 8; value |= result[i]; } if (ctx->width != 0) { value %= ctx->width; ctx->width = 0; } str_printfa(hash, "%x", (unsigned int)value); while ((int)str_len(hash) < ctx->offset) str_insert(hash, 0, "0"); ctx->offset = 0; return str_c(hash); } static const char * m_str_md5(const char *str, struct var_expand_context *ctx ATTR_UNUSED) { unsigned char digest[16]; md5_get_digest(str, strlen(str), digest); return binary_to_hex(digest, sizeof(digest)); } static const char * m_str_ldap_dn(const char *str, struct var_expand_context *ctx ATTR_UNUSED) { string_t *ret = t_str_new(256); while (*str != '\0') { if (*str == '.') str_append(ret, ",dc="); else str_append_c(ret, *str); str++; } return str_free_without_data(&ret); } static const char * m_str_trim(const char *str, struct var_expand_context *ctx ATTR_UNUSED) { size_t len; len = strlen(str); while (len > 0 && i_isspace(str[len-1])) len--; return t_strndup(str, len); } #define MAX_MODIFIER_COUNT 10 static const struct var_expand_modifier modifiers[] = { { 'L', m_str_lcase }, { 'U', m_str_ucase }, { 'E', m_str_escape }, { 'X', m_str_hex }, { 'R', m_str_reverse }, { 'H', m_str_hash }, { 'N', m_str_newhash }, { 'M', m_str_md5 }, { 'D', m_str_ldap_dn }, { 'T', m_str_trim }, { '\0', NULL } }; static int var_expand_short(const struct var_expand_table *table, char key, const char **var_r, const char **error_r) { const struct var_expand_table *t; if (table != NULL) { for (t = table; !TABLE_LAST(t); t++) { if (t->key == key) { *var_r = t->value != NULL ? t->value : ""; return 1; } } } /* not found */ if (key == '%') { *var_r = "%"; return 1; } if (*error_r == NULL) *error_r = t_strdup_printf("Unknown variable '%%%c'", key); *var_r = t_strdup_printf("UNSUPPORTED_VARIABLE_%c", key); return 0; } static int var_expand_hash(struct var_expand_context *ctx, const char *key, const char *field, const char **result_r, const char **error_r) { enum { FORMAT_HEX, FORMAT_HEX_UC, FORMAT_BASE64 } format = FORMAT_HEX; const char *p = strchr(key, ';'); const char *const *args = NULL; const char *algo = key; const char *value; int ret; if (p != NULL) { algo = t_strcut(key, ';'); args = t_strsplit(p+1, ","); } const struct hash_method *method; if (strcmp(algo, "pkcs5") == 0) { method = hash_method_lookup("sha256"); } else if ((method = hash_method_lookup(algo)) == NULL) { return 0; } string_t *field_value = t_str_new(64); string_t *salt = t_str_new(64); string_t *tmp = t_str_new(method->digest_size); if ((ret = var_expand_long(ctx, field, strlen(field), &value, error_r)) < 1) { return ret; } str_append(field_value, value); /* default values */ unsigned int rounds = 1; unsigned int truncbits = 0; if (strcmp(algo, "pkcs5") == 0) { rounds = 2048; str_append(salt, field); } while(args != NULL && *args != NULL) { const char *k = t_strcut(*args, '='); const char *value = strchr(*args, '='); if (value == NULL) { args++; continue; } else { value++; } if (strcmp(k, "rounds") == 0) { if (str_to_uint(value, &rounds)<0) { *error_r = t_strdup_printf( "Cannot parse hash arguments:" "'%s' is not number for rounds", value); return -1; } if (rounds < 1) { *error_r = t_strdup_printf( "Cannot parse hash arguments:" "rounds must be at least 1"); return -1; } } else if (strcmp(k, "truncate") == 0) { if (str_to_uint(value, &truncbits)<0) { *error_r = t_strdup_printf( "Cannot parse hash arguments:" "'%s' is not number for truncbits", value); return -1; } truncbits = I_MIN(truncbits, method->digest_size*8); } else if (strcmp(k, "salt") == 0) { str_truncate(salt, 0); if (var_expand_with_funcs(salt, value, ctx->table, ctx->func_table, ctx->context, error_r) < 0) { return -1; } break; } else if (strcmp(k, "format") == 0) { if (strcmp(value, "hex") == 0) { format = FORMAT_HEX; } else if (strcmp(value, "hexuc") == 0){ format = FORMAT_HEX_UC; } else if (strcmp(value, "base64") == 0) { format = FORMAT_BASE64; } else { *error_r = t_strdup_printf( "Cannot parse hash arguments:" "'%s' is not supported format", value); return -1; } } args++; } str_truncate(tmp, 0); if (strcmp(algo, "pkcs5") == 0) { if (pkcs5_pbkdf(PKCS5_PBKDF2, method, field_value->data, field_value->used, salt->data, salt->used, rounds, HMAC_MAX_CONTEXT_SIZE, tmp) != 0) { *error_r = "Cannot hash: PKCS5_PBKDF2 failed"; return -1; } } else { void *context = t_malloc_no0(method->context_size); str_append_str(tmp, field_value); for(;rounds>0;rounds--) { method->init(context); if (salt->used > 0) method->loop(context, salt->data, salt->used); method->loop(context, tmp->data, tmp->used); unsigned char *digest = buffer_get_modifiable_data(tmp, NULL); method->result(context, digest); if (tmp->used != method->digest_size) buffer_set_used_size(tmp, method->digest_size); } } if (truncbits > 0) buffer_truncate_rshift_bits(tmp, truncbits); switch(format) { case FORMAT_HEX: *result_r = binary_to_hex(tmp->data, tmp->used); return 1; case FORMAT_HEX_UC: *result_r = binary_to_hex(tmp->data, tmp->used); return 1; case FORMAT_BASE64: { string_t *dest = t_str_new(64); base64_encode(tmp->data, tmp->used, dest); *result_r = str_c(dest); return 1; } } i_unreached(); } static int var_expand_func(const struct var_expand_func_table *func_table, const char *key, const char *data, void *context, const char **var_r, const char **error_r) { const char *value = NULL; int ret; if (strcmp(key, "env") == 0) { value = getenv(data); *var_r = value != NULL ? value : ""; return 1; } if (func_table != NULL) { for (; func_table->key != NULL; func_table++) { if (strcmp(func_table->key, key) == 0) { ret = func_table->func(data, context, &value, error_r); *var_r = value != NULL ? value : ""; return ret; } } } if (*error_r == NULL) *error_r = t_strdup_printf("Unknown variable '%%%s'", key); *var_r = t_strdup_printf("UNSUPPORTED_VARIABLE_%s", key); return 0; } static int var_expand_try_extension(struct var_expand_context *ctx, const char *key, const char *data, const char **var_r, const char **error_r) { int ret; const char *sep = strchr(key, ';'); if (sep == NULL) sep = key + strlen(key); /* try with extensions */ const struct var_expand_extension_func_table *f; array_foreach(&var_expand_extensions, f) { /* ensure we won't match abbreviations */ size_t len = sep-key; if (strncasecmp(key, f->key, len) == 0 && f->key[len] == '\0') return f->func(ctx, key, data, var_r, error_r); } if ((ret = var_expand_func(ctx->func_table, key, data, ctx->context, var_r, error_r)) == 0) { *error_r = t_strdup_printf("Unknown variable '%%%s'", key); } return ret; } int var_expand_long(struct var_expand_context *ctx, const void *key_start, size_t key_len, const char **var_r, const char **error_r) { const struct var_expand_table *t; const char *key, *value = NULL; int ret = 1; if (ctx->table != NULL) { for (t = ctx->table; !TABLE_LAST(t); t++) { if (t->long_key != NULL && strncmp(t->long_key, key_start, key_len) == 0 && t->long_key[key_len] == '\0') { *var_r = t->value != NULL ? t->value : ""; return 1; } } } key = t_strndup(key_start, key_len); /* built-in variables: */ switch (key_len) { case 3: if (strcmp(key, "pid") == 0) value = my_pid; else if (strcmp(key, "uid") == 0) value = dec2str(geteuid()); else if (strcmp(key, "gid") == 0) value = dec2str(getegid()); break; case 8: if (strcmp(key, "hostname") == 0) value = my_hostname; break; } if (value == NULL) { const char *data = strchr(key, ':'); if (data != NULL) key = t_strdup_until(key, data++); else data = ""; ret = var_expand_try_extension(ctx, key, data, &value, error_r); if (ret <= 0 && value == NULL) { value = ""; } } *var_r = value; return ret; } int var_expand_with_funcs(string_t *dest, const char *str, const struct var_expand_table *table, const struct var_expand_func_table *func_table, void *context, const char **error_r) { const struct var_expand_modifier *m; const char *var; struct var_expand_context ctx; const char *(*modifier[MAX_MODIFIER_COUNT]) (const char *, struct var_expand_context *); const char *end; unsigned int i, modifier_count; size_t len; int ret, final_ret = 1; *error_r = NULL; i_zero(&ctx); ctx.table = table; ctx.func_table = func_table; ctx.context = context; for (; *str != '\0'; str++) { if (*str != '%') str_append_c(dest, *str); else { int sign = 1; str++; /* reset per-field modifiers */ ctx.offset = 0; ctx.width = 0; ctx.zero_padding = FALSE; /* [.][] */ if (*str == '-') { sign = -1; str++; } if (*str == '0') { ctx.zero_padding = TRUE; str++; } while (*str >= '0' && *str <= '9') { ctx.width = ctx.width*10 + (*str - '0'); str++; } if (*str == '.') { ctx.offset = sign * ctx.width; sign = 1; ctx.width = 0; str++; /* if offset was prefixed with zero (or it was plain zero), just ignore that. zero padding is done with the width. */ ctx.zero_padding = FALSE; if (*str == '0') { ctx.zero_padding = TRUE; str++; } if (*str == '-') { sign = -1; str++; } while (*str >= '0' && *str <= '9') { ctx.width = ctx.width*10 + (*str - '0'); str++; } ctx.width = sign * ctx.width; } modifier_count = 0; while (modifier_count < MAX_MODIFIER_COUNT) { modifier[modifier_count] = NULL; for (m = modifiers; m->key != '\0'; m++) { if (m->key == *str) { /* @UNSAFE */ modifier[modifier_count] = m->func; str++; break; } } if (modifier[modifier_count] == NULL) break; modifier_count++; } if (*str == '\0') break; var = NULL; if (*str == '{' && strchr(str, '}') != NULL) { /* %{long_key} */ unsigned int ctr = 1; bool escape = FALSE; end = str; while(*++end != '\0' && ctr > 0) { if (!escape && *end == '\\') { escape = TRUE; continue; } if (escape) { escape = FALSE; continue; } if (*end == '{') ctr++; if (*end == '}') ctr--; } if (ctr == 0) /* it needs to come back a bit */ end--; /* if there is no } it will consume rest of the string */ len = end - (str + 1); ret = var_expand_long(&ctx, str+1, len, &var, error_r); str = end; } else { ret = var_expand_short(ctx.table, *str, &var, error_r); } i_assert(var != NULL); if (final_ret > ret) final_ret = ret; if (ret <= 0) str_append(dest, var); else { for (i = 0; i < modifier_count; i++) var = modifier[i](var, &ctx); if (ctx.offset < 0) { /* if offset is < 0 then we want to start at the end */ size_t len = strlen(var); size_t offset_from_end = -ctx.offset; if (len > offset_from_end) var += len - offset_from_end; } else { while (*var != '\0' && ctx.offset > 0) { ctx.offset--; var++; } } if (ctx.width == 0) str_append(dest, var); else if (!ctx.zero_padding) { if (ctx.width < 0) ctx.width = strlen(var) - (-ctx.width); str_append_max(dest, var, ctx.width); } else { /* %05d -like padding. no truncation. */ ssize_t len = strlen(var); while (len < ctx.width) { str_append_c(dest, '0'); ctx.width--; } str_append(dest, var); } } } } return final_ret; } int var_expand(string_t *dest, const char *str, const struct var_expand_table *table, const char **error_r) { return var_expand_with_funcs(dest, str, table, NULL, NULL, error_r); } static bool var_get_key_range_full(const char *str, unsigned int *idx_r, unsigned int *size_r) { const struct var_expand_modifier *m; unsigned int i = 0; /* [.][] */ while ((str[i] >= '0' && str[i] <= '9') || str[i] == '-') i++; if (str[i] == '.') { i++; while ((str[i] >= '0' && str[i] <= '9') || str[i] == '-') i++; } do { for (m = modifiers; m->key != '\0'; m++) { if (m->key == str[i]) { i++; break; } } } while (m->key != '\0'); if (str[i] != '{') { /* short key */ *idx_r = i; *size_r = str[i] == '\0' ? 0 : 1; return FALSE; } else { unsigned int depth = 1; bool escape = FALSE; /* long key */ *idx_r = ++i; for (; str[i] != '\0'; i++) { if (!escape && str[i] == '\\') { escape = TRUE; continue; } if (escape) { escape = FALSE; continue; } if (str[i] == '{') depth++; if (str[i] == '}') { if (--depth==0) break; } } *size_r = i - *idx_r; return TRUE; } } char var_get_key(const char *str) { unsigned int idx, size; if (var_get_key_range_full(str, &idx, &size)) return '{'; return str[idx]; } void var_get_key_range(const char *str, unsigned int *idx_r, unsigned int *size_r) { (void)var_get_key_range_full(str, idx_r, size_r); } static bool var_has_long_key(const char **str, const char *long_key) { const char *start, *end; start = strchr(*str, '{'); i_assert(start != NULL); end = strchr(++start, '}'); if (end == NULL) return FALSE; if (strncmp(start, long_key, end-start) == 0 && long_key[end-start] == '\0') return TRUE; *str = end; return FALSE; } bool var_has_key(const char *str, char key, const char *long_key) { char c; for (; *str != '\0'; str++) { if (*str == '%' && str[1] != '\0') { str++; c = var_get_key(str); if (c == key && key != '\0') return TRUE; if (c == '{' && long_key != NULL) { if (var_has_long_key(&str, long_key)) return TRUE; } } } return FALSE; } void var_expand_extensions_deinit(void) { array_free(&var_expand_extensions); } void var_expand_extensions_init(void) { i_array_init(&var_expand_extensions, 32); /* put all hash methods there */ for(const struct hash_method **meth = hash_methods; *meth != NULL; meth++) { struct var_expand_extension_func_table *func = array_append_space(&var_expand_extensions); func->key = (*meth)->name; func->func = var_expand_hash; } /* pkcs5 */ struct var_expand_extension_func_table *func = array_append_space(&var_expand_extensions); func->key = "pkcs5"; func->func = var_expand_hash; /* if */ func = array_append_space(&var_expand_extensions); func->key = "if"; func->func = var_expand_if; } void var_expand_register_func_array(const struct var_expand_extension_func_table *funcs) { for(const struct var_expand_extension_func_table *ptr = funcs; ptr->key != NULL; ptr++) { i_assert(*ptr->key != '\0'); array_push_front(&var_expand_extensions, ptr); } } void var_expand_unregister_func_array(const struct var_expand_extension_func_table *funcs) { for(const struct var_expand_extension_func_table *ptr = funcs; ptr->key != NULL; ptr++) { i_assert(ptr->func != NULL); for(unsigned int i = 0; i < array_count(&var_expand_extensions); i++) { const struct var_expand_extension_func_table *func = array_idx(&var_expand_extensions, i); if (strcasecmp(func->key, ptr->key) == 0) { array_delete(&var_expand_extensions, i, 1); } } } } struct var_expand_table * var_expand_merge_tables(pool_t pool, const struct var_expand_table *a, const struct var_expand_table *b) { ARRAY(struct var_expand_table) table; size_t a_size = var_expand_table_size(a); size_t b_size = var_expand_table_size(b); p_array_init(&table, pool, a_size + b_size + 1); for(size_t i=0; ikey = a[i].key; entry->value = p_strdup(pool, a[i].value); entry->long_key = p_strdup(pool, a[i].long_key); } for(size_t i=0; ikey = b[i].key; entry->value = p_strdup(pool, b[i].value); entry->long_key = p_strdup(pool, b[i].long_key); } array_append_zero(&table); return array_front_modifiable(&table); } dovecot-2.3.21.1/src/lib/ostream-failure-at.h0000644000000000000000000000044414656633576015526 00000000000000#ifndef OSTREAM_FAILURE_AT_H #define OSTREAM_FAILURE_AT_H struct ostream * o_stream_create_failure_at(struct ostream *output, uoff_t failure_offset, const char *error_string); struct ostream * o_stream_create_failure_at_flush(struct ostream *output, const char *error_string); #endif dovecot-2.3.21.1/src/lib/istream-jsonstr.h0000644000000000000000000000027514656633576015173 00000000000000#ifndef ISTREAM_JSONSTR_H #define ISTREAM_JSONSTR_H /* Parse input until '"' is reached. Unescape JSON \x codes. */ struct istream *i_stream_create_jsonstr(struct istream *input); #endif dovecot-2.3.21.1/src/lib/crc32.c0000644000000000000000000000752414656633576012742 00000000000000/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "crc32.h" static uint32_t crc32tab[256] = { 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D }; uint32_t crc32_data(const void *data, size_t size) { return crc32_data_more(0, data, size); } uint32_t crc32_data_more(uint32_t crc, const void *data, size_t size) { const uint8_t *p = data, *end = p + size; crc ^= 0xffffffff; for (; p != end; p++) crc = (crc >> 8) ^ crc32tab[((crc ^ *p) & 0xff)]; crc ^= 0xffffffff; return crc; } uint32_t crc32_str(const char *str) { return crc32_str_more(0, str); } uint32_t crc32_str_more(uint32_t crc, const char *str) { const uint8_t *p = (const uint8_t *)str; crc ^= 0xffffffff; for (; *p != '\0'; p++) crc = (crc >> 8) ^ crc32tab[((crc ^ *p) & 0xff)]; crc ^= 0xffffffff; return crc; } dovecot-2.3.21.1/src/lib/base32.h0000644000000000000000000000334014656633576013102 00000000000000#ifndef BASE32_H #define BASE32_H /* Translates binary data into base32 (RFC 4648, Section 6). The src must not point to dest buffer. The pad argument determines whether output is padded with '='. */ void base32_encode(bool pad, const void *src, size_t src_size, buffer_t *dest); /* Translates binary data into base32hex (RFC 4648, Section 7). The src must not point to dest buffer. The pad argument determines whether output is padded with '='. */ void base32hex_encode(bool pad, const void *src, size_t src_size, buffer_t *dest); /* Translates base32/base32hex data into binary and appends it to dest buffer. dest may point to same buffer as src. Returns 1 if all ok, 0 if end of base32 data found, -1 if data is invalid. Any whitespace characters are ignored. This function may be called multiple times for parsing the same stream. If src_pos is non-NULL, it's updated to first non-translated character in src. */ int base32_decode(const void *src, size_t src_size, size_t *src_pos_r, buffer_t *dest) ATTR_NULL(4); int base32hex_decode(const void *src, size_t src_size, size_t *src_pos_r, buffer_t *dest) ATTR_NULL(4); /* Decode given string to a buffer allocated from data stack. */ buffer_t *t_base32_decode_str(const char *str); buffer_t *t_base32hex_decode_str(const char *str); /* Returns TRUE if c is a valid base32 encoding character (excluding '=') */ bool base32_is_valid_char(char c); bool base32hex_is_valid_char(char c); /* max. buffer size required for base32_encode()/base32hex_encode() */ #define MAX_BASE32_ENCODED_SIZE(size) \ ((size) / 5 * 8 + 8) /* max. buffer size required for base32_decode()/base32hex_decode() */ #define MAX_BASE32_DECODED_SIZE(size) \ ((size) / 8 * 5 + 5) #endif dovecot-2.3.21.1/src/lib/array.h0000644000000000000000000003457714656633576013161 00000000000000#ifndef ARRAY_H #define ARRAY_H /* Array is a buffer accessible using fixed size elements. As long as the compiler provides a typeof() operator, the array provides type safety. If a wrong type is tried to be added to the array, or if the array's contents are tried to be used using a wrong type, the compiler will give a warning. Example usage: struct foo { ARRAY(struct bar) bars; ... }; i_array_init(&foo->bars, 10); struct bar *bar = array_idx(&foo->bars, 5); struct baz *baz = array_idx(&foo->bars, 5); // compiler warning If you want to pass an array as a parameter to a function, you'll need to create a type for the array using ARRAY_DEFINE_TYPE() and use the type in the parameter using ARRAY_TYPE(). Any arrays that you want to be passing around, such as structure members as in the above example, must also be defined using ARRAY_TYPE() too, rather than ARRAY(). Example: ARRAY_DEFINE_TYPE(foo, struct foo); void do_foo(ARRAY_TYPE(foo) *foos) { struct foo *foo = array_idx(foos, 0); } struct foo_manager { ARRAY_TYPE(foo) foos; // pedantically, ARRAY(struct foo) is a different type }; // ... do_foo(&my_foo_manager->foos); // No compiler warning about mismatched types */ #include "array-decl.h" #include "buffer.h" #define p_array_init(array, pool, init_count) \ array_create(array, pool, sizeof(**(array)->v), init_count) #define i_array_init(array, init_count) \ p_array_init(array, default_pool, init_count) #define t_array_init(array, init_count) \ p_array_init(array, pool_datastack_create(), init_count) #ifdef HAVE_TYPEOF # define ARRAY_TYPE_CAST_CONST(array) \ (typeof(*(array)->v)) # define ARRAY_TYPE_CAST_MODIFIABLE(array) \ (typeof(*(array)->v_modifiable)) # define ARRAY_TYPE_CHECK(array, data) \ COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE( \ **(array)->v_modifiable, *(data)) # define ARRAY_TYPES_CHECK(array1, array2) \ COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE( \ **(array1)->v_modifiable, **(array2)->v_modifiable) #else # define ARRAY_TYPE_CAST_CONST(array) # define ARRAY_TYPE_CAST_MODIFIABLE(array) # define ARRAY_TYPE_CHECK(array, data) 0 # define ARRAY_TYPES_CHECK(array1, array2) 0 #endif /* Usage: ARRAY(struct foo) foo_arr; struct foo *foo; array_foreach(&foo_arr, foo) { .. } Note that deleting an element while iterating will cause the iteration to skip over the next element. So deleting a single element and breaking out of the loop is fine, but continuing the loop is likely a bug. Use array_foreach_reverse() instead when deleting multiple elements. */ #define array_foreach(array, elem) \ for (const void *elem ## __foreach_end = \ (const char *)(elem = *(array)->v) + (array)->arr.buffer->used; \ elem != elem ## __foreach_end; (elem)++) #define array_foreach_modifiable(array, elem) \ for (const void *elem ## _end = \ (const char *)(elem = ARRAY_TYPE_CAST_MODIFIABLE(array) \ buffer_get_modifiable_data((array)->arr.buffer, NULL)) + \ (array)->arr.buffer->used; \ elem != elem ## _end; (elem)++) /* Iterate the array in reverse order. */ #define array_foreach_reverse(array, elem) \ for (elem = CONST_PTR_OFFSET(*(array)->v, (array)->arr.buffer->used); \ (const char *)(elem--) > (const char *)*(array)->v; ) #define array_foreach_reverse_modifiable(array, elem) \ for (elem = ARRAY_TYPE_CAST_MODIFIABLE(array) \ ((char *)buffer_get_modifiable_data((array)->arr.buffer, NULL) + \ (array)->arr.buffer->used); \ (const char *)(elem--) > (const char *)*(array)->v; ) /* Usage: ARRAY(struct foo *) foo_ptrs_arr; struct foo *foo; array_foreach_elem(&foo_ptrs_arr, foo) { .. } */ #define array_foreach_elem(array, elem) \ for (const void *_foreach_end = \ CONST_PTR_OFFSET(*(array)->v, (array)->arr.buffer->used), \ *_foreach_ptr = CONST_PTR_OFFSET(*(array)->v, ARRAY_TYPE_CHECK(array, &elem) + \ COMPILE_ERROR_IF_TRUE(sizeof(elem) > sizeof(void *))) \ ; \ (_foreach_ptr != _foreach_end && \ (memcpy(&elem, _foreach_ptr, sizeof(elem)), TRUE)) \ ; \ _foreach_ptr = CONST_PTR_OFFSET(_foreach_ptr, sizeof(elem))) #define array_ptr_to_idx(array, elem) \ ((elem) - (array)->v[0]) /* Return index of iterated element inside array_foreach() or array_foreach_modifiable() loop. Note that this doesn't work inside array_foreach_elem() loop. */ #define array_foreach_idx(array, elem) \ array_ptr_to_idx(array, elem) static inline void array_create_from_buffer_i(struct array *array, buffer_t *buffer, size_t element_size) { array->buffer = buffer; array->element_size = element_size; } #define array_create_from_buffer(array, buffer, element_size) \ array_create_from_buffer_i(&(array)->arr, buffer, element_size) static inline void array_create_i(struct array *array, pool_t pool, size_t element_size, unsigned int init_count) { buffer_t *buffer; buffer = buffer_create_dynamic_max(pool, init_count * element_size, SIZE_MAX / element_size < UINT_MAX ? SIZE_MAX : UINT_MAX * element_size); array_create_from_buffer_i(array, buffer, element_size); } #define array_create(array, pool, element_size, init_count) \ array_create_i(&(array)->arr, pool, element_size, init_count) static inline void array_free_i(struct array *array) { buffer_free(&array->buffer); } #define array_free(array) \ array_free_i(&(array)->arr) static inline void * ATTR_WARN_UNUSED_RESULT array_free_without_data_i(struct array *array) { return buffer_free_without_data(&array->buffer); } #define array_free_without_data(array) \ ARRAY_TYPE_CAST_MODIFIABLE(array)array_free_without_data_i(&(array)->arr) static inline bool array_is_created_i(const struct array *array) { return array->buffer != NULL; } #define array_is_created(array) \ array_is_created_i(&(array)->arr) static inline pool_t ATTR_PURE array_get_pool_i(struct array *array) { return buffer_get_pool(array->buffer); } #define array_get_pool(array) \ array_get_pool_i(&(array)->arr) static inline void array_clear_i(struct array *array) { buffer_set_used_size(array->buffer, 0); } #define array_clear(array) \ array_clear_i(&(array)->arr) static inline unsigned int ATTR_PURE array_count_i(const struct array *array) { return array->buffer->used / array->element_size; } #define array_count(array) \ array_count_i(&(array)->arr) /* No need for the real count if all we're doing is comparing against 0 */ #define array_is_empty(array) \ ((array)->arr.buffer->used == 0) #define array_not_empty(array) \ ((array)->arr.buffer->used > 0) static inline void array_append_i(struct array *array, const void *data, unsigned int count) { buffer_append(array->buffer, data, count * array->element_size); } #define array_append(array, data, count) \ TYPE_CHECKS(void, ARRAY_TYPE_CHECK(array, data), \ array_append_i(&(array)->arr, data, count)) static inline void array_append_array_i(struct array *dest_array, const struct array *src_array) { i_assert(dest_array->element_size == src_array->element_size); buffer_append_buf(dest_array->buffer, src_array->buffer, 0, SIZE_MAX); } #define array_append_array(dest_array, src_array) \ TYPE_CHECKS(void, ARRAY_TYPES_CHECK(dest_array, src_array), \ array_append_array_i(&(dest_array)->arr, &(src_array)->arr)) static inline void array_insert_i(struct array *array, unsigned int idx, const void *data, unsigned int count) { buffer_insert(array->buffer, idx * array->element_size, data, count * array->element_size); } #define array_insert(array, idx, data, count) \ TYPE_CHECKS(void, ARRAY_TYPE_CHECK(array, data), \ array_insert_i(&(array)->arr, idx, data, count)) static inline void array_delete_i(struct array *array, unsigned int idx, unsigned int count) { buffer_delete(array->buffer, idx * array->element_size, count * array->element_size); } #define array_delete(array, idx, count) \ array_delete_i(&(array)->arr, idx, count) static inline const void * array_get_i(const struct array *array, unsigned int *count_r) { *count_r = array_count_i(array); return array->buffer->data; } #define array_get(array, count) \ ARRAY_TYPE_CAST_CONST(array)array_get_i(&(array)->arr, count) /* Re: i_assert() vs. pure: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51971#c1 */ static inline const void * ATTR_PURE array_idx_i(const struct array *array, unsigned int idx) { i_assert(idx < array->buffer->used / array->element_size); return CONST_PTR_OFFSET(array->buffer->data, idx * array->element_size); } #define array_front(array) array_idx(array, 0) #define array_front_modifiable(array) array_idx_modifiable(array, 0) #define array_back(array) array_idx(array, array_count(array)-1) #define array_back_modifiable(array) array_idx_modifiable(array, array_count(array)-1) #define array_pop_back(array) array_delete(array, array_count(array)-1, 1); #define array_push_back(array, item) array_append(array, (item), 1) #define array_pop_front(array) array_delete(array, 0, 1) #define array_push_front(array, item) array_insert(array, 0, (item), 1) #define array_idx(array, idx) \ ARRAY_TYPE_CAST_CONST(array)array_idx_i(&(array)->arr, idx) /* Using *array_idx() will fail if the compiler doesn't support typeof(). The same can be done with array_idx_elem() for arrays that have pointers. */ #ifdef HAVE_TYPEOF # define array_idx_elem(array, idx) \ (TRUE ? *array_idx(array, idx) : \ COMPILE_ERROR_IF_TRUE(sizeof(**(array)->v) != sizeof(void *))) #else # define array_idx_elem(array, idx) \ (*(void **)array_idx_i(&(array)->arr, idx)) #endif static inline void * array_get_modifiable_i(struct array *array, unsigned int *count_r) { *count_r = array_count_i(array); return buffer_get_modifiable_data(array->buffer, NULL); } #define array_get_modifiable(array, count) \ ARRAY_TYPE_CAST_MODIFIABLE(array) \ array_get_modifiable_i(&(array)->arr, count) void * array_idx_modifiable_i(const struct array *array, unsigned int idx) ATTR_PURE; #define array_idx_modifiable(array, idx) \ ARRAY_TYPE_CAST_MODIFIABLE(array) \ array_idx_modifiable_i(&(array)->arr, idx) void *array_idx_get_space_i(struct array *array, unsigned int idx); #define array_idx_get_space(array, idx) \ ARRAY_TYPE_CAST_MODIFIABLE(array) \ array_idx_get_space_i(&(array)->arr, idx) void array_idx_set_i(struct array *array, unsigned int idx, const void *data); #define array_idx_set(array, idx, data) \ TYPE_CHECKS(void, ARRAY_TYPE_CHECK(array, data), \ array_idx_set_i(&(array)->arr, idx, data)) void array_idx_clear_i(struct array *array, unsigned int idx); #define array_idx_clear(array, idx) \ array_idx_clear_i(&(array)->arr, idx) static inline void * array_append_space_i(struct array *array) { void *data; data = buffer_append_space_unsafe(array->buffer, array->element_size); memset(data, 0, array->element_size); return data; } #define array_append_space(array) \ ARRAY_TYPE_CAST_MODIFIABLE(array)array_append_space_i(&(array)->arr) #define array_append_zero(array) \ (void)array_append_space_i(&(array)->arr) void *array_insert_space_i(struct array *array, unsigned int idx); #define array_insert_space(array, idx) \ ARRAY_TYPE_CAST_MODIFIABLE(array) \ array_insert_space_i(&(array)->arr, idx) static inline void array_copy(struct array *dest, unsigned int dest_idx, const struct array *src, unsigned int src_idx, unsigned int count) { i_assert(dest->element_size == src->element_size); buffer_copy(dest->buffer, dest_idx * dest->element_size, src->buffer, src_idx * src->element_size, count * dest->element_size); } bool array_cmp_i(const struct array *array1, const struct array *array2) ATTR_PURE; #define array_cmp(array1, array2) \ array_cmp_i(&(array1)->arr, &(array2)->arr) /* Test equality via a comparator */ bool array_equal_fn_i(const struct array *array1, const struct array *array2, int (*cmp)(const void*, const void *)) ATTR_PURE; #define array_equal_fn(array1, array2, cmp) \ TYPE_CHECKS(bool, \ ARRAY_TYPES_CHECK(array1, array2) || \ CALLBACK_TYPECHECK(cmp, int (*)(typeof(*(array1)->v), \ typeof(*(array2)->v))), \ array_equal_fn_i(&(array1)->arr, &(array2)->arr, \ (int (*)(const void *, const void *))cmp)) bool array_equal_fn_ctx_i(const struct array *array1, const struct array *array2, int (*cmp)(const void*, const void *, const void *), const void *context) ATTR_PURE; /* Same, but with a context pointer. context can't be void* as ``const typeof(context)'' won't compile, so ``const typeof(*context)*'' is required instead, and that requires a complete type. */ #define array_equal_fn_ctx(array1, array2, cmp, ctx) \ TYPE_CHECKS(bool, \ ARRAY_TYPES_CHECK(array1, array2) || \ CALLBACK_TYPECHECK(cmp, int (*)(typeof(*(array1)->v), \ typeof(*(array2)->v), \ const typeof(*ctx)*)), \ array_equal_fn_ctx_i(&(array1)->arr, &(array2)->arr, \ (int (*)(const void *, const void *, const void *))cmp, ctx)) void array_reverse_i(struct array *array); #define array_reverse(array) \ array_reverse_i(&(array)->arr) void array_sort_i(struct array *array, int (*cmp)(const void *, const void *)); #define array_sort(array, cmp) \ TYPE_CHECKS(void, \ CALLBACK_TYPECHECK(cmp, int (*)(typeof(*(array)->v), \ typeof(*(array)->v))), \ array_sort_i(&(array)->arr, (int (*)(const void *, const void *))cmp)) void *array_bsearch_i(struct array *array, const void *key, int (*cmp)(const void *, const void *)); #define array_bsearch(array, key, cmp) \ TYPE_CHECKS(void *, \ CALLBACK_TYPECHECK(cmp, int (*)(typeof(const typeof(*key) *), \ typeof(*(array)->v))), \ ARRAY_TYPE_CAST_MODIFIABLE(array)array_bsearch_i(&(array)->arr, \ (const void *)key, (int (*)(const void *, const void *))cmp)) /* Returns pointer to first element for which cmp(key,elem)==0, or NULL */ const void *array_lsearch_i(const struct array *array, const void *key, int (*cmp)(const void *, const void *)); static inline void *array_lsearch_modifiable_i(struct array *array, const void *key, int (*cmp)(const void *, const void *)) { return (void *)array_lsearch_i(array, key, cmp); } #define ARRAY_LSEARCH_CALL(modifiable, array, key, cmp) \ TYPE_CHECKS(void *, \ CALLBACK_TYPECHECK(cmp, int (*)(typeof(const typeof(*key) *), \ typeof(*(array)->v))), \ array_lsearch##modifiable##i( \ &(array)->arr, (const void *)key, \ (int (*)(const void *, const void *))cmp)) #define array_lsearch(array, key, cmp) \ ARRAY_TYPE_CAST_CONST(array)ARRAY_LSEARCH_CALL(_, array, key, cmp) #define array_lsearch_modifiable(array, key, cmp) \ ARRAY_TYPE_CAST_MODIFIABLE(array)ARRAY_LSEARCH_CALL(_modifiable_, array, key, cmp) #endif dovecot-2.3.21.1/src/lib/utc-offset.h0000644000000000000000000000024414656633576014102 00000000000000#ifndef UTC_OFFSET_H #define UTC_OFFSET_H #include /* Returns given time's offset to UTC in minutes. */ int utc_offset(struct tm *tm, time_t t); #endif dovecot-2.3.21.1/src/lib/hash.h0000644000000000000000000001726014656633576012754 00000000000000#ifndef HASH_H #define HASH_H struct hash_table; #ifdef HAVE_TYPEOF # define HASH_VALUE_CAST(table) (typeof((table)._value)) #else # define HASH_VALUE_CAST(table) #endif /* Returns hash code. */ typedef unsigned int hash_callback_t(const void *p); /* Returns 0 if the pointers are equal. */ typedef int hash_cmp_callback_t(const void *p1, const void *p2); /* Create a new hash table. If initial_size is 0, the default value is used. table_pool is used to allocate/free large hash tables, node_pool is used for smaller allocations and can also be alloconly pool. The pools must not be free'd before hash_table_destroy() is called. */ void hash_table_create(struct hash_table **table_r, pool_t node_pool, unsigned int initial_size, hash_callback_t *hash_cb, hash_cmp_callback_t *key_compare_cb); #define hash_table_create(table, pool, size, hash_cb, key_cmp_cb) \ TYPE_CHECKS(void, \ COMPILE_ERROR_IF_TRUE( \ sizeof((*table)._key) != sizeof(void *) || \ sizeof((*table)._value) != sizeof(void *)) || \ COMPILE_ERROR_IF_TRUE( \ !__builtin_types_compatible_p(typeof(&key_cmp_cb), \ int (*)(typeof((*table)._key), typeof((*table)._key))) && \ !__builtin_types_compatible_p(typeof(&key_cmp_cb), \ int (*)(typeof((*table)._const_key), typeof((*table)._const_key)))) || \ COMPILE_ERROR_IF_TRUE( \ !__builtin_types_compatible_p(typeof(&hash_cb), \ unsigned int (*)(typeof((*table)._key))) && \ !__builtin_types_compatible_p(typeof(&hash_cb), \ unsigned int (*)(typeof((*table)._const_key)))), \ hash_table_create(&(*table)._table, pool, size, \ (hash_callback_t *)hash_cb, \ (hash_cmp_callback_t *)key_cmp_cb)) /* Create hash table where comparisons are done directly with the pointers. */ void hash_table_create_direct(struct hash_table **table_r, pool_t node_pool, unsigned int initial_size); #define hash_table_create_direct(table, pool, size) \ TYPE_CHECKS(void, \ COMPILE_ERROR_IF_TRUE( \ sizeof((*table)._key) != sizeof(void *) || \ sizeof((*table)._value) != sizeof(void *)), \ hash_table_create_direct(&(*table)._table, pool, size)) #define hash_table_is_created(table) \ ((table)._table != NULL) void hash_table_destroy(struct hash_table **table); #define hash_table_destroy(table) \ hash_table_destroy(&(*table)._table) /* Remove all nodes from hash table. If free_collisions is TRUE, the memory allocated from node_pool is freed, or discarded with alloconly pools. WARNING: If you p_clear() the node_pool, the free_collisions must be TRUE. */ void hash_table_clear(struct hash_table *table, bool free_collisions); #define hash_table_clear(table, free_collisions) \ hash_table_clear((table)._table, free_collisions) void *hash_table_lookup(const struct hash_table *table, const void *key) ATTR_PURE; #define hash_table_lookup(table, key) \ TYPE_CHECKS(void *, \ COMPILE_ERROR_IF_TYPES2_NOT_COMPATIBLE((table)._key, (table)._const_key, key), \ HASH_VALUE_CAST(table)hash_table_lookup((table)._table, (key))) bool hash_table_lookup_full(const struct hash_table *table, const void *lookup_key, void **orig_key_r, void **value_r); #ifndef __cplusplus # define hash_table_lookup_full(table, lookup_key, orig_key_r, value_r) \ TYPE_CHECKS(bool, \ COMPILE_ERROR_IF_TYPES2_NOT_COMPATIBLE((table)._const_key, (table)._key, lookup_key) || \ COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE((table)._keyp, orig_key_r) || \ COMPILE_ERROR_IF_TRUE(sizeof(*(orig_key_r)) != sizeof(void *)) || \ COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE((table)._valuep, value_r) || \ COMPILE_ERROR_IF_TRUE(sizeof(*(value_r)) != sizeof(void *)), \ hash_table_lookup_full((table)._table, \ (lookup_key), (void *)(orig_key_r), (void *)(value_r))) #else /* C++ requires (void **) casting, but that's not possible with strict aliasing, so .. we'll just disable the type checks */ # define hash_table_lookup_full(table, lookup_key, orig_key_r, value_r) \ hash_table_lookup_full((table)._table, lookup_key, orig_key_r, value_r) #endif /* Suppose to insert a new key-value node to the hash table. If the key already exists, assert-crash. */ void hash_table_insert(struct hash_table *table, void *key, void *value); /* If the key doesn't exists, do the exact same as hash_table_insert() If the key already exists, preserve the original key and update only the value.*/ void hash_table_update(struct hash_table *table, void *key, void *value); #define hash_table_insert(table, key, value) \ TYPE_CHECKS(void, \ COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE((table)._key, key) || \ COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE((table)._value, value), \ hash_table_insert((table)._table, (void *)(key), (void *)(value))) #define hash_table_update(table, key, value) \ TYPE_CHECKS(void, \ COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE((table)._key, key) || \ COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE((table)._value, value), \ hash_table_update((table)._table, (void *)(key), (void *)(value))) bool hash_table_try_remove(struct hash_table *table, const void *key); #define hash_table_try_remove(table, key) \ TYPE_CHECKS(bool, \ COMPILE_ERROR_IF_TYPES2_NOT_COMPATIBLE((table)._const_key, (table)._key, key), \ hash_table_try_remove((table)._table, (const void *)(key))) #define hash_table_remove(table, key) \ STMT_START { \ if (unlikely(!hash_table_try_remove(table, key))) \ i_panic("key not found from hash"); \ } STMT_END unsigned int hash_table_count(const struct hash_table *table) ATTR_PURE; #define hash_table_count(table) \ hash_table_count((table)._table) /* Iterates through all nodes in hash table. You may safely call hash_table_*() functions while iterating, but if you add any new nodes, they may or may not be called for in this iteration. */ struct hash_iterate_context *hash_table_iterate_init(struct hash_table *table); #define hash_table_iterate_init(table) \ hash_table_iterate_init((table)._table) bool hash_table_iterate(struct hash_iterate_context *ctx, void **key_r, void **value_r); #ifndef __cplusplus # define hash_table_iterate(ctx, table, key_r, value_r) \ TYPE_CHECKS(bool, \ COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE((table)._keyp, key_r) || \ COMPILE_ERROR_IF_TRUE(sizeof(*(key_r)) != sizeof(void *)) || \ COMPILE_ERROR_IF_TRUE(sizeof(*(value_r)) != sizeof(void *)) || \ COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE((table)._valuep, value_r), \ hash_table_iterate(ctx, (void *)(key_r), (void *)(value_r))) #else /* C++ requires (void **) casting, but that's not possible with strict aliasing, so .. we'll just disable the type checks */ # define hash_table_iterate(ctx, table, key_r, value_r) \ hash_table_iterate(ctx, key_r, value_r) #endif void hash_table_iterate_deinit(struct hash_iterate_context **ctx); /* Hash table isn't resized, and removed nodes aren't removed from the list while hash table is freezed. Supports nesting. */ void hash_table_freeze(struct hash_table *table); void hash_table_thaw(struct hash_table *table); #define hash_table_freeze(table) \ hash_table_freeze((table)._table) #define hash_table_thaw(table) \ hash_table_thaw((table)._table) /* Copy all nodes from one hash table to another */ void hash_table_copy(struct hash_table *dest, struct hash_table *src); #define hash_table_copy(table1, table2) \ hash_table_copy((table1)._table, (table2)._table) /* hash function for strings */ unsigned int str_hash(const char *p) ATTR_PURE; unsigned int strcase_hash(const char *p) ATTR_PURE; /* fast hash function which uppercases a-z. Does not work well with input that consists from non number/letter input, as it works by dropping 0x20. */ unsigned int strfastcase_hash(const char *p) ATTR_PURE; /* a generic hash for a given memory block */ unsigned int mem_hash(const void *p, unsigned int size) ATTR_PURE; #endif dovecot-2.3.21.1/src/lib/mkdir-parents.h0000644000000000000000000000261614656633576014610 00000000000000#ifndef MKDIR_PARENTS_H #define MKDIR_PARENTS_H #include /* Create path and all the directories under it if needed. Permissions for existing directories isn't changed. Returns 0 if ok. If directory already exists, returns -1 with errno=EEXIST. */ int mkdir_parents(const char *path, mode_t mode); /* Like mkdir_parents(), but use the given uid/gid for newly created directories. (uid_t)-1 or (gid_t)-1 can be used to indicate that it doesn't need to be changed. If gid isn't (gid_t)-1 and the parent directory had setgid-bit enabled, it's removed unless explicitly included in the mode. */ int mkdir_parents_chown(const char *path, mode_t mode, uid_t uid, gid_t gid); /* Like mkdir_parents_chown(), but change only group. If chown() fails with EACCES, use gid_origin in the error message. */ int mkdir_parents_chgrp(const char *path, mode_t mode, gid_t gid, const char *gid_origin); /* Like mkdir_parents_chown(), but don't actually create any parents. */ int mkdir_chown(const char *path, mode_t mode, uid_t uid, gid_t gid); int mkdir_chgrp(const char *path, mode_t mode, gid_t gid, const char *gid_origin); /* stat() the path or its first parent that exists. Returns 0 if ok, -1 if failed. root_dir is set to the last stat()ed directory (on success and on failure). */ int stat_first_parent(const char *path, const char **root_dir_r, struct stat *st_r); #endif dovecot-2.3.21.1/src/lib/istream-timeout.c0000644000000000000000000001137114656633576015151 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "time-util.h" #include "istream-private.h" #include "istream-timeout.h" struct timeout_istream { struct istream_private istream; struct timeout *to; struct timeval last_read_timestamp; time_t created; unsigned int timeout_msecs; bool update_timestamp; }; static void i_stream_timeout_close(struct iostream_private *stream, bool close_parent) { struct timeout_istream *tstream = container_of(stream, struct timeout_istream, istream.iostream); timeout_remove(&tstream->to); if (close_parent) i_stream_close(tstream->istream.parent); } static void i_stream_timeout_switch_ioloop_to(struct istream_private *stream, struct ioloop *ioloop) { struct timeout_istream *tstream = container_of(stream, struct timeout_istream, istream); if (tstream->to != NULL) tstream->to = io_loop_move_timeout_to(ioloop, &tstream->to); } static void i_stream_timeout(struct timeout_istream *tstream) { struct iostream_private *iostream = &tstream->istream.iostream; unsigned int over_msecs; int diff; if (tstream->update_timestamp) { /* we came here after a long-running code. timeouts are handled before IOs, so wait for i_stream_read() to be called again before assuming that we've timed out. */ return; } timeout_remove(&tstream->to); diff = timeval_diff_msecs(&ioloop_timeval, &tstream->last_read_timestamp); if (diff < (int)tstream->timeout_msecs) { /* we haven't reached the read timeout yet, update it */ if (diff < 0) diff = 0; tstream->to = timeout_add_to(io_stream_get_ioloop(iostream), tstream->timeout_msecs - diff, i_stream_timeout, tstream); return; } over_msecs = diff - tstream->timeout_msecs; io_stream_set_error(&tstream->istream.iostream, "Read timeout in %u.%03u s after %"PRIuUOFF_T" bytes%s", diff/1000, diff%1000, tstream->istream.istream.v_offset, over_msecs < 1000 ? "" : t_strdup_printf( " (requested timeout in %u ms)", tstream->timeout_msecs)); tstream->istream.istream.stream_errno = ETIMEDOUT; i_stream_set_input_pending(tstream->istream.parent, TRUE); } static void i_stream_timeout_set_pending(struct timeout_istream *tstream) { /* make sure we get called again on the next ioloop run. this updates the timeout to the timestamp where we actually would have wanted to start waiting for more data (so if there is long-running code outside the ioloop it's not counted) */ tstream->update_timestamp = TRUE; tstream->last_read_timestamp = ioloop_timeval; i_stream_set_input_pending(&tstream->istream.istream, TRUE); } static ssize_t i_stream_timeout_read(struct istream_private *stream) { struct timeout_istream *tstream = container_of(stream, struct timeout_istream, istream); struct iostream_private *iostream = &tstream->istream.iostream; ssize_t ret; i_stream_seek(stream->parent, stream->parent_start_offset + stream->istream.v_offset); ret = i_stream_read_copy_from_parent(&stream->istream); if (ret < 0) { /* failed */ if (errno == ECONNRESET || errno == EPIPE) { int diff = ioloop_time - tstream->created; io_stream_set_error(&tstream->istream.iostream, "%s (opened %d secs ago)", i_stream_get_error(stream->parent), diff); } } else if (tstream->to == NULL && tstream->timeout_msecs > 0) { /* first read. add the timeout here instead of in init in case the stream is created long before it's actually read from. */ tstream->to = timeout_add_to(io_stream_get_ioloop(iostream), tstream->timeout_msecs, i_stream_timeout, tstream); i_stream_timeout_set_pending(tstream); } else if (ret > 0 && tstream->to != NULL) { /* we read something, reset the timeout */ timeout_reset(tstream->to); i_stream_timeout_set_pending(tstream); } else if (tstream->update_timestamp) { tstream->update_timestamp = FALSE; tstream->last_read_timestamp = ioloop_timeval; } return ret; } struct istream * i_stream_create_timeout(struct istream *input, unsigned int timeout_msecs) { struct timeout_istream *tstream; tstream = i_new(struct timeout_istream, 1); tstream->timeout_msecs = timeout_msecs; tstream->istream.max_buffer_size = input->real_stream->max_buffer_size; tstream->istream.stream_size_passthrough = TRUE; tstream->created = ioloop_time; tstream->istream.read = i_stream_timeout_read; tstream->istream.switch_ioloop_to = i_stream_timeout_switch_ioloop_to; tstream->istream.iostream.close = i_stream_timeout_close; tstream->istream.istream.readable_fd = input->readable_fd; tstream->istream.istream.blocking = input->blocking; tstream->istream.istream.seekable = input->seekable; return i_stream_create(&tstream->istream, input, i_stream_get_fd(input), 0); } dovecot-2.3.21.1/src/lib/fdpass.c0000644000000000000000000001345514656633576013306 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ /* fdpass.c - File descriptor passing between processes via UNIX sockets This isn't fully portable, but pretty much all UNIXes nowadays should support this. If you're having runtime problems with fd_read(), check the end of fd_read() and play with the if condition. If you're having problems with fd_send(), try defining BUGGY_CMSG_MACROS. If this file doesn't compile at all, you should check if this is supported in your system at all. It may require some extra #define to enable it. If not, you're pretty much out of luck. Cygwin didn't last I checked. */ #define _XPG4_2 #if defined(irix) || defined (__irix__) || defined(sgi) || defined (__sgi__) # define _XOPEN_SOURCE 4 /* for IRIX */ #endif #if !defined(_AIX) && !defined(_XOPEN_SOURCE_EXTENDED) # define _XOPEN_SOURCE_EXTENDED /* for Tru64, breaks AIX */ #endif #ifdef HAVE_CONFIG_H # include "lib.h" #else # define i_assert(x) #endif #include #include #include #include #include #include #include "fdpass.h" #ifndef HAVE_CONFIG_H struct const_iovec { const void *iov_base; size_t iov_len; }; #endif /* RFC 2292 defines CMSG_*() macros, but some operating systems don't have them so we'll define our own if they don't exist. CMSG_LEN(data) is used to calculate size of sizeof(struct cmsghdr) + sizeof(data) and padding between them. CMSG_SPACE(data) also calculates the padding needed after the data, in case multiple objects are sent. cmsghdr contains cmsg_len field and two integers. cmsg_len is sometimes defined as sockaddr_t and sometimes size_t, so it can be either 32bit or 64bit. This padding is added by compiler in sizeof(struct cmsghdr). Padding required by CMSG_DATA() can vary. Usually it wants size_t or 32bit. With Solaris it's in _CMSG_DATA_ALIGNMENT (32bit), we assume others want size_t. We don't really need CMSG_SPACE() to be exactly correct, because currently we send only one object at a time. But anyway I'm trying to keep that correct in case it's sometimes needed.. */ #ifdef BUGGY_CMSG_MACROS /* Some OSes have broken CMSG macros in 64bit systems. The macros use 64bit alignment while kernel uses 32bit alignment. */ # undef CMSG_SPACE # undef CMSG_LEN # undef CMSG_DATA # define CMSG_DATA(cmsg) ((char *)((cmsg) + 1)) # define _CMSG_DATA_ALIGNMENT 4 # define _CMSG_HDR_ALIGNMENT 4 #endif #ifndef CMSG_SPACE # define MY_ALIGN(len, align) \ (((len) + align - 1) & ~(align - 1)) /* Alignment between cmsghdr and data */ # ifndef _CMSG_DATA_ALIGNMENT # define _CMSG_DATA_ALIGNMENT sizeof(size_t) # endif /* Alignment between data and next cmsghdr */ # ifndef _CMSG_HDR_ALIGNMENT # define _CMSG_HDR_ALIGNMENT sizeof(size_t) # endif # define CMSG_SPACE(len) \ (MY_ALIGN(sizeof(struct cmsghdr), _CMSG_DATA_ALIGNMENT) + \ MY_ALIGN(len, _CMSG_HDR_ALIGNMENT)) # define CMSG_LEN(len) \ (MY_ALIGN(sizeof(struct cmsghdr), _CMSG_DATA_ALIGNMENT) + (len)) #endif #ifdef SCM_RIGHTS ssize_t fd_send(int handle, int send_fd, const void *data, size_t size) { struct msghdr msg; struct const_iovec iov; struct cmsghdr *cmsg; char buf[CMSG_SPACE(sizeof(int))]; /* at least one byte is required to be sent with fd passing */ i_assert(size > 0 && size < INT_MAX); memset(&msg, 0, sizeof(struct msghdr)); iov.iov_base = data; iov.iov_len = size; msg.msg_iov = (void *)&iov; msg.msg_iovlen = 1; if (send_fd != -1) { /* set the control and controllen before CMSG_FIRSTHDR(). */ memset(buf, 0, sizeof(buf)); msg.msg_control = buf; msg.msg_controllen = sizeof(buf); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(int)); memcpy(CMSG_DATA(cmsg), &send_fd, sizeof(send_fd)); /* set the real length we want to use. Do it after all is set just in case CMSG macros required the extra padding in the end. */ msg.msg_controllen = cmsg->cmsg_len; } return sendmsg(handle, &msg, 0); } #ifdef LINUX20 /* Linux 2.0.x doesn't set any cmsg fields. Note that this might make some attacks possible so don't do it unless you really have to. */ # define CHECK_CMSG(cmsg) ((cmsg) != NULL) #else # define CHECK_CMSG(cmsg) \ ((cmsg) != NULL && \ (size_t)(cmsg)->cmsg_len >= (size_t)CMSG_LEN(sizeof(int)) && \ (cmsg)->cmsg_level == SOL_SOCKET && (cmsg)->cmsg_type == SCM_RIGHTS) #endif ssize_t fd_read(int handle, void *data, size_t size, int *fd) { struct msghdr msg; struct iovec iov; struct cmsghdr *cmsg; ssize_t ret; char buf[CMSG_SPACE(sizeof(int))]; i_assert(size > 0 && size < INT_MAX); memset(&msg, 0, sizeof (struct msghdr)); iov.iov_base = data; iov.iov_len = size; msg.msg_iov = &iov; msg.msg_iovlen = 1; memset(buf, 0, sizeof(buf)); msg.msg_control = buf; msg.msg_controllen = sizeof(buf); ret = recvmsg(handle, &msg, 0); if (ret <= 0) { *fd = -1; return ret; } /* at least one byte transferred - we should have the fd now. do extra checks to make sure it really is an fd that is being transferred to avoid potential DoS conditions. some systems don't set all these values correctly however so CHECK_CMSG() is somewhat system dependent */ cmsg = CMSG_FIRSTHDR(&msg); if (!CHECK_CMSG(cmsg)) *fd = -1; else memcpy(fd, CMSG_DATA(cmsg), sizeof(*fd)); return ret; } #else # ifdef __GNUC__ # warning SCM_RIGHTS not supported, privilege separation not possible # endif ssize_t fd_send(int handle ATTR_UNUSED, int send_fd ATTR_UNUSED, const void *data ATTR_UNUSED, size_t size ATTR_UNUSED) { errno = ENOSYS; return -1; } ssize_t fd_read(int handle ATTR_UNUSED, void *data ATTR_UNUSED, size_t size ATTR_UNUSED, int *fd ATTR_UNUSED) { errno = ENOSYS; return -1; } #endif dovecot-2.3.21.1/src/lib/child-wait.c0000644000000000000000000000614114656633576014045 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "lib-signals.h" #include "hash.h" #include "child-wait.h" #include struct child_wait { unsigned int pid_count; child_wait_callback_t *callback; void *context; }; static int child_wait_refcount = 0; /* pid_t => wait */ static HASH_TABLE(void *, struct child_wait *) child_pids; static void sigchld_handler(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED); #undef child_wait_new_with_pid struct child_wait * child_wait_new_with_pid(pid_t pid, child_wait_callback_t *callback, void *context) { struct child_wait *wait; wait = i_new(struct child_wait, 1); wait->callback = callback; wait->context = context; if (pid != (pid_t)-1) child_wait_add_pid(wait, pid); return wait; } void child_wait_free(struct child_wait **_wait) { struct child_wait *wait = *_wait; struct hash_iterate_context *iter; void *key; struct child_wait *value; *_wait = NULL; if (wait->pid_count > 0) { /* this should be rare, so iterating hash is fast enough */ iter = hash_table_iterate_init(child_pids); while (hash_table_iterate(iter, child_pids, &key, &value)) { if (value == wait) { hash_table_remove(child_pids, key); if (--wait->pid_count == 0) break; } } hash_table_iterate_deinit(&iter); } i_free(wait); } void child_wait_add_pid(struct child_wait *wait, pid_t pid) { wait->pid_count++; hash_table_insert(child_pids, POINTER_CAST(pid), wait); lib_signals_set_expected(SIGCHLD, TRUE, sigchld_handler, NULL); } void child_wait_remove_pid(struct child_wait *wait, pid_t pid) { wait->pid_count--; hash_table_remove(child_pids, POINTER_CAST(pid)); if (hash_table_count(child_pids) == 0) lib_signals_set_expected(SIGCHLD, FALSE, sigchld_handler, NULL); } static void sigchld_handler(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED) { struct child_wait_status status; while ((status.pid = waitpid(-1, &status.status, WNOHANG)) > 0) { status.wait = hash_table_lookup(child_pids, POINTER_CAST(status.pid)); if (status.wait != NULL) { child_wait_remove_pid(status.wait, status.pid); status.wait->callback(&status, status.wait->context); } } if (status.pid == -1 && errno != EINTR && errno != ECHILD) i_error("waitpid() failed: %m"); } void child_wait_switch_ioloop(void) { lib_signals_switch_ioloop(SIGCHLD, sigchld_handler, NULL); } void child_wait_init(void) { if (child_wait_refcount++ > 0) { child_wait_switch_ioloop(); return; } hash_table_create_direct(&child_pids, default_pool, 0); lib_signals_set_handler(SIGCHLD, LIBSIG_FLAGS_SAFE, sigchld_handler, NULL); } void child_wait_deinit(void) { struct hash_iterate_context *iter; void *key; struct child_wait *value; i_assert(child_wait_refcount > 0); if (--child_wait_refcount > 0) { child_wait_switch_ioloop(); return; } lib_signals_unset_handler(SIGCHLD, sigchld_handler, NULL); iter = hash_table_iterate_init(child_pids); while (hash_table_iterate(iter, child_pids, &key, &value)) i_free(value); hash_table_iterate_deinit(&iter); hash_table_destroy(&child_pids); } dovecot-2.3.21.1/src/lib/istream-sized.h0000644000000000000000000000314314656633576014604 00000000000000#ifndef ISTREAM_SIZED_H #define ISTREAM_SIZED_H struct istream_sized_error_data { /* Stream's current v_offset */ uoff_t v_offset; /* How many more bytes are being added within this read() */ size_t new_bytes; /* What's the original wanted size. */ uoff_t wanted_size; /* TRUE if we're at EOF now */ bool eof; }; typedef const char * istream_sized_callback_t(const struct istream_sized_error_data *data, void *context); /* Assume that input stream is exactly the given size. If the stream is too small, fail with stream_errno=EPIPE. If stream is too large, fail with stream_errno=EINVAL. */ struct istream *i_stream_create_sized(struct istream *input, uoff_t size); struct istream *i_stream_create_sized_range(struct istream *input, uoff_t offset, uoff_t size); /* Like i_stream_create_sized*(), but allow input stream's size to be larger. */ struct istream *i_stream_create_min_sized(struct istream *input, uoff_t min_size); struct istream *i_stream_create_min_sized_range(struct istream *input, uoff_t offset, uoff_t min_size); /* Same as i_stream_create_sized(), but set the error message via the callback. */ struct istream * i_stream_create_sized_with_callback(struct istream *input, uoff_t size, istream_sized_callback_t *error_callback, void *context); #define i_stream_create_sized_with_callback(input, size, error_callback, context) \ i_stream_create_sized_with_callback(input, size - \ CALLBACK_TYPECHECK(error_callback, \ const char *(*)(const struct istream_sized_error_data *, typeof(context))), \ (istream_sized_callback_t *)error_callback, context) #endif dovecot-2.3.21.1/src/lib/test-event-filter.c0000644000000000000000000005013514656633576015403 00000000000000/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "ioloop.h" #include "event-filter-private.h" static void test_event_filter_override_parent_fields(void) { struct event_filter *filter; const char *error; const struct failure_context failure_ctx = { .type = LOG_TYPE_DEBUG }; test_begin("event filter: override parent fields"); struct event *parent = event_create(NULL); event_add_str(parent, "str", "parent_str"); event_add_str(parent, "parent_str", "parent_str"); event_add_int(parent, "int1", 0); event_add_int(parent, "int2", 5); event_add_int(parent, "parent_int", 6); struct event *child = event_create(parent); event_add_str(child, "str", "child_str"); event_add_str(child, "child_str", "child_str"); event_add_int(child, "int1", 6); event_add_int(child, "int2", 0); event_add_int(child, "child_int", 8); /* parent matches: test a mix of parent/child fields */ filter = event_filter_create(); test_assert(event_filter_parse("str=parent_str AND int1=0 AND int2=5", filter, &error) == 0); test_assert(event_filter_match(filter, parent, &failure_ctx)); test_assert(!event_filter_match(filter, child, &failure_ctx)); event_filter_unref(&filter); /* parent matches: test fields that exist only in parent */ filter = event_filter_create(); test_assert(event_filter_parse("parent_str=parent_str AND parent_int=6", filter, &error) == 0); test_assert(event_filter_match(filter, parent, &failure_ctx)); test_assert(event_filter_match(filter, child, &failure_ctx)); event_filter_unref(&filter); /* child matches: test a mix of parent/child fields */ filter = event_filter_create(); test_assert(event_filter_parse("str=child_str AND int1=6 AND int2=0", filter, &error) == 0); test_assert(event_filter_match(filter, child, &failure_ctx)); test_assert(!event_filter_match(filter, parent, &failure_ctx)); event_filter_unref(&filter); /* child matches: test fields that exist only in child */ filter = event_filter_create(); test_assert(event_filter_parse("child_str=child_str AND child_int=8", filter, &error) == 0); test_assert(event_filter_match(filter, child, &failure_ctx)); test_assert(!event_filter_match(filter, parent, &failure_ctx)); event_filter_unref(&filter); event_unref(&parent); event_unref(&child); test_end(); } static void test_event_filter_override_global_fields(void) { struct event_filter *filter; const char *error; const struct failure_context failure_ctx = { .type = LOG_TYPE_DEBUG }; test_begin("event filter: override global fields"); struct event *global = event_create(NULL); event_add_str(global, "str", "global_str"); event_add_str(global, "global_str", "global_str"); event_add_int(global, "int1", 0); event_add_int(global, "int2", 5); event_add_int(global, "global_int", 6); event_push_global(global); struct event *local = event_create(NULL); event_add_str(local, "str", "local_str"); event_add_str(local, "local_str", "local_str"); event_add_int(local, "int1", 6); event_add_int(local, "int2", 0); event_add_int(local, "local_int", 8); /* global matches: test a mix of global/local fields */ filter = event_filter_create(); test_assert(event_filter_parse("str=global_str AND int1=0 AND int2=5", filter, &error) == 0); test_assert(event_filter_match(filter, global, &failure_ctx)); test_assert(!event_filter_match(filter, local, &failure_ctx)); event_filter_unref(&filter); /* global matches: test fields that exist only in global */ filter = event_filter_create(); test_assert(event_filter_parse("global_str=global_str AND global_int=6", filter, &error) == 0); test_assert(event_filter_match(filter, global, &failure_ctx)); test_assert(event_filter_match(filter, local, &failure_ctx)); event_filter_unref(&filter); /* local matches: test a mix of global/local fields */ filter = event_filter_create(); test_assert(event_filter_parse("str=local_str AND int1=6 AND int2=0", filter, &error) == 0); test_assert(event_filter_match(filter, local, &failure_ctx)); test_assert(!event_filter_match(filter, global, &failure_ctx)); event_filter_unref(&filter); /* local matches: test fields that exist only in local */ filter = event_filter_create(); test_assert(event_filter_parse("local_str=local_str AND local_int=8", filter, &error) == 0); test_assert(event_filter_match(filter, local, &failure_ctx)); test_assert(!event_filter_match(filter, global, &failure_ctx)); event_filter_unref(&filter); event_pop_global(global); event_unref(&global); event_unref(&local); test_end(); } static void test_event_filter_clear_parent_fields(void) { struct event_filter *filter; const char *error; const struct failure_context failure_ctx = { .type = LOG_TYPE_DEBUG }; const char *keys[] = { "str", "int" }; test_begin("event filter: clear parent fields"); struct event *parent = event_create(NULL); event_add_str(parent, "str", "parent_str"); event_add_int(parent, "int", 0); struct event *child = event_create(parent); event_field_clear(child, "str"); event_field_clear(child, "int"); for (unsigned int i = 0; i < N_ELEMENTS(keys); i++) { /* match any value */ const char *query = t_strdup_printf("%s=*", keys[i]); filter = event_filter_create(); test_assert(event_filter_parse(query, filter, &error) == 0); test_assert_idx(event_filter_match(filter, parent, &failure_ctx), i); test_assert_idx(!event_filter_match(filter, child, &failure_ctx), i); event_filter_unref(&filter); } /* match empty field */ filter = event_filter_create(); test_assert(event_filter_parse("str=\"\"", filter, &error) == 0); test_assert(!event_filter_match(filter, parent, &failure_ctx)); test_assert(event_filter_match(filter, child, &failure_ctx)); event_filter_unref(&filter); /* match nonexistent field */ filter = event_filter_create(); test_assert(event_filter_parse("nonexistent=\"\"", filter, &error) == 0); test_assert(event_filter_match(filter, parent, &failure_ctx)); test_assert(event_filter_match(filter, child, &failure_ctx)); event_filter_unref(&filter); event_unref(&parent); event_unref(&child); test_end(); } static void test_event_filter_clear_global_fields(void) { struct event_filter *filter; const char *error; const struct failure_context failure_ctx = { .type = LOG_TYPE_DEBUG }; const char *keys[] = { "str", "int" }; test_begin("event filter: clear global fields"); struct event *global = event_create(NULL); event_add_str(global, "str", "global_str"); event_add_int(global, "int", 0); event_push_global(global); struct event *local = event_create(NULL); event_field_clear(local, "str"); event_field_clear(local, "int"); for (unsigned int i = 0; i < N_ELEMENTS(keys); i++) { /* match any value */ const char *query = t_strdup_printf("%s=*", keys[i]); filter = event_filter_create(); test_assert(event_filter_parse(query, filter, &error) == 0); test_assert_idx(event_filter_match(filter, global, &failure_ctx), i); test_assert_idx(!event_filter_match(filter, local, &failure_ctx), i); event_filter_unref(&filter); } /* match empty field */ filter = event_filter_create(); test_assert(event_filter_parse("str=\"\"", filter, &error) == 0); test_assert(!event_filter_match(filter, global, &failure_ctx)); test_assert(event_filter_match(filter, local, &failure_ctx)); event_filter_unref(&filter); /* match nonexistent field */ filter = event_filter_create(); test_assert(event_filter_parse("nonexistent=\"\"", filter, &error) == 0); test_assert(event_filter_match(filter, global, &failure_ctx)); test_assert(event_filter_match(filter, local, &failure_ctx)); event_filter_unref(&filter); event_pop_global(global); event_unref(&global); event_unref(&local); test_end(); } static void test_event_filter_inc_int(void) { struct event_filter *filter; const char *error; const struct failure_context failure_ctx = { .type = LOG_TYPE_DEBUG }; test_begin("event filter: create and update keys with event_inc_int"); struct event *root = event_create(NULL); filter = event_filter_create(); test_assert(event_filter_parse("int=14", filter, &error) == 0); const struct event_field *f = event_find_field_recursive(root, "int"); i_assert(f == NULL); test_assert(!event_filter_match(filter, root, &failure_ctx)); event_inc_int(root, "int", 7); test_assert(!event_filter_match(filter, root, &failure_ctx)); f = event_find_field_recursive(root, "int"); i_assert(f != NULL); test_assert_strcmp(f->key, "int"); test_assert(f->value_type == EVENT_FIELD_VALUE_TYPE_INTMAX); test_assert(f->value.intmax == 7); event_inc_int(root, "int", 7); test_assert(event_filter_match(filter, root, &failure_ctx)); f = event_find_field_recursive(root, "int"); i_assert(f != NULL); test_assert_strcmp(f->key, "int"); test_assert(f->value_type == EVENT_FIELD_VALUE_TYPE_INTMAX); test_assert(f->value.intmax == 14); event_filter_unref(&filter); event_unref(&root); test_end(); } static void test_event_filter_parent_category_match(void) { static struct event_category parent_category = { .name = "parent", }; static struct event_category child_category = { .parent = &parent_category, .name = "child", }; struct event_filter *filter; const char *error; const struct failure_context failure_ctx = { .type = LOG_TYPE_DEBUG }; test_begin("event filter: parent category match"); struct event *e = event_create(NULL); event_add_category(e, &child_category); filter = event_filter_create(); test_assert(event_filter_parse("category=parent", filter, &error) == 0); test_assert(event_filter_match(filter, e, &failure_ctx)); event_filter_unref(&filter); event_unref(&e); test_end(); } static void test_event_filter_strlist(void) { struct event_filter *filter; const struct failure_context failure_ctx = { .type = LOG_TYPE_DEBUG }; test_begin("event filter: match string list"); struct event *e = event_create(NULL); filter = event_filter_create(); /* should match empty list */ event_filter_parse("abc=\"\"", filter, NULL); test_assert(event_filter_match(filter, e, &failure_ctx)); /* should still be empty */ event_strlist_append(e, "abc", NULL); test_assert(event_filter_match(filter, e, &failure_ctx)); /* should not match non-empty list */ event_strlist_append(e, "abc", "one"); test_assert(!event_filter_match(filter, e, &failure_ctx)); event_filter_unref(&filter); /* should match non-empty list that has value 'one' */ filter = event_filter_create(); event_strlist_append(e, "abc", "two"); event_filter_parse("abc=one", filter, NULL); test_assert(event_filter_match(filter, e, &failure_ctx)); event_filter_unref(&filter); /* should match non-empty list that has no value 'three' */ filter = event_filter_create(); event_filter_parse("abc=one AND NOT abc=three", filter, NULL); test_assert(event_filter_match(filter, e, &failure_ctx)); event_filter_unref(&filter); event_unref(&e); test_end(); } static void test_event_filter_strlist_recursive(void) { struct event_filter *filter; const struct failure_context failure_ctx = { .type = LOG_TYPE_DEBUG }; test_begin("event filter: match string list - recursive"); struct event *parent = event_create(NULL); struct event *e = event_create(parent); /* empty filter: parent is non-empty */ filter = event_filter_create(); event_filter_parse("list1=\"\"", filter, NULL); test_assert(event_filter_match(filter, e, &failure_ctx)); event_strlist_append(parent, "list1", "foo"); test_assert(!event_filter_match(filter, e, &failure_ctx)); event_filter_unref(&filter); /* matching filter: matches parent */ filter = event_filter_create(); event_filter_parse("list2=parent", filter, NULL); /* empty: */ test_assert(!event_filter_match(filter, e, &failure_ctx)); /* set parent but no child: */ event_strlist_append(parent, "list2", "parent"); test_assert(event_filter_match(filter, e, &failure_ctx)); /* set child to non-matching: */ event_strlist_append(e, "list2", "child"); test_assert(event_filter_match(filter, e, &failure_ctx)); event_filter_unref(&filter); /* matching filter: matches child */ filter = event_filter_create(); event_filter_parse("list3=child", filter, NULL); /* empty: */ test_assert(!event_filter_match(filter, e, &failure_ctx)); /* set child but no parent: */ event_strlist_append(e, "list3", "child"); test_assert(event_filter_match(filter, e, &failure_ctx)); /* set parent to non-matching: */ event_strlist_append(e, "list3", "parent"); test_assert(event_filter_match(filter, e, &failure_ctx)); event_filter_unref(&filter); event_unref(&e); event_unref(&parent); test_end(); } static void test_event_filter_strlist_global_events(void) { struct event_filter *filter; const struct failure_context failure_ctx = { .type = LOG_TYPE_DEBUG }; test_begin("event filter: match string list - global events"); struct event *global = event_create(NULL); event_push_global(global); struct event *e = event_create(NULL); /* empty filter: global is non-empty */ filter = event_filter_create(); event_filter_parse("list1=\"\"", filter, NULL); test_assert(event_filter_match(filter, e, &failure_ctx)); event_strlist_append(global, "list1", "foo"); test_assert(!event_filter_match(filter, e, &failure_ctx)); event_filter_unref(&filter); /* matching filter: matches global */ filter = event_filter_create(); event_filter_parse("list2=global", filter, NULL); /* empty: */ test_assert(!event_filter_match(filter, e, &failure_ctx)); /* set global but no local: */ event_strlist_append(global, "list2", "global"); test_assert(event_filter_match(filter, e, &failure_ctx)); /* set local to non-matching: */ event_strlist_append(e, "list2", "local"); test_assert(event_filter_match(filter, e, &failure_ctx)); event_filter_unref(&filter); /* matching filter: matches local */ filter = event_filter_create(); event_filter_parse("list3=local", filter, NULL); /* empty: */ test_assert(!event_filter_match(filter, e, &failure_ctx)); /* set local but no global: */ event_strlist_append(e, "list3", "local"); test_assert(event_filter_match(filter, e, &failure_ctx)); /* set global to non-matching: */ event_strlist_append(e, "list3", "global"); test_assert(event_filter_match(filter, e, &failure_ctx)); event_filter_unref(&filter); event_unref(&e); event_pop_global(global); event_unref(&global); test_end(); } static void test_event_filter_named_and_str(void) { struct event_filter *filter; const char *error; const struct failure_context failure_ctx = { .type = LOG_TYPE_DEBUG }; test_begin("event filter: event name and str"); filter = event_filter_create(); struct event *e_noname_nostr = event_create(NULL); struct event *e_noname_str = event_create(NULL); event_add_str(e_noname_str, "str", "str"); struct event *e_noname_wrongstr = event_create(NULL); event_add_str(e_noname_wrongstr, "str", "wrong"); struct event *e_named_nostr = event_create(NULL); event_set_name(e_named_nostr, "named"); struct event *e_named_str = event_create(NULL); event_set_name(e_named_str, "named"); event_add_str(e_named_str, "str", "str"); struct event *e_named_wrongstr = event_create(NULL); event_set_name(e_named_wrongstr, "named"); event_add_str(e_named_wrongstr, "str", "wrong"); struct event *e_wrongname_nostr = event_create(NULL); event_set_name(e_wrongname_nostr, "wrong"); struct event *e_wrongname_str = event_create(NULL); event_set_name(e_wrongname_str, "wrong"); event_add_str(e_wrongname_str, "str", "str"); struct event *e_wrongname_wrongstr = event_create(NULL); event_set_name(e_wrongname_wrongstr, "wrong"); event_add_str(e_wrongname_wrongstr, "str", "wrong"); test_assert(event_filter_parse("event=named AND str=str", filter, &error) == 0); test_assert(filter->named_queries_only); test_assert(!event_filter_match(filter, e_noname_nostr, &failure_ctx)); test_assert(!event_filter_match(filter, e_noname_str, &failure_ctx)); test_assert(!event_filter_match(filter, e_noname_wrongstr, &failure_ctx)); test_assert(!event_filter_match(filter, e_named_nostr, &failure_ctx)); test_assert(event_filter_match(filter, e_named_str, &failure_ctx)); test_assert(!event_filter_match(filter, e_named_wrongstr, &failure_ctx)); test_assert(!event_filter_match(filter, e_wrongname_nostr, &failure_ctx)); test_assert(!event_filter_match(filter, e_wrongname_str, &failure_ctx)); test_assert(!event_filter_match(filter, e_wrongname_wrongstr, &failure_ctx)); event_filter_unref(&filter); event_unref(&e_noname_nostr); event_unref(&e_noname_str); event_unref(&e_noname_wrongstr); event_unref(&e_named_nostr); event_unref(&e_named_str); event_unref(&e_named_wrongstr); event_unref(&e_wrongname_nostr); event_unref(&e_wrongname_str); event_unref(&e_wrongname_wrongstr); test_end(); } static void test_event_filter_named_or_str(void) { struct event_filter *filter; const char *error; const struct failure_context failure_ctx = { .type = LOG_TYPE_DEBUG }; test_begin("event filter: event name or str"); filter = event_filter_create(); struct event *e_noname_nostr = event_create(NULL); struct event *e_noname_str = event_create(NULL); event_add_str(e_noname_str, "str", "str"); struct event *e_noname_wrongstr = event_create(NULL); event_add_str(e_noname_wrongstr, "str", "wrong"); struct event *e_named_nostr = event_create(NULL); event_set_name(e_named_nostr, "named"); struct event *e_named_str = event_create(NULL); event_set_name(e_named_str, "named"); event_add_str(e_named_str, "str", "str"); struct event *e_named_wrongstr = event_create(NULL); event_set_name(e_named_wrongstr, "named"); event_add_str(e_named_wrongstr, "str", "wrong"); struct event *e_wrongname_nostr = event_create(NULL); event_set_name(e_wrongname_nostr, "wrong"); struct event *e_wrongname_str = event_create(NULL); event_set_name(e_wrongname_str, "wrong"); event_add_str(e_wrongname_str, "str", "str"); struct event *e_wrongname_wrongstr = event_create(NULL); event_set_name(e_wrongname_wrongstr, "wrong"); event_add_str(e_wrongname_wrongstr, "str", "wrong"); test_assert(event_filter_parse("event=named OR str=str", filter, &error) == 0); test_assert(!filter->named_queries_only); test_assert(!event_filter_match(filter, e_noname_nostr, &failure_ctx)); test_assert(event_filter_match(filter, e_noname_str, &failure_ctx)); test_assert(!event_filter_match(filter, e_noname_wrongstr, &failure_ctx)); test_assert(event_filter_match(filter, e_named_nostr, &failure_ctx)); test_assert(event_filter_match(filter, e_named_str, &failure_ctx)); test_assert(event_filter_match(filter, e_named_wrongstr, &failure_ctx)); test_assert(!event_filter_match(filter, e_wrongname_nostr, &failure_ctx)); test_assert(event_filter_match(filter, e_wrongname_str, &failure_ctx)); test_assert(!event_filter_match(filter, e_wrongname_wrongstr, &failure_ctx)); event_filter_unref(&filter); event_unref(&e_noname_nostr); event_unref(&e_noname_str); event_unref(&e_noname_wrongstr); event_unref(&e_named_nostr); event_unref(&e_named_str); event_unref(&e_named_wrongstr); event_unref(&e_wrongname_nostr); event_unref(&e_wrongname_str); event_unref(&e_wrongname_wrongstr); test_end(); } static void test_event_filter_named_separate_from_str(void) { struct event_filter *filter; const char *error; const struct failure_context failure_ctx = { .type = LOG_TYPE_DEBUG }; test_begin("event filter: event name separate from str"); filter = event_filter_create(); struct event *e_named = event_create(NULL); event_set_name(e_named, "named"); struct event *e_noname = event_create(NULL); event_add_str(e_noname, "str", "str"); test_assert(event_filter_parse("event=named", filter, &error) == 0); test_assert(event_filter_parse("str=str", filter, &error) == 0); test_assert(!filter->named_queries_only); test_assert(event_filter_match(filter, e_named, &failure_ctx)); test_assert(event_filter_match(filter, e_noname, &failure_ctx)); event_filter_unref(&filter); event_unref(&e_named); event_unref(&e_noname); test_end(); } void test_event_filter(void) { test_event_filter_override_parent_fields(); test_event_filter_override_global_fields(); test_event_filter_clear_parent_fields(); test_event_filter_clear_global_fields(); test_event_filter_inc_int(); test_event_filter_parent_category_match(); test_event_filter_strlist(); test_event_filter_strlist_recursive(); test_event_filter_strlist_global_events(); test_event_filter_named_and_str(); test_event_filter_named_or_str(); test_event_filter_named_separate_from_str(); } dovecot-2.3.21.1/src/lib/test-memarea.c0000644000000000000000000000177614656633576014415 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "buffer.h" #include "memarea.h" static bool test_callback_called = FALSE; static void test_callback(buffer_t *buf) { test_assert(!test_callback_called); test_callback_called = TRUE; buffer_free(&buf); } void test_memarea(void) { struct memarea *area, *area2; buffer_t *buf; size_t size; test_begin("memarea"); buf = buffer_create_dynamic(default_pool, 128); buffer_append(buf, "123", 3); area = memarea_init(buf->data, buf->used, test_callback, buf); test_assert(memarea_get_refcount(area) == 1); test_assert(memarea_get(area, &size) == buf->data && size == buf->used); area2 = area; memarea_ref(area2); test_assert(memarea_get_refcount(area2) == 2); test_assert(memarea_get(area2, &size) == buf->data && size == buf->used); memarea_unref(&area2); test_assert(area2 == NULL); test_assert(!test_callback_called); memarea_unref(&area); test_assert(test_callback_called); test_end(); } dovecot-2.3.21.1/src/lib/nfs-workarounds.c0000644000000000000000000002364214656633576015167 00000000000000/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ /* These tests were done with various Linux 2.6 kernels, FreeBSD 6.2 and Solaris 8 and 10. Attribute cache is usually flushed with chown()ing or fchown()ing the file. The safest way would be to use uid=-1 gid=-1, but this doesn't work with Linux (it does with FreeBSD 6.2 and Solaris). So we'll first get the file's owner and use it. As long as we're not root the file's owner can't change accidentally. If would be possible to also use chmod()/fchmod(), but that's riskier since it could actually cause an unwanted change. Write cache can be flushed with fdatasync(). It's all we need, but other tested alternatives are: fcntl locking (Linux 2.6, Solaris), fchown() (Solaris) and dup()+close() (Linux 2.6, Solaris). Read cache flushing is more problematic. There's no universal way to do it. The working methods are: Linux 2.6: fcntl(), O_DIRECT Solaris: fchown(), fcntl(), dup()+close() FreeBSD 6.2: fchown() fchown() can be easily used for Solaris and FreeBSD, but Linux requires playing with locks. O_DIRECT requires CONFIG_NFS_DIRECTIO to be enabled, so we can't always use it. */ #include "lib.h" #include "path-util.h" #include "nfs-workarounds.h" #include #include #include #if defined (__linux__) || defined(__sun) # define READ_CACHE_FLUSH_FCNTL #endif #if defined(__FreeBSD__) || defined(__sun) # define ATTRCACHE_FLUSH_CHOWN_UID_1 #endif static void nfs_flush_file_handle_cache_parent_dir(const char *path); static int nfs_safe_do(const char *path, int (*callback)(const char *path, void *context), void *context) { unsigned int i; int ret; for (i = 1;; i++) { ret = callback(path, context); if (ret == 0 || errno != ESTALE || i == NFS_ESTALE_RETRY_COUNT) break; /* ESTALE: Some operating systems may fail with this if they can't internally revalidate the NFS file handle. Flush the file handle and try again */ nfs_flush_file_handle_cache(path); } return ret; } struct nfs_safe_open_context { int flags; int fd; }; static int nfs_safe_open_callback(const char *path, void *context) { struct nfs_safe_open_context *ctx = context; ctx->fd = open(path, ctx->flags); return ctx->fd == -1 ? -1 : 0; } int nfs_safe_open(const char *path, int flags) { struct nfs_safe_open_context ctx; i_assert((flags & O_CREAT) == 0); ctx.flags = flags; if (nfs_safe_do(path, nfs_safe_open_callback, &ctx) < 0) return -1; return ctx.fd; } static int nfs_safe_stat_callback(const char *path, void *context) { struct stat *buf = context; return stat(path, buf); } int nfs_safe_stat(const char *path, struct stat *buf) { return nfs_safe_do(path, nfs_safe_stat_callback, buf); } static int nfs_safe_lstat_callback(const char *path, void *context) { struct stat *buf = context; return lstat(path, buf); } int nfs_safe_lstat(const char *path, struct stat *buf) { return nfs_safe_do(path, nfs_safe_lstat_callback, buf); } int nfs_safe_link(const char *oldpath, const char *newpath, bool links1) { struct stat st; nlink_t orig_link_count = 1; if (!links1) { if (stat(oldpath, &st) < 0) return -1; orig_link_count = st.st_nlink; } if (link(oldpath, newpath) == 0) { #ifndef __FreeBSD__ return 0; #endif /* FreeBSD at least up to v6.2 converts EEXIST errors to success. */ } else if (errno != EEXIST) return -1; /* We don't know if it succeeded or failed. stat() to make sure. */ if (stat(oldpath, &st) < 0) return -1; if (st.st_nlink == orig_link_count) { errno = EEXIST; return -1; } return 0; } static void nfs_flush_chown_uid(const char *path) { #ifdef ATTRCACHE_FLUSH_CHOWN_UID_1 uid_t uid = (uid_t)-1; if (chown(path, uid, (gid_t)-1) < 0) { if (errno == ESTALE || errno == EPERM || errno == ENOENT) { /* attr cache is flushed */ return; } if (likely(errno == ENOENT)) { nfs_flush_file_handle_cache_parent_dir(path); return; } i_error("nfs_flush_chown_uid: chown(%s) failed: %m", path); } #else struct stat st; if (stat(path, &st) == 0) { /* do nothing */ } else { if (errno == ESTALE) { /* ESTALE causes the OS to flush the attr cache */ return; } if (likely(errno == ENOENT)) { nfs_flush_file_handle_cache_parent_dir(path); return; } i_error("nfs_flush_chown_uid: stat(%s) failed: %m", path); return; } /* we use chmod for this operation since chown has been seen to drop S_UID and S_GID bits from directory inodes in certain conditions */ if (chmod(path, st.st_mode & 07777) < 0) { if (errno == EPERM) { /* attr cache is flushed */ return; } if (likely(errno == ENOENT)) { nfs_flush_file_handle_cache_parent_dir(path); return; } i_error("nfs_flush_chown_uid: chmod(%s, %04o) failed: %m", path, st.st_mode & 07777); } #endif } #ifdef __FreeBSD__ static bool nfs_flush_fchown_uid(const char *path, int fd) { uid_t uid; #ifndef ATTRCACHE_FLUSH_CHOWN_UID_1 struct stat st; if (fstat(fd, &st) < 0) { if (likely(errno == ESTALE)) return FALSE; i_error("nfs_flush_attr_cache_fchown: fstat(%s) failed: %m", path); return TRUE; } uid = st.st_uid; #else uid = (uid_t)-1; #endif if (fchown(fd, uid, (gid_t)-1) < 0) { if (errno == ESTALE) return FALSE; if (likely(errno == EACCES || errno == EPERM)) { /* attr cache is flushed */ return TRUE; } i_error("nfs_flush_attr_cache_fd_locked: fchown(%s) failed: %m", path); } return TRUE; } #endif #ifdef READ_CACHE_FLUSH_FCNTL static bool nfs_flush_fcntl(const char *path, int fd) { static bool locks_disabled = FALSE; struct flock fl; int ret; if (locks_disabled) return FALSE; /* If the file was already locked, we'll just get the same lock again. It should succeed just fine. If was was unlocked, we'll have to get a lock and then unlock it. Linux 2.6 flushes read cache only when read/write locking succeeded. */ fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; alarm(60); ret = fcntl(fd, F_SETLKW, &fl); alarm(0); if (unlikely(ret < 0)) { if (errno == ENOLCK) { locks_disabled = TRUE; return FALSE; } i_error("nfs_flush_fcntl: fcntl(%s, F_RDLCK) failed: %m", path); return FALSE; } fl.l_type = F_UNLCK; (void)fcntl(fd, F_SETLKW, &fl); return TRUE; } #endif void nfs_flush_attr_cache_unlocked(const char *path) { int fd; /* Try to flush the attribute cache the nice way first. */ fd = open(path, O_RDONLY); if (fd != -1) i_close_fd(&fd); else if (errno == ESTALE) { /* this already flushed the cache */ } else { /* most likely ENOENT, which means a negative cache hit. flush the file handles for its parent directory. */ nfs_flush_file_handle_cache_parent_dir(path); } } void nfs_flush_attr_cache_maybe_locked(const char *path) { nfs_flush_chown_uid(path); } void nfs_flush_attr_cache_fd_locked(const char *path ATTR_UNUSED, int fd ATTR_UNUSED) { #ifdef __FreeBSD__ /* FreeBSD doesn't flush attribute cache with fcntl(), so we have to do it ourself. */ (void)nfs_flush_fchown_uid(path, fd); #else /* Linux and Solaris are fine. */ #endif } static bool nfs_flush_file_handle_cache_dir(const char *path, bool try_parent ATTR_UNUSED) { #ifdef __linux__ /* chown()ing parent is the safest way to handle this */ nfs_flush_chown_uid(path); #else /* rmdir() is the only choice with FreeBSD and Solaris */ if (unlikely(rmdir(path) == 0)) { if (mkdir(path, 0700) == 0) { i_warning("nfs_flush_file_handle_cache_dir: " "rmdir(%s) unexpectedly " "removed the dir. recreated.", path); } else { i_warning("nfs_flush_file_handle_cache_dir: " "rmdir(%s) unexpectedly " "removed the dir. mkdir() failed: %m", path); } } else if (errno == ESTALE || errno == ENOTDIR || errno == ENOTEMPTY || errno == EEXIST || errno == EACCES) { /* expected failures */ } else if (errno == ENOENT) { return FALSE; } else if (errno == EINVAL && try_parent) { /* Solaris gives this if we're trying to rmdir() the current directory. Work around this by temporarily changing the current directory to the parent directory. */ const char *cur_path, *p; int cur_dir_fd; bool ret; cur_dir_fd = open(".", O_RDONLY); if (cur_dir_fd == -1) { i_error("open(.) failed for: %m"); return TRUE; } const char *error; if (t_get_working_dir(&cur_path, &error) < 0) { i_error("nfs_flush_file_handle_cache_dir: %s", error); i_close_fd(&cur_dir_fd); return TRUE; } p = strrchr(cur_path, '/'); if (p == NULL) cur_path = "/"; else cur_path = t_strdup_until(cur_path, p); if (chdir(cur_path) < 0) { i_error("nfs_flush_file_handle_cache_dir: " "chdir() failed"); } ret = nfs_flush_file_handle_cache_dir(path, FALSE); if (fchdir(cur_dir_fd) < 0) i_error("fchdir() failed: %m"); i_close_fd(&cur_dir_fd); return ret; } else { i_error("nfs_flush_file_handle_cache_dir: " "rmdir(%s) failed: %m", path); } #endif return TRUE; } static void nfs_flush_file_handle_cache_parent_dir(const char *path) { const char *p; p = strrchr(path, '/'); T_BEGIN { if (p == NULL) (void)nfs_flush_file_handle_cache_dir(".", TRUE); else (void)nfs_flush_file_handle_cache_dir(t_strdup_until(path, p), TRUE); } T_END; } void nfs_flush_file_handle_cache(const char *path) { nfs_flush_file_handle_cache_parent_dir(path); } void nfs_flush_read_cache_locked(const char *path ATTR_UNUSED, int fd ATTR_UNUSED) { #ifdef READ_CACHE_FLUSH_FCNTL /* already flushed when fcntl() was called */ #else /* we can only hope that underlying filesystem uses micro/nanosecond resolution so that attribute cache flushing notices mtime changes */ nfs_flush_attr_cache_fd_locked(path, fd); #endif } void nfs_flush_read_cache_unlocked(const char *path, int fd) { #ifdef READ_CACHE_FLUSH_FCNTL if (!nfs_flush_fcntl(path, fd)) nfs_flush_attr_cache_fd_locked(path, fd); #else nfs_flush_read_cache_locked(path, fd); #endif } dovecot-2.3.21.1/src/lib/read-full.h0000644000000000000000000000043514656633576013700 00000000000000#ifndef READ_FULL_H #define READ_FULL_H /* Read data from file. Returns -1 if error occurred, or 0 if EOF came before everything was read, or 1 if all was ok. */ int read_full(int fd, void *data, size_t size); int pread_full(int fd, void *data, size_t size, off_t offset); #endif dovecot-2.3.21.1/src/lib/hash2.c0000644000000000000000000001377314656633576013036 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "primes.h" #include "hash2.h" #define HASH_TABLE_MIN_SIZE 131 struct hash2_value { struct hash2_value *next; unsigned int key_hash; /* user_data[value_size] */ }; ARRAY_DEFINE_TYPE(hash2_value, struct hash2_value *); struct hash2_table { unsigned int count; unsigned int initial_size; unsigned int hash_table_size; unsigned int value_size; pool_t value_pool; struct hash2_value *deleted_values; ARRAY_TYPE(hash2_value) hash_table; hash2_key_callback_t *key_hash_cb; hash2_cmp_callback_t *key_compare_cb; void *context; }; static void hash2_alloc_table(struct hash2_table *hash, unsigned int size) { hash->hash_table_size = size; i_array_init(&hash->hash_table, hash->hash_table_size); (void)array_idx_get_space(&hash->hash_table, hash->hash_table_size-1); } struct hash2_table * hash2_create(unsigned int initial_size, unsigned int value_size, hash2_key_callback_t *key_hash_cb, hash2_cmp_callback_t *key_compare_cb, void *context) { struct hash2_table *hash; hash = i_new(struct hash2_table, 1); hash->initial_size = I_MAX(initial_size, HASH_TABLE_MIN_SIZE); hash->value_size = value_size; hash->key_hash_cb = key_hash_cb; hash->key_compare_cb = key_compare_cb; hash->context = context; hash->value_pool = pool_alloconly_create("hash2 value pool", 16384); hash2_alloc_table(hash, hash->initial_size); return hash; } void hash2_destroy(struct hash2_table **_hash) { struct hash2_table *hash = *_hash; *_hash = NULL; array_free(&hash->hash_table); pool_unref(&hash->value_pool); i_free(hash); } void hash2_clear(struct hash2_table *hash) { array_free(&hash->hash_table); hash2_alloc_table(hash, hash->initial_size); p_clear(hash->value_pool); hash->count = 0; hash->deleted_values = NULL; } static void hash2_resize(struct hash2_table *hash, bool grow) { ARRAY_TYPE(hash2_value) old_hash_table; struct hash2_value *old_hash, *value, **valuep, *next; unsigned int next_size, old_count, i, idx; float nodes_per_list; nodes_per_list = (float)hash->count / (float)hash->hash_table_size; if (nodes_per_list > 0.3 && nodes_per_list < 2.0) return; next_size = I_MAX(primes_closest(hash->count + 1), hash->initial_size); if (hash->hash_table_size >= next_size && (grow || next_size == hash->hash_table_size)) return; old_hash_table = hash->hash_table; hash2_alloc_table(hash, next_size); old_count = array_count(&old_hash_table); for (i = 0; i < old_count; i++) { old_hash = array_idx_elem(&old_hash_table, i); for (value = old_hash; value != NULL; value = next) { next = value->next; idx = value->key_hash % hash->hash_table_size; valuep = array_idx_modifiable(&hash->hash_table, idx); value->next = *valuep; *valuep = value; } } array_free(&old_hash_table); } void *hash2_lookup(const struct hash2_table *hash, const void *key) { unsigned int key_hash = hash->key_hash_cb(key); struct hash2_value *value; void *user_value; value = array_idx_elem(&hash->hash_table, key_hash % hash->hash_table_size); while (value != NULL) { if (value->key_hash == key_hash) { user_value = value + 1; if (hash->key_compare_cb(key, user_value, hash->context)) return user_value; } value = value->next; } return NULL; } void *hash2_iterate(const struct hash2_table *hash, unsigned int key_hash, struct hash2_iter *iter) { struct hash2_value *value; if (iter->value == NULL) { iter->key_hash = key_hash; value = array_idx_elem(&hash->hash_table, key_hash % hash->hash_table_size); iter->next_value = value; } while (iter->next_value != NULL) { if (iter->next_value->key_hash == key_hash) { iter->value = iter->next_value; iter->next_value = iter->next_value->next; return iter->value + 1; } iter->next_value = iter->next_value->next; } return NULL; } void *hash2_insert(struct hash2_table *hash, const void *key) { return hash2_insert_hash(hash, hash->key_hash_cb(key)); } void *hash2_insert_hash(struct hash2_table *hash, unsigned int key_hash) { struct hash2_value *value, **valuep; hash2_resize(hash, TRUE); if (hash->deleted_values != NULL) { value = hash->deleted_values; hash->deleted_values = value->next; value->next = NULL; memset(value + 1, 0, hash->value_size); } else { value = p_malloc(hash->value_pool, sizeof(*value) + hash->value_size); } value->key_hash = key_hash; valuep = array_idx_modifiable(&hash->hash_table, key_hash % hash->hash_table_size); value->next = *valuep; *valuep = value; hash->count++; return value + 1; } static void hash2_remove_value_p(struct hash2_table *hash, struct hash2_value **valuep) { struct hash2_value *deleted_value; deleted_value = *valuep; *valuep = deleted_value->next; deleted_value->next = hash->deleted_values; hash->deleted_values = deleted_value; hash->count--; } void hash2_remove(struct hash2_table *hash, const void *key) { unsigned int key_hash = hash->key_hash_cb(key); struct hash2_value **valuep; valuep = array_idx_modifiable(&hash->hash_table, key_hash % hash->hash_table_size); while (*valuep != NULL) { if ((*valuep)->key_hash == key_hash && hash->key_compare_cb(key, (*valuep) + 1, hash->context)) { hash2_remove_value_p(hash, valuep); hash2_resize(hash, FALSE); return; } valuep = &(*valuep)->next; } i_panic("hash2_remove(): key not found"); } void hash2_remove_iter(struct hash2_table *hash, struct hash2_iter *iter) { struct hash2_value **valuep, *next; valuep = array_idx_modifiable(&hash->hash_table, iter->key_hash % hash->hash_table_size); while (*valuep != NULL) { if (*valuep == iter->value) { next = (*valuep)->next; /* don't allow resizing, otherwise iterating would break completely */ hash2_remove_value_p(hash, valuep); iter->next_value = next; return; } valuep = &(*valuep)->next; } i_panic("hash2_remove_value(): key/value not found"); } unsigned int hash2_count(const struct hash2_table *hash) { return hash->count; } dovecot-2.3.21.1/src/lib/nfs-workarounds.h0000644000000000000000000000352714656633576015174 00000000000000#ifndef NFS_WORKAROUNDS_H #define NFS_WORKAROUNDS_H /* Note that some systems (Solaris) may use a macro to redefine struct stat */ #include /* When syscall fails with ESTALE error, how many times to try reopening the file and retrying the operation. */ #define NFS_ESTALE_RETRY_COUNT 10 /* Same as open(), but try to handle ESTALE errors. */ int nfs_safe_open(const char *path, int flags); /* Same as stat(), but try to handle ESTALE errors. Doesn't flush attribute cache. */ int nfs_safe_stat(const char *path, struct stat *buf); int nfs_safe_lstat(const char *path, struct stat *buf); /* Same as link(), but handle problems with link() by verifying the file's link count changes. If links1=TRUE, assume the original file's link count is 1, otherwise stat() first to find it out. */ int nfs_safe_link(const char *oldpath, const char *newpath, bool links1); /* Flush attribute cache for given path. The file must not be fcntl locked or the locks may get dropped. */ void nfs_flush_attr_cache_unlocked(const char *path); /* Flush attribute cache for given path. The file may be fcntl locked. */ void nfs_flush_attr_cache_maybe_locked(const char *path); /* Flush attribute cache for a fcntl locked file descriptor. If locking flushes the attribute cache with the running OS, this function does nothing. The given path is used only for logging. */ void nfs_flush_attr_cache_fd_locked(const char *path, int fd); /* Flush file handle cache for given file. */ void nfs_flush_file_handle_cache(const char *path); /* Flush read cache for fd that was just fcntl locked. If the OS flushes read cache when fcntl locking file, this function does nothing. */ void nfs_flush_read_cache_locked(const char *path, int fd); /* Flush read cache for fd that doesn't have fcntl locks. */ void nfs_flush_read_cache_unlocked(const char *path, int fd); #endif dovecot-2.3.21.1/src/lib/istream-failure-at.h0000644000000000000000000000051614656633576015520 00000000000000#ifndef ISTREAM_FAILURE_AT_H #define ISTREAM_FAILURE_AT_H struct istream * i_stream_create_failure_at(struct istream *input, uoff_t failure_offset, int stream_errno, const char *error_string); struct istream * i_stream_create_failure_at_eof(struct istream *input, int stream_errno, const char *error_string); #endif dovecot-2.3.21.1/src/lib/test-event-filter-parser.c0000644000000000000000000003320214656633576016671 00000000000000/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str.h" #include "strescape.h" #include "event-filter.h" #define GOOD(i, o) \ { \ .input = (i), \ .output = (o), \ .fails = FALSE, \ } #define BAD(i, o) \ { \ .input = (i), \ .output = (o), \ .fails = TRUE, \ } enum quoting { QUOTE_MUST, QUOTE_MAY, QUOTE_MUST_NOT, }; static const char *what_special[] = { "event", "category", "source_location", }; /* some sample field names */ static const char *what_fields_single[] = { "foo", "foo_bar", "foo-bar", }; static const char *comparators[] = { "=", "<", "<=", ">", ">=", }; /* values that may be quoted or not quoted */ static const char *values_single[] = { "foo", "foo.c", "foo.c:123", /* wildcards */ "*foo", "f*o", "foo*", "*", "?foo", "f?o", "foo?", "?", }; /* values that need to be quoted */ static const char *values_multi[] = { "foo bar", "foo\tbar", "foo\nbar", "foo\rbar", "foo\"bar", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ac " "vestibulum magna. Maecenas erat mi, finibus et tellus id, suscipit " "varius arcu. Morbi faucibus diam in ligula suscipit, non bibendum " "orci venenatis. Vestibulum mattis luctus dictum. Vivamus ultrices " "tincidunt vehicula. Aliquam nec ante vitae libero dignissim finibus " "non ac massa. Proin sit amet semper ligula. Curabitur eleifend massa " "et arcu euismod lacinia. Phasellus sapien mauris, dignissim vitae " "commodo at, consequat eget augue. Integer posuere non enim eu " "laoreet. Nulla eget lectus at enim sodales rutrum. Donec tincidunt " "nibh ac convallis pulvinar. Nunc facilisis tempus ligula. Nullam at " "ultrices enim, eu faucibus ipsum." /* utf-8: >= U+128 only */ "\xc3\xa4\xc3\xa1\xc4\x8d\xc4\x8f\xc4\x9b\xc5\x88\xc3\xb6\xc5\x99\xc3\xbc\xc3\xba\xc5\xaf", /* utf-8: ascii + combining char */ "r\xcc\x8c", /* wildcards */ "foo * bar", "foo ? bar", }; /* boolean operators used as values get lowercased unless they are quoted */ static const struct values_oper { const char *in; const char *out_unquoted; const char *out_quoted; } values_oper[] = { { "AND", "and", "AND" }, { "ANd", "and", "ANd" }, { "AnD", "and", "AnD" }, { "And", "and", "And" }, { "aND", "and", "aND" }, { "aNd", "and", "aNd" }, { "anD", "and", "anD" }, { "and", "and", "and" }, { "OR", "or", "OR" }, { "Or", "or", "Or" }, { "oR", "or", "oR" }, { "or", "or", "or" }, { "NOT", "not", "NOT" }, { "NOt", "not", "NOt" }, { "NoT", "not", "NoT" }, { "Not", "not", "Not" }, { "nOT", "not", "nOT" }, { "nOt", "not", "nOt" }, { "noT", "not", "noT" }, { "not", "not", "not" }, }; static struct test { const char *input; const char *output; bool fails; } tests[] = { GOOD("", ""), /* unquoted tokens can be only [a-zA-Z0-9:.*?_-]+ */ BAD("abc=r\xcc\x8c", "event filter: syntax error"), /* check that spaces and extra parens don't break anything */ #define CHECK_REAL(sp1, key, sp2, sp3, value, sp4) \ GOOD(sp1 key sp2 "=" sp3 value sp4, \ "(" key "=\"" value "\")") #define CHECK_SPACES(key, value, sp, op, cp) \ CHECK_REAL(sp op, key, "", "", value, "" cp), \ CHECK_REAL(op sp, key, "", "", value, "" cp), \ CHECK_REAL(op "", key, sp, "", value, "" cp), \ CHECK_REAL(op "", key, "", sp, value, "" cp), \ CHECK_REAL(op "", key, "", "", value, sp cp), \ CHECK_REAL(op "", key, "", "", value, cp sp) #define CHECK_PARENS(key, value, sp) \ CHECK_SPACES(key, value, sp, "", ""), \ CHECK_SPACES(key, value, sp, "(", ")"), \ CHECK_SPACES(key, value, sp, "((", "))"), \ CHECK_SPACES(key, value, sp, "(((", ")))") CHECK_PARENS("event", "abc", " "), CHECK_PARENS("event", "abc", "\t"), CHECK_PARENS("event", "abc", "\n"), CHECK_PARENS("event", "abc", "\r"), CHECK_PARENS("event", "abc", " "), #undef CHECK_PARENS #undef CHECK_SPACES #undef CHECK_REAL /* check empty parens */ BAD("()", "event filter: syntax error"), /* check name only / name+comparator (!negated & negated) */ #define CHECK_CMP_REAL(not, name, cmp, err) \ BAD(not name cmp, err), \ BAD(not "\"" name "\"" cmp, err) #define CHECK_CMP(name, cmp, err) \ CHECK_CMP_REAL("", name, cmp, err), \ CHECK_CMP_REAL("NOT ", name, cmp, err) #define CHECK(name) \ CHECK_CMP(name, "", \ "event filter: syntax error"), \ CHECK_CMP(name, "=", \ "event filter: syntax error"), \ CHECK_CMP(name, "<", \ "event filter: syntax error"), \ CHECK_CMP(name, "<=", \ "event filter: syntax error"), \ CHECK_CMP(name, ">", \ "event filter: syntax error"), \ CHECK_CMP(name, ">=", \ "event filter: syntax error") CHECK("event"), CHECK("source_location"), CHECK("category"), CHECK("foo-field-name"), #undef CHECK #undef CHECK_CMP #undef CHECK_CMP_REAL /* check simple nesting */ #define CHECK(binop1, binop2) \ GOOD("(event=abc " binop1 " event=def) " binop2 " event=ghi", \ "(((event=\"abc\" " binop1 " event=\"def\") " binop2 " event=\"ghi\"))"), \ GOOD("event=abc " binop1 " (event=def " binop2 " event=ghi)", \ "((event=\"abc\" " binop1 " (event=\"def\" " binop2 " event=\"ghi\")))") CHECK("AND", "AND"), CHECK("AND", "OR"), CHECK("OR", "AND"), CHECK("OR", "OR"), #undef CHECK /* check operator precedence */ #define CMP(x) "event=\"" #x "\"" #define CHECK(binop1, binop2) \ GOOD(CMP(1) " " binop1 " " CMP(2) " " binop2 " " CMP(3), \ "(((" CMP(1) " " binop1 " " CMP(2) ") " binop2 " " CMP(3) "))") CHECK("AND", "AND"), CHECK("AND", "OR"), CHECK("OR", "AND"), CHECK("OR", "OR"), #undef CHECK #undef CMP }; static void testcase(const char *name, const char *input, const char *exp, bool fails) { struct event_filter *filter; const char *error; int ret; filter = event_filter_create(); ret = event_filter_parse(input, filter, &error); test_out_quiet(name != NULL ? name : "filter parser", (ret != 0) == fails); if (ret == 0) { string_t *tmp = t_str_new(128); event_filter_export(filter, tmp); test_out_quiet(t_strdup_printf("input: %s", input), strcmp(exp, str_c(tmp)) == 0); } else { test_out_quiet(t_strdup_printf("input: %s", input), str_begins(error, exp)); } event_filter_unref(&filter); } static void test_event_filter_parser_table(void) { unsigned int i; test_begin("event filter parser: table"); for (i = 0; i < N_ELEMENTS(tests); i++) T_BEGIN { testcase(NULL, tests[i].input, tests[i].output, tests[i].fails); } T_END; test_end(); } static void test_event_filter_parser_categories(void) { static const char *cat_names[] = { "debug", "info", "warning", "error", "fatal", "panic", }; unsigned int i; test_begin("event filter parser: log type category"); for (i = 0; i < N_ELEMENTS(cat_names); i++) T_BEGIN { string_t *str = t_str_new(128); str_append(str, "(category="); str_append(str, cat_names[i]); str_append(str, ")"); testcase(NULL, str_c(str), str_c(str), FALSE); } T_END; test_end(); } static void test_event_filter_parser_simple_nesting_helper(bool not1, bool not2, bool and, const char *sp, bool sp1, bool sp2, bool sp3, bool sp4) { const char *op = and ? "AND" : "OR"; const char *expr1 = "event=\"abc\""; const char *expr2 = "event=\"def\""; const char *in; const char *exp; in = t_strdup_printf("%s(%s%s)%s%s%s(%s%s)%s", sp1 ? sp : "", not1 ? "NOT " : "", expr1, sp2 ? sp : "", op, sp3 ? sp : "", not2 ? "NOT " : "", expr2, sp4 ? sp : ""); exp = t_strdup_printf("((%s%s%s %s %s%s%s))", not1 ? "(NOT " : "", expr1, not1 ? ")" : "", op, not2 ? "(NOT " : "", expr2, not2 ? ")" : ""); testcase(NULL, in, exp, FALSE); } static void test_event_filter_parser_simple_nesting(void) { const char *whitespace[] = { "", "\t", "\n", "\r", " ", }; unsigned int i; unsigned int loc; unsigned int not; test_begin("event filter parser: simple nesting"); for (i = 0; i < N_ELEMENTS(whitespace); i++) { for (not = 0; not < 4; not++) { const bool not1 = (not & 0x2) != 0; const bool not2 = (not & 0x1) != 0; for (loc = 0; loc < 16; loc++) T_BEGIN { const bool sp1 = (loc & 0x8) != 0; const bool sp2 = (loc & 0x4) != 0; const bool sp3 = (loc & 0x2) != 0; const bool sp4 = (loc & 0x1) != 0; test_event_filter_parser_simple_nesting_helper(not1, not2, TRUE, whitespace[i], sp1, sp2, sp3, sp4); test_event_filter_parser_simple_nesting_helper(not1, not2, FALSE, whitespace[i], sp1, sp2, sp3, sp4); } T_END; } } test_end(); } /* * Test '' with each possible operator and each possible * quoting of and . Some quotings are not allowed. The keyq * and valueq arguments specify whether the and strings * should be quoted. key_special indicates that the key is *not* a field * and therefore only the = operator should parse successfully. */ static void generated_single_comparison(const char *name, bool parens, const char *key, enum quoting keyq, bool key_special, const char *value_in, const char *value_exp, enum quoting valueq) { unsigned int c, q; bool should_fail; for (c = 0; c < N_ELEMENTS(comparators); c++) { string_t *output = t_str_new(128); if (key_special && (strcmp(comparators[c], "=") != 0)) { /* the key is a not a field, only = is allowed */ str_append(output, "event filter: Only fields support inequality comparisons"); should_fail = TRUE; } else { /* the key is a field, all comparators are allowed */ str_append_c(output, '('); if (keyq != QUOTE_MUST_NOT) str_append_c(output, '"'); str_append(output, key); if (keyq != QUOTE_MUST_NOT) str_append_c(output, '"'); str_append(output, comparators[c]); str_append_c(output, '"'); str_append_escaped(output, value_exp, strlen(value_exp)); str_append_c(output, '"'); str_append_c(output, ')'); should_fail = FALSE; } for (q = 0; q < 4; q++) { const bool qkey = (q & 1) == 1; const bool qval = (q & 2) == 2; string_t *input = t_str_new(128); if ((!qkey && (keyq == QUOTE_MUST)) || (qkey && (keyq == QUOTE_MUST_NOT))) continue; if ((!qval && (valueq == QUOTE_MUST)) || (qval && (valueq == QUOTE_MUST_NOT))) continue; if (parens) str_append_c(input, '('); if (qkey) str_append_c(input, '"'); str_append(input, key); if (qkey) str_append_c(input, '"'); str_append(input, comparators[c]); if (qval) { str_append_c(input, '"'); str_append_escaped(input, value_in, strlen(value_in)); str_append_c(input, '"'); } else { str_append(input, value_in); } if (parens) str_append_c(input, ')'); testcase(name, str_c(input), str_c(output), should_fail); } } } static void test_event_filter_parser_generated(bool parens) { unsigned int w, v; test_begin(t_strdup_printf("event filter parser: parser generated parens=%s", parens ? "yes" : "no")); /* check that non-field keys work */ for (w = 0; w < N_ELEMENTS(what_special); w++) { for (v = 0; v < N_ELEMENTS(values_single); v++) generated_single_comparison("non-field/single", parens, what_special[w], QUOTE_MUST_NOT, TRUE, values_single[v], values_single[v], QUOTE_MAY); for (v = 0; v < N_ELEMENTS(values_multi); v++) generated_single_comparison("non-field/multi", parens, what_special[w], QUOTE_MUST_NOT, TRUE, values_multi[v], values_multi[v], QUOTE_MUST); for (v = 0; v < N_ELEMENTS(values_oper); v++) { generated_single_comparison("non-field/bool-op", parens, what_special[w], QUOTE_MUST_NOT, TRUE, values_oper[v].in, values_oper[v].out_unquoted, QUOTE_MUST_NOT); generated_single_comparison("non-field/bool-op", parens, what_special[w], QUOTE_MUST_NOT, TRUE, values_oper[v].in, values_oper[v].out_quoted, QUOTE_MUST); } } /* check that field keys work */ for (w = 0; w < N_ELEMENTS(what_fields_single); w++) { for (v = 0; v < N_ELEMENTS(values_single); v++) generated_single_comparison("field/single", parens, what_fields_single[w], QUOTE_MAY, FALSE, values_single[v], values_single[v], QUOTE_MAY); for (v = 0; v < N_ELEMENTS(values_multi); v++) generated_single_comparison("field/multi", parens, what_fields_single[w], QUOTE_MAY, FALSE, values_multi[v], values_multi[v], QUOTE_MUST); for (v = 0; v < N_ELEMENTS(values_oper); v++) { generated_single_comparison("field/bool-op", parens, what_fields_single[w], QUOTE_MAY, FALSE, values_oper[v].in, values_oper[v].out_unquoted, QUOTE_MUST_NOT); generated_single_comparison("field/bool-op", parens, what_fields_single[w], QUOTE_MAY, FALSE, values_oper[v].in, values_oper[v].out_quoted, QUOTE_MUST); } } test_end(); } static void test_event_filter_parser_simple_invalid(void) { test_begin("event filter parser: simple invalid"); testcase(NULL, "a=b=c", "", TRUE); test_end(); } void test_event_filter_parser(void) { test_event_filter_parser_table(); test_event_filter_parser_categories(); test_event_filter_parser_simple_nesting(); test_event_filter_parser_generated(FALSE); test_event_filter_parser_generated(TRUE); test_event_filter_parser_simple_invalid(); } dovecot-2.3.21.1/src/lib/safe-memset.c0000644000000000000000000000054014656633576014223 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "safe-memset.h" void safe_memset(void *data, int c, size_t size) { volatile unsigned int volatile_zero_idx = 0; volatile unsigned char *p = data; if (size == 0) return; do { memset(data, c, size); } while (p[volatile_zero_idx] != c); } dovecot-2.3.21.1/src/lib/sha-common.h0000644000000000000000000000036514656633576014070 00000000000000#ifndef SHA_COMMON #define SHA256_RESULTLEN (256 / 8) #define SHA256_BLOCK_SIZE (512 / 8) #define SHA384_RESULTLEN (384 / 8) #define SHA384_BLOCK_SIZE (1024 / 8) #define SHA512_RESULTLEN (512 / 8) #define SHA512_BLOCK_SIZE (1024 / 8) #endif dovecot-2.3.21.1/src/lib/hmac.h0000644000000000000000000000343214656633576012735 00000000000000#ifndef HMAC_H #define HMAC_H #include "hash-method.h" #include "sha1.h" #include "sha2.h" #define HMAC_MAX_CONTEXT_SIZE sizeof(struct sha512_ctx) struct hmac_context_priv { char ctx[HMAC_MAX_CONTEXT_SIZE]; char ctxo[HMAC_MAX_CONTEXT_SIZE]; const struct hash_method *hash; }; struct hmac_context { union { struct hmac_context_priv priv; uint64_t padding_requirement; } u; }; void hmac_init(struct hmac_context *ctx, const unsigned char *key, size_t key_len, const struct hash_method *meth); void hmac_final(struct hmac_context *ctx, unsigned char *digest); static inline void hmac_update(struct hmac_context *_ctx, const void *data, size_t size) { struct hmac_context_priv *ctx = &_ctx->u.priv; ctx->hash->loop(ctx->ctx, data, size); } buffer_t *t_hmac_data(const struct hash_method *meth, const unsigned char *key, size_t key_len, const void *data, size_t data_len); buffer_t *t_hmac_buffer(const struct hash_method *meth, const unsigned char *key, size_t key_len, const buffer_t *data); buffer_t *t_hmac_str(const struct hash_method *meth, const unsigned char *key, size_t key_len, const char *data); void hmac_hkdf(const struct hash_method *method, const unsigned char *salt, size_t salt_len, const unsigned char *ikm, size_t ikm_len, const unsigned char *info, size_t info_len, buffer_t *okm_r, size_t okm_len); static inline buffer_t * t_hmac_hkdf(const struct hash_method *method, const unsigned char *salt, size_t salt_len, const unsigned char *ikm, size_t ikm_len, const unsigned char *info, size_t info_len, size_t okm_len) { buffer_t *okm_buffer = t_buffer_create(okm_len); hmac_hkdf(method, salt, salt_len, ikm, ikm_len, info, info_len, okm_buffer, okm_len); return okm_buffer; } #endif dovecot-2.3.21.1/src/lib/file-dotlock.h0000644000000000000000000000735614656633576014412 00000000000000#ifndef FILE_DOTLOCK_H #define FILE_DOTLOCK_H #include #include struct dotlock; struct dotlock_settings { /* Dotlock files are created by first creating a temp file and then link()ing it to the dotlock. temp_prefix specifies the prefix to use for temp files. It may contain a full path. Default is ".temp.hostname.pid.". */ const char *temp_prefix; /* Use this suffix for dotlock filenames. Default is ".lock". */ const char *lock_suffix; /* Abort after this many seconds. */ unsigned int timeout; /* Override the lock file when it and the file we're protecting is older than stale_timeout. */ unsigned int stale_timeout; /* Callback is called once in a while. stale is set to TRUE if stale lock is detected and will be overridden in secs_left. If callback returns FALSE then, the lock will not be overridden. */ bool (*callback)(unsigned int secs_left, bool stale, void *context); void *context; /* Rely on O_EXCL locking to work instead of using hardlinks. It's faster, but doesn't work with all NFS implementations. */ bool use_excl_lock:1; /* Flush NFS attribute cache before stating files. */ bool nfs_flush:1; /* Use io_add_notify() to speed up finding out when an existing dotlock is deleted */ bool use_io_notify:1; }; enum dotlock_create_flags { /* If lock already exists, fail immediately */ DOTLOCK_CREATE_FLAG_NONBLOCK = 0x01, /* Don't actually create the lock file, only make sure it doesn't exist. This is racy, so you shouldn't rely on it much. */ DOTLOCK_CREATE_FLAG_CHECKONLY = 0x02 }; enum dotlock_replace_flags { /* Check that lock file hasn't been overridden before renaming. */ DOTLOCK_REPLACE_FLAG_VERIFY_OWNER = 0x01, /* Don't close the file descriptor. */ DOTLOCK_REPLACE_FLAG_DONT_CLOSE_FD = 0x02 }; /* Create dotlock. Returns 1 if successful, 0 if timeout or -1 if error. When returning 0, errno is also set to EAGAIN. */ int file_dotlock_create(const struct dotlock_settings *set, const char *path, enum dotlock_create_flags flags, struct dotlock **dotlock_r); /* Delete the dotlock file. Returns 1 if successful, 0 if the file had already been deleted or reused by someone else, -1 if I/O error. */ int ATTR_NOWARN_UNUSED_RESULT file_dotlock_delete(struct dotlock **dotlock); /* Use dotlock as the new content for file. This provides read safety without locks, but it's not very good for large files. Returns fd for lock file. If locking timed out, returns -1 and errno = EAGAIN. */ int file_dotlock_open(const struct dotlock_settings *set, const char *path, enum dotlock_create_flags flags, struct dotlock **dotlock_r); /* Like file_dotlock_open(), but use the given file permissions. */ int file_dotlock_open_mode(const struct dotlock_settings *set, const char *path, enum dotlock_create_flags flags, mode_t mode, uid_t uid, gid_t gid, struct dotlock **dotlock_r); int file_dotlock_open_group(const struct dotlock_settings *set, const char *path, enum dotlock_create_flags flags, mode_t mode, gid_t gid, const char *gid_origin, struct dotlock **dotlock_r); /* Replaces the file dotlock protects with the dotlock file itself. */ int file_dotlock_replace(struct dotlock **dotlock, enum dotlock_replace_flags flags); /* Update dotlock's mtime. If you're keeping the dotlock for a long time, it's a good idea to update it once in a while so others won't override it. If the timestamp is less than a second old, it's not updated. */ int file_dotlock_touch(struct dotlock *dotlock); /* Returns TRUE if the lock is still ok, FALSE if it's been overridden. */ bool file_dotlock_is_locked(struct dotlock *dotlock); /* Returns the lock file path. */ const char *file_dotlock_get_lock_path(struct dotlock *dotlock); #endif dovecot-2.3.21.1/src/lib/backtrace-string.h0000644000000000000000000000026714656633576015253 00000000000000#ifndef BACKTRACE_STRING_H #define BACKTRACE_STRING_H /* Returns 0 if ok, -1 if failure. */ int backtrace_append(string_t *str); int backtrace_get(const char **backtrace_r); #endif dovecot-2.3.21.1/src/lib/event-filter-parser.y0000644000000000000000000001175314656633576015751 00000000000000/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ %define api.pure %define api.prefix {event_filter_parser_} %define parse.error verbose %lex-param {void *scanner} %parse-param {struct event_filter_parser_state *state} %defines %{ #include "lib.h" #include "wildcard-match.h" #include "lib-event-private.h" #include "event-filter-private.h" #define scanner state->scanner #define YYERROR_VERBOSE extern int event_filter_parser_lex(void *, void *); void event_filter_parser_error(void *scan, const char *e) { struct event_filter_parser_state *state = scan; state->error = t_strdup_printf("event filter: %s", e); } static struct event_filter_node *key_value(struct event_filter_parser_state *state, const char *a, const char *b, enum event_filter_node_op op) { struct event_filter_node *node; enum event_filter_node_type type; if (strcmp(a, "event") == 0) type = EVENT_FILTER_NODE_TYPE_EVENT_NAME_WILDCARD; else if (strcmp(a, "category") == 0) type = EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY; else if (strcmp(a, "source_location") == 0) type = EVENT_FILTER_NODE_TYPE_EVENT_SOURCE_LOCATION; else type = EVENT_FILTER_NODE_TYPE_EVENT_FIELD_WILDCARD; /* only fields support comparators other than EQ */ if ((type != EVENT_FILTER_NODE_TYPE_EVENT_FIELD_WILDCARD) && (op != EVENT_FILTER_OP_CMP_EQ)) { state->error = "Only fields support inequality comparisons"; return NULL; } node = p_new(state->pool, struct event_filter_node, 1); node->type = type; node->op = op; switch (type) { case EVENT_FILTER_NODE_TYPE_LOGIC: i_unreached(); case EVENT_FILTER_NODE_TYPE_EVENT_NAME_WILDCARD: node->str = p_strdup(state->pool, b); if (wildcard_is_literal(node->str)) node->type = EVENT_FILTER_NODE_TYPE_EVENT_NAME_EXACT; break; case EVENT_FILTER_NODE_TYPE_EVENT_SOURCE_LOCATION: { const char *colon = strrchr(b, ':'); const char *file; uintmax_t line; /* split "filename:line-number", but also handle "filename" */ if (colon != NULL) { if (str_to_uintmax(colon + 1, &line) < 0) { file = p_strdup(state->pool, b); line = 0; } else { file = p_strdup_until(state->pool, b, colon); } } else { file = p_strdup(state->pool, b); line = 0; } node->str = file; node->intmax = line; break; } case EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY: if (!event_filter_category_to_log_type(b, &node->category.log_type)) { node->category.name = p_strdup(state->pool, b); node->category.ptr = event_category_find_registered(b); } break; case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_WILDCARD: node->field.key = p_strdup(state->pool, a); node->field.value.str = p_strdup(state->pool, b); /* Filter currently supports only comparing strings and numbers. */ if (str_to_intmax(b, &node->field.value.intmax) < 0) { /* not a number - no problem Either we have a string, or a number with wildcards */ node->field.value.intmax = INT_MIN; } if (wildcard_is_literal(node->field.value.str)) node->type = EVENT_FILTER_NODE_TYPE_EVENT_FIELD_EXACT; break; case EVENT_FILTER_NODE_TYPE_EVENT_NAME_EXACT: case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_EXACT: i_unreached(); } return node; } static struct event_filter_node *logic(struct event_filter_parser_state *state, struct event_filter_node *a, struct event_filter_node *b, enum event_filter_node_op op) { struct event_filter_node *node; node = p_new(state->pool, struct event_filter_node, 1); node->type = EVENT_FILTER_NODE_TYPE_LOGIC; node->op = op; node->children[0] = a; node->children[1] = b; return node; } /* ignore strict bool warnings in generated code */ #ifdef HAVE_STRICT_BOOL # pragma GCC diagnostic ignored "-Wstrict-bool" #endif %} %union { const char *str; enum event_filter_node_op op; struct event_filter_node *node; }; %token TOKEN STRING %token AND OR NOT %type key value %type op %type expr key_value %left AND OR %right NOT %% filter : expr { state->output = $1; } | %empty { state->output = NULL; } ; expr : expr AND expr { $$ = logic(state, $1, $3, EVENT_FILTER_OP_AND); } | expr OR expr { $$ = logic(state, $1, $3, EVENT_FILTER_OP_OR); } | NOT expr { $$ = logic(state, $2, NULL, EVENT_FILTER_OP_NOT); } | '(' expr ')' { $$ = $2; } | key_value { $$ = $1; } ; key_value : key op value { $$ = key_value(state, $1, $3, $2); if ($$ == NULL) { yyerror(state, state->error); /* avoid compiler warning about yynerrs being set, but not used */ (void)yynerrs; YYERROR; } } ; key : TOKEN { $$ = $1; } | STRING { $$ = $1; } ; value : TOKEN { $$ = $1; } | STRING { $$ = $1; } | AND { $$ = "and"; } | OR { $$ = "or"; } | NOT { $$ = "not"; } ; op : '=' { $$ = EVENT_FILTER_OP_CMP_EQ; } | '>' { $$ = EVENT_FILTER_OP_CMP_GT; } | '<' { $$ = EVENT_FILTER_OP_CMP_LT; } | '>' '=' { $$ = EVENT_FILTER_OP_CMP_GE; } | '<' '=' { $$ = EVENT_FILTER_OP_CMP_LE; } ; %% dovecot-2.3.21.1/src/lib/ioloop-notify-fd.h0000644000000000000000000000121614656633576015221 00000000000000#ifndef IOLOOP_NOTIFY_FD_H #define IOLOOP_NOTIFY_FD_H /* common notify code for fd-based notifications (dnotify, inotify) */ struct io_notify { struct io io; /* use a doubly linked list so that io_remove() is quick */ struct io_notify *prev, *next; int fd; }; struct ioloop_notify_fd_context { struct io_notify *notifies; }; struct io * io_notify_fd_add(struct ioloop_notify_fd_context *ctx, int fd, io_callback_t *callback, void *context) ATTR_NULL(4); void io_notify_fd_free(struct ioloop_notify_fd_context *ctx, struct io_notify *io); struct io_notify * io_notify_fd_find(struct ioloop_notify_fd_context *ctx, int fd); #endif dovecot-2.3.21.1/src/lib/ioloop-notify-kqueue.c0000644000000000000000000001250714656633576016127 00000000000000/* * BSD kqueue() based ioloop notify handler. * * Copyright (c) 2005 Vaclav Haisman */ #define _GNU_SOURCE #include "lib.h" #ifdef IOLOOP_NOTIFY_KQUEUE #include "ioloop-private.h" #include "llist.h" #include "time-util.h" #include #include #include #include #include #include /* kevent.udata's type just has to be different in NetBSD than in FreeBSD and OpenBSD.. */ #ifdef __NetBSD__ # define MY_EV_SET(a, b, c, d, e, f, g) \ EV_SET(a, b, c, d, e, f, (intptr_t)g) #else # define MY_EV_SET(a, b, c, d, e, f, g) \ EV_SET(a, b, c, d, e, f, g) #endif struct io_notify { struct io io; int refcount; int fd; struct io_notify *prev, *next; }; struct ioloop_notify_handler_context { int kq; struct io *event_io; struct io_notify *notifies; }; static void io_loop_notify_free(struct ioloop_notify_handler_context *ctx, struct io_notify *io) { DLLIST_REMOVE(&ctx->notifies, io); i_free(io); } static void event_callback(struct ioloop_notify_handler_context *ctx) { struct io_notify *io; struct kevent events[64]; struct timespec ts; int i, ret; ts.tv_sec = 0; ts.tv_nsec = 0; ret = kevent(ctx->kq, NULL, 0, events, N_ELEMENTS(events), &ts); if (ret <= 0) { if (ret == 0 || errno == EINTR) return; i_fatal("kevent(notify) failed: %m"); } i_gettimeofday(&ioloop_timeval); ioloop_time = ioloop_timeval.tv_sec; for (i = 0; i < ret; i++) { io = (void *)events[i].udata; i_assert(io->refcount >= 1); io->refcount++; } for (i = 0; i < ret; i++) { io = (void *)events[i].udata; /* there can be multiple events for a single io. call the callback only once if that happens. */ if (io->refcount == 2 && io->io.callback != NULL) io_loop_call_io(&io->io); if (--io->refcount == 0) io_loop_notify_free(ctx, io); } } static struct ioloop_notify_handler_context *io_loop_notify_handler_init(void) { struct ioloop_notify_handler_context *ctx; ctx = current_ioloop->notify_handler_context = i_new(struct ioloop_notify_handler_context, 1); ctx->kq = kqueue(); if (ctx->kq < 0) i_fatal("kqueue(notify) failed: %m"); fd_close_on_exec(ctx->kq, TRUE); return ctx; } void io_loop_notify_handler_deinit(struct ioloop *ioloop) { struct ioloop_notify_handler_context *ctx = ioloop->notify_handler_context; while (ctx->notifies != NULL) { struct io_notify *io = ctx->notifies; struct io *_io = &io->io; i_warning("I/O notify leak: %p (%s:%u, fd %d)", (void *)_io->callback, _io->source_filename, _io->source_linenum, io->fd); io_remove(&_io); } io_remove(&ctx->event_io); if (close(ctx->kq) < 0) i_error("close(kqueue notify) failed: %m"); i_free(ctx); } #undef io_add_notify enum io_notify_result io_add_notify(const char *path, const char *source_filename, unsigned int source_linenum, io_callback_t *callback, void *context, struct io **io_r) { struct ioloop_notify_handler_context *ctx = current_ioloop->notify_handler_context; struct kevent ev; struct io_notify *io; int fd; if (ctx == NULL) ctx = io_loop_notify_handler_init(); fd = open(path, O_RDONLY); if (fd == -1) { /* ESTALE could happen with NFS. Don't bother giving an error message then. */ if (errno != ENOENT && errno != ESTALE) i_error("open(%s) for kq notify failed: %m", path); return IO_NOTIFY_NOTFOUND; } fd_close_on_exec(fd, TRUE); io = i_new(struct io_notify, 1); io->io.condition = IO_NOTIFY; io->io.source_filename = source_filename; io->io.source_linenum = source_linenum; io->io.callback = callback; io->io.context = context; io->io.ioloop = current_ioloop; io->refcount = 1; io->fd = fd; /* EV_CLEAR flag is needed because the EVFILT_VNODE filter reports event state transitions and not the current state. With this flag, the same event is only returned once. */ MY_EV_SET(&ev, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_RENAME | NOTE_WRITE | NOTE_EXTEND | NOTE_REVOKE, 0, io); if (kevent(ctx->kq, &ev, 1, NULL, 0, NULL) < 0) { i_error("kevent(%d, %s) for notify failed: %m", fd, path); i_close_fd(&fd); i_free(io); return IO_NOTIFY_NOSUPPORT; } if (ctx->event_io == NULL) { ctx->event_io = io_add(ctx->kq, IO_READ, event_callback, io->io.ioloop->notify_handler_context); } DLLIST_PREPEND(&ctx->notifies, io); *io_r = &io->io; return IO_NOTIFY_ADDED; } void io_loop_notify_remove(struct io *_io) { struct ioloop_notify_handler_context *ctx = _io->ioloop->notify_handler_context; struct io_notify *io = (struct io_notify *)_io; struct kevent ev; MY_EV_SET(&ev, io->fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL); if (kevent(ctx->kq, &ev, 1, NULL, 0, 0) < 0) i_error("kevent(%d) for notify remove failed: %m", io->fd); if (close(io->fd) < 0) i_error("close(%d) for notify remove failed: %m", io->fd); io->fd = -1; if (--io->refcount == 0) io_loop_notify_free(ctx, io); } int io_loop_extract_notify_fd(struct ioloop *ioloop) { struct ioloop_notify_handler_context *ctx = ioloop->notify_handler_context; struct io_notify *io; int fd, new_kq; if (ctx == NULL || ctx->kq == -1) return -1; new_kq = kqueue(); if (new_kq < 0) { i_error("kqueue(notify) failed: %m"); return -1; } for (io = ctx->notifies; io != NULL; io = io->next) io->fd = -1; io_remove(&ctx->event_io); fd = ctx->kq; ctx->kq = new_kq; return fd; } #endif dovecot-2.3.21.1/src/lib/test-ostream-buffer.c0000644000000000000000000000567514656633576015731 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "buffer.h" #include "str.h" #include "randgen.h" #include "istream.h" #include "ostream.h" #define MAX_BUFSIZE 256 static void test_ostream_buffer_random_once(void) { buffer_t *buffer; struct ostream *output; char buf[MAX_BUFSIZE*4], randbuf[MAX_BUFSIZE]; unsigned int i, offset, size; buffer = buffer_create_dynamic(default_pool, 8); memset(buf, 0, sizeof(buf)); output = o_stream_create_buffer(buffer); o_stream_cork(output); size = i_rand_minmax(1, MAX_BUFSIZE); random_fill(randbuf, size); memcpy(buf, randbuf, size); test_assert(o_stream_send(output, buf, size) > 0); for (i = 0; i < 10; i++) { offset = i_rand_limit(MAX_BUFSIZE * 3); size = i_rand_minmax(1, MAX_BUFSIZE); random_fill(randbuf, size); memcpy(buf + offset, randbuf, size); test_assert(o_stream_pwrite(output, randbuf, size, offset) == 0); if (i_rand_limit(10) == 0) test_assert(o_stream_flush(output) > 0); } o_stream_uncork(output); test_assert(o_stream_finish(output) > 0); i_assert(buffer->used <= MAX_BUFSIZE*4); test_assert(memcmp(buf, buffer->data, buffer->used) == 0); o_stream_unref(&output); buffer_free(&buffer); } static void test_ostream_buffer_random(void) { unsigned int i; test_begin("ostream buffer pwrite random"); for (i = 0; i < 100; i++) T_BEGIN { test_ostream_buffer_random_once(); } T_END; test_end(); } static void test_ostream_buffer_size(void) { struct ostream *output; string_t *str = t_str_new(64); test_begin("ostream buffer size/available"); output = o_stream_create_buffer(str); test_assert(o_stream_get_buffer_used_size(output) == 0); test_assert(o_stream_get_buffer_avail_size(output) == SIZE_MAX); /* test shrinking sink's max buffer size */ o_stream_set_max_buffer_size(output, 10); test_assert(o_stream_get_buffer_used_size(output) == 0); test_assert(o_stream_get_buffer_avail_size(output) == 10); /* partial send */ const char *partial_input = "01234567890123456789"; ssize_t ret = o_stream_send_str(output, partial_input); test_assert(ret == 10); test_assert(o_stream_get_buffer_used_size(output) == 10); test_assert(o_stream_get_buffer_avail_size(output) == 0); /* increase max buffer size so that it can hold the whole message */ o_stream_set_max_buffer_size(output, 100); test_assert(o_stream_get_buffer_used_size(output) == 10); test_assert(o_stream_get_buffer_avail_size(output) == 90); /* send the rest */ ret += o_stream_send_str(output, partial_input + ret); test_assert(ret == (ssize_t)strlen(partial_input)); test_assert(output->offset == str_len(str)); test_assert(o_stream_get_buffer_used_size(output) == 20); test_assert(o_stream_get_buffer_avail_size(output) == 80); /* check buffered data */ test_assert(strcmp(str_c(str), partial_input) == 0); o_stream_unref(&output); test_end(); } void test_ostream_buffer(void) { test_ostream_buffer_random(); test_ostream_buffer_size(); } dovecot-2.3.21.1/src/lib/fdatasync-path.h0000644000000000000000000000024714656633576014734 00000000000000#ifndef FDATASYNC_PATH_H #define FDATASYNC_PATH_H /* Open and fdatasync() the path. Works for files and directories. */ int fdatasync_path(const char *path); #endif dovecot-2.3.21.1/src/lib/askpass.h0000644000000000000000000000022614656633576013470 00000000000000#ifndef ASKPASS_H #define ASKPASS_H void askpass(const char *prompt, char *buf, size_t buf_size); const char *t_askpass(const char *prompt); #endif dovecot-2.3.21.1/src/lib/byteorder.h0000644000000000000000000001766214656633576014036 00000000000000/* * Copyright (c) 2016-2017 Josef 'Jeff' Sipek * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef BYTEORDER_H #define BYTEORDER_H /* * These prototypes exist to catch bugs in the code generating macros below. */ /* return byte swapped input */ static inline uint64_t i_bswap_64(uint64_t in); static inline uint32_t i_bswap_32(uint32_t in); static inline uint16_t i_bswap_16(uint16_t in); static inline uint8_t i_bswap_8(uint8_t in); /* load an unaligned cpu native endian number from memory */ static inline uint64_t cpu64_to_cpu_unaligned(const void *in); static inline uint32_t cpu32_to_cpu_unaligned(const void *in); static inline uint16_t cpu16_to_cpu_unaligned(const void *in); static inline uint8_t cpu8_to_cpu_unaligned(const void *in); /* load an unaligned big endian number from memory */ static inline uint64_t be64_to_cpu_unaligned(const void *in); static inline uint32_t be32_to_cpu_unaligned(const void *in); static inline uint16_t be16_to_cpu_unaligned(const void *in); static inline uint8_t be8_to_cpu_unaligned(const void *in); /* load an unaligned little endian number from memory */ static inline uint64_t le64_to_cpu_unaligned(const void *in); static inline uint32_t le32_to_cpu_unaligned(const void *in); static inline uint16_t le16_to_cpu_unaligned(const void *in); static inline uint8_t le8_to_cpu_unaligned(const void *in); /* store into memory a cpu native endian number as a big endian number */ static inline void cpu64_to_be_unaligned(uint64_t in, void *out); static inline void cpu32_to_be_unaligned(uint32_t in, void *out); static inline void cpu16_to_be_unaligned(uint16_t in, void *out); static inline void cpu8_to_be_unaligned(uint8_t in, void *out); /* store into memory a cpu native endian number as a little endian number */ static inline void cpu64_to_le_unaligned(uint64_t in, void *out); static inline void cpu32_to_le_unaligned(uint32_t in, void *out); static inline void cpu16_to_le_unaligned(uint16_t in, void *out); static inline void cpu8_to_le_unaligned(uint8_t in, void *out); /* convert a big endian input into cpu native endian */ static inline uint64_t be64_to_cpu(uint64_t in); static inline uint32_t be32_to_cpu(uint32_t in); static inline uint16_t be16_to_cpu(uint16_t in); static inline uint8_t be8_to_cpu(uint8_t in); /* convert a cpu native endian input into big endian */ static inline uint64_t cpu64_to_be(uint64_t in); static inline uint32_t cpu32_to_be(uint32_t in); static inline uint16_t cpu16_to_be(uint16_t in); static inline uint8_t cpu8_to_be(uint8_t in); /* convert a little endian input into cpu native endian */ static inline uint64_t le64_to_cpu(uint64_t in); static inline uint32_t le32_to_cpu(uint32_t in); static inline uint16_t le16_to_cpu(uint16_t in); static inline uint8_t le8_to_cpu(uint8_t in); /* convert a cpu native endian input into little endian */ static inline uint64_t cpu64_to_le(uint64_t in); static inline uint32_t cpu32_to_le(uint32_t in); static inline uint16_t cpu16_to_le(uint16_t in); static inline uint8_t cpu8_to_le(uint8_t in); /* * byte swapping */ static inline uint64_t i_bswap_64(uint64_t in) { return ((in & 0xff00000000000000ULL) >> 56) | ((in & 0x00ff000000000000ULL) >> 40) | ((in & 0x0000ff0000000000ULL) >> 24) | ((in & 0x000000ff00000000ULL) >> 8) | ((in & 0x00000000ff000000ULL) << 8) | ((in & 0x0000000000ff0000ULL) << 24) | ((in & 0x000000000000ff00ULL) << 40) | ((in & 0x00000000000000ffULL) << 56); } static inline uint32_t i_bswap_32(uint32_t in) { return ((in & 0xff000000) >> 24) | ((in & 0x00ff0000) >> 8) | ((in & 0x0000ff00) << 8) | ((in & 0x000000ff) << 24); } static inline uint16_t i_bswap_16(uint16_t in) { return ((in & 0xff00) >> 8) | ((in & 0x00ff) << 8); } static inline uint8_t i_bswap_8(uint8_t in) { return (in & 0xff); } /* * unaligned big-endian integer */ static inline uint64_t be64_to_cpu_unaligned(const void *in) { const uint8_t *p = (const uint8_t *) in; return (((uint64_t) p[0] << 56) | ((uint64_t) p[1] << 48) | ((uint64_t) p[2] << 40) | ((uint64_t) p[3] << 32) | ((uint64_t) p[4] << 24) | ((uint64_t) p[5] << 16) | ((uint64_t) p[6] << 8) | ((uint64_t) p[7])); } static inline void cpu64_to_be_unaligned(uint64_t in, void *out) { uint8_t *p = (uint8_t *) out; p[0] = (in >> 56) & 0xff; p[1] = (in >> 48) & 0xff; p[2] = (in >> 40) & 0xff; p[3] = (in >> 32) & 0xff; p[4] = (in >> 24) & 0xff; p[5] = (in >> 16) & 0xff; p[6] = (in >> 8) & 0xff; p[7] = in & 0xff; } static inline uint32_t be32_to_cpu_unaligned(const void *in) { const uint8_t *p = (const uint8_t *) in; return (((uint32_t) p[0] << 24) | ((uint32_t) p[1] << 16) | ((uint32_t) p[2] << 8) | ((uint32_t) p[3])); } static inline void cpu32_to_be_unaligned(uint32_t in, void *out) { uint8_t *p = (uint8_t *) out; p[0] = (in >> 24) & 0xff; p[1] = (in >> 16) & 0xff; p[2] = (in >> 8) & 0xff; p[3] = in & 0xff; } static inline uint16_t be16_to_cpu_unaligned(const void *in) { const uint8_t *p = (const uint8_t *) in; return (((uint16_t) p[0] << 8) | ((uint16_t) p[1])); } static inline void cpu16_to_be_unaligned(uint16_t in, void *out) { uint8_t *p = (uint8_t *) out; p[0] = (in >> 8) & 0xff; p[1] = in & 0xff; } static inline uint8_t be8_to_cpu_unaligned(const void *in) { return *((const uint8_t *) in); } static inline void cpu8_to_be_unaligned(uint8_t in, void *out) { uint8_t *p = (uint8_t *) out; *p = in; } /* * unaligned little-endian & cpu-endian integers */ #define __GEN(size, bswap) \ static inline uint##size##_t le##size##_to_cpu_unaligned(const void *in)\ { \ uint##size##_t x = be##size##_to_cpu_unaligned(in); \ /* we read a LE int as BE, so we always have to byte swap */ \ return i_bswap_##size(x); \ } \ static inline void cpu##size##_to_le_unaligned(uint##size##_t in, \ void *out) \ { \ /* we'll be writing in BE, so we always have to byte swap */ \ cpu##size##_to_be_unaligned(i_bswap_##size(in), out); \ } \ static inline uint##size##_t cpu##size##_to_cpu_unaligned(const void *in)\ { \ uint##size##_t x = be##size##_to_cpu_unaligned(in); \ return bswap; \ } #ifdef WORDS_BIGENDIAN #define GEN(size) __GEN(size, x) #else #define GEN(size) __GEN(size, i_bswap_##size(x)) #endif GEN(64) GEN(32) GEN(16) GEN(8) #undef __GEN #undef GEN /* * byte ordering */ #define ___GEN(from, size, to, bswap) \ static inline uint##size##_t from##size##_to_##to(uint##size##_t x) \ { \ return bswap; \ } #ifdef WORDS_BIGENDIAN #define __GEN(from, size, to, be, le) ___GEN(from, size, to, be) #else #define __GEN(from, size, to, be, le) ___GEN(from, size, to, le) #endif #define GEN(size) \ __GEN(be, size, cpu, x, i_bswap_##size(x)) \ __GEN(cpu, size, be, x, i_bswap_##size(x)) \ __GEN(le, size, cpu, i_bswap_##size(x), x) \ __GEN(cpu, size, le, i_bswap_##size(x), x) GEN(64) GEN(32) GEN(16) GEN(8) #undef ___GEN #undef __GEN #undef GEN #endif dovecot-2.3.21.1/src/lib/test-path-util.c0000644000000000000000000001636014656633576014710 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "path-util.h" #include "unlink-directory.h" #include "str.h" #include #include #include #include #define TEMP_DIRNAME ".test-path-util" static const char *tmpdir; static const char *cwd; static const char *link1; static const char *link2; static const char *link3; static const char *link4; static void test_local_path(void) { const char *expected = t_strconcat(cwd, "/README.md", NULL); const char *npath = NULL, *error = NULL; test_assert(t_normpath_to("README.md", cwd, &npath, &error) == 0); test_assert_strcmp(npath, expected); } static void test_absolute_path_no_change(void) { const char *npath = NULL, *error = NULL; test_assert(t_normpath_to("/", "/", &npath, &error) == 0); test_assert_strcmp(npath, "/"); test_assert(t_normpath_to(cwd, cwd, &npath, &error) == 0); test_assert_strcmp(npath, cwd); } static int path_height(const char *p) { int n; for (n = 0; *p != '\0'; ++p) n += *p == '/'; return n; } static void test_travel_to_root(void) { int l = path_height(cwd); const char *npath = cwd; for (npath = cwd; l != 0; l--) { const char *error; test_assert_idx(t_normpath_to("../", npath, &npath, &error) == 0, l); } test_assert_strcmp(npath, "/"); } static void test_extra_slashes(void) { const char *npath = NULL, *error = NULL; test_assert(t_normpath_to(".", cwd, &npath, &error) == 0); test_assert_strcmp(npath, cwd); test_assert(t_normpath_to("./", cwd, &npath, &error) == 0); test_assert_strcmp(npath, cwd); test_assert(t_normpath_to(".////", cwd, &npath, &error) == 0); test_assert_strcmp(npath, cwd); } static void test_nonexistent_path(void) { const char *npath = NULL, *error = NULL; const char *expected = t_strconcat(cwd, "/nonexistent", NULL); test_assert(t_normpath_to("nonexistent", cwd, &npath, &error) == 0); test_assert_strcmp(npath, expected); test_assert(t_realpath_to("nonexistent", cwd, &npath, &error) == -1); test_assert(error != NULL); } static void test_relative_dotdot(void) { const char *rel_path = "../"TEMP_DIRNAME; const char *npath = NULL, *error = NULL; test_assert(t_normpath_to(rel_path, tmpdir, &npath, &error) == 0); test_assert_strcmp(npath, tmpdir); test_assert(t_normpath_to("..", tmpdir, &npath, &error) == 0); test_assert_strcmp(npath, cwd); test_assert(t_normpath_to("../", tmpdir, &npath, &error) == 0); test_assert_strcmp(npath, cwd); test_assert(t_normpath_to("../.", tmpdir, &npath, &error) == 0); test_assert_strcmp(npath, cwd); } static void test_link1(void) { const char *old_dir, *npath = NULL, *error = NULL; test_assert(t_realpath_to(link1, "/", &npath, &error) == 0); test_assert_strcmp(npath, tmpdir); /* .../link1/link1/child */ test_assert(t_realpath_to(t_strconcat(link1, "/link1/child", NULL), "/", &npath, &error) == 0); test_assert_strcmp(npath, t_strconcat(tmpdir, "/child", NULL)); /* relative link1/link1/child */ if (t_get_working_dir(&old_dir, &error) < 0) i_fatal("t_get_working_dir() failed: %s", error); if (chdir(tmpdir) < 0) i_fatal("chdir(%s) failed: %m", tmpdir); test_assert(t_realpath(t_strconcat("link1", "/link1/child", NULL), &npath, &error) == 0); if (chdir(old_dir) < 0) i_fatal("chdir(%s) failed: %m", old_dir); } static void test_link4(void) { const char *npath = NULL, *error = NULL; test_assert(t_realpath_to(t_strconcat(link1, "/link4/child", NULL), "/", &npath, &error) == 0); test_assert_strcmp(npath, t_strconcat(tmpdir, "/child", NULL)); } static void test_link_loop(void) { const char *npath = NULL, *error = NULL; errno = 0; test_assert(t_realpath_to(link2, "/", &npath, &error) == -1); test_assert(errno == ELOOP); test_assert(error != NULL); } static void test_abspath_vs_normpath(void) { const char *abs = t_abspath_to("../../bin", "/usr/lib/"); test_assert_strcmp(abs, "/usr/lib//../../bin"); const char *norm = NULL, *error = NULL; test_assert(t_normpath_to("../../bin", "/usr///lib/", &norm, &error) == 0); test_assert_strcmp(norm, "/bin"); } static void create_links(const char *tmpdir) { link1 = t_strconcat(tmpdir, "/link1", NULL); if (symlink(tmpdir, link1) < 0) i_fatal("symlink(%s, %s) failed: %m", tmpdir, link1); const char *link1_child = t_strconcat(link1, "/child", NULL); int fd = creat(link1_child, 0600); if (fd == -1) i_fatal("creat(%s) failed: %m", link1_child); i_close_fd(&fd); /* link2 and link3 point to each other to create a loop */ link2 = t_strconcat(tmpdir, "/link2", NULL); link3 = t_strconcat(tmpdir, "/link3", NULL); if (symlink(link3, link2) < 0) i_fatal("symlink(%s, %s) failed: %m", link3, link2); if (symlink(link2, link3) < 0) i_fatal("symlink(%s, %s) failed: %m", link2, link3); /* link4 points to link1 */ link4 = t_strconcat(tmpdir, "/link4", NULL); if (symlink("link1", link4) < 0) i_fatal("symlink(link1, %s) failed: %m", link4); } static void test_link_alloc(void) { #define COMPONENT_COMPONENT "/component-component" const char *o_tmpdir; /* idea here is to make sure component-component would optimally hit to the nearest_power value. it has to be big enough to cause requirement for allocation in t_realpath. */ string_t *basedir = t_str_new(256); str_append(basedir, cwd); str_append(basedir, "/"TEMP_DIRNAME); size_t len = nearest_power(I_MAX(127, str_len(basedir) + strlen(COMPONENT_COMPONENT) + 1)) - strlen(COMPONENT_COMPONENT); while(str_len(basedir) < len) { str_append(basedir, COMPONENT_COMPONENT); (void)mkdir(str_c(basedir), 0700); } o_tmpdir = tmpdir; tmpdir = str_c(basedir); create_links(tmpdir); test_link1(); test_link_loop(); tmpdir = o_tmpdir; } static void test_link_alloc2(void) { const char *o_tmpdir; /* try enough different sized base directory lengths so the code hits the different reallocations and tests for off-by-one errors */ string_t *basedir = t_str_new(256); str_append(basedir, cwd); str_append(basedir, "/"TEMP_DIRNAME); str_append_c(basedir, '/'); size_t base_len = str_len(basedir); o_tmpdir = tmpdir; /* path_normalize() initially allocates 128 bytes, so we'll test paths up to that length+1. */ unsigned char buf[128+1]; memset(buf, 'x', sizeof(buf)); for (size_t i = 1; i <= sizeof(buf); i++) { str_truncate(basedir, base_len); str_append_data(basedir, buf, i); tmpdir = str_c(basedir); (void)mkdir(str_c(basedir), 0700); create_links(tmpdir); test_link1(); test_link_loop(); } tmpdir = o_tmpdir; } static void test_cleanup(void) { const char *error; if (unlink_directory(tmpdir, UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0) i_error("unlink_directory() failed: %s", error); } static void test_init(void) { const char *error; test_assert(t_get_working_dir(&cwd, &error) == 0); tmpdir = t_strconcat(cwd, "/"TEMP_DIRNAME, NULL); test_cleanup(); if (mkdir(tmpdir, 0700) < 0) { i_fatal("mkdir: %m"); } create_links(tmpdir); } void test_path_util(void) { test_begin("test_path_util"); alarm(20); test_init(); test_local_path(); test_absolute_path_no_change(); test_travel_to_root(); test_extra_slashes(); test_nonexistent_path(); test_relative_dotdot(); test_link1(); test_link4(); test_link_loop(); test_abspath_vs_normpath(); test_link_alloc(); test_link_alloc2(); test_cleanup(); alarm(0); test_end(); } dovecot-2.3.21.1/src/lib/data-stack.c0000644000000000000000000005123614656633576014041 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ /* @UNSAFE: whole file */ #include "lib.h" #include "backtrace-string.h" #include "str.h" #include "data-stack.h" /* Initial stack size - this should be kept in a size that doesn't exceed in a normal use to avoid extra malloc()ing. */ #ifdef DEBUG # define INITIAL_STACK_SIZE (1024*10) #else # define INITIAL_STACK_SIZE (1024*32) #endif #ifdef DEBUG # define CLEAR_CHR 0xD5 /* D5 is mnemonic for "Data 5tack" */ # define SENTRY_COUNT (4*8) # define BLOCK_CANARY ((void *)0xBADBADD5BADBADD5) /* contains 'D5' */ # define ALLOC_SIZE(size) (MEM_ALIGN(sizeof(size_t)) + MEM_ALIGN(size + SENTRY_COUNT)) #else # define CLEAR_CHR 0 # define BLOCK_CANARY NULL # define block_canary_check(block) do { ; } while(0) # define ALLOC_SIZE(size) MEM_ALIGN(size) #endif struct stack_block { struct stack_block *prev, *next; size_t size, left; #ifdef DEBUG /* The lowest value that "left" has been in this block since it was last popped. This is used to keep track which parts of the block needs to be cleared if DEBUG is used. */ size_t left_lowwater; #endif /* NULL or a poison value, just in case something accesses the memory in front of an allocated area */ void *canary; unsigned char data[FLEXIBLE_ARRAY_MEMBER]; }; #define SIZEOF_MEMBLOCK MEM_ALIGN(sizeof(struct stack_block)) #define STACK_BLOCK_DATA(block) \ (block->data + (SIZEOF_MEMBLOCK - sizeof(struct stack_block))) struct stack_frame { struct stack_frame *prev; struct stack_block *block; /* Each frame initializes this to current_block->left, i.e. how much free space is left in the block. So the frame's start position in the block is (block.size - block_space_left) */ size_t block_space_left; size_t last_alloc_size; const char *marker; #ifdef DEBUG /* Fairly arbitrary profiling data */ unsigned long long alloc_bytes; unsigned int alloc_count; #endif }; #ifdef STATIC_CHECKER struct data_stack_frame { unsigned int id; }; #endif unsigned int data_stack_frame_id = 0; static bool data_stack_initialized = FALSE; static data_stack_frame_t root_frame_id; static struct stack_frame *current_frame; /* The latest block currently used for allocation. current_block->next is always NULL. */ static struct stack_block *current_block; /* The largest block that data stack has allocated so far, which was already freed. This can prevent rapid malloc()+free()ing when data stack is grown and shrunk constantly. */ static struct stack_block *unused_block = NULL; static struct event *event_datastack = NULL; static bool event_datastack_deinitialized = FALSE; static struct stack_block *last_buffer_block; static size_t last_buffer_size; static bool outofmem = FALSE; static union { struct stack_block block; unsigned char data[512]; } outofmem_area; static struct stack_block *mem_block_alloc(size_t min_size); static inline unsigned char *data_stack_after_last_alloc(struct stack_block *block) { return STACK_BLOCK_DATA(block) + (block->size - block->left); } static void data_stack_last_buffer_reset(bool preserve_data ATTR_UNUSED) { if (last_buffer_block != NULL) { #ifdef DEBUG unsigned char *last_alloc_end, *p, *pend; /* We assume that this function gets called before current_block changes. */ i_assert(last_buffer_block == current_block); last_alloc_end = data_stack_after_last_alloc(current_block); p = last_alloc_end + MEM_ALIGN(sizeof(size_t)) + last_buffer_size; pend = last_alloc_end + ALLOC_SIZE(last_buffer_size); #endif /* reset t_buffer_get() mark - not really needed but makes it easier to notice if t_malloc()/t_push()/t_pop() is called between t_buffer_get() and t_buffer_alloc(). do this before we get to i_panic() to avoid recursive panics. */ last_buffer_block = NULL; #ifdef DEBUG /* NOTE: If the below panic triggers, it may also be due to an internal bug in data-stack (since this is rather complex). While debugging whether that is the case, it's a good idea to change the i_panic() to abort(). Otherwise the i_panic() changes the data-stack's internal state and complicates debugging. */ while (p < pend) if (*p++ != CLEAR_CHR) i_panic("t_buffer_get(): buffer overflow"); if (!preserve_data) { p = last_alloc_end; memset(p, CLEAR_CHR, SENTRY_COUNT); } #endif } } data_stack_frame_t t_push(const char *marker) { struct stack_frame *frame; i_assert(marker != NULL); if (unlikely(!data_stack_initialized)) { /* kludgy, but allow this before initialization */ data_stack_init(); return t_push(marker); } /* allocate new block */ frame = t_buffer_get(sizeof(*frame)); frame->prev = current_frame; current_frame = frame; /* mark our current position */ current_frame->block = current_block; current_frame->block_space_left = current_block->left; current_frame->last_alloc_size = 0; current_frame->marker = marker; #ifdef DEBUG current_frame->alloc_bytes = 0; current_frame->alloc_count = 0; #endif t_buffer_alloc(sizeof(*frame)); #ifndef STATIC_CHECKER return data_stack_frame_id++; #else struct data_stack_frame *ds_frame = i_new(struct data_stack_frame, 1); ds_frame->id = data_stack_frame_id++; return ds_frame; #endif } data_stack_frame_t t_push_named(const char *format, ...) { data_stack_frame_t ret = t_push(format); #ifdef DEBUG va_list args; va_start(args, format); current_frame->marker = p_strdup_vprintf(unsafe_data_stack_pool, format, args); va_end(args); #else (void)format; /* unused in non-DEBUG builds */ #endif return ret; } #ifdef DEBUG static void block_canary_check(struct stack_block *block) { if (block->canary != BLOCK_CANARY) { /* Make sure i_panic() won't try to allocate from the same block by falling back onto our emergency block. */ current_block = &outofmem_area.block; i_panic("Corrupted data stack canary"); } } #endif static void free_blocks(struct stack_block *block) { struct stack_block *next; /* free all the blocks, except if any of them is bigger than unused_block, replace it */ while (block != NULL) { block_canary_check(block); next = block->next; #ifdef DEBUG memset(STACK_BLOCK_DATA(block), CLEAR_CHR, block->size); #endif if (block == &outofmem_area.block) ; else if (unused_block == NULL || block->size > unused_block->size) { free(unused_block); unused_block = block; } else { free(block); } block = next; } } #ifdef DEBUG static void t_pop_verify(void) { struct stack_block *block; unsigned char *p; size_t pos, max_pos, used_size; block = current_frame->block; pos = block->size - current_frame->block_space_left; while (block != NULL) { block_canary_check(block); used_size = block->size - block->left; p = STACK_BLOCK_DATA(block); while (pos < used_size) { size_t requested_size = *(size_t *)(p + pos); if (used_size - pos < requested_size) i_panic("data stack[%s]: saved alloc size broken", current_frame->marker); max_pos = pos + ALLOC_SIZE(requested_size); pos += MEM_ALIGN(sizeof(size_t)) + requested_size; for (; pos < max_pos; pos++) { if (p[pos] != CLEAR_CHR) i_panic("data stack[%s]: buffer overflow", current_frame->marker); } } /* if we had used t_buffer_get(), the rest of the buffer may not contain CLEAR_CHRs. but we've already checked all the allocations, so there's no need to check them anyway. */ block = block->next; pos = 0; } } #endif void t_pop_last_unsafe(void) { size_t block_space_left; if (unlikely(current_frame == NULL)) i_panic("t_pop() called with empty stack"); data_stack_last_buffer_reset(FALSE); #ifdef DEBUG t_pop_verify(); #endif /* Usually the block doesn't change. If it doesn't, the next pointer must also be NULL. */ if (current_block != current_frame->block) { current_block = current_frame->block; if (current_block->next != NULL) { /* free unused blocks */ free_blocks(current_block->next); current_block->next = NULL; } } block_canary_check(current_block); /* current_frame points inside the stack frame that will be freed. make sure it's not accessed after it's already freed/cleaned. */ block_space_left = current_frame->block_space_left; current_frame = current_frame->prev; #ifdef DEBUG size_t start_pos, end_pos; start_pos = current_block->size - block_space_left; end_pos = current_block->size - current_block->left_lowwater; i_assert(end_pos >= start_pos); memset(STACK_BLOCK_DATA(current_block) + start_pos, CLEAR_CHR, end_pos - start_pos); current_block->left_lowwater = block_space_left; #endif current_block->left = block_space_left; data_stack_frame_id--; } bool t_pop(data_stack_frame_t *id) { t_pop_last_unsafe(); #ifndef STATIC_CHECKER if (unlikely(data_stack_frame_id != *id)) return FALSE; *id = 0; #else unsigned int frame_id = (*id)->id; i_free_and_null(*id); if (unlikely(data_stack_frame_id != frame_id)) return FALSE; #endif return TRUE; } bool t_pop_pass_str(data_stack_frame_t *id, const char **str) { if (str == NULL || !data_stack_frame_contains(id, *str)) return t_pop(id); /* FIXME: The string could be memmove()d to the beginning of the data stack frame and the previous frame's size extended past it. This would avoid the malloc. It's a bit complicated though. */ char *tmp_str = i_strdup(*str); bool ret = t_pop(id); *str = t_strdup(tmp_str); i_free(tmp_str); return ret; } static void mem_block_reset(struct stack_block *block) { block->prev = NULL; block->next = NULL; block->left = block->size; #ifdef DEBUG block->left_lowwater = block->size; #endif } static struct stack_block *mem_block_alloc(size_t min_size) { struct stack_block *block; size_t prev_size, alloc_size; prev_size = current_block == NULL ? 0 : current_block->size; /* Use INITIAL_STACK_SIZE without growing it to nearest power. */ alloc_size = prev_size == 0 ? min_size : nearest_power(MALLOC_ADD(prev_size, min_size)); /* nearest_power() returns 2^n values, so alloc_size can't be anywhere close to SIZE_MAX */ block = malloc(SIZEOF_MEMBLOCK + alloc_size); if (unlikely(block == NULL)) { if (outofmem) { if (min_size > outofmem_area.block.left) abort(); return &outofmem_area.block; } outofmem = TRUE; i_panic("data stack: Out of memory when allocating %zu bytes", alloc_size + SIZEOF_MEMBLOCK); } block->size = alloc_size; block->canary = BLOCK_CANARY; mem_block_reset(block); #ifdef DEBUG memset(STACK_BLOCK_DATA(block), CLEAR_CHR, alloc_size); #endif return block; } static void data_stack_send_grow_event(size_t last_alloc_size) { if (event_datastack_deinitialized) { /* already in the deinitialization code - don't send more events */ return; } if (event_datastack == NULL) event_datastack = event_create(NULL); event_set_name(event_datastack, "data_stack_grow"); event_add_int(event_datastack, "alloc_size", data_stack_get_alloc_size()); event_add_int(event_datastack, "used_size", data_stack_get_used_size()); event_add_int(event_datastack, "last_alloc_size", last_alloc_size); event_add_int(event_datastack, "last_block_size", current_block->size); #ifdef DEBUG event_add_int(event_datastack, "frame_alloc_bytes", current_frame->alloc_bytes); event_add_int(event_datastack, "frame_alloc_count", current_frame->alloc_count); #endif event_add_str(event_datastack, "frame_marker", current_frame->marker); /* It's possible that the data stack gets grown and shrunk rapidly. Try to avoid doing expensive work if the event isn't even used for anything. Note that at this point all the event fields must be set already that might potentially be used by the filters. */ if (!event_want_debug(event_datastack)) return; /* Getting backtrace is potentially inefficient, so do it after checking if the event is wanted. Note that this prevents using the backtrace field in event field comparisons. */ const char *backtrace; if (backtrace_get(&backtrace) == 0) event_add_str(event_datastack, "backtrace", backtrace); string_t *str = t_str_new(128); str_printfa(str, "total_used=%zu, total_alloc=%zu, last_alloc_size=%zu", data_stack_get_used_size(), data_stack_get_alloc_size(), last_alloc_size); #ifdef DEBUG str_printfa(str, ", frame_bytes=%llu, frame_alloc_count=%u", current_frame->alloc_bytes, current_frame->alloc_count); #endif e_debug(event_datastack, "Growing data stack by %zu for '%s' (%s)", current_block->size, current_frame->marker, str_c(str)); } static void *t_malloc_real(size_t size, bool permanent) { void *ret; size_t alloc_size; bool warn = FALSE; #ifdef DEBUG int old_errno = errno; #endif if (unlikely(size == 0 || size > SSIZE_T_MAX)) i_panic("Trying to allocate %zu bytes", size); if (unlikely(!data_stack_initialized)) { /* kludgy, but allow this before initialization */ data_stack_init(); } block_canary_check(current_block); /* allocate only aligned amount of memory so alignment comes always properly */ alloc_size = ALLOC_SIZE(size); #ifdef DEBUG if(permanent) { current_frame->alloc_bytes += alloc_size; current_frame->alloc_count++; } #endif data_stack_last_buffer_reset(TRUE); if (permanent) { /* used for t_try_realloc() */ current_frame->last_alloc_size = alloc_size; } if (current_block->left < alloc_size) { struct stack_block *block; /* current block is full, see if we can use the unused_block */ if (unused_block != NULL && unused_block->size >= alloc_size) { block = unused_block; unused_block = NULL; mem_block_reset(block); } else { /* current block is full, allocate a new one */ block = mem_block_alloc(alloc_size); warn = TRUE; } /* The newly allocated block will replace the current_block, i.e. current_block always points to the last element in the linked list. */ block->prev = current_block; current_block->next = block; current_block = block; } /* enough space in current block, use it */ ret = data_stack_after_last_alloc(current_block); #ifdef DEBUG if (current_block->left - alloc_size < current_block->left_lowwater) current_block->left_lowwater = current_block->left - alloc_size; #endif if (permanent) current_block->left -= alloc_size; if (warn) T_BEGIN { /* sending event can cause errno changes. */ #ifdef DEBUG i_assert(errno == old_errno); #else int old_errno = errno; #endif /* warn after allocation, so if e_debug() wants to allocate more memory we don't go to infinite loop */ data_stack_send_grow_event(alloc_size); /* reset errno back to what it was */ errno = old_errno; } T_END; #ifdef DEBUG memcpy(ret, &size, sizeof(size)); ret = PTR_OFFSET(ret, MEM_ALIGN(sizeof(size))); /* make sure the sentry contains CLEAR_CHRs. it might not if we had used t_buffer_get(). */ memset(PTR_OFFSET(ret, size), CLEAR_CHR, MEM_ALIGN(size + SENTRY_COUNT) - size); /* we rely on errno not changing. it shouldn't. */ i_assert(errno == old_errno); #endif return ret; } void *t_malloc_no0(size_t size) { return t_malloc_real(size, TRUE); } void *t_malloc0(size_t size) { void *mem; mem = t_malloc_real(size, TRUE); memset(mem, 0, size); return mem; } bool ATTR_NO_SANITIZE_INTEGER t_try_realloc(void *mem, size_t size) { size_t debug_adjust = 0, last_alloc_size; unsigned char *after_last_alloc; if (unlikely(size == 0 || size > SSIZE_T_MAX)) i_panic("Trying to allocate %zu bytes", size); block_canary_check(current_block); data_stack_last_buffer_reset(TRUE); last_alloc_size = current_frame->last_alloc_size; /* see if we're trying to grow the memory we allocated last */ after_last_alloc = data_stack_after_last_alloc(current_block); #ifdef DEBUG debug_adjust = MEM_ALIGN(sizeof(size_t)); #endif if (after_last_alloc - last_alloc_size + debug_adjust == mem) { /* yeah, see if we have space to grow */ size_t new_alloc_size, alloc_growth; new_alloc_size = ALLOC_SIZE(size); alloc_growth = (new_alloc_size - last_alloc_size); #ifdef DEBUG size_t old_raw_size; /* sorry, non-C99 users - add braces if you need them */ old_raw_size = *(size_t *)PTR_OFFSET(mem, -(ptrdiff_t)MEM_ALIGN(sizeof(size_t))); i_assert(ALLOC_SIZE(old_raw_size) == last_alloc_size); /* Only check one byte for over-run, that catches most offenders who are likely to use t_try_realloc() */ i_assert(((unsigned char*)mem)[old_raw_size] == CLEAR_CHR); #endif if (current_block->left >= alloc_growth) { /* just shrink the available size */ current_block->left -= alloc_growth; current_frame->last_alloc_size = new_alloc_size; #ifdef DEBUG if (current_block->left < current_block->left_lowwater) current_block->left_lowwater = current_block->left; /* All reallocs are permanent by definition However, they don't count as a new allocation */ current_frame->alloc_bytes += alloc_growth; *(size_t *)PTR_OFFSET(mem, -(ptrdiff_t)MEM_ALIGN(sizeof(size_t))) = size; memset(PTR_OFFSET(mem, size), CLEAR_CHR, new_alloc_size - size - MEM_ALIGN(sizeof(size_t))); #endif return TRUE; } } return FALSE; } size_t t_get_bytes_available(void) { block_canary_check(current_block); #ifndef DEBUG const unsigned int min_extra = 0; #else const unsigned int min_extra = SENTRY_COUNT + MEM_ALIGN(sizeof(size_t)); if (current_block->left < min_extra) return 0; #endif size_t size = current_block->left - min_extra; i_assert(ALLOC_SIZE(size) == current_block->left); return size; } void *t_buffer_get(size_t size) { void *ret; ret = t_malloc_real(size, FALSE); last_buffer_size = size; last_buffer_block = current_block; return ret; } void *t_buffer_reget(void *buffer, size_t size) { size_t old_size; void *new_buffer; old_size = last_buffer_size; if (size <= old_size) return buffer; new_buffer = t_buffer_get(size); if (new_buffer != buffer) memcpy(new_buffer, buffer, old_size); return new_buffer; } void t_buffer_alloc(size_t size) { i_assert(last_buffer_block != NULL); i_assert(last_buffer_size >= size); i_assert(current_block->left >= size); /* we've already reserved the space, now we just mark it used */ (void)t_malloc_real(size, TRUE); } void t_buffer_alloc_last_full(void) { if (last_buffer_block != NULL) (void)t_malloc_real(last_buffer_size, TRUE); } bool data_stack_frame_contains(data_stack_frame_t *id, const void *_ptr) { const unsigned char *block_data, *ptr = _ptr; const struct stack_block *block; unsigned int wanted_frame_id; size_t block_start_pos, block_used; /* first handle the fast path - NULL can never be within the frame */ if (ptr == NULL) return FALSE; #ifndef STATIC_CHECKER wanted_frame_id = *id; #else wanted_frame_id = (*id)->id; #endif /* Too much effort to support more than the latest frame. It's the only thing that is currently needed anyway. */ i_assert(wanted_frame_id+1 == data_stack_frame_id); block = current_frame->block; i_assert(block != NULL); /* See if it's in the frame's first block. Only the data after block_start_pos belong to this frame. */ block_data = STACK_BLOCK_DATA(block); block_start_pos = block->size - current_frame->block_space_left; block_used = block->size - block->left; if (ptr >= block_data + block_start_pos && ptr <= block_data + block_used) return TRUE; /* See if it's in the other blocks. All the data in them belong to this frame. */ for (block = block->next; block != NULL; block = block->next) { block_data = STACK_BLOCK_DATA(block); block_used = block->size - block->left; if (ptr >= block_data && ptr < block_data + block_used) return TRUE; } return FALSE; } size_t data_stack_get_alloc_size(void) { struct stack_block *block; size_t size = 0; i_assert(current_block->next == NULL); for (block = current_block; block != NULL; block = block->prev) size += block->size; return size; } size_t data_stack_get_used_size(void) { struct stack_block *block; size_t size = 0; i_assert(current_block->next == NULL); for (block = current_block; block != NULL; block = block->prev) size += block->size - block->left; return size; } void data_stack_free_unused(void) { free(unused_block); unused_block = NULL; } void data_stack_init(void) { if (data_stack_initialized) { /* already initialized (we did auto-initialization in t_malloc/t_push) */ return; } data_stack_initialized = TRUE; data_stack_frame_id = 1; outofmem_area.block.size = outofmem_area.block.left = sizeof(outofmem_area) - sizeof(outofmem_area.block); outofmem_area.block.canary = BLOCK_CANARY; current_block = mem_block_alloc(INITIAL_STACK_SIZE); current_frame = NULL; last_buffer_block = NULL; last_buffer_size = 0; root_frame_id = t_push("data_stack_init"); } void data_stack_deinit_event(void) { event_unref(&event_datastack); event_datastack_deinitialized = TRUE; } void data_stack_deinit(void) { if (!t_pop(&root_frame_id) || current_frame != NULL) i_panic("Missing t_pop() call"); free(current_block); current_block = NULL; data_stack_free_unused(); } dovecot-2.3.21.1/src/lib/lib.c0000644000000000000000000001156114656633576012570 00000000000000/* Copyright (c) 2001-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "dovecot-version.h" #include "array.h" #include "event-filter.h" #include "env-util.h" #include "hostpid.h" #include "ipwd.h" #include "process-title.h" #include "restrict-access.h" #include "var-expand-private.h" #include "randgen.h" #include #include #include /* Mainly for including the full version information in core dumps. NOTE: Don't set this const - otherwise it won't end up in core dumps. */ char dovecot_build_info[] = DOVECOT_BUILD_INFO; static bool lib_initialized = FALSE; int dev_null_fd = -1; struct atexit_callback { int priority; lib_atexit_callback_t *callback; }; static ARRAY(struct atexit_callback) atexit_callbacks = ARRAY_INIT; static bool lib_clean_exit; #undef i_unlink int i_unlink(const char *path, const char *source_fname, unsigned int source_linenum) { if (unlink(path) < 0) { i_error("unlink(%s) failed: %m (in %s:%u)", path, source_fname, source_linenum); return -1; } return 0; } #undef i_unlink_if_exists int i_unlink_if_exists(const char *path, const char *source_fname, unsigned int source_linenum) { if (unlink(path) == 0) return 1; else if (errno == ENOENT) return 0; else { i_error("unlink(%s) failed: %m (in %s:%u)", path, source_fname, source_linenum); return -1; } } void i_getopt_reset(void) { #ifdef __GLIBC__ /* a) for subcommands allow -options anywhere in command line b) this is actually required for the reset to work (glibc bug?) */ optind = 0; #else optind = 1; #endif } void lib_atexit(lib_atexit_callback_t *callback) { lib_atexit_priority(callback, 0); } void lib_atexit_priority(lib_atexit_callback_t *callback, int priority) { struct atexit_callback *cb; const struct atexit_callback *callbacks; unsigned int i, count; if (!array_is_created(&atexit_callbacks)) i_array_init(&atexit_callbacks, 8); else { /* skip if it's already added */ callbacks = array_get(&atexit_callbacks, &count); for (i = count; i > 0; i--) { if (callbacks[i-1].callback == callback) { i_assert(callbacks[i-1].priority == priority); return; } } } cb = array_append_space(&atexit_callbacks); cb->priority = priority; cb->callback = callback; } static int atexit_callback_priority_cmp(const struct atexit_callback *cb1, const struct atexit_callback *cb2) { return cb1->priority - cb2->priority; } void lib_atexit_run(void) { const struct atexit_callback *cb; if (array_is_created(&atexit_callbacks)) { array_sort(&atexit_callbacks, atexit_callback_priority_cmp); array_foreach(&atexit_callbacks, cb) (*cb->callback)(); array_free(&atexit_callbacks); } } static void lib_open_non_stdio_dev_null(void) { dev_null_fd = open("/dev/null", O_WRONLY); if (dev_null_fd == -1) i_fatal("open(/dev/null) failed: %m"); /* Make sure stdin, stdout and stderr fds exist. We especially rely on stderr being available and a lot of code doesn't like fd being 0. We'll open /dev/null as write-only also for stdin, since if any reads are attempted from it we'll want them to fail. */ while (dev_null_fd < STDERR_FILENO) { dev_null_fd = dup(dev_null_fd); if (dev_null_fd == -1) i_fatal("dup(/dev/null) failed: %m"); } /* close the actual /dev/null fd on exec*(), but keep it in stdio fds */ fd_close_on_exec(dev_null_fd, TRUE); } void lib_set_clean_exit(bool set) { lib_clean_exit = set; } void lib_exit(int status) { lib_set_clean_exit(TRUE); exit(status); } static void lib_atexit_handler(void) { /* We're already in exit code path. Avoid using any functions that might cause strange breakage. Especially anything that could call exit() again could cause infinite looping in some OSes. */ if (!lib_clean_exit) { const char *error = "Unexpected exit - converting to abort\n"; if (write(STDERR_FILENO, error, strlen(error)) < 0) { /* ignore */ } abort(); } } void lib_init(void) { i_assert(!lib_initialized); random_init(); data_stack_init(); hostpid_init(); lib_open_non_stdio_dev_null(); lib_event_init(); event_filter_init(); var_expand_extensions_init(); /* Default to clean exit. Otherwise there would be too many accidents with e.g. command line parsing errors that try to return instead of using lib_exit(). master_service_init_finish() will change this again to be FALSE. */ lib_set_clean_exit(TRUE); atexit(lib_atexit_handler); lib_initialized = TRUE; } bool lib_is_initialized(void) { return lib_initialized; } void lib_deinit(void) { i_assert(lib_initialized); lib_initialized = FALSE; lib_atexit_run(); ipwd_deinit(); hostpid_deinit(); var_expand_extensions_deinit(); event_filter_deinit(); data_stack_deinit_event(); lib_event_deinit(); restrict_access_deinit(); i_close_fd(&dev_null_fd); data_stack_deinit(); failures_deinit(); process_title_deinit(); random_deinit(); lib_clean_exit = TRUE; } dovecot-2.3.21.1/src/lib/file-lock.h0000644000000000000000000000670114656633576013674 00000000000000#ifndef FILE_LOCK_H #define FILE_LOCK_H #include #include #define DEFAULT_LOCK_TIMEOUT 120 struct file_lock; struct dotlock; enum file_lock_method { FILE_LOCK_METHOD_FCNTL, FILE_LOCK_METHOD_FLOCK, FILE_LOCK_METHOD_DOTLOCK }; struct file_lock_settings { enum file_lock_method lock_method; /* When the lock is freed, close the fd automatically. This can be useful for files that are only created to exist as lock files. */ bool unlink_on_free:1; /* When the lock is freed, unlink() the file automatically, unless other processes are already waiting on the lock. This can be useful for files that are only created to exist as lock files. */ bool close_on_free:1; /* Do not panic when the kernel returns EDEADLK while acquiring the lock. */ bool allow_deadlock:1; }; /* Parse lock method from given string. Returns TRUE if ok, FALSE if name is unknown. */ bool file_lock_method_parse(const char *name, enum file_lock_method *method_r); /* Convert lock method to string. */ const char *file_lock_method_to_str(enum file_lock_method method); /* Lock the file. Returns 1 if successful, 0 if file is already locked, or -1 if error. lock_type is F_WRLCK or F_RDLCK. */ int file_try_lock(int fd, const char *path, int lock_type, const struct file_lock_settings *set, struct file_lock **lock_r, const char **error_r); /* Like lock_try_lock(), but return 0 only after having tried to lock for timeout_secs. */ int file_wait_lock(int fd, const char *path, int lock_type, const struct file_lock_settings *set, unsigned int timeout_secs, struct file_lock **lock_r, const char **error_r); /* Change the lock type. WARNING: This isn't an atomic operation! The result is the same as file_unlock() + file_try_lock(). */ int file_lock_try_update(struct file_lock *lock, int lock_type); /* When the lock is freed, unlink() the file automatically, unless other processes are already waiting on the lock. This can be useful for files that are only created to exist as lock files. */ void file_lock_set_unlink_on_free(struct file_lock *lock, bool set); /* When the lock is freed, close the fd automatically. This can be useful for files that are only created to exist as lock files. */ void file_lock_set_close_on_free(struct file_lock *lock, bool set); /* Convert dotlock into file_lock, which can be deleted with either file_unlock() or file_lock_free(). */ struct file_lock *file_lock_from_dotlock(struct dotlock **dotlock); /* Unlock and free the lock. */ void file_unlock(struct file_lock **lock); /* Free the lock without unlocking it (because you're closing the fd anyway). */ void file_lock_free(struct file_lock **lock); /* Returns the path given as parameter to file_*lock*(). */ const char *file_lock_get_path(struct file_lock *lock); /* Update lock file's path (after it gets renamed by the caller). This is useful mainly together with file_lock_set_unlink_on_free(). */ void file_lock_set_path(struct file_lock *lock, const char *path); /* Returns human-readable string containing the process that has the file currently locked. Returns "" if unknown, otherwise " (string)". */ const char *file_lock_find(int lock_fd, enum file_lock_method lock_method, int lock_type); /* Track the duration of a lock wait. */ void file_lock_wait_start(void); void file_lock_wait_end(const char *lock_name); /* Return how many microseconds has been spent on lock waiting. */ uint64_t file_lock_wait_get_total_usecs(void); #endif dovecot-2.3.21.1/src/lib/priorityq.c0000644000000000000000000000736714656633576014075 00000000000000/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "priorityq.h" /* Macros for moving inside an array implementation of binary tree where [0] is the root. */ #define PARENT_IDX(idx) \ (((idx) - 1) / 2) #define LEFT_CHILD_IDX(idx) \ ((idx) * 2 + 1) #define RIGHT_CHILD_IDX(idx) \ ((idx) * 2 + 2) struct priorityq { priorityq_cmp_callback_t *cmp_callback; ARRAY(struct priorityq_item *) items; }; struct priorityq * priorityq_init(priorityq_cmp_callback_t *cmp_callback, unsigned int init_size) { struct priorityq *pq; pq = i_new(struct priorityq, 1); pq->cmp_callback = cmp_callback; i_array_init(&pq->items, init_size); return pq; } void priorityq_deinit(struct priorityq **_pq) { struct priorityq *pq = *_pq; *_pq = NULL; array_free(&pq->items); i_free(pq); } unsigned int priorityq_count(const struct priorityq *pq) { return array_count(&pq->items); } static void heap_items_swap(struct priorityq_item **items, unsigned int idx1, unsigned int idx2) { struct priorityq_item *tmp; /* swap the item indexes */ i_assert(items[idx1]->idx == idx1); i_assert(items[idx2]->idx == idx2); items[idx1]->idx = idx2; items[idx2]->idx = idx1; /* swap the item pointers */ tmp = items[idx1]; items[idx1] = items[idx2]; items[idx2] = tmp; } static unsigned int heap_item_bubble_up(struct priorityq *pq, unsigned int idx) { struct priorityq_item **items; unsigned int parent_idx, count; items = array_get_modifiable(&pq->items, &count); while (idx > 0) { parent_idx = PARENT_IDX(idx); i_assert(idx < count); if (pq->cmp_callback(items[idx], items[parent_idx]) >= 0) break; /* wrong order - swap */ heap_items_swap(items, idx, parent_idx); idx = parent_idx; } return idx; } static void heap_item_bubble_down(struct priorityq *pq, unsigned int idx) { struct priorityq_item **items; unsigned int left_idx, right_idx, min_child_idx, count; items = array_get_modifiable(&pq->items, &count); while ((left_idx = LEFT_CHILD_IDX(idx)) < count) { right_idx = RIGHT_CHILD_IDX(idx); if (right_idx >= count || pq->cmp_callback(items[left_idx], items[right_idx]) < 0) min_child_idx = left_idx; else min_child_idx = right_idx; if (pq->cmp_callback(items[min_child_idx], items[idx]) >= 0) break; /* wrong order - swap */ heap_items_swap(items, idx, min_child_idx); idx = min_child_idx; } } void priorityq_add(struct priorityq *pq, struct priorityq_item *item) { item->idx = array_count(&pq->items); array_push_back(&pq->items, &item); (void)heap_item_bubble_up(pq, item->idx); } static void priorityq_remove_idx(struct priorityq *pq, unsigned int idx) { struct priorityq_item **items; unsigned int count; items = array_get_modifiable(&pq->items, &count); i_assert(idx < count); /* move last item over the removed one and fix the heap */ count--; heap_items_swap(items, idx, count); array_delete(&pq->items, count, 1); if (count > 0 && idx != count) { if (idx > 0) idx = heap_item_bubble_up(pq, idx); heap_item_bubble_down(pq, idx); } } void priorityq_remove(struct priorityq *pq, struct priorityq_item *item) { priorityq_remove_idx(pq, item->idx); item->idx = UINT_MAX; } struct priorityq_item *priorityq_peek(struct priorityq *pq) { struct priorityq_item *const *items; if (array_count(&pq->items) == 0) return NULL; items = array_front(&pq->items); return items[0]; } struct priorityq_item *priorityq_pop(struct priorityq *pq) { struct priorityq_item *item; item = priorityq_peek(pq); if (item != NULL) { priorityq_remove_idx(pq, 0); item->idx = UINT_MAX; } return item; } struct priorityq_item *const *priorityq_items(struct priorityq *pq) { if (array_count(&pq->items) == 0) return NULL; return array_front(&pq->items); } dovecot-2.3.21.1/src/lib/primes.c0000644000000000000000000000134114656633576013314 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "primes.h" static const unsigned int primes[] = { #define PRIME_SKIP_COUNT 3 17, 37, 67, 131, 257, /* next from 2^8 */ 521, 1031, 2053, 4099, 8209, 16411, 32771, 65537, /* next from 2^16 */ 131101, 262147, 524309, 1048583, 2097169, 4194319, 8388617, 16777259, /* next from 2^24 */ 33554467, 67108879, 134217757, 268435459, 536870923, 1073741827, 2147483659U, 4294967291U /* previous from 2^32 */ }; unsigned int primes_closest(unsigned int num) { unsigned int i; for (i = 31; i > PRIME_SKIP_COUNT; i--) { if ((num & (1U << i)) != 0) return primes[i - PRIME_SKIP_COUNT]; } return primes[0]; } dovecot-2.3.21.1/src/lib/test-json-tree.c0000644000000000000000000000723014656633576014703 00000000000000/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "json-tree.h" struct { enum json_type type; const char *value; } test_input[] = { { JSON_TYPE_OBJECT_KEY, "key-str" }, { JSON_TYPE_STRING, "string" }, { JSON_TYPE_OBJECT_KEY, "key-num" }, { JSON_TYPE_NUMBER, "1234" }, { JSON_TYPE_OBJECT_KEY, "key-true" }, { JSON_TYPE_TRUE, "true" }, { JSON_TYPE_OBJECT_KEY, "key-false" }, { JSON_TYPE_FALSE, "false" }, { JSON_TYPE_OBJECT_KEY, "key-null" }, { JSON_TYPE_NULL, NULL }, { JSON_TYPE_OBJECT_KEY, "key-obj-empty" }, { JSON_TYPE_OBJECT, NULL }, { JSON_TYPE_OBJECT_END, NULL }, { JSON_TYPE_OBJECT_KEY, "key-obj" }, { JSON_TYPE_OBJECT, NULL }, { JSON_TYPE_OBJECT_KEY, "sub" }, { JSON_TYPE_STRING, "value" }, { JSON_TYPE_OBJECT_END, NULL }, { JSON_TYPE_OBJECT_KEY, "key-arr-empty" }, { JSON_TYPE_ARRAY, NULL }, { JSON_TYPE_ARRAY_END, NULL }, { JSON_TYPE_OBJECT_KEY, "key-arr" }, { JSON_TYPE_ARRAY, NULL }, { JSON_TYPE_STRING, "foo" }, { JSON_TYPE_ARRAY, NULL }, { JSON_TYPE_TRUE, "true" }, { JSON_TYPE_ARRAY_END, NULL }, { JSON_TYPE_OBJECT, NULL }, { JSON_TYPE_OBJECT_KEY, "aobj" }, { JSON_TYPE_ARRAY, NULL }, { JSON_TYPE_ARRAY_END, NULL }, { JSON_TYPE_OBJECT_END, NULL }, { JSON_TYPE_OBJECT, NULL }, { JSON_TYPE_OBJECT_KEY, "aobj-key" }, { JSON_TYPE_STRING, "value1" }, { JSON_TYPE_OBJECT_END, NULL }, { JSON_TYPE_OBJECT, NULL }, { JSON_TYPE_OBJECT_KEY, "aobj-key" }, { JSON_TYPE_STRING, "value2" }, { JSON_TYPE_OBJECT_END, NULL }, { JSON_TYPE_ARRAY_END, NULL } }; void test_json_tree(void) { struct json_tree *tree; const struct json_tree_node *root, *node, *node1, *node2; unsigned int i; test_begin("json tree"); tree = json_tree_init(); for (i = 0; i < N_ELEMENTS(test_input); i++) { test_assert_idx(json_tree_append(tree, test_input[i].type, test_input[i].value) == 0, i); } root = json_tree_root(tree); i_assert(root != NULL); test_assert(root->value_type == JSON_TYPE_OBJECT); i_assert(root != NULL); for (i = 0; i < 10+2; i += 2) { node = json_tree_find_key(root, test_input[i].value); test_assert(node != NULL && node->value_type == test_input[i+1].type && null_strcmp(json_tree_get_value_str(node), test_input[i+1].value) == 0); } node = json_tree_find_key(root, "key-obj"); test_assert(node != NULL); node = json_tree_find_key(root, "key-arr-empty"); test_assert(node != NULL && node->value_type == JSON_TYPE_ARRAY && json_tree_get_child(node) == NULL); node = json_tree_find_key(root, "key-arr"); test_assert(node != NULL && node->value_type == JSON_TYPE_ARRAY); node = json_tree_get_child(node); test_assert(node != NULL && node->value_type == JSON_TYPE_STRING && strcmp(json_tree_get_value_str(node), "foo") == 0); node = node->next; test_assert(node != NULL && node->value_type == JSON_TYPE_ARRAY && json_tree_get_child(node) != NULL && json_tree_get_child(node)->next == NULL && json_tree_get_child(node)->value_type == JSON_TYPE_TRUE); node = node->next; test_assert(node != NULL && node->value_type == JSON_TYPE_OBJECT && json_tree_get_child(node) != NULL && json_tree_get_child(node)->next == NULL && json_tree_get_child(node)->value_type == JSON_TYPE_ARRAY && json_tree_get_child(json_tree_get_child(node)) == NULL); node1 = json_tree_find_child_with(node->parent, "aobj-key", "value1"); node2 = json_tree_find_child_with(node->parent, "aobj-key", "value2"); test_assert(node1 != NULL && node2 != NULL && node1 != node2); test_assert(json_tree_find_child_with(node->parent, "aobj-key", "value3") == NULL); json_tree_deinit(&tree); test_end(); } dovecot-2.3.21.1/src/lib/istream-rawlog.h0000644000000000000000000000060714656633576014763 00000000000000#ifndef ISTREAM_RAWLOG_H #define ISTREAM_RAWLOG_H #include "iostream-rawlog.h" struct istream * i_stream_create_rawlog(struct istream *input, const char *rawlog_path, int rawlog_fd, enum iostream_rawlog_flags flags); struct istream * i_stream_create_rawlog_from_stream(struct istream *input, struct ostream *rawlog_output, enum iostream_rawlog_flags flags); #endif dovecot-2.3.21.1/src/lib/ostream-unix.c0000644000000000000000000000427614656633576014462 00000000000000/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "fdpass.h" #include "ostream-file-private.h" #include "ostream-unix.h" struct unix_ostream { struct file_ostream fstream; int write_fd; }; static void o_stream_unix_close(struct iostream_private *stream, bool close_parent) { struct unix_ostream *ustream = container_of(stream, struct unix_ostream, fstream.ostream.iostream); i_close_fd(&ustream->write_fd); o_stream_file_close(stream, close_parent); } static ssize_t o_stream_unix_writev(struct file_ostream *fstream, const struct const_iovec *iov, unsigned int iov_count) { struct unix_ostream *ustream = container_of(fstream, struct unix_ostream, fstream); size_t sent; ssize_t ret; if (ustream->write_fd == -1) { /* no fd */ return o_stream_file_writev(fstream, iov, iov_count); } /* send first iovec along with fd */ if (iov_count == 0) return 0; i_assert(iov[0].iov_len > 0); ret = fd_send(fstream->fd, ustream->write_fd, iov[0].iov_base, iov[0].iov_len); if (ret < 0) return ret; /* update stream */ sent = ret; fstream->real_offset += sent; ustream->write_fd = -1; if (sent < iov[0].iov_len || iov_count == 1) { /* caller will call us again to write the rest */ return sent; } /* send remaining iovecs */ ret = o_stream_file_writev(fstream, &iov[1], iov_count-1); if (ret < 0) return (errno == EAGAIN || errno == EINTR ? (ssize_t)sent : ret); sent += ret; return sent; } struct ostream *o_stream_create_unix(int fd, size_t max_buffer_size) { struct unix_ostream *ustream; struct ostream *output; i_assert(fd != -1); ustream = i_new(struct unix_ostream, 1); ustream->write_fd = -1; output = o_stream_create_file_common(&ustream->fstream, fd, max_buffer_size, FALSE); output->real_stream->iostream.close = o_stream_unix_close; ustream->fstream.writev = o_stream_unix_writev; return output; } bool o_stream_unix_write_fd(struct ostream *output, int fd) { struct unix_ostream *ustream = container_of(output->real_stream, struct unix_ostream, fstream.ostream); i_assert(fd >= 0); if (ustream->write_fd >= 0) return FALSE; ustream->write_fd = fd; return TRUE; } dovecot-2.3.21.1/src/lib/test-uri.c0000644000000000000000000007254614656633576013610 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "test-common.h" #include "str.h" #include "str-sanitize.h" #include "uri-util.h" /* Valid uri tests */ const char *valid_uri_tests[] = { "http://www.dovecot.org", "http://127.0.0.1", "http://www.dovecot.org/frop", "http://www.dovecot.org/frop%20frop", "http://www.dovecot.org/frop/frop", "http://www.dovecot.org/frop/frop?query", "http://www.dovecot.org?query", "http://www.dovecot.org?query&query", "mailto:frop@example.com", }; unsigned int valid_uri_test_count = N_ELEMENTS(valid_uri_tests); static void test_uri_valid(void) { unsigned int i; test_begin("uri valid"); for (i = 0; i < valid_uri_test_count; i++) T_BEGIN { const char *uri_in, *error = NULL; int ret; uri_in = valid_uri_tests[i]; ret = uri_check(uri_in, 0, &error); test_out_quiet( t_strdup_printf("parse [%u] <%s>", i, str_sanitize(uri_in, 64)), ret >= 0); } T_END; test_end(); } /* Invalid uri tests */ const char *invalid_uri_tests[] = { "http", "http$44", "/index.html", "imap:[", "imap://[", "frop://friep\"", "http://example.com/settings/%00/", "http://[]/index.html", "http://example.com:65536/index.html" }; unsigned int invalid_uri_test_count = N_ELEMENTS(invalid_uri_tests); static void test_uri_invalid(void) { unsigned int i; test_begin("uri invalid"); for (i = 0; i < invalid_uri_test_count; i++) T_BEGIN { const char *uri_in, *error = NULL; int ret; uri_in = invalid_uri_tests[i]; ret = uri_check(uri_in, 0, &error); test_out_quiet( t_strdup_printf("parse [%u] <%s>", i, str_sanitize(uri_in, 64)), ret < 0); } T_END; test_end(); } /* RFC uri tests */ const char *rfc_uri_tests[] = { /* from RFC 1738 */ "http://www.acl.lanl.gov/URI/archive/uri-archive.index.html", "file://vms.host.edu/disk$user/my/notes/note12345.txt", "ftp://@host.com/", "ftp://host.com/", "ftp://foo:@host.com/", "ftp://myname@host.dom/%2Fetc/motd", "ftp://myname@host.dom/etc/motd", "ftp://myname@host.dom//etc/motd", "ftp://info.cern.ch/pub/www/doc;type=d", "http://ds.internic.net/instructions/overview.html#WARNING", /* from RFC 2056 */ "z39.50s://melvyl.ucop.edu/cat", "z39.50r://melvyl.ucop.edu/mags?elecworld.v30.n19", "z39.50r://cnidr.org:2100/tmf?bkirch_rules__a1;esn=f;rs=marc", /* from RFC 2122 */ "vemmi://zeus.mctel.fr/demo", "vemmi://zeus.mctel.fr", "vemmi://zeus.mctel.fr", "vemmi://mctel.fr/demo;$USERDATA=smith;account=1234", "vemmi://ares.mctel.fr/TEST", /* from RFC 2141 */ "URN:foo:a123,456", "urn:foo:a123,456", "urn:FOO:a123,456", "urn:foo:A123,456", "urn:foo:a123%2C456", "URN:FOO:a123%2c456", /* from RFC 2224 */ "nfs://server/d/e/f", "nfs://server//a/b/c/d/e/f", "nfs://server/a/b", /* from RFC 2229 */ "dict://dict.org/d:shortcake:", "dict://dict.org/d:shortcake:*", "dict://dict.org/d:shortcake:wordnet:", "dict://dict.org/d:shortcake:wordnet:1", "dict://dict.org/d:abcdefgh", "dict://dict.org/d:sun", "dict://dict.org/d:sun::1", "dict://dict.org/m:sun", "dict://dict.org/m:sun::soundex", "dict://dict.org/m:sun:wordnet::1", "dict://dict.org/m:sun::soundex:1", "dict://dict.org/m:sun:::", /* from RFC 2326 */ "rtsp://media.example.com:554/twister/audiotrack", "rtsp://media.example.com:554/twister", "rtsp://server.example.com/fizzle/foo", "rtsp://example.com/foo/bar/baz.rm", "rtsp://audio.example.com/audio", "rtsp://audio.example.com/twister.en", "rtsp://audio.example.com/meeting.en", "rtsp://example.com/fizzle/foo", "rtsp://bigserver.com:8001", "rtsp://example.com/meeting/audio.en", "rtsp://foo.com/bar.file", "rtsp://foo.com/bar.avi/streamid=0;seq=45102", "rtsp://foo.com/bar.avi/streamid=1;seq=30211", "rtsp://audio.example.com/twister/audio.en", "rtsp://video.example.com/twister/video", "rtsp://video.example.com/twister/video;seq=12312232;rtptime=78712811", "rtsp://audio.example.com/twister/audio.en;seq=876655;rtptime=1032181", "rtsp://foo/twister/video;seq=9810092;rtptime=3450012", "rtsp://foo.com/test.wav/streamid=0;seq=981888;rtptime=3781123", "rtsp://server.example.com/demo/548/sound", "rtsp://server.example.com/demo/548/sound", "rtsp://server.example.com/meeting", "rtsp://server.example.com/meeting/audiotrack", "rtsp://server.example.com/meeting/videotrack", "rtsp://server.example.com/meeting", "rtsp://example.com/movie/trackID=1", "rtsp://media.example.com:554/twister", /* from RFC 2371 */ "tip://123.123.123.123/?urn:xopen:xid", "tip://123.123.123.123/?transid1", /* from RFC 2384 */ "pop://rg@mailsrv.qualcomm.com", "pop://rg;AUTH=+APOP@mail.eudora.com:8110", "pop://baz;AUTH=SCRAM-MD5@foo.bar", /* from RFC 2392 */ "mid:960830.1639@XIson.com/partA.960830.1639@XIson.com", "cid:foo4%25foo1@bar.net", "cid:foo4*foo1@bar.net", /* from RFC 2397 */ "data:,A%20brief%20note", "data:image/gif;base64,R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAw" "AAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFz" "ByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSp" "a/TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJl" "ZeGl9i2icVqaNVailT6F5iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uis" "F81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PH" "hhx4dbgYKAAA7", #if 0 // this one doesn't comply with RFC 3986 "data:text/plain;charset=iso-8859-7,%be%fg%be", #endif "data:application/vnd-xxx-query,select_vcount,fcol_from_fieldtable/local", /* from RFC 2838 */ "tv:wqed.org", "tv:nbc.com", "tv:", "tv:abc.com", "tv:abc.co.au", "tv:east.hbo.com", "tv:west.hbo.com", /* from RFC 3261 */ #if 0 // these don't comply with RFC 3986 "sip:+1-212-555-1212:1234@gateway.com;user=phone", "sip:+12125551212@server.phone2net.com", "sip:+12125551212@server.phone2net.com;tag=887s", "sip:+358-555-1234567@foo.com;postd=pp22;user=phone", "sip:+358-555-1234567;isub=1411;postd=pp22@foo.com;user=phone", "sip:+358-555-1234567;phone-context=5;tsp=a.b@foo.com;user=phone", "sip:+358-555-1234567;postd=pp22@foo.com;user=phone", "sip:+358-555-1234567;POSTD=PP22@foo.com;user=phone", "sip:+358-555-1234567;postd=pp22;isub=1411@foo.com;user=phone", "sip:%61lice@atlanta.com;transport=TCPv", "sip:agb@bell-telephone.com", "sip:alice@192.0.2.4v", "sip:alice@atlanta.covm", "sip:alice@atlanta.com;maddr=239.255.255.1;ttl=15", "sip:alice@atlanta.com?priority=urgent&subject=project%20x", "sip:alice@atlanta.com?subject=project%20x&priority=urgent", "sip:alice@AtLanTa.CoM;Transport=tcp", "sip:alice@AtLanTa.CoM;Transport=UDP", "SIP:ALICE@AtLanTa.CoM;Transport=udp", "sip:alice;day=tuesday@atlanta.com", "sip:alice@pc33.atlanta.com", "sip:alice:secretword@atlanta.com;transport=tcp", "sip:anonymous@anonymizer.invalid", "sip:atlanta.com;method=REGISTER?to=alice%40atlanta.com", "sip:bigbox3.site3.atlanta.com;lr", "sip:biloxi.com;method=REGISTER;transport=tcp?to=sip:bob%40biloxi.com", "sip:biloxi.com;transport=tcp;method=REGISTER?to=sip:bob%40biloxi.com", "sip:bob@192.0.2.4", "sip:bob@biloxi.com", "sip:bob@biloxi.com:5060", "sip:bob@biloxi.com:6000;transport=tcp", "sip:bob@biloxi.com;transport=udp", "sip:bob@engineering.biloxi.com", "sip:bob@phone21.boxesbybob.com", "sip:c8oqz84zk7z@privacy.org>;tag=hyh8", "sip:callee@domain.com", "sip:callee@gateway.leftprivatespace.com", "sip:callee@u2.domain.com", "sip:callee@u2.rightprivatespace.com", "sip:caller@u1.example.com", "sip:carol@chicago.com", "sip:carol@chicago.com;security=off", "sip:carol@chicago.com;security=on", "sip:carol@chicago.com;newparam=5", "sip:carol@chicago.com;security=off", "sip:carol@chicago.com;security=on", "sip:carol@chicago.com?Subject=next%20meeting", "sip:carol@cube2214a.chicago.com", "sip:chicago.com", "sip:not-in-service-recording@atlanta.com", "sip:operator@cs.columbia.edu", "sip:p1.domain.com;lr", "sip:p1.example.com;lr", "sip:p2.domain.com;lr", "sips:1212@gateway.com", "sips:+358-555-1234567@foo.com;postd=pp22;user=phone", "sips:+358-555-1234567;postd=pp22@foo.com;user=phone", "sips:alice@atlanta.com?subject=project%20x&priority=urgent", "sip:server10.biloxi.com;lr", "sip:ss1.carrier.com", "sip:user@host?Subject=foo&Call-Info=", "sip:watson@bell-telephone.com", "sip:watson@worcester.bell-telephone.com", #endif /* from RFC 3368 */ "go:Mercedes%20Benz", "go://?Mercedes%20Benz", "go://cnrp.foo.com?Mercedes%20Benz;geography=US-ga", "go://cnrp.foo.org?Martin%20J.%20D%C3%BCrst", "go://cnrp.foo.com?id=5432345", /* from RFC 3507 */ "icap://icap.example.net:2000/services/icap-service-1", "icap://icap.net/service?mode=translate&lang=french", "icap://icap.example.net/translate?mode=french", "icap://icap-server.net/server?arg=87", "icap://icap.example.org/satisf", "icap://icap.server.net/sample-service", /* from RFC 3510 */ "ipp://example.com", "ipp://example.com/printer", "ipp://example.com/printer/tiger", "ipp://example.com/printer/fox", "ipp://example.com/printer/tiger/bob", "ipp://example.com/printer/tiger/ira", "ipp://example.com", "ipp://example.com/~smith/printer", "ipp://example.com:631/~smith/printer", "ipp://example.com/printer/123", "ipp://example.com/printer/tiger/job123", /* from RFC 3529 */ "xmlrpc.beep://stateserver.example.com/NumberToName", "xmlrpc.beep://stateserver.example.com:1026", "xmlrpc.beep://stateserver.example.com", "xmlrpc.beep://10.0.0.2:1026", "xmlrpc.beeps://stateserver.example.com/NumberToName", /* from RFC 3617 */ "tftp://example.com/myconfigurationfile;mode=netascii", "tftp://example.com/mystartupfile", /* from RFC 3859 */ "pres:fred@example.com", /* from RFC 3860 */ "im:fred@example.com", "im:pepp=example.com/fred@relay-domain", /* from RFC 3966 */ "tel:+1-201-555-0123", "tel:7042;phone-context=example.com", "tel:863-1234;phone-context=+1-914-555", /* from RFC 3981 */ "iris:dreg1//example.com/local/myhosts", "iris:dreg1//com", "iris:dreg1//com/iris/id", "iris:dreg1//example.com/domain/example.com", "iris:dreg1//example.com", "iris:dreg1//com/domain/example.com", "iris:dreg1//192.0.2.1:44/domain/example.com", "iris.lwz:dreg1//192.0.2.1:44/domain/example.com", "iris.beep:dreg1//com/domain/example.com", "iris:dreg1/bottom/example.com/domain/example.com", "iris.beep:dreg1/bottom/example.com/domain/example.com", /* from RFC 3986 */ "ftp://ftp.is.co.za/rfc/rfc1808.txt", "http://www.ietf.org/rfc/rfc2396.txt", "ldap://[2001:db8::7]/c=GB?objectClass?one", "mailto:John.Doe@example.com", "news:comp.infosystems.www.servers.unix", "tel:+1-816-555-1212", "telnet://192.0.2.16:80/", "urn:oasis:names:specification:docbook:dtd:xml:4.1.2", /* from RFC 4078 */ "crid://example.com/foobar", "crid://example.co.jp/%E3%82%A8%E3%82%A4%E3%82%AC", /* from RFC 4088 */ "snmp://example.com", "snmp://tester5@example.com:8161", "snmp://example.com/bridge1", "snmp://example.com/bridge1;800002b804616263", "snmp://example.com//1.3.6.1.2.1.1.3.0", "snmp://example.com//1.3.6.1.2.1.1.3+", "snmp://example.com//1.3.6.1.2.1.1.3.*", "snmp://example.com/bridge1/1.3.6.1.2.1.2.2.1.8.*", "snmp://example.com//(1.3.6.1.2.1.2.2.1.7,1.3.6.1.2.1.2.2.1.8).*", /* from RFC 4151 */ "tag:timothy@hpl.hp.com,2001:web/externalHome", "tag:sandro@w3.org,2004-05:Sandro", "tag:my-ids.com,2001-09-15:TimKindberg:presentations:UBath2004-05-19", "tag:blogger.com,1999:blog-555", "tag:yaml.org,2002:int", /* from RFC 4227 */ "soap.beep://stockquoteserver.example.com/StockQuote", "soap.beep://stockquoteserver.example.com:1026", "soap.beep://stockquoteserver.example.com", "soap.beep://192.0.2.0:1026", /* from RFC 4324 */ "cap://cal.example.com", "cap://cal.example.com/Company/Holidays", "cap://cal.example.com/abcd1234Usr", "cap://cal.example.com/abcd1234USR", "cap://host.com/joe", "cap:example.com/Doug", "cap://cal.example.com/sdfifgty4321", "cap://calendar.example.com", "cap://mycal.example.com", /* from RFC 4452 */ "info:ddc/22/eng//004.678", "info:lccn/2002022641", "info:sici/0363-0277(19950315)120:5%3C%3E1.0.TX;2-V", "info:bibcode/2003Icar..163..263Z", "info:pmid/12376099", /* from RFC 4501 */ "dns:www.example.org.?clAsS=IN;tYpE=A", "dns:www.example.org", "dns:simon.example.org?type=CERT", "dns://192.168.1.1/ftp.example.org?type=A", "dns:world%20wide%20web.example%5c.domain.org?TYPE=TXT", #if 0 // contains %00 encoding, which is currently always rejected "dns://fw.example.org/*.%20%00.example?type=TXT", #endif /* from RFC 4516 */ "ldap:///o=University%20of%20Michigan,c=US", "ldap://ldap1.example.net/o=University%20of%20Michigan,c=US", "ldap://ldap1.example.net/o=University%20of%20Michigan," "c=US?postalAddress", "ldap://ldap1.example.net:6666/o=University%20of%20Michigan," "c=US?\?sub?(cn=Babs%20Jensen)", "LDAP://ldap1.example.com/c=GB?objectClass?ONE", "ldap://ldap2.example.com/o=Question%3f,c=US?mail", "ldap://ldap3.example.com/o=Babsco,c=US" "??\?(four-octet=%5c00%5c00%5c00%5c04)", "ldap://ldap.example.com/o=An%20Example%5C2C%20Inc.,c=US", "ldap://ldap.example.net", "ldap://ldap.example.net/", "ldap://ldap.example.net/?", "ldap:///?\?sub?\?e-bindname=cn=Manager%2cdc=example%2cdc=com", "ldap:///?\?sub?\?!e-bindname=cn=Manager%2cdc=example%2cdc=com" /* from RFC 4975 */ "msrp://atlanta.example.com:7654/jshA7weztas;tcp", "msrp://biloxi.example.com:12763/kjhd37s2s20w2a;tcp", "msrp://host.example.com:8493/asfd34;tcp", "msrp://alice.example.com:7394/2s93i9ek2a;tcp", "msrp://bob.example.com:8493/si438dsaodes;tcp", "msrp://alicepc.example.com:7777/iau39soe2843z;tcp", "msrp://bob.example.com:8888/9di4eae923wzd;tcp", "msrp://alice.example.com:7777/iau39soe2843z;tcp", "msrp://bobpc.example.com:8888/9di4eae923wzd;tcp", "msrp://alicepc.example.com:7654/iau39soe2843z;tcp", "msrp://alicepc.example.com:8888/9di4eae923wzd;tcp", "msrp://example.com:7777/iau39soe2843z;tcp", "msrp://bob.example.com:8888/9di4eae923wzd;tcp", /* from RFC 5092 */ "imap://michael@example.org/INBOX", "imap://bester@example.org/INBOX", "imap://joe@example.com/INBOX/;uid=20/;section=1.2;urlauth=" "submit+fred:internal:91354a473744909de610943775f92038", "imap://minbari.example.org/gray-council;UIDVALIDITY=385759045/;" "UID=20/;PARTIAL=0.1024", "imap://psicorp.example.org/~peter/%E6%97%A5%E6%9C%AC%E8%AA%9E/" "%E5%8F%B0%E5%8C%97", "imap://;AUTH=GSSAPI@minbari.example.org/gray-council/;uid=20/" ";section=1.2", "imap://;AUTH=*@minbari.example.org/gray%20council?" "SUBJECT%20shadows", "imap://john;AUTH=*@minbari.example.org/babylon5/personel?" "charset%20UTF-8%20SUBJECT%20%7B14+%7D%0D%0A%D0%98%D0%B2%" "D0%B0%D0%BD%D0%BE%D0%B2%D0%B0", /* from RFC 5122 */ "xmpp:node@example.com", "xmpp://guest@example.com", "xmpp:guest@example.com", "xmpp://guest@example.com/support@example.com?message", "xmpp:support@example.com?message", "xmpp:example-node@example.com", "xmpp:example-node@example.com/some-resource", "xmpp:example.com", "xmpp:example-node@example.com?message", "xmpp:example-node@example.com?message;subject=Hello%20World", "xmpp:example-node@example.com", "xmpp:example-node@example.com?query", "xmpp:nasty!%23$%25()*+,-.;=%3F%5B%5C%5D%5E_%60%7B%7C%7D~node@example.com", "xmpp:node@example.com/repulsive%20!%23%22$%25&'()*+,-.%2F:;%3C=" "%3E%3F%40%5B%5C%5D%5E_%60%7B%7C%7D~resource", "xmpp:ji%C5%99i@%C4%8Dechy.example/v%20Praze", /* from RFC 5456 */ #if 0 // these don't comply with RFC 3986 "iax:example.com/alice", "iax:example.com:4569/alice", "iax:example.com:4570/alice?friends", "iax:192.0.2.4:4569/alice?friends", "iax:[2001:db8::1]:4569/alice?friends", "iax:example.com/12022561414", "iax:johnQ@example.com/12022561414", "iax:atlanta.com/alice", "iax:AtLaNtA.com/ALicE", "iax:atlanta.com:4569/alice", "iax:alice@atlanta.com/alice", "iax:alice@AtLaNtA.com:4569/ALicE", "iax:ALICE@atlanta.com/alice", "iax:alice@atlanta.com/alice", #endif /* from RFC 5724 */ "sms:+15105550101", "sms:+15105550101,+15105550102", "sms:+15105550101?body=hello%20there", /* from RFC 5804 */ "sieve://example.com//script", "sieve://example.com/script", /* from RFC 5538 */ "news://news.server.example/example.group.this", "news://news.server.example/*", "news://news.server.example/", "news://wild.server.example/example.group.th%3Fse", "news:example.group.*", "news:example.group.this", "news://news.gmane.org/gmane.ietf.tools", "news://news.gmane.org/p0624081dc30b8699bf9b@%5B10.20.30.108%5D", "nntp://wild.server.example/example.group.n%2Fa/12345", "nntp://news.server.example/example.group.this", "nntp://news.gmane.org/gmane.ietf.tools/742", "nntp://news.server.example/example.group.this/12345", /* from RFC 5870 */ "geo:13.4125,103.8667", "geo:48.2010,16.3695,183", "geo:48.198634,16.371648;crs=wgs84;u=40", "geo:90,-22.43;crs=WGS84", "geo:90,46", "geo:22.300;-118.44", "geo:22.3;-118.4400", "geo:66,30;u=6.500;FOo=this%2dthat", "geo:66.0,30;u=6.5;foo=this-that", "geo:70,20;foo=1.00;bar=white", "geo:70,20;foo=1;bar=white", "geo:47,11;foo=blue;bar=white", "geo:47,11;bar=white;foo=blue", "geo:22,0;bar=Blue", "geo:22,0;BAR=blue", /* from RFC 6068 */ "mailto:addr1@an.example,addr2@an.example", "mailto:?to=addr1@an.example,addr2@an.example", "mailto:addr1@an.example?to=addr2@an.example", "mailto:chris@example.com", "mailto:infobot@example.com?subject=current-issue", "mailto:infobot@example.com?body=send%20current-issue", "mailto:infobot@example.com?body=send%20current-issue%0D%0Asend%20index", "mailto:list@example.org?In-Reply-To=%3C3469A91.D10AF4C@example.com%3E", "mailto:majordomo@example.com?body=subscribe%20bamboo-l", "mailto:joe@example.com?cc=bob@example.com&body=hello", "mailto:gorby%25kremvax@example.com", "mailto:unlikely%3Faddress@example.com?blat=foop", "mailto:joe@an.example?cc=bob@an.example&body=hello", "mailto:Mike%26family@example.org", "mailto:%22not%40me%22@example.org", "mailto:%22oh%5C%5Cno%22@example.org", "mailto:%22%5C%5C%5C%22it's%5C%20ugly%5C%5C%5C%22%22@example.org", "mailto:user@example.org?subject=caf%C3%A9", "mailto:user@example.org?subject=%3D%3Futf-8%3FQ%3Fcaf%3DC3%3DA9%3F%3D", "mailto:user@example.org?subject=%3D%3Fiso-8859-1%3FQ%3Fcaf%3DE9%3F%3D", "mailto:user@example.org?subject=caf%C3%A9&body=caf%C3%A9", "mailto:user@%E7%B4%8D%E8%B1%86.example.org?subject=Test&body=NATTO", /* from RFC 6455 */ "ws://example.com/chat", /* from RFC 6694 */ "about:blank", /* from RFC 6733 */ #if 0 // these don't comply with RFC 3986 "aaa://host.example.com;transport=tcp", "aaa://host.example.com:6666;transport=tcp", "aaa://host.example.com;protocol=diameter", "aaa://host.example.com:6666;protocol=diameter", "aaa://host.example.com:6666;transport=tcp;protocol=diameter", "aaa://host.example.com:1813;transport=udp;protocol=radius", #endif /* from RFC 6787 */ "session:request1@form-level.store", "session:help@root-level.store", "session:menu1@menu-level.store", "session:request1@form-level.store", "session:request2@field-level.store", "session:helpgramar@root-level.store", "session:request1@form-level.store", "session:field3@form-level.store", /* from RFC 6920 */ "ni:///sha-256;UyaQV-Ev4rdLoHyJJWCi11OHfrYv9E1aGQAlMO2X_-Q", "ni:///sha-256-32;f4OxZQ?ct=text/plain", "ni:///sha-256;f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", "ni://example.com/sha-256;f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", "nih:sha-256-120;5326-9057-e12f-e2b7-4ba0-7c89-2560-a2;f", "nih:sha-256-32;53269057;b", "nih:3;532690-57e12f-e2b74b-a07c89-2560a2;f", /* from RFC 7064 */ "stun:example.org", "stuns:example.org", "stun:example.org:8000", /* from RFC 7065 */ "turn:example.org", "turns:example.org", "turn:example.org:8000", "turn:example.org?transport=udp", "turn:example.org?transport=tcp", "turns:example.org?transport=tcp", /* from RFC 7230 */ "http://www.example.com/hello.txt", "http://example.com:80/~smith/home.html", "http://EXAMPLE.com/%7Esmith/home.html", "http://EXAMPLE.com:/%7esmith/home.html", "http://www.example.org/where?q=now", "http://www.example.org/pub/WWW/TheProject.html", "http://www.example.org:8001", "http://www.example.org:8080/pub/WWW/TheProject.html", /* from RFC 7252 */ "coap://example.com:5683/~sensors/temp.xml", "coap://EXAMPLE.com/%7Esensors/temp.xml", "coap://EXAMPLE.com:/%7esensors/temp.xml", "coap://server/temperature", "coap://[2001:db8::2:1]/", "coap://example.net/", "coap://example.net/.well-known/core", "coap://xn--18j4d.example/%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF", "coap://198.51.100.1:61616//%2F//?%2F%2F&?%26" /* from draft-ietf-appsawg-acct-uri-06 */ "acct:foobar@status.example.net", "acct:user@example.com", "acct:bob@example.com", /* from draft-mcdonald-ipps-uri-scheme-18 */ "ipps://example.com/", "ipps://example.com/ipp", "ipps://example.com/ipp/faxout", "ipps://example.com/ipp/print", "ipps://example.com/ipp/scan", "ipps://example.com/ipp/print/bob", "ipps://example.com/ipp/print/ira", "ipps://example.com/", "ipps://example.com/ipp/print", "ipps://example.com:631/ipp/print", /* from draft-pechanec-pkcs11uri-21 */ "pkcs11:", "pkcs11:object=my-pubkey;type=public", "pkcs11:object=my-key;type=private?pin-source=file:/etc/token", "pkcs11:token=The%20Software%20PKCS%2311%20Softtoken;" "manufacturer=Snake%20Oil,%20Inc.;model=1.0;" "object=my-certificate;type=cert;" "id=%69%95%3E%5C%F4%BD%EC%91;serial=" "?pin-source=file:/etc/token_pin", "pkcs11:object=my-sign-key;type=private?module-name=mypkcs11", "pkcs11:object=my-sign-key;type=private" "?module-path=/mnt/libmypkcs11.so.1", "pkcs11:token=Software%20PKCS%2311%20softtoken;" "manufacturer=Snake%20Oil,%20Inc.?pin-value=the-pin", "pkcs11:slot-description=Sun%20Metaslot", "pkcs11:library-manufacturer=Snake%20Oil,%20Inc.;" "library-description=Soft%20Token%20Library;" "library-version=1.23", "pkcs11:token=My%20token%25%20created%20by%20Joe;" "library-version=3;id=%01%02%03%Ba%dd%Ca%fe%04%05%06", "pkcs11:token=A%20name%20with%20a%20substring%20%25%3B;" "object=my-certificate;type=cert", "pkcs11:token=Name%20with%20a%20small%20A%20with%20acute:%20%C3%A1;" "object=my-certificate;type=cert", "pkcs11:token=my-token;object=my-certificate;" "type=cert;vendor-aaa=value-a" "?pin-source=file:/etc/token_pin&vendor-bbb=value-b" }; unsigned int rfc_uri_test_count = N_ELEMENTS(rfc_uri_tests); static void test_uri_rfc(void) { unsigned int i; test_begin("uri from rfcs"); for (i = 0; i < rfc_uri_test_count; i++) T_BEGIN { const char *uri_in, *error = NULL; int ret; uri_in = rfc_uri_tests[i]; ret = uri_check(uri_in, URI_PARSE_ALLOW_FRAGMENT_PART, &error); test_out_quiet( t_strdup_printf("parse [%d] <%s>", i, str_sanitize(uri_in, 64)), ret >= 0); } T_END; test_end(); } static void test_uri_escape(void) { string_t *str = t_str_new(256); test_begin("uri escape - userinfo"); uri_append_user_data(str, NULL, "abcdefghijklmnopqrstuvwxyz"); test_assert(strcmp(str_c(str), "abcdefghijklmnopqrstuvwxyz") == 0); str_truncate(str, 0); uri_append_user_data(str, NULL, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); test_assert(strcmp(str_c(str), "ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 0); str_truncate(str, 0); uri_append_user_data(str, NULL, "0123456789"); test_assert(strcmp(str_c(str), "0123456789") == 0); str_truncate(str, 0); uri_append_user_data(str, NULL, "-._~!$&'()*+,;="); test_assert(strcmp(str_c(str), "-._~!$&'()*+,;=") == 0); str_truncate(str, 0); uri_append_user_data(str, NULL, "a@b/c/d:e"); test_assert(strcmp(str_c(str), "a%40b%2fc%2fd:e") == 0); str_truncate(str, 0); uri_append_user_data(str, NULL, "[yes]what?oh#13"); test_assert(strcmp(str_c(str), "%5byes%5dwhat%3foh%2313") == 0); str_truncate(str, 0); uri_append_user_data(str, ":", "a@b/c/d:e"); test_assert(strcmp(str_c(str), "a%40b%2fc%2fd%3ae") == 0); str_truncate(str, 0); test_end(); test_begin("uri escape - path segment"); uri_append_path_segment_data(str, NULL, "abcdefghijklmnopqrstuvwxyz"); test_assert(strcmp(str_c(str), "abcdefghijklmnopqrstuvwxyz") == 0); str_truncate(str, 0); uri_append_path_segment_data(str, NULL, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); test_assert(strcmp(str_c(str), "ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 0); str_truncate(str, 0); uri_append_path_segment_data(str, NULL, "0123456789"); test_assert(strcmp(str_c(str), "0123456789") == 0); str_truncate(str, 0); uri_append_path_segment_data(str, NULL, "-._~!$&'()*+,;="); test_assert(strcmp(str_c(str), "-._~!$&'()*+,;=") == 0); str_truncate(str, 0); uri_append_path_segment_data(str, NULL, "a@b/c/d:e"); test_assert(strcmp(str_c(str), "a@b%2fc%2fd:e") == 0); str_truncate(str, 0); uri_append_path_segment_data(str, NULL, "[yes]what?oh#13"); test_assert(strcmp(str_c(str), "%5byes%5dwhat%3foh%2313") == 0); str_truncate(str, 0); uri_append_path_segment_data(str, "@", "a@b/c/d:e"); test_assert(strcmp(str_c(str), "a%40b%2fc%2fd:e") == 0); str_truncate(str, 0); test_end(); test_begin("uri escape - path"); uri_append_path_data(str, NULL, "abcdefghijklmnopqrstuvwxyz"); test_assert(strcmp(str_c(str), "abcdefghijklmnopqrstuvwxyz") == 0); str_truncate(str, 0); uri_append_path_data(str, NULL, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); test_assert(strcmp(str_c(str), "ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 0); str_truncate(str, 0); uri_append_path_data(str, NULL, "0123456789"); test_assert(strcmp(str_c(str), "0123456789") == 0); str_truncate(str, 0); uri_append_path_data(str, NULL, "-._~!$&'()*+,;="); test_assert(strcmp(str_c(str), "-._~!$&'()*+,;=") == 0); str_truncate(str, 0); uri_append_path_data(str, NULL, "a@b/c/d:e"); test_assert(strcmp(str_c(str), "a@b/c/d:e") == 0); str_truncate(str, 0); uri_append_path_data(str, NULL, "[yes]what?oh#13"); test_assert(strcmp(str_c(str), "%5byes%5dwhat%3foh%2313") == 0); str_truncate(str, 0); uri_append_path_data(str, "@", "a@b/c/d:e"); test_assert(strcmp(str_c(str), "a%40b/c/d:e") == 0); str_truncate(str, 0); test_end(); test_begin("uri escape - query"); uri_append_query_data(str, NULL, "abcdefghijklmnopqrstuvwxyz"); test_assert(strcmp(str_c(str), "abcdefghijklmnopqrstuvwxyz") == 0); str_truncate(str, 0); uri_append_query_data(str, NULL, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); test_assert(strcmp(str_c(str), "ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 0); str_truncate(str, 0); uri_append_query_data(str, NULL, "0123456789"); test_assert(strcmp(str_c(str), "0123456789") == 0); str_truncate(str, 0); uri_append_query_data(str, NULL, "-._~!$&'()*+,;="); test_assert(strcmp(str_c(str), "-._~!$&'()*+,;=") == 0); str_truncate(str, 0); uri_append_query_data(str, NULL, "a@b/c/d:e"); test_assert(strcmp(str_c(str), "a@b/c/d:e") == 0); str_truncate(str, 0); uri_append_query_data(str, NULL, "[yes]what?oh#13"); test_assert(strcmp(str_c(str), "%5byes%5dwhat?oh%2313") == 0); str_truncate(str, 0); uri_append_query_data(str, "@", "a@b/c/d:e"); test_assert(strcmp(str_c(str), "a%40b/c/d:e") == 0); str_truncate(str, 0); test_end(); test_begin("uri escape - fragment"); uri_append_fragment_data(str, NULL, "abcdefghijklmnopqrstuvwxyz"); test_assert(strcmp(str_c(str), "abcdefghijklmnopqrstuvwxyz") == 0); str_truncate(str, 0); uri_append_fragment_data(str, NULL, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); test_assert(strcmp(str_c(str), "ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 0); str_truncate(str, 0); uri_append_fragment_data(str, NULL, "0123456789"); test_assert(strcmp(str_c(str), "0123456789") == 0); str_truncate(str, 0); uri_append_fragment_data(str, NULL, "-._~!$&'()*+,;="); test_assert(strcmp(str_c(str), "-._~!$&'()*+,;=") == 0); str_truncate(str, 0); uri_append_fragment_data(str, NULL, "a@b/c/d:e"); test_assert(strcmp(str_c(str), "a@b/c/d:e") == 0); str_truncate(str, 0); uri_append_fragment_data(str, NULL, "[yes]what?oh#13"); test_assert(strcmp(str_c(str), "%5byes%5dwhat?oh%2313") == 0); str_truncate(str, 0); uri_append_fragment_data(str, "@", "a@b/c/d:e"); test_assert(strcmp(str_c(str), "a%40b/c/d:e") == 0); str_truncate(str, 0); test_end(); test_begin("uri escape - unreserved"); uri_append_unreserved(str, "abcdefghijklmnopqrstuvwxyz"); test_assert(strcmp(str_c(str), "abcdefghijklmnopqrstuvwxyz") == 0); str_truncate(str, 0); uri_append_unreserved(str, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); test_assert(strcmp(str_c(str), "ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 0); str_truncate(str, 0); uri_append_unreserved(str, "0123456789"); test_assert(strcmp(str_c(str), "0123456789") == 0); str_truncate(str, 0); uri_append_unreserved(str, "-._~"); test_assert(strcmp(str_c(str), "-._~") == 0); str_truncate(str, 0); uri_append_unreserved(str, "!$&'()*+,;="); test_assert(strcmp(str_c(str), "%21%24%26%27%28%29%2a%2b%2c%3b%3d") == 0); str_truncate(str, 0); uri_append_unreserved(str, "a@b/c/d:e"); test_assert(strcmp(str_c(str), "a%40b%2fc%2fd%3ae") == 0); str_truncate(str, 0); uri_append_unreserved(str, "[yes]what?oh#13"); test_assert(strcmp(str_c(str), "%5byes%5dwhat%3foh%2313") == 0); str_truncate(str, 0); test_end(); test_begin("uri escape - unreserved"); uri_append_unreserved_path(str, "abcdefghijklmnopqrstuvwxyz"); test_assert(strcmp(str_c(str), "abcdefghijklmnopqrstuvwxyz") == 0); str_truncate(str, 0); uri_append_unreserved_path(str, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); test_assert(strcmp(str_c(str), "ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 0); str_truncate(str, 0); uri_append_unreserved_path(str, "0123456789"); test_assert(strcmp(str_c(str), "0123456789") == 0); str_truncate(str, 0); uri_append_unreserved_path(str, "-._~"); test_assert(strcmp(str_c(str), "-._~") == 0); str_truncate(str, 0); uri_append_unreserved_path(str, "!$&'()*+,;="); test_assert(strcmp(str_c(str), "%21%24%26%27%28%29%2a%2b%2c%3b%3d") == 0); str_truncate(str, 0); uri_append_unreserved_path(str, "a@b/c/d:e"); test_assert(strcmp(str_c(str), "a%40b/c/d%3ae") == 0); str_truncate(str, 0); uri_append_unreserved_path(str, "[yes]what?oh#13"); test_assert(strcmp(str_c(str), "%5byes%5dwhat%3foh%2313") == 0); str_truncate(str, 0); test_end(); } void test_uri(void) { test_uri_valid(); test_uri_invalid(); test_uri_rfc(); test_uri_escape(); } dovecot-2.3.21.1/src/lib/istream-seekable.h0000644000000000000000000000200214656633576015232 00000000000000#ifndef ISTREAM_SEEKABLE_H #define ISTREAM_SEEKABLE_H /* Create a seekable stream from given NULL-terminated list of input streams. Try to keep it in memory, but use a temporary file if it's too large. When max_buffer_size is reached, fd_callback is called. It should return the fd and path of the created file. Typically the callback would also unlink the file before returning. */ struct istream * i_streams_merge(struct istream *input[], size_t max_buffer_size, int (*fd_callback)(const char **path_r, void *context), void *context) ATTR_NULL(4); /* Same as i_streams_merge(), but if all of the inputs are seekable already, create a concat stream instead. */ struct istream * i_stream_create_seekable(struct istream *input[], size_t max_buffer_size, int (*fd_callback)(const char **path_r, void *context), void *context) ATTR_NULL(4); struct istream * i_stream_create_seekable_path(struct istream *input[], size_t max_buffer_size, const char *temp_path_prefix); #endif dovecot-2.3.21.1/src/lib/printf-format-fix.h0000644000000000000000000000132714656633576015402 00000000000000#ifndef PRINTF_FORMAT_FIX_H #define PRINTF_FORMAT_FIX_H /* Replaces %m in format with strerror(errno) and panics if %n modifier is used. If the format string was modified, it's returned from data stack. */ const char *printf_format_fix(const char *format) ATTR_FORMAT_ARG(1); /* Like printf_format_fix(), except return also the format string's length. */ const char *printf_format_fix_get_len(const char *format, size_t *len_r) ATTR_FORMAT_ARG(1); /* Like printf_format_fix(), except the format string is written to data stack without actually allocating it. Data stack must not be used until format string is no longer needed. */ const char *printf_format_fix_unsafe(const char *format) ATTR_FORMAT_ARG(1); #endif dovecot-2.3.21.1/src/lib/istream-try.c0000644000000000000000000001163114656633576014300 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "istream-private.h" #include "istream-try.h" struct try_istream { struct istream_private istream; size_t min_buffer_full_size; unsigned int try_input_count; struct istream **try_input; unsigned int try_idx; struct istream *final_input; }; static void i_stream_unref_try_inputs(struct try_istream *tstream) { for (unsigned int i = 0; i < tstream->try_input_count; i++) { if (tstream->try_input[i] != NULL) i_stream_unref(&tstream->try_input[i]); } tstream->try_input_count = 0; i_free(tstream->try_input); } static void i_stream_try_close(struct iostream_private *stream, bool close_parent) { struct try_istream *tstream = container_of(stream, struct try_istream, istream.iostream); if (close_parent) { if (tstream->istream.parent != NULL) i_stream_close(tstream->istream.parent); for (unsigned int i = 0; i < tstream->try_input_count; i++) { if (tstream->try_input[i] != NULL) i_stream_close(tstream->try_input[i]); } } i_stream_unref_try_inputs(tstream); } static bool i_stream_try_is_buffer_full(struct try_istream *tstream, struct istream *try_input) { /* See if one of the parent istreams have their buffer full. This is mainly intended to check with istream-tee whether its parent is full. That means that the try_input has already seen a full buffer of input, but it hasn't decided to return anything yet. But it also hasn't failed, so we'll assume that the input is correct for it and it simply needs a lot more input before it can return anything (e.g. istream-bzlib). Note that it's common for buffer_size to be 0 for all parents. This could be e.g. because the root is istream-concat, which breaks the parent hierarchy since it has multiple parents. So the buffer_size check can be thought of just as an optional extra check that sometimes works and sometimes doesn't. Note that we don't check whether skip==pos. An istream could be reading its buffer full without skipping over anything. */ while (try_input->real_stream->parent != NULL) { try_input = try_input->real_stream->parent; if (try_input->real_stream->pos >= try_input->real_stream->buffer_size && try_input->real_stream->pos >= tstream->min_buffer_full_size) return TRUE; } return FALSE; } static int i_stream_try_detect(struct try_istream *tstream) { int ret; for (; tstream->try_idx < tstream->try_input_count; tstream->try_idx++) { struct istream *try_input = tstream->try_input[tstream->try_idx]; ret = i_stream_read(try_input); if (ret == 0 && i_stream_try_is_buffer_full(tstream, try_input)) ret = 1; if (ret > 0) { i_stream_init_parent(&tstream->istream, try_input); i_stream_unref_try_inputs(tstream); return 1; } if (ret == 0) return 0; if (try_input->stream_errno == 0) { /* empty file */ tstream->istream.istream.eof = TRUE; return -1; } if (try_input->stream_errno != EINVAL) { tstream->istream.istream.stream_errno = try_input->stream_errno; io_stream_set_error(&tstream->istream.iostream, "Unexpected error while detecting stream format: %s", i_stream_get_error(try_input)); return -1; } } /* All streams failed with EINVAL. */ io_stream_set_error(&tstream->istream.iostream, "Failed to detect stream format"); tstream->istream.istream.stream_errno = EINVAL; return -1; } static ssize_t i_stream_try_read(struct istream_private *stream) { struct try_istream *tstream = container_of(stream, struct try_istream, istream); int ret; if (stream->parent == NULL) { if ((ret = i_stream_try_detect(tstream)) <= 0) return ret; } i_stream_seek(stream->parent, stream->parent_start_offset + stream->istream.v_offset); return i_stream_read_copy_from_parent(&stream->istream); } struct istream *istream_try_create(struct istream *const input[], size_t min_buffer_full_size) { struct try_istream *tstream; unsigned int count; size_t max_buffer_size = I_STREAM_MIN_SIZE; bool blocking = TRUE, seekable = TRUE; for (count = 0; input[count] != NULL; count++) { max_buffer_size = I_MAX(max_buffer_size, i_stream_get_max_buffer_size(input[count])); if (!input[count]->blocking) blocking = FALSE; if (!input[count]->seekable) seekable = FALSE; i_stream_ref(input[count]); } i_assert(count != 0); tstream = i_new(struct try_istream, 1); tstream->min_buffer_full_size = min_buffer_full_size; tstream->try_input_count = count; tstream->try_input = p_memdup(default_pool, input, sizeof(*input) * count); tstream->istream.iostream.close = i_stream_try_close; tstream->istream.max_buffer_size = max_buffer_size; tstream->istream.read = i_stream_try_read; tstream->istream.istream.readable_fd = FALSE; tstream->istream.istream.blocking = blocking; tstream->istream.istream.seekable = seekable; return i_stream_create(&tstream->istream, NULL, -1, 0); } dovecot-2.3.21.1/src/lib/connection.h0000644000000000000000000002165514656633576014173 00000000000000#ifndef CONNECTION_H #define CONNECTION_H #include "net.h" struct ioloop; struct connection; enum connection_behavior { CONNECTION_BEHAVIOR_DESTROY = 0, CONNECTION_BEHAVIOR_ALLOW }; enum connection_disconnect_reason { /* not disconnected yet */ CONNECTION_DISCONNECT_NOT = 0, /* normal requested disconnection */ CONNECTION_DISCONNECT_DEINIT, /* input buffer full */ CONNECTION_DISCONNECT_BUFFER_FULL, /* connection got disconnected */ CONNECTION_DISCONNECT_CONN_CLOSED, /* connect() timed out */ CONNECTION_DISCONNECT_CONNECT_TIMEOUT, /* remote didn't send input */ CONNECTION_DISCONNECT_IDLE_TIMEOUT, /* handshake failed */ CONNECTION_DISCONNECT_HANDSHAKE_FAILED, }; struct connection_vfuncs { void (*init)(struct connection *conn); void (*destroy)(struct connection *conn); /* For UNIX socket clients this gets called immediately (unless delayed_unix_client_connected_callback=TRUE) with success=TRUE, for IP connections it gets called later: If connect() fails, sets success=FALSE and errno. Streams aren't initialized in that situation either. destroy() is called after the callback. */ void (*client_connected)(struct connection *conn, bool success); /* implement one of the input*() methods. They return 1 = ok, continue. 0 = ok, but stop processing more lines, -1 = error, disconnect the client. */ void (*input)(struct connection *conn); int (*input_line)(struct connection *conn, const char *line); int (*input_args)(struct connection *conn, const char *const *args); /* handshake functions. Defaults to version checking. must return 1 when handshake is completed, otherwise return 0. return -1 to indicate error and disconnect client. if you implement this, remember to call connection_verify_version yourself, otherwise you end up with assert crash. these will not be called if you implement `input` virtual function. */ int (*handshake)(struct connection *conn); int (*handshake_line)(struct connection *conn, const char *line); int (*handshake_args)(struct connection *conn, const char *const *args); /* Called when the connection handshake is ready. */ void (*handshake_ready)(struct connection *conn); /* Called when input_idle_timeout_secs is reached, defaults to disconnect */ void (*idle_timeout)(struct connection *conn); /* Called when client_connect_timeout_msecs is reached, defaults to disconnect */ void (*connect_timeout)(struct connection *conn); }; struct connection_settings { const char *service_name_in; const char *service_name_out; unsigned int major_version, minor_version; unsigned int client_connect_timeout_msecs; unsigned int input_idle_timeout_secs; /* These need to be non-zero for corresponding stream to be created. */ size_t input_max_size; size_t output_max_size; enum connection_behavior input_full_behavior; /* Set to TRUE if this is a client */ bool client; /* Set to TRUE if version should not be sent */ bool dont_send_version; /* By default when only input_args() is used, or when connection_input_line_default() is used, empty lines aren't allowed since it would result in additional args[0] == NULL check. Setting this to TRUE passes it through instead of logging an error. */ bool allow_empty_args_input; /* Don't call client_connected() immediately on connection_client_connect() with UNIX sockets. This is mainly to make the functionality identical with inet sockets, which may simplify the calling code. */ bool delayed_unix_client_connected_callback; /* Put the connection id in the log prefix */ bool log_connection_id; /* If connect() to UNIX socket fails with EAGAIN, retry for this many milliseconds before giving up (0 = try once) */ unsigned int unix_client_connect_msecs; /* Turn on debug logging */ bool debug; }; struct connection { struct connection *prev, *next; struct connection_list *list; /* The name for the connection provided by the application. This is usually a host name or a unix socket path. This may be NULL if the application provides none. */ char *base_name; /* The name of the connection determined by the connection API. It is equal to base_name when that is available and otherwise it is composed from the connection properties; e.g., "ip:port". */ const char *name; char *label; char *property_label; unsigned int id; int fd_in, fd_out; struct ioloop *ioloop; struct io *io; struct istream *input; struct ostream *output; unsigned int input_idle_timeout_secs; struct timeout *to; time_t last_input; struct timeval last_input_tv; struct timeval connect_started; struct timeval connect_finished; /* set to parent event before calling init */ struct event *event_parent; struct event *event; /* connection properties */ struct ip_addr local_ip, remote_ip; in_port_t remote_port; pid_t remote_pid; uid_t remote_uid; /* received minor version */ unsigned int minor_version; /* handlers */ struct connection_vfuncs v; enum connection_disconnect_reason disconnect_reason; bool version_received:1; bool handshake_received:1; bool unix_socket:1; bool unix_peer_checked:1; bool disconnected:1; }; struct connection_list { struct connection *connections; unsigned int connections_count; unsigned int id_counter; struct connection_settings set; struct connection_vfuncs v; }; void connection_init(struct connection_list *list, struct connection *conn, const char *name) ATTR_NULL(3); void connection_init_server(struct connection_list *list, struct connection *conn, const char *name, int fd_in, int fd_out) ATTR_NULL(3); void connection_init_server_ip(struct connection_list *list, struct connection *conn, const char *name, int fd_in, int fd_out, const struct ip_addr *remote_ip, in_port_t remote_port) ATTR_NULL(3, 6); void connection_init_client_ip(struct connection_list *list, struct connection *conn, const char *hostname, const struct ip_addr *ip, in_port_t port) ATTR_NULL(3); void connection_init_client_ip_from(struct connection_list *list, struct connection *conn, const char *hostname, const struct ip_addr *ip, in_port_t port, const struct ip_addr *my_ip) ATTR_NULL(3,6); void connection_init_client_unix(struct connection_list *list, struct connection *conn, const char *path); void connection_init_client_fd(struct connection_list *list, struct connection *conn, const char *name, int fd_int, int fd_out) ATTR_NULL(3); void connection_init_from_streams(struct connection_list *list, struct connection *conn, const char *name, struct istream *input, struct ostream *output) ATTR_NULL(3); int connection_client_connect(struct connection *conn); /* Disconnects a connection */ void connection_disconnect(struct connection *conn); /* Deinitializes a connection, calls disconnect */ void connection_deinit(struct connection *conn); void connection_input_halt(struct connection *conn); /* Resume connection handling. If a new IO was added, it's marked as having pending input. */ void connection_input_resume(struct connection *conn); /* Update event fields and log prefix based on connection properties. */ void connection_update_event(struct connection *conn); /* Update connection properties and labels */ void connection_update_properties(struct connection *conn); /* This needs to be called if the input/output streams are changed */ void connection_streams_changed(struct connection *conn); /* Returns -1 = disconnected, 0 = nothing new, 1 = something new. If input_full_behavior is ALLOW, may return also -2 = buffer full. */ int connection_input_read(struct connection *conn); /* Verify that VERSION input matches what we expect. */ int connection_verify_version(struct connection *conn, const char *service_name, unsigned int major_version, unsigned int minor_version); int connection_handshake_args_default(struct connection *conn, const char *const *args); /* Returns human-readable reason for why connection was disconnected. */ const char *connection_disconnect_reason(struct connection *conn); /* Returns human-readable reason for why connection timed out, e.g. "No input for 10.023 secs". */ const char *connection_input_timeout_reason(struct connection *conn); void connection_switch_ioloop_to(struct connection *conn, struct ioloop *ioloop); void connection_switch_ioloop(struct connection *conn); struct connection_list * connection_list_init(const struct connection_settings *set, const struct connection_vfuncs *vfuncs); void connection_list_deinit(struct connection_list **list); void connection_input_default(struct connection *conn); int connection_input_line_default(struct connection *conn, const char *line); /* Change handlers, calls connection_input_halt and connection_input_resume */ void connection_set_handlers(struct connection *conn, const struct connection_vfuncs *vfuncs); void connection_set_default_handlers(struct connection *conn); #endif dovecot-2.3.21.1/src/lib/var-expand-if.c0000644000000000000000000001356014656633576014464 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "var-expand.h" #include "var-expand-private.h" #include "wildcard-match.h" #include enum var_expand_if_op { OP_UNKNOWN, OP_NUM_EQ, OP_NUM_LT, OP_NUM_LE, OP_NUM_GT, OP_NUM_GE, OP_NUM_NE, /* put all numeric comparisons before this line */ OP_STR_EQ, OP_STR_LT, OP_STR_LE, OP_STR_GT, OP_STR_GE, OP_STR_NE, OP_STR_LIKE, OP_STR_NOT_LIKE, OP_STR_REGEXP, OP_STR_NOT_REGEXP, /* keep this as last */ OP_COUNT }; static enum var_expand_if_op var_expand_if_str_to_comp(const char *op) { const char *ops[OP_COUNT] = { NULL, "==", "<", "<=", ">", ">=", "!=", "eq", "lt", "le", "gt", "ge", "ne", "*", "!*", "~", "!~", }; for(enum var_expand_if_op i = 1; i < OP_COUNT; i++) { i_assert(ops[i] != NULL); if (strcmp(op, ops[i]) == 0) return i; } return OP_UNKNOWN; } static int var_expand_if_comp(const char *lhs, const char *_op, const char *rhs, bool *result_r, const char **error_r) { bool neg = FALSE; enum var_expand_if_op op = var_expand_if_str_to_comp(_op); *result_r = FALSE; if (op == OP_UNKNOWN) { *error_r = t_strdup_printf("if: Unsupported comparator '%s'", _op); return -1; } if (op < OP_STR_EQ) { intmax_t a; intmax_t b; if (str_to_intmax(lhs, &a) < 0) { *error_r = t_strdup_printf("if: %s (lhs) is not a number", lhs); return -1; } if (str_to_intmax(rhs, &b) < 0) { *error_r = t_strdup_printf("if: %s (rhs) is not a number", rhs); return -1; } switch(op) { case OP_NUM_EQ: *result_r = a==b; return 0; case OP_NUM_LT: *result_r = ab; return 0; case OP_NUM_GE: *result_r = a>=b; return 0; case OP_NUM_NE: *result_r = a!=b; return 0; default: i_panic("Missing numeric comparator %u", op); } } switch(op) { case OP_STR_EQ: *result_r = strcmp(lhs,rhs)==0; return 0; case OP_STR_LT: *result_r = strcmp(lhs,rhs)<0; return 0; case OP_STR_LE: *result_r = strcmp(lhs,rhs)<=0; return 0; case OP_STR_GT: *result_r = strcmp(lhs,rhs)>0; return 0; case OP_STR_GE: *result_r = strcmp(lhs,rhs)>=0; return 0; case OP_STR_NE: *result_r = strcmp(lhs,rhs)!=0; return 0; case OP_STR_LIKE: *result_r = wildcard_match(lhs, rhs); return 0; case OP_STR_NOT_LIKE: *result_r = !wildcard_match(lhs, rhs); return 0; case OP_STR_NOT_REGEXP: neg = TRUE; /* fall through */ case OP_STR_REGEXP: { int ec; bool res; regex_t reg; if ((ec = regcomp(®, rhs, REG_EXTENDED)) != 0) { size_t siz; char *errbuf; siz = regerror(ec, ®, NULL, 0); errbuf = t_malloc_no0(siz); (void)regerror(ec, ®, errbuf, siz); *error_r = t_strdup_printf("if: regex failed: %s", errbuf); return -1; } if ((ec = regexec(®, lhs, 0, 0, 0)) != 0) { i_assert(ec == REG_NOMATCH); res = FALSE; } else { res = TRUE; } regfree(®); /* this should be same as neg. if NOT_REGEXP, neg == TRUE and res should be FALSE if REGEXP, ned == FALSE, and res should be TRUE */ *result_r = res != neg; return 0; } default: i_panic("Missing generic comparator %u", op); } } int var_expand_if(struct var_expand_context *ctx, const char *key, const char *field, const char **result_r, const char **error_r) { /* in case the original input had :, we need to fix that by concatenating the key and field together. */ const char *input = t_strconcat(key, ":", field, NULL); const char *p = strchr(input, ';'); const char *par_end; string_t *parbuf; const char *const *parms; unsigned int depth = 0; int ret; bool result, escape = FALSE, maybe_var = FALSE; if (p == NULL) { *error_r = "if: missing parameter(s)"; return -1; } ARRAY_TYPE(const_string) params; t_array_init(¶ms, 6); parbuf = t_str_new(64); /* we need to skip any %{} parameters here, so we can split the string correctly from , without breaking any inner expansions */ for(par_end = p+1; *par_end != '\0'; par_end++) { if (*par_end == '\\') { escape = TRUE; continue; } else if (escape) { str_append_c(parbuf, *par_end); escape = FALSE; continue; } if (*par_end == '%') { maybe_var = TRUE; } else if (maybe_var && *par_end == '{') { depth++; maybe_var = FALSE; } else if (depth > 0 && *par_end == '}') { depth--; } else if (depth == 0 && *par_end == ';') { const char *par = str_c(parbuf); array_push_back(¶ms, &par); parbuf = t_str_new(64); continue; /* if there is a unescaped : at top level it means that the key + arguments end here. it's probably a by-product of the t_strconcat at top of function, which is best handled here. */ } else if (depth == 0 && *par_end == ':') { break; } str_append_c(parbuf, *par_end); } if (str_len(parbuf) > 0) { const char *par = str_c(parbuf); array_push_back(¶ms, &par); } if (array_count(¶ms) != 5) { if (array_count(¶ms) == 4) { const char *empty = ""; array_push_back(¶ms, &empty); } else { *error_r = t_strdup_printf("if: requires four or five parameters, got %u", array_count(¶ms)); return -1; } } array_append_zero(¶ms); parms = array_front(¶ms); t_array_init(¶ms, 6); for(;*parms != NULL; parms++) { /* expand the parameters */ string_t *param = t_str_new(64); if ((ret = var_expand_with_funcs(param, *parms, ctx->table, ctx->func_table, ctx->context, error_r)) <= 0) { return ret; } const char *p = str_c(param); array_push_back(¶ms, &p); } i_assert(array_count(¶ms) == 5); /* execute comparison */ const char *const *args = array_front(¶ms); if (var_expand_if_comp(args[0], args[1], args[2], &result, error_r)<0) return -1; *result_r = result ? args[3] : args[4]; return 1; } dovecot-2.3.21.1/src/lib/sha3.c0000644000000000000000000002146614656633576012665 00000000000000/* ------------------------------------------------------------------------- * Works when compiled for either 32-bit or 64-bit targets, optimized for * 64 bit. * * Canonical implementation of Init/Update/Finalize for SHA-3 byte input. * * SHA3-256, SHA3-384, SHA-512 are implemented. SHA-224 can easily be added. * * Based on code from http://keccak.noekeon.org/ . * * I place the code that I wrote into public domain, free to use. * * I would appreciate if you give credits to this work if you used it to * write or test * your code. * * Aug 2015. Andrey Jivsov. crypto@brainhub.org * * Modified for Dovecot oy use * Oct 2016. Aki Tuomi * ---------------------------------------------------------------------- */ #include "lib.h" #include "sha3.h" #include #include #include #if defined(_MSC_VER) #define SHA3_CONST(x) x #else #define SHA3_CONST(x) x##L #endif /* The following state definition should normally be in a separate * header file */ #ifndef SHA3_ROTL64 #define SHA3_ROTL64(x, y) \ (((x) << (y)) | ((x) >> ((sizeof(uint64_t)*8) - (y)))) #endif static const uint64_t keccakf_rndc[24] = { SHA3_CONST(0x0000000000000001UL), SHA3_CONST(0x0000000000008082UL), SHA3_CONST(0x800000000000808aUL), SHA3_CONST(0x8000000080008000UL), SHA3_CONST(0x000000000000808bUL), SHA3_CONST(0x0000000080000001UL), SHA3_CONST(0x8000000080008081UL), SHA3_CONST(0x8000000000008009UL), SHA3_CONST(0x000000000000008aUL), SHA3_CONST(0x0000000000000088UL), SHA3_CONST(0x0000000080008009UL), SHA3_CONST(0x000000008000000aUL), SHA3_CONST(0x000000008000808bUL), SHA3_CONST(0x800000000000008bUL), SHA3_CONST(0x8000000000008089UL), SHA3_CONST(0x8000000000008003UL), SHA3_CONST(0x8000000000008002UL), SHA3_CONST(0x8000000000000080UL), SHA3_CONST(0x000000000000800aUL), SHA3_CONST(0x800000008000000aUL), SHA3_CONST(0x8000000080008081UL), SHA3_CONST(0x8000000000008080UL), SHA3_CONST(0x0000000080000001UL), SHA3_CONST(0x8000000080008008UL) }; static const unsigned keccakf_rotc[24] = { 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44 }; static const unsigned keccakf_piln[24] = { 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1 }; /* generally called after SHA3_KECCAK_SPONGE_WORDS-ctx->capacityWords words * are XORed into the state s */ static void ATTR_UNSIGNED_WRAPS keccakf(uint64_t s[25]) { int i, j, round; uint64_t t, bc[5]; #define KECCAK_ROUNDS 24 for(round = 0; round < KECCAK_ROUNDS; round++) { /* Theta */ for(i = 0; i < 5; i++) bc[i] = s[i] ^ s[i + 5] ^ s[i + 10] ^ s[i + 15] ^ s[i + 20]; for(i = 0; i < 5; i++) { t = bc[(i + 4) % 5] ^ SHA3_ROTL64(bc[(i + 1) % 5], 1); for(j = 0; j < 25; j += 5) s[j + i] ^= t; } /* Rho Pi */ t = s[1]; for(i = 0; i < 24; i++) { j = keccakf_piln[i]; bc[0] = s[j]; s[j] = SHA3_ROTL64(t, keccakf_rotc[i]); t = bc[0]; } /* Chi */ for(j = 0; j < 25; j += 5) { for(i = 0; i < 5; i++) bc[i] = s[j + i]; for(i = 0; i < 5; i++) s[j + i] ^= (~bc[(i + 1) % 5]) & bc[(i + 2) % 5]; } /* Iota */ s[0] ^= keccakf_rndc[round]; } } /* *************************** Public Interface ************************ */ void sha3_256_init(void *context) { struct sha3_ctx *ctx = context; i_zero(ctx); ctx->capacityWords = 2 * 256 / (8 * sizeof(uint64_t)); } void sha3_512_init(void *context) { struct sha3_ctx *ctx = context; i_zero(ctx); ctx->capacityWords = 2 * 512 / (8 * sizeof(uint64_t)); } void sha3_loop(void *context, const void *data, size_t len) { struct sha3_ctx *ctx = context; /* 0...7 -- how much is needed to have a word */ unsigned old_tail = (8 - ctx->byteIndex) & 7; size_t words; unsigned tail; size_t i; const uint8_t *buf = data; i_assert(ctx->byteIndex < 8); i_assert(ctx->wordIndex < sizeof(ctx->s) / sizeof(ctx->s[0])); if(len < old_tail) { /* have no complete word or haven't started * the word yet */ /* endian-independent code follows: */ while (len > 0) { len--; ctx->saved |= (uint64_t) (*(buf++)) << ((ctx->byteIndex++) * 8); } i_assert(ctx->byteIndex < 8); return; } if(old_tail != 0) { /* will have one word to process */ /* endian-independent code follows: */ len -= old_tail; while (old_tail > 0) { old_tail--; ctx->saved |= (uint64_t) (*(buf++)) << ((ctx->byteIndex++) * 8); } /* now ready to add saved to the sponge */ ctx->s[ctx->wordIndex] ^= ctx->saved; i_assert(ctx->byteIndex == 8); ctx->byteIndex = 0; ctx->saved = 0; if(++ctx->wordIndex == (SHA3_KECCAK_SPONGE_WORDS - ctx->capacityWords)) { keccakf(ctx->s); ctx->wordIndex = 0; } } /* now work in full words directly from input */ i_assert(ctx->byteIndex == 0); words = len / sizeof(uint64_t); tail = len - words * sizeof(uint64_t); for(i = 0; i < words; i++, buf += sizeof(uint64_t)) { const uint64_t t = (uint64_t) (buf[0]) | ((uint64_t) (buf[1]) << 8 * 1) | ((uint64_t) (buf[2]) << 8 * 2) | ((uint64_t) (buf[3]) << 8 * 3) | ((uint64_t) (buf[4]) << 8 * 4) | ((uint64_t) (buf[5]) << 8 * 5) | ((uint64_t) (buf[6]) << 8 * 6) | ((uint64_t) (buf[7]) << 8 * 7); #if defined(__x86_64__ ) || defined(__i386__) i_assert(memcmp(&t, buf, 8) == 0); #endif ctx->s[ctx->wordIndex] ^= t; if(++ctx->wordIndex == (SHA3_KECCAK_SPONGE_WORDS - ctx->capacityWords)) { keccakf(ctx->s); ctx->wordIndex = 0; } } /* finally, save the partial word */ i_assert(ctx->byteIndex == 0 && tail < 8); while (tail > 0) { tail--; ctx->saved |= (uint64_t) (*(buf++)) << ((ctx->byteIndex++) * 8); } i_assert(ctx->byteIndex < 8); } /* This is simply the 'update' with the padding block. * The padding block is 0x01 || 0x00* || 0x80. First 0x01 and last 0x80 * bytes are always present, but they can be the same byte. */ static void sha3_finalize(struct sha3_ctx *ctx) { /* Append 2-bit suffix 01, per SHA-3 spec. Instead of 1 for padding we * use 1<<2 below. The 0x02 below corresponds to the suffix 01. * Overall, we feed 0, then 1, and finally 1 to start padding. Without * M || 01, we would simply use 1 to start padding. */ /* SHA3 version */ ctx->s[ctx->wordIndex] ^= (ctx->saved ^ ((uint64_t) ((uint64_t) (0x02 | (1 << 2)) << ((ctx->byteIndex) * 8)))); ctx->s[SHA3_KECCAK_SPONGE_WORDS - ctx->capacityWords - 1] ^= SHA3_CONST(0x8000000000000000UL); keccakf(ctx->s); #ifdef WORDS_BIGENDIAN { unsigned i; for(i = 0; i < SHA3_KECCAK_SPONGE_WORDS; i++) { const unsigned t1 = (uint32_t) ctx->s[i]; const unsigned t2 = (uint32_t) ((ctx->s[i] >> 16) >> 16); ctx->sb[i * 8 + 0] = (uint8_t) (t1); ctx->sb[i * 8 + 1] = (uint8_t) (t1 >> 8); ctx->sb[i * 8 + 2] = (uint8_t) (t1 >> 16); ctx->sb[i * 8 + 3] = (uint8_t) (t1 >> 24); ctx->sb[i * 8 + 4] = (uint8_t) (t2); ctx->sb[i * 8 + 5] = (uint8_t) (t2 >> 8); ctx->sb[i * 8 + 6] = (uint8_t) (t2 >> 16); ctx->sb[i * 8 + 7] = (uint8_t) (t2 >> 24); } } #endif } void sha3_256_result(void *context, unsigned char digest[STATIC_ARRAY SHA256_RESULTLEN]) { struct sha3_ctx *ctx = context; sha3_finalize(ctx); memcpy(digest, ctx->sb, SHA256_RESULTLEN); } void sha3_512_result(void *context, unsigned char digest[STATIC_ARRAY SHA512_RESULTLEN]) { struct sha3_ctx *ctx = context; sha3_finalize(ctx); memcpy(digest, ctx->sb, SHA512_RESULTLEN); } void sha3_256_get_digest(const void *data, size_t size, unsigned char digest[STATIC_ARRAY SHA256_RESULTLEN]) { struct sha3_ctx ctx; sha3_256_init(&ctx); sha3_loop(&ctx, data, size); sha3_256_result(&ctx, digest); } void sha3_512_get_digest(const void *data, size_t size, unsigned char digest[STATIC_ARRAY SHA512_RESULTLEN]) { struct sha3_ctx ctx; sha3_512_init(&ctx); sha3_loop(&ctx, data, size); sha3_512_result(&ctx, digest); } static void hash_method_init_sha3_256(void *context) { sha3_256_init(context); } static void hash_method_loop_sha3(void *context, const void *data, size_t size) { sha3_loop(context, data, size); } static void hash_method_result_sha3_256(void *context, unsigned char *result_r) { sha3_256_result(context, result_r); } const struct hash_method hash_method_sha3_256 = { .name = "sha3-256", .block_size = SHA256_BLOCK_SIZE, .context_size = sizeof(struct sha3_ctx), .digest_size = SHA256_RESULTLEN, .init = hash_method_init_sha3_256, .loop = hash_method_loop_sha3, .result = hash_method_result_sha3_256, }; static void hash_method_init_sha3_512(void *context) { sha3_512_init(context); } static void hash_method_result_sha3_512(void *context, unsigned char *result_r) { sha3_512_result(context, result_r); } const struct hash_method hash_method_sha3_512 = { .name = "sha3-512", .block_size = SHA512_BLOCK_SIZE, .context_size = sizeof(struct sha3_ctx), .digest_size = SHA512_RESULTLEN, .init = hash_method_init_sha3_512, .loop = hash_method_loop_sha3, .result = hash_method_result_sha3_512, }; dovecot-2.3.21.1/src/lib/hex-binary.h0000644000000000000000000000101414656633576014065 00000000000000#ifndef HEX_BINARY_H #define HEX_BINARY_H /* Convert binary to hex digits allocating return value from data stack */ const char *binary_to_hex(const unsigned char *data, size_t size); const char *binary_to_hex_ucase(const unsigned char *data, size_t size); void binary_to_hex_append(string_t *dest, const unsigned char *data, size_t size); /* Convert hex to binary. data and dest may point to same value. Returns 0 if all ok, -1 if data is invalid. */ int hex_to_binary(const char *data, buffer_t *dest); #endif dovecot-2.3.21.1/src/lib/pkcs5.h0000644000000000000000000000214714656633576013054 00000000000000#ifndef PKCS5_H #define PKCS5_H 1 enum pkcs5_pbkdf_mode { PKCS5_PBKDF1, PKCS5_PBKDF2 }; /* mode - v1.0 or v2.0 hash - hash_method_lookup return value password - private password for generation password_len - length of password in octets salt - salt for generation salt_len - length of salt in octets iterations - number of iterations to hash (use at least 1000, a very large number => very very slow) dk_len - number of bytes to return from derived key result - buffer_t to hold the result, either use dynamic or make sure it fits dk_len non-zero return value indicates that either iterations was less than 1 or dk_len was too large Sample code: buffer_t *result = t_buffer_create(256); if (pkcs5_pbkdf(PKCS5_PBKDF2, hash_method_lookup("sha256"), "password", 8, "salt", 4, 4096, 256, result) != 0) { // error } */ int pkcs5_pbkdf(enum pkcs5_pbkdf_mode mode, const struct hash_method *hash, const unsigned char *password, size_t password_len, const unsigned char *salt, size_t salt_len, unsigned int iterations, uint32_t dk_len, buffer_t *result); #endif dovecot-2.3.21.1/src/lib/array-decl.h0000644000000000000000000000140714656633576014050 00000000000000#ifndef ARRAY_DECL_H #define ARRAY_DECL_H #define ARRAY(array_type) union { struct array arr; array_type const *const *v; array_type **v_modifiable; } #define ARRAY_INIT { { NULL, 0 } } #define ARRAY_DEFINE_TYPE(name, array_type) \ union array ## __ ## name { struct array arr; array_type const *const *v; array_type **v_modifiable; } #define ARRAY_TYPE(name) \ union array ## __ ## name struct array { buffer_t *buffer; size_t element_size; }; ARRAY_DEFINE_TYPE(string, char *); ARRAY_DEFINE_TYPE(const_string, const char *); ARRAY_DEFINE_TYPE(uint8_t, uint8_t); ARRAY_DEFINE_TYPE(uint16_t, uint16_t); ARRAY_DEFINE_TYPE(uint32_t, uint32_t); ARRAY_DEFINE_TYPE(uint64_t, uint64_t); ARRAY_DEFINE_TYPE(uint, unsigned int); ARRAY_DEFINE_TYPE(void_array, void *); #endif dovecot-2.3.21.1/src/lib/cpu-limit.h0000644000000000000000000000451314656633576013731 00000000000000#ifndef CPU_LIMIT #define CPU_LIMIT struct cpu_limit; enum cpu_limit_type { CPU_LIMIT_TYPE_USER = BIT(0), CPU_LIMIT_TYPE_SYSTEM = BIT(1), }; #define CPU_LIMIT_TYPE_ALL (CPU_LIMIT_TYPE_USER | CPU_LIMIT_TYPE_SYSTEM) /* Start tracking CPU usage. This internally uses setrlimit(RLIMIT_CPU) to trigger SIGXCPU to avoid constantly calling getrlimit() to check if the CPU usage has reached a limit. Once all limits created by this API are released, the original CPU resource limits are restored (if any). CPU time limits can be nested, i.e. they are never independent. The outer limits contain the bounded maximum limit for the inner limits. For example the code execution flow might be: - Set 30s CPU limit (outer limit) - Use up 5s of CPU - Set 40s CPU limit (inner limit) - Infinite loop The inner loop's limit won't even be reached here. After the inner loops runs for 25 seconds, the outer loop's 30s limit is reached. This causes both the inner and the other limit's cpu_limit_exceeded() to return TRUE. It's expected that the inner execution stops and returns back to the outer execution, which notices that the outer execution has also reached the limit. Another example where the inner limit is reached: - Set 30s CPU limit (outer limit) - Use up 5s of CPU - Set 10s CPU limit (inner limit) - Infinite loop Here the inner 10s limit is reached, and the inner execution stops. The outer execution could still run for another 15 seconds. Example usage: bool limit_reached = FALSE; limit = cpu_limit_init(5, CPU_LIMIT_TYPE_ALL); while (long_operation_iterate_once()) { if (cpu_limit_exceeded(limit)) { limit_reached = TRUE; // operation took >=5 secs break; } } cpu_limit_deinit(&limit); */ struct cpu_limit * cpu_limit_init(unsigned int cpu_limit_secs, enum cpu_limit_type type); void cpu_limit_deinit(struct cpu_limit **_climit); /* Returns TRUE if the CPU limit has been exceeded for this limit or any of its parents. */ bool cpu_limit_exceeded(struct cpu_limit *climit); unsigned int cpu_limit_get_usage_msecs(struct cpu_limit *climit, enum cpu_limit_type type); static inline unsigned int cpu_limit_get_usage_secs(struct cpu_limit *climit, enum cpu_limit_type type) { return cpu_limit_get_usage_msecs(climit, type) / 1000; } #endif dovecot-2.3.21.1/src/lib/ioloop-iolist.h0000644000000000000000000000055314656633576014630 00000000000000#ifndef IOLOOP_IOLIST_H #define IOLOOP_IOLIST_H enum { IOLOOP_IOLIST_INPUT, IOLOOP_IOLIST_OUTPUT, IOLOOP_IOLIST_ERROR, IOLOOP_IOLIST_IOS_PER_FD }; struct io_list { struct io_file *ios[IOLOOP_IOLIST_IOS_PER_FD]; }; bool ioloop_iolist_add(struct io_list *list, struct io_file *io); bool ioloop_iolist_del(struct io_list *list, struct io_file *io); #endif dovecot-2.3.21.1/src/lib/hash-format.h0000644000000000000000000000166714656633576014246 00000000000000#ifndef HASH_FORMAT_H #define HASH_FORMAT_H struct hash_format; /* Initialize formatting hash. Format can contain text with %{sha1} style variables. Each hash hash can be also truncated by specifying the number of bits to truncate to, such as %{sha1:80}. */ int hash_format_init(const char *format_string, struct hash_format **format_r, const char **error_r); /* Add more data to hash. */ void hash_format_loop(struct hash_format *format, const void *data, size_t size); /* Finish the hash and write it into given string. */ void hash_format_write(struct hash_format *format, string_t *dest); /* Reset hash to initial state. */ void hash_format_reset(struct hash_format *format); /* Write the hash into given string and free used memory. */ void hash_format_deinit(struct hash_format **format, string_t *dest); /* Free used memory without writing to string. */ void hash_format_deinit_free(struct hash_format **format); #endif dovecot-2.3.21.1/src/lib/test-log-throttle.c0000644000000000000000000000273014656633576015421 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "ioloop.h" #include "log-throttle.h" static unsigned int test_log_throttle_new_events_count; static void test_log_throttle_callback(unsigned int new_events_count, struct ioloop *ioloop) { test_log_throttle_new_events_count = new_events_count; io_loop_stop(ioloop); } void test_log_throttle(void) { const struct log_throttle_settings set = { .throttle_at_max_per_interval = 10, .unthrottle_at_max_per_interval = 5, .interval_msecs = 10, }; struct log_throttle *throttle; struct ioloop *ioloop; unsigned int i; test_begin("log throttle"); ioloop = io_loop_create(); throttle = log_throttle_init(&set, test_log_throttle_callback, ioloop); /* throttle once and drop out just below */ for (i = 0; i < 10; i++) test_assert_idx(log_throttle_accept(throttle), i); for (i = 0; i < 4; i++) test_assert_idx(!log_throttle_accept(throttle), i); io_loop_run(ioloop); test_assert(test_log_throttle_new_events_count == 4); /* throttle and continue just above */ for (i = 0; i < 10; i++) test_assert_idx(log_throttle_accept(throttle), i); for (i = 0; i < 5; i++) test_assert_idx(!log_throttle_accept(throttle), i); io_loop_run(ioloop); test_assert(test_log_throttle_new_events_count == 5); /* we should be still throttled */ test_assert(!log_throttle_accept(throttle)); log_throttle_deinit(&throttle); io_loop_destroy(&ioloop); test_end(); } dovecot-2.3.21.1/src/lib/mempool-datastack.c0000644000000000000000000001174614656633576015434 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "mempool.h" /* * The datastack pool is a thin wrapper around the datastack API. It exists * to allow datastack allocations via the pool API. * * Note: Do not confuse it with the *unsafe* datastack pool. * * Implementation * ============== * * A datastack pool maintains information about the datastack frame that was * in use when the pool was created so it can sanity check all p_new(), * p_malloc(), and p_realloc() calls. * * Creation * -------- * * When a datastack pool is created, a new pool structure is allocated from * the datastack (via t_new()). The current datastack frame number is saved * into the pool's private data (struct datastack_pool). * * Allocation & Reallocation * ------------------------- * * After verifying that the saved datastack frame id matches the currently * active one, the p_malloc() and p_realloc() calls get directed to * t_malloc0() and t_try_realloc(), respectively. There is no * per-allocation information to track. * * Freeing * ------- * * Freeing is a no-op unless the currently active data stack frame id is * different from the one saved during pool creation, in which case the * process panics. * * Clearing * -------- * * A no-op. * * Destruction * ----------- * * Since the memory backing the pool structure itself is allocated from the * datastack via t_new(), the pool and all allocations it made are freed * when the datastack frame is popped. * * Even though the pool maintains a reference count, no memory is freed when * it reaches zero. Once the reference count reaches zero, the state of the * pool is undefined and none of its memory maybe be used. */ static const char *pool_data_stack_get_name(pool_t pool); static void pool_data_stack_ref(pool_t pool); static void pool_data_stack_unref(pool_t *pool); static void *pool_data_stack_malloc(pool_t pool, size_t size); static void pool_data_stack_free(pool_t pool, void *mem); static void *pool_data_stack_realloc(pool_t pool, void *mem, size_t old_size, size_t new_size); static void pool_data_stack_clear(pool_t pool); static size_t pool_data_stack_get_max_easy_alloc_size(pool_t pool); static struct pool_vfuncs static_data_stack_pool_vfuncs = { pool_data_stack_get_name, pool_data_stack_ref, pool_data_stack_unref, pool_data_stack_malloc, pool_data_stack_free, pool_data_stack_realloc, pool_data_stack_clear, pool_data_stack_get_max_easy_alloc_size }; static const struct pool static_data_stack_pool = { .v = &static_data_stack_pool_vfuncs, .alloconly_pool = TRUE, .datastack_pool = TRUE }; struct datastack_pool { struct pool pool; int refcount; unsigned int data_stack_frame; }; pool_t pool_datastack_create(void) { struct datastack_pool *dpool; dpool = t_new(struct datastack_pool, 1); dpool->pool = static_data_stack_pool; dpool->refcount = 1; dpool->data_stack_frame = data_stack_frame_id; return &dpool->pool; } static const char *pool_data_stack_get_name(pool_t pool ATTR_UNUSED) { return "data stack"; } static void pool_data_stack_ref(pool_t pool) { struct datastack_pool *dpool = container_of(pool, struct datastack_pool, pool); if (unlikely(dpool->data_stack_frame != data_stack_frame_id)) i_panic("pool_data_stack_ref(): stack frame changed"); dpool->refcount++; } static void pool_data_stack_unref(pool_t *pool) { struct datastack_pool *dpool = container_of(*pool, struct datastack_pool, pool); if (unlikely(dpool->data_stack_frame != data_stack_frame_id)) i_panic("pool_data_stack_unref(): stack frame changed"); dpool->refcount--; i_assert(dpool->refcount >= 0); *pool = NULL; } static void *pool_data_stack_malloc(pool_t pool ATTR_UNUSED, size_t size) { struct datastack_pool *dpool = container_of(pool, struct datastack_pool, pool); if (unlikely(dpool->data_stack_frame != data_stack_frame_id)) i_panic("pool_data_stack_malloc(): stack frame changed"); return t_malloc0(size); } static void pool_data_stack_free(pool_t pool, void *mem ATTR_UNUSED) { struct datastack_pool *dpool = container_of(pool, struct datastack_pool, pool); if (unlikely(dpool->data_stack_frame != data_stack_frame_id)) i_panic("pool_data_stack_free(): stack frame changed"); } static void *pool_data_stack_realloc(pool_t pool, void *mem, size_t old_size, size_t new_size) { struct datastack_pool *dpool = container_of(pool, struct datastack_pool, pool); void *new_mem; /* @UNSAFE */ if (unlikely(dpool->data_stack_frame != data_stack_frame_id)) i_panic("pool_data_stack_realloc(): stack frame changed"); if (old_size >= new_size) return mem; if (!t_try_realloc(mem, new_size)) { new_mem = t_malloc_no0(new_size); memcpy(new_mem, mem, old_size); mem = new_mem; } memset((char *) mem + old_size, 0, new_size - old_size); return mem; } static void pool_data_stack_clear(pool_t pool ATTR_UNUSED) { } static size_t pool_data_stack_get_max_easy_alloc_size(pool_t pool ATTR_UNUSED) { return t_get_bytes_available(); } dovecot-2.3.21.1/src/lib/hook-build.c0000644000000000000000000000607414656633576014062 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "llist.h" #include "hook-build.h" struct hook_stack { struct hook_stack *prev, *next; /* Pointer to vfuncs struct. This assumes that a struct containing function pointers equals to an array of function pointers. Not ANSI-C, but should work in all OSes supported by Dovecot. Much easier anyway than doing this work manually.. */ void (**vfuncs)(); /* nonzero in the areas where vfuncs has been changed */ void (**mask)(); }; struct hook_build_context { pool_t pool; /* size of the vfuncs struct */ size_t size; /* number of function pointers in the struct */ unsigned int count; struct hook_stack *head, *tail; }; static void hook_build_append(struct hook_build_context *ctx, void (**vfuncs)()) { struct hook_stack *stack; stack = p_new(ctx->pool, struct hook_stack, 1); stack->vfuncs = vfuncs; stack->mask = p_malloc(ctx->pool, ctx->size); DLLIST2_APPEND(&ctx->head, &ctx->tail, stack); } struct hook_build_context *hook_build_init(void (**vfuncs)(), size_t size) { struct hook_build_context *ctx; pool_t pool; i_assert((size % sizeof(void (*)())) == 0); pool = pool_alloconly_create("hook build context", 2048); ctx = p_new(pool, struct hook_build_context, 1); ctx->pool = pool; ctx->size = size; ctx->count = size / sizeof(void (*)()); hook_build_append(ctx, vfuncs); return ctx; } static void hook_update_mask(struct hook_build_context *ctx, struct hook_stack *stack, void (**vlast)()) { unsigned int i; for (i = 0; i < ctx->count; i++) { if (stack->vfuncs[i] != vlast[i]) { i_assert(stack->vfuncs[i] != NULL); stack->mask[i] = stack->vfuncs[i]; } } } static void hook_copy_stack(struct hook_build_context *ctx, struct hook_stack *stack) { unsigned int i; i_assert(stack->next != NULL); for (i = 0; i < ctx->count; i++) { if (stack->mask[i] == NULL) { stack->vfuncs[i] = stack->next->vfuncs[i]; stack->mask[i] = stack->next->mask[i]; } } } void hook_build_update(struct hook_build_context *ctx, void *_vlast) { void (**vlast)() = _vlast; struct hook_stack *stack; if (ctx->tail->vfuncs == vlast) { /* no vfuncs overridden */ return; } /* ctx->vfuncs_stack->vfuncs points to the root vfuncs, ctx->vfuncs_stack->next->vfuncs points to the first super function that is being called, and so on. the previous plugin added its vfuncs to the stack tail. vlast contains the previous plugin's super vfuncs, which is where the next plugin should put its own vfuncs. first we'll need to figure out what vfuncs the previous plugin changed and update the mask */ hook_update_mask(ctx, ctx->tail, vlast); /* now go up in the stack as long as the mask isn't set, and update the vfuncs */ for (stack = ctx->tail->prev; stack != NULL; stack = stack->prev) hook_copy_stack(ctx, stack); /* add vlast to stack */ hook_build_append(ctx, vlast); } void hook_build_deinit(struct hook_build_context **_ctx) { struct hook_build_context *ctx = *_ctx; *_ctx = NULL; pool_unref(&ctx->pool); } dovecot-2.3.21.1/src/lib/test-net.c0000644000000000000000000001332414656633576013564 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "net.h" struct test_net_is_in_network_input { const char *ip; const char *net; unsigned int bits; bool ret; }; static void test_net_is_in_network(void) { static const struct test_net_is_in_network_input input[] = { { "1.2.3.4", "1.2.3.4", 32, TRUE }, { "1.2.3.4", "1.2.3.3", 32, FALSE }, { "1.2.3.4", "1.2.3.5", 32, FALSE }, { "1.2.3.4", "1.2.2.4", 32, FALSE }, { "1.2.3.4", "1.1.3.4", 32, FALSE }, { "1.2.3.4", "0.2.3.4", 32, FALSE }, { "1.2.3.253", "1.2.3.254", 31, FALSE }, { "1.2.3.254", "1.2.3.254", 31, TRUE }, { "1.2.3.255", "1.2.3.254", 31, TRUE }, { "1.2.3.255", "1.2.3.0", 24, TRUE }, { "1.2.255.255", "1.2.254.0", 23, TRUE }, { "255.255.255.255", "128.0.0.0", 1, TRUE }, { "255.255.255.255", "127.0.0.0", 1, FALSE }, { "1234:5678::abcf", "1234:5678::abce", 127, TRUE }, { "1234:5678::abcd", "1234:5678::abce", 127, FALSE }, { "123e::ffff", "123e::0", 15, TRUE }, { "::ffff:1.2.3.4", "1.2.3.4", 32, TRUE }, { "::ffff:1.2.3.4", "1.2.3.3", 32, FALSE }, { "::ffff:1.2.3.4", "::ffff:1.2.3.4", 0, FALSE } }; struct ip_addr ip, net_ip; unsigned int i; test_begin("net_is_in_network()"); for (i = 0; i < N_ELEMENTS(input); i++) { test_assert(net_addr2ip(input[i].ip, &ip) == 0); test_assert(net_addr2ip(input[i].net, &net_ip) == 0); test_assert_idx(net_is_in_network(&ip, &net_ip, input[i].bits) == input[i].ret, i); } /* make sure non-IPv4 and non-IPv6 ip_addrs fail */ test_assert(net_addr2ip("127.0.0.1", &ip) == 0); net_ip = ip; net_ip.family = 0; test_assert(!net_is_in_network(&ip, &net_ip, 0)); test_assert(!net_is_in_network(&net_ip, &ip, 0)); test_assert(net_addr2ip("::1", &ip) == 0); net_ip = ip; net_ip.family = 0; test_assert(!net_is_in_network(&ip, &net_ip, 0)); test_assert(!net_is_in_network(&net_ip, &ip, 0)); test_end(); } static void test_net_ip2addr(void) { struct ip_addr ip; test_begin("net_ip2addr()"); test_assert(net_addr2ip("127.0.0.1", &ip) == 0 && ip.family == AF_INET && ntohl(ip.u.ip4.s_addr) == (0x7f000001)); test_assert(net_addr2ip("2130706433", &ip) == 0 && ip.family == AF_INET && ntohl(ip.u.ip4.s_addr) == (0x7f000001)); test_assert(strcmp(net_ip2addr(&ip), "127.0.0.1") == 0); test_assert(net_addr2ip("255.254.253.252", &ip) == 0 && ip.family == AF_INET && ntohl(ip.u.ip4.s_addr) == (0xfffefdfc)); test_assert(strcmp(net_ip2addr(&ip), "255.254.253.252") == 0); test_assert(net_addr2ip("::5", &ip) == 0 && ip.family == AF_INET6 && ip.u.ip6.s6_addr[15] == 5); test_assert(strcmp(net_ip2addr(&ip), "::5") == 0); test_assert(net_addr2ip("[::5]", &ip) == 0 && ip.family == AF_INET6 && ip.u.ip6.s6_addr[15] == 5); test_assert(strcmp(net_ip2addr(&ip), "::5") == 0); ip.family = 123; test_assert(net_addr2ip("abc", &ip) < 0 && ip.family == 123); test_end(); } static void test_net_str2hostport(void) { const char *host; in_port_t port; test_begin("net_str2hostport()"); /* [IPv6] */ test_assert(net_str2hostport("[1::4]", 0, &host, &port) == 0 && strcmp(host, "1::4") == 0 && port == 0); test_assert(net_str2hostport("[1::4]", 1234, &host, &port) == 0 && strcmp(host, "1::4") == 0 && port == 1234); test_assert(net_str2hostport("[1::4]:78", 1234, &host, &port) == 0 && strcmp(host, "1::4") == 0 && port == 78); host = NULL; test_assert(net_str2hostport("[1::4]:", 1234, &host, &port) < 0 && host == NULL); test_assert(net_str2hostport("[1::4]:0", 1234, &host, &port) < 0 && host == NULL); test_assert(net_str2hostport("[1::4]:x", 1234, &host, &port) < 0 && host == NULL); /* IPv6 */ test_assert(net_str2hostport("1::4", 0, &host, &port) == 0 && strcmp(host, "1::4") == 0 && port == 0); test_assert(net_str2hostport("1::4", 1234, &host, &port) == 0 && strcmp(host, "1::4") == 0 && port == 1234); /* host */ test_assert(net_str2hostport("foo", 0, &host, &port) == 0 && strcmp(host, "foo") == 0 && port == 0); test_assert(net_str2hostport("foo", 1234, &host, &port) == 0 && strcmp(host, "foo") == 0 && port == 1234); test_assert(net_str2hostport("foo:78", 1234, &host, &port) == 0 && strcmp(host, "foo") == 0 && port == 78); host = NULL; test_assert(net_str2hostport("foo:", 1234, &host, &port) < 0 && host == NULL); test_assert(net_str2hostport("foo:0", 1234, &host, &port) < 0 && host == NULL); test_assert(net_str2hostport("foo:x", 1234, &host, &port) < 0 && host == NULL); /* edge cases with multiple ':' - currently these don't return errors, but perhaps they should. */ test_assert(net_str2hostport("foo::78", 1234, &host, &port) == 0 && strcmp(host, "foo::78") == 0 && port == 1234); test_assert(net_str2hostport("::foo:78", 1234, &host, &port) == 0 && strcmp(host, "::foo:78") == 0 && port == 1234); test_assert(net_str2hostport("[::foo]:78", 1234, &host, &port) == 0 && strcmp(host, "::foo") == 0 && port == 78); test_assert(net_str2hostport("[::::]", 1234, &host, &port) == 0 && strcmp(host, "::::") == 0 && port == 1234); test_assert(net_str2hostport("[::::]:78", 1234, &host, &port) == 0 && strcmp(host, "::::") == 0 && port == 78); test_end(); } static void test_net_unix_long_paths(void) { #ifdef ENAMETOOLONG int long_errno = ENAMETOOLONG; #else int long_errno = EOVERFLOW; #endif test_begin("net_*_unix() - long paths"); char path[PATH_MAX]; memset(path, 'x', sizeof(path)-1); path[sizeof(path)-1] = '\0'; test_assert(net_listen_unix(path, 1) == -1); test_assert(errno == long_errno); test_assert(net_connect_unix(path) == -1); test_assert(errno == long_errno); test_end(); } void test_net(void) { test_net_is_in_network(); test_net_ip2addr(); test_net_str2hostport(); test_net_unix_long_paths(); } dovecot-2.3.21.1/src/lib/unix-socket-create.h0000644000000000000000000000024314656633576015534 00000000000000#ifndef UNIX_SOCKET_CREATE_H #define UNIX_SOCKET_CREATE_H int unix_socket_create(const char *path, int mode, uid_t uid, gid_t gid, int backlog); #endif dovecot-2.3.21.1/src/lib/test-istream-unix.c0000644000000000000000000001034214656633576015420 00000000000000/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "net.h" #include "fdpass.h" #include "istream.h" #include "istream-unix.h" #include #include #include static int send_fd, send_fd2; static void write_one(int fd) { if (write(fd, "1", 1) < 0) i_fatal("write() failed: %m"); } static void read_one(int fd) { char buf; if (read(fd, &buf, 1) < 0) i_fatal("read() failed: m"); } static void test_server_read_nofd(struct istream *input, unsigned int idx) { const unsigned char *data; size_t size; test_assert_idx(i_stream_read_more(input, &data, &size) == 1, idx); i_stream_skip(input, 1); test_assert_idx(i_stream_unix_get_read_fd(input) == -1, idx); } static void test_server_read_fd(struct istream *input, int wanted_fd, unsigned int idx) { struct stat st1, st2; const unsigned char *data; size_t size; int recv_fd; test_assert_idx(i_stream_read_more(input, &data, &size) == 1, idx); i_stream_skip(input, 1); test_assert_idx((recv_fd = i_stream_unix_get_read_fd(input)) != -1, idx); if (recv_fd != -1) { if (fstat(recv_fd, &st1) < 0 || fstat(wanted_fd, &st2) < 0) i_fatal("fstat() failed: %m"); test_assert_idx(st1.st_ino == st2.st_ino, idx); i_close_fd(&recv_fd); } } static void test_istream_unix_server(int fd) { struct istream *input; const unsigned char *data; size_t size; input = i_stream_create_unix(fd, 1024); /* 1) simple read */ test_server_read_nofd(input, 1); write_one(fd); /* 2) fd was sent but we won't get it */ test_server_read_nofd(input, 2); /* we still shouldn't have the fd */ i_stream_set_blocking(input, FALSE); i_stream_unix_set_read_fd(input); test_assert(i_stream_read_more(input, &data, &size) == 0); test_assert(i_stream_unix_get_read_fd(input) == -1); i_stream_set_blocking(input, TRUE); write_one(fd); /* 3) the previous fd should be lost now */ test_server_read_nofd(input, 3); write_one(fd); /* 4) we should get the fd now */ test_server_read_fd(input, send_fd2, 4); write_one(fd); /* 5) the previous fd shouldn't be returned anymore */ i_stream_unix_set_read_fd(input); test_server_read_nofd(input, 5); write_one(fd); /* 6) with i_stream_unix_unset_read_fd() we shouldn't get fd anymore */ i_stream_unix_unset_read_fd(input); test_server_read_nofd(input, 6); write_one(fd); /* 7-8) two fds were sent, but we'll get only the first one */ i_stream_unix_set_read_fd(input); test_server_read_fd(input, send_fd, 7); test_server_read_nofd(input, 8); write_one(fd); /* 9-10) two fds were sent, and we'll get them both */ i_stream_unix_set_read_fd(input); test_server_read_fd(input, send_fd, 9); i_stream_unix_set_read_fd(input); test_server_read_fd(input, send_fd2, 10); write_one(fd); i_stream_destroy(&input); i_close_fd(&fd); } static void test_istream_unix_client(int fd) { /* 1) */ write_one(fd); read_one(fd); /* 2) */ if (fd_send(fd, send_fd, "1", 1) < 0) i_fatal("fd_send() failed: %m"); read_one(fd); /* 3) */ write_one(fd); read_one(fd); /* 4) */ if (fd_send(fd, send_fd2, "1", 1) < 0) i_fatal("fd_send() failed: %m"); read_one(fd); /* 5) */ write_one(fd); read_one(fd); /* 6) */ if (fd_send(fd, send_fd, "1", 1) < 0) i_fatal("fd_send() failed: %m"); read_one(fd); /* 7-8) */ if (fd_send(fd, send_fd, "1", 1) < 0) i_fatal("fd_send() failed: %m"); if (fd_send(fd, send_fd2, "1", 1) < 0) i_fatal("fd_send() failed: %m"); read_one(fd); /* 9-10) */ if (fd_send(fd, send_fd, "1", 1) < 0) i_fatal("fd_send() failed: %m"); if (fd_send(fd, send_fd2, "1", 1) < 0) i_fatal("fd_send() failed: %m"); read_one(fd); i_close_fd(&fd); } void test_istream_unix(void) { int fd[2]; test_begin("istream unix"); if ((send_fd = open("/dev/null", O_RDONLY)) == -1) i_fatal("open(/dev/null) failed: %m"); if ((send_fd2 = open("/dev/zero", O_RDONLY)) == -1) i_fatal("open(/dev/zero) failed: %m"); if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) i_fatal("socketpair() failed: %m"); switch (fork()) { case -1: i_fatal("fork() failed: %m"); case 0: i_close_fd(&fd[0]); test_istream_unix_client(fd[1]); test_exit(0); default: i_close_fd(&fd[1]); test_istream_unix_server(fd[0]); break; } i_close_fd(&send_fd); i_close_fd(&send_fd2); test_end(); } dovecot-2.3.21.1/src/lib/test-istream-chain.c0000644000000000000000000001463014656633576015523 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "istream-private.h" #include "istream-chain.h" static void test_istream_chain_basic(void) { struct istream *input, *test_input, *test_input2; struct istream_chain *chain; const unsigned char *data; size_t size; test_begin("istream chain"); test_input = test_istream_create("stream1"); test_input2 = test_istream_create("STREAM2"); input = i_stream_create_chain(&chain, IO_BLOCK_SIZE); /* no input */ test_assert(i_stream_read(input) == 0); /* stream1 input */ i_stream_chain_append(chain, test_input); test_assert(i_stream_read(input) == 7); data = i_stream_get_data(input, &size); test_assert(size == 7 && memcmp(data, "stream1", 7) == 0); test_assert(i_stream_read(input) == 0); data = i_stream_get_data(input, &size); test_assert(size == 7 && memcmp(data, "stream1", 7) == 0); /* STREAM2 input */ i_stream_chain_append(chain, test_input2); test_assert(i_stream_read(input) == 7); data = i_stream_get_data(input, &size); test_assert(size == 14 && memcmp(data, "stream1STREAM2", 14) == 0); test_assert(i_stream_read(input) == 0); data = i_stream_get_data(input, &size); test_assert(size == 14 && memcmp(data, "stream1STREAM2", 14) == 0); /* EOF */ i_stream_chain_append_eof(chain); test_assert(i_stream_read(input) == -1 && input->eof && input->stream_errno == 0); data = i_stream_get_data(input, &size); test_assert(size == 14 && memcmp(data, "stream1STREAM2", 14) == 0); i_stream_unref(&input); test_assert(test_input->eof && test_input->stream_errno == 0); test_assert(test_input2->eof && test_input2->stream_errno == 0); i_stream_unref(&test_input); i_stream_unref(&test_input2); test_end(); } static void test_istream_chain_early_end(void) { struct istream *input, *test_input; struct istream_chain *chain; test_begin("istream chain early end"); test_input = test_istream_create("string"); test_istream_set_size(test_input, 3); test_istream_set_allow_eof(test_input, FALSE); input = i_stream_create_chain(&chain, IO_BLOCK_SIZE); i_stream_chain_append(chain, test_input); test_assert(i_stream_read(input) == 3); test_istream_set_size(test_input, 5); test_assert(i_stream_read(input) == 2); /* with current implementation we could skip less than 5 and have v_offset<5, but I don't think that can work in all situations. the normal case is anyway that we'll read everything up until some point and skip over all the data up to there. */ i_stream_skip(input, 5); i_stream_unref(&input); test_assert(test_input->v_offset == 5); i_stream_unref(&test_input); test_end(); } static void test_istream_chain_accumulate(void) { struct istream *input, *tmp_istream; struct istream *test_istreams[5]; struct istream_chain *chain; const unsigned char *data; size_t size; test_begin("istream chain accumulate"); test_istreams[0] = test_istream_create("abcdefghijklmnopqrst"); test_istreams[1] = test_istream_create("ABCDEFGHIJKLMNOPQRSTUVWXY"); test_istreams[2] = test_istream_create("!\"#$%&'()*+,-./01234567890:;<="); test_istreams[3] = test_istream_create("z1y2x3w4v5u6t7s8r9q0p.o,n"); test_istreams[4] = test_istream_create("aAbBcCdDeEfFgGhHiIjJ"); input = i_stream_create_chain(&chain, IO_BLOCK_SIZE); /* no input */ test_assert(i_stream_read(input) == 0); /* first stream */ i_stream_chain_append(chain, test_istreams[0]); tmp_istream = test_istreams[0]; i_stream_unref(&tmp_istream); test_assert(i_stream_read_data(input, &data, &size, 0) == 1); test_assert(size == 20); test_assert(memcmp(data, "abcdefghijklmnopqrst", 20) == 0); /* partially skip */ i_stream_skip(input, 12); /* second stream */ i_stream_chain_append(chain, test_istreams[1]); tmp_istream = test_istreams[1]; i_stream_unref(&tmp_istream); test_istream_set_size(test_istreams[1], 0); test_assert(i_stream_read_data(input, &data, &size, 10) == 0); test_assert(size == 8); test_istream_set_size(test_istreams[1], 10); test_assert(i_stream_read_data(input, &data, &size, 10) == 1); test_assert(size == 18); test_istream_set_allow_eof(test_istreams[1], FALSE); test_assert(i_stream_read(input) == 0); test_istream_set_size(test_istreams[1], 25); test_istream_set_allow_eof(test_istreams[1], TRUE); test_assert(i_stream_read_data(input, &data, &size, 30) == 1); test_assert(size == 33); test_assert(memcmp(data, "mnopqrst" "ABCDEFGHIJKLMNOPQRSTUVWXY", 33) == 0); /* partially skip */ i_stream_skip(input, 12); /* third stream */ i_stream_chain_append(chain, test_istreams[2]); tmp_istream = test_istreams[2]; i_stream_unref(&tmp_istream); test_istream_set_size(test_istreams[2], 0); test_assert(i_stream_read(input) == 0); test_istream_set_size(test_istreams[2], 30); test_assert(i_stream_read_data(input, &data, &size, 25) == 1); test_assert(size == 51); test_assert(memcmp(data, "EFGHIJKLMNOPQRSTUVWXY" "!\"#$%&'()*+,-./01234567890:;<=", 51) == 0); test_assert(i_stream_read(input) == 0); /* partially skip */ i_stream_skip(input, 12); /* forth stream */ i_stream_chain_append(chain, test_istreams[3]); tmp_istream = test_istreams[3]; i_stream_unref(&tmp_istream); test_assert(i_stream_read_data(input, &data, &size, 40) == 1); test_assert(size == 64); test_assert(memcmp(data, "QRSTUVWXY" "!\"#$%&'()*+,-./01234567890:;<=" "z1y2x3w4v5u6t7s8r9q0p.o,n", 64) == 0); /* partially skip */ i_stream_skip(input, 6); /* fifth stream */ i_stream_chain_append(chain, test_istreams[4]); tmp_istream = test_istreams[4]; i_stream_unref(&tmp_istream); test_assert(i_stream_read_data(input, &data, &size, 60) == 1); test_assert(size == 78); test_assert(memcmp(data, "WXY" "!\"#$%&'()*+,-./01234567890:;<=" "z1y2x3w4v5u6t7s8r9q0p.o,n" "aAbBcCdDeEfFgGhHiIjJ", 78) == 0); /* EOF */ i_stream_chain_append_eof(chain); test_assert(i_stream_read(input) == -1); test_assert(input->eof && input->stream_errno == 0); test_assert(i_stream_read_data(input, &data, &size, 78) == -1); test_assert(size == 78); test_assert(memcmp(data, "WXY" "!\"#$%&'()*+,-./01234567890:;<=" "z1y2x3w4v5u6t7s8r9q0p.o,n" "aAbBcCdDeEfFgGhHiIjJ", 78) == 0); /* skip rest */ i_stream_skip(input, 78); test_assert(i_stream_read(input) == -1); test_assert(input->eof && input->stream_errno == 0); data = i_stream_get_data(input, &size); test_assert(size == 0); i_stream_unref(&input); test_end(); } void test_istream_chain(void) { test_istream_chain_basic(); test_istream_chain_early_end(); test_istream_chain_accumulate(); } dovecot-2.3.21.1/src/lib/test-multiplex.c0000644000000000000000000001053214656633576015017 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "ioloop.h" #include "str.h" #include "istream.h" #include "istream-multiplex.h" #include "ostream.h" #include "ostream-multiplex.h" #include "ostream.h" #include "randgen.h" #include struct test_channel { int fds[2]; unsigned int cid; struct istream *in; struct ostream *out; struct io *io; struct istream *in_alt; struct ostream *out_alt; struct io *io_alt; buffer_t *received; buffer_t *received_alt; unsigned int counter; }; static struct test_channel test_channel[2]; static void test_multiplex_channel_write(struct test_channel *channel) { unsigned char buf[128]; size_t len = i_rand_limit(sizeof(buf)); random_fill(buf, len); o_stream_nsend(channel->out, buf, len); o_stream_nsend(channel->out_alt, buf, len); } static void test_multiplex_stream_write(struct ostream *channel ATTR_UNUSED) { if (test_channel[0].received->used > 1000 && test_channel[1].received->used > 1000) io_loop_stop(current_ioloop); else test_multiplex_channel_write(&test_channel[i_rand_limit(2)]); } static void test_istream_multiplex_stream_read(struct test_channel *channel) { const unsigned char *data = NULL; size_t siz = 0; if (i_stream_read(channel->in) > 0) { data = i_stream_get_data(channel->in, &siz); buffer_append(channel->received, data, siz); i_stream_skip(channel->in, siz); } } static void test_istream_read_alt(struct test_channel *channel) { const unsigned char *data = NULL; size_t siz = 0; if (i_stream_read(channel->in_alt) > 0) { data = i_stream_get_data(channel->in_alt, &siz); buffer_append(channel->received_alt, data, siz); i_stream_skip(channel->in_alt, siz); } } static void setup_channel(struct test_channel *channel, struct istream *is, struct ostream *os) { /* setup first channel */ channel->in = is; channel->out = os; channel->io = io_add_istream(is, test_istream_multiplex_stream_read, channel); test_assert(pipe(channel->fds) == 0); fd_set_nonblock(channel->fds[0], TRUE); fd_set_nonblock(channel->fds[1], TRUE); channel->in_alt = i_stream_create_fd(channel->fds[0], SIZE_MAX); channel->out_alt = o_stream_create_fd(channel->fds[1], IO_BLOCK_SIZE); channel->io_alt = io_add_istream(channel->in_alt, test_istream_read_alt, channel); channel->received = buffer_create_dynamic(default_pool, 32768); channel->received_alt = buffer_create_dynamic(default_pool, 32768); } static void teardown_channel(struct test_channel *channel) { test_istream_read_alt(channel); test_assert(memcmp(channel->received->data, channel->received_alt->data, channel->received->used) == 0); test_assert(channel->received->used == channel->received_alt->used); buffer_free(&channel->received); buffer_free(&channel->received_alt); io_remove(&channel->io); io_remove(&channel->io_alt); i_stream_unref(&channel->in); test_assert(o_stream_finish(channel->out) > 0); o_stream_unref(&channel->out); i_stream_unref(&channel->in_alt); test_assert(o_stream_finish(channel->out_alt) > 0); o_stream_unref(&channel->out_alt); i_close_fd(&channel->fds[0]); i_close_fd(&channel->fds[1]); } static void test_multiplex_stream(void) { test_begin("test multiplex (stream)"); struct ioloop *ioloop = io_loop_create(); io_loop_set_current(ioloop); int fds[2]; test_assert(pipe(fds) == 0); fd_set_nonblock(fds[0], TRUE); fd_set_nonblock(fds[1], TRUE); struct ostream *os = o_stream_create_fd(fds[1], SIZE_MAX); struct istream *is = i_stream_create_fd(fds[0], SIZE_MAX); struct istream *ichan0 = i_stream_create_multiplex(is, SIZE_MAX); struct istream *ichan1 = i_stream_multiplex_add_channel(ichan0, 1); i_stream_unref(&is); struct ostream *ochan0 = o_stream_create_multiplex(os, 1024); struct ostream *ochan1 = o_stream_multiplex_add_channel(ochan0, 1); o_stream_unref(&os); struct io *io = io_add(fds[1], IO_WRITE, test_multiplex_stream_write, os); setup_channel(&test_channel[0], ichan0, ochan0); setup_channel(&test_channel[1], ichan1, ochan1); test_channel[0].cid = 0; test_channel[1].cid = 1; io_loop_run(current_ioloop); io_remove(&io); teardown_channel(&test_channel[0]); teardown_channel(&test_channel[1]); io_loop_destroy(&ioloop); i_close_fd(&fds[0]); i_close_fd(&fds[1]); test_end(); } void test_multiplex(void) { random_init(); test_multiplex_stream(); random_deinit(); } dovecot-2.3.21.1/src/lib/iostream-proxy.c0000644000000000000000000001037514656633576015026 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "str.h" #include "iostream-pump.h" #include "iostream-proxy.h" #include #undef iostream_proxy_set_completion_callback struct iostream_proxy { struct iostream_pump *ltr; struct iostream_pump *rtl; unsigned int ref; iostream_proxy_callback_t *callback; void *context; }; static void iostream_proxy_completion(struct iostream_proxy *proxy, enum iostream_proxy_side side, enum iostream_pump_status pump_status) { enum iostream_proxy_status status; switch (pump_status) { case IOSTREAM_PUMP_STATUS_INPUT_EOF: status = IOSTREAM_PROXY_STATUS_INPUT_EOF; break; case IOSTREAM_PUMP_STATUS_INPUT_ERROR: status = IOSTREAM_PROXY_STATUS_INPUT_ERROR; break; case IOSTREAM_PUMP_STATUS_OUTPUT_ERROR: status = IOSTREAM_PROXY_STATUS_OTHER_SIDE_OUTPUT_ERROR; break; default: i_unreached(); } proxy->callback(side, status, proxy->context); } static void iostream_proxy_rtl_completion(enum iostream_pump_status status, struct iostream_proxy *proxy) { iostream_proxy_completion(proxy, IOSTREAM_PROXY_SIDE_RIGHT, status); } static void iostream_proxy_ltr_completion(enum iostream_pump_status status, struct iostream_proxy *proxy) { iostream_proxy_completion(proxy, IOSTREAM_PROXY_SIDE_LEFT, status); } struct iostream_proxy * iostream_proxy_create(struct istream *left_input, struct ostream *left_output, struct istream *right_input, struct ostream *right_output) { i_assert(left_input != NULL && right_input != NULL && left_output != NULL && right_output != NULL); /* create proxy */ struct iostream_proxy *proxy = i_new(struct iostream_proxy, 1); proxy->ltr = iostream_pump_create(left_input, right_output); proxy->rtl = iostream_pump_create(right_input, left_output); iostream_pump_set_completion_callback(proxy->ltr, iostream_proxy_ltr_completion, proxy); iostream_pump_set_completion_callback(proxy->rtl, iostream_proxy_rtl_completion, proxy); proxy->ref = 1; return proxy; } void iostream_proxy_start(struct iostream_proxy *proxy) { i_assert(proxy != NULL); i_assert(proxy->callback != NULL); iostream_pump_start(proxy->rtl); iostream_pump_start(proxy->ltr); } void iostream_proxy_set_completion_callback(struct iostream_proxy *proxy, iostream_proxy_callback_t *callback, void *context) { i_assert(proxy != NULL); proxy->callback = callback; proxy->context = context; } struct istream *iostream_proxy_get_istream(struct iostream_proxy *proxy, enum iostream_proxy_side side) { i_assert(proxy != NULL); switch(side) { case IOSTREAM_PROXY_SIDE_LEFT: return iostream_pump_get_input(proxy->ltr); case IOSTREAM_PROXY_SIDE_RIGHT: return iostream_pump_get_input(proxy->rtl); default: i_unreached(); } } struct ostream *iostream_proxy_get_ostream(struct iostream_proxy *proxy, enum iostream_proxy_side side) { i_assert(proxy != NULL); switch(side) { case IOSTREAM_PROXY_SIDE_LEFT: return iostream_pump_get_output(proxy->ltr); case IOSTREAM_PROXY_SIDE_RIGHT: return iostream_pump_get_output(proxy->rtl); default: i_unreached(); } } void iostream_proxy_ref(struct iostream_proxy *proxy) { i_assert(proxy != NULL && proxy->ref > 0); proxy->ref++; } void iostream_proxy_unref(struct iostream_proxy **proxy_r) { struct iostream_proxy *proxy; if (proxy_r == NULL || *proxy_r == NULL) return; proxy = *proxy_r; *proxy_r = NULL; i_assert(proxy->ref > 0); if (--proxy->ref == 0) { /* pumps will call stop internally if refcount drops to 0 */ iostream_pump_unref(&proxy->ltr); iostream_pump_unref(&proxy->rtl); i_free(proxy); } } void iostream_proxy_stop(struct iostream_proxy *proxy) { i_assert(proxy != NULL); iostream_pump_stop(proxy->ltr); iostream_pump_stop(proxy->rtl); } bool iostream_proxy_is_waiting_output(struct iostream_proxy *proxy, enum iostream_proxy_side side) { switch (side) { case IOSTREAM_PROXY_SIDE_LEFT: return iostream_pump_is_waiting_output(proxy->ltr); case IOSTREAM_PROXY_SIDE_RIGHT: return iostream_pump_is_waiting_output(proxy->rtl); } i_unreached(); } void iostream_proxy_switch_ioloop(struct iostream_proxy *proxy) { i_assert(proxy != NULL); iostream_pump_switch_ioloop(proxy->ltr); iostream_pump_switch_ioloop(proxy->rtl); } dovecot-2.3.21.1/src/lib/test-mempool-alloconly.c0000644000000000000000000000455214656633576016443 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" static bool mem_has_bytes(const void *mem, size_t size, uint8_t b) { const uint8_t *bytes = mem; unsigned int i; for (i = 0; i < size; i++) { if (bytes[i] != b) return FALSE; } return TRUE; } void test_mempool_alloconly(void) { #define SENTRY_SIZE 32 #define SENTRY_CHAR 0xDE #define PMALLOC_MAX_COUNT 128 pool_t pool; unsigned int i, j, k; void *mem[PMALLOC_MAX_COUNT + 1]; char *sentry; test_begin("mempool_alloconly"); for (i = 0; i < 64; i++) { for (j = 1; j <= 128; j++) { pool = pool_alloconly_create(MEMPOOL_GROWING"test", i); /* make sure p_malloc() doesn't overwrite unallocated data in data stack. parts of the code relies on this. */ sentry = t_buffer_get(SENTRY_SIZE); memset(sentry, SENTRY_CHAR, SENTRY_SIZE); mem[0] = p_malloc(pool, j); memset(mem[0], j, j); for (k = 1; k <= PMALLOC_MAX_COUNT; k++) { mem[k] = p_malloc(pool, k); memset(mem[k], k, k); } test_assert(mem_has_bytes(sentry, SENTRY_SIZE, SENTRY_CHAR)); test_assert(t_buffer_get(SENTRY_SIZE) == sentry); test_assert(mem_has_bytes(mem[0], j, j)); for (k = 1; k <= PMALLOC_MAX_COUNT; k++) test_assert(mem_has_bytes(mem[k], k, k)); pool_unref(&pool); } } test_end(); } enum fatal_test_state fatal_mempool_alloconly(unsigned int stage) { static pool_t pool; if (pool == NULL && stage != 0) return FATAL_TEST_FAILURE; switch(stage) { case 0: /* forbidden size */ test_begin("fatal_mempool_alloconly"); pool = pool_alloconly_create(MEMPOOL_GROWING"fatal", 1); test_expect_fatal_string("Trying to allocate 0 bytes"); (void)p_malloc(pool, 0); return FATAL_TEST_FAILURE; case 1: /* logically impossible size */ test_expect_fatal_string("Trying to allocate"); (void)p_malloc(pool, POOL_MAX_ALLOC_SIZE + 1ULL); return FATAL_TEST_FAILURE; #ifdef _LP64 /* malloc(POOL_MAX_ALLOC_SIZE) may succeed with 32bit */ case 2: /* physically impossible size */ test_expect_fatal_string("Out of memory"); (void)p_malloc(pool, POOL_MAX_ALLOC_SIZE); return FATAL_TEST_FAILURE; #endif /* Continue with other tests as follows: case 3: something_fatal(); return FATAL_TEST_FAILURE; */ } /* Either our tests have finished, or the test suite has got confused. */ pool_unref(&pool); test_end(); return FATAL_TEST_FINISHED; } dovecot-2.3.21.1/src/lib/lib-event.c0000644000000000000000000013574114656633576013716 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "lib-event-private.h" #include "event-filter.h" #include "array.h" #include "llist.h" #include "time-util.h" #include "str.h" #include "strescape.h" #include "ioloop-private.h" #include enum event_code { EVENT_CODE_ALWAYS_LOG_SOURCE = 'a', EVENT_CODE_CATEGORY = 'c', EVENT_CODE_TV_LAST_SENT = 'l', EVENT_CODE_SENDING_NAME = 'n', EVENT_CODE_SOURCE = 's', EVENT_CODE_FIELD_INTMAX = 'I', EVENT_CODE_FIELD_STR = 'S', EVENT_CODE_FIELD_TIMEVAL = 'T', EVENT_CODE_FIELD_STRLIST = 'L', }; /* Internal event category state. Each (unique) event category maps to one internal category. (I.e., if two places attempt to register the same category, they will share the internal state.) This is required in order to support multiple registrations of the same category. Currently, the only situation in which this occurs is the stats process receiving categories from other processes and also using the same categories internally. During registration, we look up the internal state based on the new category's name. If found, we use it after sanity checking that the two are identical (i.e., they both have the same name and parent). If not found, we allocate a new internal state and use it. We stash a pointer to the internal state in struct event_category (the "internal" member). As a result, all category structs for the same category point to the same internal state. */ struct event_internal_category { /* More than one category can be represented by the internal state. To give consumers a unique but consistent category pointer, we return a pointer to this 'represetative' category structure. Because we allocated it, we know that it will live exactly as long as we need it to. */ struct event_category representative; struct event_internal_category *parent; char *name; int refcount; }; struct event_reason { struct event *event; }; extern struct event_passthrough event_passthrough_vfuncs; static struct event *events = NULL; static struct event *current_global_event = NULL; static struct event *event_last_passthrough = NULL; static ARRAY(event_callback_t *) event_handlers; static ARRAY(event_category_callback_t *) event_category_callbacks; static ARRAY(struct event_internal_category *) event_registered_categories_internal; static ARRAY(struct event_category *) event_registered_categories_representative; static ARRAY(struct event *) global_event_stack; static uint64_t event_id_counter = 0; static void get_self_rusage(struct rusage *ru_r) { if (getrusage(RUSAGE_SELF, ru_r) < 0) i_fatal("getrusage() failed: %m"); } static struct event * event_create_internal(struct event *parent, const char *source_filename, unsigned int source_linenum); static struct event_internal_category * event_category_find_internal(const char *name); static struct event *last_passthrough_event(void) { i_assert(event_last_passthrough != NULL); return event_last_passthrough; } static void event_copy_parent_defaults(struct event *event, const struct event *parent) { event->always_log_source = parent->always_log_source; event->passthrough = parent->passthrough; event->min_log_level = parent->min_log_level; event->forced_debug = parent->forced_debug; } static bool event_find_category(const struct event *event, const struct event_category *category); static void event_set_changed(struct event *event) { event->change_id++; /* It's unlikely that change_id will ever wrap, but lets be safe anyway. */ if (event->change_id == 0 || event->change_id == event->sent_to_stats_id) event->change_id++; } static bool event_call_callbacks(struct event *event, enum event_callback_type type, struct failure_context *ctx, const char *fmt, va_list args) { event_callback_t *callback; array_foreach_elem(&event_handlers, callback) { bool ret; T_BEGIN { ret = callback(event, type, ctx, fmt, args); } T_END; if (!ret) { /* event sending was stopped */ return FALSE; } } return TRUE; } static void event_call_callbacks_noargs(struct event *event, enum event_callback_type type, ...) { va_list args; /* the args are empty and not used for anything, but there doesn't seem to be any nice and standard way of passing an initialized va_list as a parameter without va_start(). */ va_start(args, type); (void)event_call_callbacks(event, type, NULL, NULL, args); va_end(args); } void event_copy_categories(struct event *to, struct event *from) { unsigned int cat_count; struct event_category *const *categories = event_get_categories(from, &cat_count); for (unsigned int i = 1; i <= cat_count; i++) event_add_category(to, categories[cat_count-i]); } void event_copy_fields(struct event *to, struct event *from) { const struct event_field *fld; unsigned int count; const char *const *values; if (!array_is_created(&from->fields)) return; array_foreach(&from->fields, fld) { switch (fld->value_type) { case EVENT_FIELD_VALUE_TYPE_STR: event_add_str(to, fld->key, fld->value.str); break; case EVENT_FIELD_VALUE_TYPE_INTMAX: event_add_int(to, fld->key, fld->value.intmax); break; case EVENT_FIELD_VALUE_TYPE_TIMEVAL: event_add_timeval(to, fld->key, &fld->value.timeval); break; case EVENT_FIELD_VALUE_TYPE_STRLIST: values = array_get(&fld->value.strlist, &count); for (unsigned int i = 0; i < count; i++) event_strlist_append(to, fld->key, values[i]); break; default: break; } } } bool event_has_all_categories(struct event *event, const struct event *other) { struct event_category **cat; if (!array_is_created(&other->categories)) return TRUE; if (!array_is_created(&event->categories)) return FALSE; array_foreach_modifiable(&other->categories, cat) { if (!event_find_category(event, *cat)) return FALSE; } return TRUE; } bool event_has_all_fields(struct event *event, const struct event *other) { struct event_field *fld; if (!array_is_created(&other->fields)) return TRUE; array_foreach_modifiable(&other->fields, fld) { if (event_find_field_nonrecursive(event, fld->key) == NULL) return FALSE; } return TRUE; } struct event *event_dup(const struct event *source) { struct event *ret = event_create_internal(source->parent, source->source_filename, source->source_linenum); string_t *str = t_str_new(256); const char *err; event_export(source, str); if (!event_import(ret, str_c(str), &err)) i_panic("event_import(%s) failed: %s", str_c(str), err); ret->tv_created_ioloop = source->tv_created_ioloop; return ret; } /* * Copy the source's categories and fields recursively. * * We recurse to the parent before copying this event's data because we may * be overriding a field. */ static void event_flatten_recurse(struct event *dst, struct event *src, struct event *limit) { if (src->parent != limit) event_flatten_recurse(dst, src->parent, limit); event_copy_categories(dst, src); event_copy_fields(dst, src); } struct event *event_flatten(struct event *src) { struct event *dst; /* If we don't have a parent or a global event, we have nothing to flatten. */ if (src->parent == NULL && current_global_event == NULL) return event_ref(src); /* We have to flatten the event. */ dst = event_create_internal(NULL, src->source_filename, src->source_linenum); dst = event_set_name(dst, src->sending_name); if (current_global_event != NULL) event_flatten_recurse(dst, current_global_event, NULL); event_flatten_recurse(dst, src, NULL); dst->tv_created_ioloop = src->tv_created_ioloop; dst->tv_created = src->tv_created; dst->tv_last_sent = src->tv_last_sent; return dst; } static inline void replace_parent_ref(struct event *event, struct event *new) { if (event->parent == new) return; /* no-op */ if (new != NULL) event_ref(new); event_unref(&event->parent); event->parent = new; } /* * Minimize the event and its ancestry. * * In general, the chain of parents starting from this event can be divided * up into four consecutive ranges: * * 1. the event itself * 2. a range of events that should be flattened into the event itself * 3. a range of trivial (i.e., no categories or fields) events that should * be skipped * 4. the rest of the chain * * Except for the first range, the event itself, the remaining ranges can * have zero events. * * As the names of these ranges imply, we want to flatten certain parts of * the ancestry, skip other parts of the ancestry and leave the remainder * untouched. * * For example, suppose that we have an event (A) with ancestors forming the * following graph: * * A -> B -> C -> D -> E -> F * * Further, suppose that B, C, and F contain some categories or fields but * have not yet been sent to an external process that knows how to reference * previously encountered events, and D contains no fields or categories of * its own (but it inherits some from E and F). * * We can define the 4 ranges: * * A: the event * B-C: flattening * D: skipping * E-end: the rest * * The output would therefore be: * * G -> E -> F * * where G contains the fields and categories of A, B, and C (and trivially * D beacuse D was empty). * * Note that even though F has not yet been sent out, we send it now because * it is part of the "rest" range. * * TODO: We could likely apply this function recursively on the "rest" * range, but further investigation is required to determine whether it is * worth it. */ struct event *event_minimize(struct event *event) { struct event *flatten_bound; struct event *skip_bound; struct event *new_event; struct event *cur; if (event->parent == NULL) return event_ref(event); /* find the bound for field/category flattening */ flatten_bound = NULL; for (cur = event->parent; cur != NULL; cur = cur->parent) { if (cur->sent_to_stats_id == 0 && timeval_cmp(&cur->tv_created_ioloop, &event->tv_created_ioloop) == 0) continue; flatten_bound = cur; break; } /* continue to find the bound for empty event skipping */ skip_bound = NULL; for (; cur != NULL; cur = cur->parent) { if (cur->sent_to_stats_id == 0 && (!array_is_created(&cur->fields) || array_is_empty(&cur->fields)) && (!array_is_created(&cur->categories) || array_is_empty(&cur->categories))) continue; skip_bound = cur; break; } /* fast path - no flattening and no skipping to do */ if ((event->parent == flatten_bound) && (event->parent == skip_bound)) return event_ref(event); new_event = event_dup(event); /* flatten */ event_flatten_recurse(new_event, event, flatten_bound); replace_parent_ref(new_event, flatten_bound); /* skip */ replace_parent_ref(new_event, skip_bound); return new_event; } static struct event * event_create_internal(struct event *parent, const char *source_filename, unsigned int source_linenum) { struct event *event; pool_t pool = pool_alloconly_create(MEMPOOL_GROWING"event", 1024); event = p_new(pool, struct event, 1); event->refcount = 1; event->id = ++event_id_counter; event->pool = pool; event->tv_created_ioloop = ioloop_timeval; event->min_log_level = LOG_TYPE_INFO; i_gettimeofday(&event->tv_created); event->source_filename = p_strdup(pool, source_filename); event->source_linenum = source_linenum; event->change_id = 1; if (parent != NULL) { event->parent = parent; event_ref(event->parent); event_copy_parent_defaults(event, parent); } DLLIST_PREPEND(&events, event); return event; } #undef event_create struct event *event_create(struct event *parent, const char *source_filename, unsigned int source_linenum) { struct event *event; event = event_create_internal(parent, source_filename, source_linenum); (void)event_call_callbacks_noargs(event, EVENT_CALLBACK_TYPE_CREATE); return event; } #undef event_create_passthrough struct event_passthrough * event_create_passthrough(struct event *parent, const char *source_filename, unsigned int source_linenum) { if (!parent->passthrough) { if (event_last_passthrough != NULL) { /* API is being used in a wrong or dangerous way */ i_panic("Can't create multiple passthrough events - " "finish the earlier with ->event()"); } struct event *event = event_create(parent, source_filename, source_linenum); event->passthrough = TRUE; /* This event only intends to extend the parent event. Use the parent's creation timestamp. */ event->tv_created_ioloop = parent->tv_created_ioloop; event->tv_created = parent->tv_created; memcpy(&event->ru_last, &parent->ru_last, sizeof(parent->ru_last)); event_last_passthrough = event; } else { event_last_passthrough = parent; } return &event_passthrough_vfuncs; } struct event *event_ref(struct event *event) { i_assert(event->refcount > 0); event->refcount++; return event; } void event_unref(struct event **_event) { struct event *event = *_event; if (event == NULL) return; *_event = NULL; i_assert(event->refcount > 0); if (--event->refcount > 0) return; i_assert(event != current_global_event); event_call_callbacks_noargs(event, EVENT_CALLBACK_TYPE_FREE); if (event_last_passthrough == event) event_last_passthrough = NULL; if (event->log_prefix_from_system_pool) i_free(event->log_prefix); i_free(event->sending_name); event_unref(&event->parent); DLLIST_REMOVE(&events, event); pool_unref(&event->pool); } struct event *events_get_head(void) { return events; } struct event *event_push_global(struct event *event) { i_assert(event != NULL); if (current_global_event != NULL) { if (!array_is_created(&global_event_stack)) i_array_init(&global_event_stack, 4); array_push_back(&global_event_stack, ¤t_global_event); } current_global_event = event; return event; } struct event *event_pop_global(struct event *event) { i_assert(event != NULL); i_assert(event == current_global_event); /* If the active context's root event is popped, we'll assert-crash later on when deactivating the context and the root event no longer exists. */ i_assert(event != io_loop_get_active_global_root()); if (!array_is_created(&global_event_stack) || array_count(&global_event_stack) == 0) current_global_event = NULL; else { unsigned int event_count; struct event *const *events = array_get(&global_event_stack, &event_count); i_assert(event_count > 0); current_global_event = events[event_count-1]; array_delete(&global_event_stack, event_count-1, 1); } return current_global_event; } struct event *event_get_global(void) { return current_global_event; } #undef event_reason_begin struct event_reason * event_reason_begin(const char *reason_code, const char *source_filename, unsigned int source_linenum) { struct event_reason *reason; reason = i_new(struct event_reason, 1); reason->event = event_create(event_get_global(), source_filename, source_linenum); event_strlist_append(reason->event, EVENT_REASON_CODE, reason_code); event_push_global(reason->event); return reason; } void event_reason_end(struct event_reason **_reason) { struct event_reason *reason = *_reason; if (reason == NULL) return; event_pop_global(reason->event); /* This event was created only for global use. It shouldn't be permanently stored anywhere. This assert could help catch bugs. */ i_assert(reason->event->refcount == 1); event_unref(&reason->event); i_free(reason); } const char *event_reason_code(const char *module, const char *name) { return event_reason_code_prefix(module, "", name); } static bool event_reason_code_module_validate(const char *module) { const char *p; for (p = module; *p != '\0'; p++) { if (*p == ' ' || *p == '-' || *p == ':') return FALSE; if (i_isupper(*p)) return FALSE; } return TRUE; } const char *event_reason_code_prefix(const char *module, const char *name_prefix, const char *name) { const char *p; i_assert(module[0] != '\0'); i_assert(name[0] != '\0'); if (!event_reason_code_module_validate(module)) { i_panic("event_reason_code_prefix(): " "Invalid module '%s'", module); } if (!event_reason_code_module_validate(name_prefix)) { i_panic("event_reason_code_prefix(): " "Invalid name_prefix '%s'", name_prefix); } string_t *str = t_str_new(strlen(module) + 1 + strlen(name_prefix) + strlen(name)); str_append(str, module); str_append_c(str, ':'); str_append(str, name_prefix); for (p = name; *p != '\0'; p++) { switch (*p) { case ' ': case '-': str_append_c(str, '_'); break; case ':': i_panic("event_reason_code_prefix(): " "name has ':' (%s, %s%s)", module, name_prefix, name); default: str_append_c(str, i_tolower(*p)); break; } } return str_c(str); } static struct event * event_set_log_prefix(struct event *event, const char *prefix, bool append) { event->log_prefix_callback = NULL; event->log_prefix_callback_context = NULL; if (event->log_prefix == NULL) { /* allocate the first log prefix from the pool */ event->log_prefix = p_strdup(event->pool, prefix); } else { /* log prefix is being updated multiple times - switch to system pool so we don't keep leaking memory */ if (event->log_prefix_from_system_pool) i_free(event->log_prefix); else event->log_prefix_from_system_pool = TRUE; event->log_prefix = i_strdup(prefix); } event->log_prefix_replace = !append; return event; } struct event * event_set_append_log_prefix(struct event *event, const char *prefix) { return event_set_log_prefix(event, prefix, TRUE); } struct event *event_replace_log_prefix(struct event *event, const char *prefix) { return event_set_log_prefix(event, prefix, FALSE); } struct event * event_drop_parent_log_prefixes(struct event *event, unsigned int count) { event->log_prefixes_dropped = count; return event; } #undef event_set_log_prefix_callback struct event * event_set_log_prefix_callback(struct event *event, bool replace, event_log_prefix_callback_t *callback, void *context) { if (event->log_prefix_from_system_pool) i_free(event->log_prefix); else event->log_prefix = NULL; event->log_prefix_replace = replace; event->log_prefix_callback = callback; event->log_prefix_callback_context = context; return event; } #undef event_set_log_message_callback struct event * event_set_log_message_callback(struct event *event, event_log_message_callback_t *callback, void *context) { event->log_message_callback = callback; event->log_message_callback_context = context; return event; } #undef event_unset_log_message_callback void event_unset_log_message_callback(struct event *event, event_log_message_callback_t *callback, void *context) { i_assert(event->log_message_callback == callback); i_assert(event->log_message_callback_context == context); event->log_message_callback = NULL; event->log_message_callback_context = NULL; } struct event * event_set_name(struct event *event, const char *name) { i_free(event->sending_name); event->sending_name = i_strdup(name); return event; } struct event * event_set_source(struct event *event, const char *filename, unsigned int linenum, bool literal_fname) { if (strcmp(event->source_filename, filename) != 0) { event->source_filename = literal_fname ? filename : p_strdup(event->pool, filename); } event->source_linenum = linenum; return event; } struct event *event_set_always_log_source(struct event *event) { event->always_log_source = TRUE; return event; } struct event *event_set_min_log_level(struct event *event, enum log_type level) { event->min_log_level = level; event_recalculate_debug_level(event); return event; } enum log_type event_get_min_log_level(const struct event *event) { return event->min_log_level; } struct event *event_set_ptr(struct event *event, const char *key, void *value) { struct event_pointer *p; if (!array_is_created(&event->pointers)) p_array_init(&event->pointers, event->pool, 4); else { /* replace existing pointer if the key already exists */ array_foreach_modifiable(&event->pointers, p) { if (strcmp(p->key, key) == 0) { p->value = value; return event; } } } p = array_append_space(&event->pointers); p->key = p_strdup(event->pool, key); p->value = value; return event; } void *event_get_ptr(const struct event *event, const char *key) { const struct event_pointer *p; if (!array_is_created(&event->pointers)) return NULL; array_foreach(&event->pointers, p) { if (strcmp(p->key, key) == 0) return p->value; } return NULL; } struct event_category *event_category_find_registered(const char *name) { struct event_category *cat; array_foreach_elem(&event_registered_categories_representative, cat) { if (strcmp(cat->name, name) == 0) return cat; } return NULL; } static struct event_internal_category * event_category_find_internal(const char *name) { struct event_internal_category *internal; array_foreach_elem(&event_registered_categories_internal, internal) { if (strcmp(internal->name, name) == 0) return internal; } return NULL; } struct event_category *const * event_get_registered_categories(unsigned int *count_r) { return array_get(&event_registered_categories_representative, count_r); } static void event_category_add_to_array(struct event_internal_category *internal) { struct event_category *representative = &internal->representative; array_push_back(&event_registered_categories_internal, &internal); array_push_back(&event_registered_categories_representative, &representative); } static struct event_category * event_category_register(struct event_category *category) { struct event_internal_category *internal = category->internal; event_category_callback_t *callback; bool allocated; if (internal != NULL) return &internal->representative; /* case 2 - see below */ /* register parent categories first */ if (category->parent != NULL) (void) event_category_register(category->parent); /* There are four cases we need to handle: 1) a new category is registered 2) same category struct is re-registered - already handled above internal NULL check 3) different category struct is registered, but it is identical to the previously registered one 4) different category struct is registered, and it is different from the previously registered one - a programming error */ internal = event_category_find_internal(category->name); if (internal == NULL) { /* case 1: first time we saw this name - allocate new */ internal = i_new(struct event_internal_category, 1); if (category->parent != NULL) internal->parent = category->parent->internal; internal->name = i_strdup(category->name); internal->refcount = 1; internal->representative.name = internal->name; internal->representative.parent = category->parent; internal->representative.internal = internal; event_category_add_to_array(internal); allocated = TRUE; } else { /* case 3 or 4: someone registered this name before - share */ if ((category->parent != NULL) && (internal->parent != category->parent->internal)) { /* case 4 */ struct event_internal_category *other = category->parent->internal; i_panic("event category parent mismatch detected: " "category %p internal %p (%s), " "internal parent %p (%s), public parent %p (%s)", category, internal, internal->name, internal->parent, internal->parent->name, other, other->name); } internal->refcount++; allocated = FALSE; } category->internal = internal; if (!allocated) { /* not the first registration of this category */ return &internal->representative; } array_foreach_elem(&event_category_callbacks, callback) T_BEGIN { callback(&internal->representative); } T_END; return &internal->representative; } static bool event_find_category(const struct event *event, const struct event_category *category) { struct event_internal_category *internal = category->internal; struct event_category *cat; /* make sure we're always looking for a representative */ i_assert(category == &internal->representative); array_foreach_elem(&event->categories, cat) { if (cat == category) return TRUE; } return FALSE; } struct event * event_add_categories(struct event *event, struct event_category *const *categories) { struct event_category *representative; if (!array_is_created(&event->categories)) p_array_init(&event->categories, event->pool, 4); for (unsigned int i = 0; categories[i] != NULL; i++) { representative = event_category_register(categories[i]); if (!event_find_category(event, representative)) array_push_back(&event->categories, &representative); } event_set_changed(event); event_recalculate_debug_level(event); return event; } struct event * event_add_category(struct event *event, struct event_category *category) { struct event_category *const categories[] = { category, NULL }; return event_add_categories(event, categories); } struct event_field * event_find_field_nonrecursive(const struct event *event, const char *key) { struct event_field *field; if (!array_is_created(&event->fields)) return NULL; array_foreach_modifiable(&event->fields, field) { if (strcmp(field->key, key) == 0) return field; } return NULL; } const struct event_field * event_find_field_recursive(const struct event *event, const char *key) { const struct event_field *field; do { if ((field = event_find_field_nonrecursive(event, key)) != NULL) return field; event = event->parent; } while (event != NULL); /* check also the global event and its parents */ event = event_get_global(); while (event != NULL) { if ((field = event_find_field_nonrecursive(event, key)) != NULL) return field; event = event->parent; } return NULL; } static void event_get_recursive_strlist(const struct event *event, pool_t pool, const char *key, ARRAY_TYPE(const_string) *dest) { const struct event_field *field; const char *str; if (event == NULL) return; field = event_find_field_nonrecursive(event, key); if (field != NULL) { if (field->value_type != EVENT_FIELD_VALUE_TYPE_STRLIST) { /* Value type unexpectedly changed. Stop recursing. */ return; } array_foreach_elem(&field->value.strlist, str) { if (array_lsearch(dest, &str, i_strcmp_p) == NULL) { if (pool != NULL) str = p_strdup(pool, str); array_push_back(dest, &str); } } } event_get_recursive_strlist(event->parent, pool, key, dest); } const char * event_find_field_recursive_str(const struct event *event, const char *key) { const struct event_field *field; field = event_find_field_recursive(event, key); if (field == NULL) return NULL; switch (field->value_type) { case EVENT_FIELD_VALUE_TYPE_STR: return field->value.str; case EVENT_FIELD_VALUE_TYPE_INTMAX: return dec2str(field->value.intmax); case EVENT_FIELD_VALUE_TYPE_TIMEVAL: return t_strdup_printf("%"PRIdTIME_T".%u", field->value.timeval.tv_sec, (unsigned int)field->value.timeval.tv_usec); case EVENT_FIELD_VALUE_TYPE_STRLIST: { ARRAY_TYPE(const_string) list; t_array_init(&list, 8); /* This is a bit different, because it needs to be merging all of the parent events' and global events' lists together. */ event_get_recursive_strlist(event, NULL, key, &list); event_get_recursive_strlist(event_get_global(), NULL, key, &list); return t_array_const_string_join(&list, ","); } } i_unreached(); } static struct event_field * event_get_field(struct event *event, const char *key, bool clear) { struct event_field *field; field = event_find_field_nonrecursive(event, key); if (field == NULL) { if (!array_is_created(&event->fields)) p_array_init(&event->fields, event->pool, 8); field = array_append_space(&event->fields); field->key = p_strdup(event->pool, key); } else if (clear) { i_zero(&field->value); } event_set_changed(event); return field; } struct event * event_add_str(struct event *event, const char *key, const char *value) { struct event_field *field; if (value == NULL) { /* silently ignoring is perhaps better than assert-crashing? */ return event; } field = event_get_field(event, key, TRUE); field->value_type = EVENT_FIELD_VALUE_TYPE_STR; field->value.str = p_strdup(event->pool, value); return event; } struct event * event_strlist_append(struct event *event, const char *key, const char *value) { struct event_field *field = event_get_field(event, key, FALSE); if (field->value_type != EVENT_FIELD_VALUE_TYPE_STRLIST) i_zero(&field->value); field->value_type = EVENT_FIELD_VALUE_TYPE_STRLIST; if (!array_is_created(&field->value.strlist)) p_array_init(&field->value.strlist, event->pool, 1); /* lets not add empty values there though */ if (value == NULL) return event; const char *str = p_strdup(event->pool, value); if (array_lsearch(&field->value.strlist, &str, i_strcmp_p) == NULL) array_push_back(&field->value.strlist, &str); return event; } struct event * event_strlist_replace(struct event *event, const char *key, const char *const *values, unsigned int count) { struct event_field *field = event_get_field(event, key, TRUE); field->value_type = EVENT_FIELD_VALUE_TYPE_STRLIST; for (unsigned int i = 0; i < count; i++) event_strlist_append(event, key, values[i]); return event; } struct event * event_strlist_copy_recursive(struct event *dest, const struct event *src, const char *key) { event_strlist_append(dest, key, NULL); struct event_field *field = event_get_field(dest, key, FALSE); i_assert(field != NULL); event_get_recursive_strlist(src, dest->pool, key, &field->value.strlist); return dest; } struct event * event_add_int(struct event *event, const char *key, intmax_t num) { struct event_field *field; field = event_get_field(event, key, TRUE); field->value_type = EVENT_FIELD_VALUE_TYPE_INTMAX; field->value.intmax = num; return event; } struct event * event_add_int_nonzero(struct event *event, const char *key, intmax_t num) { if (num != 0) return event_add_int(event, key, num); return event; } struct event * event_inc_int(struct event *event, const char *key, intmax_t num) { struct event_field *field; field = event_find_field_nonrecursive(event, key); if (field == NULL || field->value_type != EVENT_FIELD_VALUE_TYPE_INTMAX) return event_add_int(event, key, num); field->value.intmax += num; event_set_changed(event); return event; } struct event * event_add_timeval(struct event *event, const char *key, const struct timeval *tv) { struct event_field *field; field = event_get_field(event, key, TRUE); field->value_type = EVENT_FIELD_VALUE_TYPE_TIMEVAL; field->value.timeval = *tv; return event; } struct event * event_add_fields(struct event *event, const struct event_add_field *fields) { for (unsigned int i = 0; fields[i].key != NULL; i++) { if (fields[i].value != NULL) event_add_str(event, fields[i].key, fields[i].value); else if (fields[i].value_timeval.tv_sec != 0) { event_add_timeval(event, fields[i].key, &fields[i].value_timeval); } else { event_add_int(event, fields[i].key, fields[i].value_intmax); } } return event; } void event_field_clear(struct event *event, const char *key) { event_add_str(event, key, ""); } struct event *event_get_parent(const struct event *event) { return event->parent; } void event_get_create_time(const struct event *event, struct timeval *tv_r) { *tv_r = event->tv_created; } bool event_get_last_send_time(const struct event *event, struct timeval *tv_r) { *tv_r = event->tv_last_sent; return tv_r->tv_sec != 0; } void event_get_last_duration(const struct event *event, uintmax_t *duration_usecs_r) { if (event->tv_last_sent.tv_sec == 0) { *duration_usecs_r = 0; return; } long long diff = timeval_diff_usecs(&event->tv_last_sent, &event->tv_created); i_assert(diff >= 0); *duration_usecs_r = diff; } const struct event_field * event_get_fields(const struct event *event, unsigned int *count_r) { if (!array_is_created(&event->fields)) { *count_r = 0; return NULL; } return array_get(&event->fields, count_r); } struct event_category *const * event_get_categories(const struct event *event, unsigned int *count_r) { if (!array_is_created(&event->categories)) { *count_r = 0; return NULL; } return array_get(&event->categories, count_r); } void event_send(struct event *event, struct failure_context *ctx, const char *fmt, ...) { va_list args; va_start(args, fmt); event_vsend(event, ctx, fmt, args); va_end(args); } void event_vsend(struct event *event, struct failure_context *ctx, const char *fmt, va_list args) { i_gettimeofday(&event->tv_last_sent); /* Skip adding user_cpu_usecs if not enabled. */ if (event->ru_last.ru_utime.tv_sec != 0 || event->ru_last.ru_utime.tv_usec != 0) { struct rusage ru_current; get_self_rusage(&ru_current); long long udiff = timeval_diff_usecs(&ru_current.ru_utime, &event->ru_last.ru_utime); event_add_int(event, "user_cpu_usecs", udiff > 0 ? udiff : 0); } if (event_call_callbacks(event, EVENT_CALLBACK_TYPE_SEND, ctx, fmt, args)) { if (ctx->type != LOG_TYPE_DEBUG || event->sending_debug_log) i_log_typev(ctx, fmt, args); } event_send_abort(event); } void event_send_abort(struct event *event) { /* if the event is sent again, it needs a new name */ i_free(event->sending_name); if (event->passthrough) event_unref(&event); } static void event_export_field_value(string_t *dest, const struct event_field *field) { switch (field->value_type) { case EVENT_FIELD_VALUE_TYPE_STR: str_append_c(dest, EVENT_CODE_FIELD_STR); str_append_tabescaped(dest, field->key); str_append_c(dest, '\t'); str_append_tabescaped(dest, field->value.str); break; case EVENT_FIELD_VALUE_TYPE_INTMAX: str_append_c(dest, EVENT_CODE_FIELD_INTMAX); str_append_tabescaped(dest, field->key); str_printfa(dest, "\t%jd", field->value.intmax); break; case EVENT_FIELD_VALUE_TYPE_TIMEVAL: str_append_c(dest, EVENT_CODE_FIELD_TIMEVAL); str_append_tabescaped(dest, field->key); str_printfa(dest, "\t%"PRIdTIME_T"\t%u", field->value.timeval.tv_sec, (unsigned int)field->value.timeval.tv_usec); break; case EVENT_FIELD_VALUE_TYPE_STRLIST: { unsigned int count; const char *const *strlist = array_get(&field->value.strlist, &count); str_append_c(dest, EVENT_CODE_FIELD_STRLIST); str_append_tabescaped(dest, field->key); str_printfa(dest, "\t%u", count); for (unsigned int i = 0; i < count; i++) { str_append_c(dest, '\t'); str_append_tabescaped(dest, strlist[i]); } } } } void event_export(const struct event *event, string_t *dest) { /* required fields: */ str_printfa(dest, "%"PRIdTIME_T"\t%u", event->tv_created.tv_sec, (unsigned int)event->tv_created.tv_usec); /* optional fields: */ if (event->source_filename != NULL) { str_append_c(dest, '\t'); str_append_c(dest, EVENT_CODE_SOURCE); str_append_tabescaped(dest, event->source_filename); str_printfa(dest, "\t%u", event->source_linenum); } if (event->always_log_source) { str_append_c(dest, '\t'); str_append_c(dest, EVENT_CODE_ALWAYS_LOG_SOURCE); } if (event->tv_last_sent.tv_sec != 0) { str_printfa(dest, "\t%c%"PRIdTIME_T"\t%u", EVENT_CODE_TV_LAST_SENT, event->tv_last_sent.tv_sec, (unsigned int)event->tv_last_sent.tv_usec); } if (event->sending_name != NULL) { str_append_c(dest, '\t'); str_append_c(dest, EVENT_CODE_SENDING_NAME); str_append_tabescaped(dest, event->sending_name); } if (array_is_created(&event->categories)) { struct event_category *cat; array_foreach_elem(&event->categories, cat) { str_append_c(dest, '\t'); str_append_c(dest, EVENT_CODE_CATEGORY); str_append_tabescaped(dest, cat->name); } } if (array_is_created(&event->fields)) { const struct event_field *field; array_foreach(&event->fields, field) { str_append_c(dest, '\t'); event_export_field_value(dest, field); } } } bool event_import(struct event *event, const char *str, const char **error_r) { return event_import_unescaped(event, t_strsplit_tabescaped(str), error_r); } static bool event_import_tv(const char *arg_secs, const char *arg_usecs, struct timeval *tv_r, const char **error_r) { unsigned int usecs; if (str_to_time(arg_secs, &tv_r->tv_sec) < 0) { *error_r = "Invalid timeval seconds parameter"; return FALSE; } if (arg_usecs == NULL) { *error_r = "Timeval missing microseconds parameter"; return FALSE; } if (str_to_uint(arg_usecs, &usecs) < 0 || usecs >= 1000000) { *error_r = "Invalid timeval microseconds parameter"; return FALSE; } tv_r->tv_usec = usecs; return TRUE; } static bool event_import_strlist(struct event *event, struct event_field *field, const char *const **_args, const char **error_r) { const char *const *args = *_args; unsigned int count, i; field->value_type = EVENT_FIELD_VALUE_TYPE_STRLIST; if (str_to_uint(args[0], &count) < 0) { *error_r = t_strdup_printf("Field '%s' has invalid count: '%s'", field->key, args[0]); return FALSE; } p_array_init(&field->value.strlist, event->pool, count); for (i = 1; i <= count && args[i] != NULL; i++) { const char *str = p_strdup(event->pool, args[i]); array_push_back(&field->value.strlist, &str); } if (i < count) { *error_r = t_strdup_printf("Field '%s' has too few values", field->key); return FALSE; } *_args += count; return TRUE; } static bool event_import_field(struct event *event, enum event_code code, const char *arg, const char *const **_args, const char **error_r) { const char *const *args = *_args; const char *error; if (*arg == '\0') { *error_r = "Field name is missing"; return FALSE; } struct event_field *field = event_get_field(event, arg, TRUE); if (args[0] == NULL) { *error_r = "Field value is missing"; return FALSE; } switch (code) { case EVENT_CODE_FIELD_INTMAX: field->value_type = EVENT_FIELD_VALUE_TYPE_INTMAX; if (str_to_intmax(*args, &field->value.intmax) < 0) { *error_r = t_strdup_printf( "Invalid field value '%s' number for '%s'", *args, field->key); return FALSE; } break; case EVENT_CODE_FIELD_STR: if (field->value_type == EVENT_FIELD_VALUE_TYPE_STR && null_strcmp(field->value.str, *args) == 0) { /* already identical value */ break; } field->value_type = EVENT_FIELD_VALUE_TYPE_STR; field->value.str = p_strdup(event->pool, *args); break; case EVENT_CODE_FIELD_TIMEVAL: field->value_type = EVENT_FIELD_VALUE_TYPE_TIMEVAL; if (!event_import_tv(args[0], args[1], &field->value.timeval, &error)) { *error_r = t_strdup_printf("Field '%s' value '%s': %s", field->key, args[1], error); return FALSE; } args++; break; case EVENT_CODE_FIELD_STRLIST: if (!event_import_strlist(event, field, &args, error_r)) return FALSE; break; default: i_unreached(); } *_args = args; return TRUE; } static bool event_import_arg(struct event *event, const char *const **_args, const char **error_r) { const char *const *args = *_args; const char *error, *arg = *args; enum event_code code = arg[0]; arg++; switch (code) { case EVENT_CODE_ALWAYS_LOG_SOURCE: event->always_log_source = TRUE; break; case EVENT_CODE_CATEGORY: { struct event_category *category = event_category_find_registered(arg); if (category == NULL) { *error_r = t_strdup_printf( "Unregistered category: '%s'", arg); return FALSE; } if (!array_is_created(&event->categories)) p_array_init(&event->categories, event->pool, 4); if (!event_find_category(event, category)) array_push_back(&event->categories, &category); break; } case EVENT_CODE_TV_LAST_SENT: if (!event_import_tv(arg, args[1], &event->tv_last_sent, &error)) { *error_r = t_strdup_printf( "Invalid tv_last_sent: %s", error); return FALSE; } args++; break; case EVENT_CODE_SENDING_NAME: i_free(event->sending_name); event->sending_name = i_strdup(arg); break; case EVENT_CODE_SOURCE: { unsigned int linenum; if (args[1] == NULL) { *error_r = "Source line number missing"; return FALSE; } if (str_to_uint(args[1], &linenum) < 0) { *error_r = "Invalid Source line number"; return FALSE; } event_set_source(event, arg, linenum, FALSE); args++; break; } case EVENT_CODE_FIELD_INTMAX: case EVENT_CODE_FIELD_STR: case EVENT_CODE_FIELD_STRLIST: case EVENT_CODE_FIELD_TIMEVAL: { args++; if (!event_import_field(event, code, arg, &args, error_r)) return FALSE; break; } } *_args = args; return TRUE; } bool event_import_unescaped(struct event *event, const char *const *args, const char **error_r) { const char *error; /* Event's create callback has already added service: category. This imported event may be coming from another service process though, so clear it out. */ if (array_is_created(&event->categories)) array_clear(&event->categories); /* required fields: */ if (args[0] == NULL) { *error_r = "Missing required fields"; return FALSE; } if (!event_import_tv(args[0], args[1], &event->tv_created, &error)) { *error_r = t_strdup_printf("Invalid tv_created: %s", error); return FALSE; } args += 2; /* optional fields: */ while (*args != NULL) { if (!event_import_arg(event, &args, error_r)) return FALSE; args++; } return TRUE; } void event_register_callback(event_callback_t *callback) { array_push_back(&event_handlers, &callback); } void event_unregister_callback(event_callback_t *callback) { event_callback_t *const *callbackp; array_foreach(&event_handlers, callbackp) { if (*callbackp == callback) { unsigned int idx = array_foreach_idx(&event_handlers, callbackp); array_delete(&event_handlers, idx, 1); return; } } i_unreached(); } void event_category_register_callback(event_category_callback_t *callback) { array_push_back(&event_category_callbacks, &callback); } void event_category_unregister_callback(event_category_callback_t *callback) { event_category_callback_t *const *callbackp; array_foreach(&event_category_callbacks, callbackp) { if (*callbackp == callback) { unsigned int idx = array_foreach_idx(&event_category_callbacks, callbackp); array_delete(&event_category_callbacks, idx, 1); return; } } i_unreached(); } static struct event_passthrough * event_passthrough_set_append_log_prefix(const char *prefix) { event_set_append_log_prefix(last_passthrough_event(), prefix); return &event_passthrough_vfuncs; } static struct event_passthrough * event_passthrough_replace_log_prefix(const char *prefix) { event_replace_log_prefix(last_passthrough_event(), prefix); return &event_passthrough_vfuncs; } static struct event_passthrough * event_passthrough_set_name(const char *name) { event_set_name(last_passthrough_event(), name); return &event_passthrough_vfuncs; } static struct event_passthrough * event_passthrough_set_source(const char *filename, unsigned int linenum, bool literal_fname) { event_set_source(last_passthrough_event(), filename, linenum, literal_fname); return &event_passthrough_vfuncs; } static struct event_passthrough * event_passthrough_set_always_log_source(void) { event_set_always_log_source(last_passthrough_event()); return &event_passthrough_vfuncs; } static struct event_passthrough * event_passthrough_add_categories(struct event_category *const *categories) { event_add_categories(last_passthrough_event(), categories); return &event_passthrough_vfuncs; } static struct event_passthrough * event_passthrough_add_category(struct event_category *category) { event_add_category(last_passthrough_event(), category); return &event_passthrough_vfuncs; } static struct event_passthrough * event_passthrough_add_fields(const struct event_add_field *fields) { event_add_fields(last_passthrough_event(), fields); return &event_passthrough_vfuncs; } static struct event_passthrough * event_passthrough_add_str(const char *key, const char *value) { event_add_str(last_passthrough_event(), key, value); return &event_passthrough_vfuncs; } static struct event_passthrough * event_passthrough_strlist_append(const char *key, const char *value) { event_strlist_append(last_passthrough_event(), key, value); return &event_passthrough_vfuncs; } static struct event_passthrough * event_passthrough_strlist_replace(const char *key, const char *const *values, unsigned int count) { event_strlist_replace(last_passthrough_event(), key, values, count); return &event_passthrough_vfuncs; } static struct event_passthrough * event_passthrough_add_int(const char *key, intmax_t num) { event_add_int(last_passthrough_event(), key, num); return &event_passthrough_vfuncs; } static struct event_passthrough * event_passthrough_add_int_nonzero(const char *key, intmax_t num) { event_add_int_nonzero(last_passthrough_event(), key, num); return &event_passthrough_vfuncs; } static struct event_passthrough * event_passthrough_add_timeval(const char *key, const struct timeval *tv) { event_add_timeval(last_passthrough_event(), key, tv); return &event_passthrough_vfuncs; } static struct event_passthrough * event_passthrough_inc_int(const char *key, intmax_t num) { event_inc_int(last_passthrough_event(), key, num); return &event_passthrough_vfuncs; } static struct event_passthrough * event_passthrough_clear_field(const char *key) { event_field_clear(last_passthrough_event(), key); return &event_passthrough_vfuncs; } static struct event *event_passthrough_event(void) { struct event *event = last_passthrough_event(); event_last_passthrough = NULL; return event; } struct event_passthrough event_passthrough_vfuncs = { .append_log_prefix = event_passthrough_set_append_log_prefix, .replace_log_prefix = event_passthrough_replace_log_prefix, .set_name = event_passthrough_set_name, .set_source = event_passthrough_set_source, .set_always_log_source = event_passthrough_set_always_log_source, .add_categories = event_passthrough_add_categories, .add_category = event_passthrough_add_category, .add_fields = event_passthrough_add_fields, .add_str = event_passthrough_add_str, .add_int = event_passthrough_add_int, .add_int_nonzero = event_passthrough_add_int_nonzero, .add_timeval = event_passthrough_add_timeval, .inc_int = event_passthrough_inc_int, .strlist_append = event_passthrough_strlist_append, .strlist_replace = event_passthrough_strlist_replace, .clear_field = event_passthrough_clear_field, .event = event_passthrough_event, }; void event_enable_user_cpu_usecs(struct event *event) { get_self_rusage(&event->ru_last); } void lib_event_init(void) { i_array_init(&event_handlers, 4); i_array_init(&event_category_callbacks, 4); i_array_init(&event_registered_categories_internal, 16); i_array_init(&event_registered_categories_representative, 16); } void lib_event_deinit(void) { struct event_internal_category *internal; event_unset_global_debug_log_filter(); event_unset_global_debug_send_filter(); event_unset_global_core_log_filter(); for (struct event *event = events; event != NULL; event = event->next) { i_warning("Event %p leaked (parent=%p): %s:%u", event, event->parent, event->source_filename, event->source_linenum); } /* categories cannot be unregistered, so just free them here */ array_foreach_elem(&event_registered_categories_internal, internal) { i_free(internal->name); i_free(internal); } array_free(&event_handlers); array_free(&event_category_callbacks); array_free(&event_registered_categories_internal); array_free(&event_registered_categories_representative); array_free(&global_event_stack); } dovecot-2.3.21.1/src/lib/malloc-overflow.h0000644000000000000000000000373114656633576015137 00000000000000#ifndef MALLOC_OVERFLOW_H #define MALLOC_OVERFLOW_H /* MALLOC_*() can be used to calculate memory allocation sizes. If there's an overflow, it'll cleanly panic instead of causing a potential buffer overflow. Note that *_malloc(size+1) doesn't need to use MALLOC_ADD(size, 1). It wraps to size==0 and the *_malloc() calls already panic if size==0. */ static inline size_t malloc_multiply_check(size_t a, size_t b, size_t sizeof_a, size_t sizeof_b, const char *fname, unsigned int linenum) { /* the first sizeof-checks are intended to optimize away this entire if-check for types that are small enough to never wrap size_t. */ if ((sizeof_a * 2 > sizeof(size_t) || sizeof_b * 2 > sizeof(size_t)) && b != 0 && (a > SIZE_MAX / b)) { i_panic("file %s: line %d: memory allocation overflow: %zu * %zu", fname, linenum, a, b); } return a * b; } #ifndef STATIC_CHECKER # define MALLOC_MULTIPLY(a, b) \ malloc_multiply_check(a, b, sizeof(a), sizeof(b), __FILE__, __LINE__) #else /* avoid warning every time about sizeof(b) when b contains any arithmetic */ # define MALLOC_MULTIPLY(a, b) \ malloc_multiply_check(a, b, sizeof(a), sizeof(size_t), __FILE__, __LINE__) #endif static inline size_t malloc_add_check(size_t a, size_t b, size_t sizeof_a, size_t sizeof_b, const char *fname, unsigned int linenum) { /* the first sizeof-checks are intended to optimize away this entire if-check for types that are small enough to never wrap size_t. */ if ((sizeof_a >= sizeof(size_t) || sizeof_b >= sizeof(size_t)) && SIZE_MAX - a < b) { i_panic("file %s: line %d: memory allocation overflow: %zu + %zu", fname, linenum, a, b); } return a + b; } #ifndef STATIC_CHECKER # define MALLOC_ADD(a, b) \ malloc_add_check(a, b, sizeof(a), sizeof(b), __FILE__, __LINE__) #else /* avoid warning every time about sizeof(b) when b contains any arithmetic */ # define MALLOC_ADD(a, b) \ malloc_add_check(a, b, sizeof(a), sizeof(size_t), __FILE__, __LINE__) #endif #endif dovecot-2.3.21.1/src/lib/randgen.c0000644000000000000000000001367414656633576013447 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "randgen.h" #include #include #ifdef DEBUG /* For reproducing tests, fall back onto using a simple deterministic PRNG */ /* Marsaglia's 1999 KISS, de-macro-ified, and with the fixed KISS11 SHR3, which is clearly what was intended given the "cycle length 2^123" claim. */ static bool kiss_in_use; static unsigned int kiss_seed; static uint32_t kiss_z, kiss_w, kiss_jsr, kiss_jcong; static void kiss_init(unsigned int seed) { i_info("Random numbers are PRNG using kiss, as per DOVECOT_SRAND=%u", seed); kiss_seed = seed; kiss_jsr = 0x5eed5eed; /* simply musn't be 0 */ kiss_z = 1 ^ (kiss_w = kiss_jcong = seed); /* w=z=0 is bad, see Rose */ kiss_in_use = TRUE; } static unsigned int kiss_rand(void) { kiss_z = 36969 * (kiss_z&65535) + (kiss_z>>16); kiss_w = 18000 * (kiss_w&65535) + (kiss_w>>16); kiss_jcong = 69069 * kiss_jcong + 1234567; kiss_jsr^=(kiss_jsr<<13); /* <<17, >>13 gives cycle length 2^28.2 max */ kiss_jsr^=(kiss_jsr>>17); /* <<13, >>17 gives maximal cycle length */ kiss_jsr^=(kiss_jsr<<5); return (((kiss_z<<16) + kiss_w) ^ kiss_jcong) + kiss_jsr; } int rand_get_last_seed(unsigned int *seed_r) { if (!kiss_in_use) return -1; /* not using a deterministic PRNG, seed is irrelevant */ *seed_r = kiss_seed; return 0; } #endif /* get randomness from either getrandom, arc4random or /dev/urandom */ #if defined(HAVE_GETRANDOM) && HAVE_DECL_GETRANDOM != 0 # include # define USE_GETRANDOM static bool getrandom_present = TRUE; #elif defined(HAVE_ARC4RANDOM) # if defined(HAVE_LIBBSD) # include # endif # define USE_ARC4RANDOM #else static bool getrandom_present = FALSE; # define USE_RANDOM_DEV #endif static int init_refcount = 0; static int urandom_fd = -1; #if defined(USE_GETRANDOM) || defined(USE_RANDOM_DEV) /* Use a small buffer when reading randomness. This is mainly to make small random reads more efficient, such as i_rand*(). When reading larger amount of randomness this buffer is bypassed. There doesn't seem to be a big difference in Linux system CPU usage when buffer size is above 16 bytes. Double it just to be safe. Avoid it being too large anyway so we don't unnecessarily waste CPU and memory. */ #define RANDOM_READ_BUFFER_SIZE 32 static unsigned char random_next[RANDOM_READ_BUFFER_SIZE]; static size_t random_next_pos = 0; static size_t random_next_size = 0; static void random_open_urandom(void) { urandom_fd = open(DEV_URANDOM_PATH, O_RDONLY); if (urandom_fd == -1) { if (errno == ENOENT) { i_fatal("open("DEV_URANDOM_PATH") failed: doesn't exist," "currently we require it"); } else { i_fatal("open("DEV_URANDOM_PATH") failed: %m"); } } fd_close_on_exec(urandom_fd, TRUE); } static inline int random_read(unsigned char *buf, size_t size) { ssize_t ret = 0; # if defined(USE_GETRANDOM) if (getrandom_present) { ret = getrandom(buf, size, 0); if (ret < 0 && errno == ENOSYS) { getrandom_present = FALSE; /* It gets complicated here... While the libc (and its headers) indicated that getrandom() was available when we were compiled, the kernel disagreed just now at runtime. Fall back to reading /dev/urandom. */ random_open_urandom(); } } /* this is here to avoid clang complain, because getrandom_present will be always FALSE if USE_GETRANDOM is not defined */ if (!getrandom_present) # endif ret = read(urandom_fd, buf, size); if (unlikely(ret <= 0)) { if (ret == 0) { i_fatal("read("DEV_URANDOM_PATH") failed: EOF"); } else if (errno != EINTR) { if (getrandom_present) { i_fatal("getrandom() failed: %m"); } else { i_fatal("read("DEV_URANDOM_PATH") failed: %m"); } } } i_assert(ret > 0 || errno == EINTR); return ret; } #endif void random_fill(void *buf, size_t size) { i_assert(init_refcount > 0); i_assert(size < SSIZE_T_MAX); #ifdef DEBUG if (kiss_in_use) { for (size_t pos = 0; pos < size; pos++) ((unsigned char*)buf)[pos] = kiss_rand(); return; } #endif #if defined(USE_ARC4RANDOM) arc4random_buf(buf, size); #else size_t pos; ssize_t ret; for (pos = 0; pos < size; ) { if (size >= sizeof(random_next) && random_next_size == 0) { /* Asking for lots of randomness. Read directly to the destination buffer. */ ret = random_read(PTR_OFFSET(buf, pos), size - pos); if (ret > -1) pos += ret; } else { /* Asking for a little randomness. Read via a larger buffer to reduce the number of syscalls. */ if (random_next_size > random_next_pos) ret = random_next_size - random_next_pos; else { random_next_pos = 0; ret = random_read(random_next, sizeof(random_next)); random_next_size = ret < 0 ? 0 : ret; } if (ret > 0) { size_t used = I_MIN(size - pos, (size_t)ret); memcpy(PTR_OFFSET(buf, pos), random_next + random_next_pos, used); random_next_pos += used; pos += used; } } } #endif /* defined(USE_ARC4RANDOM) */ } void random_init(void) { /* static analyzer seems to require this */ unsigned int seed = 0; const char *env_seed; if (init_refcount++ > 0) return; env_seed = getenv("DOVECOT_SRAND"); #ifdef DEBUG if (env_seed != NULL && str_to_uint(env_seed, &seed) >= 0) { kiss_init(seed); /* getrandom_present = FALSE; not needed, only used in random_read() */ goto normal_exit; } #else if (env_seed != NULL && *env_seed != '\0') i_warning("DOVECOT_SRAND is not available in non-debug builds"); #endif /* DEBUG */ #if defined(USE_RANDOM_DEV) random_open_urandom(); #endif /* DO NOT REMOVE THIS - It is also needed to make sure getrandom really works. */ random_fill(&seed, sizeof(seed)); #ifdef DEBUG if (env_seed != NULL) { if (strcmp(env_seed, "kiss") != 0) i_fatal("DOVECOT_SRAND not a number or 'kiss'"); kiss_init(seed); i_close_fd(&urandom_fd); } normal_exit: #endif srand(seed); } void random_deinit(void) { if (--init_refcount > 0) return; i_close_fd(&urandom_fd); } dovecot-2.3.21.1/src/lib/lib-event.h0000644000000000000000000004731414656633576013721 00000000000000#ifndef LIB_EVENT_H #define LIB_EVENT_H /* event.h name is probably a bit too generic, so lets avoid using it. */ #include /* Field name for the reason_code string list. */ #define EVENT_REASON_CODE "reason_code" struct event; struct event_log_params; /* Hierarchical category of events. Each event can belong to multiple categories. For example [ lib-storage/maildir, syscall/io ]. The categories are expected to live as long as they're used in events. */ struct event_category { struct event_category *parent; const char *name; /* non-NULL if this category has been registered Do NOT dereference outside of event code in src/lib. At any point in time it is safe to (1) check the pointer for NULL/non-NULL to determine if this particular category instance has been registered, and (2) compare two categories' internal pointers to determine if they represent the same category. */ void *internal; }; enum event_field_value_type { EVENT_FIELD_VALUE_TYPE_STR, EVENT_FIELD_VALUE_TYPE_INTMAX, EVENT_FIELD_VALUE_TYPE_TIMEVAL, EVENT_FIELD_VALUE_TYPE_STRLIST, }; struct event_field { const char *key; enum event_field_value_type value_type; struct { const char *str; intmax_t intmax; struct timeval timeval; ARRAY_TYPE(const_string) strlist; } value; }; struct event_add_field { const char *key; /* The first non-0/NULL value is used. */ const char *value; intmax_t value_intmax; struct timeval value_timeval; }; struct event_passthrough { /* wrappers to event_set_*() and event_add_*() for passthrough events, so these can be chained like: event_create_passthrough(parent)->name("name")->...->event() */ struct event_passthrough * (*append_log_prefix)(const char *prefix); struct event_passthrough * (*replace_log_prefix)(const char *prefix); struct event_passthrough * (*set_name)(const char *name); struct event_passthrough * (*set_source)(const char *filename, unsigned int linenum, bool literal_fname); struct event_passthrough * (*set_always_log_source)(void); struct event_passthrough * (*add_categories)(struct event_category *const *categories); struct event_passthrough * (*add_category)(struct event_category *category); struct event_passthrough * (*add_fields)(const struct event_add_field *fields); struct event_passthrough * (*add_str)(const char *key, const char *value); struct event_passthrough * (*add_int)(const char *key, intmax_t num); struct event_passthrough * (*add_int_nonzero)(const char *key, intmax_t num); struct event_passthrough * (*add_timeval)(const char *key, const struct timeval *tv); struct event_passthrough * (*inc_int)(const char *key, intmax_t num); struct event_passthrough * (*strlist_append)(const char *key, const char *value); struct event_passthrough * (*strlist_replace)(const char *key, const char *const *value, unsigned int count); struct event_passthrough * (*clear_field)(const char *key); struct event *(*event)(void); }; typedef const char * event_log_prefix_callback_t(void *context); typedef const char * event_log_message_callback_t(void *context, enum log_type log_type, const char *message); /* Returns TRUE if the event has all the categories that the "other" event has (and maybe more). */ bool event_has_all_categories(struct event *event, const struct event *other); /* Returns TRUE if the event has all the fields that the "other" event has (and maybe more). Only the fields in the events themselves are checked. Parent events' fields are not checked. */ bool event_has_all_fields(struct event *event, const struct event *other); /* Returns the source event duplicated into a new event. Event pointers are dropped. */ struct event *event_dup(const struct event *source); /* Returns a flattened version of the source event. Both categories and fields will be flattened. A new reference to the source event is returned if no flattening was needed. Event pointers are dropped if a new event was created. */ struct event *event_flatten(struct event *src); /* Returns a minimized version of the source event. Remove parents with no fields or categories, attempt to flatten fields and categories to avoid sending one-off parent events. (There is a more detailed description in a comment above the function implementation.) A new reference to the source event is returned if no simplification occured. Event pointers are dropped if a new event was created. */ struct event *event_minimize(struct event *src); /* Copy all categories from source to dest. Only the categories in source event itself are copied. Parent events' categories aren't copied. */ void event_copy_categories(struct event *to, struct event *from); /* Copy all fields from source to dest. Only the fields in source event itself are copied. Parent events' fields aren't copied. */ void event_copy_fields(struct event *to, struct event *from); /* Create a new empty event under the parent event, or NULL for root event. */ struct event *event_create(struct event *parent, const char *source_filename, unsigned int source_linenum); #define event_create(parent) \ event_create((parent), __FILE__, __LINE__) /* This is a temporary "passthrough" event. Its main purpose is to make it easier to create temporary events as part of the event parameter in e_error(), e_warning(), e_info() or e_debug(). These passthrough events are automatically freed when the e_*() call is finished. Because this makes the freeing less obvious, it should be avoided outside e_*()'s event parameter. The passthrough events also change the API to be more convenient towards being used in a parameter. Instead of having to use e.g. event_add_str(event_set_name(event_create(parent), "name"), "key", "value") the event_passthrough API can be a bit more readable as: event_create_passthrough(parent)->set_name("name")-> add_str("key", "value")->event(). The passthrough event is converted to a normal event at the end with the event() call. Note that this API works by modifying the last created passthrough event, so it's not possible to have multiple passthrough events created in parallel. */ struct event_passthrough * event_create_passthrough(struct event *parent, const char *source_filename, unsigned int source_linenum); #define event_create_passthrough(parent) \ event_create_passthrough((parent), __FILE__, __LINE__) /* Reference the event. Returns the event parameter. */ struct event *event_ref(struct event *event); /* Unreference the event. If the reference count drops to 0, the event is freed. The current global event's refcount must not drop to 0. */ void event_unref(struct event **event); /* Set the event to be the global event and push it at the top of the global event stack. Returns the event parameter. The event must be explicitly popped before it's freed. The global event acts as the root event for all the events while they are being emitted. The global events don't permanently affect the event hierarchy. The global events are typically used to add extra fields to all emitted events while some specific work is running. For example the global event can be "IMAP command SELECT", which can be used for filtering events that happen while the SELECT command is being executed. However, for the created struct mailbox the parent event should be the mail_user, not the SELECT command. (If the mailbox used SELECT command as the parent event, then any future event emitted via the mailbox event would show SELECT command as the parent, even after SELECT had already finished.) The global event works the same as if all the events' roots were instead pointing to the global event. Global events don't affect log prefixes. If ioloop contexts are used, the global events will automatically follow the contexts. Any global events pushed while running in a context are popped out when the context is deactivated, and pushed back when context is activated again. The created global events should use event_get_global() as their parent event. Only the last pushed global event is used. */ struct event *event_push_global(struct event *event); /* Pop the current global event and set the global event to the next one at the top of the stack. Assert-crash if the current global event isn't the given event parameter. Returns the next (now activated) global event in the stack, or NULL if the stack is now empty. */ struct event *event_pop_global(struct event *event); /* Returns the current global event. */ struct event *event_get_global(void); /* Shortcut to create and push a global event and set its reason_code field. */ struct event_reason * event_reason_begin(const char *reason_code, const char *source_filename, unsigned int source_linenum); #define event_reason_begin(reason_code) \ event_reason_begin(reason_code, __FILE__, __LINE__) /* Finish the reason event. It pops the global event, which means it must be at the top of the stack. */ void event_reason_end(struct event_reason **reason); /* Generate a reason code as :. This function does some sanity checks and conversions to make sure the reason codes are reasonable: - Assert-crash if module has space, '-', ':' or uppercase characters. - Assert-crash if module is empty - Convert name to lowercase. - Replace all space and '-' in name with '_'. - Assert-crash if name has ':' - assert-crash if name is empty */ const char *event_reason_code(const char *module, const char *name); /* Same as event_reason_code(), but concatenate name_prefix and name. The name_prefix must not contain spaces, '-', ':' or uppercase characters. */ const char *event_reason_code_prefix(const char *module, const char *name_prefix, const char *name); /* Set the appended log prefix string for this event. All the parent events' log prefixes will be concatenated together when logging. The log type text (e.g. "Info: ") will be inserted before appended log prefixes (but after replaced log prefix). Clears log_prefix callback. */ struct event * event_set_append_log_prefix(struct event *event, const char *prefix); /* Replace the full log prefix string for this event. The parent events' log prefixes won't be used. Also, any parent event's message amendment callback is not used. Clears log_prefix callback. */ struct event *event_replace_log_prefix(struct event *event, const char *prefix); /* Drop count prefixes from parents when this event is used for logging. This does not affect the parent events. This only counts actual prefixes and not parents. If the count is higher than the actual number of prefixes added by parents, all will be dropped. */ struct event * event_drop_parent_log_prefixes(struct event *event, unsigned int count); /* Sets event prefix callback, sets log_prefix empty */ struct event * event_set_log_prefix_callback(struct event *event, bool replace, event_log_prefix_callback_t *callback, void *context); #define event_set_log_prefix_callback(event, replace, callback, context) \ event_set_log_prefix_callback(event, replace, \ (event_log_prefix_callback_t*)callback, TRUE ? context : \ CALLBACK_TYPECHECK(callback, const char *(*)(typeof(context)))) /* Sets event message amendment callback */ struct event * event_set_log_message_callback(struct event *event, event_log_message_callback_t *callback, void *context); #define event_set_log_message_callback(event, callback, context) \ event_set_log_message_callback(event, \ (event_log_message_callback_t*)callback, TRUE ? context : \ CALLBACK_TYPECHECK(callback, \ const char *(*)(typeof(context), enum log_type, \ const char *))) /* Unsets the event message amendment callback. */ void event_unset_log_message_callback(struct event *event, event_log_message_callback_t *callback, void *context); #define event_unset_log_message_callback(event, callback, context) \ event_unset_log_message_callback(event, \ (event_log_message_callback_t*)callback, context) /* Set the event's name. The name is specific to a single sending of an event, and it'll be automatically cleared once the event is sent. This should typically be used only in a parameter to e_debug(), etc. */ struct event * event_set_name(struct event *event, const char *name); /* Set the source filename:linenum to the event. If literal_fname==TRUE, it's assumed that __FILE__ has been used and the pointer is stored directly, otherwise the filename is strdup()ed. */ struct event * event_set_source(struct event *event, const char *filename, unsigned int linenum, bool literal_fname); /* Always include the source path:line in the log replies. This is especially useful when logging about unexpected syscall failures, because it allow quickly finding which of the otherwise identical syscalls in the code generated the error. */ struct event *event_set_always_log_source(struct event *event); /* Set minimum normal log level for the event. By default events with INFO level and higher are logged. This can be used to easily hide even the INFO log lines unless some verbose-setting is enabled. Note that this functionality is mostly independent of debug logging. Don't use this to enable debug log - use event_set_forced_debug() instead. */ struct event *event_set_min_log_level(struct event *event, enum log_type level); enum log_type event_get_min_log_level(const struct event *event); /* Add an internal pointer to an event. It can be looked up only with event_get_ptr(). The keys are in their own namespace and won't conflict with event fields. The pointers are specific to this specific event only - they will be dropped from any duplicated/flattened/minimized events. */ struct event *event_set_ptr(struct event *event, const char *key, void *value); /* Return a pointer set with event_set_ptr(), or NULL if it doesn't exist. The pointer is looked up only from the event itself, not its parents. */ void *event_get_ptr(const struct event *event, const char *key); /* Add NULL-terminated list of categories to the event. The categories pointer doesn't need to stay valid afterwards, but the event_category structs themselves must be. Returns the event parameter. */ struct event * event_add_categories(struct event *event, struct event_category *const *categories); /* Add a single category to the event. */ struct event * event_add_category(struct event *event, struct event_category *category); /* Add key=value field to the event. If a key already exists, it's replaced. Child events automatically inherit key=values from their parents at the time the event is sent. So changing a key in parent will change the values in the child events as well, unless the key has been overwritten in the child event. Setting the value to "" is the same as event_field_clear(). Returns the event parameter. */ struct event * event_add_str(struct event *event, const char *key, const char *value); struct event * event_add_int(struct event *event, const char *key, intmax_t num); /* Adds int value to event if it is non-zero */ struct event * event_add_int_nonzero(struct event *event, const char *key, intmax_t num); /* Increase the key's value. If it's not set or isn't an integer type, initialize the value to num. */ struct event * event_inc_int(struct event *event, const char *key, intmax_t num); struct event * event_add_timeval(struct event *event, const char *key, const struct timeval *tv); /* Append new value to list. If the key is not a list, it will be cleared first. NULL values are ignored. Duplicate values are ignored. */ struct event * event_strlist_append(struct event *event, const char *key, const char *value); /* Replace value with this strlist. */ struct event * event_strlist_replace(struct event *event, const char *key, const char *const *value, unsigned int count); /* Copy the string list from src and its parents to dest. This can be especially useful to copy the current global events' reason_codes to a more permanent (e.g. async) event that can exist after the global events are popped out. */ struct event * event_strlist_copy_recursive(struct event *dest, const struct event *src, const char *key); /* Same as event_add_str/int(), but do it via event_field struct. The fields terminates with key=NULL. Returns the event parameter. */ struct event * event_add_fields(struct event *event, const struct event_add_field *fields); /* Mark a field as nonexistent. If a parent event has the field set, this allows removing it from the child event. Using an event filter with e.g. "key=*" won't match this field anymore, although it's still visible in event_find_field*() and event_get_fields(). This is the same as using event_add_str() with value="". */ void event_field_clear(struct event *event, const char *key); /* Returns the parent event, or NULL if it doesn't exist. */ struct event *event_get_parent(const struct event *event); /* Get the event's creation time. */ void event_get_create_time(const struct event *event, struct timeval *tv_r); /* Get the time when the event was last sent. Returns TRUE if time was returned, FALSE if event has never been sent. */ bool event_get_last_send_time(const struct event *event, struct timeval *tv_r); /* Get the event duration field in microseconds. This is calculated from the event's last sent time. */ void event_get_last_duration(const struct event *event, uintmax_t *duration_usecs_r); /* Returns field for a given key, or NULL if it doesn't exist. */ struct event_field * event_find_field_nonrecursive(const struct event *event, const char *key); /* Returns field for a given key, or NULL if it doesn't exist. If the key isn't found from the event itself, find it from parent events, including from the global event. */ const struct event_field * event_find_field_recursive(const struct event *event, const char *key); /* Same as event_find_field(), but return the value converted to a string. If the field isn't stored as a string, the result is allocated from data stack. */ const char * event_find_field_recursive_str(const struct event *event, const char *key); /* Returns all key=value fields that the event has. Parent events' fields aren't returned. */ const struct event_field * event_get_fields(const struct event *event, unsigned int *count_r); /* Return all categories that the event has. Parent events' categories aren't returned. */ struct event_category *const * event_get_categories(const struct event *event, unsigned int *count_r); /* Export the event into a tabescaped string, so its fields are separated with TABs and there are no NUL, CR or LF characters. */ void event_export(const struct event *event, string_t *dest); /* Import event. The string is expected to be generated by event_export(). All the used categories must already be registered. Returns TRUE on success, FALSE on invalid string. */ bool event_import(struct event *event, const char *str, const char **error_r); /* Same as event_import(), but string is already split into an array of strings via *_strsplit_tabescaped(). */ bool event_import_unescaped(struct event *event, const char *const *args, const char **error_r); /* The event wasn't sent after all - free everything related to it. Most importantly this frees any passthrough events. Typically this shouldn't need to be called. */ void event_send_abort(struct event *event); /* Enable "user_cpu_usecs" event field to event by getting current resource usage which will be used in consequent event_send() to calculate cpu time. This function can be called multiple times to update the current resource usage. The "user_cpu_usecs" field is automatically inherited by passthrough events, but not full events. */ void event_enable_user_cpu_usecs(struct event *event); void lib_event_init(void); void lib_event_deinit(void); #endif dovecot-2.3.21.1/src/lib/test-unichar.c0000644000000000000000000001373314656633576014433 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str.h" #include "buffer.h" #include "unichar.h" static void test_unichar_uni_utf8_strlen(void) { static const char input[] = "\xC3\xA4\xC3\xA4\0a"; test_begin("uni_utf8_strlen()"); test_assert(uni_utf8_strlen(input) == 2); test_end(); test_begin("uni_utf8_strlen_n()"); test_assert(uni_utf8_strlen_n(input, 1) == 0); test_assert(uni_utf8_strlen_n(input, 2) == 1); test_assert(uni_utf8_strlen_n(input, 3) == 1); test_assert(uni_utf8_strlen_n(input, 4) == 2); test_end(); } static void test_unichar_uni_utf8_partial_strlen_n(void) { static const char input[] = "\xC3\xA4\xC3\xA4\0a"; size_t pos; test_begin("uni_utf8_partial_strlen_n()"); test_assert(uni_utf8_partial_strlen_n(input, 1, &pos) == 0 && pos == 0); test_assert(uni_utf8_partial_strlen_n(input, 2, &pos) == 1 && pos == 2); test_assert(uni_utf8_partial_strlen_n(input, 3, &pos) == 1 && pos == 2); test_assert(uni_utf8_partial_strlen_n(input, 4, &pos) == 2 && pos == 4); test_assert(uni_utf8_partial_strlen_n(input, 5, &pos) == 3 && pos == 5); test_assert(uni_utf8_partial_strlen_n(input, 6, &pos) == 4 && pos == 6); test_end(); } static void test_unichar_valid_unicode(void) { struct { const char *input; bool valid; unichar_t expected; } test_cases[] = { { "a", TRUE, 'a' }, { "\xc3\xb1", TRUE, 0x00F1 }, /* U+00F1 */ { "\xc3\x28", FALSE, 0x0 }, /* has invalid 2nd octet */ { "\xa0\xa1", FALSE, 0x0 }, /* invalid sequence identifier */ { "\xed\xb2\x80", FALSE, 0x0 }, /* UTF-8B */ { "\xed\xa0\x80", FALSE, 0x0 }, /* surrogate halves, U+D800 .. */ { "\xed\xa0\x80", FALSE, 0x0 }, { "\xed\xa1\x80", FALSE, 0x0 }, { "\xed\xa2\x80", FALSE, 0x0 }, { "\xed\xa3\x80", FALSE, 0x0 }, { "\xed\xa4\x80", FALSE, 0x0 }, { "\xed\xa5\x80", FALSE, 0x0 }, { "\xed\xa6\x80", FALSE, 0x0 }, { "\xed\xa7\x80", FALSE, 0x0 }, { "\xed\xa8\x80", FALSE, 0x0 }, { "\xed\xa9\x80", FALSE, 0x0 }, { "\xed\xaa\x80", FALSE, 0x0 }, { "\xed\xab\x80", FALSE, 0x0 }, { "\xed\xac\x80", FALSE, 0x0 }, { "\xed\xad\x80", FALSE, 0x0 }, { "\xed\xaf\x80", FALSE, 0x0 }, { "\xed\xb0\x80", FALSE, 0x0 }, { "\xed\xb1\x80", FALSE, 0x0 }, { "\xed\xb2\x80", FALSE, 0x0 }, { "\xed\xb3\x80", FALSE, 0x0 }, { "\xed\xb4\x80", FALSE, 0x0 }, { "\xed\xb5\x80", FALSE, 0x0 }, { "\xed\xb6\x80", FALSE, 0x0 }, { "\xed\xb7\x80", FALSE, 0x0 }, { "\xed\xb8\x80", FALSE, 0x0 }, { "\xed\xb9\x80", FALSE, 0x0 }, { "\xed\xba\x80", FALSE, 0x0 }, { "\xed\xbb\x80", FALSE, 0x0 }, { "\xed\xbc\x80", FALSE, 0x0 }, { "\xed\xbd\x80", FALSE, 0x0 }, { "\xed\xbf\x80", FALSE, 0x0 }, /* .. U+DFFF */ { "\xe2\x82\xa1", TRUE, 0x20A1 }, /* U+20A1 */ { "\xe2\x28\xa1", FALSE, 0x0 }, /* invalid 2nd octet */ { "\xe2\x82\x28", FALSE, 0x0 }, /* invalid 3rd octet */ { "\xf0\x90\x8c\xbc", TRUE, 0x1033C }, /* U+1033C */ { "\xf0\x28\x8c\xbc", FALSE, 0x0 }, /*invalid 2nd octet*/ { "\xf0\x90\x28\xbc", FALSE, 0x0 }, /* invalid 3rd octet */ { "\xf0\x28\x8c\x28", FALSE, 0x0 }, /* invalid 4th octet */ { "\xf4\x80\x80\x80", TRUE, 0x100000 }, /* U+100000, supplementary plane start */ { "\xf4\x8f\xbf\xbf", TRUE, 0x10FFFF }, /* U+10FFFF, maximum value */ { "\xf8\xa1\xa1\xa1\xa1", FALSE, 0x0 }, /* invalid unicode */ { "\xfc\xa1\xa1\xa1\xa1\xa1", FALSE, 0x0 }, /* invalid unicode */ }; test_begin("unichar valid unicode"); for(size_t i = 0; i < N_ELEMENTS(test_cases); i++) { unichar_t chr; if (test_cases[i].valid) { test_assert_idx(uni_utf8_get_char(test_cases[i].input, &chr) > 0, i); test_assert_idx(test_cases[i].expected == chr, i); } else { test_assert_idx(uni_utf8_get_char(test_cases[i].input, &chr) < 1, i); } } test_end(); } static void test_unichar_surrogates(void) { unichar_t orig, high, low; test_begin("unichar surrogates"); orig = 0x10437; uni_split_surrogate(orig, &high, &low); test_assert(high == 0xD801); test_assert(low == 0xDC37); test_assert(uni_join_surrogate(high, low) == orig); test_end(); } void test_unichar(void) { static const char overlong_utf8[] = "\xf8\x80\x95\x81\xa1"; static const char collate_in[] = "\xc3\xbc \xc2\xb3"; static const char collate_exp[] = "U\xcc\x88 3"; buffer_t *collate_out; unichar_t chr, chr2; string_t *str = t_str_new(16); test_begin("unichars encode/decode"); for (chr = 0; chr <= 0x10ffff; chr++) { /* skip surrogates */ if ((chr & 0xfff800) == 0xd800) continue; /* The bottom 6 bits should be irrelevant to code coverage, only test 000000, 111111, and something in between. */ if ((chr & 63) == 1) chr += i_rand_limit(62); /* After 0, somewhere between 1 and 62 */ else if ((chr & 63) > 0 && (chr & 63) < 63) chr |= 63; /* After random, straight to 63 */ str_truncate(str, 0); uni_ucs4_to_utf8_c(chr, str); test_assert(uni_utf8_str_is_valid(str_c(str))); test_assert(uni_utf8_get_char(str_c(str), &chr2) == (int)uni_utf8_char_bytes(*str_data(str))); test_assert(chr2 == chr); if ((chr & 0x63) == 0) { unsigned int utf8len = uni_utf8_char_bytes((unsigned char)*str_c(str)); /* virtually truncate the byte string */ while (--utf8len > 0) test_assert(uni_utf8_get_char_n(str_c(str), utf8len, &chr2) == 0); utf8len = uni_utf8_char_bytes((unsigned char)*str_c(str)); /* actually truncate the byte stream */ while (--utf8len > 0) { str_truncate(str, utf8len); test_assert(!uni_utf8_str_is_valid(str_c(str))); test_assert(uni_utf8_get_char(str_c(str), &chr2) == 0); } } } test_end(); test_begin("unichar collation"); collate_out = buffer_create_dynamic(default_pool, 32); uni_utf8_to_decomposed_titlecase(collate_in, sizeof(collate_in), collate_out); test_assert(strcmp(collate_out->data, collate_exp) == 0); buffer_free(&collate_out); test_assert(!uni_utf8_str_is_valid(overlong_utf8)); test_assert(uni_utf8_get_char(overlong_utf8, &chr2) < 0); test_end(); test_unichar_uni_utf8_strlen(); test_unichar_uni_utf8_partial_strlen_n(); test_unichar_valid_unicode(); test_unichar_surrogates(); } dovecot-2.3.21.1/src/lib/event-filter-lexer.c0000644000000000000000000017222214656633635015541 00000000000000#line 2 "event-filter-lexer.c" #line 4 "event-filter-lexer.c" #define YY_INT_ALIGNED short int /* A lexical scanner generated by flex */ #define FLEX_SCANNER #define YY_FLEX_MAJOR_VERSION 2 #define YY_FLEX_MINOR_VERSION 6 #define YY_FLEX_SUBMINOR_VERSION 4 #if YY_FLEX_SUBMINOR_VERSION > 0 #define FLEX_BETA #endif #ifdef yy_create_buffer #define event_filter_parser__create_buffer_ALREADY_DEFINED #else #define yy_create_buffer event_filter_parser__create_buffer #endif #ifdef yy_delete_buffer #define event_filter_parser__delete_buffer_ALREADY_DEFINED #else #define yy_delete_buffer event_filter_parser__delete_buffer #endif #ifdef yy_scan_buffer #define event_filter_parser__scan_buffer_ALREADY_DEFINED #else #define yy_scan_buffer event_filter_parser__scan_buffer #endif #ifdef yy_scan_string #define event_filter_parser__scan_string_ALREADY_DEFINED #else #define yy_scan_string event_filter_parser__scan_string #endif #ifdef yy_scan_bytes #define event_filter_parser__scan_bytes_ALREADY_DEFINED #else #define yy_scan_bytes event_filter_parser__scan_bytes #endif #ifdef yy_init_buffer #define event_filter_parser__init_buffer_ALREADY_DEFINED #else #define yy_init_buffer event_filter_parser__init_buffer #endif #ifdef yy_flush_buffer #define event_filter_parser__flush_buffer_ALREADY_DEFINED #else #define yy_flush_buffer event_filter_parser__flush_buffer #endif #ifdef yy_load_buffer_state #define event_filter_parser__load_buffer_state_ALREADY_DEFINED #else #define yy_load_buffer_state event_filter_parser__load_buffer_state #endif #ifdef yy_switch_to_buffer #define event_filter_parser__switch_to_buffer_ALREADY_DEFINED #else #define yy_switch_to_buffer event_filter_parser__switch_to_buffer #endif #ifdef yypush_buffer_state #define event_filter_parser_push_buffer_state_ALREADY_DEFINED #else #define yypush_buffer_state event_filter_parser_push_buffer_state #endif #ifdef yypop_buffer_state #define event_filter_parser_pop_buffer_state_ALREADY_DEFINED #else #define yypop_buffer_state event_filter_parser_pop_buffer_state #endif #ifdef yyensure_buffer_stack #define event_filter_parser_ensure_buffer_stack_ALREADY_DEFINED #else #define yyensure_buffer_stack event_filter_parser_ensure_buffer_stack #endif #ifdef yylex #define event_filter_parser_lex_ALREADY_DEFINED #else #define yylex event_filter_parser_lex #endif #ifdef yyrestart #define event_filter_parser_restart_ALREADY_DEFINED #else #define yyrestart event_filter_parser_restart #endif #ifdef yylex_init #define event_filter_parser_lex_init_ALREADY_DEFINED #else #define yylex_init event_filter_parser_lex_init #endif #ifdef yylex_init_extra #define event_filter_parser_lex_init_extra_ALREADY_DEFINED #else #define yylex_init_extra event_filter_parser_lex_init_extra #endif #ifdef yylex_destroy #define event_filter_parser_lex_destroy_ALREADY_DEFINED #else #define yylex_destroy event_filter_parser_lex_destroy #endif #ifdef yyget_debug #define event_filter_parser_get_debug_ALREADY_DEFINED #else #define yyget_debug event_filter_parser_get_debug #endif #ifdef yyset_debug #define event_filter_parser_set_debug_ALREADY_DEFINED #else #define yyset_debug event_filter_parser_set_debug #endif #ifdef yyget_extra #define event_filter_parser_get_extra_ALREADY_DEFINED #else #define yyget_extra event_filter_parser_get_extra #endif #ifdef yyset_extra #define event_filter_parser_set_extra_ALREADY_DEFINED #else #define yyset_extra event_filter_parser_set_extra #endif #ifdef yyget_in #define event_filter_parser_get_in_ALREADY_DEFINED #else #define yyget_in event_filter_parser_get_in #endif #ifdef yyset_in #define event_filter_parser_set_in_ALREADY_DEFINED #else #define yyset_in event_filter_parser_set_in #endif #ifdef yyget_out #define event_filter_parser_get_out_ALREADY_DEFINED #else #define yyget_out event_filter_parser_get_out #endif #ifdef yyset_out #define event_filter_parser_set_out_ALREADY_DEFINED #else #define yyset_out event_filter_parser_set_out #endif #ifdef yyget_leng #define event_filter_parser_get_leng_ALREADY_DEFINED #else #define yyget_leng event_filter_parser_get_leng #endif #ifdef yyget_text #define event_filter_parser_get_text_ALREADY_DEFINED #else #define yyget_text event_filter_parser_get_text #endif #ifdef yyget_lineno #define event_filter_parser_get_lineno_ALREADY_DEFINED #else #define yyget_lineno event_filter_parser_get_lineno #endif #ifdef yyset_lineno #define event_filter_parser_set_lineno_ALREADY_DEFINED #else #define yyset_lineno event_filter_parser_set_lineno #endif #ifdef yyget_column #define event_filter_parser_get_column_ALREADY_DEFINED #else #define yyget_column event_filter_parser_get_column #endif #ifdef yyset_column #define event_filter_parser_set_column_ALREADY_DEFINED #else #define yyset_column event_filter_parser_set_column #endif #ifdef yywrap #define event_filter_parser_wrap_ALREADY_DEFINED #else #define yywrap event_filter_parser_wrap #endif #ifdef yyget_lval #define event_filter_parser_get_lval_ALREADY_DEFINED #else #define yyget_lval event_filter_parser_get_lval #endif #ifdef yyset_lval #define event_filter_parser_set_lval_ALREADY_DEFINED #else #define yyset_lval event_filter_parser_set_lval #endif #ifdef yyalloc #define event_filter_parser_alloc_ALREADY_DEFINED #else #define yyalloc event_filter_parser_alloc #endif #ifdef yyrealloc #define event_filter_parser_realloc_ALREADY_DEFINED #else #define yyrealloc event_filter_parser_realloc #endif #ifdef yyfree #define event_filter_parser_free_ALREADY_DEFINED #else #define yyfree event_filter_parser_free #endif /* First, we deal with platform-specific or compiler-specific issues. */ /* begin standard C headers. */ #include #include #include #include /* end standard C headers. */ /* flex integer type definitions */ #ifndef FLEXINT_H #define FLEXINT_H /* C99 systems have . Non-C99 systems may or may not. */ #if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, * if you want the limit (max/min) macros for int types. */ #ifndef __STDC_LIMIT_MACROS #define __STDC_LIMIT_MACROS 1 #endif #include typedef int8_t flex_int8_t; typedef uint8_t flex_uint8_t; typedef int16_t flex_int16_t; typedef uint16_t flex_uint16_t; typedef int32_t flex_int32_t; typedef uint32_t flex_uint32_t; #else typedef signed char flex_int8_t; typedef short int flex_int16_t; typedef int flex_int32_t; typedef unsigned char flex_uint8_t; typedef unsigned short int flex_uint16_t; typedef unsigned int flex_uint32_t; /* Limits of integral types. */ #ifndef INT8_MIN #define INT8_MIN (-128) #endif #ifndef INT16_MIN #define INT16_MIN (-32767-1) #endif #ifndef INT32_MIN #define INT32_MIN (-2147483647-1) #endif #ifndef INT8_MAX #define INT8_MAX (127) #endif #ifndef INT16_MAX #define INT16_MAX (32767) #endif #ifndef INT32_MAX #define INT32_MAX (2147483647) #endif #ifndef UINT8_MAX #define UINT8_MAX (255U) #endif #ifndef UINT16_MAX #define UINT16_MAX (65535U) #endif #ifndef UINT32_MAX #define UINT32_MAX (4294967295U) #endif #ifndef SIZE_MAX #define SIZE_MAX (~(size_t)0) #endif #endif /* ! C99 */ #endif /* ! FLEXINT_H */ /* begin standard C++ headers. */ /* TODO: this is always defined, so inline it */ #define yyconst const #if defined(__GNUC__) && __GNUC__ >= 3 #define yynoreturn __attribute__((__noreturn__)) #else #define yynoreturn #endif /* Returned upon end-of-file. */ #define YY_NULL 0 /* Promotes a possibly negative, possibly signed char to an * integer in range [0..255] for use as an array index. */ #define YY_SC_TO_UI(c) ((YY_CHAR) (c)) /* An opaque pointer. */ #ifndef YY_TYPEDEF_YY_SCANNER_T #define YY_TYPEDEF_YY_SCANNER_T typedef void* yyscan_t; #endif /* For convenience, these vars (plus the bison vars far below) are macros in the reentrant scanner. */ #define yyin yyg->yyin_r #define yyout yyg->yyout_r #define yyextra yyg->yyextra_r #define yyleng yyg->yyleng_r #define yytext yyg->yytext_r #define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) #define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) #define yy_flex_debug yyg->yy_flex_debug_r /* Enter a start condition. This macro really ought to take a parameter, * but we do it the disgusting crufty way forced on us by the ()-less * definition of BEGIN. */ #define BEGIN yyg->yy_start = 1 + 2 * /* Translate the current start state into a value that can be later handed * to BEGIN to return to the state. The YYSTATE alias is for lex * compatibility. */ #define YY_START ((yyg->yy_start - 1) / 2) #define YYSTATE YY_START /* Action number for EOF rule of a given start state. */ #define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) /* Special action meaning "start processing a new file". */ #define YY_NEW_FILE yyrestart( yyin , yyscanner ) #define YY_END_OF_BUFFER_CHAR 0 /* Size of default input buffer. */ #ifndef YY_BUF_SIZE #ifdef __ia64__ /* On IA-64, the buffer size is 16k, not 8k. * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. * Ditto for the __ia64__ case accordingly. */ #define YY_BUF_SIZE 32768 #else #define YY_BUF_SIZE 16384 #endif /* __ia64__ */ #endif /* The state buf must be large enough to hold one state per character in the main buffer. */ #define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) #ifndef YY_TYPEDEF_YY_BUFFER_STATE #define YY_TYPEDEF_YY_BUFFER_STATE typedef struct yy_buffer_state *YY_BUFFER_STATE; #endif #ifndef YY_TYPEDEF_YY_SIZE_T #define YY_TYPEDEF_YY_SIZE_T typedef size_t yy_size_t; #endif #define EOB_ACT_CONTINUE_SCAN 0 #define EOB_ACT_END_OF_FILE 1 #define EOB_ACT_LAST_MATCH 2 #define YY_LESS_LINENO(n) #define YY_LINENO_REWIND_TO(ptr) /* Return all but the first "n" matched characters back to the input stream. */ #define yyless(n) \ do \ { \ /* Undo effects of setting up yytext. */ \ int yyless_macro_arg = (n); \ YY_LESS_LINENO(yyless_macro_arg);\ *yy_cp = yyg->yy_hold_char; \ YY_RESTORE_YY_MORE_OFFSET \ yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ YY_DO_BEFORE_ACTION; /* set up yytext again */ \ } \ while ( 0 ) #define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner ) #ifndef YY_STRUCT_YY_BUFFER_STATE #define YY_STRUCT_YY_BUFFER_STATE struct yy_buffer_state { FILE *yy_input_file; char *yy_ch_buf; /* input buffer */ char *yy_buf_pos; /* current position in input buffer */ /* Size of input buffer in bytes, not including room for EOB * characters. */ int yy_buf_size; /* Number of characters read into yy_ch_buf, not including EOB * characters. */ int yy_n_chars; /* Whether we "own" the buffer - i.e., we know we created it, * and can realloc() it to grow it, and should free() it to * delete it. */ int yy_is_our_buffer; /* Whether this is an "interactive" input source; if so, and * if we're using stdio for input, then we want to use getc() * instead of fread(), to make sure we stop fetching input after * each newline. */ int yy_is_interactive; /* Whether we're considered to be at the beginning of a line. * If so, '^' rules will be active on the next match, otherwise * not. */ int yy_at_bol; int yy_bs_lineno; /**< The line count. */ int yy_bs_column; /**< The column count. */ /* Whether to try to fill the input buffer when we reach the * end of it. */ int yy_fill_buffer; int yy_buffer_status; #define YY_BUFFER_NEW 0 #define YY_BUFFER_NORMAL 1 /* When an EOF's been seen but there's still some text to process * then we mark the buffer as YY_EOF_PENDING, to indicate that we * shouldn't try reading from the input source any more. We might * still have a bunch of tokens to match, though, because of * possible backing-up. * * When we actually see the EOF, we change the status to "new" * (via yyrestart()), so that the user can continue scanning by * just pointing yyin at a new input file. */ #define YY_BUFFER_EOF_PENDING 2 }; #endif /* !YY_STRUCT_YY_BUFFER_STATE */ /* We provide macros for accessing buffer states in case in the * future we want to put the buffer states in a more general * "scanner state". * * Returns the top of the stack, or NULL. */ #define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \ ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \ : NULL) /* Same as previous macro, but useful when we know that the buffer stack is not * NULL or when we need an lvalue. For internal use only. */ #define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] void yyrestart ( FILE *input_file , yyscan_t yyscanner ); void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); void yypop_buffer_state ( yyscan_t yyscanner ); static void yyensure_buffer_stack ( yyscan_t yyscanner ); static void yy_load_buffer_state ( yyscan_t yyscanner ); static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file , yyscan_t yyscanner ); #define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER , yyscanner) YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len , yyscan_t yyscanner ); void *yyalloc ( yy_size_t , yyscan_t yyscanner ); void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); void yyfree ( void * , yyscan_t yyscanner ); #define yy_new_buffer yy_create_buffer #define yy_set_interactive(is_interactive) \ { \ if ( ! YY_CURRENT_BUFFER ){ \ yyensure_buffer_stack (yyscanner); \ YY_CURRENT_BUFFER_LVALUE = \ yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ } \ YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ } #define yy_set_bol(at_bol) \ { \ if ( ! YY_CURRENT_BUFFER ){\ yyensure_buffer_stack (yyscanner); \ YY_CURRENT_BUFFER_LVALUE = \ yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ } \ YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ } #define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) /* Begin user sect3 */ #define event_filter_parser_wrap(yyscanner) (/*CONSTCOND*/1) #define YY_SKIP_YYWRAP typedef flex_uint8_t YY_CHAR; typedef int yy_state_type; #define yytext_ptr yytext_r static yy_state_type yy_get_previous_state ( yyscan_t yyscanner ); static yy_state_type yy_try_NUL_trans ( yy_state_type current_state , yyscan_t yyscanner); static int yy_get_next_buffer ( yyscan_t yyscanner ); static void yynoreturn yy_fatal_error ( const char* msg , yyscan_t yyscanner ); /* Done after the current pattern has been matched and before the * corresponding action - sets up yytext. */ #define YY_DO_BEFORE_ACTION \ yyg->yytext_ptr = yy_bp; \ yyleng = (int) (yy_cp - yy_bp); \ yyg->yy_hold_char = *yy_cp; \ *yy_cp = '\0'; \ yyg->yy_c_buf_p = yy_cp; #define YY_NUM_RULES 14 #define YY_END_OF_BUFFER 15 /* This struct is not used in this scanner, but its presence is necessary. */ struct yy_trans_info { flex_int32_t yy_verify; flex_int32_t yy_nxt; }; static const flex_int16_t yy_accept[29] = { 0, 0, 0, 0, 0, 15, 13, 12, 12, 1, 10, 11, 11, 11, 11, 3, 2, 14, 11, 11, 11, 8, 3, 6, 5, 4, 7, 9, 0 } ; static const YY_CHAR yy_ec[256] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 4, 1, 1, 1, 1, 1, 5, 5, 6, 1, 1, 6, 6, 1, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 1, 5, 5, 5, 6, 1, 7, 6, 6, 8, 6, 6, 6, 6, 6, 6, 6, 6, 6, 9, 10, 6, 6, 11, 6, 12, 6, 6, 6, 6, 6, 6, 1, 13, 1, 1, 6, 1, 7, 6, 6, 8, 6, 6, 6, 6, 6, 6, 6, 6, 6, 9, 10, 6, 6, 11, 6, 12, 6, 6, 6, 6, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } ; static const YY_CHAR yy_meta[14] = { 0, 1, 1, 2, 3, 1, 4, 4, 4, 4, 4, 4, 4, 3 } ; static const flex_int16_t yy_base[33] = { 0, 0, 0, 10, 20, 21, 56, 56, 56, 56, 56, 0, 11, 9, 7, 0, 56, 30, 0, 9, 4, 0, 0, 56, 56, 56, 0, 0, 56, 43, 11, 47, 51 } ; static const flex_int16_t yy_def[33] = { 0, 28, 1, 29, 29, 28, 28, 28, 28, 28, 28, 30, 30, 30, 30, 31, 28, 32, 30, 30, 30, 30, 31, 28, 28, 28, 30, 30, 0, 28, 28, 28, 28 } ; static const flex_int16_t yy_nxt[70] = { 0, 6, 7, 8, 9, 10, 11, 12, 11, 13, 14, 11, 11, 6, 16, 18, 27, 26, 21, 20, 19, 28, 28, 17, 16, 28, 28, 28, 28, 28, 28, 28, 28, 17, 24, 28, 28, 28, 28, 28, 28, 28, 28, 25, 15, 15, 15, 15, 22, 22, 28, 22, 23, 28, 23, 23, 5, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28 } ; static const flex_int16_t yy_chk[70] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 30, 20, 19, 14, 13, 12, 5, 0, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 4, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 29, 29, 29, 29, 31, 31, 0, 31, 32, 0, 32, 32, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28 } ; /* The intent behind this definition is that it'll catch * any uses of REJECT which flex missed. */ #define REJECT reject_used_but_not_detected #define yymore() yymore_used_but_not_detected #define YY_MORE_ADJ 0 #define YY_RESTORE_YY_MORE_OFFSET #line 1 "event-filter-lexer.l" /* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ #define YY_NO_INPUT 1 #line 13 "event-filter-lexer.l" #include "lib.h" #include "str.h" #include "event-filter-private.h" #include "event-filter-parser.h" #define YY_FATAL_ERROR(msg) { i_fatal("event filter parsing: %s", (msg)); } /* mimic renaming done by bison's api.prefix %define */ #define YYSTYPE EVENT_FILTER_PARSER_STYPE #define YY_INPUT(buf, result, max_size) \ result = event_filter_parser_input_proc(buf, max_size, yyscanner) static size_t event_filter_parser_input_proc(char *buf, size_t size, yyscan_t scanner); #pragma GCC diagnostic push /* ignore strict bool warnings in generated code */ #ifdef HAVE_STRICT_BOOL # pragma GCC diagnostic ignored "-Wstrict-bool" #endif /* ignore sign comparison errors (buggy flex) */ #pragma GCC diagnostic ignored "-Wsign-compare" /* ignore unused functions */ #pragma GCC diagnostic ignored "-Wunused-function" /* ignore unused parameters */ #pragma GCC diagnostic ignored "-Wunused-parameter" #line 695 "event-filter-lexer.c" #line 697 "event-filter-lexer.c" #define INITIAL 0 #define string 1 #ifndef YY_NO_UNISTD_H /* Special case for "unistd.h", since it is non-ANSI. We include it way * down here because we want the user's section 1 to have been scanned first. * The user has a chance to override it with an option. */ #include #endif #ifndef YY_EXTRA_TYPE #define YY_EXTRA_TYPE void * #endif /* Holds the entire state of the reentrant scanner. */ struct yyguts_t { /* User-defined. Not touched by flex. */ YY_EXTRA_TYPE yyextra_r; /* The rest are the same as the globals declared in the non-reentrant scanner. */ FILE *yyin_r, *yyout_r; size_t yy_buffer_stack_top; /**< index of top of stack. */ size_t yy_buffer_stack_max; /**< capacity of stack. */ YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ char yy_hold_char; int yy_n_chars; int yyleng_r; char *yy_c_buf_p; int yy_init; int yy_start; int yy_did_buffer_switch_on_eof; int yy_start_stack_ptr; int yy_start_stack_depth; int *yy_start_stack; yy_state_type yy_last_accepting_state; char* yy_last_accepting_cpos; int yylineno_r; int yy_flex_debug_r; char *yytext_r; int yy_more_flag; int yy_more_len; YYSTYPE * yylval_r; }; /* end struct yyguts_t */ static int yy_init_globals ( yyscan_t yyscanner ); /* This must go here because YYSTYPE and YYLTYPE are included * from bison output in section 1.*/ # define yylval yyg->yylval_r int yylex_init (yyscan_t* scanner); int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); /* Accessor methods to globals. These are made visible to non-reentrant scanners for convenience. */ int yylex_destroy ( yyscan_t yyscanner ); int yyget_debug ( yyscan_t yyscanner ); void yyset_debug ( int debug_flag , yyscan_t yyscanner ); YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); FILE *yyget_in ( yyscan_t yyscanner ); void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); FILE *yyget_out ( yyscan_t yyscanner ); void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); int yyget_leng ( yyscan_t yyscanner ); char *yyget_text ( yyscan_t yyscanner ); int yyget_lineno ( yyscan_t yyscanner ); void yyset_lineno ( int _line_number , yyscan_t yyscanner ); int yyget_column ( yyscan_t yyscanner ); void yyset_column ( int _column_no , yyscan_t yyscanner ); YYSTYPE * yyget_lval ( yyscan_t yyscanner ); void yyset_lval ( YYSTYPE * yylval_param , yyscan_t yyscanner ); /* Macros after this point can all be overridden by user definitions in * section 1. */ #ifndef YY_SKIP_YYWRAP #ifdef __cplusplus extern "C" int yywrap ( yyscan_t yyscanner ); #else extern int yywrap ( yyscan_t yyscanner ); #endif #endif #ifndef YY_NO_UNPUT #endif #ifndef yytext_ptr static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); #endif #ifdef YY_NEED_STRLEN static int yy_flex_strlen ( const char * , yyscan_t yyscanner); #endif #ifndef YY_NO_INPUT #ifdef __cplusplus static int yyinput ( yyscan_t yyscanner ); #else static int input ( yyscan_t yyscanner ); #endif #endif /* Amount of stuff to slurp up with each read. */ #ifndef YY_READ_BUF_SIZE #ifdef __ia64__ /* On IA-64, the buffer size is 16k, not 8k */ #define YY_READ_BUF_SIZE 16384 #else #define YY_READ_BUF_SIZE 8192 #endif /* __ia64__ */ #endif /* Copy whatever the last rule matched to the standard output. */ #ifndef ECHO /* This used to be an fputs(), but since the string might contain NUL's, * we now use fwrite(). */ #define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0) #endif /* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, * is returned in "result". */ #ifndef YY_INPUT #define YY_INPUT(buf,result,max_size) \ if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ { \ int c = '*'; \ int n; \ for ( n = 0; n < max_size && \ (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ buf[n] = (char) c; \ if ( c == '\n' ) \ buf[n++] = (char) c; \ if ( c == EOF && ferror( yyin ) ) \ YY_FATAL_ERROR( "input in flex scanner failed" ); \ result = n; \ } \ else \ { \ errno=0; \ while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \ { \ if( errno != EINTR) \ { \ YY_FATAL_ERROR( "input in flex scanner failed" ); \ break; \ } \ errno=0; \ clearerr(yyin); \ } \ }\ \ #endif /* No semi-colon after return; correct usage is to write "yyterminate();" - * we don't want an extra ';' after the "return" because that will cause * some compilers to complain about unreachable statements. */ #ifndef yyterminate #define yyterminate() return YY_NULL #endif /* Number of entries by which start-condition stack grows. */ #ifndef YY_START_STACK_INCR #define YY_START_STACK_INCR 25 #endif /* Report a fatal error. */ #ifndef YY_FATAL_ERROR #define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner) #endif /* end tables serialization structures and prototypes */ /* Default declaration of generated scanner - a define so the user can * easily add parameters. */ #ifndef YY_DECL #define YY_DECL_IS_OURS 1 extern int yylex \ (YYSTYPE * yylval_param , yyscan_t yyscanner); #define YY_DECL int yylex \ (YYSTYPE * yylval_param , yyscan_t yyscanner) #endif /* !YY_DECL */ /* Code executed at the beginning of each rule, after yytext and yyleng * have been set up. */ #ifndef YY_USER_ACTION #define YY_USER_ACTION #endif /* Code executed at the end of each rule. */ #ifndef YY_BREAK #define YY_BREAK /*LINTED*/break; #endif #define YY_RULE_SETUP \ YY_USER_ACTION /** The main scanner function which does all the work. */ YY_DECL { yy_state_type yy_current_state; char *yy_cp, *yy_bp; int yy_act; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yylval = yylval_param; if ( !yyg->yy_init ) { yyg->yy_init = 1; #ifdef YY_USER_INIT YY_USER_INIT; #endif if ( ! yyg->yy_start ) yyg->yy_start = 1; /* first start state */ if ( ! yyin ) yyin = stdin; if ( ! yyout ) yyout = stdout; if ( ! YY_CURRENT_BUFFER ) { yyensure_buffer_stack (yyscanner); YY_CURRENT_BUFFER_LVALUE = yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); } yy_load_buffer_state( yyscanner ); } { #line 44 "event-filter-lexer.l" #line 46 "event-filter-lexer.l" string_t *str_buf = NULL; #line 975 "event-filter-lexer.c" while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ { yy_cp = yyg->yy_c_buf_p; /* Support of yytext. */ *yy_cp = yyg->yy_hold_char; /* yy_bp points to the position in yy_ch_buf of the start of * the current run. */ yy_bp = yy_cp; yy_current_state = yyg->yy_start; yy_match: do { YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ; if ( yy_accept[yy_current_state] ) { yyg->yy_last_accepting_state = yy_current_state; yyg->yy_last_accepting_cpos = yy_cp; } while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; if ( yy_current_state >= 29 ) yy_c = yy_meta[yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; ++yy_cp; } while ( yy_current_state != 28 ); yy_cp = yyg->yy_last_accepting_cpos; yy_current_state = yyg->yy_last_accepting_state; yy_find_action: yy_act = yy_accept[yy_current_state]; YY_DO_BEFORE_ACTION; do_action: /* This label is used only to access EOF actions. */ switch ( yy_act ) { /* beginning of action switch */ case 0: /* must back up */ /* undo the effects of YY_DO_BEFORE_ACTION */ *yy_cp = yyg->yy_hold_char; yy_cp = yyg->yy_last_accepting_cpos; yy_current_state = yyg->yy_last_accepting_state; goto yy_find_action; case 1: YY_RULE_SETUP #line 48 "event-filter-lexer.l" { BEGIN(string); str_buf = t_str_new(128); } YY_BREAK case 2: YY_RULE_SETUP #line 53 "event-filter-lexer.l" { yylval->str = str_c(str_buf); BEGIN(INITIAL); return STRING; } YY_BREAK /* Note: these have to match the event_filter_append_escaped() behavior */ case 3: /* rule 3 can match eol */ YY_RULE_SETUP #line 59 "event-filter-lexer.l" { str_append(str_buf, yytext); } YY_BREAK case 4: YY_RULE_SETUP #line 60 "event-filter-lexer.l" { str_append_c(str_buf, '\\'); } YY_BREAK case 5: YY_RULE_SETUP #line 61 "event-filter-lexer.l" { str_append_c(str_buf, '"'); } YY_BREAK case 6: YY_RULE_SETUP #line 62 "event-filter-lexer.l" { str_append(str_buf, yytext); } YY_BREAK case 7: YY_RULE_SETUP #line 64 "event-filter-lexer.l" { return AND; } YY_BREAK case 8: YY_RULE_SETUP #line 65 "event-filter-lexer.l" { return OR; } YY_BREAK case 9: YY_RULE_SETUP #line 66 "event-filter-lexer.l" { return NOT; } YY_BREAK case 10: YY_RULE_SETUP #line 67 "event-filter-lexer.l" { return *yytext; } YY_BREAK case 11: YY_RULE_SETUP #line 68 "event-filter-lexer.l" { yylval->str = t_strdup(yytext); return TOKEN; } YY_BREAK case 12: /* rule 12 can match eol */ YY_RULE_SETUP #line 69 "event-filter-lexer.l" { /* ignore */ } YY_BREAK case 13: YY_RULE_SETUP #line 70 "event-filter-lexer.l" { /* * We simply return the char to the * and let the grammar error out * with a syntax error. * * Note: The cast is significant * since utf-8 bytes >=128 will * otherwise result in sign * extension and a negative int * getting returned on some * platforms (e.g., x86) which in * turn confuses the parser. E.g., * if: * *yytext = '\x80' * we get: * *yytext -> -128 * (int) *yytext -> -128 * which is wrong. With the * unsigned char cast, we get: * (u.c.) *yytext -> 128 * (int)(u.c.) *yytext -> 128 * which is correct. */ return (unsigned char) *yytext; } YY_BREAK case 14: YY_RULE_SETUP #line 96 "event-filter-lexer.l" ECHO; YY_BREAK #line 1134 "event-filter-lexer.c" case YY_STATE_EOF(INITIAL): case YY_STATE_EOF(string): yyterminate(); case YY_END_OF_BUFFER: { /* Amount of text matched not including the EOB char. */ int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1; /* Undo the effects of YY_DO_BEFORE_ACTION. */ *yy_cp = yyg->yy_hold_char; YY_RESTORE_YY_MORE_OFFSET if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) { /* We're scanning a new file or input source. It's * possible that this happened because the user * just pointed yyin at a new source and called * yylex(). If so, then we have to assure * consistency between YY_CURRENT_BUFFER and our * globals. Here is the right place to do so, because * this is the first action (other than possibly a * back-up) that will match for the new input source. */ yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; } /* Note that here we test for yy_c_buf_p "<=" to the position * of the first EOB in the buffer, since yy_c_buf_p will * already have been incremented past the NUL character * (since all states make transitions on EOB to the * end-of-buffer state). Contrast this with the test * in input(). */ if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) { /* This was really a NUL. */ yy_state_type yy_next_state; yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; yy_current_state = yy_get_previous_state( yyscanner ); /* Okay, we're now positioned to make the NUL * transition. We couldn't have * yy_get_previous_state() go ahead and do it * for us because it doesn't know how to deal * with the possibility of jamming (and we don't * want to build jamming into it because then it * will run more slowly). */ yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner); yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; if ( yy_next_state ) { /* Consume the NUL. */ yy_cp = ++yyg->yy_c_buf_p; yy_current_state = yy_next_state; goto yy_match; } else { yy_cp = yyg->yy_last_accepting_cpos; yy_current_state = yyg->yy_last_accepting_state; goto yy_find_action; } } else switch ( yy_get_next_buffer( yyscanner ) ) { case EOB_ACT_END_OF_FILE: { yyg->yy_did_buffer_switch_on_eof = 0; if ( yywrap( yyscanner ) ) { /* Note: because we've taken care in * yy_get_next_buffer() to have set up * yytext, we can now set up * yy_c_buf_p so that if some total * hoser (like flex itself) wants to * call the scanner after we return the * YY_NULL, it'll still work - another * YY_NULL will get returned. */ yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ; yy_act = YY_STATE_EOF(YY_START); goto do_action; } else { if ( ! yyg->yy_did_buffer_switch_on_eof ) YY_NEW_FILE; } break; } case EOB_ACT_CONTINUE_SCAN: yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; yy_current_state = yy_get_previous_state( yyscanner ); yy_cp = yyg->yy_c_buf_p; yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; goto yy_match; case EOB_ACT_LAST_MATCH: yyg->yy_c_buf_p = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars]; yy_current_state = yy_get_previous_state( yyscanner ); yy_cp = yyg->yy_c_buf_p; yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; goto yy_find_action; } break; } default: YY_FATAL_ERROR( "fatal flex scanner internal error--no action found" ); } /* end of action switch */ } /* end of scanning one token */ } /* end of user's declarations */ } /* end of yylex */ /* yy_get_next_buffer - try to read in a new buffer * * Returns a code representing an action: * EOB_ACT_LAST_MATCH - * EOB_ACT_CONTINUE_SCAN - continue scanning from current position * EOB_ACT_END_OF_FILE - end of file */ static int yy_get_next_buffer (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; char *source = yyg->yytext_ptr; int number_to_move, i; int ret_val; if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] ) YY_FATAL_ERROR( "fatal flex scanner internal error--end of buffer missed" ); if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) { /* Don't try to fill the buffer, so this is an EOF. */ if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 ) { /* We matched a single character, the EOB, so * treat this as a final EOF. */ return EOB_ACT_END_OF_FILE; } else { /* We matched some text prior to the EOB, first * process it. */ return EOB_ACT_LAST_MATCH; } } /* Try to read more data. */ /* First move last chars to start of buffer. */ number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr - 1); for ( i = 0; i < number_to_move; ++i ) *(dest++) = *(source++); if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) /* don't do the read, it's not guaranteed to return an EOF, * just force an EOF */ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0; else { int num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; while ( num_to_read <= 0 ) { /* Not enough room in the buffer - grow it. */ /* just a shorter name for the current buffer */ YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; int yy_c_buf_p_offset = (int) (yyg->yy_c_buf_p - b->yy_ch_buf); if ( b->yy_is_our_buffer ) { int new_size = b->yy_buf_size * 2; if ( new_size <= 0 ) b->yy_buf_size += b->yy_buf_size / 8; else b->yy_buf_size *= 2; b->yy_ch_buf = (char *) /* Include room in for 2 EOB chars. */ yyrealloc( (void *) b->yy_ch_buf, (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); } else /* Can't grow it, we don't own it. */ b->yy_ch_buf = NULL; if ( ! b->yy_ch_buf ) YY_FATAL_ERROR( "fatal error - scanner input buffer overflow" ); yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; } if ( num_to_read > YY_READ_BUF_SIZE ) num_to_read = YY_READ_BUF_SIZE; /* Read in more data. */ YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), yyg->yy_n_chars, num_to_read ); YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; } if ( yyg->yy_n_chars == 0 ) { if ( number_to_move == YY_MORE_ADJ ) { ret_val = EOB_ACT_END_OF_FILE; yyrestart( yyin , yyscanner); } else { ret_val = EOB_ACT_LAST_MATCH; YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_EOF_PENDING; } } else ret_val = EOB_ACT_CONTINUE_SCAN; if ((yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { /* Extend the array by 50%, plus the number we really need. */ int new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size , yyscanner ); if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); /* "- 2" to take care of EOB's */ YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); } yyg->yy_n_chars += number_to_move; YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR; YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; return ret_val; } /* yy_get_previous_state - get the state just before the EOB char was reached */ static yy_state_type yy_get_previous_state (yyscan_t yyscanner) { yy_state_type yy_current_state; char *yy_cp; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yy_current_state = yyg->yy_start; for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp ) { YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); if ( yy_accept[yy_current_state] ) { yyg->yy_last_accepting_state = yy_current_state; yyg->yy_last_accepting_cpos = yy_cp; } while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; if ( yy_current_state >= 29 ) yy_c = yy_meta[yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; } return yy_current_state; } /* yy_try_NUL_trans - try to make a transition on the NUL character * * synopsis * next_state = yy_try_NUL_trans( current_state ); */ static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state , yyscan_t yyscanner) { int yy_is_jam; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */ char *yy_cp = yyg->yy_c_buf_p; YY_CHAR yy_c = 1; if ( yy_accept[yy_current_state] ) { yyg->yy_last_accepting_state = yy_current_state; yyg->yy_last_accepting_cpos = yy_cp; } while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; if ( yy_current_state >= 29 ) yy_c = yy_meta[yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; yy_is_jam = (yy_current_state == 28); (void)yyg; return yy_is_jam ? 0 : yy_current_state; } #ifndef YY_NO_UNPUT #endif #ifndef YY_NO_INPUT #ifdef __cplusplus static int yyinput (yyscan_t yyscanner) #else static int input (yyscan_t yyscanner) #endif { int c; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; *yyg->yy_c_buf_p = yyg->yy_hold_char; if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) { /* yy_c_buf_p now points to the character we want to return. * If this occurs *before* the EOB characters, then it's a * valid NUL; if not, then we've hit the end of the buffer. */ if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) /* This was really a NUL. */ *yyg->yy_c_buf_p = '\0'; else { /* need more input */ int offset = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr); ++yyg->yy_c_buf_p; switch ( yy_get_next_buffer( yyscanner ) ) { case EOB_ACT_LAST_MATCH: /* This happens because yy_g_n_b() * sees that we've accumulated a * token and flags that we need to * try matching the token before * proceeding. But for input(), * there's no matching to consider. * So convert the EOB_ACT_LAST_MATCH * to EOB_ACT_END_OF_FILE. */ /* Reset buffer status. */ yyrestart( yyin , yyscanner); /*FALLTHROUGH*/ case EOB_ACT_END_OF_FILE: { if ( yywrap( yyscanner ) ) return 0; if ( ! yyg->yy_did_buffer_switch_on_eof ) YY_NEW_FILE; #ifdef __cplusplus return yyinput(yyscanner); #else return input(yyscanner); #endif } case EOB_ACT_CONTINUE_SCAN: yyg->yy_c_buf_p = yyg->yytext_ptr + offset; break; } } } c = *(unsigned char *) yyg->yy_c_buf_p; /* cast for 8-bit char's */ *yyg->yy_c_buf_p = '\0'; /* preserve yytext */ yyg->yy_hold_char = *++yyg->yy_c_buf_p; return c; } #endif /* ifndef YY_NO_INPUT */ /** Immediately switch to a different input stream. * @param input_file A readable stream. * @param yyscanner The scanner object. * @note This function does not reset the start condition to @c INITIAL . */ void yyrestart (FILE * input_file , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if ( ! YY_CURRENT_BUFFER ){ yyensure_buffer_stack (yyscanner); YY_CURRENT_BUFFER_LVALUE = yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); } yy_init_buffer( YY_CURRENT_BUFFER, input_file , yyscanner); yy_load_buffer_state( yyscanner ); } /** Switch to a different input buffer. * @param new_buffer The new input buffer. * @param yyscanner The scanner object. */ void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* TODO. We should be able to replace this entire function body * with * yypop_buffer_state(); * yypush_buffer_state(new_buffer); */ yyensure_buffer_stack (yyscanner); if ( YY_CURRENT_BUFFER == new_buffer ) return; if ( YY_CURRENT_BUFFER ) { /* Flush out information for old buffer. */ *yyg->yy_c_buf_p = yyg->yy_hold_char; YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; } YY_CURRENT_BUFFER_LVALUE = new_buffer; yy_load_buffer_state( yyscanner ); /* We don't actually know whether we did this switch during * EOF (yywrap()) processing, but the only time this flag * is looked at is after yywrap() is called, so it's safe * to go ahead and always set it. */ yyg->yy_did_buffer_switch_on_eof = 1; } static void yy_load_buffer_state (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; yyg->yy_hold_char = *yyg->yy_c_buf_p; } /** Allocate and initialize an input buffer state. * @param file A readable stream. * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. * @param yyscanner The scanner object. * @return the allocated buffer state. */ YY_BUFFER_STATE yy_create_buffer (FILE * file, int size , yyscan_t yyscanner) { YY_BUFFER_STATE b; b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); if ( ! b ) YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); b->yy_buf_size = size; /* yy_ch_buf has to be 2 characters longer than the size given because * we need to put in 2 end-of-buffer characters. */ b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); if ( ! b->yy_ch_buf ) YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); b->yy_is_our_buffer = 1; yy_init_buffer( b, file , yyscanner); return b; } /** Destroy the buffer. * @param b a buffer created with yy_create_buffer() * @param yyscanner The scanner object. */ void yy_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if ( ! b ) return; if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; if ( b->yy_is_our_buffer ) yyfree( (void *) b->yy_ch_buf , yyscanner ); yyfree( (void *) b , yyscanner ); } /* Initializes or reinitializes a buffer. * This function is sometimes called more than once on the same buffer, * such as during a yyrestart() or at EOF. */ static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) { int oerrno = errno; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yy_flush_buffer( b , yyscanner); b->yy_input_file = file; b->yy_fill_buffer = 1; /* If b is the current buffer, then yy_init_buffer was _probably_ * called from yyrestart() or through yy_get_next_buffer. * In that case, we don't want to reset the lineno or column. */ if (b != YY_CURRENT_BUFFER){ b->yy_bs_lineno = 1; b->yy_bs_column = 0; } b->yy_is_interactive = 0; errno = oerrno; } /** Discard all buffered characters. On the next scan, YY_INPUT will be called. * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. * @param yyscanner The scanner object. */ void yy_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if ( ! b ) return; b->yy_n_chars = 0; /* We always need two end-of-buffer characters. The first causes * a transition to the end-of-buffer state. The second causes * a jam in that state. */ b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; b->yy_buf_pos = &b->yy_ch_buf[0]; b->yy_at_bol = 1; b->yy_buffer_status = YY_BUFFER_NEW; if ( b == YY_CURRENT_BUFFER ) yy_load_buffer_state( yyscanner ); } /** Pushes the new state onto the stack. The new state becomes * the current state. This function will allocate the stack * if necessary. * @param new_buffer The new state. * @param yyscanner The scanner object. */ void yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if (new_buffer == NULL) return; yyensure_buffer_stack(yyscanner); /* This block is copied from yy_switch_to_buffer. */ if ( YY_CURRENT_BUFFER ) { /* Flush out information for old buffer. */ *yyg->yy_c_buf_p = yyg->yy_hold_char; YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; } /* Only push if top exists. Otherwise, replace top. */ if (YY_CURRENT_BUFFER) yyg->yy_buffer_stack_top++; YY_CURRENT_BUFFER_LVALUE = new_buffer; /* copied from yy_switch_to_buffer. */ yy_load_buffer_state( yyscanner ); yyg->yy_did_buffer_switch_on_eof = 1; } /** Removes and deletes the top of the stack, if present. * The next element becomes the new top. * @param yyscanner The scanner object. */ void yypop_buffer_state (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if (!YY_CURRENT_BUFFER) return; yy_delete_buffer(YY_CURRENT_BUFFER , yyscanner); YY_CURRENT_BUFFER_LVALUE = NULL; if (yyg->yy_buffer_stack_top > 0) --yyg->yy_buffer_stack_top; if (YY_CURRENT_BUFFER) { yy_load_buffer_state( yyscanner ); yyg->yy_did_buffer_switch_on_eof = 1; } } /* Allocates the stack if it does not exist. * Guarantees space for at least one push. */ static void yyensure_buffer_stack (yyscan_t yyscanner) { yy_size_t num_to_alloc; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if (!yyg->yy_buffer_stack) { /* First allocation is just for 2 elements, since we don't know if this * scanner will even need a stack. We use 2 instead of 1 to avoid an * immediate realloc on the next call. */ num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */ yyg->yy_buffer_stack = (struct yy_buffer_state**)yyalloc (num_to_alloc * sizeof(struct yy_buffer_state*) , yyscanner); if ( ! yyg->yy_buffer_stack ) YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*)); yyg->yy_buffer_stack_max = num_to_alloc; yyg->yy_buffer_stack_top = 0; return; } if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){ /* Increase the buffer to prepare for a possible push. */ yy_size_t grow_size = 8 /* arbitrary grow size */; num_to_alloc = yyg->yy_buffer_stack_max + grow_size; yyg->yy_buffer_stack = (struct yy_buffer_state**)yyrealloc (yyg->yy_buffer_stack, num_to_alloc * sizeof(struct yy_buffer_state*) , yyscanner); if ( ! yyg->yy_buffer_stack ) YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); /* zero only the new slots.*/ memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*)); yyg->yy_buffer_stack_max = num_to_alloc; } } /** Setup the input buffer state to scan directly from a user-specified character buffer. * @param base the character buffer * @param size the size in bytes of the character buffer * @param yyscanner The scanner object. * @return the newly allocated buffer state object. */ YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) { YY_BUFFER_STATE b; if ( size < 2 || base[size-2] != YY_END_OF_BUFFER_CHAR || base[size-1] != YY_END_OF_BUFFER_CHAR ) /* They forgot to leave room for the EOB's. */ return NULL; b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); if ( ! b ) YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */ b->yy_buf_pos = b->yy_ch_buf = base; b->yy_is_our_buffer = 0; b->yy_input_file = NULL; b->yy_n_chars = b->yy_buf_size; b->yy_is_interactive = 0; b->yy_at_bol = 1; b->yy_fill_buffer = 0; b->yy_buffer_status = YY_BUFFER_NEW; yy_switch_to_buffer( b , yyscanner ); return b; } /** Setup the input buffer state to scan a string. The next call to yylex() will * scan from a @e copy of @a str. * @param yystr a NUL-terminated string to scan * @param yyscanner The scanner object. * @return the newly allocated buffer state object. * @note If you want to scan bytes that may contain NUL values, then use * yy_scan_bytes() instead. */ YY_BUFFER_STATE yy_scan_string (const char * yystr , yyscan_t yyscanner) { return yy_scan_bytes( yystr, (int) strlen(yystr) , yyscanner); } /** Setup the input buffer state to scan the given bytes. The next call to yylex() will * scan from a @e copy of @a bytes. * @param yybytes the byte buffer to scan * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. * @param yyscanner The scanner object. * @return the newly allocated buffer state object. */ YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len , yyscan_t yyscanner) { YY_BUFFER_STATE b; char *buf; yy_size_t n; int i; /* Get memory for full buffer, including space for trailing EOB's. */ n = (yy_size_t) (_yybytes_len + 2); buf = (char *) yyalloc( n , yyscanner ); if ( ! buf ) YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); for ( i = 0; i < _yybytes_len; ++i ) buf[i] = yybytes[i]; buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; b = yy_scan_buffer( buf, n , yyscanner); if ( ! b ) YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); /* It's okay to grow etc. this buffer, and we should throw it * away when we're done. */ b->yy_is_our_buffer = 1; return b; } #ifndef YY_EXIT_FAILURE #define YY_EXIT_FAILURE 2 #endif static void yynoreturn yy_fatal_error (const char* msg , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; (void)yyg; fprintf( stderr, "%s\n", msg ); exit( YY_EXIT_FAILURE ); } /* Redefine yyless() so it works in section 3 code. */ #undef yyless #define yyless(n) \ do \ { \ /* Undo effects of setting up yytext. */ \ int yyless_macro_arg = (n); \ YY_LESS_LINENO(yyless_macro_arg);\ yytext[yyleng] = yyg->yy_hold_char; \ yyg->yy_c_buf_p = yytext + yyless_macro_arg; \ yyg->yy_hold_char = *yyg->yy_c_buf_p; \ *yyg->yy_c_buf_p = '\0'; \ yyleng = yyless_macro_arg; \ } \ while ( 0 ) /* Accessor methods (get/set functions) to struct members. */ /** Get the user-defined data for this scanner. * @param yyscanner The scanner object. */ YY_EXTRA_TYPE yyget_extra (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yyextra; } /** Get the current line number. * @param yyscanner The scanner object. */ int yyget_lineno (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if (! YY_CURRENT_BUFFER) return 0; return yylineno; } /** Get the current column number. * @param yyscanner The scanner object. */ int yyget_column (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if (! YY_CURRENT_BUFFER) return 0; return yycolumn; } /** Get the input stream. * @param yyscanner The scanner object. */ FILE *yyget_in (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yyin; } /** Get the output stream. * @param yyscanner The scanner object. */ FILE *yyget_out (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yyout; } /** Get the length of the current token. * @param yyscanner The scanner object. */ int yyget_leng (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yyleng; } /** Get the current token. * @param yyscanner The scanner object. */ char *yyget_text (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yytext; } /** Set the user-defined data. This data is never touched by the scanner. * @param user_defined The data to be associated with this scanner. * @param yyscanner The scanner object. */ void yyset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yyextra = user_defined ; } /** Set the current line number. * @param _line_number line number * @param yyscanner The scanner object. */ void yyset_lineno (int _line_number , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* lineno is only valid if an input buffer exists. */ if (! YY_CURRENT_BUFFER ) YY_FATAL_ERROR( "yyset_lineno called with no buffer" ); yylineno = _line_number; } /** Set the current column. * @param _column_no column number * @param yyscanner The scanner object. */ void yyset_column (int _column_no , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* column is only valid if an input buffer exists. */ if (! YY_CURRENT_BUFFER ) YY_FATAL_ERROR( "yyset_column called with no buffer" ); yycolumn = _column_no; } /** Set the input stream. This does not discard the current * input buffer. * @param _in_str A readable stream. * @param yyscanner The scanner object. * @see yy_switch_to_buffer */ void yyset_in (FILE * _in_str , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yyin = _in_str ; } void yyset_out (FILE * _out_str , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yyout = _out_str ; } int yyget_debug (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yy_flex_debug; } void yyset_debug (int _bdebug , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yy_flex_debug = _bdebug ; } /* Accessor methods for yylval and yylloc */ YYSTYPE * yyget_lval (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yylval; } void yyset_lval (YYSTYPE * yylval_param , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; yylval = yylval_param; } /* User-visible API */ /* yylex_init is special because it creates the scanner itself, so it is * the ONLY reentrant function that doesn't take the scanner as the last argument. * That's why we explicitly handle the declaration, instead of using our macros. */ int yylex_init(yyscan_t* ptr_yy_globals) { if (ptr_yy_globals == NULL){ errno = EINVAL; return 1; } *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), NULL ); if (*ptr_yy_globals == NULL){ errno = ENOMEM; return 1; } /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); return yy_init_globals ( *ptr_yy_globals ); } /* yylex_init_extra has the same functionality as yylex_init, but follows the * convention of taking the scanner as the last argument. Note however, that * this is a *pointer* to a scanner, as it will be allocated by this call (and * is the reason, too, why this function also must handle its own declaration). * The user defined value in the first argument will be available to yyalloc in * the yyextra field. */ int yylex_init_extra( YY_EXTRA_TYPE yy_user_defined, yyscan_t* ptr_yy_globals ) { struct yyguts_t dummy_yyguts; yyset_extra (yy_user_defined, &dummy_yyguts); if (ptr_yy_globals == NULL){ errno = EINVAL; return 1; } *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts ); if (*ptr_yy_globals == NULL){ errno = ENOMEM; return 1; } /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); yyset_extra (yy_user_defined, *ptr_yy_globals); return yy_init_globals ( *ptr_yy_globals ); } static int yy_init_globals (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* Initialization is the same as for the non-reentrant scanner. * This function is called from yylex_destroy(), so don't allocate here. */ yyg->yy_buffer_stack = NULL; yyg->yy_buffer_stack_top = 0; yyg->yy_buffer_stack_max = 0; yyg->yy_c_buf_p = NULL; yyg->yy_init = 0; yyg->yy_start = 0; yyg->yy_start_stack_ptr = 0; yyg->yy_start_stack_depth = 0; yyg->yy_start_stack = NULL; /* Defined in main.c */ #ifdef YY_STDINIT yyin = stdin; yyout = stdout; #else yyin = NULL; yyout = NULL; #endif /* For future reference: Set errno on error, since we are called by * yylex_init() */ return 0; } /* yylex_destroy is for both reentrant and non-reentrant scanners. */ int yylex_destroy (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* Pop the buffer stack, destroying each element. */ while(YY_CURRENT_BUFFER){ yy_delete_buffer( YY_CURRENT_BUFFER , yyscanner ); YY_CURRENT_BUFFER_LVALUE = NULL; yypop_buffer_state(yyscanner); } /* Destroy the stack itself. */ yyfree(yyg->yy_buffer_stack , yyscanner); yyg->yy_buffer_stack = NULL; /* Destroy the start condition stack. */ yyfree( yyg->yy_start_stack , yyscanner ); yyg->yy_start_stack = NULL; /* Reset the globals. This is important in a non-reentrant scanner so the next time * yylex() is called, initialization will occur. */ yy_init_globals( yyscanner); /* Destroy the main struct (reentrant only). */ yyfree ( yyscanner , yyscanner ); yyscanner = NULL; return 0; } /* * Internal utility routines. */ #ifndef yytext_ptr static void yy_flex_strncpy (char* s1, const char * s2, int n , yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; (void)yyg; int i; for ( i = 0; i < n; ++i ) s1[i] = s2[i]; } #endif #ifdef YY_NEED_STRLEN static int yy_flex_strlen (const char * s , yyscan_t yyscanner) { int n; for ( n = 0; s[n]; ++n ) ; return n; } #endif #define YYTABLES_NAME "yytables" #line 96 "event-filter-lexer.l" #pragma GCC diagnostic pop void *yyalloc(size_t bytes, void* yyscanner ATTR_UNUSED) { void *ptr = calloc(1, bytes); if (ptr == NULL) i_fatal_status(FATAL_OUTOFMEM, "calloc(1, %zu): Out of memory", bytes); return ptr; } void *yyrealloc (void *ptr, size_t bytes, void *yyscanner ATTR_UNUSED) { void *nptr = realloc(ptr, bytes); if (nptr == NULL) i_fatal_status(FATAL_OUTOFMEM, "realloc(ptr, %zu): Out of memory", bytes); return nptr; } void yyfree(void *ptr, void *yyscanner ATTR_UNUSED) { if (ptr == NULL) return; free(ptr); } static size_t event_filter_parser_input_proc(char *buf, size_t size, yyscan_t scanner) { struct event_filter_parser_state *state; size_t num_bytes; state = event_filter_parser_get_extra(scanner); if (state->len == state->pos) return 0; i_assert(state->len > state->pos); num_bytes = I_MIN(state->len - state->pos, size); memcpy(buf, state->input + state->pos, num_bytes); state->pos += num_bytes; return num_bytes; } dovecot-2.3.21.1/src/lib/home-expand.h0000644000000000000000000000070114656633576014226 00000000000000#ifndef HOME_EXPAND_H #define HOME_EXPAND_H /* expand ~/ or ~user/ in beginning of path. If user is unknown, the original path is returned without modification. */ const char *home_expand(const char *path); /* Returns 0 if ok, -1 if user wasn't found. */ int home_try_expand(const char **path); /* Expand ~/ in the beginning of the path with the give home directory. */ const char *home_expand_tilde(const char *path, const char *home); #endif dovecot-2.3.21.1/src/lib/test-random.c0000644000000000000000000000304214656633576014252 00000000000000/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "stats-dist.h" #include "randgen.h" #include #define TEST_RAND_SIZE_MEDIAN 100000.0 static void test_random_median(void) { uint64_t tmp; double median, average; struct stats_dist *s = stats_dist_init_with_size(TEST_RAND_SIZE_MEDIAN); test_begin("test_random (median & average)"); for(unsigned int i = 0; i < TEST_RAND_SIZE_MEDIAN; i++) { uint64_t value; value = i_rand_limit(TEST_RAND_SIZE_MEDIAN); stats_dist_add(s, value); } tmp = stats_dist_get_median(s); /* median should be 0.5 +-2% */ median = (double)tmp / TEST_RAND_SIZE_MEDIAN; test_assert(fabs(median - 0.5) < 0.01); /* average should be 0.5 +- %2 */ average = stats_dist_get_avg(s) / TEST_RAND_SIZE_MEDIAN; test_assert(fabs(average - 0.5) < 0.01); stats_dist_deinit(&s); test_end(); } static void test_random_limits(void) { test_begin("random limits"); test_assert(i_rand_limit(1) == 0); test_assert(i_rand_minmax(0, 0) == 0); test_assert(i_rand_minmax(UINT32_MAX, UINT32_MAX) == UINT32_MAX); test_end(); } void test_random(void) { test_random_median(); test_random_limits(); } enum fatal_test_state fatal_random(unsigned int stage) { switch (stage) { case 0: test_begin("random fatals"); test_expect_fatal_string("min_val <= max_val"); (void)i_rand_minmax(1, 0); return FATAL_TEST_FAILURE; case 1: test_expect_fatal_string("upper_bound > 0"); (void)i_rand_limit(0); return FATAL_TEST_FAILURE; } test_end(); return FATAL_TEST_FINISHED; } dovecot-2.3.21.1/src/lib/strescape.c0000644000000000000000000001525514656633576014017 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "strescape.h" const char *str_nescape(const void *str, size_t len) { string_t *dest = t_str_new(len*2); str_append_escaped(dest, str, len); return str_c(dest); } void str_append_escaped(string_t *dest, const void *src, size_t src_size) { const unsigned char *pstart = src, *p = src, *pend = pstart + src_size; /* see if we need to quote it */ for (; p < pend; p++) { if (IS_ESCAPED_CHAR(*p)) break; } /* quote */ str_append_data(dest, pstart, (size_t)(p - pstart)); for (; p < pend; p++) { if (IS_ESCAPED_CHAR(*p)) str_append_c(dest, '\\'); str_append_data(dest, p, 1); } } void str_append_unescaped(string_t *dest, const void *src, size_t src_size) { const unsigned char *src_c = src; size_t start = 0, i = 0; while (i < src_size) { for (; i < src_size; i++) { if (src_c[i] == '\\') break; } str_append_data(dest, src_c + start, i-start); if (i < src_size) { if (++i == src_size) break; str_append_c(dest, src_c[i++]); } start = i; } } char *str_unescape(char *str) { /* @UNSAFE */ char *dest, *start = str; while (*str != '\\') { if (*str == '\0') return start; str++; } for (dest = str; *str != '\0'; str++) { if (*str == '\\') { str++; if (*str == '\0') break; } *dest++ = *str; } *dest = '\0'; return start; } int str_unescape_next(const char **str, const char **unescaped_r) { const char *p; char *escaped; bool esc_found = FALSE; for (p = *str; *p != '\0'; p++) { if (*p == '"') break; else if (*p == '\\') { if (p[1] == '\0') return -1; esc_found = TRUE; p++; } } if (*p != '"') return -1; escaped = p_strdup_until(unsafe_data_stack_pool, *str, p); *str = p+1; *unescaped_r = !esc_found ? escaped : str_unescape(escaped); return 0; } void str_append_tabescaped_n(string_t *dest, const unsigned char *src, size_t src_size) { size_t prev_pos = 0; char esc[2] = { '\001', '\0' }; for (size_t i = 0; i < src_size; i++) { switch (src[i]) { case '\000': esc[1] = '0'; break; case '\001': esc[1] = '1'; break; case '\t': esc[1] = 't'; break; case '\r': esc[1] = 'r'; break; case '\n': esc[1] = 'n'; break; default: continue; } str_append_data(dest, src + prev_pos, i - prev_pos); str_append_data(dest, esc, 2); prev_pos = i + 1; } str_append_data(dest, src + prev_pos, src_size - prev_pos); } void str_append_tabescaped(string_t *dest, const char *src) { size_t pos, prev_pos = 0; char esc[2] = { '\001', '\0' }; for (;;) { pos = prev_pos + strcspn(src + prev_pos, "\001\t\r\n"); str_append_data(dest, src + prev_pos, pos - prev_pos); prev_pos = pos + 1; switch (src[pos]) { case '\000': /* end of src string reached */ return; case '\001': esc[1] = '1'; break; case '\t': esc[1] = 't'; break; case '\r': esc[1] = 'r'; break; case '\n': esc[1] = 'n'; break; default: i_unreached(); } str_append_data(dest, esc, 2); } } const char *str_tabescape(const char *str) { string_t *tmp; const char *p; if ((p = strpbrk(str, "\001\t\r\n")) != NULL) { tmp = t_str_new(128); str_append_data(tmp, str, p-str); str_append_tabescaped(tmp, p); return str_c(tmp); } return str; } void str_append_tabunescaped(string_t *dest, const void *src, size_t src_size) { const unsigned char *src_c = src; size_t start = 0, i = 0; while (i < src_size) { for (; i < src_size; i++) { if (src_c[i] == '\001') break; } str_append_data(dest, src_c + start, i-start); if (i < src_size) { i++; if (i < src_size) { switch (src_c[i]) { case '0': str_append_c(dest, '\000'); break; case '1': str_append_c(dest, '\001'); break; case 't': str_append_c(dest, '\t'); break; case 'r': str_append_c(dest, '\r'); break; case 'n': str_append_c(dest, '\n'); break; default: str_append_c(dest, src_c[i]); break; } i++; } } start = i; } } static char *str_tabunescape_from(char *str, char *src) { /* @UNSAFE */ char *dest, *p; dest = src; for (;;) { switch (src[1]) { case '\0': /* truncated input */ *dest = '\0'; return str; case '0': *dest++ = '\000'; break; case '1': *dest++ = '\001'; break; case 't': *dest++ = '\t'; break; case 'r': *dest++ = '\r'; break; case 'n': *dest++ = '\n'; break; default: *dest++ = src[1]; break; } src += 2; p = strchr(src, '\001'); if (p == NULL) { memmove(dest, src, strlen(src)+1); break; } size_t copy_len = p - src; memmove(dest, src, copy_len); dest += copy_len; src = p; } return str; } char *str_tabunescape(char *str) { char *src = strchr(str, '\001'); if (src == NULL) { /* no unescaping needed */ return str; } return str_tabunescape_from(str, src); } const char *t_str_tabunescape(const char *str) { const char *p; p = strchr(str, '\001'); if (p == NULL) return str; char *dest = t_strdup_noconst(str); return str_tabunescape_from(dest, dest + (p - str)); } static char **p_strsplit_tabescaped_inplace(pool_t pool, char *data) { /* @UNSAFE */ char **array; unsigned int count, new_alloc_count, alloc_count; if (*data == '\0') return p_new(pool, char *, 1); alloc_count = 32; array = pool == unsafe_data_stack_pool ? t_malloc_no0(sizeof(char *) * alloc_count) : p_malloc(pool, sizeof(char *) * alloc_count); array[0] = data; count = 1; char *need_unescape = NULL; while ((data = strpbrk(data, "\t\001")) != NULL) { /* separator or escape char found */ if (*data == '\001') { if (need_unescape == NULL) need_unescape = data; data++; continue; } if (count+1 >= alloc_count) { new_alloc_count = nearest_power(alloc_count+1); array = p_realloc(pool, array, sizeof(char *) * alloc_count, sizeof(char *) * new_alloc_count); alloc_count = new_alloc_count; } *data++ = '\0'; if (need_unescape != NULL) { str_tabunescape_from(array[count-1], need_unescape); need_unescape = NULL; } array[count++] = data; } if (need_unescape != NULL) str_tabunescape_from(array[count-1], need_unescape); i_assert(count < alloc_count); array[count] = NULL; return array; } const char *const *t_strsplit_tabescaped_inplace(char *data) { char *const *escaped = p_strsplit_tabescaped_inplace(unsafe_data_stack_pool, data); return (const char *const *)escaped; } char **p_strsplit_tabescaped(pool_t pool, const char *str) { return p_strsplit_tabescaped_inplace(pool, p_strdup(pool, str)); } const char *const *t_strsplit_tabescaped(const char *str) { return t_strsplit_tabescaped_inplace(t_strdup_noconst(str)); } dovecot-2.3.21.1/src/lib/iostream-rawlog.c0000644000000000000000000001724214656633576015140 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "hostpid.h" #include "ioloop.h" #include "buffer.h" #include "str.h" #include "net.h" #include "write-full.h" #include "time-util.h" #include "istream.h" #include "ostream.h" #include "iostream-private.h" #include "iostream-rawlog-private.h" #include "istream-rawlog.h" #include "ostream-rawlog.h" #include "iostream-rawlog.h" #include #include #define RAWLOG_MAX_LINE_LEN 8192 static void rawlog_write_timestamp(struct rawlog_iostream *rstream, bool line_ends) { unsigned char data[MAX_INT_STRLEN + 1 + 6 + 1 + 3]; buffer_t buf; if ((rstream->flags & IOSTREAM_RAWLOG_FLAG_TIMESTAMP) == 0) return; buffer_create_from_data(&buf, data, sizeof(data)); str_printfa(&buf, "%"PRIdTIME_T".%06u ", ioloop_timeval.tv_sec, (unsigned int)ioloop_timeval.tv_usec); if ((rstream->flags & IOSTREAM_RAWLOG_FLAG_BUFFERED) != 0) { str_append_c(&buf, rstream->input ? 'I' : 'O'); str_append_c(&buf, line_ends ? ':' : '>'); str_append_c(&buf, ' '); } o_stream_nsend(rstream->rawlog_output, buf.data, buf.used); } void iostream_rawlog_init(struct rawlog_iostream *rstream, enum iostream_rawlog_flags flags, bool input) { rstream->flags = flags; rstream->input = input; if ((rstream->flags & IOSTREAM_RAWLOG_FLAG_BUFFERED) != 0) rstream->buffer = buffer_create_dynamic(default_pool, 1024); } static void iostream_rawlog_write_buffered(struct rawlog_iostream *rstream, const unsigned char *data, size_t size) { const unsigned char *p; size_t pos; bool line_ends; while (size > 0) { p = memchr(data, '\n', size); if (p != NULL) { line_ends = TRUE; pos = p-data + 1; } else if (rstream->buffer->used + size < RAWLOG_MAX_LINE_LEN) { buffer_append(rstream->buffer, data, size); break; } else { line_ends = FALSE; pos = size; } rawlog_write_timestamp(rstream, line_ends); if (rstream->buffer->used > 0) { o_stream_nsend(rstream->rawlog_output, rstream->buffer->data, rstream->buffer->used); buffer_set_used_size(rstream->buffer, 0); } o_stream_nsend(rstream->rawlog_output, data, pos); data += pos; size -= pos; } } static void iostream_rawlog_write_unbuffered(struct rawlog_iostream *rstream, const unsigned char *data, size_t size) { size_t i, start; if (!rstream->line_continued) rawlog_write_timestamp(rstream, TRUE); for (start = 0, i = 1; i < size; i++) { if (data[i-1] == '\n') { o_stream_nsend(rstream->rawlog_output, data + start, i - start); rawlog_write_timestamp(rstream, TRUE); start = i; } } if (start != size) { o_stream_nsend(rstream->rawlog_output, data + start, size - start); } rstream->line_continued = data[size-1] != '\n'; } void iostream_rawlog_write(struct rawlog_iostream *rstream, const unsigned char *data, size_t size) { if (size == 0 || rstream->rawlog_output == NULL) return; io_loop_time_refresh(); o_stream_cork(rstream->rawlog_output); if ((rstream->flags & IOSTREAM_RAWLOG_FLAG_BUFFERED) != 0) iostream_rawlog_write_buffered(rstream, data, size); else iostream_rawlog_write_unbuffered(rstream, data, size); o_stream_uncork(rstream->rawlog_output); if (o_stream_flush(rstream->rawlog_output) < 0) { i_error("write(%s) failed: %s", o_stream_get_name(rstream->rawlog_output), o_stream_get_error(rstream->rawlog_output)); iostream_rawlog_close(rstream); } } void iostream_rawlog_close(struct rawlog_iostream *rstream) { o_stream_unref(&rstream->rawlog_output); buffer_free(&rstream->buffer); } static void iostream_rawlog_create_fd(int fd, const char *path, struct istream **input, struct ostream **output) { struct istream *old_input; struct ostream *old_output; old_input = *input; old_output = *output; *input = i_stream_create_rawlog(old_input, path, fd, IOSTREAM_RAWLOG_FLAG_BUFFERED | IOSTREAM_RAWLOG_FLAG_TIMESTAMP); *output = o_stream_create_rawlog(old_output, path, fd, IOSTREAM_RAWLOG_FLAG_AUTOCLOSE | IOSTREAM_RAWLOG_FLAG_BUFFERED | IOSTREAM_RAWLOG_FLAG_TIMESTAMP); i_stream_unref(&old_input); o_stream_unref(&old_output); } static int iostream_rawlog_try_create_tcp(const char *path, struct istream **input, struct ostream **output) { const char *host; struct ip_addr *ips; unsigned int ips_count; in_port_t port; int ret, fd; /* tcp:host:port */ if (!str_begins(path, "tcp:")) return 0; path += 4; if (strchr(path, '/') != NULL) return 0; if (net_str2hostport(path, 0, &host, &port) < 0 || port == 0) return 0; ret = net_gethostbyname(host, &ips, &ips_count); if (ret != 0) { i_error("net_gethostbyname(%s) failed: %s", host, net_gethosterror(ret)); return -1; } fd = net_connect_ip_blocking(&ips[0], port, NULL); if (fd == -1) { i_error("connect(%s:%u) failed: %m", net_ip2addr(&ips[0]), port); return -1; } iostream_rawlog_create_fd(fd, path, input, output); return 1; } int iostream_rawlog_create(const char *dir, struct istream **input, struct ostream **output) { static unsigned int counter = 0; const char *timestamp, *prefix; struct stat st; int ret; if ((ret = iostream_rawlog_try_create_tcp(dir, input, output)) != 0) return ret < 0 ? -1 : 0; if (stat(dir, &st) < 0) { if (errno != ENOENT && errno != EACCES) i_error("rawlog: stat(%s) failed: %m", dir); return -1; } timestamp = t_strflocaltime("%Y%m%d-%H%M%S", ioloop_time); counter++; prefix = t_strdup_printf("%s/%s.%s.%u", dir, timestamp, my_pid, counter); return iostream_rawlog_create_prefix(prefix, input, output); } int iostream_rawlog_create_prefix(const char *prefix, struct istream **input, struct ostream **output) { const char *in_path, *out_path; struct istream *old_input; struct ostream *old_output; int in_fd, out_fd; in_path = t_strdup_printf("%s.in", prefix); in_fd = open(in_path, O_CREAT | O_APPEND | O_WRONLY, 0600); if (in_fd == -1) { i_error("creat(%s) failed: %m", in_path); return -1; } out_path = t_strdup_printf("%s.out", prefix); out_fd = open(out_path, O_CREAT | O_APPEND | O_WRONLY, 0600); if (out_fd == -1) { i_error("creat(%s) failed: %m", out_path); i_close_fd(&in_fd); i_unlink(in_path); return -1; } old_input = *input; old_output = *output; *input = i_stream_create_rawlog(old_input, in_path, in_fd, IOSTREAM_RAWLOG_FLAG_AUTOCLOSE | IOSTREAM_RAWLOG_FLAG_TIMESTAMP); *output = o_stream_create_rawlog(old_output, out_path, out_fd, IOSTREAM_RAWLOG_FLAG_AUTOCLOSE | IOSTREAM_RAWLOG_FLAG_TIMESTAMP); i_stream_unref(&old_input); o_stream_unref(&old_output); return 0; } int iostream_rawlog_create_path(const char *path, struct istream **input, struct ostream **output) { int ret, fd; if ((ret = iostream_rawlog_try_create_tcp(path, input, output)) != 0) return ret < 0 ? -1 : 0; fd = open(path, O_CREAT | O_APPEND | O_WRONLY, 0600); if (fd == -1) { i_error("creat(%s) failed: %m", path); return -1; } iostream_rawlog_create_fd(fd, path, input, output); return 0; } void iostream_rawlog_create_from_stream(struct ostream *rawlog_output, struct istream **input, struct ostream **output) { const enum iostream_rawlog_flags rawlog_flags = IOSTREAM_RAWLOG_FLAG_BUFFERED | IOSTREAM_RAWLOG_FLAG_TIMESTAMP; struct istream *old_input; struct ostream *old_output; if (input != NULL) { old_input = *input; *input = i_stream_create_rawlog_from_stream(old_input, rawlog_output, rawlog_flags); i_stream_unref(&old_input); } if (output != NULL) { old_output = *output; *output = o_stream_create_rawlog_from_stream(old_output, rawlog_output, rawlog_flags); o_stream_unref(&old_output); } if (input != NULL && output != NULL) o_stream_ref(rawlog_output); } dovecot-2.3.21.1/src/lib/base32.c0000644000000000000000000002237214656633576013103 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "base32.h" #include "buffer.h" static const char b32enc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; static const char b32hexenc[] = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; static const unsigned char b32dec[256] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0-7 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 8-15 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 16-23 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 24-31 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 32-39 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 40-47 */ 0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, /* 48-55 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 56-63 */ 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* 64-71 */ 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, /* 72-79 */ 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, /* 80-87 */ 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, /* 88-95 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 96-103 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 104-111 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 112-119 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 120-127 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 128-255 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }; static const unsigned char b32hexdec[256] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0-7 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 8-15 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 16-23 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 24-31 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 32-39 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 40-47 */ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* 48-55 */ 0x08, 0x09, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 56-63 */ 0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, /* 64-71 */ 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, /* 72-79 */ 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0xff, /* 80-87 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 88-95 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 96-103 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 104-111 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 112-119 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 120-127 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 128-255 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }; static void base32_encode_with_alphabet(const char *alph, bool pad, const void *src, size_t src_size, buffer_t *dest) { const unsigned char *src_c = src; unsigned char tmp[8], endb; size_t src_pos; /* [5 3][2 5 1][4 4][1 5 2][3 5] (5)(3 2)(5)(1 4)(4 1)(5)(2 3)(5) */ /* encode main part */ for (src_pos = 0; src_pos + 4 < src_size; src_pos += 5) { tmp[0] = alph[src_c[src_pos] >> 3]; tmp[1] = alph[((src_c[src_pos] & 0x07) << 2) | ((src_c[src_pos+1] >> 6) & 0x03)]; tmp[2] = alph[((src_c[src_pos+1] >> 1) & 0x1f)]; tmp[3] = alph[((src_c[src_pos+1] & 0x01) << 4) | (src_c[src_pos+2] >> 4)]; tmp[4] = alph[((src_c[src_pos+2] & 0x0f) << 1) | (src_c[src_pos+3] >> 7)]; tmp[5] = alph[((src_c[src_pos+3] >> 2) & 0x1f)]; tmp[6] = alph[((src_c[src_pos+3] & 0x03) << 3) | (src_c[src_pos+4] >> 5)]; tmp[7] = alph[src_c[src_pos+4] & 0x1f]; buffer_append(dest, tmp, 8); } /* encode last < 5 bytes if any */ if (src_pos < src_size) { tmp[0] = alph[src_c[src_pos] >> 3]; switch (src_size - src_pos) { case 1: tmp[1] = alph[((src_c[src_pos] & 0x07) << 2)]; endb = 2; break; case 2: tmp[1] = alph[((src_c[src_pos] & 0x07) << 2) | ((src_c[src_pos+1] >> 6) & 0x03)]; tmp[2] = alph[((src_c[src_pos+1] >> 1) & 0x1f)]; tmp[3] = alph[((src_c[src_pos+1] & 0x01) << 4)]; endb = 4; break; case 3: tmp[1] = alph[((src_c[src_pos] & 0x07) << 2) | ((src_c[src_pos+1] >> 6) & 0x03)]; tmp[2] = alph[((src_c[src_pos+1] >> 1) & 0x1f)]; tmp[3] = alph[((src_c[src_pos+1] & 0x01) << 4) | (src_c[src_pos+2] >> 4)]; tmp[4] = alph[((src_c[src_pos+2] & 0x0f) << 1)]; endb = 5; break; case 4: tmp[1] = alph[((src_c[src_pos] & 0x07) << 2) | ((src_c[src_pos+1] >> 6) & 0x03)]; tmp[2] = alph[((src_c[src_pos+1] >> 1) & 0x1f)]; tmp[3] = alph[((src_c[src_pos+1] & 0x01) << 4) | (src_c[src_pos+2] >> 4)]; tmp[4] = alph[((src_c[src_pos+2] & 0x0f) << 1) | (src_c[src_pos+3] >> 7)]; tmp[5] = alph[((src_c[src_pos+3] >> 2) & 0x1f)]; tmp[6] = alph[((src_c[src_pos+3] & 0x03) << 3)]; endb = 7; break; default: i_unreached(); } /* add padding if required */ if (pad) { memset(&tmp[endb], '=', sizeof(tmp)-endb); buffer_append(dest, tmp, 8); } else { buffer_append(dest, tmp, endb); } } } void base32_encode(bool pad, const void *src, size_t src_size, buffer_t *dest) { base32_encode_with_alphabet(b32enc, pad, src, src_size, dest); } void base32hex_encode(bool pad, const void *src, size_t src_size, buffer_t *dest) { base32_encode_with_alphabet(b32hexenc, pad, src, src_size, dest); } #define IS_EMPTY(c) \ ((c) == '\n' || (c) == '\r' || (c) == ' ' || (c) == '\t') static int base32_decode_with_alphabet(const unsigned char *alph, const void *src, size_t src_size, size_t *src_pos_r, buffer_t *dest) { const unsigned char *src_c = src; size_t block_pos, src_pos; unsigned char output[5], ipos, opos; int ret = 1; /* (5)(3 2)(5)(1 4)(4 1)(5)(2 3)(5) [5 3][2 5 1][4 4][1 5 2][3 5] */ ipos = opos = 0; block_pos = 0; for (src_pos = 0; src_pos < src_size; src_pos++) { unsigned char input = alph[src_c[src_pos]]; if (input == 0xff) { if (unlikely(!IS_EMPTY(src_c[src_pos]))) break; continue; } ipos++; switch (ipos) { case 1: output[0] = input << 3; opos = 0; break; case 2: output[0] |= input >> 2; output[1] = (input & 0x03) << 6; opos = 1; break; case 3: output[1] |= input << 1; opos = 1; break; case 4: output[1] |= input >> 4; output[2] = (input & 0x0f) << 4; opos = 2; break; case 5: output[2] |= input >> 1; output[3] = (input & 0x01) << 7; opos = 3; break; case 6: output[3] |= input << 2; opos = 3; break; case 7: output[3] |= input >> 3; output[4] = ((input & 0x07) << 5); opos = 4; break; case 8: output[4] |= input; buffer_append(dest, output, 5); ipos = 0; opos = 0; block_pos = src_pos; break; default: i_unreached(); } } if (ipos > 0) { for (; src_pos < src_size; src_pos++) { if (src_c[src_pos] != '=') { if (unlikely(!IS_EMPTY(src_c[src_pos]))) { ret = -1; break; } continue; } if (++ipos >= 8) { buffer_append(dest, output, opos); ipos = 0; ret = 0; src_pos++; break; } } } if (src_pos_r != NULL) { if (ipos == 0) { for (; src_pos < src_size; src_pos++) { if (!IS_EMPTY(src_c[src_pos])) break; } *src_pos_r = src_pos; } else { *src_pos_r = block_pos; } } return ret; } int base32_decode(const void *src, size_t src_size, size_t *src_pos_r, buffer_t *dest) { return base32_decode_with_alphabet (b32dec, src, src_size, src_pos_r, dest); } int base32hex_decode(const void *src, size_t src_size, size_t *src_pos_r, buffer_t *dest) { return base32_decode_with_alphabet (b32hexdec, src, src_size, src_pos_r, dest); } buffer_t *t_base32_decode_str(const char *str) { buffer_t *buf; size_t len = strlen(str); buf = t_buffer_create(MAX_BASE32_DECODED_SIZE(len)); (void)base32_decode(str, len, NULL, buf); return buf; } buffer_t *t_base32hex_decode_str(const char *str) { buffer_t *buf; size_t len = strlen(str); buf = t_buffer_create(MAX_BASE32_DECODED_SIZE(len)); (void)base32hex_decode(str, len, NULL, buf); return buf; } bool base32_is_valid_char(char c) { return b32dec[(uint8_t)c] != 0xff; } bool base32hex_is_valid_char(char c) { return b32hexdec[(uint8_t)c] != 0xff; } dovecot-2.3.21.1/src/lib/istream-multiplex.h0000644000000000000000000000045214656633576015511 00000000000000#ifndef ISTREAM_MULTIPLEX #define ISTREAM_MULTIPLEX 1 struct istream *i_stream_create_multiplex(struct istream *parent, size_t bufsize); struct istream *i_stream_multiplex_add_channel(struct istream *stream, uint8_t cid); uint8_t i_stream_multiplex_get_channel_id(struct istream *stream); #endif dovecot-2.3.21.1/src/lib/istream-limit.c0000644000000000000000000000746114656633576014606 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "istream-private.h" struct limit_istream { struct istream_private istream; uoff_t v_size; }; static void i_stream_limit_destroy(struct iostream_private *stream) { struct limit_istream *lstream = container_of(stream, struct limit_istream, istream.iostream); uoff_t v_offset; v_offset = lstream->istream.parent_start_offset + lstream->istream.istream.v_offset; if (lstream->istream.parent->seekable || v_offset > lstream->istream.parent->v_offset) { /* get to same position in parent stream */ i_stream_seek(lstream->istream.parent, v_offset); } } static ssize_t i_stream_limit_read(struct istream_private *stream) { struct limit_istream *lstream = container_of(stream, struct limit_istream, istream); uoff_t left; ssize_t ret; size_t pos; i_stream_seek(stream->parent, lstream->istream.parent_start_offset + stream->istream.v_offset); if (stream->istream.v_offset + (stream->pos - stream->skip) >= lstream->v_size) { stream->istream.eof = TRUE; return -1; } stream->pos -= stream->skip; stream->skip = 0; stream->buffer = i_stream_get_data(stream->parent, &pos); if (pos > stream->pos) ret = 0; else do { ret = i_stream_read_memarea(stream->parent); stream->istream.stream_errno = stream->parent->stream_errno; stream->istream.eof = stream->parent->eof; stream->buffer = i_stream_get_data(stream->parent, &pos); } while (pos <= stream->pos && ret > 0); if (ret == -2) return -2; if (lstream->v_size != UOFF_T_MAX) { left = lstream->v_size - stream->istream.v_offset; if (pos >= left) { pos = left; stream->istream.eof = TRUE; } } ret = pos > stream->pos ? (ssize_t)(pos - stream->pos) : (ret == 0 ? 0 : -1); stream->pos = pos; i_assert(ret != -1 || stream->istream.eof || stream->istream.stream_errno != 0); return ret; } static int i_stream_limit_stat(struct istream_private *stream, bool exact) { struct limit_istream *lstream = container_of(stream, struct limit_istream, istream); const struct stat *st; if (i_stream_stat(stream->parent, exact, &st) < 0) { stream->istream.stream_errno = stream->parent->stream_errno; return -1; } stream->statbuf = *st; if (lstream->v_size != UOFF_T_MAX) stream->statbuf.st_size = lstream->v_size; return 0; } static int i_stream_limit_get_size(struct istream_private *stream, bool exact, uoff_t *size_r) { struct limit_istream *lstream = container_of(stream, struct limit_istream, istream); const struct stat *st; if (lstream->v_size != UOFF_T_MAX) { *size_r = lstream->v_size; return 1; } if (i_stream_stat(&stream->istream, exact, &st) < 0) return -1; if (st->st_size == -1) return 0; *size_r = st->st_size; return 1; } struct istream *i_stream_create_limit(struct istream *input, uoff_t v_size) { struct limit_istream *lstream; lstream = i_new(struct limit_istream, 1); lstream->v_size = v_size; lstream->istream.max_buffer_size = input->real_stream->max_buffer_size; lstream->istream.iostream.destroy = i_stream_limit_destroy; lstream->istream.read = i_stream_limit_read; lstream->istream.stat = i_stream_limit_stat; lstream->istream.get_size = i_stream_limit_get_size; lstream->istream.istream.readable_fd = input->readable_fd; lstream->istream.istream.blocking = input->blocking; lstream->istream.istream.seekable = input->seekable; return i_stream_create(&lstream->istream, input, i_stream_get_fd(input), 0); } struct istream *i_stream_create_range(struct istream *input, uoff_t v_offset, uoff_t v_size) { uoff_t orig_offset = input->v_offset; struct istream *ret; input->v_offset = v_offset; ret = i_stream_create_limit(input, v_size); input->v_offset = orig_offset; return ret; } dovecot-2.3.21.1/src/lib/md4.c0000644000000000000000000001703314656633576012506 00000000000000/* * MD4 (RFC-1320) message digest. * Modified from MD5 code by Andrey Panin * * Written by Solar Designer in 2001, and placed in * the public domain. There's absolutely no warranty. * * This differs from Colin Plumb's older public domain implementation in * that no 32-bit integer data type is required, there's no compile-time * endianness configuration, and the function prototypes match OpenSSL's. * The primary goals are portability and ease of use. * * This implementation is meant to be fast, but not as fast as possible. * Some known optimizations are not included to reduce source code size * and avoid compile-time configuration. */ #include "lib.h" #include "safe-memset.h" #include "md4.h" /* * The basic MD4 functions. */ #define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) #define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) #define H(x, y, z) ((x) ^ (y) ^ (z)) /* * The MD4 transformation for all four rounds. */ #define STEP(f, a, b, c, d, x, s) \ (a) += f((b), (c), (d)) + (x); \ (a) = ((a) << (s)) | ((a) >> (32 - (s))) /* * SET reads 4 input bytes in little-endian byte order and stores them * in a properly aligned word in host byte order. * * The check for little-endian architectures which tolerate unaligned * memory accesses is just an optimization. Nothing will break if it * doesn't work. */ #if defined(__i386__) || defined(__x86_64__) || defined(__vax__) /* uint_fast32_t might be 64 bit, and thus may read 4 more bytes * beyond the end of the buffer. So only read precisely 32 bits */ #define SET(n) \ (*(const uint32_t *)&ptr[(n) * 4]) #define GET(n) \ SET(n) #else #define SET(n) \ (ctx->block[(n)] = \ (uint_fast32_t)ptr[(n) * 4] | \ ((uint_fast32_t)ptr[(n) * 4 + 1] << 8) | \ ((uint_fast32_t)ptr[(n) * 4 + 2] << 16) | \ ((uint_fast32_t)ptr[(n) * 4 + 3] << 24)) #define GET(n) \ (ctx->block[(n)]) #endif /* * This processes one or more 64-byte data blocks, but does NOT update * the bit counters. There're no alignment requirements. */ static const void * ATTR_NOWARN_UNUSED_RESULT ATTR_UNSIGNED_WRAPS ATTR_NO_SANITIZE_UNDEFINED ATTR_NO_SANITIZE_INTEGER ATTR_NO_SANITIZE_IMPLICIT_CONVERSION body(struct md4_context *ctx, const void *data, size_t size) { const unsigned char *ptr; uint32_t a, b, c, d; uint32_t saved_a, saved_b, saved_c, saved_d; ptr = data; a = ctx->a; b = ctx->b; c = ctx->c; d = ctx->d; do { saved_a = a; saved_b = b; saved_c = c; saved_d = d; /* Round 1 */ STEP(F, a, b, c, d, SET( 0), 3); STEP(F, d, a, b, c, SET( 1), 7); STEP(F, c, d, a, b, SET( 2), 11); STEP(F, b, c, d, a, SET( 3), 19); STEP(F, a, b, c, d, SET( 4), 3); STEP(F, d, a, b, c, SET( 5), 7); STEP(F, c, d, a, b, SET( 6), 11); STEP(F, b, c, d, a, SET( 7), 19); STEP(F, a, b, c, d, SET( 8), 3); STEP(F, d, a, b, c, SET( 9), 7); STEP(F, c, d, a, b, SET(10), 11); STEP(F, b, c, d, a, SET(11), 19); STEP(F, a, b, c, d, SET(12), 3); STEP(F, d, a, b, c, SET(13), 7); STEP(F, c, d, a, b, SET(14), 11); STEP(F, b, c, d, a, SET(15), 19); /* Round 2 */ STEP(G, a, b, c, d, GET( 0) + 0x5A827999, 3); STEP(G, d, a, b, c, GET( 4) + 0x5A827999, 5); STEP(G, c, d, a, b, GET( 8) + 0x5A827999, 9); STEP(G, b, c, d, a, GET(12) + 0x5A827999, 13); STEP(G, a, b, c, d, GET( 1) + 0x5A827999, 3); STEP(G, d, a, b, c, GET( 5) + 0x5A827999, 5); STEP(G, c, d, a, b, GET( 9) + 0x5A827999, 9); STEP(G, b, c, d, a, GET(13) + 0x5A827999, 13); STEP(G, a, b, c, d, GET( 2) + 0x5A827999, 3); STEP(G, d, a, b, c, GET( 6) + 0x5A827999, 5); STEP(G, c, d, a, b, GET(10) + 0x5A827999, 9); STEP(G, b, c, d, a, GET(14) + 0x5A827999, 13); STEP(G, a, b, c, d, GET( 3) + 0x5A827999, 3); STEP(G, d, a, b, c, GET( 7) + 0x5A827999, 5); STEP(G, c, d, a, b, GET(11) + 0x5A827999, 9); STEP(G, b, c, d, a, GET(15) + 0x5A827999, 13); /* Round 3 */ STEP(H, a, b, c, d, GET( 0) + 0x6ED9EBA1, 3); STEP(H, d, a, b, c, GET( 8) + 0x6ED9EBA1, 9); STEP(H, c, d, a, b, GET( 4) + 0x6ED9EBA1, 11); STEP(H, b, c, d, a, GET(12) + 0x6ED9EBA1, 15); STEP(H, a, b, c, d, GET( 2) + 0x6ED9EBA1, 3); STEP(H, d, a, b, c, GET(10) + 0x6ED9EBA1, 9); STEP(H, c, d, a, b, GET( 6) + 0x6ED9EBA1, 11); STEP(H, b, c, d, a, GET(14) + 0x6ED9EBA1, 15); STEP(H, a, b, c, d, GET( 1) + 0x6ED9EBA1, 3); STEP(H, d, a, b, c, GET( 9) + 0x6ED9EBA1, 9); STEP(H, c, d, a, b, GET( 5) + 0x6ED9EBA1, 11); STEP(H, b, c, d, a, GET(13) + 0x6ED9EBA1, 15); STEP(H, a, b, c, d, GET( 3) + 0x6ED9EBA1, 3); STEP(H, d, a, b, c, GET(11) + 0x6ED9EBA1, 9); STEP(H, c, d, a, b, GET( 7) + 0x6ED9EBA1, 11); STEP(H, b, c, d, a, GET(15) + 0x6ED9EBA1, 15); a += saved_a; b += saved_b; c += saved_c; d += saved_d; ptr += 64; } while ((size -= 64) != 0); ctx->a = a; ctx->b = b; ctx->c = c; ctx->d = d; return ptr; } void md4_init(struct md4_context *ctx) { ctx->a = 0x67452301; ctx->b = 0xefcdab89; ctx->c = 0x98badcfe; ctx->d = 0x10325476; ctx->lo = 0; ctx->hi = 0; } void md4_update(struct md4_context *ctx, const void *data, size_t size) { /* @UNSAFE */ uint_fast32_t saved_lo; unsigned long used, free; saved_lo = ctx->lo; if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) ctx->hi++; ctx->hi += size >> 29; used = saved_lo & 0x3f; if (used != 0) { free = 64 - used; if (size < free) { memcpy(&ctx->buffer[used], data, size); return; } memcpy(&ctx->buffer[used], data, free); data = (const unsigned char *) data + free; size -= free; body(ctx, ctx->buffer, 64); } if (size >= 64) { data = body(ctx, data, size & ~0x3fUL); size &= 0x3f; } memcpy(ctx->buffer, data, size); } void ATTR_NO_SANITIZE_UNDEFINED ATTR_NO_SANITIZE_INTEGER ATTR_NO_SANITIZE_IMPLICIT_CONVERSION md4_final(struct md4_context *ctx, unsigned char result[STATIC_ARRAY MD4_RESULTLEN]) { /* @UNSAFE */ unsigned long used, free; used = ctx->lo & 0x3f; ctx->buffer[used++] = 0x80; free = 64 - used; if (free < 8) { memset(&ctx->buffer[used], 0, free); body(ctx, ctx->buffer, 64); used = 0; free = 64; } memset(&ctx->buffer[used], 0, free - 8); ctx->lo <<= 3; ctx->buffer[56] = ctx->lo; ctx->buffer[57] = ctx->lo >> 8; ctx->buffer[58] = ctx->lo >> 16; ctx->buffer[59] = ctx->lo >> 24; ctx->buffer[60] = ctx->hi; ctx->buffer[61] = ctx->hi >> 8; ctx->buffer[62] = ctx->hi >> 16; ctx->buffer[63] = ctx->hi >> 24; body(ctx, ctx->buffer, 64); result[0] = ctx->a; result[1] = ctx->a >> 8; result[2] = ctx->a >> 16; result[3] = ctx->a >> 24; result[4] = ctx->b; result[5] = ctx->b >> 8; result[6] = ctx->b >> 16; result[7] = ctx->b >> 24; result[8] = ctx->c; result[9] = ctx->c >> 8; result[10] = ctx->c >> 16; result[11] = ctx->c >> 24; result[12] = ctx->d; result[13] = ctx->d >> 8; result[14] = ctx->d >> 16; result[15] = ctx->d >> 24; i_zero_safe(ctx); } void md4_get_digest(const void *data, size_t size, unsigned char result[STATIC_ARRAY MD4_RESULTLEN]) { struct md4_context ctx; md4_init(&ctx); md4_update(&ctx, data, size); md4_final(&ctx, result); } static void hash_method_init_md4(void *context) { md4_init(context); } static void hash_method_loop_md4(void *context, const void *data, size_t size) { md4_update(context, data, size); } static void hash_method_result_md4(void *context, unsigned char *result_r) { md4_final(context, result_r); } const struct hash_method hash_method_md4 = { .name = "md4", .block_size = 64, /* block size is 512 bits */ .context_size = sizeof(struct md4_context), .digest_size = MD4_RESULTLEN, .init = hash_method_init_md4, .loop = hash_method_loop_md4, .result = hash_method_result_md4, }; dovecot-2.3.21.1/src/lib/log-throttle.c0000644000000000000000000000410114656633576014436 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "time-util.h" #include "log-throttle.h" struct log_throttle { struct log_throttle_settings set; log_throttle_callback_t *callback; void *context; struct timeval last_time; unsigned int last_count; struct timeout *to_throttled; }; #undef log_throttle_init struct log_throttle * log_throttle_init(const struct log_throttle_settings *set, log_throttle_callback_t *callback, void *context) { struct log_throttle *throttle; i_assert(set->throttle_at_max_per_interval > 0); i_assert(set->unthrottle_at_max_per_interval > 0); throttle = i_new(struct log_throttle, 1); throttle->set = *set; if (throttle->set.interval_msecs == 0) throttle->set.interval_msecs = 1000; throttle->callback = callback; throttle->context = context; throttle->last_time = ioloop_timeval; return throttle; } void log_throttle_deinit(struct log_throttle **_throttle) { struct log_throttle *throttle = *_throttle; *_throttle = NULL; timeout_remove(&throttle->to_throttled); i_free(throttle); } static void log_throttle_callback(struct log_throttle *throttle) { if (throttle->last_count > 0) throttle->callback(throttle->last_count, throttle->context); if (throttle->last_count < throttle->set.unthrottle_at_max_per_interval) timeout_remove(&throttle->to_throttled); throttle->last_count = 0; } bool log_throttle_accept(struct log_throttle *throttle) { if (throttle->to_throttled != NULL) { /* unthrottling and last_count resets are done only by the callback */ throttle->last_count++; return FALSE; } else if (timeval_diff_msecs(&ioloop_timeval, &throttle->last_time) >= (int)throttle->set.interval_msecs) { throttle->last_time = ioloop_timeval; throttle->last_count = 1; return TRUE; } else if (++throttle->last_count <= throttle->set.throttle_at_max_per_interval) { return TRUE; } else { throttle->last_count = 1; throttle->to_throttled = timeout_add(throttle->set.interval_msecs, log_throttle_callback, throttle); return FALSE; } } dovecot-2.3.21.1/src/lib/hmac.c0000644000000000000000000001020714656633576012726 00000000000000/* * HMAC (RFC-2104) implementation. * * Copyright (c) 2004 Andrey Panin * Copyright (c) 2011-2016 Florian Zeitz * * This software is released under the MIT license. */ #include "lib.h" #include "hmac.h" #include "safe-memset.h" #include "buffer.h" #include "hex-binary.h" void hmac_init(struct hmac_context *_ctx, const unsigned char *key, size_t key_len, const struct hash_method *meth) { struct hmac_context_priv *ctx = &_ctx->u.priv; unsigned int i; unsigned char k_ipad[meth->block_size]; unsigned char k_opad[meth->block_size]; unsigned char hashedkey[meth->digest_size]; i_assert(meth->context_size <= HMAC_MAX_CONTEXT_SIZE); ctx->hash = meth; if (key_len > meth->block_size) { meth->init(ctx->ctx); meth->loop(ctx->ctx, key, key_len); meth->result(ctx->ctx, hashedkey); key = hashedkey; key_len = meth->digest_size; } memcpy(k_ipad, key, key_len); memset(k_ipad + key_len, 0, meth->block_size - key_len); memcpy(k_opad, k_ipad, meth->block_size); for (i = 0; i < meth->block_size; i++) { k_ipad[i] ^= 0x36; k_opad[i] ^= 0x5c; } meth->init(ctx->ctx); meth->loop(ctx->ctx, k_ipad, meth->block_size); meth->init(ctx->ctxo); meth->loop(ctx->ctxo, k_opad, meth->block_size); safe_memset(k_ipad, 0, meth->block_size); safe_memset(k_opad, 0, meth->block_size); } void hmac_final(struct hmac_context *_ctx, unsigned char *digest) { struct hmac_context_priv *ctx = &_ctx->u.priv; ctx->hash->result(ctx->ctx, digest); ctx->hash->loop(ctx->ctxo, digest, ctx->hash->digest_size); ctx->hash->result(ctx->ctxo, digest); } buffer_t *t_hmac_data(const struct hash_method *meth, const unsigned char *key, size_t key_len, const void *data, size_t data_len) { struct hmac_context ctx; i_assert(meth != NULL); i_assert(key != NULL && key_len > 0); i_assert(data != NULL || data_len == 0); buffer_t *res = t_buffer_create(meth->digest_size); hmac_init(&ctx, key, key_len, meth); if (data_len > 0) hmac_update(&ctx, data, data_len); unsigned char *buf = buffer_get_space_unsafe(res, 0, meth->digest_size); hmac_final(&ctx, buf); return res; } buffer_t *t_hmac_buffer(const struct hash_method *meth, const unsigned char *key, size_t key_len, const buffer_t *data) { return t_hmac_data(meth, key, key_len, data->data, data->used); } buffer_t *t_hmac_str(const struct hash_method *meth, const unsigned char *key, size_t key_len, const char *data) { return t_hmac_data(meth, key, key_len, data, strlen(data)); } void hmac_hkdf(const struct hash_method *method, const unsigned char *salt, size_t salt_len, const unsigned char *ikm, size_t ikm_len, const unsigned char *info, size_t info_len, buffer_t *okm_r, size_t okm_len) { i_assert(method != NULL); i_assert(okm_len < 255*method->digest_size); struct hmac_context key_mac; struct hmac_context info_mac; size_t remain = okm_len; unsigned char prk[method->digest_size]; unsigned char okm[method->digest_size]; /* N = ceil(L/HashLen) */ unsigned int rounds = (okm_len + method->digest_size - 1)/method->digest_size; /* salt and info can be NULL */ i_assert(salt != NULL || salt_len == 0); i_assert(info != NULL || info_len == 0); i_assert(ikm != NULL && ikm_len > 0); i_assert(okm_r != NULL && okm_len > 0); /* but they still need valid pointer, reduces complains from static analysers */ if (salt == NULL) salt = &uchar_nul; if (info == NULL) info = &uchar_nul; /* extract */ hmac_init(&key_mac, salt, salt_len, method); hmac_update(&key_mac, ikm, ikm_len); hmac_final(&key_mac, prk); /* expand */ for (unsigned int i = 0; remain > 0 && i < rounds; i++) { unsigned char round = (i+1); size_t amt = remain; if (amt > method->digest_size) amt = method->digest_size; hmac_init(&info_mac, prk, method->digest_size, method); if (i > 0) hmac_update(&info_mac, okm, method->digest_size); hmac_update(&info_mac, info, info_len); hmac_update(&info_mac, &round, 1); memset(okm, 0, method->digest_size); hmac_final(&info_mac, okm); buffer_append(okm_r, okm, amt); remain -= amt; } safe_memset(prk, 0, sizeof(prk)); safe_memset(okm, 0, sizeof(okm)); } dovecot-2.3.21.1/src/lib/ipwd.c0000644000000000000000000000410514656633576012761 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #define _POSIX_PTHREAD_SEMANTICS /* for Solaris */ #include "lib.h" #include "ipwd.h" #include #define PWBUF_MIN_SIZE 128 #define GRBUF_MIN_SIZE 128 static void *pwbuf = NULL, *grbuf = NULL; static size_t pwbuf_size, grbuf_size; static void pw_init(void) { size_t old_pwbuf_size = pwbuf_size; if (pwbuf == NULL || errno == ERANGE) { pwbuf_size = nearest_power(old_pwbuf_size + 1); if (pwbuf_size < PWBUF_MIN_SIZE) pwbuf_size = PWBUF_MIN_SIZE; pwbuf = i_realloc(pwbuf, old_pwbuf_size, pwbuf_size); } } static void gr_init(void) { size_t old_grbuf_size = grbuf_size; if (grbuf == NULL || errno == ERANGE) { grbuf_size = nearest_power(old_grbuf_size + 1); if (grbuf_size < PWBUF_MIN_SIZE) grbuf_size = PWBUF_MIN_SIZE; grbuf = i_realloc(grbuf, old_grbuf_size, grbuf_size); } } void ipwd_deinit(void) { i_free_and_null(pwbuf); i_free_and_null(grbuf); } int i_getpwnam(const char *name, struct passwd *pwd_r) { struct passwd *result; errno = 0; do { pw_init(); errno = getpwnam_r(name, pwd_r, pwbuf, pwbuf_size, &result); } while (errno == ERANGE); if (result != NULL) return 1; if (errno == EINVAL) { /* FreeBSD fails here when name="user@domain" */ return 0; } return errno == 0 ? 0 : -1; } int i_getpwuid(uid_t uid, struct passwd *pwd_r) { struct passwd *result; errno = 0; do { pw_init(); errno = getpwuid_r(uid, pwd_r, pwbuf, pwbuf_size, &result); } while (errno == ERANGE); if (result != NULL) return 1; return errno == 0 ? 0 : -1; } int i_getgrnam(const char *name, struct group *grp_r) { struct group *result; errno = 0; do { gr_init(); errno = getgrnam_r(name, grp_r, grbuf, grbuf_size, &result); } while (errno == ERANGE); if (result != NULL) return 1; return errno == 0 ? 0 : -1; } int i_getgrgid(gid_t gid, struct group *grp_r) { struct group *result; errno = 0; do { gr_init(); errno = getgrgid_r(gid, grp_r, grbuf, grbuf_size, &result); } while (errno == ERANGE); if (result != NULL) return 1; return errno == 0 ? 0 : -1; } dovecot-2.3.21.1/src/lib/file-create-locked.c0000644000000000000000000001222314656633576015435 00000000000000/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "safe-mkstemp.h" #include "mkdir-parents.h" #include "file-lock.h" #include "file-create-locked.h" #include #include #include /* Try mkdir() + lock creation multiple times. This allows the lock file creation to work even while the directory is simultaneously being rmdir()ed. */ #define MAX_MKDIR_COUNT 10 #define MAX_RETRY_COUNT 1000 static int try_lock_existing(int fd, const char *path, const struct file_create_settings *set, struct file_lock **lock_r, const char **error_r) { struct file_lock_settings lock_set = set->lock_settings; struct stat st1, st2; int ret; lock_set.unlink_on_free = FALSE; lock_set.close_on_free = FALSE; if (fstat(fd, &st1) < 0) { *error_r = t_strdup_printf("fstat(%s) failed: %m", path); return -1; } if (file_wait_lock(fd, path, F_WRLCK, &lock_set, set->lock_timeout_secs, lock_r, error_r) <= 0) return -1; if (stat(path, &st2) == 0) { ret = st1.st_ino == st2.st_ino && CMP_DEV_T(st1.st_dev, st2.st_dev) ? 1 : 0; } else if (errno == ENOENT) { ret = 0; } else { *error_r = t_strdup_printf("stat(%s) failed: %m", path); ret = -1; } if (ret <= 0) { /* the fd is closed next - no need to unlock */ file_lock_free(lock_r); } else { file_lock_set_unlink_on_free( *lock_r, set->lock_settings.unlink_on_free); file_lock_set_close_on_free( *lock_r, set->lock_settings.close_on_free); } return ret; } static int try_mkdir(const char *path, const struct file_create_settings *set, const char **error_r) { uid_t uid = set->mkdir_uid != 0 ? set->mkdir_uid : (uid_t)-1; gid_t gid = set->mkdir_gid != 0 ? set->mkdir_gid : (gid_t)-1; const char *p = strrchr(path, '/'); if (p == NULL) return 0; const char *dir = t_strdup_until(path, p); int ret; if (uid != (uid_t)-1) ret = mkdir_parents_chown(dir, set->mkdir_mode, uid, gid); else { ret = mkdir_parents_chgrp(dir, set->mkdir_mode, gid, set->gid_origin); } if (ret < 0 && errno != EEXIST) { *error_r = t_strdup_printf("mkdir_parents(%s) failed: %m", dir); return -1; } return 1; } static int try_create_new(const char *path, const struct file_create_settings *set, int *fd_r, struct file_lock **lock_r, const char **error_r) { string_t *temp_path = t_str_new(128); int fd, orig_errno, ret = 1; int mode = set->mode != 0 ? set->mode : 0600; uid_t uid = set->uid != 0 ? set->uid : (uid_t)-1; uid_t gid = set->gid != 0 ? set->gid : (gid_t)-1; str_append(temp_path, path); for (unsigned int i = 0; ret > 0; i++) { if (uid != (uid_t)-1) fd = safe_mkstemp(temp_path, mode, uid, gid); else fd = safe_mkstemp_group(temp_path, mode, gid, set->gid_origin); if (fd != -1 || errno != ENOENT || set->mkdir_mode == 0 || i >= MAX_MKDIR_COUNT) break; int orig_errno = errno; if ((ret = try_mkdir(path, set, error_r)) < 0) return -1; errno = orig_errno; } if (fd == -1) { *error_r = t_strdup_printf("safe_mkstemp(%s) failed: %m", path); return -1; } struct file_lock_settings lock_set = set->lock_settings; lock_set.unlink_on_free = FALSE; lock_set.close_on_free = FALSE; ret = -1; if (file_try_lock(fd, str_c(temp_path), F_WRLCK, &lock_set, lock_r, error_r) <= 0) { } else if (link(str_c(temp_path), path) < 0) { if (errno == EEXIST) { /* just created by somebody else */ ret = 0; } else if (errno == ENOENT) { /* nobody should be deleting the temp file unless the entire directory is deleted. */ *error_r = t_strdup_printf( "Temporary file %s was unexpectedly deleted", str_c(temp_path)); } else { *error_r = t_strdup_printf("link(%s, %s) failed: %m", str_c(temp_path), path); } file_lock_free(lock_r); } else { file_lock_set_path(*lock_r, path); file_lock_set_unlink_on_free( *lock_r, set->lock_settings.unlink_on_free); file_lock_set_close_on_free( *lock_r, set->lock_settings.close_on_free); i_unlink_if_exists(str_c(temp_path)); *fd_r = fd; return 1; } orig_errno = errno; i_close_fd(&fd); i_unlink_if_exists(str_c(temp_path)); errno = orig_errno; return ret; } int file_create_locked(const char *path, const struct file_create_settings *set, struct file_lock **lock_r, bool *created_r, const char **error_r) { unsigned int i; int fd, ret; for (i = 0; i < MAX_RETRY_COUNT; i++) { fd = open(path, O_RDWR); if (fd != -1) { ret = try_lock_existing(fd, path, set, lock_r, error_r); if (ret > 0) { /* successfully locked an existing file */ *created_r = FALSE; return fd; } i_close_fd(&fd); if (ret < 0) return -1; } else if (errno != ENOENT) { *error_r = t_strdup_printf("open(%s) failed: %m", path); return -1; } else { /* try to create the file */ ret = try_create_new(path, set, &fd, lock_r, error_r); if (ret < 0) return -1; if (ret > 0) { /* successfully created a new locked file */ *created_r = TRUE; return fd; } /* the file was just created - try again opening and locking it */ } } *error_r = t_strdup_printf("Creating a locked file %s keeps failing", path); errno = EINVAL; return -1; } dovecot-2.3.21.1/src/lib/sha2.h0000644000000000000000000000635014656633576012664 00000000000000/* * FIPS 180-2 SHA-224/256/384/512 implementation * Last update: 02/02/2007 * Issue date: 04/30/2005 * * Copyright (C) 2005, 2007 Olivier Gay * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef SHA2_H #define SHA2_H #include "hash-method.h" #include "sha-common.h" struct sha256_ctx { uint64_t tot_len; size_t len; unsigned char block[2 * SHA256_BLOCK_SIZE]; uint32_t h[8]; }; struct sha384_ctx { uint64_t tot_len; size_t len; unsigned char block[2 * SHA384_BLOCK_SIZE]; uint64_t h[8]; }; struct sha512_ctx { uint64_t tot_len; size_t len; unsigned char block[2 * SHA512_BLOCK_SIZE]; uint64_t h[8]; }; void sha256_init(struct sha256_ctx *ctx); void sha256_loop(struct sha256_ctx *ctx, const void *data, size_t len); void sha256_result(struct sha256_ctx *ctx, unsigned char digest[STATIC_ARRAY SHA256_RESULTLEN]); void sha256_get_digest(const void *data, size_t size, unsigned char digest[STATIC_ARRAY SHA256_RESULTLEN]); void sha384_init(struct sha384_ctx *ctx); void sha384_loop(struct sha384_ctx *ctx, const void *data, size_t len); void sha384_result(struct sha384_ctx *ctx, unsigned char digest[STATIC_ARRAY SHA384_RESULTLEN]); void sha384_get_digest(const void *data, size_t size, unsigned char digest[STATIC_ARRAY SHA384_RESULTLEN]); void sha512_init(struct sha512_ctx *ctx); void sha512_loop(struct sha512_ctx *ctx, const void *data, size_t len); void sha512_result(struct sha512_ctx *ctx, unsigned char digest[STATIC_ARRAY SHA512_RESULTLEN]); void sha512_get_digest(const void *data, size_t size, unsigned char digest[STATIC_ARRAY SHA512_RESULTLEN]); extern const struct hash_method hash_method_sha256; extern const struct hash_method hash_method_sha384; extern const struct hash_method hash_method_sha512; #endif /* !SHA2_H */ dovecot-2.3.21.1/src/lib/test-fd-util.c0000644000000000000000000000105014656633576014333 00000000000000/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "fd-util.h" enum fatal_test_state fatal_i_close(unsigned int stage) { if (stage == 0) { test_begin("fatal i_close"); } else { test_end(); return FATAL_TEST_FINISHED; } int fd = 0; const char *fatal_string = t_strdup_printf( "%s: close((&fd)) @ %s:%d attempted with fd=%d", __func__, __FILE__, __LINE__ + 2, fd); test_expect_fatal_string(fatal_string); i_close_fd(&fd); /* This cannot be reached. */ return FATAL_TEST_ABORT; } dovecot-2.3.21.1/src/lib/mempool-allocfree.c0000644000000000000000000002251114656633576015421 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ /* @UNSAFE: whole file */ #include "lib.h" #include "safe-memset.h" #include "mempool.h" #include "llist.h" /* * As the name implies, allocfree pools support both allocating and freeing * memory. * * Implementation * ============== * * Each allocfree pool contains a pool structure (struct allocfree_pool) to * keep track of allocfree-specific pool information and zero or more blocks * (struct pool_block) that keep track of ranges of memory used to back the * allocations. The blocks are kept in a doubly-linked list used to keep * track of all allocations that belong to the pool. * * +-----------+ * | allocfree | * | pool | * +-----+-----+ * | * | blocks +------------+ next +------------+ next * \------->| pool block |<=====>| pool block |<=====>...<====> NULL * +------------+ prev +------------+ prev * | | | | * . . * . . * . | | * . +------------+ * | | * +------------+ * * Creation * -------- * * When an allocfree pool is created the linked list of allocated blocks is * initialized to be empty. * * Allocation & Freeing * -------------------- * * Since each allocation (via p_malloc()) corresponds to one block, * allocations are simply a matter of: * * - allocating enough memory from the system heap (via calloc()) to hold * the block header and the requested number of bytes, * - making a note of the user-requested size in the block header, * - adding the new block to the pool's linked list of blocks, and * - returning a pointer to the payload area of the block to the caller. * * Freeing memory is simpler. The passed in pointer is converted to a * struct pool_block pointer. Then the block is removed from the pool's * linked list and free()d. * * If the pool was created via pool_allocfree_create_clean(), all blocks are * safe_memset() to zero just before being free()d. * * Reallocation * ------------ * * Reallocation is done by calling realloc() with a new size that is large * enough to cover the requested number of bytes plus the block header * overhead. * * Clearing * -------- * * Clearing the pool is supposed to return the pool to the same state it was * in when it was first created. To that end, the allocfree pool frees all * the blocks allocated since the pool's creation. In other words, clearing * is equivalent to (but faster than) calling p_free() for each allocation * in the pool. * * Finally, if the pool was created via pool_allocfree_create_clean(), all * blocks are safe_memset() to zero before being free()d. * * Destruction * ----------- * * Destroying a pool first clears it (see above) and then the pool structure * itself is safe_memset() to zero (if pool_allocfree_create_clean() was * used) and free()d. (The clearing leaves the pool in a minimal state * with no blocks allocated.) */ struct allocfree_pool { struct pool pool; int refcount; size_t total_alloc_count; size_t total_alloc_used; struct pool_block *blocks; #ifdef DEBUG char *name; #endif bool clean_frees; }; struct pool_block { struct pool_block *prev,*next; size_t size; unsigned char *block; }; #define SIZEOF_ALLOCFREE_POOL MEM_ALIGN(sizeof(struct allocfree_pool)) #define SIZEOF_POOLBLOCK (MEM_ALIGN(sizeof(struct pool_block))) static const char *pool_allocfree_get_name(pool_t pool); static void pool_allocfree_ref(pool_t pool); static void pool_allocfree_unref(pool_t *pool); static void *pool_allocfree_malloc(pool_t pool, size_t size); static void pool_allocfree_free(pool_t pool, void *mem); static void *pool_allocfree_realloc(pool_t pool, void *mem, size_t old_size, size_t new_size); static void pool_allocfree_clear(pool_t pool); static size_t pool_allocfree_get_max_easy_alloc_size(pool_t pool); static const struct pool_vfuncs static_allocfree_pool_vfuncs = { pool_allocfree_get_name, pool_allocfree_ref, pool_allocfree_unref, pool_allocfree_malloc, pool_allocfree_free, pool_allocfree_realloc, pool_allocfree_clear, pool_allocfree_get_max_easy_alloc_size }; static const struct pool static_allocfree_pool = { .v = &static_allocfree_pool_vfuncs, .alloconly_pool = FALSE, .datastack_pool = FALSE }; pool_t pool_allocfree_create(const char *name ATTR_UNUSED) { struct allocfree_pool *pool; if (SIZEOF_POOLBLOCK > (SSIZE_T_MAX - POOL_MAX_ALLOC_SIZE)) i_panic("POOL_MAX_ALLOC_SIZE is too large"); pool = calloc(1, SIZEOF_ALLOCFREE_POOL); if (pool == NULL) i_fatal_status(FATAL_OUTOFMEM, "calloc(1, %zu): Out of memory", SIZEOF_ALLOCFREE_POOL); #ifdef DEBUG pool->name = strdup(name); #endif pool->pool = static_allocfree_pool; pool->refcount = 1; return &pool->pool; } pool_t pool_allocfree_create_clean(const char *name) { struct allocfree_pool *apool; pool_t pool; pool = pool_allocfree_create(name); apool = (struct allocfree_pool *)pool; apool->clean_frees = TRUE; return pool; } static void pool_allocfree_destroy(struct allocfree_pool *apool) { pool_allocfree_clear(&apool->pool); if (apool->clean_frees) safe_memset(apool, 0, SIZEOF_ALLOCFREE_POOL); #ifdef DEBUG free(apool->name); #endif free(apool); } static const char *pool_allocfree_get_name(pool_t pool ATTR_UNUSED) { #ifdef DEBUG struct allocfree_pool *apool = container_of(pool, struct allocfree_pool, pool); return apool->name; #else return "alloc"; #endif } static void pool_allocfree_ref(pool_t pool) { struct allocfree_pool *apool = container_of(pool, struct allocfree_pool, pool); i_assert(apool->refcount > 0); apool->refcount++; } static void pool_allocfree_unref(pool_t *_pool) { pool_t pool = *_pool; struct allocfree_pool *apool = container_of(pool, struct allocfree_pool, pool); i_assert(apool->refcount > 0); /* erase the pointer before freeing anything, as the pointer may exist inside the pool's memory area */ *_pool = NULL; if (--apool->refcount > 0) return; pool_allocfree_destroy(apool); } static void *pool_block_attach(struct allocfree_pool *apool, struct pool_block *block) { i_assert(block->size > 0); DLLIST_PREPEND(&apool->blocks, block); block->block = PTR_OFFSET(block,SIZEOF_POOLBLOCK); apool->total_alloc_used += block->size; apool->total_alloc_count++; return block->block; } static struct pool_block * pool_block_detach(struct allocfree_pool *apool, unsigned char *mem) { /* cannot use PTR_OFFSET because of negative value */ i_assert((uintptr_t)mem >= SIZEOF_POOLBLOCK); struct pool_block *block = (struct pool_block *)(mem - SIZEOF_POOLBLOCK); /* make sure the block we are dealing with is correct */ i_assert(block->block == mem); i_assert((block->prev == NULL || block->prev->next == block) && (block->next == NULL || block->next->prev == block)); i_assert(apool->total_alloc_used >= block->size); i_assert(apool->total_alloc_count > 0); DLLIST_REMOVE(&apool->blocks, block); apool->total_alloc_used -= block->size; apool->total_alloc_count--; return block; } static void *pool_allocfree_malloc(pool_t pool, size_t size) { struct allocfree_pool *apool = container_of(pool, struct allocfree_pool, pool); struct pool_block *block = calloc(1, SIZEOF_POOLBLOCK + size); if (block == NULL) i_fatal_status(FATAL_OUTOFMEM, "calloc(1, %zu): Out of memory", SIZEOF_POOLBLOCK + size); block->size = size; return pool_block_attach(apool, block); } static void pool_allocfree_free(pool_t pool, void *mem) { struct allocfree_pool *apool = container_of(pool, struct allocfree_pool, pool); struct pool_block *block = pool_block_detach(apool, mem); if (apool->clean_frees) safe_memset(block, 0, SIZEOF_POOLBLOCK+block->size); free(block); } static void *pool_allocfree_realloc(pool_t pool, void *mem, size_t old_size, size_t new_size) { struct allocfree_pool *apool = container_of(pool, struct allocfree_pool, pool); unsigned char *new_mem; struct pool_block *block = pool_block_detach(apool, mem); if ((new_mem = realloc(block, SIZEOF_POOLBLOCK+new_size)) == NULL) i_fatal_status(FATAL_OUTOFMEM, "realloc(block, %zu)", SIZEOF_POOLBLOCK+new_size); /* zero out new memory */ if (new_size > old_size) memset(new_mem + SIZEOF_POOLBLOCK + old_size, 0, new_size - old_size); block = (struct pool_block*)new_mem; block->size = new_size; return pool_block_attach(apool, block); } static void pool_allocfree_clear(pool_t pool) { struct allocfree_pool *apool = container_of(pool, struct allocfree_pool, pool); struct pool_block *block, *next; for (block = apool->blocks; block != NULL; block = next) { next = block->next; pool_allocfree_free(pool, block->block); } i_assert(apool->total_alloc_used == 0 && apool->total_alloc_count == 0); } static size_t pool_allocfree_get_max_easy_alloc_size(pool_t pool ATTR_UNUSED) { return 0; } size_t pool_allocfree_get_total_used_size(pool_t pool) { struct allocfree_pool *apool = container_of(pool, struct allocfree_pool, pool); return apool->total_alloc_used; } size_t pool_allocfree_get_total_alloc_size(pool_t pool) { struct allocfree_pool *apool = container_of(pool, struct allocfree_pool, pool); return apool->total_alloc_used + SIZEOF_POOLBLOCK*apool->total_alloc_count + sizeof(*apool); } dovecot-2.3.21.1/src/lib/test-wildcard-match.c0000644000000000000000000000201314656633576015652 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "wildcard-match.h" static const struct { const char *data; const char *mask; bool result; } tests[] = { { "foo", "*", TRUE }, { "foo", "*foo*", TRUE }, { "foo", "foo", TRUE }, { "foo", "f*o*o", TRUE }, { "foo", "f??", TRUE }, { "foo", "f?o", TRUE }, { "foo", "*??", TRUE }, { "foo", "???", TRUE }, { "foo", "f??*", TRUE }, { "foo", "???*", TRUE }, { "foo", "", FALSE }, { "foo", "f", FALSE }, { "foo", "fo", FALSE }, { "foo", "fooo", FALSE }, { "foo", "????", FALSE }, { "foo", "f*o*o*o", FALSE }, { "foo", "f???*", FALSE }, { "*foo", "foo", FALSE }, { "foo*", "foo", FALSE }, { "*foo*", "foo", FALSE }, { "", "*", TRUE }, { "", "", TRUE }, { "", "?", FALSE } }; void test_wildcard_match(void) { unsigned int i; test_begin("wildcard_match()"); for (i = 0; i < N_ELEMENTS(tests); i++) { test_assert_idx(wildcard_match(tests[i].data, tests[i].mask) == tests[i].result, i); } test_end(); } dovecot-2.3.21.1/src/lib/md5.c0000644000000000000000000002075614656633576012515 00000000000000/* * This is an OpenSSL-compatible implementation of the RSA Data Security, * Inc. MD5 Message-Digest Algorithm. * * Written by Solar Designer in 2001, and placed in * the public domain. There's absolutely no warranty. * * This differs from Colin Plumb's older public domain implementation in * that no 32-bit integer data type is required, there's no compile-time * endianness configuration, and the function prototypes match OpenSSL's. * The primary goals are portability and ease of use. * * This implementation is meant to be fast, but not as fast as possible. * Some known optimizations are not included to reduce source code size * and avoid compile-time configuration. */ #include "lib.h" #include "safe-memset.h" #include "md5.h" /* * The basic MD5 functions. * * F is optimized compared to its RFC 1321 definition just like in Colin * Plumb's implementation. */ #define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) #define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) #define H(x, y, z) ((x) ^ (y) ^ (z)) #define I(x, y, z) ((y) ^ ((x) | ~(z))) /* * The MD5 transformation for all four rounds. */ #define STEP(f, a, b, c, d, x, t, s) \ (a) += f((b), (c), (d)) + (x) + (t); \ (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ (a) += (b); /* * SET reads 4 input bytes in little-endian byte order and stores them * in a properly aligned word in host byte order. * * The check for little-endian architectures which tolerate unaligned * memory accesses is just an optimization. Nothing will break if it * doesn't work. */ #if defined(__i386__) || defined(__x86_64__) || defined(__vax__) #define SET(n) \ (*(const uint32_t *)&ptr[(n) * 4]) #define GET(n) \ SET(n) #else #define SET(n) \ (ctx->block[(n)] = \ (uint_fast32_t)ptr[(n) * 4] | \ ((uint_fast32_t)ptr[(n) * 4 + 1] << 8) | \ ((uint_fast32_t)ptr[(n) * 4 + 2] << 16) | \ ((uint_fast32_t)ptr[(n) * 4 + 3] << 24)) #define GET(n) \ (ctx->block[(n)]) #endif /* * This processes one or more 64-byte data blocks, but does NOT update * the bit counters. There're no alignment requirements. */ static const void * ATTR_NOWARN_UNUSED_RESULT ATTR_UNSIGNED_WRAPS ATTR_NO_SANITIZE_UNDEFINED ATTR_NO_SANITIZE_INTEGER ATTR_NO_SANITIZE_IMPLICIT_CONVERSION body(struct md5_context *ctx, const void *data, size_t size) { const unsigned char *ptr; uint_fast32_t a, b, c, d; uint_fast32_t saved_a, saved_b, saved_c, saved_d; ptr = data; a = ctx->a; b = ctx->b; c = ctx->c; d = ctx->d; do { saved_a = a; saved_b = b; saved_c = c; saved_d = d; /* Round 1 */ STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) STEP(F, c, d, a, b, SET(2), 0x242070db, 17) STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) /* Round 2 */ STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) STEP(G, d, a, b, c, GET(10), 0x02441453, 9) STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) /* Round 3 */ STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) STEP(H, d, a, b, c, GET(8), 0x8771f681, 11) STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23) STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11) STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23) STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11) STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) STEP(H, b, c, d, a, GET(6), 0x04881d05, 23) STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11) STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23) /* Round 4 */ STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) a += saved_a; b += saved_b; c += saved_c; d += saved_d; ptr += 64; } while ((size -= 64) != 0); ctx->a = a; ctx->b = b; ctx->c = c; ctx->d = d; return ptr; } void md5_init(struct md5_context *ctx) { ctx->a = 0x67452301; ctx->b = 0xefcdab89; ctx->c = 0x98badcfe; ctx->d = 0x10325476; ctx->lo = 0; ctx->hi = 0; memset(ctx->block, 0, sizeof(ctx->block)); } void ATTR_UNSIGNED_WRAPS md5_update(struct md5_context *ctx, const void *data, size_t size) { /* @UNSAFE */ uint_fast32_t saved_lo; unsigned long used, free; saved_lo = ctx->lo; if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) ctx->hi++; ctx->hi += size >> 29; used = saved_lo & 0x3f; if (used != 0) { free = 64 - used; if (size < free) { memcpy(&ctx->buffer[used], data, size); return; } memcpy(&ctx->buffer[used], data, free); data = (const unsigned char *) data + free; size -= free; body(ctx, ctx->buffer, 64); } if (size >= 64) { data = body(ctx, data, size & ~0x3fUL); size &= 0x3f; } memcpy(ctx->buffer, data, size); } void ATTR_UNSIGNED_WRAPS ATTR_NO_SANITIZE_UNDEFINED ATTR_NO_SANITIZE_INTEGER ATTR_NO_SANITIZE_IMPLICIT_CONVERSION md5_final(struct md5_context *ctx, unsigned char result[STATIC_ARRAY MD5_RESULTLEN]) { /* @UNSAFE */ unsigned long used, free; used = ctx->lo & 0x3f; ctx->buffer[used++] = 0x80; free = 64 - used; if (free < 8) { memset(&ctx->buffer[used], 0, free); body(ctx, ctx->buffer, 64); used = 0; free = 64; } memset(&ctx->buffer[used], 0, free - 8); ctx->lo <<= 3; ctx->buffer[56] = ctx->lo; ctx->buffer[57] = ctx->lo >> 8; ctx->buffer[58] = ctx->lo >> 16; ctx->buffer[59] = ctx->lo >> 24; ctx->buffer[60] = ctx->hi; ctx->buffer[61] = ctx->hi >> 8; ctx->buffer[62] = ctx->hi >> 16; ctx->buffer[63] = ctx->hi >> 24; body(ctx, ctx->buffer, 64); result[0] = ctx->a; result[1] = ctx->a >> 8; result[2] = ctx->a >> 16; result[3] = ctx->a >> 24; result[4] = ctx->b; result[5] = ctx->b >> 8; result[6] = ctx->b >> 16; result[7] = ctx->b >> 24; result[8] = ctx->c; result[9] = ctx->c >> 8; result[10] = ctx->c >> 16; result[11] = ctx->c >> 24; result[12] = ctx->d; result[13] = ctx->d >> 8; result[14] = ctx->d >> 16; result[15] = ctx->d >> 24; i_zero_safe(ctx); } void md5_get_digest(const void *data, size_t size, unsigned char result[STATIC_ARRAY MD5_RESULTLEN]) { struct md5_context ctx; md5_init(&ctx); md5_update(&ctx, data, size); md5_final(&ctx, result); } static void hash_method_init_md5(void *context) { md5_init(context); } static void hash_method_loop_md5(void *context, const void *data, size_t size) { md5_update(context, data, size); } static void hash_method_result_md5(void *context, unsigned char *result_r) { md5_final(context, result_r); } const struct hash_method hash_method_md5 = { .name = "md5", .block_size = 64, /* block size is 512 bits */ .context_size = sizeof(struct md5_context), .digest_size = MD5_RESULTLEN, .init = hash_method_init_md5, .loop = hash_method_loop_md5, .result = hash_method_result_md5, }; dovecot-2.3.21.1/src/lib/str-sanitize.c0000644000000000000000000000643014656633576014455 00000000000000/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "unichar.h" #include "str.h" #include "str-sanitize.h" static size_t str_sanitize_skip_start(const char *src, size_t max_bytes) { unichar_t chr; size_t i; for (i = 0; i < max_bytes && src[i] != '\0'; ) { int len = uni_utf8_get_char_n(src+i, max_bytes-i, &chr); if (len <= 0) break; if ((unsigned char)src[i] < 32) break; i += len; } i_assert(i <= max_bytes); return i; } static size_t str_sanitize_skip_start_utf8(const char *src, uintmax_t max_chars) { unichar_t chr; uintmax_t c; size_t i; for (i = 0, c = 0; c < max_chars && src[i] != '\0'; ) { int len = uni_utf8_get_char(src+i, &chr); if (len <= 0) break; if ((unsigned char)src[i] < 32) break; c++; i += len; } i_assert(c <= max_chars); return i; } static void str_sanitize_truncate_char(string_t *dest, unsigned int initial_pos) { const unsigned char *data = str_data(dest); size_t len = str_len(dest); i_assert(len >= initial_pos); if (len == initial_pos) return; data += initial_pos; len -= initial_pos; str_truncate(dest, initial_pos + uni_utf8_data_truncate(data, len, len-1)); } void str_sanitize_append(string_t *dest, const char *src, size_t max_bytes) { size_t initial_pos = str_len(dest); unichar_t chr; size_t i; for (i = 0; i < max_bytes && src[i] != '\0'; ) { int len = uni_utf8_get_char_n(src+i, max_bytes-i, &chr); if (len == 0) break; /* input ended too early */ if (len < 0) { /* invalid UTF-8 */ str_append_c(dest, '?'); i++; continue; } if ((unsigned char)src[i] < 32) str_append_c(dest, '?'); else str_append_data(dest, src+i, len); i += len; } if (src[i] != '\0') { if (max_bytes < 3) str_truncate(dest, initial_pos); else { while (str_len(dest) - initial_pos > max_bytes-3) str_sanitize_truncate_char(dest, initial_pos); } str_append(dest, "..."); } } void str_sanitize_append_utf8(string_t *dest, const char *src, uintmax_t max_cps) { size_t last_pos = 0; unichar_t chr; uintmax_t c; size_t i; i_assert(max_cps > 0); for (i = 0, c = 0; c < max_cps && src[i] != '\0'; ) { int len = uni_utf8_get_char(src+i, &chr); if (len == 0) break; /* input ended too early */ last_pos = str_len(dest); if (len < 0) { /* invalid UTF-8 */ str_append(dest, UNICODE_REPLACEMENT_CHAR_UTF8); i++; continue; } if ((unsigned char)src[i] < 32) str_append(dest, UNICODE_REPLACEMENT_CHAR_UTF8); else str_append_data(dest, src+i, len); i += len; c++; } if (src[i] != '\0') { str_truncate(dest, last_pos); str_append(dest, UNICODE_HORIZONTAL_ELLIPSIS_CHAR_UTF8); } } const char *str_sanitize(const char *src, size_t max_bytes) { string_t *str; size_t i; if (src == NULL) return NULL; i = str_sanitize_skip_start(src, max_bytes); if (src[i] == '\0') return src; str = t_str_new(I_MIN(max_bytes, 256)); str_sanitize_append(str, src, max_bytes); return str_c(str); } const char *str_sanitize_utf8(const char *src, uintmax_t max_cps) { string_t *str; size_t i; if (src == NULL) return NULL; i = str_sanitize_skip_start_utf8(src, max_cps); if (src[i] == '\0') return src; str = t_str_new(I_MIN(max_cps, 256)); str_sanitize_append_utf8(str, src, max_cps); return str_c(str); } dovecot-2.3.21.1/src/lib/test-str-find.c0000644000000000000000000000375214656633576014530 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str-find.h" static const char *str_find_text = "xababcd"; static bool test_str_find_substring(const char *key, int expected_pos) { const unsigned char *text = (const unsigned char *)str_find_text; const unsigned int text_len = strlen(str_find_text); struct str_find_context *ctx; unsigned int i, j, pos, max, offset; bool ret; ctx = str_find_init(pool_datastack_create(), key); /* divide text into every possible block combination and test that it matches */ i_assert(text_len > 0); max = 1U << (text_len-1); for (i = 0; i < max; i++) { str_find_reset(ctx); pos = 0; offset = 0; ret = FALSE; for (j = 0; j < text_len; j++) { if ((i & (1 << j)) != 0) { if (str_find_more(ctx, text+pos, j-pos+1)) { ret = TRUE; break; } offset += j-pos + 1; pos = j + 1; } } if (pos != text_len && !ret) { if (str_find_more(ctx, text+pos, j-pos)) ret = TRUE; } if (expected_pos < 0) { if (ret) return FALSE; } else { if (!ret) return FALSE; pos = str_find_get_match_end_pos(ctx) + offset - strlen(key); if ((int)pos != expected_pos) return FALSE; } } return TRUE; } struct str_find_input { const char *str; int pos; }; void test_str_find(void) { static const char *fail_input[] = { "xabc", "xabd", "abd" }; unsigned int idx, len; const char *key, *p; unsigned int i; bool success = TRUE; for (idx = 0; idx < strlen(str_find_text); idx++) { for (len = strlen(str_find_text)-idx; len > 0; len--) { /* we'll get a search key for all substrings of text */ T_BEGIN { key = t_strndup(str_find_text + idx, len); p = strstr(str_find_text, key); success = test_str_find_substring(key, p - str_find_text); } T_END; if (!success) break; } } for (i = 0; i < N_ELEMENTS(fail_input) && success; i++) success = test_str_find_substring(fail_input[i], -1); test_out("str_find()", success); } dovecot-2.3.21.1/src/lib/read-full.c0000644000000000000000000000124714656633576013675 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "read-full.h" #include int read_full(int fd, void *data, size_t size) { ssize_t ret; while (size > 0) { ret = read(fd, data, size < SSIZE_T_MAX ? size : SSIZE_T_MAX); if (ret <= 0) return ret; data = PTR_OFFSET(data, ret); size -= ret; } return 1; } int pread_full(int fd, void *data, size_t size, off_t offset) { ssize_t ret; while (size > 0) { ret = pread(fd, data, size < SSIZE_T_MAX ? size : SSIZE_T_MAX, offset); if (ret <= 0) return ret; data = PTR_OFFSET(data, ret); size -= ret; offset += ret; } return 1; } dovecot-2.3.21.1/src/lib/unlink-directory.h0000644000000000000000000000140114656633576015321 00000000000000#ifndef UNLINK_DIRECTORY_H #define UNLINK_DIRECTORY_H enum unlink_directory_flags { /* After unlinking all files, rmdir() the directory itself */ UNLINK_DIRECTORY_FLAG_RMDIR = 0x01, /* Don't unlink any files beginning with "." */ UNLINK_DIRECTORY_FLAG_SKIP_DOTFILES = 0x02, /* Don't recurse into subdirectories */ UNLINK_DIRECTORY_FLAG_FILES_ONLY = 0x04 }; /* Unlink directory and/or everything under it. Returns 1 if successful, 0 if error is ENOENT, -1 if other error. The returned error message contains the exact syscall that failed, e.g. "open(path) failed: Permission denied" In case of ENOENT error, error message is also set. */ int unlink_directory(const char *dir, enum unlink_directory_flags flags, const char **error_r); #endif dovecot-2.3.21.1/src/lib/test-failures.c0000644000000000000000000001067214656633576014613 00000000000000/* Copyright (c) 2001-2018 Dovecot authors, see the included COPYING file */ /* Unit tests for failure helpers */ #include "test-lib.h" #include "hostpid.h" #include "istream.h" #include "failures.h" #include static int handlers_set_me; static void test_failures_handler(const struct failure_context *ctx, const char *format ATTR_UNUSED, va_list args ATTR_UNUSED) { handlers_set_me = ctx->type; } static void test_get_set_handlers(void) { failure_callback_t *handlers[4]; test_begin("get_handlers"); i_get_failure_handlers(handlers, handlers+1, handlers+2, handlers+3); test_end(); test_begin("set_handlers"); i_set_debug_handler(&test_failures_handler); i_debug("If you see this debug, something's gone wrong"); test_assert(handlers_set_me == LOG_TYPE_DEBUG); i_set_debug_handler(handlers[3]); i_set_info_handler(&test_failures_handler); i_info("If you see this info, something's gone wrong"); test_assert(handlers_set_me == LOG_TYPE_INFO); i_set_info_handler(handlers[2]); i_set_error_handler(&test_failures_handler); i_warning("If you see this warning, something's gone wrong"); test_assert(handlers_set_me == LOG_TYPE_WARNING); i_error("If you see this error, something's gone wrong"); test_assert(handlers_set_me == LOG_TYPE_ERROR); i_set_error_handler(handlers[1]); //i_set_fatal_handler(&test_failures_handler); //i_fatal("If you see this fatal, something's gone wrong"); //test_assert(handlers_set_me == LOG_TYPE_FATAL); //i_set_fatal_handler(handlers[0]); test_end(); } static void test_expected(void) { test_begin("expected messages"); test_expect_errors(1); i_warning("deliberate warning - not suppressed"); test_expect_no_more_errors(); test_end(); } static void test_expected_str(void) { test_begin("expected strings in messages"); test_expect_error_string("be unhappy"); i_error("deliberate error - suppressed - be unhappy if you see this"); test_expect_no_more_errors(); test_end(); } static bool internal_line_match(const char *line, const char *prefix, const char *text) { if (line == NULL) return FALSE; if (line[0] != '\001') return FALSE; uint8_t type = (uint8_t)line[1]; if (type != ((LOG_TYPE_DEBUG+1) | 0x80)) return FALSE; line += 2; if (!str_begins(line, "123 ")) return FALSE; line += 4; if (!str_begins(line, prefix)) return FALSE; line += strlen(prefix); return strcmp(line, text) == 0; } static void test_internal_split(void) { int fd[2]; test_begin("splitting long internal log lines"); char long_log_prefix[PIPE_BUF+1]; memset(long_log_prefix, 'X', sizeof(long_log_prefix)-1); long_log_prefix[sizeof(long_log_prefix)-1] = '\0'; #define TEXT10 "tttttttttt" #define TEXT128 TEXT10 TEXT10 TEXT10 TEXT10 TEXT10 TEXT10 TEXT10 TEXT10 \ TEXT10 TEXT10 TEXT10 TEXT10 "tttttttt" char long_lext[PIPE_BUF*2+1]; memset(long_lext, 'T', sizeof(long_lext)-1); long_lext[sizeof(long_lext)-1] = '\0'; if (pipe(fd) < 0) i_fatal("pipe() failed: %m"); switch (fork()) { case (pid_t)-1: i_fatal("fork() failed: %m"); case 0: /* child - log writer */ if (dup2(fd[1], STDERR_FILENO) < 0) i_fatal("dup2() failed: %m"); i_close_fd(&fd[0]); i_close_fd(&fd[1]); struct failure_context ctx = { .type = LOG_TYPE_DEBUG, .log_prefix = long_log_prefix, }; i_set_failure_internal(); my_pid = "123"; i_log_type(&ctx, "little text"); i_log_type(&ctx, TEXT128 TEXT128 TEXT128); ctx.log_prefix = ""; i_log_type(&ctx, "%s", long_lext); test_exit(0); case 1: /* parent - log reader */ i_close_fd(&fd[1]); break; } alarm(10); struct istream *input = i_stream_create_fd(fd[0], SIZE_MAX); /* long prefix, little text */ const char *line = i_stream_read_next_line(input); test_assert(internal_line_match(line, long_log_prefix, "little text")); /* long prefix, text split to multiple lines */ for (unsigned int i = 0; i < 3; i++) { line = i_stream_read_next_line(input); test_assert(internal_line_match(line, long_log_prefix, TEXT128)); } /* no prefix, just lots of text */ line = i_stream_read_next_line(input); long_lext[PIPE_BUF-7] = '\0'; test_assert(internal_line_match(line, "", long_lext)); line = i_stream_read_next_line(input); test_assert(internal_line_match(line, "", long_lext)); line = i_stream_read_next_line(input); test_assert(internal_line_match(line, "", "TTTTTTTTTTTTTT")); i_stream_unref(&input); alarm(0); test_end(); } void test_failures(void) { test_get_set_handlers(); test_expected(); test_expected_str(); test_internal_split(); } dovecot-2.3.21.1/src/lib/istream-file-private.h0000644000000000000000000000102414656633576016051 00000000000000#ifndef ISTREAM_FILE_PRIVATE_H #define ISTREAM_FILE_PRIVATE_H #include "istream-private.h" struct file_istream { struct istream_private istream; uoff_t skip_left; bool file:1; bool autoclose_fd:1; bool seen_eof:1; }; struct istream * i_stream_create_file_common(struct file_istream *fstream, int fd, const char *path, size_t max_buffer_size, bool autoclose_fd); ssize_t i_stream_file_read(struct istream_private *stream); void i_stream_file_close(struct iostream_private *stream, bool close_parent); #endif dovecot-2.3.21.1/src/lib/restrict-access.h0000644000000000000000000000670314656633576015127 00000000000000#ifndef RESTRICT_ACCESS_H #define RESTRICT_ACCESS_H enum restrict_access_flags { /* If flags given to restrict_access() include * RESTRICT_ACCESS_FLAG_ALLOW_ROOT, we won't kill * ourself when we have root privileges. */ RESTRICT_ACCESS_FLAG_ALLOW_ROOT = 1, }; struct restrict_access_settings { /* UID to use, or (uid_t)-1 if you don't want to change it */ uid_t uid; /* Effective GID to use, or (gid_t)-1 if you don't want to change it */ gid_t gid; /* If not (gid_t)-1, the privileged GID can be temporarily enabled/disabled. */ gid_t privileged_gid; /* Add access to these space or comma -separated extra groups */ const char *extra_groups; /* Add access to groups this system user belongs to */ const char *system_groups_user; /* All specified GIDs must be in this range. If extra_groups or system group user contains other GIDs, they're silently dropped. */ gid_t first_valid_gid, last_valid_gid; /* Human readable "source" of UID and GID values. If non-NULL, displayed on error messages about failing to change uid/gid. */ const char *uid_source, *gid_source; /* Chroot directory */ const char *chroot_dir; /* Allow running in setuid-root mode, where real UID is root and * effective UID is non-root. By default the real UID is changed * to be the same as the effective UID. */ bool allow_setuid_root; }; /* Initialize settings with values that don't change anything. */ void restrict_access_init(struct restrict_access_settings *set); /* Restrict access as specified by the settings. If home is not NULL, it's chdir()ed after chrooting, otherwise it chdirs to / (the chroot). */ void restrict_access(const struct restrict_access_settings *set, enum restrict_access_flags flags, const char *home) ATTR_NULL(3); /* Set environment variables so they can be read with restrict_access_by_env(). */ void restrict_access_set_env(const struct restrict_access_settings *set); /* Read restrict_access_set_env() environments back into struct. */ void restrict_access_get_env(struct restrict_access_settings *set_r); /* Read restrictions from environment and call restrict_access(). If flags do not include RESTRICT_ACCESS_FLAG_ALLOW_ROOT, we'll kill ourself unless the RESTRICT_* environments caused root privileges to be dropped */ void restrict_access_by_env(enum restrict_access_flags flags, const char *home) ATTR_NULL(2); /* Return the chrooted directory if restrict_access*() chrooted, otherwise NULL. */ const char *restrict_access_get_current_chroot(void); /* Checks if PR_SET_DUMPABLE environment variable is set, and if it is, calls restrict_access_set_dumpable(allow). */ void restrict_access_allow_coredumps(bool allow); /* Sets process dumpable true or false. Setting this true allows core dumping, reading /proc/self/io, attaching with PTRACE_ATTACH, and also changes ownership of /proc/[pid] directory. */ void restrict_access_set_dumpable(bool allow); /* Gets process dumpability, returns TRUE if not supported, because we then assume that constraint is not present. */ bool restrict_access_get_dumpable(void); /* If privileged_gid was set, these functions can be used to temporarily gain access to the group. */ int restrict_access_use_priv_gid(void); void restrict_access_drop_priv_gid(void); /* Returns TRUE if privileged GID exists for this process. */ bool restrict_access_have_priv_gid(void); gid_t *restrict_get_groups_list(unsigned int *gid_count_r); void restrict_access_deinit(void); #endif dovecot-2.3.21.1/src/lib/path-util.c0000644000000000000000000002427314656633576013735 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "path-util.h" #include #include #include #define PATH_UTIL_MAX_PATH 8*1024 #define PATH_UTIL_MAX_SYMLINKS 80 static int t_getcwd_noalloc(char **dir_r, size_t *asize_r, const char **error_r) ATTR_NULL(2) { /* @UNSAFE */ char *dir; size_t asize = 128; dir = t_buffer_get(asize); while (getcwd(dir, asize) == NULL) { if (errno != ERANGE) { *error_r = t_strdup_printf("getcwd() failed: %m"); return -1; } asize = nearest_power(asize+1); dir = t_buffer_get(asize); } if (asize_r != NULL) *asize_r = asize; *dir_r = dir; return 0; } static int path_normalize(const char *path, bool resolve_links, const char **npath_r, const char **error_r) { /* @UNSAFE */ unsigned int link_count = 0; char *npath, *npath_pos; const char *p; size_t asize; i_assert(path != NULL); i_assert(npath_r != NULL); i_assert(error_r != NULL); if (path[0] != '/') { /* relative; initialize npath with current directory */ if (t_getcwd_noalloc(&npath, &asize, error_r) < 0) return -1; npath_pos = npath + strlen(npath); i_assert(npath[0] == '/'); } else { /* absolute; initialize npath with root */ asize = 128; npath = t_buffer_get(asize); npath[0] = '/'; npath_pos = npath + 1; } p = path; while (*p != '\0') { struct stat st; ptrdiff_t seglen; const char *segend; /* skip duplicate slashes */ while (*p == '/') p++; /* find end of path segment */ for (segend = p; *segend != '\0' && *segend != '/'; segend++); if (segend == p) break; /* '\0' */ seglen = segend - p; if (seglen == 1 && p[0] == '.') { /* a reference to this segment; nothing to do */ } else if (seglen == 2 && p[0] == '.' && p[1] == '.') { /* a reference to parent segment; back up to previous * slash */ i_assert(npath_pos >= npath); if ((npath_pos - npath) > 1) { if (*(npath_pos-1) == '/') npath_pos--; for (; *(npath_pos-1) != '/'; npath_pos--); } } else { /* allocate space if necessary */ i_assert(npath_pos >= npath); if ((size_t)((npath_pos - npath) + seglen + 1) >= asize) { ptrdiff_t npath_offset = npath_pos - npath; asize = nearest_power(npath_offset + seglen + 2); npath = t_buffer_reget(npath, asize); npath_pos = npath + npath_offset; } /* make sure npath now ends in slash */ i_assert(npath_pos > npath); if (*(npath_pos-1) != '/') { i_assert((size_t)((npath_pos - npath) + 1) < asize); *(npath_pos++) = '/'; } /* copy segment to normalized path */ i_assert(npath_pos >= npath); i_assert((size_t)((npath_pos - npath) + seglen) < asize); memmove(npath_pos, p, seglen); npath_pos += seglen; } if (resolve_links) { /* stat path up to here (segend points to tail) */ *npath_pos = '\0'; if (lstat(npath, &st) < 0) { *error_r = t_strdup_printf("lstat() failed: %m"); return -1; } if (S_ISLNK (st.st_mode)) { /* symlink */ char *npath_link; size_t lsize = 128, tlen = strlen(segend), espace; size_t ltlen = (link_count == 0 ? 0 : tlen); ssize_t ret; /* limit link dereferences */ if (++link_count > PATH_UTIL_MAX_SYMLINKS) { errno = ELOOP; *error_r = "Too many symlink dereferences"; return -1; } /* allocate space for preserving tail of previous symlink and first attempt at reading symlink with room for the tail buffer will look like this: [npath][0][preserved tail][link buffer][room for tail][0] */ espace = ltlen + tlen + 2; i_assert(npath_pos >= npath); if ((size_t)((npath_pos - npath) + espace + lsize) >= asize) { ptrdiff_t npath_offset = npath_pos - npath; asize = nearest_power((npath_offset + espace + lsize) + 1); lsize = asize - (npath_offset + espace); npath = t_buffer_reget(npath, asize); npath_pos = npath + npath_offset; } if (ltlen > 0) { /* preserve tail just after end of npath */ i_assert(npath_pos >= npath); i_assert((size_t)((npath_pos + 1 - npath) + ltlen) < asize); memmove(npath_pos + 1, segend, ltlen); } /* read the symlink after the preserved tail */ for (;;) { npath_link = (npath_pos + 1) + ltlen; i_assert(npath_link >= npath_pos); i_assert((size_t)((npath_link - npath) + lsize) < asize); /* attempt to read the link */ if ((ret=readlink(npath, npath_link, lsize)) < 0) { *error_r = t_strdup_printf("readlink() failed: %m"); return -1; } if ((size_t)ret < lsize) { /* POSIX doesn't guarantee the presence of a NIL */ npath_link[ret] = '\0'; break; } /* sum of new symlink content length * and path tail length may not exceed maximum */ if ((size_t)(ret + tlen) >= PATH_UTIL_MAX_PATH) { errno = ENAMETOOLONG; *error_r = "Resulting path is too long"; return -1; } /* try again with bigger buffer, we need to allocate more space as well if lsize == ret, because the returned link may have gotten truncated */ espace = ltlen + tlen + 2; i_assert(npath_pos >= npath); if ((size_t)((npath_pos - npath) + espace + lsize) >= asize || lsize == (size_t)ret) { ptrdiff_t npath_offset = npath_pos - npath; asize = nearest_power((npath_offset + espace + lsize) + 1); lsize = asize - (npath_offset + espace); npath = t_buffer_reget(npath, asize); npath_pos = npath + npath_offset; } } /* add tail of previous path at end of symlink */ i_assert(npath_link >= npath); if (ltlen > 0) { i_assert(npath_pos >= npath); i_assert((size_t)((npath_pos - npath) + 1 + tlen) < asize); i_assert((size_t)((npath_link - npath) + ret + tlen) < asize); memcpy(npath_link + ret, npath_pos + 1, tlen); } else { i_assert((size_t)((npath_link - npath) + ret + tlen) < asize); memcpy(npath_link + ret, segend, tlen); } *(npath_link+ret+tlen) = '\0'; /* use as new source path */ path = segend = npath_link; if (path[0] == '/') { /* absolute symlink; start over at root */ npath_pos = npath + 1; } else { /* relative symlink; back up to previous segment */ i_assert(npath_pos >= npath); if ((npath_pos - npath) > 1) { if (*(npath_pos-1) == '/') npath_pos--; for (; *(npath_pos-1) != '/'; npath_pos--); } } } else if (*segend != '\0' && !S_ISDIR (st.st_mode)) { /* not last segment, but not a directory either */ errno = ENOTDIR; *error_r = t_strdup_printf("Not a directory: %s", npath); return -1; } } p = segend; } i_assert(npath_pos >= npath); i_assert((size_t)(npath_pos - npath) < asize); /* remove any trailing slash */ if ((npath_pos - npath) > 1 && *(npath_pos-1) == '/') npath_pos--; *npath_pos = '\0'; t_buffer_alloc(npath_pos - npath + 1); *npath_r = npath; return 0; } int t_normpath(const char *path, const char **npath_r, const char **error_r) { return path_normalize(path, FALSE, npath_r, error_r); } int t_normpath_to(const char *path, const char *root, const char **npath_r, const char **error_r) { i_assert(path != NULL); i_assert(root != NULL); i_assert(npath_r != NULL); if (*path == '/') return t_normpath(path, npath_r, error_r); return t_normpath(t_strconcat(root, "/", path, NULL), npath_r, error_r); } int t_realpath(const char *path, const char **npath_r, const char **error_r) { return path_normalize(path, TRUE, npath_r, error_r); } int t_realpath_to(const char *path, const char *root, const char **npath_r, const char **error_r) { i_assert(path != NULL); i_assert(root != NULL); i_assert(npath_r != NULL); if (*path == '/') return t_realpath(path, npath_r, error_r); return t_realpath(t_strconcat(root, "/", path, NULL), npath_r, error_r); } int t_abspath(const char *path, const char **abspath_r, const char **error_r) { i_assert(path != NULL); i_assert(abspath_r != NULL); i_assert(error_r != NULL); if (*path == '/') { *abspath_r = path; return 0; } const char *dir, *error; if (t_get_working_dir(&dir, &error) < 0) { *error_r = t_strconcat("Failed to get working directory: ", error, NULL); return -1; } *abspath_r = t_strconcat(dir, "/", path, NULL); return 0; } const char *t_abspath_to(const char *path, const char *root) { i_assert(path != NULL); i_assert(root != NULL); if (*path == '/') return path; return t_strconcat(root, "/", path, NULL); } int t_get_working_dir(const char **dir_r, const char **error_r) { char *dir; i_assert(dir_r != NULL); i_assert(error_r != NULL); if (t_getcwd_noalloc(&dir, NULL, error_r) < 0) return -1; t_buffer_alloc(strlen(dir) + 1); *dir_r = dir; return 0; } int t_readlink(const char *path, const char **dest_r, const char **error_r) { i_assert(error_r != NULL); /* @UNSAFE */ ssize_t ret; char *dest; size_t size = 128; dest = t_buffer_get(size); while ((ret = readlink(path, dest, size)) >= (ssize_t)size) { size = nearest_power(size+1); dest = t_buffer_get(size); } if (ret < 0) { *error_r = t_strdup_printf("readlink() failed: %m"); return -1; } dest[ret] = '\0'; t_buffer_alloc(ret + 1); *dest_r = dest; return 0; } bool t_binary_abspath(const char **binpath, const char **error_r) { const char *path_env, *const *paths; string_t *path; if (**binpath == '/') { /* already have absolute path */ return TRUE; } else if (strchr(*binpath, '/') != NULL) { /* relative to current directory */ const char *error; if (t_abspath(*binpath, binpath, &error) < 0) { *error_r = t_strdup_printf("t_abspath(%s) failed: %s", *binpath, error); return FALSE; } return TRUE; } else if ((path_env = getenv("PATH")) != NULL) { /* we have to find our executable from path */ path = t_str_new(256); paths = t_strsplit(path_env, ":"); for (; *paths != NULL; paths++) { str_append(path, *paths); str_append_c(path, '/'); str_append(path, *binpath); if (access(str_c(path), X_OK) == 0) { *binpath = str_c(path); return TRUE; } str_truncate(path, 0); } *error_r = "Could not find the wanted executable from PATH"; return FALSE; } else { *error_r = "PATH environment variable undefined"; return FALSE; } } dovecot-2.3.21.1/src/lib/compat.h0000644000000000000000000002267514656633576013322 00000000000000#ifndef COMPAT_H #define COMPAT_H /* _ILP32 and _LP64 are common but not universal, make sure that exactly one of them is defined. */ #if !defined(_ILP32) && \ (SIZEOF_INT == 4) && (SIZEOF_LONG == 4) && (SIZEOF_VOID_P == 4) # define _ILP32 #endif #if !defined(_LP64) && \ (SIZEOF_INT == 4) && (SIZEOF_LONG == 8) && (SIZEOF_VOID_P == 8) # define _LP64 #endif #if defined(_ILP32) && defined(_LP64) # error "Cannot have both _ILP32 and _LP64 defined" #elif !defined(_ILP32) && !defined(_LP64) # error "Must have one of _ILP32 and _LP64 defined" #endif /* well, this is obviously wrong since it assumes it's 64bit, but older GCCs don't define it and we really want it. */ #ifndef LLONG_MAX # define LLONG_MAX 9223372036854775807LL #endif #if ((__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ > 3)) && \ defined(HAVE_TYPEOF)) && !defined(__cplusplus) # define HAVE_TYPE_CHECKS #endif /* We really want NULL to be a pointer, since we have various type-checks that may result in compiler warnings/errors if it's not. Do this only when type checking is used - it's not otherwise needed and causes compiling problems with e.g. Sun C compiler. */ #ifdef HAVE_TYPE_CHECKS # undef NULL # define NULL ((void *)0) #endif #ifndef __has_extension #define __has_extension(x) 0 // Compatibility with non-clang compilers. #endif #if (defined(__GNUC__) && __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) || \ (defined(__clang__) && (__has_extension(attribute_deprecated_with_message))) # define HAVE_ATTR_DEPRECATED int rand(void) __attribute__((deprecated("Do not use rand, use i_rand"))); int rand_r(unsigned int*) __attribute__((deprecated("Do not use rand_r, use i_rand"))); #endif #ifndef __cplusplus #ifdef HAVE__BOOL typedef _Bool bool; #else typedef int bool; #endif #endif #if defined (HAVE_UOFF_T) /* native support */ #elif defined (UOFF_T_INT) typedef unsigned int uoff_t; #elif defined (UOFF_T_LONG) typedef unsigned long uoff_t; #elif defined (UOFF_T_LONG_LONG) typedef unsigned long long uoff_t; #else # error uoff_t size not set #endif #ifndef HAVE_UINTMAX_T # if SIZEOF_LONG_LONG > 0 typedef unsigned long long uintmax_t; # else typedef unsigned long uintmax_t; # endif #endif #ifndef HAVE_UINT_FAST32_T # if SIZEOF_INT >= 4 typedef unsigned int uint_fast32_t; # else typedef unsigned long uint_fast32_t; # endif #endif #ifndef HAVE_SOCKLEN_T typedef int socklen_t; #endif /* WORDS_BIGENDIAN needs to be undefined if not enabled */ #if defined(WORDS_BIGENDIAN) && WORDS_BIGENDIAN == 0 # undef WORDS_BIGENDIAN #endif #ifdef HAVE_SYS_SYSMACROS_H # include # ifdef HAVE_SYS_MKDEV_H # include /* UnixWare */ # endif # define CMP_DEV_T(a, b) (major(a) == major(b) && minor(a) == minor(b)) #elif !defined (DEV_T_STRUCT) # define CMP_DEV_T(a, b) ((a) == (b)) #else # error I do not know how to compare dev_t #endif #ifdef HAVE_STAT_XTIM # define HAVE_ST_NSECS # define ST_ATIME_NSEC(st) ((unsigned long)(st).st_atim.tv_nsec) # define ST_MTIME_NSEC(st) ((unsigned long)(st).st_mtim.tv_nsec) # define ST_CTIME_NSEC(st) ((unsigned long)(st).st_ctim.tv_nsec) #elif defined (HAVE_STAT_XTIMESPEC) # define HAVE_ST_NSECS # define ST_ATIME_NSEC(st) ((unsigned long)(st).st_atimespec.tv_nsec) # define ST_MTIME_NSEC(st) ((unsigned long)(st).st_mtimespec.tv_nsec) # define ST_CTIME_NSEC(st) ((unsigned long)(st).st_ctimespec.tv_nsec) #else # define ST_ATIME_NSEC(st) 0UL # define ST_MTIME_NSEC(st) 0UL # define ST_CTIME_NSEC(st) 0UL #endif #ifdef HAVE_ST_NSECS /* TRUE if a nanosecond timestamp from struct stat matches another nanosecond. If nanoseconds aren't supported in struct stat, returns always TRUE (useful with NFS if some hosts support nanoseconds and others don't). */ # define ST_NTIMES_EQUAL(ns1, ns2) ((ns1) == (ns2)) #else # define ST_NTIMES_EQUAL(ns1, ns2) TRUE #endif #define CMP_ST_MTIME(st1, st2) \ ((st1)->st_mtime == (st2)->st_mtime && \ ST_NTIMES_EQUAL(ST_MTIME_NSEC(*(st1)), ST_MTIME_NSEC(*(st2)))) #define CMP_ST_CTIME(st1, st2) \ ((st1)->st_ctime == (st2)->st_ctime && \ ST_NTIMES_EQUAL(ST_CTIME_NSEC(*(st1)), ST_CTIME_NSEC(*(st2)))) /* strcasecmp(), strncasecmp() */ #ifndef HAVE_STRCASECMP # ifdef HAVE_STRICMP # define strcasecmp stricmp # define strncasecmp strnicmp # else # define strcasecmp i_my_strcasecmp # define strncasecmp i_my_strncasecmp int i_my_strcasecmp(const char *s1, const char *s2); int i_my_strncasecmp(const char *s1, const char *s2, size_t max_chars); # endif #endif #ifndef HAVE_INET_ATON # include # include # include # define inet_aton i_my_inet_aton int i_my_inet_aton(const char *cp, struct in_addr *inp); #endif #ifndef HAVE_VSYSLOG # define vsyslog i_my_vsyslog void i_my_vsyslog(int priority, const char *format, va_list args); #endif #ifndef HAVE_GETPAGESIZE # define getpagesize i_my_getpagesize int i_my_getpagesize(void); #endif #ifndef HAVE_FDATASYNC # define fdatasync fsync #endif struct const_iovec { const void *iov_base; size_t iov_len; }; #ifndef HAVE_STRUCT_IOVEC struct iovec { void *iov_base; size_t iov_len; }; #endif /* IOV_MAX should be in limits.h nowadays. Linux still (2005) requires defining _XOPEN_SOURCE to get that value. UIO_MAXIOV works with it though, so use it instead. 16 is the lowest acceptable value for all OSes. */ #ifndef IOV_MAX # include # ifdef UIO_MAXIOV # define IOV_MAX UIO_MAXIOV # else # define IOV_MAX 16 # endif #endif #ifndef HAVE_WRITEV # define writev i_my_writev struct iovec; ssize_t i_my_writev(int fd, const struct iovec *iov, int iov_len); #endif #if !defined(HAVE_PREAD) || defined(PREAD_WRAPPERS) || defined(PREAD_BROKEN) # ifndef IN_COMPAT_C # define pread i_my_pread # define pwrite i_my_pwrite # endif ssize_t i_my_pread(int fd, void *buf, size_t count, off_t offset); ssize_t i_my_pwrite(int fd, const void *buf, size_t count, off_t offset); #endif #ifndef HAVE_SETEUID # define seteuid i_my_seteuid int i_my_seteuid(uid_t euid); #endif #ifndef HAVE_SETEGID # define setegid i_my_setegid int i_my_setegid(gid_t egid); #endif #ifndef HAVE_LIBGEN_H # define basename i_my_basename char *i_my_basename(char *path); #endif #ifdef HAVE_OLD_VSNPRINTF # include # define vsnprintf i_my_vsnprintf int i_my_vsnprintf(char *str, size_t size, const char *format, va_list ap); #endif #ifndef HAVE_CLOCK_GETTIME # include # undef CLOCK_REALTIME # define CLOCK_REALTIME 1 # define clock_gettime i_my_clock_gettime int i_my_clock_gettime(int clk_id, struct timespec *tp); #endif /* ctype.h isn't safe with signed chars, use our own instead if really needed */ #define i_toupper(x) ((char) toupper((int) (unsigned char) (x))) #define i_tolower(x) ((char) tolower((int) (unsigned char) (x))) #define i_isalnum(x) (isalnum((int) (unsigned char) (x)) != 0) #define i_isalpha(x) (isalpha((int) (unsigned char) (x)) != 0) #define i_isascii(x) (isascii((int) (unsigned char) (x)) != 0) #define i_isblank(x) (isblank((int) (unsigned char) (x)) != 0) #define i_iscntrl(x) (iscntrl((int) (unsigned char) (x)) != 0) #define i_isdigit(x) (isdigit((int) (unsigned char) (x)) != 0) #define i_isgraph(x) (isgraph((int) (unsigned char) (x)) != 0) #define i_islower(x) (islower((int) (unsigned char) (x)) != 0) #define i_isprint(x) (isprint((int) (unsigned char) (x)) != 0) #define i_ispunct(x) (ispunct((int) (unsigned char) (x)) != 0) #define i_isspace(x) (isspace((int) (unsigned char) (x)) != 0) #define i_isupper(x) (isupper((int) (unsigned char) (x)) != 0) #define i_isxdigit(x) (isxdigit((int) (unsigned char) (x)) != 0) #ifndef EOVERFLOW # define EOVERFLOW ERANGE #endif #ifdef EDQUOT # define ENOSPACE(errno) ((errno) == ENOSPC || (errno) == EDQUOT) # define ENOQUOTA(errno) ((errno) == EDQUOT) #else /* probably all modern OSes have EDQUOT, but just in case one doesn't assume that ENOSPC is the same as "over quota". */ # define ENOSPACE(errno) ((errno) == ENOSPC) # define ENOQUOTA(errno) ((errno) == ENOSPC) #endif /* EPERM is returned sometimes if device doesn't support such modification */ #ifdef EROFS # define ENOACCESS(errno) \ ((errno) == EACCES || (errno) == EROFS || (errno) == EPERM) #else # define ENOACCESS(errno) ((errno) == EACCES || (errno) == EPERM) #endif #define ENOTFOUND(errno) \ ((errno) == ENOENT || (errno) == ENOTDIR || \ (errno) == ELOOP || (errno) == ENAMETOOLONG) #define ECANTLINK(errno) \ ((errno) == EXDEV || (errno) == EMLINK || (errno) == EPERM) /* Returns TRUE if unlink() failed because it attempted to delete a directory */ #define UNLINK_EISDIR(errno) \ ((errno) == EPERM || /* POSIX */ \ (errno) == EISDIR) /* Linux */ /* EBUSY is given by some NFS implementations */ #define EDESTDIREXISTS(errno) \ ((errno) == EEXIST || (errno) == ENOTEMPTY || (errno) == EBUSY) /* fstat() returns ENOENT instead of ESTALE with some Linux versions */ #define ESTALE_FSTAT(errno) \ ((errno) == ESTALE || (errno) == ENOENT) #if !defined(_POSIX_SYNCHRONIZED_IO) && \ defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && \ (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1060) /* OS X Snow Leopard has fdatasync(), but no prototype for it. */ int fdatasync(int); #endif /* Try to keep IO operations at least this size */ #ifndef IO_BLOCK_SIZE # define IO_BLOCK_SIZE 8192 #endif /* Default size for data blocks transferred over the network */ #ifndef NET_BLOCK_SIZE # define NET_BLOCK_SIZE (128*1024) #endif #if !defined(PIPE_BUF) && defined(_POSIX_PIPE_BUF) # define PIPE_BUF (8 * _POSIX_PIPE_BUF) /* for HURD */ #endif #endif dovecot-2.3.21.1/src/lib/seq-set-builder.c0000644000000000000000000000622414656633576015027 00000000000000/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "seq-set-builder.h" struct seqset_builder { string_t *str; uint32_t last_seq; size_t last_seq_pos; size_t prefix_length; }; struct seqset_builder *seqset_builder_init(string_t *str) { struct seqset_builder *builder; builder = i_new(struct seqset_builder, 1); builder->str = str; builder->last_seq = 0; builder->prefix_length = str_len(str); builder->last_seq_pos = 0; return builder; } static void seqset_builder_append_one(struct seqset_builder *builder, uint32_t seq) { builder->last_seq_pos = str_len(builder->str)+1; str_printfa(builder->str, "%u,", seq); } static void seqset_builder_create_or_merge_range(struct seqset_builder *builder, uint32_t seq) { char delimiter = '\0'; i_assert(builder->last_seq_pos > builder->prefix_length); str_truncate(builder->str, builder->last_seq_pos-1); /* Get the delimiter from the builder string */ if (str_len(builder->str) > 0 && str_len(builder->str) - 1 > builder->prefix_length) delimiter = str_data(builder->str)[str_len(builder->str) - 1]; if (delimiter == ':') { seqset_builder_append_one(builder, seq); } else if (delimiter == ',' || delimiter == '\0') { str_printfa(builder->str, "%u:", builder->last_seq); builder->last_seq_pos = str_len(builder->str) + 1; str_printfa(builder->str, "%u,", seq); } else i_unreached(); return; } void seqset_builder_add(struct seqset_builder *builder, uint32_t seq) { if (builder->last_seq == 0) { /* No seq was yet appened so just append this one */ seqset_builder_append_one(builder, seq); } else if (builder->last_seq + 1 == seq) { /* This seq is following directly on the previous one try to create a range of seqs */ seqset_builder_create_or_merge_range(builder, seq); } else { /* Append this seq without creating a range */ seqset_builder_append_one(builder, seq); } builder->last_seq = seq; } bool seqset_builder_try_add(struct seqset_builder *builder, size_t max_len, uint32_t seq) { /* Length of this sequence to be appended */ unsigned int seq_len = 0; /* Buffer to use when calculating seq length as string */ char seq_str[MAX_INT_STRLEN]; /* Current length of the seq string */ unsigned int builder_str_len = str_len(builder->str); if (builder->last_seq + 1 == seq && builder_str_len + 1 <= max_len) { /* Following sequence: This seq can't grow the overall length by more than one. */ seqset_builder_add(builder, seq); return TRUE; } if (builder_str_len + MAX_INT_STRLEN + 1 <= max_len) { /* Appending the maximum length of a sequence number and ',' still fits into max_len. There is no need to check the actual length. */ seqset_builder_add(builder, seq); return TRUE; } seq_len = strlen(dec2str_buf(seq_str, seq)) + 1; if (seq_len + builder_str_len > max_len) return FALSE; seqset_builder_add(builder, seq); return TRUE; } void seqset_builder_deinit(struct seqset_builder **builder) { /* If anything was appened to the string remove the trailing ',' */ if ((*builder)->last_seq != 0) str_truncate((*builder)->str, str_len((*builder)->str) - 1); i_free(*builder); } dovecot-2.3.21.1/src/lib/file-set-size.c0000644000000000000000000000450714656633576014504 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef HAVE_POSIX_FALLOCATE # define _XOPEN_SOURCE 600 /* Required by glibc, breaks Solaris 9 */ #endif #define _GNU_SOURCE /* for fallocate() */ #include "lib.h" #include "file-set-size.h" #include #include #include #if defined(HAVE_LINUX_FALLOC_H) && !defined(FALLOC_FL_KEEP_SIZE) /* Legacy Linux does not have the FALLOC_FL_* flags under fcntl.h */ # include #endif int file_set_size(int fd, off_t size) { #ifdef HAVE_POSIX_FALLOCATE static bool posix_fallocate_supported = TRUE; #endif char block[IO_BLOCK_SIZE]; off_t offset; ssize_t ret; struct stat st; i_assert(size >= 0); if (fstat(fd, &st) < 0) { i_error("fstat() failed: %m"); return -1; } if (size < st.st_size) { if (ftruncate(fd, size) < 0) { i_error("ftruncate() failed: %m"); return -1; } return 0; } if (size == st.st_size) return 0; #ifdef HAVE_POSIX_FALLOCATE if (posix_fallocate_supported) { int err; err = posix_fallocate(fd, st.st_size, size - st.st_size); if (err == 0) return 0; if (err != EINVAL /* Solaris */ && err != EOPNOTSUPP /* AOX */) { if (!ENOSPACE(err)) i_error("posix_fallocate() failed: %m"); return -1; } /* Not supported by kernel, fallback to writing. */ posix_fallocate_supported = FALSE; } #endif /* start growing the file */ offset = st.st_size; memset(block, 0, I_MIN((ssize_t)sizeof(block), size - offset)); while (offset < size) { ret = pwrite(fd, block, I_MIN((ssize_t)sizeof(block), size - offset), offset); if (ret < 0) { if (!ENOSPACE(errno)) i_error("pwrite() failed: %m"); return -1; } offset += ret; } return 0; } int file_preallocate(int fd ATTR_UNUSED, off_t size ATTR_UNUSED) { #if defined(HAVE_FALLOCATE) && defined(FALLOC_FL_KEEP_SIZE) /* Linux */ if (fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, size) < 0) return errno == ENOSYS ? 0 : -1; return 1; #elif defined (F_PREALLOCATE) /* OSX */ fstore_t fs; i_zero(&fs); fs.fst_flags = F_ALLOCATECONTIG; fs.fst_posmode = F_PEOFPOSMODE; fs.fst_offset = 0; fs.fst_length = size; fs.fst_bytesalloc = 0; if (fcntl(fd, F_PREALLOCATE, &fs) < 0) return -1; return fs.fst_bytesalloc > 0 ? 1 : 0; #else return 0; #endif } dovecot-2.3.21.1/src/lib/module-dir.c0000644000000000000000000004006114656633576014060 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "sort.h" #include "module-dir.h" #ifdef HAVE_MODULES #include #include #include #include #ifndef RTLD_GLOBAL # define RTLD_GLOBAL 0 #endif #ifndef RTLD_NOW # define RTLD_NOW 0 #endif static const char *module_name_drop_suffix(const char *name); void *module_get_symbol_quiet(struct module *module, const char *symbol) { /* clear out old errors */ (void)dlerror(); return dlsym(module->handle, symbol); } void *module_get_symbol(struct module *module, const char *symbol) { const char *error; void *ret; ret = module_get_symbol_quiet(module, symbol); if (ret == NULL) { error = dlerror(); if (error != NULL) { i_error("module %s: dlsym(%s) failed: %s", module->path, symbol, error); ret = NULL; } } return ret; } static void *get_symbol(struct module *module, const char *symbol, bool quiet) { if (quiet) return module_get_symbol_quiet(module, symbol); return module_get_symbol(module, symbol); } static void module_free(struct module *module) { if (module->deinit != NULL && module->initialized) module->deinit(); /* dlclose()ing removes all symbols from valgrind's visibility. if GDB environment is set, don't actually unload the module (the GDB environment is used elsewhere too) */ if (getenv("GDB") == NULL) { if (dlclose(module->handle) != 0) i_error("dlclose(%s) failed: %m", module->path); } i_free(module->path); i_free(module->name); i_free(module); } static bool module_check_wrong_binary_dependency(const struct module_dir_load_settings *set, struct module *module, const char **error_r) { const char *symbol_name, *binary_dep, *const *names; string_t *errstr; if (set->binary_name == NULL) return TRUE; symbol_name = t_strconcat(module->name, "_binary_dependency", NULL); binary_dep = dlsym(module->handle, symbol_name); if (binary_dep == NULL) return TRUE; names = t_strsplit(binary_dep, " "); if (str_array_find(names, set->binary_name)) return TRUE; errstr = t_str_new(128); str_printfa(errstr, "Can't load plugin %s: " "Plugin is intended to be used only by ", module->name); if (names[1] == NULL) str_printfa(errstr, "%s binary", binary_dep); else str_printfa(errstr, "binaries: %s", binary_dep); str_printfa(errstr, " (we're %s)", set->binary_name); *error_r = str_c(errstr); return FALSE; } static bool module_check_missing_plugin_dependencies(const struct module_dir_load_settings *set, struct module *module, struct module *all_modules, const char **error_r) { const char **deps; struct module *m; string_t *errmsg; size_t len; deps = dlsym(module->handle, t_strconcat(module->name, "_dependencies", NULL)); if (deps == NULL) return TRUE; for (; *deps != NULL; deps++) { len = strlen(*deps); for (m = all_modules; m != NULL; m = m->next) { if (strncmp(m->name, *deps, len) == 0 && (m->name[len] == '\0' || strcmp(m->name+len, "_plugin") == 0)) break; } if (m == NULL) { errmsg = t_str_new(128); str_printfa(errmsg, "Plugin %s must be loaded also", *deps); if (set->setting_name != NULL) { str_printfa(errmsg, " (you must set: %s=$%s %s)", set->setting_name, set->setting_name, *deps); } *error_r = str_c(errmsg); return FALSE; } } return TRUE; } static void *quiet_dlopen(const char *path, int flags) { #ifndef __OpenBSD__ return dlopen(path, flags); #else void *handle; int fd; /* OpenBSD likes to print all "undefined symbol" errors to stderr. Hide them by sending them to /dev/null. */ fd = dup(STDERR_FILENO); if (fd == -1) i_fatal("dup() failed: %m"); if (dup2(dev_null_fd, STDERR_FILENO) < 0) i_fatal("dup2() failed: %m"); handle = dlopen(path, flags); if (dup2(fd, STDERR_FILENO) < 0) i_fatal("dup2() failed: %m"); if (close(fd) < 0) i_error("close() failed: %m"); return handle; #endif } static bool versions_equal(const char *str1, const char *str2) { while (*str1 == *str2) { if (*str1 == '\0' || *str1 == '(') return TRUE; str1++; str2++; } return FALSE; } static int module_load(const char *path, const char *name, const struct module_dir_load_settings *set, struct module *all_modules, struct module **module_r, const char **error_r) { void *handle; struct module *module; const char *const *module_version; void (*preinit)(void); *module_r = NULL; *error_r = NULL; if (set->ignore_dlopen_errors) { handle = quiet_dlopen(path, RTLD_GLOBAL | RTLD_NOW); if (handle == NULL) { if (set->debug) { i_debug("Skipping module %s, " "because dlopen() failed: %s " "(this is usually intentional, " "so just ignore this message)", name, dlerror()); } return 0; } } else { handle = dlopen(path, RTLD_GLOBAL | RTLD_NOW); if (handle == NULL) { *error_r = t_strdup_printf("dlopen() failed: %s", dlerror()); #ifdef RTLD_LAZY /* try to give a better error message by lazily loading the plugin and checking its dependencies */ handle = dlopen(path, RTLD_LAZY); if (handle == NULL) return -1; #else return -1; #endif } } module = i_new(struct module, 1); module->path = i_strdup(path); module->name = i_strdup(name); module->handle = handle; module_version = set->abi_version == NULL ? NULL : get_symbol(module, t_strconcat(name, "_version", NULL), TRUE); if (module_version != NULL && !versions_equal(*module_version, set->abi_version)) { *error_r = t_strdup_printf( "Module is for different ABI version %s (we have %s)", *module_version, set->abi_version); module_free(module); return -1; } /* get our init func */ module->init = (void (*)(struct module *)) get_symbol(module, t_strconcat(name, "_init", NULL), !set->require_init_funcs); module->deinit = (void (*)(void)) get_symbol(module, t_strconcat(name, "_deinit", NULL), !set->require_init_funcs); preinit = (void (*)(void)) get_symbol(module, t_strconcat(name, "_preinit", NULL), TRUE); if (preinit != NULL) preinit(); if ((module->init == NULL || module->deinit == NULL) && set->require_init_funcs) { *error_r = t_strdup_printf( "Module doesn't have %s function", module->init == NULL ? "init" : "deinit"); } else if (!module_check_wrong_binary_dependency(set, module, error_r)) { /* failed */ } else if (!module_check_missing_plugin_dependencies(set, module, all_modules, error_r)) { /* failed */ } if (*error_r != NULL) { module->deinit = NULL; module_free(module); return -1; } if (set->debug) i_debug("Module loaded: %s", path); *module_r = module; return 1; } static int module_name_cmp(const char *const *n1, const char *const *n2) { const char *s1 = *n1, *s2 = *n2; if (str_begins(s1, "lib")) s1 += 3; if (str_begins(s2, "lib")) s2 += 3; return strcmp(s1, s2); } static bool module_want_load(const struct module_dir_load_settings *set, const char **names, const char *name) { if (set->filter_callback != NULL) { if (!set->filter_callback(name, set->filter_context)) return FALSE; } if (names == NULL) return TRUE; for (; *names != NULL; names++) { if (strcmp(*names, name) == 0) { *names = ""; return TRUE; } } return FALSE; } static void check_duplicates(ARRAY_TYPE(const_string) *names, const char *name, const char *dir) { const char *const *names_p, *base_name, *tmp; unsigned int i, count; base_name = module_file_get_name(name); names_p = array_get(names, &count); for (i = 0; i < count; i++) T_BEGIN { tmp = module_file_get_name(names_p[i]); if (strcmp(tmp, base_name) == 0) i_fatal("Multiple files for module %s: %s/%s, %s/%s", base_name, dir, name, dir, names_p[i]); } T_END; } struct module *module_dir_find(struct module *modules, const char *name) { struct module *module; size_t len = strlen(name); for (module = modules; module != NULL; module = module->next) { if (strncmp(module->name, name, len) == 0) { if (module->name[len] == '\0' || strcmp(module->name + len, "_plugin") == 0) return module; } } return NULL; } static bool module_is_loaded(struct module *modules, const char *name) { return module_dir_find(modules, name) != NULL; } static void module_names_fix(const char **module_names) { unsigned int i, j; if (module_names[0] == NULL) return; /* allow giving the module names also in non-base form. convert them in here. */ for (i = 0; module_names[i] != NULL; i++) module_names[i] = module_file_get_name(module_names[i]); /* @UNSAFE: drop duplicates */ i_qsort(module_names, i, sizeof(*module_names), i_strcmp_p); for (i = j = 1; module_names[i] != NULL; i++) { if (strcmp(module_names[i-1], module_names[i]) != 0) module_names[j++] = module_names[i]; } module_names[j] = NULL; } static bool module_dir_is_all_loaded(struct module *old_modules, const char **module_names) { unsigned int i; for (i = 0; module_names[i] != NULL; i++) { if (!module_is_loaded(old_modules, module_names[i])) return FALSE; } return TRUE; } static int module_dir_load_real(struct module **_modules, const char *dir, const char **module_names, const struct module_dir_load_settings *set, char **error_r) { DIR *dirp; struct dirent *d; const char *name, *p, *error, *const *names_p; struct module *modules, *module, **module_pos, *old_modules = *_modules; unsigned int i, count; ARRAY_TYPE(const_string) names; pool_t pool; int ret; *error_r = NULL; if (module_names != NULL) { if (module_dir_is_all_loaded(old_modules, module_names)) return 0; } if (set->debug) i_debug("Loading modules from directory: %s", dir); dirp = opendir(dir); if (dirp == NULL) { *error_r = i_strdup_printf("opendir(%s) failed: %m", dir); if (module_names != NULL) { /* we were given a list of modules to load. we can't fail. */ return -1; } return errno == ENOENT ? 0 : -1; } pool = pool_alloconly_create("module loader", 4096); p_array_init(&names, pool, 32); modules = NULL; for (errno = 0; (d = readdir(dirp)) != NULL; errno = 0) { name = d->d_name; if (name[0] == '.') continue; p = strstr(name, MODULE_SUFFIX); if (p == NULL || strlen(p) != 3) continue; T_BEGIN { check_duplicates(&names, name, dir); } T_END; name = p_strdup(pool, d->d_name); array_push_back(&names, &name); } if (errno != 0) *error_r = i_strdup_printf("readdir(%s) failed: %m", dir); if (closedir(dirp) < 0 && *error_r == NULL) *error_r = i_strdup_printf("closedir(%s) failed: %m", dir); if (*error_r != NULL) { pool_unref(&pool); return -1; } array_sort(&names, module_name_cmp); names_p = array_get(&names, &count); modules = old_modules; module_pos = &modules; while (*module_pos != NULL) module_pos = &(*module_pos)->next; for (i = 0; i < count; i++) T_BEGIN { const char *path, *stripped_name, *suffixless_name; name = names_p[i]; stripped_name = module_file_get_name(name); suffixless_name = module_name_drop_suffix(stripped_name); if (!module_want_load(set, module_names, suffixless_name) || module_is_loaded(old_modules, suffixless_name)) module = NULL; else { path = t_strconcat(dir, "/", name, NULL); ret = module_load(path, stripped_name, set, modules, &module, &error); if (ret >= 0) ; else if (module_names != NULL) { *error_r = i_strdup_printf("Couldn't load required plugin %s: %s", path, error); i = count; } else { i_error("Couldn't load plugin %s: %s", path, error); } } if (module != NULL) { *module_pos = module; module_pos = &module->next; } } T_END; pool_unref(&pool); if (module_names != NULL && *error_r == NULL && !set->ignore_missing) { /* make sure all modules were found */ for (; *module_names != NULL; module_names++) { if (**module_names != '\0') { *error_r = i_strdup_printf("Plugin '%s' not found from directory %s", *module_names, dir); break; } } } *_modules = modules; return *error_r != NULL ? -1 : 0; } int module_dir_try_load_missing(struct module **modules, const char *dir, const char *module_names, const struct module_dir_load_settings *set, const char **error_r) { char *error = NULL; int ret; T_BEGIN { const char **arr = NULL; if (module_names != NULL) { arr = t_strsplit_spaces(module_names, ", "); module_names_fix(arr); } ret = module_dir_load_real(modules, dir, arr, set, &error); } T_END; *error_r = t_strdup(error); i_free(error); return ret; } struct module * module_dir_load_missing(struct module *old_modules, const char *dir, const char *module_names, const struct module_dir_load_settings *set) { struct module *new_modules = old_modules; const char *error; if (module_dir_try_load_missing(&new_modules, dir, module_names, set, &error) < 0) { if (module_names != NULL) i_fatal("%s", error); else i_error("%s", error); } return new_modules; } void module_dir_init(struct module *modules) { struct module *module; for (module = modules; module != NULL; module = module->next) { if (!module->initialized) { module->initialized = TRUE; if (module->init != NULL) T_BEGIN { module->init(module); } T_END; } } } void module_dir_deinit(struct module *modules) { struct module *module, **rev; unsigned int i, count = 0; for (module = modules; module != NULL; module = module->next) { if (module->deinit != NULL && module->initialized) count++; } if (count == 0) return; /* @UNSAFE: deinitialize in reverse order */ T_BEGIN { rev = t_new(struct module *, count); for (i = 0, module = modules; i < count; ) { if (module->deinit != NULL && module->initialized) { rev[count-i-1] = module; i++; } module = module->next; } for (i = 0; i < count; i++) { module = rev[i]; T_BEGIN { module->deinit(); } T_END; module->initialized = FALSE; } } T_END; } void module_dir_unload(struct module **modules) { struct module *module, *next; /* Call all modules' deinit() first, so that they may still call each others' functions. */ module_dir_deinit(*modules); for (module = *modules; module != NULL; module = next) { next = module->next; module_free(module); } *modules = NULL; } #else #ifndef MODULE_SUFFIX # define MODULE_SUFFIX ".so" /* just to avoid build failure */ #endif struct module * module_dir_load_missing(struct module *old_modules ATTR_UNUSED, const char *dir ATTR_UNUSED, const char *module_names, const struct module_dir_load_settings *set ATTR_UNUSED) { #define NO_SUPPORT_ERRSTR "Dynamically loadable module support not built in" if (module_names == NULL) i_error(NO_SUPPORT_ERRSTR); else { i_fatal(NO_SUPPORT_ERRSTR", can't load plugins: %s", module_names); } return NULL; } void module_dir_init(struct module *modules ATTR_UNUSED) { } void module_dir_deinit(struct module *modules ATTR_UNUSED) { } void module_dir_unload(struct module **modules ATTR_UNUSED) { } struct module *module_dir_find(struct module *modules ATTR_UNUSED, const char *name ATTR_UNUSED) { return NULL; } void *module_get_symbol(struct module *module ATTR_UNUSED, const char *symbol ATTR_UNUSED) { return NULL; } void *module_get_symbol_quiet(struct module *module ATTR_UNUSED, const char *symbol ATTR_UNUSED) { return NULL; } #endif struct module *module_dir_load(const char *dir, const char *module_names, const struct module_dir_load_settings *set) { return module_dir_load_missing(NULL, dir, module_names, set); } const char *module_file_get_name(const char *fname) { const char *p; /* [lib][nn_]name(.so) */ if (str_begins(fname, "lib")) fname += 3; for (p = fname; *p != '\0'; p++) { if (*p < '0' || *p > '9') break; } if (*p == '_') fname = p + 1; p = strstr(fname, MODULE_SUFFIX); if (p == NULL) return fname; return t_strdup_until(fname, p); } static const char *module_name_drop_suffix(const char *name) { size_t len; len = strlen(name); if (len > 7 && strcmp(name + len - 7, "_plugin") == 0) name = t_strndup(name, len - 7); return name; } const char *module_get_plugin_name(struct module *module) { return module_name_drop_suffix(module->name); } dovecot-2.3.21.1/src/lib/event-filter-private.h0000644000000000000000000000626214656633576016105 00000000000000#ifndef EVENT_FILTER_PRIVATE_H #define EVENT_FILTER_PRIVATE_H #include "event-filter.h" enum event_filter_node_op { /* leaf nodes */ EVENT_FILTER_OP_CMP_EQ = 1, EVENT_FILTER_OP_CMP_GT, EVENT_FILTER_OP_CMP_LT, EVENT_FILTER_OP_CMP_GE, EVENT_FILTER_OP_CMP_LE, /* internal nodes */ EVENT_FILTER_OP_AND, EVENT_FILTER_OP_OR, EVENT_FILTER_OP_NOT, }; struct event_filter { struct event_filter *prev, *next; pool_t pool; int refcount; ARRAY(struct event_filter_query_internal) queries; bool fragment; bool named_queries_only; }; enum event_filter_node_type { /* internal nodes */ EVENT_FILTER_NODE_TYPE_LOGIC = 1, /* children */ /* leaf nodes */ EVENT_FILTER_NODE_TYPE_EVENT_NAME_EXACT, /* str */ EVENT_FILTER_NODE_TYPE_EVENT_NAME_WILDCARD, /* str */ EVENT_FILTER_NODE_TYPE_EVENT_SOURCE_LOCATION, /* str + int */ EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY, /* cat */ EVENT_FILTER_NODE_TYPE_EVENT_FIELD_EXACT, /* field */ EVENT_FILTER_NODE_TYPE_EVENT_FIELD_WILDCARD, /* field */ }; enum event_filter_log_type { EVENT_FILTER_LOG_TYPE_DEBUG = BIT(0), EVENT_FILTER_LOG_TYPE_INFO = BIT(1), EVENT_FILTER_LOG_TYPE_WARNING = BIT(2), EVENT_FILTER_LOG_TYPE_ERROR = BIT(3), EVENT_FILTER_LOG_TYPE_FATAL = BIT(4), EVENT_FILTER_LOG_TYPE_PANIC = BIT(5), EVENT_FILTER_LOG_TYPE_ALL = 0xff, }; struct event_filter_node { enum event_filter_node_type type; enum event_filter_node_op op; /* internal node */ struct event_filter_node *children[2]; /* leaf node */ const char *str; uintmax_t intmax; struct { /* * We may be dealing with one of three situations: * * 1) the category is a special "log type" category * 2) the category is a "normal" category which is: * a) registered * b) not registered * * A "log type" category is always stored here as the * log_type enum value with the name and ptr members being * NULL. * * A regular category always has a name. Additionally, if * it is registered, the category pointer is non-NULL. */ enum event_filter_log_type log_type; const char *name; struct event_category *ptr; } category; struct event_field field; }; bool event_filter_category_to_log_type(const char *name, enum event_filter_log_type *log_type_r); /* lexer & parser state */ struct event_filter_parser_state { void *scanner; const char *input; size_t len; size_t pos; pool_t pool; struct event_filter_node *output; const char *error; bool has_event_name:1; }; int event_filter_parser_lex_init(void **scanner); int event_filter_parser_lex_destroy(void *yyscanner); int event_filter_parser_parse(struct event_filter_parser_state *state); void event_filter_parser_set_extra(void *user, void *yyscanner); void event_filter_parser_error(void *scan, const char *e); /* the following are exposed to allow for unit testing */ bool event_filter_query_match_eval(struct event_filter_node *node, struct event *event, const char *source_filename, unsigned int source_linenum, enum event_filter_log_type log_type); const char * event_filter_category_from_log_type(enum event_filter_log_type log_type); struct event_filter_node * event_filter_get_expr_for_testing(struct event_filter *filter, unsigned int *count_r); #endif dovecot-2.3.21.1/src/lib/ipwd.h0000644000000000000000000000116014656633576012764 00000000000000#ifndef IPWD_H #define IPWD_H #include #include /* Replacements for standard getpw/gr*(), fixing their ability to report errors properly. As with standard getpw/gr*(), second call overwrites data used by the first one. Functions return 1 if user/group is found, 0 if not or -1 if error (with errno set). */ int i_getpwnam(const char *name, struct passwd *pwd_r); int i_getpwuid(uid_t uid, struct passwd *pwd_r); int i_getgrnam(const char *name, struct group *grp_r); int i_getgrgid(gid_t gid, struct group *grp_r); /* Free memory used by above functions. */ void ipwd_deinit(void); #endif dovecot-2.3.21.1/src/lib/UnicodeData.txt0000644000000000000000000633565312716462145014603 000000000000000000;;Cc;0;BN;;;;;N;NULL;;;; 0001;;Cc;0;BN;;;;;N;START OF HEADING;;;; 0002;;Cc;0;BN;;;;;N;START OF TEXT;;;; 0003;;Cc;0;BN;;;;;N;END OF TEXT;;;; 0004;;Cc;0;BN;;;;;N;END OF TRANSMISSION;;;; 0005;;Cc;0;BN;;;;;N;ENQUIRY;;;; 0006;;Cc;0;BN;;;;;N;ACKNOWLEDGE;;;; 0007;;Cc;0;BN;;;;;N;BELL;;;; 0008;;Cc;0;BN;;;;;N;BACKSPACE;;;; 0009;;Cc;0;S;;;;;N;CHARACTER TABULATION;;;; 000A;;Cc;0;B;;;;;N;LINE FEED (LF);;;; 000B;;Cc;0;S;;;;;N;LINE TABULATION;;;; 000C;;Cc;0;WS;;;;;N;FORM FEED (FF);;;; 000D;;Cc;0;B;;;;;N;CARRIAGE RETURN (CR);;;; 000E;;Cc;0;BN;;;;;N;SHIFT OUT;;;; 000F;;Cc;0;BN;;;;;N;SHIFT IN;;;; 0010;;Cc;0;BN;;;;;N;DATA LINK ESCAPE;;;; 0011;;Cc;0;BN;;;;;N;DEVICE CONTROL ONE;;;; 0012;;Cc;0;BN;;;;;N;DEVICE CONTROL TWO;;;; 0013;;Cc;0;BN;;;;;N;DEVICE CONTROL THREE;;;; 0014;;Cc;0;BN;;;;;N;DEVICE CONTROL FOUR;;;; 0015;;Cc;0;BN;;;;;N;NEGATIVE ACKNOWLEDGE;;;; 0016;;Cc;0;BN;;;;;N;SYNCHRONOUS IDLE;;;; 0017;;Cc;0;BN;;;;;N;END OF TRANSMISSION BLOCK;;;; 0018;;Cc;0;BN;;;;;N;CANCEL;;;; 0019;;Cc;0;BN;;;;;N;END OF MEDIUM;;;; 001A;;Cc;0;BN;;;;;N;SUBSTITUTE;;;; 001B;;Cc;0;BN;;;;;N;ESCAPE;;;; 001C;;Cc;0;B;;;;;N;INFORMATION SEPARATOR FOUR;;;; 001D;;Cc;0;B;;;;;N;INFORMATION SEPARATOR THREE;;;; 001E;;Cc;0;B;;;;;N;INFORMATION SEPARATOR TWO;;;; 001F;;Cc;0;S;;;;;N;INFORMATION SEPARATOR ONE;;;; 0020;SPACE;Zs;0;WS;;;;;N;;;;; 0021;EXCLAMATION MARK;Po;0;ON;;;;;N;;;;; 0022;QUOTATION MARK;Po;0;ON;;;;;N;;;;; 0023;NUMBER SIGN;Po;0;ET;;;;;N;;;;; 0024;DOLLAR SIGN;Sc;0;ET;;;;;N;;;;; 0025;PERCENT SIGN;Po;0;ET;;;;;N;;;;; 0026;AMPERSAND;Po;0;ON;;;;;N;;;;; 0027;APOSTROPHE;Po;0;ON;;;;;N;APOSTROPHE-QUOTE;;;; 0028;LEFT PARENTHESIS;Ps;0;ON;;;;;Y;OPENING PARENTHESIS;;;; 0029;RIGHT PARENTHESIS;Pe;0;ON;;;;;Y;CLOSING PARENTHESIS;;;; 002A;ASTERISK;Po;0;ON;;;;;N;;;;; 002B;PLUS SIGN;Sm;0;ES;;;;;N;;;;; 002C;COMMA;Po;0;CS;;;;;N;;;;; 002D;HYPHEN-MINUS;Pd;0;ES;;;;;N;;;;; 002E;FULL STOP;Po;0;CS;;;;;N;PERIOD;;;; 002F;SOLIDUS;Po;0;CS;;;;;N;SLASH;;;; 0030;DIGIT ZERO;Nd;0;EN;;0;0;0;N;;;;; 0031;DIGIT ONE;Nd;0;EN;;1;1;1;N;;;;; 0032;DIGIT TWO;Nd;0;EN;;2;2;2;N;;;;; 0033;DIGIT THREE;Nd;0;EN;;3;3;3;N;;;;; 0034;DIGIT FOUR;Nd;0;EN;;4;4;4;N;;;;; 0035;DIGIT FIVE;Nd;0;EN;;5;5;5;N;;;;; 0036;DIGIT SIX;Nd;0;EN;;6;6;6;N;;;;; 0037;DIGIT SEVEN;Nd;0;EN;;7;7;7;N;;;;; 0038;DIGIT EIGHT;Nd;0;EN;;8;8;8;N;;;;; 0039;DIGIT NINE;Nd;0;EN;;9;9;9;N;;;;; 003A;COLON;Po;0;CS;;;;;N;;;;; 003B;SEMICOLON;Po;0;ON;;;;;N;;;;; 003C;LESS-THAN SIGN;Sm;0;ON;;;;;Y;;;;; 003D;EQUALS SIGN;Sm;0;ON;;;;;N;;;;; 003E;GREATER-THAN SIGN;Sm;0;ON;;;;;Y;;;;; 003F;QUESTION MARK;Po;0;ON;;;;;N;;;;; 0040;COMMERCIAL AT;Po;0;ON;;;;;N;;;;; 0041;LATIN CAPITAL LETTER A;Lu;0;L;;;;;N;;;;0061; 0042;LATIN CAPITAL LETTER B;Lu;0;L;;;;;N;;;;0062; 0043;LATIN CAPITAL LETTER C;Lu;0;L;;;;;N;;;;0063; 0044;LATIN CAPITAL LETTER D;Lu;0;L;;;;;N;;;;0064; 0045;LATIN CAPITAL LETTER E;Lu;0;L;;;;;N;;;;0065; 0046;LATIN CAPITAL LETTER F;Lu;0;L;;;;;N;;;;0066; 0047;LATIN CAPITAL LETTER G;Lu;0;L;;;;;N;;;;0067; 0048;LATIN CAPITAL LETTER H;Lu;0;L;;;;;N;;;;0068; 0049;LATIN CAPITAL LETTER I;Lu;0;L;;;;;N;;;;0069; 004A;LATIN CAPITAL LETTER J;Lu;0;L;;;;;N;;;;006A; 004B;LATIN CAPITAL LETTER K;Lu;0;L;;;;;N;;;;006B; 004C;LATIN CAPITAL LETTER L;Lu;0;L;;;;;N;;;;006C; 004D;LATIN CAPITAL LETTER M;Lu;0;L;;;;;N;;;;006D; 004E;LATIN CAPITAL LETTER N;Lu;0;L;;;;;N;;;;006E; 004F;LATIN CAPITAL LETTER O;Lu;0;L;;;;;N;;;;006F; 0050;LATIN CAPITAL LETTER P;Lu;0;L;;;;;N;;;;0070; 0051;LATIN CAPITAL LETTER Q;Lu;0;L;;;;;N;;;;0071; 0052;LATIN CAPITAL LETTER R;Lu;0;L;;;;;N;;;;0072; 0053;LATIN CAPITAL LETTER S;Lu;0;L;;;;;N;;;;0073; 0054;LATIN CAPITAL LETTER T;Lu;0;L;;;;;N;;;;0074; 0055;LATIN CAPITAL LETTER U;Lu;0;L;;;;;N;;;;0075; 0056;LATIN CAPITAL LETTER V;Lu;0;L;;;;;N;;;;0076; 0057;LATIN CAPITAL LETTER W;Lu;0;L;;;;;N;;;;0077; 0058;LATIN CAPITAL LETTER X;Lu;0;L;;;;;N;;;;0078; 0059;LATIN CAPITAL LETTER Y;Lu;0;L;;;;;N;;;;0079; 005A;LATIN CAPITAL LETTER Z;Lu;0;L;;;;;N;;;;007A; 005B;LEFT SQUARE BRACKET;Ps;0;ON;;;;;Y;OPENING SQUARE BRACKET;;;; 005C;REVERSE SOLIDUS;Po;0;ON;;;;;N;BACKSLASH;;;; 005D;RIGHT SQUARE BRACKET;Pe;0;ON;;;;;Y;CLOSING SQUARE BRACKET;;;; 005E;CIRCUMFLEX ACCENT;Sk;0;ON;;;;;N;SPACING CIRCUMFLEX;;;; 005F;LOW LINE;Pc;0;ON;;;;;N;SPACING UNDERSCORE;;;; 0060;GRAVE ACCENT;Sk;0;ON;;;;;N;SPACING GRAVE;;;; 0061;LATIN SMALL LETTER A;Ll;0;L;;;;;N;;;0041;;0041 0062;LATIN SMALL LETTER B;Ll;0;L;;;;;N;;;0042;;0042 0063;LATIN SMALL LETTER C;Ll;0;L;;;;;N;;;0043;;0043 0064;LATIN SMALL LETTER D;Ll;0;L;;;;;N;;;0044;;0044 0065;LATIN SMALL LETTER E;Ll;0;L;;;;;N;;;0045;;0045 0066;LATIN SMALL LETTER F;Ll;0;L;;;;;N;;;0046;;0046 0067;LATIN SMALL LETTER G;Ll;0;L;;;;;N;;;0047;;0047 0068;LATIN SMALL LETTER H;Ll;0;L;;;;;N;;;0048;;0048 0069;LATIN SMALL LETTER I;Ll;0;L;;;;;N;;;0049;;0049 006A;LATIN SMALL LETTER J;Ll;0;L;;;;;N;;;004A;;004A 006B;LATIN SMALL LETTER K;Ll;0;L;;;;;N;;;004B;;004B 006C;LATIN SMALL LETTER L;Ll;0;L;;;;;N;;;004C;;004C 006D;LATIN SMALL LETTER M;Ll;0;L;;;;;N;;;004D;;004D 006E;LATIN SMALL LETTER N;Ll;0;L;;;;;N;;;004E;;004E 006F;LATIN SMALL LETTER O;Ll;0;L;;;;;N;;;004F;;004F 0070;LATIN SMALL LETTER P;Ll;0;L;;;;;N;;;0050;;0050 0071;LATIN SMALL LETTER Q;Ll;0;L;;;;;N;;;0051;;0051 0072;LATIN SMALL LETTER R;Ll;0;L;;;;;N;;;0052;;0052 0073;LATIN SMALL LETTER S;Ll;0;L;;;;;N;;;0053;;0053 0074;LATIN SMALL LETTER T;Ll;0;L;;;;;N;;;0054;;0054 0075;LATIN SMALL LETTER U;Ll;0;L;;;;;N;;;0055;;0055 0076;LATIN SMALL LETTER V;Ll;0;L;;;;;N;;;0056;;0056 0077;LATIN SMALL LETTER W;Ll;0;L;;;;;N;;;0057;;0057 0078;LATIN SMALL LETTER X;Ll;0;L;;;;;N;;;0058;;0058 0079;LATIN SMALL LETTER Y;Ll;0;L;;;;;N;;;0059;;0059 007A;LATIN SMALL LETTER Z;Ll;0;L;;;;;N;;;005A;;005A 007B;LEFT CURLY BRACKET;Ps;0;ON;;;;;Y;OPENING CURLY BRACKET;;;; 007C;VERTICAL LINE;Sm;0;ON;;;;;N;VERTICAL BAR;;;; 007D;RIGHT CURLY BRACKET;Pe;0;ON;;;;;Y;CLOSING CURLY BRACKET;;;; 007E;TILDE;Sm;0;ON;;;;;N;;;;; 007F;;Cc;0;BN;;;;;N;DELETE;;;; 0080;;Cc;0;BN;;;;;N;;;;; 0081;;Cc;0;BN;;;;;N;;;;; 0082;;Cc;0;BN;;;;;N;BREAK PERMITTED HERE;;;; 0083;;Cc;0;BN;;;;;N;NO BREAK HERE;;;; 0084;;Cc;0;BN;;;;;N;;;;; 0085;;Cc;0;B;;;;;N;NEXT LINE (NEL);;;; 0086;;Cc;0;BN;;;;;N;START OF SELECTED AREA;;;; 0087;;Cc;0;BN;;;;;N;END OF SELECTED AREA;;;; 0088;;Cc;0;BN;;;;;N;CHARACTER TABULATION SET;;;; 0089;;Cc;0;BN;;;;;N;CHARACTER TABULATION WITH JUSTIFICATION;;;; 008A;;Cc;0;BN;;;;;N;LINE TABULATION SET;;;; 008B;;Cc;0;BN;;;;;N;PARTIAL LINE FORWARD;;;; 008C;;Cc;0;BN;;;;;N;PARTIAL LINE BACKWARD;;;; 008D;;Cc;0;BN;;;;;N;REVERSE LINE FEED;;;; 008E;;Cc;0;BN;;;;;N;SINGLE SHIFT TWO;;;; 008F;;Cc;0;BN;;;;;N;SINGLE SHIFT THREE;;;; 0090;;Cc;0;BN;;;;;N;DEVICE CONTROL STRING;;;; 0091;;Cc;0;BN;;;;;N;PRIVATE USE ONE;;;; 0092;;Cc;0;BN;;;;;N;PRIVATE USE TWO;;;; 0093;;Cc;0;BN;;;;;N;SET TRANSMIT STATE;;;; 0094;;Cc;0;BN;;;;;N;CANCEL CHARACTER;;;; 0095;;Cc;0;BN;;;;;N;MESSAGE WAITING;;;; 0096;;Cc;0;BN;;;;;N;START OF GUARDED AREA;;;; 0097;;Cc;0;BN;;;;;N;END OF GUARDED AREA;;;; 0098;;Cc;0;BN;;;;;N;START OF STRING;;;; 0099;;Cc;0;BN;;;;;N;;;;; 009A;;Cc;0;BN;;;;;N;SINGLE CHARACTER INTRODUCER;;;; 009B;;Cc;0;BN;;;;;N;CONTROL SEQUENCE INTRODUCER;;;; 009C;;Cc;0;BN;;;;;N;STRING TERMINATOR;;;; 009D;;Cc;0;BN;;;;;N;OPERATING SYSTEM COMMAND;;;; 009E;;Cc;0;BN;;;;;N;PRIVACY MESSAGE;;;; 009F;;Cc;0;BN;;;;;N;APPLICATION PROGRAM COMMAND;;;; 00A0;NO-BREAK SPACE;Zs;0;CS; 0020;;;;N;NON-BREAKING SPACE;;;; 00A1;INVERTED EXCLAMATION MARK;Po;0;ON;;;;;N;;;;; 00A2;CENT SIGN;Sc;0;ET;;;;;N;;;;; 00A3;POUND SIGN;Sc;0;ET;;;;;N;;;;; 00A4;CURRENCY SIGN;Sc;0;ET;;;;;N;;;;; 00A5;YEN SIGN;Sc;0;ET;;;;;N;;;;; 00A6;BROKEN BAR;So;0;ON;;;;;N;BROKEN VERTICAL BAR;;;; 00A7;SECTION SIGN;Po;0;ON;;;;;N;;;;; 00A8;DIAERESIS;Sk;0;ON; 0020 0308;;;;N;SPACING DIAERESIS;;;; 00A9;COPYRIGHT SIGN;So;0;ON;;;;;N;;;;; 00AA;FEMININE ORDINAL INDICATOR;Lo;0;L; 0061;;;;N;;;;; 00AB;LEFT-POINTING DOUBLE ANGLE QUOTATION MARK;Pi;0;ON;;;;;Y;LEFT POINTING GUILLEMET;;;; 00AC;NOT SIGN;Sm;0;ON;;;;;N;;;;; 00AD;SOFT HYPHEN;Cf;0;BN;;;;;N;;;;; 00AE;REGISTERED SIGN;So;0;ON;;;;;N;REGISTERED TRADE MARK SIGN;;;; 00AF;MACRON;Sk;0;ON; 0020 0304;;;;N;SPACING MACRON;;;; 00B0;DEGREE SIGN;So;0;ET;;;;;N;;;;; 00B1;PLUS-MINUS SIGN;Sm;0;ET;;;;;N;PLUS-OR-MINUS SIGN;;;; 00B2;SUPERSCRIPT TWO;No;0;EN; 0032;;2;2;N;SUPERSCRIPT DIGIT TWO;;;; 00B3;SUPERSCRIPT THREE;No;0;EN; 0033;;3;3;N;SUPERSCRIPT DIGIT THREE;;;; 00B4;ACUTE ACCENT;Sk;0;ON; 0020 0301;;;;N;SPACING ACUTE;;;; 00B5;MICRO SIGN;Ll;0;L; 03BC;;;;N;;;039C;;039C 00B6;PILCROW SIGN;Po;0;ON;;;;;N;PARAGRAPH SIGN;;;; 00B7;MIDDLE DOT;Po;0;ON;;;;;N;;;;; 00B8;CEDILLA;Sk;0;ON; 0020 0327;;;;N;SPACING CEDILLA;;;; 00B9;SUPERSCRIPT ONE;No;0;EN; 0031;;1;1;N;SUPERSCRIPT DIGIT ONE;;;; 00BA;MASCULINE ORDINAL INDICATOR;Lo;0;L; 006F;;;;N;;;;; 00BB;RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK;Pf;0;ON;;;;;Y;RIGHT POINTING GUILLEMET;;;; 00BC;VULGAR FRACTION ONE QUARTER;No;0;ON; 0031 2044 0034;;;1/4;N;FRACTION ONE QUARTER;;;; 00BD;VULGAR FRACTION ONE HALF;No;0;ON; 0031 2044 0032;;;1/2;N;FRACTION ONE HALF;;;; 00BE;VULGAR FRACTION THREE QUARTERS;No;0;ON; 0033 2044 0034;;;3/4;N;FRACTION THREE QUARTERS;;;; 00BF;INVERTED QUESTION MARK;Po;0;ON;;;;;N;;;;; 00C0;LATIN CAPITAL LETTER A WITH GRAVE;Lu;0;L;0041 0300;;;;N;LATIN CAPITAL LETTER A GRAVE;;;00E0; 00C1;LATIN CAPITAL LETTER A WITH ACUTE;Lu;0;L;0041 0301;;;;N;LATIN CAPITAL LETTER A ACUTE;;;00E1; 00C2;LATIN CAPITAL LETTER A WITH CIRCUMFLEX;Lu;0;L;0041 0302;;;;N;LATIN CAPITAL LETTER A CIRCUMFLEX;;;00E2; 00C3;LATIN CAPITAL LETTER A WITH TILDE;Lu;0;L;0041 0303;;;;N;LATIN CAPITAL LETTER A TILDE;;;00E3; 00C4;LATIN CAPITAL LETTER A WITH DIAERESIS;Lu;0;L;0041 0308;;;;N;LATIN CAPITAL LETTER A DIAERESIS;;;00E4; 00C5;LATIN CAPITAL LETTER A WITH RING ABOVE;Lu;0;L;0041 030A;;;;N;LATIN CAPITAL LETTER A RING;;;00E5; 00C6;LATIN CAPITAL LETTER AE;Lu;0;L;;;;;N;LATIN CAPITAL LETTER A E;;;00E6; 00C7;LATIN CAPITAL LETTER C WITH CEDILLA;Lu;0;L;0043 0327;;;;N;LATIN CAPITAL LETTER C CEDILLA;;;00E7; 00C8;LATIN CAPITAL LETTER E WITH GRAVE;Lu;0;L;0045 0300;;;;N;LATIN CAPITAL LETTER E GRAVE;;;00E8; 00C9;LATIN CAPITAL LETTER E WITH ACUTE;Lu;0;L;0045 0301;;;;N;LATIN CAPITAL LETTER E ACUTE;;;00E9; 00CA;LATIN CAPITAL LETTER E WITH CIRCUMFLEX;Lu;0;L;0045 0302;;;;N;LATIN CAPITAL LETTER E CIRCUMFLEX;;;00EA; 00CB;LATIN CAPITAL LETTER E WITH DIAERESIS;Lu;0;L;0045 0308;;;;N;LATIN CAPITAL LETTER E DIAERESIS;;;00EB; 00CC;LATIN CAPITAL LETTER I WITH GRAVE;Lu;0;L;0049 0300;;;;N;LATIN CAPITAL LETTER I GRAVE;;;00EC; 00CD;LATIN CAPITAL LETTER I WITH ACUTE;Lu;0;L;0049 0301;;;;N;LATIN CAPITAL LETTER I ACUTE;;;00ED; 00CE;LATIN CAPITAL LETTER I WITH CIRCUMFLEX;Lu;0;L;0049 0302;;;;N;LATIN CAPITAL LETTER I CIRCUMFLEX;;;00EE; 00CF;LATIN CAPITAL LETTER I WITH DIAERESIS;Lu;0;L;0049 0308;;;;N;LATIN CAPITAL LETTER I DIAERESIS;;;00EF; 00D0;LATIN CAPITAL LETTER ETH;Lu;0;L;;;;;N;;;;00F0; 00D1;LATIN CAPITAL LETTER N WITH TILDE;Lu;0;L;004E 0303;;;;N;LATIN CAPITAL LETTER N TILDE;;;00F1; 00D2;LATIN CAPITAL LETTER O WITH GRAVE;Lu;0;L;004F 0300;;;;N;LATIN CAPITAL LETTER O GRAVE;;;00F2; 00D3;LATIN CAPITAL LETTER O WITH ACUTE;Lu;0;L;004F 0301;;;;N;LATIN CAPITAL LETTER O ACUTE;;;00F3; 00D4;LATIN CAPITAL LETTER O WITH CIRCUMFLEX;Lu;0;L;004F 0302;;;;N;LATIN CAPITAL LETTER O CIRCUMFLEX;;;00F4; 00D5;LATIN CAPITAL LETTER O WITH TILDE;Lu;0;L;004F 0303;;;;N;LATIN CAPITAL LETTER O TILDE;;;00F5; 00D6;LATIN CAPITAL LETTER O WITH DIAERESIS;Lu;0;L;004F 0308;;;;N;LATIN CAPITAL LETTER O DIAERESIS;;;00F6; 00D7;MULTIPLICATION SIGN;Sm;0;ON;;;;;N;;;;; 00D8;LATIN CAPITAL LETTER O WITH STROKE;Lu;0;L;;;;;N;LATIN CAPITAL LETTER O SLASH;;;00F8; 00D9;LATIN CAPITAL LETTER U WITH GRAVE;Lu;0;L;0055 0300;;;;N;LATIN CAPITAL LETTER U GRAVE;;;00F9; 00DA;LATIN CAPITAL LETTER U WITH ACUTE;Lu;0;L;0055 0301;;;;N;LATIN CAPITAL LETTER U ACUTE;;;00FA; 00DB;LATIN CAPITAL LETTER U WITH CIRCUMFLEX;Lu;0;L;0055 0302;;;;N;LATIN CAPITAL LETTER U CIRCUMFLEX;;;00FB; 00DC;LATIN CAPITAL LETTER U WITH DIAERESIS;Lu;0;L;0055 0308;;;;N;LATIN CAPITAL LETTER U DIAERESIS;;;00FC; 00DD;LATIN CAPITAL LETTER Y WITH ACUTE;Lu;0;L;0059 0301;;;;N;LATIN CAPITAL LETTER Y ACUTE;;;00FD; 00DE;LATIN CAPITAL LETTER THORN;Lu;0;L;;;;;N;;;;00FE; 00DF;LATIN SMALL LETTER SHARP S;Ll;0;L;;;;;N;;;;; 00E0;LATIN SMALL LETTER A WITH GRAVE;Ll;0;L;0061 0300;;;;N;LATIN SMALL LETTER A GRAVE;;00C0;;00C0 00E1;LATIN SMALL LETTER A WITH ACUTE;Ll;0;L;0061 0301;;;;N;LATIN SMALL LETTER A ACUTE;;00C1;;00C1 00E2;LATIN SMALL LETTER A WITH CIRCUMFLEX;Ll;0;L;0061 0302;;;;N;LATIN SMALL LETTER A CIRCUMFLEX;;00C2;;00C2 00E3;LATIN SMALL LETTER A WITH TILDE;Ll;0;L;0061 0303;;;;N;LATIN SMALL LETTER A TILDE;;00C3;;00C3 00E4;LATIN SMALL LETTER A WITH DIAERESIS;Ll;0;L;0061 0308;;;;N;LATIN SMALL LETTER A DIAERESIS;;00C4;;00C4 00E5;LATIN SMALL LETTER A WITH RING ABOVE;Ll;0;L;0061 030A;;;;N;LATIN SMALL LETTER A RING;;00C5;;00C5 00E6;LATIN SMALL LETTER AE;Ll;0;L;;;;;N;LATIN SMALL LETTER A E;;00C6;;00C6 00E7;LATIN SMALL LETTER C WITH CEDILLA;Ll;0;L;0063 0327;;;;N;LATIN SMALL LETTER C CEDILLA;;00C7;;00C7 00E8;LATIN SMALL LETTER E WITH GRAVE;Ll;0;L;0065 0300;;;;N;LATIN SMALL LETTER E GRAVE;;00C8;;00C8 00E9;LATIN SMALL LETTER E WITH ACUTE;Ll;0;L;0065 0301;;;;N;LATIN SMALL LETTER E ACUTE;;00C9;;00C9 00EA;LATIN SMALL LETTER E WITH CIRCUMFLEX;Ll;0;L;0065 0302;;;;N;LATIN SMALL LETTER E CIRCUMFLEX;;00CA;;00CA 00EB;LATIN SMALL LETTER E WITH DIAERESIS;Ll;0;L;0065 0308;;;;N;LATIN SMALL LETTER E DIAERESIS;;00CB;;00CB 00EC;LATIN SMALL LETTER I WITH GRAVE;Ll;0;L;0069 0300;;;;N;LATIN SMALL LETTER I GRAVE;;00CC;;00CC 00ED;LATIN SMALL LETTER I WITH ACUTE;Ll;0;L;0069 0301;;;;N;LATIN SMALL LETTER I ACUTE;;00CD;;00CD 00EE;LATIN SMALL LETTER I WITH CIRCUMFLEX;Ll;0;L;0069 0302;;;;N;LATIN SMALL LETTER I CIRCUMFLEX;;00CE;;00CE 00EF;LATIN SMALL LETTER I WITH DIAERESIS;Ll;0;L;0069 0308;;;;N;LATIN SMALL LETTER I DIAERESIS;;00CF;;00CF 00F0;LATIN SMALL LETTER ETH;Ll;0;L;;;;;N;;;00D0;;00D0 00F1;LATIN SMALL LETTER N WITH TILDE;Ll;0;L;006E 0303;;;;N;LATIN SMALL LETTER N TILDE;;00D1;;00D1 00F2;LATIN SMALL LETTER O WITH GRAVE;Ll;0;L;006F 0300;;;;N;LATIN SMALL LETTER O GRAVE;;00D2;;00D2 00F3;LATIN SMALL LETTER O WITH ACUTE;Ll;0;L;006F 0301;;;;N;LATIN SMALL LETTER O ACUTE;;00D3;;00D3 00F4;LATIN SMALL LETTER O WITH CIRCUMFLEX;Ll;0;L;006F 0302;;;;N;LATIN SMALL LETTER O CIRCUMFLEX;;00D4;;00D4 00F5;LATIN SMALL LETTER O WITH TILDE;Ll;0;L;006F 0303;;;;N;LATIN SMALL LETTER O TILDE;;00D5;;00D5 00F6;LATIN SMALL LETTER O WITH DIAERESIS;Ll;0;L;006F 0308;;;;N;LATIN SMALL LETTER O DIAERESIS;;00D6;;00D6 00F7;DIVISION SIGN;Sm;0;ON;;;;;N;;;;; 00F8;LATIN SMALL LETTER O WITH STROKE;Ll;0;L;;;;;N;LATIN SMALL LETTER O SLASH;;00D8;;00D8 00F9;LATIN SMALL LETTER U WITH GRAVE;Ll;0;L;0075 0300;;;;N;LATIN SMALL LETTER U GRAVE;;00D9;;00D9 00FA;LATIN SMALL LETTER U WITH ACUTE;Ll;0;L;0075 0301;;;;N;LATIN SMALL LETTER U ACUTE;;00DA;;00DA 00FB;LATIN SMALL LETTER U WITH CIRCUMFLEX;Ll;0;L;0075 0302;;;;N;LATIN SMALL LETTER U CIRCUMFLEX;;00DB;;00DB 00FC;LATIN SMALL LETTER U WITH DIAERESIS;Ll;0;L;0075 0308;;;;N;LATIN SMALL LETTER U DIAERESIS;;00DC;;00DC 00FD;LATIN SMALL LETTER Y WITH ACUTE;Ll;0;L;0079 0301;;;;N;LATIN SMALL LETTER Y ACUTE;;00DD;;00DD 00FE;LATIN SMALL LETTER THORN;Ll;0;L;;;;;N;;;00DE;;00DE 00FF;LATIN SMALL LETTER Y WITH DIAERESIS;Ll;0;L;0079 0308;;;;N;LATIN SMALL LETTER Y DIAERESIS;;0178;;0178 0100;LATIN CAPITAL LETTER A WITH MACRON;Lu;0;L;0041 0304;;;;N;LATIN CAPITAL LETTER A MACRON;;;0101; 0101;LATIN SMALL LETTER A WITH MACRON;Ll;0;L;0061 0304;;;;N;LATIN SMALL LETTER A MACRON;;0100;;0100 0102;LATIN CAPITAL LETTER A WITH BREVE;Lu;0;L;0041 0306;;;;N;LATIN CAPITAL LETTER A BREVE;;;0103; 0103;LATIN SMALL LETTER A WITH BREVE;Ll;0;L;0061 0306;;;;N;LATIN SMALL LETTER A BREVE;;0102;;0102 0104;LATIN CAPITAL LETTER A WITH OGONEK;Lu;0;L;0041 0328;;;;N;LATIN CAPITAL LETTER A OGONEK;;;0105; 0105;LATIN SMALL LETTER A WITH OGONEK;Ll;0;L;0061 0328;;;;N;LATIN SMALL LETTER A OGONEK;;0104;;0104 0106;LATIN CAPITAL LETTER C WITH ACUTE;Lu;0;L;0043 0301;;;;N;LATIN CAPITAL LETTER C ACUTE;;;0107; 0107;LATIN SMALL LETTER C WITH ACUTE;Ll;0;L;0063 0301;;;;N;LATIN SMALL LETTER C ACUTE;;0106;;0106 0108;LATIN CAPITAL LETTER C WITH CIRCUMFLEX;Lu;0;L;0043 0302;;;;N;LATIN CAPITAL LETTER C CIRCUMFLEX;;;0109; 0109;LATIN SMALL LETTER C WITH CIRCUMFLEX;Ll;0;L;0063 0302;;;;N;LATIN SMALL LETTER C CIRCUMFLEX;;0108;;0108 010A;LATIN CAPITAL LETTER C WITH DOT ABOVE;Lu;0;L;0043 0307;;;;N;LATIN CAPITAL LETTER C DOT;;;010B; 010B;LATIN SMALL LETTER C WITH DOT ABOVE;Ll;0;L;0063 0307;;;;N;LATIN SMALL LETTER C DOT;;010A;;010A 010C;LATIN CAPITAL LETTER C WITH CARON;Lu;0;L;0043 030C;;;;N;LATIN CAPITAL LETTER C HACEK;;;010D; 010D;LATIN SMALL LETTER C WITH CARON;Ll;0;L;0063 030C;;;;N;LATIN SMALL LETTER C HACEK;;010C;;010C 010E;LATIN CAPITAL LETTER D WITH CARON;Lu;0;L;0044 030C;;;;N;LATIN CAPITAL LETTER D HACEK;;;010F; 010F;LATIN SMALL LETTER D WITH CARON;Ll;0;L;0064 030C;;;;N;LATIN SMALL LETTER D HACEK;;010E;;010E 0110;LATIN CAPITAL LETTER D WITH STROKE;Lu;0;L;;;;;N;LATIN CAPITAL LETTER D BAR;;;0111; 0111;LATIN SMALL LETTER D WITH STROKE;Ll;0;L;;;;;N;LATIN SMALL LETTER D BAR;;0110;;0110 0112;LATIN CAPITAL LETTER E WITH MACRON;Lu;0;L;0045 0304;;;;N;LATIN CAPITAL LETTER E MACRON;;;0113; 0113;LATIN SMALL LETTER E WITH MACRON;Ll;0;L;0065 0304;;;;N;LATIN SMALL LETTER E MACRON;;0112;;0112 0114;LATIN CAPITAL LETTER E WITH BREVE;Lu;0;L;0045 0306;;;;N;LATIN CAPITAL LETTER E BREVE;;;0115; 0115;LATIN SMALL LETTER E WITH BREVE;Ll;0;L;0065 0306;;;;N;LATIN SMALL LETTER E BREVE;;0114;;0114 0116;LATIN CAPITAL LETTER E WITH DOT ABOVE;Lu;0;L;0045 0307;;;;N;LATIN CAPITAL LETTER E DOT;;;0117; 0117;LATIN SMALL LETTER E WITH DOT ABOVE;Ll;0;L;0065 0307;;;;N;LATIN SMALL LETTER E DOT;;0116;;0116 0118;LATIN CAPITAL LETTER E WITH OGONEK;Lu;0;L;0045 0328;;;;N;LATIN CAPITAL LETTER E OGONEK;;;0119; 0119;LATIN SMALL LETTER E WITH OGONEK;Ll;0;L;0065 0328;;;;N;LATIN SMALL LETTER E OGONEK;;0118;;0118 011A;LATIN CAPITAL LETTER E WITH CARON;Lu;0;L;0045 030C;;;;N;LATIN CAPITAL LETTER E HACEK;;;011B; 011B;LATIN SMALL LETTER E WITH CARON;Ll;0;L;0065 030C;;;;N;LATIN SMALL LETTER E HACEK;;011A;;011A 011C;LATIN CAPITAL LETTER G WITH CIRCUMFLEX;Lu;0;L;0047 0302;;;;N;LATIN CAPITAL LETTER G CIRCUMFLEX;;;011D; 011D;LATIN SMALL LETTER G WITH CIRCUMFLEX;Ll;0;L;0067 0302;;;;N;LATIN SMALL LETTER G CIRCUMFLEX;;011C;;011C 011E;LATIN CAPITAL LETTER G WITH BREVE;Lu;0;L;0047 0306;;;;N;LATIN CAPITAL LETTER G BREVE;;;011F; 011F;LATIN SMALL LETTER G WITH BREVE;Ll;0;L;0067 0306;;;;N;LATIN SMALL LETTER G BREVE;;011E;;011E 0120;LATIN CAPITAL LETTER G WITH DOT ABOVE;Lu;0;L;0047 0307;;;;N;LATIN CAPITAL LETTER G DOT;;;0121; 0121;LATIN SMALL LETTER G WITH DOT ABOVE;Ll;0;L;0067 0307;;;;N;LATIN SMALL LETTER G DOT;;0120;;0120 0122;LATIN CAPITAL LETTER G WITH CEDILLA;Lu;0;L;0047 0327;;;;N;LATIN CAPITAL LETTER G CEDILLA;;;0123; 0123;LATIN SMALL LETTER G WITH CEDILLA;Ll;0;L;0067 0327;;;;N;LATIN SMALL LETTER G CEDILLA;;0122;;0122 0124;LATIN CAPITAL LETTER H WITH CIRCUMFLEX;Lu;0;L;0048 0302;;;;N;LATIN CAPITAL LETTER H CIRCUMFLEX;;;0125; 0125;LATIN SMALL LETTER H WITH CIRCUMFLEX;Ll;0;L;0068 0302;;;;N;LATIN SMALL LETTER H CIRCUMFLEX;;0124;;0124 0126;LATIN CAPITAL LETTER H WITH STROKE;Lu;0;L;;;;;N;LATIN CAPITAL LETTER H BAR;;;0127; 0127;LATIN SMALL LETTER H WITH STROKE;Ll;0;L;;;;;N;LATIN SMALL LETTER H BAR;;0126;;0126 0128;LATIN CAPITAL LETTER I WITH TILDE;Lu;0;L;0049 0303;;;;N;LATIN CAPITAL LETTER I TILDE;;;0129; 0129;LATIN SMALL LETTER I WITH TILDE;Ll;0;L;0069 0303;;;;N;LATIN SMALL LETTER I TILDE;;0128;;0128 012A;LATIN CAPITAL LETTER I WITH MACRON;Lu;0;L;0049 0304;;;;N;LATIN CAPITAL LETTER I MACRON;;;012B; 012B;LATIN SMALL LETTER I WITH MACRON;Ll;0;L;0069 0304;;;;N;LATIN SMALL LETTER I MACRON;;012A;;012A 012C;LATIN CAPITAL LETTER I WITH BREVE;Lu;0;L;0049 0306;;;;N;LATIN CAPITAL LETTER I BREVE;;;012D; 012D;LATIN SMALL LETTER I WITH BREVE;Ll;0;L;0069 0306;;;;N;LATIN SMALL LETTER I BREVE;;012C;;012C 012E;LATIN CAPITAL LETTER I WITH OGONEK;Lu;0;L;0049 0328;;;;N;LATIN CAPITAL LETTER I OGONEK;;;012F; 012F;LATIN SMALL LETTER I WITH OGONEK;Ll;0;L;0069 0328;;;;N;LATIN SMALL LETTER I OGONEK;;012E;;012E 0130;LATIN CAPITAL LETTER I WITH DOT ABOVE;Lu;0;L;0049 0307;;;;N;LATIN CAPITAL LETTER I DOT;;;0069; 0131;LATIN SMALL LETTER DOTLESS I;Ll;0;L;;;;;N;;;0049;;0049 0132;LATIN CAPITAL LIGATURE IJ;Lu;0;L; 0049 004A;;;;N;LATIN CAPITAL LETTER I J;;;0133; 0133;LATIN SMALL LIGATURE IJ;Ll;0;L; 0069 006A;;;;N;LATIN SMALL LETTER I J;;0132;;0132 0134;LATIN CAPITAL LETTER J WITH CIRCUMFLEX;Lu;0;L;004A 0302;;;;N;LATIN CAPITAL LETTER J CIRCUMFLEX;;;0135; 0135;LATIN SMALL LETTER J WITH CIRCUMFLEX;Ll;0;L;006A 0302;;;;N;LATIN SMALL LETTER J CIRCUMFLEX;;0134;;0134 0136;LATIN CAPITAL LETTER K WITH CEDILLA;Lu;0;L;004B 0327;;;;N;LATIN CAPITAL LETTER K CEDILLA;;;0137; 0137;LATIN SMALL LETTER K WITH CEDILLA;Ll;0;L;006B 0327;;;;N;LATIN SMALL LETTER K CEDILLA;;0136;;0136 0138;LATIN SMALL LETTER KRA;Ll;0;L;;;;;N;;;;; 0139;LATIN CAPITAL LETTER L WITH ACUTE;Lu;0;L;004C 0301;;;;N;LATIN CAPITAL LETTER L ACUTE;;;013A; 013A;LATIN SMALL LETTER L WITH ACUTE;Ll;0;L;006C 0301;;;;N;LATIN SMALL LETTER L ACUTE;;0139;;0139 013B;LATIN CAPITAL LETTER L WITH CEDILLA;Lu;0;L;004C 0327;;;;N;LATIN CAPITAL LETTER L CEDILLA;;;013C; 013C;LATIN SMALL LETTER L WITH CEDILLA;Ll;0;L;006C 0327;;;;N;LATIN SMALL LETTER L CEDILLA;;013B;;013B 013D;LATIN CAPITAL LETTER L WITH CARON;Lu;0;L;004C 030C;;;;N;LATIN CAPITAL LETTER L HACEK;;;013E; 013E;LATIN SMALL LETTER L WITH CARON;Ll;0;L;006C 030C;;;;N;LATIN SMALL LETTER L HACEK;;013D;;013D 013F;LATIN CAPITAL LETTER L WITH MIDDLE DOT;Lu;0;L; 004C 00B7;;;;N;;;;0140; 0140;LATIN SMALL LETTER L WITH MIDDLE DOT;Ll;0;L; 006C 00B7;;;;N;;;013F;;013F 0141;LATIN CAPITAL LETTER L WITH STROKE;Lu;0;L;;;;;N;LATIN CAPITAL LETTER L SLASH;;;0142; 0142;LATIN SMALL LETTER L WITH STROKE;Ll;0;L;;;;;N;LATIN SMALL LETTER L SLASH;;0141;;0141 0143;LATIN CAPITAL LETTER N WITH ACUTE;Lu;0;L;004E 0301;;;;N;LATIN CAPITAL LETTER N ACUTE;;;0144; 0144;LATIN SMALL LETTER N WITH ACUTE;Ll;0;L;006E 0301;;;;N;LATIN SMALL LETTER N ACUTE;;0143;;0143 0145;LATIN CAPITAL LETTER N WITH CEDILLA;Lu;0;L;004E 0327;;;;N;LATIN CAPITAL LETTER N CEDILLA;;;0146; 0146;LATIN SMALL LETTER N WITH CEDILLA;Ll;0;L;006E 0327;;;;N;LATIN SMALL LETTER N CEDILLA;;0145;;0145 0147;LATIN CAPITAL LETTER N WITH CARON;Lu;0;L;004E 030C;;;;N;LATIN CAPITAL LETTER N HACEK;;;0148; 0148;LATIN SMALL LETTER N WITH CARON;Ll;0;L;006E 030C;;;;N;LATIN SMALL LETTER N HACEK;;0147;;0147 0149;LATIN SMALL LETTER N PRECEDED BY APOSTROPHE;Ll;0;L; 02BC 006E;;;;N;LATIN SMALL LETTER APOSTROPHE N;;;; 014A;LATIN CAPITAL LETTER ENG;Lu;0;L;;;;;N;;;;014B; 014B;LATIN SMALL LETTER ENG;Ll;0;L;;;;;N;;;014A;;014A 014C;LATIN CAPITAL LETTER O WITH MACRON;Lu;0;L;004F 0304;;;;N;LATIN CAPITAL LETTER O MACRON;;;014D; 014D;LATIN SMALL LETTER O WITH MACRON;Ll;0;L;006F 0304;;;;N;LATIN SMALL LETTER O MACRON;;014C;;014C 014E;LATIN CAPITAL LETTER O WITH BREVE;Lu;0;L;004F 0306;;;;N;LATIN CAPITAL LETTER O BREVE;;;014F; 014F;LATIN SMALL LETTER O WITH BREVE;Ll;0;L;006F 0306;;;;N;LATIN SMALL LETTER O BREVE;;014E;;014E 0150;LATIN CAPITAL LETTER O WITH DOUBLE ACUTE;Lu;0;L;004F 030B;;;;N;LATIN CAPITAL LETTER O DOUBLE ACUTE;;;0151; 0151;LATIN SMALL LETTER O WITH DOUBLE ACUTE;Ll;0;L;006F 030B;;;;N;LATIN SMALL LETTER O DOUBLE ACUTE;;0150;;0150 0152;LATIN CAPITAL LIGATURE OE;Lu;0;L;;;;;N;LATIN CAPITAL LETTER O E;;;0153; 0153;LATIN SMALL LIGATURE OE;Ll;0;L;;;;;N;LATIN SMALL LETTER O E;;0152;;0152 0154;LATIN CAPITAL LETTER R WITH ACUTE;Lu;0;L;0052 0301;;;;N;LATIN CAPITAL LETTER R ACUTE;;;0155; 0155;LATIN SMALL LETTER R WITH ACUTE;Ll;0;L;0072 0301;;;;N;LATIN SMALL LETTER R ACUTE;;0154;;0154 0156;LATIN CAPITAL LETTER R WITH CEDILLA;Lu;0;L;0052 0327;;;;N;LATIN CAPITAL LETTER R CEDILLA;;;0157; 0157;LATIN SMALL LETTER R WITH CEDILLA;Ll;0;L;0072 0327;;;;N;LATIN SMALL LETTER R CEDILLA;;0156;;0156 0158;LATIN CAPITAL LETTER R WITH CARON;Lu;0;L;0052 030C;;;;N;LATIN CAPITAL LETTER R HACEK;;;0159; 0159;LATIN SMALL LETTER R WITH CARON;Ll;0;L;0072 030C;;;;N;LATIN SMALL LETTER R HACEK;;0158;;0158 015A;LATIN CAPITAL LETTER S WITH ACUTE;Lu;0;L;0053 0301;;;;N;LATIN CAPITAL LETTER S ACUTE;;;015B; 015B;LATIN SMALL LETTER S WITH ACUTE;Ll;0;L;0073 0301;;;;N;LATIN SMALL LETTER S ACUTE;;015A;;015A 015C;LATIN CAPITAL LETTER S WITH CIRCUMFLEX;Lu;0;L;0053 0302;;;;N;LATIN CAPITAL LETTER S CIRCUMFLEX;;;015D; 015D;LATIN SMALL LETTER S WITH CIRCUMFLEX;Ll;0;L;0073 0302;;;;N;LATIN SMALL LETTER S CIRCUMFLEX;;015C;;015C 015E;LATIN CAPITAL LETTER S WITH CEDILLA;Lu;0;L;0053 0327;;;;N;LATIN CAPITAL LETTER S CEDILLA;;;015F; 015F;LATIN SMALL LETTER S WITH CEDILLA;Ll;0;L;0073 0327;;;;N;LATIN SMALL LETTER S CEDILLA;;015E;;015E 0160;LATIN CAPITAL LETTER S WITH CARON;Lu;0;L;0053 030C;;;;N;LATIN CAPITAL LETTER S HACEK;;;0161; 0161;LATIN SMALL LETTER S WITH CARON;Ll;0;L;0073 030C;;;;N;LATIN SMALL LETTER S HACEK;;0160;;0160 0162;LATIN CAPITAL LETTER T WITH CEDILLA;Lu;0;L;0054 0327;;;;N;LATIN CAPITAL LETTER T CEDILLA;;;0163; 0163;LATIN SMALL LETTER T WITH CEDILLA;Ll;0;L;0074 0327;;;;N;LATIN SMALL LETTER T CEDILLA;;0162;;0162 0164;LATIN CAPITAL LETTER T WITH CARON;Lu;0;L;0054 030C;;;;N;LATIN CAPITAL LETTER T HACEK;;;0165; 0165;LATIN SMALL LETTER T WITH CARON;Ll;0;L;0074 030C;;;;N;LATIN SMALL LETTER T HACEK;;0164;;0164 0166;LATIN CAPITAL LETTER T WITH STROKE;Lu;0;L;;;;;N;LATIN CAPITAL LETTER T BAR;;;0167; 0167;LATIN SMALL LETTER T WITH STROKE;Ll;0;L;;;;;N;LATIN SMALL LETTER T BAR;;0166;;0166 0168;LATIN CAPITAL LETTER U WITH TILDE;Lu;0;L;0055 0303;;;;N;LATIN CAPITAL LETTER U TILDE;;;0169; 0169;LATIN SMALL LETTER U WITH TILDE;Ll;0;L;0075 0303;;;;N;LATIN SMALL LETTER U TILDE;;0168;;0168 016A;LATIN CAPITAL LETTER U WITH MACRON;Lu;0;L;0055 0304;;;;N;LATIN CAPITAL LETTER U MACRON;;;016B; 016B;LATIN SMALL LETTER U WITH MACRON;Ll;0;L;0075 0304;;;;N;LATIN SMALL LETTER U MACRON;;016A;;016A 016C;LATIN CAPITAL LETTER U WITH BREVE;Lu;0;L;0055 0306;;;;N;LATIN CAPITAL LETTER U BREVE;;;016D; 016D;LATIN SMALL LETTER U WITH BREVE;Ll;0;L;0075 0306;;;;N;LATIN SMALL LETTER U BREVE;;016C;;016C 016E;LATIN CAPITAL LETTER U WITH RING ABOVE;Lu;0;L;0055 030A;;;;N;LATIN CAPITAL LETTER U RING;;;016F; 016F;LATIN SMALL LETTER U WITH RING ABOVE;Ll;0;L;0075 030A;;;;N;LATIN SMALL LETTER U RING;;016E;;016E 0170;LATIN CAPITAL LETTER U WITH DOUBLE ACUTE;Lu;0;L;0055 030B;;;;N;LATIN CAPITAL LETTER U DOUBLE ACUTE;;;0171; 0171;LATIN SMALL LETTER U WITH DOUBLE ACUTE;Ll;0;L;0075 030B;;;;N;LATIN SMALL LETTER U DOUBLE ACUTE;;0170;;0170 0172;LATIN CAPITAL LETTER U WITH OGONEK;Lu;0;L;0055 0328;;;;N;LATIN CAPITAL LETTER U OGONEK;;;0173; 0173;LATIN SMALL LETTER U WITH OGONEK;Ll;0;L;0075 0328;;;;N;LATIN SMALL LETTER U OGONEK;;0172;;0172 0174;LATIN CAPITAL LETTER W WITH CIRCUMFLEX;Lu;0;L;0057 0302;;;;N;LATIN CAPITAL LETTER W CIRCUMFLEX;;;0175; 0175;LATIN SMALL LETTER W WITH CIRCUMFLEX;Ll;0;L;0077 0302;;;;N;LATIN SMALL LETTER W CIRCUMFLEX;;0174;;0174 0176;LATIN CAPITAL LETTER Y WITH CIRCUMFLEX;Lu;0;L;0059 0302;;;;N;LATIN CAPITAL LETTER Y CIRCUMFLEX;;;0177; 0177;LATIN SMALL LETTER Y WITH CIRCUMFLEX;Ll;0;L;0079 0302;;;;N;LATIN SMALL LETTER Y CIRCUMFLEX;;0176;;0176 0178;LATIN CAPITAL LETTER Y WITH DIAERESIS;Lu;0;L;0059 0308;;;;N;LATIN CAPITAL LETTER Y DIAERESIS;;;00FF; 0179;LATIN CAPITAL LETTER Z WITH ACUTE;Lu;0;L;005A 0301;;;;N;LATIN CAPITAL LETTER Z ACUTE;;;017A; 017A;LATIN SMALL LETTER Z WITH ACUTE;Ll;0;L;007A 0301;;;;N;LATIN SMALL LETTER Z ACUTE;;0179;;0179 017B;LATIN CAPITAL LETTER Z WITH DOT ABOVE;Lu;0;L;005A 0307;;;;N;LATIN CAPITAL LETTER Z DOT;;;017C; 017C;LATIN SMALL LETTER Z WITH DOT ABOVE;Ll;0;L;007A 0307;;;;N;LATIN SMALL LETTER Z DOT;;017B;;017B 017D;LATIN CAPITAL LETTER Z WITH CARON;Lu;0;L;005A 030C;;;;N;LATIN CAPITAL LETTER Z HACEK;;;017E; 017E;LATIN SMALL LETTER Z WITH CARON;Ll;0;L;007A 030C;;;;N;LATIN SMALL LETTER Z HACEK;;017D;;017D 017F;LATIN SMALL LETTER LONG S;Ll;0;L; 0073;;;;N;;;0053;;0053 0180;LATIN SMALL LETTER B WITH STROKE;Ll;0;L;;;;;N;LATIN SMALL LETTER B BAR;;0243;;0243 0181;LATIN CAPITAL LETTER B WITH HOOK;Lu;0;L;;;;;N;LATIN CAPITAL LETTER B HOOK;;;0253; 0182;LATIN CAPITAL LETTER B WITH TOPBAR;Lu;0;L;;;;;N;LATIN CAPITAL LETTER B TOPBAR;;;0183; 0183;LATIN SMALL LETTER B WITH TOPBAR;Ll;0;L;;;;;N;LATIN SMALL LETTER B TOPBAR;;0182;;0182 0184;LATIN CAPITAL LETTER TONE SIX;Lu;0;L;;;;;N;;;;0185; 0185;LATIN SMALL LETTER TONE SIX;Ll;0;L;;;;;N;;;0184;;0184 0186;LATIN CAPITAL LETTER OPEN O;Lu;0;L;;;;;N;;;;0254; 0187;LATIN CAPITAL LETTER C WITH HOOK;Lu;0;L;;;;;N;LATIN CAPITAL LETTER C HOOK;;;0188; 0188;LATIN SMALL LETTER C WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER C HOOK;;0187;;0187 0189;LATIN CAPITAL LETTER AFRICAN D;Lu;0;L;;;;;N;;;;0256; 018A;LATIN CAPITAL LETTER D WITH HOOK;Lu;0;L;;;;;N;LATIN CAPITAL LETTER D HOOK;;;0257; 018B;LATIN CAPITAL LETTER D WITH TOPBAR;Lu;0;L;;;;;N;LATIN CAPITAL LETTER D TOPBAR;;;018C; 018C;LATIN SMALL LETTER D WITH TOPBAR;Ll;0;L;;;;;N;LATIN SMALL LETTER D TOPBAR;;018B;;018B 018D;LATIN SMALL LETTER TURNED DELTA;Ll;0;L;;;;;N;;;;; 018E;LATIN CAPITAL LETTER REVERSED E;Lu;0;L;;;;;N;LATIN CAPITAL LETTER TURNED E;;;01DD; 018F;LATIN CAPITAL LETTER SCHWA;Lu;0;L;;;;;N;;;;0259; 0190;LATIN CAPITAL LETTER OPEN E;Lu;0;L;;;;;N;LATIN CAPITAL LETTER EPSILON;;;025B; 0191;LATIN CAPITAL LETTER F WITH HOOK;Lu;0;L;;;;;N;LATIN CAPITAL LETTER F HOOK;;;0192; 0192;LATIN SMALL LETTER F WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER SCRIPT F;;0191;;0191 0193;LATIN CAPITAL LETTER G WITH HOOK;Lu;0;L;;;;;N;LATIN CAPITAL LETTER G HOOK;;;0260; 0194;LATIN CAPITAL LETTER GAMMA;Lu;0;L;;;;;N;;;;0263; 0195;LATIN SMALL LETTER HV;Ll;0;L;;;;;N;LATIN SMALL LETTER H V;;01F6;;01F6 0196;LATIN CAPITAL LETTER IOTA;Lu;0;L;;;;;N;;;;0269; 0197;LATIN CAPITAL LETTER I WITH STROKE;Lu;0;L;;;;;N;LATIN CAPITAL LETTER BARRED I;;;0268; 0198;LATIN CAPITAL LETTER K WITH HOOK;Lu;0;L;;;;;N;LATIN CAPITAL LETTER K HOOK;;;0199; 0199;LATIN SMALL LETTER K WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER K HOOK;;0198;;0198 019A;LATIN SMALL LETTER L WITH BAR;Ll;0;L;;;;;N;LATIN SMALL LETTER BARRED L;;023D;;023D 019B;LATIN SMALL LETTER LAMBDA WITH STROKE;Ll;0;L;;;;;N;LATIN SMALL LETTER BARRED LAMBDA;;;; 019C;LATIN CAPITAL LETTER TURNED M;Lu;0;L;;;;;N;;;;026F; 019D;LATIN CAPITAL LETTER N WITH LEFT HOOK;Lu;0;L;;;;;N;LATIN CAPITAL LETTER N HOOK;;;0272; 019E;LATIN SMALL LETTER N WITH LONG RIGHT LEG;Ll;0;L;;;;;N;;;0220;;0220 019F;LATIN CAPITAL LETTER O WITH MIDDLE TILDE;Lu;0;L;;;;;N;LATIN CAPITAL LETTER BARRED O;;;0275; 01A0;LATIN CAPITAL LETTER O WITH HORN;Lu;0;L;004F 031B;;;;N;LATIN CAPITAL LETTER O HORN;;;01A1; 01A1;LATIN SMALL LETTER O WITH HORN;Ll;0;L;006F 031B;;;;N;LATIN SMALL LETTER O HORN;;01A0;;01A0 01A2;LATIN CAPITAL LETTER OI;Lu;0;L;;;;;N;LATIN CAPITAL LETTER O I;;;01A3; 01A3;LATIN SMALL LETTER OI;Ll;0;L;;;;;N;LATIN SMALL LETTER O I;;01A2;;01A2 01A4;LATIN CAPITAL LETTER P WITH HOOK;Lu;0;L;;;;;N;LATIN CAPITAL LETTER P HOOK;;;01A5; 01A5;LATIN SMALL LETTER P WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER P HOOK;;01A4;;01A4 01A6;LATIN LETTER YR;Lu;0;L;;;;;N;LATIN LETTER Y R;;;0280; 01A7;LATIN CAPITAL LETTER TONE TWO;Lu;0;L;;;;;N;;;;01A8; 01A8;LATIN SMALL LETTER TONE TWO;Ll;0;L;;;;;N;;;01A7;;01A7 01A9;LATIN CAPITAL LETTER ESH;Lu;0;L;;;;;N;;;;0283; 01AA;LATIN LETTER REVERSED ESH LOOP;Ll;0;L;;;;;N;;;;; 01AB;LATIN SMALL LETTER T WITH PALATAL HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER T PALATAL HOOK;;;; 01AC;LATIN CAPITAL LETTER T WITH HOOK;Lu;0;L;;;;;N;LATIN CAPITAL LETTER T HOOK;;;01AD; 01AD;LATIN SMALL LETTER T WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER T HOOK;;01AC;;01AC 01AE;LATIN CAPITAL LETTER T WITH RETROFLEX HOOK;Lu;0;L;;;;;N;LATIN CAPITAL LETTER T RETROFLEX HOOK;;;0288; 01AF;LATIN CAPITAL LETTER U WITH HORN;Lu;0;L;0055 031B;;;;N;LATIN CAPITAL LETTER U HORN;;;01B0; 01B0;LATIN SMALL LETTER U WITH HORN;Ll;0;L;0075 031B;;;;N;LATIN SMALL LETTER U HORN;;01AF;;01AF 01B1;LATIN CAPITAL LETTER UPSILON;Lu;0;L;;;;;N;;;;028A; 01B2;LATIN CAPITAL LETTER V WITH HOOK;Lu;0;L;;;;;N;LATIN CAPITAL LETTER SCRIPT V;;;028B; 01B3;LATIN CAPITAL LETTER Y WITH HOOK;Lu;0;L;;;;;N;LATIN CAPITAL LETTER Y HOOK;;;01B4; 01B4;LATIN SMALL LETTER Y WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER Y HOOK;;01B3;;01B3 01B5;LATIN CAPITAL LETTER Z WITH STROKE;Lu;0;L;;;;;N;LATIN CAPITAL LETTER Z BAR;;;01B6; 01B6;LATIN SMALL LETTER Z WITH STROKE;Ll;0;L;;;;;N;LATIN SMALL LETTER Z BAR;;01B5;;01B5 01B7;LATIN CAPITAL LETTER EZH;Lu;0;L;;;;;N;LATIN CAPITAL LETTER YOGH;;;0292; 01B8;LATIN CAPITAL LETTER EZH REVERSED;Lu;0;L;;;;;N;LATIN CAPITAL LETTER REVERSED YOGH;;;01B9; 01B9;LATIN SMALL LETTER EZH REVERSED;Ll;0;L;;;;;N;LATIN SMALL LETTER REVERSED YOGH;;01B8;;01B8 01BA;LATIN SMALL LETTER EZH WITH TAIL;Ll;0;L;;;;;N;LATIN SMALL LETTER YOGH WITH TAIL;;;; 01BB;LATIN LETTER TWO WITH STROKE;Lo;0;L;;;;;N;LATIN LETTER TWO BAR;;;; 01BC;LATIN CAPITAL LETTER TONE FIVE;Lu;0;L;;;;;N;;;;01BD; 01BD;LATIN SMALL LETTER TONE FIVE;Ll;0;L;;;;;N;;;01BC;;01BC 01BE;LATIN LETTER INVERTED GLOTTAL STOP WITH STROKE;Ll;0;L;;;;;N;LATIN LETTER INVERTED GLOTTAL STOP BAR;;;; 01BF;LATIN LETTER WYNN;Ll;0;L;;;;;N;;;01F7;;01F7 01C0;LATIN LETTER DENTAL CLICK;Lo;0;L;;;;;N;LATIN LETTER PIPE;;;; 01C1;LATIN LETTER LATERAL CLICK;Lo;0;L;;;;;N;LATIN LETTER DOUBLE PIPE;;;; 01C2;LATIN LETTER ALVEOLAR CLICK;Lo;0;L;;;;;N;LATIN LETTER PIPE DOUBLE BAR;;;; 01C3;LATIN LETTER RETROFLEX CLICK;Lo;0;L;;;;;N;LATIN LETTER EXCLAMATION MARK;;;; 01C4;LATIN CAPITAL LETTER DZ WITH CARON;Lu;0;L; 0044 017D;;;;N;LATIN CAPITAL LETTER D Z HACEK;;;01C6;01C5 01C5;LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON;Lt;0;L; 0044 017E;;;;N;LATIN LETTER CAPITAL D SMALL Z HACEK;;01C4;01C6;01C5 01C6;LATIN SMALL LETTER DZ WITH CARON;Ll;0;L; 0064 017E;;;;N;LATIN SMALL LETTER D Z HACEK;;01C4;;01C5 01C7;LATIN CAPITAL LETTER LJ;Lu;0;L; 004C 004A;;;;N;LATIN CAPITAL LETTER L J;;;01C9;01C8 01C8;LATIN CAPITAL LETTER L WITH SMALL LETTER J;Lt;0;L; 004C 006A;;;;N;LATIN LETTER CAPITAL L SMALL J;;01C7;01C9;01C8 01C9;LATIN SMALL LETTER LJ;Ll;0;L; 006C 006A;;;;N;LATIN SMALL LETTER L J;;01C7;;01C8 01CA;LATIN CAPITAL LETTER NJ;Lu;0;L; 004E 004A;;;;N;LATIN CAPITAL LETTER N J;;;01CC;01CB 01CB;LATIN CAPITAL LETTER N WITH SMALL LETTER J;Lt;0;L; 004E 006A;;;;N;LATIN LETTER CAPITAL N SMALL J;;01CA;01CC;01CB 01CC;LATIN SMALL LETTER NJ;Ll;0;L; 006E 006A;;;;N;LATIN SMALL LETTER N J;;01CA;;01CB 01CD;LATIN CAPITAL LETTER A WITH CARON;Lu;0;L;0041 030C;;;;N;LATIN CAPITAL LETTER A HACEK;;;01CE; 01CE;LATIN SMALL LETTER A WITH CARON;Ll;0;L;0061 030C;;;;N;LATIN SMALL LETTER A HACEK;;01CD;;01CD 01CF;LATIN CAPITAL LETTER I WITH CARON;Lu;0;L;0049 030C;;;;N;LATIN CAPITAL LETTER I HACEK;;;01D0; 01D0;LATIN SMALL LETTER I WITH CARON;Ll;0;L;0069 030C;;;;N;LATIN SMALL LETTER I HACEK;;01CF;;01CF 01D1;LATIN CAPITAL LETTER O WITH CARON;Lu;0;L;004F 030C;;;;N;LATIN CAPITAL LETTER O HACEK;;;01D2; 01D2;LATIN SMALL LETTER O WITH CARON;Ll;0;L;006F 030C;;;;N;LATIN SMALL LETTER O HACEK;;01D1;;01D1 01D3;LATIN CAPITAL LETTER U WITH CARON;Lu;0;L;0055 030C;;;;N;LATIN CAPITAL LETTER U HACEK;;;01D4; 01D4;LATIN SMALL LETTER U WITH CARON;Ll;0;L;0075 030C;;;;N;LATIN SMALL LETTER U HACEK;;01D3;;01D3 01D5;LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON;Lu;0;L;00DC 0304;;;;N;LATIN CAPITAL LETTER U DIAERESIS MACRON;;;01D6; 01D6;LATIN SMALL LETTER U WITH DIAERESIS AND MACRON;Ll;0;L;00FC 0304;;;;N;LATIN SMALL LETTER U DIAERESIS MACRON;;01D5;;01D5 01D7;LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE;Lu;0;L;00DC 0301;;;;N;LATIN CAPITAL LETTER U DIAERESIS ACUTE;;;01D8; 01D8;LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE;Ll;0;L;00FC 0301;;;;N;LATIN SMALL LETTER U DIAERESIS ACUTE;;01D7;;01D7 01D9;LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON;Lu;0;L;00DC 030C;;;;N;LATIN CAPITAL LETTER U DIAERESIS HACEK;;;01DA; 01DA;LATIN SMALL LETTER U WITH DIAERESIS AND CARON;Ll;0;L;00FC 030C;;;;N;LATIN SMALL LETTER U DIAERESIS HACEK;;01D9;;01D9 01DB;LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE;Lu;0;L;00DC 0300;;;;N;LATIN CAPITAL LETTER U DIAERESIS GRAVE;;;01DC; 01DC;LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE;Ll;0;L;00FC 0300;;;;N;LATIN SMALL LETTER U DIAERESIS GRAVE;;01DB;;01DB 01DD;LATIN SMALL LETTER TURNED E;Ll;0;L;;;;;N;;;018E;;018E 01DE;LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON;Lu;0;L;00C4 0304;;;;N;LATIN CAPITAL LETTER A DIAERESIS MACRON;;;01DF; 01DF;LATIN SMALL LETTER A WITH DIAERESIS AND MACRON;Ll;0;L;00E4 0304;;;;N;LATIN SMALL LETTER A DIAERESIS MACRON;;01DE;;01DE 01E0;LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON;Lu;0;L;0226 0304;;;;N;LATIN CAPITAL LETTER A DOT MACRON;;;01E1; 01E1;LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON;Ll;0;L;0227 0304;;;;N;LATIN SMALL LETTER A DOT MACRON;;01E0;;01E0 01E2;LATIN CAPITAL LETTER AE WITH MACRON;Lu;0;L;00C6 0304;;;;N;LATIN CAPITAL LETTER A E MACRON;;;01E3; 01E3;LATIN SMALL LETTER AE WITH MACRON;Ll;0;L;00E6 0304;;;;N;LATIN SMALL LETTER A E MACRON;;01E2;;01E2 01E4;LATIN CAPITAL LETTER G WITH STROKE;Lu;0;L;;;;;N;LATIN CAPITAL LETTER G BAR;;;01E5; 01E5;LATIN SMALL LETTER G WITH STROKE;Ll;0;L;;;;;N;LATIN SMALL LETTER G BAR;;01E4;;01E4 01E6;LATIN CAPITAL LETTER G WITH CARON;Lu;0;L;0047 030C;;;;N;LATIN CAPITAL LETTER G HACEK;;;01E7; 01E7;LATIN SMALL LETTER G WITH CARON;Ll;0;L;0067 030C;;;;N;LATIN SMALL LETTER G HACEK;;01E6;;01E6 01E8;LATIN CAPITAL LETTER K WITH CARON;Lu;0;L;004B 030C;;;;N;LATIN CAPITAL LETTER K HACEK;;;01E9; 01E9;LATIN SMALL LETTER K WITH CARON;Ll;0;L;006B 030C;;;;N;LATIN SMALL LETTER K HACEK;;01E8;;01E8 01EA;LATIN CAPITAL LETTER O WITH OGONEK;Lu;0;L;004F 0328;;;;N;LATIN CAPITAL LETTER O OGONEK;;;01EB; 01EB;LATIN SMALL LETTER O WITH OGONEK;Ll;0;L;006F 0328;;;;N;LATIN SMALL LETTER O OGONEK;;01EA;;01EA 01EC;LATIN CAPITAL LETTER O WITH OGONEK AND MACRON;Lu;0;L;01EA 0304;;;;N;LATIN CAPITAL LETTER O OGONEK MACRON;;;01ED; 01ED;LATIN SMALL LETTER O WITH OGONEK AND MACRON;Ll;0;L;01EB 0304;;;;N;LATIN SMALL LETTER O OGONEK MACRON;;01EC;;01EC 01EE;LATIN CAPITAL LETTER EZH WITH CARON;Lu;0;L;01B7 030C;;;;N;LATIN CAPITAL LETTER YOGH HACEK;;;01EF; 01EF;LATIN SMALL LETTER EZH WITH CARON;Ll;0;L;0292 030C;;;;N;LATIN SMALL LETTER YOGH HACEK;;01EE;;01EE 01F0;LATIN SMALL LETTER J WITH CARON;Ll;0;L;006A 030C;;;;N;LATIN SMALL LETTER J HACEK;;;; 01F1;LATIN CAPITAL LETTER DZ;Lu;0;L; 0044 005A;;;;N;;;;01F3;01F2 01F2;LATIN CAPITAL LETTER D WITH SMALL LETTER Z;Lt;0;L; 0044 007A;;;;N;;;01F1;01F3;01F2 01F3;LATIN SMALL LETTER DZ;Ll;0;L; 0064 007A;;;;N;;;01F1;;01F2 01F4;LATIN CAPITAL LETTER G WITH ACUTE;Lu;0;L;0047 0301;;;;N;;;;01F5; 01F5;LATIN SMALL LETTER G WITH ACUTE;Ll;0;L;0067 0301;;;;N;;;01F4;;01F4 01F6;LATIN CAPITAL LETTER HWAIR;Lu;0;L;;;;;N;;;;0195; 01F7;LATIN CAPITAL LETTER WYNN;Lu;0;L;;;;;N;;;;01BF; 01F8;LATIN CAPITAL LETTER N WITH GRAVE;Lu;0;L;004E 0300;;;;N;;;;01F9; 01F9;LATIN SMALL LETTER N WITH GRAVE;Ll;0;L;006E 0300;;;;N;;;01F8;;01F8 01FA;LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE;Lu;0;L;00C5 0301;;;;N;;;;01FB; 01FB;LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE;Ll;0;L;00E5 0301;;;;N;;;01FA;;01FA 01FC;LATIN CAPITAL LETTER AE WITH ACUTE;Lu;0;L;00C6 0301;;;;N;;;;01FD; 01FD;LATIN SMALL LETTER AE WITH ACUTE;Ll;0;L;00E6 0301;;;;N;;;01FC;;01FC 01FE;LATIN CAPITAL LETTER O WITH STROKE AND ACUTE;Lu;0;L;00D8 0301;;;;N;;;;01FF; 01FF;LATIN SMALL LETTER O WITH STROKE AND ACUTE;Ll;0;L;00F8 0301;;;;N;;;01FE;;01FE 0200;LATIN CAPITAL LETTER A WITH DOUBLE GRAVE;Lu;0;L;0041 030F;;;;N;;;;0201; 0201;LATIN SMALL LETTER A WITH DOUBLE GRAVE;Ll;0;L;0061 030F;;;;N;;;0200;;0200 0202;LATIN CAPITAL LETTER A WITH INVERTED BREVE;Lu;0;L;0041 0311;;;;N;;;;0203; 0203;LATIN SMALL LETTER A WITH INVERTED BREVE;Ll;0;L;0061 0311;;;;N;;;0202;;0202 0204;LATIN CAPITAL LETTER E WITH DOUBLE GRAVE;Lu;0;L;0045 030F;;;;N;;;;0205; 0205;LATIN SMALL LETTER E WITH DOUBLE GRAVE;Ll;0;L;0065 030F;;;;N;;;0204;;0204 0206;LATIN CAPITAL LETTER E WITH INVERTED BREVE;Lu;0;L;0045 0311;;;;N;;;;0207; 0207;LATIN SMALL LETTER E WITH INVERTED BREVE;Ll;0;L;0065 0311;;;;N;;;0206;;0206 0208;LATIN CAPITAL LETTER I WITH DOUBLE GRAVE;Lu;0;L;0049 030F;;;;N;;;;0209; 0209;LATIN SMALL LETTER I WITH DOUBLE GRAVE;Ll;0;L;0069 030F;;;;N;;;0208;;0208 020A;LATIN CAPITAL LETTER I WITH INVERTED BREVE;Lu;0;L;0049 0311;;;;N;;;;020B; 020B;LATIN SMALL LETTER I WITH INVERTED BREVE;Ll;0;L;0069 0311;;;;N;;;020A;;020A 020C;LATIN CAPITAL LETTER O WITH DOUBLE GRAVE;Lu;0;L;004F 030F;;;;N;;;;020D; 020D;LATIN SMALL LETTER O WITH DOUBLE GRAVE;Ll;0;L;006F 030F;;;;N;;;020C;;020C 020E;LATIN CAPITAL LETTER O WITH INVERTED BREVE;Lu;0;L;004F 0311;;;;N;;;;020F; 020F;LATIN SMALL LETTER O WITH INVERTED BREVE;Ll;0;L;006F 0311;;;;N;;;020E;;020E 0210;LATIN CAPITAL LETTER R WITH DOUBLE GRAVE;Lu;0;L;0052 030F;;;;N;;;;0211; 0211;LATIN SMALL LETTER R WITH DOUBLE GRAVE;Ll;0;L;0072 030F;;;;N;;;0210;;0210 0212;LATIN CAPITAL LETTER R WITH INVERTED BREVE;Lu;0;L;0052 0311;;;;N;;;;0213; 0213;LATIN SMALL LETTER R WITH INVERTED BREVE;Ll;0;L;0072 0311;;;;N;;;0212;;0212 0214;LATIN CAPITAL LETTER U WITH DOUBLE GRAVE;Lu;0;L;0055 030F;;;;N;;;;0215; 0215;LATIN SMALL LETTER U WITH DOUBLE GRAVE;Ll;0;L;0075 030F;;;;N;;;0214;;0214 0216;LATIN CAPITAL LETTER U WITH INVERTED BREVE;Lu;0;L;0055 0311;;;;N;;;;0217; 0217;LATIN SMALL LETTER U WITH INVERTED BREVE;Ll;0;L;0075 0311;;;;N;;;0216;;0216 0218;LATIN CAPITAL LETTER S WITH COMMA BELOW;Lu;0;L;0053 0326;;;;N;;;;0219; 0219;LATIN SMALL LETTER S WITH COMMA BELOW;Ll;0;L;0073 0326;;;;N;;;0218;;0218 021A;LATIN CAPITAL LETTER T WITH COMMA BELOW;Lu;0;L;0054 0326;;;;N;;;;021B; 021B;LATIN SMALL LETTER T WITH COMMA BELOW;Ll;0;L;0074 0326;;;;N;;;021A;;021A 021C;LATIN CAPITAL LETTER YOGH;Lu;0;L;;;;;N;;;;021D; 021D;LATIN SMALL LETTER YOGH;Ll;0;L;;;;;N;;;021C;;021C 021E;LATIN CAPITAL LETTER H WITH CARON;Lu;0;L;0048 030C;;;;N;;;;021F; 021F;LATIN SMALL LETTER H WITH CARON;Ll;0;L;0068 030C;;;;N;;;021E;;021E 0220;LATIN CAPITAL LETTER N WITH LONG RIGHT LEG;Lu;0;L;;;;;N;;;;019E; 0221;LATIN SMALL LETTER D WITH CURL;Ll;0;L;;;;;N;;;;; 0222;LATIN CAPITAL LETTER OU;Lu;0;L;;;;;N;;;;0223; 0223;LATIN SMALL LETTER OU;Ll;0;L;;;;;N;;;0222;;0222 0224;LATIN CAPITAL LETTER Z WITH HOOK;Lu;0;L;;;;;N;;;;0225; 0225;LATIN SMALL LETTER Z WITH HOOK;Ll;0;L;;;;;N;;;0224;;0224 0226;LATIN CAPITAL LETTER A WITH DOT ABOVE;Lu;0;L;0041 0307;;;;N;;;;0227; 0227;LATIN SMALL LETTER A WITH DOT ABOVE;Ll;0;L;0061 0307;;;;N;;;0226;;0226 0228;LATIN CAPITAL LETTER E WITH CEDILLA;Lu;0;L;0045 0327;;;;N;;;;0229; 0229;LATIN SMALL LETTER E WITH CEDILLA;Ll;0;L;0065 0327;;;;N;;;0228;;0228 022A;LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON;Lu;0;L;00D6 0304;;;;N;;;;022B; 022B;LATIN SMALL LETTER O WITH DIAERESIS AND MACRON;Ll;0;L;00F6 0304;;;;N;;;022A;;022A 022C;LATIN CAPITAL LETTER O WITH TILDE AND MACRON;Lu;0;L;00D5 0304;;;;N;;;;022D; 022D;LATIN SMALL LETTER O WITH TILDE AND MACRON;Ll;0;L;00F5 0304;;;;N;;;022C;;022C 022E;LATIN CAPITAL LETTER O WITH DOT ABOVE;Lu;0;L;004F 0307;;;;N;;;;022F; 022F;LATIN SMALL LETTER O WITH DOT ABOVE;Ll;0;L;006F 0307;;;;N;;;022E;;022E 0230;LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON;Lu;0;L;022E 0304;;;;N;;;;0231; 0231;LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON;Ll;0;L;022F 0304;;;;N;;;0230;;0230 0232;LATIN CAPITAL LETTER Y WITH MACRON;Lu;0;L;0059 0304;;;;N;;;;0233; 0233;LATIN SMALL LETTER Y WITH MACRON;Ll;0;L;0079 0304;;;;N;;;0232;;0232 0234;LATIN SMALL LETTER L WITH CURL;Ll;0;L;;;;;N;;;;; 0235;LATIN SMALL LETTER N WITH CURL;Ll;0;L;;;;;N;;;;; 0236;LATIN SMALL LETTER T WITH CURL;Ll;0;L;;;;;N;;;;; 0237;LATIN SMALL LETTER DOTLESS J;Ll;0;L;;;;;N;;;;; 0238;LATIN SMALL LETTER DB DIGRAPH;Ll;0;L;;;;;N;;;;; 0239;LATIN SMALL LETTER QP DIGRAPH;Ll;0;L;;;;;N;;;;; 023A;LATIN CAPITAL LETTER A WITH STROKE;Lu;0;L;;;;;N;;;;2C65; 023B;LATIN CAPITAL LETTER C WITH STROKE;Lu;0;L;;;;;N;;;;023C; 023C;LATIN SMALL LETTER C WITH STROKE;Ll;0;L;;;;;N;;;023B;;023B 023D;LATIN CAPITAL LETTER L WITH BAR;Lu;0;L;;;;;N;;;;019A; 023E;LATIN CAPITAL LETTER T WITH DIAGONAL STROKE;Lu;0;L;;;;;N;;;;2C66; 023F;LATIN SMALL LETTER S WITH SWASH TAIL;Ll;0;L;;;;;N;;;2C7E;;2C7E 0240;LATIN SMALL LETTER Z WITH SWASH TAIL;Ll;0;L;;;;;N;;;2C7F;;2C7F 0241;LATIN CAPITAL LETTER GLOTTAL STOP;Lu;0;L;;;;;N;;;;0242; 0242;LATIN SMALL LETTER GLOTTAL STOP;Ll;0;L;;;;;N;;;0241;;0241 0243;LATIN CAPITAL LETTER B WITH STROKE;Lu;0;L;;;;;N;;;;0180; 0244;LATIN CAPITAL LETTER U BAR;Lu;0;L;;;;;N;;;;0289; 0245;LATIN CAPITAL LETTER TURNED V;Lu;0;L;;;;;N;;;;028C; 0246;LATIN CAPITAL LETTER E WITH STROKE;Lu;0;L;;;;;N;;;;0247; 0247;LATIN SMALL LETTER E WITH STROKE;Ll;0;L;;;;;N;;;0246;;0246 0248;LATIN CAPITAL LETTER J WITH STROKE;Lu;0;L;;;;;N;;;;0249; 0249;LATIN SMALL LETTER J WITH STROKE;Ll;0;L;;;;;N;;;0248;;0248 024A;LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL;Lu;0;L;;;;;N;;;;024B; 024B;LATIN SMALL LETTER Q WITH HOOK TAIL;Ll;0;L;;;;;N;;;024A;;024A 024C;LATIN CAPITAL LETTER R WITH STROKE;Lu;0;L;;;;;N;;;;024D; 024D;LATIN SMALL LETTER R WITH STROKE;Ll;0;L;;;;;N;;;024C;;024C 024E;LATIN CAPITAL LETTER Y WITH STROKE;Lu;0;L;;;;;N;;;;024F; 024F;LATIN SMALL LETTER Y WITH STROKE;Ll;0;L;;;;;N;;;024E;;024E 0250;LATIN SMALL LETTER TURNED A;Ll;0;L;;;;;N;;;2C6F;;2C6F 0251;LATIN SMALL LETTER ALPHA;Ll;0;L;;;;;N;LATIN SMALL LETTER SCRIPT A;;2C6D;;2C6D 0252;LATIN SMALL LETTER TURNED ALPHA;Ll;0;L;;;;;N;LATIN SMALL LETTER TURNED SCRIPT A;;2C70;;2C70 0253;LATIN SMALL LETTER B WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER B HOOK;;0181;;0181 0254;LATIN SMALL LETTER OPEN O;Ll;0;L;;;;;N;;;0186;;0186 0255;LATIN SMALL LETTER C WITH CURL;Ll;0;L;;;;;N;LATIN SMALL LETTER C CURL;;;; 0256;LATIN SMALL LETTER D WITH TAIL;Ll;0;L;;;;;N;LATIN SMALL LETTER D RETROFLEX HOOK;;0189;;0189 0257;LATIN SMALL LETTER D WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER D HOOK;;018A;;018A 0258;LATIN SMALL LETTER REVERSED E;Ll;0;L;;;;;N;;;;; 0259;LATIN SMALL LETTER SCHWA;Ll;0;L;;;;;N;;;018F;;018F 025A;LATIN SMALL LETTER SCHWA WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER SCHWA HOOK;;;; 025B;LATIN SMALL LETTER OPEN E;Ll;0;L;;;;;N;LATIN SMALL LETTER EPSILON;;0190;;0190 025C;LATIN SMALL LETTER REVERSED OPEN E;Ll;0;L;;;;;N;LATIN SMALL LETTER REVERSED EPSILON;;A7AB;;A7AB 025D;LATIN SMALL LETTER REVERSED OPEN E WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER REVERSED EPSILON HOOK;;;; 025E;LATIN SMALL LETTER CLOSED REVERSED OPEN E;Ll;0;L;;;;;N;LATIN SMALL LETTER CLOSED REVERSED EPSILON;;;; 025F;LATIN SMALL LETTER DOTLESS J WITH STROKE;Ll;0;L;;;;;N;LATIN SMALL LETTER DOTLESS J BAR;;;; 0260;LATIN SMALL LETTER G WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER G HOOK;;0193;;0193 0261;LATIN SMALL LETTER SCRIPT G;Ll;0;L;;;;;N;;;A7AC;;A7AC 0262;LATIN LETTER SMALL CAPITAL G;Ll;0;L;;;;;N;;;;; 0263;LATIN SMALL LETTER GAMMA;Ll;0;L;;;;;N;;;0194;;0194 0264;LATIN SMALL LETTER RAMS HORN;Ll;0;L;;;;;N;LATIN SMALL LETTER BABY GAMMA;;;; 0265;LATIN SMALL LETTER TURNED H;Ll;0;L;;;;;N;;;A78D;;A78D 0266;LATIN SMALL LETTER H WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER H HOOK;;A7AA;;A7AA 0267;LATIN SMALL LETTER HENG WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER HENG HOOK;;;; 0268;LATIN SMALL LETTER I WITH STROKE;Ll;0;L;;;;;N;LATIN SMALL LETTER BARRED I;;0197;;0197 0269;LATIN SMALL LETTER IOTA;Ll;0;L;;;;;N;;;0196;;0196 026A;LATIN LETTER SMALL CAPITAL I;Ll;0;L;;;;;N;;;A7AE;;A7AE 026B;LATIN SMALL LETTER L WITH MIDDLE TILDE;Ll;0;L;;;;;N;;;2C62;;2C62 026C;LATIN SMALL LETTER L WITH BELT;Ll;0;L;;;;;N;LATIN SMALL LETTER L BELT;;A7AD;;A7AD 026D;LATIN SMALL LETTER L WITH RETROFLEX HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER L RETROFLEX HOOK;;;; 026E;LATIN SMALL LETTER LEZH;Ll;0;L;;;;;N;LATIN SMALL LETTER L YOGH;;;; 026F;LATIN SMALL LETTER TURNED M;Ll;0;L;;;;;N;;;019C;;019C 0270;LATIN SMALL LETTER TURNED M WITH LONG LEG;Ll;0;L;;;;;N;;;;; 0271;LATIN SMALL LETTER M WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER M HOOK;;2C6E;;2C6E 0272;LATIN SMALL LETTER N WITH LEFT HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER N HOOK;;019D;;019D 0273;LATIN SMALL LETTER N WITH RETROFLEX HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER N RETROFLEX HOOK;;;; 0274;LATIN LETTER SMALL CAPITAL N;Ll;0;L;;;;;N;;;;; 0275;LATIN SMALL LETTER BARRED O;Ll;0;L;;;;;N;;;019F;;019F 0276;LATIN LETTER SMALL CAPITAL OE;Ll;0;L;;;;;N;LATIN LETTER SMALL CAPITAL O E;;;; 0277;LATIN SMALL LETTER CLOSED OMEGA;Ll;0;L;;;;;N;;;;; 0278;LATIN SMALL LETTER PHI;Ll;0;L;;;;;N;;;;; 0279;LATIN SMALL LETTER TURNED R;Ll;0;L;;;;;N;;;;; 027A;LATIN SMALL LETTER TURNED R WITH LONG LEG;Ll;0;L;;;;;N;;;;; 027B;LATIN SMALL LETTER TURNED R WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER TURNED R HOOK;;;; 027C;LATIN SMALL LETTER R WITH LONG LEG;Ll;0;L;;;;;N;;;;; 027D;LATIN SMALL LETTER R WITH TAIL;Ll;0;L;;;;;N;LATIN SMALL LETTER R HOOK;;2C64;;2C64 027E;LATIN SMALL LETTER R WITH FISHHOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER FISHHOOK R;;;; 027F;LATIN SMALL LETTER REVERSED R WITH FISHHOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER REVERSED FISHHOOK R;;;; 0280;LATIN LETTER SMALL CAPITAL R;Ll;0;L;;;;;N;;;01A6;;01A6 0281;LATIN LETTER SMALL CAPITAL INVERTED R;Ll;0;L;;;;;N;;;;; 0282;LATIN SMALL LETTER S WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER S HOOK;;;; 0283;LATIN SMALL LETTER ESH;Ll;0;L;;;;;N;;;01A9;;01A9 0284;LATIN SMALL LETTER DOTLESS J WITH STROKE AND HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER DOTLESS J BAR HOOK;;;; 0285;LATIN SMALL LETTER SQUAT REVERSED ESH;Ll;0;L;;;;;N;;;;; 0286;LATIN SMALL LETTER ESH WITH CURL;Ll;0;L;;;;;N;LATIN SMALL LETTER ESH CURL;;;; 0287;LATIN SMALL LETTER TURNED T;Ll;0;L;;;;;N;;;A7B1;;A7B1 0288;LATIN SMALL LETTER T WITH RETROFLEX HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER T RETROFLEX HOOK;;01AE;;01AE 0289;LATIN SMALL LETTER U BAR;Ll;0;L;;;;;N;;;0244;;0244 028A;LATIN SMALL LETTER UPSILON;Ll;0;L;;;;;N;;;01B1;;01B1 028B;LATIN SMALL LETTER V WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER SCRIPT V;;01B2;;01B2 028C;LATIN SMALL LETTER TURNED V;Ll;0;L;;;;;N;;;0245;;0245 028D;LATIN SMALL LETTER TURNED W;Ll;0;L;;;;;N;;;;; 028E;LATIN SMALL LETTER TURNED Y;Ll;0;L;;;;;N;;;;; 028F;LATIN LETTER SMALL CAPITAL Y;Ll;0;L;;;;;N;;;;; 0290;LATIN SMALL LETTER Z WITH RETROFLEX HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER Z RETROFLEX HOOK;;;; 0291;LATIN SMALL LETTER Z WITH CURL;Ll;0;L;;;;;N;LATIN SMALL LETTER Z CURL;;;; 0292;LATIN SMALL LETTER EZH;Ll;0;L;;;;;N;LATIN SMALL LETTER YOGH;;01B7;;01B7 0293;LATIN SMALL LETTER EZH WITH CURL;Ll;0;L;;;;;N;LATIN SMALL LETTER YOGH CURL;;;; 0294;LATIN LETTER GLOTTAL STOP;Lo;0;L;;;;;N;;;;; 0295;LATIN LETTER PHARYNGEAL VOICED FRICATIVE;Ll;0;L;;;;;N;LATIN LETTER REVERSED GLOTTAL STOP;;;; 0296;LATIN LETTER INVERTED GLOTTAL STOP;Ll;0;L;;;;;N;;;;; 0297;LATIN LETTER STRETCHED C;Ll;0;L;;;;;N;;;;; 0298;LATIN LETTER BILABIAL CLICK;Ll;0;L;;;;;N;LATIN LETTER BULLSEYE;;;; 0299;LATIN LETTER SMALL CAPITAL B;Ll;0;L;;;;;N;;;;; 029A;LATIN SMALL LETTER CLOSED OPEN E;Ll;0;L;;;;;N;LATIN SMALL LETTER CLOSED EPSILON;;;; 029B;LATIN LETTER SMALL CAPITAL G WITH HOOK;Ll;0;L;;;;;N;LATIN LETTER SMALL CAPITAL G HOOK;;;; 029C;LATIN LETTER SMALL CAPITAL H;Ll;0;L;;;;;N;;;;; 029D;LATIN SMALL LETTER J WITH CROSSED-TAIL;Ll;0;L;;;;;N;LATIN SMALL LETTER CROSSED-TAIL J;;A7B2;;A7B2 029E;LATIN SMALL LETTER TURNED K;Ll;0;L;;;;;N;;;A7B0;;A7B0 029F;LATIN LETTER SMALL CAPITAL L;Ll;0;L;;;;;N;;;;; 02A0;LATIN SMALL LETTER Q WITH HOOK;Ll;0;L;;;;;N;LATIN SMALL LETTER Q HOOK;;;; 02A1;LATIN LETTER GLOTTAL STOP WITH STROKE;Ll;0;L;;;;;N;LATIN LETTER GLOTTAL STOP BAR;;;; 02A2;LATIN LETTER REVERSED GLOTTAL STOP WITH STROKE;Ll;0;L;;;;;N;LATIN LETTER REVERSED GLOTTAL STOP BAR;;;; 02A3;LATIN SMALL LETTER DZ DIGRAPH;Ll;0;L;;;;;N;LATIN SMALL LETTER D Z;;;; 02A4;LATIN SMALL LETTER DEZH DIGRAPH;Ll;0;L;;;;;N;LATIN SMALL LETTER D YOGH;;;; 02A5;LATIN SMALL LETTER DZ DIGRAPH WITH CURL;Ll;0;L;;;;;N;LATIN SMALL LETTER D Z CURL;;;; 02A6;LATIN SMALL LETTER TS DIGRAPH;Ll;0;L;;;;;N;LATIN SMALL LETTER T S;;;; 02A7;LATIN SMALL LETTER TESH DIGRAPH;Ll;0;L;;;;;N;LATIN SMALL LETTER T ESH;;;; 02A8;LATIN SMALL LETTER TC DIGRAPH WITH CURL;Ll;0;L;;;;;N;LATIN SMALL LETTER T C CURL;;;; 02A9;LATIN SMALL LETTER FENG DIGRAPH;Ll;0;L;;;;;N;;;;; 02AA;LATIN SMALL LETTER LS DIGRAPH;Ll;0;L;;;;;N;;;;; 02AB;LATIN SMALL LETTER LZ DIGRAPH;Ll;0;L;;;;;N;;;;; 02AC;LATIN LETTER BILABIAL PERCUSSIVE;Ll;0;L;;;;;N;;;;; 02AD;LATIN LETTER BIDENTAL PERCUSSIVE;Ll;0;L;;;;;N;;;;; 02AE;LATIN SMALL LETTER TURNED H WITH FISHHOOK;Ll;0;L;;;;;N;;;;; 02AF;LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL;Ll;0;L;;;;;N;;;;; 02B0;MODIFIER LETTER SMALL H;Lm;0;L; 0068;;;;N;;;;; 02B1;MODIFIER LETTER SMALL H WITH HOOK;Lm;0;L; 0266;;;;N;MODIFIER LETTER SMALL H HOOK;;;; 02B2;MODIFIER LETTER SMALL J;Lm;0;L; 006A;;;;N;;;;; 02B3;MODIFIER LETTER SMALL R;Lm;0;L; 0072;;;;N;;;;; 02B4;MODIFIER LETTER SMALL TURNED R;Lm;0;L; 0279;;;;N;;;;; 02B5;MODIFIER LETTER SMALL TURNED R WITH HOOK;Lm;0;L; 027B;;;;N;MODIFIER LETTER SMALL TURNED R HOOK;;;; 02B6;MODIFIER LETTER SMALL CAPITAL INVERTED R;Lm;0;L; 0281;;;;N;;;;; 02B7;MODIFIER LETTER SMALL W;Lm;0;L; 0077;;;;N;;;;; 02B8;MODIFIER LETTER SMALL Y;Lm;0;L; 0079;;;;N;;;;; 02B9;MODIFIER LETTER PRIME;Lm;0;ON;;;;;N;;;;; 02BA;MODIFIER LETTER DOUBLE PRIME;Lm;0;ON;;;;;N;;;;; 02BB;MODIFIER LETTER TURNED COMMA;Lm;0;L;;;;;N;;;;; 02BC;MODIFIER LETTER APOSTROPHE;Lm;0;L;;;;;N;;;;; 02BD;MODIFIER LETTER REVERSED COMMA;Lm;0;L;;;;;N;;;;; 02BE;MODIFIER LETTER RIGHT HALF RING;Lm;0;L;;;;;N;;;;; 02BF;MODIFIER LETTER LEFT HALF RING;Lm;0;L;;;;;N;;;;; 02C0;MODIFIER LETTER GLOTTAL STOP;Lm;0;L;;;;;N;;;;; 02C1;MODIFIER LETTER REVERSED GLOTTAL STOP;Lm;0;L;;;;;N;;;;; 02C2;MODIFIER LETTER LEFT ARROWHEAD;Sk;0;ON;;;;;N;;;;; 02C3;MODIFIER LETTER RIGHT ARROWHEAD;Sk;0;ON;;;;;N;;;;; 02C4;MODIFIER LETTER UP ARROWHEAD;Sk;0;ON;;;;;N;;;;; 02C5;MODIFIER LETTER DOWN ARROWHEAD;Sk;0;ON;;;;;N;;;;; 02C6;MODIFIER LETTER CIRCUMFLEX ACCENT;Lm;0;ON;;;;;N;MODIFIER LETTER CIRCUMFLEX;;;; 02C7;CARON;Lm;0;ON;;;;;N;MODIFIER LETTER HACEK;;;; 02C8;MODIFIER LETTER VERTICAL LINE;Lm;0;ON;;;;;N;;;;; 02C9;MODIFIER LETTER MACRON;Lm;0;ON;;;;;N;;;;; 02CA;MODIFIER LETTER ACUTE ACCENT;Lm;0;ON;;;;;N;MODIFIER LETTER ACUTE;;;; 02CB;MODIFIER LETTER GRAVE ACCENT;Lm;0;ON;;;;;N;MODIFIER LETTER GRAVE;;;; 02CC;MODIFIER LETTER LOW VERTICAL LINE;Lm;0;ON;;;;;N;;;;; 02CD;MODIFIER LETTER LOW MACRON;Lm;0;ON;;;;;N;;;;; 02CE;MODIFIER LETTER LOW GRAVE ACCENT;Lm;0;ON;;;;;N;MODIFIER LETTER LOW GRAVE;;;; 02CF;MODIFIER LETTER LOW ACUTE ACCENT;Lm;0;ON;;;;;N;MODIFIER LETTER LOW ACUTE;;;; 02D0;MODIFIER LETTER TRIANGULAR COLON;Lm;0;L;;;;;N;;;;; 02D1;MODIFIER LETTER HALF TRIANGULAR COLON;Lm;0;L;;;;;N;;;;; 02D2;MODIFIER LETTER CENTRED RIGHT HALF RING;Sk;0;ON;;;;;N;MODIFIER LETTER CENTERED RIGHT HALF RING;;;; 02D3;MODIFIER LETTER CENTRED LEFT HALF RING;Sk;0;ON;;;;;N;MODIFIER LETTER CENTERED LEFT HALF RING;;;; 02D4;MODIFIER LETTER UP TACK;Sk;0;ON;;;;;N;;;;; 02D5;MODIFIER LETTER DOWN TACK;Sk;0;ON;;;;;N;;;;; 02D6;MODIFIER LETTER PLUS SIGN;Sk;0;ON;;;;;N;;;;; 02D7;MODIFIER LETTER MINUS SIGN;Sk;0;ON;;;;;N;;;;; 02D8;BREVE;Sk;0;ON; 0020 0306;;;;N;SPACING BREVE;;;; 02D9;DOT ABOVE;Sk;0;ON; 0020 0307;;;;N;SPACING DOT ABOVE;;;; 02DA;RING ABOVE;Sk;0;ON; 0020 030A;;;;N;SPACING RING ABOVE;;;; 02DB;OGONEK;Sk;0;ON; 0020 0328;;;;N;SPACING OGONEK;;;; 02DC;SMALL TILDE;Sk;0;ON; 0020 0303;;;;N;SPACING TILDE;;;; 02DD;DOUBLE ACUTE ACCENT;Sk;0;ON; 0020 030B;;;;N;SPACING DOUBLE ACUTE;;;; 02DE;MODIFIER LETTER RHOTIC HOOK;Sk;0;ON;;;;;N;;;;; 02DF;MODIFIER LETTER CROSS ACCENT;Sk;0;ON;;;;;N;;;;; 02E0;MODIFIER LETTER SMALL GAMMA;Lm;0;L; 0263;;;;N;;;;; 02E1;MODIFIER LETTER SMALL L;Lm;0;L; 006C;;;;N;;;;; 02E2;MODIFIER LETTER SMALL S;Lm;0;L; 0073;;;;N;;;;; 02E3;MODIFIER LETTER SMALL X;Lm;0;L; 0078;;;;N;;;;; 02E4;MODIFIER LETTER SMALL REVERSED GLOTTAL STOP;Lm;0;L; 0295;;;;N;;;;; 02E5;MODIFIER LETTER EXTRA-HIGH TONE BAR;Sk;0;ON;;;;;N;;;;; 02E6;MODIFIER LETTER HIGH TONE BAR;Sk;0;ON;;;;;N;;;;; 02E7;MODIFIER LETTER MID TONE BAR;Sk;0;ON;;;;;N;;;;; 02E8;MODIFIER LETTER LOW TONE BAR;Sk;0;ON;;;;;N;;;;; 02E9;MODIFIER LETTER EXTRA-LOW TONE BAR;Sk;0;ON;;;;;N;;;;; 02EA;MODIFIER LETTER YIN DEPARTING TONE MARK;Sk;0;ON;;;;;N;;;;; 02EB;MODIFIER LETTER YANG DEPARTING TONE MARK;Sk;0;ON;;;;;N;;;;; 02EC;MODIFIER LETTER VOICING;Lm;0;ON;;;;;N;;;;; 02ED;MODIFIER LETTER UNASPIRATED;Sk;0;ON;;;;;N;;;;; 02EE;MODIFIER LETTER DOUBLE APOSTROPHE;Lm;0;L;;;;;N;;;;; 02EF;MODIFIER LETTER LOW DOWN ARROWHEAD;Sk;0;ON;;;;;N;;;;; 02F0;MODIFIER LETTER LOW UP ARROWHEAD;Sk;0;ON;;;;;N;;;;; 02F1;MODIFIER LETTER LOW LEFT ARROWHEAD;Sk;0;ON;;;;;N;;;;; 02F2;MODIFIER LETTER LOW RIGHT ARROWHEAD;Sk;0;ON;;;;;N;;;;; 02F3;MODIFIER LETTER LOW RING;Sk;0;ON;;;;;N;;;;; 02F4;MODIFIER LETTER MIDDLE GRAVE ACCENT;Sk;0;ON;;;;;N;;;;; 02F5;MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT;Sk;0;ON;;;;;N;;;;; 02F6;MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT;Sk;0;ON;;;;;N;;;;; 02F7;MODIFIER LETTER LOW TILDE;Sk;0;ON;;;;;N;;;;; 02F8;MODIFIER LETTER RAISED COLON;Sk;0;ON;;;;;N;;;;; 02F9;MODIFIER LETTER BEGIN HIGH TONE;Sk;0;ON;;;;;N;;;;; 02FA;MODIFIER LETTER END HIGH TONE;Sk;0;ON;;;;;N;;;;; 02FB;MODIFIER LETTER BEGIN LOW TONE;Sk;0;ON;;;;;N;;;;; 02FC;MODIFIER LETTER END LOW TONE;Sk;0;ON;;;;;N;;;;; 02FD;MODIFIER LETTER SHELF;Sk;0;ON;;;;;N;;;;; 02FE;MODIFIER LETTER OPEN SHELF;Sk;0;ON;;;;;N;;;;; 02FF;MODIFIER LETTER LOW LEFT ARROW;Sk;0;ON;;;;;N;;;;; 0300;COMBINING GRAVE ACCENT;Mn;230;NSM;;;;;N;NON-SPACING GRAVE;;;; 0301;COMBINING ACUTE ACCENT;Mn;230;NSM;;;;;N;NON-SPACING ACUTE;;;; 0302;COMBINING CIRCUMFLEX ACCENT;Mn;230;NSM;;;;;N;NON-SPACING CIRCUMFLEX;;;; 0303;COMBINING TILDE;Mn;230;NSM;;;;;N;NON-SPACING TILDE;;;; 0304;COMBINING MACRON;Mn;230;NSM;;;;;N;NON-SPACING MACRON;;;; 0305;COMBINING OVERLINE;Mn;230;NSM;;;;;N;NON-SPACING OVERSCORE;;;; 0306;COMBINING BREVE;Mn;230;NSM;;;;;N;NON-SPACING BREVE;;;; 0307;COMBINING DOT ABOVE;Mn;230;NSM;;;;;N;NON-SPACING DOT ABOVE;;;; 0308;COMBINING DIAERESIS;Mn;230;NSM;;;;;N;NON-SPACING DIAERESIS;;;; 0309;COMBINING HOOK ABOVE;Mn;230;NSM;;;;;N;NON-SPACING HOOK ABOVE;;;; 030A;COMBINING RING ABOVE;Mn;230;NSM;;;;;N;NON-SPACING RING ABOVE;;;; 030B;COMBINING DOUBLE ACUTE ACCENT;Mn;230;NSM;;;;;N;NON-SPACING DOUBLE ACUTE;;;; 030C;COMBINING CARON;Mn;230;NSM;;;;;N;NON-SPACING HACEK;;;; 030D;COMBINING VERTICAL LINE ABOVE;Mn;230;NSM;;;;;N;NON-SPACING VERTICAL LINE ABOVE;;;; 030E;COMBINING DOUBLE VERTICAL LINE ABOVE;Mn;230;NSM;;;;;N;NON-SPACING DOUBLE VERTICAL LINE ABOVE;;;; 030F;COMBINING DOUBLE GRAVE ACCENT;Mn;230;NSM;;;;;N;NON-SPACING DOUBLE GRAVE;;;; 0310;COMBINING CANDRABINDU;Mn;230;NSM;;;;;N;NON-SPACING CANDRABINDU;;;; 0311;COMBINING INVERTED BREVE;Mn;230;NSM;;;;;N;NON-SPACING INVERTED BREVE;;;; 0312;COMBINING TURNED COMMA ABOVE;Mn;230;NSM;;;;;N;NON-SPACING TURNED COMMA ABOVE;;;; 0313;COMBINING COMMA ABOVE;Mn;230;NSM;;;;;N;NON-SPACING COMMA ABOVE;;;; 0314;COMBINING REVERSED COMMA ABOVE;Mn;230;NSM;;;;;N;NON-SPACING REVERSED COMMA ABOVE;;;; 0315;COMBINING COMMA ABOVE RIGHT;Mn;232;NSM;;;;;N;NON-SPACING COMMA ABOVE RIGHT;;;; 0316;COMBINING GRAVE ACCENT BELOW;Mn;220;NSM;;;;;N;NON-SPACING GRAVE BELOW;;;; 0317;COMBINING ACUTE ACCENT BELOW;Mn;220;NSM;;;;;N;NON-SPACING ACUTE BELOW;;;; 0318;COMBINING LEFT TACK BELOW;Mn;220;NSM;;;;;N;NON-SPACING LEFT TACK BELOW;;;; 0319;COMBINING RIGHT TACK BELOW;Mn;220;NSM;;;;;N;NON-SPACING RIGHT TACK BELOW;;;; 031A;COMBINING LEFT ANGLE ABOVE;Mn;232;NSM;;;;;N;NON-SPACING LEFT ANGLE ABOVE;;;; 031B;COMBINING HORN;Mn;216;NSM;;;;;N;NON-SPACING HORN;;;; 031C;COMBINING LEFT HALF RING BELOW;Mn;220;NSM;;;;;N;NON-SPACING LEFT HALF RING BELOW;;;; 031D;COMBINING UP TACK BELOW;Mn;220;NSM;;;;;N;NON-SPACING UP TACK BELOW;;;; 031E;COMBINING DOWN TACK BELOW;Mn;220;NSM;;;;;N;NON-SPACING DOWN TACK BELOW;;;; 031F;COMBINING PLUS SIGN BELOW;Mn;220;NSM;;;;;N;NON-SPACING PLUS SIGN BELOW;;;; 0320;COMBINING MINUS SIGN BELOW;Mn;220;NSM;;;;;N;NON-SPACING MINUS SIGN BELOW;;;; 0321;COMBINING PALATALIZED HOOK BELOW;Mn;202;NSM;;;;;N;NON-SPACING PALATALIZED HOOK BELOW;;;; 0322;COMBINING RETROFLEX HOOK BELOW;Mn;202;NSM;;;;;N;NON-SPACING RETROFLEX HOOK BELOW;;;; 0323;COMBINING DOT BELOW;Mn;220;NSM;;;;;N;NON-SPACING DOT BELOW;;;; 0324;COMBINING DIAERESIS BELOW;Mn;220;NSM;;;;;N;NON-SPACING DOUBLE DOT BELOW;;;; 0325;COMBINING RING BELOW;Mn;220;NSM;;;;;N;NON-SPACING RING BELOW;;;; 0326;COMBINING COMMA BELOW;Mn;220;NSM;;;;;N;NON-SPACING COMMA BELOW;;;; 0327;COMBINING CEDILLA;Mn;202;NSM;;;;;N;NON-SPACING CEDILLA;;;; 0328;COMBINING OGONEK;Mn;202;NSM;;;;;N;NON-SPACING OGONEK;;;; 0329;COMBINING VERTICAL LINE BELOW;Mn;220;NSM;;;;;N;NON-SPACING VERTICAL LINE BELOW;;;; 032A;COMBINING BRIDGE BELOW;Mn;220;NSM;;;;;N;NON-SPACING BRIDGE BELOW;;;; 032B;COMBINING INVERTED DOUBLE ARCH BELOW;Mn;220;NSM;;;;;N;NON-SPACING INVERTED DOUBLE ARCH BELOW;;;; 032C;COMBINING CARON BELOW;Mn;220;NSM;;;;;N;NON-SPACING HACEK BELOW;;;; 032D;COMBINING CIRCUMFLEX ACCENT BELOW;Mn;220;NSM;;;;;N;NON-SPACING CIRCUMFLEX BELOW;;;; 032E;COMBINING BREVE BELOW;Mn;220;NSM;;;;;N;NON-SPACING BREVE BELOW;;;; 032F;COMBINING INVERTED BREVE BELOW;Mn;220;NSM;;;;;N;NON-SPACING INVERTED BREVE BELOW;;;; 0330;COMBINING TILDE BELOW;Mn;220;NSM;;;;;N;NON-SPACING TILDE BELOW;;;; 0331;COMBINING MACRON BELOW;Mn;220;NSM;;;;;N;NON-SPACING MACRON BELOW;;;; 0332;COMBINING LOW LINE;Mn;220;NSM;;;;;N;NON-SPACING UNDERSCORE;;;; 0333;COMBINING DOUBLE LOW LINE;Mn;220;NSM;;;;;N;NON-SPACING DOUBLE UNDERSCORE;;;; 0334;COMBINING TILDE OVERLAY;Mn;1;NSM;;;;;N;NON-SPACING TILDE OVERLAY;;;; 0335;COMBINING SHORT STROKE OVERLAY;Mn;1;NSM;;;;;N;NON-SPACING SHORT BAR OVERLAY;;;; 0336;COMBINING LONG STROKE OVERLAY;Mn;1;NSM;;;;;N;NON-SPACING LONG BAR OVERLAY;;;; 0337;COMBINING SHORT SOLIDUS OVERLAY;Mn;1;NSM;;;;;N;NON-SPACING SHORT SLASH OVERLAY;;;; 0338;COMBINING LONG SOLIDUS OVERLAY;Mn;1;NSM;;;;;N;NON-SPACING LONG SLASH OVERLAY;;;; 0339;COMBINING RIGHT HALF RING BELOW;Mn;220;NSM;;;;;N;NON-SPACING RIGHT HALF RING BELOW;;;; 033A;COMBINING INVERTED BRIDGE BELOW;Mn;220;NSM;;;;;N;NON-SPACING INVERTED BRIDGE BELOW;;;; 033B;COMBINING SQUARE BELOW;Mn;220;NSM;;;;;N;NON-SPACING SQUARE BELOW;;;; 033C;COMBINING SEAGULL BELOW;Mn;220;NSM;;;;;N;NON-SPACING SEAGULL BELOW;;;; 033D;COMBINING X ABOVE;Mn;230;NSM;;;;;N;NON-SPACING X ABOVE;;;; 033E;COMBINING VERTICAL TILDE;Mn;230;NSM;;;;;N;NON-SPACING VERTICAL TILDE;;;; 033F;COMBINING DOUBLE OVERLINE;Mn;230;NSM;;;;;N;NON-SPACING DOUBLE OVERSCORE;;;; 0340;COMBINING GRAVE TONE MARK;Mn;230;NSM;0300;;;;N;NON-SPACING GRAVE TONE MARK;;;; 0341;COMBINING ACUTE TONE MARK;Mn;230;NSM;0301;;;;N;NON-SPACING ACUTE TONE MARK;;;; 0342;COMBINING GREEK PERISPOMENI;Mn;230;NSM;;;;;N;;;;; 0343;COMBINING GREEK KORONIS;Mn;230;NSM;0313;;;;N;;;;; 0344;COMBINING GREEK DIALYTIKA TONOS;Mn;230;NSM;0308 0301;;;;N;GREEK NON-SPACING DIAERESIS TONOS;;;; 0345;COMBINING GREEK YPOGEGRAMMENI;Mn;240;NSM;;;;;N;GREEK NON-SPACING IOTA BELOW;;0399;;0399 0346;COMBINING BRIDGE ABOVE;Mn;230;NSM;;;;;N;;;;; 0347;COMBINING EQUALS SIGN BELOW;Mn;220;NSM;;;;;N;;;;; 0348;COMBINING DOUBLE VERTICAL LINE BELOW;Mn;220;NSM;;;;;N;;;;; 0349;COMBINING LEFT ANGLE BELOW;Mn;220;NSM;;;;;N;;;;; 034A;COMBINING NOT TILDE ABOVE;Mn;230;NSM;;;;;N;;;;; 034B;COMBINING HOMOTHETIC ABOVE;Mn;230;NSM;;;;;N;;;;; 034C;COMBINING ALMOST EQUAL TO ABOVE;Mn;230;NSM;;;;;N;;;;; 034D;COMBINING LEFT RIGHT ARROW BELOW;Mn;220;NSM;;;;;N;;;;; 034E;COMBINING UPWARDS ARROW BELOW;Mn;220;NSM;;;;;N;;;;; 034F;COMBINING GRAPHEME JOINER;Mn;0;NSM;;;;;N;;;;; 0350;COMBINING RIGHT ARROWHEAD ABOVE;Mn;230;NSM;;;;;N;;;;; 0351;COMBINING LEFT HALF RING ABOVE;Mn;230;NSM;;;;;N;;;;; 0352;COMBINING FERMATA;Mn;230;NSM;;;;;N;;;;; 0353;COMBINING X BELOW;Mn;220;NSM;;;;;N;;;;; 0354;COMBINING LEFT ARROWHEAD BELOW;Mn;220;NSM;;;;;N;;;;; 0355;COMBINING RIGHT ARROWHEAD BELOW;Mn;220;NSM;;;;;N;;;;; 0356;COMBINING RIGHT ARROWHEAD AND UP ARROWHEAD BELOW;Mn;220;NSM;;;;;N;;;;; 0357;COMBINING RIGHT HALF RING ABOVE;Mn;230;NSM;;;;;N;;;;; 0358;COMBINING DOT ABOVE RIGHT;Mn;232;NSM;;;;;N;;;;; 0359;COMBINING ASTERISK BELOW;Mn;220;NSM;;;;;N;;;;; 035A;COMBINING DOUBLE RING BELOW;Mn;220;NSM;;;;;N;;;;; 035B;COMBINING ZIGZAG ABOVE;Mn;230;NSM;;;;;N;;;;; 035C;COMBINING DOUBLE BREVE BELOW;Mn;233;NSM;;;;;N;;;;; 035D;COMBINING DOUBLE BREVE;Mn;234;NSM;;;;;N;;;;; 035E;COMBINING DOUBLE MACRON;Mn;234;NSM;;;;;N;;;;; 035F;COMBINING DOUBLE MACRON BELOW;Mn;233;NSM;;;;;N;;;;; 0360;COMBINING DOUBLE TILDE;Mn;234;NSM;;;;;N;;;;; 0361;COMBINING DOUBLE INVERTED BREVE;Mn;234;NSM;;;;;N;;;;; 0362;COMBINING DOUBLE RIGHTWARDS ARROW BELOW;Mn;233;NSM;;;;;N;;;;; 0363;COMBINING LATIN SMALL LETTER A;Mn;230;NSM;;;;;N;;;;; 0364;COMBINING LATIN SMALL LETTER E;Mn;230;NSM;;;;;N;;;;; 0365;COMBINING LATIN SMALL LETTER I;Mn;230;NSM;;;;;N;;;;; 0366;COMBINING LATIN SMALL LETTER O;Mn;230;NSM;;;;;N;;;;; 0367;COMBINING LATIN SMALL LETTER U;Mn;230;NSM;;;;;N;;;;; 0368;COMBINING LATIN SMALL LETTER C;Mn;230;NSM;;;;;N;;;;; 0369;COMBINING LATIN SMALL LETTER D;Mn;230;NSM;;;;;N;;;;; 036A;COMBINING LATIN SMALL LETTER H;Mn;230;NSM;;;;;N;;;;; 036B;COMBINING LATIN SMALL LETTER M;Mn;230;NSM;;;;;N;;;;; 036C;COMBINING LATIN SMALL LETTER R;Mn;230;NSM;;;;;N;;;;; 036D;COMBINING LATIN SMALL LETTER T;Mn;230;NSM;;;;;N;;;;; 036E;COMBINING LATIN SMALL LETTER V;Mn;230;NSM;;;;;N;;;;; 036F;COMBINING LATIN SMALL LETTER X;Mn;230;NSM;;;;;N;;;;; 0370;GREEK CAPITAL LETTER HETA;Lu;0;L;;;;;N;;;;0371; 0371;GREEK SMALL LETTER HETA;Ll;0;L;;;;;N;;;0370;;0370 0372;GREEK CAPITAL LETTER ARCHAIC SAMPI;Lu;0;L;;;;;N;;;;0373; 0373;GREEK SMALL LETTER ARCHAIC SAMPI;Ll;0;L;;;;;N;;;0372;;0372 0374;GREEK NUMERAL SIGN;Lm;0;ON;02B9;;;;N;GREEK UPPER NUMERAL SIGN;;;; 0375;GREEK LOWER NUMERAL SIGN;Sk;0;ON;;;;;N;;;;; 0376;GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA;Lu;0;L;;;;;N;;;;0377; 0377;GREEK SMALL LETTER PAMPHYLIAN DIGAMMA;Ll;0;L;;;;;N;;;0376;;0376 037A;GREEK YPOGEGRAMMENI;Lm;0;L; 0020 0345;;;;N;GREEK SPACING IOTA BELOW;;;; 037B;GREEK SMALL REVERSED LUNATE SIGMA SYMBOL;Ll;0;L;;;;;N;;;03FD;;03FD 037C;GREEK SMALL DOTTED LUNATE SIGMA SYMBOL;Ll;0;L;;;;;N;;;03FE;;03FE 037D;GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL;Ll;0;L;;;;;N;;;03FF;;03FF 037E;GREEK QUESTION MARK;Po;0;ON;003B;;;;N;;;;; 037F;GREEK CAPITAL LETTER YOT;Lu;0;L;;;;;N;;;;03F3; 0384;GREEK TONOS;Sk;0;ON; 0020 0301;;;;N;GREEK SPACING TONOS;;;; 0385;GREEK DIALYTIKA TONOS;Sk;0;ON;00A8 0301;;;;N;GREEK SPACING DIAERESIS TONOS;;;; 0386;GREEK CAPITAL LETTER ALPHA WITH TONOS;Lu;0;L;0391 0301;;;;N;GREEK CAPITAL LETTER ALPHA TONOS;;;03AC; 0387;GREEK ANO TELEIA;Po;0;ON;00B7;;;;N;;;;; 0388;GREEK CAPITAL LETTER EPSILON WITH TONOS;Lu;0;L;0395 0301;;;;N;GREEK CAPITAL LETTER EPSILON TONOS;;;03AD; 0389;GREEK CAPITAL LETTER ETA WITH TONOS;Lu;0;L;0397 0301;;;;N;GREEK CAPITAL LETTER ETA TONOS;;;03AE; 038A;GREEK CAPITAL LETTER IOTA WITH TONOS;Lu;0;L;0399 0301;;;;N;GREEK CAPITAL LETTER IOTA TONOS;;;03AF; 038C;GREEK CAPITAL LETTER OMICRON WITH TONOS;Lu;0;L;039F 0301;;;;N;GREEK CAPITAL LETTER OMICRON TONOS;;;03CC; 038E;GREEK CAPITAL LETTER UPSILON WITH TONOS;Lu;0;L;03A5 0301;;;;N;GREEK CAPITAL LETTER UPSILON TONOS;;;03CD; 038F;GREEK CAPITAL LETTER OMEGA WITH TONOS;Lu;0;L;03A9 0301;;;;N;GREEK CAPITAL LETTER OMEGA TONOS;;;03CE; 0390;GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS;Ll;0;L;03CA 0301;;;;N;GREEK SMALL LETTER IOTA DIAERESIS TONOS;;;; 0391;GREEK CAPITAL LETTER ALPHA;Lu;0;L;;;;;N;;;;03B1; 0392;GREEK CAPITAL LETTER BETA;Lu;0;L;;;;;N;;;;03B2; 0393;GREEK CAPITAL LETTER GAMMA;Lu;0;L;;;;;N;;;;03B3; 0394;GREEK CAPITAL LETTER DELTA;Lu;0;L;;;;;N;;;;03B4; 0395;GREEK CAPITAL LETTER EPSILON;Lu;0;L;;;;;N;;;;03B5; 0396;GREEK CAPITAL LETTER ZETA;Lu;0;L;;;;;N;;;;03B6; 0397;GREEK CAPITAL LETTER ETA;Lu;0;L;;;;;N;;;;03B7; 0398;GREEK CAPITAL LETTER THETA;Lu;0;L;;;;;N;;;;03B8; 0399;GREEK CAPITAL LETTER IOTA;Lu;0;L;;;;;N;;;;03B9; 039A;GREEK CAPITAL LETTER KAPPA;Lu;0;L;;;;;N;;;;03BA; 039B;GREEK CAPITAL LETTER LAMDA;Lu;0;L;;;;;N;GREEK CAPITAL LETTER LAMBDA;;;03BB; 039C;GREEK CAPITAL LETTER MU;Lu;0;L;;;;;N;;;;03BC; 039D;GREEK CAPITAL LETTER NU;Lu;0;L;;;;;N;;;;03BD; 039E;GREEK CAPITAL LETTER XI;Lu;0;L;;;;;N;;;;03BE; 039F;GREEK CAPITAL LETTER OMICRON;Lu;0;L;;;;;N;;;;03BF; 03A0;GREEK CAPITAL LETTER PI;Lu;0;L;;;;;N;;;;03C0; 03A1;GREEK CAPITAL LETTER RHO;Lu;0;L;;;;;N;;;;03C1; 03A3;GREEK CAPITAL LETTER SIGMA;Lu;0;L;;;;;N;;;;03C3; 03A4;GREEK CAPITAL LETTER TAU;Lu;0;L;;;;;N;;;;03C4; 03A5;GREEK CAPITAL LETTER UPSILON;Lu;0;L;;;;;N;;;;03C5; 03A6;GREEK CAPITAL LETTER PHI;Lu;0;L;;;;;N;;;;03C6; 03A7;GREEK CAPITAL LETTER CHI;Lu;0;L;;;;;N;;;;03C7; 03A8;GREEK CAPITAL LETTER PSI;Lu;0;L;;;;;N;;;;03C8; 03A9;GREEK CAPITAL LETTER OMEGA;Lu;0;L;;;;;N;;;;03C9; 03AA;GREEK CAPITAL LETTER IOTA WITH DIALYTIKA;Lu;0;L;0399 0308;;;;N;GREEK CAPITAL LETTER IOTA DIAERESIS;;;03CA; 03AB;GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA;Lu;0;L;03A5 0308;;;;N;GREEK CAPITAL LETTER UPSILON DIAERESIS;;;03CB; 03AC;GREEK SMALL LETTER ALPHA WITH TONOS;Ll;0;L;03B1 0301;;;;N;GREEK SMALL LETTER ALPHA TONOS;;0386;;0386 03AD;GREEK SMALL LETTER EPSILON WITH TONOS;Ll;0;L;03B5 0301;;;;N;GREEK SMALL LETTER EPSILON TONOS;;0388;;0388 03AE;GREEK SMALL LETTER ETA WITH TONOS;Ll;0;L;03B7 0301;;;;N;GREEK SMALL LETTER ETA TONOS;;0389;;0389 03AF;GREEK SMALL LETTER IOTA WITH TONOS;Ll;0;L;03B9 0301;;;;N;GREEK SMALL LETTER IOTA TONOS;;038A;;038A 03B0;GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS;Ll;0;L;03CB 0301;;;;N;GREEK SMALL LETTER UPSILON DIAERESIS TONOS;;;; 03B1;GREEK SMALL LETTER ALPHA;Ll;0;L;;;;;N;;;0391;;0391 03B2;GREEK SMALL LETTER BETA;Ll;0;L;;;;;N;;;0392;;0392 03B3;GREEK SMALL LETTER GAMMA;Ll;0;L;;;;;N;;;0393;;0393 03B4;GREEK SMALL LETTER DELTA;Ll;0;L;;;;;N;;;0394;;0394 03B5;GREEK SMALL LETTER EPSILON;Ll;0;L;;;;;N;;;0395;;0395 03B6;GREEK SMALL LETTER ZETA;Ll;0;L;;;;;N;;;0396;;0396 03B7;GREEK SMALL LETTER ETA;Ll;0;L;;;;;N;;;0397;;0397 03B8;GREEK SMALL LETTER THETA;Ll;0;L;;;;;N;;;0398;;0398 03B9;GREEK SMALL LETTER IOTA;Ll;0;L;;;;;N;;;0399;;0399 03BA;GREEK SMALL LETTER KAPPA;Ll;0;L;;;;;N;;;039A;;039A 03BB;GREEK SMALL LETTER LAMDA;Ll;0;L;;;;;N;GREEK SMALL LETTER LAMBDA;;039B;;039B 03BC;GREEK SMALL LETTER MU;Ll;0;L;;;;;N;;;039C;;039C 03BD;GREEK SMALL LETTER NU;Ll;0;L;;;;;N;;;039D;;039D 03BE;GREEK SMALL LETTER XI;Ll;0;L;;;;;N;;;039E;;039E 03BF;GREEK SMALL LETTER OMICRON;Ll;0;L;;;;;N;;;039F;;039F 03C0;GREEK SMALL LETTER PI;Ll;0;L;;;;;N;;;03A0;;03A0 03C1;GREEK SMALL LETTER RHO;Ll;0;L;;;;;N;;;03A1;;03A1 03C2;GREEK SMALL LETTER FINAL SIGMA;Ll;0;L;;;;;N;;;03A3;;03A3 03C3;GREEK SMALL LETTER SIGMA;Ll;0;L;;;;;N;;;03A3;;03A3 03C4;GREEK SMALL LETTER TAU;Ll;0;L;;;;;N;;;03A4;;03A4 03C5;GREEK SMALL LETTER UPSILON;Ll;0;L;;;;;N;;;03A5;;03A5 03C6;GREEK SMALL LETTER PHI;Ll;0;L;;;;;N;;;03A6;;03A6 03C7;GREEK SMALL LETTER CHI;Ll;0;L;;;;;N;;;03A7;;03A7 03C8;GREEK SMALL LETTER PSI;Ll;0;L;;;;;N;;;03A8;;03A8 03C9;GREEK SMALL LETTER OMEGA;Ll;0;L;;;;;N;;;03A9;;03A9 03CA;GREEK SMALL LETTER IOTA WITH DIALYTIKA;Ll;0;L;03B9 0308;;;;N;GREEK SMALL LETTER IOTA DIAERESIS;;03AA;;03AA 03CB;GREEK SMALL LETTER UPSILON WITH DIALYTIKA;Ll;0;L;03C5 0308;;;;N;GREEK SMALL LETTER UPSILON DIAERESIS;;03AB;;03AB 03CC;GREEK SMALL LETTER OMICRON WITH TONOS;Ll;0;L;03BF 0301;;;;N;GREEK SMALL LETTER OMICRON TONOS;;038C;;038C 03CD;GREEK SMALL LETTER UPSILON WITH TONOS;Ll;0;L;03C5 0301;;;;N;GREEK SMALL LETTER UPSILON TONOS;;038E;;038E 03CE;GREEK SMALL LETTER OMEGA WITH TONOS;Ll;0;L;03C9 0301;;;;N;GREEK SMALL LETTER OMEGA TONOS;;038F;;038F 03CF;GREEK CAPITAL KAI SYMBOL;Lu;0;L;;;;;N;;;;03D7; 03D0;GREEK BETA SYMBOL;Ll;0;L; 03B2;;;;N;GREEK SMALL LETTER CURLED BETA;;0392;;0392 03D1;GREEK THETA SYMBOL;Ll;0;L; 03B8;;;;N;GREEK SMALL LETTER SCRIPT THETA;;0398;;0398 03D2;GREEK UPSILON WITH HOOK SYMBOL;Lu;0;L; 03A5;;;;N;GREEK CAPITAL LETTER UPSILON HOOK;;;; 03D3;GREEK UPSILON WITH ACUTE AND HOOK SYMBOL;Lu;0;L;03D2 0301;;;;N;GREEK CAPITAL LETTER UPSILON HOOK TONOS;;;; 03D4;GREEK UPSILON WITH DIAERESIS AND HOOK SYMBOL;Lu;0;L;03D2 0308;;;;N;GREEK CAPITAL LETTER UPSILON HOOK DIAERESIS;;;; 03D5;GREEK PHI SYMBOL;Ll;0;L; 03C6;;;;N;GREEK SMALL LETTER SCRIPT PHI;;03A6;;03A6 03D6;GREEK PI SYMBOL;Ll;0;L; 03C0;;;;N;GREEK SMALL LETTER OMEGA PI;;03A0;;03A0 03D7;GREEK KAI SYMBOL;Ll;0;L;;;;;N;;;03CF;;03CF 03D8;GREEK LETTER ARCHAIC KOPPA;Lu;0;L;;;;;N;;;;03D9; 03D9;GREEK SMALL LETTER ARCHAIC KOPPA;Ll;0;L;;;;;N;;;03D8;;03D8 03DA;GREEK LETTER STIGMA;Lu;0;L;;;;;N;GREEK CAPITAL LETTER STIGMA;;;03DB; 03DB;GREEK SMALL LETTER STIGMA;Ll;0;L;;;;;N;;;03DA;;03DA 03DC;GREEK LETTER DIGAMMA;Lu;0;L;;;;;N;GREEK CAPITAL LETTER DIGAMMA;;;03DD; 03DD;GREEK SMALL LETTER DIGAMMA;Ll;0;L;;;;;N;;;03DC;;03DC 03DE;GREEK LETTER KOPPA;Lu;0;L;;;;;N;GREEK CAPITAL LETTER KOPPA;;;03DF; 03DF;GREEK SMALL LETTER KOPPA;Ll;0;L;;;;;N;;;03DE;;03DE 03E0;GREEK LETTER SAMPI;Lu;0;L;;;;;N;GREEK CAPITAL LETTER SAMPI;;;03E1; 03E1;GREEK SMALL LETTER SAMPI;Ll;0;L;;;;;N;;;03E0;;03E0 03E2;COPTIC CAPITAL LETTER SHEI;Lu;0;L;;;;;N;GREEK CAPITAL LETTER SHEI;;;03E3; 03E3;COPTIC SMALL LETTER SHEI;Ll;0;L;;;;;N;GREEK SMALL LETTER SHEI;;03E2;;03E2 03E4;COPTIC CAPITAL LETTER FEI;Lu;0;L;;;;;N;GREEK CAPITAL LETTER FEI;;;03E5; 03E5;COPTIC SMALL LETTER FEI;Ll;0;L;;;;;N;GREEK SMALL LETTER FEI;;03E4;;03E4 03E6;COPTIC CAPITAL LETTER KHEI;Lu;0;L;;;;;N;GREEK CAPITAL LETTER KHEI;;;03E7; 03E7;COPTIC SMALL LETTER KHEI;Ll;0;L;;;;;N;GREEK SMALL LETTER KHEI;;03E6;;03E6 03E8;COPTIC CAPITAL LETTER HORI;Lu;0;L;;;;;N;GREEK CAPITAL LETTER HORI;;;03E9; 03E9;COPTIC SMALL LETTER HORI;Ll;0;L;;;;;N;GREEK SMALL LETTER HORI;;03E8;;03E8 03EA;COPTIC CAPITAL LETTER GANGIA;Lu;0;L;;;;;N;GREEK CAPITAL LETTER GANGIA;;;03EB; 03EB;COPTIC SMALL LETTER GANGIA;Ll;0;L;;;;;N;GREEK SMALL LETTER GANGIA;;03EA;;03EA 03EC;COPTIC CAPITAL LETTER SHIMA;Lu;0;L;;;;;N;GREEK CAPITAL LETTER SHIMA;;;03ED; 03ED;COPTIC SMALL LETTER SHIMA;Ll;0;L;;;;;N;GREEK SMALL LETTER SHIMA;;03EC;;03EC 03EE;COPTIC CAPITAL LETTER DEI;Lu;0;L;;;;;N;GREEK CAPITAL LETTER DEI;;;03EF; 03EF;COPTIC SMALL LETTER DEI;Ll;0;L;;;;;N;GREEK SMALL LETTER DEI;;03EE;;03EE 03F0;GREEK KAPPA SYMBOL;Ll;0;L; 03BA;;;;N;GREEK SMALL LETTER SCRIPT KAPPA;;039A;;039A 03F1;GREEK RHO SYMBOL;Ll;0;L; 03C1;;;;N;GREEK SMALL LETTER TAILED RHO;;03A1;;03A1 03F2;GREEK LUNATE SIGMA SYMBOL;Ll;0;L; 03C2;;;;N;GREEK SMALL LETTER LUNATE SIGMA;;03F9;;03F9 03F3;GREEK LETTER YOT;Ll;0;L;;;;;N;;;037F;;037F 03F4;GREEK CAPITAL THETA SYMBOL;Lu;0;L; 0398;;;;N;;;;03B8; 03F5;GREEK LUNATE EPSILON SYMBOL;Ll;0;L; 03B5;;;;N;;;0395;;0395 03F6;GREEK REVERSED LUNATE EPSILON SYMBOL;Sm;0;ON;;;;;N;;;;; 03F7;GREEK CAPITAL LETTER SHO;Lu;0;L;;;;;N;;;;03F8; 03F8;GREEK SMALL LETTER SHO;Ll;0;L;;;;;N;;;03F7;;03F7 03F9;GREEK CAPITAL LUNATE SIGMA SYMBOL;Lu;0;L; 03A3;;;;N;;;;03F2; 03FA;GREEK CAPITAL LETTER SAN;Lu;0;L;;;;;N;;;;03FB; 03FB;GREEK SMALL LETTER SAN;Ll;0;L;;;;;N;;;03FA;;03FA 03FC;GREEK RHO WITH STROKE SYMBOL;Ll;0;L;;;;;N;;;;; 03FD;GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL;Lu;0;L;;;;;N;;;;037B; 03FE;GREEK CAPITAL DOTTED LUNATE SIGMA SYMBOL;Lu;0;L;;;;;N;;;;037C; 03FF;GREEK CAPITAL REVERSED DOTTED LUNATE SIGMA SYMBOL;Lu;0;L;;;;;N;;;;037D; 0400;CYRILLIC CAPITAL LETTER IE WITH GRAVE;Lu;0;L;0415 0300;;;;N;;;;0450; 0401;CYRILLIC CAPITAL LETTER IO;Lu;0;L;0415 0308;;;;N;;;;0451; 0402;CYRILLIC CAPITAL LETTER DJE;Lu;0;L;;;;;N;;;;0452; 0403;CYRILLIC CAPITAL LETTER GJE;Lu;0;L;0413 0301;;;;N;;;;0453; 0404;CYRILLIC CAPITAL LETTER UKRAINIAN IE;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER E;;;0454; 0405;CYRILLIC CAPITAL LETTER DZE;Lu;0;L;;;;;N;;;;0455; 0406;CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER I;;;0456; 0407;CYRILLIC CAPITAL LETTER YI;Lu;0;L;0406 0308;;;;N;;;;0457; 0408;CYRILLIC CAPITAL LETTER JE;Lu;0;L;;;;;N;;;;0458; 0409;CYRILLIC CAPITAL LETTER LJE;Lu;0;L;;;;;N;;;;0459; 040A;CYRILLIC CAPITAL LETTER NJE;Lu;0;L;;;;;N;;;;045A; 040B;CYRILLIC CAPITAL LETTER TSHE;Lu;0;L;;;;;N;;;;045B; 040C;CYRILLIC CAPITAL LETTER KJE;Lu;0;L;041A 0301;;;;N;;;;045C; 040D;CYRILLIC CAPITAL LETTER I WITH GRAVE;Lu;0;L;0418 0300;;;;N;;;;045D; 040E;CYRILLIC CAPITAL LETTER SHORT U;Lu;0;L;0423 0306;;;;N;;;;045E; 040F;CYRILLIC CAPITAL LETTER DZHE;Lu;0;L;;;;;N;;;;045F; 0410;CYRILLIC CAPITAL LETTER A;Lu;0;L;;;;;N;;;;0430; 0411;CYRILLIC CAPITAL LETTER BE;Lu;0;L;;;;;N;;;;0431; 0412;CYRILLIC CAPITAL LETTER VE;Lu;0;L;;;;;N;;;;0432; 0413;CYRILLIC CAPITAL LETTER GHE;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER GE;;;0433; 0414;CYRILLIC CAPITAL LETTER DE;Lu;0;L;;;;;N;;;;0434; 0415;CYRILLIC CAPITAL LETTER IE;Lu;0;L;;;;;N;;;;0435; 0416;CYRILLIC CAPITAL LETTER ZHE;Lu;0;L;;;;;N;;;;0436; 0417;CYRILLIC CAPITAL LETTER ZE;Lu;0;L;;;;;N;;;;0437; 0418;CYRILLIC CAPITAL LETTER I;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER II;;;0438; 0419;CYRILLIC CAPITAL LETTER SHORT I;Lu;0;L;0418 0306;;;;N;CYRILLIC CAPITAL LETTER SHORT II;;;0439; 041A;CYRILLIC CAPITAL LETTER KA;Lu;0;L;;;;;N;;;;043A; 041B;CYRILLIC CAPITAL LETTER EL;Lu;0;L;;;;;N;;;;043B; 041C;CYRILLIC CAPITAL LETTER EM;Lu;0;L;;;;;N;;;;043C; 041D;CYRILLIC CAPITAL LETTER EN;Lu;0;L;;;;;N;;;;043D; 041E;CYRILLIC CAPITAL LETTER O;Lu;0;L;;;;;N;;;;043E; 041F;CYRILLIC CAPITAL LETTER PE;Lu;0;L;;;;;N;;;;043F; 0420;CYRILLIC CAPITAL LETTER ER;Lu;0;L;;;;;N;;;;0440; 0421;CYRILLIC CAPITAL LETTER ES;Lu;0;L;;;;;N;;;;0441; 0422;CYRILLIC CAPITAL LETTER TE;Lu;0;L;;;;;N;;;;0442; 0423;CYRILLIC CAPITAL LETTER U;Lu;0;L;;;;;N;;;;0443; 0424;CYRILLIC CAPITAL LETTER EF;Lu;0;L;;;;;N;;;;0444; 0425;CYRILLIC CAPITAL LETTER HA;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER KHA;;;0445; 0426;CYRILLIC CAPITAL LETTER TSE;Lu;0;L;;;;;N;;;;0446; 0427;CYRILLIC CAPITAL LETTER CHE;Lu;0;L;;;;;N;;;;0447; 0428;CYRILLIC CAPITAL LETTER SHA;Lu;0;L;;;;;N;;;;0448; 0429;CYRILLIC CAPITAL LETTER SHCHA;Lu;0;L;;;;;N;;;;0449; 042A;CYRILLIC CAPITAL LETTER HARD SIGN;Lu;0;L;;;;;N;;;;044A; 042B;CYRILLIC CAPITAL LETTER YERU;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER YERI;;;044B; 042C;CYRILLIC CAPITAL LETTER SOFT SIGN;Lu;0;L;;;;;N;;;;044C; 042D;CYRILLIC CAPITAL LETTER E;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER REVERSED E;;;044D; 042E;CYRILLIC CAPITAL LETTER YU;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER IU;;;044E; 042F;CYRILLIC CAPITAL LETTER YA;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER IA;;;044F; 0430;CYRILLIC SMALL LETTER A;Ll;0;L;;;;;N;;;0410;;0410 0431;CYRILLIC SMALL LETTER BE;Ll;0;L;;;;;N;;;0411;;0411 0432;CYRILLIC SMALL LETTER VE;Ll;0;L;;;;;N;;;0412;;0412 0433;CYRILLIC SMALL LETTER GHE;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER GE;;0413;;0413 0434;CYRILLIC SMALL LETTER DE;Ll;0;L;;;;;N;;;0414;;0414 0435;CYRILLIC SMALL LETTER IE;Ll;0;L;;;;;N;;;0415;;0415 0436;CYRILLIC SMALL LETTER ZHE;Ll;0;L;;;;;N;;;0416;;0416 0437;CYRILLIC SMALL LETTER ZE;Ll;0;L;;;;;N;;;0417;;0417 0438;CYRILLIC SMALL LETTER I;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER II;;0418;;0418 0439;CYRILLIC SMALL LETTER SHORT I;Ll;0;L;0438 0306;;;;N;CYRILLIC SMALL LETTER SHORT II;;0419;;0419 043A;CYRILLIC SMALL LETTER KA;Ll;0;L;;;;;N;;;041A;;041A 043B;CYRILLIC SMALL LETTER EL;Ll;0;L;;;;;N;;;041B;;041B 043C;CYRILLIC SMALL LETTER EM;Ll;0;L;;;;;N;;;041C;;041C 043D;CYRILLIC SMALL LETTER EN;Ll;0;L;;;;;N;;;041D;;041D 043E;CYRILLIC SMALL LETTER O;Ll;0;L;;;;;N;;;041E;;041E 043F;CYRILLIC SMALL LETTER PE;Ll;0;L;;;;;N;;;041F;;041F 0440;CYRILLIC SMALL LETTER ER;Ll;0;L;;;;;N;;;0420;;0420 0441;CYRILLIC SMALL LETTER ES;Ll;0;L;;;;;N;;;0421;;0421 0442;CYRILLIC SMALL LETTER TE;Ll;0;L;;;;;N;;;0422;;0422 0443;CYRILLIC SMALL LETTER U;Ll;0;L;;;;;N;;;0423;;0423 0444;CYRILLIC SMALL LETTER EF;Ll;0;L;;;;;N;;;0424;;0424 0445;CYRILLIC SMALL LETTER HA;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER KHA;;0425;;0425 0446;CYRILLIC SMALL LETTER TSE;Ll;0;L;;;;;N;;;0426;;0426 0447;CYRILLIC SMALL LETTER CHE;Ll;0;L;;;;;N;;;0427;;0427 0448;CYRILLIC SMALL LETTER SHA;Ll;0;L;;;;;N;;;0428;;0428 0449;CYRILLIC SMALL LETTER SHCHA;Ll;0;L;;;;;N;;;0429;;0429 044A;CYRILLIC SMALL LETTER HARD SIGN;Ll;0;L;;;;;N;;;042A;;042A 044B;CYRILLIC SMALL LETTER YERU;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER YERI;;042B;;042B 044C;CYRILLIC SMALL LETTER SOFT SIGN;Ll;0;L;;;;;N;;;042C;;042C 044D;CYRILLIC SMALL LETTER E;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER REVERSED E;;042D;;042D 044E;CYRILLIC SMALL LETTER YU;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER IU;;042E;;042E 044F;CYRILLIC SMALL LETTER YA;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER IA;;042F;;042F 0450;CYRILLIC SMALL LETTER IE WITH GRAVE;Ll;0;L;0435 0300;;;;N;;;0400;;0400 0451;CYRILLIC SMALL LETTER IO;Ll;0;L;0435 0308;;;;N;;;0401;;0401 0452;CYRILLIC SMALL LETTER DJE;Ll;0;L;;;;;N;;;0402;;0402 0453;CYRILLIC SMALL LETTER GJE;Ll;0;L;0433 0301;;;;N;;;0403;;0403 0454;CYRILLIC SMALL LETTER UKRAINIAN IE;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER E;;0404;;0404 0455;CYRILLIC SMALL LETTER DZE;Ll;0;L;;;;;N;;;0405;;0405 0456;CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER I;;0406;;0406 0457;CYRILLIC SMALL LETTER YI;Ll;0;L;0456 0308;;;;N;;;0407;;0407 0458;CYRILLIC SMALL LETTER JE;Ll;0;L;;;;;N;;;0408;;0408 0459;CYRILLIC SMALL LETTER LJE;Ll;0;L;;;;;N;;;0409;;0409 045A;CYRILLIC SMALL LETTER NJE;Ll;0;L;;;;;N;;;040A;;040A 045B;CYRILLIC SMALL LETTER TSHE;Ll;0;L;;;;;N;;;040B;;040B 045C;CYRILLIC SMALL LETTER KJE;Ll;0;L;043A 0301;;;;N;;;040C;;040C 045D;CYRILLIC SMALL LETTER I WITH GRAVE;Ll;0;L;0438 0300;;;;N;;;040D;;040D 045E;CYRILLIC SMALL LETTER SHORT U;Ll;0;L;0443 0306;;;;N;;;040E;;040E 045F;CYRILLIC SMALL LETTER DZHE;Ll;0;L;;;;;N;;;040F;;040F 0460;CYRILLIC CAPITAL LETTER OMEGA;Lu;0;L;;;;;N;;;;0461; 0461;CYRILLIC SMALL LETTER OMEGA;Ll;0;L;;;;;N;;;0460;;0460 0462;CYRILLIC CAPITAL LETTER YAT;Lu;0;L;;;;;N;;;;0463; 0463;CYRILLIC SMALL LETTER YAT;Ll;0;L;;;;;N;;;0462;;0462 0464;CYRILLIC CAPITAL LETTER IOTIFIED E;Lu;0;L;;;;;N;;;;0465; 0465;CYRILLIC SMALL LETTER IOTIFIED E;Ll;0;L;;;;;N;;;0464;;0464 0466;CYRILLIC CAPITAL LETTER LITTLE YUS;Lu;0;L;;;;;N;;;;0467; 0467;CYRILLIC SMALL LETTER LITTLE YUS;Ll;0;L;;;;;N;;;0466;;0466 0468;CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS;Lu;0;L;;;;;N;;;;0469; 0469;CYRILLIC SMALL LETTER IOTIFIED LITTLE YUS;Ll;0;L;;;;;N;;;0468;;0468 046A;CYRILLIC CAPITAL LETTER BIG YUS;Lu;0;L;;;;;N;;;;046B; 046B;CYRILLIC SMALL LETTER BIG YUS;Ll;0;L;;;;;N;;;046A;;046A 046C;CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS;Lu;0;L;;;;;N;;;;046D; 046D;CYRILLIC SMALL LETTER IOTIFIED BIG YUS;Ll;0;L;;;;;N;;;046C;;046C 046E;CYRILLIC CAPITAL LETTER KSI;Lu;0;L;;;;;N;;;;046F; 046F;CYRILLIC SMALL LETTER KSI;Ll;0;L;;;;;N;;;046E;;046E 0470;CYRILLIC CAPITAL LETTER PSI;Lu;0;L;;;;;N;;;;0471; 0471;CYRILLIC SMALL LETTER PSI;Ll;0;L;;;;;N;;;0470;;0470 0472;CYRILLIC CAPITAL LETTER FITA;Lu;0;L;;;;;N;;;;0473; 0473;CYRILLIC SMALL LETTER FITA;Ll;0;L;;;;;N;;;0472;;0472 0474;CYRILLIC CAPITAL LETTER IZHITSA;Lu;0;L;;;;;N;;;;0475; 0475;CYRILLIC SMALL LETTER IZHITSA;Ll;0;L;;;;;N;;;0474;;0474 0476;CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT;Lu;0;L;0474 030F;;;;N;CYRILLIC CAPITAL LETTER IZHITSA DOUBLE GRAVE;;;0477; 0477;CYRILLIC SMALL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT;Ll;0;L;0475 030F;;;;N;CYRILLIC SMALL LETTER IZHITSA DOUBLE GRAVE;;0476;;0476 0478;CYRILLIC CAPITAL LETTER UK;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER UK DIGRAPH;;;0479; 0479;CYRILLIC SMALL LETTER UK;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER UK DIGRAPH;;0478;;0478 047A;CYRILLIC CAPITAL LETTER ROUND OMEGA;Lu;0;L;;;;;N;;;;047B; 047B;CYRILLIC SMALL LETTER ROUND OMEGA;Ll;0;L;;;;;N;;;047A;;047A 047C;CYRILLIC CAPITAL LETTER OMEGA WITH TITLO;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER OMEGA TITLO;;;047D; 047D;CYRILLIC SMALL LETTER OMEGA WITH TITLO;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER OMEGA TITLO;;047C;;047C 047E;CYRILLIC CAPITAL LETTER OT;Lu;0;L;;;;;N;;;;047F; 047F;CYRILLIC SMALL LETTER OT;Ll;0;L;;;;;N;;;047E;;047E 0480;CYRILLIC CAPITAL LETTER KOPPA;Lu;0;L;;;;;N;;;;0481; 0481;CYRILLIC SMALL LETTER KOPPA;Ll;0;L;;;;;N;;;0480;;0480 0482;CYRILLIC THOUSANDS SIGN;So;0;L;;;;;N;;;;; 0483;COMBINING CYRILLIC TITLO;Mn;230;NSM;;;;;N;CYRILLIC NON-SPACING TITLO;;;; 0484;COMBINING CYRILLIC PALATALIZATION;Mn;230;NSM;;;;;N;CYRILLIC NON-SPACING PALATALIZATION;;;; 0485;COMBINING CYRILLIC DASIA PNEUMATA;Mn;230;NSM;;;;;N;CYRILLIC NON-SPACING DASIA PNEUMATA;;;; 0486;COMBINING CYRILLIC PSILI PNEUMATA;Mn;230;NSM;;;;;N;CYRILLIC NON-SPACING PSILI PNEUMATA;;;; 0487;COMBINING CYRILLIC POKRYTIE;Mn;230;NSM;;;;;N;;;;; 0488;COMBINING CYRILLIC HUNDRED THOUSANDS SIGN;Me;0;NSM;;;;;N;;;;; 0489;COMBINING CYRILLIC MILLIONS SIGN;Me;0;NSM;;;;;N;;;;; 048A;CYRILLIC CAPITAL LETTER SHORT I WITH TAIL;Lu;0;L;;;;;N;;;;048B; 048B;CYRILLIC SMALL LETTER SHORT I WITH TAIL;Ll;0;L;;;;;N;;;048A;;048A 048C;CYRILLIC CAPITAL LETTER SEMISOFT SIGN;Lu;0;L;;;;;N;;;;048D; 048D;CYRILLIC SMALL LETTER SEMISOFT SIGN;Ll;0;L;;;;;N;;;048C;;048C 048E;CYRILLIC CAPITAL LETTER ER WITH TICK;Lu;0;L;;;;;N;;;;048F; 048F;CYRILLIC SMALL LETTER ER WITH TICK;Ll;0;L;;;;;N;;;048E;;048E 0490;CYRILLIC CAPITAL LETTER GHE WITH UPTURN;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER GE WITH UPTURN;;;0491; 0491;CYRILLIC SMALL LETTER GHE WITH UPTURN;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER GE WITH UPTURN;;0490;;0490 0492;CYRILLIC CAPITAL LETTER GHE WITH STROKE;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER GE BAR;;;0493; 0493;CYRILLIC SMALL LETTER GHE WITH STROKE;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER GE BAR;;0492;;0492 0494;CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER GE HOOK;;;0495; 0495;CYRILLIC SMALL LETTER GHE WITH MIDDLE HOOK;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER GE HOOK;;0494;;0494 0496;CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER ZHE WITH RIGHT DESCENDER;;;0497; 0497;CYRILLIC SMALL LETTER ZHE WITH DESCENDER;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER ZHE WITH RIGHT DESCENDER;;0496;;0496 0498;CYRILLIC CAPITAL LETTER ZE WITH DESCENDER;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER ZE CEDILLA;;;0499; 0499;CYRILLIC SMALL LETTER ZE WITH DESCENDER;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER ZE CEDILLA;;0498;;0498 049A;CYRILLIC CAPITAL LETTER KA WITH DESCENDER;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER KA WITH RIGHT DESCENDER;;;049B; 049B;CYRILLIC SMALL LETTER KA WITH DESCENDER;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER KA WITH RIGHT DESCENDER;;049A;;049A 049C;CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER KA VERTICAL BAR;;;049D; 049D;CYRILLIC SMALL LETTER KA WITH VERTICAL STROKE;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER KA VERTICAL BAR;;049C;;049C 049E;CYRILLIC CAPITAL LETTER KA WITH STROKE;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER KA BAR;;;049F; 049F;CYRILLIC SMALL LETTER KA WITH STROKE;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER KA BAR;;049E;;049E 04A0;CYRILLIC CAPITAL LETTER BASHKIR KA;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER REVERSED GE KA;;;04A1; 04A1;CYRILLIC SMALL LETTER BASHKIR KA;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER REVERSED GE KA;;04A0;;04A0 04A2;CYRILLIC CAPITAL LETTER EN WITH DESCENDER;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER EN WITH RIGHT DESCENDER;;;04A3; 04A3;CYRILLIC SMALL LETTER EN WITH DESCENDER;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER EN WITH RIGHT DESCENDER;;04A2;;04A2 04A4;CYRILLIC CAPITAL LIGATURE EN GHE;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER EN GE;;;04A5; 04A5;CYRILLIC SMALL LIGATURE EN GHE;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER EN GE;;04A4;;04A4 04A6;CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER PE HOOK;;;04A7; 04A7;CYRILLIC SMALL LETTER PE WITH MIDDLE HOOK;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER PE HOOK;;04A6;;04A6 04A8;CYRILLIC CAPITAL LETTER ABKHASIAN HA;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER O HOOK;;;04A9; 04A9;CYRILLIC SMALL LETTER ABKHASIAN HA;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER O HOOK;;04A8;;04A8 04AA;CYRILLIC CAPITAL LETTER ES WITH DESCENDER;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER ES CEDILLA;;;04AB; 04AB;CYRILLIC SMALL LETTER ES WITH DESCENDER;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER ES CEDILLA;;04AA;;04AA 04AC;CYRILLIC CAPITAL LETTER TE WITH DESCENDER;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER TE WITH RIGHT DESCENDER;;;04AD; 04AD;CYRILLIC SMALL LETTER TE WITH DESCENDER;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER TE WITH RIGHT DESCENDER;;04AC;;04AC 04AE;CYRILLIC CAPITAL LETTER STRAIGHT U;Lu;0;L;;;;;N;;;;04AF; 04AF;CYRILLIC SMALL LETTER STRAIGHT U;Ll;0;L;;;;;N;;;04AE;;04AE 04B0;CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER STRAIGHT U BAR;;;04B1; 04B1;CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER STRAIGHT U BAR;;04B0;;04B0 04B2;CYRILLIC CAPITAL LETTER HA WITH DESCENDER;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER KHA WITH RIGHT DESCENDER;;;04B3; 04B3;CYRILLIC SMALL LETTER HA WITH DESCENDER;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER KHA WITH RIGHT DESCENDER;;04B2;;04B2 04B4;CYRILLIC CAPITAL LIGATURE TE TSE;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER TE TSE;;;04B5; 04B5;CYRILLIC SMALL LIGATURE TE TSE;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER TE TSE;;04B4;;04B4 04B6;CYRILLIC CAPITAL LETTER CHE WITH DESCENDER;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER CHE WITH RIGHT DESCENDER;;;04B7; 04B7;CYRILLIC SMALL LETTER CHE WITH DESCENDER;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER CHE WITH RIGHT DESCENDER;;04B6;;04B6 04B8;CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER CHE VERTICAL BAR;;;04B9; 04B9;CYRILLIC SMALL LETTER CHE WITH VERTICAL STROKE;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER CHE VERTICAL BAR;;04B8;;04B8 04BA;CYRILLIC CAPITAL LETTER SHHA;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER H;;;04BB; 04BB;CYRILLIC SMALL LETTER SHHA;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER H;;04BA;;04BA 04BC;CYRILLIC CAPITAL LETTER ABKHASIAN CHE;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER IE HOOK;;;04BD; 04BD;CYRILLIC SMALL LETTER ABKHASIAN CHE;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER IE HOOK;;04BC;;04BC 04BE;CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER IE HOOK OGONEK;;;04BF; 04BF;CYRILLIC SMALL LETTER ABKHASIAN CHE WITH DESCENDER;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER IE HOOK OGONEK;;04BE;;04BE 04C0;CYRILLIC LETTER PALOCHKA;Lu;0;L;;;;;N;CYRILLIC LETTER I;;;04CF; 04C1;CYRILLIC CAPITAL LETTER ZHE WITH BREVE;Lu;0;L;0416 0306;;;;N;CYRILLIC CAPITAL LETTER SHORT ZHE;;;04C2; 04C2;CYRILLIC SMALL LETTER ZHE WITH BREVE;Ll;0;L;0436 0306;;;;N;CYRILLIC SMALL LETTER SHORT ZHE;;04C1;;04C1 04C3;CYRILLIC CAPITAL LETTER KA WITH HOOK;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER KA HOOK;;;04C4; 04C4;CYRILLIC SMALL LETTER KA WITH HOOK;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER KA HOOK;;04C3;;04C3 04C5;CYRILLIC CAPITAL LETTER EL WITH TAIL;Lu;0;L;;;;;N;;;;04C6; 04C6;CYRILLIC SMALL LETTER EL WITH TAIL;Ll;0;L;;;;;N;;;04C5;;04C5 04C7;CYRILLIC CAPITAL LETTER EN WITH HOOK;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER EN HOOK;;;04C8; 04C8;CYRILLIC SMALL LETTER EN WITH HOOK;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER EN HOOK;;04C7;;04C7 04C9;CYRILLIC CAPITAL LETTER EN WITH TAIL;Lu;0;L;;;;;N;;;;04CA; 04CA;CYRILLIC SMALL LETTER EN WITH TAIL;Ll;0;L;;;;;N;;;04C9;;04C9 04CB;CYRILLIC CAPITAL LETTER KHAKASSIAN CHE;Lu;0;L;;;;;N;CYRILLIC CAPITAL LETTER CHE WITH LEFT DESCENDER;;;04CC; 04CC;CYRILLIC SMALL LETTER KHAKASSIAN CHE;Ll;0;L;;;;;N;CYRILLIC SMALL LETTER CHE WITH LEFT DESCENDER;;04CB;;04CB 04CD;CYRILLIC CAPITAL LETTER EM WITH TAIL;Lu;0;L;;;;;N;;;;04CE; 04CE;CYRILLIC SMALL LETTER EM WITH TAIL;Ll;0;L;;;;;N;;;04CD;;04CD 04CF;CYRILLIC SMALL LETTER PALOCHKA;Ll;0;L;;;;;N;;;04C0;;04C0 04D0;CYRILLIC CAPITAL LETTER A WITH BREVE;Lu;0;L;0410 0306;;;;N;;;;04D1; 04D1;CYRILLIC SMALL LETTER A WITH BREVE;Ll;0;L;0430 0306;;;;N;;;04D0;;04D0 04D2;CYRILLIC CAPITAL LETTER A WITH DIAERESIS;Lu;0;L;0410 0308;;;;N;;;;04D3; 04D3;CYRILLIC SMALL LETTER A WITH DIAERESIS;Ll;0;L;0430 0308;;;;N;;;04D2;;04D2 04D4;CYRILLIC CAPITAL LIGATURE A IE;Lu;0;L;;;;;N;;;;04D5; 04D5;CYRILLIC SMALL LIGATURE A IE;Ll;0;L;;;;;N;;;04D4;;04D4 04D6;CYRILLIC CAPITAL LETTER IE WITH BREVE;Lu;0;L;0415 0306;;;;N;;;;04D7; 04D7;CYRILLIC SMALL LETTER IE WITH BREVE;Ll;0;L;0435 0306;;;;N;;;04D6;;04D6 04D8;CYRILLIC CAPITAL LETTER SCHWA;Lu;0;L;;;;;N;;;;04D9; 04D9;CYRILLIC SMALL LETTER SCHWA;Ll;0;L;;;;;N;;;04D8;;04D8 04DA;CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS;Lu;0;L;04D8 0308;;;;N;;;;04DB; 04DB;CYRILLIC SMALL LETTER SCHWA WITH DIAERESIS;Ll;0;L;04D9 0308;;;;N;;;04DA;;04DA 04DC;CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS;Lu;0;L;0416 0308;;;;N;;;;04DD; 04DD;CYRILLIC SMALL LETTER ZHE WITH DIAERESIS;Ll;0;L;0436 0308;;;;N;;;04DC;;04DC 04DE;CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS;Lu;0;L;0417 0308;;;;N;;;;04DF; 04DF;CYRILLIC SMALL LETTER ZE WITH DIAERESIS;Ll;0;L;0437 0308;;;;N;;;04DE;;04DE 04E0;CYRILLIC CAPITAL LETTER ABKHASIAN DZE;Lu;0;L;;;;;N;;;;04E1; 04E1;CYRILLIC SMALL LETTER ABKHASIAN DZE;Ll;0;L;;;;;N;;;04E0;;04E0 04E2;CYRILLIC CAPITAL LETTER I WITH MACRON;Lu;0;L;0418 0304;;;;N;;;;04E3; 04E3;CYRILLIC SMALL LETTER I WITH MACRON;Ll;0;L;0438 0304;;;;N;;;04E2;;04E2 04E4;CYRILLIC CAPITAL LETTER I WITH DIAERESIS;Lu;0;L;0418 0308;;;;N;;;;04E5; 04E5;CYRILLIC SMALL LETTER I WITH DIAERESIS;Ll;0;L;0438 0308;;;;N;;;04E4;;04E4 04E6;CYRILLIC CAPITAL LETTER O WITH DIAERESIS;Lu;0;L;041E 0308;;;;N;;;;04E7; 04E7;CYRILLIC SMALL LETTER O WITH DIAERESIS;Ll;0;L;043E 0308;;;;N;;;04E6;;04E6 04E8;CYRILLIC CAPITAL LETTER BARRED O;Lu;0;L;;;;;N;;;;04E9; 04E9;CYRILLIC SMALL LETTER BARRED O;Ll;0;L;;;;;N;;;04E8;;04E8 04EA;CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS;Lu;0;L;04E8 0308;;;;N;;;;04EB; 04EB;CYRILLIC SMALL LETTER BARRED O WITH DIAERESIS;Ll;0;L;04E9 0308;;;;N;;;04EA;;04EA 04EC;CYRILLIC CAPITAL LETTER E WITH DIAERESIS;Lu;0;L;042D 0308;;;;N;;;;04ED; 04ED;CYRILLIC SMALL LETTER E WITH DIAERESIS;Ll;0;L;044D 0308;;;;N;;;04EC;;04EC 04EE;CYRILLIC CAPITAL LETTER U WITH MACRON;Lu;0;L;0423 0304;;;;N;;;;04EF; 04EF;CYRILLIC SMALL LETTER U WITH MACRON;Ll;0;L;0443 0304;;;;N;;;04EE;;04EE 04F0;CYRILLIC CAPITAL LETTER U WITH DIAERESIS;Lu;0;L;0423 0308;;;;N;;;;04F1; 04F1;CYRILLIC SMALL LETTER U WITH DIAERESIS;Ll;0;L;0443 0308;;;;N;;;04F0;;04F0 04F2;CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE;Lu;0;L;0423 030B;;;;N;;;;04F3; 04F3;CYRILLIC SMALL LETTER U WITH DOUBLE ACUTE;Ll;0;L;0443 030B;;;;N;;;04F2;;04F2 04F4;CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS;Lu;0;L;0427 0308;;;;N;;;;04F5; 04F5;CYRILLIC SMALL LETTER CHE WITH DIAERESIS;Ll;0;L;0447 0308;;;;N;;;04F4;;04F4 04F6;CYRILLIC CAPITAL LETTER GHE WITH DESCENDER;Lu;0;L;;;;;N;;;;04F7; 04F7;CYRILLIC SMALL LETTER GHE WITH DESCENDER;Ll;0;L;;;;;N;;;04F6;;04F6 04F8;CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS;Lu;0;L;042B 0308;;;;N;;;;04F9; 04F9;CYRILLIC SMALL LETTER YERU WITH DIAERESIS;Ll;0;L;044B 0308;;;;N;;;04F8;;04F8 04FA;CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK;Lu;0;L;;;;;N;;;;04FB; 04FB;CYRILLIC SMALL LETTER GHE WITH STROKE AND HOOK;Ll;0;L;;;;;N;;;04FA;;04FA 04FC;CYRILLIC CAPITAL LETTER HA WITH HOOK;Lu;0;L;;;;;N;;;;04FD; 04FD;CYRILLIC SMALL LETTER HA WITH HOOK;Ll;0;L;;;;;N;;;04FC;;04FC 04FE;CYRILLIC CAPITAL LETTER HA WITH STROKE;Lu;0;L;;;;;N;;;;04FF; 04FF;CYRILLIC SMALL LETTER HA WITH STROKE;Ll;0;L;;;;;N;;;04FE;;04FE 0500;CYRILLIC CAPITAL LETTER KOMI DE;Lu;0;L;;;;;N;;;;0501; 0501;CYRILLIC SMALL LETTER KOMI DE;Ll;0;L;;;;;N;;;0500;;0500 0502;CYRILLIC CAPITAL LETTER KOMI DJE;Lu;0;L;;;;;N;;;;0503; 0503;CYRILLIC SMALL LETTER KOMI DJE;Ll;0;L;;;;;N;;;0502;;0502 0504;CYRILLIC CAPITAL LETTER KOMI ZJE;Lu;0;L;;;;;N;;;;0505; 0505;CYRILLIC SMALL LETTER KOMI ZJE;Ll;0;L;;;;;N;;;0504;;0504 0506;CYRILLIC CAPITAL LETTER KOMI DZJE;Lu;0;L;;;;;N;;;;0507; 0507;CYRILLIC SMALL LETTER KOMI DZJE;Ll;0;L;;;;;N;;;0506;;0506 0508;CYRILLIC CAPITAL LETTER KOMI LJE;Lu;0;L;;;;;N;;;;0509; 0509;CYRILLIC SMALL LETTER KOMI LJE;Ll;0;L;;;;;N;;;0508;;0508 050A;CYRILLIC CAPITAL LETTER KOMI NJE;Lu;0;L;;;;;N;;;;050B; 050B;CYRILLIC SMALL LETTER KOMI NJE;Ll;0;L;;;;;N;;;050A;;050A 050C;CYRILLIC CAPITAL LETTER KOMI SJE;Lu;0;L;;;;;N;;;;050D; 050D;CYRILLIC SMALL LETTER KOMI SJE;Ll;0;L;;;;;N;;;050C;;050C 050E;CYRILLIC CAPITAL LETTER KOMI TJE;Lu;0;L;;;;;N;;;;050F; 050F;CYRILLIC SMALL LETTER KOMI TJE;Ll;0;L;;;;;N;;;050E;;050E 0510;CYRILLIC CAPITAL LETTER REVERSED ZE;Lu;0;L;;;;;N;;;;0511; 0511;CYRILLIC SMALL LETTER REVERSED ZE;Ll;0;L;;;;;N;;;0510;;0510 0512;CYRILLIC CAPITAL LETTER EL WITH HOOK;Lu;0;L;;;;;N;;;;0513; 0513;CYRILLIC SMALL LETTER EL WITH HOOK;Ll;0;L;;;;;N;;;0512;;0512 0514;CYRILLIC CAPITAL LETTER LHA;Lu;0;L;;;;;N;;;;0515; 0515;CYRILLIC SMALL LETTER LHA;Ll;0;L;;;;;N;;;0514;;0514 0516;CYRILLIC CAPITAL LETTER RHA;Lu;0;L;;;;;N;;;;0517; 0517;CYRILLIC SMALL LETTER RHA;Ll;0;L;;;;;N;;;0516;;0516 0518;CYRILLIC CAPITAL LETTER YAE;Lu;0;L;;;;;N;;;;0519; 0519;CYRILLIC SMALL LETTER YAE;Ll;0;L;;;;;N;;;0518;;0518 051A;CYRILLIC CAPITAL LETTER QA;Lu;0;L;;;;;N;;;;051B; 051B;CYRILLIC SMALL LETTER QA;Ll;0;L;;;;;N;;;051A;;051A 051C;CYRILLIC CAPITAL LETTER WE;Lu;0;L;;;;;N;;;;051D; 051D;CYRILLIC SMALL LETTER WE;Ll;0;L;;;;;N;;;051C;;051C 051E;CYRILLIC CAPITAL LETTER ALEUT KA;Lu;0;L;;;;;N;;;;051F; 051F;CYRILLIC SMALL LETTER ALEUT KA;Ll;0;L;;;;;N;;;051E;;051E 0520;CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK;Lu;0;L;;;;;N;;;;0521; 0521;CYRILLIC SMALL LETTER EL WITH MIDDLE HOOK;Ll;0;L;;;;;N;;;0520;;0520 0522;CYRILLIC CAPITAL LETTER EN WITH MIDDLE HOOK;Lu;0;L;;;;;N;;;;0523; 0523;CYRILLIC SMALL LETTER EN WITH MIDDLE HOOK;Ll;0;L;;;;;N;;;0522;;0522 0524;CYRILLIC CAPITAL LETTER PE WITH DESCENDER;Lu;0;L;;;;;N;;;;0525; 0525;CYRILLIC SMALL LETTER PE WITH DESCENDER;Ll;0;L;;;;;N;;;0524;;0524 0526;CYRILLIC CAPITAL LETTER SHHA WITH DESCENDER;Lu;0;L;;;;;N;;;;0527; 0527;CYRILLIC SMALL LETTER SHHA WITH DESCENDER;Ll;0;L;;;;;N;;;0526;;0526 0528;CYRILLIC CAPITAL LETTER EN WITH LEFT HOOK;Lu;0;L;;;;;N;;;;0529; 0529;CYRILLIC SMALL LETTER EN WITH LEFT HOOK;Ll;0;L;;;;;N;;;0528;;0528 052A;CYRILLIC CAPITAL LETTER DZZHE;Lu;0;L;;;;;N;;;;052B; 052B;CYRILLIC SMALL LETTER DZZHE;Ll;0;L;;;;;N;;;052A;;052A 052C;CYRILLIC CAPITAL LETTER DCHE;Lu;0;L;;;;;N;;;;052D; 052D;CYRILLIC SMALL LETTER DCHE;Ll;0;L;;;;;N;;;052C;;052C 052E;CYRILLIC CAPITAL LETTER EL WITH DESCENDER;Lu;0;L;;;;;N;;;;052F; 052F;CYRILLIC SMALL LETTER EL WITH DESCENDER;Ll;0;L;;;;;N;;;052E;;052E 0531;ARMENIAN CAPITAL LETTER AYB;Lu;0;L;;;;;N;;;;0561; 0532;ARMENIAN CAPITAL LETTER BEN;Lu;0;L;;;;;N;;;;0562; 0533;ARMENIAN CAPITAL LETTER GIM;Lu;0;L;;;;;N;;;;0563; 0534;ARMENIAN CAPITAL LETTER DA;Lu;0;L;;;;;N;;;;0564; 0535;ARMENIAN CAPITAL LETTER ECH;Lu;0;L;;;;;N;;;;0565; 0536;ARMENIAN CAPITAL LETTER ZA;Lu;0;L;;;;;N;;;;0566; 0537;ARMENIAN CAPITAL LETTER EH;Lu;0;L;;;;;N;;;;0567; 0538;ARMENIAN CAPITAL LETTER ET;Lu;0;L;;;;;N;;;;0568; 0539;ARMENIAN CAPITAL LETTER TO;Lu;0;L;;;;;N;;;;0569; 053A;ARMENIAN CAPITAL LETTER ZHE;Lu;0;L;;;;;N;;;;056A; 053B;ARMENIAN CAPITAL LETTER INI;Lu;0;L;;;;;N;;;;056B; 053C;ARMENIAN CAPITAL LETTER LIWN;Lu;0;L;;;;;N;;;;056C; 053D;ARMENIAN CAPITAL LETTER XEH;Lu;0;L;;;;;N;;;;056D; 053E;ARMENIAN CAPITAL LETTER CA;Lu;0;L;;;;;N;;;;056E; 053F;ARMENIAN CAPITAL LETTER KEN;Lu;0;L;;;;;N;;;;056F; 0540;ARMENIAN CAPITAL LETTER HO;Lu;0;L;;;;;N;;;;0570; 0541;ARMENIAN CAPITAL LETTER JA;Lu;0;L;;;;;N;;;;0571; 0542;ARMENIAN CAPITAL LETTER GHAD;Lu;0;L;;;;;N;ARMENIAN CAPITAL LETTER LAD;;;0572; 0543;ARMENIAN CAPITAL LETTER CHEH;Lu;0;L;;;;;N;;;;0573; 0544;ARMENIAN CAPITAL LETTER MEN;Lu;0;L;;;;;N;;;;0574; 0545;ARMENIAN CAPITAL LETTER YI;Lu;0;L;;;;;N;;;;0575; 0546;ARMENIAN CAPITAL LETTER NOW;Lu;0;L;;;;;N;;;;0576; 0547;ARMENIAN CAPITAL LETTER SHA;Lu;0;L;;;;;N;;;;0577; 0548;ARMENIAN CAPITAL LETTER VO;Lu;0;L;;;;;N;;;;0578; 0549;ARMENIAN CAPITAL LETTER CHA;Lu;0;L;;;;;N;;;;0579; 054A;ARMENIAN CAPITAL LETTER PEH;Lu;0;L;;;;;N;;;;057A; 054B;ARMENIAN CAPITAL LETTER JHEH;Lu;0;L;;;;;N;;;;057B; 054C;ARMENIAN CAPITAL LETTER RA;Lu;0;L;;;;;N;;;;057C; 054D;ARMENIAN CAPITAL LETTER SEH;Lu;0;L;;;;;N;;;;057D; 054E;ARMENIAN CAPITAL LETTER VEW;Lu;0;L;;;;;N;;;;057E; 054F;ARMENIAN CAPITAL LETTER TIWN;Lu;0;L;;;;;N;;;;057F; 0550;ARMENIAN CAPITAL LETTER REH;Lu;0;L;;;;;N;;;;0580; 0551;ARMENIAN CAPITAL LETTER CO;Lu;0;L;;;;;N;;;;0581; 0552;ARMENIAN CAPITAL LETTER YIWN;Lu;0;L;;;;;N;;;;0582; 0553;ARMENIAN CAPITAL LETTER PIWR;Lu;0;L;;;;;N;;;;0583; 0554;ARMENIAN CAPITAL LETTER KEH;Lu;0;L;;;;;N;;;;0584; 0555;ARMENIAN CAPITAL LETTER OH;Lu;0;L;;;;;N;;;;0585; 0556;ARMENIAN CAPITAL LETTER FEH;Lu;0;L;;;;;N;;;;0586; 0559;ARMENIAN MODIFIER LETTER LEFT HALF RING;Lm;0;L;;;;;N;;;;; 055A;ARMENIAN APOSTROPHE;Po;0;L;;;;;N;ARMENIAN MODIFIER LETTER RIGHT HALF RING;;;; 055B;ARMENIAN EMPHASIS MARK;Po;0;L;;;;;N;;;;; 055C;ARMENIAN EXCLAMATION MARK;Po;0;L;;;;;N;;;;; 055D;ARMENIAN COMMA;Po;0;L;;;;;N;;;;; 055E;ARMENIAN QUESTION MARK;Po;0;L;;;;;N;;;;; 055F;ARMENIAN ABBREVIATION MARK;Po;0;L;;;;;N;;;;; 0561;ARMENIAN SMALL LETTER AYB;Ll;0;L;;;;;N;;;0531;;0531 0562;ARMENIAN SMALL LETTER BEN;Ll;0;L;;;;;N;;;0532;;0532 0563;ARMENIAN SMALL LETTER GIM;Ll;0;L;;;;;N;;;0533;;0533 0564;ARMENIAN SMALL LETTER DA;Ll;0;L;;;;;N;;;0534;;0534 0565;ARMENIAN SMALL LETTER ECH;Ll;0;L;;;;;N;;;0535;;0535 0566;ARMENIAN SMALL LETTER ZA;Ll;0;L;;;;;N;;;0536;;0536 0567;ARMENIAN SMALL LETTER EH;Ll;0;L;;;;;N;;;0537;;0537 0568;ARMENIAN SMALL LETTER ET;Ll;0;L;;;;;N;;;0538;;0538 0569;ARMENIAN SMALL LETTER TO;Ll;0;L;;;;;N;;;0539;;0539 056A;ARMENIAN SMALL LETTER ZHE;Ll;0;L;;;;;N;;;053A;;053A 056B;ARMENIAN SMALL LETTER INI;Ll;0;L;;;;;N;;;053B;;053B 056C;ARMENIAN SMALL LETTER LIWN;Ll;0;L;;;;;N;;;053C;;053C 056D;ARMENIAN SMALL LETTER XEH;Ll;0;L;;;;;N;;;053D;;053D 056E;ARMENIAN SMALL LETTER CA;Ll;0;L;;;;;N;;;053E;;053E 056F;ARMENIAN SMALL LETTER KEN;Ll;0;L;;;;;N;;;053F;;053F 0570;ARMENIAN SMALL LETTER HO;Ll;0;L;;;;;N;;;0540;;0540 0571;ARMENIAN SMALL LETTER JA;Ll;0;L;;;;;N;;;0541;;0541 0572;ARMENIAN SMALL LETTER GHAD;Ll;0;L;;;;;N;ARMENIAN SMALL LETTER LAD;;0542;;0542 0573;ARMENIAN SMALL LETTER CHEH;Ll;0;L;;;;;N;;;0543;;0543 0574;ARMENIAN SMALL LETTER MEN;Ll;0;L;;;;;N;;;0544;;0544 0575;ARMENIAN SMALL LETTER YI;Ll;0;L;;;;;N;;;0545;;0545 0576;ARMENIAN SMALL LETTER NOW;Ll;0;L;;;;;N;;;0546;;0546 0577;ARMENIAN SMALL LETTER SHA;Ll;0;L;;;;;N;;;0547;;0547 0578;ARMENIAN SMALL LETTER VO;Ll;0;L;;;;;N;;;0548;;0548 0579;ARMENIAN SMALL LETTER CHA;Ll;0;L;;;;;N;;;0549;;0549 057A;ARMENIAN SMALL LETTER PEH;Ll;0;L;;;;;N;;;054A;;054A 057B;ARMENIAN SMALL LETTER JHEH;Ll;0;L;;;;;N;;;054B;;054B 057C;ARMENIAN SMALL LETTER RA;Ll;0;L;;;;;N;;;054C;;054C 057D;ARMENIAN SMALL LETTER SEH;Ll;0;L;;;;;N;;;054D;;054D 057E;ARMENIAN SMALL LETTER VEW;Ll;0;L;;;;;N;;;054E;;054E 057F;ARMENIAN SMALL LETTER TIWN;Ll;0;L;;;;;N;;;054F;;054F 0580;ARMENIAN SMALL LETTER REH;Ll;0;L;;;;;N;;;0550;;0550 0581;ARMENIAN SMALL LETTER CO;Ll;0;L;;;;;N;;;0551;;0551 0582;ARMENIAN SMALL LETTER YIWN;Ll;0;L;;;;;N;;;0552;;0552 0583;ARMENIAN SMALL LETTER PIWR;Ll;0;L;;;;;N;;;0553;;0553 0584;ARMENIAN SMALL LETTER KEH;Ll;0;L;;;;;N;;;0554;;0554 0585;ARMENIAN SMALL LETTER OH;Ll;0;L;;;;;N;;;0555;;0555 0586;ARMENIAN SMALL LETTER FEH;Ll;0;L;;;;;N;;;0556;;0556 0587;ARMENIAN SMALL LIGATURE ECH YIWN;Ll;0;L; 0565 0582;;;;N;;;;; 0589;ARMENIAN FULL STOP;Po;0;L;;;;;N;ARMENIAN PERIOD;;;; 058A;ARMENIAN HYPHEN;Pd;0;ON;;;;;N;;;;; 058D;RIGHT-FACING ARMENIAN ETERNITY SIGN;So;0;ON;;;;;N;;;;; 058E;LEFT-FACING ARMENIAN ETERNITY SIGN;So;0;ON;;;;;N;;;;; 058F;ARMENIAN DRAM SIGN;Sc;0;ET;;;;;N;;;;; 0591;HEBREW ACCENT ETNAHTA;Mn;220;NSM;;;;;N;;;;; 0592;HEBREW ACCENT SEGOL;Mn;230;NSM;;;;;N;;;;; 0593;HEBREW ACCENT SHALSHELET;Mn;230;NSM;;;;;N;;;;; 0594;HEBREW ACCENT ZAQEF QATAN;Mn;230;NSM;;;;;N;;;;; 0595;HEBREW ACCENT ZAQEF GADOL;Mn;230;NSM;;;;;N;;;;; 0596;HEBREW ACCENT TIPEHA;Mn;220;NSM;;;;;N;;;;; 0597;HEBREW ACCENT REVIA;Mn;230;NSM;;;;;N;;;;; 0598;HEBREW ACCENT ZARQA;Mn;230;NSM;;;;;N;;;;; 0599;HEBREW ACCENT PASHTA;Mn;230;NSM;;;;;N;;;;; 059A;HEBREW ACCENT YETIV;Mn;222;NSM;;;;;N;;;;; 059B;HEBREW ACCENT TEVIR;Mn;220;NSM;;;;;N;;;;; 059C;HEBREW ACCENT GERESH;Mn;230;NSM;;;;;N;;;;; 059D;HEBREW ACCENT GERESH MUQDAM;Mn;230;NSM;;;;;N;;;;; 059E;HEBREW ACCENT GERSHAYIM;Mn;230;NSM;;;;;N;;;;; 059F;HEBREW ACCENT QARNEY PARA;Mn;230;NSM;;;;;N;;;;; 05A0;HEBREW ACCENT TELISHA GEDOLA;Mn;230;NSM;;;;;N;;;;; 05A1;HEBREW ACCENT PAZER;Mn;230;NSM;;;;;N;;;;; 05A2;HEBREW ACCENT ATNAH HAFUKH;Mn;220;NSM;;;;;N;;;;; 05A3;HEBREW ACCENT MUNAH;Mn;220;NSM;;;;;N;;;;; 05A4;HEBREW ACCENT MAHAPAKH;Mn;220;NSM;;;;;N;;;;; 05A5;HEBREW ACCENT MERKHA;Mn;220;NSM;;;;;N;;;;; 05A6;HEBREW ACCENT MERKHA KEFULA;Mn;220;NSM;;;;;N;;;;; 05A7;HEBREW ACCENT DARGA;Mn;220;NSM;;;;;N;;;;; 05A8;HEBREW ACCENT QADMA;Mn;230;NSM;;;;;N;;;;; 05A9;HEBREW ACCENT TELISHA QETANA;Mn;230;NSM;;;;;N;;;;; 05AA;HEBREW ACCENT YERAH BEN YOMO;Mn;220;NSM;;;;;N;;;;; 05AB;HEBREW ACCENT OLE;Mn;230;NSM;;;;;N;;;;; 05AC;HEBREW ACCENT ILUY;Mn;230;NSM;;;;;N;;;;; 05AD;HEBREW ACCENT DEHI;Mn;222;NSM;;;;;N;;;;; 05AE;HEBREW ACCENT ZINOR;Mn;228;NSM;;;;;N;;;;; 05AF;HEBREW MARK MASORA CIRCLE;Mn;230;NSM;;;;;N;;;;; 05B0;HEBREW POINT SHEVA;Mn;10;NSM;;;;;N;;;;; 05B1;HEBREW POINT HATAF SEGOL;Mn;11;NSM;;;;;N;;;;; 05B2;HEBREW POINT HATAF PATAH;Mn;12;NSM;;;;;N;;;;; 05B3;HEBREW POINT HATAF QAMATS;Mn;13;NSM;;;;;N;;;;; 05B4;HEBREW POINT HIRIQ;Mn;14;NSM;;;;;N;;;;; 05B5;HEBREW POINT TSERE;Mn;15;NSM;;;;;N;;;;; 05B6;HEBREW POINT SEGOL;Mn;16;NSM;;;;;N;;;;; 05B7;HEBREW POINT PATAH;Mn;17;NSM;;;;;N;;;;; 05B8;HEBREW POINT QAMATS;Mn;18;NSM;;;;;N;;;;; 05B9;HEBREW POINT HOLAM;Mn;19;NSM;;;;;N;;;;; 05BA;HEBREW POINT HOLAM HASER FOR VAV;Mn;19;NSM;;;;;N;;;;; 05BB;HEBREW POINT QUBUTS;Mn;20;NSM;;;;;N;;;;; 05BC;HEBREW POINT DAGESH OR MAPIQ;Mn;21;NSM;;;;;N;HEBREW POINT DAGESH;;;; 05BD;HEBREW POINT METEG;Mn;22;NSM;;;;;N;;;;; 05BE;HEBREW PUNCTUATION MAQAF;Pd;0;R;;;;;N;;;;; 05BF;HEBREW POINT RAFE;Mn;23;NSM;;;;;N;;;;; 05C0;HEBREW PUNCTUATION PASEQ;Po;0;R;;;;;N;HEBREW POINT PASEQ;;;; 05C1;HEBREW POINT SHIN DOT;Mn;24;NSM;;;;;N;;;;; 05C2;HEBREW POINT SIN DOT;Mn;25;NSM;;;;;N;;;;; 05C3;HEBREW PUNCTUATION SOF PASUQ;Po;0;R;;;;;N;;;;; 05C4;HEBREW MARK UPPER DOT;Mn;230;NSM;;;;;N;;;;; 05C5;HEBREW MARK LOWER DOT;Mn;220;NSM;;;;;N;;;;; 05C6;HEBREW PUNCTUATION NUN HAFUKHA;Po;0;R;;;;;N;;;;; 05C7;HEBREW POINT QAMATS QATAN;Mn;18;NSM;;;;;N;;;;; 05D0;HEBREW LETTER ALEF;Lo;0;R;;;;;N;;;;; 05D1;HEBREW LETTER BET;Lo;0;R;;;;;N;;;;; 05D2;HEBREW LETTER GIMEL;Lo;0;R;;;;;N;;;;; 05D3;HEBREW LETTER DALET;Lo;0;R;;;;;N;;;;; 05D4;HEBREW LETTER HE;Lo;0;R;;;;;N;;;;; 05D5;HEBREW LETTER VAV;Lo;0;R;;;;;N;;;;; 05D6;HEBREW LETTER ZAYIN;Lo;0;R;;;;;N;;;;; 05D7;HEBREW LETTER HET;Lo;0;R;;;;;N;;;;; 05D8;HEBREW LETTER TET;Lo;0;R;;;;;N;;;;; 05D9;HEBREW LETTER YOD;Lo;0;R;;;;;N;;;;; 05DA;HEBREW LETTER FINAL KAF;Lo;0;R;;;;;N;;;;; 05DB;HEBREW LETTER KAF;Lo;0;R;;;;;N;;;;; 05DC;HEBREW LETTER LAMED;Lo;0;R;;;;;N;;;;; 05DD;HEBREW LETTER FINAL MEM;Lo;0;R;;;;;N;;;;; 05DE;HEBREW LETTER MEM;Lo;0;R;;;;;N;;;;; 05DF;HEBREW LETTER FINAL NUN;Lo;0;R;;;;;N;;;;; 05E0;HEBREW LETTER NUN;Lo;0;R;;;;;N;;;;; 05E1;HEBREW LETTER SAMEKH;Lo;0;R;;;;;N;;;;; 05E2;HEBREW LETTER AYIN;Lo;0;R;;;;;N;;;;; 05E3;HEBREW LETTER FINAL PE;Lo;0;R;;;;;N;;;;; 05E4;HEBREW LETTER PE;Lo;0;R;;;;;N;;;;; 05E5;HEBREW LETTER FINAL TSADI;Lo;0;R;;;;;N;;;;; 05E6;HEBREW LETTER TSADI;Lo;0;R;;;;;N;;;;; 05E7;HEBREW LETTER QOF;Lo;0;R;;;;;N;;;;; 05E8;HEBREW LETTER RESH;Lo;0;R;;;;;N;;;;; 05E9;HEBREW LETTER SHIN;Lo;0;R;;;;;N;;;;; 05EA;HEBREW LETTER TAV;Lo;0;R;;;;;N;;;;; 05F0;HEBREW LIGATURE YIDDISH DOUBLE VAV;Lo;0;R;;;;;N;HEBREW LETTER DOUBLE VAV;;;; 05F1;HEBREW LIGATURE YIDDISH VAV YOD;Lo;0;R;;;;;N;HEBREW LETTER VAV YOD;;;; 05F2;HEBREW LIGATURE YIDDISH DOUBLE YOD;Lo;0;R;;;;;N;HEBREW LETTER DOUBLE YOD;;;; 05F3;HEBREW PUNCTUATION GERESH;Po;0;R;;;;;N;;;;; 05F4;HEBREW PUNCTUATION GERSHAYIM;Po;0;R;;;;;N;;;;; 0600;ARABIC NUMBER SIGN;Cf;0;AN;;;;;N;;;;; 0601;ARABIC SIGN SANAH;Cf;0;AN;;;;;N;;;;; 0602;ARABIC FOOTNOTE MARKER;Cf;0;AN;;;;;N;;;;; 0603;ARABIC SIGN SAFHA;Cf;0;AN;;;;;N;;;;; 0604;ARABIC SIGN SAMVAT;Cf;0;AN;;;;;N;;;;; 0605;ARABIC NUMBER MARK ABOVE;Cf;0;AN;;;;;N;;;;; 0606;ARABIC-INDIC CUBE ROOT;Sm;0;ON;;;;;N;;;;; 0607;ARABIC-INDIC FOURTH ROOT;Sm;0;ON;;;;;N;;;;; 0608;ARABIC RAY;Sm;0;AL;;;;;N;;;;; 0609;ARABIC-INDIC PER MILLE SIGN;Po;0;ET;;;;;N;;;;; 060A;ARABIC-INDIC PER TEN THOUSAND SIGN;Po;0;ET;;;;;N;;;;; 060B;AFGHANI SIGN;Sc;0;AL;;;;;N;;;;; 060C;ARABIC COMMA;Po;0;CS;;;;;N;;;;; 060D;ARABIC DATE SEPARATOR;Po;0;AL;;;;;N;;;;; 060E;ARABIC POETIC VERSE SIGN;So;0;ON;;;;;N;;;;; 060F;ARABIC SIGN MISRA;So;0;ON;;;;;N;;;;; 0610;ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM;Mn;230;NSM;;;;;N;;;;; 0611;ARABIC SIGN ALAYHE ASSALLAM;Mn;230;NSM;;;;;N;;;;; 0612;ARABIC SIGN RAHMATULLAH ALAYHE;Mn;230;NSM;;;;;N;;;;; 0613;ARABIC SIGN RADI ALLAHOU ANHU;Mn;230;NSM;;;;;N;;;;; 0614;ARABIC SIGN TAKHALLUS;Mn;230;NSM;;;;;N;;;;; 0615;ARABIC SMALL HIGH TAH;Mn;230;NSM;;;;;N;;;;; 0616;ARABIC SMALL HIGH LIGATURE ALEF WITH LAM WITH YEH;Mn;230;NSM;;;;;N;;;;; 0617;ARABIC SMALL HIGH ZAIN;Mn;230;NSM;;;;;N;;;;; 0618;ARABIC SMALL FATHA;Mn;30;NSM;;;;;N;;;;; 0619;ARABIC SMALL DAMMA;Mn;31;NSM;;;;;N;;;;; 061A;ARABIC SMALL KASRA;Mn;32;NSM;;;;;N;;;;; 061B;ARABIC SEMICOLON;Po;0;AL;;;;;N;;;;; 061C;ARABIC LETTER MARK;Cf;0;AL;;;;;N;;;;; 061E;ARABIC TRIPLE DOT PUNCTUATION MARK;Po;0;AL;;;;;N;;;;; 061F;ARABIC QUESTION MARK;Po;0;AL;;;;;N;;;;; 0620;ARABIC LETTER KASHMIRI YEH;Lo;0;AL;;;;;N;;;;; 0621;ARABIC LETTER HAMZA;Lo;0;AL;;;;;N;ARABIC LETTER HAMZAH;;;; 0622;ARABIC LETTER ALEF WITH MADDA ABOVE;Lo;0;AL;0627 0653;;;;N;ARABIC LETTER MADDAH ON ALEF;;;; 0623;ARABIC LETTER ALEF WITH HAMZA ABOVE;Lo;0;AL;0627 0654;;;;N;ARABIC LETTER HAMZAH ON ALEF;;;; 0624;ARABIC LETTER WAW WITH HAMZA ABOVE;Lo;0;AL;0648 0654;;;;N;ARABIC LETTER HAMZAH ON WAW;;;; 0625;ARABIC LETTER ALEF WITH HAMZA BELOW;Lo;0;AL;0627 0655;;;;N;ARABIC LETTER HAMZAH UNDER ALEF;;;; 0626;ARABIC LETTER YEH WITH HAMZA ABOVE;Lo;0;AL;064A 0654;;;;N;ARABIC LETTER HAMZAH ON YA;;;; 0627;ARABIC LETTER ALEF;Lo;0;AL;;;;;N;;;;; 0628;ARABIC LETTER BEH;Lo;0;AL;;;;;N;ARABIC LETTER BAA;;;; 0629;ARABIC LETTER TEH MARBUTA;Lo;0;AL;;;;;N;ARABIC LETTER TAA MARBUTAH;;;; 062A;ARABIC LETTER TEH;Lo;0;AL;;;;;N;ARABIC LETTER TAA;;;; 062B;ARABIC LETTER THEH;Lo;0;AL;;;;;N;ARABIC LETTER THAA;;;; 062C;ARABIC LETTER JEEM;Lo;0;AL;;;;;N;;;;; 062D;ARABIC LETTER HAH;Lo;0;AL;;;;;N;ARABIC LETTER HAA;;;; 062E;ARABIC LETTER KHAH;Lo;0;AL;;;;;N;ARABIC LETTER KHAA;;;; 062F;ARABIC LETTER DAL;Lo;0;AL;;;;;N;;;;; 0630;ARABIC LETTER THAL;Lo;0;AL;;;;;N;;;;; 0631;ARABIC LETTER REH;Lo;0;AL;;;;;N;ARABIC LETTER RA;;;; 0632;ARABIC LETTER ZAIN;Lo;0;AL;;;;;N;;;;; 0633;ARABIC LETTER SEEN;Lo;0;AL;;;;;N;;;;; 0634;ARABIC LETTER SHEEN;Lo;0;AL;;;;;N;;;;; 0635;ARABIC LETTER SAD;Lo;0;AL;;;;;N;;;;; 0636;ARABIC LETTER DAD;Lo;0;AL;;;;;N;;;;; 0637;ARABIC LETTER TAH;Lo;0;AL;;;;;N;;;;; 0638;ARABIC LETTER ZAH;Lo;0;AL;;;;;N;ARABIC LETTER DHAH;;;; 0639;ARABIC LETTER AIN;Lo;0;AL;;;;;N;;;;; 063A;ARABIC LETTER GHAIN;Lo;0;AL;;;;;N;;;;; 063B;ARABIC LETTER KEHEH WITH TWO DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 063C;ARABIC LETTER KEHEH WITH THREE DOTS BELOW;Lo;0;AL;;;;;N;;;;; 063D;ARABIC LETTER FARSI YEH WITH INVERTED V;Lo;0;AL;;;;;N;;;;; 063E;ARABIC LETTER FARSI YEH WITH TWO DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 063F;ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 0640;ARABIC TATWEEL;Lm;0;AL;;;;;N;;;;; 0641;ARABIC LETTER FEH;Lo;0;AL;;;;;N;ARABIC LETTER FA;;;; 0642;ARABIC LETTER QAF;Lo;0;AL;;;;;N;;;;; 0643;ARABIC LETTER KAF;Lo;0;AL;;;;;N;ARABIC LETTER CAF;;;; 0644;ARABIC LETTER LAM;Lo;0;AL;;;;;N;;;;; 0645;ARABIC LETTER MEEM;Lo;0;AL;;;;;N;;;;; 0646;ARABIC LETTER NOON;Lo;0;AL;;;;;N;;;;; 0647;ARABIC LETTER HEH;Lo;0;AL;;;;;N;ARABIC LETTER HA;;;; 0648;ARABIC LETTER WAW;Lo;0;AL;;;;;N;;;;; 0649;ARABIC LETTER ALEF MAKSURA;Lo;0;AL;;;;;N;ARABIC LETTER ALEF MAQSURAH;;;; 064A;ARABIC LETTER YEH;Lo;0;AL;;;;;N;ARABIC LETTER YA;;;; 064B;ARABIC FATHATAN;Mn;27;NSM;;;;;N;;;;; 064C;ARABIC DAMMATAN;Mn;28;NSM;;;;;N;;;;; 064D;ARABIC KASRATAN;Mn;29;NSM;;;;;N;;;;; 064E;ARABIC FATHA;Mn;30;NSM;;;;;N;ARABIC FATHAH;;;; 064F;ARABIC DAMMA;Mn;31;NSM;;;;;N;ARABIC DAMMAH;;;; 0650;ARABIC KASRA;Mn;32;NSM;;;;;N;ARABIC KASRAH;;;; 0651;ARABIC SHADDA;Mn;33;NSM;;;;;N;ARABIC SHADDAH;;;; 0652;ARABIC SUKUN;Mn;34;NSM;;;;;N;;;;; 0653;ARABIC MADDAH ABOVE;Mn;230;NSM;;;;;N;;;;; 0654;ARABIC HAMZA ABOVE;Mn;230;NSM;;;;;N;;;;; 0655;ARABIC HAMZA BELOW;Mn;220;NSM;;;;;N;;;;; 0656;ARABIC SUBSCRIPT ALEF;Mn;220;NSM;;;;;N;;;;; 0657;ARABIC INVERTED DAMMA;Mn;230;NSM;;;;;N;;;;; 0658;ARABIC MARK NOON GHUNNA;Mn;230;NSM;;;;;N;;;;; 0659;ARABIC ZWARAKAY;Mn;230;NSM;;;;;N;;;;; 065A;ARABIC VOWEL SIGN SMALL V ABOVE;Mn;230;NSM;;;;;N;;;;; 065B;ARABIC VOWEL SIGN INVERTED SMALL V ABOVE;Mn;230;NSM;;;;;N;;;;; 065C;ARABIC VOWEL SIGN DOT BELOW;Mn;220;NSM;;;;;N;;;;; 065D;ARABIC REVERSED DAMMA;Mn;230;NSM;;;;;N;;;;; 065E;ARABIC FATHA WITH TWO DOTS;Mn;230;NSM;;;;;N;;;;; 065F;ARABIC WAVY HAMZA BELOW;Mn;220;NSM;;;;;N;;;;; 0660;ARABIC-INDIC DIGIT ZERO;Nd;0;AN;;0;0;0;N;;;;; 0661;ARABIC-INDIC DIGIT ONE;Nd;0;AN;;1;1;1;N;;;;; 0662;ARABIC-INDIC DIGIT TWO;Nd;0;AN;;2;2;2;N;;;;; 0663;ARABIC-INDIC DIGIT THREE;Nd;0;AN;;3;3;3;N;;;;; 0664;ARABIC-INDIC DIGIT FOUR;Nd;0;AN;;4;4;4;N;;;;; 0665;ARABIC-INDIC DIGIT FIVE;Nd;0;AN;;5;5;5;N;;;;; 0666;ARABIC-INDIC DIGIT SIX;Nd;0;AN;;6;6;6;N;;;;; 0667;ARABIC-INDIC DIGIT SEVEN;Nd;0;AN;;7;7;7;N;;;;; 0668;ARABIC-INDIC DIGIT EIGHT;Nd;0;AN;;8;8;8;N;;;;; 0669;ARABIC-INDIC DIGIT NINE;Nd;0;AN;;9;9;9;N;;;;; 066A;ARABIC PERCENT SIGN;Po;0;ET;;;;;N;;;;; 066B;ARABIC DECIMAL SEPARATOR;Po;0;AN;;;;;N;;;;; 066C;ARABIC THOUSANDS SEPARATOR;Po;0;AN;;;;;N;;;;; 066D;ARABIC FIVE POINTED STAR;Po;0;AL;;;;;N;;;;; 066E;ARABIC LETTER DOTLESS BEH;Lo;0;AL;;;;;N;;;;; 066F;ARABIC LETTER DOTLESS QAF;Lo;0;AL;;;;;N;;;;; 0670;ARABIC LETTER SUPERSCRIPT ALEF;Mn;35;NSM;;;;;N;ARABIC ALEF ABOVE;;;; 0671;ARABIC LETTER ALEF WASLA;Lo;0;AL;;;;;N;ARABIC LETTER HAMZAT WASL ON ALEF;;;; 0672;ARABIC LETTER ALEF WITH WAVY HAMZA ABOVE;Lo;0;AL;;;;;N;ARABIC LETTER WAVY HAMZAH ON ALEF;;;; 0673;ARABIC LETTER ALEF WITH WAVY HAMZA BELOW;Lo;0;AL;;;;;N;ARABIC LETTER WAVY HAMZAH UNDER ALEF;;;; 0674;ARABIC LETTER HIGH HAMZA;Lo;0;AL;;;;;N;ARABIC LETTER HIGH HAMZAH;;;; 0675;ARABIC LETTER HIGH HAMZA ALEF;Lo;0;AL; 0627 0674;;;;N;ARABIC LETTER HIGH HAMZAH ALEF;;;; 0676;ARABIC LETTER HIGH HAMZA WAW;Lo;0;AL; 0648 0674;;;;N;ARABIC LETTER HIGH HAMZAH WAW;;;; 0677;ARABIC LETTER U WITH HAMZA ABOVE;Lo;0;AL; 06C7 0674;;;;N;ARABIC LETTER HIGH HAMZAH WAW WITH DAMMAH;;;; 0678;ARABIC LETTER HIGH HAMZA YEH;Lo;0;AL; 064A 0674;;;;N;ARABIC LETTER HIGH HAMZAH YA;;;; 0679;ARABIC LETTER TTEH;Lo;0;AL;;;;;N;ARABIC LETTER TAA WITH SMALL TAH;;;; 067A;ARABIC LETTER TTEHEH;Lo;0;AL;;;;;N;ARABIC LETTER TAA WITH TWO DOTS VERTICAL ABOVE;;;; 067B;ARABIC LETTER BEEH;Lo;0;AL;;;;;N;ARABIC LETTER BAA WITH TWO DOTS VERTICAL BELOW;;;; 067C;ARABIC LETTER TEH WITH RING;Lo;0;AL;;;;;N;ARABIC LETTER TAA WITH RING;;;; 067D;ARABIC LETTER TEH WITH THREE DOTS ABOVE DOWNWARDS;Lo;0;AL;;;;;N;ARABIC LETTER TAA WITH THREE DOTS ABOVE DOWNWARD;;;; 067E;ARABIC LETTER PEH;Lo;0;AL;;;;;N;ARABIC LETTER TAA WITH THREE DOTS BELOW;;;; 067F;ARABIC LETTER TEHEH;Lo;0;AL;;;;;N;ARABIC LETTER TAA WITH FOUR DOTS ABOVE;;;; 0680;ARABIC LETTER BEHEH;Lo;0;AL;;;;;N;ARABIC LETTER BAA WITH FOUR DOTS BELOW;;;; 0681;ARABIC LETTER HAH WITH HAMZA ABOVE;Lo;0;AL;;;;;N;ARABIC LETTER HAMZAH ON HAA;;;; 0682;ARABIC LETTER HAH WITH TWO DOTS VERTICAL ABOVE;Lo;0;AL;;;;;N;ARABIC LETTER HAA WITH TWO DOTS VERTICAL ABOVE;;;; 0683;ARABIC LETTER NYEH;Lo;0;AL;;;;;N;ARABIC LETTER HAA WITH MIDDLE TWO DOTS;;;; 0684;ARABIC LETTER DYEH;Lo;0;AL;;;;;N;ARABIC LETTER HAA WITH MIDDLE TWO DOTS VERTICAL;;;; 0685;ARABIC LETTER HAH WITH THREE DOTS ABOVE;Lo;0;AL;;;;;N;ARABIC LETTER HAA WITH THREE DOTS ABOVE;;;; 0686;ARABIC LETTER TCHEH;Lo;0;AL;;;;;N;ARABIC LETTER HAA WITH MIDDLE THREE DOTS DOWNWARD;;;; 0687;ARABIC LETTER TCHEHEH;Lo;0;AL;;;;;N;ARABIC LETTER HAA WITH MIDDLE FOUR DOTS;;;; 0688;ARABIC LETTER DDAL;Lo;0;AL;;;;;N;ARABIC LETTER DAL WITH SMALL TAH;;;; 0689;ARABIC LETTER DAL WITH RING;Lo;0;AL;;;;;N;;;;; 068A;ARABIC LETTER DAL WITH DOT BELOW;Lo;0;AL;;;;;N;;;;; 068B;ARABIC LETTER DAL WITH DOT BELOW AND SMALL TAH;Lo;0;AL;;;;;N;;;;; 068C;ARABIC LETTER DAHAL;Lo;0;AL;;;;;N;ARABIC LETTER DAL WITH TWO DOTS ABOVE;;;; 068D;ARABIC LETTER DDAHAL;Lo;0;AL;;;;;N;ARABIC LETTER DAL WITH TWO DOTS BELOW;;;; 068E;ARABIC LETTER DUL;Lo;0;AL;;;;;N;ARABIC LETTER DAL WITH THREE DOTS ABOVE;;;; 068F;ARABIC LETTER DAL WITH THREE DOTS ABOVE DOWNWARDS;Lo;0;AL;;;;;N;ARABIC LETTER DAL WITH THREE DOTS ABOVE DOWNWARD;;;; 0690;ARABIC LETTER DAL WITH FOUR DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 0691;ARABIC LETTER RREH;Lo;0;AL;;;;;N;ARABIC LETTER RA WITH SMALL TAH;;;; 0692;ARABIC LETTER REH WITH SMALL V;Lo;0;AL;;;;;N;ARABIC LETTER RA WITH SMALL V;;;; 0693;ARABIC LETTER REH WITH RING;Lo;0;AL;;;;;N;ARABIC LETTER RA WITH RING;;;; 0694;ARABIC LETTER REH WITH DOT BELOW;Lo;0;AL;;;;;N;ARABIC LETTER RA WITH DOT BELOW;;;; 0695;ARABIC LETTER REH WITH SMALL V BELOW;Lo;0;AL;;;;;N;ARABIC LETTER RA WITH SMALL V BELOW;;;; 0696;ARABIC LETTER REH WITH DOT BELOW AND DOT ABOVE;Lo;0;AL;;;;;N;ARABIC LETTER RA WITH DOT BELOW AND DOT ABOVE;;;; 0697;ARABIC LETTER REH WITH TWO DOTS ABOVE;Lo;0;AL;;;;;N;ARABIC LETTER RA WITH TWO DOTS ABOVE;;;; 0698;ARABIC LETTER JEH;Lo;0;AL;;;;;N;ARABIC LETTER RA WITH THREE DOTS ABOVE;;;; 0699;ARABIC LETTER REH WITH FOUR DOTS ABOVE;Lo;0;AL;;;;;N;ARABIC LETTER RA WITH FOUR DOTS ABOVE;;;; 069A;ARABIC LETTER SEEN WITH DOT BELOW AND DOT ABOVE;Lo;0;AL;;;;;N;;;;; 069B;ARABIC LETTER SEEN WITH THREE DOTS BELOW;Lo;0;AL;;;;;N;;;;; 069C;ARABIC LETTER SEEN WITH THREE DOTS BELOW AND THREE DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 069D;ARABIC LETTER SAD WITH TWO DOTS BELOW;Lo;0;AL;;;;;N;;;;; 069E;ARABIC LETTER SAD WITH THREE DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 069F;ARABIC LETTER TAH WITH THREE DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 06A0;ARABIC LETTER AIN WITH THREE DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 06A1;ARABIC LETTER DOTLESS FEH;Lo;0;AL;;;;;N;ARABIC LETTER DOTLESS FA;;;; 06A2;ARABIC LETTER FEH WITH DOT MOVED BELOW;Lo;0;AL;;;;;N;ARABIC LETTER FA WITH DOT MOVED BELOW;;;; 06A3;ARABIC LETTER FEH WITH DOT BELOW;Lo;0;AL;;;;;N;ARABIC LETTER FA WITH DOT BELOW;;;; 06A4;ARABIC LETTER VEH;Lo;0;AL;;;;;N;ARABIC LETTER FA WITH THREE DOTS ABOVE;;;; 06A5;ARABIC LETTER FEH WITH THREE DOTS BELOW;Lo;0;AL;;;;;N;ARABIC LETTER FA WITH THREE DOTS BELOW;;;; 06A6;ARABIC LETTER PEHEH;Lo;0;AL;;;;;N;ARABIC LETTER FA WITH FOUR DOTS ABOVE;;;; 06A7;ARABIC LETTER QAF WITH DOT ABOVE;Lo;0;AL;;;;;N;;;;; 06A8;ARABIC LETTER QAF WITH THREE DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 06A9;ARABIC LETTER KEHEH;Lo;0;AL;;;;;N;ARABIC LETTER OPEN CAF;;;; 06AA;ARABIC LETTER SWASH KAF;Lo;0;AL;;;;;N;ARABIC LETTER SWASH CAF;;;; 06AB;ARABIC LETTER KAF WITH RING;Lo;0;AL;;;;;N;ARABIC LETTER CAF WITH RING;;;; 06AC;ARABIC LETTER KAF WITH DOT ABOVE;Lo;0;AL;;;;;N;ARABIC LETTER CAF WITH DOT ABOVE;;;; 06AD;ARABIC LETTER NG;Lo;0;AL;;;;;N;ARABIC LETTER CAF WITH THREE DOTS ABOVE;;;; 06AE;ARABIC LETTER KAF WITH THREE DOTS BELOW;Lo;0;AL;;;;;N;ARABIC LETTER CAF WITH THREE DOTS BELOW;;;; 06AF;ARABIC LETTER GAF;Lo;0;AL;;;;;N;;;;; 06B0;ARABIC LETTER GAF WITH RING;Lo;0;AL;;;;;N;;;;; 06B1;ARABIC LETTER NGOEH;Lo;0;AL;;;;;N;ARABIC LETTER GAF WITH TWO DOTS ABOVE;;;; 06B2;ARABIC LETTER GAF WITH TWO DOTS BELOW;Lo;0;AL;;;;;N;;;;; 06B3;ARABIC LETTER GUEH;Lo;0;AL;;;;;N;ARABIC LETTER GAF WITH TWO DOTS VERTICAL BELOW;;;; 06B4;ARABIC LETTER GAF WITH THREE DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 06B5;ARABIC LETTER LAM WITH SMALL V;Lo;0;AL;;;;;N;;;;; 06B6;ARABIC LETTER LAM WITH DOT ABOVE;Lo;0;AL;;;;;N;;;;; 06B7;ARABIC LETTER LAM WITH THREE DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 06B8;ARABIC LETTER LAM WITH THREE DOTS BELOW;Lo;0;AL;;;;;N;;;;; 06B9;ARABIC LETTER NOON WITH DOT BELOW;Lo;0;AL;;;;;N;;;;; 06BA;ARABIC LETTER NOON GHUNNA;Lo;0;AL;;;;;N;ARABIC LETTER DOTLESS NOON;;;; 06BB;ARABIC LETTER RNOON;Lo;0;AL;;;;;N;ARABIC LETTER DOTLESS NOON WITH SMALL TAH;;;; 06BC;ARABIC LETTER NOON WITH RING;Lo;0;AL;;;;;N;;;;; 06BD;ARABIC LETTER NOON WITH THREE DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 06BE;ARABIC LETTER HEH DOACHASHMEE;Lo;0;AL;;;;;N;ARABIC LETTER KNOTTED HA;;;; 06BF;ARABIC LETTER TCHEH WITH DOT ABOVE;Lo;0;AL;;;;;N;;;;; 06C0;ARABIC LETTER HEH WITH YEH ABOVE;Lo;0;AL;06D5 0654;;;;N;ARABIC LETTER HAMZAH ON HA;;;; 06C1;ARABIC LETTER HEH GOAL;Lo;0;AL;;;;;N;ARABIC LETTER HA GOAL;;;; 06C2;ARABIC LETTER HEH GOAL WITH HAMZA ABOVE;Lo;0;AL;06C1 0654;;;;N;ARABIC LETTER HAMZAH ON HA GOAL;;;; 06C3;ARABIC LETTER TEH MARBUTA GOAL;Lo;0;AL;;;;;N;ARABIC LETTER TAA MARBUTAH GOAL;;;; 06C4;ARABIC LETTER WAW WITH RING;Lo;0;AL;;;;;N;;;;; 06C5;ARABIC LETTER KIRGHIZ OE;Lo;0;AL;;;;;N;ARABIC LETTER WAW WITH BAR;;;; 06C6;ARABIC LETTER OE;Lo;0;AL;;;;;N;ARABIC LETTER WAW WITH SMALL V;;;; 06C7;ARABIC LETTER U;Lo;0;AL;;;;;N;ARABIC LETTER WAW WITH DAMMAH;;;; 06C8;ARABIC LETTER YU;Lo;0;AL;;;;;N;ARABIC LETTER WAW WITH ALEF ABOVE;;;; 06C9;ARABIC LETTER KIRGHIZ YU;Lo;0;AL;;;;;N;ARABIC LETTER WAW WITH INVERTED SMALL V;;;; 06CA;ARABIC LETTER WAW WITH TWO DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 06CB;ARABIC LETTER VE;Lo;0;AL;;;;;N;ARABIC LETTER WAW WITH THREE DOTS ABOVE;;;; 06CC;ARABIC LETTER FARSI YEH;Lo;0;AL;;;;;N;ARABIC LETTER DOTLESS YA;;;; 06CD;ARABIC LETTER YEH WITH TAIL;Lo;0;AL;;;;;N;ARABIC LETTER YA WITH TAIL;;;; 06CE;ARABIC LETTER YEH WITH SMALL V;Lo;0;AL;;;;;N;ARABIC LETTER YA WITH SMALL V;;;; 06CF;ARABIC LETTER WAW WITH DOT ABOVE;Lo;0;AL;;;;;N;;;;; 06D0;ARABIC LETTER E;Lo;0;AL;;;;;N;ARABIC LETTER YA WITH TWO DOTS VERTICAL BELOW;;;; 06D1;ARABIC LETTER YEH WITH THREE DOTS BELOW;Lo;0;AL;;;;;N;ARABIC LETTER YA WITH THREE DOTS BELOW;;;; 06D2;ARABIC LETTER YEH BARREE;Lo;0;AL;;;;;N;ARABIC LETTER YA BARREE;;;; 06D3;ARABIC LETTER YEH BARREE WITH HAMZA ABOVE;Lo;0;AL;06D2 0654;;;;N;ARABIC LETTER HAMZAH ON YA BARREE;;;; 06D4;ARABIC FULL STOP;Po;0;AL;;;;;N;ARABIC PERIOD;;;; 06D5;ARABIC LETTER AE;Lo;0;AL;;;;;N;;;;; 06D6;ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA;Mn;230;NSM;;;;;N;;;;; 06D7;ARABIC SMALL HIGH LIGATURE QAF WITH LAM WITH ALEF MAKSURA;Mn;230;NSM;;;;;N;;;;; 06D8;ARABIC SMALL HIGH MEEM INITIAL FORM;Mn;230;NSM;;;;;N;;;;; 06D9;ARABIC SMALL HIGH LAM ALEF;Mn;230;NSM;;;;;N;;;;; 06DA;ARABIC SMALL HIGH JEEM;Mn;230;NSM;;;;;N;;;;; 06DB;ARABIC SMALL HIGH THREE DOTS;Mn;230;NSM;;;;;N;;;;; 06DC;ARABIC SMALL HIGH SEEN;Mn;230;NSM;;;;;N;;;;; 06DD;ARABIC END OF AYAH;Cf;0;AN;;;;;N;;;;; 06DE;ARABIC START OF RUB EL HIZB;So;0;ON;;;;;N;;;;; 06DF;ARABIC SMALL HIGH ROUNDED ZERO;Mn;230;NSM;;;;;N;;;;; 06E0;ARABIC SMALL HIGH UPRIGHT RECTANGULAR ZERO;Mn;230;NSM;;;;;N;;;;; 06E1;ARABIC SMALL HIGH DOTLESS HEAD OF KHAH;Mn;230;NSM;;;;;N;;;;; 06E2;ARABIC SMALL HIGH MEEM ISOLATED FORM;Mn;230;NSM;;;;;N;;;;; 06E3;ARABIC SMALL LOW SEEN;Mn;220;NSM;;;;;N;;;;; 06E4;ARABIC SMALL HIGH MADDA;Mn;230;NSM;;;;;N;;;;; 06E5;ARABIC SMALL WAW;Lm;0;AL;;;;;N;;;;; 06E6;ARABIC SMALL YEH;Lm;0;AL;;;;;N;;;;; 06E7;ARABIC SMALL HIGH YEH;Mn;230;NSM;;;;;N;;;;; 06E8;ARABIC SMALL HIGH NOON;Mn;230;NSM;;;;;N;;;;; 06E9;ARABIC PLACE OF SAJDAH;So;0;ON;;;;;N;;;;; 06EA;ARABIC EMPTY CENTRE LOW STOP;Mn;220;NSM;;;;;N;;;;; 06EB;ARABIC EMPTY CENTRE HIGH STOP;Mn;230;NSM;;;;;N;;;;; 06EC;ARABIC ROUNDED HIGH STOP WITH FILLED CENTRE;Mn;230;NSM;;;;;N;;;;; 06ED;ARABIC SMALL LOW MEEM;Mn;220;NSM;;;;;N;;;;; 06EE;ARABIC LETTER DAL WITH INVERTED V;Lo;0;AL;;;;;N;;;;; 06EF;ARABIC LETTER REH WITH INVERTED V;Lo;0;AL;;;;;N;;;;; 06F0;EXTENDED ARABIC-INDIC DIGIT ZERO;Nd;0;EN;;0;0;0;N;EASTERN ARABIC-INDIC DIGIT ZERO;;;; 06F1;EXTENDED ARABIC-INDIC DIGIT ONE;Nd;0;EN;;1;1;1;N;EASTERN ARABIC-INDIC DIGIT ONE;;;; 06F2;EXTENDED ARABIC-INDIC DIGIT TWO;Nd;0;EN;;2;2;2;N;EASTERN ARABIC-INDIC DIGIT TWO;;;; 06F3;EXTENDED ARABIC-INDIC DIGIT THREE;Nd;0;EN;;3;3;3;N;EASTERN ARABIC-INDIC DIGIT THREE;;;; 06F4;EXTENDED ARABIC-INDIC DIGIT FOUR;Nd;0;EN;;4;4;4;N;EASTERN ARABIC-INDIC DIGIT FOUR;;;; 06F5;EXTENDED ARABIC-INDIC DIGIT FIVE;Nd;0;EN;;5;5;5;N;EASTERN ARABIC-INDIC DIGIT FIVE;;;; 06F6;EXTENDED ARABIC-INDIC DIGIT SIX;Nd;0;EN;;6;6;6;N;EASTERN ARABIC-INDIC DIGIT SIX;;;; 06F7;EXTENDED ARABIC-INDIC DIGIT SEVEN;Nd;0;EN;;7;7;7;N;EASTERN ARABIC-INDIC DIGIT SEVEN;;;; 06F8;EXTENDED ARABIC-INDIC DIGIT EIGHT;Nd;0;EN;;8;8;8;N;EASTERN ARABIC-INDIC DIGIT EIGHT;;;; 06F9;EXTENDED ARABIC-INDIC DIGIT NINE;Nd;0;EN;;9;9;9;N;EASTERN ARABIC-INDIC DIGIT NINE;;;; 06FA;ARABIC LETTER SHEEN WITH DOT BELOW;Lo;0;AL;;;;;N;;;;; 06FB;ARABIC LETTER DAD WITH DOT BELOW;Lo;0;AL;;;;;N;;;;; 06FC;ARABIC LETTER GHAIN WITH DOT BELOW;Lo;0;AL;;;;;N;;;;; 06FD;ARABIC SIGN SINDHI AMPERSAND;So;0;AL;;;;;N;;;;; 06FE;ARABIC SIGN SINDHI POSTPOSITION MEN;So;0;AL;;;;;N;;;;; 06FF;ARABIC LETTER HEH WITH INVERTED V;Lo;0;AL;;;;;N;;;;; 0700;SYRIAC END OF PARAGRAPH;Po;0;AL;;;;;N;;;;; 0701;SYRIAC SUPRALINEAR FULL STOP;Po;0;AL;;;;;N;;;;; 0702;SYRIAC SUBLINEAR FULL STOP;Po;0;AL;;;;;N;;;;; 0703;SYRIAC SUPRALINEAR COLON;Po;0;AL;;;;;N;;;;; 0704;SYRIAC SUBLINEAR COLON;Po;0;AL;;;;;N;;;;; 0705;SYRIAC HORIZONTAL COLON;Po;0;AL;;;;;N;;;;; 0706;SYRIAC COLON SKEWED LEFT;Po;0;AL;;;;;N;;;;; 0707;SYRIAC COLON SKEWED RIGHT;Po;0;AL;;;;;N;;;;; 0708;SYRIAC SUPRALINEAR COLON SKEWED LEFT;Po;0;AL;;;;;N;;;;; 0709;SYRIAC SUBLINEAR COLON SKEWED RIGHT;Po;0;AL;;;;;N;;;;; 070A;SYRIAC CONTRACTION;Po;0;AL;;;;;N;;;;; 070B;SYRIAC HARKLEAN OBELUS;Po;0;AL;;;;;N;;;;; 070C;SYRIAC HARKLEAN METOBELUS;Po;0;AL;;;;;N;;;;; 070D;SYRIAC HARKLEAN ASTERISCUS;Po;0;AL;;;;;N;;;;; 070F;SYRIAC ABBREVIATION MARK;Cf;0;AL;;;;;N;;;;; 0710;SYRIAC LETTER ALAPH;Lo;0;AL;;;;;N;;;;; 0711;SYRIAC LETTER SUPERSCRIPT ALAPH;Mn;36;NSM;;;;;N;;;;; 0712;SYRIAC LETTER BETH;Lo;0;AL;;;;;N;;;;; 0713;SYRIAC LETTER GAMAL;Lo;0;AL;;;;;N;;;;; 0714;SYRIAC LETTER GAMAL GARSHUNI;Lo;0;AL;;;;;N;;;;; 0715;SYRIAC LETTER DALATH;Lo;0;AL;;;;;N;;;;; 0716;SYRIAC LETTER DOTLESS DALATH RISH;Lo;0;AL;;;;;N;;;;; 0717;SYRIAC LETTER HE;Lo;0;AL;;;;;N;;;;; 0718;SYRIAC LETTER WAW;Lo;0;AL;;;;;N;;;;; 0719;SYRIAC LETTER ZAIN;Lo;0;AL;;;;;N;;;;; 071A;SYRIAC LETTER HETH;Lo;0;AL;;;;;N;;;;; 071B;SYRIAC LETTER TETH;Lo;0;AL;;;;;N;;;;; 071C;SYRIAC LETTER TETH GARSHUNI;Lo;0;AL;;;;;N;;;;; 071D;SYRIAC LETTER YUDH;Lo;0;AL;;;;;N;;;;; 071E;SYRIAC LETTER YUDH HE;Lo;0;AL;;;;;N;;;;; 071F;SYRIAC LETTER KAPH;Lo;0;AL;;;;;N;;;;; 0720;SYRIAC LETTER LAMADH;Lo;0;AL;;;;;N;;;;; 0721;SYRIAC LETTER MIM;Lo;0;AL;;;;;N;;;;; 0722;SYRIAC LETTER NUN;Lo;0;AL;;;;;N;;;;; 0723;SYRIAC LETTER SEMKATH;Lo;0;AL;;;;;N;;;;; 0724;SYRIAC LETTER FINAL SEMKATH;Lo;0;AL;;;;;N;;;;; 0725;SYRIAC LETTER E;Lo;0;AL;;;;;N;;;;; 0726;SYRIAC LETTER PE;Lo;0;AL;;;;;N;;;;; 0727;SYRIAC LETTER REVERSED PE;Lo;0;AL;;;;;N;;;;; 0728;SYRIAC LETTER SADHE;Lo;0;AL;;;;;N;;;;; 0729;SYRIAC LETTER QAPH;Lo;0;AL;;;;;N;;;;; 072A;SYRIAC LETTER RISH;Lo;0;AL;;;;;N;;;;; 072B;SYRIAC LETTER SHIN;Lo;0;AL;;;;;N;;;;; 072C;SYRIAC LETTER TAW;Lo;0;AL;;;;;N;;;;; 072D;SYRIAC LETTER PERSIAN BHETH;Lo;0;AL;;;;;N;;;;; 072E;SYRIAC LETTER PERSIAN GHAMAL;Lo;0;AL;;;;;N;;;;; 072F;SYRIAC LETTER PERSIAN DHALATH;Lo;0;AL;;;;;N;;;;; 0730;SYRIAC PTHAHA ABOVE;Mn;230;NSM;;;;;N;;;;; 0731;SYRIAC PTHAHA BELOW;Mn;220;NSM;;;;;N;;;;; 0732;SYRIAC PTHAHA DOTTED;Mn;230;NSM;;;;;N;;;;; 0733;SYRIAC ZQAPHA ABOVE;Mn;230;NSM;;;;;N;;;;; 0734;SYRIAC ZQAPHA BELOW;Mn;220;NSM;;;;;N;;;;; 0735;SYRIAC ZQAPHA DOTTED;Mn;230;NSM;;;;;N;;;;; 0736;SYRIAC RBASA ABOVE;Mn;230;NSM;;;;;N;;;;; 0737;SYRIAC RBASA BELOW;Mn;220;NSM;;;;;N;;;;; 0738;SYRIAC DOTTED ZLAMA HORIZONTAL;Mn;220;NSM;;;;;N;;;;; 0739;SYRIAC DOTTED ZLAMA ANGULAR;Mn;220;NSM;;;;;N;;;;; 073A;SYRIAC HBASA ABOVE;Mn;230;NSM;;;;;N;;;;; 073B;SYRIAC HBASA BELOW;Mn;220;NSM;;;;;N;;;;; 073C;SYRIAC HBASA-ESASA DOTTED;Mn;220;NSM;;;;;N;;;;; 073D;SYRIAC ESASA ABOVE;Mn;230;NSM;;;;;N;;;;; 073E;SYRIAC ESASA BELOW;Mn;220;NSM;;;;;N;;;;; 073F;SYRIAC RWAHA;Mn;230;NSM;;;;;N;;;;; 0740;SYRIAC FEMININE DOT;Mn;230;NSM;;;;;N;;;;; 0741;SYRIAC QUSHSHAYA;Mn;230;NSM;;;;;N;;;;; 0742;SYRIAC RUKKAKHA;Mn;220;NSM;;;;;N;;;;; 0743;SYRIAC TWO VERTICAL DOTS ABOVE;Mn;230;NSM;;;;;N;;;;; 0744;SYRIAC TWO VERTICAL DOTS BELOW;Mn;220;NSM;;;;;N;;;;; 0745;SYRIAC THREE DOTS ABOVE;Mn;230;NSM;;;;;N;;;;; 0746;SYRIAC THREE DOTS BELOW;Mn;220;NSM;;;;;N;;;;; 0747;SYRIAC OBLIQUE LINE ABOVE;Mn;230;NSM;;;;;N;;;;; 0748;SYRIAC OBLIQUE LINE BELOW;Mn;220;NSM;;;;;N;;;;; 0749;SYRIAC MUSIC;Mn;230;NSM;;;;;N;;;;; 074A;SYRIAC BARREKH;Mn;230;NSM;;;;;N;;;;; 074D;SYRIAC LETTER SOGDIAN ZHAIN;Lo;0;AL;;;;;N;;;;; 074E;SYRIAC LETTER SOGDIAN KHAPH;Lo;0;AL;;;;;N;;;;; 074F;SYRIAC LETTER SOGDIAN FE;Lo;0;AL;;;;;N;;;;; 0750;ARABIC LETTER BEH WITH THREE DOTS HORIZONTALLY BELOW;Lo;0;AL;;;;;N;;;;; 0751;ARABIC LETTER BEH WITH DOT BELOW AND THREE DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 0752;ARABIC LETTER BEH WITH THREE DOTS POINTING UPWARDS BELOW;Lo;0;AL;;;;;N;;;;; 0753;ARABIC LETTER BEH WITH THREE DOTS POINTING UPWARDS BELOW AND TWO DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 0754;ARABIC LETTER BEH WITH TWO DOTS BELOW AND DOT ABOVE;Lo;0;AL;;;;;N;;;;; 0755;ARABIC LETTER BEH WITH INVERTED SMALL V BELOW;Lo;0;AL;;;;;N;;;;; 0756;ARABIC LETTER BEH WITH SMALL V;Lo;0;AL;;;;;N;;;;; 0757;ARABIC LETTER HAH WITH TWO DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 0758;ARABIC LETTER HAH WITH THREE DOTS POINTING UPWARDS BELOW;Lo;0;AL;;;;;N;;;;; 0759;ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW AND SMALL TAH;Lo;0;AL;;;;;N;;;;; 075A;ARABIC LETTER DAL WITH INVERTED SMALL V BELOW;Lo;0;AL;;;;;N;;;;; 075B;ARABIC LETTER REH WITH STROKE;Lo;0;AL;;;;;N;;;;; 075C;ARABIC LETTER SEEN WITH FOUR DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 075D;ARABIC LETTER AIN WITH TWO DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 075E;ARABIC LETTER AIN WITH THREE DOTS POINTING DOWNWARDS ABOVE;Lo;0;AL;;;;;N;;;;; 075F;ARABIC LETTER AIN WITH TWO DOTS VERTICALLY ABOVE;Lo;0;AL;;;;;N;;;;; 0760;ARABIC LETTER FEH WITH TWO DOTS BELOW;Lo;0;AL;;;;;N;;;;; 0761;ARABIC LETTER FEH WITH THREE DOTS POINTING UPWARDS BELOW;Lo;0;AL;;;;;N;;;;; 0762;ARABIC LETTER KEHEH WITH DOT ABOVE;Lo;0;AL;;;;;N;;;;; 0763;ARABIC LETTER KEHEH WITH THREE DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 0764;ARABIC LETTER KEHEH WITH THREE DOTS POINTING UPWARDS BELOW;Lo;0;AL;;;;;N;;;;; 0765;ARABIC LETTER MEEM WITH DOT ABOVE;Lo;0;AL;;;;;N;;;;; 0766;ARABIC LETTER MEEM WITH DOT BELOW;Lo;0;AL;;;;;N;;;;; 0767;ARABIC LETTER NOON WITH TWO DOTS BELOW;Lo;0;AL;;;;;N;;;;; 0768;ARABIC LETTER NOON WITH SMALL TAH;Lo;0;AL;;;;;N;;;;; 0769;ARABIC LETTER NOON WITH SMALL V;Lo;0;AL;;;;;N;;;;; 076A;ARABIC LETTER LAM WITH BAR;Lo;0;AL;;;;;N;;;;; 076B;ARABIC LETTER REH WITH TWO DOTS VERTICALLY ABOVE;Lo;0;AL;;;;;N;;;;; 076C;ARABIC LETTER REH WITH HAMZA ABOVE;Lo;0;AL;;;;;N;;;;; 076D;ARABIC LETTER SEEN WITH TWO DOTS VERTICALLY ABOVE;Lo;0;AL;;;;;N;;;;; 076E;ARABIC LETTER HAH WITH SMALL ARABIC LETTER TAH BELOW;Lo;0;AL;;;;;N;;;;; 076F;ARABIC LETTER HAH WITH SMALL ARABIC LETTER TAH AND TWO DOTS;Lo;0;AL;;;;;N;;;;; 0770;ARABIC LETTER SEEN WITH SMALL ARABIC LETTER TAH AND TWO DOTS;Lo;0;AL;;;;;N;;;;; 0771;ARABIC LETTER REH WITH SMALL ARABIC LETTER TAH AND TWO DOTS;Lo;0;AL;;;;;N;;;;; 0772;ARABIC LETTER HAH WITH SMALL ARABIC LETTER TAH ABOVE;Lo;0;AL;;;;;N;;;;; 0773;ARABIC LETTER ALEF WITH EXTENDED ARABIC-INDIC DIGIT TWO ABOVE;Lo;0;AL;;;;;N;;;;; 0774;ARABIC LETTER ALEF WITH EXTENDED ARABIC-INDIC DIGIT THREE ABOVE;Lo;0;AL;;;;;N;;;;; 0775;ARABIC LETTER FARSI YEH WITH EXTENDED ARABIC-INDIC DIGIT TWO ABOVE;Lo;0;AL;;;;;N;;;;; 0776;ARABIC LETTER FARSI YEH WITH EXTENDED ARABIC-INDIC DIGIT THREE ABOVE;Lo;0;AL;;;;;N;;;;; 0777;ARABIC LETTER FARSI YEH WITH EXTENDED ARABIC-INDIC DIGIT FOUR BELOW;Lo;0;AL;;;;;N;;;;; 0778;ARABIC LETTER WAW WITH EXTENDED ARABIC-INDIC DIGIT TWO ABOVE;Lo;0;AL;;;;;N;;;;; 0779;ARABIC LETTER WAW WITH EXTENDED ARABIC-INDIC DIGIT THREE ABOVE;Lo;0;AL;;;;;N;;;;; 077A;ARABIC LETTER YEH BARREE WITH EXTENDED ARABIC-INDIC DIGIT TWO ABOVE;Lo;0;AL;;;;;N;;;;; 077B;ARABIC LETTER YEH BARREE WITH EXTENDED ARABIC-INDIC DIGIT THREE ABOVE;Lo;0;AL;;;;;N;;;;; 077C;ARABIC LETTER HAH WITH EXTENDED ARABIC-INDIC DIGIT FOUR BELOW;Lo;0;AL;;;;;N;;;;; 077D;ARABIC LETTER SEEN WITH EXTENDED ARABIC-INDIC DIGIT FOUR ABOVE;Lo;0;AL;;;;;N;;;;; 077E;ARABIC LETTER SEEN WITH INVERTED V;Lo;0;AL;;;;;N;;;;; 077F;ARABIC LETTER KAF WITH TWO DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 0780;THAANA LETTER HAA;Lo;0;AL;;;;;N;;;;; 0781;THAANA LETTER SHAVIYANI;Lo;0;AL;;;;;N;;;;; 0782;THAANA LETTER NOONU;Lo;0;AL;;;;;N;;;;; 0783;THAANA LETTER RAA;Lo;0;AL;;;;;N;;;;; 0784;THAANA LETTER BAA;Lo;0;AL;;;;;N;;;;; 0785;THAANA LETTER LHAVIYANI;Lo;0;AL;;;;;N;;;;; 0786;THAANA LETTER KAAFU;Lo;0;AL;;;;;N;;;;; 0787;THAANA LETTER ALIFU;Lo;0;AL;;;;;N;;;;; 0788;THAANA LETTER VAAVU;Lo;0;AL;;;;;N;;;;; 0789;THAANA LETTER MEEMU;Lo;0;AL;;;;;N;;;;; 078A;THAANA LETTER FAAFU;Lo;0;AL;;;;;N;;;;; 078B;THAANA LETTER DHAALU;Lo;0;AL;;;;;N;;;;; 078C;THAANA LETTER THAA;Lo;0;AL;;;;;N;;;;; 078D;THAANA LETTER LAAMU;Lo;0;AL;;;;;N;;;;; 078E;THAANA LETTER GAAFU;Lo;0;AL;;;;;N;;;;; 078F;THAANA LETTER GNAVIYANI;Lo;0;AL;;;;;N;;;;; 0790;THAANA LETTER SEENU;Lo;0;AL;;;;;N;;;;; 0791;THAANA LETTER DAVIYANI;Lo;0;AL;;;;;N;;;;; 0792;THAANA LETTER ZAVIYANI;Lo;0;AL;;;;;N;;;;; 0793;THAANA LETTER TAVIYANI;Lo;0;AL;;;;;N;;;;; 0794;THAANA LETTER YAA;Lo;0;AL;;;;;N;;;;; 0795;THAANA LETTER PAVIYANI;Lo;0;AL;;;;;N;;;;; 0796;THAANA LETTER JAVIYANI;Lo;0;AL;;;;;N;;;;; 0797;THAANA LETTER CHAVIYANI;Lo;0;AL;;;;;N;;;;; 0798;THAANA LETTER TTAA;Lo;0;AL;;;;;N;;;;; 0799;THAANA LETTER HHAA;Lo;0;AL;;;;;N;;;;; 079A;THAANA LETTER KHAA;Lo;0;AL;;;;;N;;;;; 079B;THAANA LETTER THAALU;Lo;0;AL;;;;;N;;;;; 079C;THAANA LETTER ZAA;Lo;0;AL;;;;;N;;;;; 079D;THAANA LETTER SHEENU;Lo;0;AL;;;;;N;;;;; 079E;THAANA LETTER SAADHU;Lo;0;AL;;;;;N;;;;; 079F;THAANA LETTER DAADHU;Lo;0;AL;;;;;N;;;;; 07A0;THAANA LETTER TO;Lo;0;AL;;;;;N;;;;; 07A1;THAANA LETTER ZO;Lo;0;AL;;;;;N;;;;; 07A2;THAANA LETTER AINU;Lo;0;AL;;;;;N;;;;; 07A3;THAANA LETTER GHAINU;Lo;0;AL;;;;;N;;;;; 07A4;THAANA LETTER QAAFU;Lo;0;AL;;;;;N;;;;; 07A5;THAANA LETTER WAAVU;Lo;0;AL;;;;;N;;;;; 07A6;THAANA ABAFILI;Mn;0;NSM;;;;;N;;;;; 07A7;THAANA AABAAFILI;Mn;0;NSM;;;;;N;;;;; 07A8;THAANA IBIFILI;Mn;0;NSM;;;;;N;;;;; 07A9;THAANA EEBEEFILI;Mn;0;NSM;;;;;N;;;;; 07AA;THAANA UBUFILI;Mn;0;NSM;;;;;N;;;;; 07AB;THAANA OOBOOFILI;Mn;0;NSM;;;;;N;;;;; 07AC;THAANA EBEFILI;Mn;0;NSM;;;;;N;;;;; 07AD;THAANA EYBEYFILI;Mn;0;NSM;;;;;N;;;;; 07AE;THAANA OBOFILI;Mn;0;NSM;;;;;N;;;;; 07AF;THAANA OABOAFILI;Mn;0;NSM;;;;;N;;;;; 07B0;THAANA SUKUN;Mn;0;NSM;;;;;N;;;;; 07B1;THAANA LETTER NAA;Lo;0;AL;;;;;N;;;;; 07C0;NKO DIGIT ZERO;Nd;0;R;;0;0;0;N;;;;; 07C1;NKO DIGIT ONE;Nd;0;R;;1;1;1;N;;;;; 07C2;NKO DIGIT TWO;Nd;0;R;;2;2;2;N;;;;; 07C3;NKO DIGIT THREE;Nd;0;R;;3;3;3;N;;;;; 07C4;NKO DIGIT FOUR;Nd;0;R;;4;4;4;N;;;;; 07C5;NKO DIGIT FIVE;Nd;0;R;;5;5;5;N;;;;; 07C6;NKO DIGIT SIX;Nd;0;R;;6;6;6;N;;;;; 07C7;NKO DIGIT SEVEN;Nd;0;R;;7;7;7;N;;;;; 07C8;NKO DIGIT EIGHT;Nd;0;R;;8;8;8;N;;;;; 07C9;NKO DIGIT NINE;Nd;0;R;;9;9;9;N;;;;; 07CA;NKO LETTER A;Lo;0;R;;;;;N;;;;; 07CB;NKO LETTER EE;Lo;0;R;;;;;N;;;;; 07CC;NKO LETTER I;Lo;0;R;;;;;N;;;;; 07CD;NKO LETTER E;Lo;0;R;;;;;N;;;;; 07CE;NKO LETTER U;Lo;0;R;;;;;N;;;;; 07CF;NKO LETTER OO;Lo;0;R;;;;;N;;;;; 07D0;NKO LETTER O;Lo;0;R;;;;;N;;;;; 07D1;NKO LETTER DAGBASINNA;Lo;0;R;;;;;N;;;;; 07D2;NKO LETTER N;Lo;0;R;;;;;N;;;;; 07D3;NKO LETTER BA;Lo;0;R;;;;;N;;;;; 07D4;NKO LETTER PA;Lo;0;R;;;;;N;;;;; 07D5;NKO LETTER TA;Lo;0;R;;;;;N;;;;; 07D6;NKO LETTER JA;Lo;0;R;;;;;N;;;;; 07D7;NKO LETTER CHA;Lo;0;R;;;;;N;;;;; 07D8;NKO LETTER DA;Lo;0;R;;;;;N;;;;; 07D9;NKO LETTER RA;Lo;0;R;;;;;N;;;;; 07DA;NKO LETTER RRA;Lo;0;R;;;;;N;;;;; 07DB;NKO LETTER SA;Lo;0;R;;;;;N;;;;; 07DC;NKO LETTER GBA;Lo;0;R;;;;;N;;;;; 07DD;NKO LETTER FA;Lo;0;R;;;;;N;;;;; 07DE;NKO LETTER KA;Lo;0;R;;;;;N;;;;; 07DF;NKO LETTER LA;Lo;0;R;;;;;N;;;;; 07E0;NKO LETTER NA WOLOSO;Lo;0;R;;;;;N;;;;; 07E1;NKO LETTER MA;Lo;0;R;;;;;N;;;;; 07E2;NKO LETTER NYA;Lo;0;R;;;;;N;;;;; 07E3;NKO LETTER NA;Lo;0;R;;;;;N;;;;; 07E4;NKO LETTER HA;Lo;0;R;;;;;N;;;;; 07E5;NKO LETTER WA;Lo;0;R;;;;;N;;;;; 07E6;NKO LETTER YA;Lo;0;R;;;;;N;;;;; 07E7;NKO LETTER NYA WOLOSO;Lo;0;R;;;;;N;;;;; 07E8;NKO LETTER JONA JA;Lo;0;R;;;;;N;;;;; 07E9;NKO LETTER JONA CHA;Lo;0;R;;;;;N;;;;; 07EA;NKO LETTER JONA RA;Lo;0;R;;;;;N;;;;; 07EB;NKO COMBINING SHORT HIGH TONE;Mn;230;NSM;;;;;N;;;;; 07EC;NKO COMBINING SHORT LOW TONE;Mn;230;NSM;;;;;N;;;;; 07ED;NKO COMBINING SHORT RISING TONE;Mn;230;NSM;;;;;N;;;;; 07EE;NKO COMBINING LONG DESCENDING TONE;Mn;230;NSM;;;;;N;;;;; 07EF;NKO COMBINING LONG HIGH TONE;Mn;230;NSM;;;;;N;;;;; 07F0;NKO COMBINING LONG LOW TONE;Mn;230;NSM;;;;;N;;;;; 07F1;NKO COMBINING LONG RISING TONE;Mn;230;NSM;;;;;N;;;;; 07F2;NKO COMBINING NASALIZATION MARK;Mn;220;NSM;;;;;N;;;;; 07F3;NKO COMBINING DOUBLE DOT ABOVE;Mn;230;NSM;;;;;N;;;;; 07F4;NKO HIGH TONE APOSTROPHE;Lm;0;R;;;;;N;;;;; 07F5;NKO LOW TONE APOSTROPHE;Lm;0;R;;;;;N;;;;; 07F6;NKO SYMBOL OO DENNEN;So;0;ON;;;;;N;;;;; 07F7;NKO SYMBOL GBAKURUNEN;Po;0;ON;;;;;N;;;;; 07F8;NKO COMMA;Po;0;ON;;;;;N;;;;; 07F9;NKO EXCLAMATION MARK;Po;0;ON;;;;;N;;;;; 07FA;NKO LAJANYALAN;Lm;0;R;;;;;N;;;;; 0800;SAMARITAN LETTER ALAF;Lo;0;R;;;;;N;;;;; 0801;SAMARITAN LETTER BIT;Lo;0;R;;;;;N;;;;; 0802;SAMARITAN LETTER GAMAN;Lo;0;R;;;;;N;;;;; 0803;SAMARITAN LETTER DALAT;Lo;0;R;;;;;N;;;;; 0804;SAMARITAN LETTER IY;Lo;0;R;;;;;N;;;;; 0805;SAMARITAN LETTER BAA;Lo;0;R;;;;;N;;;;; 0806;SAMARITAN LETTER ZEN;Lo;0;R;;;;;N;;;;; 0807;SAMARITAN LETTER IT;Lo;0;R;;;;;N;;;;; 0808;SAMARITAN LETTER TIT;Lo;0;R;;;;;N;;;;; 0809;SAMARITAN LETTER YUT;Lo;0;R;;;;;N;;;;; 080A;SAMARITAN LETTER KAAF;Lo;0;R;;;;;N;;;;; 080B;SAMARITAN LETTER LABAT;Lo;0;R;;;;;N;;;;; 080C;SAMARITAN LETTER MIM;Lo;0;R;;;;;N;;;;; 080D;SAMARITAN LETTER NUN;Lo;0;R;;;;;N;;;;; 080E;SAMARITAN LETTER SINGAAT;Lo;0;R;;;;;N;;;;; 080F;SAMARITAN LETTER IN;Lo;0;R;;;;;N;;;;; 0810;SAMARITAN LETTER FI;Lo;0;R;;;;;N;;;;; 0811;SAMARITAN LETTER TSAADIY;Lo;0;R;;;;;N;;;;; 0812;SAMARITAN LETTER QUF;Lo;0;R;;;;;N;;;;; 0813;SAMARITAN LETTER RISH;Lo;0;R;;;;;N;;;;; 0814;SAMARITAN LETTER SHAN;Lo;0;R;;;;;N;;;;; 0815;SAMARITAN LETTER TAAF;Lo;0;R;;;;;N;;;;; 0816;SAMARITAN MARK IN;Mn;230;NSM;;;;;N;;;;; 0817;SAMARITAN MARK IN-ALAF;Mn;230;NSM;;;;;N;;;;; 0818;SAMARITAN MARK OCCLUSION;Mn;230;NSM;;;;;N;;;;; 0819;SAMARITAN MARK DAGESH;Mn;230;NSM;;;;;N;;;;; 081A;SAMARITAN MODIFIER LETTER EPENTHETIC YUT;Lm;0;R;;;;;N;;;;; 081B;SAMARITAN MARK EPENTHETIC YUT;Mn;230;NSM;;;;;N;;;;; 081C;SAMARITAN VOWEL SIGN LONG E;Mn;230;NSM;;;;;N;;;;; 081D;SAMARITAN VOWEL SIGN E;Mn;230;NSM;;;;;N;;;;; 081E;SAMARITAN VOWEL SIGN OVERLONG AA;Mn;230;NSM;;;;;N;;;;; 081F;SAMARITAN VOWEL SIGN LONG AA;Mn;230;NSM;;;;;N;;;;; 0820;SAMARITAN VOWEL SIGN AA;Mn;230;NSM;;;;;N;;;;; 0821;SAMARITAN VOWEL SIGN OVERLONG A;Mn;230;NSM;;;;;N;;;;; 0822;SAMARITAN VOWEL SIGN LONG A;Mn;230;NSM;;;;;N;;;;; 0823;SAMARITAN VOWEL SIGN A;Mn;230;NSM;;;;;N;;;;; 0824;SAMARITAN MODIFIER LETTER SHORT A;Lm;0;R;;;;;N;;;;; 0825;SAMARITAN VOWEL SIGN SHORT A;Mn;230;NSM;;;;;N;;;;; 0826;SAMARITAN VOWEL SIGN LONG U;Mn;230;NSM;;;;;N;;;;; 0827;SAMARITAN VOWEL SIGN U;Mn;230;NSM;;;;;N;;;;; 0828;SAMARITAN MODIFIER LETTER I;Lm;0;R;;;;;N;;;;; 0829;SAMARITAN VOWEL SIGN LONG I;Mn;230;NSM;;;;;N;;;;; 082A;SAMARITAN VOWEL SIGN I;Mn;230;NSM;;;;;N;;;;; 082B;SAMARITAN VOWEL SIGN O;Mn;230;NSM;;;;;N;;;;; 082C;SAMARITAN VOWEL SIGN SUKUN;Mn;230;NSM;;;;;N;;;;; 082D;SAMARITAN MARK NEQUDAA;Mn;230;NSM;;;;;N;;;;; 0830;SAMARITAN PUNCTUATION NEQUDAA;Po;0;R;;;;;N;;;;; 0831;SAMARITAN PUNCTUATION AFSAAQ;Po;0;R;;;;;N;;;;; 0832;SAMARITAN PUNCTUATION ANGED;Po;0;R;;;;;N;;;;; 0833;SAMARITAN PUNCTUATION BAU;Po;0;R;;;;;N;;;;; 0834;SAMARITAN PUNCTUATION ATMAAU;Po;0;R;;;;;N;;;;; 0835;SAMARITAN PUNCTUATION SHIYYAALAA;Po;0;R;;;;;N;;;;; 0836;SAMARITAN ABBREVIATION MARK;Po;0;R;;;;;N;;;;; 0837;SAMARITAN PUNCTUATION MELODIC QITSA;Po;0;R;;;;;N;;;;; 0838;SAMARITAN PUNCTUATION ZIQAA;Po;0;R;;;;;N;;;;; 0839;SAMARITAN PUNCTUATION QITSA;Po;0;R;;;;;N;;;;; 083A;SAMARITAN PUNCTUATION ZAEF;Po;0;R;;;;;N;;;;; 083B;SAMARITAN PUNCTUATION TURU;Po;0;R;;;;;N;;;;; 083C;SAMARITAN PUNCTUATION ARKAANU;Po;0;R;;;;;N;;;;; 083D;SAMARITAN PUNCTUATION SOF MASHFAAT;Po;0;R;;;;;N;;;;; 083E;SAMARITAN PUNCTUATION ANNAAU;Po;0;R;;;;;N;;;;; 0840;MANDAIC LETTER HALQA;Lo;0;R;;;;;N;;;;; 0841;MANDAIC LETTER AB;Lo;0;R;;;;;N;;;;; 0842;MANDAIC LETTER AG;Lo;0;R;;;;;N;;;;; 0843;MANDAIC LETTER AD;Lo;0;R;;;;;N;;;;; 0844;MANDAIC LETTER AH;Lo;0;R;;;;;N;;;;; 0845;MANDAIC LETTER USHENNA;Lo;0;R;;;;;N;;;;; 0846;MANDAIC LETTER AZ;Lo;0;R;;;;;N;;;;; 0847;MANDAIC LETTER IT;Lo;0;R;;;;;N;;;;; 0848;MANDAIC LETTER ATT;Lo;0;R;;;;;N;;;;; 0849;MANDAIC LETTER AKSA;Lo;0;R;;;;;N;;;;; 084A;MANDAIC LETTER AK;Lo;0;R;;;;;N;;;;; 084B;MANDAIC LETTER AL;Lo;0;R;;;;;N;;;;; 084C;MANDAIC LETTER AM;Lo;0;R;;;;;N;;;;; 084D;MANDAIC LETTER AN;Lo;0;R;;;;;N;;;;; 084E;MANDAIC LETTER AS;Lo;0;R;;;;;N;;;;; 084F;MANDAIC LETTER IN;Lo;0;R;;;;;N;;;;; 0850;MANDAIC LETTER AP;Lo;0;R;;;;;N;;;;; 0851;MANDAIC LETTER ASZ;Lo;0;R;;;;;N;;;;; 0852;MANDAIC LETTER AQ;Lo;0;R;;;;;N;;;;; 0853;MANDAIC LETTER AR;Lo;0;R;;;;;N;;;;; 0854;MANDAIC LETTER ASH;Lo;0;R;;;;;N;;;;; 0855;MANDAIC LETTER AT;Lo;0;R;;;;;N;;;;; 0856;MANDAIC LETTER DUSHENNA;Lo;0;R;;;;;N;;;;; 0857;MANDAIC LETTER KAD;Lo;0;R;;;;;N;;;;; 0858;MANDAIC LETTER AIN;Lo;0;R;;;;;N;;;;; 0859;MANDAIC AFFRICATION MARK;Mn;220;NSM;;;;;N;;;;; 085A;MANDAIC VOCALIZATION MARK;Mn;220;NSM;;;;;N;;;;; 085B;MANDAIC GEMINATION MARK;Mn;220;NSM;;;;;N;;;;; 085E;MANDAIC PUNCTUATION;Po;0;R;;;;;N;;;;; 08A0;ARABIC LETTER BEH WITH SMALL V BELOW;Lo;0;AL;;;;;N;;;;; 08A1;ARABIC LETTER BEH WITH HAMZA ABOVE;Lo;0;AL;;;;;N;;;;; 08A2;ARABIC LETTER JEEM WITH TWO DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 08A3;ARABIC LETTER TAH WITH TWO DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 08A4;ARABIC LETTER FEH WITH DOT BELOW AND THREE DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 08A5;ARABIC LETTER QAF WITH DOT BELOW;Lo;0;AL;;;;;N;;;;; 08A6;ARABIC LETTER LAM WITH DOUBLE BAR;Lo;0;AL;;;;;N;;;;; 08A7;ARABIC LETTER MEEM WITH THREE DOTS ABOVE;Lo;0;AL;;;;;N;;;;; 08A8;ARABIC LETTER YEH WITH TWO DOTS BELOW AND HAMZA ABOVE;Lo;0;AL;;;;;N;;;;; 08A9;ARABIC LETTER YEH WITH TWO DOTS BELOW AND DOT ABOVE;Lo;0;AL;;;;;N;;;;; 08AA;ARABIC LETTER REH WITH LOOP;Lo;0;AL;;;;;N;;;;; 08AB;ARABIC LETTER WAW WITH DOT WITHIN;Lo;0;AL;;;;;N;;;;; 08AC;ARABIC LETTER ROHINGYA YEH;Lo;0;AL;;;;;N;;;;; 08AD;ARABIC LETTER LOW ALEF;Lo;0;AL;;;;;N;;;;; 08AE;ARABIC LETTER DAL WITH THREE DOTS BELOW;Lo;0;AL;;;;;N;;;;; 08AF;ARABIC LETTER SAD WITH THREE DOTS BELOW;Lo;0;AL;;;;;N;;;;; 08B0;ARABIC LETTER GAF WITH INVERTED STROKE;Lo;0;AL;;;;;N;;;;; 08B1;ARABIC LETTER STRAIGHT WAW;Lo;0;AL;;;;;N;;;;; 08B2;ARABIC LETTER ZAIN WITH INVERTED V ABOVE;Lo;0;AL;;;;;N;;;;; 08B3;ARABIC LETTER AIN WITH THREE DOTS BELOW;Lo;0;AL;;;;;N;;;;; 08B4;ARABIC LETTER KAF WITH DOT BELOW;Lo;0;AL;;;;;N;;;;; 08B6;ARABIC LETTER BEH WITH SMALL MEEM ABOVE;Lo;0;AL;;;;;N;;;;; 08B7;ARABIC LETTER PEH WITH SMALL MEEM ABOVE;Lo;0;AL;;;;;N;;;;; 08B8;ARABIC LETTER TEH WITH SMALL TEH ABOVE;Lo;0;AL;;;;;N;;;;; 08B9;ARABIC LETTER REH WITH SMALL NOON ABOVE;Lo;0;AL;;;;;N;;;;; 08BA;ARABIC LETTER YEH WITH TWO DOTS BELOW AND SMALL NOON ABOVE;Lo;0;AL;;;;;N;;;;; 08BB;ARABIC LETTER AFRICAN FEH;Lo;0;AL;;;;;N;;;;; 08BC;ARABIC LETTER AFRICAN QAF;Lo;0;AL;;;;;N;;;;; 08BD;ARABIC LETTER AFRICAN NOON;Lo;0;AL;;;;;N;;;;; 08D4;ARABIC SMALL HIGH WORD AR-RUB;Mn;230;NSM;;;;;N;;;;; 08D5;ARABIC SMALL HIGH SAD;Mn;230;NSM;;;;;N;;;;; 08D6;ARABIC SMALL HIGH AIN;Mn;230;NSM;;;;;N;;;;; 08D7;ARABIC SMALL HIGH QAF;Mn;230;NSM;;;;;N;;;;; 08D8;ARABIC SMALL HIGH NOON WITH KASRA;Mn;230;NSM;;;;;N;;;;; 08D9;ARABIC SMALL LOW NOON WITH KASRA;Mn;230;NSM;;;;;N;;;;; 08DA;ARABIC SMALL HIGH WORD ATH-THALATHA;Mn;230;NSM;;;;;N;;;;; 08DB;ARABIC SMALL HIGH WORD AS-SAJDA;Mn;230;NSM;;;;;N;;;;; 08DC;ARABIC SMALL HIGH WORD AN-NISF;Mn;230;NSM;;;;;N;;;;; 08DD;ARABIC SMALL HIGH WORD SAKTA;Mn;230;NSM;;;;;N;;;;; 08DE;ARABIC SMALL HIGH WORD QIF;Mn;230;NSM;;;;;N;;;;; 08DF;ARABIC SMALL HIGH WORD WAQFA;Mn;230;NSM;;;;;N;;;;; 08E0;ARABIC SMALL HIGH FOOTNOTE MARKER;Mn;230;NSM;;;;;N;;;;; 08E1;ARABIC SMALL HIGH SIGN SAFHA;Mn;230;NSM;;;;;N;;;;; 08E2;ARABIC DISPUTED END OF AYAH;Cf;0;AN;;;;;N;;;;; 08E3;ARABIC TURNED DAMMA BELOW;Mn;220;NSM;;;;;N;;;;; 08E4;ARABIC CURLY FATHA;Mn;230;NSM;;;;;N;;;;; 08E5;ARABIC CURLY DAMMA;Mn;230;NSM;;;;;N;;;;; 08E6;ARABIC CURLY KASRA;Mn;220;NSM;;;;;N;;;;; 08E7;ARABIC CURLY FATHATAN;Mn;230;NSM;;;;;N;;;;; 08E8;ARABIC CURLY DAMMATAN;Mn;230;NSM;;;;;N;;;;; 08E9;ARABIC CURLY KASRATAN;Mn;220;NSM;;;;;N;;;;; 08EA;ARABIC TONE ONE DOT ABOVE;Mn;230;NSM;;;;;N;;;;; 08EB;ARABIC TONE TWO DOTS ABOVE;Mn;230;NSM;;;;;N;;;;; 08EC;ARABIC TONE LOOP ABOVE;Mn;230;NSM;;;;;N;;;;; 08ED;ARABIC TONE ONE DOT BELOW;Mn;220;NSM;;;;;N;;;;; 08EE;ARABIC TONE TWO DOTS BELOW;Mn;220;NSM;;;;;N;;;;; 08EF;ARABIC TONE LOOP BELOW;Mn;220;NSM;;;;;N;;;;; 08F0;ARABIC OPEN FATHATAN;Mn;27;NSM;;;;;N;;;;; 08F1;ARABIC OPEN DAMMATAN;Mn;28;NSM;;;;;N;;;;; 08F2;ARABIC OPEN KASRATAN;Mn;29;NSM;;;;;N;;;;; 08F3;ARABIC SMALL HIGH WAW;Mn;230;NSM;;;;;N;;;;; 08F4;ARABIC FATHA WITH RING;Mn;230;NSM;;;;;N;;;;; 08F5;ARABIC FATHA WITH DOT ABOVE;Mn;230;NSM;;;;;N;;;;; 08F6;ARABIC KASRA WITH DOT BELOW;Mn;220;NSM;;;;;N;;;;; 08F7;ARABIC LEFT ARROWHEAD ABOVE;Mn;230;NSM;;;;;N;;;;; 08F8;ARABIC RIGHT ARROWHEAD ABOVE;Mn;230;NSM;;;;;N;;;;; 08F9;ARABIC LEFT ARROWHEAD BELOW;Mn;220;NSM;;;;;N;;;;; 08FA;ARABIC RIGHT ARROWHEAD BELOW;Mn;220;NSM;;;;;N;;;;; 08FB;ARABIC DOUBLE RIGHT ARROWHEAD ABOVE;Mn;230;NSM;;;;;N;;;;; 08FC;ARABIC DOUBLE RIGHT ARROWHEAD ABOVE WITH DOT;Mn;230;NSM;;;;;N;;;;; 08FD;ARABIC RIGHT ARROWHEAD ABOVE WITH DOT;Mn;230;NSM;;;;;N;;;;; 08FE;ARABIC DAMMA WITH DOT;Mn;230;NSM;;;;;N;;;;; 08FF;ARABIC MARK SIDEWAYS NOON GHUNNA;Mn;230;NSM;;;;;N;;;;; 0900;DEVANAGARI SIGN INVERTED CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 0901;DEVANAGARI SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 0902;DEVANAGARI SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; 0903;DEVANAGARI SIGN VISARGA;Mc;0;L;;;;;N;;;;; 0904;DEVANAGARI LETTER SHORT A;Lo;0;L;;;;;N;;;;; 0905;DEVANAGARI LETTER A;Lo;0;L;;;;;N;;;;; 0906;DEVANAGARI LETTER AA;Lo;0;L;;;;;N;;;;; 0907;DEVANAGARI LETTER I;Lo;0;L;;;;;N;;;;; 0908;DEVANAGARI LETTER II;Lo;0;L;;;;;N;;;;; 0909;DEVANAGARI LETTER U;Lo;0;L;;;;;N;;;;; 090A;DEVANAGARI LETTER UU;Lo;0;L;;;;;N;;;;; 090B;DEVANAGARI LETTER VOCALIC R;Lo;0;L;;;;;N;;;;; 090C;DEVANAGARI LETTER VOCALIC L;Lo;0;L;;;;;N;;;;; 090D;DEVANAGARI LETTER CANDRA E;Lo;0;L;;;;;N;;;;; 090E;DEVANAGARI LETTER SHORT E;Lo;0;L;;;;;N;;;;; 090F;DEVANAGARI LETTER E;Lo;0;L;;;;;N;;;;; 0910;DEVANAGARI LETTER AI;Lo;0;L;;;;;N;;;;; 0911;DEVANAGARI LETTER CANDRA O;Lo;0;L;;;;;N;;;;; 0912;DEVANAGARI LETTER SHORT O;Lo;0;L;;;;;N;;;;; 0913;DEVANAGARI LETTER O;Lo;0;L;;;;;N;;;;; 0914;DEVANAGARI LETTER AU;Lo;0;L;;;;;N;;;;; 0915;DEVANAGARI LETTER KA;Lo;0;L;;;;;N;;;;; 0916;DEVANAGARI LETTER KHA;Lo;0;L;;;;;N;;;;; 0917;DEVANAGARI LETTER GA;Lo;0;L;;;;;N;;;;; 0918;DEVANAGARI LETTER GHA;Lo;0;L;;;;;N;;;;; 0919;DEVANAGARI LETTER NGA;Lo;0;L;;;;;N;;;;; 091A;DEVANAGARI LETTER CA;Lo;0;L;;;;;N;;;;; 091B;DEVANAGARI LETTER CHA;Lo;0;L;;;;;N;;;;; 091C;DEVANAGARI LETTER JA;Lo;0;L;;;;;N;;;;; 091D;DEVANAGARI LETTER JHA;Lo;0;L;;;;;N;;;;; 091E;DEVANAGARI LETTER NYA;Lo;0;L;;;;;N;;;;; 091F;DEVANAGARI LETTER TTA;Lo;0;L;;;;;N;;;;; 0920;DEVANAGARI LETTER TTHA;Lo;0;L;;;;;N;;;;; 0921;DEVANAGARI LETTER DDA;Lo;0;L;;;;;N;;;;; 0922;DEVANAGARI LETTER DDHA;Lo;0;L;;;;;N;;;;; 0923;DEVANAGARI LETTER NNA;Lo;0;L;;;;;N;;;;; 0924;DEVANAGARI LETTER TA;Lo;0;L;;;;;N;;;;; 0925;DEVANAGARI LETTER THA;Lo;0;L;;;;;N;;;;; 0926;DEVANAGARI LETTER DA;Lo;0;L;;;;;N;;;;; 0927;DEVANAGARI LETTER DHA;Lo;0;L;;;;;N;;;;; 0928;DEVANAGARI LETTER NA;Lo;0;L;;;;;N;;;;; 0929;DEVANAGARI LETTER NNNA;Lo;0;L;0928 093C;;;;N;;;;; 092A;DEVANAGARI LETTER PA;Lo;0;L;;;;;N;;;;; 092B;DEVANAGARI LETTER PHA;Lo;0;L;;;;;N;;;;; 092C;DEVANAGARI LETTER BA;Lo;0;L;;;;;N;;;;; 092D;DEVANAGARI LETTER BHA;Lo;0;L;;;;;N;;;;; 092E;DEVANAGARI LETTER MA;Lo;0;L;;;;;N;;;;; 092F;DEVANAGARI LETTER YA;Lo;0;L;;;;;N;;;;; 0930;DEVANAGARI LETTER RA;Lo;0;L;;;;;N;;;;; 0931;DEVANAGARI LETTER RRA;Lo;0;L;0930 093C;;;;N;;;;; 0932;DEVANAGARI LETTER LA;Lo;0;L;;;;;N;;;;; 0933;DEVANAGARI LETTER LLA;Lo;0;L;;;;;N;;;;; 0934;DEVANAGARI LETTER LLLA;Lo;0;L;0933 093C;;;;N;;;;; 0935;DEVANAGARI LETTER VA;Lo;0;L;;;;;N;;;;; 0936;DEVANAGARI LETTER SHA;Lo;0;L;;;;;N;;;;; 0937;DEVANAGARI LETTER SSA;Lo;0;L;;;;;N;;;;; 0938;DEVANAGARI LETTER SA;Lo;0;L;;;;;N;;;;; 0939;DEVANAGARI LETTER HA;Lo;0;L;;;;;N;;;;; 093A;DEVANAGARI VOWEL SIGN OE;Mn;0;NSM;;;;;N;;;;; 093B;DEVANAGARI VOWEL SIGN OOE;Mc;0;L;;;;;N;;;;; 093C;DEVANAGARI SIGN NUKTA;Mn;7;NSM;;;;;N;;;;; 093D;DEVANAGARI SIGN AVAGRAHA;Lo;0;L;;;;;N;;;;; 093E;DEVANAGARI VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 093F;DEVANAGARI VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 0940;DEVANAGARI VOWEL SIGN II;Mc;0;L;;;;;N;;;;; 0941;DEVANAGARI VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 0942;DEVANAGARI VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 0943;DEVANAGARI VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;; 0944;DEVANAGARI VOWEL SIGN VOCALIC RR;Mn;0;NSM;;;;;N;;;;; 0945;DEVANAGARI VOWEL SIGN CANDRA E;Mn;0;NSM;;;;;N;;;;; 0946;DEVANAGARI VOWEL SIGN SHORT E;Mn;0;NSM;;;;;N;;;;; 0947;DEVANAGARI VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;; 0948;DEVANAGARI VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;; 0949;DEVANAGARI VOWEL SIGN CANDRA O;Mc;0;L;;;;;N;;;;; 094A;DEVANAGARI VOWEL SIGN SHORT O;Mc;0;L;;;;;N;;;;; 094B;DEVANAGARI VOWEL SIGN O;Mc;0;L;;;;;N;;;;; 094C;DEVANAGARI VOWEL SIGN AU;Mc;0;L;;;;;N;;;;; 094D;DEVANAGARI SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 094E;DEVANAGARI VOWEL SIGN PRISHTHAMATRA E;Mc;0;L;;;;;N;;;;; 094F;DEVANAGARI VOWEL SIGN AW;Mc;0;L;;;;;N;;;;; 0950;DEVANAGARI OM;Lo;0;L;;;;;N;;;;; 0951;DEVANAGARI STRESS SIGN UDATTA;Mn;230;NSM;;;;;N;;;;; 0952;DEVANAGARI STRESS SIGN ANUDATTA;Mn;220;NSM;;;;;N;;;;; 0953;DEVANAGARI GRAVE ACCENT;Mn;230;NSM;;;;;N;;;;; 0954;DEVANAGARI ACUTE ACCENT;Mn;230;NSM;;;;;N;;;;; 0955;DEVANAGARI VOWEL SIGN CANDRA LONG E;Mn;0;NSM;;;;;N;;;;; 0956;DEVANAGARI VOWEL SIGN UE;Mn;0;NSM;;;;;N;;;;; 0957;DEVANAGARI VOWEL SIGN UUE;Mn;0;NSM;;;;;N;;;;; 0958;DEVANAGARI LETTER QA;Lo;0;L;0915 093C;;;;N;;;;; 0959;DEVANAGARI LETTER KHHA;Lo;0;L;0916 093C;;;;N;;;;; 095A;DEVANAGARI LETTER GHHA;Lo;0;L;0917 093C;;;;N;;;;; 095B;DEVANAGARI LETTER ZA;Lo;0;L;091C 093C;;;;N;;;;; 095C;DEVANAGARI LETTER DDDHA;Lo;0;L;0921 093C;;;;N;;;;; 095D;DEVANAGARI LETTER RHA;Lo;0;L;0922 093C;;;;N;;;;; 095E;DEVANAGARI LETTER FA;Lo;0;L;092B 093C;;;;N;;;;; 095F;DEVANAGARI LETTER YYA;Lo;0;L;092F 093C;;;;N;;;;; 0960;DEVANAGARI LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 0961;DEVANAGARI LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; 0962;DEVANAGARI VOWEL SIGN VOCALIC L;Mn;0;NSM;;;;;N;;;;; 0963;DEVANAGARI VOWEL SIGN VOCALIC LL;Mn;0;NSM;;;;;N;;;;; 0964;DEVANAGARI DANDA;Po;0;L;;;;;N;;;;; 0965;DEVANAGARI DOUBLE DANDA;Po;0;L;;;;;N;;;;; 0966;DEVANAGARI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 0967;DEVANAGARI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 0968;DEVANAGARI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 0969;DEVANAGARI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 096A;DEVANAGARI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 096B;DEVANAGARI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 096C;DEVANAGARI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 096D;DEVANAGARI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 096E;DEVANAGARI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 096F;DEVANAGARI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 0970;DEVANAGARI ABBREVIATION SIGN;Po;0;L;;;;;N;;;;; 0971;DEVANAGARI SIGN HIGH SPACING DOT;Lm;0;L;;;;;N;;;;; 0972;DEVANAGARI LETTER CANDRA A;Lo;0;L;;;;;N;;;;; 0973;DEVANAGARI LETTER OE;Lo;0;L;;;;;N;;;;; 0974;DEVANAGARI LETTER OOE;Lo;0;L;;;;;N;;;;; 0975;DEVANAGARI LETTER AW;Lo;0;L;;;;;N;;;;; 0976;DEVANAGARI LETTER UE;Lo;0;L;;;;;N;;;;; 0977;DEVANAGARI LETTER UUE;Lo;0;L;;;;;N;;;;; 0978;DEVANAGARI LETTER MARWARI DDA;Lo;0;L;;;;;N;;;;; 0979;DEVANAGARI LETTER ZHA;Lo;0;L;;;;;N;;;;; 097A;DEVANAGARI LETTER HEAVY YA;Lo;0;L;;;;;N;;;;; 097B;DEVANAGARI LETTER GGA;Lo;0;L;;;;;N;;;;; 097C;DEVANAGARI LETTER JJA;Lo;0;L;;;;;N;;;;; 097D;DEVANAGARI LETTER GLOTTAL STOP;Lo;0;L;;;;;N;;;;; 097E;DEVANAGARI LETTER DDDA;Lo;0;L;;;;;N;;;;; 097F;DEVANAGARI LETTER BBA;Lo;0;L;;;;;N;;;;; 0980;BENGALI ANJI;Lo;0;L;;;;;N;;;;; 0981;BENGALI SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 0982;BENGALI SIGN ANUSVARA;Mc;0;L;;;;;N;;;;; 0983;BENGALI SIGN VISARGA;Mc;0;L;;;;;N;;;;; 0985;BENGALI LETTER A;Lo;0;L;;;;;N;;;;; 0986;BENGALI LETTER AA;Lo;0;L;;;;;N;;;;; 0987;BENGALI LETTER I;Lo;0;L;;;;;N;;;;; 0988;BENGALI LETTER II;Lo;0;L;;;;;N;;;;; 0989;BENGALI LETTER U;Lo;0;L;;;;;N;;;;; 098A;BENGALI LETTER UU;Lo;0;L;;;;;N;;;;; 098B;BENGALI LETTER VOCALIC R;Lo;0;L;;;;;N;;;;; 098C;BENGALI LETTER VOCALIC L;Lo;0;L;;;;;N;;;;; 098F;BENGALI LETTER E;Lo;0;L;;;;;N;;;;; 0990;BENGALI LETTER AI;Lo;0;L;;;;;N;;;;; 0993;BENGALI LETTER O;Lo;0;L;;;;;N;;;;; 0994;BENGALI LETTER AU;Lo;0;L;;;;;N;;;;; 0995;BENGALI LETTER KA;Lo;0;L;;;;;N;;;;; 0996;BENGALI LETTER KHA;Lo;0;L;;;;;N;;;;; 0997;BENGALI LETTER GA;Lo;0;L;;;;;N;;;;; 0998;BENGALI LETTER GHA;Lo;0;L;;;;;N;;;;; 0999;BENGALI LETTER NGA;Lo;0;L;;;;;N;;;;; 099A;BENGALI LETTER CA;Lo;0;L;;;;;N;;;;; 099B;BENGALI LETTER CHA;Lo;0;L;;;;;N;;;;; 099C;BENGALI LETTER JA;Lo;0;L;;;;;N;;;;; 099D;BENGALI LETTER JHA;Lo;0;L;;;;;N;;;;; 099E;BENGALI LETTER NYA;Lo;0;L;;;;;N;;;;; 099F;BENGALI LETTER TTA;Lo;0;L;;;;;N;;;;; 09A0;BENGALI LETTER TTHA;Lo;0;L;;;;;N;;;;; 09A1;BENGALI LETTER DDA;Lo;0;L;;;;;N;;;;; 09A2;BENGALI LETTER DDHA;Lo;0;L;;;;;N;;;;; 09A3;BENGALI LETTER NNA;Lo;0;L;;;;;N;;;;; 09A4;BENGALI LETTER TA;Lo;0;L;;;;;N;;;;; 09A5;BENGALI LETTER THA;Lo;0;L;;;;;N;;;;; 09A6;BENGALI LETTER DA;Lo;0;L;;;;;N;;;;; 09A7;BENGALI LETTER DHA;Lo;0;L;;;;;N;;;;; 09A8;BENGALI LETTER NA;Lo;0;L;;;;;N;;;;; 09AA;BENGALI LETTER PA;Lo;0;L;;;;;N;;;;; 09AB;BENGALI LETTER PHA;Lo;0;L;;;;;N;;;;; 09AC;BENGALI LETTER BA;Lo;0;L;;;;;N;;;;; 09AD;BENGALI LETTER BHA;Lo;0;L;;;;;N;;;;; 09AE;BENGALI LETTER MA;Lo;0;L;;;;;N;;;;; 09AF;BENGALI LETTER YA;Lo;0;L;;;;;N;;;;; 09B0;BENGALI LETTER RA;Lo;0;L;;;;;N;;;;; 09B2;BENGALI LETTER LA;Lo;0;L;;;;;N;;;;; 09B6;BENGALI LETTER SHA;Lo;0;L;;;;;N;;;;; 09B7;BENGALI LETTER SSA;Lo;0;L;;;;;N;;;;; 09B8;BENGALI LETTER SA;Lo;0;L;;;;;N;;;;; 09B9;BENGALI LETTER HA;Lo;0;L;;;;;N;;;;; 09BC;BENGALI SIGN NUKTA;Mn;7;NSM;;;;;N;;;;; 09BD;BENGALI SIGN AVAGRAHA;Lo;0;L;;;;;N;;;;; 09BE;BENGALI VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 09BF;BENGALI VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 09C0;BENGALI VOWEL SIGN II;Mc;0;L;;;;;N;;;;; 09C1;BENGALI VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 09C2;BENGALI VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 09C3;BENGALI VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;; 09C4;BENGALI VOWEL SIGN VOCALIC RR;Mn;0;NSM;;;;;N;;;;; 09C7;BENGALI VOWEL SIGN E;Mc;0;L;;;;;N;;;;; 09C8;BENGALI VOWEL SIGN AI;Mc;0;L;;;;;N;;;;; 09CB;BENGALI VOWEL SIGN O;Mc;0;L;09C7 09BE;;;;N;;;;; 09CC;BENGALI VOWEL SIGN AU;Mc;0;L;09C7 09D7;;;;N;;;;; 09CD;BENGALI SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 09CE;BENGALI LETTER KHANDA TA;Lo;0;L;;;;;N;;;;; 09D7;BENGALI AU LENGTH MARK;Mc;0;L;;;;;N;;;;; 09DC;BENGALI LETTER RRA;Lo;0;L;09A1 09BC;;;;N;;;;; 09DD;BENGALI LETTER RHA;Lo;0;L;09A2 09BC;;;;N;;;;; 09DF;BENGALI LETTER YYA;Lo;0;L;09AF 09BC;;;;N;;;;; 09E0;BENGALI LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 09E1;BENGALI LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; 09E2;BENGALI VOWEL SIGN VOCALIC L;Mn;0;NSM;;;;;N;;;;; 09E3;BENGALI VOWEL SIGN VOCALIC LL;Mn;0;NSM;;;;;N;;;;; 09E6;BENGALI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 09E7;BENGALI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 09E8;BENGALI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 09E9;BENGALI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 09EA;BENGALI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 09EB;BENGALI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 09EC;BENGALI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 09ED;BENGALI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 09EE;BENGALI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 09EF;BENGALI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 09F0;BENGALI LETTER RA WITH MIDDLE DIAGONAL;Lo;0;L;;;;;N;;;;; 09F1;BENGALI LETTER RA WITH LOWER DIAGONAL;Lo;0;L;;;;;N;BENGALI LETTER VA WITH LOWER DIAGONAL;;;; 09F2;BENGALI RUPEE MARK;Sc;0;ET;;;;;N;;;;; 09F3;BENGALI RUPEE SIGN;Sc;0;ET;;;;;N;;;;; 09F4;BENGALI CURRENCY NUMERATOR ONE;No;0;L;;;;1/16;N;;;;; 09F5;BENGALI CURRENCY NUMERATOR TWO;No;0;L;;;;1/8;N;;;;; 09F6;BENGALI CURRENCY NUMERATOR THREE;No;0;L;;;;3/16;N;;;;; 09F7;BENGALI CURRENCY NUMERATOR FOUR;No;0;L;;;;1/4;N;;;;; 09F8;BENGALI CURRENCY NUMERATOR ONE LESS THAN THE DENOMINATOR;No;0;L;;;;3/4;N;;;;; 09F9;BENGALI CURRENCY DENOMINATOR SIXTEEN;No;0;L;;;;16;N;;;;; 09FA;BENGALI ISSHAR;So;0;L;;;;;N;;;;; 09FB;BENGALI GANDA MARK;Sc;0;ET;;;;;N;;;;; 0A01;GURMUKHI SIGN ADAK BINDI;Mn;0;NSM;;;;;N;;;;; 0A02;GURMUKHI SIGN BINDI;Mn;0;NSM;;;;;N;;;;; 0A03;GURMUKHI SIGN VISARGA;Mc;0;L;;;;;N;;;;; 0A05;GURMUKHI LETTER A;Lo;0;L;;;;;N;;;;; 0A06;GURMUKHI LETTER AA;Lo;0;L;;;;;N;;;;; 0A07;GURMUKHI LETTER I;Lo;0;L;;;;;N;;;;; 0A08;GURMUKHI LETTER II;Lo;0;L;;;;;N;;;;; 0A09;GURMUKHI LETTER U;Lo;0;L;;;;;N;;;;; 0A0A;GURMUKHI LETTER UU;Lo;0;L;;;;;N;;;;; 0A0F;GURMUKHI LETTER EE;Lo;0;L;;;;;N;;;;; 0A10;GURMUKHI LETTER AI;Lo;0;L;;;;;N;;;;; 0A13;GURMUKHI LETTER OO;Lo;0;L;;;;;N;;;;; 0A14;GURMUKHI LETTER AU;Lo;0;L;;;;;N;;;;; 0A15;GURMUKHI LETTER KA;Lo;0;L;;;;;N;;;;; 0A16;GURMUKHI LETTER KHA;Lo;0;L;;;;;N;;;;; 0A17;GURMUKHI LETTER GA;Lo;0;L;;;;;N;;;;; 0A18;GURMUKHI LETTER GHA;Lo;0;L;;;;;N;;;;; 0A19;GURMUKHI LETTER NGA;Lo;0;L;;;;;N;;;;; 0A1A;GURMUKHI LETTER CA;Lo;0;L;;;;;N;;;;; 0A1B;GURMUKHI LETTER CHA;Lo;0;L;;;;;N;;;;; 0A1C;GURMUKHI LETTER JA;Lo;0;L;;;;;N;;;;; 0A1D;GURMUKHI LETTER JHA;Lo;0;L;;;;;N;;;;; 0A1E;GURMUKHI LETTER NYA;Lo;0;L;;;;;N;;;;; 0A1F;GURMUKHI LETTER TTA;Lo;0;L;;;;;N;;;;; 0A20;GURMUKHI LETTER TTHA;Lo;0;L;;;;;N;;;;; 0A21;GURMUKHI LETTER DDA;Lo;0;L;;;;;N;;;;; 0A22;GURMUKHI LETTER DDHA;Lo;0;L;;;;;N;;;;; 0A23;GURMUKHI LETTER NNA;Lo;0;L;;;;;N;;;;; 0A24;GURMUKHI LETTER TA;Lo;0;L;;;;;N;;;;; 0A25;GURMUKHI LETTER THA;Lo;0;L;;;;;N;;;;; 0A26;GURMUKHI LETTER DA;Lo;0;L;;;;;N;;;;; 0A27;GURMUKHI LETTER DHA;Lo;0;L;;;;;N;;;;; 0A28;GURMUKHI LETTER NA;Lo;0;L;;;;;N;;;;; 0A2A;GURMUKHI LETTER PA;Lo;0;L;;;;;N;;;;; 0A2B;GURMUKHI LETTER PHA;Lo;0;L;;;;;N;;;;; 0A2C;GURMUKHI LETTER BA;Lo;0;L;;;;;N;;;;; 0A2D;GURMUKHI LETTER BHA;Lo;0;L;;;;;N;;;;; 0A2E;GURMUKHI LETTER MA;Lo;0;L;;;;;N;;;;; 0A2F;GURMUKHI LETTER YA;Lo;0;L;;;;;N;;;;; 0A30;GURMUKHI LETTER RA;Lo;0;L;;;;;N;;;;; 0A32;GURMUKHI LETTER LA;Lo;0;L;;;;;N;;;;; 0A33;GURMUKHI LETTER LLA;Lo;0;L;0A32 0A3C;;;;N;;;;; 0A35;GURMUKHI LETTER VA;Lo;0;L;;;;;N;;;;; 0A36;GURMUKHI LETTER SHA;Lo;0;L;0A38 0A3C;;;;N;;;;; 0A38;GURMUKHI LETTER SA;Lo;0;L;;;;;N;;;;; 0A39;GURMUKHI LETTER HA;Lo;0;L;;;;;N;;;;; 0A3C;GURMUKHI SIGN NUKTA;Mn;7;NSM;;;;;N;;;;; 0A3E;GURMUKHI VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 0A3F;GURMUKHI VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 0A40;GURMUKHI VOWEL SIGN II;Mc;0;L;;;;;N;;;;; 0A41;GURMUKHI VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 0A42;GURMUKHI VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 0A47;GURMUKHI VOWEL SIGN EE;Mn;0;NSM;;;;;N;;;;; 0A48;GURMUKHI VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;; 0A4B;GURMUKHI VOWEL SIGN OO;Mn;0;NSM;;;;;N;;;;; 0A4C;GURMUKHI VOWEL SIGN AU;Mn;0;NSM;;;;;N;;;;; 0A4D;GURMUKHI SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 0A51;GURMUKHI SIGN UDAAT;Mn;0;NSM;;;;;N;;;;; 0A59;GURMUKHI LETTER KHHA;Lo;0;L;0A16 0A3C;;;;N;;;;; 0A5A;GURMUKHI LETTER GHHA;Lo;0;L;0A17 0A3C;;;;N;;;;; 0A5B;GURMUKHI LETTER ZA;Lo;0;L;0A1C 0A3C;;;;N;;;;; 0A5C;GURMUKHI LETTER RRA;Lo;0;L;;;;;N;;;;; 0A5E;GURMUKHI LETTER FA;Lo;0;L;0A2B 0A3C;;;;N;;;;; 0A66;GURMUKHI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 0A67;GURMUKHI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 0A68;GURMUKHI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 0A69;GURMUKHI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 0A6A;GURMUKHI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 0A6B;GURMUKHI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 0A6C;GURMUKHI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 0A6D;GURMUKHI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 0A6E;GURMUKHI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 0A6F;GURMUKHI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 0A70;GURMUKHI TIPPI;Mn;0;NSM;;;;;N;;;;; 0A71;GURMUKHI ADDAK;Mn;0;NSM;;;;;N;;;;; 0A72;GURMUKHI IRI;Lo;0;L;;;;;N;;;;; 0A73;GURMUKHI URA;Lo;0;L;;;;;N;;;;; 0A74;GURMUKHI EK ONKAR;Lo;0;L;;;;;N;;;;; 0A75;GURMUKHI SIGN YAKASH;Mn;0;NSM;;;;;N;;;;; 0A81;GUJARATI SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 0A82;GUJARATI SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; 0A83;GUJARATI SIGN VISARGA;Mc;0;L;;;;;N;;;;; 0A85;GUJARATI LETTER A;Lo;0;L;;;;;N;;;;; 0A86;GUJARATI LETTER AA;Lo;0;L;;;;;N;;;;; 0A87;GUJARATI LETTER I;Lo;0;L;;;;;N;;;;; 0A88;GUJARATI LETTER II;Lo;0;L;;;;;N;;;;; 0A89;GUJARATI LETTER U;Lo;0;L;;;;;N;;;;; 0A8A;GUJARATI LETTER UU;Lo;0;L;;;;;N;;;;; 0A8B;GUJARATI LETTER VOCALIC R;Lo;0;L;;;;;N;;;;; 0A8C;GUJARATI LETTER VOCALIC L;Lo;0;L;;;;;N;;;;; 0A8D;GUJARATI VOWEL CANDRA E;Lo;0;L;;;;;N;;;;; 0A8F;GUJARATI LETTER E;Lo;0;L;;;;;N;;;;; 0A90;GUJARATI LETTER AI;Lo;0;L;;;;;N;;;;; 0A91;GUJARATI VOWEL CANDRA O;Lo;0;L;;;;;N;;;;; 0A93;GUJARATI LETTER O;Lo;0;L;;;;;N;;;;; 0A94;GUJARATI LETTER AU;Lo;0;L;;;;;N;;;;; 0A95;GUJARATI LETTER KA;Lo;0;L;;;;;N;;;;; 0A96;GUJARATI LETTER KHA;Lo;0;L;;;;;N;;;;; 0A97;GUJARATI LETTER GA;Lo;0;L;;;;;N;;;;; 0A98;GUJARATI LETTER GHA;Lo;0;L;;;;;N;;;;; 0A99;GUJARATI LETTER NGA;Lo;0;L;;;;;N;;;;; 0A9A;GUJARATI LETTER CA;Lo;0;L;;;;;N;;;;; 0A9B;GUJARATI LETTER CHA;Lo;0;L;;;;;N;;;;; 0A9C;GUJARATI LETTER JA;Lo;0;L;;;;;N;;;;; 0A9D;GUJARATI LETTER JHA;Lo;0;L;;;;;N;;;;; 0A9E;GUJARATI LETTER NYA;Lo;0;L;;;;;N;;;;; 0A9F;GUJARATI LETTER TTA;Lo;0;L;;;;;N;;;;; 0AA0;GUJARATI LETTER TTHA;Lo;0;L;;;;;N;;;;; 0AA1;GUJARATI LETTER DDA;Lo;0;L;;;;;N;;;;; 0AA2;GUJARATI LETTER DDHA;Lo;0;L;;;;;N;;;;; 0AA3;GUJARATI LETTER NNA;Lo;0;L;;;;;N;;;;; 0AA4;GUJARATI LETTER TA;Lo;0;L;;;;;N;;;;; 0AA5;GUJARATI LETTER THA;Lo;0;L;;;;;N;;;;; 0AA6;GUJARATI LETTER DA;Lo;0;L;;;;;N;;;;; 0AA7;GUJARATI LETTER DHA;Lo;0;L;;;;;N;;;;; 0AA8;GUJARATI LETTER NA;Lo;0;L;;;;;N;;;;; 0AAA;GUJARATI LETTER PA;Lo;0;L;;;;;N;;;;; 0AAB;GUJARATI LETTER PHA;Lo;0;L;;;;;N;;;;; 0AAC;GUJARATI LETTER BA;Lo;0;L;;;;;N;;;;; 0AAD;GUJARATI LETTER BHA;Lo;0;L;;;;;N;;;;; 0AAE;GUJARATI LETTER MA;Lo;0;L;;;;;N;;;;; 0AAF;GUJARATI LETTER YA;Lo;0;L;;;;;N;;;;; 0AB0;GUJARATI LETTER RA;Lo;0;L;;;;;N;;;;; 0AB2;GUJARATI LETTER LA;Lo;0;L;;;;;N;;;;; 0AB3;GUJARATI LETTER LLA;Lo;0;L;;;;;N;;;;; 0AB5;GUJARATI LETTER VA;Lo;0;L;;;;;N;;;;; 0AB6;GUJARATI LETTER SHA;Lo;0;L;;;;;N;;;;; 0AB7;GUJARATI LETTER SSA;Lo;0;L;;;;;N;;;;; 0AB8;GUJARATI LETTER SA;Lo;0;L;;;;;N;;;;; 0AB9;GUJARATI LETTER HA;Lo;0;L;;;;;N;;;;; 0ABC;GUJARATI SIGN NUKTA;Mn;7;NSM;;;;;N;;;;; 0ABD;GUJARATI SIGN AVAGRAHA;Lo;0;L;;;;;N;;;;; 0ABE;GUJARATI VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 0ABF;GUJARATI VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 0AC0;GUJARATI VOWEL SIGN II;Mc;0;L;;;;;N;;;;; 0AC1;GUJARATI VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 0AC2;GUJARATI VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 0AC3;GUJARATI VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;; 0AC4;GUJARATI VOWEL SIGN VOCALIC RR;Mn;0;NSM;;;;;N;;;;; 0AC5;GUJARATI VOWEL SIGN CANDRA E;Mn;0;NSM;;;;;N;;;;; 0AC7;GUJARATI VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;; 0AC8;GUJARATI VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;; 0AC9;GUJARATI VOWEL SIGN CANDRA O;Mc;0;L;;;;;N;;;;; 0ACB;GUJARATI VOWEL SIGN O;Mc;0;L;;;;;N;;;;; 0ACC;GUJARATI VOWEL SIGN AU;Mc;0;L;;;;;N;;;;; 0ACD;GUJARATI SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 0AD0;GUJARATI OM;Lo;0;L;;;;;N;;;;; 0AE0;GUJARATI LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 0AE1;GUJARATI LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; 0AE2;GUJARATI VOWEL SIGN VOCALIC L;Mn;0;NSM;;;;;N;;;;; 0AE3;GUJARATI VOWEL SIGN VOCALIC LL;Mn;0;NSM;;;;;N;;;;; 0AE6;GUJARATI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 0AE7;GUJARATI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 0AE8;GUJARATI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 0AE9;GUJARATI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 0AEA;GUJARATI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 0AEB;GUJARATI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 0AEC;GUJARATI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 0AED;GUJARATI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 0AEE;GUJARATI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 0AEF;GUJARATI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 0AF0;GUJARATI ABBREVIATION SIGN;Po;0;L;;;;;N;;;;; 0AF1;GUJARATI RUPEE SIGN;Sc;0;ET;;;;;N;;;;; 0AF9;GUJARATI LETTER ZHA;Lo;0;L;;;;;N;;;;; 0B01;ORIYA SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 0B02;ORIYA SIGN ANUSVARA;Mc;0;L;;;;;N;;;;; 0B03;ORIYA SIGN VISARGA;Mc;0;L;;;;;N;;;;; 0B05;ORIYA LETTER A;Lo;0;L;;;;;N;;;;; 0B06;ORIYA LETTER AA;Lo;0;L;;;;;N;;;;; 0B07;ORIYA LETTER I;Lo;0;L;;;;;N;;;;; 0B08;ORIYA LETTER II;Lo;0;L;;;;;N;;;;; 0B09;ORIYA LETTER U;Lo;0;L;;;;;N;;;;; 0B0A;ORIYA LETTER UU;Lo;0;L;;;;;N;;;;; 0B0B;ORIYA LETTER VOCALIC R;Lo;0;L;;;;;N;;;;; 0B0C;ORIYA LETTER VOCALIC L;Lo;0;L;;;;;N;;;;; 0B0F;ORIYA LETTER E;Lo;0;L;;;;;N;;;;; 0B10;ORIYA LETTER AI;Lo;0;L;;;;;N;;;;; 0B13;ORIYA LETTER O;Lo;0;L;;;;;N;;;;; 0B14;ORIYA LETTER AU;Lo;0;L;;;;;N;;;;; 0B15;ORIYA LETTER KA;Lo;0;L;;;;;N;;;;; 0B16;ORIYA LETTER KHA;Lo;0;L;;;;;N;;;;; 0B17;ORIYA LETTER GA;Lo;0;L;;;;;N;;;;; 0B18;ORIYA LETTER GHA;Lo;0;L;;;;;N;;;;; 0B19;ORIYA LETTER NGA;Lo;0;L;;;;;N;;;;; 0B1A;ORIYA LETTER CA;Lo;0;L;;;;;N;;;;; 0B1B;ORIYA LETTER CHA;Lo;0;L;;;;;N;;;;; 0B1C;ORIYA LETTER JA;Lo;0;L;;;;;N;;;;; 0B1D;ORIYA LETTER JHA;Lo;0;L;;;;;N;;;;; 0B1E;ORIYA LETTER NYA;Lo;0;L;;;;;N;;;;; 0B1F;ORIYA LETTER TTA;Lo;0;L;;;;;N;;;;; 0B20;ORIYA LETTER TTHA;Lo;0;L;;;;;N;;;;; 0B21;ORIYA LETTER DDA;Lo;0;L;;;;;N;;;;; 0B22;ORIYA LETTER DDHA;Lo;0;L;;;;;N;;;;; 0B23;ORIYA LETTER NNA;Lo;0;L;;;;;N;;;;; 0B24;ORIYA LETTER TA;Lo;0;L;;;;;N;;;;; 0B25;ORIYA LETTER THA;Lo;0;L;;;;;N;;;;; 0B26;ORIYA LETTER DA;Lo;0;L;;;;;N;;;;; 0B27;ORIYA LETTER DHA;Lo;0;L;;;;;N;;;;; 0B28;ORIYA LETTER NA;Lo;0;L;;;;;N;;;;; 0B2A;ORIYA LETTER PA;Lo;0;L;;;;;N;;;;; 0B2B;ORIYA LETTER PHA;Lo;0;L;;;;;N;;;;; 0B2C;ORIYA LETTER BA;Lo;0;L;;;;;N;;;;; 0B2D;ORIYA LETTER BHA;Lo;0;L;;;;;N;;;;; 0B2E;ORIYA LETTER MA;Lo;0;L;;;;;N;;;;; 0B2F;ORIYA LETTER YA;Lo;0;L;;;;;N;;;;; 0B30;ORIYA LETTER RA;Lo;0;L;;;;;N;;;;; 0B32;ORIYA LETTER LA;Lo;0;L;;;;;N;;;;; 0B33;ORIYA LETTER LLA;Lo;0;L;;;;;N;;;;; 0B35;ORIYA LETTER VA;Lo;0;L;;;;;N;;;;; 0B36;ORIYA LETTER SHA;Lo;0;L;;;;;N;;;;; 0B37;ORIYA LETTER SSA;Lo;0;L;;;;;N;;;;; 0B38;ORIYA LETTER SA;Lo;0;L;;;;;N;;;;; 0B39;ORIYA LETTER HA;Lo;0;L;;;;;N;;;;; 0B3C;ORIYA SIGN NUKTA;Mn;7;NSM;;;;;N;;;;; 0B3D;ORIYA SIGN AVAGRAHA;Lo;0;L;;;;;N;;;;; 0B3E;ORIYA VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 0B3F;ORIYA VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; 0B40;ORIYA VOWEL SIGN II;Mc;0;L;;;;;N;;;;; 0B41;ORIYA VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 0B42;ORIYA VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 0B43;ORIYA VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;; 0B44;ORIYA VOWEL SIGN VOCALIC RR;Mn;0;NSM;;;;;N;;;;; 0B47;ORIYA VOWEL SIGN E;Mc;0;L;;;;;N;;;;; 0B48;ORIYA VOWEL SIGN AI;Mc;0;L;0B47 0B56;;;;N;;;;; 0B4B;ORIYA VOWEL SIGN O;Mc;0;L;0B47 0B3E;;;;N;;;;; 0B4C;ORIYA VOWEL SIGN AU;Mc;0;L;0B47 0B57;;;;N;;;;; 0B4D;ORIYA SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 0B56;ORIYA AI LENGTH MARK;Mn;0;NSM;;;;;N;;;;; 0B57;ORIYA AU LENGTH MARK;Mc;0;L;;;;;N;;;;; 0B5C;ORIYA LETTER RRA;Lo;0;L;0B21 0B3C;;;;N;;;;; 0B5D;ORIYA LETTER RHA;Lo;0;L;0B22 0B3C;;;;N;;;;; 0B5F;ORIYA LETTER YYA;Lo;0;L;;;;;N;;;;; 0B60;ORIYA LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 0B61;ORIYA LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; 0B62;ORIYA VOWEL SIGN VOCALIC L;Mn;0;NSM;;;;;N;;;;; 0B63;ORIYA VOWEL SIGN VOCALIC LL;Mn;0;NSM;;;;;N;;;;; 0B66;ORIYA DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 0B67;ORIYA DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 0B68;ORIYA DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 0B69;ORIYA DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 0B6A;ORIYA DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 0B6B;ORIYA DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 0B6C;ORIYA DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 0B6D;ORIYA DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 0B6E;ORIYA DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 0B6F;ORIYA DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 0B70;ORIYA ISSHAR;So;0;L;;;;;N;;;;; 0B71;ORIYA LETTER WA;Lo;0;L;;;;;N;;;;; 0B72;ORIYA FRACTION ONE QUARTER;No;0;L;;;;1/4;N;;;;; 0B73;ORIYA FRACTION ONE HALF;No;0;L;;;;1/2;N;;;;; 0B74;ORIYA FRACTION THREE QUARTERS;No;0;L;;;;3/4;N;;;;; 0B75;ORIYA FRACTION ONE SIXTEENTH;No;0;L;;;;1/16;N;;;;; 0B76;ORIYA FRACTION ONE EIGHTH;No;0;L;;;;1/8;N;;;;; 0B77;ORIYA FRACTION THREE SIXTEENTHS;No;0;L;;;;3/16;N;;;;; 0B82;TAMIL SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; 0B83;TAMIL SIGN VISARGA;Lo;0;L;;;;;N;;;;; 0B85;TAMIL LETTER A;Lo;0;L;;;;;N;;;;; 0B86;TAMIL LETTER AA;Lo;0;L;;;;;N;;;;; 0B87;TAMIL LETTER I;Lo;0;L;;;;;N;;;;; 0B88;TAMIL LETTER II;Lo;0;L;;;;;N;;;;; 0B89;TAMIL LETTER U;Lo;0;L;;;;;N;;;;; 0B8A;TAMIL LETTER UU;Lo;0;L;;;;;N;;;;; 0B8E;TAMIL LETTER E;Lo;0;L;;;;;N;;;;; 0B8F;TAMIL LETTER EE;Lo;0;L;;;;;N;;;;; 0B90;TAMIL LETTER AI;Lo;0;L;;;;;N;;;;; 0B92;TAMIL LETTER O;Lo;0;L;;;;;N;;;;; 0B93;TAMIL LETTER OO;Lo;0;L;;;;;N;;;;; 0B94;TAMIL LETTER AU;Lo;0;L;0B92 0BD7;;;;N;;;;; 0B95;TAMIL LETTER KA;Lo;0;L;;;;;N;;;;; 0B99;TAMIL LETTER NGA;Lo;0;L;;;;;N;;;;; 0B9A;TAMIL LETTER CA;Lo;0;L;;;;;N;;;;; 0B9C;TAMIL LETTER JA;Lo;0;L;;;;;N;;;;; 0B9E;TAMIL LETTER NYA;Lo;0;L;;;;;N;;;;; 0B9F;TAMIL LETTER TTA;Lo;0;L;;;;;N;;;;; 0BA3;TAMIL LETTER NNA;Lo;0;L;;;;;N;;;;; 0BA4;TAMIL LETTER TA;Lo;0;L;;;;;N;;;;; 0BA8;TAMIL LETTER NA;Lo;0;L;;;;;N;;;;; 0BA9;TAMIL LETTER NNNA;Lo;0;L;;;;;N;;;;; 0BAA;TAMIL LETTER PA;Lo;0;L;;;;;N;;;;; 0BAE;TAMIL LETTER MA;Lo;0;L;;;;;N;;;;; 0BAF;TAMIL LETTER YA;Lo;0;L;;;;;N;;;;; 0BB0;TAMIL LETTER RA;Lo;0;L;;;;;N;;;;; 0BB1;TAMIL LETTER RRA;Lo;0;L;;;;;N;;;;; 0BB2;TAMIL LETTER LA;Lo;0;L;;;;;N;;;;; 0BB3;TAMIL LETTER LLA;Lo;0;L;;;;;N;;;;; 0BB4;TAMIL LETTER LLLA;Lo;0;L;;;;;N;;;;; 0BB5;TAMIL LETTER VA;Lo;0;L;;;;;N;;;;; 0BB6;TAMIL LETTER SHA;Lo;0;L;;;;;N;;;;; 0BB7;TAMIL LETTER SSA;Lo;0;L;;;;;N;;;;; 0BB8;TAMIL LETTER SA;Lo;0;L;;;;;N;;;;; 0BB9;TAMIL LETTER HA;Lo;0;L;;;;;N;;;;; 0BBE;TAMIL VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 0BBF;TAMIL VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 0BC0;TAMIL VOWEL SIGN II;Mn;0;NSM;;;;;N;;;;; 0BC1;TAMIL VOWEL SIGN U;Mc;0;L;;;;;N;;;;; 0BC2;TAMIL VOWEL SIGN UU;Mc;0;L;;;;;N;;;;; 0BC6;TAMIL VOWEL SIGN E;Mc;0;L;;;;;N;;;;; 0BC7;TAMIL VOWEL SIGN EE;Mc;0;L;;;;;N;;;;; 0BC8;TAMIL VOWEL SIGN AI;Mc;0;L;;;;;N;;;;; 0BCA;TAMIL VOWEL SIGN O;Mc;0;L;0BC6 0BBE;;;;N;;;;; 0BCB;TAMIL VOWEL SIGN OO;Mc;0;L;0BC7 0BBE;;;;N;;;;; 0BCC;TAMIL VOWEL SIGN AU;Mc;0;L;0BC6 0BD7;;;;N;;;;; 0BCD;TAMIL SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 0BD0;TAMIL OM;Lo;0;L;;;;;N;;;;; 0BD7;TAMIL AU LENGTH MARK;Mc;0;L;;;;;N;;;;; 0BE6;TAMIL DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 0BE7;TAMIL DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 0BE8;TAMIL DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 0BE9;TAMIL DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 0BEA;TAMIL DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 0BEB;TAMIL DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 0BEC;TAMIL DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 0BED;TAMIL DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 0BEE;TAMIL DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 0BEF;TAMIL DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 0BF0;TAMIL NUMBER TEN;No;0;L;;;;10;N;;;;; 0BF1;TAMIL NUMBER ONE HUNDRED;No;0;L;;;;100;N;;;;; 0BF2;TAMIL NUMBER ONE THOUSAND;No;0;L;;;;1000;N;;;;; 0BF3;TAMIL DAY SIGN;So;0;ON;;;;;N;;;;; 0BF4;TAMIL MONTH SIGN;So;0;ON;;;;;N;;;;; 0BF5;TAMIL YEAR SIGN;So;0;ON;;;;;N;;;;; 0BF6;TAMIL DEBIT SIGN;So;0;ON;;;;;N;;;;; 0BF7;TAMIL CREDIT SIGN;So;0;ON;;;;;N;;;;; 0BF8;TAMIL AS ABOVE SIGN;So;0;ON;;;;;N;;;;; 0BF9;TAMIL RUPEE SIGN;Sc;0;ET;;;;;N;;;;; 0BFA;TAMIL NUMBER SIGN;So;0;ON;;;;;N;;;;; 0C00;TELUGU SIGN COMBINING CANDRABINDU ABOVE;Mn;0;NSM;;;;;N;;;;; 0C01;TELUGU SIGN CANDRABINDU;Mc;0;L;;;;;N;;;;; 0C02;TELUGU SIGN ANUSVARA;Mc;0;L;;;;;N;;;;; 0C03;TELUGU SIGN VISARGA;Mc;0;L;;;;;N;;;;; 0C05;TELUGU LETTER A;Lo;0;L;;;;;N;;;;; 0C06;TELUGU LETTER AA;Lo;0;L;;;;;N;;;;; 0C07;TELUGU LETTER I;Lo;0;L;;;;;N;;;;; 0C08;TELUGU LETTER II;Lo;0;L;;;;;N;;;;; 0C09;TELUGU LETTER U;Lo;0;L;;;;;N;;;;; 0C0A;TELUGU LETTER UU;Lo;0;L;;;;;N;;;;; 0C0B;TELUGU LETTER VOCALIC R;Lo;0;L;;;;;N;;;;; 0C0C;TELUGU LETTER VOCALIC L;Lo;0;L;;;;;N;;;;; 0C0E;TELUGU LETTER E;Lo;0;L;;;;;N;;;;; 0C0F;TELUGU LETTER EE;Lo;0;L;;;;;N;;;;; 0C10;TELUGU LETTER AI;Lo;0;L;;;;;N;;;;; 0C12;TELUGU LETTER O;Lo;0;L;;;;;N;;;;; 0C13;TELUGU LETTER OO;Lo;0;L;;;;;N;;;;; 0C14;TELUGU LETTER AU;Lo;0;L;;;;;N;;;;; 0C15;TELUGU LETTER KA;Lo;0;L;;;;;N;;;;; 0C16;TELUGU LETTER KHA;Lo;0;L;;;;;N;;;;; 0C17;TELUGU LETTER GA;Lo;0;L;;;;;N;;;;; 0C18;TELUGU LETTER GHA;Lo;0;L;;;;;N;;;;; 0C19;TELUGU LETTER NGA;Lo;0;L;;;;;N;;;;; 0C1A;TELUGU LETTER CA;Lo;0;L;;;;;N;;;;; 0C1B;TELUGU LETTER CHA;Lo;0;L;;;;;N;;;;; 0C1C;TELUGU LETTER JA;Lo;0;L;;;;;N;;;;; 0C1D;TELUGU LETTER JHA;Lo;0;L;;;;;N;;;;; 0C1E;TELUGU LETTER NYA;Lo;0;L;;;;;N;;;;; 0C1F;TELUGU LETTER TTA;Lo;0;L;;;;;N;;;;; 0C20;TELUGU LETTER TTHA;Lo;0;L;;;;;N;;;;; 0C21;TELUGU LETTER DDA;Lo;0;L;;;;;N;;;;; 0C22;TELUGU LETTER DDHA;Lo;0;L;;;;;N;;;;; 0C23;TELUGU LETTER NNA;Lo;0;L;;;;;N;;;;; 0C24;TELUGU LETTER TA;Lo;0;L;;;;;N;;;;; 0C25;TELUGU LETTER THA;Lo;0;L;;;;;N;;;;; 0C26;TELUGU LETTER DA;Lo;0;L;;;;;N;;;;; 0C27;TELUGU LETTER DHA;Lo;0;L;;;;;N;;;;; 0C28;TELUGU LETTER NA;Lo;0;L;;;;;N;;;;; 0C2A;TELUGU LETTER PA;Lo;0;L;;;;;N;;;;; 0C2B;TELUGU LETTER PHA;Lo;0;L;;;;;N;;;;; 0C2C;TELUGU LETTER BA;Lo;0;L;;;;;N;;;;; 0C2D;TELUGU LETTER BHA;Lo;0;L;;;;;N;;;;; 0C2E;TELUGU LETTER MA;Lo;0;L;;;;;N;;;;; 0C2F;TELUGU LETTER YA;Lo;0;L;;;;;N;;;;; 0C30;TELUGU LETTER RA;Lo;0;L;;;;;N;;;;; 0C31;TELUGU LETTER RRA;Lo;0;L;;;;;N;;;;; 0C32;TELUGU LETTER LA;Lo;0;L;;;;;N;;;;; 0C33;TELUGU LETTER LLA;Lo;0;L;;;;;N;;;;; 0C34;TELUGU LETTER LLLA;Lo;0;L;;;;;N;;;;; 0C35;TELUGU LETTER VA;Lo;0;L;;;;;N;;;;; 0C36;TELUGU LETTER SHA;Lo;0;L;;;;;N;;;;; 0C37;TELUGU LETTER SSA;Lo;0;L;;;;;N;;;;; 0C38;TELUGU LETTER SA;Lo;0;L;;;;;N;;;;; 0C39;TELUGU LETTER HA;Lo;0;L;;;;;N;;;;; 0C3D;TELUGU SIGN AVAGRAHA;Lo;0;L;;;;;N;;;;; 0C3E;TELUGU VOWEL SIGN AA;Mn;0;NSM;;;;;N;;;;; 0C3F;TELUGU VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; 0C40;TELUGU VOWEL SIGN II;Mn;0;NSM;;;;;N;;;;; 0C41;TELUGU VOWEL SIGN U;Mc;0;L;;;;;N;;;;; 0C42;TELUGU VOWEL SIGN UU;Mc;0;L;;;;;N;;;;; 0C43;TELUGU VOWEL SIGN VOCALIC R;Mc;0;L;;;;;N;;;;; 0C44;TELUGU VOWEL SIGN VOCALIC RR;Mc;0;L;;;;;N;;;;; 0C46;TELUGU VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;; 0C47;TELUGU VOWEL SIGN EE;Mn;0;NSM;;;;;N;;;;; 0C48;TELUGU VOWEL SIGN AI;Mn;0;NSM;0C46 0C56;;;;N;;;;; 0C4A;TELUGU VOWEL SIGN O;Mn;0;NSM;;;;;N;;;;; 0C4B;TELUGU VOWEL SIGN OO;Mn;0;NSM;;;;;N;;;;; 0C4C;TELUGU VOWEL SIGN AU;Mn;0;NSM;;;;;N;;;;; 0C4D;TELUGU SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 0C55;TELUGU LENGTH MARK;Mn;84;NSM;;;;;N;;;;; 0C56;TELUGU AI LENGTH MARK;Mn;91;NSM;;;;;N;;;;; 0C58;TELUGU LETTER TSA;Lo;0;L;;;;;N;;;;; 0C59;TELUGU LETTER DZA;Lo;0;L;;;;;N;;;;; 0C5A;TELUGU LETTER RRRA;Lo;0;L;;;;;N;;;;; 0C60;TELUGU LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 0C61;TELUGU LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; 0C62;TELUGU VOWEL SIGN VOCALIC L;Mn;0;NSM;;;;;N;;;;; 0C63;TELUGU VOWEL SIGN VOCALIC LL;Mn;0;NSM;;;;;N;;;;; 0C66;TELUGU DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 0C67;TELUGU DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 0C68;TELUGU DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 0C69;TELUGU DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 0C6A;TELUGU DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 0C6B;TELUGU DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 0C6C;TELUGU DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 0C6D;TELUGU DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 0C6E;TELUGU DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 0C6F;TELUGU DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 0C78;TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF FOUR;No;0;ON;;;;0;N;;;;; 0C79;TELUGU FRACTION DIGIT ONE FOR ODD POWERS OF FOUR;No;0;ON;;;;1;N;;;;; 0C7A;TELUGU FRACTION DIGIT TWO FOR ODD POWERS OF FOUR;No;0;ON;;;;2;N;;;;; 0C7B;TELUGU FRACTION DIGIT THREE FOR ODD POWERS OF FOUR;No;0;ON;;;;3;N;;;;; 0C7C;TELUGU FRACTION DIGIT ONE FOR EVEN POWERS OF FOUR;No;0;ON;;;;1;N;;;;; 0C7D;TELUGU FRACTION DIGIT TWO FOR EVEN POWERS OF FOUR;No;0;ON;;;;2;N;;;;; 0C7E;TELUGU FRACTION DIGIT THREE FOR EVEN POWERS OF FOUR;No;0;ON;;;;3;N;;;;; 0C7F;TELUGU SIGN TUUMU;So;0;L;;;;;N;;;;; 0C80;KANNADA SIGN SPACING CANDRABINDU;Lo;0;L;;;;;N;;;;; 0C81;KANNADA SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 0C82;KANNADA SIGN ANUSVARA;Mc;0;L;;;;;N;;;;; 0C83;KANNADA SIGN VISARGA;Mc;0;L;;;;;N;;;;; 0C85;KANNADA LETTER A;Lo;0;L;;;;;N;;;;; 0C86;KANNADA LETTER AA;Lo;0;L;;;;;N;;;;; 0C87;KANNADA LETTER I;Lo;0;L;;;;;N;;;;; 0C88;KANNADA LETTER II;Lo;0;L;;;;;N;;;;; 0C89;KANNADA LETTER U;Lo;0;L;;;;;N;;;;; 0C8A;KANNADA LETTER UU;Lo;0;L;;;;;N;;;;; 0C8B;KANNADA LETTER VOCALIC R;Lo;0;L;;;;;N;;;;; 0C8C;KANNADA LETTER VOCALIC L;Lo;0;L;;;;;N;;;;; 0C8E;KANNADA LETTER E;Lo;0;L;;;;;N;;;;; 0C8F;KANNADA LETTER EE;Lo;0;L;;;;;N;;;;; 0C90;KANNADA LETTER AI;Lo;0;L;;;;;N;;;;; 0C92;KANNADA LETTER O;Lo;0;L;;;;;N;;;;; 0C93;KANNADA LETTER OO;Lo;0;L;;;;;N;;;;; 0C94;KANNADA LETTER AU;Lo;0;L;;;;;N;;;;; 0C95;KANNADA LETTER KA;Lo;0;L;;;;;N;;;;; 0C96;KANNADA LETTER KHA;Lo;0;L;;;;;N;;;;; 0C97;KANNADA LETTER GA;Lo;0;L;;;;;N;;;;; 0C98;KANNADA LETTER GHA;Lo;0;L;;;;;N;;;;; 0C99;KANNADA LETTER NGA;Lo;0;L;;;;;N;;;;; 0C9A;KANNADA LETTER CA;Lo;0;L;;;;;N;;;;; 0C9B;KANNADA LETTER CHA;Lo;0;L;;;;;N;;;;; 0C9C;KANNADA LETTER JA;Lo;0;L;;;;;N;;;;; 0C9D;KANNADA LETTER JHA;Lo;0;L;;;;;N;;;;; 0C9E;KANNADA LETTER NYA;Lo;0;L;;;;;N;;;;; 0C9F;KANNADA LETTER TTA;Lo;0;L;;;;;N;;;;; 0CA0;KANNADA LETTER TTHA;Lo;0;L;;;;;N;;;;; 0CA1;KANNADA LETTER DDA;Lo;0;L;;;;;N;;;;; 0CA2;KANNADA LETTER DDHA;Lo;0;L;;;;;N;;;;; 0CA3;KANNADA LETTER NNA;Lo;0;L;;;;;N;;;;; 0CA4;KANNADA LETTER TA;Lo;0;L;;;;;N;;;;; 0CA5;KANNADA LETTER THA;Lo;0;L;;;;;N;;;;; 0CA6;KANNADA LETTER DA;Lo;0;L;;;;;N;;;;; 0CA7;KANNADA LETTER DHA;Lo;0;L;;;;;N;;;;; 0CA8;KANNADA LETTER NA;Lo;0;L;;;;;N;;;;; 0CAA;KANNADA LETTER PA;Lo;0;L;;;;;N;;;;; 0CAB;KANNADA LETTER PHA;Lo;0;L;;;;;N;;;;; 0CAC;KANNADA LETTER BA;Lo;0;L;;;;;N;;;;; 0CAD;KANNADA LETTER BHA;Lo;0;L;;;;;N;;;;; 0CAE;KANNADA LETTER MA;Lo;0;L;;;;;N;;;;; 0CAF;KANNADA LETTER YA;Lo;0;L;;;;;N;;;;; 0CB0;KANNADA LETTER RA;Lo;0;L;;;;;N;;;;; 0CB1;KANNADA LETTER RRA;Lo;0;L;;;;;N;;;;; 0CB2;KANNADA LETTER LA;Lo;0;L;;;;;N;;;;; 0CB3;KANNADA LETTER LLA;Lo;0;L;;;;;N;;;;; 0CB5;KANNADA LETTER VA;Lo;0;L;;;;;N;;;;; 0CB6;KANNADA LETTER SHA;Lo;0;L;;;;;N;;;;; 0CB7;KANNADA LETTER SSA;Lo;0;L;;;;;N;;;;; 0CB8;KANNADA LETTER SA;Lo;0;L;;;;;N;;;;; 0CB9;KANNADA LETTER HA;Lo;0;L;;;;;N;;;;; 0CBC;KANNADA SIGN NUKTA;Mn;7;NSM;;;;;N;;;;; 0CBD;KANNADA SIGN AVAGRAHA;Lo;0;L;;;;;N;;;;; 0CBE;KANNADA VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 0CBF;KANNADA VOWEL SIGN I;Mn;0;L;;;;;N;;;;; 0CC0;KANNADA VOWEL SIGN II;Mc;0;L;0CBF 0CD5;;;;N;;;;; 0CC1;KANNADA VOWEL SIGN U;Mc;0;L;;;;;N;;;;; 0CC2;KANNADA VOWEL SIGN UU;Mc;0;L;;;;;N;;;;; 0CC3;KANNADA VOWEL SIGN VOCALIC R;Mc;0;L;;;;;N;;;;; 0CC4;KANNADA VOWEL SIGN VOCALIC RR;Mc;0;L;;;;;N;;;;; 0CC6;KANNADA VOWEL SIGN E;Mn;0;L;;;;;N;;;;; 0CC7;KANNADA VOWEL SIGN EE;Mc;0;L;0CC6 0CD5;;;;N;;;;; 0CC8;KANNADA VOWEL SIGN AI;Mc;0;L;0CC6 0CD6;;;;N;;;;; 0CCA;KANNADA VOWEL SIGN O;Mc;0;L;0CC6 0CC2;;;;N;;;;; 0CCB;KANNADA VOWEL SIGN OO;Mc;0;L;0CCA 0CD5;;;;N;;;;; 0CCC;KANNADA VOWEL SIGN AU;Mn;0;NSM;;;;;N;;;;; 0CCD;KANNADA SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 0CD5;KANNADA LENGTH MARK;Mc;0;L;;;;;N;;;;; 0CD6;KANNADA AI LENGTH MARK;Mc;0;L;;;;;N;;;;; 0CDE;KANNADA LETTER FA;Lo;0;L;;;;;N;;;;; 0CE0;KANNADA LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 0CE1;KANNADA LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; 0CE2;KANNADA VOWEL SIGN VOCALIC L;Mn;0;NSM;;;;;N;;;;; 0CE3;KANNADA VOWEL SIGN VOCALIC LL;Mn;0;NSM;;;;;N;;;;; 0CE6;KANNADA DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 0CE7;KANNADA DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 0CE8;KANNADA DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 0CE9;KANNADA DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 0CEA;KANNADA DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 0CEB;KANNADA DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 0CEC;KANNADA DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 0CED;KANNADA DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 0CEE;KANNADA DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 0CEF;KANNADA DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 0CF1;KANNADA SIGN JIHVAMULIYA;Lo;0;L;;;;;N;;;;; 0CF2;KANNADA SIGN UPADHMANIYA;Lo;0;L;;;;;N;;;;; 0D01;MALAYALAM SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 0D02;MALAYALAM SIGN ANUSVARA;Mc;0;L;;;;;N;;;;; 0D03;MALAYALAM SIGN VISARGA;Mc;0;L;;;;;N;;;;; 0D05;MALAYALAM LETTER A;Lo;0;L;;;;;N;;;;; 0D06;MALAYALAM LETTER AA;Lo;0;L;;;;;N;;;;; 0D07;MALAYALAM LETTER I;Lo;0;L;;;;;N;;;;; 0D08;MALAYALAM LETTER II;Lo;0;L;;;;;N;;;;; 0D09;MALAYALAM LETTER U;Lo;0;L;;;;;N;;;;; 0D0A;MALAYALAM LETTER UU;Lo;0;L;;;;;N;;;;; 0D0B;MALAYALAM LETTER VOCALIC R;Lo;0;L;;;;;N;;;;; 0D0C;MALAYALAM LETTER VOCALIC L;Lo;0;L;;;;;N;;;;; 0D0E;MALAYALAM LETTER E;Lo;0;L;;;;;N;;;;; 0D0F;MALAYALAM LETTER EE;Lo;0;L;;;;;N;;;;; 0D10;MALAYALAM LETTER AI;Lo;0;L;;;;;N;;;;; 0D12;MALAYALAM LETTER O;Lo;0;L;;;;;N;;;;; 0D13;MALAYALAM LETTER OO;Lo;0;L;;;;;N;;;;; 0D14;MALAYALAM LETTER AU;Lo;0;L;;;;;N;;;;; 0D15;MALAYALAM LETTER KA;Lo;0;L;;;;;N;;;;; 0D16;MALAYALAM LETTER KHA;Lo;0;L;;;;;N;;;;; 0D17;MALAYALAM LETTER GA;Lo;0;L;;;;;N;;;;; 0D18;MALAYALAM LETTER GHA;Lo;0;L;;;;;N;;;;; 0D19;MALAYALAM LETTER NGA;Lo;0;L;;;;;N;;;;; 0D1A;MALAYALAM LETTER CA;Lo;0;L;;;;;N;;;;; 0D1B;MALAYALAM LETTER CHA;Lo;0;L;;;;;N;;;;; 0D1C;MALAYALAM LETTER JA;Lo;0;L;;;;;N;;;;; 0D1D;MALAYALAM LETTER JHA;Lo;0;L;;;;;N;;;;; 0D1E;MALAYALAM LETTER NYA;Lo;0;L;;;;;N;;;;; 0D1F;MALAYALAM LETTER TTA;Lo;0;L;;;;;N;;;;; 0D20;MALAYALAM LETTER TTHA;Lo;0;L;;;;;N;;;;; 0D21;MALAYALAM LETTER DDA;Lo;0;L;;;;;N;;;;; 0D22;MALAYALAM LETTER DDHA;Lo;0;L;;;;;N;;;;; 0D23;MALAYALAM LETTER NNA;Lo;0;L;;;;;N;;;;; 0D24;MALAYALAM LETTER TA;Lo;0;L;;;;;N;;;;; 0D25;MALAYALAM LETTER THA;Lo;0;L;;;;;N;;;;; 0D26;MALAYALAM LETTER DA;Lo;0;L;;;;;N;;;;; 0D27;MALAYALAM LETTER DHA;Lo;0;L;;;;;N;;;;; 0D28;MALAYALAM LETTER NA;Lo;0;L;;;;;N;;;;; 0D29;MALAYALAM LETTER NNNA;Lo;0;L;;;;;N;;;;; 0D2A;MALAYALAM LETTER PA;Lo;0;L;;;;;N;;;;; 0D2B;MALAYALAM LETTER PHA;Lo;0;L;;;;;N;;;;; 0D2C;MALAYALAM LETTER BA;Lo;0;L;;;;;N;;;;; 0D2D;MALAYALAM LETTER BHA;Lo;0;L;;;;;N;;;;; 0D2E;MALAYALAM LETTER MA;Lo;0;L;;;;;N;;;;; 0D2F;MALAYALAM LETTER YA;Lo;0;L;;;;;N;;;;; 0D30;MALAYALAM LETTER RA;Lo;0;L;;;;;N;;;;; 0D31;MALAYALAM LETTER RRA;Lo;0;L;;;;;N;;;;; 0D32;MALAYALAM LETTER LA;Lo;0;L;;;;;N;;;;; 0D33;MALAYALAM LETTER LLA;Lo;0;L;;;;;N;;;;; 0D34;MALAYALAM LETTER LLLA;Lo;0;L;;;;;N;;;;; 0D35;MALAYALAM LETTER VA;Lo;0;L;;;;;N;;;;; 0D36;MALAYALAM LETTER SHA;Lo;0;L;;;;;N;;;;; 0D37;MALAYALAM LETTER SSA;Lo;0;L;;;;;N;;;;; 0D38;MALAYALAM LETTER SA;Lo;0;L;;;;;N;;;;; 0D39;MALAYALAM LETTER HA;Lo;0;L;;;;;N;;;;; 0D3A;MALAYALAM LETTER TTTA;Lo;0;L;;;;;N;;;;; 0D3D;MALAYALAM SIGN AVAGRAHA;Lo;0;L;;;;;N;;;;; 0D3E;MALAYALAM VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 0D3F;MALAYALAM VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 0D40;MALAYALAM VOWEL SIGN II;Mc;0;L;;;;;N;;;;; 0D41;MALAYALAM VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 0D42;MALAYALAM VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 0D43;MALAYALAM VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;; 0D44;MALAYALAM VOWEL SIGN VOCALIC RR;Mn;0;NSM;;;;;N;;;;; 0D46;MALAYALAM VOWEL SIGN E;Mc;0;L;;;;;N;;;;; 0D47;MALAYALAM VOWEL SIGN EE;Mc;0;L;;;;;N;;;;; 0D48;MALAYALAM VOWEL SIGN AI;Mc;0;L;;;;;N;;;;; 0D4A;MALAYALAM VOWEL SIGN O;Mc;0;L;0D46 0D3E;;;;N;;;;; 0D4B;MALAYALAM VOWEL SIGN OO;Mc;0;L;0D47 0D3E;;;;N;;;;; 0D4C;MALAYALAM VOWEL SIGN AU;Mc;0;L;0D46 0D57;;;;N;;;;; 0D4D;MALAYALAM SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 0D4E;MALAYALAM LETTER DOT REPH;Lo;0;L;;;;;N;;;;; 0D4F;MALAYALAM SIGN PARA;So;0;L;;;;;N;;;;; 0D54;MALAYALAM LETTER CHILLU M;Lo;0;L;;;;;N;;;;; 0D55;MALAYALAM LETTER CHILLU Y;Lo;0;L;;;;;N;;;;; 0D56;MALAYALAM LETTER CHILLU LLL;Lo;0;L;;;;;N;;;;; 0D57;MALAYALAM AU LENGTH MARK;Mc;0;L;;;;;N;;;;; 0D58;MALAYALAM FRACTION ONE ONE-HUNDRED-AND-SIXTIETH;No;0;L;;;;1/160;N;;;;; 0D59;MALAYALAM FRACTION ONE FORTIETH;No;0;L;;;;1/40;N;;;;; 0D5A;MALAYALAM FRACTION THREE EIGHTIETHS;No;0;L;;;;3/80;N;;;;; 0D5B;MALAYALAM FRACTION ONE TWENTIETH;No;0;L;;;;1/20;N;;;;; 0D5C;MALAYALAM FRACTION ONE TENTH;No;0;L;;;;1/10;N;;;;; 0D5D;MALAYALAM FRACTION THREE TWENTIETHS;No;0;L;;;;3/20;N;;;;; 0D5E;MALAYALAM FRACTION ONE FIFTH;No;0;L;;;;1/5;N;;;;; 0D5F;MALAYALAM LETTER ARCHAIC II;Lo;0;L;;;;;N;;;;; 0D60;MALAYALAM LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 0D61;MALAYALAM LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; 0D62;MALAYALAM VOWEL SIGN VOCALIC L;Mn;0;NSM;;;;;N;;;;; 0D63;MALAYALAM VOWEL SIGN VOCALIC LL;Mn;0;NSM;;;;;N;;;;; 0D66;MALAYALAM DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 0D67;MALAYALAM DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 0D68;MALAYALAM DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 0D69;MALAYALAM DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 0D6A;MALAYALAM DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 0D6B;MALAYALAM DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 0D6C;MALAYALAM DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 0D6D;MALAYALAM DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 0D6E;MALAYALAM DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 0D6F;MALAYALAM DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 0D70;MALAYALAM NUMBER TEN;No;0;L;;;;10;N;;;;; 0D71;MALAYALAM NUMBER ONE HUNDRED;No;0;L;;;;100;N;;;;; 0D72;MALAYALAM NUMBER ONE THOUSAND;No;0;L;;;;1000;N;;;;; 0D73;MALAYALAM FRACTION ONE QUARTER;No;0;L;;;;1/4;N;;;;; 0D74;MALAYALAM FRACTION ONE HALF;No;0;L;;;;1/2;N;;;;; 0D75;MALAYALAM FRACTION THREE QUARTERS;No;0;L;;;;3/4;N;;;;; 0D76;MALAYALAM FRACTION ONE SIXTEENTH;No;0;L;;;;1/16;N;;;;; 0D77;MALAYALAM FRACTION ONE EIGHTH;No;0;L;;;;1/8;N;;;;; 0D78;MALAYALAM FRACTION THREE SIXTEENTHS;No;0;L;;;;3/16;N;;;;; 0D79;MALAYALAM DATE MARK;So;0;L;;;;;N;;;;; 0D7A;MALAYALAM LETTER CHILLU NN;Lo;0;L;;;;;N;;;;; 0D7B;MALAYALAM LETTER CHILLU N;Lo;0;L;;;;;N;;;;; 0D7C;MALAYALAM LETTER CHILLU RR;Lo;0;L;;;;;N;;;;; 0D7D;MALAYALAM LETTER CHILLU L;Lo;0;L;;;;;N;;;;; 0D7E;MALAYALAM LETTER CHILLU LL;Lo;0;L;;;;;N;;;;; 0D7F;MALAYALAM LETTER CHILLU K;Lo;0;L;;;;;N;;;;; 0D82;SINHALA SIGN ANUSVARAYA;Mc;0;L;;;;;N;;;;; 0D83;SINHALA SIGN VISARGAYA;Mc;0;L;;;;;N;;;;; 0D85;SINHALA LETTER AYANNA;Lo;0;L;;;;;N;;;;; 0D86;SINHALA LETTER AAYANNA;Lo;0;L;;;;;N;;;;; 0D87;SINHALA LETTER AEYANNA;Lo;0;L;;;;;N;;;;; 0D88;SINHALA LETTER AEEYANNA;Lo;0;L;;;;;N;;;;; 0D89;SINHALA LETTER IYANNA;Lo;0;L;;;;;N;;;;; 0D8A;SINHALA LETTER IIYANNA;Lo;0;L;;;;;N;;;;; 0D8B;SINHALA LETTER UYANNA;Lo;0;L;;;;;N;;;;; 0D8C;SINHALA LETTER UUYANNA;Lo;0;L;;;;;N;;;;; 0D8D;SINHALA LETTER IRUYANNA;Lo;0;L;;;;;N;;;;; 0D8E;SINHALA LETTER IRUUYANNA;Lo;0;L;;;;;N;;;;; 0D8F;SINHALA LETTER ILUYANNA;Lo;0;L;;;;;N;;;;; 0D90;SINHALA LETTER ILUUYANNA;Lo;0;L;;;;;N;;;;; 0D91;SINHALA LETTER EYANNA;Lo;0;L;;;;;N;;;;; 0D92;SINHALA LETTER EEYANNA;Lo;0;L;;;;;N;;;;; 0D93;SINHALA LETTER AIYANNA;Lo;0;L;;;;;N;;;;; 0D94;SINHALA LETTER OYANNA;Lo;0;L;;;;;N;;;;; 0D95;SINHALA LETTER OOYANNA;Lo;0;L;;;;;N;;;;; 0D96;SINHALA LETTER AUYANNA;Lo;0;L;;;;;N;;;;; 0D9A;SINHALA LETTER ALPAPRAANA KAYANNA;Lo;0;L;;;;;N;;;;; 0D9B;SINHALA LETTER MAHAAPRAANA KAYANNA;Lo;0;L;;;;;N;;;;; 0D9C;SINHALA LETTER ALPAPRAANA GAYANNA;Lo;0;L;;;;;N;;;;; 0D9D;SINHALA LETTER MAHAAPRAANA GAYANNA;Lo;0;L;;;;;N;;;;; 0D9E;SINHALA LETTER KANTAJA NAASIKYAYA;Lo;0;L;;;;;N;;;;; 0D9F;SINHALA LETTER SANYAKA GAYANNA;Lo;0;L;;;;;N;;;;; 0DA0;SINHALA LETTER ALPAPRAANA CAYANNA;Lo;0;L;;;;;N;;;;; 0DA1;SINHALA LETTER MAHAAPRAANA CAYANNA;Lo;0;L;;;;;N;;;;; 0DA2;SINHALA LETTER ALPAPRAANA JAYANNA;Lo;0;L;;;;;N;;;;; 0DA3;SINHALA LETTER MAHAAPRAANA JAYANNA;Lo;0;L;;;;;N;;;;; 0DA4;SINHALA LETTER TAALUJA NAASIKYAYA;Lo;0;L;;;;;N;;;;; 0DA5;SINHALA LETTER TAALUJA SANYOOGA NAAKSIKYAYA;Lo;0;L;;;;;N;;;;; 0DA6;SINHALA LETTER SANYAKA JAYANNA;Lo;0;L;;;;;N;;;;; 0DA7;SINHALA LETTER ALPAPRAANA TTAYANNA;Lo;0;L;;;;;N;;;;; 0DA8;SINHALA LETTER MAHAAPRAANA TTAYANNA;Lo;0;L;;;;;N;;;;; 0DA9;SINHALA LETTER ALPAPRAANA DDAYANNA;Lo;0;L;;;;;N;;;;; 0DAA;SINHALA LETTER MAHAAPRAANA DDAYANNA;Lo;0;L;;;;;N;;;;; 0DAB;SINHALA LETTER MUURDHAJA NAYANNA;Lo;0;L;;;;;N;;;;; 0DAC;SINHALA LETTER SANYAKA DDAYANNA;Lo;0;L;;;;;N;;;;; 0DAD;SINHALA LETTER ALPAPRAANA TAYANNA;Lo;0;L;;;;;N;;;;; 0DAE;SINHALA LETTER MAHAAPRAANA TAYANNA;Lo;0;L;;;;;N;;;;; 0DAF;SINHALA LETTER ALPAPRAANA DAYANNA;Lo;0;L;;;;;N;;;;; 0DB0;SINHALA LETTER MAHAAPRAANA DAYANNA;Lo;0;L;;;;;N;;;;; 0DB1;SINHALA LETTER DANTAJA NAYANNA;Lo;0;L;;;;;N;;;;; 0DB3;SINHALA LETTER SANYAKA DAYANNA;Lo;0;L;;;;;N;;;;; 0DB4;SINHALA LETTER ALPAPRAANA PAYANNA;Lo;0;L;;;;;N;;;;; 0DB5;SINHALA LETTER MAHAAPRAANA PAYANNA;Lo;0;L;;;;;N;;;;; 0DB6;SINHALA LETTER ALPAPRAANA BAYANNA;Lo;0;L;;;;;N;;;;; 0DB7;SINHALA LETTER MAHAAPRAANA BAYANNA;Lo;0;L;;;;;N;;;;; 0DB8;SINHALA LETTER MAYANNA;Lo;0;L;;;;;N;;;;; 0DB9;SINHALA LETTER AMBA BAYANNA;Lo;0;L;;;;;N;;;;; 0DBA;SINHALA LETTER YAYANNA;Lo;0;L;;;;;N;;;;; 0DBB;SINHALA LETTER RAYANNA;Lo;0;L;;;;;N;;;;; 0DBD;SINHALA LETTER DANTAJA LAYANNA;Lo;0;L;;;;;N;;;;; 0DC0;SINHALA LETTER VAYANNA;Lo;0;L;;;;;N;;;;; 0DC1;SINHALA LETTER TAALUJA SAYANNA;Lo;0;L;;;;;N;;;;; 0DC2;SINHALA LETTER MUURDHAJA SAYANNA;Lo;0;L;;;;;N;;;;; 0DC3;SINHALA LETTER DANTAJA SAYANNA;Lo;0;L;;;;;N;;;;; 0DC4;SINHALA LETTER HAYANNA;Lo;0;L;;;;;N;;;;; 0DC5;SINHALA LETTER MUURDHAJA LAYANNA;Lo;0;L;;;;;N;;;;; 0DC6;SINHALA LETTER FAYANNA;Lo;0;L;;;;;N;;;;; 0DCA;SINHALA SIGN AL-LAKUNA;Mn;9;NSM;;;;;N;;;;; 0DCF;SINHALA VOWEL SIGN AELA-PILLA;Mc;0;L;;;;;N;;;;; 0DD0;SINHALA VOWEL SIGN KETTI AEDA-PILLA;Mc;0;L;;;;;N;;;;; 0DD1;SINHALA VOWEL SIGN DIGA AEDA-PILLA;Mc;0;L;;;;;N;;;;; 0DD2;SINHALA VOWEL SIGN KETTI IS-PILLA;Mn;0;NSM;;;;;N;;;;; 0DD3;SINHALA VOWEL SIGN DIGA IS-PILLA;Mn;0;NSM;;;;;N;;;;; 0DD4;SINHALA VOWEL SIGN KETTI PAA-PILLA;Mn;0;NSM;;;;;N;;;;; 0DD6;SINHALA VOWEL SIGN DIGA PAA-PILLA;Mn;0;NSM;;;;;N;;;;; 0DD8;SINHALA VOWEL SIGN GAETTA-PILLA;Mc;0;L;;;;;N;;;;; 0DD9;SINHALA VOWEL SIGN KOMBUVA;Mc;0;L;;;;;N;;;;; 0DDA;SINHALA VOWEL SIGN DIGA KOMBUVA;Mc;0;L;0DD9 0DCA;;;;N;;;;; 0DDB;SINHALA VOWEL SIGN KOMBU DEKA;Mc;0;L;;;;;N;;;;; 0DDC;SINHALA VOWEL SIGN KOMBUVA HAA AELA-PILLA;Mc;0;L;0DD9 0DCF;;;;N;;;;; 0DDD;SINHALA VOWEL SIGN KOMBUVA HAA DIGA AELA-PILLA;Mc;0;L;0DDC 0DCA;;;;N;;;;; 0DDE;SINHALA VOWEL SIGN KOMBUVA HAA GAYANUKITTA;Mc;0;L;0DD9 0DDF;;;;N;;;;; 0DDF;SINHALA VOWEL SIGN GAYANUKITTA;Mc;0;L;;;;;N;;;;; 0DE6;SINHALA LITH DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 0DE7;SINHALA LITH DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 0DE8;SINHALA LITH DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 0DE9;SINHALA LITH DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 0DEA;SINHALA LITH DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 0DEB;SINHALA LITH DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 0DEC;SINHALA LITH DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 0DED;SINHALA LITH DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 0DEE;SINHALA LITH DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 0DEF;SINHALA LITH DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 0DF2;SINHALA VOWEL SIGN DIGA GAETTA-PILLA;Mc;0;L;;;;;N;;;;; 0DF3;SINHALA VOWEL SIGN DIGA GAYANUKITTA;Mc;0;L;;;;;N;;;;; 0DF4;SINHALA PUNCTUATION KUNDDALIYA;Po;0;L;;;;;N;;;;; 0E01;THAI CHARACTER KO KAI;Lo;0;L;;;;;N;THAI LETTER KO KAI;;;; 0E02;THAI CHARACTER KHO KHAI;Lo;0;L;;;;;N;THAI LETTER KHO KHAI;;;; 0E03;THAI CHARACTER KHO KHUAT;Lo;0;L;;;;;N;THAI LETTER KHO KHUAT;;;; 0E04;THAI CHARACTER KHO KHWAI;Lo;0;L;;;;;N;THAI LETTER KHO KHWAI;;;; 0E05;THAI CHARACTER KHO KHON;Lo;0;L;;;;;N;THAI LETTER KHO KHON;;;; 0E06;THAI CHARACTER KHO RAKHANG;Lo;0;L;;;;;N;THAI LETTER KHO RAKHANG;;;; 0E07;THAI CHARACTER NGO NGU;Lo;0;L;;;;;N;THAI LETTER NGO NGU;;;; 0E08;THAI CHARACTER CHO CHAN;Lo;0;L;;;;;N;THAI LETTER CHO CHAN;;;; 0E09;THAI CHARACTER CHO CHING;Lo;0;L;;;;;N;THAI LETTER CHO CHING;;;; 0E0A;THAI CHARACTER CHO CHANG;Lo;0;L;;;;;N;THAI LETTER CHO CHANG;;;; 0E0B;THAI CHARACTER SO SO;Lo;0;L;;;;;N;THAI LETTER SO SO;;;; 0E0C;THAI CHARACTER CHO CHOE;Lo;0;L;;;;;N;THAI LETTER CHO CHOE;;;; 0E0D;THAI CHARACTER YO YING;Lo;0;L;;;;;N;THAI LETTER YO YING;;;; 0E0E;THAI CHARACTER DO CHADA;Lo;0;L;;;;;N;THAI LETTER DO CHADA;;;; 0E0F;THAI CHARACTER TO PATAK;Lo;0;L;;;;;N;THAI LETTER TO PATAK;;;; 0E10;THAI CHARACTER THO THAN;Lo;0;L;;;;;N;THAI LETTER THO THAN;;;; 0E11;THAI CHARACTER THO NANGMONTHO;Lo;0;L;;;;;N;THAI LETTER THO NANGMONTHO;;;; 0E12;THAI CHARACTER THO PHUTHAO;Lo;0;L;;;;;N;THAI LETTER THO PHUTHAO;;;; 0E13;THAI CHARACTER NO NEN;Lo;0;L;;;;;N;THAI LETTER NO NEN;;;; 0E14;THAI CHARACTER DO DEK;Lo;0;L;;;;;N;THAI LETTER DO DEK;;;; 0E15;THAI CHARACTER TO TAO;Lo;0;L;;;;;N;THAI LETTER TO TAO;;;; 0E16;THAI CHARACTER THO THUNG;Lo;0;L;;;;;N;THAI LETTER THO THUNG;;;; 0E17;THAI CHARACTER THO THAHAN;Lo;0;L;;;;;N;THAI LETTER THO THAHAN;;;; 0E18;THAI CHARACTER THO THONG;Lo;0;L;;;;;N;THAI LETTER THO THONG;;;; 0E19;THAI CHARACTER NO NU;Lo;0;L;;;;;N;THAI LETTER NO NU;;;; 0E1A;THAI CHARACTER BO BAIMAI;Lo;0;L;;;;;N;THAI LETTER BO BAIMAI;;;; 0E1B;THAI CHARACTER PO PLA;Lo;0;L;;;;;N;THAI LETTER PO PLA;;;; 0E1C;THAI CHARACTER PHO PHUNG;Lo;0;L;;;;;N;THAI LETTER PHO PHUNG;;;; 0E1D;THAI CHARACTER FO FA;Lo;0;L;;;;;N;THAI LETTER FO FA;;;; 0E1E;THAI CHARACTER PHO PHAN;Lo;0;L;;;;;N;THAI LETTER PHO PHAN;;;; 0E1F;THAI CHARACTER FO FAN;Lo;0;L;;;;;N;THAI LETTER FO FAN;;;; 0E20;THAI CHARACTER PHO SAMPHAO;Lo;0;L;;;;;N;THAI LETTER PHO SAMPHAO;;;; 0E21;THAI CHARACTER MO MA;Lo;0;L;;;;;N;THAI LETTER MO MA;;;; 0E22;THAI CHARACTER YO YAK;Lo;0;L;;;;;N;THAI LETTER YO YAK;;;; 0E23;THAI CHARACTER RO RUA;Lo;0;L;;;;;N;THAI LETTER RO RUA;;;; 0E24;THAI CHARACTER RU;Lo;0;L;;;;;N;THAI LETTER RU;;;; 0E25;THAI CHARACTER LO LING;Lo;0;L;;;;;N;THAI LETTER LO LING;;;; 0E26;THAI CHARACTER LU;Lo;0;L;;;;;N;THAI LETTER LU;;;; 0E27;THAI CHARACTER WO WAEN;Lo;0;L;;;;;N;THAI LETTER WO WAEN;;;; 0E28;THAI CHARACTER SO SALA;Lo;0;L;;;;;N;THAI LETTER SO SALA;;;; 0E29;THAI CHARACTER SO RUSI;Lo;0;L;;;;;N;THAI LETTER SO RUSI;;;; 0E2A;THAI CHARACTER SO SUA;Lo;0;L;;;;;N;THAI LETTER SO SUA;;;; 0E2B;THAI CHARACTER HO HIP;Lo;0;L;;;;;N;THAI LETTER HO HIP;;;; 0E2C;THAI CHARACTER LO CHULA;Lo;0;L;;;;;N;THAI LETTER LO CHULA;;;; 0E2D;THAI CHARACTER O ANG;Lo;0;L;;;;;N;THAI LETTER O ANG;;;; 0E2E;THAI CHARACTER HO NOKHUK;Lo;0;L;;;;;N;THAI LETTER HO NOK HUK;;;; 0E2F;THAI CHARACTER PAIYANNOI;Lo;0;L;;;;;N;THAI PAI YAN NOI;;;; 0E30;THAI CHARACTER SARA A;Lo;0;L;;;;;N;THAI VOWEL SIGN SARA A;;;; 0E31;THAI CHARACTER MAI HAN-AKAT;Mn;0;NSM;;;;;N;THAI VOWEL SIGN MAI HAN-AKAT;;;; 0E32;THAI CHARACTER SARA AA;Lo;0;L;;;;;N;THAI VOWEL SIGN SARA AA;;;; 0E33;THAI CHARACTER SARA AM;Lo;0;L; 0E4D 0E32;;;;N;THAI VOWEL SIGN SARA AM;;;; 0E34;THAI CHARACTER SARA I;Mn;0;NSM;;;;;N;THAI VOWEL SIGN SARA I;;;; 0E35;THAI CHARACTER SARA II;Mn;0;NSM;;;;;N;THAI VOWEL SIGN SARA II;;;; 0E36;THAI CHARACTER SARA UE;Mn;0;NSM;;;;;N;THAI VOWEL SIGN SARA UE;;;; 0E37;THAI CHARACTER SARA UEE;Mn;0;NSM;;;;;N;THAI VOWEL SIGN SARA UEE;;;; 0E38;THAI CHARACTER SARA U;Mn;103;NSM;;;;;N;THAI VOWEL SIGN SARA U;;;; 0E39;THAI CHARACTER SARA UU;Mn;103;NSM;;;;;N;THAI VOWEL SIGN SARA UU;;;; 0E3A;THAI CHARACTER PHINTHU;Mn;9;NSM;;;;;N;THAI VOWEL SIGN PHINTHU;;;; 0E3F;THAI CURRENCY SYMBOL BAHT;Sc;0;ET;;;;;N;THAI BAHT SIGN;;;; 0E40;THAI CHARACTER SARA E;Lo;0;L;;;;;N;THAI VOWEL SIGN SARA E;;;; 0E41;THAI CHARACTER SARA AE;Lo;0;L;;;;;N;THAI VOWEL SIGN SARA AE;;;; 0E42;THAI CHARACTER SARA O;Lo;0;L;;;;;N;THAI VOWEL SIGN SARA O;;;; 0E43;THAI CHARACTER SARA AI MAIMUAN;Lo;0;L;;;;;N;THAI VOWEL SIGN SARA MAI MUAN;;;; 0E44;THAI CHARACTER SARA AI MAIMALAI;Lo;0;L;;;;;N;THAI VOWEL SIGN SARA MAI MALAI;;;; 0E45;THAI CHARACTER LAKKHANGYAO;Lo;0;L;;;;;N;THAI LAK KHANG YAO;;;; 0E46;THAI CHARACTER MAIYAMOK;Lm;0;L;;;;;N;THAI MAI YAMOK;;;; 0E47;THAI CHARACTER MAITAIKHU;Mn;0;NSM;;;;;N;THAI VOWEL SIGN MAI TAI KHU;;;; 0E48;THAI CHARACTER MAI EK;Mn;107;NSM;;;;;N;THAI TONE MAI EK;;;; 0E49;THAI CHARACTER MAI THO;Mn;107;NSM;;;;;N;THAI TONE MAI THO;;;; 0E4A;THAI CHARACTER MAI TRI;Mn;107;NSM;;;;;N;THAI TONE MAI TRI;;;; 0E4B;THAI CHARACTER MAI CHATTAWA;Mn;107;NSM;;;;;N;THAI TONE MAI CHATTAWA;;;; 0E4C;THAI CHARACTER THANTHAKHAT;Mn;0;NSM;;;;;N;THAI THANTHAKHAT;;;; 0E4D;THAI CHARACTER NIKHAHIT;Mn;0;NSM;;;;;N;THAI NIKKHAHIT;;;; 0E4E;THAI CHARACTER YAMAKKAN;Mn;0;NSM;;;;;N;THAI YAMAKKAN;;;; 0E4F;THAI CHARACTER FONGMAN;Po;0;L;;;;;N;THAI FONGMAN;;;; 0E50;THAI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 0E51;THAI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 0E52;THAI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 0E53;THAI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 0E54;THAI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 0E55;THAI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 0E56;THAI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 0E57;THAI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 0E58;THAI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 0E59;THAI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 0E5A;THAI CHARACTER ANGKHANKHU;Po;0;L;;;;;N;THAI ANGKHANKHU;;;; 0E5B;THAI CHARACTER KHOMUT;Po;0;L;;;;;N;THAI KHOMUT;;;; 0E81;LAO LETTER KO;Lo;0;L;;;;;N;;;;; 0E82;LAO LETTER KHO SUNG;Lo;0;L;;;;;N;;;;; 0E84;LAO LETTER KHO TAM;Lo;0;L;;;;;N;;;;; 0E87;LAO LETTER NGO;Lo;0;L;;;;;N;;;;; 0E88;LAO LETTER CO;Lo;0;L;;;;;N;;;;; 0E8A;LAO LETTER SO TAM;Lo;0;L;;;;;N;;;;; 0E8D;LAO LETTER NYO;Lo;0;L;;;;;N;;;;; 0E94;LAO LETTER DO;Lo;0;L;;;;;N;;;;; 0E95;LAO LETTER TO;Lo;0;L;;;;;N;;;;; 0E96;LAO LETTER THO SUNG;Lo;0;L;;;;;N;;;;; 0E97;LAO LETTER THO TAM;Lo;0;L;;;;;N;;;;; 0E99;LAO LETTER NO;Lo;0;L;;;;;N;;;;; 0E9A;LAO LETTER BO;Lo;0;L;;;;;N;;;;; 0E9B;LAO LETTER PO;Lo;0;L;;;;;N;;;;; 0E9C;LAO LETTER PHO SUNG;Lo;0;L;;;;;N;;;;; 0E9D;LAO LETTER FO TAM;Lo;0;L;;;;;N;;;;; 0E9E;LAO LETTER PHO TAM;Lo;0;L;;;;;N;;;;; 0E9F;LAO LETTER FO SUNG;Lo;0;L;;;;;N;;;;; 0EA1;LAO LETTER MO;Lo;0;L;;;;;N;;;;; 0EA2;LAO LETTER YO;Lo;0;L;;;;;N;;;;; 0EA3;LAO LETTER LO LING;Lo;0;L;;;;;N;;;;; 0EA5;LAO LETTER LO LOOT;Lo;0;L;;;;;N;;;;; 0EA7;LAO LETTER WO;Lo;0;L;;;;;N;;;;; 0EAA;LAO LETTER SO SUNG;Lo;0;L;;;;;N;;;;; 0EAB;LAO LETTER HO SUNG;Lo;0;L;;;;;N;;;;; 0EAD;LAO LETTER O;Lo;0;L;;;;;N;;;;; 0EAE;LAO LETTER HO TAM;Lo;0;L;;;;;N;;;;; 0EAF;LAO ELLIPSIS;Lo;0;L;;;;;N;;;;; 0EB0;LAO VOWEL SIGN A;Lo;0;L;;;;;N;;;;; 0EB1;LAO VOWEL SIGN MAI KAN;Mn;0;NSM;;;;;N;;;;; 0EB2;LAO VOWEL SIGN AA;Lo;0;L;;;;;N;;;;; 0EB3;LAO VOWEL SIGN AM;Lo;0;L; 0ECD 0EB2;;;;N;;;;; 0EB4;LAO VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; 0EB5;LAO VOWEL SIGN II;Mn;0;NSM;;;;;N;;;;; 0EB6;LAO VOWEL SIGN Y;Mn;0;NSM;;;;;N;;;;; 0EB7;LAO VOWEL SIGN YY;Mn;0;NSM;;;;;N;;;;; 0EB8;LAO VOWEL SIGN U;Mn;118;NSM;;;;;N;;;;; 0EB9;LAO VOWEL SIGN UU;Mn;118;NSM;;;;;N;;;;; 0EBB;LAO VOWEL SIGN MAI KON;Mn;0;NSM;;;;;N;;;;; 0EBC;LAO SEMIVOWEL SIGN LO;Mn;0;NSM;;;;;N;;;;; 0EBD;LAO SEMIVOWEL SIGN NYO;Lo;0;L;;;;;N;;;;; 0EC0;LAO VOWEL SIGN E;Lo;0;L;;;;;N;;;;; 0EC1;LAO VOWEL SIGN EI;Lo;0;L;;;;;N;;;;; 0EC2;LAO VOWEL SIGN O;Lo;0;L;;;;;N;;;;; 0EC3;LAO VOWEL SIGN AY;Lo;0;L;;;;;N;;;;; 0EC4;LAO VOWEL SIGN AI;Lo;0;L;;;;;N;;;;; 0EC6;LAO KO LA;Lm;0;L;;;;;N;;;;; 0EC8;LAO TONE MAI EK;Mn;122;NSM;;;;;N;;;;; 0EC9;LAO TONE MAI THO;Mn;122;NSM;;;;;N;;;;; 0ECA;LAO TONE MAI TI;Mn;122;NSM;;;;;N;;;;; 0ECB;LAO TONE MAI CATAWA;Mn;122;NSM;;;;;N;;;;; 0ECC;LAO CANCELLATION MARK;Mn;0;NSM;;;;;N;;;;; 0ECD;LAO NIGGAHITA;Mn;0;NSM;;;;;N;;;;; 0ED0;LAO DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 0ED1;LAO DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 0ED2;LAO DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 0ED3;LAO DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 0ED4;LAO DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 0ED5;LAO DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 0ED6;LAO DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 0ED7;LAO DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 0ED8;LAO DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 0ED9;LAO DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 0EDC;LAO HO NO;Lo;0;L; 0EAB 0E99;;;;N;;;;; 0EDD;LAO HO MO;Lo;0;L; 0EAB 0EA1;;;;N;;;;; 0EDE;LAO LETTER KHMU GO;Lo;0;L;;;;;N;;;;; 0EDF;LAO LETTER KHMU NYO;Lo;0;L;;;;;N;;;;; 0F00;TIBETAN SYLLABLE OM;Lo;0;L;;;;;N;;;;; 0F01;TIBETAN MARK GTER YIG MGO TRUNCATED A;So;0;L;;;;;N;;;;; 0F02;TIBETAN MARK GTER YIG MGO -UM RNAM BCAD MA;So;0;L;;;;;N;;;;; 0F03;TIBETAN MARK GTER YIG MGO -UM GTER TSHEG MA;So;0;L;;;;;N;;;;; 0F04;TIBETAN MARK INITIAL YIG MGO MDUN MA;Po;0;L;;;;;N;TIBETAN SINGLE ORNAMENT;;;; 0F05;TIBETAN MARK CLOSING YIG MGO SGAB MA;Po;0;L;;;;;N;;;;; 0F06;TIBETAN MARK CARET YIG MGO PHUR SHAD MA;Po;0;L;;;;;N;;;;; 0F07;TIBETAN MARK YIG MGO TSHEG SHAD MA;Po;0;L;;;;;N;;;;; 0F08;TIBETAN MARK SBRUL SHAD;Po;0;L;;;;;N;TIBETAN RGYANSHAD;;;; 0F09;TIBETAN MARK BSKUR YIG MGO;Po;0;L;;;;;N;;;;; 0F0A;TIBETAN MARK BKA- SHOG YIG MGO;Po;0;L;;;;;N;;;;; 0F0B;TIBETAN MARK INTERSYLLABIC TSHEG;Po;0;L;;;;;N;TIBETAN TSEG;;;; 0F0C;TIBETAN MARK DELIMITER TSHEG BSTAR;Po;0;L; 0F0B;;;;N;;;;; 0F0D;TIBETAN MARK SHAD;Po;0;L;;;;;N;TIBETAN SHAD;;;; 0F0E;TIBETAN MARK NYIS SHAD;Po;0;L;;;;;N;TIBETAN DOUBLE SHAD;;;; 0F0F;TIBETAN MARK TSHEG SHAD;Po;0;L;;;;;N;;;;; 0F10;TIBETAN MARK NYIS TSHEG SHAD;Po;0;L;;;;;N;;;;; 0F11;TIBETAN MARK RIN CHEN SPUNGS SHAD;Po;0;L;;;;;N;TIBETAN RINCHANPHUNGSHAD;;;; 0F12;TIBETAN MARK RGYA GRAM SHAD;Po;0;L;;;;;N;;;;; 0F13;TIBETAN MARK CARET -DZUD RTAGS ME LONG CAN;So;0;L;;;;;N;;;;; 0F14;TIBETAN MARK GTER TSHEG;Po;0;L;;;;;N;TIBETAN COMMA;;;; 0F15;TIBETAN LOGOTYPE SIGN CHAD RTAGS;So;0;L;;;;;N;;;;; 0F16;TIBETAN LOGOTYPE SIGN LHAG RTAGS;So;0;L;;;;;N;;;;; 0F17;TIBETAN ASTROLOGICAL SIGN SGRA GCAN -CHAR RTAGS;So;0;L;;;;;N;;;;; 0F18;TIBETAN ASTROLOGICAL SIGN -KHYUD PA;Mn;220;NSM;;;;;N;;;;; 0F19;TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS;Mn;220;NSM;;;;;N;;;;; 0F1A;TIBETAN SIGN RDEL DKAR GCIG;So;0;L;;;;;N;;;;; 0F1B;TIBETAN SIGN RDEL DKAR GNYIS;So;0;L;;;;;N;;;;; 0F1C;TIBETAN SIGN RDEL DKAR GSUM;So;0;L;;;;;N;;;;; 0F1D;TIBETAN SIGN RDEL NAG GCIG;So;0;L;;;;;N;;;;; 0F1E;TIBETAN SIGN RDEL NAG GNYIS;So;0;L;;;;;N;;;;; 0F1F;TIBETAN SIGN RDEL DKAR RDEL NAG;So;0;L;;;;;N;;;;; 0F20;TIBETAN DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 0F21;TIBETAN DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 0F22;TIBETAN DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 0F23;TIBETAN DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 0F24;TIBETAN DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 0F25;TIBETAN DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 0F26;TIBETAN DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 0F27;TIBETAN DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 0F28;TIBETAN DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 0F29;TIBETAN DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 0F2A;TIBETAN DIGIT HALF ONE;No;0;L;;;;1/2;N;;;;; 0F2B;TIBETAN DIGIT HALF TWO;No;0;L;;;;3/2;N;;;;; 0F2C;TIBETAN DIGIT HALF THREE;No;0;L;;;;5/2;N;;;;; 0F2D;TIBETAN DIGIT HALF FOUR;No;0;L;;;;7/2;N;;;;; 0F2E;TIBETAN DIGIT HALF FIVE;No;0;L;;;;9/2;N;;;;; 0F2F;TIBETAN DIGIT HALF SIX;No;0;L;;;;11/2;N;;;;; 0F30;TIBETAN DIGIT HALF SEVEN;No;0;L;;;;13/2;N;;;;; 0F31;TIBETAN DIGIT HALF EIGHT;No;0;L;;;;15/2;N;;;;; 0F32;TIBETAN DIGIT HALF NINE;No;0;L;;;;17/2;N;;;;; 0F33;TIBETAN DIGIT HALF ZERO;No;0;L;;;;-1/2;N;;;;; 0F34;TIBETAN MARK BSDUS RTAGS;So;0;L;;;;;N;;;;; 0F35;TIBETAN MARK NGAS BZUNG NYI ZLA;Mn;220;NSM;;;;;N;TIBETAN HONORIFIC UNDER RING;;;; 0F36;TIBETAN MARK CARET -DZUD RTAGS BZHI MIG CAN;So;0;L;;;;;N;;;;; 0F37;TIBETAN MARK NGAS BZUNG SGOR RTAGS;Mn;220;NSM;;;;;N;TIBETAN UNDER RING;;;; 0F38;TIBETAN MARK CHE MGO;So;0;L;;;;;N;;;;; 0F39;TIBETAN MARK TSA -PHRU;Mn;216;NSM;;;;;N;TIBETAN LENITION MARK;;;; 0F3A;TIBETAN MARK GUG RTAGS GYON;Ps;0;ON;;;;;Y;;;;; 0F3B;TIBETAN MARK GUG RTAGS GYAS;Pe;0;ON;;;;;Y;;;;; 0F3C;TIBETAN MARK ANG KHANG GYON;Ps;0;ON;;;;;Y;TIBETAN LEFT BRACE;;;; 0F3D;TIBETAN MARK ANG KHANG GYAS;Pe;0;ON;;;;;Y;TIBETAN RIGHT BRACE;;;; 0F3E;TIBETAN SIGN YAR TSHES;Mc;0;L;;;;;N;;;;; 0F3F;TIBETAN SIGN MAR TSHES;Mc;0;L;;;;;N;;;;; 0F40;TIBETAN LETTER KA;Lo;0;L;;;;;N;;;;; 0F41;TIBETAN LETTER KHA;Lo;0;L;;;;;N;;;;; 0F42;TIBETAN LETTER GA;Lo;0;L;;;;;N;;;;; 0F43;TIBETAN LETTER GHA;Lo;0;L;0F42 0FB7;;;;N;;;;; 0F44;TIBETAN LETTER NGA;Lo;0;L;;;;;N;;;;; 0F45;TIBETAN LETTER CA;Lo;0;L;;;;;N;;;;; 0F46;TIBETAN LETTER CHA;Lo;0;L;;;;;N;;;;; 0F47;TIBETAN LETTER JA;Lo;0;L;;;;;N;;;;; 0F49;TIBETAN LETTER NYA;Lo;0;L;;;;;N;;;;; 0F4A;TIBETAN LETTER TTA;Lo;0;L;;;;;N;TIBETAN LETTER REVERSED TA;;;; 0F4B;TIBETAN LETTER TTHA;Lo;0;L;;;;;N;TIBETAN LETTER REVERSED THA;;;; 0F4C;TIBETAN LETTER DDA;Lo;0;L;;;;;N;TIBETAN LETTER REVERSED DA;;;; 0F4D;TIBETAN LETTER DDHA;Lo;0;L;0F4C 0FB7;;;;N;;;;; 0F4E;TIBETAN LETTER NNA;Lo;0;L;;;;;N;TIBETAN LETTER REVERSED NA;;;; 0F4F;TIBETAN LETTER TA;Lo;0;L;;;;;N;;;;; 0F50;TIBETAN LETTER THA;Lo;0;L;;;;;N;;;;; 0F51;TIBETAN LETTER DA;Lo;0;L;;;;;N;;;;; 0F52;TIBETAN LETTER DHA;Lo;0;L;0F51 0FB7;;;;N;;;;; 0F53;TIBETAN LETTER NA;Lo;0;L;;;;;N;;;;; 0F54;TIBETAN LETTER PA;Lo;0;L;;;;;N;;;;; 0F55;TIBETAN LETTER PHA;Lo;0;L;;;;;N;;;;; 0F56;TIBETAN LETTER BA;Lo;0;L;;;;;N;;;;; 0F57;TIBETAN LETTER BHA;Lo;0;L;0F56 0FB7;;;;N;;;;; 0F58;TIBETAN LETTER MA;Lo;0;L;;;;;N;;;;; 0F59;TIBETAN LETTER TSA;Lo;0;L;;;;;N;;;;; 0F5A;TIBETAN LETTER TSHA;Lo;0;L;;;;;N;;;;; 0F5B;TIBETAN LETTER DZA;Lo;0;L;;;;;N;;;;; 0F5C;TIBETAN LETTER DZHA;Lo;0;L;0F5B 0FB7;;;;N;;;;; 0F5D;TIBETAN LETTER WA;Lo;0;L;;;;;N;;;;; 0F5E;TIBETAN LETTER ZHA;Lo;0;L;;;;;N;;;;; 0F5F;TIBETAN LETTER ZA;Lo;0;L;;;;;N;;;;; 0F60;TIBETAN LETTER -A;Lo;0;L;;;;;N;TIBETAN LETTER AA;;;; 0F61;TIBETAN LETTER YA;Lo;0;L;;;;;N;;;;; 0F62;TIBETAN LETTER RA;Lo;0;L;;;;;N;;;;; 0F63;TIBETAN LETTER LA;Lo;0;L;;;;;N;;;;; 0F64;TIBETAN LETTER SHA;Lo;0;L;;;;;N;;;;; 0F65;TIBETAN LETTER SSA;Lo;0;L;;;;;N;TIBETAN LETTER REVERSED SHA;;;; 0F66;TIBETAN LETTER SA;Lo;0;L;;;;;N;;;;; 0F67;TIBETAN LETTER HA;Lo;0;L;;;;;N;;;;; 0F68;TIBETAN LETTER A;Lo;0;L;;;;;N;;;;; 0F69;TIBETAN LETTER KSSA;Lo;0;L;0F40 0FB5;;;;N;;;;; 0F6A;TIBETAN LETTER FIXED-FORM RA;Lo;0;L;;;;;N;;;;; 0F6B;TIBETAN LETTER KKA;Lo;0;L;;;;;N;;;;; 0F6C;TIBETAN LETTER RRA;Lo;0;L;;;;;N;;;;; 0F71;TIBETAN VOWEL SIGN AA;Mn;129;NSM;;;;;N;;;;; 0F72;TIBETAN VOWEL SIGN I;Mn;130;NSM;;;;;N;;;;; 0F73;TIBETAN VOWEL SIGN II;Mn;0;NSM;0F71 0F72;;;;N;;;;; 0F74;TIBETAN VOWEL SIGN U;Mn;132;NSM;;;;;N;;;;; 0F75;TIBETAN VOWEL SIGN UU;Mn;0;NSM;0F71 0F74;;;;N;;;;; 0F76;TIBETAN VOWEL SIGN VOCALIC R;Mn;0;NSM;0FB2 0F80;;;;N;;;;; 0F77;TIBETAN VOWEL SIGN VOCALIC RR;Mn;0;NSM; 0FB2 0F81;;;;N;;;;; 0F78;TIBETAN VOWEL SIGN VOCALIC L;Mn;0;NSM;0FB3 0F80;;;;N;;;;; 0F79;TIBETAN VOWEL SIGN VOCALIC LL;Mn;0;NSM; 0FB3 0F81;;;;N;;;;; 0F7A;TIBETAN VOWEL SIGN E;Mn;130;NSM;;;;;N;;;;; 0F7B;TIBETAN VOWEL SIGN EE;Mn;130;NSM;;;;;N;TIBETAN VOWEL SIGN AI;;;; 0F7C;TIBETAN VOWEL SIGN O;Mn;130;NSM;;;;;N;;;;; 0F7D;TIBETAN VOWEL SIGN OO;Mn;130;NSM;;;;;N;TIBETAN VOWEL SIGN AU;;;; 0F7E;TIBETAN SIGN RJES SU NGA RO;Mn;0;NSM;;;;;N;TIBETAN ANUSVARA;;;; 0F7F;TIBETAN SIGN RNAM BCAD;Mc;0;L;;;;;N;TIBETAN VISARGA;;;; 0F80;TIBETAN VOWEL SIGN REVERSED I;Mn;130;NSM;;;;;N;TIBETAN VOWEL SIGN SHORT I;;;; 0F81;TIBETAN VOWEL SIGN REVERSED II;Mn;0;NSM;0F71 0F80;;;;N;;;;; 0F82;TIBETAN SIGN NYI ZLA NAA DA;Mn;230;NSM;;;;;N;TIBETAN CANDRABINDU WITH ORNAMENT;;;; 0F83;TIBETAN SIGN SNA LDAN;Mn;230;NSM;;;;;N;TIBETAN CANDRABINDU;;;; 0F84;TIBETAN MARK HALANTA;Mn;9;NSM;;;;;N;TIBETAN VIRAMA;;;; 0F85;TIBETAN MARK PALUTA;Po;0;L;;;;;N;TIBETAN CHUCHENYIGE;;;; 0F86;TIBETAN SIGN LCI RTAGS;Mn;230;NSM;;;;;N;;;;; 0F87;TIBETAN SIGN YANG RTAGS;Mn;230;NSM;;;;;N;;;;; 0F88;TIBETAN SIGN LCE TSA CAN;Lo;0;L;;;;;N;;;;; 0F89;TIBETAN SIGN MCHU CAN;Lo;0;L;;;;;N;;;;; 0F8A;TIBETAN SIGN GRU CAN RGYINGS;Lo;0;L;;;;;N;;;;; 0F8B;TIBETAN SIGN GRU MED RGYINGS;Lo;0;L;;;;;N;;;;; 0F8C;TIBETAN SIGN INVERTED MCHU CAN;Lo;0;L;;;;;N;;;;; 0F8D;TIBETAN SUBJOINED SIGN LCE TSA CAN;Mn;0;NSM;;;;;N;;;;; 0F8E;TIBETAN SUBJOINED SIGN MCHU CAN;Mn;0;NSM;;;;;N;;;;; 0F8F;TIBETAN SUBJOINED SIGN INVERTED MCHU CAN;Mn;0;NSM;;;;;N;;;;; 0F90;TIBETAN SUBJOINED LETTER KA;Mn;0;NSM;;;;;N;;;;; 0F91;TIBETAN SUBJOINED LETTER KHA;Mn;0;NSM;;;;;N;;;;; 0F92;TIBETAN SUBJOINED LETTER GA;Mn;0;NSM;;;;;N;;;;; 0F93;TIBETAN SUBJOINED LETTER GHA;Mn;0;NSM;0F92 0FB7;;;;N;;;;; 0F94;TIBETAN SUBJOINED LETTER NGA;Mn;0;NSM;;;;;N;;;;; 0F95;TIBETAN SUBJOINED LETTER CA;Mn;0;NSM;;;;;N;;;;; 0F96;TIBETAN SUBJOINED LETTER CHA;Mn;0;NSM;;;;;N;;;;; 0F97;TIBETAN SUBJOINED LETTER JA;Mn;0;NSM;;;;;N;;;;; 0F99;TIBETAN SUBJOINED LETTER NYA;Mn;0;NSM;;;;;N;;;;; 0F9A;TIBETAN SUBJOINED LETTER TTA;Mn;0;NSM;;;;;N;;;;; 0F9B;TIBETAN SUBJOINED LETTER TTHA;Mn;0;NSM;;;;;N;;;;; 0F9C;TIBETAN SUBJOINED LETTER DDA;Mn;0;NSM;;;;;N;;;;; 0F9D;TIBETAN SUBJOINED LETTER DDHA;Mn;0;NSM;0F9C 0FB7;;;;N;;;;; 0F9E;TIBETAN SUBJOINED LETTER NNA;Mn;0;NSM;;;;;N;;;;; 0F9F;TIBETAN SUBJOINED LETTER TA;Mn;0;NSM;;;;;N;;;;; 0FA0;TIBETAN SUBJOINED LETTER THA;Mn;0;NSM;;;;;N;;;;; 0FA1;TIBETAN SUBJOINED LETTER DA;Mn;0;NSM;;;;;N;;;;; 0FA2;TIBETAN SUBJOINED LETTER DHA;Mn;0;NSM;0FA1 0FB7;;;;N;;;;; 0FA3;TIBETAN SUBJOINED LETTER NA;Mn;0;NSM;;;;;N;;;;; 0FA4;TIBETAN SUBJOINED LETTER PA;Mn;0;NSM;;;;;N;;;;; 0FA5;TIBETAN SUBJOINED LETTER PHA;Mn;0;NSM;;;;;N;;;;; 0FA6;TIBETAN SUBJOINED LETTER BA;Mn;0;NSM;;;;;N;;;;; 0FA7;TIBETAN SUBJOINED LETTER BHA;Mn;0;NSM;0FA6 0FB7;;;;N;;;;; 0FA8;TIBETAN SUBJOINED LETTER MA;Mn;0;NSM;;;;;N;;;;; 0FA9;TIBETAN SUBJOINED LETTER TSA;Mn;0;NSM;;;;;N;;;;; 0FAA;TIBETAN SUBJOINED LETTER TSHA;Mn;0;NSM;;;;;N;;;;; 0FAB;TIBETAN SUBJOINED LETTER DZA;Mn;0;NSM;;;;;N;;;;; 0FAC;TIBETAN SUBJOINED LETTER DZHA;Mn;0;NSM;0FAB 0FB7;;;;N;;;;; 0FAD;TIBETAN SUBJOINED LETTER WA;Mn;0;NSM;;;;;N;;;;; 0FAE;TIBETAN SUBJOINED LETTER ZHA;Mn;0;NSM;;;;;N;;;;; 0FAF;TIBETAN SUBJOINED LETTER ZA;Mn;0;NSM;;;;;N;;;;; 0FB0;TIBETAN SUBJOINED LETTER -A;Mn;0;NSM;;;;;N;;;;; 0FB1;TIBETAN SUBJOINED LETTER YA;Mn;0;NSM;;;;;N;;;;; 0FB2;TIBETAN SUBJOINED LETTER RA;Mn;0;NSM;;;;;N;;;;; 0FB3;TIBETAN SUBJOINED LETTER LA;Mn;0;NSM;;;;;N;;;;; 0FB4;TIBETAN SUBJOINED LETTER SHA;Mn;0;NSM;;;;;N;;;;; 0FB5;TIBETAN SUBJOINED LETTER SSA;Mn;0;NSM;;;;;N;;;;; 0FB6;TIBETAN SUBJOINED LETTER SA;Mn;0;NSM;;;;;N;;;;; 0FB7;TIBETAN SUBJOINED LETTER HA;Mn;0;NSM;;;;;N;;;;; 0FB8;TIBETAN SUBJOINED LETTER A;Mn;0;NSM;;;;;N;;;;; 0FB9;TIBETAN SUBJOINED LETTER KSSA;Mn;0;NSM;0F90 0FB5;;;;N;;;;; 0FBA;TIBETAN SUBJOINED LETTER FIXED-FORM WA;Mn;0;NSM;;;;;N;;;;; 0FBB;TIBETAN SUBJOINED LETTER FIXED-FORM YA;Mn;0;NSM;;;;;N;;;;; 0FBC;TIBETAN SUBJOINED LETTER FIXED-FORM RA;Mn;0;NSM;;;;;N;;;;; 0FBE;TIBETAN KU RU KHA;So;0;L;;;;;N;;;;; 0FBF;TIBETAN KU RU KHA BZHI MIG CAN;So;0;L;;;;;N;;;;; 0FC0;TIBETAN CANTILLATION SIGN HEAVY BEAT;So;0;L;;;;;N;;;;; 0FC1;TIBETAN CANTILLATION SIGN LIGHT BEAT;So;0;L;;;;;N;;;;; 0FC2;TIBETAN CANTILLATION SIGN CANG TE-U;So;0;L;;;;;N;;;;; 0FC3;TIBETAN CANTILLATION SIGN SBUB -CHAL;So;0;L;;;;;N;;;;; 0FC4;TIBETAN SYMBOL DRIL BU;So;0;L;;;;;N;;;;; 0FC5;TIBETAN SYMBOL RDO RJE;So;0;L;;;;;N;;;;; 0FC6;TIBETAN SYMBOL PADMA GDAN;Mn;220;NSM;;;;;N;;;;; 0FC7;TIBETAN SYMBOL RDO RJE RGYA GRAM;So;0;L;;;;;N;;;;; 0FC8;TIBETAN SYMBOL PHUR PA;So;0;L;;;;;N;;;;; 0FC9;TIBETAN SYMBOL NOR BU;So;0;L;;;;;N;;;;; 0FCA;TIBETAN SYMBOL NOR BU NYIS -KHYIL;So;0;L;;;;;N;;;;; 0FCB;TIBETAN SYMBOL NOR BU GSUM -KHYIL;So;0;L;;;;;N;;;;; 0FCC;TIBETAN SYMBOL NOR BU BZHI -KHYIL;So;0;L;;;;;N;;;;; 0FCE;TIBETAN SIGN RDEL NAG RDEL DKAR;So;0;L;;;;;N;;;;; 0FCF;TIBETAN SIGN RDEL NAG GSUM;So;0;L;;;;;N;;;;; 0FD0;TIBETAN MARK BSKA- SHOG GI MGO RGYAN;Po;0;L;;;;;N;;;;; 0FD1;TIBETAN MARK MNYAM YIG GI MGO RGYAN;Po;0;L;;;;;N;;;;; 0FD2;TIBETAN MARK NYIS TSHEG;Po;0;L;;;;;N;;;;; 0FD3;TIBETAN MARK INITIAL BRDA RNYING YIG MGO MDUN MA;Po;0;L;;;;;N;;;;; 0FD4;TIBETAN MARK CLOSING BRDA RNYING YIG MGO SGAB MA;Po;0;L;;;;;N;;;;; 0FD5;RIGHT-FACING SVASTI SIGN;So;0;L;;;;;N;;;;; 0FD6;LEFT-FACING SVASTI SIGN;So;0;L;;;;;N;;;;; 0FD7;RIGHT-FACING SVASTI SIGN WITH DOTS;So;0;L;;;;;N;;;;; 0FD8;LEFT-FACING SVASTI SIGN WITH DOTS;So;0;L;;;;;N;;;;; 0FD9;TIBETAN MARK LEADING MCHAN RTAGS;Po;0;L;;;;;N;;;;; 0FDA;TIBETAN MARK TRAILING MCHAN RTAGS;Po;0;L;;;;;N;;;;; 1000;MYANMAR LETTER KA;Lo;0;L;;;;;N;;;;; 1001;MYANMAR LETTER KHA;Lo;0;L;;;;;N;;;;; 1002;MYANMAR LETTER GA;Lo;0;L;;;;;N;;;;; 1003;MYANMAR LETTER GHA;Lo;0;L;;;;;N;;;;; 1004;MYANMAR LETTER NGA;Lo;0;L;;;;;N;;;;; 1005;MYANMAR LETTER CA;Lo;0;L;;;;;N;;;;; 1006;MYANMAR LETTER CHA;Lo;0;L;;;;;N;;;;; 1007;MYANMAR LETTER JA;Lo;0;L;;;;;N;;;;; 1008;MYANMAR LETTER JHA;Lo;0;L;;;;;N;;;;; 1009;MYANMAR LETTER NYA;Lo;0;L;;;;;N;;;;; 100A;MYANMAR LETTER NNYA;Lo;0;L;;;;;N;;;;; 100B;MYANMAR LETTER TTA;Lo;0;L;;;;;N;;;;; 100C;MYANMAR LETTER TTHA;Lo;0;L;;;;;N;;;;; 100D;MYANMAR LETTER DDA;Lo;0;L;;;;;N;;;;; 100E;MYANMAR LETTER DDHA;Lo;0;L;;;;;N;;;;; 100F;MYANMAR LETTER NNA;Lo;0;L;;;;;N;;;;; 1010;MYANMAR LETTER TA;Lo;0;L;;;;;N;;;;; 1011;MYANMAR LETTER THA;Lo;0;L;;;;;N;;;;; 1012;MYANMAR LETTER DA;Lo;0;L;;;;;N;;;;; 1013;MYANMAR LETTER DHA;Lo;0;L;;;;;N;;;;; 1014;MYANMAR LETTER NA;Lo;0;L;;;;;N;;;;; 1015;MYANMAR LETTER PA;Lo;0;L;;;;;N;;;;; 1016;MYANMAR LETTER PHA;Lo;0;L;;;;;N;;;;; 1017;MYANMAR LETTER BA;Lo;0;L;;;;;N;;;;; 1018;MYANMAR LETTER BHA;Lo;0;L;;;;;N;;;;; 1019;MYANMAR LETTER MA;Lo;0;L;;;;;N;;;;; 101A;MYANMAR LETTER YA;Lo;0;L;;;;;N;;;;; 101B;MYANMAR LETTER RA;Lo;0;L;;;;;N;;;;; 101C;MYANMAR LETTER LA;Lo;0;L;;;;;N;;;;; 101D;MYANMAR LETTER WA;Lo;0;L;;;;;N;;;;; 101E;MYANMAR LETTER SA;Lo;0;L;;;;;N;;;;; 101F;MYANMAR LETTER HA;Lo;0;L;;;;;N;;;;; 1020;MYANMAR LETTER LLA;Lo;0;L;;;;;N;;;;; 1021;MYANMAR LETTER A;Lo;0;L;;;;;N;;;;; 1022;MYANMAR LETTER SHAN A;Lo;0;L;;;;;N;;;;; 1023;MYANMAR LETTER I;Lo;0;L;;;;;N;;;;; 1024;MYANMAR LETTER II;Lo;0;L;;;;;N;;;;; 1025;MYANMAR LETTER U;Lo;0;L;;;;;N;;;;; 1026;MYANMAR LETTER UU;Lo;0;L;1025 102E;;;;N;;;;; 1027;MYANMAR LETTER E;Lo;0;L;;;;;N;;;;; 1028;MYANMAR LETTER MON E;Lo;0;L;;;;;N;;;;; 1029;MYANMAR LETTER O;Lo;0;L;;;;;N;;;;; 102A;MYANMAR LETTER AU;Lo;0;L;;;;;N;;;;; 102B;MYANMAR VOWEL SIGN TALL AA;Mc;0;L;;;;;N;;;;; 102C;MYANMAR VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 102D;MYANMAR VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; 102E;MYANMAR VOWEL SIGN II;Mn;0;NSM;;;;;N;;;;; 102F;MYANMAR VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 1030;MYANMAR VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 1031;MYANMAR VOWEL SIGN E;Mc;0;L;;;;;N;;;;; 1032;MYANMAR VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;; 1033;MYANMAR VOWEL SIGN MON II;Mn;0;NSM;;;;;N;;;;; 1034;MYANMAR VOWEL SIGN MON O;Mn;0;NSM;;;;;N;;;;; 1035;MYANMAR VOWEL SIGN E ABOVE;Mn;0;NSM;;;;;N;;;;; 1036;MYANMAR SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; 1037;MYANMAR SIGN DOT BELOW;Mn;7;NSM;;;;;N;;;;; 1038;MYANMAR SIGN VISARGA;Mc;0;L;;;;;N;;;;; 1039;MYANMAR SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 103A;MYANMAR SIGN ASAT;Mn;9;NSM;;;;;N;;;;; 103B;MYANMAR CONSONANT SIGN MEDIAL YA;Mc;0;L;;;;;N;;;;; 103C;MYANMAR CONSONANT SIGN MEDIAL RA;Mc;0;L;;;;;N;;;;; 103D;MYANMAR CONSONANT SIGN MEDIAL WA;Mn;0;NSM;;;;;N;;;;; 103E;MYANMAR CONSONANT SIGN MEDIAL HA;Mn;0;NSM;;;;;N;;;;; 103F;MYANMAR LETTER GREAT SA;Lo;0;L;;;;;N;;;;; 1040;MYANMAR DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 1041;MYANMAR DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 1042;MYANMAR DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 1043;MYANMAR DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 1044;MYANMAR DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 1045;MYANMAR DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 1046;MYANMAR DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 1047;MYANMAR DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 1048;MYANMAR DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 1049;MYANMAR DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 104A;MYANMAR SIGN LITTLE SECTION;Po;0;L;;;;;N;;;;; 104B;MYANMAR SIGN SECTION;Po;0;L;;;;;N;;;;; 104C;MYANMAR SYMBOL LOCATIVE;Po;0;L;;;;;N;;;;; 104D;MYANMAR SYMBOL COMPLETED;Po;0;L;;;;;N;;;;; 104E;MYANMAR SYMBOL AFOREMENTIONED;Po;0;L;;;;;N;;;;; 104F;MYANMAR SYMBOL GENITIVE;Po;0;L;;;;;N;;;;; 1050;MYANMAR LETTER SHA;Lo;0;L;;;;;N;;;;; 1051;MYANMAR LETTER SSA;Lo;0;L;;;;;N;;;;; 1052;MYANMAR LETTER VOCALIC R;Lo;0;L;;;;;N;;;;; 1053;MYANMAR LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 1054;MYANMAR LETTER VOCALIC L;Lo;0;L;;;;;N;;;;; 1055;MYANMAR LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; 1056;MYANMAR VOWEL SIGN VOCALIC R;Mc;0;L;;;;;N;;;;; 1057;MYANMAR VOWEL SIGN VOCALIC RR;Mc;0;L;;;;;N;;;;; 1058;MYANMAR VOWEL SIGN VOCALIC L;Mn;0;NSM;;;;;N;;;;; 1059;MYANMAR VOWEL SIGN VOCALIC LL;Mn;0;NSM;;;;;N;;;;; 105A;MYANMAR LETTER MON NGA;Lo;0;L;;;;;N;;;;; 105B;MYANMAR LETTER MON JHA;Lo;0;L;;;;;N;;;;; 105C;MYANMAR LETTER MON BBA;Lo;0;L;;;;;N;;;;; 105D;MYANMAR LETTER MON BBE;Lo;0;L;;;;;N;;;;; 105E;MYANMAR CONSONANT SIGN MON MEDIAL NA;Mn;0;NSM;;;;;N;;;;; 105F;MYANMAR CONSONANT SIGN MON MEDIAL MA;Mn;0;NSM;;;;;N;;;;; 1060;MYANMAR CONSONANT SIGN MON MEDIAL LA;Mn;0;NSM;;;;;N;;;;; 1061;MYANMAR LETTER SGAW KAREN SHA;Lo;0;L;;;;;N;;;;; 1062;MYANMAR VOWEL SIGN SGAW KAREN EU;Mc;0;L;;;;;N;;;;; 1063;MYANMAR TONE MARK SGAW KAREN HATHI;Mc;0;L;;;;;N;;;;; 1064;MYANMAR TONE MARK SGAW KAREN KE PHO;Mc;0;L;;;;;N;;;;; 1065;MYANMAR LETTER WESTERN PWO KAREN THA;Lo;0;L;;;;;N;;;;; 1066;MYANMAR LETTER WESTERN PWO KAREN PWA;Lo;0;L;;;;;N;;;;; 1067;MYANMAR VOWEL SIGN WESTERN PWO KAREN EU;Mc;0;L;;;;;N;;;;; 1068;MYANMAR VOWEL SIGN WESTERN PWO KAREN UE;Mc;0;L;;;;;N;;;;; 1069;MYANMAR SIGN WESTERN PWO KAREN TONE-1;Mc;0;L;;;;;N;;;;; 106A;MYANMAR SIGN WESTERN PWO KAREN TONE-2;Mc;0;L;;;;;N;;;;; 106B;MYANMAR SIGN WESTERN PWO KAREN TONE-3;Mc;0;L;;;;;N;;;;; 106C;MYANMAR SIGN WESTERN PWO KAREN TONE-4;Mc;0;L;;;;;N;;;;; 106D;MYANMAR SIGN WESTERN PWO KAREN TONE-5;Mc;0;L;;;;;N;;;;; 106E;MYANMAR LETTER EASTERN PWO KAREN NNA;Lo;0;L;;;;;N;;;;; 106F;MYANMAR LETTER EASTERN PWO KAREN YWA;Lo;0;L;;;;;N;;;;; 1070;MYANMAR LETTER EASTERN PWO KAREN GHWA;Lo;0;L;;;;;N;;;;; 1071;MYANMAR VOWEL SIGN GEBA KAREN I;Mn;0;NSM;;;;;N;;;;; 1072;MYANMAR VOWEL SIGN KAYAH OE;Mn;0;NSM;;;;;N;;;;; 1073;MYANMAR VOWEL SIGN KAYAH U;Mn;0;NSM;;;;;N;;;;; 1074;MYANMAR VOWEL SIGN KAYAH EE;Mn;0;NSM;;;;;N;;;;; 1075;MYANMAR LETTER SHAN KA;Lo;0;L;;;;;N;;;;; 1076;MYANMAR LETTER SHAN KHA;Lo;0;L;;;;;N;;;;; 1077;MYANMAR LETTER SHAN GA;Lo;0;L;;;;;N;;;;; 1078;MYANMAR LETTER SHAN CA;Lo;0;L;;;;;N;;;;; 1079;MYANMAR LETTER SHAN ZA;Lo;0;L;;;;;N;;;;; 107A;MYANMAR LETTER SHAN NYA;Lo;0;L;;;;;N;;;;; 107B;MYANMAR LETTER SHAN DA;Lo;0;L;;;;;N;;;;; 107C;MYANMAR LETTER SHAN NA;Lo;0;L;;;;;N;;;;; 107D;MYANMAR LETTER SHAN PHA;Lo;0;L;;;;;N;;;;; 107E;MYANMAR LETTER SHAN FA;Lo;0;L;;;;;N;;;;; 107F;MYANMAR LETTER SHAN BA;Lo;0;L;;;;;N;;;;; 1080;MYANMAR LETTER SHAN THA;Lo;0;L;;;;;N;;;;; 1081;MYANMAR LETTER SHAN HA;Lo;0;L;;;;;N;;;;; 1082;MYANMAR CONSONANT SIGN SHAN MEDIAL WA;Mn;0;NSM;;;;;N;;;;; 1083;MYANMAR VOWEL SIGN SHAN AA;Mc;0;L;;;;;N;;;;; 1084;MYANMAR VOWEL SIGN SHAN E;Mc;0;L;;;;;N;;;;; 1085;MYANMAR VOWEL SIGN SHAN E ABOVE;Mn;0;NSM;;;;;N;;;;; 1086;MYANMAR VOWEL SIGN SHAN FINAL Y;Mn;0;NSM;;;;;N;;;;; 1087;MYANMAR SIGN SHAN TONE-2;Mc;0;L;;;;;N;;;;; 1088;MYANMAR SIGN SHAN TONE-3;Mc;0;L;;;;;N;;;;; 1089;MYANMAR SIGN SHAN TONE-5;Mc;0;L;;;;;N;;;;; 108A;MYANMAR SIGN SHAN TONE-6;Mc;0;L;;;;;N;;;;; 108B;MYANMAR SIGN SHAN COUNCIL TONE-2;Mc;0;L;;;;;N;;;;; 108C;MYANMAR SIGN SHAN COUNCIL TONE-3;Mc;0;L;;;;;N;;;;; 108D;MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE;Mn;220;NSM;;;;;N;;;;; 108E;MYANMAR LETTER RUMAI PALAUNG FA;Lo;0;L;;;;;N;;;;; 108F;MYANMAR SIGN RUMAI PALAUNG TONE-5;Mc;0;L;;;;;N;;;;; 1090;MYANMAR SHAN DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 1091;MYANMAR SHAN DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 1092;MYANMAR SHAN DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 1093;MYANMAR SHAN DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 1094;MYANMAR SHAN DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 1095;MYANMAR SHAN DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 1096;MYANMAR SHAN DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 1097;MYANMAR SHAN DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 1098;MYANMAR SHAN DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 1099;MYANMAR SHAN DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 109A;MYANMAR SIGN KHAMTI TONE-1;Mc;0;L;;;;;N;;;;; 109B;MYANMAR SIGN KHAMTI TONE-3;Mc;0;L;;;;;N;;;;; 109C;MYANMAR VOWEL SIGN AITON A;Mc;0;L;;;;;N;;;;; 109D;MYANMAR VOWEL SIGN AITON AI;Mn;0;NSM;;;;;N;;;;; 109E;MYANMAR SYMBOL SHAN ONE;So;0;L;;;;;N;;;;; 109F;MYANMAR SYMBOL SHAN EXCLAMATION;So;0;L;;;;;N;;;;; 10A0;GEORGIAN CAPITAL LETTER AN;Lu;0;L;;;;;N;;;;2D00; 10A1;GEORGIAN CAPITAL LETTER BAN;Lu;0;L;;;;;N;;;;2D01; 10A2;GEORGIAN CAPITAL LETTER GAN;Lu;0;L;;;;;N;;;;2D02; 10A3;GEORGIAN CAPITAL LETTER DON;Lu;0;L;;;;;N;;;;2D03; 10A4;GEORGIAN CAPITAL LETTER EN;Lu;0;L;;;;;N;;;;2D04; 10A5;GEORGIAN CAPITAL LETTER VIN;Lu;0;L;;;;;N;;;;2D05; 10A6;GEORGIAN CAPITAL LETTER ZEN;Lu;0;L;;;;;N;;;;2D06; 10A7;GEORGIAN CAPITAL LETTER TAN;Lu;0;L;;;;;N;;;;2D07; 10A8;GEORGIAN CAPITAL LETTER IN;Lu;0;L;;;;;N;;;;2D08; 10A9;GEORGIAN CAPITAL LETTER KAN;Lu;0;L;;;;;N;;;;2D09; 10AA;GEORGIAN CAPITAL LETTER LAS;Lu;0;L;;;;;N;;;;2D0A; 10AB;GEORGIAN CAPITAL LETTER MAN;Lu;0;L;;;;;N;;;;2D0B; 10AC;GEORGIAN CAPITAL LETTER NAR;Lu;0;L;;;;;N;;;;2D0C; 10AD;GEORGIAN CAPITAL LETTER ON;Lu;0;L;;;;;N;;;;2D0D; 10AE;GEORGIAN CAPITAL LETTER PAR;Lu;0;L;;;;;N;;;;2D0E; 10AF;GEORGIAN CAPITAL LETTER ZHAR;Lu;0;L;;;;;N;;;;2D0F; 10B0;GEORGIAN CAPITAL LETTER RAE;Lu;0;L;;;;;N;;;;2D10; 10B1;GEORGIAN CAPITAL LETTER SAN;Lu;0;L;;;;;N;;;;2D11; 10B2;GEORGIAN CAPITAL LETTER TAR;Lu;0;L;;;;;N;;;;2D12; 10B3;GEORGIAN CAPITAL LETTER UN;Lu;0;L;;;;;N;;;;2D13; 10B4;GEORGIAN CAPITAL LETTER PHAR;Lu;0;L;;;;;N;;;;2D14; 10B5;GEORGIAN CAPITAL LETTER KHAR;Lu;0;L;;;;;N;;;;2D15; 10B6;GEORGIAN CAPITAL LETTER GHAN;Lu;0;L;;;;;N;;;;2D16; 10B7;GEORGIAN CAPITAL LETTER QAR;Lu;0;L;;;;;N;;;;2D17; 10B8;GEORGIAN CAPITAL LETTER SHIN;Lu;0;L;;;;;N;;;;2D18; 10B9;GEORGIAN CAPITAL LETTER CHIN;Lu;0;L;;;;;N;;;;2D19; 10BA;GEORGIAN CAPITAL LETTER CAN;Lu;0;L;;;;;N;;;;2D1A; 10BB;GEORGIAN CAPITAL LETTER JIL;Lu;0;L;;;;;N;;;;2D1B; 10BC;GEORGIAN CAPITAL LETTER CIL;Lu;0;L;;;;;N;;;;2D1C; 10BD;GEORGIAN CAPITAL LETTER CHAR;Lu;0;L;;;;;N;;;;2D1D; 10BE;GEORGIAN CAPITAL LETTER XAN;Lu;0;L;;;;;N;;;;2D1E; 10BF;GEORGIAN CAPITAL LETTER JHAN;Lu;0;L;;;;;N;;;;2D1F; 10C0;GEORGIAN CAPITAL LETTER HAE;Lu;0;L;;;;;N;;;;2D20; 10C1;GEORGIAN CAPITAL LETTER HE;Lu;0;L;;;;;N;;;;2D21; 10C2;GEORGIAN CAPITAL LETTER HIE;Lu;0;L;;;;;N;;;;2D22; 10C3;GEORGIAN CAPITAL LETTER WE;Lu;0;L;;;;;N;;;;2D23; 10C4;GEORGIAN CAPITAL LETTER HAR;Lu;0;L;;;;;N;;;;2D24; 10C5;GEORGIAN CAPITAL LETTER HOE;Lu;0;L;;;;;N;;;;2D25; 10C7;GEORGIAN CAPITAL LETTER YN;Lu;0;L;;;;;N;;;;2D27; 10CD;GEORGIAN CAPITAL LETTER AEN;Lu;0;L;;;;;N;;;;2D2D; 10D0;GEORGIAN LETTER AN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER AN;;;; 10D1;GEORGIAN LETTER BAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER BAN;;;; 10D2;GEORGIAN LETTER GAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER GAN;;;; 10D3;GEORGIAN LETTER DON;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER DON;;;; 10D4;GEORGIAN LETTER EN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER EN;;;; 10D5;GEORGIAN LETTER VIN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER VIN;;;; 10D6;GEORGIAN LETTER ZEN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER ZEN;;;; 10D7;GEORGIAN LETTER TAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER TAN;;;; 10D8;GEORGIAN LETTER IN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER IN;;;; 10D9;GEORGIAN LETTER KAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER KAN;;;; 10DA;GEORGIAN LETTER LAS;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER LAS;;;; 10DB;GEORGIAN LETTER MAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER MAN;;;; 10DC;GEORGIAN LETTER NAR;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER NAR;;;; 10DD;GEORGIAN LETTER ON;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER ON;;;; 10DE;GEORGIAN LETTER PAR;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER PAR;;;; 10DF;GEORGIAN LETTER ZHAR;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER ZHAR;;;; 10E0;GEORGIAN LETTER RAE;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER RAE;;;; 10E1;GEORGIAN LETTER SAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER SAN;;;; 10E2;GEORGIAN LETTER TAR;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER TAR;;;; 10E3;GEORGIAN LETTER UN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER UN;;;; 10E4;GEORGIAN LETTER PHAR;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER PHAR;;;; 10E5;GEORGIAN LETTER KHAR;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER KHAR;;;; 10E6;GEORGIAN LETTER GHAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER GHAN;;;; 10E7;GEORGIAN LETTER QAR;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER QAR;;;; 10E8;GEORGIAN LETTER SHIN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER SHIN;;;; 10E9;GEORGIAN LETTER CHIN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER CHIN;;;; 10EA;GEORGIAN LETTER CAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER CAN;;;; 10EB;GEORGIAN LETTER JIL;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER JIL;;;; 10EC;GEORGIAN LETTER CIL;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER CIL;;;; 10ED;GEORGIAN LETTER CHAR;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER CHAR;;;; 10EE;GEORGIAN LETTER XAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER XAN;;;; 10EF;GEORGIAN LETTER JHAN;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER JHAN;;;; 10F0;GEORGIAN LETTER HAE;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER HAE;;;; 10F1;GEORGIAN LETTER HE;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER HE;;;; 10F2;GEORGIAN LETTER HIE;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER HIE;;;; 10F3;GEORGIAN LETTER WE;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER WE;;;; 10F4;GEORGIAN LETTER HAR;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER HAR;;;; 10F5;GEORGIAN LETTER HOE;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER HOE;;;; 10F6;GEORGIAN LETTER FI;Lo;0;L;;;;;N;GEORGIAN SMALL LETTER FI;;;; 10F7;GEORGIAN LETTER YN;Lo;0;L;;;;;N;;;;; 10F8;GEORGIAN LETTER ELIFI;Lo;0;L;;;;;N;;;;; 10F9;GEORGIAN LETTER TURNED GAN;Lo;0;L;;;;;N;;;;; 10FA;GEORGIAN LETTER AIN;Lo;0;L;;;;;N;;;;; 10FB;GEORGIAN PARAGRAPH SEPARATOR;Po;0;L;;;;;N;;;;; 10FC;MODIFIER LETTER GEORGIAN NAR;Lm;0;L; 10DC;;;;N;;;;; 10FD;GEORGIAN LETTER AEN;Lo;0;L;;;;;N;;;;; 10FE;GEORGIAN LETTER HARD SIGN;Lo;0;L;;;;;N;;;;; 10FF;GEORGIAN LETTER LABIAL SIGN;Lo;0;L;;;;;N;;;;; 1100;HANGUL CHOSEONG KIYEOK;Lo;0;L;;;;;N;;;;; 1101;HANGUL CHOSEONG SSANGKIYEOK;Lo;0;L;;;;;N;;;;; 1102;HANGUL CHOSEONG NIEUN;Lo;0;L;;;;;N;;;;; 1103;HANGUL CHOSEONG TIKEUT;Lo;0;L;;;;;N;;;;; 1104;HANGUL CHOSEONG SSANGTIKEUT;Lo;0;L;;;;;N;;;;; 1105;HANGUL CHOSEONG RIEUL;Lo;0;L;;;;;N;;;;; 1106;HANGUL CHOSEONG MIEUM;Lo;0;L;;;;;N;;;;; 1107;HANGUL CHOSEONG PIEUP;Lo;0;L;;;;;N;;;;; 1108;HANGUL CHOSEONG SSANGPIEUP;Lo;0;L;;;;;N;;;;; 1109;HANGUL CHOSEONG SIOS;Lo;0;L;;;;;N;;;;; 110A;HANGUL CHOSEONG SSANGSIOS;Lo;0;L;;;;;N;;;;; 110B;HANGUL CHOSEONG IEUNG;Lo;0;L;;;;;N;;;;; 110C;HANGUL CHOSEONG CIEUC;Lo;0;L;;;;;N;;;;; 110D;HANGUL CHOSEONG SSANGCIEUC;Lo;0;L;;;;;N;;;;; 110E;HANGUL CHOSEONG CHIEUCH;Lo;0;L;;;;;N;;;;; 110F;HANGUL CHOSEONG KHIEUKH;Lo;0;L;;;;;N;;;;; 1110;HANGUL CHOSEONG THIEUTH;Lo;0;L;;;;;N;;;;; 1111;HANGUL CHOSEONG PHIEUPH;Lo;0;L;;;;;N;;;;; 1112;HANGUL CHOSEONG HIEUH;Lo;0;L;;;;;N;;;;; 1113;HANGUL CHOSEONG NIEUN-KIYEOK;Lo;0;L;;;;;N;;;;; 1114;HANGUL CHOSEONG SSANGNIEUN;Lo;0;L;;;;;N;;;;; 1115;HANGUL CHOSEONG NIEUN-TIKEUT;Lo;0;L;;;;;N;;;;; 1116;HANGUL CHOSEONG NIEUN-PIEUP;Lo;0;L;;;;;N;;;;; 1117;HANGUL CHOSEONG TIKEUT-KIYEOK;Lo;0;L;;;;;N;;;;; 1118;HANGUL CHOSEONG RIEUL-NIEUN;Lo;0;L;;;;;N;;;;; 1119;HANGUL CHOSEONG SSANGRIEUL;Lo;0;L;;;;;N;;;;; 111A;HANGUL CHOSEONG RIEUL-HIEUH;Lo;0;L;;;;;N;;;;; 111B;HANGUL CHOSEONG KAPYEOUNRIEUL;Lo;0;L;;;;;N;;;;; 111C;HANGUL CHOSEONG MIEUM-PIEUP;Lo;0;L;;;;;N;;;;; 111D;HANGUL CHOSEONG KAPYEOUNMIEUM;Lo;0;L;;;;;N;;;;; 111E;HANGUL CHOSEONG PIEUP-KIYEOK;Lo;0;L;;;;;N;;;;; 111F;HANGUL CHOSEONG PIEUP-NIEUN;Lo;0;L;;;;;N;;;;; 1120;HANGUL CHOSEONG PIEUP-TIKEUT;Lo;0;L;;;;;N;;;;; 1121;HANGUL CHOSEONG PIEUP-SIOS;Lo;0;L;;;;;N;;;;; 1122;HANGUL CHOSEONG PIEUP-SIOS-KIYEOK;Lo;0;L;;;;;N;;;;; 1123;HANGUL CHOSEONG PIEUP-SIOS-TIKEUT;Lo;0;L;;;;;N;;;;; 1124;HANGUL CHOSEONG PIEUP-SIOS-PIEUP;Lo;0;L;;;;;N;;;;; 1125;HANGUL CHOSEONG PIEUP-SSANGSIOS;Lo;0;L;;;;;N;;;;; 1126;HANGUL CHOSEONG PIEUP-SIOS-CIEUC;Lo;0;L;;;;;N;;;;; 1127;HANGUL CHOSEONG PIEUP-CIEUC;Lo;0;L;;;;;N;;;;; 1128;HANGUL CHOSEONG PIEUP-CHIEUCH;Lo;0;L;;;;;N;;;;; 1129;HANGUL CHOSEONG PIEUP-THIEUTH;Lo;0;L;;;;;N;;;;; 112A;HANGUL CHOSEONG PIEUP-PHIEUPH;Lo;0;L;;;;;N;;;;; 112B;HANGUL CHOSEONG KAPYEOUNPIEUP;Lo;0;L;;;;;N;;;;; 112C;HANGUL CHOSEONG KAPYEOUNSSANGPIEUP;Lo;0;L;;;;;N;;;;; 112D;HANGUL CHOSEONG SIOS-KIYEOK;Lo;0;L;;;;;N;;;;; 112E;HANGUL CHOSEONG SIOS-NIEUN;Lo;0;L;;;;;N;;;;; 112F;HANGUL CHOSEONG SIOS-TIKEUT;Lo;0;L;;;;;N;;;;; 1130;HANGUL CHOSEONG SIOS-RIEUL;Lo;0;L;;;;;N;;;;; 1131;HANGUL CHOSEONG SIOS-MIEUM;Lo;0;L;;;;;N;;;;; 1132;HANGUL CHOSEONG SIOS-PIEUP;Lo;0;L;;;;;N;;;;; 1133;HANGUL CHOSEONG SIOS-PIEUP-KIYEOK;Lo;0;L;;;;;N;;;;; 1134;HANGUL CHOSEONG SIOS-SSANGSIOS;Lo;0;L;;;;;N;;;;; 1135;HANGUL CHOSEONG SIOS-IEUNG;Lo;0;L;;;;;N;;;;; 1136;HANGUL CHOSEONG SIOS-CIEUC;Lo;0;L;;;;;N;;;;; 1137;HANGUL CHOSEONG SIOS-CHIEUCH;Lo;0;L;;;;;N;;;;; 1138;HANGUL CHOSEONG SIOS-KHIEUKH;Lo;0;L;;;;;N;;;;; 1139;HANGUL CHOSEONG SIOS-THIEUTH;Lo;0;L;;;;;N;;;;; 113A;HANGUL CHOSEONG SIOS-PHIEUPH;Lo;0;L;;;;;N;;;;; 113B;HANGUL CHOSEONG SIOS-HIEUH;Lo;0;L;;;;;N;;;;; 113C;HANGUL CHOSEONG CHITUEUMSIOS;Lo;0;L;;;;;N;;;;; 113D;HANGUL CHOSEONG CHITUEUMSSANGSIOS;Lo;0;L;;;;;N;;;;; 113E;HANGUL CHOSEONG CEONGCHIEUMSIOS;Lo;0;L;;;;;N;;;;; 113F;HANGUL CHOSEONG CEONGCHIEUMSSANGSIOS;Lo;0;L;;;;;N;;;;; 1140;HANGUL CHOSEONG PANSIOS;Lo;0;L;;;;;N;;;;; 1141;HANGUL CHOSEONG IEUNG-KIYEOK;Lo;0;L;;;;;N;;;;; 1142;HANGUL CHOSEONG IEUNG-TIKEUT;Lo;0;L;;;;;N;;;;; 1143;HANGUL CHOSEONG IEUNG-MIEUM;Lo;0;L;;;;;N;;;;; 1144;HANGUL CHOSEONG IEUNG-PIEUP;Lo;0;L;;;;;N;;;;; 1145;HANGUL CHOSEONG IEUNG-SIOS;Lo;0;L;;;;;N;;;;; 1146;HANGUL CHOSEONG IEUNG-PANSIOS;Lo;0;L;;;;;N;;;;; 1147;HANGUL CHOSEONG SSANGIEUNG;Lo;0;L;;;;;N;;;;; 1148;HANGUL CHOSEONG IEUNG-CIEUC;Lo;0;L;;;;;N;;;;; 1149;HANGUL CHOSEONG IEUNG-CHIEUCH;Lo;0;L;;;;;N;;;;; 114A;HANGUL CHOSEONG IEUNG-THIEUTH;Lo;0;L;;;;;N;;;;; 114B;HANGUL CHOSEONG IEUNG-PHIEUPH;Lo;0;L;;;;;N;;;;; 114C;HANGUL CHOSEONG YESIEUNG;Lo;0;L;;;;;N;;;;; 114D;HANGUL CHOSEONG CIEUC-IEUNG;Lo;0;L;;;;;N;;;;; 114E;HANGUL CHOSEONG CHITUEUMCIEUC;Lo;0;L;;;;;N;;;;; 114F;HANGUL CHOSEONG CHITUEUMSSANGCIEUC;Lo;0;L;;;;;N;;;;; 1150;HANGUL CHOSEONG CEONGCHIEUMCIEUC;Lo;0;L;;;;;N;;;;; 1151;HANGUL CHOSEONG CEONGCHIEUMSSANGCIEUC;Lo;0;L;;;;;N;;;;; 1152;HANGUL CHOSEONG CHIEUCH-KHIEUKH;Lo;0;L;;;;;N;;;;; 1153;HANGUL CHOSEONG CHIEUCH-HIEUH;Lo;0;L;;;;;N;;;;; 1154;HANGUL CHOSEONG CHITUEUMCHIEUCH;Lo;0;L;;;;;N;;;;; 1155;HANGUL CHOSEONG CEONGCHIEUMCHIEUCH;Lo;0;L;;;;;N;;;;; 1156;HANGUL CHOSEONG PHIEUPH-PIEUP;Lo;0;L;;;;;N;;;;; 1157;HANGUL CHOSEONG KAPYEOUNPHIEUPH;Lo;0;L;;;;;N;;;;; 1158;HANGUL CHOSEONG SSANGHIEUH;Lo;0;L;;;;;N;;;;; 1159;HANGUL CHOSEONG YEORINHIEUH;Lo;0;L;;;;;N;;;;; 115A;HANGUL CHOSEONG KIYEOK-TIKEUT;Lo;0;L;;;;;N;;;;; 115B;HANGUL CHOSEONG NIEUN-SIOS;Lo;0;L;;;;;N;;;;; 115C;HANGUL CHOSEONG NIEUN-CIEUC;Lo;0;L;;;;;N;;;;; 115D;HANGUL CHOSEONG NIEUN-HIEUH;Lo;0;L;;;;;N;;;;; 115E;HANGUL CHOSEONG TIKEUT-RIEUL;Lo;0;L;;;;;N;;;;; 115F;HANGUL CHOSEONG FILLER;Lo;0;L;;;;;N;;;;; 1160;HANGUL JUNGSEONG FILLER;Lo;0;L;;;;;N;;;;; 1161;HANGUL JUNGSEONG A;Lo;0;L;;;;;N;;;;; 1162;HANGUL JUNGSEONG AE;Lo;0;L;;;;;N;;;;; 1163;HANGUL JUNGSEONG YA;Lo;0;L;;;;;N;;;;; 1164;HANGUL JUNGSEONG YAE;Lo;0;L;;;;;N;;;;; 1165;HANGUL JUNGSEONG EO;Lo;0;L;;;;;N;;;;; 1166;HANGUL JUNGSEONG E;Lo;0;L;;;;;N;;;;; 1167;HANGUL JUNGSEONG YEO;Lo;0;L;;;;;N;;;;; 1168;HANGUL JUNGSEONG YE;Lo;0;L;;;;;N;;;;; 1169;HANGUL JUNGSEONG O;Lo;0;L;;;;;N;;;;; 116A;HANGUL JUNGSEONG WA;Lo;0;L;;;;;N;;;;; 116B;HANGUL JUNGSEONG WAE;Lo;0;L;;;;;N;;;;; 116C;HANGUL JUNGSEONG OE;Lo;0;L;;;;;N;;;;; 116D;HANGUL JUNGSEONG YO;Lo;0;L;;;;;N;;;;; 116E;HANGUL JUNGSEONG U;Lo;0;L;;;;;N;;;;; 116F;HANGUL JUNGSEONG WEO;Lo;0;L;;;;;N;;;;; 1170;HANGUL JUNGSEONG WE;Lo;0;L;;;;;N;;;;; 1171;HANGUL JUNGSEONG WI;Lo;0;L;;;;;N;;;;; 1172;HANGUL JUNGSEONG YU;Lo;0;L;;;;;N;;;;; 1173;HANGUL JUNGSEONG EU;Lo;0;L;;;;;N;;;;; 1174;HANGUL JUNGSEONG YI;Lo;0;L;;;;;N;;;;; 1175;HANGUL JUNGSEONG I;Lo;0;L;;;;;N;;;;; 1176;HANGUL JUNGSEONG A-O;Lo;0;L;;;;;N;;;;; 1177;HANGUL JUNGSEONG A-U;Lo;0;L;;;;;N;;;;; 1178;HANGUL JUNGSEONG YA-O;Lo;0;L;;;;;N;;;;; 1179;HANGUL JUNGSEONG YA-YO;Lo;0;L;;;;;N;;;;; 117A;HANGUL JUNGSEONG EO-O;Lo;0;L;;;;;N;;;;; 117B;HANGUL JUNGSEONG EO-U;Lo;0;L;;;;;N;;;;; 117C;HANGUL JUNGSEONG EO-EU;Lo;0;L;;;;;N;;;;; 117D;HANGUL JUNGSEONG YEO-O;Lo;0;L;;;;;N;;;;; 117E;HANGUL JUNGSEONG YEO-U;Lo;0;L;;;;;N;;;;; 117F;HANGUL JUNGSEONG O-EO;Lo;0;L;;;;;N;;;;; 1180;HANGUL JUNGSEONG O-E;Lo;0;L;;;;;N;;;;; 1181;HANGUL JUNGSEONG O-YE;Lo;0;L;;;;;N;;;;; 1182;HANGUL JUNGSEONG O-O;Lo;0;L;;;;;N;;;;; 1183;HANGUL JUNGSEONG O-U;Lo;0;L;;;;;N;;;;; 1184;HANGUL JUNGSEONG YO-YA;Lo;0;L;;;;;N;;;;; 1185;HANGUL JUNGSEONG YO-YAE;Lo;0;L;;;;;N;;;;; 1186;HANGUL JUNGSEONG YO-YEO;Lo;0;L;;;;;N;;;;; 1187;HANGUL JUNGSEONG YO-O;Lo;0;L;;;;;N;;;;; 1188;HANGUL JUNGSEONG YO-I;Lo;0;L;;;;;N;;;;; 1189;HANGUL JUNGSEONG U-A;Lo;0;L;;;;;N;;;;; 118A;HANGUL JUNGSEONG U-AE;Lo;0;L;;;;;N;;;;; 118B;HANGUL JUNGSEONG U-EO-EU;Lo;0;L;;;;;N;;;;; 118C;HANGUL JUNGSEONG U-YE;Lo;0;L;;;;;N;;;;; 118D;HANGUL JUNGSEONG U-U;Lo;0;L;;;;;N;;;;; 118E;HANGUL JUNGSEONG YU-A;Lo;0;L;;;;;N;;;;; 118F;HANGUL JUNGSEONG YU-EO;Lo;0;L;;;;;N;;;;; 1190;HANGUL JUNGSEONG YU-E;Lo;0;L;;;;;N;;;;; 1191;HANGUL JUNGSEONG YU-YEO;Lo;0;L;;;;;N;;;;; 1192;HANGUL JUNGSEONG YU-YE;Lo;0;L;;;;;N;;;;; 1193;HANGUL JUNGSEONG YU-U;Lo;0;L;;;;;N;;;;; 1194;HANGUL JUNGSEONG YU-I;Lo;0;L;;;;;N;;;;; 1195;HANGUL JUNGSEONG EU-U;Lo;0;L;;;;;N;;;;; 1196;HANGUL JUNGSEONG EU-EU;Lo;0;L;;;;;N;;;;; 1197;HANGUL JUNGSEONG YI-U;Lo;0;L;;;;;N;;;;; 1198;HANGUL JUNGSEONG I-A;Lo;0;L;;;;;N;;;;; 1199;HANGUL JUNGSEONG I-YA;Lo;0;L;;;;;N;;;;; 119A;HANGUL JUNGSEONG I-O;Lo;0;L;;;;;N;;;;; 119B;HANGUL JUNGSEONG I-U;Lo;0;L;;;;;N;;;;; 119C;HANGUL JUNGSEONG I-EU;Lo;0;L;;;;;N;;;;; 119D;HANGUL JUNGSEONG I-ARAEA;Lo;0;L;;;;;N;;;;; 119E;HANGUL JUNGSEONG ARAEA;Lo;0;L;;;;;N;;;;; 119F;HANGUL JUNGSEONG ARAEA-EO;Lo;0;L;;;;;N;;;;; 11A0;HANGUL JUNGSEONG ARAEA-U;Lo;0;L;;;;;N;;;;; 11A1;HANGUL JUNGSEONG ARAEA-I;Lo;0;L;;;;;N;;;;; 11A2;HANGUL JUNGSEONG SSANGARAEA;Lo;0;L;;;;;N;;;;; 11A3;HANGUL JUNGSEONG A-EU;Lo;0;L;;;;;N;;;;; 11A4;HANGUL JUNGSEONG YA-U;Lo;0;L;;;;;N;;;;; 11A5;HANGUL JUNGSEONG YEO-YA;Lo;0;L;;;;;N;;;;; 11A6;HANGUL JUNGSEONG O-YA;Lo;0;L;;;;;N;;;;; 11A7;HANGUL JUNGSEONG O-YAE;Lo;0;L;;;;;N;;;;; 11A8;HANGUL JONGSEONG KIYEOK;Lo;0;L;;;;;N;;;;; 11A9;HANGUL JONGSEONG SSANGKIYEOK;Lo;0;L;;;;;N;;;;; 11AA;HANGUL JONGSEONG KIYEOK-SIOS;Lo;0;L;;;;;N;;;;; 11AB;HANGUL JONGSEONG NIEUN;Lo;0;L;;;;;N;;;;; 11AC;HANGUL JONGSEONG NIEUN-CIEUC;Lo;0;L;;;;;N;;;;; 11AD;HANGUL JONGSEONG NIEUN-HIEUH;Lo;0;L;;;;;N;;;;; 11AE;HANGUL JONGSEONG TIKEUT;Lo;0;L;;;;;N;;;;; 11AF;HANGUL JONGSEONG RIEUL;Lo;0;L;;;;;N;;;;; 11B0;HANGUL JONGSEONG RIEUL-KIYEOK;Lo;0;L;;;;;N;;;;; 11B1;HANGUL JONGSEONG RIEUL-MIEUM;Lo;0;L;;;;;N;;;;; 11B2;HANGUL JONGSEONG RIEUL-PIEUP;Lo;0;L;;;;;N;;;;; 11B3;HANGUL JONGSEONG RIEUL-SIOS;Lo;0;L;;;;;N;;;;; 11B4;HANGUL JONGSEONG RIEUL-THIEUTH;Lo;0;L;;;;;N;;;;; 11B5;HANGUL JONGSEONG RIEUL-PHIEUPH;Lo;0;L;;;;;N;;;;; 11B6;HANGUL JONGSEONG RIEUL-HIEUH;Lo;0;L;;;;;N;;;;; 11B7;HANGUL JONGSEONG MIEUM;Lo;0;L;;;;;N;;;;; 11B8;HANGUL JONGSEONG PIEUP;Lo;0;L;;;;;N;;;;; 11B9;HANGUL JONGSEONG PIEUP-SIOS;Lo;0;L;;;;;N;;;;; 11BA;HANGUL JONGSEONG SIOS;Lo;0;L;;;;;N;;;;; 11BB;HANGUL JONGSEONG SSANGSIOS;Lo;0;L;;;;;N;;;;; 11BC;HANGUL JONGSEONG IEUNG;Lo;0;L;;;;;N;;;;; 11BD;HANGUL JONGSEONG CIEUC;Lo;0;L;;;;;N;;;;; 11BE;HANGUL JONGSEONG CHIEUCH;Lo;0;L;;;;;N;;;;; 11BF;HANGUL JONGSEONG KHIEUKH;Lo;0;L;;;;;N;;;;; 11C0;HANGUL JONGSEONG THIEUTH;Lo;0;L;;;;;N;;;;; 11C1;HANGUL JONGSEONG PHIEUPH;Lo;0;L;;;;;N;;;;; 11C2;HANGUL JONGSEONG HIEUH;Lo;0;L;;;;;N;;;;; 11C3;HANGUL JONGSEONG KIYEOK-RIEUL;Lo;0;L;;;;;N;;;;; 11C4;HANGUL JONGSEONG KIYEOK-SIOS-KIYEOK;Lo;0;L;;;;;N;;;;; 11C5;HANGUL JONGSEONG NIEUN-KIYEOK;Lo;0;L;;;;;N;;;;; 11C6;HANGUL JONGSEONG NIEUN-TIKEUT;Lo;0;L;;;;;N;;;;; 11C7;HANGUL JONGSEONG NIEUN-SIOS;Lo;0;L;;;;;N;;;;; 11C8;HANGUL JONGSEONG NIEUN-PANSIOS;Lo;0;L;;;;;N;;;;; 11C9;HANGUL JONGSEONG NIEUN-THIEUTH;Lo;0;L;;;;;N;;;;; 11CA;HANGUL JONGSEONG TIKEUT-KIYEOK;Lo;0;L;;;;;N;;;;; 11CB;HANGUL JONGSEONG TIKEUT-RIEUL;Lo;0;L;;;;;N;;;;; 11CC;HANGUL JONGSEONG RIEUL-KIYEOK-SIOS;Lo;0;L;;;;;N;;;;; 11CD;HANGUL JONGSEONG RIEUL-NIEUN;Lo;0;L;;;;;N;;;;; 11CE;HANGUL JONGSEONG RIEUL-TIKEUT;Lo;0;L;;;;;N;;;;; 11CF;HANGUL JONGSEONG RIEUL-TIKEUT-HIEUH;Lo;0;L;;;;;N;;;;; 11D0;HANGUL JONGSEONG SSANGRIEUL;Lo;0;L;;;;;N;;;;; 11D1;HANGUL JONGSEONG RIEUL-MIEUM-KIYEOK;Lo;0;L;;;;;N;;;;; 11D2;HANGUL JONGSEONG RIEUL-MIEUM-SIOS;Lo;0;L;;;;;N;;;;; 11D3;HANGUL JONGSEONG RIEUL-PIEUP-SIOS;Lo;0;L;;;;;N;;;;; 11D4;HANGUL JONGSEONG RIEUL-PIEUP-HIEUH;Lo;0;L;;;;;N;;;;; 11D5;HANGUL JONGSEONG RIEUL-KAPYEOUNPIEUP;Lo;0;L;;;;;N;;;;; 11D6;HANGUL JONGSEONG RIEUL-SSANGSIOS;Lo;0;L;;;;;N;;;;; 11D7;HANGUL JONGSEONG RIEUL-PANSIOS;Lo;0;L;;;;;N;;;;; 11D8;HANGUL JONGSEONG RIEUL-KHIEUKH;Lo;0;L;;;;;N;;;;; 11D9;HANGUL JONGSEONG RIEUL-YEORINHIEUH;Lo;0;L;;;;;N;;;;; 11DA;HANGUL JONGSEONG MIEUM-KIYEOK;Lo;0;L;;;;;N;;;;; 11DB;HANGUL JONGSEONG MIEUM-RIEUL;Lo;0;L;;;;;N;;;;; 11DC;HANGUL JONGSEONG MIEUM-PIEUP;Lo;0;L;;;;;N;;;;; 11DD;HANGUL JONGSEONG MIEUM-SIOS;Lo;0;L;;;;;N;;;;; 11DE;HANGUL JONGSEONG MIEUM-SSANGSIOS;Lo;0;L;;;;;N;;;;; 11DF;HANGUL JONGSEONG MIEUM-PANSIOS;Lo;0;L;;;;;N;;;;; 11E0;HANGUL JONGSEONG MIEUM-CHIEUCH;Lo;0;L;;;;;N;;;;; 11E1;HANGUL JONGSEONG MIEUM-HIEUH;Lo;0;L;;;;;N;;;;; 11E2;HANGUL JONGSEONG KAPYEOUNMIEUM;Lo;0;L;;;;;N;;;;; 11E3;HANGUL JONGSEONG PIEUP-RIEUL;Lo;0;L;;;;;N;;;;; 11E4;HANGUL JONGSEONG PIEUP-PHIEUPH;Lo;0;L;;;;;N;;;;; 11E5;HANGUL JONGSEONG PIEUP-HIEUH;Lo;0;L;;;;;N;;;;; 11E6;HANGUL JONGSEONG KAPYEOUNPIEUP;Lo;0;L;;;;;N;;;;; 11E7;HANGUL JONGSEONG SIOS-KIYEOK;Lo;0;L;;;;;N;;;;; 11E8;HANGUL JONGSEONG SIOS-TIKEUT;Lo;0;L;;;;;N;;;;; 11E9;HANGUL JONGSEONG SIOS-RIEUL;Lo;0;L;;;;;N;;;;; 11EA;HANGUL JONGSEONG SIOS-PIEUP;Lo;0;L;;;;;N;;;;; 11EB;HANGUL JONGSEONG PANSIOS;Lo;0;L;;;;;N;;;;; 11EC;HANGUL JONGSEONG IEUNG-KIYEOK;Lo;0;L;;;;;N;;;;; 11ED;HANGUL JONGSEONG IEUNG-SSANGKIYEOK;Lo;0;L;;;;;N;;;;; 11EE;HANGUL JONGSEONG SSANGIEUNG;Lo;0;L;;;;;N;;;;; 11EF;HANGUL JONGSEONG IEUNG-KHIEUKH;Lo;0;L;;;;;N;;;;; 11F0;HANGUL JONGSEONG YESIEUNG;Lo;0;L;;;;;N;;;;; 11F1;HANGUL JONGSEONG YESIEUNG-SIOS;Lo;0;L;;;;;N;;;;; 11F2;HANGUL JONGSEONG YESIEUNG-PANSIOS;Lo;0;L;;;;;N;;;;; 11F3;HANGUL JONGSEONG PHIEUPH-PIEUP;Lo;0;L;;;;;N;;;;; 11F4;HANGUL JONGSEONG KAPYEOUNPHIEUPH;Lo;0;L;;;;;N;;;;; 11F5;HANGUL JONGSEONG HIEUH-NIEUN;Lo;0;L;;;;;N;;;;; 11F6;HANGUL JONGSEONG HIEUH-RIEUL;Lo;0;L;;;;;N;;;;; 11F7;HANGUL JONGSEONG HIEUH-MIEUM;Lo;0;L;;;;;N;;;;; 11F8;HANGUL JONGSEONG HIEUH-PIEUP;Lo;0;L;;;;;N;;;;; 11F9;HANGUL JONGSEONG YEORINHIEUH;Lo;0;L;;;;;N;;;;; 11FA;HANGUL JONGSEONG KIYEOK-NIEUN;Lo;0;L;;;;;N;;;;; 11FB;HANGUL JONGSEONG KIYEOK-PIEUP;Lo;0;L;;;;;N;;;;; 11FC;HANGUL JONGSEONG KIYEOK-CHIEUCH;Lo;0;L;;;;;N;;;;; 11FD;HANGUL JONGSEONG KIYEOK-KHIEUKH;Lo;0;L;;;;;N;;;;; 11FE;HANGUL JONGSEONG KIYEOK-HIEUH;Lo;0;L;;;;;N;;;;; 11FF;HANGUL JONGSEONG SSANGNIEUN;Lo;0;L;;;;;N;;;;; 1200;ETHIOPIC SYLLABLE HA;Lo;0;L;;;;;N;;;;; 1201;ETHIOPIC SYLLABLE HU;Lo;0;L;;;;;N;;;;; 1202;ETHIOPIC SYLLABLE HI;Lo;0;L;;;;;N;;;;; 1203;ETHIOPIC SYLLABLE HAA;Lo;0;L;;;;;N;;;;; 1204;ETHIOPIC SYLLABLE HEE;Lo;0;L;;;;;N;;;;; 1205;ETHIOPIC SYLLABLE HE;Lo;0;L;;;;;N;;;;; 1206;ETHIOPIC SYLLABLE HO;Lo;0;L;;;;;N;;;;; 1207;ETHIOPIC SYLLABLE HOA;Lo;0;L;;;;;N;;;;; 1208;ETHIOPIC SYLLABLE LA;Lo;0;L;;;;;N;;;;; 1209;ETHIOPIC SYLLABLE LU;Lo;0;L;;;;;N;;;;; 120A;ETHIOPIC SYLLABLE LI;Lo;0;L;;;;;N;;;;; 120B;ETHIOPIC SYLLABLE LAA;Lo;0;L;;;;;N;;;;; 120C;ETHIOPIC SYLLABLE LEE;Lo;0;L;;;;;N;;;;; 120D;ETHIOPIC SYLLABLE LE;Lo;0;L;;;;;N;;;;; 120E;ETHIOPIC SYLLABLE LO;Lo;0;L;;;;;N;;;;; 120F;ETHIOPIC SYLLABLE LWA;Lo;0;L;;;;;N;;;;; 1210;ETHIOPIC SYLLABLE HHA;Lo;0;L;;;;;N;;;;; 1211;ETHIOPIC SYLLABLE HHU;Lo;0;L;;;;;N;;;;; 1212;ETHIOPIC SYLLABLE HHI;Lo;0;L;;;;;N;;;;; 1213;ETHIOPIC SYLLABLE HHAA;Lo;0;L;;;;;N;;;;; 1214;ETHIOPIC SYLLABLE HHEE;Lo;0;L;;;;;N;;;;; 1215;ETHIOPIC SYLLABLE HHE;Lo;0;L;;;;;N;;;;; 1216;ETHIOPIC SYLLABLE HHO;Lo;0;L;;;;;N;;;;; 1217;ETHIOPIC SYLLABLE HHWA;Lo;0;L;;;;;N;;;;; 1218;ETHIOPIC SYLLABLE MA;Lo;0;L;;;;;N;;;;; 1219;ETHIOPIC SYLLABLE MU;Lo;0;L;;;;;N;;;;; 121A;ETHIOPIC SYLLABLE MI;Lo;0;L;;;;;N;;;;; 121B;ETHIOPIC SYLLABLE MAA;Lo;0;L;;;;;N;;;;; 121C;ETHIOPIC SYLLABLE MEE;Lo;0;L;;;;;N;;;;; 121D;ETHIOPIC SYLLABLE ME;Lo;0;L;;;;;N;;;;; 121E;ETHIOPIC SYLLABLE MO;Lo;0;L;;;;;N;;;;; 121F;ETHIOPIC SYLLABLE MWA;Lo;0;L;;;;;N;;;;; 1220;ETHIOPIC SYLLABLE SZA;Lo;0;L;;;;;N;;;;; 1221;ETHIOPIC SYLLABLE SZU;Lo;0;L;;;;;N;;;;; 1222;ETHIOPIC SYLLABLE SZI;Lo;0;L;;;;;N;;;;; 1223;ETHIOPIC SYLLABLE SZAA;Lo;0;L;;;;;N;;;;; 1224;ETHIOPIC SYLLABLE SZEE;Lo;0;L;;;;;N;;;;; 1225;ETHIOPIC SYLLABLE SZE;Lo;0;L;;;;;N;;;;; 1226;ETHIOPIC SYLLABLE SZO;Lo;0;L;;;;;N;;;;; 1227;ETHIOPIC SYLLABLE SZWA;Lo;0;L;;;;;N;;;;; 1228;ETHIOPIC SYLLABLE RA;Lo;0;L;;;;;N;;;;; 1229;ETHIOPIC SYLLABLE RU;Lo;0;L;;;;;N;;;;; 122A;ETHIOPIC SYLLABLE RI;Lo;0;L;;;;;N;;;;; 122B;ETHIOPIC SYLLABLE RAA;Lo;0;L;;;;;N;;;;; 122C;ETHIOPIC SYLLABLE REE;Lo;0;L;;;;;N;;;;; 122D;ETHIOPIC SYLLABLE RE;Lo;0;L;;;;;N;;;;; 122E;ETHIOPIC SYLLABLE RO;Lo;0;L;;;;;N;;;;; 122F;ETHIOPIC SYLLABLE RWA;Lo;0;L;;;;;N;;;;; 1230;ETHIOPIC SYLLABLE SA;Lo;0;L;;;;;N;;;;; 1231;ETHIOPIC SYLLABLE SU;Lo;0;L;;;;;N;;;;; 1232;ETHIOPIC SYLLABLE SI;Lo;0;L;;;;;N;;;;; 1233;ETHIOPIC SYLLABLE SAA;Lo;0;L;;;;;N;;;;; 1234;ETHIOPIC SYLLABLE SEE;Lo;0;L;;;;;N;;;;; 1235;ETHIOPIC SYLLABLE SE;Lo;0;L;;;;;N;;;;; 1236;ETHIOPIC SYLLABLE SO;Lo;0;L;;;;;N;;;;; 1237;ETHIOPIC SYLLABLE SWA;Lo;0;L;;;;;N;;;;; 1238;ETHIOPIC SYLLABLE SHA;Lo;0;L;;;;;N;;;;; 1239;ETHIOPIC SYLLABLE SHU;Lo;0;L;;;;;N;;;;; 123A;ETHIOPIC SYLLABLE SHI;Lo;0;L;;;;;N;;;;; 123B;ETHIOPIC SYLLABLE SHAA;Lo;0;L;;;;;N;;;;; 123C;ETHIOPIC SYLLABLE SHEE;Lo;0;L;;;;;N;;;;; 123D;ETHIOPIC SYLLABLE SHE;Lo;0;L;;;;;N;;;;; 123E;ETHIOPIC SYLLABLE SHO;Lo;0;L;;;;;N;;;;; 123F;ETHIOPIC SYLLABLE SHWA;Lo;0;L;;;;;N;;;;; 1240;ETHIOPIC SYLLABLE QA;Lo;0;L;;;;;N;;;;; 1241;ETHIOPIC SYLLABLE QU;Lo;0;L;;;;;N;;;;; 1242;ETHIOPIC SYLLABLE QI;Lo;0;L;;;;;N;;;;; 1243;ETHIOPIC SYLLABLE QAA;Lo;0;L;;;;;N;;;;; 1244;ETHIOPIC SYLLABLE QEE;Lo;0;L;;;;;N;;;;; 1245;ETHIOPIC SYLLABLE QE;Lo;0;L;;;;;N;;;;; 1246;ETHIOPIC SYLLABLE QO;Lo;0;L;;;;;N;;;;; 1247;ETHIOPIC SYLLABLE QOA;Lo;0;L;;;;;N;;;;; 1248;ETHIOPIC SYLLABLE QWA;Lo;0;L;;;;;N;;;;; 124A;ETHIOPIC SYLLABLE QWI;Lo;0;L;;;;;N;;;;; 124B;ETHIOPIC SYLLABLE QWAA;Lo;0;L;;;;;N;;;;; 124C;ETHIOPIC SYLLABLE QWEE;Lo;0;L;;;;;N;;;;; 124D;ETHIOPIC SYLLABLE QWE;Lo;0;L;;;;;N;;;;; 1250;ETHIOPIC SYLLABLE QHA;Lo;0;L;;;;;N;;;;; 1251;ETHIOPIC SYLLABLE QHU;Lo;0;L;;;;;N;;;;; 1252;ETHIOPIC SYLLABLE QHI;Lo;0;L;;;;;N;;;;; 1253;ETHIOPIC SYLLABLE QHAA;Lo;0;L;;;;;N;;;;; 1254;ETHIOPIC SYLLABLE QHEE;Lo;0;L;;;;;N;;;;; 1255;ETHIOPIC SYLLABLE QHE;Lo;0;L;;;;;N;;;;; 1256;ETHIOPIC SYLLABLE QHO;Lo;0;L;;;;;N;;;;; 1258;ETHIOPIC SYLLABLE QHWA;Lo;0;L;;;;;N;;;;; 125A;ETHIOPIC SYLLABLE QHWI;Lo;0;L;;;;;N;;;;; 125B;ETHIOPIC SYLLABLE QHWAA;Lo;0;L;;;;;N;;;;; 125C;ETHIOPIC SYLLABLE QHWEE;Lo;0;L;;;;;N;;;;; 125D;ETHIOPIC SYLLABLE QHWE;Lo;0;L;;;;;N;;;;; 1260;ETHIOPIC SYLLABLE BA;Lo;0;L;;;;;N;;;;; 1261;ETHIOPIC SYLLABLE BU;Lo;0;L;;;;;N;;;;; 1262;ETHIOPIC SYLLABLE BI;Lo;0;L;;;;;N;;;;; 1263;ETHIOPIC SYLLABLE BAA;Lo;0;L;;;;;N;;;;; 1264;ETHIOPIC SYLLABLE BEE;Lo;0;L;;;;;N;;;;; 1265;ETHIOPIC SYLLABLE BE;Lo;0;L;;;;;N;;;;; 1266;ETHIOPIC SYLLABLE BO;Lo;0;L;;;;;N;;;;; 1267;ETHIOPIC SYLLABLE BWA;Lo;0;L;;;;;N;;;;; 1268;ETHIOPIC SYLLABLE VA;Lo;0;L;;;;;N;;;;; 1269;ETHIOPIC SYLLABLE VU;Lo;0;L;;;;;N;;;;; 126A;ETHIOPIC SYLLABLE VI;Lo;0;L;;;;;N;;;;; 126B;ETHIOPIC SYLLABLE VAA;Lo;0;L;;;;;N;;;;; 126C;ETHIOPIC SYLLABLE VEE;Lo;0;L;;;;;N;;;;; 126D;ETHIOPIC SYLLABLE VE;Lo;0;L;;;;;N;;;;; 126E;ETHIOPIC SYLLABLE VO;Lo;0;L;;;;;N;;;;; 126F;ETHIOPIC SYLLABLE VWA;Lo;0;L;;;;;N;;;;; 1270;ETHIOPIC SYLLABLE TA;Lo;0;L;;;;;N;;;;; 1271;ETHIOPIC SYLLABLE TU;Lo;0;L;;;;;N;;;;; 1272;ETHIOPIC SYLLABLE TI;Lo;0;L;;;;;N;;;;; 1273;ETHIOPIC SYLLABLE TAA;Lo;0;L;;;;;N;;;;; 1274;ETHIOPIC SYLLABLE TEE;Lo;0;L;;;;;N;;;;; 1275;ETHIOPIC SYLLABLE TE;Lo;0;L;;;;;N;;;;; 1276;ETHIOPIC SYLLABLE TO;Lo;0;L;;;;;N;;;;; 1277;ETHIOPIC SYLLABLE TWA;Lo;0;L;;;;;N;;;;; 1278;ETHIOPIC SYLLABLE CA;Lo;0;L;;;;;N;;;;; 1279;ETHIOPIC SYLLABLE CU;Lo;0;L;;;;;N;;;;; 127A;ETHIOPIC SYLLABLE CI;Lo;0;L;;;;;N;;;;; 127B;ETHIOPIC SYLLABLE CAA;Lo;0;L;;;;;N;;;;; 127C;ETHIOPIC SYLLABLE CEE;Lo;0;L;;;;;N;;;;; 127D;ETHIOPIC SYLLABLE CE;Lo;0;L;;;;;N;;;;; 127E;ETHIOPIC SYLLABLE CO;Lo;0;L;;;;;N;;;;; 127F;ETHIOPIC SYLLABLE CWA;Lo;0;L;;;;;N;;;;; 1280;ETHIOPIC SYLLABLE XA;Lo;0;L;;;;;N;;;;; 1281;ETHIOPIC SYLLABLE XU;Lo;0;L;;;;;N;;;;; 1282;ETHIOPIC SYLLABLE XI;Lo;0;L;;;;;N;;;;; 1283;ETHIOPIC SYLLABLE XAA;Lo;0;L;;;;;N;;;;; 1284;ETHIOPIC SYLLABLE XEE;Lo;0;L;;;;;N;;;;; 1285;ETHIOPIC SYLLABLE XE;Lo;0;L;;;;;N;;;;; 1286;ETHIOPIC SYLLABLE XO;Lo;0;L;;;;;N;;;;; 1287;ETHIOPIC SYLLABLE XOA;Lo;0;L;;;;;N;;;;; 1288;ETHIOPIC SYLLABLE XWA;Lo;0;L;;;;;N;;;;; 128A;ETHIOPIC SYLLABLE XWI;Lo;0;L;;;;;N;;;;; 128B;ETHIOPIC SYLLABLE XWAA;Lo;0;L;;;;;N;;;;; 128C;ETHIOPIC SYLLABLE XWEE;Lo;0;L;;;;;N;;;;; 128D;ETHIOPIC SYLLABLE XWE;Lo;0;L;;;;;N;;;;; 1290;ETHIOPIC SYLLABLE NA;Lo;0;L;;;;;N;;;;; 1291;ETHIOPIC SYLLABLE NU;Lo;0;L;;;;;N;;;;; 1292;ETHIOPIC SYLLABLE NI;Lo;0;L;;;;;N;;;;; 1293;ETHIOPIC SYLLABLE NAA;Lo;0;L;;;;;N;;;;; 1294;ETHIOPIC SYLLABLE NEE;Lo;0;L;;;;;N;;;;; 1295;ETHIOPIC SYLLABLE NE;Lo;0;L;;;;;N;;;;; 1296;ETHIOPIC SYLLABLE NO;Lo;0;L;;;;;N;;;;; 1297;ETHIOPIC SYLLABLE NWA;Lo;0;L;;;;;N;;;;; 1298;ETHIOPIC SYLLABLE NYA;Lo;0;L;;;;;N;;;;; 1299;ETHIOPIC SYLLABLE NYU;Lo;0;L;;;;;N;;;;; 129A;ETHIOPIC SYLLABLE NYI;Lo;0;L;;;;;N;;;;; 129B;ETHIOPIC SYLLABLE NYAA;Lo;0;L;;;;;N;;;;; 129C;ETHIOPIC SYLLABLE NYEE;Lo;0;L;;;;;N;;;;; 129D;ETHIOPIC SYLLABLE NYE;Lo;0;L;;;;;N;;;;; 129E;ETHIOPIC SYLLABLE NYO;Lo;0;L;;;;;N;;;;; 129F;ETHIOPIC SYLLABLE NYWA;Lo;0;L;;;;;N;;;;; 12A0;ETHIOPIC SYLLABLE GLOTTAL A;Lo;0;L;;;;;N;;;;; 12A1;ETHIOPIC SYLLABLE GLOTTAL U;Lo;0;L;;;;;N;;;;; 12A2;ETHIOPIC SYLLABLE GLOTTAL I;Lo;0;L;;;;;N;;;;; 12A3;ETHIOPIC SYLLABLE GLOTTAL AA;Lo;0;L;;;;;N;;;;; 12A4;ETHIOPIC SYLLABLE GLOTTAL EE;Lo;0;L;;;;;N;;;;; 12A5;ETHIOPIC SYLLABLE GLOTTAL E;Lo;0;L;;;;;N;;;;; 12A6;ETHIOPIC SYLLABLE GLOTTAL O;Lo;0;L;;;;;N;;;;; 12A7;ETHIOPIC SYLLABLE GLOTTAL WA;Lo;0;L;;;;;N;;;;; 12A8;ETHIOPIC SYLLABLE KA;Lo;0;L;;;;;N;;;;; 12A9;ETHIOPIC SYLLABLE KU;Lo;0;L;;;;;N;;;;; 12AA;ETHIOPIC SYLLABLE KI;Lo;0;L;;;;;N;;;;; 12AB;ETHIOPIC SYLLABLE KAA;Lo;0;L;;;;;N;;;;; 12AC;ETHIOPIC SYLLABLE KEE;Lo;0;L;;;;;N;;;;; 12AD;ETHIOPIC SYLLABLE KE;Lo;0;L;;;;;N;;;;; 12AE;ETHIOPIC SYLLABLE KO;Lo;0;L;;;;;N;;;;; 12AF;ETHIOPIC SYLLABLE KOA;Lo;0;L;;;;;N;;;;; 12B0;ETHIOPIC SYLLABLE KWA;Lo;0;L;;;;;N;;;;; 12B2;ETHIOPIC SYLLABLE KWI;Lo;0;L;;;;;N;;;;; 12B3;ETHIOPIC SYLLABLE KWAA;Lo;0;L;;;;;N;;;;; 12B4;ETHIOPIC SYLLABLE KWEE;Lo;0;L;;;;;N;;;;; 12B5;ETHIOPIC SYLLABLE KWE;Lo;0;L;;;;;N;;;;; 12B8;ETHIOPIC SYLLABLE KXA;Lo;0;L;;;;;N;;;;; 12B9;ETHIOPIC SYLLABLE KXU;Lo;0;L;;;;;N;;;;; 12BA;ETHIOPIC SYLLABLE KXI;Lo;0;L;;;;;N;;;;; 12BB;ETHIOPIC SYLLABLE KXAA;Lo;0;L;;;;;N;;;;; 12BC;ETHIOPIC SYLLABLE KXEE;Lo;0;L;;;;;N;;;;; 12BD;ETHIOPIC SYLLABLE KXE;Lo;0;L;;;;;N;;;;; 12BE;ETHIOPIC SYLLABLE KXO;Lo;0;L;;;;;N;;;;; 12C0;ETHIOPIC SYLLABLE KXWA;Lo;0;L;;;;;N;;;;; 12C2;ETHIOPIC SYLLABLE KXWI;Lo;0;L;;;;;N;;;;; 12C3;ETHIOPIC SYLLABLE KXWAA;Lo;0;L;;;;;N;;;;; 12C4;ETHIOPIC SYLLABLE KXWEE;Lo;0;L;;;;;N;;;;; 12C5;ETHIOPIC SYLLABLE KXWE;Lo;0;L;;;;;N;;;;; 12C8;ETHIOPIC SYLLABLE WA;Lo;0;L;;;;;N;;;;; 12C9;ETHIOPIC SYLLABLE WU;Lo;0;L;;;;;N;;;;; 12CA;ETHIOPIC SYLLABLE WI;Lo;0;L;;;;;N;;;;; 12CB;ETHIOPIC SYLLABLE WAA;Lo;0;L;;;;;N;;;;; 12CC;ETHIOPIC SYLLABLE WEE;Lo;0;L;;;;;N;;;;; 12CD;ETHIOPIC SYLLABLE WE;Lo;0;L;;;;;N;;;;; 12CE;ETHIOPIC SYLLABLE WO;Lo;0;L;;;;;N;;;;; 12CF;ETHIOPIC SYLLABLE WOA;Lo;0;L;;;;;N;;;;; 12D0;ETHIOPIC SYLLABLE PHARYNGEAL A;Lo;0;L;;;;;N;;;;; 12D1;ETHIOPIC SYLLABLE PHARYNGEAL U;Lo;0;L;;;;;N;;;;; 12D2;ETHIOPIC SYLLABLE PHARYNGEAL I;Lo;0;L;;;;;N;;;;; 12D3;ETHIOPIC SYLLABLE PHARYNGEAL AA;Lo;0;L;;;;;N;;;;; 12D4;ETHIOPIC SYLLABLE PHARYNGEAL EE;Lo;0;L;;;;;N;;;;; 12D5;ETHIOPIC SYLLABLE PHARYNGEAL E;Lo;0;L;;;;;N;;;;; 12D6;ETHIOPIC SYLLABLE PHARYNGEAL O;Lo;0;L;;;;;N;;;;; 12D8;ETHIOPIC SYLLABLE ZA;Lo;0;L;;;;;N;;;;; 12D9;ETHIOPIC SYLLABLE ZU;Lo;0;L;;;;;N;;;;; 12DA;ETHIOPIC SYLLABLE ZI;Lo;0;L;;;;;N;;;;; 12DB;ETHIOPIC SYLLABLE ZAA;Lo;0;L;;;;;N;;;;; 12DC;ETHIOPIC SYLLABLE ZEE;Lo;0;L;;;;;N;;;;; 12DD;ETHIOPIC SYLLABLE ZE;Lo;0;L;;;;;N;;;;; 12DE;ETHIOPIC SYLLABLE ZO;Lo;0;L;;;;;N;;;;; 12DF;ETHIOPIC SYLLABLE ZWA;Lo;0;L;;;;;N;;;;; 12E0;ETHIOPIC SYLLABLE ZHA;Lo;0;L;;;;;N;;;;; 12E1;ETHIOPIC SYLLABLE ZHU;Lo;0;L;;;;;N;;;;; 12E2;ETHIOPIC SYLLABLE ZHI;Lo;0;L;;;;;N;;;;; 12E3;ETHIOPIC SYLLABLE ZHAA;Lo;0;L;;;;;N;;;;; 12E4;ETHIOPIC SYLLABLE ZHEE;Lo;0;L;;;;;N;;;;; 12E5;ETHIOPIC SYLLABLE ZHE;Lo;0;L;;;;;N;;;;; 12E6;ETHIOPIC SYLLABLE ZHO;Lo;0;L;;;;;N;;;;; 12E7;ETHIOPIC SYLLABLE ZHWA;Lo;0;L;;;;;N;;;;; 12E8;ETHIOPIC SYLLABLE YA;Lo;0;L;;;;;N;;;;; 12E9;ETHIOPIC SYLLABLE YU;Lo;0;L;;;;;N;;;;; 12EA;ETHIOPIC SYLLABLE YI;Lo;0;L;;;;;N;;;;; 12EB;ETHIOPIC SYLLABLE YAA;Lo;0;L;;;;;N;;;;; 12EC;ETHIOPIC SYLLABLE YEE;Lo;0;L;;;;;N;;;;; 12ED;ETHIOPIC SYLLABLE YE;Lo;0;L;;;;;N;;;;; 12EE;ETHIOPIC SYLLABLE YO;Lo;0;L;;;;;N;;;;; 12EF;ETHIOPIC SYLLABLE YOA;Lo;0;L;;;;;N;;;;; 12F0;ETHIOPIC SYLLABLE DA;Lo;0;L;;;;;N;;;;; 12F1;ETHIOPIC SYLLABLE DU;Lo;0;L;;;;;N;;;;; 12F2;ETHIOPIC SYLLABLE DI;Lo;0;L;;;;;N;;;;; 12F3;ETHIOPIC SYLLABLE DAA;Lo;0;L;;;;;N;;;;; 12F4;ETHIOPIC SYLLABLE DEE;Lo;0;L;;;;;N;;;;; 12F5;ETHIOPIC SYLLABLE DE;Lo;0;L;;;;;N;;;;; 12F6;ETHIOPIC SYLLABLE DO;Lo;0;L;;;;;N;;;;; 12F7;ETHIOPIC SYLLABLE DWA;Lo;0;L;;;;;N;;;;; 12F8;ETHIOPIC SYLLABLE DDA;Lo;0;L;;;;;N;;;;; 12F9;ETHIOPIC SYLLABLE DDU;Lo;0;L;;;;;N;;;;; 12FA;ETHIOPIC SYLLABLE DDI;Lo;0;L;;;;;N;;;;; 12FB;ETHIOPIC SYLLABLE DDAA;Lo;0;L;;;;;N;;;;; 12FC;ETHIOPIC SYLLABLE DDEE;Lo;0;L;;;;;N;;;;; 12FD;ETHIOPIC SYLLABLE DDE;Lo;0;L;;;;;N;;;;; 12FE;ETHIOPIC SYLLABLE DDO;Lo;0;L;;;;;N;;;;; 12FF;ETHIOPIC SYLLABLE DDWA;Lo;0;L;;;;;N;;;;; 1300;ETHIOPIC SYLLABLE JA;Lo;0;L;;;;;N;;;;; 1301;ETHIOPIC SYLLABLE JU;Lo;0;L;;;;;N;;;;; 1302;ETHIOPIC SYLLABLE JI;Lo;0;L;;;;;N;;;;; 1303;ETHIOPIC SYLLABLE JAA;Lo;0;L;;;;;N;;;;; 1304;ETHIOPIC SYLLABLE JEE;Lo;0;L;;;;;N;;;;; 1305;ETHIOPIC SYLLABLE JE;Lo;0;L;;;;;N;;;;; 1306;ETHIOPIC SYLLABLE JO;Lo;0;L;;;;;N;;;;; 1307;ETHIOPIC SYLLABLE JWA;Lo;0;L;;;;;N;;;;; 1308;ETHIOPIC SYLLABLE GA;Lo;0;L;;;;;N;;;;; 1309;ETHIOPIC SYLLABLE GU;Lo;0;L;;;;;N;;;;; 130A;ETHIOPIC SYLLABLE GI;Lo;0;L;;;;;N;;;;; 130B;ETHIOPIC SYLLABLE GAA;Lo;0;L;;;;;N;;;;; 130C;ETHIOPIC SYLLABLE GEE;Lo;0;L;;;;;N;;;;; 130D;ETHIOPIC SYLLABLE GE;Lo;0;L;;;;;N;;;;; 130E;ETHIOPIC SYLLABLE GO;Lo;0;L;;;;;N;;;;; 130F;ETHIOPIC SYLLABLE GOA;Lo;0;L;;;;;N;;;;; 1310;ETHIOPIC SYLLABLE GWA;Lo;0;L;;;;;N;;;;; 1312;ETHIOPIC SYLLABLE GWI;Lo;0;L;;;;;N;;;;; 1313;ETHIOPIC SYLLABLE GWAA;Lo;0;L;;;;;N;;;;; 1314;ETHIOPIC SYLLABLE GWEE;Lo;0;L;;;;;N;;;;; 1315;ETHIOPIC SYLLABLE GWE;Lo;0;L;;;;;N;;;;; 1318;ETHIOPIC SYLLABLE GGA;Lo;0;L;;;;;N;;;;; 1319;ETHIOPIC SYLLABLE GGU;Lo;0;L;;;;;N;;;;; 131A;ETHIOPIC SYLLABLE GGI;Lo;0;L;;;;;N;;;;; 131B;ETHIOPIC SYLLABLE GGAA;Lo;0;L;;;;;N;;;;; 131C;ETHIOPIC SYLLABLE GGEE;Lo;0;L;;;;;N;;;;; 131D;ETHIOPIC SYLLABLE GGE;Lo;0;L;;;;;N;;;;; 131E;ETHIOPIC SYLLABLE GGO;Lo;0;L;;;;;N;;;;; 131F;ETHIOPIC SYLLABLE GGWAA;Lo;0;L;;;;;N;;;;; 1320;ETHIOPIC SYLLABLE THA;Lo;0;L;;;;;N;;;;; 1321;ETHIOPIC SYLLABLE THU;Lo;0;L;;;;;N;;;;; 1322;ETHIOPIC SYLLABLE THI;Lo;0;L;;;;;N;;;;; 1323;ETHIOPIC SYLLABLE THAA;Lo;0;L;;;;;N;;;;; 1324;ETHIOPIC SYLLABLE THEE;Lo;0;L;;;;;N;;;;; 1325;ETHIOPIC SYLLABLE THE;Lo;0;L;;;;;N;;;;; 1326;ETHIOPIC SYLLABLE THO;Lo;0;L;;;;;N;;;;; 1327;ETHIOPIC SYLLABLE THWA;Lo;0;L;;;;;N;;;;; 1328;ETHIOPIC SYLLABLE CHA;Lo;0;L;;;;;N;;;;; 1329;ETHIOPIC SYLLABLE CHU;Lo;0;L;;;;;N;;;;; 132A;ETHIOPIC SYLLABLE CHI;Lo;0;L;;;;;N;;;;; 132B;ETHIOPIC SYLLABLE CHAA;Lo;0;L;;;;;N;;;;; 132C;ETHIOPIC SYLLABLE CHEE;Lo;0;L;;;;;N;;;;; 132D;ETHIOPIC SYLLABLE CHE;Lo;0;L;;;;;N;;;;; 132E;ETHIOPIC SYLLABLE CHO;Lo;0;L;;;;;N;;;;; 132F;ETHIOPIC SYLLABLE CHWA;Lo;0;L;;;;;N;;;;; 1330;ETHIOPIC SYLLABLE PHA;Lo;0;L;;;;;N;;;;; 1331;ETHIOPIC SYLLABLE PHU;Lo;0;L;;;;;N;;;;; 1332;ETHIOPIC SYLLABLE PHI;Lo;0;L;;;;;N;;;;; 1333;ETHIOPIC SYLLABLE PHAA;Lo;0;L;;;;;N;;;;; 1334;ETHIOPIC SYLLABLE PHEE;Lo;0;L;;;;;N;;;;; 1335;ETHIOPIC SYLLABLE PHE;Lo;0;L;;;;;N;;;;; 1336;ETHIOPIC SYLLABLE PHO;Lo;0;L;;;;;N;;;;; 1337;ETHIOPIC SYLLABLE PHWA;Lo;0;L;;;;;N;;;;; 1338;ETHIOPIC SYLLABLE TSA;Lo;0;L;;;;;N;;;;; 1339;ETHIOPIC SYLLABLE TSU;Lo;0;L;;;;;N;;;;; 133A;ETHIOPIC SYLLABLE TSI;Lo;0;L;;;;;N;;;;; 133B;ETHIOPIC SYLLABLE TSAA;Lo;0;L;;;;;N;;;;; 133C;ETHIOPIC SYLLABLE TSEE;Lo;0;L;;;;;N;;;;; 133D;ETHIOPIC SYLLABLE TSE;Lo;0;L;;;;;N;;;;; 133E;ETHIOPIC SYLLABLE TSO;Lo;0;L;;;;;N;;;;; 133F;ETHIOPIC SYLLABLE TSWA;Lo;0;L;;;;;N;;;;; 1340;ETHIOPIC SYLLABLE TZA;Lo;0;L;;;;;N;;;;; 1341;ETHIOPIC SYLLABLE TZU;Lo;0;L;;;;;N;;;;; 1342;ETHIOPIC SYLLABLE TZI;Lo;0;L;;;;;N;;;;; 1343;ETHIOPIC SYLLABLE TZAA;Lo;0;L;;;;;N;;;;; 1344;ETHIOPIC SYLLABLE TZEE;Lo;0;L;;;;;N;;;;; 1345;ETHIOPIC SYLLABLE TZE;Lo;0;L;;;;;N;;;;; 1346;ETHIOPIC SYLLABLE TZO;Lo;0;L;;;;;N;;;;; 1347;ETHIOPIC SYLLABLE TZOA;Lo;0;L;;;;;N;;;;; 1348;ETHIOPIC SYLLABLE FA;Lo;0;L;;;;;N;;;;; 1349;ETHIOPIC SYLLABLE FU;Lo;0;L;;;;;N;;;;; 134A;ETHIOPIC SYLLABLE FI;Lo;0;L;;;;;N;;;;; 134B;ETHIOPIC SYLLABLE FAA;Lo;0;L;;;;;N;;;;; 134C;ETHIOPIC SYLLABLE FEE;Lo;0;L;;;;;N;;;;; 134D;ETHIOPIC SYLLABLE FE;Lo;0;L;;;;;N;;;;; 134E;ETHIOPIC SYLLABLE FO;Lo;0;L;;;;;N;;;;; 134F;ETHIOPIC SYLLABLE FWA;Lo;0;L;;;;;N;;;;; 1350;ETHIOPIC SYLLABLE PA;Lo;0;L;;;;;N;;;;; 1351;ETHIOPIC SYLLABLE PU;Lo;0;L;;;;;N;;;;; 1352;ETHIOPIC SYLLABLE PI;Lo;0;L;;;;;N;;;;; 1353;ETHIOPIC SYLLABLE PAA;Lo;0;L;;;;;N;;;;; 1354;ETHIOPIC SYLLABLE PEE;Lo;0;L;;;;;N;;;;; 1355;ETHIOPIC SYLLABLE PE;Lo;0;L;;;;;N;;;;; 1356;ETHIOPIC SYLLABLE PO;Lo;0;L;;;;;N;;;;; 1357;ETHIOPIC SYLLABLE PWA;Lo;0;L;;;;;N;;;;; 1358;ETHIOPIC SYLLABLE RYA;Lo;0;L;;;;;N;;;;; 1359;ETHIOPIC SYLLABLE MYA;Lo;0;L;;;;;N;;;;; 135A;ETHIOPIC SYLLABLE FYA;Lo;0;L;;;;;N;;;;; 135D;ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK;Mn;230;NSM;;;;;N;;;;; 135E;ETHIOPIC COMBINING VOWEL LENGTH MARK;Mn;230;NSM;;;;;N;;;;; 135F;ETHIOPIC COMBINING GEMINATION MARK;Mn;230;NSM;;;;;N;;;;; 1360;ETHIOPIC SECTION MARK;Po;0;L;;;;;N;;;;; 1361;ETHIOPIC WORDSPACE;Po;0;L;;;;;N;;;;; 1362;ETHIOPIC FULL STOP;Po;0;L;;;;;N;;;;; 1363;ETHIOPIC COMMA;Po;0;L;;;;;N;;;;; 1364;ETHIOPIC SEMICOLON;Po;0;L;;;;;N;;;;; 1365;ETHIOPIC COLON;Po;0;L;;;;;N;;;;; 1366;ETHIOPIC PREFACE COLON;Po;0;L;;;;;N;;;;; 1367;ETHIOPIC QUESTION MARK;Po;0;L;;;;;N;;;;; 1368;ETHIOPIC PARAGRAPH SEPARATOR;Po;0;L;;;;;N;;;;; 1369;ETHIOPIC DIGIT ONE;No;0;L;;;1;1;N;;;;; 136A;ETHIOPIC DIGIT TWO;No;0;L;;;2;2;N;;;;; 136B;ETHIOPIC DIGIT THREE;No;0;L;;;3;3;N;;;;; 136C;ETHIOPIC DIGIT FOUR;No;0;L;;;4;4;N;;;;; 136D;ETHIOPIC DIGIT FIVE;No;0;L;;;5;5;N;;;;; 136E;ETHIOPIC DIGIT SIX;No;0;L;;;6;6;N;;;;; 136F;ETHIOPIC DIGIT SEVEN;No;0;L;;;7;7;N;;;;; 1370;ETHIOPIC DIGIT EIGHT;No;0;L;;;8;8;N;;;;; 1371;ETHIOPIC DIGIT NINE;No;0;L;;;9;9;N;;;;; 1372;ETHIOPIC NUMBER TEN;No;0;L;;;;10;N;;;;; 1373;ETHIOPIC NUMBER TWENTY;No;0;L;;;;20;N;;;;; 1374;ETHIOPIC NUMBER THIRTY;No;0;L;;;;30;N;;;;; 1375;ETHIOPIC NUMBER FORTY;No;0;L;;;;40;N;;;;; 1376;ETHIOPIC NUMBER FIFTY;No;0;L;;;;50;N;;;;; 1377;ETHIOPIC NUMBER SIXTY;No;0;L;;;;60;N;;;;; 1378;ETHIOPIC NUMBER SEVENTY;No;0;L;;;;70;N;;;;; 1379;ETHIOPIC NUMBER EIGHTY;No;0;L;;;;80;N;;;;; 137A;ETHIOPIC NUMBER NINETY;No;0;L;;;;90;N;;;;; 137B;ETHIOPIC NUMBER HUNDRED;No;0;L;;;;100;N;;;;; 137C;ETHIOPIC NUMBER TEN THOUSAND;No;0;L;;;;10000;N;;;;; 1380;ETHIOPIC SYLLABLE SEBATBEIT MWA;Lo;0;L;;;;;N;;;;; 1381;ETHIOPIC SYLLABLE MWI;Lo;0;L;;;;;N;;;;; 1382;ETHIOPIC SYLLABLE MWEE;Lo;0;L;;;;;N;;;;; 1383;ETHIOPIC SYLLABLE MWE;Lo;0;L;;;;;N;;;;; 1384;ETHIOPIC SYLLABLE SEBATBEIT BWA;Lo;0;L;;;;;N;;;;; 1385;ETHIOPIC SYLLABLE BWI;Lo;0;L;;;;;N;;;;; 1386;ETHIOPIC SYLLABLE BWEE;Lo;0;L;;;;;N;;;;; 1387;ETHIOPIC SYLLABLE BWE;Lo;0;L;;;;;N;;;;; 1388;ETHIOPIC SYLLABLE SEBATBEIT FWA;Lo;0;L;;;;;N;;;;; 1389;ETHIOPIC SYLLABLE FWI;Lo;0;L;;;;;N;;;;; 138A;ETHIOPIC SYLLABLE FWEE;Lo;0;L;;;;;N;;;;; 138B;ETHIOPIC SYLLABLE FWE;Lo;0;L;;;;;N;;;;; 138C;ETHIOPIC SYLLABLE SEBATBEIT PWA;Lo;0;L;;;;;N;;;;; 138D;ETHIOPIC SYLLABLE PWI;Lo;0;L;;;;;N;;;;; 138E;ETHIOPIC SYLLABLE PWEE;Lo;0;L;;;;;N;;;;; 138F;ETHIOPIC SYLLABLE PWE;Lo;0;L;;;;;N;;;;; 1390;ETHIOPIC TONAL MARK YIZET;So;0;ON;;;;;N;;;;; 1391;ETHIOPIC TONAL MARK DERET;So;0;ON;;;;;N;;;;; 1392;ETHIOPIC TONAL MARK RIKRIK;So;0;ON;;;;;N;;;;; 1393;ETHIOPIC TONAL MARK SHORT RIKRIK;So;0;ON;;;;;N;;;;; 1394;ETHIOPIC TONAL MARK DIFAT;So;0;ON;;;;;N;;;;; 1395;ETHIOPIC TONAL MARK KENAT;So;0;ON;;;;;N;;;;; 1396;ETHIOPIC TONAL MARK CHIRET;So;0;ON;;;;;N;;;;; 1397;ETHIOPIC TONAL MARK HIDET;So;0;ON;;;;;N;;;;; 1398;ETHIOPIC TONAL MARK DERET-HIDET;So;0;ON;;;;;N;;;;; 1399;ETHIOPIC TONAL MARK KURT;So;0;ON;;;;;N;;;;; 13A0;CHEROKEE LETTER A;Lu;0;L;;;;;N;;;;AB70; 13A1;CHEROKEE LETTER E;Lu;0;L;;;;;N;;;;AB71; 13A2;CHEROKEE LETTER I;Lu;0;L;;;;;N;;;;AB72; 13A3;CHEROKEE LETTER O;Lu;0;L;;;;;N;;;;AB73; 13A4;CHEROKEE LETTER U;Lu;0;L;;;;;N;;;;AB74; 13A5;CHEROKEE LETTER V;Lu;0;L;;;;;N;;;;AB75; 13A6;CHEROKEE LETTER GA;Lu;0;L;;;;;N;;;;AB76; 13A7;CHEROKEE LETTER KA;Lu;0;L;;;;;N;;;;AB77; 13A8;CHEROKEE LETTER GE;Lu;0;L;;;;;N;;;;AB78; 13A9;CHEROKEE LETTER GI;Lu;0;L;;;;;N;;;;AB79; 13AA;CHEROKEE LETTER GO;Lu;0;L;;;;;N;;;;AB7A; 13AB;CHEROKEE LETTER GU;Lu;0;L;;;;;N;;;;AB7B; 13AC;CHEROKEE LETTER GV;Lu;0;L;;;;;N;;;;AB7C; 13AD;CHEROKEE LETTER HA;Lu;0;L;;;;;N;;;;AB7D; 13AE;CHEROKEE LETTER HE;Lu;0;L;;;;;N;;;;AB7E; 13AF;CHEROKEE LETTER HI;Lu;0;L;;;;;N;;;;AB7F; 13B0;CHEROKEE LETTER HO;Lu;0;L;;;;;N;;;;AB80; 13B1;CHEROKEE LETTER HU;Lu;0;L;;;;;N;;;;AB81; 13B2;CHEROKEE LETTER HV;Lu;0;L;;;;;N;;;;AB82; 13B3;CHEROKEE LETTER LA;Lu;0;L;;;;;N;;;;AB83; 13B4;CHEROKEE LETTER LE;Lu;0;L;;;;;N;;;;AB84; 13B5;CHEROKEE LETTER LI;Lu;0;L;;;;;N;;;;AB85; 13B6;CHEROKEE LETTER LO;Lu;0;L;;;;;N;;;;AB86; 13B7;CHEROKEE LETTER LU;Lu;0;L;;;;;N;;;;AB87; 13B8;CHEROKEE LETTER LV;Lu;0;L;;;;;N;;;;AB88; 13B9;CHEROKEE LETTER MA;Lu;0;L;;;;;N;;;;AB89; 13BA;CHEROKEE LETTER ME;Lu;0;L;;;;;N;;;;AB8A; 13BB;CHEROKEE LETTER MI;Lu;0;L;;;;;N;;;;AB8B; 13BC;CHEROKEE LETTER MO;Lu;0;L;;;;;N;;;;AB8C; 13BD;CHEROKEE LETTER MU;Lu;0;L;;;;;N;;;;AB8D; 13BE;CHEROKEE LETTER NA;Lu;0;L;;;;;N;;;;AB8E; 13BF;CHEROKEE LETTER HNA;Lu;0;L;;;;;N;;;;AB8F; 13C0;CHEROKEE LETTER NAH;Lu;0;L;;;;;N;;;;AB90; 13C1;CHEROKEE LETTER NE;Lu;0;L;;;;;N;;;;AB91; 13C2;CHEROKEE LETTER NI;Lu;0;L;;;;;N;;;;AB92; 13C3;CHEROKEE LETTER NO;Lu;0;L;;;;;N;;;;AB93; 13C4;CHEROKEE LETTER NU;Lu;0;L;;;;;N;;;;AB94; 13C5;CHEROKEE LETTER NV;Lu;0;L;;;;;N;;;;AB95; 13C6;CHEROKEE LETTER QUA;Lu;0;L;;;;;N;;;;AB96; 13C7;CHEROKEE LETTER QUE;Lu;0;L;;;;;N;;;;AB97; 13C8;CHEROKEE LETTER QUI;Lu;0;L;;;;;N;;;;AB98; 13C9;CHEROKEE LETTER QUO;Lu;0;L;;;;;N;;;;AB99; 13CA;CHEROKEE LETTER QUU;Lu;0;L;;;;;N;;;;AB9A; 13CB;CHEROKEE LETTER QUV;Lu;0;L;;;;;N;;;;AB9B; 13CC;CHEROKEE LETTER SA;Lu;0;L;;;;;N;;;;AB9C; 13CD;CHEROKEE LETTER S;Lu;0;L;;;;;N;;;;AB9D; 13CE;CHEROKEE LETTER SE;Lu;0;L;;;;;N;;;;AB9E; 13CF;CHEROKEE LETTER SI;Lu;0;L;;;;;N;;;;AB9F; 13D0;CHEROKEE LETTER SO;Lu;0;L;;;;;N;;;;ABA0; 13D1;CHEROKEE LETTER SU;Lu;0;L;;;;;N;;;;ABA1; 13D2;CHEROKEE LETTER SV;Lu;0;L;;;;;N;;;;ABA2; 13D3;CHEROKEE LETTER DA;Lu;0;L;;;;;N;;;;ABA3; 13D4;CHEROKEE LETTER TA;Lu;0;L;;;;;N;;;;ABA4; 13D5;CHEROKEE LETTER DE;Lu;0;L;;;;;N;;;;ABA5; 13D6;CHEROKEE LETTER TE;Lu;0;L;;;;;N;;;;ABA6; 13D7;CHEROKEE LETTER DI;Lu;0;L;;;;;N;;;;ABA7; 13D8;CHEROKEE LETTER TI;Lu;0;L;;;;;N;;;;ABA8; 13D9;CHEROKEE LETTER DO;Lu;0;L;;;;;N;;;;ABA9; 13DA;CHEROKEE LETTER DU;Lu;0;L;;;;;N;;;;ABAA; 13DB;CHEROKEE LETTER DV;Lu;0;L;;;;;N;;;;ABAB; 13DC;CHEROKEE LETTER DLA;Lu;0;L;;;;;N;;;;ABAC; 13DD;CHEROKEE LETTER TLA;Lu;0;L;;;;;N;;;;ABAD; 13DE;CHEROKEE LETTER TLE;Lu;0;L;;;;;N;;;;ABAE; 13DF;CHEROKEE LETTER TLI;Lu;0;L;;;;;N;;;;ABAF; 13E0;CHEROKEE LETTER TLO;Lu;0;L;;;;;N;;;;ABB0; 13E1;CHEROKEE LETTER TLU;Lu;0;L;;;;;N;;;;ABB1; 13E2;CHEROKEE LETTER TLV;Lu;0;L;;;;;N;;;;ABB2; 13E3;CHEROKEE LETTER TSA;Lu;0;L;;;;;N;;;;ABB3; 13E4;CHEROKEE LETTER TSE;Lu;0;L;;;;;N;;;;ABB4; 13E5;CHEROKEE LETTER TSI;Lu;0;L;;;;;N;;;;ABB5; 13E6;CHEROKEE LETTER TSO;Lu;0;L;;;;;N;;;;ABB6; 13E7;CHEROKEE LETTER TSU;Lu;0;L;;;;;N;;;;ABB7; 13E8;CHEROKEE LETTER TSV;Lu;0;L;;;;;N;;;;ABB8; 13E9;CHEROKEE LETTER WA;Lu;0;L;;;;;N;;;;ABB9; 13EA;CHEROKEE LETTER WE;Lu;0;L;;;;;N;;;;ABBA; 13EB;CHEROKEE LETTER WI;Lu;0;L;;;;;N;;;;ABBB; 13EC;CHEROKEE LETTER WO;Lu;0;L;;;;;N;;;;ABBC; 13ED;CHEROKEE LETTER WU;Lu;0;L;;;;;N;;;;ABBD; 13EE;CHEROKEE LETTER WV;Lu;0;L;;;;;N;;;;ABBE; 13EF;CHEROKEE LETTER YA;Lu;0;L;;;;;N;;;;ABBF; 13F0;CHEROKEE LETTER YE;Lu;0;L;;;;;N;;;;13F8; 13F1;CHEROKEE LETTER YI;Lu;0;L;;;;;N;;;;13F9; 13F2;CHEROKEE LETTER YO;Lu;0;L;;;;;N;;;;13FA; 13F3;CHEROKEE LETTER YU;Lu;0;L;;;;;N;;;;13FB; 13F4;CHEROKEE LETTER YV;Lu;0;L;;;;;N;;;;13FC; 13F5;CHEROKEE LETTER MV;Lu;0;L;;;;;N;;;;13FD; 13F8;CHEROKEE SMALL LETTER YE;Ll;0;L;;;;;N;;;13F0;;13F0 13F9;CHEROKEE SMALL LETTER YI;Ll;0;L;;;;;N;;;13F1;;13F1 13FA;CHEROKEE SMALL LETTER YO;Ll;0;L;;;;;N;;;13F2;;13F2 13FB;CHEROKEE SMALL LETTER YU;Ll;0;L;;;;;N;;;13F3;;13F3 13FC;CHEROKEE SMALL LETTER YV;Ll;0;L;;;;;N;;;13F4;;13F4 13FD;CHEROKEE SMALL LETTER MV;Ll;0;L;;;;;N;;;13F5;;13F5 1400;CANADIAN SYLLABICS HYPHEN;Pd;0;ON;;;;;N;;;;; 1401;CANADIAN SYLLABICS E;Lo;0;L;;;;;N;;;;; 1402;CANADIAN SYLLABICS AAI;Lo;0;L;;;;;N;;;;; 1403;CANADIAN SYLLABICS I;Lo;0;L;;;;;N;;;;; 1404;CANADIAN SYLLABICS II;Lo;0;L;;;;;N;;;;; 1405;CANADIAN SYLLABICS O;Lo;0;L;;;;;N;;;;; 1406;CANADIAN SYLLABICS OO;Lo;0;L;;;;;N;;;;; 1407;CANADIAN SYLLABICS Y-CREE OO;Lo;0;L;;;;;N;;;;; 1408;CANADIAN SYLLABICS CARRIER EE;Lo;0;L;;;;;N;;;;; 1409;CANADIAN SYLLABICS CARRIER I;Lo;0;L;;;;;N;;;;; 140A;CANADIAN SYLLABICS A;Lo;0;L;;;;;N;;;;; 140B;CANADIAN SYLLABICS AA;Lo;0;L;;;;;N;;;;; 140C;CANADIAN SYLLABICS WE;Lo;0;L;;;;;N;;;;; 140D;CANADIAN SYLLABICS WEST-CREE WE;Lo;0;L;;;;;N;;;;; 140E;CANADIAN SYLLABICS WI;Lo;0;L;;;;;N;;;;; 140F;CANADIAN SYLLABICS WEST-CREE WI;Lo;0;L;;;;;N;;;;; 1410;CANADIAN SYLLABICS WII;Lo;0;L;;;;;N;;;;; 1411;CANADIAN SYLLABICS WEST-CREE WII;Lo;0;L;;;;;N;;;;; 1412;CANADIAN SYLLABICS WO;Lo;0;L;;;;;N;;;;; 1413;CANADIAN SYLLABICS WEST-CREE WO;Lo;0;L;;;;;N;;;;; 1414;CANADIAN SYLLABICS WOO;Lo;0;L;;;;;N;;;;; 1415;CANADIAN SYLLABICS WEST-CREE WOO;Lo;0;L;;;;;N;;;;; 1416;CANADIAN SYLLABICS NASKAPI WOO;Lo;0;L;;;;;N;;;;; 1417;CANADIAN SYLLABICS WA;Lo;0;L;;;;;N;;;;; 1418;CANADIAN SYLLABICS WEST-CREE WA;Lo;0;L;;;;;N;;;;; 1419;CANADIAN SYLLABICS WAA;Lo;0;L;;;;;N;;;;; 141A;CANADIAN SYLLABICS WEST-CREE WAA;Lo;0;L;;;;;N;;;;; 141B;CANADIAN SYLLABICS NASKAPI WAA;Lo;0;L;;;;;N;;;;; 141C;CANADIAN SYLLABICS AI;Lo;0;L;;;;;N;;;;; 141D;CANADIAN SYLLABICS Y-CREE W;Lo;0;L;;;;;N;;;;; 141E;CANADIAN SYLLABICS GLOTTAL STOP;Lo;0;L;;;;;N;;;;; 141F;CANADIAN SYLLABICS FINAL ACUTE;Lo;0;L;;;;;N;;;;; 1420;CANADIAN SYLLABICS FINAL GRAVE;Lo;0;L;;;;;N;;;;; 1421;CANADIAN SYLLABICS FINAL BOTTOM HALF RING;Lo;0;L;;;;;N;;;;; 1422;CANADIAN SYLLABICS FINAL TOP HALF RING;Lo;0;L;;;;;N;;;;; 1423;CANADIAN SYLLABICS FINAL RIGHT HALF RING;Lo;0;L;;;;;N;;;;; 1424;CANADIAN SYLLABICS FINAL RING;Lo;0;L;;;;;N;;;;; 1425;CANADIAN SYLLABICS FINAL DOUBLE ACUTE;Lo;0;L;;;;;N;;;;; 1426;CANADIAN SYLLABICS FINAL DOUBLE SHORT VERTICAL STROKES;Lo;0;L;;;;;N;;;;; 1427;CANADIAN SYLLABICS FINAL MIDDLE DOT;Lo;0;L;;;;;N;;;;; 1428;CANADIAN SYLLABICS FINAL SHORT HORIZONTAL STROKE;Lo;0;L;;;;;N;;;;; 1429;CANADIAN SYLLABICS FINAL PLUS;Lo;0;L;;;;;N;;;;; 142A;CANADIAN SYLLABICS FINAL DOWN TACK;Lo;0;L;;;;;N;;;;; 142B;CANADIAN SYLLABICS EN;Lo;0;L;;;;;N;;;;; 142C;CANADIAN SYLLABICS IN;Lo;0;L;;;;;N;;;;; 142D;CANADIAN SYLLABICS ON;Lo;0;L;;;;;N;;;;; 142E;CANADIAN SYLLABICS AN;Lo;0;L;;;;;N;;;;; 142F;CANADIAN SYLLABICS PE;Lo;0;L;;;;;N;;;;; 1430;CANADIAN SYLLABICS PAAI;Lo;0;L;;;;;N;;;;; 1431;CANADIAN SYLLABICS PI;Lo;0;L;;;;;N;;;;; 1432;CANADIAN SYLLABICS PII;Lo;0;L;;;;;N;;;;; 1433;CANADIAN SYLLABICS PO;Lo;0;L;;;;;N;;;;; 1434;CANADIAN SYLLABICS POO;Lo;0;L;;;;;N;;;;; 1435;CANADIAN SYLLABICS Y-CREE POO;Lo;0;L;;;;;N;;;;; 1436;CANADIAN SYLLABICS CARRIER HEE;Lo;0;L;;;;;N;;;;; 1437;CANADIAN SYLLABICS CARRIER HI;Lo;0;L;;;;;N;;;;; 1438;CANADIAN SYLLABICS PA;Lo;0;L;;;;;N;;;;; 1439;CANADIAN SYLLABICS PAA;Lo;0;L;;;;;N;;;;; 143A;CANADIAN SYLLABICS PWE;Lo;0;L;;;;;N;;;;; 143B;CANADIAN SYLLABICS WEST-CREE PWE;Lo;0;L;;;;;N;;;;; 143C;CANADIAN SYLLABICS PWI;Lo;0;L;;;;;N;;;;; 143D;CANADIAN SYLLABICS WEST-CREE PWI;Lo;0;L;;;;;N;;;;; 143E;CANADIAN SYLLABICS PWII;Lo;0;L;;;;;N;;;;; 143F;CANADIAN SYLLABICS WEST-CREE PWII;Lo;0;L;;;;;N;;;;; 1440;CANADIAN SYLLABICS PWO;Lo;0;L;;;;;N;;;;; 1441;CANADIAN SYLLABICS WEST-CREE PWO;Lo;0;L;;;;;N;;;;; 1442;CANADIAN SYLLABICS PWOO;Lo;0;L;;;;;N;;;;; 1443;CANADIAN SYLLABICS WEST-CREE PWOO;Lo;0;L;;;;;N;;;;; 1444;CANADIAN SYLLABICS PWA;Lo;0;L;;;;;N;;;;; 1445;CANADIAN SYLLABICS WEST-CREE PWA;Lo;0;L;;;;;N;;;;; 1446;CANADIAN SYLLABICS PWAA;Lo;0;L;;;;;N;;;;; 1447;CANADIAN SYLLABICS WEST-CREE PWAA;Lo;0;L;;;;;N;;;;; 1448;CANADIAN SYLLABICS Y-CREE PWAA;Lo;0;L;;;;;N;;;;; 1449;CANADIAN SYLLABICS P;Lo;0;L;;;;;N;;;;; 144A;CANADIAN SYLLABICS WEST-CREE P;Lo;0;L;;;;;N;;;;; 144B;CANADIAN SYLLABICS CARRIER H;Lo;0;L;;;;;N;;;;; 144C;CANADIAN SYLLABICS TE;Lo;0;L;;;;;N;;;;; 144D;CANADIAN SYLLABICS TAAI;Lo;0;L;;;;;N;;;;; 144E;CANADIAN SYLLABICS TI;Lo;0;L;;;;;N;;;;; 144F;CANADIAN SYLLABICS TII;Lo;0;L;;;;;N;;;;; 1450;CANADIAN SYLLABICS TO;Lo;0;L;;;;;N;;;;; 1451;CANADIAN SYLLABICS TOO;Lo;0;L;;;;;N;;;;; 1452;CANADIAN SYLLABICS Y-CREE TOO;Lo;0;L;;;;;N;;;;; 1453;CANADIAN SYLLABICS CARRIER DEE;Lo;0;L;;;;;N;;;;; 1454;CANADIAN SYLLABICS CARRIER DI;Lo;0;L;;;;;N;;;;; 1455;CANADIAN SYLLABICS TA;Lo;0;L;;;;;N;;;;; 1456;CANADIAN SYLLABICS TAA;Lo;0;L;;;;;N;;;;; 1457;CANADIAN SYLLABICS TWE;Lo;0;L;;;;;N;;;;; 1458;CANADIAN SYLLABICS WEST-CREE TWE;Lo;0;L;;;;;N;;;;; 1459;CANADIAN SYLLABICS TWI;Lo;0;L;;;;;N;;;;; 145A;CANADIAN SYLLABICS WEST-CREE TWI;Lo;0;L;;;;;N;;;;; 145B;CANADIAN SYLLABICS TWII;Lo;0;L;;;;;N;;;;; 145C;CANADIAN SYLLABICS WEST-CREE TWII;Lo;0;L;;;;;N;;;;; 145D;CANADIAN SYLLABICS TWO;Lo;0;L;;;;;N;;;;; 145E;CANADIAN SYLLABICS WEST-CREE TWO;Lo;0;L;;;;;N;;;;; 145F;CANADIAN SYLLABICS TWOO;Lo;0;L;;;;;N;;;;; 1460;CANADIAN SYLLABICS WEST-CREE TWOO;Lo;0;L;;;;;N;;;;; 1461;CANADIAN SYLLABICS TWA;Lo;0;L;;;;;N;;;;; 1462;CANADIAN SYLLABICS WEST-CREE TWA;Lo;0;L;;;;;N;;;;; 1463;CANADIAN SYLLABICS TWAA;Lo;0;L;;;;;N;;;;; 1464;CANADIAN SYLLABICS WEST-CREE TWAA;Lo;0;L;;;;;N;;;;; 1465;CANADIAN SYLLABICS NASKAPI TWAA;Lo;0;L;;;;;N;;;;; 1466;CANADIAN SYLLABICS T;Lo;0;L;;;;;N;;;;; 1467;CANADIAN SYLLABICS TTE;Lo;0;L;;;;;N;;;;; 1468;CANADIAN SYLLABICS TTI;Lo;0;L;;;;;N;;;;; 1469;CANADIAN SYLLABICS TTO;Lo;0;L;;;;;N;;;;; 146A;CANADIAN SYLLABICS TTA;Lo;0;L;;;;;N;;;;; 146B;CANADIAN SYLLABICS KE;Lo;0;L;;;;;N;;;;; 146C;CANADIAN SYLLABICS KAAI;Lo;0;L;;;;;N;;;;; 146D;CANADIAN SYLLABICS KI;Lo;0;L;;;;;N;;;;; 146E;CANADIAN SYLLABICS KII;Lo;0;L;;;;;N;;;;; 146F;CANADIAN SYLLABICS KO;Lo;0;L;;;;;N;;;;; 1470;CANADIAN SYLLABICS KOO;Lo;0;L;;;;;N;;;;; 1471;CANADIAN SYLLABICS Y-CREE KOO;Lo;0;L;;;;;N;;;;; 1472;CANADIAN SYLLABICS KA;Lo;0;L;;;;;N;;;;; 1473;CANADIAN SYLLABICS KAA;Lo;0;L;;;;;N;;;;; 1474;CANADIAN SYLLABICS KWE;Lo;0;L;;;;;N;;;;; 1475;CANADIAN SYLLABICS WEST-CREE KWE;Lo;0;L;;;;;N;;;;; 1476;CANADIAN SYLLABICS KWI;Lo;0;L;;;;;N;;;;; 1477;CANADIAN SYLLABICS WEST-CREE KWI;Lo;0;L;;;;;N;;;;; 1478;CANADIAN SYLLABICS KWII;Lo;0;L;;;;;N;;;;; 1479;CANADIAN SYLLABICS WEST-CREE KWII;Lo;0;L;;;;;N;;;;; 147A;CANADIAN SYLLABICS KWO;Lo;0;L;;;;;N;;;;; 147B;CANADIAN SYLLABICS WEST-CREE KWO;Lo;0;L;;;;;N;;;;; 147C;CANADIAN SYLLABICS KWOO;Lo;0;L;;;;;N;;;;; 147D;CANADIAN SYLLABICS WEST-CREE KWOO;Lo;0;L;;;;;N;;;;; 147E;CANADIAN SYLLABICS KWA;Lo;0;L;;;;;N;;;;; 147F;CANADIAN SYLLABICS WEST-CREE KWA;Lo;0;L;;;;;N;;;;; 1480;CANADIAN SYLLABICS KWAA;Lo;0;L;;;;;N;;;;; 1481;CANADIAN SYLLABICS WEST-CREE KWAA;Lo;0;L;;;;;N;;;;; 1482;CANADIAN SYLLABICS NASKAPI KWAA;Lo;0;L;;;;;N;;;;; 1483;CANADIAN SYLLABICS K;Lo;0;L;;;;;N;;;;; 1484;CANADIAN SYLLABICS KW;Lo;0;L;;;;;N;;;;; 1485;CANADIAN SYLLABICS SOUTH-SLAVEY KEH;Lo;0;L;;;;;N;;;;; 1486;CANADIAN SYLLABICS SOUTH-SLAVEY KIH;Lo;0;L;;;;;N;;;;; 1487;CANADIAN SYLLABICS SOUTH-SLAVEY KOH;Lo;0;L;;;;;N;;;;; 1488;CANADIAN SYLLABICS SOUTH-SLAVEY KAH;Lo;0;L;;;;;N;;;;; 1489;CANADIAN SYLLABICS CE;Lo;0;L;;;;;N;;;;; 148A;CANADIAN SYLLABICS CAAI;Lo;0;L;;;;;N;;;;; 148B;CANADIAN SYLLABICS CI;Lo;0;L;;;;;N;;;;; 148C;CANADIAN SYLLABICS CII;Lo;0;L;;;;;N;;;;; 148D;CANADIAN SYLLABICS CO;Lo;0;L;;;;;N;;;;; 148E;CANADIAN SYLLABICS COO;Lo;0;L;;;;;N;;;;; 148F;CANADIAN SYLLABICS Y-CREE COO;Lo;0;L;;;;;N;;;;; 1490;CANADIAN SYLLABICS CA;Lo;0;L;;;;;N;;;;; 1491;CANADIAN SYLLABICS CAA;Lo;0;L;;;;;N;;;;; 1492;CANADIAN SYLLABICS CWE;Lo;0;L;;;;;N;;;;; 1493;CANADIAN SYLLABICS WEST-CREE CWE;Lo;0;L;;;;;N;;;;; 1494;CANADIAN SYLLABICS CWI;Lo;0;L;;;;;N;;;;; 1495;CANADIAN SYLLABICS WEST-CREE CWI;Lo;0;L;;;;;N;;;;; 1496;CANADIAN SYLLABICS CWII;Lo;0;L;;;;;N;;;;; 1497;CANADIAN SYLLABICS WEST-CREE CWII;Lo;0;L;;;;;N;;;;; 1498;CANADIAN SYLLABICS CWO;Lo;0;L;;;;;N;;;;; 1499;CANADIAN SYLLABICS WEST-CREE CWO;Lo;0;L;;;;;N;;;;; 149A;CANADIAN SYLLABICS CWOO;Lo;0;L;;;;;N;;;;; 149B;CANADIAN SYLLABICS WEST-CREE CWOO;Lo;0;L;;;;;N;;;;; 149C;CANADIAN SYLLABICS CWA;Lo;0;L;;;;;N;;;;; 149D;CANADIAN SYLLABICS WEST-CREE CWA;Lo;0;L;;;;;N;;;;; 149E;CANADIAN SYLLABICS CWAA;Lo;0;L;;;;;N;;;;; 149F;CANADIAN SYLLABICS WEST-CREE CWAA;Lo;0;L;;;;;N;;;;; 14A0;CANADIAN SYLLABICS NASKAPI CWAA;Lo;0;L;;;;;N;;;;; 14A1;CANADIAN SYLLABICS C;Lo;0;L;;;;;N;;;;; 14A2;CANADIAN SYLLABICS SAYISI TH;Lo;0;L;;;;;N;;;;; 14A3;CANADIAN SYLLABICS ME;Lo;0;L;;;;;N;;;;; 14A4;CANADIAN SYLLABICS MAAI;Lo;0;L;;;;;N;;;;; 14A5;CANADIAN SYLLABICS MI;Lo;0;L;;;;;N;;;;; 14A6;CANADIAN SYLLABICS MII;Lo;0;L;;;;;N;;;;; 14A7;CANADIAN SYLLABICS MO;Lo;0;L;;;;;N;;;;; 14A8;CANADIAN SYLLABICS MOO;Lo;0;L;;;;;N;;;;; 14A9;CANADIAN SYLLABICS Y-CREE MOO;Lo;0;L;;;;;N;;;;; 14AA;CANADIAN SYLLABICS MA;Lo;0;L;;;;;N;;;;; 14AB;CANADIAN SYLLABICS MAA;Lo;0;L;;;;;N;;;;; 14AC;CANADIAN SYLLABICS MWE;Lo;0;L;;;;;N;;;;; 14AD;CANADIAN SYLLABICS WEST-CREE MWE;Lo;0;L;;;;;N;;;;; 14AE;CANADIAN SYLLABICS MWI;Lo;0;L;;;;;N;;;;; 14AF;CANADIAN SYLLABICS WEST-CREE MWI;Lo;0;L;;;;;N;;;;; 14B0;CANADIAN SYLLABICS MWII;Lo;0;L;;;;;N;;;;; 14B1;CANADIAN SYLLABICS WEST-CREE MWII;Lo;0;L;;;;;N;;;;; 14B2;CANADIAN SYLLABICS MWO;Lo;0;L;;;;;N;;;;; 14B3;CANADIAN SYLLABICS WEST-CREE MWO;Lo;0;L;;;;;N;;;;; 14B4;CANADIAN SYLLABICS MWOO;Lo;0;L;;;;;N;;;;; 14B5;CANADIAN SYLLABICS WEST-CREE MWOO;Lo;0;L;;;;;N;;;;; 14B6;CANADIAN SYLLABICS MWA;Lo;0;L;;;;;N;;;;; 14B7;CANADIAN SYLLABICS WEST-CREE MWA;Lo;0;L;;;;;N;;;;; 14B8;CANADIAN SYLLABICS MWAA;Lo;0;L;;;;;N;;;;; 14B9;CANADIAN SYLLABICS WEST-CREE MWAA;Lo;0;L;;;;;N;;;;; 14BA;CANADIAN SYLLABICS NASKAPI MWAA;Lo;0;L;;;;;N;;;;; 14BB;CANADIAN SYLLABICS M;Lo;0;L;;;;;N;;;;; 14BC;CANADIAN SYLLABICS WEST-CREE M;Lo;0;L;;;;;N;;;;; 14BD;CANADIAN SYLLABICS MH;Lo;0;L;;;;;N;;;;; 14BE;CANADIAN SYLLABICS ATHAPASCAN M;Lo;0;L;;;;;N;;;;; 14BF;CANADIAN SYLLABICS SAYISI M;Lo;0;L;;;;;N;;;;; 14C0;CANADIAN SYLLABICS NE;Lo;0;L;;;;;N;;;;; 14C1;CANADIAN SYLLABICS NAAI;Lo;0;L;;;;;N;;;;; 14C2;CANADIAN SYLLABICS NI;Lo;0;L;;;;;N;;;;; 14C3;CANADIAN SYLLABICS NII;Lo;0;L;;;;;N;;;;; 14C4;CANADIAN SYLLABICS NO;Lo;0;L;;;;;N;;;;; 14C5;CANADIAN SYLLABICS NOO;Lo;0;L;;;;;N;;;;; 14C6;CANADIAN SYLLABICS Y-CREE NOO;Lo;0;L;;;;;N;;;;; 14C7;CANADIAN SYLLABICS NA;Lo;0;L;;;;;N;;;;; 14C8;CANADIAN SYLLABICS NAA;Lo;0;L;;;;;N;;;;; 14C9;CANADIAN SYLLABICS NWE;Lo;0;L;;;;;N;;;;; 14CA;CANADIAN SYLLABICS WEST-CREE NWE;Lo;0;L;;;;;N;;;;; 14CB;CANADIAN SYLLABICS NWA;Lo;0;L;;;;;N;;;;; 14CC;CANADIAN SYLLABICS WEST-CREE NWA;Lo;0;L;;;;;N;;;;; 14CD;CANADIAN SYLLABICS NWAA;Lo;0;L;;;;;N;;;;; 14CE;CANADIAN SYLLABICS WEST-CREE NWAA;Lo;0;L;;;;;N;;;;; 14CF;CANADIAN SYLLABICS NASKAPI NWAA;Lo;0;L;;;;;N;;;;; 14D0;CANADIAN SYLLABICS N;Lo;0;L;;;;;N;;;;; 14D1;CANADIAN SYLLABICS CARRIER NG;Lo;0;L;;;;;N;;;;; 14D2;CANADIAN SYLLABICS NH;Lo;0;L;;;;;N;;;;; 14D3;CANADIAN SYLLABICS LE;Lo;0;L;;;;;N;;;;; 14D4;CANADIAN SYLLABICS LAAI;Lo;0;L;;;;;N;;;;; 14D5;CANADIAN SYLLABICS LI;Lo;0;L;;;;;N;;;;; 14D6;CANADIAN SYLLABICS LII;Lo;0;L;;;;;N;;;;; 14D7;CANADIAN SYLLABICS LO;Lo;0;L;;;;;N;;;;; 14D8;CANADIAN SYLLABICS LOO;Lo;0;L;;;;;N;;;;; 14D9;CANADIAN SYLLABICS Y-CREE LOO;Lo;0;L;;;;;N;;;;; 14DA;CANADIAN SYLLABICS LA;Lo;0;L;;;;;N;;;;; 14DB;CANADIAN SYLLABICS LAA;Lo;0;L;;;;;N;;;;; 14DC;CANADIAN SYLLABICS LWE;Lo;0;L;;;;;N;;;;; 14DD;CANADIAN SYLLABICS WEST-CREE LWE;Lo;0;L;;;;;N;;;;; 14DE;CANADIAN SYLLABICS LWI;Lo;0;L;;;;;N;;;;; 14DF;CANADIAN SYLLABICS WEST-CREE LWI;Lo;0;L;;;;;N;;;;; 14E0;CANADIAN SYLLABICS LWII;Lo;0;L;;;;;N;;;;; 14E1;CANADIAN SYLLABICS WEST-CREE LWII;Lo;0;L;;;;;N;;;;; 14E2;CANADIAN SYLLABICS LWO;Lo;0;L;;;;;N;;;;; 14E3;CANADIAN SYLLABICS WEST-CREE LWO;Lo;0;L;;;;;N;;;;; 14E4;CANADIAN SYLLABICS LWOO;Lo;0;L;;;;;N;;;;; 14E5;CANADIAN SYLLABICS WEST-CREE LWOO;Lo;0;L;;;;;N;;;;; 14E6;CANADIAN SYLLABICS LWA;Lo;0;L;;;;;N;;;;; 14E7;CANADIAN SYLLABICS WEST-CREE LWA;Lo;0;L;;;;;N;;;;; 14E8;CANADIAN SYLLABICS LWAA;Lo;0;L;;;;;N;;;;; 14E9;CANADIAN SYLLABICS WEST-CREE LWAA;Lo;0;L;;;;;N;;;;; 14EA;CANADIAN SYLLABICS L;Lo;0;L;;;;;N;;;;; 14EB;CANADIAN SYLLABICS WEST-CREE L;Lo;0;L;;;;;N;;;;; 14EC;CANADIAN SYLLABICS MEDIAL L;Lo;0;L;;;;;N;;;;; 14ED;CANADIAN SYLLABICS SE;Lo;0;L;;;;;N;;;;; 14EE;CANADIAN SYLLABICS SAAI;Lo;0;L;;;;;N;;;;; 14EF;CANADIAN SYLLABICS SI;Lo;0;L;;;;;N;;;;; 14F0;CANADIAN SYLLABICS SII;Lo;0;L;;;;;N;;;;; 14F1;CANADIAN SYLLABICS SO;Lo;0;L;;;;;N;;;;; 14F2;CANADIAN SYLLABICS SOO;Lo;0;L;;;;;N;;;;; 14F3;CANADIAN SYLLABICS Y-CREE SOO;Lo;0;L;;;;;N;;;;; 14F4;CANADIAN SYLLABICS SA;Lo;0;L;;;;;N;;;;; 14F5;CANADIAN SYLLABICS SAA;Lo;0;L;;;;;N;;;;; 14F6;CANADIAN SYLLABICS SWE;Lo;0;L;;;;;N;;;;; 14F7;CANADIAN SYLLABICS WEST-CREE SWE;Lo;0;L;;;;;N;;;;; 14F8;CANADIAN SYLLABICS SWI;Lo;0;L;;;;;N;;;;; 14F9;CANADIAN SYLLABICS WEST-CREE SWI;Lo;0;L;;;;;N;;;;; 14FA;CANADIAN SYLLABICS SWII;Lo;0;L;;;;;N;;;;; 14FB;CANADIAN SYLLABICS WEST-CREE SWII;Lo;0;L;;;;;N;;;;; 14FC;CANADIAN SYLLABICS SWO;Lo;0;L;;;;;N;;;;; 14FD;CANADIAN SYLLABICS WEST-CREE SWO;Lo;0;L;;;;;N;;;;; 14FE;CANADIAN SYLLABICS SWOO;Lo;0;L;;;;;N;;;;; 14FF;CANADIAN SYLLABICS WEST-CREE SWOO;Lo;0;L;;;;;N;;;;; 1500;CANADIAN SYLLABICS SWA;Lo;0;L;;;;;N;;;;; 1501;CANADIAN SYLLABICS WEST-CREE SWA;Lo;0;L;;;;;N;;;;; 1502;CANADIAN SYLLABICS SWAA;Lo;0;L;;;;;N;;;;; 1503;CANADIAN SYLLABICS WEST-CREE SWAA;Lo;0;L;;;;;N;;;;; 1504;CANADIAN SYLLABICS NASKAPI SWAA;Lo;0;L;;;;;N;;;;; 1505;CANADIAN SYLLABICS S;Lo;0;L;;;;;N;;;;; 1506;CANADIAN SYLLABICS ATHAPASCAN S;Lo;0;L;;;;;N;;;;; 1507;CANADIAN SYLLABICS SW;Lo;0;L;;;;;N;;;;; 1508;CANADIAN SYLLABICS BLACKFOOT S;Lo;0;L;;;;;N;;;;; 1509;CANADIAN SYLLABICS MOOSE-CREE SK;Lo;0;L;;;;;N;;;;; 150A;CANADIAN SYLLABICS NASKAPI SKW;Lo;0;L;;;;;N;;;;; 150B;CANADIAN SYLLABICS NASKAPI S-W;Lo;0;L;;;;;N;;;;; 150C;CANADIAN SYLLABICS NASKAPI SPWA;Lo;0;L;;;;;N;;;;; 150D;CANADIAN SYLLABICS NASKAPI STWA;Lo;0;L;;;;;N;;;;; 150E;CANADIAN SYLLABICS NASKAPI SKWA;Lo;0;L;;;;;N;;;;; 150F;CANADIAN SYLLABICS NASKAPI SCWA;Lo;0;L;;;;;N;;;;; 1510;CANADIAN SYLLABICS SHE;Lo;0;L;;;;;N;;;;; 1511;CANADIAN SYLLABICS SHI;Lo;0;L;;;;;N;;;;; 1512;CANADIAN SYLLABICS SHII;Lo;0;L;;;;;N;;;;; 1513;CANADIAN SYLLABICS SHO;Lo;0;L;;;;;N;;;;; 1514;CANADIAN SYLLABICS SHOO;Lo;0;L;;;;;N;;;;; 1515;CANADIAN SYLLABICS SHA;Lo;0;L;;;;;N;;;;; 1516;CANADIAN SYLLABICS SHAA;Lo;0;L;;;;;N;;;;; 1517;CANADIAN SYLLABICS SHWE;Lo;0;L;;;;;N;;;;; 1518;CANADIAN SYLLABICS WEST-CREE SHWE;Lo;0;L;;;;;N;;;;; 1519;CANADIAN SYLLABICS SHWI;Lo;0;L;;;;;N;;;;; 151A;CANADIAN SYLLABICS WEST-CREE SHWI;Lo;0;L;;;;;N;;;;; 151B;CANADIAN SYLLABICS SHWII;Lo;0;L;;;;;N;;;;; 151C;CANADIAN SYLLABICS WEST-CREE SHWII;Lo;0;L;;;;;N;;;;; 151D;CANADIAN SYLLABICS SHWO;Lo;0;L;;;;;N;;;;; 151E;CANADIAN SYLLABICS WEST-CREE SHWO;Lo;0;L;;;;;N;;;;; 151F;CANADIAN SYLLABICS SHWOO;Lo;0;L;;;;;N;;;;; 1520;CANADIAN SYLLABICS WEST-CREE SHWOO;Lo;0;L;;;;;N;;;;; 1521;CANADIAN SYLLABICS SHWA;Lo;0;L;;;;;N;;;;; 1522;CANADIAN SYLLABICS WEST-CREE SHWA;Lo;0;L;;;;;N;;;;; 1523;CANADIAN SYLLABICS SHWAA;Lo;0;L;;;;;N;;;;; 1524;CANADIAN SYLLABICS WEST-CREE SHWAA;Lo;0;L;;;;;N;;;;; 1525;CANADIAN SYLLABICS SH;Lo;0;L;;;;;N;;;;; 1526;CANADIAN SYLLABICS YE;Lo;0;L;;;;;N;;;;; 1527;CANADIAN SYLLABICS YAAI;Lo;0;L;;;;;N;;;;; 1528;CANADIAN SYLLABICS YI;Lo;0;L;;;;;N;;;;; 1529;CANADIAN SYLLABICS YII;Lo;0;L;;;;;N;;;;; 152A;CANADIAN SYLLABICS YO;Lo;0;L;;;;;N;;;;; 152B;CANADIAN SYLLABICS YOO;Lo;0;L;;;;;N;;;;; 152C;CANADIAN SYLLABICS Y-CREE YOO;Lo;0;L;;;;;N;;;;; 152D;CANADIAN SYLLABICS YA;Lo;0;L;;;;;N;;;;; 152E;CANADIAN SYLLABICS YAA;Lo;0;L;;;;;N;;;;; 152F;CANADIAN SYLLABICS YWE;Lo;0;L;;;;;N;;;;; 1530;CANADIAN SYLLABICS WEST-CREE YWE;Lo;0;L;;;;;N;;;;; 1531;CANADIAN SYLLABICS YWI;Lo;0;L;;;;;N;;;;; 1532;CANADIAN SYLLABICS WEST-CREE YWI;Lo;0;L;;;;;N;;;;; 1533;CANADIAN SYLLABICS YWII;Lo;0;L;;;;;N;;;;; 1534;CANADIAN SYLLABICS WEST-CREE YWII;Lo;0;L;;;;;N;;;;; 1535;CANADIAN SYLLABICS YWO;Lo;0;L;;;;;N;;;;; 1536;CANADIAN SYLLABICS WEST-CREE YWO;Lo;0;L;;;;;N;;;;; 1537;CANADIAN SYLLABICS YWOO;Lo;0;L;;;;;N;;;;; 1538;CANADIAN SYLLABICS WEST-CREE YWOO;Lo;0;L;;;;;N;;;;; 1539;CANADIAN SYLLABICS YWA;Lo;0;L;;;;;N;;;;; 153A;CANADIAN SYLLABICS WEST-CREE YWA;Lo;0;L;;;;;N;;;;; 153B;CANADIAN SYLLABICS YWAA;Lo;0;L;;;;;N;;;;; 153C;CANADIAN SYLLABICS WEST-CREE YWAA;Lo;0;L;;;;;N;;;;; 153D;CANADIAN SYLLABICS NASKAPI YWAA;Lo;0;L;;;;;N;;;;; 153E;CANADIAN SYLLABICS Y;Lo;0;L;;;;;N;;;;; 153F;CANADIAN SYLLABICS BIBLE-CREE Y;Lo;0;L;;;;;N;;;;; 1540;CANADIAN SYLLABICS WEST-CREE Y;Lo;0;L;;;;;N;;;;; 1541;CANADIAN SYLLABICS SAYISI YI;Lo;0;L;;;;;N;;;;; 1542;CANADIAN SYLLABICS RE;Lo;0;L;;;;;N;;;;; 1543;CANADIAN SYLLABICS R-CREE RE;Lo;0;L;;;;;N;;;;; 1544;CANADIAN SYLLABICS WEST-CREE LE;Lo;0;L;;;;;N;;;;; 1545;CANADIAN SYLLABICS RAAI;Lo;0;L;;;;;N;;;;; 1546;CANADIAN SYLLABICS RI;Lo;0;L;;;;;N;;;;; 1547;CANADIAN SYLLABICS RII;Lo;0;L;;;;;N;;;;; 1548;CANADIAN SYLLABICS RO;Lo;0;L;;;;;N;;;;; 1549;CANADIAN SYLLABICS ROO;Lo;0;L;;;;;N;;;;; 154A;CANADIAN SYLLABICS WEST-CREE LO;Lo;0;L;;;;;N;;;;; 154B;CANADIAN SYLLABICS RA;Lo;0;L;;;;;N;;;;; 154C;CANADIAN SYLLABICS RAA;Lo;0;L;;;;;N;;;;; 154D;CANADIAN SYLLABICS WEST-CREE LA;Lo;0;L;;;;;N;;;;; 154E;CANADIAN SYLLABICS RWAA;Lo;0;L;;;;;N;;;;; 154F;CANADIAN SYLLABICS WEST-CREE RWAA;Lo;0;L;;;;;N;;;;; 1550;CANADIAN SYLLABICS R;Lo;0;L;;;;;N;;;;; 1551;CANADIAN SYLLABICS WEST-CREE R;Lo;0;L;;;;;N;;;;; 1552;CANADIAN SYLLABICS MEDIAL R;Lo;0;L;;;;;N;;;;; 1553;CANADIAN SYLLABICS FE;Lo;0;L;;;;;N;;;;; 1554;CANADIAN SYLLABICS FAAI;Lo;0;L;;;;;N;;;;; 1555;CANADIAN SYLLABICS FI;Lo;0;L;;;;;N;;;;; 1556;CANADIAN SYLLABICS FII;Lo;0;L;;;;;N;;;;; 1557;CANADIAN SYLLABICS FO;Lo;0;L;;;;;N;;;;; 1558;CANADIAN SYLLABICS FOO;Lo;0;L;;;;;N;;;;; 1559;CANADIAN SYLLABICS FA;Lo;0;L;;;;;N;;;;; 155A;CANADIAN SYLLABICS FAA;Lo;0;L;;;;;N;;;;; 155B;CANADIAN SYLLABICS FWAA;Lo;0;L;;;;;N;;;;; 155C;CANADIAN SYLLABICS WEST-CREE FWAA;Lo;0;L;;;;;N;;;;; 155D;CANADIAN SYLLABICS F;Lo;0;L;;;;;N;;;;; 155E;CANADIAN SYLLABICS THE;Lo;0;L;;;;;N;;;;; 155F;CANADIAN SYLLABICS N-CREE THE;Lo;0;L;;;;;N;;;;; 1560;CANADIAN SYLLABICS THI;Lo;0;L;;;;;N;;;;; 1561;CANADIAN SYLLABICS N-CREE THI;Lo;0;L;;;;;N;;;;; 1562;CANADIAN SYLLABICS THII;Lo;0;L;;;;;N;;;;; 1563;CANADIAN SYLLABICS N-CREE THII;Lo;0;L;;;;;N;;;;; 1564;CANADIAN SYLLABICS THO;Lo;0;L;;;;;N;;;;; 1565;CANADIAN SYLLABICS THOO;Lo;0;L;;;;;N;;;;; 1566;CANADIAN SYLLABICS THA;Lo;0;L;;;;;N;;;;; 1567;CANADIAN SYLLABICS THAA;Lo;0;L;;;;;N;;;;; 1568;CANADIAN SYLLABICS THWAA;Lo;0;L;;;;;N;;;;; 1569;CANADIAN SYLLABICS WEST-CREE THWAA;Lo;0;L;;;;;N;;;;; 156A;CANADIAN SYLLABICS TH;Lo;0;L;;;;;N;;;;; 156B;CANADIAN SYLLABICS TTHE;Lo;0;L;;;;;N;;;;; 156C;CANADIAN SYLLABICS TTHI;Lo;0;L;;;;;N;;;;; 156D;CANADIAN SYLLABICS TTHO;Lo;0;L;;;;;N;;;;; 156E;CANADIAN SYLLABICS TTHA;Lo;0;L;;;;;N;;;;; 156F;CANADIAN SYLLABICS TTH;Lo;0;L;;;;;N;;;;; 1570;CANADIAN SYLLABICS TYE;Lo;0;L;;;;;N;;;;; 1571;CANADIAN SYLLABICS TYI;Lo;0;L;;;;;N;;;;; 1572;CANADIAN SYLLABICS TYO;Lo;0;L;;;;;N;;;;; 1573;CANADIAN SYLLABICS TYA;Lo;0;L;;;;;N;;;;; 1574;CANADIAN SYLLABICS NUNAVIK HE;Lo;0;L;;;;;N;;;;; 1575;CANADIAN SYLLABICS NUNAVIK HI;Lo;0;L;;;;;N;;;;; 1576;CANADIAN SYLLABICS NUNAVIK HII;Lo;0;L;;;;;N;;;;; 1577;CANADIAN SYLLABICS NUNAVIK HO;Lo;0;L;;;;;N;;;;; 1578;CANADIAN SYLLABICS NUNAVIK HOO;Lo;0;L;;;;;N;;;;; 1579;CANADIAN SYLLABICS NUNAVIK HA;Lo;0;L;;;;;N;;;;; 157A;CANADIAN SYLLABICS NUNAVIK HAA;Lo;0;L;;;;;N;;;;; 157B;CANADIAN SYLLABICS NUNAVIK H;Lo;0;L;;;;;N;;;;; 157C;CANADIAN SYLLABICS NUNAVUT H;Lo;0;L;;;;;N;;;;; 157D;CANADIAN SYLLABICS HK;Lo;0;L;;;;;N;;;;; 157E;CANADIAN SYLLABICS QAAI;Lo;0;L;;;;;N;;;;; 157F;CANADIAN SYLLABICS QI;Lo;0;L;;;;;N;;;;; 1580;CANADIAN SYLLABICS QII;Lo;0;L;;;;;N;;;;; 1581;CANADIAN SYLLABICS QO;Lo;0;L;;;;;N;;;;; 1582;CANADIAN SYLLABICS QOO;Lo;0;L;;;;;N;;;;; 1583;CANADIAN SYLLABICS QA;Lo;0;L;;;;;N;;;;; 1584;CANADIAN SYLLABICS QAA;Lo;0;L;;;;;N;;;;; 1585;CANADIAN SYLLABICS Q;Lo;0;L;;;;;N;;;;; 1586;CANADIAN SYLLABICS TLHE;Lo;0;L;;;;;N;;;;; 1587;CANADIAN SYLLABICS TLHI;Lo;0;L;;;;;N;;;;; 1588;CANADIAN SYLLABICS TLHO;Lo;0;L;;;;;N;;;;; 1589;CANADIAN SYLLABICS TLHA;Lo;0;L;;;;;N;;;;; 158A;CANADIAN SYLLABICS WEST-CREE RE;Lo;0;L;;;;;N;;;;; 158B;CANADIAN SYLLABICS WEST-CREE RI;Lo;0;L;;;;;N;;;;; 158C;CANADIAN SYLLABICS WEST-CREE RO;Lo;0;L;;;;;N;;;;; 158D;CANADIAN SYLLABICS WEST-CREE RA;Lo;0;L;;;;;N;;;;; 158E;CANADIAN SYLLABICS NGAAI;Lo;0;L;;;;;N;;;;; 158F;CANADIAN SYLLABICS NGI;Lo;0;L;;;;;N;;;;; 1590;CANADIAN SYLLABICS NGII;Lo;0;L;;;;;N;;;;; 1591;CANADIAN SYLLABICS NGO;Lo;0;L;;;;;N;;;;; 1592;CANADIAN SYLLABICS NGOO;Lo;0;L;;;;;N;;;;; 1593;CANADIAN SYLLABICS NGA;Lo;0;L;;;;;N;;;;; 1594;CANADIAN SYLLABICS NGAA;Lo;0;L;;;;;N;;;;; 1595;CANADIAN SYLLABICS NG;Lo;0;L;;;;;N;;;;; 1596;CANADIAN SYLLABICS NNG;Lo;0;L;;;;;N;;;;; 1597;CANADIAN SYLLABICS SAYISI SHE;Lo;0;L;;;;;N;;;;; 1598;CANADIAN SYLLABICS SAYISI SHI;Lo;0;L;;;;;N;;;;; 1599;CANADIAN SYLLABICS SAYISI SHO;Lo;0;L;;;;;N;;;;; 159A;CANADIAN SYLLABICS SAYISI SHA;Lo;0;L;;;;;N;;;;; 159B;CANADIAN SYLLABICS WOODS-CREE THE;Lo;0;L;;;;;N;;;;; 159C;CANADIAN SYLLABICS WOODS-CREE THI;Lo;0;L;;;;;N;;;;; 159D;CANADIAN SYLLABICS WOODS-CREE THO;Lo;0;L;;;;;N;;;;; 159E;CANADIAN SYLLABICS WOODS-CREE THA;Lo;0;L;;;;;N;;;;; 159F;CANADIAN SYLLABICS WOODS-CREE TH;Lo;0;L;;;;;N;;;;; 15A0;CANADIAN SYLLABICS LHI;Lo;0;L;;;;;N;;;;; 15A1;CANADIAN SYLLABICS LHII;Lo;0;L;;;;;N;;;;; 15A2;CANADIAN SYLLABICS LHO;Lo;0;L;;;;;N;;;;; 15A3;CANADIAN SYLLABICS LHOO;Lo;0;L;;;;;N;;;;; 15A4;CANADIAN SYLLABICS LHA;Lo;0;L;;;;;N;;;;; 15A5;CANADIAN SYLLABICS LHAA;Lo;0;L;;;;;N;;;;; 15A6;CANADIAN SYLLABICS LH;Lo;0;L;;;;;N;;;;; 15A7;CANADIAN SYLLABICS TH-CREE THE;Lo;0;L;;;;;N;;;;; 15A8;CANADIAN SYLLABICS TH-CREE THI;Lo;0;L;;;;;N;;;;; 15A9;CANADIAN SYLLABICS TH-CREE THII;Lo;0;L;;;;;N;;;;; 15AA;CANADIAN SYLLABICS TH-CREE THO;Lo;0;L;;;;;N;;;;; 15AB;CANADIAN SYLLABICS TH-CREE THOO;Lo;0;L;;;;;N;;;;; 15AC;CANADIAN SYLLABICS TH-CREE THA;Lo;0;L;;;;;N;;;;; 15AD;CANADIAN SYLLABICS TH-CREE THAA;Lo;0;L;;;;;N;;;;; 15AE;CANADIAN SYLLABICS TH-CREE TH;Lo;0;L;;;;;N;;;;; 15AF;CANADIAN SYLLABICS AIVILIK B;Lo;0;L;;;;;N;;;;; 15B0;CANADIAN SYLLABICS BLACKFOOT E;Lo;0;L;;;;;N;;;;; 15B1;CANADIAN SYLLABICS BLACKFOOT I;Lo;0;L;;;;;N;;;;; 15B2;CANADIAN SYLLABICS BLACKFOOT O;Lo;0;L;;;;;N;;;;; 15B3;CANADIAN SYLLABICS BLACKFOOT A;Lo;0;L;;;;;N;;;;; 15B4;CANADIAN SYLLABICS BLACKFOOT WE;Lo;0;L;;;;;N;;;;; 15B5;CANADIAN SYLLABICS BLACKFOOT WI;Lo;0;L;;;;;N;;;;; 15B6;CANADIAN SYLLABICS BLACKFOOT WO;Lo;0;L;;;;;N;;;;; 15B7;CANADIAN SYLLABICS BLACKFOOT WA;Lo;0;L;;;;;N;;;;; 15B8;CANADIAN SYLLABICS BLACKFOOT NE;Lo;0;L;;;;;N;;;;; 15B9;CANADIAN SYLLABICS BLACKFOOT NI;Lo;0;L;;;;;N;;;;; 15BA;CANADIAN SYLLABICS BLACKFOOT NO;Lo;0;L;;;;;N;;;;; 15BB;CANADIAN SYLLABICS BLACKFOOT NA;Lo;0;L;;;;;N;;;;; 15BC;CANADIAN SYLLABICS BLACKFOOT KE;Lo;0;L;;;;;N;;;;; 15BD;CANADIAN SYLLABICS BLACKFOOT KI;Lo;0;L;;;;;N;;;;; 15BE;CANADIAN SYLLABICS BLACKFOOT KO;Lo;0;L;;;;;N;;;;; 15BF;CANADIAN SYLLABICS BLACKFOOT KA;Lo;0;L;;;;;N;;;;; 15C0;CANADIAN SYLLABICS SAYISI HE;Lo;0;L;;;;;N;;;;; 15C1;CANADIAN SYLLABICS SAYISI HI;Lo;0;L;;;;;N;;;;; 15C2;CANADIAN SYLLABICS SAYISI HO;Lo;0;L;;;;;N;;;;; 15C3;CANADIAN SYLLABICS SAYISI HA;Lo;0;L;;;;;N;;;;; 15C4;CANADIAN SYLLABICS CARRIER GHU;Lo;0;L;;;;;N;;;;; 15C5;CANADIAN SYLLABICS CARRIER GHO;Lo;0;L;;;;;N;;;;; 15C6;CANADIAN SYLLABICS CARRIER GHE;Lo;0;L;;;;;N;;;;; 15C7;CANADIAN SYLLABICS CARRIER GHEE;Lo;0;L;;;;;N;;;;; 15C8;CANADIAN SYLLABICS CARRIER GHI;Lo;0;L;;;;;N;;;;; 15C9;CANADIAN SYLLABICS CARRIER GHA;Lo;0;L;;;;;N;;;;; 15CA;CANADIAN SYLLABICS CARRIER RU;Lo;0;L;;;;;N;;;;; 15CB;CANADIAN SYLLABICS CARRIER RO;Lo;0;L;;;;;N;;;;; 15CC;CANADIAN SYLLABICS CARRIER RE;Lo;0;L;;;;;N;;;;; 15CD;CANADIAN SYLLABICS CARRIER REE;Lo;0;L;;;;;N;;;;; 15CE;CANADIAN SYLLABICS CARRIER RI;Lo;0;L;;;;;N;;;;; 15CF;CANADIAN SYLLABICS CARRIER RA;Lo;0;L;;;;;N;;;;; 15D0;CANADIAN SYLLABICS CARRIER WU;Lo;0;L;;;;;N;;;;; 15D1;CANADIAN SYLLABICS CARRIER WO;Lo;0;L;;;;;N;;;;; 15D2;CANADIAN SYLLABICS CARRIER WE;Lo;0;L;;;;;N;;;;; 15D3;CANADIAN SYLLABICS CARRIER WEE;Lo;0;L;;;;;N;;;;; 15D4;CANADIAN SYLLABICS CARRIER WI;Lo;0;L;;;;;N;;;;; 15D5;CANADIAN SYLLABICS CARRIER WA;Lo;0;L;;;;;N;;;;; 15D6;CANADIAN SYLLABICS CARRIER HWU;Lo;0;L;;;;;N;;;;; 15D7;CANADIAN SYLLABICS CARRIER HWO;Lo;0;L;;;;;N;;;;; 15D8;CANADIAN SYLLABICS CARRIER HWE;Lo;0;L;;;;;N;;;;; 15D9;CANADIAN SYLLABICS CARRIER HWEE;Lo;0;L;;;;;N;;;;; 15DA;CANADIAN SYLLABICS CARRIER HWI;Lo;0;L;;;;;N;;;;; 15DB;CANADIAN SYLLABICS CARRIER HWA;Lo;0;L;;;;;N;;;;; 15DC;CANADIAN SYLLABICS CARRIER THU;Lo;0;L;;;;;N;;;;; 15DD;CANADIAN SYLLABICS CARRIER THO;Lo;0;L;;;;;N;;;;; 15DE;CANADIAN SYLLABICS CARRIER THE;Lo;0;L;;;;;N;;;;; 15DF;CANADIAN SYLLABICS CARRIER THEE;Lo;0;L;;;;;N;;;;; 15E0;CANADIAN SYLLABICS CARRIER THI;Lo;0;L;;;;;N;;;;; 15E1;CANADIAN SYLLABICS CARRIER THA;Lo;0;L;;;;;N;;;;; 15E2;CANADIAN SYLLABICS CARRIER TTU;Lo;0;L;;;;;N;;;;; 15E3;CANADIAN SYLLABICS CARRIER TTO;Lo;0;L;;;;;N;;;;; 15E4;CANADIAN SYLLABICS CARRIER TTE;Lo;0;L;;;;;N;;;;; 15E5;CANADIAN SYLLABICS CARRIER TTEE;Lo;0;L;;;;;N;;;;; 15E6;CANADIAN SYLLABICS CARRIER TTI;Lo;0;L;;;;;N;;;;; 15E7;CANADIAN SYLLABICS CARRIER TTA;Lo;0;L;;;;;N;;;;; 15E8;CANADIAN SYLLABICS CARRIER PU;Lo;0;L;;;;;N;;;;; 15E9;CANADIAN SYLLABICS CARRIER PO;Lo;0;L;;;;;N;;;;; 15EA;CANADIAN SYLLABICS CARRIER PE;Lo;0;L;;;;;N;;;;; 15EB;CANADIAN SYLLABICS CARRIER PEE;Lo;0;L;;;;;N;;;;; 15EC;CANADIAN SYLLABICS CARRIER PI;Lo;0;L;;;;;N;;;;; 15ED;CANADIAN SYLLABICS CARRIER PA;Lo;0;L;;;;;N;;;;; 15EE;CANADIAN SYLLABICS CARRIER P;Lo;0;L;;;;;N;;;;; 15EF;CANADIAN SYLLABICS CARRIER GU;Lo;0;L;;;;;N;;;;; 15F0;CANADIAN SYLLABICS CARRIER GO;Lo;0;L;;;;;N;;;;; 15F1;CANADIAN SYLLABICS CARRIER GE;Lo;0;L;;;;;N;;;;; 15F2;CANADIAN SYLLABICS CARRIER GEE;Lo;0;L;;;;;N;;;;; 15F3;CANADIAN SYLLABICS CARRIER GI;Lo;0;L;;;;;N;;;;; 15F4;CANADIAN SYLLABICS CARRIER GA;Lo;0;L;;;;;N;;;;; 15F5;CANADIAN SYLLABICS CARRIER KHU;Lo;0;L;;;;;N;;;;; 15F6;CANADIAN SYLLABICS CARRIER KHO;Lo;0;L;;;;;N;;;;; 15F7;CANADIAN SYLLABICS CARRIER KHE;Lo;0;L;;;;;N;;;;; 15F8;CANADIAN SYLLABICS CARRIER KHEE;Lo;0;L;;;;;N;;;;; 15F9;CANADIAN SYLLABICS CARRIER KHI;Lo;0;L;;;;;N;;;;; 15FA;CANADIAN SYLLABICS CARRIER KHA;Lo;0;L;;;;;N;;;;; 15FB;CANADIAN SYLLABICS CARRIER KKU;Lo;0;L;;;;;N;;;;; 15FC;CANADIAN SYLLABICS CARRIER KKO;Lo;0;L;;;;;N;;;;; 15FD;CANADIAN SYLLABICS CARRIER KKE;Lo;0;L;;;;;N;;;;; 15FE;CANADIAN SYLLABICS CARRIER KKEE;Lo;0;L;;;;;N;;;;; 15FF;CANADIAN SYLLABICS CARRIER KKI;Lo;0;L;;;;;N;;;;; 1600;CANADIAN SYLLABICS CARRIER KKA;Lo;0;L;;;;;N;;;;; 1601;CANADIAN SYLLABICS CARRIER KK;Lo;0;L;;;;;N;;;;; 1602;CANADIAN SYLLABICS CARRIER NU;Lo;0;L;;;;;N;;;;; 1603;CANADIAN SYLLABICS CARRIER NO;Lo;0;L;;;;;N;;;;; 1604;CANADIAN SYLLABICS CARRIER NE;Lo;0;L;;;;;N;;;;; 1605;CANADIAN SYLLABICS CARRIER NEE;Lo;0;L;;;;;N;;;;; 1606;CANADIAN SYLLABICS CARRIER NI;Lo;0;L;;;;;N;;;;; 1607;CANADIAN SYLLABICS CARRIER NA;Lo;0;L;;;;;N;;;;; 1608;CANADIAN SYLLABICS CARRIER MU;Lo;0;L;;;;;N;;;;; 1609;CANADIAN SYLLABICS CARRIER MO;Lo;0;L;;;;;N;;;;; 160A;CANADIAN SYLLABICS CARRIER ME;Lo;0;L;;;;;N;;;;; 160B;CANADIAN SYLLABICS CARRIER MEE;Lo;0;L;;;;;N;;;;; 160C;CANADIAN SYLLABICS CARRIER MI;Lo;0;L;;;;;N;;;;; 160D;CANADIAN SYLLABICS CARRIER MA;Lo;0;L;;;;;N;;;;; 160E;CANADIAN SYLLABICS CARRIER YU;Lo;0;L;;;;;N;;;;; 160F;CANADIAN SYLLABICS CARRIER YO;Lo;0;L;;;;;N;;;;; 1610;CANADIAN SYLLABICS CARRIER YE;Lo;0;L;;;;;N;;;;; 1611;CANADIAN SYLLABICS CARRIER YEE;Lo;0;L;;;;;N;;;;; 1612;CANADIAN SYLLABICS CARRIER YI;Lo;0;L;;;;;N;;;;; 1613;CANADIAN SYLLABICS CARRIER YA;Lo;0;L;;;;;N;;;;; 1614;CANADIAN SYLLABICS CARRIER JU;Lo;0;L;;;;;N;;;;; 1615;CANADIAN SYLLABICS SAYISI JU;Lo;0;L;;;;;N;;;;; 1616;CANADIAN SYLLABICS CARRIER JO;Lo;0;L;;;;;N;;;;; 1617;CANADIAN SYLLABICS CARRIER JE;Lo;0;L;;;;;N;;;;; 1618;CANADIAN SYLLABICS CARRIER JEE;Lo;0;L;;;;;N;;;;; 1619;CANADIAN SYLLABICS CARRIER JI;Lo;0;L;;;;;N;;;;; 161A;CANADIAN SYLLABICS SAYISI JI;Lo;0;L;;;;;N;;;;; 161B;CANADIAN SYLLABICS CARRIER JA;Lo;0;L;;;;;N;;;;; 161C;CANADIAN SYLLABICS CARRIER JJU;Lo;0;L;;;;;N;;;;; 161D;CANADIAN SYLLABICS CARRIER JJO;Lo;0;L;;;;;N;;;;; 161E;CANADIAN SYLLABICS CARRIER JJE;Lo;0;L;;;;;N;;;;; 161F;CANADIAN SYLLABICS CARRIER JJEE;Lo;0;L;;;;;N;;;;; 1620;CANADIAN SYLLABICS CARRIER JJI;Lo;0;L;;;;;N;;;;; 1621;CANADIAN SYLLABICS CARRIER JJA;Lo;0;L;;;;;N;;;;; 1622;CANADIAN SYLLABICS CARRIER LU;Lo;0;L;;;;;N;;;;; 1623;CANADIAN SYLLABICS CARRIER LO;Lo;0;L;;;;;N;;;;; 1624;CANADIAN SYLLABICS CARRIER LE;Lo;0;L;;;;;N;;;;; 1625;CANADIAN SYLLABICS CARRIER LEE;Lo;0;L;;;;;N;;;;; 1626;CANADIAN SYLLABICS CARRIER LI;Lo;0;L;;;;;N;;;;; 1627;CANADIAN SYLLABICS CARRIER LA;Lo;0;L;;;;;N;;;;; 1628;CANADIAN SYLLABICS CARRIER DLU;Lo;0;L;;;;;N;;;;; 1629;CANADIAN SYLLABICS CARRIER DLO;Lo;0;L;;;;;N;;;;; 162A;CANADIAN SYLLABICS CARRIER DLE;Lo;0;L;;;;;N;;;;; 162B;CANADIAN SYLLABICS CARRIER DLEE;Lo;0;L;;;;;N;;;;; 162C;CANADIAN SYLLABICS CARRIER DLI;Lo;0;L;;;;;N;;;;; 162D;CANADIAN SYLLABICS CARRIER DLA;Lo;0;L;;;;;N;;;;; 162E;CANADIAN SYLLABICS CARRIER LHU;Lo;0;L;;;;;N;;;;; 162F;CANADIAN SYLLABICS CARRIER LHO;Lo;0;L;;;;;N;;;;; 1630;CANADIAN SYLLABICS CARRIER LHE;Lo;0;L;;;;;N;;;;; 1631;CANADIAN SYLLABICS CARRIER LHEE;Lo;0;L;;;;;N;;;;; 1632;CANADIAN SYLLABICS CARRIER LHI;Lo;0;L;;;;;N;;;;; 1633;CANADIAN SYLLABICS CARRIER LHA;Lo;0;L;;;;;N;;;;; 1634;CANADIAN SYLLABICS CARRIER TLHU;Lo;0;L;;;;;N;;;;; 1635;CANADIAN SYLLABICS CARRIER TLHO;Lo;0;L;;;;;N;;;;; 1636;CANADIAN SYLLABICS CARRIER TLHE;Lo;0;L;;;;;N;;;;; 1637;CANADIAN SYLLABICS CARRIER TLHEE;Lo;0;L;;;;;N;;;;; 1638;CANADIAN SYLLABICS CARRIER TLHI;Lo;0;L;;;;;N;;;;; 1639;CANADIAN SYLLABICS CARRIER TLHA;Lo;0;L;;;;;N;;;;; 163A;CANADIAN SYLLABICS CARRIER TLU;Lo;0;L;;;;;N;;;;; 163B;CANADIAN SYLLABICS CARRIER TLO;Lo;0;L;;;;;N;;;;; 163C;CANADIAN SYLLABICS CARRIER TLE;Lo;0;L;;;;;N;;;;; 163D;CANADIAN SYLLABICS CARRIER TLEE;Lo;0;L;;;;;N;;;;; 163E;CANADIAN SYLLABICS CARRIER TLI;Lo;0;L;;;;;N;;;;; 163F;CANADIAN SYLLABICS CARRIER TLA;Lo;0;L;;;;;N;;;;; 1640;CANADIAN SYLLABICS CARRIER ZU;Lo;0;L;;;;;N;;;;; 1641;CANADIAN SYLLABICS CARRIER ZO;Lo;0;L;;;;;N;;;;; 1642;CANADIAN SYLLABICS CARRIER ZE;Lo;0;L;;;;;N;;;;; 1643;CANADIAN SYLLABICS CARRIER ZEE;Lo;0;L;;;;;N;;;;; 1644;CANADIAN SYLLABICS CARRIER ZI;Lo;0;L;;;;;N;;;;; 1645;CANADIAN SYLLABICS CARRIER ZA;Lo;0;L;;;;;N;;;;; 1646;CANADIAN SYLLABICS CARRIER Z;Lo;0;L;;;;;N;;;;; 1647;CANADIAN SYLLABICS CARRIER INITIAL Z;Lo;0;L;;;;;N;;;;; 1648;CANADIAN SYLLABICS CARRIER DZU;Lo;0;L;;;;;N;;;;; 1649;CANADIAN SYLLABICS CARRIER DZO;Lo;0;L;;;;;N;;;;; 164A;CANADIAN SYLLABICS CARRIER DZE;Lo;0;L;;;;;N;;;;; 164B;CANADIAN SYLLABICS CARRIER DZEE;Lo;0;L;;;;;N;;;;; 164C;CANADIAN SYLLABICS CARRIER DZI;Lo;0;L;;;;;N;;;;; 164D;CANADIAN SYLLABICS CARRIER DZA;Lo;0;L;;;;;N;;;;; 164E;CANADIAN SYLLABICS CARRIER SU;Lo;0;L;;;;;N;;;;; 164F;CANADIAN SYLLABICS CARRIER SO;Lo;0;L;;;;;N;;;;; 1650;CANADIAN SYLLABICS CARRIER SE;Lo;0;L;;;;;N;;;;; 1651;CANADIAN SYLLABICS CARRIER SEE;Lo;0;L;;;;;N;;;;; 1652;CANADIAN SYLLABICS CARRIER SI;Lo;0;L;;;;;N;;;;; 1653;CANADIAN SYLLABICS CARRIER SA;Lo;0;L;;;;;N;;;;; 1654;CANADIAN SYLLABICS CARRIER SHU;Lo;0;L;;;;;N;;;;; 1655;CANADIAN SYLLABICS CARRIER SHO;Lo;0;L;;;;;N;;;;; 1656;CANADIAN SYLLABICS CARRIER SHE;Lo;0;L;;;;;N;;;;; 1657;CANADIAN SYLLABICS CARRIER SHEE;Lo;0;L;;;;;N;;;;; 1658;CANADIAN SYLLABICS CARRIER SHI;Lo;0;L;;;;;N;;;;; 1659;CANADIAN SYLLABICS CARRIER SHA;Lo;0;L;;;;;N;;;;; 165A;CANADIAN SYLLABICS CARRIER SH;Lo;0;L;;;;;N;;;;; 165B;CANADIAN SYLLABICS CARRIER TSU;Lo;0;L;;;;;N;;;;; 165C;CANADIAN SYLLABICS CARRIER TSO;Lo;0;L;;;;;N;;;;; 165D;CANADIAN SYLLABICS CARRIER TSE;Lo;0;L;;;;;N;;;;; 165E;CANADIAN SYLLABICS CARRIER TSEE;Lo;0;L;;;;;N;;;;; 165F;CANADIAN SYLLABICS CARRIER TSI;Lo;0;L;;;;;N;;;;; 1660;CANADIAN SYLLABICS CARRIER TSA;Lo;0;L;;;;;N;;;;; 1661;CANADIAN SYLLABICS CARRIER CHU;Lo;0;L;;;;;N;;;;; 1662;CANADIAN SYLLABICS CARRIER CHO;Lo;0;L;;;;;N;;;;; 1663;CANADIAN SYLLABICS CARRIER CHE;Lo;0;L;;;;;N;;;;; 1664;CANADIAN SYLLABICS CARRIER CHEE;Lo;0;L;;;;;N;;;;; 1665;CANADIAN SYLLABICS CARRIER CHI;Lo;0;L;;;;;N;;;;; 1666;CANADIAN SYLLABICS CARRIER CHA;Lo;0;L;;;;;N;;;;; 1667;CANADIAN SYLLABICS CARRIER TTSU;Lo;0;L;;;;;N;;;;; 1668;CANADIAN SYLLABICS CARRIER TTSO;Lo;0;L;;;;;N;;;;; 1669;CANADIAN SYLLABICS CARRIER TTSE;Lo;0;L;;;;;N;;;;; 166A;CANADIAN SYLLABICS CARRIER TTSEE;Lo;0;L;;;;;N;;;;; 166B;CANADIAN SYLLABICS CARRIER TTSI;Lo;0;L;;;;;N;;;;; 166C;CANADIAN SYLLABICS CARRIER TTSA;Lo;0;L;;;;;N;;;;; 166D;CANADIAN SYLLABICS CHI SIGN;Po;0;L;;;;;N;;;;; 166E;CANADIAN SYLLABICS FULL STOP;Po;0;L;;;;;N;;;;; 166F;CANADIAN SYLLABICS QAI;Lo;0;L;;;;;N;;;;; 1670;CANADIAN SYLLABICS NGAI;Lo;0;L;;;;;N;;;;; 1671;CANADIAN SYLLABICS NNGI;Lo;0;L;;;;;N;;;;; 1672;CANADIAN SYLLABICS NNGII;Lo;0;L;;;;;N;;;;; 1673;CANADIAN SYLLABICS NNGO;Lo;0;L;;;;;N;;;;; 1674;CANADIAN SYLLABICS NNGOO;Lo;0;L;;;;;N;;;;; 1675;CANADIAN SYLLABICS NNGA;Lo;0;L;;;;;N;;;;; 1676;CANADIAN SYLLABICS NNGAA;Lo;0;L;;;;;N;;;;; 1677;CANADIAN SYLLABICS WOODS-CREE THWEE;Lo;0;L;;;;;N;;;;; 1678;CANADIAN SYLLABICS WOODS-CREE THWI;Lo;0;L;;;;;N;;;;; 1679;CANADIAN SYLLABICS WOODS-CREE THWII;Lo;0;L;;;;;N;;;;; 167A;CANADIAN SYLLABICS WOODS-CREE THWO;Lo;0;L;;;;;N;;;;; 167B;CANADIAN SYLLABICS WOODS-CREE THWOO;Lo;0;L;;;;;N;;;;; 167C;CANADIAN SYLLABICS WOODS-CREE THWA;Lo;0;L;;;;;N;;;;; 167D;CANADIAN SYLLABICS WOODS-CREE THWAA;Lo;0;L;;;;;N;;;;; 167E;CANADIAN SYLLABICS WOODS-CREE FINAL TH;Lo;0;L;;;;;N;;;;; 167F;CANADIAN SYLLABICS BLACKFOOT W;Lo;0;L;;;;;N;;;;; 1680;OGHAM SPACE MARK;Zs;0;WS;;;;;N;;;;; 1681;OGHAM LETTER BEITH;Lo;0;L;;;;;N;;;;; 1682;OGHAM LETTER LUIS;Lo;0;L;;;;;N;;;;; 1683;OGHAM LETTER FEARN;Lo;0;L;;;;;N;;;;; 1684;OGHAM LETTER SAIL;Lo;0;L;;;;;N;;;;; 1685;OGHAM LETTER NION;Lo;0;L;;;;;N;;;;; 1686;OGHAM LETTER UATH;Lo;0;L;;;;;N;;;;; 1687;OGHAM LETTER DAIR;Lo;0;L;;;;;N;;;;; 1688;OGHAM LETTER TINNE;Lo;0;L;;;;;N;;;;; 1689;OGHAM LETTER COLL;Lo;0;L;;;;;N;;;;; 168A;OGHAM LETTER CEIRT;Lo;0;L;;;;;N;;;;; 168B;OGHAM LETTER MUIN;Lo;0;L;;;;;N;;;;; 168C;OGHAM LETTER GORT;Lo;0;L;;;;;N;;;;; 168D;OGHAM LETTER NGEADAL;Lo;0;L;;;;;N;;;;; 168E;OGHAM LETTER STRAIF;Lo;0;L;;;;;N;;;;; 168F;OGHAM LETTER RUIS;Lo;0;L;;;;;N;;;;; 1690;OGHAM LETTER AILM;Lo;0;L;;;;;N;;;;; 1691;OGHAM LETTER ONN;Lo;0;L;;;;;N;;;;; 1692;OGHAM LETTER UR;Lo;0;L;;;;;N;;;;; 1693;OGHAM LETTER EADHADH;Lo;0;L;;;;;N;;;;; 1694;OGHAM LETTER IODHADH;Lo;0;L;;;;;N;;;;; 1695;OGHAM LETTER EABHADH;Lo;0;L;;;;;N;;;;; 1696;OGHAM LETTER OR;Lo;0;L;;;;;N;;;;; 1697;OGHAM LETTER UILLEANN;Lo;0;L;;;;;N;;;;; 1698;OGHAM LETTER IFIN;Lo;0;L;;;;;N;;;;; 1699;OGHAM LETTER EAMHANCHOLL;Lo;0;L;;;;;N;;;;; 169A;OGHAM LETTER PEITH;Lo;0;L;;;;;N;;;;; 169B;OGHAM FEATHER MARK;Ps;0;ON;;;;;Y;;;;; 169C;OGHAM REVERSED FEATHER MARK;Pe;0;ON;;;;;Y;;;;; 16A0;RUNIC LETTER FEHU FEOH FE F;Lo;0;L;;;;;N;;;;; 16A1;RUNIC LETTER V;Lo;0;L;;;;;N;;;;; 16A2;RUNIC LETTER URUZ UR U;Lo;0;L;;;;;N;;;;; 16A3;RUNIC LETTER YR;Lo;0;L;;;;;N;;;;; 16A4;RUNIC LETTER Y;Lo;0;L;;;;;N;;;;; 16A5;RUNIC LETTER W;Lo;0;L;;;;;N;;;;; 16A6;RUNIC LETTER THURISAZ THURS THORN;Lo;0;L;;;;;N;;;;; 16A7;RUNIC LETTER ETH;Lo;0;L;;;;;N;;;;; 16A8;RUNIC LETTER ANSUZ A;Lo;0;L;;;;;N;;;;; 16A9;RUNIC LETTER OS O;Lo;0;L;;;;;N;;;;; 16AA;RUNIC LETTER AC A;Lo;0;L;;;;;N;;;;; 16AB;RUNIC LETTER AESC;Lo;0;L;;;;;N;;;;; 16AC;RUNIC LETTER LONG-BRANCH-OSS O;Lo;0;L;;;;;N;;;;; 16AD;RUNIC LETTER SHORT-TWIG-OSS O;Lo;0;L;;;;;N;;;;; 16AE;RUNIC LETTER O;Lo;0;L;;;;;N;;;;; 16AF;RUNIC LETTER OE;Lo;0;L;;;;;N;;;;; 16B0;RUNIC LETTER ON;Lo;0;L;;;;;N;;;;; 16B1;RUNIC LETTER RAIDO RAD REID R;Lo;0;L;;;;;N;;;;; 16B2;RUNIC LETTER KAUNA;Lo;0;L;;;;;N;;;;; 16B3;RUNIC LETTER CEN;Lo;0;L;;;;;N;;;;; 16B4;RUNIC LETTER KAUN K;Lo;0;L;;;;;N;;;;; 16B5;RUNIC LETTER G;Lo;0;L;;;;;N;;;;; 16B6;RUNIC LETTER ENG;Lo;0;L;;;;;N;;;;; 16B7;RUNIC LETTER GEBO GYFU G;Lo;0;L;;;;;N;;;;; 16B8;RUNIC LETTER GAR;Lo;0;L;;;;;N;;;;; 16B9;RUNIC LETTER WUNJO WYNN W;Lo;0;L;;;;;N;;;;; 16BA;RUNIC LETTER HAGLAZ H;Lo;0;L;;;;;N;;;;; 16BB;RUNIC LETTER HAEGL H;Lo;0;L;;;;;N;;;;; 16BC;RUNIC LETTER LONG-BRANCH-HAGALL H;Lo;0;L;;;;;N;;;;; 16BD;RUNIC LETTER SHORT-TWIG-HAGALL H;Lo;0;L;;;;;N;;;;; 16BE;RUNIC LETTER NAUDIZ NYD NAUD N;Lo;0;L;;;;;N;;;;; 16BF;RUNIC LETTER SHORT-TWIG-NAUD N;Lo;0;L;;;;;N;;;;; 16C0;RUNIC LETTER DOTTED-N;Lo;0;L;;;;;N;;;;; 16C1;RUNIC LETTER ISAZ IS ISS I;Lo;0;L;;;;;N;;;;; 16C2;RUNIC LETTER E;Lo;0;L;;;;;N;;;;; 16C3;RUNIC LETTER JERAN J;Lo;0;L;;;;;N;;;;; 16C4;RUNIC LETTER GER;Lo;0;L;;;;;N;;;;; 16C5;RUNIC LETTER LONG-BRANCH-AR AE;Lo;0;L;;;;;N;;;;; 16C6;RUNIC LETTER SHORT-TWIG-AR A;Lo;0;L;;;;;N;;;;; 16C7;RUNIC LETTER IWAZ EOH;Lo;0;L;;;;;N;;;;; 16C8;RUNIC LETTER PERTHO PEORTH P;Lo;0;L;;;;;N;;;;; 16C9;RUNIC LETTER ALGIZ EOLHX;Lo;0;L;;;;;N;;;;; 16CA;RUNIC LETTER SOWILO S;Lo;0;L;;;;;N;;;;; 16CB;RUNIC LETTER SIGEL LONG-BRANCH-SOL S;Lo;0;L;;;;;N;;;;; 16CC;RUNIC LETTER SHORT-TWIG-SOL S;Lo;0;L;;;;;N;;;;; 16CD;RUNIC LETTER C;Lo;0;L;;;;;N;;;;; 16CE;RUNIC LETTER Z;Lo;0;L;;;;;N;;;;; 16CF;RUNIC LETTER TIWAZ TIR TYR T;Lo;0;L;;;;;N;;;;; 16D0;RUNIC LETTER SHORT-TWIG-TYR T;Lo;0;L;;;;;N;;;;; 16D1;RUNIC LETTER D;Lo;0;L;;;;;N;;;;; 16D2;RUNIC LETTER BERKANAN BEORC BJARKAN B;Lo;0;L;;;;;N;;;;; 16D3;RUNIC LETTER SHORT-TWIG-BJARKAN B;Lo;0;L;;;;;N;;;;; 16D4;RUNIC LETTER DOTTED-P;Lo;0;L;;;;;N;;;;; 16D5;RUNIC LETTER OPEN-P;Lo;0;L;;;;;N;;;;; 16D6;RUNIC LETTER EHWAZ EH E;Lo;0;L;;;;;N;;;;; 16D7;RUNIC LETTER MANNAZ MAN M;Lo;0;L;;;;;N;;;;; 16D8;RUNIC LETTER LONG-BRANCH-MADR M;Lo;0;L;;;;;N;;;;; 16D9;RUNIC LETTER SHORT-TWIG-MADR M;Lo;0;L;;;;;N;;;;; 16DA;RUNIC LETTER LAUKAZ LAGU LOGR L;Lo;0;L;;;;;N;;;;; 16DB;RUNIC LETTER DOTTED-L;Lo;0;L;;;;;N;;;;; 16DC;RUNIC LETTER INGWAZ;Lo;0;L;;;;;N;;;;; 16DD;RUNIC LETTER ING;Lo;0;L;;;;;N;;;;; 16DE;RUNIC LETTER DAGAZ DAEG D;Lo;0;L;;;;;N;;;;; 16DF;RUNIC LETTER OTHALAN ETHEL O;Lo;0;L;;;;;N;;;;; 16E0;RUNIC LETTER EAR;Lo;0;L;;;;;N;;;;; 16E1;RUNIC LETTER IOR;Lo;0;L;;;;;N;;;;; 16E2;RUNIC LETTER CWEORTH;Lo;0;L;;;;;N;;;;; 16E3;RUNIC LETTER CALC;Lo;0;L;;;;;N;;;;; 16E4;RUNIC LETTER CEALC;Lo;0;L;;;;;N;;;;; 16E5;RUNIC LETTER STAN;Lo;0;L;;;;;N;;;;; 16E6;RUNIC LETTER LONG-BRANCH-YR;Lo;0;L;;;;;N;;;;; 16E7;RUNIC LETTER SHORT-TWIG-YR;Lo;0;L;;;;;N;;;;; 16E8;RUNIC LETTER ICELANDIC-YR;Lo;0;L;;;;;N;;;;; 16E9;RUNIC LETTER Q;Lo;0;L;;;;;N;;;;; 16EA;RUNIC LETTER X;Lo;0;L;;;;;N;;;;; 16EB;RUNIC SINGLE PUNCTUATION;Po;0;L;;;;;N;;;;; 16EC;RUNIC MULTIPLE PUNCTUATION;Po;0;L;;;;;N;;;;; 16ED;RUNIC CROSS PUNCTUATION;Po;0;L;;;;;N;;;;; 16EE;RUNIC ARLAUG SYMBOL;Nl;0;L;;;;17;N;;;;; 16EF;RUNIC TVIMADUR SYMBOL;Nl;0;L;;;;18;N;;;;; 16F0;RUNIC BELGTHOR SYMBOL;Nl;0;L;;;;19;N;;;;; 16F1;RUNIC LETTER K;Lo;0;L;;;;;N;;;;; 16F2;RUNIC LETTER SH;Lo;0;L;;;;;N;;;;; 16F3;RUNIC LETTER OO;Lo;0;L;;;;;N;;;;; 16F4;RUNIC LETTER FRANKS CASKET OS;Lo;0;L;;;;;N;;;;; 16F5;RUNIC LETTER FRANKS CASKET IS;Lo;0;L;;;;;N;;;;; 16F6;RUNIC LETTER FRANKS CASKET EH;Lo;0;L;;;;;N;;;;; 16F7;RUNIC LETTER FRANKS CASKET AC;Lo;0;L;;;;;N;;;;; 16F8;RUNIC LETTER FRANKS CASKET AESC;Lo;0;L;;;;;N;;;;; 1700;TAGALOG LETTER A;Lo;0;L;;;;;N;;;;; 1701;TAGALOG LETTER I;Lo;0;L;;;;;N;;;;; 1702;TAGALOG LETTER U;Lo;0;L;;;;;N;;;;; 1703;TAGALOG LETTER KA;Lo;0;L;;;;;N;;;;; 1704;TAGALOG LETTER GA;Lo;0;L;;;;;N;;;;; 1705;TAGALOG LETTER NGA;Lo;0;L;;;;;N;;;;; 1706;TAGALOG LETTER TA;Lo;0;L;;;;;N;;;;; 1707;TAGALOG LETTER DA;Lo;0;L;;;;;N;;;;; 1708;TAGALOG LETTER NA;Lo;0;L;;;;;N;;;;; 1709;TAGALOG LETTER PA;Lo;0;L;;;;;N;;;;; 170A;TAGALOG LETTER BA;Lo;0;L;;;;;N;;;;; 170B;TAGALOG LETTER MA;Lo;0;L;;;;;N;;;;; 170C;TAGALOG LETTER YA;Lo;0;L;;;;;N;;;;; 170E;TAGALOG LETTER LA;Lo;0;L;;;;;N;;;;; 170F;TAGALOG LETTER WA;Lo;0;L;;;;;N;;;;; 1710;TAGALOG LETTER SA;Lo;0;L;;;;;N;;;;; 1711;TAGALOG LETTER HA;Lo;0;L;;;;;N;;;;; 1712;TAGALOG VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; 1713;TAGALOG VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 1714;TAGALOG SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 1720;HANUNOO LETTER A;Lo;0;L;;;;;N;;;;; 1721;HANUNOO LETTER I;Lo;0;L;;;;;N;;;;; 1722;HANUNOO LETTER U;Lo;0;L;;;;;N;;;;; 1723;HANUNOO LETTER KA;Lo;0;L;;;;;N;;;;; 1724;HANUNOO LETTER GA;Lo;0;L;;;;;N;;;;; 1725;HANUNOO LETTER NGA;Lo;0;L;;;;;N;;;;; 1726;HANUNOO LETTER TA;Lo;0;L;;;;;N;;;;; 1727;HANUNOO LETTER DA;Lo;0;L;;;;;N;;;;; 1728;HANUNOO LETTER NA;Lo;0;L;;;;;N;;;;; 1729;HANUNOO LETTER PA;Lo;0;L;;;;;N;;;;; 172A;HANUNOO LETTER BA;Lo;0;L;;;;;N;;;;; 172B;HANUNOO LETTER MA;Lo;0;L;;;;;N;;;;; 172C;HANUNOO LETTER YA;Lo;0;L;;;;;N;;;;; 172D;HANUNOO LETTER RA;Lo;0;L;;;;;N;;;;; 172E;HANUNOO LETTER LA;Lo;0;L;;;;;N;;;;; 172F;HANUNOO LETTER WA;Lo;0;L;;;;;N;;;;; 1730;HANUNOO LETTER SA;Lo;0;L;;;;;N;;;;; 1731;HANUNOO LETTER HA;Lo;0;L;;;;;N;;;;; 1732;HANUNOO VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; 1733;HANUNOO VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 1734;HANUNOO SIGN PAMUDPOD;Mn;9;NSM;;;;;N;;;;; 1735;PHILIPPINE SINGLE PUNCTUATION;Po;0;L;;;;;N;;;;; 1736;PHILIPPINE DOUBLE PUNCTUATION;Po;0;L;;;;;N;;;;; 1740;BUHID LETTER A;Lo;0;L;;;;;N;;;;; 1741;BUHID LETTER I;Lo;0;L;;;;;N;;;;; 1742;BUHID LETTER U;Lo;0;L;;;;;N;;;;; 1743;BUHID LETTER KA;Lo;0;L;;;;;N;;;;; 1744;BUHID LETTER GA;Lo;0;L;;;;;N;;;;; 1745;BUHID LETTER NGA;Lo;0;L;;;;;N;;;;; 1746;BUHID LETTER TA;Lo;0;L;;;;;N;;;;; 1747;BUHID LETTER DA;Lo;0;L;;;;;N;;;;; 1748;BUHID LETTER NA;Lo;0;L;;;;;N;;;;; 1749;BUHID LETTER PA;Lo;0;L;;;;;N;;;;; 174A;BUHID LETTER BA;Lo;0;L;;;;;N;;;;; 174B;BUHID LETTER MA;Lo;0;L;;;;;N;;;;; 174C;BUHID LETTER YA;Lo;0;L;;;;;N;;;;; 174D;BUHID LETTER RA;Lo;0;L;;;;;N;;;;; 174E;BUHID LETTER LA;Lo;0;L;;;;;N;;;;; 174F;BUHID LETTER WA;Lo;0;L;;;;;N;;;;; 1750;BUHID LETTER SA;Lo;0;L;;;;;N;;;;; 1751;BUHID LETTER HA;Lo;0;L;;;;;N;;;;; 1752;BUHID VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; 1753;BUHID VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 1760;TAGBANWA LETTER A;Lo;0;L;;;;;N;;;;; 1761;TAGBANWA LETTER I;Lo;0;L;;;;;N;;;;; 1762;TAGBANWA LETTER U;Lo;0;L;;;;;N;;;;; 1763;TAGBANWA LETTER KA;Lo;0;L;;;;;N;;;;; 1764;TAGBANWA LETTER GA;Lo;0;L;;;;;N;;;;; 1765;TAGBANWA LETTER NGA;Lo;0;L;;;;;N;;;;; 1766;TAGBANWA LETTER TA;Lo;0;L;;;;;N;;;;; 1767;TAGBANWA LETTER DA;Lo;0;L;;;;;N;;;;; 1768;TAGBANWA LETTER NA;Lo;0;L;;;;;N;;;;; 1769;TAGBANWA LETTER PA;Lo;0;L;;;;;N;;;;; 176A;TAGBANWA LETTER BA;Lo;0;L;;;;;N;;;;; 176B;TAGBANWA LETTER MA;Lo;0;L;;;;;N;;;;; 176C;TAGBANWA LETTER YA;Lo;0;L;;;;;N;;;;; 176E;TAGBANWA LETTER LA;Lo;0;L;;;;;N;;;;; 176F;TAGBANWA LETTER WA;Lo;0;L;;;;;N;;;;; 1770;TAGBANWA LETTER SA;Lo;0;L;;;;;N;;;;; 1772;TAGBANWA VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; 1773;TAGBANWA VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 1780;KHMER LETTER KA;Lo;0;L;;;;;N;;;;; 1781;KHMER LETTER KHA;Lo;0;L;;;;;N;;;;; 1782;KHMER LETTER KO;Lo;0;L;;;;;N;;;;; 1783;KHMER LETTER KHO;Lo;0;L;;;;;N;;;;; 1784;KHMER LETTER NGO;Lo;0;L;;;;;N;;;;; 1785;KHMER LETTER CA;Lo;0;L;;;;;N;;;;; 1786;KHMER LETTER CHA;Lo;0;L;;;;;N;;;;; 1787;KHMER LETTER CO;Lo;0;L;;;;;N;;;;; 1788;KHMER LETTER CHO;Lo;0;L;;;;;N;;;;; 1789;KHMER LETTER NYO;Lo;0;L;;;;;N;;;;; 178A;KHMER LETTER DA;Lo;0;L;;;;;N;;;;; 178B;KHMER LETTER TTHA;Lo;0;L;;;;;N;;;;; 178C;KHMER LETTER DO;Lo;0;L;;;;;N;;;;; 178D;KHMER LETTER TTHO;Lo;0;L;;;;;N;;;;; 178E;KHMER LETTER NNO;Lo;0;L;;;;;N;;;;; 178F;KHMER LETTER TA;Lo;0;L;;;;;N;;;;; 1790;KHMER LETTER THA;Lo;0;L;;;;;N;;;;; 1791;KHMER LETTER TO;Lo;0;L;;;;;N;;;;; 1792;KHMER LETTER THO;Lo;0;L;;;;;N;;;;; 1793;KHMER LETTER NO;Lo;0;L;;;;;N;;;;; 1794;KHMER LETTER BA;Lo;0;L;;;;;N;;;;; 1795;KHMER LETTER PHA;Lo;0;L;;;;;N;;;;; 1796;KHMER LETTER PO;Lo;0;L;;;;;N;;;;; 1797;KHMER LETTER PHO;Lo;0;L;;;;;N;;;;; 1798;KHMER LETTER MO;Lo;0;L;;;;;N;;;;; 1799;KHMER LETTER YO;Lo;0;L;;;;;N;;;;; 179A;KHMER LETTER RO;Lo;0;L;;;;;N;;;;; 179B;KHMER LETTER LO;Lo;0;L;;;;;N;;;;; 179C;KHMER LETTER VO;Lo;0;L;;;;;N;;;;; 179D;KHMER LETTER SHA;Lo;0;L;;;;;N;;;;; 179E;KHMER LETTER SSO;Lo;0;L;;;;;N;;;;; 179F;KHMER LETTER SA;Lo;0;L;;;;;N;;;;; 17A0;KHMER LETTER HA;Lo;0;L;;;;;N;;;;; 17A1;KHMER LETTER LA;Lo;0;L;;;;;N;;;;; 17A2;KHMER LETTER QA;Lo;0;L;;;;;N;;;;; 17A3;KHMER INDEPENDENT VOWEL QAQ;Lo;0;L;;;;;N;;;;; 17A4;KHMER INDEPENDENT VOWEL QAA;Lo;0;L;;;;;N;;;;; 17A5;KHMER INDEPENDENT VOWEL QI;Lo;0;L;;;;;N;;;;; 17A6;KHMER INDEPENDENT VOWEL QII;Lo;0;L;;;;;N;;;;; 17A7;KHMER INDEPENDENT VOWEL QU;Lo;0;L;;;;;N;;;;; 17A8;KHMER INDEPENDENT VOWEL QUK;Lo;0;L;;;;;N;;;;; 17A9;KHMER INDEPENDENT VOWEL QUU;Lo;0;L;;;;;N;;;;; 17AA;KHMER INDEPENDENT VOWEL QUUV;Lo;0;L;;;;;N;;;;; 17AB;KHMER INDEPENDENT VOWEL RY;Lo;0;L;;;;;N;;;;; 17AC;KHMER INDEPENDENT VOWEL RYY;Lo;0;L;;;;;N;;;;; 17AD;KHMER INDEPENDENT VOWEL LY;Lo;0;L;;;;;N;;;;; 17AE;KHMER INDEPENDENT VOWEL LYY;Lo;0;L;;;;;N;;;;; 17AF;KHMER INDEPENDENT VOWEL QE;Lo;0;L;;;;;N;;;;; 17B0;KHMER INDEPENDENT VOWEL QAI;Lo;0;L;;;;;N;;;;; 17B1;KHMER INDEPENDENT VOWEL QOO TYPE ONE;Lo;0;L;;;;;N;;;;; 17B2;KHMER INDEPENDENT VOWEL QOO TYPE TWO;Lo;0;L;;;;;N;;;;; 17B3;KHMER INDEPENDENT VOWEL QAU;Lo;0;L;;;;;N;;;;; 17B4;KHMER VOWEL INHERENT AQ;Mn;0;NSM;;;;;N;;;;; 17B5;KHMER VOWEL INHERENT AA;Mn;0;NSM;;;;;N;;;;; 17B6;KHMER VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 17B7;KHMER VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; 17B8;KHMER VOWEL SIGN II;Mn;0;NSM;;;;;N;;;;; 17B9;KHMER VOWEL SIGN Y;Mn;0;NSM;;;;;N;;;;; 17BA;KHMER VOWEL SIGN YY;Mn;0;NSM;;;;;N;;;;; 17BB;KHMER VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 17BC;KHMER VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 17BD;KHMER VOWEL SIGN UA;Mn;0;NSM;;;;;N;;;;; 17BE;KHMER VOWEL SIGN OE;Mc;0;L;;;;;N;;;;; 17BF;KHMER VOWEL SIGN YA;Mc;0;L;;;;;N;;;;; 17C0;KHMER VOWEL SIGN IE;Mc;0;L;;;;;N;;;;; 17C1;KHMER VOWEL SIGN E;Mc;0;L;;;;;N;;;;; 17C2;KHMER VOWEL SIGN AE;Mc;0;L;;;;;N;;;;; 17C3;KHMER VOWEL SIGN AI;Mc;0;L;;;;;N;;;;; 17C4;KHMER VOWEL SIGN OO;Mc;0;L;;;;;N;;;;; 17C5;KHMER VOWEL SIGN AU;Mc;0;L;;;;;N;;;;; 17C6;KHMER SIGN NIKAHIT;Mn;0;NSM;;;;;N;;;;; 17C7;KHMER SIGN REAHMUK;Mc;0;L;;;;;N;;;;; 17C8;KHMER SIGN YUUKALEAPINTU;Mc;0;L;;;;;N;;;;; 17C9;KHMER SIGN MUUSIKATOAN;Mn;0;NSM;;;;;N;;;;; 17CA;KHMER SIGN TRIISAP;Mn;0;NSM;;;;;N;;;;; 17CB;KHMER SIGN BANTOC;Mn;0;NSM;;;;;N;;;;; 17CC;KHMER SIGN ROBAT;Mn;0;NSM;;;;;N;;;;; 17CD;KHMER SIGN TOANDAKHIAT;Mn;0;NSM;;;;;N;;;;; 17CE;KHMER SIGN KAKABAT;Mn;0;NSM;;;;;N;;;;; 17CF;KHMER SIGN AHSDA;Mn;0;NSM;;;;;N;;;;; 17D0;KHMER SIGN SAMYOK SANNYA;Mn;0;NSM;;;;;N;;;;; 17D1;KHMER SIGN VIRIAM;Mn;0;NSM;;;;;N;;;;; 17D2;KHMER SIGN COENG;Mn;9;NSM;;;;;N;;;;; 17D3;KHMER SIGN BATHAMASAT;Mn;0;NSM;;;;;N;;;;; 17D4;KHMER SIGN KHAN;Po;0;L;;;;;N;;;;; 17D5;KHMER SIGN BARIYOOSAN;Po;0;L;;;;;N;;;;; 17D6;KHMER SIGN CAMNUC PII KUUH;Po;0;L;;;;;N;;;;; 17D7;KHMER SIGN LEK TOO;Lm;0;L;;;;;N;;;;; 17D8;KHMER SIGN BEYYAL;Po;0;L;;;;;N;;;;; 17D9;KHMER SIGN PHNAEK MUAN;Po;0;L;;;;;N;;;;; 17DA;KHMER SIGN KOOMUUT;Po;0;L;;;;;N;;;;; 17DB;KHMER CURRENCY SYMBOL RIEL;Sc;0;ET;;;;;N;;;;; 17DC;KHMER SIGN AVAKRAHASANYA;Lo;0;L;;;;;N;;;;; 17DD;KHMER SIGN ATTHACAN;Mn;230;NSM;;;;;N;;;;; 17E0;KHMER DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 17E1;KHMER DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 17E2;KHMER DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 17E3;KHMER DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 17E4;KHMER DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 17E5;KHMER DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 17E6;KHMER DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 17E7;KHMER DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 17E8;KHMER DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 17E9;KHMER DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 17F0;KHMER SYMBOL LEK ATTAK SON;No;0;ON;;;;0;N;;;;; 17F1;KHMER SYMBOL LEK ATTAK MUOY;No;0;ON;;;;1;N;;;;; 17F2;KHMER SYMBOL LEK ATTAK PII;No;0;ON;;;;2;N;;;;; 17F3;KHMER SYMBOL LEK ATTAK BEI;No;0;ON;;;;3;N;;;;; 17F4;KHMER SYMBOL LEK ATTAK BUON;No;0;ON;;;;4;N;;;;; 17F5;KHMER SYMBOL LEK ATTAK PRAM;No;0;ON;;;;5;N;;;;; 17F6;KHMER SYMBOL LEK ATTAK PRAM-MUOY;No;0;ON;;;;6;N;;;;; 17F7;KHMER SYMBOL LEK ATTAK PRAM-PII;No;0;ON;;;;7;N;;;;; 17F8;KHMER SYMBOL LEK ATTAK PRAM-BEI;No;0;ON;;;;8;N;;;;; 17F9;KHMER SYMBOL LEK ATTAK PRAM-BUON;No;0;ON;;;;9;N;;;;; 1800;MONGOLIAN BIRGA;Po;0;ON;;;;;N;;;;; 1801;MONGOLIAN ELLIPSIS;Po;0;ON;;;;;N;;;;; 1802;MONGOLIAN COMMA;Po;0;ON;;;;;N;;;;; 1803;MONGOLIAN FULL STOP;Po;0;ON;;;;;N;;;;; 1804;MONGOLIAN COLON;Po;0;ON;;;;;N;;;;; 1805;MONGOLIAN FOUR DOTS;Po;0;ON;;;;;N;;;;; 1806;MONGOLIAN TODO SOFT HYPHEN;Pd;0;ON;;;;;N;;;;; 1807;MONGOLIAN SIBE SYLLABLE BOUNDARY MARKER;Po;0;ON;;;;;N;;;;; 1808;MONGOLIAN MANCHU COMMA;Po;0;ON;;;;;N;;;;; 1809;MONGOLIAN MANCHU FULL STOP;Po;0;ON;;;;;N;;;;; 180A;MONGOLIAN NIRUGU;Po;0;ON;;;;;N;;;;; 180B;MONGOLIAN FREE VARIATION SELECTOR ONE;Mn;0;NSM;;;;;N;;;;; 180C;MONGOLIAN FREE VARIATION SELECTOR TWO;Mn;0;NSM;;;;;N;;;;; 180D;MONGOLIAN FREE VARIATION SELECTOR THREE;Mn;0;NSM;;;;;N;;;;; 180E;MONGOLIAN VOWEL SEPARATOR;Cf;0;BN;;;;;N;;;;; 1810;MONGOLIAN DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 1811;MONGOLIAN DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 1812;MONGOLIAN DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 1813;MONGOLIAN DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 1814;MONGOLIAN DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 1815;MONGOLIAN DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 1816;MONGOLIAN DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 1817;MONGOLIAN DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 1818;MONGOLIAN DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 1819;MONGOLIAN DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 1820;MONGOLIAN LETTER A;Lo;0;L;;;;;N;;;;; 1821;MONGOLIAN LETTER E;Lo;0;L;;;;;N;;;;; 1822;MONGOLIAN LETTER I;Lo;0;L;;;;;N;;;;; 1823;MONGOLIAN LETTER O;Lo;0;L;;;;;N;;;;; 1824;MONGOLIAN LETTER U;Lo;0;L;;;;;N;;;;; 1825;MONGOLIAN LETTER OE;Lo;0;L;;;;;N;;;;; 1826;MONGOLIAN LETTER UE;Lo;0;L;;;;;N;;;;; 1827;MONGOLIAN LETTER EE;Lo;0;L;;;;;N;;;;; 1828;MONGOLIAN LETTER NA;Lo;0;L;;;;;N;;;;; 1829;MONGOLIAN LETTER ANG;Lo;0;L;;;;;N;;;;; 182A;MONGOLIAN LETTER BA;Lo;0;L;;;;;N;;;;; 182B;MONGOLIAN LETTER PA;Lo;0;L;;;;;N;;;;; 182C;MONGOLIAN LETTER QA;Lo;0;L;;;;;N;;;;; 182D;MONGOLIAN LETTER GA;Lo;0;L;;;;;N;;;;; 182E;MONGOLIAN LETTER MA;Lo;0;L;;;;;N;;;;; 182F;MONGOLIAN LETTER LA;Lo;0;L;;;;;N;;;;; 1830;MONGOLIAN LETTER SA;Lo;0;L;;;;;N;;;;; 1831;MONGOLIAN LETTER SHA;Lo;0;L;;;;;N;;;;; 1832;MONGOLIAN LETTER TA;Lo;0;L;;;;;N;;;;; 1833;MONGOLIAN LETTER DA;Lo;0;L;;;;;N;;;;; 1834;MONGOLIAN LETTER CHA;Lo;0;L;;;;;N;;;;; 1835;MONGOLIAN LETTER JA;Lo;0;L;;;;;N;;;;; 1836;MONGOLIAN LETTER YA;Lo;0;L;;;;;N;;;;; 1837;MONGOLIAN LETTER RA;Lo;0;L;;;;;N;;;;; 1838;MONGOLIAN LETTER WA;Lo;0;L;;;;;N;;;;; 1839;MONGOLIAN LETTER FA;Lo;0;L;;;;;N;;;;; 183A;MONGOLIAN LETTER KA;Lo;0;L;;;;;N;;;;; 183B;MONGOLIAN LETTER KHA;Lo;0;L;;;;;N;;;;; 183C;MONGOLIAN LETTER TSA;Lo;0;L;;;;;N;;;;; 183D;MONGOLIAN LETTER ZA;Lo;0;L;;;;;N;;;;; 183E;MONGOLIAN LETTER HAA;Lo;0;L;;;;;N;;;;; 183F;MONGOLIAN LETTER ZRA;Lo;0;L;;;;;N;;;;; 1840;MONGOLIAN LETTER LHA;Lo;0;L;;;;;N;;;;; 1841;MONGOLIAN LETTER ZHI;Lo;0;L;;;;;N;;;;; 1842;MONGOLIAN LETTER CHI;Lo;0;L;;;;;N;;;;; 1843;MONGOLIAN LETTER TODO LONG VOWEL SIGN;Lm;0;L;;;;;N;;;;; 1844;MONGOLIAN LETTER TODO E;Lo;0;L;;;;;N;;;;; 1845;MONGOLIAN LETTER TODO I;Lo;0;L;;;;;N;;;;; 1846;MONGOLIAN LETTER TODO O;Lo;0;L;;;;;N;;;;; 1847;MONGOLIAN LETTER TODO U;Lo;0;L;;;;;N;;;;; 1848;MONGOLIAN LETTER TODO OE;Lo;0;L;;;;;N;;;;; 1849;MONGOLIAN LETTER TODO UE;Lo;0;L;;;;;N;;;;; 184A;MONGOLIAN LETTER TODO ANG;Lo;0;L;;;;;N;;;;; 184B;MONGOLIAN LETTER TODO BA;Lo;0;L;;;;;N;;;;; 184C;MONGOLIAN LETTER TODO PA;Lo;0;L;;;;;N;;;;; 184D;MONGOLIAN LETTER TODO QA;Lo;0;L;;;;;N;;;;; 184E;MONGOLIAN LETTER TODO GA;Lo;0;L;;;;;N;;;;; 184F;MONGOLIAN LETTER TODO MA;Lo;0;L;;;;;N;;;;; 1850;MONGOLIAN LETTER TODO TA;Lo;0;L;;;;;N;;;;; 1851;MONGOLIAN LETTER TODO DA;Lo;0;L;;;;;N;;;;; 1852;MONGOLIAN LETTER TODO CHA;Lo;0;L;;;;;N;;;;; 1853;MONGOLIAN LETTER TODO JA;Lo;0;L;;;;;N;;;;; 1854;MONGOLIAN LETTER TODO TSA;Lo;0;L;;;;;N;;;;; 1855;MONGOLIAN LETTER TODO YA;Lo;0;L;;;;;N;;;;; 1856;MONGOLIAN LETTER TODO WA;Lo;0;L;;;;;N;;;;; 1857;MONGOLIAN LETTER TODO KA;Lo;0;L;;;;;N;;;;; 1858;MONGOLIAN LETTER TODO GAA;Lo;0;L;;;;;N;;;;; 1859;MONGOLIAN LETTER TODO HAA;Lo;0;L;;;;;N;;;;; 185A;MONGOLIAN LETTER TODO JIA;Lo;0;L;;;;;N;;;;; 185B;MONGOLIAN LETTER TODO NIA;Lo;0;L;;;;;N;;;;; 185C;MONGOLIAN LETTER TODO DZA;Lo;0;L;;;;;N;;;;; 185D;MONGOLIAN LETTER SIBE E;Lo;0;L;;;;;N;;;;; 185E;MONGOLIAN LETTER SIBE I;Lo;0;L;;;;;N;;;;; 185F;MONGOLIAN LETTER SIBE IY;Lo;0;L;;;;;N;;;;; 1860;MONGOLIAN LETTER SIBE UE;Lo;0;L;;;;;N;;;;; 1861;MONGOLIAN LETTER SIBE U;Lo;0;L;;;;;N;;;;; 1862;MONGOLIAN LETTER SIBE ANG;Lo;0;L;;;;;N;;;;; 1863;MONGOLIAN LETTER SIBE KA;Lo;0;L;;;;;N;;;;; 1864;MONGOLIAN LETTER SIBE GA;Lo;0;L;;;;;N;;;;; 1865;MONGOLIAN LETTER SIBE HA;Lo;0;L;;;;;N;;;;; 1866;MONGOLIAN LETTER SIBE PA;Lo;0;L;;;;;N;;;;; 1867;MONGOLIAN LETTER SIBE SHA;Lo;0;L;;;;;N;;;;; 1868;MONGOLIAN LETTER SIBE TA;Lo;0;L;;;;;N;;;;; 1869;MONGOLIAN LETTER SIBE DA;Lo;0;L;;;;;N;;;;; 186A;MONGOLIAN LETTER SIBE JA;Lo;0;L;;;;;N;;;;; 186B;MONGOLIAN LETTER SIBE FA;Lo;0;L;;;;;N;;;;; 186C;MONGOLIAN LETTER SIBE GAA;Lo;0;L;;;;;N;;;;; 186D;MONGOLIAN LETTER SIBE HAA;Lo;0;L;;;;;N;;;;; 186E;MONGOLIAN LETTER SIBE TSA;Lo;0;L;;;;;N;;;;; 186F;MONGOLIAN LETTER SIBE ZA;Lo;0;L;;;;;N;;;;; 1870;MONGOLIAN LETTER SIBE RAA;Lo;0;L;;;;;N;;;;; 1871;MONGOLIAN LETTER SIBE CHA;Lo;0;L;;;;;N;;;;; 1872;MONGOLIAN LETTER SIBE ZHA;Lo;0;L;;;;;N;;;;; 1873;MONGOLIAN LETTER MANCHU I;Lo;0;L;;;;;N;;;;; 1874;MONGOLIAN LETTER MANCHU KA;Lo;0;L;;;;;N;;;;; 1875;MONGOLIAN LETTER MANCHU RA;Lo;0;L;;;;;N;;;;; 1876;MONGOLIAN LETTER MANCHU FA;Lo;0;L;;;;;N;;;;; 1877;MONGOLIAN LETTER MANCHU ZHA;Lo;0;L;;;;;N;;;;; 1880;MONGOLIAN LETTER ALI GALI ANUSVARA ONE;Lo;0;L;;;;;N;;;;; 1881;MONGOLIAN LETTER ALI GALI VISARGA ONE;Lo;0;L;;;;;N;;;;; 1882;MONGOLIAN LETTER ALI GALI DAMARU;Lo;0;L;;;;;N;;;;; 1883;MONGOLIAN LETTER ALI GALI UBADAMA;Lo;0;L;;;;;N;;;;; 1884;MONGOLIAN LETTER ALI GALI INVERTED UBADAMA;Lo;0;L;;;;;N;;;;; 1885;MONGOLIAN LETTER ALI GALI BALUDA;Mn;0;NSM;;;;;N;;;;; 1886;MONGOLIAN LETTER ALI GALI THREE BALUDA;Mn;0;NSM;;;;;N;;;;; 1887;MONGOLIAN LETTER ALI GALI A;Lo;0;L;;;;;N;;;;; 1888;MONGOLIAN LETTER ALI GALI I;Lo;0;L;;;;;N;;;;; 1889;MONGOLIAN LETTER ALI GALI KA;Lo;0;L;;;;;N;;;;; 188A;MONGOLIAN LETTER ALI GALI NGA;Lo;0;L;;;;;N;;;;; 188B;MONGOLIAN LETTER ALI GALI CA;Lo;0;L;;;;;N;;;;; 188C;MONGOLIAN LETTER ALI GALI TTA;Lo;0;L;;;;;N;;;;; 188D;MONGOLIAN LETTER ALI GALI TTHA;Lo;0;L;;;;;N;;;;; 188E;MONGOLIAN LETTER ALI GALI DDA;Lo;0;L;;;;;N;;;;; 188F;MONGOLIAN LETTER ALI GALI NNA;Lo;0;L;;;;;N;;;;; 1890;MONGOLIAN LETTER ALI GALI TA;Lo;0;L;;;;;N;;;;; 1891;MONGOLIAN LETTER ALI GALI DA;Lo;0;L;;;;;N;;;;; 1892;MONGOLIAN LETTER ALI GALI PA;Lo;0;L;;;;;N;;;;; 1893;MONGOLIAN LETTER ALI GALI PHA;Lo;0;L;;;;;N;;;;; 1894;MONGOLIAN LETTER ALI GALI SSA;Lo;0;L;;;;;N;;;;; 1895;MONGOLIAN LETTER ALI GALI ZHA;Lo;0;L;;;;;N;;;;; 1896;MONGOLIAN LETTER ALI GALI ZA;Lo;0;L;;;;;N;;;;; 1897;MONGOLIAN LETTER ALI GALI AH;Lo;0;L;;;;;N;;;;; 1898;MONGOLIAN LETTER TODO ALI GALI TA;Lo;0;L;;;;;N;;;;; 1899;MONGOLIAN LETTER TODO ALI GALI ZHA;Lo;0;L;;;;;N;;;;; 189A;MONGOLIAN LETTER MANCHU ALI GALI GHA;Lo;0;L;;;;;N;;;;; 189B;MONGOLIAN LETTER MANCHU ALI GALI NGA;Lo;0;L;;;;;N;;;;; 189C;MONGOLIAN LETTER MANCHU ALI GALI CA;Lo;0;L;;;;;N;;;;; 189D;MONGOLIAN LETTER MANCHU ALI GALI JHA;Lo;0;L;;;;;N;;;;; 189E;MONGOLIAN LETTER MANCHU ALI GALI TTA;Lo;0;L;;;;;N;;;;; 189F;MONGOLIAN LETTER MANCHU ALI GALI DDHA;Lo;0;L;;;;;N;;;;; 18A0;MONGOLIAN LETTER MANCHU ALI GALI TA;Lo;0;L;;;;;N;;;;; 18A1;MONGOLIAN LETTER MANCHU ALI GALI DHA;Lo;0;L;;;;;N;;;;; 18A2;MONGOLIAN LETTER MANCHU ALI GALI SSA;Lo;0;L;;;;;N;;;;; 18A3;MONGOLIAN LETTER MANCHU ALI GALI CYA;Lo;0;L;;;;;N;;;;; 18A4;MONGOLIAN LETTER MANCHU ALI GALI ZHA;Lo;0;L;;;;;N;;;;; 18A5;MONGOLIAN LETTER MANCHU ALI GALI ZA;Lo;0;L;;;;;N;;;;; 18A6;MONGOLIAN LETTER ALI GALI HALF U;Lo;0;L;;;;;N;;;;; 18A7;MONGOLIAN LETTER ALI GALI HALF YA;Lo;0;L;;;;;N;;;;; 18A8;MONGOLIAN LETTER MANCHU ALI GALI BHA;Lo;0;L;;;;;N;;;;; 18A9;MONGOLIAN LETTER ALI GALI DAGALGA;Mn;228;NSM;;;;;N;;;;; 18AA;MONGOLIAN LETTER MANCHU ALI GALI LHA;Lo;0;L;;;;;N;;;;; 18B0;CANADIAN SYLLABICS OY;Lo;0;L;;;;;N;;;;; 18B1;CANADIAN SYLLABICS AY;Lo;0;L;;;;;N;;;;; 18B2;CANADIAN SYLLABICS AAY;Lo;0;L;;;;;N;;;;; 18B3;CANADIAN SYLLABICS WAY;Lo;0;L;;;;;N;;;;; 18B4;CANADIAN SYLLABICS POY;Lo;0;L;;;;;N;;;;; 18B5;CANADIAN SYLLABICS PAY;Lo;0;L;;;;;N;;;;; 18B6;CANADIAN SYLLABICS PWOY;Lo;0;L;;;;;N;;;;; 18B7;CANADIAN SYLLABICS TAY;Lo;0;L;;;;;N;;;;; 18B8;CANADIAN SYLLABICS KAY;Lo;0;L;;;;;N;;;;; 18B9;CANADIAN SYLLABICS KWAY;Lo;0;L;;;;;N;;;;; 18BA;CANADIAN SYLLABICS MAY;Lo;0;L;;;;;N;;;;; 18BB;CANADIAN SYLLABICS NOY;Lo;0;L;;;;;N;;;;; 18BC;CANADIAN SYLLABICS NAY;Lo;0;L;;;;;N;;;;; 18BD;CANADIAN SYLLABICS LAY;Lo;0;L;;;;;N;;;;; 18BE;CANADIAN SYLLABICS SOY;Lo;0;L;;;;;N;;;;; 18BF;CANADIAN SYLLABICS SAY;Lo;0;L;;;;;N;;;;; 18C0;CANADIAN SYLLABICS SHOY;Lo;0;L;;;;;N;;;;; 18C1;CANADIAN SYLLABICS SHAY;Lo;0;L;;;;;N;;;;; 18C2;CANADIAN SYLLABICS SHWOY;Lo;0;L;;;;;N;;;;; 18C3;CANADIAN SYLLABICS YOY;Lo;0;L;;;;;N;;;;; 18C4;CANADIAN SYLLABICS YAY;Lo;0;L;;;;;N;;;;; 18C5;CANADIAN SYLLABICS RAY;Lo;0;L;;;;;N;;;;; 18C6;CANADIAN SYLLABICS NWI;Lo;0;L;;;;;N;;;;; 18C7;CANADIAN SYLLABICS OJIBWAY NWI;Lo;0;L;;;;;N;;;;; 18C8;CANADIAN SYLLABICS NWII;Lo;0;L;;;;;N;;;;; 18C9;CANADIAN SYLLABICS OJIBWAY NWII;Lo;0;L;;;;;N;;;;; 18CA;CANADIAN SYLLABICS NWO;Lo;0;L;;;;;N;;;;; 18CB;CANADIAN SYLLABICS OJIBWAY NWO;Lo;0;L;;;;;N;;;;; 18CC;CANADIAN SYLLABICS NWOO;Lo;0;L;;;;;N;;;;; 18CD;CANADIAN SYLLABICS OJIBWAY NWOO;Lo;0;L;;;;;N;;;;; 18CE;CANADIAN SYLLABICS RWEE;Lo;0;L;;;;;N;;;;; 18CF;CANADIAN SYLLABICS RWI;Lo;0;L;;;;;N;;;;; 18D0;CANADIAN SYLLABICS RWII;Lo;0;L;;;;;N;;;;; 18D1;CANADIAN SYLLABICS RWO;Lo;0;L;;;;;N;;;;; 18D2;CANADIAN SYLLABICS RWOO;Lo;0;L;;;;;N;;;;; 18D3;CANADIAN SYLLABICS RWA;Lo;0;L;;;;;N;;;;; 18D4;CANADIAN SYLLABICS OJIBWAY P;Lo;0;L;;;;;N;;;;; 18D5;CANADIAN SYLLABICS OJIBWAY T;Lo;0;L;;;;;N;;;;; 18D6;CANADIAN SYLLABICS OJIBWAY K;Lo;0;L;;;;;N;;;;; 18D7;CANADIAN SYLLABICS OJIBWAY C;Lo;0;L;;;;;N;;;;; 18D8;CANADIAN SYLLABICS OJIBWAY M;Lo;0;L;;;;;N;;;;; 18D9;CANADIAN SYLLABICS OJIBWAY N;Lo;0;L;;;;;N;;;;; 18DA;CANADIAN SYLLABICS OJIBWAY S;Lo;0;L;;;;;N;;;;; 18DB;CANADIAN SYLLABICS OJIBWAY SH;Lo;0;L;;;;;N;;;;; 18DC;CANADIAN SYLLABICS EASTERN W;Lo;0;L;;;;;N;;;;; 18DD;CANADIAN SYLLABICS WESTERN W;Lo;0;L;;;;;N;;;;; 18DE;CANADIAN SYLLABICS FINAL SMALL RING;Lo;0;L;;;;;N;;;;; 18DF;CANADIAN SYLLABICS FINAL RAISED DOT;Lo;0;L;;;;;N;;;;; 18E0;CANADIAN SYLLABICS R-CREE RWE;Lo;0;L;;;;;N;;;;; 18E1;CANADIAN SYLLABICS WEST-CREE LOO;Lo;0;L;;;;;N;;;;; 18E2;CANADIAN SYLLABICS WEST-CREE LAA;Lo;0;L;;;;;N;;;;; 18E3;CANADIAN SYLLABICS THWE;Lo;0;L;;;;;N;;;;; 18E4;CANADIAN SYLLABICS THWA;Lo;0;L;;;;;N;;;;; 18E5;CANADIAN SYLLABICS TTHWE;Lo;0;L;;;;;N;;;;; 18E6;CANADIAN SYLLABICS TTHOO;Lo;0;L;;;;;N;;;;; 18E7;CANADIAN SYLLABICS TTHAA;Lo;0;L;;;;;N;;;;; 18E8;CANADIAN SYLLABICS TLHWE;Lo;0;L;;;;;N;;;;; 18E9;CANADIAN SYLLABICS TLHOO;Lo;0;L;;;;;N;;;;; 18EA;CANADIAN SYLLABICS SAYISI SHWE;Lo;0;L;;;;;N;;;;; 18EB;CANADIAN SYLLABICS SAYISI SHOO;Lo;0;L;;;;;N;;;;; 18EC;CANADIAN SYLLABICS SAYISI HOO;Lo;0;L;;;;;N;;;;; 18ED;CANADIAN SYLLABICS CARRIER GWU;Lo;0;L;;;;;N;;;;; 18EE;CANADIAN SYLLABICS CARRIER DENE GEE;Lo;0;L;;;;;N;;;;; 18EF;CANADIAN SYLLABICS CARRIER GAA;Lo;0;L;;;;;N;;;;; 18F0;CANADIAN SYLLABICS CARRIER GWA;Lo;0;L;;;;;N;;;;; 18F1;CANADIAN SYLLABICS SAYISI JUU;Lo;0;L;;;;;N;;;;; 18F2;CANADIAN SYLLABICS CARRIER JWA;Lo;0;L;;;;;N;;;;; 18F3;CANADIAN SYLLABICS BEAVER DENE L;Lo;0;L;;;;;N;;;;; 18F4;CANADIAN SYLLABICS BEAVER DENE R;Lo;0;L;;;;;N;;;;; 18F5;CANADIAN SYLLABICS CARRIER DENTAL S;Lo;0;L;;;;;N;;;;; 1900;LIMBU VOWEL-CARRIER LETTER;Lo;0;L;;;;;N;;;;; 1901;LIMBU LETTER KA;Lo;0;L;;;;;N;;;;; 1902;LIMBU LETTER KHA;Lo;0;L;;;;;N;;;;; 1903;LIMBU LETTER GA;Lo;0;L;;;;;N;;;;; 1904;LIMBU LETTER GHA;Lo;0;L;;;;;N;;;;; 1905;LIMBU LETTER NGA;Lo;0;L;;;;;N;;;;; 1906;LIMBU LETTER CA;Lo;0;L;;;;;N;;;;; 1907;LIMBU LETTER CHA;Lo;0;L;;;;;N;;;;; 1908;LIMBU LETTER JA;Lo;0;L;;;;;N;;;;; 1909;LIMBU LETTER JHA;Lo;0;L;;;;;N;;;;; 190A;LIMBU LETTER YAN;Lo;0;L;;;;;N;;;;; 190B;LIMBU LETTER TA;Lo;0;L;;;;;N;;;;; 190C;LIMBU LETTER THA;Lo;0;L;;;;;N;;;;; 190D;LIMBU LETTER DA;Lo;0;L;;;;;N;;;;; 190E;LIMBU LETTER DHA;Lo;0;L;;;;;N;;;;; 190F;LIMBU LETTER NA;Lo;0;L;;;;;N;;;;; 1910;LIMBU LETTER PA;Lo;0;L;;;;;N;;;;; 1911;LIMBU LETTER PHA;Lo;0;L;;;;;N;;;;; 1912;LIMBU LETTER BA;Lo;0;L;;;;;N;;;;; 1913;LIMBU LETTER BHA;Lo;0;L;;;;;N;;;;; 1914;LIMBU LETTER MA;Lo;0;L;;;;;N;;;;; 1915;LIMBU LETTER YA;Lo;0;L;;;;;N;;;;; 1916;LIMBU LETTER RA;Lo;0;L;;;;;N;;;;; 1917;LIMBU LETTER LA;Lo;0;L;;;;;N;;;;; 1918;LIMBU LETTER WA;Lo;0;L;;;;;N;;;;; 1919;LIMBU LETTER SHA;Lo;0;L;;;;;N;;;;; 191A;LIMBU LETTER SSA;Lo;0;L;;;;;N;;;;; 191B;LIMBU LETTER SA;Lo;0;L;;;;;N;;;;; 191C;LIMBU LETTER HA;Lo;0;L;;;;;N;;;;; 191D;LIMBU LETTER GYAN;Lo;0;L;;;;;N;;;;; 191E;LIMBU LETTER TRA;Lo;0;L;;;;;N;;;;; 1920;LIMBU VOWEL SIGN A;Mn;0;NSM;;;;;N;;;;; 1921;LIMBU VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; 1922;LIMBU VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 1923;LIMBU VOWEL SIGN EE;Mc;0;L;;;;;N;;;;; 1924;LIMBU VOWEL SIGN AI;Mc;0;L;;;;;N;;;;; 1925;LIMBU VOWEL SIGN OO;Mc;0;L;;;;;N;;;;; 1926;LIMBU VOWEL SIGN AU;Mc;0;L;;;;;N;;;;; 1927;LIMBU VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;; 1928;LIMBU VOWEL SIGN O;Mn;0;NSM;;;;;N;;;;; 1929;LIMBU SUBJOINED LETTER YA;Mc;0;L;;;;;N;;;;; 192A;LIMBU SUBJOINED LETTER RA;Mc;0;L;;;;;N;;;;; 192B;LIMBU SUBJOINED LETTER WA;Mc;0;L;;;;;N;;;;; 1930;LIMBU SMALL LETTER KA;Mc;0;L;;;;;N;;;;; 1931;LIMBU SMALL LETTER NGA;Mc;0;L;;;;;N;;;;; 1932;LIMBU SMALL LETTER ANUSVARA;Mn;0;NSM;;;;;N;;;;; 1933;LIMBU SMALL LETTER TA;Mc;0;L;;;;;N;;;;; 1934;LIMBU SMALL LETTER NA;Mc;0;L;;;;;N;;;;; 1935;LIMBU SMALL LETTER PA;Mc;0;L;;;;;N;;;;; 1936;LIMBU SMALL LETTER MA;Mc;0;L;;;;;N;;;;; 1937;LIMBU SMALL LETTER RA;Mc;0;L;;;;;N;;;;; 1938;LIMBU SMALL LETTER LA;Mc;0;L;;;;;N;;;;; 1939;LIMBU SIGN MUKPHRENG;Mn;222;NSM;;;;;N;;;;; 193A;LIMBU SIGN KEMPHRENG;Mn;230;NSM;;;;;N;;;;; 193B;LIMBU SIGN SA-I;Mn;220;NSM;;;;;N;;;;; 1940;LIMBU SIGN LOO;So;0;ON;;;;;N;;;;; 1944;LIMBU EXCLAMATION MARK;Po;0;ON;;;;;N;;;;; 1945;LIMBU QUESTION MARK;Po;0;ON;;;;;N;;;;; 1946;LIMBU DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 1947;LIMBU DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 1948;LIMBU DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 1949;LIMBU DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 194A;LIMBU DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 194B;LIMBU DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 194C;LIMBU DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 194D;LIMBU DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 194E;LIMBU DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 194F;LIMBU DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 1950;TAI LE LETTER KA;Lo;0;L;;;;;N;;;;; 1951;TAI LE LETTER XA;Lo;0;L;;;;;N;;;;; 1952;TAI LE LETTER NGA;Lo;0;L;;;;;N;;;;; 1953;TAI LE LETTER TSA;Lo;0;L;;;;;N;;;;; 1954;TAI LE LETTER SA;Lo;0;L;;;;;N;;;;; 1955;TAI LE LETTER YA;Lo;0;L;;;;;N;;;;; 1956;TAI LE LETTER TA;Lo;0;L;;;;;N;;;;; 1957;TAI LE LETTER THA;Lo;0;L;;;;;N;;;;; 1958;TAI LE LETTER LA;Lo;0;L;;;;;N;;;;; 1959;TAI LE LETTER PA;Lo;0;L;;;;;N;;;;; 195A;TAI LE LETTER PHA;Lo;0;L;;;;;N;;;;; 195B;TAI LE LETTER MA;Lo;0;L;;;;;N;;;;; 195C;TAI LE LETTER FA;Lo;0;L;;;;;N;;;;; 195D;TAI LE LETTER VA;Lo;0;L;;;;;N;;;;; 195E;TAI LE LETTER HA;Lo;0;L;;;;;N;;;;; 195F;TAI LE LETTER QA;Lo;0;L;;;;;N;;;;; 1960;TAI LE LETTER KHA;Lo;0;L;;;;;N;;;;; 1961;TAI LE LETTER TSHA;Lo;0;L;;;;;N;;;;; 1962;TAI LE LETTER NA;Lo;0;L;;;;;N;;;;; 1963;TAI LE LETTER A;Lo;0;L;;;;;N;;;;; 1964;TAI LE LETTER I;Lo;0;L;;;;;N;;;;; 1965;TAI LE LETTER EE;Lo;0;L;;;;;N;;;;; 1966;TAI LE LETTER EH;Lo;0;L;;;;;N;;;;; 1967;TAI LE LETTER U;Lo;0;L;;;;;N;;;;; 1968;TAI LE LETTER OO;Lo;0;L;;;;;N;;;;; 1969;TAI LE LETTER O;Lo;0;L;;;;;N;;;;; 196A;TAI LE LETTER UE;Lo;0;L;;;;;N;;;;; 196B;TAI LE LETTER E;Lo;0;L;;;;;N;;;;; 196C;TAI LE LETTER AUE;Lo;0;L;;;;;N;;;;; 196D;TAI LE LETTER AI;Lo;0;L;;;;;N;;;;; 1970;TAI LE LETTER TONE-2;Lo;0;L;;;;;N;;;;; 1971;TAI LE LETTER TONE-3;Lo;0;L;;;;;N;;;;; 1972;TAI LE LETTER TONE-4;Lo;0;L;;;;;N;;;;; 1973;TAI LE LETTER TONE-5;Lo;0;L;;;;;N;;;;; 1974;TAI LE LETTER TONE-6;Lo;0;L;;;;;N;;;;; 1980;NEW TAI LUE LETTER HIGH QA;Lo;0;L;;;;;N;;;;; 1981;NEW TAI LUE LETTER LOW QA;Lo;0;L;;;;;N;;;;; 1982;NEW TAI LUE LETTER HIGH KA;Lo;0;L;;;;;N;;;;; 1983;NEW TAI LUE LETTER HIGH XA;Lo;0;L;;;;;N;;;;; 1984;NEW TAI LUE LETTER HIGH NGA;Lo;0;L;;;;;N;;;;; 1985;NEW TAI LUE LETTER LOW KA;Lo;0;L;;;;;N;;;;; 1986;NEW TAI LUE LETTER LOW XA;Lo;0;L;;;;;N;;;;; 1987;NEW TAI LUE LETTER LOW NGA;Lo;0;L;;;;;N;;;;; 1988;NEW TAI LUE LETTER HIGH TSA;Lo;0;L;;;;;N;;;;; 1989;NEW TAI LUE LETTER HIGH SA;Lo;0;L;;;;;N;;;;; 198A;NEW TAI LUE LETTER HIGH YA;Lo;0;L;;;;;N;;;;; 198B;NEW TAI LUE LETTER LOW TSA;Lo;0;L;;;;;N;;;;; 198C;NEW TAI LUE LETTER LOW SA;Lo;0;L;;;;;N;;;;; 198D;NEW TAI LUE LETTER LOW YA;Lo;0;L;;;;;N;;;;; 198E;NEW TAI LUE LETTER HIGH TA;Lo;0;L;;;;;N;;;;; 198F;NEW TAI LUE LETTER HIGH THA;Lo;0;L;;;;;N;;;;; 1990;NEW TAI LUE LETTER HIGH NA;Lo;0;L;;;;;N;;;;; 1991;NEW TAI LUE LETTER LOW TA;Lo;0;L;;;;;N;;;;; 1992;NEW TAI LUE LETTER LOW THA;Lo;0;L;;;;;N;;;;; 1993;NEW TAI LUE LETTER LOW NA;Lo;0;L;;;;;N;;;;; 1994;NEW TAI LUE LETTER HIGH PA;Lo;0;L;;;;;N;;;;; 1995;NEW TAI LUE LETTER HIGH PHA;Lo;0;L;;;;;N;;;;; 1996;NEW TAI LUE LETTER HIGH MA;Lo;0;L;;;;;N;;;;; 1997;NEW TAI LUE LETTER LOW PA;Lo;0;L;;;;;N;;;;; 1998;NEW TAI LUE LETTER LOW PHA;Lo;0;L;;;;;N;;;;; 1999;NEW TAI LUE LETTER LOW MA;Lo;0;L;;;;;N;;;;; 199A;NEW TAI LUE LETTER HIGH FA;Lo;0;L;;;;;N;;;;; 199B;NEW TAI LUE LETTER HIGH VA;Lo;0;L;;;;;N;;;;; 199C;NEW TAI LUE LETTER HIGH LA;Lo;0;L;;;;;N;;;;; 199D;NEW TAI LUE LETTER LOW FA;Lo;0;L;;;;;N;;;;; 199E;NEW TAI LUE LETTER LOW VA;Lo;0;L;;;;;N;;;;; 199F;NEW TAI LUE LETTER LOW LA;Lo;0;L;;;;;N;;;;; 19A0;NEW TAI LUE LETTER HIGH HA;Lo;0;L;;;;;N;;;;; 19A1;NEW TAI LUE LETTER HIGH DA;Lo;0;L;;;;;N;;;;; 19A2;NEW TAI LUE LETTER HIGH BA;Lo;0;L;;;;;N;;;;; 19A3;NEW TAI LUE LETTER LOW HA;Lo;0;L;;;;;N;;;;; 19A4;NEW TAI LUE LETTER LOW DA;Lo;0;L;;;;;N;;;;; 19A5;NEW TAI LUE LETTER LOW BA;Lo;0;L;;;;;N;;;;; 19A6;NEW TAI LUE LETTER HIGH KVA;Lo;0;L;;;;;N;;;;; 19A7;NEW TAI LUE LETTER HIGH XVA;Lo;0;L;;;;;N;;;;; 19A8;NEW TAI LUE LETTER LOW KVA;Lo;0;L;;;;;N;;;;; 19A9;NEW TAI LUE LETTER LOW XVA;Lo;0;L;;;;;N;;;;; 19AA;NEW TAI LUE LETTER HIGH SUA;Lo;0;L;;;;;N;;;;; 19AB;NEW TAI LUE LETTER LOW SUA;Lo;0;L;;;;;N;;;;; 19B0;NEW TAI LUE VOWEL SIGN VOWEL SHORTENER;Lo;0;L;;;;;N;;;;; 19B1;NEW TAI LUE VOWEL SIGN AA;Lo;0;L;;;;;N;;;;; 19B2;NEW TAI LUE VOWEL SIGN II;Lo;0;L;;;;;N;;;;; 19B3;NEW TAI LUE VOWEL SIGN U;Lo;0;L;;;;;N;;;;; 19B4;NEW TAI LUE VOWEL SIGN UU;Lo;0;L;;;;;N;;;;; 19B5;NEW TAI LUE VOWEL SIGN E;Lo;0;L;;;;;N;;;;; 19B6;NEW TAI LUE VOWEL SIGN AE;Lo;0;L;;;;;N;;;;; 19B7;NEW TAI LUE VOWEL SIGN O;Lo;0;L;;;;;N;;;;; 19B8;NEW TAI LUE VOWEL SIGN OA;Lo;0;L;;;;;N;;;;; 19B9;NEW TAI LUE VOWEL SIGN UE;Lo;0;L;;;;;N;;;;; 19BA;NEW TAI LUE VOWEL SIGN AY;Lo;0;L;;;;;N;;;;; 19BB;NEW TAI LUE VOWEL SIGN AAY;Lo;0;L;;;;;N;;;;; 19BC;NEW TAI LUE VOWEL SIGN UY;Lo;0;L;;;;;N;;;;; 19BD;NEW TAI LUE VOWEL SIGN OY;Lo;0;L;;;;;N;;;;; 19BE;NEW TAI LUE VOWEL SIGN OAY;Lo;0;L;;;;;N;;;;; 19BF;NEW TAI LUE VOWEL SIGN UEY;Lo;0;L;;;;;N;;;;; 19C0;NEW TAI LUE VOWEL SIGN IY;Lo;0;L;;;;;N;;;;; 19C1;NEW TAI LUE LETTER FINAL V;Lo;0;L;;;;;N;;;;; 19C2;NEW TAI LUE LETTER FINAL NG;Lo;0;L;;;;;N;;;;; 19C3;NEW TAI LUE LETTER FINAL N;Lo;0;L;;;;;N;;;;; 19C4;NEW TAI LUE LETTER FINAL M;Lo;0;L;;;;;N;;;;; 19C5;NEW TAI LUE LETTER FINAL K;Lo;0;L;;;;;N;;;;; 19C6;NEW TAI LUE LETTER FINAL D;Lo;0;L;;;;;N;;;;; 19C7;NEW TAI LUE LETTER FINAL B;Lo;0;L;;;;;N;;;;; 19C8;NEW TAI LUE TONE MARK-1;Lo;0;L;;;;;N;;;;; 19C9;NEW TAI LUE TONE MARK-2;Lo;0;L;;;;;N;;;;; 19D0;NEW TAI LUE DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 19D1;NEW TAI LUE DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 19D2;NEW TAI LUE DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 19D3;NEW TAI LUE DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 19D4;NEW TAI LUE DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 19D5;NEW TAI LUE DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 19D6;NEW TAI LUE DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 19D7;NEW TAI LUE DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 19D8;NEW TAI LUE DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 19D9;NEW TAI LUE DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 19DA;NEW TAI LUE THAM DIGIT ONE;No;0;L;;;1;1;N;;;;; 19DE;NEW TAI LUE SIGN LAE;So;0;ON;;;;;N;;;;; 19DF;NEW TAI LUE SIGN LAEV;So;0;ON;;;;;N;;;;; 19E0;KHMER SYMBOL PATHAMASAT;So;0;ON;;;;;N;;;;; 19E1;KHMER SYMBOL MUOY KOET;So;0;ON;;;;;N;;;;; 19E2;KHMER SYMBOL PII KOET;So;0;ON;;;;;N;;;;; 19E3;KHMER SYMBOL BEI KOET;So;0;ON;;;;;N;;;;; 19E4;KHMER SYMBOL BUON KOET;So;0;ON;;;;;N;;;;; 19E5;KHMER SYMBOL PRAM KOET;So;0;ON;;;;;N;;;;; 19E6;KHMER SYMBOL PRAM-MUOY KOET;So;0;ON;;;;;N;;;;; 19E7;KHMER SYMBOL PRAM-PII KOET;So;0;ON;;;;;N;;;;; 19E8;KHMER SYMBOL PRAM-BEI KOET;So;0;ON;;;;;N;;;;; 19E9;KHMER SYMBOL PRAM-BUON KOET;So;0;ON;;;;;N;;;;; 19EA;KHMER SYMBOL DAP KOET;So;0;ON;;;;;N;;;;; 19EB;KHMER SYMBOL DAP-MUOY KOET;So;0;ON;;;;;N;;;;; 19EC;KHMER SYMBOL DAP-PII KOET;So;0;ON;;;;;N;;;;; 19ED;KHMER SYMBOL DAP-BEI KOET;So;0;ON;;;;;N;;;;; 19EE;KHMER SYMBOL DAP-BUON KOET;So;0;ON;;;;;N;;;;; 19EF;KHMER SYMBOL DAP-PRAM KOET;So;0;ON;;;;;N;;;;; 19F0;KHMER SYMBOL TUTEYASAT;So;0;ON;;;;;N;;;;; 19F1;KHMER SYMBOL MUOY ROC;So;0;ON;;;;;N;;;;; 19F2;KHMER SYMBOL PII ROC;So;0;ON;;;;;N;;;;; 19F3;KHMER SYMBOL BEI ROC;So;0;ON;;;;;N;;;;; 19F4;KHMER SYMBOL BUON ROC;So;0;ON;;;;;N;;;;; 19F5;KHMER SYMBOL PRAM ROC;So;0;ON;;;;;N;;;;; 19F6;KHMER SYMBOL PRAM-MUOY ROC;So;0;ON;;;;;N;;;;; 19F7;KHMER SYMBOL PRAM-PII ROC;So;0;ON;;;;;N;;;;; 19F8;KHMER SYMBOL PRAM-BEI ROC;So;0;ON;;;;;N;;;;; 19F9;KHMER SYMBOL PRAM-BUON ROC;So;0;ON;;;;;N;;;;; 19FA;KHMER SYMBOL DAP ROC;So;0;ON;;;;;N;;;;; 19FB;KHMER SYMBOL DAP-MUOY ROC;So;0;ON;;;;;N;;;;; 19FC;KHMER SYMBOL DAP-PII ROC;So;0;ON;;;;;N;;;;; 19FD;KHMER SYMBOL DAP-BEI ROC;So;0;ON;;;;;N;;;;; 19FE;KHMER SYMBOL DAP-BUON ROC;So;0;ON;;;;;N;;;;; 19FF;KHMER SYMBOL DAP-PRAM ROC;So;0;ON;;;;;N;;;;; 1A00;BUGINESE LETTER KA;Lo;0;L;;;;;N;;;;; 1A01;BUGINESE LETTER GA;Lo;0;L;;;;;N;;;;; 1A02;BUGINESE LETTER NGA;Lo;0;L;;;;;N;;;;; 1A03;BUGINESE LETTER NGKA;Lo;0;L;;;;;N;;;;; 1A04;BUGINESE LETTER PA;Lo;0;L;;;;;N;;;;; 1A05;BUGINESE LETTER BA;Lo;0;L;;;;;N;;;;; 1A06;BUGINESE LETTER MA;Lo;0;L;;;;;N;;;;; 1A07;BUGINESE LETTER MPA;Lo;0;L;;;;;N;;;;; 1A08;BUGINESE LETTER TA;Lo;0;L;;;;;N;;;;; 1A09;BUGINESE LETTER DA;Lo;0;L;;;;;N;;;;; 1A0A;BUGINESE LETTER NA;Lo;0;L;;;;;N;;;;; 1A0B;BUGINESE LETTER NRA;Lo;0;L;;;;;N;;;;; 1A0C;BUGINESE LETTER CA;Lo;0;L;;;;;N;;;;; 1A0D;BUGINESE LETTER JA;Lo;0;L;;;;;N;;;;; 1A0E;BUGINESE LETTER NYA;Lo;0;L;;;;;N;;;;; 1A0F;BUGINESE LETTER NYCA;Lo;0;L;;;;;N;;;;; 1A10;BUGINESE LETTER YA;Lo;0;L;;;;;N;;;;; 1A11;BUGINESE LETTER RA;Lo;0;L;;;;;N;;;;; 1A12;BUGINESE LETTER LA;Lo;0;L;;;;;N;;;;; 1A13;BUGINESE LETTER VA;Lo;0;L;;;;;N;;;;; 1A14;BUGINESE LETTER SA;Lo;0;L;;;;;N;;;;; 1A15;BUGINESE LETTER A;Lo;0;L;;;;;N;;;;; 1A16;BUGINESE LETTER HA;Lo;0;L;;;;;N;;;;; 1A17;BUGINESE VOWEL SIGN I;Mn;230;NSM;;;;;N;;;;; 1A18;BUGINESE VOWEL SIGN U;Mn;220;NSM;;;;;N;;;;; 1A19;BUGINESE VOWEL SIGN E;Mc;0;L;;;;;N;;;;; 1A1A;BUGINESE VOWEL SIGN O;Mc;0;L;;;;;N;;;;; 1A1B;BUGINESE VOWEL SIGN AE;Mn;0;NSM;;;;;N;;;;; 1A1E;BUGINESE PALLAWA;Po;0;L;;;;;N;;;;; 1A1F;BUGINESE END OF SECTION;Po;0;L;;;;;N;;;;; 1A20;TAI THAM LETTER HIGH KA;Lo;0;L;;;;;N;;;;; 1A21;TAI THAM LETTER HIGH KHA;Lo;0;L;;;;;N;;;;; 1A22;TAI THAM LETTER HIGH KXA;Lo;0;L;;;;;N;;;;; 1A23;TAI THAM LETTER LOW KA;Lo;0;L;;;;;N;;;;; 1A24;TAI THAM LETTER LOW KXA;Lo;0;L;;;;;N;;;;; 1A25;TAI THAM LETTER LOW KHA;Lo;0;L;;;;;N;;;;; 1A26;TAI THAM LETTER NGA;Lo;0;L;;;;;N;;;;; 1A27;TAI THAM LETTER HIGH CA;Lo;0;L;;;;;N;;;;; 1A28;TAI THAM LETTER HIGH CHA;Lo;0;L;;;;;N;;;;; 1A29;TAI THAM LETTER LOW CA;Lo;0;L;;;;;N;;;;; 1A2A;TAI THAM LETTER LOW SA;Lo;0;L;;;;;N;;;;; 1A2B;TAI THAM LETTER LOW CHA;Lo;0;L;;;;;N;;;;; 1A2C;TAI THAM LETTER NYA;Lo;0;L;;;;;N;;;;; 1A2D;TAI THAM LETTER RATA;Lo;0;L;;;;;N;;;;; 1A2E;TAI THAM LETTER HIGH RATHA;Lo;0;L;;;;;N;;;;; 1A2F;TAI THAM LETTER DA;Lo;0;L;;;;;N;;;;; 1A30;TAI THAM LETTER LOW RATHA;Lo;0;L;;;;;N;;;;; 1A31;TAI THAM LETTER RANA;Lo;0;L;;;;;N;;;;; 1A32;TAI THAM LETTER HIGH TA;Lo;0;L;;;;;N;;;;; 1A33;TAI THAM LETTER HIGH THA;Lo;0;L;;;;;N;;;;; 1A34;TAI THAM LETTER LOW TA;Lo;0;L;;;;;N;;;;; 1A35;TAI THAM LETTER LOW THA;Lo;0;L;;;;;N;;;;; 1A36;TAI THAM LETTER NA;Lo;0;L;;;;;N;;;;; 1A37;TAI THAM LETTER BA;Lo;0;L;;;;;N;;;;; 1A38;TAI THAM LETTER HIGH PA;Lo;0;L;;;;;N;;;;; 1A39;TAI THAM LETTER HIGH PHA;Lo;0;L;;;;;N;;;;; 1A3A;TAI THAM LETTER HIGH FA;Lo;0;L;;;;;N;;;;; 1A3B;TAI THAM LETTER LOW PA;Lo;0;L;;;;;N;;;;; 1A3C;TAI THAM LETTER LOW FA;Lo;0;L;;;;;N;;;;; 1A3D;TAI THAM LETTER LOW PHA;Lo;0;L;;;;;N;;;;; 1A3E;TAI THAM LETTER MA;Lo;0;L;;;;;N;;;;; 1A3F;TAI THAM LETTER LOW YA;Lo;0;L;;;;;N;;;;; 1A40;TAI THAM LETTER HIGH YA;Lo;0;L;;;;;N;;;;; 1A41;TAI THAM LETTER RA;Lo;0;L;;;;;N;;;;; 1A42;TAI THAM LETTER RUE;Lo;0;L;;;;;N;;;;; 1A43;TAI THAM LETTER LA;Lo;0;L;;;;;N;;;;; 1A44;TAI THAM LETTER LUE;Lo;0;L;;;;;N;;;;; 1A45;TAI THAM LETTER WA;Lo;0;L;;;;;N;;;;; 1A46;TAI THAM LETTER HIGH SHA;Lo;0;L;;;;;N;;;;; 1A47;TAI THAM LETTER HIGH SSA;Lo;0;L;;;;;N;;;;; 1A48;TAI THAM LETTER HIGH SA;Lo;0;L;;;;;N;;;;; 1A49;TAI THAM LETTER HIGH HA;Lo;0;L;;;;;N;;;;; 1A4A;TAI THAM LETTER LLA;Lo;0;L;;;;;N;;;;; 1A4B;TAI THAM LETTER A;Lo;0;L;;;;;N;;;;; 1A4C;TAI THAM LETTER LOW HA;Lo;0;L;;;;;N;;;;; 1A4D;TAI THAM LETTER I;Lo;0;L;;;;;N;;;;; 1A4E;TAI THAM LETTER II;Lo;0;L;;;;;N;;;;; 1A4F;TAI THAM LETTER U;Lo;0;L;;;;;N;;;;; 1A50;TAI THAM LETTER UU;Lo;0;L;;;;;N;;;;; 1A51;TAI THAM LETTER EE;Lo;0;L;;;;;N;;;;; 1A52;TAI THAM LETTER OO;Lo;0;L;;;;;N;;;;; 1A53;TAI THAM LETTER LAE;Lo;0;L;;;;;N;;;;; 1A54;TAI THAM LETTER GREAT SA;Lo;0;L;;;;;N;;;;; 1A55;TAI THAM CONSONANT SIGN MEDIAL RA;Mc;0;L;;;;;N;;;;; 1A56;TAI THAM CONSONANT SIGN MEDIAL LA;Mn;0;NSM;;;;;N;;;;; 1A57;TAI THAM CONSONANT SIGN LA TANG LAI;Mc;0;L;;;;;N;;;;; 1A58;TAI THAM SIGN MAI KANG LAI;Mn;0;NSM;;;;;N;;;;; 1A59;TAI THAM CONSONANT SIGN FINAL NGA;Mn;0;NSM;;;;;N;;;;; 1A5A;TAI THAM CONSONANT SIGN LOW PA;Mn;0;NSM;;;;;N;;;;; 1A5B;TAI THAM CONSONANT SIGN HIGH RATHA OR LOW PA;Mn;0;NSM;;;;;N;;;;; 1A5C;TAI THAM CONSONANT SIGN MA;Mn;0;NSM;;;;;N;;;;; 1A5D;TAI THAM CONSONANT SIGN BA;Mn;0;NSM;;;;;N;;;;; 1A5E;TAI THAM CONSONANT SIGN SA;Mn;0;NSM;;;;;N;;;;; 1A60;TAI THAM SIGN SAKOT;Mn;9;NSM;;;;;N;;;;; 1A61;TAI THAM VOWEL SIGN A;Mc;0;L;;;;;N;;;;; 1A62;TAI THAM VOWEL SIGN MAI SAT;Mn;0;NSM;;;;;N;;;;; 1A63;TAI THAM VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 1A64;TAI THAM VOWEL SIGN TALL AA;Mc;0;L;;;;;N;;;;; 1A65;TAI THAM VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; 1A66;TAI THAM VOWEL SIGN II;Mn;0;NSM;;;;;N;;;;; 1A67;TAI THAM VOWEL SIGN UE;Mn;0;NSM;;;;;N;;;;; 1A68;TAI THAM VOWEL SIGN UUE;Mn;0;NSM;;;;;N;;;;; 1A69;TAI THAM VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 1A6A;TAI THAM VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 1A6B;TAI THAM VOWEL SIGN O;Mn;0;NSM;;;;;N;;;;; 1A6C;TAI THAM VOWEL SIGN OA BELOW;Mn;0;NSM;;;;;N;;;;; 1A6D;TAI THAM VOWEL SIGN OY;Mc;0;L;;;;;N;;;;; 1A6E;TAI THAM VOWEL SIGN E;Mc;0;L;;;;;N;;;;; 1A6F;TAI THAM VOWEL SIGN AE;Mc;0;L;;;;;N;;;;; 1A70;TAI THAM VOWEL SIGN OO;Mc;0;L;;;;;N;;;;; 1A71;TAI THAM VOWEL SIGN AI;Mc;0;L;;;;;N;;;;; 1A72;TAI THAM VOWEL SIGN THAM AI;Mc;0;L;;;;;N;;;;; 1A73;TAI THAM VOWEL SIGN OA ABOVE;Mn;0;NSM;;;;;N;;;;; 1A74;TAI THAM SIGN MAI KANG;Mn;0;NSM;;;;;N;;;;; 1A75;TAI THAM SIGN TONE-1;Mn;230;NSM;;;;;N;;;;; 1A76;TAI THAM SIGN TONE-2;Mn;230;NSM;;;;;N;;;;; 1A77;TAI THAM SIGN KHUEN TONE-3;Mn;230;NSM;;;;;N;;;;; 1A78;TAI THAM SIGN KHUEN TONE-4;Mn;230;NSM;;;;;N;;;;; 1A79;TAI THAM SIGN KHUEN TONE-5;Mn;230;NSM;;;;;N;;;;; 1A7A;TAI THAM SIGN RA HAAM;Mn;230;NSM;;;;;N;;;;; 1A7B;TAI THAM SIGN MAI SAM;Mn;230;NSM;;;;;N;;;;; 1A7C;TAI THAM SIGN KHUEN-LUE KARAN;Mn;230;NSM;;;;;N;;;;; 1A7F;TAI THAM COMBINING CRYPTOGRAMMIC DOT;Mn;220;NSM;;;;;N;;;;; 1A80;TAI THAM HORA DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 1A81;TAI THAM HORA DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 1A82;TAI THAM HORA DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 1A83;TAI THAM HORA DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 1A84;TAI THAM HORA DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 1A85;TAI THAM HORA DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 1A86;TAI THAM HORA DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 1A87;TAI THAM HORA DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 1A88;TAI THAM HORA DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 1A89;TAI THAM HORA DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 1A90;TAI THAM THAM DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 1A91;TAI THAM THAM DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 1A92;TAI THAM THAM DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 1A93;TAI THAM THAM DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 1A94;TAI THAM THAM DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 1A95;TAI THAM THAM DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 1A96;TAI THAM THAM DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 1A97;TAI THAM THAM DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 1A98;TAI THAM THAM DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 1A99;TAI THAM THAM DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 1AA0;TAI THAM SIGN WIANG;Po;0;L;;;;;N;;;;; 1AA1;TAI THAM SIGN WIANGWAAK;Po;0;L;;;;;N;;;;; 1AA2;TAI THAM SIGN SAWAN;Po;0;L;;;;;N;;;;; 1AA3;TAI THAM SIGN KEOW;Po;0;L;;;;;N;;;;; 1AA4;TAI THAM SIGN HOY;Po;0;L;;;;;N;;;;; 1AA5;TAI THAM SIGN DOKMAI;Po;0;L;;;;;N;;;;; 1AA6;TAI THAM SIGN REVERSED ROTATED RANA;Po;0;L;;;;;N;;;;; 1AA7;TAI THAM SIGN MAI YAMOK;Lm;0;L;;;;;N;;;;; 1AA8;TAI THAM SIGN KAAN;Po;0;L;;;;;N;;;;; 1AA9;TAI THAM SIGN KAANKUU;Po;0;L;;;;;N;;;;; 1AAA;TAI THAM SIGN SATKAAN;Po;0;L;;;;;N;;;;; 1AAB;TAI THAM SIGN SATKAANKUU;Po;0;L;;;;;N;;;;; 1AAC;TAI THAM SIGN HANG;Po;0;L;;;;;N;;;;; 1AAD;TAI THAM SIGN CAANG;Po;0;L;;;;;N;;;;; 1AB0;COMBINING DOUBLED CIRCUMFLEX ACCENT;Mn;230;NSM;;;;;N;;;;; 1AB1;COMBINING DIAERESIS-RING;Mn;230;NSM;;;;;N;;;;; 1AB2;COMBINING INFINITY;Mn;230;NSM;;;;;N;;;;; 1AB3;COMBINING DOWNWARDS ARROW;Mn;230;NSM;;;;;N;;;;; 1AB4;COMBINING TRIPLE DOT;Mn;230;NSM;;;;;N;;;;; 1AB5;COMBINING X-X BELOW;Mn;220;NSM;;;;;N;;;;; 1AB6;COMBINING WIGGLY LINE BELOW;Mn;220;NSM;;;;;N;;;;; 1AB7;COMBINING OPEN MARK BELOW;Mn;220;NSM;;;;;N;;;;; 1AB8;COMBINING DOUBLE OPEN MARK BELOW;Mn;220;NSM;;;;;N;;;;; 1AB9;COMBINING LIGHT CENTRALIZATION STROKE BELOW;Mn;220;NSM;;;;;N;;;;; 1ABA;COMBINING STRONG CENTRALIZATION STROKE BELOW;Mn;220;NSM;;;;;N;;;;; 1ABB;COMBINING PARENTHESES ABOVE;Mn;230;NSM;;;;;N;;;;; 1ABC;COMBINING DOUBLE PARENTHESES ABOVE;Mn;230;NSM;;;;;N;;;;; 1ABD;COMBINING PARENTHESES BELOW;Mn;220;NSM;;;;;N;;;;; 1ABE;COMBINING PARENTHESES OVERLAY;Me;0;NSM;;;;;N;;;;; 1B00;BALINESE SIGN ULU RICEM;Mn;0;NSM;;;;;N;;;;; 1B01;BALINESE SIGN ULU CANDRA;Mn;0;NSM;;;;;N;;;;; 1B02;BALINESE SIGN CECEK;Mn;0;NSM;;;;;N;;;;; 1B03;BALINESE SIGN SURANG;Mn;0;NSM;;;;;N;;;;; 1B04;BALINESE SIGN BISAH;Mc;0;L;;;;;N;;;;; 1B05;BALINESE LETTER AKARA;Lo;0;L;;;;;N;;;;; 1B06;BALINESE LETTER AKARA TEDUNG;Lo;0;L;1B05 1B35;;;;N;;;;; 1B07;BALINESE LETTER IKARA;Lo;0;L;;;;;N;;;;; 1B08;BALINESE LETTER IKARA TEDUNG;Lo;0;L;1B07 1B35;;;;N;;;;; 1B09;BALINESE LETTER UKARA;Lo;0;L;;;;;N;;;;; 1B0A;BALINESE LETTER UKARA TEDUNG;Lo;0;L;1B09 1B35;;;;N;;;;; 1B0B;BALINESE LETTER RA REPA;Lo;0;L;;;;;N;;;;; 1B0C;BALINESE LETTER RA REPA TEDUNG;Lo;0;L;1B0B 1B35;;;;N;;;;; 1B0D;BALINESE LETTER LA LENGA;Lo;0;L;;;;;N;;;;; 1B0E;BALINESE LETTER LA LENGA TEDUNG;Lo;0;L;1B0D 1B35;;;;N;;;;; 1B0F;BALINESE LETTER EKARA;Lo;0;L;;;;;N;;;;; 1B10;BALINESE LETTER AIKARA;Lo;0;L;;;;;N;;;;; 1B11;BALINESE LETTER OKARA;Lo;0;L;;;;;N;;;;; 1B12;BALINESE LETTER OKARA TEDUNG;Lo;0;L;1B11 1B35;;;;N;;;;; 1B13;BALINESE LETTER KA;Lo;0;L;;;;;N;;;;; 1B14;BALINESE LETTER KA MAHAPRANA;Lo;0;L;;;;;N;;;;; 1B15;BALINESE LETTER GA;Lo;0;L;;;;;N;;;;; 1B16;BALINESE LETTER GA GORA;Lo;0;L;;;;;N;;;;; 1B17;BALINESE LETTER NGA;Lo;0;L;;;;;N;;;;; 1B18;BALINESE LETTER CA;Lo;0;L;;;;;N;;;;; 1B19;BALINESE LETTER CA LACA;Lo;0;L;;;;;N;;;;; 1B1A;BALINESE LETTER JA;Lo;0;L;;;;;N;;;;; 1B1B;BALINESE LETTER JA JERA;Lo;0;L;;;;;N;;;;; 1B1C;BALINESE LETTER NYA;Lo;0;L;;;;;N;;;;; 1B1D;BALINESE LETTER TA LATIK;Lo;0;L;;;;;N;;;;; 1B1E;BALINESE LETTER TA MURDA MAHAPRANA;Lo;0;L;;;;;N;;;;; 1B1F;BALINESE LETTER DA MURDA ALPAPRANA;Lo;0;L;;;;;N;;;;; 1B20;BALINESE LETTER DA MURDA MAHAPRANA;Lo;0;L;;;;;N;;;;; 1B21;BALINESE LETTER NA RAMBAT;Lo;0;L;;;;;N;;;;; 1B22;BALINESE LETTER TA;Lo;0;L;;;;;N;;;;; 1B23;BALINESE LETTER TA TAWA;Lo;0;L;;;;;N;;;;; 1B24;BALINESE LETTER DA;Lo;0;L;;;;;N;;;;; 1B25;BALINESE LETTER DA MADU;Lo;0;L;;;;;N;;;;; 1B26;BALINESE LETTER NA;Lo;0;L;;;;;N;;;;; 1B27;BALINESE LETTER PA;Lo;0;L;;;;;N;;;;; 1B28;BALINESE LETTER PA KAPAL;Lo;0;L;;;;;N;;;;; 1B29;BALINESE LETTER BA;Lo;0;L;;;;;N;;;;; 1B2A;BALINESE LETTER BA KEMBANG;Lo;0;L;;;;;N;;;;; 1B2B;BALINESE LETTER MA;Lo;0;L;;;;;N;;;;; 1B2C;BALINESE LETTER YA;Lo;0;L;;;;;N;;;;; 1B2D;BALINESE LETTER RA;Lo;0;L;;;;;N;;;;; 1B2E;BALINESE LETTER LA;Lo;0;L;;;;;N;;;;; 1B2F;BALINESE LETTER WA;Lo;0;L;;;;;N;;;;; 1B30;BALINESE LETTER SA SAGA;Lo;0;L;;;;;N;;;;; 1B31;BALINESE LETTER SA SAPA;Lo;0;L;;;;;N;;;;; 1B32;BALINESE LETTER SA;Lo;0;L;;;;;N;;;;; 1B33;BALINESE LETTER HA;Lo;0;L;;;;;N;;;;; 1B34;BALINESE SIGN REREKAN;Mn;7;NSM;;;;;N;;;;; 1B35;BALINESE VOWEL SIGN TEDUNG;Mc;0;L;;;;;N;;;;; 1B36;BALINESE VOWEL SIGN ULU;Mn;0;NSM;;;;;N;;;;; 1B37;BALINESE VOWEL SIGN ULU SARI;Mn;0;NSM;;;;;N;;;;; 1B38;BALINESE VOWEL SIGN SUKU;Mn;0;NSM;;;;;N;;;;; 1B39;BALINESE VOWEL SIGN SUKU ILUT;Mn;0;NSM;;;;;N;;;;; 1B3A;BALINESE VOWEL SIGN RA REPA;Mn;0;NSM;;;;;N;;;;; 1B3B;BALINESE VOWEL SIGN RA REPA TEDUNG;Mc;0;L;1B3A 1B35;;;;N;;;;; 1B3C;BALINESE VOWEL SIGN LA LENGA;Mn;0;NSM;;;;;N;;;;; 1B3D;BALINESE VOWEL SIGN LA LENGA TEDUNG;Mc;0;L;1B3C 1B35;;;;N;;;;; 1B3E;BALINESE VOWEL SIGN TALING;Mc;0;L;;;;;N;;;;; 1B3F;BALINESE VOWEL SIGN TALING REPA;Mc;0;L;;;;;N;;;;; 1B40;BALINESE VOWEL SIGN TALING TEDUNG;Mc;0;L;1B3E 1B35;;;;N;;;;; 1B41;BALINESE VOWEL SIGN TALING REPA TEDUNG;Mc;0;L;1B3F 1B35;;;;N;;;;; 1B42;BALINESE VOWEL SIGN PEPET;Mn;0;NSM;;;;;N;;;;; 1B43;BALINESE VOWEL SIGN PEPET TEDUNG;Mc;0;L;1B42 1B35;;;;N;;;;; 1B44;BALINESE ADEG ADEG;Mc;9;L;;;;;N;;;;; 1B45;BALINESE LETTER KAF SASAK;Lo;0;L;;;;;N;;;;; 1B46;BALINESE LETTER KHOT SASAK;Lo;0;L;;;;;N;;;;; 1B47;BALINESE LETTER TZIR SASAK;Lo;0;L;;;;;N;;;;; 1B48;BALINESE LETTER EF SASAK;Lo;0;L;;;;;N;;;;; 1B49;BALINESE LETTER VE SASAK;Lo;0;L;;;;;N;;;;; 1B4A;BALINESE LETTER ZAL SASAK;Lo;0;L;;;;;N;;;;; 1B4B;BALINESE LETTER ASYURA SASAK;Lo;0;L;;;;;N;;;;; 1B50;BALINESE DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 1B51;BALINESE DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 1B52;BALINESE DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 1B53;BALINESE DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 1B54;BALINESE DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 1B55;BALINESE DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 1B56;BALINESE DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 1B57;BALINESE DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 1B58;BALINESE DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 1B59;BALINESE DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 1B5A;BALINESE PANTI;Po;0;L;;;;;N;;;;; 1B5B;BALINESE PAMADA;Po;0;L;;;;;N;;;;; 1B5C;BALINESE WINDU;Po;0;L;;;;;N;;;;; 1B5D;BALINESE CARIK PAMUNGKAH;Po;0;L;;;;;N;;;;; 1B5E;BALINESE CARIK SIKI;Po;0;L;;;;;N;;;;; 1B5F;BALINESE CARIK PAREREN;Po;0;L;;;;;N;;;;; 1B60;BALINESE PAMENENG;Po;0;L;;;;;N;;;;; 1B61;BALINESE MUSICAL SYMBOL DONG;So;0;L;;;;;N;;;;; 1B62;BALINESE MUSICAL SYMBOL DENG;So;0;L;;;;;N;;;;; 1B63;BALINESE MUSICAL SYMBOL DUNG;So;0;L;;;;;N;;;;; 1B64;BALINESE MUSICAL SYMBOL DANG;So;0;L;;;;;N;;;;; 1B65;BALINESE MUSICAL SYMBOL DANG SURANG;So;0;L;;;;;N;;;;; 1B66;BALINESE MUSICAL SYMBOL DING;So;0;L;;;;;N;;;;; 1B67;BALINESE MUSICAL SYMBOL DAENG;So;0;L;;;;;N;;;;; 1B68;BALINESE MUSICAL SYMBOL DEUNG;So;0;L;;;;;N;;;;; 1B69;BALINESE MUSICAL SYMBOL DAING;So;0;L;;;;;N;;;;; 1B6A;BALINESE MUSICAL SYMBOL DANG GEDE;So;0;L;;;;;N;;;;; 1B6B;BALINESE MUSICAL SYMBOL COMBINING TEGEH;Mn;230;NSM;;;;;N;;;;; 1B6C;BALINESE MUSICAL SYMBOL COMBINING ENDEP;Mn;220;NSM;;;;;N;;;;; 1B6D;BALINESE MUSICAL SYMBOL COMBINING KEMPUL;Mn;230;NSM;;;;;N;;;;; 1B6E;BALINESE MUSICAL SYMBOL COMBINING KEMPLI;Mn;230;NSM;;;;;N;;;;; 1B6F;BALINESE MUSICAL SYMBOL COMBINING JEGOGAN;Mn;230;NSM;;;;;N;;;;; 1B70;BALINESE MUSICAL SYMBOL COMBINING KEMPUL WITH JEGOGAN;Mn;230;NSM;;;;;N;;;;; 1B71;BALINESE MUSICAL SYMBOL COMBINING KEMPLI WITH JEGOGAN;Mn;230;NSM;;;;;N;;;;; 1B72;BALINESE MUSICAL SYMBOL COMBINING BENDE;Mn;230;NSM;;;;;N;;;;; 1B73;BALINESE MUSICAL SYMBOL COMBINING GONG;Mn;230;NSM;;;;;N;;;;; 1B74;BALINESE MUSICAL SYMBOL RIGHT-HAND OPEN DUG;So;0;L;;;;;N;;;;; 1B75;BALINESE MUSICAL SYMBOL RIGHT-HAND OPEN DAG;So;0;L;;;;;N;;;;; 1B76;BALINESE MUSICAL SYMBOL RIGHT-HAND CLOSED TUK;So;0;L;;;;;N;;;;; 1B77;BALINESE MUSICAL SYMBOL RIGHT-HAND CLOSED TAK;So;0;L;;;;;N;;;;; 1B78;BALINESE MUSICAL SYMBOL LEFT-HAND OPEN PANG;So;0;L;;;;;N;;;;; 1B79;BALINESE MUSICAL SYMBOL LEFT-HAND OPEN PUNG;So;0;L;;;;;N;;;;; 1B7A;BALINESE MUSICAL SYMBOL LEFT-HAND CLOSED PLAK;So;0;L;;;;;N;;;;; 1B7B;BALINESE MUSICAL SYMBOL LEFT-HAND CLOSED PLUK;So;0;L;;;;;N;;;;; 1B7C;BALINESE MUSICAL SYMBOL LEFT-HAND OPEN PING;So;0;L;;;;;N;;;;; 1B80;SUNDANESE SIGN PANYECEK;Mn;0;NSM;;;;;N;;;;; 1B81;SUNDANESE SIGN PANGLAYAR;Mn;0;NSM;;;;;N;;;;; 1B82;SUNDANESE SIGN PANGWISAD;Mc;0;L;;;;;N;;;;; 1B83;SUNDANESE LETTER A;Lo;0;L;;;;;N;;;;; 1B84;SUNDANESE LETTER I;Lo;0;L;;;;;N;;;;; 1B85;SUNDANESE LETTER U;Lo;0;L;;;;;N;;;;; 1B86;SUNDANESE LETTER AE;Lo;0;L;;;;;N;;;;; 1B87;SUNDANESE LETTER O;Lo;0;L;;;;;N;;;;; 1B88;SUNDANESE LETTER E;Lo;0;L;;;;;N;;;;; 1B89;SUNDANESE LETTER EU;Lo;0;L;;;;;N;;;;; 1B8A;SUNDANESE LETTER KA;Lo;0;L;;;;;N;;;;; 1B8B;SUNDANESE LETTER QA;Lo;0;L;;;;;N;;;;; 1B8C;SUNDANESE LETTER GA;Lo;0;L;;;;;N;;;;; 1B8D;SUNDANESE LETTER NGA;Lo;0;L;;;;;N;;;;; 1B8E;SUNDANESE LETTER CA;Lo;0;L;;;;;N;;;;; 1B8F;SUNDANESE LETTER JA;Lo;0;L;;;;;N;;;;; 1B90;SUNDANESE LETTER ZA;Lo;0;L;;;;;N;;;;; 1B91;SUNDANESE LETTER NYA;Lo;0;L;;;;;N;;;;; 1B92;SUNDANESE LETTER TA;Lo;0;L;;;;;N;;;;; 1B93;SUNDANESE LETTER DA;Lo;0;L;;;;;N;;;;; 1B94;SUNDANESE LETTER NA;Lo;0;L;;;;;N;;;;; 1B95;SUNDANESE LETTER PA;Lo;0;L;;;;;N;;;;; 1B96;SUNDANESE LETTER FA;Lo;0;L;;;;;N;;;;; 1B97;SUNDANESE LETTER VA;Lo;0;L;;;;;N;;;;; 1B98;SUNDANESE LETTER BA;Lo;0;L;;;;;N;;;;; 1B99;SUNDANESE LETTER MA;Lo;0;L;;;;;N;;;;; 1B9A;SUNDANESE LETTER YA;Lo;0;L;;;;;N;;;;; 1B9B;SUNDANESE LETTER RA;Lo;0;L;;;;;N;;;;; 1B9C;SUNDANESE LETTER LA;Lo;0;L;;;;;N;;;;; 1B9D;SUNDANESE LETTER WA;Lo;0;L;;;;;N;;;;; 1B9E;SUNDANESE LETTER SA;Lo;0;L;;;;;N;;;;; 1B9F;SUNDANESE LETTER XA;Lo;0;L;;;;;N;;;;; 1BA0;SUNDANESE LETTER HA;Lo;0;L;;;;;N;;;;; 1BA1;SUNDANESE CONSONANT SIGN PAMINGKAL;Mc;0;L;;;;;N;;;;; 1BA2;SUNDANESE CONSONANT SIGN PANYAKRA;Mn;0;NSM;;;;;N;;;;; 1BA3;SUNDANESE CONSONANT SIGN PANYIKU;Mn;0;NSM;;;;;N;;;;; 1BA4;SUNDANESE VOWEL SIGN PANGHULU;Mn;0;NSM;;;;;N;;;;; 1BA5;SUNDANESE VOWEL SIGN PANYUKU;Mn;0;NSM;;;;;N;;;;; 1BA6;SUNDANESE VOWEL SIGN PANAELAENG;Mc;0;L;;;;;N;;;;; 1BA7;SUNDANESE VOWEL SIGN PANOLONG;Mc;0;L;;;;;N;;;;; 1BA8;SUNDANESE VOWEL SIGN PAMEPET;Mn;0;NSM;;;;;N;;;;; 1BA9;SUNDANESE VOWEL SIGN PANEULEUNG;Mn;0;NSM;;;;;N;;;;; 1BAA;SUNDANESE SIGN PAMAAEH;Mc;9;L;;;;;N;;;;; 1BAB;SUNDANESE SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 1BAC;SUNDANESE CONSONANT SIGN PASANGAN MA;Mn;0;NSM;;;;;N;;;;; 1BAD;SUNDANESE CONSONANT SIGN PASANGAN WA;Mn;0;NSM;;;;;N;;;;; 1BAE;SUNDANESE LETTER KHA;Lo;0;L;;;;;N;;;;; 1BAF;SUNDANESE LETTER SYA;Lo;0;L;;;;;N;;;;; 1BB0;SUNDANESE DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 1BB1;SUNDANESE DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 1BB2;SUNDANESE DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 1BB3;SUNDANESE DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 1BB4;SUNDANESE DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 1BB5;SUNDANESE DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 1BB6;SUNDANESE DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 1BB7;SUNDANESE DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 1BB8;SUNDANESE DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 1BB9;SUNDANESE DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 1BBA;SUNDANESE AVAGRAHA;Lo;0;L;;;;;N;;;;; 1BBB;SUNDANESE LETTER REU;Lo;0;L;;;;;N;;;;; 1BBC;SUNDANESE LETTER LEU;Lo;0;L;;;;;N;;;;; 1BBD;SUNDANESE LETTER BHA;Lo;0;L;;;;;N;;;;; 1BBE;SUNDANESE LETTER FINAL K;Lo;0;L;;;;;N;;;;; 1BBF;SUNDANESE LETTER FINAL M;Lo;0;L;;;;;N;;;;; 1BC0;BATAK LETTER A;Lo;0;L;;;;;N;;;;; 1BC1;BATAK LETTER SIMALUNGUN A;Lo;0;L;;;;;N;;;;; 1BC2;BATAK LETTER HA;Lo;0;L;;;;;N;;;;; 1BC3;BATAK LETTER SIMALUNGUN HA;Lo;0;L;;;;;N;;;;; 1BC4;BATAK LETTER MANDAILING HA;Lo;0;L;;;;;N;;;;; 1BC5;BATAK LETTER BA;Lo;0;L;;;;;N;;;;; 1BC6;BATAK LETTER KARO BA;Lo;0;L;;;;;N;;;;; 1BC7;BATAK LETTER PA;Lo;0;L;;;;;N;;;;; 1BC8;BATAK LETTER SIMALUNGUN PA;Lo;0;L;;;;;N;;;;; 1BC9;BATAK LETTER NA;Lo;0;L;;;;;N;;;;; 1BCA;BATAK LETTER MANDAILING NA;Lo;0;L;;;;;N;;;;; 1BCB;BATAK LETTER WA;Lo;0;L;;;;;N;;;;; 1BCC;BATAK LETTER SIMALUNGUN WA;Lo;0;L;;;;;N;;;;; 1BCD;BATAK LETTER PAKPAK WA;Lo;0;L;;;;;N;;;;; 1BCE;BATAK LETTER GA;Lo;0;L;;;;;N;;;;; 1BCF;BATAK LETTER SIMALUNGUN GA;Lo;0;L;;;;;N;;;;; 1BD0;BATAK LETTER JA;Lo;0;L;;;;;N;;;;; 1BD1;BATAK LETTER DA;Lo;0;L;;;;;N;;;;; 1BD2;BATAK LETTER RA;Lo;0;L;;;;;N;;;;; 1BD3;BATAK LETTER SIMALUNGUN RA;Lo;0;L;;;;;N;;;;; 1BD4;BATAK LETTER MA;Lo;0;L;;;;;N;;;;; 1BD5;BATAK LETTER SIMALUNGUN MA;Lo;0;L;;;;;N;;;;; 1BD6;BATAK LETTER SOUTHERN TA;Lo;0;L;;;;;N;;;;; 1BD7;BATAK LETTER NORTHERN TA;Lo;0;L;;;;;N;;;;; 1BD8;BATAK LETTER SA;Lo;0;L;;;;;N;;;;; 1BD9;BATAK LETTER SIMALUNGUN SA;Lo;0;L;;;;;N;;;;; 1BDA;BATAK LETTER MANDAILING SA;Lo;0;L;;;;;N;;;;; 1BDB;BATAK LETTER YA;Lo;0;L;;;;;N;;;;; 1BDC;BATAK LETTER SIMALUNGUN YA;Lo;0;L;;;;;N;;;;; 1BDD;BATAK LETTER NGA;Lo;0;L;;;;;N;;;;; 1BDE;BATAK LETTER LA;Lo;0;L;;;;;N;;;;; 1BDF;BATAK LETTER SIMALUNGUN LA;Lo;0;L;;;;;N;;;;; 1BE0;BATAK LETTER NYA;Lo;0;L;;;;;N;;;;; 1BE1;BATAK LETTER CA;Lo;0;L;;;;;N;;;;; 1BE2;BATAK LETTER NDA;Lo;0;L;;;;;N;;;;; 1BE3;BATAK LETTER MBA;Lo;0;L;;;;;N;;;;; 1BE4;BATAK LETTER I;Lo;0;L;;;;;N;;;;; 1BE5;BATAK LETTER U;Lo;0;L;;;;;N;;;;; 1BE6;BATAK SIGN TOMPI;Mn;7;NSM;;;;;N;;;;; 1BE7;BATAK VOWEL SIGN E;Mc;0;L;;;;;N;;;;; 1BE8;BATAK VOWEL SIGN PAKPAK E;Mn;0;NSM;;;;;N;;;;; 1BE9;BATAK VOWEL SIGN EE;Mn;0;NSM;;;;;N;;;;; 1BEA;BATAK VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 1BEB;BATAK VOWEL SIGN KARO I;Mc;0;L;;;;;N;;;;; 1BEC;BATAK VOWEL SIGN O;Mc;0;L;;;;;N;;;;; 1BED;BATAK VOWEL SIGN KARO O;Mn;0;NSM;;;;;N;;;;; 1BEE;BATAK VOWEL SIGN U;Mc;0;L;;;;;N;;;;; 1BEF;BATAK VOWEL SIGN U FOR SIMALUNGUN SA;Mn;0;NSM;;;;;N;;;;; 1BF0;BATAK CONSONANT SIGN NG;Mn;0;NSM;;;;;N;;;;; 1BF1;BATAK CONSONANT SIGN H;Mn;0;NSM;;;;;N;;;;; 1BF2;BATAK PANGOLAT;Mc;9;L;;;;;N;;;;; 1BF3;BATAK PANONGONAN;Mc;9;L;;;;;N;;;;; 1BFC;BATAK SYMBOL BINDU NA METEK;Po;0;L;;;;;N;;;;; 1BFD;BATAK SYMBOL BINDU PINARBORAS;Po;0;L;;;;;N;;;;; 1BFE;BATAK SYMBOL BINDU JUDUL;Po;0;L;;;;;N;;;;; 1BFF;BATAK SYMBOL BINDU PANGOLAT;Po;0;L;;;;;N;;;;; 1C00;LEPCHA LETTER KA;Lo;0;L;;;;;N;;;;; 1C01;LEPCHA LETTER KLA;Lo;0;L;;;;;N;;;;; 1C02;LEPCHA LETTER KHA;Lo;0;L;;;;;N;;;;; 1C03;LEPCHA LETTER GA;Lo;0;L;;;;;N;;;;; 1C04;LEPCHA LETTER GLA;Lo;0;L;;;;;N;;;;; 1C05;LEPCHA LETTER NGA;Lo;0;L;;;;;N;;;;; 1C06;LEPCHA LETTER CA;Lo;0;L;;;;;N;;;;; 1C07;LEPCHA LETTER CHA;Lo;0;L;;;;;N;;;;; 1C08;LEPCHA LETTER JA;Lo;0;L;;;;;N;;;;; 1C09;LEPCHA LETTER NYA;Lo;0;L;;;;;N;;;;; 1C0A;LEPCHA LETTER TA;Lo;0;L;;;;;N;;;;; 1C0B;LEPCHA LETTER THA;Lo;0;L;;;;;N;;;;; 1C0C;LEPCHA LETTER DA;Lo;0;L;;;;;N;;;;; 1C0D;LEPCHA LETTER NA;Lo;0;L;;;;;N;;;;; 1C0E;LEPCHA LETTER PA;Lo;0;L;;;;;N;;;;; 1C0F;LEPCHA LETTER PLA;Lo;0;L;;;;;N;;;;; 1C10;LEPCHA LETTER PHA;Lo;0;L;;;;;N;;;;; 1C11;LEPCHA LETTER FA;Lo;0;L;;;;;N;;;;; 1C12;LEPCHA LETTER FLA;Lo;0;L;;;;;N;;;;; 1C13;LEPCHA LETTER BA;Lo;0;L;;;;;N;;;;; 1C14;LEPCHA LETTER BLA;Lo;0;L;;;;;N;;;;; 1C15;LEPCHA LETTER MA;Lo;0;L;;;;;N;;;;; 1C16;LEPCHA LETTER MLA;Lo;0;L;;;;;N;;;;; 1C17;LEPCHA LETTER TSA;Lo;0;L;;;;;N;;;;; 1C18;LEPCHA LETTER TSHA;Lo;0;L;;;;;N;;;;; 1C19;LEPCHA LETTER DZA;Lo;0;L;;;;;N;;;;; 1C1A;LEPCHA LETTER YA;Lo;0;L;;;;;N;;;;; 1C1B;LEPCHA LETTER RA;Lo;0;L;;;;;N;;;;; 1C1C;LEPCHA LETTER LA;Lo;0;L;;;;;N;;;;; 1C1D;LEPCHA LETTER HA;Lo;0;L;;;;;N;;;;; 1C1E;LEPCHA LETTER HLA;Lo;0;L;;;;;N;;;;; 1C1F;LEPCHA LETTER VA;Lo;0;L;;;;;N;;;;; 1C20;LEPCHA LETTER SA;Lo;0;L;;;;;N;;;;; 1C21;LEPCHA LETTER SHA;Lo;0;L;;;;;N;;;;; 1C22;LEPCHA LETTER WA;Lo;0;L;;;;;N;;;;; 1C23;LEPCHA LETTER A;Lo;0;L;;;;;N;;;;; 1C24;LEPCHA SUBJOINED LETTER YA;Mc;0;L;;;;;N;;;;; 1C25;LEPCHA SUBJOINED LETTER RA;Mc;0;L;;;;;N;;;;; 1C26;LEPCHA VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 1C27;LEPCHA VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 1C28;LEPCHA VOWEL SIGN O;Mc;0;L;;;;;N;;;;; 1C29;LEPCHA VOWEL SIGN OO;Mc;0;L;;;;;N;;;;; 1C2A;LEPCHA VOWEL SIGN U;Mc;0;L;;;;;N;;;;; 1C2B;LEPCHA VOWEL SIGN UU;Mc;0;L;;;;;N;;;;; 1C2C;LEPCHA VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;; 1C2D;LEPCHA CONSONANT SIGN K;Mn;0;NSM;;;;;N;;;;; 1C2E;LEPCHA CONSONANT SIGN M;Mn;0;NSM;;;;;N;;;;; 1C2F;LEPCHA CONSONANT SIGN L;Mn;0;NSM;;;;;N;;;;; 1C30;LEPCHA CONSONANT SIGN N;Mn;0;NSM;;;;;N;;;;; 1C31;LEPCHA CONSONANT SIGN P;Mn;0;NSM;;;;;N;;;;; 1C32;LEPCHA CONSONANT SIGN R;Mn;0;NSM;;;;;N;;;;; 1C33;LEPCHA CONSONANT SIGN T;Mn;0;NSM;;;;;N;;;;; 1C34;LEPCHA CONSONANT SIGN NYIN-DO;Mc;0;L;;;;;N;;;;; 1C35;LEPCHA CONSONANT SIGN KANG;Mc;0;L;;;;;N;;;;; 1C36;LEPCHA SIGN RAN;Mn;0;NSM;;;;;N;;;;; 1C37;LEPCHA SIGN NUKTA;Mn;7;NSM;;;;;N;;;;; 1C3B;LEPCHA PUNCTUATION TA-ROL;Po;0;L;;;;;N;;;;; 1C3C;LEPCHA PUNCTUATION NYET THYOOM TA-ROL;Po;0;L;;;;;N;;;;; 1C3D;LEPCHA PUNCTUATION CER-WA;Po;0;L;;;;;N;;;;; 1C3E;LEPCHA PUNCTUATION TSHOOK CER-WA;Po;0;L;;;;;N;;;;; 1C3F;LEPCHA PUNCTUATION TSHOOK;Po;0;L;;;;;N;;;;; 1C40;LEPCHA DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 1C41;LEPCHA DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 1C42;LEPCHA DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 1C43;LEPCHA DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 1C44;LEPCHA DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 1C45;LEPCHA DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 1C46;LEPCHA DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 1C47;LEPCHA DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 1C48;LEPCHA DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 1C49;LEPCHA DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 1C4D;LEPCHA LETTER TTA;Lo;0;L;;;;;N;;;;; 1C4E;LEPCHA LETTER TTHA;Lo;0;L;;;;;N;;;;; 1C4F;LEPCHA LETTER DDA;Lo;0;L;;;;;N;;;;; 1C50;OL CHIKI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 1C51;OL CHIKI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 1C52;OL CHIKI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 1C53;OL CHIKI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 1C54;OL CHIKI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 1C55;OL CHIKI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 1C56;OL CHIKI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 1C57;OL CHIKI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 1C58;OL CHIKI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 1C59;OL CHIKI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 1C5A;OL CHIKI LETTER LA;Lo;0;L;;;;;N;;;;; 1C5B;OL CHIKI LETTER AT;Lo;0;L;;;;;N;;;;; 1C5C;OL CHIKI LETTER AG;Lo;0;L;;;;;N;;;;; 1C5D;OL CHIKI LETTER ANG;Lo;0;L;;;;;N;;;;; 1C5E;OL CHIKI LETTER AL;Lo;0;L;;;;;N;;;;; 1C5F;OL CHIKI LETTER LAA;Lo;0;L;;;;;N;;;;; 1C60;OL CHIKI LETTER AAK;Lo;0;L;;;;;N;;;;; 1C61;OL CHIKI LETTER AAJ;Lo;0;L;;;;;N;;;;; 1C62;OL CHIKI LETTER AAM;Lo;0;L;;;;;N;;;;; 1C63;OL CHIKI LETTER AAW;Lo;0;L;;;;;N;;;;; 1C64;OL CHIKI LETTER LI;Lo;0;L;;;;;N;;;;; 1C65;OL CHIKI LETTER IS;Lo;0;L;;;;;N;;;;; 1C66;OL CHIKI LETTER IH;Lo;0;L;;;;;N;;;;; 1C67;OL CHIKI LETTER INY;Lo;0;L;;;;;N;;;;; 1C68;OL CHIKI LETTER IR;Lo;0;L;;;;;N;;;;; 1C69;OL CHIKI LETTER LU;Lo;0;L;;;;;N;;;;; 1C6A;OL CHIKI LETTER UC;Lo;0;L;;;;;N;;;;; 1C6B;OL CHIKI LETTER UD;Lo;0;L;;;;;N;;;;; 1C6C;OL CHIKI LETTER UNN;Lo;0;L;;;;;N;;;;; 1C6D;OL CHIKI LETTER UY;Lo;0;L;;;;;N;;;;; 1C6E;OL CHIKI LETTER LE;Lo;0;L;;;;;N;;;;; 1C6F;OL CHIKI LETTER EP;Lo;0;L;;;;;N;;;;; 1C70;OL CHIKI LETTER EDD;Lo;0;L;;;;;N;;;;; 1C71;OL CHIKI LETTER EN;Lo;0;L;;;;;N;;;;; 1C72;OL CHIKI LETTER ERR;Lo;0;L;;;;;N;;;;; 1C73;OL CHIKI LETTER LO;Lo;0;L;;;;;N;;;;; 1C74;OL CHIKI LETTER OTT;Lo;0;L;;;;;N;;;;; 1C75;OL CHIKI LETTER OB;Lo;0;L;;;;;N;;;;; 1C76;OL CHIKI LETTER OV;Lo;0;L;;;;;N;;;;; 1C77;OL CHIKI LETTER OH;Lo;0;L;;;;;N;;;;; 1C78;OL CHIKI MU TTUDDAG;Lm;0;L;;;;;N;;;;; 1C79;OL CHIKI GAAHLAA TTUDDAAG;Lm;0;L;;;;;N;;;;; 1C7A;OL CHIKI MU-GAAHLAA TTUDDAAG;Lm;0;L;;;;;N;;;;; 1C7B;OL CHIKI RELAA;Lm;0;L;;;;;N;;;;; 1C7C;OL CHIKI PHAARKAA;Lm;0;L;;;;;N;;;;; 1C7D;OL CHIKI AHAD;Lm;0;L;;;;;N;;;;; 1C7E;OL CHIKI PUNCTUATION MUCAAD;Po;0;L;;;;;N;;;;; 1C7F;OL CHIKI PUNCTUATION DOUBLE MUCAAD;Po;0;L;;;;;N;;;;; 1C80;CYRILLIC SMALL LETTER ROUNDED VE;Ll;0;L;;;;;N;;;0412;;0412 1C81;CYRILLIC SMALL LETTER LONG-LEGGED DE;Ll;0;L;;;;;N;;;0414;;0414 1C82;CYRILLIC SMALL LETTER NARROW O;Ll;0;L;;;;;N;;;041E;;041E 1C83;CYRILLIC SMALL LETTER WIDE ES;Ll;0;L;;;;;N;;;0421;;0421 1C84;CYRILLIC SMALL LETTER TALL TE;Ll;0;L;;;;;N;;;0422;;0422 1C85;CYRILLIC SMALL LETTER THREE-LEGGED TE;Ll;0;L;;;;;N;;;0422;;0422 1C86;CYRILLIC SMALL LETTER TALL HARD SIGN;Ll;0;L;;;;;N;;;042A;;042A 1C87;CYRILLIC SMALL LETTER TALL YAT;Ll;0;L;;;;;N;;;0462;;0462 1C88;CYRILLIC SMALL LETTER UNBLENDED UK;Ll;0;L;;;;;N;;;A64A;;A64A 1CC0;SUNDANESE PUNCTUATION BINDU SURYA;Po;0;L;;;;;N;;;;; 1CC1;SUNDANESE PUNCTUATION BINDU PANGLONG;Po;0;L;;;;;N;;;;; 1CC2;SUNDANESE PUNCTUATION BINDU PURNAMA;Po;0;L;;;;;N;;;;; 1CC3;SUNDANESE PUNCTUATION BINDU CAKRA;Po;0;L;;;;;N;;;;; 1CC4;SUNDANESE PUNCTUATION BINDU LEU SATANGA;Po;0;L;;;;;N;;;;; 1CC5;SUNDANESE PUNCTUATION BINDU KA SATANGA;Po;0;L;;;;;N;;;;; 1CC6;SUNDANESE PUNCTUATION BINDU DA SATANGA;Po;0;L;;;;;N;;;;; 1CC7;SUNDANESE PUNCTUATION BINDU BA SATANGA;Po;0;L;;;;;N;;;;; 1CD0;VEDIC TONE KARSHANA;Mn;230;NSM;;;;;N;;;;; 1CD1;VEDIC TONE SHARA;Mn;230;NSM;;;;;N;;;;; 1CD2;VEDIC TONE PRENKHA;Mn;230;NSM;;;;;N;;;;; 1CD3;VEDIC SIGN NIHSHVASA;Po;0;L;;;;;N;;;;; 1CD4;VEDIC SIGN YAJURVEDIC MIDLINE SVARITA;Mn;1;NSM;;;;;N;;;;; 1CD5;VEDIC TONE YAJURVEDIC AGGRAVATED INDEPENDENT SVARITA;Mn;220;NSM;;;;;N;;;;; 1CD6;VEDIC TONE YAJURVEDIC INDEPENDENT SVARITA;Mn;220;NSM;;;;;N;;;;; 1CD7;VEDIC TONE YAJURVEDIC KATHAKA INDEPENDENT SVARITA;Mn;220;NSM;;;;;N;;;;; 1CD8;VEDIC TONE CANDRA BELOW;Mn;220;NSM;;;;;N;;;;; 1CD9;VEDIC TONE YAJURVEDIC KATHAKA INDEPENDENT SVARITA SCHROEDER;Mn;220;NSM;;;;;N;;;;; 1CDA;VEDIC TONE DOUBLE SVARITA;Mn;230;NSM;;;;;N;;;;; 1CDB;VEDIC TONE TRIPLE SVARITA;Mn;230;NSM;;;;;N;;;;; 1CDC;VEDIC TONE KATHAKA ANUDATTA;Mn;220;NSM;;;;;N;;;;; 1CDD;VEDIC TONE DOT BELOW;Mn;220;NSM;;;;;N;;;;; 1CDE;VEDIC TONE TWO DOTS BELOW;Mn;220;NSM;;;;;N;;;;; 1CDF;VEDIC TONE THREE DOTS BELOW;Mn;220;NSM;;;;;N;;;;; 1CE0;VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA;Mn;230;NSM;;;;;N;;;;; 1CE1;VEDIC TONE ATHARVAVEDIC INDEPENDENT SVARITA;Mc;0;L;;;;;N;;;;; 1CE2;VEDIC SIGN VISARGA SVARITA;Mn;1;NSM;;;;;N;;;;; 1CE3;VEDIC SIGN VISARGA UDATTA;Mn;1;NSM;;;;;N;;;;; 1CE4;VEDIC SIGN REVERSED VISARGA UDATTA;Mn;1;NSM;;;;;N;;;;; 1CE5;VEDIC SIGN VISARGA ANUDATTA;Mn;1;NSM;;;;;N;;;;; 1CE6;VEDIC SIGN REVERSED VISARGA ANUDATTA;Mn;1;NSM;;;;;N;;;;; 1CE7;VEDIC SIGN VISARGA UDATTA WITH TAIL;Mn;1;NSM;;;;;N;;;;; 1CE8;VEDIC SIGN VISARGA ANUDATTA WITH TAIL;Mn;1;NSM;;;;;N;;;;; 1CE9;VEDIC SIGN ANUSVARA ANTARGOMUKHA;Lo;0;L;;;;;N;;;;; 1CEA;VEDIC SIGN ANUSVARA BAHIRGOMUKHA;Lo;0;L;;;;;N;;;;; 1CEB;VEDIC SIGN ANUSVARA VAMAGOMUKHA;Lo;0;L;;;;;N;;;;; 1CEC;VEDIC SIGN ANUSVARA VAMAGOMUKHA WITH TAIL;Lo;0;L;;;;;N;;;;; 1CED;VEDIC SIGN TIRYAK;Mn;220;NSM;;;;;N;;;;; 1CEE;VEDIC SIGN HEXIFORM LONG ANUSVARA;Lo;0;L;;;;;N;;;;; 1CEF;VEDIC SIGN LONG ANUSVARA;Lo;0;L;;;;;N;;;;; 1CF0;VEDIC SIGN RTHANG LONG ANUSVARA;Lo;0;L;;;;;N;;;;; 1CF1;VEDIC SIGN ANUSVARA UBHAYATO MUKHA;Lo;0;L;;;;;N;;;;; 1CF2;VEDIC SIGN ARDHAVISARGA;Mc;0;L;;;;;N;;;;; 1CF3;VEDIC SIGN ROTATED ARDHAVISARGA;Mc;0;L;;;;;N;;;;; 1CF4;VEDIC TONE CANDRA ABOVE;Mn;230;NSM;;;;;N;;;;; 1CF5;VEDIC SIGN JIHVAMULIYA;Lo;0;L;;;;;N;;;;; 1CF6;VEDIC SIGN UPADHMANIYA;Lo;0;L;;;;;N;;;;; 1CF8;VEDIC TONE RING ABOVE;Mn;230;NSM;;;;;N;;;;; 1CF9;VEDIC TONE DOUBLE RING ABOVE;Mn;230;NSM;;;;;N;;;;; 1D00;LATIN LETTER SMALL CAPITAL A;Ll;0;L;;;;;N;;;;; 1D01;LATIN LETTER SMALL CAPITAL AE;Ll;0;L;;;;;N;;;;; 1D02;LATIN SMALL LETTER TURNED AE;Ll;0;L;;;;;N;;;;; 1D03;LATIN LETTER SMALL CAPITAL BARRED B;Ll;0;L;;;;;N;;;;; 1D04;LATIN LETTER SMALL CAPITAL C;Ll;0;L;;;;;N;;;;; 1D05;LATIN LETTER SMALL CAPITAL D;Ll;0;L;;;;;N;;;;; 1D06;LATIN LETTER SMALL CAPITAL ETH;Ll;0;L;;;;;N;;;;; 1D07;LATIN LETTER SMALL CAPITAL E;Ll;0;L;;;;;N;;;;; 1D08;LATIN SMALL LETTER TURNED OPEN E;Ll;0;L;;;;;N;;;;; 1D09;LATIN SMALL LETTER TURNED I;Ll;0;L;;;;;N;;;;; 1D0A;LATIN LETTER SMALL CAPITAL J;Ll;0;L;;;;;N;;;;; 1D0B;LATIN LETTER SMALL CAPITAL K;Ll;0;L;;;;;N;;;;; 1D0C;LATIN LETTER SMALL CAPITAL L WITH STROKE;Ll;0;L;;;;;N;;;;; 1D0D;LATIN LETTER SMALL CAPITAL M;Ll;0;L;;;;;N;;;;; 1D0E;LATIN LETTER SMALL CAPITAL REVERSED N;Ll;0;L;;;;;N;;;;; 1D0F;LATIN LETTER SMALL CAPITAL O;Ll;0;L;;;;;N;;;;; 1D10;LATIN LETTER SMALL CAPITAL OPEN O;Ll;0;L;;;;;N;;;;; 1D11;LATIN SMALL LETTER SIDEWAYS O;Ll;0;L;;;;;N;;;;; 1D12;LATIN SMALL LETTER SIDEWAYS OPEN O;Ll;0;L;;;;;N;;;;; 1D13;LATIN SMALL LETTER SIDEWAYS O WITH STROKE;Ll;0;L;;;;;N;;;;; 1D14;LATIN SMALL LETTER TURNED OE;Ll;0;L;;;;;N;;;;; 1D15;LATIN LETTER SMALL CAPITAL OU;Ll;0;L;;;;;N;;;;; 1D16;LATIN SMALL LETTER TOP HALF O;Ll;0;L;;;;;N;;;;; 1D17;LATIN SMALL LETTER BOTTOM HALF O;Ll;0;L;;;;;N;;;;; 1D18;LATIN LETTER SMALL CAPITAL P;Ll;0;L;;;;;N;;;;; 1D19;LATIN LETTER SMALL CAPITAL REVERSED R;Ll;0;L;;;;;N;;;;; 1D1A;LATIN LETTER SMALL CAPITAL TURNED R;Ll;0;L;;;;;N;;;;; 1D1B;LATIN LETTER SMALL CAPITAL T;Ll;0;L;;;;;N;;;;; 1D1C;LATIN LETTER SMALL CAPITAL U;Ll;0;L;;;;;N;;;;; 1D1D;LATIN SMALL LETTER SIDEWAYS U;Ll;0;L;;;;;N;;;;; 1D1E;LATIN SMALL LETTER SIDEWAYS DIAERESIZED U;Ll;0;L;;;;;N;;;;; 1D1F;LATIN SMALL LETTER SIDEWAYS TURNED M;Ll;0;L;;;;;N;;;;; 1D20;LATIN LETTER SMALL CAPITAL V;Ll;0;L;;;;;N;;;;; 1D21;LATIN LETTER SMALL CAPITAL W;Ll;0;L;;;;;N;;;;; 1D22;LATIN LETTER SMALL CAPITAL Z;Ll;0;L;;;;;N;;;;; 1D23;LATIN LETTER SMALL CAPITAL EZH;Ll;0;L;;;;;N;;;;; 1D24;LATIN LETTER VOICED LARYNGEAL SPIRANT;Ll;0;L;;;;;N;;;;; 1D25;LATIN LETTER AIN;Ll;0;L;;;;;N;;;;; 1D26;GREEK LETTER SMALL CAPITAL GAMMA;Ll;0;L;;;;;N;;;;; 1D27;GREEK LETTER SMALL CAPITAL LAMDA;Ll;0;L;;;;;N;;;;; 1D28;GREEK LETTER SMALL CAPITAL PI;Ll;0;L;;;;;N;;;;; 1D29;GREEK LETTER SMALL CAPITAL RHO;Ll;0;L;;;;;N;;;;; 1D2A;GREEK LETTER SMALL CAPITAL PSI;Ll;0;L;;;;;N;;;;; 1D2B;CYRILLIC LETTER SMALL CAPITAL EL;Ll;0;L;;;;;N;;;;; 1D2C;MODIFIER LETTER CAPITAL A;Lm;0;L; 0041;;;;N;;;;; 1D2D;MODIFIER LETTER CAPITAL AE;Lm;0;L; 00C6;;;;N;;;;; 1D2E;MODIFIER LETTER CAPITAL B;Lm;0;L; 0042;;;;N;;;;; 1D2F;MODIFIER LETTER CAPITAL BARRED B;Lm;0;L;;;;;N;;;;; 1D30;MODIFIER LETTER CAPITAL D;Lm;0;L; 0044;;;;N;;;;; 1D31;MODIFIER LETTER CAPITAL E;Lm;0;L; 0045;;;;N;;;;; 1D32;MODIFIER LETTER CAPITAL REVERSED E;Lm;0;L; 018E;;;;N;;;;; 1D33;MODIFIER LETTER CAPITAL G;Lm;0;L; 0047;;;;N;;;;; 1D34;MODIFIER LETTER CAPITAL H;Lm;0;L; 0048;;;;N;;;;; 1D35;MODIFIER LETTER CAPITAL I;Lm;0;L; 0049;;;;N;;;;; 1D36;MODIFIER LETTER CAPITAL J;Lm;0;L; 004A;;;;N;;;;; 1D37;MODIFIER LETTER CAPITAL K;Lm;0;L; 004B;;;;N;;;;; 1D38;MODIFIER LETTER CAPITAL L;Lm;0;L; 004C;;;;N;;;;; 1D39;MODIFIER LETTER CAPITAL M;Lm;0;L; 004D;;;;N;;;;; 1D3A;MODIFIER LETTER CAPITAL N;Lm;0;L; 004E;;;;N;;;;; 1D3B;MODIFIER LETTER CAPITAL REVERSED N;Lm;0;L;;;;;N;;;;; 1D3C;MODIFIER LETTER CAPITAL O;Lm;0;L; 004F;;;;N;;;;; 1D3D;MODIFIER LETTER CAPITAL OU;Lm;0;L; 0222;;;;N;;;;; 1D3E;MODIFIER LETTER CAPITAL P;Lm;0;L; 0050;;;;N;;;;; 1D3F;MODIFIER LETTER CAPITAL R;Lm;0;L; 0052;;;;N;;;;; 1D40;MODIFIER LETTER CAPITAL T;Lm;0;L; 0054;;;;N;;;;; 1D41;MODIFIER LETTER CAPITAL U;Lm;0;L; 0055;;;;N;;;;; 1D42;MODIFIER LETTER CAPITAL W;Lm;0;L; 0057;;;;N;;;;; 1D43;MODIFIER LETTER SMALL A;Lm;0;L; 0061;;;;N;;;;; 1D44;MODIFIER LETTER SMALL TURNED A;Lm;0;L; 0250;;;;N;;;;; 1D45;MODIFIER LETTER SMALL ALPHA;Lm;0;L; 0251;;;;N;;;;; 1D46;MODIFIER LETTER SMALL TURNED AE;Lm;0;L; 1D02;;;;N;;;;; 1D47;MODIFIER LETTER SMALL B;Lm;0;L; 0062;;;;N;;;;; 1D48;MODIFIER LETTER SMALL D;Lm;0;L; 0064;;;;N;;;;; 1D49;MODIFIER LETTER SMALL E;Lm;0;L; 0065;;;;N;;;;; 1D4A;MODIFIER LETTER SMALL SCHWA;Lm;0;L; 0259;;;;N;;;;; 1D4B;MODIFIER LETTER SMALL OPEN E;Lm;0;L; 025B;;;;N;;;;; 1D4C;MODIFIER LETTER SMALL TURNED OPEN E;Lm;0;L; 025C;;;;N;;;;; 1D4D;MODIFIER LETTER SMALL G;Lm;0;L; 0067;;;;N;;;;; 1D4E;MODIFIER LETTER SMALL TURNED I;Lm;0;L;;;;;N;;;;; 1D4F;MODIFIER LETTER SMALL K;Lm;0;L; 006B;;;;N;;;;; 1D50;MODIFIER LETTER SMALL M;Lm;0;L; 006D;;;;N;;;;; 1D51;MODIFIER LETTER SMALL ENG;Lm;0;L; 014B;;;;N;;;;; 1D52;MODIFIER LETTER SMALL O;Lm;0;L; 006F;;;;N;;;;; 1D53;MODIFIER LETTER SMALL OPEN O;Lm;0;L; 0254;;;;N;;;;; 1D54;MODIFIER LETTER SMALL TOP HALF O;Lm;0;L; 1D16;;;;N;;;;; 1D55;MODIFIER LETTER SMALL BOTTOM HALF O;Lm;0;L; 1D17;;;;N;;;;; 1D56;MODIFIER LETTER SMALL P;Lm;0;L; 0070;;;;N;;;;; 1D57;MODIFIER LETTER SMALL T;Lm;0;L; 0074;;;;N;;;;; 1D58;MODIFIER LETTER SMALL U;Lm;0;L; 0075;;;;N;;;;; 1D59;MODIFIER LETTER SMALL SIDEWAYS U;Lm;0;L; 1D1D;;;;N;;;;; 1D5A;MODIFIER LETTER SMALL TURNED M;Lm;0;L; 026F;;;;N;;;;; 1D5B;MODIFIER LETTER SMALL V;Lm;0;L; 0076;;;;N;;;;; 1D5C;MODIFIER LETTER SMALL AIN;Lm;0;L; 1D25;;;;N;;;;; 1D5D;MODIFIER LETTER SMALL BETA;Lm;0;L; 03B2;;;;N;;;;; 1D5E;MODIFIER LETTER SMALL GREEK GAMMA;Lm;0;L; 03B3;;;;N;;;;; 1D5F;MODIFIER LETTER SMALL DELTA;Lm;0;L; 03B4;;;;N;;;;; 1D60;MODIFIER LETTER SMALL GREEK PHI;Lm;0;L; 03C6;;;;N;;;;; 1D61;MODIFIER LETTER SMALL CHI;Lm;0;L; 03C7;;;;N;;;;; 1D62;LATIN SUBSCRIPT SMALL LETTER I;Lm;0;L; 0069;;;;N;;;;; 1D63;LATIN SUBSCRIPT SMALL LETTER R;Lm;0;L; 0072;;;;N;;;;; 1D64;LATIN SUBSCRIPT SMALL LETTER U;Lm;0;L; 0075;;;;N;;;;; 1D65;LATIN SUBSCRIPT SMALL LETTER V;Lm;0;L; 0076;;;;N;;;;; 1D66;GREEK SUBSCRIPT SMALL LETTER BETA;Lm;0;L; 03B2;;;;N;;;;; 1D67;GREEK SUBSCRIPT SMALL LETTER GAMMA;Lm;0;L; 03B3;;;;N;;;;; 1D68;GREEK SUBSCRIPT SMALL LETTER RHO;Lm;0;L; 03C1;;;;N;;;;; 1D69;GREEK SUBSCRIPT SMALL LETTER PHI;Lm;0;L; 03C6;;;;N;;;;; 1D6A;GREEK SUBSCRIPT SMALL LETTER CHI;Lm;0;L; 03C7;;;;N;;;;; 1D6B;LATIN SMALL LETTER UE;Ll;0;L;;;;;N;;;;; 1D6C;LATIN SMALL LETTER B WITH MIDDLE TILDE;Ll;0;L;;;;;N;;;;; 1D6D;LATIN SMALL LETTER D WITH MIDDLE TILDE;Ll;0;L;;;;;N;;;;; 1D6E;LATIN SMALL LETTER F WITH MIDDLE TILDE;Ll;0;L;;;;;N;;;;; 1D6F;LATIN SMALL LETTER M WITH MIDDLE TILDE;Ll;0;L;;;;;N;;;;; 1D70;LATIN SMALL LETTER N WITH MIDDLE TILDE;Ll;0;L;;;;;N;;;;; 1D71;LATIN SMALL LETTER P WITH MIDDLE TILDE;Ll;0;L;;;;;N;;;;; 1D72;LATIN SMALL LETTER R WITH MIDDLE TILDE;Ll;0;L;;;;;N;;;;; 1D73;LATIN SMALL LETTER R WITH FISHHOOK AND MIDDLE TILDE;Ll;0;L;;;;;N;;;;; 1D74;LATIN SMALL LETTER S WITH MIDDLE TILDE;Ll;0;L;;;;;N;;;;; 1D75;LATIN SMALL LETTER T WITH MIDDLE TILDE;Ll;0;L;;;;;N;;;;; 1D76;LATIN SMALL LETTER Z WITH MIDDLE TILDE;Ll;0;L;;;;;N;;;;; 1D77;LATIN SMALL LETTER TURNED G;Ll;0;L;;;;;N;;;;; 1D78;MODIFIER LETTER CYRILLIC EN;Lm;0;L; 043D;;;;N;;;;; 1D79;LATIN SMALL LETTER INSULAR G;Ll;0;L;;;;;N;;;A77D;;A77D 1D7A;LATIN SMALL LETTER TH WITH STRIKETHROUGH;Ll;0;L;;;;;N;;;;; 1D7B;LATIN SMALL CAPITAL LETTER I WITH STROKE;Ll;0;L;;;;;N;;;;; 1D7C;LATIN SMALL LETTER IOTA WITH STROKE;Ll;0;L;;;;;N;;;;; 1D7D;LATIN SMALL LETTER P WITH STROKE;Ll;0;L;;;;;N;;;2C63;;2C63 1D7E;LATIN SMALL CAPITAL LETTER U WITH STROKE;Ll;0;L;;;;;N;;;;; 1D7F;LATIN SMALL LETTER UPSILON WITH STROKE;Ll;0;L;;;;;N;;;;; 1D80;LATIN SMALL LETTER B WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;; 1D81;LATIN SMALL LETTER D WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;; 1D82;LATIN SMALL LETTER F WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;; 1D83;LATIN SMALL LETTER G WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;; 1D84;LATIN SMALL LETTER K WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;; 1D85;LATIN SMALL LETTER L WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;; 1D86;LATIN SMALL LETTER M WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;; 1D87;LATIN SMALL LETTER N WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;; 1D88;LATIN SMALL LETTER P WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;; 1D89;LATIN SMALL LETTER R WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;; 1D8A;LATIN SMALL LETTER S WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;; 1D8B;LATIN SMALL LETTER ESH WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;; 1D8C;LATIN SMALL LETTER V WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;; 1D8D;LATIN SMALL LETTER X WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;; 1D8E;LATIN SMALL LETTER Z WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;; 1D8F;LATIN SMALL LETTER A WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;; 1D90;LATIN SMALL LETTER ALPHA WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;; 1D91;LATIN SMALL LETTER D WITH HOOK AND TAIL;Ll;0;L;;;;;N;;;;; 1D92;LATIN SMALL LETTER E WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;; 1D93;LATIN SMALL LETTER OPEN E WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;; 1D94;LATIN SMALL LETTER REVERSED OPEN E WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;; 1D95;LATIN SMALL LETTER SCHWA WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;; 1D96;LATIN SMALL LETTER I WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;; 1D97;LATIN SMALL LETTER OPEN O WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;; 1D98;LATIN SMALL LETTER ESH WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;; 1D99;LATIN SMALL LETTER U WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;; 1D9A;LATIN SMALL LETTER EZH WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;; 1D9B;MODIFIER LETTER SMALL TURNED ALPHA;Lm;0;L; 0252;;;;N;;;;; 1D9C;MODIFIER LETTER SMALL C;Lm;0;L; 0063;;;;N;;;;; 1D9D;MODIFIER LETTER SMALL C WITH CURL;Lm;0;L; 0255;;;;N;;;;; 1D9E;MODIFIER LETTER SMALL ETH;Lm;0;L; 00F0;;;;N;;;;; 1D9F;MODIFIER LETTER SMALL REVERSED OPEN E;Lm;0;L; 025C;;;;N;;;;; 1DA0;MODIFIER LETTER SMALL F;Lm;0;L; 0066;;;;N;;;;; 1DA1;MODIFIER LETTER SMALL DOTLESS J WITH STROKE;Lm;0;L; 025F;;;;N;;;;; 1DA2;MODIFIER LETTER SMALL SCRIPT G;Lm;0;L; 0261;;;;N;;;;; 1DA3;MODIFIER LETTER SMALL TURNED H;Lm;0;L; 0265;;;;N;;;;; 1DA4;MODIFIER LETTER SMALL I WITH STROKE;Lm;0;L; 0268;;;;N;;;;; 1DA5;MODIFIER LETTER SMALL IOTA;Lm;0;L; 0269;;;;N;;;;; 1DA6;MODIFIER LETTER SMALL CAPITAL I;Lm;0;L; 026A;;;;N;;;;; 1DA7;MODIFIER LETTER SMALL CAPITAL I WITH STROKE;Lm;0;L; 1D7B;;;;N;;;;; 1DA8;MODIFIER LETTER SMALL J WITH CROSSED-TAIL;Lm;0;L; 029D;;;;N;;;;; 1DA9;MODIFIER LETTER SMALL L WITH RETROFLEX HOOK;Lm;0;L; 026D;;;;N;;;;; 1DAA;MODIFIER LETTER SMALL L WITH PALATAL HOOK;Lm;0;L; 1D85;;;;N;;;;; 1DAB;MODIFIER LETTER SMALL CAPITAL L;Lm;0;L; 029F;;;;N;;;;; 1DAC;MODIFIER LETTER SMALL M WITH HOOK;Lm;0;L; 0271;;;;N;;;;; 1DAD;MODIFIER LETTER SMALL TURNED M WITH LONG LEG;Lm;0;L; 0270;;;;N;;;;; 1DAE;MODIFIER LETTER SMALL N WITH LEFT HOOK;Lm;0;L; 0272;;;;N;;;;; 1DAF;MODIFIER LETTER SMALL N WITH RETROFLEX HOOK;Lm;0;L; 0273;;;;N;;;;; 1DB0;MODIFIER LETTER SMALL CAPITAL N;Lm;0;L; 0274;;;;N;;;;; 1DB1;MODIFIER LETTER SMALL BARRED O;Lm;0;L; 0275;;;;N;;;;; 1DB2;MODIFIER LETTER SMALL PHI;Lm;0;L; 0278;;;;N;;;;; 1DB3;MODIFIER LETTER SMALL S WITH HOOK;Lm;0;L; 0282;;;;N;;;;; 1DB4;MODIFIER LETTER SMALL ESH;Lm;0;L; 0283;;;;N;;;;; 1DB5;MODIFIER LETTER SMALL T WITH PALATAL HOOK;Lm;0;L; 01AB;;;;N;;;;; 1DB6;MODIFIER LETTER SMALL U BAR;Lm;0;L; 0289;;;;N;;;;; 1DB7;MODIFIER LETTER SMALL UPSILON;Lm;0;L; 028A;;;;N;;;;; 1DB8;MODIFIER LETTER SMALL CAPITAL U;Lm;0;L; 1D1C;;;;N;;;;; 1DB9;MODIFIER LETTER SMALL V WITH HOOK;Lm;0;L; 028B;;;;N;;;;; 1DBA;MODIFIER LETTER SMALL TURNED V;Lm;0;L; 028C;;;;N;;;;; 1DBB;MODIFIER LETTER SMALL Z;Lm;0;L; 007A;;;;N;;;;; 1DBC;MODIFIER LETTER SMALL Z WITH RETROFLEX HOOK;Lm;0;L; 0290;;;;N;;;;; 1DBD;MODIFIER LETTER SMALL Z WITH CURL;Lm;0;L; 0291;;;;N;;;;; 1DBE;MODIFIER LETTER SMALL EZH;Lm;0;L; 0292;;;;N;;;;; 1DBF;MODIFIER LETTER SMALL THETA;Lm;0;L; 03B8;;;;N;;;;; 1DC0;COMBINING DOTTED GRAVE ACCENT;Mn;230;NSM;;;;;N;;;;; 1DC1;COMBINING DOTTED ACUTE ACCENT;Mn;230;NSM;;;;;N;;;;; 1DC2;COMBINING SNAKE BELOW;Mn;220;NSM;;;;;N;;;;; 1DC3;COMBINING SUSPENSION MARK;Mn;230;NSM;;;;;N;;;;; 1DC4;COMBINING MACRON-ACUTE;Mn;230;NSM;;;;;N;;;;; 1DC5;COMBINING GRAVE-MACRON;Mn;230;NSM;;;;;N;;;;; 1DC6;COMBINING MACRON-GRAVE;Mn;230;NSM;;;;;N;;;;; 1DC7;COMBINING ACUTE-MACRON;Mn;230;NSM;;;;;N;;;;; 1DC8;COMBINING GRAVE-ACUTE-GRAVE;Mn;230;NSM;;;;;N;;;;; 1DC9;COMBINING ACUTE-GRAVE-ACUTE;Mn;230;NSM;;;;;N;;;;; 1DCA;COMBINING LATIN SMALL LETTER R BELOW;Mn;220;NSM;;;;;N;;;;; 1DCB;COMBINING BREVE-MACRON;Mn;230;NSM;;;;;N;;;;; 1DCC;COMBINING MACRON-BREVE;Mn;230;NSM;;;;;N;;;;; 1DCD;COMBINING DOUBLE CIRCUMFLEX ABOVE;Mn;234;NSM;;;;;N;;;;; 1DCE;COMBINING OGONEK ABOVE;Mn;214;NSM;;;;;N;;;;; 1DCF;COMBINING ZIGZAG BELOW;Mn;220;NSM;;;;;N;;;;; 1DD0;COMBINING IS BELOW;Mn;202;NSM;;;;;N;;;;; 1DD1;COMBINING UR ABOVE;Mn;230;NSM;;;;;N;;;;; 1DD2;COMBINING US ABOVE;Mn;230;NSM;;;;;N;;;;; 1DD3;COMBINING LATIN SMALL LETTER FLATTENED OPEN A ABOVE;Mn;230;NSM;;;;;N;;;;; 1DD4;COMBINING LATIN SMALL LETTER AE;Mn;230;NSM;;;;;N;;;;; 1DD5;COMBINING LATIN SMALL LETTER AO;Mn;230;NSM;;;;;N;;;;; 1DD6;COMBINING LATIN SMALL LETTER AV;Mn;230;NSM;;;;;N;;;;; 1DD7;COMBINING LATIN SMALL LETTER C CEDILLA;Mn;230;NSM;;;;;N;;;;; 1DD8;COMBINING LATIN SMALL LETTER INSULAR D;Mn;230;NSM;;;;;N;;;;; 1DD9;COMBINING LATIN SMALL LETTER ETH;Mn;230;NSM;;;;;N;;;;; 1DDA;COMBINING LATIN SMALL LETTER G;Mn;230;NSM;;;;;N;;;;; 1DDB;COMBINING LATIN LETTER SMALL CAPITAL G;Mn;230;NSM;;;;;N;;;;; 1DDC;COMBINING LATIN SMALL LETTER K;Mn;230;NSM;;;;;N;;;;; 1DDD;COMBINING LATIN SMALL LETTER L;Mn;230;NSM;;;;;N;;;;; 1DDE;COMBINING LATIN LETTER SMALL CAPITAL L;Mn;230;NSM;;;;;N;;;;; 1DDF;COMBINING LATIN LETTER SMALL CAPITAL M;Mn;230;NSM;;;;;N;;;;; 1DE0;COMBINING LATIN SMALL LETTER N;Mn;230;NSM;;;;;N;;;;; 1DE1;COMBINING LATIN LETTER SMALL CAPITAL N;Mn;230;NSM;;;;;N;;;;; 1DE2;COMBINING LATIN LETTER SMALL CAPITAL R;Mn;230;NSM;;;;;N;;;;; 1DE3;COMBINING LATIN SMALL LETTER R ROTUNDA;Mn;230;NSM;;;;;N;;;;; 1DE4;COMBINING LATIN SMALL LETTER S;Mn;230;NSM;;;;;N;;;;; 1DE5;COMBINING LATIN SMALL LETTER LONG S;Mn;230;NSM;;;;;N;;;;; 1DE6;COMBINING LATIN SMALL LETTER Z;Mn;230;NSM;;;;;N;;;;; 1DE7;COMBINING LATIN SMALL LETTER ALPHA;Mn;230;NSM;;;;;N;;;;; 1DE8;COMBINING LATIN SMALL LETTER B;Mn;230;NSM;;;;;N;;;;; 1DE9;COMBINING LATIN SMALL LETTER BETA;Mn;230;NSM;;;;;N;;;;; 1DEA;COMBINING LATIN SMALL LETTER SCHWA;Mn;230;NSM;;;;;N;;;;; 1DEB;COMBINING LATIN SMALL LETTER F;Mn;230;NSM;;;;;N;;;;; 1DEC;COMBINING LATIN SMALL LETTER L WITH DOUBLE MIDDLE TILDE;Mn;230;NSM;;;;;N;;;;; 1DED;COMBINING LATIN SMALL LETTER O WITH LIGHT CENTRALIZATION STROKE;Mn;230;NSM;;;;;N;;;;; 1DEE;COMBINING LATIN SMALL LETTER P;Mn;230;NSM;;;;;N;;;;; 1DEF;COMBINING LATIN SMALL LETTER ESH;Mn;230;NSM;;;;;N;;;;; 1DF0;COMBINING LATIN SMALL LETTER U WITH LIGHT CENTRALIZATION STROKE;Mn;230;NSM;;;;;N;;;;; 1DF1;COMBINING LATIN SMALL LETTER W;Mn;230;NSM;;;;;N;;;;; 1DF2;COMBINING LATIN SMALL LETTER A WITH DIAERESIS;Mn;230;NSM;;;;;N;;;;; 1DF3;COMBINING LATIN SMALL LETTER O WITH DIAERESIS;Mn;230;NSM;;;;;N;;;;; 1DF4;COMBINING LATIN SMALL LETTER U WITH DIAERESIS;Mn;230;NSM;;;;;N;;;;; 1DF5;COMBINING UP TACK ABOVE;Mn;230;NSM;;;;;N;;;;; 1DFB;COMBINING DELETION MARK;Mn;230;NSM;;;;;N;;;;; 1DFC;COMBINING DOUBLE INVERTED BREVE BELOW;Mn;233;NSM;;;;;N;;;;; 1DFD;COMBINING ALMOST EQUAL TO BELOW;Mn;220;NSM;;;;;N;;;;; 1DFE;COMBINING LEFT ARROWHEAD ABOVE;Mn;230;NSM;;;;;N;;;;; 1DFF;COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW;Mn;220;NSM;;;;;N;;;;; 1E00;LATIN CAPITAL LETTER A WITH RING BELOW;Lu;0;L;0041 0325;;;;N;;;;1E01; 1E01;LATIN SMALL LETTER A WITH RING BELOW;Ll;0;L;0061 0325;;;;N;;;1E00;;1E00 1E02;LATIN CAPITAL LETTER B WITH DOT ABOVE;Lu;0;L;0042 0307;;;;N;;;;1E03; 1E03;LATIN SMALL LETTER B WITH DOT ABOVE;Ll;0;L;0062 0307;;;;N;;;1E02;;1E02 1E04;LATIN CAPITAL LETTER B WITH DOT BELOW;Lu;0;L;0042 0323;;;;N;;;;1E05; 1E05;LATIN SMALL LETTER B WITH DOT BELOW;Ll;0;L;0062 0323;;;;N;;;1E04;;1E04 1E06;LATIN CAPITAL LETTER B WITH LINE BELOW;Lu;0;L;0042 0331;;;;N;;;;1E07; 1E07;LATIN SMALL LETTER B WITH LINE BELOW;Ll;0;L;0062 0331;;;;N;;;1E06;;1E06 1E08;LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE;Lu;0;L;00C7 0301;;;;N;;;;1E09; 1E09;LATIN SMALL LETTER C WITH CEDILLA AND ACUTE;Ll;0;L;00E7 0301;;;;N;;;1E08;;1E08 1E0A;LATIN CAPITAL LETTER D WITH DOT ABOVE;Lu;0;L;0044 0307;;;;N;;;;1E0B; 1E0B;LATIN SMALL LETTER D WITH DOT ABOVE;Ll;0;L;0064 0307;;;;N;;;1E0A;;1E0A 1E0C;LATIN CAPITAL LETTER D WITH DOT BELOW;Lu;0;L;0044 0323;;;;N;;;;1E0D; 1E0D;LATIN SMALL LETTER D WITH DOT BELOW;Ll;0;L;0064 0323;;;;N;;;1E0C;;1E0C 1E0E;LATIN CAPITAL LETTER D WITH LINE BELOW;Lu;0;L;0044 0331;;;;N;;;;1E0F; 1E0F;LATIN SMALL LETTER D WITH LINE BELOW;Ll;0;L;0064 0331;;;;N;;;1E0E;;1E0E 1E10;LATIN CAPITAL LETTER D WITH CEDILLA;Lu;0;L;0044 0327;;;;N;;;;1E11; 1E11;LATIN SMALL LETTER D WITH CEDILLA;Ll;0;L;0064 0327;;;;N;;;1E10;;1E10 1E12;LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW;Lu;0;L;0044 032D;;;;N;;;;1E13; 1E13;LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW;Ll;0;L;0064 032D;;;;N;;;1E12;;1E12 1E14;LATIN CAPITAL LETTER E WITH MACRON AND GRAVE;Lu;0;L;0112 0300;;;;N;;;;1E15; 1E15;LATIN SMALL LETTER E WITH MACRON AND GRAVE;Ll;0;L;0113 0300;;;;N;;;1E14;;1E14 1E16;LATIN CAPITAL LETTER E WITH MACRON AND ACUTE;Lu;0;L;0112 0301;;;;N;;;;1E17; 1E17;LATIN SMALL LETTER E WITH MACRON AND ACUTE;Ll;0;L;0113 0301;;;;N;;;1E16;;1E16 1E18;LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW;Lu;0;L;0045 032D;;;;N;;;;1E19; 1E19;LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW;Ll;0;L;0065 032D;;;;N;;;1E18;;1E18 1E1A;LATIN CAPITAL LETTER E WITH TILDE BELOW;Lu;0;L;0045 0330;;;;N;;;;1E1B; 1E1B;LATIN SMALL LETTER E WITH TILDE BELOW;Ll;0;L;0065 0330;;;;N;;;1E1A;;1E1A 1E1C;LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE;Lu;0;L;0228 0306;;;;N;;;;1E1D; 1E1D;LATIN SMALL LETTER E WITH CEDILLA AND BREVE;Ll;0;L;0229 0306;;;;N;;;1E1C;;1E1C 1E1E;LATIN CAPITAL LETTER F WITH DOT ABOVE;Lu;0;L;0046 0307;;;;N;;;;1E1F; 1E1F;LATIN SMALL LETTER F WITH DOT ABOVE;Ll;0;L;0066 0307;;;;N;;;1E1E;;1E1E 1E20;LATIN CAPITAL LETTER G WITH MACRON;Lu;0;L;0047 0304;;;;N;;;;1E21; 1E21;LATIN SMALL LETTER G WITH MACRON;Ll;0;L;0067 0304;;;;N;;;1E20;;1E20 1E22;LATIN CAPITAL LETTER H WITH DOT ABOVE;Lu;0;L;0048 0307;;;;N;;;;1E23; 1E23;LATIN SMALL LETTER H WITH DOT ABOVE;Ll;0;L;0068 0307;;;;N;;;1E22;;1E22 1E24;LATIN CAPITAL LETTER H WITH DOT BELOW;Lu;0;L;0048 0323;;;;N;;;;1E25; 1E25;LATIN SMALL LETTER H WITH DOT BELOW;Ll;0;L;0068 0323;;;;N;;;1E24;;1E24 1E26;LATIN CAPITAL LETTER H WITH DIAERESIS;Lu;0;L;0048 0308;;;;N;;;;1E27; 1E27;LATIN SMALL LETTER H WITH DIAERESIS;Ll;0;L;0068 0308;;;;N;;;1E26;;1E26 1E28;LATIN CAPITAL LETTER H WITH CEDILLA;Lu;0;L;0048 0327;;;;N;;;;1E29; 1E29;LATIN SMALL LETTER H WITH CEDILLA;Ll;0;L;0068 0327;;;;N;;;1E28;;1E28 1E2A;LATIN CAPITAL LETTER H WITH BREVE BELOW;Lu;0;L;0048 032E;;;;N;;;;1E2B; 1E2B;LATIN SMALL LETTER H WITH BREVE BELOW;Ll;0;L;0068 032E;;;;N;;;1E2A;;1E2A 1E2C;LATIN CAPITAL LETTER I WITH TILDE BELOW;Lu;0;L;0049 0330;;;;N;;;;1E2D; 1E2D;LATIN SMALL LETTER I WITH TILDE BELOW;Ll;0;L;0069 0330;;;;N;;;1E2C;;1E2C 1E2E;LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE;Lu;0;L;00CF 0301;;;;N;;;;1E2F; 1E2F;LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE;Ll;0;L;00EF 0301;;;;N;;;1E2E;;1E2E 1E30;LATIN CAPITAL LETTER K WITH ACUTE;Lu;0;L;004B 0301;;;;N;;;;1E31; 1E31;LATIN SMALL LETTER K WITH ACUTE;Ll;0;L;006B 0301;;;;N;;;1E30;;1E30 1E32;LATIN CAPITAL LETTER K WITH DOT BELOW;Lu;0;L;004B 0323;;;;N;;;;1E33; 1E33;LATIN SMALL LETTER K WITH DOT BELOW;Ll;0;L;006B 0323;;;;N;;;1E32;;1E32 1E34;LATIN CAPITAL LETTER K WITH LINE BELOW;Lu;0;L;004B 0331;;;;N;;;;1E35; 1E35;LATIN SMALL LETTER K WITH LINE BELOW;Ll;0;L;006B 0331;;;;N;;;1E34;;1E34 1E36;LATIN CAPITAL LETTER L WITH DOT BELOW;Lu;0;L;004C 0323;;;;N;;;;1E37; 1E37;LATIN SMALL LETTER L WITH DOT BELOW;Ll;0;L;006C 0323;;;;N;;;1E36;;1E36 1E38;LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON;Lu;0;L;1E36 0304;;;;N;;;;1E39; 1E39;LATIN SMALL LETTER L WITH DOT BELOW AND MACRON;Ll;0;L;1E37 0304;;;;N;;;1E38;;1E38 1E3A;LATIN CAPITAL LETTER L WITH LINE BELOW;Lu;0;L;004C 0331;;;;N;;;;1E3B; 1E3B;LATIN SMALL LETTER L WITH LINE BELOW;Ll;0;L;006C 0331;;;;N;;;1E3A;;1E3A 1E3C;LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW;Lu;0;L;004C 032D;;;;N;;;;1E3D; 1E3D;LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW;Ll;0;L;006C 032D;;;;N;;;1E3C;;1E3C 1E3E;LATIN CAPITAL LETTER M WITH ACUTE;Lu;0;L;004D 0301;;;;N;;;;1E3F; 1E3F;LATIN SMALL LETTER M WITH ACUTE;Ll;0;L;006D 0301;;;;N;;;1E3E;;1E3E 1E40;LATIN CAPITAL LETTER M WITH DOT ABOVE;Lu;0;L;004D 0307;;;;N;;;;1E41; 1E41;LATIN SMALL LETTER M WITH DOT ABOVE;Ll;0;L;006D 0307;;;;N;;;1E40;;1E40 1E42;LATIN CAPITAL LETTER M WITH DOT BELOW;Lu;0;L;004D 0323;;;;N;;;;1E43; 1E43;LATIN SMALL LETTER M WITH DOT BELOW;Ll;0;L;006D 0323;;;;N;;;1E42;;1E42 1E44;LATIN CAPITAL LETTER N WITH DOT ABOVE;Lu;0;L;004E 0307;;;;N;;;;1E45; 1E45;LATIN SMALL LETTER N WITH DOT ABOVE;Ll;0;L;006E 0307;;;;N;;;1E44;;1E44 1E46;LATIN CAPITAL LETTER N WITH DOT BELOW;Lu;0;L;004E 0323;;;;N;;;;1E47; 1E47;LATIN SMALL LETTER N WITH DOT BELOW;Ll;0;L;006E 0323;;;;N;;;1E46;;1E46 1E48;LATIN CAPITAL LETTER N WITH LINE BELOW;Lu;0;L;004E 0331;;;;N;;;;1E49; 1E49;LATIN SMALL LETTER N WITH LINE BELOW;Ll;0;L;006E 0331;;;;N;;;1E48;;1E48 1E4A;LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW;Lu;0;L;004E 032D;;;;N;;;;1E4B; 1E4B;LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW;Ll;0;L;006E 032D;;;;N;;;1E4A;;1E4A 1E4C;LATIN CAPITAL LETTER O WITH TILDE AND ACUTE;Lu;0;L;00D5 0301;;;;N;;;;1E4D; 1E4D;LATIN SMALL LETTER O WITH TILDE AND ACUTE;Ll;0;L;00F5 0301;;;;N;;;1E4C;;1E4C 1E4E;LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS;Lu;0;L;00D5 0308;;;;N;;;;1E4F; 1E4F;LATIN SMALL LETTER O WITH TILDE AND DIAERESIS;Ll;0;L;00F5 0308;;;;N;;;1E4E;;1E4E 1E50;LATIN CAPITAL LETTER O WITH MACRON AND GRAVE;Lu;0;L;014C 0300;;;;N;;;;1E51; 1E51;LATIN SMALL LETTER O WITH MACRON AND GRAVE;Ll;0;L;014D 0300;;;;N;;;1E50;;1E50 1E52;LATIN CAPITAL LETTER O WITH MACRON AND ACUTE;Lu;0;L;014C 0301;;;;N;;;;1E53; 1E53;LATIN SMALL LETTER O WITH MACRON AND ACUTE;Ll;0;L;014D 0301;;;;N;;;1E52;;1E52 1E54;LATIN CAPITAL LETTER P WITH ACUTE;Lu;0;L;0050 0301;;;;N;;;;1E55; 1E55;LATIN SMALL LETTER P WITH ACUTE;Ll;0;L;0070 0301;;;;N;;;1E54;;1E54 1E56;LATIN CAPITAL LETTER P WITH DOT ABOVE;Lu;0;L;0050 0307;;;;N;;;;1E57; 1E57;LATIN SMALL LETTER P WITH DOT ABOVE;Ll;0;L;0070 0307;;;;N;;;1E56;;1E56 1E58;LATIN CAPITAL LETTER R WITH DOT ABOVE;Lu;0;L;0052 0307;;;;N;;;;1E59; 1E59;LATIN SMALL LETTER R WITH DOT ABOVE;Ll;0;L;0072 0307;;;;N;;;1E58;;1E58 1E5A;LATIN CAPITAL LETTER R WITH DOT BELOW;Lu;0;L;0052 0323;;;;N;;;;1E5B; 1E5B;LATIN SMALL LETTER R WITH DOT BELOW;Ll;0;L;0072 0323;;;;N;;;1E5A;;1E5A 1E5C;LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON;Lu;0;L;1E5A 0304;;;;N;;;;1E5D; 1E5D;LATIN SMALL LETTER R WITH DOT BELOW AND MACRON;Ll;0;L;1E5B 0304;;;;N;;;1E5C;;1E5C 1E5E;LATIN CAPITAL LETTER R WITH LINE BELOW;Lu;0;L;0052 0331;;;;N;;;;1E5F; 1E5F;LATIN SMALL LETTER R WITH LINE BELOW;Ll;0;L;0072 0331;;;;N;;;1E5E;;1E5E 1E60;LATIN CAPITAL LETTER S WITH DOT ABOVE;Lu;0;L;0053 0307;;;;N;;;;1E61; 1E61;LATIN SMALL LETTER S WITH DOT ABOVE;Ll;0;L;0073 0307;;;;N;;;1E60;;1E60 1E62;LATIN CAPITAL LETTER S WITH DOT BELOW;Lu;0;L;0053 0323;;;;N;;;;1E63; 1E63;LATIN SMALL LETTER S WITH DOT BELOW;Ll;0;L;0073 0323;;;;N;;;1E62;;1E62 1E64;LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE;Lu;0;L;015A 0307;;;;N;;;;1E65; 1E65;LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE;Ll;0;L;015B 0307;;;;N;;;1E64;;1E64 1E66;LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE;Lu;0;L;0160 0307;;;;N;;;;1E67; 1E67;LATIN SMALL LETTER S WITH CARON AND DOT ABOVE;Ll;0;L;0161 0307;;;;N;;;1E66;;1E66 1E68;LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE;Lu;0;L;1E62 0307;;;;N;;;;1E69; 1E69;LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE;Ll;0;L;1E63 0307;;;;N;;;1E68;;1E68 1E6A;LATIN CAPITAL LETTER T WITH DOT ABOVE;Lu;0;L;0054 0307;;;;N;;;;1E6B; 1E6B;LATIN SMALL LETTER T WITH DOT ABOVE;Ll;0;L;0074 0307;;;;N;;;1E6A;;1E6A 1E6C;LATIN CAPITAL LETTER T WITH DOT BELOW;Lu;0;L;0054 0323;;;;N;;;;1E6D; 1E6D;LATIN SMALL LETTER T WITH DOT BELOW;Ll;0;L;0074 0323;;;;N;;;1E6C;;1E6C 1E6E;LATIN CAPITAL LETTER T WITH LINE BELOW;Lu;0;L;0054 0331;;;;N;;;;1E6F; 1E6F;LATIN SMALL LETTER T WITH LINE BELOW;Ll;0;L;0074 0331;;;;N;;;1E6E;;1E6E 1E70;LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW;Lu;0;L;0054 032D;;;;N;;;;1E71; 1E71;LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW;Ll;0;L;0074 032D;;;;N;;;1E70;;1E70 1E72;LATIN CAPITAL LETTER U WITH DIAERESIS BELOW;Lu;0;L;0055 0324;;;;N;;;;1E73; 1E73;LATIN SMALL LETTER U WITH DIAERESIS BELOW;Ll;0;L;0075 0324;;;;N;;;1E72;;1E72 1E74;LATIN CAPITAL LETTER U WITH TILDE BELOW;Lu;0;L;0055 0330;;;;N;;;;1E75; 1E75;LATIN SMALL LETTER U WITH TILDE BELOW;Ll;0;L;0075 0330;;;;N;;;1E74;;1E74 1E76;LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW;Lu;0;L;0055 032D;;;;N;;;;1E77; 1E77;LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW;Ll;0;L;0075 032D;;;;N;;;1E76;;1E76 1E78;LATIN CAPITAL LETTER U WITH TILDE AND ACUTE;Lu;0;L;0168 0301;;;;N;;;;1E79; 1E79;LATIN SMALL LETTER U WITH TILDE AND ACUTE;Ll;0;L;0169 0301;;;;N;;;1E78;;1E78 1E7A;LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS;Lu;0;L;016A 0308;;;;N;;;;1E7B; 1E7B;LATIN SMALL LETTER U WITH MACRON AND DIAERESIS;Ll;0;L;016B 0308;;;;N;;;1E7A;;1E7A 1E7C;LATIN CAPITAL LETTER V WITH TILDE;Lu;0;L;0056 0303;;;;N;;;;1E7D; 1E7D;LATIN SMALL LETTER V WITH TILDE;Ll;0;L;0076 0303;;;;N;;;1E7C;;1E7C 1E7E;LATIN CAPITAL LETTER V WITH DOT BELOW;Lu;0;L;0056 0323;;;;N;;;;1E7F; 1E7F;LATIN SMALL LETTER V WITH DOT BELOW;Ll;0;L;0076 0323;;;;N;;;1E7E;;1E7E 1E80;LATIN CAPITAL LETTER W WITH GRAVE;Lu;0;L;0057 0300;;;;N;;;;1E81; 1E81;LATIN SMALL LETTER W WITH GRAVE;Ll;0;L;0077 0300;;;;N;;;1E80;;1E80 1E82;LATIN CAPITAL LETTER W WITH ACUTE;Lu;0;L;0057 0301;;;;N;;;;1E83; 1E83;LATIN SMALL LETTER W WITH ACUTE;Ll;0;L;0077 0301;;;;N;;;1E82;;1E82 1E84;LATIN CAPITAL LETTER W WITH DIAERESIS;Lu;0;L;0057 0308;;;;N;;;;1E85; 1E85;LATIN SMALL LETTER W WITH DIAERESIS;Ll;0;L;0077 0308;;;;N;;;1E84;;1E84 1E86;LATIN CAPITAL LETTER W WITH DOT ABOVE;Lu;0;L;0057 0307;;;;N;;;;1E87; 1E87;LATIN SMALL LETTER W WITH DOT ABOVE;Ll;0;L;0077 0307;;;;N;;;1E86;;1E86 1E88;LATIN CAPITAL LETTER W WITH DOT BELOW;Lu;0;L;0057 0323;;;;N;;;;1E89; 1E89;LATIN SMALL LETTER W WITH DOT BELOW;Ll;0;L;0077 0323;;;;N;;;1E88;;1E88 1E8A;LATIN CAPITAL LETTER X WITH DOT ABOVE;Lu;0;L;0058 0307;;;;N;;;;1E8B; 1E8B;LATIN SMALL LETTER X WITH DOT ABOVE;Ll;0;L;0078 0307;;;;N;;;1E8A;;1E8A 1E8C;LATIN CAPITAL LETTER X WITH DIAERESIS;Lu;0;L;0058 0308;;;;N;;;;1E8D; 1E8D;LATIN SMALL LETTER X WITH DIAERESIS;Ll;0;L;0078 0308;;;;N;;;1E8C;;1E8C 1E8E;LATIN CAPITAL LETTER Y WITH DOT ABOVE;Lu;0;L;0059 0307;;;;N;;;;1E8F; 1E8F;LATIN SMALL LETTER Y WITH DOT ABOVE;Ll;0;L;0079 0307;;;;N;;;1E8E;;1E8E 1E90;LATIN CAPITAL LETTER Z WITH CIRCUMFLEX;Lu;0;L;005A 0302;;;;N;;;;1E91; 1E91;LATIN SMALL LETTER Z WITH CIRCUMFLEX;Ll;0;L;007A 0302;;;;N;;;1E90;;1E90 1E92;LATIN CAPITAL LETTER Z WITH DOT BELOW;Lu;0;L;005A 0323;;;;N;;;;1E93; 1E93;LATIN SMALL LETTER Z WITH DOT BELOW;Ll;0;L;007A 0323;;;;N;;;1E92;;1E92 1E94;LATIN CAPITAL LETTER Z WITH LINE BELOW;Lu;0;L;005A 0331;;;;N;;;;1E95; 1E95;LATIN SMALL LETTER Z WITH LINE BELOW;Ll;0;L;007A 0331;;;;N;;;1E94;;1E94 1E96;LATIN SMALL LETTER H WITH LINE BELOW;Ll;0;L;0068 0331;;;;N;;;;; 1E97;LATIN SMALL LETTER T WITH DIAERESIS;Ll;0;L;0074 0308;;;;N;;;;; 1E98;LATIN SMALL LETTER W WITH RING ABOVE;Ll;0;L;0077 030A;;;;N;;;;; 1E99;LATIN SMALL LETTER Y WITH RING ABOVE;Ll;0;L;0079 030A;;;;N;;;;; 1E9A;LATIN SMALL LETTER A WITH RIGHT HALF RING;Ll;0;L; 0061 02BE;;;;N;;;;; 1E9B;LATIN SMALL LETTER LONG S WITH DOT ABOVE;Ll;0;L;017F 0307;;;;N;;;1E60;;1E60 1E9C;LATIN SMALL LETTER LONG S WITH DIAGONAL STROKE;Ll;0;L;;;;;N;;;;; 1E9D;LATIN SMALL LETTER LONG S WITH HIGH STROKE;Ll;0;L;;;;;N;;;;; 1E9E;LATIN CAPITAL LETTER SHARP S;Lu;0;L;;;;;N;;;;00DF; 1E9F;LATIN SMALL LETTER DELTA;Ll;0;L;;;;;N;;;;; 1EA0;LATIN CAPITAL LETTER A WITH DOT BELOW;Lu;0;L;0041 0323;;;;N;;;;1EA1; 1EA1;LATIN SMALL LETTER A WITH DOT BELOW;Ll;0;L;0061 0323;;;;N;;;1EA0;;1EA0 1EA2;LATIN CAPITAL LETTER A WITH HOOK ABOVE;Lu;0;L;0041 0309;;;;N;;;;1EA3; 1EA3;LATIN SMALL LETTER A WITH HOOK ABOVE;Ll;0;L;0061 0309;;;;N;;;1EA2;;1EA2 1EA4;LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE;Lu;0;L;00C2 0301;;;;N;;;;1EA5; 1EA5;LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE;Ll;0;L;00E2 0301;;;;N;;;1EA4;;1EA4 1EA6;LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE;Lu;0;L;00C2 0300;;;;N;;;;1EA7; 1EA7;LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE;Ll;0;L;00E2 0300;;;;N;;;1EA6;;1EA6 1EA8;LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE;Lu;0;L;00C2 0309;;;;N;;;;1EA9; 1EA9;LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE;Ll;0;L;00E2 0309;;;;N;;;1EA8;;1EA8 1EAA;LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE;Lu;0;L;00C2 0303;;;;N;;;;1EAB; 1EAB;LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE;Ll;0;L;00E2 0303;;;;N;;;1EAA;;1EAA 1EAC;LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW;Lu;0;L;1EA0 0302;;;;N;;;;1EAD; 1EAD;LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW;Ll;0;L;1EA1 0302;;;;N;;;1EAC;;1EAC 1EAE;LATIN CAPITAL LETTER A WITH BREVE AND ACUTE;Lu;0;L;0102 0301;;;;N;;;;1EAF; 1EAF;LATIN SMALL LETTER A WITH BREVE AND ACUTE;Ll;0;L;0103 0301;;;;N;;;1EAE;;1EAE 1EB0;LATIN CAPITAL LETTER A WITH BREVE AND GRAVE;Lu;0;L;0102 0300;;;;N;;;;1EB1; 1EB1;LATIN SMALL LETTER A WITH BREVE AND GRAVE;Ll;0;L;0103 0300;;;;N;;;1EB0;;1EB0 1EB2;LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE;Lu;0;L;0102 0309;;;;N;;;;1EB3; 1EB3;LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE;Ll;0;L;0103 0309;;;;N;;;1EB2;;1EB2 1EB4;LATIN CAPITAL LETTER A WITH BREVE AND TILDE;Lu;0;L;0102 0303;;;;N;;;;1EB5; 1EB5;LATIN SMALL LETTER A WITH BREVE AND TILDE;Ll;0;L;0103 0303;;;;N;;;1EB4;;1EB4 1EB6;LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW;Lu;0;L;1EA0 0306;;;;N;;;;1EB7; 1EB7;LATIN SMALL LETTER A WITH BREVE AND DOT BELOW;Ll;0;L;1EA1 0306;;;;N;;;1EB6;;1EB6 1EB8;LATIN CAPITAL LETTER E WITH DOT BELOW;Lu;0;L;0045 0323;;;;N;;;;1EB9; 1EB9;LATIN SMALL LETTER E WITH DOT BELOW;Ll;0;L;0065 0323;;;;N;;;1EB8;;1EB8 1EBA;LATIN CAPITAL LETTER E WITH HOOK ABOVE;Lu;0;L;0045 0309;;;;N;;;;1EBB; 1EBB;LATIN SMALL LETTER E WITH HOOK ABOVE;Ll;0;L;0065 0309;;;;N;;;1EBA;;1EBA 1EBC;LATIN CAPITAL LETTER E WITH TILDE;Lu;0;L;0045 0303;;;;N;;;;1EBD; 1EBD;LATIN SMALL LETTER E WITH TILDE;Ll;0;L;0065 0303;;;;N;;;1EBC;;1EBC 1EBE;LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE;Lu;0;L;00CA 0301;;;;N;;;;1EBF; 1EBF;LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE;Ll;0;L;00EA 0301;;;;N;;;1EBE;;1EBE 1EC0;LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE;Lu;0;L;00CA 0300;;;;N;;;;1EC1; 1EC1;LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE;Ll;0;L;00EA 0300;;;;N;;;1EC0;;1EC0 1EC2;LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE;Lu;0;L;00CA 0309;;;;N;;;;1EC3; 1EC3;LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE;Ll;0;L;00EA 0309;;;;N;;;1EC2;;1EC2 1EC4;LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE;Lu;0;L;00CA 0303;;;;N;;;;1EC5; 1EC5;LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE;Ll;0;L;00EA 0303;;;;N;;;1EC4;;1EC4 1EC6;LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW;Lu;0;L;1EB8 0302;;;;N;;;;1EC7; 1EC7;LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW;Ll;0;L;1EB9 0302;;;;N;;;1EC6;;1EC6 1EC8;LATIN CAPITAL LETTER I WITH HOOK ABOVE;Lu;0;L;0049 0309;;;;N;;;;1EC9; 1EC9;LATIN SMALL LETTER I WITH HOOK ABOVE;Ll;0;L;0069 0309;;;;N;;;1EC8;;1EC8 1ECA;LATIN CAPITAL LETTER I WITH DOT BELOW;Lu;0;L;0049 0323;;;;N;;;;1ECB; 1ECB;LATIN SMALL LETTER I WITH DOT BELOW;Ll;0;L;0069 0323;;;;N;;;1ECA;;1ECA 1ECC;LATIN CAPITAL LETTER O WITH DOT BELOW;Lu;0;L;004F 0323;;;;N;;;;1ECD; 1ECD;LATIN SMALL LETTER O WITH DOT BELOW;Ll;0;L;006F 0323;;;;N;;;1ECC;;1ECC 1ECE;LATIN CAPITAL LETTER O WITH HOOK ABOVE;Lu;0;L;004F 0309;;;;N;;;;1ECF; 1ECF;LATIN SMALL LETTER O WITH HOOK ABOVE;Ll;0;L;006F 0309;;;;N;;;1ECE;;1ECE 1ED0;LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE;Lu;0;L;00D4 0301;;;;N;;;;1ED1; 1ED1;LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE;Ll;0;L;00F4 0301;;;;N;;;1ED0;;1ED0 1ED2;LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE;Lu;0;L;00D4 0300;;;;N;;;;1ED3; 1ED3;LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE;Ll;0;L;00F4 0300;;;;N;;;1ED2;;1ED2 1ED4;LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE;Lu;0;L;00D4 0309;;;;N;;;;1ED5; 1ED5;LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE;Ll;0;L;00F4 0309;;;;N;;;1ED4;;1ED4 1ED6;LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE;Lu;0;L;00D4 0303;;;;N;;;;1ED7; 1ED7;LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE;Ll;0;L;00F4 0303;;;;N;;;1ED6;;1ED6 1ED8;LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW;Lu;0;L;1ECC 0302;;;;N;;;;1ED9; 1ED9;LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW;Ll;0;L;1ECD 0302;;;;N;;;1ED8;;1ED8 1EDA;LATIN CAPITAL LETTER O WITH HORN AND ACUTE;Lu;0;L;01A0 0301;;;;N;;;;1EDB; 1EDB;LATIN SMALL LETTER O WITH HORN AND ACUTE;Ll;0;L;01A1 0301;;;;N;;;1EDA;;1EDA 1EDC;LATIN CAPITAL LETTER O WITH HORN AND GRAVE;Lu;0;L;01A0 0300;;;;N;;;;1EDD; 1EDD;LATIN SMALL LETTER O WITH HORN AND GRAVE;Ll;0;L;01A1 0300;;;;N;;;1EDC;;1EDC 1EDE;LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE;Lu;0;L;01A0 0309;;;;N;;;;1EDF; 1EDF;LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE;Ll;0;L;01A1 0309;;;;N;;;1EDE;;1EDE 1EE0;LATIN CAPITAL LETTER O WITH HORN AND TILDE;Lu;0;L;01A0 0303;;;;N;;;;1EE1; 1EE1;LATIN SMALL LETTER O WITH HORN AND TILDE;Ll;0;L;01A1 0303;;;;N;;;1EE0;;1EE0 1EE2;LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW;Lu;0;L;01A0 0323;;;;N;;;;1EE3; 1EE3;LATIN SMALL LETTER O WITH HORN AND DOT BELOW;Ll;0;L;01A1 0323;;;;N;;;1EE2;;1EE2 1EE4;LATIN CAPITAL LETTER U WITH DOT BELOW;Lu;0;L;0055 0323;;;;N;;;;1EE5; 1EE5;LATIN SMALL LETTER U WITH DOT BELOW;Ll;0;L;0075 0323;;;;N;;;1EE4;;1EE4 1EE6;LATIN CAPITAL LETTER U WITH HOOK ABOVE;Lu;0;L;0055 0309;;;;N;;;;1EE7; 1EE7;LATIN SMALL LETTER U WITH HOOK ABOVE;Ll;0;L;0075 0309;;;;N;;;1EE6;;1EE6 1EE8;LATIN CAPITAL LETTER U WITH HORN AND ACUTE;Lu;0;L;01AF 0301;;;;N;;;;1EE9; 1EE9;LATIN SMALL LETTER U WITH HORN AND ACUTE;Ll;0;L;01B0 0301;;;;N;;;1EE8;;1EE8 1EEA;LATIN CAPITAL LETTER U WITH HORN AND GRAVE;Lu;0;L;01AF 0300;;;;N;;;;1EEB; 1EEB;LATIN SMALL LETTER U WITH HORN AND GRAVE;Ll;0;L;01B0 0300;;;;N;;;1EEA;;1EEA 1EEC;LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE;Lu;0;L;01AF 0309;;;;N;;;;1EED; 1EED;LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE;Ll;0;L;01B0 0309;;;;N;;;1EEC;;1EEC 1EEE;LATIN CAPITAL LETTER U WITH HORN AND TILDE;Lu;0;L;01AF 0303;;;;N;;;;1EEF; 1EEF;LATIN SMALL LETTER U WITH HORN AND TILDE;Ll;0;L;01B0 0303;;;;N;;;1EEE;;1EEE 1EF0;LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW;Lu;0;L;01AF 0323;;;;N;;;;1EF1; 1EF1;LATIN SMALL LETTER U WITH HORN AND DOT BELOW;Ll;0;L;01B0 0323;;;;N;;;1EF0;;1EF0 1EF2;LATIN CAPITAL LETTER Y WITH GRAVE;Lu;0;L;0059 0300;;;;N;;;;1EF3; 1EF3;LATIN SMALL LETTER Y WITH GRAVE;Ll;0;L;0079 0300;;;;N;;;1EF2;;1EF2 1EF4;LATIN CAPITAL LETTER Y WITH DOT BELOW;Lu;0;L;0059 0323;;;;N;;;;1EF5; 1EF5;LATIN SMALL LETTER Y WITH DOT BELOW;Ll;0;L;0079 0323;;;;N;;;1EF4;;1EF4 1EF6;LATIN CAPITAL LETTER Y WITH HOOK ABOVE;Lu;0;L;0059 0309;;;;N;;;;1EF7; 1EF7;LATIN SMALL LETTER Y WITH HOOK ABOVE;Ll;0;L;0079 0309;;;;N;;;1EF6;;1EF6 1EF8;LATIN CAPITAL LETTER Y WITH TILDE;Lu;0;L;0059 0303;;;;N;;;;1EF9; 1EF9;LATIN SMALL LETTER Y WITH TILDE;Ll;0;L;0079 0303;;;;N;;;1EF8;;1EF8 1EFA;LATIN CAPITAL LETTER MIDDLE-WELSH LL;Lu;0;L;;;;;N;;;;1EFB; 1EFB;LATIN SMALL LETTER MIDDLE-WELSH LL;Ll;0;L;;;;;N;;;1EFA;;1EFA 1EFC;LATIN CAPITAL LETTER MIDDLE-WELSH V;Lu;0;L;;;;;N;;;;1EFD; 1EFD;LATIN SMALL LETTER MIDDLE-WELSH V;Ll;0;L;;;;;N;;;1EFC;;1EFC 1EFE;LATIN CAPITAL LETTER Y WITH LOOP;Lu;0;L;;;;;N;;;;1EFF; 1EFF;LATIN SMALL LETTER Y WITH LOOP;Ll;0;L;;;;;N;;;1EFE;;1EFE 1F00;GREEK SMALL LETTER ALPHA WITH PSILI;Ll;0;L;03B1 0313;;;;N;;;1F08;;1F08 1F01;GREEK SMALL LETTER ALPHA WITH DASIA;Ll;0;L;03B1 0314;;;;N;;;1F09;;1F09 1F02;GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA;Ll;0;L;1F00 0300;;;;N;;;1F0A;;1F0A 1F03;GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA;Ll;0;L;1F01 0300;;;;N;;;1F0B;;1F0B 1F04;GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA;Ll;0;L;1F00 0301;;;;N;;;1F0C;;1F0C 1F05;GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA;Ll;0;L;1F01 0301;;;;N;;;1F0D;;1F0D 1F06;GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI;Ll;0;L;1F00 0342;;;;N;;;1F0E;;1F0E 1F07;GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI;Ll;0;L;1F01 0342;;;;N;;;1F0F;;1F0F 1F08;GREEK CAPITAL LETTER ALPHA WITH PSILI;Lu;0;L;0391 0313;;;;N;;;;1F00; 1F09;GREEK CAPITAL LETTER ALPHA WITH DASIA;Lu;0;L;0391 0314;;;;N;;;;1F01; 1F0A;GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA;Lu;0;L;1F08 0300;;;;N;;;;1F02; 1F0B;GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA;Lu;0;L;1F09 0300;;;;N;;;;1F03; 1F0C;GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA;Lu;0;L;1F08 0301;;;;N;;;;1F04; 1F0D;GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA;Lu;0;L;1F09 0301;;;;N;;;;1F05; 1F0E;GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI;Lu;0;L;1F08 0342;;;;N;;;;1F06; 1F0F;GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI;Lu;0;L;1F09 0342;;;;N;;;;1F07; 1F10;GREEK SMALL LETTER EPSILON WITH PSILI;Ll;0;L;03B5 0313;;;;N;;;1F18;;1F18 1F11;GREEK SMALL LETTER EPSILON WITH DASIA;Ll;0;L;03B5 0314;;;;N;;;1F19;;1F19 1F12;GREEK SMALL LETTER EPSILON WITH PSILI AND VARIA;Ll;0;L;1F10 0300;;;;N;;;1F1A;;1F1A 1F13;GREEK SMALL LETTER EPSILON WITH DASIA AND VARIA;Ll;0;L;1F11 0300;;;;N;;;1F1B;;1F1B 1F14;GREEK SMALL LETTER EPSILON WITH PSILI AND OXIA;Ll;0;L;1F10 0301;;;;N;;;1F1C;;1F1C 1F15;GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA;Ll;0;L;1F11 0301;;;;N;;;1F1D;;1F1D 1F18;GREEK CAPITAL LETTER EPSILON WITH PSILI;Lu;0;L;0395 0313;;;;N;;;;1F10; 1F19;GREEK CAPITAL LETTER EPSILON WITH DASIA;Lu;0;L;0395 0314;;;;N;;;;1F11; 1F1A;GREEK CAPITAL LETTER EPSILON WITH PSILI AND VARIA;Lu;0;L;1F18 0300;;;;N;;;;1F12; 1F1B;GREEK CAPITAL LETTER EPSILON WITH DASIA AND VARIA;Lu;0;L;1F19 0300;;;;N;;;;1F13; 1F1C;GREEK CAPITAL LETTER EPSILON WITH PSILI AND OXIA;Lu;0;L;1F18 0301;;;;N;;;;1F14; 1F1D;GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA;Lu;0;L;1F19 0301;;;;N;;;;1F15; 1F20;GREEK SMALL LETTER ETA WITH PSILI;Ll;0;L;03B7 0313;;;;N;;;1F28;;1F28 1F21;GREEK SMALL LETTER ETA WITH DASIA;Ll;0;L;03B7 0314;;;;N;;;1F29;;1F29 1F22;GREEK SMALL LETTER ETA WITH PSILI AND VARIA;Ll;0;L;1F20 0300;;;;N;;;1F2A;;1F2A 1F23;GREEK SMALL LETTER ETA WITH DASIA AND VARIA;Ll;0;L;1F21 0300;;;;N;;;1F2B;;1F2B 1F24;GREEK SMALL LETTER ETA WITH PSILI AND OXIA;Ll;0;L;1F20 0301;;;;N;;;1F2C;;1F2C 1F25;GREEK SMALL LETTER ETA WITH DASIA AND OXIA;Ll;0;L;1F21 0301;;;;N;;;1F2D;;1F2D 1F26;GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI;Ll;0;L;1F20 0342;;;;N;;;1F2E;;1F2E 1F27;GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI;Ll;0;L;1F21 0342;;;;N;;;1F2F;;1F2F 1F28;GREEK CAPITAL LETTER ETA WITH PSILI;Lu;0;L;0397 0313;;;;N;;;;1F20; 1F29;GREEK CAPITAL LETTER ETA WITH DASIA;Lu;0;L;0397 0314;;;;N;;;;1F21; 1F2A;GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA;Lu;0;L;1F28 0300;;;;N;;;;1F22; 1F2B;GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA;Lu;0;L;1F29 0300;;;;N;;;;1F23; 1F2C;GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA;Lu;0;L;1F28 0301;;;;N;;;;1F24; 1F2D;GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA;Lu;0;L;1F29 0301;;;;N;;;;1F25; 1F2E;GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI;Lu;0;L;1F28 0342;;;;N;;;;1F26; 1F2F;GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI;Lu;0;L;1F29 0342;;;;N;;;;1F27; 1F30;GREEK SMALL LETTER IOTA WITH PSILI;Ll;0;L;03B9 0313;;;;N;;;1F38;;1F38 1F31;GREEK SMALL LETTER IOTA WITH DASIA;Ll;0;L;03B9 0314;;;;N;;;1F39;;1F39 1F32;GREEK SMALL LETTER IOTA WITH PSILI AND VARIA;Ll;0;L;1F30 0300;;;;N;;;1F3A;;1F3A 1F33;GREEK SMALL LETTER IOTA WITH DASIA AND VARIA;Ll;0;L;1F31 0300;;;;N;;;1F3B;;1F3B 1F34;GREEK SMALL LETTER IOTA WITH PSILI AND OXIA;Ll;0;L;1F30 0301;;;;N;;;1F3C;;1F3C 1F35;GREEK SMALL LETTER IOTA WITH DASIA AND OXIA;Ll;0;L;1F31 0301;;;;N;;;1F3D;;1F3D 1F36;GREEK SMALL LETTER IOTA WITH PSILI AND PERISPOMENI;Ll;0;L;1F30 0342;;;;N;;;1F3E;;1F3E 1F37;GREEK SMALL LETTER IOTA WITH DASIA AND PERISPOMENI;Ll;0;L;1F31 0342;;;;N;;;1F3F;;1F3F 1F38;GREEK CAPITAL LETTER IOTA WITH PSILI;Lu;0;L;0399 0313;;;;N;;;;1F30; 1F39;GREEK CAPITAL LETTER IOTA WITH DASIA;Lu;0;L;0399 0314;;;;N;;;;1F31; 1F3A;GREEK CAPITAL LETTER IOTA WITH PSILI AND VARIA;Lu;0;L;1F38 0300;;;;N;;;;1F32; 1F3B;GREEK CAPITAL LETTER IOTA WITH DASIA AND VARIA;Lu;0;L;1F39 0300;;;;N;;;;1F33; 1F3C;GREEK CAPITAL LETTER IOTA WITH PSILI AND OXIA;Lu;0;L;1F38 0301;;;;N;;;;1F34; 1F3D;GREEK CAPITAL LETTER IOTA WITH DASIA AND OXIA;Lu;0;L;1F39 0301;;;;N;;;;1F35; 1F3E;GREEK CAPITAL LETTER IOTA WITH PSILI AND PERISPOMENI;Lu;0;L;1F38 0342;;;;N;;;;1F36; 1F3F;GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI;Lu;0;L;1F39 0342;;;;N;;;;1F37; 1F40;GREEK SMALL LETTER OMICRON WITH PSILI;Ll;0;L;03BF 0313;;;;N;;;1F48;;1F48 1F41;GREEK SMALL LETTER OMICRON WITH DASIA;Ll;0;L;03BF 0314;;;;N;;;1F49;;1F49 1F42;GREEK SMALL LETTER OMICRON WITH PSILI AND VARIA;Ll;0;L;1F40 0300;;;;N;;;1F4A;;1F4A 1F43;GREEK SMALL LETTER OMICRON WITH DASIA AND VARIA;Ll;0;L;1F41 0300;;;;N;;;1F4B;;1F4B 1F44;GREEK SMALL LETTER OMICRON WITH PSILI AND OXIA;Ll;0;L;1F40 0301;;;;N;;;1F4C;;1F4C 1F45;GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA;Ll;0;L;1F41 0301;;;;N;;;1F4D;;1F4D 1F48;GREEK CAPITAL LETTER OMICRON WITH PSILI;Lu;0;L;039F 0313;;;;N;;;;1F40; 1F49;GREEK CAPITAL LETTER OMICRON WITH DASIA;Lu;0;L;039F 0314;;;;N;;;;1F41; 1F4A;GREEK CAPITAL LETTER OMICRON WITH PSILI AND VARIA;Lu;0;L;1F48 0300;;;;N;;;;1F42; 1F4B;GREEK CAPITAL LETTER OMICRON WITH DASIA AND VARIA;Lu;0;L;1F49 0300;;;;N;;;;1F43; 1F4C;GREEK CAPITAL LETTER OMICRON WITH PSILI AND OXIA;Lu;0;L;1F48 0301;;;;N;;;;1F44; 1F4D;GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA;Lu;0;L;1F49 0301;;;;N;;;;1F45; 1F50;GREEK SMALL LETTER UPSILON WITH PSILI;Ll;0;L;03C5 0313;;;;N;;;;; 1F51;GREEK SMALL LETTER UPSILON WITH DASIA;Ll;0;L;03C5 0314;;;;N;;;1F59;;1F59 1F52;GREEK SMALL LETTER UPSILON WITH PSILI AND VARIA;Ll;0;L;1F50 0300;;;;N;;;;; 1F53;GREEK SMALL LETTER UPSILON WITH DASIA AND VARIA;Ll;0;L;1F51 0300;;;;N;;;1F5B;;1F5B 1F54;GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA;Ll;0;L;1F50 0301;;;;N;;;;; 1F55;GREEK SMALL LETTER UPSILON WITH DASIA AND OXIA;Ll;0;L;1F51 0301;;;;N;;;1F5D;;1F5D 1F56;GREEK SMALL LETTER UPSILON WITH PSILI AND PERISPOMENI;Ll;0;L;1F50 0342;;;;N;;;;; 1F57;GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI;Ll;0;L;1F51 0342;;;;N;;;1F5F;;1F5F 1F59;GREEK CAPITAL LETTER UPSILON WITH DASIA;Lu;0;L;03A5 0314;;;;N;;;;1F51; 1F5B;GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA;Lu;0;L;1F59 0300;;;;N;;;;1F53; 1F5D;GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA;Lu;0;L;1F59 0301;;;;N;;;;1F55; 1F5F;GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI;Lu;0;L;1F59 0342;;;;N;;;;1F57; 1F60;GREEK SMALL LETTER OMEGA WITH PSILI;Ll;0;L;03C9 0313;;;;N;;;1F68;;1F68 1F61;GREEK SMALL LETTER OMEGA WITH DASIA;Ll;0;L;03C9 0314;;;;N;;;1F69;;1F69 1F62;GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA;Ll;0;L;1F60 0300;;;;N;;;1F6A;;1F6A 1F63;GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA;Ll;0;L;1F61 0300;;;;N;;;1F6B;;1F6B 1F64;GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA;Ll;0;L;1F60 0301;;;;N;;;1F6C;;1F6C 1F65;GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA;Ll;0;L;1F61 0301;;;;N;;;1F6D;;1F6D 1F66;GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI;Ll;0;L;1F60 0342;;;;N;;;1F6E;;1F6E 1F67;GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI;Ll;0;L;1F61 0342;;;;N;;;1F6F;;1F6F 1F68;GREEK CAPITAL LETTER OMEGA WITH PSILI;Lu;0;L;03A9 0313;;;;N;;;;1F60; 1F69;GREEK CAPITAL LETTER OMEGA WITH DASIA;Lu;0;L;03A9 0314;;;;N;;;;1F61; 1F6A;GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA;Lu;0;L;1F68 0300;;;;N;;;;1F62; 1F6B;GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA;Lu;0;L;1F69 0300;;;;N;;;;1F63; 1F6C;GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA;Lu;0;L;1F68 0301;;;;N;;;;1F64; 1F6D;GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA;Lu;0;L;1F69 0301;;;;N;;;;1F65; 1F6E;GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI;Lu;0;L;1F68 0342;;;;N;;;;1F66; 1F6F;GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI;Lu;0;L;1F69 0342;;;;N;;;;1F67; 1F70;GREEK SMALL LETTER ALPHA WITH VARIA;Ll;0;L;03B1 0300;;;;N;;;1FBA;;1FBA 1F71;GREEK SMALL LETTER ALPHA WITH OXIA;Ll;0;L;03AC;;;;N;;;1FBB;;1FBB 1F72;GREEK SMALL LETTER EPSILON WITH VARIA;Ll;0;L;03B5 0300;;;;N;;;1FC8;;1FC8 1F73;GREEK SMALL LETTER EPSILON WITH OXIA;Ll;0;L;03AD;;;;N;;;1FC9;;1FC9 1F74;GREEK SMALL LETTER ETA WITH VARIA;Ll;0;L;03B7 0300;;;;N;;;1FCA;;1FCA 1F75;GREEK SMALL LETTER ETA WITH OXIA;Ll;0;L;03AE;;;;N;;;1FCB;;1FCB 1F76;GREEK SMALL LETTER IOTA WITH VARIA;Ll;0;L;03B9 0300;;;;N;;;1FDA;;1FDA 1F77;GREEK SMALL LETTER IOTA WITH OXIA;Ll;0;L;03AF;;;;N;;;1FDB;;1FDB 1F78;GREEK SMALL LETTER OMICRON WITH VARIA;Ll;0;L;03BF 0300;;;;N;;;1FF8;;1FF8 1F79;GREEK SMALL LETTER OMICRON WITH OXIA;Ll;0;L;03CC;;;;N;;;1FF9;;1FF9 1F7A;GREEK SMALL LETTER UPSILON WITH VARIA;Ll;0;L;03C5 0300;;;;N;;;1FEA;;1FEA 1F7B;GREEK SMALL LETTER UPSILON WITH OXIA;Ll;0;L;03CD;;;;N;;;1FEB;;1FEB 1F7C;GREEK SMALL LETTER OMEGA WITH VARIA;Ll;0;L;03C9 0300;;;;N;;;1FFA;;1FFA 1F7D;GREEK SMALL LETTER OMEGA WITH OXIA;Ll;0;L;03CE;;;;N;;;1FFB;;1FFB 1F80;GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI;Ll;0;L;1F00 0345;;;;N;;;1F88;;1F88 1F81;GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI;Ll;0;L;1F01 0345;;;;N;;;1F89;;1F89 1F82;GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA AND YPOGEGRAMMENI;Ll;0;L;1F02 0345;;;;N;;;1F8A;;1F8A 1F83;GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA AND YPOGEGRAMMENI;Ll;0;L;1F03 0345;;;;N;;;1F8B;;1F8B 1F84;GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA AND YPOGEGRAMMENI;Ll;0;L;1F04 0345;;;;N;;;1F8C;;1F8C 1F85;GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA AND YPOGEGRAMMENI;Ll;0;L;1F05 0345;;;;N;;;1F8D;;1F8D 1F86;GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI;Ll;0;L;1F06 0345;;;;N;;;1F8E;;1F8E 1F87;GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI;Ll;0;L;1F07 0345;;;;N;;;1F8F;;1F8F 1F88;GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI;Lt;0;L;1F08 0345;;;;N;;;;1F80; 1F89;GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI;Lt;0;L;1F09 0345;;;;N;;;;1F81; 1F8A;GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI;Lt;0;L;1F0A 0345;;;;N;;;;1F82; 1F8B;GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI;Lt;0;L;1F0B 0345;;;;N;;;;1F83; 1F8C;GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI;Lt;0;L;1F0C 0345;;;;N;;;;1F84; 1F8D;GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI;Lt;0;L;1F0D 0345;;;;N;;;;1F85; 1F8E;GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI;Lt;0;L;1F0E 0345;;;;N;;;;1F86; 1F8F;GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI;Lt;0;L;1F0F 0345;;;;N;;;;1F87; 1F90;GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI;Ll;0;L;1F20 0345;;;;N;;;1F98;;1F98 1F91;GREEK SMALL LETTER ETA WITH DASIA AND YPOGEGRAMMENI;Ll;0;L;1F21 0345;;;;N;;;1F99;;1F99 1F92;GREEK SMALL LETTER ETA WITH PSILI AND VARIA AND YPOGEGRAMMENI;Ll;0;L;1F22 0345;;;;N;;;1F9A;;1F9A 1F93;GREEK SMALL LETTER ETA WITH DASIA AND VARIA AND YPOGEGRAMMENI;Ll;0;L;1F23 0345;;;;N;;;1F9B;;1F9B 1F94;GREEK SMALL LETTER ETA WITH PSILI AND OXIA AND YPOGEGRAMMENI;Ll;0;L;1F24 0345;;;;N;;;1F9C;;1F9C 1F95;GREEK SMALL LETTER ETA WITH DASIA AND OXIA AND YPOGEGRAMMENI;Ll;0;L;1F25 0345;;;;N;;;1F9D;;1F9D 1F96;GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI;Ll;0;L;1F26 0345;;;;N;;;1F9E;;1F9E 1F97;GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI;Ll;0;L;1F27 0345;;;;N;;;1F9F;;1F9F 1F98;GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI;Lt;0;L;1F28 0345;;;;N;;;;1F90; 1F99;GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI;Lt;0;L;1F29 0345;;;;N;;;;1F91; 1F9A;GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI;Lt;0;L;1F2A 0345;;;;N;;;;1F92; 1F9B;GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI;Lt;0;L;1F2B 0345;;;;N;;;;1F93; 1F9C;GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI;Lt;0;L;1F2C 0345;;;;N;;;;1F94; 1F9D;GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI;Lt;0;L;1F2D 0345;;;;N;;;;1F95; 1F9E;GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI;Lt;0;L;1F2E 0345;;;;N;;;;1F96; 1F9F;GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI;Lt;0;L;1F2F 0345;;;;N;;;;1F97; 1FA0;GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI;Ll;0;L;1F60 0345;;;;N;;;1FA8;;1FA8 1FA1;GREEK SMALL LETTER OMEGA WITH DASIA AND YPOGEGRAMMENI;Ll;0;L;1F61 0345;;;;N;;;1FA9;;1FA9 1FA2;GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA AND YPOGEGRAMMENI;Ll;0;L;1F62 0345;;;;N;;;1FAA;;1FAA 1FA3;GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA AND YPOGEGRAMMENI;Ll;0;L;1F63 0345;;;;N;;;1FAB;;1FAB 1FA4;GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA AND YPOGEGRAMMENI;Ll;0;L;1F64 0345;;;;N;;;1FAC;;1FAC 1FA5;GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA AND YPOGEGRAMMENI;Ll;0;L;1F65 0345;;;;N;;;1FAD;;1FAD 1FA6;GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI;Ll;0;L;1F66 0345;;;;N;;;1FAE;;1FAE 1FA7;GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI;Ll;0;L;1F67 0345;;;;N;;;1FAF;;1FAF 1FA8;GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI;Lt;0;L;1F68 0345;;;;N;;;;1FA0; 1FA9;GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI;Lt;0;L;1F69 0345;;;;N;;;;1FA1; 1FAA;GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI;Lt;0;L;1F6A 0345;;;;N;;;;1FA2; 1FAB;GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI;Lt;0;L;1F6B 0345;;;;N;;;;1FA3; 1FAC;GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI;Lt;0;L;1F6C 0345;;;;N;;;;1FA4; 1FAD;GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI;Lt;0;L;1F6D 0345;;;;N;;;;1FA5; 1FAE;GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI;Lt;0;L;1F6E 0345;;;;N;;;;1FA6; 1FAF;GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI;Lt;0;L;1F6F 0345;;;;N;;;;1FA7; 1FB0;GREEK SMALL LETTER ALPHA WITH VRACHY;Ll;0;L;03B1 0306;;;;N;;;1FB8;;1FB8 1FB1;GREEK SMALL LETTER ALPHA WITH MACRON;Ll;0;L;03B1 0304;;;;N;;;1FB9;;1FB9 1FB2;GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI;Ll;0;L;1F70 0345;;;;N;;;;; 1FB3;GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI;Ll;0;L;03B1 0345;;;;N;;;1FBC;;1FBC 1FB4;GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI;Ll;0;L;03AC 0345;;;;N;;;;; 1FB6;GREEK SMALL LETTER ALPHA WITH PERISPOMENI;Ll;0;L;03B1 0342;;;;N;;;;; 1FB7;GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI;Ll;0;L;1FB6 0345;;;;N;;;;; 1FB8;GREEK CAPITAL LETTER ALPHA WITH VRACHY;Lu;0;L;0391 0306;;;;N;;;;1FB0; 1FB9;GREEK CAPITAL LETTER ALPHA WITH MACRON;Lu;0;L;0391 0304;;;;N;;;;1FB1; 1FBA;GREEK CAPITAL LETTER ALPHA WITH VARIA;Lu;0;L;0391 0300;;;;N;;;;1F70; 1FBB;GREEK CAPITAL LETTER ALPHA WITH OXIA;Lu;0;L;0386;;;;N;;;;1F71; 1FBC;GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI;Lt;0;L;0391 0345;;;;N;;;;1FB3; 1FBD;GREEK KORONIS;Sk;0;ON; 0020 0313;;;;N;;;;; 1FBE;GREEK PROSGEGRAMMENI;Ll;0;L;03B9;;;;N;;;0399;;0399 1FBF;GREEK PSILI;Sk;0;ON; 0020 0313;;;;N;;;;; 1FC0;GREEK PERISPOMENI;Sk;0;ON; 0020 0342;;;;N;;;;; 1FC1;GREEK DIALYTIKA AND PERISPOMENI;Sk;0;ON;00A8 0342;;;;N;;;;; 1FC2;GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI;Ll;0;L;1F74 0345;;;;N;;;;; 1FC3;GREEK SMALL LETTER ETA WITH YPOGEGRAMMENI;Ll;0;L;03B7 0345;;;;N;;;1FCC;;1FCC 1FC4;GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI;Ll;0;L;03AE 0345;;;;N;;;;; 1FC6;GREEK SMALL LETTER ETA WITH PERISPOMENI;Ll;0;L;03B7 0342;;;;N;;;;; 1FC7;GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI;Ll;0;L;1FC6 0345;;;;N;;;;; 1FC8;GREEK CAPITAL LETTER EPSILON WITH VARIA;Lu;0;L;0395 0300;;;;N;;;;1F72; 1FC9;GREEK CAPITAL LETTER EPSILON WITH OXIA;Lu;0;L;0388;;;;N;;;;1F73; 1FCA;GREEK CAPITAL LETTER ETA WITH VARIA;Lu;0;L;0397 0300;;;;N;;;;1F74; 1FCB;GREEK CAPITAL LETTER ETA WITH OXIA;Lu;0;L;0389;;;;N;;;;1F75; 1FCC;GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI;Lt;0;L;0397 0345;;;;N;;;;1FC3; 1FCD;GREEK PSILI AND VARIA;Sk;0;ON;1FBF 0300;;;;N;;;;; 1FCE;GREEK PSILI AND OXIA;Sk;0;ON;1FBF 0301;;;;N;;;;; 1FCF;GREEK PSILI AND PERISPOMENI;Sk;0;ON;1FBF 0342;;;;N;;;;; 1FD0;GREEK SMALL LETTER IOTA WITH VRACHY;Ll;0;L;03B9 0306;;;;N;;;1FD8;;1FD8 1FD1;GREEK SMALL LETTER IOTA WITH MACRON;Ll;0;L;03B9 0304;;;;N;;;1FD9;;1FD9 1FD2;GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA;Ll;0;L;03CA 0300;;;;N;;;;; 1FD3;GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA;Ll;0;L;0390;;;;N;;;;; 1FD6;GREEK SMALL LETTER IOTA WITH PERISPOMENI;Ll;0;L;03B9 0342;;;;N;;;;; 1FD7;GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI;Ll;0;L;03CA 0342;;;;N;;;;; 1FD8;GREEK CAPITAL LETTER IOTA WITH VRACHY;Lu;0;L;0399 0306;;;;N;;;;1FD0; 1FD9;GREEK CAPITAL LETTER IOTA WITH MACRON;Lu;0;L;0399 0304;;;;N;;;;1FD1; 1FDA;GREEK CAPITAL LETTER IOTA WITH VARIA;Lu;0;L;0399 0300;;;;N;;;;1F76; 1FDB;GREEK CAPITAL LETTER IOTA WITH OXIA;Lu;0;L;038A;;;;N;;;;1F77; 1FDD;GREEK DASIA AND VARIA;Sk;0;ON;1FFE 0300;;;;N;;;;; 1FDE;GREEK DASIA AND OXIA;Sk;0;ON;1FFE 0301;;;;N;;;;; 1FDF;GREEK DASIA AND PERISPOMENI;Sk;0;ON;1FFE 0342;;;;N;;;;; 1FE0;GREEK SMALL LETTER UPSILON WITH VRACHY;Ll;0;L;03C5 0306;;;;N;;;1FE8;;1FE8 1FE1;GREEK SMALL LETTER UPSILON WITH MACRON;Ll;0;L;03C5 0304;;;;N;;;1FE9;;1FE9 1FE2;GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA;Ll;0;L;03CB 0300;;;;N;;;;; 1FE3;GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA;Ll;0;L;03B0;;;;N;;;;; 1FE4;GREEK SMALL LETTER RHO WITH PSILI;Ll;0;L;03C1 0313;;;;N;;;;; 1FE5;GREEK SMALL LETTER RHO WITH DASIA;Ll;0;L;03C1 0314;;;;N;;;1FEC;;1FEC 1FE6;GREEK SMALL LETTER UPSILON WITH PERISPOMENI;Ll;0;L;03C5 0342;;;;N;;;;; 1FE7;GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI;Ll;0;L;03CB 0342;;;;N;;;;; 1FE8;GREEK CAPITAL LETTER UPSILON WITH VRACHY;Lu;0;L;03A5 0306;;;;N;;;;1FE0; 1FE9;GREEK CAPITAL LETTER UPSILON WITH MACRON;Lu;0;L;03A5 0304;;;;N;;;;1FE1; 1FEA;GREEK CAPITAL LETTER UPSILON WITH VARIA;Lu;0;L;03A5 0300;;;;N;;;;1F7A; 1FEB;GREEK CAPITAL LETTER UPSILON WITH OXIA;Lu;0;L;038E;;;;N;;;;1F7B; 1FEC;GREEK CAPITAL LETTER RHO WITH DASIA;Lu;0;L;03A1 0314;;;;N;;;;1FE5; 1FED;GREEK DIALYTIKA AND VARIA;Sk;0;ON;00A8 0300;;;;N;;;;; 1FEE;GREEK DIALYTIKA AND OXIA;Sk;0;ON;0385;;;;N;;;;; 1FEF;GREEK VARIA;Sk;0;ON;0060;;;;N;;;;; 1FF2;GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI;Ll;0;L;1F7C 0345;;;;N;;;;; 1FF3;GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI;Ll;0;L;03C9 0345;;;;N;;;1FFC;;1FFC 1FF4;GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI;Ll;0;L;03CE 0345;;;;N;;;;; 1FF6;GREEK SMALL LETTER OMEGA WITH PERISPOMENI;Ll;0;L;03C9 0342;;;;N;;;;; 1FF7;GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI;Ll;0;L;1FF6 0345;;;;N;;;;; 1FF8;GREEK CAPITAL LETTER OMICRON WITH VARIA;Lu;0;L;039F 0300;;;;N;;;;1F78; 1FF9;GREEK CAPITAL LETTER OMICRON WITH OXIA;Lu;0;L;038C;;;;N;;;;1F79; 1FFA;GREEK CAPITAL LETTER OMEGA WITH VARIA;Lu;0;L;03A9 0300;;;;N;;;;1F7C; 1FFB;GREEK CAPITAL LETTER OMEGA WITH OXIA;Lu;0;L;038F;;;;N;;;;1F7D; 1FFC;GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI;Lt;0;L;03A9 0345;;;;N;;;;1FF3; 1FFD;GREEK OXIA;Sk;0;ON;00B4;;;;N;;;;; 1FFE;GREEK DASIA;Sk;0;ON; 0020 0314;;;;N;;;;; 2000;EN QUAD;Zs;0;WS;2002;;;;N;;;;; 2001;EM QUAD;Zs;0;WS;2003;;;;N;;;;; 2002;EN SPACE;Zs;0;WS; 0020;;;;N;;;;; 2003;EM SPACE;Zs;0;WS; 0020;;;;N;;;;; 2004;THREE-PER-EM SPACE;Zs;0;WS; 0020;;;;N;;;;; 2005;FOUR-PER-EM SPACE;Zs;0;WS; 0020;;;;N;;;;; 2006;SIX-PER-EM SPACE;Zs;0;WS; 0020;;;;N;;;;; 2007;FIGURE SPACE;Zs;0;WS; 0020;;;;N;;;;; 2008;PUNCTUATION SPACE;Zs;0;WS; 0020;;;;N;;;;; 2009;THIN SPACE;Zs;0;WS; 0020;;;;N;;;;; 200A;HAIR SPACE;Zs;0;WS; 0020;;;;N;;;;; 200B;ZERO WIDTH SPACE;Cf;0;BN;;;;;N;;;;; 200C;ZERO WIDTH NON-JOINER;Cf;0;BN;;;;;N;;;;; 200D;ZERO WIDTH JOINER;Cf;0;BN;;;;;N;;;;; 200E;LEFT-TO-RIGHT MARK;Cf;0;L;;;;;N;;;;; 200F;RIGHT-TO-LEFT MARK;Cf;0;R;;;;;N;;;;; 2010;HYPHEN;Pd;0;ON;;;;;N;;;;; 2011;NON-BREAKING HYPHEN;Pd;0;ON; 2010;;;;N;;;;; 2012;FIGURE DASH;Pd;0;ON;;;;;N;;;;; 2013;EN DASH;Pd;0;ON;;;;;N;;;;; 2014;EM DASH;Pd;0;ON;;;;;N;;;;; 2015;HORIZONTAL BAR;Pd;0;ON;;;;;N;QUOTATION DASH;;;; 2016;DOUBLE VERTICAL LINE;Po;0;ON;;;;;N;DOUBLE VERTICAL BAR;;;; 2017;DOUBLE LOW LINE;Po;0;ON; 0020 0333;;;;N;SPACING DOUBLE UNDERSCORE;;;; 2018;LEFT SINGLE QUOTATION MARK;Pi;0;ON;;;;;N;SINGLE TURNED COMMA QUOTATION MARK;;;; 2019;RIGHT SINGLE QUOTATION MARK;Pf;0;ON;;;;;N;SINGLE COMMA QUOTATION MARK;;;; 201A;SINGLE LOW-9 QUOTATION MARK;Ps;0;ON;;;;;N;LOW SINGLE COMMA QUOTATION MARK;;;; 201B;SINGLE HIGH-REVERSED-9 QUOTATION MARK;Pi;0;ON;;;;;N;SINGLE REVERSED COMMA QUOTATION MARK;;;; 201C;LEFT DOUBLE QUOTATION MARK;Pi;0;ON;;;;;N;DOUBLE TURNED COMMA QUOTATION MARK;;;; 201D;RIGHT DOUBLE QUOTATION MARK;Pf;0;ON;;;;;N;DOUBLE COMMA QUOTATION MARK;;;; 201E;DOUBLE LOW-9 QUOTATION MARK;Ps;0;ON;;;;;N;LOW DOUBLE COMMA QUOTATION MARK;;;; 201F;DOUBLE HIGH-REVERSED-9 QUOTATION MARK;Pi;0;ON;;;;;N;DOUBLE REVERSED COMMA QUOTATION MARK;;;; 2020;DAGGER;Po;0;ON;;;;;N;;;;; 2021;DOUBLE DAGGER;Po;0;ON;;;;;N;;;;; 2022;BULLET;Po;0;ON;;;;;N;;;;; 2023;TRIANGULAR BULLET;Po;0;ON;;;;;N;;;;; 2024;ONE DOT LEADER;Po;0;ON; 002E;;;;N;;;;; 2025;TWO DOT LEADER;Po;0;ON; 002E 002E;;;;N;;;;; 2026;HORIZONTAL ELLIPSIS;Po;0;ON; 002E 002E 002E;;;;N;;;;; 2027;HYPHENATION POINT;Po;0;ON;;;;;N;;;;; 2028;LINE SEPARATOR;Zl;0;WS;;;;;N;;;;; 2029;PARAGRAPH SEPARATOR;Zp;0;B;;;;;N;;;;; 202A;LEFT-TO-RIGHT EMBEDDING;Cf;0;LRE;;;;;N;;;;; 202B;RIGHT-TO-LEFT EMBEDDING;Cf;0;RLE;;;;;N;;;;; 202C;POP DIRECTIONAL FORMATTING;Cf;0;PDF;;;;;N;;;;; 202D;LEFT-TO-RIGHT OVERRIDE;Cf;0;LRO;;;;;N;;;;; 202E;RIGHT-TO-LEFT OVERRIDE;Cf;0;RLO;;;;;N;;;;; 202F;NARROW NO-BREAK SPACE;Zs;0;CS; 0020;;;;N;;;;; 2030;PER MILLE SIGN;Po;0;ET;;;;;N;;;;; 2031;PER TEN THOUSAND SIGN;Po;0;ET;;;;;N;;;;; 2032;PRIME;Po;0;ET;;;;;N;;;;; 2033;DOUBLE PRIME;Po;0;ET; 2032 2032;;;;N;;;;; 2034;TRIPLE PRIME;Po;0;ET; 2032 2032 2032;;;;N;;;;; 2035;REVERSED PRIME;Po;0;ON;;;;;N;;;;; 2036;REVERSED DOUBLE PRIME;Po;0;ON; 2035 2035;;;;N;;;;; 2037;REVERSED TRIPLE PRIME;Po;0;ON; 2035 2035 2035;;;;N;;;;; 2038;CARET;Po;0;ON;;;;;N;;;;; 2039;SINGLE LEFT-POINTING ANGLE QUOTATION MARK;Pi;0;ON;;;;;Y;LEFT POINTING SINGLE GUILLEMET;;;; 203A;SINGLE RIGHT-POINTING ANGLE QUOTATION MARK;Pf;0;ON;;;;;Y;RIGHT POINTING SINGLE GUILLEMET;;;; 203B;REFERENCE MARK;Po;0;ON;;;;;N;;;;; 203C;DOUBLE EXCLAMATION MARK;Po;0;ON; 0021 0021;;;;N;;;;; 203D;INTERROBANG;Po;0;ON;;;;;N;;;;; 203E;OVERLINE;Po;0;ON; 0020 0305;;;;N;SPACING OVERSCORE;;;; 203F;UNDERTIE;Pc;0;ON;;;;;N;;;;; 2040;CHARACTER TIE;Pc;0;ON;;;;;N;;;;; 2041;CARET INSERTION POINT;Po;0;ON;;;;;N;;;;; 2042;ASTERISM;Po;0;ON;;;;;N;;;;; 2043;HYPHEN BULLET;Po;0;ON;;;;;N;;;;; 2044;FRACTION SLASH;Sm;0;CS;;;;;N;;;;; 2045;LEFT SQUARE BRACKET WITH QUILL;Ps;0;ON;;;;;Y;;;;; 2046;RIGHT SQUARE BRACKET WITH QUILL;Pe;0;ON;;;;;Y;;;;; 2047;DOUBLE QUESTION MARK;Po;0;ON; 003F 003F;;;;N;;;;; 2048;QUESTION EXCLAMATION MARK;Po;0;ON; 003F 0021;;;;N;;;;; 2049;EXCLAMATION QUESTION MARK;Po;0;ON; 0021 003F;;;;N;;;;; 204A;TIRONIAN SIGN ET;Po;0;ON;;;;;N;;;;; 204B;REVERSED PILCROW SIGN;Po;0;ON;;;;;N;;;;; 204C;BLACK LEFTWARDS BULLET;Po;0;ON;;;;;N;;;;; 204D;BLACK RIGHTWARDS BULLET;Po;0;ON;;;;;N;;;;; 204E;LOW ASTERISK;Po;0;ON;;;;;N;;;;; 204F;REVERSED SEMICOLON;Po;0;ON;;;;;N;;;;; 2050;CLOSE UP;Po;0;ON;;;;;N;;;;; 2051;TWO ASTERISKS ALIGNED VERTICALLY;Po;0;ON;;;;;N;;;;; 2052;COMMERCIAL MINUS SIGN;Sm;0;ON;;;;;N;;;;; 2053;SWUNG DASH;Po;0;ON;;;;;N;;;;; 2054;INVERTED UNDERTIE;Pc;0;ON;;;;;N;;;;; 2055;FLOWER PUNCTUATION MARK;Po;0;ON;;;;;N;;;;; 2056;THREE DOT PUNCTUATION;Po;0;ON;;;;;N;;;;; 2057;QUADRUPLE PRIME;Po;0;ON; 2032 2032 2032 2032;;;;N;;;;; 2058;FOUR DOT PUNCTUATION;Po;0;ON;;;;;N;;;;; 2059;FIVE DOT PUNCTUATION;Po;0;ON;;;;;N;;;;; 205A;TWO DOT PUNCTUATION;Po;0;ON;;;;;N;;;;; 205B;FOUR DOT MARK;Po;0;ON;;;;;N;;;;; 205C;DOTTED CROSS;Po;0;ON;;;;;N;;;;; 205D;TRICOLON;Po;0;ON;;;;;N;;;;; 205E;VERTICAL FOUR DOTS;Po;0;ON;;;;;N;;;;; 205F;MEDIUM MATHEMATICAL SPACE;Zs;0;WS; 0020;;;;N;;;;; 2060;WORD JOINER;Cf;0;BN;;;;;N;;;;; 2061;FUNCTION APPLICATION;Cf;0;BN;;;;;N;;;;; 2062;INVISIBLE TIMES;Cf;0;BN;;;;;N;;;;; 2063;INVISIBLE SEPARATOR;Cf;0;BN;;;;;N;;;;; 2064;INVISIBLE PLUS;Cf;0;BN;;;;;N;;;;; 2066;LEFT-TO-RIGHT ISOLATE;Cf;0;LRI;;;;;N;;;;; 2067;RIGHT-TO-LEFT ISOLATE;Cf;0;RLI;;;;;N;;;;; 2068;FIRST STRONG ISOLATE;Cf;0;FSI;;;;;N;;;;; 2069;POP DIRECTIONAL ISOLATE;Cf;0;PDI;;;;;N;;;;; 206A;INHIBIT SYMMETRIC SWAPPING;Cf;0;BN;;;;;N;;;;; 206B;ACTIVATE SYMMETRIC SWAPPING;Cf;0;BN;;;;;N;;;;; 206C;INHIBIT ARABIC FORM SHAPING;Cf;0;BN;;;;;N;;;;; 206D;ACTIVATE ARABIC FORM SHAPING;Cf;0;BN;;;;;N;;;;; 206E;NATIONAL DIGIT SHAPES;Cf;0;BN;;;;;N;;;;; 206F;NOMINAL DIGIT SHAPES;Cf;0;BN;;;;;N;;;;; 2070;SUPERSCRIPT ZERO;No;0;EN; 0030;;0;0;N;SUPERSCRIPT DIGIT ZERO;;;; 2071;SUPERSCRIPT LATIN SMALL LETTER I;Lm;0;L; 0069;;;;N;;;;; 2074;SUPERSCRIPT FOUR;No;0;EN; 0034;;4;4;N;SUPERSCRIPT DIGIT FOUR;;;; 2075;SUPERSCRIPT FIVE;No;0;EN; 0035;;5;5;N;SUPERSCRIPT DIGIT FIVE;;;; 2076;SUPERSCRIPT SIX;No;0;EN; 0036;;6;6;N;SUPERSCRIPT DIGIT SIX;;;; 2077;SUPERSCRIPT SEVEN;No;0;EN; 0037;;7;7;N;SUPERSCRIPT DIGIT SEVEN;;;; 2078;SUPERSCRIPT EIGHT;No;0;EN; 0038;;8;8;N;SUPERSCRIPT DIGIT EIGHT;;;; 2079;SUPERSCRIPT NINE;No;0;EN; 0039;;9;9;N;SUPERSCRIPT DIGIT NINE;;;; 207A;SUPERSCRIPT PLUS SIGN;Sm;0;ES; 002B;;;;N;;;;; 207B;SUPERSCRIPT MINUS;Sm;0;ES; 2212;;;;N;SUPERSCRIPT HYPHEN-MINUS;;;; 207C;SUPERSCRIPT EQUALS SIGN;Sm;0;ON; 003D;;;;N;;;;; 207D;SUPERSCRIPT LEFT PARENTHESIS;Ps;0;ON; 0028;;;;Y;SUPERSCRIPT OPENING PARENTHESIS;;;; 207E;SUPERSCRIPT RIGHT PARENTHESIS;Pe;0;ON; 0029;;;;Y;SUPERSCRIPT CLOSING PARENTHESIS;;;; 207F;SUPERSCRIPT LATIN SMALL LETTER N;Lm;0;L; 006E;;;;N;;;;; 2080;SUBSCRIPT ZERO;No;0;EN; 0030;;0;0;N;SUBSCRIPT DIGIT ZERO;;;; 2081;SUBSCRIPT ONE;No;0;EN; 0031;;1;1;N;SUBSCRIPT DIGIT ONE;;;; 2082;SUBSCRIPT TWO;No;0;EN; 0032;;2;2;N;SUBSCRIPT DIGIT TWO;;;; 2083;SUBSCRIPT THREE;No;0;EN; 0033;;3;3;N;SUBSCRIPT DIGIT THREE;;;; 2084;SUBSCRIPT FOUR;No;0;EN; 0034;;4;4;N;SUBSCRIPT DIGIT FOUR;;;; 2085;SUBSCRIPT FIVE;No;0;EN; 0035;;5;5;N;SUBSCRIPT DIGIT FIVE;;;; 2086;SUBSCRIPT SIX;No;0;EN; 0036;;6;6;N;SUBSCRIPT DIGIT SIX;;;; 2087;SUBSCRIPT SEVEN;No;0;EN; 0037;;7;7;N;SUBSCRIPT DIGIT SEVEN;;;; 2088;SUBSCRIPT EIGHT;No;0;EN; 0038;;8;8;N;SUBSCRIPT DIGIT EIGHT;;;; 2089;SUBSCRIPT NINE;No;0;EN; 0039;;9;9;N;SUBSCRIPT DIGIT NINE;;;; 208A;SUBSCRIPT PLUS SIGN;Sm;0;ES; 002B;;;;N;;;;; 208B;SUBSCRIPT MINUS;Sm;0;ES; 2212;;;;N;SUBSCRIPT HYPHEN-MINUS;;;; 208C;SUBSCRIPT EQUALS SIGN;Sm;0;ON; 003D;;;;N;;;;; 208D;SUBSCRIPT LEFT PARENTHESIS;Ps;0;ON; 0028;;;;Y;SUBSCRIPT OPENING PARENTHESIS;;;; 208E;SUBSCRIPT RIGHT PARENTHESIS;Pe;0;ON; 0029;;;;Y;SUBSCRIPT CLOSING PARENTHESIS;;;; 2090;LATIN SUBSCRIPT SMALL LETTER A;Lm;0;L; 0061;;;;N;;;;; 2091;LATIN SUBSCRIPT SMALL LETTER E;Lm;0;L; 0065;;;;N;;;;; 2092;LATIN SUBSCRIPT SMALL LETTER O;Lm;0;L; 006F;;;;N;;;;; 2093;LATIN SUBSCRIPT SMALL LETTER X;Lm;0;L; 0078;;;;N;;;;; 2094;LATIN SUBSCRIPT SMALL LETTER SCHWA;Lm;0;L; 0259;;;;N;;;;; 2095;LATIN SUBSCRIPT SMALL LETTER H;Lm;0;L; 0068;;;;N;;;;; 2096;LATIN SUBSCRIPT SMALL LETTER K;Lm;0;L; 006B;;;;N;;;;; 2097;LATIN SUBSCRIPT SMALL LETTER L;Lm;0;L; 006C;;;;N;;;;; 2098;LATIN SUBSCRIPT SMALL LETTER M;Lm;0;L; 006D;;;;N;;;;; 2099;LATIN SUBSCRIPT SMALL LETTER N;Lm;0;L; 006E;;;;N;;;;; 209A;LATIN SUBSCRIPT SMALL LETTER P;Lm;0;L; 0070;;;;N;;;;; 209B;LATIN SUBSCRIPT SMALL LETTER S;Lm;0;L; 0073;;;;N;;;;; 209C;LATIN SUBSCRIPT SMALL LETTER T;Lm;0;L; 0074;;;;N;;;;; 20A0;EURO-CURRENCY SIGN;Sc;0;ET;;;;;N;;;;; 20A1;COLON SIGN;Sc;0;ET;;;;;N;;;;; 20A2;CRUZEIRO SIGN;Sc;0;ET;;;;;N;;;;; 20A3;FRENCH FRANC SIGN;Sc;0;ET;;;;;N;;;;; 20A4;LIRA SIGN;Sc;0;ET;;;;;N;;;;; 20A5;MILL SIGN;Sc;0;ET;;;;;N;;;;; 20A6;NAIRA SIGN;Sc;0;ET;;;;;N;;;;; 20A7;PESETA SIGN;Sc;0;ET;;;;;N;;;;; 20A8;RUPEE SIGN;Sc;0;ET; 0052 0073;;;;N;;;;; 20A9;WON SIGN;Sc;0;ET;;;;;N;;;;; 20AA;NEW SHEQEL SIGN;Sc;0;ET;;;;;N;;;;; 20AB;DONG SIGN;Sc;0;ET;;;;;N;;;;; 20AC;EURO SIGN;Sc;0;ET;;;;;N;;;;; 20AD;KIP SIGN;Sc;0;ET;;;;;N;;;;; 20AE;TUGRIK SIGN;Sc;0;ET;;;;;N;;;;; 20AF;DRACHMA SIGN;Sc;0;ET;;;;;N;;;;; 20B0;GERMAN PENNY SIGN;Sc;0;ET;;;;;N;;;;; 20B1;PESO SIGN;Sc;0;ET;;;;;N;;;;; 20B2;GUARANI SIGN;Sc;0;ET;;;;;N;;;;; 20B3;AUSTRAL SIGN;Sc;0;ET;;;;;N;;;;; 20B4;HRYVNIA SIGN;Sc;0;ET;;;;;N;;;;; 20B5;CEDI SIGN;Sc;0;ET;;;;;N;;;;; 20B6;LIVRE TOURNOIS SIGN;Sc;0;ET;;;;;N;;;;; 20B7;SPESMILO SIGN;Sc;0;ET;;;;;N;;;;; 20B8;TENGE SIGN;Sc;0;ET;;;;;N;;;;; 20B9;INDIAN RUPEE SIGN;Sc;0;ET;;;;;N;;;;; 20BA;TURKISH LIRA SIGN;Sc;0;ET;;;;;N;;;;; 20BB;NORDIC MARK SIGN;Sc;0;ET;;;;;N;;;;; 20BC;MANAT SIGN;Sc;0;ET;;;;;N;;;;; 20BD;RUBLE SIGN;Sc;0;ET;;;;;N;;;;; 20BE;LARI SIGN;Sc;0;ET;;;;;N;;;;; 20D0;COMBINING LEFT HARPOON ABOVE;Mn;230;NSM;;;;;N;NON-SPACING LEFT HARPOON ABOVE;;;; 20D1;COMBINING RIGHT HARPOON ABOVE;Mn;230;NSM;;;;;N;NON-SPACING RIGHT HARPOON ABOVE;;;; 20D2;COMBINING LONG VERTICAL LINE OVERLAY;Mn;1;NSM;;;;;N;NON-SPACING LONG VERTICAL BAR OVERLAY;;;; 20D3;COMBINING SHORT VERTICAL LINE OVERLAY;Mn;1;NSM;;;;;N;NON-SPACING SHORT VERTICAL BAR OVERLAY;;;; 20D4;COMBINING ANTICLOCKWISE ARROW ABOVE;Mn;230;NSM;;;;;N;NON-SPACING ANTICLOCKWISE ARROW ABOVE;;;; 20D5;COMBINING CLOCKWISE ARROW ABOVE;Mn;230;NSM;;;;;N;NON-SPACING CLOCKWISE ARROW ABOVE;;;; 20D6;COMBINING LEFT ARROW ABOVE;Mn;230;NSM;;;;;N;NON-SPACING LEFT ARROW ABOVE;;;; 20D7;COMBINING RIGHT ARROW ABOVE;Mn;230;NSM;;;;;N;NON-SPACING RIGHT ARROW ABOVE;;;; 20D8;COMBINING RING OVERLAY;Mn;1;NSM;;;;;N;NON-SPACING RING OVERLAY;;;; 20D9;COMBINING CLOCKWISE RING OVERLAY;Mn;1;NSM;;;;;N;NON-SPACING CLOCKWISE RING OVERLAY;;;; 20DA;COMBINING ANTICLOCKWISE RING OVERLAY;Mn;1;NSM;;;;;N;NON-SPACING ANTICLOCKWISE RING OVERLAY;;;; 20DB;COMBINING THREE DOTS ABOVE;Mn;230;NSM;;;;;N;NON-SPACING THREE DOTS ABOVE;;;; 20DC;COMBINING FOUR DOTS ABOVE;Mn;230;NSM;;;;;N;NON-SPACING FOUR DOTS ABOVE;;;; 20DD;COMBINING ENCLOSING CIRCLE;Me;0;NSM;;;;;N;ENCLOSING CIRCLE;;;; 20DE;COMBINING ENCLOSING SQUARE;Me;0;NSM;;;;;N;ENCLOSING SQUARE;;;; 20DF;COMBINING ENCLOSING DIAMOND;Me;0;NSM;;;;;N;ENCLOSING DIAMOND;;;; 20E0;COMBINING ENCLOSING CIRCLE BACKSLASH;Me;0;NSM;;;;;N;ENCLOSING CIRCLE SLASH;;;; 20E1;COMBINING LEFT RIGHT ARROW ABOVE;Mn;230;NSM;;;;;N;NON-SPACING LEFT RIGHT ARROW ABOVE;;;; 20E2;COMBINING ENCLOSING SCREEN;Me;0;NSM;;;;;N;;;;; 20E3;COMBINING ENCLOSING KEYCAP;Me;0;NSM;;;;;N;;;;; 20E4;COMBINING ENCLOSING UPWARD POINTING TRIANGLE;Me;0;NSM;;;;;N;;;;; 20E5;COMBINING REVERSE SOLIDUS OVERLAY;Mn;1;NSM;;;;;N;;;;; 20E6;COMBINING DOUBLE VERTICAL STROKE OVERLAY;Mn;1;NSM;;;;;N;;;;; 20E7;COMBINING ANNUITY SYMBOL;Mn;230;NSM;;;;;N;;;;; 20E8;COMBINING TRIPLE UNDERDOT;Mn;220;NSM;;;;;N;;;;; 20E9;COMBINING WIDE BRIDGE ABOVE;Mn;230;NSM;;;;;N;;;;; 20EA;COMBINING LEFTWARDS ARROW OVERLAY;Mn;1;NSM;;;;;N;;;;; 20EB;COMBINING LONG DOUBLE SOLIDUS OVERLAY;Mn;1;NSM;;;;;N;;;;; 20EC;COMBINING RIGHTWARDS HARPOON WITH BARB DOWNWARDS;Mn;220;NSM;;;;;N;;;;; 20ED;COMBINING LEFTWARDS HARPOON WITH BARB DOWNWARDS;Mn;220;NSM;;;;;N;;;;; 20EE;COMBINING LEFT ARROW BELOW;Mn;220;NSM;;;;;N;;;;; 20EF;COMBINING RIGHT ARROW BELOW;Mn;220;NSM;;;;;N;;;;; 20F0;COMBINING ASTERISK ABOVE;Mn;230;NSM;;;;;N;;;;; 2100;ACCOUNT OF;So;0;ON; 0061 002F 0063;;;;N;;;;; 2101;ADDRESSED TO THE SUBJECT;So;0;ON; 0061 002F 0073;;;;N;;;;; 2102;DOUBLE-STRUCK CAPITAL C;Lu;0;L; 0043;;;;N;DOUBLE-STRUCK C;;;; 2103;DEGREE CELSIUS;So;0;ON; 00B0 0043;;;;N;DEGREES CENTIGRADE;;;; 2104;CENTRE LINE SYMBOL;So;0;ON;;;;;N;C L SYMBOL;;;; 2105;CARE OF;So;0;ON; 0063 002F 006F;;;;N;;;;; 2106;CADA UNA;So;0;ON; 0063 002F 0075;;;;N;;;;; 2107;EULER CONSTANT;Lu;0;L; 0190;;;;N;EULERS;;;; 2108;SCRUPLE;So;0;ON;;;;;N;;;;; 2109;DEGREE FAHRENHEIT;So;0;ON; 00B0 0046;;;;N;DEGREES FAHRENHEIT;;;; 210A;SCRIPT SMALL G;Ll;0;L; 0067;;;;N;;;;; 210B;SCRIPT CAPITAL H;Lu;0;L; 0048;;;;N;SCRIPT H;;;; 210C;BLACK-LETTER CAPITAL H;Lu;0;L; 0048;;;;N;BLACK-LETTER H;;;; 210D;DOUBLE-STRUCK CAPITAL H;Lu;0;L; 0048;;;;N;DOUBLE-STRUCK H;;;; 210E;PLANCK CONSTANT;Ll;0;L; 0068;;;;N;;;;; 210F;PLANCK CONSTANT OVER TWO PI;Ll;0;L; 0127;;;;N;PLANCK CONSTANT OVER 2 PI;;;; 2110;SCRIPT CAPITAL I;Lu;0;L; 0049;;;;N;SCRIPT I;;;; 2111;BLACK-LETTER CAPITAL I;Lu;0;L; 0049;;;;N;BLACK-LETTER I;;;; 2112;SCRIPT CAPITAL L;Lu;0;L; 004C;;;;N;SCRIPT L;;;; 2113;SCRIPT SMALL L;Ll;0;L; 006C;;;;N;;;;; 2114;L B BAR SYMBOL;So;0;ON;;;;;N;;;;; 2115;DOUBLE-STRUCK CAPITAL N;Lu;0;L; 004E;;;;N;DOUBLE-STRUCK N;;;; 2116;NUMERO SIGN;So;0;ON; 004E 006F;;;;N;NUMERO;;;; 2117;SOUND RECORDING COPYRIGHT;So;0;ON;;;;;N;;;;; 2118;SCRIPT CAPITAL P;Sm;0;ON;;;;;N;SCRIPT P;;;; 2119;DOUBLE-STRUCK CAPITAL P;Lu;0;L; 0050;;;;N;DOUBLE-STRUCK P;;;; 211A;DOUBLE-STRUCK CAPITAL Q;Lu;0;L; 0051;;;;N;DOUBLE-STRUCK Q;;;; 211B;SCRIPT CAPITAL R;Lu;0;L; 0052;;;;N;SCRIPT R;;;; 211C;BLACK-LETTER CAPITAL R;Lu;0;L; 0052;;;;N;BLACK-LETTER R;;;; 211D;DOUBLE-STRUCK CAPITAL R;Lu;0;L; 0052;;;;N;DOUBLE-STRUCK R;;;; 211E;PRESCRIPTION TAKE;So;0;ON;;;;;N;;;;; 211F;RESPONSE;So;0;ON;;;;;N;;;;; 2120;SERVICE MARK;So;0;ON; 0053 004D;;;;N;;;;; 2121;TELEPHONE SIGN;So;0;ON; 0054 0045 004C;;;;N;T E L SYMBOL;;;; 2122;TRADE MARK SIGN;So;0;ON; 0054 004D;;;;N;TRADEMARK;;;; 2123;VERSICLE;So;0;ON;;;;;N;;;;; 2124;DOUBLE-STRUCK CAPITAL Z;Lu;0;L; 005A;;;;N;DOUBLE-STRUCK Z;;;; 2125;OUNCE SIGN;So;0;ON;;;;;N;OUNCE;;;; 2126;OHM SIGN;Lu;0;L;03A9;;;;N;OHM;;;03C9; 2127;INVERTED OHM SIGN;So;0;ON;;;;;N;MHO;;;; 2128;BLACK-LETTER CAPITAL Z;Lu;0;L; 005A;;;;N;BLACK-LETTER Z;;;; 2129;TURNED GREEK SMALL LETTER IOTA;So;0;ON;;;;;N;;;;; 212A;KELVIN SIGN;Lu;0;L;004B;;;;N;DEGREES KELVIN;;;006B; 212B;ANGSTROM SIGN;Lu;0;L;00C5;;;;N;ANGSTROM UNIT;;;00E5; 212C;SCRIPT CAPITAL B;Lu;0;L; 0042;;;;N;SCRIPT B;;;; 212D;BLACK-LETTER CAPITAL C;Lu;0;L; 0043;;;;N;BLACK-LETTER C;;;; 212E;ESTIMATED SYMBOL;So;0;ET;;;;;N;;;;; 212F;SCRIPT SMALL E;Ll;0;L; 0065;;;;N;;;;; 2130;SCRIPT CAPITAL E;Lu;0;L; 0045;;;;N;SCRIPT E;;;; 2131;SCRIPT CAPITAL F;Lu;0;L; 0046;;;;N;SCRIPT F;;;; 2132;TURNED CAPITAL F;Lu;0;L;;;;;N;TURNED F;;;214E; 2133;SCRIPT CAPITAL M;Lu;0;L; 004D;;;;N;SCRIPT M;;;; 2134;SCRIPT SMALL O;Ll;0;L; 006F;;;;N;;;;; 2135;ALEF SYMBOL;Lo;0;L; 05D0;;;;N;FIRST TRANSFINITE CARDINAL;;;; 2136;BET SYMBOL;Lo;0;L; 05D1;;;;N;SECOND TRANSFINITE CARDINAL;;;; 2137;GIMEL SYMBOL;Lo;0;L; 05D2;;;;N;THIRD TRANSFINITE CARDINAL;;;; 2138;DALET SYMBOL;Lo;0;L; 05D3;;;;N;FOURTH TRANSFINITE CARDINAL;;;; 2139;INFORMATION SOURCE;Ll;0;L; 0069;;;;N;;;;; 213A;ROTATED CAPITAL Q;So;0;ON;;;;;N;;;;; 213B;FACSIMILE SIGN;So;0;ON; 0046 0041 0058;;;;N;;;;; 213C;DOUBLE-STRUCK SMALL PI;Ll;0;L; 03C0;;;;N;;;;; 213D;DOUBLE-STRUCK SMALL GAMMA;Ll;0;L; 03B3;;;;N;;;;; 213E;DOUBLE-STRUCK CAPITAL GAMMA;Lu;0;L; 0393;;;;N;;;;; 213F;DOUBLE-STRUCK CAPITAL PI;Lu;0;L; 03A0;;;;N;;;;; 2140;DOUBLE-STRUCK N-ARY SUMMATION;Sm;0;ON; 2211;;;;Y;;;;; 2141;TURNED SANS-SERIF CAPITAL G;Sm;0;ON;;;;;N;;;;; 2142;TURNED SANS-SERIF CAPITAL L;Sm;0;ON;;;;;N;;;;; 2143;REVERSED SANS-SERIF CAPITAL L;Sm;0;ON;;;;;N;;;;; 2144;TURNED SANS-SERIF CAPITAL Y;Sm;0;ON;;;;;N;;;;; 2145;DOUBLE-STRUCK ITALIC CAPITAL D;Lu;0;L; 0044;;;;N;;;;; 2146;DOUBLE-STRUCK ITALIC SMALL D;Ll;0;L; 0064;;;;N;;;;; 2147;DOUBLE-STRUCK ITALIC SMALL E;Ll;0;L; 0065;;;;N;;;;; 2148;DOUBLE-STRUCK ITALIC SMALL I;Ll;0;L; 0069;;;;N;;;;; 2149;DOUBLE-STRUCK ITALIC SMALL J;Ll;0;L; 006A;;;;N;;;;; 214A;PROPERTY LINE;So;0;ON;;;;;N;;;;; 214B;TURNED AMPERSAND;Sm;0;ON;;;;;N;;;;; 214C;PER SIGN;So;0;ON;;;;;N;;;;; 214D;AKTIESELSKAB;So;0;ON;;;;;N;;;;; 214E;TURNED SMALL F;Ll;0;L;;;;;N;;;2132;;2132 214F;SYMBOL FOR SAMARITAN SOURCE;So;0;L;;;;;N;;;;; 2150;VULGAR FRACTION ONE SEVENTH;No;0;ON; 0031 2044 0037;;;1/7;N;;;;; 2151;VULGAR FRACTION ONE NINTH;No;0;ON; 0031 2044 0039;;;1/9;N;;;;; 2152;VULGAR FRACTION ONE TENTH;No;0;ON; 0031 2044 0031 0030;;;1/10;N;;;;; 2153;VULGAR FRACTION ONE THIRD;No;0;ON; 0031 2044 0033;;;1/3;N;FRACTION ONE THIRD;;;; 2154;VULGAR FRACTION TWO THIRDS;No;0;ON; 0032 2044 0033;;;2/3;N;FRACTION TWO THIRDS;;;; 2155;VULGAR FRACTION ONE FIFTH;No;0;ON; 0031 2044 0035;;;1/5;N;FRACTION ONE FIFTH;;;; 2156;VULGAR FRACTION TWO FIFTHS;No;0;ON; 0032 2044 0035;;;2/5;N;FRACTION TWO FIFTHS;;;; 2157;VULGAR FRACTION THREE FIFTHS;No;0;ON; 0033 2044 0035;;;3/5;N;FRACTION THREE FIFTHS;;;; 2158;VULGAR FRACTION FOUR FIFTHS;No;0;ON; 0034 2044 0035;;;4/5;N;FRACTION FOUR FIFTHS;;;; 2159;VULGAR FRACTION ONE SIXTH;No;0;ON; 0031 2044 0036;;;1/6;N;FRACTION ONE SIXTH;;;; 215A;VULGAR FRACTION FIVE SIXTHS;No;0;ON; 0035 2044 0036;;;5/6;N;FRACTION FIVE SIXTHS;;;; 215B;VULGAR FRACTION ONE EIGHTH;No;0;ON; 0031 2044 0038;;;1/8;N;FRACTION ONE EIGHTH;;;; 215C;VULGAR FRACTION THREE EIGHTHS;No;0;ON; 0033 2044 0038;;;3/8;N;FRACTION THREE EIGHTHS;;;; 215D;VULGAR FRACTION FIVE EIGHTHS;No;0;ON; 0035 2044 0038;;;5/8;N;FRACTION FIVE EIGHTHS;;;; 215E;VULGAR FRACTION SEVEN EIGHTHS;No;0;ON; 0037 2044 0038;;;7/8;N;FRACTION SEVEN EIGHTHS;;;; 215F;FRACTION NUMERATOR ONE;No;0;ON; 0031 2044;;;1;N;;;;; 2160;ROMAN NUMERAL ONE;Nl;0;L; 0049;;;1;N;;;;2170; 2161;ROMAN NUMERAL TWO;Nl;0;L; 0049 0049;;;2;N;;;;2171; 2162;ROMAN NUMERAL THREE;Nl;0;L; 0049 0049 0049;;;3;N;;;;2172; 2163;ROMAN NUMERAL FOUR;Nl;0;L; 0049 0056;;;4;N;;;;2173; 2164;ROMAN NUMERAL FIVE;Nl;0;L; 0056;;;5;N;;;;2174; 2165;ROMAN NUMERAL SIX;Nl;0;L; 0056 0049;;;6;N;;;;2175; 2166;ROMAN NUMERAL SEVEN;Nl;0;L; 0056 0049 0049;;;7;N;;;;2176; 2167;ROMAN NUMERAL EIGHT;Nl;0;L; 0056 0049 0049 0049;;;8;N;;;;2177; 2168;ROMAN NUMERAL NINE;Nl;0;L; 0049 0058;;;9;N;;;;2178; 2169;ROMAN NUMERAL TEN;Nl;0;L; 0058;;;10;N;;;;2179; 216A;ROMAN NUMERAL ELEVEN;Nl;0;L; 0058 0049;;;11;N;;;;217A; 216B;ROMAN NUMERAL TWELVE;Nl;0;L; 0058 0049 0049;;;12;N;;;;217B; 216C;ROMAN NUMERAL FIFTY;Nl;0;L; 004C;;;50;N;;;;217C; 216D;ROMAN NUMERAL ONE HUNDRED;Nl;0;L; 0043;;;100;N;;;;217D; 216E;ROMAN NUMERAL FIVE HUNDRED;Nl;0;L; 0044;;;500;N;;;;217E; 216F;ROMAN NUMERAL ONE THOUSAND;Nl;0;L; 004D;;;1000;N;;;;217F; 2170;SMALL ROMAN NUMERAL ONE;Nl;0;L; 0069;;;1;N;;;2160;;2160 2171;SMALL ROMAN NUMERAL TWO;Nl;0;L; 0069 0069;;;2;N;;;2161;;2161 2172;SMALL ROMAN NUMERAL THREE;Nl;0;L; 0069 0069 0069;;;3;N;;;2162;;2162 2173;SMALL ROMAN NUMERAL FOUR;Nl;0;L; 0069 0076;;;4;N;;;2163;;2163 2174;SMALL ROMAN NUMERAL FIVE;Nl;0;L; 0076;;;5;N;;;2164;;2164 2175;SMALL ROMAN NUMERAL SIX;Nl;0;L; 0076 0069;;;6;N;;;2165;;2165 2176;SMALL ROMAN NUMERAL SEVEN;Nl;0;L; 0076 0069 0069;;;7;N;;;2166;;2166 2177;SMALL ROMAN NUMERAL EIGHT;Nl;0;L; 0076 0069 0069 0069;;;8;N;;;2167;;2167 2178;SMALL ROMAN NUMERAL NINE;Nl;0;L; 0069 0078;;;9;N;;;2168;;2168 2179;SMALL ROMAN NUMERAL TEN;Nl;0;L; 0078;;;10;N;;;2169;;2169 217A;SMALL ROMAN NUMERAL ELEVEN;Nl;0;L; 0078 0069;;;11;N;;;216A;;216A 217B;SMALL ROMAN NUMERAL TWELVE;Nl;0;L; 0078 0069 0069;;;12;N;;;216B;;216B 217C;SMALL ROMAN NUMERAL FIFTY;Nl;0;L; 006C;;;50;N;;;216C;;216C 217D;SMALL ROMAN NUMERAL ONE HUNDRED;Nl;0;L; 0063;;;100;N;;;216D;;216D 217E;SMALL ROMAN NUMERAL FIVE HUNDRED;Nl;0;L; 0064;;;500;N;;;216E;;216E 217F;SMALL ROMAN NUMERAL ONE THOUSAND;Nl;0;L; 006D;;;1000;N;;;216F;;216F 2180;ROMAN NUMERAL ONE THOUSAND C D;Nl;0;L;;;;1000;N;;;;; 2181;ROMAN NUMERAL FIVE THOUSAND;Nl;0;L;;;;5000;N;;;;; 2182;ROMAN NUMERAL TEN THOUSAND;Nl;0;L;;;;10000;N;;;;; 2183;ROMAN NUMERAL REVERSED ONE HUNDRED;Lu;0;L;;;;;N;;;;2184; 2184;LATIN SMALL LETTER REVERSED C;Ll;0;L;;;;;N;;;2183;;2183 2185;ROMAN NUMERAL SIX LATE FORM;Nl;0;L;;;;6;N;;;;; 2186;ROMAN NUMERAL FIFTY EARLY FORM;Nl;0;L;;;;50;N;;;;; 2187;ROMAN NUMERAL FIFTY THOUSAND;Nl;0;L;;;;50000;N;;;;; 2188;ROMAN NUMERAL ONE HUNDRED THOUSAND;Nl;0;L;;;;100000;N;;;;; 2189;VULGAR FRACTION ZERO THIRDS;No;0;ON; 0030 2044 0033;;;0;N;;;;; 218A;TURNED DIGIT TWO;So;0;ON;;;;;N;;;;; 218B;TURNED DIGIT THREE;So;0;ON;;;;;N;;;;; 2190;LEFTWARDS ARROW;Sm;0;ON;;;;;N;LEFT ARROW;;;; 2191;UPWARDS ARROW;Sm;0;ON;;;;;N;UP ARROW;;;; 2192;RIGHTWARDS ARROW;Sm;0;ON;;;;;N;RIGHT ARROW;;;; 2193;DOWNWARDS ARROW;Sm;0;ON;;;;;N;DOWN ARROW;;;; 2194;LEFT RIGHT ARROW;Sm;0;ON;;;;;N;;;;; 2195;UP DOWN ARROW;So;0;ON;;;;;N;;;;; 2196;NORTH WEST ARROW;So;0;ON;;;;;N;UPPER LEFT ARROW;;;; 2197;NORTH EAST ARROW;So;0;ON;;;;;N;UPPER RIGHT ARROW;;;; 2198;SOUTH EAST ARROW;So;0;ON;;;;;N;LOWER RIGHT ARROW;;;; 2199;SOUTH WEST ARROW;So;0;ON;;;;;N;LOWER LEFT ARROW;;;; 219A;LEFTWARDS ARROW WITH STROKE;Sm;0;ON;2190 0338;;;;N;LEFT ARROW WITH STROKE;;;; 219B;RIGHTWARDS ARROW WITH STROKE;Sm;0;ON;2192 0338;;;;N;RIGHT ARROW WITH STROKE;;;; 219C;LEFTWARDS WAVE ARROW;So;0;ON;;;;;N;LEFT WAVE ARROW;;;; 219D;RIGHTWARDS WAVE ARROW;So;0;ON;;;;;N;RIGHT WAVE ARROW;;;; 219E;LEFTWARDS TWO HEADED ARROW;So;0;ON;;;;;N;LEFT TWO HEADED ARROW;;;; 219F;UPWARDS TWO HEADED ARROW;So;0;ON;;;;;N;UP TWO HEADED ARROW;;;; 21A0;RIGHTWARDS TWO HEADED ARROW;Sm;0;ON;;;;;N;RIGHT TWO HEADED ARROW;;;; 21A1;DOWNWARDS TWO HEADED ARROW;So;0;ON;;;;;N;DOWN TWO HEADED ARROW;;;; 21A2;LEFTWARDS ARROW WITH TAIL;So;0;ON;;;;;N;LEFT ARROW WITH TAIL;;;; 21A3;RIGHTWARDS ARROW WITH TAIL;Sm;0;ON;;;;;N;RIGHT ARROW WITH TAIL;;;; 21A4;LEFTWARDS ARROW FROM BAR;So;0;ON;;;;;N;LEFT ARROW FROM BAR;;;; 21A5;UPWARDS ARROW FROM BAR;So;0;ON;;;;;N;UP ARROW FROM BAR;;;; 21A6;RIGHTWARDS ARROW FROM BAR;Sm;0;ON;;;;;N;RIGHT ARROW FROM BAR;;;; 21A7;DOWNWARDS ARROW FROM BAR;So;0;ON;;;;;N;DOWN ARROW FROM BAR;;;; 21A8;UP DOWN ARROW WITH BASE;So;0;ON;;;;;N;;;;; 21A9;LEFTWARDS ARROW WITH HOOK;So;0;ON;;;;;N;LEFT ARROW WITH HOOK;;;; 21AA;RIGHTWARDS ARROW WITH HOOK;So;0;ON;;;;;N;RIGHT ARROW WITH HOOK;;;; 21AB;LEFTWARDS ARROW WITH LOOP;So;0;ON;;;;;N;LEFT ARROW WITH LOOP;;;; 21AC;RIGHTWARDS ARROW WITH LOOP;So;0;ON;;;;;N;RIGHT ARROW WITH LOOP;;;; 21AD;LEFT RIGHT WAVE ARROW;So;0;ON;;;;;N;;;;; 21AE;LEFT RIGHT ARROW WITH STROKE;Sm;0;ON;2194 0338;;;;N;;;;; 21AF;DOWNWARDS ZIGZAG ARROW;So;0;ON;;;;;N;DOWN ZIGZAG ARROW;;;; 21B0;UPWARDS ARROW WITH TIP LEFTWARDS;So;0;ON;;;;;N;UP ARROW WITH TIP LEFT;;;; 21B1;UPWARDS ARROW WITH TIP RIGHTWARDS;So;0;ON;;;;;N;UP ARROW WITH TIP RIGHT;;;; 21B2;DOWNWARDS ARROW WITH TIP LEFTWARDS;So;0;ON;;;;;N;DOWN ARROW WITH TIP LEFT;;;; 21B3;DOWNWARDS ARROW WITH TIP RIGHTWARDS;So;0;ON;;;;;N;DOWN ARROW WITH TIP RIGHT;;;; 21B4;RIGHTWARDS ARROW WITH CORNER DOWNWARDS;So;0;ON;;;;;N;RIGHT ARROW WITH CORNER DOWN;;;; 21B5;DOWNWARDS ARROW WITH CORNER LEFTWARDS;So;0;ON;;;;;N;DOWN ARROW WITH CORNER LEFT;;;; 21B6;ANTICLOCKWISE TOP SEMICIRCLE ARROW;So;0;ON;;;;;N;;;;; 21B7;CLOCKWISE TOP SEMICIRCLE ARROW;So;0;ON;;;;;N;;;;; 21B8;NORTH WEST ARROW TO LONG BAR;So;0;ON;;;;;N;UPPER LEFT ARROW TO LONG BAR;;;; 21B9;LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR;So;0;ON;;;;;N;LEFT ARROW TO BAR OVER RIGHT ARROW TO BAR;;;; 21BA;ANTICLOCKWISE OPEN CIRCLE ARROW;So;0;ON;;;;;N;;;;; 21BB;CLOCKWISE OPEN CIRCLE ARROW;So;0;ON;;;;;N;;;;; 21BC;LEFTWARDS HARPOON WITH BARB UPWARDS;So;0;ON;;;;;N;LEFT HARPOON WITH BARB UP;;;; 21BD;LEFTWARDS HARPOON WITH BARB DOWNWARDS;So;0;ON;;;;;N;LEFT HARPOON WITH BARB DOWN;;;; 21BE;UPWARDS HARPOON WITH BARB RIGHTWARDS;So;0;ON;;;;;N;UP HARPOON WITH BARB RIGHT;;;; 21BF;UPWARDS HARPOON WITH BARB LEFTWARDS;So;0;ON;;;;;N;UP HARPOON WITH BARB LEFT;;;; 21C0;RIGHTWARDS HARPOON WITH BARB UPWARDS;So;0;ON;;;;;N;RIGHT HARPOON WITH BARB UP;;;; 21C1;RIGHTWARDS HARPOON WITH BARB DOWNWARDS;So;0;ON;;;;;N;RIGHT HARPOON WITH BARB DOWN;;;; 21C2;DOWNWARDS HARPOON WITH BARB RIGHTWARDS;So;0;ON;;;;;N;DOWN HARPOON WITH BARB RIGHT;;;; 21C3;DOWNWARDS HARPOON WITH BARB LEFTWARDS;So;0;ON;;;;;N;DOWN HARPOON WITH BARB LEFT;;;; 21C4;RIGHTWARDS ARROW OVER LEFTWARDS ARROW;So;0;ON;;;;;N;RIGHT ARROW OVER LEFT ARROW;;;; 21C5;UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW;So;0;ON;;;;;N;UP ARROW LEFT OF DOWN ARROW;;;; 21C6;LEFTWARDS ARROW OVER RIGHTWARDS ARROW;So;0;ON;;;;;N;LEFT ARROW OVER RIGHT ARROW;;;; 21C7;LEFTWARDS PAIRED ARROWS;So;0;ON;;;;;N;LEFT PAIRED ARROWS;;;; 21C8;UPWARDS PAIRED ARROWS;So;0;ON;;;;;N;UP PAIRED ARROWS;;;; 21C9;RIGHTWARDS PAIRED ARROWS;So;0;ON;;;;;N;RIGHT PAIRED ARROWS;;;; 21CA;DOWNWARDS PAIRED ARROWS;So;0;ON;;;;;N;DOWN PAIRED ARROWS;;;; 21CB;LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON;So;0;ON;;;;;N;LEFT HARPOON OVER RIGHT HARPOON;;;; 21CC;RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON;So;0;ON;;;;;N;RIGHT HARPOON OVER LEFT HARPOON;;;; 21CD;LEFTWARDS DOUBLE ARROW WITH STROKE;So;0;ON;21D0 0338;;;;N;LEFT DOUBLE ARROW WITH STROKE;;;; 21CE;LEFT RIGHT DOUBLE ARROW WITH STROKE;Sm;0;ON;21D4 0338;;;;N;;;;; 21CF;RIGHTWARDS DOUBLE ARROW WITH STROKE;Sm;0;ON;21D2 0338;;;;N;RIGHT DOUBLE ARROW WITH STROKE;;;; 21D0;LEFTWARDS DOUBLE ARROW;So;0;ON;;;;;N;LEFT DOUBLE ARROW;;;; 21D1;UPWARDS DOUBLE ARROW;So;0;ON;;;;;N;UP DOUBLE ARROW;;;; 21D2;RIGHTWARDS DOUBLE ARROW;Sm;0;ON;;;;;N;RIGHT DOUBLE ARROW;;;; 21D3;DOWNWARDS DOUBLE ARROW;So;0;ON;;;;;N;DOWN DOUBLE ARROW;;;; 21D4;LEFT RIGHT DOUBLE ARROW;Sm;0;ON;;;;;N;;;;; 21D5;UP DOWN DOUBLE ARROW;So;0;ON;;;;;N;;;;; 21D6;NORTH WEST DOUBLE ARROW;So;0;ON;;;;;N;UPPER LEFT DOUBLE ARROW;;;; 21D7;NORTH EAST DOUBLE ARROW;So;0;ON;;;;;N;UPPER RIGHT DOUBLE ARROW;;;; 21D8;SOUTH EAST DOUBLE ARROW;So;0;ON;;;;;N;LOWER RIGHT DOUBLE ARROW;;;; 21D9;SOUTH WEST DOUBLE ARROW;So;0;ON;;;;;N;LOWER LEFT DOUBLE ARROW;;;; 21DA;LEFTWARDS TRIPLE ARROW;So;0;ON;;;;;N;LEFT TRIPLE ARROW;;;; 21DB;RIGHTWARDS TRIPLE ARROW;So;0;ON;;;;;N;RIGHT TRIPLE ARROW;;;; 21DC;LEFTWARDS SQUIGGLE ARROW;So;0;ON;;;;;N;LEFT SQUIGGLE ARROW;;;; 21DD;RIGHTWARDS SQUIGGLE ARROW;So;0;ON;;;;;N;RIGHT SQUIGGLE ARROW;;;; 21DE;UPWARDS ARROW WITH DOUBLE STROKE;So;0;ON;;;;;N;UP ARROW WITH DOUBLE STROKE;;;; 21DF;DOWNWARDS ARROW WITH DOUBLE STROKE;So;0;ON;;;;;N;DOWN ARROW WITH DOUBLE STROKE;;;; 21E0;LEFTWARDS DASHED ARROW;So;0;ON;;;;;N;LEFT DASHED ARROW;;;; 21E1;UPWARDS DASHED ARROW;So;0;ON;;;;;N;UP DASHED ARROW;;;; 21E2;RIGHTWARDS DASHED ARROW;So;0;ON;;;;;N;RIGHT DASHED ARROW;;;; 21E3;DOWNWARDS DASHED ARROW;So;0;ON;;;;;N;DOWN DASHED ARROW;;;; 21E4;LEFTWARDS ARROW TO BAR;So;0;ON;;;;;N;LEFT ARROW TO BAR;;;; 21E5;RIGHTWARDS ARROW TO BAR;So;0;ON;;;;;N;RIGHT ARROW TO BAR;;;; 21E6;LEFTWARDS WHITE ARROW;So;0;ON;;;;;N;WHITE LEFT ARROW;;;; 21E7;UPWARDS WHITE ARROW;So;0;ON;;;;;N;WHITE UP ARROW;;;; 21E8;RIGHTWARDS WHITE ARROW;So;0;ON;;;;;N;WHITE RIGHT ARROW;;;; 21E9;DOWNWARDS WHITE ARROW;So;0;ON;;;;;N;WHITE DOWN ARROW;;;; 21EA;UPWARDS WHITE ARROW FROM BAR;So;0;ON;;;;;N;WHITE UP ARROW FROM BAR;;;; 21EB;UPWARDS WHITE ARROW ON PEDESTAL;So;0;ON;;;;;N;;;;; 21EC;UPWARDS WHITE ARROW ON PEDESTAL WITH HORIZONTAL BAR;So;0;ON;;;;;N;;;;; 21ED;UPWARDS WHITE ARROW ON PEDESTAL WITH VERTICAL BAR;So;0;ON;;;;;N;;;;; 21EE;UPWARDS WHITE DOUBLE ARROW;So;0;ON;;;;;N;;;;; 21EF;UPWARDS WHITE DOUBLE ARROW ON PEDESTAL;So;0;ON;;;;;N;;;;; 21F0;RIGHTWARDS WHITE ARROW FROM WALL;So;0;ON;;;;;N;;;;; 21F1;NORTH WEST ARROW TO CORNER;So;0;ON;;;;;N;;;;; 21F2;SOUTH EAST ARROW TO CORNER;So;0;ON;;;;;N;;;;; 21F3;UP DOWN WHITE ARROW;So;0;ON;;;;;N;;;;; 21F4;RIGHT ARROW WITH SMALL CIRCLE;Sm;0;ON;;;;;N;;;;; 21F5;DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW;Sm;0;ON;;;;;N;;;;; 21F6;THREE RIGHTWARDS ARROWS;Sm;0;ON;;;;;N;;;;; 21F7;LEFTWARDS ARROW WITH VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 21F8;RIGHTWARDS ARROW WITH VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 21F9;LEFT RIGHT ARROW WITH VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 21FA;LEFTWARDS ARROW WITH DOUBLE VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 21FB;RIGHTWARDS ARROW WITH DOUBLE VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 21FC;LEFT RIGHT ARROW WITH DOUBLE VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 21FD;LEFTWARDS OPEN-HEADED ARROW;Sm;0;ON;;;;;N;;;;; 21FE;RIGHTWARDS OPEN-HEADED ARROW;Sm;0;ON;;;;;N;;;;; 21FF;LEFT RIGHT OPEN-HEADED ARROW;Sm;0;ON;;;;;N;;;;; 2200;FOR ALL;Sm;0;ON;;;;;N;;;;; 2201;COMPLEMENT;Sm;0;ON;;;;;Y;;;;; 2202;PARTIAL DIFFERENTIAL;Sm;0;ON;;;;;Y;;;;; 2203;THERE EXISTS;Sm;0;ON;;;;;Y;;;;; 2204;THERE DOES NOT EXIST;Sm;0;ON;2203 0338;;;;Y;;;;; 2205;EMPTY SET;Sm;0;ON;;;;;N;;;;; 2206;INCREMENT;Sm;0;ON;;;;;N;;;;; 2207;NABLA;Sm;0;ON;;;;;N;;;;; 2208;ELEMENT OF;Sm;0;ON;;;;;Y;;;;; 2209;NOT AN ELEMENT OF;Sm;0;ON;2208 0338;;;;Y;;;;; 220A;SMALL ELEMENT OF;Sm;0;ON;;;;;Y;;;;; 220B;CONTAINS AS MEMBER;Sm;0;ON;;;;;Y;;;;; 220C;DOES NOT CONTAIN AS MEMBER;Sm;0;ON;220B 0338;;;;Y;;;;; 220D;SMALL CONTAINS AS MEMBER;Sm;0;ON;;;;;Y;;;;; 220E;END OF PROOF;Sm;0;ON;;;;;N;;;;; 220F;N-ARY PRODUCT;Sm;0;ON;;;;;N;;;;; 2210;N-ARY COPRODUCT;Sm;0;ON;;;;;N;;;;; 2211;N-ARY SUMMATION;Sm;0;ON;;;;;Y;;;;; 2212;MINUS SIGN;Sm;0;ES;;;;;N;;;;; 2213;MINUS-OR-PLUS SIGN;Sm;0;ET;;;;;N;;;;; 2214;DOT PLUS;Sm;0;ON;;;;;N;;;;; 2215;DIVISION SLASH;Sm;0;ON;;;;;Y;;;;; 2216;SET MINUS;Sm;0;ON;;;;;Y;;;;; 2217;ASTERISK OPERATOR;Sm;0;ON;;;;;N;;;;; 2218;RING OPERATOR;Sm;0;ON;;;;;N;;;;; 2219;BULLET OPERATOR;Sm;0;ON;;;;;N;;;;; 221A;SQUARE ROOT;Sm;0;ON;;;;;Y;;;;; 221B;CUBE ROOT;Sm;0;ON;;;;;Y;;;;; 221C;FOURTH ROOT;Sm;0;ON;;;;;Y;;;;; 221D;PROPORTIONAL TO;Sm;0;ON;;;;;Y;;;;; 221E;INFINITY;Sm;0;ON;;;;;N;;;;; 221F;RIGHT ANGLE;Sm;0;ON;;;;;Y;;;;; 2220;ANGLE;Sm;0;ON;;;;;Y;;;;; 2221;MEASURED ANGLE;Sm;0;ON;;;;;Y;;;;; 2222;SPHERICAL ANGLE;Sm;0;ON;;;;;Y;;;;; 2223;DIVIDES;Sm;0;ON;;;;;N;;;;; 2224;DOES NOT DIVIDE;Sm;0;ON;2223 0338;;;;Y;;;;; 2225;PARALLEL TO;Sm;0;ON;;;;;N;;;;; 2226;NOT PARALLEL TO;Sm;0;ON;2225 0338;;;;Y;;;;; 2227;LOGICAL AND;Sm;0;ON;;;;;N;;;;; 2228;LOGICAL OR;Sm;0;ON;;;;;N;;;;; 2229;INTERSECTION;Sm;0;ON;;;;;N;;;;; 222A;UNION;Sm;0;ON;;;;;N;;;;; 222B;INTEGRAL;Sm;0;ON;;;;;Y;;;;; 222C;DOUBLE INTEGRAL;Sm;0;ON; 222B 222B;;;;Y;;;;; 222D;TRIPLE INTEGRAL;Sm;0;ON; 222B 222B 222B;;;;Y;;;;; 222E;CONTOUR INTEGRAL;Sm;0;ON;;;;;Y;;;;; 222F;SURFACE INTEGRAL;Sm;0;ON; 222E 222E;;;;Y;;;;; 2230;VOLUME INTEGRAL;Sm;0;ON; 222E 222E 222E;;;;Y;;;;; 2231;CLOCKWISE INTEGRAL;Sm;0;ON;;;;;Y;;;;; 2232;CLOCKWISE CONTOUR INTEGRAL;Sm;0;ON;;;;;Y;;;;; 2233;ANTICLOCKWISE CONTOUR INTEGRAL;Sm;0;ON;;;;;Y;;;;; 2234;THEREFORE;Sm;0;ON;;;;;N;;;;; 2235;BECAUSE;Sm;0;ON;;;;;N;;;;; 2236;RATIO;Sm;0;ON;;;;;N;;;;; 2237;PROPORTION;Sm;0;ON;;;;;N;;;;; 2238;DOT MINUS;Sm;0;ON;;;;;N;;;;; 2239;EXCESS;Sm;0;ON;;;;;Y;;;;; 223A;GEOMETRIC PROPORTION;Sm;0;ON;;;;;N;;;;; 223B;HOMOTHETIC;Sm;0;ON;;;;;Y;;;;; 223C;TILDE OPERATOR;Sm;0;ON;;;;;Y;;;;; 223D;REVERSED TILDE;Sm;0;ON;;;;;Y;;;;; 223E;INVERTED LAZY S;Sm;0;ON;;;;;Y;;;;; 223F;SINE WAVE;Sm;0;ON;;;;;Y;;;;; 2240;WREATH PRODUCT;Sm;0;ON;;;;;Y;;;;; 2241;NOT TILDE;Sm;0;ON;223C 0338;;;;Y;;;;; 2242;MINUS TILDE;Sm;0;ON;;;;;Y;;;;; 2243;ASYMPTOTICALLY EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2244;NOT ASYMPTOTICALLY EQUAL TO;Sm;0;ON;2243 0338;;;;Y;;;;; 2245;APPROXIMATELY EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2246;APPROXIMATELY BUT NOT ACTUALLY EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2247;NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO;Sm;0;ON;2245 0338;;;;Y;;;;; 2248;ALMOST EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2249;NOT ALMOST EQUAL TO;Sm;0;ON;2248 0338;;;;Y;;;;; 224A;ALMOST EQUAL OR EQUAL TO;Sm;0;ON;;;;;Y;;;;; 224B;TRIPLE TILDE;Sm;0;ON;;;;;Y;;;;; 224C;ALL EQUAL TO;Sm;0;ON;;;;;Y;;;;; 224D;EQUIVALENT TO;Sm;0;ON;;;;;N;;;;; 224E;GEOMETRICALLY EQUIVALENT TO;Sm;0;ON;;;;;N;;;;; 224F;DIFFERENCE BETWEEN;Sm;0;ON;;;;;N;;;;; 2250;APPROACHES THE LIMIT;Sm;0;ON;;;;;N;;;;; 2251;GEOMETRICALLY EQUAL TO;Sm;0;ON;;;;;N;;;;; 2252;APPROXIMATELY EQUAL TO OR THE IMAGE OF;Sm;0;ON;;;;;Y;;;;; 2253;IMAGE OF OR APPROXIMATELY EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2254;COLON EQUALS;Sm;0;ON;;;;;Y;COLON EQUAL;;;; 2255;EQUALS COLON;Sm;0;ON;;;;;Y;EQUAL COLON;;;; 2256;RING IN EQUAL TO;Sm;0;ON;;;;;N;;;;; 2257;RING EQUAL TO;Sm;0;ON;;;;;N;;;;; 2258;CORRESPONDS TO;Sm;0;ON;;;;;N;;;;; 2259;ESTIMATES;Sm;0;ON;;;;;N;;;;; 225A;EQUIANGULAR TO;Sm;0;ON;;;;;N;;;;; 225B;STAR EQUALS;Sm;0;ON;;;;;N;;;;; 225C;DELTA EQUAL TO;Sm;0;ON;;;;;N;;;;; 225D;EQUAL TO BY DEFINITION;Sm;0;ON;;;;;N;;;;; 225E;MEASURED BY;Sm;0;ON;;;;;N;;;;; 225F;QUESTIONED EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2260;NOT EQUAL TO;Sm;0;ON;003D 0338;;;;Y;;;;; 2261;IDENTICAL TO;Sm;0;ON;;;;;N;;;;; 2262;NOT IDENTICAL TO;Sm;0;ON;2261 0338;;;;Y;;;;; 2263;STRICTLY EQUIVALENT TO;Sm;0;ON;;;;;N;;;;; 2264;LESS-THAN OR EQUAL TO;Sm;0;ON;;;;;Y;LESS THAN OR EQUAL TO;;;; 2265;GREATER-THAN OR EQUAL TO;Sm;0;ON;;;;;Y;GREATER THAN OR EQUAL TO;;;; 2266;LESS-THAN OVER EQUAL TO;Sm;0;ON;;;;;Y;LESS THAN OVER EQUAL TO;;;; 2267;GREATER-THAN OVER EQUAL TO;Sm;0;ON;;;;;Y;GREATER THAN OVER EQUAL TO;;;; 2268;LESS-THAN BUT NOT EQUAL TO;Sm;0;ON;;;;;Y;LESS THAN BUT NOT EQUAL TO;;;; 2269;GREATER-THAN BUT NOT EQUAL TO;Sm;0;ON;;;;;Y;GREATER THAN BUT NOT EQUAL TO;;;; 226A;MUCH LESS-THAN;Sm;0;ON;;;;;Y;MUCH LESS THAN;;;; 226B;MUCH GREATER-THAN;Sm;0;ON;;;;;Y;MUCH GREATER THAN;;;; 226C;BETWEEN;Sm;0;ON;;;;;N;;;;; 226D;NOT EQUIVALENT TO;Sm;0;ON;224D 0338;;;;N;;;;; 226E;NOT LESS-THAN;Sm;0;ON;003C 0338;;;;Y;NOT LESS THAN;;;; 226F;NOT GREATER-THAN;Sm;0;ON;003E 0338;;;;Y;NOT GREATER THAN;;;; 2270;NEITHER LESS-THAN NOR EQUAL TO;Sm;0;ON;2264 0338;;;;Y;NEITHER LESS THAN NOR EQUAL TO;;;; 2271;NEITHER GREATER-THAN NOR EQUAL TO;Sm;0;ON;2265 0338;;;;Y;NEITHER GREATER THAN NOR EQUAL TO;;;; 2272;LESS-THAN OR EQUIVALENT TO;Sm;0;ON;;;;;Y;LESS THAN OR EQUIVALENT TO;;;; 2273;GREATER-THAN OR EQUIVALENT TO;Sm;0;ON;;;;;Y;GREATER THAN OR EQUIVALENT TO;;;; 2274;NEITHER LESS-THAN NOR EQUIVALENT TO;Sm;0;ON;2272 0338;;;;Y;NEITHER LESS THAN NOR EQUIVALENT TO;;;; 2275;NEITHER GREATER-THAN NOR EQUIVALENT TO;Sm;0;ON;2273 0338;;;;Y;NEITHER GREATER THAN NOR EQUIVALENT TO;;;; 2276;LESS-THAN OR GREATER-THAN;Sm;0;ON;;;;;Y;LESS THAN OR GREATER THAN;;;; 2277;GREATER-THAN OR LESS-THAN;Sm;0;ON;;;;;Y;GREATER THAN OR LESS THAN;;;; 2278;NEITHER LESS-THAN NOR GREATER-THAN;Sm;0;ON;2276 0338;;;;Y;NEITHER LESS THAN NOR GREATER THAN;;;; 2279;NEITHER GREATER-THAN NOR LESS-THAN;Sm;0;ON;2277 0338;;;;Y;NEITHER GREATER THAN NOR LESS THAN;;;; 227A;PRECEDES;Sm;0;ON;;;;;Y;;;;; 227B;SUCCEEDS;Sm;0;ON;;;;;Y;;;;; 227C;PRECEDES OR EQUAL TO;Sm;0;ON;;;;;Y;;;;; 227D;SUCCEEDS OR EQUAL TO;Sm;0;ON;;;;;Y;;;;; 227E;PRECEDES OR EQUIVALENT TO;Sm;0;ON;;;;;Y;;;;; 227F;SUCCEEDS OR EQUIVALENT TO;Sm;0;ON;;;;;Y;;;;; 2280;DOES NOT PRECEDE;Sm;0;ON;227A 0338;;;;Y;;;;; 2281;DOES NOT SUCCEED;Sm;0;ON;227B 0338;;;;Y;;;;; 2282;SUBSET OF;Sm;0;ON;;;;;Y;;;;; 2283;SUPERSET OF;Sm;0;ON;;;;;Y;;;;; 2284;NOT A SUBSET OF;Sm;0;ON;2282 0338;;;;Y;;;;; 2285;NOT A SUPERSET OF;Sm;0;ON;2283 0338;;;;Y;;;;; 2286;SUBSET OF OR EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2287;SUPERSET OF OR EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2288;NEITHER A SUBSET OF NOR EQUAL TO;Sm;0;ON;2286 0338;;;;Y;;;;; 2289;NEITHER A SUPERSET OF NOR EQUAL TO;Sm;0;ON;2287 0338;;;;Y;;;;; 228A;SUBSET OF WITH NOT EQUAL TO;Sm;0;ON;;;;;Y;SUBSET OF OR NOT EQUAL TO;;;; 228B;SUPERSET OF WITH NOT EQUAL TO;Sm;0;ON;;;;;Y;SUPERSET OF OR NOT EQUAL TO;;;; 228C;MULTISET;Sm;0;ON;;;;;Y;;;;; 228D;MULTISET MULTIPLICATION;Sm;0;ON;;;;;N;;;;; 228E;MULTISET UNION;Sm;0;ON;;;;;N;;;;; 228F;SQUARE IMAGE OF;Sm;0;ON;;;;;Y;;;;; 2290;SQUARE ORIGINAL OF;Sm;0;ON;;;;;Y;;;;; 2291;SQUARE IMAGE OF OR EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2292;SQUARE ORIGINAL OF OR EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2293;SQUARE CAP;Sm;0;ON;;;;;N;;;;; 2294;SQUARE CUP;Sm;0;ON;;;;;N;;;;; 2295;CIRCLED PLUS;Sm;0;ON;;;;;N;;;;; 2296;CIRCLED MINUS;Sm;0;ON;;;;;N;;;;; 2297;CIRCLED TIMES;Sm;0;ON;;;;;N;;;;; 2298;CIRCLED DIVISION SLASH;Sm;0;ON;;;;;Y;;;;; 2299;CIRCLED DOT OPERATOR;Sm;0;ON;;;;;N;;;;; 229A;CIRCLED RING OPERATOR;Sm;0;ON;;;;;N;;;;; 229B;CIRCLED ASTERISK OPERATOR;Sm;0;ON;;;;;N;;;;; 229C;CIRCLED EQUALS;Sm;0;ON;;;;;N;;;;; 229D;CIRCLED DASH;Sm;0;ON;;;;;N;;;;; 229E;SQUARED PLUS;Sm;0;ON;;;;;N;;;;; 229F;SQUARED MINUS;Sm;0;ON;;;;;N;;;;; 22A0;SQUARED TIMES;Sm;0;ON;;;;;N;;;;; 22A1;SQUARED DOT OPERATOR;Sm;0;ON;;;;;N;;;;; 22A2;RIGHT TACK;Sm;0;ON;;;;;Y;;;;; 22A3;LEFT TACK;Sm;0;ON;;;;;Y;;;;; 22A4;DOWN TACK;Sm;0;ON;;;;;N;;;;; 22A5;UP TACK;Sm;0;ON;;;;;N;;;;; 22A6;ASSERTION;Sm;0;ON;;;;;Y;;;;; 22A7;MODELS;Sm;0;ON;;;;;Y;;;;; 22A8;TRUE;Sm;0;ON;;;;;Y;;;;; 22A9;FORCES;Sm;0;ON;;;;;Y;;;;; 22AA;TRIPLE VERTICAL BAR RIGHT TURNSTILE;Sm;0;ON;;;;;Y;;;;; 22AB;DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE;Sm;0;ON;;;;;Y;;;;; 22AC;DOES NOT PROVE;Sm;0;ON;22A2 0338;;;;Y;;;;; 22AD;NOT TRUE;Sm;0;ON;22A8 0338;;;;Y;;;;; 22AE;DOES NOT FORCE;Sm;0;ON;22A9 0338;;;;Y;;;;; 22AF;NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE;Sm;0;ON;22AB 0338;;;;Y;;;;; 22B0;PRECEDES UNDER RELATION;Sm;0;ON;;;;;Y;;;;; 22B1;SUCCEEDS UNDER RELATION;Sm;0;ON;;;;;Y;;;;; 22B2;NORMAL SUBGROUP OF;Sm;0;ON;;;;;Y;;;;; 22B3;CONTAINS AS NORMAL SUBGROUP;Sm;0;ON;;;;;Y;;;;; 22B4;NORMAL SUBGROUP OF OR EQUAL TO;Sm;0;ON;;;;;Y;;;;; 22B5;CONTAINS AS NORMAL SUBGROUP OR EQUAL TO;Sm;0;ON;;;;;Y;;;;; 22B6;ORIGINAL OF;Sm;0;ON;;;;;Y;;;;; 22B7;IMAGE OF;Sm;0;ON;;;;;Y;;;;; 22B8;MULTIMAP;Sm;0;ON;;;;;Y;;;;; 22B9;HERMITIAN CONJUGATE MATRIX;Sm;0;ON;;;;;N;;;;; 22BA;INTERCALATE;Sm;0;ON;;;;;N;;;;; 22BB;XOR;Sm;0;ON;;;;;N;;;;; 22BC;NAND;Sm;0;ON;;;;;N;;;;; 22BD;NOR;Sm;0;ON;;;;;N;;;;; 22BE;RIGHT ANGLE WITH ARC;Sm;0;ON;;;;;Y;;;;; 22BF;RIGHT TRIANGLE;Sm;0;ON;;;;;Y;;;;; 22C0;N-ARY LOGICAL AND;Sm;0;ON;;;;;N;;;;; 22C1;N-ARY LOGICAL OR;Sm;0;ON;;;;;N;;;;; 22C2;N-ARY INTERSECTION;Sm;0;ON;;;;;N;;;;; 22C3;N-ARY UNION;Sm;0;ON;;;;;N;;;;; 22C4;DIAMOND OPERATOR;Sm;0;ON;;;;;N;;;;; 22C5;DOT OPERATOR;Sm;0;ON;;;;;N;;;;; 22C6;STAR OPERATOR;Sm;0;ON;;;;;N;;;;; 22C7;DIVISION TIMES;Sm;0;ON;;;;;N;;;;; 22C8;BOWTIE;Sm;0;ON;;;;;N;;;;; 22C9;LEFT NORMAL FACTOR SEMIDIRECT PRODUCT;Sm;0;ON;;;;;Y;;;;; 22CA;RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT;Sm;0;ON;;;;;Y;;;;; 22CB;LEFT SEMIDIRECT PRODUCT;Sm;0;ON;;;;;Y;;;;; 22CC;RIGHT SEMIDIRECT PRODUCT;Sm;0;ON;;;;;Y;;;;; 22CD;REVERSED TILDE EQUALS;Sm;0;ON;;;;;Y;;;;; 22CE;CURLY LOGICAL OR;Sm;0;ON;;;;;N;;;;; 22CF;CURLY LOGICAL AND;Sm;0;ON;;;;;N;;;;; 22D0;DOUBLE SUBSET;Sm;0;ON;;;;;Y;;;;; 22D1;DOUBLE SUPERSET;Sm;0;ON;;;;;Y;;;;; 22D2;DOUBLE INTERSECTION;Sm;0;ON;;;;;N;;;;; 22D3;DOUBLE UNION;Sm;0;ON;;;;;N;;;;; 22D4;PITCHFORK;Sm;0;ON;;;;;N;;;;; 22D5;EQUAL AND PARALLEL TO;Sm;0;ON;;;;;N;;;;; 22D6;LESS-THAN WITH DOT;Sm;0;ON;;;;;Y;LESS THAN WITH DOT;;;; 22D7;GREATER-THAN WITH DOT;Sm;0;ON;;;;;Y;GREATER THAN WITH DOT;;;; 22D8;VERY MUCH LESS-THAN;Sm;0;ON;;;;;Y;VERY MUCH LESS THAN;;;; 22D9;VERY MUCH GREATER-THAN;Sm;0;ON;;;;;Y;VERY MUCH GREATER THAN;;;; 22DA;LESS-THAN EQUAL TO OR GREATER-THAN;Sm;0;ON;;;;;Y;LESS THAN EQUAL TO OR GREATER THAN;;;; 22DB;GREATER-THAN EQUAL TO OR LESS-THAN;Sm;0;ON;;;;;Y;GREATER THAN EQUAL TO OR LESS THAN;;;; 22DC;EQUAL TO OR LESS-THAN;Sm;0;ON;;;;;Y;EQUAL TO OR LESS THAN;;;; 22DD;EQUAL TO OR GREATER-THAN;Sm;0;ON;;;;;Y;EQUAL TO OR GREATER THAN;;;; 22DE;EQUAL TO OR PRECEDES;Sm;0;ON;;;;;Y;;;;; 22DF;EQUAL TO OR SUCCEEDS;Sm;0;ON;;;;;Y;;;;; 22E0;DOES NOT PRECEDE OR EQUAL;Sm;0;ON;227C 0338;;;;Y;;;;; 22E1;DOES NOT SUCCEED OR EQUAL;Sm;0;ON;227D 0338;;;;Y;;;;; 22E2;NOT SQUARE IMAGE OF OR EQUAL TO;Sm;0;ON;2291 0338;;;;Y;;;;; 22E3;NOT SQUARE ORIGINAL OF OR EQUAL TO;Sm;0;ON;2292 0338;;;;Y;;;;; 22E4;SQUARE IMAGE OF OR NOT EQUAL TO;Sm;0;ON;;;;;Y;;;;; 22E5;SQUARE ORIGINAL OF OR NOT EQUAL TO;Sm;0;ON;;;;;Y;;;;; 22E6;LESS-THAN BUT NOT EQUIVALENT TO;Sm;0;ON;;;;;Y;LESS THAN BUT NOT EQUIVALENT TO;;;; 22E7;GREATER-THAN BUT NOT EQUIVALENT TO;Sm;0;ON;;;;;Y;GREATER THAN BUT NOT EQUIVALENT TO;;;; 22E8;PRECEDES BUT NOT EQUIVALENT TO;Sm;0;ON;;;;;Y;;;;; 22E9;SUCCEEDS BUT NOT EQUIVALENT TO;Sm;0;ON;;;;;Y;;;;; 22EA;NOT NORMAL SUBGROUP OF;Sm;0;ON;22B2 0338;;;;Y;;;;; 22EB;DOES NOT CONTAIN AS NORMAL SUBGROUP;Sm;0;ON;22B3 0338;;;;Y;;;;; 22EC;NOT NORMAL SUBGROUP OF OR EQUAL TO;Sm;0;ON;22B4 0338;;;;Y;;;;; 22ED;DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL;Sm;0;ON;22B5 0338;;;;Y;;;;; 22EE;VERTICAL ELLIPSIS;Sm;0;ON;;;;;N;;;;; 22EF;MIDLINE HORIZONTAL ELLIPSIS;Sm;0;ON;;;;;N;;;;; 22F0;UP RIGHT DIAGONAL ELLIPSIS;Sm;0;ON;;;;;Y;;;;; 22F1;DOWN RIGHT DIAGONAL ELLIPSIS;Sm;0;ON;;;;;Y;;;;; 22F2;ELEMENT OF WITH LONG HORIZONTAL STROKE;Sm;0;ON;;;;;Y;;;;; 22F3;ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE;Sm;0;ON;;;;;Y;;;;; 22F4;SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE;Sm;0;ON;;;;;Y;;;;; 22F5;ELEMENT OF WITH DOT ABOVE;Sm;0;ON;;;;;Y;;;;; 22F6;ELEMENT OF WITH OVERBAR;Sm;0;ON;;;;;Y;;;;; 22F7;SMALL ELEMENT OF WITH OVERBAR;Sm;0;ON;;;;;Y;;;;; 22F8;ELEMENT OF WITH UNDERBAR;Sm;0;ON;;;;;Y;;;;; 22F9;ELEMENT OF WITH TWO HORIZONTAL STROKES;Sm;0;ON;;;;;Y;;;;; 22FA;CONTAINS WITH LONG HORIZONTAL STROKE;Sm;0;ON;;;;;Y;;;;; 22FB;CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE;Sm;0;ON;;;;;Y;;;;; 22FC;SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE;Sm;0;ON;;;;;Y;;;;; 22FD;CONTAINS WITH OVERBAR;Sm;0;ON;;;;;Y;;;;; 22FE;SMALL CONTAINS WITH OVERBAR;Sm;0;ON;;;;;Y;;;;; 22FF;Z NOTATION BAG MEMBERSHIP;Sm;0;ON;;;;;Y;;;;; 2300;DIAMETER SIGN;So;0;ON;;;;;N;;;;; 2301;ELECTRIC ARROW;So;0;ON;;;;;N;;;;; 2302;HOUSE;So;0;ON;;;;;N;;;;; 2303;UP ARROWHEAD;So;0;ON;;;;;N;;;;; 2304;DOWN ARROWHEAD;So;0;ON;;;;;N;;;;; 2305;PROJECTIVE;So;0;ON;;;;;N;;;;; 2306;PERSPECTIVE;So;0;ON;;;;;N;;;;; 2307;WAVY LINE;So;0;ON;;;;;N;;;;; 2308;LEFT CEILING;Ps;0;ON;;;;;Y;;;;; 2309;RIGHT CEILING;Pe;0;ON;;;;;Y;;;;; 230A;LEFT FLOOR;Ps;0;ON;;;;;Y;;;;; 230B;RIGHT FLOOR;Pe;0;ON;;;;;Y;;;;; 230C;BOTTOM RIGHT CROP;So;0;ON;;;;;N;;;;; 230D;BOTTOM LEFT CROP;So;0;ON;;;;;N;;;;; 230E;TOP RIGHT CROP;So;0;ON;;;;;N;;;;; 230F;TOP LEFT CROP;So;0;ON;;;;;N;;;;; 2310;REVERSED NOT SIGN;So;0;ON;;;;;N;;;;; 2311;SQUARE LOZENGE;So;0;ON;;;;;N;;;;; 2312;ARC;So;0;ON;;;;;N;;;;; 2313;SEGMENT;So;0;ON;;;;;N;;;;; 2314;SECTOR;So;0;ON;;;;;N;;;;; 2315;TELEPHONE RECORDER;So;0;ON;;;;;N;;;;; 2316;POSITION INDICATOR;So;0;ON;;;;;N;;;;; 2317;VIEWDATA SQUARE;So;0;ON;;;;;N;;;;; 2318;PLACE OF INTEREST SIGN;So;0;ON;;;;;N;COMMAND KEY;;;; 2319;TURNED NOT SIGN;So;0;ON;;;;;N;;;;; 231A;WATCH;So;0;ON;;;;;N;;;;; 231B;HOURGLASS;So;0;ON;;;;;N;;;;; 231C;TOP LEFT CORNER;So;0;ON;;;;;N;;;;; 231D;TOP RIGHT CORNER;So;0;ON;;;;;N;;;;; 231E;BOTTOM LEFT CORNER;So;0;ON;;;;;N;;;;; 231F;BOTTOM RIGHT CORNER;So;0;ON;;;;;N;;;;; 2320;TOP HALF INTEGRAL;Sm;0;ON;;;;;Y;;;;; 2321;BOTTOM HALF INTEGRAL;Sm;0;ON;;;;;Y;;;;; 2322;FROWN;So;0;ON;;;;;N;;;;; 2323;SMILE;So;0;ON;;;;;N;;;;; 2324;UP ARROWHEAD BETWEEN TWO HORIZONTAL BARS;So;0;ON;;;;;N;ENTER KEY;;;; 2325;OPTION KEY;So;0;ON;;;;;N;;;;; 2326;ERASE TO THE RIGHT;So;0;ON;;;;;N;DELETE TO THE RIGHT KEY;;;; 2327;X IN A RECTANGLE BOX;So;0;ON;;;;;N;CLEAR KEY;;;; 2328;KEYBOARD;So;0;ON;;;;;N;;;;; 2329;LEFT-POINTING ANGLE BRACKET;Ps;0;ON;3008;;;;Y;BRA;;;; 232A;RIGHT-POINTING ANGLE BRACKET;Pe;0;ON;3009;;;;Y;KET;;;; 232B;ERASE TO THE LEFT;So;0;ON;;;;;N;DELETE TO THE LEFT KEY;;;; 232C;BENZENE RING;So;0;ON;;;;;N;;;;; 232D;CYLINDRICITY;So;0;ON;;;;;N;;;;; 232E;ALL AROUND-PROFILE;So;0;ON;;;;;N;;;;; 232F;SYMMETRY;So;0;ON;;;;;N;;;;; 2330;TOTAL RUNOUT;So;0;ON;;;;;N;;;;; 2331;DIMENSION ORIGIN;So;0;ON;;;;;N;;;;; 2332;CONICAL TAPER;So;0;ON;;;;;N;;;;; 2333;SLOPE;So;0;ON;;;;;N;;;;; 2334;COUNTERBORE;So;0;ON;;;;;N;;;;; 2335;COUNTERSINK;So;0;ON;;;;;N;;;;; 2336;APL FUNCTIONAL SYMBOL I-BEAM;So;0;L;;;;;N;;;;; 2337;APL FUNCTIONAL SYMBOL SQUISH QUAD;So;0;L;;;;;N;;;;; 2338;APL FUNCTIONAL SYMBOL QUAD EQUAL;So;0;L;;;;;N;;;;; 2339;APL FUNCTIONAL SYMBOL QUAD DIVIDE;So;0;L;;;;;N;;;;; 233A;APL FUNCTIONAL SYMBOL QUAD DIAMOND;So;0;L;;;;;N;;;;; 233B;APL FUNCTIONAL SYMBOL QUAD JOT;So;0;L;;;;;N;;;;; 233C;APL FUNCTIONAL SYMBOL QUAD CIRCLE;So;0;L;;;;;N;;;;; 233D;APL FUNCTIONAL SYMBOL CIRCLE STILE;So;0;L;;;;;N;;;;; 233E;APL FUNCTIONAL SYMBOL CIRCLE JOT;So;0;L;;;;;N;;;;; 233F;APL FUNCTIONAL SYMBOL SLASH BAR;So;0;L;;;;;N;;;;; 2340;APL FUNCTIONAL SYMBOL BACKSLASH BAR;So;0;L;;;;;N;;;;; 2341;APL FUNCTIONAL SYMBOL QUAD SLASH;So;0;L;;;;;N;;;;; 2342;APL FUNCTIONAL SYMBOL QUAD BACKSLASH;So;0;L;;;;;N;;;;; 2343;APL FUNCTIONAL SYMBOL QUAD LESS-THAN;So;0;L;;;;;N;;;;; 2344;APL FUNCTIONAL SYMBOL QUAD GREATER-THAN;So;0;L;;;;;N;;;;; 2345;APL FUNCTIONAL SYMBOL LEFTWARDS VANE;So;0;L;;;;;N;;;;; 2346;APL FUNCTIONAL SYMBOL RIGHTWARDS VANE;So;0;L;;;;;N;;;;; 2347;APL FUNCTIONAL SYMBOL QUAD LEFTWARDS ARROW;So;0;L;;;;;N;;;;; 2348;APL FUNCTIONAL SYMBOL QUAD RIGHTWARDS ARROW;So;0;L;;;;;N;;;;; 2349;APL FUNCTIONAL SYMBOL CIRCLE BACKSLASH;So;0;L;;;;;N;;;;; 234A;APL FUNCTIONAL SYMBOL DOWN TACK UNDERBAR;So;0;L;;;;;N;;;;; 234B;APL FUNCTIONAL SYMBOL DELTA STILE;So;0;L;;;;;N;;;;; 234C;APL FUNCTIONAL SYMBOL QUAD DOWN CARET;So;0;L;;;;;N;;;;; 234D;APL FUNCTIONAL SYMBOL QUAD DELTA;So;0;L;;;;;N;;;;; 234E;APL FUNCTIONAL SYMBOL DOWN TACK JOT;So;0;L;;;;;N;;;;; 234F;APL FUNCTIONAL SYMBOL UPWARDS VANE;So;0;L;;;;;N;;;;; 2350;APL FUNCTIONAL SYMBOL QUAD UPWARDS ARROW;So;0;L;;;;;N;;;;; 2351;APL FUNCTIONAL SYMBOL UP TACK OVERBAR;So;0;L;;;;;N;;;;; 2352;APL FUNCTIONAL SYMBOL DEL STILE;So;0;L;;;;;N;;;;; 2353;APL FUNCTIONAL SYMBOL QUAD UP CARET;So;0;L;;;;;N;;;;; 2354;APL FUNCTIONAL SYMBOL QUAD DEL;So;0;L;;;;;N;;;;; 2355;APL FUNCTIONAL SYMBOL UP TACK JOT;So;0;L;;;;;N;;;;; 2356;APL FUNCTIONAL SYMBOL DOWNWARDS VANE;So;0;L;;;;;N;;;;; 2357;APL FUNCTIONAL SYMBOL QUAD DOWNWARDS ARROW;So;0;L;;;;;N;;;;; 2358;APL FUNCTIONAL SYMBOL QUOTE UNDERBAR;So;0;L;;;;;N;;;;; 2359;APL FUNCTIONAL SYMBOL DELTA UNDERBAR;So;0;L;;;;;N;;;;; 235A;APL FUNCTIONAL SYMBOL DIAMOND UNDERBAR;So;0;L;;;;;N;;;;; 235B;APL FUNCTIONAL SYMBOL JOT UNDERBAR;So;0;L;;;;;N;;;;; 235C;APL FUNCTIONAL SYMBOL CIRCLE UNDERBAR;So;0;L;;;;;N;;;;; 235D;APL FUNCTIONAL SYMBOL UP SHOE JOT;So;0;L;;;;;N;;;;; 235E;APL FUNCTIONAL SYMBOL QUOTE QUAD;So;0;L;;;;;N;;;;; 235F;APL FUNCTIONAL SYMBOL CIRCLE STAR;So;0;L;;;;;N;;;;; 2360;APL FUNCTIONAL SYMBOL QUAD COLON;So;0;L;;;;;N;;;;; 2361;APL FUNCTIONAL SYMBOL UP TACK DIAERESIS;So;0;L;;;;;N;;;;; 2362;APL FUNCTIONAL SYMBOL DEL DIAERESIS;So;0;L;;;;;N;;;;; 2363;APL FUNCTIONAL SYMBOL STAR DIAERESIS;So;0;L;;;;;N;;;;; 2364;APL FUNCTIONAL SYMBOL JOT DIAERESIS;So;0;L;;;;;N;;;;; 2365;APL FUNCTIONAL SYMBOL CIRCLE DIAERESIS;So;0;L;;;;;N;;;;; 2366;APL FUNCTIONAL SYMBOL DOWN SHOE STILE;So;0;L;;;;;N;;;;; 2367;APL FUNCTIONAL SYMBOL LEFT SHOE STILE;So;0;L;;;;;N;;;;; 2368;APL FUNCTIONAL SYMBOL TILDE DIAERESIS;So;0;L;;;;;N;;;;; 2369;APL FUNCTIONAL SYMBOL GREATER-THAN DIAERESIS;So;0;L;;;;;N;;;;; 236A;APL FUNCTIONAL SYMBOL COMMA BAR;So;0;L;;;;;N;;;;; 236B;APL FUNCTIONAL SYMBOL DEL TILDE;So;0;L;;;;;N;;;;; 236C;APL FUNCTIONAL SYMBOL ZILDE;So;0;L;;;;;N;;;;; 236D;APL FUNCTIONAL SYMBOL STILE TILDE;So;0;L;;;;;N;;;;; 236E;APL FUNCTIONAL SYMBOL SEMICOLON UNDERBAR;So;0;L;;;;;N;;;;; 236F;APL FUNCTIONAL SYMBOL QUAD NOT EQUAL;So;0;L;;;;;N;;;;; 2370;APL FUNCTIONAL SYMBOL QUAD QUESTION;So;0;L;;;;;N;;;;; 2371;APL FUNCTIONAL SYMBOL DOWN CARET TILDE;So;0;L;;;;;N;;;;; 2372;APL FUNCTIONAL SYMBOL UP CARET TILDE;So;0;L;;;;;N;;;;; 2373;APL FUNCTIONAL SYMBOL IOTA;So;0;L;;;;;N;;;;; 2374;APL FUNCTIONAL SYMBOL RHO;So;0;L;;;;;N;;;;; 2375;APL FUNCTIONAL SYMBOL OMEGA;So;0;L;;;;;N;;;;; 2376;APL FUNCTIONAL SYMBOL ALPHA UNDERBAR;So;0;L;;;;;N;;;;; 2377;APL FUNCTIONAL SYMBOL EPSILON UNDERBAR;So;0;L;;;;;N;;;;; 2378;APL FUNCTIONAL SYMBOL IOTA UNDERBAR;So;0;L;;;;;N;;;;; 2379;APL FUNCTIONAL SYMBOL OMEGA UNDERBAR;So;0;L;;;;;N;;;;; 237A;APL FUNCTIONAL SYMBOL ALPHA;So;0;L;;;;;N;;;;; 237B;NOT CHECK MARK;So;0;ON;;;;;N;;;;; 237C;RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW;Sm;0;ON;;;;;N;;;;; 237D;SHOULDERED OPEN BOX;So;0;ON;;;;;N;;;;; 237E;BELL SYMBOL;So;0;ON;;;;;N;;;;; 237F;VERTICAL LINE WITH MIDDLE DOT;So;0;ON;;;;;N;;;;; 2380;INSERTION SYMBOL;So;0;ON;;;;;N;;;;; 2381;CONTINUOUS UNDERLINE SYMBOL;So;0;ON;;;;;N;;;;; 2382;DISCONTINUOUS UNDERLINE SYMBOL;So;0;ON;;;;;N;;;;; 2383;EMPHASIS SYMBOL;So;0;ON;;;;;N;;;;; 2384;COMPOSITION SYMBOL;So;0;ON;;;;;N;;;;; 2385;WHITE SQUARE WITH CENTRE VERTICAL LINE;So;0;ON;;;;;N;;;;; 2386;ENTER SYMBOL;So;0;ON;;;;;N;;;;; 2387;ALTERNATIVE KEY SYMBOL;So;0;ON;;;;;N;;;;; 2388;HELM SYMBOL;So;0;ON;;;;;N;;;;; 2389;CIRCLED HORIZONTAL BAR WITH NOTCH;So;0;ON;;;;;N;;;;; 238A;CIRCLED TRIANGLE DOWN;So;0;ON;;;;;N;;;;; 238B;BROKEN CIRCLE WITH NORTHWEST ARROW;So;0;ON;;;;;N;;;;; 238C;UNDO SYMBOL;So;0;ON;;;;;N;;;;; 238D;MONOSTABLE SYMBOL;So;0;ON;;;;;N;;;;; 238E;HYSTERESIS SYMBOL;So;0;ON;;;;;N;;;;; 238F;OPEN-CIRCUIT-OUTPUT H-TYPE SYMBOL;So;0;ON;;;;;N;;;;; 2390;OPEN-CIRCUIT-OUTPUT L-TYPE SYMBOL;So;0;ON;;;;;N;;;;; 2391;PASSIVE-PULL-DOWN-OUTPUT SYMBOL;So;0;ON;;;;;N;;;;; 2392;PASSIVE-PULL-UP-OUTPUT SYMBOL;So;0;ON;;;;;N;;;;; 2393;DIRECT CURRENT SYMBOL FORM TWO;So;0;ON;;;;;N;;;;; 2394;SOFTWARE-FUNCTION SYMBOL;So;0;ON;;;;;N;;;;; 2395;APL FUNCTIONAL SYMBOL QUAD;So;0;L;;;;;N;;;;; 2396;DECIMAL SEPARATOR KEY SYMBOL;So;0;ON;;;;;N;;;;; 2397;PREVIOUS PAGE;So;0;ON;;;;;N;;;;; 2398;NEXT PAGE;So;0;ON;;;;;N;;;;; 2399;PRINT SCREEN SYMBOL;So;0;ON;;;;;N;;;;; 239A;CLEAR SCREEN SYMBOL;So;0;ON;;;;;N;;;;; 239B;LEFT PARENTHESIS UPPER HOOK;Sm;0;ON;;;;;N;;;;; 239C;LEFT PARENTHESIS EXTENSION;Sm;0;ON;;;;;N;;;;; 239D;LEFT PARENTHESIS LOWER HOOK;Sm;0;ON;;;;;N;;;;; 239E;RIGHT PARENTHESIS UPPER HOOK;Sm;0;ON;;;;;N;;;;; 239F;RIGHT PARENTHESIS EXTENSION;Sm;0;ON;;;;;N;;;;; 23A0;RIGHT PARENTHESIS LOWER HOOK;Sm;0;ON;;;;;N;;;;; 23A1;LEFT SQUARE BRACKET UPPER CORNER;Sm;0;ON;;;;;N;;;;; 23A2;LEFT SQUARE BRACKET EXTENSION;Sm;0;ON;;;;;N;;;;; 23A3;LEFT SQUARE BRACKET LOWER CORNER;Sm;0;ON;;;;;N;;;;; 23A4;RIGHT SQUARE BRACKET UPPER CORNER;Sm;0;ON;;;;;N;;;;; 23A5;RIGHT SQUARE BRACKET EXTENSION;Sm;0;ON;;;;;N;;;;; 23A6;RIGHT SQUARE BRACKET LOWER CORNER;Sm;0;ON;;;;;N;;;;; 23A7;LEFT CURLY BRACKET UPPER HOOK;Sm;0;ON;;;;;N;;;;; 23A8;LEFT CURLY BRACKET MIDDLE PIECE;Sm;0;ON;;;;;N;;;;; 23A9;LEFT CURLY BRACKET LOWER HOOK;Sm;0;ON;;;;;N;;;;; 23AA;CURLY BRACKET EXTENSION;Sm;0;ON;;;;;N;;;;; 23AB;RIGHT CURLY BRACKET UPPER HOOK;Sm;0;ON;;;;;N;;;;; 23AC;RIGHT CURLY BRACKET MIDDLE PIECE;Sm;0;ON;;;;;N;;;;; 23AD;RIGHT CURLY BRACKET LOWER HOOK;Sm;0;ON;;;;;N;;;;; 23AE;INTEGRAL EXTENSION;Sm;0;ON;;;;;N;;;;; 23AF;HORIZONTAL LINE EXTENSION;Sm;0;ON;;;;;N;;;;; 23B0;UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION;Sm;0;ON;;;;;N;;;;; 23B1;UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION;Sm;0;ON;;;;;N;;;;; 23B2;SUMMATION TOP;Sm;0;ON;;;;;N;;;;; 23B3;SUMMATION BOTTOM;Sm;0;ON;;;;;N;;;;; 23B4;TOP SQUARE BRACKET;So;0;ON;;;;;N;;;;; 23B5;BOTTOM SQUARE BRACKET;So;0;ON;;;;;N;;;;; 23B6;BOTTOM SQUARE BRACKET OVER TOP SQUARE BRACKET;So;0;ON;;;;;N;;;;; 23B7;RADICAL SYMBOL BOTTOM;So;0;ON;;;;;N;;;;; 23B8;LEFT VERTICAL BOX LINE;So;0;ON;;;;;N;;;;; 23B9;RIGHT VERTICAL BOX LINE;So;0;ON;;;;;N;;;;; 23BA;HORIZONTAL SCAN LINE-1;So;0;ON;;;;;N;;;;; 23BB;HORIZONTAL SCAN LINE-3;So;0;ON;;;;;N;;;;; 23BC;HORIZONTAL SCAN LINE-7;So;0;ON;;;;;N;;;;; 23BD;HORIZONTAL SCAN LINE-9;So;0;ON;;;;;N;;;;; 23BE;DENTISTRY SYMBOL LIGHT VERTICAL AND TOP RIGHT;So;0;ON;;;;;N;;;;; 23BF;DENTISTRY SYMBOL LIGHT VERTICAL AND BOTTOM RIGHT;So;0;ON;;;;;N;;;;; 23C0;DENTISTRY SYMBOL LIGHT VERTICAL WITH CIRCLE;So;0;ON;;;;;N;;;;; 23C1;DENTISTRY SYMBOL LIGHT DOWN AND HORIZONTAL WITH CIRCLE;So;0;ON;;;;;N;;;;; 23C2;DENTISTRY SYMBOL LIGHT UP AND HORIZONTAL WITH CIRCLE;So;0;ON;;;;;N;;;;; 23C3;DENTISTRY SYMBOL LIGHT VERTICAL WITH TRIANGLE;So;0;ON;;;;;N;;;;; 23C4;DENTISTRY SYMBOL LIGHT DOWN AND HORIZONTAL WITH TRIANGLE;So;0;ON;;;;;N;;;;; 23C5;DENTISTRY SYMBOL LIGHT UP AND HORIZONTAL WITH TRIANGLE;So;0;ON;;;;;N;;;;; 23C6;DENTISTRY SYMBOL LIGHT VERTICAL AND WAVE;So;0;ON;;;;;N;;;;; 23C7;DENTISTRY SYMBOL LIGHT DOWN AND HORIZONTAL WITH WAVE;So;0;ON;;;;;N;;;;; 23C8;DENTISTRY SYMBOL LIGHT UP AND HORIZONTAL WITH WAVE;So;0;ON;;;;;N;;;;; 23C9;DENTISTRY SYMBOL LIGHT DOWN AND HORIZONTAL;So;0;ON;;;;;N;;;;; 23CA;DENTISTRY SYMBOL LIGHT UP AND HORIZONTAL;So;0;ON;;;;;N;;;;; 23CB;DENTISTRY SYMBOL LIGHT VERTICAL AND TOP LEFT;So;0;ON;;;;;N;;;;; 23CC;DENTISTRY SYMBOL LIGHT VERTICAL AND BOTTOM LEFT;So;0;ON;;;;;N;;;;; 23CD;SQUARE FOOT;So;0;ON;;;;;N;;;;; 23CE;RETURN SYMBOL;So;0;ON;;;;;N;;;;; 23CF;EJECT SYMBOL;So;0;ON;;;;;N;;;;; 23D0;VERTICAL LINE EXTENSION;So;0;ON;;;;;N;;;;; 23D1;METRICAL BREVE;So;0;ON;;;;;N;;;;; 23D2;METRICAL LONG OVER SHORT;So;0;ON;;;;;N;;;;; 23D3;METRICAL SHORT OVER LONG;So;0;ON;;;;;N;;;;; 23D4;METRICAL LONG OVER TWO SHORTS;So;0;ON;;;;;N;;;;; 23D5;METRICAL TWO SHORTS OVER LONG;So;0;ON;;;;;N;;;;; 23D6;METRICAL TWO SHORTS JOINED;So;0;ON;;;;;N;;;;; 23D7;METRICAL TRISEME;So;0;ON;;;;;N;;;;; 23D8;METRICAL TETRASEME;So;0;ON;;;;;N;;;;; 23D9;METRICAL PENTASEME;So;0;ON;;;;;N;;;;; 23DA;EARTH GROUND;So;0;ON;;;;;N;;;;; 23DB;FUSE;So;0;ON;;;;;N;;;;; 23DC;TOP PARENTHESIS;Sm;0;ON;;;;;N;;;;; 23DD;BOTTOM PARENTHESIS;Sm;0;ON;;;;;N;;;;; 23DE;TOP CURLY BRACKET;Sm;0;ON;;;;;N;;;;; 23DF;BOTTOM CURLY BRACKET;Sm;0;ON;;;;;N;;;;; 23E0;TOP TORTOISE SHELL BRACKET;Sm;0;ON;;;;;N;;;;; 23E1;BOTTOM TORTOISE SHELL BRACKET;Sm;0;ON;;;;;N;;;;; 23E2;WHITE TRAPEZIUM;So;0;ON;;;;;N;;;;; 23E3;BENZENE RING WITH CIRCLE;So;0;ON;;;;;N;;;;; 23E4;STRAIGHTNESS;So;0;ON;;;;;N;;;;; 23E5;FLATNESS;So;0;ON;;;;;N;;;;; 23E6;AC CURRENT;So;0;ON;;;;;N;;;;; 23E7;ELECTRICAL INTERSECTION;So;0;ON;;;;;N;;;;; 23E8;DECIMAL EXPONENT SYMBOL;So;0;ON;;;;;N;;;;; 23E9;BLACK RIGHT-POINTING DOUBLE TRIANGLE;So;0;ON;;;;;N;;;;; 23EA;BLACK LEFT-POINTING DOUBLE TRIANGLE;So;0;ON;;;;;N;;;;; 23EB;BLACK UP-POINTING DOUBLE TRIANGLE;So;0;ON;;;;;N;;;;; 23EC;BLACK DOWN-POINTING DOUBLE TRIANGLE;So;0;ON;;;;;N;;;;; 23ED;BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR;So;0;ON;;;;;N;;;;; 23EE;BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR;So;0;ON;;;;;N;;;;; 23EF;BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR;So;0;ON;;;;;N;;;;; 23F0;ALARM CLOCK;So;0;ON;;;;;N;;;;; 23F1;STOPWATCH;So;0;ON;;;;;N;;;;; 23F2;TIMER CLOCK;So;0;ON;;;;;N;;;;; 23F3;HOURGLASS WITH FLOWING SAND;So;0;ON;;;;;N;;;;; 23F4;BLACK MEDIUM LEFT-POINTING TRIANGLE;So;0;ON;;;;;N;;;;; 23F5;BLACK MEDIUM RIGHT-POINTING TRIANGLE;So;0;ON;;;;;N;;;;; 23F6;BLACK MEDIUM UP-POINTING TRIANGLE;So;0;ON;;;;;N;;;;; 23F7;BLACK MEDIUM DOWN-POINTING TRIANGLE;So;0;ON;;;;;N;;;;; 23F8;DOUBLE VERTICAL BAR;So;0;ON;;;;;N;;;;; 23F9;BLACK SQUARE FOR STOP;So;0;ON;;;;;N;;;;; 23FA;BLACK CIRCLE FOR RECORD;So;0;ON;;;;;N;;;;; 23FB;POWER SYMBOL;So;0;ON;;;;;N;;;;; 23FC;POWER ON-OFF SYMBOL;So;0;ON;;;;;N;;;;; 23FD;POWER ON SYMBOL;So;0;ON;;;;;N;;;;; 23FE;POWER SLEEP SYMBOL;So;0;ON;;;;;N;;;;; 2400;SYMBOL FOR NULL;So;0;ON;;;;;N;GRAPHIC FOR NULL;;;; 2401;SYMBOL FOR START OF HEADING;So;0;ON;;;;;N;GRAPHIC FOR START OF HEADING;;;; 2402;SYMBOL FOR START OF TEXT;So;0;ON;;;;;N;GRAPHIC FOR START OF TEXT;;;; 2403;SYMBOL FOR END OF TEXT;So;0;ON;;;;;N;GRAPHIC FOR END OF TEXT;;;; 2404;SYMBOL FOR END OF TRANSMISSION;So;0;ON;;;;;N;GRAPHIC FOR END OF TRANSMISSION;;;; 2405;SYMBOL FOR ENQUIRY;So;0;ON;;;;;N;GRAPHIC FOR ENQUIRY;;;; 2406;SYMBOL FOR ACKNOWLEDGE;So;0;ON;;;;;N;GRAPHIC FOR ACKNOWLEDGE;;;; 2407;SYMBOL FOR BELL;So;0;ON;;;;;N;GRAPHIC FOR BELL;;;; 2408;SYMBOL FOR BACKSPACE;So;0;ON;;;;;N;GRAPHIC FOR BACKSPACE;;;; 2409;SYMBOL FOR HORIZONTAL TABULATION;So;0;ON;;;;;N;GRAPHIC FOR HORIZONTAL TABULATION;;;; 240A;SYMBOL FOR LINE FEED;So;0;ON;;;;;N;GRAPHIC FOR LINE FEED;;;; 240B;SYMBOL FOR VERTICAL TABULATION;So;0;ON;;;;;N;GRAPHIC FOR VERTICAL TABULATION;;;; 240C;SYMBOL FOR FORM FEED;So;0;ON;;;;;N;GRAPHIC FOR FORM FEED;;;; 240D;SYMBOL FOR CARRIAGE RETURN;So;0;ON;;;;;N;GRAPHIC FOR CARRIAGE RETURN;;;; 240E;SYMBOL FOR SHIFT OUT;So;0;ON;;;;;N;GRAPHIC FOR SHIFT OUT;;;; 240F;SYMBOL FOR SHIFT IN;So;0;ON;;;;;N;GRAPHIC FOR SHIFT IN;;;; 2410;SYMBOL FOR DATA LINK ESCAPE;So;0;ON;;;;;N;GRAPHIC FOR DATA LINK ESCAPE;;;; 2411;SYMBOL FOR DEVICE CONTROL ONE;So;0;ON;;;;;N;GRAPHIC FOR DEVICE CONTROL ONE;;;; 2412;SYMBOL FOR DEVICE CONTROL TWO;So;0;ON;;;;;N;GRAPHIC FOR DEVICE CONTROL TWO;;;; 2413;SYMBOL FOR DEVICE CONTROL THREE;So;0;ON;;;;;N;GRAPHIC FOR DEVICE CONTROL THREE;;;; 2414;SYMBOL FOR DEVICE CONTROL FOUR;So;0;ON;;;;;N;GRAPHIC FOR DEVICE CONTROL FOUR;;;; 2415;SYMBOL FOR NEGATIVE ACKNOWLEDGE;So;0;ON;;;;;N;GRAPHIC FOR NEGATIVE ACKNOWLEDGE;;;; 2416;SYMBOL FOR SYNCHRONOUS IDLE;So;0;ON;;;;;N;GRAPHIC FOR SYNCHRONOUS IDLE;;;; 2417;SYMBOL FOR END OF TRANSMISSION BLOCK;So;0;ON;;;;;N;GRAPHIC FOR END OF TRANSMISSION BLOCK;;;; 2418;SYMBOL FOR CANCEL;So;0;ON;;;;;N;GRAPHIC FOR CANCEL;;;; 2419;SYMBOL FOR END OF MEDIUM;So;0;ON;;;;;N;GRAPHIC FOR END OF MEDIUM;;;; 241A;SYMBOL FOR SUBSTITUTE;So;0;ON;;;;;N;GRAPHIC FOR SUBSTITUTE;;;; 241B;SYMBOL FOR ESCAPE;So;0;ON;;;;;N;GRAPHIC FOR ESCAPE;;;; 241C;SYMBOL FOR FILE SEPARATOR;So;0;ON;;;;;N;GRAPHIC FOR FILE SEPARATOR;;;; 241D;SYMBOL FOR GROUP SEPARATOR;So;0;ON;;;;;N;GRAPHIC FOR GROUP SEPARATOR;;;; 241E;SYMBOL FOR RECORD SEPARATOR;So;0;ON;;;;;N;GRAPHIC FOR RECORD SEPARATOR;;;; 241F;SYMBOL FOR UNIT SEPARATOR;So;0;ON;;;;;N;GRAPHIC FOR UNIT SEPARATOR;;;; 2420;SYMBOL FOR SPACE;So;0;ON;;;;;N;GRAPHIC FOR SPACE;;;; 2421;SYMBOL FOR DELETE;So;0;ON;;;;;N;GRAPHIC FOR DELETE;;;; 2422;BLANK SYMBOL;So;0;ON;;;;;N;BLANK;;;; 2423;OPEN BOX;So;0;ON;;;;;N;;;;; 2424;SYMBOL FOR NEWLINE;So;0;ON;;;;;N;GRAPHIC FOR NEWLINE;;;; 2425;SYMBOL FOR DELETE FORM TWO;So;0;ON;;;;;N;;;;; 2426;SYMBOL FOR SUBSTITUTE FORM TWO;So;0;ON;;;;;N;;;;; 2440;OCR HOOK;So;0;ON;;;;;N;;;;; 2441;OCR CHAIR;So;0;ON;;;;;N;;;;; 2442;OCR FORK;So;0;ON;;;;;N;;;;; 2443;OCR INVERTED FORK;So;0;ON;;;;;N;;;;; 2444;OCR BELT BUCKLE;So;0;ON;;;;;N;;;;; 2445;OCR BOW TIE;So;0;ON;;;;;N;;;;; 2446;OCR BRANCH BANK IDENTIFICATION;So;0;ON;;;;;N;;;;; 2447;OCR AMOUNT OF CHECK;So;0;ON;;;;;N;;;;; 2448;OCR DASH;So;0;ON;;;;;N;;;;; 2449;OCR CUSTOMER ACCOUNT NUMBER;So;0;ON;;;;;N;;;;; 244A;OCR DOUBLE BACKSLASH;So;0;ON;;;;;N;;;;; 2460;CIRCLED DIGIT ONE;No;0;ON; 0031;;1;1;N;;;;; 2461;CIRCLED DIGIT TWO;No;0;ON; 0032;;2;2;N;;;;; 2462;CIRCLED DIGIT THREE;No;0;ON; 0033;;3;3;N;;;;; 2463;CIRCLED DIGIT FOUR;No;0;ON; 0034;;4;4;N;;;;; 2464;CIRCLED DIGIT FIVE;No;0;ON; 0035;;5;5;N;;;;; 2465;CIRCLED DIGIT SIX;No;0;ON; 0036;;6;6;N;;;;; 2466;CIRCLED DIGIT SEVEN;No;0;ON; 0037;;7;7;N;;;;; 2467;CIRCLED DIGIT EIGHT;No;0;ON; 0038;;8;8;N;;;;; 2468;CIRCLED DIGIT NINE;No;0;ON; 0039;;9;9;N;;;;; 2469;CIRCLED NUMBER TEN;No;0;ON; 0031 0030;;;10;N;;;;; 246A;CIRCLED NUMBER ELEVEN;No;0;ON; 0031 0031;;;11;N;;;;; 246B;CIRCLED NUMBER TWELVE;No;0;ON; 0031 0032;;;12;N;;;;; 246C;CIRCLED NUMBER THIRTEEN;No;0;ON; 0031 0033;;;13;N;;;;; 246D;CIRCLED NUMBER FOURTEEN;No;0;ON; 0031 0034;;;14;N;;;;; 246E;CIRCLED NUMBER FIFTEEN;No;0;ON; 0031 0035;;;15;N;;;;; 246F;CIRCLED NUMBER SIXTEEN;No;0;ON; 0031 0036;;;16;N;;;;; 2470;CIRCLED NUMBER SEVENTEEN;No;0;ON; 0031 0037;;;17;N;;;;; 2471;CIRCLED NUMBER EIGHTEEN;No;0;ON; 0031 0038;;;18;N;;;;; 2472;CIRCLED NUMBER NINETEEN;No;0;ON; 0031 0039;;;19;N;;;;; 2473;CIRCLED NUMBER TWENTY;No;0;ON; 0032 0030;;;20;N;;;;; 2474;PARENTHESIZED DIGIT ONE;No;0;ON; 0028 0031 0029;;1;1;N;;;;; 2475;PARENTHESIZED DIGIT TWO;No;0;ON; 0028 0032 0029;;2;2;N;;;;; 2476;PARENTHESIZED DIGIT THREE;No;0;ON; 0028 0033 0029;;3;3;N;;;;; 2477;PARENTHESIZED DIGIT FOUR;No;0;ON; 0028 0034 0029;;4;4;N;;;;; 2478;PARENTHESIZED DIGIT FIVE;No;0;ON; 0028 0035 0029;;5;5;N;;;;; 2479;PARENTHESIZED DIGIT SIX;No;0;ON; 0028 0036 0029;;6;6;N;;;;; 247A;PARENTHESIZED DIGIT SEVEN;No;0;ON; 0028 0037 0029;;7;7;N;;;;; 247B;PARENTHESIZED DIGIT EIGHT;No;0;ON; 0028 0038 0029;;8;8;N;;;;; 247C;PARENTHESIZED DIGIT NINE;No;0;ON; 0028 0039 0029;;9;9;N;;;;; 247D;PARENTHESIZED NUMBER TEN;No;0;ON; 0028 0031 0030 0029;;;10;N;;;;; 247E;PARENTHESIZED NUMBER ELEVEN;No;0;ON; 0028 0031 0031 0029;;;11;N;;;;; 247F;PARENTHESIZED NUMBER TWELVE;No;0;ON; 0028 0031 0032 0029;;;12;N;;;;; 2480;PARENTHESIZED NUMBER THIRTEEN;No;0;ON; 0028 0031 0033 0029;;;13;N;;;;; 2481;PARENTHESIZED NUMBER FOURTEEN;No;0;ON; 0028 0031 0034 0029;;;14;N;;;;; 2482;PARENTHESIZED NUMBER FIFTEEN;No;0;ON; 0028 0031 0035 0029;;;15;N;;;;; 2483;PARENTHESIZED NUMBER SIXTEEN;No;0;ON; 0028 0031 0036 0029;;;16;N;;;;; 2484;PARENTHESIZED NUMBER SEVENTEEN;No;0;ON; 0028 0031 0037 0029;;;17;N;;;;; 2485;PARENTHESIZED NUMBER EIGHTEEN;No;0;ON; 0028 0031 0038 0029;;;18;N;;;;; 2486;PARENTHESIZED NUMBER NINETEEN;No;0;ON; 0028 0031 0039 0029;;;19;N;;;;; 2487;PARENTHESIZED NUMBER TWENTY;No;0;ON; 0028 0032 0030 0029;;;20;N;;;;; 2488;DIGIT ONE FULL STOP;No;0;EN; 0031 002E;;1;1;N;DIGIT ONE PERIOD;;;; 2489;DIGIT TWO FULL STOP;No;0;EN; 0032 002E;;2;2;N;DIGIT TWO PERIOD;;;; 248A;DIGIT THREE FULL STOP;No;0;EN; 0033 002E;;3;3;N;DIGIT THREE PERIOD;;;; 248B;DIGIT FOUR FULL STOP;No;0;EN; 0034 002E;;4;4;N;DIGIT FOUR PERIOD;;;; 248C;DIGIT FIVE FULL STOP;No;0;EN; 0035 002E;;5;5;N;DIGIT FIVE PERIOD;;;; 248D;DIGIT SIX FULL STOP;No;0;EN; 0036 002E;;6;6;N;DIGIT SIX PERIOD;;;; 248E;DIGIT SEVEN FULL STOP;No;0;EN; 0037 002E;;7;7;N;DIGIT SEVEN PERIOD;;;; 248F;DIGIT EIGHT FULL STOP;No;0;EN; 0038 002E;;8;8;N;DIGIT EIGHT PERIOD;;;; 2490;DIGIT NINE FULL STOP;No;0;EN; 0039 002E;;9;9;N;DIGIT NINE PERIOD;;;; 2491;NUMBER TEN FULL STOP;No;0;EN; 0031 0030 002E;;;10;N;NUMBER TEN PERIOD;;;; 2492;NUMBER ELEVEN FULL STOP;No;0;EN; 0031 0031 002E;;;11;N;NUMBER ELEVEN PERIOD;;;; 2493;NUMBER TWELVE FULL STOP;No;0;EN; 0031 0032 002E;;;12;N;NUMBER TWELVE PERIOD;;;; 2494;NUMBER THIRTEEN FULL STOP;No;0;EN; 0031 0033 002E;;;13;N;NUMBER THIRTEEN PERIOD;;;; 2495;NUMBER FOURTEEN FULL STOP;No;0;EN; 0031 0034 002E;;;14;N;NUMBER FOURTEEN PERIOD;;;; 2496;NUMBER FIFTEEN FULL STOP;No;0;EN; 0031 0035 002E;;;15;N;NUMBER FIFTEEN PERIOD;;;; 2497;NUMBER SIXTEEN FULL STOP;No;0;EN; 0031 0036 002E;;;16;N;NUMBER SIXTEEN PERIOD;;;; 2498;NUMBER SEVENTEEN FULL STOP;No;0;EN; 0031 0037 002E;;;17;N;NUMBER SEVENTEEN PERIOD;;;; 2499;NUMBER EIGHTEEN FULL STOP;No;0;EN; 0031 0038 002E;;;18;N;NUMBER EIGHTEEN PERIOD;;;; 249A;NUMBER NINETEEN FULL STOP;No;0;EN; 0031 0039 002E;;;19;N;NUMBER NINETEEN PERIOD;;;; 249B;NUMBER TWENTY FULL STOP;No;0;EN; 0032 0030 002E;;;20;N;NUMBER TWENTY PERIOD;;;; 249C;PARENTHESIZED LATIN SMALL LETTER A;So;0;L; 0028 0061 0029;;;;N;;;;; 249D;PARENTHESIZED LATIN SMALL LETTER B;So;0;L; 0028 0062 0029;;;;N;;;;; 249E;PARENTHESIZED LATIN SMALL LETTER C;So;0;L; 0028 0063 0029;;;;N;;;;; 249F;PARENTHESIZED LATIN SMALL LETTER D;So;0;L; 0028 0064 0029;;;;N;;;;; 24A0;PARENTHESIZED LATIN SMALL LETTER E;So;0;L; 0028 0065 0029;;;;N;;;;; 24A1;PARENTHESIZED LATIN SMALL LETTER F;So;0;L; 0028 0066 0029;;;;N;;;;; 24A2;PARENTHESIZED LATIN SMALL LETTER G;So;0;L; 0028 0067 0029;;;;N;;;;; 24A3;PARENTHESIZED LATIN SMALL LETTER H;So;0;L; 0028 0068 0029;;;;N;;;;; 24A4;PARENTHESIZED LATIN SMALL LETTER I;So;0;L; 0028 0069 0029;;;;N;;;;; 24A5;PARENTHESIZED LATIN SMALL LETTER J;So;0;L; 0028 006A 0029;;;;N;;;;; 24A6;PARENTHESIZED LATIN SMALL LETTER K;So;0;L; 0028 006B 0029;;;;N;;;;; 24A7;PARENTHESIZED LATIN SMALL LETTER L;So;0;L; 0028 006C 0029;;;;N;;;;; 24A8;PARENTHESIZED LATIN SMALL LETTER M;So;0;L; 0028 006D 0029;;;;N;;;;; 24A9;PARENTHESIZED LATIN SMALL LETTER N;So;0;L; 0028 006E 0029;;;;N;;;;; 24AA;PARENTHESIZED LATIN SMALL LETTER O;So;0;L; 0028 006F 0029;;;;N;;;;; 24AB;PARENTHESIZED LATIN SMALL LETTER P;So;0;L; 0028 0070 0029;;;;N;;;;; 24AC;PARENTHESIZED LATIN SMALL LETTER Q;So;0;L; 0028 0071 0029;;;;N;;;;; 24AD;PARENTHESIZED LATIN SMALL LETTER R;So;0;L; 0028 0072 0029;;;;N;;;;; 24AE;PARENTHESIZED LATIN SMALL LETTER S;So;0;L; 0028 0073 0029;;;;N;;;;; 24AF;PARENTHESIZED LATIN SMALL LETTER T;So;0;L; 0028 0074 0029;;;;N;;;;; 24B0;PARENTHESIZED LATIN SMALL LETTER U;So;0;L; 0028 0075 0029;;;;N;;;;; 24B1;PARENTHESIZED LATIN SMALL LETTER V;So;0;L; 0028 0076 0029;;;;N;;;;; 24B2;PARENTHESIZED LATIN SMALL LETTER W;So;0;L; 0028 0077 0029;;;;N;;;;; 24B3;PARENTHESIZED LATIN SMALL LETTER X;So;0;L; 0028 0078 0029;;;;N;;;;; 24B4;PARENTHESIZED LATIN SMALL LETTER Y;So;0;L; 0028 0079 0029;;;;N;;;;; 24B5;PARENTHESIZED LATIN SMALL LETTER Z;So;0;L; 0028 007A 0029;;;;N;;;;; 24B6;CIRCLED LATIN CAPITAL LETTER A;So;0;L; 0041;;;;N;;;;24D0; 24B7;CIRCLED LATIN CAPITAL LETTER B;So;0;L; 0042;;;;N;;;;24D1; 24B8;CIRCLED LATIN CAPITAL LETTER C;So;0;L; 0043;;;;N;;;;24D2; 24B9;CIRCLED LATIN CAPITAL LETTER D;So;0;L; 0044;;;;N;;;;24D3; 24BA;CIRCLED LATIN CAPITAL LETTER E;So;0;L; 0045;;;;N;;;;24D4; 24BB;CIRCLED LATIN CAPITAL LETTER F;So;0;L; 0046;;;;N;;;;24D5; 24BC;CIRCLED LATIN CAPITAL LETTER G;So;0;L; 0047;;;;N;;;;24D6; 24BD;CIRCLED LATIN CAPITAL LETTER H;So;0;L; 0048;;;;N;;;;24D7; 24BE;CIRCLED LATIN CAPITAL LETTER I;So;0;L; 0049;;;;N;;;;24D8; 24BF;CIRCLED LATIN CAPITAL LETTER J;So;0;L; 004A;;;;N;;;;24D9; 24C0;CIRCLED LATIN CAPITAL LETTER K;So;0;L; 004B;;;;N;;;;24DA; 24C1;CIRCLED LATIN CAPITAL LETTER L;So;0;L; 004C;;;;N;;;;24DB; 24C2;CIRCLED LATIN CAPITAL LETTER M;So;0;L; 004D;;;;N;;;;24DC; 24C3;CIRCLED LATIN CAPITAL LETTER N;So;0;L; 004E;;;;N;;;;24DD; 24C4;CIRCLED LATIN CAPITAL LETTER O;So;0;L; 004F;;;;N;;;;24DE; 24C5;CIRCLED LATIN CAPITAL LETTER P;So;0;L; 0050;;;;N;;;;24DF; 24C6;CIRCLED LATIN CAPITAL LETTER Q;So;0;L; 0051;;;;N;;;;24E0; 24C7;CIRCLED LATIN CAPITAL LETTER R;So;0;L; 0052;;;;N;;;;24E1; 24C8;CIRCLED LATIN CAPITAL LETTER S;So;0;L; 0053;;;;N;;;;24E2; 24C9;CIRCLED LATIN CAPITAL LETTER T;So;0;L; 0054;;;;N;;;;24E3; 24CA;CIRCLED LATIN CAPITAL LETTER U;So;0;L; 0055;;;;N;;;;24E4; 24CB;CIRCLED LATIN CAPITAL LETTER V;So;0;L; 0056;;;;N;;;;24E5; 24CC;CIRCLED LATIN CAPITAL LETTER W;So;0;L; 0057;;;;N;;;;24E6; 24CD;CIRCLED LATIN CAPITAL LETTER X;So;0;L; 0058;;;;N;;;;24E7; 24CE;CIRCLED LATIN CAPITAL LETTER Y;So;0;L; 0059;;;;N;;;;24E8; 24CF;CIRCLED LATIN CAPITAL LETTER Z;So;0;L; 005A;;;;N;;;;24E9; 24D0;CIRCLED LATIN SMALL LETTER A;So;0;L; 0061;;;;N;;;24B6;;24B6 24D1;CIRCLED LATIN SMALL LETTER B;So;0;L; 0062;;;;N;;;24B7;;24B7 24D2;CIRCLED LATIN SMALL LETTER C;So;0;L; 0063;;;;N;;;24B8;;24B8 24D3;CIRCLED LATIN SMALL LETTER D;So;0;L; 0064;;;;N;;;24B9;;24B9 24D4;CIRCLED LATIN SMALL LETTER E;So;0;L; 0065;;;;N;;;24BA;;24BA 24D5;CIRCLED LATIN SMALL LETTER F;So;0;L; 0066;;;;N;;;24BB;;24BB 24D6;CIRCLED LATIN SMALL LETTER G;So;0;L; 0067;;;;N;;;24BC;;24BC 24D7;CIRCLED LATIN SMALL LETTER H;So;0;L; 0068;;;;N;;;24BD;;24BD 24D8;CIRCLED LATIN SMALL LETTER I;So;0;L; 0069;;;;N;;;24BE;;24BE 24D9;CIRCLED LATIN SMALL LETTER J;So;0;L; 006A;;;;N;;;24BF;;24BF 24DA;CIRCLED LATIN SMALL LETTER K;So;0;L; 006B;;;;N;;;24C0;;24C0 24DB;CIRCLED LATIN SMALL LETTER L;So;0;L; 006C;;;;N;;;24C1;;24C1 24DC;CIRCLED LATIN SMALL LETTER M;So;0;L; 006D;;;;N;;;24C2;;24C2 24DD;CIRCLED LATIN SMALL LETTER N;So;0;L; 006E;;;;N;;;24C3;;24C3 24DE;CIRCLED LATIN SMALL LETTER O;So;0;L; 006F;;;;N;;;24C4;;24C4 24DF;CIRCLED LATIN SMALL LETTER P;So;0;L; 0070;;;;N;;;24C5;;24C5 24E0;CIRCLED LATIN SMALL LETTER Q;So;0;L; 0071;;;;N;;;24C6;;24C6 24E1;CIRCLED LATIN SMALL LETTER R;So;0;L; 0072;;;;N;;;24C7;;24C7 24E2;CIRCLED LATIN SMALL LETTER S;So;0;L; 0073;;;;N;;;24C8;;24C8 24E3;CIRCLED LATIN SMALL LETTER T;So;0;L; 0074;;;;N;;;24C9;;24C9 24E4;CIRCLED LATIN SMALL LETTER U;So;0;L; 0075;;;;N;;;24CA;;24CA 24E5;CIRCLED LATIN SMALL LETTER V;So;0;L; 0076;;;;N;;;24CB;;24CB 24E6;CIRCLED LATIN SMALL LETTER W;So;0;L; 0077;;;;N;;;24CC;;24CC 24E7;CIRCLED LATIN SMALL LETTER X;So;0;L; 0078;;;;N;;;24CD;;24CD 24E8;CIRCLED LATIN SMALL LETTER Y;So;0;L; 0079;;;;N;;;24CE;;24CE 24E9;CIRCLED LATIN SMALL LETTER Z;So;0;L; 007A;;;;N;;;24CF;;24CF 24EA;CIRCLED DIGIT ZERO;No;0;ON; 0030;;0;0;N;;;;; 24EB;NEGATIVE CIRCLED NUMBER ELEVEN;No;0;ON;;;;11;N;;;;; 24EC;NEGATIVE CIRCLED NUMBER TWELVE;No;0;ON;;;;12;N;;;;; 24ED;NEGATIVE CIRCLED NUMBER THIRTEEN;No;0;ON;;;;13;N;;;;; 24EE;NEGATIVE CIRCLED NUMBER FOURTEEN;No;0;ON;;;;14;N;;;;; 24EF;NEGATIVE CIRCLED NUMBER FIFTEEN;No;0;ON;;;;15;N;;;;; 24F0;NEGATIVE CIRCLED NUMBER SIXTEEN;No;0;ON;;;;16;N;;;;; 24F1;NEGATIVE CIRCLED NUMBER SEVENTEEN;No;0;ON;;;;17;N;;;;; 24F2;NEGATIVE CIRCLED NUMBER EIGHTEEN;No;0;ON;;;;18;N;;;;; 24F3;NEGATIVE CIRCLED NUMBER NINETEEN;No;0;ON;;;;19;N;;;;; 24F4;NEGATIVE CIRCLED NUMBER TWENTY;No;0;ON;;;;20;N;;;;; 24F5;DOUBLE CIRCLED DIGIT ONE;No;0;ON;;;1;1;N;;;;; 24F6;DOUBLE CIRCLED DIGIT TWO;No;0;ON;;;2;2;N;;;;; 24F7;DOUBLE CIRCLED DIGIT THREE;No;0;ON;;;3;3;N;;;;; 24F8;DOUBLE CIRCLED DIGIT FOUR;No;0;ON;;;4;4;N;;;;; 24F9;DOUBLE CIRCLED DIGIT FIVE;No;0;ON;;;5;5;N;;;;; 24FA;DOUBLE CIRCLED DIGIT SIX;No;0;ON;;;6;6;N;;;;; 24FB;DOUBLE CIRCLED DIGIT SEVEN;No;0;ON;;;7;7;N;;;;; 24FC;DOUBLE CIRCLED DIGIT EIGHT;No;0;ON;;;8;8;N;;;;; 24FD;DOUBLE CIRCLED DIGIT NINE;No;0;ON;;;9;9;N;;;;; 24FE;DOUBLE CIRCLED NUMBER TEN;No;0;ON;;;;10;N;;;;; 24FF;NEGATIVE CIRCLED DIGIT ZERO;No;0;ON;;;0;0;N;;;;; 2500;BOX DRAWINGS LIGHT HORIZONTAL;So;0;ON;;;;;N;FORMS LIGHT HORIZONTAL;;;; 2501;BOX DRAWINGS HEAVY HORIZONTAL;So;0;ON;;;;;N;FORMS HEAVY HORIZONTAL;;;; 2502;BOX DRAWINGS LIGHT VERTICAL;So;0;ON;;;;;N;FORMS LIGHT VERTICAL;;;; 2503;BOX DRAWINGS HEAVY VERTICAL;So;0;ON;;;;;N;FORMS HEAVY VERTICAL;;;; 2504;BOX DRAWINGS LIGHT TRIPLE DASH HORIZONTAL;So;0;ON;;;;;N;FORMS LIGHT TRIPLE DASH HORIZONTAL;;;; 2505;BOX DRAWINGS HEAVY TRIPLE DASH HORIZONTAL;So;0;ON;;;;;N;FORMS HEAVY TRIPLE DASH HORIZONTAL;;;; 2506;BOX DRAWINGS LIGHT TRIPLE DASH VERTICAL;So;0;ON;;;;;N;FORMS LIGHT TRIPLE DASH VERTICAL;;;; 2507;BOX DRAWINGS HEAVY TRIPLE DASH VERTICAL;So;0;ON;;;;;N;FORMS HEAVY TRIPLE DASH VERTICAL;;;; 2508;BOX DRAWINGS LIGHT QUADRUPLE DASH HORIZONTAL;So;0;ON;;;;;N;FORMS LIGHT QUADRUPLE DASH HORIZONTAL;;;; 2509;BOX DRAWINGS HEAVY QUADRUPLE DASH HORIZONTAL;So;0;ON;;;;;N;FORMS HEAVY QUADRUPLE DASH HORIZONTAL;;;; 250A;BOX DRAWINGS LIGHT QUADRUPLE DASH VERTICAL;So;0;ON;;;;;N;FORMS LIGHT QUADRUPLE DASH VERTICAL;;;; 250B;BOX DRAWINGS HEAVY QUADRUPLE DASH VERTICAL;So;0;ON;;;;;N;FORMS HEAVY QUADRUPLE DASH VERTICAL;;;; 250C;BOX DRAWINGS LIGHT DOWN AND RIGHT;So;0;ON;;;;;N;FORMS LIGHT DOWN AND RIGHT;;;; 250D;BOX DRAWINGS DOWN LIGHT AND RIGHT HEAVY;So;0;ON;;;;;N;FORMS DOWN LIGHT AND RIGHT HEAVY;;;; 250E;BOX DRAWINGS DOWN HEAVY AND RIGHT LIGHT;So;0;ON;;;;;N;FORMS DOWN HEAVY AND RIGHT LIGHT;;;; 250F;BOX DRAWINGS HEAVY DOWN AND RIGHT;So;0;ON;;;;;N;FORMS HEAVY DOWN AND RIGHT;;;; 2510;BOX DRAWINGS LIGHT DOWN AND LEFT;So;0;ON;;;;;N;FORMS LIGHT DOWN AND LEFT;;;; 2511;BOX DRAWINGS DOWN LIGHT AND LEFT HEAVY;So;0;ON;;;;;N;FORMS DOWN LIGHT AND LEFT HEAVY;;;; 2512;BOX DRAWINGS DOWN HEAVY AND LEFT LIGHT;So;0;ON;;;;;N;FORMS DOWN HEAVY AND LEFT LIGHT;;;; 2513;BOX DRAWINGS HEAVY DOWN AND LEFT;So;0;ON;;;;;N;FORMS HEAVY DOWN AND LEFT;;;; 2514;BOX DRAWINGS LIGHT UP AND RIGHT;So;0;ON;;;;;N;FORMS LIGHT UP AND RIGHT;;;; 2515;BOX DRAWINGS UP LIGHT AND RIGHT HEAVY;So;0;ON;;;;;N;FORMS UP LIGHT AND RIGHT HEAVY;;;; 2516;BOX DRAWINGS UP HEAVY AND RIGHT LIGHT;So;0;ON;;;;;N;FORMS UP HEAVY AND RIGHT LIGHT;;;; 2517;BOX DRAWINGS HEAVY UP AND RIGHT;So;0;ON;;;;;N;FORMS HEAVY UP AND RIGHT;;;; 2518;BOX DRAWINGS LIGHT UP AND LEFT;So;0;ON;;;;;N;FORMS LIGHT UP AND LEFT;;;; 2519;BOX DRAWINGS UP LIGHT AND LEFT HEAVY;So;0;ON;;;;;N;FORMS UP LIGHT AND LEFT HEAVY;;;; 251A;BOX DRAWINGS UP HEAVY AND LEFT LIGHT;So;0;ON;;;;;N;FORMS UP HEAVY AND LEFT LIGHT;;;; 251B;BOX DRAWINGS HEAVY UP AND LEFT;So;0;ON;;;;;N;FORMS HEAVY UP AND LEFT;;;; 251C;BOX DRAWINGS LIGHT VERTICAL AND RIGHT;So;0;ON;;;;;N;FORMS LIGHT VERTICAL AND RIGHT;;;; 251D;BOX DRAWINGS VERTICAL LIGHT AND RIGHT HEAVY;So;0;ON;;;;;N;FORMS VERTICAL LIGHT AND RIGHT HEAVY;;;; 251E;BOX DRAWINGS UP HEAVY AND RIGHT DOWN LIGHT;So;0;ON;;;;;N;FORMS UP HEAVY AND RIGHT DOWN LIGHT;;;; 251F;BOX DRAWINGS DOWN HEAVY AND RIGHT UP LIGHT;So;0;ON;;;;;N;FORMS DOWN HEAVY AND RIGHT UP LIGHT;;;; 2520;BOX DRAWINGS VERTICAL HEAVY AND RIGHT LIGHT;So;0;ON;;;;;N;FORMS VERTICAL HEAVY AND RIGHT LIGHT;;;; 2521;BOX DRAWINGS DOWN LIGHT AND RIGHT UP HEAVY;So;0;ON;;;;;N;FORMS DOWN LIGHT AND RIGHT UP HEAVY;;;; 2522;BOX DRAWINGS UP LIGHT AND RIGHT DOWN HEAVY;So;0;ON;;;;;N;FORMS UP LIGHT AND RIGHT DOWN HEAVY;;;; 2523;BOX DRAWINGS HEAVY VERTICAL AND RIGHT;So;0;ON;;;;;N;FORMS HEAVY VERTICAL AND RIGHT;;;; 2524;BOX DRAWINGS LIGHT VERTICAL AND LEFT;So;0;ON;;;;;N;FORMS LIGHT VERTICAL AND LEFT;;;; 2525;BOX DRAWINGS VERTICAL LIGHT AND LEFT HEAVY;So;0;ON;;;;;N;FORMS VERTICAL LIGHT AND LEFT HEAVY;;;; 2526;BOX DRAWINGS UP HEAVY AND LEFT DOWN LIGHT;So;0;ON;;;;;N;FORMS UP HEAVY AND LEFT DOWN LIGHT;;;; 2527;BOX DRAWINGS DOWN HEAVY AND LEFT UP LIGHT;So;0;ON;;;;;N;FORMS DOWN HEAVY AND LEFT UP LIGHT;;;; 2528;BOX DRAWINGS VERTICAL HEAVY AND LEFT LIGHT;So;0;ON;;;;;N;FORMS VERTICAL HEAVY AND LEFT LIGHT;;;; 2529;BOX DRAWINGS DOWN LIGHT AND LEFT UP HEAVY;So;0;ON;;;;;N;FORMS DOWN LIGHT AND LEFT UP HEAVY;;;; 252A;BOX DRAWINGS UP LIGHT AND LEFT DOWN HEAVY;So;0;ON;;;;;N;FORMS UP LIGHT AND LEFT DOWN HEAVY;;;; 252B;BOX DRAWINGS HEAVY VERTICAL AND LEFT;So;0;ON;;;;;N;FORMS HEAVY VERTICAL AND LEFT;;;; 252C;BOX DRAWINGS LIGHT DOWN AND HORIZONTAL;So;0;ON;;;;;N;FORMS LIGHT DOWN AND HORIZONTAL;;;; 252D;BOX DRAWINGS LEFT HEAVY AND RIGHT DOWN LIGHT;So;0;ON;;;;;N;FORMS LEFT HEAVY AND RIGHT DOWN LIGHT;;;; 252E;BOX DRAWINGS RIGHT HEAVY AND LEFT DOWN LIGHT;So;0;ON;;;;;N;FORMS RIGHT HEAVY AND LEFT DOWN LIGHT;;;; 252F;BOX DRAWINGS DOWN LIGHT AND HORIZONTAL HEAVY;So;0;ON;;;;;N;FORMS DOWN LIGHT AND HORIZONTAL HEAVY;;;; 2530;BOX DRAWINGS DOWN HEAVY AND HORIZONTAL LIGHT;So;0;ON;;;;;N;FORMS DOWN HEAVY AND HORIZONTAL LIGHT;;;; 2531;BOX DRAWINGS RIGHT LIGHT AND LEFT DOWN HEAVY;So;0;ON;;;;;N;FORMS RIGHT LIGHT AND LEFT DOWN HEAVY;;;; 2532;BOX DRAWINGS LEFT LIGHT AND RIGHT DOWN HEAVY;So;0;ON;;;;;N;FORMS LEFT LIGHT AND RIGHT DOWN HEAVY;;;; 2533;BOX DRAWINGS HEAVY DOWN AND HORIZONTAL;So;0;ON;;;;;N;FORMS HEAVY DOWN AND HORIZONTAL;;;; 2534;BOX DRAWINGS LIGHT UP AND HORIZONTAL;So;0;ON;;;;;N;FORMS LIGHT UP AND HORIZONTAL;;;; 2535;BOX DRAWINGS LEFT HEAVY AND RIGHT UP LIGHT;So;0;ON;;;;;N;FORMS LEFT HEAVY AND RIGHT UP LIGHT;;;; 2536;BOX DRAWINGS RIGHT HEAVY AND LEFT UP LIGHT;So;0;ON;;;;;N;FORMS RIGHT HEAVY AND LEFT UP LIGHT;;;; 2537;BOX DRAWINGS UP LIGHT AND HORIZONTAL HEAVY;So;0;ON;;;;;N;FORMS UP LIGHT AND HORIZONTAL HEAVY;;;; 2538;BOX DRAWINGS UP HEAVY AND HORIZONTAL LIGHT;So;0;ON;;;;;N;FORMS UP HEAVY AND HORIZONTAL LIGHT;;;; 2539;BOX DRAWINGS RIGHT LIGHT AND LEFT UP HEAVY;So;0;ON;;;;;N;FORMS RIGHT LIGHT AND LEFT UP HEAVY;;;; 253A;BOX DRAWINGS LEFT LIGHT AND RIGHT UP HEAVY;So;0;ON;;;;;N;FORMS LEFT LIGHT AND RIGHT UP HEAVY;;;; 253B;BOX DRAWINGS HEAVY UP AND HORIZONTAL;So;0;ON;;;;;N;FORMS HEAVY UP AND HORIZONTAL;;;; 253C;BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL;So;0;ON;;;;;N;FORMS LIGHT VERTICAL AND HORIZONTAL;;;; 253D;BOX DRAWINGS LEFT HEAVY AND RIGHT VERTICAL LIGHT;So;0;ON;;;;;N;FORMS LEFT HEAVY AND RIGHT VERTICAL LIGHT;;;; 253E;BOX DRAWINGS RIGHT HEAVY AND LEFT VERTICAL LIGHT;So;0;ON;;;;;N;FORMS RIGHT HEAVY AND LEFT VERTICAL LIGHT;;;; 253F;BOX DRAWINGS VERTICAL LIGHT AND HORIZONTAL HEAVY;So;0;ON;;;;;N;FORMS VERTICAL LIGHT AND HORIZONTAL HEAVY;;;; 2540;BOX DRAWINGS UP HEAVY AND DOWN HORIZONTAL LIGHT;So;0;ON;;;;;N;FORMS UP HEAVY AND DOWN HORIZONTAL LIGHT;;;; 2541;BOX DRAWINGS DOWN HEAVY AND UP HORIZONTAL LIGHT;So;0;ON;;;;;N;FORMS DOWN HEAVY AND UP HORIZONTAL LIGHT;;;; 2542;BOX DRAWINGS VERTICAL HEAVY AND HORIZONTAL LIGHT;So;0;ON;;;;;N;FORMS VERTICAL HEAVY AND HORIZONTAL LIGHT;;;; 2543;BOX DRAWINGS LEFT UP HEAVY AND RIGHT DOWN LIGHT;So;0;ON;;;;;N;FORMS LEFT UP HEAVY AND RIGHT DOWN LIGHT;;;; 2544;BOX DRAWINGS RIGHT UP HEAVY AND LEFT DOWN LIGHT;So;0;ON;;;;;N;FORMS RIGHT UP HEAVY AND LEFT DOWN LIGHT;;;; 2545;BOX DRAWINGS LEFT DOWN HEAVY AND RIGHT UP LIGHT;So;0;ON;;;;;N;FORMS LEFT DOWN HEAVY AND RIGHT UP LIGHT;;;; 2546;BOX DRAWINGS RIGHT DOWN HEAVY AND LEFT UP LIGHT;So;0;ON;;;;;N;FORMS RIGHT DOWN HEAVY AND LEFT UP LIGHT;;;; 2547;BOX DRAWINGS DOWN LIGHT AND UP HORIZONTAL HEAVY;So;0;ON;;;;;N;FORMS DOWN LIGHT AND UP HORIZONTAL HEAVY;;;; 2548;BOX DRAWINGS UP LIGHT AND DOWN HORIZONTAL HEAVY;So;0;ON;;;;;N;FORMS UP LIGHT AND DOWN HORIZONTAL HEAVY;;;; 2549;BOX DRAWINGS RIGHT LIGHT AND LEFT VERTICAL HEAVY;So;0;ON;;;;;N;FORMS RIGHT LIGHT AND LEFT VERTICAL HEAVY;;;; 254A;BOX DRAWINGS LEFT LIGHT AND RIGHT VERTICAL HEAVY;So;0;ON;;;;;N;FORMS LEFT LIGHT AND RIGHT VERTICAL HEAVY;;;; 254B;BOX DRAWINGS HEAVY VERTICAL AND HORIZONTAL;So;0;ON;;;;;N;FORMS HEAVY VERTICAL AND HORIZONTAL;;;; 254C;BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL;So;0;ON;;;;;N;FORMS LIGHT DOUBLE DASH HORIZONTAL;;;; 254D;BOX DRAWINGS HEAVY DOUBLE DASH HORIZONTAL;So;0;ON;;;;;N;FORMS HEAVY DOUBLE DASH HORIZONTAL;;;; 254E;BOX DRAWINGS LIGHT DOUBLE DASH VERTICAL;So;0;ON;;;;;N;FORMS LIGHT DOUBLE DASH VERTICAL;;;; 254F;BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL;So;0;ON;;;;;N;FORMS HEAVY DOUBLE DASH VERTICAL;;;; 2550;BOX DRAWINGS DOUBLE HORIZONTAL;So;0;ON;;;;;N;FORMS DOUBLE HORIZONTAL;;;; 2551;BOX DRAWINGS DOUBLE VERTICAL;So;0;ON;;;;;N;FORMS DOUBLE VERTICAL;;;; 2552;BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE;So;0;ON;;;;;N;FORMS DOWN SINGLE AND RIGHT DOUBLE;;;; 2553;BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE;So;0;ON;;;;;N;FORMS DOWN DOUBLE AND RIGHT SINGLE;;;; 2554;BOX DRAWINGS DOUBLE DOWN AND RIGHT;So;0;ON;;;;;N;FORMS DOUBLE DOWN AND RIGHT;;;; 2555;BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE;So;0;ON;;;;;N;FORMS DOWN SINGLE AND LEFT DOUBLE;;;; 2556;BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE;So;0;ON;;;;;N;FORMS DOWN DOUBLE AND LEFT SINGLE;;;; 2557;BOX DRAWINGS DOUBLE DOWN AND LEFT;So;0;ON;;;;;N;FORMS DOUBLE DOWN AND LEFT;;;; 2558;BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE;So;0;ON;;;;;N;FORMS UP SINGLE AND RIGHT DOUBLE;;;; 2559;BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE;So;0;ON;;;;;N;FORMS UP DOUBLE AND RIGHT SINGLE;;;; 255A;BOX DRAWINGS DOUBLE UP AND RIGHT;So;0;ON;;;;;N;FORMS DOUBLE UP AND RIGHT;;;; 255B;BOX DRAWINGS UP SINGLE AND LEFT DOUBLE;So;0;ON;;;;;N;FORMS UP SINGLE AND LEFT DOUBLE;;;; 255C;BOX DRAWINGS UP DOUBLE AND LEFT SINGLE;So;0;ON;;;;;N;FORMS UP DOUBLE AND LEFT SINGLE;;;; 255D;BOX DRAWINGS DOUBLE UP AND LEFT;So;0;ON;;;;;N;FORMS DOUBLE UP AND LEFT;;;; 255E;BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE;So;0;ON;;;;;N;FORMS VERTICAL SINGLE AND RIGHT DOUBLE;;;; 255F;BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE;So;0;ON;;;;;N;FORMS VERTICAL DOUBLE AND RIGHT SINGLE;;;; 2560;BOX DRAWINGS DOUBLE VERTICAL AND RIGHT;So;0;ON;;;;;N;FORMS DOUBLE VERTICAL AND RIGHT;;;; 2561;BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE;So;0;ON;;;;;N;FORMS VERTICAL SINGLE AND LEFT DOUBLE;;;; 2562;BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE;So;0;ON;;;;;N;FORMS VERTICAL DOUBLE AND LEFT SINGLE;;;; 2563;BOX DRAWINGS DOUBLE VERTICAL AND LEFT;So;0;ON;;;;;N;FORMS DOUBLE VERTICAL AND LEFT;;;; 2564;BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE;So;0;ON;;;;;N;FORMS DOWN SINGLE AND HORIZONTAL DOUBLE;;;; 2565;BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE;So;0;ON;;;;;N;FORMS DOWN DOUBLE AND HORIZONTAL SINGLE;;;; 2566;BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL;So;0;ON;;;;;N;FORMS DOUBLE DOWN AND HORIZONTAL;;;; 2567;BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE;So;0;ON;;;;;N;FORMS UP SINGLE AND HORIZONTAL DOUBLE;;;; 2568;BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE;So;0;ON;;;;;N;FORMS UP DOUBLE AND HORIZONTAL SINGLE;;;; 2569;BOX DRAWINGS DOUBLE UP AND HORIZONTAL;So;0;ON;;;;;N;FORMS DOUBLE UP AND HORIZONTAL;;;; 256A;BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE;So;0;ON;;;;;N;FORMS VERTICAL SINGLE AND HORIZONTAL DOUBLE;;;; 256B;BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE;So;0;ON;;;;;N;FORMS VERTICAL DOUBLE AND HORIZONTAL SINGLE;;;; 256C;BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL;So;0;ON;;;;;N;FORMS DOUBLE VERTICAL AND HORIZONTAL;;;; 256D;BOX DRAWINGS LIGHT ARC DOWN AND RIGHT;So;0;ON;;;;;N;FORMS LIGHT ARC DOWN AND RIGHT;;;; 256E;BOX DRAWINGS LIGHT ARC DOWN AND LEFT;So;0;ON;;;;;N;FORMS LIGHT ARC DOWN AND LEFT;;;; 256F;BOX DRAWINGS LIGHT ARC UP AND LEFT;So;0;ON;;;;;N;FORMS LIGHT ARC UP AND LEFT;;;; 2570;BOX DRAWINGS LIGHT ARC UP AND RIGHT;So;0;ON;;;;;N;FORMS LIGHT ARC UP AND RIGHT;;;; 2571;BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT;So;0;ON;;;;;N;FORMS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT;;;; 2572;BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT;So;0;ON;;;;;N;FORMS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT;;;; 2573;BOX DRAWINGS LIGHT DIAGONAL CROSS;So;0;ON;;;;;N;FORMS LIGHT DIAGONAL CROSS;;;; 2574;BOX DRAWINGS LIGHT LEFT;So;0;ON;;;;;N;FORMS LIGHT LEFT;;;; 2575;BOX DRAWINGS LIGHT UP;So;0;ON;;;;;N;FORMS LIGHT UP;;;; 2576;BOX DRAWINGS LIGHT RIGHT;So;0;ON;;;;;N;FORMS LIGHT RIGHT;;;; 2577;BOX DRAWINGS LIGHT DOWN;So;0;ON;;;;;N;FORMS LIGHT DOWN;;;; 2578;BOX DRAWINGS HEAVY LEFT;So;0;ON;;;;;N;FORMS HEAVY LEFT;;;; 2579;BOX DRAWINGS HEAVY UP;So;0;ON;;;;;N;FORMS HEAVY UP;;;; 257A;BOX DRAWINGS HEAVY RIGHT;So;0;ON;;;;;N;FORMS HEAVY RIGHT;;;; 257B;BOX DRAWINGS HEAVY DOWN;So;0;ON;;;;;N;FORMS HEAVY DOWN;;;; 257C;BOX DRAWINGS LIGHT LEFT AND HEAVY RIGHT;So;0;ON;;;;;N;FORMS LIGHT LEFT AND HEAVY RIGHT;;;; 257D;BOX DRAWINGS LIGHT UP AND HEAVY DOWN;So;0;ON;;;;;N;FORMS LIGHT UP AND HEAVY DOWN;;;; 257E;BOX DRAWINGS HEAVY LEFT AND LIGHT RIGHT;So;0;ON;;;;;N;FORMS HEAVY LEFT AND LIGHT RIGHT;;;; 257F;BOX DRAWINGS HEAVY UP AND LIGHT DOWN;So;0;ON;;;;;N;FORMS HEAVY UP AND LIGHT DOWN;;;; 2580;UPPER HALF BLOCK;So;0;ON;;;;;N;;;;; 2581;LOWER ONE EIGHTH BLOCK;So;0;ON;;;;;N;;;;; 2582;LOWER ONE QUARTER BLOCK;So;0;ON;;;;;N;;;;; 2583;LOWER THREE EIGHTHS BLOCK;So;0;ON;;;;;N;;;;; 2584;LOWER HALF BLOCK;So;0;ON;;;;;N;;;;; 2585;LOWER FIVE EIGHTHS BLOCK;So;0;ON;;;;;N;;;;; 2586;LOWER THREE QUARTERS BLOCK;So;0;ON;;;;;N;LOWER THREE QUARTER BLOCK;;;; 2587;LOWER SEVEN EIGHTHS BLOCK;So;0;ON;;;;;N;;;;; 2588;FULL BLOCK;So;0;ON;;;;;N;;;;; 2589;LEFT SEVEN EIGHTHS BLOCK;So;0;ON;;;;;N;;;;; 258A;LEFT THREE QUARTERS BLOCK;So;0;ON;;;;;N;LEFT THREE QUARTER BLOCK;;;; 258B;LEFT FIVE EIGHTHS BLOCK;So;0;ON;;;;;N;;;;; 258C;LEFT HALF BLOCK;So;0;ON;;;;;N;;;;; 258D;LEFT THREE EIGHTHS BLOCK;So;0;ON;;;;;N;;;;; 258E;LEFT ONE QUARTER BLOCK;So;0;ON;;;;;N;;;;; 258F;LEFT ONE EIGHTH BLOCK;So;0;ON;;;;;N;;;;; 2590;RIGHT HALF BLOCK;So;0;ON;;;;;N;;;;; 2591;LIGHT SHADE;So;0;ON;;;;;N;;;;; 2592;MEDIUM SHADE;So;0;ON;;;;;N;;;;; 2593;DARK SHADE;So;0;ON;;;;;N;;;;; 2594;UPPER ONE EIGHTH BLOCK;So;0;ON;;;;;N;;;;; 2595;RIGHT ONE EIGHTH BLOCK;So;0;ON;;;;;N;;;;; 2596;QUADRANT LOWER LEFT;So;0;ON;;;;;N;;;;; 2597;QUADRANT LOWER RIGHT;So;0;ON;;;;;N;;;;; 2598;QUADRANT UPPER LEFT;So;0;ON;;;;;N;;;;; 2599;QUADRANT UPPER LEFT AND LOWER LEFT AND LOWER RIGHT;So;0;ON;;;;;N;;;;; 259A;QUADRANT UPPER LEFT AND LOWER RIGHT;So;0;ON;;;;;N;;;;; 259B;QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER LEFT;So;0;ON;;;;;N;;;;; 259C;QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER RIGHT;So;0;ON;;;;;N;;;;; 259D;QUADRANT UPPER RIGHT;So;0;ON;;;;;N;;;;; 259E;QUADRANT UPPER RIGHT AND LOWER LEFT;So;0;ON;;;;;N;;;;; 259F;QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT;So;0;ON;;;;;N;;;;; 25A0;BLACK SQUARE;So;0;ON;;;;;N;;;;; 25A1;WHITE SQUARE;So;0;ON;;;;;N;;;;; 25A2;WHITE SQUARE WITH ROUNDED CORNERS;So;0;ON;;;;;N;;;;; 25A3;WHITE SQUARE CONTAINING BLACK SMALL SQUARE;So;0;ON;;;;;N;;;;; 25A4;SQUARE WITH HORIZONTAL FILL;So;0;ON;;;;;N;;;;; 25A5;SQUARE WITH VERTICAL FILL;So;0;ON;;;;;N;;;;; 25A6;SQUARE WITH ORTHOGONAL CROSSHATCH FILL;So;0;ON;;;;;N;;;;; 25A7;SQUARE WITH UPPER LEFT TO LOWER RIGHT FILL;So;0;ON;;;;;N;;;;; 25A8;SQUARE WITH UPPER RIGHT TO LOWER LEFT FILL;So;0;ON;;;;;N;;;;; 25A9;SQUARE WITH DIAGONAL CROSSHATCH FILL;So;0;ON;;;;;N;;;;; 25AA;BLACK SMALL SQUARE;So;0;ON;;;;;N;;;;; 25AB;WHITE SMALL SQUARE;So;0;ON;;;;;N;;;;; 25AC;BLACK RECTANGLE;So;0;ON;;;;;N;;;;; 25AD;WHITE RECTANGLE;So;0;ON;;;;;N;;;;; 25AE;BLACK VERTICAL RECTANGLE;So;0;ON;;;;;N;;;;; 25AF;WHITE VERTICAL RECTANGLE;So;0;ON;;;;;N;;;;; 25B0;BLACK PARALLELOGRAM;So;0;ON;;;;;N;;;;; 25B1;WHITE PARALLELOGRAM;So;0;ON;;;;;N;;;;; 25B2;BLACK UP-POINTING TRIANGLE;So;0;ON;;;;;N;BLACK UP POINTING TRIANGLE;;;; 25B3;WHITE UP-POINTING TRIANGLE;So;0;ON;;;;;N;WHITE UP POINTING TRIANGLE;;;; 25B4;BLACK UP-POINTING SMALL TRIANGLE;So;0;ON;;;;;N;BLACK UP POINTING SMALL TRIANGLE;;;; 25B5;WHITE UP-POINTING SMALL TRIANGLE;So;0;ON;;;;;N;WHITE UP POINTING SMALL TRIANGLE;;;; 25B6;BLACK RIGHT-POINTING TRIANGLE;So;0;ON;;;;;N;BLACK RIGHT POINTING TRIANGLE;;;; 25B7;WHITE RIGHT-POINTING TRIANGLE;Sm;0;ON;;;;;N;WHITE RIGHT POINTING TRIANGLE;;;; 25B8;BLACK RIGHT-POINTING SMALL TRIANGLE;So;0;ON;;;;;N;BLACK RIGHT POINTING SMALL TRIANGLE;;;; 25B9;WHITE RIGHT-POINTING SMALL TRIANGLE;So;0;ON;;;;;N;WHITE RIGHT POINTING SMALL TRIANGLE;;;; 25BA;BLACK RIGHT-POINTING POINTER;So;0;ON;;;;;N;BLACK RIGHT POINTING POINTER;;;; 25BB;WHITE RIGHT-POINTING POINTER;So;0;ON;;;;;N;WHITE RIGHT POINTING POINTER;;;; 25BC;BLACK DOWN-POINTING TRIANGLE;So;0;ON;;;;;N;BLACK DOWN POINTING TRIANGLE;;;; 25BD;WHITE DOWN-POINTING TRIANGLE;So;0;ON;;;;;N;WHITE DOWN POINTING TRIANGLE;;;; 25BE;BLACK DOWN-POINTING SMALL TRIANGLE;So;0;ON;;;;;N;BLACK DOWN POINTING SMALL TRIANGLE;;;; 25BF;WHITE DOWN-POINTING SMALL TRIANGLE;So;0;ON;;;;;N;WHITE DOWN POINTING SMALL TRIANGLE;;;; 25C0;BLACK LEFT-POINTING TRIANGLE;So;0;ON;;;;;N;BLACK LEFT POINTING TRIANGLE;;;; 25C1;WHITE LEFT-POINTING TRIANGLE;Sm;0;ON;;;;;N;WHITE LEFT POINTING TRIANGLE;;;; 25C2;BLACK LEFT-POINTING SMALL TRIANGLE;So;0;ON;;;;;N;BLACK LEFT POINTING SMALL TRIANGLE;;;; 25C3;WHITE LEFT-POINTING SMALL TRIANGLE;So;0;ON;;;;;N;WHITE LEFT POINTING SMALL TRIANGLE;;;; 25C4;BLACK LEFT-POINTING POINTER;So;0;ON;;;;;N;BLACK LEFT POINTING POINTER;;;; 25C5;WHITE LEFT-POINTING POINTER;So;0;ON;;;;;N;WHITE LEFT POINTING POINTER;;;; 25C6;BLACK DIAMOND;So;0;ON;;;;;N;;;;; 25C7;WHITE DIAMOND;So;0;ON;;;;;N;;;;; 25C8;WHITE DIAMOND CONTAINING BLACK SMALL DIAMOND;So;0;ON;;;;;N;;;;; 25C9;FISHEYE;So;0;ON;;;;;N;;;;; 25CA;LOZENGE;So;0;ON;;;;;N;;;;; 25CB;WHITE CIRCLE;So;0;ON;;;;;N;;;;; 25CC;DOTTED CIRCLE;So;0;ON;;;;;N;;;;; 25CD;CIRCLE WITH VERTICAL FILL;So;0;ON;;;;;N;;;;; 25CE;BULLSEYE;So;0;ON;;;;;N;;;;; 25CF;BLACK CIRCLE;So;0;ON;;;;;N;;;;; 25D0;CIRCLE WITH LEFT HALF BLACK;So;0;ON;;;;;N;;;;; 25D1;CIRCLE WITH RIGHT HALF BLACK;So;0;ON;;;;;N;;;;; 25D2;CIRCLE WITH LOWER HALF BLACK;So;0;ON;;;;;N;;;;; 25D3;CIRCLE WITH UPPER HALF BLACK;So;0;ON;;;;;N;;;;; 25D4;CIRCLE WITH UPPER RIGHT QUADRANT BLACK;So;0;ON;;;;;N;;;;; 25D5;CIRCLE WITH ALL BUT UPPER LEFT QUADRANT BLACK;So;0;ON;;;;;N;;;;; 25D6;LEFT HALF BLACK CIRCLE;So;0;ON;;;;;N;;;;; 25D7;RIGHT HALF BLACK CIRCLE;So;0;ON;;;;;N;;;;; 25D8;INVERSE BULLET;So;0;ON;;;;;N;;;;; 25D9;INVERSE WHITE CIRCLE;So;0;ON;;;;;N;;;;; 25DA;UPPER HALF INVERSE WHITE CIRCLE;So;0;ON;;;;;N;;;;; 25DB;LOWER HALF INVERSE WHITE CIRCLE;So;0;ON;;;;;N;;;;; 25DC;UPPER LEFT QUADRANT CIRCULAR ARC;So;0;ON;;;;;N;;;;; 25DD;UPPER RIGHT QUADRANT CIRCULAR ARC;So;0;ON;;;;;N;;;;; 25DE;LOWER RIGHT QUADRANT CIRCULAR ARC;So;0;ON;;;;;N;;;;; 25DF;LOWER LEFT QUADRANT CIRCULAR ARC;So;0;ON;;;;;N;;;;; 25E0;UPPER HALF CIRCLE;So;0;ON;;;;;N;;;;; 25E1;LOWER HALF CIRCLE;So;0;ON;;;;;N;;;;; 25E2;BLACK LOWER RIGHT TRIANGLE;So;0;ON;;;;;N;;;;; 25E3;BLACK LOWER LEFT TRIANGLE;So;0;ON;;;;;N;;;;; 25E4;BLACK UPPER LEFT TRIANGLE;So;0;ON;;;;;N;;;;; 25E5;BLACK UPPER RIGHT TRIANGLE;So;0;ON;;;;;N;;;;; 25E6;WHITE BULLET;So;0;ON;;;;;N;;;;; 25E7;SQUARE WITH LEFT HALF BLACK;So;0;ON;;;;;N;;;;; 25E8;SQUARE WITH RIGHT HALF BLACK;So;0;ON;;;;;N;;;;; 25E9;SQUARE WITH UPPER LEFT DIAGONAL HALF BLACK;So;0;ON;;;;;N;;;;; 25EA;SQUARE WITH LOWER RIGHT DIAGONAL HALF BLACK;So;0;ON;;;;;N;;;;; 25EB;WHITE SQUARE WITH VERTICAL BISECTING LINE;So;0;ON;;;;;N;;;;; 25EC;WHITE UP-POINTING TRIANGLE WITH DOT;So;0;ON;;;;;N;WHITE UP POINTING TRIANGLE WITH DOT;;;; 25ED;UP-POINTING TRIANGLE WITH LEFT HALF BLACK;So;0;ON;;;;;N;UP POINTING TRIANGLE WITH LEFT HALF BLACK;;;; 25EE;UP-POINTING TRIANGLE WITH RIGHT HALF BLACK;So;0;ON;;;;;N;UP POINTING TRIANGLE WITH RIGHT HALF BLACK;;;; 25EF;LARGE CIRCLE;So;0;ON;;;;;N;;;;; 25F0;WHITE SQUARE WITH UPPER LEFT QUADRANT;So;0;ON;;;;;N;;;;; 25F1;WHITE SQUARE WITH LOWER LEFT QUADRANT;So;0;ON;;;;;N;;;;; 25F2;WHITE SQUARE WITH LOWER RIGHT QUADRANT;So;0;ON;;;;;N;;;;; 25F3;WHITE SQUARE WITH UPPER RIGHT QUADRANT;So;0;ON;;;;;N;;;;; 25F4;WHITE CIRCLE WITH UPPER LEFT QUADRANT;So;0;ON;;;;;N;;;;; 25F5;WHITE CIRCLE WITH LOWER LEFT QUADRANT;So;0;ON;;;;;N;;;;; 25F6;WHITE CIRCLE WITH LOWER RIGHT QUADRANT;So;0;ON;;;;;N;;;;; 25F7;WHITE CIRCLE WITH UPPER RIGHT QUADRANT;So;0;ON;;;;;N;;;;; 25F8;UPPER LEFT TRIANGLE;Sm;0;ON;;;;;N;;;;; 25F9;UPPER RIGHT TRIANGLE;Sm;0;ON;;;;;N;;;;; 25FA;LOWER LEFT TRIANGLE;Sm;0;ON;;;;;N;;;;; 25FB;WHITE MEDIUM SQUARE;Sm;0;ON;;;;;N;;;;; 25FC;BLACK MEDIUM SQUARE;Sm;0;ON;;;;;N;;;;; 25FD;WHITE MEDIUM SMALL SQUARE;Sm;0;ON;;;;;N;;;;; 25FE;BLACK MEDIUM SMALL SQUARE;Sm;0;ON;;;;;N;;;;; 25FF;LOWER RIGHT TRIANGLE;Sm;0;ON;;;;;N;;;;; 2600;BLACK SUN WITH RAYS;So;0;ON;;;;;N;;;;; 2601;CLOUD;So;0;ON;;;;;N;;;;; 2602;UMBRELLA;So;0;ON;;;;;N;;;;; 2603;SNOWMAN;So;0;ON;;;;;N;;;;; 2604;COMET;So;0;ON;;;;;N;;;;; 2605;BLACK STAR;So;0;ON;;;;;N;;;;; 2606;WHITE STAR;So;0;ON;;;;;N;;;;; 2607;LIGHTNING;So;0;ON;;;;;N;;;;; 2608;THUNDERSTORM;So;0;ON;;;;;N;;;;; 2609;SUN;So;0;ON;;;;;N;;;;; 260A;ASCENDING NODE;So;0;ON;;;;;N;;;;; 260B;DESCENDING NODE;So;0;ON;;;;;N;;;;; 260C;CONJUNCTION;So;0;ON;;;;;N;;;;; 260D;OPPOSITION;So;0;ON;;;;;N;;;;; 260E;BLACK TELEPHONE;So;0;ON;;;;;N;;;;; 260F;WHITE TELEPHONE;So;0;ON;;;;;N;;;;; 2610;BALLOT BOX;So;0;ON;;;;;N;;;;; 2611;BALLOT BOX WITH CHECK;So;0;ON;;;;;N;;;;; 2612;BALLOT BOX WITH X;So;0;ON;;;;;N;;;;; 2613;SALTIRE;So;0;ON;;;;;N;;;;; 2614;UMBRELLA WITH RAIN DROPS;So;0;ON;;;;;N;;;;; 2615;HOT BEVERAGE;So;0;ON;;;;;N;;;;; 2616;WHITE SHOGI PIECE;So;0;ON;;;;;N;;;;; 2617;BLACK SHOGI PIECE;So;0;ON;;;;;N;;;;; 2618;SHAMROCK;So;0;ON;;;;;N;;;;; 2619;REVERSED ROTATED FLORAL HEART BULLET;So;0;ON;;;;;N;;;;; 261A;BLACK LEFT POINTING INDEX;So;0;ON;;;;;N;;;;; 261B;BLACK RIGHT POINTING INDEX;So;0;ON;;;;;N;;;;; 261C;WHITE LEFT POINTING INDEX;So;0;ON;;;;;N;;;;; 261D;WHITE UP POINTING INDEX;So;0;ON;;;;;N;;;;; 261E;WHITE RIGHT POINTING INDEX;So;0;ON;;;;;N;;;;; 261F;WHITE DOWN POINTING INDEX;So;0;ON;;;;;N;;;;; 2620;SKULL AND CROSSBONES;So;0;ON;;;;;N;;;;; 2621;CAUTION SIGN;So;0;ON;;;;;N;;;;; 2622;RADIOACTIVE SIGN;So;0;ON;;;;;N;;;;; 2623;BIOHAZARD SIGN;So;0;ON;;;;;N;;;;; 2624;CADUCEUS;So;0;ON;;;;;N;;;;; 2625;ANKH;So;0;ON;;;;;N;;;;; 2626;ORTHODOX CROSS;So;0;ON;;;;;N;;;;; 2627;CHI RHO;So;0;ON;;;;;N;;;;; 2628;CROSS OF LORRAINE;So;0;ON;;;;;N;;;;; 2629;CROSS OF JERUSALEM;So;0;ON;;;;;N;;;;; 262A;STAR AND CRESCENT;So;0;ON;;;;;N;;;;; 262B;FARSI SYMBOL;So;0;ON;;;;;N;SYMBOL OF IRAN;;;; 262C;ADI SHAKTI;So;0;ON;;;;;N;;;;; 262D;HAMMER AND SICKLE;So;0;ON;;;;;N;;;;; 262E;PEACE SYMBOL;So;0;ON;;;;;N;;;;; 262F;YIN YANG;So;0;ON;;;;;N;;;;; 2630;TRIGRAM FOR HEAVEN;So;0;ON;;;;;N;;;;; 2631;TRIGRAM FOR LAKE;So;0;ON;;;;;N;;;;; 2632;TRIGRAM FOR FIRE;So;0;ON;;;;;N;;;;; 2633;TRIGRAM FOR THUNDER;So;0;ON;;;;;N;;;;; 2634;TRIGRAM FOR WIND;So;0;ON;;;;;N;;;;; 2635;TRIGRAM FOR WATER;So;0;ON;;;;;N;;;;; 2636;TRIGRAM FOR MOUNTAIN;So;0;ON;;;;;N;;;;; 2637;TRIGRAM FOR EARTH;So;0;ON;;;;;N;;;;; 2638;WHEEL OF DHARMA;So;0;ON;;;;;N;;;;; 2639;WHITE FROWNING FACE;So;0;ON;;;;;N;;;;; 263A;WHITE SMILING FACE;So;0;ON;;;;;N;;;;; 263B;BLACK SMILING FACE;So;0;ON;;;;;N;;;;; 263C;WHITE SUN WITH RAYS;So;0;ON;;;;;N;;;;; 263D;FIRST QUARTER MOON;So;0;ON;;;;;N;;;;; 263E;LAST QUARTER MOON;So;0;ON;;;;;N;;;;; 263F;MERCURY;So;0;ON;;;;;N;;;;; 2640;FEMALE SIGN;So;0;ON;;;;;N;;;;; 2641;EARTH;So;0;ON;;;;;N;;;;; 2642;MALE SIGN;So;0;ON;;;;;N;;;;; 2643;JUPITER;So;0;ON;;;;;N;;;;; 2644;SATURN;So;0;ON;;;;;N;;;;; 2645;URANUS;So;0;ON;;;;;N;;;;; 2646;NEPTUNE;So;0;ON;;;;;N;;;;; 2647;PLUTO;So;0;ON;;;;;N;;;;; 2648;ARIES;So;0;ON;;;;;N;;;;; 2649;TAURUS;So;0;ON;;;;;N;;;;; 264A;GEMINI;So;0;ON;;;;;N;;;;; 264B;CANCER;So;0;ON;;;;;N;;;;; 264C;LEO;So;0;ON;;;;;N;;;;; 264D;VIRGO;So;0;ON;;;;;N;;;;; 264E;LIBRA;So;0;ON;;;;;N;;;;; 264F;SCORPIUS;So;0;ON;;;;;N;;;;; 2650;SAGITTARIUS;So;0;ON;;;;;N;;;;; 2651;CAPRICORN;So;0;ON;;;;;N;;;;; 2652;AQUARIUS;So;0;ON;;;;;N;;;;; 2653;PISCES;So;0;ON;;;;;N;;;;; 2654;WHITE CHESS KING;So;0;ON;;;;;N;;;;; 2655;WHITE CHESS QUEEN;So;0;ON;;;;;N;;;;; 2656;WHITE CHESS ROOK;So;0;ON;;;;;N;;;;; 2657;WHITE CHESS BISHOP;So;0;ON;;;;;N;;;;; 2658;WHITE CHESS KNIGHT;So;0;ON;;;;;N;;;;; 2659;WHITE CHESS PAWN;So;0;ON;;;;;N;;;;; 265A;BLACK CHESS KING;So;0;ON;;;;;N;;;;; 265B;BLACK CHESS QUEEN;So;0;ON;;;;;N;;;;; 265C;BLACK CHESS ROOK;So;0;ON;;;;;N;;;;; 265D;BLACK CHESS BISHOP;So;0;ON;;;;;N;;;;; 265E;BLACK CHESS KNIGHT;So;0;ON;;;;;N;;;;; 265F;BLACK CHESS PAWN;So;0;ON;;;;;N;;;;; 2660;BLACK SPADE SUIT;So;0;ON;;;;;N;;;;; 2661;WHITE HEART SUIT;So;0;ON;;;;;N;;;;; 2662;WHITE DIAMOND SUIT;So;0;ON;;;;;N;;;;; 2663;BLACK CLUB SUIT;So;0;ON;;;;;N;;;;; 2664;WHITE SPADE SUIT;So;0;ON;;;;;N;;;;; 2665;BLACK HEART SUIT;So;0;ON;;;;;N;;;;; 2666;BLACK DIAMOND SUIT;So;0;ON;;;;;N;;;;; 2667;WHITE CLUB SUIT;So;0;ON;;;;;N;;;;; 2668;HOT SPRINGS;So;0;ON;;;;;N;;;;; 2669;QUARTER NOTE;So;0;ON;;;;;N;;;;; 266A;EIGHTH NOTE;So;0;ON;;;;;N;;;;; 266B;BEAMED EIGHTH NOTES;So;0;ON;;;;;N;BARRED EIGHTH NOTES;;;; 266C;BEAMED SIXTEENTH NOTES;So;0;ON;;;;;N;BARRED SIXTEENTH NOTES;;;; 266D;MUSIC FLAT SIGN;So;0;ON;;;;;N;FLAT;;;; 266E;MUSIC NATURAL SIGN;So;0;ON;;;;;N;NATURAL;;;; 266F;MUSIC SHARP SIGN;Sm;0;ON;;;;;N;SHARP;;;; 2670;WEST SYRIAC CROSS;So;0;ON;;;;;N;;;;; 2671;EAST SYRIAC CROSS;So;0;ON;;;;;N;;;;; 2672;UNIVERSAL RECYCLING SYMBOL;So;0;ON;;;;;N;;;;; 2673;RECYCLING SYMBOL FOR TYPE-1 PLASTICS;So;0;ON;;;;;N;;;;; 2674;RECYCLING SYMBOL FOR TYPE-2 PLASTICS;So;0;ON;;;;;N;;;;; 2675;RECYCLING SYMBOL FOR TYPE-3 PLASTICS;So;0;ON;;;;;N;;;;; 2676;RECYCLING SYMBOL FOR TYPE-4 PLASTICS;So;0;ON;;;;;N;;;;; 2677;RECYCLING SYMBOL FOR TYPE-5 PLASTICS;So;0;ON;;;;;N;;;;; 2678;RECYCLING SYMBOL FOR TYPE-6 PLASTICS;So;0;ON;;;;;N;;;;; 2679;RECYCLING SYMBOL FOR TYPE-7 PLASTICS;So;0;ON;;;;;N;;;;; 267A;RECYCLING SYMBOL FOR GENERIC MATERIALS;So;0;ON;;;;;N;;;;; 267B;BLACK UNIVERSAL RECYCLING SYMBOL;So;0;ON;;;;;N;;;;; 267C;RECYCLED PAPER SYMBOL;So;0;ON;;;;;N;;;;; 267D;PARTIALLY-RECYCLED PAPER SYMBOL;So;0;ON;;;;;N;;;;; 267E;PERMANENT PAPER SIGN;So;0;ON;;;;;N;;;;; 267F;WHEELCHAIR SYMBOL;So;0;ON;;;;;N;;;;; 2680;DIE FACE-1;So;0;ON;;;;;N;;;;; 2681;DIE FACE-2;So;0;ON;;;;;N;;;;; 2682;DIE FACE-3;So;0;ON;;;;;N;;;;; 2683;DIE FACE-4;So;0;ON;;;;;N;;;;; 2684;DIE FACE-5;So;0;ON;;;;;N;;;;; 2685;DIE FACE-6;So;0;ON;;;;;N;;;;; 2686;WHITE CIRCLE WITH DOT RIGHT;So;0;ON;;;;;N;;;;; 2687;WHITE CIRCLE WITH TWO DOTS;So;0;ON;;;;;N;;;;; 2688;BLACK CIRCLE WITH WHITE DOT RIGHT;So;0;ON;;;;;N;;;;; 2689;BLACK CIRCLE WITH TWO WHITE DOTS;So;0;ON;;;;;N;;;;; 268A;MONOGRAM FOR YANG;So;0;ON;;;;;N;;;;; 268B;MONOGRAM FOR YIN;So;0;ON;;;;;N;;;;; 268C;DIGRAM FOR GREATER YANG;So;0;ON;;;;;N;;;;; 268D;DIGRAM FOR LESSER YIN;So;0;ON;;;;;N;;;;; 268E;DIGRAM FOR LESSER YANG;So;0;ON;;;;;N;;;;; 268F;DIGRAM FOR GREATER YIN;So;0;ON;;;;;N;;;;; 2690;WHITE FLAG;So;0;ON;;;;;N;;;;; 2691;BLACK FLAG;So;0;ON;;;;;N;;;;; 2692;HAMMER AND PICK;So;0;ON;;;;;N;;;;; 2693;ANCHOR;So;0;ON;;;;;N;;;;; 2694;CROSSED SWORDS;So;0;ON;;;;;N;;;;; 2695;STAFF OF AESCULAPIUS;So;0;ON;;;;;N;;;;; 2696;SCALES;So;0;ON;;;;;N;;;;; 2697;ALEMBIC;So;0;ON;;;;;N;;;;; 2698;FLOWER;So;0;ON;;;;;N;;;;; 2699;GEAR;So;0;ON;;;;;N;;;;; 269A;STAFF OF HERMES;So;0;ON;;;;;N;;;;; 269B;ATOM SYMBOL;So;0;ON;;;;;N;;;;; 269C;FLEUR-DE-LIS;So;0;ON;;;;;N;;;;; 269D;OUTLINED WHITE STAR;So;0;ON;;;;;N;;;;; 269E;THREE LINES CONVERGING RIGHT;So;0;ON;;;;;N;;;;; 269F;THREE LINES CONVERGING LEFT;So;0;ON;;;;;N;;;;; 26A0;WARNING SIGN;So;0;ON;;;;;N;;;;; 26A1;HIGH VOLTAGE SIGN;So;0;ON;;;;;N;;;;; 26A2;DOUBLED FEMALE SIGN;So;0;ON;;;;;N;;;;; 26A3;DOUBLED MALE SIGN;So;0;ON;;;;;N;;;;; 26A4;INTERLOCKED FEMALE AND MALE SIGN;So;0;ON;;;;;N;;;;; 26A5;MALE AND FEMALE SIGN;So;0;ON;;;;;N;;;;; 26A6;MALE WITH STROKE SIGN;So;0;ON;;;;;N;;;;; 26A7;MALE WITH STROKE AND MALE AND FEMALE SIGN;So;0;ON;;;;;N;;;;; 26A8;VERTICAL MALE WITH STROKE SIGN;So;0;ON;;;;;N;;;;; 26A9;HORIZONTAL MALE WITH STROKE SIGN;So;0;ON;;;;;N;;;;; 26AA;MEDIUM WHITE CIRCLE;So;0;ON;;;;;N;;;;; 26AB;MEDIUM BLACK CIRCLE;So;0;ON;;;;;N;;;;; 26AC;MEDIUM SMALL WHITE CIRCLE;So;0;L;;;;;N;;;;; 26AD;MARRIAGE SYMBOL;So;0;ON;;;;;N;;;;; 26AE;DIVORCE SYMBOL;So;0;ON;;;;;N;;;;; 26AF;UNMARRIED PARTNERSHIP SYMBOL;So;0;ON;;;;;N;;;;; 26B0;COFFIN;So;0;ON;;;;;N;;;;; 26B1;FUNERAL URN;So;0;ON;;;;;N;;;;; 26B2;NEUTER;So;0;ON;;;;;N;;;;; 26B3;CERES;So;0;ON;;;;;N;;;;; 26B4;PALLAS;So;0;ON;;;;;N;;;;; 26B5;JUNO;So;0;ON;;;;;N;;;;; 26B6;VESTA;So;0;ON;;;;;N;;;;; 26B7;CHIRON;So;0;ON;;;;;N;;;;; 26B8;BLACK MOON LILITH;So;0;ON;;;;;N;;;;; 26B9;SEXTILE;So;0;ON;;;;;N;;;;; 26BA;SEMISEXTILE;So;0;ON;;;;;N;;;;; 26BB;QUINCUNX;So;0;ON;;;;;N;;;;; 26BC;SESQUIQUADRATE;So;0;ON;;;;;N;;;;; 26BD;SOCCER BALL;So;0;ON;;;;;N;;;;; 26BE;BASEBALL;So;0;ON;;;;;N;;;;; 26BF;SQUARED KEY;So;0;ON;;;;;N;;;;; 26C0;WHITE DRAUGHTS MAN;So;0;ON;;;;;N;;;;; 26C1;WHITE DRAUGHTS KING;So;0;ON;;;;;N;;;;; 26C2;BLACK DRAUGHTS MAN;So;0;ON;;;;;N;;;;; 26C3;BLACK DRAUGHTS KING;So;0;ON;;;;;N;;;;; 26C4;SNOWMAN WITHOUT SNOW;So;0;ON;;;;;N;;;;; 26C5;SUN BEHIND CLOUD;So;0;ON;;;;;N;;;;; 26C6;RAIN;So;0;ON;;;;;N;;;;; 26C7;BLACK SNOWMAN;So;0;ON;;;;;N;;;;; 26C8;THUNDER CLOUD AND RAIN;So;0;ON;;;;;N;;;;; 26C9;TURNED WHITE SHOGI PIECE;So;0;ON;;;;;N;;;;; 26CA;TURNED BLACK SHOGI PIECE;So;0;ON;;;;;N;;;;; 26CB;WHITE DIAMOND IN SQUARE;So;0;ON;;;;;N;;;;; 26CC;CROSSING LANES;So;0;ON;;;;;N;;;;; 26CD;DISABLED CAR;So;0;ON;;;;;N;;;;; 26CE;OPHIUCHUS;So;0;ON;;;;;N;;;;; 26CF;PICK;So;0;ON;;;;;N;;;;; 26D0;CAR SLIDING;So;0;ON;;;;;N;;;;; 26D1;HELMET WITH WHITE CROSS;So;0;ON;;;;;N;;;;; 26D2;CIRCLED CROSSING LANES;So;0;ON;;;;;N;;;;; 26D3;CHAINS;So;0;ON;;;;;N;;;;; 26D4;NO ENTRY;So;0;ON;;;;;N;;;;; 26D5;ALTERNATE ONE-WAY LEFT WAY TRAFFIC;So;0;ON;;;;;N;;;;; 26D6;BLACK TWO-WAY LEFT WAY TRAFFIC;So;0;ON;;;;;N;;;;; 26D7;WHITE TWO-WAY LEFT WAY TRAFFIC;So;0;ON;;;;;N;;;;; 26D8;BLACK LEFT LANE MERGE;So;0;ON;;;;;N;;;;; 26D9;WHITE LEFT LANE MERGE;So;0;ON;;;;;N;;;;; 26DA;DRIVE SLOW SIGN;So;0;ON;;;;;N;;;;; 26DB;HEAVY WHITE DOWN-POINTING TRIANGLE;So;0;ON;;;;;N;;;;; 26DC;LEFT CLOSED ENTRY;So;0;ON;;;;;N;;;;; 26DD;SQUARED SALTIRE;So;0;ON;;;;;N;;;;; 26DE;FALLING DIAGONAL IN WHITE CIRCLE IN BLACK SQUARE;So;0;ON;;;;;N;;;;; 26DF;BLACK TRUCK;So;0;ON;;;;;N;;;;; 26E0;RESTRICTED LEFT ENTRY-1;So;0;ON;;;;;N;;;;; 26E1;RESTRICTED LEFT ENTRY-2;So;0;ON;;;;;N;;;;; 26E2;ASTRONOMICAL SYMBOL FOR URANUS;So;0;ON;;;;;N;;;;; 26E3;HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE;So;0;ON;;;;;N;;;;; 26E4;PENTAGRAM;So;0;ON;;;;;N;;;;; 26E5;RIGHT-HANDED INTERLACED PENTAGRAM;So;0;ON;;;;;N;;;;; 26E6;LEFT-HANDED INTERLACED PENTAGRAM;So;0;ON;;;;;N;;;;; 26E7;INVERTED PENTAGRAM;So;0;ON;;;;;N;;;;; 26E8;BLACK CROSS ON SHIELD;So;0;ON;;;;;N;;;;; 26E9;SHINTO SHRINE;So;0;ON;;;;;N;;;;; 26EA;CHURCH;So;0;ON;;;;;N;;;;; 26EB;CASTLE;So;0;ON;;;;;N;;;;; 26EC;HISTORIC SITE;So;0;ON;;;;;N;;;;; 26ED;GEAR WITHOUT HUB;So;0;ON;;;;;N;;;;; 26EE;GEAR WITH HANDLES;So;0;ON;;;;;N;;;;; 26EF;MAP SYMBOL FOR LIGHTHOUSE;So;0;ON;;;;;N;;;;; 26F0;MOUNTAIN;So;0;ON;;;;;N;;;;; 26F1;UMBRELLA ON GROUND;So;0;ON;;;;;N;;;;; 26F2;FOUNTAIN;So;0;ON;;;;;N;;;;; 26F3;FLAG IN HOLE;So;0;ON;;;;;N;;;;; 26F4;FERRY;So;0;ON;;;;;N;;;;; 26F5;SAILBOAT;So;0;ON;;;;;N;;;;; 26F6;SQUARE FOUR CORNERS;So;0;ON;;;;;N;;;;; 26F7;SKIER;So;0;ON;;;;;N;;;;; 26F8;ICE SKATE;So;0;ON;;;;;N;;;;; 26F9;PERSON WITH BALL;So;0;ON;;;;;N;;;;; 26FA;TENT;So;0;ON;;;;;N;;;;; 26FB;JAPANESE BANK SYMBOL;So;0;ON;;;;;N;;;;; 26FC;HEADSTONE GRAVEYARD SYMBOL;So;0;ON;;;;;N;;;;; 26FD;FUEL PUMP;So;0;ON;;;;;N;;;;; 26FE;CUP ON BLACK SQUARE;So;0;ON;;;;;N;;;;; 26FF;WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE;So;0;ON;;;;;N;;;;; 2700;BLACK SAFETY SCISSORS;So;0;ON;;;;;N;;;;; 2701;UPPER BLADE SCISSORS;So;0;ON;;;;;N;;;;; 2702;BLACK SCISSORS;So;0;ON;;;;;N;;;;; 2703;LOWER BLADE SCISSORS;So;0;ON;;;;;N;;;;; 2704;WHITE SCISSORS;So;0;ON;;;;;N;;;;; 2705;WHITE HEAVY CHECK MARK;So;0;ON;;;;;N;;;;; 2706;TELEPHONE LOCATION SIGN;So;0;ON;;;;;N;;;;; 2707;TAPE DRIVE;So;0;ON;;;;;N;;;;; 2708;AIRPLANE;So;0;ON;;;;;N;;;;; 2709;ENVELOPE;So;0;ON;;;;;N;;;;; 270A;RAISED FIST;So;0;ON;;;;;N;;;;; 270B;RAISED HAND;So;0;ON;;;;;N;;;;; 270C;VICTORY HAND;So;0;ON;;;;;N;;;;; 270D;WRITING HAND;So;0;ON;;;;;N;;;;; 270E;LOWER RIGHT PENCIL;So;0;ON;;;;;N;;;;; 270F;PENCIL;So;0;ON;;;;;N;;;;; 2710;UPPER RIGHT PENCIL;So;0;ON;;;;;N;;;;; 2711;WHITE NIB;So;0;ON;;;;;N;;;;; 2712;BLACK NIB;So;0;ON;;;;;N;;;;; 2713;CHECK MARK;So;0;ON;;;;;N;;;;; 2714;HEAVY CHECK MARK;So;0;ON;;;;;N;;;;; 2715;MULTIPLICATION X;So;0;ON;;;;;N;;;;; 2716;HEAVY MULTIPLICATION X;So;0;ON;;;;;N;;;;; 2717;BALLOT X;So;0;ON;;;;;N;;;;; 2718;HEAVY BALLOT X;So;0;ON;;;;;N;;;;; 2719;OUTLINED GREEK CROSS;So;0;ON;;;;;N;;;;; 271A;HEAVY GREEK CROSS;So;0;ON;;;;;N;;;;; 271B;OPEN CENTRE CROSS;So;0;ON;;;;;N;OPEN CENTER CROSS;;;; 271C;HEAVY OPEN CENTRE CROSS;So;0;ON;;;;;N;HEAVY OPEN CENTER CROSS;;;; 271D;LATIN CROSS;So;0;ON;;;;;N;;;;; 271E;SHADOWED WHITE LATIN CROSS;So;0;ON;;;;;N;;;;; 271F;OUTLINED LATIN CROSS;So;0;ON;;;;;N;;;;; 2720;MALTESE CROSS;So;0;ON;;;;;N;;;;; 2721;STAR OF DAVID;So;0;ON;;;;;N;;;;; 2722;FOUR TEARDROP-SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 2723;FOUR BALLOON-SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 2724;HEAVY FOUR BALLOON-SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 2725;FOUR CLUB-SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 2726;BLACK FOUR POINTED STAR;So;0;ON;;;;;N;;;;; 2727;WHITE FOUR POINTED STAR;So;0;ON;;;;;N;;;;; 2728;SPARKLES;So;0;ON;;;;;N;;;;; 2729;STRESS OUTLINED WHITE STAR;So;0;ON;;;;;N;;;;; 272A;CIRCLED WHITE STAR;So;0;ON;;;;;N;;;;; 272B;OPEN CENTRE BLACK STAR;So;0;ON;;;;;N;OPEN CENTER BLACK STAR;;;; 272C;BLACK CENTRE WHITE STAR;So;0;ON;;;;;N;BLACK CENTER WHITE STAR;;;; 272D;OUTLINED BLACK STAR;So;0;ON;;;;;N;;;;; 272E;HEAVY OUTLINED BLACK STAR;So;0;ON;;;;;N;;;;; 272F;PINWHEEL STAR;So;0;ON;;;;;N;;;;; 2730;SHADOWED WHITE STAR;So;0;ON;;;;;N;;;;; 2731;HEAVY ASTERISK;So;0;ON;;;;;N;;;;; 2732;OPEN CENTRE ASTERISK;So;0;ON;;;;;N;OPEN CENTER ASTERISK;;;; 2733;EIGHT SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 2734;EIGHT POINTED BLACK STAR;So;0;ON;;;;;N;;;;; 2735;EIGHT POINTED PINWHEEL STAR;So;0;ON;;;;;N;;;;; 2736;SIX POINTED BLACK STAR;So;0;ON;;;;;N;;;;; 2737;EIGHT POINTED RECTILINEAR BLACK STAR;So;0;ON;;;;;N;;;;; 2738;HEAVY EIGHT POINTED RECTILINEAR BLACK STAR;So;0;ON;;;;;N;;;;; 2739;TWELVE POINTED BLACK STAR;So;0;ON;;;;;N;;;;; 273A;SIXTEEN POINTED ASTERISK;So;0;ON;;;;;N;;;;; 273B;TEARDROP-SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 273C;OPEN CENTRE TEARDROP-SPOKED ASTERISK;So;0;ON;;;;;N;OPEN CENTER TEARDROP-SPOKED ASTERISK;;;; 273D;HEAVY TEARDROP-SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 273E;SIX PETALLED BLACK AND WHITE FLORETTE;So;0;ON;;;;;N;;;;; 273F;BLACK FLORETTE;So;0;ON;;;;;N;;;;; 2740;WHITE FLORETTE;So;0;ON;;;;;N;;;;; 2741;EIGHT PETALLED OUTLINED BLACK FLORETTE;So;0;ON;;;;;N;;;;; 2742;CIRCLED OPEN CENTRE EIGHT POINTED STAR;So;0;ON;;;;;N;CIRCLED OPEN CENTER EIGHT POINTED STAR;;;; 2743;HEAVY TEARDROP-SPOKED PINWHEEL ASTERISK;So;0;ON;;;;;N;;;;; 2744;SNOWFLAKE;So;0;ON;;;;;N;;;;; 2745;TIGHT TRIFOLIATE SNOWFLAKE;So;0;ON;;;;;N;;;;; 2746;HEAVY CHEVRON SNOWFLAKE;So;0;ON;;;;;N;;;;; 2747;SPARKLE;So;0;ON;;;;;N;;;;; 2748;HEAVY SPARKLE;So;0;ON;;;;;N;;;;; 2749;BALLOON-SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 274A;EIGHT TEARDROP-SPOKED PROPELLER ASTERISK;So;0;ON;;;;;N;;;;; 274B;HEAVY EIGHT TEARDROP-SPOKED PROPELLER ASTERISK;So;0;ON;;;;;N;;;;; 274C;CROSS MARK;So;0;ON;;;;;N;;;;; 274D;SHADOWED WHITE CIRCLE;So;0;ON;;;;;N;;;;; 274E;NEGATIVE SQUARED CROSS MARK;So;0;ON;;;;;N;;;;; 274F;LOWER RIGHT DROP-SHADOWED WHITE SQUARE;So;0;ON;;;;;N;;;;; 2750;UPPER RIGHT DROP-SHADOWED WHITE SQUARE;So;0;ON;;;;;N;;;;; 2751;LOWER RIGHT SHADOWED WHITE SQUARE;So;0;ON;;;;;N;;;;; 2752;UPPER RIGHT SHADOWED WHITE SQUARE;So;0;ON;;;;;N;;;;; 2753;BLACK QUESTION MARK ORNAMENT;So;0;ON;;;;;N;;;;; 2754;WHITE QUESTION MARK ORNAMENT;So;0;ON;;;;;N;;;;; 2755;WHITE EXCLAMATION MARK ORNAMENT;So;0;ON;;;;;N;;;;; 2756;BLACK DIAMOND MINUS WHITE X;So;0;ON;;;;;N;;;;; 2757;HEAVY EXCLAMATION MARK SYMBOL;So;0;ON;;;;;N;;;;; 2758;LIGHT VERTICAL BAR;So;0;ON;;;;;N;;;;; 2759;MEDIUM VERTICAL BAR;So;0;ON;;;;;N;;;;; 275A;HEAVY VERTICAL BAR;So;0;ON;;;;;N;;;;; 275B;HEAVY SINGLE TURNED COMMA QUOTATION MARK ORNAMENT;So;0;ON;;;;;N;;;;; 275C;HEAVY SINGLE COMMA QUOTATION MARK ORNAMENT;So;0;ON;;;;;N;;;;; 275D;HEAVY DOUBLE TURNED COMMA QUOTATION MARK ORNAMENT;So;0;ON;;;;;N;;;;; 275E;HEAVY DOUBLE COMMA QUOTATION MARK ORNAMENT;So;0;ON;;;;;N;;;;; 275F;HEAVY LOW SINGLE COMMA QUOTATION MARK ORNAMENT;So;0;ON;;;;;N;;;;; 2760;HEAVY LOW DOUBLE COMMA QUOTATION MARK ORNAMENT;So;0;ON;;;;;N;;;;; 2761;CURVED STEM PARAGRAPH SIGN ORNAMENT;So;0;ON;;;;;N;;;;; 2762;HEAVY EXCLAMATION MARK ORNAMENT;So;0;ON;;;;;N;;;;; 2763;HEAVY HEART EXCLAMATION MARK ORNAMENT;So;0;ON;;;;;N;;;;; 2764;HEAVY BLACK HEART;So;0;ON;;;;;N;;;;; 2765;ROTATED HEAVY BLACK HEART BULLET;So;0;ON;;;;;N;;;;; 2766;FLORAL HEART;So;0;ON;;;;;N;;;;; 2767;ROTATED FLORAL HEART BULLET;So;0;ON;;;;;N;;;;; 2768;MEDIUM LEFT PARENTHESIS ORNAMENT;Ps;0;ON;;;;;Y;;;;; 2769;MEDIUM RIGHT PARENTHESIS ORNAMENT;Pe;0;ON;;;;;Y;;;;; 276A;MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT;Ps;0;ON;;;;;Y;;;;; 276B;MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT;Pe;0;ON;;;;;Y;;;;; 276C;MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT;Ps;0;ON;;;;;Y;;;;; 276D;MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT;Pe;0;ON;;;;;Y;;;;; 276E;HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT;Ps;0;ON;;;;;Y;;;;; 276F;HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT;Pe;0;ON;;;;;Y;;;;; 2770;HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT;Ps;0;ON;;;;;Y;;;;; 2771;HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT;Pe;0;ON;;;;;Y;;;;; 2772;LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT;Ps;0;ON;;;;;Y;;;;; 2773;LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT;Pe;0;ON;;;;;Y;;;;; 2774;MEDIUM LEFT CURLY BRACKET ORNAMENT;Ps;0;ON;;;;;Y;;;;; 2775;MEDIUM RIGHT CURLY BRACKET ORNAMENT;Pe;0;ON;;;;;Y;;;;; 2776;DINGBAT NEGATIVE CIRCLED DIGIT ONE;No;0;ON;;;1;1;N;INVERSE CIRCLED DIGIT ONE;;;; 2777;DINGBAT NEGATIVE CIRCLED DIGIT TWO;No;0;ON;;;2;2;N;INVERSE CIRCLED DIGIT TWO;;;; 2778;DINGBAT NEGATIVE CIRCLED DIGIT THREE;No;0;ON;;;3;3;N;INVERSE CIRCLED DIGIT THREE;;;; 2779;DINGBAT NEGATIVE CIRCLED DIGIT FOUR;No;0;ON;;;4;4;N;INVERSE CIRCLED DIGIT FOUR;;;; 277A;DINGBAT NEGATIVE CIRCLED DIGIT FIVE;No;0;ON;;;5;5;N;INVERSE CIRCLED DIGIT FIVE;;;; 277B;DINGBAT NEGATIVE CIRCLED DIGIT SIX;No;0;ON;;;6;6;N;INVERSE CIRCLED DIGIT SIX;;;; 277C;DINGBAT NEGATIVE CIRCLED DIGIT SEVEN;No;0;ON;;;7;7;N;INVERSE CIRCLED DIGIT SEVEN;;;; 277D;DINGBAT NEGATIVE CIRCLED DIGIT EIGHT;No;0;ON;;;8;8;N;INVERSE CIRCLED DIGIT EIGHT;;;; 277E;DINGBAT NEGATIVE CIRCLED DIGIT NINE;No;0;ON;;;9;9;N;INVERSE CIRCLED DIGIT NINE;;;; 277F;DINGBAT NEGATIVE CIRCLED NUMBER TEN;No;0;ON;;;;10;N;INVERSE CIRCLED NUMBER TEN;;;; 2780;DINGBAT CIRCLED SANS-SERIF DIGIT ONE;No;0;ON;;;1;1;N;CIRCLED SANS-SERIF DIGIT ONE;;;; 2781;DINGBAT CIRCLED SANS-SERIF DIGIT TWO;No;0;ON;;;2;2;N;CIRCLED SANS-SERIF DIGIT TWO;;;; 2782;DINGBAT CIRCLED SANS-SERIF DIGIT THREE;No;0;ON;;;3;3;N;CIRCLED SANS-SERIF DIGIT THREE;;;; 2783;DINGBAT CIRCLED SANS-SERIF DIGIT FOUR;No;0;ON;;;4;4;N;CIRCLED SANS-SERIF DIGIT FOUR;;;; 2784;DINGBAT CIRCLED SANS-SERIF DIGIT FIVE;No;0;ON;;;5;5;N;CIRCLED SANS-SERIF DIGIT FIVE;;;; 2785;DINGBAT CIRCLED SANS-SERIF DIGIT SIX;No;0;ON;;;6;6;N;CIRCLED SANS-SERIF DIGIT SIX;;;; 2786;DINGBAT CIRCLED SANS-SERIF DIGIT SEVEN;No;0;ON;;;7;7;N;CIRCLED SANS-SERIF DIGIT SEVEN;;;; 2787;DINGBAT CIRCLED SANS-SERIF DIGIT EIGHT;No;0;ON;;;8;8;N;CIRCLED SANS-SERIF DIGIT EIGHT;;;; 2788;DINGBAT CIRCLED SANS-SERIF DIGIT NINE;No;0;ON;;;9;9;N;CIRCLED SANS-SERIF DIGIT NINE;;;; 2789;DINGBAT CIRCLED SANS-SERIF NUMBER TEN;No;0;ON;;;;10;N;CIRCLED SANS-SERIF NUMBER TEN;;;; 278A;DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ONE;No;0;ON;;;1;1;N;INVERSE CIRCLED SANS-SERIF DIGIT ONE;;;; 278B;DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT TWO;No;0;ON;;;2;2;N;INVERSE CIRCLED SANS-SERIF DIGIT TWO;;;; 278C;DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT THREE;No;0;ON;;;3;3;N;INVERSE CIRCLED SANS-SERIF DIGIT THREE;;;; 278D;DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FOUR;No;0;ON;;;4;4;N;INVERSE CIRCLED SANS-SERIF DIGIT FOUR;;;; 278E;DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FIVE;No;0;ON;;;5;5;N;INVERSE CIRCLED SANS-SERIF DIGIT FIVE;;;; 278F;DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SIX;No;0;ON;;;6;6;N;INVERSE CIRCLED SANS-SERIF DIGIT SIX;;;; 2790;DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SEVEN;No;0;ON;;;7;7;N;INVERSE CIRCLED SANS-SERIF DIGIT SEVEN;;;; 2791;DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT EIGHT;No;0;ON;;;8;8;N;INVERSE CIRCLED SANS-SERIF DIGIT EIGHT;;;; 2792;DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT NINE;No;0;ON;;;9;9;N;INVERSE CIRCLED SANS-SERIF DIGIT NINE;;;; 2793;DINGBAT NEGATIVE CIRCLED SANS-SERIF NUMBER TEN;No;0;ON;;;;10;N;INVERSE CIRCLED SANS-SERIF NUMBER TEN;;;; 2794;HEAVY WIDE-HEADED RIGHTWARDS ARROW;So;0;ON;;;;;N;HEAVY WIDE-HEADED RIGHT ARROW;;;; 2795;HEAVY PLUS SIGN;So;0;ON;;;;;N;;;;; 2796;HEAVY MINUS SIGN;So;0;ON;;;;;N;;;;; 2797;HEAVY DIVISION SIGN;So;0;ON;;;;;N;;;;; 2798;HEAVY SOUTH EAST ARROW;So;0;ON;;;;;N;HEAVY LOWER RIGHT ARROW;;;; 2799;HEAVY RIGHTWARDS ARROW;So;0;ON;;;;;N;HEAVY RIGHT ARROW;;;; 279A;HEAVY NORTH EAST ARROW;So;0;ON;;;;;N;HEAVY UPPER RIGHT ARROW;;;; 279B;DRAFTING POINT RIGHTWARDS ARROW;So;0;ON;;;;;N;DRAFTING POINT RIGHT ARROW;;;; 279C;HEAVY ROUND-TIPPED RIGHTWARDS ARROW;So;0;ON;;;;;N;HEAVY ROUND-TIPPED RIGHT ARROW;;;; 279D;TRIANGLE-HEADED RIGHTWARDS ARROW;So;0;ON;;;;;N;TRIANGLE-HEADED RIGHT ARROW;;;; 279E;HEAVY TRIANGLE-HEADED RIGHTWARDS ARROW;So;0;ON;;;;;N;HEAVY TRIANGLE-HEADED RIGHT ARROW;;;; 279F;DASHED TRIANGLE-HEADED RIGHTWARDS ARROW;So;0;ON;;;;;N;DASHED TRIANGLE-HEADED RIGHT ARROW;;;; 27A0;HEAVY DASHED TRIANGLE-HEADED RIGHTWARDS ARROW;So;0;ON;;;;;N;HEAVY DASHED TRIANGLE-HEADED RIGHT ARROW;;;; 27A1;BLACK RIGHTWARDS ARROW;So;0;ON;;;;;N;BLACK RIGHT ARROW;;;; 27A2;THREE-D TOP-LIGHTED RIGHTWARDS ARROWHEAD;So;0;ON;;;;;N;THREE-D TOP-LIGHTED RIGHT ARROWHEAD;;;; 27A3;THREE-D BOTTOM-LIGHTED RIGHTWARDS ARROWHEAD;So;0;ON;;;;;N;THREE-D BOTTOM-LIGHTED RIGHT ARROWHEAD;;;; 27A4;BLACK RIGHTWARDS ARROWHEAD;So;0;ON;;;;;N;BLACK RIGHT ARROWHEAD;;;; 27A5;HEAVY BLACK CURVED DOWNWARDS AND RIGHTWARDS ARROW;So;0;ON;;;;;N;HEAVY BLACK CURVED DOWN AND RIGHT ARROW;;;; 27A6;HEAVY BLACK CURVED UPWARDS AND RIGHTWARDS ARROW;So;0;ON;;;;;N;HEAVY BLACK CURVED UP AND RIGHT ARROW;;;; 27A7;SQUAT BLACK RIGHTWARDS ARROW;So;0;ON;;;;;N;SQUAT BLACK RIGHT ARROW;;;; 27A8;HEAVY CONCAVE-POINTED BLACK RIGHTWARDS ARROW;So;0;ON;;;;;N;HEAVY CONCAVE-POINTED BLACK RIGHT ARROW;;;; 27A9;RIGHT-SHADED WHITE RIGHTWARDS ARROW;So;0;ON;;;;;N;RIGHT-SHADED WHITE RIGHT ARROW;;;; 27AA;LEFT-SHADED WHITE RIGHTWARDS ARROW;So;0;ON;;;;;N;LEFT-SHADED WHITE RIGHT ARROW;;;; 27AB;BACK-TILTED SHADOWED WHITE RIGHTWARDS ARROW;So;0;ON;;;;;N;BACK-TILTED SHADOWED WHITE RIGHT ARROW;;;; 27AC;FRONT-TILTED SHADOWED WHITE RIGHTWARDS ARROW;So;0;ON;;;;;N;FRONT-TILTED SHADOWED WHITE RIGHT ARROW;;;; 27AD;HEAVY LOWER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW;So;0;ON;;;;;N;HEAVY LOWER RIGHT-SHADOWED WHITE RIGHT ARROW;;;; 27AE;HEAVY UPPER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW;So;0;ON;;;;;N;HEAVY UPPER RIGHT-SHADOWED WHITE RIGHT ARROW;;;; 27AF;NOTCHED LOWER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW;So;0;ON;;;;;N;NOTCHED LOWER RIGHT-SHADOWED WHITE RIGHT ARROW;;;; 27B0;CURLY LOOP;So;0;ON;;;;;N;;;;; 27B1;NOTCHED UPPER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW;So;0;ON;;;;;N;NOTCHED UPPER RIGHT-SHADOWED WHITE RIGHT ARROW;;;; 27B2;CIRCLED HEAVY WHITE RIGHTWARDS ARROW;So;0;ON;;;;;N;CIRCLED HEAVY WHITE RIGHT ARROW;;;; 27B3;WHITE-FEATHERED RIGHTWARDS ARROW;So;0;ON;;;;;N;WHITE-FEATHERED RIGHT ARROW;;;; 27B4;BLACK-FEATHERED SOUTH EAST ARROW;So;0;ON;;;;;N;BLACK-FEATHERED LOWER RIGHT ARROW;;;; 27B5;BLACK-FEATHERED RIGHTWARDS ARROW;So;0;ON;;;;;N;BLACK-FEATHERED RIGHT ARROW;;;; 27B6;BLACK-FEATHERED NORTH EAST ARROW;So;0;ON;;;;;N;BLACK-FEATHERED UPPER RIGHT ARROW;;;; 27B7;HEAVY BLACK-FEATHERED SOUTH EAST ARROW;So;0;ON;;;;;N;HEAVY BLACK-FEATHERED LOWER RIGHT ARROW;;;; 27B8;HEAVY BLACK-FEATHERED RIGHTWARDS ARROW;So;0;ON;;;;;N;HEAVY BLACK-FEATHERED RIGHT ARROW;;;; 27B9;HEAVY BLACK-FEATHERED NORTH EAST ARROW;So;0;ON;;;;;N;HEAVY BLACK-FEATHERED UPPER RIGHT ARROW;;;; 27BA;TEARDROP-BARBED RIGHTWARDS ARROW;So;0;ON;;;;;N;TEARDROP-BARBED RIGHT ARROW;;;; 27BB;HEAVY TEARDROP-SHANKED RIGHTWARDS ARROW;So;0;ON;;;;;N;HEAVY TEARDROP-SHANKED RIGHT ARROW;;;; 27BC;WEDGE-TAILED RIGHTWARDS ARROW;So;0;ON;;;;;N;WEDGE-TAILED RIGHT ARROW;;;; 27BD;HEAVY WEDGE-TAILED RIGHTWARDS ARROW;So;0;ON;;;;;N;HEAVY WEDGE-TAILED RIGHT ARROW;;;; 27BE;OPEN-OUTLINED RIGHTWARDS ARROW;So;0;ON;;;;;N;OPEN-OUTLINED RIGHT ARROW;;;; 27BF;DOUBLE CURLY LOOP;So;0;ON;;;;;N;;;;; 27C0;THREE DIMENSIONAL ANGLE;Sm;0;ON;;;;;Y;;;;; 27C1;WHITE TRIANGLE CONTAINING SMALL WHITE TRIANGLE;Sm;0;ON;;;;;N;;;;; 27C2;PERPENDICULAR;Sm;0;ON;;;;;N;;;;; 27C3;OPEN SUBSET;Sm;0;ON;;;;;Y;;;;; 27C4;OPEN SUPERSET;Sm;0;ON;;;;;Y;;;;; 27C5;LEFT S-SHAPED BAG DELIMITER;Ps;0;ON;;;;;Y;;;;; 27C6;RIGHT S-SHAPED BAG DELIMITER;Pe;0;ON;;;;;Y;;;;; 27C7;OR WITH DOT INSIDE;Sm;0;ON;;;;;N;;;;; 27C8;REVERSE SOLIDUS PRECEDING SUBSET;Sm;0;ON;;;;;Y;;;;; 27C9;SUPERSET PRECEDING SOLIDUS;Sm;0;ON;;;;;Y;;;;; 27CA;VERTICAL BAR WITH HORIZONTAL STROKE;Sm;0;ON;;;;;N;;;;; 27CB;MATHEMATICAL RISING DIAGONAL;Sm;0;ON;;;;;Y;;;;; 27CC;LONG DIVISION;Sm;0;ON;;;;;Y;;;;; 27CD;MATHEMATICAL FALLING DIAGONAL;Sm;0;ON;;;;;Y;;;;; 27CE;SQUARED LOGICAL AND;Sm;0;ON;;;;;N;;;;; 27CF;SQUARED LOGICAL OR;Sm;0;ON;;;;;N;;;;; 27D0;WHITE DIAMOND WITH CENTRED DOT;Sm;0;ON;;;;;N;;;;; 27D1;AND WITH DOT;Sm;0;ON;;;;;N;;;;; 27D2;ELEMENT OF OPENING UPWARDS;Sm;0;ON;;;;;N;;;;; 27D3;LOWER RIGHT CORNER WITH DOT;Sm;0;ON;;;;;Y;;;;; 27D4;UPPER LEFT CORNER WITH DOT;Sm;0;ON;;;;;Y;;;;; 27D5;LEFT OUTER JOIN;Sm;0;ON;;;;;Y;;;;; 27D6;RIGHT OUTER JOIN;Sm;0;ON;;;;;Y;;;;; 27D7;FULL OUTER JOIN;Sm;0;ON;;;;;N;;;;; 27D8;LARGE UP TACK;Sm;0;ON;;;;;N;;;;; 27D9;LARGE DOWN TACK;Sm;0;ON;;;;;N;;;;; 27DA;LEFT AND RIGHT DOUBLE TURNSTILE;Sm;0;ON;;;;;N;;;;; 27DB;LEFT AND RIGHT TACK;Sm;0;ON;;;;;N;;;;; 27DC;LEFT MULTIMAP;Sm;0;ON;;;;;Y;;;;; 27DD;LONG RIGHT TACK;Sm;0;ON;;;;;Y;;;;; 27DE;LONG LEFT TACK;Sm;0;ON;;;;;Y;;;;; 27DF;UP TACK WITH CIRCLE ABOVE;Sm;0;ON;;;;;N;;;;; 27E0;LOZENGE DIVIDED BY HORIZONTAL RULE;Sm;0;ON;;;;;N;;;;; 27E1;WHITE CONCAVE-SIDED DIAMOND;Sm;0;ON;;;;;N;;;;; 27E2;WHITE CONCAVE-SIDED DIAMOND WITH LEFTWARDS TICK;Sm;0;ON;;;;;Y;;;;; 27E3;WHITE CONCAVE-SIDED DIAMOND WITH RIGHTWARDS TICK;Sm;0;ON;;;;;Y;;;;; 27E4;WHITE SQUARE WITH LEFTWARDS TICK;Sm;0;ON;;;;;Y;;;;; 27E5;WHITE SQUARE WITH RIGHTWARDS TICK;Sm;0;ON;;;;;Y;;;;; 27E6;MATHEMATICAL LEFT WHITE SQUARE BRACKET;Ps;0;ON;;;;;Y;;;;; 27E7;MATHEMATICAL RIGHT WHITE SQUARE BRACKET;Pe;0;ON;;;;;Y;;;;; 27E8;MATHEMATICAL LEFT ANGLE BRACKET;Ps;0;ON;;;;;Y;;;;; 27E9;MATHEMATICAL RIGHT ANGLE BRACKET;Pe;0;ON;;;;;Y;;;;; 27EA;MATHEMATICAL LEFT DOUBLE ANGLE BRACKET;Ps;0;ON;;;;;Y;;;;; 27EB;MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET;Pe;0;ON;;;;;Y;;;;; 27EC;MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET;Ps;0;ON;;;;;Y;;;;; 27ED;MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET;Pe;0;ON;;;;;Y;;;;; 27EE;MATHEMATICAL LEFT FLATTENED PARENTHESIS;Ps;0;ON;;;;;Y;;;;; 27EF;MATHEMATICAL RIGHT FLATTENED PARENTHESIS;Pe;0;ON;;;;;Y;;;;; 27F0;UPWARDS QUADRUPLE ARROW;Sm;0;ON;;;;;N;;;;; 27F1;DOWNWARDS QUADRUPLE ARROW;Sm;0;ON;;;;;N;;;;; 27F2;ANTICLOCKWISE GAPPED CIRCLE ARROW;Sm;0;ON;;;;;N;;;;; 27F3;CLOCKWISE GAPPED CIRCLE ARROW;Sm;0;ON;;;;;N;;;;; 27F4;RIGHT ARROW WITH CIRCLED PLUS;Sm;0;ON;;;;;N;;;;; 27F5;LONG LEFTWARDS ARROW;Sm;0;ON;;;;;N;;;;; 27F6;LONG RIGHTWARDS ARROW;Sm;0;ON;;;;;N;;;;; 27F7;LONG LEFT RIGHT ARROW;Sm;0;ON;;;;;N;;;;; 27F8;LONG LEFTWARDS DOUBLE ARROW;Sm;0;ON;;;;;N;;;;; 27F9;LONG RIGHTWARDS DOUBLE ARROW;Sm;0;ON;;;;;N;;;;; 27FA;LONG LEFT RIGHT DOUBLE ARROW;Sm;0;ON;;;;;N;;;;; 27FB;LONG LEFTWARDS ARROW FROM BAR;Sm;0;ON;;;;;N;;;;; 27FC;LONG RIGHTWARDS ARROW FROM BAR;Sm;0;ON;;;;;N;;;;; 27FD;LONG LEFTWARDS DOUBLE ARROW FROM BAR;Sm;0;ON;;;;;N;;;;; 27FE;LONG RIGHTWARDS DOUBLE ARROW FROM BAR;Sm;0;ON;;;;;N;;;;; 27FF;LONG RIGHTWARDS SQUIGGLE ARROW;Sm;0;ON;;;;;N;;;;; 2800;BRAILLE PATTERN BLANK;So;0;L;;;;;N;;;;; 2801;BRAILLE PATTERN DOTS-1;So;0;L;;;;;N;;;;; 2802;BRAILLE PATTERN DOTS-2;So;0;L;;;;;N;;;;; 2803;BRAILLE PATTERN DOTS-12;So;0;L;;;;;N;;;;; 2804;BRAILLE PATTERN DOTS-3;So;0;L;;;;;N;;;;; 2805;BRAILLE PATTERN DOTS-13;So;0;L;;;;;N;;;;; 2806;BRAILLE PATTERN DOTS-23;So;0;L;;;;;N;;;;; 2807;BRAILLE PATTERN DOTS-123;So;0;L;;;;;N;;;;; 2808;BRAILLE PATTERN DOTS-4;So;0;L;;;;;N;;;;; 2809;BRAILLE PATTERN DOTS-14;So;0;L;;;;;N;;;;; 280A;BRAILLE PATTERN DOTS-24;So;0;L;;;;;N;;;;; 280B;BRAILLE PATTERN DOTS-124;So;0;L;;;;;N;;;;; 280C;BRAILLE PATTERN DOTS-34;So;0;L;;;;;N;;;;; 280D;BRAILLE PATTERN DOTS-134;So;0;L;;;;;N;;;;; 280E;BRAILLE PATTERN DOTS-234;So;0;L;;;;;N;;;;; 280F;BRAILLE PATTERN DOTS-1234;So;0;L;;;;;N;;;;; 2810;BRAILLE PATTERN DOTS-5;So;0;L;;;;;N;;;;; 2811;BRAILLE PATTERN DOTS-15;So;0;L;;;;;N;;;;; 2812;BRAILLE PATTERN DOTS-25;So;0;L;;;;;N;;;;; 2813;BRAILLE PATTERN DOTS-125;So;0;L;;;;;N;;;;; 2814;BRAILLE PATTERN DOTS-35;So;0;L;;;;;N;;;;; 2815;BRAILLE PATTERN DOTS-135;So;0;L;;;;;N;;;;; 2816;BRAILLE PATTERN DOTS-235;So;0;L;;;;;N;;;;; 2817;BRAILLE PATTERN DOTS-1235;So;0;L;;;;;N;;;;; 2818;BRAILLE PATTERN DOTS-45;So;0;L;;;;;N;;;;; 2819;BRAILLE PATTERN DOTS-145;So;0;L;;;;;N;;;;; 281A;BRAILLE PATTERN DOTS-245;So;0;L;;;;;N;;;;; 281B;BRAILLE PATTERN DOTS-1245;So;0;L;;;;;N;;;;; 281C;BRAILLE PATTERN DOTS-345;So;0;L;;;;;N;;;;; 281D;BRAILLE PATTERN DOTS-1345;So;0;L;;;;;N;;;;; 281E;BRAILLE PATTERN DOTS-2345;So;0;L;;;;;N;;;;; 281F;BRAILLE PATTERN DOTS-12345;So;0;L;;;;;N;;;;; 2820;BRAILLE PATTERN DOTS-6;So;0;L;;;;;N;;;;; 2821;BRAILLE PATTERN DOTS-16;So;0;L;;;;;N;;;;; 2822;BRAILLE PATTERN DOTS-26;So;0;L;;;;;N;;;;; 2823;BRAILLE PATTERN DOTS-126;So;0;L;;;;;N;;;;; 2824;BRAILLE PATTERN DOTS-36;So;0;L;;;;;N;;;;; 2825;BRAILLE PATTERN DOTS-136;So;0;L;;;;;N;;;;; 2826;BRAILLE PATTERN DOTS-236;So;0;L;;;;;N;;;;; 2827;BRAILLE PATTERN DOTS-1236;So;0;L;;;;;N;;;;; 2828;BRAILLE PATTERN DOTS-46;So;0;L;;;;;N;;;;; 2829;BRAILLE PATTERN DOTS-146;So;0;L;;;;;N;;;;; 282A;BRAILLE PATTERN DOTS-246;So;0;L;;;;;N;;;;; 282B;BRAILLE PATTERN DOTS-1246;So;0;L;;;;;N;;;;; 282C;BRAILLE PATTERN DOTS-346;So;0;L;;;;;N;;;;; 282D;BRAILLE PATTERN DOTS-1346;So;0;L;;;;;N;;;;; 282E;BRAILLE PATTERN DOTS-2346;So;0;L;;;;;N;;;;; 282F;BRAILLE PATTERN DOTS-12346;So;0;L;;;;;N;;;;; 2830;BRAILLE PATTERN DOTS-56;So;0;L;;;;;N;;;;; 2831;BRAILLE PATTERN DOTS-156;So;0;L;;;;;N;;;;; 2832;BRAILLE PATTERN DOTS-256;So;0;L;;;;;N;;;;; 2833;BRAILLE PATTERN DOTS-1256;So;0;L;;;;;N;;;;; 2834;BRAILLE PATTERN DOTS-356;So;0;L;;;;;N;;;;; 2835;BRAILLE PATTERN DOTS-1356;So;0;L;;;;;N;;;;; 2836;BRAILLE PATTERN DOTS-2356;So;0;L;;;;;N;;;;; 2837;BRAILLE PATTERN DOTS-12356;So;0;L;;;;;N;;;;; 2838;BRAILLE PATTERN DOTS-456;So;0;L;;;;;N;;;;; 2839;BRAILLE PATTERN DOTS-1456;So;0;L;;;;;N;;;;; 283A;BRAILLE PATTERN DOTS-2456;So;0;L;;;;;N;;;;; 283B;BRAILLE PATTERN DOTS-12456;So;0;L;;;;;N;;;;; 283C;BRAILLE PATTERN DOTS-3456;So;0;L;;;;;N;;;;; 283D;BRAILLE PATTERN DOTS-13456;So;0;L;;;;;N;;;;; 283E;BRAILLE PATTERN DOTS-23456;So;0;L;;;;;N;;;;; 283F;BRAILLE PATTERN DOTS-123456;So;0;L;;;;;N;;;;; 2840;BRAILLE PATTERN DOTS-7;So;0;L;;;;;N;;;;; 2841;BRAILLE PATTERN DOTS-17;So;0;L;;;;;N;;;;; 2842;BRAILLE PATTERN DOTS-27;So;0;L;;;;;N;;;;; 2843;BRAILLE PATTERN DOTS-127;So;0;L;;;;;N;;;;; 2844;BRAILLE PATTERN DOTS-37;So;0;L;;;;;N;;;;; 2845;BRAILLE PATTERN DOTS-137;So;0;L;;;;;N;;;;; 2846;BRAILLE PATTERN DOTS-237;So;0;L;;;;;N;;;;; 2847;BRAILLE PATTERN DOTS-1237;So;0;L;;;;;N;;;;; 2848;BRAILLE PATTERN DOTS-47;So;0;L;;;;;N;;;;; 2849;BRAILLE PATTERN DOTS-147;So;0;L;;;;;N;;;;; 284A;BRAILLE PATTERN DOTS-247;So;0;L;;;;;N;;;;; 284B;BRAILLE PATTERN DOTS-1247;So;0;L;;;;;N;;;;; 284C;BRAILLE PATTERN DOTS-347;So;0;L;;;;;N;;;;; 284D;BRAILLE PATTERN DOTS-1347;So;0;L;;;;;N;;;;; 284E;BRAILLE PATTERN DOTS-2347;So;0;L;;;;;N;;;;; 284F;BRAILLE PATTERN DOTS-12347;So;0;L;;;;;N;;;;; 2850;BRAILLE PATTERN DOTS-57;So;0;L;;;;;N;;;;; 2851;BRAILLE PATTERN DOTS-157;So;0;L;;;;;N;;;;; 2852;BRAILLE PATTERN DOTS-257;So;0;L;;;;;N;;;;; 2853;BRAILLE PATTERN DOTS-1257;So;0;L;;;;;N;;;;; 2854;BRAILLE PATTERN DOTS-357;So;0;L;;;;;N;;;;; 2855;BRAILLE PATTERN DOTS-1357;So;0;L;;;;;N;;;;; 2856;BRAILLE PATTERN DOTS-2357;So;0;L;;;;;N;;;;; 2857;BRAILLE PATTERN DOTS-12357;So;0;L;;;;;N;;;;; 2858;BRAILLE PATTERN DOTS-457;So;0;L;;;;;N;;;;; 2859;BRAILLE PATTERN DOTS-1457;So;0;L;;;;;N;;;;; 285A;BRAILLE PATTERN DOTS-2457;So;0;L;;;;;N;;;;; 285B;BRAILLE PATTERN DOTS-12457;So;0;L;;;;;N;;;;; 285C;BRAILLE PATTERN DOTS-3457;So;0;L;;;;;N;;;;; 285D;BRAILLE PATTERN DOTS-13457;So;0;L;;;;;N;;;;; 285E;BRAILLE PATTERN DOTS-23457;So;0;L;;;;;N;;;;; 285F;BRAILLE PATTERN DOTS-123457;So;0;L;;;;;N;;;;; 2860;BRAILLE PATTERN DOTS-67;So;0;L;;;;;N;;;;; 2861;BRAILLE PATTERN DOTS-167;So;0;L;;;;;N;;;;; 2862;BRAILLE PATTERN DOTS-267;So;0;L;;;;;N;;;;; 2863;BRAILLE PATTERN DOTS-1267;So;0;L;;;;;N;;;;; 2864;BRAILLE PATTERN DOTS-367;So;0;L;;;;;N;;;;; 2865;BRAILLE PATTERN DOTS-1367;So;0;L;;;;;N;;;;; 2866;BRAILLE PATTERN DOTS-2367;So;0;L;;;;;N;;;;; 2867;BRAILLE PATTERN DOTS-12367;So;0;L;;;;;N;;;;; 2868;BRAILLE PATTERN DOTS-467;So;0;L;;;;;N;;;;; 2869;BRAILLE PATTERN DOTS-1467;So;0;L;;;;;N;;;;; 286A;BRAILLE PATTERN DOTS-2467;So;0;L;;;;;N;;;;; 286B;BRAILLE PATTERN DOTS-12467;So;0;L;;;;;N;;;;; 286C;BRAILLE PATTERN DOTS-3467;So;0;L;;;;;N;;;;; 286D;BRAILLE PATTERN DOTS-13467;So;0;L;;;;;N;;;;; 286E;BRAILLE PATTERN DOTS-23467;So;0;L;;;;;N;;;;; 286F;BRAILLE PATTERN DOTS-123467;So;0;L;;;;;N;;;;; 2870;BRAILLE PATTERN DOTS-567;So;0;L;;;;;N;;;;; 2871;BRAILLE PATTERN DOTS-1567;So;0;L;;;;;N;;;;; 2872;BRAILLE PATTERN DOTS-2567;So;0;L;;;;;N;;;;; 2873;BRAILLE PATTERN DOTS-12567;So;0;L;;;;;N;;;;; 2874;BRAILLE PATTERN DOTS-3567;So;0;L;;;;;N;;;;; 2875;BRAILLE PATTERN DOTS-13567;So;0;L;;;;;N;;;;; 2876;BRAILLE PATTERN DOTS-23567;So;0;L;;;;;N;;;;; 2877;BRAILLE PATTERN DOTS-123567;So;0;L;;;;;N;;;;; 2878;BRAILLE PATTERN DOTS-4567;So;0;L;;;;;N;;;;; 2879;BRAILLE PATTERN DOTS-14567;So;0;L;;;;;N;;;;; 287A;BRAILLE PATTERN DOTS-24567;So;0;L;;;;;N;;;;; 287B;BRAILLE PATTERN DOTS-124567;So;0;L;;;;;N;;;;; 287C;BRAILLE PATTERN DOTS-34567;So;0;L;;;;;N;;;;; 287D;BRAILLE PATTERN DOTS-134567;So;0;L;;;;;N;;;;; 287E;BRAILLE PATTERN DOTS-234567;So;0;L;;;;;N;;;;; 287F;BRAILLE PATTERN DOTS-1234567;So;0;L;;;;;N;;;;; 2880;BRAILLE PATTERN DOTS-8;So;0;L;;;;;N;;;;; 2881;BRAILLE PATTERN DOTS-18;So;0;L;;;;;N;;;;; 2882;BRAILLE PATTERN DOTS-28;So;0;L;;;;;N;;;;; 2883;BRAILLE PATTERN DOTS-128;So;0;L;;;;;N;;;;; 2884;BRAILLE PATTERN DOTS-38;So;0;L;;;;;N;;;;; 2885;BRAILLE PATTERN DOTS-138;So;0;L;;;;;N;;;;; 2886;BRAILLE PATTERN DOTS-238;So;0;L;;;;;N;;;;; 2887;BRAILLE PATTERN DOTS-1238;So;0;L;;;;;N;;;;; 2888;BRAILLE PATTERN DOTS-48;So;0;L;;;;;N;;;;; 2889;BRAILLE PATTERN DOTS-148;So;0;L;;;;;N;;;;; 288A;BRAILLE PATTERN DOTS-248;So;0;L;;;;;N;;;;; 288B;BRAILLE PATTERN DOTS-1248;So;0;L;;;;;N;;;;; 288C;BRAILLE PATTERN DOTS-348;So;0;L;;;;;N;;;;; 288D;BRAILLE PATTERN DOTS-1348;So;0;L;;;;;N;;;;; 288E;BRAILLE PATTERN DOTS-2348;So;0;L;;;;;N;;;;; 288F;BRAILLE PATTERN DOTS-12348;So;0;L;;;;;N;;;;; 2890;BRAILLE PATTERN DOTS-58;So;0;L;;;;;N;;;;; 2891;BRAILLE PATTERN DOTS-158;So;0;L;;;;;N;;;;; 2892;BRAILLE PATTERN DOTS-258;So;0;L;;;;;N;;;;; 2893;BRAILLE PATTERN DOTS-1258;So;0;L;;;;;N;;;;; 2894;BRAILLE PATTERN DOTS-358;So;0;L;;;;;N;;;;; 2895;BRAILLE PATTERN DOTS-1358;So;0;L;;;;;N;;;;; 2896;BRAILLE PATTERN DOTS-2358;So;0;L;;;;;N;;;;; 2897;BRAILLE PATTERN DOTS-12358;So;0;L;;;;;N;;;;; 2898;BRAILLE PATTERN DOTS-458;So;0;L;;;;;N;;;;; 2899;BRAILLE PATTERN DOTS-1458;So;0;L;;;;;N;;;;; 289A;BRAILLE PATTERN DOTS-2458;So;0;L;;;;;N;;;;; 289B;BRAILLE PATTERN DOTS-12458;So;0;L;;;;;N;;;;; 289C;BRAILLE PATTERN DOTS-3458;So;0;L;;;;;N;;;;; 289D;BRAILLE PATTERN DOTS-13458;So;0;L;;;;;N;;;;; 289E;BRAILLE PATTERN DOTS-23458;So;0;L;;;;;N;;;;; 289F;BRAILLE PATTERN DOTS-123458;So;0;L;;;;;N;;;;; 28A0;BRAILLE PATTERN DOTS-68;So;0;L;;;;;N;;;;; 28A1;BRAILLE PATTERN DOTS-168;So;0;L;;;;;N;;;;; 28A2;BRAILLE PATTERN DOTS-268;So;0;L;;;;;N;;;;; 28A3;BRAILLE PATTERN DOTS-1268;So;0;L;;;;;N;;;;; 28A4;BRAILLE PATTERN DOTS-368;So;0;L;;;;;N;;;;; 28A5;BRAILLE PATTERN DOTS-1368;So;0;L;;;;;N;;;;; 28A6;BRAILLE PATTERN DOTS-2368;So;0;L;;;;;N;;;;; 28A7;BRAILLE PATTERN DOTS-12368;So;0;L;;;;;N;;;;; 28A8;BRAILLE PATTERN DOTS-468;So;0;L;;;;;N;;;;; 28A9;BRAILLE PATTERN DOTS-1468;So;0;L;;;;;N;;;;; 28AA;BRAILLE PATTERN DOTS-2468;So;0;L;;;;;N;;;;; 28AB;BRAILLE PATTERN DOTS-12468;So;0;L;;;;;N;;;;; 28AC;BRAILLE PATTERN DOTS-3468;So;0;L;;;;;N;;;;; 28AD;BRAILLE PATTERN DOTS-13468;So;0;L;;;;;N;;;;; 28AE;BRAILLE PATTERN DOTS-23468;So;0;L;;;;;N;;;;; 28AF;BRAILLE PATTERN DOTS-123468;So;0;L;;;;;N;;;;; 28B0;BRAILLE PATTERN DOTS-568;So;0;L;;;;;N;;;;; 28B1;BRAILLE PATTERN DOTS-1568;So;0;L;;;;;N;;;;; 28B2;BRAILLE PATTERN DOTS-2568;So;0;L;;;;;N;;;;; 28B3;BRAILLE PATTERN DOTS-12568;So;0;L;;;;;N;;;;; 28B4;BRAILLE PATTERN DOTS-3568;So;0;L;;;;;N;;;;; 28B5;BRAILLE PATTERN DOTS-13568;So;0;L;;;;;N;;;;; 28B6;BRAILLE PATTERN DOTS-23568;So;0;L;;;;;N;;;;; 28B7;BRAILLE PATTERN DOTS-123568;So;0;L;;;;;N;;;;; 28B8;BRAILLE PATTERN DOTS-4568;So;0;L;;;;;N;;;;; 28B9;BRAILLE PATTERN DOTS-14568;So;0;L;;;;;N;;;;; 28BA;BRAILLE PATTERN DOTS-24568;So;0;L;;;;;N;;;;; 28BB;BRAILLE PATTERN DOTS-124568;So;0;L;;;;;N;;;;; 28BC;BRAILLE PATTERN DOTS-34568;So;0;L;;;;;N;;;;; 28BD;BRAILLE PATTERN DOTS-134568;So;0;L;;;;;N;;;;; 28BE;BRAILLE PATTERN DOTS-234568;So;0;L;;;;;N;;;;; 28BF;BRAILLE PATTERN DOTS-1234568;So;0;L;;;;;N;;;;; 28C0;BRAILLE PATTERN DOTS-78;So;0;L;;;;;N;;;;; 28C1;BRAILLE PATTERN DOTS-178;So;0;L;;;;;N;;;;; 28C2;BRAILLE PATTERN DOTS-278;So;0;L;;;;;N;;;;; 28C3;BRAILLE PATTERN DOTS-1278;So;0;L;;;;;N;;;;; 28C4;BRAILLE PATTERN DOTS-378;So;0;L;;;;;N;;;;; 28C5;BRAILLE PATTERN DOTS-1378;So;0;L;;;;;N;;;;; 28C6;BRAILLE PATTERN DOTS-2378;So;0;L;;;;;N;;;;; 28C7;BRAILLE PATTERN DOTS-12378;So;0;L;;;;;N;;;;; 28C8;BRAILLE PATTERN DOTS-478;So;0;L;;;;;N;;;;; 28C9;BRAILLE PATTERN DOTS-1478;So;0;L;;;;;N;;;;; 28CA;BRAILLE PATTERN DOTS-2478;So;0;L;;;;;N;;;;; 28CB;BRAILLE PATTERN DOTS-12478;So;0;L;;;;;N;;;;; 28CC;BRAILLE PATTERN DOTS-3478;So;0;L;;;;;N;;;;; 28CD;BRAILLE PATTERN DOTS-13478;So;0;L;;;;;N;;;;; 28CE;BRAILLE PATTERN DOTS-23478;So;0;L;;;;;N;;;;; 28CF;BRAILLE PATTERN DOTS-123478;So;0;L;;;;;N;;;;; 28D0;BRAILLE PATTERN DOTS-578;So;0;L;;;;;N;;;;; 28D1;BRAILLE PATTERN DOTS-1578;So;0;L;;;;;N;;;;; 28D2;BRAILLE PATTERN DOTS-2578;So;0;L;;;;;N;;;;; 28D3;BRAILLE PATTERN DOTS-12578;So;0;L;;;;;N;;;;; 28D4;BRAILLE PATTERN DOTS-3578;So;0;L;;;;;N;;;;; 28D5;BRAILLE PATTERN DOTS-13578;So;0;L;;;;;N;;;;; 28D6;BRAILLE PATTERN DOTS-23578;So;0;L;;;;;N;;;;; 28D7;BRAILLE PATTERN DOTS-123578;So;0;L;;;;;N;;;;; 28D8;BRAILLE PATTERN DOTS-4578;So;0;L;;;;;N;;;;; 28D9;BRAILLE PATTERN DOTS-14578;So;0;L;;;;;N;;;;; 28DA;BRAILLE PATTERN DOTS-24578;So;0;L;;;;;N;;;;; 28DB;BRAILLE PATTERN DOTS-124578;So;0;L;;;;;N;;;;; 28DC;BRAILLE PATTERN DOTS-34578;So;0;L;;;;;N;;;;; 28DD;BRAILLE PATTERN DOTS-134578;So;0;L;;;;;N;;;;; 28DE;BRAILLE PATTERN DOTS-234578;So;0;L;;;;;N;;;;; 28DF;BRAILLE PATTERN DOTS-1234578;So;0;L;;;;;N;;;;; 28E0;BRAILLE PATTERN DOTS-678;So;0;L;;;;;N;;;;; 28E1;BRAILLE PATTERN DOTS-1678;So;0;L;;;;;N;;;;; 28E2;BRAILLE PATTERN DOTS-2678;So;0;L;;;;;N;;;;; 28E3;BRAILLE PATTERN DOTS-12678;So;0;L;;;;;N;;;;; 28E4;BRAILLE PATTERN DOTS-3678;So;0;L;;;;;N;;;;; 28E5;BRAILLE PATTERN DOTS-13678;So;0;L;;;;;N;;;;; 28E6;BRAILLE PATTERN DOTS-23678;So;0;L;;;;;N;;;;; 28E7;BRAILLE PATTERN DOTS-123678;So;0;L;;;;;N;;;;; 28E8;BRAILLE PATTERN DOTS-4678;So;0;L;;;;;N;;;;; 28E9;BRAILLE PATTERN DOTS-14678;So;0;L;;;;;N;;;;; 28EA;BRAILLE PATTERN DOTS-24678;So;0;L;;;;;N;;;;; 28EB;BRAILLE PATTERN DOTS-124678;So;0;L;;;;;N;;;;; 28EC;BRAILLE PATTERN DOTS-34678;So;0;L;;;;;N;;;;; 28ED;BRAILLE PATTERN DOTS-134678;So;0;L;;;;;N;;;;; 28EE;BRAILLE PATTERN DOTS-234678;So;0;L;;;;;N;;;;; 28EF;BRAILLE PATTERN DOTS-1234678;So;0;L;;;;;N;;;;; 28F0;BRAILLE PATTERN DOTS-5678;So;0;L;;;;;N;;;;; 28F1;BRAILLE PATTERN DOTS-15678;So;0;L;;;;;N;;;;; 28F2;BRAILLE PATTERN DOTS-25678;So;0;L;;;;;N;;;;; 28F3;BRAILLE PATTERN DOTS-125678;So;0;L;;;;;N;;;;; 28F4;BRAILLE PATTERN DOTS-35678;So;0;L;;;;;N;;;;; 28F5;BRAILLE PATTERN DOTS-135678;So;0;L;;;;;N;;;;; 28F6;BRAILLE PATTERN DOTS-235678;So;0;L;;;;;N;;;;; 28F7;BRAILLE PATTERN DOTS-1235678;So;0;L;;;;;N;;;;; 28F8;BRAILLE PATTERN DOTS-45678;So;0;L;;;;;N;;;;; 28F9;BRAILLE PATTERN DOTS-145678;So;0;L;;;;;N;;;;; 28FA;BRAILLE PATTERN DOTS-245678;So;0;L;;;;;N;;;;; 28FB;BRAILLE PATTERN DOTS-1245678;So;0;L;;;;;N;;;;; 28FC;BRAILLE PATTERN DOTS-345678;So;0;L;;;;;N;;;;; 28FD;BRAILLE PATTERN DOTS-1345678;So;0;L;;;;;N;;;;; 28FE;BRAILLE PATTERN DOTS-2345678;So;0;L;;;;;N;;;;; 28FF;BRAILLE PATTERN DOTS-12345678;So;0;L;;;;;N;;;;; 2900;RIGHTWARDS TWO-HEADED ARROW WITH VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 2901;RIGHTWARDS TWO-HEADED ARROW WITH DOUBLE VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 2902;LEFTWARDS DOUBLE ARROW WITH VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 2903;RIGHTWARDS DOUBLE ARROW WITH VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 2904;LEFT RIGHT DOUBLE ARROW WITH VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 2905;RIGHTWARDS TWO-HEADED ARROW FROM BAR;Sm;0;ON;;;;;N;;;;; 2906;LEFTWARDS DOUBLE ARROW FROM BAR;Sm;0;ON;;;;;N;;;;; 2907;RIGHTWARDS DOUBLE ARROW FROM BAR;Sm;0;ON;;;;;N;;;;; 2908;DOWNWARDS ARROW WITH HORIZONTAL STROKE;Sm;0;ON;;;;;N;;;;; 2909;UPWARDS ARROW WITH HORIZONTAL STROKE;Sm;0;ON;;;;;N;;;;; 290A;UPWARDS TRIPLE ARROW;Sm;0;ON;;;;;N;;;;; 290B;DOWNWARDS TRIPLE ARROW;Sm;0;ON;;;;;N;;;;; 290C;LEFTWARDS DOUBLE DASH ARROW;Sm;0;ON;;;;;N;;;;; 290D;RIGHTWARDS DOUBLE DASH ARROW;Sm;0;ON;;;;;N;;;;; 290E;LEFTWARDS TRIPLE DASH ARROW;Sm;0;ON;;;;;N;;;;; 290F;RIGHTWARDS TRIPLE DASH ARROW;Sm;0;ON;;;;;N;;;;; 2910;RIGHTWARDS TWO-HEADED TRIPLE DASH ARROW;Sm;0;ON;;;;;N;;;;; 2911;RIGHTWARDS ARROW WITH DOTTED STEM;Sm;0;ON;;;;;N;;;;; 2912;UPWARDS ARROW TO BAR;Sm;0;ON;;;;;N;;;;; 2913;DOWNWARDS ARROW TO BAR;Sm;0;ON;;;;;N;;;;; 2914;RIGHTWARDS ARROW WITH TAIL WITH VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 2915;RIGHTWARDS ARROW WITH TAIL WITH DOUBLE VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 2916;RIGHTWARDS TWO-HEADED ARROW WITH TAIL;Sm;0;ON;;;;;N;;;;; 2917;RIGHTWARDS TWO-HEADED ARROW WITH TAIL WITH VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 2918;RIGHTWARDS TWO-HEADED ARROW WITH TAIL WITH DOUBLE VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 2919;LEFTWARDS ARROW-TAIL;Sm;0;ON;;;;;N;;;;; 291A;RIGHTWARDS ARROW-TAIL;Sm;0;ON;;;;;N;;;;; 291B;LEFTWARDS DOUBLE ARROW-TAIL;Sm;0;ON;;;;;N;;;;; 291C;RIGHTWARDS DOUBLE ARROW-TAIL;Sm;0;ON;;;;;N;;;;; 291D;LEFTWARDS ARROW TO BLACK DIAMOND;Sm;0;ON;;;;;N;;;;; 291E;RIGHTWARDS ARROW TO BLACK DIAMOND;Sm;0;ON;;;;;N;;;;; 291F;LEFTWARDS ARROW FROM BAR TO BLACK DIAMOND;Sm;0;ON;;;;;N;;;;; 2920;RIGHTWARDS ARROW FROM BAR TO BLACK DIAMOND;Sm;0;ON;;;;;N;;;;; 2921;NORTH WEST AND SOUTH EAST ARROW;Sm;0;ON;;;;;N;;;;; 2922;NORTH EAST AND SOUTH WEST ARROW;Sm;0;ON;;;;;N;;;;; 2923;NORTH WEST ARROW WITH HOOK;Sm;0;ON;;;;;N;;;;; 2924;NORTH EAST ARROW WITH HOOK;Sm;0;ON;;;;;N;;;;; 2925;SOUTH EAST ARROW WITH HOOK;Sm;0;ON;;;;;N;;;;; 2926;SOUTH WEST ARROW WITH HOOK;Sm;0;ON;;;;;N;;;;; 2927;NORTH WEST ARROW AND NORTH EAST ARROW;Sm;0;ON;;;;;N;;;;; 2928;NORTH EAST ARROW AND SOUTH EAST ARROW;Sm;0;ON;;;;;N;;;;; 2929;SOUTH EAST ARROW AND SOUTH WEST ARROW;Sm;0;ON;;;;;N;;;;; 292A;SOUTH WEST ARROW AND NORTH WEST ARROW;Sm;0;ON;;;;;N;;;;; 292B;RISING DIAGONAL CROSSING FALLING DIAGONAL;Sm;0;ON;;;;;N;;;;; 292C;FALLING DIAGONAL CROSSING RISING DIAGONAL;Sm;0;ON;;;;;N;;;;; 292D;SOUTH EAST ARROW CROSSING NORTH EAST ARROW;Sm;0;ON;;;;;N;;;;; 292E;NORTH EAST ARROW CROSSING SOUTH EAST ARROW;Sm;0;ON;;;;;N;;;;; 292F;FALLING DIAGONAL CROSSING NORTH EAST ARROW;Sm;0;ON;;;;;N;;;;; 2930;RISING DIAGONAL CROSSING SOUTH EAST ARROW;Sm;0;ON;;;;;N;;;;; 2931;NORTH EAST ARROW CROSSING NORTH WEST ARROW;Sm;0;ON;;;;;N;;;;; 2932;NORTH WEST ARROW CROSSING NORTH EAST ARROW;Sm;0;ON;;;;;N;;;;; 2933;WAVE ARROW POINTING DIRECTLY RIGHT;Sm;0;ON;;;;;N;;;;; 2934;ARROW POINTING RIGHTWARDS THEN CURVING UPWARDS;Sm;0;ON;;;;;N;;;;; 2935;ARROW POINTING RIGHTWARDS THEN CURVING DOWNWARDS;Sm;0;ON;;;;;N;;;;; 2936;ARROW POINTING DOWNWARDS THEN CURVING LEFTWARDS;Sm;0;ON;;;;;N;;;;; 2937;ARROW POINTING DOWNWARDS THEN CURVING RIGHTWARDS;Sm;0;ON;;;;;N;;;;; 2938;RIGHT-SIDE ARC CLOCKWISE ARROW;Sm;0;ON;;;;;N;;;;; 2939;LEFT-SIDE ARC ANTICLOCKWISE ARROW;Sm;0;ON;;;;;N;;;;; 293A;TOP ARC ANTICLOCKWISE ARROW;Sm;0;ON;;;;;N;;;;; 293B;BOTTOM ARC ANTICLOCKWISE ARROW;Sm;0;ON;;;;;N;;;;; 293C;TOP ARC CLOCKWISE ARROW WITH MINUS;Sm;0;ON;;;;;N;;;;; 293D;TOP ARC ANTICLOCKWISE ARROW WITH PLUS;Sm;0;ON;;;;;N;;;;; 293E;LOWER RIGHT SEMICIRCULAR CLOCKWISE ARROW;Sm;0;ON;;;;;N;;;;; 293F;LOWER LEFT SEMICIRCULAR ANTICLOCKWISE ARROW;Sm;0;ON;;;;;N;;;;; 2940;ANTICLOCKWISE CLOSED CIRCLE ARROW;Sm;0;ON;;;;;N;;;;; 2941;CLOCKWISE CLOSED CIRCLE ARROW;Sm;0;ON;;;;;N;;;;; 2942;RIGHTWARDS ARROW ABOVE SHORT LEFTWARDS ARROW;Sm;0;ON;;;;;N;;;;; 2943;LEFTWARDS ARROW ABOVE SHORT RIGHTWARDS ARROW;Sm;0;ON;;;;;N;;;;; 2944;SHORT RIGHTWARDS ARROW ABOVE LEFTWARDS ARROW;Sm;0;ON;;;;;N;;;;; 2945;RIGHTWARDS ARROW WITH PLUS BELOW;Sm;0;ON;;;;;N;;;;; 2946;LEFTWARDS ARROW WITH PLUS BELOW;Sm;0;ON;;;;;N;;;;; 2947;RIGHTWARDS ARROW THROUGH X;Sm;0;ON;;;;;N;;;;; 2948;LEFT RIGHT ARROW THROUGH SMALL CIRCLE;Sm;0;ON;;;;;N;;;;; 2949;UPWARDS TWO-HEADED ARROW FROM SMALL CIRCLE;Sm;0;ON;;;;;N;;;;; 294A;LEFT BARB UP RIGHT BARB DOWN HARPOON;Sm;0;ON;;;;;N;;;;; 294B;LEFT BARB DOWN RIGHT BARB UP HARPOON;Sm;0;ON;;;;;N;;;;; 294C;UP BARB RIGHT DOWN BARB LEFT HARPOON;Sm;0;ON;;;;;N;;;;; 294D;UP BARB LEFT DOWN BARB RIGHT HARPOON;Sm;0;ON;;;;;N;;;;; 294E;LEFT BARB UP RIGHT BARB UP HARPOON;Sm;0;ON;;;;;N;;;;; 294F;UP BARB RIGHT DOWN BARB RIGHT HARPOON;Sm;0;ON;;;;;N;;;;; 2950;LEFT BARB DOWN RIGHT BARB DOWN HARPOON;Sm;0;ON;;;;;N;;;;; 2951;UP BARB LEFT DOWN BARB LEFT HARPOON;Sm;0;ON;;;;;N;;;;; 2952;LEFTWARDS HARPOON WITH BARB UP TO BAR;Sm;0;ON;;;;;N;;;;; 2953;RIGHTWARDS HARPOON WITH BARB UP TO BAR;Sm;0;ON;;;;;N;;;;; 2954;UPWARDS HARPOON WITH BARB RIGHT TO BAR;Sm;0;ON;;;;;N;;;;; 2955;DOWNWARDS HARPOON WITH BARB RIGHT TO BAR;Sm;0;ON;;;;;N;;;;; 2956;LEFTWARDS HARPOON WITH BARB DOWN TO BAR;Sm;0;ON;;;;;N;;;;; 2957;RIGHTWARDS HARPOON WITH BARB DOWN TO BAR;Sm;0;ON;;;;;N;;;;; 2958;UPWARDS HARPOON WITH BARB LEFT TO BAR;Sm;0;ON;;;;;N;;;;; 2959;DOWNWARDS HARPOON WITH BARB LEFT TO BAR;Sm;0;ON;;;;;N;;;;; 295A;LEFTWARDS HARPOON WITH BARB UP FROM BAR;Sm;0;ON;;;;;N;;;;; 295B;RIGHTWARDS HARPOON WITH BARB UP FROM BAR;Sm;0;ON;;;;;N;;;;; 295C;UPWARDS HARPOON WITH BARB RIGHT FROM BAR;Sm;0;ON;;;;;N;;;;; 295D;DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR;Sm;0;ON;;;;;N;;;;; 295E;LEFTWARDS HARPOON WITH BARB DOWN FROM BAR;Sm;0;ON;;;;;N;;;;; 295F;RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR;Sm;0;ON;;;;;N;;;;; 2960;UPWARDS HARPOON WITH BARB LEFT FROM BAR;Sm;0;ON;;;;;N;;;;; 2961;DOWNWARDS HARPOON WITH BARB LEFT FROM BAR;Sm;0;ON;;;;;N;;;;; 2962;LEFTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB DOWN;Sm;0;ON;;;;;N;;;;; 2963;UPWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT;Sm;0;ON;;;;;N;;;;; 2964;RIGHTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB DOWN;Sm;0;ON;;;;;N;;;;; 2965;DOWNWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT;Sm;0;ON;;;;;N;;;;; 2966;LEFTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB UP;Sm;0;ON;;;;;N;;;;; 2967;LEFTWARDS HARPOON WITH BARB DOWN ABOVE RIGHTWARDS HARPOON WITH BARB DOWN;Sm;0;ON;;;;;N;;;;; 2968;RIGHTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB UP;Sm;0;ON;;;;;N;;;;; 2969;RIGHTWARDS HARPOON WITH BARB DOWN ABOVE LEFTWARDS HARPOON WITH BARB DOWN;Sm;0;ON;;;;;N;;;;; 296A;LEFTWARDS HARPOON WITH BARB UP ABOVE LONG DASH;Sm;0;ON;;;;;N;;;;; 296B;LEFTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH;Sm;0;ON;;;;;N;;;;; 296C;RIGHTWARDS HARPOON WITH BARB UP ABOVE LONG DASH;Sm;0;ON;;;;;N;;;;; 296D;RIGHTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH;Sm;0;ON;;;;;N;;;;; 296E;UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT;Sm;0;ON;;;;;N;;;;; 296F;DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT;Sm;0;ON;;;;;N;;;;; 2970;RIGHT DOUBLE ARROW WITH ROUNDED HEAD;Sm;0;ON;;;;;N;;;;; 2971;EQUALS SIGN ABOVE RIGHTWARDS ARROW;Sm;0;ON;;;;;N;;;;; 2972;TILDE OPERATOR ABOVE RIGHTWARDS ARROW;Sm;0;ON;;;;;N;;;;; 2973;LEFTWARDS ARROW ABOVE TILDE OPERATOR;Sm;0;ON;;;;;N;;;;; 2974;RIGHTWARDS ARROW ABOVE TILDE OPERATOR;Sm;0;ON;;;;;N;;;;; 2975;RIGHTWARDS ARROW ABOVE ALMOST EQUAL TO;Sm;0;ON;;;;;N;;;;; 2976;LESS-THAN ABOVE LEFTWARDS ARROW;Sm;0;ON;;;;;N;;;;; 2977;LEFTWARDS ARROW THROUGH LESS-THAN;Sm;0;ON;;;;;N;;;;; 2978;GREATER-THAN ABOVE RIGHTWARDS ARROW;Sm;0;ON;;;;;N;;;;; 2979;SUBSET ABOVE RIGHTWARDS ARROW;Sm;0;ON;;;;;N;;;;; 297A;LEFTWARDS ARROW THROUGH SUBSET;Sm;0;ON;;;;;N;;;;; 297B;SUPERSET ABOVE LEFTWARDS ARROW;Sm;0;ON;;;;;N;;;;; 297C;LEFT FISH TAIL;Sm;0;ON;;;;;N;;;;; 297D;RIGHT FISH TAIL;Sm;0;ON;;;;;N;;;;; 297E;UP FISH TAIL;Sm;0;ON;;;;;N;;;;; 297F;DOWN FISH TAIL;Sm;0;ON;;;;;N;;;;; 2980;TRIPLE VERTICAL BAR DELIMITER;Sm;0;ON;;;;;N;;;;; 2981;Z NOTATION SPOT;Sm;0;ON;;;;;N;;;;; 2982;Z NOTATION TYPE COLON;Sm;0;ON;;;;;N;;;;; 2983;LEFT WHITE CURLY BRACKET;Ps;0;ON;;;;;Y;;;;; 2984;RIGHT WHITE CURLY BRACKET;Pe;0;ON;;;;;Y;;;;; 2985;LEFT WHITE PARENTHESIS;Ps;0;ON;;;;;Y;;;;; 2986;RIGHT WHITE PARENTHESIS;Pe;0;ON;;;;;Y;;;;; 2987;Z NOTATION LEFT IMAGE BRACKET;Ps;0;ON;;;;;Y;;;;; 2988;Z NOTATION RIGHT IMAGE BRACKET;Pe;0;ON;;;;;Y;;;;; 2989;Z NOTATION LEFT BINDING BRACKET;Ps;0;ON;;;;;Y;;;;; 298A;Z NOTATION RIGHT BINDING BRACKET;Pe;0;ON;;;;;Y;;;;; 298B;LEFT SQUARE BRACKET WITH UNDERBAR;Ps;0;ON;;;;;Y;;;;; 298C;RIGHT SQUARE BRACKET WITH UNDERBAR;Pe;0;ON;;;;;Y;;;;; 298D;LEFT SQUARE BRACKET WITH TICK IN TOP CORNER;Ps;0;ON;;;;;Y;;;;; 298E;RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER;Pe;0;ON;;;;;Y;;;;; 298F;LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER;Ps;0;ON;;;;;Y;;;;; 2990;RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER;Pe;0;ON;;;;;Y;;;;; 2991;LEFT ANGLE BRACKET WITH DOT;Ps;0;ON;;;;;Y;;;;; 2992;RIGHT ANGLE BRACKET WITH DOT;Pe;0;ON;;;;;Y;;;;; 2993;LEFT ARC LESS-THAN BRACKET;Ps;0;ON;;;;;Y;;;;; 2994;RIGHT ARC GREATER-THAN BRACKET;Pe;0;ON;;;;;Y;;;;; 2995;DOUBLE LEFT ARC GREATER-THAN BRACKET;Ps;0;ON;;;;;Y;;;;; 2996;DOUBLE RIGHT ARC LESS-THAN BRACKET;Pe;0;ON;;;;;Y;;;;; 2997;LEFT BLACK TORTOISE SHELL BRACKET;Ps;0;ON;;;;;Y;;;;; 2998;RIGHT BLACK TORTOISE SHELL BRACKET;Pe;0;ON;;;;;Y;;;;; 2999;DOTTED FENCE;Sm;0;ON;;;;;N;;;;; 299A;VERTICAL ZIGZAG LINE;Sm;0;ON;;;;;N;;;;; 299B;MEASURED ANGLE OPENING LEFT;Sm;0;ON;;;;;Y;;;;; 299C;RIGHT ANGLE VARIANT WITH SQUARE;Sm;0;ON;;;;;Y;;;;; 299D;MEASURED RIGHT ANGLE WITH DOT;Sm;0;ON;;;;;Y;;;;; 299E;ANGLE WITH S INSIDE;Sm;0;ON;;;;;Y;;;;; 299F;ACUTE ANGLE;Sm;0;ON;;;;;Y;;;;; 29A0;SPHERICAL ANGLE OPENING LEFT;Sm;0;ON;;;;;Y;;;;; 29A1;SPHERICAL ANGLE OPENING UP;Sm;0;ON;;;;;Y;;;;; 29A2;TURNED ANGLE;Sm;0;ON;;;;;Y;;;;; 29A3;REVERSED ANGLE;Sm;0;ON;;;;;Y;;;;; 29A4;ANGLE WITH UNDERBAR;Sm;0;ON;;;;;Y;;;;; 29A5;REVERSED ANGLE WITH UNDERBAR;Sm;0;ON;;;;;Y;;;;; 29A6;OBLIQUE ANGLE OPENING UP;Sm;0;ON;;;;;Y;;;;; 29A7;OBLIQUE ANGLE OPENING DOWN;Sm;0;ON;;;;;Y;;;;; 29A8;MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND RIGHT;Sm;0;ON;;;;;Y;;;;; 29A9;MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND LEFT;Sm;0;ON;;;;;Y;;;;; 29AA;MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND RIGHT;Sm;0;ON;;;;;Y;;;;; 29AB;MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND LEFT;Sm;0;ON;;;;;Y;;;;; 29AC;MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND UP;Sm;0;ON;;;;;Y;;;;; 29AD;MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND UP;Sm;0;ON;;;;;Y;;;;; 29AE;MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND DOWN;Sm;0;ON;;;;;Y;;;;; 29AF;MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND DOWN;Sm;0;ON;;;;;Y;;;;; 29B0;REVERSED EMPTY SET;Sm;0;ON;;;;;N;;;;; 29B1;EMPTY SET WITH OVERBAR;Sm;0;ON;;;;;N;;;;; 29B2;EMPTY SET WITH SMALL CIRCLE ABOVE;Sm;0;ON;;;;;N;;;;; 29B3;EMPTY SET WITH RIGHT ARROW ABOVE;Sm;0;ON;;;;;N;;;;; 29B4;EMPTY SET WITH LEFT ARROW ABOVE;Sm;0;ON;;;;;N;;;;; 29B5;CIRCLE WITH HORIZONTAL BAR;Sm;0;ON;;;;;N;;;;; 29B6;CIRCLED VERTICAL BAR;Sm;0;ON;;;;;N;;;;; 29B7;CIRCLED PARALLEL;Sm;0;ON;;;;;N;;;;; 29B8;CIRCLED REVERSE SOLIDUS;Sm;0;ON;;;;;Y;;;;; 29B9;CIRCLED PERPENDICULAR;Sm;0;ON;;;;;N;;;;; 29BA;CIRCLE DIVIDED BY HORIZONTAL BAR AND TOP HALF DIVIDED BY VERTICAL BAR;Sm;0;ON;;;;;N;;;;; 29BB;CIRCLE WITH SUPERIMPOSED X;Sm;0;ON;;;;;N;;;;; 29BC;CIRCLED ANTICLOCKWISE-ROTATED DIVISION SIGN;Sm;0;ON;;;;;N;;;;; 29BD;UP ARROW THROUGH CIRCLE;Sm;0;ON;;;;;N;;;;; 29BE;CIRCLED WHITE BULLET;Sm;0;ON;;;;;N;;;;; 29BF;CIRCLED BULLET;Sm;0;ON;;;;;N;;;;; 29C0;CIRCLED LESS-THAN;Sm;0;ON;;;;;Y;;;;; 29C1;CIRCLED GREATER-THAN;Sm;0;ON;;;;;Y;;;;; 29C2;CIRCLE WITH SMALL CIRCLE TO THE RIGHT;Sm;0;ON;;;;;Y;;;;; 29C3;CIRCLE WITH TWO HORIZONTAL STROKES TO THE RIGHT;Sm;0;ON;;;;;Y;;;;; 29C4;SQUARED RISING DIAGONAL SLASH;Sm;0;ON;;;;;Y;;;;; 29C5;SQUARED FALLING DIAGONAL SLASH;Sm;0;ON;;;;;Y;;;;; 29C6;SQUARED ASTERISK;Sm;0;ON;;;;;N;;;;; 29C7;SQUARED SMALL CIRCLE;Sm;0;ON;;;;;N;;;;; 29C8;SQUARED SQUARE;Sm;0;ON;;;;;N;;;;; 29C9;TWO JOINED SQUARES;Sm;0;ON;;;;;Y;;;;; 29CA;TRIANGLE WITH DOT ABOVE;Sm;0;ON;;;;;N;;;;; 29CB;TRIANGLE WITH UNDERBAR;Sm;0;ON;;;;;N;;;;; 29CC;S IN TRIANGLE;Sm;0;ON;;;;;N;;;;; 29CD;TRIANGLE WITH SERIFS AT BOTTOM;Sm;0;ON;;;;;N;;;;; 29CE;RIGHT TRIANGLE ABOVE LEFT TRIANGLE;Sm;0;ON;;;;;Y;;;;; 29CF;LEFT TRIANGLE BESIDE VERTICAL BAR;Sm;0;ON;;;;;Y;;;;; 29D0;VERTICAL BAR BESIDE RIGHT TRIANGLE;Sm;0;ON;;;;;Y;;;;; 29D1;BOWTIE WITH LEFT HALF BLACK;Sm;0;ON;;;;;Y;;;;; 29D2;BOWTIE WITH RIGHT HALF BLACK;Sm;0;ON;;;;;Y;;;;; 29D3;BLACK BOWTIE;Sm;0;ON;;;;;N;;;;; 29D4;TIMES WITH LEFT HALF BLACK;Sm;0;ON;;;;;Y;;;;; 29D5;TIMES WITH RIGHT HALF BLACK;Sm;0;ON;;;;;Y;;;;; 29D6;WHITE HOURGLASS;Sm;0;ON;;;;;N;;;;; 29D7;BLACK HOURGLASS;Sm;0;ON;;;;;N;;;;; 29D8;LEFT WIGGLY FENCE;Ps;0;ON;;;;;Y;;;;; 29D9;RIGHT WIGGLY FENCE;Pe;0;ON;;;;;Y;;;;; 29DA;LEFT DOUBLE WIGGLY FENCE;Ps;0;ON;;;;;Y;;;;; 29DB;RIGHT DOUBLE WIGGLY FENCE;Pe;0;ON;;;;;Y;;;;; 29DC;INCOMPLETE INFINITY;Sm;0;ON;;;;;Y;;;;; 29DD;TIE OVER INFINITY;Sm;0;ON;;;;;N;;;;; 29DE;INFINITY NEGATED WITH VERTICAL BAR;Sm;0;ON;;;;;N;;;;; 29DF;DOUBLE-ENDED MULTIMAP;Sm;0;ON;;;;;N;;;;; 29E0;SQUARE WITH CONTOURED OUTLINE;Sm;0;ON;;;;;N;;;;; 29E1;INCREASES AS;Sm;0;ON;;;;;Y;;;;; 29E2;SHUFFLE PRODUCT;Sm;0;ON;;;;;N;;;;; 29E3;EQUALS SIGN AND SLANTED PARALLEL;Sm;0;ON;;;;;Y;;;;; 29E4;EQUALS SIGN AND SLANTED PARALLEL WITH TILDE ABOVE;Sm;0;ON;;;;;Y;;;;; 29E5;IDENTICAL TO AND SLANTED PARALLEL;Sm;0;ON;;;;;Y;;;;; 29E6;GLEICH STARK;Sm;0;ON;;;;;N;;;;; 29E7;THERMODYNAMIC;Sm;0;ON;;;;;N;;;;; 29E8;DOWN-POINTING TRIANGLE WITH LEFT HALF BLACK;Sm;0;ON;;;;;Y;;;;; 29E9;DOWN-POINTING TRIANGLE WITH RIGHT HALF BLACK;Sm;0;ON;;;;;Y;;;;; 29EA;BLACK DIAMOND WITH DOWN ARROW;Sm;0;ON;;;;;N;;;;; 29EB;BLACK LOZENGE;Sm;0;ON;;;;;N;;;;; 29EC;WHITE CIRCLE WITH DOWN ARROW;Sm;0;ON;;;;;N;;;;; 29ED;BLACK CIRCLE WITH DOWN ARROW;Sm;0;ON;;;;;N;;;;; 29EE;ERROR-BARRED WHITE SQUARE;Sm;0;ON;;;;;N;;;;; 29EF;ERROR-BARRED BLACK SQUARE;Sm;0;ON;;;;;N;;;;; 29F0;ERROR-BARRED WHITE DIAMOND;Sm;0;ON;;;;;N;;;;; 29F1;ERROR-BARRED BLACK DIAMOND;Sm;0;ON;;;;;N;;;;; 29F2;ERROR-BARRED WHITE CIRCLE;Sm;0;ON;;;;;N;;;;; 29F3;ERROR-BARRED BLACK CIRCLE;Sm;0;ON;;;;;N;;;;; 29F4;RULE-DELAYED;Sm;0;ON;;;;;Y;;;;; 29F5;REVERSE SOLIDUS OPERATOR;Sm;0;ON;;;;;Y;;;;; 29F6;SOLIDUS WITH OVERBAR;Sm;0;ON;;;;;Y;;;;; 29F7;REVERSE SOLIDUS WITH HORIZONTAL STROKE;Sm;0;ON;;;;;Y;;;;; 29F8;BIG SOLIDUS;Sm;0;ON;;;;;Y;;;;; 29F9;BIG REVERSE SOLIDUS;Sm;0;ON;;;;;Y;;;;; 29FA;DOUBLE PLUS;Sm;0;ON;;;;;N;;;;; 29FB;TRIPLE PLUS;Sm;0;ON;;;;;N;;;;; 29FC;LEFT-POINTING CURVED ANGLE BRACKET;Ps;0;ON;;;;;Y;;;;; 29FD;RIGHT-POINTING CURVED ANGLE BRACKET;Pe;0;ON;;;;;Y;;;;; 29FE;TINY;Sm;0;ON;;;;;N;;;;; 29FF;MINY;Sm;0;ON;;;;;N;;;;; 2A00;N-ARY CIRCLED DOT OPERATOR;Sm;0;ON;;;;;N;;;;; 2A01;N-ARY CIRCLED PLUS OPERATOR;Sm;0;ON;;;;;N;;;;; 2A02;N-ARY CIRCLED TIMES OPERATOR;Sm;0;ON;;;;;N;;;;; 2A03;N-ARY UNION OPERATOR WITH DOT;Sm;0;ON;;;;;N;;;;; 2A04;N-ARY UNION OPERATOR WITH PLUS;Sm;0;ON;;;;;N;;;;; 2A05;N-ARY SQUARE INTERSECTION OPERATOR;Sm;0;ON;;;;;N;;;;; 2A06;N-ARY SQUARE UNION OPERATOR;Sm;0;ON;;;;;N;;;;; 2A07;TWO LOGICAL AND OPERATOR;Sm;0;ON;;;;;N;;;;; 2A08;TWO LOGICAL OR OPERATOR;Sm;0;ON;;;;;N;;;;; 2A09;N-ARY TIMES OPERATOR;Sm;0;ON;;;;;N;;;;; 2A0A;MODULO TWO SUM;Sm;0;ON;;;;;Y;;;;; 2A0B;SUMMATION WITH INTEGRAL;Sm;0;ON;;;;;Y;;;;; 2A0C;QUADRUPLE INTEGRAL OPERATOR;Sm;0;ON; 222B 222B 222B 222B;;;;Y;;;;; 2A0D;FINITE PART INTEGRAL;Sm;0;ON;;;;;Y;;;;; 2A0E;INTEGRAL WITH DOUBLE STROKE;Sm;0;ON;;;;;Y;;;;; 2A0F;INTEGRAL AVERAGE WITH SLASH;Sm;0;ON;;;;;Y;;;;; 2A10;CIRCULATION FUNCTION;Sm;0;ON;;;;;Y;;;;; 2A11;ANTICLOCKWISE INTEGRATION;Sm;0;ON;;;;;Y;;;;; 2A12;LINE INTEGRATION WITH RECTANGULAR PATH AROUND POLE;Sm;0;ON;;;;;Y;;;;; 2A13;LINE INTEGRATION WITH SEMICIRCULAR PATH AROUND POLE;Sm;0;ON;;;;;Y;;;;; 2A14;LINE INTEGRATION NOT INCLUDING THE POLE;Sm;0;ON;;;;;Y;;;;; 2A15;INTEGRAL AROUND A POINT OPERATOR;Sm;0;ON;;;;;Y;;;;; 2A16;QUATERNION INTEGRAL OPERATOR;Sm;0;ON;;;;;Y;;;;; 2A17;INTEGRAL WITH LEFTWARDS ARROW WITH HOOK;Sm;0;ON;;;;;Y;;;;; 2A18;INTEGRAL WITH TIMES SIGN;Sm;0;ON;;;;;Y;;;;; 2A19;INTEGRAL WITH INTERSECTION;Sm;0;ON;;;;;Y;;;;; 2A1A;INTEGRAL WITH UNION;Sm;0;ON;;;;;Y;;;;; 2A1B;INTEGRAL WITH OVERBAR;Sm;0;ON;;;;;Y;;;;; 2A1C;INTEGRAL WITH UNDERBAR;Sm;0;ON;;;;;Y;;;;; 2A1D;JOIN;Sm;0;ON;;;;;N;;;;; 2A1E;LARGE LEFT TRIANGLE OPERATOR;Sm;0;ON;;;;;Y;;;;; 2A1F;Z NOTATION SCHEMA COMPOSITION;Sm;0;ON;;;;;Y;;;;; 2A20;Z NOTATION SCHEMA PIPING;Sm;0;ON;;;;;Y;;;;; 2A21;Z NOTATION SCHEMA PROJECTION;Sm;0;ON;;;;;Y;;;;; 2A22;PLUS SIGN WITH SMALL CIRCLE ABOVE;Sm;0;ON;;;;;N;;;;; 2A23;PLUS SIGN WITH CIRCUMFLEX ACCENT ABOVE;Sm;0;ON;;;;;N;;;;; 2A24;PLUS SIGN WITH TILDE ABOVE;Sm;0;ON;;;;;Y;;;;; 2A25;PLUS SIGN WITH DOT BELOW;Sm;0;ON;;;;;N;;;;; 2A26;PLUS SIGN WITH TILDE BELOW;Sm;0;ON;;;;;Y;;;;; 2A27;PLUS SIGN WITH SUBSCRIPT TWO;Sm;0;ON;;;;;N;;;;; 2A28;PLUS SIGN WITH BLACK TRIANGLE;Sm;0;ON;;;;;N;;;;; 2A29;MINUS SIGN WITH COMMA ABOVE;Sm;0;ON;;;;;Y;;;;; 2A2A;MINUS SIGN WITH DOT BELOW;Sm;0;ON;;;;;N;;;;; 2A2B;MINUS SIGN WITH FALLING DOTS;Sm;0;ON;;;;;Y;;;;; 2A2C;MINUS SIGN WITH RISING DOTS;Sm;0;ON;;;;;Y;;;;; 2A2D;PLUS SIGN IN LEFT HALF CIRCLE;Sm;0;ON;;;;;Y;;;;; 2A2E;PLUS SIGN IN RIGHT HALF CIRCLE;Sm;0;ON;;;;;Y;;;;; 2A2F;VECTOR OR CROSS PRODUCT;Sm;0;ON;;;;;N;;;;; 2A30;MULTIPLICATION SIGN WITH DOT ABOVE;Sm;0;ON;;;;;N;;;;; 2A31;MULTIPLICATION SIGN WITH UNDERBAR;Sm;0;ON;;;;;N;;;;; 2A32;SEMIDIRECT PRODUCT WITH BOTTOM CLOSED;Sm;0;ON;;;;;N;;;;; 2A33;SMASH PRODUCT;Sm;0;ON;;;;;N;;;;; 2A34;MULTIPLICATION SIGN IN LEFT HALF CIRCLE;Sm;0;ON;;;;;Y;;;;; 2A35;MULTIPLICATION SIGN IN RIGHT HALF CIRCLE;Sm;0;ON;;;;;Y;;;;; 2A36;CIRCLED MULTIPLICATION SIGN WITH CIRCUMFLEX ACCENT;Sm;0;ON;;;;;N;;;;; 2A37;MULTIPLICATION SIGN IN DOUBLE CIRCLE;Sm;0;ON;;;;;N;;;;; 2A38;CIRCLED DIVISION SIGN;Sm;0;ON;;;;;N;;;;; 2A39;PLUS SIGN IN TRIANGLE;Sm;0;ON;;;;;N;;;;; 2A3A;MINUS SIGN IN TRIANGLE;Sm;0;ON;;;;;N;;;;; 2A3B;MULTIPLICATION SIGN IN TRIANGLE;Sm;0;ON;;;;;N;;;;; 2A3C;INTERIOR PRODUCT;Sm;0;ON;;;;;Y;;;;; 2A3D;RIGHTHAND INTERIOR PRODUCT;Sm;0;ON;;;;;Y;;;;; 2A3E;Z NOTATION RELATIONAL COMPOSITION;Sm;0;ON;;;;;Y;;;;; 2A3F;AMALGAMATION OR COPRODUCT;Sm;0;ON;;;;;N;;;;; 2A40;INTERSECTION WITH DOT;Sm;0;ON;;;;;N;;;;; 2A41;UNION WITH MINUS SIGN;Sm;0;ON;;;;;N;;;;; 2A42;UNION WITH OVERBAR;Sm;0;ON;;;;;N;;;;; 2A43;INTERSECTION WITH OVERBAR;Sm;0;ON;;;;;N;;;;; 2A44;INTERSECTION WITH LOGICAL AND;Sm;0;ON;;;;;N;;;;; 2A45;UNION WITH LOGICAL OR;Sm;0;ON;;;;;N;;;;; 2A46;UNION ABOVE INTERSECTION;Sm;0;ON;;;;;N;;;;; 2A47;INTERSECTION ABOVE UNION;Sm;0;ON;;;;;N;;;;; 2A48;UNION ABOVE BAR ABOVE INTERSECTION;Sm;0;ON;;;;;N;;;;; 2A49;INTERSECTION ABOVE BAR ABOVE UNION;Sm;0;ON;;;;;N;;;;; 2A4A;UNION BESIDE AND JOINED WITH UNION;Sm;0;ON;;;;;N;;;;; 2A4B;INTERSECTION BESIDE AND JOINED WITH INTERSECTION;Sm;0;ON;;;;;N;;;;; 2A4C;CLOSED UNION WITH SERIFS;Sm;0;ON;;;;;N;;;;; 2A4D;CLOSED INTERSECTION WITH SERIFS;Sm;0;ON;;;;;N;;;;; 2A4E;DOUBLE SQUARE INTERSECTION;Sm;0;ON;;;;;N;;;;; 2A4F;DOUBLE SQUARE UNION;Sm;0;ON;;;;;N;;;;; 2A50;CLOSED UNION WITH SERIFS AND SMASH PRODUCT;Sm;0;ON;;;;;N;;;;; 2A51;LOGICAL AND WITH DOT ABOVE;Sm;0;ON;;;;;N;;;;; 2A52;LOGICAL OR WITH DOT ABOVE;Sm;0;ON;;;;;N;;;;; 2A53;DOUBLE LOGICAL AND;Sm;0;ON;;;;;N;;;;; 2A54;DOUBLE LOGICAL OR;Sm;0;ON;;;;;N;;;;; 2A55;TWO INTERSECTING LOGICAL AND;Sm;0;ON;;;;;N;;;;; 2A56;TWO INTERSECTING LOGICAL OR;Sm;0;ON;;;;;N;;;;; 2A57;SLOPING LARGE OR;Sm;0;ON;;;;;Y;;;;; 2A58;SLOPING LARGE AND;Sm;0;ON;;;;;Y;;;;; 2A59;LOGICAL OR OVERLAPPING LOGICAL AND;Sm;0;ON;;;;;N;;;;; 2A5A;LOGICAL AND WITH MIDDLE STEM;Sm;0;ON;;;;;N;;;;; 2A5B;LOGICAL OR WITH MIDDLE STEM;Sm;0;ON;;;;;N;;;;; 2A5C;LOGICAL AND WITH HORIZONTAL DASH;Sm;0;ON;;;;;N;;;;; 2A5D;LOGICAL OR WITH HORIZONTAL DASH;Sm;0;ON;;;;;N;;;;; 2A5E;LOGICAL AND WITH DOUBLE OVERBAR;Sm;0;ON;;;;;N;;;;; 2A5F;LOGICAL AND WITH UNDERBAR;Sm;0;ON;;;;;N;;;;; 2A60;LOGICAL AND WITH DOUBLE UNDERBAR;Sm;0;ON;;;;;N;;;;; 2A61;SMALL VEE WITH UNDERBAR;Sm;0;ON;;;;;N;;;;; 2A62;LOGICAL OR WITH DOUBLE OVERBAR;Sm;0;ON;;;;;N;;;;; 2A63;LOGICAL OR WITH DOUBLE UNDERBAR;Sm;0;ON;;;;;N;;;;; 2A64;Z NOTATION DOMAIN ANTIRESTRICTION;Sm;0;ON;;;;;Y;;;;; 2A65;Z NOTATION RANGE ANTIRESTRICTION;Sm;0;ON;;;;;Y;;;;; 2A66;EQUALS SIGN WITH DOT BELOW;Sm;0;ON;;;;;N;;;;; 2A67;IDENTICAL WITH DOT ABOVE;Sm;0;ON;;;;;N;;;;; 2A68;TRIPLE HORIZONTAL BAR WITH DOUBLE VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 2A69;TRIPLE HORIZONTAL BAR WITH TRIPLE VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 2A6A;TILDE OPERATOR WITH DOT ABOVE;Sm;0;ON;;;;;Y;;;;; 2A6B;TILDE OPERATOR WITH RISING DOTS;Sm;0;ON;;;;;Y;;;;; 2A6C;SIMILAR MINUS SIMILAR;Sm;0;ON;;;;;Y;;;;; 2A6D;CONGRUENT WITH DOT ABOVE;Sm;0;ON;;;;;Y;;;;; 2A6E;EQUALS WITH ASTERISK;Sm;0;ON;;;;;N;;;;; 2A6F;ALMOST EQUAL TO WITH CIRCUMFLEX ACCENT;Sm;0;ON;;;;;Y;;;;; 2A70;APPROXIMATELY EQUAL OR EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2A71;EQUALS SIGN ABOVE PLUS SIGN;Sm;0;ON;;;;;N;;;;; 2A72;PLUS SIGN ABOVE EQUALS SIGN;Sm;0;ON;;;;;N;;;;; 2A73;EQUALS SIGN ABOVE TILDE OPERATOR;Sm;0;ON;;;;;Y;;;;; 2A74;DOUBLE COLON EQUAL;Sm;0;ON; 003A 003A 003D;;;;Y;;;;; 2A75;TWO CONSECUTIVE EQUALS SIGNS;Sm;0;ON; 003D 003D;;;;N;;;;; 2A76;THREE CONSECUTIVE EQUALS SIGNS;Sm;0;ON; 003D 003D 003D;;;;N;;;;; 2A77;EQUALS SIGN WITH TWO DOTS ABOVE AND TWO DOTS BELOW;Sm;0;ON;;;;;N;;;;; 2A78;EQUIVALENT WITH FOUR DOTS ABOVE;Sm;0;ON;;;;;N;;;;; 2A79;LESS-THAN WITH CIRCLE INSIDE;Sm;0;ON;;;;;Y;;;;; 2A7A;GREATER-THAN WITH CIRCLE INSIDE;Sm;0;ON;;;;;Y;;;;; 2A7B;LESS-THAN WITH QUESTION MARK ABOVE;Sm;0;ON;;;;;Y;;;;; 2A7C;GREATER-THAN WITH QUESTION MARK ABOVE;Sm;0;ON;;;;;Y;;;;; 2A7D;LESS-THAN OR SLANTED EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2A7E;GREATER-THAN OR SLANTED EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2A7F;LESS-THAN OR SLANTED EQUAL TO WITH DOT INSIDE;Sm;0;ON;;;;;Y;;;;; 2A80;GREATER-THAN OR SLANTED EQUAL TO WITH DOT INSIDE;Sm;0;ON;;;;;Y;;;;; 2A81;LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE;Sm;0;ON;;;;;Y;;;;; 2A82;GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE;Sm;0;ON;;;;;Y;;;;; 2A83;LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE RIGHT;Sm;0;ON;;;;;Y;;;;; 2A84;GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE LEFT;Sm;0;ON;;;;;Y;;;;; 2A85;LESS-THAN OR APPROXIMATE;Sm;0;ON;;;;;Y;;;;; 2A86;GREATER-THAN OR APPROXIMATE;Sm;0;ON;;;;;Y;;;;; 2A87;LESS-THAN AND SINGLE-LINE NOT EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2A88;GREATER-THAN AND SINGLE-LINE NOT EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2A89;LESS-THAN AND NOT APPROXIMATE;Sm;0;ON;;;;;Y;;;;; 2A8A;GREATER-THAN AND NOT APPROXIMATE;Sm;0;ON;;;;;Y;;;;; 2A8B;LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN;Sm;0;ON;;;;;Y;;;;; 2A8C;GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN;Sm;0;ON;;;;;Y;;;;; 2A8D;LESS-THAN ABOVE SIMILAR OR EQUAL;Sm;0;ON;;;;;Y;;;;; 2A8E;GREATER-THAN ABOVE SIMILAR OR EQUAL;Sm;0;ON;;;;;Y;;;;; 2A8F;LESS-THAN ABOVE SIMILAR ABOVE GREATER-THAN;Sm;0;ON;;;;;Y;;;;; 2A90;GREATER-THAN ABOVE SIMILAR ABOVE LESS-THAN;Sm;0;ON;;;;;Y;;;;; 2A91;LESS-THAN ABOVE GREATER-THAN ABOVE DOUBLE-LINE EQUAL;Sm;0;ON;;;;;Y;;;;; 2A92;GREATER-THAN ABOVE LESS-THAN ABOVE DOUBLE-LINE EQUAL;Sm;0;ON;;;;;Y;;;;; 2A93;LESS-THAN ABOVE SLANTED EQUAL ABOVE GREATER-THAN ABOVE SLANTED EQUAL;Sm;0;ON;;;;;Y;;;;; 2A94;GREATER-THAN ABOVE SLANTED EQUAL ABOVE LESS-THAN ABOVE SLANTED EQUAL;Sm;0;ON;;;;;Y;;;;; 2A95;SLANTED EQUAL TO OR LESS-THAN;Sm;0;ON;;;;;Y;;;;; 2A96;SLANTED EQUAL TO OR GREATER-THAN;Sm;0;ON;;;;;Y;;;;; 2A97;SLANTED EQUAL TO OR LESS-THAN WITH DOT INSIDE;Sm;0;ON;;;;;Y;;;;; 2A98;SLANTED EQUAL TO OR GREATER-THAN WITH DOT INSIDE;Sm;0;ON;;;;;Y;;;;; 2A99;DOUBLE-LINE EQUAL TO OR LESS-THAN;Sm;0;ON;;;;;Y;;;;; 2A9A;DOUBLE-LINE EQUAL TO OR GREATER-THAN;Sm;0;ON;;;;;Y;;;;; 2A9B;DOUBLE-LINE SLANTED EQUAL TO OR LESS-THAN;Sm;0;ON;;;;;Y;;;;; 2A9C;DOUBLE-LINE SLANTED EQUAL TO OR GREATER-THAN;Sm;0;ON;;;;;Y;;;;; 2A9D;SIMILAR OR LESS-THAN;Sm;0;ON;;;;;Y;;;;; 2A9E;SIMILAR OR GREATER-THAN;Sm;0;ON;;;;;Y;;;;; 2A9F;SIMILAR ABOVE LESS-THAN ABOVE EQUALS SIGN;Sm;0;ON;;;;;Y;;;;; 2AA0;SIMILAR ABOVE GREATER-THAN ABOVE EQUALS SIGN;Sm;0;ON;;;;;Y;;;;; 2AA1;DOUBLE NESTED LESS-THAN;Sm;0;ON;;;;;Y;;;;; 2AA2;DOUBLE NESTED GREATER-THAN;Sm;0;ON;;;;;Y;;;;; 2AA3;DOUBLE NESTED LESS-THAN WITH UNDERBAR;Sm;0;ON;;;;;Y;;;;; 2AA4;GREATER-THAN OVERLAPPING LESS-THAN;Sm;0;ON;;;;;N;;;;; 2AA5;GREATER-THAN BESIDE LESS-THAN;Sm;0;ON;;;;;N;;;;; 2AA6;LESS-THAN CLOSED BY CURVE;Sm;0;ON;;;;;Y;;;;; 2AA7;GREATER-THAN CLOSED BY CURVE;Sm;0;ON;;;;;Y;;;;; 2AA8;LESS-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL;Sm;0;ON;;;;;Y;;;;; 2AA9;GREATER-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL;Sm;0;ON;;;;;Y;;;;; 2AAA;SMALLER THAN;Sm;0;ON;;;;;Y;;;;; 2AAB;LARGER THAN;Sm;0;ON;;;;;Y;;;;; 2AAC;SMALLER THAN OR EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2AAD;LARGER THAN OR EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2AAE;EQUALS SIGN WITH BUMPY ABOVE;Sm;0;ON;;;;;N;;;;; 2AAF;PRECEDES ABOVE SINGLE-LINE EQUALS SIGN;Sm;0;ON;;;;;Y;;;;; 2AB0;SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN;Sm;0;ON;;;;;Y;;;;; 2AB1;PRECEDES ABOVE SINGLE-LINE NOT EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2AB2;SUCCEEDS ABOVE SINGLE-LINE NOT EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2AB3;PRECEDES ABOVE EQUALS SIGN;Sm;0;ON;;;;;Y;;;;; 2AB4;SUCCEEDS ABOVE EQUALS SIGN;Sm;0;ON;;;;;Y;;;;; 2AB5;PRECEDES ABOVE NOT EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2AB6;SUCCEEDS ABOVE NOT EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2AB7;PRECEDES ABOVE ALMOST EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2AB8;SUCCEEDS ABOVE ALMOST EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2AB9;PRECEDES ABOVE NOT ALMOST EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2ABA;SUCCEEDS ABOVE NOT ALMOST EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2ABB;DOUBLE PRECEDES;Sm;0;ON;;;;;Y;;;;; 2ABC;DOUBLE SUCCEEDS;Sm;0;ON;;;;;Y;;;;; 2ABD;SUBSET WITH DOT;Sm;0;ON;;;;;Y;;;;; 2ABE;SUPERSET WITH DOT;Sm;0;ON;;;;;Y;;;;; 2ABF;SUBSET WITH PLUS SIGN BELOW;Sm;0;ON;;;;;Y;;;;; 2AC0;SUPERSET WITH PLUS SIGN BELOW;Sm;0;ON;;;;;Y;;;;; 2AC1;SUBSET WITH MULTIPLICATION SIGN BELOW;Sm;0;ON;;;;;Y;;;;; 2AC2;SUPERSET WITH MULTIPLICATION SIGN BELOW;Sm;0;ON;;;;;Y;;;;; 2AC3;SUBSET OF OR EQUAL TO WITH DOT ABOVE;Sm;0;ON;;;;;Y;;;;; 2AC4;SUPERSET OF OR EQUAL TO WITH DOT ABOVE;Sm;0;ON;;;;;Y;;;;; 2AC5;SUBSET OF ABOVE EQUALS SIGN;Sm;0;ON;;;;;Y;;;;; 2AC6;SUPERSET OF ABOVE EQUALS SIGN;Sm;0;ON;;;;;Y;;;;; 2AC7;SUBSET OF ABOVE TILDE OPERATOR;Sm;0;ON;;;;;Y;;;;; 2AC8;SUPERSET OF ABOVE TILDE OPERATOR;Sm;0;ON;;;;;Y;;;;; 2AC9;SUBSET OF ABOVE ALMOST EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2ACA;SUPERSET OF ABOVE ALMOST EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2ACB;SUBSET OF ABOVE NOT EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2ACC;SUPERSET OF ABOVE NOT EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2ACD;SQUARE LEFT OPEN BOX OPERATOR;Sm;0;ON;;;;;Y;;;;; 2ACE;SQUARE RIGHT OPEN BOX OPERATOR;Sm;0;ON;;;;;Y;;;;; 2ACF;CLOSED SUBSET;Sm;0;ON;;;;;Y;;;;; 2AD0;CLOSED SUPERSET;Sm;0;ON;;;;;Y;;;;; 2AD1;CLOSED SUBSET OR EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2AD2;CLOSED SUPERSET OR EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2AD3;SUBSET ABOVE SUPERSET;Sm;0;ON;;;;;Y;;;;; 2AD4;SUPERSET ABOVE SUBSET;Sm;0;ON;;;;;Y;;;;; 2AD5;SUBSET ABOVE SUBSET;Sm;0;ON;;;;;Y;;;;; 2AD6;SUPERSET ABOVE SUPERSET;Sm;0;ON;;;;;Y;;;;; 2AD7;SUPERSET BESIDE SUBSET;Sm;0;ON;;;;;N;;;;; 2AD8;SUPERSET BESIDE AND JOINED BY DASH WITH SUBSET;Sm;0;ON;;;;;N;;;;; 2AD9;ELEMENT OF OPENING DOWNWARDS;Sm;0;ON;;;;;N;;;;; 2ADA;PITCHFORK WITH TEE TOP;Sm;0;ON;;;;;N;;;;; 2ADB;TRANSVERSAL INTERSECTION;Sm;0;ON;;;;;N;;;;; 2ADC;FORKING;Sm;0;ON;2ADD 0338;;;;Y;;;;; 2ADD;NONFORKING;Sm;0;ON;;;;;N;;;;; 2ADE;SHORT LEFT TACK;Sm;0;ON;;;;;Y;;;;; 2ADF;SHORT DOWN TACK;Sm;0;ON;;;;;N;;;;; 2AE0;SHORT UP TACK;Sm;0;ON;;;;;N;;;;; 2AE1;PERPENDICULAR WITH S;Sm;0;ON;;;;;N;;;;; 2AE2;VERTICAL BAR TRIPLE RIGHT TURNSTILE;Sm;0;ON;;;;;Y;;;;; 2AE3;DOUBLE VERTICAL BAR LEFT TURNSTILE;Sm;0;ON;;;;;Y;;;;; 2AE4;VERTICAL BAR DOUBLE LEFT TURNSTILE;Sm;0;ON;;;;;Y;;;;; 2AE5;DOUBLE VERTICAL BAR DOUBLE LEFT TURNSTILE;Sm;0;ON;;;;;Y;;;;; 2AE6;LONG DASH FROM LEFT MEMBER OF DOUBLE VERTICAL;Sm;0;ON;;;;;Y;;;;; 2AE7;SHORT DOWN TACK WITH OVERBAR;Sm;0;ON;;;;;N;;;;; 2AE8;SHORT UP TACK WITH UNDERBAR;Sm;0;ON;;;;;N;;;;; 2AE9;SHORT UP TACK ABOVE SHORT DOWN TACK;Sm;0;ON;;;;;N;;;;; 2AEA;DOUBLE DOWN TACK;Sm;0;ON;;;;;N;;;;; 2AEB;DOUBLE UP TACK;Sm;0;ON;;;;;N;;;;; 2AEC;DOUBLE STROKE NOT SIGN;Sm;0;ON;;;;;Y;;;;; 2AED;REVERSED DOUBLE STROKE NOT SIGN;Sm;0;ON;;;;;Y;;;;; 2AEE;DOES NOT DIVIDE WITH REVERSED NEGATION SLASH;Sm;0;ON;;;;;Y;;;;; 2AEF;VERTICAL LINE WITH CIRCLE ABOVE;Sm;0;ON;;;;;N;;;;; 2AF0;VERTICAL LINE WITH CIRCLE BELOW;Sm;0;ON;;;;;N;;;;; 2AF1;DOWN TACK WITH CIRCLE BELOW;Sm;0;ON;;;;;N;;;;; 2AF2;PARALLEL WITH HORIZONTAL STROKE;Sm;0;ON;;;;;N;;;;; 2AF3;PARALLEL WITH TILDE OPERATOR;Sm;0;ON;;;;;Y;;;;; 2AF4;TRIPLE VERTICAL BAR BINARY RELATION;Sm;0;ON;;;;;N;;;;; 2AF5;TRIPLE VERTICAL BAR WITH HORIZONTAL STROKE;Sm;0;ON;;;;;N;;;;; 2AF6;TRIPLE COLON OPERATOR;Sm;0;ON;;;;;N;;;;; 2AF7;TRIPLE NESTED LESS-THAN;Sm;0;ON;;;;;Y;;;;; 2AF8;TRIPLE NESTED GREATER-THAN;Sm;0;ON;;;;;Y;;;;; 2AF9;DOUBLE-LINE SLANTED LESS-THAN OR EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2AFA;DOUBLE-LINE SLANTED GREATER-THAN OR EQUAL TO;Sm;0;ON;;;;;Y;;;;; 2AFB;TRIPLE SOLIDUS BINARY RELATION;Sm;0;ON;;;;;Y;;;;; 2AFC;LARGE TRIPLE VERTICAL BAR OPERATOR;Sm;0;ON;;;;;N;;;;; 2AFD;DOUBLE SOLIDUS OPERATOR;Sm;0;ON;;;;;Y;;;;; 2AFE;WHITE VERTICAL BAR;Sm;0;ON;;;;;N;;;;; 2AFF;N-ARY WHITE VERTICAL BAR;Sm;0;ON;;;;;N;;;;; 2B00;NORTH EAST WHITE ARROW;So;0;ON;;;;;N;;;;; 2B01;NORTH WEST WHITE ARROW;So;0;ON;;;;;N;;;;; 2B02;SOUTH EAST WHITE ARROW;So;0;ON;;;;;N;;;;; 2B03;SOUTH WEST WHITE ARROW;So;0;ON;;;;;N;;;;; 2B04;LEFT RIGHT WHITE ARROW;So;0;ON;;;;;N;;;;; 2B05;LEFTWARDS BLACK ARROW;So;0;ON;;;;;N;;;;; 2B06;UPWARDS BLACK ARROW;So;0;ON;;;;;N;;;;; 2B07;DOWNWARDS BLACK ARROW;So;0;ON;;;;;N;;;;; 2B08;NORTH EAST BLACK ARROW;So;0;ON;;;;;N;;;;; 2B09;NORTH WEST BLACK ARROW;So;0;ON;;;;;N;;;;; 2B0A;SOUTH EAST BLACK ARROW;So;0;ON;;;;;N;;;;; 2B0B;SOUTH WEST BLACK ARROW;So;0;ON;;;;;N;;;;; 2B0C;LEFT RIGHT BLACK ARROW;So;0;ON;;;;;N;;;;; 2B0D;UP DOWN BLACK ARROW;So;0;ON;;;;;N;;;;; 2B0E;RIGHTWARDS ARROW WITH TIP DOWNWARDS;So;0;ON;;;;;N;;;;; 2B0F;RIGHTWARDS ARROW WITH TIP UPWARDS;So;0;ON;;;;;N;;;;; 2B10;LEFTWARDS ARROW WITH TIP DOWNWARDS;So;0;ON;;;;;N;;;;; 2B11;LEFTWARDS ARROW WITH TIP UPWARDS;So;0;ON;;;;;N;;;;; 2B12;SQUARE WITH TOP HALF BLACK;So;0;ON;;;;;N;;;;; 2B13;SQUARE WITH BOTTOM HALF BLACK;So;0;ON;;;;;N;;;;; 2B14;SQUARE WITH UPPER RIGHT DIAGONAL HALF BLACK;So;0;ON;;;;;N;;;;; 2B15;SQUARE WITH LOWER LEFT DIAGONAL HALF BLACK;So;0;ON;;;;;N;;;;; 2B16;DIAMOND WITH LEFT HALF BLACK;So;0;ON;;;;;N;;;;; 2B17;DIAMOND WITH RIGHT HALF BLACK;So;0;ON;;;;;N;;;;; 2B18;DIAMOND WITH TOP HALF BLACK;So;0;ON;;;;;N;;;;; 2B19;DIAMOND WITH BOTTOM HALF BLACK;So;0;ON;;;;;N;;;;; 2B1A;DOTTED SQUARE;So;0;ON;;;;;N;;;;; 2B1B;BLACK LARGE SQUARE;So;0;ON;;;;;N;;;;; 2B1C;WHITE LARGE SQUARE;So;0;ON;;;;;N;;;;; 2B1D;BLACK VERY SMALL SQUARE;So;0;ON;;;;;N;;;;; 2B1E;WHITE VERY SMALL SQUARE;So;0;ON;;;;;N;;;;; 2B1F;BLACK PENTAGON;So;0;ON;;;;;N;;;;; 2B20;WHITE PENTAGON;So;0;ON;;;;;N;;;;; 2B21;WHITE HEXAGON;So;0;ON;;;;;N;;;;; 2B22;BLACK HEXAGON;So;0;ON;;;;;N;;;;; 2B23;HORIZONTAL BLACK HEXAGON;So;0;ON;;;;;N;;;;; 2B24;BLACK LARGE CIRCLE;So;0;ON;;;;;N;;;;; 2B25;BLACK MEDIUM DIAMOND;So;0;ON;;;;;N;;;;; 2B26;WHITE MEDIUM DIAMOND;So;0;ON;;;;;N;;;;; 2B27;BLACK MEDIUM LOZENGE;So;0;ON;;;;;N;;;;; 2B28;WHITE MEDIUM LOZENGE;So;0;ON;;;;;N;;;;; 2B29;BLACK SMALL DIAMOND;So;0;ON;;;;;N;;;;; 2B2A;BLACK SMALL LOZENGE;So;0;ON;;;;;N;;;;; 2B2B;WHITE SMALL LOZENGE;So;0;ON;;;;;N;;;;; 2B2C;BLACK HORIZONTAL ELLIPSE;So;0;ON;;;;;N;;;;; 2B2D;WHITE HORIZONTAL ELLIPSE;So;0;ON;;;;;N;;;;; 2B2E;BLACK VERTICAL ELLIPSE;So;0;ON;;;;;N;;;;; 2B2F;WHITE VERTICAL ELLIPSE;So;0;ON;;;;;N;;;;; 2B30;LEFT ARROW WITH SMALL CIRCLE;Sm;0;ON;;;;;N;;;;; 2B31;THREE LEFTWARDS ARROWS;Sm;0;ON;;;;;N;;;;; 2B32;LEFT ARROW WITH CIRCLED PLUS;Sm;0;ON;;;;;N;;;;; 2B33;LONG LEFTWARDS SQUIGGLE ARROW;Sm;0;ON;;;;;N;;;;; 2B34;LEFTWARDS TWO-HEADED ARROW WITH VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 2B35;LEFTWARDS TWO-HEADED ARROW WITH DOUBLE VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 2B36;LEFTWARDS TWO-HEADED ARROW FROM BAR;Sm;0;ON;;;;;N;;;;; 2B37;LEFTWARDS TWO-HEADED TRIPLE DASH ARROW;Sm;0;ON;;;;;N;;;;; 2B38;LEFTWARDS ARROW WITH DOTTED STEM;Sm;0;ON;;;;;N;;;;; 2B39;LEFTWARDS ARROW WITH TAIL WITH VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 2B3A;LEFTWARDS ARROW WITH TAIL WITH DOUBLE VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 2B3B;LEFTWARDS TWO-HEADED ARROW WITH TAIL;Sm;0;ON;;;;;N;;;;; 2B3C;LEFTWARDS TWO-HEADED ARROW WITH TAIL WITH VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 2B3D;LEFTWARDS TWO-HEADED ARROW WITH TAIL WITH DOUBLE VERTICAL STROKE;Sm;0;ON;;;;;N;;;;; 2B3E;LEFTWARDS ARROW THROUGH X;Sm;0;ON;;;;;N;;;;; 2B3F;WAVE ARROW POINTING DIRECTLY LEFT;Sm;0;ON;;;;;N;;;;; 2B40;EQUALS SIGN ABOVE LEFTWARDS ARROW;Sm;0;ON;;;;;N;;;;; 2B41;REVERSE TILDE OPERATOR ABOVE LEFTWARDS ARROW;Sm;0;ON;;;;;N;;;;; 2B42;LEFTWARDS ARROW ABOVE REVERSE ALMOST EQUAL TO;Sm;0;ON;;;;;N;;;;; 2B43;RIGHTWARDS ARROW THROUGH GREATER-THAN;Sm;0;ON;;;;;N;;;;; 2B44;RIGHTWARDS ARROW THROUGH SUPERSET;Sm;0;ON;;;;;N;;;;; 2B45;LEFTWARDS QUADRUPLE ARROW;So;0;ON;;;;;N;;;;; 2B46;RIGHTWARDS QUADRUPLE ARROW;So;0;ON;;;;;N;;;;; 2B47;REVERSE TILDE OPERATOR ABOVE RIGHTWARDS ARROW;Sm;0;ON;;;;;N;;;;; 2B48;RIGHTWARDS ARROW ABOVE REVERSE ALMOST EQUAL TO;Sm;0;ON;;;;;N;;;;; 2B49;TILDE OPERATOR ABOVE LEFTWARDS ARROW;Sm;0;ON;;;;;N;;;;; 2B4A;LEFTWARDS ARROW ABOVE ALMOST EQUAL TO;Sm;0;ON;;;;;N;;;;; 2B4B;LEFTWARDS ARROW ABOVE REVERSE TILDE OPERATOR;Sm;0;ON;;;;;N;;;;; 2B4C;RIGHTWARDS ARROW ABOVE REVERSE TILDE OPERATOR;Sm;0;ON;;;;;N;;;;; 2B4D;DOWNWARDS TRIANGLE-HEADED ZIGZAG ARROW;So;0;ON;;;;;N;;;;; 2B4E;SHORT SLANTED NORTH ARROW;So;0;ON;;;;;N;;;;; 2B4F;SHORT BACKSLANTED SOUTH ARROW;So;0;ON;;;;;N;;;;; 2B50;WHITE MEDIUM STAR;So;0;ON;;;;;N;;;;; 2B51;BLACK SMALL STAR;So;0;ON;;;;;N;;;;; 2B52;WHITE SMALL STAR;So;0;ON;;;;;N;;;;; 2B53;BLACK RIGHT-POINTING PENTAGON;So;0;ON;;;;;N;;;;; 2B54;WHITE RIGHT-POINTING PENTAGON;So;0;ON;;;;;N;;;;; 2B55;HEAVY LARGE CIRCLE;So;0;ON;;;;;N;;;;; 2B56;HEAVY OVAL WITH OVAL INSIDE;So;0;ON;;;;;N;;;;; 2B57;HEAVY CIRCLE WITH CIRCLE INSIDE;So;0;ON;;;;;N;;;;; 2B58;HEAVY CIRCLE;So;0;ON;;;;;N;;;;; 2B59;HEAVY CIRCLED SALTIRE;So;0;ON;;;;;N;;;;; 2B5A;SLANTED NORTH ARROW WITH HOOKED HEAD;So;0;ON;;;;;N;;;;; 2B5B;BACKSLANTED SOUTH ARROW WITH HOOKED TAIL;So;0;ON;;;;;N;;;;; 2B5C;SLANTED NORTH ARROW WITH HORIZONTAL TAIL;So;0;ON;;;;;N;;;;; 2B5D;BACKSLANTED SOUTH ARROW WITH HORIZONTAL TAIL;So;0;ON;;;;;N;;;;; 2B5E;BENT ARROW POINTING DOWNWARDS THEN NORTH EAST;So;0;ON;;;;;N;;;;; 2B5F;SHORT BENT ARROW POINTING DOWNWARDS THEN NORTH EAST;So;0;ON;;;;;N;;;;; 2B60;LEFTWARDS TRIANGLE-HEADED ARROW;So;0;ON;;;;;N;;;;; 2B61;UPWARDS TRIANGLE-HEADED ARROW;So;0;ON;;;;;N;;;;; 2B62;RIGHTWARDS TRIANGLE-HEADED ARROW;So;0;ON;;;;;N;;;;; 2B63;DOWNWARDS TRIANGLE-HEADED ARROW;So;0;ON;;;;;N;;;;; 2B64;LEFT RIGHT TRIANGLE-HEADED ARROW;So;0;ON;;;;;N;;;;; 2B65;UP DOWN TRIANGLE-HEADED ARROW;So;0;ON;;;;;N;;;;; 2B66;NORTH WEST TRIANGLE-HEADED ARROW;So;0;ON;;;;;N;;;;; 2B67;NORTH EAST TRIANGLE-HEADED ARROW;So;0;ON;;;;;N;;;;; 2B68;SOUTH EAST TRIANGLE-HEADED ARROW;So;0;ON;;;;;N;;;;; 2B69;SOUTH WEST TRIANGLE-HEADED ARROW;So;0;ON;;;;;N;;;;; 2B6A;LEFTWARDS TRIANGLE-HEADED DASHED ARROW;So;0;ON;;;;;N;;;;; 2B6B;UPWARDS TRIANGLE-HEADED DASHED ARROW;So;0;ON;;;;;N;;;;; 2B6C;RIGHTWARDS TRIANGLE-HEADED DASHED ARROW;So;0;ON;;;;;N;;;;; 2B6D;DOWNWARDS TRIANGLE-HEADED DASHED ARROW;So;0;ON;;;;;N;;;;; 2B6E;CLOCKWISE TRIANGLE-HEADED OPEN CIRCLE ARROW;So;0;ON;;;;;N;;;;; 2B6F;ANTICLOCKWISE TRIANGLE-HEADED OPEN CIRCLE ARROW;So;0;ON;;;;;N;;;;; 2B70;LEFTWARDS TRIANGLE-HEADED ARROW TO BAR;So;0;ON;;;;;N;;;;; 2B71;UPWARDS TRIANGLE-HEADED ARROW TO BAR;So;0;ON;;;;;N;;;;; 2B72;RIGHTWARDS TRIANGLE-HEADED ARROW TO BAR;So;0;ON;;;;;N;;;;; 2B73;DOWNWARDS TRIANGLE-HEADED ARROW TO BAR;So;0;ON;;;;;N;;;;; 2B76;NORTH WEST TRIANGLE-HEADED ARROW TO BAR;So;0;ON;;;;;N;;;;; 2B77;NORTH EAST TRIANGLE-HEADED ARROW TO BAR;So;0;ON;;;;;N;;;;; 2B78;SOUTH EAST TRIANGLE-HEADED ARROW TO BAR;So;0;ON;;;;;N;;;;; 2B79;SOUTH WEST TRIANGLE-HEADED ARROW TO BAR;So;0;ON;;;;;N;;;;; 2B7A;LEFTWARDS TRIANGLE-HEADED ARROW WITH DOUBLE HORIZONTAL STROKE;So;0;ON;;;;;N;;;;; 2B7B;UPWARDS TRIANGLE-HEADED ARROW WITH DOUBLE HORIZONTAL STROKE;So;0;ON;;;;;N;;;;; 2B7C;RIGHTWARDS TRIANGLE-HEADED ARROW WITH DOUBLE HORIZONTAL STROKE;So;0;ON;;;;;N;;;;; 2B7D;DOWNWARDS TRIANGLE-HEADED ARROW WITH DOUBLE HORIZONTAL STROKE;So;0;ON;;;;;N;;;;; 2B7E;HORIZONTAL TAB KEY;So;0;ON;;;;;N;;;;; 2B7F;VERTICAL TAB KEY;So;0;ON;;;;;N;;;;; 2B80;LEFTWARDS TRIANGLE-HEADED ARROW OVER RIGHTWARDS TRIANGLE-HEADED ARROW;So;0;ON;;;;;N;;;;; 2B81;UPWARDS TRIANGLE-HEADED ARROW LEFTWARDS OF DOWNWARDS TRIANGLE-HEADED ARROW;So;0;ON;;;;;N;;;;; 2B82;RIGHTWARDS TRIANGLE-HEADED ARROW OVER LEFTWARDS TRIANGLE-HEADED ARROW;So;0;ON;;;;;N;;;;; 2B83;DOWNWARDS TRIANGLE-HEADED ARROW LEFTWARDS OF UPWARDS TRIANGLE-HEADED ARROW;So;0;ON;;;;;N;;;;; 2B84;LEFTWARDS TRIANGLE-HEADED PAIRED ARROWS;So;0;ON;;;;;N;;;;; 2B85;UPWARDS TRIANGLE-HEADED PAIRED ARROWS;So;0;ON;;;;;N;;;;; 2B86;RIGHTWARDS TRIANGLE-HEADED PAIRED ARROWS;So;0;ON;;;;;N;;;;; 2B87;DOWNWARDS TRIANGLE-HEADED PAIRED ARROWS;So;0;ON;;;;;N;;;;; 2B88;LEFTWARDS BLACK CIRCLED WHITE ARROW;So;0;ON;;;;;N;;;;; 2B89;UPWARDS BLACK CIRCLED WHITE ARROW;So;0;ON;;;;;N;;;;; 2B8A;RIGHTWARDS BLACK CIRCLED WHITE ARROW;So;0;ON;;;;;N;;;;; 2B8B;DOWNWARDS BLACK CIRCLED WHITE ARROW;So;0;ON;;;;;N;;;;; 2B8C;ANTICLOCKWISE TRIANGLE-HEADED RIGHT U-SHAPED ARROW;So;0;ON;;;;;N;;;;; 2B8D;ANTICLOCKWISE TRIANGLE-HEADED BOTTOM U-SHAPED ARROW;So;0;ON;;;;;N;;;;; 2B8E;ANTICLOCKWISE TRIANGLE-HEADED LEFT U-SHAPED ARROW;So;0;ON;;;;;N;;;;; 2B8F;ANTICLOCKWISE TRIANGLE-HEADED TOP U-SHAPED ARROW;So;0;ON;;;;;N;;;;; 2B90;RETURN LEFT;So;0;ON;;;;;N;;;;; 2B91;RETURN RIGHT;So;0;ON;;;;;N;;;;; 2B92;NEWLINE LEFT;So;0;ON;;;;;N;;;;; 2B93;NEWLINE RIGHT;So;0;ON;;;;;N;;;;; 2B94;FOUR CORNER ARROWS CIRCLING ANTICLOCKWISE;So;0;ON;;;;;N;;;;; 2B95;RIGHTWARDS BLACK ARROW;So;0;ON;;;;;N;;;;; 2B98;THREE-D TOP-LIGHTED LEFTWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 2B99;THREE-D RIGHT-LIGHTED UPWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 2B9A;THREE-D TOP-LIGHTED RIGHTWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 2B9B;THREE-D LEFT-LIGHTED DOWNWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 2B9C;BLACK LEFTWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 2B9D;BLACK UPWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 2B9E;BLACK RIGHTWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 2B9F;BLACK DOWNWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 2BA0;DOWNWARDS TRIANGLE-HEADED ARROW WITH LONG TIP LEFTWARDS;So;0;ON;;;;;N;;;;; 2BA1;DOWNWARDS TRIANGLE-HEADED ARROW WITH LONG TIP RIGHTWARDS;So;0;ON;;;;;N;;;;; 2BA2;UPWARDS TRIANGLE-HEADED ARROW WITH LONG TIP LEFTWARDS;So;0;ON;;;;;N;;;;; 2BA3;UPWARDS TRIANGLE-HEADED ARROW WITH LONG TIP RIGHTWARDS;So;0;ON;;;;;N;;;;; 2BA4;LEFTWARDS TRIANGLE-HEADED ARROW WITH LONG TIP UPWARDS;So;0;ON;;;;;N;;;;; 2BA5;RIGHTWARDS TRIANGLE-HEADED ARROW WITH LONG TIP UPWARDS;So;0;ON;;;;;N;;;;; 2BA6;LEFTWARDS TRIANGLE-HEADED ARROW WITH LONG TIP DOWNWARDS;So;0;ON;;;;;N;;;;; 2BA7;RIGHTWARDS TRIANGLE-HEADED ARROW WITH LONG TIP DOWNWARDS;So;0;ON;;;;;N;;;;; 2BA8;BLACK CURVED DOWNWARDS AND LEFTWARDS ARROW;So;0;ON;;;;;N;;;;; 2BA9;BLACK CURVED DOWNWARDS AND RIGHTWARDS ARROW;So;0;ON;;;;;N;;;;; 2BAA;BLACK CURVED UPWARDS AND LEFTWARDS ARROW;So;0;ON;;;;;N;;;;; 2BAB;BLACK CURVED UPWARDS AND RIGHTWARDS ARROW;So;0;ON;;;;;N;;;;; 2BAC;BLACK CURVED LEFTWARDS AND UPWARDS ARROW;So;0;ON;;;;;N;;;;; 2BAD;BLACK CURVED RIGHTWARDS AND UPWARDS ARROW;So;0;ON;;;;;N;;;;; 2BAE;BLACK CURVED LEFTWARDS AND DOWNWARDS ARROW;So;0;ON;;;;;N;;;;; 2BAF;BLACK CURVED RIGHTWARDS AND DOWNWARDS ARROW;So;0;ON;;;;;N;;;;; 2BB0;RIBBON ARROW DOWN LEFT;So;0;ON;;;;;N;;;;; 2BB1;RIBBON ARROW DOWN RIGHT;So;0;ON;;;;;N;;;;; 2BB2;RIBBON ARROW UP LEFT;So;0;ON;;;;;N;;;;; 2BB3;RIBBON ARROW UP RIGHT;So;0;ON;;;;;N;;;;; 2BB4;RIBBON ARROW LEFT UP;So;0;ON;;;;;N;;;;; 2BB5;RIBBON ARROW RIGHT UP;So;0;ON;;;;;N;;;;; 2BB6;RIBBON ARROW LEFT DOWN;So;0;ON;;;;;N;;;;; 2BB7;RIBBON ARROW RIGHT DOWN;So;0;ON;;;;;N;;;;; 2BB8;UPWARDS WHITE ARROW FROM BAR WITH HORIZONTAL BAR;So;0;ON;;;;;N;;;;; 2BB9;UP ARROWHEAD IN A RECTANGLE BOX;So;0;ON;;;;;N;;;;; 2BBD;BALLOT BOX WITH LIGHT X;So;0;ON;;;;;N;;;;; 2BBE;CIRCLED X;So;0;ON;;;;;N;;;;; 2BBF;CIRCLED BOLD X;So;0;ON;;;;;N;;;;; 2BC0;BLACK SQUARE CENTRED;So;0;ON;;;;;N;;;;; 2BC1;BLACK DIAMOND CENTRED;So;0;ON;;;;;N;;;;; 2BC2;TURNED BLACK PENTAGON;So;0;ON;;;;;N;;;;; 2BC3;HORIZONTAL BLACK OCTAGON;So;0;ON;;;;;N;;;;; 2BC4;BLACK OCTAGON;So;0;ON;;;;;N;;;;; 2BC5;BLACK MEDIUM UP-POINTING TRIANGLE CENTRED;So;0;ON;;;;;N;;;;; 2BC6;BLACK MEDIUM DOWN-POINTING TRIANGLE CENTRED;So;0;ON;;;;;N;;;;; 2BC7;BLACK MEDIUM LEFT-POINTING TRIANGLE CENTRED;So;0;ON;;;;;N;;;;; 2BC8;BLACK MEDIUM RIGHT-POINTING TRIANGLE CENTRED;So;0;ON;;;;;N;;;;; 2BCA;TOP HALF BLACK CIRCLE;So;0;ON;;;;;N;;;;; 2BCB;BOTTOM HALF BLACK CIRCLE;So;0;ON;;;;;N;;;;; 2BCC;LIGHT FOUR POINTED BLACK CUSP;So;0;ON;;;;;N;;;;; 2BCD;ROTATED LIGHT FOUR POINTED BLACK CUSP;So;0;ON;;;;;N;;;;; 2BCE;WHITE FOUR POINTED CUSP;So;0;ON;;;;;N;;;;; 2BCF;ROTATED WHITE FOUR POINTED CUSP;So;0;ON;;;;;N;;;;; 2BD0;SQUARE POSITION INDICATOR;So;0;ON;;;;;N;;;;; 2BD1;UNCERTAINTY SIGN;So;0;ON;;;;;N;;;;; 2BEC;LEFTWARDS TWO-HEADED ARROW WITH TRIANGLE ARROWHEADS;So;0;ON;;;;;N;;;;; 2BED;UPWARDS TWO-HEADED ARROW WITH TRIANGLE ARROWHEADS;So;0;ON;;;;;N;;;;; 2BEE;RIGHTWARDS TWO-HEADED ARROW WITH TRIANGLE ARROWHEADS;So;0;ON;;;;;N;;;;; 2BEF;DOWNWARDS TWO-HEADED ARROW WITH TRIANGLE ARROWHEADS;So;0;ON;;;;;N;;;;; 2C00;GLAGOLITIC CAPITAL LETTER AZU;Lu;0;L;;;;;N;;;;2C30; 2C01;GLAGOLITIC CAPITAL LETTER BUKY;Lu;0;L;;;;;N;;;;2C31; 2C02;GLAGOLITIC CAPITAL LETTER VEDE;Lu;0;L;;;;;N;;;;2C32; 2C03;GLAGOLITIC CAPITAL LETTER GLAGOLI;Lu;0;L;;;;;N;;;;2C33; 2C04;GLAGOLITIC CAPITAL LETTER DOBRO;Lu;0;L;;;;;N;;;;2C34; 2C05;GLAGOLITIC CAPITAL LETTER YESTU;Lu;0;L;;;;;N;;;;2C35; 2C06;GLAGOLITIC CAPITAL LETTER ZHIVETE;Lu;0;L;;;;;N;;;;2C36; 2C07;GLAGOLITIC CAPITAL LETTER DZELO;Lu;0;L;;;;;N;;;;2C37; 2C08;GLAGOLITIC CAPITAL LETTER ZEMLJA;Lu;0;L;;;;;N;;;;2C38; 2C09;GLAGOLITIC CAPITAL LETTER IZHE;Lu;0;L;;;;;N;;;;2C39; 2C0A;GLAGOLITIC CAPITAL LETTER INITIAL IZHE;Lu;0;L;;;;;N;;;;2C3A; 2C0B;GLAGOLITIC CAPITAL LETTER I;Lu;0;L;;;;;N;;;;2C3B; 2C0C;GLAGOLITIC CAPITAL LETTER DJERVI;Lu;0;L;;;;;N;;;;2C3C; 2C0D;GLAGOLITIC CAPITAL LETTER KAKO;Lu;0;L;;;;;N;;;;2C3D; 2C0E;GLAGOLITIC CAPITAL LETTER LJUDIJE;Lu;0;L;;;;;N;;;;2C3E; 2C0F;GLAGOLITIC CAPITAL LETTER MYSLITE;Lu;0;L;;;;;N;;;;2C3F; 2C10;GLAGOLITIC CAPITAL LETTER NASHI;Lu;0;L;;;;;N;;;;2C40; 2C11;GLAGOLITIC CAPITAL LETTER ONU;Lu;0;L;;;;;N;;;;2C41; 2C12;GLAGOLITIC CAPITAL LETTER POKOJI;Lu;0;L;;;;;N;;;;2C42; 2C13;GLAGOLITIC CAPITAL LETTER RITSI;Lu;0;L;;;;;N;;;;2C43; 2C14;GLAGOLITIC CAPITAL LETTER SLOVO;Lu;0;L;;;;;N;;;;2C44; 2C15;GLAGOLITIC CAPITAL LETTER TVRIDO;Lu;0;L;;;;;N;;;;2C45; 2C16;GLAGOLITIC CAPITAL LETTER UKU;Lu;0;L;;;;;N;;;;2C46; 2C17;GLAGOLITIC CAPITAL LETTER FRITU;Lu;0;L;;;;;N;;;;2C47; 2C18;GLAGOLITIC CAPITAL LETTER HERU;Lu;0;L;;;;;N;;;;2C48; 2C19;GLAGOLITIC CAPITAL LETTER OTU;Lu;0;L;;;;;N;;;;2C49; 2C1A;GLAGOLITIC CAPITAL LETTER PE;Lu;0;L;;;;;N;;;;2C4A; 2C1B;GLAGOLITIC CAPITAL LETTER SHTA;Lu;0;L;;;;;N;;;;2C4B; 2C1C;GLAGOLITIC CAPITAL LETTER TSI;Lu;0;L;;;;;N;;;;2C4C; 2C1D;GLAGOLITIC CAPITAL LETTER CHRIVI;Lu;0;L;;;;;N;;;;2C4D; 2C1E;GLAGOLITIC CAPITAL LETTER SHA;Lu;0;L;;;;;N;;;;2C4E; 2C1F;GLAGOLITIC CAPITAL LETTER YERU;Lu;0;L;;;;;N;;;;2C4F; 2C20;GLAGOLITIC CAPITAL LETTER YERI;Lu;0;L;;;;;N;;;;2C50; 2C21;GLAGOLITIC CAPITAL LETTER YATI;Lu;0;L;;;;;N;;;;2C51; 2C22;GLAGOLITIC CAPITAL LETTER SPIDERY HA;Lu;0;L;;;;;N;;;;2C52; 2C23;GLAGOLITIC CAPITAL LETTER YU;Lu;0;L;;;;;N;;;;2C53; 2C24;GLAGOLITIC CAPITAL LETTER SMALL YUS;Lu;0;L;;;;;N;;;;2C54; 2C25;GLAGOLITIC CAPITAL LETTER SMALL YUS WITH TAIL;Lu;0;L;;;;;N;;;;2C55; 2C26;GLAGOLITIC CAPITAL LETTER YO;Lu;0;L;;;;;N;;;;2C56; 2C27;GLAGOLITIC CAPITAL LETTER IOTATED SMALL YUS;Lu;0;L;;;;;N;;;;2C57; 2C28;GLAGOLITIC CAPITAL LETTER BIG YUS;Lu;0;L;;;;;N;;;;2C58; 2C29;GLAGOLITIC CAPITAL LETTER IOTATED BIG YUS;Lu;0;L;;;;;N;;;;2C59; 2C2A;GLAGOLITIC CAPITAL LETTER FITA;Lu;0;L;;;;;N;;;;2C5A; 2C2B;GLAGOLITIC CAPITAL LETTER IZHITSA;Lu;0;L;;;;;N;;;;2C5B; 2C2C;GLAGOLITIC CAPITAL LETTER SHTAPIC;Lu;0;L;;;;;N;;;;2C5C; 2C2D;GLAGOLITIC CAPITAL LETTER TROKUTASTI A;Lu;0;L;;;;;N;;;;2C5D; 2C2E;GLAGOLITIC CAPITAL LETTER LATINATE MYSLITE;Lu;0;L;;;;;N;;;;2C5E; 2C30;GLAGOLITIC SMALL LETTER AZU;Ll;0;L;;;;;N;;;2C00;;2C00 2C31;GLAGOLITIC SMALL LETTER BUKY;Ll;0;L;;;;;N;;;2C01;;2C01 2C32;GLAGOLITIC SMALL LETTER VEDE;Ll;0;L;;;;;N;;;2C02;;2C02 2C33;GLAGOLITIC SMALL LETTER GLAGOLI;Ll;0;L;;;;;N;;;2C03;;2C03 2C34;GLAGOLITIC SMALL LETTER DOBRO;Ll;0;L;;;;;N;;;2C04;;2C04 2C35;GLAGOLITIC SMALL LETTER YESTU;Ll;0;L;;;;;N;;;2C05;;2C05 2C36;GLAGOLITIC SMALL LETTER ZHIVETE;Ll;0;L;;;;;N;;;2C06;;2C06 2C37;GLAGOLITIC SMALL LETTER DZELO;Ll;0;L;;;;;N;;;2C07;;2C07 2C38;GLAGOLITIC SMALL LETTER ZEMLJA;Ll;0;L;;;;;N;;;2C08;;2C08 2C39;GLAGOLITIC SMALL LETTER IZHE;Ll;0;L;;;;;N;;;2C09;;2C09 2C3A;GLAGOLITIC SMALL LETTER INITIAL IZHE;Ll;0;L;;;;;N;;;2C0A;;2C0A 2C3B;GLAGOLITIC SMALL LETTER I;Ll;0;L;;;;;N;;;2C0B;;2C0B 2C3C;GLAGOLITIC SMALL LETTER DJERVI;Ll;0;L;;;;;N;;;2C0C;;2C0C 2C3D;GLAGOLITIC SMALL LETTER KAKO;Ll;0;L;;;;;N;;;2C0D;;2C0D 2C3E;GLAGOLITIC SMALL LETTER LJUDIJE;Ll;0;L;;;;;N;;;2C0E;;2C0E 2C3F;GLAGOLITIC SMALL LETTER MYSLITE;Ll;0;L;;;;;N;;;2C0F;;2C0F 2C40;GLAGOLITIC SMALL LETTER NASHI;Ll;0;L;;;;;N;;;2C10;;2C10 2C41;GLAGOLITIC SMALL LETTER ONU;Ll;0;L;;;;;N;;;2C11;;2C11 2C42;GLAGOLITIC SMALL LETTER POKOJI;Ll;0;L;;;;;N;;;2C12;;2C12 2C43;GLAGOLITIC SMALL LETTER RITSI;Ll;0;L;;;;;N;;;2C13;;2C13 2C44;GLAGOLITIC SMALL LETTER SLOVO;Ll;0;L;;;;;N;;;2C14;;2C14 2C45;GLAGOLITIC SMALL LETTER TVRIDO;Ll;0;L;;;;;N;;;2C15;;2C15 2C46;GLAGOLITIC SMALL LETTER UKU;Ll;0;L;;;;;N;;;2C16;;2C16 2C47;GLAGOLITIC SMALL LETTER FRITU;Ll;0;L;;;;;N;;;2C17;;2C17 2C48;GLAGOLITIC SMALL LETTER HERU;Ll;0;L;;;;;N;;;2C18;;2C18 2C49;GLAGOLITIC SMALL LETTER OTU;Ll;0;L;;;;;N;;;2C19;;2C19 2C4A;GLAGOLITIC SMALL LETTER PE;Ll;0;L;;;;;N;;;2C1A;;2C1A 2C4B;GLAGOLITIC SMALL LETTER SHTA;Ll;0;L;;;;;N;;;2C1B;;2C1B 2C4C;GLAGOLITIC SMALL LETTER TSI;Ll;0;L;;;;;N;;;2C1C;;2C1C 2C4D;GLAGOLITIC SMALL LETTER CHRIVI;Ll;0;L;;;;;N;;;2C1D;;2C1D 2C4E;GLAGOLITIC SMALL LETTER SHA;Ll;0;L;;;;;N;;;2C1E;;2C1E 2C4F;GLAGOLITIC SMALL LETTER YERU;Ll;0;L;;;;;N;;;2C1F;;2C1F 2C50;GLAGOLITIC SMALL LETTER YERI;Ll;0;L;;;;;N;;;2C20;;2C20 2C51;GLAGOLITIC SMALL LETTER YATI;Ll;0;L;;;;;N;;;2C21;;2C21 2C52;GLAGOLITIC SMALL LETTER SPIDERY HA;Ll;0;L;;;;;N;;;2C22;;2C22 2C53;GLAGOLITIC SMALL LETTER YU;Ll;0;L;;;;;N;;;2C23;;2C23 2C54;GLAGOLITIC SMALL LETTER SMALL YUS;Ll;0;L;;;;;N;;;2C24;;2C24 2C55;GLAGOLITIC SMALL LETTER SMALL YUS WITH TAIL;Ll;0;L;;;;;N;;;2C25;;2C25 2C56;GLAGOLITIC SMALL LETTER YO;Ll;0;L;;;;;N;;;2C26;;2C26 2C57;GLAGOLITIC SMALL LETTER IOTATED SMALL YUS;Ll;0;L;;;;;N;;;2C27;;2C27 2C58;GLAGOLITIC SMALL LETTER BIG YUS;Ll;0;L;;;;;N;;;2C28;;2C28 2C59;GLAGOLITIC SMALL LETTER IOTATED BIG YUS;Ll;0;L;;;;;N;;;2C29;;2C29 2C5A;GLAGOLITIC SMALL LETTER FITA;Ll;0;L;;;;;N;;;2C2A;;2C2A 2C5B;GLAGOLITIC SMALL LETTER IZHITSA;Ll;0;L;;;;;N;;;2C2B;;2C2B 2C5C;GLAGOLITIC SMALL LETTER SHTAPIC;Ll;0;L;;;;;N;;;2C2C;;2C2C 2C5D;GLAGOLITIC SMALL LETTER TROKUTASTI A;Ll;0;L;;;;;N;;;2C2D;;2C2D 2C5E;GLAGOLITIC SMALL LETTER LATINATE MYSLITE;Ll;0;L;;;;;N;;;2C2E;;2C2E 2C60;LATIN CAPITAL LETTER L WITH DOUBLE BAR;Lu;0;L;;;;;N;;;;2C61; 2C61;LATIN SMALL LETTER L WITH DOUBLE BAR;Ll;0;L;;;;;N;;;2C60;;2C60 2C62;LATIN CAPITAL LETTER L WITH MIDDLE TILDE;Lu;0;L;;;;;N;;;;026B; 2C63;LATIN CAPITAL LETTER P WITH STROKE;Lu;0;L;;;;;N;;;;1D7D; 2C64;LATIN CAPITAL LETTER R WITH TAIL;Lu;0;L;;;;;N;;;;027D; 2C65;LATIN SMALL LETTER A WITH STROKE;Ll;0;L;;;;;N;;;023A;;023A 2C66;LATIN SMALL LETTER T WITH DIAGONAL STROKE;Ll;0;L;;;;;N;;;023E;;023E 2C67;LATIN CAPITAL LETTER H WITH DESCENDER;Lu;0;L;;;;;N;;;;2C68; 2C68;LATIN SMALL LETTER H WITH DESCENDER;Ll;0;L;;;;;N;;;2C67;;2C67 2C69;LATIN CAPITAL LETTER K WITH DESCENDER;Lu;0;L;;;;;N;;;;2C6A; 2C6A;LATIN SMALL LETTER K WITH DESCENDER;Ll;0;L;;;;;N;;;2C69;;2C69 2C6B;LATIN CAPITAL LETTER Z WITH DESCENDER;Lu;0;L;;;;;N;;;;2C6C; 2C6C;LATIN SMALL LETTER Z WITH DESCENDER;Ll;0;L;;;;;N;;;2C6B;;2C6B 2C6D;LATIN CAPITAL LETTER ALPHA;Lu;0;L;;;;;N;;;;0251; 2C6E;LATIN CAPITAL LETTER M WITH HOOK;Lu;0;L;;;;;N;;;;0271; 2C6F;LATIN CAPITAL LETTER TURNED A;Lu;0;L;;;;;N;;;;0250; 2C70;LATIN CAPITAL LETTER TURNED ALPHA;Lu;0;L;;;;;N;;;;0252; 2C71;LATIN SMALL LETTER V WITH RIGHT HOOK;Ll;0;L;;;;;N;;;;; 2C72;LATIN CAPITAL LETTER W WITH HOOK;Lu;0;L;;;;;N;;;;2C73; 2C73;LATIN SMALL LETTER W WITH HOOK;Ll;0;L;;;;;N;;;2C72;;2C72 2C74;LATIN SMALL LETTER V WITH CURL;Ll;0;L;;;;;N;;;;; 2C75;LATIN CAPITAL LETTER HALF H;Lu;0;L;;;;;N;;;;2C76; 2C76;LATIN SMALL LETTER HALF H;Ll;0;L;;;;;N;;;2C75;;2C75 2C77;LATIN SMALL LETTER TAILLESS PHI;Ll;0;L;;;;;N;;;;; 2C78;LATIN SMALL LETTER E WITH NOTCH;Ll;0;L;;;;;N;;;;; 2C79;LATIN SMALL LETTER TURNED R WITH TAIL;Ll;0;L;;;;;N;;;;; 2C7A;LATIN SMALL LETTER O WITH LOW RING INSIDE;Ll;0;L;;;;;N;;;;; 2C7B;LATIN LETTER SMALL CAPITAL TURNED E;Ll;0;L;;;;;N;;;;; 2C7C;LATIN SUBSCRIPT SMALL LETTER J;Lm;0;L; 006A;;;;N;;;;; 2C7D;MODIFIER LETTER CAPITAL V;Lm;0;L; 0056;;;;N;;;;; 2C7E;LATIN CAPITAL LETTER S WITH SWASH TAIL;Lu;0;L;;;;;N;;;;023F; 2C7F;LATIN CAPITAL LETTER Z WITH SWASH TAIL;Lu;0;L;;;;;N;;;;0240; 2C80;COPTIC CAPITAL LETTER ALFA;Lu;0;L;;;;;N;;;;2C81; 2C81;COPTIC SMALL LETTER ALFA;Ll;0;L;;;;;N;;;2C80;;2C80 2C82;COPTIC CAPITAL LETTER VIDA;Lu;0;L;;;;;N;;;;2C83; 2C83;COPTIC SMALL LETTER VIDA;Ll;0;L;;;;;N;;;2C82;;2C82 2C84;COPTIC CAPITAL LETTER GAMMA;Lu;0;L;;;;;N;;;;2C85; 2C85;COPTIC SMALL LETTER GAMMA;Ll;0;L;;;;;N;;;2C84;;2C84 2C86;COPTIC CAPITAL LETTER DALDA;Lu;0;L;;;;;N;;;;2C87; 2C87;COPTIC SMALL LETTER DALDA;Ll;0;L;;;;;N;;;2C86;;2C86 2C88;COPTIC CAPITAL LETTER EIE;Lu;0;L;;;;;N;;;;2C89; 2C89;COPTIC SMALL LETTER EIE;Ll;0;L;;;;;N;;;2C88;;2C88 2C8A;COPTIC CAPITAL LETTER SOU;Lu;0;L;;;;;N;;;;2C8B; 2C8B;COPTIC SMALL LETTER SOU;Ll;0;L;;;;;N;;;2C8A;;2C8A 2C8C;COPTIC CAPITAL LETTER ZATA;Lu;0;L;;;;;N;;;;2C8D; 2C8D;COPTIC SMALL LETTER ZATA;Ll;0;L;;;;;N;;;2C8C;;2C8C 2C8E;COPTIC CAPITAL LETTER HATE;Lu;0;L;;;;;N;;;;2C8F; 2C8F;COPTIC SMALL LETTER HATE;Ll;0;L;;;;;N;;;2C8E;;2C8E 2C90;COPTIC CAPITAL LETTER THETHE;Lu;0;L;;;;;N;;;;2C91; 2C91;COPTIC SMALL LETTER THETHE;Ll;0;L;;;;;N;;;2C90;;2C90 2C92;COPTIC CAPITAL LETTER IAUDA;Lu;0;L;;;;;N;;;;2C93; 2C93;COPTIC SMALL LETTER IAUDA;Ll;0;L;;;;;N;;;2C92;;2C92 2C94;COPTIC CAPITAL LETTER KAPA;Lu;0;L;;;;;N;;;;2C95; 2C95;COPTIC SMALL LETTER KAPA;Ll;0;L;;;;;N;;;2C94;;2C94 2C96;COPTIC CAPITAL LETTER LAULA;Lu;0;L;;;;;N;;;;2C97; 2C97;COPTIC SMALL LETTER LAULA;Ll;0;L;;;;;N;;;2C96;;2C96 2C98;COPTIC CAPITAL LETTER MI;Lu;0;L;;;;;N;;;;2C99; 2C99;COPTIC SMALL LETTER MI;Ll;0;L;;;;;N;;;2C98;;2C98 2C9A;COPTIC CAPITAL LETTER NI;Lu;0;L;;;;;N;;;;2C9B; 2C9B;COPTIC SMALL LETTER NI;Ll;0;L;;;;;N;;;2C9A;;2C9A 2C9C;COPTIC CAPITAL LETTER KSI;Lu;0;L;;;;;N;;;;2C9D; 2C9D;COPTIC SMALL LETTER KSI;Ll;0;L;;;;;N;;;2C9C;;2C9C 2C9E;COPTIC CAPITAL LETTER O;Lu;0;L;;;;;N;;;;2C9F; 2C9F;COPTIC SMALL LETTER O;Ll;0;L;;;;;N;;;2C9E;;2C9E 2CA0;COPTIC CAPITAL LETTER PI;Lu;0;L;;;;;N;;;;2CA1; 2CA1;COPTIC SMALL LETTER PI;Ll;0;L;;;;;N;;;2CA0;;2CA0 2CA2;COPTIC CAPITAL LETTER RO;Lu;0;L;;;;;N;;;;2CA3; 2CA3;COPTIC SMALL LETTER RO;Ll;0;L;;;;;N;;;2CA2;;2CA2 2CA4;COPTIC CAPITAL LETTER SIMA;Lu;0;L;;;;;N;;;;2CA5; 2CA5;COPTIC SMALL LETTER SIMA;Ll;0;L;;;;;N;;;2CA4;;2CA4 2CA6;COPTIC CAPITAL LETTER TAU;Lu;0;L;;;;;N;;;;2CA7; 2CA7;COPTIC SMALL LETTER TAU;Ll;0;L;;;;;N;;;2CA6;;2CA6 2CA8;COPTIC CAPITAL LETTER UA;Lu;0;L;;;;;N;;;;2CA9; 2CA9;COPTIC SMALL LETTER UA;Ll;0;L;;;;;N;;;2CA8;;2CA8 2CAA;COPTIC CAPITAL LETTER FI;Lu;0;L;;;;;N;;;;2CAB; 2CAB;COPTIC SMALL LETTER FI;Ll;0;L;;;;;N;;;2CAA;;2CAA 2CAC;COPTIC CAPITAL LETTER KHI;Lu;0;L;;;;;N;;;;2CAD; 2CAD;COPTIC SMALL LETTER KHI;Ll;0;L;;;;;N;;;2CAC;;2CAC 2CAE;COPTIC CAPITAL LETTER PSI;Lu;0;L;;;;;N;;;;2CAF; 2CAF;COPTIC SMALL LETTER PSI;Ll;0;L;;;;;N;;;2CAE;;2CAE 2CB0;COPTIC CAPITAL LETTER OOU;Lu;0;L;;;;;N;;;;2CB1; 2CB1;COPTIC SMALL LETTER OOU;Ll;0;L;;;;;N;;;2CB0;;2CB0 2CB2;COPTIC CAPITAL LETTER DIALECT-P ALEF;Lu;0;L;;;;;N;;;;2CB3; 2CB3;COPTIC SMALL LETTER DIALECT-P ALEF;Ll;0;L;;;;;N;;;2CB2;;2CB2 2CB4;COPTIC CAPITAL LETTER OLD COPTIC AIN;Lu;0;L;;;;;N;;;;2CB5; 2CB5;COPTIC SMALL LETTER OLD COPTIC AIN;Ll;0;L;;;;;N;;;2CB4;;2CB4 2CB6;COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE;Lu;0;L;;;;;N;;;;2CB7; 2CB7;COPTIC SMALL LETTER CRYPTOGRAMMIC EIE;Ll;0;L;;;;;N;;;2CB6;;2CB6 2CB8;COPTIC CAPITAL LETTER DIALECT-P KAPA;Lu;0;L;;;;;N;;;;2CB9; 2CB9;COPTIC SMALL LETTER DIALECT-P KAPA;Ll;0;L;;;;;N;;;2CB8;;2CB8 2CBA;COPTIC CAPITAL LETTER DIALECT-P NI;Lu;0;L;;;;;N;;;;2CBB; 2CBB;COPTIC SMALL LETTER DIALECT-P NI;Ll;0;L;;;;;N;;;2CBA;;2CBA 2CBC;COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI;Lu;0;L;;;;;N;;;;2CBD; 2CBD;COPTIC SMALL LETTER CRYPTOGRAMMIC NI;Ll;0;L;;;;;N;;;2CBC;;2CBC 2CBE;COPTIC CAPITAL LETTER OLD COPTIC OOU;Lu;0;L;;;;;N;;;;2CBF; 2CBF;COPTIC SMALL LETTER OLD COPTIC OOU;Ll;0;L;;;;;N;;;2CBE;;2CBE 2CC0;COPTIC CAPITAL LETTER SAMPI;Lu;0;L;;;;;N;;;;2CC1; 2CC1;COPTIC SMALL LETTER SAMPI;Ll;0;L;;;;;N;;;2CC0;;2CC0 2CC2;COPTIC CAPITAL LETTER CROSSED SHEI;Lu;0;L;;;;;N;;;;2CC3; 2CC3;COPTIC SMALL LETTER CROSSED SHEI;Ll;0;L;;;;;N;;;2CC2;;2CC2 2CC4;COPTIC CAPITAL LETTER OLD COPTIC SHEI;Lu;0;L;;;;;N;;;;2CC5; 2CC5;COPTIC SMALL LETTER OLD COPTIC SHEI;Ll;0;L;;;;;N;;;2CC4;;2CC4 2CC6;COPTIC CAPITAL LETTER OLD COPTIC ESH;Lu;0;L;;;;;N;;;;2CC7; 2CC7;COPTIC SMALL LETTER OLD COPTIC ESH;Ll;0;L;;;;;N;;;2CC6;;2CC6 2CC8;COPTIC CAPITAL LETTER AKHMIMIC KHEI;Lu;0;L;;;;;N;;;;2CC9; 2CC9;COPTIC SMALL LETTER AKHMIMIC KHEI;Ll;0;L;;;;;N;;;2CC8;;2CC8 2CCA;COPTIC CAPITAL LETTER DIALECT-P HORI;Lu;0;L;;;;;N;;;;2CCB; 2CCB;COPTIC SMALL LETTER DIALECT-P HORI;Ll;0;L;;;;;N;;;2CCA;;2CCA 2CCC;COPTIC CAPITAL LETTER OLD COPTIC HORI;Lu;0;L;;;;;N;;;;2CCD; 2CCD;COPTIC SMALL LETTER OLD COPTIC HORI;Ll;0;L;;;;;N;;;2CCC;;2CCC 2CCE;COPTIC CAPITAL LETTER OLD COPTIC HA;Lu;0;L;;;;;N;;;;2CCF; 2CCF;COPTIC SMALL LETTER OLD COPTIC HA;Ll;0;L;;;;;N;;;2CCE;;2CCE 2CD0;COPTIC CAPITAL LETTER L-SHAPED HA;Lu;0;L;;;;;N;;;;2CD1; 2CD1;COPTIC SMALL LETTER L-SHAPED HA;Ll;0;L;;;;;N;;;2CD0;;2CD0 2CD2;COPTIC CAPITAL LETTER OLD COPTIC HEI;Lu;0;L;;;;;N;;;;2CD3; 2CD3;COPTIC SMALL LETTER OLD COPTIC HEI;Ll;0;L;;;;;N;;;2CD2;;2CD2 2CD4;COPTIC CAPITAL LETTER OLD COPTIC HAT;Lu;0;L;;;;;N;;;;2CD5; 2CD5;COPTIC SMALL LETTER OLD COPTIC HAT;Ll;0;L;;;;;N;;;2CD4;;2CD4 2CD6;COPTIC CAPITAL LETTER OLD COPTIC GANGIA;Lu;0;L;;;;;N;;;;2CD7; 2CD7;COPTIC SMALL LETTER OLD COPTIC GANGIA;Ll;0;L;;;;;N;;;2CD6;;2CD6 2CD8;COPTIC CAPITAL LETTER OLD COPTIC DJA;Lu;0;L;;;;;N;;;;2CD9; 2CD9;COPTIC SMALL LETTER OLD COPTIC DJA;Ll;0;L;;;;;N;;;2CD8;;2CD8 2CDA;COPTIC CAPITAL LETTER OLD COPTIC SHIMA;Lu;0;L;;;;;N;;;;2CDB; 2CDB;COPTIC SMALL LETTER OLD COPTIC SHIMA;Ll;0;L;;;;;N;;;2CDA;;2CDA 2CDC;COPTIC CAPITAL LETTER OLD NUBIAN SHIMA;Lu;0;L;;;;;N;;;;2CDD; 2CDD;COPTIC SMALL LETTER OLD NUBIAN SHIMA;Ll;0;L;;;;;N;;;2CDC;;2CDC 2CDE;COPTIC CAPITAL LETTER OLD NUBIAN NGI;Lu;0;L;;;;;N;;;;2CDF; 2CDF;COPTIC SMALL LETTER OLD NUBIAN NGI;Ll;0;L;;;;;N;;;2CDE;;2CDE 2CE0;COPTIC CAPITAL LETTER OLD NUBIAN NYI;Lu;0;L;;;;;N;;;;2CE1; 2CE1;COPTIC SMALL LETTER OLD NUBIAN NYI;Ll;0;L;;;;;N;;;2CE0;;2CE0 2CE2;COPTIC CAPITAL LETTER OLD NUBIAN WAU;Lu;0;L;;;;;N;;;;2CE3; 2CE3;COPTIC SMALL LETTER OLD NUBIAN WAU;Ll;0;L;;;;;N;;;2CE2;;2CE2 2CE4;COPTIC SYMBOL KAI;Ll;0;L;;;;;N;;;;; 2CE5;COPTIC SYMBOL MI RO;So;0;ON;;;;;N;;;;; 2CE6;COPTIC SYMBOL PI RO;So;0;ON;;;;;N;;;;; 2CE7;COPTIC SYMBOL STAUROS;So;0;ON;;;;;N;;;;; 2CE8;COPTIC SYMBOL TAU RO;So;0;ON;;;;;N;;;;; 2CE9;COPTIC SYMBOL KHI RO;So;0;ON;;;;;N;;;;; 2CEA;COPTIC SYMBOL SHIMA SIMA;So;0;ON;;;;;N;;;;; 2CEB;COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI;Lu;0;L;;;;;N;;;;2CEC; 2CEC;COPTIC SMALL LETTER CRYPTOGRAMMIC SHEI;Ll;0;L;;;;;N;;;2CEB;;2CEB 2CED;COPTIC CAPITAL LETTER CRYPTOGRAMMIC GANGIA;Lu;0;L;;;;;N;;;;2CEE; 2CEE;COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA;Ll;0;L;;;;;N;;;2CED;;2CED 2CEF;COPTIC COMBINING NI ABOVE;Mn;230;NSM;;;;;N;;;;; 2CF0;COPTIC COMBINING SPIRITUS ASPER;Mn;230;NSM;;;;;N;;;;; 2CF1;COPTIC COMBINING SPIRITUS LENIS;Mn;230;NSM;;;;;N;;;;; 2CF2;COPTIC CAPITAL LETTER BOHAIRIC KHEI;Lu;0;L;;;;;N;;;;2CF3; 2CF3;COPTIC SMALL LETTER BOHAIRIC KHEI;Ll;0;L;;;;;N;;;2CF2;;2CF2 2CF9;COPTIC OLD NUBIAN FULL STOP;Po;0;ON;;;;;N;;;;; 2CFA;COPTIC OLD NUBIAN DIRECT QUESTION MARK;Po;0;ON;;;;;N;;;;; 2CFB;COPTIC OLD NUBIAN INDIRECT QUESTION MARK;Po;0;ON;;;;;N;;;;; 2CFC;COPTIC OLD NUBIAN VERSE DIVIDER;Po;0;ON;;;;;N;;;;; 2CFD;COPTIC FRACTION ONE HALF;No;0;ON;;;;1/2;N;;;;; 2CFE;COPTIC FULL STOP;Po;0;ON;;;;;N;;;;; 2CFF;COPTIC MORPHOLOGICAL DIVIDER;Po;0;ON;;;;;N;;;;; 2D00;GEORGIAN SMALL LETTER AN;Ll;0;L;;;;;N;;;10A0;;10A0 2D01;GEORGIAN SMALL LETTER BAN;Ll;0;L;;;;;N;;;10A1;;10A1 2D02;GEORGIAN SMALL LETTER GAN;Ll;0;L;;;;;N;;;10A2;;10A2 2D03;GEORGIAN SMALL LETTER DON;Ll;0;L;;;;;N;;;10A3;;10A3 2D04;GEORGIAN SMALL LETTER EN;Ll;0;L;;;;;N;;;10A4;;10A4 2D05;GEORGIAN SMALL LETTER VIN;Ll;0;L;;;;;N;;;10A5;;10A5 2D06;GEORGIAN SMALL LETTER ZEN;Ll;0;L;;;;;N;;;10A6;;10A6 2D07;GEORGIAN SMALL LETTER TAN;Ll;0;L;;;;;N;;;10A7;;10A7 2D08;GEORGIAN SMALL LETTER IN;Ll;0;L;;;;;N;;;10A8;;10A8 2D09;GEORGIAN SMALL LETTER KAN;Ll;0;L;;;;;N;;;10A9;;10A9 2D0A;GEORGIAN SMALL LETTER LAS;Ll;0;L;;;;;N;;;10AA;;10AA 2D0B;GEORGIAN SMALL LETTER MAN;Ll;0;L;;;;;N;;;10AB;;10AB 2D0C;GEORGIAN SMALL LETTER NAR;Ll;0;L;;;;;N;;;10AC;;10AC 2D0D;GEORGIAN SMALL LETTER ON;Ll;0;L;;;;;N;;;10AD;;10AD 2D0E;GEORGIAN SMALL LETTER PAR;Ll;0;L;;;;;N;;;10AE;;10AE 2D0F;GEORGIAN SMALL LETTER ZHAR;Ll;0;L;;;;;N;;;10AF;;10AF 2D10;GEORGIAN SMALL LETTER RAE;Ll;0;L;;;;;N;;;10B0;;10B0 2D11;GEORGIAN SMALL LETTER SAN;Ll;0;L;;;;;N;;;10B1;;10B1 2D12;GEORGIAN SMALL LETTER TAR;Ll;0;L;;;;;N;;;10B2;;10B2 2D13;GEORGIAN SMALL LETTER UN;Ll;0;L;;;;;N;;;10B3;;10B3 2D14;GEORGIAN SMALL LETTER PHAR;Ll;0;L;;;;;N;;;10B4;;10B4 2D15;GEORGIAN SMALL LETTER KHAR;Ll;0;L;;;;;N;;;10B5;;10B5 2D16;GEORGIAN SMALL LETTER GHAN;Ll;0;L;;;;;N;;;10B6;;10B6 2D17;GEORGIAN SMALL LETTER QAR;Ll;0;L;;;;;N;;;10B7;;10B7 2D18;GEORGIAN SMALL LETTER SHIN;Ll;0;L;;;;;N;;;10B8;;10B8 2D19;GEORGIAN SMALL LETTER CHIN;Ll;0;L;;;;;N;;;10B9;;10B9 2D1A;GEORGIAN SMALL LETTER CAN;Ll;0;L;;;;;N;;;10BA;;10BA 2D1B;GEORGIAN SMALL LETTER JIL;Ll;0;L;;;;;N;;;10BB;;10BB 2D1C;GEORGIAN SMALL LETTER CIL;Ll;0;L;;;;;N;;;10BC;;10BC 2D1D;GEORGIAN SMALL LETTER CHAR;Ll;0;L;;;;;N;;;10BD;;10BD 2D1E;GEORGIAN SMALL LETTER XAN;Ll;0;L;;;;;N;;;10BE;;10BE 2D1F;GEORGIAN SMALL LETTER JHAN;Ll;0;L;;;;;N;;;10BF;;10BF 2D20;GEORGIAN SMALL LETTER HAE;Ll;0;L;;;;;N;;;10C0;;10C0 2D21;GEORGIAN SMALL LETTER HE;Ll;0;L;;;;;N;;;10C1;;10C1 2D22;GEORGIAN SMALL LETTER HIE;Ll;0;L;;;;;N;;;10C2;;10C2 2D23;GEORGIAN SMALL LETTER WE;Ll;0;L;;;;;N;;;10C3;;10C3 2D24;GEORGIAN SMALL LETTER HAR;Ll;0;L;;;;;N;;;10C4;;10C4 2D25;GEORGIAN SMALL LETTER HOE;Ll;0;L;;;;;N;;;10C5;;10C5 2D27;GEORGIAN SMALL LETTER YN;Ll;0;L;;;;;N;;;10C7;;10C7 2D2D;GEORGIAN SMALL LETTER AEN;Ll;0;L;;;;;N;;;10CD;;10CD 2D30;TIFINAGH LETTER YA;Lo;0;L;;;;;N;;;;; 2D31;TIFINAGH LETTER YAB;Lo;0;L;;;;;N;;;;; 2D32;TIFINAGH LETTER YABH;Lo;0;L;;;;;N;;;;; 2D33;TIFINAGH LETTER YAG;Lo;0;L;;;;;N;;;;; 2D34;TIFINAGH LETTER YAGHH;Lo;0;L;;;;;N;;;;; 2D35;TIFINAGH LETTER BERBER ACADEMY YAJ;Lo;0;L;;;;;N;;;;; 2D36;TIFINAGH LETTER YAJ;Lo;0;L;;;;;N;;;;; 2D37;TIFINAGH LETTER YAD;Lo;0;L;;;;;N;;;;; 2D38;TIFINAGH LETTER YADH;Lo;0;L;;;;;N;;;;; 2D39;TIFINAGH LETTER YADD;Lo;0;L;;;;;N;;;;; 2D3A;TIFINAGH LETTER YADDH;Lo;0;L;;;;;N;;;;; 2D3B;TIFINAGH LETTER YEY;Lo;0;L;;;;;N;;;;; 2D3C;TIFINAGH LETTER YAF;Lo;0;L;;;;;N;;;;; 2D3D;TIFINAGH LETTER YAK;Lo;0;L;;;;;N;;;;; 2D3E;TIFINAGH LETTER TUAREG YAK;Lo;0;L;;;;;N;;;;; 2D3F;TIFINAGH LETTER YAKHH;Lo;0;L;;;;;N;;;;; 2D40;TIFINAGH LETTER YAH;Lo;0;L;;;;;N;;;;; 2D41;TIFINAGH LETTER BERBER ACADEMY YAH;Lo;0;L;;;;;N;;;;; 2D42;TIFINAGH LETTER TUAREG YAH;Lo;0;L;;;;;N;;;;; 2D43;TIFINAGH LETTER YAHH;Lo;0;L;;;;;N;;;;; 2D44;TIFINAGH LETTER YAA;Lo;0;L;;;;;N;;;;; 2D45;TIFINAGH LETTER YAKH;Lo;0;L;;;;;N;;;;; 2D46;TIFINAGH LETTER TUAREG YAKH;Lo;0;L;;;;;N;;;;; 2D47;TIFINAGH LETTER YAQ;Lo;0;L;;;;;N;;;;; 2D48;TIFINAGH LETTER TUAREG YAQ;Lo;0;L;;;;;N;;;;; 2D49;TIFINAGH LETTER YI;Lo;0;L;;;;;N;;;;; 2D4A;TIFINAGH LETTER YAZH;Lo;0;L;;;;;N;;;;; 2D4B;TIFINAGH LETTER AHAGGAR YAZH;Lo;0;L;;;;;N;;;;; 2D4C;TIFINAGH LETTER TUAREG YAZH;Lo;0;L;;;;;N;;;;; 2D4D;TIFINAGH LETTER YAL;Lo;0;L;;;;;N;;;;; 2D4E;TIFINAGH LETTER YAM;Lo;0;L;;;;;N;;;;; 2D4F;TIFINAGH LETTER YAN;Lo;0;L;;;;;N;;;;; 2D50;TIFINAGH LETTER TUAREG YAGN;Lo;0;L;;;;;N;;;;; 2D51;TIFINAGH LETTER TUAREG YANG;Lo;0;L;;;;;N;;;;; 2D52;TIFINAGH LETTER YAP;Lo;0;L;;;;;N;;;;; 2D53;TIFINAGH LETTER YU;Lo;0;L;;;;;N;;;;; 2D54;TIFINAGH LETTER YAR;Lo;0;L;;;;;N;;;;; 2D55;TIFINAGH LETTER YARR;Lo;0;L;;;;;N;;;;; 2D56;TIFINAGH LETTER YAGH;Lo;0;L;;;;;N;;;;; 2D57;TIFINAGH LETTER TUAREG YAGH;Lo;0;L;;;;;N;;;;; 2D58;TIFINAGH LETTER AYER YAGH;Lo;0;L;;;;;N;;;;; 2D59;TIFINAGH LETTER YAS;Lo;0;L;;;;;N;;;;; 2D5A;TIFINAGH LETTER YASS;Lo;0;L;;;;;N;;;;; 2D5B;TIFINAGH LETTER YASH;Lo;0;L;;;;;N;;;;; 2D5C;TIFINAGH LETTER YAT;Lo;0;L;;;;;N;;;;; 2D5D;TIFINAGH LETTER YATH;Lo;0;L;;;;;N;;;;; 2D5E;TIFINAGH LETTER YACH;Lo;0;L;;;;;N;;;;; 2D5F;TIFINAGH LETTER YATT;Lo;0;L;;;;;N;;;;; 2D60;TIFINAGH LETTER YAV;Lo;0;L;;;;;N;;;;; 2D61;TIFINAGH LETTER YAW;Lo;0;L;;;;;N;;;;; 2D62;TIFINAGH LETTER YAY;Lo;0;L;;;;;N;;;;; 2D63;TIFINAGH LETTER YAZ;Lo;0;L;;;;;N;;;;; 2D64;TIFINAGH LETTER TAWELLEMET YAZ;Lo;0;L;;;;;N;;;;; 2D65;TIFINAGH LETTER YAZZ;Lo;0;L;;;;;N;;;;; 2D66;TIFINAGH LETTER YE;Lo;0;L;;;;;N;;;;; 2D67;TIFINAGH LETTER YO;Lo;0;L;;;;;N;;;;; 2D6F;TIFINAGH MODIFIER LETTER LABIALIZATION MARK;Lm;0;L; 2D61;;;;N;;;;; 2D70;TIFINAGH SEPARATOR MARK;Po;0;L;;;;;N;;;;; 2D7F;TIFINAGH CONSONANT JOINER;Mn;9;NSM;;;;;N;;;;; 2D80;ETHIOPIC SYLLABLE LOA;Lo;0;L;;;;;N;;;;; 2D81;ETHIOPIC SYLLABLE MOA;Lo;0;L;;;;;N;;;;; 2D82;ETHIOPIC SYLLABLE ROA;Lo;0;L;;;;;N;;;;; 2D83;ETHIOPIC SYLLABLE SOA;Lo;0;L;;;;;N;;;;; 2D84;ETHIOPIC SYLLABLE SHOA;Lo;0;L;;;;;N;;;;; 2D85;ETHIOPIC SYLLABLE BOA;Lo;0;L;;;;;N;;;;; 2D86;ETHIOPIC SYLLABLE TOA;Lo;0;L;;;;;N;;;;; 2D87;ETHIOPIC SYLLABLE COA;Lo;0;L;;;;;N;;;;; 2D88;ETHIOPIC SYLLABLE NOA;Lo;0;L;;;;;N;;;;; 2D89;ETHIOPIC SYLLABLE NYOA;Lo;0;L;;;;;N;;;;; 2D8A;ETHIOPIC SYLLABLE GLOTTAL OA;Lo;0;L;;;;;N;;;;; 2D8B;ETHIOPIC SYLLABLE ZOA;Lo;0;L;;;;;N;;;;; 2D8C;ETHIOPIC SYLLABLE DOA;Lo;0;L;;;;;N;;;;; 2D8D;ETHIOPIC SYLLABLE DDOA;Lo;0;L;;;;;N;;;;; 2D8E;ETHIOPIC SYLLABLE JOA;Lo;0;L;;;;;N;;;;; 2D8F;ETHIOPIC SYLLABLE THOA;Lo;0;L;;;;;N;;;;; 2D90;ETHIOPIC SYLLABLE CHOA;Lo;0;L;;;;;N;;;;; 2D91;ETHIOPIC SYLLABLE PHOA;Lo;0;L;;;;;N;;;;; 2D92;ETHIOPIC SYLLABLE POA;Lo;0;L;;;;;N;;;;; 2D93;ETHIOPIC SYLLABLE GGWA;Lo;0;L;;;;;N;;;;; 2D94;ETHIOPIC SYLLABLE GGWI;Lo;0;L;;;;;N;;;;; 2D95;ETHIOPIC SYLLABLE GGWEE;Lo;0;L;;;;;N;;;;; 2D96;ETHIOPIC SYLLABLE GGWE;Lo;0;L;;;;;N;;;;; 2DA0;ETHIOPIC SYLLABLE SSA;Lo;0;L;;;;;N;;;;; 2DA1;ETHIOPIC SYLLABLE SSU;Lo;0;L;;;;;N;;;;; 2DA2;ETHIOPIC SYLLABLE SSI;Lo;0;L;;;;;N;;;;; 2DA3;ETHIOPIC SYLLABLE SSAA;Lo;0;L;;;;;N;;;;; 2DA4;ETHIOPIC SYLLABLE SSEE;Lo;0;L;;;;;N;;;;; 2DA5;ETHIOPIC SYLLABLE SSE;Lo;0;L;;;;;N;;;;; 2DA6;ETHIOPIC SYLLABLE SSO;Lo;0;L;;;;;N;;;;; 2DA8;ETHIOPIC SYLLABLE CCA;Lo;0;L;;;;;N;;;;; 2DA9;ETHIOPIC SYLLABLE CCU;Lo;0;L;;;;;N;;;;; 2DAA;ETHIOPIC SYLLABLE CCI;Lo;0;L;;;;;N;;;;; 2DAB;ETHIOPIC SYLLABLE CCAA;Lo;0;L;;;;;N;;;;; 2DAC;ETHIOPIC SYLLABLE CCEE;Lo;0;L;;;;;N;;;;; 2DAD;ETHIOPIC SYLLABLE CCE;Lo;0;L;;;;;N;;;;; 2DAE;ETHIOPIC SYLLABLE CCO;Lo;0;L;;;;;N;;;;; 2DB0;ETHIOPIC SYLLABLE ZZA;Lo;0;L;;;;;N;;;;; 2DB1;ETHIOPIC SYLLABLE ZZU;Lo;0;L;;;;;N;;;;; 2DB2;ETHIOPIC SYLLABLE ZZI;Lo;0;L;;;;;N;;;;; 2DB3;ETHIOPIC SYLLABLE ZZAA;Lo;0;L;;;;;N;;;;; 2DB4;ETHIOPIC SYLLABLE ZZEE;Lo;0;L;;;;;N;;;;; 2DB5;ETHIOPIC SYLLABLE ZZE;Lo;0;L;;;;;N;;;;; 2DB6;ETHIOPIC SYLLABLE ZZO;Lo;0;L;;;;;N;;;;; 2DB8;ETHIOPIC SYLLABLE CCHA;Lo;0;L;;;;;N;;;;; 2DB9;ETHIOPIC SYLLABLE CCHU;Lo;0;L;;;;;N;;;;; 2DBA;ETHIOPIC SYLLABLE CCHI;Lo;0;L;;;;;N;;;;; 2DBB;ETHIOPIC SYLLABLE CCHAA;Lo;0;L;;;;;N;;;;; 2DBC;ETHIOPIC SYLLABLE CCHEE;Lo;0;L;;;;;N;;;;; 2DBD;ETHIOPIC SYLLABLE CCHE;Lo;0;L;;;;;N;;;;; 2DBE;ETHIOPIC SYLLABLE CCHO;Lo;0;L;;;;;N;;;;; 2DC0;ETHIOPIC SYLLABLE QYA;Lo;0;L;;;;;N;;;;; 2DC1;ETHIOPIC SYLLABLE QYU;Lo;0;L;;;;;N;;;;; 2DC2;ETHIOPIC SYLLABLE QYI;Lo;0;L;;;;;N;;;;; 2DC3;ETHIOPIC SYLLABLE QYAA;Lo;0;L;;;;;N;;;;; 2DC4;ETHIOPIC SYLLABLE QYEE;Lo;0;L;;;;;N;;;;; 2DC5;ETHIOPIC SYLLABLE QYE;Lo;0;L;;;;;N;;;;; 2DC6;ETHIOPIC SYLLABLE QYO;Lo;0;L;;;;;N;;;;; 2DC8;ETHIOPIC SYLLABLE KYA;Lo;0;L;;;;;N;;;;; 2DC9;ETHIOPIC SYLLABLE KYU;Lo;0;L;;;;;N;;;;; 2DCA;ETHIOPIC SYLLABLE KYI;Lo;0;L;;;;;N;;;;; 2DCB;ETHIOPIC SYLLABLE KYAA;Lo;0;L;;;;;N;;;;; 2DCC;ETHIOPIC SYLLABLE KYEE;Lo;0;L;;;;;N;;;;; 2DCD;ETHIOPIC SYLLABLE KYE;Lo;0;L;;;;;N;;;;; 2DCE;ETHIOPIC SYLLABLE KYO;Lo;0;L;;;;;N;;;;; 2DD0;ETHIOPIC SYLLABLE XYA;Lo;0;L;;;;;N;;;;; 2DD1;ETHIOPIC SYLLABLE XYU;Lo;0;L;;;;;N;;;;; 2DD2;ETHIOPIC SYLLABLE XYI;Lo;0;L;;;;;N;;;;; 2DD3;ETHIOPIC SYLLABLE XYAA;Lo;0;L;;;;;N;;;;; 2DD4;ETHIOPIC SYLLABLE XYEE;Lo;0;L;;;;;N;;;;; 2DD5;ETHIOPIC SYLLABLE XYE;Lo;0;L;;;;;N;;;;; 2DD6;ETHIOPIC SYLLABLE XYO;Lo;0;L;;;;;N;;;;; 2DD8;ETHIOPIC SYLLABLE GYA;Lo;0;L;;;;;N;;;;; 2DD9;ETHIOPIC SYLLABLE GYU;Lo;0;L;;;;;N;;;;; 2DDA;ETHIOPIC SYLLABLE GYI;Lo;0;L;;;;;N;;;;; 2DDB;ETHIOPIC SYLLABLE GYAA;Lo;0;L;;;;;N;;;;; 2DDC;ETHIOPIC SYLLABLE GYEE;Lo;0;L;;;;;N;;;;; 2DDD;ETHIOPIC SYLLABLE GYE;Lo;0;L;;;;;N;;;;; 2DDE;ETHIOPIC SYLLABLE GYO;Lo;0;L;;;;;N;;;;; 2DE0;COMBINING CYRILLIC LETTER BE;Mn;230;NSM;;;;;N;;;;; 2DE1;COMBINING CYRILLIC LETTER VE;Mn;230;NSM;;;;;N;;;;; 2DE2;COMBINING CYRILLIC LETTER GHE;Mn;230;NSM;;;;;N;;;;; 2DE3;COMBINING CYRILLIC LETTER DE;Mn;230;NSM;;;;;N;;;;; 2DE4;COMBINING CYRILLIC LETTER ZHE;Mn;230;NSM;;;;;N;;;;; 2DE5;COMBINING CYRILLIC LETTER ZE;Mn;230;NSM;;;;;N;;;;; 2DE6;COMBINING CYRILLIC LETTER KA;Mn;230;NSM;;;;;N;;;;; 2DE7;COMBINING CYRILLIC LETTER EL;Mn;230;NSM;;;;;N;;;;; 2DE8;COMBINING CYRILLIC LETTER EM;Mn;230;NSM;;;;;N;;;;; 2DE9;COMBINING CYRILLIC LETTER EN;Mn;230;NSM;;;;;N;;;;; 2DEA;COMBINING CYRILLIC LETTER O;Mn;230;NSM;;;;;N;;;;; 2DEB;COMBINING CYRILLIC LETTER PE;Mn;230;NSM;;;;;N;;;;; 2DEC;COMBINING CYRILLIC LETTER ER;Mn;230;NSM;;;;;N;;;;; 2DED;COMBINING CYRILLIC LETTER ES;Mn;230;NSM;;;;;N;;;;; 2DEE;COMBINING CYRILLIC LETTER TE;Mn;230;NSM;;;;;N;;;;; 2DEF;COMBINING CYRILLIC LETTER HA;Mn;230;NSM;;;;;N;;;;; 2DF0;COMBINING CYRILLIC LETTER TSE;Mn;230;NSM;;;;;N;;;;; 2DF1;COMBINING CYRILLIC LETTER CHE;Mn;230;NSM;;;;;N;;;;; 2DF2;COMBINING CYRILLIC LETTER SHA;Mn;230;NSM;;;;;N;;;;; 2DF3;COMBINING CYRILLIC LETTER SHCHA;Mn;230;NSM;;;;;N;;;;; 2DF4;COMBINING CYRILLIC LETTER FITA;Mn;230;NSM;;;;;N;;;;; 2DF5;COMBINING CYRILLIC LETTER ES-TE;Mn;230;NSM;;;;;N;;;;; 2DF6;COMBINING CYRILLIC LETTER A;Mn;230;NSM;;;;;N;;;;; 2DF7;COMBINING CYRILLIC LETTER IE;Mn;230;NSM;;;;;N;;;;; 2DF8;COMBINING CYRILLIC LETTER DJERV;Mn;230;NSM;;;;;N;;;;; 2DF9;COMBINING CYRILLIC LETTER MONOGRAPH UK;Mn;230;NSM;;;;;N;;;;; 2DFA;COMBINING CYRILLIC LETTER YAT;Mn;230;NSM;;;;;N;;;;; 2DFB;COMBINING CYRILLIC LETTER YU;Mn;230;NSM;;;;;N;;;;; 2DFC;COMBINING CYRILLIC LETTER IOTIFIED A;Mn;230;NSM;;;;;N;;;;; 2DFD;COMBINING CYRILLIC LETTER LITTLE YUS;Mn;230;NSM;;;;;N;;;;; 2DFE;COMBINING CYRILLIC LETTER BIG YUS;Mn;230;NSM;;;;;N;;;;; 2DFF;COMBINING CYRILLIC LETTER IOTIFIED BIG YUS;Mn;230;NSM;;;;;N;;;;; 2E00;RIGHT ANGLE SUBSTITUTION MARKER;Po;0;ON;;;;;N;;;;; 2E01;RIGHT ANGLE DOTTED SUBSTITUTION MARKER;Po;0;ON;;;;;N;;;;; 2E02;LEFT SUBSTITUTION BRACKET;Pi;0;ON;;;;;Y;;;;; 2E03;RIGHT SUBSTITUTION BRACKET;Pf;0;ON;;;;;Y;;;;; 2E04;LEFT DOTTED SUBSTITUTION BRACKET;Pi;0;ON;;;;;Y;;;;; 2E05;RIGHT DOTTED SUBSTITUTION BRACKET;Pf;0;ON;;;;;Y;;;;; 2E06;RAISED INTERPOLATION MARKER;Po;0;ON;;;;;N;;;;; 2E07;RAISED DOTTED INTERPOLATION MARKER;Po;0;ON;;;;;N;;;;; 2E08;DOTTED TRANSPOSITION MARKER;Po;0;ON;;;;;N;;;;; 2E09;LEFT TRANSPOSITION BRACKET;Pi;0;ON;;;;;Y;;;;; 2E0A;RIGHT TRANSPOSITION BRACKET;Pf;0;ON;;;;;Y;;;;; 2E0B;RAISED SQUARE;Po;0;ON;;;;;N;;;;; 2E0C;LEFT RAISED OMISSION BRACKET;Pi;0;ON;;;;;Y;;;;; 2E0D;RIGHT RAISED OMISSION BRACKET;Pf;0;ON;;;;;Y;;;;; 2E0E;EDITORIAL CORONIS;Po;0;ON;;;;;N;;;;; 2E0F;PARAGRAPHOS;Po;0;ON;;;;;N;;;;; 2E10;FORKED PARAGRAPHOS;Po;0;ON;;;;;N;;;;; 2E11;REVERSED FORKED PARAGRAPHOS;Po;0;ON;;;;;N;;;;; 2E12;HYPODIASTOLE;Po;0;ON;;;;;N;;;;; 2E13;DOTTED OBELOS;Po;0;ON;;;;;N;;;;; 2E14;DOWNWARDS ANCORA;Po;0;ON;;;;;N;;;;; 2E15;UPWARDS ANCORA;Po;0;ON;;;;;N;;;;; 2E16;DOTTED RIGHT-POINTING ANGLE;Po;0;ON;;;;;N;;;;; 2E17;DOUBLE OBLIQUE HYPHEN;Pd;0;ON;;;;;N;;;;; 2E18;INVERTED INTERROBANG;Po;0;ON;;;;;N;;;;; 2E19;PALM BRANCH;Po;0;ON;;;;;N;;;;; 2E1A;HYPHEN WITH DIAERESIS;Pd;0;ON;;;;;N;;;;; 2E1B;TILDE WITH RING ABOVE;Po;0;ON;;;;;N;;;;; 2E1C;LEFT LOW PARAPHRASE BRACKET;Pi;0;ON;;;;;Y;;;;; 2E1D;RIGHT LOW PARAPHRASE BRACKET;Pf;0;ON;;;;;Y;;;;; 2E1E;TILDE WITH DOT ABOVE;Po;0;ON;;;;;N;;;;; 2E1F;TILDE WITH DOT BELOW;Po;0;ON;;;;;N;;;;; 2E20;LEFT VERTICAL BAR WITH QUILL;Pi;0;ON;;;;;Y;;;;; 2E21;RIGHT VERTICAL BAR WITH QUILL;Pf;0;ON;;;;;Y;;;;; 2E22;TOP LEFT HALF BRACKET;Ps;0;ON;;;;;Y;;;;; 2E23;TOP RIGHT HALF BRACKET;Pe;0;ON;;;;;Y;;;;; 2E24;BOTTOM LEFT HALF BRACKET;Ps;0;ON;;;;;Y;;;;; 2E25;BOTTOM RIGHT HALF BRACKET;Pe;0;ON;;;;;Y;;;;; 2E26;LEFT SIDEWAYS U BRACKET;Ps;0;ON;;;;;Y;;;;; 2E27;RIGHT SIDEWAYS U BRACKET;Pe;0;ON;;;;;Y;;;;; 2E28;LEFT DOUBLE PARENTHESIS;Ps;0;ON;;;;;Y;;;;; 2E29;RIGHT DOUBLE PARENTHESIS;Pe;0;ON;;;;;Y;;;;; 2E2A;TWO DOTS OVER ONE DOT PUNCTUATION;Po;0;ON;;;;;N;;;;; 2E2B;ONE DOT OVER TWO DOTS PUNCTUATION;Po;0;ON;;;;;N;;;;; 2E2C;SQUARED FOUR DOT PUNCTUATION;Po;0;ON;;;;;N;;;;; 2E2D;FIVE DOT MARK;Po;0;ON;;;;;N;;;;; 2E2E;REVERSED QUESTION MARK;Po;0;ON;;;;;N;;;;; 2E2F;VERTICAL TILDE;Lm;0;ON;;;;;N;;;;; 2E30;RING POINT;Po;0;ON;;;;;N;;;;; 2E31;WORD SEPARATOR MIDDLE DOT;Po;0;ON;;;;;N;;;;; 2E32;TURNED COMMA;Po;0;ON;;;;;N;;;;; 2E33;RAISED DOT;Po;0;ON;;;;;N;;;;; 2E34;RAISED COMMA;Po;0;ON;;;;;N;;;;; 2E35;TURNED SEMICOLON;Po;0;ON;;;;;N;;;;; 2E36;DAGGER WITH LEFT GUARD;Po;0;ON;;;;;N;;;;; 2E37;DAGGER WITH RIGHT GUARD;Po;0;ON;;;;;N;;;;; 2E38;TURNED DAGGER;Po;0;ON;;;;;N;;;;; 2E39;TOP HALF SECTION SIGN;Po;0;ON;;;;;N;;;;; 2E3A;TWO-EM DASH;Pd;0;ON;;;;;N;;;;; 2E3B;THREE-EM DASH;Pd;0;ON;;;;;N;;;;; 2E3C;STENOGRAPHIC FULL STOP;Po;0;ON;;;;;N;;;;; 2E3D;VERTICAL SIX DOTS;Po;0;ON;;;;;N;;;;; 2E3E;WIGGLY VERTICAL LINE;Po;0;ON;;;;;N;;;;; 2E3F;CAPITULUM;Po;0;ON;;;;;N;;;;; 2E40;DOUBLE HYPHEN;Pd;0;ON;;;;;N;;;;; 2E41;REVERSED COMMA;Po;0;ON;;;;;N;;;;; 2E42;DOUBLE LOW-REVERSED-9 QUOTATION MARK;Ps;0;ON;;;;;N;;;;; 2E43;DASH WITH LEFT UPTURN;Po;0;ON;;;;;N;;;;; 2E44;DOUBLE SUSPENSION MARK;Po;0;ON;;;;;N;;;;; 2E80;CJK RADICAL REPEAT;So;0;ON;;;;;N;;;;; 2E81;CJK RADICAL CLIFF;So;0;ON;;;;;N;;;;; 2E82;CJK RADICAL SECOND ONE;So;0;ON;;;;;N;;;;; 2E83;CJK RADICAL SECOND TWO;So;0;ON;;;;;N;;;;; 2E84;CJK RADICAL SECOND THREE;So;0;ON;;;;;N;;;;; 2E85;CJK RADICAL PERSON;So;0;ON;;;;;N;;;;; 2E86;CJK RADICAL BOX;So;0;ON;;;;;N;;;;; 2E87;CJK RADICAL TABLE;So;0;ON;;;;;N;;;;; 2E88;CJK RADICAL KNIFE ONE;So;0;ON;;;;;N;;;;; 2E89;CJK RADICAL KNIFE TWO;So;0;ON;;;;;N;;;;; 2E8A;CJK RADICAL DIVINATION;So;0;ON;;;;;N;;;;; 2E8B;CJK RADICAL SEAL;So;0;ON;;;;;N;;;;; 2E8C;CJK RADICAL SMALL ONE;So;0;ON;;;;;N;;;;; 2E8D;CJK RADICAL SMALL TWO;So;0;ON;;;;;N;;;;; 2E8E;CJK RADICAL LAME ONE;So;0;ON;;;;;N;;;;; 2E8F;CJK RADICAL LAME TWO;So;0;ON;;;;;N;;;;; 2E90;CJK RADICAL LAME THREE;So;0;ON;;;;;N;;;;; 2E91;CJK RADICAL LAME FOUR;So;0;ON;;;;;N;;;;; 2E92;CJK RADICAL SNAKE;So;0;ON;;;;;N;;;;; 2E93;CJK RADICAL THREAD;So;0;ON;;;;;N;;;;; 2E94;CJK RADICAL SNOUT ONE;So;0;ON;;;;;N;;;;; 2E95;CJK RADICAL SNOUT TWO;So;0;ON;;;;;N;;;;; 2E96;CJK RADICAL HEART ONE;So;0;ON;;;;;N;;;;; 2E97;CJK RADICAL HEART TWO;So;0;ON;;;;;N;;;;; 2E98;CJK RADICAL HAND;So;0;ON;;;;;N;;;;; 2E99;CJK RADICAL RAP;So;0;ON;;;;;N;;;;; 2E9B;CJK RADICAL CHOKE;So;0;ON;;;;;N;;;;; 2E9C;CJK RADICAL SUN;So;0;ON;;;;;N;;;;; 2E9D;CJK RADICAL MOON;So;0;ON;;;;;N;;;;; 2E9E;CJK RADICAL DEATH;So;0;ON;;;;;N;;;;; 2E9F;CJK RADICAL MOTHER;So;0;ON; 6BCD;;;;N;;;;; 2EA0;CJK RADICAL CIVILIAN;So;0;ON;;;;;N;;;;; 2EA1;CJK RADICAL WATER ONE;So;0;ON;;;;;N;;;;; 2EA2;CJK RADICAL WATER TWO;So;0;ON;;;;;N;;;;; 2EA3;CJK RADICAL FIRE;So;0;ON;;;;;N;;;;; 2EA4;CJK RADICAL PAW ONE;So;0;ON;;;;;N;;;;; 2EA5;CJK RADICAL PAW TWO;So;0;ON;;;;;N;;;;; 2EA6;CJK RADICAL SIMPLIFIED HALF TREE TRUNK;So;0;ON;;;;;N;;;;; 2EA7;CJK RADICAL COW;So;0;ON;;;;;N;;;;; 2EA8;CJK RADICAL DOG;So;0;ON;;;;;N;;;;; 2EA9;CJK RADICAL JADE;So;0;ON;;;;;N;;;;; 2EAA;CJK RADICAL BOLT OF CLOTH;So;0;ON;;;;;N;;;;; 2EAB;CJK RADICAL EYE;So;0;ON;;;;;N;;;;; 2EAC;CJK RADICAL SPIRIT ONE;So;0;ON;;;;;N;;;;; 2EAD;CJK RADICAL SPIRIT TWO;So;0;ON;;;;;N;;;;; 2EAE;CJK RADICAL BAMBOO;So;0;ON;;;;;N;;;;; 2EAF;CJK RADICAL SILK;So;0;ON;;;;;N;;;;; 2EB0;CJK RADICAL C-SIMPLIFIED SILK;So;0;ON;;;;;N;;;;; 2EB1;CJK RADICAL NET ONE;So;0;ON;;;;;N;;;;; 2EB2;CJK RADICAL NET TWO;So;0;ON;;;;;N;;;;; 2EB3;CJK RADICAL NET THREE;So;0;ON;;;;;N;;;;; 2EB4;CJK RADICAL NET FOUR;So;0;ON;;;;;N;;;;; 2EB5;CJK RADICAL MESH;So;0;ON;;;;;N;;;;; 2EB6;CJK RADICAL SHEEP;So;0;ON;;;;;N;;;;; 2EB7;CJK RADICAL RAM;So;0;ON;;;;;N;;;;; 2EB8;CJK RADICAL EWE;So;0;ON;;;;;N;;;;; 2EB9;CJK RADICAL OLD;So;0;ON;;;;;N;;;;; 2EBA;CJK RADICAL BRUSH ONE;So;0;ON;;;;;N;;;;; 2EBB;CJK RADICAL BRUSH TWO;So;0;ON;;;;;N;;;;; 2EBC;CJK RADICAL MEAT;So;0;ON;;;;;N;;;;; 2EBD;CJK RADICAL MORTAR;So;0;ON;;;;;N;;;;; 2EBE;CJK RADICAL GRASS ONE;So;0;ON;;;;;N;;;;; 2EBF;CJK RADICAL GRASS TWO;So;0;ON;;;;;N;;;;; 2EC0;CJK RADICAL GRASS THREE;So;0;ON;;;;;N;;;;; 2EC1;CJK RADICAL TIGER;So;0;ON;;;;;N;;;;; 2EC2;CJK RADICAL CLOTHES;So;0;ON;;;;;N;;;;; 2EC3;CJK RADICAL WEST ONE;So;0;ON;;;;;N;;;;; 2EC4;CJK RADICAL WEST TWO;So;0;ON;;;;;N;;;;; 2EC5;CJK RADICAL C-SIMPLIFIED SEE;So;0;ON;;;;;N;;;;; 2EC6;CJK RADICAL SIMPLIFIED HORN;So;0;ON;;;;;N;;;;; 2EC7;CJK RADICAL HORN;So;0;ON;;;;;N;;;;; 2EC8;CJK RADICAL C-SIMPLIFIED SPEECH;So;0;ON;;;;;N;;;;; 2EC9;CJK RADICAL C-SIMPLIFIED SHELL;So;0;ON;;;;;N;;;;; 2ECA;CJK RADICAL FOOT;So;0;ON;;;;;N;;;;; 2ECB;CJK RADICAL C-SIMPLIFIED CART;So;0;ON;;;;;N;;;;; 2ECC;CJK RADICAL SIMPLIFIED WALK;So;0;ON;;;;;N;;;;; 2ECD;CJK RADICAL WALK ONE;So;0;ON;;;;;N;;;;; 2ECE;CJK RADICAL WALK TWO;So;0;ON;;;;;N;;;;; 2ECF;CJK RADICAL CITY;So;0;ON;;;;;N;;;;; 2ED0;CJK RADICAL C-SIMPLIFIED GOLD;So;0;ON;;;;;N;;;;; 2ED1;CJK RADICAL LONG ONE;So;0;ON;;;;;N;;;;; 2ED2;CJK RADICAL LONG TWO;So;0;ON;;;;;N;;;;; 2ED3;CJK RADICAL C-SIMPLIFIED LONG;So;0;ON;;;;;N;;;;; 2ED4;CJK RADICAL C-SIMPLIFIED GATE;So;0;ON;;;;;N;;;;; 2ED5;CJK RADICAL MOUND ONE;So;0;ON;;;;;N;;;;; 2ED6;CJK RADICAL MOUND TWO;So;0;ON;;;;;N;;;;; 2ED7;CJK RADICAL RAIN;So;0;ON;;;;;N;;;;; 2ED8;CJK RADICAL BLUE;So;0;ON;;;;;N;;;;; 2ED9;CJK RADICAL C-SIMPLIFIED TANNED LEATHER;So;0;ON;;;;;N;;;;; 2EDA;CJK RADICAL C-SIMPLIFIED LEAF;So;0;ON;;;;;N;;;;; 2EDB;CJK RADICAL C-SIMPLIFIED WIND;So;0;ON;;;;;N;;;;; 2EDC;CJK RADICAL C-SIMPLIFIED FLY;So;0;ON;;;;;N;;;;; 2EDD;CJK RADICAL EAT ONE;So;0;ON;;;;;N;;;;; 2EDE;CJK RADICAL EAT TWO;So;0;ON;;;;;N;;;;; 2EDF;CJK RADICAL EAT THREE;So;0;ON;;;;;N;;;;; 2EE0;CJK RADICAL C-SIMPLIFIED EAT;So;0;ON;;;;;N;;;;; 2EE1;CJK RADICAL HEAD;So;0;ON;;;;;N;;;;; 2EE2;CJK RADICAL C-SIMPLIFIED HORSE;So;0;ON;;;;;N;;;;; 2EE3;CJK RADICAL BONE;So;0;ON;;;;;N;;;;; 2EE4;CJK RADICAL GHOST;So;0;ON;;;;;N;;;;; 2EE5;CJK RADICAL C-SIMPLIFIED FISH;So;0;ON;;;;;N;;;;; 2EE6;CJK RADICAL C-SIMPLIFIED BIRD;So;0;ON;;;;;N;;;;; 2EE7;CJK RADICAL C-SIMPLIFIED SALT;So;0;ON;;;;;N;;;;; 2EE8;CJK RADICAL SIMPLIFIED WHEAT;So;0;ON;;;;;N;;;;; 2EE9;CJK RADICAL SIMPLIFIED YELLOW;So;0;ON;;;;;N;;;;; 2EEA;CJK RADICAL C-SIMPLIFIED FROG;So;0;ON;;;;;N;;;;; 2EEB;CJK RADICAL J-SIMPLIFIED EVEN;So;0;ON;;;;;N;;;;; 2EEC;CJK RADICAL C-SIMPLIFIED EVEN;So;0;ON;;;;;N;;;;; 2EED;CJK RADICAL J-SIMPLIFIED TOOTH;So;0;ON;;;;;N;;;;; 2EEE;CJK RADICAL C-SIMPLIFIED TOOTH;So;0;ON;;;;;N;;;;; 2EEF;CJK RADICAL J-SIMPLIFIED DRAGON;So;0;ON;;;;;N;;;;; 2EF0;CJK RADICAL C-SIMPLIFIED DRAGON;So;0;ON;;;;;N;;;;; 2EF1;CJK RADICAL TURTLE;So;0;ON;;;;;N;;;;; 2EF2;CJK RADICAL J-SIMPLIFIED TURTLE;So;0;ON;;;;;N;;;;; 2EF3;CJK RADICAL C-SIMPLIFIED TURTLE;So;0;ON; 9F9F;;;;N;;;;; 2F00;KANGXI RADICAL ONE;So;0;ON; 4E00;;;;N;;;;; 2F01;KANGXI RADICAL LINE;So;0;ON; 4E28;;;;N;;;;; 2F02;KANGXI RADICAL DOT;So;0;ON; 4E36;;;;N;;;;; 2F03;KANGXI RADICAL SLASH;So;0;ON; 4E3F;;;;N;;;;; 2F04;KANGXI RADICAL SECOND;So;0;ON; 4E59;;;;N;;;;; 2F05;KANGXI RADICAL HOOK;So;0;ON; 4E85;;;;N;;;;; 2F06;KANGXI RADICAL TWO;So;0;ON; 4E8C;;;;N;;;;; 2F07;KANGXI RADICAL LID;So;0;ON; 4EA0;;;;N;;;;; 2F08;KANGXI RADICAL MAN;So;0;ON; 4EBA;;;;N;;;;; 2F09;KANGXI RADICAL LEGS;So;0;ON; 513F;;;;N;;;;; 2F0A;KANGXI RADICAL ENTER;So;0;ON; 5165;;;;N;;;;; 2F0B;KANGXI RADICAL EIGHT;So;0;ON; 516B;;;;N;;;;; 2F0C;KANGXI RADICAL DOWN BOX;So;0;ON; 5182;;;;N;;;;; 2F0D;KANGXI RADICAL COVER;So;0;ON; 5196;;;;N;;;;; 2F0E;KANGXI RADICAL ICE;So;0;ON; 51AB;;;;N;;;;; 2F0F;KANGXI RADICAL TABLE;So;0;ON; 51E0;;;;N;;;;; 2F10;KANGXI RADICAL OPEN BOX;So;0;ON; 51F5;;;;N;;;;; 2F11;KANGXI RADICAL KNIFE;So;0;ON; 5200;;;;N;;;;; 2F12;KANGXI RADICAL POWER;So;0;ON; 529B;;;;N;;;;; 2F13;KANGXI RADICAL WRAP;So;0;ON; 52F9;;;;N;;;;; 2F14;KANGXI RADICAL SPOON;So;0;ON; 5315;;;;N;;;;; 2F15;KANGXI RADICAL RIGHT OPEN BOX;So;0;ON; 531A;;;;N;;;;; 2F16;KANGXI RADICAL HIDING ENCLOSURE;So;0;ON; 5338;;;;N;;;;; 2F17;KANGXI RADICAL TEN;So;0;ON; 5341;;;;N;;;;; 2F18;KANGXI RADICAL DIVINATION;So;0;ON; 535C;;;;N;;;;; 2F19;KANGXI RADICAL SEAL;So;0;ON; 5369;;;;N;;;;; 2F1A;KANGXI RADICAL CLIFF;So;0;ON; 5382;;;;N;;;;; 2F1B;KANGXI RADICAL PRIVATE;So;0;ON; 53B6;;;;N;;;;; 2F1C;KANGXI RADICAL AGAIN;So;0;ON; 53C8;;;;N;;;;; 2F1D;KANGXI RADICAL MOUTH;So;0;ON; 53E3;;;;N;;;;; 2F1E;KANGXI RADICAL ENCLOSURE;So;0;ON; 56D7;;;;N;;;;; 2F1F;KANGXI RADICAL EARTH;So;0;ON; 571F;;;;N;;;;; 2F20;KANGXI RADICAL SCHOLAR;So;0;ON; 58EB;;;;N;;;;; 2F21;KANGXI RADICAL GO;So;0;ON; 5902;;;;N;;;;; 2F22;KANGXI RADICAL GO SLOWLY;So;0;ON; 590A;;;;N;;;;; 2F23;KANGXI RADICAL EVENING;So;0;ON; 5915;;;;N;;;;; 2F24;KANGXI RADICAL BIG;So;0;ON; 5927;;;;N;;;;; 2F25;KANGXI RADICAL WOMAN;So;0;ON; 5973;;;;N;;;;; 2F26;KANGXI RADICAL CHILD;So;0;ON; 5B50;;;;N;;;;; 2F27;KANGXI RADICAL ROOF;So;0;ON; 5B80;;;;N;;;;; 2F28;KANGXI RADICAL INCH;So;0;ON; 5BF8;;;;N;;;;; 2F29;KANGXI RADICAL SMALL;So;0;ON; 5C0F;;;;N;;;;; 2F2A;KANGXI RADICAL LAME;So;0;ON; 5C22;;;;N;;;;; 2F2B;KANGXI RADICAL CORPSE;So;0;ON; 5C38;;;;N;;;;; 2F2C;KANGXI RADICAL SPROUT;So;0;ON; 5C6E;;;;N;;;;; 2F2D;KANGXI RADICAL MOUNTAIN;So;0;ON; 5C71;;;;N;;;;; 2F2E;KANGXI RADICAL RIVER;So;0;ON; 5DDB;;;;N;;;;; 2F2F;KANGXI RADICAL WORK;So;0;ON; 5DE5;;;;N;;;;; 2F30;KANGXI RADICAL ONESELF;So;0;ON; 5DF1;;;;N;;;;; 2F31;KANGXI RADICAL TURBAN;So;0;ON; 5DFE;;;;N;;;;; 2F32;KANGXI RADICAL DRY;So;0;ON; 5E72;;;;N;;;;; 2F33;KANGXI RADICAL SHORT THREAD;So;0;ON; 5E7A;;;;N;;;;; 2F34;KANGXI RADICAL DOTTED CLIFF;So;0;ON; 5E7F;;;;N;;;;; 2F35;KANGXI RADICAL LONG STRIDE;So;0;ON; 5EF4;;;;N;;;;; 2F36;KANGXI RADICAL TWO HANDS;So;0;ON; 5EFE;;;;N;;;;; 2F37;KANGXI RADICAL SHOOT;So;0;ON; 5F0B;;;;N;;;;; 2F38;KANGXI RADICAL BOW;So;0;ON; 5F13;;;;N;;;;; 2F39;KANGXI RADICAL SNOUT;So;0;ON; 5F50;;;;N;;;;; 2F3A;KANGXI RADICAL BRISTLE;So;0;ON; 5F61;;;;N;;;;; 2F3B;KANGXI RADICAL STEP;So;0;ON; 5F73;;;;N;;;;; 2F3C;KANGXI RADICAL HEART;So;0;ON; 5FC3;;;;N;;;;; 2F3D;KANGXI RADICAL HALBERD;So;0;ON; 6208;;;;N;;;;; 2F3E;KANGXI RADICAL DOOR;So;0;ON; 6236;;;;N;;;;; 2F3F;KANGXI RADICAL HAND;So;0;ON; 624B;;;;N;;;;; 2F40;KANGXI RADICAL BRANCH;So;0;ON; 652F;;;;N;;;;; 2F41;KANGXI RADICAL RAP;So;0;ON; 6534;;;;N;;;;; 2F42;KANGXI RADICAL SCRIPT;So;0;ON; 6587;;;;N;;;;; 2F43;KANGXI RADICAL DIPPER;So;0;ON; 6597;;;;N;;;;; 2F44;KANGXI RADICAL AXE;So;0;ON; 65A4;;;;N;;;;; 2F45;KANGXI RADICAL SQUARE;So;0;ON; 65B9;;;;N;;;;; 2F46;KANGXI RADICAL NOT;So;0;ON; 65E0;;;;N;;;;; 2F47;KANGXI RADICAL SUN;So;0;ON; 65E5;;;;N;;;;; 2F48;KANGXI RADICAL SAY;So;0;ON; 66F0;;;;N;;;;; 2F49;KANGXI RADICAL MOON;So;0;ON; 6708;;;;N;;;;; 2F4A;KANGXI RADICAL TREE;So;0;ON; 6728;;;;N;;;;; 2F4B;KANGXI RADICAL LACK;So;0;ON; 6B20;;;;N;;;;; 2F4C;KANGXI RADICAL STOP;So;0;ON; 6B62;;;;N;;;;; 2F4D;KANGXI RADICAL DEATH;So;0;ON; 6B79;;;;N;;;;; 2F4E;KANGXI RADICAL WEAPON;So;0;ON; 6BB3;;;;N;;;;; 2F4F;KANGXI RADICAL DO NOT;So;0;ON; 6BCB;;;;N;;;;; 2F50;KANGXI RADICAL COMPARE;So;0;ON; 6BD4;;;;N;;;;; 2F51;KANGXI RADICAL FUR;So;0;ON; 6BDB;;;;N;;;;; 2F52;KANGXI RADICAL CLAN;So;0;ON; 6C0F;;;;N;;;;; 2F53;KANGXI RADICAL STEAM;So;0;ON; 6C14;;;;N;;;;; 2F54;KANGXI RADICAL WATER;So;0;ON; 6C34;;;;N;;;;; 2F55;KANGXI RADICAL FIRE;So;0;ON; 706B;;;;N;;;;; 2F56;KANGXI RADICAL CLAW;So;0;ON; 722A;;;;N;;;;; 2F57;KANGXI RADICAL FATHER;So;0;ON; 7236;;;;N;;;;; 2F58;KANGXI RADICAL DOUBLE X;So;0;ON; 723B;;;;N;;;;; 2F59;KANGXI RADICAL HALF TREE TRUNK;So;0;ON; 723F;;;;N;;;;; 2F5A;KANGXI RADICAL SLICE;So;0;ON; 7247;;;;N;;;;; 2F5B;KANGXI RADICAL FANG;So;0;ON; 7259;;;;N;;;;; 2F5C;KANGXI RADICAL COW;So;0;ON; 725B;;;;N;;;;; 2F5D;KANGXI RADICAL DOG;So;0;ON; 72AC;;;;N;;;;; 2F5E;KANGXI RADICAL PROFOUND;So;0;ON; 7384;;;;N;;;;; 2F5F;KANGXI RADICAL JADE;So;0;ON; 7389;;;;N;;;;; 2F60;KANGXI RADICAL MELON;So;0;ON; 74DC;;;;N;;;;; 2F61;KANGXI RADICAL TILE;So;0;ON; 74E6;;;;N;;;;; 2F62;KANGXI RADICAL SWEET;So;0;ON; 7518;;;;N;;;;; 2F63;KANGXI RADICAL LIFE;So;0;ON; 751F;;;;N;;;;; 2F64;KANGXI RADICAL USE;So;0;ON; 7528;;;;N;;;;; 2F65;KANGXI RADICAL FIELD;So;0;ON; 7530;;;;N;;;;; 2F66;KANGXI RADICAL BOLT OF CLOTH;So;0;ON; 758B;;;;N;;;;; 2F67;KANGXI RADICAL SICKNESS;So;0;ON; 7592;;;;N;;;;; 2F68;KANGXI RADICAL DOTTED TENT;So;0;ON; 7676;;;;N;;;;; 2F69;KANGXI RADICAL WHITE;So;0;ON; 767D;;;;N;;;;; 2F6A;KANGXI RADICAL SKIN;So;0;ON; 76AE;;;;N;;;;; 2F6B;KANGXI RADICAL DISH;So;0;ON; 76BF;;;;N;;;;; 2F6C;KANGXI RADICAL EYE;So;0;ON; 76EE;;;;N;;;;; 2F6D;KANGXI RADICAL SPEAR;So;0;ON; 77DB;;;;N;;;;; 2F6E;KANGXI RADICAL ARROW;So;0;ON; 77E2;;;;N;;;;; 2F6F;KANGXI RADICAL STONE;So;0;ON; 77F3;;;;N;;;;; 2F70;KANGXI RADICAL SPIRIT;So;0;ON; 793A;;;;N;;;;; 2F71;KANGXI RADICAL TRACK;So;0;ON; 79B8;;;;N;;;;; 2F72;KANGXI RADICAL GRAIN;So;0;ON; 79BE;;;;N;;;;; 2F73;KANGXI RADICAL CAVE;So;0;ON; 7A74;;;;N;;;;; 2F74;KANGXI RADICAL STAND;So;0;ON; 7ACB;;;;N;;;;; 2F75;KANGXI RADICAL BAMBOO;So;0;ON; 7AF9;;;;N;;;;; 2F76;KANGXI RADICAL RICE;So;0;ON; 7C73;;;;N;;;;; 2F77;KANGXI RADICAL SILK;So;0;ON; 7CF8;;;;N;;;;; 2F78;KANGXI RADICAL JAR;So;0;ON; 7F36;;;;N;;;;; 2F79;KANGXI RADICAL NET;So;0;ON; 7F51;;;;N;;;;; 2F7A;KANGXI RADICAL SHEEP;So;0;ON; 7F8A;;;;N;;;;; 2F7B;KANGXI RADICAL FEATHER;So;0;ON; 7FBD;;;;N;;;;; 2F7C;KANGXI RADICAL OLD;So;0;ON; 8001;;;;N;;;;; 2F7D;KANGXI RADICAL AND;So;0;ON; 800C;;;;N;;;;; 2F7E;KANGXI RADICAL PLOW;So;0;ON; 8012;;;;N;;;;; 2F7F;KANGXI RADICAL EAR;So;0;ON; 8033;;;;N;;;;; 2F80;KANGXI RADICAL BRUSH;So;0;ON; 807F;;;;N;;;;; 2F81;KANGXI RADICAL MEAT;So;0;ON; 8089;;;;N;;;;; 2F82;KANGXI RADICAL MINISTER;So;0;ON; 81E3;;;;N;;;;; 2F83;KANGXI RADICAL SELF;So;0;ON; 81EA;;;;N;;;;; 2F84;KANGXI RADICAL ARRIVE;So;0;ON; 81F3;;;;N;;;;; 2F85;KANGXI RADICAL MORTAR;So;0;ON; 81FC;;;;N;;;;; 2F86;KANGXI RADICAL TONGUE;So;0;ON; 820C;;;;N;;;;; 2F87;KANGXI RADICAL OPPOSE;So;0;ON; 821B;;;;N;;;;; 2F88;KANGXI RADICAL BOAT;So;0;ON; 821F;;;;N;;;;; 2F89;KANGXI RADICAL STOPPING;So;0;ON; 826E;;;;N;;;;; 2F8A;KANGXI RADICAL COLOR;So;0;ON; 8272;;;;N;;;;; 2F8B;KANGXI RADICAL GRASS;So;0;ON; 8278;;;;N;;;;; 2F8C;KANGXI RADICAL TIGER;So;0;ON; 864D;;;;N;;;;; 2F8D;KANGXI RADICAL INSECT;So;0;ON; 866B;;;;N;;;;; 2F8E;KANGXI RADICAL BLOOD;So;0;ON; 8840;;;;N;;;;; 2F8F;KANGXI RADICAL WALK ENCLOSURE;So;0;ON; 884C;;;;N;;;;; 2F90;KANGXI RADICAL CLOTHES;So;0;ON; 8863;;;;N;;;;; 2F91;KANGXI RADICAL WEST;So;0;ON; 897E;;;;N;;;;; 2F92;KANGXI RADICAL SEE;So;0;ON; 898B;;;;N;;;;; 2F93;KANGXI RADICAL HORN;So;0;ON; 89D2;;;;N;;;;; 2F94;KANGXI RADICAL SPEECH;So;0;ON; 8A00;;;;N;;;;; 2F95;KANGXI RADICAL VALLEY;So;0;ON; 8C37;;;;N;;;;; 2F96;KANGXI RADICAL BEAN;So;0;ON; 8C46;;;;N;;;;; 2F97;KANGXI RADICAL PIG;So;0;ON; 8C55;;;;N;;;;; 2F98;KANGXI RADICAL BADGER;So;0;ON; 8C78;;;;N;;;;; 2F99;KANGXI RADICAL SHELL;So;0;ON; 8C9D;;;;N;;;;; 2F9A;KANGXI RADICAL RED;So;0;ON; 8D64;;;;N;;;;; 2F9B;KANGXI RADICAL RUN;So;0;ON; 8D70;;;;N;;;;; 2F9C;KANGXI RADICAL FOOT;So;0;ON; 8DB3;;;;N;;;;; 2F9D;KANGXI RADICAL BODY;So;0;ON; 8EAB;;;;N;;;;; 2F9E;KANGXI RADICAL CART;So;0;ON; 8ECA;;;;N;;;;; 2F9F;KANGXI RADICAL BITTER;So;0;ON; 8F9B;;;;N;;;;; 2FA0;KANGXI RADICAL MORNING;So;0;ON; 8FB0;;;;N;;;;; 2FA1;KANGXI RADICAL WALK;So;0;ON; 8FB5;;;;N;;;;; 2FA2;KANGXI RADICAL CITY;So;0;ON; 9091;;;;N;;;;; 2FA3;KANGXI RADICAL WINE;So;0;ON; 9149;;;;N;;;;; 2FA4;KANGXI RADICAL DISTINGUISH;So;0;ON; 91C6;;;;N;;;;; 2FA5;KANGXI RADICAL VILLAGE;So;0;ON; 91CC;;;;N;;;;; 2FA6;KANGXI RADICAL GOLD;So;0;ON; 91D1;;;;N;;;;; 2FA7;KANGXI RADICAL LONG;So;0;ON; 9577;;;;N;;;;; 2FA8;KANGXI RADICAL GATE;So;0;ON; 9580;;;;N;;;;; 2FA9;KANGXI RADICAL MOUND;So;0;ON; 961C;;;;N;;;;; 2FAA;KANGXI RADICAL SLAVE;So;0;ON; 96B6;;;;N;;;;; 2FAB;KANGXI RADICAL SHORT TAILED BIRD;So;0;ON; 96B9;;;;N;;;;; 2FAC;KANGXI RADICAL RAIN;So;0;ON; 96E8;;;;N;;;;; 2FAD;KANGXI RADICAL BLUE;So;0;ON; 9751;;;;N;;;;; 2FAE;KANGXI RADICAL WRONG;So;0;ON; 975E;;;;N;;;;; 2FAF;KANGXI RADICAL FACE;So;0;ON; 9762;;;;N;;;;; 2FB0;KANGXI RADICAL LEATHER;So;0;ON; 9769;;;;N;;;;; 2FB1;KANGXI RADICAL TANNED LEATHER;So;0;ON; 97CB;;;;N;;;;; 2FB2;KANGXI RADICAL LEEK;So;0;ON; 97ED;;;;N;;;;; 2FB3;KANGXI RADICAL SOUND;So;0;ON; 97F3;;;;N;;;;; 2FB4;KANGXI RADICAL LEAF;So;0;ON; 9801;;;;N;;;;; 2FB5;KANGXI RADICAL WIND;So;0;ON; 98A8;;;;N;;;;; 2FB6;KANGXI RADICAL FLY;So;0;ON; 98DB;;;;N;;;;; 2FB7;KANGXI RADICAL EAT;So;0;ON; 98DF;;;;N;;;;; 2FB8;KANGXI RADICAL HEAD;So;0;ON; 9996;;;;N;;;;; 2FB9;KANGXI RADICAL FRAGRANT;So;0;ON; 9999;;;;N;;;;; 2FBA;KANGXI RADICAL HORSE;So;0;ON; 99AC;;;;N;;;;; 2FBB;KANGXI RADICAL BONE;So;0;ON; 9AA8;;;;N;;;;; 2FBC;KANGXI RADICAL TALL;So;0;ON; 9AD8;;;;N;;;;; 2FBD;KANGXI RADICAL HAIR;So;0;ON; 9ADF;;;;N;;;;; 2FBE;KANGXI RADICAL FIGHT;So;0;ON; 9B25;;;;N;;;;; 2FBF;KANGXI RADICAL SACRIFICIAL WINE;So;0;ON; 9B2F;;;;N;;;;; 2FC0;KANGXI RADICAL CAULDRON;So;0;ON; 9B32;;;;N;;;;; 2FC1;KANGXI RADICAL GHOST;So;0;ON; 9B3C;;;;N;;;;; 2FC2;KANGXI RADICAL FISH;So;0;ON; 9B5A;;;;N;;;;; 2FC3;KANGXI RADICAL BIRD;So;0;ON; 9CE5;;;;N;;;;; 2FC4;KANGXI RADICAL SALT;So;0;ON; 9E75;;;;N;;;;; 2FC5;KANGXI RADICAL DEER;So;0;ON; 9E7F;;;;N;;;;; 2FC6;KANGXI RADICAL WHEAT;So;0;ON; 9EA5;;;;N;;;;; 2FC7;KANGXI RADICAL HEMP;So;0;ON; 9EBB;;;;N;;;;; 2FC8;KANGXI RADICAL YELLOW;So;0;ON; 9EC3;;;;N;;;;; 2FC9;KANGXI RADICAL MILLET;So;0;ON; 9ECD;;;;N;;;;; 2FCA;KANGXI RADICAL BLACK;So;0;ON; 9ED1;;;;N;;;;; 2FCB;KANGXI RADICAL EMBROIDERY;So;0;ON; 9EF9;;;;N;;;;; 2FCC;KANGXI RADICAL FROG;So;0;ON; 9EFD;;;;N;;;;; 2FCD;KANGXI RADICAL TRIPOD;So;0;ON; 9F0E;;;;N;;;;; 2FCE;KANGXI RADICAL DRUM;So;0;ON; 9F13;;;;N;;;;; 2FCF;KANGXI RADICAL RAT;So;0;ON; 9F20;;;;N;;;;; 2FD0;KANGXI RADICAL NOSE;So;0;ON; 9F3B;;;;N;;;;; 2FD1;KANGXI RADICAL EVEN;So;0;ON; 9F4A;;;;N;;;;; 2FD2;KANGXI RADICAL TOOTH;So;0;ON; 9F52;;;;N;;;;; 2FD3;KANGXI RADICAL DRAGON;So;0;ON; 9F8D;;;;N;;;;; 2FD4;KANGXI RADICAL TURTLE;So;0;ON; 9F9C;;;;N;;;;; 2FD5;KANGXI RADICAL FLUTE;So;0;ON; 9FA0;;;;N;;;;; 2FF0;IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT;So;0;ON;;;;;N;;;;; 2FF1;IDEOGRAPHIC DESCRIPTION CHARACTER ABOVE TO BELOW;So;0;ON;;;;;N;;;;; 2FF2;IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO MIDDLE AND RIGHT;So;0;ON;;;;;N;;;;; 2FF3;IDEOGRAPHIC DESCRIPTION CHARACTER ABOVE TO MIDDLE AND BELOW;So;0;ON;;;;;N;;;;; 2FF4;IDEOGRAPHIC DESCRIPTION CHARACTER FULL SURROUND;So;0;ON;;;;;N;;;;; 2FF5;IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM ABOVE;So;0;ON;;;;;N;;;;; 2FF6;IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM BELOW;So;0;ON;;;;;N;;;;; 2FF7;IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM LEFT;So;0;ON;;;;;N;;;;; 2FF8;IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM UPPER LEFT;So;0;ON;;;;;N;;;;; 2FF9;IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM UPPER RIGHT;So;0;ON;;;;;N;;;;; 2FFA;IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM LOWER LEFT;So;0;ON;;;;;N;;;;; 2FFB;IDEOGRAPHIC DESCRIPTION CHARACTER OVERLAID;So;0;ON;;;;;N;;;;; 3000;IDEOGRAPHIC SPACE;Zs;0;WS; 0020;;;;N;;;;; 3001;IDEOGRAPHIC COMMA;Po;0;ON;;;;;N;;;;; 3002;IDEOGRAPHIC FULL STOP;Po;0;ON;;;;;N;IDEOGRAPHIC PERIOD;;;; 3003;DITTO MARK;Po;0;ON;;;;;N;;;;; 3004;JAPANESE INDUSTRIAL STANDARD SYMBOL;So;0;ON;;;;;N;;;;; 3005;IDEOGRAPHIC ITERATION MARK;Lm;0;L;;;;;N;;;;; 3006;IDEOGRAPHIC CLOSING MARK;Lo;0;L;;;;;N;;;;; 3007;IDEOGRAPHIC NUMBER ZERO;Nl;0;L;;;;0;N;;;;; 3008;LEFT ANGLE BRACKET;Ps;0;ON;;;;;Y;OPENING ANGLE BRACKET;;;; 3009;RIGHT ANGLE BRACKET;Pe;0;ON;;;;;Y;CLOSING ANGLE BRACKET;;;; 300A;LEFT DOUBLE ANGLE BRACKET;Ps;0;ON;;;;;Y;OPENING DOUBLE ANGLE BRACKET;;;; 300B;RIGHT DOUBLE ANGLE BRACKET;Pe;0;ON;;;;;Y;CLOSING DOUBLE ANGLE BRACKET;;;; 300C;LEFT CORNER BRACKET;Ps;0;ON;;;;;Y;OPENING CORNER BRACKET;;;; 300D;RIGHT CORNER BRACKET;Pe;0;ON;;;;;Y;CLOSING CORNER BRACKET;;;; 300E;LEFT WHITE CORNER BRACKET;Ps;0;ON;;;;;Y;OPENING WHITE CORNER BRACKET;;;; 300F;RIGHT WHITE CORNER BRACKET;Pe;0;ON;;;;;Y;CLOSING WHITE CORNER BRACKET;;;; 3010;LEFT BLACK LENTICULAR BRACKET;Ps;0;ON;;;;;Y;OPENING BLACK LENTICULAR BRACKET;;;; 3011;RIGHT BLACK LENTICULAR BRACKET;Pe;0;ON;;;;;Y;CLOSING BLACK LENTICULAR BRACKET;;;; 3012;POSTAL MARK;So;0;ON;;;;;N;;;;; 3013;GETA MARK;So;0;ON;;;;;N;;;;; 3014;LEFT TORTOISE SHELL BRACKET;Ps;0;ON;;;;;Y;OPENING TORTOISE SHELL BRACKET;;;; 3015;RIGHT TORTOISE SHELL BRACKET;Pe;0;ON;;;;;Y;CLOSING TORTOISE SHELL BRACKET;;;; 3016;LEFT WHITE LENTICULAR BRACKET;Ps;0;ON;;;;;Y;OPENING WHITE LENTICULAR BRACKET;;;; 3017;RIGHT WHITE LENTICULAR BRACKET;Pe;0;ON;;;;;Y;CLOSING WHITE LENTICULAR BRACKET;;;; 3018;LEFT WHITE TORTOISE SHELL BRACKET;Ps;0;ON;;;;;Y;OPENING WHITE TORTOISE SHELL BRACKET;;;; 3019;RIGHT WHITE TORTOISE SHELL BRACKET;Pe;0;ON;;;;;Y;CLOSING WHITE TORTOISE SHELL BRACKET;;;; 301A;LEFT WHITE SQUARE BRACKET;Ps;0;ON;;;;;Y;OPENING WHITE SQUARE BRACKET;;;; 301B;RIGHT WHITE SQUARE BRACKET;Pe;0;ON;;;;;Y;CLOSING WHITE SQUARE BRACKET;;;; 301C;WAVE DASH;Pd;0;ON;;;;;N;;;;; 301D;REVERSED DOUBLE PRIME QUOTATION MARK;Ps;0;ON;;;;;N;;;;; 301E;DOUBLE PRIME QUOTATION MARK;Pe;0;ON;;;;;N;;;;; 301F;LOW DOUBLE PRIME QUOTATION MARK;Pe;0;ON;;;;;N;;;;; 3020;POSTAL MARK FACE;So;0;ON;;;;;N;;;;; 3021;HANGZHOU NUMERAL ONE;Nl;0;L;;;;1;N;;;;; 3022;HANGZHOU NUMERAL TWO;Nl;0;L;;;;2;N;;;;; 3023;HANGZHOU NUMERAL THREE;Nl;0;L;;;;3;N;;;;; 3024;HANGZHOU NUMERAL FOUR;Nl;0;L;;;;4;N;;;;; 3025;HANGZHOU NUMERAL FIVE;Nl;0;L;;;;5;N;;;;; 3026;HANGZHOU NUMERAL SIX;Nl;0;L;;;;6;N;;;;; 3027;HANGZHOU NUMERAL SEVEN;Nl;0;L;;;;7;N;;;;; 3028;HANGZHOU NUMERAL EIGHT;Nl;0;L;;;;8;N;;;;; 3029;HANGZHOU NUMERAL NINE;Nl;0;L;;;;9;N;;;;; 302A;IDEOGRAPHIC LEVEL TONE MARK;Mn;218;NSM;;;;;N;;;;; 302B;IDEOGRAPHIC RISING TONE MARK;Mn;228;NSM;;;;;N;;;;; 302C;IDEOGRAPHIC DEPARTING TONE MARK;Mn;232;NSM;;;;;N;;;;; 302D;IDEOGRAPHIC ENTERING TONE MARK;Mn;222;NSM;;;;;N;;;;; 302E;HANGUL SINGLE DOT TONE MARK;Mc;224;L;;;;;N;;;;; 302F;HANGUL DOUBLE DOT TONE MARK;Mc;224;L;;;;;N;;;;; 3030;WAVY DASH;Pd;0;ON;;;;;N;;;;; 3031;VERTICAL KANA REPEAT MARK;Lm;0;L;;;;;N;;;;; 3032;VERTICAL KANA REPEAT WITH VOICED SOUND MARK;Lm;0;L;;;;;N;;;;; 3033;VERTICAL KANA REPEAT MARK UPPER HALF;Lm;0;L;;;;;N;;;;; 3034;VERTICAL KANA REPEAT WITH VOICED SOUND MARK UPPER HALF;Lm;0;L;;;;;N;;;;; 3035;VERTICAL KANA REPEAT MARK LOWER HALF;Lm;0;L;;;;;N;;;;; 3036;CIRCLED POSTAL MARK;So;0;ON; 3012;;;;N;;;;; 3037;IDEOGRAPHIC TELEGRAPH LINE FEED SEPARATOR SYMBOL;So;0;ON;;;;;N;;;;; 3038;HANGZHOU NUMERAL TEN;Nl;0;L; 5341;;;10;N;;;;; 3039;HANGZHOU NUMERAL TWENTY;Nl;0;L; 5344;;;20;N;;;;; 303A;HANGZHOU NUMERAL THIRTY;Nl;0;L; 5345;;;30;N;;;;; 303B;VERTICAL IDEOGRAPHIC ITERATION MARK;Lm;0;L;;;;;N;;;;; 303C;MASU MARK;Lo;0;L;;;;;N;;;;; 303D;PART ALTERNATION MARK;Po;0;ON;;;;;N;;;;; 303E;IDEOGRAPHIC VARIATION INDICATOR;So;0;ON;;;;;N;;;;; 303F;IDEOGRAPHIC HALF FILL SPACE;So;0;ON;;;;;N;;;;; 3041;HIRAGANA LETTER SMALL A;Lo;0;L;;;;;N;;;;; 3042;HIRAGANA LETTER A;Lo;0;L;;;;;N;;;;; 3043;HIRAGANA LETTER SMALL I;Lo;0;L;;;;;N;;;;; 3044;HIRAGANA LETTER I;Lo;0;L;;;;;N;;;;; 3045;HIRAGANA LETTER SMALL U;Lo;0;L;;;;;N;;;;; 3046;HIRAGANA LETTER U;Lo;0;L;;;;;N;;;;; 3047;HIRAGANA LETTER SMALL E;Lo;0;L;;;;;N;;;;; 3048;HIRAGANA LETTER E;Lo;0;L;;;;;N;;;;; 3049;HIRAGANA LETTER SMALL O;Lo;0;L;;;;;N;;;;; 304A;HIRAGANA LETTER O;Lo;0;L;;;;;N;;;;; 304B;HIRAGANA LETTER KA;Lo;0;L;;;;;N;;;;; 304C;HIRAGANA LETTER GA;Lo;0;L;304B 3099;;;;N;;;;; 304D;HIRAGANA LETTER KI;Lo;0;L;;;;;N;;;;; 304E;HIRAGANA LETTER GI;Lo;0;L;304D 3099;;;;N;;;;; 304F;HIRAGANA LETTER KU;Lo;0;L;;;;;N;;;;; 3050;HIRAGANA LETTER GU;Lo;0;L;304F 3099;;;;N;;;;; 3051;HIRAGANA LETTER KE;Lo;0;L;;;;;N;;;;; 3052;HIRAGANA LETTER GE;Lo;0;L;3051 3099;;;;N;;;;; 3053;HIRAGANA LETTER KO;Lo;0;L;;;;;N;;;;; 3054;HIRAGANA LETTER GO;Lo;0;L;3053 3099;;;;N;;;;; 3055;HIRAGANA LETTER SA;Lo;0;L;;;;;N;;;;; 3056;HIRAGANA LETTER ZA;Lo;0;L;3055 3099;;;;N;;;;; 3057;HIRAGANA LETTER SI;Lo;0;L;;;;;N;;;;; 3058;HIRAGANA LETTER ZI;Lo;0;L;3057 3099;;;;N;;;;; 3059;HIRAGANA LETTER SU;Lo;0;L;;;;;N;;;;; 305A;HIRAGANA LETTER ZU;Lo;0;L;3059 3099;;;;N;;;;; 305B;HIRAGANA LETTER SE;Lo;0;L;;;;;N;;;;; 305C;HIRAGANA LETTER ZE;Lo;0;L;305B 3099;;;;N;;;;; 305D;HIRAGANA LETTER SO;Lo;0;L;;;;;N;;;;; 305E;HIRAGANA LETTER ZO;Lo;0;L;305D 3099;;;;N;;;;; 305F;HIRAGANA LETTER TA;Lo;0;L;;;;;N;;;;; 3060;HIRAGANA LETTER DA;Lo;0;L;305F 3099;;;;N;;;;; 3061;HIRAGANA LETTER TI;Lo;0;L;;;;;N;;;;; 3062;HIRAGANA LETTER DI;Lo;0;L;3061 3099;;;;N;;;;; 3063;HIRAGANA LETTER SMALL TU;Lo;0;L;;;;;N;;;;; 3064;HIRAGANA LETTER TU;Lo;0;L;;;;;N;;;;; 3065;HIRAGANA LETTER DU;Lo;0;L;3064 3099;;;;N;;;;; 3066;HIRAGANA LETTER TE;Lo;0;L;;;;;N;;;;; 3067;HIRAGANA LETTER DE;Lo;0;L;3066 3099;;;;N;;;;; 3068;HIRAGANA LETTER TO;Lo;0;L;;;;;N;;;;; 3069;HIRAGANA LETTER DO;Lo;0;L;3068 3099;;;;N;;;;; 306A;HIRAGANA LETTER NA;Lo;0;L;;;;;N;;;;; 306B;HIRAGANA LETTER NI;Lo;0;L;;;;;N;;;;; 306C;HIRAGANA LETTER NU;Lo;0;L;;;;;N;;;;; 306D;HIRAGANA LETTER NE;Lo;0;L;;;;;N;;;;; 306E;HIRAGANA LETTER NO;Lo;0;L;;;;;N;;;;; 306F;HIRAGANA LETTER HA;Lo;0;L;;;;;N;;;;; 3070;HIRAGANA LETTER BA;Lo;0;L;306F 3099;;;;N;;;;; 3071;HIRAGANA LETTER PA;Lo;0;L;306F 309A;;;;N;;;;; 3072;HIRAGANA LETTER HI;Lo;0;L;;;;;N;;;;; 3073;HIRAGANA LETTER BI;Lo;0;L;3072 3099;;;;N;;;;; 3074;HIRAGANA LETTER PI;Lo;0;L;3072 309A;;;;N;;;;; 3075;HIRAGANA LETTER HU;Lo;0;L;;;;;N;;;;; 3076;HIRAGANA LETTER BU;Lo;0;L;3075 3099;;;;N;;;;; 3077;HIRAGANA LETTER PU;Lo;0;L;3075 309A;;;;N;;;;; 3078;HIRAGANA LETTER HE;Lo;0;L;;;;;N;;;;; 3079;HIRAGANA LETTER BE;Lo;0;L;3078 3099;;;;N;;;;; 307A;HIRAGANA LETTER PE;Lo;0;L;3078 309A;;;;N;;;;; 307B;HIRAGANA LETTER HO;Lo;0;L;;;;;N;;;;; 307C;HIRAGANA LETTER BO;Lo;0;L;307B 3099;;;;N;;;;; 307D;HIRAGANA LETTER PO;Lo;0;L;307B 309A;;;;N;;;;; 307E;HIRAGANA LETTER MA;Lo;0;L;;;;;N;;;;; 307F;HIRAGANA LETTER MI;Lo;0;L;;;;;N;;;;; 3080;HIRAGANA LETTER MU;Lo;0;L;;;;;N;;;;; 3081;HIRAGANA LETTER ME;Lo;0;L;;;;;N;;;;; 3082;HIRAGANA LETTER MO;Lo;0;L;;;;;N;;;;; 3083;HIRAGANA LETTER SMALL YA;Lo;0;L;;;;;N;;;;; 3084;HIRAGANA LETTER YA;Lo;0;L;;;;;N;;;;; 3085;HIRAGANA LETTER SMALL YU;Lo;0;L;;;;;N;;;;; 3086;HIRAGANA LETTER YU;Lo;0;L;;;;;N;;;;; 3087;HIRAGANA LETTER SMALL YO;Lo;0;L;;;;;N;;;;; 3088;HIRAGANA LETTER YO;Lo;0;L;;;;;N;;;;; 3089;HIRAGANA LETTER RA;Lo;0;L;;;;;N;;;;; 308A;HIRAGANA LETTER RI;Lo;0;L;;;;;N;;;;; 308B;HIRAGANA LETTER RU;Lo;0;L;;;;;N;;;;; 308C;HIRAGANA LETTER RE;Lo;0;L;;;;;N;;;;; 308D;HIRAGANA LETTER RO;Lo;0;L;;;;;N;;;;; 308E;HIRAGANA LETTER SMALL WA;Lo;0;L;;;;;N;;;;; 308F;HIRAGANA LETTER WA;Lo;0;L;;;;;N;;;;; 3090;HIRAGANA LETTER WI;Lo;0;L;;;;;N;;;;; 3091;HIRAGANA LETTER WE;Lo;0;L;;;;;N;;;;; 3092;HIRAGANA LETTER WO;Lo;0;L;;;;;N;;;;; 3093;HIRAGANA LETTER N;Lo;0;L;;;;;N;;;;; 3094;HIRAGANA LETTER VU;Lo;0;L;3046 3099;;;;N;;;;; 3095;HIRAGANA LETTER SMALL KA;Lo;0;L;;;;;N;;;;; 3096;HIRAGANA LETTER SMALL KE;Lo;0;L;;;;;N;;;;; 3099;COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK;Mn;8;NSM;;;;;N;NON-SPACING KATAKANA-HIRAGANA VOICED SOUND MARK;;;; 309A;COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK;Mn;8;NSM;;;;;N;NON-SPACING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK;;;; 309B;KATAKANA-HIRAGANA VOICED SOUND MARK;Sk;0;ON; 0020 3099;;;;N;;;;; 309C;KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK;Sk;0;ON; 0020 309A;;;;N;;;;; 309D;HIRAGANA ITERATION MARK;Lm;0;L;;;;;N;;;;; 309E;HIRAGANA VOICED ITERATION MARK;Lm;0;L;309D 3099;;;;N;;;;; 309F;HIRAGANA DIGRAPH YORI;Lo;0;L; 3088 308A;;;;N;;;;; 30A0;KATAKANA-HIRAGANA DOUBLE HYPHEN;Pd;0;ON;;;;;N;;;;; 30A1;KATAKANA LETTER SMALL A;Lo;0;L;;;;;N;;;;; 30A2;KATAKANA LETTER A;Lo;0;L;;;;;N;;;;; 30A3;KATAKANA LETTER SMALL I;Lo;0;L;;;;;N;;;;; 30A4;KATAKANA LETTER I;Lo;0;L;;;;;N;;;;; 30A5;KATAKANA LETTER SMALL U;Lo;0;L;;;;;N;;;;; 30A6;KATAKANA LETTER U;Lo;0;L;;;;;N;;;;; 30A7;KATAKANA LETTER SMALL E;Lo;0;L;;;;;N;;;;; 30A8;KATAKANA LETTER E;Lo;0;L;;;;;N;;;;; 30A9;KATAKANA LETTER SMALL O;Lo;0;L;;;;;N;;;;; 30AA;KATAKANA LETTER O;Lo;0;L;;;;;N;;;;; 30AB;KATAKANA LETTER KA;Lo;0;L;;;;;N;;;;; 30AC;KATAKANA LETTER GA;Lo;0;L;30AB 3099;;;;N;;;;; 30AD;KATAKANA LETTER KI;Lo;0;L;;;;;N;;;;; 30AE;KATAKANA LETTER GI;Lo;0;L;30AD 3099;;;;N;;;;; 30AF;KATAKANA LETTER KU;Lo;0;L;;;;;N;;;;; 30B0;KATAKANA LETTER GU;Lo;0;L;30AF 3099;;;;N;;;;; 30B1;KATAKANA LETTER KE;Lo;0;L;;;;;N;;;;; 30B2;KATAKANA LETTER GE;Lo;0;L;30B1 3099;;;;N;;;;; 30B3;KATAKANA LETTER KO;Lo;0;L;;;;;N;;;;; 30B4;KATAKANA LETTER GO;Lo;0;L;30B3 3099;;;;N;;;;; 30B5;KATAKANA LETTER SA;Lo;0;L;;;;;N;;;;; 30B6;KATAKANA LETTER ZA;Lo;0;L;30B5 3099;;;;N;;;;; 30B7;KATAKANA LETTER SI;Lo;0;L;;;;;N;;;;; 30B8;KATAKANA LETTER ZI;Lo;0;L;30B7 3099;;;;N;;;;; 30B9;KATAKANA LETTER SU;Lo;0;L;;;;;N;;;;; 30BA;KATAKANA LETTER ZU;Lo;0;L;30B9 3099;;;;N;;;;; 30BB;KATAKANA LETTER SE;Lo;0;L;;;;;N;;;;; 30BC;KATAKANA LETTER ZE;Lo;0;L;30BB 3099;;;;N;;;;; 30BD;KATAKANA LETTER SO;Lo;0;L;;;;;N;;;;; 30BE;KATAKANA LETTER ZO;Lo;0;L;30BD 3099;;;;N;;;;; 30BF;KATAKANA LETTER TA;Lo;0;L;;;;;N;;;;; 30C0;KATAKANA LETTER DA;Lo;0;L;30BF 3099;;;;N;;;;; 30C1;KATAKANA LETTER TI;Lo;0;L;;;;;N;;;;; 30C2;KATAKANA LETTER DI;Lo;0;L;30C1 3099;;;;N;;;;; 30C3;KATAKANA LETTER SMALL TU;Lo;0;L;;;;;N;;;;; 30C4;KATAKANA LETTER TU;Lo;0;L;;;;;N;;;;; 30C5;KATAKANA LETTER DU;Lo;0;L;30C4 3099;;;;N;;;;; 30C6;KATAKANA LETTER TE;Lo;0;L;;;;;N;;;;; 30C7;KATAKANA LETTER DE;Lo;0;L;30C6 3099;;;;N;;;;; 30C8;KATAKANA LETTER TO;Lo;0;L;;;;;N;;;;; 30C9;KATAKANA LETTER DO;Lo;0;L;30C8 3099;;;;N;;;;; 30CA;KATAKANA LETTER NA;Lo;0;L;;;;;N;;;;; 30CB;KATAKANA LETTER NI;Lo;0;L;;;;;N;;;;; 30CC;KATAKANA LETTER NU;Lo;0;L;;;;;N;;;;; 30CD;KATAKANA LETTER NE;Lo;0;L;;;;;N;;;;; 30CE;KATAKANA LETTER NO;Lo;0;L;;;;;N;;;;; 30CF;KATAKANA LETTER HA;Lo;0;L;;;;;N;;;;; 30D0;KATAKANA LETTER BA;Lo;0;L;30CF 3099;;;;N;;;;; 30D1;KATAKANA LETTER PA;Lo;0;L;30CF 309A;;;;N;;;;; 30D2;KATAKANA LETTER HI;Lo;0;L;;;;;N;;;;; 30D3;KATAKANA LETTER BI;Lo;0;L;30D2 3099;;;;N;;;;; 30D4;KATAKANA LETTER PI;Lo;0;L;30D2 309A;;;;N;;;;; 30D5;KATAKANA LETTER HU;Lo;0;L;;;;;N;;;;; 30D6;KATAKANA LETTER BU;Lo;0;L;30D5 3099;;;;N;;;;; 30D7;KATAKANA LETTER PU;Lo;0;L;30D5 309A;;;;N;;;;; 30D8;KATAKANA LETTER HE;Lo;0;L;;;;;N;;;;; 30D9;KATAKANA LETTER BE;Lo;0;L;30D8 3099;;;;N;;;;; 30DA;KATAKANA LETTER PE;Lo;0;L;30D8 309A;;;;N;;;;; 30DB;KATAKANA LETTER HO;Lo;0;L;;;;;N;;;;; 30DC;KATAKANA LETTER BO;Lo;0;L;30DB 3099;;;;N;;;;; 30DD;KATAKANA LETTER PO;Lo;0;L;30DB 309A;;;;N;;;;; 30DE;KATAKANA LETTER MA;Lo;0;L;;;;;N;;;;; 30DF;KATAKANA LETTER MI;Lo;0;L;;;;;N;;;;; 30E0;KATAKANA LETTER MU;Lo;0;L;;;;;N;;;;; 30E1;KATAKANA LETTER ME;Lo;0;L;;;;;N;;;;; 30E2;KATAKANA LETTER MO;Lo;0;L;;;;;N;;;;; 30E3;KATAKANA LETTER SMALL YA;Lo;0;L;;;;;N;;;;; 30E4;KATAKANA LETTER YA;Lo;0;L;;;;;N;;;;; 30E5;KATAKANA LETTER SMALL YU;Lo;0;L;;;;;N;;;;; 30E6;KATAKANA LETTER YU;Lo;0;L;;;;;N;;;;; 30E7;KATAKANA LETTER SMALL YO;Lo;0;L;;;;;N;;;;; 30E8;KATAKANA LETTER YO;Lo;0;L;;;;;N;;;;; 30E9;KATAKANA LETTER RA;Lo;0;L;;;;;N;;;;; 30EA;KATAKANA LETTER RI;Lo;0;L;;;;;N;;;;; 30EB;KATAKANA LETTER RU;Lo;0;L;;;;;N;;;;; 30EC;KATAKANA LETTER RE;Lo;0;L;;;;;N;;;;; 30ED;KATAKANA LETTER RO;Lo;0;L;;;;;N;;;;; 30EE;KATAKANA LETTER SMALL WA;Lo;0;L;;;;;N;;;;; 30EF;KATAKANA LETTER WA;Lo;0;L;;;;;N;;;;; 30F0;KATAKANA LETTER WI;Lo;0;L;;;;;N;;;;; 30F1;KATAKANA LETTER WE;Lo;0;L;;;;;N;;;;; 30F2;KATAKANA LETTER WO;Lo;0;L;;;;;N;;;;; 30F3;KATAKANA LETTER N;Lo;0;L;;;;;N;;;;; 30F4;KATAKANA LETTER VU;Lo;0;L;30A6 3099;;;;N;;;;; 30F5;KATAKANA LETTER SMALL KA;Lo;0;L;;;;;N;;;;; 30F6;KATAKANA LETTER SMALL KE;Lo;0;L;;;;;N;;;;; 30F7;KATAKANA LETTER VA;Lo;0;L;30EF 3099;;;;N;;;;; 30F8;KATAKANA LETTER VI;Lo;0;L;30F0 3099;;;;N;;;;; 30F9;KATAKANA LETTER VE;Lo;0;L;30F1 3099;;;;N;;;;; 30FA;KATAKANA LETTER VO;Lo;0;L;30F2 3099;;;;N;;;;; 30FB;KATAKANA MIDDLE DOT;Po;0;ON;;;;;N;;;;; 30FC;KATAKANA-HIRAGANA PROLONGED SOUND MARK;Lm;0;L;;;;;N;;;;; 30FD;KATAKANA ITERATION MARK;Lm;0;L;;;;;N;;;;; 30FE;KATAKANA VOICED ITERATION MARK;Lm;0;L;30FD 3099;;;;N;;;;; 30FF;KATAKANA DIGRAPH KOTO;Lo;0;L; 30B3 30C8;;;;N;;;;; 3105;BOPOMOFO LETTER B;Lo;0;L;;;;;N;;;;; 3106;BOPOMOFO LETTER P;Lo;0;L;;;;;N;;;;; 3107;BOPOMOFO LETTER M;Lo;0;L;;;;;N;;;;; 3108;BOPOMOFO LETTER F;Lo;0;L;;;;;N;;;;; 3109;BOPOMOFO LETTER D;Lo;0;L;;;;;N;;;;; 310A;BOPOMOFO LETTER T;Lo;0;L;;;;;N;;;;; 310B;BOPOMOFO LETTER N;Lo;0;L;;;;;N;;;;; 310C;BOPOMOFO LETTER L;Lo;0;L;;;;;N;;;;; 310D;BOPOMOFO LETTER G;Lo;0;L;;;;;N;;;;; 310E;BOPOMOFO LETTER K;Lo;0;L;;;;;N;;;;; 310F;BOPOMOFO LETTER H;Lo;0;L;;;;;N;;;;; 3110;BOPOMOFO LETTER J;Lo;0;L;;;;;N;;;;; 3111;BOPOMOFO LETTER Q;Lo;0;L;;;;;N;;;;; 3112;BOPOMOFO LETTER X;Lo;0;L;;;;;N;;;;; 3113;BOPOMOFO LETTER ZH;Lo;0;L;;;;;N;;;;; 3114;BOPOMOFO LETTER CH;Lo;0;L;;;;;N;;;;; 3115;BOPOMOFO LETTER SH;Lo;0;L;;;;;N;;;;; 3116;BOPOMOFO LETTER R;Lo;0;L;;;;;N;;;;; 3117;BOPOMOFO LETTER Z;Lo;0;L;;;;;N;;;;; 3118;BOPOMOFO LETTER C;Lo;0;L;;;;;N;;;;; 3119;BOPOMOFO LETTER S;Lo;0;L;;;;;N;;;;; 311A;BOPOMOFO LETTER A;Lo;0;L;;;;;N;;;;; 311B;BOPOMOFO LETTER O;Lo;0;L;;;;;N;;;;; 311C;BOPOMOFO LETTER E;Lo;0;L;;;;;N;;;;; 311D;BOPOMOFO LETTER EH;Lo;0;L;;;;;N;;;;; 311E;BOPOMOFO LETTER AI;Lo;0;L;;;;;N;;;;; 311F;BOPOMOFO LETTER EI;Lo;0;L;;;;;N;;;;; 3120;BOPOMOFO LETTER AU;Lo;0;L;;;;;N;;;;; 3121;BOPOMOFO LETTER OU;Lo;0;L;;;;;N;;;;; 3122;BOPOMOFO LETTER AN;Lo;0;L;;;;;N;;;;; 3123;BOPOMOFO LETTER EN;Lo;0;L;;;;;N;;;;; 3124;BOPOMOFO LETTER ANG;Lo;0;L;;;;;N;;;;; 3125;BOPOMOFO LETTER ENG;Lo;0;L;;;;;N;;;;; 3126;BOPOMOFO LETTER ER;Lo;0;L;;;;;N;;;;; 3127;BOPOMOFO LETTER I;Lo;0;L;;;;;N;;;;; 3128;BOPOMOFO LETTER U;Lo;0;L;;;;;N;;;;; 3129;BOPOMOFO LETTER IU;Lo;0;L;;;;;N;;;;; 312A;BOPOMOFO LETTER V;Lo;0;L;;;;;N;;;;; 312B;BOPOMOFO LETTER NG;Lo;0;L;;;;;N;;;;; 312C;BOPOMOFO LETTER GN;Lo;0;L;;;;;N;;;;; 312D;BOPOMOFO LETTER IH;Lo;0;L;;;;;N;;;;; 3131;HANGUL LETTER KIYEOK;Lo;0;L; 1100;;;;N;HANGUL LETTER GIYEOG;;;; 3132;HANGUL LETTER SSANGKIYEOK;Lo;0;L; 1101;;;;N;HANGUL LETTER SSANG GIYEOG;;;; 3133;HANGUL LETTER KIYEOK-SIOS;Lo;0;L; 11AA;;;;N;HANGUL LETTER GIYEOG SIOS;;;; 3134;HANGUL LETTER NIEUN;Lo;0;L; 1102;;;;N;;;;; 3135;HANGUL LETTER NIEUN-CIEUC;Lo;0;L; 11AC;;;;N;HANGUL LETTER NIEUN JIEUJ;;;; 3136;HANGUL LETTER NIEUN-HIEUH;Lo;0;L; 11AD;;;;N;HANGUL LETTER NIEUN HIEUH;;;; 3137;HANGUL LETTER TIKEUT;Lo;0;L; 1103;;;;N;HANGUL LETTER DIGEUD;;;; 3138;HANGUL LETTER SSANGTIKEUT;Lo;0;L; 1104;;;;N;HANGUL LETTER SSANG DIGEUD;;;; 3139;HANGUL LETTER RIEUL;Lo;0;L; 1105;;;;N;HANGUL LETTER LIEUL;;;; 313A;HANGUL LETTER RIEUL-KIYEOK;Lo;0;L; 11B0;;;;N;HANGUL LETTER LIEUL GIYEOG;;;; 313B;HANGUL LETTER RIEUL-MIEUM;Lo;0;L; 11B1;;;;N;HANGUL LETTER LIEUL MIEUM;;;; 313C;HANGUL LETTER RIEUL-PIEUP;Lo;0;L; 11B2;;;;N;HANGUL LETTER LIEUL BIEUB;;;; 313D;HANGUL LETTER RIEUL-SIOS;Lo;0;L; 11B3;;;;N;HANGUL LETTER LIEUL SIOS;;;; 313E;HANGUL LETTER RIEUL-THIEUTH;Lo;0;L; 11B4;;;;N;HANGUL LETTER LIEUL TIEUT;;;; 313F;HANGUL LETTER RIEUL-PHIEUPH;Lo;0;L; 11B5;;;;N;HANGUL LETTER LIEUL PIEUP;;;; 3140;HANGUL LETTER RIEUL-HIEUH;Lo;0;L; 111A;;;;N;HANGUL LETTER LIEUL HIEUH;;;; 3141;HANGUL LETTER MIEUM;Lo;0;L; 1106;;;;N;;;;; 3142;HANGUL LETTER PIEUP;Lo;0;L; 1107;;;;N;HANGUL LETTER BIEUB;;;; 3143;HANGUL LETTER SSANGPIEUP;Lo;0;L; 1108;;;;N;HANGUL LETTER SSANG BIEUB;;;; 3144;HANGUL LETTER PIEUP-SIOS;Lo;0;L; 1121;;;;N;HANGUL LETTER BIEUB SIOS;;;; 3145;HANGUL LETTER SIOS;Lo;0;L; 1109;;;;N;;;;; 3146;HANGUL LETTER SSANGSIOS;Lo;0;L; 110A;;;;N;HANGUL LETTER SSANG SIOS;;;; 3147;HANGUL LETTER IEUNG;Lo;0;L; 110B;;;;N;;;;; 3148;HANGUL LETTER CIEUC;Lo;0;L; 110C;;;;N;HANGUL LETTER JIEUJ;;;; 3149;HANGUL LETTER SSANGCIEUC;Lo;0;L; 110D;;;;N;HANGUL LETTER SSANG JIEUJ;;;; 314A;HANGUL LETTER CHIEUCH;Lo;0;L; 110E;;;;N;HANGUL LETTER CIEUC;;;; 314B;HANGUL LETTER KHIEUKH;Lo;0;L; 110F;;;;N;HANGUL LETTER KIYEOK;;;; 314C;HANGUL LETTER THIEUTH;Lo;0;L; 1110;;;;N;HANGUL LETTER TIEUT;;;; 314D;HANGUL LETTER PHIEUPH;Lo;0;L; 1111;;;;N;HANGUL LETTER PIEUP;;;; 314E;HANGUL LETTER HIEUH;Lo;0;L; 1112;;;;N;;;;; 314F;HANGUL LETTER A;Lo;0;L; 1161;;;;N;;;;; 3150;HANGUL LETTER AE;Lo;0;L; 1162;;;;N;;;;; 3151;HANGUL LETTER YA;Lo;0;L; 1163;;;;N;;;;; 3152;HANGUL LETTER YAE;Lo;0;L; 1164;;;;N;;;;; 3153;HANGUL LETTER EO;Lo;0;L; 1165;;;;N;;;;; 3154;HANGUL LETTER E;Lo;0;L; 1166;;;;N;;;;; 3155;HANGUL LETTER YEO;Lo;0;L; 1167;;;;N;;;;; 3156;HANGUL LETTER YE;Lo;0;L; 1168;;;;N;;;;; 3157;HANGUL LETTER O;Lo;0;L; 1169;;;;N;;;;; 3158;HANGUL LETTER WA;Lo;0;L; 116A;;;;N;;;;; 3159;HANGUL LETTER WAE;Lo;0;L; 116B;;;;N;;;;; 315A;HANGUL LETTER OE;Lo;0;L; 116C;;;;N;;;;; 315B;HANGUL LETTER YO;Lo;0;L; 116D;;;;N;;;;; 315C;HANGUL LETTER U;Lo;0;L; 116E;;;;N;;;;; 315D;HANGUL LETTER WEO;Lo;0;L; 116F;;;;N;;;;; 315E;HANGUL LETTER WE;Lo;0;L; 1170;;;;N;;;;; 315F;HANGUL LETTER WI;Lo;0;L; 1171;;;;N;;;;; 3160;HANGUL LETTER YU;Lo;0;L; 1172;;;;N;;;;; 3161;HANGUL LETTER EU;Lo;0;L; 1173;;;;N;;;;; 3162;HANGUL LETTER YI;Lo;0;L; 1174;;;;N;;;;; 3163;HANGUL LETTER I;Lo;0;L; 1175;;;;N;;;;; 3164;HANGUL FILLER;Lo;0;L; 1160;;;;N;HANGUL CAE OM;;;; 3165;HANGUL LETTER SSANGNIEUN;Lo;0;L; 1114;;;;N;HANGUL LETTER SSANG NIEUN;;;; 3166;HANGUL LETTER NIEUN-TIKEUT;Lo;0;L; 1115;;;;N;HANGUL LETTER NIEUN DIGEUD;;;; 3167;HANGUL LETTER NIEUN-SIOS;Lo;0;L; 11C7;;;;N;HANGUL LETTER NIEUN SIOS;;;; 3168;HANGUL LETTER NIEUN-PANSIOS;Lo;0;L; 11C8;;;;N;HANGUL LETTER NIEUN BAN CHI EUM;;;; 3169;HANGUL LETTER RIEUL-KIYEOK-SIOS;Lo;0;L; 11CC;;;;N;HANGUL LETTER LIEUL GIYEOG SIOS;;;; 316A;HANGUL LETTER RIEUL-TIKEUT;Lo;0;L; 11CE;;;;N;HANGUL LETTER LIEUL DIGEUD;;;; 316B;HANGUL LETTER RIEUL-PIEUP-SIOS;Lo;0;L; 11D3;;;;N;HANGUL LETTER LIEUL BIEUB SIOS;;;; 316C;HANGUL LETTER RIEUL-PANSIOS;Lo;0;L; 11D7;;;;N;HANGUL LETTER LIEUL BAN CHI EUM;;;; 316D;HANGUL LETTER RIEUL-YEORINHIEUH;Lo;0;L; 11D9;;;;N;HANGUL LETTER LIEUL YEOLIN HIEUH;;;; 316E;HANGUL LETTER MIEUM-PIEUP;Lo;0;L; 111C;;;;N;HANGUL LETTER MIEUM BIEUB;;;; 316F;HANGUL LETTER MIEUM-SIOS;Lo;0;L; 11DD;;;;N;HANGUL LETTER MIEUM SIOS;;;; 3170;HANGUL LETTER MIEUM-PANSIOS;Lo;0;L; 11DF;;;;N;HANGUL LETTER BIEUB BAN CHI EUM;;;; 3171;HANGUL LETTER KAPYEOUNMIEUM;Lo;0;L; 111D;;;;N;HANGUL LETTER MIEUM SUN GYEONG EUM;;;; 3172;HANGUL LETTER PIEUP-KIYEOK;Lo;0;L; 111E;;;;N;HANGUL LETTER BIEUB GIYEOG;;;; 3173;HANGUL LETTER PIEUP-TIKEUT;Lo;0;L; 1120;;;;N;HANGUL LETTER BIEUB DIGEUD;;;; 3174;HANGUL LETTER PIEUP-SIOS-KIYEOK;Lo;0;L; 1122;;;;N;HANGUL LETTER BIEUB SIOS GIYEOG;;;; 3175;HANGUL LETTER PIEUP-SIOS-TIKEUT;Lo;0;L; 1123;;;;N;HANGUL LETTER BIEUB SIOS DIGEUD;;;; 3176;HANGUL LETTER PIEUP-CIEUC;Lo;0;L; 1127;;;;N;HANGUL LETTER BIEUB JIEUJ;;;; 3177;HANGUL LETTER PIEUP-THIEUTH;Lo;0;L; 1129;;;;N;HANGUL LETTER BIEUB TIEUT;;;; 3178;HANGUL LETTER KAPYEOUNPIEUP;Lo;0;L; 112B;;;;N;HANGUL LETTER BIEUB SUN GYEONG EUM;;;; 3179;HANGUL LETTER KAPYEOUNSSANGPIEUP;Lo;0;L; 112C;;;;N;HANGUL LETTER SSANG BIEUB SUN GYEONG EUM;;;; 317A;HANGUL LETTER SIOS-KIYEOK;Lo;0;L; 112D;;;;N;HANGUL LETTER SIOS GIYEOG;;;; 317B;HANGUL LETTER SIOS-NIEUN;Lo;0;L; 112E;;;;N;HANGUL LETTER SIOS NIEUN;;;; 317C;HANGUL LETTER SIOS-TIKEUT;Lo;0;L; 112F;;;;N;HANGUL LETTER SIOS DIGEUD;;;; 317D;HANGUL LETTER SIOS-PIEUP;Lo;0;L; 1132;;;;N;HANGUL LETTER SIOS BIEUB;;;; 317E;HANGUL LETTER SIOS-CIEUC;Lo;0;L; 1136;;;;N;HANGUL LETTER SIOS JIEUJ;;;; 317F;HANGUL LETTER PANSIOS;Lo;0;L; 1140;;;;N;HANGUL LETTER BAN CHI EUM;;;; 3180;HANGUL LETTER SSANGIEUNG;Lo;0;L; 1147;;;;N;HANGUL LETTER SSANG IEUNG;;;; 3181;HANGUL LETTER YESIEUNG;Lo;0;L; 114C;;;;N;HANGUL LETTER NGIEUNG;;;; 3182;HANGUL LETTER YESIEUNG-SIOS;Lo;0;L; 11F1;;;;N;HANGUL LETTER NGIEUNG SIOS;;;; 3183;HANGUL LETTER YESIEUNG-PANSIOS;Lo;0;L; 11F2;;;;N;HANGUL LETTER NGIEUNG BAN CHI EUM;;;; 3184;HANGUL LETTER KAPYEOUNPHIEUPH;Lo;0;L; 1157;;;;N;HANGUL LETTER PIEUP SUN GYEONG EUM;;;; 3185;HANGUL LETTER SSANGHIEUH;Lo;0;L; 1158;;;;N;HANGUL LETTER SSANG HIEUH;;;; 3186;HANGUL LETTER YEORINHIEUH;Lo;0;L; 1159;;;;N;HANGUL LETTER YEOLIN HIEUH;;;; 3187;HANGUL LETTER YO-YA;Lo;0;L; 1184;;;;N;HANGUL LETTER YOYA;;;; 3188;HANGUL LETTER YO-YAE;Lo;0;L; 1185;;;;N;HANGUL LETTER YOYAE;;;; 3189;HANGUL LETTER YO-I;Lo;0;L; 1188;;;;N;HANGUL LETTER YOI;;;; 318A;HANGUL LETTER YU-YEO;Lo;0;L; 1191;;;;N;HANGUL LETTER YUYEO;;;; 318B;HANGUL LETTER YU-YE;Lo;0;L; 1192;;;;N;HANGUL LETTER YUYE;;;; 318C;HANGUL LETTER YU-I;Lo;0;L; 1194;;;;N;HANGUL LETTER YUI;;;; 318D;HANGUL LETTER ARAEA;Lo;0;L; 119E;;;;N;HANGUL LETTER ALAE A;;;; 318E;HANGUL LETTER ARAEAE;Lo;0;L; 11A1;;;;N;HANGUL LETTER ALAE AE;;;; 3190;IDEOGRAPHIC ANNOTATION LINKING MARK;So;0;L;;;;;N;KANBUN TATETEN;;;; 3191;IDEOGRAPHIC ANNOTATION REVERSE MARK;So;0;L;;;;;N;KAERITEN RE;;;; 3192;IDEOGRAPHIC ANNOTATION ONE MARK;No;0;L; 4E00;;;1;N;KAERITEN ITI;;;; 3193;IDEOGRAPHIC ANNOTATION TWO MARK;No;0;L; 4E8C;;;2;N;KAERITEN NI;;;; 3194;IDEOGRAPHIC ANNOTATION THREE MARK;No;0;L; 4E09;;;3;N;KAERITEN SAN;;;; 3195;IDEOGRAPHIC ANNOTATION FOUR MARK;No;0;L; 56DB;;;4;N;KAERITEN SI;;;; 3196;IDEOGRAPHIC ANNOTATION TOP MARK;So;0;L; 4E0A;;;;N;KAERITEN ZYOU;;;; 3197;IDEOGRAPHIC ANNOTATION MIDDLE MARK;So;0;L; 4E2D;;;;N;KAERITEN TYUU;;;; 3198;IDEOGRAPHIC ANNOTATION BOTTOM MARK;So;0;L; 4E0B;;;;N;KAERITEN GE;;;; 3199;IDEOGRAPHIC ANNOTATION FIRST MARK;So;0;L; 7532;;;;N;KAERITEN KOU;;;; 319A;IDEOGRAPHIC ANNOTATION SECOND MARK;So;0;L; 4E59;;;;N;KAERITEN OTU;;;; 319B;IDEOGRAPHIC ANNOTATION THIRD MARK;So;0;L; 4E19;;;;N;KAERITEN HEI;;;; 319C;IDEOGRAPHIC ANNOTATION FOURTH MARK;So;0;L; 4E01;;;;N;KAERITEN TEI;;;; 319D;IDEOGRAPHIC ANNOTATION HEAVEN MARK;So;0;L; 5929;;;;N;KAERITEN TEN;;;; 319E;IDEOGRAPHIC ANNOTATION EARTH MARK;So;0;L; 5730;;;;N;KAERITEN TI;;;; 319F;IDEOGRAPHIC ANNOTATION MAN MARK;So;0;L; 4EBA;;;;N;KAERITEN ZIN;;;; 31A0;BOPOMOFO LETTER BU;Lo;0;L;;;;;N;;;;; 31A1;BOPOMOFO LETTER ZI;Lo;0;L;;;;;N;;;;; 31A2;BOPOMOFO LETTER JI;Lo;0;L;;;;;N;;;;; 31A3;BOPOMOFO LETTER GU;Lo;0;L;;;;;N;;;;; 31A4;BOPOMOFO LETTER EE;Lo;0;L;;;;;N;;;;; 31A5;BOPOMOFO LETTER ENN;Lo;0;L;;;;;N;;;;; 31A6;BOPOMOFO LETTER OO;Lo;0;L;;;;;N;;;;; 31A7;BOPOMOFO LETTER ONN;Lo;0;L;;;;;N;;;;; 31A8;BOPOMOFO LETTER IR;Lo;0;L;;;;;N;;;;; 31A9;BOPOMOFO LETTER ANN;Lo;0;L;;;;;N;;;;; 31AA;BOPOMOFO LETTER INN;Lo;0;L;;;;;N;;;;; 31AB;BOPOMOFO LETTER UNN;Lo;0;L;;;;;N;;;;; 31AC;BOPOMOFO LETTER IM;Lo;0;L;;;;;N;;;;; 31AD;BOPOMOFO LETTER NGG;Lo;0;L;;;;;N;;;;; 31AE;BOPOMOFO LETTER AINN;Lo;0;L;;;;;N;;;;; 31AF;BOPOMOFO LETTER AUNN;Lo;0;L;;;;;N;;;;; 31B0;BOPOMOFO LETTER AM;Lo;0;L;;;;;N;;;;; 31B1;BOPOMOFO LETTER OM;Lo;0;L;;;;;N;;;;; 31B2;BOPOMOFO LETTER ONG;Lo;0;L;;;;;N;;;;; 31B3;BOPOMOFO LETTER INNN;Lo;0;L;;;;;N;;;;; 31B4;BOPOMOFO FINAL LETTER P;Lo;0;L;;;;;N;;;;; 31B5;BOPOMOFO FINAL LETTER T;Lo;0;L;;;;;N;;;;; 31B6;BOPOMOFO FINAL LETTER K;Lo;0;L;;;;;N;;;;; 31B7;BOPOMOFO FINAL LETTER H;Lo;0;L;;;;;N;;;;; 31B8;BOPOMOFO LETTER GH;Lo;0;L;;;;;N;;;;; 31B9;BOPOMOFO LETTER LH;Lo;0;L;;;;;N;;;;; 31BA;BOPOMOFO LETTER ZY;Lo;0;L;;;;;N;;;;; 31C0;CJK STROKE T;So;0;ON;;;;;N;;;;; 31C1;CJK STROKE WG;So;0;ON;;;;;N;;;;; 31C2;CJK STROKE XG;So;0;ON;;;;;N;;;;; 31C3;CJK STROKE BXG;So;0;ON;;;;;N;;;;; 31C4;CJK STROKE SW;So;0;ON;;;;;N;;;;; 31C5;CJK STROKE HZZ;So;0;ON;;;;;N;;;;; 31C6;CJK STROKE HZG;So;0;ON;;;;;N;;;;; 31C7;CJK STROKE HP;So;0;ON;;;;;N;;;;; 31C8;CJK STROKE HZWG;So;0;ON;;;;;N;;;;; 31C9;CJK STROKE SZWG;So;0;ON;;;;;N;;;;; 31CA;CJK STROKE HZT;So;0;ON;;;;;N;;;;; 31CB;CJK STROKE HZZP;So;0;ON;;;;;N;;;;; 31CC;CJK STROKE HPWG;So;0;ON;;;;;N;;;;; 31CD;CJK STROKE HZW;So;0;ON;;;;;N;;;;; 31CE;CJK STROKE HZZZ;So;0;ON;;;;;N;;;;; 31CF;CJK STROKE N;So;0;ON;;;;;N;;;;; 31D0;CJK STROKE H;So;0;ON;;;;;N;;;;; 31D1;CJK STROKE S;So;0;ON;;;;;N;;;;; 31D2;CJK STROKE P;So;0;ON;;;;;N;;;;; 31D3;CJK STROKE SP;So;0;ON;;;;;N;;;;; 31D4;CJK STROKE D;So;0;ON;;;;;N;;;;; 31D5;CJK STROKE HZ;So;0;ON;;;;;N;;;;; 31D6;CJK STROKE HG;So;0;ON;;;;;N;;;;; 31D7;CJK STROKE SZ;So;0;ON;;;;;N;;;;; 31D8;CJK STROKE SWZ;So;0;ON;;;;;N;;;;; 31D9;CJK STROKE ST;So;0;ON;;;;;N;;;;; 31DA;CJK STROKE SG;So;0;ON;;;;;N;;;;; 31DB;CJK STROKE PD;So;0;ON;;;;;N;;;;; 31DC;CJK STROKE PZ;So;0;ON;;;;;N;;;;; 31DD;CJK STROKE TN;So;0;ON;;;;;N;;;;; 31DE;CJK STROKE SZZ;So;0;ON;;;;;N;;;;; 31DF;CJK STROKE SWG;So;0;ON;;;;;N;;;;; 31E0;CJK STROKE HXWG;So;0;ON;;;;;N;;;;; 31E1;CJK STROKE HZZZG;So;0;ON;;;;;N;;;;; 31E2;CJK STROKE PG;So;0;ON;;;;;N;;;;; 31E3;CJK STROKE Q;So;0;ON;;;;;N;;;;; 31F0;KATAKANA LETTER SMALL KU;Lo;0;L;;;;;N;;;;; 31F1;KATAKANA LETTER SMALL SI;Lo;0;L;;;;;N;;;;; 31F2;KATAKANA LETTER SMALL SU;Lo;0;L;;;;;N;;;;; 31F3;KATAKANA LETTER SMALL TO;Lo;0;L;;;;;N;;;;; 31F4;KATAKANA LETTER SMALL NU;Lo;0;L;;;;;N;;;;; 31F5;KATAKANA LETTER SMALL HA;Lo;0;L;;;;;N;;;;; 31F6;KATAKANA LETTER SMALL HI;Lo;0;L;;;;;N;;;;; 31F7;KATAKANA LETTER SMALL HU;Lo;0;L;;;;;N;;;;; 31F8;KATAKANA LETTER SMALL HE;Lo;0;L;;;;;N;;;;; 31F9;KATAKANA LETTER SMALL HO;Lo;0;L;;;;;N;;;;; 31FA;KATAKANA LETTER SMALL MU;Lo;0;L;;;;;N;;;;; 31FB;KATAKANA LETTER SMALL RA;Lo;0;L;;;;;N;;;;; 31FC;KATAKANA LETTER SMALL RI;Lo;0;L;;;;;N;;;;; 31FD;KATAKANA LETTER SMALL RU;Lo;0;L;;;;;N;;;;; 31FE;KATAKANA LETTER SMALL RE;Lo;0;L;;;;;N;;;;; 31FF;KATAKANA LETTER SMALL RO;Lo;0;L;;;;;N;;;;; 3200;PARENTHESIZED HANGUL KIYEOK;So;0;L; 0028 1100 0029;;;;N;PARENTHESIZED HANGUL GIYEOG;;;; 3201;PARENTHESIZED HANGUL NIEUN;So;0;L; 0028 1102 0029;;;;N;;;;; 3202;PARENTHESIZED HANGUL TIKEUT;So;0;L; 0028 1103 0029;;;;N;PARENTHESIZED HANGUL DIGEUD;;;; 3203;PARENTHESIZED HANGUL RIEUL;So;0;L; 0028 1105 0029;;;;N;PARENTHESIZED HANGUL LIEUL;;;; 3204;PARENTHESIZED HANGUL MIEUM;So;0;L; 0028 1106 0029;;;;N;;;;; 3205;PARENTHESIZED HANGUL PIEUP;So;0;L; 0028 1107 0029;;;;N;PARENTHESIZED HANGUL BIEUB;;;; 3206;PARENTHESIZED HANGUL SIOS;So;0;L; 0028 1109 0029;;;;N;;;;; 3207;PARENTHESIZED HANGUL IEUNG;So;0;L; 0028 110B 0029;;;;N;;;;; 3208;PARENTHESIZED HANGUL CIEUC;So;0;L; 0028 110C 0029;;;;N;PARENTHESIZED HANGUL JIEUJ;;;; 3209;PARENTHESIZED HANGUL CHIEUCH;So;0;L; 0028 110E 0029;;;;N;PARENTHESIZED HANGUL CIEUC;;;; 320A;PARENTHESIZED HANGUL KHIEUKH;So;0;L; 0028 110F 0029;;;;N;PARENTHESIZED HANGUL KIYEOK;;;; 320B;PARENTHESIZED HANGUL THIEUTH;So;0;L; 0028 1110 0029;;;;N;PARENTHESIZED HANGUL TIEUT;;;; 320C;PARENTHESIZED HANGUL PHIEUPH;So;0;L; 0028 1111 0029;;;;N;PARENTHESIZED HANGUL PIEUP;;;; 320D;PARENTHESIZED HANGUL HIEUH;So;0;L; 0028 1112 0029;;;;N;;;;; 320E;PARENTHESIZED HANGUL KIYEOK A;So;0;L; 0028 1100 1161 0029;;;;N;PARENTHESIZED HANGUL GA;;;; 320F;PARENTHESIZED HANGUL NIEUN A;So;0;L; 0028 1102 1161 0029;;;;N;PARENTHESIZED HANGUL NA;;;; 3210;PARENTHESIZED HANGUL TIKEUT A;So;0;L; 0028 1103 1161 0029;;;;N;PARENTHESIZED HANGUL DA;;;; 3211;PARENTHESIZED HANGUL RIEUL A;So;0;L; 0028 1105 1161 0029;;;;N;PARENTHESIZED HANGUL LA;;;; 3212;PARENTHESIZED HANGUL MIEUM A;So;0;L; 0028 1106 1161 0029;;;;N;PARENTHESIZED HANGUL MA;;;; 3213;PARENTHESIZED HANGUL PIEUP A;So;0;L; 0028 1107 1161 0029;;;;N;PARENTHESIZED HANGUL BA;;;; 3214;PARENTHESIZED HANGUL SIOS A;So;0;L; 0028 1109 1161 0029;;;;N;PARENTHESIZED HANGUL SA;;;; 3215;PARENTHESIZED HANGUL IEUNG A;So;0;L; 0028 110B 1161 0029;;;;N;PARENTHESIZED HANGUL A;;;; 3216;PARENTHESIZED HANGUL CIEUC A;So;0;L; 0028 110C 1161 0029;;;;N;PARENTHESIZED HANGUL JA;;;; 3217;PARENTHESIZED HANGUL CHIEUCH A;So;0;L; 0028 110E 1161 0029;;;;N;PARENTHESIZED HANGUL CA;;;; 3218;PARENTHESIZED HANGUL KHIEUKH A;So;0;L; 0028 110F 1161 0029;;;;N;PARENTHESIZED HANGUL KA;;;; 3219;PARENTHESIZED HANGUL THIEUTH A;So;0;L; 0028 1110 1161 0029;;;;N;PARENTHESIZED HANGUL TA;;;; 321A;PARENTHESIZED HANGUL PHIEUPH A;So;0;L; 0028 1111 1161 0029;;;;N;PARENTHESIZED HANGUL PA;;;; 321B;PARENTHESIZED HANGUL HIEUH A;So;0;L; 0028 1112 1161 0029;;;;N;PARENTHESIZED HANGUL HA;;;; 321C;PARENTHESIZED HANGUL CIEUC U;So;0;L; 0028 110C 116E 0029;;;;N;PARENTHESIZED HANGUL JU;;;; 321D;PARENTHESIZED KOREAN CHARACTER OJEON;So;0;ON; 0028 110B 1169 110C 1165 11AB 0029;;;;N;;;;; 321E;PARENTHESIZED KOREAN CHARACTER O HU;So;0;ON; 0028 110B 1169 1112 116E 0029;;;;N;;;;; 3220;PARENTHESIZED IDEOGRAPH ONE;No;0;L; 0028 4E00 0029;;;1;N;;;;; 3221;PARENTHESIZED IDEOGRAPH TWO;No;0;L; 0028 4E8C 0029;;;2;N;;;;; 3222;PARENTHESIZED IDEOGRAPH THREE;No;0;L; 0028 4E09 0029;;;3;N;;;;; 3223;PARENTHESIZED IDEOGRAPH FOUR;No;0;L; 0028 56DB 0029;;;4;N;;;;; 3224;PARENTHESIZED IDEOGRAPH FIVE;No;0;L; 0028 4E94 0029;;;5;N;;;;; 3225;PARENTHESIZED IDEOGRAPH SIX;No;0;L; 0028 516D 0029;;;6;N;;;;; 3226;PARENTHESIZED IDEOGRAPH SEVEN;No;0;L; 0028 4E03 0029;;;7;N;;;;; 3227;PARENTHESIZED IDEOGRAPH EIGHT;No;0;L; 0028 516B 0029;;;8;N;;;;; 3228;PARENTHESIZED IDEOGRAPH NINE;No;0;L; 0028 4E5D 0029;;;9;N;;;;; 3229;PARENTHESIZED IDEOGRAPH TEN;No;0;L; 0028 5341 0029;;;10;N;;;;; 322A;PARENTHESIZED IDEOGRAPH MOON;So;0;L; 0028 6708 0029;;;;N;;;;; 322B;PARENTHESIZED IDEOGRAPH FIRE;So;0;L; 0028 706B 0029;;;;N;;;;; 322C;PARENTHESIZED IDEOGRAPH WATER;So;0;L; 0028 6C34 0029;;;;N;;;;; 322D;PARENTHESIZED IDEOGRAPH WOOD;So;0;L; 0028 6728 0029;;;;N;;;;; 322E;PARENTHESIZED IDEOGRAPH METAL;So;0;L; 0028 91D1 0029;;;;N;;;;; 322F;PARENTHESIZED IDEOGRAPH EARTH;So;0;L; 0028 571F 0029;;;;N;;;;; 3230;PARENTHESIZED IDEOGRAPH SUN;So;0;L; 0028 65E5 0029;;;;N;;;;; 3231;PARENTHESIZED IDEOGRAPH STOCK;So;0;L; 0028 682A 0029;;;;N;;;;; 3232;PARENTHESIZED IDEOGRAPH HAVE;So;0;L; 0028 6709 0029;;;;N;;;;; 3233;PARENTHESIZED IDEOGRAPH SOCIETY;So;0;L; 0028 793E 0029;;;;N;;;;; 3234;PARENTHESIZED IDEOGRAPH NAME;So;0;L; 0028 540D 0029;;;;N;;;;; 3235;PARENTHESIZED IDEOGRAPH SPECIAL;So;0;L; 0028 7279 0029;;;;N;;;;; 3236;PARENTHESIZED IDEOGRAPH FINANCIAL;So;0;L; 0028 8CA1 0029;;;;N;;;;; 3237;PARENTHESIZED IDEOGRAPH CONGRATULATION;So;0;L; 0028 795D 0029;;;;N;;;;; 3238;PARENTHESIZED IDEOGRAPH LABOR;So;0;L; 0028 52B4 0029;;;;N;;;;; 3239;PARENTHESIZED IDEOGRAPH REPRESENT;So;0;L; 0028 4EE3 0029;;;;N;;;;; 323A;PARENTHESIZED IDEOGRAPH CALL;So;0;L; 0028 547C 0029;;;;N;;;;; 323B;PARENTHESIZED IDEOGRAPH STUDY;So;0;L; 0028 5B66 0029;;;;N;;;;; 323C;PARENTHESIZED IDEOGRAPH SUPERVISE;So;0;L; 0028 76E3 0029;;;;N;;;;; 323D;PARENTHESIZED IDEOGRAPH ENTERPRISE;So;0;L; 0028 4F01 0029;;;;N;;;;; 323E;PARENTHESIZED IDEOGRAPH RESOURCE;So;0;L; 0028 8CC7 0029;;;;N;;;;; 323F;PARENTHESIZED IDEOGRAPH ALLIANCE;So;0;L; 0028 5354 0029;;;;N;;;;; 3240;PARENTHESIZED IDEOGRAPH FESTIVAL;So;0;L; 0028 796D 0029;;;;N;;;;; 3241;PARENTHESIZED IDEOGRAPH REST;So;0;L; 0028 4F11 0029;;;;N;;;;; 3242;PARENTHESIZED IDEOGRAPH SELF;So;0;L; 0028 81EA 0029;;;;N;;;;; 3243;PARENTHESIZED IDEOGRAPH REACH;So;0;L; 0028 81F3 0029;;;;N;;;;; 3244;CIRCLED IDEOGRAPH QUESTION;So;0;L; 554F;;;;N;;;;; 3245;CIRCLED IDEOGRAPH KINDERGARTEN;So;0;L; 5E7C;;;;N;;;;; 3246;CIRCLED IDEOGRAPH SCHOOL;So;0;L; 6587;;;;N;;;;; 3247;CIRCLED IDEOGRAPH KOTO;So;0;L; 7B8F;;;;N;;;;; 3248;CIRCLED NUMBER TEN ON BLACK SQUARE;No;0;L;;;;10;N;;;;; 3249;CIRCLED NUMBER TWENTY ON BLACK SQUARE;No;0;L;;;;20;N;;;;; 324A;CIRCLED NUMBER THIRTY ON BLACK SQUARE;No;0;L;;;;30;N;;;;; 324B;CIRCLED NUMBER FORTY ON BLACK SQUARE;No;0;L;;;;40;N;;;;; 324C;CIRCLED NUMBER FIFTY ON BLACK SQUARE;No;0;L;;;;50;N;;;;; 324D;CIRCLED NUMBER SIXTY ON BLACK SQUARE;No;0;L;;;;60;N;;;;; 324E;CIRCLED NUMBER SEVENTY ON BLACK SQUARE;No;0;L;;;;70;N;;;;; 324F;CIRCLED NUMBER EIGHTY ON BLACK SQUARE;No;0;L;;;;80;N;;;;; 3250;PARTNERSHIP SIGN;So;0;ON; 0050 0054 0045;;;;N;;;;; 3251;CIRCLED NUMBER TWENTY ONE;No;0;ON; 0032 0031;;;21;N;;;;; 3252;CIRCLED NUMBER TWENTY TWO;No;0;ON; 0032 0032;;;22;N;;;;; 3253;CIRCLED NUMBER TWENTY THREE;No;0;ON; 0032 0033;;;23;N;;;;; 3254;CIRCLED NUMBER TWENTY FOUR;No;0;ON; 0032 0034;;;24;N;;;;; 3255;CIRCLED NUMBER TWENTY FIVE;No;0;ON; 0032 0035;;;25;N;;;;; 3256;CIRCLED NUMBER TWENTY SIX;No;0;ON; 0032 0036;;;26;N;;;;; 3257;CIRCLED NUMBER TWENTY SEVEN;No;0;ON; 0032 0037;;;27;N;;;;; 3258;CIRCLED NUMBER TWENTY EIGHT;No;0;ON; 0032 0038;;;28;N;;;;; 3259;CIRCLED NUMBER TWENTY NINE;No;0;ON; 0032 0039;;;29;N;;;;; 325A;CIRCLED NUMBER THIRTY;No;0;ON; 0033 0030;;;30;N;;;;; 325B;CIRCLED NUMBER THIRTY ONE;No;0;ON; 0033 0031;;;31;N;;;;; 325C;CIRCLED NUMBER THIRTY TWO;No;0;ON; 0033 0032;;;32;N;;;;; 325D;CIRCLED NUMBER THIRTY THREE;No;0;ON; 0033 0033;;;33;N;;;;; 325E;CIRCLED NUMBER THIRTY FOUR;No;0;ON; 0033 0034;;;34;N;;;;; 325F;CIRCLED NUMBER THIRTY FIVE;No;0;ON; 0033 0035;;;35;N;;;;; 3260;CIRCLED HANGUL KIYEOK;So;0;L; 1100;;;;N;CIRCLED HANGUL GIYEOG;;;; 3261;CIRCLED HANGUL NIEUN;So;0;L; 1102;;;;N;;;;; 3262;CIRCLED HANGUL TIKEUT;So;0;L; 1103;;;;N;CIRCLED HANGUL DIGEUD;;;; 3263;CIRCLED HANGUL RIEUL;So;0;L; 1105;;;;N;CIRCLED HANGUL LIEUL;;;; 3264;CIRCLED HANGUL MIEUM;So;0;L; 1106;;;;N;;;;; 3265;CIRCLED HANGUL PIEUP;So;0;L; 1107;;;;N;CIRCLED HANGUL BIEUB;;;; 3266;CIRCLED HANGUL SIOS;So;0;L; 1109;;;;N;;;;; 3267;CIRCLED HANGUL IEUNG;So;0;L; 110B;;;;N;;;;; 3268;CIRCLED HANGUL CIEUC;So;0;L; 110C;;;;N;CIRCLED HANGUL JIEUJ;;;; 3269;CIRCLED HANGUL CHIEUCH;So;0;L; 110E;;;;N;CIRCLED HANGUL CIEUC;;;; 326A;CIRCLED HANGUL KHIEUKH;So;0;L; 110F;;;;N;CIRCLED HANGUL KIYEOK;;;; 326B;CIRCLED HANGUL THIEUTH;So;0;L; 1110;;;;N;CIRCLED HANGUL TIEUT;;;; 326C;CIRCLED HANGUL PHIEUPH;So;0;L; 1111;;;;N;CIRCLED HANGUL PIEUP;;;; 326D;CIRCLED HANGUL HIEUH;So;0;L; 1112;;;;N;;;;; 326E;CIRCLED HANGUL KIYEOK A;So;0;L; 1100 1161;;;;N;CIRCLED HANGUL GA;;;; 326F;CIRCLED HANGUL NIEUN A;So;0;L; 1102 1161;;;;N;CIRCLED HANGUL NA;;;; 3270;CIRCLED HANGUL TIKEUT A;So;0;L; 1103 1161;;;;N;CIRCLED HANGUL DA;;;; 3271;CIRCLED HANGUL RIEUL A;So;0;L; 1105 1161;;;;N;CIRCLED HANGUL LA;;;; 3272;CIRCLED HANGUL MIEUM A;So;0;L; 1106 1161;;;;N;CIRCLED HANGUL MA;;;; 3273;CIRCLED HANGUL PIEUP A;So;0;L; 1107 1161;;;;N;CIRCLED HANGUL BA;;;; 3274;CIRCLED HANGUL SIOS A;So;0;L; 1109 1161;;;;N;CIRCLED HANGUL SA;;;; 3275;CIRCLED HANGUL IEUNG A;So;0;L; 110B 1161;;;;N;CIRCLED HANGUL A;;;; 3276;CIRCLED HANGUL CIEUC A;So;0;L; 110C 1161;;;;N;CIRCLED HANGUL JA;;;; 3277;CIRCLED HANGUL CHIEUCH A;So;0;L; 110E 1161;;;;N;CIRCLED HANGUL CA;;;; 3278;CIRCLED HANGUL KHIEUKH A;So;0;L; 110F 1161;;;;N;CIRCLED HANGUL KA;;;; 3279;CIRCLED HANGUL THIEUTH A;So;0;L; 1110 1161;;;;N;CIRCLED HANGUL TA;;;; 327A;CIRCLED HANGUL PHIEUPH A;So;0;L; 1111 1161;;;;N;CIRCLED HANGUL PA;;;; 327B;CIRCLED HANGUL HIEUH A;So;0;L; 1112 1161;;;;N;CIRCLED HANGUL HA;;;; 327C;CIRCLED KOREAN CHARACTER CHAMKO;So;0;ON; 110E 1161 11B7 1100 1169;;;;N;;;;; 327D;CIRCLED KOREAN CHARACTER JUEUI;So;0;ON; 110C 116E 110B 1174;;;;N;;;;; 327E;CIRCLED HANGUL IEUNG U;So;0;ON; 110B 116E;;;;N;;;;; 327F;KOREAN STANDARD SYMBOL;So;0;L;;;;;N;;;;; 3280;CIRCLED IDEOGRAPH ONE;No;0;L; 4E00;;;1;N;;;;; 3281;CIRCLED IDEOGRAPH TWO;No;0;L; 4E8C;;;2;N;;;;; 3282;CIRCLED IDEOGRAPH THREE;No;0;L; 4E09;;;3;N;;;;; 3283;CIRCLED IDEOGRAPH FOUR;No;0;L; 56DB;;;4;N;;;;; 3284;CIRCLED IDEOGRAPH FIVE;No;0;L; 4E94;;;5;N;;;;; 3285;CIRCLED IDEOGRAPH SIX;No;0;L; 516D;;;6;N;;;;; 3286;CIRCLED IDEOGRAPH SEVEN;No;0;L; 4E03;;;7;N;;;;; 3287;CIRCLED IDEOGRAPH EIGHT;No;0;L; 516B;;;8;N;;;;; 3288;CIRCLED IDEOGRAPH NINE;No;0;L; 4E5D;;;9;N;;;;; 3289;CIRCLED IDEOGRAPH TEN;No;0;L; 5341;;;10;N;;;;; 328A;CIRCLED IDEOGRAPH MOON;So;0;L; 6708;;;;N;;;;; 328B;CIRCLED IDEOGRAPH FIRE;So;0;L; 706B;;;;N;;;;; 328C;CIRCLED IDEOGRAPH WATER;So;0;L; 6C34;;;;N;;;;; 328D;CIRCLED IDEOGRAPH WOOD;So;0;L; 6728;;;;N;;;;; 328E;CIRCLED IDEOGRAPH METAL;So;0;L; 91D1;;;;N;;;;; 328F;CIRCLED IDEOGRAPH EARTH;So;0;L; 571F;;;;N;;;;; 3290;CIRCLED IDEOGRAPH SUN;So;0;L; 65E5;;;;N;;;;; 3291;CIRCLED IDEOGRAPH STOCK;So;0;L; 682A;;;;N;;;;; 3292;CIRCLED IDEOGRAPH HAVE;So;0;L; 6709;;;;N;;;;; 3293;CIRCLED IDEOGRAPH SOCIETY;So;0;L; 793E;;;;N;;;;; 3294;CIRCLED IDEOGRAPH NAME;So;0;L; 540D;;;;N;;;;; 3295;CIRCLED IDEOGRAPH SPECIAL;So;0;L; 7279;;;;N;;;;; 3296;CIRCLED IDEOGRAPH FINANCIAL;So;0;L; 8CA1;;;;N;;;;; 3297;CIRCLED IDEOGRAPH CONGRATULATION;So;0;L; 795D;;;;N;;;;; 3298;CIRCLED IDEOGRAPH LABOR;So;0;L; 52B4;;;;N;;;;; 3299;CIRCLED IDEOGRAPH SECRET;So;0;L; 79D8;;;;N;;;;; 329A;CIRCLED IDEOGRAPH MALE;So;0;L; 7537;;;;N;;;;; 329B;CIRCLED IDEOGRAPH FEMALE;So;0;L; 5973;;;;N;;;;; 329C;CIRCLED IDEOGRAPH SUITABLE;So;0;L; 9069;;;;N;;;;; 329D;CIRCLED IDEOGRAPH EXCELLENT;So;0;L; 512A;;;;N;;;;; 329E;CIRCLED IDEOGRAPH PRINT;So;0;L; 5370;;;;N;;;;; 329F;CIRCLED IDEOGRAPH ATTENTION;So;0;L; 6CE8;;;;N;;;;; 32A0;CIRCLED IDEOGRAPH ITEM;So;0;L; 9805;;;;N;;;;; 32A1;CIRCLED IDEOGRAPH REST;So;0;L; 4F11;;;;N;;;;; 32A2;CIRCLED IDEOGRAPH COPY;So;0;L; 5199;;;;N;;;;; 32A3;CIRCLED IDEOGRAPH CORRECT;So;0;L; 6B63;;;;N;;;;; 32A4;CIRCLED IDEOGRAPH HIGH;So;0;L; 4E0A;;;;N;;;;; 32A5;CIRCLED IDEOGRAPH CENTRE;So;0;L; 4E2D;;;;N;CIRCLED IDEOGRAPH CENTER;;;; 32A6;CIRCLED IDEOGRAPH LOW;So;0;L; 4E0B;;;;N;;;;; 32A7;CIRCLED IDEOGRAPH LEFT;So;0;L; 5DE6;;;;N;;;;; 32A8;CIRCLED IDEOGRAPH RIGHT;So;0;L; 53F3;;;;N;;;;; 32A9;CIRCLED IDEOGRAPH MEDICINE;So;0;L; 533B;;;;N;;;;; 32AA;CIRCLED IDEOGRAPH RELIGION;So;0;L; 5B97;;;;N;;;;; 32AB;CIRCLED IDEOGRAPH STUDY;So;0;L; 5B66;;;;N;;;;; 32AC;CIRCLED IDEOGRAPH SUPERVISE;So;0;L; 76E3;;;;N;;;;; 32AD;CIRCLED IDEOGRAPH ENTERPRISE;So;0;L; 4F01;;;;N;;;;; 32AE;CIRCLED IDEOGRAPH RESOURCE;So;0;L; 8CC7;;;;N;;;;; 32AF;CIRCLED IDEOGRAPH ALLIANCE;So;0;L; 5354;;;;N;;;;; 32B0;CIRCLED IDEOGRAPH NIGHT;So;0;L; 591C;;;;N;;;;; 32B1;CIRCLED NUMBER THIRTY SIX;No;0;ON; 0033 0036;;;36;N;;;;; 32B2;CIRCLED NUMBER THIRTY SEVEN;No;0;ON; 0033 0037;;;37;N;;;;; 32B3;CIRCLED NUMBER THIRTY EIGHT;No;0;ON; 0033 0038;;;38;N;;;;; 32B4;CIRCLED NUMBER THIRTY NINE;No;0;ON; 0033 0039;;;39;N;;;;; 32B5;CIRCLED NUMBER FORTY;No;0;ON; 0034 0030;;;40;N;;;;; 32B6;CIRCLED NUMBER FORTY ONE;No;0;ON; 0034 0031;;;41;N;;;;; 32B7;CIRCLED NUMBER FORTY TWO;No;0;ON; 0034 0032;;;42;N;;;;; 32B8;CIRCLED NUMBER FORTY THREE;No;0;ON; 0034 0033;;;43;N;;;;; 32B9;CIRCLED NUMBER FORTY FOUR;No;0;ON; 0034 0034;;;44;N;;;;; 32BA;CIRCLED NUMBER FORTY FIVE;No;0;ON; 0034 0035;;;45;N;;;;; 32BB;CIRCLED NUMBER FORTY SIX;No;0;ON; 0034 0036;;;46;N;;;;; 32BC;CIRCLED NUMBER FORTY SEVEN;No;0;ON; 0034 0037;;;47;N;;;;; 32BD;CIRCLED NUMBER FORTY EIGHT;No;0;ON; 0034 0038;;;48;N;;;;; 32BE;CIRCLED NUMBER FORTY NINE;No;0;ON; 0034 0039;;;49;N;;;;; 32BF;CIRCLED NUMBER FIFTY;No;0;ON; 0035 0030;;;50;N;;;;; 32C0;IDEOGRAPHIC TELEGRAPH SYMBOL FOR JANUARY;So;0;L; 0031 6708;;;;N;;;;; 32C1;IDEOGRAPHIC TELEGRAPH SYMBOL FOR FEBRUARY;So;0;L; 0032 6708;;;;N;;;;; 32C2;IDEOGRAPHIC TELEGRAPH SYMBOL FOR MARCH;So;0;L; 0033 6708;;;;N;;;;; 32C3;IDEOGRAPHIC TELEGRAPH SYMBOL FOR APRIL;So;0;L; 0034 6708;;;;N;;;;; 32C4;IDEOGRAPHIC TELEGRAPH SYMBOL FOR MAY;So;0;L; 0035 6708;;;;N;;;;; 32C5;IDEOGRAPHIC TELEGRAPH SYMBOL FOR JUNE;So;0;L; 0036 6708;;;;N;;;;; 32C6;IDEOGRAPHIC TELEGRAPH SYMBOL FOR JULY;So;0;L; 0037 6708;;;;N;;;;; 32C7;IDEOGRAPHIC TELEGRAPH SYMBOL FOR AUGUST;So;0;L; 0038 6708;;;;N;;;;; 32C8;IDEOGRAPHIC TELEGRAPH SYMBOL FOR SEPTEMBER;So;0;L; 0039 6708;;;;N;;;;; 32C9;IDEOGRAPHIC TELEGRAPH SYMBOL FOR OCTOBER;So;0;L; 0031 0030 6708;;;;N;;;;; 32CA;IDEOGRAPHIC TELEGRAPH SYMBOL FOR NOVEMBER;So;0;L; 0031 0031 6708;;;;N;;;;; 32CB;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DECEMBER;So;0;L; 0031 0032 6708;;;;N;;;;; 32CC;SQUARE HG;So;0;ON; 0048 0067;;;;N;;;;; 32CD;SQUARE ERG;So;0;ON; 0065 0072 0067;;;;N;;;;; 32CE;SQUARE EV;So;0;ON; 0065 0056;;;;N;;;;; 32CF;LIMITED LIABILITY SIGN;So;0;ON; 004C 0054 0044;;;;N;;;;; 32D0;CIRCLED KATAKANA A;So;0;L; 30A2;;;;N;;;;; 32D1;CIRCLED KATAKANA I;So;0;L; 30A4;;;;N;;;;; 32D2;CIRCLED KATAKANA U;So;0;L; 30A6;;;;N;;;;; 32D3;CIRCLED KATAKANA E;So;0;L; 30A8;;;;N;;;;; 32D4;CIRCLED KATAKANA O;So;0;L; 30AA;;;;N;;;;; 32D5;CIRCLED KATAKANA KA;So;0;L; 30AB;;;;N;;;;; 32D6;CIRCLED KATAKANA KI;So;0;L; 30AD;;;;N;;;;; 32D7;CIRCLED KATAKANA KU;So;0;L; 30AF;;;;N;;;;; 32D8;CIRCLED KATAKANA KE;So;0;L; 30B1;;;;N;;;;; 32D9;CIRCLED KATAKANA KO;So;0;L; 30B3;;;;N;;;;; 32DA;CIRCLED KATAKANA SA;So;0;L; 30B5;;;;N;;;;; 32DB;CIRCLED KATAKANA SI;So;0;L; 30B7;;;;N;;;;; 32DC;CIRCLED KATAKANA SU;So;0;L; 30B9;;;;N;;;;; 32DD;CIRCLED KATAKANA SE;So;0;L; 30BB;;;;N;;;;; 32DE;CIRCLED KATAKANA SO;So;0;L; 30BD;;;;N;;;;; 32DF;CIRCLED KATAKANA TA;So;0;L; 30BF;;;;N;;;;; 32E0;CIRCLED KATAKANA TI;So;0;L; 30C1;;;;N;;;;; 32E1;CIRCLED KATAKANA TU;So;0;L; 30C4;;;;N;;;;; 32E2;CIRCLED KATAKANA TE;So;0;L; 30C6;;;;N;;;;; 32E3;CIRCLED KATAKANA TO;So;0;L; 30C8;;;;N;;;;; 32E4;CIRCLED KATAKANA NA;So;0;L; 30CA;;;;N;;;;; 32E5;CIRCLED KATAKANA NI;So;0;L; 30CB;;;;N;;;;; 32E6;CIRCLED KATAKANA NU;So;0;L; 30CC;;;;N;;;;; 32E7;CIRCLED KATAKANA NE;So;0;L; 30CD;;;;N;;;;; 32E8;CIRCLED KATAKANA NO;So;0;L; 30CE;;;;N;;;;; 32E9;CIRCLED KATAKANA HA;So;0;L; 30CF;;;;N;;;;; 32EA;CIRCLED KATAKANA HI;So;0;L; 30D2;;;;N;;;;; 32EB;CIRCLED KATAKANA HU;So;0;L; 30D5;;;;N;;;;; 32EC;CIRCLED KATAKANA HE;So;0;L; 30D8;;;;N;;;;; 32ED;CIRCLED KATAKANA HO;So;0;L; 30DB;;;;N;;;;; 32EE;CIRCLED KATAKANA MA;So;0;L; 30DE;;;;N;;;;; 32EF;CIRCLED KATAKANA MI;So;0;L; 30DF;;;;N;;;;; 32F0;CIRCLED KATAKANA MU;So;0;L; 30E0;;;;N;;;;; 32F1;CIRCLED KATAKANA ME;So;0;L; 30E1;;;;N;;;;; 32F2;CIRCLED KATAKANA MO;So;0;L; 30E2;;;;N;;;;; 32F3;CIRCLED KATAKANA YA;So;0;L; 30E4;;;;N;;;;; 32F4;CIRCLED KATAKANA YU;So;0;L; 30E6;;;;N;;;;; 32F5;CIRCLED KATAKANA YO;So;0;L; 30E8;;;;N;;;;; 32F6;CIRCLED KATAKANA RA;So;0;L; 30E9;;;;N;;;;; 32F7;CIRCLED KATAKANA RI;So;0;L; 30EA;;;;N;;;;; 32F8;CIRCLED KATAKANA RU;So;0;L; 30EB;;;;N;;;;; 32F9;CIRCLED KATAKANA RE;So;0;L; 30EC;;;;N;;;;; 32FA;CIRCLED KATAKANA RO;So;0;L; 30ED;;;;N;;;;; 32FB;CIRCLED KATAKANA WA;So;0;L; 30EF;;;;N;;;;; 32FC;CIRCLED KATAKANA WI;So;0;L; 30F0;;;;N;;;;; 32FD;CIRCLED KATAKANA WE;So;0;L; 30F1;;;;N;;;;; 32FE;CIRCLED KATAKANA WO;So;0;L; 30F2;;;;N;;;;; 3300;SQUARE APAATO;So;0;L; 30A2 30D1 30FC 30C8;;;;N;SQUARED APAATO;;;; 3301;SQUARE ARUHUA;So;0;L; 30A2 30EB 30D5 30A1;;;;N;SQUARED ARUHUA;;;; 3302;SQUARE ANPEA;So;0;L; 30A2 30F3 30DA 30A2;;;;N;SQUARED ANPEA;;;; 3303;SQUARE AARU;So;0;L; 30A2 30FC 30EB;;;;N;SQUARED AARU;;;; 3304;SQUARE ININGU;So;0;L; 30A4 30CB 30F3 30B0;;;;N;SQUARED ININGU;;;; 3305;SQUARE INTI;So;0;L; 30A4 30F3 30C1;;;;N;SQUARED INTI;;;; 3306;SQUARE UON;So;0;L; 30A6 30A9 30F3;;;;N;SQUARED UON;;;; 3307;SQUARE ESUKUUDO;So;0;L; 30A8 30B9 30AF 30FC 30C9;;;;N;SQUARED ESUKUUDO;;;; 3308;SQUARE EEKAA;So;0;L; 30A8 30FC 30AB 30FC;;;;N;SQUARED EEKAA;;;; 3309;SQUARE ONSU;So;0;L; 30AA 30F3 30B9;;;;N;SQUARED ONSU;;;; 330A;SQUARE OOMU;So;0;L; 30AA 30FC 30E0;;;;N;SQUARED OOMU;;;; 330B;SQUARE KAIRI;So;0;L; 30AB 30A4 30EA;;;;N;SQUARED KAIRI;;;; 330C;SQUARE KARATTO;So;0;L; 30AB 30E9 30C3 30C8;;;;N;SQUARED KARATTO;;;; 330D;SQUARE KARORII;So;0;L; 30AB 30ED 30EA 30FC;;;;N;SQUARED KARORII;;;; 330E;SQUARE GARON;So;0;L; 30AC 30ED 30F3;;;;N;SQUARED GARON;;;; 330F;SQUARE GANMA;So;0;L; 30AC 30F3 30DE;;;;N;SQUARED GANMA;;;; 3310;SQUARE GIGA;So;0;L; 30AE 30AC;;;;N;SQUARED GIGA;;;; 3311;SQUARE GINII;So;0;L; 30AE 30CB 30FC;;;;N;SQUARED GINII;;;; 3312;SQUARE KYURII;So;0;L; 30AD 30E5 30EA 30FC;;;;N;SQUARED KYURII;;;; 3313;SQUARE GIRUDAA;So;0;L; 30AE 30EB 30C0 30FC;;;;N;SQUARED GIRUDAA;;;; 3314;SQUARE KIRO;So;0;L; 30AD 30ED;;;;N;SQUARED KIRO;;;; 3315;SQUARE KIROGURAMU;So;0;L; 30AD 30ED 30B0 30E9 30E0;;;;N;SQUARED KIROGURAMU;;;; 3316;SQUARE KIROMEETORU;So;0;L; 30AD 30ED 30E1 30FC 30C8 30EB;;;;N;SQUARED KIROMEETORU;;;; 3317;SQUARE KIROWATTO;So;0;L; 30AD 30ED 30EF 30C3 30C8;;;;N;SQUARED KIROWATTO;;;; 3318;SQUARE GURAMU;So;0;L; 30B0 30E9 30E0;;;;N;SQUARED GURAMU;;;; 3319;SQUARE GURAMUTON;So;0;L; 30B0 30E9 30E0 30C8 30F3;;;;N;SQUARED GURAMUTON;;;; 331A;SQUARE KURUZEIRO;So;0;L; 30AF 30EB 30BC 30A4 30ED;;;;N;SQUARED KURUZEIRO;;;; 331B;SQUARE KUROONE;So;0;L; 30AF 30ED 30FC 30CD;;;;N;SQUARED KUROONE;;;; 331C;SQUARE KEESU;So;0;L; 30B1 30FC 30B9;;;;N;SQUARED KEESU;;;; 331D;SQUARE KORUNA;So;0;L; 30B3 30EB 30CA;;;;N;SQUARED KORUNA;;;; 331E;SQUARE KOOPO;So;0;L; 30B3 30FC 30DD;;;;N;SQUARED KOOPO;;;; 331F;SQUARE SAIKURU;So;0;L; 30B5 30A4 30AF 30EB;;;;N;SQUARED SAIKURU;;;; 3320;SQUARE SANTIIMU;So;0;L; 30B5 30F3 30C1 30FC 30E0;;;;N;SQUARED SANTIIMU;;;; 3321;SQUARE SIRINGU;So;0;L; 30B7 30EA 30F3 30B0;;;;N;SQUARED SIRINGU;;;; 3322;SQUARE SENTI;So;0;L; 30BB 30F3 30C1;;;;N;SQUARED SENTI;;;; 3323;SQUARE SENTO;So;0;L; 30BB 30F3 30C8;;;;N;SQUARED SENTO;;;; 3324;SQUARE DAASU;So;0;L; 30C0 30FC 30B9;;;;N;SQUARED DAASU;;;; 3325;SQUARE DESI;So;0;L; 30C7 30B7;;;;N;SQUARED DESI;;;; 3326;SQUARE DORU;So;0;L; 30C9 30EB;;;;N;SQUARED DORU;;;; 3327;SQUARE TON;So;0;L; 30C8 30F3;;;;N;SQUARED TON;;;; 3328;SQUARE NANO;So;0;L; 30CA 30CE;;;;N;SQUARED NANO;;;; 3329;SQUARE NOTTO;So;0;L; 30CE 30C3 30C8;;;;N;SQUARED NOTTO;;;; 332A;SQUARE HAITU;So;0;L; 30CF 30A4 30C4;;;;N;SQUARED HAITU;;;; 332B;SQUARE PAASENTO;So;0;L; 30D1 30FC 30BB 30F3 30C8;;;;N;SQUARED PAASENTO;;;; 332C;SQUARE PAATU;So;0;L; 30D1 30FC 30C4;;;;N;SQUARED PAATU;;;; 332D;SQUARE BAARERU;So;0;L; 30D0 30FC 30EC 30EB;;;;N;SQUARED BAARERU;;;; 332E;SQUARE PIASUTORU;So;0;L; 30D4 30A2 30B9 30C8 30EB;;;;N;SQUARED PIASUTORU;;;; 332F;SQUARE PIKURU;So;0;L; 30D4 30AF 30EB;;;;N;SQUARED PIKURU;;;; 3330;SQUARE PIKO;So;0;L; 30D4 30B3;;;;N;SQUARED PIKO;;;; 3331;SQUARE BIRU;So;0;L; 30D3 30EB;;;;N;SQUARED BIRU;;;; 3332;SQUARE HUARADDO;So;0;L; 30D5 30A1 30E9 30C3 30C9;;;;N;SQUARED HUARADDO;;;; 3333;SQUARE HUIITO;So;0;L; 30D5 30A3 30FC 30C8;;;;N;SQUARED HUIITO;;;; 3334;SQUARE BUSSYERU;So;0;L; 30D6 30C3 30B7 30A7 30EB;;;;N;SQUARED BUSSYERU;;;; 3335;SQUARE HURAN;So;0;L; 30D5 30E9 30F3;;;;N;SQUARED HURAN;;;; 3336;SQUARE HEKUTAARU;So;0;L; 30D8 30AF 30BF 30FC 30EB;;;;N;SQUARED HEKUTAARU;;;; 3337;SQUARE PESO;So;0;L; 30DA 30BD;;;;N;SQUARED PESO;;;; 3338;SQUARE PENIHI;So;0;L; 30DA 30CB 30D2;;;;N;SQUARED PENIHI;;;; 3339;SQUARE HERUTU;So;0;L; 30D8 30EB 30C4;;;;N;SQUARED HERUTU;;;; 333A;SQUARE PENSU;So;0;L; 30DA 30F3 30B9;;;;N;SQUARED PENSU;;;; 333B;SQUARE PEEZI;So;0;L; 30DA 30FC 30B8;;;;N;SQUARED PEEZI;;;; 333C;SQUARE BEETA;So;0;L; 30D9 30FC 30BF;;;;N;SQUARED BEETA;;;; 333D;SQUARE POINTO;So;0;L; 30DD 30A4 30F3 30C8;;;;N;SQUARED POINTO;;;; 333E;SQUARE BORUTO;So;0;L; 30DC 30EB 30C8;;;;N;SQUARED BORUTO;;;; 333F;SQUARE HON;So;0;L; 30DB 30F3;;;;N;SQUARED HON;;;; 3340;SQUARE PONDO;So;0;L; 30DD 30F3 30C9;;;;N;SQUARED PONDO;;;; 3341;SQUARE HOORU;So;0;L; 30DB 30FC 30EB;;;;N;SQUARED HOORU;;;; 3342;SQUARE HOON;So;0;L; 30DB 30FC 30F3;;;;N;SQUARED HOON;;;; 3343;SQUARE MAIKURO;So;0;L; 30DE 30A4 30AF 30ED;;;;N;SQUARED MAIKURO;;;; 3344;SQUARE MAIRU;So;0;L; 30DE 30A4 30EB;;;;N;SQUARED MAIRU;;;; 3345;SQUARE MAHHA;So;0;L; 30DE 30C3 30CF;;;;N;SQUARED MAHHA;;;; 3346;SQUARE MARUKU;So;0;L; 30DE 30EB 30AF;;;;N;SQUARED MARUKU;;;; 3347;SQUARE MANSYON;So;0;L; 30DE 30F3 30B7 30E7 30F3;;;;N;SQUARED MANSYON;;;; 3348;SQUARE MIKURON;So;0;L; 30DF 30AF 30ED 30F3;;;;N;SQUARED MIKURON;;;; 3349;SQUARE MIRI;So;0;L; 30DF 30EA;;;;N;SQUARED MIRI;;;; 334A;SQUARE MIRIBAARU;So;0;L; 30DF 30EA 30D0 30FC 30EB;;;;N;SQUARED MIRIBAARU;;;; 334B;SQUARE MEGA;So;0;L; 30E1 30AC;;;;N;SQUARED MEGA;;;; 334C;SQUARE MEGATON;So;0;L; 30E1 30AC 30C8 30F3;;;;N;SQUARED MEGATON;;;; 334D;SQUARE MEETORU;So;0;L; 30E1 30FC 30C8 30EB;;;;N;SQUARED MEETORU;;;; 334E;SQUARE YAADO;So;0;L; 30E4 30FC 30C9;;;;N;SQUARED YAADO;;;; 334F;SQUARE YAARU;So;0;L; 30E4 30FC 30EB;;;;N;SQUARED YAARU;;;; 3350;SQUARE YUAN;So;0;L; 30E6 30A2 30F3;;;;N;SQUARED YUAN;;;; 3351;SQUARE RITTORU;So;0;L; 30EA 30C3 30C8 30EB;;;;N;SQUARED RITTORU;;;; 3352;SQUARE RIRA;So;0;L; 30EA 30E9;;;;N;SQUARED RIRA;;;; 3353;SQUARE RUPII;So;0;L; 30EB 30D4 30FC;;;;N;SQUARED RUPII;;;; 3354;SQUARE RUUBURU;So;0;L; 30EB 30FC 30D6 30EB;;;;N;SQUARED RUUBURU;;;; 3355;SQUARE REMU;So;0;L; 30EC 30E0;;;;N;SQUARED REMU;;;; 3356;SQUARE RENTOGEN;So;0;L; 30EC 30F3 30C8 30B2 30F3;;;;N;SQUARED RENTOGEN;;;; 3357;SQUARE WATTO;So;0;L; 30EF 30C3 30C8;;;;N;SQUARED WATTO;;;; 3358;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR ZERO;So;0;L; 0030 70B9;;;;N;;;;; 3359;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR ONE;So;0;L; 0031 70B9;;;;N;;;;; 335A;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWO;So;0;L; 0032 70B9;;;;N;;;;; 335B;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR THREE;So;0;L; 0033 70B9;;;;N;;;;; 335C;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR FOUR;So;0;L; 0034 70B9;;;;N;;;;; 335D;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR FIVE;So;0;L; 0035 70B9;;;;N;;;;; 335E;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR SIX;So;0;L; 0036 70B9;;;;N;;;;; 335F;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR SEVEN;So;0;L; 0037 70B9;;;;N;;;;; 3360;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR EIGHT;So;0;L; 0038 70B9;;;;N;;;;; 3361;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR NINE;So;0;L; 0039 70B9;;;;N;;;;; 3362;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TEN;So;0;L; 0031 0030 70B9;;;;N;;;;; 3363;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR ELEVEN;So;0;L; 0031 0031 70B9;;;;N;;;;; 3364;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWELVE;So;0;L; 0031 0032 70B9;;;;N;;;;; 3365;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR THIRTEEN;So;0;L; 0031 0033 70B9;;;;N;;;;; 3366;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR FOURTEEN;So;0;L; 0031 0034 70B9;;;;N;;;;; 3367;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR FIFTEEN;So;0;L; 0031 0035 70B9;;;;N;;;;; 3368;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR SIXTEEN;So;0;L; 0031 0036 70B9;;;;N;;;;; 3369;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR SEVENTEEN;So;0;L; 0031 0037 70B9;;;;N;;;;; 336A;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR EIGHTEEN;So;0;L; 0031 0038 70B9;;;;N;;;;; 336B;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR NINETEEN;So;0;L; 0031 0039 70B9;;;;N;;;;; 336C;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWENTY;So;0;L; 0032 0030 70B9;;;;N;;;;; 336D;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWENTY-ONE;So;0;L; 0032 0031 70B9;;;;N;;;;; 336E;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWENTY-TWO;So;0;L; 0032 0032 70B9;;;;N;;;;; 336F;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWENTY-THREE;So;0;L; 0032 0033 70B9;;;;N;;;;; 3370;IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWENTY-FOUR;So;0;L; 0032 0034 70B9;;;;N;;;;; 3371;SQUARE HPA;So;0;L; 0068 0050 0061;;;;N;;;;; 3372;SQUARE DA;So;0;L; 0064 0061;;;;N;;;;; 3373;SQUARE AU;So;0;L; 0041 0055;;;;N;;;;; 3374;SQUARE BAR;So;0;L; 0062 0061 0072;;;;N;;;;; 3375;SQUARE OV;So;0;L; 006F 0056;;;;N;;;;; 3376;SQUARE PC;So;0;L; 0070 0063;;;;N;;;;; 3377;SQUARE DM;So;0;ON; 0064 006D;;;;N;;;;; 3378;SQUARE DM SQUARED;So;0;ON; 0064 006D 00B2;;;;N;;;;; 3379;SQUARE DM CUBED;So;0;ON; 0064 006D 00B3;;;;N;;;;; 337A;SQUARE IU;So;0;ON; 0049 0055;;;;N;;;;; 337B;SQUARE ERA NAME HEISEI;So;0;L; 5E73 6210;;;;N;SQUARED TWO IDEOGRAPHS ERA NAME HEISEI;;;; 337C;SQUARE ERA NAME SYOUWA;So;0;L; 662D 548C;;;;N;SQUARED TWO IDEOGRAPHS ERA NAME SYOUWA;;;; 337D;SQUARE ERA NAME TAISYOU;So;0;L; 5927 6B63;;;;N;SQUARED TWO IDEOGRAPHS ERA NAME TAISYOU;;;; 337E;SQUARE ERA NAME MEIZI;So;0;L; 660E 6CBB;;;;N;SQUARED TWO IDEOGRAPHS ERA NAME MEIZI;;;; 337F;SQUARE CORPORATION;So;0;L; 682A 5F0F 4F1A 793E;;;;N;SQUARED FOUR IDEOGRAPHS CORPORATION;;;; 3380;SQUARE PA AMPS;So;0;L; 0070 0041;;;;N;SQUARED PA AMPS;;;; 3381;SQUARE NA;So;0;L; 006E 0041;;;;N;SQUARED NA;;;; 3382;SQUARE MU A;So;0;L; 03BC 0041;;;;N;SQUARED MU A;;;; 3383;SQUARE MA;So;0;L; 006D 0041;;;;N;SQUARED MA;;;; 3384;SQUARE KA;So;0;L; 006B 0041;;;;N;SQUARED KA;;;; 3385;SQUARE KB;So;0;L; 004B 0042;;;;N;SQUARED KB;;;; 3386;SQUARE MB;So;0;L; 004D 0042;;;;N;SQUARED MB;;;; 3387;SQUARE GB;So;0;L; 0047 0042;;;;N;SQUARED GB;;;; 3388;SQUARE CAL;So;0;L; 0063 0061 006C;;;;N;SQUARED CAL;;;; 3389;SQUARE KCAL;So;0;L; 006B 0063 0061 006C;;;;N;SQUARED KCAL;;;; 338A;SQUARE PF;So;0;L; 0070 0046;;;;N;SQUARED PF;;;; 338B;SQUARE NF;So;0;L; 006E 0046;;;;N;SQUARED NF;;;; 338C;SQUARE MU F;So;0;L; 03BC 0046;;;;N;SQUARED MU F;;;; 338D;SQUARE MU G;So;0;L; 03BC 0067;;;;N;SQUARED MU G;;;; 338E;SQUARE MG;So;0;L; 006D 0067;;;;N;SQUARED MG;;;; 338F;SQUARE KG;So;0;L; 006B 0067;;;;N;SQUARED KG;;;; 3390;SQUARE HZ;So;0;L; 0048 007A;;;;N;SQUARED HZ;;;; 3391;SQUARE KHZ;So;0;L; 006B 0048 007A;;;;N;SQUARED KHZ;;;; 3392;SQUARE MHZ;So;0;L; 004D 0048 007A;;;;N;SQUARED MHZ;;;; 3393;SQUARE GHZ;So;0;L; 0047 0048 007A;;;;N;SQUARED GHZ;;;; 3394;SQUARE THZ;So;0;L; 0054 0048 007A;;;;N;SQUARED THZ;;;; 3395;SQUARE MU L;So;0;L; 03BC 2113;;;;N;SQUARED MU L;;;; 3396;SQUARE ML;So;0;L; 006D 2113;;;;N;SQUARED ML;;;; 3397;SQUARE DL;So;0;L; 0064 2113;;;;N;SQUARED DL;;;; 3398;SQUARE KL;So;0;L; 006B 2113;;;;N;SQUARED KL;;;; 3399;SQUARE FM;So;0;L; 0066 006D;;;;N;SQUARED FM;;;; 339A;SQUARE NM;So;0;L; 006E 006D;;;;N;SQUARED NM;;;; 339B;SQUARE MU M;So;0;L; 03BC 006D;;;;N;SQUARED MU M;;;; 339C;SQUARE MM;So;0;L; 006D 006D;;;;N;SQUARED MM;;;; 339D;SQUARE CM;So;0;L; 0063 006D;;;;N;SQUARED CM;;;; 339E;SQUARE KM;So;0;L; 006B 006D;;;;N;SQUARED KM;;;; 339F;SQUARE MM SQUARED;So;0;L; 006D 006D 00B2;;;;N;SQUARED MM SQUARED;;;; 33A0;SQUARE CM SQUARED;So;0;L; 0063 006D 00B2;;;;N;SQUARED CM SQUARED;;;; 33A1;SQUARE M SQUARED;So;0;L; 006D 00B2;;;;N;SQUARED M SQUARED;;;; 33A2;SQUARE KM SQUARED;So;0;L; 006B 006D 00B2;;;;N;SQUARED KM SQUARED;;;; 33A3;SQUARE MM CUBED;So;0;L; 006D 006D 00B3;;;;N;SQUARED MM CUBED;;;; 33A4;SQUARE CM CUBED;So;0;L; 0063 006D 00B3;;;;N;SQUARED CM CUBED;;;; 33A5;SQUARE M CUBED;So;0;L; 006D 00B3;;;;N;SQUARED M CUBED;;;; 33A6;SQUARE KM CUBED;So;0;L; 006B 006D 00B3;;;;N;SQUARED KM CUBED;;;; 33A7;SQUARE M OVER S;So;0;L; 006D 2215 0073;;;;N;SQUARED M OVER S;;;; 33A8;SQUARE M OVER S SQUARED;So;0;L; 006D 2215 0073 00B2;;;;N;SQUARED M OVER S SQUARED;;;; 33A9;SQUARE PA;So;0;L; 0050 0061;;;;N;SQUARED PA;;;; 33AA;SQUARE KPA;So;0;L; 006B 0050 0061;;;;N;SQUARED KPA;;;; 33AB;SQUARE MPA;So;0;L; 004D 0050 0061;;;;N;SQUARED MPA;;;; 33AC;SQUARE GPA;So;0;L; 0047 0050 0061;;;;N;SQUARED GPA;;;; 33AD;SQUARE RAD;So;0;L; 0072 0061 0064;;;;N;SQUARED RAD;;;; 33AE;SQUARE RAD OVER S;So;0;L; 0072 0061 0064 2215 0073;;;;N;SQUARED RAD OVER S;;;; 33AF;SQUARE RAD OVER S SQUARED;So;0;L; 0072 0061 0064 2215 0073 00B2;;;;N;SQUARED RAD OVER S SQUARED;;;; 33B0;SQUARE PS;So;0;L; 0070 0073;;;;N;SQUARED PS;;;; 33B1;SQUARE NS;So;0;L; 006E 0073;;;;N;SQUARED NS;;;; 33B2;SQUARE MU S;So;0;L; 03BC 0073;;;;N;SQUARED MU S;;;; 33B3;SQUARE MS;So;0;L; 006D 0073;;;;N;SQUARED MS;;;; 33B4;SQUARE PV;So;0;L; 0070 0056;;;;N;SQUARED PV;;;; 33B5;SQUARE NV;So;0;L; 006E 0056;;;;N;SQUARED NV;;;; 33B6;SQUARE MU V;So;0;L; 03BC 0056;;;;N;SQUARED MU V;;;; 33B7;SQUARE MV;So;0;L; 006D 0056;;;;N;SQUARED MV;;;; 33B8;SQUARE KV;So;0;L; 006B 0056;;;;N;SQUARED KV;;;; 33B9;SQUARE MV MEGA;So;0;L; 004D 0056;;;;N;SQUARED MV MEGA;;;; 33BA;SQUARE PW;So;0;L; 0070 0057;;;;N;SQUARED PW;;;; 33BB;SQUARE NW;So;0;L; 006E 0057;;;;N;SQUARED NW;;;; 33BC;SQUARE MU W;So;0;L; 03BC 0057;;;;N;SQUARED MU W;;;; 33BD;SQUARE MW;So;0;L; 006D 0057;;;;N;SQUARED MW;;;; 33BE;SQUARE KW;So;0;L; 006B 0057;;;;N;SQUARED KW;;;; 33BF;SQUARE MW MEGA;So;0;L; 004D 0057;;;;N;SQUARED MW MEGA;;;; 33C0;SQUARE K OHM;So;0;L; 006B 03A9;;;;N;SQUARED K OHM;;;; 33C1;SQUARE M OHM;So;0;L; 004D 03A9;;;;N;SQUARED M OHM;;;; 33C2;SQUARE AM;So;0;L; 0061 002E 006D 002E;;;;N;SQUARED AM;;;; 33C3;SQUARE BQ;So;0;L; 0042 0071;;;;N;SQUARED BQ;;;; 33C4;SQUARE CC;So;0;L; 0063 0063;;;;N;SQUARED CC;;;; 33C5;SQUARE CD;So;0;L; 0063 0064;;;;N;SQUARED CD;;;; 33C6;SQUARE C OVER KG;So;0;L; 0043 2215 006B 0067;;;;N;SQUARED C OVER KG;;;; 33C7;SQUARE CO;So;0;L; 0043 006F 002E;;;;N;SQUARED CO;;;; 33C8;SQUARE DB;So;0;L; 0064 0042;;;;N;SQUARED DB;;;; 33C9;SQUARE GY;So;0;L; 0047 0079;;;;N;SQUARED GY;;;; 33CA;SQUARE HA;So;0;L; 0068 0061;;;;N;SQUARED HA;;;; 33CB;SQUARE HP;So;0;L; 0048 0050;;;;N;SQUARED HP;;;; 33CC;SQUARE IN;So;0;L; 0069 006E;;;;N;SQUARED IN;;;; 33CD;SQUARE KK;So;0;L; 004B 004B;;;;N;SQUARED KK;;;; 33CE;SQUARE KM CAPITAL;So;0;L; 004B 004D;;;;N;SQUARED KM CAPITAL;;;; 33CF;SQUARE KT;So;0;L; 006B 0074;;;;N;SQUARED KT;;;; 33D0;SQUARE LM;So;0;L; 006C 006D;;;;N;SQUARED LM;;;; 33D1;SQUARE LN;So;0;L; 006C 006E;;;;N;SQUARED LN;;;; 33D2;SQUARE LOG;So;0;L; 006C 006F 0067;;;;N;SQUARED LOG;;;; 33D3;SQUARE LX;So;0;L; 006C 0078;;;;N;SQUARED LX;;;; 33D4;SQUARE MB SMALL;So;0;L; 006D 0062;;;;N;SQUARED MB SMALL;;;; 33D5;SQUARE MIL;So;0;L; 006D 0069 006C;;;;N;SQUARED MIL;;;; 33D6;SQUARE MOL;So;0;L; 006D 006F 006C;;;;N;SQUARED MOL;;;; 33D7;SQUARE PH;So;0;L; 0050 0048;;;;N;SQUARED PH;;;; 33D8;SQUARE PM;So;0;L; 0070 002E 006D 002E;;;;N;SQUARED PM;;;; 33D9;SQUARE PPM;So;0;L; 0050 0050 004D;;;;N;SQUARED PPM;;;; 33DA;SQUARE PR;So;0;L; 0050 0052;;;;N;SQUARED PR;;;; 33DB;SQUARE SR;So;0;L; 0073 0072;;;;N;SQUARED SR;;;; 33DC;SQUARE SV;So;0;L; 0053 0076;;;;N;SQUARED SV;;;; 33DD;SQUARE WB;So;0;L; 0057 0062;;;;N;SQUARED WB;;;; 33DE;SQUARE V OVER M;So;0;ON; 0056 2215 006D;;;;N;;;;; 33DF;SQUARE A OVER M;So;0;ON; 0041 2215 006D;;;;N;;;;; 33E0;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY ONE;So;0;L; 0031 65E5;;;;N;;;;; 33E1;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWO;So;0;L; 0032 65E5;;;;N;;;;; 33E2;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY THREE;So;0;L; 0033 65E5;;;;N;;;;; 33E3;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY FOUR;So;0;L; 0034 65E5;;;;N;;;;; 33E4;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY FIVE;So;0;L; 0035 65E5;;;;N;;;;; 33E5;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY SIX;So;0;L; 0036 65E5;;;;N;;;;; 33E6;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY SEVEN;So;0;L; 0037 65E5;;;;N;;;;; 33E7;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY EIGHT;So;0;L; 0038 65E5;;;;N;;;;; 33E8;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY NINE;So;0;L; 0039 65E5;;;;N;;;;; 33E9;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TEN;So;0;L; 0031 0030 65E5;;;;N;;;;; 33EA;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY ELEVEN;So;0;L; 0031 0031 65E5;;;;N;;;;; 33EB;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWELVE;So;0;L; 0031 0032 65E5;;;;N;;;;; 33EC;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY THIRTEEN;So;0;L; 0031 0033 65E5;;;;N;;;;; 33ED;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY FOURTEEN;So;0;L; 0031 0034 65E5;;;;N;;;;; 33EE;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY FIFTEEN;So;0;L; 0031 0035 65E5;;;;N;;;;; 33EF;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY SIXTEEN;So;0;L; 0031 0036 65E5;;;;N;;;;; 33F0;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY SEVENTEEN;So;0;L; 0031 0037 65E5;;;;N;;;;; 33F1;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY EIGHTEEN;So;0;L; 0031 0038 65E5;;;;N;;;;; 33F2;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY NINETEEN;So;0;L; 0031 0039 65E5;;;;N;;;;; 33F3;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY;So;0;L; 0032 0030 65E5;;;;N;;;;; 33F4;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-ONE;So;0;L; 0032 0031 65E5;;;;N;;;;; 33F5;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-TWO;So;0;L; 0032 0032 65E5;;;;N;;;;; 33F6;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-THREE;So;0;L; 0032 0033 65E5;;;;N;;;;; 33F7;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-FOUR;So;0;L; 0032 0034 65E5;;;;N;;;;; 33F8;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-FIVE;So;0;L; 0032 0035 65E5;;;;N;;;;; 33F9;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-SIX;So;0;L; 0032 0036 65E5;;;;N;;;;; 33FA;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-SEVEN;So;0;L; 0032 0037 65E5;;;;N;;;;; 33FB;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-EIGHT;So;0;L; 0032 0038 65E5;;;;N;;;;; 33FC;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-NINE;So;0;L; 0032 0039 65E5;;;;N;;;;; 33FD;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY THIRTY;So;0;L; 0033 0030 65E5;;;;N;;;;; 33FE;IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY THIRTY-ONE;So;0;L; 0033 0031 65E5;;;;N;;;;; 33FF;SQUARE GAL;So;0;ON; 0067 0061 006C;;;;N;;;;; 3400;;Lo;0;L;;;;;N;;;;; 4DB5;;Lo;0;L;;;;;N;;;;; 4DC0;HEXAGRAM FOR THE CREATIVE HEAVEN;So;0;ON;;;;;N;;;;; 4DC1;HEXAGRAM FOR THE RECEPTIVE EARTH;So;0;ON;;;;;N;;;;; 4DC2;HEXAGRAM FOR DIFFICULTY AT THE BEGINNING;So;0;ON;;;;;N;;;;; 4DC3;HEXAGRAM FOR YOUTHFUL FOLLY;So;0;ON;;;;;N;;;;; 4DC4;HEXAGRAM FOR WAITING;So;0;ON;;;;;N;;;;; 4DC5;HEXAGRAM FOR CONFLICT;So;0;ON;;;;;N;;;;; 4DC6;HEXAGRAM FOR THE ARMY;So;0;ON;;;;;N;;;;; 4DC7;HEXAGRAM FOR HOLDING TOGETHER;So;0;ON;;;;;N;;;;; 4DC8;HEXAGRAM FOR SMALL TAMING;So;0;ON;;;;;N;;;;; 4DC9;HEXAGRAM FOR TREADING;So;0;ON;;;;;N;;;;; 4DCA;HEXAGRAM FOR PEACE;So;0;ON;;;;;N;;;;; 4DCB;HEXAGRAM FOR STANDSTILL;So;0;ON;;;;;N;;;;; 4DCC;HEXAGRAM FOR FELLOWSHIP;So;0;ON;;;;;N;;;;; 4DCD;HEXAGRAM FOR GREAT POSSESSION;So;0;ON;;;;;N;;;;; 4DCE;HEXAGRAM FOR MODESTY;So;0;ON;;;;;N;;;;; 4DCF;HEXAGRAM FOR ENTHUSIASM;So;0;ON;;;;;N;;;;; 4DD0;HEXAGRAM FOR FOLLOWING;So;0;ON;;;;;N;;;;; 4DD1;HEXAGRAM FOR WORK ON THE DECAYED;So;0;ON;;;;;N;;;;; 4DD2;HEXAGRAM FOR APPROACH;So;0;ON;;;;;N;;;;; 4DD3;HEXAGRAM FOR CONTEMPLATION;So;0;ON;;;;;N;;;;; 4DD4;HEXAGRAM FOR BITING THROUGH;So;0;ON;;;;;N;;;;; 4DD5;HEXAGRAM FOR GRACE;So;0;ON;;;;;N;;;;; 4DD6;HEXAGRAM FOR SPLITTING APART;So;0;ON;;;;;N;;;;; 4DD7;HEXAGRAM FOR RETURN;So;0;ON;;;;;N;;;;; 4DD8;HEXAGRAM FOR INNOCENCE;So;0;ON;;;;;N;;;;; 4DD9;HEXAGRAM FOR GREAT TAMING;So;0;ON;;;;;N;;;;; 4DDA;HEXAGRAM FOR MOUTH CORNERS;So;0;ON;;;;;N;;;;; 4DDB;HEXAGRAM FOR GREAT PREPONDERANCE;So;0;ON;;;;;N;;;;; 4DDC;HEXAGRAM FOR THE ABYSMAL WATER;So;0;ON;;;;;N;;;;; 4DDD;HEXAGRAM FOR THE CLINGING FIRE;So;0;ON;;;;;N;;;;; 4DDE;HEXAGRAM FOR INFLUENCE;So;0;ON;;;;;N;;;;; 4DDF;HEXAGRAM FOR DURATION;So;0;ON;;;;;N;;;;; 4DE0;HEXAGRAM FOR RETREAT;So;0;ON;;;;;N;;;;; 4DE1;HEXAGRAM FOR GREAT POWER;So;0;ON;;;;;N;;;;; 4DE2;HEXAGRAM FOR PROGRESS;So;0;ON;;;;;N;;;;; 4DE3;HEXAGRAM FOR DARKENING OF THE LIGHT;So;0;ON;;;;;N;;;;; 4DE4;HEXAGRAM FOR THE FAMILY;So;0;ON;;;;;N;;;;; 4DE5;HEXAGRAM FOR OPPOSITION;So;0;ON;;;;;N;;;;; 4DE6;HEXAGRAM FOR OBSTRUCTION;So;0;ON;;;;;N;;;;; 4DE7;HEXAGRAM FOR DELIVERANCE;So;0;ON;;;;;N;;;;; 4DE8;HEXAGRAM FOR DECREASE;So;0;ON;;;;;N;;;;; 4DE9;HEXAGRAM FOR INCREASE;So;0;ON;;;;;N;;;;; 4DEA;HEXAGRAM FOR BREAKTHROUGH;So;0;ON;;;;;N;;;;; 4DEB;HEXAGRAM FOR COMING TO MEET;So;0;ON;;;;;N;;;;; 4DEC;HEXAGRAM FOR GATHERING TOGETHER;So;0;ON;;;;;N;;;;; 4DED;HEXAGRAM FOR PUSHING UPWARD;So;0;ON;;;;;N;;;;; 4DEE;HEXAGRAM FOR OPPRESSION;So;0;ON;;;;;N;;;;; 4DEF;HEXAGRAM FOR THE WELL;So;0;ON;;;;;N;;;;; 4DF0;HEXAGRAM FOR REVOLUTION;So;0;ON;;;;;N;;;;; 4DF1;HEXAGRAM FOR THE CAULDRON;So;0;ON;;;;;N;;;;; 4DF2;HEXAGRAM FOR THE AROUSING THUNDER;So;0;ON;;;;;N;;;;; 4DF3;HEXAGRAM FOR THE KEEPING STILL MOUNTAIN;So;0;ON;;;;;N;;;;; 4DF4;HEXAGRAM FOR DEVELOPMENT;So;0;ON;;;;;N;;;;; 4DF5;HEXAGRAM FOR THE MARRYING MAIDEN;So;0;ON;;;;;N;;;;; 4DF6;HEXAGRAM FOR ABUNDANCE;So;0;ON;;;;;N;;;;; 4DF7;HEXAGRAM FOR THE WANDERER;So;0;ON;;;;;N;;;;; 4DF8;HEXAGRAM FOR THE GENTLE WIND;So;0;ON;;;;;N;;;;; 4DF9;HEXAGRAM FOR THE JOYOUS LAKE;So;0;ON;;;;;N;;;;; 4DFA;HEXAGRAM FOR DISPERSION;So;0;ON;;;;;N;;;;; 4DFB;HEXAGRAM FOR LIMITATION;So;0;ON;;;;;N;;;;; 4DFC;HEXAGRAM FOR INNER TRUTH;So;0;ON;;;;;N;;;;; 4DFD;HEXAGRAM FOR SMALL PREPONDERANCE;So;0;ON;;;;;N;;;;; 4DFE;HEXAGRAM FOR AFTER COMPLETION;So;0;ON;;;;;N;;;;; 4DFF;HEXAGRAM FOR BEFORE COMPLETION;So;0;ON;;;;;N;;;;; 4E00;;Lo;0;L;;;;;N;;;;; 9FD5;;Lo;0;L;;;;;N;;;;; A000;YI SYLLABLE IT;Lo;0;L;;;;;N;;;;; A001;YI SYLLABLE IX;Lo;0;L;;;;;N;;;;; A002;YI SYLLABLE I;Lo;0;L;;;;;N;;;;; A003;YI SYLLABLE IP;Lo;0;L;;;;;N;;;;; A004;YI SYLLABLE IET;Lo;0;L;;;;;N;;;;; A005;YI SYLLABLE IEX;Lo;0;L;;;;;N;;;;; A006;YI SYLLABLE IE;Lo;0;L;;;;;N;;;;; A007;YI SYLLABLE IEP;Lo;0;L;;;;;N;;;;; A008;YI SYLLABLE AT;Lo;0;L;;;;;N;;;;; A009;YI SYLLABLE AX;Lo;0;L;;;;;N;;;;; A00A;YI SYLLABLE A;Lo;0;L;;;;;N;;;;; A00B;YI SYLLABLE AP;Lo;0;L;;;;;N;;;;; A00C;YI SYLLABLE UOX;Lo;0;L;;;;;N;;;;; A00D;YI SYLLABLE UO;Lo;0;L;;;;;N;;;;; A00E;YI SYLLABLE UOP;Lo;0;L;;;;;N;;;;; A00F;YI SYLLABLE OT;Lo;0;L;;;;;N;;;;; A010;YI SYLLABLE OX;Lo;0;L;;;;;N;;;;; A011;YI SYLLABLE O;Lo;0;L;;;;;N;;;;; A012;YI SYLLABLE OP;Lo;0;L;;;;;N;;;;; A013;YI SYLLABLE EX;Lo;0;L;;;;;N;;;;; A014;YI SYLLABLE E;Lo;0;L;;;;;N;;;;; A015;YI SYLLABLE WU;Lm;0;L;;;;;N;;;;; A016;YI SYLLABLE BIT;Lo;0;L;;;;;N;;;;; A017;YI SYLLABLE BIX;Lo;0;L;;;;;N;;;;; A018;YI SYLLABLE BI;Lo;0;L;;;;;N;;;;; A019;YI SYLLABLE BIP;Lo;0;L;;;;;N;;;;; A01A;YI SYLLABLE BIET;Lo;0;L;;;;;N;;;;; A01B;YI SYLLABLE BIEX;Lo;0;L;;;;;N;;;;; A01C;YI SYLLABLE BIE;Lo;0;L;;;;;N;;;;; A01D;YI SYLLABLE BIEP;Lo;0;L;;;;;N;;;;; A01E;YI SYLLABLE BAT;Lo;0;L;;;;;N;;;;; A01F;YI SYLLABLE BAX;Lo;0;L;;;;;N;;;;; A020;YI SYLLABLE BA;Lo;0;L;;;;;N;;;;; A021;YI SYLLABLE BAP;Lo;0;L;;;;;N;;;;; A022;YI SYLLABLE BUOX;Lo;0;L;;;;;N;;;;; A023;YI SYLLABLE BUO;Lo;0;L;;;;;N;;;;; A024;YI SYLLABLE BUOP;Lo;0;L;;;;;N;;;;; A025;YI SYLLABLE BOT;Lo;0;L;;;;;N;;;;; A026;YI SYLLABLE BOX;Lo;0;L;;;;;N;;;;; A027;YI SYLLABLE BO;Lo;0;L;;;;;N;;;;; A028;YI SYLLABLE BOP;Lo;0;L;;;;;N;;;;; A029;YI SYLLABLE BEX;Lo;0;L;;;;;N;;;;; A02A;YI SYLLABLE BE;Lo;0;L;;;;;N;;;;; A02B;YI SYLLABLE BEP;Lo;0;L;;;;;N;;;;; A02C;YI SYLLABLE BUT;Lo;0;L;;;;;N;;;;; A02D;YI SYLLABLE BUX;Lo;0;L;;;;;N;;;;; A02E;YI SYLLABLE BU;Lo;0;L;;;;;N;;;;; A02F;YI SYLLABLE BUP;Lo;0;L;;;;;N;;;;; A030;YI SYLLABLE BURX;Lo;0;L;;;;;N;;;;; A031;YI SYLLABLE BUR;Lo;0;L;;;;;N;;;;; A032;YI SYLLABLE BYT;Lo;0;L;;;;;N;;;;; A033;YI SYLLABLE BYX;Lo;0;L;;;;;N;;;;; A034;YI SYLLABLE BY;Lo;0;L;;;;;N;;;;; A035;YI SYLLABLE BYP;Lo;0;L;;;;;N;;;;; A036;YI SYLLABLE BYRX;Lo;0;L;;;;;N;;;;; A037;YI SYLLABLE BYR;Lo;0;L;;;;;N;;;;; A038;YI SYLLABLE PIT;Lo;0;L;;;;;N;;;;; A039;YI SYLLABLE PIX;Lo;0;L;;;;;N;;;;; A03A;YI SYLLABLE PI;Lo;0;L;;;;;N;;;;; A03B;YI SYLLABLE PIP;Lo;0;L;;;;;N;;;;; A03C;YI SYLLABLE PIEX;Lo;0;L;;;;;N;;;;; A03D;YI SYLLABLE PIE;Lo;0;L;;;;;N;;;;; A03E;YI SYLLABLE PIEP;Lo;0;L;;;;;N;;;;; A03F;YI SYLLABLE PAT;Lo;0;L;;;;;N;;;;; A040;YI SYLLABLE PAX;Lo;0;L;;;;;N;;;;; A041;YI SYLLABLE PA;Lo;0;L;;;;;N;;;;; A042;YI SYLLABLE PAP;Lo;0;L;;;;;N;;;;; A043;YI SYLLABLE PUOX;Lo;0;L;;;;;N;;;;; A044;YI SYLLABLE PUO;Lo;0;L;;;;;N;;;;; A045;YI SYLLABLE PUOP;Lo;0;L;;;;;N;;;;; A046;YI SYLLABLE POT;Lo;0;L;;;;;N;;;;; A047;YI SYLLABLE POX;Lo;0;L;;;;;N;;;;; A048;YI SYLLABLE PO;Lo;0;L;;;;;N;;;;; A049;YI SYLLABLE POP;Lo;0;L;;;;;N;;;;; A04A;YI SYLLABLE PUT;Lo;0;L;;;;;N;;;;; A04B;YI SYLLABLE PUX;Lo;0;L;;;;;N;;;;; A04C;YI SYLLABLE PU;Lo;0;L;;;;;N;;;;; A04D;YI SYLLABLE PUP;Lo;0;L;;;;;N;;;;; A04E;YI SYLLABLE PURX;Lo;0;L;;;;;N;;;;; A04F;YI SYLLABLE PUR;Lo;0;L;;;;;N;;;;; A050;YI SYLLABLE PYT;Lo;0;L;;;;;N;;;;; A051;YI SYLLABLE PYX;Lo;0;L;;;;;N;;;;; A052;YI SYLLABLE PY;Lo;0;L;;;;;N;;;;; A053;YI SYLLABLE PYP;Lo;0;L;;;;;N;;;;; A054;YI SYLLABLE PYRX;Lo;0;L;;;;;N;;;;; A055;YI SYLLABLE PYR;Lo;0;L;;;;;N;;;;; A056;YI SYLLABLE BBIT;Lo;0;L;;;;;N;;;;; A057;YI SYLLABLE BBIX;Lo;0;L;;;;;N;;;;; A058;YI SYLLABLE BBI;Lo;0;L;;;;;N;;;;; A059;YI SYLLABLE BBIP;Lo;0;L;;;;;N;;;;; A05A;YI SYLLABLE BBIET;Lo;0;L;;;;;N;;;;; A05B;YI SYLLABLE BBIEX;Lo;0;L;;;;;N;;;;; A05C;YI SYLLABLE BBIE;Lo;0;L;;;;;N;;;;; A05D;YI SYLLABLE BBIEP;Lo;0;L;;;;;N;;;;; A05E;YI SYLLABLE BBAT;Lo;0;L;;;;;N;;;;; A05F;YI SYLLABLE BBAX;Lo;0;L;;;;;N;;;;; A060;YI SYLLABLE BBA;Lo;0;L;;;;;N;;;;; A061;YI SYLLABLE BBAP;Lo;0;L;;;;;N;;;;; A062;YI SYLLABLE BBUOX;Lo;0;L;;;;;N;;;;; A063;YI SYLLABLE BBUO;Lo;0;L;;;;;N;;;;; A064;YI SYLLABLE BBUOP;Lo;0;L;;;;;N;;;;; A065;YI SYLLABLE BBOT;Lo;0;L;;;;;N;;;;; A066;YI SYLLABLE BBOX;Lo;0;L;;;;;N;;;;; A067;YI SYLLABLE BBO;Lo;0;L;;;;;N;;;;; A068;YI SYLLABLE BBOP;Lo;0;L;;;;;N;;;;; A069;YI SYLLABLE BBEX;Lo;0;L;;;;;N;;;;; A06A;YI SYLLABLE BBE;Lo;0;L;;;;;N;;;;; A06B;YI SYLLABLE BBEP;Lo;0;L;;;;;N;;;;; A06C;YI SYLLABLE BBUT;Lo;0;L;;;;;N;;;;; A06D;YI SYLLABLE BBUX;Lo;0;L;;;;;N;;;;; A06E;YI SYLLABLE BBU;Lo;0;L;;;;;N;;;;; A06F;YI SYLLABLE BBUP;Lo;0;L;;;;;N;;;;; A070;YI SYLLABLE BBURX;Lo;0;L;;;;;N;;;;; A071;YI SYLLABLE BBUR;Lo;0;L;;;;;N;;;;; A072;YI SYLLABLE BBYT;Lo;0;L;;;;;N;;;;; A073;YI SYLLABLE BBYX;Lo;0;L;;;;;N;;;;; A074;YI SYLLABLE BBY;Lo;0;L;;;;;N;;;;; A075;YI SYLLABLE BBYP;Lo;0;L;;;;;N;;;;; A076;YI SYLLABLE NBIT;Lo;0;L;;;;;N;;;;; A077;YI SYLLABLE NBIX;Lo;0;L;;;;;N;;;;; A078;YI SYLLABLE NBI;Lo;0;L;;;;;N;;;;; A079;YI SYLLABLE NBIP;Lo;0;L;;;;;N;;;;; A07A;YI SYLLABLE NBIEX;Lo;0;L;;;;;N;;;;; A07B;YI SYLLABLE NBIE;Lo;0;L;;;;;N;;;;; A07C;YI SYLLABLE NBIEP;Lo;0;L;;;;;N;;;;; A07D;YI SYLLABLE NBAT;Lo;0;L;;;;;N;;;;; A07E;YI SYLLABLE NBAX;Lo;0;L;;;;;N;;;;; A07F;YI SYLLABLE NBA;Lo;0;L;;;;;N;;;;; A080;YI SYLLABLE NBAP;Lo;0;L;;;;;N;;;;; A081;YI SYLLABLE NBOT;Lo;0;L;;;;;N;;;;; A082;YI SYLLABLE NBOX;Lo;0;L;;;;;N;;;;; A083;YI SYLLABLE NBO;Lo;0;L;;;;;N;;;;; A084;YI SYLLABLE NBOP;Lo;0;L;;;;;N;;;;; A085;YI SYLLABLE NBUT;Lo;0;L;;;;;N;;;;; A086;YI SYLLABLE NBUX;Lo;0;L;;;;;N;;;;; A087;YI SYLLABLE NBU;Lo;0;L;;;;;N;;;;; A088;YI SYLLABLE NBUP;Lo;0;L;;;;;N;;;;; A089;YI SYLLABLE NBURX;Lo;0;L;;;;;N;;;;; A08A;YI SYLLABLE NBUR;Lo;0;L;;;;;N;;;;; A08B;YI SYLLABLE NBYT;Lo;0;L;;;;;N;;;;; A08C;YI SYLLABLE NBYX;Lo;0;L;;;;;N;;;;; A08D;YI SYLLABLE NBY;Lo;0;L;;;;;N;;;;; A08E;YI SYLLABLE NBYP;Lo;0;L;;;;;N;;;;; A08F;YI SYLLABLE NBYRX;Lo;0;L;;;;;N;;;;; A090;YI SYLLABLE NBYR;Lo;0;L;;;;;N;;;;; A091;YI SYLLABLE HMIT;Lo;0;L;;;;;N;;;;; A092;YI SYLLABLE HMIX;Lo;0;L;;;;;N;;;;; A093;YI SYLLABLE HMI;Lo;0;L;;;;;N;;;;; A094;YI SYLLABLE HMIP;Lo;0;L;;;;;N;;;;; A095;YI SYLLABLE HMIEX;Lo;0;L;;;;;N;;;;; A096;YI SYLLABLE HMIE;Lo;0;L;;;;;N;;;;; A097;YI SYLLABLE HMIEP;Lo;0;L;;;;;N;;;;; A098;YI SYLLABLE HMAT;Lo;0;L;;;;;N;;;;; A099;YI SYLLABLE HMAX;Lo;0;L;;;;;N;;;;; A09A;YI SYLLABLE HMA;Lo;0;L;;;;;N;;;;; A09B;YI SYLLABLE HMAP;Lo;0;L;;;;;N;;;;; A09C;YI SYLLABLE HMUOX;Lo;0;L;;;;;N;;;;; A09D;YI SYLLABLE HMUO;Lo;0;L;;;;;N;;;;; A09E;YI SYLLABLE HMUOP;Lo;0;L;;;;;N;;;;; A09F;YI SYLLABLE HMOT;Lo;0;L;;;;;N;;;;; A0A0;YI SYLLABLE HMOX;Lo;0;L;;;;;N;;;;; A0A1;YI SYLLABLE HMO;Lo;0;L;;;;;N;;;;; A0A2;YI SYLLABLE HMOP;Lo;0;L;;;;;N;;;;; A0A3;YI SYLLABLE HMUT;Lo;0;L;;;;;N;;;;; A0A4;YI SYLLABLE HMUX;Lo;0;L;;;;;N;;;;; A0A5;YI SYLLABLE HMU;Lo;0;L;;;;;N;;;;; A0A6;YI SYLLABLE HMUP;Lo;0;L;;;;;N;;;;; A0A7;YI SYLLABLE HMURX;Lo;0;L;;;;;N;;;;; A0A8;YI SYLLABLE HMUR;Lo;0;L;;;;;N;;;;; A0A9;YI SYLLABLE HMYX;Lo;0;L;;;;;N;;;;; A0AA;YI SYLLABLE HMY;Lo;0;L;;;;;N;;;;; A0AB;YI SYLLABLE HMYP;Lo;0;L;;;;;N;;;;; A0AC;YI SYLLABLE HMYRX;Lo;0;L;;;;;N;;;;; A0AD;YI SYLLABLE HMYR;Lo;0;L;;;;;N;;;;; A0AE;YI SYLLABLE MIT;Lo;0;L;;;;;N;;;;; A0AF;YI SYLLABLE MIX;Lo;0;L;;;;;N;;;;; A0B0;YI SYLLABLE MI;Lo;0;L;;;;;N;;;;; A0B1;YI SYLLABLE MIP;Lo;0;L;;;;;N;;;;; A0B2;YI SYLLABLE MIEX;Lo;0;L;;;;;N;;;;; A0B3;YI SYLLABLE MIE;Lo;0;L;;;;;N;;;;; A0B4;YI SYLLABLE MIEP;Lo;0;L;;;;;N;;;;; A0B5;YI SYLLABLE MAT;Lo;0;L;;;;;N;;;;; A0B6;YI SYLLABLE MAX;Lo;0;L;;;;;N;;;;; A0B7;YI SYLLABLE MA;Lo;0;L;;;;;N;;;;; A0B8;YI SYLLABLE MAP;Lo;0;L;;;;;N;;;;; A0B9;YI SYLLABLE MUOT;Lo;0;L;;;;;N;;;;; A0BA;YI SYLLABLE MUOX;Lo;0;L;;;;;N;;;;; A0BB;YI SYLLABLE MUO;Lo;0;L;;;;;N;;;;; A0BC;YI SYLLABLE MUOP;Lo;0;L;;;;;N;;;;; A0BD;YI SYLLABLE MOT;Lo;0;L;;;;;N;;;;; A0BE;YI SYLLABLE MOX;Lo;0;L;;;;;N;;;;; A0BF;YI SYLLABLE MO;Lo;0;L;;;;;N;;;;; A0C0;YI SYLLABLE MOP;Lo;0;L;;;;;N;;;;; A0C1;YI SYLLABLE MEX;Lo;0;L;;;;;N;;;;; A0C2;YI SYLLABLE ME;Lo;0;L;;;;;N;;;;; A0C3;YI SYLLABLE MUT;Lo;0;L;;;;;N;;;;; A0C4;YI SYLLABLE MUX;Lo;0;L;;;;;N;;;;; A0C5;YI SYLLABLE MU;Lo;0;L;;;;;N;;;;; A0C6;YI SYLLABLE MUP;Lo;0;L;;;;;N;;;;; A0C7;YI SYLLABLE MURX;Lo;0;L;;;;;N;;;;; A0C8;YI SYLLABLE MUR;Lo;0;L;;;;;N;;;;; A0C9;YI SYLLABLE MYT;Lo;0;L;;;;;N;;;;; A0CA;YI SYLLABLE MYX;Lo;0;L;;;;;N;;;;; A0CB;YI SYLLABLE MY;Lo;0;L;;;;;N;;;;; A0CC;YI SYLLABLE MYP;Lo;0;L;;;;;N;;;;; A0CD;YI SYLLABLE FIT;Lo;0;L;;;;;N;;;;; A0CE;YI SYLLABLE FIX;Lo;0;L;;;;;N;;;;; A0CF;YI SYLLABLE FI;Lo;0;L;;;;;N;;;;; A0D0;YI SYLLABLE FIP;Lo;0;L;;;;;N;;;;; A0D1;YI SYLLABLE FAT;Lo;0;L;;;;;N;;;;; A0D2;YI SYLLABLE FAX;Lo;0;L;;;;;N;;;;; A0D3;YI SYLLABLE FA;Lo;0;L;;;;;N;;;;; A0D4;YI SYLLABLE FAP;Lo;0;L;;;;;N;;;;; A0D5;YI SYLLABLE FOX;Lo;0;L;;;;;N;;;;; A0D6;YI SYLLABLE FO;Lo;0;L;;;;;N;;;;; A0D7;YI SYLLABLE FOP;Lo;0;L;;;;;N;;;;; A0D8;YI SYLLABLE FUT;Lo;0;L;;;;;N;;;;; A0D9;YI SYLLABLE FUX;Lo;0;L;;;;;N;;;;; A0DA;YI SYLLABLE FU;Lo;0;L;;;;;N;;;;; A0DB;YI SYLLABLE FUP;Lo;0;L;;;;;N;;;;; A0DC;YI SYLLABLE FURX;Lo;0;L;;;;;N;;;;; A0DD;YI SYLLABLE FUR;Lo;0;L;;;;;N;;;;; A0DE;YI SYLLABLE FYT;Lo;0;L;;;;;N;;;;; A0DF;YI SYLLABLE FYX;Lo;0;L;;;;;N;;;;; A0E0;YI SYLLABLE FY;Lo;0;L;;;;;N;;;;; A0E1;YI SYLLABLE FYP;Lo;0;L;;;;;N;;;;; A0E2;YI SYLLABLE VIT;Lo;0;L;;;;;N;;;;; A0E3;YI SYLLABLE VIX;Lo;0;L;;;;;N;;;;; A0E4;YI SYLLABLE VI;Lo;0;L;;;;;N;;;;; A0E5;YI SYLLABLE VIP;Lo;0;L;;;;;N;;;;; A0E6;YI SYLLABLE VIET;Lo;0;L;;;;;N;;;;; A0E7;YI SYLLABLE VIEX;Lo;0;L;;;;;N;;;;; A0E8;YI SYLLABLE VIE;Lo;0;L;;;;;N;;;;; A0E9;YI SYLLABLE VIEP;Lo;0;L;;;;;N;;;;; A0EA;YI SYLLABLE VAT;Lo;0;L;;;;;N;;;;; A0EB;YI SYLLABLE VAX;Lo;0;L;;;;;N;;;;; A0EC;YI SYLLABLE VA;Lo;0;L;;;;;N;;;;; A0ED;YI SYLLABLE VAP;Lo;0;L;;;;;N;;;;; A0EE;YI SYLLABLE VOT;Lo;0;L;;;;;N;;;;; A0EF;YI SYLLABLE VOX;Lo;0;L;;;;;N;;;;; A0F0;YI SYLLABLE VO;Lo;0;L;;;;;N;;;;; A0F1;YI SYLLABLE VOP;Lo;0;L;;;;;N;;;;; A0F2;YI SYLLABLE VEX;Lo;0;L;;;;;N;;;;; A0F3;YI SYLLABLE VEP;Lo;0;L;;;;;N;;;;; A0F4;YI SYLLABLE VUT;Lo;0;L;;;;;N;;;;; A0F5;YI SYLLABLE VUX;Lo;0;L;;;;;N;;;;; A0F6;YI SYLLABLE VU;Lo;0;L;;;;;N;;;;; A0F7;YI SYLLABLE VUP;Lo;0;L;;;;;N;;;;; A0F8;YI SYLLABLE VURX;Lo;0;L;;;;;N;;;;; A0F9;YI SYLLABLE VUR;Lo;0;L;;;;;N;;;;; A0FA;YI SYLLABLE VYT;Lo;0;L;;;;;N;;;;; A0FB;YI SYLLABLE VYX;Lo;0;L;;;;;N;;;;; A0FC;YI SYLLABLE VY;Lo;0;L;;;;;N;;;;; A0FD;YI SYLLABLE VYP;Lo;0;L;;;;;N;;;;; A0FE;YI SYLLABLE VYRX;Lo;0;L;;;;;N;;;;; A0FF;YI SYLLABLE VYR;Lo;0;L;;;;;N;;;;; A100;YI SYLLABLE DIT;Lo;0;L;;;;;N;;;;; A101;YI SYLLABLE DIX;Lo;0;L;;;;;N;;;;; A102;YI SYLLABLE DI;Lo;0;L;;;;;N;;;;; A103;YI SYLLABLE DIP;Lo;0;L;;;;;N;;;;; A104;YI SYLLABLE DIEX;Lo;0;L;;;;;N;;;;; A105;YI SYLLABLE DIE;Lo;0;L;;;;;N;;;;; A106;YI SYLLABLE DIEP;Lo;0;L;;;;;N;;;;; A107;YI SYLLABLE DAT;Lo;0;L;;;;;N;;;;; A108;YI SYLLABLE DAX;Lo;0;L;;;;;N;;;;; A109;YI SYLLABLE DA;Lo;0;L;;;;;N;;;;; A10A;YI SYLLABLE DAP;Lo;0;L;;;;;N;;;;; A10B;YI SYLLABLE DUOX;Lo;0;L;;;;;N;;;;; A10C;YI SYLLABLE DUO;Lo;0;L;;;;;N;;;;; A10D;YI SYLLABLE DOT;Lo;0;L;;;;;N;;;;; A10E;YI SYLLABLE DOX;Lo;0;L;;;;;N;;;;; A10F;YI SYLLABLE DO;Lo;0;L;;;;;N;;;;; A110;YI SYLLABLE DOP;Lo;0;L;;;;;N;;;;; A111;YI SYLLABLE DEX;Lo;0;L;;;;;N;;;;; A112;YI SYLLABLE DE;Lo;0;L;;;;;N;;;;; A113;YI SYLLABLE DEP;Lo;0;L;;;;;N;;;;; A114;YI SYLLABLE DUT;Lo;0;L;;;;;N;;;;; A115;YI SYLLABLE DUX;Lo;0;L;;;;;N;;;;; A116;YI SYLLABLE DU;Lo;0;L;;;;;N;;;;; A117;YI SYLLABLE DUP;Lo;0;L;;;;;N;;;;; A118;YI SYLLABLE DURX;Lo;0;L;;;;;N;;;;; A119;YI SYLLABLE DUR;Lo;0;L;;;;;N;;;;; A11A;YI SYLLABLE TIT;Lo;0;L;;;;;N;;;;; A11B;YI SYLLABLE TIX;Lo;0;L;;;;;N;;;;; A11C;YI SYLLABLE TI;Lo;0;L;;;;;N;;;;; A11D;YI SYLLABLE TIP;Lo;0;L;;;;;N;;;;; A11E;YI SYLLABLE TIEX;Lo;0;L;;;;;N;;;;; A11F;YI SYLLABLE TIE;Lo;0;L;;;;;N;;;;; A120;YI SYLLABLE TIEP;Lo;0;L;;;;;N;;;;; A121;YI SYLLABLE TAT;Lo;0;L;;;;;N;;;;; A122;YI SYLLABLE TAX;Lo;0;L;;;;;N;;;;; A123;YI SYLLABLE TA;Lo;0;L;;;;;N;;;;; A124;YI SYLLABLE TAP;Lo;0;L;;;;;N;;;;; A125;YI SYLLABLE TUOT;Lo;0;L;;;;;N;;;;; A126;YI SYLLABLE TUOX;Lo;0;L;;;;;N;;;;; A127;YI SYLLABLE TUO;Lo;0;L;;;;;N;;;;; A128;YI SYLLABLE TUOP;Lo;0;L;;;;;N;;;;; A129;YI SYLLABLE TOT;Lo;0;L;;;;;N;;;;; A12A;YI SYLLABLE TOX;Lo;0;L;;;;;N;;;;; A12B;YI SYLLABLE TO;Lo;0;L;;;;;N;;;;; A12C;YI SYLLABLE TOP;Lo;0;L;;;;;N;;;;; A12D;YI SYLLABLE TEX;Lo;0;L;;;;;N;;;;; A12E;YI SYLLABLE TE;Lo;0;L;;;;;N;;;;; A12F;YI SYLLABLE TEP;Lo;0;L;;;;;N;;;;; A130;YI SYLLABLE TUT;Lo;0;L;;;;;N;;;;; A131;YI SYLLABLE TUX;Lo;0;L;;;;;N;;;;; A132;YI SYLLABLE TU;Lo;0;L;;;;;N;;;;; A133;YI SYLLABLE TUP;Lo;0;L;;;;;N;;;;; A134;YI SYLLABLE TURX;Lo;0;L;;;;;N;;;;; A135;YI SYLLABLE TUR;Lo;0;L;;;;;N;;;;; A136;YI SYLLABLE DDIT;Lo;0;L;;;;;N;;;;; A137;YI SYLLABLE DDIX;Lo;0;L;;;;;N;;;;; A138;YI SYLLABLE DDI;Lo;0;L;;;;;N;;;;; A139;YI SYLLABLE DDIP;Lo;0;L;;;;;N;;;;; A13A;YI SYLLABLE DDIEX;Lo;0;L;;;;;N;;;;; A13B;YI SYLLABLE DDIE;Lo;0;L;;;;;N;;;;; A13C;YI SYLLABLE DDIEP;Lo;0;L;;;;;N;;;;; A13D;YI SYLLABLE DDAT;Lo;0;L;;;;;N;;;;; A13E;YI SYLLABLE DDAX;Lo;0;L;;;;;N;;;;; A13F;YI SYLLABLE DDA;Lo;0;L;;;;;N;;;;; A140;YI SYLLABLE DDAP;Lo;0;L;;;;;N;;;;; A141;YI SYLLABLE DDUOX;Lo;0;L;;;;;N;;;;; A142;YI SYLLABLE DDUO;Lo;0;L;;;;;N;;;;; A143;YI SYLLABLE DDUOP;Lo;0;L;;;;;N;;;;; A144;YI SYLLABLE DDOT;Lo;0;L;;;;;N;;;;; A145;YI SYLLABLE DDOX;Lo;0;L;;;;;N;;;;; A146;YI SYLLABLE DDO;Lo;0;L;;;;;N;;;;; A147;YI SYLLABLE DDOP;Lo;0;L;;;;;N;;;;; A148;YI SYLLABLE DDEX;Lo;0;L;;;;;N;;;;; A149;YI SYLLABLE DDE;Lo;0;L;;;;;N;;;;; A14A;YI SYLLABLE DDEP;Lo;0;L;;;;;N;;;;; A14B;YI SYLLABLE DDUT;Lo;0;L;;;;;N;;;;; A14C;YI SYLLABLE DDUX;Lo;0;L;;;;;N;;;;; A14D;YI SYLLABLE DDU;Lo;0;L;;;;;N;;;;; A14E;YI SYLLABLE DDUP;Lo;0;L;;;;;N;;;;; A14F;YI SYLLABLE DDURX;Lo;0;L;;;;;N;;;;; A150;YI SYLLABLE DDUR;Lo;0;L;;;;;N;;;;; A151;YI SYLLABLE NDIT;Lo;0;L;;;;;N;;;;; A152;YI SYLLABLE NDIX;Lo;0;L;;;;;N;;;;; A153;YI SYLLABLE NDI;Lo;0;L;;;;;N;;;;; A154;YI SYLLABLE NDIP;Lo;0;L;;;;;N;;;;; A155;YI SYLLABLE NDIEX;Lo;0;L;;;;;N;;;;; A156;YI SYLLABLE NDIE;Lo;0;L;;;;;N;;;;; A157;YI SYLLABLE NDAT;Lo;0;L;;;;;N;;;;; A158;YI SYLLABLE NDAX;Lo;0;L;;;;;N;;;;; A159;YI SYLLABLE NDA;Lo;0;L;;;;;N;;;;; A15A;YI SYLLABLE NDAP;Lo;0;L;;;;;N;;;;; A15B;YI SYLLABLE NDOT;Lo;0;L;;;;;N;;;;; A15C;YI SYLLABLE NDOX;Lo;0;L;;;;;N;;;;; A15D;YI SYLLABLE NDO;Lo;0;L;;;;;N;;;;; A15E;YI SYLLABLE NDOP;Lo;0;L;;;;;N;;;;; A15F;YI SYLLABLE NDEX;Lo;0;L;;;;;N;;;;; A160;YI SYLLABLE NDE;Lo;0;L;;;;;N;;;;; A161;YI SYLLABLE NDEP;Lo;0;L;;;;;N;;;;; A162;YI SYLLABLE NDUT;Lo;0;L;;;;;N;;;;; A163;YI SYLLABLE NDUX;Lo;0;L;;;;;N;;;;; A164;YI SYLLABLE NDU;Lo;0;L;;;;;N;;;;; A165;YI SYLLABLE NDUP;Lo;0;L;;;;;N;;;;; A166;YI SYLLABLE NDURX;Lo;0;L;;;;;N;;;;; A167;YI SYLLABLE NDUR;Lo;0;L;;;;;N;;;;; A168;YI SYLLABLE HNIT;Lo;0;L;;;;;N;;;;; A169;YI SYLLABLE HNIX;Lo;0;L;;;;;N;;;;; A16A;YI SYLLABLE HNI;Lo;0;L;;;;;N;;;;; A16B;YI SYLLABLE HNIP;Lo;0;L;;;;;N;;;;; A16C;YI SYLLABLE HNIET;Lo;0;L;;;;;N;;;;; A16D;YI SYLLABLE HNIEX;Lo;0;L;;;;;N;;;;; A16E;YI SYLLABLE HNIE;Lo;0;L;;;;;N;;;;; A16F;YI SYLLABLE HNIEP;Lo;0;L;;;;;N;;;;; A170;YI SYLLABLE HNAT;Lo;0;L;;;;;N;;;;; A171;YI SYLLABLE HNAX;Lo;0;L;;;;;N;;;;; A172;YI SYLLABLE HNA;Lo;0;L;;;;;N;;;;; A173;YI SYLLABLE HNAP;Lo;0;L;;;;;N;;;;; A174;YI SYLLABLE HNUOX;Lo;0;L;;;;;N;;;;; A175;YI SYLLABLE HNUO;Lo;0;L;;;;;N;;;;; A176;YI SYLLABLE HNOT;Lo;0;L;;;;;N;;;;; A177;YI SYLLABLE HNOX;Lo;0;L;;;;;N;;;;; A178;YI SYLLABLE HNOP;Lo;0;L;;;;;N;;;;; A179;YI SYLLABLE HNEX;Lo;0;L;;;;;N;;;;; A17A;YI SYLLABLE HNE;Lo;0;L;;;;;N;;;;; A17B;YI SYLLABLE HNEP;Lo;0;L;;;;;N;;;;; A17C;YI SYLLABLE HNUT;Lo;0;L;;;;;N;;;;; A17D;YI SYLLABLE NIT;Lo;0;L;;;;;N;;;;; A17E;YI SYLLABLE NIX;Lo;0;L;;;;;N;;;;; A17F;YI SYLLABLE NI;Lo;0;L;;;;;N;;;;; A180;YI SYLLABLE NIP;Lo;0;L;;;;;N;;;;; A181;YI SYLLABLE NIEX;Lo;0;L;;;;;N;;;;; A182;YI SYLLABLE NIE;Lo;0;L;;;;;N;;;;; A183;YI SYLLABLE NIEP;Lo;0;L;;;;;N;;;;; A184;YI SYLLABLE NAX;Lo;0;L;;;;;N;;;;; A185;YI SYLLABLE NA;Lo;0;L;;;;;N;;;;; A186;YI SYLLABLE NAP;Lo;0;L;;;;;N;;;;; A187;YI SYLLABLE NUOX;Lo;0;L;;;;;N;;;;; A188;YI SYLLABLE NUO;Lo;0;L;;;;;N;;;;; A189;YI SYLLABLE NUOP;Lo;0;L;;;;;N;;;;; A18A;YI SYLLABLE NOT;Lo;0;L;;;;;N;;;;; A18B;YI SYLLABLE NOX;Lo;0;L;;;;;N;;;;; A18C;YI SYLLABLE NO;Lo;0;L;;;;;N;;;;; A18D;YI SYLLABLE NOP;Lo;0;L;;;;;N;;;;; A18E;YI SYLLABLE NEX;Lo;0;L;;;;;N;;;;; A18F;YI SYLLABLE NE;Lo;0;L;;;;;N;;;;; A190;YI SYLLABLE NEP;Lo;0;L;;;;;N;;;;; A191;YI SYLLABLE NUT;Lo;0;L;;;;;N;;;;; A192;YI SYLLABLE NUX;Lo;0;L;;;;;N;;;;; A193;YI SYLLABLE NU;Lo;0;L;;;;;N;;;;; A194;YI SYLLABLE NUP;Lo;0;L;;;;;N;;;;; A195;YI SYLLABLE NURX;Lo;0;L;;;;;N;;;;; A196;YI SYLLABLE NUR;Lo;0;L;;;;;N;;;;; A197;YI SYLLABLE HLIT;Lo;0;L;;;;;N;;;;; A198;YI SYLLABLE HLIX;Lo;0;L;;;;;N;;;;; A199;YI SYLLABLE HLI;Lo;0;L;;;;;N;;;;; A19A;YI SYLLABLE HLIP;Lo;0;L;;;;;N;;;;; A19B;YI SYLLABLE HLIEX;Lo;0;L;;;;;N;;;;; A19C;YI SYLLABLE HLIE;Lo;0;L;;;;;N;;;;; A19D;YI SYLLABLE HLIEP;Lo;0;L;;;;;N;;;;; A19E;YI SYLLABLE HLAT;Lo;0;L;;;;;N;;;;; A19F;YI SYLLABLE HLAX;Lo;0;L;;;;;N;;;;; A1A0;YI SYLLABLE HLA;Lo;0;L;;;;;N;;;;; A1A1;YI SYLLABLE HLAP;Lo;0;L;;;;;N;;;;; A1A2;YI SYLLABLE HLUOX;Lo;0;L;;;;;N;;;;; A1A3;YI SYLLABLE HLUO;Lo;0;L;;;;;N;;;;; A1A4;YI SYLLABLE HLUOP;Lo;0;L;;;;;N;;;;; A1A5;YI SYLLABLE HLOX;Lo;0;L;;;;;N;;;;; A1A6;YI SYLLABLE HLO;Lo;0;L;;;;;N;;;;; A1A7;YI SYLLABLE HLOP;Lo;0;L;;;;;N;;;;; A1A8;YI SYLLABLE HLEX;Lo;0;L;;;;;N;;;;; A1A9;YI SYLLABLE HLE;Lo;0;L;;;;;N;;;;; A1AA;YI SYLLABLE HLEP;Lo;0;L;;;;;N;;;;; A1AB;YI SYLLABLE HLUT;Lo;0;L;;;;;N;;;;; A1AC;YI SYLLABLE HLUX;Lo;0;L;;;;;N;;;;; A1AD;YI SYLLABLE HLU;Lo;0;L;;;;;N;;;;; A1AE;YI SYLLABLE HLUP;Lo;0;L;;;;;N;;;;; A1AF;YI SYLLABLE HLURX;Lo;0;L;;;;;N;;;;; A1B0;YI SYLLABLE HLUR;Lo;0;L;;;;;N;;;;; A1B1;YI SYLLABLE HLYT;Lo;0;L;;;;;N;;;;; A1B2;YI SYLLABLE HLYX;Lo;0;L;;;;;N;;;;; A1B3;YI SYLLABLE HLY;Lo;0;L;;;;;N;;;;; A1B4;YI SYLLABLE HLYP;Lo;0;L;;;;;N;;;;; A1B5;YI SYLLABLE HLYRX;Lo;0;L;;;;;N;;;;; A1B6;YI SYLLABLE HLYR;Lo;0;L;;;;;N;;;;; A1B7;YI SYLLABLE LIT;Lo;0;L;;;;;N;;;;; A1B8;YI SYLLABLE LIX;Lo;0;L;;;;;N;;;;; A1B9;YI SYLLABLE LI;Lo;0;L;;;;;N;;;;; A1BA;YI SYLLABLE LIP;Lo;0;L;;;;;N;;;;; A1BB;YI SYLLABLE LIET;Lo;0;L;;;;;N;;;;; A1BC;YI SYLLABLE LIEX;Lo;0;L;;;;;N;;;;; A1BD;YI SYLLABLE LIE;Lo;0;L;;;;;N;;;;; A1BE;YI SYLLABLE LIEP;Lo;0;L;;;;;N;;;;; A1BF;YI SYLLABLE LAT;Lo;0;L;;;;;N;;;;; A1C0;YI SYLLABLE LAX;Lo;0;L;;;;;N;;;;; A1C1;YI SYLLABLE LA;Lo;0;L;;;;;N;;;;; A1C2;YI SYLLABLE LAP;Lo;0;L;;;;;N;;;;; A1C3;YI SYLLABLE LUOT;Lo;0;L;;;;;N;;;;; A1C4;YI SYLLABLE LUOX;Lo;0;L;;;;;N;;;;; A1C5;YI SYLLABLE LUO;Lo;0;L;;;;;N;;;;; A1C6;YI SYLLABLE LUOP;Lo;0;L;;;;;N;;;;; A1C7;YI SYLLABLE LOT;Lo;0;L;;;;;N;;;;; A1C8;YI SYLLABLE LOX;Lo;0;L;;;;;N;;;;; A1C9;YI SYLLABLE LO;Lo;0;L;;;;;N;;;;; A1CA;YI SYLLABLE LOP;Lo;0;L;;;;;N;;;;; A1CB;YI SYLLABLE LEX;Lo;0;L;;;;;N;;;;; A1CC;YI SYLLABLE LE;Lo;0;L;;;;;N;;;;; A1CD;YI SYLLABLE LEP;Lo;0;L;;;;;N;;;;; A1CE;YI SYLLABLE LUT;Lo;0;L;;;;;N;;;;; A1CF;YI SYLLABLE LUX;Lo;0;L;;;;;N;;;;; A1D0;YI SYLLABLE LU;Lo;0;L;;;;;N;;;;; A1D1;YI SYLLABLE LUP;Lo;0;L;;;;;N;;;;; A1D2;YI SYLLABLE LURX;Lo;0;L;;;;;N;;;;; A1D3;YI SYLLABLE LUR;Lo;0;L;;;;;N;;;;; A1D4;YI SYLLABLE LYT;Lo;0;L;;;;;N;;;;; A1D5;YI SYLLABLE LYX;Lo;0;L;;;;;N;;;;; A1D6;YI SYLLABLE LY;Lo;0;L;;;;;N;;;;; A1D7;YI SYLLABLE LYP;Lo;0;L;;;;;N;;;;; A1D8;YI SYLLABLE LYRX;Lo;0;L;;;;;N;;;;; A1D9;YI SYLLABLE LYR;Lo;0;L;;;;;N;;;;; A1DA;YI SYLLABLE GIT;Lo;0;L;;;;;N;;;;; A1DB;YI SYLLABLE GIX;Lo;0;L;;;;;N;;;;; A1DC;YI SYLLABLE GI;Lo;0;L;;;;;N;;;;; A1DD;YI SYLLABLE GIP;Lo;0;L;;;;;N;;;;; A1DE;YI SYLLABLE GIET;Lo;0;L;;;;;N;;;;; A1DF;YI SYLLABLE GIEX;Lo;0;L;;;;;N;;;;; A1E0;YI SYLLABLE GIE;Lo;0;L;;;;;N;;;;; A1E1;YI SYLLABLE GIEP;Lo;0;L;;;;;N;;;;; A1E2;YI SYLLABLE GAT;Lo;0;L;;;;;N;;;;; A1E3;YI SYLLABLE GAX;Lo;0;L;;;;;N;;;;; A1E4;YI SYLLABLE GA;Lo;0;L;;;;;N;;;;; A1E5;YI SYLLABLE GAP;Lo;0;L;;;;;N;;;;; A1E6;YI SYLLABLE GUOT;Lo;0;L;;;;;N;;;;; A1E7;YI SYLLABLE GUOX;Lo;0;L;;;;;N;;;;; A1E8;YI SYLLABLE GUO;Lo;0;L;;;;;N;;;;; A1E9;YI SYLLABLE GUOP;Lo;0;L;;;;;N;;;;; A1EA;YI SYLLABLE GOT;Lo;0;L;;;;;N;;;;; A1EB;YI SYLLABLE GOX;Lo;0;L;;;;;N;;;;; A1EC;YI SYLLABLE GO;Lo;0;L;;;;;N;;;;; A1ED;YI SYLLABLE GOP;Lo;0;L;;;;;N;;;;; A1EE;YI SYLLABLE GET;Lo;0;L;;;;;N;;;;; A1EF;YI SYLLABLE GEX;Lo;0;L;;;;;N;;;;; A1F0;YI SYLLABLE GE;Lo;0;L;;;;;N;;;;; A1F1;YI SYLLABLE GEP;Lo;0;L;;;;;N;;;;; A1F2;YI SYLLABLE GUT;Lo;0;L;;;;;N;;;;; A1F3;YI SYLLABLE GUX;Lo;0;L;;;;;N;;;;; A1F4;YI SYLLABLE GU;Lo;0;L;;;;;N;;;;; A1F5;YI SYLLABLE GUP;Lo;0;L;;;;;N;;;;; A1F6;YI SYLLABLE GURX;Lo;0;L;;;;;N;;;;; A1F7;YI SYLLABLE GUR;Lo;0;L;;;;;N;;;;; A1F8;YI SYLLABLE KIT;Lo;0;L;;;;;N;;;;; A1F9;YI SYLLABLE KIX;Lo;0;L;;;;;N;;;;; A1FA;YI SYLLABLE KI;Lo;0;L;;;;;N;;;;; A1FB;YI SYLLABLE KIP;Lo;0;L;;;;;N;;;;; A1FC;YI SYLLABLE KIEX;Lo;0;L;;;;;N;;;;; A1FD;YI SYLLABLE KIE;Lo;0;L;;;;;N;;;;; A1FE;YI SYLLABLE KIEP;Lo;0;L;;;;;N;;;;; A1FF;YI SYLLABLE KAT;Lo;0;L;;;;;N;;;;; A200;YI SYLLABLE KAX;Lo;0;L;;;;;N;;;;; A201;YI SYLLABLE KA;Lo;0;L;;;;;N;;;;; A202;YI SYLLABLE KAP;Lo;0;L;;;;;N;;;;; A203;YI SYLLABLE KUOX;Lo;0;L;;;;;N;;;;; A204;YI SYLLABLE KUO;Lo;0;L;;;;;N;;;;; A205;YI SYLLABLE KUOP;Lo;0;L;;;;;N;;;;; A206;YI SYLLABLE KOT;Lo;0;L;;;;;N;;;;; A207;YI SYLLABLE KOX;Lo;0;L;;;;;N;;;;; A208;YI SYLLABLE KO;Lo;0;L;;;;;N;;;;; A209;YI SYLLABLE KOP;Lo;0;L;;;;;N;;;;; A20A;YI SYLLABLE KET;Lo;0;L;;;;;N;;;;; A20B;YI SYLLABLE KEX;Lo;0;L;;;;;N;;;;; A20C;YI SYLLABLE KE;Lo;0;L;;;;;N;;;;; A20D;YI SYLLABLE KEP;Lo;0;L;;;;;N;;;;; A20E;YI SYLLABLE KUT;Lo;0;L;;;;;N;;;;; A20F;YI SYLLABLE KUX;Lo;0;L;;;;;N;;;;; A210;YI SYLLABLE KU;Lo;0;L;;;;;N;;;;; A211;YI SYLLABLE KUP;Lo;0;L;;;;;N;;;;; A212;YI SYLLABLE KURX;Lo;0;L;;;;;N;;;;; A213;YI SYLLABLE KUR;Lo;0;L;;;;;N;;;;; A214;YI SYLLABLE GGIT;Lo;0;L;;;;;N;;;;; A215;YI SYLLABLE GGIX;Lo;0;L;;;;;N;;;;; A216;YI SYLLABLE GGI;Lo;0;L;;;;;N;;;;; A217;YI SYLLABLE GGIEX;Lo;0;L;;;;;N;;;;; A218;YI SYLLABLE GGIE;Lo;0;L;;;;;N;;;;; A219;YI SYLLABLE GGIEP;Lo;0;L;;;;;N;;;;; A21A;YI SYLLABLE GGAT;Lo;0;L;;;;;N;;;;; A21B;YI SYLLABLE GGAX;Lo;0;L;;;;;N;;;;; A21C;YI SYLLABLE GGA;Lo;0;L;;;;;N;;;;; A21D;YI SYLLABLE GGAP;Lo;0;L;;;;;N;;;;; A21E;YI SYLLABLE GGUOT;Lo;0;L;;;;;N;;;;; A21F;YI SYLLABLE GGUOX;Lo;0;L;;;;;N;;;;; A220;YI SYLLABLE GGUO;Lo;0;L;;;;;N;;;;; A221;YI SYLLABLE GGUOP;Lo;0;L;;;;;N;;;;; A222;YI SYLLABLE GGOT;Lo;0;L;;;;;N;;;;; A223;YI SYLLABLE GGOX;Lo;0;L;;;;;N;;;;; A224;YI SYLLABLE GGO;Lo;0;L;;;;;N;;;;; A225;YI SYLLABLE GGOP;Lo;0;L;;;;;N;;;;; A226;YI SYLLABLE GGET;Lo;0;L;;;;;N;;;;; A227;YI SYLLABLE GGEX;Lo;0;L;;;;;N;;;;; A228;YI SYLLABLE GGE;Lo;0;L;;;;;N;;;;; A229;YI SYLLABLE GGEP;Lo;0;L;;;;;N;;;;; A22A;YI SYLLABLE GGUT;Lo;0;L;;;;;N;;;;; A22B;YI SYLLABLE GGUX;Lo;0;L;;;;;N;;;;; A22C;YI SYLLABLE GGU;Lo;0;L;;;;;N;;;;; A22D;YI SYLLABLE GGUP;Lo;0;L;;;;;N;;;;; A22E;YI SYLLABLE GGURX;Lo;0;L;;;;;N;;;;; A22F;YI SYLLABLE GGUR;Lo;0;L;;;;;N;;;;; A230;YI SYLLABLE MGIEX;Lo;0;L;;;;;N;;;;; A231;YI SYLLABLE MGIE;Lo;0;L;;;;;N;;;;; A232;YI SYLLABLE MGAT;Lo;0;L;;;;;N;;;;; A233;YI SYLLABLE MGAX;Lo;0;L;;;;;N;;;;; A234;YI SYLLABLE MGA;Lo;0;L;;;;;N;;;;; A235;YI SYLLABLE MGAP;Lo;0;L;;;;;N;;;;; A236;YI SYLLABLE MGUOX;Lo;0;L;;;;;N;;;;; A237;YI SYLLABLE MGUO;Lo;0;L;;;;;N;;;;; A238;YI SYLLABLE MGUOP;Lo;0;L;;;;;N;;;;; A239;YI SYLLABLE MGOT;Lo;0;L;;;;;N;;;;; A23A;YI SYLLABLE MGOX;Lo;0;L;;;;;N;;;;; A23B;YI SYLLABLE MGO;Lo;0;L;;;;;N;;;;; A23C;YI SYLLABLE MGOP;Lo;0;L;;;;;N;;;;; A23D;YI SYLLABLE MGEX;Lo;0;L;;;;;N;;;;; A23E;YI SYLLABLE MGE;Lo;0;L;;;;;N;;;;; A23F;YI SYLLABLE MGEP;Lo;0;L;;;;;N;;;;; A240;YI SYLLABLE MGUT;Lo;0;L;;;;;N;;;;; A241;YI SYLLABLE MGUX;Lo;0;L;;;;;N;;;;; A242;YI SYLLABLE MGU;Lo;0;L;;;;;N;;;;; A243;YI SYLLABLE MGUP;Lo;0;L;;;;;N;;;;; A244;YI SYLLABLE MGURX;Lo;0;L;;;;;N;;;;; A245;YI SYLLABLE MGUR;Lo;0;L;;;;;N;;;;; A246;YI SYLLABLE HXIT;Lo;0;L;;;;;N;;;;; A247;YI SYLLABLE HXIX;Lo;0;L;;;;;N;;;;; A248;YI SYLLABLE HXI;Lo;0;L;;;;;N;;;;; A249;YI SYLLABLE HXIP;Lo;0;L;;;;;N;;;;; A24A;YI SYLLABLE HXIET;Lo;0;L;;;;;N;;;;; A24B;YI SYLLABLE HXIEX;Lo;0;L;;;;;N;;;;; A24C;YI SYLLABLE HXIE;Lo;0;L;;;;;N;;;;; A24D;YI SYLLABLE HXIEP;Lo;0;L;;;;;N;;;;; A24E;YI SYLLABLE HXAT;Lo;0;L;;;;;N;;;;; A24F;YI SYLLABLE HXAX;Lo;0;L;;;;;N;;;;; A250;YI SYLLABLE HXA;Lo;0;L;;;;;N;;;;; A251;YI SYLLABLE HXAP;Lo;0;L;;;;;N;;;;; A252;YI SYLLABLE HXUOT;Lo;0;L;;;;;N;;;;; A253;YI SYLLABLE HXUOX;Lo;0;L;;;;;N;;;;; A254;YI SYLLABLE HXUO;Lo;0;L;;;;;N;;;;; A255;YI SYLLABLE HXUOP;Lo;0;L;;;;;N;;;;; A256;YI SYLLABLE HXOT;Lo;0;L;;;;;N;;;;; A257;YI SYLLABLE HXOX;Lo;0;L;;;;;N;;;;; A258;YI SYLLABLE HXO;Lo;0;L;;;;;N;;;;; A259;YI SYLLABLE HXOP;Lo;0;L;;;;;N;;;;; A25A;YI SYLLABLE HXEX;Lo;0;L;;;;;N;;;;; A25B;YI SYLLABLE HXE;Lo;0;L;;;;;N;;;;; A25C;YI SYLLABLE HXEP;Lo;0;L;;;;;N;;;;; A25D;YI SYLLABLE NGIEX;Lo;0;L;;;;;N;;;;; A25E;YI SYLLABLE NGIE;Lo;0;L;;;;;N;;;;; A25F;YI SYLLABLE NGIEP;Lo;0;L;;;;;N;;;;; A260;YI SYLLABLE NGAT;Lo;0;L;;;;;N;;;;; A261;YI SYLLABLE NGAX;Lo;0;L;;;;;N;;;;; A262;YI SYLLABLE NGA;Lo;0;L;;;;;N;;;;; A263;YI SYLLABLE NGAP;Lo;0;L;;;;;N;;;;; A264;YI SYLLABLE NGUOT;Lo;0;L;;;;;N;;;;; A265;YI SYLLABLE NGUOX;Lo;0;L;;;;;N;;;;; A266;YI SYLLABLE NGUO;Lo;0;L;;;;;N;;;;; A267;YI SYLLABLE NGOT;Lo;0;L;;;;;N;;;;; A268;YI SYLLABLE NGOX;Lo;0;L;;;;;N;;;;; A269;YI SYLLABLE NGO;Lo;0;L;;;;;N;;;;; A26A;YI SYLLABLE NGOP;Lo;0;L;;;;;N;;;;; A26B;YI SYLLABLE NGEX;Lo;0;L;;;;;N;;;;; A26C;YI SYLLABLE NGE;Lo;0;L;;;;;N;;;;; A26D;YI SYLLABLE NGEP;Lo;0;L;;;;;N;;;;; A26E;YI SYLLABLE HIT;Lo;0;L;;;;;N;;;;; A26F;YI SYLLABLE HIEX;Lo;0;L;;;;;N;;;;; A270;YI SYLLABLE HIE;Lo;0;L;;;;;N;;;;; A271;YI SYLLABLE HAT;Lo;0;L;;;;;N;;;;; A272;YI SYLLABLE HAX;Lo;0;L;;;;;N;;;;; A273;YI SYLLABLE HA;Lo;0;L;;;;;N;;;;; A274;YI SYLLABLE HAP;Lo;0;L;;;;;N;;;;; A275;YI SYLLABLE HUOT;Lo;0;L;;;;;N;;;;; A276;YI SYLLABLE HUOX;Lo;0;L;;;;;N;;;;; A277;YI SYLLABLE HUO;Lo;0;L;;;;;N;;;;; A278;YI SYLLABLE HUOP;Lo;0;L;;;;;N;;;;; A279;YI SYLLABLE HOT;Lo;0;L;;;;;N;;;;; A27A;YI SYLLABLE HOX;Lo;0;L;;;;;N;;;;; A27B;YI SYLLABLE HO;Lo;0;L;;;;;N;;;;; A27C;YI SYLLABLE HOP;Lo;0;L;;;;;N;;;;; A27D;YI SYLLABLE HEX;Lo;0;L;;;;;N;;;;; A27E;YI SYLLABLE HE;Lo;0;L;;;;;N;;;;; A27F;YI SYLLABLE HEP;Lo;0;L;;;;;N;;;;; A280;YI SYLLABLE WAT;Lo;0;L;;;;;N;;;;; A281;YI SYLLABLE WAX;Lo;0;L;;;;;N;;;;; A282;YI SYLLABLE WA;Lo;0;L;;;;;N;;;;; A283;YI SYLLABLE WAP;Lo;0;L;;;;;N;;;;; A284;YI SYLLABLE WUOX;Lo;0;L;;;;;N;;;;; A285;YI SYLLABLE WUO;Lo;0;L;;;;;N;;;;; A286;YI SYLLABLE WUOP;Lo;0;L;;;;;N;;;;; A287;YI SYLLABLE WOX;Lo;0;L;;;;;N;;;;; A288;YI SYLLABLE WO;Lo;0;L;;;;;N;;;;; A289;YI SYLLABLE WOP;Lo;0;L;;;;;N;;;;; A28A;YI SYLLABLE WEX;Lo;0;L;;;;;N;;;;; A28B;YI SYLLABLE WE;Lo;0;L;;;;;N;;;;; A28C;YI SYLLABLE WEP;Lo;0;L;;;;;N;;;;; A28D;YI SYLLABLE ZIT;Lo;0;L;;;;;N;;;;; A28E;YI SYLLABLE ZIX;Lo;0;L;;;;;N;;;;; A28F;YI SYLLABLE ZI;Lo;0;L;;;;;N;;;;; A290;YI SYLLABLE ZIP;Lo;0;L;;;;;N;;;;; A291;YI SYLLABLE ZIEX;Lo;0;L;;;;;N;;;;; A292;YI SYLLABLE ZIE;Lo;0;L;;;;;N;;;;; A293;YI SYLLABLE ZIEP;Lo;0;L;;;;;N;;;;; A294;YI SYLLABLE ZAT;Lo;0;L;;;;;N;;;;; A295;YI SYLLABLE ZAX;Lo;0;L;;;;;N;;;;; A296;YI SYLLABLE ZA;Lo;0;L;;;;;N;;;;; A297;YI SYLLABLE ZAP;Lo;0;L;;;;;N;;;;; A298;YI SYLLABLE ZUOX;Lo;0;L;;;;;N;;;;; A299;YI SYLLABLE ZUO;Lo;0;L;;;;;N;;;;; A29A;YI SYLLABLE ZUOP;Lo;0;L;;;;;N;;;;; A29B;YI SYLLABLE ZOT;Lo;0;L;;;;;N;;;;; A29C;YI SYLLABLE ZOX;Lo;0;L;;;;;N;;;;; A29D;YI SYLLABLE ZO;Lo;0;L;;;;;N;;;;; A29E;YI SYLLABLE ZOP;Lo;0;L;;;;;N;;;;; A29F;YI SYLLABLE ZEX;Lo;0;L;;;;;N;;;;; A2A0;YI SYLLABLE ZE;Lo;0;L;;;;;N;;;;; A2A1;YI SYLLABLE ZEP;Lo;0;L;;;;;N;;;;; A2A2;YI SYLLABLE ZUT;Lo;0;L;;;;;N;;;;; A2A3;YI SYLLABLE ZUX;Lo;0;L;;;;;N;;;;; A2A4;YI SYLLABLE ZU;Lo;0;L;;;;;N;;;;; A2A5;YI SYLLABLE ZUP;Lo;0;L;;;;;N;;;;; A2A6;YI SYLLABLE ZURX;Lo;0;L;;;;;N;;;;; A2A7;YI SYLLABLE ZUR;Lo;0;L;;;;;N;;;;; A2A8;YI SYLLABLE ZYT;Lo;0;L;;;;;N;;;;; A2A9;YI SYLLABLE ZYX;Lo;0;L;;;;;N;;;;; A2AA;YI SYLLABLE ZY;Lo;0;L;;;;;N;;;;; A2AB;YI SYLLABLE ZYP;Lo;0;L;;;;;N;;;;; A2AC;YI SYLLABLE ZYRX;Lo;0;L;;;;;N;;;;; A2AD;YI SYLLABLE ZYR;Lo;0;L;;;;;N;;;;; A2AE;YI SYLLABLE CIT;Lo;0;L;;;;;N;;;;; A2AF;YI SYLLABLE CIX;Lo;0;L;;;;;N;;;;; A2B0;YI SYLLABLE CI;Lo;0;L;;;;;N;;;;; A2B1;YI SYLLABLE CIP;Lo;0;L;;;;;N;;;;; A2B2;YI SYLLABLE CIET;Lo;0;L;;;;;N;;;;; A2B3;YI SYLLABLE CIEX;Lo;0;L;;;;;N;;;;; A2B4;YI SYLLABLE CIE;Lo;0;L;;;;;N;;;;; A2B5;YI SYLLABLE CIEP;Lo;0;L;;;;;N;;;;; A2B6;YI SYLLABLE CAT;Lo;0;L;;;;;N;;;;; A2B7;YI SYLLABLE CAX;Lo;0;L;;;;;N;;;;; A2B8;YI SYLLABLE CA;Lo;0;L;;;;;N;;;;; A2B9;YI SYLLABLE CAP;Lo;0;L;;;;;N;;;;; A2BA;YI SYLLABLE CUOX;Lo;0;L;;;;;N;;;;; A2BB;YI SYLLABLE CUO;Lo;0;L;;;;;N;;;;; A2BC;YI SYLLABLE CUOP;Lo;0;L;;;;;N;;;;; A2BD;YI SYLLABLE COT;Lo;0;L;;;;;N;;;;; A2BE;YI SYLLABLE COX;Lo;0;L;;;;;N;;;;; A2BF;YI SYLLABLE CO;Lo;0;L;;;;;N;;;;; A2C0;YI SYLLABLE COP;Lo;0;L;;;;;N;;;;; A2C1;YI SYLLABLE CEX;Lo;0;L;;;;;N;;;;; A2C2;YI SYLLABLE CE;Lo;0;L;;;;;N;;;;; A2C3;YI SYLLABLE CEP;Lo;0;L;;;;;N;;;;; A2C4;YI SYLLABLE CUT;Lo;0;L;;;;;N;;;;; A2C5;YI SYLLABLE CUX;Lo;0;L;;;;;N;;;;; A2C6;YI SYLLABLE CU;Lo;0;L;;;;;N;;;;; A2C7;YI SYLLABLE CUP;Lo;0;L;;;;;N;;;;; A2C8;YI SYLLABLE CURX;Lo;0;L;;;;;N;;;;; A2C9;YI SYLLABLE CUR;Lo;0;L;;;;;N;;;;; A2CA;YI SYLLABLE CYT;Lo;0;L;;;;;N;;;;; A2CB;YI SYLLABLE CYX;Lo;0;L;;;;;N;;;;; A2CC;YI SYLLABLE CY;Lo;0;L;;;;;N;;;;; A2CD;YI SYLLABLE CYP;Lo;0;L;;;;;N;;;;; A2CE;YI SYLLABLE CYRX;Lo;0;L;;;;;N;;;;; A2CF;YI SYLLABLE CYR;Lo;0;L;;;;;N;;;;; A2D0;YI SYLLABLE ZZIT;Lo;0;L;;;;;N;;;;; A2D1;YI SYLLABLE ZZIX;Lo;0;L;;;;;N;;;;; A2D2;YI SYLLABLE ZZI;Lo;0;L;;;;;N;;;;; A2D3;YI SYLLABLE ZZIP;Lo;0;L;;;;;N;;;;; A2D4;YI SYLLABLE ZZIET;Lo;0;L;;;;;N;;;;; A2D5;YI SYLLABLE ZZIEX;Lo;0;L;;;;;N;;;;; A2D6;YI SYLLABLE ZZIE;Lo;0;L;;;;;N;;;;; A2D7;YI SYLLABLE ZZIEP;Lo;0;L;;;;;N;;;;; A2D8;YI SYLLABLE ZZAT;Lo;0;L;;;;;N;;;;; A2D9;YI SYLLABLE ZZAX;Lo;0;L;;;;;N;;;;; A2DA;YI SYLLABLE ZZA;Lo;0;L;;;;;N;;;;; A2DB;YI SYLLABLE ZZAP;Lo;0;L;;;;;N;;;;; A2DC;YI SYLLABLE ZZOX;Lo;0;L;;;;;N;;;;; A2DD;YI SYLLABLE ZZO;Lo;0;L;;;;;N;;;;; A2DE;YI SYLLABLE ZZOP;Lo;0;L;;;;;N;;;;; A2DF;YI SYLLABLE ZZEX;Lo;0;L;;;;;N;;;;; A2E0;YI SYLLABLE ZZE;Lo;0;L;;;;;N;;;;; A2E1;YI SYLLABLE ZZEP;Lo;0;L;;;;;N;;;;; A2E2;YI SYLLABLE ZZUX;Lo;0;L;;;;;N;;;;; A2E3;YI SYLLABLE ZZU;Lo;0;L;;;;;N;;;;; A2E4;YI SYLLABLE ZZUP;Lo;0;L;;;;;N;;;;; A2E5;YI SYLLABLE ZZURX;Lo;0;L;;;;;N;;;;; A2E6;YI SYLLABLE ZZUR;Lo;0;L;;;;;N;;;;; A2E7;YI SYLLABLE ZZYT;Lo;0;L;;;;;N;;;;; A2E8;YI SYLLABLE ZZYX;Lo;0;L;;;;;N;;;;; A2E9;YI SYLLABLE ZZY;Lo;0;L;;;;;N;;;;; A2EA;YI SYLLABLE ZZYP;Lo;0;L;;;;;N;;;;; A2EB;YI SYLLABLE ZZYRX;Lo;0;L;;;;;N;;;;; A2EC;YI SYLLABLE ZZYR;Lo;0;L;;;;;N;;;;; A2ED;YI SYLLABLE NZIT;Lo;0;L;;;;;N;;;;; A2EE;YI SYLLABLE NZIX;Lo;0;L;;;;;N;;;;; A2EF;YI SYLLABLE NZI;Lo;0;L;;;;;N;;;;; A2F0;YI SYLLABLE NZIP;Lo;0;L;;;;;N;;;;; A2F1;YI SYLLABLE NZIEX;Lo;0;L;;;;;N;;;;; A2F2;YI SYLLABLE NZIE;Lo;0;L;;;;;N;;;;; A2F3;YI SYLLABLE NZIEP;Lo;0;L;;;;;N;;;;; A2F4;YI SYLLABLE NZAT;Lo;0;L;;;;;N;;;;; A2F5;YI SYLLABLE NZAX;Lo;0;L;;;;;N;;;;; A2F6;YI SYLLABLE NZA;Lo;0;L;;;;;N;;;;; A2F7;YI SYLLABLE NZAP;Lo;0;L;;;;;N;;;;; A2F8;YI SYLLABLE NZUOX;Lo;0;L;;;;;N;;;;; A2F9;YI SYLLABLE NZUO;Lo;0;L;;;;;N;;;;; A2FA;YI SYLLABLE NZOX;Lo;0;L;;;;;N;;;;; A2FB;YI SYLLABLE NZOP;Lo;0;L;;;;;N;;;;; A2FC;YI SYLLABLE NZEX;Lo;0;L;;;;;N;;;;; A2FD;YI SYLLABLE NZE;Lo;0;L;;;;;N;;;;; A2FE;YI SYLLABLE NZUX;Lo;0;L;;;;;N;;;;; A2FF;YI SYLLABLE NZU;Lo;0;L;;;;;N;;;;; A300;YI SYLLABLE NZUP;Lo;0;L;;;;;N;;;;; A301;YI SYLLABLE NZURX;Lo;0;L;;;;;N;;;;; A302;YI SYLLABLE NZUR;Lo;0;L;;;;;N;;;;; A303;YI SYLLABLE NZYT;Lo;0;L;;;;;N;;;;; A304;YI SYLLABLE NZYX;Lo;0;L;;;;;N;;;;; A305;YI SYLLABLE NZY;Lo;0;L;;;;;N;;;;; A306;YI SYLLABLE NZYP;Lo;0;L;;;;;N;;;;; A307;YI SYLLABLE NZYRX;Lo;0;L;;;;;N;;;;; A308;YI SYLLABLE NZYR;Lo;0;L;;;;;N;;;;; A309;YI SYLLABLE SIT;Lo;0;L;;;;;N;;;;; A30A;YI SYLLABLE SIX;Lo;0;L;;;;;N;;;;; A30B;YI SYLLABLE SI;Lo;0;L;;;;;N;;;;; A30C;YI SYLLABLE SIP;Lo;0;L;;;;;N;;;;; A30D;YI SYLLABLE SIEX;Lo;0;L;;;;;N;;;;; A30E;YI SYLLABLE SIE;Lo;0;L;;;;;N;;;;; A30F;YI SYLLABLE SIEP;Lo;0;L;;;;;N;;;;; A310;YI SYLLABLE SAT;Lo;0;L;;;;;N;;;;; A311;YI SYLLABLE SAX;Lo;0;L;;;;;N;;;;; A312;YI SYLLABLE SA;Lo;0;L;;;;;N;;;;; A313;YI SYLLABLE SAP;Lo;0;L;;;;;N;;;;; A314;YI SYLLABLE SUOX;Lo;0;L;;;;;N;;;;; A315;YI SYLLABLE SUO;Lo;0;L;;;;;N;;;;; A316;YI SYLLABLE SUOP;Lo;0;L;;;;;N;;;;; A317;YI SYLLABLE SOT;Lo;0;L;;;;;N;;;;; A318;YI SYLLABLE SOX;Lo;0;L;;;;;N;;;;; A319;YI SYLLABLE SO;Lo;0;L;;;;;N;;;;; A31A;YI SYLLABLE SOP;Lo;0;L;;;;;N;;;;; A31B;YI SYLLABLE SEX;Lo;0;L;;;;;N;;;;; A31C;YI SYLLABLE SE;Lo;0;L;;;;;N;;;;; A31D;YI SYLLABLE SEP;Lo;0;L;;;;;N;;;;; A31E;YI SYLLABLE SUT;Lo;0;L;;;;;N;;;;; A31F;YI SYLLABLE SUX;Lo;0;L;;;;;N;;;;; A320;YI SYLLABLE SU;Lo;0;L;;;;;N;;;;; A321;YI SYLLABLE SUP;Lo;0;L;;;;;N;;;;; A322;YI SYLLABLE SURX;Lo;0;L;;;;;N;;;;; A323;YI SYLLABLE SUR;Lo;0;L;;;;;N;;;;; A324;YI SYLLABLE SYT;Lo;0;L;;;;;N;;;;; A325;YI SYLLABLE SYX;Lo;0;L;;;;;N;;;;; A326;YI SYLLABLE SY;Lo;0;L;;;;;N;;;;; A327;YI SYLLABLE SYP;Lo;0;L;;;;;N;;;;; A328;YI SYLLABLE SYRX;Lo;0;L;;;;;N;;;;; A329;YI SYLLABLE SYR;Lo;0;L;;;;;N;;;;; A32A;YI SYLLABLE SSIT;Lo;0;L;;;;;N;;;;; A32B;YI SYLLABLE SSIX;Lo;0;L;;;;;N;;;;; A32C;YI SYLLABLE SSI;Lo;0;L;;;;;N;;;;; A32D;YI SYLLABLE SSIP;Lo;0;L;;;;;N;;;;; A32E;YI SYLLABLE SSIEX;Lo;0;L;;;;;N;;;;; A32F;YI SYLLABLE SSIE;Lo;0;L;;;;;N;;;;; A330;YI SYLLABLE SSIEP;Lo;0;L;;;;;N;;;;; A331;YI SYLLABLE SSAT;Lo;0;L;;;;;N;;;;; A332;YI SYLLABLE SSAX;Lo;0;L;;;;;N;;;;; A333;YI SYLLABLE SSA;Lo;0;L;;;;;N;;;;; A334;YI SYLLABLE SSAP;Lo;0;L;;;;;N;;;;; A335;YI SYLLABLE SSOT;Lo;0;L;;;;;N;;;;; A336;YI SYLLABLE SSOX;Lo;0;L;;;;;N;;;;; A337;YI SYLLABLE SSO;Lo;0;L;;;;;N;;;;; A338;YI SYLLABLE SSOP;Lo;0;L;;;;;N;;;;; A339;YI SYLLABLE SSEX;Lo;0;L;;;;;N;;;;; A33A;YI SYLLABLE SSE;Lo;0;L;;;;;N;;;;; A33B;YI SYLLABLE SSEP;Lo;0;L;;;;;N;;;;; A33C;YI SYLLABLE SSUT;Lo;0;L;;;;;N;;;;; A33D;YI SYLLABLE SSUX;Lo;0;L;;;;;N;;;;; A33E;YI SYLLABLE SSU;Lo;0;L;;;;;N;;;;; A33F;YI SYLLABLE SSUP;Lo;0;L;;;;;N;;;;; A340;YI SYLLABLE SSYT;Lo;0;L;;;;;N;;;;; A341;YI SYLLABLE SSYX;Lo;0;L;;;;;N;;;;; A342;YI SYLLABLE SSY;Lo;0;L;;;;;N;;;;; A343;YI SYLLABLE SSYP;Lo;0;L;;;;;N;;;;; A344;YI SYLLABLE SSYRX;Lo;0;L;;;;;N;;;;; A345;YI SYLLABLE SSYR;Lo;0;L;;;;;N;;;;; A346;YI SYLLABLE ZHAT;Lo;0;L;;;;;N;;;;; A347;YI SYLLABLE ZHAX;Lo;0;L;;;;;N;;;;; A348;YI SYLLABLE ZHA;Lo;0;L;;;;;N;;;;; A349;YI SYLLABLE ZHAP;Lo;0;L;;;;;N;;;;; A34A;YI SYLLABLE ZHUOX;Lo;0;L;;;;;N;;;;; A34B;YI SYLLABLE ZHUO;Lo;0;L;;;;;N;;;;; A34C;YI SYLLABLE ZHUOP;Lo;0;L;;;;;N;;;;; A34D;YI SYLLABLE ZHOT;Lo;0;L;;;;;N;;;;; A34E;YI SYLLABLE ZHOX;Lo;0;L;;;;;N;;;;; A34F;YI SYLLABLE ZHO;Lo;0;L;;;;;N;;;;; A350;YI SYLLABLE ZHOP;Lo;0;L;;;;;N;;;;; A351;YI SYLLABLE ZHET;Lo;0;L;;;;;N;;;;; A352;YI SYLLABLE ZHEX;Lo;0;L;;;;;N;;;;; A353;YI SYLLABLE ZHE;Lo;0;L;;;;;N;;;;; A354;YI SYLLABLE ZHEP;Lo;0;L;;;;;N;;;;; A355;YI SYLLABLE ZHUT;Lo;0;L;;;;;N;;;;; A356;YI SYLLABLE ZHUX;Lo;0;L;;;;;N;;;;; A357;YI SYLLABLE ZHU;Lo;0;L;;;;;N;;;;; A358;YI SYLLABLE ZHUP;Lo;0;L;;;;;N;;;;; A359;YI SYLLABLE ZHURX;Lo;0;L;;;;;N;;;;; A35A;YI SYLLABLE ZHUR;Lo;0;L;;;;;N;;;;; A35B;YI SYLLABLE ZHYT;Lo;0;L;;;;;N;;;;; A35C;YI SYLLABLE ZHYX;Lo;0;L;;;;;N;;;;; A35D;YI SYLLABLE ZHY;Lo;0;L;;;;;N;;;;; A35E;YI SYLLABLE ZHYP;Lo;0;L;;;;;N;;;;; A35F;YI SYLLABLE ZHYRX;Lo;0;L;;;;;N;;;;; A360;YI SYLLABLE ZHYR;Lo;0;L;;;;;N;;;;; A361;YI SYLLABLE CHAT;Lo;0;L;;;;;N;;;;; A362;YI SYLLABLE CHAX;Lo;0;L;;;;;N;;;;; A363;YI SYLLABLE CHA;Lo;0;L;;;;;N;;;;; A364;YI SYLLABLE CHAP;Lo;0;L;;;;;N;;;;; A365;YI SYLLABLE CHUOT;Lo;0;L;;;;;N;;;;; A366;YI SYLLABLE CHUOX;Lo;0;L;;;;;N;;;;; A367;YI SYLLABLE CHUO;Lo;0;L;;;;;N;;;;; A368;YI SYLLABLE CHUOP;Lo;0;L;;;;;N;;;;; A369;YI SYLLABLE CHOT;Lo;0;L;;;;;N;;;;; A36A;YI SYLLABLE CHOX;Lo;0;L;;;;;N;;;;; A36B;YI SYLLABLE CHO;Lo;0;L;;;;;N;;;;; A36C;YI SYLLABLE CHOP;Lo;0;L;;;;;N;;;;; A36D;YI SYLLABLE CHET;Lo;0;L;;;;;N;;;;; A36E;YI SYLLABLE CHEX;Lo;0;L;;;;;N;;;;; A36F;YI SYLLABLE CHE;Lo;0;L;;;;;N;;;;; A370;YI SYLLABLE CHEP;Lo;0;L;;;;;N;;;;; A371;YI SYLLABLE CHUX;Lo;0;L;;;;;N;;;;; A372;YI SYLLABLE CHU;Lo;0;L;;;;;N;;;;; A373;YI SYLLABLE CHUP;Lo;0;L;;;;;N;;;;; A374;YI SYLLABLE CHURX;Lo;0;L;;;;;N;;;;; A375;YI SYLLABLE CHUR;Lo;0;L;;;;;N;;;;; A376;YI SYLLABLE CHYT;Lo;0;L;;;;;N;;;;; A377;YI SYLLABLE CHYX;Lo;0;L;;;;;N;;;;; A378;YI SYLLABLE CHY;Lo;0;L;;;;;N;;;;; A379;YI SYLLABLE CHYP;Lo;0;L;;;;;N;;;;; A37A;YI SYLLABLE CHYRX;Lo;0;L;;;;;N;;;;; A37B;YI SYLLABLE CHYR;Lo;0;L;;;;;N;;;;; A37C;YI SYLLABLE RRAX;Lo;0;L;;;;;N;;;;; A37D;YI SYLLABLE RRA;Lo;0;L;;;;;N;;;;; A37E;YI SYLLABLE RRUOX;Lo;0;L;;;;;N;;;;; A37F;YI SYLLABLE RRUO;Lo;0;L;;;;;N;;;;; A380;YI SYLLABLE RROT;Lo;0;L;;;;;N;;;;; A381;YI SYLLABLE RROX;Lo;0;L;;;;;N;;;;; A382;YI SYLLABLE RRO;Lo;0;L;;;;;N;;;;; A383;YI SYLLABLE RROP;Lo;0;L;;;;;N;;;;; A384;YI SYLLABLE RRET;Lo;0;L;;;;;N;;;;; A385;YI SYLLABLE RREX;Lo;0;L;;;;;N;;;;; A386;YI SYLLABLE RRE;Lo;0;L;;;;;N;;;;; A387;YI SYLLABLE RREP;Lo;0;L;;;;;N;;;;; A388;YI SYLLABLE RRUT;Lo;0;L;;;;;N;;;;; A389;YI SYLLABLE RRUX;Lo;0;L;;;;;N;;;;; A38A;YI SYLLABLE RRU;Lo;0;L;;;;;N;;;;; A38B;YI SYLLABLE RRUP;Lo;0;L;;;;;N;;;;; A38C;YI SYLLABLE RRURX;Lo;0;L;;;;;N;;;;; A38D;YI SYLLABLE RRUR;Lo;0;L;;;;;N;;;;; A38E;YI SYLLABLE RRYT;Lo;0;L;;;;;N;;;;; A38F;YI SYLLABLE RRYX;Lo;0;L;;;;;N;;;;; A390;YI SYLLABLE RRY;Lo;0;L;;;;;N;;;;; A391;YI SYLLABLE RRYP;Lo;0;L;;;;;N;;;;; A392;YI SYLLABLE RRYRX;Lo;0;L;;;;;N;;;;; A393;YI SYLLABLE RRYR;Lo;0;L;;;;;N;;;;; A394;YI SYLLABLE NRAT;Lo;0;L;;;;;N;;;;; A395;YI SYLLABLE NRAX;Lo;0;L;;;;;N;;;;; A396;YI SYLLABLE NRA;Lo;0;L;;;;;N;;;;; A397;YI SYLLABLE NRAP;Lo;0;L;;;;;N;;;;; A398;YI SYLLABLE NROX;Lo;0;L;;;;;N;;;;; A399;YI SYLLABLE NRO;Lo;0;L;;;;;N;;;;; A39A;YI SYLLABLE NROP;Lo;0;L;;;;;N;;;;; A39B;YI SYLLABLE NRET;Lo;0;L;;;;;N;;;;; A39C;YI SYLLABLE NREX;Lo;0;L;;;;;N;;;;; A39D;YI SYLLABLE NRE;Lo;0;L;;;;;N;;;;; A39E;YI SYLLABLE NREP;Lo;0;L;;;;;N;;;;; A39F;YI SYLLABLE NRUT;Lo;0;L;;;;;N;;;;; A3A0;YI SYLLABLE NRUX;Lo;0;L;;;;;N;;;;; A3A1;YI SYLLABLE NRU;Lo;0;L;;;;;N;;;;; A3A2;YI SYLLABLE NRUP;Lo;0;L;;;;;N;;;;; A3A3;YI SYLLABLE NRURX;Lo;0;L;;;;;N;;;;; A3A4;YI SYLLABLE NRUR;Lo;0;L;;;;;N;;;;; A3A5;YI SYLLABLE NRYT;Lo;0;L;;;;;N;;;;; A3A6;YI SYLLABLE NRYX;Lo;0;L;;;;;N;;;;; A3A7;YI SYLLABLE NRY;Lo;0;L;;;;;N;;;;; A3A8;YI SYLLABLE NRYP;Lo;0;L;;;;;N;;;;; A3A9;YI SYLLABLE NRYRX;Lo;0;L;;;;;N;;;;; A3AA;YI SYLLABLE NRYR;Lo;0;L;;;;;N;;;;; A3AB;YI SYLLABLE SHAT;Lo;0;L;;;;;N;;;;; A3AC;YI SYLLABLE SHAX;Lo;0;L;;;;;N;;;;; A3AD;YI SYLLABLE SHA;Lo;0;L;;;;;N;;;;; A3AE;YI SYLLABLE SHAP;Lo;0;L;;;;;N;;;;; A3AF;YI SYLLABLE SHUOX;Lo;0;L;;;;;N;;;;; A3B0;YI SYLLABLE SHUO;Lo;0;L;;;;;N;;;;; A3B1;YI SYLLABLE SHUOP;Lo;0;L;;;;;N;;;;; A3B2;YI SYLLABLE SHOT;Lo;0;L;;;;;N;;;;; A3B3;YI SYLLABLE SHOX;Lo;0;L;;;;;N;;;;; A3B4;YI SYLLABLE SHO;Lo;0;L;;;;;N;;;;; A3B5;YI SYLLABLE SHOP;Lo;0;L;;;;;N;;;;; A3B6;YI SYLLABLE SHET;Lo;0;L;;;;;N;;;;; A3B7;YI SYLLABLE SHEX;Lo;0;L;;;;;N;;;;; A3B8;YI SYLLABLE SHE;Lo;0;L;;;;;N;;;;; A3B9;YI SYLLABLE SHEP;Lo;0;L;;;;;N;;;;; A3BA;YI SYLLABLE SHUT;Lo;0;L;;;;;N;;;;; A3BB;YI SYLLABLE SHUX;Lo;0;L;;;;;N;;;;; A3BC;YI SYLLABLE SHU;Lo;0;L;;;;;N;;;;; A3BD;YI SYLLABLE SHUP;Lo;0;L;;;;;N;;;;; A3BE;YI SYLLABLE SHURX;Lo;0;L;;;;;N;;;;; A3BF;YI SYLLABLE SHUR;Lo;0;L;;;;;N;;;;; A3C0;YI SYLLABLE SHYT;Lo;0;L;;;;;N;;;;; A3C1;YI SYLLABLE SHYX;Lo;0;L;;;;;N;;;;; A3C2;YI SYLLABLE SHY;Lo;0;L;;;;;N;;;;; A3C3;YI SYLLABLE SHYP;Lo;0;L;;;;;N;;;;; A3C4;YI SYLLABLE SHYRX;Lo;0;L;;;;;N;;;;; A3C5;YI SYLLABLE SHYR;Lo;0;L;;;;;N;;;;; A3C6;YI SYLLABLE RAT;Lo;0;L;;;;;N;;;;; A3C7;YI SYLLABLE RAX;Lo;0;L;;;;;N;;;;; A3C8;YI SYLLABLE RA;Lo;0;L;;;;;N;;;;; A3C9;YI SYLLABLE RAP;Lo;0;L;;;;;N;;;;; A3CA;YI SYLLABLE RUOX;Lo;0;L;;;;;N;;;;; A3CB;YI SYLLABLE RUO;Lo;0;L;;;;;N;;;;; A3CC;YI SYLLABLE RUOP;Lo;0;L;;;;;N;;;;; A3CD;YI SYLLABLE ROT;Lo;0;L;;;;;N;;;;; A3CE;YI SYLLABLE ROX;Lo;0;L;;;;;N;;;;; A3CF;YI SYLLABLE RO;Lo;0;L;;;;;N;;;;; A3D0;YI SYLLABLE ROP;Lo;0;L;;;;;N;;;;; A3D1;YI SYLLABLE REX;Lo;0;L;;;;;N;;;;; A3D2;YI SYLLABLE RE;Lo;0;L;;;;;N;;;;; A3D3;YI SYLLABLE REP;Lo;0;L;;;;;N;;;;; A3D4;YI SYLLABLE RUT;Lo;0;L;;;;;N;;;;; A3D5;YI SYLLABLE RUX;Lo;0;L;;;;;N;;;;; A3D6;YI SYLLABLE RU;Lo;0;L;;;;;N;;;;; A3D7;YI SYLLABLE RUP;Lo;0;L;;;;;N;;;;; A3D8;YI SYLLABLE RURX;Lo;0;L;;;;;N;;;;; A3D9;YI SYLLABLE RUR;Lo;0;L;;;;;N;;;;; A3DA;YI SYLLABLE RYT;Lo;0;L;;;;;N;;;;; A3DB;YI SYLLABLE RYX;Lo;0;L;;;;;N;;;;; A3DC;YI SYLLABLE RY;Lo;0;L;;;;;N;;;;; A3DD;YI SYLLABLE RYP;Lo;0;L;;;;;N;;;;; A3DE;YI SYLLABLE RYRX;Lo;0;L;;;;;N;;;;; A3DF;YI SYLLABLE RYR;Lo;0;L;;;;;N;;;;; A3E0;YI SYLLABLE JIT;Lo;0;L;;;;;N;;;;; A3E1;YI SYLLABLE JIX;Lo;0;L;;;;;N;;;;; A3E2;YI SYLLABLE JI;Lo;0;L;;;;;N;;;;; A3E3;YI SYLLABLE JIP;Lo;0;L;;;;;N;;;;; A3E4;YI SYLLABLE JIET;Lo;0;L;;;;;N;;;;; A3E5;YI SYLLABLE JIEX;Lo;0;L;;;;;N;;;;; A3E6;YI SYLLABLE JIE;Lo;0;L;;;;;N;;;;; A3E7;YI SYLLABLE JIEP;Lo;0;L;;;;;N;;;;; A3E8;YI SYLLABLE JUOT;Lo;0;L;;;;;N;;;;; A3E9;YI SYLLABLE JUOX;Lo;0;L;;;;;N;;;;; A3EA;YI SYLLABLE JUO;Lo;0;L;;;;;N;;;;; A3EB;YI SYLLABLE JUOP;Lo;0;L;;;;;N;;;;; A3EC;YI SYLLABLE JOT;Lo;0;L;;;;;N;;;;; A3ED;YI SYLLABLE JOX;Lo;0;L;;;;;N;;;;; A3EE;YI SYLLABLE JO;Lo;0;L;;;;;N;;;;; A3EF;YI SYLLABLE JOP;Lo;0;L;;;;;N;;;;; A3F0;YI SYLLABLE JUT;Lo;0;L;;;;;N;;;;; A3F1;YI SYLLABLE JUX;Lo;0;L;;;;;N;;;;; A3F2;YI SYLLABLE JU;Lo;0;L;;;;;N;;;;; A3F3;YI SYLLABLE JUP;Lo;0;L;;;;;N;;;;; A3F4;YI SYLLABLE JURX;Lo;0;L;;;;;N;;;;; A3F5;YI SYLLABLE JUR;Lo;0;L;;;;;N;;;;; A3F6;YI SYLLABLE JYT;Lo;0;L;;;;;N;;;;; A3F7;YI SYLLABLE JYX;Lo;0;L;;;;;N;;;;; A3F8;YI SYLLABLE JY;Lo;0;L;;;;;N;;;;; A3F9;YI SYLLABLE JYP;Lo;0;L;;;;;N;;;;; A3FA;YI SYLLABLE JYRX;Lo;0;L;;;;;N;;;;; A3FB;YI SYLLABLE JYR;Lo;0;L;;;;;N;;;;; A3FC;YI SYLLABLE QIT;Lo;0;L;;;;;N;;;;; A3FD;YI SYLLABLE QIX;Lo;0;L;;;;;N;;;;; A3FE;YI SYLLABLE QI;Lo;0;L;;;;;N;;;;; A3FF;YI SYLLABLE QIP;Lo;0;L;;;;;N;;;;; A400;YI SYLLABLE QIET;Lo;0;L;;;;;N;;;;; A401;YI SYLLABLE QIEX;Lo;0;L;;;;;N;;;;; A402;YI SYLLABLE QIE;Lo;0;L;;;;;N;;;;; A403;YI SYLLABLE QIEP;Lo;0;L;;;;;N;;;;; A404;YI SYLLABLE QUOT;Lo;0;L;;;;;N;;;;; A405;YI SYLLABLE QUOX;Lo;0;L;;;;;N;;;;; A406;YI SYLLABLE QUO;Lo;0;L;;;;;N;;;;; A407;YI SYLLABLE QUOP;Lo;0;L;;;;;N;;;;; A408;YI SYLLABLE QOT;Lo;0;L;;;;;N;;;;; A409;YI SYLLABLE QOX;Lo;0;L;;;;;N;;;;; A40A;YI SYLLABLE QO;Lo;0;L;;;;;N;;;;; A40B;YI SYLLABLE QOP;Lo;0;L;;;;;N;;;;; A40C;YI SYLLABLE QUT;Lo;0;L;;;;;N;;;;; A40D;YI SYLLABLE QUX;Lo;0;L;;;;;N;;;;; A40E;YI SYLLABLE QU;Lo;0;L;;;;;N;;;;; A40F;YI SYLLABLE QUP;Lo;0;L;;;;;N;;;;; A410;YI SYLLABLE QURX;Lo;0;L;;;;;N;;;;; A411;YI SYLLABLE QUR;Lo;0;L;;;;;N;;;;; A412;YI SYLLABLE QYT;Lo;0;L;;;;;N;;;;; A413;YI SYLLABLE QYX;Lo;0;L;;;;;N;;;;; A414;YI SYLLABLE QY;Lo;0;L;;;;;N;;;;; A415;YI SYLLABLE QYP;Lo;0;L;;;;;N;;;;; A416;YI SYLLABLE QYRX;Lo;0;L;;;;;N;;;;; A417;YI SYLLABLE QYR;Lo;0;L;;;;;N;;;;; A418;YI SYLLABLE JJIT;Lo;0;L;;;;;N;;;;; A419;YI SYLLABLE JJIX;Lo;0;L;;;;;N;;;;; A41A;YI SYLLABLE JJI;Lo;0;L;;;;;N;;;;; A41B;YI SYLLABLE JJIP;Lo;0;L;;;;;N;;;;; A41C;YI SYLLABLE JJIET;Lo;0;L;;;;;N;;;;; A41D;YI SYLLABLE JJIEX;Lo;0;L;;;;;N;;;;; A41E;YI SYLLABLE JJIE;Lo;0;L;;;;;N;;;;; A41F;YI SYLLABLE JJIEP;Lo;0;L;;;;;N;;;;; A420;YI SYLLABLE JJUOX;Lo;0;L;;;;;N;;;;; A421;YI SYLLABLE JJUO;Lo;0;L;;;;;N;;;;; A422;YI SYLLABLE JJUOP;Lo;0;L;;;;;N;;;;; A423;YI SYLLABLE JJOT;Lo;0;L;;;;;N;;;;; A424;YI SYLLABLE JJOX;Lo;0;L;;;;;N;;;;; A425;YI SYLLABLE JJO;Lo;0;L;;;;;N;;;;; A426;YI SYLLABLE JJOP;Lo;0;L;;;;;N;;;;; A427;YI SYLLABLE JJUT;Lo;0;L;;;;;N;;;;; A428;YI SYLLABLE JJUX;Lo;0;L;;;;;N;;;;; A429;YI SYLLABLE JJU;Lo;0;L;;;;;N;;;;; A42A;YI SYLLABLE JJUP;Lo;0;L;;;;;N;;;;; A42B;YI SYLLABLE JJURX;Lo;0;L;;;;;N;;;;; A42C;YI SYLLABLE JJUR;Lo;0;L;;;;;N;;;;; A42D;YI SYLLABLE JJYT;Lo;0;L;;;;;N;;;;; A42E;YI SYLLABLE JJYX;Lo;0;L;;;;;N;;;;; A42F;YI SYLLABLE JJY;Lo;0;L;;;;;N;;;;; A430;YI SYLLABLE JJYP;Lo;0;L;;;;;N;;;;; A431;YI SYLLABLE NJIT;Lo;0;L;;;;;N;;;;; A432;YI SYLLABLE NJIX;Lo;0;L;;;;;N;;;;; A433;YI SYLLABLE NJI;Lo;0;L;;;;;N;;;;; A434;YI SYLLABLE NJIP;Lo;0;L;;;;;N;;;;; A435;YI SYLLABLE NJIET;Lo;0;L;;;;;N;;;;; A436;YI SYLLABLE NJIEX;Lo;0;L;;;;;N;;;;; A437;YI SYLLABLE NJIE;Lo;0;L;;;;;N;;;;; A438;YI SYLLABLE NJIEP;Lo;0;L;;;;;N;;;;; A439;YI SYLLABLE NJUOX;Lo;0;L;;;;;N;;;;; A43A;YI SYLLABLE NJUO;Lo;0;L;;;;;N;;;;; A43B;YI SYLLABLE NJOT;Lo;0;L;;;;;N;;;;; A43C;YI SYLLABLE NJOX;Lo;0;L;;;;;N;;;;; A43D;YI SYLLABLE NJO;Lo;0;L;;;;;N;;;;; A43E;YI SYLLABLE NJOP;Lo;0;L;;;;;N;;;;; A43F;YI SYLLABLE NJUX;Lo;0;L;;;;;N;;;;; A440;YI SYLLABLE NJU;Lo;0;L;;;;;N;;;;; A441;YI SYLLABLE NJUP;Lo;0;L;;;;;N;;;;; A442;YI SYLLABLE NJURX;Lo;0;L;;;;;N;;;;; A443;YI SYLLABLE NJUR;Lo;0;L;;;;;N;;;;; A444;YI SYLLABLE NJYT;Lo;0;L;;;;;N;;;;; A445;YI SYLLABLE NJYX;Lo;0;L;;;;;N;;;;; A446;YI SYLLABLE NJY;Lo;0;L;;;;;N;;;;; A447;YI SYLLABLE NJYP;Lo;0;L;;;;;N;;;;; A448;YI SYLLABLE NJYRX;Lo;0;L;;;;;N;;;;; A449;YI SYLLABLE NJYR;Lo;0;L;;;;;N;;;;; A44A;YI SYLLABLE NYIT;Lo;0;L;;;;;N;;;;; A44B;YI SYLLABLE NYIX;Lo;0;L;;;;;N;;;;; A44C;YI SYLLABLE NYI;Lo;0;L;;;;;N;;;;; A44D;YI SYLLABLE NYIP;Lo;0;L;;;;;N;;;;; A44E;YI SYLLABLE NYIET;Lo;0;L;;;;;N;;;;; A44F;YI SYLLABLE NYIEX;Lo;0;L;;;;;N;;;;; A450;YI SYLLABLE NYIE;Lo;0;L;;;;;N;;;;; A451;YI SYLLABLE NYIEP;Lo;0;L;;;;;N;;;;; A452;YI SYLLABLE NYUOX;Lo;0;L;;;;;N;;;;; A453;YI SYLLABLE NYUO;Lo;0;L;;;;;N;;;;; A454;YI SYLLABLE NYUOP;Lo;0;L;;;;;N;;;;; A455;YI SYLLABLE NYOT;Lo;0;L;;;;;N;;;;; A456;YI SYLLABLE NYOX;Lo;0;L;;;;;N;;;;; A457;YI SYLLABLE NYO;Lo;0;L;;;;;N;;;;; A458;YI SYLLABLE NYOP;Lo;0;L;;;;;N;;;;; A459;YI SYLLABLE NYUT;Lo;0;L;;;;;N;;;;; A45A;YI SYLLABLE NYUX;Lo;0;L;;;;;N;;;;; A45B;YI SYLLABLE NYU;Lo;0;L;;;;;N;;;;; A45C;YI SYLLABLE NYUP;Lo;0;L;;;;;N;;;;; A45D;YI SYLLABLE XIT;Lo;0;L;;;;;N;;;;; A45E;YI SYLLABLE XIX;Lo;0;L;;;;;N;;;;; A45F;YI SYLLABLE XI;Lo;0;L;;;;;N;;;;; A460;YI SYLLABLE XIP;Lo;0;L;;;;;N;;;;; A461;YI SYLLABLE XIET;Lo;0;L;;;;;N;;;;; A462;YI SYLLABLE XIEX;Lo;0;L;;;;;N;;;;; A463;YI SYLLABLE XIE;Lo;0;L;;;;;N;;;;; A464;YI SYLLABLE XIEP;Lo;0;L;;;;;N;;;;; A465;YI SYLLABLE XUOX;Lo;0;L;;;;;N;;;;; A466;YI SYLLABLE XUO;Lo;0;L;;;;;N;;;;; A467;YI SYLLABLE XOT;Lo;0;L;;;;;N;;;;; A468;YI SYLLABLE XOX;Lo;0;L;;;;;N;;;;; A469;YI SYLLABLE XO;Lo;0;L;;;;;N;;;;; A46A;YI SYLLABLE XOP;Lo;0;L;;;;;N;;;;; A46B;YI SYLLABLE XYT;Lo;0;L;;;;;N;;;;; A46C;YI SYLLABLE XYX;Lo;0;L;;;;;N;;;;; A46D;YI SYLLABLE XY;Lo;0;L;;;;;N;;;;; A46E;YI SYLLABLE XYP;Lo;0;L;;;;;N;;;;; A46F;YI SYLLABLE XYRX;Lo;0;L;;;;;N;;;;; A470;YI SYLLABLE XYR;Lo;0;L;;;;;N;;;;; A471;YI SYLLABLE YIT;Lo;0;L;;;;;N;;;;; A472;YI SYLLABLE YIX;Lo;0;L;;;;;N;;;;; A473;YI SYLLABLE YI;Lo;0;L;;;;;N;;;;; A474;YI SYLLABLE YIP;Lo;0;L;;;;;N;;;;; A475;YI SYLLABLE YIET;Lo;0;L;;;;;N;;;;; A476;YI SYLLABLE YIEX;Lo;0;L;;;;;N;;;;; A477;YI SYLLABLE YIE;Lo;0;L;;;;;N;;;;; A478;YI SYLLABLE YIEP;Lo;0;L;;;;;N;;;;; A479;YI SYLLABLE YUOT;Lo;0;L;;;;;N;;;;; A47A;YI SYLLABLE YUOX;Lo;0;L;;;;;N;;;;; A47B;YI SYLLABLE YUO;Lo;0;L;;;;;N;;;;; A47C;YI SYLLABLE YUOP;Lo;0;L;;;;;N;;;;; A47D;YI SYLLABLE YOT;Lo;0;L;;;;;N;;;;; A47E;YI SYLLABLE YOX;Lo;0;L;;;;;N;;;;; A47F;YI SYLLABLE YO;Lo;0;L;;;;;N;;;;; A480;YI SYLLABLE YOP;Lo;0;L;;;;;N;;;;; A481;YI SYLLABLE YUT;Lo;0;L;;;;;N;;;;; A482;YI SYLLABLE YUX;Lo;0;L;;;;;N;;;;; A483;YI SYLLABLE YU;Lo;0;L;;;;;N;;;;; A484;YI SYLLABLE YUP;Lo;0;L;;;;;N;;;;; A485;YI SYLLABLE YURX;Lo;0;L;;;;;N;;;;; A486;YI SYLLABLE YUR;Lo;0;L;;;;;N;;;;; A487;YI SYLLABLE YYT;Lo;0;L;;;;;N;;;;; A488;YI SYLLABLE YYX;Lo;0;L;;;;;N;;;;; A489;YI SYLLABLE YY;Lo;0;L;;;;;N;;;;; A48A;YI SYLLABLE YYP;Lo;0;L;;;;;N;;;;; A48B;YI SYLLABLE YYRX;Lo;0;L;;;;;N;;;;; A48C;YI SYLLABLE YYR;Lo;0;L;;;;;N;;;;; A490;YI RADICAL QOT;So;0;ON;;;;;N;;;;; A491;YI RADICAL LI;So;0;ON;;;;;N;;;;; A492;YI RADICAL KIT;So;0;ON;;;;;N;;;;; A493;YI RADICAL NYIP;So;0;ON;;;;;N;;;;; A494;YI RADICAL CYP;So;0;ON;;;;;N;;;;; A495;YI RADICAL SSI;So;0;ON;;;;;N;;;;; A496;YI RADICAL GGOP;So;0;ON;;;;;N;;;;; A497;YI RADICAL GEP;So;0;ON;;;;;N;;;;; A498;YI RADICAL MI;So;0;ON;;;;;N;;;;; A499;YI RADICAL HXIT;So;0;ON;;;;;N;;;;; A49A;YI RADICAL LYR;So;0;ON;;;;;N;;;;; A49B;YI RADICAL BBUT;So;0;ON;;;;;N;;;;; A49C;YI RADICAL MOP;So;0;ON;;;;;N;;;;; A49D;YI RADICAL YO;So;0;ON;;;;;N;;;;; A49E;YI RADICAL PUT;So;0;ON;;;;;N;;;;; A49F;YI RADICAL HXUO;So;0;ON;;;;;N;;;;; A4A0;YI RADICAL TAT;So;0;ON;;;;;N;;;;; A4A1;YI RADICAL GA;So;0;ON;;;;;N;;;;; A4A2;YI RADICAL ZUP;So;0;ON;;;;;N;;;;; A4A3;YI RADICAL CYT;So;0;ON;;;;;N;;;;; A4A4;YI RADICAL DDUR;So;0;ON;;;;;N;;;;; A4A5;YI RADICAL BUR;So;0;ON;;;;;N;;;;; A4A6;YI RADICAL GGUO;So;0;ON;;;;;N;;;;; A4A7;YI RADICAL NYOP;So;0;ON;;;;;N;;;;; A4A8;YI RADICAL TU;So;0;ON;;;;;N;;;;; A4A9;YI RADICAL OP;So;0;ON;;;;;N;;;;; A4AA;YI RADICAL JJUT;So;0;ON;;;;;N;;;;; A4AB;YI RADICAL ZOT;So;0;ON;;;;;N;;;;; A4AC;YI RADICAL PYT;So;0;ON;;;;;N;;;;; A4AD;YI RADICAL HMO;So;0;ON;;;;;N;;;;; A4AE;YI RADICAL YIT;So;0;ON;;;;;N;;;;; A4AF;YI RADICAL VUR;So;0;ON;;;;;N;;;;; A4B0;YI RADICAL SHY;So;0;ON;;;;;N;;;;; A4B1;YI RADICAL VEP;So;0;ON;;;;;N;;;;; A4B2;YI RADICAL ZA;So;0;ON;;;;;N;;;;; A4B3;YI RADICAL JO;So;0;ON;;;;;N;;;;; A4B4;YI RADICAL NZUP;So;0;ON;;;;;N;;;;; A4B5;YI RADICAL JJY;So;0;ON;;;;;N;;;;; A4B6;YI RADICAL GOT;So;0;ON;;;;;N;;;;; A4B7;YI RADICAL JJIE;So;0;ON;;;;;N;;;;; A4B8;YI RADICAL WO;So;0;ON;;;;;N;;;;; A4B9;YI RADICAL DU;So;0;ON;;;;;N;;;;; A4BA;YI RADICAL SHUR;So;0;ON;;;;;N;;;;; A4BB;YI RADICAL LIE;So;0;ON;;;;;N;;;;; A4BC;YI RADICAL CY;So;0;ON;;;;;N;;;;; A4BD;YI RADICAL CUOP;So;0;ON;;;;;N;;;;; A4BE;YI RADICAL CIP;So;0;ON;;;;;N;;;;; A4BF;YI RADICAL HXOP;So;0;ON;;;;;N;;;;; A4C0;YI RADICAL SHAT;So;0;ON;;;;;N;;;;; A4C1;YI RADICAL ZUR;So;0;ON;;;;;N;;;;; A4C2;YI RADICAL SHOP;So;0;ON;;;;;N;;;;; A4C3;YI RADICAL CHE;So;0;ON;;;;;N;;;;; A4C4;YI RADICAL ZZIET;So;0;ON;;;;;N;;;;; A4C5;YI RADICAL NBIE;So;0;ON;;;;;N;;;;; A4C6;YI RADICAL KE;So;0;ON;;;;;N;;;;; A4D0;LISU LETTER BA;Lo;0;L;;;;;N;;;;; A4D1;LISU LETTER PA;Lo;0;L;;;;;N;;;;; A4D2;LISU LETTER PHA;Lo;0;L;;;;;N;;;;; A4D3;LISU LETTER DA;Lo;0;L;;;;;N;;;;; A4D4;LISU LETTER TA;Lo;0;L;;;;;N;;;;; A4D5;LISU LETTER THA;Lo;0;L;;;;;N;;;;; A4D6;LISU LETTER GA;Lo;0;L;;;;;N;;;;; A4D7;LISU LETTER KA;Lo;0;L;;;;;N;;;;; A4D8;LISU LETTER KHA;Lo;0;L;;;;;N;;;;; A4D9;LISU LETTER JA;Lo;0;L;;;;;N;;;;; A4DA;LISU LETTER CA;Lo;0;L;;;;;N;;;;; A4DB;LISU LETTER CHA;Lo;0;L;;;;;N;;;;; A4DC;LISU LETTER DZA;Lo;0;L;;;;;N;;;;; A4DD;LISU LETTER TSA;Lo;0;L;;;;;N;;;;; A4DE;LISU LETTER TSHA;Lo;0;L;;;;;N;;;;; A4DF;LISU LETTER MA;Lo;0;L;;;;;N;;;;; A4E0;LISU LETTER NA;Lo;0;L;;;;;N;;;;; A4E1;LISU LETTER LA;Lo;0;L;;;;;N;;;;; A4E2;LISU LETTER SA;Lo;0;L;;;;;N;;;;; A4E3;LISU LETTER ZHA;Lo;0;L;;;;;N;;;;; A4E4;LISU LETTER ZA;Lo;0;L;;;;;N;;;;; A4E5;LISU LETTER NGA;Lo;0;L;;;;;N;;;;; A4E6;LISU LETTER HA;Lo;0;L;;;;;N;;;;; A4E7;LISU LETTER XA;Lo;0;L;;;;;N;;;;; A4E8;LISU LETTER HHA;Lo;0;L;;;;;N;;;;; A4E9;LISU LETTER FA;Lo;0;L;;;;;N;;;;; A4EA;LISU LETTER WA;Lo;0;L;;;;;N;;;;; A4EB;LISU LETTER SHA;Lo;0;L;;;;;N;;;;; A4EC;LISU LETTER YA;Lo;0;L;;;;;N;;;;; A4ED;LISU LETTER GHA;Lo;0;L;;;;;N;;;;; A4EE;LISU LETTER A;Lo;0;L;;;;;N;;;;; A4EF;LISU LETTER AE;Lo;0;L;;;;;N;;;;; A4F0;LISU LETTER E;Lo;0;L;;;;;N;;;;; A4F1;LISU LETTER EU;Lo;0;L;;;;;N;;;;; A4F2;LISU LETTER I;Lo;0;L;;;;;N;;;;; A4F3;LISU LETTER O;Lo;0;L;;;;;N;;;;; A4F4;LISU LETTER U;Lo;0;L;;;;;N;;;;; A4F5;LISU LETTER UE;Lo;0;L;;;;;N;;;;; A4F6;LISU LETTER UH;Lo;0;L;;;;;N;;;;; A4F7;LISU LETTER OE;Lo;0;L;;;;;N;;;;; A4F8;LISU LETTER TONE MYA TI;Lm;0;L;;;;;N;;;;; A4F9;LISU LETTER TONE NA PO;Lm;0;L;;;;;N;;;;; A4FA;LISU LETTER TONE MYA CYA;Lm;0;L;;;;;N;;;;; A4FB;LISU LETTER TONE MYA BO;Lm;0;L;;;;;N;;;;; A4FC;LISU LETTER TONE MYA NA;Lm;0;L;;;;;N;;;;; A4FD;LISU LETTER TONE MYA JEU;Lm;0;L;;;;;N;;;;; A4FE;LISU PUNCTUATION COMMA;Po;0;L;;;;;N;;;;; A4FF;LISU PUNCTUATION FULL STOP;Po;0;L;;;;;N;;;;; A500;VAI SYLLABLE EE;Lo;0;L;;;;;N;;;;; A501;VAI SYLLABLE EEN;Lo;0;L;;;;;N;;;;; A502;VAI SYLLABLE HEE;Lo;0;L;;;;;N;;;;; A503;VAI SYLLABLE WEE;Lo;0;L;;;;;N;;;;; A504;VAI SYLLABLE WEEN;Lo;0;L;;;;;N;;;;; A505;VAI SYLLABLE PEE;Lo;0;L;;;;;N;;;;; A506;VAI SYLLABLE BHEE;Lo;0;L;;;;;N;;;;; A507;VAI SYLLABLE BEE;Lo;0;L;;;;;N;;;;; A508;VAI SYLLABLE MBEE;Lo;0;L;;;;;N;;;;; A509;VAI SYLLABLE KPEE;Lo;0;L;;;;;N;;;;; A50A;VAI SYLLABLE MGBEE;Lo;0;L;;;;;N;;;;; A50B;VAI SYLLABLE GBEE;Lo;0;L;;;;;N;;;;; A50C;VAI SYLLABLE FEE;Lo;0;L;;;;;N;;;;; A50D;VAI SYLLABLE VEE;Lo;0;L;;;;;N;;;;; A50E;VAI SYLLABLE TEE;Lo;0;L;;;;;N;;;;; A50F;VAI SYLLABLE THEE;Lo;0;L;;;;;N;;;;; A510;VAI SYLLABLE DHEE;Lo;0;L;;;;;N;;;;; A511;VAI SYLLABLE DHHEE;Lo;0;L;;;;;N;;;;; A512;VAI SYLLABLE LEE;Lo;0;L;;;;;N;;;;; A513;VAI SYLLABLE REE;Lo;0;L;;;;;N;;;;; A514;VAI SYLLABLE DEE;Lo;0;L;;;;;N;;;;; A515;VAI SYLLABLE NDEE;Lo;0;L;;;;;N;;;;; A516;VAI SYLLABLE SEE;Lo;0;L;;;;;N;;;;; A517;VAI SYLLABLE SHEE;Lo;0;L;;;;;N;;;;; A518;VAI SYLLABLE ZEE;Lo;0;L;;;;;N;;;;; A519;VAI SYLLABLE ZHEE;Lo;0;L;;;;;N;;;;; A51A;VAI SYLLABLE CEE;Lo;0;L;;;;;N;;;;; A51B;VAI SYLLABLE JEE;Lo;0;L;;;;;N;;;;; A51C;VAI SYLLABLE NJEE;Lo;0;L;;;;;N;;;;; A51D;VAI SYLLABLE YEE;Lo;0;L;;;;;N;;;;; A51E;VAI SYLLABLE KEE;Lo;0;L;;;;;N;;;;; A51F;VAI SYLLABLE NGGEE;Lo;0;L;;;;;N;;;;; A520;VAI SYLLABLE GEE;Lo;0;L;;;;;N;;;;; A521;VAI SYLLABLE MEE;Lo;0;L;;;;;N;;;;; A522;VAI SYLLABLE NEE;Lo;0;L;;;;;N;;;;; A523;VAI SYLLABLE NYEE;Lo;0;L;;;;;N;;;;; A524;VAI SYLLABLE I;Lo;0;L;;;;;N;;;;; A525;VAI SYLLABLE IN;Lo;0;L;;;;;N;;;;; A526;VAI SYLLABLE HI;Lo;0;L;;;;;N;;;;; A527;VAI SYLLABLE HIN;Lo;0;L;;;;;N;;;;; A528;VAI SYLLABLE WI;Lo;0;L;;;;;N;;;;; A529;VAI SYLLABLE WIN;Lo;0;L;;;;;N;;;;; A52A;VAI SYLLABLE PI;Lo;0;L;;;;;N;;;;; A52B;VAI SYLLABLE BHI;Lo;0;L;;;;;N;;;;; A52C;VAI SYLLABLE BI;Lo;0;L;;;;;N;;;;; A52D;VAI SYLLABLE MBI;Lo;0;L;;;;;N;;;;; A52E;VAI SYLLABLE KPI;Lo;0;L;;;;;N;;;;; A52F;VAI SYLLABLE MGBI;Lo;0;L;;;;;N;;;;; A530;VAI SYLLABLE GBI;Lo;0;L;;;;;N;;;;; A531;VAI SYLLABLE FI;Lo;0;L;;;;;N;;;;; A532;VAI SYLLABLE VI;Lo;0;L;;;;;N;;;;; A533;VAI SYLLABLE TI;Lo;0;L;;;;;N;;;;; A534;VAI SYLLABLE THI;Lo;0;L;;;;;N;;;;; A535;VAI SYLLABLE DHI;Lo;0;L;;;;;N;;;;; A536;VAI SYLLABLE DHHI;Lo;0;L;;;;;N;;;;; A537;VAI SYLLABLE LI;Lo;0;L;;;;;N;;;;; A538;VAI SYLLABLE RI;Lo;0;L;;;;;N;;;;; A539;VAI SYLLABLE DI;Lo;0;L;;;;;N;;;;; A53A;VAI SYLLABLE NDI;Lo;0;L;;;;;N;;;;; A53B;VAI SYLLABLE SI;Lo;0;L;;;;;N;;;;; A53C;VAI SYLLABLE SHI;Lo;0;L;;;;;N;;;;; A53D;VAI SYLLABLE ZI;Lo;0;L;;;;;N;;;;; A53E;VAI SYLLABLE ZHI;Lo;0;L;;;;;N;;;;; A53F;VAI SYLLABLE CI;Lo;0;L;;;;;N;;;;; A540;VAI SYLLABLE JI;Lo;0;L;;;;;N;;;;; A541;VAI SYLLABLE NJI;Lo;0;L;;;;;N;;;;; A542;VAI SYLLABLE YI;Lo;0;L;;;;;N;;;;; A543;VAI SYLLABLE KI;Lo;0;L;;;;;N;;;;; A544;VAI SYLLABLE NGGI;Lo;0;L;;;;;N;;;;; A545;VAI SYLLABLE GI;Lo;0;L;;;;;N;;;;; A546;VAI SYLLABLE MI;Lo;0;L;;;;;N;;;;; A547;VAI SYLLABLE NI;Lo;0;L;;;;;N;;;;; A548;VAI SYLLABLE NYI;Lo;0;L;;;;;N;;;;; A549;VAI SYLLABLE A;Lo;0;L;;;;;N;;;;; A54A;VAI SYLLABLE AN;Lo;0;L;;;;;N;;;;; A54B;VAI SYLLABLE NGAN;Lo;0;L;;;;;N;;;;; A54C;VAI SYLLABLE HA;Lo;0;L;;;;;N;;;;; A54D;VAI SYLLABLE HAN;Lo;0;L;;;;;N;;;;; A54E;VAI SYLLABLE WA;Lo;0;L;;;;;N;;;;; A54F;VAI SYLLABLE WAN;Lo;0;L;;;;;N;;;;; A550;VAI SYLLABLE PA;Lo;0;L;;;;;N;;;;; A551;VAI SYLLABLE BHA;Lo;0;L;;;;;N;;;;; A552;VAI SYLLABLE BA;Lo;0;L;;;;;N;;;;; A553;VAI SYLLABLE MBA;Lo;0;L;;;;;N;;;;; A554;VAI SYLLABLE KPA;Lo;0;L;;;;;N;;;;; A555;VAI SYLLABLE KPAN;Lo;0;L;;;;;N;;;;; A556;VAI SYLLABLE MGBA;Lo;0;L;;;;;N;;;;; A557;VAI SYLLABLE GBA;Lo;0;L;;;;;N;;;;; A558;VAI SYLLABLE FA;Lo;0;L;;;;;N;;;;; A559;VAI SYLLABLE VA;Lo;0;L;;;;;N;;;;; A55A;VAI SYLLABLE TA;Lo;0;L;;;;;N;;;;; A55B;VAI SYLLABLE THA;Lo;0;L;;;;;N;;;;; A55C;VAI SYLLABLE DHA;Lo;0;L;;;;;N;;;;; A55D;VAI SYLLABLE DHHA;Lo;0;L;;;;;N;;;;; A55E;VAI SYLLABLE LA;Lo;0;L;;;;;N;;;;; A55F;VAI SYLLABLE RA;Lo;0;L;;;;;N;;;;; A560;VAI SYLLABLE DA;Lo;0;L;;;;;N;;;;; A561;VAI SYLLABLE NDA;Lo;0;L;;;;;N;;;;; A562;VAI SYLLABLE SA;Lo;0;L;;;;;N;;;;; A563;VAI SYLLABLE SHA;Lo;0;L;;;;;N;;;;; A564;VAI SYLLABLE ZA;Lo;0;L;;;;;N;;;;; A565;VAI SYLLABLE ZHA;Lo;0;L;;;;;N;;;;; A566;VAI SYLLABLE CA;Lo;0;L;;;;;N;;;;; A567;VAI SYLLABLE JA;Lo;0;L;;;;;N;;;;; A568;VAI SYLLABLE NJA;Lo;0;L;;;;;N;;;;; A569;VAI SYLLABLE YA;Lo;0;L;;;;;N;;;;; A56A;VAI SYLLABLE KA;Lo;0;L;;;;;N;;;;; A56B;VAI SYLLABLE KAN;Lo;0;L;;;;;N;;;;; A56C;VAI SYLLABLE NGGA;Lo;0;L;;;;;N;;;;; A56D;VAI SYLLABLE GA;Lo;0;L;;;;;N;;;;; A56E;VAI SYLLABLE MA;Lo;0;L;;;;;N;;;;; A56F;VAI SYLLABLE NA;Lo;0;L;;;;;N;;;;; A570;VAI SYLLABLE NYA;Lo;0;L;;;;;N;;;;; A571;VAI SYLLABLE OO;Lo;0;L;;;;;N;;;;; A572;VAI SYLLABLE OON;Lo;0;L;;;;;N;;;;; A573;VAI SYLLABLE HOO;Lo;0;L;;;;;N;;;;; A574;VAI SYLLABLE WOO;Lo;0;L;;;;;N;;;;; A575;VAI SYLLABLE WOON;Lo;0;L;;;;;N;;;;; A576;VAI SYLLABLE POO;Lo;0;L;;;;;N;;;;; A577;VAI SYLLABLE BHOO;Lo;0;L;;;;;N;;;;; A578;VAI SYLLABLE BOO;Lo;0;L;;;;;N;;;;; A579;VAI SYLLABLE MBOO;Lo;0;L;;;;;N;;;;; A57A;VAI SYLLABLE KPOO;Lo;0;L;;;;;N;;;;; A57B;VAI SYLLABLE MGBOO;Lo;0;L;;;;;N;;;;; A57C;VAI SYLLABLE GBOO;Lo;0;L;;;;;N;;;;; A57D;VAI SYLLABLE FOO;Lo;0;L;;;;;N;;;;; A57E;VAI SYLLABLE VOO;Lo;0;L;;;;;N;;;;; A57F;VAI SYLLABLE TOO;Lo;0;L;;;;;N;;;;; A580;VAI SYLLABLE THOO;Lo;0;L;;;;;N;;;;; A581;VAI SYLLABLE DHOO;Lo;0;L;;;;;N;;;;; A582;VAI SYLLABLE DHHOO;Lo;0;L;;;;;N;;;;; A583;VAI SYLLABLE LOO;Lo;0;L;;;;;N;;;;; A584;VAI SYLLABLE ROO;Lo;0;L;;;;;N;;;;; A585;VAI SYLLABLE DOO;Lo;0;L;;;;;N;;;;; A586;VAI SYLLABLE NDOO;Lo;0;L;;;;;N;;;;; A587;VAI SYLLABLE SOO;Lo;0;L;;;;;N;;;;; A588;VAI SYLLABLE SHOO;Lo;0;L;;;;;N;;;;; A589;VAI SYLLABLE ZOO;Lo;0;L;;;;;N;;;;; A58A;VAI SYLLABLE ZHOO;Lo;0;L;;;;;N;;;;; A58B;VAI SYLLABLE COO;Lo;0;L;;;;;N;;;;; A58C;VAI SYLLABLE JOO;Lo;0;L;;;;;N;;;;; A58D;VAI SYLLABLE NJOO;Lo;0;L;;;;;N;;;;; A58E;VAI SYLLABLE YOO;Lo;0;L;;;;;N;;;;; A58F;VAI SYLLABLE KOO;Lo;0;L;;;;;N;;;;; A590;VAI SYLLABLE NGGOO;Lo;0;L;;;;;N;;;;; A591;VAI SYLLABLE GOO;Lo;0;L;;;;;N;;;;; A592;VAI SYLLABLE MOO;Lo;0;L;;;;;N;;;;; A593;VAI SYLLABLE NOO;Lo;0;L;;;;;N;;;;; A594;VAI SYLLABLE NYOO;Lo;0;L;;;;;N;;;;; A595;VAI SYLLABLE U;Lo;0;L;;;;;N;;;;; A596;VAI SYLLABLE UN;Lo;0;L;;;;;N;;;;; A597;VAI SYLLABLE HU;Lo;0;L;;;;;N;;;;; A598;VAI SYLLABLE HUN;Lo;0;L;;;;;N;;;;; A599;VAI SYLLABLE WU;Lo;0;L;;;;;N;;;;; A59A;VAI SYLLABLE WUN;Lo;0;L;;;;;N;;;;; A59B;VAI SYLLABLE PU;Lo;0;L;;;;;N;;;;; A59C;VAI SYLLABLE BHU;Lo;0;L;;;;;N;;;;; A59D;VAI SYLLABLE BU;Lo;0;L;;;;;N;;;;; A59E;VAI SYLLABLE MBU;Lo;0;L;;;;;N;;;;; A59F;VAI SYLLABLE KPU;Lo;0;L;;;;;N;;;;; A5A0;VAI SYLLABLE MGBU;Lo;0;L;;;;;N;;;;; A5A1;VAI SYLLABLE GBU;Lo;0;L;;;;;N;;;;; A5A2;VAI SYLLABLE FU;Lo;0;L;;;;;N;;;;; A5A3;VAI SYLLABLE VU;Lo;0;L;;;;;N;;;;; A5A4;VAI SYLLABLE TU;Lo;0;L;;;;;N;;;;; A5A5;VAI SYLLABLE THU;Lo;0;L;;;;;N;;;;; A5A6;VAI SYLLABLE DHU;Lo;0;L;;;;;N;;;;; A5A7;VAI SYLLABLE DHHU;Lo;0;L;;;;;N;;;;; A5A8;VAI SYLLABLE LU;Lo;0;L;;;;;N;;;;; A5A9;VAI SYLLABLE RU;Lo;0;L;;;;;N;;;;; A5AA;VAI SYLLABLE DU;Lo;0;L;;;;;N;;;;; A5AB;VAI SYLLABLE NDU;Lo;0;L;;;;;N;;;;; A5AC;VAI SYLLABLE SU;Lo;0;L;;;;;N;;;;; A5AD;VAI SYLLABLE SHU;Lo;0;L;;;;;N;;;;; A5AE;VAI SYLLABLE ZU;Lo;0;L;;;;;N;;;;; A5AF;VAI SYLLABLE ZHU;Lo;0;L;;;;;N;;;;; A5B0;VAI SYLLABLE CU;Lo;0;L;;;;;N;;;;; A5B1;VAI SYLLABLE JU;Lo;0;L;;;;;N;;;;; A5B2;VAI SYLLABLE NJU;Lo;0;L;;;;;N;;;;; A5B3;VAI SYLLABLE YU;Lo;0;L;;;;;N;;;;; A5B4;VAI SYLLABLE KU;Lo;0;L;;;;;N;;;;; A5B5;VAI SYLLABLE NGGU;Lo;0;L;;;;;N;;;;; A5B6;VAI SYLLABLE GU;Lo;0;L;;;;;N;;;;; A5B7;VAI SYLLABLE MU;Lo;0;L;;;;;N;;;;; A5B8;VAI SYLLABLE NU;Lo;0;L;;;;;N;;;;; A5B9;VAI SYLLABLE NYU;Lo;0;L;;;;;N;;;;; A5BA;VAI SYLLABLE O;Lo;0;L;;;;;N;;;;; A5BB;VAI SYLLABLE ON;Lo;0;L;;;;;N;;;;; A5BC;VAI SYLLABLE NGON;Lo;0;L;;;;;N;;;;; A5BD;VAI SYLLABLE HO;Lo;0;L;;;;;N;;;;; A5BE;VAI SYLLABLE HON;Lo;0;L;;;;;N;;;;; A5BF;VAI SYLLABLE WO;Lo;0;L;;;;;N;;;;; A5C0;VAI SYLLABLE WON;Lo;0;L;;;;;N;;;;; A5C1;VAI SYLLABLE PO;Lo;0;L;;;;;N;;;;; A5C2;VAI SYLLABLE BHO;Lo;0;L;;;;;N;;;;; A5C3;VAI SYLLABLE BO;Lo;0;L;;;;;N;;;;; A5C4;VAI SYLLABLE MBO;Lo;0;L;;;;;N;;;;; A5C5;VAI SYLLABLE KPO;Lo;0;L;;;;;N;;;;; A5C6;VAI SYLLABLE MGBO;Lo;0;L;;;;;N;;;;; A5C7;VAI SYLLABLE GBO;Lo;0;L;;;;;N;;;;; A5C8;VAI SYLLABLE GBON;Lo;0;L;;;;;N;;;;; A5C9;VAI SYLLABLE FO;Lo;0;L;;;;;N;;;;; A5CA;VAI SYLLABLE VO;Lo;0;L;;;;;N;;;;; A5CB;VAI SYLLABLE TO;Lo;0;L;;;;;N;;;;; A5CC;VAI SYLLABLE THO;Lo;0;L;;;;;N;;;;; A5CD;VAI SYLLABLE DHO;Lo;0;L;;;;;N;;;;; A5CE;VAI SYLLABLE DHHO;Lo;0;L;;;;;N;;;;; A5CF;VAI SYLLABLE LO;Lo;0;L;;;;;N;;;;; A5D0;VAI SYLLABLE RO;Lo;0;L;;;;;N;;;;; A5D1;VAI SYLLABLE DO;Lo;0;L;;;;;N;;;;; A5D2;VAI SYLLABLE NDO;Lo;0;L;;;;;N;;;;; A5D3;VAI SYLLABLE SO;Lo;0;L;;;;;N;;;;; A5D4;VAI SYLLABLE SHO;Lo;0;L;;;;;N;;;;; A5D5;VAI SYLLABLE ZO;Lo;0;L;;;;;N;;;;; A5D6;VAI SYLLABLE ZHO;Lo;0;L;;;;;N;;;;; A5D7;VAI SYLLABLE CO;Lo;0;L;;;;;N;;;;; A5D8;VAI SYLLABLE JO;Lo;0;L;;;;;N;;;;; A5D9;VAI SYLLABLE NJO;Lo;0;L;;;;;N;;;;; A5DA;VAI SYLLABLE YO;Lo;0;L;;;;;N;;;;; A5DB;VAI SYLLABLE KO;Lo;0;L;;;;;N;;;;; A5DC;VAI SYLLABLE NGGO;Lo;0;L;;;;;N;;;;; A5DD;VAI SYLLABLE GO;Lo;0;L;;;;;N;;;;; A5DE;VAI SYLLABLE MO;Lo;0;L;;;;;N;;;;; A5DF;VAI SYLLABLE NO;Lo;0;L;;;;;N;;;;; A5E0;VAI SYLLABLE NYO;Lo;0;L;;;;;N;;;;; A5E1;VAI SYLLABLE E;Lo;0;L;;;;;N;;;;; A5E2;VAI SYLLABLE EN;Lo;0;L;;;;;N;;;;; A5E3;VAI SYLLABLE NGEN;Lo;0;L;;;;;N;;;;; A5E4;VAI SYLLABLE HE;Lo;0;L;;;;;N;;;;; A5E5;VAI SYLLABLE HEN;Lo;0;L;;;;;N;;;;; A5E6;VAI SYLLABLE WE;Lo;0;L;;;;;N;;;;; A5E7;VAI SYLLABLE WEN;Lo;0;L;;;;;N;;;;; A5E8;VAI SYLLABLE PE;Lo;0;L;;;;;N;;;;; A5E9;VAI SYLLABLE BHE;Lo;0;L;;;;;N;;;;; A5EA;VAI SYLLABLE BE;Lo;0;L;;;;;N;;;;; A5EB;VAI SYLLABLE MBE;Lo;0;L;;;;;N;;;;; A5EC;VAI SYLLABLE KPE;Lo;0;L;;;;;N;;;;; A5ED;VAI SYLLABLE KPEN;Lo;0;L;;;;;N;;;;; A5EE;VAI SYLLABLE MGBE;Lo;0;L;;;;;N;;;;; A5EF;VAI SYLLABLE GBE;Lo;0;L;;;;;N;;;;; A5F0;VAI SYLLABLE GBEN;Lo;0;L;;;;;N;;;;; A5F1;VAI SYLLABLE FE;Lo;0;L;;;;;N;;;;; A5F2;VAI SYLLABLE VE;Lo;0;L;;;;;N;;;;; A5F3;VAI SYLLABLE TE;Lo;0;L;;;;;N;;;;; A5F4;VAI SYLLABLE THE;Lo;0;L;;;;;N;;;;; A5F5;VAI SYLLABLE DHE;Lo;0;L;;;;;N;;;;; A5F6;VAI SYLLABLE DHHE;Lo;0;L;;;;;N;;;;; A5F7;VAI SYLLABLE LE;Lo;0;L;;;;;N;;;;; A5F8;VAI SYLLABLE RE;Lo;0;L;;;;;N;;;;; A5F9;VAI SYLLABLE DE;Lo;0;L;;;;;N;;;;; A5FA;VAI SYLLABLE NDE;Lo;0;L;;;;;N;;;;; A5FB;VAI SYLLABLE SE;Lo;0;L;;;;;N;;;;; A5FC;VAI SYLLABLE SHE;Lo;0;L;;;;;N;;;;; A5FD;VAI SYLLABLE ZE;Lo;0;L;;;;;N;;;;; A5FE;VAI SYLLABLE ZHE;Lo;0;L;;;;;N;;;;; A5FF;VAI SYLLABLE CE;Lo;0;L;;;;;N;;;;; A600;VAI SYLLABLE JE;Lo;0;L;;;;;N;;;;; A601;VAI SYLLABLE NJE;Lo;0;L;;;;;N;;;;; A602;VAI SYLLABLE YE;Lo;0;L;;;;;N;;;;; A603;VAI SYLLABLE KE;Lo;0;L;;;;;N;;;;; A604;VAI SYLLABLE NGGE;Lo;0;L;;;;;N;;;;; A605;VAI SYLLABLE NGGEN;Lo;0;L;;;;;N;;;;; A606;VAI SYLLABLE GE;Lo;0;L;;;;;N;;;;; A607;VAI SYLLABLE GEN;Lo;0;L;;;;;N;;;;; A608;VAI SYLLABLE ME;Lo;0;L;;;;;N;;;;; A609;VAI SYLLABLE NE;Lo;0;L;;;;;N;;;;; A60A;VAI SYLLABLE NYE;Lo;0;L;;;;;N;;;;; A60B;VAI SYLLABLE NG;Lo;0;L;;;;;N;;;;; A60C;VAI SYLLABLE LENGTHENER;Lm;0;L;;;;;N;;;;; A60D;VAI COMMA;Po;0;ON;;;;;N;;;;; A60E;VAI FULL STOP;Po;0;ON;;;;;N;;;;; A60F;VAI QUESTION MARK;Po;0;ON;;;;;N;;;;; A610;VAI SYLLABLE NDOLE FA;Lo;0;L;;;;;N;;;;; A611;VAI SYLLABLE NDOLE KA;Lo;0;L;;;;;N;;;;; A612;VAI SYLLABLE NDOLE SOO;Lo;0;L;;;;;N;;;;; A613;VAI SYMBOL FEENG;Lo;0;L;;;;;N;;;;; A614;VAI SYMBOL KEENG;Lo;0;L;;;;;N;;;;; A615;VAI SYMBOL TING;Lo;0;L;;;;;N;;;;; A616;VAI SYMBOL NII;Lo;0;L;;;;;N;;;;; A617;VAI SYMBOL BANG;Lo;0;L;;;;;N;;;;; A618;VAI SYMBOL FAA;Lo;0;L;;;;;N;;;;; A619;VAI SYMBOL TAA;Lo;0;L;;;;;N;;;;; A61A;VAI SYMBOL DANG;Lo;0;L;;;;;N;;;;; A61B;VAI SYMBOL DOONG;Lo;0;L;;;;;N;;;;; A61C;VAI SYMBOL KUNG;Lo;0;L;;;;;N;;;;; A61D;VAI SYMBOL TONG;Lo;0;L;;;;;N;;;;; A61E;VAI SYMBOL DO-O;Lo;0;L;;;;;N;;;;; A61F;VAI SYMBOL JONG;Lo;0;L;;;;;N;;;;; A620;VAI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; A621;VAI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; A622;VAI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; A623;VAI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; A624;VAI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; A625;VAI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; A626;VAI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; A627;VAI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; A628;VAI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; A629;VAI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; A62A;VAI SYLLABLE NDOLE MA;Lo;0;L;;;;;N;;;;; A62B;VAI SYLLABLE NDOLE DO;Lo;0;L;;;;;N;;;;; A640;CYRILLIC CAPITAL LETTER ZEMLYA;Lu;0;L;;;;;N;;;;A641; A641;CYRILLIC SMALL LETTER ZEMLYA;Ll;0;L;;;;;N;;;A640;;A640 A642;CYRILLIC CAPITAL LETTER DZELO;Lu;0;L;;;;;N;;;;A643; A643;CYRILLIC SMALL LETTER DZELO;Ll;0;L;;;;;N;;;A642;;A642 A644;CYRILLIC CAPITAL LETTER REVERSED DZE;Lu;0;L;;;;;N;;;;A645; A645;CYRILLIC SMALL LETTER REVERSED DZE;Ll;0;L;;;;;N;;;A644;;A644 A646;CYRILLIC CAPITAL LETTER IOTA;Lu;0;L;;;;;N;;;;A647; A647;CYRILLIC SMALL LETTER IOTA;Ll;0;L;;;;;N;;;A646;;A646 A648;CYRILLIC CAPITAL LETTER DJERV;Lu;0;L;;;;;N;;;;A649; A649;CYRILLIC SMALL LETTER DJERV;Ll;0;L;;;;;N;;;A648;;A648 A64A;CYRILLIC CAPITAL LETTER MONOGRAPH UK;Lu;0;L;;;;;N;;;;A64B; A64B;CYRILLIC SMALL LETTER MONOGRAPH UK;Ll;0;L;;;;;N;;;A64A;;A64A A64C;CYRILLIC CAPITAL LETTER BROAD OMEGA;Lu;0;L;;;;;N;;;;A64D; A64D;CYRILLIC SMALL LETTER BROAD OMEGA;Ll;0;L;;;;;N;;;A64C;;A64C A64E;CYRILLIC CAPITAL LETTER NEUTRAL YER;Lu;0;L;;;;;N;;;;A64F; A64F;CYRILLIC SMALL LETTER NEUTRAL YER;Ll;0;L;;;;;N;;;A64E;;A64E A650;CYRILLIC CAPITAL LETTER YERU WITH BACK YER;Lu;0;L;;;;;N;;;;A651; A651;CYRILLIC SMALL LETTER YERU WITH BACK YER;Ll;0;L;;;;;N;;;A650;;A650 A652;CYRILLIC CAPITAL LETTER IOTIFIED YAT;Lu;0;L;;;;;N;;;;A653; A653;CYRILLIC SMALL LETTER IOTIFIED YAT;Ll;0;L;;;;;N;;;A652;;A652 A654;CYRILLIC CAPITAL LETTER REVERSED YU;Lu;0;L;;;;;N;;;;A655; A655;CYRILLIC SMALL LETTER REVERSED YU;Ll;0;L;;;;;N;;;A654;;A654 A656;CYRILLIC CAPITAL LETTER IOTIFIED A;Lu;0;L;;;;;N;;;;A657; A657;CYRILLIC SMALL LETTER IOTIFIED A;Ll;0;L;;;;;N;;;A656;;A656 A658;CYRILLIC CAPITAL LETTER CLOSED LITTLE YUS;Lu;0;L;;;;;N;;;;A659; A659;CYRILLIC SMALL LETTER CLOSED LITTLE YUS;Ll;0;L;;;;;N;;;A658;;A658 A65A;CYRILLIC CAPITAL LETTER BLENDED YUS;Lu;0;L;;;;;N;;;;A65B; A65B;CYRILLIC SMALL LETTER BLENDED YUS;Ll;0;L;;;;;N;;;A65A;;A65A A65C;CYRILLIC CAPITAL LETTER IOTIFIED CLOSED LITTLE YUS;Lu;0;L;;;;;N;;;;A65D; A65D;CYRILLIC SMALL LETTER IOTIFIED CLOSED LITTLE YUS;Ll;0;L;;;;;N;;;A65C;;A65C A65E;CYRILLIC CAPITAL LETTER YN;Lu;0;L;;;;;N;;;;A65F; A65F;CYRILLIC SMALL LETTER YN;Ll;0;L;;;;;N;;;A65E;;A65E A660;CYRILLIC CAPITAL LETTER REVERSED TSE;Lu;0;L;;;;;N;;;;A661; A661;CYRILLIC SMALL LETTER REVERSED TSE;Ll;0;L;;;;;N;;;A660;;A660 A662;CYRILLIC CAPITAL LETTER SOFT DE;Lu;0;L;;;;;N;;;;A663; A663;CYRILLIC SMALL LETTER SOFT DE;Ll;0;L;;;;;N;;;A662;;A662 A664;CYRILLIC CAPITAL LETTER SOFT EL;Lu;0;L;;;;;N;;;;A665; A665;CYRILLIC SMALL LETTER SOFT EL;Ll;0;L;;;;;N;;;A664;;A664 A666;CYRILLIC CAPITAL LETTER SOFT EM;Lu;0;L;;;;;N;;;;A667; A667;CYRILLIC SMALL LETTER SOFT EM;Ll;0;L;;;;;N;;;A666;;A666 A668;CYRILLIC CAPITAL LETTER MONOCULAR O;Lu;0;L;;;;;N;;;;A669; A669;CYRILLIC SMALL LETTER MONOCULAR O;Ll;0;L;;;;;N;;;A668;;A668 A66A;CYRILLIC CAPITAL LETTER BINOCULAR O;Lu;0;L;;;;;N;;;;A66B; A66B;CYRILLIC SMALL LETTER BINOCULAR O;Ll;0;L;;;;;N;;;A66A;;A66A A66C;CYRILLIC CAPITAL LETTER DOUBLE MONOCULAR O;Lu;0;L;;;;;N;;;;A66D; A66D;CYRILLIC SMALL LETTER DOUBLE MONOCULAR O;Ll;0;L;;;;;N;;;A66C;;A66C A66E;CYRILLIC LETTER MULTIOCULAR O;Lo;0;L;;;;;N;;;;; A66F;COMBINING CYRILLIC VZMET;Mn;230;NSM;;;;;N;;;;; A670;COMBINING CYRILLIC TEN MILLIONS SIGN;Me;0;NSM;;;;;N;;;;; A671;COMBINING CYRILLIC HUNDRED MILLIONS SIGN;Me;0;NSM;;;;;N;;;;; A672;COMBINING CYRILLIC THOUSAND MILLIONS SIGN;Me;0;NSM;;;;;N;;;;; A673;SLAVONIC ASTERISK;Po;0;ON;;;;;N;;;;; A674;COMBINING CYRILLIC LETTER UKRAINIAN IE;Mn;230;NSM;;;;;N;;;;; A675;COMBINING CYRILLIC LETTER I;Mn;230;NSM;;;;;N;;;;; A676;COMBINING CYRILLIC LETTER YI;Mn;230;NSM;;;;;N;;;;; A677;COMBINING CYRILLIC LETTER U;Mn;230;NSM;;;;;N;;;;; A678;COMBINING CYRILLIC LETTER HARD SIGN;Mn;230;NSM;;;;;N;;;;; A679;COMBINING CYRILLIC LETTER YERU;Mn;230;NSM;;;;;N;;;;; A67A;COMBINING CYRILLIC LETTER SOFT SIGN;Mn;230;NSM;;;;;N;;;;; A67B;COMBINING CYRILLIC LETTER OMEGA;Mn;230;NSM;;;;;N;;;;; A67C;COMBINING CYRILLIC KAVYKA;Mn;230;NSM;;;;;N;;;;; A67D;COMBINING CYRILLIC PAYEROK;Mn;230;NSM;;;;;N;;;;; A67E;CYRILLIC KAVYKA;Po;0;ON;;;;;N;;;;; A67F;CYRILLIC PAYEROK;Lm;0;ON;;;;;N;;;;; A680;CYRILLIC CAPITAL LETTER DWE;Lu;0;L;;;;;N;;;;A681; A681;CYRILLIC SMALL LETTER DWE;Ll;0;L;;;;;N;;;A680;;A680 A682;CYRILLIC CAPITAL LETTER DZWE;Lu;0;L;;;;;N;;;;A683; A683;CYRILLIC SMALL LETTER DZWE;Ll;0;L;;;;;N;;;A682;;A682 A684;CYRILLIC CAPITAL LETTER ZHWE;Lu;0;L;;;;;N;;;;A685; A685;CYRILLIC SMALL LETTER ZHWE;Ll;0;L;;;;;N;;;A684;;A684 A686;CYRILLIC CAPITAL LETTER CCHE;Lu;0;L;;;;;N;;;;A687; A687;CYRILLIC SMALL LETTER CCHE;Ll;0;L;;;;;N;;;A686;;A686 A688;CYRILLIC CAPITAL LETTER DZZE;Lu;0;L;;;;;N;;;;A689; A689;CYRILLIC SMALL LETTER DZZE;Ll;0;L;;;;;N;;;A688;;A688 A68A;CYRILLIC CAPITAL LETTER TE WITH MIDDLE HOOK;Lu;0;L;;;;;N;;;;A68B; A68B;CYRILLIC SMALL LETTER TE WITH MIDDLE HOOK;Ll;0;L;;;;;N;;;A68A;;A68A A68C;CYRILLIC CAPITAL LETTER TWE;Lu;0;L;;;;;N;;;;A68D; A68D;CYRILLIC SMALL LETTER TWE;Ll;0;L;;;;;N;;;A68C;;A68C A68E;CYRILLIC CAPITAL LETTER TSWE;Lu;0;L;;;;;N;;;;A68F; A68F;CYRILLIC SMALL LETTER TSWE;Ll;0;L;;;;;N;;;A68E;;A68E A690;CYRILLIC CAPITAL LETTER TSSE;Lu;0;L;;;;;N;;;;A691; A691;CYRILLIC SMALL LETTER TSSE;Ll;0;L;;;;;N;;;A690;;A690 A692;CYRILLIC CAPITAL LETTER TCHE;Lu;0;L;;;;;N;;;;A693; A693;CYRILLIC SMALL LETTER TCHE;Ll;0;L;;;;;N;;;A692;;A692 A694;CYRILLIC CAPITAL LETTER HWE;Lu;0;L;;;;;N;;;;A695; A695;CYRILLIC SMALL LETTER HWE;Ll;0;L;;;;;N;;;A694;;A694 A696;CYRILLIC CAPITAL LETTER SHWE;Lu;0;L;;;;;N;;;;A697; A697;CYRILLIC SMALL LETTER SHWE;Ll;0;L;;;;;N;;;A696;;A696 A698;CYRILLIC CAPITAL LETTER DOUBLE O;Lu;0;L;;;;;N;;;;A699; A699;CYRILLIC SMALL LETTER DOUBLE O;Ll;0;L;;;;;N;;;A698;;A698 A69A;CYRILLIC CAPITAL LETTER CROSSED O;Lu;0;L;;;;;N;;;;A69B; A69B;CYRILLIC SMALL LETTER CROSSED O;Ll;0;L;;;;;N;;;A69A;;A69A A69C;MODIFIER LETTER CYRILLIC HARD SIGN;Lm;0;L; 044A;;;;N;;;;; A69D;MODIFIER LETTER CYRILLIC SOFT SIGN;Lm;0;L; 044C;;;;N;;;;; A69E;COMBINING CYRILLIC LETTER EF;Mn;230;NSM;;;;;N;;;;; A69F;COMBINING CYRILLIC LETTER IOTIFIED E;Mn;230;NSM;;;;;N;;;;; A6A0;BAMUM LETTER A;Lo;0;L;;;;;N;;;;; A6A1;BAMUM LETTER KA;Lo;0;L;;;;;N;;;;; A6A2;BAMUM LETTER U;Lo;0;L;;;;;N;;;;; A6A3;BAMUM LETTER KU;Lo;0;L;;;;;N;;;;; A6A4;BAMUM LETTER EE;Lo;0;L;;;;;N;;;;; A6A5;BAMUM LETTER REE;Lo;0;L;;;;;N;;;;; A6A6;BAMUM LETTER TAE;Lo;0;L;;;;;N;;;;; A6A7;BAMUM LETTER O;Lo;0;L;;;;;N;;;;; A6A8;BAMUM LETTER NYI;Lo;0;L;;;;;N;;;;; A6A9;BAMUM LETTER I;Lo;0;L;;;;;N;;;;; A6AA;BAMUM LETTER LA;Lo;0;L;;;;;N;;;;; A6AB;BAMUM LETTER PA;Lo;0;L;;;;;N;;;;; A6AC;BAMUM LETTER RII;Lo;0;L;;;;;N;;;;; A6AD;BAMUM LETTER RIEE;Lo;0;L;;;;;N;;;;; A6AE;BAMUM LETTER LEEEE;Lo;0;L;;;;;N;;;;; A6AF;BAMUM LETTER MEEEE;Lo;0;L;;;;;N;;;;; A6B0;BAMUM LETTER TAA;Lo;0;L;;;;;N;;;;; A6B1;BAMUM LETTER NDAA;Lo;0;L;;;;;N;;;;; A6B2;BAMUM LETTER NJAEM;Lo;0;L;;;;;N;;;;; A6B3;BAMUM LETTER M;Lo;0;L;;;;;N;;;;; A6B4;BAMUM LETTER SUU;Lo;0;L;;;;;N;;;;; A6B5;BAMUM LETTER MU;Lo;0;L;;;;;N;;;;; A6B6;BAMUM LETTER SHII;Lo;0;L;;;;;N;;;;; A6B7;BAMUM LETTER SI;Lo;0;L;;;;;N;;;;; A6B8;BAMUM LETTER SHEUX;Lo;0;L;;;;;N;;;;; A6B9;BAMUM LETTER SEUX;Lo;0;L;;;;;N;;;;; A6BA;BAMUM LETTER KYEE;Lo;0;L;;;;;N;;;;; A6BB;BAMUM LETTER KET;Lo;0;L;;;;;N;;;;; A6BC;BAMUM LETTER NUAE;Lo;0;L;;;;;N;;;;; A6BD;BAMUM LETTER NU;Lo;0;L;;;;;N;;;;; A6BE;BAMUM LETTER NJUAE;Lo;0;L;;;;;N;;;;; A6BF;BAMUM LETTER YOQ;Lo;0;L;;;;;N;;;;; A6C0;BAMUM LETTER SHU;Lo;0;L;;;;;N;;;;; A6C1;BAMUM LETTER YUQ;Lo;0;L;;;;;N;;;;; A6C2;BAMUM LETTER YA;Lo;0;L;;;;;N;;;;; A6C3;BAMUM LETTER NSHA;Lo;0;L;;;;;N;;;;; A6C4;BAMUM LETTER KEUX;Lo;0;L;;;;;N;;;;; A6C5;BAMUM LETTER PEUX;Lo;0;L;;;;;N;;;;; A6C6;BAMUM LETTER NJEE;Lo;0;L;;;;;N;;;;; A6C7;BAMUM LETTER NTEE;Lo;0;L;;;;;N;;;;; A6C8;BAMUM LETTER PUE;Lo;0;L;;;;;N;;;;; A6C9;BAMUM LETTER WUE;Lo;0;L;;;;;N;;;;; A6CA;BAMUM LETTER PEE;Lo;0;L;;;;;N;;;;; A6CB;BAMUM LETTER FEE;Lo;0;L;;;;;N;;;;; A6CC;BAMUM LETTER RU;Lo;0;L;;;;;N;;;;; A6CD;BAMUM LETTER LU;Lo;0;L;;;;;N;;;;; A6CE;BAMUM LETTER MI;Lo;0;L;;;;;N;;;;; A6CF;BAMUM LETTER NI;Lo;0;L;;;;;N;;;;; A6D0;BAMUM LETTER REUX;Lo;0;L;;;;;N;;;;; A6D1;BAMUM LETTER RAE;Lo;0;L;;;;;N;;;;; A6D2;BAMUM LETTER KEN;Lo;0;L;;;;;N;;;;; A6D3;BAMUM LETTER NGKWAEN;Lo;0;L;;;;;N;;;;; A6D4;BAMUM LETTER NGGA;Lo;0;L;;;;;N;;;;; A6D5;BAMUM LETTER NGA;Lo;0;L;;;;;N;;;;; A6D6;BAMUM LETTER SHO;Lo;0;L;;;;;N;;;;; A6D7;BAMUM LETTER PUAE;Lo;0;L;;;;;N;;;;; A6D8;BAMUM LETTER FU;Lo;0;L;;;;;N;;;;; A6D9;BAMUM LETTER FOM;Lo;0;L;;;;;N;;;;; A6DA;BAMUM LETTER WA;Lo;0;L;;;;;N;;;;; A6DB;BAMUM LETTER NA;Lo;0;L;;;;;N;;;;; A6DC;BAMUM LETTER LI;Lo;0;L;;;;;N;;;;; A6DD;BAMUM LETTER PI;Lo;0;L;;;;;N;;;;; A6DE;BAMUM LETTER LOQ;Lo;0;L;;;;;N;;;;; A6DF;BAMUM LETTER KO;Lo;0;L;;;;;N;;;;; A6E0;BAMUM LETTER MBEN;Lo;0;L;;;;;N;;;;; A6E1;BAMUM LETTER REN;Lo;0;L;;;;;N;;;;; A6E2;BAMUM LETTER MEN;Lo;0;L;;;;;N;;;;; A6E3;BAMUM LETTER MA;Lo;0;L;;;;;N;;;;; A6E4;BAMUM LETTER TI;Lo;0;L;;;;;N;;;;; A6E5;BAMUM LETTER KI;Lo;0;L;;;;;N;;;;; A6E6;BAMUM LETTER MO;Nl;0;L;;;;1;N;;;;; A6E7;BAMUM LETTER MBAA;Nl;0;L;;;;2;N;;;;; A6E8;BAMUM LETTER TET;Nl;0;L;;;;3;N;;;;; A6E9;BAMUM LETTER KPA;Nl;0;L;;;;4;N;;;;; A6EA;BAMUM LETTER TEN;Nl;0;L;;;;5;N;;;;; A6EB;BAMUM LETTER NTUU;Nl;0;L;;;;6;N;;;;; A6EC;BAMUM LETTER SAMBA;Nl;0;L;;;;7;N;;;;; A6ED;BAMUM LETTER FAAMAE;Nl;0;L;;;;8;N;;;;; A6EE;BAMUM LETTER KOVUU;Nl;0;L;;;;9;N;;;;; A6EF;BAMUM LETTER KOGHOM;Nl;0;L;;;;0;N;;;;; A6F0;BAMUM COMBINING MARK KOQNDON;Mn;230;NSM;;;;;N;;;;; A6F1;BAMUM COMBINING MARK TUKWENTIS;Mn;230;NSM;;;;;N;;;;; A6F2;BAMUM NJAEMLI;Po;0;L;;;;;N;;;;; A6F3;BAMUM FULL STOP;Po;0;L;;;;;N;;;;; A6F4;BAMUM COLON;Po;0;L;;;;;N;;;;; A6F5;BAMUM COMMA;Po;0;L;;;;;N;;;;; A6F6;BAMUM SEMICOLON;Po;0;L;;;;;N;;;;; A6F7;BAMUM QUESTION MARK;Po;0;L;;;;;N;;;;; A700;MODIFIER LETTER CHINESE TONE YIN PING;Sk;0;ON;;;;;N;;;;; A701;MODIFIER LETTER CHINESE TONE YANG PING;Sk;0;ON;;;;;N;;;;; A702;MODIFIER LETTER CHINESE TONE YIN SHANG;Sk;0;ON;;;;;N;;;;; A703;MODIFIER LETTER CHINESE TONE YANG SHANG;Sk;0;ON;;;;;N;;;;; A704;MODIFIER LETTER CHINESE TONE YIN QU;Sk;0;ON;;;;;N;;;;; A705;MODIFIER LETTER CHINESE TONE YANG QU;Sk;0;ON;;;;;N;;;;; A706;MODIFIER LETTER CHINESE TONE YIN RU;Sk;0;ON;;;;;N;;;;; A707;MODIFIER LETTER CHINESE TONE YANG RU;Sk;0;ON;;;;;N;;;;; A708;MODIFIER LETTER EXTRA-HIGH DOTTED TONE BAR;Sk;0;ON;;;;;N;;;;; A709;MODIFIER LETTER HIGH DOTTED TONE BAR;Sk;0;ON;;;;;N;;;;; A70A;MODIFIER LETTER MID DOTTED TONE BAR;Sk;0;ON;;;;;N;;;;; A70B;MODIFIER LETTER LOW DOTTED TONE BAR;Sk;0;ON;;;;;N;;;;; A70C;MODIFIER LETTER EXTRA-LOW DOTTED TONE BAR;Sk;0;ON;;;;;N;;;;; A70D;MODIFIER LETTER EXTRA-HIGH DOTTED LEFT-STEM TONE BAR;Sk;0;ON;;;;;N;;;;; A70E;MODIFIER LETTER HIGH DOTTED LEFT-STEM TONE BAR;Sk;0;ON;;;;;N;;;;; A70F;MODIFIER LETTER MID DOTTED LEFT-STEM TONE BAR;Sk;0;ON;;;;;N;;;;; A710;MODIFIER LETTER LOW DOTTED LEFT-STEM TONE BAR;Sk;0;ON;;;;;N;;;;; A711;MODIFIER LETTER EXTRA-LOW DOTTED LEFT-STEM TONE BAR;Sk;0;ON;;;;;N;;;;; A712;MODIFIER LETTER EXTRA-HIGH LEFT-STEM TONE BAR;Sk;0;ON;;;;;N;;;;; A713;MODIFIER LETTER HIGH LEFT-STEM TONE BAR;Sk;0;ON;;;;;N;;;;; A714;MODIFIER LETTER MID LEFT-STEM TONE BAR;Sk;0;ON;;;;;N;;;;; A715;MODIFIER LETTER LOW LEFT-STEM TONE BAR;Sk;0;ON;;;;;N;;;;; A716;MODIFIER LETTER EXTRA-LOW LEFT-STEM TONE BAR;Sk;0;ON;;;;;N;;;;; A717;MODIFIER LETTER DOT VERTICAL BAR;Lm;0;ON;;;;;N;;;;; A718;MODIFIER LETTER DOT SLASH;Lm;0;ON;;;;;N;;;;; A719;MODIFIER LETTER DOT HORIZONTAL BAR;Lm;0;ON;;;;;N;;;;; A71A;MODIFIER LETTER LOWER RIGHT CORNER ANGLE;Lm;0;ON;;;;;N;;;;; A71B;MODIFIER LETTER RAISED UP ARROW;Lm;0;ON;;;;;N;;;;; A71C;MODIFIER LETTER RAISED DOWN ARROW;Lm;0;ON;;;;;N;;;;; A71D;MODIFIER LETTER RAISED EXCLAMATION MARK;Lm;0;ON;;;;;N;;;;; A71E;MODIFIER LETTER RAISED INVERTED EXCLAMATION MARK;Lm;0;ON;;;;;N;;;;; A71F;MODIFIER LETTER LOW INVERTED EXCLAMATION MARK;Lm;0;ON;;;;;N;;;;; A720;MODIFIER LETTER STRESS AND HIGH TONE;Sk;0;ON;;;;;N;;;;; A721;MODIFIER LETTER STRESS AND LOW TONE;Sk;0;ON;;;;;N;;;;; A722;LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF;Lu;0;L;;;;;N;;;;A723; A723;LATIN SMALL LETTER EGYPTOLOGICAL ALEF;Ll;0;L;;;;;N;;;A722;;A722 A724;LATIN CAPITAL LETTER EGYPTOLOGICAL AIN;Lu;0;L;;;;;N;;;;A725; A725;LATIN SMALL LETTER EGYPTOLOGICAL AIN;Ll;0;L;;;;;N;;;A724;;A724 A726;LATIN CAPITAL LETTER HENG;Lu;0;L;;;;;N;;;;A727; A727;LATIN SMALL LETTER HENG;Ll;0;L;;;;;N;;;A726;;A726 A728;LATIN CAPITAL LETTER TZ;Lu;0;L;;;;;N;;;;A729; A729;LATIN SMALL LETTER TZ;Ll;0;L;;;;;N;;;A728;;A728 A72A;LATIN CAPITAL LETTER TRESILLO;Lu;0;L;;;;;N;;;;A72B; A72B;LATIN SMALL LETTER TRESILLO;Ll;0;L;;;;;N;;;A72A;;A72A A72C;LATIN CAPITAL LETTER CUATRILLO;Lu;0;L;;;;;N;;;;A72D; A72D;LATIN SMALL LETTER CUATRILLO;Ll;0;L;;;;;N;;;A72C;;A72C A72E;LATIN CAPITAL LETTER CUATRILLO WITH COMMA;Lu;0;L;;;;;N;;;;A72F; A72F;LATIN SMALL LETTER CUATRILLO WITH COMMA;Ll;0;L;;;;;N;;;A72E;;A72E A730;LATIN LETTER SMALL CAPITAL F;Ll;0;L;;;;;N;;;;; A731;LATIN LETTER SMALL CAPITAL S;Ll;0;L;;;;;N;;;;; A732;LATIN CAPITAL LETTER AA;Lu;0;L;;;;;N;;;;A733; A733;LATIN SMALL LETTER AA;Ll;0;L;;;;;N;;;A732;;A732 A734;LATIN CAPITAL LETTER AO;Lu;0;L;;;;;N;;;;A735; A735;LATIN SMALL LETTER AO;Ll;0;L;;;;;N;;;A734;;A734 A736;LATIN CAPITAL LETTER AU;Lu;0;L;;;;;N;;;;A737; A737;LATIN SMALL LETTER AU;Ll;0;L;;;;;N;;;A736;;A736 A738;LATIN CAPITAL LETTER AV;Lu;0;L;;;;;N;;;;A739; A739;LATIN SMALL LETTER AV;Ll;0;L;;;;;N;;;A738;;A738 A73A;LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR;Lu;0;L;;;;;N;;;;A73B; A73B;LATIN SMALL LETTER AV WITH HORIZONTAL BAR;Ll;0;L;;;;;N;;;A73A;;A73A A73C;LATIN CAPITAL LETTER AY;Lu;0;L;;;;;N;;;;A73D; A73D;LATIN SMALL LETTER AY;Ll;0;L;;;;;N;;;A73C;;A73C A73E;LATIN CAPITAL LETTER REVERSED C WITH DOT;Lu;0;L;;;;;N;;;;A73F; A73F;LATIN SMALL LETTER REVERSED C WITH DOT;Ll;0;L;;;;;N;;;A73E;;A73E A740;LATIN CAPITAL LETTER K WITH STROKE;Lu;0;L;;;;;N;;;;A741; A741;LATIN SMALL LETTER K WITH STROKE;Ll;0;L;;;;;N;;;A740;;A740 A742;LATIN CAPITAL LETTER K WITH DIAGONAL STROKE;Lu;0;L;;;;;N;;;;A743; A743;LATIN SMALL LETTER K WITH DIAGONAL STROKE;Ll;0;L;;;;;N;;;A742;;A742 A744;LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE;Lu;0;L;;;;;N;;;;A745; A745;LATIN SMALL LETTER K WITH STROKE AND DIAGONAL STROKE;Ll;0;L;;;;;N;;;A744;;A744 A746;LATIN CAPITAL LETTER BROKEN L;Lu;0;L;;;;;N;;;;A747; A747;LATIN SMALL LETTER BROKEN L;Ll;0;L;;;;;N;;;A746;;A746 A748;LATIN CAPITAL LETTER L WITH HIGH STROKE;Lu;0;L;;;;;N;;;;A749; A749;LATIN SMALL LETTER L WITH HIGH STROKE;Ll;0;L;;;;;N;;;A748;;A748 A74A;LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY;Lu;0;L;;;;;N;;;;A74B; A74B;LATIN SMALL LETTER O WITH LONG STROKE OVERLAY;Ll;0;L;;;;;N;;;A74A;;A74A A74C;LATIN CAPITAL LETTER O WITH LOOP;Lu;0;L;;;;;N;;;;A74D; A74D;LATIN SMALL LETTER O WITH LOOP;Ll;0;L;;;;;N;;;A74C;;A74C A74E;LATIN CAPITAL LETTER OO;Lu;0;L;;;;;N;;;;A74F; A74F;LATIN SMALL LETTER OO;Ll;0;L;;;;;N;;;A74E;;A74E A750;LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER;Lu;0;L;;;;;N;;;;A751; A751;LATIN SMALL LETTER P WITH STROKE THROUGH DESCENDER;Ll;0;L;;;;;N;;;A750;;A750 A752;LATIN CAPITAL LETTER P WITH FLOURISH;Lu;0;L;;;;;N;;;;A753; A753;LATIN SMALL LETTER P WITH FLOURISH;Ll;0;L;;;;;N;;;A752;;A752 A754;LATIN CAPITAL LETTER P WITH SQUIRREL TAIL;Lu;0;L;;;;;N;;;;A755; A755;LATIN SMALL LETTER P WITH SQUIRREL TAIL;Ll;0;L;;;;;N;;;A754;;A754 A756;LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER;Lu;0;L;;;;;N;;;;A757; A757;LATIN SMALL LETTER Q WITH STROKE THROUGH DESCENDER;Ll;0;L;;;;;N;;;A756;;A756 A758;LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE;Lu;0;L;;;;;N;;;;A759; A759;LATIN SMALL LETTER Q WITH DIAGONAL STROKE;Ll;0;L;;;;;N;;;A758;;A758 A75A;LATIN CAPITAL LETTER R ROTUNDA;Lu;0;L;;;;;N;;;;A75B; A75B;LATIN SMALL LETTER R ROTUNDA;Ll;0;L;;;;;N;;;A75A;;A75A A75C;LATIN CAPITAL LETTER RUM ROTUNDA;Lu;0;L;;;;;N;;;;A75D; A75D;LATIN SMALL LETTER RUM ROTUNDA;Ll;0;L;;;;;N;;;A75C;;A75C A75E;LATIN CAPITAL LETTER V WITH DIAGONAL STROKE;Lu;0;L;;;;;N;;;;A75F; A75F;LATIN SMALL LETTER V WITH DIAGONAL STROKE;Ll;0;L;;;;;N;;;A75E;;A75E A760;LATIN CAPITAL LETTER VY;Lu;0;L;;;;;N;;;;A761; A761;LATIN SMALL LETTER VY;Ll;0;L;;;;;N;;;A760;;A760 A762;LATIN CAPITAL LETTER VISIGOTHIC Z;Lu;0;L;;;;;N;;;;A763; A763;LATIN SMALL LETTER VISIGOTHIC Z;Ll;0;L;;;;;N;;;A762;;A762 A764;LATIN CAPITAL LETTER THORN WITH STROKE;Lu;0;L;;;;;N;;;;A765; A765;LATIN SMALL LETTER THORN WITH STROKE;Ll;0;L;;;;;N;;;A764;;A764 A766;LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER;Lu;0;L;;;;;N;;;;A767; A767;LATIN SMALL LETTER THORN WITH STROKE THROUGH DESCENDER;Ll;0;L;;;;;N;;;A766;;A766 A768;LATIN CAPITAL LETTER VEND;Lu;0;L;;;;;N;;;;A769; A769;LATIN SMALL LETTER VEND;Ll;0;L;;;;;N;;;A768;;A768 A76A;LATIN CAPITAL LETTER ET;Lu;0;L;;;;;N;;;;A76B; A76B;LATIN SMALL LETTER ET;Ll;0;L;;;;;N;;;A76A;;A76A A76C;LATIN CAPITAL LETTER IS;Lu;0;L;;;;;N;;;;A76D; A76D;LATIN SMALL LETTER IS;Ll;0;L;;;;;N;;;A76C;;A76C A76E;LATIN CAPITAL LETTER CON;Lu;0;L;;;;;N;;;;A76F; A76F;LATIN SMALL LETTER CON;Ll;0;L;;;;;N;;;A76E;;A76E A770;MODIFIER LETTER US;Lm;0;L; A76F;;;;N;;;;; A771;LATIN SMALL LETTER DUM;Ll;0;L;;;;;N;;;;; A772;LATIN SMALL LETTER LUM;Ll;0;L;;;;;N;;;;; A773;LATIN SMALL LETTER MUM;Ll;0;L;;;;;N;;;;; A774;LATIN SMALL LETTER NUM;Ll;0;L;;;;;N;;;;; A775;LATIN SMALL LETTER RUM;Ll;0;L;;;;;N;;;;; A776;LATIN LETTER SMALL CAPITAL RUM;Ll;0;L;;;;;N;;;;; A777;LATIN SMALL LETTER TUM;Ll;0;L;;;;;N;;;;; A778;LATIN SMALL LETTER UM;Ll;0;L;;;;;N;;;;; A779;LATIN CAPITAL LETTER INSULAR D;Lu;0;L;;;;;N;;;;A77A; A77A;LATIN SMALL LETTER INSULAR D;Ll;0;L;;;;;N;;;A779;;A779 A77B;LATIN CAPITAL LETTER INSULAR F;Lu;0;L;;;;;N;;;;A77C; A77C;LATIN SMALL LETTER INSULAR F;Ll;0;L;;;;;N;;;A77B;;A77B A77D;LATIN CAPITAL LETTER INSULAR G;Lu;0;L;;;;;N;;;;1D79; A77E;LATIN CAPITAL LETTER TURNED INSULAR G;Lu;0;L;;;;;N;;;;A77F; A77F;LATIN SMALL LETTER TURNED INSULAR G;Ll;0;L;;;;;N;;;A77E;;A77E A780;LATIN CAPITAL LETTER TURNED L;Lu;0;L;;;;;N;;;;A781; A781;LATIN SMALL LETTER TURNED L;Ll;0;L;;;;;N;;;A780;;A780 A782;LATIN CAPITAL LETTER INSULAR R;Lu;0;L;;;;;N;;;;A783; A783;LATIN SMALL LETTER INSULAR R;Ll;0;L;;;;;N;;;A782;;A782 A784;LATIN CAPITAL LETTER INSULAR S;Lu;0;L;;;;;N;;;;A785; A785;LATIN SMALL LETTER INSULAR S;Ll;0;L;;;;;N;;;A784;;A784 A786;LATIN CAPITAL LETTER INSULAR T;Lu;0;L;;;;;N;;;;A787; A787;LATIN SMALL LETTER INSULAR T;Ll;0;L;;;;;N;;;A786;;A786 A788;MODIFIER LETTER LOW CIRCUMFLEX ACCENT;Lm;0;ON;;;;;N;;;;; A789;MODIFIER LETTER COLON;Sk;0;L;;;;;N;;;;; A78A;MODIFIER LETTER SHORT EQUALS SIGN;Sk;0;L;;;;;N;;;;; A78B;LATIN CAPITAL LETTER SALTILLO;Lu;0;L;;;;;N;;;;A78C; A78C;LATIN SMALL LETTER SALTILLO;Ll;0;L;;;;;N;;;A78B;;A78B A78D;LATIN CAPITAL LETTER TURNED H;Lu;0;L;;;;;N;;;;0265; A78E;LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT;Ll;0;L;;;;;N;;;;; A78F;LATIN LETTER SINOLOGICAL DOT;Lo;0;L;;;;;N;;;;; A790;LATIN CAPITAL LETTER N WITH DESCENDER;Lu;0;L;;;;;N;;;;A791; A791;LATIN SMALL LETTER N WITH DESCENDER;Ll;0;L;;;;;N;;;A790;;A790 A792;LATIN CAPITAL LETTER C WITH BAR;Lu;0;L;;;;;N;;;;A793; A793;LATIN SMALL LETTER C WITH BAR;Ll;0;L;;;;;N;;;A792;;A792 A794;LATIN SMALL LETTER C WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;; A795;LATIN SMALL LETTER H WITH PALATAL HOOK;Ll;0;L;;;;;N;;;;; A796;LATIN CAPITAL LETTER B WITH FLOURISH;Lu;0;L;;;;;N;;;;A797; A797;LATIN SMALL LETTER B WITH FLOURISH;Ll;0;L;;;;;N;;;A796;;A796 A798;LATIN CAPITAL LETTER F WITH STROKE;Lu;0;L;;;;;N;;;;A799; A799;LATIN SMALL LETTER F WITH STROKE;Ll;0;L;;;;;N;;;A798;;A798 A79A;LATIN CAPITAL LETTER VOLAPUK AE;Lu;0;L;;;;;N;;;;A79B; A79B;LATIN SMALL LETTER VOLAPUK AE;Ll;0;L;;;;;N;;;A79A;;A79A A79C;LATIN CAPITAL LETTER VOLAPUK OE;Lu;0;L;;;;;N;;;;A79D; A79D;LATIN SMALL LETTER VOLAPUK OE;Ll;0;L;;;;;N;;;A79C;;A79C A79E;LATIN CAPITAL LETTER VOLAPUK UE;Lu;0;L;;;;;N;;;;A79F; A79F;LATIN SMALL LETTER VOLAPUK UE;Ll;0;L;;;;;N;;;A79E;;A79E A7A0;LATIN CAPITAL LETTER G WITH OBLIQUE STROKE;Lu;0;L;;;;;N;;;;A7A1; A7A1;LATIN SMALL LETTER G WITH OBLIQUE STROKE;Ll;0;L;;;;;N;;;A7A0;;A7A0 A7A2;LATIN CAPITAL LETTER K WITH OBLIQUE STROKE;Lu;0;L;;;;;N;;;;A7A3; A7A3;LATIN SMALL LETTER K WITH OBLIQUE STROKE;Ll;0;L;;;;;N;;;A7A2;;A7A2 A7A4;LATIN CAPITAL LETTER N WITH OBLIQUE STROKE;Lu;0;L;;;;;N;;;;A7A5; A7A5;LATIN SMALL LETTER N WITH OBLIQUE STROKE;Ll;0;L;;;;;N;;;A7A4;;A7A4 A7A6;LATIN CAPITAL LETTER R WITH OBLIQUE STROKE;Lu;0;L;;;;;N;;;;A7A7; A7A7;LATIN SMALL LETTER R WITH OBLIQUE STROKE;Ll;0;L;;;;;N;;;A7A6;;A7A6 A7A8;LATIN CAPITAL LETTER S WITH OBLIQUE STROKE;Lu;0;L;;;;;N;;;;A7A9; A7A9;LATIN SMALL LETTER S WITH OBLIQUE STROKE;Ll;0;L;;;;;N;;;A7A8;;A7A8 A7AA;LATIN CAPITAL LETTER H WITH HOOK;Lu;0;L;;;;;N;;;;0266; A7AB;LATIN CAPITAL LETTER REVERSED OPEN E;Lu;0;L;;;;;N;;;;025C; A7AC;LATIN CAPITAL LETTER SCRIPT G;Lu;0;L;;;;;N;;;;0261; A7AD;LATIN CAPITAL LETTER L WITH BELT;Lu;0;L;;;;;N;;;;026C; A7AE;LATIN CAPITAL LETTER SMALL CAPITAL I;Lu;0;L;;;;;N;;;;026A; A7B0;LATIN CAPITAL LETTER TURNED K;Lu;0;L;;;;;N;;;;029E; A7B1;LATIN CAPITAL LETTER TURNED T;Lu;0;L;;;;;N;;;;0287; A7B2;LATIN CAPITAL LETTER J WITH CROSSED-TAIL;Lu;0;L;;;;;N;;;;029D; A7B3;LATIN CAPITAL LETTER CHI;Lu;0;L;;;;;N;;;;AB53; A7B4;LATIN CAPITAL LETTER BETA;Lu;0;L;;;;;N;;;;A7B5; A7B5;LATIN SMALL LETTER BETA;Ll;0;L;;;;;N;;;A7B4;;A7B4 A7B6;LATIN CAPITAL LETTER OMEGA;Lu;0;L;;;;;N;;;;A7B7; A7B7;LATIN SMALL LETTER OMEGA;Ll;0;L;;;;;N;;;A7B6;;A7B6 A7F7;LATIN EPIGRAPHIC LETTER SIDEWAYS I;Lo;0;L;;;;;N;;;;; A7F8;MODIFIER LETTER CAPITAL H WITH STROKE;Lm;0;L; 0126;;;;N;;;;; A7F9;MODIFIER LETTER SMALL LIGATURE OE;Lm;0;L; 0153;;;;N;;;;; A7FA;LATIN LETTER SMALL CAPITAL TURNED M;Ll;0;L;;;;;N;;;;; A7FB;LATIN EPIGRAPHIC LETTER REVERSED F;Lo;0;L;;;;;N;;;;; A7FC;LATIN EPIGRAPHIC LETTER REVERSED P;Lo;0;L;;;;;N;;;;; A7FD;LATIN EPIGRAPHIC LETTER INVERTED M;Lo;0;L;;;;;N;;;;; A7FE;LATIN EPIGRAPHIC LETTER I LONGA;Lo;0;L;;;;;N;;;;; A7FF;LATIN EPIGRAPHIC LETTER ARCHAIC M;Lo;0;L;;;;;N;;;;; A800;SYLOTI NAGRI LETTER A;Lo;0;L;;;;;N;;;;; A801;SYLOTI NAGRI LETTER I;Lo;0;L;;;;;N;;;;; A802;SYLOTI NAGRI SIGN DVISVARA;Mn;0;NSM;;;;;N;;;;; A803;SYLOTI NAGRI LETTER U;Lo;0;L;;;;;N;;;;; A804;SYLOTI NAGRI LETTER E;Lo;0;L;;;;;N;;;;; A805;SYLOTI NAGRI LETTER O;Lo;0;L;;;;;N;;;;; A806;SYLOTI NAGRI SIGN HASANTA;Mn;9;NSM;;;;;N;;;;; A807;SYLOTI NAGRI LETTER KO;Lo;0;L;;;;;N;;;;; A808;SYLOTI NAGRI LETTER KHO;Lo;0;L;;;;;N;;;;; A809;SYLOTI NAGRI LETTER GO;Lo;0;L;;;;;N;;;;; A80A;SYLOTI NAGRI LETTER GHO;Lo;0;L;;;;;N;;;;; A80B;SYLOTI NAGRI SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; A80C;SYLOTI NAGRI LETTER CO;Lo;0;L;;;;;N;;;;; A80D;SYLOTI NAGRI LETTER CHO;Lo;0;L;;;;;N;;;;; A80E;SYLOTI NAGRI LETTER JO;Lo;0;L;;;;;N;;;;; A80F;SYLOTI NAGRI LETTER JHO;Lo;0;L;;;;;N;;;;; A810;SYLOTI NAGRI LETTER TTO;Lo;0;L;;;;;N;;;;; A811;SYLOTI NAGRI LETTER TTHO;Lo;0;L;;;;;N;;;;; A812;SYLOTI NAGRI LETTER DDO;Lo;0;L;;;;;N;;;;; A813;SYLOTI NAGRI LETTER DDHO;Lo;0;L;;;;;N;;;;; A814;SYLOTI NAGRI LETTER TO;Lo;0;L;;;;;N;;;;; A815;SYLOTI NAGRI LETTER THO;Lo;0;L;;;;;N;;;;; A816;SYLOTI NAGRI LETTER DO;Lo;0;L;;;;;N;;;;; A817;SYLOTI NAGRI LETTER DHO;Lo;0;L;;;;;N;;;;; A818;SYLOTI NAGRI LETTER NO;Lo;0;L;;;;;N;;;;; A819;SYLOTI NAGRI LETTER PO;Lo;0;L;;;;;N;;;;; A81A;SYLOTI NAGRI LETTER PHO;Lo;0;L;;;;;N;;;;; A81B;SYLOTI NAGRI LETTER BO;Lo;0;L;;;;;N;;;;; A81C;SYLOTI NAGRI LETTER BHO;Lo;0;L;;;;;N;;;;; A81D;SYLOTI NAGRI LETTER MO;Lo;0;L;;;;;N;;;;; A81E;SYLOTI NAGRI LETTER RO;Lo;0;L;;;;;N;;;;; A81F;SYLOTI NAGRI LETTER LO;Lo;0;L;;;;;N;;;;; A820;SYLOTI NAGRI LETTER RRO;Lo;0;L;;;;;N;;;;; A821;SYLOTI NAGRI LETTER SO;Lo;0;L;;;;;N;;;;; A822;SYLOTI NAGRI LETTER HO;Lo;0;L;;;;;N;;;;; A823;SYLOTI NAGRI VOWEL SIGN A;Mc;0;L;;;;;N;;;;; A824;SYLOTI NAGRI VOWEL SIGN I;Mc;0;L;;;;;N;;;;; A825;SYLOTI NAGRI VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; A826;SYLOTI NAGRI VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;; A827;SYLOTI NAGRI VOWEL SIGN OO;Mc;0;L;;;;;N;;;;; A828;SYLOTI NAGRI POETRY MARK-1;So;0;ON;;;;;N;;;;; A829;SYLOTI NAGRI POETRY MARK-2;So;0;ON;;;;;N;;;;; A82A;SYLOTI NAGRI POETRY MARK-3;So;0;ON;;;;;N;;;;; A82B;SYLOTI NAGRI POETRY MARK-4;So;0;ON;;;;;N;;;;; A830;NORTH INDIC FRACTION ONE QUARTER;No;0;L;;;;1/4;N;;;;; A831;NORTH INDIC FRACTION ONE HALF;No;0;L;;;;1/2;N;;;;; A832;NORTH INDIC FRACTION THREE QUARTERS;No;0;L;;;;3/4;N;;;;; A833;NORTH INDIC FRACTION ONE SIXTEENTH;No;0;L;;;;1/16;N;;;;; A834;NORTH INDIC FRACTION ONE EIGHTH;No;0;L;;;;1/8;N;;;;; A835;NORTH INDIC FRACTION THREE SIXTEENTHS;No;0;L;;;;3/16;N;;;;; A836;NORTH INDIC QUARTER MARK;So;0;L;;;;;N;;;;; A837;NORTH INDIC PLACEHOLDER MARK;So;0;L;;;;;N;;;;; A838;NORTH INDIC RUPEE MARK;Sc;0;ET;;;;;N;;;;; A839;NORTH INDIC QUANTITY MARK;So;0;ET;;;;;N;;;;; A840;PHAGS-PA LETTER KA;Lo;0;L;;;;;N;;;;; A841;PHAGS-PA LETTER KHA;Lo;0;L;;;;;N;;;;; A842;PHAGS-PA LETTER GA;Lo;0;L;;;;;N;;;;; A843;PHAGS-PA LETTER NGA;Lo;0;L;;;;;N;;;;; A844;PHAGS-PA LETTER CA;Lo;0;L;;;;;N;;;;; A845;PHAGS-PA LETTER CHA;Lo;0;L;;;;;N;;;;; A846;PHAGS-PA LETTER JA;Lo;0;L;;;;;N;;;;; A847;PHAGS-PA LETTER NYA;Lo;0;L;;;;;N;;;;; A848;PHAGS-PA LETTER TA;Lo;0;L;;;;;N;;;;; A849;PHAGS-PA LETTER THA;Lo;0;L;;;;;N;;;;; A84A;PHAGS-PA LETTER DA;Lo;0;L;;;;;N;;;;; A84B;PHAGS-PA LETTER NA;Lo;0;L;;;;;N;;;;; A84C;PHAGS-PA LETTER PA;Lo;0;L;;;;;N;;;;; A84D;PHAGS-PA LETTER PHA;Lo;0;L;;;;;N;;;;; A84E;PHAGS-PA LETTER BA;Lo;0;L;;;;;N;;;;; A84F;PHAGS-PA LETTER MA;Lo;0;L;;;;;N;;;;; A850;PHAGS-PA LETTER TSA;Lo;0;L;;;;;N;;;;; A851;PHAGS-PA LETTER TSHA;Lo;0;L;;;;;N;;;;; A852;PHAGS-PA LETTER DZA;Lo;0;L;;;;;N;;;;; A853;PHAGS-PA LETTER WA;Lo;0;L;;;;;N;;;;; A854;PHAGS-PA LETTER ZHA;Lo;0;L;;;;;N;;;;; A855;PHAGS-PA LETTER ZA;Lo;0;L;;;;;N;;;;; A856;PHAGS-PA LETTER SMALL A;Lo;0;L;;;;;N;;;;; A857;PHAGS-PA LETTER YA;Lo;0;L;;;;;N;;;;; A858;PHAGS-PA LETTER RA;Lo;0;L;;;;;N;;;;; A859;PHAGS-PA LETTER LA;Lo;0;L;;;;;N;;;;; A85A;PHAGS-PA LETTER SHA;Lo;0;L;;;;;N;;;;; A85B;PHAGS-PA LETTER SA;Lo;0;L;;;;;N;;;;; A85C;PHAGS-PA LETTER HA;Lo;0;L;;;;;N;;;;; A85D;PHAGS-PA LETTER A;Lo;0;L;;;;;N;;;;; A85E;PHAGS-PA LETTER I;Lo;0;L;;;;;N;;;;; A85F;PHAGS-PA LETTER U;Lo;0;L;;;;;N;;;;; A860;PHAGS-PA LETTER E;Lo;0;L;;;;;N;;;;; A861;PHAGS-PA LETTER O;Lo;0;L;;;;;N;;;;; A862;PHAGS-PA LETTER QA;Lo;0;L;;;;;N;;;;; A863;PHAGS-PA LETTER XA;Lo;0;L;;;;;N;;;;; A864;PHAGS-PA LETTER FA;Lo;0;L;;;;;N;;;;; A865;PHAGS-PA LETTER GGA;Lo;0;L;;;;;N;;;;; A866;PHAGS-PA LETTER EE;Lo;0;L;;;;;N;;;;; A867;PHAGS-PA SUBJOINED LETTER WA;Lo;0;L;;;;;N;;;;; A868;PHAGS-PA SUBJOINED LETTER YA;Lo;0;L;;;;;N;;;;; A869;PHAGS-PA LETTER TTA;Lo;0;L;;;;;N;;;;; A86A;PHAGS-PA LETTER TTHA;Lo;0;L;;;;;N;;;;; A86B;PHAGS-PA LETTER DDA;Lo;0;L;;;;;N;;;;; A86C;PHAGS-PA LETTER NNA;Lo;0;L;;;;;N;;;;; A86D;PHAGS-PA LETTER ALTERNATE YA;Lo;0;L;;;;;N;;;;; A86E;PHAGS-PA LETTER VOICELESS SHA;Lo;0;L;;;;;N;;;;; A86F;PHAGS-PA LETTER VOICED HA;Lo;0;L;;;;;N;;;;; A870;PHAGS-PA LETTER ASPIRATED FA;Lo;0;L;;;;;N;;;;; A871;PHAGS-PA SUBJOINED LETTER RA;Lo;0;L;;;;;N;;;;; A872;PHAGS-PA SUPERFIXED LETTER RA;Lo;0;L;;;;;N;;;;; A873;PHAGS-PA LETTER CANDRABINDU;Lo;0;L;;;;;N;;;;; A874;PHAGS-PA SINGLE HEAD MARK;Po;0;ON;;;;;N;;;;; A875;PHAGS-PA DOUBLE HEAD MARK;Po;0;ON;;;;;N;;;;; A876;PHAGS-PA MARK SHAD;Po;0;ON;;;;;N;;;;; A877;PHAGS-PA MARK DOUBLE SHAD;Po;0;ON;;;;;N;;;;; A880;SAURASHTRA SIGN ANUSVARA;Mc;0;L;;;;;N;;;;; A881;SAURASHTRA SIGN VISARGA;Mc;0;L;;;;;N;;;;; A882;SAURASHTRA LETTER A;Lo;0;L;;;;;N;;;;; A883;SAURASHTRA LETTER AA;Lo;0;L;;;;;N;;;;; A884;SAURASHTRA LETTER I;Lo;0;L;;;;;N;;;;; A885;SAURASHTRA LETTER II;Lo;0;L;;;;;N;;;;; A886;SAURASHTRA LETTER U;Lo;0;L;;;;;N;;;;; A887;SAURASHTRA LETTER UU;Lo;0;L;;;;;N;;;;; A888;SAURASHTRA LETTER VOCALIC R;Lo;0;L;;;;;N;;;;; A889;SAURASHTRA LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; A88A;SAURASHTRA LETTER VOCALIC L;Lo;0;L;;;;;N;;;;; A88B;SAURASHTRA LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; A88C;SAURASHTRA LETTER E;Lo;0;L;;;;;N;;;;; A88D;SAURASHTRA LETTER EE;Lo;0;L;;;;;N;;;;; A88E;SAURASHTRA LETTER AI;Lo;0;L;;;;;N;;;;; A88F;SAURASHTRA LETTER O;Lo;0;L;;;;;N;;;;; A890;SAURASHTRA LETTER OO;Lo;0;L;;;;;N;;;;; A891;SAURASHTRA LETTER AU;Lo;0;L;;;;;N;;;;; A892;SAURASHTRA LETTER KA;Lo;0;L;;;;;N;;;;; A893;SAURASHTRA LETTER KHA;Lo;0;L;;;;;N;;;;; A894;SAURASHTRA LETTER GA;Lo;0;L;;;;;N;;;;; A895;SAURASHTRA LETTER GHA;Lo;0;L;;;;;N;;;;; A896;SAURASHTRA LETTER NGA;Lo;0;L;;;;;N;;;;; A897;SAURASHTRA LETTER CA;Lo;0;L;;;;;N;;;;; A898;SAURASHTRA LETTER CHA;Lo;0;L;;;;;N;;;;; A899;SAURASHTRA LETTER JA;Lo;0;L;;;;;N;;;;; A89A;SAURASHTRA LETTER JHA;Lo;0;L;;;;;N;;;;; A89B;SAURASHTRA LETTER NYA;Lo;0;L;;;;;N;;;;; A89C;SAURASHTRA LETTER TTA;Lo;0;L;;;;;N;;;;; A89D;SAURASHTRA LETTER TTHA;Lo;0;L;;;;;N;;;;; A89E;SAURASHTRA LETTER DDA;Lo;0;L;;;;;N;;;;; A89F;SAURASHTRA LETTER DDHA;Lo;0;L;;;;;N;;;;; A8A0;SAURASHTRA LETTER NNA;Lo;0;L;;;;;N;;;;; A8A1;SAURASHTRA LETTER TA;Lo;0;L;;;;;N;;;;; A8A2;SAURASHTRA LETTER THA;Lo;0;L;;;;;N;;;;; A8A3;SAURASHTRA LETTER DA;Lo;0;L;;;;;N;;;;; A8A4;SAURASHTRA LETTER DHA;Lo;0;L;;;;;N;;;;; A8A5;SAURASHTRA LETTER NA;Lo;0;L;;;;;N;;;;; A8A6;SAURASHTRA LETTER PA;Lo;0;L;;;;;N;;;;; A8A7;SAURASHTRA LETTER PHA;Lo;0;L;;;;;N;;;;; A8A8;SAURASHTRA LETTER BA;Lo;0;L;;;;;N;;;;; A8A9;SAURASHTRA LETTER BHA;Lo;0;L;;;;;N;;;;; A8AA;SAURASHTRA LETTER MA;Lo;0;L;;;;;N;;;;; A8AB;SAURASHTRA LETTER YA;Lo;0;L;;;;;N;;;;; A8AC;SAURASHTRA LETTER RA;Lo;0;L;;;;;N;;;;; A8AD;SAURASHTRA LETTER LA;Lo;0;L;;;;;N;;;;; A8AE;SAURASHTRA LETTER VA;Lo;0;L;;;;;N;;;;; A8AF;SAURASHTRA LETTER SHA;Lo;0;L;;;;;N;;;;; A8B0;SAURASHTRA LETTER SSA;Lo;0;L;;;;;N;;;;; A8B1;SAURASHTRA LETTER SA;Lo;0;L;;;;;N;;;;; A8B2;SAURASHTRA LETTER HA;Lo;0;L;;;;;N;;;;; A8B3;SAURASHTRA LETTER LLA;Lo;0;L;;;;;N;;;;; A8B4;SAURASHTRA CONSONANT SIGN HAARU;Mc;0;L;;;;;N;;;;; A8B5;SAURASHTRA VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; A8B6;SAURASHTRA VOWEL SIGN I;Mc;0;L;;;;;N;;;;; A8B7;SAURASHTRA VOWEL SIGN II;Mc;0;L;;;;;N;;;;; A8B8;SAURASHTRA VOWEL SIGN U;Mc;0;L;;;;;N;;;;; A8B9;SAURASHTRA VOWEL SIGN UU;Mc;0;L;;;;;N;;;;; A8BA;SAURASHTRA VOWEL SIGN VOCALIC R;Mc;0;L;;;;;N;;;;; A8BB;SAURASHTRA VOWEL SIGN VOCALIC RR;Mc;0;L;;;;;N;;;;; A8BC;SAURASHTRA VOWEL SIGN VOCALIC L;Mc;0;L;;;;;N;;;;; A8BD;SAURASHTRA VOWEL SIGN VOCALIC LL;Mc;0;L;;;;;N;;;;; A8BE;SAURASHTRA VOWEL SIGN E;Mc;0;L;;;;;N;;;;; A8BF;SAURASHTRA VOWEL SIGN EE;Mc;0;L;;;;;N;;;;; A8C0;SAURASHTRA VOWEL SIGN AI;Mc;0;L;;;;;N;;;;; A8C1;SAURASHTRA VOWEL SIGN O;Mc;0;L;;;;;N;;;;; A8C2;SAURASHTRA VOWEL SIGN OO;Mc;0;L;;;;;N;;;;; A8C3;SAURASHTRA VOWEL SIGN AU;Mc;0;L;;;;;N;;;;; A8C4;SAURASHTRA SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; A8C5;SAURASHTRA SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; A8CE;SAURASHTRA DANDA;Po;0;L;;;;;N;;;;; A8CF;SAURASHTRA DOUBLE DANDA;Po;0;L;;;;;N;;;;; A8D0;SAURASHTRA DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; A8D1;SAURASHTRA DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; A8D2;SAURASHTRA DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; A8D3;SAURASHTRA DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; A8D4;SAURASHTRA DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; A8D5;SAURASHTRA DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; A8D6;SAURASHTRA DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; A8D7;SAURASHTRA DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; A8D8;SAURASHTRA DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; A8D9;SAURASHTRA DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; A8E0;COMBINING DEVANAGARI DIGIT ZERO;Mn;230;NSM;;;;;N;;;;; A8E1;COMBINING DEVANAGARI DIGIT ONE;Mn;230;NSM;;;;;N;;;;; A8E2;COMBINING DEVANAGARI DIGIT TWO;Mn;230;NSM;;;;;N;;;;; A8E3;COMBINING DEVANAGARI DIGIT THREE;Mn;230;NSM;;;;;N;;;;; A8E4;COMBINING DEVANAGARI DIGIT FOUR;Mn;230;NSM;;;;;N;;;;; A8E5;COMBINING DEVANAGARI DIGIT FIVE;Mn;230;NSM;;;;;N;;;;; A8E6;COMBINING DEVANAGARI DIGIT SIX;Mn;230;NSM;;;;;N;;;;; A8E7;COMBINING DEVANAGARI DIGIT SEVEN;Mn;230;NSM;;;;;N;;;;; A8E8;COMBINING DEVANAGARI DIGIT EIGHT;Mn;230;NSM;;;;;N;;;;; A8E9;COMBINING DEVANAGARI DIGIT NINE;Mn;230;NSM;;;;;N;;;;; A8EA;COMBINING DEVANAGARI LETTER A;Mn;230;NSM;;;;;N;;;;; A8EB;COMBINING DEVANAGARI LETTER U;Mn;230;NSM;;;;;N;;;;; A8EC;COMBINING DEVANAGARI LETTER KA;Mn;230;NSM;;;;;N;;;;; A8ED;COMBINING DEVANAGARI LETTER NA;Mn;230;NSM;;;;;N;;;;; A8EE;COMBINING DEVANAGARI LETTER PA;Mn;230;NSM;;;;;N;;;;; A8EF;COMBINING DEVANAGARI LETTER RA;Mn;230;NSM;;;;;N;;;;; A8F0;COMBINING DEVANAGARI LETTER VI;Mn;230;NSM;;;;;N;;;;; A8F1;COMBINING DEVANAGARI SIGN AVAGRAHA;Mn;230;NSM;;;;;N;;;;; A8F2;DEVANAGARI SIGN SPACING CANDRABINDU;Lo;0;L;;;;;N;;;;; A8F3;DEVANAGARI SIGN CANDRABINDU VIRAMA;Lo;0;L;;;;;N;;;;; A8F4;DEVANAGARI SIGN DOUBLE CANDRABINDU VIRAMA;Lo;0;L;;;;;N;;;;; A8F5;DEVANAGARI SIGN CANDRABINDU TWO;Lo;0;L;;;;;N;;;;; A8F6;DEVANAGARI SIGN CANDRABINDU THREE;Lo;0;L;;;;;N;;;;; A8F7;DEVANAGARI SIGN CANDRABINDU AVAGRAHA;Lo;0;L;;;;;N;;;;; A8F8;DEVANAGARI SIGN PUSHPIKA;Po;0;L;;;;;N;;;;; A8F9;DEVANAGARI GAP FILLER;Po;0;L;;;;;N;;;;; A8FA;DEVANAGARI CARET;Po;0;L;;;;;N;;;;; A8FB;DEVANAGARI HEADSTROKE;Lo;0;L;;;;;N;;;;; A8FC;DEVANAGARI SIGN SIDDHAM;Po;0;L;;;;;N;;;;; A8FD;DEVANAGARI JAIN OM;Lo;0;L;;;;;N;;;;; A900;KAYAH LI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; A901;KAYAH LI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; A902;KAYAH LI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; A903;KAYAH LI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; A904;KAYAH LI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; A905;KAYAH LI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; A906;KAYAH LI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; A907;KAYAH LI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; A908;KAYAH LI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; A909;KAYAH LI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; A90A;KAYAH LI LETTER KA;Lo;0;L;;;;;N;;;;; A90B;KAYAH LI LETTER KHA;Lo;0;L;;;;;N;;;;; A90C;KAYAH LI LETTER GA;Lo;0;L;;;;;N;;;;; A90D;KAYAH LI LETTER NGA;Lo;0;L;;;;;N;;;;; A90E;KAYAH LI LETTER SA;Lo;0;L;;;;;N;;;;; A90F;KAYAH LI LETTER SHA;Lo;0;L;;;;;N;;;;; A910;KAYAH LI LETTER ZA;Lo;0;L;;;;;N;;;;; A911;KAYAH LI LETTER NYA;Lo;0;L;;;;;N;;;;; A912;KAYAH LI LETTER TA;Lo;0;L;;;;;N;;;;; A913;KAYAH LI LETTER HTA;Lo;0;L;;;;;N;;;;; A914;KAYAH LI LETTER NA;Lo;0;L;;;;;N;;;;; A915;KAYAH LI LETTER PA;Lo;0;L;;;;;N;;;;; A916;KAYAH LI LETTER PHA;Lo;0;L;;;;;N;;;;; A917;KAYAH LI LETTER MA;Lo;0;L;;;;;N;;;;; A918;KAYAH LI LETTER DA;Lo;0;L;;;;;N;;;;; A919;KAYAH LI LETTER BA;Lo;0;L;;;;;N;;;;; A91A;KAYAH LI LETTER RA;Lo;0;L;;;;;N;;;;; A91B;KAYAH LI LETTER YA;Lo;0;L;;;;;N;;;;; A91C;KAYAH LI LETTER LA;Lo;0;L;;;;;N;;;;; A91D;KAYAH LI LETTER WA;Lo;0;L;;;;;N;;;;; A91E;KAYAH LI LETTER THA;Lo;0;L;;;;;N;;;;; A91F;KAYAH LI LETTER HA;Lo;0;L;;;;;N;;;;; A920;KAYAH LI LETTER VA;Lo;0;L;;;;;N;;;;; A921;KAYAH LI LETTER CA;Lo;0;L;;;;;N;;;;; A922;KAYAH LI LETTER A;Lo;0;L;;;;;N;;;;; A923;KAYAH LI LETTER OE;Lo;0;L;;;;;N;;;;; A924;KAYAH LI LETTER I;Lo;0;L;;;;;N;;;;; A925;KAYAH LI LETTER OO;Lo;0;L;;;;;N;;;;; A926;KAYAH LI VOWEL UE;Mn;0;NSM;;;;;N;;;;; A927;KAYAH LI VOWEL E;Mn;0;NSM;;;;;N;;;;; A928;KAYAH LI VOWEL U;Mn;0;NSM;;;;;N;;;;; A929;KAYAH LI VOWEL EE;Mn;0;NSM;;;;;N;;;;; A92A;KAYAH LI VOWEL O;Mn;0;NSM;;;;;N;;;;; A92B;KAYAH LI TONE PLOPHU;Mn;220;NSM;;;;;N;;;;; A92C;KAYAH LI TONE CALYA;Mn;220;NSM;;;;;N;;;;; A92D;KAYAH LI TONE CALYA PLOPHU;Mn;220;NSM;;;;;N;;;;; A92E;KAYAH LI SIGN CWI;Po;0;L;;;;;N;;;;; A92F;KAYAH LI SIGN SHYA;Po;0;L;;;;;N;;;;; A930;REJANG LETTER KA;Lo;0;L;;;;;N;;;;; A931;REJANG LETTER GA;Lo;0;L;;;;;N;;;;; A932;REJANG LETTER NGA;Lo;0;L;;;;;N;;;;; A933;REJANG LETTER TA;Lo;0;L;;;;;N;;;;; A934;REJANG LETTER DA;Lo;0;L;;;;;N;;;;; A935;REJANG LETTER NA;Lo;0;L;;;;;N;;;;; A936;REJANG LETTER PA;Lo;0;L;;;;;N;;;;; A937;REJANG LETTER BA;Lo;0;L;;;;;N;;;;; A938;REJANG LETTER MA;Lo;0;L;;;;;N;;;;; A939;REJANG LETTER CA;Lo;0;L;;;;;N;;;;; A93A;REJANG LETTER JA;Lo;0;L;;;;;N;;;;; A93B;REJANG LETTER NYA;Lo;0;L;;;;;N;;;;; A93C;REJANG LETTER SA;Lo;0;L;;;;;N;;;;; A93D;REJANG LETTER RA;Lo;0;L;;;;;N;;;;; A93E;REJANG LETTER LA;Lo;0;L;;;;;N;;;;; A93F;REJANG LETTER YA;Lo;0;L;;;;;N;;;;; A940;REJANG LETTER WA;Lo;0;L;;;;;N;;;;; A941;REJANG LETTER HA;Lo;0;L;;;;;N;;;;; A942;REJANG LETTER MBA;Lo;0;L;;;;;N;;;;; A943;REJANG LETTER NGGA;Lo;0;L;;;;;N;;;;; A944;REJANG LETTER NDA;Lo;0;L;;;;;N;;;;; A945;REJANG LETTER NYJA;Lo;0;L;;;;;N;;;;; A946;REJANG LETTER A;Lo;0;L;;;;;N;;;;; A947;REJANG VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; A948;REJANG VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; A949;REJANG VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;; A94A;REJANG VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;; A94B;REJANG VOWEL SIGN O;Mn;0;NSM;;;;;N;;;;; A94C;REJANG VOWEL SIGN AU;Mn;0;NSM;;;;;N;;;;; A94D;REJANG VOWEL SIGN EU;Mn;0;NSM;;;;;N;;;;; A94E;REJANG VOWEL SIGN EA;Mn;0;NSM;;;;;N;;;;; A94F;REJANG CONSONANT SIGN NG;Mn;0;NSM;;;;;N;;;;; A950;REJANG CONSONANT SIGN N;Mn;0;NSM;;;;;N;;;;; A951;REJANG CONSONANT SIGN R;Mn;0;NSM;;;;;N;;;;; A952;REJANG CONSONANT SIGN H;Mc;0;L;;;;;N;;;;; A953;REJANG VIRAMA;Mc;9;L;;;;;N;;;;; A95F;REJANG SECTION MARK;Po;0;L;;;;;N;;;;; A960;HANGUL CHOSEONG TIKEUT-MIEUM;Lo;0;L;;;;;N;;;;; A961;HANGUL CHOSEONG TIKEUT-PIEUP;Lo;0;L;;;;;N;;;;; A962;HANGUL CHOSEONG TIKEUT-SIOS;Lo;0;L;;;;;N;;;;; A963;HANGUL CHOSEONG TIKEUT-CIEUC;Lo;0;L;;;;;N;;;;; A964;HANGUL CHOSEONG RIEUL-KIYEOK;Lo;0;L;;;;;N;;;;; A965;HANGUL CHOSEONG RIEUL-SSANGKIYEOK;Lo;0;L;;;;;N;;;;; A966;HANGUL CHOSEONG RIEUL-TIKEUT;Lo;0;L;;;;;N;;;;; A967;HANGUL CHOSEONG RIEUL-SSANGTIKEUT;Lo;0;L;;;;;N;;;;; A968;HANGUL CHOSEONG RIEUL-MIEUM;Lo;0;L;;;;;N;;;;; A969;HANGUL CHOSEONG RIEUL-PIEUP;Lo;0;L;;;;;N;;;;; A96A;HANGUL CHOSEONG RIEUL-SSANGPIEUP;Lo;0;L;;;;;N;;;;; A96B;HANGUL CHOSEONG RIEUL-KAPYEOUNPIEUP;Lo;0;L;;;;;N;;;;; A96C;HANGUL CHOSEONG RIEUL-SIOS;Lo;0;L;;;;;N;;;;; A96D;HANGUL CHOSEONG RIEUL-CIEUC;Lo;0;L;;;;;N;;;;; A96E;HANGUL CHOSEONG RIEUL-KHIEUKH;Lo;0;L;;;;;N;;;;; A96F;HANGUL CHOSEONG MIEUM-KIYEOK;Lo;0;L;;;;;N;;;;; A970;HANGUL CHOSEONG MIEUM-TIKEUT;Lo;0;L;;;;;N;;;;; A971;HANGUL CHOSEONG MIEUM-SIOS;Lo;0;L;;;;;N;;;;; A972;HANGUL CHOSEONG PIEUP-SIOS-THIEUTH;Lo;0;L;;;;;N;;;;; A973;HANGUL CHOSEONG PIEUP-KHIEUKH;Lo;0;L;;;;;N;;;;; A974;HANGUL CHOSEONG PIEUP-HIEUH;Lo;0;L;;;;;N;;;;; A975;HANGUL CHOSEONG SSANGSIOS-PIEUP;Lo;0;L;;;;;N;;;;; A976;HANGUL CHOSEONG IEUNG-RIEUL;Lo;0;L;;;;;N;;;;; A977;HANGUL CHOSEONG IEUNG-HIEUH;Lo;0;L;;;;;N;;;;; A978;HANGUL CHOSEONG SSANGCIEUC-HIEUH;Lo;0;L;;;;;N;;;;; A979;HANGUL CHOSEONG SSANGTHIEUTH;Lo;0;L;;;;;N;;;;; A97A;HANGUL CHOSEONG PHIEUPH-HIEUH;Lo;0;L;;;;;N;;;;; A97B;HANGUL CHOSEONG HIEUH-SIOS;Lo;0;L;;;;;N;;;;; A97C;HANGUL CHOSEONG SSANGYEORINHIEUH;Lo;0;L;;;;;N;;;;; A980;JAVANESE SIGN PANYANGGA;Mn;0;NSM;;;;;N;;;;; A981;JAVANESE SIGN CECAK;Mn;0;NSM;;;;;N;;;;; A982;JAVANESE SIGN LAYAR;Mn;0;NSM;;;;;N;;;;; A983;JAVANESE SIGN WIGNYAN;Mc;0;L;;;;;N;;;;; A984;JAVANESE LETTER A;Lo;0;L;;;;;N;;;;; A985;JAVANESE LETTER I KAWI;Lo;0;L;;;;;N;;;;; A986;JAVANESE LETTER I;Lo;0;L;;;;;N;;;;; A987;JAVANESE LETTER II;Lo;0;L;;;;;N;;;;; A988;JAVANESE LETTER U;Lo;0;L;;;;;N;;;;; A989;JAVANESE LETTER PA CEREK;Lo;0;L;;;;;N;;;;; A98A;JAVANESE LETTER NGA LELET;Lo;0;L;;;;;N;;;;; A98B;JAVANESE LETTER NGA LELET RASWADI;Lo;0;L;;;;;N;;;;; A98C;JAVANESE LETTER E;Lo;0;L;;;;;N;;;;; A98D;JAVANESE LETTER AI;Lo;0;L;;;;;N;;;;; A98E;JAVANESE LETTER O;Lo;0;L;;;;;N;;;;; A98F;JAVANESE LETTER KA;Lo;0;L;;;;;N;;;;; A990;JAVANESE LETTER KA SASAK;Lo;0;L;;;;;N;;;;; A991;JAVANESE LETTER KA MURDA;Lo;0;L;;;;;N;;;;; A992;JAVANESE LETTER GA;Lo;0;L;;;;;N;;;;; A993;JAVANESE LETTER GA MURDA;Lo;0;L;;;;;N;;;;; A994;JAVANESE LETTER NGA;Lo;0;L;;;;;N;;;;; A995;JAVANESE LETTER CA;Lo;0;L;;;;;N;;;;; A996;JAVANESE LETTER CA MURDA;Lo;0;L;;;;;N;;;;; A997;JAVANESE LETTER JA;Lo;0;L;;;;;N;;;;; A998;JAVANESE LETTER NYA MURDA;Lo;0;L;;;;;N;;;;; A999;JAVANESE LETTER JA MAHAPRANA;Lo;0;L;;;;;N;;;;; A99A;JAVANESE LETTER NYA;Lo;0;L;;;;;N;;;;; A99B;JAVANESE LETTER TTA;Lo;0;L;;;;;N;;;;; A99C;JAVANESE LETTER TTA MAHAPRANA;Lo;0;L;;;;;N;;;;; A99D;JAVANESE LETTER DDA;Lo;0;L;;;;;N;;;;; A99E;JAVANESE LETTER DDA MAHAPRANA;Lo;0;L;;;;;N;;;;; A99F;JAVANESE LETTER NA MURDA;Lo;0;L;;;;;N;;;;; A9A0;JAVANESE LETTER TA;Lo;0;L;;;;;N;;;;; A9A1;JAVANESE LETTER TA MURDA;Lo;0;L;;;;;N;;;;; A9A2;JAVANESE LETTER DA;Lo;0;L;;;;;N;;;;; A9A3;JAVANESE LETTER DA MAHAPRANA;Lo;0;L;;;;;N;;;;; A9A4;JAVANESE LETTER NA;Lo;0;L;;;;;N;;;;; A9A5;JAVANESE LETTER PA;Lo;0;L;;;;;N;;;;; A9A6;JAVANESE LETTER PA MURDA;Lo;0;L;;;;;N;;;;; A9A7;JAVANESE LETTER BA;Lo;0;L;;;;;N;;;;; A9A8;JAVANESE LETTER BA MURDA;Lo;0;L;;;;;N;;;;; A9A9;JAVANESE LETTER MA;Lo;0;L;;;;;N;;;;; A9AA;JAVANESE LETTER YA;Lo;0;L;;;;;N;;;;; A9AB;JAVANESE LETTER RA;Lo;0;L;;;;;N;;;;; A9AC;JAVANESE LETTER RA AGUNG;Lo;0;L;;;;;N;;;;; A9AD;JAVANESE LETTER LA;Lo;0;L;;;;;N;;;;; A9AE;JAVANESE LETTER WA;Lo;0;L;;;;;N;;;;; A9AF;JAVANESE LETTER SA MURDA;Lo;0;L;;;;;N;;;;; A9B0;JAVANESE LETTER SA MAHAPRANA;Lo;0;L;;;;;N;;;;; A9B1;JAVANESE LETTER SA;Lo;0;L;;;;;N;;;;; A9B2;JAVANESE LETTER HA;Lo;0;L;;;;;N;;;;; A9B3;JAVANESE SIGN CECAK TELU;Mn;7;NSM;;;;;N;;;;; A9B4;JAVANESE VOWEL SIGN TARUNG;Mc;0;L;;;;;N;;;;; A9B5;JAVANESE VOWEL SIGN TOLONG;Mc;0;L;;;;;N;;;;; A9B6;JAVANESE VOWEL SIGN WULU;Mn;0;NSM;;;;;N;;;;; A9B7;JAVANESE VOWEL SIGN WULU MELIK;Mn;0;NSM;;;;;N;;;;; A9B8;JAVANESE VOWEL SIGN SUKU;Mn;0;NSM;;;;;N;;;;; A9B9;JAVANESE VOWEL SIGN SUKU MENDUT;Mn;0;NSM;;;;;N;;;;; A9BA;JAVANESE VOWEL SIGN TALING;Mc;0;L;;;;;N;;;;; A9BB;JAVANESE VOWEL SIGN DIRGA MURE;Mc;0;L;;;;;N;;;;; A9BC;JAVANESE VOWEL SIGN PEPET;Mn;0;NSM;;;;;N;;;;; A9BD;JAVANESE CONSONANT SIGN KERET;Mc;0;L;;;;;N;;;;; A9BE;JAVANESE CONSONANT SIGN PENGKAL;Mc;0;L;;;;;N;;;;; A9BF;JAVANESE CONSONANT SIGN CAKRA;Mc;0;L;;;;;N;;;;; A9C0;JAVANESE PANGKON;Mc;9;L;;;;;N;;;;; A9C1;JAVANESE LEFT RERENGGAN;Po;0;L;;;;;N;;;;; A9C2;JAVANESE RIGHT RERENGGAN;Po;0;L;;;;;N;;;;; A9C3;JAVANESE PADA ANDAP;Po;0;L;;;;;N;;;;; A9C4;JAVANESE PADA MADYA;Po;0;L;;;;;N;;;;; A9C5;JAVANESE PADA LUHUR;Po;0;L;;;;;N;;;;; A9C6;JAVANESE PADA WINDU;Po;0;L;;;;;N;;;;; A9C7;JAVANESE PADA PANGKAT;Po;0;L;;;;;N;;;;; A9C8;JAVANESE PADA LINGSA;Po;0;L;;;;;N;;;;; A9C9;JAVANESE PADA LUNGSI;Po;0;L;;;;;N;;;;; A9CA;JAVANESE PADA ADEG;Po;0;L;;;;;N;;;;; A9CB;JAVANESE PADA ADEG ADEG;Po;0;L;;;;;N;;;;; A9CC;JAVANESE PADA PISELEH;Po;0;L;;;;;N;;;;; A9CD;JAVANESE TURNED PADA PISELEH;Po;0;L;;;;;N;;;;; A9CF;JAVANESE PANGRANGKEP;Lm;0;L;;;;;N;;;;; A9D0;JAVANESE DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; A9D1;JAVANESE DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; A9D2;JAVANESE DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; A9D3;JAVANESE DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; A9D4;JAVANESE DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; A9D5;JAVANESE DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; A9D6;JAVANESE DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; A9D7;JAVANESE DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; A9D8;JAVANESE DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; A9D9;JAVANESE DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; A9DE;JAVANESE PADA TIRTA TUMETES;Po;0;L;;;;;N;;;;; A9DF;JAVANESE PADA ISEN-ISEN;Po;0;L;;;;;N;;;;; A9E0;MYANMAR LETTER SHAN GHA;Lo;0;L;;;;;N;;;;; A9E1;MYANMAR LETTER SHAN CHA;Lo;0;L;;;;;N;;;;; A9E2;MYANMAR LETTER SHAN JHA;Lo;0;L;;;;;N;;;;; A9E3;MYANMAR LETTER SHAN NNA;Lo;0;L;;;;;N;;;;; A9E4;MYANMAR LETTER SHAN BHA;Lo;0;L;;;;;N;;;;; A9E5;MYANMAR SIGN SHAN SAW;Mn;0;NSM;;;;;N;;;;; A9E6;MYANMAR MODIFIER LETTER SHAN REDUPLICATION;Lm;0;L;;;;;N;;;;; A9E7;MYANMAR LETTER TAI LAING NYA;Lo;0;L;;;;;N;;;;; A9E8;MYANMAR LETTER TAI LAING FA;Lo;0;L;;;;;N;;;;; A9E9;MYANMAR LETTER TAI LAING GA;Lo;0;L;;;;;N;;;;; A9EA;MYANMAR LETTER TAI LAING GHA;Lo;0;L;;;;;N;;;;; A9EB;MYANMAR LETTER TAI LAING JA;Lo;0;L;;;;;N;;;;; A9EC;MYANMAR LETTER TAI LAING JHA;Lo;0;L;;;;;N;;;;; A9ED;MYANMAR LETTER TAI LAING DDA;Lo;0;L;;;;;N;;;;; A9EE;MYANMAR LETTER TAI LAING DDHA;Lo;0;L;;;;;N;;;;; A9EF;MYANMAR LETTER TAI LAING NNA;Lo;0;L;;;;;N;;;;; A9F0;MYANMAR TAI LAING DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; A9F1;MYANMAR TAI LAING DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; A9F2;MYANMAR TAI LAING DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; A9F3;MYANMAR TAI LAING DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; A9F4;MYANMAR TAI LAING DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; A9F5;MYANMAR TAI LAING DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; A9F6;MYANMAR TAI LAING DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; A9F7;MYANMAR TAI LAING DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; A9F8;MYANMAR TAI LAING DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; A9F9;MYANMAR TAI LAING DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; A9FA;MYANMAR LETTER TAI LAING LLA;Lo;0;L;;;;;N;;;;; A9FB;MYANMAR LETTER TAI LAING DA;Lo;0;L;;;;;N;;;;; A9FC;MYANMAR LETTER TAI LAING DHA;Lo;0;L;;;;;N;;;;; A9FD;MYANMAR LETTER TAI LAING BA;Lo;0;L;;;;;N;;;;; A9FE;MYANMAR LETTER TAI LAING BHA;Lo;0;L;;;;;N;;;;; AA00;CHAM LETTER A;Lo;0;L;;;;;N;;;;; AA01;CHAM LETTER I;Lo;0;L;;;;;N;;;;; AA02;CHAM LETTER U;Lo;0;L;;;;;N;;;;; AA03;CHAM LETTER E;Lo;0;L;;;;;N;;;;; AA04;CHAM LETTER AI;Lo;0;L;;;;;N;;;;; AA05;CHAM LETTER O;Lo;0;L;;;;;N;;;;; AA06;CHAM LETTER KA;Lo;0;L;;;;;N;;;;; AA07;CHAM LETTER KHA;Lo;0;L;;;;;N;;;;; AA08;CHAM LETTER GA;Lo;0;L;;;;;N;;;;; AA09;CHAM LETTER GHA;Lo;0;L;;;;;N;;;;; AA0A;CHAM LETTER NGUE;Lo;0;L;;;;;N;;;;; AA0B;CHAM LETTER NGA;Lo;0;L;;;;;N;;;;; AA0C;CHAM LETTER CHA;Lo;0;L;;;;;N;;;;; AA0D;CHAM LETTER CHHA;Lo;0;L;;;;;N;;;;; AA0E;CHAM LETTER JA;Lo;0;L;;;;;N;;;;; AA0F;CHAM LETTER JHA;Lo;0;L;;;;;N;;;;; AA10;CHAM LETTER NHUE;Lo;0;L;;;;;N;;;;; AA11;CHAM LETTER NHA;Lo;0;L;;;;;N;;;;; AA12;CHAM LETTER NHJA;Lo;0;L;;;;;N;;;;; AA13;CHAM LETTER TA;Lo;0;L;;;;;N;;;;; AA14;CHAM LETTER THA;Lo;0;L;;;;;N;;;;; AA15;CHAM LETTER DA;Lo;0;L;;;;;N;;;;; AA16;CHAM LETTER DHA;Lo;0;L;;;;;N;;;;; AA17;CHAM LETTER NUE;Lo;0;L;;;;;N;;;;; AA18;CHAM LETTER NA;Lo;0;L;;;;;N;;;;; AA19;CHAM LETTER DDA;Lo;0;L;;;;;N;;;;; AA1A;CHAM LETTER PA;Lo;0;L;;;;;N;;;;; AA1B;CHAM LETTER PPA;Lo;0;L;;;;;N;;;;; AA1C;CHAM LETTER PHA;Lo;0;L;;;;;N;;;;; AA1D;CHAM LETTER BA;Lo;0;L;;;;;N;;;;; AA1E;CHAM LETTER BHA;Lo;0;L;;;;;N;;;;; AA1F;CHAM LETTER MUE;Lo;0;L;;;;;N;;;;; AA20;CHAM LETTER MA;Lo;0;L;;;;;N;;;;; AA21;CHAM LETTER BBA;Lo;0;L;;;;;N;;;;; AA22;CHAM LETTER YA;Lo;0;L;;;;;N;;;;; AA23;CHAM LETTER RA;Lo;0;L;;;;;N;;;;; AA24;CHAM LETTER LA;Lo;0;L;;;;;N;;;;; AA25;CHAM LETTER VA;Lo;0;L;;;;;N;;;;; AA26;CHAM LETTER SSA;Lo;0;L;;;;;N;;;;; AA27;CHAM LETTER SA;Lo;0;L;;;;;N;;;;; AA28;CHAM LETTER HA;Lo;0;L;;;;;N;;;;; AA29;CHAM VOWEL SIGN AA;Mn;0;NSM;;;;;N;;;;; AA2A;CHAM VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; AA2B;CHAM VOWEL SIGN II;Mn;0;NSM;;;;;N;;;;; AA2C;CHAM VOWEL SIGN EI;Mn;0;NSM;;;;;N;;;;; AA2D;CHAM VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; AA2E;CHAM VOWEL SIGN OE;Mn;0;NSM;;;;;N;;;;; AA2F;CHAM VOWEL SIGN O;Mc;0;L;;;;;N;;;;; AA30;CHAM VOWEL SIGN AI;Mc;0;L;;;;;N;;;;; AA31;CHAM VOWEL SIGN AU;Mn;0;NSM;;;;;N;;;;; AA32;CHAM VOWEL SIGN UE;Mn;0;NSM;;;;;N;;;;; AA33;CHAM CONSONANT SIGN YA;Mc;0;L;;;;;N;;;;; AA34;CHAM CONSONANT SIGN RA;Mc;0;L;;;;;N;;;;; AA35;CHAM CONSONANT SIGN LA;Mn;0;NSM;;;;;N;;;;; AA36;CHAM CONSONANT SIGN WA;Mn;0;NSM;;;;;N;;;;; AA40;CHAM LETTER FINAL K;Lo;0;L;;;;;N;;;;; AA41;CHAM LETTER FINAL G;Lo;0;L;;;;;N;;;;; AA42;CHAM LETTER FINAL NG;Lo;0;L;;;;;N;;;;; AA43;CHAM CONSONANT SIGN FINAL NG;Mn;0;NSM;;;;;N;;;;; AA44;CHAM LETTER FINAL CH;Lo;0;L;;;;;N;;;;; AA45;CHAM LETTER FINAL T;Lo;0;L;;;;;N;;;;; AA46;CHAM LETTER FINAL N;Lo;0;L;;;;;N;;;;; AA47;CHAM LETTER FINAL P;Lo;0;L;;;;;N;;;;; AA48;CHAM LETTER FINAL Y;Lo;0;L;;;;;N;;;;; AA49;CHAM LETTER FINAL R;Lo;0;L;;;;;N;;;;; AA4A;CHAM LETTER FINAL L;Lo;0;L;;;;;N;;;;; AA4B;CHAM LETTER FINAL SS;Lo;0;L;;;;;N;;;;; AA4C;CHAM CONSONANT SIGN FINAL M;Mn;0;NSM;;;;;N;;;;; AA4D;CHAM CONSONANT SIGN FINAL H;Mc;0;L;;;;;N;;;;; AA50;CHAM DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; AA51;CHAM DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; AA52;CHAM DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; AA53;CHAM DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; AA54;CHAM DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; AA55;CHAM DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; AA56;CHAM DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; AA57;CHAM DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; AA58;CHAM DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; AA59;CHAM DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; AA5C;CHAM PUNCTUATION SPIRAL;Po;0;L;;;;;N;;;;; AA5D;CHAM PUNCTUATION DANDA;Po;0;L;;;;;N;;;;; AA5E;CHAM PUNCTUATION DOUBLE DANDA;Po;0;L;;;;;N;;;;; AA5F;CHAM PUNCTUATION TRIPLE DANDA;Po;0;L;;;;;N;;;;; AA60;MYANMAR LETTER KHAMTI GA;Lo;0;L;;;;;N;;;;; AA61;MYANMAR LETTER KHAMTI CA;Lo;0;L;;;;;N;;;;; AA62;MYANMAR LETTER KHAMTI CHA;Lo;0;L;;;;;N;;;;; AA63;MYANMAR LETTER KHAMTI JA;Lo;0;L;;;;;N;;;;; AA64;MYANMAR LETTER KHAMTI JHA;Lo;0;L;;;;;N;;;;; AA65;MYANMAR LETTER KHAMTI NYA;Lo;0;L;;;;;N;;;;; AA66;MYANMAR LETTER KHAMTI TTA;Lo;0;L;;;;;N;;;;; AA67;MYANMAR LETTER KHAMTI TTHA;Lo;0;L;;;;;N;;;;; AA68;MYANMAR LETTER KHAMTI DDA;Lo;0;L;;;;;N;;;;; AA69;MYANMAR LETTER KHAMTI DDHA;Lo;0;L;;;;;N;;;;; AA6A;MYANMAR LETTER KHAMTI DHA;Lo;0;L;;;;;N;;;;; AA6B;MYANMAR LETTER KHAMTI NA;Lo;0;L;;;;;N;;;;; AA6C;MYANMAR LETTER KHAMTI SA;Lo;0;L;;;;;N;;;;; AA6D;MYANMAR LETTER KHAMTI HA;Lo;0;L;;;;;N;;;;; AA6E;MYANMAR LETTER KHAMTI HHA;Lo;0;L;;;;;N;;;;; AA6F;MYANMAR LETTER KHAMTI FA;Lo;0;L;;;;;N;;;;; AA70;MYANMAR MODIFIER LETTER KHAMTI REDUPLICATION;Lm;0;L;;;;;N;;;;; AA71;MYANMAR LETTER KHAMTI XA;Lo;0;L;;;;;N;;;;; AA72;MYANMAR LETTER KHAMTI ZA;Lo;0;L;;;;;N;;;;; AA73;MYANMAR LETTER KHAMTI RA;Lo;0;L;;;;;N;;;;; AA74;MYANMAR LOGOGRAM KHAMTI OAY;Lo;0;L;;;;;N;;;;; AA75;MYANMAR LOGOGRAM KHAMTI QN;Lo;0;L;;;;;N;;;;; AA76;MYANMAR LOGOGRAM KHAMTI HM;Lo;0;L;;;;;N;;;;; AA77;MYANMAR SYMBOL AITON EXCLAMATION;So;0;L;;;;;N;;;;; AA78;MYANMAR SYMBOL AITON ONE;So;0;L;;;;;N;;;;; AA79;MYANMAR SYMBOL AITON TWO;So;0;L;;;;;N;;;;; AA7A;MYANMAR LETTER AITON RA;Lo;0;L;;;;;N;;;;; AA7B;MYANMAR SIGN PAO KAREN TONE;Mc;0;L;;;;;N;;;;; AA7C;MYANMAR SIGN TAI LAING TONE-2;Mn;0;NSM;;;;;N;;;;; AA7D;MYANMAR SIGN TAI LAING TONE-5;Mc;0;L;;;;;N;;;;; AA7E;MYANMAR LETTER SHWE PALAUNG CHA;Lo;0;L;;;;;N;;;;; AA7F;MYANMAR LETTER SHWE PALAUNG SHA;Lo;0;L;;;;;N;;;;; AA80;TAI VIET LETTER LOW KO;Lo;0;L;;;;;N;;;;; AA81;TAI VIET LETTER HIGH KO;Lo;0;L;;;;;N;;;;; AA82;TAI VIET LETTER LOW KHO;Lo;0;L;;;;;N;;;;; AA83;TAI VIET LETTER HIGH KHO;Lo;0;L;;;;;N;;;;; AA84;TAI VIET LETTER LOW KHHO;Lo;0;L;;;;;N;;;;; AA85;TAI VIET LETTER HIGH KHHO;Lo;0;L;;;;;N;;;;; AA86;TAI VIET LETTER LOW GO;Lo;0;L;;;;;N;;;;; AA87;TAI VIET LETTER HIGH GO;Lo;0;L;;;;;N;;;;; AA88;TAI VIET LETTER LOW NGO;Lo;0;L;;;;;N;;;;; AA89;TAI VIET LETTER HIGH NGO;Lo;0;L;;;;;N;;;;; AA8A;TAI VIET LETTER LOW CO;Lo;0;L;;;;;N;;;;; AA8B;TAI VIET LETTER HIGH CO;Lo;0;L;;;;;N;;;;; AA8C;TAI VIET LETTER LOW CHO;Lo;0;L;;;;;N;;;;; AA8D;TAI VIET LETTER HIGH CHO;Lo;0;L;;;;;N;;;;; AA8E;TAI VIET LETTER LOW SO;Lo;0;L;;;;;N;;;;; AA8F;TAI VIET LETTER HIGH SO;Lo;0;L;;;;;N;;;;; AA90;TAI VIET LETTER LOW NYO;Lo;0;L;;;;;N;;;;; AA91;TAI VIET LETTER HIGH NYO;Lo;0;L;;;;;N;;;;; AA92;TAI VIET LETTER LOW DO;Lo;0;L;;;;;N;;;;; AA93;TAI VIET LETTER HIGH DO;Lo;0;L;;;;;N;;;;; AA94;TAI VIET LETTER LOW TO;Lo;0;L;;;;;N;;;;; AA95;TAI VIET LETTER HIGH TO;Lo;0;L;;;;;N;;;;; AA96;TAI VIET LETTER LOW THO;Lo;0;L;;;;;N;;;;; AA97;TAI VIET LETTER HIGH THO;Lo;0;L;;;;;N;;;;; AA98;TAI VIET LETTER LOW NO;Lo;0;L;;;;;N;;;;; AA99;TAI VIET LETTER HIGH NO;Lo;0;L;;;;;N;;;;; AA9A;TAI VIET LETTER LOW BO;Lo;0;L;;;;;N;;;;; AA9B;TAI VIET LETTER HIGH BO;Lo;0;L;;;;;N;;;;; AA9C;TAI VIET LETTER LOW PO;Lo;0;L;;;;;N;;;;; AA9D;TAI VIET LETTER HIGH PO;Lo;0;L;;;;;N;;;;; AA9E;TAI VIET LETTER LOW PHO;Lo;0;L;;;;;N;;;;; AA9F;TAI VIET LETTER HIGH PHO;Lo;0;L;;;;;N;;;;; AAA0;TAI VIET LETTER LOW FO;Lo;0;L;;;;;N;;;;; AAA1;TAI VIET LETTER HIGH FO;Lo;0;L;;;;;N;;;;; AAA2;TAI VIET LETTER LOW MO;Lo;0;L;;;;;N;;;;; AAA3;TAI VIET LETTER HIGH MO;Lo;0;L;;;;;N;;;;; AAA4;TAI VIET LETTER LOW YO;Lo;0;L;;;;;N;;;;; AAA5;TAI VIET LETTER HIGH YO;Lo;0;L;;;;;N;;;;; AAA6;TAI VIET LETTER LOW RO;Lo;0;L;;;;;N;;;;; AAA7;TAI VIET LETTER HIGH RO;Lo;0;L;;;;;N;;;;; AAA8;TAI VIET LETTER LOW LO;Lo;0;L;;;;;N;;;;; AAA9;TAI VIET LETTER HIGH LO;Lo;0;L;;;;;N;;;;; AAAA;TAI VIET LETTER LOW VO;Lo;0;L;;;;;N;;;;; AAAB;TAI VIET LETTER HIGH VO;Lo;0;L;;;;;N;;;;; AAAC;TAI VIET LETTER LOW HO;Lo;0;L;;;;;N;;;;; AAAD;TAI VIET LETTER HIGH HO;Lo;0;L;;;;;N;;;;; AAAE;TAI VIET LETTER LOW O;Lo;0;L;;;;;N;;;;; AAAF;TAI VIET LETTER HIGH O;Lo;0;L;;;;;N;;;;; AAB0;TAI VIET MAI KANG;Mn;230;NSM;;;;;N;;;;; AAB1;TAI VIET VOWEL AA;Lo;0;L;;;;;N;;;;; AAB2;TAI VIET VOWEL I;Mn;230;NSM;;;;;N;;;;; AAB3;TAI VIET VOWEL UE;Mn;230;NSM;;;;;N;;;;; AAB4;TAI VIET VOWEL U;Mn;220;NSM;;;;;N;;;;; AAB5;TAI VIET VOWEL E;Lo;0;L;;;;;N;;;;; AAB6;TAI VIET VOWEL O;Lo;0;L;;;;;N;;;;; AAB7;TAI VIET MAI KHIT;Mn;230;NSM;;;;;N;;;;; AAB8;TAI VIET VOWEL IA;Mn;230;NSM;;;;;N;;;;; AAB9;TAI VIET VOWEL UEA;Lo;0;L;;;;;N;;;;; AABA;TAI VIET VOWEL UA;Lo;0;L;;;;;N;;;;; AABB;TAI VIET VOWEL AUE;Lo;0;L;;;;;N;;;;; AABC;TAI VIET VOWEL AY;Lo;0;L;;;;;N;;;;; AABD;TAI VIET VOWEL AN;Lo;0;L;;;;;N;;;;; AABE;TAI VIET VOWEL AM;Mn;230;NSM;;;;;N;;;;; AABF;TAI VIET TONE MAI EK;Mn;230;NSM;;;;;N;;;;; AAC0;TAI VIET TONE MAI NUENG;Lo;0;L;;;;;N;;;;; AAC1;TAI VIET TONE MAI THO;Mn;230;NSM;;;;;N;;;;; AAC2;TAI VIET TONE MAI SONG;Lo;0;L;;;;;N;;;;; AADB;TAI VIET SYMBOL KON;Lo;0;L;;;;;N;;;;; AADC;TAI VIET SYMBOL NUENG;Lo;0;L;;;;;N;;;;; AADD;TAI VIET SYMBOL SAM;Lm;0;L;;;;;N;;;;; AADE;TAI VIET SYMBOL HO HOI;Po;0;L;;;;;N;;;;; AADF;TAI VIET SYMBOL KOI KOI;Po;0;L;;;;;N;;;;; AAE0;MEETEI MAYEK LETTER E;Lo;0;L;;;;;N;;;;; AAE1;MEETEI MAYEK LETTER O;Lo;0;L;;;;;N;;;;; AAE2;MEETEI MAYEK LETTER CHA;Lo;0;L;;;;;N;;;;; AAE3;MEETEI MAYEK LETTER NYA;Lo;0;L;;;;;N;;;;; AAE4;MEETEI MAYEK LETTER TTA;Lo;0;L;;;;;N;;;;; AAE5;MEETEI MAYEK LETTER TTHA;Lo;0;L;;;;;N;;;;; AAE6;MEETEI MAYEK LETTER DDA;Lo;0;L;;;;;N;;;;; AAE7;MEETEI MAYEK LETTER DDHA;Lo;0;L;;;;;N;;;;; AAE8;MEETEI MAYEK LETTER NNA;Lo;0;L;;;;;N;;;;; AAE9;MEETEI MAYEK LETTER SHA;Lo;0;L;;;;;N;;;;; AAEA;MEETEI MAYEK LETTER SSA;Lo;0;L;;;;;N;;;;; AAEB;MEETEI MAYEK VOWEL SIGN II;Mc;0;L;;;;;N;;;;; AAEC;MEETEI MAYEK VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; AAED;MEETEI MAYEK VOWEL SIGN AAI;Mn;0;NSM;;;;;N;;;;; AAEE;MEETEI MAYEK VOWEL SIGN AU;Mc;0;L;;;;;N;;;;; AAEF;MEETEI MAYEK VOWEL SIGN AAU;Mc;0;L;;;;;N;;;;; AAF0;MEETEI MAYEK CHEIKHAN;Po;0;L;;;;;N;;;;; AAF1;MEETEI MAYEK AHANG KHUDAM;Po;0;L;;;;;N;;;;; AAF2;MEETEI MAYEK ANJI;Lo;0;L;;;;;N;;;;; AAF3;MEETEI MAYEK SYLLABLE REPETITION MARK;Lm;0;L;;;;;N;;;;; AAF4;MEETEI MAYEK WORD REPETITION MARK;Lm;0;L;;;;;N;;;;; AAF5;MEETEI MAYEK VOWEL SIGN VISARGA;Mc;0;L;;;;;N;;;;; AAF6;MEETEI MAYEK VIRAMA;Mn;9;NSM;;;;;N;;;;; AB01;ETHIOPIC SYLLABLE TTHU;Lo;0;L;;;;;N;;;;; AB02;ETHIOPIC SYLLABLE TTHI;Lo;0;L;;;;;N;;;;; AB03;ETHIOPIC SYLLABLE TTHAA;Lo;0;L;;;;;N;;;;; AB04;ETHIOPIC SYLLABLE TTHEE;Lo;0;L;;;;;N;;;;; AB05;ETHIOPIC SYLLABLE TTHE;Lo;0;L;;;;;N;;;;; AB06;ETHIOPIC SYLLABLE TTHO;Lo;0;L;;;;;N;;;;; AB09;ETHIOPIC SYLLABLE DDHU;Lo;0;L;;;;;N;;;;; AB0A;ETHIOPIC SYLLABLE DDHI;Lo;0;L;;;;;N;;;;; AB0B;ETHIOPIC SYLLABLE DDHAA;Lo;0;L;;;;;N;;;;; AB0C;ETHIOPIC SYLLABLE DDHEE;Lo;0;L;;;;;N;;;;; AB0D;ETHIOPIC SYLLABLE DDHE;Lo;0;L;;;;;N;;;;; AB0E;ETHIOPIC SYLLABLE DDHO;Lo;0;L;;;;;N;;;;; AB11;ETHIOPIC SYLLABLE DZU;Lo;0;L;;;;;N;;;;; AB12;ETHIOPIC SYLLABLE DZI;Lo;0;L;;;;;N;;;;; AB13;ETHIOPIC SYLLABLE DZAA;Lo;0;L;;;;;N;;;;; AB14;ETHIOPIC SYLLABLE DZEE;Lo;0;L;;;;;N;;;;; AB15;ETHIOPIC SYLLABLE DZE;Lo;0;L;;;;;N;;;;; AB16;ETHIOPIC SYLLABLE DZO;Lo;0;L;;;;;N;;;;; AB20;ETHIOPIC SYLLABLE CCHHA;Lo;0;L;;;;;N;;;;; AB21;ETHIOPIC SYLLABLE CCHHU;Lo;0;L;;;;;N;;;;; AB22;ETHIOPIC SYLLABLE CCHHI;Lo;0;L;;;;;N;;;;; AB23;ETHIOPIC SYLLABLE CCHHAA;Lo;0;L;;;;;N;;;;; AB24;ETHIOPIC SYLLABLE CCHHEE;Lo;0;L;;;;;N;;;;; AB25;ETHIOPIC SYLLABLE CCHHE;Lo;0;L;;;;;N;;;;; AB26;ETHIOPIC SYLLABLE CCHHO;Lo;0;L;;;;;N;;;;; AB28;ETHIOPIC SYLLABLE BBA;Lo;0;L;;;;;N;;;;; AB29;ETHIOPIC SYLLABLE BBU;Lo;0;L;;;;;N;;;;; AB2A;ETHIOPIC SYLLABLE BBI;Lo;0;L;;;;;N;;;;; AB2B;ETHIOPIC SYLLABLE BBAA;Lo;0;L;;;;;N;;;;; AB2C;ETHIOPIC SYLLABLE BBEE;Lo;0;L;;;;;N;;;;; AB2D;ETHIOPIC SYLLABLE BBE;Lo;0;L;;;;;N;;;;; AB2E;ETHIOPIC SYLLABLE BBO;Lo;0;L;;;;;N;;;;; AB30;LATIN SMALL LETTER BARRED ALPHA;Ll;0;L;;;;;N;;;;; AB31;LATIN SMALL LETTER A REVERSED-SCHWA;Ll;0;L;;;;;N;;;;; AB32;LATIN SMALL LETTER BLACKLETTER E;Ll;0;L;;;;;N;;;;; AB33;LATIN SMALL LETTER BARRED E;Ll;0;L;;;;;N;;;;; AB34;LATIN SMALL LETTER E WITH FLOURISH;Ll;0;L;;;;;N;;;;; AB35;LATIN SMALL LETTER LENIS F;Ll;0;L;;;;;N;;;;; AB36;LATIN SMALL LETTER SCRIPT G WITH CROSSED-TAIL;Ll;0;L;;;;;N;;;;; AB37;LATIN SMALL LETTER L WITH INVERTED LAZY S;Ll;0;L;;;;;N;;;;; AB38;LATIN SMALL LETTER L WITH DOUBLE MIDDLE TILDE;Ll;0;L;;;;;N;;;;; AB39;LATIN SMALL LETTER L WITH MIDDLE RING;Ll;0;L;;;;;N;;;;; AB3A;LATIN SMALL LETTER M WITH CROSSED-TAIL;Ll;0;L;;;;;N;;;;; AB3B;LATIN SMALL LETTER N WITH CROSSED-TAIL;Ll;0;L;;;;;N;;;;; AB3C;LATIN SMALL LETTER ENG WITH CROSSED-TAIL;Ll;0;L;;;;;N;;;;; AB3D;LATIN SMALL LETTER BLACKLETTER O;Ll;0;L;;;;;N;;;;; AB3E;LATIN SMALL LETTER BLACKLETTER O WITH STROKE;Ll;0;L;;;;;N;;;;; AB3F;LATIN SMALL LETTER OPEN O WITH STROKE;Ll;0;L;;;;;N;;;;; AB40;LATIN SMALL LETTER INVERTED OE;Ll;0;L;;;;;N;;;;; AB41;LATIN SMALL LETTER TURNED OE WITH STROKE;Ll;0;L;;;;;N;;;;; AB42;LATIN SMALL LETTER TURNED OE WITH HORIZONTAL STROKE;Ll;0;L;;;;;N;;;;; AB43;LATIN SMALL LETTER TURNED O OPEN-O;Ll;0;L;;;;;N;;;;; AB44;LATIN SMALL LETTER TURNED O OPEN-O WITH STROKE;Ll;0;L;;;;;N;;;;; AB45;LATIN SMALL LETTER STIRRUP R;Ll;0;L;;;;;N;;;;; AB46;LATIN LETTER SMALL CAPITAL R WITH RIGHT LEG;Ll;0;L;;;;;N;;;;; AB47;LATIN SMALL LETTER R WITHOUT HANDLE;Ll;0;L;;;;;N;;;;; AB48;LATIN SMALL LETTER DOUBLE R;Ll;0;L;;;;;N;;;;; AB49;LATIN SMALL LETTER R WITH CROSSED-TAIL;Ll;0;L;;;;;N;;;;; AB4A;LATIN SMALL LETTER DOUBLE R WITH CROSSED-TAIL;Ll;0;L;;;;;N;;;;; AB4B;LATIN SMALL LETTER SCRIPT R;Ll;0;L;;;;;N;;;;; AB4C;LATIN SMALL LETTER SCRIPT R WITH RING;Ll;0;L;;;;;N;;;;; AB4D;LATIN SMALL LETTER BASELINE ESH;Ll;0;L;;;;;N;;;;; AB4E;LATIN SMALL LETTER U WITH SHORT RIGHT LEG;Ll;0;L;;;;;N;;;;; AB4F;LATIN SMALL LETTER U BAR WITH SHORT RIGHT LEG;Ll;0;L;;;;;N;;;;; AB50;LATIN SMALL LETTER UI;Ll;0;L;;;;;N;;;;; AB51;LATIN SMALL LETTER TURNED UI;Ll;0;L;;;;;N;;;;; AB52;LATIN SMALL LETTER U WITH LEFT HOOK;Ll;0;L;;;;;N;;;;; AB53;LATIN SMALL LETTER CHI;Ll;0;L;;;;;N;;;A7B3;;A7B3 AB54;LATIN SMALL LETTER CHI WITH LOW RIGHT RING;Ll;0;L;;;;;N;;;;; AB55;LATIN SMALL LETTER CHI WITH LOW LEFT SERIF;Ll;0;L;;;;;N;;;;; AB56;LATIN SMALL LETTER X WITH LOW RIGHT RING;Ll;0;L;;;;;N;;;;; AB57;LATIN SMALL LETTER X WITH LONG LEFT LEG;Ll;0;L;;;;;N;;;;; AB58;LATIN SMALL LETTER X WITH LONG LEFT LEG AND LOW RIGHT RING;Ll;0;L;;;;;N;;;;; AB59;LATIN SMALL LETTER X WITH LONG LEFT LEG WITH SERIF;Ll;0;L;;;;;N;;;;; AB5A;LATIN SMALL LETTER Y WITH SHORT RIGHT LEG;Ll;0;L;;;;;N;;;;; AB5B;MODIFIER BREVE WITH INVERTED BREVE;Sk;0;L;;;;;N;;;;; AB5C;MODIFIER LETTER SMALL HENG;Lm;0;L; A727;;;;N;;;;; AB5D;MODIFIER LETTER SMALL L WITH INVERTED LAZY S;Lm;0;L; AB37;;;;N;;;;; AB5E;MODIFIER LETTER SMALL L WITH MIDDLE TILDE;Lm;0;L; 026B;;;;N;;;;; AB5F;MODIFIER LETTER SMALL U WITH LEFT HOOK;Lm;0;L; AB52;;;;N;;;;; AB60;LATIN SMALL LETTER SAKHA YAT;Ll;0;L;;;;;N;;;;; AB61;LATIN SMALL LETTER IOTIFIED E;Ll;0;L;;;;;N;;;;; AB62;LATIN SMALL LETTER OPEN OE;Ll;0;L;;;;;N;;;;; AB63;LATIN SMALL LETTER UO;Ll;0;L;;;;;N;;;;; AB64;LATIN SMALL LETTER INVERTED ALPHA;Ll;0;L;;;;;N;;;;; AB65;GREEK LETTER SMALL CAPITAL OMEGA;Ll;0;L;;;;;N;;;;; AB70;CHEROKEE SMALL LETTER A;Ll;0;L;;;;;N;;;13A0;;13A0 AB71;CHEROKEE SMALL LETTER E;Ll;0;L;;;;;N;;;13A1;;13A1 AB72;CHEROKEE SMALL LETTER I;Ll;0;L;;;;;N;;;13A2;;13A2 AB73;CHEROKEE SMALL LETTER O;Ll;0;L;;;;;N;;;13A3;;13A3 AB74;CHEROKEE SMALL LETTER U;Ll;0;L;;;;;N;;;13A4;;13A4 AB75;CHEROKEE SMALL LETTER V;Ll;0;L;;;;;N;;;13A5;;13A5 AB76;CHEROKEE SMALL LETTER GA;Ll;0;L;;;;;N;;;13A6;;13A6 AB77;CHEROKEE SMALL LETTER KA;Ll;0;L;;;;;N;;;13A7;;13A7 AB78;CHEROKEE SMALL LETTER GE;Ll;0;L;;;;;N;;;13A8;;13A8 AB79;CHEROKEE SMALL LETTER GI;Ll;0;L;;;;;N;;;13A9;;13A9 AB7A;CHEROKEE SMALL LETTER GO;Ll;0;L;;;;;N;;;13AA;;13AA AB7B;CHEROKEE SMALL LETTER GU;Ll;0;L;;;;;N;;;13AB;;13AB AB7C;CHEROKEE SMALL LETTER GV;Ll;0;L;;;;;N;;;13AC;;13AC AB7D;CHEROKEE SMALL LETTER HA;Ll;0;L;;;;;N;;;13AD;;13AD AB7E;CHEROKEE SMALL LETTER HE;Ll;0;L;;;;;N;;;13AE;;13AE AB7F;CHEROKEE SMALL LETTER HI;Ll;0;L;;;;;N;;;13AF;;13AF AB80;CHEROKEE SMALL LETTER HO;Ll;0;L;;;;;N;;;13B0;;13B0 AB81;CHEROKEE SMALL LETTER HU;Ll;0;L;;;;;N;;;13B1;;13B1 AB82;CHEROKEE SMALL LETTER HV;Ll;0;L;;;;;N;;;13B2;;13B2 AB83;CHEROKEE SMALL LETTER LA;Ll;0;L;;;;;N;;;13B3;;13B3 AB84;CHEROKEE SMALL LETTER LE;Ll;0;L;;;;;N;;;13B4;;13B4 AB85;CHEROKEE SMALL LETTER LI;Ll;0;L;;;;;N;;;13B5;;13B5 AB86;CHEROKEE SMALL LETTER LO;Ll;0;L;;;;;N;;;13B6;;13B6 AB87;CHEROKEE SMALL LETTER LU;Ll;0;L;;;;;N;;;13B7;;13B7 AB88;CHEROKEE SMALL LETTER LV;Ll;0;L;;;;;N;;;13B8;;13B8 AB89;CHEROKEE SMALL LETTER MA;Ll;0;L;;;;;N;;;13B9;;13B9 AB8A;CHEROKEE SMALL LETTER ME;Ll;0;L;;;;;N;;;13BA;;13BA AB8B;CHEROKEE SMALL LETTER MI;Ll;0;L;;;;;N;;;13BB;;13BB AB8C;CHEROKEE SMALL LETTER MO;Ll;0;L;;;;;N;;;13BC;;13BC AB8D;CHEROKEE SMALL LETTER MU;Ll;0;L;;;;;N;;;13BD;;13BD AB8E;CHEROKEE SMALL LETTER NA;Ll;0;L;;;;;N;;;13BE;;13BE AB8F;CHEROKEE SMALL LETTER HNA;Ll;0;L;;;;;N;;;13BF;;13BF AB90;CHEROKEE SMALL LETTER NAH;Ll;0;L;;;;;N;;;13C0;;13C0 AB91;CHEROKEE SMALL LETTER NE;Ll;0;L;;;;;N;;;13C1;;13C1 AB92;CHEROKEE SMALL LETTER NI;Ll;0;L;;;;;N;;;13C2;;13C2 AB93;CHEROKEE SMALL LETTER NO;Ll;0;L;;;;;N;;;13C3;;13C3 AB94;CHEROKEE SMALL LETTER NU;Ll;0;L;;;;;N;;;13C4;;13C4 AB95;CHEROKEE SMALL LETTER NV;Ll;0;L;;;;;N;;;13C5;;13C5 AB96;CHEROKEE SMALL LETTER QUA;Ll;0;L;;;;;N;;;13C6;;13C6 AB97;CHEROKEE SMALL LETTER QUE;Ll;0;L;;;;;N;;;13C7;;13C7 AB98;CHEROKEE SMALL LETTER QUI;Ll;0;L;;;;;N;;;13C8;;13C8 AB99;CHEROKEE SMALL LETTER QUO;Ll;0;L;;;;;N;;;13C9;;13C9 AB9A;CHEROKEE SMALL LETTER QUU;Ll;0;L;;;;;N;;;13CA;;13CA AB9B;CHEROKEE SMALL LETTER QUV;Ll;0;L;;;;;N;;;13CB;;13CB AB9C;CHEROKEE SMALL LETTER SA;Ll;0;L;;;;;N;;;13CC;;13CC AB9D;CHEROKEE SMALL LETTER S;Ll;0;L;;;;;N;;;13CD;;13CD AB9E;CHEROKEE SMALL LETTER SE;Ll;0;L;;;;;N;;;13CE;;13CE AB9F;CHEROKEE SMALL LETTER SI;Ll;0;L;;;;;N;;;13CF;;13CF ABA0;CHEROKEE SMALL LETTER SO;Ll;0;L;;;;;N;;;13D0;;13D0 ABA1;CHEROKEE SMALL LETTER SU;Ll;0;L;;;;;N;;;13D1;;13D1 ABA2;CHEROKEE SMALL LETTER SV;Ll;0;L;;;;;N;;;13D2;;13D2 ABA3;CHEROKEE SMALL LETTER DA;Ll;0;L;;;;;N;;;13D3;;13D3 ABA4;CHEROKEE SMALL LETTER TA;Ll;0;L;;;;;N;;;13D4;;13D4 ABA5;CHEROKEE SMALL LETTER DE;Ll;0;L;;;;;N;;;13D5;;13D5 ABA6;CHEROKEE SMALL LETTER TE;Ll;0;L;;;;;N;;;13D6;;13D6 ABA7;CHEROKEE SMALL LETTER DI;Ll;0;L;;;;;N;;;13D7;;13D7 ABA8;CHEROKEE SMALL LETTER TI;Ll;0;L;;;;;N;;;13D8;;13D8 ABA9;CHEROKEE SMALL LETTER DO;Ll;0;L;;;;;N;;;13D9;;13D9 ABAA;CHEROKEE SMALL LETTER DU;Ll;0;L;;;;;N;;;13DA;;13DA ABAB;CHEROKEE SMALL LETTER DV;Ll;0;L;;;;;N;;;13DB;;13DB ABAC;CHEROKEE SMALL LETTER DLA;Ll;0;L;;;;;N;;;13DC;;13DC ABAD;CHEROKEE SMALL LETTER TLA;Ll;0;L;;;;;N;;;13DD;;13DD ABAE;CHEROKEE SMALL LETTER TLE;Ll;0;L;;;;;N;;;13DE;;13DE ABAF;CHEROKEE SMALL LETTER TLI;Ll;0;L;;;;;N;;;13DF;;13DF ABB0;CHEROKEE SMALL LETTER TLO;Ll;0;L;;;;;N;;;13E0;;13E0 ABB1;CHEROKEE SMALL LETTER TLU;Ll;0;L;;;;;N;;;13E1;;13E1 ABB2;CHEROKEE SMALL LETTER TLV;Ll;0;L;;;;;N;;;13E2;;13E2 ABB3;CHEROKEE SMALL LETTER TSA;Ll;0;L;;;;;N;;;13E3;;13E3 ABB4;CHEROKEE SMALL LETTER TSE;Ll;0;L;;;;;N;;;13E4;;13E4 ABB5;CHEROKEE SMALL LETTER TSI;Ll;0;L;;;;;N;;;13E5;;13E5 ABB6;CHEROKEE SMALL LETTER TSO;Ll;0;L;;;;;N;;;13E6;;13E6 ABB7;CHEROKEE SMALL LETTER TSU;Ll;0;L;;;;;N;;;13E7;;13E7 ABB8;CHEROKEE SMALL LETTER TSV;Ll;0;L;;;;;N;;;13E8;;13E8 ABB9;CHEROKEE SMALL LETTER WA;Ll;0;L;;;;;N;;;13E9;;13E9 ABBA;CHEROKEE SMALL LETTER WE;Ll;0;L;;;;;N;;;13EA;;13EA ABBB;CHEROKEE SMALL LETTER WI;Ll;0;L;;;;;N;;;13EB;;13EB ABBC;CHEROKEE SMALL LETTER WO;Ll;0;L;;;;;N;;;13EC;;13EC ABBD;CHEROKEE SMALL LETTER WU;Ll;0;L;;;;;N;;;13ED;;13ED ABBE;CHEROKEE SMALL LETTER WV;Ll;0;L;;;;;N;;;13EE;;13EE ABBF;CHEROKEE SMALL LETTER YA;Ll;0;L;;;;;N;;;13EF;;13EF ABC0;MEETEI MAYEK LETTER KOK;Lo;0;L;;;;;N;;;;; ABC1;MEETEI MAYEK LETTER SAM;Lo;0;L;;;;;N;;;;; ABC2;MEETEI MAYEK LETTER LAI;Lo;0;L;;;;;N;;;;; ABC3;MEETEI MAYEK LETTER MIT;Lo;0;L;;;;;N;;;;; ABC4;MEETEI MAYEK LETTER PA;Lo;0;L;;;;;N;;;;; ABC5;MEETEI MAYEK LETTER NA;Lo;0;L;;;;;N;;;;; ABC6;MEETEI MAYEK LETTER CHIL;Lo;0;L;;;;;N;;;;; ABC7;MEETEI MAYEK LETTER TIL;Lo;0;L;;;;;N;;;;; ABC8;MEETEI MAYEK LETTER KHOU;Lo;0;L;;;;;N;;;;; ABC9;MEETEI MAYEK LETTER NGOU;Lo;0;L;;;;;N;;;;; ABCA;MEETEI MAYEK LETTER THOU;Lo;0;L;;;;;N;;;;; ABCB;MEETEI MAYEK LETTER WAI;Lo;0;L;;;;;N;;;;; ABCC;MEETEI MAYEK LETTER YANG;Lo;0;L;;;;;N;;;;; ABCD;MEETEI MAYEK LETTER HUK;Lo;0;L;;;;;N;;;;; ABCE;MEETEI MAYEK LETTER UN;Lo;0;L;;;;;N;;;;; ABCF;MEETEI MAYEK LETTER I;Lo;0;L;;;;;N;;;;; ABD0;MEETEI MAYEK LETTER PHAM;Lo;0;L;;;;;N;;;;; ABD1;MEETEI MAYEK LETTER ATIYA;Lo;0;L;;;;;N;;;;; ABD2;MEETEI MAYEK LETTER GOK;Lo;0;L;;;;;N;;;;; ABD3;MEETEI MAYEK LETTER JHAM;Lo;0;L;;;;;N;;;;; ABD4;MEETEI MAYEK LETTER RAI;Lo;0;L;;;;;N;;;;; ABD5;MEETEI MAYEK LETTER BA;Lo;0;L;;;;;N;;;;; ABD6;MEETEI MAYEK LETTER JIL;Lo;0;L;;;;;N;;;;; ABD7;MEETEI MAYEK LETTER DIL;Lo;0;L;;;;;N;;;;; ABD8;MEETEI MAYEK LETTER GHOU;Lo;0;L;;;;;N;;;;; ABD9;MEETEI MAYEK LETTER DHOU;Lo;0;L;;;;;N;;;;; ABDA;MEETEI MAYEK LETTER BHAM;Lo;0;L;;;;;N;;;;; ABDB;MEETEI MAYEK LETTER KOK LONSUM;Lo;0;L;;;;;N;;;;; ABDC;MEETEI MAYEK LETTER LAI LONSUM;Lo;0;L;;;;;N;;;;; ABDD;MEETEI MAYEK LETTER MIT LONSUM;Lo;0;L;;;;;N;;;;; ABDE;MEETEI MAYEK LETTER PA LONSUM;Lo;0;L;;;;;N;;;;; ABDF;MEETEI MAYEK LETTER NA LONSUM;Lo;0;L;;;;;N;;;;; ABE0;MEETEI MAYEK LETTER TIL LONSUM;Lo;0;L;;;;;N;;;;; ABE1;MEETEI MAYEK LETTER NGOU LONSUM;Lo;0;L;;;;;N;;;;; ABE2;MEETEI MAYEK LETTER I LONSUM;Lo;0;L;;;;;N;;;;; ABE3;MEETEI MAYEK VOWEL SIGN ONAP;Mc;0;L;;;;;N;;;;; ABE4;MEETEI MAYEK VOWEL SIGN INAP;Mc;0;L;;;;;N;;;;; ABE5;MEETEI MAYEK VOWEL SIGN ANAP;Mn;0;NSM;;;;;N;;;;; ABE6;MEETEI MAYEK VOWEL SIGN YENAP;Mc;0;L;;;;;N;;;;; ABE7;MEETEI MAYEK VOWEL SIGN SOUNAP;Mc;0;L;;;;;N;;;;; ABE8;MEETEI MAYEK VOWEL SIGN UNAP;Mn;0;NSM;;;;;N;;;;; ABE9;MEETEI MAYEK VOWEL SIGN CHEINAP;Mc;0;L;;;;;N;;;;; ABEA;MEETEI MAYEK VOWEL SIGN NUNG;Mc;0;L;;;;;N;;;;; ABEB;MEETEI MAYEK CHEIKHEI;Po;0;L;;;;;N;;;;; ABEC;MEETEI MAYEK LUM IYEK;Mc;0;L;;;;;N;;;;; ABED;MEETEI MAYEK APUN IYEK;Mn;9;NSM;;;;;N;;;;; ABF0;MEETEI MAYEK DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; ABF1;MEETEI MAYEK DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; ABF2;MEETEI MAYEK DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; ABF3;MEETEI MAYEK DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; ABF4;MEETEI MAYEK DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; ABF5;MEETEI MAYEK DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; ABF6;MEETEI MAYEK DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; ABF7;MEETEI MAYEK DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; ABF8;MEETEI MAYEK DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; ABF9;MEETEI MAYEK DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; AC00;;Lo;0;L;;;;;N;;;;; D7A3;;Lo;0;L;;;;;N;;;;; D7B0;HANGUL JUNGSEONG O-YEO;Lo;0;L;;;;;N;;;;; D7B1;HANGUL JUNGSEONG O-O-I;Lo;0;L;;;;;N;;;;; D7B2;HANGUL JUNGSEONG YO-A;Lo;0;L;;;;;N;;;;; D7B3;HANGUL JUNGSEONG YO-AE;Lo;0;L;;;;;N;;;;; D7B4;HANGUL JUNGSEONG YO-EO;Lo;0;L;;;;;N;;;;; D7B5;HANGUL JUNGSEONG U-YEO;Lo;0;L;;;;;N;;;;; D7B6;HANGUL JUNGSEONG U-I-I;Lo;0;L;;;;;N;;;;; D7B7;HANGUL JUNGSEONG YU-AE;Lo;0;L;;;;;N;;;;; D7B8;HANGUL JUNGSEONG YU-O;Lo;0;L;;;;;N;;;;; D7B9;HANGUL JUNGSEONG EU-A;Lo;0;L;;;;;N;;;;; D7BA;HANGUL JUNGSEONG EU-EO;Lo;0;L;;;;;N;;;;; D7BB;HANGUL JUNGSEONG EU-E;Lo;0;L;;;;;N;;;;; D7BC;HANGUL JUNGSEONG EU-O;Lo;0;L;;;;;N;;;;; D7BD;HANGUL JUNGSEONG I-YA-O;Lo;0;L;;;;;N;;;;; D7BE;HANGUL JUNGSEONG I-YAE;Lo;0;L;;;;;N;;;;; D7BF;HANGUL JUNGSEONG I-YEO;Lo;0;L;;;;;N;;;;; D7C0;HANGUL JUNGSEONG I-YE;Lo;0;L;;;;;N;;;;; D7C1;HANGUL JUNGSEONG I-O-I;Lo;0;L;;;;;N;;;;; D7C2;HANGUL JUNGSEONG I-YO;Lo;0;L;;;;;N;;;;; D7C3;HANGUL JUNGSEONG I-YU;Lo;0;L;;;;;N;;;;; D7C4;HANGUL JUNGSEONG I-I;Lo;0;L;;;;;N;;;;; D7C5;HANGUL JUNGSEONG ARAEA-A;Lo;0;L;;;;;N;;;;; D7C6;HANGUL JUNGSEONG ARAEA-E;Lo;0;L;;;;;N;;;;; D7CB;HANGUL JONGSEONG NIEUN-RIEUL;Lo;0;L;;;;;N;;;;; D7CC;HANGUL JONGSEONG NIEUN-CHIEUCH;Lo;0;L;;;;;N;;;;; D7CD;HANGUL JONGSEONG SSANGTIKEUT;Lo;0;L;;;;;N;;;;; D7CE;HANGUL JONGSEONG SSANGTIKEUT-PIEUP;Lo;0;L;;;;;N;;;;; D7CF;HANGUL JONGSEONG TIKEUT-PIEUP;Lo;0;L;;;;;N;;;;; D7D0;HANGUL JONGSEONG TIKEUT-SIOS;Lo;0;L;;;;;N;;;;; D7D1;HANGUL JONGSEONG TIKEUT-SIOS-KIYEOK;Lo;0;L;;;;;N;;;;; D7D2;HANGUL JONGSEONG TIKEUT-CIEUC;Lo;0;L;;;;;N;;;;; D7D3;HANGUL JONGSEONG TIKEUT-CHIEUCH;Lo;0;L;;;;;N;;;;; D7D4;HANGUL JONGSEONG TIKEUT-THIEUTH;Lo;0;L;;;;;N;;;;; D7D5;HANGUL JONGSEONG RIEUL-SSANGKIYEOK;Lo;0;L;;;;;N;;;;; D7D6;HANGUL JONGSEONG RIEUL-KIYEOK-HIEUH;Lo;0;L;;;;;N;;;;; D7D7;HANGUL JONGSEONG SSANGRIEUL-KHIEUKH;Lo;0;L;;;;;N;;;;; D7D8;HANGUL JONGSEONG RIEUL-MIEUM-HIEUH;Lo;0;L;;;;;N;;;;; D7D9;HANGUL JONGSEONG RIEUL-PIEUP-TIKEUT;Lo;0;L;;;;;N;;;;; D7DA;HANGUL JONGSEONG RIEUL-PIEUP-PHIEUPH;Lo;0;L;;;;;N;;;;; D7DB;HANGUL JONGSEONG RIEUL-YESIEUNG;Lo;0;L;;;;;N;;;;; D7DC;HANGUL JONGSEONG RIEUL-YEORINHIEUH-HIEUH;Lo;0;L;;;;;N;;;;; D7DD;HANGUL JONGSEONG KAPYEOUNRIEUL;Lo;0;L;;;;;N;;;;; D7DE;HANGUL JONGSEONG MIEUM-NIEUN;Lo;0;L;;;;;N;;;;; D7DF;HANGUL JONGSEONG MIEUM-SSANGNIEUN;Lo;0;L;;;;;N;;;;; D7E0;HANGUL JONGSEONG SSANGMIEUM;Lo;0;L;;;;;N;;;;; D7E1;HANGUL JONGSEONG MIEUM-PIEUP-SIOS;Lo;0;L;;;;;N;;;;; D7E2;HANGUL JONGSEONG MIEUM-CIEUC;Lo;0;L;;;;;N;;;;; D7E3;HANGUL JONGSEONG PIEUP-TIKEUT;Lo;0;L;;;;;N;;;;; D7E4;HANGUL JONGSEONG PIEUP-RIEUL-PHIEUPH;Lo;0;L;;;;;N;;;;; D7E5;HANGUL JONGSEONG PIEUP-MIEUM;Lo;0;L;;;;;N;;;;; D7E6;HANGUL JONGSEONG SSANGPIEUP;Lo;0;L;;;;;N;;;;; D7E7;HANGUL JONGSEONG PIEUP-SIOS-TIKEUT;Lo;0;L;;;;;N;;;;; D7E8;HANGUL JONGSEONG PIEUP-CIEUC;Lo;0;L;;;;;N;;;;; D7E9;HANGUL JONGSEONG PIEUP-CHIEUCH;Lo;0;L;;;;;N;;;;; D7EA;HANGUL JONGSEONG SIOS-MIEUM;Lo;0;L;;;;;N;;;;; D7EB;HANGUL JONGSEONG SIOS-KAPYEOUNPIEUP;Lo;0;L;;;;;N;;;;; D7EC;HANGUL JONGSEONG SSANGSIOS-KIYEOK;Lo;0;L;;;;;N;;;;; D7ED;HANGUL JONGSEONG SSANGSIOS-TIKEUT;Lo;0;L;;;;;N;;;;; D7EE;HANGUL JONGSEONG SIOS-PANSIOS;Lo;0;L;;;;;N;;;;; D7EF;HANGUL JONGSEONG SIOS-CIEUC;Lo;0;L;;;;;N;;;;; D7F0;HANGUL JONGSEONG SIOS-CHIEUCH;Lo;0;L;;;;;N;;;;; D7F1;HANGUL JONGSEONG SIOS-THIEUTH;Lo;0;L;;;;;N;;;;; D7F2;HANGUL JONGSEONG SIOS-HIEUH;Lo;0;L;;;;;N;;;;; D7F3;HANGUL JONGSEONG PANSIOS-PIEUP;Lo;0;L;;;;;N;;;;; D7F4;HANGUL JONGSEONG PANSIOS-KAPYEOUNPIEUP;Lo;0;L;;;;;N;;;;; D7F5;HANGUL JONGSEONG YESIEUNG-MIEUM;Lo;0;L;;;;;N;;;;; D7F6;HANGUL JONGSEONG YESIEUNG-HIEUH;Lo;0;L;;;;;N;;;;; D7F7;HANGUL JONGSEONG CIEUC-PIEUP;Lo;0;L;;;;;N;;;;; D7F8;HANGUL JONGSEONG CIEUC-SSANGPIEUP;Lo;0;L;;;;;N;;;;; D7F9;HANGUL JONGSEONG SSANGCIEUC;Lo;0;L;;;;;N;;;;; D7FA;HANGUL JONGSEONG PHIEUPH-SIOS;Lo;0;L;;;;;N;;;;; D7FB;HANGUL JONGSEONG PHIEUPH-THIEUTH;Lo;0;L;;;;;N;;;;; D800;;Cs;0;L;;;;;N;;;;; DB7F;;Cs;0;L;;;;;N;;;;; DB80;;Cs;0;L;;;;;N;;;;; DBFF;;Cs;0;L;;;;;N;;;;; DC00;;Cs;0;L;;;;;N;;;;; DFFF;;Cs;0;L;;;;;N;;;;; E000;;Co;0;L;;;;;N;;;;; F8FF;;Co;0;L;;;;;N;;;;; F900;CJK COMPATIBILITY IDEOGRAPH-F900;Lo;0;L;8C48;;;;N;;;;; F901;CJK COMPATIBILITY IDEOGRAPH-F901;Lo;0;L;66F4;;;;N;;;;; F902;CJK COMPATIBILITY IDEOGRAPH-F902;Lo;0;L;8ECA;;;;N;;;;; F903;CJK COMPATIBILITY IDEOGRAPH-F903;Lo;0;L;8CC8;;;;N;;;;; F904;CJK COMPATIBILITY IDEOGRAPH-F904;Lo;0;L;6ED1;;;;N;;;;; F905;CJK COMPATIBILITY IDEOGRAPH-F905;Lo;0;L;4E32;;;;N;;;;; F906;CJK COMPATIBILITY IDEOGRAPH-F906;Lo;0;L;53E5;;;;N;;;;; F907;CJK COMPATIBILITY IDEOGRAPH-F907;Lo;0;L;9F9C;;;;N;;;;; F908;CJK COMPATIBILITY IDEOGRAPH-F908;Lo;0;L;9F9C;;;;N;;;;; F909;CJK COMPATIBILITY IDEOGRAPH-F909;Lo;0;L;5951;;;;N;;;;; F90A;CJK COMPATIBILITY IDEOGRAPH-F90A;Lo;0;L;91D1;;;;N;;;;; F90B;CJK COMPATIBILITY IDEOGRAPH-F90B;Lo;0;L;5587;;;;N;;;;; F90C;CJK COMPATIBILITY IDEOGRAPH-F90C;Lo;0;L;5948;;;;N;;;;; F90D;CJK COMPATIBILITY IDEOGRAPH-F90D;Lo;0;L;61F6;;;;N;;;;; F90E;CJK COMPATIBILITY IDEOGRAPH-F90E;Lo;0;L;7669;;;;N;;;;; F90F;CJK COMPATIBILITY IDEOGRAPH-F90F;Lo;0;L;7F85;;;;N;;;;; F910;CJK COMPATIBILITY IDEOGRAPH-F910;Lo;0;L;863F;;;;N;;;;; F911;CJK COMPATIBILITY IDEOGRAPH-F911;Lo;0;L;87BA;;;;N;;;;; F912;CJK COMPATIBILITY IDEOGRAPH-F912;Lo;0;L;88F8;;;;N;;;;; F913;CJK COMPATIBILITY IDEOGRAPH-F913;Lo;0;L;908F;;;;N;;;;; F914;CJK COMPATIBILITY IDEOGRAPH-F914;Lo;0;L;6A02;;;;N;;;;; F915;CJK COMPATIBILITY IDEOGRAPH-F915;Lo;0;L;6D1B;;;;N;;;;; F916;CJK COMPATIBILITY IDEOGRAPH-F916;Lo;0;L;70D9;;;;N;;;;; F917;CJK COMPATIBILITY IDEOGRAPH-F917;Lo;0;L;73DE;;;;N;;;;; F918;CJK COMPATIBILITY IDEOGRAPH-F918;Lo;0;L;843D;;;;N;;;;; F919;CJK COMPATIBILITY IDEOGRAPH-F919;Lo;0;L;916A;;;;N;;;;; F91A;CJK COMPATIBILITY IDEOGRAPH-F91A;Lo;0;L;99F1;;;;N;;;;; F91B;CJK COMPATIBILITY IDEOGRAPH-F91B;Lo;0;L;4E82;;;;N;;;;; F91C;CJK COMPATIBILITY IDEOGRAPH-F91C;Lo;0;L;5375;;;;N;;;;; F91D;CJK COMPATIBILITY IDEOGRAPH-F91D;Lo;0;L;6B04;;;;N;;;;; F91E;CJK COMPATIBILITY IDEOGRAPH-F91E;Lo;0;L;721B;;;;N;;;;; F91F;CJK COMPATIBILITY IDEOGRAPH-F91F;Lo;0;L;862D;;;;N;;;;; F920;CJK COMPATIBILITY IDEOGRAPH-F920;Lo;0;L;9E1E;;;;N;;;;; F921;CJK COMPATIBILITY IDEOGRAPH-F921;Lo;0;L;5D50;;;;N;;;;; F922;CJK COMPATIBILITY IDEOGRAPH-F922;Lo;0;L;6FEB;;;;N;;;;; F923;CJK COMPATIBILITY IDEOGRAPH-F923;Lo;0;L;85CD;;;;N;;;;; F924;CJK COMPATIBILITY IDEOGRAPH-F924;Lo;0;L;8964;;;;N;;;;; F925;CJK COMPATIBILITY IDEOGRAPH-F925;Lo;0;L;62C9;;;;N;;;;; F926;CJK COMPATIBILITY IDEOGRAPH-F926;Lo;0;L;81D8;;;;N;;;;; F927;CJK COMPATIBILITY IDEOGRAPH-F927;Lo;0;L;881F;;;;N;;;;; F928;CJK COMPATIBILITY IDEOGRAPH-F928;Lo;0;L;5ECA;;;;N;;;;; F929;CJK COMPATIBILITY IDEOGRAPH-F929;Lo;0;L;6717;;;;N;;;;; F92A;CJK COMPATIBILITY IDEOGRAPH-F92A;Lo;0;L;6D6A;;;;N;;;;; F92B;CJK COMPATIBILITY IDEOGRAPH-F92B;Lo;0;L;72FC;;;;N;;;;; F92C;CJK COMPATIBILITY IDEOGRAPH-F92C;Lo;0;L;90CE;;;;N;;;;; F92D;CJK COMPATIBILITY IDEOGRAPH-F92D;Lo;0;L;4F86;;;;N;;;;; F92E;CJK COMPATIBILITY IDEOGRAPH-F92E;Lo;0;L;51B7;;;;N;;;;; F92F;CJK COMPATIBILITY IDEOGRAPH-F92F;Lo;0;L;52DE;;;;N;;;;; F930;CJK COMPATIBILITY IDEOGRAPH-F930;Lo;0;L;64C4;;;;N;;;;; F931;CJK COMPATIBILITY IDEOGRAPH-F931;Lo;0;L;6AD3;;;;N;;;;; F932;CJK COMPATIBILITY IDEOGRAPH-F932;Lo;0;L;7210;;;;N;;;;; F933;CJK COMPATIBILITY IDEOGRAPH-F933;Lo;0;L;76E7;;;;N;;;;; F934;CJK COMPATIBILITY IDEOGRAPH-F934;Lo;0;L;8001;;;;N;;;;; F935;CJK COMPATIBILITY IDEOGRAPH-F935;Lo;0;L;8606;;;;N;;;;; F936;CJK COMPATIBILITY IDEOGRAPH-F936;Lo;0;L;865C;;;;N;;;;; F937;CJK COMPATIBILITY IDEOGRAPH-F937;Lo;0;L;8DEF;;;;N;;;;; F938;CJK COMPATIBILITY IDEOGRAPH-F938;Lo;0;L;9732;;;;N;;;;; F939;CJK COMPATIBILITY IDEOGRAPH-F939;Lo;0;L;9B6F;;;;N;;;;; F93A;CJK COMPATIBILITY IDEOGRAPH-F93A;Lo;0;L;9DFA;;;;N;;;;; F93B;CJK COMPATIBILITY IDEOGRAPH-F93B;Lo;0;L;788C;;;;N;;;;; F93C;CJK COMPATIBILITY IDEOGRAPH-F93C;Lo;0;L;797F;;;;N;;;;; F93D;CJK COMPATIBILITY IDEOGRAPH-F93D;Lo;0;L;7DA0;;;;N;;;;; F93E;CJK COMPATIBILITY IDEOGRAPH-F93E;Lo;0;L;83C9;;;;N;;;;; F93F;CJK COMPATIBILITY IDEOGRAPH-F93F;Lo;0;L;9304;;;;N;;;;; F940;CJK COMPATIBILITY IDEOGRAPH-F940;Lo;0;L;9E7F;;;;N;;;;; F941;CJK COMPATIBILITY IDEOGRAPH-F941;Lo;0;L;8AD6;;;;N;;;;; F942;CJK COMPATIBILITY IDEOGRAPH-F942;Lo;0;L;58DF;;;;N;;;;; F943;CJK COMPATIBILITY IDEOGRAPH-F943;Lo;0;L;5F04;;;;N;;;;; F944;CJK COMPATIBILITY IDEOGRAPH-F944;Lo;0;L;7C60;;;;N;;;;; F945;CJK COMPATIBILITY IDEOGRAPH-F945;Lo;0;L;807E;;;;N;;;;; F946;CJK COMPATIBILITY IDEOGRAPH-F946;Lo;0;L;7262;;;;N;;;;; F947;CJK COMPATIBILITY IDEOGRAPH-F947;Lo;0;L;78CA;;;;N;;;;; F948;CJK COMPATIBILITY IDEOGRAPH-F948;Lo;0;L;8CC2;;;;N;;;;; F949;CJK COMPATIBILITY IDEOGRAPH-F949;Lo;0;L;96F7;;;;N;;;;; F94A;CJK COMPATIBILITY IDEOGRAPH-F94A;Lo;0;L;58D8;;;;N;;;;; F94B;CJK COMPATIBILITY IDEOGRAPH-F94B;Lo;0;L;5C62;;;;N;;;;; F94C;CJK COMPATIBILITY IDEOGRAPH-F94C;Lo;0;L;6A13;;;;N;;;;; F94D;CJK COMPATIBILITY IDEOGRAPH-F94D;Lo;0;L;6DDA;;;;N;;;;; F94E;CJK COMPATIBILITY IDEOGRAPH-F94E;Lo;0;L;6F0F;;;;N;;;;; F94F;CJK COMPATIBILITY IDEOGRAPH-F94F;Lo;0;L;7D2F;;;;N;;;;; F950;CJK COMPATIBILITY IDEOGRAPH-F950;Lo;0;L;7E37;;;;N;;;;; F951;CJK COMPATIBILITY IDEOGRAPH-F951;Lo;0;L;964B;;;;N;;;;; F952;CJK COMPATIBILITY IDEOGRAPH-F952;Lo;0;L;52D2;;;;N;;;;; F953;CJK COMPATIBILITY IDEOGRAPH-F953;Lo;0;L;808B;;;;N;;;;; F954;CJK COMPATIBILITY IDEOGRAPH-F954;Lo;0;L;51DC;;;;N;;;;; F955;CJK COMPATIBILITY IDEOGRAPH-F955;Lo;0;L;51CC;;;;N;;;;; F956;CJK COMPATIBILITY IDEOGRAPH-F956;Lo;0;L;7A1C;;;;N;;;;; F957;CJK COMPATIBILITY IDEOGRAPH-F957;Lo;0;L;7DBE;;;;N;;;;; F958;CJK COMPATIBILITY IDEOGRAPH-F958;Lo;0;L;83F1;;;;N;;;;; F959;CJK COMPATIBILITY IDEOGRAPH-F959;Lo;0;L;9675;;;;N;;;;; F95A;CJK COMPATIBILITY IDEOGRAPH-F95A;Lo;0;L;8B80;;;;N;;;;; F95B;CJK COMPATIBILITY IDEOGRAPH-F95B;Lo;0;L;62CF;;;;N;;;;; F95C;CJK COMPATIBILITY IDEOGRAPH-F95C;Lo;0;L;6A02;;;;N;;;;; F95D;CJK COMPATIBILITY IDEOGRAPH-F95D;Lo;0;L;8AFE;;;;N;;;;; F95E;CJK COMPATIBILITY IDEOGRAPH-F95E;Lo;0;L;4E39;;;;N;;;;; F95F;CJK COMPATIBILITY IDEOGRAPH-F95F;Lo;0;L;5BE7;;;;N;;;;; F960;CJK COMPATIBILITY IDEOGRAPH-F960;Lo;0;L;6012;;;;N;;;;; F961;CJK COMPATIBILITY IDEOGRAPH-F961;Lo;0;L;7387;;;;N;;;;; F962;CJK COMPATIBILITY IDEOGRAPH-F962;Lo;0;L;7570;;;;N;;;;; F963;CJK COMPATIBILITY IDEOGRAPH-F963;Lo;0;L;5317;;;;N;;;;; F964;CJK COMPATIBILITY IDEOGRAPH-F964;Lo;0;L;78FB;;;;N;;;;; F965;CJK COMPATIBILITY IDEOGRAPH-F965;Lo;0;L;4FBF;;;;N;;;;; F966;CJK COMPATIBILITY IDEOGRAPH-F966;Lo;0;L;5FA9;;;;N;;;;; F967;CJK COMPATIBILITY IDEOGRAPH-F967;Lo;0;L;4E0D;;;;N;;;;; F968;CJK COMPATIBILITY IDEOGRAPH-F968;Lo;0;L;6CCC;;;;N;;;;; F969;CJK COMPATIBILITY IDEOGRAPH-F969;Lo;0;L;6578;;;;N;;;;; F96A;CJK COMPATIBILITY IDEOGRAPH-F96A;Lo;0;L;7D22;;;;N;;;;; F96B;CJK COMPATIBILITY IDEOGRAPH-F96B;Lo;0;L;53C3;;;3;N;;;;; F96C;CJK COMPATIBILITY IDEOGRAPH-F96C;Lo;0;L;585E;;;;N;;;;; F96D;CJK COMPATIBILITY IDEOGRAPH-F96D;Lo;0;L;7701;;;;N;;;;; F96E;CJK COMPATIBILITY IDEOGRAPH-F96E;Lo;0;L;8449;;;;N;;;;; F96F;CJK COMPATIBILITY IDEOGRAPH-F96F;Lo;0;L;8AAA;;;;N;;;;; F970;CJK COMPATIBILITY IDEOGRAPH-F970;Lo;0;L;6BBA;;;;N;;;;; F971;CJK COMPATIBILITY IDEOGRAPH-F971;Lo;0;L;8FB0;;;;N;;;;; F972;CJK COMPATIBILITY IDEOGRAPH-F972;Lo;0;L;6C88;;;;N;;;;; F973;CJK COMPATIBILITY IDEOGRAPH-F973;Lo;0;L;62FE;;;10;N;;;;; F974;CJK COMPATIBILITY IDEOGRAPH-F974;Lo;0;L;82E5;;;;N;;;;; F975;CJK COMPATIBILITY IDEOGRAPH-F975;Lo;0;L;63A0;;;;N;;;;; F976;CJK COMPATIBILITY IDEOGRAPH-F976;Lo;0;L;7565;;;;N;;;;; F977;CJK COMPATIBILITY IDEOGRAPH-F977;Lo;0;L;4EAE;;;;N;;;;; F978;CJK COMPATIBILITY IDEOGRAPH-F978;Lo;0;L;5169;;;2;N;;;;; F979;CJK COMPATIBILITY IDEOGRAPH-F979;Lo;0;L;51C9;;;;N;;;;; F97A;CJK COMPATIBILITY IDEOGRAPH-F97A;Lo;0;L;6881;;;;N;;;;; F97B;CJK COMPATIBILITY IDEOGRAPH-F97B;Lo;0;L;7CE7;;;;N;;;;; F97C;CJK COMPATIBILITY IDEOGRAPH-F97C;Lo;0;L;826F;;;;N;;;;; F97D;CJK COMPATIBILITY IDEOGRAPH-F97D;Lo;0;L;8AD2;;;;N;;;;; F97E;CJK COMPATIBILITY IDEOGRAPH-F97E;Lo;0;L;91CF;;;;N;;;;; F97F;CJK COMPATIBILITY IDEOGRAPH-F97F;Lo;0;L;52F5;;;;N;;;;; F980;CJK COMPATIBILITY IDEOGRAPH-F980;Lo;0;L;5442;;;;N;;;;; F981;CJK COMPATIBILITY IDEOGRAPH-F981;Lo;0;L;5973;;;;N;;;;; F982;CJK COMPATIBILITY IDEOGRAPH-F982;Lo;0;L;5EEC;;;;N;;;;; F983;CJK COMPATIBILITY IDEOGRAPH-F983;Lo;0;L;65C5;;;;N;;;;; F984;CJK COMPATIBILITY IDEOGRAPH-F984;Lo;0;L;6FFE;;;;N;;;;; F985;CJK COMPATIBILITY IDEOGRAPH-F985;Lo;0;L;792A;;;;N;;;;; F986;CJK COMPATIBILITY IDEOGRAPH-F986;Lo;0;L;95AD;;;;N;;;;; F987;CJK COMPATIBILITY IDEOGRAPH-F987;Lo;0;L;9A6A;;;;N;;;;; F988;CJK COMPATIBILITY IDEOGRAPH-F988;Lo;0;L;9E97;;;;N;;;;; F989;CJK COMPATIBILITY IDEOGRAPH-F989;Lo;0;L;9ECE;;;;N;;;;; F98A;CJK COMPATIBILITY IDEOGRAPH-F98A;Lo;0;L;529B;;;;N;;;;; F98B;CJK COMPATIBILITY IDEOGRAPH-F98B;Lo;0;L;66C6;;;;N;;;;; F98C;CJK COMPATIBILITY IDEOGRAPH-F98C;Lo;0;L;6B77;;;;N;;;;; F98D;CJK COMPATIBILITY IDEOGRAPH-F98D;Lo;0;L;8F62;;;;N;;;;; F98E;CJK COMPATIBILITY IDEOGRAPH-F98E;Lo;0;L;5E74;;;;N;;;;; F98F;CJK COMPATIBILITY IDEOGRAPH-F98F;Lo;0;L;6190;;;;N;;;;; F990;CJK COMPATIBILITY IDEOGRAPH-F990;Lo;0;L;6200;;;;N;;;;; F991;CJK COMPATIBILITY IDEOGRAPH-F991;Lo;0;L;649A;;;;N;;;;; F992;CJK COMPATIBILITY IDEOGRAPH-F992;Lo;0;L;6F23;;;;N;;;;; F993;CJK COMPATIBILITY IDEOGRAPH-F993;Lo;0;L;7149;;;;N;;;;; F994;CJK COMPATIBILITY IDEOGRAPH-F994;Lo;0;L;7489;;;;N;;;;; F995;CJK COMPATIBILITY IDEOGRAPH-F995;Lo;0;L;79CA;;;;N;;;;; F996;CJK COMPATIBILITY IDEOGRAPH-F996;Lo;0;L;7DF4;;;;N;;;;; F997;CJK COMPATIBILITY IDEOGRAPH-F997;Lo;0;L;806F;;;;N;;;;; F998;CJK COMPATIBILITY IDEOGRAPH-F998;Lo;0;L;8F26;;;;N;;;;; F999;CJK COMPATIBILITY IDEOGRAPH-F999;Lo;0;L;84EE;;;;N;;;;; F99A;CJK COMPATIBILITY IDEOGRAPH-F99A;Lo;0;L;9023;;;;N;;;;; F99B;CJK COMPATIBILITY IDEOGRAPH-F99B;Lo;0;L;934A;;;;N;;;;; F99C;CJK COMPATIBILITY IDEOGRAPH-F99C;Lo;0;L;5217;;;;N;;;;; F99D;CJK COMPATIBILITY IDEOGRAPH-F99D;Lo;0;L;52A3;;;;N;;;;; F99E;CJK COMPATIBILITY IDEOGRAPH-F99E;Lo;0;L;54BD;;;;N;;;;; F99F;CJK COMPATIBILITY IDEOGRAPH-F99F;Lo;0;L;70C8;;;;N;;;;; F9A0;CJK COMPATIBILITY IDEOGRAPH-F9A0;Lo;0;L;88C2;;;;N;;;;; F9A1;CJK COMPATIBILITY IDEOGRAPH-F9A1;Lo;0;L;8AAA;;;;N;;;;; F9A2;CJK COMPATIBILITY IDEOGRAPH-F9A2;Lo;0;L;5EC9;;;;N;;;;; F9A3;CJK COMPATIBILITY IDEOGRAPH-F9A3;Lo;0;L;5FF5;;;;N;;;;; F9A4;CJK COMPATIBILITY IDEOGRAPH-F9A4;Lo;0;L;637B;;;;N;;;;; F9A5;CJK COMPATIBILITY IDEOGRAPH-F9A5;Lo;0;L;6BAE;;;;N;;;;; F9A6;CJK COMPATIBILITY IDEOGRAPH-F9A6;Lo;0;L;7C3E;;;;N;;;;; F9A7;CJK COMPATIBILITY IDEOGRAPH-F9A7;Lo;0;L;7375;;;;N;;;;; F9A8;CJK COMPATIBILITY IDEOGRAPH-F9A8;Lo;0;L;4EE4;;;;N;;;;; F9A9;CJK COMPATIBILITY IDEOGRAPH-F9A9;Lo;0;L;56F9;;;;N;;;;; F9AA;CJK COMPATIBILITY IDEOGRAPH-F9AA;Lo;0;L;5BE7;;;;N;;;;; F9AB;CJK COMPATIBILITY IDEOGRAPH-F9AB;Lo;0;L;5DBA;;;;N;;;;; F9AC;CJK COMPATIBILITY IDEOGRAPH-F9AC;Lo;0;L;601C;;;;N;;;;; F9AD;CJK COMPATIBILITY IDEOGRAPH-F9AD;Lo;0;L;73B2;;;;N;;;;; F9AE;CJK COMPATIBILITY IDEOGRAPH-F9AE;Lo;0;L;7469;;;;N;;;;; F9AF;CJK COMPATIBILITY IDEOGRAPH-F9AF;Lo;0;L;7F9A;;;;N;;;;; F9B0;CJK COMPATIBILITY IDEOGRAPH-F9B0;Lo;0;L;8046;;;;N;;;;; F9B1;CJK COMPATIBILITY IDEOGRAPH-F9B1;Lo;0;L;9234;;;;N;;;;; F9B2;CJK COMPATIBILITY IDEOGRAPH-F9B2;Lo;0;L;96F6;;;0;N;;;;; F9B3;CJK COMPATIBILITY IDEOGRAPH-F9B3;Lo;0;L;9748;;;;N;;;;; F9B4;CJK COMPATIBILITY IDEOGRAPH-F9B4;Lo;0;L;9818;;;;N;;;;; F9B5;CJK COMPATIBILITY IDEOGRAPH-F9B5;Lo;0;L;4F8B;;;;N;;;;; F9B6;CJK COMPATIBILITY IDEOGRAPH-F9B6;Lo;0;L;79AE;;;;N;;;;; F9B7;CJK COMPATIBILITY IDEOGRAPH-F9B7;Lo;0;L;91B4;;;;N;;;;; F9B8;CJK COMPATIBILITY IDEOGRAPH-F9B8;Lo;0;L;96B8;;;;N;;;;; F9B9;CJK COMPATIBILITY IDEOGRAPH-F9B9;Lo;0;L;60E1;;;;N;;;;; F9BA;CJK COMPATIBILITY IDEOGRAPH-F9BA;Lo;0;L;4E86;;;;N;;;;; F9BB;CJK COMPATIBILITY IDEOGRAPH-F9BB;Lo;0;L;50DA;;;;N;;;;; F9BC;CJK COMPATIBILITY IDEOGRAPH-F9BC;Lo;0;L;5BEE;;;;N;;;;; F9BD;CJK COMPATIBILITY IDEOGRAPH-F9BD;Lo;0;L;5C3F;;;;N;;;;; F9BE;CJK COMPATIBILITY IDEOGRAPH-F9BE;Lo;0;L;6599;;;;N;;;;; F9BF;CJK COMPATIBILITY IDEOGRAPH-F9BF;Lo;0;L;6A02;;;;N;;;;; F9C0;CJK COMPATIBILITY IDEOGRAPH-F9C0;Lo;0;L;71CE;;;;N;;;;; F9C1;CJK COMPATIBILITY IDEOGRAPH-F9C1;Lo;0;L;7642;;;;N;;;;; F9C2;CJK COMPATIBILITY IDEOGRAPH-F9C2;Lo;0;L;84FC;;;;N;;;;; F9C3;CJK COMPATIBILITY IDEOGRAPH-F9C3;Lo;0;L;907C;;;;N;;;;; F9C4;CJK COMPATIBILITY IDEOGRAPH-F9C4;Lo;0;L;9F8D;;;;N;;;;; F9C5;CJK COMPATIBILITY IDEOGRAPH-F9C5;Lo;0;L;6688;;;;N;;;;; F9C6;CJK COMPATIBILITY IDEOGRAPH-F9C6;Lo;0;L;962E;;;;N;;;;; F9C7;CJK COMPATIBILITY IDEOGRAPH-F9C7;Lo;0;L;5289;;;;N;;;;; F9C8;CJK COMPATIBILITY IDEOGRAPH-F9C8;Lo;0;L;677B;;;;N;;;;; F9C9;CJK COMPATIBILITY IDEOGRAPH-F9C9;Lo;0;L;67F3;;;;N;;;;; F9CA;CJK COMPATIBILITY IDEOGRAPH-F9CA;Lo;0;L;6D41;;;;N;;;;; F9CB;CJK COMPATIBILITY IDEOGRAPH-F9CB;Lo;0;L;6E9C;;;;N;;;;; F9CC;CJK COMPATIBILITY IDEOGRAPH-F9CC;Lo;0;L;7409;;;;N;;;;; F9CD;CJK COMPATIBILITY IDEOGRAPH-F9CD;Lo;0;L;7559;;;;N;;;;; F9CE;CJK COMPATIBILITY IDEOGRAPH-F9CE;Lo;0;L;786B;;;;N;;;;; F9CF;CJK COMPATIBILITY IDEOGRAPH-F9CF;Lo;0;L;7D10;;;;N;;;;; F9D0;CJK COMPATIBILITY IDEOGRAPH-F9D0;Lo;0;L;985E;;;;N;;;;; F9D1;CJK COMPATIBILITY IDEOGRAPH-F9D1;Lo;0;L;516D;;;6;N;;;;; F9D2;CJK COMPATIBILITY IDEOGRAPH-F9D2;Lo;0;L;622E;;;;N;;;;; F9D3;CJK COMPATIBILITY IDEOGRAPH-F9D3;Lo;0;L;9678;;;6;N;;;;; F9D4;CJK COMPATIBILITY IDEOGRAPH-F9D4;Lo;0;L;502B;;;;N;;;;; F9D5;CJK COMPATIBILITY IDEOGRAPH-F9D5;Lo;0;L;5D19;;;;N;;;;; F9D6;CJK COMPATIBILITY IDEOGRAPH-F9D6;Lo;0;L;6DEA;;;;N;;;;; F9D7;CJK COMPATIBILITY IDEOGRAPH-F9D7;Lo;0;L;8F2A;;;;N;;;;; F9D8;CJK COMPATIBILITY IDEOGRAPH-F9D8;Lo;0;L;5F8B;;;;N;;;;; F9D9;CJK COMPATIBILITY IDEOGRAPH-F9D9;Lo;0;L;6144;;;;N;;;;; F9DA;CJK COMPATIBILITY IDEOGRAPH-F9DA;Lo;0;L;6817;;;;N;;;;; F9DB;CJK COMPATIBILITY IDEOGRAPH-F9DB;Lo;0;L;7387;;;;N;;;;; F9DC;CJK COMPATIBILITY IDEOGRAPH-F9DC;Lo;0;L;9686;;;;N;;;;; F9DD;CJK COMPATIBILITY IDEOGRAPH-F9DD;Lo;0;L;5229;;;;N;;;;; F9DE;CJK COMPATIBILITY IDEOGRAPH-F9DE;Lo;0;L;540F;;;;N;;;;; F9DF;CJK COMPATIBILITY IDEOGRAPH-F9DF;Lo;0;L;5C65;;;;N;;;;; F9E0;CJK COMPATIBILITY IDEOGRAPH-F9E0;Lo;0;L;6613;;;;N;;;;; F9E1;CJK COMPATIBILITY IDEOGRAPH-F9E1;Lo;0;L;674E;;;;N;;;;; F9E2;CJK COMPATIBILITY IDEOGRAPH-F9E2;Lo;0;L;68A8;;;;N;;;;; F9E3;CJK COMPATIBILITY IDEOGRAPH-F9E3;Lo;0;L;6CE5;;;;N;;;;; F9E4;CJK COMPATIBILITY IDEOGRAPH-F9E4;Lo;0;L;7406;;;;N;;;;; F9E5;CJK COMPATIBILITY IDEOGRAPH-F9E5;Lo;0;L;75E2;;;;N;;;;; F9E6;CJK COMPATIBILITY IDEOGRAPH-F9E6;Lo;0;L;7F79;;;;N;;;;; F9E7;CJK COMPATIBILITY IDEOGRAPH-F9E7;Lo;0;L;88CF;;;;N;;;;; F9E8;CJK COMPATIBILITY IDEOGRAPH-F9E8;Lo;0;L;88E1;;;;N;;;;; F9E9;CJK COMPATIBILITY IDEOGRAPH-F9E9;Lo;0;L;91CC;;;;N;;;;; F9EA;CJK COMPATIBILITY IDEOGRAPH-F9EA;Lo;0;L;96E2;;;;N;;;;; F9EB;CJK COMPATIBILITY IDEOGRAPH-F9EB;Lo;0;L;533F;;;;N;;;;; F9EC;CJK COMPATIBILITY IDEOGRAPH-F9EC;Lo;0;L;6EBA;;;;N;;;;; F9ED;CJK COMPATIBILITY IDEOGRAPH-F9ED;Lo;0;L;541D;;;;N;;;;; F9EE;CJK COMPATIBILITY IDEOGRAPH-F9EE;Lo;0;L;71D0;;;;N;;;;; F9EF;CJK COMPATIBILITY IDEOGRAPH-F9EF;Lo;0;L;7498;;;;N;;;;; F9F0;CJK COMPATIBILITY IDEOGRAPH-F9F0;Lo;0;L;85FA;;;;N;;;;; F9F1;CJK COMPATIBILITY IDEOGRAPH-F9F1;Lo;0;L;96A3;;;;N;;;;; F9F2;CJK COMPATIBILITY IDEOGRAPH-F9F2;Lo;0;L;9C57;;;;N;;;;; F9F3;CJK COMPATIBILITY IDEOGRAPH-F9F3;Lo;0;L;9E9F;;;;N;;;;; F9F4;CJK COMPATIBILITY IDEOGRAPH-F9F4;Lo;0;L;6797;;;;N;;;;; F9F5;CJK COMPATIBILITY IDEOGRAPH-F9F5;Lo;0;L;6DCB;;;;N;;;;; F9F6;CJK COMPATIBILITY IDEOGRAPH-F9F6;Lo;0;L;81E8;;;;N;;;;; F9F7;CJK COMPATIBILITY IDEOGRAPH-F9F7;Lo;0;L;7ACB;;;;N;;;;; F9F8;CJK COMPATIBILITY IDEOGRAPH-F9F8;Lo;0;L;7B20;;;;N;;;;; F9F9;CJK COMPATIBILITY IDEOGRAPH-F9F9;Lo;0;L;7C92;;;;N;;;;; F9FA;CJK COMPATIBILITY IDEOGRAPH-F9FA;Lo;0;L;72C0;;;;N;;;;; F9FB;CJK COMPATIBILITY IDEOGRAPH-F9FB;Lo;0;L;7099;;;;N;;;;; F9FC;CJK COMPATIBILITY IDEOGRAPH-F9FC;Lo;0;L;8B58;;;;N;;;;; F9FD;CJK COMPATIBILITY IDEOGRAPH-F9FD;Lo;0;L;4EC0;;;10;N;;;;; F9FE;CJK COMPATIBILITY IDEOGRAPH-F9FE;Lo;0;L;8336;;;;N;;;;; F9FF;CJK COMPATIBILITY IDEOGRAPH-F9FF;Lo;0;L;523A;;;;N;;;;; FA00;CJK COMPATIBILITY IDEOGRAPH-FA00;Lo;0;L;5207;;;;N;;;;; FA01;CJK COMPATIBILITY IDEOGRAPH-FA01;Lo;0;L;5EA6;;;;N;;;;; FA02;CJK COMPATIBILITY IDEOGRAPH-FA02;Lo;0;L;62D3;;;;N;;;;; FA03;CJK COMPATIBILITY IDEOGRAPH-FA03;Lo;0;L;7CD6;;;;N;;;;; FA04;CJK COMPATIBILITY IDEOGRAPH-FA04;Lo;0;L;5B85;;;;N;;;;; FA05;CJK COMPATIBILITY IDEOGRAPH-FA05;Lo;0;L;6D1E;;;;N;;;;; FA06;CJK COMPATIBILITY IDEOGRAPH-FA06;Lo;0;L;66B4;;;;N;;;;; FA07;CJK COMPATIBILITY IDEOGRAPH-FA07;Lo;0;L;8F3B;;;;N;;;;; FA08;CJK COMPATIBILITY IDEOGRAPH-FA08;Lo;0;L;884C;;;;N;;;;; FA09;CJK COMPATIBILITY IDEOGRAPH-FA09;Lo;0;L;964D;;;;N;;;;; FA0A;CJK COMPATIBILITY IDEOGRAPH-FA0A;Lo;0;L;898B;;;;N;;;;; FA0B;CJK COMPATIBILITY IDEOGRAPH-FA0B;Lo;0;L;5ED3;;;;N;;;;; FA0C;CJK COMPATIBILITY IDEOGRAPH-FA0C;Lo;0;L;5140;;;;N;;;;; FA0D;CJK COMPATIBILITY IDEOGRAPH-FA0D;Lo;0;L;55C0;;;;N;;;;; FA0E;CJK COMPATIBILITY IDEOGRAPH-FA0E;Lo;0;L;;;;;N;;;;; FA0F;CJK COMPATIBILITY IDEOGRAPH-FA0F;Lo;0;L;;;;;N;;;;; FA10;CJK COMPATIBILITY IDEOGRAPH-FA10;Lo;0;L;585A;;;;N;;;;; FA11;CJK COMPATIBILITY IDEOGRAPH-FA11;Lo;0;L;;;;;N;;;;; FA12;CJK COMPATIBILITY IDEOGRAPH-FA12;Lo;0;L;6674;;;;N;;;;; FA13;CJK COMPATIBILITY IDEOGRAPH-FA13;Lo;0;L;;;;;N;;;;; FA14;CJK COMPATIBILITY IDEOGRAPH-FA14;Lo;0;L;;;;;N;;;;; FA15;CJK COMPATIBILITY IDEOGRAPH-FA15;Lo;0;L;51DE;;;;N;;;;; FA16;CJK COMPATIBILITY IDEOGRAPH-FA16;Lo;0;L;732A;;;;N;;;;; FA17;CJK COMPATIBILITY IDEOGRAPH-FA17;Lo;0;L;76CA;;;;N;;;;; FA18;CJK COMPATIBILITY IDEOGRAPH-FA18;Lo;0;L;793C;;;;N;;;;; FA19;CJK COMPATIBILITY IDEOGRAPH-FA19;Lo;0;L;795E;;;;N;;;;; FA1A;CJK COMPATIBILITY IDEOGRAPH-FA1A;Lo;0;L;7965;;;;N;;;;; FA1B;CJK COMPATIBILITY IDEOGRAPH-FA1B;Lo;0;L;798F;;;;N;;;;; FA1C;CJK COMPATIBILITY IDEOGRAPH-FA1C;Lo;0;L;9756;;;;N;;;;; FA1D;CJK COMPATIBILITY IDEOGRAPH-FA1D;Lo;0;L;7CBE;;;;N;;;;; FA1E;CJK COMPATIBILITY IDEOGRAPH-FA1E;Lo;0;L;7FBD;;;;N;;;;; FA1F;CJK COMPATIBILITY IDEOGRAPH-FA1F;Lo;0;L;;;;;N;;;;; FA20;CJK COMPATIBILITY IDEOGRAPH-FA20;Lo;0;L;8612;;;;N;;;;; FA21;CJK COMPATIBILITY IDEOGRAPH-FA21;Lo;0;L;;;;;N;;;;; FA22;CJK COMPATIBILITY IDEOGRAPH-FA22;Lo;0;L;8AF8;;;;N;;;;; FA23;CJK COMPATIBILITY IDEOGRAPH-FA23;Lo;0;L;;;;;N;;;;; FA24;CJK COMPATIBILITY IDEOGRAPH-FA24;Lo;0;L;;;;;N;;;;; FA25;CJK COMPATIBILITY IDEOGRAPH-FA25;Lo;0;L;9038;;;;N;;;;; FA26;CJK COMPATIBILITY IDEOGRAPH-FA26;Lo;0;L;90FD;;;;N;;;;; FA27;CJK COMPATIBILITY IDEOGRAPH-FA27;Lo;0;L;;;;;N;;;;; FA28;CJK COMPATIBILITY IDEOGRAPH-FA28;Lo;0;L;;;;;N;;;;; FA29;CJK COMPATIBILITY IDEOGRAPH-FA29;Lo;0;L;;;;;N;;;;; FA2A;CJK COMPATIBILITY IDEOGRAPH-FA2A;Lo;0;L;98EF;;;;N;;;;; FA2B;CJK COMPATIBILITY IDEOGRAPH-FA2B;Lo;0;L;98FC;;;;N;;;;; FA2C;CJK COMPATIBILITY IDEOGRAPH-FA2C;Lo;0;L;9928;;;;N;;;;; FA2D;CJK COMPATIBILITY IDEOGRAPH-FA2D;Lo;0;L;9DB4;;;;N;;;;; FA2E;CJK COMPATIBILITY IDEOGRAPH-FA2E;Lo;0;L;90DE;;;;N;;;;; FA2F;CJK COMPATIBILITY IDEOGRAPH-FA2F;Lo;0;L;96B7;;;;N;;;;; FA30;CJK COMPATIBILITY IDEOGRAPH-FA30;Lo;0;L;4FAE;;;;N;;;;; FA31;CJK COMPATIBILITY IDEOGRAPH-FA31;Lo;0;L;50E7;;;;N;;;;; FA32;CJK COMPATIBILITY IDEOGRAPH-FA32;Lo;0;L;514D;;;;N;;;;; FA33;CJK COMPATIBILITY IDEOGRAPH-FA33;Lo;0;L;52C9;;;;N;;;;; FA34;CJK COMPATIBILITY IDEOGRAPH-FA34;Lo;0;L;52E4;;;;N;;;;; FA35;CJK COMPATIBILITY IDEOGRAPH-FA35;Lo;0;L;5351;;;;N;;;;; FA36;CJK COMPATIBILITY IDEOGRAPH-FA36;Lo;0;L;559D;;;;N;;;;; FA37;CJK COMPATIBILITY IDEOGRAPH-FA37;Lo;0;L;5606;;;;N;;;;; FA38;CJK COMPATIBILITY IDEOGRAPH-FA38;Lo;0;L;5668;;;;N;;;;; FA39;CJK COMPATIBILITY IDEOGRAPH-FA39;Lo;0;L;5840;;;;N;;;;; FA3A;CJK COMPATIBILITY IDEOGRAPH-FA3A;Lo;0;L;58A8;;;;N;;;;; FA3B;CJK COMPATIBILITY IDEOGRAPH-FA3B;Lo;0;L;5C64;;;;N;;;;; FA3C;CJK COMPATIBILITY IDEOGRAPH-FA3C;Lo;0;L;5C6E;;;;N;;;;; FA3D;CJK COMPATIBILITY IDEOGRAPH-FA3D;Lo;0;L;6094;;;;N;;;;; FA3E;CJK COMPATIBILITY IDEOGRAPH-FA3E;Lo;0;L;6168;;;;N;;;;; FA3F;CJK COMPATIBILITY IDEOGRAPH-FA3F;Lo;0;L;618E;;;;N;;;;; FA40;CJK COMPATIBILITY IDEOGRAPH-FA40;Lo;0;L;61F2;;;;N;;;;; FA41;CJK COMPATIBILITY IDEOGRAPH-FA41;Lo;0;L;654F;;;;N;;;;; FA42;CJK COMPATIBILITY IDEOGRAPH-FA42;Lo;0;L;65E2;;;;N;;;;; FA43;CJK COMPATIBILITY IDEOGRAPH-FA43;Lo;0;L;6691;;;;N;;;;; FA44;CJK COMPATIBILITY IDEOGRAPH-FA44;Lo;0;L;6885;;;;N;;;;; FA45;CJK COMPATIBILITY IDEOGRAPH-FA45;Lo;0;L;6D77;;;;N;;;;; FA46;CJK COMPATIBILITY IDEOGRAPH-FA46;Lo;0;L;6E1A;;;;N;;;;; FA47;CJK COMPATIBILITY IDEOGRAPH-FA47;Lo;0;L;6F22;;;;N;;;;; FA48;CJK COMPATIBILITY IDEOGRAPH-FA48;Lo;0;L;716E;;;;N;;;;; FA49;CJK COMPATIBILITY IDEOGRAPH-FA49;Lo;0;L;722B;;;;N;;;;; FA4A;CJK COMPATIBILITY IDEOGRAPH-FA4A;Lo;0;L;7422;;;;N;;;;; FA4B;CJK COMPATIBILITY IDEOGRAPH-FA4B;Lo;0;L;7891;;;;N;;;;; FA4C;CJK COMPATIBILITY IDEOGRAPH-FA4C;Lo;0;L;793E;;;;N;;;;; FA4D;CJK COMPATIBILITY IDEOGRAPH-FA4D;Lo;0;L;7949;;;;N;;;;; FA4E;CJK COMPATIBILITY IDEOGRAPH-FA4E;Lo;0;L;7948;;;;N;;;;; FA4F;CJK COMPATIBILITY IDEOGRAPH-FA4F;Lo;0;L;7950;;;;N;;;;; FA50;CJK COMPATIBILITY IDEOGRAPH-FA50;Lo;0;L;7956;;;;N;;;;; FA51;CJK COMPATIBILITY IDEOGRAPH-FA51;Lo;0;L;795D;;;;N;;;;; FA52;CJK COMPATIBILITY IDEOGRAPH-FA52;Lo;0;L;798D;;;;N;;;;; FA53;CJK COMPATIBILITY IDEOGRAPH-FA53;Lo;0;L;798E;;;;N;;;;; FA54;CJK COMPATIBILITY IDEOGRAPH-FA54;Lo;0;L;7A40;;;;N;;;;; FA55;CJK COMPATIBILITY IDEOGRAPH-FA55;Lo;0;L;7A81;;;;N;;;;; FA56;CJK COMPATIBILITY IDEOGRAPH-FA56;Lo;0;L;7BC0;;;;N;;;;; FA57;CJK COMPATIBILITY IDEOGRAPH-FA57;Lo;0;L;7DF4;;;;N;;;;; FA58;CJK COMPATIBILITY IDEOGRAPH-FA58;Lo;0;L;7E09;;;;N;;;;; FA59;CJK COMPATIBILITY IDEOGRAPH-FA59;Lo;0;L;7E41;;;;N;;;;; FA5A;CJK COMPATIBILITY IDEOGRAPH-FA5A;Lo;0;L;7F72;;;;N;;;;; FA5B;CJK COMPATIBILITY IDEOGRAPH-FA5B;Lo;0;L;8005;;;;N;;;;; FA5C;CJK COMPATIBILITY IDEOGRAPH-FA5C;Lo;0;L;81ED;;;;N;;;;; FA5D;CJK COMPATIBILITY IDEOGRAPH-FA5D;Lo;0;L;8279;;;;N;;;;; FA5E;CJK COMPATIBILITY IDEOGRAPH-FA5E;Lo;0;L;8279;;;;N;;;;; FA5F;CJK COMPATIBILITY IDEOGRAPH-FA5F;Lo;0;L;8457;;;;N;;;;; FA60;CJK COMPATIBILITY IDEOGRAPH-FA60;Lo;0;L;8910;;;;N;;;;; FA61;CJK COMPATIBILITY IDEOGRAPH-FA61;Lo;0;L;8996;;;;N;;;;; FA62;CJK COMPATIBILITY IDEOGRAPH-FA62;Lo;0;L;8B01;;;;N;;;;; FA63;CJK COMPATIBILITY IDEOGRAPH-FA63;Lo;0;L;8B39;;;;N;;;;; FA64;CJK COMPATIBILITY IDEOGRAPH-FA64;Lo;0;L;8CD3;;;;N;;;;; FA65;CJK COMPATIBILITY IDEOGRAPH-FA65;Lo;0;L;8D08;;;;N;;;;; FA66;CJK COMPATIBILITY IDEOGRAPH-FA66;Lo;0;L;8FB6;;;;N;;;;; FA67;CJK COMPATIBILITY IDEOGRAPH-FA67;Lo;0;L;9038;;;;N;;;;; FA68;CJK COMPATIBILITY IDEOGRAPH-FA68;Lo;0;L;96E3;;;;N;;;;; FA69;CJK COMPATIBILITY IDEOGRAPH-FA69;Lo;0;L;97FF;;;;N;;;;; FA6A;CJK COMPATIBILITY IDEOGRAPH-FA6A;Lo;0;L;983B;;;;N;;;;; FA6B;CJK COMPATIBILITY IDEOGRAPH-FA6B;Lo;0;L;6075;;;;N;;;;; FA6C;CJK COMPATIBILITY IDEOGRAPH-FA6C;Lo;0;L;242EE;;;;N;;;;; FA6D;CJK COMPATIBILITY IDEOGRAPH-FA6D;Lo;0;L;8218;;;;N;;;;; FA70;CJK COMPATIBILITY IDEOGRAPH-FA70;Lo;0;L;4E26;;;;N;;;;; FA71;CJK COMPATIBILITY IDEOGRAPH-FA71;Lo;0;L;51B5;;;;N;;;;; FA72;CJK COMPATIBILITY IDEOGRAPH-FA72;Lo;0;L;5168;;;;N;;;;; FA73;CJK COMPATIBILITY IDEOGRAPH-FA73;Lo;0;L;4F80;;;;N;;;;; FA74;CJK COMPATIBILITY IDEOGRAPH-FA74;Lo;0;L;5145;;;;N;;;;; FA75;CJK COMPATIBILITY IDEOGRAPH-FA75;Lo;0;L;5180;;;;N;;;;; FA76;CJK COMPATIBILITY IDEOGRAPH-FA76;Lo;0;L;52C7;;;;N;;;;; FA77;CJK COMPATIBILITY IDEOGRAPH-FA77;Lo;0;L;52FA;;;;N;;;;; FA78;CJK COMPATIBILITY IDEOGRAPH-FA78;Lo;0;L;559D;;;;N;;;;; FA79;CJK COMPATIBILITY IDEOGRAPH-FA79;Lo;0;L;5555;;;;N;;;;; FA7A;CJK COMPATIBILITY IDEOGRAPH-FA7A;Lo;0;L;5599;;;;N;;;;; FA7B;CJK COMPATIBILITY IDEOGRAPH-FA7B;Lo;0;L;55E2;;;;N;;;;; FA7C;CJK COMPATIBILITY IDEOGRAPH-FA7C;Lo;0;L;585A;;;;N;;;;; FA7D;CJK COMPATIBILITY IDEOGRAPH-FA7D;Lo;0;L;58B3;;;;N;;;;; FA7E;CJK COMPATIBILITY IDEOGRAPH-FA7E;Lo;0;L;5944;;;;N;;;;; FA7F;CJK COMPATIBILITY IDEOGRAPH-FA7F;Lo;0;L;5954;;;;N;;;;; FA80;CJK COMPATIBILITY IDEOGRAPH-FA80;Lo;0;L;5A62;;;;N;;;;; FA81;CJK COMPATIBILITY IDEOGRAPH-FA81;Lo;0;L;5B28;;;;N;;;;; FA82;CJK COMPATIBILITY IDEOGRAPH-FA82;Lo;0;L;5ED2;;;;N;;;;; FA83;CJK COMPATIBILITY IDEOGRAPH-FA83;Lo;0;L;5ED9;;;;N;;;;; FA84;CJK COMPATIBILITY IDEOGRAPH-FA84;Lo;0;L;5F69;;;;N;;;;; FA85;CJK COMPATIBILITY IDEOGRAPH-FA85;Lo;0;L;5FAD;;;;N;;;;; FA86;CJK COMPATIBILITY IDEOGRAPH-FA86;Lo;0;L;60D8;;;;N;;;;; FA87;CJK COMPATIBILITY IDEOGRAPH-FA87;Lo;0;L;614E;;;;N;;;;; FA88;CJK COMPATIBILITY IDEOGRAPH-FA88;Lo;0;L;6108;;;;N;;;;; FA89;CJK COMPATIBILITY IDEOGRAPH-FA89;Lo;0;L;618E;;;;N;;;;; FA8A;CJK COMPATIBILITY IDEOGRAPH-FA8A;Lo;0;L;6160;;;;N;;;;; FA8B;CJK COMPATIBILITY IDEOGRAPH-FA8B;Lo;0;L;61F2;;;;N;;;;; FA8C;CJK COMPATIBILITY IDEOGRAPH-FA8C;Lo;0;L;6234;;;;N;;;;; FA8D;CJK COMPATIBILITY IDEOGRAPH-FA8D;Lo;0;L;63C4;;;;N;;;;; FA8E;CJK COMPATIBILITY IDEOGRAPH-FA8E;Lo;0;L;641C;;;;N;;;;; FA8F;CJK COMPATIBILITY IDEOGRAPH-FA8F;Lo;0;L;6452;;;;N;;;;; FA90;CJK COMPATIBILITY IDEOGRAPH-FA90;Lo;0;L;6556;;;;N;;;;; FA91;CJK COMPATIBILITY IDEOGRAPH-FA91;Lo;0;L;6674;;;;N;;;;; FA92;CJK COMPATIBILITY IDEOGRAPH-FA92;Lo;0;L;6717;;;;N;;;;; FA93;CJK COMPATIBILITY IDEOGRAPH-FA93;Lo;0;L;671B;;;;N;;;;; FA94;CJK COMPATIBILITY IDEOGRAPH-FA94;Lo;0;L;6756;;;;N;;;;; FA95;CJK COMPATIBILITY IDEOGRAPH-FA95;Lo;0;L;6B79;;;;N;;;;; FA96;CJK COMPATIBILITY IDEOGRAPH-FA96;Lo;0;L;6BBA;;;;N;;;;; FA97;CJK COMPATIBILITY IDEOGRAPH-FA97;Lo;0;L;6D41;;;;N;;;;; FA98;CJK COMPATIBILITY IDEOGRAPH-FA98;Lo;0;L;6EDB;;;;N;;;;; FA99;CJK COMPATIBILITY IDEOGRAPH-FA99;Lo;0;L;6ECB;;;;N;;;;; FA9A;CJK COMPATIBILITY IDEOGRAPH-FA9A;Lo;0;L;6F22;;;;N;;;;; FA9B;CJK COMPATIBILITY IDEOGRAPH-FA9B;Lo;0;L;701E;;;;N;;;;; FA9C;CJK COMPATIBILITY IDEOGRAPH-FA9C;Lo;0;L;716E;;;;N;;;;; FA9D;CJK COMPATIBILITY IDEOGRAPH-FA9D;Lo;0;L;77A7;;;;N;;;;; FA9E;CJK COMPATIBILITY IDEOGRAPH-FA9E;Lo;0;L;7235;;;;N;;;;; FA9F;CJK COMPATIBILITY IDEOGRAPH-FA9F;Lo;0;L;72AF;;;;N;;;;; FAA0;CJK COMPATIBILITY IDEOGRAPH-FAA0;Lo;0;L;732A;;;;N;;;;; FAA1;CJK COMPATIBILITY IDEOGRAPH-FAA1;Lo;0;L;7471;;;;N;;;;; FAA2;CJK COMPATIBILITY IDEOGRAPH-FAA2;Lo;0;L;7506;;;;N;;;;; FAA3;CJK COMPATIBILITY IDEOGRAPH-FAA3;Lo;0;L;753B;;;;N;;;;; FAA4;CJK COMPATIBILITY IDEOGRAPH-FAA4;Lo;0;L;761D;;;;N;;;;; FAA5;CJK COMPATIBILITY IDEOGRAPH-FAA5;Lo;0;L;761F;;;;N;;;;; FAA6;CJK COMPATIBILITY IDEOGRAPH-FAA6;Lo;0;L;76CA;;;;N;;;;; FAA7;CJK COMPATIBILITY IDEOGRAPH-FAA7;Lo;0;L;76DB;;;;N;;;;; FAA8;CJK COMPATIBILITY IDEOGRAPH-FAA8;Lo;0;L;76F4;;;;N;;;;; FAA9;CJK COMPATIBILITY IDEOGRAPH-FAA9;Lo;0;L;774A;;;;N;;;;; FAAA;CJK COMPATIBILITY IDEOGRAPH-FAAA;Lo;0;L;7740;;;;N;;;;; FAAB;CJK COMPATIBILITY IDEOGRAPH-FAAB;Lo;0;L;78CC;;;;N;;;;; FAAC;CJK COMPATIBILITY IDEOGRAPH-FAAC;Lo;0;L;7AB1;;;;N;;;;; FAAD;CJK COMPATIBILITY IDEOGRAPH-FAAD;Lo;0;L;7BC0;;;;N;;;;; FAAE;CJK COMPATIBILITY IDEOGRAPH-FAAE;Lo;0;L;7C7B;;;;N;;;;; FAAF;CJK COMPATIBILITY IDEOGRAPH-FAAF;Lo;0;L;7D5B;;;;N;;;;; FAB0;CJK COMPATIBILITY IDEOGRAPH-FAB0;Lo;0;L;7DF4;;;;N;;;;; FAB1;CJK COMPATIBILITY IDEOGRAPH-FAB1;Lo;0;L;7F3E;;;;N;;;;; FAB2;CJK COMPATIBILITY IDEOGRAPH-FAB2;Lo;0;L;8005;;;;N;;;;; FAB3;CJK COMPATIBILITY IDEOGRAPH-FAB3;Lo;0;L;8352;;;;N;;;;; FAB4;CJK COMPATIBILITY IDEOGRAPH-FAB4;Lo;0;L;83EF;;;;N;;;;; FAB5;CJK COMPATIBILITY IDEOGRAPH-FAB5;Lo;0;L;8779;;;;N;;;;; FAB6;CJK COMPATIBILITY IDEOGRAPH-FAB6;Lo;0;L;8941;;;;N;;;;; FAB7;CJK COMPATIBILITY IDEOGRAPH-FAB7;Lo;0;L;8986;;;;N;;;;; FAB8;CJK COMPATIBILITY IDEOGRAPH-FAB8;Lo;0;L;8996;;;;N;;;;; FAB9;CJK COMPATIBILITY IDEOGRAPH-FAB9;Lo;0;L;8ABF;;;;N;;;;; FABA;CJK COMPATIBILITY IDEOGRAPH-FABA;Lo;0;L;8AF8;;;;N;;;;; FABB;CJK COMPATIBILITY IDEOGRAPH-FABB;Lo;0;L;8ACB;;;;N;;;;; FABC;CJK COMPATIBILITY IDEOGRAPH-FABC;Lo;0;L;8B01;;;;N;;;;; FABD;CJK COMPATIBILITY IDEOGRAPH-FABD;Lo;0;L;8AFE;;;;N;;;;; FABE;CJK COMPATIBILITY IDEOGRAPH-FABE;Lo;0;L;8AED;;;;N;;;;; FABF;CJK COMPATIBILITY IDEOGRAPH-FABF;Lo;0;L;8B39;;;;N;;;;; FAC0;CJK COMPATIBILITY IDEOGRAPH-FAC0;Lo;0;L;8B8A;;;;N;;;;; FAC1;CJK COMPATIBILITY IDEOGRAPH-FAC1;Lo;0;L;8D08;;;;N;;;;; FAC2;CJK COMPATIBILITY IDEOGRAPH-FAC2;Lo;0;L;8F38;;;;N;;;;; FAC3;CJK COMPATIBILITY IDEOGRAPH-FAC3;Lo;0;L;9072;;;;N;;;;; FAC4;CJK COMPATIBILITY IDEOGRAPH-FAC4;Lo;0;L;9199;;;;N;;;;; FAC5;CJK COMPATIBILITY IDEOGRAPH-FAC5;Lo;0;L;9276;;;;N;;;;; FAC6;CJK COMPATIBILITY IDEOGRAPH-FAC6;Lo;0;L;967C;;;;N;;;;; FAC7;CJK COMPATIBILITY IDEOGRAPH-FAC7;Lo;0;L;96E3;;;;N;;;;; FAC8;CJK COMPATIBILITY IDEOGRAPH-FAC8;Lo;0;L;9756;;;;N;;;;; FAC9;CJK COMPATIBILITY IDEOGRAPH-FAC9;Lo;0;L;97DB;;;;N;;;;; FACA;CJK COMPATIBILITY IDEOGRAPH-FACA;Lo;0;L;97FF;;;;N;;;;; FACB;CJK COMPATIBILITY IDEOGRAPH-FACB;Lo;0;L;980B;;;;N;;;;; FACC;CJK COMPATIBILITY IDEOGRAPH-FACC;Lo;0;L;983B;;;;N;;;;; FACD;CJK COMPATIBILITY IDEOGRAPH-FACD;Lo;0;L;9B12;;;;N;;;;; FACE;CJK COMPATIBILITY IDEOGRAPH-FACE;Lo;0;L;9F9C;;;;N;;;;; FACF;CJK COMPATIBILITY IDEOGRAPH-FACF;Lo;0;L;2284A;;;;N;;;;; FAD0;CJK COMPATIBILITY IDEOGRAPH-FAD0;Lo;0;L;22844;;;;N;;;;; FAD1;CJK COMPATIBILITY IDEOGRAPH-FAD1;Lo;0;L;233D5;;;;N;;;;; FAD2;CJK COMPATIBILITY IDEOGRAPH-FAD2;Lo;0;L;3B9D;;;;N;;;;; FAD3;CJK COMPATIBILITY IDEOGRAPH-FAD3;Lo;0;L;4018;;;;N;;;;; FAD4;CJK COMPATIBILITY IDEOGRAPH-FAD4;Lo;0;L;4039;;;;N;;;;; FAD5;CJK COMPATIBILITY IDEOGRAPH-FAD5;Lo;0;L;25249;;;;N;;;;; FAD6;CJK COMPATIBILITY IDEOGRAPH-FAD6;Lo;0;L;25CD0;;;;N;;;;; FAD7;CJK COMPATIBILITY IDEOGRAPH-FAD7;Lo;0;L;27ED3;;;;N;;;;; FAD8;CJK COMPATIBILITY IDEOGRAPH-FAD8;Lo;0;L;9F43;;;;N;;;;; FAD9;CJK COMPATIBILITY IDEOGRAPH-FAD9;Lo;0;L;9F8E;;;;N;;;;; FB00;LATIN SMALL LIGATURE FF;Ll;0;L; 0066 0066;;;;N;;;;; FB01;LATIN SMALL LIGATURE FI;Ll;0;L; 0066 0069;;;;N;;;;; FB02;LATIN SMALL LIGATURE FL;Ll;0;L; 0066 006C;;;;N;;;;; FB03;LATIN SMALL LIGATURE FFI;Ll;0;L; 0066 0066 0069;;;;N;;;;; FB04;LATIN SMALL LIGATURE FFL;Ll;0;L; 0066 0066 006C;;;;N;;;;; FB05;LATIN SMALL LIGATURE LONG S T;Ll;0;L; 017F 0074;;;;N;;;;; FB06;LATIN SMALL LIGATURE ST;Ll;0;L; 0073 0074;;;;N;;;;; FB13;ARMENIAN SMALL LIGATURE MEN NOW;Ll;0;L; 0574 0576;;;;N;;;;; FB14;ARMENIAN SMALL LIGATURE MEN ECH;Ll;0;L; 0574 0565;;;;N;;;;; FB15;ARMENIAN SMALL LIGATURE MEN INI;Ll;0;L; 0574 056B;;;;N;;;;; FB16;ARMENIAN SMALL LIGATURE VEW NOW;Ll;0;L; 057E 0576;;;;N;;;;; FB17;ARMENIAN SMALL LIGATURE MEN XEH;Ll;0;L; 0574 056D;;;;N;;;;; FB1D;HEBREW LETTER YOD WITH HIRIQ;Lo;0;R;05D9 05B4;;;;N;;;;; FB1E;HEBREW POINT JUDEO-SPANISH VARIKA;Mn;26;NSM;;;;;N;HEBREW POINT VARIKA;;;; FB1F;HEBREW LIGATURE YIDDISH YOD YOD PATAH;Lo;0;R;05F2 05B7;;;;N;;;;; FB20;HEBREW LETTER ALTERNATIVE AYIN;Lo;0;R; 05E2;;;;N;;;;; FB21;HEBREW LETTER WIDE ALEF;Lo;0;R; 05D0;;;;N;;;;; FB22;HEBREW LETTER WIDE DALET;Lo;0;R; 05D3;;;;N;;;;; FB23;HEBREW LETTER WIDE HE;Lo;0;R; 05D4;;;;N;;;;; FB24;HEBREW LETTER WIDE KAF;Lo;0;R; 05DB;;;;N;;;;; FB25;HEBREW LETTER WIDE LAMED;Lo;0;R; 05DC;;;;N;;;;; FB26;HEBREW LETTER WIDE FINAL MEM;Lo;0;R; 05DD;;;;N;;;;; FB27;HEBREW LETTER WIDE RESH;Lo;0;R; 05E8;;;;N;;;;; FB28;HEBREW LETTER WIDE TAV;Lo;0;R; 05EA;;;;N;;;;; FB29;HEBREW LETTER ALTERNATIVE PLUS SIGN;Sm;0;ES; 002B;;;;N;;;;; FB2A;HEBREW LETTER SHIN WITH SHIN DOT;Lo;0;R;05E9 05C1;;;;N;;;;; FB2B;HEBREW LETTER SHIN WITH SIN DOT;Lo;0;R;05E9 05C2;;;;N;;;;; FB2C;HEBREW LETTER SHIN WITH DAGESH AND SHIN DOT;Lo;0;R;FB49 05C1;;;;N;;;;; FB2D;HEBREW LETTER SHIN WITH DAGESH AND SIN DOT;Lo;0;R;FB49 05C2;;;;N;;;;; FB2E;HEBREW LETTER ALEF WITH PATAH;Lo;0;R;05D0 05B7;;;;N;;;;; FB2F;HEBREW LETTER ALEF WITH QAMATS;Lo;0;R;05D0 05B8;;;;N;;;;; FB30;HEBREW LETTER ALEF WITH MAPIQ;Lo;0;R;05D0 05BC;;;;N;;;;; FB31;HEBREW LETTER BET WITH DAGESH;Lo;0;R;05D1 05BC;;;;N;;;;; FB32;HEBREW LETTER GIMEL WITH DAGESH;Lo;0;R;05D2 05BC;;;;N;;;;; FB33;HEBREW LETTER DALET WITH DAGESH;Lo;0;R;05D3 05BC;;;;N;;;;; FB34;HEBREW LETTER HE WITH MAPIQ;Lo;0;R;05D4 05BC;;;;N;;;;; FB35;HEBREW LETTER VAV WITH DAGESH;Lo;0;R;05D5 05BC;;;;N;;;;; FB36;HEBREW LETTER ZAYIN WITH DAGESH;Lo;0;R;05D6 05BC;;;;N;;;;; FB38;HEBREW LETTER TET WITH DAGESH;Lo;0;R;05D8 05BC;;;;N;;;;; FB39;HEBREW LETTER YOD WITH DAGESH;Lo;0;R;05D9 05BC;;;;N;;;;; FB3A;HEBREW LETTER FINAL KAF WITH DAGESH;Lo;0;R;05DA 05BC;;;;N;;;;; FB3B;HEBREW LETTER KAF WITH DAGESH;Lo;0;R;05DB 05BC;;;;N;;;;; FB3C;HEBREW LETTER LAMED WITH DAGESH;Lo;0;R;05DC 05BC;;;;N;;;;; FB3E;HEBREW LETTER MEM WITH DAGESH;Lo;0;R;05DE 05BC;;;;N;;;;; FB40;HEBREW LETTER NUN WITH DAGESH;Lo;0;R;05E0 05BC;;;;N;;;;; FB41;HEBREW LETTER SAMEKH WITH DAGESH;Lo;0;R;05E1 05BC;;;;N;;;;; FB43;HEBREW LETTER FINAL PE WITH DAGESH;Lo;0;R;05E3 05BC;;;;N;;;;; FB44;HEBREW LETTER PE WITH DAGESH;Lo;0;R;05E4 05BC;;;;N;;;;; FB46;HEBREW LETTER TSADI WITH DAGESH;Lo;0;R;05E6 05BC;;;;N;;;;; FB47;HEBREW LETTER QOF WITH DAGESH;Lo;0;R;05E7 05BC;;;;N;;;;; FB48;HEBREW LETTER RESH WITH DAGESH;Lo;0;R;05E8 05BC;;;;N;;;;; FB49;HEBREW LETTER SHIN WITH DAGESH;Lo;0;R;05E9 05BC;;;;N;;;;; FB4A;HEBREW LETTER TAV WITH DAGESH;Lo;0;R;05EA 05BC;;;;N;;;;; FB4B;HEBREW LETTER VAV WITH HOLAM;Lo;0;R;05D5 05B9;;;;N;;;;; FB4C;HEBREW LETTER BET WITH RAFE;Lo;0;R;05D1 05BF;;;;N;;;;; FB4D;HEBREW LETTER KAF WITH RAFE;Lo;0;R;05DB 05BF;;;;N;;;;; FB4E;HEBREW LETTER PE WITH RAFE;Lo;0;R;05E4 05BF;;;;N;;;;; FB4F;HEBREW LIGATURE ALEF LAMED;Lo;0;R; 05D0 05DC;;;;N;;;;; FB50;ARABIC LETTER ALEF WASLA ISOLATED FORM;Lo;0;AL; 0671;;;;N;;;;; FB51;ARABIC LETTER ALEF WASLA FINAL FORM;Lo;0;AL; 0671;;;;N;;;;; FB52;ARABIC LETTER BEEH ISOLATED FORM;Lo;0;AL; 067B;;;;N;;;;; FB53;ARABIC LETTER BEEH FINAL FORM;Lo;0;AL; 067B;;;;N;;;;; FB54;ARABIC LETTER BEEH INITIAL FORM;Lo;0;AL; 067B;;;;N;;;;; FB55;ARABIC LETTER BEEH MEDIAL FORM;Lo;0;AL; 067B;;;;N;;;;; FB56;ARABIC LETTER PEH ISOLATED FORM;Lo;0;AL; 067E;;;;N;;;;; FB57;ARABIC LETTER PEH FINAL FORM;Lo;0;AL; 067E;;;;N;;;;; FB58;ARABIC LETTER PEH INITIAL FORM;Lo;0;AL; 067E;;;;N;;;;; FB59;ARABIC LETTER PEH MEDIAL FORM;Lo;0;AL; 067E;;;;N;;;;; FB5A;ARABIC LETTER BEHEH ISOLATED FORM;Lo;0;AL; 0680;;;;N;;;;; FB5B;ARABIC LETTER BEHEH FINAL FORM;Lo;0;AL; 0680;;;;N;;;;; FB5C;ARABIC LETTER BEHEH INITIAL FORM;Lo;0;AL; 0680;;;;N;;;;; FB5D;ARABIC LETTER BEHEH MEDIAL FORM;Lo;0;AL; 0680;;;;N;;;;; FB5E;ARABIC LETTER TTEHEH ISOLATED FORM;Lo;0;AL; 067A;;;;N;;;;; FB5F;ARABIC LETTER TTEHEH FINAL FORM;Lo;0;AL; 067A;;;;N;;;;; FB60;ARABIC LETTER TTEHEH INITIAL FORM;Lo;0;AL; 067A;;;;N;;;;; FB61;ARABIC LETTER TTEHEH MEDIAL FORM;Lo;0;AL; 067A;;;;N;;;;; FB62;ARABIC LETTER TEHEH ISOLATED FORM;Lo;0;AL; 067F;;;;N;;;;; FB63;ARABIC LETTER TEHEH FINAL FORM;Lo;0;AL; 067F;;;;N;;;;; FB64;ARABIC LETTER TEHEH INITIAL FORM;Lo;0;AL; 067F;;;;N;;;;; FB65;ARABIC LETTER TEHEH MEDIAL FORM;Lo;0;AL; 067F;;;;N;;;;; FB66;ARABIC LETTER TTEH ISOLATED FORM;Lo;0;AL; 0679;;;;N;;;;; FB67;ARABIC LETTER TTEH FINAL FORM;Lo;0;AL; 0679;;;;N;;;;; FB68;ARABIC LETTER TTEH INITIAL FORM;Lo;0;AL; 0679;;;;N;;;;; FB69;ARABIC LETTER TTEH MEDIAL FORM;Lo;0;AL; 0679;;;;N;;;;; FB6A;ARABIC LETTER VEH ISOLATED FORM;Lo;0;AL; 06A4;;;;N;;;;; FB6B;ARABIC LETTER VEH FINAL FORM;Lo;0;AL; 06A4;;;;N;;;;; FB6C;ARABIC LETTER VEH INITIAL FORM;Lo;0;AL; 06A4;;;;N;;;;; FB6D;ARABIC LETTER VEH MEDIAL FORM;Lo;0;AL; 06A4;;;;N;;;;; FB6E;ARABIC LETTER PEHEH ISOLATED FORM;Lo;0;AL; 06A6;;;;N;;;;; FB6F;ARABIC LETTER PEHEH FINAL FORM;Lo;0;AL; 06A6;;;;N;;;;; FB70;ARABIC LETTER PEHEH INITIAL FORM;Lo;0;AL; 06A6;;;;N;;;;; FB71;ARABIC LETTER PEHEH MEDIAL FORM;Lo;0;AL; 06A6;;;;N;;;;; FB72;ARABIC LETTER DYEH ISOLATED FORM;Lo;0;AL; 0684;;;;N;;;;; FB73;ARABIC LETTER DYEH FINAL FORM;Lo;0;AL; 0684;;;;N;;;;; FB74;ARABIC LETTER DYEH INITIAL FORM;Lo;0;AL; 0684;;;;N;;;;; FB75;ARABIC LETTER DYEH MEDIAL FORM;Lo;0;AL; 0684;;;;N;;;;; FB76;ARABIC LETTER NYEH ISOLATED FORM;Lo;0;AL; 0683;;;;N;;;;; FB77;ARABIC LETTER NYEH FINAL FORM;Lo;0;AL; 0683;;;;N;;;;; FB78;ARABIC LETTER NYEH INITIAL FORM;Lo;0;AL; 0683;;;;N;;;;; FB79;ARABIC LETTER NYEH MEDIAL FORM;Lo;0;AL; 0683;;;;N;;;;; FB7A;ARABIC LETTER TCHEH ISOLATED FORM;Lo;0;AL; 0686;;;;N;;;;; FB7B;ARABIC LETTER TCHEH FINAL FORM;Lo;0;AL; 0686;;;;N;;;;; FB7C;ARABIC LETTER TCHEH INITIAL FORM;Lo;0;AL; 0686;;;;N;;;;; FB7D;ARABIC LETTER TCHEH MEDIAL FORM;Lo;0;AL; 0686;;;;N;;;;; FB7E;ARABIC LETTER TCHEHEH ISOLATED FORM;Lo;0;AL; 0687;;;;N;;;;; FB7F;ARABIC LETTER TCHEHEH FINAL FORM;Lo;0;AL; 0687;;;;N;;;;; FB80;ARABIC LETTER TCHEHEH INITIAL FORM;Lo;0;AL; 0687;;;;N;;;;; FB81;ARABIC LETTER TCHEHEH MEDIAL FORM;Lo;0;AL; 0687;;;;N;;;;; FB82;ARABIC LETTER DDAHAL ISOLATED FORM;Lo;0;AL; 068D;;;;N;;;;; FB83;ARABIC LETTER DDAHAL FINAL FORM;Lo;0;AL; 068D;;;;N;;;;; FB84;ARABIC LETTER DAHAL ISOLATED FORM;Lo;0;AL; 068C;;;;N;;;;; FB85;ARABIC LETTER DAHAL FINAL FORM;Lo;0;AL; 068C;;;;N;;;;; FB86;ARABIC LETTER DUL ISOLATED FORM;Lo;0;AL; 068E;;;;N;;;;; FB87;ARABIC LETTER DUL FINAL FORM;Lo;0;AL; 068E;;;;N;;;;; FB88;ARABIC LETTER DDAL ISOLATED FORM;Lo;0;AL; 0688;;;;N;;;;; FB89;ARABIC LETTER DDAL FINAL FORM;Lo;0;AL; 0688;;;;N;;;;; FB8A;ARABIC LETTER JEH ISOLATED FORM;Lo;0;AL; 0698;;;;N;;;;; FB8B;ARABIC LETTER JEH FINAL FORM;Lo;0;AL; 0698;;;;N;;;;; FB8C;ARABIC LETTER RREH ISOLATED FORM;Lo;0;AL; 0691;;;;N;;;;; FB8D;ARABIC LETTER RREH FINAL FORM;Lo;0;AL; 0691;;;;N;;;;; FB8E;ARABIC LETTER KEHEH ISOLATED FORM;Lo;0;AL; 06A9;;;;N;;;;; FB8F;ARABIC LETTER KEHEH FINAL FORM;Lo;0;AL; 06A9;;;;N;;;;; FB90;ARABIC LETTER KEHEH INITIAL FORM;Lo;0;AL; 06A9;;;;N;;;;; FB91;ARABIC LETTER KEHEH MEDIAL FORM;Lo;0;AL; 06A9;;;;N;;;;; FB92;ARABIC LETTER GAF ISOLATED FORM;Lo;0;AL; 06AF;;;;N;;;;; FB93;ARABIC LETTER GAF FINAL FORM;Lo;0;AL; 06AF;;;;N;;;;; FB94;ARABIC LETTER GAF INITIAL FORM;Lo;0;AL; 06AF;;;;N;;;;; FB95;ARABIC LETTER GAF MEDIAL FORM;Lo;0;AL; 06AF;;;;N;;;;; FB96;ARABIC LETTER GUEH ISOLATED FORM;Lo;0;AL; 06B3;;;;N;;;;; FB97;ARABIC LETTER GUEH FINAL FORM;Lo;0;AL; 06B3;;;;N;;;;; FB98;ARABIC LETTER GUEH INITIAL FORM;Lo;0;AL; 06B3;;;;N;;;;; FB99;ARABIC LETTER GUEH MEDIAL FORM;Lo;0;AL; 06B3;;;;N;;;;; FB9A;ARABIC LETTER NGOEH ISOLATED FORM;Lo;0;AL; 06B1;;;;N;;;;; FB9B;ARABIC LETTER NGOEH FINAL FORM;Lo;0;AL; 06B1;;;;N;;;;; FB9C;ARABIC LETTER NGOEH INITIAL FORM;Lo;0;AL; 06B1;;;;N;;;;; FB9D;ARABIC LETTER NGOEH MEDIAL FORM;Lo;0;AL; 06B1;;;;N;;;;; FB9E;ARABIC LETTER NOON GHUNNA ISOLATED FORM;Lo;0;AL; 06BA;;;;N;;;;; FB9F;ARABIC LETTER NOON GHUNNA FINAL FORM;Lo;0;AL; 06BA;;;;N;;;;; FBA0;ARABIC LETTER RNOON ISOLATED FORM;Lo;0;AL; 06BB;;;;N;;;;; FBA1;ARABIC LETTER RNOON FINAL FORM;Lo;0;AL; 06BB;;;;N;;;;; FBA2;ARABIC LETTER RNOON INITIAL FORM;Lo;0;AL; 06BB;;;;N;;;;; FBA3;ARABIC LETTER RNOON MEDIAL FORM;Lo;0;AL; 06BB;;;;N;;;;; FBA4;ARABIC LETTER HEH WITH YEH ABOVE ISOLATED FORM;Lo;0;AL; 06C0;;;;N;;;;; FBA5;ARABIC LETTER HEH WITH YEH ABOVE FINAL FORM;Lo;0;AL; 06C0;;;;N;;;;; FBA6;ARABIC LETTER HEH GOAL ISOLATED FORM;Lo;0;AL; 06C1;;;;N;;;;; FBA7;ARABIC LETTER HEH GOAL FINAL FORM;Lo;0;AL; 06C1;;;;N;;;;; FBA8;ARABIC LETTER HEH GOAL INITIAL FORM;Lo;0;AL; 06C1;;;;N;;;;; FBA9;ARABIC LETTER HEH GOAL MEDIAL FORM;Lo;0;AL; 06C1;;;;N;;;;; FBAA;ARABIC LETTER HEH DOACHASHMEE ISOLATED FORM;Lo;0;AL; 06BE;;;;N;;;;; FBAB;ARABIC LETTER HEH DOACHASHMEE FINAL FORM;Lo;0;AL; 06BE;;;;N;;;;; FBAC;ARABIC LETTER HEH DOACHASHMEE INITIAL FORM;Lo;0;AL; 06BE;;;;N;;;;; FBAD;ARABIC LETTER HEH DOACHASHMEE MEDIAL FORM;Lo;0;AL; 06BE;;;;N;;;;; FBAE;ARABIC LETTER YEH BARREE ISOLATED FORM;Lo;0;AL; 06D2;;;;N;;;;; FBAF;ARABIC LETTER YEH BARREE FINAL FORM;Lo;0;AL; 06D2;;;;N;;;;; FBB0;ARABIC LETTER YEH BARREE WITH HAMZA ABOVE ISOLATED FORM;Lo;0;AL; 06D3;;;;N;;;;; FBB1;ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM;Lo;0;AL; 06D3;;;;N;;;;; FBB2;ARABIC SYMBOL DOT ABOVE;Sk;0;AL;;;;;N;;;;; FBB3;ARABIC SYMBOL DOT BELOW;Sk;0;AL;;;;;N;;;;; FBB4;ARABIC SYMBOL TWO DOTS ABOVE;Sk;0;AL;;;;;N;;;;; FBB5;ARABIC SYMBOL TWO DOTS BELOW;Sk;0;AL;;;;;N;;;;; FBB6;ARABIC SYMBOL THREE DOTS ABOVE;Sk;0;AL;;;;;N;;;;; FBB7;ARABIC SYMBOL THREE DOTS BELOW;Sk;0;AL;;;;;N;;;;; FBB8;ARABIC SYMBOL THREE DOTS POINTING DOWNWARDS ABOVE;Sk;0;AL;;;;;N;;;;; FBB9;ARABIC SYMBOL THREE DOTS POINTING DOWNWARDS BELOW;Sk;0;AL;;;;;N;;;;; FBBA;ARABIC SYMBOL FOUR DOTS ABOVE;Sk;0;AL;;;;;N;;;;; FBBB;ARABIC SYMBOL FOUR DOTS BELOW;Sk;0;AL;;;;;N;;;;; FBBC;ARABIC SYMBOL DOUBLE VERTICAL BAR BELOW;Sk;0;AL;;;;;N;;;;; FBBD;ARABIC SYMBOL TWO DOTS VERTICALLY ABOVE;Sk;0;AL;;;;;N;;;;; FBBE;ARABIC SYMBOL TWO DOTS VERTICALLY BELOW;Sk;0;AL;;;;;N;;;;; FBBF;ARABIC SYMBOL RING;Sk;0;AL;;;;;N;;;;; FBC0;ARABIC SYMBOL SMALL TAH ABOVE;Sk;0;AL;;;;;N;;;;; FBC1;ARABIC SYMBOL SMALL TAH BELOW;Sk;0;AL;;;;;N;;;;; FBD3;ARABIC LETTER NG ISOLATED FORM;Lo;0;AL; 06AD;;;;N;;;;; FBD4;ARABIC LETTER NG FINAL FORM;Lo;0;AL; 06AD;;;;N;;;;; FBD5;ARABIC LETTER NG INITIAL FORM;Lo;0;AL; 06AD;;;;N;;;;; FBD6;ARABIC LETTER NG MEDIAL FORM;Lo;0;AL; 06AD;;;;N;;;;; FBD7;ARABIC LETTER U ISOLATED FORM;Lo;0;AL; 06C7;;;;N;;;;; FBD8;ARABIC LETTER U FINAL FORM;Lo;0;AL; 06C7;;;;N;;;;; FBD9;ARABIC LETTER OE ISOLATED FORM;Lo;0;AL; 06C6;;;;N;;;;; FBDA;ARABIC LETTER OE FINAL FORM;Lo;0;AL; 06C6;;;;N;;;;; FBDB;ARABIC LETTER YU ISOLATED FORM;Lo;0;AL; 06C8;;;;N;;;;; FBDC;ARABIC LETTER YU FINAL FORM;Lo;0;AL; 06C8;;;;N;;;;; FBDD;ARABIC LETTER U WITH HAMZA ABOVE ISOLATED FORM;Lo;0;AL; 0677;;;;N;;;;; FBDE;ARABIC LETTER VE ISOLATED FORM;Lo;0;AL; 06CB;;;;N;;;;; FBDF;ARABIC LETTER VE FINAL FORM;Lo;0;AL; 06CB;;;;N;;;;; FBE0;ARABIC LETTER KIRGHIZ OE ISOLATED FORM;Lo;0;AL; 06C5;;;;N;;;;; FBE1;ARABIC LETTER KIRGHIZ OE FINAL FORM;Lo;0;AL; 06C5;;;;N;;;;; FBE2;ARABIC LETTER KIRGHIZ YU ISOLATED FORM;Lo;0;AL; 06C9;;;;N;;;;; FBE3;ARABIC LETTER KIRGHIZ YU FINAL FORM;Lo;0;AL; 06C9;;;;N;;;;; FBE4;ARABIC LETTER E ISOLATED FORM;Lo;0;AL; 06D0;;;;N;;;;; FBE5;ARABIC LETTER E FINAL FORM;Lo;0;AL; 06D0;;;;N;;;;; FBE6;ARABIC LETTER E INITIAL FORM;Lo;0;AL; 06D0;;;;N;;;;; FBE7;ARABIC LETTER E MEDIAL FORM;Lo;0;AL; 06D0;;;;N;;;;; FBE8;ARABIC LETTER UIGHUR KAZAKH KIRGHIZ ALEF MAKSURA INITIAL FORM;Lo;0;AL; 0649;;;;N;;;;; FBE9;ARABIC LETTER UIGHUR KAZAKH KIRGHIZ ALEF MAKSURA MEDIAL FORM;Lo;0;AL; 0649;;;;N;;;;; FBEA;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF ISOLATED FORM;Lo;0;AL; 0626 0627;;;;N;;;;; FBEB;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF FINAL FORM;Lo;0;AL; 0626 0627;;;;N;;;;; FBEC;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH AE ISOLATED FORM;Lo;0;AL; 0626 06D5;;;;N;;;;; FBED;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH AE FINAL FORM;Lo;0;AL; 0626 06D5;;;;N;;;;; FBEE;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH WAW ISOLATED FORM;Lo;0;AL; 0626 0648;;;;N;;;;; FBEF;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH WAW FINAL FORM;Lo;0;AL; 0626 0648;;;;N;;;;; FBF0;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH U ISOLATED FORM;Lo;0;AL; 0626 06C7;;;;N;;;;; FBF1;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH U FINAL FORM;Lo;0;AL; 0626 06C7;;;;N;;;;; FBF2;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH OE ISOLATED FORM;Lo;0;AL; 0626 06C6;;;;N;;;;; FBF3;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH OE FINAL FORM;Lo;0;AL; 0626 06C6;;;;N;;;;; FBF4;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH YU ISOLATED FORM;Lo;0;AL; 0626 06C8;;;;N;;;;; FBF5;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH YU FINAL FORM;Lo;0;AL; 0626 06C8;;;;N;;;;; FBF6;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH E ISOLATED FORM;Lo;0;AL; 0626 06D0;;;;N;;;;; FBF7;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH E FINAL FORM;Lo;0;AL; 0626 06D0;;;;N;;;;; FBF8;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH E INITIAL FORM;Lo;0;AL; 0626 06D0;;;;N;;;;; FBF9;ARABIC LIGATURE UIGHUR KIRGHIZ YEH WITH HAMZA ABOVE WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 0626 0649;;;;N;;;;; FBFA;ARABIC LIGATURE UIGHUR KIRGHIZ YEH WITH HAMZA ABOVE WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0626 0649;;;;N;;;;; FBFB;ARABIC LIGATURE UIGHUR KIRGHIZ YEH WITH HAMZA ABOVE WITH ALEF MAKSURA INITIAL FORM;Lo;0;AL; 0626 0649;;;;N;;;;; FBFC;ARABIC LETTER FARSI YEH ISOLATED FORM;Lo;0;AL; 06CC;;;;N;;;;; FBFD;ARABIC LETTER FARSI YEH FINAL FORM;Lo;0;AL; 06CC;;;;N;;;;; FBFE;ARABIC LETTER FARSI YEH INITIAL FORM;Lo;0;AL; 06CC;;;;N;;;;; FBFF;ARABIC LETTER FARSI YEH MEDIAL FORM;Lo;0;AL; 06CC;;;;N;;;;; FC00;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH JEEM ISOLATED FORM;Lo;0;AL; 0626 062C;;;;N;;;;; FC01;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH HAH ISOLATED FORM;Lo;0;AL; 0626 062D;;;;N;;;;; FC02;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH MEEM ISOLATED FORM;Lo;0;AL; 0626 0645;;;;N;;;;; FC03;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 0626 0649;;;;N;;;;; FC04;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH YEH ISOLATED FORM;Lo;0;AL; 0626 064A;;;;N;;;;; FC05;ARABIC LIGATURE BEH WITH JEEM ISOLATED FORM;Lo;0;AL; 0628 062C;;;;N;;;;; FC06;ARABIC LIGATURE BEH WITH HAH ISOLATED FORM;Lo;0;AL; 0628 062D;;;;N;;;;; FC07;ARABIC LIGATURE BEH WITH KHAH ISOLATED FORM;Lo;0;AL; 0628 062E;;;;N;;;;; FC08;ARABIC LIGATURE BEH WITH MEEM ISOLATED FORM;Lo;0;AL; 0628 0645;;;;N;;;;; FC09;ARABIC LIGATURE BEH WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 0628 0649;;;;N;;;;; FC0A;ARABIC LIGATURE BEH WITH YEH ISOLATED FORM;Lo;0;AL; 0628 064A;;;;N;;;;; FC0B;ARABIC LIGATURE TEH WITH JEEM ISOLATED FORM;Lo;0;AL; 062A 062C;;;;N;;;;; FC0C;ARABIC LIGATURE TEH WITH HAH ISOLATED FORM;Lo;0;AL; 062A 062D;;;;N;;;;; FC0D;ARABIC LIGATURE TEH WITH KHAH ISOLATED FORM;Lo;0;AL; 062A 062E;;;;N;;;;; FC0E;ARABIC LIGATURE TEH WITH MEEM ISOLATED FORM;Lo;0;AL; 062A 0645;;;;N;;;;; FC0F;ARABIC LIGATURE TEH WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 062A 0649;;;;N;;;;; FC10;ARABIC LIGATURE TEH WITH YEH ISOLATED FORM;Lo;0;AL; 062A 064A;;;;N;;;;; FC11;ARABIC LIGATURE THEH WITH JEEM ISOLATED FORM;Lo;0;AL; 062B 062C;;;;N;;;;; FC12;ARABIC LIGATURE THEH WITH MEEM ISOLATED FORM;Lo;0;AL; 062B 0645;;;;N;;;;; FC13;ARABIC LIGATURE THEH WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 062B 0649;;;;N;;;;; FC14;ARABIC LIGATURE THEH WITH YEH ISOLATED FORM;Lo;0;AL; 062B 064A;;;;N;;;;; FC15;ARABIC LIGATURE JEEM WITH HAH ISOLATED FORM;Lo;0;AL; 062C 062D;;;;N;;;;; FC16;ARABIC LIGATURE JEEM WITH MEEM ISOLATED FORM;Lo;0;AL; 062C 0645;;;;N;;;;; FC17;ARABIC LIGATURE HAH WITH JEEM ISOLATED FORM;Lo;0;AL; 062D 062C;;;;N;;;;; FC18;ARABIC LIGATURE HAH WITH MEEM ISOLATED FORM;Lo;0;AL; 062D 0645;;;;N;;;;; FC19;ARABIC LIGATURE KHAH WITH JEEM ISOLATED FORM;Lo;0;AL; 062E 062C;;;;N;;;;; FC1A;ARABIC LIGATURE KHAH WITH HAH ISOLATED FORM;Lo;0;AL; 062E 062D;;;;N;;;;; FC1B;ARABIC LIGATURE KHAH WITH MEEM ISOLATED FORM;Lo;0;AL; 062E 0645;;;;N;;;;; FC1C;ARABIC LIGATURE SEEN WITH JEEM ISOLATED FORM;Lo;0;AL; 0633 062C;;;;N;;;;; FC1D;ARABIC LIGATURE SEEN WITH HAH ISOLATED FORM;Lo;0;AL; 0633 062D;;;;N;;;;; FC1E;ARABIC LIGATURE SEEN WITH KHAH ISOLATED FORM;Lo;0;AL; 0633 062E;;;;N;;;;; FC1F;ARABIC LIGATURE SEEN WITH MEEM ISOLATED FORM;Lo;0;AL; 0633 0645;;;;N;;;;; FC20;ARABIC LIGATURE SAD WITH HAH ISOLATED FORM;Lo;0;AL; 0635 062D;;;;N;;;;; FC21;ARABIC LIGATURE SAD WITH MEEM ISOLATED FORM;Lo;0;AL; 0635 0645;;;;N;;;;; FC22;ARABIC LIGATURE DAD WITH JEEM ISOLATED FORM;Lo;0;AL; 0636 062C;;;;N;;;;; FC23;ARABIC LIGATURE DAD WITH HAH ISOLATED FORM;Lo;0;AL; 0636 062D;;;;N;;;;; FC24;ARABIC LIGATURE DAD WITH KHAH ISOLATED FORM;Lo;0;AL; 0636 062E;;;;N;;;;; FC25;ARABIC LIGATURE DAD WITH MEEM ISOLATED FORM;Lo;0;AL; 0636 0645;;;;N;;;;; FC26;ARABIC LIGATURE TAH WITH HAH ISOLATED FORM;Lo;0;AL; 0637 062D;;;;N;;;;; FC27;ARABIC LIGATURE TAH WITH MEEM ISOLATED FORM;Lo;0;AL; 0637 0645;;;;N;;;;; FC28;ARABIC LIGATURE ZAH WITH MEEM ISOLATED FORM;Lo;0;AL; 0638 0645;;;;N;;;;; FC29;ARABIC LIGATURE AIN WITH JEEM ISOLATED FORM;Lo;0;AL; 0639 062C;;;;N;;;;; FC2A;ARABIC LIGATURE AIN WITH MEEM ISOLATED FORM;Lo;0;AL; 0639 0645;;;;N;;;;; FC2B;ARABIC LIGATURE GHAIN WITH JEEM ISOLATED FORM;Lo;0;AL; 063A 062C;;;;N;;;;; FC2C;ARABIC LIGATURE GHAIN WITH MEEM ISOLATED FORM;Lo;0;AL; 063A 0645;;;;N;;;;; FC2D;ARABIC LIGATURE FEH WITH JEEM ISOLATED FORM;Lo;0;AL; 0641 062C;;;;N;;;;; FC2E;ARABIC LIGATURE FEH WITH HAH ISOLATED FORM;Lo;0;AL; 0641 062D;;;;N;;;;; FC2F;ARABIC LIGATURE FEH WITH KHAH ISOLATED FORM;Lo;0;AL; 0641 062E;;;;N;;;;; FC30;ARABIC LIGATURE FEH WITH MEEM ISOLATED FORM;Lo;0;AL; 0641 0645;;;;N;;;;; FC31;ARABIC LIGATURE FEH WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 0641 0649;;;;N;;;;; FC32;ARABIC LIGATURE FEH WITH YEH ISOLATED FORM;Lo;0;AL; 0641 064A;;;;N;;;;; FC33;ARABIC LIGATURE QAF WITH HAH ISOLATED FORM;Lo;0;AL; 0642 062D;;;;N;;;;; FC34;ARABIC LIGATURE QAF WITH MEEM ISOLATED FORM;Lo;0;AL; 0642 0645;;;;N;;;;; FC35;ARABIC LIGATURE QAF WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 0642 0649;;;;N;;;;; FC36;ARABIC LIGATURE QAF WITH YEH ISOLATED FORM;Lo;0;AL; 0642 064A;;;;N;;;;; FC37;ARABIC LIGATURE KAF WITH ALEF ISOLATED FORM;Lo;0;AL; 0643 0627;;;;N;;;;; FC38;ARABIC LIGATURE KAF WITH JEEM ISOLATED FORM;Lo;0;AL; 0643 062C;;;;N;;;;; FC39;ARABIC LIGATURE KAF WITH HAH ISOLATED FORM;Lo;0;AL; 0643 062D;;;;N;;;;; FC3A;ARABIC LIGATURE KAF WITH KHAH ISOLATED FORM;Lo;0;AL; 0643 062E;;;;N;;;;; FC3B;ARABIC LIGATURE KAF WITH LAM ISOLATED FORM;Lo;0;AL; 0643 0644;;;;N;;;;; FC3C;ARABIC LIGATURE KAF WITH MEEM ISOLATED FORM;Lo;0;AL; 0643 0645;;;;N;;;;; FC3D;ARABIC LIGATURE KAF WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 0643 0649;;;;N;;;;; FC3E;ARABIC LIGATURE KAF WITH YEH ISOLATED FORM;Lo;0;AL; 0643 064A;;;;N;;;;; FC3F;ARABIC LIGATURE LAM WITH JEEM ISOLATED FORM;Lo;0;AL; 0644 062C;;;;N;;;;; FC40;ARABIC LIGATURE LAM WITH HAH ISOLATED FORM;Lo;0;AL; 0644 062D;;;;N;;;;; FC41;ARABIC LIGATURE LAM WITH KHAH ISOLATED FORM;Lo;0;AL; 0644 062E;;;;N;;;;; FC42;ARABIC LIGATURE LAM WITH MEEM ISOLATED FORM;Lo;0;AL; 0644 0645;;;;N;;;;; FC43;ARABIC LIGATURE LAM WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 0644 0649;;;;N;;;;; FC44;ARABIC LIGATURE LAM WITH YEH ISOLATED FORM;Lo;0;AL; 0644 064A;;;;N;;;;; FC45;ARABIC LIGATURE MEEM WITH JEEM ISOLATED FORM;Lo;0;AL; 0645 062C;;;;N;;;;; FC46;ARABIC LIGATURE MEEM WITH HAH ISOLATED FORM;Lo;0;AL; 0645 062D;;;;N;;;;; FC47;ARABIC LIGATURE MEEM WITH KHAH ISOLATED FORM;Lo;0;AL; 0645 062E;;;;N;;;;; FC48;ARABIC LIGATURE MEEM WITH MEEM ISOLATED FORM;Lo;0;AL; 0645 0645;;;;N;;;;; FC49;ARABIC LIGATURE MEEM WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 0645 0649;;;;N;;;;; FC4A;ARABIC LIGATURE MEEM WITH YEH ISOLATED FORM;Lo;0;AL; 0645 064A;;;;N;;;;; FC4B;ARABIC LIGATURE NOON WITH JEEM ISOLATED FORM;Lo;0;AL; 0646 062C;;;;N;;;;; FC4C;ARABIC LIGATURE NOON WITH HAH ISOLATED FORM;Lo;0;AL; 0646 062D;;;;N;;;;; FC4D;ARABIC LIGATURE NOON WITH KHAH ISOLATED FORM;Lo;0;AL; 0646 062E;;;;N;;;;; FC4E;ARABIC LIGATURE NOON WITH MEEM ISOLATED FORM;Lo;0;AL; 0646 0645;;;;N;;;;; FC4F;ARABIC LIGATURE NOON WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 0646 0649;;;;N;;;;; FC50;ARABIC LIGATURE NOON WITH YEH ISOLATED FORM;Lo;0;AL; 0646 064A;;;;N;;;;; FC51;ARABIC LIGATURE HEH WITH JEEM ISOLATED FORM;Lo;0;AL; 0647 062C;;;;N;;;;; FC52;ARABIC LIGATURE HEH WITH MEEM ISOLATED FORM;Lo;0;AL; 0647 0645;;;;N;;;;; FC53;ARABIC LIGATURE HEH WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 0647 0649;;;;N;;;;; FC54;ARABIC LIGATURE HEH WITH YEH ISOLATED FORM;Lo;0;AL; 0647 064A;;;;N;;;;; FC55;ARABIC LIGATURE YEH WITH JEEM ISOLATED FORM;Lo;0;AL; 064A 062C;;;;N;;;;; FC56;ARABIC LIGATURE YEH WITH HAH ISOLATED FORM;Lo;0;AL; 064A 062D;;;;N;;;;; FC57;ARABIC LIGATURE YEH WITH KHAH ISOLATED FORM;Lo;0;AL; 064A 062E;;;;N;;;;; FC58;ARABIC LIGATURE YEH WITH MEEM ISOLATED FORM;Lo;0;AL; 064A 0645;;;;N;;;;; FC59;ARABIC LIGATURE YEH WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 064A 0649;;;;N;;;;; FC5A;ARABIC LIGATURE YEH WITH YEH ISOLATED FORM;Lo;0;AL; 064A 064A;;;;N;;;;; FC5B;ARABIC LIGATURE THAL WITH SUPERSCRIPT ALEF ISOLATED FORM;Lo;0;AL; 0630 0670;;;;N;;;;; FC5C;ARABIC LIGATURE REH WITH SUPERSCRIPT ALEF ISOLATED FORM;Lo;0;AL; 0631 0670;;;;N;;;;; FC5D;ARABIC LIGATURE ALEF MAKSURA WITH SUPERSCRIPT ALEF ISOLATED FORM;Lo;0;AL; 0649 0670;;;;N;;;;; FC5E;ARABIC LIGATURE SHADDA WITH DAMMATAN ISOLATED FORM;Lo;0;AL; 0020 064C 0651;;;;N;;;;; FC5F;ARABIC LIGATURE SHADDA WITH KASRATAN ISOLATED FORM;Lo;0;AL; 0020 064D 0651;;;;N;;;;; FC60;ARABIC LIGATURE SHADDA WITH FATHA ISOLATED FORM;Lo;0;AL; 0020 064E 0651;;;;N;;;;; FC61;ARABIC LIGATURE SHADDA WITH DAMMA ISOLATED FORM;Lo;0;AL; 0020 064F 0651;;;;N;;;;; FC62;ARABIC LIGATURE SHADDA WITH KASRA ISOLATED FORM;Lo;0;AL; 0020 0650 0651;;;;N;;;;; FC63;ARABIC LIGATURE SHADDA WITH SUPERSCRIPT ALEF ISOLATED FORM;Lo;0;AL; 0020 0651 0670;;;;N;;;;; FC64;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH REH FINAL FORM;Lo;0;AL; 0626 0631;;;;N;;;;; FC65;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ZAIN FINAL FORM;Lo;0;AL; 0626 0632;;;;N;;;;; FC66;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH MEEM FINAL FORM;Lo;0;AL; 0626 0645;;;;N;;;;; FC67;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH NOON FINAL FORM;Lo;0;AL; 0626 0646;;;;N;;;;; FC68;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0626 0649;;;;N;;;;; FC69;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH YEH FINAL FORM;Lo;0;AL; 0626 064A;;;;N;;;;; FC6A;ARABIC LIGATURE BEH WITH REH FINAL FORM;Lo;0;AL; 0628 0631;;;;N;;;;; FC6B;ARABIC LIGATURE BEH WITH ZAIN FINAL FORM;Lo;0;AL; 0628 0632;;;;N;;;;; FC6C;ARABIC LIGATURE BEH WITH MEEM FINAL FORM;Lo;0;AL; 0628 0645;;;;N;;;;; FC6D;ARABIC LIGATURE BEH WITH NOON FINAL FORM;Lo;0;AL; 0628 0646;;;;N;;;;; FC6E;ARABIC LIGATURE BEH WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0628 0649;;;;N;;;;; FC6F;ARABIC LIGATURE BEH WITH YEH FINAL FORM;Lo;0;AL; 0628 064A;;;;N;;;;; FC70;ARABIC LIGATURE TEH WITH REH FINAL FORM;Lo;0;AL; 062A 0631;;;;N;;;;; FC71;ARABIC LIGATURE TEH WITH ZAIN FINAL FORM;Lo;0;AL; 062A 0632;;;;N;;;;; FC72;ARABIC LIGATURE TEH WITH MEEM FINAL FORM;Lo;0;AL; 062A 0645;;;;N;;;;; FC73;ARABIC LIGATURE TEH WITH NOON FINAL FORM;Lo;0;AL; 062A 0646;;;;N;;;;; FC74;ARABIC LIGATURE TEH WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 062A 0649;;;;N;;;;; FC75;ARABIC LIGATURE TEH WITH YEH FINAL FORM;Lo;0;AL; 062A 064A;;;;N;;;;; FC76;ARABIC LIGATURE THEH WITH REH FINAL FORM;Lo;0;AL; 062B 0631;;;;N;;;;; FC77;ARABIC LIGATURE THEH WITH ZAIN FINAL FORM;Lo;0;AL; 062B 0632;;;;N;;;;; FC78;ARABIC LIGATURE THEH WITH MEEM FINAL FORM;Lo;0;AL; 062B 0645;;;;N;;;;; FC79;ARABIC LIGATURE THEH WITH NOON FINAL FORM;Lo;0;AL; 062B 0646;;;;N;;;;; FC7A;ARABIC LIGATURE THEH WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 062B 0649;;;;N;;;;; FC7B;ARABIC LIGATURE THEH WITH YEH FINAL FORM;Lo;0;AL; 062B 064A;;;;N;;;;; FC7C;ARABIC LIGATURE FEH WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0641 0649;;;;N;;;;; FC7D;ARABIC LIGATURE FEH WITH YEH FINAL FORM;Lo;0;AL; 0641 064A;;;;N;;;;; FC7E;ARABIC LIGATURE QAF WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0642 0649;;;;N;;;;; FC7F;ARABIC LIGATURE QAF WITH YEH FINAL FORM;Lo;0;AL; 0642 064A;;;;N;;;;; FC80;ARABIC LIGATURE KAF WITH ALEF FINAL FORM;Lo;0;AL; 0643 0627;;;;N;;;;; FC81;ARABIC LIGATURE KAF WITH LAM FINAL FORM;Lo;0;AL; 0643 0644;;;;N;;;;; FC82;ARABIC LIGATURE KAF WITH MEEM FINAL FORM;Lo;0;AL; 0643 0645;;;;N;;;;; FC83;ARABIC LIGATURE KAF WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0643 0649;;;;N;;;;; FC84;ARABIC LIGATURE KAF WITH YEH FINAL FORM;Lo;0;AL; 0643 064A;;;;N;;;;; FC85;ARABIC LIGATURE LAM WITH MEEM FINAL FORM;Lo;0;AL; 0644 0645;;;;N;;;;; FC86;ARABIC LIGATURE LAM WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0644 0649;;;;N;;;;; FC87;ARABIC LIGATURE LAM WITH YEH FINAL FORM;Lo;0;AL; 0644 064A;;;;N;;;;; FC88;ARABIC LIGATURE MEEM WITH ALEF FINAL FORM;Lo;0;AL; 0645 0627;;;;N;;;;; FC89;ARABIC LIGATURE MEEM WITH MEEM FINAL FORM;Lo;0;AL; 0645 0645;;;;N;;;;; FC8A;ARABIC LIGATURE NOON WITH REH FINAL FORM;Lo;0;AL; 0646 0631;;;;N;;;;; FC8B;ARABIC LIGATURE NOON WITH ZAIN FINAL FORM;Lo;0;AL; 0646 0632;;;;N;;;;; FC8C;ARABIC LIGATURE NOON WITH MEEM FINAL FORM;Lo;0;AL; 0646 0645;;;;N;;;;; FC8D;ARABIC LIGATURE NOON WITH NOON FINAL FORM;Lo;0;AL; 0646 0646;;;;N;;;;; FC8E;ARABIC LIGATURE NOON WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0646 0649;;;;N;;;;; FC8F;ARABIC LIGATURE NOON WITH YEH FINAL FORM;Lo;0;AL; 0646 064A;;;;N;;;;; FC90;ARABIC LIGATURE ALEF MAKSURA WITH SUPERSCRIPT ALEF FINAL FORM;Lo;0;AL; 0649 0670;;;;N;;;;; FC91;ARABIC LIGATURE YEH WITH REH FINAL FORM;Lo;0;AL; 064A 0631;;;;N;;;;; FC92;ARABIC LIGATURE YEH WITH ZAIN FINAL FORM;Lo;0;AL; 064A 0632;;;;N;;;;; FC93;ARABIC LIGATURE YEH WITH MEEM FINAL FORM;Lo;0;AL; 064A 0645;;;;N;;;;; FC94;ARABIC LIGATURE YEH WITH NOON FINAL FORM;Lo;0;AL; 064A 0646;;;;N;;;;; FC95;ARABIC LIGATURE YEH WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 064A 0649;;;;N;;;;; FC96;ARABIC LIGATURE YEH WITH YEH FINAL FORM;Lo;0;AL; 064A 064A;;;;N;;;;; FC97;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH JEEM INITIAL FORM;Lo;0;AL; 0626 062C;;;;N;;;;; FC98;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH HAH INITIAL FORM;Lo;0;AL; 0626 062D;;;;N;;;;; FC99;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH KHAH INITIAL FORM;Lo;0;AL; 0626 062E;;;;N;;;;; FC9A;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH MEEM INITIAL FORM;Lo;0;AL; 0626 0645;;;;N;;;;; FC9B;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH HEH INITIAL FORM;Lo;0;AL; 0626 0647;;;;N;;;;; FC9C;ARABIC LIGATURE BEH WITH JEEM INITIAL FORM;Lo;0;AL; 0628 062C;;;;N;;;;; FC9D;ARABIC LIGATURE BEH WITH HAH INITIAL FORM;Lo;0;AL; 0628 062D;;;;N;;;;; FC9E;ARABIC LIGATURE BEH WITH KHAH INITIAL FORM;Lo;0;AL; 0628 062E;;;;N;;;;; FC9F;ARABIC LIGATURE BEH WITH MEEM INITIAL FORM;Lo;0;AL; 0628 0645;;;;N;;;;; FCA0;ARABIC LIGATURE BEH WITH HEH INITIAL FORM;Lo;0;AL; 0628 0647;;;;N;;;;; FCA1;ARABIC LIGATURE TEH WITH JEEM INITIAL FORM;Lo;0;AL; 062A 062C;;;;N;;;;; FCA2;ARABIC LIGATURE TEH WITH HAH INITIAL FORM;Lo;0;AL; 062A 062D;;;;N;;;;; FCA3;ARABIC LIGATURE TEH WITH KHAH INITIAL FORM;Lo;0;AL; 062A 062E;;;;N;;;;; FCA4;ARABIC LIGATURE TEH WITH MEEM INITIAL FORM;Lo;0;AL; 062A 0645;;;;N;;;;; FCA5;ARABIC LIGATURE TEH WITH HEH INITIAL FORM;Lo;0;AL; 062A 0647;;;;N;;;;; FCA6;ARABIC LIGATURE THEH WITH MEEM INITIAL FORM;Lo;0;AL; 062B 0645;;;;N;;;;; FCA7;ARABIC LIGATURE JEEM WITH HAH INITIAL FORM;Lo;0;AL; 062C 062D;;;;N;;;;; FCA8;ARABIC LIGATURE JEEM WITH MEEM INITIAL FORM;Lo;0;AL; 062C 0645;;;;N;;;;; FCA9;ARABIC LIGATURE HAH WITH JEEM INITIAL FORM;Lo;0;AL; 062D 062C;;;;N;;;;; FCAA;ARABIC LIGATURE HAH WITH MEEM INITIAL FORM;Lo;0;AL; 062D 0645;;;;N;;;;; FCAB;ARABIC LIGATURE KHAH WITH JEEM INITIAL FORM;Lo;0;AL; 062E 062C;;;;N;;;;; FCAC;ARABIC LIGATURE KHAH WITH MEEM INITIAL FORM;Lo;0;AL; 062E 0645;;;;N;;;;; FCAD;ARABIC LIGATURE SEEN WITH JEEM INITIAL FORM;Lo;0;AL; 0633 062C;;;;N;;;;; FCAE;ARABIC LIGATURE SEEN WITH HAH INITIAL FORM;Lo;0;AL; 0633 062D;;;;N;;;;; FCAF;ARABIC LIGATURE SEEN WITH KHAH INITIAL FORM;Lo;0;AL; 0633 062E;;;;N;;;;; FCB0;ARABIC LIGATURE SEEN WITH MEEM INITIAL FORM;Lo;0;AL; 0633 0645;;;;N;;;;; FCB1;ARABIC LIGATURE SAD WITH HAH INITIAL FORM;Lo;0;AL; 0635 062D;;;;N;;;;; FCB2;ARABIC LIGATURE SAD WITH KHAH INITIAL FORM;Lo;0;AL; 0635 062E;;;;N;;;;; FCB3;ARABIC LIGATURE SAD WITH MEEM INITIAL FORM;Lo;0;AL; 0635 0645;;;;N;;;;; FCB4;ARABIC LIGATURE DAD WITH JEEM INITIAL FORM;Lo;0;AL; 0636 062C;;;;N;;;;; FCB5;ARABIC LIGATURE DAD WITH HAH INITIAL FORM;Lo;0;AL; 0636 062D;;;;N;;;;; FCB6;ARABIC LIGATURE DAD WITH KHAH INITIAL FORM;Lo;0;AL; 0636 062E;;;;N;;;;; FCB7;ARABIC LIGATURE DAD WITH MEEM INITIAL FORM;Lo;0;AL; 0636 0645;;;;N;;;;; FCB8;ARABIC LIGATURE TAH WITH HAH INITIAL FORM;Lo;0;AL; 0637 062D;;;;N;;;;; FCB9;ARABIC LIGATURE ZAH WITH MEEM INITIAL FORM;Lo;0;AL; 0638 0645;;;;N;;;;; FCBA;ARABIC LIGATURE AIN WITH JEEM INITIAL FORM;Lo;0;AL; 0639 062C;;;;N;;;;; FCBB;ARABIC LIGATURE AIN WITH MEEM INITIAL FORM;Lo;0;AL; 0639 0645;;;;N;;;;; FCBC;ARABIC LIGATURE GHAIN WITH JEEM INITIAL FORM;Lo;0;AL; 063A 062C;;;;N;;;;; FCBD;ARABIC LIGATURE GHAIN WITH MEEM INITIAL FORM;Lo;0;AL; 063A 0645;;;;N;;;;; FCBE;ARABIC LIGATURE FEH WITH JEEM INITIAL FORM;Lo;0;AL; 0641 062C;;;;N;;;;; FCBF;ARABIC LIGATURE FEH WITH HAH INITIAL FORM;Lo;0;AL; 0641 062D;;;;N;;;;; FCC0;ARABIC LIGATURE FEH WITH KHAH INITIAL FORM;Lo;0;AL; 0641 062E;;;;N;;;;; FCC1;ARABIC LIGATURE FEH WITH MEEM INITIAL FORM;Lo;0;AL; 0641 0645;;;;N;;;;; FCC2;ARABIC LIGATURE QAF WITH HAH INITIAL FORM;Lo;0;AL; 0642 062D;;;;N;;;;; FCC3;ARABIC LIGATURE QAF WITH MEEM INITIAL FORM;Lo;0;AL; 0642 0645;;;;N;;;;; FCC4;ARABIC LIGATURE KAF WITH JEEM INITIAL FORM;Lo;0;AL; 0643 062C;;;;N;;;;; FCC5;ARABIC LIGATURE KAF WITH HAH INITIAL FORM;Lo;0;AL; 0643 062D;;;;N;;;;; FCC6;ARABIC LIGATURE KAF WITH KHAH INITIAL FORM;Lo;0;AL; 0643 062E;;;;N;;;;; FCC7;ARABIC LIGATURE KAF WITH LAM INITIAL FORM;Lo;0;AL; 0643 0644;;;;N;;;;; FCC8;ARABIC LIGATURE KAF WITH MEEM INITIAL FORM;Lo;0;AL; 0643 0645;;;;N;;;;; FCC9;ARABIC LIGATURE LAM WITH JEEM INITIAL FORM;Lo;0;AL; 0644 062C;;;;N;;;;; FCCA;ARABIC LIGATURE LAM WITH HAH INITIAL FORM;Lo;0;AL; 0644 062D;;;;N;;;;; FCCB;ARABIC LIGATURE LAM WITH KHAH INITIAL FORM;Lo;0;AL; 0644 062E;;;;N;;;;; FCCC;ARABIC LIGATURE LAM WITH MEEM INITIAL FORM;Lo;0;AL; 0644 0645;;;;N;;;;; FCCD;ARABIC LIGATURE LAM WITH HEH INITIAL FORM;Lo;0;AL; 0644 0647;;;;N;;;;; FCCE;ARABIC LIGATURE MEEM WITH JEEM INITIAL FORM;Lo;0;AL; 0645 062C;;;;N;;;;; FCCF;ARABIC LIGATURE MEEM WITH HAH INITIAL FORM;Lo;0;AL; 0645 062D;;;;N;;;;; FCD0;ARABIC LIGATURE MEEM WITH KHAH INITIAL FORM;Lo;0;AL; 0645 062E;;;;N;;;;; FCD1;ARABIC LIGATURE MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0645 0645;;;;N;;;;; FCD2;ARABIC LIGATURE NOON WITH JEEM INITIAL FORM;Lo;0;AL; 0646 062C;;;;N;;;;; FCD3;ARABIC LIGATURE NOON WITH HAH INITIAL FORM;Lo;0;AL; 0646 062D;;;;N;;;;; FCD4;ARABIC LIGATURE NOON WITH KHAH INITIAL FORM;Lo;0;AL; 0646 062E;;;;N;;;;; FCD5;ARABIC LIGATURE NOON WITH MEEM INITIAL FORM;Lo;0;AL; 0646 0645;;;;N;;;;; FCD6;ARABIC LIGATURE NOON WITH HEH INITIAL FORM;Lo;0;AL; 0646 0647;;;;N;;;;; FCD7;ARABIC LIGATURE HEH WITH JEEM INITIAL FORM;Lo;0;AL; 0647 062C;;;;N;;;;; FCD8;ARABIC LIGATURE HEH WITH MEEM INITIAL FORM;Lo;0;AL; 0647 0645;;;;N;;;;; FCD9;ARABIC LIGATURE HEH WITH SUPERSCRIPT ALEF INITIAL FORM;Lo;0;AL; 0647 0670;;;;N;;;;; FCDA;ARABIC LIGATURE YEH WITH JEEM INITIAL FORM;Lo;0;AL; 064A 062C;;;;N;;;;; FCDB;ARABIC LIGATURE YEH WITH HAH INITIAL FORM;Lo;0;AL; 064A 062D;;;;N;;;;; FCDC;ARABIC LIGATURE YEH WITH KHAH INITIAL FORM;Lo;0;AL; 064A 062E;;;;N;;;;; FCDD;ARABIC LIGATURE YEH WITH MEEM INITIAL FORM;Lo;0;AL; 064A 0645;;;;N;;;;; FCDE;ARABIC LIGATURE YEH WITH HEH INITIAL FORM;Lo;0;AL; 064A 0647;;;;N;;;;; FCDF;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH MEEM MEDIAL FORM;Lo;0;AL; 0626 0645;;;;N;;;;; FCE0;ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH HEH MEDIAL FORM;Lo;0;AL; 0626 0647;;;;N;;;;; FCE1;ARABIC LIGATURE BEH WITH MEEM MEDIAL FORM;Lo;0;AL; 0628 0645;;;;N;;;;; FCE2;ARABIC LIGATURE BEH WITH HEH MEDIAL FORM;Lo;0;AL; 0628 0647;;;;N;;;;; FCE3;ARABIC LIGATURE TEH WITH MEEM MEDIAL FORM;Lo;0;AL; 062A 0645;;;;N;;;;; FCE4;ARABIC LIGATURE TEH WITH HEH MEDIAL FORM;Lo;0;AL; 062A 0647;;;;N;;;;; FCE5;ARABIC LIGATURE THEH WITH MEEM MEDIAL FORM;Lo;0;AL; 062B 0645;;;;N;;;;; FCE6;ARABIC LIGATURE THEH WITH HEH MEDIAL FORM;Lo;0;AL; 062B 0647;;;;N;;;;; FCE7;ARABIC LIGATURE SEEN WITH MEEM MEDIAL FORM;Lo;0;AL; 0633 0645;;;;N;;;;; FCE8;ARABIC LIGATURE SEEN WITH HEH MEDIAL FORM;Lo;0;AL; 0633 0647;;;;N;;;;; FCE9;ARABIC LIGATURE SHEEN WITH MEEM MEDIAL FORM;Lo;0;AL; 0634 0645;;;;N;;;;; FCEA;ARABIC LIGATURE SHEEN WITH HEH MEDIAL FORM;Lo;0;AL; 0634 0647;;;;N;;;;; FCEB;ARABIC LIGATURE KAF WITH LAM MEDIAL FORM;Lo;0;AL; 0643 0644;;;;N;;;;; FCEC;ARABIC LIGATURE KAF WITH MEEM MEDIAL FORM;Lo;0;AL; 0643 0645;;;;N;;;;; FCED;ARABIC LIGATURE LAM WITH MEEM MEDIAL FORM;Lo;0;AL; 0644 0645;;;;N;;;;; FCEE;ARABIC LIGATURE NOON WITH MEEM MEDIAL FORM;Lo;0;AL; 0646 0645;;;;N;;;;; FCEF;ARABIC LIGATURE NOON WITH HEH MEDIAL FORM;Lo;0;AL; 0646 0647;;;;N;;;;; FCF0;ARABIC LIGATURE YEH WITH MEEM MEDIAL FORM;Lo;0;AL; 064A 0645;;;;N;;;;; FCF1;ARABIC LIGATURE YEH WITH HEH MEDIAL FORM;Lo;0;AL; 064A 0647;;;;N;;;;; FCF2;ARABIC LIGATURE SHADDA WITH FATHA MEDIAL FORM;Lo;0;AL; 0640 064E 0651;;;;N;;;;; FCF3;ARABIC LIGATURE SHADDA WITH DAMMA MEDIAL FORM;Lo;0;AL; 0640 064F 0651;;;;N;;;;; FCF4;ARABIC LIGATURE SHADDA WITH KASRA MEDIAL FORM;Lo;0;AL; 0640 0650 0651;;;;N;;;;; FCF5;ARABIC LIGATURE TAH WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 0637 0649;;;;N;;;;; FCF6;ARABIC LIGATURE TAH WITH YEH ISOLATED FORM;Lo;0;AL; 0637 064A;;;;N;;;;; FCF7;ARABIC LIGATURE AIN WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 0639 0649;;;;N;;;;; FCF8;ARABIC LIGATURE AIN WITH YEH ISOLATED FORM;Lo;0;AL; 0639 064A;;;;N;;;;; FCF9;ARABIC LIGATURE GHAIN WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 063A 0649;;;;N;;;;; FCFA;ARABIC LIGATURE GHAIN WITH YEH ISOLATED FORM;Lo;0;AL; 063A 064A;;;;N;;;;; FCFB;ARABIC LIGATURE SEEN WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 0633 0649;;;;N;;;;; FCFC;ARABIC LIGATURE SEEN WITH YEH ISOLATED FORM;Lo;0;AL; 0633 064A;;;;N;;;;; FCFD;ARABIC LIGATURE SHEEN WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 0634 0649;;;;N;;;;; FCFE;ARABIC LIGATURE SHEEN WITH YEH ISOLATED FORM;Lo;0;AL; 0634 064A;;;;N;;;;; FCFF;ARABIC LIGATURE HAH WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 062D 0649;;;;N;;;;; FD00;ARABIC LIGATURE HAH WITH YEH ISOLATED FORM;Lo;0;AL; 062D 064A;;;;N;;;;; FD01;ARABIC LIGATURE JEEM WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 062C 0649;;;;N;;;;; FD02;ARABIC LIGATURE JEEM WITH YEH ISOLATED FORM;Lo;0;AL; 062C 064A;;;;N;;;;; FD03;ARABIC LIGATURE KHAH WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 062E 0649;;;;N;;;;; FD04;ARABIC LIGATURE KHAH WITH YEH ISOLATED FORM;Lo;0;AL; 062E 064A;;;;N;;;;; FD05;ARABIC LIGATURE SAD WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 0635 0649;;;;N;;;;; FD06;ARABIC LIGATURE SAD WITH YEH ISOLATED FORM;Lo;0;AL; 0635 064A;;;;N;;;;; FD07;ARABIC LIGATURE DAD WITH ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 0636 0649;;;;N;;;;; FD08;ARABIC LIGATURE DAD WITH YEH ISOLATED FORM;Lo;0;AL; 0636 064A;;;;N;;;;; FD09;ARABIC LIGATURE SHEEN WITH JEEM ISOLATED FORM;Lo;0;AL; 0634 062C;;;;N;;;;; FD0A;ARABIC LIGATURE SHEEN WITH HAH ISOLATED FORM;Lo;0;AL; 0634 062D;;;;N;;;;; FD0B;ARABIC LIGATURE SHEEN WITH KHAH ISOLATED FORM;Lo;0;AL; 0634 062E;;;;N;;;;; FD0C;ARABIC LIGATURE SHEEN WITH MEEM ISOLATED FORM;Lo;0;AL; 0634 0645;;;;N;;;;; FD0D;ARABIC LIGATURE SHEEN WITH REH ISOLATED FORM;Lo;0;AL; 0634 0631;;;;N;;;;; FD0E;ARABIC LIGATURE SEEN WITH REH ISOLATED FORM;Lo;0;AL; 0633 0631;;;;N;;;;; FD0F;ARABIC LIGATURE SAD WITH REH ISOLATED FORM;Lo;0;AL; 0635 0631;;;;N;;;;; FD10;ARABIC LIGATURE DAD WITH REH ISOLATED FORM;Lo;0;AL; 0636 0631;;;;N;;;;; FD11;ARABIC LIGATURE TAH WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0637 0649;;;;N;;;;; FD12;ARABIC LIGATURE TAH WITH YEH FINAL FORM;Lo;0;AL; 0637 064A;;;;N;;;;; FD13;ARABIC LIGATURE AIN WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0639 0649;;;;N;;;;; FD14;ARABIC LIGATURE AIN WITH YEH FINAL FORM;Lo;0;AL; 0639 064A;;;;N;;;;; FD15;ARABIC LIGATURE GHAIN WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 063A 0649;;;;N;;;;; FD16;ARABIC LIGATURE GHAIN WITH YEH FINAL FORM;Lo;0;AL; 063A 064A;;;;N;;;;; FD17;ARABIC LIGATURE SEEN WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0633 0649;;;;N;;;;; FD18;ARABIC LIGATURE SEEN WITH YEH FINAL FORM;Lo;0;AL; 0633 064A;;;;N;;;;; FD19;ARABIC LIGATURE SHEEN WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0634 0649;;;;N;;;;; FD1A;ARABIC LIGATURE SHEEN WITH YEH FINAL FORM;Lo;0;AL; 0634 064A;;;;N;;;;; FD1B;ARABIC LIGATURE HAH WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 062D 0649;;;;N;;;;; FD1C;ARABIC LIGATURE HAH WITH YEH FINAL FORM;Lo;0;AL; 062D 064A;;;;N;;;;; FD1D;ARABIC LIGATURE JEEM WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 062C 0649;;;;N;;;;; FD1E;ARABIC LIGATURE JEEM WITH YEH FINAL FORM;Lo;0;AL; 062C 064A;;;;N;;;;; FD1F;ARABIC LIGATURE KHAH WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 062E 0649;;;;N;;;;; FD20;ARABIC LIGATURE KHAH WITH YEH FINAL FORM;Lo;0;AL; 062E 064A;;;;N;;;;; FD21;ARABIC LIGATURE SAD WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0635 0649;;;;N;;;;; FD22;ARABIC LIGATURE SAD WITH YEH FINAL FORM;Lo;0;AL; 0635 064A;;;;N;;;;; FD23;ARABIC LIGATURE DAD WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0636 0649;;;;N;;;;; FD24;ARABIC LIGATURE DAD WITH YEH FINAL FORM;Lo;0;AL; 0636 064A;;;;N;;;;; FD25;ARABIC LIGATURE SHEEN WITH JEEM FINAL FORM;Lo;0;AL; 0634 062C;;;;N;;;;; FD26;ARABIC LIGATURE SHEEN WITH HAH FINAL FORM;Lo;0;AL; 0634 062D;;;;N;;;;; FD27;ARABIC LIGATURE SHEEN WITH KHAH FINAL FORM;Lo;0;AL; 0634 062E;;;;N;;;;; FD28;ARABIC LIGATURE SHEEN WITH MEEM FINAL FORM;Lo;0;AL; 0634 0645;;;;N;;;;; FD29;ARABIC LIGATURE SHEEN WITH REH FINAL FORM;Lo;0;AL; 0634 0631;;;;N;;;;; FD2A;ARABIC LIGATURE SEEN WITH REH FINAL FORM;Lo;0;AL; 0633 0631;;;;N;;;;; FD2B;ARABIC LIGATURE SAD WITH REH FINAL FORM;Lo;0;AL; 0635 0631;;;;N;;;;; FD2C;ARABIC LIGATURE DAD WITH REH FINAL FORM;Lo;0;AL; 0636 0631;;;;N;;;;; FD2D;ARABIC LIGATURE SHEEN WITH JEEM INITIAL FORM;Lo;0;AL; 0634 062C;;;;N;;;;; FD2E;ARABIC LIGATURE SHEEN WITH HAH INITIAL FORM;Lo;0;AL; 0634 062D;;;;N;;;;; FD2F;ARABIC LIGATURE SHEEN WITH KHAH INITIAL FORM;Lo;0;AL; 0634 062E;;;;N;;;;; FD30;ARABIC LIGATURE SHEEN WITH MEEM INITIAL FORM;Lo;0;AL; 0634 0645;;;;N;;;;; FD31;ARABIC LIGATURE SEEN WITH HEH INITIAL FORM;Lo;0;AL; 0633 0647;;;;N;;;;; FD32;ARABIC LIGATURE SHEEN WITH HEH INITIAL FORM;Lo;0;AL; 0634 0647;;;;N;;;;; FD33;ARABIC LIGATURE TAH WITH MEEM INITIAL FORM;Lo;0;AL; 0637 0645;;;;N;;;;; FD34;ARABIC LIGATURE SEEN WITH JEEM MEDIAL FORM;Lo;0;AL; 0633 062C;;;;N;;;;; FD35;ARABIC LIGATURE SEEN WITH HAH MEDIAL FORM;Lo;0;AL; 0633 062D;;;;N;;;;; FD36;ARABIC LIGATURE SEEN WITH KHAH MEDIAL FORM;Lo;0;AL; 0633 062E;;;;N;;;;; FD37;ARABIC LIGATURE SHEEN WITH JEEM MEDIAL FORM;Lo;0;AL; 0634 062C;;;;N;;;;; FD38;ARABIC LIGATURE SHEEN WITH HAH MEDIAL FORM;Lo;0;AL; 0634 062D;;;;N;;;;; FD39;ARABIC LIGATURE SHEEN WITH KHAH MEDIAL FORM;Lo;0;AL; 0634 062E;;;;N;;;;; FD3A;ARABIC LIGATURE TAH WITH MEEM MEDIAL FORM;Lo;0;AL; 0637 0645;;;;N;;;;; FD3B;ARABIC LIGATURE ZAH WITH MEEM MEDIAL FORM;Lo;0;AL; 0638 0645;;;;N;;;;; FD3C;ARABIC LIGATURE ALEF WITH FATHATAN FINAL FORM;Lo;0;AL; 0627 064B;;;;N;;;;; FD3D;ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM;Lo;0;AL; 0627 064B;;;;N;;;;; FD3E;ORNATE LEFT PARENTHESIS;Pe;0;ON;;;;;N;;;;; FD3F;ORNATE RIGHT PARENTHESIS;Ps;0;ON;;;;;N;;;;; FD50;ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM;Lo;0;AL; 062A 062C 0645;;;;N;;;;; FD51;ARABIC LIGATURE TEH WITH HAH WITH JEEM FINAL FORM;Lo;0;AL; 062A 062D 062C;;;;N;;;;; FD52;ARABIC LIGATURE TEH WITH HAH WITH JEEM INITIAL FORM;Lo;0;AL; 062A 062D 062C;;;;N;;;;; FD53;ARABIC LIGATURE TEH WITH HAH WITH MEEM INITIAL FORM;Lo;0;AL; 062A 062D 0645;;;;N;;;;; FD54;ARABIC LIGATURE TEH WITH KHAH WITH MEEM INITIAL FORM;Lo;0;AL; 062A 062E 0645;;;;N;;;;; FD55;ARABIC LIGATURE TEH WITH MEEM WITH JEEM INITIAL FORM;Lo;0;AL; 062A 0645 062C;;;;N;;;;; FD56;ARABIC LIGATURE TEH WITH MEEM WITH HAH INITIAL FORM;Lo;0;AL; 062A 0645 062D;;;;N;;;;; FD57;ARABIC LIGATURE TEH WITH MEEM WITH KHAH INITIAL FORM;Lo;0;AL; 062A 0645 062E;;;;N;;;;; FD58;ARABIC LIGATURE JEEM WITH MEEM WITH HAH FINAL FORM;Lo;0;AL; 062C 0645 062D;;;;N;;;;; FD59;ARABIC LIGATURE JEEM WITH MEEM WITH HAH INITIAL FORM;Lo;0;AL; 062C 0645 062D;;;;N;;;;; FD5A;ARABIC LIGATURE HAH WITH MEEM WITH YEH FINAL FORM;Lo;0;AL; 062D 0645 064A;;;;N;;;;; FD5B;ARABIC LIGATURE HAH WITH MEEM WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 062D 0645 0649;;;;N;;;;; FD5C;ARABIC LIGATURE SEEN WITH HAH WITH JEEM INITIAL FORM;Lo;0;AL; 0633 062D 062C;;;;N;;;;; FD5D;ARABIC LIGATURE SEEN WITH JEEM WITH HAH INITIAL FORM;Lo;0;AL; 0633 062C 062D;;;;N;;;;; FD5E;ARABIC LIGATURE SEEN WITH JEEM WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0633 062C 0649;;;;N;;;;; FD5F;ARABIC LIGATURE SEEN WITH MEEM WITH HAH FINAL FORM;Lo;0;AL; 0633 0645 062D;;;;N;;;;; FD60;ARABIC LIGATURE SEEN WITH MEEM WITH HAH INITIAL FORM;Lo;0;AL; 0633 0645 062D;;;;N;;;;; FD61;ARABIC LIGATURE SEEN WITH MEEM WITH JEEM INITIAL FORM;Lo;0;AL; 0633 0645 062C;;;;N;;;;; FD62;ARABIC LIGATURE SEEN WITH MEEM WITH MEEM FINAL FORM;Lo;0;AL; 0633 0645 0645;;;;N;;;;; FD63;ARABIC LIGATURE SEEN WITH MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0633 0645 0645;;;;N;;;;; FD64;ARABIC LIGATURE SAD WITH HAH WITH HAH FINAL FORM;Lo;0;AL; 0635 062D 062D;;;;N;;;;; FD65;ARABIC LIGATURE SAD WITH HAH WITH HAH INITIAL FORM;Lo;0;AL; 0635 062D 062D;;;;N;;;;; FD66;ARABIC LIGATURE SAD WITH MEEM WITH MEEM FINAL FORM;Lo;0;AL; 0635 0645 0645;;;;N;;;;; FD67;ARABIC LIGATURE SHEEN WITH HAH WITH MEEM FINAL FORM;Lo;0;AL; 0634 062D 0645;;;;N;;;;; FD68;ARABIC LIGATURE SHEEN WITH HAH WITH MEEM INITIAL FORM;Lo;0;AL; 0634 062D 0645;;;;N;;;;; FD69;ARABIC LIGATURE SHEEN WITH JEEM WITH YEH FINAL FORM;Lo;0;AL; 0634 062C 064A;;;;N;;;;; FD6A;ARABIC LIGATURE SHEEN WITH MEEM WITH KHAH FINAL FORM;Lo;0;AL; 0634 0645 062E;;;;N;;;;; FD6B;ARABIC LIGATURE SHEEN WITH MEEM WITH KHAH INITIAL FORM;Lo;0;AL; 0634 0645 062E;;;;N;;;;; FD6C;ARABIC LIGATURE SHEEN WITH MEEM WITH MEEM FINAL FORM;Lo;0;AL; 0634 0645 0645;;;;N;;;;; FD6D;ARABIC LIGATURE SHEEN WITH MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0634 0645 0645;;;;N;;;;; FD6E;ARABIC LIGATURE DAD WITH HAH WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0636 062D 0649;;;;N;;;;; FD6F;ARABIC LIGATURE DAD WITH KHAH WITH MEEM FINAL FORM;Lo;0;AL; 0636 062E 0645;;;;N;;;;; FD70;ARABIC LIGATURE DAD WITH KHAH WITH MEEM INITIAL FORM;Lo;0;AL; 0636 062E 0645;;;;N;;;;; FD71;ARABIC LIGATURE TAH WITH MEEM WITH HAH FINAL FORM;Lo;0;AL; 0637 0645 062D;;;;N;;;;; FD72;ARABIC LIGATURE TAH WITH MEEM WITH HAH INITIAL FORM;Lo;0;AL; 0637 0645 062D;;;;N;;;;; FD73;ARABIC LIGATURE TAH WITH MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0637 0645 0645;;;;N;;;;; FD74;ARABIC LIGATURE TAH WITH MEEM WITH YEH FINAL FORM;Lo;0;AL; 0637 0645 064A;;;;N;;;;; FD75;ARABIC LIGATURE AIN WITH JEEM WITH MEEM FINAL FORM;Lo;0;AL; 0639 062C 0645;;;;N;;;;; FD76;ARABIC LIGATURE AIN WITH MEEM WITH MEEM FINAL FORM;Lo;0;AL; 0639 0645 0645;;;;N;;;;; FD77;ARABIC LIGATURE AIN WITH MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0639 0645 0645;;;;N;;;;; FD78;ARABIC LIGATURE AIN WITH MEEM WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0639 0645 0649;;;;N;;;;; FD79;ARABIC LIGATURE GHAIN WITH MEEM WITH MEEM FINAL FORM;Lo;0;AL; 063A 0645 0645;;;;N;;;;; FD7A;ARABIC LIGATURE GHAIN WITH MEEM WITH YEH FINAL FORM;Lo;0;AL; 063A 0645 064A;;;;N;;;;; FD7B;ARABIC LIGATURE GHAIN WITH MEEM WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 063A 0645 0649;;;;N;;;;; FD7C;ARABIC LIGATURE FEH WITH KHAH WITH MEEM FINAL FORM;Lo;0;AL; 0641 062E 0645;;;;N;;;;; FD7D;ARABIC LIGATURE FEH WITH KHAH WITH MEEM INITIAL FORM;Lo;0;AL; 0641 062E 0645;;;;N;;;;; FD7E;ARABIC LIGATURE QAF WITH MEEM WITH HAH FINAL FORM;Lo;0;AL; 0642 0645 062D;;;;N;;;;; FD7F;ARABIC LIGATURE QAF WITH MEEM WITH MEEM FINAL FORM;Lo;0;AL; 0642 0645 0645;;;;N;;;;; FD80;ARABIC LIGATURE LAM WITH HAH WITH MEEM FINAL FORM;Lo;0;AL; 0644 062D 0645;;;;N;;;;; FD81;ARABIC LIGATURE LAM WITH HAH WITH YEH FINAL FORM;Lo;0;AL; 0644 062D 064A;;;;N;;;;; FD82;ARABIC LIGATURE LAM WITH HAH WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0644 062D 0649;;;;N;;;;; FD83;ARABIC LIGATURE LAM WITH JEEM WITH JEEM INITIAL FORM;Lo;0;AL; 0644 062C 062C;;;;N;;;;; FD84;ARABIC LIGATURE LAM WITH JEEM WITH JEEM FINAL FORM;Lo;0;AL; 0644 062C 062C;;;;N;;;;; FD85;ARABIC LIGATURE LAM WITH KHAH WITH MEEM FINAL FORM;Lo;0;AL; 0644 062E 0645;;;;N;;;;; FD86;ARABIC LIGATURE LAM WITH KHAH WITH MEEM INITIAL FORM;Lo;0;AL; 0644 062E 0645;;;;N;;;;; FD87;ARABIC LIGATURE LAM WITH MEEM WITH HAH FINAL FORM;Lo;0;AL; 0644 0645 062D;;;;N;;;;; FD88;ARABIC LIGATURE LAM WITH MEEM WITH HAH INITIAL FORM;Lo;0;AL; 0644 0645 062D;;;;N;;;;; FD89;ARABIC LIGATURE MEEM WITH HAH WITH JEEM INITIAL FORM;Lo;0;AL; 0645 062D 062C;;;;N;;;;; FD8A;ARABIC LIGATURE MEEM WITH HAH WITH MEEM INITIAL FORM;Lo;0;AL; 0645 062D 0645;;;;N;;;;; FD8B;ARABIC LIGATURE MEEM WITH HAH WITH YEH FINAL FORM;Lo;0;AL; 0645 062D 064A;;;;N;;;;; FD8C;ARABIC LIGATURE MEEM WITH JEEM WITH HAH INITIAL FORM;Lo;0;AL; 0645 062C 062D;;;;N;;;;; FD8D;ARABIC LIGATURE MEEM WITH JEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0645 062C 0645;;;;N;;;;; FD8E;ARABIC LIGATURE MEEM WITH KHAH WITH JEEM INITIAL FORM;Lo;0;AL; 0645 062E 062C;;;;N;;;;; FD8F;ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM;Lo;0;AL; 0645 062E 0645;;;;N;;;;; FD92;ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM;Lo;0;AL; 0645 062C 062E;;;;N;;;;; FD93;ARABIC LIGATURE HEH WITH MEEM WITH JEEM INITIAL FORM;Lo;0;AL; 0647 0645 062C;;;;N;;;;; FD94;ARABIC LIGATURE HEH WITH MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0647 0645 0645;;;;N;;;;; FD95;ARABIC LIGATURE NOON WITH HAH WITH MEEM INITIAL FORM;Lo;0;AL; 0646 062D 0645;;;;N;;;;; FD96;ARABIC LIGATURE NOON WITH HAH WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0646 062D 0649;;;;N;;;;; FD97;ARABIC LIGATURE NOON WITH JEEM WITH MEEM FINAL FORM;Lo;0;AL; 0646 062C 0645;;;;N;;;;; FD98;ARABIC LIGATURE NOON WITH JEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0646 062C 0645;;;;N;;;;; FD99;ARABIC LIGATURE NOON WITH JEEM WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0646 062C 0649;;;;N;;;;; FD9A;ARABIC LIGATURE NOON WITH MEEM WITH YEH FINAL FORM;Lo;0;AL; 0646 0645 064A;;;;N;;;;; FD9B;ARABIC LIGATURE NOON WITH MEEM WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0646 0645 0649;;;;N;;;;; FD9C;ARABIC LIGATURE YEH WITH MEEM WITH MEEM FINAL FORM;Lo;0;AL; 064A 0645 0645;;;;N;;;;; FD9D;ARABIC LIGATURE YEH WITH MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 064A 0645 0645;;;;N;;;;; FD9E;ARABIC LIGATURE BEH WITH KHAH WITH YEH FINAL FORM;Lo;0;AL; 0628 062E 064A;;;;N;;;;; FD9F;ARABIC LIGATURE TEH WITH JEEM WITH YEH FINAL FORM;Lo;0;AL; 062A 062C 064A;;;;N;;;;; FDA0;ARABIC LIGATURE TEH WITH JEEM WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 062A 062C 0649;;;;N;;;;; FDA1;ARABIC LIGATURE TEH WITH KHAH WITH YEH FINAL FORM;Lo;0;AL; 062A 062E 064A;;;;N;;;;; FDA2;ARABIC LIGATURE TEH WITH KHAH WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 062A 062E 0649;;;;N;;;;; FDA3;ARABIC LIGATURE TEH WITH MEEM WITH YEH FINAL FORM;Lo;0;AL; 062A 0645 064A;;;;N;;;;; FDA4;ARABIC LIGATURE TEH WITH MEEM WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 062A 0645 0649;;;;N;;;;; FDA5;ARABIC LIGATURE JEEM WITH MEEM WITH YEH FINAL FORM;Lo;0;AL; 062C 0645 064A;;;;N;;;;; FDA6;ARABIC LIGATURE JEEM WITH HAH WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 062C 062D 0649;;;;N;;;;; FDA7;ARABIC LIGATURE JEEM WITH MEEM WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 062C 0645 0649;;;;N;;;;; FDA8;ARABIC LIGATURE SEEN WITH KHAH WITH ALEF MAKSURA FINAL FORM;Lo;0;AL; 0633 062E 0649;;;;N;;;;; FDA9;ARABIC LIGATURE SAD WITH HAH WITH YEH FINAL FORM;Lo;0;AL; 0635 062D 064A;;;;N;;;;; FDAA;ARABIC LIGATURE SHEEN WITH HAH WITH YEH FINAL FORM;Lo;0;AL; 0634 062D 064A;;;;N;;;;; FDAB;ARABIC LIGATURE DAD WITH HAH WITH YEH FINAL FORM;Lo;0;AL; 0636 062D 064A;;;;N;;;;; FDAC;ARABIC LIGATURE LAM WITH JEEM WITH YEH FINAL FORM;Lo;0;AL; 0644 062C 064A;;;;N;;;;; FDAD;ARABIC LIGATURE LAM WITH MEEM WITH YEH FINAL FORM;Lo;0;AL; 0644 0645 064A;;;;N;;;;; FDAE;ARABIC LIGATURE YEH WITH HAH WITH YEH FINAL FORM;Lo;0;AL; 064A 062D 064A;;;;N;;;;; FDAF;ARABIC LIGATURE YEH WITH JEEM WITH YEH FINAL FORM;Lo;0;AL; 064A 062C 064A;;;;N;;;;; FDB0;ARABIC LIGATURE YEH WITH MEEM WITH YEH FINAL FORM;Lo;0;AL; 064A 0645 064A;;;;N;;;;; FDB1;ARABIC LIGATURE MEEM WITH MEEM WITH YEH FINAL FORM;Lo;0;AL; 0645 0645 064A;;;;N;;;;; FDB2;ARABIC LIGATURE QAF WITH MEEM WITH YEH FINAL FORM;Lo;0;AL; 0642 0645 064A;;;;N;;;;; FDB3;ARABIC LIGATURE NOON WITH HAH WITH YEH FINAL FORM;Lo;0;AL; 0646 062D 064A;;;;N;;;;; FDB4;ARABIC LIGATURE QAF WITH MEEM WITH HAH INITIAL FORM;Lo;0;AL; 0642 0645 062D;;;;N;;;;; FDB5;ARABIC LIGATURE LAM WITH HAH WITH MEEM INITIAL FORM;Lo;0;AL; 0644 062D 0645;;;;N;;;;; FDB6;ARABIC LIGATURE AIN WITH MEEM WITH YEH FINAL FORM;Lo;0;AL; 0639 0645 064A;;;;N;;;;; FDB7;ARABIC LIGATURE KAF WITH MEEM WITH YEH FINAL FORM;Lo;0;AL; 0643 0645 064A;;;;N;;;;; FDB8;ARABIC LIGATURE NOON WITH JEEM WITH HAH INITIAL FORM;Lo;0;AL; 0646 062C 062D;;;;N;;;;; FDB9;ARABIC LIGATURE MEEM WITH KHAH WITH YEH FINAL FORM;Lo;0;AL; 0645 062E 064A;;;;N;;;;; FDBA;ARABIC LIGATURE LAM WITH JEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0644 062C 0645;;;;N;;;;; FDBB;ARABIC LIGATURE KAF WITH MEEM WITH MEEM FINAL FORM;Lo;0;AL; 0643 0645 0645;;;;N;;;;; FDBC;ARABIC LIGATURE LAM WITH JEEM WITH MEEM FINAL FORM;Lo;0;AL; 0644 062C 0645;;;;N;;;;; FDBD;ARABIC LIGATURE NOON WITH JEEM WITH HAH FINAL FORM;Lo;0;AL; 0646 062C 062D;;;;N;;;;; FDBE;ARABIC LIGATURE JEEM WITH HAH WITH YEH FINAL FORM;Lo;0;AL; 062C 062D 064A;;;;N;;;;; FDBF;ARABIC LIGATURE HAH WITH JEEM WITH YEH FINAL FORM;Lo;0;AL; 062D 062C 064A;;;;N;;;;; FDC0;ARABIC LIGATURE MEEM WITH JEEM WITH YEH FINAL FORM;Lo;0;AL; 0645 062C 064A;;;;N;;;;; FDC1;ARABIC LIGATURE FEH WITH MEEM WITH YEH FINAL FORM;Lo;0;AL; 0641 0645 064A;;;;N;;;;; FDC2;ARABIC LIGATURE BEH WITH HAH WITH YEH FINAL FORM;Lo;0;AL; 0628 062D 064A;;;;N;;;;; FDC3;ARABIC LIGATURE KAF WITH MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0643 0645 0645;;;;N;;;;; FDC4;ARABIC LIGATURE AIN WITH JEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0639 062C 0645;;;;N;;;;; FDC5;ARABIC LIGATURE SAD WITH MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0635 0645 0645;;;;N;;;;; FDC6;ARABIC LIGATURE SEEN WITH KHAH WITH YEH FINAL FORM;Lo;0;AL; 0633 062E 064A;;;;N;;;;; FDC7;ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM;Lo;0;AL; 0646 062C 064A;;;;N;;;;; FDF0;ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM;Lo;0;AL; 0635 0644 06D2;;;;N;;;;; FDF1;ARABIC LIGATURE QALA USED AS KORANIC STOP SIGN ISOLATED FORM;Lo;0;AL; 0642 0644 06D2;;;;N;;;;; FDF2;ARABIC LIGATURE ALLAH ISOLATED FORM;Lo;0;AL; 0627 0644 0644 0647;;;;N;;;;; FDF3;ARABIC LIGATURE AKBAR ISOLATED FORM;Lo;0;AL; 0627 0643 0628 0631;;;;N;;;;; FDF4;ARABIC LIGATURE MOHAMMAD ISOLATED FORM;Lo;0;AL; 0645 062D 0645 062F;;;;N;;;;; FDF5;ARABIC LIGATURE SALAM ISOLATED FORM;Lo;0;AL; 0635 0644 0639 0645;;;;N;;;;; FDF6;ARABIC LIGATURE RASOUL ISOLATED FORM;Lo;0;AL; 0631 0633 0648 0644;;;;N;;;;; FDF7;ARABIC LIGATURE ALAYHE ISOLATED FORM;Lo;0;AL; 0639 0644 064A 0647;;;;N;;;;; FDF8;ARABIC LIGATURE WASALLAM ISOLATED FORM;Lo;0;AL; 0648 0633 0644 0645;;;;N;;;;; FDF9;ARABIC LIGATURE SALLA ISOLATED FORM;Lo;0;AL; 0635 0644 0649;;;;N;;;;; FDFA;ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM;Lo;0;AL; 0635 0644 0649 0020 0627 0644 0644 0647 0020 0639 0644 064A 0647 0020 0648 0633 0644 0645;;;;N;ARABIC LETTER SALLALLAHOU ALAYHE WASALLAM;;;; FDFB;ARABIC LIGATURE JALLAJALALOUHOU;Lo;0;AL; 062C 0644 0020 062C 0644 0627 0644 0647;;;;N;ARABIC LETTER JALLAJALALOUHOU;;;; FDFC;RIAL SIGN;Sc;0;AL; 0631 06CC 0627 0644;;;;N;;;;; FDFD;ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM;So;0;ON;;;;;N;;;;; FE00;VARIATION SELECTOR-1;Mn;0;NSM;;;;;N;;;;; FE01;VARIATION SELECTOR-2;Mn;0;NSM;;;;;N;;;;; FE02;VARIATION SELECTOR-3;Mn;0;NSM;;;;;N;;;;; FE03;VARIATION SELECTOR-4;Mn;0;NSM;;;;;N;;;;; FE04;VARIATION SELECTOR-5;Mn;0;NSM;;;;;N;;;;; FE05;VARIATION SELECTOR-6;Mn;0;NSM;;;;;N;;;;; FE06;VARIATION SELECTOR-7;Mn;0;NSM;;;;;N;;;;; FE07;VARIATION SELECTOR-8;Mn;0;NSM;;;;;N;;;;; FE08;VARIATION SELECTOR-9;Mn;0;NSM;;;;;N;;;;; FE09;VARIATION SELECTOR-10;Mn;0;NSM;;;;;N;;;;; FE0A;VARIATION SELECTOR-11;Mn;0;NSM;;;;;N;;;;; FE0B;VARIATION SELECTOR-12;Mn;0;NSM;;;;;N;;;;; FE0C;VARIATION SELECTOR-13;Mn;0;NSM;;;;;N;;;;; FE0D;VARIATION SELECTOR-14;Mn;0;NSM;;;;;N;;;;; FE0E;VARIATION SELECTOR-15;Mn;0;NSM;;;;;N;;;;; FE0F;VARIATION SELECTOR-16;Mn;0;NSM;;;;;N;;;;; FE10;PRESENTATION FORM FOR VERTICAL COMMA;Po;0;ON; 002C;;;;N;;;;; FE11;PRESENTATION FORM FOR VERTICAL IDEOGRAPHIC COMMA;Po;0;ON; 3001;;;;N;;;;; FE12;PRESENTATION FORM FOR VERTICAL IDEOGRAPHIC FULL STOP;Po;0;ON; 3002;;;;N;;;;; FE13;PRESENTATION FORM FOR VERTICAL COLON;Po;0;ON; 003A;;;;N;;;;; FE14;PRESENTATION FORM FOR VERTICAL SEMICOLON;Po;0;ON; 003B;;;;N;;;;; FE15;PRESENTATION FORM FOR VERTICAL EXCLAMATION MARK;Po;0;ON; 0021;;;;N;;;;; FE16;PRESENTATION FORM FOR VERTICAL QUESTION MARK;Po;0;ON; 003F;;;;N;;;;; FE17;PRESENTATION FORM FOR VERTICAL LEFT WHITE LENTICULAR BRACKET;Ps;0;ON; 3016;;;;N;;;;; FE18;PRESENTATION FORM FOR VERTICAL RIGHT WHITE LENTICULAR BRAKCET;Pe;0;ON; 3017;;;;N;;;;; FE19;PRESENTATION FORM FOR VERTICAL HORIZONTAL ELLIPSIS;Po;0;ON; 2026;;;;N;;;;; FE20;COMBINING LIGATURE LEFT HALF;Mn;230;NSM;;;;;N;;;;; FE21;COMBINING LIGATURE RIGHT HALF;Mn;230;NSM;;;;;N;;;;; FE22;COMBINING DOUBLE TILDE LEFT HALF;Mn;230;NSM;;;;;N;;;;; FE23;COMBINING DOUBLE TILDE RIGHT HALF;Mn;230;NSM;;;;;N;;;;; FE24;COMBINING MACRON LEFT HALF;Mn;230;NSM;;;;;N;;;;; FE25;COMBINING MACRON RIGHT HALF;Mn;230;NSM;;;;;N;;;;; FE26;COMBINING CONJOINING MACRON;Mn;230;NSM;;;;;N;;;;; FE27;COMBINING LIGATURE LEFT HALF BELOW;Mn;220;NSM;;;;;N;;;;; FE28;COMBINING LIGATURE RIGHT HALF BELOW;Mn;220;NSM;;;;;N;;;;; FE29;COMBINING TILDE LEFT HALF BELOW;Mn;220;NSM;;;;;N;;;;; FE2A;COMBINING TILDE RIGHT HALF BELOW;Mn;220;NSM;;;;;N;;;;; FE2B;COMBINING MACRON LEFT HALF BELOW;Mn;220;NSM;;;;;N;;;;; FE2C;COMBINING MACRON RIGHT HALF BELOW;Mn;220;NSM;;;;;N;;;;; FE2D;COMBINING CONJOINING MACRON BELOW;Mn;220;NSM;;;;;N;;;;; FE2E;COMBINING CYRILLIC TITLO LEFT HALF;Mn;230;NSM;;;;;N;;;;; FE2F;COMBINING CYRILLIC TITLO RIGHT HALF;Mn;230;NSM;;;;;N;;;;; FE30;PRESENTATION FORM FOR VERTICAL TWO DOT LEADER;Po;0;ON; 2025;;;;N;GLYPH FOR VERTICAL TWO DOT LEADER;;;; FE31;PRESENTATION FORM FOR VERTICAL EM DASH;Pd;0;ON; 2014;;;;N;GLYPH FOR VERTICAL EM DASH;;;; FE32;PRESENTATION FORM FOR VERTICAL EN DASH;Pd;0;ON; 2013;;;;N;GLYPH FOR VERTICAL EN DASH;;;; FE33;PRESENTATION FORM FOR VERTICAL LOW LINE;Pc;0;ON; 005F;;;;N;GLYPH FOR VERTICAL SPACING UNDERSCORE;;;; FE34;PRESENTATION FORM FOR VERTICAL WAVY LOW LINE;Pc;0;ON; 005F;;;;N;GLYPH FOR VERTICAL SPACING WAVY UNDERSCORE;;;; FE35;PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS;Ps;0;ON; 0028;;;;N;GLYPH FOR VERTICAL OPENING PARENTHESIS;;;; FE36;PRESENTATION FORM FOR VERTICAL RIGHT PARENTHESIS;Pe;0;ON; 0029;;;;N;GLYPH FOR VERTICAL CLOSING PARENTHESIS;;;; FE37;PRESENTATION FORM FOR VERTICAL LEFT CURLY BRACKET;Ps;0;ON; 007B;;;;N;GLYPH FOR VERTICAL OPENING CURLY BRACKET;;;; FE38;PRESENTATION FORM FOR VERTICAL RIGHT CURLY BRACKET;Pe;0;ON; 007D;;;;N;GLYPH FOR VERTICAL CLOSING CURLY BRACKET;;;; FE39;PRESENTATION FORM FOR VERTICAL LEFT TORTOISE SHELL BRACKET;Ps;0;ON; 3014;;;;N;GLYPH FOR VERTICAL OPENING TORTOISE SHELL BRACKET;;;; FE3A;PRESENTATION FORM FOR VERTICAL RIGHT TORTOISE SHELL BRACKET;Pe;0;ON; 3015;;;;N;GLYPH FOR VERTICAL CLOSING TORTOISE SHELL BRACKET;;;; FE3B;PRESENTATION FORM FOR VERTICAL LEFT BLACK LENTICULAR BRACKET;Ps;0;ON; 3010;;;;N;GLYPH FOR VERTICAL OPENING BLACK LENTICULAR BRACKET;;;; FE3C;PRESENTATION FORM FOR VERTICAL RIGHT BLACK LENTICULAR BRACKET;Pe;0;ON; 3011;;;;N;GLYPH FOR VERTICAL CLOSING BLACK LENTICULAR BRACKET;;;; FE3D;PRESENTATION FORM FOR VERTICAL LEFT DOUBLE ANGLE BRACKET;Ps;0;ON; 300A;;;;N;GLYPH FOR VERTICAL OPENING DOUBLE ANGLE BRACKET;;;; FE3E;PRESENTATION FORM FOR VERTICAL RIGHT DOUBLE ANGLE BRACKET;Pe;0;ON; 300B;;;;N;GLYPH FOR VERTICAL CLOSING DOUBLE ANGLE BRACKET;;;; FE3F;PRESENTATION FORM FOR VERTICAL LEFT ANGLE BRACKET;Ps;0;ON; 3008;;;;N;GLYPH FOR VERTICAL OPENING ANGLE BRACKET;;;; FE40;PRESENTATION FORM FOR VERTICAL RIGHT ANGLE BRACKET;Pe;0;ON; 3009;;;;N;GLYPH FOR VERTICAL CLOSING ANGLE BRACKET;;;; FE41;PRESENTATION FORM FOR VERTICAL LEFT CORNER BRACKET;Ps;0;ON; 300C;;;;N;GLYPH FOR VERTICAL OPENING CORNER BRACKET;;;; FE42;PRESENTATION FORM FOR VERTICAL RIGHT CORNER BRACKET;Pe;0;ON; 300D;;;;N;GLYPH FOR VERTICAL CLOSING CORNER BRACKET;;;; FE43;PRESENTATION FORM FOR VERTICAL LEFT WHITE CORNER BRACKET;Ps;0;ON; 300E;;;;N;GLYPH FOR VERTICAL OPENING WHITE CORNER BRACKET;;;; FE44;PRESENTATION FORM FOR VERTICAL RIGHT WHITE CORNER BRACKET;Pe;0;ON; 300F;;;;N;GLYPH FOR VERTICAL CLOSING WHITE CORNER BRACKET;;;; FE45;SESAME DOT;Po;0;ON;;;;;N;;;;; FE46;WHITE SESAME DOT;Po;0;ON;;;;;N;;;;; FE47;PRESENTATION FORM FOR VERTICAL LEFT SQUARE BRACKET;Ps;0;ON; 005B;;;;N;;;;; FE48;PRESENTATION FORM FOR VERTICAL RIGHT SQUARE BRACKET;Pe;0;ON; 005D;;;;N;;;;; FE49;DASHED OVERLINE;Po;0;ON; 203E;;;;N;SPACING DASHED OVERSCORE;;;; FE4A;CENTRELINE OVERLINE;Po;0;ON; 203E;;;;N;SPACING CENTERLINE OVERSCORE;;;; FE4B;WAVY OVERLINE;Po;0;ON; 203E;;;;N;SPACING WAVY OVERSCORE;;;; FE4C;DOUBLE WAVY OVERLINE;Po;0;ON; 203E;;;;N;SPACING DOUBLE WAVY OVERSCORE;;;; FE4D;DASHED LOW LINE;Pc;0;ON; 005F;;;;N;SPACING DASHED UNDERSCORE;;;; FE4E;CENTRELINE LOW LINE;Pc;0;ON; 005F;;;;N;SPACING CENTERLINE UNDERSCORE;;;; FE4F;WAVY LOW LINE;Pc;0;ON; 005F;;;;N;SPACING WAVY UNDERSCORE;;;; FE50;SMALL COMMA;Po;0;CS; 002C;;;;N;;;;; FE51;SMALL IDEOGRAPHIC COMMA;Po;0;ON; 3001;;;;N;;;;; FE52;SMALL FULL STOP;Po;0;CS; 002E;;;;N;SMALL PERIOD;;;; FE54;SMALL SEMICOLON;Po;0;ON; 003B;;;;N;;;;; FE55;SMALL COLON;Po;0;CS; 003A;;;;N;;;;; FE56;SMALL QUESTION MARK;Po;0;ON; 003F;;;;N;;;;; FE57;SMALL EXCLAMATION MARK;Po;0;ON; 0021;;;;N;;;;; FE58;SMALL EM DASH;Pd;0;ON; 2014;;;;N;;;;; FE59;SMALL LEFT PARENTHESIS;Ps;0;ON; 0028;;;;Y;SMALL OPENING PARENTHESIS;;;; FE5A;SMALL RIGHT PARENTHESIS;Pe;0;ON; 0029;;;;Y;SMALL CLOSING PARENTHESIS;;;; FE5B;SMALL LEFT CURLY BRACKET;Ps;0;ON; 007B;;;;Y;SMALL OPENING CURLY BRACKET;;;; FE5C;SMALL RIGHT CURLY BRACKET;Pe;0;ON; 007D;;;;Y;SMALL CLOSING CURLY BRACKET;;;; FE5D;SMALL LEFT TORTOISE SHELL BRACKET;Ps;0;ON; 3014;;;;Y;SMALL OPENING TORTOISE SHELL BRACKET;;;; FE5E;SMALL RIGHT TORTOISE SHELL BRACKET;Pe;0;ON; 3015;;;;Y;SMALL CLOSING TORTOISE SHELL BRACKET;;;; FE5F;SMALL NUMBER SIGN;Po;0;ET; 0023;;;;N;;;;; FE60;SMALL AMPERSAND;Po;0;ON; 0026;;;;N;;;;; FE61;SMALL ASTERISK;Po;0;ON; 002A;;;;N;;;;; FE62;SMALL PLUS SIGN;Sm;0;ES; 002B;;;;N;;;;; FE63;SMALL HYPHEN-MINUS;Pd;0;ES; 002D;;;;N;;;;; FE64;SMALL LESS-THAN SIGN;Sm;0;ON; 003C;;;;Y;;;;; FE65;SMALL GREATER-THAN SIGN;Sm;0;ON; 003E;;;;Y;;;;; FE66;SMALL EQUALS SIGN;Sm;0;ON; 003D;;;;N;;;;; FE68;SMALL REVERSE SOLIDUS;Po;0;ON; 005C;;;;N;SMALL BACKSLASH;;;; FE69;SMALL DOLLAR SIGN;Sc;0;ET; 0024;;;;N;;;;; FE6A;SMALL PERCENT SIGN;Po;0;ET; 0025;;;;N;;;;; FE6B;SMALL COMMERCIAL AT;Po;0;ON; 0040;;;;N;;;;; FE70;ARABIC FATHATAN ISOLATED FORM;Lo;0;AL; 0020 064B;;;;N;ARABIC SPACING FATHATAN;;;; FE71;ARABIC TATWEEL WITH FATHATAN ABOVE;Lo;0;AL; 0640 064B;;;;N;ARABIC FATHATAN ON TATWEEL;;;; FE72;ARABIC DAMMATAN ISOLATED FORM;Lo;0;AL; 0020 064C;;;;N;ARABIC SPACING DAMMATAN;;;; FE73;ARABIC TAIL FRAGMENT;Lo;0;AL;;;;;N;;;;; FE74;ARABIC KASRATAN ISOLATED FORM;Lo;0;AL; 0020 064D;;;;N;ARABIC SPACING KASRATAN;;;; FE76;ARABIC FATHA ISOLATED FORM;Lo;0;AL; 0020 064E;;;;N;ARABIC SPACING FATHAH;;;; FE77;ARABIC FATHA MEDIAL FORM;Lo;0;AL; 0640 064E;;;;N;ARABIC FATHAH ON TATWEEL;;;; FE78;ARABIC DAMMA ISOLATED FORM;Lo;0;AL; 0020 064F;;;;N;ARABIC SPACING DAMMAH;;;; FE79;ARABIC DAMMA MEDIAL FORM;Lo;0;AL; 0640 064F;;;;N;ARABIC DAMMAH ON TATWEEL;;;; FE7A;ARABIC KASRA ISOLATED FORM;Lo;0;AL; 0020 0650;;;;N;ARABIC SPACING KASRAH;;;; FE7B;ARABIC KASRA MEDIAL FORM;Lo;0;AL; 0640 0650;;;;N;ARABIC KASRAH ON TATWEEL;;;; FE7C;ARABIC SHADDA ISOLATED FORM;Lo;0;AL; 0020 0651;;;;N;ARABIC SPACING SHADDAH;;;; FE7D;ARABIC SHADDA MEDIAL FORM;Lo;0;AL; 0640 0651;;;;N;ARABIC SHADDAH ON TATWEEL;;;; FE7E;ARABIC SUKUN ISOLATED FORM;Lo;0;AL; 0020 0652;;;;N;ARABIC SPACING SUKUN;;;; FE7F;ARABIC SUKUN MEDIAL FORM;Lo;0;AL; 0640 0652;;;;N;ARABIC SUKUN ON TATWEEL;;;; FE80;ARABIC LETTER HAMZA ISOLATED FORM;Lo;0;AL; 0621;;;;N;GLYPH FOR ISOLATE ARABIC HAMZAH;;;; FE81;ARABIC LETTER ALEF WITH MADDA ABOVE ISOLATED FORM;Lo;0;AL; 0622;;;;N;GLYPH FOR ISOLATE ARABIC MADDAH ON ALEF;;;; FE82;ARABIC LETTER ALEF WITH MADDA ABOVE FINAL FORM;Lo;0;AL; 0622;;;;N;GLYPH FOR FINAL ARABIC MADDAH ON ALEF;;;; FE83;ARABIC LETTER ALEF WITH HAMZA ABOVE ISOLATED FORM;Lo;0;AL; 0623;;;;N;GLYPH FOR ISOLATE ARABIC HAMZAH ON ALEF;;;; FE84;ARABIC LETTER ALEF WITH HAMZA ABOVE FINAL FORM;Lo;0;AL; 0623;;;;N;GLYPH FOR FINAL ARABIC HAMZAH ON ALEF;;;; FE85;ARABIC LETTER WAW WITH HAMZA ABOVE ISOLATED FORM;Lo;0;AL; 0624;;;;N;GLYPH FOR ISOLATE ARABIC HAMZAH ON WAW;;;; FE86;ARABIC LETTER WAW WITH HAMZA ABOVE FINAL FORM;Lo;0;AL; 0624;;;;N;GLYPH FOR FINAL ARABIC HAMZAH ON WAW;;;; FE87;ARABIC LETTER ALEF WITH HAMZA BELOW ISOLATED FORM;Lo;0;AL; 0625;;;;N;GLYPH FOR ISOLATE ARABIC HAMZAH UNDER ALEF;;;; FE88;ARABIC LETTER ALEF WITH HAMZA BELOW FINAL FORM;Lo;0;AL; 0625;;;;N;GLYPH FOR FINAL ARABIC HAMZAH UNDER ALEF;;;; FE89;ARABIC LETTER YEH WITH HAMZA ABOVE ISOLATED FORM;Lo;0;AL; 0626;;;;N;GLYPH FOR ISOLATE ARABIC HAMZAH ON YA;;;; FE8A;ARABIC LETTER YEH WITH HAMZA ABOVE FINAL FORM;Lo;0;AL; 0626;;;;N;GLYPH FOR FINAL ARABIC HAMZAH ON YA;;;; FE8B;ARABIC LETTER YEH WITH HAMZA ABOVE INITIAL FORM;Lo;0;AL; 0626;;;;N;GLYPH FOR INITIAL ARABIC HAMZAH ON YA;;;; FE8C;ARABIC LETTER YEH WITH HAMZA ABOVE MEDIAL FORM;Lo;0;AL; 0626;;;;N;GLYPH FOR MEDIAL ARABIC HAMZAH ON YA;;;; FE8D;ARABIC LETTER ALEF ISOLATED FORM;Lo;0;AL; 0627;;;;N;GLYPH FOR ISOLATE ARABIC ALEF;;;; FE8E;ARABIC LETTER ALEF FINAL FORM;Lo;0;AL; 0627;;;;N;GLYPH FOR FINAL ARABIC ALEF;;;; FE8F;ARABIC LETTER BEH ISOLATED FORM;Lo;0;AL; 0628;;;;N;GLYPH FOR ISOLATE ARABIC BAA;;;; FE90;ARABIC LETTER BEH FINAL FORM;Lo;0;AL; 0628;;;;N;GLYPH FOR FINAL ARABIC BAA;;;; FE91;ARABIC LETTER BEH INITIAL FORM;Lo;0;AL; 0628;;;;N;GLYPH FOR INITIAL ARABIC BAA;;;; FE92;ARABIC LETTER BEH MEDIAL FORM;Lo;0;AL; 0628;;;;N;GLYPH FOR MEDIAL ARABIC BAA;;;; FE93;ARABIC LETTER TEH MARBUTA ISOLATED FORM;Lo;0;AL; 0629;;;;N;GLYPH FOR ISOLATE ARABIC TAA MARBUTAH;;;; FE94;ARABIC LETTER TEH MARBUTA FINAL FORM;Lo;0;AL; 0629;;;;N;GLYPH FOR FINAL ARABIC TAA MARBUTAH;;;; FE95;ARABIC LETTER TEH ISOLATED FORM;Lo;0;AL; 062A;;;;N;GLYPH FOR ISOLATE ARABIC TAA;;;; FE96;ARABIC LETTER TEH FINAL FORM;Lo;0;AL; 062A;;;;N;GLYPH FOR FINAL ARABIC TAA;;;; FE97;ARABIC LETTER TEH INITIAL FORM;Lo;0;AL; 062A;;;;N;GLYPH FOR INITIAL ARABIC TAA;;;; FE98;ARABIC LETTER TEH MEDIAL FORM;Lo;0;AL; 062A;;;;N;GLYPH FOR MEDIAL ARABIC TAA;;;; FE99;ARABIC LETTER THEH ISOLATED FORM;Lo;0;AL; 062B;;;;N;GLYPH FOR ISOLATE ARABIC THAA;;;; FE9A;ARABIC LETTER THEH FINAL FORM;Lo;0;AL; 062B;;;;N;GLYPH FOR FINAL ARABIC THAA;;;; FE9B;ARABIC LETTER THEH INITIAL FORM;Lo;0;AL; 062B;;;;N;GLYPH FOR INITIAL ARABIC THAA;;;; FE9C;ARABIC LETTER THEH MEDIAL FORM;Lo;0;AL; 062B;;;;N;GLYPH FOR MEDIAL ARABIC THAA;;;; FE9D;ARABIC LETTER JEEM ISOLATED FORM;Lo;0;AL; 062C;;;;N;GLYPH FOR ISOLATE ARABIC JEEM;;;; FE9E;ARABIC LETTER JEEM FINAL FORM;Lo;0;AL; 062C;;;;N;GLYPH FOR FINAL ARABIC JEEM;;;; FE9F;ARABIC LETTER JEEM INITIAL FORM;Lo;0;AL; 062C;;;;N;GLYPH FOR INITIAL ARABIC JEEM;;;; FEA0;ARABIC LETTER JEEM MEDIAL FORM;Lo;0;AL; 062C;;;;N;GLYPH FOR MEDIAL ARABIC JEEM;;;; FEA1;ARABIC LETTER HAH ISOLATED FORM;Lo;0;AL; 062D;;;;N;GLYPH FOR ISOLATE ARABIC HAA;;;; FEA2;ARABIC LETTER HAH FINAL FORM;Lo;0;AL; 062D;;;;N;GLYPH FOR FINAL ARABIC HAA;;;; FEA3;ARABIC LETTER HAH INITIAL FORM;Lo;0;AL; 062D;;;;N;GLYPH FOR INITIAL ARABIC HAA;;;; FEA4;ARABIC LETTER HAH MEDIAL FORM;Lo;0;AL; 062D;;;;N;GLYPH FOR MEDIAL ARABIC HAA;;;; FEA5;ARABIC LETTER KHAH ISOLATED FORM;Lo;0;AL; 062E;;;;N;GLYPH FOR ISOLATE ARABIC KHAA;;;; FEA6;ARABIC LETTER KHAH FINAL FORM;Lo;0;AL; 062E;;;;N;GLYPH FOR FINAL ARABIC KHAA;;;; FEA7;ARABIC LETTER KHAH INITIAL FORM;Lo;0;AL; 062E;;;;N;GLYPH FOR INITIAL ARABIC KHAA;;;; FEA8;ARABIC LETTER KHAH MEDIAL FORM;Lo;0;AL; 062E;;;;N;GLYPH FOR MEDIAL ARABIC KHAA;;;; FEA9;ARABIC LETTER DAL ISOLATED FORM;Lo;0;AL; 062F;;;;N;GLYPH FOR ISOLATE ARABIC DAL;;;; FEAA;ARABIC LETTER DAL FINAL FORM;Lo;0;AL; 062F;;;;N;GLYPH FOR FINAL ARABIC DAL;;;; FEAB;ARABIC LETTER THAL ISOLATED FORM;Lo;0;AL; 0630;;;;N;GLYPH FOR ISOLATE ARABIC THAL;;;; FEAC;ARABIC LETTER THAL FINAL FORM;Lo;0;AL; 0630;;;;N;GLYPH FOR FINAL ARABIC THAL;;;; FEAD;ARABIC LETTER REH ISOLATED FORM;Lo;0;AL; 0631;;;;N;GLYPH FOR ISOLATE ARABIC RA;;;; FEAE;ARABIC LETTER REH FINAL FORM;Lo;0;AL; 0631;;;;N;GLYPH FOR FINAL ARABIC RA;;;; FEAF;ARABIC LETTER ZAIN ISOLATED FORM;Lo;0;AL; 0632;;;;N;GLYPH FOR ISOLATE ARABIC ZAIN;;;; FEB0;ARABIC LETTER ZAIN FINAL FORM;Lo;0;AL; 0632;;;;N;GLYPH FOR FINAL ARABIC ZAIN;;;; FEB1;ARABIC LETTER SEEN ISOLATED FORM;Lo;0;AL; 0633;;;;N;GLYPH FOR ISOLATE ARABIC SEEN;;;; FEB2;ARABIC LETTER SEEN FINAL FORM;Lo;0;AL; 0633;;;;N;GLYPH FOR FINAL ARABIC SEEN;;;; FEB3;ARABIC LETTER SEEN INITIAL FORM;Lo;0;AL; 0633;;;;N;GLYPH FOR INITIAL ARABIC SEEN;;;; FEB4;ARABIC LETTER SEEN MEDIAL FORM;Lo;0;AL; 0633;;;;N;GLYPH FOR MEDIAL ARABIC SEEN;;;; FEB5;ARABIC LETTER SHEEN ISOLATED FORM;Lo;0;AL; 0634;;;;N;GLYPH FOR ISOLATE ARABIC SHEEN;;;; FEB6;ARABIC LETTER SHEEN FINAL FORM;Lo;0;AL; 0634;;;;N;GLYPH FOR FINAL ARABIC SHEEN;;;; FEB7;ARABIC LETTER SHEEN INITIAL FORM;Lo;0;AL; 0634;;;;N;GLYPH FOR INITIAL ARABIC SHEEN;;;; FEB8;ARABIC LETTER SHEEN MEDIAL FORM;Lo;0;AL; 0634;;;;N;GLYPH FOR MEDIAL ARABIC SHEEN;;;; FEB9;ARABIC LETTER SAD ISOLATED FORM;Lo;0;AL; 0635;;;;N;GLYPH FOR ISOLATE ARABIC SAD;;;; FEBA;ARABIC LETTER SAD FINAL FORM;Lo;0;AL; 0635;;;;N;GLYPH FOR FINAL ARABIC SAD;;;; FEBB;ARABIC LETTER SAD INITIAL FORM;Lo;0;AL; 0635;;;;N;GLYPH FOR INITIAL ARABIC SAD;;;; FEBC;ARABIC LETTER SAD MEDIAL FORM;Lo;0;AL; 0635;;;;N;GLYPH FOR MEDIAL ARABIC SAD;;;; FEBD;ARABIC LETTER DAD ISOLATED FORM;Lo;0;AL; 0636;;;;N;GLYPH FOR ISOLATE ARABIC DAD;;;; FEBE;ARABIC LETTER DAD FINAL FORM;Lo;0;AL; 0636;;;;N;GLYPH FOR FINAL ARABIC DAD;;;; FEBF;ARABIC LETTER DAD INITIAL FORM;Lo;0;AL; 0636;;;;N;GLYPH FOR INITIAL ARABIC DAD;;;; FEC0;ARABIC LETTER DAD MEDIAL FORM;Lo;0;AL; 0636;;;;N;GLYPH FOR MEDIAL ARABIC DAD;;;; FEC1;ARABIC LETTER TAH ISOLATED FORM;Lo;0;AL; 0637;;;;N;GLYPH FOR ISOLATE ARABIC TAH;;;; FEC2;ARABIC LETTER TAH FINAL FORM;Lo;0;AL; 0637;;;;N;GLYPH FOR FINAL ARABIC TAH;;;; FEC3;ARABIC LETTER TAH INITIAL FORM;Lo;0;AL; 0637;;;;N;GLYPH FOR INITIAL ARABIC TAH;;;; FEC4;ARABIC LETTER TAH MEDIAL FORM;Lo;0;AL; 0637;;;;N;GLYPH FOR MEDIAL ARABIC TAH;;;; FEC5;ARABIC LETTER ZAH ISOLATED FORM;Lo;0;AL; 0638;;;;N;GLYPH FOR ISOLATE ARABIC DHAH;;;; FEC6;ARABIC LETTER ZAH FINAL FORM;Lo;0;AL; 0638;;;;N;GLYPH FOR FINAL ARABIC DHAH;;;; FEC7;ARABIC LETTER ZAH INITIAL FORM;Lo;0;AL; 0638;;;;N;GLYPH FOR INITIAL ARABIC DHAH;;;; FEC8;ARABIC LETTER ZAH MEDIAL FORM;Lo;0;AL; 0638;;;;N;GLYPH FOR MEDIAL ARABIC DHAH;;;; FEC9;ARABIC LETTER AIN ISOLATED FORM;Lo;0;AL; 0639;;;;N;GLYPH FOR ISOLATE ARABIC AIN;;;; FECA;ARABIC LETTER AIN FINAL FORM;Lo;0;AL; 0639;;;;N;GLYPH FOR FINAL ARABIC AIN;;;; FECB;ARABIC LETTER AIN INITIAL FORM;Lo;0;AL; 0639;;;;N;GLYPH FOR INITIAL ARABIC AIN;;;; FECC;ARABIC LETTER AIN MEDIAL FORM;Lo;0;AL; 0639;;;;N;GLYPH FOR MEDIAL ARABIC AIN;;;; FECD;ARABIC LETTER GHAIN ISOLATED FORM;Lo;0;AL; 063A;;;;N;GLYPH FOR ISOLATE ARABIC GHAIN;;;; FECE;ARABIC LETTER GHAIN FINAL FORM;Lo;0;AL; 063A;;;;N;GLYPH FOR FINAL ARABIC GHAIN;;;; FECF;ARABIC LETTER GHAIN INITIAL FORM;Lo;0;AL; 063A;;;;N;GLYPH FOR INITIAL ARABIC GHAIN;;;; FED0;ARABIC LETTER GHAIN MEDIAL FORM;Lo;0;AL; 063A;;;;N;GLYPH FOR MEDIAL ARABIC GHAIN;;;; FED1;ARABIC LETTER FEH ISOLATED FORM;Lo;0;AL; 0641;;;;N;GLYPH FOR ISOLATE ARABIC FA;;;; FED2;ARABIC LETTER FEH FINAL FORM;Lo;0;AL; 0641;;;;N;GLYPH FOR FINAL ARABIC FA;;;; FED3;ARABIC LETTER FEH INITIAL FORM;Lo;0;AL; 0641;;;;N;GLYPH FOR INITIAL ARABIC FA;;;; FED4;ARABIC LETTER FEH MEDIAL FORM;Lo;0;AL; 0641;;;;N;GLYPH FOR MEDIAL ARABIC FA;;;; FED5;ARABIC LETTER QAF ISOLATED FORM;Lo;0;AL; 0642;;;;N;GLYPH FOR ISOLATE ARABIC QAF;;;; FED6;ARABIC LETTER QAF FINAL FORM;Lo;0;AL; 0642;;;;N;GLYPH FOR FINAL ARABIC QAF;;;; FED7;ARABIC LETTER QAF INITIAL FORM;Lo;0;AL; 0642;;;;N;GLYPH FOR INITIAL ARABIC QAF;;;; FED8;ARABIC LETTER QAF MEDIAL FORM;Lo;0;AL; 0642;;;;N;GLYPH FOR MEDIAL ARABIC QAF;;;; FED9;ARABIC LETTER KAF ISOLATED FORM;Lo;0;AL; 0643;;;;N;GLYPH FOR ISOLATE ARABIC CAF;;;; FEDA;ARABIC LETTER KAF FINAL FORM;Lo;0;AL; 0643;;;;N;GLYPH FOR FINAL ARABIC CAF;;;; FEDB;ARABIC LETTER KAF INITIAL FORM;Lo;0;AL; 0643;;;;N;GLYPH FOR INITIAL ARABIC CAF;;;; FEDC;ARABIC LETTER KAF MEDIAL FORM;Lo;0;AL; 0643;;;;N;GLYPH FOR MEDIAL ARABIC CAF;;;; FEDD;ARABIC LETTER LAM ISOLATED FORM;Lo;0;AL; 0644;;;;N;GLYPH FOR ISOLATE ARABIC LAM;;;; FEDE;ARABIC LETTER LAM FINAL FORM;Lo;0;AL; 0644;;;;N;GLYPH FOR FINAL ARABIC LAM;;;; FEDF;ARABIC LETTER LAM INITIAL FORM;Lo;0;AL; 0644;;;;N;GLYPH FOR INITIAL ARABIC LAM;;;; FEE0;ARABIC LETTER LAM MEDIAL FORM;Lo;0;AL; 0644;;;;N;GLYPH FOR MEDIAL ARABIC LAM;;;; FEE1;ARABIC LETTER MEEM ISOLATED FORM;Lo;0;AL; 0645;;;;N;GLYPH FOR ISOLATE ARABIC MEEM;;;; FEE2;ARABIC LETTER MEEM FINAL FORM;Lo;0;AL; 0645;;;;N;GLYPH FOR FINAL ARABIC MEEM;;;; FEE3;ARABIC LETTER MEEM INITIAL FORM;Lo;0;AL; 0645;;;;N;GLYPH FOR INITIAL ARABIC MEEM;;;; FEE4;ARABIC LETTER MEEM MEDIAL FORM;Lo;0;AL; 0645;;;;N;GLYPH FOR MEDIAL ARABIC MEEM;;;; FEE5;ARABIC LETTER NOON ISOLATED FORM;Lo;0;AL; 0646;;;;N;GLYPH FOR ISOLATE ARABIC NOON;;;; FEE6;ARABIC LETTER NOON FINAL FORM;Lo;0;AL; 0646;;;;N;GLYPH FOR FINAL ARABIC NOON;;;; FEE7;ARABIC LETTER NOON INITIAL FORM;Lo;0;AL; 0646;;;;N;GLYPH FOR INITIAL ARABIC NOON;;;; FEE8;ARABIC LETTER NOON MEDIAL FORM;Lo;0;AL; 0646;;;;N;GLYPH FOR MEDIAL ARABIC NOON;;;; FEE9;ARABIC LETTER HEH ISOLATED FORM;Lo;0;AL; 0647;;;;N;GLYPH FOR ISOLATE ARABIC HA;;;; FEEA;ARABIC LETTER HEH FINAL FORM;Lo;0;AL; 0647;;;;N;GLYPH FOR FINAL ARABIC HA;;;; FEEB;ARABIC LETTER HEH INITIAL FORM;Lo;0;AL; 0647;;;;N;GLYPH FOR INITIAL ARABIC HA;;;; FEEC;ARABIC LETTER HEH MEDIAL FORM;Lo;0;AL; 0647;;;;N;GLYPH FOR MEDIAL ARABIC HA;;;; FEED;ARABIC LETTER WAW ISOLATED FORM;Lo;0;AL; 0648;;;;N;GLYPH FOR ISOLATE ARABIC WAW;;;; FEEE;ARABIC LETTER WAW FINAL FORM;Lo;0;AL; 0648;;;;N;GLYPH FOR FINAL ARABIC WAW;;;; FEEF;ARABIC LETTER ALEF MAKSURA ISOLATED FORM;Lo;0;AL; 0649;;;;N;GLYPH FOR ISOLATE ARABIC ALEF MAQSURAH;;;; FEF0;ARABIC LETTER ALEF MAKSURA FINAL FORM;Lo;0;AL; 0649;;;;N;GLYPH FOR FINAL ARABIC ALEF MAQSURAH;;;; FEF1;ARABIC LETTER YEH ISOLATED FORM;Lo;0;AL; 064A;;;;N;GLYPH FOR ISOLATE ARABIC YA;;;; FEF2;ARABIC LETTER YEH FINAL FORM;Lo;0;AL; 064A;;;;N;GLYPH FOR FINAL ARABIC YA;;;; FEF3;ARABIC LETTER YEH INITIAL FORM;Lo;0;AL; 064A;;;;N;GLYPH FOR INITIAL ARABIC YA;;;; FEF4;ARABIC LETTER YEH MEDIAL FORM;Lo;0;AL; 064A;;;;N;GLYPH FOR MEDIAL ARABIC YA;;;; FEF5;ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM;Lo;0;AL; 0644 0622;;;;N;GLYPH FOR ISOLATE ARABIC MADDAH ON LIGATURE LAM ALEF;;;; FEF6;ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE FINAL FORM;Lo;0;AL; 0644 0622;;;;N;GLYPH FOR FINAL ARABIC MADDAH ON LIGATURE LAM ALEF;;;; FEF7;ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM;Lo;0;AL; 0644 0623;;;;N;GLYPH FOR ISOLATE ARABIC HAMZAH ON LIGATURE LAM ALEF;;;; FEF8;ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE FINAL FORM;Lo;0;AL; 0644 0623;;;;N;GLYPH FOR FINAL ARABIC HAMZAH ON LIGATURE LAM ALEF;;;; FEF9;ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW ISOLATED FORM;Lo;0;AL; 0644 0625;;;;N;GLYPH FOR ISOLATE ARABIC HAMZAH UNDER LIGATURE LAM ALEF;;;; FEFA;ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW FINAL FORM;Lo;0;AL; 0644 0625;;;;N;GLYPH FOR FINAL ARABIC HAMZAH UNDER LIGATURE LAM ALEF;;;; FEFB;ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM;Lo;0;AL; 0644 0627;;;;N;GLYPH FOR ISOLATE ARABIC LIGATURE LAM ALEF;;;; FEFC;ARABIC LIGATURE LAM WITH ALEF FINAL FORM;Lo;0;AL; 0644 0627;;;;N;GLYPH FOR FINAL ARABIC LIGATURE LAM ALEF;;;; FEFF;ZERO WIDTH NO-BREAK SPACE;Cf;0;BN;;;;;N;BYTE ORDER MARK;;;; FF01;FULLWIDTH EXCLAMATION MARK;Po;0;ON; 0021;;;;N;;;;; FF02;FULLWIDTH QUOTATION MARK;Po;0;ON; 0022;;;;N;;;;; FF03;FULLWIDTH NUMBER SIGN;Po;0;ET; 0023;;;;N;;;;; FF04;FULLWIDTH DOLLAR SIGN;Sc;0;ET; 0024;;;;N;;;;; FF05;FULLWIDTH PERCENT SIGN;Po;0;ET; 0025;;;;N;;;;; FF06;FULLWIDTH AMPERSAND;Po;0;ON; 0026;;;;N;;;;; FF07;FULLWIDTH APOSTROPHE;Po;0;ON; 0027;;;;N;;;;; FF08;FULLWIDTH LEFT PARENTHESIS;Ps;0;ON; 0028;;;;Y;FULLWIDTH OPENING PARENTHESIS;;;; FF09;FULLWIDTH RIGHT PARENTHESIS;Pe;0;ON; 0029;;;;Y;FULLWIDTH CLOSING PARENTHESIS;;;; FF0A;FULLWIDTH ASTERISK;Po;0;ON; 002A;;;;N;;;;; FF0B;FULLWIDTH PLUS SIGN;Sm;0;ES; 002B;;;;N;;;;; FF0C;FULLWIDTH COMMA;Po;0;CS; 002C;;;;N;;;;; FF0D;FULLWIDTH HYPHEN-MINUS;Pd;0;ES; 002D;;;;N;;;;; FF0E;FULLWIDTH FULL STOP;Po;0;CS; 002E;;;;N;FULLWIDTH PERIOD;;;; FF0F;FULLWIDTH SOLIDUS;Po;0;CS; 002F;;;;N;FULLWIDTH SLASH;;;; FF10;FULLWIDTH DIGIT ZERO;Nd;0;EN; 0030;0;0;0;N;;;;; FF11;FULLWIDTH DIGIT ONE;Nd;0;EN; 0031;1;1;1;N;;;;; FF12;FULLWIDTH DIGIT TWO;Nd;0;EN; 0032;2;2;2;N;;;;; FF13;FULLWIDTH DIGIT THREE;Nd;0;EN; 0033;3;3;3;N;;;;; FF14;FULLWIDTH DIGIT FOUR;Nd;0;EN; 0034;4;4;4;N;;;;; FF15;FULLWIDTH DIGIT FIVE;Nd;0;EN; 0035;5;5;5;N;;;;; FF16;FULLWIDTH DIGIT SIX;Nd;0;EN; 0036;6;6;6;N;;;;; FF17;FULLWIDTH DIGIT SEVEN;Nd;0;EN; 0037;7;7;7;N;;;;; FF18;FULLWIDTH DIGIT EIGHT;Nd;0;EN; 0038;8;8;8;N;;;;; FF19;FULLWIDTH DIGIT NINE;Nd;0;EN; 0039;9;9;9;N;;;;; FF1A;FULLWIDTH COLON;Po;0;CS; 003A;;;;N;;;;; FF1B;FULLWIDTH SEMICOLON;Po;0;ON; 003B;;;;N;;;;; FF1C;FULLWIDTH LESS-THAN SIGN;Sm;0;ON; 003C;;;;Y;;;;; FF1D;FULLWIDTH EQUALS SIGN;Sm;0;ON; 003D;;;;N;;;;; FF1E;FULLWIDTH GREATER-THAN SIGN;Sm;0;ON; 003E;;;;Y;;;;; FF1F;FULLWIDTH QUESTION MARK;Po;0;ON; 003F;;;;N;;;;; FF20;FULLWIDTH COMMERCIAL AT;Po;0;ON; 0040;;;;N;;;;; FF21;FULLWIDTH LATIN CAPITAL LETTER A;Lu;0;L; 0041;;;;N;;;;FF41; FF22;FULLWIDTH LATIN CAPITAL LETTER B;Lu;0;L; 0042;;;;N;;;;FF42; FF23;FULLWIDTH LATIN CAPITAL LETTER C;Lu;0;L; 0043;;;;N;;;;FF43; FF24;FULLWIDTH LATIN CAPITAL LETTER D;Lu;0;L; 0044;;;;N;;;;FF44; FF25;FULLWIDTH LATIN CAPITAL LETTER E;Lu;0;L; 0045;;;;N;;;;FF45; FF26;FULLWIDTH LATIN CAPITAL LETTER F;Lu;0;L; 0046;;;;N;;;;FF46; FF27;FULLWIDTH LATIN CAPITAL LETTER G;Lu;0;L; 0047;;;;N;;;;FF47; FF28;FULLWIDTH LATIN CAPITAL LETTER H;Lu;0;L; 0048;;;;N;;;;FF48; FF29;FULLWIDTH LATIN CAPITAL LETTER I;Lu;0;L; 0049;;;;N;;;;FF49; FF2A;FULLWIDTH LATIN CAPITAL LETTER J;Lu;0;L; 004A;;;;N;;;;FF4A; FF2B;FULLWIDTH LATIN CAPITAL LETTER K;Lu;0;L; 004B;;;;N;;;;FF4B; FF2C;FULLWIDTH LATIN CAPITAL LETTER L;Lu;0;L; 004C;;;;N;;;;FF4C; FF2D;FULLWIDTH LATIN CAPITAL LETTER M;Lu;0;L; 004D;;;;N;;;;FF4D; FF2E;FULLWIDTH LATIN CAPITAL LETTER N;Lu;0;L; 004E;;;;N;;;;FF4E; FF2F;FULLWIDTH LATIN CAPITAL LETTER O;Lu;0;L; 004F;;;;N;;;;FF4F; FF30;FULLWIDTH LATIN CAPITAL LETTER P;Lu;0;L; 0050;;;;N;;;;FF50; FF31;FULLWIDTH LATIN CAPITAL LETTER Q;Lu;0;L; 0051;;;;N;;;;FF51; FF32;FULLWIDTH LATIN CAPITAL LETTER R;Lu;0;L; 0052;;;;N;;;;FF52; FF33;FULLWIDTH LATIN CAPITAL LETTER S;Lu;0;L; 0053;;;;N;;;;FF53; FF34;FULLWIDTH LATIN CAPITAL LETTER T;Lu;0;L; 0054;;;;N;;;;FF54; FF35;FULLWIDTH LATIN CAPITAL LETTER U;Lu;0;L; 0055;;;;N;;;;FF55; FF36;FULLWIDTH LATIN CAPITAL LETTER V;Lu;0;L; 0056;;;;N;;;;FF56; FF37;FULLWIDTH LATIN CAPITAL LETTER W;Lu;0;L; 0057;;;;N;;;;FF57; FF38;FULLWIDTH LATIN CAPITAL LETTER X;Lu;0;L; 0058;;;;N;;;;FF58; FF39;FULLWIDTH LATIN CAPITAL LETTER Y;Lu;0;L; 0059;;;;N;;;;FF59; FF3A;FULLWIDTH LATIN CAPITAL LETTER Z;Lu;0;L; 005A;;;;N;;;;FF5A; FF3B;FULLWIDTH LEFT SQUARE BRACKET;Ps;0;ON; 005B;;;;Y;FULLWIDTH OPENING SQUARE BRACKET;;;; FF3C;FULLWIDTH REVERSE SOLIDUS;Po;0;ON; 005C;;;;N;FULLWIDTH BACKSLASH;;;; FF3D;FULLWIDTH RIGHT SQUARE BRACKET;Pe;0;ON; 005D;;;;Y;FULLWIDTH CLOSING SQUARE BRACKET;;;; FF3E;FULLWIDTH CIRCUMFLEX ACCENT;Sk;0;ON; 005E;;;;N;FULLWIDTH SPACING CIRCUMFLEX;;;; FF3F;FULLWIDTH LOW LINE;Pc;0;ON; 005F;;;;N;FULLWIDTH SPACING UNDERSCORE;;;; FF40;FULLWIDTH GRAVE ACCENT;Sk;0;ON; 0060;;;;N;FULLWIDTH SPACING GRAVE;;;; FF41;FULLWIDTH LATIN SMALL LETTER A;Ll;0;L; 0061;;;;N;;;FF21;;FF21 FF42;FULLWIDTH LATIN SMALL LETTER B;Ll;0;L; 0062;;;;N;;;FF22;;FF22 FF43;FULLWIDTH LATIN SMALL LETTER C;Ll;0;L; 0063;;;;N;;;FF23;;FF23 FF44;FULLWIDTH LATIN SMALL LETTER D;Ll;0;L; 0064;;;;N;;;FF24;;FF24 FF45;FULLWIDTH LATIN SMALL LETTER E;Ll;0;L; 0065;;;;N;;;FF25;;FF25 FF46;FULLWIDTH LATIN SMALL LETTER F;Ll;0;L; 0066;;;;N;;;FF26;;FF26 FF47;FULLWIDTH LATIN SMALL LETTER G;Ll;0;L; 0067;;;;N;;;FF27;;FF27 FF48;FULLWIDTH LATIN SMALL LETTER H;Ll;0;L; 0068;;;;N;;;FF28;;FF28 FF49;FULLWIDTH LATIN SMALL LETTER I;Ll;0;L; 0069;;;;N;;;FF29;;FF29 FF4A;FULLWIDTH LATIN SMALL LETTER J;Ll;0;L; 006A;;;;N;;;FF2A;;FF2A FF4B;FULLWIDTH LATIN SMALL LETTER K;Ll;0;L; 006B;;;;N;;;FF2B;;FF2B FF4C;FULLWIDTH LATIN SMALL LETTER L;Ll;0;L; 006C;;;;N;;;FF2C;;FF2C FF4D;FULLWIDTH LATIN SMALL LETTER M;Ll;0;L; 006D;;;;N;;;FF2D;;FF2D FF4E;FULLWIDTH LATIN SMALL LETTER N;Ll;0;L; 006E;;;;N;;;FF2E;;FF2E FF4F;FULLWIDTH LATIN SMALL LETTER O;Ll;0;L; 006F;;;;N;;;FF2F;;FF2F FF50;FULLWIDTH LATIN SMALL LETTER P;Ll;0;L; 0070;;;;N;;;FF30;;FF30 FF51;FULLWIDTH LATIN SMALL LETTER Q;Ll;0;L; 0071;;;;N;;;FF31;;FF31 FF52;FULLWIDTH LATIN SMALL LETTER R;Ll;0;L; 0072;;;;N;;;FF32;;FF32 FF53;FULLWIDTH LATIN SMALL LETTER S;Ll;0;L; 0073;;;;N;;;FF33;;FF33 FF54;FULLWIDTH LATIN SMALL LETTER T;Ll;0;L; 0074;;;;N;;;FF34;;FF34 FF55;FULLWIDTH LATIN SMALL LETTER U;Ll;0;L; 0075;;;;N;;;FF35;;FF35 FF56;FULLWIDTH LATIN SMALL LETTER V;Ll;0;L; 0076;;;;N;;;FF36;;FF36 FF57;FULLWIDTH LATIN SMALL LETTER W;Ll;0;L; 0077;;;;N;;;FF37;;FF37 FF58;FULLWIDTH LATIN SMALL LETTER X;Ll;0;L; 0078;;;;N;;;FF38;;FF38 FF59;FULLWIDTH LATIN SMALL LETTER Y;Ll;0;L; 0079;;;;N;;;FF39;;FF39 FF5A;FULLWIDTH LATIN SMALL LETTER Z;Ll;0;L; 007A;;;;N;;;FF3A;;FF3A FF5B;FULLWIDTH LEFT CURLY BRACKET;Ps;0;ON; 007B;;;;Y;FULLWIDTH OPENING CURLY BRACKET;;;; FF5C;FULLWIDTH VERTICAL LINE;Sm;0;ON; 007C;;;;N;FULLWIDTH VERTICAL BAR;;;; FF5D;FULLWIDTH RIGHT CURLY BRACKET;Pe;0;ON; 007D;;;;Y;FULLWIDTH CLOSING CURLY BRACKET;;;; FF5E;FULLWIDTH TILDE;Sm;0;ON; 007E;;;;N;FULLWIDTH SPACING TILDE;;;; FF5F;FULLWIDTH LEFT WHITE PARENTHESIS;Ps;0;ON; 2985;;;;Y;;;;; FF60;FULLWIDTH RIGHT WHITE PARENTHESIS;Pe;0;ON; 2986;;;;Y;;;;; FF61;HALFWIDTH IDEOGRAPHIC FULL STOP;Po;0;ON; 3002;;;;N;HALFWIDTH IDEOGRAPHIC PERIOD;;;; FF62;HALFWIDTH LEFT CORNER BRACKET;Ps;0;ON; 300C;;;;Y;HALFWIDTH OPENING CORNER BRACKET;;;; FF63;HALFWIDTH RIGHT CORNER BRACKET;Pe;0;ON; 300D;;;;Y;HALFWIDTH CLOSING CORNER BRACKET;;;; FF64;HALFWIDTH IDEOGRAPHIC COMMA;Po;0;ON; 3001;;;;N;;;;; FF65;HALFWIDTH KATAKANA MIDDLE DOT;Po;0;ON; 30FB;;;;N;;;;; FF66;HALFWIDTH KATAKANA LETTER WO;Lo;0;L; 30F2;;;;N;;;;; FF67;HALFWIDTH KATAKANA LETTER SMALL A;Lo;0;L; 30A1;;;;N;;;;; FF68;HALFWIDTH KATAKANA LETTER SMALL I;Lo;0;L; 30A3;;;;N;;;;; FF69;HALFWIDTH KATAKANA LETTER SMALL U;Lo;0;L; 30A5;;;;N;;;;; FF6A;HALFWIDTH KATAKANA LETTER SMALL E;Lo;0;L; 30A7;;;;N;;;;; FF6B;HALFWIDTH KATAKANA LETTER SMALL O;Lo;0;L; 30A9;;;;N;;;;; FF6C;HALFWIDTH KATAKANA LETTER SMALL YA;Lo;0;L; 30E3;;;;N;;;;; FF6D;HALFWIDTH KATAKANA LETTER SMALL YU;Lo;0;L; 30E5;;;;N;;;;; FF6E;HALFWIDTH KATAKANA LETTER SMALL YO;Lo;0;L; 30E7;;;;N;;;;; FF6F;HALFWIDTH KATAKANA LETTER SMALL TU;Lo;0;L; 30C3;;;;N;;;;; FF70;HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK;Lm;0;L; 30FC;;;;N;;;;; FF71;HALFWIDTH KATAKANA LETTER A;Lo;0;L; 30A2;;;;N;;;;; FF72;HALFWIDTH KATAKANA LETTER I;Lo;0;L; 30A4;;;;N;;;;; FF73;HALFWIDTH KATAKANA LETTER U;Lo;0;L; 30A6;;;;N;;;;; FF74;HALFWIDTH KATAKANA LETTER E;Lo;0;L; 30A8;;;;N;;;;; FF75;HALFWIDTH KATAKANA LETTER O;Lo;0;L; 30AA;;;;N;;;;; FF76;HALFWIDTH KATAKANA LETTER KA;Lo;0;L; 30AB;;;;N;;;;; FF77;HALFWIDTH KATAKANA LETTER KI;Lo;0;L; 30AD;;;;N;;;;; FF78;HALFWIDTH KATAKANA LETTER KU;Lo;0;L; 30AF;;;;N;;;;; FF79;HALFWIDTH KATAKANA LETTER KE;Lo;0;L; 30B1;;;;N;;;;; FF7A;HALFWIDTH KATAKANA LETTER KO;Lo;0;L; 30B3;;;;N;;;;; FF7B;HALFWIDTH KATAKANA LETTER SA;Lo;0;L; 30B5;;;;N;;;;; FF7C;HALFWIDTH KATAKANA LETTER SI;Lo;0;L; 30B7;;;;N;;;;; FF7D;HALFWIDTH KATAKANA LETTER SU;Lo;0;L; 30B9;;;;N;;;;; FF7E;HALFWIDTH KATAKANA LETTER SE;Lo;0;L; 30BB;;;;N;;;;; FF7F;HALFWIDTH KATAKANA LETTER SO;Lo;0;L; 30BD;;;;N;;;;; FF80;HALFWIDTH KATAKANA LETTER TA;Lo;0;L; 30BF;;;;N;;;;; FF81;HALFWIDTH KATAKANA LETTER TI;Lo;0;L; 30C1;;;;N;;;;; FF82;HALFWIDTH KATAKANA LETTER TU;Lo;0;L; 30C4;;;;N;;;;; FF83;HALFWIDTH KATAKANA LETTER TE;Lo;0;L; 30C6;;;;N;;;;; FF84;HALFWIDTH KATAKANA LETTER TO;Lo;0;L; 30C8;;;;N;;;;; FF85;HALFWIDTH KATAKANA LETTER NA;Lo;0;L; 30CA;;;;N;;;;; FF86;HALFWIDTH KATAKANA LETTER NI;Lo;0;L; 30CB;;;;N;;;;; FF87;HALFWIDTH KATAKANA LETTER NU;Lo;0;L; 30CC;;;;N;;;;; FF88;HALFWIDTH KATAKANA LETTER NE;Lo;0;L; 30CD;;;;N;;;;; FF89;HALFWIDTH KATAKANA LETTER NO;Lo;0;L; 30CE;;;;N;;;;; FF8A;HALFWIDTH KATAKANA LETTER HA;Lo;0;L; 30CF;;;;N;;;;; FF8B;HALFWIDTH KATAKANA LETTER HI;Lo;0;L; 30D2;;;;N;;;;; FF8C;HALFWIDTH KATAKANA LETTER HU;Lo;0;L; 30D5;;;;N;;;;; FF8D;HALFWIDTH KATAKANA LETTER HE;Lo;0;L; 30D8;;;;N;;;;; FF8E;HALFWIDTH KATAKANA LETTER HO;Lo;0;L; 30DB;;;;N;;;;; FF8F;HALFWIDTH KATAKANA LETTER MA;Lo;0;L; 30DE;;;;N;;;;; FF90;HALFWIDTH KATAKANA LETTER MI;Lo;0;L; 30DF;;;;N;;;;; FF91;HALFWIDTH KATAKANA LETTER MU;Lo;0;L; 30E0;;;;N;;;;; FF92;HALFWIDTH KATAKANA LETTER ME;Lo;0;L; 30E1;;;;N;;;;; FF93;HALFWIDTH KATAKANA LETTER MO;Lo;0;L; 30E2;;;;N;;;;; FF94;HALFWIDTH KATAKANA LETTER YA;Lo;0;L; 30E4;;;;N;;;;; FF95;HALFWIDTH KATAKANA LETTER YU;Lo;0;L; 30E6;;;;N;;;;; FF96;HALFWIDTH KATAKANA LETTER YO;Lo;0;L; 30E8;;;;N;;;;; FF97;HALFWIDTH KATAKANA LETTER RA;Lo;0;L; 30E9;;;;N;;;;; FF98;HALFWIDTH KATAKANA LETTER RI;Lo;0;L; 30EA;;;;N;;;;; FF99;HALFWIDTH KATAKANA LETTER RU;Lo;0;L; 30EB;;;;N;;;;; FF9A;HALFWIDTH KATAKANA LETTER RE;Lo;0;L; 30EC;;;;N;;;;; FF9B;HALFWIDTH KATAKANA LETTER RO;Lo;0;L; 30ED;;;;N;;;;; FF9C;HALFWIDTH KATAKANA LETTER WA;Lo;0;L; 30EF;;;;N;;;;; FF9D;HALFWIDTH KATAKANA LETTER N;Lo;0;L; 30F3;;;;N;;;;; FF9E;HALFWIDTH KATAKANA VOICED SOUND MARK;Lm;0;L; 3099;;;;N;;;;; FF9F;HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK;Lm;0;L; 309A;;;;N;;;;; FFA0;HALFWIDTH HANGUL FILLER;Lo;0;L; 3164;;;;N;HALFWIDTH HANGUL CAE OM;;;; FFA1;HALFWIDTH HANGUL LETTER KIYEOK;Lo;0;L; 3131;;;;N;HALFWIDTH HANGUL LETTER GIYEOG;;;; FFA2;HALFWIDTH HANGUL LETTER SSANGKIYEOK;Lo;0;L; 3132;;;;N;HALFWIDTH HANGUL LETTER SSANG GIYEOG;;;; FFA3;HALFWIDTH HANGUL LETTER KIYEOK-SIOS;Lo;0;L; 3133;;;;N;HALFWIDTH HANGUL LETTER GIYEOG SIOS;;;; FFA4;HALFWIDTH HANGUL LETTER NIEUN;Lo;0;L; 3134;;;;N;;;;; FFA5;HALFWIDTH HANGUL LETTER NIEUN-CIEUC;Lo;0;L; 3135;;;;N;HALFWIDTH HANGUL LETTER NIEUN JIEUJ;;;; FFA6;HALFWIDTH HANGUL LETTER NIEUN-HIEUH;Lo;0;L; 3136;;;;N;HALFWIDTH HANGUL LETTER NIEUN HIEUH;;;; FFA7;HALFWIDTH HANGUL LETTER TIKEUT;Lo;0;L; 3137;;;;N;HALFWIDTH HANGUL LETTER DIGEUD;;;; FFA8;HALFWIDTH HANGUL LETTER SSANGTIKEUT;Lo;0;L; 3138;;;;N;HALFWIDTH HANGUL LETTER SSANG DIGEUD;;;; FFA9;HALFWIDTH HANGUL LETTER RIEUL;Lo;0;L; 3139;;;;N;HALFWIDTH HANGUL LETTER LIEUL;;;; FFAA;HALFWIDTH HANGUL LETTER RIEUL-KIYEOK;Lo;0;L; 313A;;;;N;HALFWIDTH HANGUL LETTER LIEUL GIYEOG;;;; FFAB;HALFWIDTH HANGUL LETTER RIEUL-MIEUM;Lo;0;L; 313B;;;;N;HALFWIDTH HANGUL LETTER LIEUL MIEUM;;;; FFAC;HALFWIDTH HANGUL LETTER RIEUL-PIEUP;Lo;0;L; 313C;;;;N;HALFWIDTH HANGUL LETTER LIEUL BIEUB;;;; FFAD;HALFWIDTH HANGUL LETTER RIEUL-SIOS;Lo;0;L; 313D;;;;N;HALFWIDTH HANGUL LETTER LIEUL SIOS;;;; FFAE;HALFWIDTH HANGUL LETTER RIEUL-THIEUTH;Lo;0;L; 313E;;;;N;HALFWIDTH HANGUL LETTER LIEUL TIEUT;;;; FFAF;HALFWIDTH HANGUL LETTER RIEUL-PHIEUPH;Lo;0;L; 313F;;;;N;HALFWIDTH HANGUL LETTER LIEUL PIEUP;;;; FFB0;HALFWIDTH HANGUL LETTER RIEUL-HIEUH;Lo;0;L; 3140;;;;N;HALFWIDTH HANGUL LETTER LIEUL HIEUH;;;; FFB1;HALFWIDTH HANGUL LETTER MIEUM;Lo;0;L; 3141;;;;N;;;;; FFB2;HALFWIDTH HANGUL LETTER PIEUP;Lo;0;L; 3142;;;;N;HALFWIDTH HANGUL LETTER BIEUB;;;; FFB3;HALFWIDTH HANGUL LETTER SSANGPIEUP;Lo;0;L; 3143;;;;N;HALFWIDTH HANGUL LETTER SSANG BIEUB;;;; FFB4;HALFWIDTH HANGUL LETTER PIEUP-SIOS;Lo;0;L; 3144;;;;N;HALFWIDTH HANGUL LETTER BIEUB SIOS;;;; FFB5;HALFWIDTH HANGUL LETTER SIOS;Lo;0;L; 3145;;;;N;;;;; FFB6;HALFWIDTH HANGUL LETTER SSANGSIOS;Lo;0;L; 3146;;;;N;HALFWIDTH HANGUL LETTER SSANG SIOS;;;; FFB7;HALFWIDTH HANGUL LETTER IEUNG;Lo;0;L; 3147;;;;N;;;;; FFB8;HALFWIDTH HANGUL LETTER CIEUC;Lo;0;L; 3148;;;;N;HALFWIDTH HANGUL LETTER JIEUJ;;;; FFB9;HALFWIDTH HANGUL LETTER SSANGCIEUC;Lo;0;L; 3149;;;;N;HALFWIDTH HANGUL LETTER SSANG JIEUJ;;;; FFBA;HALFWIDTH HANGUL LETTER CHIEUCH;Lo;0;L; 314A;;;;N;HALFWIDTH HANGUL LETTER CIEUC;;;; FFBB;HALFWIDTH HANGUL LETTER KHIEUKH;Lo;0;L; 314B;;;;N;HALFWIDTH HANGUL LETTER KIYEOK;;;; FFBC;HALFWIDTH HANGUL LETTER THIEUTH;Lo;0;L; 314C;;;;N;HALFWIDTH HANGUL LETTER TIEUT;;;; FFBD;HALFWIDTH HANGUL LETTER PHIEUPH;Lo;0;L; 314D;;;;N;HALFWIDTH HANGUL LETTER PIEUP;;;; FFBE;HALFWIDTH HANGUL LETTER HIEUH;Lo;0;L; 314E;;;;N;;;;; FFC2;HALFWIDTH HANGUL LETTER A;Lo;0;L; 314F;;;;N;;;;; FFC3;HALFWIDTH HANGUL LETTER AE;Lo;0;L; 3150;;;;N;;;;; FFC4;HALFWIDTH HANGUL LETTER YA;Lo;0;L; 3151;;;;N;;;;; FFC5;HALFWIDTH HANGUL LETTER YAE;Lo;0;L; 3152;;;;N;;;;; FFC6;HALFWIDTH HANGUL LETTER EO;Lo;0;L; 3153;;;;N;;;;; FFC7;HALFWIDTH HANGUL LETTER E;Lo;0;L; 3154;;;;N;;;;; FFCA;HALFWIDTH HANGUL LETTER YEO;Lo;0;L; 3155;;;;N;;;;; FFCB;HALFWIDTH HANGUL LETTER YE;Lo;0;L; 3156;;;;N;;;;; FFCC;HALFWIDTH HANGUL LETTER O;Lo;0;L; 3157;;;;N;;;;; FFCD;HALFWIDTH HANGUL LETTER WA;Lo;0;L; 3158;;;;N;;;;; FFCE;HALFWIDTH HANGUL LETTER WAE;Lo;0;L; 3159;;;;N;;;;; FFCF;HALFWIDTH HANGUL LETTER OE;Lo;0;L; 315A;;;;N;;;;; FFD2;HALFWIDTH HANGUL LETTER YO;Lo;0;L; 315B;;;;N;;;;; FFD3;HALFWIDTH HANGUL LETTER U;Lo;0;L; 315C;;;;N;;;;; FFD4;HALFWIDTH HANGUL LETTER WEO;Lo;0;L; 315D;;;;N;;;;; FFD5;HALFWIDTH HANGUL LETTER WE;Lo;0;L; 315E;;;;N;;;;; FFD6;HALFWIDTH HANGUL LETTER WI;Lo;0;L; 315F;;;;N;;;;; FFD7;HALFWIDTH HANGUL LETTER YU;Lo;0;L; 3160;;;;N;;;;; FFDA;HALFWIDTH HANGUL LETTER EU;Lo;0;L; 3161;;;;N;;;;; FFDB;HALFWIDTH HANGUL LETTER YI;Lo;0;L; 3162;;;;N;;;;; FFDC;HALFWIDTH HANGUL LETTER I;Lo;0;L; 3163;;;;N;;;;; FFE0;FULLWIDTH CENT SIGN;Sc;0;ET; 00A2;;;;N;;;;; FFE1;FULLWIDTH POUND SIGN;Sc;0;ET; 00A3;;;;N;;;;; FFE2;FULLWIDTH NOT SIGN;Sm;0;ON; 00AC;;;;N;;;;; FFE3;FULLWIDTH MACRON;Sk;0;ON; 00AF;;;;N;FULLWIDTH SPACING MACRON;;;; FFE4;FULLWIDTH BROKEN BAR;So;0;ON; 00A6;;;;N;FULLWIDTH BROKEN VERTICAL BAR;;;; FFE5;FULLWIDTH YEN SIGN;Sc;0;ET; 00A5;;;;N;;;;; FFE6;FULLWIDTH WON SIGN;Sc;0;ET; 20A9;;;;N;;;;; FFE8;HALFWIDTH FORMS LIGHT VERTICAL;So;0;ON; 2502;;;;N;;;;; FFE9;HALFWIDTH LEFTWARDS ARROW;Sm;0;ON; 2190;;;;N;;;;; FFEA;HALFWIDTH UPWARDS ARROW;Sm;0;ON; 2191;;;;N;;;;; FFEB;HALFWIDTH RIGHTWARDS ARROW;Sm;0;ON; 2192;;;;N;;;;; FFEC;HALFWIDTH DOWNWARDS ARROW;Sm;0;ON; 2193;;;;N;;;;; FFED;HALFWIDTH BLACK SQUARE;So;0;ON; 25A0;;;;N;;;;; FFEE;HALFWIDTH WHITE CIRCLE;So;0;ON; 25CB;;;;N;;;;; FFF9;INTERLINEAR ANNOTATION ANCHOR;Cf;0;ON;;;;;N;;;;; FFFA;INTERLINEAR ANNOTATION SEPARATOR;Cf;0;ON;;;;;N;;;;; FFFB;INTERLINEAR ANNOTATION TERMINATOR;Cf;0;ON;;;;;N;;;;; FFFC;OBJECT REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 10000;LINEAR B SYLLABLE B008 A;Lo;0;L;;;;;N;;;;; 10001;LINEAR B SYLLABLE B038 E;Lo;0;L;;;;;N;;;;; 10002;LINEAR B SYLLABLE B028 I;Lo;0;L;;;;;N;;;;; 10003;LINEAR B SYLLABLE B061 O;Lo;0;L;;;;;N;;;;; 10004;LINEAR B SYLLABLE B010 U;Lo;0;L;;;;;N;;;;; 10005;LINEAR B SYLLABLE B001 DA;Lo;0;L;;;;;N;;;;; 10006;LINEAR B SYLLABLE B045 DE;Lo;0;L;;;;;N;;;;; 10007;LINEAR B SYLLABLE B007 DI;Lo;0;L;;;;;N;;;;; 10008;LINEAR B SYLLABLE B014 DO;Lo;0;L;;;;;N;;;;; 10009;LINEAR B SYLLABLE B051 DU;Lo;0;L;;;;;N;;;;; 1000A;LINEAR B SYLLABLE B057 JA;Lo;0;L;;;;;N;;;;; 1000B;LINEAR B SYLLABLE B046 JE;Lo;0;L;;;;;N;;;;; 1000D;LINEAR B SYLLABLE B036 JO;Lo;0;L;;;;;N;;;;; 1000E;LINEAR B SYLLABLE B065 JU;Lo;0;L;;;;;N;;;;; 1000F;LINEAR B SYLLABLE B077 KA;Lo;0;L;;;;;N;;;;; 10010;LINEAR B SYLLABLE B044 KE;Lo;0;L;;;;;N;;;;; 10011;LINEAR B SYLLABLE B067 KI;Lo;0;L;;;;;N;;;;; 10012;LINEAR B SYLLABLE B070 KO;Lo;0;L;;;;;N;;;;; 10013;LINEAR B SYLLABLE B081 KU;Lo;0;L;;;;;N;;;;; 10014;LINEAR B SYLLABLE B080 MA;Lo;0;L;;;;;N;;;;; 10015;LINEAR B SYLLABLE B013 ME;Lo;0;L;;;;;N;;;;; 10016;LINEAR B SYLLABLE B073 MI;Lo;0;L;;;;;N;;;;; 10017;LINEAR B SYLLABLE B015 MO;Lo;0;L;;;;;N;;;;; 10018;LINEAR B SYLLABLE B023 MU;Lo;0;L;;;;;N;;;;; 10019;LINEAR B SYLLABLE B006 NA;Lo;0;L;;;;;N;;;;; 1001A;LINEAR B SYLLABLE B024 NE;Lo;0;L;;;;;N;;;;; 1001B;LINEAR B SYLLABLE B030 NI;Lo;0;L;;;;;N;;;;; 1001C;LINEAR B SYLLABLE B052 NO;Lo;0;L;;;;;N;;;;; 1001D;LINEAR B SYLLABLE B055 NU;Lo;0;L;;;;;N;;;;; 1001E;LINEAR B SYLLABLE B003 PA;Lo;0;L;;;;;N;;;;; 1001F;LINEAR B SYLLABLE B072 PE;Lo;0;L;;;;;N;;;;; 10020;LINEAR B SYLLABLE B039 PI;Lo;0;L;;;;;N;;;;; 10021;LINEAR B SYLLABLE B011 PO;Lo;0;L;;;;;N;;;;; 10022;LINEAR B SYLLABLE B050 PU;Lo;0;L;;;;;N;;;;; 10023;LINEAR B SYLLABLE B016 QA;Lo;0;L;;;;;N;;;;; 10024;LINEAR B SYLLABLE B078 QE;Lo;0;L;;;;;N;;;;; 10025;LINEAR B SYLLABLE B021 QI;Lo;0;L;;;;;N;;;;; 10026;LINEAR B SYLLABLE B032 QO;Lo;0;L;;;;;N;;;;; 10028;LINEAR B SYLLABLE B060 RA;Lo;0;L;;;;;N;;;;; 10029;LINEAR B SYLLABLE B027 RE;Lo;0;L;;;;;N;;;;; 1002A;LINEAR B SYLLABLE B053 RI;Lo;0;L;;;;;N;;;;; 1002B;LINEAR B SYLLABLE B002 RO;Lo;0;L;;;;;N;;;;; 1002C;LINEAR B SYLLABLE B026 RU;Lo;0;L;;;;;N;;;;; 1002D;LINEAR B SYLLABLE B031 SA;Lo;0;L;;;;;N;;;;; 1002E;LINEAR B SYLLABLE B009 SE;Lo;0;L;;;;;N;;;;; 1002F;LINEAR B SYLLABLE B041 SI;Lo;0;L;;;;;N;;;;; 10030;LINEAR B SYLLABLE B012 SO;Lo;0;L;;;;;N;;;;; 10031;LINEAR B SYLLABLE B058 SU;Lo;0;L;;;;;N;;;;; 10032;LINEAR B SYLLABLE B059 TA;Lo;0;L;;;;;N;;;;; 10033;LINEAR B SYLLABLE B004 TE;Lo;0;L;;;;;N;;;;; 10034;LINEAR B SYLLABLE B037 TI;Lo;0;L;;;;;N;;;;; 10035;LINEAR B SYLLABLE B005 TO;Lo;0;L;;;;;N;;;;; 10036;LINEAR B SYLLABLE B069 TU;Lo;0;L;;;;;N;;;;; 10037;LINEAR B SYLLABLE B054 WA;Lo;0;L;;;;;N;;;;; 10038;LINEAR B SYLLABLE B075 WE;Lo;0;L;;;;;N;;;;; 10039;LINEAR B SYLLABLE B040 WI;Lo;0;L;;;;;N;;;;; 1003A;LINEAR B SYLLABLE B042 WO;Lo;0;L;;;;;N;;;;; 1003C;LINEAR B SYLLABLE B017 ZA;Lo;0;L;;;;;N;;;;; 1003D;LINEAR B SYLLABLE B074 ZE;Lo;0;L;;;;;N;;;;; 1003F;LINEAR B SYLLABLE B020 ZO;Lo;0;L;;;;;N;;;;; 10040;LINEAR B SYLLABLE B025 A2;Lo;0;L;;;;;N;;;;; 10041;LINEAR B SYLLABLE B043 A3;Lo;0;L;;;;;N;;;;; 10042;LINEAR B SYLLABLE B085 AU;Lo;0;L;;;;;N;;;;; 10043;LINEAR B SYLLABLE B071 DWE;Lo;0;L;;;;;N;;;;; 10044;LINEAR B SYLLABLE B090 DWO;Lo;0;L;;;;;N;;;;; 10045;LINEAR B SYLLABLE B048 NWA;Lo;0;L;;;;;N;;;;; 10046;LINEAR B SYLLABLE B029 PU2;Lo;0;L;;;;;N;;;;; 10047;LINEAR B SYLLABLE B062 PTE;Lo;0;L;;;;;N;;;;; 10048;LINEAR B SYLLABLE B076 RA2;Lo;0;L;;;;;N;;;;; 10049;LINEAR B SYLLABLE B033 RA3;Lo;0;L;;;;;N;;;;; 1004A;LINEAR B SYLLABLE B068 RO2;Lo;0;L;;;;;N;;;;; 1004B;LINEAR B SYLLABLE B066 TA2;Lo;0;L;;;;;N;;;;; 1004C;LINEAR B SYLLABLE B087 TWE;Lo;0;L;;;;;N;;;;; 1004D;LINEAR B SYLLABLE B091 TWO;Lo;0;L;;;;;N;;;;; 10050;LINEAR B SYMBOL B018;Lo;0;L;;;;;N;;;;; 10051;LINEAR B SYMBOL B019;Lo;0;L;;;;;N;;;;; 10052;LINEAR B SYMBOL B022;Lo;0;L;;;;;N;;;;; 10053;LINEAR B SYMBOL B034;Lo;0;L;;;;;N;;;;; 10054;LINEAR B SYMBOL B047;Lo;0;L;;;;;N;;;;; 10055;LINEAR B SYMBOL B049;Lo;0;L;;;;;N;;;;; 10056;LINEAR B SYMBOL B056;Lo;0;L;;;;;N;;;;; 10057;LINEAR B SYMBOL B063;Lo;0;L;;;;;N;;;;; 10058;LINEAR B SYMBOL B064;Lo;0;L;;;;;N;;;;; 10059;LINEAR B SYMBOL B079;Lo;0;L;;;;;N;;;;; 1005A;LINEAR B SYMBOL B082;Lo;0;L;;;;;N;;;;; 1005B;LINEAR B SYMBOL B083;Lo;0;L;;;;;N;;;;; 1005C;LINEAR B SYMBOL B086;Lo;0;L;;;;;N;;;;; 1005D;LINEAR B SYMBOL B089;Lo;0;L;;;;;N;;;;; 10080;LINEAR B IDEOGRAM B100 MAN;Lo;0;L;;;;;N;;;;; 10081;LINEAR B IDEOGRAM B102 WOMAN;Lo;0;L;;;;;N;;;;; 10082;LINEAR B IDEOGRAM B104 DEER;Lo;0;L;;;;;N;;;;; 10083;LINEAR B IDEOGRAM B105 EQUID;Lo;0;L;;;;;N;;;;; 10084;LINEAR B IDEOGRAM B105F MARE;Lo;0;L;;;;;N;;;;; 10085;LINEAR B IDEOGRAM B105M STALLION;Lo;0;L;;;;;N;;;;; 10086;LINEAR B IDEOGRAM B106F EWE;Lo;0;L;;;;;N;;;;; 10087;LINEAR B IDEOGRAM B106M RAM;Lo;0;L;;;;;N;;;;; 10088;LINEAR B IDEOGRAM B107F SHE-GOAT;Lo;0;L;;;;;N;;;;; 10089;LINEAR B IDEOGRAM B107M HE-GOAT;Lo;0;L;;;;;N;;;;; 1008A;LINEAR B IDEOGRAM B108F SOW;Lo;0;L;;;;;N;;;;; 1008B;LINEAR B IDEOGRAM B108M BOAR;Lo;0;L;;;;;N;;;;; 1008C;LINEAR B IDEOGRAM B109F COW;Lo;0;L;;;;;N;;;;; 1008D;LINEAR B IDEOGRAM B109M BULL;Lo;0;L;;;;;N;;;;; 1008E;LINEAR B IDEOGRAM B120 WHEAT;Lo;0;L;;;;;N;;;;; 1008F;LINEAR B IDEOGRAM B121 BARLEY;Lo;0;L;;;;;N;;;;; 10090;LINEAR B IDEOGRAM B122 OLIVE;Lo;0;L;;;;;N;;;;; 10091;LINEAR B IDEOGRAM B123 SPICE;Lo;0;L;;;;;N;;;;; 10092;LINEAR B IDEOGRAM B125 CYPERUS;Lo;0;L;;;;;N;;;;; 10093;LINEAR B MONOGRAM B127 KAPO;Lo;0;L;;;;;N;;;;; 10094;LINEAR B MONOGRAM B128 KANAKO;Lo;0;L;;;;;N;;;;; 10095;LINEAR B IDEOGRAM B130 OIL;Lo;0;L;;;;;N;;;;; 10096;LINEAR B IDEOGRAM B131 WINE;Lo;0;L;;;;;N;;;;; 10097;LINEAR B IDEOGRAM B132;Lo;0;L;;;;;N;;;;; 10098;LINEAR B MONOGRAM B133 AREPA;Lo;0;L;;;;;N;;;;; 10099;LINEAR B MONOGRAM B135 MERI;Lo;0;L;;;;;N;;;;; 1009A;LINEAR B IDEOGRAM B140 BRONZE;Lo;0;L;;;;;N;;;;; 1009B;LINEAR B IDEOGRAM B141 GOLD;Lo;0;L;;;;;N;;;;; 1009C;LINEAR B IDEOGRAM B142;Lo;0;L;;;;;N;;;;; 1009D;LINEAR B IDEOGRAM B145 WOOL;Lo;0;L;;;;;N;;;;; 1009E;LINEAR B IDEOGRAM B146;Lo;0;L;;;;;N;;;;; 1009F;LINEAR B IDEOGRAM B150;Lo;0;L;;;;;N;;;;; 100A0;LINEAR B IDEOGRAM B151 HORN;Lo;0;L;;;;;N;;;;; 100A1;LINEAR B IDEOGRAM B152;Lo;0;L;;;;;N;;;;; 100A2;LINEAR B IDEOGRAM B153;Lo;0;L;;;;;N;;;;; 100A3;LINEAR B IDEOGRAM B154;Lo;0;L;;;;;N;;;;; 100A4;LINEAR B MONOGRAM B156 TURO2;Lo;0;L;;;;;N;;;;; 100A5;LINEAR B IDEOGRAM B157;Lo;0;L;;;;;N;;;;; 100A6;LINEAR B IDEOGRAM B158;Lo;0;L;;;;;N;;;;; 100A7;LINEAR B IDEOGRAM B159 CLOTH;Lo;0;L;;;;;N;;;;; 100A8;LINEAR B IDEOGRAM B160;Lo;0;L;;;;;N;;;;; 100A9;LINEAR B IDEOGRAM B161;Lo;0;L;;;;;N;;;;; 100AA;LINEAR B IDEOGRAM B162 GARMENT;Lo;0;L;;;;;N;;;;; 100AB;LINEAR B IDEOGRAM B163 ARMOUR;Lo;0;L;;;;;N;;;;; 100AC;LINEAR B IDEOGRAM B164;Lo;0;L;;;;;N;;;;; 100AD;LINEAR B IDEOGRAM B165;Lo;0;L;;;;;N;;;;; 100AE;LINEAR B IDEOGRAM B166;Lo;0;L;;;;;N;;;;; 100AF;LINEAR B IDEOGRAM B167;Lo;0;L;;;;;N;;;;; 100B0;LINEAR B IDEOGRAM B168;Lo;0;L;;;;;N;;;;; 100B1;LINEAR B IDEOGRAM B169;Lo;0;L;;;;;N;;;;; 100B2;LINEAR B IDEOGRAM B170;Lo;0;L;;;;;N;;;;; 100B3;LINEAR B IDEOGRAM B171;Lo;0;L;;;;;N;;;;; 100B4;LINEAR B IDEOGRAM B172;Lo;0;L;;;;;N;;;;; 100B5;LINEAR B IDEOGRAM B173 MONTH;Lo;0;L;;;;;N;;;;; 100B6;LINEAR B IDEOGRAM B174;Lo;0;L;;;;;N;;;;; 100B7;LINEAR B IDEOGRAM B176 TREE;Lo;0;L;;;;;N;;;;; 100B8;LINEAR B IDEOGRAM B177;Lo;0;L;;;;;N;;;;; 100B9;LINEAR B IDEOGRAM B178;Lo;0;L;;;;;N;;;;; 100BA;LINEAR B IDEOGRAM B179;Lo;0;L;;;;;N;;;;; 100BB;LINEAR B IDEOGRAM B180;Lo;0;L;;;;;N;;;;; 100BC;LINEAR B IDEOGRAM B181;Lo;0;L;;;;;N;;;;; 100BD;LINEAR B IDEOGRAM B182;Lo;0;L;;;;;N;;;;; 100BE;LINEAR B IDEOGRAM B183;Lo;0;L;;;;;N;;;;; 100BF;LINEAR B IDEOGRAM B184;Lo;0;L;;;;;N;;;;; 100C0;LINEAR B IDEOGRAM B185;Lo;0;L;;;;;N;;;;; 100C1;LINEAR B IDEOGRAM B189;Lo;0;L;;;;;N;;;;; 100C2;LINEAR B IDEOGRAM B190;Lo;0;L;;;;;N;;;;; 100C3;LINEAR B IDEOGRAM B191 HELMET;Lo;0;L;;;;;N;;;;; 100C4;LINEAR B IDEOGRAM B220 FOOTSTOOL;Lo;0;L;;;;;N;;;;; 100C5;LINEAR B IDEOGRAM B225 BATHTUB;Lo;0;L;;;;;N;;;;; 100C6;LINEAR B IDEOGRAM B230 SPEAR;Lo;0;L;;;;;N;;;;; 100C7;LINEAR B IDEOGRAM B231 ARROW;Lo;0;L;;;;;N;;;;; 100C8;LINEAR B IDEOGRAM B232;Lo;0;L;;;;;N;;;;; 100C9;LINEAR B IDEOGRAM B233 SWORD;Lo;0;L;;;;;N;;;;; 100CA;LINEAR B IDEOGRAM B234;Lo;0;L;;;;;N;;;;; 100CB;LINEAR B IDEOGRAM B236;Lo;0;L;;;;;N;;;;; 100CC;LINEAR B IDEOGRAM B240 WHEELED CHARIOT;Lo;0;L;;;;;N;;;;; 100CD;LINEAR B IDEOGRAM B241 CHARIOT;Lo;0;L;;;;;N;;;;; 100CE;LINEAR B IDEOGRAM B242 CHARIOT FRAME;Lo;0;L;;;;;N;;;;; 100CF;LINEAR B IDEOGRAM B243 WHEEL;Lo;0;L;;;;;N;;;;; 100D0;LINEAR B IDEOGRAM B245;Lo;0;L;;;;;N;;;;; 100D1;LINEAR B IDEOGRAM B246;Lo;0;L;;;;;N;;;;; 100D2;LINEAR B MONOGRAM B247 DIPTE;Lo;0;L;;;;;N;;;;; 100D3;LINEAR B IDEOGRAM B248;Lo;0;L;;;;;N;;;;; 100D4;LINEAR B IDEOGRAM B249;Lo;0;L;;;;;N;;;;; 100D5;LINEAR B IDEOGRAM B251;Lo;0;L;;;;;N;;;;; 100D6;LINEAR B IDEOGRAM B252;Lo;0;L;;;;;N;;;;; 100D7;LINEAR B IDEOGRAM B253;Lo;0;L;;;;;N;;;;; 100D8;LINEAR B IDEOGRAM B254 DART;Lo;0;L;;;;;N;;;;; 100D9;LINEAR B IDEOGRAM B255;Lo;0;L;;;;;N;;;;; 100DA;LINEAR B IDEOGRAM B256;Lo;0;L;;;;;N;;;;; 100DB;LINEAR B IDEOGRAM B257;Lo;0;L;;;;;N;;;;; 100DC;LINEAR B IDEOGRAM B258;Lo;0;L;;;;;N;;;;; 100DD;LINEAR B IDEOGRAM B259;Lo;0;L;;;;;N;;;;; 100DE;LINEAR B IDEOGRAM VESSEL B155;Lo;0;L;;;;;N;;;;; 100DF;LINEAR B IDEOGRAM VESSEL B200;Lo;0;L;;;;;N;;;;; 100E0;LINEAR B IDEOGRAM VESSEL B201;Lo;0;L;;;;;N;;;;; 100E1;LINEAR B IDEOGRAM VESSEL B202;Lo;0;L;;;;;N;;;;; 100E2;LINEAR B IDEOGRAM VESSEL B203;Lo;0;L;;;;;N;;;;; 100E3;LINEAR B IDEOGRAM VESSEL B204;Lo;0;L;;;;;N;;;;; 100E4;LINEAR B IDEOGRAM VESSEL B205;Lo;0;L;;;;;N;;;;; 100E5;LINEAR B IDEOGRAM VESSEL B206;Lo;0;L;;;;;N;;;;; 100E6;LINEAR B IDEOGRAM VESSEL B207;Lo;0;L;;;;;N;;;;; 100E7;LINEAR B IDEOGRAM VESSEL B208;Lo;0;L;;;;;N;;;;; 100E8;LINEAR B IDEOGRAM VESSEL B209;Lo;0;L;;;;;N;;;;; 100E9;LINEAR B IDEOGRAM VESSEL B210;Lo;0;L;;;;;N;;;;; 100EA;LINEAR B IDEOGRAM VESSEL B211;Lo;0;L;;;;;N;;;;; 100EB;LINEAR B IDEOGRAM VESSEL B212;Lo;0;L;;;;;N;;;;; 100EC;LINEAR B IDEOGRAM VESSEL B213;Lo;0;L;;;;;N;;;;; 100ED;LINEAR B IDEOGRAM VESSEL B214;Lo;0;L;;;;;N;;;;; 100EE;LINEAR B IDEOGRAM VESSEL B215;Lo;0;L;;;;;N;;;;; 100EF;LINEAR B IDEOGRAM VESSEL B216;Lo;0;L;;;;;N;;;;; 100F0;LINEAR B IDEOGRAM VESSEL B217;Lo;0;L;;;;;N;;;;; 100F1;LINEAR B IDEOGRAM VESSEL B218;Lo;0;L;;;;;N;;;;; 100F2;LINEAR B IDEOGRAM VESSEL B219;Lo;0;L;;;;;N;;;;; 100F3;LINEAR B IDEOGRAM VESSEL B221;Lo;0;L;;;;;N;;;;; 100F4;LINEAR B IDEOGRAM VESSEL B222;Lo;0;L;;;;;N;;;;; 100F5;LINEAR B IDEOGRAM VESSEL B226;Lo;0;L;;;;;N;;;;; 100F6;LINEAR B IDEOGRAM VESSEL B227;Lo;0;L;;;;;N;;;;; 100F7;LINEAR B IDEOGRAM VESSEL B228;Lo;0;L;;;;;N;;;;; 100F8;LINEAR B IDEOGRAM VESSEL B229;Lo;0;L;;;;;N;;;;; 100F9;LINEAR B IDEOGRAM VESSEL B250;Lo;0;L;;;;;N;;;;; 100FA;LINEAR B IDEOGRAM VESSEL B305;Lo;0;L;;;;;N;;;;; 10100;AEGEAN WORD SEPARATOR LINE;Po;0;L;;;;;N;;;;; 10101;AEGEAN WORD SEPARATOR DOT;Po;0;ON;;;;;N;;;;; 10102;AEGEAN CHECK MARK;Po;0;L;;;;;N;;;;; 10107;AEGEAN NUMBER ONE;No;0;L;;;;1;N;;;;; 10108;AEGEAN NUMBER TWO;No;0;L;;;;2;N;;;;; 10109;AEGEAN NUMBER THREE;No;0;L;;;;3;N;;;;; 1010A;AEGEAN NUMBER FOUR;No;0;L;;;;4;N;;;;; 1010B;AEGEAN NUMBER FIVE;No;0;L;;;;5;N;;;;; 1010C;AEGEAN NUMBER SIX;No;0;L;;;;6;N;;;;; 1010D;AEGEAN NUMBER SEVEN;No;0;L;;;;7;N;;;;; 1010E;AEGEAN NUMBER EIGHT;No;0;L;;;;8;N;;;;; 1010F;AEGEAN NUMBER NINE;No;0;L;;;;9;N;;;;; 10110;AEGEAN NUMBER TEN;No;0;L;;;;10;N;;;;; 10111;AEGEAN NUMBER TWENTY;No;0;L;;;;20;N;;;;; 10112;AEGEAN NUMBER THIRTY;No;0;L;;;;30;N;;;;; 10113;AEGEAN NUMBER FORTY;No;0;L;;;;40;N;;;;; 10114;AEGEAN NUMBER FIFTY;No;0;L;;;;50;N;;;;; 10115;AEGEAN NUMBER SIXTY;No;0;L;;;;60;N;;;;; 10116;AEGEAN NUMBER SEVENTY;No;0;L;;;;70;N;;;;; 10117;AEGEAN NUMBER EIGHTY;No;0;L;;;;80;N;;;;; 10118;AEGEAN NUMBER NINETY;No;0;L;;;;90;N;;;;; 10119;AEGEAN NUMBER ONE HUNDRED;No;0;L;;;;100;N;;;;; 1011A;AEGEAN NUMBER TWO HUNDRED;No;0;L;;;;200;N;;;;; 1011B;AEGEAN NUMBER THREE HUNDRED;No;0;L;;;;300;N;;;;; 1011C;AEGEAN NUMBER FOUR HUNDRED;No;0;L;;;;400;N;;;;; 1011D;AEGEAN NUMBER FIVE HUNDRED;No;0;L;;;;500;N;;;;; 1011E;AEGEAN NUMBER SIX HUNDRED;No;0;L;;;;600;N;;;;; 1011F;AEGEAN NUMBER SEVEN HUNDRED;No;0;L;;;;700;N;;;;; 10120;AEGEAN NUMBER EIGHT HUNDRED;No;0;L;;;;800;N;;;;; 10121;AEGEAN NUMBER NINE HUNDRED;No;0;L;;;;900;N;;;;; 10122;AEGEAN NUMBER ONE THOUSAND;No;0;L;;;;1000;N;;;;; 10123;AEGEAN NUMBER TWO THOUSAND;No;0;L;;;;2000;N;;;;; 10124;AEGEAN NUMBER THREE THOUSAND;No;0;L;;;;3000;N;;;;; 10125;AEGEAN NUMBER FOUR THOUSAND;No;0;L;;;;4000;N;;;;; 10126;AEGEAN NUMBER FIVE THOUSAND;No;0;L;;;;5000;N;;;;; 10127;AEGEAN NUMBER SIX THOUSAND;No;0;L;;;;6000;N;;;;; 10128;AEGEAN NUMBER SEVEN THOUSAND;No;0;L;;;;7000;N;;;;; 10129;AEGEAN NUMBER EIGHT THOUSAND;No;0;L;;;;8000;N;;;;; 1012A;AEGEAN NUMBER NINE THOUSAND;No;0;L;;;;9000;N;;;;; 1012B;AEGEAN NUMBER TEN THOUSAND;No;0;L;;;;10000;N;;;;; 1012C;AEGEAN NUMBER TWENTY THOUSAND;No;0;L;;;;20000;N;;;;; 1012D;AEGEAN NUMBER THIRTY THOUSAND;No;0;L;;;;30000;N;;;;; 1012E;AEGEAN NUMBER FORTY THOUSAND;No;0;L;;;;40000;N;;;;; 1012F;AEGEAN NUMBER FIFTY THOUSAND;No;0;L;;;;50000;N;;;;; 10130;AEGEAN NUMBER SIXTY THOUSAND;No;0;L;;;;60000;N;;;;; 10131;AEGEAN NUMBER SEVENTY THOUSAND;No;0;L;;;;70000;N;;;;; 10132;AEGEAN NUMBER EIGHTY THOUSAND;No;0;L;;;;80000;N;;;;; 10133;AEGEAN NUMBER NINETY THOUSAND;No;0;L;;;;90000;N;;;;; 10137;AEGEAN WEIGHT BASE UNIT;So;0;L;;;;;N;;;;; 10138;AEGEAN WEIGHT FIRST SUBUNIT;So;0;L;;;;;N;;;;; 10139;AEGEAN WEIGHT SECOND SUBUNIT;So;0;L;;;;;N;;;;; 1013A;AEGEAN WEIGHT THIRD SUBUNIT;So;0;L;;;;;N;;;;; 1013B;AEGEAN WEIGHT FOURTH SUBUNIT;So;0;L;;;;;N;;;;; 1013C;AEGEAN DRY MEASURE FIRST SUBUNIT;So;0;L;;;;;N;;;;; 1013D;AEGEAN LIQUID MEASURE FIRST SUBUNIT;So;0;L;;;;;N;;;;; 1013E;AEGEAN MEASURE SECOND SUBUNIT;So;0;L;;;;;N;;;;; 1013F;AEGEAN MEASURE THIRD SUBUNIT;So;0;L;;;;;N;;;;; 10140;GREEK ACROPHONIC ATTIC ONE QUARTER;Nl;0;ON;;;;1/4;N;;;;; 10141;GREEK ACROPHONIC ATTIC ONE HALF;Nl;0;ON;;;;1/2;N;;;;; 10142;GREEK ACROPHONIC ATTIC ONE DRACHMA;Nl;0;ON;;;;1;N;;;;; 10143;GREEK ACROPHONIC ATTIC FIVE;Nl;0;ON;;;;5;N;;;;; 10144;GREEK ACROPHONIC ATTIC FIFTY;Nl;0;ON;;;;50;N;;;;; 10145;GREEK ACROPHONIC ATTIC FIVE HUNDRED;Nl;0;ON;;;;500;N;;;;; 10146;GREEK ACROPHONIC ATTIC FIVE THOUSAND;Nl;0;ON;;;;5000;N;;;;; 10147;GREEK ACROPHONIC ATTIC FIFTY THOUSAND;Nl;0;ON;;;;50000;N;;;;; 10148;GREEK ACROPHONIC ATTIC FIVE TALENTS;Nl;0;ON;;;;5;N;;;;; 10149;GREEK ACROPHONIC ATTIC TEN TALENTS;Nl;0;ON;;;;10;N;;;;; 1014A;GREEK ACROPHONIC ATTIC FIFTY TALENTS;Nl;0;ON;;;;50;N;;;;; 1014B;GREEK ACROPHONIC ATTIC ONE HUNDRED TALENTS;Nl;0;ON;;;;100;N;;;;; 1014C;GREEK ACROPHONIC ATTIC FIVE HUNDRED TALENTS;Nl;0;ON;;;;500;N;;;;; 1014D;GREEK ACROPHONIC ATTIC ONE THOUSAND TALENTS;Nl;0;ON;;;;1000;N;;;;; 1014E;GREEK ACROPHONIC ATTIC FIVE THOUSAND TALENTS;Nl;0;ON;;;;5000;N;;;;; 1014F;GREEK ACROPHONIC ATTIC FIVE STATERS;Nl;0;ON;;;;5;N;;;;; 10150;GREEK ACROPHONIC ATTIC TEN STATERS;Nl;0;ON;;;;10;N;;;;; 10151;GREEK ACROPHONIC ATTIC FIFTY STATERS;Nl;0;ON;;;;50;N;;;;; 10152;GREEK ACROPHONIC ATTIC ONE HUNDRED STATERS;Nl;0;ON;;;;100;N;;;;; 10153;GREEK ACROPHONIC ATTIC FIVE HUNDRED STATERS;Nl;0;ON;;;;500;N;;;;; 10154;GREEK ACROPHONIC ATTIC ONE THOUSAND STATERS;Nl;0;ON;;;;1000;N;;;;; 10155;GREEK ACROPHONIC ATTIC TEN THOUSAND STATERS;Nl;0;ON;;;;10000;N;;;;; 10156;GREEK ACROPHONIC ATTIC FIFTY THOUSAND STATERS;Nl;0;ON;;;;50000;N;;;;; 10157;GREEK ACROPHONIC ATTIC TEN MNAS;Nl;0;ON;;;;10;N;;;;; 10158;GREEK ACROPHONIC HERAEUM ONE PLETHRON;Nl;0;ON;;;;1;N;;;;; 10159;GREEK ACROPHONIC THESPIAN ONE;Nl;0;ON;;;;1;N;;;;; 1015A;GREEK ACROPHONIC HERMIONIAN ONE;Nl;0;ON;;;;1;N;;;;; 1015B;GREEK ACROPHONIC EPIDAUREAN TWO;Nl;0;ON;;;;2;N;;;;; 1015C;GREEK ACROPHONIC THESPIAN TWO;Nl;0;ON;;;;2;N;;;;; 1015D;GREEK ACROPHONIC CYRENAIC TWO DRACHMAS;Nl;0;ON;;;;2;N;;;;; 1015E;GREEK ACROPHONIC EPIDAUREAN TWO DRACHMAS;Nl;0;ON;;;;2;N;;;;; 1015F;GREEK ACROPHONIC TROEZENIAN FIVE;Nl;0;ON;;;;5;N;;;;; 10160;GREEK ACROPHONIC TROEZENIAN TEN;Nl;0;ON;;;;10;N;;;;; 10161;GREEK ACROPHONIC TROEZENIAN TEN ALTERNATE FORM;Nl;0;ON;;;;10;N;;;;; 10162;GREEK ACROPHONIC HERMIONIAN TEN;Nl;0;ON;;;;10;N;;;;; 10163;GREEK ACROPHONIC MESSENIAN TEN;Nl;0;ON;;;;10;N;;;;; 10164;GREEK ACROPHONIC THESPIAN TEN;Nl;0;ON;;;;10;N;;;;; 10165;GREEK ACROPHONIC THESPIAN THIRTY;Nl;0;ON;;;;30;N;;;;; 10166;GREEK ACROPHONIC TROEZENIAN FIFTY;Nl;0;ON;;;;50;N;;;;; 10167;GREEK ACROPHONIC TROEZENIAN FIFTY ALTERNATE FORM;Nl;0;ON;;;;50;N;;;;; 10168;GREEK ACROPHONIC HERMIONIAN FIFTY;Nl;0;ON;;;;50;N;;;;; 10169;GREEK ACROPHONIC THESPIAN FIFTY;Nl;0;ON;;;;50;N;;;;; 1016A;GREEK ACROPHONIC THESPIAN ONE HUNDRED;Nl;0;ON;;;;100;N;;;;; 1016B;GREEK ACROPHONIC THESPIAN THREE HUNDRED;Nl;0;ON;;;;300;N;;;;; 1016C;GREEK ACROPHONIC EPIDAUREAN FIVE HUNDRED;Nl;0;ON;;;;500;N;;;;; 1016D;GREEK ACROPHONIC TROEZENIAN FIVE HUNDRED;Nl;0;ON;;;;500;N;;;;; 1016E;GREEK ACROPHONIC THESPIAN FIVE HUNDRED;Nl;0;ON;;;;500;N;;;;; 1016F;GREEK ACROPHONIC CARYSTIAN FIVE HUNDRED;Nl;0;ON;;;;500;N;;;;; 10170;GREEK ACROPHONIC NAXIAN FIVE HUNDRED;Nl;0;ON;;;;500;N;;;;; 10171;GREEK ACROPHONIC THESPIAN ONE THOUSAND;Nl;0;ON;;;;1000;N;;;;; 10172;GREEK ACROPHONIC THESPIAN FIVE THOUSAND;Nl;0;ON;;;;5000;N;;;;; 10173;GREEK ACROPHONIC DELPHIC FIVE MNAS;Nl;0;ON;;;;5;N;;;;; 10174;GREEK ACROPHONIC STRATIAN FIFTY MNAS;Nl;0;ON;;;;50;N;;;;; 10175;GREEK ONE HALF SIGN;No;0;ON;;;;1/2;N;;;;; 10176;GREEK ONE HALF SIGN ALTERNATE FORM;No;0;ON;;;;1/2;N;;;;; 10177;GREEK TWO THIRDS SIGN;No;0;ON;;;;2/3;N;;;;; 10178;GREEK THREE QUARTERS SIGN;No;0;ON;;;;3/4;N;;;;; 10179;GREEK YEAR SIGN;So;0;ON;;;;;N;;;;; 1017A;GREEK TALENT SIGN;So;0;ON;;;;;N;;;;; 1017B;GREEK DRACHMA SIGN;So;0;ON;;;;;N;;;;; 1017C;GREEK OBOL SIGN;So;0;ON;;;;;N;;;;; 1017D;GREEK TWO OBOLS SIGN;So;0;ON;;;;;N;;;;; 1017E;GREEK THREE OBOLS SIGN;So;0;ON;;;;;N;;;;; 1017F;GREEK FOUR OBOLS SIGN;So;0;ON;;;;;N;;;;; 10180;GREEK FIVE OBOLS SIGN;So;0;ON;;;;;N;;;;; 10181;GREEK METRETES SIGN;So;0;ON;;;;;N;;;;; 10182;GREEK KYATHOS BASE SIGN;So;0;ON;;;;;N;;;;; 10183;GREEK LITRA SIGN;So;0;ON;;;;;N;;;;; 10184;GREEK OUNKIA SIGN;So;0;ON;;;;;N;;;;; 10185;GREEK XESTES SIGN;So;0;ON;;;;;N;;;;; 10186;GREEK ARTABE SIGN;So;0;ON;;;;;N;;;;; 10187;GREEK AROURA SIGN;So;0;ON;;;;;N;;;;; 10188;GREEK GRAMMA SIGN;So;0;ON;;;;;N;;;;; 10189;GREEK TRYBLION BASE SIGN;So;0;ON;;;;;N;;;;; 1018A;GREEK ZERO SIGN;No;0;ON;;;;0;N;;;;; 1018B;GREEK ONE QUARTER SIGN;No;0;ON;;;;1/4;N;;;;; 1018C;GREEK SINUSOID SIGN;So;0;ON;;;;;N;;;;; 1018D;GREEK INDICTION SIGN;So;0;L;;;;;N;;;;; 1018E;NOMISMA SIGN;So;0;L;;;;;N;;;;; 10190;ROMAN SEXTANS SIGN;So;0;ON;;;;;N;;;;; 10191;ROMAN UNCIA SIGN;So;0;ON;;;;;N;;;;; 10192;ROMAN SEMUNCIA SIGN;So;0;ON;;;;;N;;;;; 10193;ROMAN SEXTULA SIGN;So;0;ON;;;;;N;;;;; 10194;ROMAN DIMIDIA SEXTULA SIGN;So;0;ON;;;;;N;;;;; 10195;ROMAN SILIQUA SIGN;So;0;ON;;;;;N;;;;; 10196;ROMAN DENARIUS SIGN;So;0;ON;;;;;N;;;;; 10197;ROMAN QUINARIUS SIGN;So;0;ON;;;;;N;;;;; 10198;ROMAN SESTERTIUS SIGN;So;0;ON;;;;;N;;;;; 10199;ROMAN DUPONDIUS SIGN;So;0;ON;;;;;N;;;;; 1019A;ROMAN AS SIGN;So;0;ON;;;;;N;;;;; 1019B;ROMAN CENTURIAL SIGN;So;0;ON;;;;;N;;;;; 101A0;GREEK SYMBOL TAU RHO;So;0;ON;;;;;N;;;;; 101D0;PHAISTOS DISC SIGN PEDESTRIAN;So;0;L;;;;;N;;;;; 101D1;PHAISTOS DISC SIGN PLUMED HEAD;So;0;L;;;;;N;;;;; 101D2;PHAISTOS DISC SIGN TATTOOED HEAD;So;0;L;;;;;N;;;;; 101D3;PHAISTOS DISC SIGN CAPTIVE;So;0;L;;;;;N;;;;; 101D4;PHAISTOS DISC SIGN CHILD;So;0;L;;;;;N;;;;; 101D5;PHAISTOS DISC SIGN WOMAN;So;0;L;;;;;N;;;;; 101D6;PHAISTOS DISC SIGN HELMET;So;0;L;;;;;N;;;;; 101D7;PHAISTOS DISC SIGN GAUNTLET;So;0;L;;;;;N;;;;; 101D8;PHAISTOS DISC SIGN TIARA;So;0;L;;;;;N;;;;; 101D9;PHAISTOS DISC SIGN ARROW;So;0;L;;;;;N;;;;; 101DA;PHAISTOS DISC SIGN BOW;So;0;L;;;;;N;;;;; 101DB;PHAISTOS DISC SIGN SHIELD;So;0;L;;;;;N;;;;; 101DC;PHAISTOS DISC SIGN CLUB;So;0;L;;;;;N;;;;; 101DD;PHAISTOS DISC SIGN MANACLES;So;0;L;;;;;N;;;;; 101DE;PHAISTOS DISC SIGN MATTOCK;So;0;L;;;;;N;;;;; 101DF;PHAISTOS DISC SIGN SAW;So;0;L;;;;;N;;;;; 101E0;PHAISTOS DISC SIGN LID;So;0;L;;;;;N;;;;; 101E1;PHAISTOS DISC SIGN BOOMERANG;So;0;L;;;;;N;;;;; 101E2;PHAISTOS DISC SIGN CARPENTRY PLANE;So;0;L;;;;;N;;;;; 101E3;PHAISTOS DISC SIGN DOLIUM;So;0;L;;;;;N;;;;; 101E4;PHAISTOS DISC SIGN COMB;So;0;L;;;;;N;;;;; 101E5;PHAISTOS DISC SIGN SLING;So;0;L;;;;;N;;;;; 101E6;PHAISTOS DISC SIGN COLUMN;So;0;L;;;;;N;;;;; 101E7;PHAISTOS DISC SIGN BEEHIVE;So;0;L;;;;;N;;;;; 101E8;PHAISTOS DISC SIGN SHIP;So;0;L;;;;;N;;;;; 101E9;PHAISTOS DISC SIGN HORN;So;0;L;;;;;N;;;;; 101EA;PHAISTOS DISC SIGN HIDE;So;0;L;;;;;N;;;;; 101EB;PHAISTOS DISC SIGN BULLS LEG;So;0;L;;;;;N;;;;; 101EC;PHAISTOS DISC SIGN CAT;So;0;L;;;;;N;;;;; 101ED;PHAISTOS DISC SIGN RAM;So;0;L;;;;;N;;;;; 101EE;PHAISTOS DISC SIGN EAGLE;So;0;L;;;;;N;;;;; 101EF;PHAISTOS DISC SIGN DOVE;So;0;L;;;;;N;;;;; 101F0;PHAISTOS DISC SIGN TUNNY;So;0;L;;;;;N;;;;; 101F1;PHAISTOS DISC SIGN BEE;So;0;L;;;;;N;;;;; 101F2;PHAISTOS DISC SIGN PLANE TREE;So;0;L;;;;;N;;;;; 101F3;PHAISTOS DISC SIGN VINE;So;0;L;;;;;N;;;;; 101F4;PHAISTOS DISC SIGN PAPYRUS;So;0;L;;;;;N;;;;; 101F5;PHAISTOS DISC SIGN ROSETTE;So;0;L;;;;;N;;;;; 101F6;PHAISTOS DISC SIGN LILY;So;0;L;;;;;N;;;;; 101F7;PHAISTOS DISC SIGN OX BACK;So;0;L;;;;;N;;;;; 101F8;PHAISTOS DISC SIGN FLUTE;So;0;L;;;;;N;;;;; 101F9;PHAISTOS DISC SIGN GRATER;So;0;L;;;;;N;;;;; 101FA;PHAISTOS DISC SIGN STRAINER;So;0;L;;;;;N;;;;; 101FB;PHAISTOS DISC SIGN SMALL AXE;So;0;L;;;;;N;;;;; 101FC;PHAISTOS DISC SIGN WAVY BAND;So;0;L;;;;;N;;;;; 101FD;PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE;Mn;220;NSM;;;;;N;;;;; 10280;LYCIAN LETTER A;Lo;0;L;;;;;N;;;;; 10281;LYCIAN LETTER E;Lo;0;L;;;;;N;;;;; 10282;LYCIAN LETTER B;Lo;0;L;;;;;N;;;;; 10283;LYCIAN LETTER BH;Lo;0;L;;;;;N;;;;; 10284;LYCIAN LETTER G;Lo;0;L;;;;;N;;;;; 10285;LYCIAN LETTER D;Lo;0;L;;;;;N;;;;; 10286;LYCIAN LETTER I;Lo;0;L;;;;;N;;;;; 10287;LYCIAN LETTER W;Lo;0;L;;;;;N;;;;; 10288;LYCIAN LETTER Z;Lo;0;L;;;;;N;;;;; 10289;LYCIAN LETTER TH;Lo;0;L;;;;;N;;;;; 1028A;LYCIAN LETTER J;Lo;0;L;;;;;N;;;;; 1028B;LYCIAN LETTER K;Lo;0;L;;;;;N;;;;; 1028C;LYCIAN LETTER Q;Lo;0;L;;;;;N;;;;; 1028D;LYCIAN LETTER L;Lo;0;L;;;;;N;;;;; 1028E;LYCIAN LETTER M;Lo;0;L;;;;;N;;;;; 1028F;LYCIAN LETTER N;Lo;0;L;;;;;N;;;;; 10290;LYCIAN LETTER MM;Lo;0;L;;;;;N;;;;; 10291;LYCIAN LETTER NN;Lo;0;L;;;;;N;;;;; 10292;LYCIAN LETTER U;Lo;0;L;;;;;N;;;;; 10293;LYCIAN LETTER P;Lo;0;L;;;;;N;;;;; 10294;LYCIAN LETTER KK;Lo;0;L;;;;;N;;;;; 10295;LYCIAN LETTER R;Lo;0;L;;;;;N;;;;; 10296;LYCIAN LETTER S;Lo;0;L;;;;;N;;;;; 10297;LYCIAN LETTER T;Lo;0;L;;;;;N;;;;; 10298;LYCIAN LETTER TT;Lo;0;L;;;;;N;;;;; 10299;LYCIAN LETTER AN;Lo;0;L;;;;;N;;;;; 1029A;LYCIAN LETTER EN;Lo;0;L;;;;;N;;;;; 1029B;LYCIAN LETTER H;Lo;0;L;;;;;N;;;;; 1029C;LYCIAN LETTER X;Lo;0;L;;;;;N;;;;; 102A0;CARIAN LETTER A;Lo;0;L;;;;;N;;;;; 102A1;CARIAN LETTER P2;Lo;0;L;;;;;N;;;;; 102A2;CARIAN LETTER D;Lo;0;L;;;;;N;;;;; 102A3;CARIAN LETTER L;Lo;0;L;;;;;N;;;;; 102A4;CARIAN LETTER UUU;Lo;0;L;;;;;N;;;;; 102A5;CARIAN LETTER R;Lo;0;L;;;;;N;;;;; 102A6;CARIAN LETTER LD;Lo;0;L;;;;;N;;;;; 102A7;CARIAN LETTER A2;Lo;0;L;;;;;N;;;;; 102A8;CARIAN LETTER Q;Lo;0;L;;;;;N;;;;; 102A9;CARIAN LETTER B;Lo;0;L;;;;;N;;;;; 102AA;CARIAN LETTER M;Lo;0;L;;;;;N;;;;; 102AB;CARIAN LETTER O;Lo;0;L;;;;;N;;;;; 102AC;CARIAN LETTER D2;Lo;0;L;;;;;N;;;;; 102AD;CARIAN LETTER T;Lo;0;L;;;;;N;;;;; 102AE;CARIAN LETTER SH;Lo;0;L;;;;;N;;;;; 102AF;CARIAN LETTER SH2;Lo;0;L;;;;;N;;;;; 102B0;CARIAN LETTER S;Lo;0;L;;;;;N;;;;; 102B1;CARIAN LETTER C-18;Lo;0;L;;;;;N;;;;; 102B2;CARIAN LETTER U;Lo;0;L;;;;;N;;;;; 102B3;CARIAN LETTER NN;Lo;0;L;;;;;N;;;;; 102B4;CARIAN LETTER X;Lo;0;L;;;;;N;;;;; 102B5;CARIAN LETTER N;Lo;0;L;;;;;N;;;;; 102B6;CARIAN LETTER TT2;Lo;0;L;;;;;N;;;;; 102B7;CARIAN LETTER P;Lo;0;L;;;;;N;;;;; 102B8;CARIAN LETTER SS;Lo;0;L;;;;;N;;;;; 102B9;CARIAN LETTER I;Lo;0;L;;;;;N;;;;; 102BA;CARIAN LETTER E;Lo;0;L;;;;;N;;;;; 102BB;CARIAN LETTER UUUU;Lo;0;L;;;;;N;;;;; 102BC;CARIAN LETTER K;Lo;0;L;;;;;N;;;;; 102BD;CARIAN LETTER K2;Lo;0;L;;;;;N;;;;; 102BE;CARIAN LETTER ND;Lo;0;L;;;;;N;;;;; 102BF;CARIAN LETTER UU;Lo;0;L;;;;;N;;;;; 102C0;CARIAN LETTER G;Lo;0;L;;;;;N;;;;; 102C1;CARIAN LETTER G2;Lo;0;L;;;;;N;;;;; 102C2;CARIAN LETTER ST;Lo;0;L;;;;;N;;;;; 102C3;CARIAN LETTER ST2;Lo;0;L;;;;;N;;;;; 102C4;CARIAN LETTER NG;Lo;0;L;;;;;N;;;;; 102C5;CARIAN LETTER II;Lo;0;L;;;;;N;;;;; 102C6;CARIAN LETTER C-39;Lo;0;L;;;;;N;;;;; 102C7;CARIAN LETTER TT;Lo;0;L;;;;;N;;;;; 102C8;CARIAN LETTER UUU2;Lo;0;L;;;;;N;;;;; 102C9;CARIAN LETTER RR;Lo;0;L;;;;;N;;;;; 102CA;CARIAN LETTER MB;Lo;0;L;;;;;N;;;;; 102CB;CARIAN LETTER MB2;Lo;0;L;;;;;N;;;;; 102CC;CARIAN LETTER MB3;Lo;0;L;;;;;N;;;;; 102CD;CARIAN LETTER MB4;Lo;0;L;;;;;N;;;;; 102CE;CARIAN LETTER LD2;Lo;0;L;;;;;N;;;;; 102CF;CARIAN LETTER E2;Lo;0;L;;;;;N;;;;; 102D0;CARIAN LETTER UUU3;Lo;0;L;;;;;N;;;;; 102E0;COPTIC EPACT THOUSANDS MARK;Mn;220;NSM;;;;;N;;;;; 102E1;COPTIC EPACT DIGIT ONE;No;0;EN;;;;1;N;;;;; 102E2;COPTIC EPACT DIGIT TWO;No;0;EN;;;;2;N;;;;; 102E3;COPTIC EPACT DIGIT THREE;No;0;EN;;;;3;N;;;;; 102E4;COPTIC EPACT DIGIT FOUR;No;0;EN;;;;4;N;;;;; 102E5;COPTIC EPACT DIGIT FIVE;No;0;EN;;;;5;N;;;;; 102E6;COPTIC EPACT DIGIT SIX;No;0;EN;;;;6;N;;;;; 102E7;COPTIC EPACT DIGIT SEVEN;No;0;EN;;;;7;N;;;;; 102E8;COPTIC EPACT DIGIT EIGHT;No;0;EN;;;;8;N;;;;; 102E9;COPTIC EPACT DIGIT NINE;No;0;EN;;;;9;N;;;;; 102EA;COPTIC EPACT NUMBER TEN;No;0;EN;;;;10;N;;;;; 102EB;COPTIC EPACT NUMBER TWENTY;No;0;EN;;;;20;N;;;;; 102EC;COPTIC EPACT NUMBER THIRTY;No;0;EN;;;;30;N;;;;; 102ED;COPTIC EPACT NUMBER FORTY;No;0;EN;;;;40;N;;;;; 102EE;COPTIC EPACT NUMBER FIFTY;No;0;EN;;;;50;N;;;;; 102EF;COPTIC EPACT NUMBER SIXTY;No;0;EN;;;;60;N;;;;; 102F0;COPTIC EPACT NUMBER SEVENTY;No;0;EN;;;;70;N;;;;; 102F1;COPTIC EPACT NUMBER EIGHTY;No;0;EN;;;;80;N;;;;; 102F2;COPTIC EPACT NUMBER NINETY;No;0;EN;;;;90;N;;;;; 102F3;COPTIC EPACT NUMBER ONE HUNDRED;No;0;EN;;;;100;N;;;;; 102F4;COPTIC EPACT NUMBER TWO HUNDRED;No;0;EN;;;;200;N;;;;; 102F5;COPTIC EPACT NUMBER THREE HUNDRED;No;0;EN;;;;300;N;;;;; 102F6;COPTIC EPACT NUMBER FOUR HUNDRED;No;0;EN;;;;400;N;;;;; 102F7;COPTIC EPACT NUMBER FIVE HUNDRED;No;0;EN;;;;500;N;;;;; 102F8;COPTIC EPACT NUMBER SIX HUNDRED;No;0;EN;;;;600;N;;;;; 102F9;COPTIC EPACT NUMBER SEVEN HUNDRED;No;0;EN;;;;700;N;;;;; 102FA;COPTIC EPACT NUMBER EIGHT HUNDRED;No;0;EN;;;;800;N;;;;; 102FB;COPTIC EPACT NUMBER NINE HUNDRED;No;0;EN;;;;900;N;;;;; 10300;OLD ITALIC LETTER A;Lo;0;L;;;;;N;;;;; 10301;OLD ITALIC LETTER BE;Lo;0;L;;;;;N;;;;; 10302;OLD ITALIC LETTER KE;Lo;0;L;;;;;N;;;;; 10303;OLD ITALIC LETTER DE;Lo;0;L;;;;;N;;;;; 10304;OLD ITALIC LETTER E;Lo;0;L;;;;;N;;;;; 10305;OLD ITALIC LETTER VE;Lo;0;L;;;;;N;;;;; 10306;OLD ITALIC LETTER ZE;Lo;0;L;;;;;N;;;;; 10307;OLD ITALIC LETTER HE;Lo;0;L;;;;;N;;;;; 10308;OLD ITALIC LETTER THE;Lo;0;L;;;;;N;;;;; 10309;OLD ITALIC LETTER I;Lo;0;L;;;;;N;;;;; 1030A;OLD ITALIC LETTER KA;Lo;0;L;;;;;N;;;;; 1030B;OLD ITALIC LETTER EL;Lo;0;L;;;;;N;;;;; 1030C;OLD ITALIC LETTER EM;Lo;0;L;;;;;N;;;;; 1030D;OLD ITALIC LETTER EN;Lo;0;L;;;;;N;;;;; 1030E;OLD ITALIC LETTER ESH;Lo;0;L;;;;;N;;;;; 1030F;OLD ITALIC LETTER O;Lo;0;L;;;;;N;;;;; 10310;OLD ITALIC LETTER PE;Lo;0;L;;;;;N;;;;; 10311;OLD ITALIC LETTER SHE;Lo;0;L;;;;;N;;;;; 10312;OLD ITALIC LETTER KU;Lo;0;L;;;;;N;;;;; 10313;OLD ITALIC LETTER ER;Lo;0;L;;;;;N;;;;; 10314;OLD ITALIC LETTER ES;Lo;0;L;;;;;N;;;;; 10315;OLD ITALIC LETTER TE;Lo;0;L;;;;;N;;;;; 10316;OLD ITALIC LETTER U;Lo;0;L;;;;;N;;;;; 10317;OLD ITALIC LETTER EKS;Lo;0;L;;;;;N;;;;; 10318;OLD ITALIC LETTER PHE;Lo;0;L;;;;;N;;;;; 10319;OLD ITALIC LETTER KHE;Lo;0;L;;;;;N;;;;; 1031A;OLD ITALIC LETTER EF;Lo;0;L;;;;;N;;;;; 1031B;OLD ITALIC LETTER ERS;Lo;0;L;;;;;N;;;;; 1031C;OLD ITALIC LETTER CHE;Lo;0;L;;;;;N;;;;; 1031D;OLD ITALIC LETTER II;Lo;0;L;;;;;N;;;;; 1031E;OLD ITALIC LETTER UU;Lo;0;L;;;;;N;;;;; 1031F;OLD ITALIC LETTER ESS;Lo;0;L;;;;;N;;;;; 10320;OLD ITALIC NUMERAL ONE;No;0;L;;;;1;N;;;;; 10321;OLD ITALIC NUMERAL FIVE;No;0;L;;;;5;N;;;;; 10322;OLD ITALIC NUMERAL TEN;No;0;L;;;;10;N;;;;; 10323;OLD ITALIC NUMERAL FIFTY;No;0;L;;;;50;N;;;;; 10330;GOTHIC LETTER AHSA;Lo;0;L;;;;;N;;;;; 10331;GOTHIC LETTER BAIRKAN;Lo;0;L;;;;;N;;;;; 10332;GOTHIC LETTER GIBA;Lo;0;L;;;;;N;;;;; 10333;GOTHIC LETTER DAGS;Lo;0;L;;;;;N;;;;; 10334;GOTHIC LETTER AIHVUS;Lo;0;L;;;;;N;;;;; 10335;GOTHIC LETTER QAIRTHRA;Lo;0;L;;;;;N;;;;; 10336;GOTHIC LETTER IUJA;Lo;0;L;;;;;N;;;;; 10337;GOTHIC LETTER HAGL;Lo;0;L;;;;;N;;;;; 10338;GOTHIC LETTER THIUTH;Lo;0;L;;;;;N;;;;; 10339;GOTHIC LETTER EIS;Lo;0;L;;;;;N;;;;; 1033A;GOTHIC LETTER KUSMA;Lo;0;L;;;;;N;;;;; 1033B;GOTHIC LETTER LAGUS;Lo;0;L;;;;;N;;;;; 1033C;GOTHIC LETTER MANNA;Lo;0;L;;;;;N;;;;; 1033D;GOTHIC LETTER NAUTHS;Lo;0;L;;;;;N;;;;; 1033E;GOTHIC LETTER JER;Lo;0;L;;;;;N;;;;; 1033F;GOTHIC LETTER URUS;Lo;0;L;;;;;N;;;;; 10340;GOTHIC LETTER PAIRTHRA;Lo;0;L;;;;;N;;;;; 10341;GOTHIC LETTER NINETY;Nl;0;L;;;;90;N;;;;; 10342;GOTHIC LETTER RAIDA;Lo;0;L;;;;;N;;;;; 10343;GOTHIC LETTER SAUIL;Lo;0;L;;;;;N;;;;; 10344;GOTHIC LETTER TEIWS;Lo;0;L;;;;;N;;;;; 10345;GOTHIC LETTER WINJA;Lo;0;L;;;;;N;;;;; 10346;GOTHIC LETTER FAIHU;Lo;0;L;;;;;N;;;;; 10347;GOTHIC LETTER IGGWS;Lo;0;L;;;;;N;;;;; 10348;GOTHIC LETTER HWAIR;Lo;0;L;;;;;N;;;;; 10349;GOTHIC LETTER OTHAL;Lo;0;L;;;;;N;;;;; 1034A;GOTHIC LETTER NINE HUNDRED;Nl;0;L;;;;900;N;;;;; 10350;OLD PERMIC LETTER AN;Lo;0;L;;;;;N;;;;; 10351;OLD PERMIC LETTER BUR;Lo;0;L;;;;;N;;;;; 10352;OLD PERMIC LETTER GAI;Lo;0;L;;;;;N;;;;; 10353;OLD PERMIC LETTER DOI;Lo;0;L;;;;;N;;;;; 10354;OLD PERMIC LETTER E;Lo;0;L;;;;;N;;;;; 10355;OLD PERMIC LETTER ZHOI;Lo;0;L;;;;;N;;;;; 10356;OLD PERMIC LETTER DZHOI;Lo;0;L;;;;;N;;;;; 10357;OLD PERMIC LETTER ZATA;Lo;0;L;;;;;N;;;;; 10358;OLD PERMIC LETTER DZITA;Lo;0;L;;;;;N;;;;; 10359;OLD PERMIC LETTER I;Lo;0;L;;;;;N;;;;; 1035A;OLD PERMIC LETTER KOKE;Lo;0;L;;;;;N;;;;; 1035B;OLD PERMIC LETTER LEI;Lo;0;L;;;;;N;;;;; 1035C;OLD PERMIC LETTER MENOE;Lo;0;L;;;;;N;;;;; 1035D;OLD PERMIC LETTER NENOE;Lo;0;L;;;;;N;;;;; 1035E;OLD PERMIC LETTER VOOI;Lo;0;L;;;;;N;;;;; 1035F;OLD PERMIC LETTER PEEI;Lo;0;L;;;;;N;;;;; 10360;OLD PERMIC LETTER REI;Lo;0;L;;;;;N;;;;; 10361;OLD PERMIC LETTER SII;Lo;0;L;;;;;N;;;;; 10362;OLD PERMIC LETTER TAI;Lo;0;L;;;;;N;;;;; 10363;OLD PERMIC LETTER U;Lo;0;L;;;;;N;;;;; 10364;OLD PERMIC LETTER CHERY;Lo;0;L;;;;;N;;;;; 10365;OLD PERMIC LETTER SHOOI;Lo;0;L;;;;;N;;;;; 10366;OLD PERMIC LETTER SHCHOOI;Lo;0;L;;;;;N;;;;; 10367;OLD PERMIC LETTER YRY;Lo;0;L;;;;;N;;;;; 10368;OLD PERMIC LETTER YERU;Lo;0;L;;;;;N;;;;; 10369;OLD PERMIC LETTER O;Lo;0;L;;;;;N;;;;; 1036A;OLD PERMIC LETTER OO;Lo;0;L;;;;;N;;;;; 1036B;OLD PERMIC LETTER EF;Lo;0;L;;;;;N;;;;; 1036C;OLD PERMIC LETTER HA;Lo;0;L;;;;;N;;;;; 1036D;OLD PERMIC LETTER TSIU;Lo;0;L;;;;;N;;;;; 1036E;OLD PERMIC LETTER VER;Lo;0;L;;;;;N;;;;; 1036F;OLD PERMIC LETTER YER;Lo;0;L;;;;;N;;;;; 10370;OLD PERMIC LETTER YERI;Lo;0;L;;;;;N;;;;; 10371;OLD PERMIC LETTER YAT;Lo;0;L;;;;;N;;;;; 10372;OLD PERMIC LETTER IE;Lo;0;L;;;;;N;;;;; 10373;OLD PERMIC LETTER YU;Lo;0;L;;;;;N;;;;; 10374;OLD PERMIC LETTER YA;Lo;0;L;;;;;N;;;;; 10375;OLD PERMIC LETTER IA;Lo;0;L;;;;;N;;;;; 10376;COMBINING OLD PERMIC LETTER AN;Mn;230;NSM;;;;;N;;;;; 10377;COMBINING OLD PERMIC LETTER DOI;Mn;230;NSM;;;;;N;;;;; 10378;COMBINING OLD PERMIC LETTER ZATA;Mn;230;NSM;;;;;N;;;;; 10379;COMBINING OLD PERMIC LETTER NENOE;Mn;230;NSM;;;;;N;;;;; 1037A;COMBINING OLD PERMIC LETTER SII;Mn;230;NSM;;;;;N;;;;; 10380;UGARITIC LETTER ALPA;Lo;0;L;;;;;N;;;;; 10381;UGARITIC LETTER BETA;Lo;0;L;;;;;N;;;;; 10382;UGARITIC LETTER GAMLA;Lo;0;L;;;;;N;;;;; 10383;UGARITIC LETTER KHA;Lo;0;L;;;;;N;;;;; 10384;UGARITIC LETTER DELTA;Lo;0;L;;;;;N;;;;; 10385;UGARITIC LETTER HO;Lo;0;L;;;;;N;;;;; 10386;UGARITIC LETTER WO;Lo;0;L;;;;;N;;;;; 10387;UGARITIC LETTER ZETA;Lo;0;L;;;;;N;;;;; 10388;UGARITIC LETTER HOTA;Lo;0;L;;;;;N;;;;; 10389;UGARITIC LETTER TET;Lo;0;L;;;;;N;;;;; 1038A;UGARITIC LETTER YOD;Lo;0;L;;;;;N;;;;; 1038B;UGARITIC LETTER KAF;Lo;0;L;;;;;N;;;;; 1038C;UGARITIC LETTER SHIN;Lo;0;L;;;;;N;;;;; 1038D;UGARITIC LETTER LAMDA;Lo;0;L;;;;;N;;;;; 1038E;UGARITIC LETTER MEM;Lo;0;L;;;;;N;;;;; 1038F;UGARITIC LETTER DHAL;Lo;0;L;;;;;N;;;;; 10390;UGARITIC LETTER NUN;Lo;0;L;;;;;N;;;;; 10391;UGARITIC LETTER ZU;Lo;0;L;;;;;N;;;;; 10392;UGARITIC LETTER SAMKA;Lo;0;L;;;;;N;;;;; 10393;UGARITIC LETTER AIN;Lo;0;L;;;;;N;;;;; 10394;UGARITIC LETTER PU;Lo;0;L;;;;;N;;;;; 10395;UGARITIC LETTER SADE;Lo;0;L;;;;;N;;;;; 10396;UGARITIC LETTER QOPA;Lo;0;L;;;;;N;;;;; 10397;UGARITIC LETTER RASHA;Lo;0;L;;;;;N;;;;; 10398;UGARITIC LETTER THANNA;Lo;0;L;;;;;N;;;;; 10399;UGARITIC LETTER GHAIN;Lo;0;L;;;;;N;;;;; 1039A;UGARITIC LETTER TO;Lo;0;L;;;;;N;;;;; 1039B;UGARITIC LETTER I;Lo;0;L;;;;;N;;;;; 1039C;UGARITIC LETTER U;Lo;0;L;;;;;N;;;;; 1039D;UGARITIC LETTER SSU;Lo;0;L;;;;;N;;;;; 1039F;UGARITIC WORD DIVIDER;Po;0;L;;;;;N;;;;; 103A0;OLD PERSIAN SIGN A;Lo;0;L;;;;;N;;;;; 103A1;OLD PERSIAN SIGN I;Lo;0;L;;;;;N;;;;; 103A2;OLD PERSIAN SIGN U;Lo;0;L;;;;;N;;;;; 103A3;OLD PERSIAN SIGN KA;Lo;0;L;;;;;N;;;;; 103A4;OLD PERSIAN SIGN KU;Lo;0;L;;;;;N;;;;; 103A5;OLD PERSIAN SIGN GA;Lo;0;L;;;;;N;;;;; 103A6;OLD PERSIAN SIGN GU;Lo;0;L;;;;;N;;;;; 103A7;OLD PERSIAN SIGN XA;Lo;0;L;;;;;N;;;;; 103A8;OLD PERSIAN SIGN CA;Lo;0;L;;;;;N;;;;; 103A9;OLD PERSIAN SIGN JA;Lo;0;L;;;;;N;;;;; 103AA;OLD PERSIAN SIGN JI;Lo;0;L;;;;;N;;;;; 103AB;OLD PERSIAN SIGN TA;Lo;0;L;;;;;N;;;;; 103AC;OLD PERSIAN SIGN TU;Lo;0;L;;;;;N;;;;; 103AD;OLD PERSIAN SIGN DA;Lo;0;L;;;;;N;;;;; 103AE;OLD PERSIAN SIGN DI;Lo;0;L;;;;;N;;;;; 103AF;OLD PERSIAN SIGN DU;Lo;0;L;;;;;N;;;;; 103B0;OLD PERSIAN SIGN THA;Lo;0;L;;;;;N;;;;; 103B1;OLD PERSIAN SIGN PA;Lo;0;L;;;;;N;;;;; 103B2;OLD PERSIAN SIGN BA;Lo;0;L;;;;;N;;;;; 103B3;OLD PERSIAN SIGN FA;Lo;0;L;;;;;N;;;;; 103B4;OLD PERSIAN SIGN NA;Lo;0;L;;;;;N;;;;; 103B5;OLD PERSIAN SIGN NU;Lo;0;L;;;;;N;;;;; 103B6;OLD PERSIAN SIGN MA;Lo;0;L;;;;;N;;;;; 103B7;OLD PERSIAN SIGN MI;Lo;0;L;;;;;N;;;;; 103B8;OLD PERSIAN SIGN MU;Lo;0;L;;;;;N;;;;; 103B9;OLD PERSIAN SIGN YA;Lo;0;L;;;;;N;;;;; 103BA;OLD PERSIAN SIGN VA;Lo;0;L;;;;;N;;;;; 103BB;OLD PERSIAN SIGN VI;Lo;0;L;;;;;N;;;;; 103BC;OLD PERSIAN SIGN RA;Lo;0;L;;;;;N;;;;; 103BD;OLD PERSIAN SIGN RU;Lo;0;L;;;;;N;;;;; 103BE;OLD PERSIAN SIGN LA;Lo;0;L;;;;;N;;;;; 103BF;OLD PERSIAN SIGN SA;Lo;0;L;;;;;N;;;;; 103C0;OLD PERSIAN SIGN ZA;Lo;0;L;;;;;N;;;;; 103C1;OLD PERSIAN SIGN SHA;Lo;0;L;;;;;N;;;;; 103C2;OLD PERSIAN SIGN SSA;Lo;0;L;;;;;N;;;;; 103C3;OLD PERSIAN SIGN HA;Lo;0;L;;;;;N;;;;; 103C8;OLD PERSIAN SIGN AURAMAZDAA;Lo;0;L;;;;;N;;;;; 103C9;OLD PERSIAN SIGN AURAMAZDAA-2;Lo;0;L;;;;;N;;;;; 103CA;OLD PERSIAN SIGN AURAMAZDAAHA;Lo;0;L;;;;;N;;;;; 103CB;OLD PERSIAN SIGN XSHAAYATHIYA;Lo;0;L;;;;;N;;;;; 103CC;OLD PERSIAN SIGN DAHYAAUSH;Lo;0;L;;;;;N;;;;; 103CD;OLD PERSIAN SIGN DAHYAAUSH-2;Lo;0;L;;;;;N;;;;; 103CE;OLD PERSIAN SIGN BAGA;Lo;0;L;;;;;N;;;;; 103CF;OLD PERSIAN SIGN BUUMISH;Lo;0;L;;;;;N;;;;; 103D0;OLD PERSIAN WORD DIVIDER;Po;0;L;;;;;N;;;;; 103D1;OLD PERSIAN NUMBER ONE;Nl;0;L;;;;1;N;;;;; 103D2;OLD PERSIAN NUMBER TWO;Nl;0;L;;;;2;N;;;;; 103D3;OLD PERSIAN NUMBER TEN;Nl;0;L;;;;10;N;;;;; 103D4;OLD PERSIAN NUMBER TWENTY;Nl;0;L;;;;20;N;;;;; 103D5;OLD PERSIAN NUMBER HUNDRED;Nl;0;L;;;;100;N;;;;; 10400;DESERET CAPITAL LETTER LONG I;Lu;0;L;;;;;N;;;;10428; 10401;DESERET CAPITAL LETTER LONG E;Lu;0;L;;;;;N;;;;10429; 10402;DESERET CAPITAL LETTER LONG A;Lu;0;L;;;;;N;;;;1042A; 10403;DESERET CAPITAL LETTER LONG AH;Lu;0;L;;;;;N;;;;1042B; 10404;DESERET CAPITAL LETTER LONG O;Lu;0;L;;;;;N;;;;1042C; 10405;DESERET CAPITAL LETTER LONG OO;Lu;0;L;;;;;N;;;;1042D; 10406;DESERET CAPITAL LETTER SHORT I;Lu;0;L;;;;;N;;;;1042E; 10407;DESERET CAPITAL LETTER SHORT E;Lu;0;L;;;;;N;;;;1042F; 10408;DESERET CAPITAL LETTER SHORT A;Lu;0;L;;;;;N;;;;10430; 10409;DESERET CAPITAL LETTER SHORT AH;Lu;0;L;;;;;N;;;;10431; 1040A;DESERET CAPITAL LETTER SHORT O;Lu;0;L;;;;;N;;;;10432; 1040B;DESERET CAPITAL LETTER SHORT OO;Lu;0;L;;;;;N;;;;10433; 1040C;DESERET CAPITAL LETTER AY;Lu;0;L;;;;;N;;;;10434; 1040D;DESERET CAPITAL LETTER OW;Lu;0;L;;;;;N;;;;10435; 1040E;DESERET CAPITAL LETTER WU;Lu;0;L;;;;;N;;;;10436; 1040F;DESERET CAPITAL LETTER YEE;Lu;0;L;;;;;N;;;;10437; 10410;DESERET CAPITAL LETTER H;Lu;0;L;;;;;N;;;;10438; 10411;DESERET CAPITAL LETTER PEE;Lu;0;L;;;;;N;;;;10439; 10412;DESERET CAPITAL LETTER BEE;Lu;0;L;;;;;N;;;;1043A; 10413;DESERET CAPITAL LETTER TEE;Lu;0;L;;;;;N;;;;1043B; 10414;DESERET CAPITAL LETTER DEE;Lu;0;L;;;;;N;;;;1043C; 10415;DESERET CAPITAL LETTER CHEE;Lu;0;L;;;;;N;;;;1043D; 10416;DESERET CAPITAL LETTER JEE;Lu;0;L;;;;;N;;;;1043E; 10417;DESERET CAPITAL LETTER KAY;Lu;0;L;;;;;N;;;;1043F; 10418;DESERET CAPITAL LETTER GAY;Lu;0;L;;;;;N;;;;10440; 10419;DESERET CAPITAL LETTER EF;Lu;0;L;;;;;N;;;;10441; 1041A;DESERET CAPITAL LETTER VEE;Lu;0;L;;;;;N;;;;10442; 1041B;DESERET CAPITAL LETTER ETH;Lu;0;L;;;;;N;;;;10443; 1041C;DESERET CAPITAL LETTER THEE;Lu;0;L;;;;;N;;;;10444; 1041D;DESERET CAPITAL LETTER ES;Lu;0;L;;;;;N;;;;10445; 1041E;DESERET CAPITAL LETTER ZEE;Lu;0;L;;;;;N;;;;10446; 1041F;DESERET CAPITAL LETTER ESH;Lu;0;L;;;;;N;;;;10447; 10420;DESERET CAPITAL LETTER ZHEE;Lu;0;L;;;;;N;;;;10448; 10421;DESERET CAPITAL LETTER ER;Lu;0;L;;;;;N;;;;10449; 10422;DESERET CAPITAL LETTER EL;Lu;0;L;;;;;N;;;;1044A; 10423;DESERET CAPITAL LETTER EM;Lu;0;L;;;;;N;;;;1044B; 10424;DESERET CAPITAL LETTER EN;Lu;0;L;;;;;N;;;;1044C; 10425;DESERET CAPITAL LETTER ENG;Lu;0;L;;;;;N;;;;1044D; 10426;DESERET CAPITAL LETTER OI;Lu;0;L;;;;;N;;;;1044E; 10427;DESERET CAPITAL LETTER EW;Lu;0;L;;;;;N;;;;1044F; 10428;DESERET SMALL LETTER LONG I;Ll;0;L;;;;;N;;;10400;;10400 10429;DESERET SMALL LETTER LONG E;Ll;0;L;;;;;N;;;10401;;10401 1042A;DESERET SMALL LETTER LONG A;Ll;0;L;;;;;N;;;10402;;10402 1042B;DESERET SMALL LETTER LONG AH;Ll;0;L;;;;;N;;;10403;;10403 1042C;DESERET SMALL LETTER LONG O;Ll;0;L;;;;;N;;;10404;;10404 1042D;DESERET SMALL LETTER LONG OO;Ll;0;L;;;;;N;;;10405;;10405 1042E;DESERET SMALL LETTER SHORT I;Ll;0;L;;;;;N;;;10406;;10406 1042F;DESERET SMALL LETTER SHORT E;Ll;0;L;;;;;N;;;10407;;10407 10430;DESERET SMALL LETTER SHORT A;Ll;0;L;;;;;N;;;10408;;10408 10431;DESERET SMALL LETTER SHORT AH;Ll;0;L;;;;;N;;;10409;;10409 10432;DESERET SMALL LETTER SHORT O;Ll;0;L;;;;;N;;;1040A;;1040A 10433;DESERET SMALL LETTER SHORT OO;Ll;0;L;;;;;N;;;1040B;;1040B 10434;DESERET SMALL LETTER AY;Ll;0;L;;;;;N;;;1040C;;1040C 10435;DESERET SMALL LETTER OW;Ll;0;L;;;;;N;;;1040D;;1040D 10436;DESERET SMALL LETTER WU;Ll;0;L;;;;;N;;;1040E;;1040E 10437;DESERET SMALL LETTER YEE;Ll;0;L;;;;;N;;;1040F;;1040F 10438;DESERET SMALL LETTER H;Ll;0;L;;;;;N;;;10410;;10410 10439;DESERET SMALL LETTER PEE;Ll;0;L;;;;;N;;;10411;;10411 1043A;DESERET SMALL LETTER BEE;Ll;0;L;;;;;N;;;10412;;10412 1043B;DESERET SMALL LETTER TEE;Ll;0;L;;;;;N;;;10413;;10413 1043C;DESERET SMALL LETTER DEE;Ll;0;L;;;;;N;;;10414;;10414 1043D;DESERET SMALL LETTER CHEE;Ll;0;L;;;;;N;;;10415;;10415 1043E;DESERET SMALL LETTER JEE;Ll;0;L;;;;;N;;;10416;;10416 1043F;DESERET SMALL LETTER KAY;Ll;0;L;;;;;N;;;10417;;10417 10440;DESERET SMALL LETTER GAY;Ll;0;L;;;;;N;;;10418;;10418 10441;DESERET SMALL LETTER EF;Ll;0;L;;;;;N;;;10419;;10419 10442;DESERET SMALL LETTER VEE;Ll;0;L;;;;;N;;;1041A;;1041A 10443;DESERET SMALL LETTER ETH;Ll;0;L;;;;;N;;;1041B;;1041B 10444;DESERET SMALL LETTER THEE;Ll;0;L;;;;;N;;;1041C;;1041C 10445;DESERET SMALL LETTER ES;Ll;0;L;;;;;N;;;1041D;;1041D 10446;DESERET SMALL LETTER ZEE;Ll;0;L;;;;;N;;;1041E;;1041E 10447;DESERET SMALL LETTER ESH;Ll;0;L;;;;;N;;;1041F;;1041F 10448;DESERET SMALL LETTER ZHEE;Ll;0;L;;;;;N;;;10420;;10420 10449;DESERET SMALL LETTER ER;Ll;0;L;;;;;N;;;10421;;10421 1044A;DESERET SMALL LETTER EL;Ll;0;L;;;;;N;;;10422;;10422 1044B;DESERET SMALL LETTER EM;Ll;0;L;;;;;N;;;10423;;10423 1044C;DESERET SMALL LETTER EN;Ll;0;L;;;;;N;;;10424;;10424 1044D;DESERET SMALL LETTER ENG;Ll;0;L;;;;;N;;;10425;;10425 1044E;DESERET SMALL LETTER OI;Ll;0;L;;;;;N;;;10426;;10426 1044F;DESERET SMALL LETTER EW;Ll;0;L;;;;;N;;;10427;;10427 10450;SHAVIAN LETTER PEEP;Lo;0;L;;;;;N;;;;; 10451;SHAVIAN LETTER TOT;Lo;0;L;;;;;N;;;;; 10452;SHAVIAN LETTER KICK;Lo;0;L;;;;;N;;;;; 10453;SHAVIAN LETTER FEE;Lo;0;L;;;;;N;;;;; 10454;SHAVIAN LETTER THIGH;Lo;0;L;;;;;N;;;;; 10455;SHAVIAN LETTER SO;Lo;0;L;;;;;N;;;;; 10456;SHAVIAN LETTER SURE;Lo;0;L;;;;;N;;;;; 10457;SHAVIAN LETTER CHURCH;Lo;0;L;;;;;N;;;;; 10458;SHAVIAN LETTER YEA;Lo;0;L;;;;;N;;;;; 10459;SHAVIAN LETTER HUNG;Lo;0;L;;;;;N;;;;; 1045A;SHAVIAN LETTER BIB;Lo;0;L;;;;;N;;;;; 1045B;SHAVIAN LETTER DEAD;Lo;0;L;;;;;N;;;;; 1045C;SHAVIAN LETTER GAG;Lo;0;L;;;;;N;;;;; 1045D;SHAVIAN LETTER VOW;Lo;0;L;;;;;N;;;;; 1045E;SHAVIAN LETTER THEY;Lo;0;L;;;;;N;;;;; 1045F;SHAVIAN LETTER ZOO;Lo;0;L;;;;;N;;;;; 10460;SHAVIAN LETTER MEASURE;Lo;0;L;;;;;N;;;;; 10461;SHAVIAN LETTER JUDGE;Lo;0;L;;;;;N;;;;; 10462;SHAVIAN LETTER WOE;Lo;0;L;;;;;N;;;;; 10463;SHAVIAN LETTER HA-HA;Lo;0;L;;;;;N;;;;; 10464;SHAVIAN LETTER LOLL;Lo;0;L;;;;;N;;;;; 10465;SHAVIAN LETTER MIME;Lo;0;L;;;;;N;;;;; 10466;SHAVIAN LETTER IF;Lo;0;L;;;;;N;;;;; 10467;SHAVIAN LETTER EGG;Lo;0;L;;;;;N;;;;; 10468;SHAVIAN LETTER ASH;Lo;0;L;;;;;N;;;;; 10469;SHAVIAN LETTER ADO;Lo;0;L;;;;;N;;;;; 1046A;SHAVIAN LETTER ON;Lo;0;L;;;;;N;;;;; 1046B;SHAVIAN LETTER WOOL;Lo;0;L;;;;;N;;;;; 1046C;SHAVIAN LETTER OUT;Lo;0;L;;;;;N;;;;; 1046D;SHAVIAN LETTER AH;Lo;0;L;;;;;N;;;;; 1046E;SHAVIAN LETTER ROAR;Lo;0;L;;;;;N;;;;; 1046F;SHAVIAN LETTER NUN;Lo;0;L;;;;;N;;;;; 10470;SHAVIAN LETTER EAT;Lo;0;L;;;;;N;;;;; 10471;SHAVIAN LETTER AGE;Lo;0;L;;;;;N;;;;; 10472;SHAVIAN LETTER ICE;Lo;0;L;;;;;N;;;;; 10473;SHAVIAN LETTER UP;Lo;0;L;;;;;N;;;;; 10474;SHAVIAN LETTER OAK;Lo;0;L;;;;;N;;;;; 10475;SHAVIAN LETTER OOZE;Lo;0;L;;;;;N;;;;; 10476;SHAVIAN LETTER OIL;Lo;0;L;;;;;N;;;;; 10477;SHAVIAN LETTER AWE;Lo;0;L;;;;;N;;;;; 10478;SHAVIAN LETTER ARE;Lo;0;L;;;;;N;;;;; 10479;SHAVIAN LETTER OR;Lo;0;L;;;;;N;;;;; 1047A;SHAVIAN LETTER AIR;Lo;0;L;;;;;N;;;;; 1047B;SHAVIAN LETTER ERR;Lo;0;L;;;;;N;;;;; 1047C;SHAVIAN LETTER ARRAY;Lo;0;L;;;;;N;;;;; 1047D;SHAVIAN LETTER EAR;Lo;0;L;;;;;N;;;;; 1047E;SHAVIAN LETTER IAN;Lo;0;L;;;;;N;;;;; 1047F;SHAVIAN LETTER YEW;Lo;0;L;;;;;N;;;;; 10480;OSMANYA LETTER ALEF;Lo;0;L;;;;;N;;;;; 10481;OSMANYA LETTER BA;Lo;0;L;;;;;N;;;;; 10482;OSMANYA LETTER TA;Lo;0;L;;;;;N;;;;; 10483;OSMANYA LETTER JA;Lo;0;L;;;;;N;;;;; 10484;OSMANYA LETTER XA;Lo;0;L;;;;;N;;;;; 10485;OSMANYA LETTER KHA;Lo;0;L;;;;;N;;;;; 10486;OSMANYA LETTER DEEL;Lo;0;L;;;;;N;;;;; 10487;OSMANYA LETTER RA;Lo;0;L;;;;;N;;;;; 10488;OSMANYA LETTER SA;Lo;0;L;;;;;N;;;;; 10489;OSMANYA LETTER SHIIN;Lo;0;L;;;;;N;;;;; 1048A;OSMANYA LETTER DHA;Lo;0;L;;;;;N;;;;; 1048B;OSMANYA LETTER CAYN;Lo;0;L;;;;;N;;;;; 1048C;OSMANYA LETTER GA;Lo;0;L;;;;;N;;;;; 1048D;OSMANYA LETTER FA;Lo;0;L;;;;;N;;;;; 1048E;OSMANYA LETTER QAAF;Lo;0;L;;;;;N;;;;; 1048F;OSMANYA LETTER KAAF;Lo;0;L;;;;;N;;;;; 10490;OSMANYA LETTER LAAN;Lo;0;L;;;;;N;;;;; 10491;OSMANYA LETTER MIIN;Lo;0;L;;;;;N;;;;; 10492;OSMANYA LETTER NUUN;Lo;0;L;;;;;N;;;;; 10493;OSMANYA LETTER WAW;Lo;0;L;;;;;N;;;;; 10494;OSMANYA LETTER HA;Lo;0;L;;;;;N;;;;; 10495;OSMANYA LETTER YA;Lo;0;L;;;;;N;;;;; 10496;OSMANYA LETTER A;Lo;0;L;;;;;N;;;;; 10497;OSMANYA LETTER E;Lo;0;L;;;;;N;;;;; 10498;OSMANYA LETTER I;Lo;0;L;;;;;N;;;;; 10499;OSMANYA LETTER O;Lo;0;L;;;;;N;;;;; 1049A;OSMANYA LETTER U;Lo;0;L;;;;;N;;;;; 1049B;OSMANYA LETTER AA;Lo;0;L;;;;;N;;;;; 1049C;OSMANYA LETTER EE;Lo;0;L;;;;;N;;;;; 1049D;OSMANYA LETTER OO;Lo;0;L;;;;;N;;;;; 104A0;OSMANYA DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 104A1;OSMANYA DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 104A2;OSMANYA DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 104A3;OSMANYA DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 104A4;OSMANYA DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 104A5;OSMANYA DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 104A6;OSMANYA DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 104A7;OSMANYA DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 104A8;OSMANYA DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 104A9;OSMANYA DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 104B0;OSAGE CAPITAL LETTER A;Lu;0;L;;;;;N;;;;104D8; 104B1;OSAGE CAPITAL LETTER AI;Lu;0;L;;;;;N;;;;104D9; 104B2;OSAGE CAPITAL LETTER AIN;Lu;0;L;;;;;N;;;;104DA; 104B3;OSAGE CAPITAL LETTER AH;Lu;0;L;;;;;N;;;;104DB; 104B4;OSAGE CAPITAL LETTER BRA;Lu;0;L;;;;;N;;;;104DC; 104B5;OSAGE CAPITAL LETTER CHA;Lu;0;L;;;;;N;;;;104DD; 104B6;OSAGE CAPITAL LETTER EHCHA;Lu;0;L;;;;;N;;;;104DE; 104B7;OSAGE CAPITAL LETTER E;Lu;0;L;;;;;N;;;;104DF; 104B8;OSAGE CAPITAL LETTER EIN;Lu;0;L;;;;;N;;;;104E0; 104B9;OSAGE CAPITAL LETTER HA;Lu;0;L;;;;;N;;;;104E1; 104BA;OSAGE CAPITAL LETTER HYA;Lu;0;L;;;;;N;;;;104E2; 104BB;OSAGE CAPITAL LETTER I;Lu;0;L;;;;;N;;;;104E3; 104BC;OSAGE CAPITAL LETTER KA;Lu;0;L;;;;;N;;;;104E4; 104BD;OSAGE CAPITAL LETTER EHKA;Lu;0;L;;;;;N;;;;104E5; 104BE;OSAGE CAPITAL LETTER KYA;Lu;0;L;;;;;N;;;;104E6; 104BF;OSAGE CAPITAL LETTER LA;Lu;0;L;;;;;N;;;;104E7; 104C0;OSAGE CAPITAL LETTER MA;Lu;0;L;;;;;N;;;;104E8; 104C1;OSAGE CAPITAL LETTER NA;Lu;0;L;;;;;N;;;;104E9; 104C2;OSAGE CAPITAL LETTER O;Lu;0;L;;;;;N;;;;104EA; 104C3;OSAGE CAPITAL LETTER OIN;Lu;0;L;;;;;N;;;;104EB; 104C4;OSAGE CAPITAL LETTER PA;Lu;0;L;;;;;N;;;;104EC; 104C5;OSAGE CAPITAL LETTER EHPA;Lu;0;L;;;;;N;;;;104ED; 104C6;OSAGE CAPITAL LETTER SA;Lu;0;L;;;;;N;;;;104EE; 104C7;OSAGE CAPITAL LETTER SHA;Lu;0;L;;;;;N;;;;104EF; 104C8;OSAGE CAPITAL LETTER TA;Lu;0;L;;;;;N;;;;104F0; 104C9;OSAGE CAPITAL LETTER EHTA;Lu;0;L;;;;;N;;;;104F1; 104CA;OSAGE CAPITAL LETTER TSA;Lu;0;L;;;;;N;;;;104F2; 104CB;OSAGE CAPITAL LETTER EHTSA;Lu;0;L;;;;;N;;;;104F3; 104CC;OSAGE CAPITAL LETTER TSHA;Lu;0;L;;;;;N;;;;104F4; 104CD;OSAGE CAPITAL LETTER DHA;Lu;0;L;;;;;N;;;;104F5; 104CE;OSAGE CAPITAL LETTER U;Lu;0;L;;;;;N;;;;104F6; 104CF;OSAGE CAPITAL LETTER WA;Lu;0;L;;;;;N;;;;104F7; 104D0;OSAGE CAPITAL LETTER KHA;Lu;0;L;;;;;N;;;;104F8; 104D1;OSAGE CAPITAL LETTER GHA;Lu;0;L;;;;;N;;;;104F9; 104D2;OSAGE CAPITAL LETTER ZA;Lu;0;L;;;;;N;;;;104FA; 104D3;OSAGE CAPITAL LETTER ZHA;Lu;0;L;;;;;N;;;;104FB; 104D8;OSAGE SMALL LETTER A;Ll;0;L;;;;;N;;;104B0;;104B0 104D9;OSAGE SMALL LETTER AI;Ll;0;L;;;;;N;;;104B1;;104B1 104DA;OSAGE SMALL LETTER AIN;Ll;0;L;;;;;N;;;104B2;;104B2 104DB;OSAGE SMALL LETTER AH;Ll;0;L;;;;;N;;;104B3;;104B3 104DC;OSAGE SMALL LETTER BRA;Ll;0;L;;;;;N;;;104B4;;104B4 104DD;OSAGE SMALL LETTER CHA;Ll;0;L;;;;;N;;;104B5;;104B5 104DE;OSAGE SMALL LETTER EHCHA;Ll;0;L;;;;;N;;;104B6;;104B6 104DF;OSAGE SMALL LETTER E;Ll;0;L;;;;;N;;;104B7;;104B7 104E0;OSAGE SMALL LETTER EIN;Ll;0;L;;;;;N;;;104B8;;104B8 104E1;OSAGE SMALL LETTER HA;Ll;0;L;;;;;N;;;104B9;;104B9 104E2;OSAGE SMALL LETTER HYA;Ll;0;L;;;;;N;;;104BA;;104BA 104E3;OSAGE SMALL LETTER I;Ll;0;L;;;;;N;;;104BB;;104BB 104E4;OSAGE SMALL LETTER KA;Ll;0;L;;;;;N;;;104BC;;104BC 104E5;OSAGE SMALL LETTER EHKA;Ll;0;L;;;;;N;;;104BD;;104BD 104E6;OSAGE SMALL LETTER KYA;Ll;0;L;;;;;N;;;104BE;;104BE 104E7;OSAGE SMALL LETTER LA;Ll;0;L;;;;;N;;;104BF;;104BF 104E8;OSAGE SMALL LETTER MA;Ll;0;L;;;;;N;;;104C0;;104C0 104E9;OSAGE SMALL LETTER NA;Ll;0;L;;;;;N;;;104C1;;104C1 104EA;OSAGE SMALL LETTER O;Ll;0;L;;;;;N;;;104C2;;104C2 104EB;OSAGE SMALL LETTER OIN;Ll;0;L;;;;;N;;;104C3;;104C3 104EC;OSAGE SMALL LETTER PA;Ll;0;L;;;;;N;;;104C4;;104C4 104ED;OSAGE SMALL LETTER EHPA;Ll;0;L;;;;;N;;;104C5;;104C5 104EE;OSAGE SMALL LETTER SA;Ll;0;L;;;;;N;;;104C6;;104C6 104EF;OSAGE SMALL LETTER SHA;Ll;0;L;;;;;N;;;104C7;;104C7 104F0;OSAGE SMALL LETTER TA;Ll;0;L;;;;;N;;;104C8;;104C8 104F1;OSAGE SMALL LETTER EHTA;Ll;0;L;;;;;N;;;104C9;;104C9 104F2;OSAGE SMALL LETTER TSA;Ll;0;L;;;;;N;;;104CA;;104CA 104F3;OSAGE SMALL LETTER EHTSA;Ll;0;L;;;;;N;;;104CB;;104CB 104F4;OSAGE SMALL LETTER TSHA;Ll;0;L;;;;;N;;;104CC;;104CC 104F5;OSAGE SMALL LETTER DHA;Ll;0;L;;;;;N;;;104CD;;104CD 104F6;OSAGE SMALL LETTER U;Ll;0;L;;;;;N;;;104CE;;104CE 104F7;OSAGE SMALL LETTER WA;Ll;0;L;;;;;N;;;104CF;;104CF 104F8;OSAGE SMALL LETTER KHA;Ll;0;L;;;;;N;;;104D0;;104D0 104F9;OSAGE SMALL LETTER GHA;Ll;0;L;;;;;N;;;104D1;;104D1 104FA;OSAGE SMALL LETTER ZA;Ll;0;L;;;;;N;;;104D2;;104D2 104FB;OSAGE SMALL LETTER ZHA;Ll;0;L;;;;;N;;;104D3;;104D3 10500;ELBASAN LETTER A;Lo;0;L;;;;;N;;;;; 10501;ELBASAN LETTER BE;Lo;0;L;;;;;N;;;;; 10502;ELBASAN LETTER CE;Lo;0;L;;;;;N;;;;; 10503;ELBASAN LETTER CHE;Lo;0;L;;;;;N;;;;; 10504;ELBASAN LETTER DE;Lo;0;L;;;;;N;;;;; 10505;ELBASAN LETTER NDE;Lo;0;L;;;;;N;;;;; 10506;ELBASAN LETTER DHE;Lo;0;L;;;;;N;;;;; 10507;ELBASAN LETTER EI;Lo;0;L;;;;;N;;;;; 10508;ELBASAN LETTER E;Lo;0;L;;;;;N;;;;; 10509;ELBASAN LETTER FE;Lo;0;L;;;;;N;;;;; 1050A;ELBASAN LETTER GE;Lo;0;L;;;;;N;;;;; 1050B;ELBASAN LETTER GJE;Lo;0;L;;;;;N;;;;; 1050C;ELBASAN LETTER HE;Lo;0;L;;;;;N;;;;; 1050D;ELBASAN LETTER I;Lo;0;L;;;;;N;;;;; 1050E;ELBASAN LETTER JE;Lo;0;L;;;;;N;;;;; 1050F;ELBASAN LETTER KE;Lo;0;L;;;;;N;;;;; 10510;ELBASAN LETTER LE;Lo;0;L;;;;;N;;;;; 10511;ELBASAN LETTER LLE;Lo;0;L;;;;;N;;;;; 10512;ELBASAN LETTER ME;Lo;0;L;;;;;N;;;;; 10513;ELBASAN LETTER NE;Lo;0;L;;;;;N;;;;; 10514;ELBASAN LETTER NA;Lo;0;L;;;;;N;;;;; 10515;ELBASAN LETTER NJE;Lo;0;L;;;;;N;;;;; 10516;ELBASAN LETTER O;Lo;0;L;;;;;N;;;;; 10517;ELBASAN LETTER PE;Lo;0;L;;;;;N;;;;; 10518;ELBASAN LETTER QE;Lo;0;L;;;;;N;;;;; 10519;ELBASAN LETTER RE;Lo;0;L;;;;;N;;;;; 1051A;ELBASAN LETTER RRE;Lo;0;L;;;;;N;;;;; 1051B;ELBASAN LETTER SE;Lo;0;L;;;;;N;;;;; 1051C;ELBASAN LETTER SHE;Lo;0;L;;;;;N;;;;; 1051D;ELBASAN LETTER TE;Lo;0;L;;;;;N;;;;; 1051E;ELBASAN LETTER THE;Lo;0;L;;;;;N;;;;; 1051F;ELBASAN LETTER U;Lo;0;L;;;;;N;;;;; 10520;ELBASAN LETTER VE;Lo;0;L;;;;;N;;;;; 10521;ELBASAN LETTER XE;Lo;0;L;;;;;N;;;;; 10522;ELBASAN LETTER Y;Lo;0;L;;;;;N;;;;; 10523;ELBASAN LETTER ZE;Lo;0;L;;;;;N;;;;; 10524;ELBASAN LETTER ZHE;Lo;0;L;;;;;N;;;;; 10525;ELBASAN LETTER GHE;Lo;0;L;;;;;N;;;;; 10526;ELBASAN LETTER GHAMMA;Lo;0;L;;;;;N;;;;; 10527;ELBASAN LETTER KHE;Lo;0;L;;;;;N;;;;; 10530;CAUCASIAN ALBANIAN LETTER ALT;Lo;0;L;;;;;N;;;;; 10531;CAUCASIAN ALBANIAN LETTER BET;Lo;0;L;;;;;N;;;;; 10532;CAUCASIAN ALBANIAN LETTER GIM;Lo;0;L;;;;;N;;;;; 10533;CAUCASIAN ALBANIAN LETTER DAT;Lo;0;L;;;;;N;;;;; 10534;CAUCASIAN ALBANIAN LETTER EB;Lo;0;L;;;;;N;;;;; 10535;CAUCASIAN ALBANIAN LETTER ZARL;Lo;0;L;;;;;N;;;;; 10536;CAUCASIAN ALBANIAN LETTER EYN;Lo;0;L;;;;;N;;;;; 10537;CAUCASIAN ALBANIAN LETTER ZHIL;Lo;0;L;;;;;N;;;;; 10538;CAUCASIAN ALBANIAN LETTER TAS;Lo;0;L;;;;;N;;;;; 10539;CAUCASIAN ALBANIAN LETTER CHA;Lo;0;L;;;;;N;;;;; 1053A;CAUCASIAN ALBANIAN LETTER YOWD;Lo;0;L;;;;;N;;;;; 1053B;CAUCASIAN ALBANIAN LETTER ZHA;Lo;0;L;;;;;N;;;;; 1053C;CAUCASIAN ALBANIAN LETTER IRB;Lo;0;L;;;;;N;;;;; 1053D;CAUCASIAN ALBANIAN LETTER SHA;Lo;0;L;;;;;N;;;;; 1053E;CAUCASIAN ALBANIAN LETTER LAN;Lo;0;L;;;;;N;;;;; 1053F;CAUCASIAN ALBANIAN LETTER INYA;Lo;0;L;;;;;N;;;;; 10540;CAUCASIAN ALBANIAN LETTER XEYN;Lo;0;L;;;;;N;;;;; 10541;CAUCASIAN ALBANIAN LETTER DYAN;Lo;0;L;;;;;N;;;;; 10542;CAUCASIAN ALBANIAN LETTER CAR;Lo;0;L;;;;;N;;;;; 10543;CAUCASIAN ALBANIAN LETTER JHOX;Lo;0;L;;;;;N;;;;; 10544;CAUCASIAN ALBANIAN LETTER KAR;Lo;0;L;;;;;N;;;;; 10545;CAUCASIAN ALBANIAN LETTER LYIT;Lo;0;L;;;;;N;;;;; 10546;CAUCASIAN ALBANIAN LETTER HEYT;Lo;0;L;;;;;N;;;;; 10547;CAUCASIAN ALBANIAN LETTER QAY;Lo;0;L;;;;;N;;;;; 10548;CAUCASIAN ALBANIAN LETTER AOR;Lo;0;L;;;;;N;;;;; 10549;CAUCASIAN ALBANIAN LETTER CHOY;Lo;0;L;;;;;N;;;;; 1054A;CAUCASIAN ALBANIAN LETTER CHI;Lo;0;L;;;;;N;;;;; 1054B;CAUCASIAN ALBANIAN LETTER CYAY;Lo;0;L;;;;;N;;;;; 1054C;CAUCASIAN ALBANIAN LETTER MAQ;Lo;0;L;;;;;N;;;;; 1054D;CAUCASIAN ALBANIAN LETTER QAR;Lo;0;L;;;;;N;;;;; 1054E;CAUCASIAN ALBANIAN LETTER NOWC;Lo;0;L;;;;;N;;;;; 1054F;CAUCASIAN ALBANIAN LETTER DZYAY;Lo;0;L;;;;;N;;;;; 10550;CAUCASIAN ALBANIAN LETTER SHAK;Lo;0;L;;;;;N;;;;; 10551;CAUCASIAN ALBANIAN LETTER JAYN;Lo;0;L;;;;;N;;;;; 10552;CAUCASIAN ALBANIAN LETTER ON;Lo;0;L;;;;;N;;;;; 10553;CAUCASIAN ALBANIAN LETTER TYAY;Lo;0;L;;;;;N;;;;; 10554;CAUCASIAN ALBANIAN LETTER FAM;Lo;0;L;;;;;N;;;;; 10555;CAUCASIAN ALBANIAN LETTER DZAY;Lo;0;L;;;;;N;;;;; 10556;CAUCASIAN ALBANIAN LETTER CHAT;Lo;0;L;;;;;N;;;;; 10557;CAUCASIAN ALBANIAN LETTER PEN;Lo;0;L;;;;;N;;;;; 10558;CAUCASIAN ALBANIAN LETTER GHEYS;Lo;0;L;;;;;N;;;;; 10559;CAUCASIAN ALBANIAN LETTER RAT;Lo;0;L;;;;;N;;;;; 1055A;CAUCASIAN ALBANIAN LETTER SEYK;Lo;0;L;;;;;N;;;;; 1055B;CAUCASIAN ALBANIAN LETTER VEYZ;Lo;0;L;;;;;N;;;;; 1055C;CAUCASIAN ALBANIAN LETTER TIWR;Lo;0;L;;;;;N;;;;; 1055D;CAUCASIAN ALBANIAN LETTER SHOY;Lo;0;L;;;;;N;;;;; 1055E;CAUCASIAN ALBANIAN LETTER IWN;Lo;0;L;;;;;N;;;;; 1055F;CAUCASIAN ALBANIAN LETTER CYAW;Lo;0;L;;;;;N;;;;; 10560;CAUCASIAN ALBANIAN LETTER CAYN;Lo;0;L;;;;;N;;;;; 10561;CAUCASIAN ALBANIAN LETTER YAYD;Lo;0;L;;;;;N;;;;; 10562;CAUCASIAN ALBANIAN LETTER PIWR;Lo;0;L;;;;;N;;;;; 10563;CAUCASIAN ALBANIAN LETTER KIW;Lo;0;L;;;;;N;;;;; 1056F;CAUCASIAN ALBANIAN CITATION MARK;Po;0;L;;;;;N;;;;; 10600;LINEAR A SIGN AB001;Lo;0;L;;;;;N;;;;; 10601;LINEAR A SIGN AB002;Lo;0;L;;;;;N;;;;; 10602;LINEAR A SIGN AB003;Lo;0;L;;;;;N;;;;; 10603;LINEAR A SIGN AB004;Lo;0;L;;;;;N;;;;; 10604;LINEAR A SIGN AB005;Lo;0;L;;;;;N;;;;; 10605;LINEAR A SIGN AB006;Lo;0;L;;;;;N;;;;; 10606;LINEAR A SIGN AB007;Lo;0;L;;;;;N;;;;; 10607;LINEAR A SIGN AB008;Lo;0;L;;;;;N;;;;; 10608;LINEAR A SIGN AB009;Lo;0;L;;;;;N;;;;; 10609;LINEAR A SIGN AB010;Lo;0;L;;;;;N;;;;; 1060A;LINEAR A SIGN AB011;Lo;0;L;;;;;N;;;;; 1060B;LINEAR A SIGN AB013;Lo;0;L;;;;;N;;;;; 1060C;LINEAR A SIGN AB016;Lo;0;L;;;;;N;;;;; 1060D;LINEAR A SIGN AB017;Lo;0;L;;;;;N;;;;; 1060E;LINEAR A SIGN AB020;Lo;0;L;;;;;N;;;;; 1060F;LINEAR A SIGN AB021;Lo;0;L;;;;;N;;;;; 10610;LINEAR A SIGN AB021F;Lo;0;L;;;;;N;;;;; 10611;LINEAR A SIGN AB021M;Lo;0;L;;;;;N;;;;; 10612;LINEAR A SIGN AB022;Lo;0;L;;;;;N;;;;; 10613;LINEAR A SIGN AB022F;Lo;0;L;;;;;N;;;;; 10614;LINEAR A SIGN AB022M;Lo;0;L;;;;;N;;;;; 10615;LINEAR A SIGN AB023;Lo;0;L;;;;;N;;;;; 10616;LINEAR A SIGN AB023M;Lo;0;L;;;;;N;;;;; 10617;LINEAR A SIGN AB024;Lo;0;L;;;;;N;;;;; 10618;LINEAR A SIGN AB026;Lo;0;L;;;;;N;;;;; 10619;LINEAR A SIGN AB027;Lo;0;L;;;;;N;;;;; 1061A;LINEAR A SIGN AB028;Lo;0;L;;;;;N;;;;; 1061B;LINEAR A SIGN A028B;Lo;0;L;;;;;N;;;;; 1061C;LINEAR A SIGN AB029;Lo;0;L;;;;;N;;;;; 1061D;LINEAR A SIGN AB030;Lo;0;L;;;;;N;;;;; 1061E;LINEAR A SIGN AB031;Lo;0;L;;;;;N;;;;; 1061F;LINEAR A SIGN AB034;Lo;0;L;;;;;N;;;;; 10620;LINEAR A SIGN AB037;Lo;0;L;;;;;N;;;;; 10621;LINEAR A SIGN AB038;Lo;0;L;;;;;N;;;;; 10622;LINEAR A SIGN AB039;Lo;0;L;;;;;N;;;;; 10623;LINEAR A SIGN AB040;Lo;0;L;;;;;N;;;;; 10624;LINEAR A SIGN AB041;Lo;0;L;;;;;N;;;;; 10625;LINEAR A SIGN AB044;Lo;0;L;;;;;N;;;;; 10626;LINEAR A SIGN AB045;Lo;0;L;;;;;N;;;;; 10627;LINEAR A SIGN AB046;Lo;0;L;;;;;N;;;;; 10628;LINEAR A SIGN AB047;Lo;0;L;;;;;N;;;;; 10629;LINEAR A SIGN AB048;Lo;0;L;;;;;N;;;;; 1062A;LINEAR A SIGN AB049;Lo;0;L;;;;;N;;;;; 1062B;LINEAR A SIGN AB050;Lo;0;L;;;;;N;;;;; 1062C;LINEAR A SIGN AB051;Lo;0;L;;;;;N;;;;; 1062D;LINEAR A SIGN AB053;Lo;0;L;;;;;N;;;;; 1062E;LINEAR A SIGN AB054;Lo;0;L;;;;;N;;;;; 1062F;LINEAR A SIGN AB055;Lo;0;L;;;;;N;;;;; 10630;LINEAR A SIGN AB056;Lo;0;L;;;;;N;;;;; 10631;LINEAR A SIGN AB057;Lo;0;L;;;;;N;;;;; 10632;LINEAR A SIGN AB058;Lo;0;L;;;;;N;;;;; 10633;LINEAR A SIGN AB059;Lo;0;L;;;;;N;;;;; 10634;LINEAR A SIGN AB060;Lo;0;L;;;;;N;;;;; 10635;LINEAR A SIGN AB061;Lo;0;L;;;;;N;;;;; 10636;LINEAR A SIGN AB065;Lo;0;L;;;;;N;;;;; 10637;LINEAR A SIGN AB066;Lo;0;L;;;;;N;;;;; 10638;LINEAR A SIGN AB067;Lo;0;L;;;;;N;;;;; 10639;LINEAR A SIGN AB069;Lo;0;L;;;;;N;;;;; 1063A;LINEAR A SIGN AB070;Lo;0;L;;;;;N;;;;; 1063B;LINEAR A SIGN AB073;Lo;0;L;;;;;N;;;;; 1063C;LINEAR A SIGN AB074;Lo;0;L;;;;;N;;;;; 1063D;LINEAR A SIGN AB076;Lo;0;L;;;;;N;;;;; 1063E;LINEAR A SIGN AB077;Lo;0;L;;;;;N;;;;; 1063F;LINEAR A SIGN AB078;Lo;0;L;;;;;N;;;;; 10640;LINEAR A SIGN AB079;Lo;0;L;;;;;N;;;;; 10641;LINEAR A SIGN AB080;Lo;0;L;;;;;N;;;;; 10642;LINEAR A SIGN AB081;Lo;0;L;;;;;N;;;;; 10643;LINEAR A SIGN AB082;Lo;0;L;;;;;N;;;;; 10644;LINEAR A SIGN AB085;Lo;0;L;;;;;N;;;;; 10645;LINEAR A SIGN AB086;Lo;0;L;;;;;N;;;;; 10646;LINEAR A SIGN AB087;Lo;0;L;;;;;N;;;;; 10647;LINEAR A SIGN A100-102;Lo;0;L;;;;;N;;;;; 10648;LINEAR A SIGN AB118;Lo;0;L;;;;;N;;;;; 10649;LINEAR A SIGN AB120;Lo;0;L;;;;;N;;;;; 1064A;LINEAR A SIGN A120B;Lo;0;L;;;;;N;;;;; 1064B;LINEAR A SIGN AB122;Lo;0;L;;;;;N;;;;; 1064C;LINEAR A SIGN AB123;Lo;0;L;;;;;N;;;;; 1064D;LINEAR A SIGN AB131A;Lo;0;L;;;;;N;;;;; 1064E;LINEAR A SIGN AB131B;Lo;0;L;;;;;N;;;;; 1064F;LINEAR A SIGN A131C;Lo;0;L;;;;;N;;;;; 10650;LINEAR A SIGN AB164;Lo;0;L;;;;;N;;;;; 10651;LINEAR A SIGN AB171;Lo;0;L;;;;;N;;;;; 10652;LINEAR A SIGN AB180;Lo;0;L;;;;;N;;;;; 10653;LINEAR A SIGN AB188;Lo;0;L;;;;;N;;;;; 10654;LINEAR A SIGN AB191;Lo;0;L;;;;;N;;;;; 10655;LINEAR A SIGN A301;Lo;0;L;;;;;N;;;;; 10656;LINEAR A SIGN A302;Lo;0;L;;;;;N;;;;; 10657;LINEAR A SIGN A303;Lo;0;L;;;;;N;;;;; 10658;LINEAR A SIGN A304;Lo;0;L;;;;;N;;;;; 10659;LINEAR A SIGN A305;Lo;0;L;;;;;N;;;;; 1065A;LINEAR A SIGN A306;Lo;0;L;;;;;N;;;;; 1065B;LINEAR A SIGN A307;Lo;0;L;;;;;N;;;;; 1065C;LINEAR A SIGN A308;Lo;0;L;;;;;N;;;;; 1065D;LINEAR A SIGN A309A;Lo;0;L;;;;;N;;;;; 1065E;LINEAR A SIGN A309B;Lo;0;L;;;;;N;;;;; 1065F;LINEAR A SIGN A309C;Lo;0;L;;;;;N;;;;; 10660;LINEAR A SIGN A310;Lo;0;L;;;;;N;;;;; 10661;LINEAR A SIGN A311;Lo;0;L;;;;;N;;;;; 10662;LINEAR A SIGN A312;Lo;0;L;;;;;N;;;;; 10663;LINEAR A SIGN A313A;Lo;0;L;;;;;N;;;;; 10664;LINEAR A SIGN A313B;Lo;0;L;;;;;N;;;;; 10665;LINEAR A SIGN A313C;Lo;0;L;;;;;N;;;;; 10666;LINEAR A SIGN A314;Lo;0;L;;;;;N;;;;; 10667;LINEAR A SIGN A315;Lo;0;L;;;;;N;;;;; 10668;LINEAR A SIGN A316;Lo;0;L;;;;;N;;;;; 10669;LINEAR A SIGN A317;Lo;0;L;;;;;N;;;;; 1066A;LINEAR A SIGN A318;Lo;0;L;;;;;N;;;;; 1066B;LINEAR A SIGN A319;Lo;0;L;;;;;N;;;;; 1066C;LINEAR A SIGN A320;Lo;0;L;;;;;N;;;;; 1066D;LINEAR A SIGN A321;Lo;0;L;;;;;N;;;;; 1066E;LINEAR A SIGN A322;Lo;0;L;;;;;N;;;;; 1066F;LINEAR A SIGN A323;Lo;0;L;;;;;N;;;;; 10670;LINEAR A SIGN A324;Lo;0;L;;;;;N;;;;; 10671;LINEAR A SIGN A325;Lo;0;L;;;;;N;;;;; 10672;LINEAR A SIGN A326;Lo;0;L;;;;;N;;;;; 10673;LINEAR A SIGN A327;Lo;0;L;;;;;N;;;;; 10674;LINEAR A SIGN A328;Lo;0;L;;;;;N;;;;; 10675;LINEAR A SIGN A329;Lo;0;L;;;;;N;;;;; 10676;LINEAR A SIGN A330;Lo;0;L;;;;;N;;;;; 10677;LINEAR A SIGN A331;Lo;0;L;;;;;N;;;;; 10678;LINEAR A SIGN A332;Lo;0;L;;;;;N;;;;; 10679;LINEAR A SIGN A333;Lo;0;L;;;;;N;;;;; 1067A;LINEAR A SIGN A334;Lo;0;L;;;;;N;;;;; 1067B;LINEAR A SIGN A335;Lo;0;L;;;;;N;;;;; 1067C;LINEAR A SIGN A336;Lo;0;L;;;;;N;;;;; 1067D;LINEAR A SIGN A337;Lo;0;L;;;;;N;;;;; 1067E;LINEAR A SIGN A338;Lo;0;L;;;;;N;;;;; 1067F;LINEAR A SIGN A339;Lo;0;L;;;;;N;;;;; 10680;LINEAR A SIGN A340;Lo;0;L;;;;;N;;;;; 10681;LINEAR A SIGN A341;Lo;0;L;;;;;N;;;;; 10682;LINEAR A SIGN A342;Lo;0;L;;;;;N;;;;; 10683;LINEAR A SIGN A343;Lo;0;L;;;;;N;;;;; 10684;LINEAR A SIGN A344;Lo;0;L;;;;;N;;;;; 10685;LINEAR A SIGN A345;Lo;0;L;;;;;N;;;;; 10686;LINEAR A SIGN A346;Lo;0;L;;;;;N;;;;; 10687;LINEAR A SIGN A347;Lo;0;L;;;;;N;;;;; 10688;LINEAR A SIGN A348;Lo;0;L;;;;;N;;;;; 10689;LINEAR A SIGN A349;Lo;0;L;;;;;N;;;;; 1068A;LINEAR A SIGN A350;Lo;0;L;;;;;N;;;;; 1068B;LINEAR A SIGN A351;Lo;0;L;;;;;N;;;;; 1068C;LINEAR A SIGN A352;Lo;0;L;;;;;N;;;;; 1068D;LINEAR A SIGN A353;Lo;0;L;;;;;N;;;;; 1068E;LINEAR A SIGN A354;Lo;0;L;;;;;N;;;;; 1068F;LINEAR A SIGN A355;Lo;0;L;;;;;N;;;;; 10690;LINEAR A SIGN A356;Lo;0;L;;;;;N;;;;; 10691;LINEAR A SIGN A357;Lo;0;L;;;;;N;;;;; 10692;LINEAR A SIGN A358;Lo;0;L;;;;;N;;;;; 10693;LINEAR A SIGN A359;Lo;0;L;;;;;N;;;;; 10694;LINEAR A SIGN A360;Lo;0;L;;;;;N;;;;; 10695;LINEAR A SIGN A361;Lo;0;L;;;;;N;;;;; 10696;LINEAR A SIGN A362;Lo;0;L;;;;;N;;;;; 10697;LINEAR A SIGN A363;Lo;0;L;;;;;N;;;;; 10698;LINEAR A SIGN A364;Lo;0;L;;;;;N;;;;; 10699;LINEAR A SIGN A365;Lo;0;L;;;;;N;;;;; 1069A;LINEAR A SIGN A366;Lo;0;L;;;;;N;;;;; 1069B;LINEAR A SIGN A367;Lo;0;L;;;;;N;;;;; 1069C;LINEAR A SIGN A368;Lo;0;L;;;;;N;;;;; 1069D;LINEAR A SIGN A369;Lo;0;L;;;;;N;;;;; 1069E;LINEAR A SIGN A370;Lo;0;L;;;;;N;;;;; 1069F;LINEAR A SIGN A371;Lo;0;L;;;;;N;;;;; 106A0;LINEAR A SIGN A400-VAS;Lo;0;L;;;;;N;;;;; 106A1;LINEAR A SIGN A401-VAS;Lo;0;L;;;;;N;;;;; 106A2;LINEAR A SIGN A402-VAS;Lo;0;L;;;;;N;;;;; 106A3;LINEAR A SIGN A403-VAS;Lo;0;L;;;;;N;;;;; 106A4;LINEAR A SIGN A404-VAS;Lo;0;L;;;;;N;;;;; 106A5;LINEAR A SIGN A405-VAS;Lo;0;L;;;;;N;;;;; 106A6;LINEAR A SIGN A406-VAS;Lo;0;L;;;;;N;;;;; 106A7;LINEAR A SIGN A407-VAS;Lo;0;L;;;;;N;;;;; 106A8;LINEAR A SIGN A408-VAS;Lo;0;L;;;;;N;;;;; 106A9;LINEAR A SIGN A409-VAS;Lo;0;L;;;;;N;;;;; 106AA;LINEAR A SIGN A410-VAS;Lo;0;L;;;;;N;;;;; 106AB;LINEAR A SIGN A411-VAS;Lo;0;L;;;;;N;;;;; 106AC;LINEAR A SIGN A412-VAS;Lo;0;L;;;;;N;;;;; 106AD;LINEAR A SIGN A413-VAS;Lo;0;L;;;;;N;;;;; 106AE;LINEAR A SIGN A414-VAS;Lo;0;L;;;;;N;;;;; 106AF;LINEAR A SIGN A415-VAS;Lo;0;L;;;;;N;;;;; 106B0;LINEAR A SIGN A416-VAS;Lo;0;L;;;;;N;;;;; 106B1;LINEAR A SIGN A417-VAS;Lo;0;L;;;;;N;;;;; 106B2;LINEAR A SIGN A418-VAS;Lo;0;L;;;;;N;;;;; 106B3;LINEAR A SIGN A501;Lo;0;L;;;;;N;;;;; 106B4;LINEAR A SIGN A502;Lo;0;L;;;;;N;;;;; 106B5;LINEAR A SIGN A503;Lo;0;L;;;;;N;;;;; 106B6;LINEAR A SIGN A504;Lo;0;L;;;;;N;;;;; 106B7;LINEAR A SIGN A505;Lo;0;L;;;;;N;;;;; 106B8;LINEAR A SIGN A506;Lo;0;L;;;;;N;;;;; 106B9;LINEAR A SIGN A508;Lo;0;L;;;;;N;;;;; 106BA;LINEAR A SIGN A509;Lo;0;L;;;;;N;;;;; 106BB;LINEAR A SIGN A510;Lo;0;L;;;;;N;;;;; 106BC;LINEAR A SIGN A511;Lo;0;L;;;;;N;;;;; 106BD;LINEAR A SIGN A512;Lo;0;L;;;;;N;;;;; 106BE;LINEAR A SIGN A513;Lo;0;L;;;;;N;;;;; 106BF;LINEAR A SIGN A515;Lo;0;L;;;;;N;;;;; 106C0;LINEAR A SIGN A516;Lo;0;L;;;;;N;;;;; 106C1;LINEAR A SIGN A520;Lo;0;L;;;;;N;;;;; 106C2;LINEAR A SIGN A521;Lo;0;L;;;;;N;;;;; 106C3;LINEAR A SIGN A523;Lo;0;L;;;;;N;;;;; 106C4;LINEAR A SIGN A524;Lo;0;L;;;;;N;;;;; 106C5;LINEAR A SIGN A525;Lo;0;L;;;;;N;;;;; 106C6;LINEAR A SIGN A526;Lo;0;L;;;;;N;;;;; 106C7;LINEAR A SIGN A527;Lo;0;L;;;;;N;;;;; 106C8;LINEAR A SIGN A528;Lo;0;L;;;;;N;;;;; 106C9;LINEAR A SIGN A529;Lo;0;L;;;;;N;;;;; 106CA;LINEAR A SIGN A530;Lo;0;L;;;;;N;;;;; 106CB;LINEAR A SIGN A531;Lo;0;L;;;;;N;;;;; 106CC;LINEAR A SIGN A532;Lo;0;L;;;;;N;;;;; 106CD;LINEAR A SIGN A534;Lo;0;L;;;;;N;;;;; 106CE;LINEAR A SIGN A535;Lo;0;L;;;;;N;;;;; 106CF;LINEAR A SIGN A536;Lo;0;L;;;;;N;;;;; 106D0;LINEAR A SIGN A537;Lo;0;L;;;;;N;;;;; 106D1;LINEAR A SIGN A538;Lo;0;L;;;;;N;;;;; 106D2;LINEAR A SIGN A539;Lo;0;L;;;;;N;;;;; 106D3;LINEAR A SIGN A540;Lo;0;L;;;;;N;;;;; 106D4;LINEAR A SIGN A541;Lo;0;L;;;;;N;;;;; 106D5;LINEAR A SIGN A542;Lo;0;L;;;;;N;;;;; 106D6;LINEAR A SIGN A545;Lo;0;L;;;;;N;;;;; 106D7;LINEAR A SIGN A547;Lo;0;L;;;;;N;;;;; 106D8;LINEAR A SIGN A548;Lo;0;L;;;;;N;;;;; 106D9;LINEAR A SIGN A549;Lo;0;L;;;;;N;;;;; 106DA;LINEAR A SIGN A550;Lo;0;L;;;;;N;;;;; 106DB;LINEAR A SIGN A551;Lo;0;L;;;;;N;;;;; 106DC;LINEAR A SIGN A552;Lo;0;L;;;;;N;;;;; 106DD;LINEAR A SIGN A553;Lo;0;L;;;;;N;;;;; 106DE;LINEAR A SIGN A554;Lo;0;L;;;;;N;;;;; 106DF;LINEAR A SIGN A555;Lo;0;L;;;;;N;;;;; 106E0;LINEAR A SIGN A556;Lo;0;L;;;;;N;;;;; 106E1;LINEAR A SIGN A557;Lo;0;L;;;;;N;;;;; 106E2;LINEAR A SIGN A559;Lo;0;L;;;;;N;;;;; 106E3;LINEAR A SIGN A563;Lo;0;L;;;;;N;;;;; 106E4;LINEAR A SIGN A564;Lo;0;L;;;;;N;;;;; 106E5;LINEAR A SIGN A565;Lo;0;L;;;;;N;;;;; 106E6;LINEAR A SIGN A566;Lo;0;L;;;;;N;;;;; 106E7;LINEAR A SIGN A568;Lo;0;L;;;;;N;;;;; 106E8;LINEAR A SIGN A569;Lo;0;L;;;;;N;;;;; 106E9;LINEAR A SIGN A570;Lo;0;L;;;;;N;;;;; 106EA;LINEAR A SIGN A571;Lo;0;L;;;;;N;;;;; 106EB;LINEAR A SIGN A572;Lo;0;L;;;;;N;;;;; 106EC;LINEAR A SIGN A573;Lo;0;L;;;;;N;;;;; 106ED;LINEAR A SIGN A574;Lo;0;L;;;;;N;;;;; 106EE;LINEAR A SIGN A575;Lo;0;L;;;;;N;;;;; 106EF;LINEAR A SIGN A576;Lo;0;L;;;;;N;;;;; 106F0;LINEAR A SIGN A577;Lo;0;L;;;;;N;;;;; 106F1;LINEAR A SIGN A578;Lo;0;L;;;;;N;;;;; 106F2;LINEAR A SIGN A579;Lo;0;L;;;;;N;;;;; 106F3;LINEAR A SIGN A580;Lo;0;L;;;;;N;;;;; 106F4;LINEAR A SIGN A581;Lo;0;L;;;;;N;;;;; 106F5;LINEAR A SIGN A582;Lo;0;L;;;;;N;;;;; 106F6;LINEAR A SIGN A583;Lo;0;L;;;;;N;;;;; 106F7;LINEAR A SIGN A584;Lo;0;L;;;;;N;;;;; 106F8;LINEAR A SIGN A585;Lo;0;L;;;;;N;;;;; 106F9;LINEAR A SIGN A586;Lo;0;L;;;;;N;;;;; 106FA;LINEAR A SIGN A587;Lo;0;L;;;;;N;;;;; 106FB;LINEAR A SIGN A588;Lo;0;L;;;;;N;;;;; 106FC;LINEAR A SIGN A589;Lo;0;L;;;;;N;;;;; 106FD;LINEAR A SIGN A591;Lo;0;L;;;;;N;;;;; 106FE;LINEAR A SIGN A592;Lo;0;L;;;;;N;;;;; 106FF;LINEAR A SIGN A594;Lo;0;L;;;;;N;;;;; 10700;LINEAR A SIGN A595;Lo;0;L;;;;;N;;;;; 10701;LINEAR A SIGN A596;Lo;0;L;;;;;N;;;;; 10702;LINEAR A SIGN A598;Lo;0;L;;;;;N;;;;; 10703;LINEAR A SIGN A600;Lo;0;L;;;;;N;;;;; 10704;LINEAR A SIGN A601;Lo;0;L;;;;;N;;;;; 10705;LINEAR A SIGN A602;Lo;0;L;;;;;N;;;;; 10706;LINEAR A SIGN A603;Lo;0;L;;;;;N;;;;; 10707;LINEAR A SIGN A604;Lo;0;L;;;;;N;;;;; 10708;LINEAR A SIGN A606;Lo;0;L;;;;;N;;;;; 10709;LINEAR A SIGN A608;Lo;0;L;;;;;N;;;;; 1070A;LINEAR A SIGN A609;Lo;0;L;;;;;N;;;;; 1070B;LINEAR A SIGN A610;Lo;0;L;;;;;N;;;;; 1070C;LINEAR A SIGN A611;Lo;0;L;;;;;N;;;;; 1070D;LINEAR A SIGN A612;Lo;0;L;;;;;N;;;;; 1070E;LINEAR A SIGN A613;Lo;0;L;;;;;N;;;;; 1070F;LINEAR A SIGN A614;Lo;0;L;;;;;N;;;;; 10710;LINEAR A SIGN A615;Lo;0;L;;;;;N;;;;; 10711;LINEAR A SIGN A616;Lo;0;L;;;;;N;;;;; 10712;LINEAR A SIGN A617;Lo;0;L;;;;;N;;;;; 10713;LINEAR A SIGN A618;Lo;0;L;;;;;N;;;;; 10714;LINEAR A SIGN A619;Lo;0;L;;;;;N;;;;; 10715;LINEAR A SIGN A620;Lo;0;L;;;;;N;;;;; 10716;LINEAR A SIGN A621;Lo;0;L;;;;;N;;;;; 10717;LINEAR A SIGN A622;Lo;0;L;;;;;N;;;;; 10718;LINEAR A SIGN A623;Lo;0;L;;;;;N;;;;; 10719;LINEAR A SIGN A624;Lo;0;L;;;;;N;;;;; 1071A;LINEAR A SIGN A626;Lo;0;L;;;;;N;;;;; 1071B;LINEAR A SIGN A627;Lo;0;L;;;;;N;;;;; 1071C;LINEAR A SIGN A628;Lo;0;L;;;;;N;;;;; 1071D;LINEAR A SIGN A629;Lo;0;L;;;;;N;;;;; 1071E;LINEAR A SIGN A634;Lo;0;L;;;;;N;;;;; 1071F;LINEAR A SIGN A637;Lo;0;L;;;;;N;;;;; 10720;LINEAR A SIGN A638;Lo;0;L;;;;;N;;;;; 10721;LINEAR A SIGN A640;Lo;0;L;;;;;N;;;;; 10722;LINEAR A SIGN A642;Lo;0;L;;;;;N;;;;; 10723;LINEAR A SIGN A643;Lo;0;L;;;;;N;;;;; 10724;LINEAR A SIGN A644;Lo;0;L;;;;;N;;;;; 10725;LINEAR A SIGN A645;Lo;0;L;;;;;N;;;;; 10726;LINEAR A SIGN A646;Lo;0;L;;;;;N;;;;; 10727;LINEAR A SIGN A648;Lo;0;L;;;;;N;;;;; 10728;LINEAR A SIGN A649;Lo;0;L;;;;;N;;;;; 10729;LINEAR A SIGN A651;Lo;0;L;;;;;N;;;;; 1072A;LINEAR A SIGN A652;Lo;0;L;;;;;N;;;;; 1072B;LINEAR A SIGN A653;Lo;0;L;;;;;N;;;;; 1072C;LINEAR A SIGN A654;Lo;0;L;;;;;N;;;;; 1072D;LINEAR A SIGN A655;Lo;0;L;;;;;N;;;;; 1072E;LINEAR A SIGN A656;Lo;0;L;;;;;N;;;;; 1072F;LINEAR A SIGN A657;Lo;0;L;;;;;N;;;;; 10730;LINEAR A SIGN A658;Lo;0;L;;;;;N;;;;; 10731;LINEAR A SIGN A659;Lo;0;L;;;;;N;;;;; 10732;LINEAR A SIGN A660;Lo;0;L;;;;;N;;;;; 10733;LINEAR A SIGN A661;Lo;0;L;;;;;N;;;;; 10734;LINEAR A SIGN A662;Lo;0;L;;;;;N;;;;; 10735;LINEAR A SIGN A663;Lo;0;L;;;;;N;;;;; 10736;LINEAR A SIGN A664;Lo;0;L;;;;;N;;;;; 10740;LINEAR A SIGN A701 A;Lo;0;L;;;;;N;;;;; 10741;LINEAR A SIGN A702 B;Lo;0;L;;;;;N;;;;; 10742;LINEAR A SIGN A703 D;Lo;0;L;;;;;N;;;;; 10743;LINEAR A SIGN A704 E;Lo;0;L;;;;;N;;;;; 10744;LINEAR A SIGN A705 F;Lo;0;L;;;;;N;;;;; 10745;LINEAR A SIGN A706 H;Lo;0;L;;;;;N;;;;; 10746;LINEAR A SIGN A707 J;Lo;0;L;;;;;N;;;;; 10747;LINEAR A SIGN A708 K;Lo;0;L;;;;;N;;;;; 10748;LINEAR A SIGN A709 L;Lo;0;L;;;;;N;;;;; 10749;LINEAR A SIGN A709-2 L2;Lo;0;L;;;;;N;;;;; 1074A;LINEAR A SIGN A709-3 L3;Lo;0;L;;;;;N;;;;; 1074B;LINEAR A SIGN A709-4 L4;Lo;0;L;;;;;N;;;;; 1074C;LINEAR A SIGN A709-6 L6;Lo;0;L;;;;;N;;;;; 1074D;LINEAR A SIGN A710 W;Lo;0;L;;;;;N;;;;; 1074E;LINEAR A SIGN A711 X;Lo;0;L;;;;;N;;;;; 1074F;LINEAR A SIGN A712 Y;Lo;0;L;;;;;N;;;;; 10750;LINEAR A SIGN A713 OMEGA;Lo;0;L;;;;;N;;;;; 10751;LINEAR A SIGN A714 ABB;Lo;0;L;;;;;N;;;;; 10752;LINEAR A SIGN A715 BB;Lo;0;L;;;;;N;;;;; 10753;LINEAR A SIGN A717 DD;Lo;0;L;;;;;N;;;;; 10754;LINEAR A SIGN A726 EYYY;Lo;0;L;;;;;N;;;;; 10755;LINEAR A SIGN A732 JE;Lo;0;L;;;;;N;;;;; 10760;LINEAR A SIGN A800;Lo;0;L;;;;;N;;;;; 10761;LINEAR A SIGN A801;Lo;0;L;;;;;N;;;;; 10762;LINEAR A SIGN A802;Lo;0;L;;;;;N;;;;; 10763;LINEAR A SIGN A803;Lo;0;L;;;;;N;;;;; 10764;LINEAR A SIGN A804;Lo;0;L;;;;;N;;;;; 10765;LINEAR A SIGN A805;Lo;0;L;;;;;N;;;;; 10766;LINEAR A SIGN A806;Lo;0;L;;;;;N;;;;; 10767;LINEAR A SIGN A807;Lo;0;L;;;;;N;;;;; 10800;CYPRIOT SYLLABLE A;Lo;0;R;;;;;N;;;;; 10801;CYPRIOT SYLLABLE E;Lo;0;R;;;;;N;;;;; 10802;CYPRIOT SYLLABLE I;Lo;0;R;;;;;N;;;;; 10803;CYPRIOT SYLLABLE O;Lo;0;R;;;;;N;;;;; 10804;CYPRIOT SYLLABLE U;Lo;0;R;;;;;N;;;;; 10805;CYPRIOT SYLLABLE JA;Lo;0;R;;;;;N;;;;; 10808;CYPRIOT SYLLABLE JO;Lo;0;R;;;;;N;;;;; 1080A;CYPRIOT SYLLABLE KA;Lo;0;R;;;;;N;;;;; 1080B;CYPRIOT SYLLABLE KE;Lo;0;R;;;;;N;;;;; 1080C;CYPRIOT SYLLABLE KI;Lo;0;R;;;;;N;;;;; 1080D;CYPRIOT SYLLABLE KO;Lo;0;R;;;;;N;;;;; 1080E;CYPRIOT SYLLABLE KU;Lo;0;R;;;;;N;;;;; 1080F;CYPRIOT SYLLABLE LA;Lo;0;R;;;;;N;;;;; 10810;CYPRIOT SYLLABLE LE;Lo;0;R;;;;;N;;;;; 10811;CYPRIOT SYLLABLE LI;Lo;0;R;;;;;N;;;;; 10812;CYPRIOT SYLLABLE LO;Lo;0;R;;;;;N;;;;; 10813;CYPRIOT SYLLABLE LU;Lo;0;R;;;;;N;;;;; 10814;CYPRIOT SYLLABLE MA;Lo;0;R;;;;;N;;;;; 10815;CYPRIOT SYLLABLE ME;Lo;0;R;;;;;N;;;;; 10816;CYPRIOT SYLLABLE MI;Lo;0;R;;;;;N;;;;; 10817;CYPRIOT SYLLABLE MO;Lo;0;R;;;;;N;;;;; 10818;CYPRIOT SYLLABLE MU;Lo;0;R;;;;;N;;;;; 10819;CYPRIOT SYLLABLE NA;Lo;0;R;;;;;N;;;;; 1081A;CYPRIOT SYLLABLE NE;Lo;0;R;;;;;N;;;;; 1081B;CYPRIOT SYLLABLE NI;Lo;0;R;;;;;N;;;;; 1081C;CYPRIOT SYLLABLE NO;Lo;0;R;;;;;N;;;;; 1081D;CYPRIOT SYLLABLE NU;Lo;0;R;;;;;N;;;;; 1081E;CYPRIOT SYLLABLE PA;Lo;0;R;;;;;N;;;;; 1081F;CYPRIOT SYLLABLE PE;Lo;0;R;;;;;N;;;;; 10820;CYPRIOT SYLLABLE PI;Lo;0;R;;;;;N;;;;; 10821;CYPRIOT SYLLABLE PO;Lo;0;R;;;;;N;;;;; 10822;CYPRIOT SYLLABLE PU;Lo;0;R;;;;;N;;;;; 10823;CYPRIOT SYLLABLE RA;Lo;0;R;;;;;N;;;;; 10824;CYPRIOT SYLLABLE RE;Lo;0;R;;;;;N;;;;; 10825;CYPRIOT SYLLABLE RI;Lo;0;R;;;;;N;;;;; 10826;CYPRIOT SYLLABLE RO;Lo;0;R;;;;;N;;;;; 10827;CYPRIOT SYLLABLE RU;Lo;0;R;;;;;N;;;;; 10828;CYPRIOT SYLLABLE SA;Lo;0;R;;;;;N;;;;; 10829;CYPRIOT SYLLABLE SE;Lo;0;R;;;;;N;;;;; 1082A;CYPRIOT SYLLABLE SI;Lo;0;R;;;;;N;;;;; 1082B;CYPRIOT SYLLABLE SO;Lo;0;R;;;;;N;;;;; 1082C;CYPRIOT SYLLABLE SU;Lo;0;R;;;;;N;;;;; 1082D;CYPRIOT SYLLABLE TA;Lo;0;R;;;;;N;;;;; 1082E;CYPRIOT SYLLABLE TE;Lo;0;R;;;;;N;;;;; 1082F;CYPRIOT SYLLABLE TI;Lo;0;R;;;;;N;;;;; 10830;CYPRIOT SYLLABLE TO;Lo;0;R;;;;;N;;;;; 10831;CYPRIOT SYLLABLE TU;Lo;0;R;;;;;N;;;;; 10832;CYPRIOT SYLLABLE WA;Lo;0;R;;;;;N;;;;; 10833;CYPRIOT SYLLABLE WE;Lo;0;R;;;;;N;;;;; 10834;CYPRIOT SYLLABLE WI;Lo;0;R;;;;;N;;;;; 10835;CYPRIOT SYLLABLE WO;Lo;0;R;;;;;N;;;;; 10837;CYPRIOT SYLLABLE XA;Lo;0;R;;;;;N;;;;; 10838;CYPRIOT SYLLABLE XE;Lo;0;R;;;;;N;;;;; 1083C;CYPRIOT SYLLABLE ZA;Lo;0;R;;;;;N;;;;; 1083F;CYPRIOT SYLLABLE ZO;Lo;0;R;;;;;N;;;;; 10840;IMPERIAL ARAMAIC LETTER ALEPH;Lo;0;R;;;;;N;;;;; 10841;IMPERIAL ARAMAIC LETTER BETH;Lo;0;R;;;;;N;;;;; 10842;IMPERIAL ARAMAIC LETTER GIMEL;Lo;0;R;;;;;N;;;;; 10843;IMPERIAL ARAMAIC LETTER DALETH;Lo;0;R;;;;;N;;;;; 10844;IMPERIAL ARAMAIC LETTER HE;Lo;0;R;;;;;N;;;;; 10845;IMPERIAL ARAMAIC LETTER WAW;Lo;0;R;;;;;N;;;;; 10846;IMPERIAL ARAMAIC LETTER ZAYIN;Lo;0;R;;;;;N;;;;; 10847;IMPERIAL ARAMAIC LETTER HETH;Lo;0;R;;;;;N;;;;; 10848;IMPERIAL ARAMAIC LETTER TETH;Lo;0;R;;;;;N;;;;; 10849;IMPERIAL ARAMAIC LETTER YODH;Lo;0;R;;;;;N;;;;; 1084A;IMPERIAL ARAMAIC LETTER KAPH;Lo;0;R;;;;;N;;;;; 1084B;IMPERIAL ARAMAIC LETTER LAMEDH;Lo;0;R;;;;;N;;;;; 1084C;IMPERIAL ARAMAIC LETTER MEM;Lo;0;R;;;;;N;;;;; 1084D;IMPERIAL ARAMAIC LETTER NUN;Lo;0;R;;;;;N;;;;; 1084E;IMPERIAL ARAMAIC LETTER SAMEKH;Lo;0;R;;;;;N;;;;; 1084F;IMPERIAL ARAMAIC LETTER AYIN;Lo;0;R;;;;;N;;;;; 10850;IMPERIAL ARAMAIC LETTER PE;Lo;0;R;;;;;N;;;;; 10851;IMPERIAL ARAMAIC LETTER SADHE;Lo;0;R;;;;;N;;;;; 10852;IMPERIAL ARAMAIC LETTER QOPH;Lo;0;R;;;;;N;;;;; 10853;IMPERIAL ARAMAIC LETTER RESH;Lo;0;R;;;;;N;;;;; 10854;IMPERIAL ARAMAIC LETTER SHIN;Lo;0;R;;;;;N;;;;; 10855;IMPERIAL ARAMAIC LETTER TAW;Lo;0;R;;;;;N;;;;; 10857;IMPERIAL ARAMAIC SECTION SIGN;Po;0;R;;;;;N;;;;; 10858;IMPERIAL ARAMAIC NUMBER ONE;No;0;R;;;;1;N;;;;; 10859;IMPERIAL ARAMAIC NUMBER TWO;No;0;R;;;;2;N;;;;; 1085A;IMPERIAL ARAMAIC NUMBER THREE;No;0;R;;;;3;N;;;;; 1085B;IMPERIAL ARAMAIC NUMBER TEN;No;0;R;;;;10;N;;;;; 1085C;IMPERIAL ARAMAIC NUMBER TWENTY;No;0;R;;;;20;N;;;;; 1085D;IMPERIAL ARAMAIC NUMBER ONE HUNDRED;No;0;R;;;;100;N;;;;; 1085E;IMPERIAL ARAMAIC NUMBER ONE THOUSAND;No;0;R;;;;1000;N;;;;; 1085F;IMPERIAL ARAMAIC NUMBER TEN THOUSAND;No;0;R;;;;10000;N;;;;; 10860;PALMYRENE LETTER ALEPH;Lo;0;R;;;;;N;;;;; 10861;PALMYRENE LETTER BETH;Lo;0;R;;;;;N;;;;; 10862;PALMYRENE LETTER GIMEL;Lo;0;R;;;;;N;;;;; 10863;PALMYRENE LETTER DALETH;Lo;0;R;;;;;N;;;;; 10864;PALMYRENE LETTER HE;Lo;0;R;;;;;N;;;;; 10865;PALMYRENE LETTER WAW;Lo;0;R;;;;;N;;;;; 10866;PALMYRENE LETTER ZAYIN;Lo;0;R;;;;;N;;;;; 10867;PALMYRENE LETTER HETH;Lo;0;R;;;;;N;;;;; 10868;PALMYRENE LETTER TETH;Lo;0;R;;;;;N;;;;; 10869;PALMYRENE LETTER YODH;Lo;0;R;;;;;N;;;;; 1086A;PALMYRENE LETTER KAPH;Lo;0;R;;;;;N;;;;; 1086B;PALMYRENE LETTER LAMEDH;Lo;0;R;;;;;N;;;;; 1086C;PALMYRENE LETTER MEM;Lo;0;R;;;;;N;;;;; 1086D;PALMYRENE LETTER FINAL NUN;Lo;0;R;;;;;N;;;;; 1086E;PALMYRENE LETTER NUN;Lo;0;R;;;;;N;;;;; 1086F;PALMYRENE LETTER SAMEKH;Lo;0;R;;;;;N;;;;; 10870;PALMYRENE LETTER AYIN;Lo;0;R;;;;;N;;;;; 10871;PALMYRENE LETTER PE;Lo;0;R;;;;;N;;;;; 10872;PALMYRENE LETTER SADHE;Lo;0;R;;;;;N;;;;; 10873;PALMYRENE LETTER QOPH;Lo;0;R;;;;;N;;;;; 10874;PALMYRENE LETTER RESH;Lo;0;R;;;;;N;;;;; 10875;PALMYRENE LETTER SHIN;Lo;0;R;;;;;N;;;;; 10876;PALMYRENE LETTER TAW;Lo;0;R;;;;;N;;;;; 10877;PALMYRENE LEFT-POINTING FLEURON;So;0;R;;;;;N;;;;; 10878;PALMYRENE RIGHT-POINTING FLEURON;So;0;R;;;;;N;;;;; 10879;PALMYRENE NUMBER ONE;No;0;R;;;;1;N;;;;; 1087A;PALMYRENE NUMBER TWO;No;0;R;;;;2;N;;;;; 1087B;PALMYRENE NUMBER THREE;No;0;R;;;;3;N;;;;; 1087C;PALMYRENE NUMBER FOUR;No;0;R;;;;4;N;;;;; 1087D;PALMYRENE NUMBER FIVE;No;0;R;;;;5;N;;;;; 1087E;PALMYRENE NUMBER TEN;No;0;R;;;;10;N;;;;; 1087F;PALMYRENE NUMBER TWENTY;No;0;R;;;;20;N;;;;; 10880;NABATAEAN LETTER FINAL ALEPH;Lo;0;R;;;;;N;;;;; 10881;NABATAEAN LETTER ALEPH;Lo;0;R;;;;;N;;;;; 10882;NABATAEAN LETTER FINAL BETH;Lo;0;R;;;;;N;;;;; 10883;NABATAEAN LETTER BETH;Lo;0;R;;;;;N;;;;; 10884;NABATAEAN LETTER GIMEL;Lo;0;R;;;;;N;;;;; 10885;NABATAEAN LETTER DALETH;Lo;0;R;;;;;N;;;;; 10886;NABATAEAN LETTER FINAL HE;Lo;0;R;;;;;N;;;;; 10887;NABATAEAN LETTER HE;Lo;0;R;;;;;N;;;;; 10888;NABATAEAN LETTER WAW;Lo;0;R;;;;;N;;;;; 10889;NABATAEAN LETTER ZAYIN;Lo;0;R;;;;;N;;;;; 1088A;NABATAEAN LETTER HETH;Lo;0;R;;;;;N;;;;; 1088B;NABATAEAN LETTER TETH;Lo;0;R;;;;;N;;;;; 1088C;NABATAEAN LETTER FINAL YODH;Lo;0;R;;;;;N;;;;; 1088D;NABATAEAN LETTER YODH;Lo;0;R;;;;;N;;;;; 1088E;NABATAEAN LETTER FINAL KAPH;Lo;0;R;;;;;N;;;;; 1088F;NABATAEAN LETTER KAPH;Lo;0;R;;;;;N;;;;; 10890;NABATAEAN LETTER FINAL LAMEDH;Lo;0;R;;;;;N;;;;; 10891;NABATAEAN LETTER LAMEDH;Lo;0;R;;;;;N;;;;; 10892;NABATAEAN LETTER FINAL MEM;Lo;0;R;;;;;N;;;;; 10893;NABATAEAN LETTER MEM;Lo;0;R;;;;;N;;;;; 10894;NABATAEAN LETTER FINAL NUN;Lo;0;R;;;;;N;;;;; 10895;NABATAEAN LETTER NUN;Lo;0;R;;;;;N;;;;; 10896;NABATAEAN LETTER SAMEKH;Lo;0;R;;;;;N;;;;; 10897;NABATAEAN LETTER AYIN;Lo;0;R;;;;;N;;;;; 10898;NABATAEAN LETTER PE;Lo;0;R;;;;;N;;;;; 10899;NABATAEAN LETTER SADHE;Lo;0;R;;;;;N;;;;; 1089A;NABATAEAN LETTER QOPH;Lo;0;R;;;;;N;;;;; 1089B;NABATAEAN LETTER RESH;Lo;0;R;;;;;N;;;;; 1089C;NABATAEAN LETTER FINAL SHIN;Lo;0;R;;;;;N;;;;; 1089D;NABATAEAN LETTER SHIN;Lo;0;R;;;;;N;;;;; 1089E;NABATAEAN LETTER TAW;Lo;0;R;;;;;N;;;;; 108A7;NABATAEAN NUMBER ONE;No;0;R;;;;1;N;;;;; 108A8;NABATAEAN NUMBER TWO;No;0;R;;;;2;N;;;;; 108A9;NABATAEAN NUMBER THREE;No;0;R;;;;3;N;;;;; 108AA;NABATAEAN NUMBER FOUR;No;0;R;;;;4;N;;;;; 108AB;NABATAEAN CRUCIFORM NUMBER FOUR;No;0;R;;;;4;N;;;;; 108AC;NABATAEAN NUMBER FIVE;No;0;R;;;;5;N;;;;; 108AD;NABATAEAN NUMBER TEN;No;0;R;;;;10;N;;;;; 108AE;NABATAEAN NUMBER TWENTY;No;0;R;;;;20;N;;;;; 108AF;NABATAEAN NUMBER ONE HUNDRED;No;0;R;;;;100;N;;;;; 108E0;HATRAN LETTER ALEPH;Lo;0;R;;;;;N;;;;; 108E1;HATRAN LETTER BETH;Lo;0;R;;;;;N;;;;; 108E2;HATRAN LETTER GIMEL;Lo;0;R;;;;;N;;;;; 108E3;HATRAN LETTER DALETH-RESH;Lo;0;R;;;;;N;;;;; 108E4;HATRAN LETTER HE;Lo;0;R;;;;;N;;;;; 108E5;HATRAN LETTER WAW;Lo;0;R;;;;;N;;;;; 108E6;HATRAN LETTER ZAYN;Lo;0;R;;;;;N;;;;; 108E7;HATRAN LETTER HETH;Lo;0;R;;;;;N;;;;; 108E8;HATRAN LETTER TETH;Lo;0;R;;;;;N;;;;; 108E9;HATRAN LETTER YODH;Lo;0;R;;;;;N;;;;; 108EA;HATRAN LETTER KAPH;Lo;0;R;;;;;N;;;;; 108EB;HATRAN LETTER LAMEDH;Lo;0;R;;;;;N;;;;; 108EC;HATRAN LETTER MEM;Lo;0;R;;;;;N;;;;; 108ED;HATRAN LETTER NUN;Lo;0;R;;;;;N;;;;; 108EE;HATRAN LETTER SAMEKH;Lo;0;R;;;;;N;;;;; 108EF;HATRAN LETTER AYN;Lo;0;R;;;;;N;;;;; 108F0;HATRAN LETTER PE;Lo;0;R;;;;;N;;;;; 108F1;HATRAN LETTER SADHE;Lo;0;R;;;;;N;;;;; 108F2;HATRAN LETTER QOPH;Lo;0;R;;;;;N;;;;; 108F4;HATRAN LETTER SHIN;Lo;0;R;;;;;N;;;;; 108F5;HATRAN LETTER TAW;Lo;0;R;;;;;N;;;;; 108FB;HATRAN NUMBER ONE;No;0;R;;;;1;N;;;;; 108FC;HATRAN NUMBER FIVE;No;0;R;;;;5;N;;;;; 108FD;HATRAN NUMBER TEN;No;0;R;;;;10;N;;;;; 108FE;HATRAN NUMBER TWENTY;No;0;R;;;;20;N;;;;; 108FF;HATRAN NUMBER ONE HUNDRED;No;0;R;;;;100;N;;;;; 10900;PHOENICIAN LETTER ALF;Lo;0;R;;;;;N;;;;; 10901;PHOENICIAN LETTER BET;Lo;0;R;;;;;N;;;;; 10902;PHOENICIAN LETTER GAML;Lo;0;R;;;;;N;;;;; 10903;PHOENICIAN LETTER DELT;Lo;0;R;;;;;N;;;;; 10904;PHOENICIAN LETTER HE;Lo;0;R;;;;;N;;;;; 10905;PHOENICIAN LETTER WAU;Lo;0;R;;;;;N;;;;; 10906;PHOENICIAN LETTER ZAI;Lo;0;R;;;;;N;;;;; 10907;PHOENICIAN LETTER HET;Lo;0;R;;;;;N;;;;; 10908;PHOENICIAN LETTER TET;Lo;0;R;;;;;N;;;;; 10909;PHOENICIAN LETTER YOD;Lo;0;R;;;;;N;;;;; 1090A;PHOENICIAN LETTER KAF;Lo;0;R;;;;;N;;;;; 1090B;PHOENICIAN LETTER LAMD;Lo;0;R;;;;;N;;;;; 1090C;PHOENICIAN LETTER MEM;Lo;0;R;;;;;N;;;;; 1090D;PHOENICIAN LETTER NUN;Lo;0;R;;;;;N;;;;; 1090E;PHOENICIAN LETTER SEMK;Lo;0;R;;;;;N;;;;; 1090F;PHOENICIAN LETTER AIN;Lo;0;R;;;;;N;;;;; 10910;PHOENICIAN LETTER PE;Lo;0;R;;;;;N;;;;; 10911;PHOENICIAN LETTER SADE;Lo;0;R;;;;;N;;;;; 10912;PHOENICIAN LETTER QOF;Lo;0;R;;;;;N;;;;; 10913;PHOENICIAN LETTER ROSH;Lo;0;R;;;;;N;;;;; 10914;PHOENICIAN LETTER SHIN;Lo;0;R;;;;;N;;;;; 10915;PHOENICIAN LETTER TAU;Lo;0;R;;;;;N;;;;; 10916;PHOENICIAN NUMBER ONE;No;0;R;;;;1;N;;;;; 10917;PHOENICIAN NUMBER TEN;No;0;R;;;;10;N;;;;; 10918;PHOENICIAN NUMBER TWENTY;No;0;R;;;;20;N;;;;; 10919;PHOENICIAN NUMBER ONE HUNDRED;No;0;R;;;;100;N;;;;; 1091A;PHOENICIAN NUMBER TWO;No;0;R;;;;2;N;;;;; 1091B;PHOENICIAN NUMBER THREE;No;0;R;;;;3;N;;;;; 1091F;PHOENICIAN WORD SEPARATOR;Po;0;ON;;;;;N;;;;; 10920;LYDIAN LETTER A;Lo;0;R;;;;;N;;;;; 10921;LYDIAN LETTER B;Lo;0;R;;;;;N;;;;; 10922;LYDIAN LETTER G;Lo;0;R;;;;;N;;;;; 10923;LYDIAN LETTER D;Lo;0;R;;;;;N;;;;; 10924;LYDIAN LETTER E;Lo;0;R;;;;;N;;;;; 10925;LYDIAN LETTER V;Lo;0;R;;;;;N;;;;; 10926;LYDIAN LETTER I;Lo;0;R;;;;;N;;;;; 10927;LYDIAN LETTER Y;Lo;0;R;;;;;N;;;;; 10928;LYDIAN LETTER K;Lo;0;R;;;;;N;;;;; 10929;LYDIAN LETTER L;Lo;0;R;;;;;N;;;;; 1092A;LYDIAN LETTER M;Lo;0;R;;;;;N;;;;; 1092B;LYDIAN LETTER N;Lo;0;R;;;;;N;;;;; 1092C;LYDIAN LETTER O;Lo;0;R;;;;;N;;;;; 1092D;LYDIAN LETTER R;Lo;0;R;;;;;N;;;;; 1092E;LYDIAN LETTER SS;Lo;0;R;;;;;N;;;;; 1092F;LYDIAN LETTER T;Lo;0;R;;;;;N;;;;; 10930;LYDIAN LETTER U;Lo;0;R;;;;;N;;;;; 10931;LYDIAN LETTER F;Lo;0;R;;;;;N;;;;; 10932;LYDIAN LETTER Q;Lo;0;R;;;;;N;;;;; 10933;LYDIAN LETTER S;Lo;0;R;;;;;N;;;;; 10934;LYDIAN LETTER TT;Lo;0;R;;;;;N;;;;; 10935;LYDIAN LETTER AN;Lo;0;R;;;;;N;;;;; 10936;LYDIAN LETTER EN;Lo;0;R;;;;;N;;;;; 10937;LYDIAN LETTER LY;Lo;0;R;;;;;N;;;;; 10938;LYDIAN LETTER NN;Lo;0;R;;;;;N;;;;; 10939;LYDIAN LETTER C;Lo;0;R;;;;;N;;;;; 1093F;LYDIAN TRIANGULAR MARK;Po;0;R;;;;;N;;;;; 10980;MEROITIC HIEROGLYPHIC LETTER A;Lo;0;R;;;;;N;;;;; 10981;MEROITIC HIEROGLYPHIC LETTER E;Lo;0;R;;;;;N;;;;; 10982;MEROITIC HIEROGLYPHIC LETTER I;Lo;0;R;;;;;N;;;;; 10983;MEROITIC HIEROGLYPHIC LETTER O;Lo;0;R;;;;;N;;;;; 10984;MEROITIC HIEROGLYPHIC LETTER YA;Lo;0;R;;;;;N;;;;; 10985;MEROITIC HIEROGLYPHIC LETTER WA;Lo;0;R;;;;;N;;;;; 10986;MEROITIC HIEROGLYPHIC LETTER BA;Lo;0;R;;;;;N;;;;; 10987;MEROITIC HIEROGLYPHIC LETTER BA-2;Lo;0;R;;;;;N;;;;; 10988;MEROITIC HIEROGLYPHIC LETTER PA;Lo;0;R;;;;;N;;;;; 10989;MEROITIC HIEROGLYPHIC LETTER MA;Lo;0;R;;;;;N;;;;; 1098A;MEROITIC HIEROGLYPHIC LETTER NA;Lo;0;R;;;;;N;;;;; 1098B;MEROITIC HIEROGLYPHIC LETTER NA-2;Lo;0;R;;;;;N;;;;; 1098C;MEROITIC HIEROGLYPHIC LETTER NE;Lo;0;R;;;;;N;;;;; 1098D;MEROITIC HIEROGLYPHIC LETTER NE-2;Lo;0;R;;;;;N;;;;; 1098E;MEROITIC HIEROGLYPHIC LETTER RA;Lo;0;R;;;;;N;;;;; 1098F;MEROITIC HIEROGLYPHIC LETTER RA-2;Lo;0;R;;;;;N;;;;; 10990;MEROITIC HIEROGLYPHIC LETTER LA;Lo;0;R;;;;;N;;;;; 10991;MEROITIC HIEROGLYPHIC LETTER KHA;Lo;0;R;;;;;N;;;;; 10992;MEROITIC HIEROGLYPHIC LETTER HHA;Lo;0;R;;;;;N;;;;; 10993;MEROITIC HIEROGLYPHIC LETTER SA;Lo;0;R;;;;;N;;;;; 10994;MEROITIC HIEROGLYPHIC LETTER SA-2;Lo;0;R;;;;;N;;;;; 10995;MEROITIC HIEROGLYPHIC LETTER SE;Lo;0;R;;;;;N;;;;; 10996;MEROITIC HIEROGLYPHIC LETTER KA;Lo;0;R;;;;;N;;;;; 10997;MEROITIC HIEROGLYPHIC LETTER QA;Lo;0;R;;;;;N;;;;; 10998;MEROITIC HIEROGLYPHIC LETTER TA;Lo;0;R;;;;;N;;;;; 10999;MEROITIC HIEROGLYPHIC LETTER TA-2;Lo;0;R;;;;;N;;;;; 1099A;MEROITIC HIEROGLYPHIC LETTER TE;Lo;0;R;;;;;N;;;;; 1099B;MEROITIC HIEROGLYPHIC LETTER TE-2;Lo;0;R;;;;;N;;;;; 1099C;MEROITIC HIEROGLYPHIC LETTER TO;Lo;0;R;;;;;N;;;;; 1099D;MEROITIC HIEROGLYPHIC LETTER DA;Lo;0;R;;;;;N;;;;; 1099E;MEROITIC HIEROGLYPHIC SYMBOL VIDJ;Lo;0;R;;;;;N;;;;; 1099F;MEROITIC HIEROGLYPHIC SYMBOL VIDJ-2;Lo;0;R;;;;;N;;;;; 109A0;MEROITIC CURSIVE LETTER A;Lo;0;R;;;;;N;;;;; 109A1;MEROITIC CURSIVE LETTER E;Lo;0;R;;;;;N;;;;; 109A2;MEROITIC CURSIVE LETTER I;Lo;0;R;;;;;N;;;;; 109A3;MEROITIC CURSIVE LETTER O;Lo;0;R;;;;;N;;;;; 109A4;MEROITIC CURSIVE LETTER YA;Lo;0;R;;;;;N;;;;; 109A5;MEROITIC CURSIVE LETTER WA;Lo;0;R;;;;;N;;;;; 109A6;MEROITIC CURSIVE LETTER BA;Lo;0;R;;;;;N;;;;; 109A7;MEROITIC CURSIVE LETTER PA;Lo;0;R;;;;;N;;;;; 109A8;MEROITIC CURSIVE LETTER MA;Lo;0;R;;;;;N;;;;; 109A9;MEROITIC CURSIVE LETTER NA;Lo;0;R;;;;;N;;;;; 109AA;MEROITIC CURSIVE LETTER NE;Lo;0;R;;;;;N;;;;; 109AB;MEROITIC CURSIVE LETTER RA;Lo;0;R;;;;;N;;;;; 109AC;MEROITIC CURSIVE LETTER LA;Lo;0;R;;;;;N;;;;; 109AD;MEROITIC CURSIVE LETTER KHA;Lo;0;R;;;;;N;;;;; 109AE;MEROITIC CURSIVE LETTER HHA;Lo;0;R;;;;;N;;;;; 109AF;MEROITIC CURSIVE LETTER SA;Lo;0;R;;;;;N;;;;; 109B0;MEROITIC CURSIVE LETTER ARCHAIC SA;Lo;0;R;;;;;N;;;;; 109B1;MEROITIC CURSIVE LETTER SE;Lo;0;R;;;;;N;;;;; 109B2;MEROITIC CURSIVE LETTER KA;Lo;0;R;;;;;N;;;;; 109B3;MEROITIC CURSIVE LETTER QA;Lo;0;R;;;;;N;;;;; 109B4;MEROITIC CURSIVE LETTER TA;Lo;0;R;;;;;N;;;;; 109B5;MEROITIC CURSIVE LETTER TE;Lo;0;R;;;;;N;;;;; 109B6;MEROITIC CURSIVE LETTER TO;Lo;0;R;;;;;N;;;;; 109B7;MEROITIC CURSIVE LETTER DA;Lo;0;R;;;;;N;;;;; 109BC;MEROITIC CURSIVE FRACTION ELEVEN TWELFTHS;No;0;R;;;;11/12;N;;;;; 109BD;MEROITIC CURSIVE FRACTION ONE HALF;No;0;R;;;;1/2;N;;;;; 109BE;MEROITIC CURSIVE LOGOGRAM RMT;Lo;0;R;;;;;N;;;;; 109BF;MEROITIC CURSIVE LOGOGRAM IMN;Lo;0;R;;;;;N;;;;; 109C0;MEROITIC CURSIVE NUMBER ONE;No;0;R;;;;1;N;;;;; 109C1;MEROITIC CURSIVE NUMBER TWO;No;0;R;;;;2;N;;;;; 109C2;MEROITIC CURSIVE NUMBER THREE;No;0;R;;;;3;N;;;;; 109C3;MEROITIC CURSIVE NUMBER FOUR;No;0;R;;;;4;N;;;;; 109C4;MEROITIC CURSIVE NUMBER FIVE;No;0;R;;;;5;N;;;;; 109C5;MEROITIC CURSIVE NUMBER SIX;No;0;R;;;;6;N;;;;; 109C6;MEROITIC CURSIVE NUMBER SEVEN;No;0;R;;;;7;N;;;;; 109C7;MEROITIC CURSIVE NUMBER EIGHT;No;0;R;;;;8;N;;;;; 109C8;MEROITIC CURSIVE NUMBER NINE;No;0;R;;;;9;N;;;;; 109C9;MEROITIC CURSIVE NUMBER TEN;No;0;R;;;;10;N;;;;; 109CA;MEROITIC CURSIVE NUMBER TWENTY;No;0;R;;;;20;N;;;;; 109CB;MEROITIC CURSIVE NUMBER THIRTY;No;0;R;;;;30;N;;;;; 109CC;MEROITIC CURSIVE NUMBER FORTY;No;0;R;;;;40;N;;;;; 109CD;MEROITIC CURSIVE NUMBER FIFTY;No;0;R;;;;50;N;;;;; 109CE;MEROITIC CURSIVE NUMBER SIXTY;No;0;R;;;;60;N;;;;; 109CF;MEROITIC CURSIVE NUMBER SEVENTY;No;0;R;;;;70;N;;;;; 109D2;MEROITIC CURSIVE NUMBER ONE HUNDRED;No;0;R;;;;100;N;;;;; 109D3;MEROITIC CURSIVE NUMBER TWO HUNDRED;No;0;R;;;;200;N;;;;; 109D4;MEROITIC CURSIVE NUMBER THREE HUNDRED;No;0;R;;;;300;N;;;;; 109D5;MEROITIC CURSIVE NUMBER FOUR HUNDRED;No;0;R;;;;400;N;;;;; 109D6;MEROITIC CURSIVE NUMBER FIVE HUNDRED;No;0;R;;;;500;N;;;;; 109D7;MEROITIC CURSIVE NUMBER SIX HUNDRED;No;0;R;;;;600;N;;;;; 109D8;MEROITIC CURSIVE NUMBER SEVEN HUNDRED;No;0;R;;;;700;N;;;;; 109D9;MEROITIC CURSIVE NUMBER EIGHT HUNDRED;No;0;R;;;;800;N;;;;; 109DA;MEROITIC CURSIVE NUMBER NINE HUNDRED;No;0;R;;;;900;N;;;;; 109DB;MEROITIC CURSIVE NUMBER ONE THOUSAND;No;0;R;;;;1000;N;;;;; 109DC;MEROITIC CURSIVE NUMBER TWO THOUSAND;No;0;R;;;;2000;N;;;;; 109DD;MEROITIC CURSIVE NUMBER THREE THOUSAND;No;0;R;;;;3000;N;;;;; 109DE;MEROITIC CURSIVE NUMBER FOUR THOUSAND;No;0;R;;;;4000;N;;;;; 109DF;MEROITIC CURSIVE NUMBER FIVE THOUSAND;No;0;R;;;;5000;N;;;;; 109E0;MEROITIC CURSIVE NUMBER SIX THOUSAND;No;0;R;;;;6000;N;;;;; 109E1;MEROITIC CURSIVE NUMBER SEVEN THOUSAND;No;0;R;;;;7000;N;;;;; 109E2;MEROITIC CURSIVE NUMBER EIGHT THOUSAND;No;0;R;;;;8000;N;;;;; 109E3;MEROITIC CURSIVE NUMBER NINE THOUSAND;No;0;R;;;;9000;N;;;;; 109E4;MEROITIC CURSIVE NUMBER TEN THOUSAND;No;0;R;;;;10000;N;;;;; 109E5;MEROITIC CURSIVE NUMBER TWENTY THOUSAND;No;0;R;;;;20000;N;;;;; 109E6;MEROITIC CURSIVE NUMBER THIRTY THOUSAND;No;0;R;;;;30000;N;;;;; 109E7;MEROITIC CURSIVE NUMBER FORTY THOUSAND;No;0;R;;;;40000;N;;;;; 109E8;MEROITIC CURSIVE NUMBER FIFTY THOUSAND;No;0;R;;;;50000;N;;;;; 109E9;MEROITIC CURSIVE NUMBER SIXTY THOUSAND;No;0;R;;;;60000;N;;;;; 109EA;MEROITIC CURSIVE NUMBER SEVENTY THOUSAND;No;0;R;;;;70000;N;;;;; 109EB;MEROITIC CURSIVE NUMBER EIGHTY THOUSAND;No;0;R;;;;80000;N;;;;; 109EC;MEROITIC CURSIVE NUMBER NINETY THOUSAND;No;0;R;;;;90000;N;;;;; 109ED;MEROITIC CURSIVE NUMBER ONE HUNDRED THOUSAND;No;0;R;;;;100000;N;;;;; 109EE;MEROITIC CURSIVE NUMBER TWO HUNDRED THOUSAND;No;0;R;;;;200000;N;;;;; 109EF;MEROITIC CURSIVE NUMBER THREE HUNDRED THOUSAND;No;0;R;;;;300000;N;;;;; 109F0;MEROITIC CURSIVE NUMBER FOUR HUNDRED THOUSAND;No;0;R;;;;400000;N;;;;; 109F1;MEROITIC CURSIVE NUMBER FIVE HUNDRED THOUSAND;No;0;R;;;;500000;N;;;;; 109F2;MEROITIC CURSIVE NUMBER SIX HUNDRED THOUSAND;No;0;R;;;;600000;N;;;;; 109F3;MEROITIC CURSIVE NUMBER SEVEN HUNDRED THOUSAND;No;0;R;;;;700000;N;;;;; 109F4;MEROITIC CURSIVE NUMBER EIGHT HUNDRED THOUSAND;No;0;R;;;;800000;N;;;;; 109F5;MEROITIC CURSIVE NUMBER NINE HUNDRED THOUSAND;No;0;R;;;;900000;N;;;;; 109F6;MEROITIC CURSIVE FRACTION ONE TWELFTH;No;0;R;;;;1/12;N;;;;; 109F7;MEROITIC CURSIVE FRACTION TWO TWELFTHS;No;0;R;;;;2/12;N;;;;; 109F8;MEROITIC CURSIVE FRACTION THREE TWELFTHS;No;0;R;;;;3/12;N;;;;; 109F9;MEROITIC CURSIVE FRACTION FOUR TWELFTHS;No;0;R;;;;4/12;N;;;;; 109FA;MEROITIC CURSIVE FRACTION FIVE TWELFTHS;No;0;R;;;;5/12;N;;;;; 109FB;MEROITIC CURSIVE FRACTION SIX TWELFTHS;No;0;R;;;;6/12;N;;;;; 109FC;MEROITIC CURSIVE FRACTION SEVEN TWELFTHS;No;0;R;;;;7/12;N;;;;; 109FD;MEROITIC CURSIVE FRACTION EIGHT TWELFTHS;No;0;R;;;;8/12;N;;;;; 109FE;MEROITIC CURSIVE FRACTION NINE TWELFTHS;No;0;R;;;;9/12;N;;;;; 109FF;MEROITIC CURSIVE FRACTION TEN TWELFTHS;No;0;R;;;;10/12;N;;;;; 10A00;KHAROSHTHI LETTER A;Lo;0;R;;;;;N;;;;; 10A01;KHAROSHTHI VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; 10A02;KHAROSHTHI VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 10A03;KHAROSHTHI VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;; 10A05;KHAROSHTHI VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;; 10A06;KHAROSHTHI VOWEL SIGN O;Mn;0;NSM;;;;;N;;;;; 10A0C;KHAROSHTHI VOWEL LENGTH MARK;Mn;0;NSM;;;;;N;;;;; 10A0D;KHAROSHTHI SIGN DOUBLE RING BELOW;Mn;220;NSM;;;;;N;;;;; 10A0E;KHAROSHTHI SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; 10A0F;KHAROSHTHI SIGN VISARGA;Mn;230;NSM;;;;;N;;;;; 10A10;KHAROSHTHI LETTER KA;Lo;0;R;;;;;N;;;;; 10A11;KHAROSHTHI LETTER KHA;Lo;0;R;;;;;N;;;;; 10A12;KHAROSHTHI LETTER GA;Lo;0;R;;;;;N;;;;; 10A13;KHAROSHTHI LETTER GHA;Lo;0;R;;;;;N;;;;; 10A15;KHAROSHTHI LETTER CA;Lo;0;R;;;;;N;;;;; 10A16;KHAROSHTHI LETTER CHA;Lo;0;R;;;;;N;;;;; 10A17;KHAROSHTHI LETTER JA;Lo;0;R;;;;;N;;;;; 10A19;KHAROSHTHI LETTER NYA;Lo;0;R;;;;;N;;;;; 10A1A;KHAROSHTHI LETTER TTA;Lo;0;R;;;;;N;;;;; 10A1B;KHAROSHTHI LETTER TTHA;Lo;0;R;;;;;N;;;;; 10A1C;KHAROSHTHI LETTER DDA;Lo;0;R;;;;;N;;;;; 10A1D;KHAROSHTHI LETTER DDHA;Lo;0;R;;;;;N;;;;; 10A1E;KHAROSHTHI LETTER NNA;Lo;0;R;;;;;N;;;;; 10A1F;KHAROSHTHI LETTER TA;Lo;0;R;;;;;N;;;;; 10A20;KHAROSHTHI LETTER THA;Lo;0;R;;;;;N;;;;; 10A21;KHAROSHTHI LETTER DA;Lo;0;R;;;;;N;;;;; 10A22;KHAROSHTHI LETTER DHA;Lo;0;R;;;;;N;;;;; 10A23;KHAROSHTHI LETTER NA;Lo;0;R;;;;;N;;;;; 10A24;KHAROSHTHI LETTER PA;Lo;0;R;;;;;N;;;;; 10A25;KHAROSHTHI LETTER PHA;Lo;0;R;;;;;N;;;;; 10A26;KHAROSHTHI LETTER BA;Lo;0;R;;;;;N;;;;; 10A27;KHAROSHTHI LETTER BHA;Lo;0;R;;;;;N;;;;; 10A28;KHAROSHTHI LETTER MA;Lo;0;R;;;;;N;;;;; 10A29;KHAROSHTHI LETTER YA;Lo;0;R;;;;;N;;;;; 10A2A;KHAROSHTHI LETTER RA;Lo;0;R;;;;;N;;;;; 10A2B;KHAROSHTHI LETTER LA;Lo;0;R;;;;;N;;;;; 10A2C;KHAROSHTHI LETTER VA;Lo;0;R;;;;;N;;;;; 10A2D;KHAROSHTHI LETTER SHA;Lo;0;R;;;;;N;;;;; 10A2E;KHAROSHTHI LETTER SSA;Lo;0;R;;;;;N;;;;; 10A2F;KHAROSHTHI LETTER SA;Lo;0;R;;;;;N;;;;; 10A30;KHAROSHTHI LETTER ZA;Lo;0;R;;;;;N;;;;; 10A31;KHAROSHTHI LETTER HA;Lo;0;R;;;;;N;;;;; 10A32;KHAROSHTHI LETTER KKA;Lo;0;R;;;;;N;;;;; 10A33;KHAROSHTHI LETTER TTTHA;Lo;0;R;;;;;N;;;;; 10A38;KHAROSHTHI SIGN BAR ABOVE;Mn;230;NSM;;;;;N;;;;; 10A39;KHAROSHTHI SIGN CAUDA;Mn;1;NSM;;;;;N;;;;; 10A3A;KHAROSHTHI SIGN DOT BELOW;Mn;220;NSM;;;;;N;;;;; 10A3F;KHAROSHTHI VIRAMA;Mn;9;NSM;;;;;N;;;;; 10A40;KHAROSHTHI DIGIT ONE;No;0;R;;;1;1;N;;;;; 10A41;KHAROSHTHI DIGIT TWO;No;0;R;;;2;2;N;;;;; 10A42;KHAROSHTHI DIGIT THREE;No;0;R;;;3;3;N;;;;; 10A43;KHAROSHTHI DIGIT FOUR;No;0;R;;;4;4;N;;;;; 10A44;KHAROSHTHI NUMBER TEN;No;0;R;;;;10;N;;;;; 10A45;KHAROSHTHI NUMBER TWENTY;No;0;R;;;;20;N;;;;; 10A46;KHAROSHTHI NUMBER ONE HUNDRED;No;0;R;;;;100;N;;;;; 10A47;KHAROSHTHI NUMBER ONE THOUSAND;No;0;R;;;;1000;N;;;;; 10A50;KHAROSHTHI PUNCTUATION DOT;Po;0;R;;;;;N;;;;; 10A51;KHAROSHTHI PUNCTUATION SMALL CIRCLE;Po;0;R;;;;;N;;;;; 10A52;KHAROSHTHI PUNCTUATION CIRCLE;Po;0;R;;;;;N;;;;; 10A53;KHAROSHTHI PUNCTUATION CRESCENT BAR;Po;0;R;;;;;N;;;;; 10A54;KHAROSHTHI PUNCTUATION MANGALAM;Po;0;R;;;;;N;;;;; 10A55;KHAROSHTHI PUNCTUATION LOTUS;Po;0;R;;;;;N;;;;; 10A56;KHAROSHTHI PUNCTUATION DANDA;Po;0;R;;;;;N;;;;; 10A57;KHAROSHTHI PUNCTUATION DOUBLE DANDA;Po;0;R;;;;;N;;;;; 10A58;KHAROSHTHI PUNCTUATION LINES;Po;0;R;;;;;N;;;;; 10A60;OLD SOUTH ARABIAN LETTER HE;Lo;0;R;;;;;N;;;;; 10A61;OLD SOUTH ARABIAN LETTER LAMEDH;Lo;0;R;;;;;N;;;;; 10A62;OLD SOUTH ARABIAN LETTER HETH;Lo;0;R;;;;;N;;;;; 10A63;OLD SOUTH ARABIAN LETTER MEM;Lo;0;R;;;;;N;;;;; 10A64;OLD SOUTH ARABIAN LETTER QOPH;Lo;0;R;;;;;N;;;;; 10A65;OLD SOUTH ARABIAN LETTER WAW;Lo;0;R;;;;;N;;;;; 10A66;OLD SOUTH ARABIAN LETTER SHIN;Lo;0;R;;;;;N;;;;; 10A67;OLD SOUTH ARABIAN LETTER RESH;Lo;0;R;;;;;N;;;;; 10A68;OLD SOUTH ARABIAN LETTER BETH;Lo;0;R;;;;;N;;;;; 10A69;OLD SOUTH ARABIAN LETTER TAW;Lo;0;R;;;;;N;;;;; 10A6A;OLD SOUTH ARABIAN LETTER SAT;Lo;0;R;;;;;N;;;;; 10A6B;OLD SOUTH ARABIAN LETTER KAPH;Lo;0;R;;;;;N;;;;; 10A6C;OLD SOUTH ARABIAN LETTER NUN;Lo;0;R;;;;;N;;;;; 10A6D;OLD SOUTH ARABIAN LETTER KHETH;Lo;0;R;;;;;N;;;;; 10A6E;OLD SOUTH ARABIAN LETTER SADHE;Lo;0;R;;;;;N;;;;; 10A6F;OLD SOUTH ARABIAN LETTER SAMEKH;Lo;0;R;;;;;N;;;;; 10A70;OLD SOUTH ARABIAN LETTER FE;Lo;0;R;;;;;N;;;;; 10A71;OLD SOUTH ARABIAN LETTER ALEF;Lo;0;R;;;;;N;;;;; 10A72;OLD SOUTH ARABIAN LETTER AYN;Lo;0;R;;;;;N;;;;; 10A73;OLD SOUTH ARABIAN LETTER DHADHE;Lo;0;R;;;;;N;;;;; 10A74;OLD SOUTH ARABIAN LETTER GIMEL;Lo;0;R;;;;;N;;;;; 10A75;OLD SOUTH ARABIAN LETTER DALETH;Lo;0;R;;;;;N;;;;; 10A76;OLD SOUTH ARABIAN LETTER GHAYN;Lo;0;R;;;;;N;;;;; 10A77;OLD SOUTH ARABIAN LETTER TETH;Lo;0;R;;;;;N;;;;; 10A78;OLD SOUTH ARABIAN LETTER ZAYN;Lo;0;R;;;;;N;;;;; 10A79;OLD SOUTH ARABIAN LETTER DHALETH;Lo;0;R;;;;;N;;;;; 10A7A;OLD SOUTH ARABIAN LETTER YODH;Lo;0;R;;;;;N;;;;; 10A7B;OLD SOUTH ARABIAN LETTER THAW;Lo;0;R;;;;;N;;;;; 10A7C;OLD SOUTH ARABIAN LETTER THETH;Lo;0;R;;;;;N;;;;; 10A7D;OLD SOUTH ARABIAN NUMBER ONE;No;0;R;;;;1;N;;;;; 10A7E;OLD SOUTH ARABIAN NUMBER FIFTY;No;0;R;;;;50;N;;;;; 10A7F;OLD SOUTH ARABIAN NUMERIC INDICATOR;Po;0;R;;;;;N;;;;; 10A80;OLD NORTH ARABIAN LETTER HEH;Lo;0;R;;;;;N;;;;; 10A81;OLD NORTH ARABIAN LETTER LAM;Lo;0;R;;;;;N;;;;; 10A82;OLD NORTH ARABIAN LETTER HAH;Lo;0;R;;;;;N;;;;; 10A83;OLD NORTH ARABIAN LETTER MEEM;Lo;0;R;;;;;N;;;;; 10A84;OLD NORTH ARABIAN LETTER QAF;Lo;0;R;;;;;N;;;;; 10A85;OLD NORTH ARABIAN LETTER WAW;Lo;0;R;;;;;N;;;;; 10A86;OLD NORTH ARABIAN LETTER ES-2;Lo;0;R;;;;;N;;;;; 10A87;OLD NORTH ARABIAN LETTER REH;Lo;0;R;;;;;N;;;;; 10A88;OLD NORTH ARABIAN LETTER BEH;Lo;0;R;;;;;N;;;;; 10A89;OLD NORTH ARABIAN LETTER TEH;Lo;0;R;;;;;N;;;;; 10A8A;OLD NORTH ARABIAN LETTER ES-1;Lo;0;R;;;;;N;;;;; 10A8B;OLD NORTH ARABIAN LETTER KAF;Lo;0;R;;;;;N;;;;; 10A8C;OLD NORTH ARABIAN LETTER NOON;Lo;0;R;;;;;N;;;;; 10A8D;OLD NORTH ARABIAN LETTER KHAH;Lo;0;R;;;;;N;;;;; 10A8E;OLD NORTH ARABIAN LETTER SAD;Lo;0;R;;;;;N;;;;; 10A8F;OLD NORTH ARABIAN LETTER ES-3;Lo;0;R;;;;;N;;;;; 10A90;OLD NORTH ARABIAN LETTER FEH;Lo;0;R;;;;;N;;;;; 10A91;OLD NORTH ARABIAN LETTER ALEF;Lo;0;R;;;;;N;;;;; 10A92;OLD NORTH ARABIAN LETTER AIN;Lo;0;R;;;;;N;;;;; 10A93;OLD NORTH ARABIAN LETTER DAD;Lo;0;R;;;;;N;;;;; 10A94;OLD NORTH ARABIAN LETTER GEEM;Lo;0;R;;;;;N;;;;; 10A95;OLD NORTH ARABIAN LETTER DAL;Lo;0;R;;;;;N;;;;; 10A96;OLD NORTH ARABIAN LETTER GHAIN;Lo;0;R;;;;;N;;;;; 10A97;OLD NORTH ARABIAN LETTER TAH;Lo;0;R;;;;;N;;;;; 10A98;OLD NORTH ARABIAN LETTER ZAIN;Lo;0;R;;;;;N;;;;; 10A99;OLD NORTH ARABIAN LETTER THAL;Lo;0;R;;;;;N;;;;; 10A9A;OLD NORTH ARABIAN LETTER YEH;Lo;0;R;;;;;N;;;;; 10A9B;OLD NORTH ARABIAN LETTER THEH;Lo;0;R;;;;;N;;;;; 10A9C;OLD NORTH ARABIAN LETTER ZAH;Lo;0;R;;;;;N;;;;; 10A9D;OLD NORTH ARABIAN NUMBER ONE;No;0;R;;;;1;N;;;;; 10A9E;OLD NORTH ARABIAN NUMBER TEN;No;0;R;;;;10;N;;;;; 10A9F;OLD NORTH ARABIAN NUMBER TWENTY;No;0;R;;;;20;N;;;;; 10AC0;MANICHAEAN LETTER ALEPH;Lo;0;R;;;;;N;;;;; 10AC1;MANICHAEAN LETTER BETH;Lo;0;R;;;;;N;;;;; 10AC2;MANICHAEAN LETTER BHETH;Lo;0;R;;;;;N;;;;; 10AC3;MANICHAEAN LETTER GIMEL;Lo;0;R;;;;;N;;;;; 10AC4;MANICHAEAN LETTER GHIMEL;Lo;0;R;;;;;N;;;;; 10AC5;MANICHAEAN LETTER DALETH;Lo;0;R;;;;;N;;;;; 10AC6;MANICHAEAN LETTER HE;Lo;0;R;;;;;N;;;;; 10AC7;MANICHAEAN LETTER WAW;Lo;0;R;;;;;N;;;;; 10AC8;MANICHAEAN SIGN UD;So;0;R;;;;;N;;;;; 10AC9;MANICHAEAN LETTER ZAYIN;Lo;0;R;;;;;N;;;;; 10ACA;MANICHAEAN LETTER ZHAYIN;Lo;0;R;;;;;N;;;;; 10ACB;MANICHAEAN LETTER JAYIN;Lo;0;R;;;;;N;;;;; 10ACC;MANICHAEAN LETTER JHAYIN;Lo;0;R;;;;;N;;;;; 10ACD;MANICHAEAN LETTER HETH;Lo;0;R;;;;;N;;;;; 10ACE;MANICHAEAN LETTER TETH;Lo;0;R;;;;;N;;;;; 10ACF;MANICHAEAN LETTER YODH;Lo;0;R;;;;;N;;;;; 10AD0;MANICHAEAN LETTER KAPH;Lo;0;R;;;;;N;;;;; 10AD1;MANICHAEAN LETTER XAPH;Lo;0;R;;;;;N;;;;; 10AD2;MANICHAEAN LETTER KHAPH;Lo;0;R;;;;;N;;;;; 10AD3;MANICHAEAN LETTER LAMEDH;Lo;0;R;;;;;N;;;;; 10AD4;MANICHAEAN LETTER DHAMEDH;Lo;0;R;;;;;N;;;;; 10AD5;MANICHAEAN LETTER THAMEDH;Lo;0;R;;;;;N;;;;; 10AD6;MANICHAEAN LETTER MEM;Lo;0;R;;;;;N;;;;; 10AD7;MANICHAEAN LETTER NUN;Lo;0;R;;;;;N;;;;; 10AD8;MANICHAEAN LETTER SAMEKH;Lo;0;R;;;;;N;;;;; 10AD9;MANICHAEAN LETTER AYIN;Lo;0;R;;;;;N;;;;; 10ADA;MANICHAEAN LETTER AAYIN;Lo;0;R;;;;;N;;;;; 10ADB;MANICHAEAN LETTER PE;Lo;0;R;;;;;N;;;;; 10ADC;MANICHAEAN LETTER FE;Lo;0;R;;;;;N;;;;; 10ADD;MANICHAEAN LETTER SADHE;Lo;0;R;;;;;N;;;;; 10ADE;MANICHAEAN LETTER QOPH;Lo;0;R;;;;;N;;;;; 10ADF;MANICHAEAN LETTER XOPH;Lo;0;R;;;;;N;;;;; 10AE0;MANICHAEAN LETTER QHOPH;Lo;0;R;;;;;N;;;;; 10AE1;MANICHAEAN LETTER RESH;Lo;0;R;;;;;N;;;;; 10AE2;MANICHAEAN LETTER SHIN;Lo;0;R;;;;;N;;;;; 10AE3;MANICHAEAN LETTER SSHIN;Lo;0;R;;;;;N;;;;; 10AE4;MANICHAEAN LETTER TAW;Lo;0;R;;;;;N;;;;; 10AE5;MANICHAEAN ABBREVIATION MARK ABOVE;Mn;230;NSM;;;;;N;;;;; 10AE6;MANICHAEAN ABBREVIATION MARK BELOW;Mn;220;NSM;;;;;N;;;;; 10AEB;MANICHAEAN NUMBER ONE;No;0;R;;;;1;N;;;;; 10AEC;MANICHAEAN NUMBER FIVE;No;0;R;;;;5;N;;;;; 10AED;MANICHAEAN NUMBER TEN;No;0;R;;;;10;N;;;;; 10AEE;MANICHAEAN NUMBER TWENTY;No;0;R;;;;20;N;;;;; 10AEF;MANICHAEAN NUMBER ONE HUNDRED;No;0;R;;;;100;N;;;;; 10AF0;MANICHAEAN PUNCTUATION STAR;Po;0;R;;;;;N;;;;; 10AF1;MANICHAEAN PUNCTUATION FLEURON;Po;0;R;;;;;N;;;;; 10AF2;MANICHAEAN PUNCTUATION DOUBLE DOT WITHIN DOT;Po;0;R;;;;;N;;;;; 10AF3;MANICHAEAN PUNCTUATION DOT WITHIN DOT;Po;0;R;;;;;N;;;;; 10AF4;MANICHAEAN PUNCTUATION DOT;Po;0;R;;;;;N;;;;; 10AF5;MANICHAEAN PUNCTUATION TWO DOTS;Po;0;R;;;;;N;;;;; 10AF6;MANICHAEAN PUNCTUATION LINE FILLER;Po;0;R;;;;;N;;;;; 10B00;AVESTAN LETTER A;Lo;0;R;;;;;N;;;;; 10B01;AVESTAN LETTER AA;Lo;0;R;;;;;N;;;;; 10B02;AVESTAN LETTER AO;Lo;0;R;;;;;N;;;;; 10B03;AVESTAN LETTER AAO;Lo;0;R;;;;;N;;;;; 10B04;AVESTAN LETTER AN;Lo;0;R;;;;;N;;;;; 10B05;AVESTAN LETTER AAN;Lo;0;R;;;;;N;;;;; 10B06;AVESTAN LETTER AE;Lo;0;R;;;;;N;;;;; 10B07;AVESTAN LETTER AEE;Lo;0;R;;;;;N;;;;; 10B08;AVESTAN LETTER E;Lo;0;R;;;;;N;;;;; 10B09;AVESTAN LETTER EE;Lo;0;R;;;;;N;;;;; 10B0A;AVESTAN LETTER O;Lo;0;R;;;;;N;;;;; 10B0B;AVESTAN LETTER OO;Lo;0;R;;;;;N;;;;; 10B0C;AVESTAN LETTER I;Lo;0;R;;;;;N;;;;; 10B0D;AVESTAN LETTER II;Lo;0;R;;;;;N;;;;; 10B0E;AVESTAN LETTER U;Lo;0;R;;;;;N;;;;; 10B0F;AVESTAN LETTER UU;Lo;0;R;;;;;N;;;;; 10B10;AVESTAN LETTER KE;Lo;0;R;;;;;N;;;;; 10B11;AVESTAN LETTER XE;Lo;0;R;;;;;N;;;;; 10B12;AVESTAN LETTER XYE;Lo;0;R;;;;;N;;;;; 10B13;AVESTAN LETTER XVE;Lo;0;R;;;;;N;;;;; 10B14;AVESTAN LETTER GE;Lo;0;R;;;;;N;;;;; 10B15;AVESTAN LETTER GGE;Lo;0;R;;;;;N;;;;; 10B16;AVESTAN LETTER GHE;Lo;0;R;;;;;N;;;;; 10B17;AVESTAN LETTER CE;Lo;0;R;;;;;N;;;;; 10B18;AVESTAN LETTER JE;Lo;0;R;;;;;N;;;;; 10B19;AVESTAN LETTER TE;Lo;0;R;;;;;N;;;;; 10B1A;AVESTAN LETTER THE;Lo;0;R;;;;;N;;;;; 10B1B;AVESTAN LETTER DE;Lo;0;R;;;;;N;;;;; 10B1C;AVESTAN LETTER DHE;Lo;0;R;;;;;N;;;;; 10B1D;AVESTAN LETTER TTE;Lo;0;R;;;;;N;;;;; 10B1E;AVESTAN LETTER PE;Lo;0;R;;;;;N;;;;; 10B1F;AVESTAN LETTER FE;Lo;0;R;;;;;N;;;;; 10B20;AVESTAN LETTER BE;Lo;0;R;;;;;N;;;;; 10B21;AVESTAN LETTER BHE;Lo;0;R;;;;;N;;;;; 10B22;AVESTAN LETTER NGE;Lo;0;R;;;;;N;;;;; 10B23;AVESTAN LETTER NGYE;Lo;0;R;;;;;N;;;;; 10B24;AVESTAN LETTER NGVE;Lo;0;R;;;;;N;;;;; 10B25;AVESTAN LETTER NE;Lo;0;R;;;;;N;;;;; 10B26;AVESTAN LETTER NYE;Lo;0;R;;;;;N;;;;; 10B27;AVESTAN LETTER NNE;Lo;0;R;;;;;N;;;;; 10B28;AVESTAN LETTER ME;Lo;0;R;;;;;N;;;;; 10B29;AVESTAN LETTER HME;Lo;0;R;;;;;N;;;;; 10B2A;AVESTAN LETTER YYE;Lo;0;R;;;;;N;;;;; 10B2B;AVESTAN LETTER YE;Lo;0;R;;;;;N;;;;; 10B2C;AVESTAN LETTER VE;Lo;0;R;;;;;N;;;;; 10B2D;AVESTAN LETTER RE;Lo;0;R;;;;;N;;;;; 10B2E;AVESTAN LETTER LE;Lo;0;R;;;;;N;;;;; 10B2F;AVESTAN LETTER SE;Lo;0;R;;;;;N;;;;; 10B30;AVESTAN LETTER ZE;Lo;0;R;;;;;N;;;;; 10B31;AVESTAN LETTER SHE;Lo;0;R;;;;;N;;;;; 10B32;AVESTAN LETTER ZHE;Lo;0;R;;;;;N;;;;; 10B33;AVESTAN LETTER SHYE;Lo;0;R;;;;;N;;;;; 10B34;AVESTAN LETTER SSHE;Lo;0;R;;;;;N;;;;; 10B35;AVESTAN LETTER HE;Lo;0;R;;;;;N;;;;; 10B39;AVESTAN ABBREVIATION MARK;Po;0;ON;;;;;N;;;;; 10B3A;TINY TWO DOTS OVER ONE DOT PUNCTUATION;Po;0;ON;;;;;N;;;;; 10B3B;SMALL TWO DOTS OVER ONE DOT PUNCTUATION;Po;0;ON;;;;;N;;;;; 10B3C;LARGE TWO DOTS OVER ONE DOT PUNCTUATION;Po;0;ON;;;;;N;;;;; 10B3D;LARGE ONE DOT OVER TWO DOTS PUNCTUATION;Po;0;ON;;;;;N;;;;; 10B3E;LARGE TWO RINGS OVER ONE RING PUNCTUATION;Po;0;ON;;;;;N;;;;; 10B3F;LARGE ONE RING OVER TWO RINGS PUNCTUATION;Po;0;ON;;;;;N;;;;; 10B40;INSCRIPTIONAL PARTHIAN LETTER ALEPH;Lo;0;R;;;;;N;;;;; 10B41;INSCRIPTIONAL PARTHIAN LETTER BETH;Lo;0;R;;;;;N;;;;; 10B42;INSCRIPTIONAL PARTHIAN LETTER GIMEL;Lo;0;R;;;;;N;;;;; 10B43;INSCRIPTIONAL PARTHIAN LETTER DALETH;Lo;0;R;;;;;N;;;;; 10B44;INSCRIPTIONAL PARTHIAN LETTER HE;Lo;0;R;;;;;N;;;;; 10B45;INSCRIPTIONAL PARTHIAN LETTER WAW;Lo;0;R;;;;;N;;;;; 10B46;INSCRIPTIONAL PARTHIAN LETTER ZAYIN;Lo;0;R;;;;;N;;;;; 10B47;INSCRIPTIONAL PARTHIAN LETTER HETH;Lo;0;R;;;;;N;;;;; 10B48;INSCRIPTIONAL PARTHIAN LETTER TETH;Lo;0;R;;;;;N;;;;; 10B49;INSCRIPTIONAL PARTHIAN LETTER YODH;Lo;0;R;;;;;N;;;;; 10B4A;INSCRIPTIONAL PARTHIAN LETTER KAPH;Lo;0;R;;;;;N;;;;; 10B4B;INSCRIPTIONAL PARTHIAN LETTER LAMEDH;Lo;0;R;;;;;N;;;;; 10B4C;INSCRIPTIONAL PARTHIAN LETTER MEM;Lo;0;R;;;;;N;;;;; 10B4D;INSCRIPTIONAL PARTHIAN LETTER NUN;Lo;0;R;;;;;N;;;;; 10B4E;INSCRIPTIONAL PARTHIAN LETTER SAMEKH;Lo;0;R;;;;;N;;;;; 10B4F;INSCRIPTIONAL PARTHIAN LETTER AYIN;Lo;0;R;;;;;N;;;;; 10B50;INSCRIPTIONAL PARTHIAN LETTER PE;Lo;0;R;;;;;N;;;;; 10B51;INSCRIPTIONAL PARTHIAN LETTER SADHE;Lo;0;R;;;;;N;;;;; 10B52;INSCRIPTIONAL PARTHIAN LETTER QOPH;Lo;0;R;;;;;N;;;;; 10B53;INSCRIPTIONAL PARTHIAN LETTER RESH;Lo;0;R;;;;;N;;;;; 10B54;INSCRIPTIONAL PARTHIAN LETTER SHIN;Lo;0;R;;;;;N;;;;; 10B55;INSCRIPTIONAL PARTHIAN LETTER TAW;Lo;0;R;;;;;N;;;;; 10B58;INSCRIPTIONAL PARTHIAN NUMBER ONE;No;0;R;;;;1;N;;;;; 10B59;INSCRIPTIONAL PARTHIAN NUMBER TWO;No;0;R;;;;2;N;;;;; 10B5A;INSCRIPTIONAL PARTHIAN NUMBER THREE;No;0;R;;;;3;N;;;;; 10B5B;INSCRIPTIONAL PARTHIAN NUMBER FOUR;No;0;R;;;;4;N;;;;; 10B5C;INSCRIPTIONAL PARTHIAN NUMBER TEN;No;0;R;;;;10;N;;;;; 10B5D;INSCRIPTIONAL PARTHIAN NUMBER TWENTY;No;0;R;;;;20;N;;;;; 10B5E;INSCRIPTIONAL PARTHIAN NUMBER ONE HUNDRED;No;0;R;;;;100;N;;;;; 10B5F;INSCRIPTIONAL PARTHIAN NUMBER ONE THOUSAND;No;0;R;;;;1000;N;;;;; 10B60;INSCRIPTIONAL PAHLAVI LETTER ALEPH;Lo;0;R;;;;;N;;;;; 10B61;INSCRIPTIONAL PAHLAVI LETTER BETH;Lo;0;R;;;;;N;;;;; 10B62;INSCRIPTIONAL PAHLAVI LETTER GIMEL;Lo;0;R;;;;;N;;;;; 10B63;INSCRIPTIONAL PAHLAVI LETTER DALETH;Lo;0;R;;;;;N;;;;; 10B64;INSCRIPTIONAL PAHLAVI LETTER HE;Lo;0;R;;;;;N;;;;; 10B65;INSCRIPTIONAL PAHLAVI LETTER WAW-AYIN-RESH;Lo;0;R;;;;;N;;;;; 10B66;INSCRIPTIONAL PAHLAVI LETTER ZAYIN;Lo;0;R;;;;;N;;;;; 10B67;INSCRIPTIONAL PAHLAVI LETTER HETH;Lo;0;R;;;;;N;;;;; 10B68;INSCRIPTIONAL PAHLAVI LETTER TETH;Lo;0;R;;;;;N;;;;; 10B69;INSCRIPTIONAL PAHLAVI LETTER YODH;Lo;0;R;;;;;N;;;;; 10B6A;INSCRIPTIONAL PAHLAVI LETTER KAPH;Lo;0;R;;;;;N;;;;; 10B6B;INSCRIPTIONAL PAHLAVI LETTER LAMEDH;Lo;0;R;;;;;N;;;;; 10B6C;INSCRIPTIONAL PAHLAVI LETTER MEM-QOPH;Lo;0;R;;;;;N;;;;; 10B6D;INSCRIPTIONAL PAHLAVI LETTER NUN;Lo;0;R;;;;;N;;;;; 10B6E;INSCRIPTIONAL PAHLAVI LETTER SAMEKH;Lo;0;R;;;;;N;;;;; 10B6F;INSCRIPTIONAL PAHLAVI LETTER PE;Lo;0;R;;;;;N;;;;; 10B70;INSCRIPTIONAL PAHLAVI LETTER SADHE;Lo;0;R;;;;;N;;;;; 10B71;INSCRIPTIONAL PAHLAVI LETTER SHIN;Lo;0;R;;;;;N;;;;; 10B72;INSCRIPTIONAL PAHLAVI LETTER TAW;Lo;0;R;;;;;N;;;;; 10B78;INSCRIPTIONAL PAHLAVI NUMBER ONE;No;0;R;;;;1;N;;;;; 10B79;INSCRIPTIONAL PAHLAVI NUMBER TWO;No;0;R;;;;2;N;;;;; 10B7A;INSCRIPTIONAL PAHLAVI NUMBER THREE;No;0;R;;;;3;N;;;;; 10B7B;INSCRIPTIONAL PAHLAVI NUMBER FOUR;No;0;R;;;;4;N;;;;; 10B7C;INSCRIPTIONAL PAHLAVI NUMBER TEN;No;0;R;;;;10;N;;;;; 10B7D;INSCRIPTIONAL PAHLAVI NUMBER TWENTY;No;0;R;;;;20;N;;;;; 10B7E;INSCRIPTIONAL PAHLAVI NUMBER ONE HUNDRED;No;0;R;;;;100;N;;;;; 10B7F;INSCRIPTIONAL PAHLAVI NUMBER ONE THOUSAND;No;0;R;;;;1000;N;;;;; 10B80;PSALTER PAHLAVI LETTER ALEPH;Lo;0;R;;;;;N;;;;; 10B81;PSALTER PAHLAVI LETTER BETH;Lo;0;R;;;;;N;;;;; 10B82;PSALTER PAHLAVI LETTER GIMEL;Lo;0;R;;;;;N;;;;; 10B83;PSALTER PAHLAVI LETTER DALETH;Lo;0;R;;;;;N;;;;; 10B84;PSALTER PAHLAVI LETTER HE;Lo;0;R;;;;;N;;;;; 10B85;PSALTER PAHLAVI LETTER WAW-AYIN-RESH;Lo;0;R;;;;;N;;;;; 10B86;PSALTER PAHLAVI LETTER ZAYIN;Lo;0;R;;;;;N;;;;; 10B87;PSALTER PAHLAVI LETTER HETH;Lo;0;R;;;;;N;;;;; 10B88;PSALTER PAHLAVI LETTER YODH;Lo;0;R;;;;;N;;;;; 10B89;PSALTER PAHLAVI LETTER KAPH;Lo;0;R;;;;;N;;;;; 10B8A;PSALTER PAHLAVI LETTER LAMEDH;Lo;0;R;;;;;N;;;;; 10B8B;PSALTER PAHLAVI LETTER MEM-QOPH;Lo;0;R;;;;;N;;;;; 10B8C;PSALTER PAHLAVI LETTER NUN;Lo;0;R;;;;;N;;;;; 10B8D;PSALTER PAHLAVI LETTER SAMEKH;Lo;0;R;;;;;N;;;;; 10B8E;PSALTER PAHLAVI LETTER PE;Lo;0;R;;;;;N;;;;; 10B8F;PSALTER PAHLAVI LETTER SADHE;Lo;0;R;;;;;N;;;;; 10B90;PSALTER PAHLAVI LETTER SHIN;Lo;0;R;;;;;N;;;;; 10B91;PSALTER PAHLAVI LETTER TAW;Lo;0;R;;;;;N;;;;; 10B99;PSALTER PAHLAVI SECTION MARK;Po;0;R;;;;;N;;;;; 10B9A;PSALTER PAHLAVI TURNED SECTION MARK;Po;0;R;;;;;N;;;;; 10B9B;PSALTER PAHLAVI FOUR DOTS WITH CROSS;Po;0;R;;;;;N;;;;; 10B9C;PSALTER PAHLAVI FOUR DOTS WITH DOT;Po;0;R;;;;;N;;;;; 10BA9;PSALTER PAHLAVI NUMBER ONE;No;0;R;;;;1;N;;;;; 10BAA;PSALTER PAHLAVI NUMBER TWO;No;0;R;;;;2;N;;;;; 10BAB;PSALTER PAHLAVI NUMBER THREE;No;0;R;;;;3;N;;;;; 10BAC;PSALTER PAHLAVI NUMBER FOUR;No;0;R;;;;4;N;;;;; 10BAD;PSALTER PAHLAVI NUMBER TEN;No;0;R;;;;10;N;;;;; 10BAE;PSALTER PAHLAVI NUMBER TWENTY;No;0;R;;;;20;N;;;;; 10BAF;PSALTER PAHLAVI NUMBER ONE HUNDRED;No;0;R;;;;100;N;;;;; 10C00;OLD TURKIC LETTER ORKHON A;Lo;0;R;;;;;N;;;;; 10C01;OLD TURKIC LETTER YENISEI A;Lo;0;R;;;;;N;;;;; 10C02;OLD TURKIC LETTER YENISEI AE;Lo;0;R;;;;;N;;;;; 10C03;OLD TURKIC LETTER ORKHON I;Lo;0;R;;;;;N;;;;; 10C04;OLD TURKIC LETTER YENISEI I;Lo;0;R;;;;;N;;;;; 10C05;OLD TURKIC LETTER YENISEI E;Lo;0;R;;;;;N;;;;; 10C06;OLD TURKIC LETTER ORKHON O;Lo;0;R;;;;;N;;;;; 10C07;OLD TURKIC LETTER ORKHON OE;Lo;0;R;;;;;N;;;;; 10C08;OLD TURKIC LETTER YENISEI OE;Lo;0;R;;;;;N;;;;; 10C09;OLD TURKIC LETTER ORKHON AB;Lo;0;R;;;;;N;;;;; 10C0A;OLD TURKIC LETTER YENISEI AB;Lo;0;R;;;;;N;;;;; 10C0B;OLD TURKIC LETTER ORKHON AEB;Lo;0;R;;;;;N;;;;; 10C0C;OLD TURKIC LETTER YENISEI AEB;Lo;0;R;;;;;N;;;;; 10C0D;OLD TURKIC LETTER ORKHON AG;Lo;0;R;;;;;N;;;;; 10C0E;OLD TURKIC LETTER YENISEI AG;Lo;0;R;;;;;N;;;;; 10C0F;OLD TURKIC LETTER ORKHON AEG;Lo;0;R;;;;;N;;;;; 10C10;OLD TURKIC LETTER YENISEI AEG;Lo;0;R;;;;;N;;;;; 10C11;OLD TURKIC LETTER ORKHON AD;Lo;0;R;;;;;N;;;;; 10C12;OLD TURKIC LETTER YENISEI AD;Lo;0;R;;;;;N;;;;; 10C13;OLD TURKIC LETTER ORKHON AED;Lo;0;R;;;;;N;;;;; 10C14;OLD TURKIC LETTER ORKHON EZ;Lo;0;R;;;;;N;;;;; 10C15;OLD TURKIC LETTER YENISEI EZ;Lo;0;R;;;;;N;;;;; 10C16;OLD TURKIC LETTER ORKHON AY;Lo;0;R;;;;;N;;;;; 10C17;OLD TURKIC LETTER YENISEI AY;Lo;0;R;;;;;N;;;;; 10C18;OLD TURKIC LETTER ORKHON AEY;Lo;0;R;;;;;N;;;;; 10C19;OLD TURKIC LETTER YENISEI AEY;Lo;0;R;;;;;N;;;;; 10C1A;OLD TURKIC LETTER ORKHON AEK;Lo;0;R;;;;;N;;;;; 10C1B;OLD TURKIC LETTER YENISEI AEK;Lo;0;R;;;;;N;;;;; 10C1C;OLD TURKIC LETTER ORKHON OEK;Lo;0;R;;;;;N;;;;; 10C1D;OLD TURKIC LETTER YENISEI OEK;Lo;0;R;;;;;N;;;;; 10C1E;OLD TURKIC LETTER ORKHON AL;Lo;0;R;;;;;N;;;;; 10C1F;OLD TURKIC LETTER YENISEI AL;Lo;0;R;;;;;N;;;;; 10C20;OLD TURKIC LETTER ORKHON AEL;Lo;0;R;;;;;N;;;;; 10C21;OLD TURKIC LETTER ORKHON ELT;Lo;0;R;;;;;N;;;;; 10C22;OLD TURKIC LETTER ORKHON EM;Lo;0;R;;;;;N;;;;; 10C23;OLD TURKIC LETTER ORKHON AN;Lo;0;R;;;;;N;;;;; 10C24;OLD TURKIC LETTER ORKHON AEN;Lo;0;R;;;;;N;;;;; 10C25;OLD TURKIC LETTER YENISEI AEN;Lo;0;R;;;;;N;;;;; 10C26;OLD TURKIC LETTER ORKHON ENT;Lo;0;R;;;;;N;;;;; 10C27;OLD TURKIC LETTER YENISEI ENT;Lo;0;R;;;;;N;;;;; 10C28;OLD TURKIC LETTER ORKHON ENC;Lo;0;R;;;;;N;;;;; 10C29;OLD TURKIC LETTER YENISEI ENC;Lo;0;R;;;;;N;;;;; 10C2A;OLD TURKIC LETTER ORKHON ENY;Lo;0;R;;;;;N;;;;; 10C2B;OLD TURKIC LETTER YENISEI ENY;Lo;0;R;;;;;N;;;;; 10C2C;OLD TURKIC LETTER YENISEI ANG;Lo;0;R;;;;;N;;;;; 10C2D;OLD TURKIC LETTER ORKHON ENG;Lo;0;R;;;;;N;;;;; 10C2E;OLD TURKIC LETTER YENISEI AENG;Lo;0;R;;;;;N;;;;; 10C2F;OLD TURKIC LETTER ORKHON EP;Lo;0;R;;;;;N;;;;; 10C30;OLD TURKIC LETTER ORKHON OP;Lo;0;R;;;;;N;;;;; 10C31;OLD TURKIC LETTER ORKHON IC;Lo;0;R;;;;;N;;;;; 10C32;OLD TURKIC LETTER ORKHON EC;Lo;0;R;;;;;N;;;;; 10C33;OLD TURKIC LETTER YENISEI EC;Lo;0;R;;;;;N;;;;; 10C34;OLD TURKIC LETTER ORKHON AQ;Lo;0;R;;;;;N;;;;; 10C35;OLD TURKIC LETTER YENISEI AQ;Lo;0;R;;;;;N;;;;; 10C36;OLD TURKIC LETTER ORKHON IQ;Lo;0;R;;;;;N;;;;; 10C37;OLD TURKIC LETTER YENISEI IQ;Lo;0;R;;;;;N;;;;; 10C38;OLD TURKIC LETTER ORKHON OQ;Lo;0;R;;;;;N;;;;; 10C39;OLD TURKIC LETTER YENISEI OQ;Lo;0;R;;;;;N;;;;; 10C3A;OLD TURKIC LETTER ORKHON AR;Lo;0;R;;;;;N;;;;; 10C3B;OLD TURKIC LETTER YENISEI AR;Lo;0;R;;;;;N;;;;; 10C3C;OLD TURKIC LETTER ORKHON AER;Lo;0;R;;;;;N;;;;; 10C3D;OLD TURKIC LETTER ORKHON AS;Lo;0;R;;;;;N;;;;; 10C3E;OLD TURKIC LETTER ORKHON AES;Lo;0;R;;;;;N;;;;; 10C3F;OLD TURKIC LETTER ORKHON ASH;Lo;0;R;;;;;N;;;;; 10C40;OLD TURKIC LETTER YENISEI ASH;Lo;0;R;;;;;N;;;;; 10C41;OLD TURKIC LETTER ORKHON ESH;Lo;0;R;;;;;N;;;;; 10C42;OLD TURKIC LETTER YENISEI ESH;Lo;0;R;;;;;N;;;;; 10C43;OLD TURKIC LETTER ORKHON AT;Lo;0;R;;;;;N;;;;; 10C44;OLD TURKIC LETTER YENISEI AT;Lo;0;R;;;;;N;;;;; 10C45;OLD TURKIC LETTER ORKHON AET;Lo;0;R;;;;;N;;;;; 10C46;OLD TURKIC LETTER YENISEI AET;Lo;0;R;;;;;N;;;;; 10C47;OLD TURKIC LETTER ORKHON OT;Lo;0;R;;;;;N;;;;; 10C48;OLD TURKIC LETTER ORKHON BASH;Lo;0;R;;;;;N;;;;; 10C80;OLD HUNGARIAN CAPITAL LETTER A;Lu;0;R;;;;;N;;;;10CC0; 10C81;OLD HUNGARIAN CAPITAL LETTER AA;Lu;0;R;;;;;N;;;;10CC1; 10C82;OLD HUNGARIAN CAPITAL LETTER EB;Lu;0;R;;;;;N;;;;10CC2; 10C83;OLD HUNGARIAN CAPITAL LETTER AMB;Lu;0;R;;;;;N;;;;10CC3; 10C84;OLD HUNGARIAN CAPITAL LETTER EC;Lu;0;R;;;;;N;;;;10CC4; 10C85;OLD HUNGARIAN CAPITAL LETTER ENC;Lu;0;R;;;;;N;;;;10CC5; 10C86;OLD HUNGARIAN CAPITAL LETTER ECS;Lu;0;R;;;;;N;;;;10CC6; 10C87;OLD HUNGARIAN CAPITAL LETTER ED;Lu;0;R;;;;;N;;;;10CC7; 10C88;OLD HUNGARIAN CAPITAL LETTER AND;Lu;0;R;;;;;N;;;;10CC8; 10C89;OLD HUNGARIAN CAPITAL LETTER E;Lu;0;R;;;;;N;;;;10CC9; 10C8A;OLD HUNGARIAN CAPITAL LETTER CLOSE E;Lu;0;R;;;;;N;;;;10CCA; 10C8B;OLD HUNGARIAN CAPITAL LETTER EE;Lu;0;R;;;;;N;;;;10CCB; 10C8C;OLD HUNGARIAN CAPITAL LETTER EF;Lu;0;R;;;;;N;;;;10CCC; 10C8D;OLD HUNGARIAN CAPITAL LETTER EG;Lu;0;R;;;;;N;;;;10CCD; 10C8E;OLD HUNGARIAN CAPITAL LETTER EGY;Lu;0;R;;;;;N;;;;10CCE; 10C8F;OLD HUNGARIAN CAPITAL LETTER EH;Lu;0;R;;;;;N;;;;10CCF; 10C90;OLD HUNGARIAN CAPITAL LETTER I;Lu;0;R;;;;;N;;;;10CD0; 10C91;OLD HUNGARIAN CAPITAL LETTER II;Lu;0;R;;;;;N;;;;10CD1; 10C92;OLD HUNGARIAN CAPITAL LETTER EJ;Lu;0;R;;;;;N;;;;10CD2; 10C93;OLD HUNGARIAN CAPITAL LETTER EK;Lu;0;R;;;;;N;;;;10CD3; 10C94;OLD HUNGARIAN CAPITAL LETTER AK;Lu;0;R;;;;;N;;;;10CD4; 10C95;OLD HUNGARIAN CAPITAL LETTER UNK;Lu;0;R;;;;;N;;;;10CD5; 10C96;OLD HUNGARIAN CAPITAL LETTER EL;Lu;0;R;;;;;N;;;;10CD6; 10C97;OLD HUNGARIAN CAPITAL LETTER ELY;Lu;0;R;;;;;N;;;;10CD7; 10C98;OLD HUNGARIAN CAPITAL LETTER EM;Lu;0;R;;;;;N;;;;10CD8; 10C99;OLD HUNGARIAN CAPITAL LETTER EN;Lu;0;R;;;;;N;;;;10CD9; 10C9A;OLD HUNGARIAN CAPITAL LETTER ENY;Lu;0;R;;;;;N;;;;10CDA; 10C9B;OLD HUNGARIAN CAPITAL LETTER O;Lu;0;R;;;;;N;;;;10CDB; 10C9C;OLD HUNGARIAN CAPITAL LETTER OO;Lu;0;R;;;;;N;;;;10CDC; 10C9D;OLD HUNGARIAN CAPITAL LETTER NIKOLSBURG OE;Lu;0;R;;;;;N;;;;10CDD; 10C9E;OLD HUNGARIAN CAPITAL LETTER RUDIMENTA OE;Lu;0;R;;;;;N;;;;10CDE; 10C9F;OLD HUNGARIAN CAPITAL LETTER OEE;Lu;0;R;;;;;N;;;;10CDF; 10CA0;OLD HUNGARIAN CAPITAL LETTER EP;Lu;0;R;;;;;N;;;;10CE0; 10CA1;OLD HUNGARIAN CAPITAL LETTER EMP;Lu;0;R;;;;;N;;;;10CE1; 10CA2;OLD HUNGARIAN CAPITAL LETTER ER;Lu;0;R;;;;;N;;;;10CE2; 10CA3;OLD HUNGARIAN CAPITAL LETTER SHORT ER;Lu;0;R;;;;;N;;;;10CE3; 10CA4;OLD HUNGARIAN CAPITAL LETTER ES;Lu;0;R;;;;;N;;;;10CE4; 10CA5;OLD HUNGARIAN CAPITAL LETTER ESZ;Lu;0;R;;;;;N;;;;10CE5; 10CA6;OLD HUNGARIAN CAPITAL LETTER ET;Lu;0;R;;;;;N;;;;10CE6; 10CA7;OLD HUNGARIAN CAPITAL LETTER ENT;Lu;0;R;;;;;N;;;;10CE7; 10CA8;OLD HUNGARIAN CAPITAL LETTER ETY;Lu;0;R;;;;;N;;;;10CE8; 10CA9;OLD HUNGARIAN CAPITAL LETTER ECH;Lu;0;R;;;;;N;;;;10CE9; 10CAA;OLD HUNGARIAN CAPITAL LETTER U;Lu;0;R;;;;;N;;;;10CEA; 10CAB;OLD HUNGARIAN CAPITAL LETTER UU;Lu;0;R;;;;;N;;;;10CEB; 10CAC;OLD HUNGARIAN CAPITAL LETTER NIKOLSBURG UE;Lu;0;R;;;;;N;;;;10CEC; 10CAD;OLD HUNGARIAN CAPITAL LETTER RUDIMENTA UE;Lu;0;R;;;;;N;;;;10CED; 10CAE;OLD HUNGARIAN CAPITAL LETTER EV;Lu;0;R;;;;;N;;;;10CEE; 10CAF;OLD HUNGARIAN CAPITAL LETTER EZ;Lu;0;R;;;;;N;;;;10CEF; 10CB0;OLD HUNGARIAN CAPITAL LETTER EZS;Lu;0;R;;;;;N;;;;10CF0; 10CB1;OLD HUNGARIAN CAPITAL LETTER ENT-SHAPED SIGN;Lu;0;R;;;;;N;;;;10CF1; 10CB2;OLD HUNGARIAN CAPITAL LETTER US;Lu;0;R;;;;;N;;;;10CF2; 10CC0;OLD HUNGARIAN SMALL LETTER A;Ll;0;R;;;;;N;;;10C80;;10C80 10CC1;OLD HUNGARIAN SMALL LETTER AA;Ll;0;R;;;;;N;;;10C81;;10C81 10CC2;OLD HUNGARIAN SMALL LETTER EB;Ll;0;R;;;;;N;;;10C82;;10C82 10CC3;OLD HUNGARIAN SMALL LETTER AMB;Ll;0;R;;;;;N;;;10C83;;10C83 10CC4;OLD HUNGARIAN SMALL LETTER EC;Ll;0;R;;;;;N;;;10C84;;10C84 10CC5;OLD HUNGARIAN SMALL LETTER ENC;Ll;0;R;;;;;N;;;10C85;;10C85 10CC6;OLD HUNGARIAN SMALL LETTER ECS;Ll;0;R;;;;;N;;;10C86;;10C86 10CC7;OLD HUNGARIAN SMALL LETTER ED;Ll;0;R;;;;;N;;;10C87;;10C87 10CC8;OLD HUNGARIAN SMALL LETTER AND;Ll;0;R;;;;;N;;;10C88;;10C88 10CC9;OLD HUNGARIAN SMALL LETTER E;Ll;0;R;;;;;N;;;10C89;;10C89 10CCA;OLD HUNGARIAN SMALL LETTER CLOSE E;Ll;0;R;;;;;N;;;10C8A;;10C8A 10CCB;OLD HUNGARIAN SMALL LETTER EE;Ll;0;R;;;;;N;;;10C8B;;10C8B 10CCC;OLD HUNGARIAN SMALL LETTER EF;Ll;0;R;;;;;N;;;10C8C;;10C8C 10CCD;OLD HUNGARIAN SMALL LETTER EG;Ll;0;R;;;;;N;;;10C8D;;10C8D 10CCE;OLD HUNGARIAN SMALL LETTER EGY;Ll;0;R;;;;;N;;;10C8E;;10C8E 10CCF;OLD HUNGARIAN SMALL LETTER EH;Ll;0;R;;;;;N;;;10C8F;;10C8F 10CD0;OLD HUNGARIAN SMALL LETTER I;Ll;0;R;;;;;N;;;10C90;;10C90 10CD1;OLD HUNGARIAN SMALL LETTER II;Ll;0;R;;;;;N;;;10C91;;10C91 10CD2;OLD HUNGARIAN SMALL LETTER EJ;Ll;0;R;;;;;N;;;10C92;;10C92 10CD3;OLD HUNGARIAN SMALL LETTER EK;Ll;0;R;;;;;N;;;10C93;;10C93 10CD4;OLD HUNGARIAN SMALL LETTER AK;Ll;0;R;;;;;N;;;10C94;;10C94 10CD5;OLD HUNGARIAN SMALL LETTER UNK;Ll;0;R;;;;;N;;;10C95;;10C95 10CD6;OLD HUNGARIAN SMALL LETTER EL;Ll;0;R;;;;;N;;;10C96;;10C96 10CD7;OLD HUNGARIAN SMALL LETTER ELY;Ll;0;R;;;;;N;;;10C97;;10C97 10CD8;OLD HUNGARIAN SMALL LETTER EM;Ll;0;R;;;;;N;;;10C98;;10C98 10CD9;OLD HUNGARIAN SMALL LETTER EN;Ll;0;R;;;;;N;;;10C99;;10C99 10CDA;OLD HUNGARIAN SMALL LETTER ENY;Ll;0;R;;;;;N;;;10C9A;;10C9A 10CDB;OLD HUNGARIAN SMALL LETTER O;Ll;0;R;;;;;N;;;10C9B;;10C9B 10CDC;OLD HUNGARIAN SMALL LETTER OO;Ll;0;R;;;;;N;;;10C9C;;10C9C 10CDD;OLD HUNGARIAN SMALL LETTER NIKOLSBURG OE;Ll;0;R;;;;;N;;;10C9D;;10C9D 10CDE;OLD HUNGARIAN SMALL LETTER RUDIMENTA OE;Ll;0;R;;;;;N;;;10C9E;;10C9E 10CDF;OLD HUNGARIAN SMALL LETTER OEE;Ll;0;R;;;;;N;;;10C9F;;10C9F 10CE0;OLD HUNGARIAN SMALL LETTER EP;Ll;0;R;;;;;N;;;10CA0;;10CA0 10CE1;OLD HUNGARIAN SMALL LETTER EMP;Ll;0;R;;;;;N;;;10CA1;;10CA1 10CE2;OLD HUNGARIAN SMALL LETTER ER;Ll;0;R;;;;;N;;;10CA2;;10CA2 10CE3;OLD HUNGARIAN SMALL LETTER SHORT ER;Ll;0;R;;;;;N;;;10CA3;;10CA3 10CE4;OLD HUNGARIAN SMALL LETTER ES;Ll;0;R;;;;;N;;;10CA4;;10CA4 10CE5;OLD HUNGARIAN SMALL LETTER ESZ;Ll;0;R;;;;;N;;;10CA5;;10CA5 10CE6;OLD HUNGARIAN SMALL LETTER ET;Ll;0;R;;;;;N;;;10CA6;;10CA6 10CE7;OLD HUNGARIAN SMALL LETTER ENT;Ll;0;R;;;;;N;;;10CA7;;10CA7 10CE8;OLD HUNGARIAN SMALL LETTER ETY;Ll;0;R;;;;;N;;;10CA8;;10CA8 10CE9;OLD HUNGARIAN SMALL LETTER ECH;Ll;0;R;;;;;N;;;10CA9;;10CA9 10CEA;OLD HUNGARIAN SMALL LETTER U;Ll;0;R;;;;;N;;;10CAA;;10CAA 10CEB;OLD HUNGARIAN SMALL LETTER UU;Ll;0;R;;;;;N;;;10CAB;;10CAB 10CEC;OLD HUNGARIAN SMALL LETTER NIKOLSBURG UE;Ll;0;R;;;;;N;;;10CAC;;10CAC 10CED;OLD HUNGARIAN SMALL LETTER RUDIMENTA UE;Ll;0;R;;;;;N;;;10CAD;;10CAD 10CEE;OLD HUNGARIAN SMALL LETTER EV;Ll;0;R;;;;;N;;;10CAE;;10CAE 10CEF;OLD HUNGARIAN SMALL LETTER EZ;Ll;0;R;;;;;N;;;10CAF;;10CAF 10CF0;OLD HUNGARIAN SMALL LETTER EZS;Ll;0;R;;;;;N;;;10CB0;;10CB0 10CF1;OLD HUNGARIAN SMALL LETTER ENT-SHAPED SIGN;Ll;0;R;;;;;N;;;10CB1;;10CB1 10CF2;OLD HUNGARIAN SMALL LETTER US;Ll;0;R;;;;;N;;;10CB2;;10CB2 10CFA;OLD HUNGARIAN NUMBER ONE;No;0;R;;;;1;N;;;;; 10CFB;OLD HUNGARIAN NUMBER FIVE;No;0;R;;;;5;N;;;;; 10CFC;OLD HUNGARIAN NUMBER TEN;No;0;R;;;;10;N;;;;; 10CFD;OLD HUNGARIAN NUMBER FIFTY;No;0;R;;;;50;N;;;;; 10CFE;OLD HUNGARIAN NUMBER ONE HUNDRED;No;0;R;;;;100;N;;;;; 10CFF;OLD HUNGARIAN NUMBER ONE THOUSAND;No;0;R;;;;1000;N;;;;; 10E60;RUMI DIGIT ONE;No;0;AN;;;1;1;N;;;;; 10E61;RUMI DIGIT TWO;No;0;AN;;;2;2;N;;;;; 10E62;RUMI DIGIT THREE;No;0;AN;;;3;3;N;;;;; 10E63;RUMI DIGIT FOUR;No;0;AN;;;4;4;N;;;;; 10E64;RUMI DIGIT FIVE;No;0;AN;;;5;5;N;;;;; 10E65;RUMI DIGIT SIX;No;0;AN;;;6;6;N;;;;; 10E66;RUMI DIGIT SEVEN;No;0;AN;;;7;7;N;;;;; 10E67;RUMI DIGIT EIGHT;No;0;AN;;;8;8;N;;;;; 10E68;RUMI DIGIT NINE;No;0;AN;;;9;9;N;;;;; 10E69;RUMI NUMBER TEN;No;0;AN;;;;10;N;;;;; 10E6A;RUMI NUMBER TWENTY;No;0;AN;;;;20;N;;;;; 10E6B;RUMI NUMBER THIRTY;No;0;AN;;;;30;N;;;;; 10E6C;RUMI NUMBER FORTY;No;0;AN;;;;40;N;;;;; 10E6D;RUMI NUMBER FIFTY;No;0;AN;;;;50;N;;;;; 10E6E;RUMI NUMBER SIXTY;No;0;AN;;;;60;N;;;;; 10E6F;RUMI NUMBER SEVENTY;No;0;AN;;;;70;N;;;;; 10E70;RUMI NUMBER EIGHTY;No;0;AN;;;;80;N;;;;; 10E71;RUMI NUMBER NINETY;No;0;AN;;;;90;N;;;;; 10E72;RUMI NUMBER ONE HUNDRED;No;0;AN;;;;100;N;;;;; 10E73;RUMI NUMBER TWO HUNDRED;No;0;AN;;;;200;N;;;;; 10E74;RUMI NUMBER THREE HUNDRED;No;0;AN;;;;300;N;;;;; 10E75;RUMI NUMBER FOUR HUNDRED;No;0;AN;;;;400;N;;;;; 10E76;RUMI NUMBER FIVE HUNDRED;No;0;AN;;;;500;N;;;;; 10E77;RUMI NUMBER SIX HUNDRED;No;0;AN;;;;600;N;;;;; 10E78;RUMI NUMBER SEVEN HUNDRED;No;0;AN;;;;700;N;;;;; 10E79;RUMI NUMBER EIGHT HUNDRED;No;0;AN;;;;800;N;;;;; 10E7A;RUMI NUMBER NINE HUNDRED;No;0;AN;;;;900;N;;;;; 10E7B;RUMI FRACTION ONE HALF;No;0;AN;;;;1/2;N;;;;; 10E7C;RUMI FRACTION ONE QUARTER;No;0;AN;;;;1/4;N;;;;; 10E7D;RUMI FRACTION ONE THIRD;No;0;AN;;;;1/3;N;;;;; 10E7E;RUMI FRACTION TWO THIRDS;No;0;AN;;;;2/3;N;;;;; 11000;BRAHMI SIGN CANDRABINDU;Mc;0;L;;;;;N;;;;; 11001;BRAHMI SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; 11002;BRAHMI SIGN VISARGA;Mc;0;L;;;;;N;;;;; 11003;BRAHMI SIGN JIHVAMULIYA;Lo;0;L;;;;;N;;;;; 11004;BRAHMI SIGN UPADHMANIYA;Lo;0;L;;;;;N;;;;; 11005;BRAHMI LETTER A;Lo;0;L;;;;;N;;;;; 11006;BRAHMI LETTER AA;Lo;0;L;;;;;N;;;;; 11007;BRAHMI LETTER I;Lo;0;L;;;;;N;;;;; 11008;BRAHMI LETTER II;Lo;0;L;;;;;N;;;;; 11009;BRAHMI LETTER U;Lo;0;L;;;;;N;;;;; 1100A;BRAHMI LETTER UU;Lo;0;L;;;;;N;;;;; 1100B;BRAHMI LETTER VOCALIC R;Lo;0;L;;;;;N;;;;; 1100C;BRAHMI LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 1100D;BRAHMI LETTER VOCALIC L;Lo;0;L;;;;;N;;;;; 1100E;BRAHMI LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; 1100F;BRAHMI LETTER E;Lo;0;L;;;;;N;;;;; 11010;BRAHMI LETTER AI;Lo;0;L;;;;;N;;;;; 11011;BRAHMI LETTER O;Lo;0;L;;;;;N;;;;; 11012;BRAHMI LETTER AU;Lo;0;L;;;;;N;;;;; 11013;BRAHMI LETTER KA;Lo;0;L;;;;;N;;;;; 11014;BRAHMI LETTER KHA;Lo;0;L;;;;;N;;;;; 11015;BRAHMI LETTER GA;Lo;0;L;;;;;N;;;;; 11016;BRAHMI LETTER GHA;Lo;0;L;;;;;N;;;;; 11017;BRAHMI LETTER NGA;Lo;0;L;;;;;N;;;;; 11018;BRAHMI LETTER CA;Lo;0;L;;;;;N;;;;; 11019;BRAHMI LETTER CHA;Lo;0;L;;;;;N;;;;; 1101A;BRAHMI LETTER JA;Lo;0;L;;;;;N;;;;; 1101B;BRAHMI LETTER JHA;Lo;0;L;;;;;N;;;;; 1101C;BRAHMI LETTER NYA;Lo;0;L;;;;;N;;;;; 1101D;BRAHMI LETTER TTA;Lo;0;L;;;;;N;;;;; 1101E;BRAHMI LETTER TTHA;Lo;0;L;;;;;N;;;;; 1101F;BRAHMI LETTER DDA;Lo;0;L;;;;;N;;;;; 11020;BRAHMI LETTER DDHA;Lo;0;L;;;;;N;;;;; 11021;BRAHMI LETTER NNA;Lo;0;L;;;;;N;;;;; 11022;BRAHMI LETTER TA;Lo;0;L;;;;;N;;;;; 11023;BRAHMI LETTER THA;Lo;0;L;;;;;N;;;;; 11024;BRAHMI LETTER DA;Lo;0;L;;;;;N;;;;; 11025;BRAHMI LETTER DHA;Lo;0;L;;;;;N;;;;; 11026;BRAHMI LETTER NA;Lo;0;L;;;;;N;;;;; 11027;BRAHMI LETTER PA;Lo;0;L;;;;;N;;;;; 11028;BRAHMI LETTER PHA;Lo;0;L;;;;;N;;;;; 11029;BRAHMI LETTER BA;Lo;0;L;;;;;N;;;;; 1102A;BRAHMI LETTER BHA;Lo;0;L;;;;;N;;;;; 1102B;BRAHMI LETTER MA;Lo;0;L;;;;;N;;;;; 1102C;BRAHMI LETTER YA;Lo;0;L;;;;;N;;;;; 1102D;BRAHMI LETTER RA;Lo;0;L;;;;;N;;;;; 1102E;BRAHMI LETTER LA;Lo;0;L;;;;;N;;;;; 1102F;BRAHMI LETTER VA;Lo;0;L;;;;;N;;;;; 11030;BRAHMI LETTER SHA;Lo;0;L;;;;;N;;;;; 11031;BRAHMI LETTER SSA;Lo;0;L;;;;;N;;;;; 11032;BRAHMI LETTER SA;Lo;0;L;;;;;N;;;;; 11033;BRAHMI LETTER HA;Lo;0;L;;;;;N;;;;; 11034;BRAHMI LETTER LLA;Lo;0;L;;;;;N;;;;; 11035;BRAHMI LETTER OLD TAMIL LLLA;Lo;0;L;;;;;N;;;;; 11036;BRAHMI LETTER OLD TAMIL RRA;Lo;0;L;;;;;N;;;;; 11037;BRAHMI LETTER OLD TAMIL NNNA;Lo;0;L;;;;;N;;;;; 11038;BRAHMI VOWEL SIGN AA;Mn;0;NSM;;;;;N;;;;; 11039;BRAHMI VOWEL SIGN BHATTIPROLU AA;Mn;0;NSM;;;;;N;;;;; 1103A;BRAHMI VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; 1103B;BRAHMI VOWEL SIGN II;Mn;0;NSM;;;;;N;;;;; 1103C;BRAHMI VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 1103D;BRAHMI VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 1103E;BRAHMI VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;; 1103F;BRAHMI VOWEL SIGN VOCALIC RR;Mn;0;NSM;;;;;N;;;;; 11040;BRAHMI VOWEL SIGN VOCALIC L;Mn;0;NSM;;;;;N;;;;; 11041;BRAHMI VOWEL SIGN VOCALIC LL;Mn;0;NSM;;;;;N;;;;; 11042;BRAHMI VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;; 11043;BRAHMI VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;; 11044;BRAHMI VOWEL SIGN O;Mn;0;NSM;;;;;N;;;;; 11045;BRAHMI VOWEL SIGN AU;Mn;0;NSM;;;;;N;;;;; 11046;BRAHMI VIRAMA;Mn;9;NSM;;;;;N;;;;; 11047;BRAHMI DANDA;Po;0;L;;;;;N;;;;; 11048;BRAHMI DOUBLE DANDA;Po;0;L;;;;;N;;;;; 11049;BRAHMI PUNCTUATION DOT;Po;0;L;;;;;N;;;;; 1104A;BRAHMI PUNCTUATION DOUBLE DOT;Po;0;L;;;;;N;;;;; 1104B;BRAHMI PUNCTUATION LINE;Po;0;L;;;;;N;;;;; 1104C;BRAHMI PUNCTUATION CRESCENT BAR;Po;0;L;;;;;N;;;;; 1104D;BRAHMI PUNCTUATION LOTUS;Po;0;L;;;;;N;;;;; 11052;BRAHMI NUMBER ONE;No;0;ON;;;1;1;N;;;;; 11053;BRAHMI NUMBER TWO;No;0;ON;;;2;2;N;;;;; 11054;BRAHMI NUMBER THREE;No;0;ON;;;3;3;N;;;;; 11055;BRAHMI NUMBER FOUR;No;0;ON;;;4;4;N;;;;; 11056;BRAHMI NUMBER FIVE;No;0;ON;;;5;5;N;;;;; 11057;BRAHMI NUMBER SIX;No;0;ON;;;6;6;N;;;;; 11058;BRAHMI NUMBER SEVEN;No;0;ON;;;7;7;N;;;;; 11059;BRAHMI NUMBER EIGHT;No;0;ON;;;8;8;N;;;;; 1105A;BRAHMI NUMBER NINE;No;0;ON;;;9;9;N;;;;; 1105B;BRAHMI NUMBER TEN;No;0;ON;;;;10;N;;;;; 1105C;BRAHMI NUMBER TWENTY;No;0;ON;;;;20;N;;;;; 1105D;BRAHMI NUMBER THIRTY;No;0;ON;;;;30;N;;;;; 1105E;BRAHMI NUMBER FORTY;No;0;ON;;;;40;N;;;;; 1105F;BRAHMI NUMBER FIFTY;No;0;ON;;;;50;N;;;;; 11060;BRAHMI NUMBER SIXTY;No;0;ON;;;;60;N;;;;; 11061;BRAHMI NUMBER SEVENTY;No;0;ON;;;;70;N;;;;; 11062;BRAHMI NUMBER EIGHTY;No;0;ON;;;;80;N;;;;; 11063;BRAHMI NUMBER NINETY;No;0;ON;;;;90;N;;;;; 11064;BRAHMI NUMBER ONE HUNDRED;No;0;ON;;;;100;N;;;;; 11065;BRAHMI NUMBER ONE THOUSAND;No;0;ON;;;;1000;N;;;;; 11066;BRAHMI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 11067;BRAHMI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 11068;BRAHMI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 11069;BRAHMI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 1106A;BRAHMI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 1106B;BRAHMI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 1106C;BRAHMI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 1106D;BRAHMI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 1106E;BRAHMI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 1106F;BRAHMI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 1107F;BRAHMI NUMBER JOINER;Mn;9;NSM;;;;;N;;;;; 11080;KAITHI SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 11081;KAITHI SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; 11082;KAITHI SIGN VISARGA;Mc;0;L;;;;;N;;;;; 11083;KAITHI LETTER A;Lo;0;L;;;;;N;;;;; 11084;KAITHI LETTER AA;Lo;0;L;;;;;N;;;;; 11085;KAITHI LETTER I;Lo;0;L;;;;;N;;;;; 11086;KAITHI LETTER II;Lo;0;L;;;;;N;;;;; 11087;KAITHI LETTER U;Lo;0;L;;;;;N;;;;; 11088;KAITHI LETTER UU;Lo;0;L;;;;;N;;;;; 11089;KAITHI LETTER E;Lo;0;L;;;;;N;;;;; 1108A;KAITHI LETTER AI;Lo;0;L;;;;;N;;;;; 1108B;KAITHI LETTER O;Lo;0;L;;;;;N;;;;; 1108C;KAITHI LETTER AU;Lo;0;L;;;;;N;;;;; 1108D;KAITHI LETTER KA;Lo;0;L;;;;;N;;;;; 1108E;KAITHI LETTER KHA;Lo;0;L;;;;;N;;;;; 1108F;KAITHI LETTER GA;Lo;0;L;;;;;N;;;;; 11090;KAITHI LETTER GHA;Lo;0;L;;;;;N;;;;; 11091;KAITHI LETTER NGA;Lo;0;L;;;;;N;;;;; 11092;KAITHI LETTER CA;Lo;0;L;;;;;N;;;;; 11093;KAITHI LETTER CHA;Lo;0;L;;;;;N;;;;; 11094;KAITHI LETTER JA;Lo;0;L;;;;;N;;;;; 11095;KAITHI LETTER JHA;Lo;0;L;;;;;N;;;;; 11096;KAITHI LETTER NYA;Lo;0;L;;;;;N;;;;; 11097;KAITHI LETTER TTA;Lo;0;L;;;;;N;;;;; 11098;KAITHI LETTER TTHA;Lo;0;L;;;;;N;;;;; 11099;KAITHI LETTER DDA;Lo;0;L;;;;;N;;;;; 1109A;KAITHI LETTER DDDHA;Lo;0;L;11099 110BA;;;;N;;;;; 1109B;KAITHI LETTER DDHA;Lo;0;L;;;;;N;;;;; 1109C;KAITHI LETTER RHA;Lo;0;L;1109B 110BA;;;;N;;;;; 1109D;KAITHI LETTER NNA;Lo;0;L;;;;;N;;;;; 1109E;KAITHI LETTER TA;Lo;0;L;;;;;N;;;;; 1109F;KAITHI LETTER THA;Lo;0;L;;;;;N;;;;; 110A0;KAITHI LETTER DA;Lo;0;L;;;;;N;;;;; 110A1;KAITHI LETTER DHA;Lo;0;L;;;;;N;;;;; 110A2;KAITHI LETTER NA;Lo;0;L;;;;;N;;;;; 110A3;KAITHI LETTER PA;Lo;0;L;;;;;N;;;;; 110A4;KAITHI LETTER PHA;Lo;0;L;;;;;N;;;;; 110A5;KAITHI LETTER BA;Lo;0;L;;;;;N;;;;; 110A6;KAITHI LETTER BHA;Lo;0;L;;;;;N;;;;; 110A7;KAITHI LETTER MA;Lo;0;L;;;;;N;;;;; 110A8;KAITHI LETTER YA;Lo;0;L;;;;;N;;;;; 110A9;KAITHI LETTER RA;Lo;0;L;;;;;N;;;;; 110AA;KAITHI LETTER LA;Lo;0;L;;;;;N;;;;; 110AB;KAITHI LETTER VA;Lo;0;L;110A5 110BA;;;;N;;;;; 110AC;KAITHI LETTER SHA;Lo;0;L;;;;;N;;;;; 110AD;KAITHI LETTER SSA;Lo;0;L;;;;;N;;;;; 110AE;KAITHI LETTER SA;Lo;0;L;;;;;N;;;;; 110AF;KAITHI LETTER HA;Lo;0;L;;;;;N;;;;; 110B0;KAITHI VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 110B1;KAITHI VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 110B2;KAITHI VOWEL SIGN II;Mc;0;L;;;;;N;;;;; 110B3;KAITHI VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 110B4;KAITHI VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 110B5;KAITHI VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;; 110B6;KAITHI VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;; 110B7;KAITHI VOWEL SIGN O;Mc;0;L;;;;;N;;;;; 110B8;KAITHI VOWEL SIGN AU;Mc;0;L;;;;;N;;;;; 110B9;KAITHI SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 110BA;KAITHI SIGN NUKTA;Mn;7;NSM;;;;;N;;;;; 110BB;KAITHI ABBREVIATION SIGN;Po;0;L;;;;;N;;;;; 110BC;KAITHI ENUMERATION SIGN;Po;0;L;;;;;N;;;;; 110BD;KAITHI NUMBER SIGN;Cf;0;L;;;;;N;;;;; 110BE;KAITHI SECTION MARK;Po;0;L;;;;;N;;;;; 110BF;KAITHI DOUBLE SECTION MARK;Po;0;L;;;;;N;;;;; 110C0;KAITHI DANDA;Po;0;L;;;;;N;;;;; 110C1;KAITHI DOUBLE DANDA;Po;0;L;;;;;N;;;;; 110D0;SORA SOMPENG LETTER SAH;Lo;0;L;;;;;N;;;;; 110D1;SORA SOMPENG LETTER TAH;Lo;0;L;;;;;N;;;;; 110D2;SORA SOMPENG LETTER BAH;Lo;0;L;;;;;N;;;;; 110D3;SORA SOMPENG LETTER CAH;Lo;0;L;;;;;N;;;;; 110D4;SORA SOMPENG LETTER DAH;Lo;0;L;;;;;N;;;;; 110D5;SORA SOMPENG LETTER GAH;Lo;0;L;;;;;N;;;;; 110D6;SORA SOMPENG LETTER MAH;Lo;0;L;;;;;N;;;;; 110D7;SORA SOMPENG LETTER NGAH;Lo;0;L;;;;;N;;;;; 110D8;SORA SOMPENG LETTER LAH;Lo;0;L;;;;;N;;;;; 110D9;SORA SOMPENG LETTER NAH;Lo;0;L;;;;;N;;;;; 110DA;SORA SOMPENG LETTER VAH;Lo;0;L;;;;;N;;;;; 110DB;SORA SOMPENG LETTER PAH;Lo;0;L;;;;;N;;;;; 110DC;SORA SOMPENG LETTER YAH;Lo;0;L;;;;;N;;;;; 110DD;SORA SOMPENG LETTER RAH;Lo;0;L;;;;;N;;;;; 110DE;SORA SOMPENG LETTER HAH;Lo;0;L;;;;;N;;;;; 110DF;SORA SOMPENG LETTER KAH;Lo;0;L;;;;;N;;;;; 110E0;SORA SOMPENG LETTER JAH;Lo;0;L;;;;;N;;;;; 110E1;SORA SOMPENG LETTER NYAH;Lo;0;L;;;;;N;;;;; 110E2;SORA SOMPENG LETTER AH;Lo;0;L;;;;;N;;;;; 110E3;SORA SOMPENG LETTER EEH;Lo;0;L;;;;;N;;;;; 110E4;SORA SOMPENG LETTER IH;Lo;0;L;;;;;N;;;;; 110E5;SORA SOMPENG LETTER UH;Lo;0;L;;;;;N;;;;; 110E6;SORA SOMPENG LETTER OH;Lo;0;L;;;;;N;;;;; 110E7;SORA SOMPENG LETTER EH;Lo;0;L;;;;;N;;;;; 110E8;SORA SOMPENG LETTER MAE;Lo;0;L;;;;;N;;;;; 110F0;SORA SOMPENG DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 110F1;SORA SOMPENG DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 110F2;SORA SOMPENG DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 110F3;SORA SOMPENG DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 110F4;SORA SOMPENG DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 110F5;SORA SOMPENG DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 110F6;SORA SOMPENG DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 110F7;SORA SOMPENG DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 110F8;SORA SOMPENG DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 110F9;SORA SOMPENG DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 11100;CHAKMA SIGN CANDRABINDU;Mn;230;NSM;;;;;N;;;;; 11101;CHAKMA SIGN ANUSVARA;Mn;230;NSM;;;;;N;;;;; 11102;CHAKMA SIGN VISARGA;Mn;230;NSM;;;;;N;;;;; 11103;CHAKMA LETTER AA;Lo;0;L;;;;;N;;;;; 11104;CHAKMA LETTER I;Lo;0;L;;;;;N;;;;; 11105;CHAKMA LETTER U;Lo;0;L;;;;;N;;;;; 11106;CHAKMA LETTER E;Lo;0;L;;;;;N;;;;; 11107;CHAKMA LETTER KAA;Lo;0;L;;;;;N;;;;; 11108;CHAKMA LETTER KHAA;Lo;0;L;;;;;N;;;;; 11109;CHAKMA LETTER GAA;Lo;0;L;;;;;N;;;;; 1110A;CHAKMA LETTER GHAA;Lo;0;L;;;;;N;;;;; 1110B;CHAKMA LETTER NGAA;Lo;0;L;;;;;N;;;;; 1110C;CHAKMA LETTER CAA;Lo;0;L;;;;;N;;;;; 1110D;CHAKMA LETTER CHAA;Lo;0;L;;;;;N;;;;; 1110E;CHAKMA LETTER JAA;Lo;0;L;;;;;N;;;;; 1110F;CHAKMA LETTER JHAA;Lo;0;L;;;;;N;;;;; 11110;CHAKMA LETTER NYAA;Lo;0;L;;;;;N;;;;; 11111;CHAKMA LETTER TTAA;Lo;0;L;;;;;N;;;;; 11112;CHAKMA LETTER TTHAA;Lo;0;L;;;;;N;;;;; 11113;CHAKMA LETTER DDAA;Lo;0;L;;;;;N;;;;; 11114;CHAKMA LETTER DDHAA;Lo;0;L;;;;;N;;;;; 11115;CHAKMA LETTER NNAA;Lo;0;L;;;;;N;;;;; 11116;CHAKMA LETTER TAA;Lo;0;L;;;;;N;;;;; 11117;CHAKMA LETTER THAA;Lo;0;L;;;;;N;;;;; 11118;CHAKMA LETTER DAA;Lo;0;L;;;;;N;;;;; 11119;CHAKMA LETTER DHAA;Lo;0;L;;;;;N;;;;; 1111A;CHAKMA LETTER NAA;Lo;0;L;;;;;N;;;;; 1111B;CHAKMA LETTER PAA;Lo;0;L;;;;;N;;;;; 1111C;CHAKMA LETTER PHAA;Lo;0;L;;;;;N;;;;; 1111D;CHAKMA LETTER BAA;Lo;0;L;;;;;N;;;;; 1111E;CHAKMA LETTER BHAA;Lo;0;L;;;;;N;;;;; 1111F;CHAKMA LETTER MAA;Lo;0;L;;;;;N;;;;; 11120;CHAKMA LETTER YYAA;Lo;0;L;;;;;N;;;;; 11121;CHAKMA LETTER YAA;Lo;0;L;;;;;N;;;;; 11122;CHAKMA LETTER RAA;Lo;0;L;;;;;N;;;;; 11123;CHAKMA LETTER LAA;Lo;0;L;;;;;N;;;;; 11124;CHAKMA LETTER WAA;Lo;0;L;;;;;N;;;;; 11125;CHAKMA LETTER SAA;Lo;0;L;;;;;N;;;;; 11126;CHAKMA LETTER HAA;Lo;0;L;;;;;N;;;;; 11127;CHAKMA VOWEL SIGN A;Mn;0;NSM;;;;;N;;;;; 11128;CHAKMA VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; 11129;CHAKMA VOWEL SIGN II;Mn;0;NSM;;;;;N;;;;; 1112A;CHAKMA VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 1112B;CHAKMA VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 1112C;CHAKMA VOWEL SIGN E;Mc;0;L;;;;;N;;;;; 1112D;CHAKMA VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;; 1112E;CHAKMA VOWEL SIGN O;Mn;0;NSM;11131 11127;;;;N;;;;; 1112F;CHAKMA VOWEL SIGN AU;Mn;0;NSM;11132 11127;;;;N;;;;; 11130;CHAKMA VOWEL SIGN OI;Mn;0;NSM;;;;;N;;;;; 11131;CHAKMA O MARK;Mn;0;NSM;;;;;N;;;;; 11132;CHAKMA AU MARK;Mn;0;NSM;;;;;N;;;;; 11133;CHAKMA VIRAMA;Mn;9;NSM;;;;;N;;;;; 11134;CHAKMA MAAYYAA;Mn;9;NSM;;;;;N;;;;; 11136;CHAKMA DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 11137;CHAKMA DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 11138;CHAKMA DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 11139;CHAKMA DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 1113A;CHAKMA DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 1113B;CHAKMA DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 1113C;CHAKMA DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 1113D;CHAKMA DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 1113E;CHAKMA DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 1113F;CHAKMA DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 11140;CHAKMA SECTION MARK;Po;0;L;;;;;N;;;;; 11141;CHAKMA DANDA;Po;0;L;;;;;N;;;;; 11142;CHAKMA DOUBLE DANDA;Po;0;L;;;;;N;;;;; 11143;CHAKMA QUESTION MARK;Po;0;L;;;;;N;;;;; 11150;MAHAJANI LETTER A;Lo;0;L;;;;;N;;;;; 11151;MAHAJANI LETTER I;Lo;0;L;;;;;N;;;;; 11152;MAHAJANI LETTER U;Lo;0;L;;;;;N;;;;; 11153;MAHAJANI LETTER E;Lo;0;L;;;;;N;;;;; 11154;MAHAJANI LETTER O;Lo;0;L;;;;;N;;;;; 11155;MAHAJANI LETTER KA;Lo;0;L;;;;;N;;;;; 11156;MAHAJANI LETTER KHA;Lo;0;L;;;;;N;;;;; 11157;MAHAJANI LETTER GA;Lo;0;L;;;;;N;;;;; 11158;MAHAJANI LETTER GHA;Lo;0;L;;;;;N;;;;; 11159;MAHAJANI LETTER CA;Lo;0;L;;;;;N;;;;; 1115A;MAHAJANI LETTER CHA;Lo;0;L;;;;;N;;;;; 1115B;MAHAJANI LETTER JA;Lo;0;L;;;;;N;;;;; 1115C;MAHAJANI LETTER JHA;Lo;0;L;;;;;N;;;;; 1115D;MAHAJANI LETTER NYA;Lo;0;L;;;;;N;;;;; 1115E;MAHAJANI LETTER TTA;Lo;0;L;;;;;N;;;;; 1115F;MAHAJANI LETTER TTHA;Lo;0;L;;;;;N;;;;; 11160;MAHAJANI LETTER DDA;Lo;0;L;;;;;N;;;;; 11161;MAHAJANI LETTER DDHA;Lo;0;L;;;;;N;;;;; 11162;MAHAJANI LETTER NNA;Lo;0;L;;;;;N;;;;; 11163;MAHAJANI LETTER TA;Lo;0;L;;;;;N;;;;; 11164;MAHAJANI LETTER THA;Lo;0;L;;;;;N;;;;; 11165;MAHAJANI LETTER DA;Lo;0;L;;;;;N;;;;; 11166;MAHAJANI LETTER DHA;Lo;0;L;;;;;N;;;;; 11167;MAHAJANI LETTER NA;Lo;0;L;;;;;N;;;;; 11168;MAHAJANI LETTER PA;Lo;0;L;;;;;N;;;;; 11169;MAHAJANI LETTER PHA;Lo;0;L;;;;;N;;;;; 1116A;MAHAJANI LETTER BA;Lo;0;L;;;;;N;;;;; 1116B;MAHAJANI LETTER BHA;Lo;0;L;;;;;N;;;;; 1116C;MAHAJANI LETTER MA;Lo;0;L;;;;;N;;;;; 1116D;MAHAJANI LETTER RA;Lo;0;L;;;;;N;;;;; 1116E;MAHAJANI LETTER LA;Lo;0;L;;;;;N;;;;; 1116F;MAHAJANI LETTER VA;Lo;0;L;;;;;N;;;;; 11170;MAHAJANI LETTER SA;Lo;0;L;;;;;N;;;;; 11171;MAHAJANI LETTER HA;Lo;0;L;;;;;N;;;;; 11172;MAHAJANI LETTER RRA;Lo;0;L;;;;;N;;;;; 11173;MAHAJANI SIGN NUKTA;Mn;7;NSM;;;;;N;;;;; 11174;MAHAJANI ABBREVIATION SIGN;Po;0;L;;;;;N;;;;; 11175;MAHAJANI SECTION MARK;Po;0;L;;;;;N;;;;; 11176;MAHAJANI LIGATURE SHRI;Lo;0;L;;;;;N;;;;; 11180;SHARADA SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 11181;SHARADA SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; 11182;SHARADA SIGN VISARGA;Mc;0;L;;;;;N;;;;; 11183;SHARADA LETTER A;Lo;0;L;;;;;N;;;;; 11184;SHARADA LETTER AA;Lo;0;L;;;;;N;;;;; 11185;SHARADA LETTER I;Lo;0;L;;;;;N;;;;; 11186;SHARADA LETTER II;Lo;0;L;;;;;N;;;;; 11187;SHARADA LETTER U;Lo;0;L;;;;;N;;;;; 11188;SHARADA LETTER UU;Lo;0;L;;;;;N;;;;; 11189;SHARADA LETTER VOCALIC R;Lo;0;L;;;;;N;;;;; 1118A;SHARADA LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 1118B;SHARADA LETTER VOCALIC L;Lo;0;L;;;;;N;;;;; 1118C;SHARADA LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; 1118D;SHARADA LETTER E;Lo;0;L;;;;;N;;;;; 1118E;SHARADA LETTER AI;Lo;0;L;;;;;N;;;;; 1118F;SHARADA LETTER O;Lo;0;L;;;;;N;;;;; 11190;SHARADA LETTER AU;Lo;0;L;;;;;N;;;;; 11191;SHARADA LETTER KA;Lo;0;L;;;;;N;;;;; 11192;SHARADA LETTER KHA;Lo;0;L;;;;;N;;;;; 11193;SHARADA LETTER GA;Lo;0;L;;;;;N;;;;; 11194;SHARADA LETTER GHA;Lo;0;L;;;;;N;;;;; 11195;SHARADA LETTER NGA;Lo;0;L;;;;;N;;;;; 11196;SHARADA LETTER CA;Lo;0;L;;;;;N;;;;; 11197;SHARADA LETTER CHA;Lo;0;L;;;;;N;;;;; 11198;SHARADA LETTER JA;Lo;0;L;;;;;N;;;;; 11199;SHARADA LETTER JHA;Lo;0;L;;;;;N;;;;; 1119A;SHARADA LETTER NYA;Lo;0;L;;;;;N;;;;; 1119B;SHARADA LETTER TTA;Lo;0;L;;;;;N;;;;; 1119C;SHARADA LETTER TTHA;Lo;0;L;;;;;N;;;;; 1119D;SHARADA LETTER DDA;Lo;0;L;;;;;N;;;;; 1119E;SHARADA LETTER DDHA;Lo;0;L;;;;;N;;;;; 1119F;SHARADA LETTER NNA;Lo;0;L;;;;;N;;;;; 111A0;SHARADA LETTER TA;Lo;0;L;;;;;N;;;;; 111A1;SHARADA LETTER THA;Lo;0;L;;;;;N;;;;; 111A2;SHARADA LETTER DA;Lo;0;L;;;;;N;;;;; 111A3;SHARADA LETTER DHA;Lo;0;L;;;;;N;;;;; 111A4;SHARADA LETTER NA;Lo;0;L;;;;;N;;;;; 111A5;SHARADA LETTER PA;Lo;0;L;;;;;N;;;;; 111A6;SHARADA LETTER PHA;Lo;0;L;;;;;N;;;;; 111A7;SHARADA LETTER BA;Lo;0;L;;;;;N;;;;; 111A8;SHARADA LETTER BHA;Lo;0;L;;;;;N;;;;; 111A9;SHARADA LETTER MA;Lo;0;L;;;;;N;;;;; 111AA;SHARADA LETTER YA;Lo;0;L;;;;;N;;;;; 111AB;SHARADA LETTER RA;Lo;0;L;;;;;N;;;;; 111AC;SHARADA LETTER LA;Lo;0;L;;;;;N;;;;; 111AD;SHARADA LETTER LLA;Lo;0;L;;;;;N;;;;; 111AE;SHARADA LETTER VA;Lo;0;L;;;;;N;;;;; 111AF;SHARADA LETTER SHA;Lo;0;L;;;;;N;;;;; 111B0;SHARADA LETTER SSA;Lo;0;L;;;;;N;;;;; 111B1;SHARADA LETTER SA;Lo;0;L;;;;;N;;;;; 111B2;SHARADA LETTER HA;Lo;0;L;;;;;N;;;;; 111B3;SHARADA VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 111B4;SHARADA VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 111B5;SHARADA VOWEL SIGN II;Mc;0;L;;;;;N;;;;; 111B6;SHARADA VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 111B7;SHARADA VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 111B8;SHARADA VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;; 111B9;SHARADA VOWEL SIGN VOCALIC RR;Mn;0;NSM;;;;;N;;;;; 111BA;SHARADA VOWEL SIGN VOCALIC L;Mn;0;NSM;;;;;N;;;;; 111BB;SHARADA VOWEL SIGN VOCALIC LL;Mn;0;NSM;;;;;N;;;;; 111BC;SHARADA VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;; 111BD;SHARADA VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;; 111BE;SHARADA VOWEL SIGN O;Mn;0;NSM;;;;;N;;;;; 111BF;SHARADA VOWEL SIGN AU;Mc;0;L;;;;;N;;;;; 111C0;SHARADA SIGN VIRAMA;Mc;9;L;;;;;N;;;;; 111C1;SHARADA SIGN AVAGRAHA;Lo;0;L;;;;;N;;;;; 111C2;SHARADA SIGN JIHVAMULIYA;Lo;0;L;;;;;N;;;;; 111C3;SHARADA SIGN UPADHMANIYA;Lo;0;L;;;;;N;;;;; 111C4;SHARADA OM;Lo;0;L;;;;;N;;;;; 111C5;SHARADA DANDA;Po;0;L;;;;;N;;;;; 111C6;SHARADA DOUBLE DANDA;Po;0;L;;;;;N;;;;; 111C7;SHARADA ABBREVIATION SIGN;Po;0;L;;;;;N;;;;; 111C8;SHARADA SEPARATOR;Po;0;L;;;;;N;;;;; 111C9;SHARADA SANDHI MARK;Po;0;L;;;;;N;;;;; 111CA;SHARADA SIGN NUKTA;Mn;7;NSM;;;;;N;;;;; 111CB;SHARADA VOWEL MODIFIER MARK;Mn;0;NSM;;;;;N;;;;; 111CC;SHARADA EXTRA SHORT VOWEL MARK;Mn;0;NSM;;;;;N;;;;; 111CD;SHARADA SUTRA MARK;Po;0;L;;;;;N;;;;; 111D0;SHARADA DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 111D1;SHARADA DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 111D2;SHARADA DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 111D3;SHARADA DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 111D4;SHARADA DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 111D5;SHARADA DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 111D6;SHARADA DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 111D7;SHARADA DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 111D8;SHARADA DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 111D9;SHARADA DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 111DA;SHARADA EKAM;Lo;0;L;;;;;N;;;;; 111DB;SHARADA SIGN SIDDHAM;Po;0;L;;;;;N;;;;; 111DC;SHARADA HEADSTROKE;Lo;0;L;;;;;N;;;;; 111DD;SHARADA CONTINUATION SIGN;Po;0;L;;;;;N;;;;; 111DE;SHARADA SECTION MARK-1;Po;0;L;;;;;N;;;;; 111DF;SHARADA SECTION MARK-2;Po;0;L;;;;;N;;;;; 111E1;SINHALA ARCHAIC DIGIT ONE;No;0;L;;;;1;N;;;;; 111E2;SINHALA ARCHAIC DIGIT TWO;No;0;L;;;;2;N;;;;; 111E3;SINHALA ARCHAIC DIGIT THREE;No;0;L;;;;3;N;;;;; 111E4;SINHALA ARCHAIC DIGIT FOUR;No;0;L;;;;4;N;;;;; 111E5;SINHALA ARCHAIC DIGIT FIVE;No;0;L;;;;5;N;;;;; 111E6;SINHALA ARCHAIC DIGIT SIX;No;0;L;;;;6;N;;;;; 111E7;SINHALA ARCHAIC DIGIT SEVEN;No;0;L;;;;7;N;;;;; 111E8;SINHALA ARCHAIC DIGIT EIGHT;No;0;L;;;;8;N;;;;; 111E9;SINHALA ARCHAIC DIGIT NINE;No;0;L;;;;9;N;;;;; 111EA;SINHALA ARCHAIC NUMBER TEN;No;0;L;;;;10;N;;;;; 111EB;SINHALA ARCHAIC NUMBER TWENTY;No;0;L;;;;20;N;;;;; 111EC;SINHALA ARCHAIC NUMBER THIRTY;No;0;L;;;;30;N;;;;; 111ED;SINHALA ARCHAIC NUMBER FORTY;No;0;L;;;;40;N;;;;; 111EE;SINHALA ARCHAIC NUMBER FIFTY;No;0;L;;;;50;N;;;;; 111EF;SINHALA ARCHAIC NUMBER SIXTY;No;0;L;;;;60;N;;;;; 111F0;SINHALA ARCHAIC NUMBER SEVENTY;No;0;L;;;;70;N;;;;; 111F1;SINHALA ARCHAIC NUMBER EIGHTY;No;0;L;;;;80;N;;;;; 111F2;SINHALA ARCHAIC NUMBER NINETY;No;0;L;;;;90;N;;;;; 111F3;SINHALA ARCHAIC NUMBER ONE HUNDRED;No;0;L;;;;100;N;;;;; 111F4;SINHALA ARCHAIC NUMBER ONE THOUSAND;No;0;L;;;;1000;N;;;;; 11200;KHOJKI LETTER A;Lo;0;L;;;;;N;;;;; 11201;KHOJKI LETTER AA;Lo;0;L;;;;;N;;;;; 11202;KHOJKI LETTER I;Lo;0;L;;;;;N;;;;; 11203;KHOJKI LETTER U;Lo;0;L;;;;;N;;;;; 11204;KHOJKI LETTER E;Lo;0;L;;;;;N;;;;; 11205;KHOJKI LETTER AI;Lo;0;L;;;;;N;;;;; 11206;KHOJKI LETTER O;Lo;0;L;;;;;N;;;;; 11207;KHOJKI LETTER AU;Lo;0;L;;;;;N;;;;; 11208;KHOJKI LETTER KA;Lo;0;L;;;;;N;;;;; 11209;KHOJKI LETTER KHA;Lo;0;L;;;;;N;;;;; 1120A;KHOJKI LETTER GA;Lo;0;L;;;;;N;;;;; 1120B;KHOJKI LETTER GGA;Lo;0;L;;;;;N;;;;; 1120C;KHOJKI LETTER GHA;Lo;0;L;;;;;N;;;;; 1120D;KHOJKI LETTER NGA;Lo;0;L;;;;;N;;;;; 1120E;KHOJKI LETTER CA;Lo;0;L;;;;;N;;;;; 1120F;KHOJKI LETTER CHA;Lo;0;L;;;;;N;;;;; 11210;KHOJKI LETTER JA;Lo;0;L;;;;;N;;;;; 11211;KHOJKI LETTER JJA;Lo;0;L;;;;;N;;;;; 11213;KHOJKI LETTER NYA;Lo;0;L;;;;;N;;;;; 11214;KHOJKI LETTER TTA;Lo;0;L;;;;;N;;;;; 11215;KHOJKI LETTER TTHA;Lo;0;L;;;;;N;;;;; 11216;KHOJKI LETTER DDA;Lo;0;L;;;;;N;;;;; 11217;KHOJKI LETTER DDHA;Lo;0;L;;;;;N;;;;; 11218;KHOJKI LETTER NNA;Lo;0;L;;;;;N;;;;; 11219;KHOJKI LETTER TA;Lo;0;L;;;;;N;;;;; 1121A;KHOJKI LETTER THA;Lo;0;L;;;;;N;;;;; 1121B;KHOJKI LETTER DA;Lo;0;L;;;;;N;;;;; 1121C;KHOJKI LETTER DDDA;Lo;0;L;;;;;N;;;;; 1121D;KHOJKI LETTER DHA;Lo;0;L;;;;;N;;;;; 1121E;KHOJKI LETTER NA;Lo;0;L;;;;;N;;;;; 1121F;KHOJKI LETTER PA;Lo;0;L;;;;;N;;;;; 11220;KHOJKI LETTER PHA;Lo;0;L;;;;;N;;;;; 11221;KHOJKI LETTER BA;Lo;0;L;;;;;N;;;;; 11222;KHOJKI LETTER BBA;Lo;0;L;;;;;N;;;;; 11223;KHOJKI LETTER BHA;Lo;0;L;;;;;N;;;;; 11224;KHOJKI LETTER MA;Lo;0;L;;;;;N;;;;; 11225;KHOJKI LETTER YA;Lo;0;L;;;;;N;;;;; 11226;KHOJKI LETTER RA;Lo;0;L;;;;;N;;;;; 11227;KHOJKI LETTER LA;Lo;0;L;;;;;N;;;;; 11228;KHOJKI LETTER VA;Lo;0;L;;;;;N;;;;; 11229;KHOJKI LETTER SA;Lo;0;L;;;;;N;;;;; 1122A;KHOJKI LETTER HA;Lo;0;L;;;;;N;;;;; 1122B;KHOJKI LETTER LLA;Lo;0;L;;;;;N;;;;; 1122C;KHOJKI VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 1122D;KHOJKI VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 1122E;KHOJKI VOWEL SIGN II;Mc;0;L;;;;;N;;;;; 1122F;KHOJKI VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 11230;KHOJKI VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;; 11231;KHOJKI VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;; 11232;KHOJKI VOWEL SIGN O;Mc;0;L;;;;;N;;;;; 11233;KHOJKI VOWEL SIGN AU;Mc;0;L;;;;;N;;;;; 11234;KHOJKI SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; 11235;KHOJKI SIGN VIRAMA;Mc;9;L;;;;;N;;;;; 11236;KHOJKI SIGN NUKTA;Mn;7;NSM;;;;;N;;;;; 11237;KHOJKI SIGN SHADDA;Mn;0;NSM;;;;;N;;;;; 11238;KHOJKI DANDA;Po;0;L;;;;;N;;;;; 11239;KHOJKI DOUBLE DANDA;Po;0;L;;;;;N;;;;; 1123A;KHOJKI WORD SEPARATOR;Po;0;L;;;;;N;;;;; 1123B;KHOJKI SECTION MARK;Po;0;L;;;;;N;;;;; 1123C;KHOJKI DOUBLE SECTION MARK;Po;0;L;;;;;N;;;;; 1123D;KHOJKI ABBREVIATION SIGN;Po;0;L;;;;;N;;;;; 1123E;KHOJKI SIGN SUKUN;Mn;0;NSM;;;;;N;;;;; 11280;MULTANI LETTER A;Lo;0;L;;;;;N;;;;; 11281;MULTANI LETTER I;Lo;0;L;;;;;N;;;;; 11282;MULTANI LETTER U;Lo;0;L;;;;;N;;;;; 11283;MULTANI LETTER E;Lo;0;L;;;;;N;;;;; 11284;MULTANI LETTER KA;Lo;0;L;;;;;N;;;;; 11285;MULTANI LETTER KHA;Lo;0;L;;;;;N;;;;; 11286;MULTANI LETTER GA;Lo;0;L;;;;;N;;;;; 11288;MULTANI LETTER GHA;Lo;0;L;;;;;N;;;;; 1128A;MULTANI LETTER CA;Lo;0;L;;;;;N;;;;; 1128B;MULTANI LETTER CHA;Lo;0;L;;;;;N;;;;; 1128C;MULTANI LETTER JA;Lo;0;L;;;;;N;;;;; 1128D;MULTANI LETTER JJA;Lo;0;L;;;;;N;;;;; 1128F;MULTANI LETTER NYA;Lo;0;L;;;;;N;;;;; 11290;MULTANI LETTER TTA;Lo;0;L;;;;;N;;;;; 11291;MULTANI LETTER TTHA;Lo;0;L;;;;;N;;;;; 11292;MULTANI LETTER DDA;Lo;0;L;;;;;N;;;;; 11293;MULTANI LETTER DDDA;Lo;0;L;;;;;N;;;;; 11294;MULTANI LETTER DDHA;Lo;0;L;;;;;N;;;;; 11295;MULTANI LETTER NNA;Lo;0;L;;;;;N;;;;; 11296;MULTANI LETTER TA;Lo;0;L;;;;;N;;;;; 11297;MULTANI LETTER THA;Lo;0;L;;;;;N;;;;; 11298;MULTANI LETTER DA;Lo;0;L;;;;;N;;;;; 11299;MULTANI LETTER DHA;Lo;0;L;;;;;N;;;;; 1129A;MULTANI LETTER NA;Lo;0;L;;;;;N;;;;; 1129B;MULTANI LETTER PA;Lo;0;L;;;;;N;;;;; 1129C;MULTANI LETTER PHA;Lo;0;L;;;;;N;;;;; 1129D;MULTANI LETTER BA;Lo;0;L;;;;;N;;;;; 1129F;MULTANI LETTER BHA;Lo;0;L;;;;;N;;;;; 112A0;MULTANI LETTER MA;Lo;0;L;;;;;N;;;;; 112A1;MULTANI LETTER YA;Lo;0;L;;;;;N;;;;; 112A2;MULTANI LETTER RA;Lo;0;L;;;;;N;;;;; 112A3;MULTANI LETTER LA;Lo;0;L;;;;;N;;;;; 112A4;MULTANI LETTER VA;Lo;0;L;;;;;N;;;;; 112A5;MULTANI LETTER SA;Lo;0;L;;;;;N;;;;; 112A6;MULTANI LETTER HA;Lo;0;L;;;;;N;;;;; 112A7;MULTANI LETTER RRA;Lo;0;L;;;;;N;;;;; 112A8;MULTANI LETTER RHA;Lo;0;L;;;;;N;;;;; 112A9;MULTANI SECTION MARK;Po;0;L;;;;;N;;;;; 112B0;KHUDAWADI LETTER A;Lo;0;L;;;;;N;;;;; 112B1;KHUDAWADI LETTER AA;Lo;0;L;;;;;N;;;;; 112B2;KHUDAWADI LETTER I;Lo;0;L;;;;;N;;;;; 112B3;KHUDAWADI LETTER II;Lo;0;L;;;;;N;;;;; 112B4;KHUDAWADI LETTER U;Lo;0;L;;;;;N;;;;; 112B5;KHUDAWADI LETTER UU;Lo;0;L;;;;;N;;;;; 112B6;KHUDAWADI LETTER E;Lo;0;L;;;;;N;;;;; 112B7;KHUDAWADI LETTER AI;Lo;0;L;;;;;N;;;;; 112B8;KHUDAWADI LETTER O;Lo;0;L;;;;;N;;;;; 112B9;KHUDAWADI LETTER AU;Lo;0;L;;;;;N;;;;; 112BA;KHUDAWADI LETTER KA;Lo;0;L;;;;;N;;;;; 112BB;KHUDAWADI LETTER KHA;Lo;0;L;;;;;N;;;;; 112BC;KHUDAWADI LETTER GA;Lo;0;L;;;;;N;;;;; 112BD;KHUDAWADI LETTER GGA;Lo;0;L;;;;;N;;;;; 112BE;KHUDAWADI LETTER GHA;Lo;0;L;;;;;N;;;;; 112BF;KHUDAWADI LETTER NGA;Lo;0;L;;;;;N;;;;; 112C0;KHUDAWADI LETTER CA;Lo;0;L;;;;;N;;;;; 112C1;KHUDAWADI LETTER CHA;Lo;0;L;;;;;N;;;;; 112C2;KHUDAWADI LETTER JA;Lo;0;L;;;;;N;;;;; 112C3;KHUDAWADI LETTER JJA;Lo;0;L;;;;;N;;;;; 112C4;KHUDAWADI LETTER JHA;Lo;0;L;;;;;N;;;;; 112C5;KHUDAWADI LETTER NYA;Lo;0;L;;;;;N;;;;; 112C6;KHUDAWADI LETTER TTA;Lo;0;L;;;;;N;;;;; 112C7;KHUDAWADI LETTER TTHA;Lo;0;L;;;;;N;;;;; 112C8;KHUDAWADI LETTER DDA;Lo;0;L;;;;;N;;;;; 112C9;KHUDAWADI LETTER DDDA;Lo;0;L;;;;;N;;;;; 112CA;KHUDAWADI LETTER RRA;Lo;0;L;;;;;N;;;;; 112CB;KHUDAWADI LETTER DDHA;Lo;0;L;;;;;N;;;;; 112CC;KHUDAWADI LETTER NNA;Lo;0;L;;;;;N;;;;; 112CD;KHUDAWADI LETTER TA;Lo;0;L;;;;;N;;;;; 112CE;KHUDAWADI LETTER THA;Lo;0;L;;;;;N;;;;; 112CF;KHUDAWADI LETTER DA;Lo;0;L;;;;;N;;;;; 112D0;KHUDAWADI LETTER DHA;Lo;0;L;;;;;N;;;;; 112D1;KHUDAWADI LETTER NA;Lo;0;L;;;;;N;;;;; 112D2;KHUDAWADI LETTER PA;Lo;0;L;;;;;N;;;;; 112D3;KHUDAWADI LETTER PHA;Lo;0;L;;;;;N;;;;; 112D4;KHUDAWADI LETTER BA;Lo;0;L;;;;;N;;;;; 112D5;KHUDAWADI LETTER BBA;Lo;0;L;;;;;N;;;;; 112D6;KHUDAWADI LETTER BHA;Lo;0;L;;;;;N;;;;; 112D7;KHUDAWADI LETTER MA;Lo;0;L;;;;;N;;;;; 112D8;KHUDAWADI LETTER YA;Lo;0;L;;;;;N;;;;; 112D9;KHUDAWADI LETTER RA;Lo;0;L;;;;;N;;;;; 112DA;KHUDAWADI LETTER LA;Lo;0;L;;;;;N;;;;; 112DB;KHUDAWADI LETTER VA;Lo;0;L;;;;;N;;;;; 112DC;KHUDAWADI LETTER SHA;Lo;0;L;;;;;N;;;;; 112DD;KHUDAWADI LETTER SA;Lo;0;L;;;;;N;;;;; 112DE;KHUDAWADI LETTER HA;Lo;0;L;;;;;N;;;;; 112DF;KHUDAWADI SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; 112E0;KHUDAWADI VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 112E1;KHUDAWADI VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 112E2;KHUDAWADI VOWEL SIGN II;Mc;0;L;;;;;N;;;;; 112E3;KHUDAWADI VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 112E4;KHUDAWADI VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 112E5;KHUDAWADI VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;; 112E6;KHUDAWADI VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;; 112E7;KHUDAWADI VOWEL SIGN O;Mn;0;NSM;;;;;N;;;;; 112E8;KHUDAWADI VOWEL SIGN AU;Mn;0;NSM;;;;;N;;;;; 112E9;KHUDAWADI SIGN NUKTA;Mn;7;NSM;;;;;N;;;;; 112EA;KHUDAWADI SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 112F0;KHUDAWADI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 112F1;KHUDAWADI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 112F2;KHUDAWADI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 112F3;KHUDAWADI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 112F4;KHUDAWADI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 112F5;KHUDAWADI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 112F6;KHUDAWADI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 112F7;KHUDAWADI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 112F8;KHUDAWADI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 112F9;KHUDAWADI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 11300;GRANTHA SIGN COMBINING ANUSVARA ABOVE;Mn;0;NSM;;;;;N;;;;; 11301;GRANTHA SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 11302;GRANTHA SIGN ANUSVARA;Mc;0;L;;;;;N;;;;; 11303;GRANTHA SIGN VISARGA;Mc;0;L;;;;;N;;;;; 11305;GRANTHA LETTER A;Lo;0;L;;;;;N;;;;; 11306;GRANTHA LETTER AA;Lo;0;L;;;;;N;;;;; 11307;GRANTHA LETTER I;Lo;0;L;;;;;N;;;;; 11308;GRANTHA LETTER II;Lo;0;L;;;;;N;;;;; 11309;GRANTHA LETTER U;Lo;0;L;;;;;N;;;;; 1130A;GRANTHA LETTER UU;Lo;0;L;;;;;N;;;;; 1130B;GRANTHA LETTER VOCALIC R;Lo;0;L;;;;;N;;;;; 1130C;GRANTHA LETTER VOCALIC L;Lo;0;L;;;;;N;;;;; 1130F;GRANTHA LETTER EE;Lo;0;L;;;;;N;;;;; 11310;GRANTHA LETTER AI;Lo;0;L;;;;;N;;;;; 11313;GRANTHA LETTER OO;Lo;0;L;;;;;N;;;;; 11314;GRANTHA LETTER AU;Lo;0;L;;;;;N;;;;; 11315;GRANTHA LETTER KA;Lo;0;L;;;;;N;;;;; 11316;GRANTHA LETTER KHA;Lo;0;L;;;;;N;;;;; 11317;GRANTHA LETTER GA;Lo;0;L;;;;;N;;;;; 11318;GRANTHA LETTER GHA;Lo;0;L;;;;;N;;;;; 11319;GRANTHA LETTER NGA;Lo;0;L;;;;;N;;;;; 1131A;GRANTHA LETTER CA;Lo;0;L;;;;;N;;;;; 1131B;GRANTHA LETTER CHA;Lo;0;L;;;;;N;;;;; 1131C;GRANTHA LETTER JA;Lo;0;L;;;;;N;;;;; 1131D;GRANTHA LETTER JHA;Lo;0;L;;;;;N;;;;; 1131E;GRANTHA LETTER NYA;Lo;0;L;;;;;N;;;;; 1131F;GRANTHA LETTER TTA;Lo;0;L;;;;;N;;;;; 11320;GRANTHA LETTER TTHA;Lo;0;L;;;;;N;;;;; 11321;GRANTHA LETTER DDA;Lo;0;L;;;;;N;;;;; 11322;GRANTHA LETTER DDHA;Lo;0;L;;;;;N;;;;; 11323;GRANTHA LETTER NNA;Lo;0;L;;;;;N;;;;; 11324;GRANTHA LETTER TA;Lo;0;L;;;;;N;;;;; 11325;GRANTHA LETTER THA;Lo;0;L;;;;;N;;;;; 11326;GRANTHA LETTER DA;Lo;0;L;;;;;N;;;;; 11327;GRANTHA LETTER DHA;Lo;0;L;;;;;N;;;;; 11328;GRANTHA LETTER NA;Lo;0;L;;;;;N;;;;; 1132A;GRANTHA LETTER PA;Lo;0;L;;;;;N;;;;; 1132B;GRANTHA LETTER PHA;Lo;0;L;;;;;N;;;;; 1132C;GRANTHA LETTER BA;Lo;0;L;;;;;N;;;;; 1132D;GRANTHA LETTER BHA;Lo;0;L;;;;;N;;;;; 1132E;GRANTHA LETTER MA;Lo;0;L;;;;;N;;;;; 1132F;GRANTHA LETTER YA;Lo;0;L;;;;;N;;;;; 11330;GRANTHA LETTER RA;Lo;0;L;;;;;N;;;;; 11332;GRANTHA LETTER LA;Lo;0;L;;;;;N;;;;; 11333;GRANTHA LETTER LLA;Lo;0;L;;;;;N;;;;; 11335;GRANTHA LETTER VA;Lo;0;L;;;;;N;;;;; 11336;GRANTHA LETTER SHA;Lo;0;L;;;;;N;;;;; 11337;GRANTHA LETTER SSA;Lo;0;L;;;;;N;;;;; 11338;GRANTHA LETTER SA;Lo;0;L;;;;;N;;;;; 11339;GRANTHA LETTER HA;Lo;0;L;;;;;N;;;;; 1133C;GRANTHA SIGN NUKTA;Mn;7;NSM;;;;;N;;;;; 1133D;GRANTHA SIGN AVAGRAHA;Lo;0;L;;;;;N;;;;; 1133E;GRANTHA VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 1133F;GRANTHA VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 11340;GRANTHA VOWEL SIGN II;Mn;0;NSM;;;;;N;;;;; 11341;GRANTHA VOWEL SIGN U;Mc;0;L;;;;;N;;;;; 11342;GRANTHA VOWEL SIGN UU;Mc;0;L;;;;;N;;;;; 11343;GRANTHA VOWEL SIGN VOCALIC R;Mc;0;L;;;;;N;;;;; 11344;GRANTHA VOWEL SIGN VOCALIC RR;Mc;0;L;;;;;N;;;;; 11347;GRANTHA VOWEL SIGN EE;Mc;0;L;;;;;N;;;;; 11348;GRANTHA VOWEL SIGN AI;Mc;0;L;;;;;N;;;;; 1134B;GRANTHA VOWEL SIGN OO;Mc;0;L;11347 1133E;;;;N;;;;; 1134C;GRANTHA VOWEL SIGN AU;Mc;0;L;11347 11357;;;;N;;;;; 1134D;GRANTHA SIGN VIRAMA;Mc;9;L;;;;;N;;;;; 11350;GRANTHA OM;Lo;0;L;;;;;N;;;;; 11357;GRANTHA AU LENGTH MARK;Mc;0;L;;;;;N;;;;; 1135D;GRANTHA SIGN PLUTA;Lo;0;L;;;;;N;;;;; 1135E;GRANTHA LETTER VEDIC ANUSVARA;Lo;0;L;;;;;N;;;;; 1135F;GRANTHA LETTER VEDIC DOUBLE ANUSVARA;Lo;0;L;;;;;N;;;;; 11360;GRANTHA LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 11361;GRANTHA LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; 11362;GRANTHA VOWEL SIGN VOCALIC L;Mc;0;L;;;;;N;;;;; 11363;GRANTHA VOWEL SIGN VOCALIC LL;Mc;0;L;;;;;N;;;;; 11366;COMBINING GRANTHA DIGIT ZERO;Mn;230;NSM;;;;;N;;;;; 11367;COMBINING GRANTHA DIGIT ONE;Mn;230;NSM;;;;;N;;;;; 11368;COMBINING GRANTHA DIGIT TWO;Mn;230;NSM;;;;;N;;;;; 11369;COMBINING GRANTHA DIGIT THREE;Mn;230;NSM;;;;;N;;;;; 1136A;COMBINING GRANTHA DIGIT FOUR;Mn;230;NSM;;;;;N;;;;; 1136B;COMBINING GRANTHA DIGIT FIVE;Mn;230;NSM;;;;;N;;;;; 1136C;COMBINING GRANTHA DIGIT SIX;Mn;230;NSM;;;;;N;;;;; 11370;COMBINING GRANTHA LETTER A;Mn;230;NSM;;;;;N;;;;; 11371;COMBINING GRANTHA LETTER KA;Mn;230;NSM;;;;;N;;;;; 11372;COMBINING GRANTHA LETTER NA;Mn;230;NSM;;;;;N;;;;; 11373;COMBINING GRANTHA LETTER VI;Mn;230;NSM;;;;;N;;;;; 11374;COMBINING GRANTHA LETTER PA;Mn;230;NSM;;;;;N;;;;; 11400;NEWA LETTER A;Lo;0;L;;;;;N;;;;; 11401;NEWA LETTER AA;Lo;0;L;;;;;N;;;;; 11402;NEWA LETTER I;Lo;0;L;;;;;N;;;;; 11403;NEWA LETTER II;Lo;0;L;;;;;N;;;;; 11404;NEWA LETTER U;Lo;0;L;;;;;N;;;;; 11405;NEWA LETTER UU;Lo;0;L;;;;;N;;;;; 11406;NEWA LETTER VOCALIC R;Lo;0;L;;;;;N;;;;; 11407;NEWA LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 11408;NEWA LETTER VOCALIC L;Lo;0;L;;;;;N;;;;; 11409;NEWA LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; 1140A;NEWA LETTER E;Lo;0;L;;;;;N;;;;; 1140B;NEWA LETTER AI;Lo;0;L;;;;;N;;;;; 1140C;NEWA LETTER O;Lo;0;L;;;;;N;;;;; 1140D;NEWA LETTER AU;Lo;0;L;;;;;N;;;;; 1140E;NEWA LETTER KA;Lo;0;L;;;;;N;;;;; 1140F;NEWA LETTER KHA;Lo;0;L;;;;;N;;;;; 11410;NEWA LETTER GA;Lo;0;L;;;;;N;;;;; 11411;NEWA LETTER GHA;Lo;0;L;;;;;N;;;;; 11412;NEWA LETTER NGA;Lo;0;L;;;;;N;;;;; 11413;NEWA LETTER NGHA;Lo;0;L;;;;;N;;;;; 11414;NEWA LETTER CA;Lo;0;L;;;;;N;;;;; 11415;NEWA LETTER CHA;Lo;0;L;;;;;N;;;;; 11416;NEWA LETTER JA;Lo;0;L;;;;;N;;;;; 11417;NEWA LETTER JHA;Lo;0;L;;;;;N;;;;; 11418;NEWA LETTER NYA;Lo;0;L;;;;;N;;;;; 11419;NEWA LETTER NYHA;Lo;0;L;;;;;N;;;;; 1141A;NEWA LETTER TTA;Lo;0;L;;;;;N;;;;; 1141B;NEWA LETTER TTHA;Lo;0;L;;;;;N;;;;; 1141C;NEWA LETTER DDA;Lo;0;L;;;;;N;;;;; 1141D;NEWA LETTER DDHA;Lo;0;L;;;;;N;;;;; 1141E;NEWA LETTER NNA;Lo;0;L;;;;;N;;;;; 1141F;NEWA LETTER TA;Lo;0;L;;;;;N;;;;; 11420;NEWA LETTER THA;Lo;0;L;;;;;N;;;;; 11421;NEWA LETTER DA;Lo;0;L;;;;;N;;;;; 11422;NEWA LETTER DHA;Lo;0;L;;;;;N;;;;; 11423;NEWA LETTER NA;Lo;0;L;;;;;N;;;;; 11424;NEWA LETTER NHA;Lo;0;L;;;;;N;;;;; 11425;NEWA LETTER PA;Lo;0;L;;;;;N;;;;; 11426;NEWA LETTER PHA;Lo;0;L;;;;;N;;;;; 11427;NEWA LETTER BA;Lo;0;L;;;;;N;;;;; 11428;NEWA LETTER BHA;Lo;0;L;;;;;N;;;;; 11429;NEWA LETTER MA;Lo;0;L;;;;;N;;;;; 1142A;NEWA LETTER MHA;Lo;0;L;;;;;N;;;;; 1142B;NEWA LETTER YA;Lo;0;L;;;;;N;;;;; 1142C;NEWA LETTER RA;Lo;0;L;;;;;N;;;;; 1142D;NEWA LETTER RHA;Lo;0;L;;;;;N;;;;; 1142E;NEWA LETTER LA;Lo;0;L;;;;;N;;;;; 1142F;NEWA LETTER LHA;Lo;0;L;;;;;N;;;;; 11430;NEWA LETTER WA;Lo;0;L;;;;;N;;;;; 11431;NEWA LETTER SHA;Lo;0;L;;;;;N;;;;; 11432;NEWA LETTER SSA;Lo;0;L;;;;;N;;;;; 11433;NEWA LETTER SA;Lo;0;L;;;;;N;;;;; 11434;NEWA LETTER HA;Lo;0;L;;;;;N;;;;; 11435;NEWA VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 11436;NEWA VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 11437;NEWA VOWEL SIGN II;Mc;0;L;;;;;N;;;;; 11438;NEWA VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 11439;NEWA VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 1143A;NEWA VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;; 1143B;NEWA VOWEL SIGN VOCALIC RR;Mn;0;NSM;;;;;N;;;;; 1143C;NEWA VOWEL SIGN VOCALIC L;Mn;0;NSM;;;;;N;;;;; 1143D;NEWA VOWEL SIGN VOCALIC LL;Mn;0;NSM;;;;;N;;;;; 1143E;NEWA VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;; 1143F;NEWA VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;; 11440;NEWA VOWEL SIGN O;Mc;0;L;;;;;N;;;;; 11441;NEWA VOWEL SIGN AU;Mc;0;L;;;;;N;;;;; 11442;NEWA SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 11443;NEWA SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 11444;NEWA SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; 11445;NEWA SIGN VISARGA;Mc;0;L;;;;;N;;;;; 11446;NEWA SIGN NUKTA;Mn;7;NSM;;;;;N;;;;; 11447;NEWA SIGN AVAGRAHA;Lo;0;L;;;;;N;;;;; 11448;NEWA SIGN FINAL ANUSVARA;Lo;0;L;;;;;N;;;;; 11449;NEWA OM;Lo;0;L;;;;;N;;;;; 1144A;NEWA SIDDHI;Lo;0;L;;;;;N;;;;; 1144B;NEWA DANDA;Po;0;L;;;;;N;;;;; 1144C;NEWA DOUBLE DANDA;Po;0;L;;;;;N;;;;; 1144D;NEWA COMMA;Po;0;L;;;;;N;;;;; 1144E;NEWA GAP FILLER;Po;0;L;;;;;N;;;;; 1144F;NEWA ABBREVIATION SIGN;Po;0;L;;;;;N;;;;; 11450;NEWA DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 11451;NEWA DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 11452;NEWA DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 11453;NEWA DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 11454;NEWA DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 11455;NEWA DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 11456;NEWA DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 11457;NEWA DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 11458;NEWA DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 11459;NEWA DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 1145B;NEWA PLACEHOLDER MARK;Po;0;L;;;;;N;;;;; 1145D;NEWA INSERTION SIGN;Po;0;L;;;;;N;;;;; 11480;TIRHUTA ANJI;Lo;0;L;;;;;N;;;;; 11481;TIRHUTA LETTER A;Lo;0;L;;;;;N;;;;; 11482;TIRHUTA LETTER AA;Lo;0;L;;;;;N;;;;; 11483;TIRHUTA LETTER I;Lo;0;L;;;;;N;;;;; 11484;TIRHUTA LETTER II;Lo;0;L;;;;;N;;;;; 11485;TIRHUTA LETTER U;Lo;0;L;;;;;N;;;;; 11486;TIRHUTA LETTER UU;Lo;0;L;;;;;N;;;;; 11487;TIRHUTA LETTER VOCALIC R;Lo;0;L;;;;;N;;;;; 11488;TIRHUTA LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 11489;TIRHUTA LETTER VOCALIC L;Lo;0;L;;;;;N;;;;; 1148A;TIRHUTA LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; 1148B;TIRHUTA LETTER E;Lo;0;L;;;;;N;;;;; 1148C;TIRHUTA LETTER AI;Lo;0;L;;;;;N;;;;; 1148D;TIRHUTA LETTER O;Lo;0;L;;;;;N;;;;; 1148E;TIRHUTA LETTER AU;Lo;0;L;;;;;N;;;;; 1148F;TIRHUTA LETTER KA;Lo;0;L;;;;;N;;;;; 11490;TIRHUTA LETTER KHA;Lo;0;L;;;;;N;;;;; 11491;TIRHUTA LETTER GA;Lo;0;L;;;;;N;;;;; 11492;TIRHUTA LETTER GHA;Lo;0;L;;;;;N;;;;; 11493;TIRHUTA LETTER NGA;Lo;0;L;;;;;N;;;;; 11494;TIRHUTA LETTER CA;Lo;0;L;;;;;N;;;;; 11495;TIRHUTA LETTER CHA;Lo;0;L;;;;;N;;;;; 11496;TIRHUTA LETTER JA;Lo;0;L;;;;;N;;;;; 11497;TIRHUTA LETTER JHA;Lo;0;L;;;;;N;;;;; 11498;TIRHUTA LETTER NYA;Lo;0;L;;;;;N;;;;; 11499;TIRHUTA LETTER TTA;Lo;0;L;;;;;N;;;;; 1149A;TIRHUTA LETTER TTHA;Lo;0;L;;;;;N;;;;; 1149B;TIRHUTA LETTER DDA;Lo;0;L;;;;;N;;;;; 1149C;TIRHUTA LETTER DDHA;Lo;0;L;;;;;N;;;;; 1149D;TIRHUTA LETTER NNA;Lo;0;L;;;;;N;;;;; 1149E;TIRHUTA LETTER TA;Lo;0;L;;;;;N;;;;; 1149F;TIRHUTA LETTER THA;Lo;0;L;;;;;N;;;;; 114A0;TIRHUTA LETTER DA;Lo;0;L;;;;;N;;;;; 114A1;TIRHUTA LETTER DHA;Lo;0;L;;;;;N;;;;; 114A2;TIRHUTA LETTER NA;Lo;0;L;;;;;N;;;;; 114A3;TIRHUTA LETTER PA;Lo;0;L;;;;;N;;;;; 114A4;TIRHUTA LETTER PHA;Lo;0;L;;;;;N;;;;; 114A5;TIRHUTA LETTER BA;Lo;0;L;;;;;N;;;;; 114A6;TIRHUTA LETTER BHA;Lo;0;L;;;;;N;;;;; 114A7;TIRHUTA LETTER MA;Lo;0;L;;;;;N;;;;; 114A8;TIRHUTA LETTER YA;Lo;0;L;;;;;N;;;;; 114A9;TIRHUTA LETTER RA;Lo;0;L;;;;;N;;;;; 114AA;TIRHUTA LETTER LA;Lo;0;L;;;;;N;;;;; 114AB;TIRHUTA LETTER VA;Lo;0;L;;;;;N;;;;; 114AC;TIRHUTA LETTER SHA;Lo;0;L;;;;;N;;;;; 114AD;TIRHUTA LETTER SSA;Lo;0;L;;;;;N;;;;; 114AE;TIRHUTA LETTER SA;Lo;0;L;;;;;N;;;;; 114AF;TIRHUTA LETTER HA;Lo;0;L;;;;;N;;;;; 114B0;TIRHUTA VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 114B1;TIRHUTA VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 114B2;TIRHUTA VOWEL SIGN II;Mc;0;L;;;;;N;;;;; 114B3;TIRHUTA VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 114B4;TIRHUTA VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 114B5;TIRHUTA VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;; 114B6;TIRHUTA VOWEL SIGN VOCALIC RR;Mn;0;NSM;;;;;N;;;;; 114B7;TIRHUTA VOWEL SIGN VOCALIC L;Mn;0;NSM;;;;;N;;;;; 114B8;TIRHUTA VOWEL SIGN VOCALIC LL;Mn;0;NSM;;;;;N;;;;; 114B9;TIRHUTA VOWEL SIGN E;Mc;0;L;;;;;N;;;;; 114BA;TIRHUTA VOWEL SIGN SHORT E;Mn;0;NSM;;;;;N;;;;; 114BB;TIRHUTA VOWEL SIGN AI;Mc;0;L;114B9 114BA;;;;N;;;;; 114BC;TIRHUTA VOWEL SIGN O;Mc;0;L;114B9 114B0;;;;N;;;;; 114BD;TIRHUTA VOWEL SIGN SHORT O;Mc;0;L;;;;;N;;;;; 114BE;TIRHUTA VOWEL SIGN AU;Mc;0;L;114B9 114BD;;;;N;;;;; 114BF;TIRHUTA SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 114C0;TIRHUTA SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; 114C1;TIRHUTA SIGN VISARGA;Mc;0;L;;;;;N;;;;; 114C2;TIRHUTA SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 114C3;TIRHUTA SIGN NUKTA;Mn;7;NSM;;;;;N;;;;; 114C4;TIRHUTA SIGN AVAGRAHA;Lo;0;L;;;;;N;;;;; 114C5;TIRHUTA GVANG;Lo;0;L;;;;;N;;;;; 114C6;TIRHUTA ABBREVIATION SIGN;Po;0;L;;;;;N;;;;; 114C7;TIRHUTA OM;Lo;0;L;;;;;N;;;;; 114D0;TIRHUTA DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 114D1;TIRHUTA DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 114D2;TIRHUTA DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 114D3;TIRHUTA DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 114D4;TIRHUTA DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 114D5;TIRHUTA DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 114D6;TIRHUTA DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 114D7;TIRHUTA DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 114D8;TIRHUTA DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 114D9;TIRHUTA DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 11580;SIDDHAM LETTER A;Lo;0;L;;;;;N;;;;; 11581;SIDDHAM LETTER AA;Lo;0;L;;;;;N;;;;; 11582;SIDDHAM LETTER I;Lo;0;L;;;;;N;;;;; 11583;SIDDHAM LETTER II;Lo;0;L;;;;;N;;;;; 11584;SIDDHAM LETTER U;Lo;0;L;;;;;N;;;;; 11585;SIDDHAM LETTER UU;Lo;0;L;;;;;N;;;;; 11586;SIDDHAM LETTER VOCALIC R;Lo;0;L;;;;;N;;;;; 11587;SIDDHAM LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 11588;SIDDHAM LETTER VOCALIC L;Lo;0;L;;;;;N;;;;; 11589;SIDDHAM LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; 1158A;SIDDHAM LETTER E;Lo;0;L;;;;;N;;;;; 1158B;SIDDHAM LETTER AI;Lo;0;L;;;;;N;;;;; 1158C;SIDDHAM LETTER O;Lo;0;L;;;;;N;;;;; 1158D;SIDDHAM LETTER AU;Lo;0;L;;;;;N;;;;; 1158E;SIDDHAM LETTER KA;Lo;0;L;;;;;N;;;;; 1158F;SIDDHAM LETTER KHA;Lo;0;L;;;;;N;;;;; 11590;SIDDHAM LETTER GA;Lo;0;L;;;;;N;;;;; 11591;SIDDHAM LETTER GHA;Lo;0;L;;;;;N;;;;; 11592;SIDDHAM LETTER NGA;Lo;0;L;;;;;N;;;;; 11593;SIDDHAM LETTER CA;Lo;0;L;;;;;N;;;;; 11594;SIDDHAM LETTER CHA;Lo;0;L;;;;;N;;;;; 11595;SIDDHAM LETTER JA;Lo;0;L;;;;;N;;;;; 11596;SIDDHAM LETTER JHA;Lo;0;L;;;;;N;;;;; 11597;SIDDHAM LETTER NYA;Lo;0;L;;;;;N;;;;; 11598;SIDDHAM LETTER TTA;Lo;0;L;;;;;N;;;;; 11599;SIDDHAM LETTER TTHA;Lo;0;L;;;;;N;;;;; 1159A;SIDDHAM LETTER DDA;Lo;0;L;;;;;N;;;;; 1159B;SIDDHAM LETTER DDHA;Lo;0;L;;;;;N;;;;; 1159C;SIDDHAM LETTER NNA;Lo;0;L;;;;;N;;;;; 1159D;SIDDHAM LETTER TA;Lo;0;L;;;;;N;;;;; 1159E;SIDDHAM LETTER THA;Lo;0;L;;;;;N;;;;; 1159F;SIDDHAM LETTER DA;Lo;0;L;;;;;N;;;;; 115A0;SIDDHAM LETTER DHA;Lo;0;L;;;;;N;;;;; 115A1;SIDDHAM LETTER NA;Lo;0;L;;;;;N;;;;; 115A2;SIDDHAM LETTER PA;Lo;0;L;;;;;N;;;;; 115A3;SIDDHAM LETTER PHA;Lo;0;L;;;;;N;;;;; 115A4;SIDDHAM LETTER BA;Lo;0;L;;;;;N;;;;; 115A5;SIDDHAM LETTER BHA;Lo;0;L;;;;;N;;;;; 115A6;SIDDHAM LETTER MA;Lo;0;L;;;;;N;;;;; 115A7;SIDDHAM LETTER YA;Lo;0;L;;;;;N;;;;; 115A8;SIDDHAM LETTER RA;Lo;0;L;;;;;N;;;;; 115A9;SIDDHAM LETTER LA;Lo;0;L;;;;;N;;;;; 115AA;SIDDHAM LETTER VA;Lo;0;L;;;;;N;;;;; 115AB;SIDDHAM LETTER SHA;Lo;0;L;;;;;N;;;;; 115AC;SIDDHAM LETTER SSA;Lo;0;L;;;;;N;;;;; 115AD;SIDDHAM LETTER SA;Lo;0;L;;;;;N;;;;; 115AE;SIDDHAM LETTER HA;Lo;0;L;;;;;N;;;;; 115AF;SIDDHAM VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 115B0;SIDDHAM VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 115B1;SIDDHAM VOWEL SIGN II;Mc;0;L;;;;;N;;;;; 115B2;SIDDHAM VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 115B3;SIDDHAM VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 115B4;SIDDHAM VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;; 115B5;SIDDHAM VOWEL SIGN VOCALIC RR;Mn;0;NSM;;;;;N;;;;; 115B8;SIDDHAM VOWEL SIGN E;Mc;0;L;;;;;N;;;;; 115B9;SIDDHAM VOWEL SIGN AI;Mc;0;L;;;;;N;;;;; 115BA;SIDDHAM VOWEL SIGN O;Mc;0;L;115B8 115AF;;;;N;;;;; 115BB;SIDDHAM VOWEL SIGN AU;Mc;0;L;115B9 115AF;;;;N;;;;; 115BC;SIDDHAM SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 115BD;SIDDHAM SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; 115BE;SIDDHAM SIGN VISARGA;Mc;0;L;;;;;N;;;;; 115BF;SIDDHAM SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 115C0;SIDDHAM SIGN NUKTA;Mn;7;NSM;;;;;N;;;;; 115C1;SIDDHAM SIGN SIDDHAM;Po;0;L;;;;;N;;;;; 115C2;SIDDHAM DANDA;Po;0;L;;;;;N;;;;; 115C3;SIDDHAM DOUBLE DANDA;Po;0;L;;;;;N;;;;; 115C4;SIDDHAM SEPARATOR DOT;Po;0;L;;;;;N;;;;; 115C5;SIDDHAM SEPARATOR BAR;Po;0;L;;;;;N;;;;; 115C6;SIDDHAM REPETITION MARK-1;Po;0;L;;;;;N;;;;; 115C7;SIDDHAM REPETITION MARK-2;Po;0;L;;;;;N;;;;; 115C8;SIDDHAM REPETITION MARK-3;Po;0;L;;;;;N;;;;; 115C9;SIDDHAM END OF TEXT MARK;Po;0;L;;;;;N;;;;; 115CA;SIDDHAM SECTION MARK WITH TRIDENT AND U-SHAPED ORNAMENTS;Po;0;L;;;;;N;;;;; 115CB;SIDDHAM SECTION MARK WITH TRIDENT AND DOTTED CRESCENTS;Po;0;L;;;;;N;;;;; 115CC;SIDDHAM SECTION MARK WITH RAYS AND DOTTED CRESCENTS;Po;0;L;;;;;N;;;;; 115CD;SIDDHAM SECTION MARK WITH RAYS AND DOTTED DOUBLE CRESCENTS;Po;0;L;;;;;N;;;;; 115CE;SIDDHAM SECTION MARK WITH RAYS AND DOTTED TRIPLE CRESCENTS;Po;0;L;;;;;N;;;;; 115CF;SIDDHAM SECTION MARK DOUBLE RING;Po;0;L;;;;;N;;;;; 115D0;SIDDHAM SECTION MARK DOUBLE RING WITH RAYS;Po;0;L;;;;;N;;;;; 115D1;SIDDHAM SECTION MARK WITH DOUBLE CRESCENTS;Po;0;L;;;;;N;;;;; 115D2;SIDDHAM SECTION MARK WITH TRIPLE CRESCENTS;Po;0;L;;;;;N;;;;; 115D3;SIDDHAM SECTION MARK WITH QUADRUPLE CRESCENTS;Po;0;L;;;;;N;;;;; 115D4;SIDDHAM SECTION MARK WITH SEPTUPLE CRESCENTS;Po;0;L;;;;;N;;;;; 115D5;SIDDHAM SECTION MARK WITH CIRCLES AND RAYS;Po;0;L;;;;;N;;;;; 115D6;SIDDHAM SECTION MARK WITH CIRCLES AND TWO ENCLOSURES;Po;0;L;;;;;N;;;;; 115D7;SIDDHAM SECTION MARK WITH CIRCLES AND FOUR ENCLOSURES;Po;0;L;;;;;N;;;;; 115D8;SIDDHAM LETTER THREE-CIRCLE ALTERNATE I;Lo;0;L;;;;;N;;;;; 115D9;SIDDHAM LETTER TWO-CIRCLE ALTERNATE I;Lo;0;L;;;;;N;;;;; 115DA;SIDDHAM LETTER TWO-CIRCLE ALTERNATE II;Lo;0;L;;;;;N;;;;; 115DB;SIDDHAM LETTER ALTERNATE U;Lo;0;L;;;;;N;;;;; 115DC;SIDDHAM VOWEL SIGN ALTERNATE U;Mn;0;NSM;;;;;N;;;;; 115DD;SIDDHAM VOWEL SIGN ALTERNATE UU;Mn;0;NSM;;;;;N;;;;; 11600;MODI LETTER A;Lo;0;L;;;;;N;;;;; 11601;MODI LETTER AA;Lo;0;L;;;;;N;;;;; 11602;MODI LETTER I;Lo;0;L;;;;;N;;;;; 11603;MODI LETTER II;Lo;0;L;;;;;N;;;;; 11604;MODI LETTER U;Lo;0;L;;;;;N;;;;; 11605;MODI LETTER UU;Lo;0;L;;;;;N;;;;; 11606;MODI LETTER VOCALIC R;Lo;0;L;;;;;N;;;;; 11607;MODI LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 11608;MODI LETTER VOCALIC L;Lo;0;L;;;;;N;;;;; 11609;MODI LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; 1160A;MODI LETTER E;Lo;0;L;;;;;N;;;;; 1160B;MODI LETTER AI;Lo;0;L;;;;;N;;;;; 1160C;MODI LETTER O;Lo;0;L;;;;;N;;;;; 1160D;MODI LETTER AU;Lo;0;L;;;;;N;;;;; 1160E;MODI LETTER KA;Lo;0;L;;;;;N;;;;; 1160F;MODI LETTER KHA;Lo;0;L;;;;;N;;;;; 11610;MODI LETTER GA;Lo;0;L;;;;;N;;;;; 11611;MODI LETTER GHA;Lo;0;L;;;;;N;;;;; 11612;MODI LETTER NGA;Lo;0;L;;;;;N;;;;; 11613;MODI LETTER CA;Lo;0;L;;;;;N;;;;; 11614;MODI LETTER CHA;Lo;0;L;;;;;N;;;;; 11615;MODI LETTER JA;Lo;0;L;;;;;N;;;;; 11616;MODI LETTER JHA;Lo;0;L;;;;;N;;;;; 11617;MODI LETTER NYA;Lo;0;L;;;;;N;;;;; 11618;MODI LETTER TTA;Lo;0;L;;;;;N;;;;; 11619;MODI LETTER TTHA;Lo;0;L;;;;;N;;;;; 1161A;MODI LETTER DDA;Lo;0;L;;;;;N;;;;; 1161B;MODI LETTER DDHA;Lo;0;L;;;;;N;;;;; 1161C;MODI LETTER NNA;Lo;0;L;;;;;N;;;;; 1161D;MODI LETTER TA;Lo;0;L;;;;;N;;;;; 1161E;MODI LETTER THA;Lo;0;L;;;;;N;;;;; 1161F;MODI LETTER DA;Lo;0;L;;;;;N;;;;; 11620;MODI LETTER DHA;Lo;0;L;;;;;N;;;;; 11621;MODI LETTER NA;Lo;0;L;;;;;N;;;;; 11622;MODI LETTER PA;Lo;0;L;;;;;N;;;;; 11623;MODI LETTER PHA;Lo;0;L;;;;;N;;;;; 11624;MODI LETTER BA;Lo;0;L;;;;;N;;;;; 11625;MODI LETTER BHA;Lo;0;L;;;;;N;;;;; 11626;MODI LETTER MA;Lo;0;L;;;;;N;;;;; 11627;MODI LETTER YA;Lo;0;L;;;;;N;;;;; 11628;MODI LETTER RA;Lo;0;L;;;;;N;;;;; 11629;MODI LETTER LA;Lo;0;L;;;;;N;;;;; 1162A;MODI LETTER VA;Lo;0;L;;;;;N;;;;; 1162B;MODI LETTER SHA;Lo;0;L;;;;;N;;;;; 1162C;MODI LETTER SSA;Lo;0;L;;;;;N;;;;; 1162D;MODI LETTER SA;Lo;0;L;;;;;N;;;;; 1162E;MODI LETTER HA;Lo;0;L;;;;;N;;;;; 1162F;MODI LETTER LLA;Lo;0;L;;;;;N;;;;; 11630;MODI VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 11631;MODI VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 11632;MODI VOWEL SIGN II;Mc;0;L;;;;;N;;;;; 11633;MODI VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 11634;MODI VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 11635;MODI VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;; 11636;MODI VOWEL SIGN VOCALIC RR;Mn;0;NSM;;;;;N;;;;; 11637;MODI VOWEL SIGN VOCALIC L;Mn;0;NSM;;;;;N;;;;; 11638;MODI VOWEL SIGN VOCALIC LL;Mn;0;NSM;;;;;N;;;;; 11639;MODI VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;; 1163A;MODI VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;; 1163B;MODI VOWEL SIGN O;Mc;0;L;;;;;N;;;;; 1163C;MODI VOWEL SIGN AU;Mc;0;L;;;;;N;;;;; 1163D;MODI SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; 1163E;MODI SIGN VISARGA;Mc;0;L;;;;;N;;;;; 1163F;MODI SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 11640;MODI SIGN ARDHACANDRA;Mn;0;NSM;;;;;N;;;;; 11641;MODI DANDA;Po;0;L;;;;;N;;;;; 11642;MODI DOUBLE DANDA;Po;0;L;;;;;N;;;;; 11643;MODI ABBREVIATION SIGN;Po;0;L;;;;;N;;;;; 11644;MODI SIGN HUVA;Lo;0;L;;;;;N;;;;; 11650;MODI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 11651;MODI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 11652;MODI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 11653;MODI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 11654;MODI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 11655;MODI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 11656;MODI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 11657;MODI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 11658;MODI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 11659;MODI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 11660;MONGOLIAN BIRGA WITH ORNAMENT;Po;0;ON;;;;;N;;;;; 11661;MONGOLIAN ROTATED BIRGA;Po;0;ON;;;;;N;;;;; 11662;MONGOLIAN DOUBLE BIRGA WITH ORNAMENT;Po;0;ON;;;;;N;;;;; 11663;MONGOLIAN TRIPLE BIRGA WITH ORNAMENT;Po;0;ON;;;;;N;;;;; 11664;MONGOLIAN BIRGA WITH DOUBLE ORNAMENT;Po;0;ON;;;;;N;;;;; 11665;MONGOLIAN ROTATED BIRGA WITH ORNAMENT;Po;0;ON;;;;;N;;;;; 11666;MONGOLIAN ROTATED BIRGA WITH DOUBLE ORNAMENT;Po;0;ON;;;;;N;;;;; 11667;MONGOLIAN INVERTED BIRGA;Po;0;ON;;;;;N;;;;; 11668;MONGOLIAN INVERTED BIRGA WITH DOUBLE ORNAMENT;Po;0;ON;;;;;N;;;;; 11669;MONGOLIAN SWIRL BIRGA;Po;0;ON;;;;;N;;;;; 1166A;MONGOLIAN SWIRL BIRGA WITH ORNAMENT;Po;0;ON;;;;;N;;;;; 1166B;MONGOLIAN SWIRL BIRGA WITH DOUBLE ORNAMENT;Po;0;ON;;;;;N;;;;; 1166C;MONGOLIAN TURNED SWIRL BIRGA WITH DOUBLE ORNAMENT;Po;0;ON;;;;;N;;;;; 11680;TAKRI LETTER A;Lo;0;L;;;;;N;;;;; 11681;TAKRI LETTER AA;Lo;0;L;;;;;N;;;;; 11682;TAKRI LETTER I;Lo;0;L;;;;;N;;;;; 11683;TAKRI LETTER II;Lo;0;L;;;;;N;;;;; 11684;TAKRI LETTER U;Lo;0;L;;;;;N;;;;; 11685;TAKRI LETTER UU;Lo;0;L;;;;;N;;;;; 11686;TAKRI LETTER E;Lo;0;L;;;;;N;;;;; 11687;TAKRI LETTER AI;Lo;0;L;;;;;N;;;;; 11688;TAKRI LETTER O;Lo;0;L;;;;;N;;;;; 11689;TAKRI LETTER AU;Lo;0;L;;;;;N;;;;; 1168A;TAKRI LETTER KA;Lo;0;L;;;;;N;;;;; 1168B;TAKRI LETTER KHA;Lo;0;L;;;;;N;;;;; 1168C;TAKRI LETTER GA;Lo;0;L;;;;;N;;;;; 1168D;TAKRI LETTER GHA;Lo;0;L;;;;;N;;;;; 1168E;TAKRI LETTER NGA;Lo;0;L;;;;;N;;;;; 1168F;TAKRI LETTER CA;Lo;0;L;;;;;N;;;;; 11690;TAKRI LETTER CHA;Lo;0;L;;;;;N;;;;; 11691;TAKRI LETTER JA;Lo;0;L;;;;;N;;;;; 11692;TAKRI LETTER JHA;Lo;0;L;;;;;N;;;;; 11693;TAKRI LETTER NYA;Lo;0;L;;;;;N;;;;; 11694;TAKRI LETTER TTA;Lo;0;L;;;;;N;;;;; 11695;TAKRI LETTER TTHA;Lo;0;L;;;;;N;;;;; 11696;TAKRI LETTER DDA;Lo;0;L;;;;;N;;;;; 11697;TAKRI LETTER DDHA;Lo;0;L;;;;;N;;;;; 11698;TAKRI LETTER NNA;Lo;0;L;;;;;N;;;;; 11699;TAKRI LETTER TA;Lo;0;L;;;;;N;;;;; 1169A;TAKRI LETTER THA;Lo;0;L;;;;;N;;;;; 1169B;TAKRI LETTER DA;Lo;0;L;;;;;N;;;;; 1169C;TAKRI LETTER DHA;Lo;0;L;;;;;N;;;;; 1169D;TAKRI LETTER NA;Lo;0;L;;;;;N;;;;; 1169E;TAKRI LETTER PA;Lo;0;L;;;;;N;;;;; 1169F;TAKRI LETTER PHA;Lo;0;L;;;;;N;;;;; 116A0;TAKRI LETTER BA;Lo;0;L;;;;;N;;;;; 116A1;TAKRI LETTER BHA;Lo;0;L;;;;;N;;;;; 116A2;TAKRI LETTER MA;Lo;0;L;;;;;N;;;;; 116A3;TAKRI LETTER YA;Lo;0;L;;;;;N;;;;; 116A4;TAKRI LETTER RA;Lo;0;L;;;;;N;;;;; 116A5;TAKRI LETTER LA;Lo;0;L;;;;;N;;;;; 116A6;TAKRI LETTER VA;Lo;0;L;;;;;N;;;;; 116A7;TAKRI LETTER SHA;Lo;0;L;;;;;N;;;;; 116A8;TAKRI LETTER SA;Lo;0;L;;;;;N;;;;; 116A9;TAKRI LETTER HA;Lo;0;L;;;;;N;;;;; 116AA;TAKRI LETTER RRA;Lo;0;L;;;;;N;;;;; 116AB;TAKRI SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; 116AC;TAKRI SIGN VISARGA;Mc;0;L;;;;;N;;;;; 116AD;TAKRI VOWEL SIGN AA;Mn;0;NSM;;;;;N;;;;; 116AE;TAKRI VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 116AF;TAKRI VOWEL SIGN II;Mc;0;L;;;;;N;;;;; 116B0;TAKRI VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 116B1;TAKRI VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 116B2;TAKRI VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;; 116B3;TAKRI VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;; 116B4;TAKRI VOWEL SIGN O;Mn;0;NSM;;;;;N;;;;; 116B5;TAKRI VOWEL SIGN AU;Mn;0;NSM;;;;;N;;;;; 116B6;TAKRI SIGN VIRAMA;Mc;9;L;;;;;N;;;;; 116B7;TAKRI SIGN NUKTA;Mn;7;NSM;;;;;N;;;;; 116C0;TAKRI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 116C1;TAKRI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 116C2;TAKRI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 116C3;TAKRI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 116C4;TAKRI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 116C5;TAKRI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 116C6;TAKRI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 116C7;TAKRI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 116C8;TAKRI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 116C9;TAKRI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 11700;AHOM LETTER KA;Lo;0;L;;;;;N;;;;; 11701;AHOM LETTER KHA;Lo;0;L;;;;;N;;;;; 11702;AHOM LETTER NGA;Lo;0;L;;;;;N;;;;; 11703;AHOM LETTER NA;Lo;0;L;;;;;N;;;;; 11704;AHOM LETTER TA;Lo;0;L;;;;;N;;;;; 11705;AHOM LETTER ALTERNATE TA;Lo;0;L;;;;;N;;;;; 11706;AHOM LETTER PA;Lo;0;L;;;;;N;;;;; 11707;AHOM LETTER PHA;Lo;0;L;;;;;N;;;;; 11708;AHOM LETTER BA;Lo;0;L;;;;;N;;;;; 11709;AHOM LETTER MA;Lo;0;L;;;;;N;;;;; 1170A;AHOM LETTER JA;Lo;0;L;;;;;N;;;;; 1170B;AHOM LETTER CHA;Lo;0;L;;;;;N;;;;; 1170C;AHOM LETTER THA;Lo;0;L;;;;;N;;;;; 1170D;AHOM LETTER RA;Lo;0;L;;;;;N;;;;; 1170E;AHOM LETTER LA;Lo;0;L;;;;;N;;;;; 1170F;AHOM LETTER SA;Lo;0;L;;;;;N;;;;; 11710;AHOM LETTER NYA;Lo;0;L;;;;;N;;;;; 11711;AHOM LETTER HA;Lo;0;L;;;;;N;;;;; 11712;AHOM LETTER A;Lo;0;L;;;;;N;;;;; 11713;AHOM LETTER DA;Lo;0;L;;;;;N;;;;; 11714;AHOM LETTER DHA;Lo;0;L;;;;;N;;;;; 11715;AHOM LETTER GA;Lo;0;L;;;;;N;;;;; 11716;AHOM LETTER ALTERNATE GA;Lo;0;L;;;;;N;;;;; 11717;AHOM LETTER GHA;Lo;0;L;;;;;N;;;;; 11718;AHOM LETTER BHA;Lo;0;L;;;;;N;;;;; 11719;AHOM LETTER JHA;Lo;0;L;;;;;N;;;;; 1171D;AHOM CONSONANT SIGN MEDIAL LA;Mn;0;NSM;;;;;N;;;;; 1171E;AHOM CONSONANT SIGN MEDIAL RA;Mn;0;NSM;;;;;N;;;;; 1171F;AHOM CONSONANT SIGN MEDIAL LIGATING RA;Mn;0;NSM;;;;;N;;;;; 11720;AHOM VOWEL SIGN A;Mc;0;L;;;;;N;;;;; 11721;AHOM VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 11722;AHOM VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; 11723;AHOM VOWEL SIGN II;Mn;0;NSM;;;;;N;;;;; 11724;AHOM VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 11725;AHOM VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 11726;AHOM VOWEL SIGN E;Mc;0;L;;;;;N;;;;; 11727;AHOM VOWEL SIGN AW;Mn;0;NSM;;;;;N;;;;; 11728;AHOM VOWEL SIGN O;Mn;0;NSM;;;;;N;;;;; 11729;AHOM VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;; 1172A;AHOM VOWEL SIGN AM;Mn;0;NSM;;;;;N;;;;; 1172B;AHOM SIGN KILLER;Mn;9;NSM;;;;;N;;;;; 11730;AHOM DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 11731;AHOM DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 11732;AHOM DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 11733;AHOM DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 11734;AHOM DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 11735;AHOM DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 11736;AHOM DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 11737;AHOM DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 11738;AHOM DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 11739;AHOM DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 1173A;AHOM NUMBER TEN;No;0;L;;;;10;N;;;;; 1173B;AHOM NUMBER TWENTY;No;0;L;;;;20;N;;;;; 1173C;AHOM SIGN SMALL SECTION;Po;0;L;;;;;N;;;;; 1173D;AHOM SIGN SECTION;Po;0;L;;;;;N;;;;; 1173E;AHOM SIGN RULAI;Po;0;L;;;;;N;;;;; 1173F;AHOM SYMBOL VI;So;0;L;;;;;N;;;;; 118A0;WARANG CITI CAPITAL LETTER NGAA;Lu;0;L;;;;;N;;;;118C0; 118A1;WARANG CITI CAPITAL LETTER A;Lu;0;L;;;;;N;;;;118C1; 118A2;WARANG CITI CAPITAL LETTER WI;Lu;0;L;;;;;N;;;;118C2; 118A3;WARANG CITI CAPITAL LETTER YU;Lu;0;L;;;;;N;;;;118C3; 118A4;WARANG CITI CAPITAL LETTER YA;Lu;0;L;;;;;N;;;;118C4; 118A5;WARANG CITI CAPITAL LETTER YO;Lu;0;L;;;;;N;;;;118C5; 118A6;WARANG CITI CAPITAL LETTER II;Lu;0;L;;;;;N;;;;118C6; 118A7;WARANG CITI CAPITAL LETTER UU;Lu;0;L;;;;;N;;;;118C7; 118A8;WARANG CITI CAPITAL LETTER E;Lu;0;L;;;;;N;;;;118C8; 118A9;WARANG CITI CAPITAL LETTER O;Lu;0;L;;;;;N;;;;118C9; 118AA;WARANG CITI CAPITAL LETTER ANG;Lu;0;L;;;;;N;;;;118CA; 118AB;WARANG CITI CAPITAL LETTER GA;Lu;0;L;;;;;N;;;;118CB; 118AC;WARANG CITI CAPITAL LETTER KO;Lu;0;L;;;;;N;;;;118CC; 118AD;WARANG CITI CAPITAL LETTER ENY;Lu;0;L;;;;;N;;;;118CD; 118AE;WARANG CITI CAPITAL LETTER YUJ;Lu;0;L;;;;;N;;;;118CE; 118AF;WARANG CITI CAPITAL LETTER UC;Lu;0;L;;;;;N;;;;118CF; 118B0;WARANG CITI CAPITAL LETTER ENN;Lu;0;L;;;;;N;;;;118D0; 118B1;WARANG CITI CAPITAL LETTER ODD;Lu;0;L;;;;;N;;;;118D1; 118B2;WARANG CITI CAPITAL LETTER TTE;Lu;0;L;;;;;N;;;;118D2; 118B3;WARANG CITI CAPITAL LETTER NUNG;Lu;0;L;;;;;N;;;;118D3; 118B4;WARANG CITI CAPITAL LETTER DA;Lu;0;L;;;;;N;;;;118D4; 118B5;WARANG CITI CAPITAL LETTER AT;Lu;0;L;;;;;N;;;;118D5; 118B6;WARANG CITI CAPITAL LETTER AM;Lu;0;L;;;;;N;;;;118D6; 118B7;WARANG CITI CAPITAL LETTER BU;Lu;0;L;;;;;N;;;;118D7; 118B8;WARANG CITI CAPITAL LETTER PU;Lu;0;L;;;;;N;;;;118D8; 118B9;WARANG CITI CAPITAL LETTER HIYO;Lu;0;L;;;;;N;;;;118D9; 118BA;WARANG CITI CAPITAL LETTER HOLO;Lu;0;L;;;;;N;;;;118DA; 118BB;WARANG CITI CAPITAL LETTER HORR;Lu;0;L;;;;;N;;;;118DB; 118BC;WARANG CITI CAPITAL LETTER HAR;Lu;0;L;;;;;N;;;;118DC; 118BD;WARANG CITI CAPITAL LETTER SSUU;Lu;0;L;;;;;N;;;;118DD; 118BE;WARANG CITI CAPITAL LETTER SII;Lu;0;L;;;;;N;;;;118DE; 118BF;WARANG CITI CAPITAL LETTER VIYO;Lu;0;L;;;;;N;;;;118DF; 118C0;WARANG CITI SMALL LETTER NGAA;Ll;0;L;;;;;N;;;118A0;;118A0 118C1;WARANG CITI SMALL LETTER A;Ll;0;L;;;;;N;;;118A1;;118A1 118C2;WARANG CITI SMALL LETTER WI;Ll;0;L;;;;;N;;;118A2;;118A2 118C3;WARANG CITI SMALL LETTER YU;Ll;0;L;;;;;N;;;118A3;;118A3 118C4;WARANG CITI SMALL LETTER YA;Ll;0;L;;;;;N;;;118A4;;118A4 118C5;WARANG CITI SMALL LETTER YO;Ll;0;L;;;;;N;;;118A5;;118A5 118C6;WARANG CITI SMALL LETTER II;Ll;0;L;;;;;N;;;118A6;;118A6 118C7;WARANG CITI SMALL LETTER UU;Ll;0;L;;;;;N;;;118A7;;118A7 118C8;WARANG CITI SMALL LETTER E;Ll;0;L;;;;;N;;;118A8;;118A8 118C9;WARANG CITI SMALL LETTER O;Ll;0;L;;;;;N;;;118A9;;118A9 118CA;WARANG CITI SMALL LETTER ANG;Ll;0;L;;;;;N;;;118AA;;118AA 118CB;WARANG CITI SMALL LETTER GA;Ll;0;L;;;;;N;;;118AB;;118AB 118CC;WARANG CITI SMALL LETTER KO;Ll;0;L;;;;;N;;;118AC;;118AC 118CD;WARANG CITI SMALL LETTER ENY;Ll;0;L;;;;;N;;;118AD;;118AD 118CE;WARANG CITI SMALL LETTER YUJ;Ll;0;L;;;;;N;;;118AE;;118AE 118CF;WARANG CITI SMALL LETTER UC;Ll;0;L;;;;;N;;;118AF;;118AF 118D0;WARANG CITI SMALL LETTER ENN;Ll;0;L;;;;;N;;;118B0;;118B0 118D1;WARANG CITI SMALL LETTER ODD;Ll;0;L;;;;;N;;;118B1;;118B1 118D2;WARANG CITI SMALL LETTER TTE;Ll;0;L;;;;;N;;;118B2;;118B2 118D3;WARANG CITI SMALL LETTER NUNG;Ll;0;L;;;;;N;;;118B3;;118B3 118D4;WARANG CITI SMALL LETTER DA;Ll;0;L;;;;;N;;;118B4;;118B4 118D5;WARANG CITI SMALL LETTER AT;Ll;0;L;;;;;N;;;118B5;;118B5 118D6;WARANG CITI SMALL LETTER AM;Ll;0;L;;;;;N;;;118B6;;118B6 118D7;WARANG CITI SMALL LETTER BU;Ll;0;L;;;;;N;;;118B7;;118B7 118D8;WARANG CITI SMALL LETTER PU;Ll;0;L;;;;;N;;;118B8;;118B8 118D9;WARANG CITI SMALL LETTER HIYO;Ll;0;L;;;;;N;;;118B9;;118B9 118DA;WARANG CITI SMALL LETTER HOLO;Ll;0;L;;;;;N;;;118BA;;118BA 118DB;WARANG CITI SMALL LETTER HORR;Ll;0;L;;;;;N;;;118BB;;118BB 118DC;WARANG CITI SMALL LETTER HAR;Ll;0;L;;;;;N;;;118BC;;118BC 118DD;WARANG CITI SMALL LETTER SSUU;Ll;0;L;;;;;N;;;118BD;;118BD 118DE;WARANG CITI SMALL LETTER SII;Ll;0;L;;;;;N;;;118BE;;118BE 118DF;WARANG CITI SMALL LETTER VIYO;Ll;0;L;;;;;N;;;118BF;;118BF 118E0;WARANG CITI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 118E1;WARANG CITI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 118E2;WARANG CITI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 118E3;WARANG CITI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 118E4;WARANG CITI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 118E5;WARANG CITI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 118E6;WARANG CITI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 118E7;WARANG CITI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 118E8;WARANG CITI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 118E9;WARANG CITI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 118EA;WARANG CITI NUMBER TEN;No;0;L;;;;10;N;;;;; 118EB;WARANG CITI NUMBER TWENTY;No;0;L;;;;20;N;;;;; 118EC;WARANG CITI NUMBER THIRTY;No;0;L;;;;30;N;;;;; 118ED;WARANG CITI NUMBER FORTY;No;0;L;;;;40;N;;;;; 118EE;WARANG CITI NUMBER FIFTY;No;0;L;;;;50;N;;;;; 118EF;WARANG CITI NUMBER SIXTY;No;0;L;;;;60;N;;;;; 118F0;WARANG CITI NUMBER SEVENTY;No;0;L;;;;70;N;;;;; 118F1;WARANG CITI NUMBER EIGHTY;No;0;L;;;;80;N;;;;; 118F2;WARANG CITI NUMBER NINETY;No;0;L;;;;90;N;;;;; 118FF;WARANG CITI OM;Lo;0;L;;;;;N;;;;; 11AC0;PAU CIN HAU LETTER PA;Lo;0;L;;;;;N;;;;; 11AC1;PAU CIN HAU LETTER KA;Lo;0;L;;;;;N;;;;; 11AC2;PAU CIN HAU LETTER LA;Lo;0;L;;;;;N;;;;; 11AC3;PAU CIN HAU LETTER MA;Lo;0;L;;;;;N;;;;; 11AC4;PAU CIN HAU LETTER DA;Lo;0;L;;;;;N;;;;; 11AC5;PAU CIN HAU LETTER ZA;Lo;0;L;;;;;N;;;;; 11AC6;PAU CIN HAU LETTER VA;Lo;0;L;;;;;N;;;;; 11AC7;PAU CIN HAU LETTER NGA;Lo;0;L;;;;;N;;;;; 11AC8;PAU CIN HAU LETTER HA;Lo;0;L;;;;;N;;;;; 11AC9;PAU CIN HAU LETTER GA;Lo;0;L;;;;;N;;;;; 11ACA;PAU CIN HAU LETTER KHA;Lo;0;L;;;;;N;;;;; 11ACB;PAU CIN HAU LETTER SA;Lo;0;L;;;;;N;;;;; 11ACC;PAU CIN HAU LETTER BA;Lo;0;L;;;;;N;;;;; 11ACD;PAU CIN HAU LETTER CA;Lo;0;L;;;;;N;;;;; 11ACE;PAU CIN HAU LETTER TA;Lo;0;L;;;;;N;;;;; 11ACF;PAU CIN HAU LETTER THA;Lo;0;L;;;;;N;;;;; 11AD0;PAU CIN HAU LETTER NA;Lo;0;L;;;;;N;;;;; 11AD1;PAU CIN HAU LETTER PHA;Lo;0;L;;;;;N;;;;; 11AD2;PAU CIN HAU LETTER RA;Lo;0;L;;;;;N;;;;; 11AD3;PAU CIN HAU LETTER FA;Lo;0;L;;;;;N;;;;; 11AD4;PAU CIN HAU LETTER CHA;Lo;0;L;;;;;N;;;;; 11AD5;PAU CIN HAU LETTER A;Lo;0;L;;;;;N;;;;; 11AD6;PAU CIN HAU LETTER E;Lo;0;L;;;;;N;;;;; 11AD7;PAU CIN HAU LETTER I;Lo;0;L;;;;;N;;;;; 11AD8;PAU CIN HAU LETTER O;Lo;0;L;;;;;N;;;;; 11AD9;PAU CIN HAU LETTER U;Lo;0;L;;;;;N;;;;; 11ADA;PAU CIN HAU LETTER UA;Lo;0;L;;;;;N;;;;; 11ADB;PAU CIN HAU LETTER IA;Lo;0;L;;;;;N;;;;; 11ADC;PAU CIN HAU LETTER FINAL P;Lo;0;L;;;;;N;;;;; 11ADD;PAU CIN HAU LETTER FINAL K;Lo;0;L;;;;;N;;;;; 11ADE;PAU CIN HAU LETTER FINAL T;Lo;0;L;;;;;N;;;;; 11ADF;PAU CIN HAU LETTER FINAL M;Lo;0;L;;;;;N;;;;; 11AE0;PAU CIN HAU LETTER FINAL N;Lo;0;L;;;;;N;;;;; 11AE1;PAU CIN HAU LETTER FINAL L;Lo;0;L;;;;;N;;;;; 11AE2;PAU CIN HAU LETTER FINAL W;Lo;0;L;;;;;N;;;;; 11AE3;PAU CIN HAU LETTER FINAL NG;Lo;0;L;;;;;N;;;;; 11AE4;PAU CIN HAU LETTER FINAL Y;Lo;0;L;;;;;N;;;;; 11AE5;PAU CIN HAU RISING TONE LONG;Lo;0;L;;;;;N;;;;; 11AE6;PAU CIN HAU RISING TONE;Lo;0;L;;;;;N;;;;; 11AE7;PAU CIN HAU SANDHI GLOTTAL STOP;Lo;0;L;;;;;N;;;;; 11AE8;PAU CIN HAU RISING TONE LONG FINAL;Lo;0;L;;;;;N;;;;; 11AE9;PAU CIN HAU RISING TONE FINAL;Lo;0;L;;;;;N;;;;; 11AEA;PAU CIN HAU SANDHI GLOTTAL STOP FINAL;Lo;0;L;;;;;N;;;;; 11AEB;PAU CIN HAU SANDHI TONE LONG;Lo;0;L;;;;;N;;;;; 11AEC;PAU CIN HAU SANDHI TONE;Lo;0;L;;;;;N;;;;; 11AED;PAU CIN HAU SANDHI TONE LONG FINAL;Lo;0;L;;;;;N;;;;; 11AEE;PAU CIN HAU SANDHI TONE FINAL;Lo;0;L;;;;;N;;;;; 11AEF;PAU CIN HAU MID-LEVEL TONE;Lo;0;L;;;;;N;;;;; 11AF0;PAU CIN HAU GLOTTAL STOP VARIANT;Lo;0;L;;;;;N;;;;; 11AF1;PAU CIN HAU MID-LEVEL TONE LONG FINAL;Lo;0;L;;;;;N;;;;; 11AF2;PAU CIN HAU MID-LEVEL TONE FINAL;Lo;0;L;;;;;N;;;;; 11AF3;PAU CIN HAU LOW-FALLING TONE LONG;Lo;0;L;;;;;N;;;;; 11AF4;PAU CIN HAU LOW-FALLING TONE;Lo;0;L;;;;;N;;;;; 11AF5;PAU CIN HAU GLOTTAL STOP;Lo;0;L;;;;;N;;;;; 11AF6;PAU CIN HAU LOW-FALLING TONE LONG FINAL;Lo;0;L;;;;;N;;;;; 11AF7;PAU CIN HAU LOW-FALLING TONE FINAL;Lo;0;L;;;;;N;;;;; 11AF8;PAU CIN HAU GLOTTAL STOP FINAL;Lo;0;L;;;;;N;;;;; 11C00;BHAIKSUKI LETTER A;Lo;0;L;;;;;N;;;;; 11C01;BHAIKSUKI LETTER AA;Lo;0;L;;;;;N;;;;; 11C02;BHAIKSUKI LETTER I;Lo;0;L;;;;;N;;;;; 11C03;BHAIKSUKI LETTER II;Lo;0;L;;;;;N;;;;; 11C04;BHAIKSUKI LETTER U;Lo;0;L;;;;;N;;;;; 11C05;BHAIKSUKI LETTER UU;Lo;0;L;;;;;N;;;;; 11C06;BHAIKSUKI LETTER VOCALIC R;Lo;0;L;;;;;N;;;;; 11C07;BHAIKSUKI LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 11C08;BHAIKSUKI LETTER VOCALIC L;Lo;0;L;;;;;N;;;;; 11C0A;BHAIKSUKI LETTER E;Lo;0;L;;;;;N;;;;; 11C0B;BHAIKSUKI LETTER AI;Lo;0;L;;;;;N;;;;; 11C0C;BHAIKSUKI LETTER O;Lo;0;L;;;;;N;;;;; 11C0D;BHAIKSUKI LETTER AU;Lo;0;L;;;;;N;;;;; 11C0E;BHAIKSUKI LETTER KA;Lo;0;L;;;;;N;;;;; 11C0F;BHAIKSUKI LETTER KHA;Lo;0;L;;;;;N;;;;; 11C10;BHAIKSUKI LETTER GA;Lo;0;L;;;;;N;;;;; 11C11;BHAIKSUKI LETTER GHA;Lo;0;L;;;;;N;;;;; 11C12;BHAIKSUKI LETTER NGA;Lo;0;L;;;;;N;;;;; 11C13;BHAIKSUKI LETTER CA;Lo;0;L;;;;;N;;;;; 11C14;BHAIKSUKI LETTER CHA;Lo;0;L;;;;;N;;;;; 11C15;BHAIKSUKI LETTER JA;Lo;0;L;;;;;N;;;;; 11C16;BHAIKSUKI LETTER JHA;Lo;0;L;;;;;N;;;;; 11C17;BHAIKSUKI LETTER NYA;Lo;0;L;;;;;N;;;;; 11C18;BHAIKSUKI LETTER TTA;Lo;0;L;;;;;N;;;;; 11C19;BHAIKSUKI LETTER TTHA;Lo;0;L;;;;;N;;;;; 11C1A;BHAIKSUKI LETTER DDA;Lo;0;L;;;;;N;;;;; 11C1B;BHAIKSUKI LETTER DDHA;Lo;0;L;;;;;N;;;;; 11C1C;BHAIKSUKI LETTER NNA;Lo;0;L;;;;;N;;;;; 11C1D;BHAIKSUKI LETTER TA;Lo;0;L;;;;;N;;;;; 11C1E;BHAIKSUKI LETTER THA;Lo;0;L;;;;;N;;;;; 11C1F;BHAIKSUKI LETTER DA;Lo;0;L;;;;;N;;;;; 11C20;BHAIKSUKI LETTER DHA;Lo;0;L;;;;;N;;;;; 11C21;BHAIKSUKI LETTER NA;Lo;0;L;;;;;N;;;;; 11C22;BHAIKSUKI LETTER PA;Lo;0;L;;;;;N;;;;; 11C23;BHAIKSUKI LETTER PHA;Lo;0;L;;;;;N;;;;; 11C24;BHAIKSUKI LETTER BA;Lo;0;L;;;;;N;;;;; 11C25;BHAIKSUKI LETTER BHA;Lo;0;L;;;;;N;;;;; 11C26;BHAIKSUKI LETTER MA;Lo;0;L;;;;;N;;;;; 11C27;BHAIKSUKI LETTER YA;Lo;0;L;;;;;N;;;;; 11C28;BHAIKSUKI LETTER RA;Lo;0;L;;;;;N;;;;; 11C29;BHAIKSUKI LETTER LA;Lo;0;L;;;;;N;;;;; 11C2A;BHAIKSUKI LETTER VA;Lo;0;L;;;;;N;;;;; 11C2B;BHAIKSUKI LETTER SHA;Lo;0;L;;;;;N;;;;; 11C2C;BHAIKSUKI LETTER SSA;Lo;0;L;;;;;N;;;;; 11C2D;BHAIKSUKI LETTER SA;Lo;0;L;;;;;N;;;;; 11C2E;BHAIKSUKI LETTER HA;Lo;0;L;;;;;N;;;;; 11C2F;BHAIKSUKI VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 11C30;BHAIKSUKI VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; 11C31;BHAIKSUKI VOWEL SIGN II;Mn;0;NSM;;;;;N;;;;; 11C32;BHAIKSUKI VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 11C33;BHAIKSUKI VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; 11C34;BHAIKSUKI VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;; 11C35;BHAIKSUKI VOWEL SIGN VOCALIC RR;Mn;0;NSM;;;;;N;;;;; 11C36;BHAIKSUKI VOWEL SIGN VOCALIC L;Mn;0;NSM;;;;;N;;;;; 11C38;BHAIKSUKI VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;; 11C39;BHAIKSUKI VOWEL SIGN AI;Mn;0;NSM;;;;;N;;;;; 11C3A;BHAIKSUKI VOWEL SIGN O;Mn;0;NSM;;;;;N;;;;; 11C3B;BHAIKSUKI VOWEL SIGN AU;Mn;0;NSM;;;;;N;;;;; 11C3C;BHAIKSUKI SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 11C3D;BHAIKSUKI SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; 11C3E;BHAIKSUKI SIGN VISARGA;Mc;0;L;;;;;N;;;;; 11C3F;BHAIKSUKI SIGN VIRAMA;Mn;9;L;;;;;N;;;;; 11C40;BHAIKSUKI SIGN AVAGRAHA;Lo;0;L;;;;;N;;;;; 11C41;BHAIKSUKI DANDA;Po;0;L;;;;;N;;;;; 11C42;BHAIKSUKI DOUBLE DANDA;Po;0;L;;;;;N;;;;; 11C43;BHAIKSUKI WORD SEPARATOR;Po;0;L;;;;;N;;;;; 11C44;BHAIKSUKI GAP FILLER-1;Po;0;L;;;;;N;;;;; 11C45;BHAIKSUKI GAP FILLER-2;Po;0;L;;;;;N;;;;; 11C50;BHAIKSUKI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 11C51;BHAIKSUKI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 11C52;BHAIKSUKI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 11C53;BHAIKSUKI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 11C54;BHAIKSUKI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 11C55;BHAIKSUKI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 11C56;BHAIKSUKI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 11C57;BHAIKSUKI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 11C58;BHAIKSUKI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 11C59;BHAIKSUKI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 11C5A;BHAIKSUKI NUMBER ONE;No;0;L;;;;1;N;;;;; 11C5B;BHAIKSUKI NUMBER TWO;No;0;L;;;;2;N;;;;; 11C5C;BHAIKSUKI NUMBER THREE;No;0;L;;;;3;N;;;;; 11C5D;BHAIKSUKI NUMBER FOUR;No;0;L;;;;4;N;;;;; 11C5E;BHAIKSUKI NUMBER FIVE;No;0;L;;;;5;N;;;;; 11C5F;BHAIKSUKI NUMBER SIX;No;0;L;;;;6;N;;;;; 11C60;BHAIKSUKI NUMBER SEVEN;No;0;L;;;;7;N;;;;; 11C61;BHAIKSUKI NUMBER EIGHT;No;0;L;;;;8;N;;;;; 11C62;BHAIKSUKI NUMBER NINE;No;0;L;;;;9;N;;;;; 11C63;BHAIKSUKI NUMBER TEN;No;0;L;;;;10;N;;;;; 11C64;BHAIKSUKI NUMBER TWENTY;No;0;L;;;;20;N;;;;; 11C65;BHAIKSUKI NUMBER THIRTY;No;0;L;;;;30;N;;;;; 11C66;BHAIKSUKI NUMBER FORTY;No;0;L;;;;40;N;;;;; 11C67;BHAIKSUKI NUMBER FIFTY;No;0;L;;;;50;N;;;;; 11C68;BHAIKSUKI NUMBER SIXTY;No;0;L;;;;60;N;;;;; 11C69;BHAIKSUKI NUMBER SEVENTY;No;0;L;;;;70;N;;;;; 11C6A;BHAIKSUKI NUMBER EIGHTY;No;0;L;;;;80;N;;;;; 11C6B;BHAIKSUKI NUMBER NINETY;No;0;L;;;;90;N;;;;; 11C6C;BHAIKSUKI HUNDREDS UNIT MARK;No;0;L;;;;100;N;;;;; 11C70;MARCHEN HEAD MARK;Po;0;L;;;;;N;;;;; 11C71;MARCHEN MARK SHAD;Po;0;L;;;;;N;;;;; 11C72;MARCHEN LETTER KA;Lo;0;L;;;;;N;;;;; 11C73;MARCHEN LETTER KHA;Lo;0;L;;;;;N;;;;; 11C74;MARCHEN LETTER GA;Lo;0;L;;;;;N;;;;; 11C75;MARCHEN LETTER NGA;Lo;0;L;;;;;N;;;;; 11C76;MARCHEN LETTER CA;Lo;0;L;;;;;N;;;;; 11C77;MARCHEN LETTER CHA;Lo;0;L;;;;;N;;;;; 11C78;MARCHEN LETTER JA;Lo;0;L;;;;;N;;;;; 11C79;MARCHEN LETTER NYA;Lo;0;L;;;;;N;;;;; 11C7A;MARCHEN LETTER TA;Lo;0;L;;;;;N;;;;; 11C7B;MARCHEN LETTER THA;Lo;0;L;;;;;N;;;;; 11C7C;MARCHEN LETTER DA;Lo;0;L;;;;;N;;;;; 11C7D;MARCHEN LETTER NA;Lo;0;L;;;;;N;;;;; 11C7E;MARCHEN LETTER PA;Lo;0;L;;;;;N;;;;; 11C7F;MARCHEN LETTER PHA;Lo;0;L;;;;;N;;;;; 11C80;MARCHEN LETTER BA;Lo;0;L;;;;;N;;;;; 11C81;MARCHEN LETTER MA;Lo;0;L;;;;;N;;;;; 11C82;MARCHEN LETTER TSA;Lo;0;L;;;;;N;;;;; 11C83;MARCHEN LETTER TSHA;Lo;0;L;;;;;N;;;;; 11C84;MARCHEN LETTER DZA;Lo;0;L;;;;;N;;;;; 11C85;MARCHEN LETTER WA;Lo;0;L;;;;;N;;;;; 11C86;MARCHEN LETTER ZHA;Lo;0;L;;;;;N;;;;; 11C87;MARCHEN LETTER ZA;Lo;0;L;;;;;N;;;;; 11C88;MARCHEN LETTER -A;Lo;0;L;;;;;N;;;;; 11C89;MARCHEN LETTER YA;Lo;0;L;;;;;N;;;;; 11C8A;MARCHEN LETTER RA;Lo;0;L;;;;;N;;;;; 11C8B;MARCHEN LETTER LA;Lo;0;L;;;;;N;;;;; 11C8C;MARCHEN LETTER SHA;Lo;0;L;;;;;N;;;;; 11C8D;MARCHEN LETTER SA;Lo;0;L;;;;;N;;;;; 11C8E;MARCHEN LETTER HA;Lo;0;L;;;;;N;;;;; 11C8F;MARCHEN LETTER A;Lo;0;L;;;;;N;;;;; 11C92;MARCHEN SUBJOINED LETTER KA;Mn;0;NSM;;;;;N;;;;; 11C93;MARCHEN SUBJOINED LETTER KHA;Mn;0;NSM;;;;;N;;;;; 11C94;MARCHEN SUBJOINED LETTER GA;Mn;0;NSM;;;;;N;;;;; 11C95;MARCHEN SUBJOINED LETTER NGA;Mn;0;NSM;;;;;N;;;;; 11C96;MARCHEN SUBJOINED LETTER CA;Mn;0;NSM;;;;;N;;;;; 11C97;MARCHEN SUBJOINED LETTER CHA;Mn;0;NSM;;;;;N;;;;; 11C98;MARCHEN SUBJOINED LETTER JA;Mn;0;NSM;;;;;N;;;;; 11C99;MARCHEN SUBJOINED LETTER NYA;Mn;0;NSM;;;;;N;;;;; 11C9A;MARCHEN SUBJOINED LETTER TA;Mn;0;NSM;;;;;N;;;;; 11C9B;MARCHEN SUBJOINED LETTER THA;Mn;0;NSM;;;;;N;;;;; 11C9C;MARCHEN SUBJOINED LETTER DA;Mn;0;NSM;;;;;N;;;;; 11C9D;MARCHEN SUBJOINED LETTER NA;Mn;0;NSM;;;;;N;;;;; 11C9E;MARCHEN SUBJOINED LETTER PA;Mn;0;NSM;;;;;N;;;;; 11C9F;MARCHEN SUBJOINED LETTER PHA;Mn;0;NSM;;;;;N;;;;; 11CA0;MARCHEN SUBJOINED LETTER BA;Mn;0;NSM;;;;;N;;;;; 11CA1;MARCHEN SUBJOINED LETTER MA;Mn;0;NSM;;;;;N;;;;; 11CA2;MARCHEN SUBJOINED LETTER TSA;Mn;0;NSM;;;;;N;;;;; 11CA3;MARCHEN SUBJOINED LETTER TSHA;Mn;0;NSM;;;;;N;;;;; 11CA4;MARCHEN SUBJOINED LETTER DZA;Mn;0;NSM;;;;;N;;;;; 11CA5;MARCHEN SUBJOINED LETTER WA;Mn;0;NSM;;;;;N;;;;; 11CA6;MARCHEN SUBJOINED LETTER ZHA;Mn;0;NSM;;;;;N;;;;; 11CA7;MARCHEN SUBJOINED LETTER ZA;Mn;0;NSM;;;;;N;;;;; 11CA9;MARCHEN SUBJOINED LETTER YA;Mc;0;L;;;;;N;;;;; 11CAA;MARCHEN SUBJOINED LETTER RA;Mn;0;NSM;;;;;N;;;;; 11CAB;MARCHEN SUBJOINED LETTER LA;Mn;0;NSM;;;;;N;;;;; 11CAC;MARCHEN SUBJOINED LETTER SHA;Mn;0;NSM;;;;;N;;;;; 11CAD;MARCHEN SUBJOINED LETTER SA;Mn;0;NSM;;;;;N;;;;; 11CAE;MARCHEN SUBJOINED LETTER HA;Mn;0;NSM;;;;;N;;;;; 11CAF;MARCHEN SUBJOINED LETTER A;Mn;0;NSM;;;;;N;;;;; 11CB0;MARCHEN VOWEL SIGN AA;Mn;0;NSM;;;;;N;;;;; 11CB1;MARCHEN VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 11CB2;MARCHEN VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; 11CB3;MARCHEN VOWEL SIGN E;Mn;0;NSM;;;;;N;;;;; 11CB4;MARCHEN VOWEL SIGN O;Mc;0;L;;;;;N;;;;; 11CB5;MARCHEN SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; 11CB6;MARCHEN SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 12000;CUNEIFORM SIGN A;Lo;0;L;;;;;N;;;;; 12001;CUNEIFORM SIGN A TIMES A;Lo;0;L;;;;;N;;;;; 12002;CUNEIFORM SIGN A TIMES BAD;Lo;0;L;;;;;N;;;;; 12003;CUNEIFORM SIGN A TIMES GAN2 TENU;Lo;0;L;;;;;N;;;;; 12004;CUNEIFORM SIGN A TIMES HA;Lo;0;L;;;;;N;;;;; 12005;CUNEIFORM SIGN A TIMES IGI;Lo;0;L;;;;;N;;;;; 12006;CUNEIFORM SIGN A TIMES LAGAR GUNU;Lo;0;L;;;;;N;;;;; 12007;CUNEIFORM SIGN A TIMES MUSH;Lo;0;L;;;;;N;;;;; 12008;CUNEIFORM SIGN A TIMES SAG;Lo;0;L;;;;;N;;;;; 12009;CUNEIFORM SIGN A2;Lo;0;L;;;;;N;;;;; 1200A;CUNEIFORM SIGN AB;Lo;0;L;;;;;N;;;;; 1200B;CUNEIFORM SIGN AB TIMES ASH2;Lo;0;L;;;;;N;;;;; 1200C;CUNEIFORM SIGN AB TIMES DUN3 GUNU;Lo;0;L;;;;;N;;;;; 1200D;CUNEIFORM SIGN AB TIMES GAL;Lo;0;L;;;;;N;;;;; 1200E;CUNEIFORM SIGN AB TIMES GAN2 TENU;Lo;0;L;;;;;N;;;;; 1200F;CUNEIFORM SIGN AB TIMES HA;Lo;0;L;;;;;N;;;;; 12010;CUNEIFORM SIGN AB TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 12011;CUNEIFORM SIGN AB TIMES IMIN;Lo;0;L;;;;;N;;;;; 12012;CUNEIFORM SIGN AB TIMES LAGAB;Lo;0;L;;;;;N;;;;; 12013;CUNEIFORM SIGN AB TIMES SHESH;Lo;0;L;;;;;N;;;;; 12014;CUNEIFORM SIGN AB TIMES U PLUS U PLUS U;Lo;0;L;;;;;N;;;;; 12015;CUNEIFORM SIGN AB GUNU;Lo;0;L;;;;;N;;;;; 12016;CUNEIFORM SIGN AB2;Lo;0;L;;;;;N;;;;; 12017;CUNEIFORM SIGN AB2 TIMES BALAG;Lo;0;L;;;;;N;;;;; 12018;CUNEIFORM SIGN AB2 TIMES GAN2 TENU;Lo;0;L;;;;;N;;;;; 12019;CUNEIFORM SIGN AB2 TIMES ME PLUS EN;Lo;0;L;;;;;N;;;;; 1201A;CUNEIFORM SIGN AB2 TIMES SHA3;Lo;0;L;;;;;N;;;;; 1201B;CUNEIFORM SIGN AB2 TIMES TAK4;Lo;0;L;;;;;N;;;;; 1201C;CUNEIFORM SIGN AD;Lo;0;L;;;;;N;;;;; 1201D;CUNEIFORM SIGN AK;Lo;0;L;;;;;N;;;;; 1201E;CUNEIFORM SIGN AK TIMES ERIN2;Lo;0;L;;;;;N;;;;; 1201F;CUNEIFORM SIGN AK TIMES SHITA PLUS GISH;Lo;0;L;;;;;N;;;;; 12020;CUNEIFORM SIGN AL;Lo;0;L;;;;;N;;;;; 12021;CUNEIFORM SIGN AL TIMES AL;Lo;0;L;;;;;N;;;;; 12022;CUNEIFORM SIGN AL TIMES DIM2;Lo;0;L;;;;;N;;;;; 12023;CUNEIFORM SIGN AL TIMES GISH;Lo;0;L;;;;;N;;;;; 12024;CUNEIFORM SIGN AL TIMES HA;Lo;0;L;;;;;N;;;;; 12025;CUNEIFORM SIGN AL TIMES KAD3;Lo;0;L;;;;;N;;;;; 12026;CUNEIFORM SIGN AL TIMES KI;Lo;0;L;;;;;N;;;;; 12027;CUNEIFORM SIGN AL TIMES SHE;Lo;0;L;;;;;N;;;;; 12028;CUNEIFORM SIGN AL TIMES USH;Lo;0;L;;;;;N;;;;; 12029;CUNEIFORM SIGN ALAN;Lo;0;L;;;;;N;;;;; 1202A;CUNEIFORM SIGN ALEPH;Lo;0;L;;;;;N;;;;; 1202B;CUNEIFORM SIGN AMAR;Lo;0;L;;;;;N;;;;; 1202C;CUNEIFORM SIGN AMAR TIMES SHE;Lo;0;L;;;;;N;;;;; 1202D;CUNEIFORM SIGN AN;Lo;0;L;;;;;N;;;;; 1202E;CUNEIFORM SIGN AN OVER AN;Lo;0;L;;;;;N;;;;; 1202F;CUNEIFORM SIGN AN THREE TIMES;Lo;0;L;;;;;N;;;;; 12030;CUNEIFORM SIGN AN PLUS NAGA OPPOSING AN PLUS NAGA;Lo;0;L;;;;;N;;;;; 12031;CUNEIFORM SIGN AN PLUS NAGA SQUARED;Lo;0;L;;;;;N;;;;; 12032;CUNEIFORM SIGN ANSHE;Lo;0;L;;;;;N;;;;; 12033;CUNEIFORM SIGN APIN;Lo;0;L;;;;;N;;;;; 12034;CUNEIFORM SIGN ARAD;Lo;0;L;;;;;N;;;;; 12035;CUNEIFORM SIGN ARAD TIMES KUR;Lo;0;L;;;;;N;;;;; 12036;CUNEIFORM SIGN ARKAB;Lo;0;L;;;;;N;;;;; 12037;CUNEIFORM SIGN ASAL2;Lo;0;L;;;;;N;;;;; 12038;CUNEIFORM SIGN ASH;Lo;0;L;;;;;N;;;;; 12039;CUNEIFORM SIGN ASH ZIDA TENU;Lo;0;L;;;;;N;;;;; 1203A;CUNEIFORM SIGN ASH KABA TENU;Lo;0;L;;;;;N;;;;; 1203B;CUNEIFORM SIGN ASH OVER ASH TUG2 OVER TUG2 TUG2 OVER TUG2 PAP;Lo;0;L;;;;;N;;;;; 1203C;CUNEIFORM SIGN ASH OVER ASH OVER ASH;Lo;0;L;;;;;N;;;;; 1203D;CUNEIFORM SIGN ASH OVER ASH OVER ASH CROSSING ASH OVER ASH OVER ASH;Lo;0;L;;;;;N;;;;; 1203E;CUNEIFORM SIGN ASH2;Lo;0;L;;;;;N;;;;; 1203F;CUNEIFORM SIGN ASHGAB;Lo;0;L;;;;;N;;;;; 12040;CUNEIFORM SIGN BA;Lo;0;L;;;;;N;;;;; 12041;CUNEIFORM SIGN BAD;Lo;0;L;;;;;N;;;;; 12042;CUNEIFORM SIGN BAG3;Lo;0;L;;;;;N;;;;; 12043;CUNEIFORM SIGN BAHAR2;Lo;0;L;;;;;N;;;;; 12044;CUNEIFORM SIGN BAL;Lo;0;L;;;;;N;;;;; 12045;CUNEIFORM SIGN BAL OVER BAL;Lo;0;L;;;;;N;;;;; 12046;CUNEIFORM SIGN BALAG;Lo;0;L;;;;;N;;;;; 12047;CUNEIFORM SIGN BAR;Lo;0;L;;;;;N;;;;; 12048;CUNEIFORM SIGN BARA2;Lo;0;L;;;;;N;;;;; 12049;CUNEIFORM SIGN BI;Lo;0;L;;;;;N;;;;; 1204A;CUNEIFORM SIGN BI TIMES A;Lo;0;L;;;;;N;;;;; 1204B;CUNEIFORM SIGN BI TIMES GAR;Lo;0;L;;;;;N;;;;; 1204C;CUNEIFORM SIGN BI TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 1204D;CUNEIFORM SIGN BU;Lo;0;L;;;;;N;;;;; 1204E;CUNEIFORM SIGN BU OVER BU AB;Lo;0;L;;;;;N;;;;; 1204F;CUNEIFORM SIGN BU OVER BU UN;Lo;0;L;;;;;N;;;;; 12050;CUNEIFORM SIGN BU CROSSING BU;Lo;0;L;;;;;N;;;;; 12051;CUNEIFORM SIGN BULUG;Lo;0;L;;;;;N;;;;; 12052;CUNEIFORM SIGN BULUG OVER BULUG;Lo;0;L;;;;;N;;;;; 12053;CUNEIFORM SIGN BUR;Lo;0;L;;;;;N;;;;; 12054;CUNEIFORM SIGN BUR2;Lo;0;L;;;;;N;;;;; 12055;CUNEIFORM SIGN DA;Lo;0;L;;;;;N;;;;; 12056;CUNEIFORM SIGN DAG;Lo;0;L;;;;;N;;;;; 12057;CUNEIFORM SIGN DAG KISIM5 TIMES A PLUS MASH;Lo;0;L;;;;;N;;;;; 12058;CUNEIFORM SIGN DAG KISIM5 TIMES AMAR;Lo;0;L;;;;;N;;;;; 12059;CUNEIFORM SIGN DAG KISIM5 TIMES BALAG;Lo;0;L;;;;;N;;;;; 1205A;CUNEIFORM SIGN DAG KISIM5 TIMES BI;Lo;0;L;;;;;N;;;;; 1205B;CUNEIFORM SIGN DAG KISIM5 TIMES GA;Lo;0;L;;;;;N;;;;; 1205C;CUNEIFORM SIGN DAG KISIM5 TIMES GA PLUS MASH;Lo;0;L;;;;;N;;;;; 1205D;CUNEIFORM SIGN DAG KISIM5 TIMES GI;Lo;0;L;;;;;N;;;;; 1205E;CUNEIFORM SIGN DAG KISIM5 TIMES GIR2;Lo;0;L;;;;;N;;;;; 1205F;CUNEIFORM SIGN DAG KISIM5 TIMES GUD;Lo;0;L;;;;;N;;;;; 12060;CUNEIFORM SIGN DAG KISIM5 TIMES HA;Lo;0;L;;;;;N;;;;; 12061;CUNEIFORM SIGN DAG KISIM5 TIMES IR;Lo;0;L;;;;;N;;;;; 12062;CUNEIFORM SIGN DAG KISIM5 TIMES IR PLUS LU;Lo;0;L;;;;;N;;;;; 12063;CUNEIFORM SIGN DAG KISIM5 TIMES KAK;Lo;0;L;;;;;N;;;;; 12064;CUNEIFORM SIGN DAG KISIM5 TIMES LA;Lo;0;L;;;;;N;;;;; 12065;CUNEIFORM SIGN DAG KISIM5 TIMES LU;Lo;0;L;;;;;N;;;;; 12066;CUNEIFORM SIGN DAG KISIM5 TIMES LU PLUS MASH2;Lo;0;L;;;;;N;;;;; 12067;CUNEIFORM SIGN DAG KISIM5 TIMES LUM;Lo;0;L;;;;;N;;;;; 12068;CUNEIFORM SIGN DAG KISIM5 TIMES NE;Lo;0;L;;;;;N;;;;; 12069;CUNEIFORM SIGN DAG KISIM5 TIMES PAP PLUS PAP;Lo;0;L;;;;;N;;;;; 1206A;CUNEIFORM SIGN DAG KISIM5 TIMES SI;Lo;0;L;;;;;N;;;;; 1206B;CUNEIFORM SIGN DAG KISIM5 TIMES TAK4;Lo;0;L;;;;;N;;;;; 1206C;CUNEIFORM SIGN DAG KISIM5 TIMES U2 PLUS GIR2;Lo;0;L;;;;;N;;;;; 1206D;CUNEIFORM SIGN DAG KISIM5 TIMES USH;Lo;0;L;;;;;N;;;;; 1206E;CUNEIFORM SIGN DAM;Lo;0;L;;;;;N;;;;; 1206F;CUNEIFORM SIGN DAR;Lo;0;L;;;;;N;;;;; 12070;CUNEIFORM SIGN DARA3;Lo;0;L;;;;;N;;;;; 12071;CUNEIFORM SIGN DARA4;Lo;0;L;;;;;N;;;;; 12072;CUNEIFORM SIGN DI;Lo;0;L;;;;;N;;;;; 12073;CUNEIFORM SIGN DIB;Lo;0;L;;;;;N;;;;; 12074;CUNEIFORM SIGN DIM;Lo;0;L;;;;;N;;;;; 12075;CUNEIFORM SIGN DIM TIMES SHE;Lo;0;L;;;;;N;;;;; 12076;CUNEIFORM SIGN DIM2;Lo;0;L;;;;;N;;;;; 12077;CUNEIFORM SIGN DIN;Lo;0;L;;;;;N;;;;; 12078;CUNEIFORM SIGN DIN KASKAL U GUNU DISH;Lo;0;L;;;;;N;;;;; 12079;CUNEIFORM SIGN DISH;Lo;0;L;;;;;N;;;;; 1207A;CUNEIFORM SIGN DU;Lo;0;L;;;;;N;;;;; 1207B;CUNEIFORM SIGN DU OVER DU;Lo;0;L;;;;;N;;;;; 1207C;CUNEIFORM SIGN DU GUNU;Lo;0;L;;;;;N;;;;; 1207D;CUNEIFORM SIGN DU SHESHIG;Lo;0;L;;;;;N;;;;; 1207E;CUNEIFORM SIGN DUB;Lo;0;L;;;;;N;;;;; 1207F;CUNEIFORM SIGN DUB TIMES ESH2;Lo;0;L;;;;;N;;;;; 12080;CUNEIFORM SIGN DUB2;Lo;0;L;;;;;N;;;;; 12081;CUNEIFORM SIGN DUG;Lo;0;L;;;;;N;;;;; 12082;CUNEIFORM SIGN DUGUD;Lo;0;L;;;;;N;;;;; 12083;CUNEIFORM SIGN DUH;Lo;0;L;;;;;N;;;;; 12084;CUNEIFORM SIGN DUN;Lo;0;L;;;;;N;;;;; 12085;CUNEIFORM SIGN DUN3;Lo;0;L;;;;;N;;;;; 12086;CUNEIFORM SIGN DUN3 GUNU;Lo;0;L;;;;;N;;;;; 12087;CUNEIFORM SIGN DUN3 GUNU GUNU;Lo;0;L;;;;;N;;;;; 12088;CUNEIFORM SIGN DUN4;Lo;0;L;;;;;N;;;;; 12089;CUNEIFORM SIGN DUR2;Lo;0;L;;;;;N;;;;; 1208A;CUNEIFORM SIGN E;Lo;0;L;;;;;N;;;;; 1208B;CUNEIFORM SIGN E TIMES PAP;Lo;0;L;;;;;N;;;;; 1208C;CUNEIFORM SIGN E OVER E NUN OVER NUN;Lo;0;L;;;;;N;;;;; 1208D;CUNEIFORM SIGN E2;Lo;0;L;;;;;N;;;;; 1208E;CUNEIFORM SIGN E2 TIMES A PLUS HA PLUS DA;Lo;0;L;;;;;N;;;;; 1208F;CUNEIFORM SIGN E2 TIMES GAR;Lo;0;L;;;;;N;;;;; 12090;CUNEIFORM SIGN E2 TIMES MI;Lo;0;L;;;;;N;;;;; 12091;CUNEIFORM SIGN E2 TIMES SAL;Lo;0;L;;;;;N;;;;; 12092;CUNEIFORM SIGN E2 TIMES SHE;Lo;0;L;;;;;N;;;;; 12093;CUNEIFORM SIGN E2 TIMES U;Lo;0;L;;;;;N;;;;; 12094;CUNEIFORM SIGN EDIN;Lo;0;L;;;;;N;;;;; 12095;CUNEIFORM SIGN EGIR;Lo;0;L;;;;;N;;;;; 12096;CUNEIFORM SIGN EL;Lo;0;L;;;;;N;;;;; 12097;CUNEIFORM SIGN EN;Lo;0;L;;;;;N;;;;; 12098;CUNEIFORM SIGN EN TIMES GAN2;Lo;0;L;;;;;N;;;;; 12099;CUNEIFORM SIGN EN TIMES GAN2 TENU;Lo;0;L;;;;;N;;;;; 1209A;CUNEIFORM SIGN EN TIMES ME;Lo;0;L;;;;;N;;;;; 1209B;CUNEIFORM SIGN EN CROSSING EN;Lo;0;L;;;;;N;;;;; 1209C;CUNEIFORM SIGN EN OPPOSING EN;Lo;0;L;;;;;N;;;;; 1209D;CUNEIFORM SIGN EN SQUARED;Lo;0;L;;;;;N;;;;; 1209E;CUNEIFORM SIGN EREN;Lo;0;L;;;;;N;;;;; 1209F;CUNEIFORM SIGN ERIN2;Lo;0;L;;;;;N;;;;; 120A0;CUNEIFORM SIGN ESH2;Lo;0;L;;;;;N;;;;; 120A1;CUNEIFORM SIGN EZEN;Lo;0;L;;;;;N;;;;; 120A2;CUNEIFORM SIGN EZEN TIMES A;Lo;0;L;;;;;N;;;;; 120A3;CUNEIFORM SIGN EZEN TIMES A PLUS LAL;Lo;0;L;;;;;N;;;;; 120A4;CUNEIFORM SIGN EZEN TIMES A PLUS LAL TIMES LAL;Lo;0;L;;;;;N;;;;; 120A5;CUNEIFORM SIGN EZEN TIMES AN;Lo;0;L;;;;;N;;;;; 120A6;CUNEIFORM SIGN EZEN TIMES BAD;Lo;0;L;;;;;N;;;;; 120A7;CUNEIFORM SIGN EZEN TIMES DUN3 GUNU;Lo;0;L;;;;;N;;;;; 120A8;CUNEIFORM SIGN EZEN TIMES DUN3 GUNU GUNU;Lo;0;L;;;;;N;;;;; 120A9;CUNEIFORM SIGN EZEN TIMES HA;Lo;0;L;;;;;N;;;;; 120AA;CUNEIFORM SIGN EZEN TIMES HA GUNU;Lo;0;L;;;;;N;;;;; 120AB;CUNEIFORM SIGN EZEN TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 120AC;CUNEIFORM SIGN EZEN TIMES KASKAL;Lo;0;L;;;;;N;;;;; 120AD;CUNEIFORM SIGN EZEN TIMES KASKAL SQUARED;Lo;0;L;;;;;N;;;;; 120AE;CUNEIFORM SIGN EZEN TIMES KU3;Lo;0;L;;;;;N;;;;; 120AF;CUNEIFORM SIGN EZEN TIMES LA;Lo;0;L;;;;;N;;;;; 120B0;CUNEIFORM SIGN EZEN TIMES LAL TIMES LAL;Lo;0;L;;;;;N;;;;; 120B1;CUNEIFORM SIGN EZEN TIMES LI;Lo;0;L;;;;;N;;;;; 120B2;CUNEIFORM SIGN EZEN TIMES LU;Lo;0;L;;;;;N;;;;; 120B3;CUNEIFORM SIGN EZEN TIMES U2;Lo;0;L;;;;;N;;;;; 120B4;CUNEIFORM SIGN EZEN TIMES UD;Lo;0;L;;;;;N;;;;; 120B5;CUNEIFORM SIGN GA;Lo;0;L;;;;;N;;;;; 120B6;CUNEIFORM SIGN GA GUNU;Lo;0;L;;;;;N;;;;; 120B7;CUNEIFORM SIGN GA2;Lo;0;L;;;;;N;;;;; 120B8;CUNEIFORM SIGN GA2 TIMES A PLUS DA PLUS HA;Lo;0;L;;;;;N;;;;; 120B9;CUNEIFORM SIGN GA2 TIMES A PLUS HA;Lo;0;L;;;;;N;;;;; 120BA;CUNEIFORM SIGN GA2 TIMES A PLUS IGI;Lo;0;L;;;;;N;;;;; 120BB;CUNEIFORM SIGN GA2 TIMES AB2 TENU PLUS TAB;Lo;0;L;;;;;N;;;;; 120BC;CUNEIFORM SIGN GA2 TIMES AN;Lo;0;L;;;;;N;;;;; 120BD;CUNEIFORM SIGN GA2 TIMES ASH;Lo;0;L;;;;;N;;;;; 120BE;CUNEIFORM SIGN GA2 TIMES ASH2 PLUS GAL;Lo;0;L;;;;;N;;;;; 120BF;CUNEIFORM SIGN GA2 TIMES BAD;Lo;0;L;;;;;N;;;;; 120C0;CUNEIFORM SIGN GA2 TIMES BAR PLUS RA;Lo;0;L;;;;;N;;;;; 120C1;CUNEIFORM SIGN GA2 TIMES BUR;Lo;0;L;;;;;N;;;;; 120C2;CUNEIFORM SIGN GA2 TIMES BUR PLUS RA;Lo;0;L;;;;;N;;;;; 120C3;CUNEIFORM SIGN GA2 TIMES DA;Lo;0;L;;;;;N;;;;; 120C4;CUNEIFORM SIGN GA2 TIMES DI;Lo;0;L;;;;;N;;;;; 120C5;CUNEIFORM SIGN GA2 TIMES DIM TIMES SHE;Lo;0;L;;;;;N;;;;; 120C6;CUNEIFORM SIGN GA2 TIMES DUB;Lo;0;L;;;;;N;;;;; 120C7;CUNEIFORM SIGN GA2 TIMES EL;Lo;0;L;;;;;N;;;;; 120C8;CUNEIFORM SIGN GA2 TIMES EL PLUS LA;Lo;0;L;;;;;N;;;;; 120C9;CUNEIFORM SIGN GA2 TIMES EN;Lo;0;L;;;;;N;;;;; 120CA;CUNEIFORM SIGN GA2 TIMES EN TIMES GAN2 TENU;Lo;0;L;;;;;N;;;;; 120CB;CUNEIFORM SIGN GA2 TIMES GAN2 TENU;Lo;0;L;;;;;N;;;;; 120CC;CUNEIFORM SIGN GA2 TIMES GAR;Lo;0;L;;;;;N;;;;; 120CD;CUNEIFORM SIGN GA2 TIMES GI;Lo;0;L;;;;;N;;;;; 120CE;CUNEIFORM SIGN GA2 TIMES GI4;Lo;0;L;;;;;N;;;;; 120CF;CUNEIFORM SIGN GA2 TIMES GI4 PLUS A;Lo;0;L;;;;;N;;;;; 120D0;CUNEIFORM SIGN GA2 TIMES GIR2 PLUS SU;Lo;0;L;;;;;N;;;;; 120D1;CUNEIFORM SIGN GA2 TIMES HA PLUS LU PLUS ESH2;Lo;0;L;;;;;N;;;;; 120D2;CUNEIFORM SIGN GA2 TIMES HAL;Lo;0;L;;;;;N;;;;; 120D3;CUNEIFORM SIGN GA2 TIMES HAL PLUS LA;Lo;0;L;;;;;N;;;;; 120D4;CUNEIFORM SIGN GA2 TIMES HI PLUS LI;Lo;0;L;;;;;N;;;;; 120D5;CUNEIFORM SIGN GA2 TIMES HUB2;Lo;0;L;;;;;N;;;;; 120D6;CUNEIFORM SIGN GA2 TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 120D7;CUNEIFORM SIGN GA2 TIMES ISH PLUS HU PLUS ASH;Lo;0;L;;;;;N;;;;; 120D8;CUNEIFORM SIGN GA2 TIMES KAK;Lo;0;L;;;;;N;;;;; 120D9;CUNEIFORM SIGN GA2 TIMES KASKAL;Lo;0;L;;;;;N;;;;; 120DA;CUNEIFORM SIGN GA2 TIMES KID;Lo;0;L;;;;;N;;;;; 120DB;CUNEIFORM SIGN GA2 TIMES KID PLUS LAL;Lo;0;L;;;;;N;;;;; 120DC;CUNEIFORM SIGN GA2 TIMES KU3 PLUS AN;Lo;0;L;;;;;N;;;;; 120DD;CUNEIFORM SIGN GA2 TIMES LA;Lo;0;L;;;;;N;;;;; 120DE;CUNEIFORM SIGN GA2 TIMES ME PLUS EN;Lo;0;L;;;;;N;;;;; 120DF;CUNEIFORM SIGN GA2 TIMES MI;Lo;0;L;;;;;N;;;;; 120E0;CUNEIFORM SIGN GA2 TIMES NUN;Lo;0;L;;;;;N;;;;; 120E1;CUNEIFORM SIGN GA2 TIMES NUN OVER NUN;Lo;0;L;;;;;N;;;;; 120E2;CUNEIFORM SIGN GA2 TIMES PA;Lo;0;L;;;;;N;;;;; 120E3;CUNEIFORM SIGN GA2 TIMES SAL;Lo;0;L;;;;;N;;;;; 120E4;CUNEIFORM SIGN GA2 TIMES SAR;Lo;0;L;;;;;N;;;;; 120E5;CUNEIFORM SIGN GA2 TIMES SHE;Lo;0;L;;;;;N;;;;; 120E6;CUNEIFORM SIGN GA2 TIMES SHE PLUS TUR;Lo;0;L;;;;;N;;;;; 120E7;CUNEIFORM SIGN GA2 TIMES SHID;Lo;0;L;;;;;N;;;;; 120E8;CUNEIFORM SIGN GA2 TIMES SUM;Lo;0;L;;;;;N;;;;; 120E9;CUNEIFORM SIGN GA2 TIMES TAK4;Lo;0;L;;;;;N;;;;; 120EA;CUNEIFORM SIGN GA2 TIMES U;Lo;0;L;;;;;N;;;;; 120EB;CUNEIFORM SIGN GA2 TIMES UD;Lo;0;L;;;;;N;;;;; 120EC;CUNEIFORM SIGN GA2 TIMES UD PLUS DU;Lo;0;L;;;;;N;;;;; 120ED;CUNEIFORM SIGN GA2 OVER GA2;Lo;0;L;;;;;N;;;;; 120EE;CUNEIFORM SIGN GABA;Lo;0;L;;;;;N;;;;; 120EF;CUNEIFORM SIGN GABA CROSSING GABA;Lo;0;L;;;;;N;;;;; 120F0;CUNEIFORM SIGN GAD;Lo;0;L;;;;;N;;;;; 120F1;CUNEIFORM SIGN GAD OVER GAD GAR OVER GAR;Lo;0;L;;;;;N;;;;; 120F2;CUNEIFORM SIGN GAL;Lo;0;L;;;;;N;;;;; 120F3;CUNEIFORM SIGN GAL GAD OVER GAD GAR OVER GAR;Lo;0;L;;;;;N;;;;; 120F4;CUNEIFORM SIGN GALAM;Lo;0;L;;;;;N;;;;; 120F5;CUNEIFORM SIGN GAM;Lo;0;L;;;;;N;;;;; 120F6;CUNEIFORM SIGN GAN;Lo;0;L;;;;;N;;;;; 120F7;CUNEIFORM SIGN GAN2;Lo;0;L;;;;;N;;;;; 120F8;CUNEIFORM SIGN GAN2 TENU;Lo;0;L;;;;;N;;;;; 120F9;CUNEIFORM SIGN GAN2 OVER GAN2;Lo;0;L;;;;;N;;;;; 120FA;CUNEIFORM SIGN GAN2 CROSSING GAN2;Lo;0;L;;;;;N;;;;; 120FB;CUNEIFORM SIGN GAR;Lo;0;L;;;;;N;;;;; 120FC;CUNEIFORM SIGN GAR3;Lo;0;L;;;;;N;;;;; 120FD;CUNEIFORM SIGN GASHAN;Lo;0;L;;;;;N;;;;; 120FE;CUNEIFORM SIGN GESHTIN;Lo;0;L;;;;;N;;;;; 120FF;CUNEIFORM SIGN GESHTIN TIMES KUR;Lo;0;L;;;;;N;;;;; 12100;CUNEIFORM SIGN GI;Lo;0;L;;;;;N;;;;; 12101;CUNEIFORM SIGN GI TIMES E;Lo;0;L;;;;;N;;;;; 12102;CUNEIFORM SIGN GI TIMES U;Lo;0;L;;;;;N;;;;; 12103;CUNEIFORM SIGN GI CROSSING GI;Lo;0;L;;;;;N;;;;; 12104;CUNEIFORM SIGN GI4;Lo;0;L;;;;;N;;;;; 12105;CUNEIFORM SIGN GI4 OVER GI4;Lo;0;L;;;;;N;;;;; 12106;CUNEIFORM SIGN GI4 CROSSING GI4;Lo;0;L;;;;;N;;;;; 12107;CUNEIFORM SIGN GIDIM;Lo;0;L;;;;;N;;;;; 12108;CUNEIFORM SIGN GIR2;Lo;0;L;;;;;N;;;;; 12109;CUNEIFORM SIGN GIR2 GUNU;Lo;0;L;;;;;N;;;;; 1210A;CUNEIFORM SIGN GIR3;Lo;0;L;;;;;N;;;;; 1210B;CUNEIFORM SIGN GIR3 TIMES A PLUS IGI;Lo;0;L;;;;;N;;;;; 1210C;CUNEIFORM SIGN GIR3 TIMES GAN2 TENU;Lo;0;L;;;;;N;;;;; 1210D;CUNEIFORM SIGN GIR3 TIMES IGI;Lo;0;L;;;;;N;;;;; 1210E;CUNEIFORM SIGN GIR3 TIMES LU PLUS IGI;Lo;0;L;;;;;N;;;;; 1210F;CUNEIFORM SIGN GIR3 TIMES PA;Lo;0;L;;;;;N;;;;; 12110;CUNEIFORM SIGN GISAL;Lo;0;L;;;;;N;;;;; 12111;CUNEIFORM SIGN GISH;Lo;0;L;;;;;N;;;;; 12112;CUNEIFORM SIGN GISH CROSSING GISH;Lo;0;L;;;;;N;;;;; 12113;CUNEIFORM SIGN GISH TIMES BAD;Lo;0;L;;;;;N;;;;; 12114;CUNEIFORM SIGN GISH TIMES TAK4;Lo;0;L;;;;;N;;;;; 12115;CUNEIFORM SIGN GISH TENU;Lo;0;L;;;;;N;;;;; 12116;CUNEIFORM SIGN GU;Lo;0;L;;;;;N;;;;; 12117;CUNEIFORM SIGN GU CROSSING GU;Lo;0;L;;;;;N;;;;; 12118;CUNEIFORM SIGN GU2;Lo;0;L;;;;;N;;;;; 12119;CUNEIFORM SIGN GU2 TIMES KAK;Lo;0;L;;;;;N;;;;; 1211A;CUNEIFORM SIGN GU2 TIMES KAK TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 1211B;CUNEIFORM SIGN GU2 TIMES NUN;Lo;0;L;;;;;N;;;;; 1211C;CUNEIFORM SIGN GU2 TIMES SAL PLUS TUG2;Lo;0;L;;;;;N;;;;; 1211D;CUNEIFORM SIGN GU2 GUNU;Lo;0;L;;;;;N;;;;; 1211E;CUNEIFORM SIGN GUD;Lo;0;L;;;;;N;;;;; 1211F;CUNEIFORM SIGN GUD TIMES A PLUS KUR;Lo;0;L;;;;;N;;;;; 12120;CUNEIFORM SIGN GUD TIMES KUR;Lo;0;L;;;;;N;;;;; 12121;CUNEIFORM SIGN GUD OVER GUD LUGAL;Lo;0;L;;;;;N;;;;; 12122;CUNEIFORM SIGN GUL;Lo;0;L;;;;;N;;;;; 12123;CUNEIFORM SIGN GUM;Lo;0;L;;;;;N;;;;; 12124;CUNEIFORM SIGN GUM TIMES SHE;Lo;0;L;;;;;N;;;;; 12125;CUNEIFORM SIGN GUR;Lo;0;L;;;;;N;;;;; 12126;CUNEIFORM SIGN GUR7;Lo;0;L;;;;;N;;;;; 12127;CUNEIFORM SIGN GURUN;Lo;0;L;;;;;N;;;;; 12128;CUNEIFORM SIGN GURUSH;Lo;0;L;;;;;N;;;;; 12129;CUNEIFORM SIGN HA;Lo;0;L;;;;;N;;;;; 1212A;CUNEIFORM SIGN HA TENU;Lo;0;L;;;;;N;;;;; 1212B;CUNEIFORM SIGN HA GUNU;Lo;0;L;;;;;N;;;;; 1212C;CUNEIFORM SIGN HAL;Lo;0;L;;;;;N;;;;; 1212D;CUNEIFORM SIGN HI;Lo;0;L;;;;;N;;;;; 1212E;CUNEIFORM SIGN HI TIMES ASH;Lo;0;L;;;;;N;;;;; 1212F;CUNEIFORM SIGN HI TIMES ASH2;Lo;0;L;;;;;N;;;;; 12130;CUNEIFORM SIGN HI TIMES BAD;Lo;0;L;;;;;N;;;;; 12131;CUNEIFORM SIGN HI TIMES DISH;Lo;0;L;;;;;N;;;;; 12132;CUNEIFORM SIGN HI TIMES GAD;Lo;0;L;;;;;N;;;;; 12133;CUNEIFORM SIGN HI TIMES KIN;Lo;0;L;;;;;N;;;;; 12134;CUNEIFORM SIGN HI TIMES NUN;Lo;0;L;;;;;N;;;;; 12135;CUNEIFORM SIGN HI TIMES SHE;Lo;0;L;;;;;N;;;;; 12136;CUNEIFORM SIGN HI TIMES U;Lo;0;L;;;;;N;;;;; 12137;CUNEIFORM SIGN HU;Lo;0;L;;;;;N;;;;; 12138;CUNEIFORM SIGN HUB2;Lo;0;L;;;;;N;;;;; 12139;CUNEIFORM SIGN HUB2 TIMES AN;Lo;0;L;;;;;N;;;;; 1213A;CUNEIFORM SIGN HUB2 TIMES HAL;Lo;0;L;;;;;N;;;;; 1213B;CUNEIFORM SIGN HUB2 TIMES KASKAL;Lo;0;L;;;;;N;;;;; 1213C;CUNEIFORM SIGN HUB2 TIMES LISH;Lo;0;L;;;;;N;;;;; 1213D;CUNEIFORM SIGN HUB2 TIMES UD;Lo;0;L;;;;;N;;;;; 1213E;CUNEIFORM SIGN HUL2;Lo;0;L;;;;;N;;;;; 1213F;CUNEIFORM SIGN I;Lo;0;L;;;;;N;;;;; 12140;CUNEIFORM SIGN I A;Lo;0;L;;;;;N;;;;; 12141;CUNEIFORM SIGN IB;Lo;0;L;;;;;N;;;;; 12142;CUNEIFORM SIGN IDIM;Lo;0;L;;;;;N;;;;; 12143;CUNEIFORM SIGN IDIM OVER IDIM BUR;Lo;0;L;;;;;N;;;;; 12144;CUNEIFORM SIGN IDIM OVER IDIM SQUARED;Lo;0;L;;;;;N;;;;; 12145;CUNEIFORM SIGN IG;Lo;0;L;;;;;N;;;;; 12146;CUNEIFORM SIGN IGI;Lo;0;L;;;;;N;;;;; 12147;CUNEIFORM SIGN IGI DIB;Lo;0;L;;;;;N;;;;; 12148;CUNEIFORM SIGN IGI RI;Lo;0;L;;;;;N;;;;; 12149;CUNEIFORM SIGN IGI OVER IGI SHIR OVER SHIR UD OVER UD;Lo;0;L;;;;;N;;;;; 1214A;CUNEIFORM SIGN IGI GUNU;Lo;0;L;;;;;N;;;;; 1214B;CUNEIFORM SIGN IL;Lo;0;L;;;;;N;;;;; 1214C;CUNEIFORM SIGN IL TIMES GAN2 TENU;Lo;0;L;;;;;N;;;;; 1214D;CUNEIFORM SIGN IL2;Lo;0;L;;;;;N;;;;; 1214E;CUNEIFORM SIGN IM;Lo;0;L;;;;;N;;;;; 1214F;CUNEIFORM SIGN IM TIMES TAK4;Lo;0;L;;;;;N;;;;; 12150;CUNEIFORM SIGN IM CROSSING IM;Lo;0;L;;;;;N;;;;; 12151;CUNEIFORM SIGN IM OPPOSING IM;Lo;0;L;;;;;N;;;;; 12152;CUNEIFORM SIGN IM SQUARED;Lo;0;L;;;;;N;;;;; 12153;CUNEIFORM SIGN IMIN;Lo;0;L;;;;;N;;;;; 12154;CUNEIFORM SIGN IN;Lo;0;L;;;;;N;;;;; 12155;CUNEIFORM SIGN IR;Lo;0;L;;;;;N;;;;; 12156;CUNEIFORM SIGN ISH;Lo;0;L;;;;;N;;;;; 12157;CUNEIFORM SIGN KA;Lo;0;L;;;;;N;;;;; 12158;CUNEIFORM SIGN KA TIMES A;Lo;0;L;;;;;N;;;;; 12159;CUNEIFORM SIGN KA TIMES AD;Lo;0;L;;;;;N;;;;; 1215A;CUNEIFORM SIGN KA TIMES AD PLUS KU3;Lo;0;L;;;;;N;;;;; 1215B;CUNEIFORM SIGN KA TIMES ASH2;Lo;0;L;;;;;N;;;;; 1215C;CUNEIFORM SIGN KA TIMES BAD;Lo;0;L;;;;;N;;;;; 1215D;CUNEIFORM SIGN KA TIMES BALAG;Lo;0;L;;;;;N;;;;; 1215E;CUNEIFORM SIGN KA TIMES BAR;Lo;0;L;;;;;N;;;;; 1215F;CUNEIFORM SIGN KA TIMES BI;Lo;0;L;;;;;N;;;;; 12160;CUNEIFORM SIGN KA TIMES ERIN2;Lo;0;L;;;;;N;;;;; 12161;CUNEIFORM SIGN KA TIMES ESH2;Lo;0;L;;;;;N;;;;; 12162;CUNEIFORM SIGN KA TIMES GA;Lo;0;L;;;;;N;;;;; 12163;CUNEIFORM SIGN KA TIMES GAL;Lo;0;L;;;;;N;;;;; 12164;CUNEIFORM SIGN KA TIMES GAN2 TENU;Lo;0;L;;;;;N;;;;; 12165;CUNEIFORM SIGN KA TIMES GAR;Lo;0;L;;;;;N;;;;; 12166;CUNEIFORM SIGN KA TIMES GAR PLUS SHA3 PLUS A;Lo;0;L;;;;;N;;;;; 12167;CUNEIFORM SIGN KA TIMES GI;Lo;0;L;;;;;N;;;;; 12168;CUNEIFORM SIGN KA TIMES GIR2;Lo;0;L;;;;;N;;;;; 12169;CUNEIFORM SIGN KA TIMES GISH PLUS SAR;Lo;0;L;;;;;N;;;;; 1216A;CUNEIFORM SIGN KA TIMES GISH CROSSING GISH;Lo;0;L;;;;;N;;;;; 1216B;CUNEIFORM SIGN KA TIMES GU;Lo;0;L;;;;;N;;;;; 1216C;CUNEIFORM SIGN KA TIMES GUR7;Lo;0;L;;;;;N;;;;; 1216D;CUNEIFORM SIGN KA TIMES IGI;Lo;0;L;;;;;N;;;;; 1216E;CUNEIFORM SIGN KA TIMES IM;Lo;0;L;;;;;N;;;;; 1216F;CUNEIFORM SIGN KA TIMES KAK;Lo;0;L;;;;;N;;;;; 12170;CUNEIFORM SIGN KA TIMES KI;Lo;0;L;;;;;N;;;;; 12171;CUNEIFORM SIGN KA TIMES KID;Lo;0;L;;;;;N;;;;; 12172;CUNEIFORM SIGN KA TIMES LI;Lo;0;L;;;;;N;;;;; 12173;CUNEIFORM SIGN KA TIMES LU;Lo;0;L;;;;;N;;;;; 12174;CUNEIFORM SIGN KA TIMES ME;Lo;0;L;;;;;N;;;;; 12175;CUNEIFORM SIGN KA TIMES ME PLUS DU;Lo;0;L;;;;;N;;;;; 12176;CUNEIFORM SIGN KA TIMES ME PLUS GI;Lo;0;L;;;;;N;;;;; 12177;CUNEIFORM SIGN KA TIMES ME PLUS TE;Lo;0;L;;;;;N;;;;; 12178;CUNEIFORM SIGN KA TIMES MI;Lo;0;L;;;;;N;;;;; 12179;CUNEIFORM SIGN KA TIMES MI PLUS NUNUZ;Lo;0;L;;;;;N;;;;; 1217A;CUNEIFORM SIGN KA TIMES NE;Lo;0;L;;;;;N;;;;; 1217B;CUNEIFORM SIGN KA TIMES NUN;Lo;0;L;;;;;N;;;;; 1217C;CUNEIFORM SIGN KA TIMES PI;Lo;0;L;;;;;N;;;;; 1217D;CUNEIFORM SIGN KA TIMES RU;Lo;0;L;;;;;N;;;;; 1217E;CUNEIFORM SIGN KA TIMES SA;Lo;0;L;;;;;N;;;;; 1217F;CUNEIFORM SIGN KA TIMES SAR;Lo;0;L;;;;;N;;;;; 12180;CUNEIFORM SIGN KA TIMES SHA;Lo;0;L;;;;;N;;;;; 12181;CUNEIFORM SIGN KA TIMES SHE;Lo;0;L;;;;;N;;;;; 12182;CUNEIFORM SIGN KA TIMES SHID;Lo;0;L;;;;;N;;;;; 12183;CUNEIFORM SIGN KA TIMES SHU;Lo;0;L;;;;;N;;;;; 12184;CUNEIFORM SIGN KA TIMES SIG;Lo;0;L;;;;;N;;;;; 12185;CUNEIFORM SIGN KA TIMES SUHUR;Lo;0;L;;;;;N;;;;; 12186;CUNEIFORM SIGN KA TIMES TAR;Lo;0;L;;;;;N;;;;; 12187;CUNEIFORM SIGN KA TIMES U;Lo;0;L;;;;;N;;;;; 12188;CUNEIFORM SIGN KA TIMES U2;Lo;0;L;;;;;N;;;;; 12189;CUNEIFORM SIGN KA TIMES UD;Lo;0;L;;;;;N;;;;; 1218A;CUNEIFORM SIGN KA TIMES UMUM TIMES PA;Lo;0;L;;;;;N;;;;; 1218B;CUNEIFORM SIGN KA TIMES USH;Lo;0;L;;;;;N;;;;; 1218C;CUNEIFORM SIGN KA TIMES ZI;Lo;0;L;;;;;N;;;;; 1218D;CUNEIFORM SIGN KA2;Lo;0;L;;;;;N;;;;; 1218E;CUNEIFORM SIGN KA2 CROSSING KA2;Lo;0;L;;;;;N;;;;; 1218F;CUNEIFORM SIGN KAB;Lo;0;L;;;;;N;;;;; 12190;CUNEIFORM SIGN KAD2;Lo;0;L;;;;;N;;;;; 12191;CUNEIFORM SIGN KAD3;Lo;0;L;;;;;N;;;;; 12192;CUNEIFORM SIGN KAD4;Lo;0;L;;;;;N;;;;; 12193;CUNEIFORM SIGN KAD5;Lo;0;L;;;;;N;;;;; 12194;CUNEIFORM SIGN KAD5 OVER KAD5;Lo;0;L;;;;;N;;;;; 12195;CUNEIFORM SIGN KAK;Lo;0;L;;;;;N;;;;; 12196;CUNEIFORM SIGN KAK TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 12197;CUNEIFORM SIGN KAL;Lo;0;L;;;;;N;;;;; 12198;CUNEIFORM SIGN KAL TIMES BAD;Lo;0;L;;;;;N;;;;; 12199;CUNEIFORM SIGN KAL CROSSING KAL;Lo;0;L;;;;;N;;;;; 1219A;CUNEIFORM SIGN KAM2;Lo;0;L;;;;;N;;;;; 1219B;CUNEIFORM SIGN KAM4;Lo;0;L;;;;;N;;;;; 1219C;CUNEIFORM SIGN KASKAL;Lo;0;L;;;;;N;;;;; 1219D;CUNEIFORM SIGN KASKAL LAGAB TIMES U OVER LAGAB TIMES U;Lo;0;L;;;;;N;;;;; 1219E;CUNEIFORM SIGN KASKAL OVER KASKAL LAGAB TIMES U OVER LAGAB TIMES U;Lo;0;L;;;;;N;;;;; 1219F;CUNEIFORM SIGN KESH2;Lo;0;L;;;;;N;;;;; 121A0;CUNEIFORM SIGN KI;Lo;0;L;;;;;N;;;;; 121A1;CUNEIFORM SIGN KI TIMES BAD;Lo;0;L;;;;;N;;;;; 121A2;CUNEIFORM SIGN KI TIMES U;Lo;0;L;;;;;N;;;;; 121A3;CUNEIFORM SIGN KI TIMES UD;Lo;0;L;;;;;N;;;;; 121A4;CUNEIFORM SIGN KID;Lo;0;L;;;;;N;;;;; 121A5;CUNEIFORM SIGN KIN;Lo;0;L;;;;;N;;;;; 121A6;CUNEIFORM SIGN KISAL;Lo;0;L;;;;;N;;;;; 121A7;CUNEIFORM SIGN KISH;Lo;0;L;;;;;N;;;;; 121A8;CUNEIFORM SIGN KISIM5;Lo;0;L;;;;;N;;;;; 121A9;CUNEIFORM SIGN KISIM5 OVER KISIM5;Lo;0;L;;;;;N;;;;; 121AA;CUNEIFORM SIGN KU;Lo;0;L;;;;;N;;;;; 121AB;CUNEIFORM SIGN KU OVER HI TIMES ASH2 KU OVER HI TIMES ASH2;Lo;0;L;;;;;N;;;;; 121AC;CUNEIFORM SIGN KU3;Lo;0;L;;;;;N;;;;; 121AD;CUNEIFORM SIGN KU4;Lo;0;L;;;;;N;;;;; 121AE;CUNEIFORM SIGN KU4 VARIANT FORM;Lo;0;L;;;;;N;;;;; 121AF;CUNEIFORM SIGN KU7;Lo;0;L;;;;;N;;;;; 121B0;CUNEIFORM SIGN KUL;Lo;0;L;;;;;N;;;;; 121B1;CUNEIFORM SIGN KUL GUNU;Lo;0;L;;;;;N;;;;; 121B2;CUNEIFORM SIGN KUN;Lo;0;L;;;;;N;;;;; 121B3;CUNEIFORM SIGN KUR;Lo;0;L;;;;;N;;;;; 121B4;CUNEIFORM SIGN KUR OPPOSING KUR;Lo;0;L;;;;;N;;;;; 121B5;CUNEIFORM SIGN KUSHU2;Lo;0;L;;;;;N;;;;; 121B6;CUNEIFORM SIGN KWU318;Lo;0;L;;;;;N;;;;; 121B7;CUNEIFORM SIGN LA;Lo;0;L;;;;;N;;;;; 121B8;CUNEIFORM SIGN LAGAB;Lo;0;L;;;;;N;;;;; 121B9;CUNEIFORM SIGN LAGAB TIMES A;Lo;0;L;;;;;N;;;;; 121BA;CUNEIFORM SIGN LAGAB TIMES A PLUS DA PLUS HA;Lo;0;L;;;;;N;;;;; 121BB;CUNEIFORM SIGN LAGAB TIMES A PLUS GAR;Lo;0;L;;;;;N;;;;; 121BC;CUNEIFORM SIGN LAGAB TIMES A PLUS LAL;Lo;0;L;;;;;N;;;;; 121BD;CUNEIFORM SIGN LAGAB TIMES AL;Lo;0;L;;;;;N;;;;; 121BE;CUNEIFORM SIGN LAGAB TIMES AN;Lo;0;L;;;;;N;;;;; 121BF;CUNEIFORM SIGN LAGAB TIMES ASH ZIDA TENU;Lo;0;L;;;;;N;;;;; 121C0;CUNEIFORM SIGN LAGAB TIMES BAD;Lo;0;L;;;;;N;;;;; 121C1;CUNEIFORM SIGN LAGAB TIMES BI;Lo;0;L;;;;;N;;;;; 121C2;CUNEIFORM SIGN LAGAB TIMES DAR;Lo;0;L;;;;;N;;;;; 121C3;CUNEIFORM SIGN LAGAB TIMES EN;Lo;0;L;;;;;N;;;;; 121C4;CUNEIFORM SIGN LAGAB TIMES GA;Lo;0;L;;;;;N;;;;; 121C5;CUNEIFORM SIGN LAGAB TIMES GAR;Lo;0;L;;;;;N;;;;; 121C6;CUNEIFORM SIGN LAGAB TIMES GUD;Lo;0;L;;;;;N;;;;; 121C7;CUNEIFORM SIGN LAGAB TIMES GUD PLUS GUD;Lo;0;L;;;;;N;;;;; 121C8;CUNEIFORM SIGN LAGAB TIMES HA;Lo;0;L;;;;;N;;;;; 121C9;CUNEIFORM SIGN LAGAB TIMES HAL;Lo;0;L;;;;;N;;;;; 121CA;CUNEIFORM SIGN LAGAB TIMES HI TIMES NUN;Lo;0;L;;;;;N;;;;; 121CB;CUNEIFORM SIGN LAGAB TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 121CC;CUNEIFORM SIGN LAGAB TIMES IM;Lo;0;L;;;;;N;;;;; 121CD;CUNEIFORM SIGN LAGAB TIMES IM PLUS HA;Lo;0;L;;;;;N;;;;; 121CE;CUNEIFORM SIGN LAGAB TIMES IM PLUS LU;Lo;0;L;;;;;N;;;;; 121CF;CUNEIFORM SIGN LAGAB TIMES KI;Lo;0;L;;;;;N;;;;; 121D0;CUNEIFORM SIGN LAGAB TIMES KIN;Lo;0;L;;;;;N;;;;; 121D1;CUNEIFORM SIGN LAGAB TIMES KU3;Lo;0;L;;;;;N;;;;; 121D2;CUNEIFORM SIGN LAGAB TIMES KUL;Lo;0;L;;;;;N;;;;; 121D3;CUNEIFORM SIGN LAGAB TIMES KUL PLUS HI PLUS A;Lo;0;L;;;;;N;;;;; 121D4;CUNEIFORM SIGN LAGAB TIMES LAGAB;Lo;0;L;;;;;N;;;;; 121D5;CUNEIFORM SIGN LAGAB TIMES LISH;Lo;0;L;;;;;N;;;;; 121D6;CUNEIFORM SIGN LAGAB TIMES LU;Lo;0;L;;;;;N;;;;; 121D7;CUNEIFORM SIGN LAGAB TIMES LUL;Lo;0;L;;;;;N;;;;; 121D8;CUNEIFORM SIGN LAGAB TIMES ME;Lo;0;L;;;;;N;;;;; 121D9;CUNEIFORM SIGN LAGAB TIMES ME PLUS EN;Lo;0;L;;;;;N;;;;; 121DA;CUNEIFORM SIGN LAGAB TIMES MUSH;Lo;0;L;;;;;N;;;;; 121DB;CUNEIFORM SIGN LAGAB TIMES NE;Lo;0;L;;;;;N;;;;; 121DC;CUNEIFORM SIGN LAGAB TIMES SHE PLUS SUM;Lo;0;L;;;;;N;;;;; 121DD;CUNEIFORM SIGN LAGAB TIMES SHITA PLUS GISH PLUS ERIN2;Lo;0;L;;;;;N;;;;; 121DE;CUNEIFORM SIGN LAGAB TIMES SHITA PLUS GISH TENU;Lo;0;L;;;;;N;;;;; 121DF;CUNEIFORM SIGN LAGAB TIMES SHU2;Lo;0;L;;;;;N;;;;; 121E0;CUNEIFORM SIGN LAGAB TIMES SHU2 PLUS SHU2;Lo;0;L;;;;;N;;;;; 121E1;CUNEIFORM SIGN LAGAB TIMES SUM;Lo;0;L;;;;;N;;;;; 121E2;CUNEIFORM SIGN LAGAB TIMES TAG;Lo;0;L;;;;;N;;;;; 121E3;CUNEIFORM SIGN LAGAB TIMES TAK4;Lo;0;L;;;;;N;;;;; 121E4;CUNEIFORM SIGN LAGAB TIMES TE PLUS A PLUS SU PLUS NA;Lo;0;L;;;;;N;;;;; 121E5;CUNEIFORM SIGN LAGAB TIMES U;Lo;0;L;;;;;N;;;;; 121E6;CUNEIFORM SIGN LAGAB TIMES U PLUS A;Lo;0;L;;;;;N;;;;; 121E7;CUNEIFORM SIGN LAGAB TIMES U PLUS U PLUS U;Lo;0;L;;;;;N;;;;; 121E8;CUNEIFORM SIGN LAGAB TIMES U2 PLUS ASH;Lo;0;L;;;;;N;;;;; 121E9;CUNEIFORM SIGN LAGAB TIMES UD;Lo;0;L;;;;;N;;;;; 121EA;CUNEIFORM SIGN LAGAB TIMES USH;Lo;0;L;;;;;N;;;;; 121EB;CUNEIFORM SIGN LAGAB SQUARED;Lo;0;L;;;;;N;;;;; 121EC;CUNEIFORM SIGN LAGAR;Lo;0;L;;;;;N;;;;; 121ED;CUNEIFORM SIGN LAGAR TIMES SHE;Lo;0;L;;;;;N;;;;; 121EE;CUNEIFORM SIGN LAGAR TIMES SHE PLUS SUM;Lo;0;L;;;;;N;;;;; 121EF;CUNEIFORM SIGN LAGAR GUNU;Lo;0;L;;;;;N;;;;; 121F0;CUNEIFORM SIGN LAGAR GUNU OVER LAGAR GUNU SHE;Lo;0;L;;;;;N;;;;; 121F1;CUNEIFORM SIGN LAHSHU;Lo;0;L;;;;;N;;;;; 121F2;CUNEIFORM SIGN LAL;Lo;0;L;;;;;N;;;;; 121F3;CUNEIFORM SIGN LAL TIMES LAL;Lo;0;L;;;;;N;;;;; 121F4;CUNEIFORM SIGN LAM;Lo;0;L;;;;;N;;;;; 121F5;CUNEIFORM SIGN LAM TIMES KUR;Lo;0;L;;;;;N;;;;; 121F6;CUNEIFORM SIGN LAM TIMES KUR PLUS RU;Lo;0;L;;;;;N;;;;; 121F7;CUNEIFORM SIGN LI;Lo;0;L;;;;;N;;;;; 121F8;CUNEIFORM SIGN LIL;Lo;0;L;;;;;N;;;;; 121F9;CUNEIFORM SIGN LIMMU2;Lo;0;L;;;;;N;;;;; 121FA;CUNEIFORM SIGN LISH;Lo;0;L;;;;;N;;;;; 121FB;CUNEIFORM SIGN LU;Lo;0;L;;;;;N;;;;; 121FC;CUNEIFORM SIGN LU TIMES BAD;Lo;0;L;;;;;N;;;;; 121FD;CUNEIFORM SIGN LU2;Lo;0;L;;;;;N;;;;; 121FE;CUNEIFORM SIGN LU2 TIMES AL;Lo;0;L;;;;;N;;;;; 121FF;CUNEIFORM SIGN LU2 TIMES BAD;Lo;0;L;;;;;N;;;;; 12200;CUNEIFORM SIGN LU2 TIMES ESH2;Lo;0;L;;;;;N;;;;; 12201;CUNEIFORM SIGN LU2 TIMES ESH2 TENU;Lo;0;L;;;;;N;;;;; 12202;CUNEIFORM SIGN LU2 TIMES GAN2 TENU;Lo;0;L;;;;;N;;;;; 12203;CUNEIFORM SIGN LU2 TIMES HI TIMES BAD;Lo;0;L;;;;;N;;;;; 12204;CUNEIFORM SIGN LU2 TIMES IM;Lo;0;L;;;;;N;;;;; 12205;CUNEIFORM SIGN LU2 TIMES KAD2;Lo;0;L;;;;;N;;;;; 12206;CUNEIFORM SIGN LU2 TIMES KAD3;Lo;0;L;;;;;N;;;;; 12207;CUNEIFORM SIGN LU2 TIMES KAD3 PLUS ASH;Lo;0;L;;;;;N;;;;; 12208;CUNEIFORM SIGN LU2 TIMES KI;Lo;0;L;;;;;N;;;;; 12209;CUNEIFORM SIGN LU2 TIMES LA PLUS ASH;Lo;0;L;;;;;N;;;;; 1220A;CUNEIFORM SIGN LU2 TIMES LAGAB;Lo;0;L;;;;;N;;;;; 1220B;CUNEIFORM SIGN LU2 TIMES ME PLUS EN;Lo;0;L;;;;;N;;;;; 1220C;CUNEIFORM SIGN LU2 TIMES NE;Lo;0;L;;;;;N;;;;; 1220D;CUNEIFORM SIGN LU2 TIMES NU;Lo;0;L;;;;;N;;;;; 1220E;CUNEIFORM SIGN LU2 TIMES SI PLUS ASH;Lo;0;L;;;;;N;;;;; 1220F;CUNEIFORM SIGN LU2 TIMES SIK2 PLUS BU;Lo;0;L;;;;;N;;;;; 12210;CUNEIFORM SIGN LU2 TIMES TUG2;Lo;0;L;;;;;N;;;;; 12211;CUNEIFORM SIGN LU2 TENU;Lo;0;L;;;;;N;;;;; 12212;CUNEIFORM SIGN LU2 CROSSING LU2;Lo;0;L;;;;;N;;;;; 12213;CUNEIFORM SIGN LU2 OPPOSING LU2;Lo;0;L;;;;;N;;;;; 12214;CUNEIFORM SIGN LU2 SQUARED;Lo;0;L;;;;;N;;;;; 12215;CUNEIFORM SIGN LU2 SHESHIG;Lo;0;L;;;;;N;;;;; 12216;CUNEIFORM SIGN LU3;Lo;0;L;;;;;N;;;;; 12217;CUNEIFORM SIGN LUGAL;Lo;0;L;;;;;N;;;;; 12218;CUNEIFORM SIGN LUGAL OVER LUGAL;Lo;0;L;;;;;N;;;;; 12219;CUNEIFORM SIGN LUGAL OPPOSING LUGAL;Lo;0;L;;;;;N;;;;; 1221A;CUNEIFORM SIGN LUGAL SHESHIG;Lo;0;L;;;;;N;;;;; 1221B;CUNEIFORM SIGN LUH;Lo;0;L;;;;;N;;;;; 1221C;CUNEIFORM SIGN LUL;Lo;0;L;;;;;N;;;;; 1221D;CUNEIFORM SIGN LUM;Lo;0;L;;;;;N;;;;; 1221E;CUNEIFORM SIGN LUM OVER LUM;Lo;0;L;;;;;N;;;;; 1221F;CUNEIFORM SIGN LUM OVER LUM GAR OVER GAR;Lo;0;L;;;;;N;;;;; 12220;CUNEIFORM SIGN MA;Lo;0;L;;;;;N;;;;; 12221;CUNEIFORM SIGN MA TIMES TAK4;Lo;0;L;;;;;N;;;;; 12222;CUNEIFORM SIGN MA GUNU;Lo;0;L;;;;;N;;;;; 12223;CUNEIFORM SIGN MA2;Lo;0;L;;;;;N;;;;; 12224;CUNEIFORM SIGN MAH;Lo;0;L;;;;;N;;;;; 12225;CUNEIFORM SIGN MAR;Lo;0;L;;;;;N;;;;; 12226;CUNEIFORM SIGN MASH;Lo;0;L;;;;;N;;;;; 12227;CUNEIFORM SIGN MASH2;Lo;0;L;;;;;N;;;;; 12228;CUNEIFORM SIGN ME;Lo;0;L;;;;;N;;;;; 12229;CUNEIFORM SIGN MES;Lo;0;L;;;;;N;;;;; 1222A;CUNEIFORM SIGN MI;Lo;0;L;;;;;N;;;;; 1222B;CUNEIFORM SIGN MIN;Lo;0;L;;;;;N;;;;; 1222C;CUNEIFORM SIGN MU;Lo;0;L;;;;;N;;;;; 1222D;CUNEIFORM SIGN MU OVER MU;Lo;0;L;;;;;N;;;;; 1222E;CUNEIFORM SIGN MUG;Lo;0;L;;;;;N;;;;; 1222F;CUNEIFORM SIGN MUG GUNU;Lo;0;L;;;;;N;;;;; 12230;CUNEIFORM SIGN MUNSUB;Lo;0;L;;;;;N;;;;; 12231;CUNEIFORM SIGN MURGU2;Lo;0;L;;;;;N;;;;; 12232;CUNEIFORM SIGN MUSH;Lo;0;L;;;;;N;;;;; 12233;CUNEIFORM SIGN MUSH TIMES A;Lo;0;L;;;;;N;;;;; 12234;CUNEIFORM SIGN MUSH TIMES KUR;Lo;0;L;;;;;N;;;;; 12235;CUNEIFORM SIGN MUSH TIMES ZA;Lo;0;L;;;;;N;;;;; 12236;CUNEIFORM SIGN MUSH OVER MUSH;Lo;0;L;;;;;N;;;;; 12237;CUNEIFORM SIGN MUSH OVER MUSH TIMES A PLUS NA;Lo;0;L;;;;;N;;;;; 12238;CUNEIFORM SIGN MUSH CROSSING MUSH;Lo;0;L;;;;;N;;;;; 12239;CUNEIFORM SIGN MUSH3;Lo;0;L;;;;;N;;;;; 1223A;CUNEIFORM SIGN MUSH3 TIMES A;Lo;0;L;;;;;N;;;;; 1223B;CUNEIFORM SIGN MUSH3 TIMES A PLUS DI;Lo;0;L;;;;;N;;;;; 1223C;CUNEIFORM SIGN MUSH3 TIMES DI;Lo;0;L;;;;;N;;;;; 1223D;CUNEIFORM SIGN MUSH3 GUNU;Lo;0;L;;;;;N;;;;; 1223E;CUNEIFORM SIGN NA;Lo;0;L;;;;;N;;;;; 1223F;CUNEIFORM SIGN NA2;Lo;0;L;;;;;N;;;;; 12240;CUNEIFORM SIGN NAGA;Lo;0;L;;;;;N;;;;; 12241;CUNEIFORM SIGN NAGA INVERTED;Lo;0;L;;;;;N;;;;; 12242;CUNEIFORM SIGN NAGA TIMES SHU TENU;Lo;0;L;;;;;N;;;;; 12243;CUNEIFORM SIGN NAGA OPPOSING NAGA;Lo;0;L;;;;;N;;;;; 12244;CUNEIFORM SIGN NAGAR;Lo;0;L;;;;;N;;;;; 12245;CUNEIFORM SIGN NAM NUTILLU;Lo;0;L;;;;;N;;;;; 12246;CUNEIFORM SIGN NAM;Lo;0;L;;;;;N;;;;; 12247;CUNEIFORM SIGN NAM2;Lo;0;L;;;;;N;;;;; 12248;CUNEIFORM SIGN NE;Lo;0;L;;;;;N;;;;; 12249;CUNEIFORM SIGN NE TIMES A;Lo;0;L;;;;;N;;;;; 1224A;CUNEIFORM SIGN NE TIMES UD;Lo;0;L;;;;;N;;;;; 1224B;CUNEIFORM SIGN NE SHESHIG;Lo;0;L;;;;;N;;;;; 1224C;CUNEIFORM SIGN NI;Lo;0;L;;;;;N;;;;; 1224D;CUNEIFORM SIGN NI TIMES E;Lo;0;L;;;;;N;;;;; 1224E;CUNEIFORM SIGN NI2;Lo;0;L;;;;;N;;;;; 1224F;CUNEIFORM SIGN NIM;Lo;0;L;;;;;N;;;;; 12250;CUNEIFORM SIGN NIM TIMES GAN2 TENU;Lo;0;L;;;;;N;;;;; 12251;CUNEIFORM SIGN NIM TIMES GAR PLUS GAN2 TENU;Lo;0;L;;;;;N;;;;; 12252;CUNEIFORM SIGN NINDA2;Lo;0;L;;;;;N;;;;; 12253;CUNEIFORM SIGN NINDA2 TIMES AN;Lo;0;L;;;;;N;;;;; 12254;CUNEIFORM SIGN NINDA2 TIMES ASH;Lo;0;L;;;;;N;;;;; 12255;CUNEIFORM SIGN NINDA2 TIMES ASH PLUS ASH;Lo;0;L;;;;;N;;;;; 12256;CUNEIFORM SIGN NINDA2 TIMES GUD;Lo;0;L;;;;;N;;;;; 12257;CUNEIFORM SIGN NINDA2 TIMES ME PLUS GAN2 TENU;Lo;0;L;;;;;N;;;;; 12258;CUNEIFORM SIGN NINDA2 TIMES NE;Lo;0;L;;;;;N;;;;; 12259;CUNEIFORM SIGN NINDA2 TIMES NUN;Lo;0;L;;;;;N;;;;; 1225A;CUNEIFORM SIGN NINDA2 TIMES SHE;Lo;0;L;;;;;N;;;;; 1225B;CUNEIFORM SIGN NINDA2 TIMES SHE PLUS A AN;Lo;0;L;;;;;N;;;;; 1225C;CUNEIFORM SIGN NINDA2 TIMES SHE PLUS ASH;Lo;0;L;;;;;N;;;;; 1225D;CUNEIFORM SIGN NINDA2 TIMES SHE PLUS ASH PLUS ASH;Lo;0;L;;;;;N;;;;; 1225E;CUNEIFORM SIGN NINDA2 TIMES U2 PLUS ASH;Lo;0;L;;;;;N;;;;; 1225F;CUNEIFORM SIGN NINDA2 TIMES USH;Lo;0;L;;;;;N;;;;; 12260;CUNEIFORM SIGN NISAG;Lo;0;L;;;;;N;;;;; 12261;CUNEIFORM SIGN NU;Lo;0;L;;;;;N;;;;; 12262;CUNEIFORM SIGN NU11;Lo;0;L;;;;;N;;;;; 12263;CUNEIFORM SIGN NUN;Lo;0;L;;;;;N;;;;; 12264;CUNEIFORM SIGN NUN LAGAR TIMES GAR;Lo;0;L;;;;;N;;;;; 12265;CUNEIFORM SIGN NUN LAGAR TIMES MASH;Lo;0;L;;;;;N;;;;; 12266;CUNEIFORM SIGN NUN LAGAR TIMES SAL;Lo;0;L;;;;;N;;;;; 12267;CUNEIFORM SIGN NUN LAGAR TIMES SAL OVER NUN LAGAR TIMES SAL;Lo;0;L;;;;;N;;;;; 12268;CUNEIFORM SIGN NUN LAGAR TIMES USH;Lo;0;L;;;;;N;;;;; 12269;CUNEIFORM SIGN NUN TENU;Lo;0;L;;;;;N;;;;; 1226A;CUNEIFORM SIGN NUN OVER NUN;Lo;0;L;;;;;N;;;;; 1226B;CUNEIFORM SIGN NUN CROSSING NUN;Lo;0;L;;;;;N;;;;; 1226C;CUNEIFORM SIGN NUN CROSSING NUN LAGAR OVER LAGAR;Lo;0;L;;;;;N;;;;; 1226D;CUNEIFORM SIGN NUNUZ;Lo;0;L;;;;;N;;;;; 1226E;CUNEIFORM SIGN NUNUZ AB2 TIMES ASHGAB;Lo;0;L;;;;;N;;;;; 1226F;CUNEIFORM SIGN NUNUZ AB2 TIMES BI;Lo;0;L;;;;;N;;;;; 12270;CUNEIFORM SIGN NUNUZ AB2 TIMES DUG;Lo;0;L;;;;;N;;;;; 12271;CUNEIFORM SIGN NUNUZ AB2 TIMES GUD;Lo;0;L;;;;;N;;;;; 12272;CUNEIFORM SIGN NUNUZ AB2 TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 12273;CUNEIFORM SIGN NUNUZ AB2 TIMES KAD3;Lo;0;L;;;;;N;;;;; 12274;CUNEIFORM SIGN NUNUZ AB2 TIMES LA;Lo;0;L;;;;;N;;;;; 12275;CUNEIFORM SIGN NUNUZ AB2 TIMES NE;Lo;0;L;;;;;N;;;;; 12276;CUNEIFORM SIGN NUNUZ AB2 TIMES SILA3;Lo;0;L;;;;;N;;;;; 12277;CUNEIFORM SIGN NUNUZ AB2 TIMES U2;Lo;0;L;;;;;N;;;;; 12278;CUNEIFORM SIGN NUNUZ KISIM5 TIMES BI;Lo;0;L;;;;;N;;;;; 12279;CUNEIFORM SIGN NUNUZ KISIM5 TIMES BI U;Lo;0;L;;;;;N;;;;; 1227A;CUNEIFORM SIGN PA;Lo;0;L;;;;;N;;;;; 1227B;CUNEIFORM SIGN PAD;Lo;0;L;;;;;N;;;;; 1227C;CUNEIFORM SIGN PAN;Lo;0;L;;;;;N;;;;; 1227D;CUNEIFORM SIGN PAP;Lo;0;L;;;;;N;;;;; 1227E;CUNEIFORM SIGN PESH2;Lo;0;L;;;;;N;;;;; 1227F;CUNEIFORM SIGN PI;Lo;0;L;;;;;N;;;;; 12280;CUNEIFORM SIGN PI TIMES A;Lo;0;L;;;;;N;;;;; 12281;CUNEIFORM SIGN PI TIMES AB;Lo;0;L;;;;;N;;;;; 12282;CUNEIFORM SIGN PI TIMES BI;Lo;0;L;;;;;N;;;;; 12283;CUNEIFORM SIGN PI TIMES BU;Lo;0;L;;;;;N;;;;; 12284;CUNEIFORM SIGN PI TIMES E;Lo;0;L;;;;;N;;;;; 12285;CUNEIFORM SIGN PI TIMES I;Lo;0;L;;;;;N;;;;; 12286;CUNEIFORM SIGN PI TIMES IB;Lo;0;L;;;;;N;;;;; 12287;CUNEIFORM SIGN PI TIMES U;Lo;0;L;;;;;N;;;;; 12288;CUNEIFORM SIGN PI TIMES U2;Lo;0;L;;;;;N;;;;; 12289;CUNEIFORM SIGN PI CROSSING PI;Lo;0;L;;;;;N;;;;; 1228A;CUNEIFORM SIGN PIRIG;Lo;0;L;;;;;N;;;;; 1228B;CUNEIFORM SIGN PIRIG TIMES KAL;Lo;0;L;;;;;N;;;;; 1228C;CUNEIFORM SIGN PIRIG TIMES UD;Lo;0;L;;;;;N;;;;; 1228D;CUNEIFORM SIGN PIRIG TIMES ZA;Lo;0;L;;;;;N;;;;; 1228E;CUNEIFORM SIGN PIRIG OPPOSING PIRIG;Lo;0;L;;;;;N;;;;; 1228F;CUNEIFORM SIGN RA;Lo;0;L;;;;;N;;;;; 12290;CUNEIFORM SIGN RAB;Lo;0;L;;;;;N;;;;; 12291;CUNEIFORM SIGN RI;Lo;0;L;;;;;N;;;;; 12292;CUNEIFORM SIGN RU;Lo;0;L;;;;;N;;;;; 12293;CUNEIFORM SIGN SA;Lo;0;L;;;;;N;;;;; 12294;CUNEIFORM SIGN SAG NUTILLU;Lo;0;L;;;;;N;;;;; 12295;CUNEIFORM SIGN SAG;Lo;0;L;;;;;N;;;;; 12296;CUNEIFORM SIGN SAG TIMES A;Lo;0;L;;;;;N;;;;; 12297;CUNEIFORM SIGN SAG TIMES DU;Lo;0;L;;;;;N;;;;; 12298;CUNEIFORM SIGN SAG TIMES DUB;Lo;0;L;;;;;N;;;;; 12299;CUNEIFORM SIGN SAG TIMES HA;Lo;0;L;;;;;N;;;;; 1229A;CUNEIFORM SIGN SAG TIMES KAK;Lo;0;L;;;;;N;;;;; 1229B;CUNEIFORM SIGN SAG TIMES KUR;Lo;0;L;;;;;N;;;;; 1229C;CUNEIFORM SIGN SAG TIMES LUM;Lo;0;L;;;;;N;;;;; 1229D;CUNEIFORM SIGN SAG TIMES MI;Lo;0;L;;;;;N;;;;; 1229E;CUNEIFORM SIGN SAG TIMES NUN;Lo;0;L;;;;;N;;;;; 1229F;CUNEIFORM SIGN SAG TIMES SAL;Lo;0;L;;;;;N;;;;; 122A0;CUNEIFORM SIGN SAG TIMES SHID;Lo;0;L;;;;;N;;;;; 122A1;CUNEIFORM SIGN SAG TIMES TAB;Lo;0;L;;;;;N;;;;; 122A2;CUNEIFORM SIGN SAG TIMES U2;Lo;0;L;;;;;N;;;;; 122A3;CUNEIFORM SIGN SAG TIMES UB;Lo;0;L;;;;;N;;;;; 122A4;CUNEIFORM SIGN SAG TIMES UM;Lo;0;L;;;;;N;;;;; 122A5;CUNEIFORM SIGN SAG TIMES UR;Lo;0;L;;;;;N;;;;; 122A6;CUNEIFORM SIGN SAG TIMES USH;Lo;0;L;;;;;N;;;;; 122A7;CUNEIFORM SIGN SAG OVER SAG;Lo;0;L;;;;;N;;;;; 122A8;CUNEIFORM SIGN SAG GUNU;Lo;0;L;;;;;N;;;;; 122A9;CUNEIFORM SIGN SAL;Lo;0;L;;;;;N;;;;; 122AA;CUNEIFORM SIGN SAL LAGAB TIMES ASH2;Lo;0;L;;;;;N;;;;; 122AB;CUNEIFORM SIGN SANGA2;Lo;0;L;;;;;N;;;;; 122AC;CUNEIFORM SIGN SAR;Lo;0;L;;;;;N;;;;; 122AD;CUNEIFORM SIGN SHA;Lo;0;L;;;;;N;;;;; 122AE;CUNEIFORM SIGN SHA3;Lo;0;L;;;;;N;;;;; 122AF;CUNEIFORM SIGN SHA3 TIMES A;Lo;0;L;;;;;N;;;;; 122B0;CUNEIFORM SIGN SHA3 TIMES BAD;Lo;0;L;;;;;N;;;;; 122B1;CUNEIFORM SIGN SHA3 TIMES GISH;Lo;0;L;;;;;N;;;;; 122B2;CUNEIFORM SIGN SHA3 TIMES NE;Lo;0;L;;;;;N;;;;; 122B3;CUNEIFORM SIGN SHA3 TIMES SHU2;Lo;0;L;;;;;N;;;;; 122B4;CUNEIFORM SIGN SHA3 TIMES TUR;Lo;0;L;;;;;N;;;;; 122B5;CUNEIFORM SIGN SHA3 TIMES U;Lo;0;L;;;;;N;;;;; 122B6;CUNEIFORM SIGN SHA3 TIMES U PLUS A;Lo;0;L;;;;;N;;;;; 122B7;CUNEIFORM SIGN SHA6;Lo;0;L;;;;;N;;;;; 122B8;CUNEIFORM SIGN SHAB6;Lo;0;L;;;;;N;;;;; 122B9;CUNEIFORM SIGN SHAR2;Lo;0;L;;;;;N;;;;; 122BA;CUNEIFORM SIGN SHE;Lo;0;L;;;;;N;;;;; 122BB;CUNEIFORM SIGN SHE HU;Lo;0;L;;;;;N;;;;; 122BC;CUNEIFORM SIGN SHE OVER SHE GAD OVER GAD GAR OVER GAR;Lo;0;L;;;;;N;;;;; 122BD;CUNEIFORM SIGN SHE OVER SHE TAB OVER TAB GAR OVER GAR;Lo;0;L;;;;;N;;;;; 122BE;CUNEIFORM SIGN SHEG9;Lo;0;L;;;;;N;;;;; 122BF;CUNEIFORM SIGN SHEN;Lo;0;L;;;;;N;;;;; 122C0;CUNEIFORM SIGN SHESH;Lo;0;L;;;;;N;;;;; 122C1;CUNEIFORM SIGN SHESH2;Lo;0;L;;;;;N;;;;; 122C2;CUNEIFORM SIGN SHESHLAM;Lo;0;L;;;;;N;;;;; 122C3;CUNEIFORM SIGN SHID;Lo;0;L;;;;;N;;;;; 122C4;CUNEIFORM SIGN SHID TIMES A;Lo;0;L;;;;;N;;;;; 122C5;CUNEIFORM SIGN SHID TIMES IM;Lo;0;L;;;;;N;;;;; 122C6;CUNEIFORM SIGN SHIM;Lo;0;L;;;;;N;;;;; 122C7;CUNEIFORM SIGN SHIM TIMES A;Lo;0;L;;;;;N;;;;; 122C8;CUNEIFORM SIGN SHIM TIMES BAL;Lo;0;L;;;;;N;;;;; 122C9;CUNEIFORM SIGN SHIM TIMES BULUG;Lo;0;L;;;;;N;;;;; 122CA;CUNEIFORM SIGN SHIM TIMES DIN;Lo;0;L;;;;;N;;;;; 122CB;CUNEIFORM SIGN SHIM TIMES GAR;Lo;0;L;;;;;N;;;;; 122CC;CUNEIFORM SIGN SHIM TIMES IGI;Lo;0;L;;;;;N;;;;; 122CD;CUNEIFORM SIGN SHIM TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 122CE;CUNEIFORM SIGN SHIM TIMES KUSHU2;Lo;0;L;;;;;N;;;;; 122CF;CUNEIFORM SIGN SHIM TIMES LUL;Lo;0;L;;;;;N;;;;; 122D0;CUNEIFORM SIGN SHIM TIMES MUG;Lo;0;L;;;;;N;;;;; 122D1;CUNEIFORM SIGN SHIM TIMES SAL;Lo;0;L;;;;;N;;;;; 122D2;CUNEIFORM SIGN SHINIG;Lo;0;L;;;;;N;;;;; 122D3;CUNEIFORM SIGN SHIR;Lo;0;L;;;;;N;;;;; 122D4;CUNEIFORM SIGN SHIR TENU;Lo;0;L;;;;;N;;;;; 122D5;CUNEIFORM SIGN SHIR OVER SHIR BUR OVER BUR;Lo;0;L;;;;;N;;;;; 122D6;CUNEIFORM SIGN SHITA;Lo;0;L;;;;;N;;;;; 122D7;CUNEIFORM SIGN SHU;Lo;0;L;;;;;N;;;;; 122D8;CUNEIFORM SIGN SHU OVER INVERTED SHU;Lo;0;L;;;;;N;;;;; 122D9;CUNEIFORM SIGN SHU2;Lo;0;L;;;;;N;;;;; 122DA;CUNEIFORM SIGN SHUBUR;Lo;0;L;;;;;N;;;;; 122DB;CUNEIFORM SIGN SI;Lo;0;L;;;;;N;;;;; 122DC;CUNEIFORM SIGN SI GUNU;Lo;0;L;;;;;N;;;;; 122DD;CUNEIFORM SIGN SIG;Lo;0;L;;;;;N;;;;; 122DE;CUNEIFORM SIGN SIG4;Lo;0;L;;;;;N;;;;; 122DF;CUNEIFORM SIGN SIG4 OVER SIG4 SHU2;Lo;0;L;;;;;N;;;;; 122E0;CUNEIFORM SIGN SIK2;Lo;0;L;;;;;N;;;;; 122E1;CUNEIFORM SIGN SILA3;Lo;0;L;;;;;N;;;;; 122E2;CUNEIFORM SIGN SU;Lo;0;L;;;;;N;;;;; 122E3;CUNEIFORM SIGN SU OVER SU;Lo;0;L;;;;;N;;;;; 122E4;CUNEIFORM SIGN SUD;Lo;0;L;;;;;N;;;;; 122E5;CUNEIFORM SIGN SUD2;Lo;0;L;;;;;N;;;;; 122E6;CUNEIFORM SIGN SUHUR;Lo;0;L;;;;;N;;;;; 122E7;CUNEIFORM SIGN SUM;Lo;0;L;;;;;N;;;;; 122E8;CUNEIFORM SIGN SUMASH;Lo;0;L;;;;;N;;;;; 122E9;CUNEIFORM SIGN SUR;Lo;0;L;;;;;N;;;;; 122EA;CUNEIFORM SIGN SUR9;Lo;0;L;;;;;N;;;;; 122EB;CUNEIFORM SIGN TA;Lo;0;L;;;;;N;;;;; 122EC;CUNEIFORM SIGN TA ASTERISK;Lo;0;L;;;;;N;;;;; 122ED;CUNEIFORM SIGN TA TIMES HI;Lo;0;L;;;;;N;;;;; 122EE;CUNEIFORM SIGN TA TIMES MI;Lo;0;L;;;;;N;;;;; 122EF;CUNEIFORM SIGN TA GUNU;Lo;0;L;;;;;N;;;;; 122F0;CUNEIFORM SIGN TAB;Lo;0;L;;;;;N;;;;; 122F1;CUNEIFORM SIGN TAB OVER TAB NI OVER NI DISH OVER DISH;Lo;0;L;;;;;N;;;;; 122F2;CUNEIFORM SIGN TAB SQUARED;Lo;0;L;;;;;N;;;;; 122F3;CUNEIFORM SIGN TAG;Lo;0;L;;;;;N;;;;; 122F4;CUNEIFORM SIGN TAG TIMES BI;Lo;0;L;;;;;N;;;;; 122F5;CUNEIFORM SIGN TAG TIMES GUD;Lo;0;L;;;;;N;;;;; 122F6;CUNEIFORM SIGN TAG TIMES SHE;Lo;0;L;;;;;N;;;;; 122F7;CUNEIFORM SIGN TAG TIMES SHU;Lo;0;L;;;;;N;;;;; 122F8;CUNEIFORM SIGN TAG TIMES TUG2;Lo;0;L;;;;;N;;;;; 122F9;CUNEIFORM SIGN TAG TIMES UD;Lo;0;L;;;;;N;;;;; 122FA;CUNEIFORM SIGN TAK4;Lo;0;L;;;;;N;;;;; 122FB;CUNEIFORM SIGN TAR;Lo;0;L;;;;;N;;;;; 122FC;CUNEIFORM SIGN TE;Lo;0;L;;;;;N;;;;; 122FD;CUNEIFORM SIGN TE GUNU;Lo;0;L;;;;;N;;;;; 122FE;CUNEIFORM SIGN TI;Lo;0;L;;;;;N;;;;; 122FF;CUNEIFORM SIGN TI TENU;Lo;0;L;;;;;N;;;;; 12300;CUNEIFORM SIGN TIL;Lo;0;L;;;;;N;;;;; 12301;CUNEIFORM SIGN TIR;Lo;0;L;;;;;N;;;;; 12302;CUNEIFORM SIGN TIR TIMES TAK4;Lo;0;L;;;;;N;;;;; 12303;CUNEIFORM SIGN TIR OVER TIR;Lo;0;L;;;;;N;;;;; 12304;CUNEIFORM SIGN TIR OVER TIR GAD OVER GAD GAR OVER GAR;Lo;0;L;;;;;N;;;;; 12305;CUNEIFORM SIGN TU;Lo;0;L;;;;;N;;;;; 12306;CUNEIFORM SIGN TUG2;Lo;0;L;;;;;N;;;;; 12307;CUNEIFORM SIGN TUK;Lo;0;L;;;;;N;;;;; 12308;CUNEIFORM SIGN TUM;Lo;0;L;;;;;N;;;;; 12309;CUNEIFORM SIGN TUR;Lo;0;L;;;;;N;;;;; 1230A;CUNEIFORM SIGN TUR OVER TUR ZA OVER ZA;Lo;0;L;;;;;N;;;;; 1230B;CUNEIFORM SIGN U;Lo;0;L;;;;;N;;;;; 1230C;CUNEIFORM SIGN U GUD;Lo;0;L;;;;;N;;;;; 1230D;CUNEIFORM SIGN U U U;Lo;0;L;;;;;N;;;;; 1230E;CUNEIFORM SIGN U OVER U PA OVER PA GAR OVER GAR;Lo;0;L;;;;;N;;;;; 1230F;CUNEIFORM SIGN U OVER U SUR OVER SUR;Lo;0;L;;;;;N;;;;; 12310;CUNEIFORM SIGN U OVER U U REVERSED OVER U REVERSED;Lo;0;L;;;;;N;;;;; 12311;CUNEIFORM SIGN U2;Lo;0;L;;;;;N;;;;; 12312;CUNEIFORM SIGN UB;Lo;0;L;;;;;N;;;;; 12313;CUNEIFORM SIGN UD;Lo;0;L;;;;;N;;;;; 12314;CUNEIFORM SIGN UD KUSHU2;Lo;0;L;;;;;N;;;;; 12315;CUNEIFORM SIGN UD TIMES BAD;Lo;0;L;;;;;N;;;;; 12316;CUNEIFORM SIGN UD TIMES MI;Lo;0;L;;;;;N;;;;; 12317;CUNEIFORM SIGN UD TIMES U PLUS U PLUS U;Lo;0;L;;;;;N;;;;; 12318;CUNEIFORM SIGN UD TIMES U PLUS U PLUS U GUNU;Lo;0;L;;;;;N;;;;; 12319;CUNEIFORM SIGN UD GUNU;Lo;0;L;;;;;N;;;;; 1231A;CUNEIFORM SIGN UD SHESHIG;Lo;0;L;;;;;N;;;;; 1231B;CUNEIFORM SIGN UD SHESHIG TIMES BAD;Lo;0;L;;;;;N;;;;; 1231C;CUNEIFORM SIGN UDUG;Lo;0;L;;;;;N;;;;; 1231D;CUNEIFORM SIGN UM;Lo;0;L;;;;;N;;;;; 1231E;CUNEIFORM SIGN UM TIMES LAGAB;Lo;0;L;;;;;N;;;;; 1231F;CUNEIFORM SIGN UM TIMES ME PLUS DA;Lo;0;L;;;;;N;;;;; 12320;CUNEIFORM SIGN UM TIMES SHA3;Lo;0;L;;;;;N;;;;; 12321;CUNEIFORM SIGN UM TIMES U;Lo;0;L;;;;;N;;;;; 12322;CUNEIFORM SIGN UMBIN;Lo;0;L;;;;;N;;;;; 12323;CUNEIFORM SIGN UMUM;Lo;0;L;;;;;N;;;;; 12324;CUNEIFORM SIGN UMUM TIMES KASKAL;Lo;0;L;;;;;N;;;;; 12325;CUNEIFORM SIGN UMUM TIMES PA;Lo;0;L;;;;;N;;;;; 12326;CUNEIFORM SIGN UN;Lo;0;L;;;;;N;;;;; 12327;CUNEIFORM SIGN UN GUNU;Lo;0;L;;;;;N;;;;; 12328;CUNEIFORM SIGN UR;Lo;0;L;;;;;N;;;;; 12329;CUNEIFORM SIGN UR CROSSING UR;Lo;0;L;;;;;N;;;;; 1232A;CUNEIFORM SIGN UR SHESHIG;Lo;0;L;;;;;N;;;;; 1232B;CUNEIFORM SIGN UR2;Lo;0;L;;;;;N;;;;; 1232C;CUNEIFORM SIGN UR2 TIMES A PLUS HA;Lo;0;L;;;;;N;;;;; 1232D;CUNEIFORM SIGN UR2 TIMES A PLUS NA;Lo;0;L;;;;;N;;;;; 1232E;CUNEIFORM SIGN UR2 TIMES AL;Lo;0;L;;;;;N;;;;; 1232F;CUNEIFORM SIGN UR2 TIMES HA;Lo;0;L;;;;;N;;;;; 12330;CUNEIFORM SIGN UR2 TIMES NUN;Lo;0;L;;;;;N;;;;; 12331;CUNEIFORM SIGN UR2 TIMES U2;Lo;0;L;;;;;N;;;;; 12332;CUNEIFORM SIGN UR2 TIMES U2 PLUS ASH;Lo;0;L;;;;;N;;;;; 12333;CUNEIFORM SIGN UR2 TIMES U2 PLUS BI;Lo;0;L;;;;;N;;;;; 12334;CUNEIFORM SIGN UR4;Lo;0;L;;;;;N;;;;; 12335;CUNEIFORM SIGN URI;Lo;0;L;;;;;N;;;;; 12336;CUNEIFORM SIGN URI3;Lo;0;L;;;;;N;;;;; 12337;CUNEIFORM SIGN URU;Lo;0;L;;;;;N;;;;; 12338;CUNEIFORM SIGN URU TIMES A;Lo;0;L;;;;;N;;;;; 12339;CUNEIFORM SIGN URU TIMES ASHGAB;Lo;0;L;;;;;N;;;;; 1233A;CUNEIFORM SIGN URU TIMES BAR;Lo;0;L;;;;;N;;;;; 1233B;CUNEIFORM SIGN URU TIMES DUN;Lo;0;L;;;;;N;;;;; 1233C;CUNEIFORM SIGN URU TIMES GA;Lo;0;L;;;;;N;;;;; 1233D;CUNEIFORM SIGN URU TIMES GAL;Lo;0;L;;;;;N;;;;; 1233E;CUNEIFORM SIGN URU TIMES GAN2 TENU;Lo;0;L;;;;;N;;;;; 1233F;CUNEIFORM SIGN URU TIMES GAR;Lo;0;L;;;;;N;;;;; 12340;CUNEIFORM SIGN URU TIMES GU;Lo;0;L;;;;;N;;;;; 12341;CUNEIFORM SIGN URU TIMES HA;Lo;0;L;;;;;N;;;;; 12342;CUNEIFORM SIGN URU TIMES IGI;Lo;0;L;;;;;N;;;;; 12343;CUNEIFORM SIGN URU TIMES IM;Lo;0;L;;;;;N;;;;; 12344;CUNEIFORM SIGN URU TIMES ISH;Lo;0;L;;;;;N;;;;; 12345;CUNEIFORM SIGN URU TIMES KI;Lo;0;L;;;;;N;;;;; 12346;CUNEIFORM SIGN URU TIMES LUM;Lo;0;L;;;;;N;;;;; 12347;CUNEIFORM SIGN URU TIMES MIN;Lo;0;L;;;;;N;;;;; 12348;CUNEIFORM SIGN URU TIMES PA;Lo;0;L;;;;;N;;;;; 12349;CUNEIFORM SIGN URU TIMES SHE;Lo;0;L;;;;;N;;;;; 1234A;CUNEIFORM SIGN URU TIMES SIG4;Lo;0;L;;;;;N;;;;; 1234B;CUNEIFORM SIGN URU TIMES TU;Lo;0;L;;;;;N;;;;; 1234C;CUNEIFORM SIGN URU TIMES U PLUS GUD;Lo;0;L;;;;;N;;;;; 1234D;CUNEIFORM SIGN URU TIMES UD;Lo;0;L;;;;;N;;;;; 1234E;CUNEIFORM SIGN URU TIMES URUDA;Lo;0;L;;;;;N;;;;; 1234F;CUNEIFORM SIGN URUDA;Lo;0;L;;;;;N;;;;; 12350;CUNEIFORM SIGN URUDA TIMES U;Lo;0;L;;;;;N;;;;; 12351;CUNEIFORM SIGN USH;Lo;0;L;;;;;N;;;;; 12352;CUNEIFORM SIGN USH TIMES A;Lo;0;L;;;;;N;;;;; 12353;CUNEIFORM SIGN USH TIMES KU;Lo;0;L;;;;;N;;;;; 12354;CUNEIFORM SIGN USH TIMES KUR;Lo;0;L;;;;;N;;;;; 12355;CUNEIFORM SIGN USH TIMES TAK4;Lo;0;L;;;;;N;;;;; 12356;CUNEIFORM SIGN USHX;Lo;0;L;;;;;N;;;;; 12357;CUNEIFORM SIGN USH2;Lo;0;L;;;;;N;;;;; 12358;CUNEIFORM SIGN USHUMX;Lo;0;L;;;;;N;;;;; 12359;CUNEIFORM SIGN UTUKI;Lo;0;L;;;;;N;;;;; 1235A;CUNEIFORM SIGN UZ3;Lo;0;L;;;;;N;;;;; 1235B;CUNEIFORM SIGN UZ3 TIMES KASKAL;Lo;0;L;;;;;N;;;;; 1235C;CUNEIFORM SIGN UZU;Lo;0;L;;;;;N;;;;; 1235D;CUNEIFORM SIGN ZA;Lo;0;L;;;;;N;;;;; 1235E;CUNEIFORM SIGN ZA TENU;Lo;0;L;;;;;N;;;;; 1235F;CUNEIFORM SIGN ZA SQUARED TIMES KUR;Lo;0;L;;;;;N;;;;; 12360;CUNEIFORM SIGN ZAG;Lo;0;L;;;;;N;;;;; 12361;CUNEIFORM SIGN ZAMX;Lo;0;L;;;;;N;;;;; 12362;CUNEIFORM SIGN ZE2;Lo;0;L;;;;;N;;;;; 12363;CUNEIFORM SIGN ZI;Lo;0;L;;;;;N;;;;; 12364;CUNEIFORM SIGN ZI OVER ZI;Lo;0;L;;;;;N;;;;; 12365;CUNEIFORM SIGN ZI3;Lo;0;L;;;;;N;;;;; 12366;CUNEIFORM SIGN ZIB;Lo;0;L;;;;;N;;;;; 12367;CUNEIFORM SIGN ZIB KABA TENU;Lo;0;L;;;;;N;;;;; 12368;CUNEIFORM SIGN ZIG;Lo;0;L;;;;;N;;;;; 12369;CUNEIFORM SIGN ZIZ2;Lo;0;L;;;;;N;;;;; 1236A;CUNEIFORM SIGN ZU;Lo;0;L;;;;;N;;;;; 1236B;CUNEIFORM SIGN ZU5;Lo;0;L;;;;;N;;;;; 1236C;CUNEIFORM SIGN ZU5 TIMES A;Lo;0;L;;;;;N;;;;; 1236D;CUNEIFORM SIGN ZUBUR;Lo;0;L;;;;;N;;;;; 1236E;CUNEIFORM SIGN ZUM;Lo;0;L;;;;;N;;;;; 1236F;CUNEIFORM SIGN KAP ELAMITE;Lo;0;L;;;;;N;;;;; 12370;CUNEIFORM SIGN AB TIMES NUN;Lo;0;L;;;;;N;;;;; 12371;CUNEIFORM SIGN AB2 TIMES A;Lo;0;L;;;;;N;;;;; 12372;CUNEIFORM SIGN AMAR TIMES KUG;Lo;0;L;;;;;N;;;;; 12373;CUNEIFORM SIGN DAG KISIM5 TIMES U2 PLUS MASH;Lo;0;L;;;;;N;;;;; 12374;CUNEIFORM SIGN DAG3;Lo;0;L;;;;;N;;;;; 12375;CUNEIFORM SIGN DISH PLUS SHU;Lo;0;L;;;;;N;;;;; 12376;CUNEIFORM SIGN DUB TIMES SHE;Lo;0;L;;;;;N;;;;; 12377;CUNEIFORM SIGN EZEN TIMES GUD;Lo;0;L;;;;;N;;;;; 12378;CUNEIFORM SIGN EZEN TIMES SHE;Lo;0;L;;;;;N;;;;; 12379;CUNEIFORM SIGN GA2 TIMES AN PLUS KAK PLUS A;Lo;0;L;;;;;N;;;;; 1237A;CUNEIFORM SIGN GA2 TIMES ASH2;Lo;0;L;;;;;N;;;;; 1237B;CUNEIFORM SIGN GE22;Lo;0;L;;;;;N;;;;; 1237C;CUNEIFORM SIGN GIG;Lo;0;L;;;;;N;;;;; 1237D;CUNEIFORM SIGN HUSH;Lo;0;L;;;;;N;;;;; 1237E;CUNEIFORM SIGN KA TIMES ANSHE;Lo;0;L;;;;;N;;;;; 1237F;CUNEIFORM SIGN KA TIMES ASH3;Lo;0;L;;;;;N;;;;; 12380;CUNEIFORM SIGN KA TIMES GISH;Lo;0;L;;;;;N;;;;; 12381;CUNEIFORM SIGN KA TIMES GUD;Lo;0;L;;;;;N;;;;; 12382;CUNEIFORM SIGN KA TIMES HI TIMES ASH2;Lo;0;L;;;;;N;;;;; 12383;CUNEIFORM SIGN KA TIMES LUM;Lo;0;L;;;;;N;;;;; 12384;CUNEIFORM SIGN KA TIMES PA;Lo;0;L;;;;;N;;;;; 12385;CUNEIFORM SIGN KA TIMES SHUL;Lo;0;L;;;;;N;;;;; 12386;CUNEIFORM SIGN KA TIMES TU;Lo;0;L;;;;;N;;;;; 12387;CUNEIFORM SIGN KA TIMES UR2;Lo;0;L;;;;;N;;;;; 12388;CUNEIFORM SIGN LAGAB TIMES GI;Lo;0;L;;;;;N;;;;; 12389;CUNEIFORM SIGN LU2 SHESHIG TIMES BAD;Lo;0;L;;;;;N;;;;; 1238A;CUNEIFORM SIGN LU2 TIMES ESH2 PLUS LAL;Lo;0;L;;;;;N;;;;; 1238B;CUNEIFORM SIGN LU2 TIMES SHU;Lo;0;L;;;;;N;;;;; 1238C;CUNEIFORM SIGN MESH;Lo;0;L;;;;;N;;;;; 1238D;CUNEIFORM SIGN MUSH3 TIMES ZA;Lo;0;L;;;;;N;;;;; 1238E;CUNEIFORM SIGN NA4;Lo;0;L;;;;;N;;;;; 1238F;CUNEIFORM SIGN NIN;Lo;0;L;;;;;N;;;;; 12390;CUNEIFORM SIGN NIN9;Lo;0;L;;;;;N;;;;; 12391;CUNEIFORM SIGN NINDA2 TIMES BAL;Lo;0;L;;;;;N;;;;; 12392;CUNEIFORM SIGN NINDA2 TIMES GI;Lo;0;L;;;;;N;;;;; 12393;CUNEIFORM SIGN NU11 ROTATED NINETY DEGREES;Lo;0;L;;;;;N;;;;; 12394;CUNEIFORM SIGN PESH2 ASTERISK;Lo;0;L;;;;;N;;;;; 12395;CUNEIFORM SIGN PIR2;Lo;0;L;;;;;N;;;;; 12396;CUNEIFORM SIGN SAG TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 12397;CUNEIFORM SIGN TI2;Lo;0;L;;;;;N;;;;; 12398;CUNEIFORM SIGN UM TIMES ME;Lo;0;L;;;;;N;;;;; 12399;CUNEIFORM SIGN U U;Lo;0;L;;;;;N;;;;; 12400;CUNEIFORM NUMERIC SIGN TWO ASH;Nl;0;L;;;;2;N;;;;; 12401;CUNEIFORM NUMERIC SIGN THREE ASH;Nl;0;L;;;;3;N;;;;; 12402;CUNEIFORM NUMERIC SIGN FOUR ASH;Nl;0;L;;;;4;N;;;;; 12403;CUNEIFORM NUMERIC SIGN FIVE ASH;Nl;0;L;;;;5;N;;;;; 12404;CUNEIFORM NUMERIC SIGN SIX ASH;Nl;0;L;;;;6;N;;;;; 12405;CUNEIFORM NUMERIC SIGN SEVEN ASH;Nl;0;L;;;;7;N;;;;; 12406;CUNEIFORM NUMERIC SIGN EIGHT ASH;Nl;0;L;;;;8;N;;;;; 12407;CUNEIFORM NUMERIC SIGN NINE ASH;Nl;0;L;;;;9;N;;;;; 12408;CUNEIFORM NUMERIC SIGN THREE DISH;Nl;0;L;;;;3;N;;;;; 12409;CUNEIFORM NUMERIC SIGN FOUR DISH;Nl;0;L;;;;4;N;;;;; 1240A;CUNEIFORM NUMERIC SIGN FIVE DISH;Nl;0;L;;;;5;N;;;;; 1240B;CUNEIFORM NUMERIC SIGN SIX DISH;Nl;0;L;;;;6;N;;;;; 1240C;CUNEIFORM NUMERIC SIGN SEVEN DISH;Nl;0;L;;;;7;N;;;;; 1240D;CUNEIFORM NUMERIC SIGN EIGHT DISH;Nl;0;L;;;;8;N;;;;; 1240E;CUNEIFORM NUMERIC SIGN NINE DISH;Nl;0;L;;;;9;N;;;;; 1240F;CUNEIFORM NUMERIC SIGN FOUR U;Nl;0;L;;;;4;N;;;;; 12410;CUNEIFORM NUMERIC SIGN FIVE U;Nl;0;L;;;;5;N;;;;; 12411;CUNEIFORM NUMERIC SIGN SIX U;Nl;0;L;;;;6;N;;;;; 12412;CUNEIFORM NUMERIC SIGN SEVEN U;Nl;0;L;;;;7;N;;;;; 12413;CUNEIFORM NUMERIC SIGN EIGHT U;Nl;0;L;;;;8;N;;;;; 12414;CUNEIFORM NUMERIC SIGN NINE U;Nl;0;L;;;;9;N;;;;; 12415;CUNEIFORM NUMERIC SIGN ONE GESH2;Nl;0;L;;;;1;N;;;;; 12416;CUNEIFORM NUMERIC SIGN TWO GESH2;Nl;0;L;;;;2;N;;;;; 12417;CUNEIFORM NUMERIC SIGN THREE GESH2;Nl;0;L;;;;3;N;;;;; 12418;CUNEIFORM NUMERIC SIGN FOUR GESH2;Nl;0;L;;;;4;N;;;;; 12419;CUNEIFORM NUMERIC SIGN FIVE GESH2;Nl;0;L;;;;5;N;;;;; 1241A;CUNEIFORM NUMERIC SIGN SIX GESH2;Nl;0;L;;;;6;N;;;;; 1241B;CUNEIFORM NUMERIC SIGN SEVEN GESH2;Nl;0;L;;;;7;N;;;;; 1241C;CUNEIFORM NUMERIC SIGN EIGHT GESH2;Nl;0;L;;;;8;N;;;;; 1241D;CUNEIFORM NUMERIC SIGN NINE GESH2;Nl;0;L;;;;9;N;;;;; 1241E;CUNEIFORM NUMERIC SIGN ONE GESHU;Nl;0;L;;;;1;N;;;;; 1241F;CUNEIFORM NUMERIC SIGN TWO GESHU;Nl;0;L;;;;2;N;;;;; 12420;CUNEIFORM NUMERIC SIGN THREE GESHU;Nl;0;L;;;;3;N;;;;; 12421;CUNEIFORM NUMERIC SIGN FOUR GESHU;Nl;0;L;;;;4;N;;;;; 12422;CUNEIFORM NUMERIC SIGN FIVE GESHU;Nl;0;L;;;;5;N;;;;; 12423;CUNEIFORM NUMERIC SIGN TWO SHAR2;Nl;0;L;;;;2;N;;;;; 12424;CUNEIFORM NUMERIC SIGN THREE SHAR2;Nl;0;L;;;;3;N;;;;; 12425;CUNEIFORM NUMERIC SIGN THREE SHAR2 VARIANT FORM;Nl;0;L;;;;3;N;;;;; 12426;CUNEIFORM NUMERIC SIGN FOUR SHAR2;Nl;0;L;;;;4;N;;;;; 12427;CUNEIFORM NUMERIC SIGN FIVE SHAR2;Nl;0;L;;;;5;N;;;;; 12428;CUNEIFORM NUMERIC SIGN SIX SHAR2;Nl;0;L;;;;6;N;;;;; 12429;CUNEIFORM NUMERIC SIGN SEVEN SHAR2;Nl;0;L;;;;7;N;;;;; 1242A;CUNEIFORM NUMERIC SIGN EIGHT SHAR2;Nl;0;L;;;;8;N;;;;; 1242B;CUNEIFORM NUMERIC SIGN NINE SHAR2;Nl;0;L;;;;9;N;;;;; 1242C;CUNEIFORM NUMERIC SIGN ONE SHARU;Nl;0;L;;;;1;N;;;;; 1242D;CUNEIFORM NUMERIC SIGN TWO SHARU;Nl;0;L;;;;2;N;;;;; 1242E;CUNEIFORM NUMERIC SIGN THREE SHARU;Nl;0;L;;;;3;N;;;;; 1242F;CUNEIFORM NUMERIC SIGN THREE SHARU VARIANT FORM;Nl;0;L;;;;3;N;;;;; 12430;CUNEIFORM NUMERIC SIGN FOUR SHARU;Nl;0;L;;;;4;N;;;;; 12431;CUNEIFORM NUMERIC SIGN FIVE SHARU;Nl;0;L;;;;5;N;;;;; 12432;CUNEIFORM NUMERIC SIGN SHAR2 TIMES GAL PLUS DISH;Nl;0;L;;;;216000;N;;;;; 12433;CUNEIFORM NUMERIC SIGN SHAR2 TIMES GAL PLUS MIN;Nl;0;L;;;;432000;N;;;;; 12434;CUNEIFORM NUMERIC SIGN ONE BURU;Nl;0;L;;;;1;N;;;;; 12435;CUNEIFORM NUMERIC SIGN TWO BURU;Nl;0;L;;;;2;N;;;;; 12436;CUNEIFORM NUMERIC SIGN THREE BURU;Nl;0;L;;;;3;N;;;;; 12437;CUNEIFORM NUMERIC SIGN THREE BURU VARIANT FORM;Nl;0;L;;;;3;N;;;;; 12438;CUNEIFORM NUMERIC SIGN FOUR BURU;Nl;0;L;;;;4;N;;;;; 12439;CUNEIFORM NUMERIC SIGN FIVE BURU;Nl;0;L;;;;5;N;;;;; 1243A;CUNEIFORM NUMERIC SIGN THREE VARIANT FORM ESH16;Nl;0;L;;;;3;N;;;;; 1243B;CUNEIFORM NUMERIC SIGN THREE VARIANT FORM ESH21;Nl;0;L;;;;3;N;;;;; 1243C;CUNEIFORM NUMERIC SIGN FOUR VARIANT FORM LIMMU;Nl;0;L;;;;4;N;;;;; 1243D;CUNEIFORM NUMERIC SIGN FOUR VARIANT FORM LIMMU4;Nl;0;L;;;;4;N;;;;; 1243E;CUNEIFORM NUMERIC SIGN FOUR VARIANT FORM LIMMU A;Nl;0;L;;;;4;N;;;;; 1243F;CUNEIFORM NUMERIC SIGN FOUR VARIANT FORM LIMMU B;Nl;0;L;;;;4;N;;;;; 12440;CUNEIFORM NUMERIC SIGN SIX VARIANT FORM ASH9;Nl;0;L;;;;6;N;;;;; 12441;CUNEIFORM NUMERIC SIGN SEVEN VARIANT FORM IMIN3;Nl;0;L;;;;7;N;;;;; 12442;CUNEIFORM NUMERIC SIGN SEVEN VARIANT FORM IMIN A;Nl;0;L;;;;7;N;;;;; 12443;CUNEIFORM NUMERIC SIGN SEVEN VARIANT FORM IMIN B;Nl;0;L;;;;7;N;;;;; 12444;CUNEIFORM NUMERIC SIGN EIGHT VARIANT FORM USSU;Nl;0;L;;;;8;N;;;;; 12445;CUNEIFORM NUMERIC SIGN EIGHT VARIANT FORM USSU3;Nl;0;L;;;;8;N;;;;; 12446;CUNEIFORM NUMERIC SIGN NINE VARIANT FORM ILIMMU;Nl;0;L;;;;9;N;;;;; 12447;CUNEIFORM NUMERIC SIGN NINE VARIANT FORM ILIMMU3;Nl;0;L;;;;9;N;;;;; 12448;CUNEIFORM NUMERIC SIGN NINE VARIANT FORM ILIMMU4;Nl;0;L;;;;9;N;;;;; 12449;CUNEIFORM NUMERIC SIGN NINE VARIANT FORM ILIMMU A;Nl;0;L;;;;9;N;;;;; 1244A;CUNEIFORM NUMERIC SIGN TWO ASH TENU;Nl;0;L;;;;2;N;;;;; 1244B;CUNEIFORM NUMERIC SIGN THREE ASH TENU;Nl;0;L;;;;3;N;;;;; 1244C;CUNEIFORM NUMERIC SIGN FOUR ASH TENU;Nl;0;L;;;;4;N;;;;; 1244D;CUNEIFORM NUMERIC SIGN FIVE ASH TENU;Nl;0;L;;;;5;N;;;;; 1244E;CUNEIFORM NUMERIC SIGN SIX ASH TENU;Nl;0;L;;;;6;N;;;;; 1244F;CUNEIFORM NUMERIC SIGN ONE BAN2;Nl;0;L;;;;1;N;;;;; 12450;CUNEIFORM NUMERIC SIGN TWO BAN2;Nl;0;L;;;;2;N;;;;; 12451;CUNEIFORM NUMERIC SIGN THREE BAN2;Nl;0;L;;;;3;N;;;;; 12452;CUNEIFORM NUMERIC SIGN FOUR BAN2;Nl;0;L;;;;4;N;;;;; 12453;CUNEIFORM NUMERIC SIGN FOUR BAN2 VARIANT FORM;Nl;0;L;;;;4;N;;;;; 12454;CUNEIFORM NUMERIC SIGN FIVE BAN2;Nl;0;L;;;;5;N;;;;; 12455;CUNEIFORM NUMERIC SIGN FIVE BAN2 VARIANT FORM;Nl;0;L;;;;5;N;;;;; 12456;CUNEIFORM NUMERIC SIGN NIGIDAMIN;Nl;0;L;;;;2;N;;;;; 12457;CUNEIFORM NUMERIC SIGN NIGIDAESH;Nl;0;L;;;;3;N;;;;; 12458;CUNEIFORM NUMERIC SIGN ONE ESHE3;Nl;0;L;;;;1;N;;;;; 12459;CUNEIFORM NUMERIC SIGN TWO ESHE3;Nl;0;L;;;;2;N;;;;; 1245A;CUNEIFORM NUMERIC SIGN ONE THIRD DISH;Nl;0;L;;;;1/3;N;;;;; 1245B;CUNEIFORM NUMERIC SIGN TWO THIRDS DISH;Nl;0;L;;;;2/3;N;;;;; 1245C;CUNEIFORM NUMERIC SIGN FIVE SIXTHS DISH;Nl;0;L;;;;5/6;N;;;;; 1245D;CUNEIFORM NUMERIC SIGN ONE THIRD VARIANT FORM A;Nl;0;L;;;;1/3;N;;;;; 1245E;CUNEIFORM NUMERIC SIGN TWO THIRDS VARIANT FORM A;Nl;0;L;;;;2/3;N;;;;; 1245F;CUNEIFORM NUMERIC SIGN ONE EIGHTH ASH;Nl;0;L;;;;1/8;N;;;;; 12460;CUNEIFORM NUMERIC SIGN ONE QUARTER ASH;Nl;0;L;;;;1/4;N;;;;; 12461;CUNEIFORM NUMERIC SIGN OLD ASSYRIAN ONE SIXTH;Nl;0;L;;;;1/6;N;;;;; 12462;CUNEIFORM NUMERIC SIGN OLD ASSYRIAN ONE QUARTER;Nl;0;L;;;;1/4;N;;;;; 12463;CUNEIFORM NUMERIC SIGN ONE QUARTER GUR;Nl;0;L;;;;1/4;N;;;;; 12464;CUNEIFORM NUMERIC SIGN ONE HALF GUR;Nl;0;L;;;;1/2;N;;;;; 12465;CUNEIFORM NUMERIC SIGN ELAMITE ONE THIRD;Nl;0;L;;;;1/3;N;;;;; 12466;CUNEIFORM NUMERIC SIGN ELAMITE TWO THIRDS;Nl;0;L;;;;2/3;N;;;;; 12467;CUNEIFORM NUMERIC SIGN ELAMITE FORTY;Nl;0;L;;;;40;N;;;;; 12468;CUNEIFORM NUMERIC SIGN ELAMITE FIFTY;Nl;0;L;;;;50;N;;;;; 12469;CUNEIFORM NUMERIC SIGN FOUR U VARIANT FORM;Nl;0;L;;;;4;N;;;;; 1246A;CUNEIFORM NUMERIC SIGN FIVE U VARIANT FORM;Nl;0;L;;;;5;N;;;;; 1246B;CUNEIFORM NUMERIC SIGN SIX U VARIANT FORM;Nl;0;L;;;;6;N;;;;; 1246C;CUNEIFORM NUMERIC SIGN SEVEN U VARIANT FORM;Nl;0;L;;;;7;N;;;;; 1246D;CUNEIFORM NUMERIC SIGN EIGHT U VARIANT FORM;Nl;0;L;;;;8;N;;;;; 1246E;CUNEIFORM NUMERIC SIGN NINE U VARIANT FORM;Nl;0;L;;;;9;N;;;;; 12470;CUNEIFORM PUNCTUATION SIGN OLD ASSYRIAN WORD DIVIDER;Po;0;L;;;;;N;;;;; 12471;CUNEIFORM PUNCTUATION SIGN VERTICAL COLON;Po;0;L;;;;;N;;;;; 12472;CUNEIFORM PUNCTUATION SIGN DIAGONAL COLON;Po;0;L;;;;;N;;;;; 12473;CUNEIFORM PUNCTUATION SIGN DIAGONAL TRICOLON;Po;0;L;;;;;N;;;;; 12474;CUNEIFORM PUNCTUATION SIGN DIAGONAL QUADCOLON;Po;0;L;;;;;N;;;;; 12480;CUNEIFORM SIGN AB TIMES NUN TENU;Lo;0;L;;;;;N;;;;; 12481;CUNEIFORM SIGN AB TIMES SHU2;Lo;0;L;;;;;N;;;;; 12482;CUNEIFORM SIGN AD TIMES ESH2;Lo;0;L;;;;;N;;;;; 12483;CUNEIFORM SIGN BAD TIMES DISH TENU;Lo;0;L;;;;;N;;;;; 12484;CUNEIFORM SIGN BAHAR2 TIMES AB2;Lo;0;L;;;;;N;;;;; 12485;CUNEIFORM SIGN BAHAR2 TIMES NI;Lo;0;L;;;;;N;;;;; 12486;CUNEIFORM SIGN BAHAR2 TIMES ZA;Lo;0;L;;;;;N;;;;; 12487;CUNEIFORM SIGN BU OVER BU TIMES NA2;Lo;0;L;;;;;N;;;;; 12488;CUNEIFORM SIGN DA TIMES TAK4;Lo;0;L;;;;;N;;;;; 12489;CUNEIFORM SIGN DAG TIMES KUR;Lo;0;L;;;;;N;;;;; 1248A;CUNEIFORM SIGN DIM TIMES IGI;Lo;0;L;;;;;N;;;;; 1248B;CUNEIFORM SIGN DIM TIMES U U U;Lo;0;L;;;;;N;;;;; 1248C;CUNEIFORM SIGN DIM2 TIMES UD;Lo;0;L;;;;;N;;;;; 1248D;CUNEIFORM SIGN DUG TIMES ANSHE;Lo;0;L;;;;;N;;;;; 1248E;CUNEIFORM SIGN DUG TIMES ASH;Lo;0;L;;;;;N;;;;; 1248F;CUNEIFORM SIGN DUG TIMES ASH AT LEFT;Lo;0;L;;;;;N;;;;; 12490;CUNEIFORM SIGN DUG TIMES DIN;Lo;0;L;;;;;N;;;;; 12491;CUNEIFORM SIGN DUG TIMES DUN;Lo;0;L;;;;;N;;;;; 12492;CUNEIFORM SIGN DUG TIMES ERIN2;Lo;0;L;;;;;N;;;;; 12493;CUNEIFORM SIGN DUG TIMES GA;Lo;0;L;;;;;N;;;;; 12494;CUNEIFORM SIGN DUG TIMES GI;Lo;0;L;;;;;N;;;;; 12495;CUNEIFORM SIGN DUG TIMES GIR2 GUNU;Lo;0;L;;;;;N;;;;; 12496;CUNEIFORM SIGN DUG TIMES GISH;Lo;0;L;;;;;N;;;;; 12497;CUNEIFORM SIGN DUG TIMES HA;Lo;0;L;;;;;N;;;;; 12498;CUNEIFORM SIGN DUG TIMES HI;Lo;0;L;;;;;N;;;;; 12499;CUNEIFORM SIGN DUG TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 1249A;CUNEIFORM SIGN DUG TIMES KASKAL;Lo;0;L;;;;;N;;;;; 1249B;CUNEIFORM SIGN DUG TIMES KUR;Lo;0;L;;;;;N;;;;; 1249C;CUNEIFORM SIGN DUG TIMES KUSHU2;Lo;0;L;;;;;N;;;;; 1249D;CUNEIFORM SIGN DUG TIMES KUSHU2 PLUS KASKAL;Lo;0;L;;;;;N;;;;; 1249E;CUNEIFORM SIGN DUG TIMES LAK-020;Lo;0;L;;;;;N;;;;; 1249F;CUNEIFORM SIGN DUG TIMES LAM;Lo;0;L;;;;;N;;;;; 124A0;CUNEIFORM SIGN DUG TIMES LAM TIMES KUR;Lo;0;L;;;;;N;;;;; 124A1;CUNEIFORM SIGN DUG TIMES LUH PLUS GISH;Lo;0;L;;;;;N;;;;; 124A2;CUNEIFORM SIGN DUG TIMES MASH;Lo;0;L;;;;;N;;;;; 124A3;CUNEIFORM SIGN DUG TIMES MES;Lo;0;L;;;;;N;;;;; 124A4;CUNEIFORM SIGN DUG TIMES MI;Lo;0;L;;;;;N;;;;; 124A5;CUNEIFORM SIGN DUG TIMES NI;Lo;0;L;;;;;N;;;;; 124A6;CUNEIFORM SIGN DUG TIMES PI;Lo;0;L;;;;;N;;;;; 124A7;CUNEIFORM SIGN DUG TIMES SHE;Lo;0;L;;;;;N;;;;; 124A8;CUNEIFORM SIGN DUG TIMES SI GUNU;Lo;0;L;;;;;N;;;;; 124A9;CUNEIFORM SIGN E2 TIMES KUR;Lo;0;L;;;;;N;;;;; 124AA;CUNEIFORM SIGN E2 TIMES PAP;Lo;0;L;;;;;N;;;;; 124AB;CUNEIFORM SIGN ERIN2 X;Lo;0;L;;;;;N;;;;; 124AC;CUNEIFORM SIGN ESH2 CROSSING ESH2;Lo;0;L;;;;;N;;;;; 124AD;CUNEIFORM SIGN EZEN SHESHIG TIMES ASH;Lo;0;L;;;;;N;;;;; 124AE;CUNEIFORM SIGN EZEN SHESHIG TIMES HI;Lo;0;L;;;;;N;;;;; 124AF;CUNEIFORM SIGN EZEN SHESHIG TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 124B0;CUNEIFORM SIGN EZEN SHESHIG TIMES LA;Lo;0;L;;;;;N;;;;; 124B1;CUNEIFORM SIGN EZEN SHESHIG TIMES LAL;Lo;0;L;;;;;N;;;;; 124B2;CUNEIFORM SIGN EZEN SHESHIG TIMES ME;Lo;0;L;;;;;N;;;;; 124B3;CUNEIFORM SIGN EZEN SHESHIG TIMES MES;Lo;0;L;;;;;N;;;;; 124B4;CUNEIFORM SIGN EZEN SHESHIG TIMES SU;Lo;0;L;;;;;N;;;;; 124B5;CUNEIFORM SIGN EZEN TIMES SU;Lo;0;L;;;;;N;;;;; 124B6;CUNEIFORM SIGN GA2 TIMES BAHAR2;Lo;0;L;;;;;N;;;;; 124B7;CUNEIFORM SIGN GA2 TIMES DIM GUNU;Lo;0;L;;;;;N;;;;; 124B8;CUNEIFORM SIGN GA2 TIMES DUG TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 124B9;CUNEIFORM SIGN GA2 TIMES DUG TIMES KASKAL;Lo;0;L;;;;;N;;;;; 124BA;CUNEIFORM SIGN GA2 TIMES EREN;Lo;0;L;;;;;N;;;;; 124BB;CUNEIFORM SIGN GA2 TIMES GA;Lo;0;L;;;;;N;;;;; 124BC;CUNEIFORM SIGN GA2 TIMES GAR PLUS DI;Lo;0;L;;;;;N;;;;; 124BD;CUNEIFORM SIGN GA2 TIMES GAR PLUS NE;Lo;0;L;;;;;N;;;;; 124BE;CUNEIFORM SIGN GA2 TIMES HA PLUS A;Lo;0;L;;;;;N;;;;; 124BF;CUNEIFORM SIGN GA2 TIMES KUSHU2 PLUS KASKAL;Lo;0;L;;;;;N;;;;; 124C0;CUNEIFORM SIGN GA2 TIMES LAM;Lo;0;L;;;;;N;;;;; 124C1;CUNEIFORM SIGN GA2 TIMES LAM TIMES KUR;Lo;0;L;;;;;N;;;;; 124C2;CUNEIFORM SIGN GA2 TIMES LUH;Lo;0;L;;;;;N;;;;; 124C3;CUNEIFORM SIGN GA2 TIMES MUSH;Lo;0;L;;;;;N;;;;; 124C4;CUNEIFORM SIGN GA2 TIMES NE;Lo;0;L;;;;;N;;;;; 124C5;CUNEIFORM SIGN GA2 TIMES NE PLUS E2;Lo;0;L;;;;;N;;;;; 124C6;CUNEIFORM SIGN GA2 TIMES NE PLUS GI;Lo;0;L;;;;;N;;;;; 124C7;CUNEIFORM SIGN GA2 TIMES SHIM;Lo;0;L;;;;;N;;;;; 124C8;CUNEIFORM SIGN GA2 TIMES ZIZ2;Lo;0;L;;;;;N;;;;; 124C9;CUNEIFORM SIGN GABA ROTATED NINETY DEGREES;Lo;0;L;;;;;N;;;;; 124CA;CUNEIFORM SIGN GESHTIN TIMES U;Lo;0;L;;;;;N;;;;; 124CB;CUNEIFORM SIGN GISH TIMES GISH CROSSING GISH;Lo;0;L;;;;;N;;;;; 124CC;CUNEIFORM SIGN GU2 TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 124CD;CUNEIFORM SIGN GUD PLUS GISH TIMES TAK4;Lo;0;L;;;;;N;;;;; 124CE;CUNEIFORM SIGN HA TENU GUNU;Lo;0;L;;;;;N;;;;; 124CF;CUNEIFORM SIGN HI TIMES ASH OVER HI TIMES ASH;Lo;0;L;;;;;N;;;;; 124D0;CUNEIFORM SIGN KA TIMES BU;Lo;0;L;;;;;N;;;;; 124D1;CUNEIFORM SIGN KA TIMES KA;Lo;0;L;;;;;N;;;;; 124D2;CUNEIFORM SIGN KA TIMES U U U;Lo;0;L;;;;;N;;;;; 124D3;CUNEIFORM SIGN KA TIMES UR;Lo;0;L;;;;;N;;;;; 124D4;CUNEIFORM SIGN LAGAB TIMES ZU OVER ZU;Lo;0;L;;;;;N;;;;; 124D5;CUNEIFORM SIGN LAK-003;Lo;0;L;;;;;N;;;;; 124D6;CUNEIFORM SIGN LAK-021;Lo;0;L;;;;;N;;;;; 124D7;CUNEIFORM SIGN LAK-025;Lo;0;L;;;;;N;;;;; 124D8;CUNEIFORM SIGN LAK-030;Lo;0;L;;;;;N;;;;; 124D9;CUNEIFORM SIGN LAK-050;Lo;0;L;;;;;N;;;;; 124DA;CUNEIFORM SIGN LAK-051;Lo;0;L;;;;;N;;;;; 124DB;CUNEIFORM SIGN LAK-062;Lo;0;L;;;;;N;;;;; 124DC;CUNEIFORM SIGN LAK-079 OVER LAK-079 GUNU;Lo;0;L;;;;;N;;;;; 124DD;CUNEIFORM SIGN LAK-080;Lo;0;L;;;;;N;;;;; 124DE;CUNEIFORM SIGN LAK-081 OVER LAK-081;Lo;0;L;;;;;N;;;;; 124DF;CUNEIFORM SIGN LAK-092;Lo;0;L;;;;;N;;;;; 124E0;CUNEIFORM SIGN LAK-130;Lo;0;L;;;;;N;;;;; 124E1;CUNEIFORM SIGN LAK-142;Lo;0;L;;;;;N;;;;; 124E2;CUNEIFORM SIGN LAK-210;Lo;0;L;;;;;N;;;;; 124E3;CUNEIFORM SIGN LAK-219;Lo;0;L;;;;;N;;;;; 124E4;CUNEIFORM SIGN LAK-220;Lo;0;L;;;;;N;;;;; 124E5;CUNEIFORM SIGN LAK-225;Lo;0;L;;;;;N;;;;; 124E6;CUNEIFORM SIGN LAK-228;Lo;0;L;;;;;N;;;;; 124E7;CUNEIFORM SIGN LAK-238;Lo;0;L;;;;;N;;;;; 124E8;CUNEIFORM SIGN LAK-265;Lo;0;L;;;;;N;;;;; 124E9;CUNEIFORM SIGN LAK-266;Lo;0;L;;;;;N;;;;; 124EA;CUNEIFORM SIGN LAK-343;Lo;0;L;;;;;N;;;;; 124EB;CUNEIFORM SIGN LAK-347;Lo;0;L;;;;;N;;;;; 124EC;CUNEIFORM SIGN LAK-348;Lo;0;L;;;;;N;;;;; 124ED;CUNEIFORM SIGN LAK-383;Lo;0;L;;;;;N;;;;; 124EE;CUNEIFORM SIGN LAK-384;Lo;0;L;;;;;N;;;;; 124EF;CUNEIFORM SIGN LAK-390;Lo;0;L;;;;;N;;;;; 124F0;CUNEIFORM SIGN LAK-441;Lo;0;L;;;;;N;;;;; 124F1;CUNEIFORM SIGN LAK-449;Lo;0;L;;;;;N;;;;; 124F2;CUNEIFORM SIGN LAK-449 TIMES GU;Lo;0;L;;;;;N;;;;; 124F3;CUNEIFORM SIGN LAK-449 TIMES IGI;Lo;0;L;;;;;N;;;;; 124F4;CUNEIFORM SIGN LAK-449 TIMES PAP PLUS LU3;Lo;0;L;;;;;N;;;;; 124F5;CUNEIFORM SIGN LAK-449 TIMES PAP PLUS PAP PLUS LU3;Lo;0;L;;;;;N;;;;; 124F6;CUNEIFORM SIGN LAK-449 TIMES U2 PLUS BA;Lo;0;L;;;;;N;;;;; 124F7;CUNEIFORM SIGN LAK-450;Lo;0;L;;;;;N;;;;; 124F8;CUNEIFORM SIGN LAK-457;Lo;0;L;;;;;N;;;;; 124F9;CUNEIFORM SIGN LAK-470;Lo;0;L;;;;;N;;;;; 124FA;CUNEIFORM SIGN LAK-483;Lo;0;L;;;;;N;;;;; 124FB;CUNEIFORM SIGN LAK-490;Lo;0;L;;;;;N;;;;; 124FC;CUNEIFORM SIGN LAK-492;Lo;0;L;;;;;N;;;;; 124FD;CUNEIFORM SIGN LAK-493;Lo;0;L;;;;;N;;;;; 124FE;CUNEIFORM SIGN LAK-495;Lo;0;L;;;;;N;;;;; 124FF;CUNEIFORM SIGN LAK-550;Lo;0;L;;;;;N;;;;; 12500;CUNEIFORM SIGN LAK-608;Lo;0;L;;;;;N;;;;; 12501;CUNEIFORM SIGN LAK-617;Lo;0;L;;;;;N;;;;; 12502;CUNEIFORM SIGN LAK-617 TIMES ASH;Lo;0;L;;;;;N;;;;; 12503;CUNEIFORM SIGN LAK-617 TIMES BAD;Lo;0;L;;;;;N;;;;; 12504;CUNEIFORM SIGN LAK-617 TIMES DUN3 GUNU GUNU;Lo;0;L;;;;;N;;;;; 12505;CUNEIFORM SIGN LAK-617 TIMES KU3;Lo;0;L;;;;;N;;;;; 12506;CUNEIFORM SIGN LAK-617 TIMES LA;Lo;0;L;;;;;N;;;;; 12507;CUNEIFORM SIGN LAK-617 TIMES TAR;Lo;0;L;;;;;N;;;;; 12508;CUNEIFORM SIGN LAK-617 TIMES TE;Lo;0;L;;;;;N;;;;; 12509;CUNEIFORM SIGN LAK-617 TIMES U2;Lo;0;L;;;;;N;;;;; 1250A;CUNEIFORM SIGN LAK-617 TIMES UD;Lo;0;L;;;;;N;;;;; 1250B;CUNEIFORM SIGN LAK-617 TIMES URUDA;Lo;0;L;;;;;N;;;;; 1250C;CUNEIFORM SIGN LAK-636;Lo;0;L;;;;;N;;;;; 1250D;CUNEIFORM SIGN LAK-648;Lo;0;L;;;;;N;;;;; 1250E;CUNEIFORM SIGN LAK-648 TIMES DUB;Lo;0;L;;;;;N;;;;; 1250F;CUNEIFORM SIGN LAK-648 TIMES GA;Lo;0;L;;;;;N;;;;; 12510;CUNEIFORM SIGN LAK-648 TIMES IGI;Lo;0;L;;;;;N;;;;; 12511;CUNEIFORM SIGN LAK-648 TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 12512;CUNEIFORM SIGN LAK-648 TIMES NI;Lo;0;L;;;;;N;;;;; 12513;CUNEIFORM SIGN LAK-648 TIMES PAP PLUS PAP PLUS LU3;Lo;0;L;;;;;N;;;;; 12514;CUNEIFORM SIGN LAK-648 TIMES SHESH PLUS KI;Lo;0;L;;;;;N;;;;; 12515;CUNEIFORM SIGN LAK-648 TIMES UD;Lo;0;L;;;;;N;;;;; 12516;CUNEIFORM SIGN LAK-648 TIMES URUDA;Lo;0;L;;;;;N;;;;; 12517;CUNEIFORM SIGN LAK-724;Lo;0;L;;;;;N;;;;; 12518;CUNEIFORM SIGN LAK-749;Lo;0;L;;;;;N;;;;; 12519;CUNEIFORM SIGN LU2 GUNU TIMES ASH;Lo;0;L;;;;;N;;;;; 1251A;CUNEIFORM SIGN LU2 TIMES DISH;Lo;0;L;;;;;N;;;;; 1251B;CUNEIFORM SIGN LU2 TIMES HAL;Lo;0;L;;;;;N;;;;; 1251C;CUNEIFORM SIGN LU2 TIMES PAP;Lo;0;L;;;;;N;;;;; 1251D;CUNEIFORM SIGN LU2 TIMES PAP PLUS PAP PLUS LU3;Lo;0;L;;;;;N;;;;; 1251E;CUNEIFORM SIGN LU2 TIMES TAK4;Lo;0;L;;;;;N;;;;; 1251F;CUNEIFORM SIGN MI PLUS ZA7;Lo;0;L;;;;;N;;;;; 12520;CUNEIFORM SIGN MUSH OVER MUSH TIMES GA;Lo;0;L;;;;;N;;;;; 12521;CUNEIFORM SIGN MUSH OVER MUSH TIMES KAK;Lo;0;L;;;;;N;;;;; 12522;CUNEIFORM SIGN NINDA2 TIMES DIM GUNU;Lo;0;L;;;;;N;;;;; 12523;CUNEIFORM SIGN NINDA2 TIMES GISH;Lo;0;L;;;;;N;;;;; 12524;CUNEIFORM SIGN NINDA2 TIMES GUL;Lo;0;L;;;;;N;;;;; 12525;CUNEIFORM SIGN NINDA2 TIMES HI;Lo;0;L;;;;;N;;;;; 12526;CUNEIFORM SIGN NINDA2 TIMES KESH2;Lo;0;L;;;;;N;;;;; 12527;CUNEIFORM SIGN NINDA2 TIMES LAK-050;Lo;0;L;;;;;N;;;;; 12528;CUNEIFORM SIGN NINDA2 TIMES MASH;Lo;0;L;;;;;N;;;;; 12529;CUNEIFORM SIGN NINDA2 TIMES PAP PLUS PAP;Lo;0;L;;;;;N;;;;; 1252A;CUNEIFORM SIGN NINDA2 TIMES U;Lo;0;L;;;;;N;;;;; 1252B;CUNEIFORM SIGN NINDA2 TIMES U PLUS U;Lo;0;L;;;;;N;;;;; 1252C;CUNEIFORM SIGN NINDA2 TIMES URUDA;Lo;0;L;;;;;N;;;;; 1252D;CUNEIFORM SIGN SAG GUNU TIMES HA;Lo;0;L;;;;;N;;;;; 1252E;CUNEIFORM SIGN SAG TIMES EN;Lo;0;L;;;;;N;;;;; 1252F;CUNEIFORM SIGN SAG TIMES SHE AT LEFT;Lo;0;L;;;;;N;;;;; 12530;CUNEIFORM SIGN SAG TIMES TAK4;Lo;0;L;;;;;N;;;;; 12531;CUNEIFORM SIGN SHA6 TENU;Lo;0;L;;;;;N;;;;; 12532;CUNEIFORM SIGN SHE OVER SHE;Lo;0;L;;;;;N;;;;; 12533;CUNEIFORM SIGN SHE PLUS HUB2;Lo;0;L;;;;;N;;;;; 12534;CUNEIFORM SIGN SHE PLUS NAM2;Lo;0;L;;;;;N;;;;; 12535;CUNEIFORM SIGN SHE PLUS SAR;Lo;0;L;;;;;N;;;;; 12536;CUNEIFORM SIGN SHU2 PLUS DUG TIMES NI;Lo;0;L;;;;;N;;;;; 12537;CUNEIFORM SIGN SHU2 PLUS E2 TIMES AN;Lo;0;L;;;;;N;;;;; 12538;CUNEIFORM SIGN SI TIMES TAK4;Lo;0;L;;;;;N;;;;; 12539;CUNEIFORM SIGN TAK4 PLUS SAG;Lo;0;L;;;;;N;;;;; 1253A;CUNEIFORM SIGN TUM TIMES GAN2 TENU;Lo;0;L;;;;;N;;;;; 1253B;CUNEIFORM SIGN TUM TIMES THREE DISH;Lo;0;L;;;;;N;;;;; 1253C;CUNEIFORM SIGN UR2 INVERTED;Lo;0;L;;;;;N;;;;; 1253D;CUNEIFORM SIGN UR2 TIMES UD;Lo;0;L;;;;;N;;;;; 1253E;CUNEIFORM SIGN URU TIMES DARA3;Lo;0;L;;;;;N;;;;; 1253F;CUNEIFORM SIGN URU TIMES LAK-668;Lo;0;L;;;;;N;;;;; 12540;CUNEIFORM SIGN URU TIMES LU3;Lo;0;L;;;;;N;;;;; 12541;CUNEIFORM SIGN ZA7;Lo;0;L;;;;;N;;;;; 12542;CUNEIFORM SIGN ZU OVER ZU PLUS SAR;Lo;0;L;;;;;N;;;;; 12543;CUNEIFORM SIGN ZU5 TIMES THREE DISH TENU;Lo;0;L;;;;;N;;;;; 13000;EGYPTIAN HIEROGLYPH A001;Lo;0;L;;;;;N;;;;; 13001;EGYPTIAN HIEROGLYPH A002;Lo;0;L;;;;;N;;;;; 13002;EGYPTIAN HIEROGLYPH A003;Lo;0;L;;;;;N;;;;; 13003;EGYPTIAN HIEROGLYPH A004;Lo;0;L;;;;;N;;;;; 13004;EGYPTIAN HIEROGLYPH A005;Lo;0;L;;;;;N;;;;; 13005;EGYPTIAN HIEROGLYPH A005A;Lo;0;L;;;;;N;;;;; 13006;EGYPTIAN HIEROGLYPH A006;Lo;0;L;;;;;N;;;;; 13007;EGYPTIAN HIEROGLYPH A006A;Lo;0;L;;;;;N;;;;; 13008;EGYPTIAN HIEROGLYPH A006B;Lo;0;L;;;;;N;;;;; 13009;EGYPTIAN HIEROGLYPH A007;Lo;0;L;;;;;N;;;;; 1300A;EGYPTIAN HIEROGLYPH A008;Lo;0;L;;;;;N;;;;; 1300B;EGYPTIAN HIEROGLYPH A009;Lo;0;L;;;;;N;;;;; 1300C;EGYPTIAN HIEROGLYPH A010;Lo;0;L;;;;;N;;;;; 1300D;EGYPTIAN HIEROGLYPH A011;Lo;0;L;;;;;N;;;;; 1300E;EGYPTIAN HIEROGLYPH A012;Lo;0;L;;;;;N;;;;; 1300F;EGYPTIAN HIEROGLYPH A013;Lo;0;L;;;;;N;;;;; 13010;EGYPTIAN HIEROGLYPH A014;Lo;0;L;;;;;N;;;;; 13011;EGYPTIAN HIEROGLYPH A014A;Lo;0;L;;;;;N;;;;; 13012;EGYPTIAN HIEROGLYPH A015;Lo;0;L;;;;;N;;;;; 13013;EGYPTIAN HIEROGLYPH A016;Lo;0;L;;;;;N;;;;; 13014;EGYPTIAN HIEROGLYPH A017;Lo;0;L;;;;;N;;;;; 13015;EGYPTIAN HIEROGLYPH A017A;Lo;0;L;;;;;N;;;;; 13016;EGYPTIAN HIEROGLYPH A018;Lo;0;L;;;;;N;;;;; 13017;EGYPTIAN HIEROGLYPH A019;Lo;0;L;;;;;N;;;;; 13018;EGYPTIAN HIEROGLYPH A020;Lo;0;L;;;;;N;;;;; 13019;EGYPTIAN HIEROGLYPH A021;Lo;0;L;;;;;N;;;;; 1301A;EGYPTIAN HIEROGLYPH A022;Lo;0;L;;;;;N;;;;; 1301B;EGYPTIAN HIEROGLYPH A023;Lo;0;L;;;;;N;;;;; 1301C;EGYPTIAN HIEROGLYPH A024;Lo;0;L;;;;;N;;;;; 1301D;EGYPTIAN HIEROGLYPH A025;Lo;0;L;;;;;N;;;;; 1301E;EGYPTIAN HIEROGLYPH A026;Lo;0;L;;;;;N;;;;; 1301F;EGYPTIAN HIEROGLYPH A027;Lo;0;L;;;;;N;;;;; 13020;EGYPTIAN HIEROGLYPH A028;Lo;0;L;;;;;N;;;;; 13021;EGYPTIAN HIEROGLYPH A029;Lo;0;L;;;;;N;;;;; 13022;EGYPTIAN HIEROGLYPH A030;Lo;0;L;;;;;N;;;;; 13023;EGYPTIAN HIEROGLYPH A031;Lo;0;L;;;;;N;;;;; 13024;EGYPTIAN HIEROGLYPH A032;Lo;0;L;;;;;N;;;;; 13025;EGYPTIAN HIEROGLYPH A032A;Lo;0;L;;;;;N;;;;; 13026;EGYPTIAN HIEROGLYPH A033;Lo;0;L;;;;;N;;;;; 13027;EGYPTIAN HIEROGLYPH A034;Lo;0;L;;;;;N;;;;; 13028;EGYPTIAN HIEROGLYPH A035;Lo;0;L;;;;;N;;;;; 13029;EGYPTIAN HIEROGLYPH A036;Lo;0;L;;;;;N;;;;; 1302A;EGYPTIAN HIEROGLYPH A037;Lo;0;L;;;;;N;;;;; 1302B;EGYPTIAN HIEROGLYPH A038;Lo;0;L;;;;;N;;;;; 1302C;EGYPTIAN HIEROGLYPH A039;Lo;0;L;;;;;N;;;;; 1302D;EGYPTIAN HIEROGLYPH A040;Lo;0;L;;;;;N;;;;; 1302E;EGYPTIAN HIEROGLYPH A040A;Lo;0;L;;;;;N;;;;; 1302F;EGYPTIAN HIEROGLYPH A041;Lo;0;L;;;;;N;;;;; 13030;EGYPTIAN HIEROGLYPH A042;Lo;0;L;;;;;N;;;;; 13031;EGYPTIAN HIEROGLYPH A042A;Lo;0;L;;;;;N;;;;; 13032;EGYPTIAN HIEROGLYPH A043;Lo;0;L;;;;;N;;;;; 13033;EGYPTIAN HIEROGLYPH A043A;Lo;0;L;;;;;N;;;;; 13034;EGYPTIAN HIEROGLYPH A044;Lo;0;L;;;;;N;;;;; 13035;EGYPTIAN HIEROGLYPH A045;Lo;0;L;;;;;N;;;;; 13036;EGYPTIAN HIEROGLYPH A045A;Lo;0;L;;;;;N;;;;; 13037;EGYPTIAN HIEROGLYPH A046;Lo;0;L;;;;;N;;;;; 13038;EGYPTIAN HIEROGLYPH A047;Lo;0;L;;;;;N;;;;; 13039;EGYPTIAN HIEROGLYPH A048;Lo;0;L;;;;;N;;;;; 1303A;EGYPTIAN HIEROGLYPH A049;Lo;0;L;;;;;N;;;;; 1303B;EGYPTIAN HIEROGLYPH A050;Lo;0;L;;;;;N;;;;; 1303C;EGYPTIAN HIEROGLYPH A051;Lo;0;L;;;;;N;;;;; 1303D;EGYPTIAN HIEROGLYPH A052;Lo;0;L;;;;;N;;;;; 1303E;EGYPTIAN HIEROGLYPH A053;Lo;0;L;;;;;N;;;;; 1303F;EGYPTIAN HIEROGLYPH A054;Lo;0;L;;;;;N;;;;; 13040;EGYPTIAN HIEROGLYPH A055;Lo;0;L;;;;;N;;;;; 13041;EGYPTIAN HIEROGLYPH A056;Lo;0;L;;;;;N;;;;; 13042;EGYPTIAN HIEROGLYPH A057;Lo;0;L;;;;;N;;;;; 13043;EGYPTIAN HIEROGLYPH A058;Lo;0;L;;;;;N;;;;; 13044;EGYPTIAN HIEROGLYPH A059;Lo;0;L;;;;;N;;;;; 13045;EGYPTIAN HIEROGLYPH A060;Lo;0;L;;;;;N;;;;; 13046;EGYPTIAN HIEROGLYPH A061;Lo;0;L;;;;;N;;;;; 13047;EGYPTIAN HIEROGLYPH A062;Lo;0;L;;;;;N;;;;; 13048;EGYPTIAN HIEROGLYPH A063;Lo;0;L;;;;;N;;;;; 13049;EGYPTIAN HIEROGLYPH A064;Lo;0;L;;;;;N;;;;; 1304A;EGYPTIAN HIEROGLYPH A065;Lo;0;L;;;;;N;;;;; 1304B;EGYPTIAN HIEROGLYPH A066;Lo;0;L;;;;;N;;;;; 1304C;EGYPTIAN HIEROGLYPH A067;Lo;0;L;;;;;N;;;;; 1304D;EGYPTIAN HIEROGLYPH A068;Lo;0;L;;;;;N;;;;; 1304E;EGYPTIAN HIEROGLYPH A069;Lo;0;L;;;;;N;;;;; 1304F;EGYPTIAN HIEROGLYPH A070;Lo;0;L;;;;;N;;;;; 13050;EGYPTIAN HIEROGLYPH B001;Lo;0;L;;;;;N;;;;; 13051;EGYPTIAN HIEROGLYPH B002;Lo;0;L;;;;;N;;;;; 13052;EGYPTIAN HIEROGLYPH B003;Lo;0;L;;;;;N;;;;; 13053;EGYPTIAN HIEROGLYPH B004;Lo;0;L;;;;;N;;;;; 13054;EGYPTIAN HIEROGLYPH B005;Lo;0;L;;;;;N;;;;; 13055;EGYPTIAN HIEROGLYPH B005A;Lo;0;L;;;;;N;;;;; 13056;EGYPTIAN HIEROGLYPH B006;Lo;0;L;;;;;N;;;;; 13057;EGYPTIAN HIEROGLYPH B007;Lo;0;L;;;;;N;;;;; 13058;EGYPTIAN HIEROGLYPH B008;Lo;0;L;;;;;N;;;;; 13059;EGYPTIAN HIEROGLYPH B009;Lo;0;L;;;;;N;;;;; 1305A;EGYPTIAN HIEROGLYPH C001;Lo;0;L;;;;;N;;;;; 1305B;EGYPTIAN HIEROGLYPH C002;Lo;0;L;;;;;N;;;;; 1305C;EGYPTIAN HIEROGLYPH C002A;Lo;0;L;;;;;N;;;;; 1305D;EGYPTIAN HIEROGLYPH C002B;Lo;0;L;;;;;N;;;;; 1305E;EGYPTIAN HIEROGLYPH C002C;Lo;0;L;;;;;N;;;;; 1305F;EGYPTIAN HIEROGLYPH C003;Lo;0;L;;;;;N;;;;; 13060;EGYPTIAN HIEROGLYPH C004;Lo;0;L;;;;;N;;;;; 13061;EGYPTIAN HIEROGLYPH C005;Lo;0;L;;;;;N;;;;; 13062;EGYPTIAN HIEROGLYPH C006;Lo;0;L;;;;;N;;;;; 13063;EGYPTIAN HIEROGLYPH C007;Lo;0;L;;;;;N;;;;; 13064;EGYPTIAN HIEROGLYPH C008;Lo;0;L;;;;;N;;;;; 13065;EGYPTIAN HIEROGLYPH C009;Lo;0;L;;;;;N;;;;; 13066;EGYPTIAN HIEROGLYPH C010;Lo;0;L;;;;;N;;;;; 13067;EGYPTIAN HIEROGLYPH C010A;Lo;0;L;;;;;N;;;;; 13068;EGYPTIAN HIEROGLYPH C011;Lo;0;L;;;;;N;;;;; 13069;EGYPTIAN HIEROGLYPH C012;Lo;0;L;;;;;N;;;;; 1306A;EGYPTIAN HIEROGLYPH C013;Lo;0;L;;;;;N;;;;; 1306B;EGYPTIAN HIEROGLYPH C014;Lo;0;L;;;;;N;;;;; 1306C;EGYPTIAN HIEROGLYPH C015;Lo;0;L;;;;;N;;;;; 1306D;EGYPTIAN HIEROGLYPH C016;Lo;0;L;;;;;N;;;;; 1306E;EGYPTIAN HIEROGLYPH C017;Lo;0;L;;;;;N;;;;; 1306F;EGYPTIAN HIEROGLYPH C018;Lo;0;L;;;;;N;;;;; 13070;EGYPTIAN HIEROGLYPH C019;Lo;0;L;;;;;N;;;;; 13071;EGYPTIAN HIEROGLYPH C020;Lo;0;L;;;;;N;;;;; 13072;EGYPTIAN HIEROGLYPH C021;Lo;0;L;;;;;N;;;;; 13073;EGYPTIAN HIEROGLYPH C022;Lo;0;L;;;;;N;;;;; 13074;EGYPTIAN HIEROGLYPH C023;Lo;0;L;;;;;N;;;;; 13075;EGYPTIAN HIEROGLYPH C024;Lo;0;L;;;;;N;;;;; 13076;EGYPTIAN HIEROGLYPH D001;Lo;0;L;;;;;N;;;;; 13077;EGYPTIAN HIEROGLYPH D002;Lo;0;L;;;;;N;;;;; 13078;EGYPTIAN HIEROGLYPH D003;Lo;0;L;;;;;N;;;;; 13079;EGYPTIAN HIEROGLYPH D004;Lo;0;L;;;;;N;;;;; 1307A;EGYPTIAN HIEROGLYPH D005;Lo;0;L;;;;;N;;;;; 1307B;EGYPTIAN HIEROGLYPH D006;Lo;0;L;;;;;N;;;;; 1307C;EGYPTIAN HIEROGLYPH D007;Lo;0;L;;;;;N;;;;; 1307D;EGYPTIAN HIEROGLYPH D008;Lo;0;L;;;;;N;;;;; 1307E;EGYPTIAN HIEROGLYPH D008A;Lo;0;L;;;;;N;;;;; 1307F;EGYPTIAN HIEROGLYPH D009;Lo;0;L;;;;;N;;;;; 13080;EGYPTIAN HIEROGLYPH D010;Lo;0;L;;;;;N;;;;; 13081;EGYPTIAN HIEROGLYPH D011;Lo;0;L;;;;;N;;;;; 13082;EGYPTIAN HIEROGLYPH D012;Lo;0;L;;;;;N;;;;; 13083;EGYPTIAN HIEROGLYPH D013;Lo;0;L;;;;;N;;;;; 13084;EGYPTIAN HIEROGLYPH D014;Lo;0;L;;;;;N;;;;; 13085;EGYPTIAN HIEROGLYPH D015;Lo;0;L;;;;;N;;;;; 13086;EGYPTIAN HIEROGLYPH D016;Lo;0;L;;;;;N;;;;; 13087;EGYPTIAN HIEROGLYPH D017;Lo;0;L;;;;;N;;;;; 13088;EGYPTIAN HIEROGLYPH D018;Lo;0;L;;;;;N;;;;; 13089;EGYPTIAN HIEROGLYPH D019;Lo;0;L;;;;;N;;;;; 1308A;EGYPTIAN HIEROGLYPH D020;Lo;0;L;;;;;N;;;;; 1308B;EGYPTIAN HIEROGLYPH D021;Lo;0;L;;;;;N;;;;; 1308C;EGYPTIAN HIEROGLYPH D022;Lo;0;L;;;;;N;;;;; 1308D;EGYPTIAN HIEROGLYPH D023;Lo;0;L;;;;;N;;;;; 1308E;EGYPTIAN HIEROGLYPH D024;Lo;0;L;;;;;N;;;;; 1308F;EGYPTIAN HIEROGLYPH D025;Lo;0;L;;;;;N;;;;; 13090;EGYPTIAN HIEROGLYPH D026;Lo;0;L;;;;;N;;;;; 13091;EGYPTIAN HIEROGLYPH D027;Lo;0;L;;;;;N;;;;; 13092;EGYPTIAN HIEROGLYPH D027A;Lo;0;L;;;;;N;;;;; 13093;EGYPTIAN HIEROGLYPH D028;Lo;0;L;;;;;N;;;;; 13094;EGYPTIAN HIEROGLYPH D029;Lo;0;L;;;;;N;;;;; 13095;EGYPTIAN HIEROGLYPH D030;Lo;0;L;;;;;N;;;;; 13096;EGYPTIAN HIEROGLYPH D031;Lo;0;L;;;;;N;;;;; 13097;EGYPTIAN HIEROGLYPH D031A;Lo;0;L;;;;;N;;;;; 13098;EGYPTIAN HIEROGLYPH D032;Lo;0;L;;;;;N;;;;; 13099;EGYPTIAN HIEROGLYPH D033;Lo;0;L;;;;;N;;;;; 1309A;EGYPTIAN HIEROGLYPH D034;Lo;0;L;;;;;N;;;;; 1309B;EGYPTIAN HIEROGLYPH D034A;Lo;0;L;;;;;N;;;;; 1309C;EGYPTIAN HIEROGLYPH D035;Lo;0;L;;;;;N;;;;; 1309D;EGYPTIAN HIEROGLYPH D036;Lo;0;L;;;;;N;;;;; 1309E;EGYPTIAN HIEROGLYPH D037;Lo;0;L;;;;;N;;;;; 1309F;EGYPTIAN HIEROGLYPH D038;Lo;0;L;;;;;N;;;;; 130A0;EGYPTIAN HIEROGLYPH D039;Lo;0;L;;;;;N;;;;; 130A1;EGYPTIAN HIEROGLYPH D040;Lo;0;L;;;;;N;;;;; 130A2;EGYPTIAN HIEROGLYPH D041;Lo;0;L;;;;;N;;;;; 130A3;EGYPTIAN HIEROGLYPH D042;Lo;0;L;;;;;N;;;;; 130A4;EGYPTIAN HIEROGLYPH D043;Lo;0;L;;;;;N;;;;; 130A5;EGYPTIAN HIEROGLYPH D044;Lo;0;L;;;;;N;;;;; 130A6;EGYPTIAN HIEROGLYPH D045;Lo;0;L;;;;;N;;;;; 130A7;EGYPTIAN HIEROGLYPH D046;Lo;0;L;;;;;N;;;;; 130A8;EGYPTIAN HIEROGLYPH D046A;Lo;0;L;;;;;N;;;;; 130A9;EGYPTIAN HIEROGLYPH D047;Lo;0;L;;;;;N;;;;; 130AA;EGYPTIAN HIEROGLYPH D048;Lo;0;L;;;;;N;;;;; 130AB;EGYPTIAN HIEROGLYPH D048A;Lo;0;L;;;;;N;;;;; 130AC;EGYPTIAN HIEROGLYPH D049;Lo;0;L;;;;;N;;;;; 130AD;EGYPTIAN HIEROGLYPH D050;Lo;0;L;;;;;N;;;;; 130AE;EGYPTIAN HIEROGLYPH D050A;Lo;0;L;;;;;N;;;;; 130AF;EGYPTIAN HIEROGLYPH D050B;Lo;0;L;;;;;N;;;;; 130B0;EGYPTIAN HIEROGLYPH D050C;Lo;0;L;;;;;N;;;;; 130B1;EGYPTIAN HIEROGLYPH D050D;Lo;0;L;;;;;N;;;;; 130B2;EGYPTIAN HIEROGLYPH D050E;Lo;0;L;;;;;N;;;;; 130B3;EGYPTIAN HIEROGLYPH D050F;Lo;0;L;;;;;N;;;;; 130B4;EGYPTIAN HIEROGLYPH D050G;Lo;0;L;;;;;N;;;;; 130B5;EGYPTIAN HIEROGLYPH D050H;Lo;0;L;;;;;N;;;;; 130B6;EGYPTIAN HIEROGLYPH D050I;Lo;0;L;;;;;N;;;;; 130B7;EGYPTIAN HIEROGLYPH D051;Lo;0;L;;;;;N;;;;; 130B8;EGYPTIAN HIEROGLYPH D052;Lo;0;L;;;;;N;;;;; 130B9;EGYPTIAN HIEROGLYPH D052A;Lo;0;L;;;;;N;;;;; 130BA;EGYPTIAN HIEROGLYPH D053;Lo;0;L;;;;;N;;;;; 130BB;EGYPTIAN HIEROGLYPH D054;Lo;0;L;;;;;N;;;;; 130BC;EGYPTIAN HIEROGLYPH D054A;Lo;0;L;;;;;N;;;;; 130BD;EGYPTIAN HIEROGLYPH D055;Lo;0;L;;;;;N;;;;; 130BE;EGYPTIAN HIEROGLYPH D056;Lo;0;L;;;;;N;;;;; 130BF;EGYPTIAN HIEROGLYPH D057;Lo;0;L;;;;;N;;;;; 130C0;EGYPTIAN HIEROGLYPH D058;Lo;0;L;;;;;N;;;;; 130C1;EGYPTIAN HIEROGLYPH D059;Lo;0;L;;;;;N;;;;; 130C2;EGYPTIAN HIEROGLYPH D060;Lo;0;L;;;;;N;;;;; 130C3;EGYPTIAN HIEROGLYPH D061;Lo;0;L;;;;;N;;;;; 130C4;EGYPTIAN HIEROGLYPH D062;Lo;0;L;;;;;N;;;;; 130C5;EGYPTIAN HIEROGLYPH D063;Lo;0;L;;;;;N;;;;; 130C6;EGYPTIAN HIEROGLYPH D064;Lo;0;L;;;;;N;;;;; 130C7;EGYPTIAN HIEROGLYPH D065;Lo;0;L;;;;;N;;;;; 130C8;EGYPTIAN HIEROGLYPH D066;Lo;0;L;;;;;N;;;;; 130C9;EGYPTIAN HIEROGLYPH D067;Lo;0;L;;;;;N;;;;; 130CA;EGYPTIAN HIEROGLYPH D067A;Lo;0;L;;;;;N;;;;; 130CB;EGYPTIAN HIEROGLYPH D067B;Lo;0;L;;;;;N;;;;; 130CC;EGYPTIAN HIEROGLYPH D067C;Lo;0;L;;;;;N;;;;; 130CD;EGYPTIAN HIEROGLYPH D067D;Lo;0;L;;;;;N;;;;; 130CE;EGYPTIAN HIEROGLYPH D067E;Lo;0;L;;;;;N;;;;; 130CF;EGYPTIAN HIEROGLYPH D067F;Lo;0;L;;;;;N;;;;; 130D0;EGYPTIAN HIEROGLYPH D067G;Lo;0;L;;;;;N;;;;; 130D1;EGYPTIAN HIEROGLYPH D067H;Lo;0;L;;;;;N;;;;; 130D2;EGYPTIAN HIEROGLYPH E001;Lo;0;L;;;;;N;;;;; 130D3;EGYPTIAN HIEROGLYPH E002;Lo;0;L;;;;;N;;;;; 130D4;EGYPTIAN HIEROGLYPH E003;Lo;0;L;;;;;N;;;;; 130D5;EGYPTIAN HIEROGLYPH E004;Lo;0;L;;;;;N;;;;; 130D6;EGYPTIAN HIEROGLYPH E005;Lo;0;L;;;;;N;;;;; 130D7;EGYPTIAN HIEROGLYPH E006;Lo;0;L;;;;;N;;;;; 130D8;EGYPTIAN HIEROGLYPH E007;Lo;0;L;;;;;N;;;;; 130D9;EGYPTIAN HIEROGLYPH E008;Lo;0;L;;;;;N;;;;; 130DA;EGYPTIAN HIEROGLYPH E008A;Lo;0;L;;;;;N;;;;; 130DB;EGYPTIAN HIEROGLYPH E009;Lo;0;L;;;;;N;;;;; 130DC;EGYPTIAN HIEROGLYPH E009A;Lo;0;L;;;;;N;;;;; 130DD;EGYPTIAN HIEROGLYPH E010;Lo;0;L;;;;;N;;;;; 130DE;EGYPTIAN HIEROGLYPH E011;Lo;0;L;;;;;N;;;;; 130DF;EGYPTIAN HIEROGLYPH E012;Lo;0;L;;;;;N;;;;; 130E0;EGYPTIAN HIEROGLYPH E013;Lo;0;L;;;;;N;;;;; 130E1;EGYPTIAN HIEROGLYPH E014;Lo;0;L;;;;;N;;;;; 130E2;EGYPTIAN HIEROGLYPH E015;Lo;0;L;;;;;N;;;;; 130E3;EGYPTIAN HIEROGLYPH E016;Lo;0;L;;;;;N;;;;; 130E4;EGYPTIAN HIEROGLYPH E016A;Lo;0;L;;;;;N;;;;; 130E5;EGYPTIAN HIEROGLYPH E017;Lo;0;L;;;;;N;;;;; 130E6;EGYPTIAN HIEROGLYPH E017A;Lo;0;L;;;;;N;;;;; 130E7;EGYPTIAN HIEROGLYPH E018;Lo;0;L;;;;;N;;;;; 130E8;EGYPTIAN HIEROGLYPH E019;Lo;0;L;;;;;N;;;;; 130E9;EGYPTIAN HIEROGLYPH E020;Lo;0;L;;;;;N;;;;; 130EA;EGYPTIAN HIEROGLYPH E020A;Lo;0;L;;;;;N;;;;; 130EB;EGYPTIAN HIEROGLYPH E021;Lo;0;L;;;;;N;;;;; 130EC;EGYPTIAN HIEROGLYPH E022;Lo;0;L;;;;;N;;;;; 130ED;EGYPTIAN HIEROGLYPH E023;Lo;0;L;;;;;N;;;;; 130EE;EGYPTIAN HIEROGLYPH E024;Lo;0;L;;;;;N;;;;; 130EF;EGYPTIAN HIEROGLYPH E025;Lo;0;L;;;;;N;;;;; 130F0;EGYPTIAN HIEROGLYPH E026;Lo;0;L;;;;;N;;;;; 130F1;EGYPTIAN HIEROGLYPH E027;Lo;0;L;;;;;N;;;;; 130F2;EGYPTIAN HIEROGLYPH E028;Lo;0;L;;;;;N;;;;; 130F3;EGYPTIAN HIEROGLYPH E028A;Lo;0;L;;;;;N;;;;; 130F4;EGYPTIAN HIEROGLYPH E029;Lo;0;L;;;;;N;;;;; 130F5;EGYPTIAN HIEROGLYPH E030;Lo;0;L;;;;;N;;;;; 130F6;EGYPTIAN HIEROGLYPH E031;Lo;0;L;;;;;N;;;;; 130F7;EGYPTIAN HIEROGLYPH E032;Lo;0;L;;;;;N;;;;; 130F8;EGYPTIAN HIEROGLYPH E033;Lo;0;L;;;;;N;;;;; 130F9;EGYPTIAN HIEROGLYPH E034;Lo;0;L;;;;;N;;;;; 130FA;EGYPTIAN HIEROGLYPH E034A;Lo;0;L;;;;;N;;;;; 130FB;EGYPTIAN HIEROGLYPH E036;Lo;0;L;;;;;N;;;;; 130FC;EGYPTIAN HIEROGLYPH E037;Lo;0;L;;;;;N;;;;; 130FD;EGYPTIAN HIEROGLYPH E038;Lo;0;L;;;;;N;;;;; 130FE;EGYPTIAN HIEROGLYPH F001;Lo;0;L;;;;;N;;;;; 130FF;EGYPTIAN HIEROGLYPH F001A;Lo;0;L;;;;;N;;;;; 13100;EGYPTIAN HIEROGLYPH F002;Lo;0;L;;;;;N;;;;; 13101;EGYPTIAN HIEROGLYPH F003;Lo;0;L;;;;;N;;;;; 13102;EGYPTIAN HIEROGLYPH F004;Lo;0;L;;;;;N;;;;; 13103;EGYPTIAN HIEROGLYPH F005;Lo;0;L;;;;;N;;;;; 13104;EGYPTIAN HIEROGLYPH F006;Lo;0;L;;;;;N;;;;; 13105;EGYPTIAN HIEROGLYPH F007;Lo;0;L;;;;;N;;;;; 13106;EGYPTIAN HIEROGLYPH F008;Lo;0;L;;;;;N;;;;; 13107;EGYPTIAN HIEROGLYPH F009;Lo;0;L;;;;;N;;;;; 13108;EGYPTIAN HIEROGLYPH F010;Lo;0;L;;;;;N;;;;; 13109;EGYPTIAN HIEROGLYPH F011;Lo;0;L;;;;;N;;;;; 1310A;EGYPTIAN HIEROGLYPH F012;Lo;0;L;;;;;N;;;;; 1310B;EGYPTIAN HIEROGLYPH F013;Lo;0;L;;;;;N;;;;; 1310C;EGYPTIAN HIEROGLYPH F013A;Lo;0;L;;;;;N;;;;; 1310D;EGYPTIAN HIEROGLYPH F014;Lo;0;L;;;;;N;;;;; 1310E;EGYPTIAN HIEROGLYPH F015;Lo;0;L;;;;;N;;;;; 1310F;EGYPTIAN HIEROGLYPH F016;Lo;0;L;;;;;N;;;;; 13110;EGYPTIAN HIEROGLYPH F017;Lo;0;L;;;;;N;;;;; 13111;EGYPTIAN HIEROGLYPH F018;Lo;0;L;;;;;N;;;;; 13112;EGYPTIAN HIEROGLYPH F019;Lo;0;L;;;;;N;;;;; 13113;EGYPTIAN HIEROGLYPH F020;Lo;0;L;;;;;N;;;;; 13114;EGYPTIAN HIEROGLYPH F021;Lo;0;L;;;;;N;;;;; 13115;EGYPTIAN HIEROGLYPH F021A;Lo;0;L;;;;;N;;;;; 13116;EGYPTIAN HIEROGLYPH F022;Lo;0;L;;;;;N;;;;; 13117;EGYPTIAN HIEROGLYPH F023;Lo;0;L;;;;;N;;;;; 13118;EGYPTIAN HIEROGLYPH F024;Lo;0;L;;;;;N;;;;; 13119;EGYPTIAN HIEROGLYPH F025;Lo;0;L;;;;;N;;;;; 1311A;EGYPTIAN HIEROGLYPH F026;Lo;0;L;;;;;N;;;;; 1311B;EGYPTIAN HIEROGLYPH F027;Lo;0;L;;;;;N;;;;; 1311C;EGYPTIAN HIEROGLYPH F028;Lo;0;L;;;;;N;;;;; 1311D;EGYPTIAN HIEROGLYPH F029;Lo;0;L;;;;;N;;;;; 1311E;EGYPTIAN HIEROGLYPH F030;Lo;0;L;;;;;N;;;;; 1311F;EGYPTIAN HIEROGLYPH F031;Lo;0;L;;;;;N;;;;; 13120;EGYPTIAN HIEROGLYPH F031A;Lo;0;L;;;;;N;;;;; 13121;EGYPTIAN HIEROGLYPH F032;Lo;0;L;;;;;N;;;;; 13122;EGYPTIAN HIEROGLYPH F033;Lo;0;L;;;;;N;;;;; 13123;EGYPTIAN HIEROGLYPH F034;Lo;0;L;;;;;N;;;;; 13124;EGYPTIAN HIEROGLYPH F035;Lo;0;L;;;;;N;;;;; 13125;EGYPTIAN HIEROGLYPH F036;Lo;0;L;;;;;N;;;;; 13126;EGYPTIAN HIEROGLYPH F037;Lo;0;L;;;;;N;;;;; 13127;EGYPTIAN HIEROGLYPH F037A;Lo;0;L;;;;;N;;;;; 13128;EGYPTIAN HIEROGLYPH F038;Lo;0;L;;;;;N;;;;; 13129;EGYPTIAN HIEROGLYPH F038A;Lo;0;L;;;;;N;;;;; 1312A;EGYPTIAN HIEROGLYPH F039;Lo;0;L;;;;;N;;;;; 1312B;EGYPTIAN HIEROGLYPH F040;Lo;0;L;;;;;N;;;;; 1312C;EGYPTIAN HIEROGLYPH F041;Lo;0;L;;;;;N;;;;; 1312D;EGYPTIAN HIEROGLYPH F042;Lo;0;L;;;;;N;;;;; 1312E;EGYPTIAN HIEROGLYPH F043;Lo;0;L;;;;;N;;;;; 1312F;EGYPTIAN HIEROGLYPH F044;Lo;0;L;;;;;N;;;;; 13130;EGYPTIAN HIEROGLYPH F045;Lo;0;L;;;;;N;;;;; 13131;EGYPTIAN HIEROGLYPH F045A;Lo;0;L;;;;;N;;;;; 13132;EGYPTIAN HIEROGLYPH F046;Lo;0;L;;;;;N;;;;; 13133;EGYPTIAN HIEROGLYPH F046A;Lo;0;L;;;;;N;;;;; 13134;EGYPTIAN HIEROGLYPH F047;Lo;0;L;;;;;N;;;;; 13135;EGYPTIAN HIEROGLYPH F047A;Lo;0;L;;;;;N;;;;; 13136;EGYPTIAN HIEROGLYPH F048;Lo;0;L;;;;;N;;;;; 13137;EGYPTIAN HIEROGLYPH F049;Lo;0;L;;;;;N;;;;; 13138;EGYPTIAN HIEROGLYPH F050;Lo;0;L;;;;;N;;;;; 13139;EGYPTIAN HIEROGLYPH F051;Lo;0;L;;;;;N;;;;; 1313A;EGYPTIAN HIEROGLYPH F051A;Lo;0;L;;;;;N;;;;; 1313B;EGYPTIAN HIEROGLYPH F051B;Lo;0;L;;;;;N;;;;; 1313C;EGYPTIAN HIEROGLYPH F051C;Lo;0;L;;;;;N;;;;; 1313D;EGYPTIAN HIEROGLYPH F052;Lo;0;L;;;;;N;;;;; 1313E;EGYPTIAN HIEROGLYPH F053;Lo;0;L;;;;;N;;;;; 1313F;EGYPTIAN HIEROGLYPH G001;Lo;0;L;;;;;N;;;;; 13140;EGYPTIAN HIEROGLYPH G002;Lo;0;L;;;;;N;;;;; 13141;EGYPTIAN HIEROGLYPH G003;Lo;0;L;;;;;N;;;;; 13142;EGYPTIAN HIEROGLYPH G004;Lo;0;L;;;;;N;;;;; 13143;EGYPTIAN HIEROGLYPH G005;Lo;0;L;;;;;N;;;;; 13144;EGYPTIAN HIEROGLYPH G006;Lo;0;L;;;;;N;;;;; 13145;EGYPTIAN HIEROGLYPH G006A;Lo;0;L;;;;;N;;;;; 13146;EGYPTIAN HIEROGLYPH G007;Lo;0;L;;;;;N;;;;; 13147;EGYPTIAN HIEROGLYPH G007A;Lo;0;L;;;;;N;;;;; 13148;EGYPTIAN HIEROGLYPH G007B;Lo;0;L;;;;;N;;;;; 13149;EGYPTIAN HIEROGLYPH G008;Lo;0;L;;;;;N;;;;; 1314A;EGYPTIAN HIEROGLYPH G009;Lo;0;L;;;;;N;;;;; 1314B;EGYPTIAN HIEROGLYPH G010;Lo;0;L;;;;;N;;;;; 1314C;EGYPTIAN HIEROGLYPH G011;Lo;0;L;;;;;N;;;;; 1314D;EGYPTIAN HIEROGLYPH G011A;Lo;0;L;;;;;N;;;;; 1314E;EGYPTIAN HIEROGLYPH G012;Lo;0;L;;;;;N;;;;; 1314F;EGYPTIAN HIEROGLYPH G013;Lo;0;L;;;;;N;;;;; 13150;EGYPTIAN HIEROGLYPH G014;Lo;0;L;;;;;N;;;;; 13151;EGYPTIAN HIEROGLYPH G015;Lo;0;L;;;;;N;;;;; 13152;EGYPTIAN HIEROGLYPH G016;Lo;0;L;;;;;N;;;;; 13153;EGYPTIAN HIEROGLYPH G017;Lo;0;L;;;;;N;;;;; 13154;EGYPTIAN HIEROGLYPH G018;Lo;0;L;;;;;N;;;;; 13155;EGYPTIAN HIEROGLYPH G019;Lo;0;L;;;;;N;;;;; 13156;EGYPTIAN HIEROGLYPH G020;Lo;0;L;;;;;N;;;;; 13157;EGYPTIAN HIEROGLYPH G020A;Lo;0;L;;;;;N;;;;; 13158;EGYPTIAN HIEROGLYPH G021;Lo;0;L;;;;;N;;;;; 13159;EGYPTIAN HIEROGLYPH G022;Lo;0;L;;;;;N;;;;; 1315A;EGYPTIAN HIEROGLYPH G023;Lo;0;L;;;;;N;;;;; 1315B;EGYPTIAN HIEROGLYPH G024;Lo;0;L;;;;;N;;;;; 1315C;EGYPTIAN HIEROGLYPH G025;Lo;0;L;;;;;N;;;;; 1315D;EGYPTIAN HIEROGLYPH G026;Lo;0;L;;;;;N;;;;; 1315E;EGYPTIAN HIEROGLYPH G026A;Lo;0;L;;;;;N;;;;; 1315F;EGYPTIAN HIEROGLYPH G027;Lo;0;L;;;;;N;;;;; 13160;EGYPTIAN HIEROGLYPH G028;Lo;0;L;;;;;N;;;;; 13161;EGYPTIAN HIEROGLYPH G029;Lo;0;L;;;;;N;;;;; 13162;EGYPTIAN HIEROGLYPH G030;Lo;0;L;;;;;N;;;;; 13163;EGYPTIAN HIEROGLYPH G031;Lo;0;L;;;;;N;;;;; 13164;EGYPTIAN HIEROGLYPH G032;Lo;0;L;;;;;N;;;;; 13165;EGYPTIAN HIEROGLYPH G033;Lo;0;L;;;;;N;;;;; 13166;EGYPTIAN HIEROGLYPH G034;Lo;0;L;;;;;N;;;;; 13167;EGYPTIAN HIEROGLYPH G035;Lo;0;L;;;;;N;;;;; 13168;EGYPTIAN HIEROGLYPH G036;Lo;0;L;;;;;N;;;;; 13169;EGYPTIAN HIEROGLYPH G036A;Lo;0;L;;;;;N;;;;; 1316A;EGYPTIAN HIEROGLYPH G037;Lo;0;L;;;;;N;;;;; 1316B;EGYPTIAN HIEROGLYPH G037A;Lo;0;L;;;;;N;;;;; 1316C;EGYPTIAN HIEROGLYPH G038;Lo;0;L;;;;;N;;;;; 1316D;EGYPTIAN HIEROGLYPH G039;Lo;0;L;;;;;N;;;;; 1316E;EGYPTIAN HIEROGLYPH G040;Lo;0;L;;;;;N;;;;; 1316F;EGYPTIAN HIEROGLYPH G041;Lo;0;L;;;;;N;;;;; 13170;EGYPTIAN HIEROGLYPH G042;Lo;0;L;;;;;N;;;;; 13171;EGYPTIAN HIEROGLYPH G043;Lo;0;L;;;;;N;;;;; 13172;EGYPTIAN HIEROGLYPH G043A;Lo;0;L;;;;;N;;;;; 13173;EGYPTIAN HIEROGLYPH G044;Lo;0;L;;;;;N;;;;; 13174;EGYPTIAN HIEROGLYPH G045;Lo;0;L;;;;;N;;;;; 13175;EGYPTIAN HIEROGLYPH G045A;Lo;0;L;;;;;N;;;;; 13176;EGYPTIAN HIEROGLYPH G046;Lo;0;L;;;;;N;;;;; 13177;EGYPTIAN HIEROGLYPH G047;Lo;0;L;;;;;N;;;;; 13178;EGYPTIAN HIEROGLYPH G048;Lo;0;L;;;;;N;;;;; 13179;EGYPTIAN HIEROGLYPH G049;Lo;0;L;;;;;N;;;;; 1317A;EGYPTIAN HIEROGLYPH G050;Lo;0;L;;;;;N;;;;; 1317B;EGYPTIAN HIEROGLYPH G051;Lo;0;L;;;;;N;;;;; 1317C;EGYPTIAN HIEROGLYPH G052;Lo;0;L;;;;;N;;;;; 1317D;EGYPTIAN HIEROGLYPH G053;Lo;0;L;;;;;N;;;;; 1317E;EGYPTIAN HIEROGLYPH G054;Lo;0;L;;;;;N;;;;; 1317F;EGYPTIAN HIEROGLYPH H001;Lo;0;L;;;;;N;;;;; 13180;EGYPTIAN HIEROGLYPH H002;Lo;0;L;;;;;N;;;;; 13181;EGYPTIAN HIEROGLYPH H003;Lo;0;L;;;;;N;;;;; 13182;EGYPTIAN HIEROGLYPH H004;Lo;0;L;;;;;N;;;;; 13183;EGYPTIAN HIEROGLYPH H005;Lo;0;L;;;;;N;;;;; 13184;EGYPTIAN HIEROGLYPH H006;Lo;0;L;;;;;N;;;;; 13185;EGYPTIAN HIEROGLYPH H006A;Lo;0;L;;;;;N;;;;; 13186;EGYPTIAN HIEROGLYPH H007;Lo;0;L;;;;;N;;;;; 13187;EGYPTIAN HIEROGLYPH H008;Lo;0;L;;;;;N;;;;; 13188;EGYPTIAN HIEROGLYPH I001;Lo;0;L;;;;;N;;;;; 13189;EGYPTIAN HIEROGLYPH I002;Lo;0;L;;;;;N;;;;; 1318A;EGYPTIAN HIEROGLYPH I003;Lo;0;L;;;;;N;;;;; 1318B;EGYPTIAN HIEROGLYPH I004;Lo;0;L;;;;;N;;;;; 1318C;EGYPTIAN HIEROGLYPH I005;Lo;0;L;;;;;N;;;;; 1318D;EGYPTIAN HIEROGLYPH I005A;Lo;0;L;;;;;N;;;;; 1318E;EGYPTIAN HIEROGLYPH I006;Lo;0;L;;;;;N;;;;; 1318F;EGYPTIAN HIEROGLYPH I007;Lo;0;L;;;;;N;;;;; 13190;EGYPTIAN HIEROGLYPH I008;Lo;0;L;;;;;N;;;;; 13191;EGYPTIAN HIEROGLYPH I009;Lo;0;L;;;;;N;;;;; 13192;EGYPTIAN HIEROGLYPH I009A;Lo;0;L;;;;;N;;;;; 13193;EGYPTIAN HIEROGLYPH I010;Lo;0;L;;;;;N;;;;; 13194;EGYPTIAN HIEROGLYPH I010A;Lo;0;L;;;;;N;;;;; 13195;EGYPTIAN HIEROGLYPH I011;Lo;0;L;;;;;N;;;;; 13196;EGYPTIAN HIEROGLYPH I011A;Lo;0;L;;;;;N;;;;; 13197;EGYPTIAN HIEROGLYPH I012;Lo;0;L;;;;;N;;;;; 13198;EGYPTIAN HIEROGLYPH I013;Lo;0;L;;;;;N;;;;; 13199;EGYPTIAN HIEROGLYPH I014;Lo;0;L;;;;;N;;;;; 1319A;EGYPTIAN HIEROGLYPH I015;Lo;0;L;;;;;N;;;;; 1319B;EGYPTIAN HIEROGLYPH K001;Lo;0;L;;;;;N;;;;; 1319C;EGYPTIAN HIEROGLYPH K002;Lo;0;L;;;;;N;;;;; 1319D;EGYPTIAN HIEROGLYPH K003;Lo;0;L;;;;;N;;;;; 1319E;EGYPTIAN HIEROGLYPH K004;Lo;0;L;;;;;N;;;;; 1319F;EGYPTIAN HIEROGLYPH K005;Lo;0;L;;;;;N;;;;; 131A0;EGYPTIAN HIEROGLYPH K006;Lo;0;L;;;;;N;;;;; 131A1;EGYPTIAN HIEROGLYPH K007;Lo;0;L;;;;;N;;;;; 131A2;EGYPTIAN HIEROGLYPH K008;Lo;0;L;;;;;N;;;;; 131A3;EGYPTIAN HIEROGLYPH L001;Lo;0;L;;;;;N;;;;; 131A4;EGYPTIAN HIEROGLYPH L002;Lo;0;L;;;;;N;;;;; 131A5;EGYPTIAN HIEROGLYPH L002A;Lo;0;L;;;;;N;;;;; 131A6;EGYPTIAN HIEROGLYPH L003;Lo;0;L;;;;;N;;;;; 131A7;EGYPTIAN HIEROGLYPH L004;Lo;0;L;;;;;N;;;;; 131A8;EGYPTIAN HIEROGLYPH L005;Lo;0;L;;;;;N;;;;; 131A9;EGYPTIAN HIEROGLYPH L006;Lo;0;L;;;;;N;;;;; 131AA;EGYPTIAN HIEROGLYPH L006A;Lo;0;L;;;;;N;;;;; 131AB;EGYPTIAN HIEROGLYPH L007;Lo;0;L;;;;;N;;;;; 131AC;EGYPTIAN HIEROGLYPH L008;Lo;0;L;;;;;N;;;;; 131AD;EGYPTIAN HIEROGLYPH M001;Lo;0;L;;;;;N;;;;; 131AE;EGYPTIAN HIEROGLYPH M001A;Lo;0;L;;;;;N;;;;; 131AF;EGYPTIAN HIEROGLYPH M001B;Lo;0;L;;;;;N;;;;; 131B0;EGYPTIAN HIEROGLYPH M002;Lo;0;L;;;;;N;;;;; 131B1;EGYPTIAN HIEROGLYPH M003;Lo;0;L;;;;;N;;;;; 131B2;EGYPTIAN HIEROGLYPH M003A;Lo;0;L;;;;;N;;;;; 131B3;EGYPTIAN HIEROGLYPH M004;Lo;0;L;;;;;N;;;;; 131B4;EGYPTIAN HIEROGLYPH M005;Lo;0;L;;;;;N;;;;; 131B5;EGYPTIAN HIEROGLYPH M006;Lo;0;L;;;;;N;;;;; 131B6;EGYPTIAN HIEROGLYPH M007;Lo;0;L;;;;;N;;;;; 131B7;EGYPTIAN HIEROGLYPH M008;Lo;0;L;;;;;N;;;;; 131B8;EGYPTIAN HIEROGLYPH M009;Lo;0;L;;;;;N;;;;; 131B9;EGYPTIAN HIEROGLYPH M010;Lo;0;L;;;;;N;;;;; 131BA;EGYPTIAN HIEROGLYPH M010A;Lo;0;L;;;;;N;;;;; 131BB;EGYPTIAN HIEROGLYPH M011;Lo;0;L;;;;;N;;;;; 131BC;EGYPTIAN HIEROGLYPH M012;Lo;0;L;;;;;N;;;;; 131BD;EGYPTIAN HIEROGLYPH M012A;Lo;0;L;;;;;N;;;;; 131BE;EGYPTIAN HIEROGLYPH M012B;Lo;0;L;;;;;N;;;;; 131BF;EGYPTIAN HIEROGLYPH M012C;Lo;0;L;;;;;N;;;;; 131C0;EGYPTIAN HIEROGLYPH M012D;Lo;0;L;;;;;N;;;;; 131C1;EGYPTIAN HIEROGLYPH M012E;Lo;0;L;;;;;N;;;;; 131C2;EGYPTIAN HIEROGLYPH M012F;Lo;0;L;;;;;N;;;;; 131C3;EGYPTIAN HIEROGLYPH M012G;Lo;0;L;;;;;N;;;;; 131C4;EGYPTIAN HIEROGLYPH M012H;Lo;0;L;;;;;N;;;;; 131C5;EGYPTIAN HIEROGLYPH M013;Lo;0;L;;;;;N;;;;; 131C6;EGYPTIAN HIEROGLYPH M014;Lo;0;L;;;;;N;;;;; 131C7;EGYPTIAN HIEROGLYPH M015;Lo;0;L;;;;;N;;;;; 131C8;EGYPTIAN HIEROGLYPH M015A;Lo;0;L;;;;;N;;;;; 131C9;EGYPTIAN HIEROGLYPH M016;Lo;0;L;;;;;N;;;;; 131CA;EGYPTIAN HIEROGLYPH M016A;Lo;0;L;;;;;N;;;;; 131CB;EGYPTIAN HIEROGLYPH M017;Lo;0;L;;;;;N;;;;; 131CC;EGYPTIAN HIEROGLYPH M017A;Lo;0;L;;;;;N;;;;; 131CD;EGYPTIAN HIEROGLYPH M018;Lo;0;L;;;;;N;;;;; 131CE;EGYPTIAN HIEROGLYPH M019;Lo;0;L;;;;;N;;;;; 131CF;EGYPTIAN HIEROGLYPH M020;Lo;0;L;;;;;N;;;;; 131D0;EGYPTIAN HIEROGLYPH M021;Lo;0;L;;;;;N;;;;; 131D1;EGYPTIAN HIEROGLYPH M022;Lo;0;L;;;;;N;;;;; 131D2;EGYPTIAN HIEROGLYPH M022A;Lo;0;L;;;;;N;;;;; 131D3;EGYPTIAN HIEROGLYPH M023;Lo;0;L;;;;;N;;;;; 131D4;EGYPTIAN HIEROGLYPH M024;Lo;0;L;;;;;N;;;;; 131D5;EGYPTIAN HIEROGLYPH M024A;Lo;0;L;;;;;N;;;;; 131D6;EGYPTIAN HIEROGLYPH M025;Lo;0;L;;;;;N;;;;; 131D7;EGYPTIAN HIEROGLYPH M026;Lo;0;L;;;;;N;;;;; 131D8;EGYPTIAN HIEROGLYPH M027;Lo;0;L;;;;;N;;;;; 131D9;EGYPTIAN HIEROGLYPH M028;Lo;0;L;;;;;N;;;;; 131DA;EGYPTIAN HIEROGLYPH M028A;Lo;0;L;;;;;N;;;;; 131DB;EGYPTIAN HIEROGLYPH M029;Lo;0;L;;;;;N;;;;; 131DC;EGYPTIAN HIEROGLYPH M030;Lo;0;L;;;;;N;;;;; 131DD;EGYPTIAN HIEROGLYPH M031;Lo;0;L;;;;;N;;;;; 131DE;EGYPTIAN HIEROGLYPH M031A;Lo;0;L;;;;;N;;;;; 131DF;EGYPTIAN HIEROGLYPH M032;Lo;0;L;;;;;N;;;;; 131E0;EGYPTIAN HIEROGLYPH M033;Lo;0;L;;;;;N;;;;; 131E1;EGYPTIAN HIEROGLYPH M033A;Lo;0;L;;;;;N;;;;; 131E2;EGYPTIAN HIEROGLYPH M033B;Lo;0;L;;;;;N;;;;; 131E3;EGYPTIAN HIEROGLYPH M034;Lo;0;L;;;;;N;;;;; 131E4;EGYPTIAN HIEROGLYPH M035;Lo;0;L;;;;;N;;;;; 131E5;EGYPTIAN HIEROGLYPH M036;Lo;0;L;;;;;N;;;;; 131E6;EGYPTIAN HIEROGLYPH M037;Lo;0;L;;;;;N;;;;; 131E7;EGYPTIAN HIEROGLYPH M038;Lo;0;L;;;;;N;;;;; 131E8;EGYPTIAN HIEROGLYPH M039;Lo;0;L;;;;;N;;;;; 131E9;EGYPTIAN HIEROGLYPH M040;Lo;0;L;;;;;N;;;;; 131EA;EGYPTIAN HIEROGLYPH M040A;Lo;0;L;;;;;N;;;;; 131EB;EGYPTIAN HIEROGLYPH M041;Lo;0;L;;;;;N;;;;; 131EC;EGYPTIAN HIEROGLYPH M042;Lo;0;L;;;;;N;;;;; 131ED;EGYPTIAN HIEROGLYPH M043;Lo;0;L;;;;;N;;;;; 131EE;EGYPTIAN HIEROGLYPH M044;Lo;0;L;;;;;N;;;;; 131EF;EGYPTIAN HIEROGLYPH N001;Lo;0;L;;;;;N;;;;; 131F0;EGYPTIAN HIEROGLYPH N002;Lo;0;L;;;;;N;;;;; 131F1;EGYPTIAN HIEROGLYPH N003;Lo;0;L;;;;;N;;;;; 131F2;EGYPTIAN HIEROGLYPH N004;Lo;0;L;;;;;N;;;;; 131F3;EGYPTIAN HIEROGLYPH N005;Lo;0;L;;;;;N;;;;; 131F4;EGYPTIAN HIEROGLYPH N006;Lo;0;L;;;;;N;;;;; 131F5;EGYPTIAN HIEROGLYPH N007;Lo;0;L;;;;;N;;;;; 131F6;EGYPTIAN HIEROGLYPH N008;Lo;0;L;;;;;N;;;;; 131F7;EGYPTIAN HIEROGLYPH N009;Lo;0;L;;;;;N;;;;; 131F8;EGYPTIAN HIEROGLYPH N010;Lo;0;L;;;;;N;;;;; 131F9;EGYPTIAN HIEROGLYPH N011;Lo;0;L;;;;;N;;;;; 131FA;EGYPTIAN HIEROGLYPH N012;Lo;0;L;;;;;N;;;;; 131FB;EGYPTIAN HIEROGLYPH N013;Lo;0;L;;;;;N;;;;; 131FC;EGYPTIAN HIEROGLYPH N014;Lo;0;L;;;;;N;;;;; 131FD;EGYPTIAN HIEROGLYPH N015;Lo;0;L;;;;;N;;;;; 131FE;EGYPTIAN HIEROGLYPH N016;Lo;0;L;;;;;N;;;;; 131FF;EGYPTIAN HIEROGLYPH N017;Lo;0;L;;;;;N;;;;; 13200;EGYPTIAN HIEROGLYPH N018;Lo;0;L;;;;;N;;;;; 13201;EGYPTIAN HIEROGLYPH N018A;Lo;0;L;;;;;N;;;;; 13202;EGYPTIAN HIEROGLYPH N018B;Lo;0;L;;;;;N;;;;; 13203;EGYPTIAN HIEROGLYPH N019;Lo;0;L;;;;;N;;;;; 13204;EGYPTIAN HIEROGLYPH N020;Lo;0;L;;;;;N;;;;; 13205;EGYPTIAN HIEROGLYPH N021;Lo;0;L;;;;;N;;;;; 13206;EGYPTIAN HIEROGLYPH N022;Lo;0;L;;;;;N;;;;; 13207;EGYPTIAN HIEROGLYPH N023;Lo;0;L;;;;;N;;;;; 13208;EGYPTIAN HIEROGLYPH N024;Lo;0;L;;;;;N;;;;; 13209;EGYPTIAN HIEROGLYPH N025;Lo;0;L;;;;;N;;;;; 1320A;EGYPTIAN HIEROGLYPH N025A;Lo;0;L;;;;;N;;;;; 1320B;EGYPTIAN HIEROGLYPH N026;Lo;0;L;;;;;N;;;;; 1320C;EGYPTIAN HIEROGLYPH N027;Lo;0;L;;;;;N;;;;; 1320D;EGYPTIAN HIEROGLYPH N028;Lo;0;L;;;;;N;;;;; 1320E;EGYPTIAN HIEROGLYPH N029;Lo;0;L;;;;;N;;;;; 1320F;EGYPTIAN HIEROGLYPH N030;Lo;0;L;;;;;N;;;;; 13210;EGYPTIAN HIEROGLYPH N031;Lo;0;L;;;;;N;;;;; 13211;EGYPTIAN HIEROGLYPH N032;Lo;0;L;;;;;N;;;;; 13212;EGYPTIAN HIEROGLYPH N033;Lo;0;L;;;;;N;;;;; 13213;EGYPTIAN HIEROGLYPH N033A;Lo;0;L;;;;;N;;;;; 13214;EGYPTIAN HIEROGLYPH N034;Lo;0;L;;;;;N;;;;; 13215;EGYPTIAN HIEROGLYPH N034A;Lo;0;L;;;;;N;;;;; 13216;EGYPTIAN HIEROGLYPH N035;Lo;0;L;;;;;N;;;;; 13217;EGYPTIAN HIEROGLYPH N035A;Lo;0;L;;;;;N;;;;; 13218;EGYPTIAN HIEROGLYPH N036;Lo;0;L;;;;;N;;;;; 13219;EGYPTIAN HIEROGLYPH N037;Lo;0;L;;;;;N;;;;; 1321A;EGYPTIAN HIEROGLYPH N037A;Lo;0;L;;;;;N;;;;; 1321B;EGYPTIAN HIEROGLYPH N038;Lo;0;L;;;;;N;;;;; 1321C;EGYPTIAN HIEROGLYPH N039;Lo;0;L;;;;;N;;;;; 1321D;EGYPTIAN HIEROGLYPH N040;Lo;0;L;;;;;N;;;;; 1321E;EGYPTIAN HIEROGLYPH N041;Lo;0;L;;;;;N;;;;; 1321F;EGYPTIAN HIEROGLYPH N042;Lo;0;L;;;;;N;;;;; 13220;EGYPTIAN HIEROGLYPH NL001;Lo;0;L;;;;;N;;;;; 13221;EGYPTIAN HIEROGLYPH NL002;Lo;0;L;;;;;N;;;;; 13222;EGYPTIAN HIEROGLYPH NL003;Lo;0;L;;;;;N;;;;; 13223;EGYPTIAN HIEROGLYPH NL004;Lo;0;L;;;;;N;;;;; 13224;EGYPTIAN HIEROGLYPH NL005;Lo;0;L;;;;;N;;;;; 13225;EGYPTIAN HIEROGLYPH NL005A;Lo;0;L;;;;;N;;;;; 13226;EGYPTIAN HIEROGLYPH NL006;Lo;0;L;;;;;N;;;;; 13227;EGYPTIAN HIEROGLYPH NL007;Lo;0;L;;;;;N;;;;; 13228;EGYPTIAN HIEROGLYPH NL008;Lo;0;L;;;;;N;;;;; 13229;EGYPTIAN HIEROGLYPH NL009;Lo;0;L;;;;;N;;;;; 1322A;EGYPTIAN HIEROGLYPH NL010;Lo;0;L;;;;;N;;;;; 1322B;EGYPTIAN HIEROGLYPH NL011;Lo;0;L;;;;;N;;;;; 1322C;EGYPTIAN HIEROGLYPH NL012;Lo;0;L;;;;;N;;;;; 1322D;EGYPTIAN HIEROGLYPH NL013;Lo;0;L;;;;;N;;;;; 1322E;EGYPTIAN HIEROGLYPH NL014;Lo;0;L;;;;;N;;;;; 1322F;EGYPTIAN HIEROGLYPH NL015;Lo;0;L;;;;;N;;;;; 13230;EGYPTIAN HIEROGLYPH NL016;Lo;0;L;;;;;N;;;;; 13231;EGYPTIAN HIEROGLYPH NL017;Lo;0;L;;;;;N;;;;; 13232;EGYPTIAN HIEROGLYPH NL017A;Lo;0;L;;;;;N;;;;; 13233;EGYPTIAN HIEROGLYPH NL018;Lo;0;L;;;;;N;;;;; 13234;EGYPTIAN HIEROGLYPH NL019;Lo;0;L;;;;;N;;;;; 13235;EGYPTIAN HIEROGLYPH NL020;Lo;0;L;;;;;N;;;;; 13236;EGYPTIAN HIEROGLYPH NU001;Lo;0;L;;;;;N;;;;; 13237;EGYPTIAN HIEROGLYPH NU002;Lo;0;L;;;;;N;;;;; 13238;EGYPTIAN HIEROGLYPH NU003;Lo;0;L;;;;;N;;;;; 13239;EGYPTIAN HIEROGLYPH NU004;Lo;0;L;;;;;N;;;;; 1323A;EGYPTIAN HIEROGLYPH NU005;Lo;0;L;;;;;N;;;;; 1323B;EGYPTIAN HIEROGLYPH NU006;Lo;0;L;;;;;N;;;;; 1323C;EGYPTIAN HIEROGLYPH NU007;Lo;0;L;;;;;N;;;;; 1323D;EGYPTIAN HIEROGLYPH NU008;Lo;0;L;;;;;N;;;;; 1323E;EGYPTIAN HIEROGLYPH NU009;Lo;0;L;;;;;N;;;;; 1323F;EGYPTIAN HIEROGLYPH NU010;Lo;0;L;;;;;N;;;;; 13240;EGYPTIAN HIEROGLYPH NU010A;Lo;0;L;;;;;N;;;;; 13241;EGYPTIAN HIEROGLYPH NU011;Lo;0;L;;;;;N;;;;; 13242;EGYPTIAN HIEROGLYPH NU011A;Lo;0;L;;;;;N;;;;; 13243;EGYPTIAN HIEROGLYPH NU012;Lo;0;L;;;;;N;;;;; 13244;EGYPTIAN HIEROGLYPH NU013;Lo;0;L;;;;;N;;;;; 13245;EGYPTIAN HIEROGLYPH NU014;Lo;0;L;;;;;N;;;;; 13246;EGYPTIAN HIEROGLYPH NU015;Lo;0;L;;;;;N;;;;; 13247;EGYPTIAN HIEROGLYPH NU016;Lo;0;L;;;;;N;;;;; 13248;EGYPTIAN HIEROGLYPH NU017;Lo;0;L;;;;;N;;;;; 13249;EGYPTIAN HIEROGLYPH NU018;Lo;0;L;;;;;N;;;;; 1324A;EGYPTIAN HIEROGLYPH NU018A;Lo;0;L;;;;;N;;;;; 1324B;EGYPTIAN HIEROGLYPH NU019;Lo;0;L;;;;;N;;;;; 1324C;EGYPTIAN HIEROGLYPH NU020;Lo;0;L;;;;;N;;;;; 1324D;EGYPTIAN HIEROGLYPH NU021;Lo;0;L;;;;;N;;;;; 1324E;EGYPTIAN HIEROGLYPH NU022;Lo;0;L;;;;;N;;;;; 1324F;EGYPTIAN HIEROGLYPH NU022A;Lo;0;L;;;;;N;;;;; 13250;EGYPTIAN HIEROGLYPH O001;Lo;0;L;;;;;N;;;;; 13251;EGYPTIAN HIEROGLYPH O001A;Lo;0;L;;;;;N;;;;; 13252;EGYPTIAN HIEROGLYPH O002;Lo;0;L;;;;;N;;;;; 13253;EGYPTIAN HIEROGLYPH O003;Lo;0;L;;;;;N;;;;; 13254;EGYPTIAN HIEROGLYPH O004;Lo;0;L;;;;;N;;;;; 13255;EGYPTIAN HIEROGLYPH O005;Lo;0;L;;;;;N;;;;; 13256;EGYPTIAN HIEROGLYPH O005A;Lo;0;L;;;;;N;;;;; 13257;EGYPTIAN HIEROGLYPH O006;Lo;0;L;;;;;N;;;;; 13258;EGYPTIAN HIEROGLYPH O006A;Lo;0;L;;;;;N;;;;; 13259;EGYPTIAN HIEROGLYPH O006B;Lo;0;L;;;;;N;;;;; 1325A;EGYPTIAN HIEROGLYPH O006C;Lo;0;L;;;;;N;;;;; 1325B;EGYPTIAN HIEROGLYPH O006D;Lo;0;L;;;;;N;;;;; 1325C;EGYPTIAN HIEROGLYPH O006E;Lo;0;L;;;;;N;;;;; 1325D;EGYPTIAN HIEROGLYPH O006F;Lo;0;L;;;;;N;;;;; 1325E;EGYPTIAN HIEROGLYPH O007;Lo;0;L;;;;;N;;;;; 1325F;EGYPTIAN HIEROGLYPH O008;Lo;0;L;;;;;N;;;;; 13260;EGYPTIAN HIEROGLYPH O009;Lo;0;L;;;;;N;;;;; 13261;EGYPTIAN HIEROGLYPH O010;Lo;0;L;;;;;N;;;;; 13262;EGYPTIAN HIEROGLYPH O010A;Lo;0;L;;;;;N;;;;; 13263;EGYPTIAN HIEROGLYPH O010B;Lo;0;L;;;;;N;;;;; 13264;EGYPTIAN HIEROGLYPH O010C;Lo;0;L;;;;;N;;;;; 13265;EGYPTIAN HIEROGLYPH O011;Lo;0;L;;;;;N;;;;; 13266;EGYPTIAN HIEROGLYPH O012;Lo;0;L;;;;;N;;;;; 13267;EGYPTIAN HIEROGLYPH O013;Lo;0;L;;;;;N;;;;; 13268;EGYPTIAN HIEROGLYPH O014;Lo;0;L;;;;;N;;;;; 13269;EGYPTIAN HIEROGLYPH O015;Lo;0;L;;;;;N;;;;; 1326A;EGYPTIAN HIEROGLYPH O016;Lo;0;L;;;;;N;;;;; 1326B;EGYPTIAN HIEROGLYPH O017;Lo;0;L;;;;;N;;;;; 1326C;EGYPTIAN HIEROGLYPH O018;Lo;0;L;;;;;N;;;;; 1326D;EGYPTIAN HIEROGLYPH O019;Lo;0;L;;;;;N;;;;; 1326E;EGYPTIAN HIEROGLYPH O019A;Lo;0;L;;;;;N;;;;; 1326F;EGYPTIAN HIEROGLYPH O020;Lo;0;L;;;;;N;;;;; 13270;EGYPTIAN HIEROGLYPH O020A;Lo;0;L;;;;;N;;;;; 13271;EGYPTIAN HIEROGLYPH O021;Lo;0;L;;;;;N;;;;; 13272;EGYPTIAN HIEROGLYPH O022;Lo;0;L;;;;;N;;;;; 13273;EGYPTIAN HIEROGLYPH O023;Lo;0;L;;;;;N;;;;; 13274;EGYPTIAN HIEROGLYPH O024;Lo;0;L;;;;;N;;;;; 13275;EGYPTIAN HIEROGLYPH O024A;Lo;0;L;;;;;N;;;;; 13276;EGYPTIAN HIEROGLYPH O025;Lo;0;L;;;;;N;;;;; 13277;EGYPTIAN HIEROGLYPH O025A;Lo;0;L;;;;;N;;;;; 13278;EGYPTIAN HIEROGLYPH O026;Lo;0;L;;;;;N;;;;; 13279;EGYPTIAN HIEROGLYPH O027;Lo;0;L;;;;;N;;;;; 1327A;EGYPTIAN HIEROGLYPH O028;Lo;0;L;;;;;N;;;;; 1327B;EGYPTIAN HIEROGLYPH O029;Lo;0;L;;;;;N;;;;; 1327C;EGYPTIAN HIEROGLYPH O029A;Lo;0;L;;;;;N;;;;; 1327D;EGYPTIAN HIEROGLYPH O030;Lo;0;L;;;;;N;;;;; 1327E;EGYPTIAN HIEROGLYPH O030A;Lo;0;L;;;;;N;;;;; 1327F;EGYPTIAN HIEROGLYPH O031;Lo;0;L;;;;;N;;;;; 13280;EGYPTIAN HIEROGLYPH O032;Lo;0;L;;;;;N;;;;; 13281;EGYPTIAN HIEROGLYPH O033;Lo;0;L;;;;;N;;;;; 13282;EGYPTIAN HIEROGLYPH O033A;Lo;0;L;;;;;N;;;;; 13283;EGYPTIAN HIEROGLYPH O034;Lo;0;L;;;;;N;;;;; 13284;EGYPTIAN HIEROGLYPH O035;Lo;0;L;;;;;N;;;;; 13285;EGYPTIAN HIEROGLYPH O036;Lo;0;L;;;;;N;;;;; 13286;EGYPTIAN HIEROGLYPH O036A;Lo;0;L;;;;;N;;;;; 13287;EGYPTIAN HIEROGLYPH O036B;Lo;0;L;;;;;N;;;;; 13288;EGYPTIAN HIEROGLYPH O036C;Lo;0;L;;;;;N;;;;; 13289;EGYPTIAN HIEROGLYPH O036D;Lo;0;L;;;;;N;;;;; 1328A;EGYPTIAN HIEROGLYPH O037;Lo;0;L;;;;;N;;;;; 1328B;EGYPTIAN HIEROGLYPH O038;Lo;0;L;;;;;N;;;;; 1328C;EGYPTIAN HIEROGLYPH O039;Lo;0;L;;;;;N;;;;; 1328D;EGYPTIAN HIEROGLYPH O040;Lo;0;L;;;;;N;;;;; 1328E;EGYPTIAN HIEROGLYPH O041;Lo;0;L;;;;;N;;;;; 1328F;EGYPTIAN HIEROGLYPH O042;Lo;0;L;;;;;N;;;;; 13290;EGYPTIAN HIEROGLYPH O043;Lo;0;L;;;;;N;;;;; 13291;EGYPTIAN HIEROGLYPH O044;Lo;0;L;;;;;N;;;;; 13292;EGYPTIAN HIEROGLYPH O045;Lo;0;L;;;;;N;;;;; 13293;EGYPTIAN HIEROGLYPH O046;Lo;0;L;;;;;N;;;;; 13294;EGYPTIAN HIEROGLYPH O047;Lo;0;L;;;;;N;;;;; 13295;EGYPTIAN HIEROGLYPH O048;Lo;0;L;;;;;N;;;;; 13296;EGYPTIAN HIEROGLYPH O049;Lo;0;L;;;;;N;;;;; 13297;EGYPTIAN HIEROGLYPH O050;Lo;0;L;;;;;N;;;;; 13298;EGYPTIAN HIEROGLYPH O050A;Lo;0;L;;;;;N;;;;; 13299;EGYPTIAN HIEROGLYPH O050B;Lo;0;L;;;;;N;;;;; 1329A;EGYPTIAN HIEROGLYPH O051;Lo;0;L;;;;;N;;;;; 1329B;EGYPTIAN HIEROGLYPH P001;Lo;0;L;;;;;N;;;;; 1329C;EGYPTIAN HIEROGLYPH P001A;Lo;0;L;;;;;N;;;;; 1329D;EGYPTIAN HIEROGLYPH P002;Lo;0;L;;;;;N;;;;; 1329E;EGYPTIAN HIEROGLYPH P003;Lo;0;L;;;;;N;;;;; 1329F;EGYPTIAN HIEROGLYPH P003A;Lo;0;L;;;;;N;;;;; 132A0;EGYPTIAN HIEROGLYPH P004;Lo;0;L;;;;;N;;;;; 132A1;EGYPTIAN HIEROGLYPH P005;Lo;0;L;;;;;N;;;;; 132A2;EGYPTIAN HIEROGLYPH P006;Lo;0;L;;;;;N;;;;; 132A3;EGYPTIAN HIEROGLYPH P007;Lo;0;L;;;;;N;;;;; 132A4;EGYPTIAN HIEROGLYPH P008;Lo;0;L;;;;;N;;;;; 132A5;EGYPTIAN HIEROGLYPH P009;Lo;0;L;;;;;N;;;;; 132A6;EGYPTIAN HIEROGLYPH P010;Lo;0;L;;;;;N;;;;; 132A7;EGYPTIAN HIEROGLYPH P011;Lo;0;L;;;;;N;;;;; 132A8;EGYPTIAN HIEROGLYPH Q001;Lo;0;L;;;;;N;;;;; 132A9;EGYPTIAN HIEROGLYPH Q002;Lo;0;L;;;;;N;;;;; 132AA;EGYPTIAN HIEROGLYPH Q003;Lo;0;L;;;;;N;;;;; 132AB;EGYPTIAN HIEROGLYPH Q004;Lo;0;L;;;;;N;;;;; 132AC;EGYPTIAN HIEROGLYPH Q005;Lo;0;L;;;;;N;;;;; 132AD;EGYPTIAN HIEROGLYPH Q006;Lo;0;L;;;;;N;;;;; 132AE;EGYPTIAN HIEROGLYPH Q007;Lo;0;L;;;;;N;;;;; 132AF;EGYPTIAN HIEROGLYPH R001;Lo;0;L;;;;;N;;;;; 132B0;EGYPTIAN HIEROGLYPH R002;Lo;0;L;;;;;N;;;;; 132B1;EGYPTIAN HIEROGLYPH R002A;Lo;0;L;;;;;N;;;;; 132B2;EGYPTIAN HIEROGLYPH R003;Lo;0;L;;;;;N;;;;; 132B3;EGYPTIAN HIEROGLYPH R003A;Lo;0;L;;;;;N;;;;; 132B4;EGYPTIAN HIEROGLYPH R003B;Lo;0;L;;;;;N;;;;; 132B5;EGYPTIAN HIEROGLYPH R004;Lo;0;L;;;;;N;;;;; 132B6;EGYPTIAN HIEROGLYPH R005;Lo;0;L;;;;;N;;;;; 132B7;EGYPTIAN HIEROGLYPH R006;Lo;0;L;;;;;N;;;;; 132B8;EGYPTIAN HIEROGLYPH R007;Lo;0;L;;;;;N;;;;; 132B9;EGYPTIAN HIEROGLYPH R008;Lo;0;L;;;;;N;;;;; 132BA;EGYPTIAN HIEROGLYPH R009;Lo;0;L;;;;;N;;;;; 132BB;EGYPTIAN HIEROGLYPH R010;Lo;0;L;;;;;N;;;;; 132BC;EGYPTIAN HIEROGLYPH R010A;Lo;0;L;;;;;N;;;;; 132BD;EGYPTIAN HIEROGLYPH R011;Lo;0;L;;;;;N;;;;; 132BE;EGYPTIAN HIEROGLYPH R012;Lo;0;L;;;;;N;;;;; 132BF;EGYPTIAN HIEROGLYPH R013;Lo;0;L;;;;;N;;;;; 132C0;EGYPTIAN HIEROGLYPH R014;Lo;0;L;;;;;N;;;;; 132C1;EGYPTIAN HIEROGLYPH R015;Lo;0;L;;;;;N;;;;; 132C2;EGYPTIAN HIEROGLYPH R016;Lo;0;L;;;;;N;;;;; 132C3;EGYPTIAN HIEROGLYPH R016A;Lo;0;L;;;;;N;;;;; 132C4;EGYPTIAN HIEROGLYPH R017;Lo;0;L;;;;;N;;;;; 132C5;EGYPTIAN HIEROGLYPH R018;Lo;0;L;;;;;N;;;;; 132C6;EGYPTIAN HIEROGLYPH R019;Lo;0;L;;;;;N;;;;; 132C7;EGYPTIAN HIEROGLYPH R020;Lo;0;L;;;;;N;;;;; 132C8;EGYPTIAN HIEROGLYPH R021;Lo;0;L;;;;;N;;;;; 132C9;EGYPTIAN HIEROGLYPH R022;Lo;0;L;;;;;N;;;;; 132CA;EGYPTIAN HIEROGLYPH R023;Lo;0;L;;;;;N;;;;; 132CB;EGYPTIAN HIEROGLYPH R024;Lo;0;L;;;;;N;;;;; 132CC;EGYPTIAN HIEROGLYPH R025;Lo;0;L;;;;;N;;;;; 132CD;EGYPTIAN HIEROGLYPH R026;Lo;0;L;;;;;N;;;;; 132CE;EGYPTIAN HIEROGLYPH R027;Lo;0;L;;;;;N;;;;; 132CF;EGYPTIAN HIEROGLYPH R028;Lo;0;L;;;;;N;;;;; 132D0;EGYPTIAN HIEROGLYPH R029;Lo;0;L;;;;;N;;;;; 132D1;EGYPTIAN HIEROGLYPH S001;Lo;0;L;;;;;N;;;;; 132D2;EGYPTIAN HIEROGLYPH S002;Lo;0;L;;;;;N;;;;; 132D3;EGYPTIAN HIEROGLYPH S002A;Lo;0;L;;;;;N;;;;; 132D4;EGYPTIAN HIEROGLYPH S003;Lo;0;L;;;;;N;;;;; 132D5;EGYPTIAN HIEROGLYPH S004;Lo;0;L;;;;;N;;;;; 132D6;EGYPTIAN HIEROGLYPH S005;Lo;0;L;;;;;N;;;;; 132D7;EGYPTIAN HIEROGLYPH S006;Lo;0;L;;;;;N;;;;; 132D8;EGYPTIAN HIEROGLYPH S006A;Lo;0;L;;;;;N;;;;; 132D9;EGYPTIAN HIEROGLYPH S007;Lo;0;L;;;;;N;;;;; 132DA;EGYPTIAN HIEROGLYPH S008;Lo;0;L;;;;;N;;;;; 132DB;EGYPTIAN HIEROGLYPH S009;Lo;0;L;;;;;N;;;;; 132DC;EGYPTIAN HIEROGLYPH S010;Lo;0;L;;;;;N;;;;; 132DD;EGYPTIAN HIEROGLYPH S011;Lo;0;L;;;;;N;;;;; 132DE;EGYPTIAN HIEROGLYPH S012;Lo;0;L;;;;;N;;;;; 132DF;EGYPTIAN HIEROGLYPH S013;Lo;0;L;;;;;N;;;;; 132E0;EGYPTIAN HIEROGLYPH S014;Lo;0;L;;;;;N;;;;; 132E1;EGYPTIAN HIEROGLYPH S014A;Lo;0;L;;;;;N;;;;; 132E2;EGYPTIAN HIEROGLYPH S014B;Lo;0;L;;;;;N;;;;; 132E3;EGYPTIAN HIEROGLYPH S015;Lo;0;L;;;;;N;;;;; 132E4;EGYPTIAN HIEROGLYPH S016;Lo;0;L;;;;;N;;;;; 132E5;EGYPTIAN HIEROGLYPH S017;Lo;0;L;;;;;N;;;;; 132E6;EGYPTIAN HIEROGLYPH S017A;Lo;0;L;;;;;N;;;;; 132E7;EGYPTIAN HIEROGLYPH S018;Lo;0;L;;;;;N;;;;; 132E8;EGYPTIAN HIEROGLYPH S019;Lo;0;L;;;;;N;;;;; 132E9;EGYPTIAN HIEROGLYPH S020;Lo;0;L;;;;;N;;;;; 132EA;EGYPTIAN HIEROGLYPH S021;Lo;0;L;;;;;N;;;;; 132EB;EGYPTIAN HIEROGLYPH S022;Lo;0;L;;;;;N;;;;; 132EC;EGYPTIAN HIEROGLYPH S023;Lo;0;L;;;;;N;;;;; 132ED;EGYPTIAN HIEROGLYPH S024;Lo;0;L;;;;;N;;;;; 132EE;EGYPTIAN HIEROGLYPH S025;Lo;0;L;;;;;N;;;;; 132EF;EGYPTIAN HIEROGLYPH S026;Lo;0;L;;;;;N;;;;; 132F0;EGYPTIAN HIEROGLYPH S026A;Lo;0;L;;;;;N;;;;; 132F1;EGYPTIAN HIEROGLYPH S026B;Lo;0;L;;;;;N;;;;; 132F2;EGYPTIAN HIEROGLYPH S027;Lo;0;L;;;;;N;;;;; 132F3;EGYPTIAN HIEROGLYPH S028;Lo;0;L;;;;;N;;;;; 132F4;EGYPTIAN HIEROGLYPH S029;Lo;0;L;;;;;N;;;;; 132F5;EGYPTIAN HIEROGLYPH S030;Lo;0;L;;;;;N;;;;; 132F6;EGYPTIAN HIEROGLYPH S031;Lo;0;L;;;;;N;;;;; 132F7;EGYPTIAN HIEROGLYPH S032;Lo;0;L;;;;;N;;;;; 132F8;EGYPTIAN HIEROGLYPH S033;Lo;0;L;;;;;N;;;;; 132F9;EGYPTIAN HIEROGLYPH S034;Lo;0;L;;;;;N;;;;; 132FA;EGYPTIAN HIEROGLYPH S035;Lo;0;L;;;;;N;;;;; 132FB;EGYPTIAN HIEROGLYPH S035A;Lo;0;L;;;;;N;;;;; 132FC;EGYPTIAN HIEROGLYPH S036;Lo;0;L;;;;;N;;;;; 132FD;EGYPTIAN HIEROGLYPH S037;Lo;0;L;;;;;N;;;;; 132FE;EGYPTIAN HIEROGLYPH S038;Lo;0;L;;;;;N;;;;; 132FF;EGYPTIAN HIEROGLYPH S039;Lo;0;L;;;;;N;;;;; 13300;EGYPTIAN HIEROGLYPH S040;Lo;0;L;;;;;N;;;;; 13301;EGYPTIAN HIEROGLYPH S041;Lo;0;L;;;;;N;;;;; 13302;EGYPTIAN HIEROGLYPH S042;Lo;0;L;;;;;N;;;;; 13303;EGYPTIAN HIEROGLYPH S043;Lo;0;L;;;;;N;;;;; 13304;EGYPTIAN HIEROGLYPH S044;Lo;0;L;;;;;N;;;;; 13305;EGYPTIAN HIEROGLYPH S045;Lo;0;L;;;;;N;;;;; 13306;EGYPTIAN HIEROGLYPH S046;Lo;0;L;;;;;N;;;;; 13307;EGYPTIAN HIEROGLYPH T001;Lo;0;L;;;;;N;;;;; 13308;EGYPTIAN HIEROGLYPH T002;Lo;0;L;;;;;N;;;;; 13309;EGYPTIAN HIEROGLYPH T003;Lo;0;L;;;;;N;;;;; 1330A;EGYPTIAN HIEROGLYPH T003A;Lo;0;L;;;;;N;;;;; 1330B;EGYPTIAN HIEROGLYPH T004;Lo;0;L;;;;;N;;;;; 1330C;EGYPTIAN HIEROGLYPH T005;Lo;0;L;;;;;N;;;;; 1330D;EGYPTIAN HIEROGLYPH T006;Lo;0;L;;;;;N;;;;; 1330E;EGYPTIAN HIEROGLYPH T007;Lo;0;L;;;;;N;;;;; 1330F;EGYPTIAN HIEROGLYPH T007A;Lo;0;L;;;;;N;;;;; 13310;EGYPTIAN HIEROGLYPH T008;Lo;0;L;;;;;N;;;;; 13311;EGYPTIAN HIEROGLYPH T008A;Lo;0;L;;;;;N;;;;; 13312;EGYPTIAN HIEROGLYPH T009;Lo;0;L;;;;;N;;;;; 13313;EGYPTIAN HIEROGLYPH T009A;Lo;0;L;;;;;N;;;;; 13314;EGYPTIAN HIEROGLYPH T010;Lo;0;L;;;;;N;;;;; 13315;EGYPTIAN HIEROGLYPH T011;Lo;0;L;;;;;N;;;;; 13316;EGYPTIAN HIEROGLYPH T011A;Lo;0;L;;;;;N;;;;; 13317;EGYPTIAN HIEROGLYPH T012;Lo;0;L;;;;;N;;;;; 13318;EGYPTIAN HIEROGLYPH T013;Lo;0;L;;;;;N;;;;; 13319;EGYPTIAN HIEROGLYPH T014;Lo;0;L;;;;;N;;;;; 1331A;EGYPTIAN HIEROGLYPH T015;Lo;0;L;;;;;N;;;;; 1331B;EGYPTIAN HIEROGLYPH T016;Lo;0;L;;;;;N;;;;; 1331C;EGYPTIAN HIEROGLYPH T016A;Lo;0;L;;;;;N;;;;; 1331D;EGYPTIAN HIEROGLYPH T017;Lo;0;L;;;;;N;;;;; 1331E;EGYPTIAN HIEROGLYPH T018;Lo;0;L;;;;;N;;;;; 1331F;EGYPTIAN HIEROGLYPH T019;Lo;0;L;;;;;N;;;;; 13320;EGYPTIAN HIEROGLYPH T020;Lo;0;L;;;;;N;;;;; 13321;EGYPTIAN HIEROGLYPH T021;Lo;0;L;;;;;N;;;;; 13322;EGYPTIAN HIEROGLYPH T022;Lo;0;L;;;;;N;;;;; 13323;EGYPTIAN HIEROGLYPH T023;Lo;0;L;;;;;N;;;;; 13324;EGYPTIAN HIEROGLYPH T024;Lo;0;L;;;;;N;;;;; 13325;EGYPTIAN HIEROGLYPH T025;Lo;0;L;;;;;N;;;;; 13326;EGYPTIAN HIEROGLYPH T026;Lo;0;L;;;;;N;;;;; 13327;EGYPTIAN HIEROGLYPH T027;Lo;0;L;;;;;N;;;;; 13328;EGYPTIAN HIEROGLYPH T028;Lo;0;L;;;;;N;;;;; 13329;EGYPTIAN HIEROGLYPH T029;Lo;0;L;;;;;N;;;;; 1332A;EGYPTIAN HIEROGLYPH T030;Lo;0;L;;;;;N;;;;; 1332B;EGYPTIAN HIEROGLYPH T031;Lo;0;L;;;;;N;;;;; 1332C;EGYPTIAN HIEROGLYPH T032;Lo;0;L;;;;;N;;;;; 1332D;EGYPTIAN HIEROGLYPH T032A;Lo;0;L;;;;;N;;;;; 1332E;EGYPTIAN HIEROGLYPH T033;Lo;0;L;;;;;N;;;;; 1332F;EGYPTIAN HIEROGLYPH T033A;Lo;0;L;;;;;N;;;;; 13330;EGYPTIAN HIEROGLYPH T034;Lo;0;L;;;;;N;;;;; 13331;EGYPTIAN HIEROGLYPH T035;Lo;0;L;;;;;N;;;;; 13332;EGYPTIAN HIEROGLYPH T036;Lo;0;L;;;;;N;;;;; 13333;EGYPTIAN HIEROGLYPH U001;Lo;0;L;;;;;N;;;;; 13334;EGYPTIAN HIEROGLYPH U002;Lo;0;L;;;;;N;;;;; 13335;EGYPTIAN HIEROGLYPH U003;Lo;0;L;;;;;N;;;;; 13336;EGYPTIAN HIEROGLYPH U004;Lo;0;L;;;;;N;;;;; 13337;EGYPTIAN HIEROGLYPH U005;Lo;0;L;;;;;N;;;;; 13338;EGYPTIAN HIEROGLYPH U006;Lo;0;L;;;;;N;;;;; 13339;EGYPTIAN HIEROGLYPH U006A;Lo;0;L;;;;;N;;;;; 1333A;EGYPTIAN HIEROGLYPH U006B;Lo;0;L;;;;;N;;;;; 1333B;EGYPTIAN HIEROGLYPH U007;Lo;0;L;;;;;N;;;;; 1333C;EGYPTIAN HIEROGLYPH U008;Lo;0;L;;;;;N;;;;; 1333D;EGYPTIAN HIEROGLYPH U009;Lo;0;L;;;;;N;;;;; 1333E;EGYPTIAN HIEROGLYPH U010;Lo;0;L;;;;;N;;;;; 1333F;EGYPTIAN HIEROGLYPH U011;Lo;0;L;;;;;N;;;;; 13340;EGYPTIAN HIEROGLYPH U012;Lo;0;L;;;;;N;;;;; 13341;EGYPTIAN HIEROGLYPH U013;Lo;0;L;;;;;N;;;;; 13342;EGYPTIAN HIEROGLYPH U014;Lo;0;L;;;;;N;;;;; 13343;EGYPTIAN HIEROGLYPH U015;Lo;0;L;;;;;N;;;;; 13344;EGYPTIAN HIEROGLYPH U016;Lo;0;L;;;;;N;;;;; 13345;EGYPTIAN HIEROGLYPH U017;Lo;0;L;;;;;N;;;;; 13346;EGYPTIAN HIEROGLYPH U018;Lo;0;L;;;;;N;;;;; 13347;EGYPTIAN HIEROGLYPH U019;Lo;0;L;;;;;N;;;;; 13348;EGYPTIAN HIEROGLYPH U020;Lo;0;L;;;;;N;;;;; 13349;EGYPTIAN HIEROGLYPH U021;Lo;0;L;;;;;N;;;;; 1334A;EGYPTIAN HIEROGLYPH U022;Lo;0;L;;;;;N;;;;; 1334B;EGYPTIAN HIEROGLYPH U023;Lo;0;L;;;;;N;;;;; 1334C;EGYPTIAN HIEROGLYPH U023A;Lo;0;L;;;;;N;;;;; 1334D;EGYPTIAN HIEROGLYPH U024;Lo;0;L;;;;;N;;;;; 1334E;EGYPTIAN HIEROGLYPH U025;Lo;0;L;;;;;N;;;;; 1334F;EGYPTIAN HIEROGLYPH U026;Lo;0;L;;;;;N;;;;; 13350;EGYPTIAN HIEROGLYPH U027;Lo;0;L;;;;;N;;;;; 13351;EGYPTIAN HIEROGLYPH U028;Lo;0;L;;;;;N;;;;; 13352;EGYPTIAN HIEROGLYPH U029;Lo;0;L;;;;;N;;;;; 13353;EGYPTIAN HIEROGLYPH U029A;Lo;0;L;;;;;N;;;;; 13354;EGYPTIAN HIEROGLYPH U030;Lo;0;L;;;;;N;;;;; 13355;EGYPTIAN HIEROGLYPH U031;Lo;0;L;;;;;N;;;;; 13356;EGYPTIAN HIEROGLYPH U032;Lo;0;L;;;;;N;;;;; 13357;EGYPTIAN HIEROGLYPH U032A;Lo;0;L;;;;;N;;;;; 13358;EGYPTIAN HIEROGLYPH U033;Lo;0;L;;;;;N;;;;; 13359;EGYPTIAN HIEROGLYPH U034;Lo;0;L;;;;;N;;;;; 1335A;EGYPTIAN HIEROGLYPH U035;Lo;0;L;;;;;N;;;;; 1335B;EGYPTIAN HIEROGLYPH U036;Lo;0;L;;;;;N;;;;; 1335C;EGYPTIAN HIEROGLYPH U037;Lo;0;L;;;;;N;;;;; 1335D;EGYPTIAN HIEROGLYPH U038;Lo;0;L;;;;;N;;;;; 1335E;EGYPTIAN HIEROGLYPH U039;Lo;0;L;;;;;N;;;;; 1335F;EGYPTIAN HIEROGLYPH U040;Lo;0;L;;;;;N;;;;; 13360;EGYPTIAN HIEROGLYPH U041;Lo;0;L;;;;;N;;;;; 13361;EGYPTIAN HIEROGLYPH U042;Lo;0;L;;;;;N;;;;; 13362;EGYPTIAN HIEROGLYPH V001;Lo;0;L;;;;;N;;;;; 13363;EGYPTIAN HIEROGLYPH V001A;Lo;0;L;;;;;N;;;;; 13364;EGYPTIAN HIEROGLYPH V001B;Lo;0;L;;;;;N;;;;; 13365;EGYPTIAN HIEROGLYPH V001C;Lo;0;L;;;;;N;;;;; 13366;EGYPTIAN HIEROGLYPH V001D;Lo;0;L;;;;;N;;;;; 13367;EGYPTIAN HIEROGLYPH V001E;Lo;0;L;;;;;N;;;;; 13368;EGYPTIAN HIEROGLYPH V001F;Lo;0;L;;;;;N;;;;; 13369;EGYPTIAN HIEROGLYPH V001G;Lo;0;L;;;;;N;;;;; 1336A;EGYPTIAN HIEROGLYPH V001H;Lo;0;L;;;;;N;;;;; 1336B;EGYPTIAN HIEROGLYPH V001I;Lo;0;L;;;;;N;;;;; 1336C;EGYPTIAN HIEROGLYPH V002;Lo;0;L;;;;;N;;;;; 1336D;EGYPTIAN HIEROGLYPH V002A;Lo;0;L;;;;;N;;;;; 1336E;EGYPTIAN HIEROGLYPH V003;Lo;0;L;;;;;N;;;;; 1336F;EGYPTIAN HIEROGLYPH V004;Lo;0;L;;;;;N;;;;; 13370;EGYPTIAN HIEROGLYPH V005;Lo;0;L;;;;;N;;;;; 13371;EGYPTIAN HIEROGLYPH V006;Lo;0;L;;;;;N;;;;; 13372;EGYPTIAN HIEROGLYPH V007;Lo;0;L;;;;;N;;;;; 13373;EGYPTIAN HIEROGLYPH V007A;Lo;0;L;;;;;N;;;;; 13374;EGYPTIAN HIEROGLYPH V007B;Lo;0;L;;;;;N;;;;; 13375;EGYPTIAN HIEROGLYPH V008;Lo;0;L;;;;;N;;;;; 13376;EGYPTIAN HIEROGLYPH V009;Lo;0;L;;;;;N;;;;; 13377;EGYPTIAN HIEROGLYPH V010;Lo;0;L;;;;;N;;;;; 13378;EGYPTIAN HIEROGLYPH V011;Lo;0;L;;;;;N;;;;; 13379;EGYPTIAN HIEROGLYPH V011A;Lo;0;L;;;;;N;;;;; 1337A;EGYPTIAN HIEROGLYPH V011B;Lo;0;L;;;;;N;;;;; 1337B;EGYPTIAN HIEROGLYPH V011C;Lo;0;L;;;;;N;;;;; 1337C;EGYPTIAN HIEROGLYPH V012;Lo;0;L;;;;;N;;;;; 1337D;EGYPTIAN HIEROGLYPH V012A;Lo;0;L;;;;;N;;;;; 1337E;EGYPTIAN HIEROGLYPH V012B;Lo;0;L;;;;;N;;;;; 1337F;EGYPTIAN HIEROGLYPH V013;Lo;0;L;;;;;N;;;;; 13380;EGYPTIAN HIEROGLYPH V014;Lo;0;L;;;;;N;;;;; 13381;EGYPTIAN HIEROGLYPH V015;Lo;0;L;;;;;N;;;;; 13382;EGYPTIAN HIEROGLYPH V016;Lo;0;L;;;;;N;;;;; 13383;EGYPTIAN HIEROGLYPH V017;Lo;0;L;;;;;N;;;;; 13384;EGYPTIAN HIEROGLYPH V018;Lo;0;L;;;;;N;;;;; 13385;EGYPTIAN HIEROGLYPH V019;Lo;0;L;;;;;N;;;;; 13386;EGYPTIAN HIEROGLYPH V020;Lo;0;L;;;;;N;;;;; 13387;EGYPTIAN HIEROGLYPH V020A;Lo;0;L;;;;;N;;;;; 13388;EGYPTIAN HIEROGLYPH V020B;Lo;0;L;;;;;N;;;;; 13389;EGYPTIAN HIEROGLYPH V020C;Lo;0;L;;;;;N;;;;; 1338A;EGYPTIAN HIEROGLYPH V020D;Lo;0;L;;;;;N;;;;; 1338B;EGYPTIAN HIEROGLYPH V020E;Lo;0;L;;;;;N;;;;; 1338C;EGYPTIAN HIEROGLYPH V020F;Lo;0;L;;;;;N;;;;; 1338D;EGYPTIAN HIEROGLYPH V020G;Lo;0;L;;;;;N;;;;; 1338E;EGYPTIAN HIEROGLYPH V020H;Lo;0;L;;;;;N;;;;; 1338F;EGYPTIAN HIEROGLYPH V020I;Lo;0;L;;;;;N;;;;; 13390;EGYPTIAN HIEROGLYPH V020J;Lo;0;L;;;;;N;;;;; 13391;EGYPTIAN HIEROGLYPH V020K;Lo;0;L;;;;;N;;;;; 13392;EGYPTIAN HIEROGLYPH V020L;Lo;0;L;;;;;N;;;;; 13393;EGYPTIAN HIEROGLYPH V021;Lo;0;L;;;;;N;;;;; 13394;EGYPTIAN HIEROGLYPH V022;Lo;0;L;;;;;N;;;;; 13395;EGYPTIAN HIEROGLYPH V023;Lo;0;L;;;;;N;;;;; 13396;EGYPTIAN HIEROGLYPH V023A;Lo;0;L;;;;;N;;;;; 13397;EGYPTIAN HIEROGLYPH V024;Lo;0;L;;;;;N;;;;; 13398;EGYPTIAN HIEROGLYPH V025;Lo;0;L;;;;;N;;;;; 13399;EGYPTIAN HIEROGLYPH V026;Lo;0;L;;;;;N;;;;; 1339A;EGYPTIAN HIEROGLYPH V027;Lo;0;L;;;;;N;;;;; 1339B;EGYPTIAN HIEROGLYPH V028;Lo;0;L;;;;;N;;;;; 1339C;EGYPTIAN HIEROGLYPH V028A;Lo;0;L;;;;;N;;;;; 1339D;EGYPTIAN HIEROGLYPH V029;Lo;0;L;;;;;N;;;;; 1339E;EGYPTIAN HIEROGLYPH V029A;Lo;0;L;;;;;N;;;;; 1339F;EGYPTIAN HIEROGLYPH V030;Lo;0;L;;;;;N;;;;; 133A0;EGYPTIAN HIEROGLYPH V030A;Lo;0;L;;;;;N;;;;; 133A1;EGYPTIAN HIEROGLYPH V031;Lo;0;L;;;;;N;;;;; 133A2;EGYPTIAN HIEROGLYPH V031A;Lo;0;L;;;;;N;;;;; 133A3;EGYPTIAN HIEROGLYPH V032;Lo;0;L;;;;;N;;;;; 133A4;EGYPTIAN HIEROGLYPH V033;Lo;0;L;;;;;N;;;;; 133A5;EGYPTIAN HIEROGLYPH V033A;Lo;0;L;;;;;N;;;;; 133A6;EGYPTIAN HIEROGLYPH V034;Lo;0;L;;;;;N;;;;; 133A7;EGYPTIAN HIEROGLYPH V035;Lo;0;L;;;;;N;;;;; 133A8;EGYPTIAN HIEROGLYPH V036;Lo;0;L;;;;;N;;;;; 133A9;EGYPTIAN HIEROGLYPH V037;Lo;0;L;;;;;N;;;;; 133AA;EGYPTIAN HIEROGLYPH V037A;Lo;0;L;;;;;N;;;;; 133AB;EGYPTIAN HIEROGLYPH V038;Lo;0;L;;;;;N;;;;; 133AC;EGYPTIAN HIEROGLYPH V039;Lo;0;L;;;;;N;;;;; 133AD;EGYPTIAN HIEROGLYPH V040;Lo;0;L;;;;;N;;;;; 133AE;EGYPTIAN HIEROGLYPH V040A;Lo;0;L;;;;;N;;;;; 133AF;EGYPTIAN HIEROGLYPH W001;Lo;0;L;;;;;N;;;;; 133B0;EGYPTIAN HIEROGLYPH W002;Lo;0;L;;;;;N;;;;; 133B1;EGYPTIAN HIEROGLYPH W003;Lo;0;L;;;;;N;;;;; 133B2;EGYPTIAN HIEROGLYPH W003A;Lo;0;L;;;;;N;;;;; 133B3;EGYPTIAN HIEROGLYPH W004;Lo;0;L;;;;;N;;;;; 133B4;EGYPTIAN HIEROGLYPH W005;Lo;0;L;;;;;N;;;;; 133B5;EGYPTIAN HIEROGLYPH W006;Lo;0;L;;;;;N;;;;; 133B6;EGYPTIAN HIEROGLYPH W007;Lo;0;L;;;;;N;;;;; 133B7;EGYPTIAN HIEROGLYPH W008;Lo;0;L;;;;;N;;;;; 133B8;EGYPTIAN HIEROGLYPH W009;Lo;0;L;;;;;N;;;;; 133B9;EGYPTIAN HIEROGLYPH W009A;Lo;0;L;;;;;N;;;;; 133BA;EGYPTIAN HIEROGLYPH W010;Lo;0;L;;;;;N;;;;; 133BB;EGYPTIAN HIEROGLYPH W010A;Lo;0;L;;;;;N;;;;; 133BC;EGYPTIAN HIEROGLYPH W011;Lo;0;L;;;;;N;;;;; 133BD;EGYPTIAN HIEROGLYPH W012;Lo;0;L;;;;;N;;;;; 133BE;EGYPTIAN HIEROGLYPH W013;Lo;0;L;;;;;N;;;;; 133BF;EGYPTIAN HIEROGLYPH W014;Lo;0;L;;;;;N;;;;; 133C0;EGYPTIAN HIEROGLYPH W014A;Lo;0;L;;;;;N;;;;; 133C1;EGYPTIAN HIEROGLYPH W015;Lo;0;L;;;;;N;;;;; 133C2;EGYPTIAN HIEROGLYPH W016;Lo;0;L;;;;;N;;;;; 133C3;EGYPTIAN HIEROGLYPH W017;Lo;0;L;;;;;N;;;;; 133C4;EGYPTIAN HIEROGLYPH W017A;Lo;0;L;;;;;N;;;;; 133C5;EGYPTIAN HIEROGLYPH W018;Lo;0;L;;;;;N;;;;; 133C6;EGYPTIAN HIEROGLYPH W018A;Lo;0;L;;;;;N;;;;; 133C7;EGYPTIAN HIEROGLYPH W019;Lo;0;L;;;;;N;;;;; 133C8;EGYPTIAN HIEROGLYPH W020;Lo;0;L;;;;;N;;;;; 133C9;EGYPTIAN HIEROGLYPH W021;Lo;0;L;;;;;N;;;;; 133CA;EGYPTIAN HIEROGLYPH W022;Lo;0;L;;;;;N;;;;; 133CB;EGYPTIAN HIEROGLYPH W023;Lo;0;L;;;;;N;;;;; 133CC;EGYPTIAN HIEROGLYPH W024;Lo;0;L;;;;;N;;;;; 133CD;EGYPTIAN HIEROGLYPH W024A;Lo;0;L;;;;;N;;;;; 133CE;EGYPTIAN HIEROGLYPH W025;Lo;0;L;;;;;N;;;;; 133CF;EGYPTIAN HIEROGLYPH X001;Lo;0;L;;;;;N;;;;; 133D0;EGYPTIAN HIEROGLYPH X002;Lo;0;L;;;;;N;;;;; 133D1;EGYPTIAN HIEROGLYPH X003;Lo;0;L;;;;;N;;;;; 133D2;EGYPTIAN HIEROGLYPH X004;Lo;0;L;;;;;N;;;;; 133D3;EGYPTIAN HIEROGLYPH X004A;Lo;0;L;;;;;N;;;;; 133D4;EGYPTIAN HIEROGLYPH X004B;Lo;0;L;;;;;N;;;;; 133D5;EGYPTIAN HIEROGLYPH X005;Lo;0;L;;;;;N;;;;; 133D6;EGYPTIAN HIEROGLYPH X006;Lo;0;L;;;;;N;;;;; 133D7;EGYPTIAN HIEROGLYPH X006A;Lo;0;L;;;;;N;;;;; 133D8;EGYPTIAN HIEROGLYPH X007;Lo;0;L;;;;;N;;;;; 133D9;EGYPTIAN HIEROGLYPH X008;Lo;0;L;;;;;N;;;;; 133DA;EGYPTIAN HIEROGLYPH X008A;Lo;0;L;;;;;N;;;;; 133DB;EGYPTIAN HIEROGLYPH Y001;Lo;0;L;;;;;N;;;;; 133DC;EGYPTIAN HIEROGLYPH Y001A;Lo;0;L;;;;;N;;;;; 133DD;EGYPTIAN HIEROGLYPH Y002;Lo;0;L;;;;;N;;;;; 133DE;EGYPTIAN HIEROGLYPH Y003;Lo;0;L;;;;;N;;;;; 133DF;EGYPTIAN HIEROGLYPH Y004;Lo;0;L;;;;;N;;;;; 133E0;EGYPTIAN HIEROGLYPH Y005;Lo;0;L;;;;;N;;;;; 133E1;EGYPTIAN HIEROGLYPH Y006;Lo;0;L;;;;;N;;;;; 133E2;EGYPTIAN HIEROGLYPH Y007;Lo;0;L;;;;;N;;;;; 133E3;EGYPTIAN HIEROGLYPH Y008;Lo;0;L;;;;;N;;;;; 133E4;EGYPTIAN HIEROGLYPH Z001;Lo;0;L;;;;;N;;;;; 133E5;EGYPTIAN HIEROGLYPH Z002;Lo;0;L;;;;;N;;;;; 133E6;EGYPTIAN HIEROGLYPH Z002A;Lo;0;L;;;;;N;;;;; 133E7;EGYPTIAN HIEROGLYPH Z002B;Lo;0;L;;;;;N;;;;; 133E8;EGYPTIAN HIEROGLYPH Z002C;Lo;0;L;;;;;N;;;;; 133E9;EGYPTIAN HIEROGLYPH Z002D;Lo;0;L;;;;;N;;;;; 133EA;EGYPTIAN HIEROGLYPH Z003;Lo;0;L;;;;;N;;;;; 133EB;EGYPTIAN HIEROGLYPH Z003A;Lo;0;L;;;;;N;;;;; 133EC;EGYPTIAN HIEROGLYPH Z003B;Lo;0;L;;;;;N;;;;; 133ED;EGYPTIAN HIEROGLYPH Z004;Lo;0;L;;;;;N;;;;; 133EE;EGYPTIAN HIEROGLYPH Z004A;Lo;0;L;;;;;N;;;;; 133EF;EGYPTIAN HIEROGLYPH Z005;Lo;0;L;;;;;N;;;;; 133F0;EGYPTIAN HIEROGLYPH Z005A;Lo;0;L;;;;;N;;;;; 133F1;EGYPTIAN HIEROGLYPH Z006;Lo;0;L;;;;;N;;;;; 133F2;EGYPTIAN HIEROGLYPH Z007;Lo;0;L;;;;;N;;;;; 133F3;EGYPTIAN HIEROGLYPH Z008;Lo;0;L;;;;;N;;;;; 133F4;EGYPTIAN HIEROGLYPH Z009;Lo;0;L;;;;;N;;;;; 133F5;EGYPTIAN HIEROGLYPH Z010;Lo;0;L;;;;;N;;;;; 133F6;EGYPTIAN HIEROGLYPH Z011;Lo;0;L;;;;;N;;;;; 133F7;EGYPTIAN HIEROGLYPH Z012;Lo;0;L;;;;;N;;;;; 133F8;EGYPTIAN HIEROGLYPH Z013;Lo;0;L;;;;;N;;;;; 133F9;EGYPTIAN HIEROGLYPH Z014;Lo;0;L;;;;;N;;;;; 133FA;EGYPTIAN HIEROGLYPH Z015;Lo;0;L;;;;;N;;;;; 133FB;EGYPTIAN HIEROGLYPH Z015A;Lo;0;L;;;;;N;;;;; 133FC;EGYPTIAN HIEROGLYPH Z015B;Lo;0;L;;;;;N;;;;; 133FD;EGYPTIAN HIEROGLYPH Z015C;Lo;0;L;;;;;N;;;;; 133FE;EGYPTIAN HIEROGLYPH Z015D;Lo;0;L;;;;;N;;;;; 133FF;EGYPTIAN HIEROGLYPH Z015E;Lo;0;L;;;;;N;;;;; 13400;EGYPTIAN HIEROGLYPH Z015F;Lo;0;L;;;;;N;;;;; 13401;EGYPTIAN HIEROGLYPH Z015G;Lo;0;L;;;;;N;;;;; 13402;EGYPTIAN HIEROGLYPH Z015H;Lo;0;L;;;;;N;;;;; 13403;EGYPTIAN HIEROGLYPH Z015I;Lo;0;L;;;;;N;;;;; 13404;EGYPTIAN HIEROGLYPH Z016;Lo;0;L;;;;;N;;;;; 13405;EGYPTIAN HIEROGLYPH Z016A;Lo;0;L;;;;;N;;;;; 13406;EGYPTIAN HIEROGLYPH Z016B;Lo;0;L;;;;;N;;;;; 13407;EGYPTIAN HIEROGLYPH Z016C;Lo;0;L;;;;;N;;;;; 13408;EGYPTIAN HIEROGLYPH Z016D;Lo;0;L;;;;;N;;;;; 13409;EGYPTIAN HIEROGLYPH Z016E;Lo;0;L;;;;;N;;;;; 1340A;EGYPTIAN HIEROGLYPH Z016F;Lo;0;L;;;;;N;;;;; 1340B;EGYPTIAN HIEROGLYPH Z016G;Lo;0;L;;;;;N;;;;; 1340C;EGYPTIAN HIEROGLYPH Z016H;Lo;0;L;;;;;N;;;;; 1340D;EGYPTIAN HIEROGLYPH AA001;Lo;0;L;;;;;N;;;;; 1340E;EGYPTIAN HIEROGLYPH AA002;Lo;0;L;;;;;N;;;;; 1340F;EGYPTIAN HIEROGLYPH AA003;Lo;0;L;;;;;N;;;;; 13410;EGYPTIAN HIEROGLYPH AA004;Lo;0;L;;;;;N;;;;; 13411;EGYPTIAN HIEROGLYPH AA005;Lo;0;L;;;;;N;;;;; 13412;EGYPTIAN HIEROGLYPH AA006;Lo;0;L;;;;;N;;;;; 13413;EGYPTIAN HIEROGLYPH AA007;Lo;0;L;;;;;N;;;;; 13414;EGYPTIAN HIEROGLYPH AA007A;Lo;0;L;;;;;N;;;;; 13415;EGYPTIAN HIEROGLYPH AA007B;Lo;0;L;;;;;N;;;;; 13416;EGYPTIAN HIEROGLYPH AA008;Lo;0;L;;;;;N;;;;; 13417;EGYPTIAN HIEROGLYPH AA009;Lo;0;L;;;;;N;;;;; 13418;EGYPTIAN HIEROGLYPH AA010;Lo;0;L;;;;;N;;;;; 13419;EGYPTIAN HIEROGLYPH AA011;Lo;0;L;;;;;N;;;;; 1341A;EGYPTIAN HIEROGLYPH AA012;Lo;0;L;;;;;N;;;;; 1341B;EGYPTIAN HIEROGLYPH AA013;Lo;0;L;;;;;N;;;;; 1341C;EGYPTIAN HIEROGLYPH AA014;Lo;0;L;;;;;N;;;;; 1341D;EGYPTIAN HIEROGLYPH AA015;Lo;0;L;;;;;N;;;;; 1341E;EGYPTIAN HIEROGLYPH AA016;Lo;0;L;;;;;N;;;;; 1341F;EGYPTIAN HIEROGLYPH AA017;Lo;0;L;;;;;N;;;;; 13420;EGYPTIAN HIEROGLYPH AA018;Lo;0;L;;;;;N;;;;; 13421;EGYPTIAN HIEROGLYPH AA019;Lo;0;L;;;;;N;;;;; 13422;EGYPTIAN HIEROGLYPH AA020;Lo;0;L;;;;;N;;;;; 13423;EGYPTIAN HIEROGLYPH AA021;Lo;0;L;;;;;N;;;;; 13424;EGYPTIAN HIEROGLYPH AA022;Lo;0;L;;;;;N;;;;; 13425;EGYPTIAN HIEROGLYPH AA023;Lo;0;L;;;;;N;;;;; 13426;EGYPTIAN HIEROGLYPH AA024;Lo;0;L;;;;;N;;;;; 13427;EGYPTIAN HIEROGLYPH AA025;Lo;0;L;;;;;N;;;;; 13428;EGYPTIAN HIEROGLYPH AA026;Lo;0;L;;;;;N;;;;; 13429;EGYPTIAN HIEROGLYPH AA027;Lo;0;L;;;;;N;;;;; 1342A;EGYPTIAN HIEROGLYPH AA028;Lo;0;L;;;;;N;;;;; 1342B;EGYPTIAN HIEROGLYPH AA029;Lo;0;L;;;;;N;;;;; 1342C;EGYPTIAN HIEROGLYPH AA030;Lo;0;L;;;;;N;;;;; 1342D;EGYPTIAN HIEROGLYPH AA031;Lo;0;L;;;;;N;;;;; 1342E;EGYPTIAN HIEROGLYPH AA032;Lo;0;L;;;;;N;;;;; 14400;ANATOLIAN HIEROGLYPH A001;Lo;0;L;;;;;N;;;;; 14401;ANATOLIAN HIEROGLYPH A002;Lo;0;L;;;;;N;;;;; 14402;ANATOLIAN HIEROGLYPH A003;Lo;0;L;;;;;N;;;;; 14403;ANATOLIAN HIEROGLYPH A004;Lo;0;L;;;;;N;;;;; 14404;ANATOLIAN HIEROGLYPH A005;Lo;0;L;;;;;N;;;;; 14405;ANATOLIAN HIEROGLYPH A006;Lo;0;L;;;;;N;;;;; 14406;ANATOLIAN HIEROGLYPH A007;Lo;0;L;;;;;N;;;;; 14407;ANATOLIAN HIEROGLYPH A008;Lo;0;L;;;;;N;;;;; 14408;ANATOLIAN HIEROGLYPH A009;Lo;0;L;;;;;N;;;;; 14409;ANATOLIAN HIEROGLYPH A010;Lo;0;L;;;;;N;;;;; 1440A;ANATOLIAN HIEROGLYPH A010A;Lo;0;L;;;;;N;;;;; 1440B;ANATOLIAN HIEROGLYPH A011;Lo;0;L;;;;;N;;;;; 1440C;ANATOLIAN HIEROGLYPH A012;Lo;0;L;;;;;N;;;;; 1440D;ANATOLIAN HIEROGLYPH A013;Lo;0;L;;;;;N;;;;; 1440E;ANATOLIAN HIEROGLYPH A014;Lo;0;L;;;;;N;;;;; 1440F;ANATOLIAN HIEROGLYPH A015;Lo;0;L;;;;;N;;;;; 14410;ANATOLIAN HIEROGLYPH A016;Lo;0;L;;;;;N;;;;; 14411;ANATOLIAN HIEROGLYPH A017;Lo;0;L;;;;;N;;;;; 14412;ANATOLIAN HIEROGLYPH A018;Lo;0;L;;;;;N;;;;; 14413;ANATOLIAN HIEROGLYPH A019;Lo;0;L;;;;;N;;;;; 14414;ANATOLIAN HIEROGLYPH A020;Lo;0;L;;;;;N;;;;; 14415;ANATOLIAN HIEROGLYPH A021;Lo;0;L;;;;;N;;;;; 14416;ANATOLIAN HIEROGLYPH A022;Lo;0;L;;;;;N;;;;; 14417;ANATOLIAN HIEROGLYPH A023;Lo;0;L;;;;;N;;;;; 14418;ANATOLIAN HIEROGLYPH A024;Lo;0;L;;;;;N;;;;; 14419;ANATOLIAN HIEROGLYPH A025;Lo;0;L;;;;;N;;;;; 1441A;ANATOLIAN HIEROGLYPH A026;Lo;0;L;;;;;N;;;;; 1441B;ANATOLIAN HIEROGLYPH A026A;Lo;0;L;;;;;N;;;;; 1441C;ANATOLIAN HIEROGLYPH A027;Lo;0;L;;;;;N;;;;; 1441D;ANATOLIAN HIEROGLYPH A028;Lo;0;L;;;;;N;;;;; 1441E;ANATOLIAN HIEROGLYPH A029;Lo;0;L;;;;;N;;;;; 1441F;ANATOLIAN HIEROGLYPH A030;Lo;0;L;;;;;N;;;;; 14420;ANATOLIAN HIEROGLYPH A031;Lo;0;L;;;;;N;;;;; 14421;ANATOLIAN HIEROGLYPH A032;Lo;0;L;;;;;N;;;;; 14422;ANATOLIAN HIEROGLYPH A033;Lo;0;L;;;;;N;;;;; 14423;ANATOLIAN HIEROGLYPH A034;Lo;0;L;;;;;N;;;;; 14424;ANATOLIAN HIEROGLYPH A035;Lo;0;L;;;;;N;;;;; 14425;ANATOLIAN HIEROGLYPH A036;Lo;0;L;;;;;N;;;;; 14426;ANATOLIAN HIEROGLYPH A037;Lo;0;L;;;;;N;;;;; 14427;ANATOLIAN HIEROGLYPH A038;Lo;0;L;;;;;N;;;;; 14428;ANATOLIAN HIEROGLYPH A039;Lo;0;L;;;;;N;;;;; 14429;ANATOLIAN HIEROGLYPH A039A;Lo;0;L;;;;;N;;;;; 1442A;ANATOLIAN HIEROGLYPH A040;Lo;0;L;;;;;N;;;;; 1442B;ANATOLIAN HIEROGLYPH A041;Lo;0;L;;;;;N;;;;; 1442C;ANATOLIAN HIEROGLYPH A041A;Lo;0;L;;;;;N;;;;; 1442D;ANATOLIAN HIEROGLYPH A042;Lo;0;L;;;;;N;;;;; 1442E;ANATOLIAN HIEROGLYPH A043;Lo;0;L;;;;;N;;;;; 1442F;ANATOLIAN HIEROGLYPH A044;Lo;0;L;;;;;N;;;;; 14430;ANATOLIAN HIEROGLYPH A045;Lo;0;L;;;;;N;;;;; 14431;ANATOLIAN HIEROGLYPH A045A;Lo;0;L;;;;;N;;;;; 14432;ANATOLIAN HIEROGLYPH A046;Lo;0;L;;;;;N;;;;; 14433;ANATOLIAN HIEROGLYPH A046A;Lo;0;L;;;;;N;;;;; 14434;ANATOLIAN HIEROGLYPH A046B;Lo;0;L;;;;;N;;;;; 14435;ANATOLIAN HIEROGLYPH A047;Lo;0;L;;;;;N;;;;; 14436;ANATOLIAN HIEROGLYPH A048;Lo;0;L;;;;;N;;;;; 14437;ANATOLIAN HIEROGLYPH A049;Lo;0;L;;;;;N;;;;; 14438;ANATOLIAN HIEROGLYPH A050;Lo;0;L;;;;;N;;;;; 14439;ANATOLIAN HIEROGLYPH A051;Lo;0;L;;;;;N;;;;; 1443A;ANATOLIAN HIEROGLYPH A052;Lo;0;L;;;;;N;;;;; 1443B;ANATOLIAN HIEROGLYPH A053;Lo;0;L;;;;;N;;;;; 1443C;ANATOLIAN HIEROGLYPH A054;Lo;0;L;;;;;N;;;;; 1443D;ANATOLIAN HIEROGLYPH A055;Lo;0;L;;;;;N;;;;; 1443E;ANATOLIAN HIEROGLYPH A056;Lo;0;L;;;;;N;;;;; 1443F;ANATOLIAN HIEROGLYPH A057;Lo;0;L;;;;;N;;;;; 14440;ANATOLIAN HIEROGLYPH A058;Lo;0;L;;;;;N;;;;; 14441;ANATOLIAN HIEROGLYPH A059;Lo;0;L;;;;;N;;;;; 14442;ANATOLIAN HIEROGLYPH A060;Lo;0;L;;;;;N;;;;; 14443;ANATOLIAN HIEROGLYPH A061;Lo;0;L;;;;;N;;;;; 14444;ANATOLIAN HIEROGLYPH A062;Lo;0;L;;;;;N;;;;; 14445;ANATOLIAN HIEROGLYPH A063;Lo;0;L;;;;;N;;;;; 14446;ANATOLIAN HIEROGLYPH A064;Lo;0;L;;;;;N;;;;; 14447;ANATOLIAN HIEROGLYPH A065;Lo;0;L;;;;;N;;;;; 14448;ANATOLIAN HIEROGLYPH A066;Lo;0;L;;;;;N;;;;; 14449;ANATOLIAN HIEROGLYPH A066A;Lo;0;L;;;;;N;;;;; 1444A;ANATOLIAN HIEROGLYPH A066B;Lo;0;L;;;;;N;;;;; 1444B;ANATOLIAN HIEROGLYPH A066C;Lo;0;L;;;;;N;;;;; 1444C;ANATOLIAN HIEROGLYPH A067;Lo;0;L;;;;;N;;;;; 1444D;ANATOLIAN HIEROGLYPH A068;Lo;0;L;;;;;N;;;;; 1444E;ANATOLIAN HIEROGLYPH A069;Lo;0;L;;;;;N;;;;; 1444F;ANATOLIAN HIEROGLYPH A070;Lo;0;L;;;;;N;;;;; 14450;ANATOLIAN HIEROGLYPH A071;Lo;0;L;;;;;N;;;;; 14451;ANATOLIAN HIEROGLYPH A072;Lo;0;L;;;;;N;;;;; 14452;ANATOLIAN HIEROGLYPH A073;Lo;0;L;;;;;N;;;;; 14453;ANATOLIAN HIEROGLYPH A074;Lo;0;L;;;;;N;;;;; 14454;ANATOLIAN HIEROGLYPH A075;Lo;0;L;;;;;N;;;;; 14455;ANATOLIAN HIEROGLYPH A076;Lo;0;L;;;;;N;;;;; 14456;ANATOLIAN HIEROGLYPH A077;Lo;0;L;;;;;N;;;;; 14457;ANATOLIAN HIEROGLYPH A078;Lo;0;L;;;;;N;;;;; 14458;ANATOLIAN HIEROGLYPH A079;Lo;0;L;;;;;N;;;;; 14459;ANATOLIAN HIEROGLYPH A080;Lo;0;L;;;;;N;;;;; 1445A;ANATOLIAN HIEROGLYPH A081;Lo;0;L;;;;;N;;;;; 1445B;ANATOLIAN HIEROGLYPH A082;Lo;0;L;;;;;N;;;;; 1445C;ANATOLIAN HIEROGLYPH A083;Lo;0;L;;;;;N;;;;; 1445D;ANATOLIAN HIEROGLYPH A084;Lo;0;L;;;;;N;;;;; 1445E;ANATOLIAN HIEROGLYPH A085;Lo;0;L;;;;;N;;;;; 1445F;ANATOLIAN HIEROGLYPH A086;Lo;0;L;;;;;N;;;;; 14460;ANATOLIAN HIEROGLYPH A087;Lo;0;L;;;;;N;;;;; 14461;ANATOLIAN HIEROGLYPH A088;Lo;0;L;;;;;N;;;;; 14462;ANATOLIAN HIEROGLYPH A089;Lo;0;L;;;;;N;;;;; 14463;ANATOLIAN HIEROGLYPH A090;Lo;0;L;;;;;N;;;;; 14464;ANATOLIAN HIEROGLYPH A091;Lo;0;L;;;;;N;;;;; 14465;ANATOLIAN HIEROGLYPH A092;Lo;0;L;;;;;N;;;;; 14466;ANATOLIAN HIEROGLYPH A093;Lo;0;L;;;;;N;;;;; 14467;ANATOLIAN HIEROGLYPH A094;Lo;0;L;;;;;N;;;;; 14468;ANATOLIAN HIEROGLYPH A095;Lo;0;L;;;;;N;;;;; 14469;ANATOLIAN HIEROGLYPH A096;Lo;0;L;;;;;N;;;;; 1446A;ANATOLIAN HIEROGLYPH A097;Lo;0;L;;;;;N;;;;; 1446B;ANATOLIAN HIEROGLYPH A097A;Lo;0;L;;;;;N;;;;; 1446C;ANATOLIAN HIEROGLYPH A098;Lo;0;L;;;;;N;;;;; 1446D;ANATOLIAN HIEROGLYPH A098A;Lo;0;L;;;;;N;;;;; 1446E;ANATOLIAN HIEROGLYPH A099;Lo;0;L;;;;;N;;;;; 1446F;ANATOLIAN HIEROGLYPH A100;Lo;0;L;;;;;N;;;;; 14470;ANATOLIAN HIEROGLYPH A100A;Lo;0;L;;;;;N;;;;; 14471;ANATOLIAN HIEROGLYPH A101;Lo;0;L;;;;;N;;;;; 14472;ANATOLIAN HIEROGLYPH A101A;Lo;0;L;;;;;N;;;;; 14473;ANATOLIAN HIEROGLYPH A102;Lo;0;L;;;;;N;;;;; 14474;ANATOLIAN HIEROGLYPH A102A;Lo;0;L;;;;;N;;;;; 14475;ANATOLIAN HIEROGLYPH A103;Lo;0;L;;;;;N;;;;; 14476;ANATOLIAN HIEROGLYPH A104;Lo;0;L;;;;;N;;;;; 14477;ANATOLIAN HIEROGLYPH A104A;Lo;0;L;;;;;N;;;;; 14478;ANATOLIAN HIEROGLYPH A104B;Lo;0;L;;;;;N;;;;; 14479;ANATOLIAN HIEROGLYPH A104C;Lo;0;L;;;;;N;;;;; 1447A;ANATOLIAN HIEROGLYPH A105;Lo;0;L;;;;;N;;;;; 1447B;ANATOLIAN HIEROGLYPH A105A;Lo;0;L;;;;;N;;;;; 1447C;ANATOLIAN HIEROGLYPH A105B;Lo;0;L;;;;;N;;;;; 1447D;ANATOLIAN HIEROGLYPH A106;Lo;0;L;;;;;N;;;;; 1447E;ANATOLIAN HIEROGLYPH A107;Lo;0;L;;;;;N;;;;; 1447F;ANATOLIAN HIEROGLYPH A107A;Lo;0;L;;;;;N;;;;; 14480;ANATOLIAN HIEROGLYPH A107B;Lo;0;L;;;;;N;;;;; 14481;ANATOLIAN HIEROGLYPH A107C;Lo;0;L;;;;;N;;;;; 14482;ANATOLIAN HIEROGLYPH A108;Lo;0;L;;;;;N;;;;; 14483;ANATOLIAN HIEROGLYPH A109;Lo;0;L;;;;;N;;;;; 14484;ANATOLIAN HIEROGLYPH A110;Lo;0;L;;;;;N;;;;; 14485;ANATOLIAN HIEROGLYPH A110A;Lo;0;L;;;;;N;;;;; 14486;ANATOLIAN HIEROGLYPH A110B;Lo;0;L;;;;;N;;;;; 14487;ANATOLIAN HIEROGLYPH A111;Lo;0;L;;;;;N;;;;; 14488;ANATOLIAN HIEROGLYPH A112;Lo;0;L;;;;;N;;;;; 14489;ANATOLIAN HIEROGLYPH A113;Lo;0;L;;;;;N;;;;; 1448A;ANATOLIAN HIEROGLYPH A114;Lo;0;L;;;;;N;;;;; 1448B;ANATOLIAN HIEROGLYPH A115;Lo;0;L;;;;;N;;;;; 1448C;ANATOLIAN HIEROGLYPH A115A;Lo;0;L;;;;;N;;;;; 1448D;ANATOLIAN HIEROGLYPH A116;Lo;0;L;;;;;N;;;;; 1448E;ANATOLIAN HIEROGLYPH A117;Lo;0;L;;;;;N;;;;; 1448F;ANATOLIAN HIEROGLYPH A118;Lo;0;L;;;;;N;;;;; 14490;ANATOLIAN HIEROGLYPH A119;Lo;0;L;;;;;N;;;;; 14491;ANATOLIAN HIEROGLYPH A120;Lo;0;L;;;;;N;;;;; 14492;ANATOLIAN HIEROGLYPH A121;Lo;0;L;;;;;N;;;;; 14493;ANATOLIAN HIEROGLYPH A122;Lo;0;L;;;;;N;;;;; 14494;ANATOLIAN HIEROGLYPH A123;Lo;0;L;;;;;N;;;;; 14495;ANATOLIAN HIEROGLYPH A124;Lo;0;L;;;;;N;;;;; 14496;ANATOLIAN HIEROGLYPH A125;Lo;0;L;;;;;N;;;;; 14497;ANATOLIAN HIEROGLYPH A125A;Lo;0;L;;;;;N;;;;; 14498;ANATOLIAN HIEROGLYPH A126;Lo;0;L;;;;;N;;;;; 14499;ANATOLIAN HIEROGLYPH A127;Lo;0;L;;;;;N;;;;; 1449A;ANATOLIAN HIEROGLYPH A128;Lo;0;L;;;;;N;;;;; 1449B;ANATOLIAN HIEROGLYPH A129;Lo;0;L;;;;;N;;;;; 1449C;ANATOLIAN HIEROGLYPH A130;Lo;0;L;;;;;N;;;;; 1449D;ANATOLIAN HIEROGLYPH A131;Lo;0;L;;;;;N;;;;; 1449E;ANATOLIAN HIEROGLYPH A132;Lo;0;L;;;;;N;;;;; 1449F;ANATOLIAN HIEROGLYPH A133;Lo;0;L;;;;;N;;;;; 144A0;ANATOLIAN HIEROGLYPH A134;Lo;0;L;;;;;N;;;;; 144A1;ANATOLIAN HIEROGLYPH A135;Lo;0;L;;;;;N;;;;; 144A2;ANATOLIAN HIEROGLYPH A135A;Lo;0;L;;;;;N;;;;; 144A3;ANATOLIAN HIEROGLYPH A136;Lo;0;L;;;;;N;;;;; 144A4;ANATOLIAN HIEROGLYPH A137;Lo;0;L;;;;;N;;;;; 144A5;ANATOLIAN HIEROGLYPH A138;Lo;0;L;;;;;N;;;;; 144A6;ANATOLIAN HIEROGLYPH A139;Lo;0;L;;;;;N;;;;; 144A7;ANATOLIAN HIEROGLYPH A140;Lo;0;L;;;;;N;;;;; 144A8;ANATOLIAN HIEROGLYPH A141;Lo;0;L;;;;;N;;;;; 144A9;ANATOLIAN HIEROGLYPH A142;Lo;0;L;;;;;N;;;;; 144AA;ANATOLIAN HIEROGLYPH A143;Lo;0;L;;;;;N;;;;; 144AB;ANATOLIAN HIEROGLYPH A144;Lo;0;L;;;;;N;;;;; 144AC;ANATOLIAN HIEROGLYPH A145;Lo;0;L;;;;;N;;;;; 144AD;ANATOLIAN HIEROGLYPH A146;Lo;0;L;;;;;N;;;;; 144AE;ANATOLIAN HIEROGLYPH A147;Lo;0;L;;;;;N;;;;; 144AF;ANATOLIAN HIEROGLYPH A148;Lo;0;L;;;;;N;;;;; 144B0;ANATOLIAN HIEROGLYPH A149;Lo;0;L;;;;;N;;;;; 144B1;ANATOLIAN HIEROGLYPH A150;Lo;0;L;;;;;N;;;;; 144B2;ANATOLIAN HIEROGLYPH A151;Lo;0;L;;;;;N;;;;; 144B3;ANATOLIAN HIEROGLYPH A152;Lo;0;L;;;;;N;;;;; 144B4;ANATOLIAN HIEROGLYPH A153;Lo;0;L;;;;;N;;;;; 144B5;ANATOLIAN HIEROGLYPH A154;Lo;0;L;;;;;N;;;;; 144B6;ANATOLIAN HIEROGLYPH A155;Lo;0;L;;;;;N;;;;; 144B7;ANATOLIAN HIEROGLYPH A156;Lo;0;L;;;;;N;;;;; 144B8;ANATOLIAN HIEROGLYPH A157;Lo;0;L;;;;;N;;;;; 144B9;ANATOLIAN HIEROGLYPH A158;Lo;0;L;;;;;N;;;;; 144BA;ANATOLIAN HIEROGLYPH A159;Lo;0;L;;;;;N;;;;; 144BB;ANATOLIAN HIEROGLYPH A160;Lo;0;L;;;;;N;;;;; 144BC;ANATOLIAN HIEROGLYPH A161;Lo;0;L;;;;;N;;;;; 144BD;ANATOLIAN HIEROGLYPH A162;Lo;0;L;;;;;N;;;;; 144BE;ANATOLIAN HIEROGLYPH A163;Lo;0;L;;;;;N;;;;; 144BF;ANATOLIAN HIEROGLYPH A164;Lo;0;L;;;;;N;;;;; 144C0;ANATOLIAN HIEROGLYPH A165;Lo;0;L;;;;;N;;;;; 144C1;ANATOLIAN HIEROGLYPH A166;Lo;0;L;;;;;N;;;;; 144C2;ANATOLIAN HIEROGLYPH A167;Lo;0;L;;;;;N;;;;; 144C3;ANATOLIAN HIEROGLYPH A168;Lo;0;L;;;;;N;;;;; 144C4;ANATOLIAN HIEROGLYPH A169;Lo;0;L;;;;;N;;;;; 144C5;ANATOLIAN HIEROGLYPH A170;Lo;0;L;;;;;N;;;;; 144C6;ANATOLIAN HIEROGLYPH A171;Lo;0;L;;;;;N;;;;; 144C7;ANATOLIAN HIEROGLYPH A172;Lo;0;L;;;;;N;;;;; 144C8;ANATOLIAN HIEROGLYPH A173;Lo;0;L;;;;;N;;;;; 144C9;ANATOLIAN HIEROGLYPH A174;Lo;0;L;;;;;N;;;;; 144CA;ANATOLIAN HIEROGLYPH A175;Lo;0;L;;;;;N;;;;; 144CB;ANATOLIAN HIEROGLYPH A176;Lo;0;L;;;;;N;;;;; 144CC;ANATOLIAN HIEROGLYPH A177;Lo;0;L;;;;;N;;;;; 144CD;ANATOLIAN HIEROGLYPH A178;Lo;0;L;;;;;N;;;;; 144CE;ANATOLIAN HIEROGLYPH A179;Lo;0;L;;;;;N;;;;; 144CF;ANATOLIAN HIEROGLYPH A180;Lo;0;L;;;;;N;;;;; 144D0;ANATOLIAN HIEROGLYPH A181;Lo;0;L;;;;;N;;;;; 144D1;ANATOLIAN HIEROGLYPH A182;Lo;0;L;;;;;N;;;;; 144D2;ANATOLIAN HIEROGLYPH A183;Lo;0;L;;;;;N;;;;; 144D3;ANATOLIAN HIEROGLYPH A184;Lo;0;L;;;;;N;;;;; 144D4;ANATOLIAN HIEROGLYPH A185;Lo;0;L;;;;;N;;;;; 144D5;ANATOLIAN HIEROGLYPH A186;Lo;0;L;;;;;N;;;;; 144D6;ANATOLIAN HIEROGLYPH A187;Lo;0;L;;;;;N;;;;; 144D7;ANATOLIAN HIEROGLYPH A188;Lo;0;L;;;;;N;;;;; 144D8;ANATOLIAN HIEROGLYPH A189;Lo;0;L;;;;;N;;;;; 144D9;ANATOLIAN HIEROGLYPH A190;Lo;0;L;;;;;N;;;;; 144DA;ANATOLIAN HIEROGLYPH A191;Lo;0;L;;;;;N;;;;; 144DB;ANATOLIAN HIEROGLYPH A192;Lo;0;L;;;;;N;;;;; 144DC;ANATOLIAN HIEROGLYPH A193;Lo;0;L;;;;;N;;;;; 144DD;ANATOLIAN HIEROGLYPH A194;Lo;0;L;;;;;N;;;;; 144DE;ANATOLIAN HIEROGLYPH A195;Lo;0;L;;;;;N;;;;; 144DF;ANATOLIAN HIEROGLYPH A196;Lo;0;L;;;;;N;;;;; 144E0;ANATOLIAN HIEROGLYPH A197;Lo;0;L;;;;;N;;;;; 144E1;ANATOLIAN HIEROGLYPH A198;Lo;0;L;;;;;N;;;;; 144E2;ANATOLIAN HIEROGLYPH A199;Lo;0;L;;;;;N;;;;; 144E3;ANATOLIAN HIEROGLYPH A200;Lo;0;L;;;;;N;;;;; 144E4;ANATOLIAN HIEROGLYPH A201;Lo;0;L;;;;;N;;;;; 144E5;ANATOLIAN HIEROGLYPH A202;Lo;0;L;;;;;N;;;;; 144E6;ANATOLIAN HIEROGLYPH A202A;Lo;0;L;;;;;N;;;;; 144E7;ANATOLIAN HIEROGLYPH A202B;Lo;0;L;;;;;N;;;;; 144E8;ANATOLIAN HIEROGLYPH A203;Lo;0;L;;;;;N;;;;; 144E9;ANATOLIAN HIEROGLYPH A204;Lo;0;L;;;;;N;;;;; 144EA;ANATOLIAN HIEROGLYPH A205;Lo;0;L;;;;;N;;;;; 144EB;ANATOLIAN HIEROGLYPH A206;Lo;0;L;;;;;N;;;;; 144EC;ANATOLIAN HIEROGLYPH A207;Lo;0;L;;;;;N;;;;; 144ED;ANATOLIAN HIEROGLYPH A207A;Lo;0;L;;;;;N;;;;; 144EE;ANATOLIAN HIEROGLYPH A208;Lo;0;L;;;;;N;;;;; 144EF;ANATOLIAN HIEROGLYPH A209;Lo;0;L;;;;;N;;;;; 144F0;ANATOLIAN HIEROGLYPH A209A;Lo;0;L;;;;;N;;;;; 144F1;ANATOLIAN HIEROGLYPH A210;Lo;0;L;;;;;N;;;;; 144F2;ANATOLIAN HIEROGLYPH A211;Lo;0;L;;;;;N;;;;; 144F3;ANATOLIAN HIEROGLYPH A212;Lo;0;L;;;;;N;;;;; 144F4;ANATOLIAN HIEROGLYPH A213;Lo;0;L;;;;;N;;;;; 144F5;ANATOLIAN HIEROGLYPH A214;Lo;0;L;;;;;N;;;;; 144F6;ANATOLIAN HIEROGLYPH A215;Lo;0;L;;;;;N;;;;; 144F7;ANATOLIAN HIEROGLYPH A215A;Lo;0;L;;;;;N;;;;; 144F8;ANATOLIAN HIEROGLYPH A216;Lo;0;L;;;;;N;;;;; 144F9;ANATOLIAN HIEROGLYPH A216A;Lo;0;L;;;;;N;;;;; 144FA;ANATOLIAN HIEROGLYPH A217;Lo;0;L;;;;;N;;;;; 144FB;ANATOLIAN HIEROGLYPH A218;Lo;0;L;;;;;N;;;;; 144FC;ANATOLIAN HIEROGLYPH A219;Lo;0;L;;;;;N;;;;; 144FD;ANATOLIAN HIEROGLYPH A220;Lo;0;L;;;;;N;;;;; 144FE;ANATOLIAN HIEROGLYPH A221;Lo;0;L;;;;;N;;;;; 144FF;ANATOLIAN HIEROGLYPH A222;Lo;0;L;;;;;N;;;;; 14500;ANATOLIAN HIEROGLYPH A223;Lo;0;L;;;;;N;;;;; 14501;ANATOLIAN HIEROGLYPH A224;Lo;0;L;;;;;N;;;;; 14502;ANATOLIAN HIEROGLYPH A225;Lo;0;L;;;;;N;;;;; 14503;ANATOLIAN HIEROGLYPH A226;Lo;0;L;;;;;N;;;;; 14504;ANATOLIAN HIEROGLYPH A227;Lo;0;L;;;;;N;;;;; 14505;ANATOLIAN HIEROGLYPH A227A;Lo;0;L;;;;;N;;;;; 14506;ANATOLIAN HIEROGLYPH A228;Lo;0;L;;;;;N;;;;; 14507;ANATOLIAN HIEROGLYPH A229;Lo;0;L;;;;;N;;;;; 14508;ANATOLIAN HIEROGLYPH A230;Lo;0;L;;;;;N;;;;; 14509;ANATOLIAN HIEROGLYPH A231;Lo;0;L;;;;;N;;;;; 1450A;ANATOLIAN HIEROGLYPH A232;Lo;0;L;;;;;N;;;;; 1450B;ANATOLIAN HIEROGLYPH A233;Lo;0;L;;;;;N;;;;; 1450C;ANATOLIAN HIEROGLYPH A234;Lo;0;L;;;;;N;;;;; 1450D;ANATOLIAN HIEROGLYPH A235;Lo;0;L;;;;;N;;;;; 1450E;ANATOLIAN HIEROGLYPH A236;Lo;0;L;;;;;N;;;;; 1450F;ANATOLIAN HIEROGLYPH A237;Lo;0;L;;;;;N;;;;; 14510;ANATOLIAN HIEROGLYPH A238;Lo;0;L;;;;;N;;;;; 14511;ANATOLIAN HIEROGLYPH A239;Lo;0;L;;;;;N;;;;; 14512;ANATOLIAN HIEROGLYPH A240;Lo;0;L;;;;;N;;;;; 14513;ANATOLIAN HIEROGLYPH A241;Lo;0;L;;;;;N;;;;; 14514;ANATOLIAN HIEROGLYPH A242;Lo;0;L;;;;;N;;;;; 14515;ANATOLIAN HIEROGLYPH A243;Lo;0;L;;;;;N;;;;; 14516;ANATOLIAN HIEROGLYPH A244;Lo;0;L;;;;;N;;;;; 14517;ANATOLIAN HIEROGLYPH A245;Lo;0;L;;;;;N;;;;; 14518;ANATOLIAN HIEROGLYPH A246;Lo;0;L;;;;;N;;;;; 14519;ANATOLIAN HIEROGLYPH A247;Lo;0;L;;;;;N;;;;; 1451A;ANATOLIAN HIEROGLYPH A248;Lo;0;L;;;;;N;;;;; 1451B;ANATOLIAN HIEROGLYPH A249;Lo;0;L;;;;;N;;;;; 1451C;ANATOLIAN HIEROGLYPH A250;Lo;0;L;;;;;N;;;;; 1451D;ANATOLIAN HIEROGLYPH A251;Lo;0;L;;;;;N;;;;; 1451E;ANATOLIAN HIEROGLYPH A252;Lo;0;L;;;;;N;;;;; 1451F;ANATOLIAN HIEROGLYPH A253;Lo;0;L;;;;;N;;;;; 14520;ANATOLIAN HIEROGLYPH A254;Lo;0;L;;;;;N;;;;; 14521;ANATOLIAN HIEROGLYPH A255;Lo;0;L;;;;;N;;;;; 14522;ANATOLIAN HIEROGLYPH A256;Lo;0;L;;;;;N;;;;; 14523;ANATOLIAN HIEROGLYPH A257;Lo;0;L;;;;;N;;;;; 14524;ANATOLIAN HIEROGLYPH A258;Lo;0;L;;;;;N;;;;; 14525;ANATOLIAN HIEROGLYPH A259;Lo;0;L;;;;;N;;;;; 14526;ANATOLIAN HIEROGLYPH A260;Lo;0;L;;;;;N;;;;; 14527;ANATOLIAN HIEROGLYPH A261;Lo;0;L;;;;;N;;;;; 14528;ANATOLIAN HIEROGLYPH A262;Lo;0;L;;;;;N;;;;; 14529;ANATOLIAN HIEROGLYPH A263;Lo;0;L;;;;;N;;;;; 1452A;ANATOLIAN HIEROGLYPH A264;Lo;0;L;;;;;N;;;;; 1452B;ANATOLIAN HIEROGLYPH A265;Lo;0;L;;;;;N;;;;; 1452C;ANATOLIAN HIEROGLYPH A266;Lo;0;L;;;;;N;;;;; 1452D;ANATOLIAN HIEROGLYPH A267;Lo;0;L;;;;;N;;;;; 1452E;ANATOLIAN HIEROGLYPH A267A;Lo;0;L;;;;;N;;;;; 1452F;ANATOLIAN HIEROGLYPH A268;Lo;0;L;;;;;N;;;;; 14530;ANATOLIAN HIEROGLYPH A269;Lo;0;L;;;;;N;;;;; 14531;ANATOLIAN HIEROGLYPH A270;Lo;0;L;;;;;N;;;;; 14532;ANATOLIAN HIEROGLYPH A271;Lo;0;L;;;;;N;;;;; 14533;ANATOLIAN HIEROGLYPH A272;Lo;0;L;;;;;N;;;;; 14534;ANATOLIAN HIEROGLYPH A273;Lo;0;L;;;;;N;;;;; 14535;ANATOLIAN HIEROGLYPH A274;Lo;0;L;;;;;N;;;;; 14536;ANATOLIAN HIEROGLYPH A275;Lo;0;L;;;;;N;;;;; 14537;ANATOLIAN HIEROGLYPH A276;Lo;0;L;;;;;N;;;;; 14538;ANATOLIAN HIEROGLYPH A277;Lo;0;L;;;;;N;;;;; 14539;ANATOLIAN HIEROGLYPH A278;Lo;0;L;;;;;N;;;;; 1453A;ANATOLIAN HIEROGLYPH A279;Lo;0;L;;;;;N;;;;; 1453B;ANATOLIAN HIEROGLYPH A280;Lo;0;L;;;;;N;;;;; 1453C;ANATOLIAN HIEROGLYPH A281;Lo;0;L;;;;;N;;;;; 1453D;ANATOLIAN HIEROGLYPH A282;Lo;0;L;;;;;N;;;;; 1453E;ANATOLIAN HIEROGLYPH A283;Lo;0;L;;;;;N;;;;; 1453F;ANATOLIAN HIEROGLYPH A284;Lo;0;L;;;;;N;;;;; 14540;ANATOLIAN HIEROGLYPH A285;Lo;0;L;;;;;N;;;;; 14541;ANATOLIAN HIEROGLYPH A286;Lo;0;L;;;;;N;;;;; 14542;ANATOLIAN HIEROGLYPH A287;Lo;0;L;;;;;N;;;;; 14543;ANATOLIAN HIEROGLYPH A288;Lo;0;L;;;;;N;;;;; 14544;ANATOLIAN HIEROGLYPH A289;Lo;0;L;;;;;N;;;;; 14545;ANATOLIAN HIEROGLYPH A289A;Lo;0;L;;;;;N;;;;; 14546;ANATOLIAN HIEROGLYPH A290;Lo;0;L;;;;;N;;;;; 14547;ANATOLIAN HIEROGLYPH A291;Lo;0;L;;;;;N;;;;; 14548;ANATOLIAN HIEROGLYPH A292;Lo;0;L;;;;;N;;;;; 14549;ANATOLIAN HIEROGLYPH A293;Lo;0;L;;;;;N;;;;; 1454A;ANATOLIAN HIEROGLYPH A294;Lo;0;L;;;;;N;;;;; 1454B;ANATOLIAN HIEROGLYPH A294A;Lo;0;L;;;;;N;;;;; 1454C;ANATOLIAN HIEROGLYPH A295;Lo;0;L;;;;;N;;;;; 1454D;ANATOLIAN HIEROGLYPH A296;Lo;0;L;;;;;N;;;;; 1454E;ANATOLIAN HIEROGLYPH A297;Lo;0;L;;;;;N;;;;; 1454F;ANATOLIAN HIEROGLYPH A298;Lo;0;L;;;;;N;;;;; 14550;ANATOLIAN HIEROGLYPH A299;Lo;0;L;;;;;N;;;;; 14551;ANATOLIAN HIEROGLYPH A299A;Lo;0;L;;;;;N;;;;; 14552;ANATOLIAN HIEROGLYPH A300;Lo;0;L;;;;;N;;;;; 14553;ANATOLIAN HIEROGLYPH A301;Lo;0;L;;;;;N;;;;; 14554;ANATOLIAN HIEROGLYPH A302;Lo;0;L;;;;;N;;;;; 14555;ANATOLIAN HIEROGLYPH A303;Lo;0;L;;;;;N;;;;; 14556;ANATOLIAN HIEROGLYPH A304;Lo;0;L;;;;;N;;;;; 14557;ANATOLIAN HIEROGLYPH A305;Lo;0;L;;;;;N;;;;; 14558;ANATOLIAN HIEROGLYPH A306;Lo;0;L;;;;;N;;;;; 14559;ANATOLIAN HIEROGLYPH A307;Lo;0;L;;;;;N;;;;; 1455A;ANATOLIAN HIEROGLYPH A308;Lo;0;L;;;;;N;;;;; 1455B;ANATOLIAN HIEROGLYPH A309;Lo;0;L;;;;;N;;;;; 1455C;ANATOLIAN HIEROGLYPH A309A;Lo;0;L;;;;;N;;;;; 1455D;ANATOLIAN HIEROGLYPH A310;Lo;0;L;;;;;N;;;;; 1455E;ANATOLIAN HIEROGLYPH A311;Lo;0;L;;;;;N;;;;; 1455F;ANATOLIAN HIEROGLYPH A312;Lo;0;L;;;;;N;;;;; 14560;ANATOLIAN HIEROGLYPH A313;Lo;0;L;;;;;N;;;;; 14561;ANATOLIAN HIEROGLYPH A314;Lo;0;L;;;;;N;;;;; 14562;ANATOLIAN HIEROGLYPH A315;Lo;0;L;;;;;N;;;;; 14563;ANATOLIAN HIEROGLYPH A316;Lo;0;L;;;;;N;;;;; 14564;ANATOLIAN HIEROGLYPH A317;Lo;0;L;;;;;N;;;;; 14565;ANATOLIAN HIEROGLYPH A318;Lo;0;L;;;;;N;;;;; 14566;ANATOLIAN HIEROGLYPH A319;Lo;0;L;;;;;N;;;;; 14567;ANATOLIAN HIEROGLYPH A320;Lo;0;L;;;;;N;;;;; 14568;ANATOLIAN HIEROGLYPH A321;Lo;0;L;;;;;N;;;;; 14569;ANATOLIAN HIEROGLYPH A322;Lo;0;L;;;;;N;;;;; 1456A;ANATOLIAN HIEROGLYPH A323;Lo;0;L;;;;;N;;;;; 1456B;ANATOLIAN HIEROGLYPH A324;Lo;0;L;;;;;N;;;;; 1456C;ANATOLIAN HIEROGLYPH A325;Lo;0;L;;;;;N;;;;; 1456D;ANATOLIAN HIEROGLYPH A326;Lo;0;L;;;;;N;;;;; 1456E;ANATOLIAN HIEROGLYPH A327;Lo;0;L;;;;;N;;;;; 1456F;ANATOLIAN HIEROGLYPH A328;Lo;0;L;;;;;N;;;;; 14570;ANATOLIAN HIEROGLYPH A329;Lo;0;L;;;;;N;;;;; 14571;ANATOLIAN HIEROGLYPH A329A;Lo;0;L;;;;;N;;;;; 14572;ANATOLIAN HIEROGLYPH A330;Lo;0;L;;;;;N;;;;; 14573;ANATOLIAN HIEROGLYPH A331;Lo;0;L;;;;;N;;;;; 14574;ANATOLIAN HIEROGLYPH A332A;Lo;0;L;;;;;N;;;;; 14575;ANATOLIAN HIEROGLYPH A332B;Lo;0;L;;;;;N;;;;; 14576;ANATOLIAN HIEROGLYPH A332C;Lo;0;L;;;;;N;;;;; 14577;ANATOLIAN HIEROGLYPH A333;Lo;0;L;;;;;N;;;;; 14578;ANATOLIAN HIEROGLYPH A334;Lo;0;L;;;;;N;;;;; 14579;ANATOLIAN HIEROGLYPH A335;Lo;0;L;;;;;N;;;;; 1457A;ANATOLIAN HIEROGLYPH A336;Lo;0;L;;;;;N;;;;; 1457B;ANATOLIAN HIEROGLYPH A336A;Lo;0;L;;;;;N;;;;; 1457C;ANATOLIAN HIEROGLYPH A336B;Lo;0;L;;;;;N;;;;; 1457D;ANATOLIAN HIEROGLYPH A336C;Lo;0;L;;;;;N;;;;; 1457E;ANATOLIAN HIEROGLYPH A337;Lo;0;L;;;;;N;;;;; 1457F;ANATOLIAN HIEROGLYPH A338;Lo;0;L;;;;;N;;;;; 14580;ANATOLIAN HIEROGLYPH A339;Lo;0;L;;;;;N;;;;; 14581;ANATOLIAN HIEROGLYPH A340;Lo;0;L;;;;;N;;;;; 14582;ANATOLIAN HIEROGLYPH A341;Lo;0;L;;;;;N;;;;; 14583;ANATOLIAN HIEROGLYPH A342;Lo;0;L;;;;;N;;;;; 14584;ANATOLIAN HIEROGLYPH A343;Lo;0;L;;;;;N;;;;; 14585;ANATOLIAN HIEROGLYPH A344;Lo;0;L;;;;;N;;;;; 14586;ANATOLIAN HIEROGLYPH A345;Lo;0;L;;;;;N;;;;; 14587;ANATOLIAN HIEROGLYPH A346;Lo;0;L;;;;;N;;;;; 14588;ANATOLIAN HIEROGLYPH A347;Lo;0;L;;;;;N;;;;; 14589;ANATOLIAN HIEROGLYPH A348;Lo;0;L;;;;;N;;;;; 1458A;ANATOLIAN HIEROGLYPH A349;Lo;0;L;;;;;N;;;;; 1458B;ANATOLIAN HIEROGLYPH A350;Lo;0;L;;;;;N;;;;; 1458C;ANATOLIAN HIEROGLYPH A351;Lo;0;L;;;;;N;;;;; 1458D;ANATOLIAN HIEROGLYPH A352;Lo;0;L;;;;;N;;;;; 1458E;ANATOLIAN HIEROGLYPH A353;Lo;0;L;;;;;N;;;;; 1458F;ANATOLIAN HIEROGLYPH A354;Lo;0;L;;;;;N;;;;; 14590;ANATOLIAN HIEROGLYPH A355;Lo;0;L;;;;;N;;;;; 14591;ANATOLIAN HIEROGLYPH A356;Lo;0;L;;;;;N;;;;; 14592;ANATOLIAN HIEROGLYPH A357;Lo;0;L;;;;;N;;;;; 14593;ANATOLIAN HIEROGLYPH A358;Lo;0;L;;;;;N;;;;; 14594;ANATOLIAN HIEROGLYPH A359;Lo;0;L;;;;;N;;;;; 14595;ANATOLIAN HIEROGLYPH A359A;Lo;0;L;;;;;N;;;;; 14596;ANATOLIAN HIEROGLYPH A360;Lo;0;L;;;;;N;;;;; 14597;ANATOLIAN HIEROGLYPH A361;Lo;0;L;;;;;N;;;;; 14598;ANATOLIAN HIEROGLYPH A362;Lo;0;L;;;;;N;;;;; 14599;ANATOLIAN HIEROGLYPH A363;Lo;0;L;;;;;N;;;;; 1459A;ANATOLIAN HIEROGLYPH A364;Lo;0;L;;;;;N;;;;; 1459B;ANATOLIAN HIEROGLYPH A364A;Lo;0;L;;;;;N;;;;; 1459C;ANATOLIAN HIEROGLYPH A365;Lo;0;L;;;;;N;;;;; 1459D;ANATOLIAN HIEROGLYPH A366;Lo;0;L;;;;;N;;;;; 1459E;ANATOLIAN HIEROGLYPH A367;Lo;0;L;;;;;N;;;;; 1459F;ANATOLIAN HIEROGLYPH A368;Lo;0;L;;;;;N;;;;; 145A0;ANATOLIAN HIEROGLYPH A368A;Lo;0;L;;;;;N;;;;; 145A1;ANATOLIAN HIEROGLYPH A369;Lo;0;L;;;;;N;;;;; 145A2;ANATOLIAN HIEROGLYPH A370;Lo;0;L;;;;;N;;;;; 145A3;ANATOLIAN HIEROGLYPH A371;Lo;0;L;;;;;N;;;;; 145A4;ANATOLIAN HIEROGLYPH A371A;Lo;0;L;;;;;N;;;;; 145A5;ANATOLIAN HIEROGLYPH A372;Lo;0;L;;;;;N;;;;; 145A6;ANATOLIAN HIEROGLYPH A373;Lo;0;L;;;;;N;;;;; 145A7;ANATOLIAN HIEROGLYPH A374;Lo;0;L;;;;;N;;;;; 145A8;ANATOLIAN HIEROGLYPH A375;Lo;0;L;;;;;N;;;;; 145A9;ANATOLIAN HIEROGLYPH A376;Lo;0;L;;;;;N;;;;; 145AA;ANATOLIAN HIEROGLYPH A377;Lo;0;L;;;;;N;;;;; 145AB;ANATOLIAN HIEROGLYPH A378;Lo;0;L;;;;;N;;;;; 145AC;ANATOLIAN HIEROGLYPH A379;Lo;0;L;;;;;N;;;;; 145AD;ANATOLIAN HIEROGLYPH A380;Lo;0;L;;;;;N;;;;; 145AE;ANATOLIAN HIEROGLYPH A381;Lo;0;L;;;;;N;;;;; 145AF;ANATOLIAN HIEROGLYPH A381A;Lo;0;L;;;;;N;;;;; 145B0;ANATOLIAN HIEROGLYPH A382;Lo;0;L;;;;;N;;;;; 145B1;ANATOLIAN HIEROGLYPH A383 RA OR RI;Lo;0;L;;;;;N;;;;; 145B2;ANATOLIAN HIEROGLYPH A383A;Lo;0;L;;;;;N;;;;; 145B3;ANATOLIAN HIEROGLYPH A384;Lo;0;L;;;;;N;;;;; 145B4;ANATOLIAN HIEROGLYPH A385;Lo;0;L;;;;;N;;;;; 145B5;ANATOLIAN HIEROGLYPH A386;Lo;0;L;;;;;N;;;;; 145B6;ANATOLIAN HIEROGLYPH A386A;Lo;0;L;;;;;N;;;;; 145B7;ANATOLIAN HIEROGLYPH A387;Lo;0;L;;;;;N;;;;; 145B8;ANATOLIAN HIEROGLYPH A388;Lo;0;L;;;;;N;;;;; 145B9;ANATOLIAN HIEROGLYPH A389;Lo;0;L;;;;;N;;;;; 145BA;ANATOLIAN HIEROGLYPH A390;Lo;0;L;;;;;N;;;;; 145BB;ANATOLIAN HIEROGLYPH A391;Lo;0;L;;;;;N;;;;; 145BC;ANATOLIAN HIEROGLYPH A392;Lo;0;L;;;;;N;;;;; 145BD;ANATOLIAN HIEROGLYPH A393 EIGHT;Lo;0;L;;;;;N;;;;; 145BE;ANATOLIAN HIEROGLYPH A394;Lo;0;L;;;;;N;;;;; 145BF;ANATOLIAN HIEROGLYPH A395;Lo;0;L;;;;;N;;;;; 145C0;ANATOLIAN HIEROGLYPH A396;Lo;0;L;;;;;N;;;;; 145C1;ANATOLIAN HIEROGLYPH A397;Lo;0;L;;;;;N;;;;; 145C2;ANATOLIAN HIEROGLYPH A398;Lo;0;L;;;;;N;;;;; 145C3;ANATOLIAN HIEROGLYPH A399;Lo;0;L;;;;;N;;;;; 145C4;ANATOLIAN HIEROGLYPH A400;Lo;0;L;;;;;N;;;;; 145C5;ANATOLIAN HIEROGLYPH A401;Lo;0;L;;;;;N;;;;; 145C6;ANATOLIAN HIEROGLYPH A402;Lo;0;L;;;;;N;;;;; 145C7;ANATOLIAN HIEROGLYPH A403;Lo;0;L;;;;;N;;;;; 145C8;ANATOLIAN HIEROGLYPH A404;Lo;0;L;;;;;N;;;;; 145C9;ANATOLIAN HIEROGLYPH A405;Lo;0;L;;;;;N;;;;; 145CA;ANATOLIAN HIEROGLYPH A406;Lo;0;L;;;;;N;;;;; 145CB;ANATOLIAN HIEROGLYPH A407;Lo;0;L;;;;;N;;;;; 145CC;ANATOLIAN HIEROGLYPH A408;Lo;0;L;;;;;N;;;;; 145CD;ANATOLIAN HIEROGLYPH A409;Lo;0;L;;;;;N;;;;; 145CE;ANATOLIAN HIEROGLYPH A410 BEGIN LOGOGRAM MARK;Lo;0;L;;;;;N;;;;; 145CF;ANATOLIAN HIEROGLYPH A410A END LOGOGRAM MARK;Lo;0;L;;;;;N;;;;; 145D0;ANATOLIAN HIEROGLYPH A411;Lo;0;L;;;;;N;;;;; 145D1;ANATOLIAN HIEROGLYPH A412;Lo;0;L;;;;;N;;;;; 145D2;ANATOLIAN HIEROGLYPH A413;Lo;0;L;;;;;N;;;;; 145D3;ANATOLIAN HIEROGLYPH A414;Lo;0;L;;;;;N;;;;; 145D4;ANATOLIAN HIEROGLYPH A415;Lo;0;L;;;;;N;;;;; 145D5;ANATOLIAN HIEROGLYPH A416;Lo;0;L;;;;;N;;;;; 145D6;ANATOLIAN HIEROGLYPH A417;Lo;0;L;;;;;N;;;;; 145D7;ANATOLIAN HIEROGLYPH A418;Lo;0;L;;;;;N;;;;; 145D8;ANATOLIAN HIEROGLYPH A419;Lo;0;L;;;;;N;;;;; 145D9;ANATOLIAN HIEROGLYPH A420;Lo;0;L;;;;;N;;;;; 145DA;ANATOLIAN HIEROGLYPH A421;Lo;0;L;;;;;N;;;;; 145DB;ANATOLIAN HIEROGLYPH A422;Lo;0;L;;;;;N;;;;; 145DC;ANATOLIAN HIEROGLYPH A423;Lo;0;L;;;;;N;;;;; 145DD;ANATOLIAN HIEROGLYPH A424;Lo;0;L;;;;;N;;;;; 145DE;ANATOLIAN HIEROGLYPH A425;Lo;0;L;;;;;N;;;;; 145DF;ANATOLIAN HIEROGLYPH A426;Lo;0;L;;;;;N;;;;; 145E0;ANATOLIAN HIEROGLYPH A427;Lo;0;L;;;;;N;;;;; 145E1;ANATOLIAN HIEROGLYPH A428;Lo;0;L;;;;;N;;;;; 145E2;ANATOLIAN HIEROGLYPH A429;Lo;0;L;;;;;N;;;;; 145E3;ANATOLIAN HIEROGLYPH A430;Lo;0;L;;;;;N;;;;; 145E4;ANATOLIAN HIEROGLYPH A431;Lo;0;L;;;;;N;;;;; 145E5;ANATOLIAN HIEROGLYPH A432;Lo;0;L;;;;;N;;;;; 145E6;ANATOLIAN HIEROGLYPH A433;Lo;0;L;;;;;N;;;;; 145E7;ANATOLIAN HIEROGLYPH A434;Lo;0;L;;;;;N;;;;; 145E8;ANATOLIAN HIEROGLYPH A435;Lo;0;L;;;;;N;;;;; 145E9;ANATOLIAN HIEROGLYPH A436;Lo;0;L;;;;;N;;;;; 145EA;ANATOLIAN HIEROGLYPH A437;Lo;0;L;;;;;N;;;;; 145EB;ANATOLIAN HIEROGLYPH A438;Lo;0;L;;;;;N;;;;; 145EC;ANATOLIAN HIEROGLYPH A439;Lo;0;L;;;;;N;;;;; 145ED;ANATOLIAN HIEROGLYPH A440;Lo;0;L;;;;;N;;;;; 145EE;ANATOLIAN HIEROGLYPH A441;Lo;0;L;;;;;N;;;;; 145EF;ANATOLIAN HIEROGLYPH A442;Lo;0;L;;;;;N;;;;; 145F0;ANATOLIAN HIEROGLYPH A443;Lo;0;L;;;;;N;;;;; 145F1;ANATOLIAN HIEROGLYPH A444;Lo;0;L;;;;;N;;;;; 145F2;ANATOLIAN HIEROGLYPH A445;Lo;0;L;;;;;N;;;;; 145F3;ANATOLIAN HIEROGLYPH A446;Lo;0;L;;;;;N;;;;; 145F4;ANATOLIAN HIEROGLYPH A447;Lo;0;L;;;;;N;;;;; 145F5;ANATOLIAN HIEROGLYPH A448;Lo;0;L;;;;;N;;;;; 145F6;ANATOLIAN HIEROGLYPH A449;Lo;0;L;;;;;N;;;;; 145F7;ANATOLIAN HIEROGLYPH A450;Lo;0;L;;;;;N;;;;; 145F8;ANATOLIAN HIEROGLYPH A450A;Lo;0;L;;;;;N;;;;; 145F9;ANATOLIAN HIEROGLYPH A451;Lo;0;L;;;;;N;;;;; 145FA;ANATOLIAN HIEROGLYPH A452;Lo;0;L;;;;;N;;;;; 145FB;ANATOLIAN HIEROGLYPH A453;Lo;0;L;;;;;N;;;;; 145FC;ANATOLIAN HIEROGLYPH A454;Lo;0;L;;;;;N;;;;; 145FD;ANATOLIAN HIEROGLYPH A455;Lo;0;L;;;;;N;;;;; 145FE;ANATOLIAN HIEROGLYPH A456;Lo;0;L;;;;;N;;;;; 145FF;ANATOLIAN HIEROGLYPH A457;Lo;0;L;;;;;N;;;;; 14600;ANATOLIAN HIEROGLYPH A457A;Lo;0;L;;;;;N;;;;; 14601;ANATOLIAN HIEROGLYPH A458;Lo;0;L;;;;;N;;;;; 14602;ANATOLIAN HIEROGLYPH A459;Lo;0;L;;;;;N;;;;; 14603;ANATOLIAN HIEROGLYPH A460;Lo;0;L;;;;;N;;;;; 14604;ANATOLIAN HIEROGLYPH A461;Lo;0;L;;;;;N;;;;; 14605;ANATOLIAN HIEROGLYPH A462;Lo;0;L;;;;;N;;;;; 14606;ANATOLIAN HIEROGLYPH A463;Lo;0;L;;;;;N;;;;; 14607;ANATOLIAN HIEROGLYPH A464;Lo;0;L;;;;;N;;;;; 14608;ANATOLIAN HIEROGLYPH A465;Lo;0;L;;;;;N;;;;; 14609;ANATOLIAN HIEROGLYPH A466;Lo;0;L;;;;;N;;;;; 1460A;ANATOLIAN HIEROGLYPH A467;Lo;0;L;;;;;N;;;;; 1460B;ANATOLIAN HIEROGLYPH A468;Lo;0;L;;;;;N;;;;; 1460C;ANATOLIAN HIEROGLYPH A469;Lo;0;L;;;;;N;;;;; 1460D;ANATOLIAN HIEROGLYPH A470;Lo;0;L;;;;;N;;;;; 1460E;ANATOLIAN HIEROGLYPH A471;Lo;0;L;;;;;N;;;;; 1460F;ANATOLIAN HIEROGLYPH A472;Lo;0;L;;;;;N;;;;; 14610;ANATOLIAN HIEROGLYPH A473;Lo;0;L;;;;;N;;;;; 14611;ANATOLIAN HIEROGLYPH A474;Lo;0;L;;;;;N;;;;; 14612;ANATOLIAN HIEROGLYPH A475;Lo;0;L;;;;;N;;;;; 14613;ANATOLIAN HIEROGLYPH A476;Lo;0;L;;;;;N;;;;; 14614;ANATOLIAN HIEROGLYPH A477;Lo;0;L;;;;;N;;;;; 14615;ANATOLIAN HIEROGLYPH A478;Lo;0;L;;;;;N;;;;; 14616;ANATOLIAN HIEROGLYPH A479;Lo;0;L;;;;;N;;;;; 14617;ANATOLIAN HIEROGLYPH A480;Lo;0;L;;;;;N;;;;; 14618;ANATOLIAN HIEROGLYPH A481;Lo;0;L;;;;;N;;;;; 14619;ANATOLIAN HIEROGLYPH A482;Lo;0;L;;;;;N;;;;; 1461A;ANATOLIAN HIEROGLYPH A483;Lo;0;L;;;;;N;;;;; 1461B;ANATOLIAN HIEROGLYPH A484;Lo;0;L;;;;;N;;;;; 1461C;ANATOLIAN HIEROGLYPH A485;Lo;0;L;;;;;N;;;;; 1461D;ANATOLIAN HIEROGLYPH A486;Lo;0;L;;;;;N;;;;; 1461E;ANATOLIAN HIEROGLYPH A487;Lo;0;L;;;;;N;;;;; 1461F;ANATOLIAN HIEROGLYPH A488;Lo;0;L;;;;;N;;;;; 14620;ANATOLIAN HIEROGLYPH A489;Lo;0;L;;;;;N;;;;; 14621;ANATOLIAN HIEROGLYPH A490;Lo;0;L;;;;;N;;;;; 14622;ANATOLIAN HIEROGLYPH A491;Lo;0;L;;;;;N;;;;; 14623;ANATOLIAN HIEROGLYPH A492;Lo;0;L;;;;;N;;;;; 14624;ANATOLIAN HIEROGLYPH A493;Lo;0;L;;;;;N;;;;; 14625;ANATOLIAN HIEROGLYPH A494;Lo;0;L;;;;;N;;;;; 14626;ANATOLIAN HIEROGLYPH A495;Lo;0;L;;;;;N;;;;; 14627;ANATOLIAN HIEROGLYPH A496;Lo;0;L;;;;;N;;;;; 14628;ANATOLIAN HIEROGLYPH A497;Lo;0;L;;;;;N;;;;; 14629;ANATOLIAN HIEROGLYPH A501;Lo;0;L;;;;;N;;;;; 1462A;ANATOLIAN HIEROGLYPH A502;Lo;0;L;;;;;N;;;;; 1462B;ANATOLIAN HIEROGLYPH A503;Lo;0;L;;;;;N;;;;; 1462C;ANATOLIAN HIEROGLYPH A504;Lo;0;L;;;;;N;;;;; 1462D;ANATOLIAN HIEROGLYPH A505;Lo;0;L;;;;;N;;;;; 1462E;ANATOLIAN HIEROGLYPH A506;Lo;0;L;;;;;N;;;;; 1462F;ANATOLIAN HIEROGLYPH A507;Lo;0;L;;;;;N;;;;; 14630;ANATOLIAN HIEROGLYPH A508;Lo;0;L;;;;;N;;;;; 14631;ANATOLIAN HIEROGLYPH A509;Lo;0;L;;;;;N;;;;; 14632;ANATOLIAN HIEROGLYPH A510;Lo;0;L;;;;;N;;;;; 14633;ANATOLIAN HIEROGLYPH A511;Lo;0;L;;;;;N;;;;; 14634;ANATOLIAN HIEROGLYPH A512;Lo;0;L;;;;;N;;;;; 14635;ANATOLIAN HIEROGLYPH A513;Lo;0;L;;;;;N;;;;; 14636;ANATOLIAN HIEROGLYPH A514;Lo;0;L;;;;;N;;;;; 14637;ANATOLIAN HIEROGLYPH A515;Lo;0;L;;;;;N;;;;; 14638;ANATOLIAN HIEROGLYPH A516;Lo;0;L;;;;;N;;;;; 14639;ANATOLIAN HIEROGLYPH A517;Lo;0;L;;;;;N;;;;; 1463A;ANATOLIAN HIEROGLYPH A518;Lo;0;L;;;;;N;;;;; 1463B;ANATOLIAN HIEROGLYPH A519;Lo;0;L;;;;;N;;;;; 1463C;ANATOLIAN HIEROGLYPH A520;Lo;0;L;;;;;N;;;;; 1463D;ANATOLIAN HIEROGLYPH A521;Lo;0;L;;;;;N;;;;; 1463E;ANATOLIAN HIEROGLYPH A522;Lo;0;L;;;;;N;;;;; 1463F;ANATOLIAN HIEROGLYPH A523;Lo;0;L;;;;;N;;;;; 14640;ANATOLIAN HIEROGLYPH A524;Lo;0;L;;;;;N;;;;; 14641;ANATOLIAN HIEROGLYPH A525;Lo;0;L;;;;;N;;;;; 14642;ANATOLIAN HIEROGLYPH A526;Lo;0;L;;;;;N;;;;; 14643;ANATOLIAN HIEROGLYPH A527;Lo;0;L;;;;;N;;;;; 14644;ANATOLIAN HIEROGLYPH A528;Lo;0;L;;;;;N;;;;; 14645;ANATOLIAN HIEROGLYPH A529;Lo;0;L;;;;;N;;;;; 14646;ANATOLIAN HIEROGLYPH A530;Lo;0;L;;;;;N;;;;; 16800;BAMUM LETTER PHASE-A NGKUE MFON;Lo;0;L;;;;;N;;;;; 16801;BAMUM LETTER PHASE-A GBIEE FON;Lo;0;L;;;;;N;;;;; 16802;BAMUM LETTER PHASE-A PON MFON PIPAEMGBIEE;Lo;0;L;;;;;N;;;;; 16803;BAMUM LETTER PHASE-A PON MFON PIPAEMBA;Lo;0;L;;;;;N;;;;; 16804;BAMUM LETTER PHASE-A NAA MFON;Lo;0;L;;;;;N;;;;; 16805;BAMUM LETTER PHASE-A SHUENSHUET;Lo;0;L;;;;;N;;;;; 16806;BAMUM LETTER PHASE-A TITA MFON;Lo;0;L;;;;;N;;;;; 16807;BAMUM LETTER PHASE-A NZA MFON;Lo;0;L;;;;;N;;;;; 16808;BAMUM LETTER PHASE-A SHINDA PA NJI;Lo;0;L;;;;;N;;;;; 16809;BAMUM LETTER PHASE-A PON PA NJI PIPAEMGBIEE;Lo;0;L;;;;;N;;;;; 1680A;BAMUM LETTER PHASE-A PON PA NJI PIPAEMBA;Lo;0;L;;;;;N;;;;; 1680B;BAMUM LETTER PHASE-A MAEMBGBIEE;Lo;0;L;;;;;N;;;;; 1680C;BAMUM LETTER PHASE-A TU MAEMBA;Lo;0;L;;;;;N;;;;; 1680D;BAMUM LETTER PHASE-A NGANGU;Lo;0;L;;;;;N;;;;; 1680E;BAMUM LETTER PHASE-A MAEMVEUX;Lo;0;L;;;;;N;;;;; 1680F;BAMUM LETTER PHASE-A MANSUAE;Lo;0;L;;;;;N;;;;; 16810;BAMUM LETTER PHASE-A MVEUAENGAM;Lo;0;L;;;;;N;;;;; 16811;BAMUM LETTER PHASE-A SEUNYAM;Lo;0;L;;;;;N;;;;; 16812;BAMUM LETTER PHASE-A NTOQPEN;Lo;0;L;;;;;N;;;;; 16813;BAMUM LETTER PHASE-A KEUKEUTNDA;Lo;0;L;;;;;N;;;;; 16814;BAMUM LETTER PHASE-A NKINDI;Lo;0;L;;;;;N;;;;; 16815;BAMUM LETTER PHASE-A SUU;Lo;0;L;;;;;N;;;;; 16816;BAMUM LETTER PHASE-A NGKUENZEUM;Lo;0;L;;;;;N;;;;; 16817;BAMUM LETTER PHASE-A LAPAQ;Lo;0;L;;;;;N;;;;; 16818;BAMUM LETTER PHASE-A LET KUT;Lo;0;L;;;;;N;;;;; 16819;BAMUM LETTER PHASE-A NTAP MFAA;Lo;0;L;;;;;N;;;;; 1681A;BAMUM LETTER PHASE-A MAEKEUP;Lo;0;L;;;;;N;;;;; 1681B;BAMUM LETTER PHASE-A PASHAE;Lo;0;L;;;;;N;;;;; 1681C;BAMUM LETTER PHASE-A GHEUAERAE;Lo;0;L;;;;;N;;;;; 1681D;BAMUM LETTER PHASE-A PAMSHAE;Lo;0;L;;;;;N;;;;; 1681E;BAMUM LETTER PHASE-A MON NGGEUAET;Lo;0;L;;;;;N;;;;; 1681F;BAMUM LETTER PHASE-A NZUN MEUT;Lo;0;L;;;;;N;;;;; 16820;BAMUM LETTER PHASE-A U YUQ NAE;Lo;0;L;;;;;N;;;;; 16821;BAMUM LETTER PHASE-A GHEUAEGHEUAE;Lo;0;L;;;;;N;;;;; 16822;BAMUM LETTER PHASE-A NTAP NTAA;Lo;0;L;;;;;N;;;;; 16823;BAMUM LETTER PHASE-A SISA;Lo;0;L;;;;;N;;;;; 16824;BAMUM LETTER PHASE-A MGBASA;Lo;0;L;;;;;N;;;;; 16825;BAMUM LETTER PHASE-A MEUNJOMNDEUQ;Lo;0;L;;;;;N;;;;; 16826;BAMUM LETTER PHASE-A MOOMPUQ;Lo;0;L;;;;;N;;;;; 16827;BAMUM LETTER PHASE-A KAFA;Lo;0;L;;;;;N;;;;; 16828;BAMUM LETTER PHASE-A PA LEERAEWA;Lo;0;L;;;;;N;;;;; 16829;BAMUM LETTER PHASE-A NDA LEERAEWA;Lo;0;L;;;;;N;;;;; 1682A;BAMUM LETTER PHASE-A PET;Lo;0;L;;;;;N;;;;; 1682B;BAMUM LETTER PHASE-A MAEMKPEN;Lo;0;L;;;;;N;;;;; 1682C;BAMUM LETTER PHASE-A NIKA;Lo;0;L;;;;;N;;;;; 1682D;BAMUM LETTER PHASE-A PUP;Lo;0;L;;;;;N;;;;; 1682E;BAMUM LETTER PHASE-A TUAEP;Lo;0;L;;;;;N;;;;; 1682F;BAMUM LETTER PHASE-A LUAEP;Lo;0;L;;;;;N;;;;; 16830;BAMUM LETTER PHASE-A SONJAM;Lo;0;L;;;;;N;;;;; 16831;BAMUM LETTER PHASE-A TEUTEUWEN;Lo;0;L;;;;;N;;;;; 16832;BAMUM LETTER PHASE-A MAENYI;Lo;0;L;;;;;N;;;;; 16833;BAMUM LETTER PHASE-A KET;Lo;0;L;;;;;N;;;;; 16834;BAMUM LETTER PHASE-A NDAANGGEUAET;Lo;0;L;;;;;N;;;;; 16835;BAMUM LETTER PHASE-A KUOQ;Lo;0;L;;;;;N;;;;; 16836;BAMUM LETTER PHASE-A MOOMEUT;Lo;0;L;;;;;N;;;;; 16837;BAMUM LETTER PHASE-A SHUM;Lo;0;L;;;;;N;;;;; 16838;BAMUM LETTER PHASE-A LOMMAE;Lo;0;L;;;;;N;;;;; 16839;BAMUM LETTER PHASE-A FIRI;Lo;0;L;;;;;N;;;;; 1683A;BAMUM LETTER PHASE-A ROM;Lo;0;L;;;;;N;;;;; 1683B;BAMUM LETTER PHASE-A KPOQ;Lo;0;L;;;;;N;;;;; 1683C;BAMUM LETTER PHASE-A SOQ;Lo;0;L;;;;;N;;;;; 1683D;BAMUM LETTER PHASE-A MAP PIEET;Lo;0;L;;;;;N;;;;; 1683E;BAMUM LETTER PHASE-A SHIRAE;Lo;0;L;;;;;N;;;;; 1683F;BAMUM LETTER PHASE-A NTAP;Lo;0;L;;;;;N;;;;; 16840;BAMUM LETTER PHASE-A SHOQ NSHUT YUM;Lo;0;L;;;;;N;;;;; 16841;BAMUM LETTER PHASE-A NYIT MONGKEUAEQ;Lo;0;L;;;;;N;;;;; 16842;BAMUM LETTER PHASE-A PAARAE;Lo;0;L;;;;;N;;;;; 16843;BAMUM LETTER PHASE-A NKAARAE;Lo;0;L;;;;;N;;;;; 16844;BAMUM LETTER PHASE-A UNKNOWN;Lo;0;L;;;;;N;;;;; 16845;BAMUM LETTER PHASE-A NGGEN;Lo;0;L;;;;;N;;;;; 16846;BAMUM LETTER PHASE-A MAESI;Lo;0;L;;;;;N;;;;; 16847;BAMUM LETTER PHASE-A NJAM;Lo;0;L;;;;;N;;;;; 16848;BAMUM LETTER PHASE-A MBANYI;Lo;0;L;;;;;N;;;;; 16849;BAMUM LETTER PHASE-A NYET;Lo;0;L;;;;;N;;;;; 1684A;BAMUM LETTER PHASE-A TEUAEN;Lo;0;L;;;;;N;;;;; 1684B;BAMUM LETTER PHASE-A SOT;Lo;0;L;;;;;N;;;;; 1684C;BAMUM LETTER PHASE-A PAAM;Lo;0;L;;;;;N;;;;; 1684D;BAMUM LETTER PHASE-A NSHIEE;Lo;0;L;;;;;N;;;;; 1684E;BAMUM LETTER PHASE-A MAEM;Lo;0;L;;;;;N;;;;; 1684F;BAMUM LETTER PHASE-A NYI;Lo;0;L;;;;;N;;;;; 16850;BAMUM LETTER PHASE-A KAQ;Lo;0;L;;;;;N;;;;; 16851;BAMUM LETTER PHASE-A NSHA;Lo;0;L;;;;;N;;;;; 16852;BAMUM LETTER PHASE-A VEE;Lo;0;L;;;;;N;;;;; 16853;BAMUM LETTER PHASE-A LU;Lo;0;L;;;;;N;;;;; 16854;BAMUM LETTER PHASE-A NEN;Lo;0;L;;;;;N;;;;; 16855;BAMUM LETTER PHASE-A NAQ;Lo;0;L;;;;;N;;;;; 16856;BAMUM LETTER PHASE-A MBAQ;Lo;0;L;;;;;N;;;;; 16857;BAMUM LETTER PHASE-B NSHUET;Lo;0;L;;;;;N;;;;; 16858;BAMUM LETTER PHASE-B TU MAEMGBIEE;Lo;0;L;;;;;N;;;;; 16859;BAMUM LETTER PHASE-B SIEE;Lo;0;L;;;;;N;;;;; 1685A;BAMUM LETTER PHASE-B SET TU;Lo;0;L;;;;;N;;;;; 1685B;BAMUM LETTER PHASE-B LOM NTEUM;Lo;0;L;;;;;N;;;;; 1685C;BAMUM LETTER PHASE-B MBA MAELEE;Lo;0;L;;;;;N;;;;; 1685D;BAMUM LETTER PHASE-B KIEEM;Lo;0;L;;;;;N;;;;; 1685E;BAMUM LETTER PHASE-B YEURAE;Lo;0;L;;;;;N;;;;; 1685F;BAMUM LETTER PHASE-B MBAARAE;Lo;0;L;;;;;N;;;;; 16860;BAMUM LETTER PHASE-B KAM;Lo;0;L;;;;;N;;;;; 16861;BAMUM LETTER PHASE-B PEESHI;Lo;0;L;;;;;N;;;;; 16862;BAMUM LETTER PHASE-B YAFU LEERAEWA;Lo;0;L;;;;;N;;;;; 16863;BAMUM LETTER PHASE-B LAM NSHUT NYAM;Lo;0;L;;;;;N;;;;; 16864;BAMUM LETTER PHASE-B NTIEE SHEUOQ;Lo;0;L;;;;;N;;;;; 16865;BAMUM LETTER PHASE-B NDU NJAA;Lo;0;L;;;;;N;;;;; 16866;BAMUM LETTER PHASE-B GHEUGHEUAEM;Lo;0;L;;;;;N;;;;; 16867;BAMUM LETTER PHASE-B PIT;Lo;0;L;;;;;N;;;;; 16868;BAMUM LETTER PHASE-B TU NSIEE;Lo;0;L;;;;;N;;;;; 16869;BAMUM LETTER PHASE-B SHET NJAQ;Lo;0;L;;;;;N;;;;; 1686A;BAMUM LETTER PHASE-B SHEUAEQTU;Lo;0;L;;;;;N;;;;; 1686B;BAMUM LETTER PHASE-B MFON TEUAEQ;Lo;0;L;;;;;N;;;;; 1686C;BAMUM LETTER PHASE-B MBIT MBAAKET;Lo;0;L;;;;;N;;;;; 1686D;BAMUM LETTER PHASE-B NYI NTEUM;Lo;0;L;;;;;N;;;;; 1686E;BAMUM LETTER PHASE-B KEUPUQ;Lo;0;L;;;;;N;;;;; 1686F;BAMUM LETTER PHASE-B GHEUGHEN;Lo;0;L;;;;;N;;;;; 16870;BAMUM LETTER PHASE-B KEUYEUX;Lo;0;L;;;;;N;;;;; 16871;BAMUM LETTER PHASE-B LAANAE;Lo;0;L;;;;;N;;;;; 16872;BAMUM LETTER PHASE-B PARUM;Lo;0;L;;;;;N;;;;; 16873;BAMUM LETTER PHASE-B VEUM;Lo;0;L;;;;;N;;;;; 16874;BAMUM LETTER PHASE-B NGKINDI MVOP;Lo;0;L;;;;;N;;;;; 16875;BAMUM LETTER PHASE-B NGGEU MBU;Lo;0;L;;;;;N;;;;; 16876;BAMUM LETTER PHASE-B WUAET;Lo;0;L;;;;;N;;;;; 16877;BAMUM LETTER PHASE-B SAKEUAE;Lo;0;L;;;;;N;;;;; 16878;BAMUM LETTER PHASE-B TAAM;Lo;0;L;;;;;N;;;;; 16879;BAMUM LETTER PHASE-B MEUQ;Lo;0;L;;;;;N;;;;; 1687A;BAMUM LETTER PHASE-B NGGUOQ;Lo;0;L;;;;;N;;;;; 1687B;BAMUM LETTER PHASE-B NGGUOQ LARGE;Lo;0;L;;;;;N;;;;; 1687C;BAMUM LETTER PHASE-B MFIYAQ;Lo;0;L;;;;;N;;;;; 1687D;BAMUM LETTER PHASE-B SUE;Lo;0;L;;;;;N;;;;; 1687E;BAMUM LETTER PHASE-B MBEURI;Lo;0;L;;;;;N;;;;; 1687F;BAMUM LETTER PHASE-B MONTIEEN;Lo;0;L;;;;;N;;;;; 16880;BAMUM LETTER PHASE-B NYAEMAE;Lo;0;L;;;;;N;;;;; 16881;BAMUM LETTER PHASE-B PUNGAAM;Lo;0;L;;;;;N;;;;; 16882;BAMUM LETTER PHASE-B MEUT NGGEET;Lo;0;L;;;;;N;;;;; 16883;BAMUM LETTER PHASE-B FEUX;Lo;0;L;;;;;N;;;;; 16884;BAMUM LETTER PHASE-B MBUOQ;Lo;0;L;;;;;N;;;;; 16885;BAMUM LETTER PHASE-B FEE;Lo;0;L;;;;;N;;;;; 16886;BAMUM LETTER PHASE-B KEUAEM;Lo;0;L;;;;;N;;;;; 16887;BAMUM LETTER PHASE-B MA NJEUAENA;Lo;0;L;;;;;N;;;;; 16888;BAMUM LETTER PHASE-B MA NJUQA;Lo;0;L;;;;;N;;;;; 16889;BAMUM LETTER PHASE-B LET;Lo;0;L;;;;;N;;;;; 1688A;BAMUM LETTER PHASE-B NGGAAM;Lo;0;L;;;;;N;;;;; 1688B;BAMUM LETTER PHASE-B NSEN;Lo;0;L;;;;;N;;;;; 1688C;BAMUM LETTER PHASE-B MA;Lo;0;L;;;;;N;;;;; 1688D;BAMUM LETTER PHASE-B KIQ;Lo;0;L;;;;;N;;;;; 1688E;BAMUM LETTER PHASE-B NGOM;Lo;0;L;;;;;N;;;;; 1688F;BAMUM LETTER PHASE-C NGKUE MAEMBA;Lo;0;L;;;;;N;;;;; 16890;BAMUM LETTER PHASE-C NZA;Lo;0;L;;;;;N;;;;; 16891;BAMUM LETTER PHASE-C YUM;Lo;0;L;;;;;N;;;;; 16892;BAMUM LETTER PHASE-C WANGKUOQ;Lo;0;L;;;;;N;;;;; 16893;BAMUM LETTER PHASE-C NGGEN;Lo;0;L;;;;;N;;;;; 16894;BAMUM LETTER PHASE-C NDEUAEREE;Lo;0;L;;;;;N;;;;; 16895;BAMUM LETTER PHASE-C NGKAQ;Lo;0;L;;;;;N;;;;; 16896;BAMUM LETTER PHASE-C GHARAE;Lo;0;L;;;;;N;;;;; 16897;BAMUM LETTER PHASE-C MBEEKEET;Lo;0;L;;;;;N;;;;; 16898;BAMUM LETTER PHASE-C GBAYI;Lo;0;L;;;;;N;;;;; 16899;BAMUM LETTER PHASE-C NYIR MKPARAQ MEUN;Lo;0;L;;;;;N;;;;; 1689A;BAMUM LETTER PHASE-C NTU MBIT;Lo;0;L;;;;;N;;;;; 1689B;BAMUM LETTER PHASE-C MBEUM;Lo;0;L;;;;;N;;;;; 1689C;BAMUM LETTER PHASE-C PIRIEEN;Lo;0;L;;;;;N;;;;; 1689D;BAMUM LETTER PHASE-C NDOMBU;Lo;0;L;;;;;N;;;;; 1689E;BAMUM LETTER PHASE-C MBAA CABBAGE-TREE;Lo;0;L;;;;;N;;;;; 1689F;BAMUM LETTER PHASE-C KEUSHEUAEP;Lo;0;L;;;;;N;;;;; 168A0;BAMUM LETTER PHASE-C GHAP;Lo;0;L;;;;;N;;;;; 168A1;BAMUM LETTER PHASE-C KEUKAQ;Lo;0;L;;;;;N;;;;; 168A2;BAMUM LETTER PHASE-C YU MUOMAE;Lo;0;L;;;;;N;;;;; 168A3;BAMUM LETTER PHASE-C NZEUM;Lo;0;L;;;;;N;;;;; 168A4;BAMUM LETTER PHASE-C MBUE;Lo;0;L;;;;;N;;;;; 168A5;BAMUM LETTER PHASE-C NSEUAEN;Lo;0;L;;;;;N;;;;; 168A6;BAMUM LETTER PHASE-C MBIT;Lo;0;L;;;;;N;;;;; 168A7;BAMUM LETTER PHASE-C YEUQ;Lo;0;L;;;;;N;;;;; 168A8;BAMUM LETTER PHASE-C KPARAQ;Lo;0;L;;;;;N;;;;; 168A9;BAMUM LETTER PHASE-C KAA;Lo;0;L;;;;;N;;;;; 168AA;BAMUM LETTER PHASE-C SEUX;Lo;0;L;;;;;N;;;;; 168AB;BAMUM LETTER PHASE-C NDIDA;Lo;0;L;;;;;N;;;;; 168AC;BAMUM LETTER PHASE-C TAASHAE;Lo;0;L;;;;;N;;;;; 168AD;BAMUM LETTER PHASE-C NJUEQ;Lo;0;L;;;;;N;;;;; 168AE;BAMUM LETTER PHASE-C TITA YUE;Lo;0;L;;;;;N;;;;; 168AF;BAMUM LETTER PHASE-C SUAET;Lo;0;L;;;;;N;;;;; 168B0;BAMUM LETTER PHASE-C NGGUAEN NYAM;Lo;0;L;;;;;N;;;;; 168B1;BAMUM LETTER PHASE-C VEUX;Lo;0;L;;;;;N;;;;; 168B2;BAMUM LETTER PHASE-C NANSANAQ;Lo;0;L;;;;;N;;;;; 168B3;BAMUM LETTER PHASE-C MA KEUAERI;Lo;0;L;;;;;N;;;;; 168B4;BAMUM LETTER PHASE-C NTAA;Lo;0;L;;;;;N;;;;; 168B5;BAMUM LETTER PHASE-C NGGUON;Lo;0;L;;;;;N;;;;; 168B6;BAMUM LETTER PHASE-C LAP;Lo;0;L;;;;;N;;;;; 168B7;BAMUM LETTER PHASE-C MBIRIEEN;Lo;0;L;;;;;N;;;;; 168B8;BAMUM LETTER PHASE-C MGBASAQ;Lo;0;L;;;;;N;;;;; 168B9;BAMUM LETTER PHASE-C NTEUNGBA;Lo;0;L;;;;;N;;;;; 168BA;BAMUM LETTER PHASE-C TEUTEUX;Lo;0;L;;;;;N;;;;; 168BB;BAMUM LETTER PHASE-C NGGUM;Lo;0;L;;;;;N;;;;; 168BC;BAMUM LETTER PHASE-C FUE;Lo;0;L;;;;;N;;;;; 168BD;BAMUM LETTER PHASE-C NDEUT;Lo;0;L;;;;;N;;;;; 168BE;BAMUM LETTER PHASE-C NSA;Lo;0;L;;;;;N;;;;; 168BF;BAMUM LETTER PHASE-C NSHAQ;Lo;0;L;;;;;N;;;;; 168C0;BAMUM LETTER PHASE-C BUNG;Lo;0;L;;;;;N;;;;; 168C1;BAMUM LETTER PHASE-C VEUAEPEN;Lo;0;L;;;;;N;;;;; 168C2;BAMUM LETTER PHASE-C MBERAE;Lo;0;L;;;;;N;;;;; 168C3;BAMUM LETTER PHASE-C RU;Lo;0;L;;;;;N;;;;; 168C4;BAMUM LETTER PHASE-C NJAEM;Lo;0;L;;;;;N;;;;; 168C5;BAMUM LETTER PHASE-C LAM;Lo;0;L;;;;;N;;;;; 168C6;BAMUM LETTER PHASE-C TITUAEP;Lo;0;L;;;;;N;;;;; 168C7;BAMUM LETTER PHASE-C NSUOT NGOM;Lo;0;L;;;;;N;;;;; 168C8;BAMUM LETTER PHASE-C NJEEEE;Lo;0;L;;;;;N;;;;; 168C9;BAMUM LETTER PHASE-C KET;Lo;0;L;;;;;N;;;;; 168CA;BAMUM LETTER PHASE-C NGGU;Lo;0;L;;;;;N;;;;; 168CB;BAMUM LETTER PHASE-C MAESI;Lo;0;L;;;;;N;;;;; 168CC;BAMUM LETTER PHASE-C MBUAEM;Lo;0;L;;;;;N;;;;; 168CD;BAMUM LETTER PHASE-C LU;Lo;0;L;;;;;N;;;;; 168CE;BAMUM LETTER PHASE-C KUT;Lo;0;L;;;;;N;;;;; 168CF;BAMUM LETTER PHASE-C NJAM;Lo;0;L;;;;;N;;;;; 168D0;BAMUM LETTER PHASE-C NGOM;Lo;0;L;;;;;N;;;;; 168D1;BAMUM LETTER PHASE-C WUP;Lo;0;L;;;;;N;;;;; 168D2;BAMUM LETTER PHASE-C NGGUEET;Lo;0;L;;;;;N;;;;; 168D3;BAMUM LETTER PHASE-C NSOM;Lo;0;L;;;;;N;;;;; 168D4;BAMUM LETTER PHASE-C NTEN;Lo;0;L;;;;;N;;;;; 168D5;BAMUM LETTER PHASE-C KUOP NKAARAE;Lo;0;L;;;;;N;;;;; 168D6;BAMUM LETTER PHASE-C NSUN;Lo;0;L;;;;;N;;;;; 168D7;BAMUM LETTER PHASE-C NDAM;Lo;0;L;;;;;N;;;;; 168D8;BAMUM LETTER PHASE-C MA NSIEE;Lo;0;L;;;;;N;;;;; 168D9;BAMUM LETTER PHASE-C YAA;Lo;0;L;;;;;N;;;;; 168DA;BAMUM LETTER PHASE-C NDAP;Lo;0;L;;;;;N;;;;; 168DB;BAMUM LETTER PHASE-C SHUEQ;Lo;0;L;;;;;N;;;;; 168DC;BAMUM LETTER PHASE-C SETFON;Lo;0;L;;;;;N;;;;; 168DD;BAMUM LETTER PHASE-C MBI;Lo;0;L;;;;;N;;;;; 168DE;BAMUM LETTER PHASE-C MAEMBA;Lo;0;L;;;;;N;;;;; 168DF;BAMUM LETTER PHASE-C MBANYI;Lo;0;L;;;;;N;;;;; 168E0;BAMUM LETTER PHASE-C KEUSEUX;Lo;0;L;;;;;N;;;;; 168E1;BAMUM LETTER PHASE-C MBEUX;Lo;0;L;;;;;N;;;;; 168E2;BAMUM LETTER PHASE-C KEUM;Lo;0;L;;;;;N;;;;; 168E3;BAMUM LETTER PHASE-C MBAA PICKET;Lo;0;L;;;;;N;;;;; 168E4;BAMUM LETTER PHASE-C YUWOQ;Lo;0;L;;;;;N;;;;; 168E5;BAMUM LETTER PHASE-C NJEUX;Lo;0;L;;;;;N;;;;; 168E6;BAMUM LETTER PHASE-C MIEE;Lo;0;L;;;;;N;;;;; 168E7;BAMUM LETTER PHASE-C MUAE;Lo;0;L;;;;;N;;;;; 168E8;BAMUM LETTER PHASE-C SHIQ;Lo;0;L;;;;;N;;;;; 168E9;BAMUM LETTER PHASE-C KEN LAW;Lo;0;L;;;;;N;;;;; 168EA;BAMUM LETTER PHASE-C KEN FATIGUE;Lo;0;L;;;;;N;;;;; 168EB;BAMUM LETTER PHASE-C NGAQ;Lo;0;L;;;;;N;;;;; 168EC;BAMUM LETTER PHASE-C NAQ;Lo;0;L;;;;;N;;;;; 168ED;BAMUM LETTER PHASE-C LIQ;Lo;0;L;;;;;N;;;;; 168EE;BAMUM LETTER PHASE-C PIN;Lo;0;L;;;;;N;;;;; 168EF;BAMUM LETTER PHASE-C PEN;Lo;0;L;;;;;N;;;;; 168F0;BAMUM LETTER PHASE-C TET;Lo;0;L;;;;;N;;;;; 168F1;BAMUM LETTER PHASE-D MBUO;Lo;0;L;;;;;N;;;;; 168F2;BAMUM LETTER PHASE-D WAP;Lo;0;L;;;;;N;;;;; 168F3;BAMUM LETTER PHASE-D NJI;Lo;0;L;;;;;N;;;;; 168F4;BAMUM LETTER PHASE-D MFON;Lo;0;L;;;;;N;;;;; 168F5;BAMUM LETTER PHASE-D NJIEE;Lo;0;L;;;;;N;;;;; 168F6;BAMUM LETTER PHASE-D LIEE;Lo;0;L;;;;;N;;;;; 168F7;BAMUM LETTER PHASE-D NJEUT;Lo;0;L;;;;;N;;;;; 168F8;BAMUM LETTER PHASE-D NSHEE;Lo;0;L;;;;;N;;;;; 168F9;BAMUM LETTER PHASE-D NGGAAMAE;Lo;0;L;;;;;N;;;;; 168FA;BAMUM LETTER PHASE-D NYAM;Lo;0;L;;;;;N;;;;; 168FB;BAMUM LETTER PHASE-D WUAEN;Lo;0;L;;;;;N;;;;; 168FC;BAMUM LETTER PHASE-D NGKUN;Lo;0;L;;;;;N;;;;; 168FD;BAMUM LETTER PHASE-D SHEE;Lo;0;L;;;;;N;;;;; 168FE;BAMUM LETTER PHASE-D NGKAP;Lo;0;L;;;;;N;;;;; 168FF;BAMUM LETTER PHASE-D KEUAETMEUN;Lo;0;L;;;;;N;;;;; 16900;BAMUM LETTER PHASE-D TEUT;Lo;0;L;;;;;N;;;;; 16901;BAMUM LETTER PHASE-D SHEUAE;Lo;0;L;;;;;N;;;;; 16902;BAMUM LETTER PHASE-D NJAP;Lo;0;L;;;;;N;;;;; 16903;BAMUM LETTER PHASE-D SUE;Lo;0;L;;;;;N;;;;; 16904;BAMUM LETTER PHASE-D KET;Lo;0;L;;;;;N;;;;; 16905;BAMUM LETTER PHASE-D YAEMMAE;Lo;0;L;;;;;N;;;;; 16906;BAMUM LETTER PHASE-D KUOM;Lo;0;L;;;;;N;;;;; 16907;BAMUM LETTER PHASE-D SAP;Lo;0;L;;;;;N;;;;; 16908;BAMUM LETTER PHASE-D MFEUT;Lo;0;L;;;;;N;;;;; 16909;BAMUM LETTER PHASE-D NDEUX;Lo;0;L;;;;;N;;;;; 1690A;BAMUM LETTER PHASE-D MALEERI;Lo;0;L;;;;;N;;;;; 1690B;BAMUM LETTER PHASE-D MEUT;Lo;0;L;;;;;N;;;;; 1690C;BAMUM LETTER PHASE-D SEUAEQ;Lo;0;L;;;;;N;;;;; 1690D;BAMUM LETTER PHASE-D YEN;Lo;0;L;;;;;N;;;;; 1690E;BAMUM LETTER PHASE-D NJEUAEM;Lo;0;L;;;;;N;;;;; 1690F;BAMUM LETTER PHASE-D KEUOT MBUAE;Lo;0;L;;;;;N;;;;; 16910;BAMUM LETTER PHASE-D NGKEURI;Lo;0;L;;;;;N;;;;; 16911;BAMUM LETTER PHASE-D TU;Lo;0;L;;;;;N;;;;; 16912;BAMUM LETTER PHASE-D GHAA;Lo;0;L;;;;;N;;;;; 16913;BAMUM LETTER PHASE-D NGKYEE;Lo;0;L;;;;;N;;;;; 16914;BAMUM LETTER PHASE-D FEUFEUAET;Lo;0;L;;;;;N;;;;; 16915;BAMUM LETTER PHASE-D NDEE;Lo;0;L;;;;;N;;;;; 16916;BAMUM LETTER PHASE-D MGBOFUM;Lo;0;L;;;;;N;;;;; 16917;BAMUM LETTER PHASE-D LEUAEP;Lo;0;L;;;;;N;;;;; 16918;BAMUM LETTER PHASE-D NDON;Lo;0;L;;;;;N;;;;; 16919;BAMUM LETTER PHASE-D MONI;Lo;0;L;;;;;N;;;;; 1691A;BAMUM LETTER PHASE-D MGBEUN;Lo;0;L;;;;;N;;;;; 1691B;BAMUM LETTER PHASE-D PUUT;Lo;0;L;;;;;N;;;;; 1691C;BAMUM LETTER PHASE-D MGBIEE;Lo;0;L;;;;;N;;;;; 1691D;BAMUM LETTER PHASE-D MFO;Lo;0;L;;;;;N;;;;; 1691E;BAMUM LETTER PHASE-D LUM;Lo;0;L;;;;;N;;;;; 1691F;BAMUM LETTER PHASE-D NSIEEP;Lo;0;L;;;;;N;;;;; 16920;BAMUM LETTER PHASE-D MBAA;Lo;0;L;;;;;N;;;;; 16921;BAMUM LETTER PHASE-D KWAET;Lo;0;L;;;;;N;;;;; 16922;BAMUM LETTER PHASE-D NYET;Lo;0;L;;;;;N;;;;; 16923;BAMUM LETTER PHASE-D TEUAEN;Lo;0;L;;;;;N;;;;; 16924;BAMUM LETTER PHASE-D SOT;Lo;0;L;;;;;N;;;;; 16925;BAMUM LETTER PHASE-D YUWOQ;Lo;0;L;;;;;N;;;;; 16926;BAMUM LETTER PHASE-D KEUM;Lo;0;L;;;;;N;;;;; 16927;BAMUM LETTER PHASE-D RAEM;Lo;0;L;;;;;N;;;;; 16928;BAMUM LETTER PHASE-D TEEEE;Lo;0;L;;;;;N;;;;; 16929;BAMUM LETTER PHASE-D NGKEUAEQ;Lo;0;L;;;;;N;;;;; 1692A;BAMUM LETTER PHASE-D MFEUAE;Lo;0;L;;;;;N;;;;; 1692B;BAMUM LETTER PHASE-D NSIEET;Lo;0;L;;;;;N;;;;; 1692C;BAMUM LETTER PHASE-D KEUP;Lo;0;L;;;;;N;;;;; 1692D;BAMUM LETTER PHASE-D PIP;Lo;0;L;;;;;N;;;;; 1692E;BAMUM LETTER PHASE-D PEUTAE;Lo;0;L;;;;;N;;;;; 1692F;BAMUM LETTER PHASE-D NYUE;Lo;0;L;;;;;N;;;;; 16930;BAMUM LETTER PHASE-D LET;Lo;0;L;;;;;N;;;;; 16931;BAMUM LETTER PHASE-D NGGAAM;Lo;0;L;;;;;N;;;;; 16932;BAMUM LETTER PHASE-D MFIEE;Lo;0;L;;;;;N;;;;; 16933;BAMUM LETTER PHASE-D NGGWAEN;Lo;0;L;;;;;N;;;;; 16934;BAMUM LETTER PHASE-D YUOM;Lo;0;L;;;;;N;;;;; 16935;BAMUM LETTER PHASE-D PAP;Lo;0;L;;;;;N;;;;; 16936;BAMUM LETTER PHASE-D YUOP;Lo;0;L;;;;;N;;;;; 16937;BAMUM LETTER PHASE-D NDAM;Lo;0;L;;;;;N;;;;; 16938;BAMUM LETTER PHASE-D NTEUM;Lo;0;L;;;;;N;;;;; 16939;BAMUM LETTER PHASE-D SUAE;Lo;0;L;;;;;N;;;;; 1693A;BAMUM LETTER PHASE-D KUN;Lo;0;L;;;;;N;;;;; 1693B;BAMUM LETTER PHASE-D NGGEUX;Lo;0;L;;;;;N;;;;; 1693C;BAMUM LETTER PHASE-D NGKIEE;Lo;0;L;;;;;N;;;;; 1693D;BAMUM LETTER PHASE-D TUOT;Lo;0;L;;;;;N;;;;; 1693E;BAMUM LETTER PHASE-D MEUN;Lo;0;L;;;;;N;;;;; 1693F;BAMUM LETTER PHASE-D KUQ;Lo;0;L;;;;;N;;;;; 16940;BAMUM LETTER PHASE-D NSUM;Lo;0;L;;;;;N;;;;; 16941;BAMUM LETTER PHASE-D TEUN;Lo;0;L;;;;;N;;;;; 16942;BAMUM LETTER PHASE-D MAENJET;Lo;0;L;;;;;N;;;;; 16943;BAMUM LETTER PHASE-D NGGAP;Lo;0;L;;;;;N;;;;; 16944;BAMUM LETTER PHASE-D LEUM;Lo;0;L;;;;;N;;;;; 16945;BAMUM LETTER PHASE-D NGGUOM;Lo;0;L;;;;;N;;;;; 16946;BAMUM LETTER PHASE-D NSHUT;Lo;0;L;;;;;N;;;;; 16947;BAMUM LETTER PHASE-D NJUEQ;Lo;0;L;;;;;N;;;;; 16948;BAMUM LETTER PHASE-D GHEUAE;Lo;0;L;;;;;N;;;;; 16949;BAMUM LETTER PHASE-D KU;Lo;0;L;;;;;N;;;;; 1694A;BAMUM LETTER PHASE-D REN OLD;Lo;0;L;;;;;N;;;;; 1694B;BAMUM LETTER PHASE-D TAE;Lo;0;L;;;;;N;;;;; 1694C;BAMUM LETTER PHASE-D TOQ;Lo;0;L;;;;;N;;;;; 1694D;BAMUM LETTER PHASE-D NYI;Lo;0;L;;;;;N;;;;; 1694E;BAMUM LETTER PHASE-D RII;Lo;0;L;;;;;N;;;;; 1694F;BAMUM LETTER PHASE-D LEEEE;Lo;0;L;;;;;N;;;;; 16950;BAMUM LETTER PHASE-D MEEEE;Lo;0;L;;;;;N;;;;; 16951;BAMUM LETTER PHASE-D M;Lo;0;L;;;;;N;;;;; 16952;BAMUM LETTER PHASE-D SUU;Lo;0;L;;;;;N;;;;; 16953;BAMUM LETTER PHASE-D MU;Lo;0;L;;;;;N;;;;; 16954;BAMUM LETTER PHASE-D SHII;Lo;0;L;;;;;N;;;;; 16955;BAMUM LETTER PHASE-D SHEUX;Lo;0;L;;;;;N;;;;; 16956;BAMUM LETTER PHASE-D KYEE;Lo;0;L;;;;;N;;;;; 16957;BAMUM LETTER PHASE-D NU;Lo;0;L;;;;;N;;;;; 16958;BAMUM LETTER PHASE-D SHU;Lo;0;L;;;;;N;;;;; 16959;BAMUM LETTER PHASE-D NTEE;Lo;0;L;;;;;N;;;;; 1695A;BAMUM LETTER PHASE-D PEE;Lo;0;L;;;;;N;;;;; 1695B;BAMUM LETTER PHASE-D NI;Lo;0;L;;;;;N;;;;; 1695C;BAMUM LETTER PHASE-D SHOQ;Lo;0;L;;;;;N;;;;; 1695D;BAMUM LETTER PHASE-D PUQ;Lo;0;L;;;;;N;;;;; 1695E;BAMUM LETTER PHASE-D MVOP;Lo;0;L;;;;;N;;;;; 1695F;BAMUM LETTER PHASE-D LOQ;Lo;0;L;;;;;N;;;;; 16960;BAMUM LETTER PHASE-D REN MUCH;Lo;0;L;;;;;N;;;;; 16961;BAMUM LETTER PHASE-D TI;Lo;0;L;;;;;N;;;;; 16962;BAMUM LETTER PHASE-D NTUU;Lo;0;L;;;;;N;;;;; 16963;BAMUM LETTER PHASE-D MBAA SEVEN;Lo;0;L;;;;;N;;;;; 16964;BAMUM LETTER PHASE-D SAQ;Lo;0;L;;;;;N;;;;; 16965;BAMUM LETTER PHASE-D FAA;Lo;0;L;;;;;N;;;;; 16966;BAMUM LETTER PHASE-E NDAP;Lo;0;L;;;;;N;;;;; 16967;BAMUM LETTER PHASE-E TOON;Lo;0;L;;;;;N;;;;; 16968;BAMUM LETTER PHASE-E MBEUM;Lo;0;L;;;;;N;;;;; 16969;BAMUM LETTER PHASE-E LAP;Lo;0;L;;;;;N;;;;; 1696A;BAMUM LETTER PHASE-E VOM;Lo;0;L;;;;;N;;;;; 1696B;BAMUM LETTER PHASE-E LOON;Lo;0;L;;;;;N;;;;; 1696C;BAMUM LETTER PHASE-E PAA;Lo;0;L;;;;;N;;;;; 1696D;BAMUM LETTER PHASE-E SOM;Lo;0;L;;;;;N;;;;; 1696E;BAMUM LETTER PHASE-E RAQ;Lo;0;L;;;;;N;;;;; 1696F;BAMUM LETTER PHASE-E NSHUOP;Lo;0;L;;;;;N;;;;; 16970;BAMUM LETTER PHASE-E NDUN;Lo;0;L;;;;;N;;;;; 16971;BAMUM LETTER PHASE-E PUAE;Lo;0;L;;;;;N;;;;; 16972;BAMUM LETTER PHASE-E TAM;Lo;0;L;;;;;N;;;;; 16973;BAMUM LETTER PHASE-E NGKA;Lo;0;L;;;;;N;;;;; 16974;BAMUM LETTER PHASE-E KPEUX;Lo;0;L;;;;;N;;;;; 16975;BAMUM LETTER PHASE-E WUO;Lo;0;L;;;;;N;;;;; 16976;BAMUM LETTER PHASE-E SEE;Lo;0;L;;;;;N;;;;; 16977;BAMUM LETTER PHASE-E NGGEUAET;Lo;0;L;;;;;N;;;;; 16978;BAMUM LETTER PHASE-E PAAM;Lo;0;L;;;;;N;;;;; 16979;BAMUM LETTER PHASE-E TOO;Lo;0;L;;;;;N;;;;; 1697A;BAMUM LETTER PHASE-E KUOP;Lo;0;L;;;;;N;;;;; 1697B;BAMUM LETTER PHASE-E LOM;Lo;0;L;;;;;N;;;;; 1697C;BAMUM LETTER PHASE-E NSHIEE;Lo;0;L;;;;;N;;;;; 1697D;BAMUM LETTER PHASE-E NGOP;Lo;0;L;;;;;N;;;;; 1697E;BAMUM LETTER PHASE-E MAEM;Lo;0;L;;;;;N;;;;; 1697F;BAMUM LETTER PHASE-E NGKEUX;Lo;0;L;;;;;N;;;;; 16980;BAMUM LETTER PHASE-E NGOQ;Lo;0;L;;;;;N;;;;; 16981;BAMUM LETTER PHASE-E NSHUE;Lo;0;L;;;;;N;;;;; 16982;BAMUM LETTER PHASE-E RIMGBA;Lo;0;L;;;;;N;;;;; 16983;BAMUM LETTER PHASE-E NJEUX;Lo;0;L;;;;;N;;;;; 16984;BAMUM LETTER PHASE-E PEEM;Lo;0;L;;;;;N;;;;; 16985;BAMUM LETTER PHASE-E SAA;Lo;0;L;;;;;N;;;;; 16986;BAMUM LETTER PHASE-E NGGURAE;Lo;0;L;;;;;N;;;;; 16987;BAMUM LETTER PHASE-E MGBA;Lo;0;L;;;;;N;;;;; 16988;BAMUM LETTER PHASE-E GHEUX;Lo;0;L;;;;;N;;;;; 16989;BAMUM LETTER PHASE-E NGKEUAEM;Lo;0;L;;;;;N;;;;; 1698A;BAMUM LETTER PHASE-E NJAEMLI;Lo;0;L;;;;;N;;;;; 1698B;BAMUM LETTER PHASE-E MAP;Lo;0;L;;;;;N;;;;; 1698C;BAMUM LETTER PHASE-E LOOT;Lo;0;L;;;;;N;;;;; 1698D;BAMUM LETTER PHASE-E NGGEEEE;Lo;0;L;;;;;N;;;;; 1698E;BAMUM LETTER PHASE-E NDIQ;Lo;0;L;;;;;N;;;;; 1698F;BAMUM LETTER PHASE-E TAEN NTEUM;Lo;0;L;;;;;N;;;;; 16990;BAMUM LETTER PHASE-E SET;Lo;0;L;;;;;N;;;;; 16991;BAMUM LETTER PHASE-E PUM;Lo;0;L;;;;;N;;;;; 16992;BAMUM LETTER PHASE-E NDAA SOFTNESS;Lo;0;L;;;;;N;;;;; 16993;BAMUM LETTER PHASE-E NGGUAESHAE NYAM;Lo;0;L;;;;;N;;;;; 16994;BAMUM LETTER PHASE-E YIEE;Lo;0;L;;;;;N;;;;; 16995;BAMUM LETTER PHASE-E GHEUN;Lo;0;L;;;;;N;;;;; 16996;BAMUM LETTER PHASE-E TUAE;Lo;0;L;;;;;N;;;;; 16997;BAMUM LETTER PHASE-E YEUAE;Lo;0;L;;;;;N;;;;; 16998;BAMUM LETTER PHASE-E PO;Lo;0;L;;;;;N;;;;; 16999;BAMUM LETTER PHASE-E TUMAE;Lo;0;L;;;;;N;;;;; 1699A;BAMUM LETTER PHASE-E KEUAE;Lo;0;L;;;;;N;;;;; 1699B;BAMUM LETTER PHASE-E SUAEN;Lo;0;L;;;;;N;;;;; 1699C;BAMUM LETTER PHASE-E TEUAEQ;Lo;0;L;;;;;N;;;;; 1699D;BAMUM LETTER PHASE-E VEUAE;Lo;0;L;;;;;N;;;;; 1699E;BAMUM LETTER PHASE-E WEUX;Lo;0;L;;;;;N;;;;; 1699F;BAMUM LETTER PHASE-E LAAM;Lo;0;L;;;;;N;;;;; 169A0;BAMUM LETTER PHASE-E PU;Lo;0;L;;;;;N;;;;; 169A1;BAMUM LETTER PHASE-E TAAQ;Lo;0;L;;;;;N;;;;; 169A2;BAMUM LETTER PHASE-E GHAAMAE;Lo;0;L;;;;;N;;;;; 169A3;BAMUM LETTER PHASE-E NGEUREUT;Lo;0;L;;;;;N;;;;; 169A4;BAMUM LETTER PHASE-E SHEUAEQ;Lo;0;L;;;;;N;;;;; 169A5;BAMUM LETTER PHASE-E MGBEN;Lo;0;L;;;;;N;;;;; 169A6;BAMUM LETTER PHASE-E MBEE;Lo;0;L;;;;;N;;;;; 169A7;BAMUM LETTER PHASE-E NZAQ;Lo;0;L;;;;;N;;;;; 169A8;BAMUM LETTER PHASE-E NKOM;Lo;0;L;;;;;N;;;;; 169A9;BAMUM LETTER PHASE-E GBET;Lo;0;L;;;;;N;;;;; 169AA;BAMUM LETTER PHASE-E TUM;Lo;0;L;;;;;N;;;;; 169AB;BAMUM LETTER PHASE-E KUET;Lo;0;L;;;;;N;;;;; 169AC;BAMUM LETTER PHASE-E YAP;Lo;0;L;;;;;N;;;;; 169AD;BAMUM LETTER PHASE-E NYI CLEAVER;Lo;0;L;;;;;N;;;;; 169AE;BAMUM LETTER PHASE-E YIT;Lo;0;L;;;;;N;;;;; 169AF;BAMUM LETTER PHASE-E MFEUQ;Lo;0;L;;;;;N;;;;; 169B0;BAMUM LETTER PHASE-E NDIAQ;Lo;0;L;;;;;N;;;;; 169B1;BAMUM LETTER PHASE-E PIEEQ;Lo;0;L;;;;;N;;;;; 169B2;BAMUM LETTER PHASE-E YUEQ;Lo;0;L;;;;;N;;;;; 169B3;BAMUM LETTER PHASE-E LEUAEM;Lo;0;L;;;;;N;;;;; 169B4;BAMUM LETTER PHASE-E FUE;Lo;0;L;;;;;N;;;;; 169B5;BAMUM LETTER PHASE-E GBEUX;Lo;0;L;;;;;N;;;;; 169B6;BAMUM LETTER PHASE-E NGKUP;Lo;0;L;;;;;N;;;;; 169B7;BAMUM LETTER PHASE-E KET;Lo;0;L;;;;;N;;;;; 169B8;BAMUM LETTER PHASE-E MAE;Lo;0;L;;;;;N;;;;; 169B9;BAMUM LETTER PHASE-E NGKAAMI;Lo;0;L;;;;;N;;;;; 169BA;BAMUM LETTER PHASE-E GHET;Lo;0;L;;;;;N;;;;; 169BB;BAMUM LETTER PHASE-E FA;Lo;0;L;;;;;N;;;;; 169BC;BAMUM LETTER PHASE-E NTUM;Lo;0;L;;;;;N;;;;; 169BD;BAMUM LETTER PHASE-E PEUT;Lo;0;L;;;;;N;;;;; 169BE;BAMUM LETTER PHASE-E YEUM;Lo;0;L;;;;;N;;;;; 169BF;BAMUM LETTER PHASE-E NGGEUAE;Lo;0;L;;;;;N;;;;; 169C0;BAMUM LETTER PHASE-E NYI BETWEEN;Lo;0;L;;;;;N;;;;; 169C1;BAMUM LETTER PHASE-E NZUQ;Lo;0;L;;;;;N;;;;; 169C2;BAMUM LETTER PHASE-E POON;Lo;0;L;;;;;N;;;;; 169C3;BAMUM LETTER PHASE-E MIEE;Lo;0;L;;;;;N;;;;; 169C4;BAMUM LETTER PHASE-E FUET;Lo;0;L;;;;;N;;;;; 169C5;BAMUM LETTER PHASE-E NAE;Lo;0;L;;;;;N;;;;; 169C6;BAMUM LETTER PHASE-E MUAE;Lo;0;L;;;;;N;;;;; 169C7;BAMUM LETTER PHASE-E GHEUAE;Lo;0;L;;;;;N;;;;; 169C8;BAMUM LETTER PHASE-E FU I;Lo;0;L;;;;;N;;;;; 169C9;BAMUM LETTER PHASE-E MVI;Lo;0;L;;;;;N;;;;; 169CA;BAMUM LETTER PHASE-E PUAQ;Lo;0;L;;;;;N;;;;; 169CB;BAMUM LETTER PHASE-E NGKUM;Lo;0;L;;;;;N;;;;; 169CC;BAMUM LETTER PHASE-E KUT;Lo;0;L;;;;;N;;;;; 169CD;BAMUM LETTER PHASE-E PIET;Lo;0;L;;;;;N;;;;; 169CE;BAMUM LETTER PHASE-E NTAP;Lo;0;L;;;;;N;;;;; 169CF;BAMUM LETTER PHASE-E YEUAET;Lo;0;L;;;;;N;;;;; 169D0;BAMUM LETTER PHASE-E NGGUP;Lo;0;L;;;;;N;;;;; 169D1;BAMUM LETTER PHASE-E PA PEOPLE;Lo;0;L;;;;;N;;;;; 169D2;BAMUM LETTER PHASE-E FU CALL;Lo;0;L;;;;;N;;;;; 169D3;BAMUM LETTER PHASE-E FOM;Lo;0;L;;;;;N;;;;; 169D4;BAMUM LETTER PHASE-E NJEE;Lo;0;L;;;;;N;;;;; 169D5;BAMUM LETTER PHASE-E A;Lo;0;L;;;;;N;;;;; 169D6;BAMUM LETTER PHASE-E TOQ;Lo;0;L;;;;;N;;;;; 169D7;BAMUM LETTER PHASE-E O;Lo;0;L;;;;;N;;;;; 169D8;BAMUM LETTER PHASE-E I;Lo;0;L;;;;;N;;;;; 169D9;BAMUM LETTER PHASE-E LAQ;Lo;0;L;;;;;N;;;;; 169DA;BAMUM LETTER PHASE-E PA PLURAL;Lo;0;L;;;;;N;;;;; 169DB;BAMUM LETTER PHASE-E TAA;Lo;0;L;;;;;N;;;;; 169DC;BAMUM LETTER PHASE-E TAQ;Lo;0;L;;;;;N;;;;; 169DD;BAMUM LETTER PHASE-E NDAA MY HOUSE;Lo;0;L;;;;;N;;;;; 169DE;BAMUM LETTER PHASE-E SHIQ;Lo;0;L;;;;;N;;;;; 169DF;BAMUM LETTER PHASE-E YEUX;Lo;0;L;;;;;N;;;;; 169E0;BAMUM LETTER PHASE-E NGUAE;Lo;0;L;;;;;N;;;;; 169E1;BAMUM LETTER PHASE-E YUAEN;Lo;0;L;;;;;N;;;;; 169E2;BAMUM LETTER PHASE-E YOQ SWIMMING;Lo;0;L;;;;;N;;;;; 169E3;BAMUM LETTER PHASE-E YOQ COVER;Lo;0;L;;;;;N;;;;; 169E4;BAMUM LETTER PHASE-E YUQ;Lo;0;L;;;;;N;;;;; 169E5;BAMUM LETTER PHASE-E YUN;Lo;0;L;;;;;N;;;;; 169E6;BAMUM LETTER PHASE-E KEUX;Lo;0;L;;;;;N;;;;; 169E7;BAMUM LETTER PHASE-E PEUX;Lo;0;L;;;;;N;;;;; 169E8;BAMUM LETTER PHASE-E NJEE EPOCH;Lo;0;L;;;;;N;;;;; 169E9;BAMUM LETTER PHASE-E PUE;Lo;0;L;;;;;N;;;;; 169EA;BAMUM LETTER PHASE-E WUE;Lo;0;L;;;;;N;;;;; 169EB;BAMUM LETTER PHASE-E FEE;Lo;0;L;;;;;N;;;;; 169EC;BAMUM LETTER PHASE-E VEE;Lo;0;L;;;;;N;;;;; 169ED;BAMUM LETTER PHASE-E LU;Lo;0;L;;;;;N;;;;; 169EE;BAMUM LETTER PHASE-E MI;Lo;0;L;;;;;N;;;;; 169EF;BAMUM LETTER PHASE-E REUX;Lo;0;L;;;;;N;;;;; 169F0;BAMUM LETTER PHASE-E RAE;Lo;0;L;;;;;N;;;;; 169F1;BAMUM LETTER PHASE-E NGUAET;Lo;0;L;;;;;N;;;;; 169F2;BAMUM LETTER PHASE-E NGA;Lo;0;L;;;;;N;;;;; 169F3;BAMUM LETTER PHASE-E SHO;Lo;0;L;;;;;N;;;;; 169F4;BAMUM LETTER PHASE-E SHOQ;Lo;0;L;;;;;N;;;;; 169F5;BAMUM LETTER PHASE-E FU REMEDY;Lo;0;L;;;;;N;;;;; 169F6;BAMUM LETTER PHASE-E NA;Lo;0;L;;;;;N;;;;; 169F7;BAMUM LETTER PHASE-E PI;Lo;0;L;;;;;N;;;;; 169F8;BAMUM LETTER PHASE-E LOQ;Lo;0;L;;;;;N;;;;; 169F9;BAMUM LETTER PHASE-E KO;Lo;0;L;;;;;N;;;;; 169FA;BAMUM LETTER PHASE-E MEN;Lo;0;L;;;;;N;;;;; 169FB;BAMUM LETTER PHASE-E MA;Lo;0;L;;;;;N;;;;; 169FC;BAMUM LETTER PHASE-E MAQ;Lo;0;L;;;;;N;;;;; 169FD;BAMUM LETTER PHASE-E TEU;Lo;0;L;;;;;N;;;;; 169FE;BAMUM LETTER PHASE-E KI;Lo;0;L;;;;;N;;;;; 169FF;BAMUM LETTER PHASE-E MON;Lo;0;L;;;;;N;;;;; 16A00;BAMUM LETTER PHASE-E TEN;Lo;0;L;;;;;N;;;;; 16A01;BAMUM LETTER PHASE-E FAQ;Lo;0;L;;;;;N;;;;; 16A02;BAMUM LETTER PHASE-E GHOM;Lo;0;L;;;;;N;;;;; 16A03;BAMUM LETTER PHASE-F KA;Lo;0;L;;;;;N;;;;; 16A04;BAMUM LETTER PHASE-F U;Lo;0;L;;;;;N;;;;; 16A05;BAMUM LETTER PHASE-F KU;Lo;0;L;;;;;N;;;;; 16A06;BAMUM LETTER PHASE-F EE;Lo;0;L;;;;;N;;;;; 16A07;BAMUM LETTER PHASE-F REE;Lo;0;L;;;;;N;;;;; 16A08;BAMUM LETTER PHASE-F TAE;Lo;0;L;;;;;N;;;;; 16A09;BAMUM LETTER PHASE-F NYI;Lo;0;L;;;;;N;;;;; 16A0A;BAMUM LETTER PHASE-F LA;Lo;0;L;;;;;N;;;;; 16A0B;BAMUM LETTER PHASE-F RII;Lo;0;L;;;;;N;;;;; 16A0C;BAMUM LETTER PHASE-F RIEE;Lo;0;L;;;;;N;;;;; 16A0D;BAMUM LETTER PHASE-F MEEEE;Lo;0;L;;;;;N;;;;; 16A0E;BAMUM LETTER PHASE-F TAA;Lo;0;L;;;;;N;;;;; 16A0F;BAMUM LETTER PHASE-F NDAA;Lo;0;L;;;;;N;;;;; 16A10;BAMUM LETTER PHASE-F NJAEM;Lo;0;L;;;;;N;;;;; 16A11;BAMUM LETTER PHASE-F M;Lo;0;L;;;;;N;;;;; 16A12;BAMUM LETTER PHASE-F SUU;Lo;0;L;;;;;N;;;;; 16A13;BAMUM LETTER PHASE-F SHII;Lo;0;L;;;;;N;;;;; 16A14;BAMUM LETTER PHASE-F SI;Lo;0;L;;;;;N;;;;; 16A15;BAMUM LETTER PHASE-F SEUX;Lo;0;L;;;;;N;;;;; 16A16;BAMUM LETTER PHASE-F KYEE;Lo;0;L;;;;;N;;;;; 16A17;BAMUM LETTER PHASE-F KET;Lo;0;L;;;;;N;;;;; 16A18;BAMUM LETTER PHASE-F NUAE;Lo;0;L;;;;;N;;;;; 16A19;BAMUM LETTER PHASE-F NU;Lo;0;L;;;;;N;;;;; 16A1A;BAMUM LETTER PHASE-F NJUAE;Lo;0;L;;;;;N;;;;; 16A1B;BAMUM LETTER PHASE-F YOQ;Lo;0;L;;;;;N;;;;; 16A1C;BAMUM LETTER PHASE-F SHU;Lo;0;L;;;;;N;;;;; 16A1D;BAMUM LETTER PHASE-F YA;Lo;0;L;;;;;N;;;;; 16A1E;BAMUM LETTER PHASE-F NSHA;Lo;0;L;;;;;N;;;;; 16A1F;BAMUM LETTER PHASE-F PEUX;Lo;0;L;;;;;N;;;;; 16A20;BAMUM LETTER PHASE-F NTEE;Lo;0;L;;;;;N;;;;; 16A21;BAMUM LETTER PHASE-F WUE;Lo;0;L;;;;;N;;;;; 16A22;BAMUM LETTER PHASE-F PEE;Lo;0;L;;;;;N;;;;; 16A23;BAMUM LETTER PHASE-F RU;Lo;0;L;;;;;N;;;;; 16A24;BAMUM LETTER PHASE-F NI;Lo;0;L;;;;;N;;;;; 16A25;BAMUM LETTER PHASE-F REUX;Lo;0;L;;;;;N;;;;; 16A26;BAMUM LETTER PHASE-F KEN;Lo;0;L;;;;;N;;;;; 16A27;BAMUM LETTER PHASE-F NGKWAEN;Lo;0;L;;;;;N;;;;; 16A28;BAMUM LETTER PHASE-F NGGA;Lo;0;L;;;;;N;;;;; 16A29;BAMUM LETTER PHASE-F SHO;Lo;0;L;;;;;N;;;;; 16A2A;BAMUM LETTER PHASE-F PUAE;Lo;0;L;;;;;N;;;;; 16A2B;BAMUM LETTER PHASE-F FOM;Lo;0;L;;;;;N;;;;; 16A2C;BAMUM LETTER PHASE-F WA;Lo;0;L;;;;;N;;;;; 16A2D;BAMUM LETTER PHASE-F LI;Lo;0;L;;;;;N;;;;; 16A2E;BAMUM LETTER PHASE-F LOQ;Lo;0;L;;;;;N;;;;; 16A2F;BAMUM LETTER PHASE-F KO;Lo;0;L;;;;;N;;;;; 16A30;BAMUM LETTER PHASE-F MBEN;Lo;0;L;;;;;N;;;;; 16A31;BAMUM LETTER PHASE-F REN;Lo;0;L;;;;;N;;;;; 16A32;BAMUM LETTER PHASE-F MA;Lo;0;L;;;;;N;;;;; 16A33;BAMUM LETTER PHASE-F MO;Lo;0;L;;;;;N;;;;; 16A34;BAMUM LETTER PHASE-F MBAA;Lo;0;L;;;;;N;;;;; 16A35;BAMUM LETTER PHASE-F TET;Lo;0;L;;;;;N;;;;; 16A36;BAMUM LETTER PHASE-F KPA;Lo;0;L;;;;;N;;;;; 16A37;BAMUM LETTER PHASE-F SAMBA;Lo;0;L;;;;;N;;;;; 16A38;BAMUM LETTER PHASE-F VUEQ;Lo;0;L;;;;;N;;;;; 16A40;MRO LETTER TA;Lo;0;L;;;;;N;;;;; 16A41;MRO LETTER NGI;Lo;0;L;;;;;N;;;;; 16A42;MRO LETTER YO;Lo;0;L;;;;;N;;;;; 16A43;MRO LETTER MIM;Lo;0;L;;;;;N;;;;; 16A44;MRO LETTER BA;Lo;0;L;;;;;N;;;;; 16A45;MRO LETTER DA;Lo;0;L;;;;;N;;;;; 16A46;MRO LETTER A;Lo;0;L;;;;;N;;;;; 16A47;MRO LETTER PHI;Lo;0;L;;;;;N;;;;; 16A48;MRO LETTER KHAI;Lo;0;L;;;;;N;;;;; 16A49;MRO LETTER HAO;Lo;0;L;;;;;N;;;;; 16A4A;MRO LETTER DAI;Lo;0;L;;;;;N;;;;; 16A4B;MRO LETTER CHU;Lo;0;L;;;;;N;;;;; 16A4C;MRO LETTER KEAAE;Lo;0;L;;;;;N;;;;; 16A4D;MRO LETTER OL;Lo;0;L;;;;;N;;;;; 16A4E;MRO LETTER MAEM;Lo;0;L;;;;;N;;;;; 16A4F;MRO LETTER NIN;Lo;0;L;;;;;N;;;;; 16A50;MRO LETTER PA;Lo;0;L;;;;;N;;;;; 16A51;MRO LETTER OO;Lo;0;L;;;;;N;;;;; 16A52;MRO LETTER O;Lo;0;L;;;;;N;;;;; 16A53;MRO LETTER RO;Lo;0;L;;;;;N;;;;; 16A54;MRO LETTER SHI;Lo;0;L;;;;;N;;;;; 16A55;MRO LETTER THEA;Lo;0;L;;;;;N;;;;; 16A56;MRO LETTER EA;Lo;0;L;;;;;N;;;;; 16A57;MRO LETTER WA;Lo;0;L;;;;;N;;;;; 16A58;MRO LETTER E;Lo;0;L;;;;;N;;;;; 16A59;MRO LETTER KO;Lo;0;L;;;;;N;;;;; 16A5A;MRO LETTER LAN;Lo;0;L;;;;;N;;;;; 16A5B;MRO LETTER LA;Lo;0;L;;;;;N;;;;; 16A5C;MRO LETTER HAI;Lo;0;L;;;;;N;;;;; 16A5D;MRO LETTER RI;Lo;0;L;;;;;N;;;;; 16A5E;MRO LETTER TEK;Lo;0;L;;;;;N;;;;; 16A60;MRO DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 16A61;MRO DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 16A62;MRO DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 16A63;MRO DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 16A64;MRO DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 16A65;MRO DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 16A66;MRO DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 16A67;MRO DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 16A68;MRO DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 16A69;MRO DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 16A6E;MRO DANDA;Po;0;L;;;;;N;;;;; 16A6F;MRO DOUBLE DANDA;Po;0;L;;;;;N;;;;; 16AD0;BASSA VAH LETTER ENNI;Lo;0;L;;;;;N;;;;; 16AD1;BASSA VAH LETTER KA;Lo;0;L;;;;;N;;;;; 16AD2;BASSA VAH LETTER SE;Lo;0;L;;;;;N;;;;; 16AD3;BASSA VAH LETTER FA;Lo;0;L;;;;;N;;;;; 16AD4;BASSA VAH LETTER MBE;Lo;0;L;;;;;N;;;;; 16AD5;BASSA VAH LETTER YIE;Lo;0;L;;;;;N;;;;; 16AD6;BASSA VAH LETTER GAH;Lo;0;L;;;;;N;;;;; 16AD7;BASSA VAH LETTER DHII;Lo;0;L;;;;;N;;;;; 16AD8;BASSA VAH LETTER KPAH;Lo;0;L;;;;;N;;;;; 16AD9;BASSA VAH LETTER JO;Lo;0;L;;;;;N;;;;; 16ADA;BASSA VAH LETTER HWAH;Lo;0;L;;;;;N;;;;; 16ADB;BASSA VAH LETTER WA;Lo;0;L;;;;;N;;;;; 16ADC;BASSA VAH LETTER ZO;Lo;0;L;;;;;N;;;;; 16ADD;BASSA VAH LETTER GBU;Lo;0;L;;;;;N;;;;; 16ADE;BASSA VAH LETTER DO;Lo;0;L;;;;;N;;;;; 16ADF;BASSA VAH LETTER CE;Lo;0;L;;;;;N;;;;; 16AE0;BASSA VAH LETTER UWU;Lo;0;L;;;;;N;;;;; 16AE1;BASSA VAH LETTER TO;Lo;0;L;;;;;N;;;;; 16AE2;BASSA VAH LETTER BA;Lo;0;L;;;;;N;;;;; 16AE3;BASSA VAH LETTER VU;Lo;0;L;;;;;N;;;;; 16AE4;BASSA VAH LETTER YEIN;Lo;0;L;;;;;N;;;;; 16AE5;BASSA VAH LETTER PA;Lo;0;L;;;;;N;;;;; 16AE6;BASSA VAH LETTER WADDA;Lo;0;L;;;;;N;;;;; 16AE7;BASSA VAH LETTER A;Lo;0;L;;;;;N;;;;; 16AE8;BASSA VAH LETTER O;Lo;0;L;;;;;N;;;;; 16AE9;BASSA VAH LETTER OO;Lo;0;L;;;;;N;;;;; 16AEA;BASSA VAH LETTER U;Lo;0;L;;;;;N;;;;; 16AEB;BASSA VAH LETTER EE;Lo;0;L;;;;;N;;;;; 16AEC;BASSA VAH LETTER E;Lo;0;L;;;;;N;;;;; 16AED;BASSA VAH LETTER I;Lo;0;L;;;;;N;;;;; 16AF0;BASSA VAH COMBINING HIGH TONE;Mn;1;NSM;;;;;N;;;;; 16AF1;BASSA VAH COMBINING LOW TONE;Mn;1;NSM;;;;;N;;;;; 16AF2;BASSA VAH COMBINING MID TONE;Mn;1;NSM;;;;;N;;;;; 16AF3;BASSA VAH COMBINING LOW-MID TONE;Mn;1;NSM;;;;;N;;;;; 16AF4;BASSA VAH COMBINING HIGH-LOW TONE;Mn;1;NSM;;;;;N;;;;; 16AF5;BASSA VAH FULL STOP;Po;0;L;;;;;N;;;;; 16B00;PAHAWH HMONG VOWEL KEEB;Lo;0;L;;;;;N;;;;; 16B01;PAHAWH HMONG VOWEL KEEV;Lo;0;L;;;;;N;;;;; 16B02;PAHAWH HMONG VOWEL KIB;Lo;0;L;;;;;N;;;;; 16B03;PAHAWH HMONG VOWEL KIV;Lo;0;L;;;;;N;;;;; 16B04;PAHAWH HMONG VOWEL KAUB;Lo;0;L;;;;;N;;;;; 16B05;PAHAWH HMONG VOWEL KAUV;Lo;0;L;;;;;N;;;;; 16B06;PAHAWH HMONG VOWEL KUB;Lo;0;L;;;;;N;;;;; 16B07;PAHAWH HMONG VOWEL KUV;Lo;0;L;;;;;N;;;;; 16B08;PAHAWH HMONG VOWEL KEB;Lo;0;L;;;;;N;;;;; 16B09;PAHAWH HMONG VOWEL KEV;Lo;0;L;;;;;N;;;;; 16B0A;PAHAWH HMONG VOWEL KAIB;Lo;0;L;;;;;N;;;;; 16B0B;PAHAWH HMONG VOWEL KAIV;Lo;0;L;;;;;N;;;;; 16B0C;PAHAWH HMONG VOWEL KOOB;Lo;0;L;;;;;N;;;;; 16B0D;PAHAWH HMONG VOWEL KOOV;Lo;0;L;;;;;N;;;;; 16B0E;PAHAWH HMONG VOWEL KAWB;Lo;0;L;;;;;N;;;;; 16B0F;PAHAWH HMONG VOWEL KAWV;Lo;0;L;;;;;N;;;;; 16B10;PAHAWH HMONG VOWEL KUAB;Lo;0;L;;;;;N;;;;; 16B11;PAHAWH HMONG VOWEL KUAV;Lo;0;L;;;;;N;;;;; 16B12;PAHAWH HMONG VOWEL KOB;Lo;0;L;;;;;N;;;;; 16B13;PAHAWH HMONG VOWEL KOV;Lo;0;L;;;;;N;;;;; 16B14;PAHAWH HMONG VOWEL KIAB;Lo;0;L;;;;;N;;;;; 16B15;PAHAWH HMONG VOWEL KIAV;Lo;0;L;;;;;N;;;;; 16B16;PAHAWH HMONG VOWEL KAB;Lo;0;L;;;;;N;;;;; 16B17;PAHAWH HMONG VOWEL KAV;Lo;0;L;;;;;N;;;;; 16B18;PAHAWH HMONG VOWEL KWB;Lo;0;L;;;;;N;;;;; 16B19;PAHAWH HMONG VOWEL KWV;Lo;0;L;;;;;N;;;;; 16B1A;PAHAWH HMONG VOWEL KAAB;Lo;0;L;;;;;N;;;;; 16B1B;PAHAWH HMONG VOWEL KAAV;Lo;0;L;;;;;N;;;;; 16B1C;PAHAWH HMONG CONSONANT VAU;Lo;0;L;;;;;N;;;;; 16B1D;PAHAWH HMONG CONSONANT NTSAU;Lo;0;L;;;;;N;;;;; 16B1E;PAHAWH HMONG CONSONANT LAU;Lo;0;L;;;;;N;;;;; 16B1F;PAHAWH HMONG CONSONANT HAU;Lo;0;L;;;;;N;;;;; 16B20;PAHAWH HMONG CONSONANT NLAU;Lo;0;L;;;;;N;;;;; 16B21;PAHAWH HMONG CONSONANT RAU;Lo;0;L;;;;;N;;;;; 16B22;PAHAWH HMONG CONSONANT NKAU;Lo;0;L;;;;;N;;;;; 16B23;PAHAWH HMONG CONSONANT QHAU;Lo;0;L;;;;;N;;;;; 16B24;PAHAWH HMONG CONSONANT YAU;Lo;0;L;;;;;N;;;;; 16B25;PAHAWH HMONG CONSONANT HLAU;Lo;0;L;;;;;N;;;;; 16B26;PAHAWH HMONG CONSONANT MAU;Lo;0;L;;;;;N;;;;; 16B27;PAHAWH HMONG CONSONANT CHAU;Lo;0;L;;;;;N;;;;; 16B28;PAHAWH HMONG CONSONANT NCHAU;Lo;0;L;;;;;N;;;;; 16B29;PAHAWH HMONG CONSONANT HNAU;Lo;0;L;;;;;N;;;;; 16B2A;PAHAWH HMONG CONSONANT PLHAU;Lo;0;L;;;;;N;;;;; 16B2B;PAHAWH HMONG CONSONANT NTHAU;Lo;0;L;;;;;N;;;;; 16B2C;PAHAWH HMONG CONSONANT NAU;Lo;0;L;;;;;N;;;;; 16B2D;PAHAWH HMONG CONSONANT AU;Lo;0;L;;;;;N;;;;; 16B2E;PAHAWH HMONG CONSONANT XAU;Lo;0;L;;;;;N;;;;; 16B2F;PAHAWH HMONG CONSONANT CAU;Lo;0;L;;;;;N;;;;; 16B30;PAHAWH HMONG MARK CIM TUB;Mn;230;NSM;;;;;N;;;;; 16B31;PAHAWH HMONG MARK CIM SO;Mn;230;NSM;;;;;N;;;;; 16B32;PAHAWH HMONG MARK CIM KES;Mn;230;NSM;;;;;N;;;;; 16B33;PAHAWH HMONG MARK CIM KHAV;Mn;230;NSM;;;;;N;;;;; 16B34;PAHAWH HMONG MARK CIM SUAM;Mn;230;NSM;;;;;N;;;;; 16B35;PAHAWH HMONG MARK CIM HOM;Mn;230;NSM;;;;;N;;;;; 16B36;PAHAWH HMONG MARK CIM TAUM;Mn;230;NSM;;;;;N;;;;; 16B37;PAHAWH HMONG SIGN VOS THOM;Po;0;L;;;;;N;;;;; 16B38;PAHAWH HMONG SIGN VOS TSHAB CEEB;Po;0;L;;;;;N;;;;; 16B39;PAHAWH HMONG SIGN CIM CHEEM;Po;0;L;;;;;N;;;;; 16B3A;PAHAWH HMONG SIGN VOS THIAB;Po;0;L;;;;;N;;;;; 16B3B;PAHAWH HMONG SIGN VOS FEEM;Po;0;L;;;;;N;;;;; 16B3C;PAHAWH HMONG SIGN XYEEM NTXIV;So;0;L;;;;;N;;;;; 16B3D;PAHAWH HMONG SIGN XYEEM RHO;So;0;L;;;;;N;;;;; 16B3E;PAHAWH HMONG SIGN XYEEM TOV;So;0;L;;;;;N;;;;; 16B3F;PAHAWH HMONG SIGN XYEEM FAIB;So;0;L;;;;;N;;;;; 16B40;PAHAWH HMONG SIGN VOS SEEV;Lm;0;L;;;;;N;;;;; 16B41;PAHAWH HMONG SIGN MEEJ SUAB;Lm;0;L;;;;;N;;;;; 16B42;PAHAWH HMONG SIGN VOS NRUA;Lm;0;L;;;;;N;;;;; 16B43;PAHAWH HMONG SIGN IB YAM;Lm;0;L;;;;;N;;;;; 16B44;PAHAWH HMONG SIGN XAUS;Po;0;L;;;;;N;;;;; 16B45;PAHAWH HMONG SIGN CIM TSOV ROG;So;0;L;;;;;N;;;;; 16B50;PAHAWH HMONG DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 16B51;PAHAWH HMONG DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 16B52;PAHAWH HMONG DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; 16B53;PAHAWH HMONG DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; 16B54;PAHAWH HMONG DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; 16B55;PAHAWH HMONG DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; 16B56;PAHAWH HMONG DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; 16B57;PAHAWH HMONG DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 16B58;PAHAWH HMONG DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 16B59;PAHAWH HMONG DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 16B5B;PAHAWH HMONG NUMBER TENS;No;0;L;;;;10;N;;;;; 16B5C;PAHAWH HMONG NUMBER HUNDREDS;No;0;L;;;;100;N;;;;; 16B5D;PAHAWH HMONG NUMBER TEN THOUSANDS;No;0;L;;;;10000;N;;;;; 16B5E;PAHAWH HMONG NUMBER MILLIONS;No;0;L;;;;1000000;N;;;;; 16B5F;PAHAWH HMONG NUMBER HUNDRED MILLIONS;No;0;L;;;;100000000;N;;;;; 16B60;PAHAWH HMONG NUMBER TEN BILLIONS;No;0;L;;;;10000000000;N;;;;; 16B61;PAHAWH HMONG NUMBER TRILLIONS;No;0;L;;;;1000000000000;N;;;;; 16B63;PAHAWH HMONG SIGN VOS LUB;Lo;0;L;;;;;N;;;;; 16B64;PAHAWH HMONG SIGN XYOO;Lo;0;L;;;;;N;;;;; 16B65;PAHAWH HMONG SIGN HLI;Lo;0;L;;;;;N;;;;; 16B66;PAHAWH HMONG SIGN THIRD-STAGE HLI;Lo;0;L;;;;;N;;;;; 16B67;PAHAWH HMONG SIGN ZWJ THAJ;Lo;0;L;;;;;N;;;;; 16B68;PAHAWH HMONG SIGN HNUB;Lo;0;L;;;;;N;;;;; 16B69;PAHAWH HMONG SIGN NQIG;Lo;0;L;;;;;N;;;;; 16B6A;PAHAWH HMONG SIGN XIAB;Lo;0;L;;;;;N;;;;; 16B6B;PAHAWH HMONG SIGN NTUJ;Lo;0;L;;;;;N;;;;; 16B6C;PAHAWH HMONG SIGN AV;Lo;0;L;;;;;N;;;;; 16B6D;PAHAWH HMONG SIGN TXHEEJ CEEV;Lo;0;L;;;;;N;;;;; 16B6E;PAHAWH HMONG SIGN MEEJ TSEEB;Lo;0;L;;;;;N;;;;; 16B6F;PAHAWH HMONG SIGN TAU;Lo;0;L;;;;;N;;;;; 16B70;PAHAWH HMONG SIGN LOS;Lo;0;L;;;;;N;;;;; 16B71;PAHAWH HMONG SIGN MUS;Lo;0;L;;;;;N;;;;; 16B72;PAHAWH HMONG SIGN CIM HAIS LUS NTOG NTOG;Lo;0;L;;;;;N;;;;; 16B73;PAHAWH HMONG SIGN CIM CUAM TSHOOJ;Lo;0;L;;;;;N;;;;; 16B74;PAHAWH HMONG SIGN CIM TXWV;Lo;0;L;;;;;N;;;;; 16B75;PAHAWH HMONG SIGN CIM TXWV CHWV;Lo;0;L;;;;;N;;;;; 16B76;PAHAWH HMONG SIGN CIM PUB DAWB;Lo;0;L;;;;;N;;;;; 16B77;PAHAWH HMONG SIGN CIM NRES TOS;Lo;0;L;;;;;N;;;;; 16B7D;PAHAWH HMONG CLAN SIGN TSHEEJ;Lo;0;L;;;;;N;;;;; 16B7E;PAHAWH HMONG CLAN SIGN YEEG;Lo;0;L;;;;;N;;;;; 16B7F;PAHAWH HMONG CLAN SIGN LIS;Lo;0;L;;;;;N;;;;; 16B80;PAHAWH HMONG CLAN SIGN LAUJ;Lo;0;L;;;;;N;;;;; 16B81;PAHAWH HMONG CLAN SIGN XYOOJ;Lo;0;L;;;;;N;;;;; 16B82;PAHAWH HMONG CLAN SIGN KOO;Lo;0;L;;;;;N;;;;; 16B83;PAHAWH HMONG CLAN SIGN HAWJ;Lo;0;L;;;;;N;;;;; 16B84;PAHAWH HMONG CLAN SIGN MUAS;Lo;0;L;;;;;N;;;;; 16B85;PAHAWH HMONG CLAN SIGN THOJ;Lo;0;L;;;;;N;;;;; 16B86;PAHAWH HMONG CLAN SIGN TSAB;Lo;0;L;;;;;N;;;;; 16B87;PAHAWH HMONG CLAN SIGN PHAB;Lo;0;L;;;;;N;;;;; 16B88;PAHAWH HMONG CLAN SIGN KHAB;Lo;0;L;;;;;N;;;;; 16B89;PAHAWH HMONG CLAN SIGN HAM;Lo;0;L;;;;;N;;;;; 16B8A;PAHAWH HMONG CLAN SIGN VAJ;Lo;0;L;;;;;N;;;;; 16B8B;PAHAWH HMONG CLAN SIGN FAJ;Lo;0;L;;;;;N;;;;; 16B8C;PAHAWH HMONG CLAN SIGN YAJ;Lo;0;L;;;;;N;;;;; 16B8D;PAHAWH HMONG CLAN SIGN TSWB;Lo;0;L;;;;;N;;;;; 16B8E;PAHAWH HMONG CLAN SIGN KWM;Lo;0;L;;;;;N;;;;; 16B8F;PAHAWH HMONG CLAN SIGN VWJ;Lo;0;L;;;;;N;;;;; 16F00;MIAO LETTER PA;Lo;0;L;;;;;N;;;;; 16F01;MIAO LETTER BA;Lo;0;L;;;;;N;;;;; 16F02;MIAO LETTER YI PA;Lo;0;L;;;;;N;;;;; 16F03;MIAO LETTER PLA;Lo;0;L;;;;;N;;;;; 16F04;MIAO LETTER MA;Lo;0;L;;;;;N;;;;; 16F05;MIAO LETTER MHA;Lo;0;L;;;;;N;;;;; 16F06;MIAO LETTER ARCHAIC MA;Lo;0;L;;;;;N;;;;; 16F07;MIAO LETTER FA;Lo;0;L;;;;;N;;;;; 16F08;MIAO LETTER VA;Lo;0;L;;;;;N;;;;; 16F09;MIAO LETTER VFA;Lo;0;L;;;;;N;;;;; 16F0A;MIAO LETTER TA;Lo;0;L;;;;;N;;;;; 16F0B;MIAO LETTER DA;Lo;0;L;;;;;N;;;;; 16F0C;MIAO LETTER YI TTA;Lo;0;L;;;;;N;;;;; 16F0D;MIAO LETTER YI TA;Lo;0;L;;;;;N;;;;; 16F0E;MIAO LETTER TTA;Lo;0;L;;;;;N;;;;; 16F0F;MIAO LETTER DDA;Lo;0;L;;;;;N;;;;; 16F10;MIAO LETTER NA;Lo;0;L;;;;;N;;;;; 16F11;MIAO LETTER NHA;Lo;0;L;;;;;N;;;;; 16F12;MIAO LETTER YI NNA;Lo;0;L;;;;;N;;;;; 16F13;MIAO LETTER ARCHAIC NA;Lo;0;L;;;;;N;;;;; 16F14;MIAO LETTER NNA;Lo;0;L;;;;;N;;;;; 16F15;MIAO LETTER NNHA;Lo;0;L;;;;;N;;;;; 16F16;MIAO LETTER LA;Lo;0;L;;;;;N;;;;; 16F17;MIAO LETTER LYA;Lo;0;L;;;;;N;;;;; 16F18;MIAO LETTER LHA;Lo;0;L;;;;;N;;;;; 16F19;MIAO LETTER LHYA;Lo;0;L;;;;;N;;;;; 16F1A;MIAO LETTER TLHA;Lo;0;L;;;;;N;;;;; 16F1B;MIAO LETTER DLHA;Lo;0;L;;;;;N;;;;; 16F1C;MIAO LETTER TLHYA;Lo;0;L;;;;;N;;;;; 16F1D;MIAO LETTER DLHYA;Lo;0;L;;;;;N;;;;; 16F1E;MIAO LETTER KA;Lo;0;L;;;;;N;;;;; 16F1F;MIAO LETTER GA;Lo;0;L;;;;;N;;;;; 16F20;MIAO LETTER YI KA;Lo;0;L;;;;;N;;;;; 16F21;MIAO LETTER QA;Lo;0;L;;;;;N;;;;; 16F22;MIAO LETTER QGA;Lo;0;L;;;;;N;;;;; 16F23;MIAO LETTER NGA;Lo;0;L;;;;;N;;;;; 16F24;MIAO LETTER NGHA;Lo;0;L;;;;;N;;;;; 16F25;MIAO LETTER ARCHAIC NGA;Lo;0;L;;;;;N;;;;; 16F26;MIAO LETTER HA;Lo;0;L;;;;;N;;;;; 16F27;MIAO LETTER XA;Lo;0;L;;;;;N;;;;; 16F28;MIAO LETTER GHA;Lo;0;L;;;;;N;;;;; 16F29;MIAO LETTER GHHA;Lo;0;L;;;;;N;;;;; 16F2A;MIAO LETTER TSSA;Lo;0;L;;;;;N;;;;; 16F2B;MIAO LETTER DZZA;Lo;0;L;;;;;N;;;;; 16F2C;MIAO LETTER NYA;Lo;0;L;;;;;N;;;;; 16F2D;MIAO LETTER NYHA;Lo;0;L;;;;;N;;;;; 16F2E;MIAO LETTER TSHA;Lo;0;L;;;;;N;;;;; 16F2F;MIAO LETTER DZHA;Lo;0;L;;;;;N;;;;; 16F30;MIAO LETTER YI TSHA;Lo;0;L;;;;;N;;;;; 16F31;MIAO LETTER YI DZHA;Lo;0;L;;;;;N;;;;; 16F32;MIAO LETTER REFORMED TSHA;Lo;0;L;;;;;N;;;;; 16F33;MIAO LETTER SHA;Lo;0;L;;;;;N;;;;; 16F34;MIAO LETTER SSA;Lo;0;L;;;;;N;;;;; 16F35;MIAO LETTER ZHA;Lo;0;L;;;;;N;;;;; 16F36;MIAO LETTER ZSHA;Lo;0;L;;;;;N;;;;; 16F37;MIAO LETTER TSA;Lo;0;L;;;;;N;;;;; 16F38;MIAO LETTER DZA;Lo;0;L;;;;;N;;;;; 16F39;MIAO LETTER YI TSA;Lo;0;L;;;;;N;;;;; 16F3A;MIAO LETTER SA;Lo;0;L;;;;;N;;;;; 16F3B;MIAO LETTER ZA;Lo;0;L;;;;;N;;;;; 16F3C;MIAO LETTER ZSA;Lo;0;L;;;;;N;;;;; 16F3D;MIAO LETTER ZZA;Lo;0;L;;;;;N;;;;; 16F3E;MIAO LETTER ZZSA;Lo;0;L;;;;;N;;;;; 16F3F;MIAO LETTER ARCHAIC ZZA;Lo;0;L;;;;;N;;;;; 16F40;MIAO LETTER ZZYA;Lo;0;L;;;;;N;;;;; 16F41;MIAO LETTER ZZSYA;Lo;0;L;;;;;N;;;;; 16F42;MIAO LETTER WA;Lo;0;L;;;;;N;;;;; 16F43;MIAO LETTER AH;Lo;0;L;;;;;N;;;;; 16F44;MIAO LETTER HHA;Lo;0;L;;;;;N;;;;; 16F50;MIAO LETTER NASALIZATION;Lo;0;L;;;;;N;;;;; 16F51;MIAO SIGN ASPIRATION;Mc;0;L;;;;;N;;;;; 16F52;MIAO SIGN REFORMED VOICING;Mc;0;L;;;;;N;;;;; 16F53;MIAO SIGN REFORMED ASPIRATION;Mc;0;L;;;;;N;;;;; 16F54;MIAO VOWEL SIGN A;Mc;0;L;;;;;N;;;;; 16F55;MIAO VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; 16F56;MIAO VOWEL SIGN AHH;Mc;0;L;;;;;N;;;;; 16F57;MIAO VOWEL SIGN AN;Mc;0;L;;;;;N;;;;; 16F58;MIAO VOWEL SIGN ANG;Mc;0;L;;;;;N;;;;; 16F59;MIAO VOWEL SIGN O;Mc;0;L;;;;;N;;;;; 16F5A;MIAO VOWEL SIGN OO;Mc;0;L;;;;;N;;;;; 16F5B;MIAO VOWEL SIGN WO;Mc;0;L;;;;;N;;;;; 16F5C;MIAO VOWEL SIGN W;Mc;0;L;;;;;N;;;;; 16F5D;MIAO VOWEL SIGN E;Mc;0;L;;;;;N;;;;; 16F5E;MIAO VOWEL SIGN EN;Mc;0;L;;;;;N;;;;; 16F5F;MIAO VOWEL SIGN ENG;Mc;0;L;;;;;N;;;;; 16F60;MIAO VOWEL SIGN OEY;Mc;0;L;;;;;N;;;;; 16F61;MIAO VOWEL SIGN I;Mc;0;L;;;;;N;;;;; 16F62;MIAO VOWEL SIGN IA;Mc;0;L;;;;;N;;;;; 16F63;MIAO VOWEL SIGN IAN;Mc;0;L;;;;;N;;;;; 16F64;MIAO VOWEL SIGN IANG;Mc;0;L;;;;;N;;;;; 16F65;MIAO VOWEL SIGN IO;Mc;0;L;;;;;N;;;;; 16F66;MIAO VOWEL SIGN IE;Mc;0;L;;;;;N;;;;; 16F67;MIAO VOWEL SIGN II;Mc;0;L;;;;;N;;;;; 16F68;MIAO VOWEL SIGN IU;Mc;0;L;;;;;N;;;;; 16F69;MIAO VOWEL SIGN ING;Mc;0;L;;;;;N;;;;; 16F6A;MIAO VOWEL SIGN U;Mc;0;L;;;;;N;;;;; 16F6B;MIAO VOWEL SIGN UA;Mc;0;L;;;;;N;;;;; 16F6C;MIAO VOWEL SIGN UAN;Mc;0;L;;;;;N;;;;; 16F6D;MIAO VOWEL SIGN UANG;Mc;0;L;;;;;N;;;;; 16F6E;MIAO VOWEL SIGN UU;Mc;0;L;;;;;N;;;;; 16F6F;MIAO VOWEL SIGN UEI;Mc;0;L;;;;;N;;;;; 16F70;MIAO VOWEL SIGN UNG;Mc;0;L;;;;;N;;;;; 16F71;MIAO VOWEL SIGN Y;Mc;0;L;;;;;N;;;;; 16F72;MIAO VOWEL SIGN YI;Mc;0;L;;;;;N;;;;; 16F73;MIAO VOWEL SIGN AE;Mc;0;L;;;;;N;;;;; 16F74;MIAO VOWEL SIGN AEE;Mc;0;L;;;;;N;;;;; 16F75;MIAO VOWEL SIGN ERR;Mc;0;L;;;;;N;;;;; 16F76;MIAO VOWEL SIGN ROUNDED ERR;Mc;0;L;;;;;N;;;;; 16F77;MIAO VOWEL SIGN ER;Mc;0;L;;;;;N;;;;; 16F78;MIAO VOWEL SIGN ROUNDED ER;Mc;0;L;;;;;N;;;;; 16F79;MIAO VOWEL SIGN AI;Mc;0;L;;;;;N;;;;; 16F7A;MIAO VOWEL SIGN EI;Mc;0;L;;;;;N;;;;; 16F7B;MIAO VOWEL SIGN AU;Mc;0;L;;;;;N;;;;; 16F7C;MIAO VOWEL SIGN OU;Mc;0;L;;;;;N;;;;; 16F7D;MIAO VOWEL SIGN N;Mc;0;L;;;;;N;;;;; 16F7E;MIAO VOWEL SIGN NG;Mc;0;L;;;;;N;;;;; 16F8F;MIAO TONE RIGHT;Mn;0;NSM;;;;;N;;;;; 16F90;MIAO TONE TOP RIGHT;Mn;0;NSM;;;;;N;;;;; 16F91;MIAO TONE ABOVE;Mn;0;NSM;;;;;N;;;;; 16F92;MIAO TONE BELOW;Mn;0;NSM;;;;;N;;;;; 16F93;MIAO LETTER TONE-2;Lm;0;L;;;;;N;;;;; 16F94;MIAO LETTER TONE-3;Lm;0;L;;;;;N;;;;; 16F95;MIAO LETTER TONE-4;Lm;0;L;;;;;N;;;;; 16F96;MIAO LETTER TONE-5;Lm;0;L;;;;;N;;;;; 16F97;MIAO LETTER TONE-6;Lm;0;L;;;;;N;;;;; 16F98;MIAO LETTER TONE-7;Lm;0;L;;;;;N;;;;; 16F99;MIAO LETTER TONE-8;Lm;0;L;;;;;N;;;;; 16F9A;MIAO LETTER REFORMED TONE-1;Lm;0;L;;;;;N;;;;; 16F9B;MIAO LETTER REFORMED TONE-2;Lm;0;L;;;;;N;;;;; 16F9C;MIAO LETTER REFORMED TONE-4;Lm;0;L;;;;;N;;;;; 16F9D;MIAO LETTER REFORMED TONE-5;Lm;0;L;;;;;N;;;;; 16F9E;MIAO LETTER REFORMED TONE-6;Lm;0;L;;;;;N;;;;; 16F9F;MIAO LETTER REFORMED TONE-8;Lm;0;L;;;;;N;;;;; 16FE0;TANGUT ITERATION MARK;Lm;0;L;;;;;N;;;;; 17000;;Lo;0;L;;;;;N;;;;; 187EC;;Lo;0;L;;;;;N;;;;; 18800;TANGUT COMPONENT-001;Lo;0;L;;;;;N;;;;; 18801;TANGUT COMPONENT-002;Lo;0;L;;;;;N;;;;; 18802;TANGUT COMPONENT-003;Lo;0;L;;;;;N;;;;; 18803;TANGUT COMPONENT-004;Lo;0;L;;;;;N;;;;; 18804;TANGUT COMPONENT-005;Lo;0;L;;;;;N;;;;; 18805;TANGUT COMPONENT-006;Lo;0;L;;;;;N;;;;; 18806;TANGUT COMPONENT-007;Lo;0;L;;;;;N;;;;; 18807;TANGUT COMPONENT-008;Lo;0;L;;;;;N;;;;; 18808;TANGUT COMPONENT-009;Lo;0;L;;;;;N;;;;; 18809;TANGUT COMPONENT-010;Lo;0;L;;;;;N;;;;; 1880A;TANGUT COMPONENT-011;Lo;0;L;;;;;N;;;;; 1880B;TANGUT COMPONENT-012;Lo;0;L;;;;;N;;;;; 1880C;TANGUT COMPONENT-013;Lo;0;L;;;;;N;;;;; 1880D;TANGUT COMPONENT-014;Lo;0;L;;;;;N;;;;; 1880E;TANGUT COMPONENT-015;Lo;0;L;;;;;N;;;;; 1880F;TANGUT COMPONENT-016;Lo;0;L;;;;;N;;;;; 18810;TANGUT COMPONENT-017;Lo;0;L;;;;;N;;;;; 18811;TANGUT COMPONENT-018;Lo;0;L;;;;;N;;;;; 18812;TANGUT COMPONENT-019;Lo;0;L;;;;;N;;;;; 18813;TANGUT COMPONENT-020;Lo;0;L;;;;;N;;;;; 18814;TANGUT COMPONENT-021;Lo;0;L;;;;;N;;;;; 18815;TANGUT COMPONENT-022;Lo;0;L;;;;;N;;;;; 18816;TANGUT COMPONENT-023;Lo;0;L;;;;;N;;;;; 18817;TANGUT COMPONENT-024;Lo;0;L;;;;;N;;;;; 18818;TANGUT COMPONENT-025;Lo;0;L;;;;;N;;;;; 18819;TANGUT COMPONENT-026;Lo;0;L;;;;;N;;;;; 1881A;TANGUT COMPONENT-027;Lo;0;L;;;;;N;;;;; 1881B;TANGUT COMPONENT-028;Lo;0;L;;;;;N;;;;; 1881C;TANGUT COMPONENT-029;Lo;0;L;;;;;N;;;;; 1881D;TANGUT COMPONENT-030;Lo;0;L;;;;;N;;;;; 1881E;TANGUT COMPONENT-031;Lo;0;L;;;;;N;;;;; 1881F;TANGUT COMPONENT-032;Lo;0;L;;;;;N;;;;; 18820;TANGUT COMPONENT-033;Lo;0;L;;;;;N;;;;; 18821;TANGUT COMPONENT-034;Lo;0;L;;;;;N;;;;; 18822;TANGUT COMPONENT-035;Lo;0;L;;;;;N;;;;; 18823;TANGUT COMPONENT-036;Lo;0;L;;;;;N;;;;; 18824;TANGUT COMPONENT-037;Lo;0;L;;;;;N;;;;; 18825;TANGUT COMPONENT-038;Lo;0;L;;;;;N;;;;; 18826;TANGUT COMPONENT-039;Lo;0;L;;;;;N;;;;; 18827;TANGUT COMPONENT-040;Lo;0;L;;;;;N;;;;; 18828;TANGUT COMPONENT-041;Lo;0;L;;;;;N;;;;; 18829;TANGUT COMPONENT-042;Lo;0;L;;;;;N;;;;; 1882A;TANGUT COMPONENT-043;Lo;0;L;;;;;N;;;;; 1882B;TANGUT COMPONENT-044;Lo;0;L;;;;;N;;;;; 1882C;TANGUT COMPONENT-045;Lo;0;L;;;;;N;;;;; 1882D;TANGUT COMPONENT-046;Lo;0;L;;;;;N;;;;; 1882E;TANGUT COMPONENT-047;Lo;0;L;;;;;N;;;;; 1882F;TANGUT COMPONENT-048;Lo;0;L;;;;;N;;;;; 18830;TANGUT COMPONENT-049;Lo;0;L;;;;;N;;;;; 18831;TANGUT COMPONENT-050;Lo;0;L;;;;;N;;;;; 18832;TANGUT COMPONENT-051;Lo;0;L;;;;;N;;;;; 18833;TANGUT COMPONENT-052;Lo;0;L;;;;;N;;;;; 18834;TANGUT COMPONENT-053;Lo;0;L;;;;;N;;;;; 18835;TANGUT COMPONENT-054;Lo;0;L;;;;;N;;;;; 18836;TANGUT COMPONENT-055;Lo;0;L;;;;;N;;;;; 18837;TANGUT COMPONENT-056;Lo;0;L;;;;;N;;;;; 18838;TANGUT COMPONENT-057;Lo;0;L;;;;;N;;;;; 18839;TANGUT COMPONENT-058;Lo;0;L;;;;;N;;;;; 1883A;TANGUT COMPONENT-059;Lo;0;L;;;;;N;;;;; 1883B;TANGUT COMPONENT-060;Lo;0;L;;;;;N;;;;; 1883C;TANGUT COMPONENT-061;Lo;0;L;;;;;N;;;;; 1883D;TANGUT COMPONENT-062;Lo;0;L;;;;;N;;;;; 1883E;TANGUT COMPONENT-063;Lo;0;L;;;;;N;;;;; 1883F;TANGUT COMPONENT-064;Lo;0;L;;;;;N;;;;; 18840;TANGUT COMPONENT-065;Lo;0;L;;;;;N;;;;; 18841;TANGUT COMPONENT-066;Lo;0;L;;;;;N;;;;; 18842;TANGUT COMPONENT-067;Lo;0;L;;;;;N;;;;; 18843;TANGUT COMPONENT-068;Lo;0;L;;;;;N;;;;; 18844;TANGUT COMPONENT-069;Lo;0;L;;;;;N;;;;; 18845;TANGUT COMPONENT-070;Lo;0;L;;;;;N;;;;; 18846;TANGUT COMPONENT-071;Lo;0;L;;;;;N;;;;; 18847;TANGUT COMPONENT-072;Lo;0;L;;;;;N;;;;; 18848;TANGUT COMPONENT-073;Lo;0;L;;;;;N;;;;; 18849;TANGUT COMPONENT-074;Lo;0;L;;;;;N;;;;; 1884A;TANGUT COMPONENT-075;Lo;0;L;;;;;N;;;;; 1884B;TANGUT COMPONENT-076;Lo;0;L;;;;;N;;;;; 1884C;TANGUT COMPONENT-077;Lo;0;L;;;;;N;;;;; 1884D;TANGUT COMPONENT-078;Lo;0;L;;;;;N;;;;; 1884E;TANGUT COMPONENT-079;Lo;0;L;;;;;N;;;;; 1884F;TANGUT COMPONENT-080;Lo;0;L;;;;;N;;;;; 18850;TANGUT COMPONENT-081;Lo;0;L;;;;;N;;;;; 18851;TANGUT COMPONENT-082;Lo;0;L;;;;;N;;;;; 18852;TANGUT COMPONENT-083;Lo;0;L;;;;;N;;;;; 18853;TANGUT COMPONENT-084;Lo;0;L;;;;;N;;;;; 18854;TANGUT COMPONENT-085;Lo;0;L;;;;;N;;;;; 18855;TANGUT COMPONENT-086;Lo;0;L;;;;;N;;;;; 18856;TANGUT COMPONENT-087;Lo;0;L;;;;;N;;;;; 18857;TANGUT COMPONENT-088;Lo;0;L;;;;;N;;;;; 18858;TANGUT COMPONENT-089;Lo;0;L;;;;;N;;;;; 18859;TANGUT COMPONENT-090;Lo;0;L;;;;;N;;;;; 1885A;TANGUT COMPONENT-091;Lo;0;L;;;;;N;;;;; 1885B;TANGUT COMPONENT-092;Lo;0;L;;;;;N;;;;; 1885C;TANGUT COMPONENT-093;Lo;0;L;;;;;N;;;;; 1885D;TANGUT COMPONENT-094;Lo;0;L;;;;;N;;;;; 1885E;TANGUT COMPONENT-095;Lo;0;L;;;;;N;;;;; 1885F;TANGUT COMPONENT-096;Lo;0;L;;;;;N;;;;; 18860;TANGUT COMPONENT-097;Lo;0;L;;;;;N;;;;; 18861;TANGUT COMPONENT-098;Lo;0;L;;;;;N;;;;; 18862;TANGUT COMPONENT-099;Lo;0;L;;;;;N;;;;; 18863;TANGUT COMPONENT-100;Lo;0;L;;;;;N;;;;; 18864;TANGUT COMPONENT-101;Lo;0;L;;;;;N;;;;; 18865;TANGUT COMPONENT-102;Lo;0;L;;;;;N;;;;; 18866;TANGUT COMPONENT-103;Lo;0;L;;;;;N;;;;; 18867;TANGUT COMPONENT-104;Lo;0;L;;;;;N;;;;; 18868;TANGUT COMPONENT-105;Lo;0;L;;;;;N;;;;; 18869;TANGUT COMPONENT-106;Lo;0;L;;;;;N;;;;; 1886A;TANGUT COMPONENT-107;Lo;0;L;;;;;N;;;;; 1886B;TANGUT COMPONENT-108;Lo;0;L;;;;;N;;;;; 1886C;TANGUT COMPONENT-109;Lo;0;L;;;;;N;;;;; 1886D;TANGUT COMPONENT-110;Lo;0;L;;;;;N;;;;; 1886E;TANGUT COMPONENT-111;Lo;0;L;;;;;N;;;;; 1886F;TANGUT COMPONENT-112;Lo;0;L;;;;;N;;;;; 18870;TANGUT COMPONENT-113;Lo;0;L;;;;;N;;;;; 18871;TANGUT COMPONENT-114;Lo;0;L;;;;;N;;;;; 18872;TANGUT COMPONENT-115;Lo;0;L;;;;;N;;;;; 18873;TANGUT COMPONENT-116;Lo;0;L;;;;;N;;;;; 18874;TANGUT COMPONENT-117;Lo;0;L;;;;;N;;;;; 18875;TANGUT COMPONENT-118;Lo;0;L;;;;;N;;;;; 18876;TANGUT COMPONENT-119;Lo;0;L;;;;;N;;;;; 18877;TANGUT COMPONENT-120;Lo;0;L;;;;;N;;;;; 18878;TANGUT COMPONENT-121;Lo;0;L;;;;;N;;;;; 18879;TANGUT COMPONENT-122;Lo;0;L;;;;;N;;;;; 1887A;TANGUT COMPONENT-123;Lo;0;L;;;;;N;;;;; 1887B;TANGUT COMPONENT-124;Lo;0;L;;;;;N;;;;; 1887C;TANGUT COMPONENT-125;Lo;0;L;;;;;N;;;;; 1887D;TANGUT COMPONENT-126;Lo;0;L;;;;;N;;;;; 1887E;TANGUT COMPONENT-127;Lo;0;L;;;;;N;;;;; 1887F;TANGUT COMPONENT-128;Lo;0;L;;;;;N;;;;; 18880;TANGUT COMPONENT-129;Lo;0;L;;;;;N;;;;; 18881;TANGUT COMPONENT-130;Lo;0;L;;;;;N;;;;; 18882;TANGUT COMPONENT-131;Lo;0;L;;;;;N;;;;; 18883;TANGUT COMPONENT-132;Lo;0;L;;;;;N;;;;; 18884;TANGUT COMPONENT-133;Lo;0;L;;;;;N;;;;; 18885;TANGUT COMPONENT-134;Lo;0;L;;;;;N;;;;; 18886;TANGUT COMPONENT-135;Lo;0;L;;;;;N;;;;; 18887;TANGUT COMPONENT-136;Lo;0;L;;;;;N;;;;; 18888;TANGUT COMPONENT-137;Lo;0;L;;;;;N;;;;; 18889;TANGUT COMPONENT-138;Lo;0;L;;;;;N;;;;; 1888A;TANGUT COMPONENT-139;Lo;0;L;;;;;N;;;;; 1888B;TANGUT COMPONENT-140;Lo;0;L;;;;;N;;;;; 1888C;TANGUT COMPONENT-141;Lo;0;L;;;;;N;;;;; 1888D;TANGUT COMPONENT-142;Lo;0;L;;;;;N;;;;; 1888E;TANGUT COMPONENT-143;Lo;0;L;;;;;N;;;;; 1888F;TANGUT COMPONENT-144;Lo;0;L;;;;;N;;;;; 18890;TANGUT COMPONENT-145;Lo;0;L;;;;;N;;;;; 18891;TANGUT COMPONENT-146;Lo;0;L;;;;;N;;;;; 18892;TANGUT COMPONENT-147;Lo;0;L;;;;;N;;;;; 18893;TANGUT COMPONENT-148;Lo;0;L;;;;;N;;;;; 18894;TANGUT COMPONENT-149;Lo;0;L;;;;;N;;;;; 18895;TANGUT COMPONENT-150;Lo;0;L;;;;;N;;;;; 18896;TANGUT COMPONENT-151;Lo;0;L;;;;;N;;;;; 18897;TANGUT COMPONENT-152;Lo;0;L;;;;;N;;;;; 18898;TANGUT COMPONENT-153;Lo;0;L;;;;;N;;;;; 18899;TANGUT COMPONENT-154;Lo;0;L;;;;;N;;;;; 1889A;TANGUT COMPONENT-155;Lo;0;L;;;;;N;;;;; 1889B;TANGUT COMPONENT-156;Lo;0;L;;;;;N;;;;; 1889C;TANGUT COMPONENT-157;Lo;0;L;;;;;N;;;;; 1889D;TANGUT COMPONENT-158;Lo;0;L;;;;;N;;;;; 1889E;TANGUT COMPONENT-159;Lo;0;L;;;;;N;;;;; 1889F;TANGUT COMPONENT-160;Lo;0;L;;;;;N;;;;; 188A0;TANGUT COMPONENT-161;Lo;0;L;;;;;N;;;;; 188A1;TANGUT COMPONENT-162;Lo;0;L;;;;;N;;;;; 188A2;TANGUT COMPONENT-163;Lo;0;L;;;;;N;;;;; 188A3;TANGUT COMPONENT-164;Lo;0;L;;;;;N;;;;; 188A4;TANGUT COMPONENT-165;Lo;0;L;;;;;N;;;;; 188A5;TANGUT COMPONENT-166;Lo;0;L;;;;;N;;;;; 188A6;TANGUT COMPONENT-167;Lo;0;L;;;;;N;;;;; 188A7;TANGUT COMPONENT-168;Lo;0;L;;;;;N;;;;; 188A8;TANGUT COMPONENT-169;Lo;0;L;;;;;N;;;;; 188A9;TANGUT COMPONENT-170;Lo;0;L;;;;;N;;;;; 188AA;TANGUT COMPONENT-171;Lo;0;L;;;;;N;;;;; 188AB;TANGUT COMPONENT-172;Lo;0;L;;;;;N;;;;; 188AC;TANGUT COMPONENT-173;Lo;0;L;;;;;N;;;;; 188AD;TANGUT COMPONENT-174;Lo;0;L;;;;;N;;;;; 188AE;TANGUT COMPONENT-175;Lo;0;L;;;;;N;;;;; 188AF;TANGUT COMPONENT-176;Lo;0;L;;;;;N;;;;; 188B0;TANGUT COMPONENT-177;Lo;0;L;;;;;N;;;;; 188B1;TANGUT COMPONENT-178;Lo;0;L;;;;;N;;;;; 188B2;TANGUT COMPONENT-179;Lo;0;L;;;;;N;;;;; 188B3;TANGUT COMPONENT-180;Lo;0;L;;;;;N;;;;; 188B4;TANGUT COMPONENT-181;Lo;0;L;;;;;N;;;;; 188B5;TANGUT COMPONENT-182;Lo;0;L;;;;;N;;;;; 188B6;TANGUT COMPONENT-183;Lo;0;L;;;;;N;;;;; 188B7;TANGUT COMPONENT-184;Lo;0;L;;;;;N;;;;; 188B8;TANGUT COMPONENT-185;Lo;0;L;;;;;N;;;;; 188B9;TANGUT COMPONENT-186;Lo;0;L;;;;;N;;;;; 188BA;TANGUT COMPONENT-187;Lo;0;L;;;;;N;;;;; 188BB;TANGUT COMPONENT-188;Lo;0;L;;;;;N;;;;; 188BC;TANGUT COMPONENT-189;Lo;0;L;;;;;N;;;;; 188BD;TANGUT COMPONENT-190;Lo;0;L;;;;;N;;;;; 188BE;TANGUT COMPONENT-191;Lo;0;L;;;;;N;;;;; 188BF;TANGUT COMPONENT-192;Lo;0;L;;;;;N;;;;; 188C0;TANGUT COMPONENT-193;Lo;0;L;;;;;N;;;;; 188C1;TANGUT COMPONENT-194;Lo;0;L;;;;;N;;;;; 188C2;TANGUT COMPONENT-195;Lo;0;L;;;;;N;;;;; 188C3;TANGUT COMPONENT-196;Lo;0;L;;;;;N;;;;; 188C4;TANGUT COMPONENT-197;Lo;0;L;;;;;N;;;;; 188C5;TANGUT COMPONENT-198;Lo;0;L;;;;;N;;;;; 188C6;TANGUT COMPONENT-199;Lo;0;L;;;;;N;;;;; 188C7;TANGUT COMPONENT-200;Lo;0;L;;;;;N;;;;; 188C8;TANGUT COMPONENT-201;Lo;0;L;;;;;N;;;;; 188C9;TANGUT COMPONENT-202;Lo;0;L;;;;;N;;;;; 188CA;TANGUT COMPONENT-203;Lo;0;L;;;;;N;;;;; 188CB;TANGUT COMPONENT-204;Lo;0;L;;;;;N;;;;; 188CC;TANGUT COMPONENT-205;Lo;0;L;;;;;N;;;;; 188CD;TANGUT COMPONENT-206;Lo;0;L;;;;;N;;;;; 188CE;TANGUT COMPONENT-207;Lo;0;L;;;;;N;;;;; 188CF;TANGUT COMPONENT-208;Lo;0;L;;;;;N;;;;; 188D0;TANGUT COMPONENT-209;Lo;0;L;;;;;N;;;;; 188D1;TANGUT COMPONENT-210;Lo;0;L;;;;;N;;;;; 188D2;TANGUT COMPONENT-211;Lo;0;L;;;;;N;;;;; 188D3;TANGUT COMPONENT-212;Lo;0;L;;;;;N;;;;; 188D4;TANGUT COMPONENT-213;Lo;0;L;;;;;N;;;;; 188D5;TANGUT COMPONENT-214;Lo;0;L;;;;;N;;;;; 188D6;TANGUT COMPONENT-215;Lo;0;L;;;;;N;;;;; 188D7;TANGUT COMPONENT-216;Lo;0;L;;;;;N;;;;; 188D8;TANGUT COMPONENT-217;Lo;0;L;;;;;N;;;;; 188D9;TANGUT COMPONENT-218;Lo;0;L;;;;;N;;;;; 188DA;TANGUT COMPONENT-219;Lo;0;L;;;;;N;;;;; 188DB;TANGUT COMPONENT-220;Lo;0;L;;;;;N;;;;; 188DC;TANGUT COMPONENT-221;Lo;0;L;;;;;N;;;;; 188DD;TANGUT COMPONENT-222;Lo;0;L;;;;;N;;;;; 188DE;TANGUT COMPONENT-223;Lo;0;L;;;;;N;;;;; 188DF;TANGUT COMPONENT-224;Lo;0;L;;;;;N;;;;; 188E0;TANGUT COMPONENT-225;Lo;0;L;;;;;N;;;;; 188E1;TANGUT COMPONENT-226;Lo;0;L;;;;;N;;;;; 188E2;TANGUT COMPONENT-227;Lo;0;L;;;;;N;;;;; 188E3;TANGUT COMPONENT-228;Lo;0;L;;;;;N;;;;; 188E4;TANGUT COMPONENT-229;Lo;0;L;;;;;N;;;;; 188E5;TANGUT COMPONENT-230;Lo;0;L;;;;;N;;;;; 188E6;TANGUT COMPONENT-231;Lo;0;L;;;;;N;;;;; 188E7;TANGUT COMPONENT-232;Lo;0;L;;;;;N;;;;; 188E8;TANGUT COMPONENT-233;Lo;0;L;;;;;N;;;;; 188E9;TANGUT COMPONENT-234;Lo;0;L;;;;;N;;;;; 188EA;TANGUT COMPONENT-235;Lo;0;L;;;;;N;;;;; 188EB;TANGUT COMPONENT-236;Lo;0;L;;;;;N;;;;; 188EC;TANGUT COMPONENT-237;Lo;0;L;;;;;N;;;;; 188ED;TANGUT COMPONENT-238;Lo;0;L;;;;;N;;;;; 188EE;TANGUT COMPONENT-239;Lo;0;L;;;;;N;;;;; 188EF;TANGUT COMPONENT-240;Lo;0;L;;;;;N;;;;; 188F0;TANGUT COMPONENT-241;Lo;0;L;;;;;N;;;;; 188F1;TANGUT COMPONENT-242;Lo;0;L;;;;;N;;;;; 188F2;TANGUT COMPONENT-243;Lo;0;L;;;;;N;;;;; 188F3;TANGUT COMPONENT-244;Lo;0;L;;;;;N;;;;; 188F4;TANGUT COMPONENT-245;Lo;0;L;;;;;N;;;;; 188F5;TANGUT COMPONENT-246;Lo;0;L;;;;;N;;;;; 188F6;TANGUT COMPONENT-247;Lo;0;L;;;;;N;;;;; 188F7;TANGUT COMPONENT-248;Lo;0;L;;;;;N;;;;; 188F8;TANGUT COMPONENT-249;Lo;0;L;;;;;N;;;;; 188F9;TANGUT COMPONENT-250;Lo;0;L;;;;;N;;;;; 188FA;TANGUT COMPONENT-251;Lo;0;L;;;;;N;;;;; 188FB;TANGUT COMPONENT-252;Lo;0;L;;;;;N;;;;; 188FC;TANGUT COMPONENT-253;Lo;0;L;;;;;N;;;;; 188FD;TANGUT COMPONENT-254;Lo;0;L;;;;;N;;;;; 188FE;TANGUT COMPONENT-255;Lo;0;L;;;;;N;;;;; 188FF;TANGUT COMPONENT-256;Lo;0;L;;;;;N;;;;; 18900;TANGUT COMPONENT-257;Lo;0;L;;;;;N;;;;; 18901;TANGUT COMPONENT-258;Lo;0;L;;;;;N;;;;; 18902;TANGUT COMPONENT-259;Lo;0;L;;;;;N;;;;; 18903;TANGUT COMPONENT-260;Lo;0;L;;;;;N;;;;; 18904;TANGUT COMPONENT-261;Lo;0;L;;;;;N;;;;; 18905;TANGUT COMPONENT-262;Lo;0;L;;;;;N;;;;; 18906;TANGUT COMPONENT-263;Lo;0;L;;;;;N;;;;; 18907;TANGUT COMPONENT-264;Lo;0;L;;;;;N;;;;; 18908;TANGUT COMPONENT-265;Lo;0;L;;;;;N;;;;; 18909;TANGUT COMPONENT-266;Lo;0;L;;;;;N;;;;; 1890A;TANGUT COMPONENT-267;Lo;0;L;;;;;N;;;;; 1890B;TANGUT COMPONENT-268;Lo;0;L;;;;;N;;;;; 1890C;TANGUT COMPONENT-269;Lo;0;L;;;;;N;;;;; 1890D;TANGUT COMPONENT-270;Lo;0;L;;;;;N;;;;; 1890E;TANGUT COMPONENT-271;Lo;0;L;;;;;N;;;;; 1890F;TANGUT COMPONENT-272;Lo;0;L;;;;;N;;;;; 18910;TANGUT COMPONENT-273;Lo;0;L;;;;;N;;;;; 18911;TANGUT COMPONENT-274;Lo;0;L;;;;;N;;;;; 18912;TANGUT COMPONENT-275;Lo;0;L;;;;;N;;;;; 18913;TANGUT COMPONENT-276;Lo;0;L;;;;;N;;;;; 18914;TANGUT COMPONENT-277;Lo;0;L;;;;;N;;;;; 18915;TANGUT COMPONENT-278;Lo;0;L;;;;;N;;;;; 18916;TANGUT COMPONENT-279;Lo;0;L;;;;;N;;;;; 18917;TANGUT COMPONENT-280;Lo;0;L;;;;;N;;;;; 18918;TANGUT COMPONENT-281;Lo;0;L;;;;;N;;;;; 18919;TANGUT COMPONENT-282;Lo;0;L;;;;;N;;;;; 1891A;TANGUT COMPONENT-283;Lo;0;L;;;;;N;;;;; 1891B;TANGUT COMPONENT-284;Lo;0;L;;;;;N;;;;; 1891C;TANGUT COMPONENT-285;Lo;0;L;;;;;N;;;;; 1891D;TANGUT COMPONENT-286;Lo;0;L;;;;;N;;;;; 1891E;TANGUT COMPONENT-287;Lo;0;L;;;;;N;;;;; 1891F;TANGUT COMPONENT-288;Lo;0;L;;;;;N;;;;; 18920;TANGUT COMPONENT-289;Lo;0;L;;;;;N;;;;; 18921;TANGUT COMPONENT-290;Lo;0;L;;;;;N;;;;; 18922;TANGUT COMPONENT-291;Lo;0;L;;;;;N;;;;; 18923;TANGUT COMPONENT-292;Lo;0;L;;;;;N;;;;; 18924;TANGUT COMPONENT-293;Lo;0;L;;;;;N;;;;; 18925;TANGUT COMPONENT-294;Lo;0;L;;;;;N;;;;; 18926;TANGUT COMPONENT-295;Lo;0;L;;;;;N;;;;; 18927;TANGUT COMPONENT-296;Lo;0;L;;;;;N;;;;; 18928;TANGUT COMPONENT-297;Lo;0;L;;;;;N;;;;; 18929;TANGUT COMPONENT-298;Lo;0;L;;;;;N;;;;; 1892A;TANGUT COMPONENT-299;Lo;0;L;;;;;N;;;;; 1892B;TANGUT COMPONENT-300;Lo;0;L;;;;;N;;;;; 1892C;TANGUT COMPONENT-301;Lo;0;L;;;;;N;;;;; 1892D;TANGUT COMPONENT-302;Lo;0;L;;;;;N;;;;; 1892E;TANGUT COMPONENT-303;Lo;0;L;;;;;N;;;;; 1892F;TANGUT COMPONENT-304;Lo;0;L;;;;;N;;;;; 18930;TANGUT COMPONENT-305;Lo;0;L;;;;;N;;;;; 18931;TANGUT COMPONENT-306;Lo;0;L;;;;;N;;;;; 18932;TANGUT COMPONENT-307;Lo;0;L;;;;;N;;;;; 18933;TANGUT COMPONENT-308;Lo;0;L;;;;;N;;;;; 18934;TANGUT COMPONENT-309;Lo;0;L;;;;;N;;;;; 18935;TANGUT COMPONENT-310;Lo;0;L;;;;;N;;;;; 18936;TANGUT COMPONENT-311;Lo;0;L;;;;;N;;;;; 18937;TANGUT COMPONENT-312;Lo;0;L;;;;;N;;;;; 18938;TANGUT COMPONENT-313;Lo;0;L;;;;;N;;;;; 18939;TANGUT COMPONENT-314;Lo;0;L;;;;;N;;;;; 1893A;TANGUT COMPONENT-315;Lo;0;L;;;;;N;;;;; 1893B;TANGUT COMPONENT-316;Lo;0;L;;;;;N;;;;; 1893C;TANGUT COMPONENT-317;Lo;0;L;;;;;N;;;;; 1893D;TANGUT COMPONENT-318;Lo;0;L;;;;;N;;;;; 1893E;TANGUT COMPONENT-319;Lo;0;L;;;;;N;;;;; 1893F;TANGUT COMPONENT-320;Lo;0;L;;;;;N;;;;; 18940;TANGUT COMPONENT-321;Lo;0;L;;;;;N;;;;; 18941;TANGUT COMPONENT-322;Lo;0;L;;;;;N;;;;; 18942;TANGUT COMPONENT-323;Lo;0;L;;;;;N;;;;; 18943;TANGUT COMPONENT-324;Lo;0;L;;;;;N;;;;; 18944;TANGUT COMPONENT-325;Lo;0;L;;;;;N;;;;; 18945;TANGUT COMPONENT-326;Lo;0;L;;;;;N;;;;; 18946;TANGUT COMPONENT-327;Lo;0;L;;;;;N;;;;; 18947;TANGUT COMPONENT-328;Lo;0;L;;;;;N;;;;; 18948;TANGUT COMPONENT-329;Lo;0;L;;;;;N;;;;; 18949;TANGUT COMPONENT-330;Lo;0;L;;;;;N;;;;; 1894A;TANGUT COMPONENT-331;Lo;0;L;;;;;N;;;;; 1894B;TANGUT COMPONENT-332;Lo;0;L;;;;;N;;;;; 1894C;TANGUT COMPONENT-333;Lo;0;L;;;;;N;;;;; 1894D;TANGUT COMPONENT-334;Lo;0;L;;;;;N;;;;; 1894E;TANGUT COMPONENT-335;Lo;0;L;;;;;N;;;;; 1894F;TANGUT COMPONENT-336;Lo;0;L;;;;;N;;;;; 18950;TANGUT COMPONENT-337;Lo;0;L;;;;;N;;;;; 18951;TANGUT COMPONENT-338;Lo;0;L;;;;;N;;;;; 18952;TANGUT COMPONENT-339;Lo;0;L;;;;;N;;;;; 18953;TANGUT COMPONENT-340;Lo;0;L;;;;;N;;;;; 18954;TANGUT COMPONENT-341;Lo;0;L;;;;;N;;;;; 18955;TANGUT COMPONENT-342;Lo;0;L;;;;;N;;;;; 18956;TANGUT COMPONENT-343;Lo;0;L;;;;;N;;;;; 18957;TANGUT COMPONENT-344;Lo;0;L;;;;;N;;;;; 18958;TANGUT COMPONENT-345;Lo;0;L;;;;;N;;;;; 18959;TANGUT COMPONENT-346;Lo;0;L;;;;;N;;;;; 1895A;TANGUT COMPONENT-347;Lo;0;L;;;;;N;;;;; 1895B;TANGUT COMPONENT-348;Lo;0;L;;;;;N;;;;; 1895C;TANGUT COMPONENT-349;Lo;0;L;;;;;N;;;;; 1895D;TANGUT COMPONENT-350;Lo;0;L;;;;;N;;;;; 1895E;TANGUT COMPONENT-351;Lo;0;L;;;;;N;;;;; 1895F;TANGUT COMPONENT-352;Lo;0;L;;;;;N;;;;; 18960;TANGUT COMPONENT-353;Lo;0;L;;;;;N;;;;; 18961;TANGUT COMPONENT-354;Lo;0;L;;;;;N;;;;; 18962;TANGUT COMPONENT-355;Lo;0;L;;;;;N;;;;; 18963;TANGUT COMPONENT-356;Lo;0;L;;;;;N;;;;; 18964;TANGUT COMPONENT-357;Lo;0;L;;;;;N;;;;; 18965;TANGUT COMPONENT-358;Lo;0;L;;;;;N;;;;; 18966;TANGUT COMPONENT-359;Lo;0;L;;;;;N;;;;; 18967;TANGUT COMPONENT-360;Lo;0;L;;;;;N;;;;; 18968;TANGUT COMPONENT-361;Lo;0;L;;;;;N;;;;; 18969;TANGUT COMPONENT-362;Lo;0;L;;;;;N;;;;; 1896A;TANGUT COMPONENT-363;Lo;0;L;;;;;N;;;;; 1896B;TANGUT COMPONENT-364;Lo;0;L;;;;;N;;;;; 1896C;TANGUT COMPONENT-365;Lo;0;L;;;;;N;;;;; 1896D;TANGUT COMPONENT-366;Lo;0;L;;;;;N;;;;; 1896E;TANGUT COMPONENT-367;Lo;0;L;;;;;N;;;;; 1896F;TANGUT COMPONENT-368;Lo;0;L;;;;;N;;;;; 18970;TANGUT COMPONENT-369;Lo;0;L;;;;;N;;;;; 18971;TANGUT COMPONENT-370;Lo;0;L;;;;;N;;;;; 18972;TANGUT COMPONENT-371;Lo;0;L;;;;;N;;;;; 18973;TANGUT COMPONENT-372;Lo;0;L;;;;;N;;;;; 18974;TANGUT COMPONENT-373;Lo;0;L;;;;;N;;;;; 18975;TANGUT COMPONENT-374;Lo;0;L;;;;;N;;;;; 18976;TANGUT COMPONENT-375;Lo;0;L;;;;;N;;;;; 18977;TANGUT COMPONENT-376;Lo;0;L;;;;;N;;;;; 18978;TANGUT COMPONENT-377;Lo;0;L;;;;;N;;;;; 18979;TANGUT COMPONENT-378;Lo;0;L;;;;;N;;;;; 1897A;TANGUT COMPONENT-379;Lo;0;L;;;;;N;;;;; 1897B;TANGUT COMPONENT-380;Lo;0;L;;;;;N;;;;; 1897C;TANGUT COMPONENT-381;Lo;0;L;;;;;N;;;;; 1897D;TANGUT COMPONENT-382;Lo;0;L;;;;;N;;;;; 1897E;TANGUT COMPONENT-383;Lo;0;L;;;;;N;;;;; 1897F;TANGUT COMPONENT-384;Lo;0;L;;;;;N;;;;; 18980;TANGUT COMPONENT-385;Lo;0;L;;;;;N;;;;; 18981;TANGUT COMPONENT-386;Lo;0;L;;;;;N;;;;; 18982;TANGUT COMPONENT-387;Lo;0;L;;;;;N;;;;; 18983;TANGUT COMPONENT-388;Lo;0;L;;;;;N;;;;; 18984;TANGUT COMPONENT-389;Lo;0;L;;;;;N;;;;; 18985;TANGUT COMPONENT-390;Lo;0;L;;;;;N;;;;; 18986;TANGUT COMPONENT-391;Lo;0;L;;;;;N;;;;; 18987;TANGUT COMPONENT-392;Lo;0;L;;;;;N;;;;; 18988;TANGUT COMPONENT-393;Lo;0;L;;;;;N;;;;; 18989;TANGUT COMPONENT-394;Lo;0;L;;;;;N;;;;; 1898A;TANGUT COMPONENT-395;Lo;0;L;;;;;N;;;;; 1898B;TANGUT COMPONENT-396;Lo;0;L;;;;;N;;;;; 1898C;TANGUT COMPONENT-397;Lo;0;L;;;;;N;;;;; 1898D;TANGUT COMPONENT-398;Lo;0;L;;;;;N;;;;; 1898E;TANGUT COMPONENT-399;Lo;0;L;;;;;N;;;;; 1898F;TANGUT COMPONENT-400;Lo;0;L;;;;;N;;;;; 18990;TANGUT COMPONENT-401;Lo;0;L;;;;;N;;;;; 18991;TANGUT COMPONENT-402;Lo;0;L;;;;;N;;;;; 18992;TANGUT COMPONENT-403;Lo;0;L;;;;;N;;;;; 18993;TANGUT COMPONENT-404;Lo;0;L;;;;;N;;;;; 18994;TANGUT COMPONENT-405;Lo;0;L;;;;;N;;;;; 18995;TANGUT COMPONENT-406;Lo;0;L;;;;;N;;;;; 18996;TANGUT COMPONENT-407;Lo;0;L;;;;;N;;;;; 18997;TANGUT COMPONENT-408;Lo;0;L;;;;;N;;;;; 18998;TANGUT COMPONENT-409;Lo;0;L;;;;;N;;;;; 18999;TANGUT COMPONENT-410;Lo;0;L;;;;;N;;;;; 1899A;TANGUT COMPONENT-411;Lo;0;L;;;;;N;;;;; 1899B;TANGUT COMPONENT-412;Lo;0;L;;;;;N;;;;; 1899C;TANGUT COMPONENT-413;Lo;0;L;;;;;N;;;;; 1899D;TANGUT COMPONENT-414;Lo;0;L;;;;;N;;;;; 1899E;TANGUT COMPONENT-415;Lo;0;L;;;;;N;;;;; 1899F;TANGUT COMPONENT-416;Lo;0;L;;;;;N;;;;; 189A0;TANGUT COMPONENT-417;Lo;0;L;;;;;N;;;;; 189A1;TANGUT COMPONENT-418;Lo;0;L;;;;;N;;;;; 189A2;TANGUT COMPONENT-419;Lo;0;L;;;;;N;;;;; 189A3;TANGUT COMPONENT-420;Lo;0;L;;;;;N;;;;; 189A4;TANGUT COMPONENT-421;Lo;0;L;;;;;N;;;;; 189A5;TANGUT COMPONENT-422;Lo;0;L;;;;;N;;;;; 189A6;TANGUT COMPONENT-423;Lo;0;L;;;;;N;;;;; 189A7;TANGUT COMPONENT-424;Lo;0;L;;;;;N;;;;; 189A8;TANGUT COMPONENT-425;Lo;0;L;;;;;N;;;;; 189A9;TANGUT COMPONENT-426;Lo;0;L;;;;;N;;;;; 189AA;TANGUT COMPONENT-427;Lo;0;L;;;;;N;;;;; 189AB;TANGUT COMPONENT-428;Lo;0;L;;;;;N;;;;; 189AC;TANGUT COMPONENT-429;Lo;0;L;;;;;N;;;;; 189AD;TANGUT COMPONENT-430;Lo;0;L;;;;;N;;;;; 189AE;TANGUT COMPONENT-431;Lo;0;L;;;;;N;;;;; 189AF;TANGUT COMPONENT-432;Lo;0;L;;;;;N;;;;; 189B0;TANGUT COMPONENT-433;Lo;0;L;;;;;N;;;;; 189B1;TANGUT COMPONENT-434;Lo;0;L;;;;;N;;;;; 189B2;TANGUT COMPONENT-435;Lo;0;L;;;;;N;;;;; 189B3;TANGUT COMPONENT-436;Lo;0;L;;;;;N;;;;; 189B4;TANGUT COMPONENT-437;Lo;0;L;;;;;N;;;;; 189B5;TANGUT COMPONENT-438;Lo;0;L;;;;;N;;;;; 189B6;TANGUT COMPONENT-439;Lo;0;L;;;;;N;;;;; 189B7;TANGUT COMPONENT-440;Lo;0;L;;;;;N;;;;; 189B8;TANGUT COMPONENT-441;Lo;0;L;;;;;N;;;;; 189B9;TANGUT COMPONENT-442;Lo;0;L;;;;;N;;;;; 189BA;TANGUT COMPONENT-443;Lo;0;L;;;;;N;;;;; 189BB;TANGUT COMPONENT-444;Lo;0;L;;;;;N;;;;; 189BC;TANGUT COMPONENT-445;Lo;0;L;;;;;N;;;;; 189BD;TANGUT COMPONENT-446;Lo;0;L;;;;;N;;;;; 189BE;TANGUT COMPONENT-447;Lo;0;L;;;;;N;;;;; 189BF;TANGUT COMPONENT-448;Lo;0;L;;;;;N;;;;; 189C0;TANGUT COMPONENT-449;Lo;0;L;;;;;N;;;;; 189C1;TANGUT COMPONENT-450;Lo;0;L;;;;;N;;;;; 189C2;TANGUT COMPONENT-451;Lo;0;L;;;;;N;;;;; 189C3;TANGUT COMPONENT-452;Lo;0;L;;;;;N;;;;; 189C4;TANGUT COMPONENT-453;Lo;0;L;;;;;N;;;;; 189C5;TANGUT COMPONENT-454;Lo;0;L;;;;;N;;;;; 189C6;TANGUT COMPONENT-455;Lo;0;L;;;;;N;;;;; 189C7;TANGUT COMPONENT-456;Lo;0;L;;;;;N;;;;; 189C8;TANGUT COMPONENT-457;Lo;0;L;;;;;N;;;;; 189C9;TANGUT COMPONENT-458;Lo;0;L;;;;;N;;;;; 189CA;TANGUT COMPONENT-459;Lo;0;L;;;;;N;;;;; 189CB;TANGUT COMPONENT-460;Lo;0;L;;;;;N;;;;; 189CC;TANGUT COMPONENT-461;Lo;0;L;;;;;N;;;;; 189CD;TANGUT COMPONENT-462;Lo;0;L;;;;;N;;;;; 189CE;TANGUT COMPONENT-463;Lo;0;L;;;;;N;;;;; 189CF;TANGUT COMPONENT-464;Lo;0;L;;;;;N;;;;; 189D0;TANGUT COMPONENT-465;Lo;0;L;;;;;N;;;;; 189D1;TANGUT COMPONENT-466;Lo;0;L;;;;;N;;;;; 189D2;TANGUT COMPONENT-467;Lo;0;L;;;;;N;;;;; 189D3;TANGUT COMPONENT-468;Lo;0;L;;;;;N;;;;; 189D4;TANGUT COMPONENT-469;Lo;0;L;;;;;N;;;;; 189D5;TANGUT COMPONENT-470;Lo;0;L;;;;;N;;;;; 189D6;TANGUT COMPONENT-471;Lo;0;L;;;;;N;;;;; 189D7;TANGUT COMPONENT-472;Lo;0;L;;;;;N;;;;; 189D8;TANGUT COMPONENT-473;Lo;0;L;;;;;N;;;;; 189D9;TANGUT COMPONENT-474;Lo;0;L;;;;;N;;;;; 189DA;TANGUT COMPONENT-475;Lo;0;L;;;;;N;;;;; 189DB;TANGUT COMPONENT-476;Lo;0;L;;;;;N;;;;; 189DC;TANGUT COMPONENT-477;Lo;0;L;;;;;N;;;;; 189DD;TANGUT COMPONENT-478;Lo;0;L;;;;;N;;;;; 189DE;TANGUT COMPONENT-479;Lo;0;L;;;;;N;;;;; 189DF;TANGUT COMPONENT-480;Lo;0;L;;;;;N;;;;; 189E0;TANGUT COMPONENT-481;Lo;0;L;;;;;N;;;;; 189E1;TANGUT COMPONENT-482;Lo;0;L;;;;;N;;;;; 189E2;TANGUT COMPONENT-483;Lo;0;L;;;;;N;;;;; 189E3;TANGUT COMPONENT-484;Lo;0;L;;;;;N;;;;; 189E4;TANGUT COMPONENT-485;Lo;0;L;;;;;N;;;;; 189E5;TANGUT COMPONENT-486;Lo;0;L;;;;;N;;;;; 189E6;TANGUT COMPONENT-487;Lo;0;L;;;;;N;;;;; 189E7;TANGUT COMPONENT-488;Lo;0;L;;;;;N;;;;; 189E8;TANGUT COMPONENT-489;Lo;0;L;;;;;N;;;;; 189E9;TANGUT COMPONENT-490;Lo;0;L;;;;;N;;;;; 189EA;TANGUT COMPONENT-491;Lo;0;L;;;;;N;;;;; 189EB;TANGUT COMPONENT-492;Lo;0;L;;;;;N;;;;; 189EC;TANGUT COMPONENT-493;Lo;0;L;;;;;N;;;;; 189ED;TANGUT COMPONENT-494;Lo;0;L;;;;;N;;;;; 189EE;TANGUT COMPONENT-495;Lo;0;L;;;;;N;;;;; 189EF;TANGUT COMPONENT-496;Lo;0;L;;;;;N;;;;; 189F0;TANGUT COMPONENT-497;Lo;0;L;;;;;N;;;;; 189F1;TANGUT COMPONENT-498;Lo;0;L;;;;;N;;;;; 189F2;TANGUT COMPONENT-499;Lo;0;L;;;;;N;;;;; 189F3;TANGUT COMPONENT-500;Lo;0;L;;;;;N;;;;; 189F4;TANGUT COMPONENT-501;Lo;0;L;;;;;N;;;;; 189F5;TANGUT COMPONENT-502;Lo;0;L;;;;;N;;;;; 189F6;TANGUT COMPONENT-503;Lo;0;L;;;;;N;;;;; 189F7;TANGUT COMPONENT-504;Lo;0;L;;;;;N;;;;; 189F8;TANGUT COMPONENT-505;Lo;0;L;;;;;N;;;;; 189F9;TANGUT COMPONENT-506;Lo;0;L;;;;;N;;;;; 189FA;TANGUT COMPONENT-507;Lo;0;L;;;;;N;;;;; 189FB;TANGUT COMPONENT-508;Lo;0;L;;;;;N;;;;; 189FC;TANGUT COMPONENT-509;Lo;0;L;;;;;N;;;;; 189FD;TANGUT COMPONENT-510;Lo;0;L;;;;;N;;;;; 189FE;TANGUT COMPONENT-511;Lo;0;L;;;;;N;;;;; 189FF;TANGUT COMPONENT-512;Lo;0;L;;;;;N;;;;; 18A00;TANGUT COMPONENT-513;Lo;0;L;;;;;N;;;;; 18A01;TANGUT COMPONENT-514;Lo;0;L;;;;;N;;;;; 18A02;TANGUT COMPONENT-515;Lo;0;L;;;;;N;;;;; 18A03;TANGUT COMPONENT-516;Lo;0;L;;;;;N;;;;; 18A04;TANGUT COMPONENT-517;Lo;0;L;;;;;N;;;;; 18A05;TANGUT COMPONENT-518;Lo;0;L;;;;;N;;;;; 18A06;TANGUT COMPONENT-519;Lo;0;L;;;;;N;;;;; 18A07;TANGUT COMPONENT-520;Lo;0;L;;;;;N;;;;; 18A08;TANGUT COMPONENT-521;Lo;0;L;;;;;N;;;;; 18A09;TANGUT COMPONENT-522;Lo;0;L;;;;;N;;;;; 18A0A;TANGUT COMPONENT-523;Lo;0;L;;;;;N;;;;; 18A0B;TANGUT COMPONENT-524;Lo;0;L;;;;;N;;;;; 18A0C;TANGUT COMPONENT-525;Lo;0;L;;;;;N;;;;; 18A0D;TANGUT COMPONENT-526;Lo;0;L;;;;;N;;;;; 18A0E;TANGUT COMPONENT-527;Lo;0;L;;;;;N;;;;; 18A0F;TANGUT COMPONENT-528;Lo;0;L;;;;;N;;;;; 18A10;TANGUT COMPONENT-529;Lo;0;L;;;;;N;;;;; 18A11;TANGUT COMPONENT-530;Lo;0;L;;;;;N;;;;; 18A12;TANGUT COMPONENT-531;Lo;0;L;;;;;N;;;;; 18A13;TANGUT COMPONENT-532;Lo;0;L;;;;;N;;;;; 18A14;TANGUT COMPONENT-533;Lo;0;L;;;;;N;;;;; 18A15;TANGUT COMPONENT-534;Lo;0;L;;;;;N;;;;; 18A16;TANGUT COMPONENT-535;Lo;0;L;;;;;N;;;;; 18A17;TANGUT COMPONENT-536;Lo;0;L;;;;;N;;;;; 18A18;TANGUT COMPONENT-537;Lo;0;L;;;;;N;;;;; 18A19;TANGUT COMPONENT-538;Lo;0;L;;;;;N;;;;; 18A1A;TANGUT COMPONENT-539;Lo;0;L;;;;;N;;;;; 18A1B;TANGUT COMPONENT-540;Lo;0;L;;;;;N;;;;; 18A1C;TANGUT COMPONENT-541;Lo;0;L;;;;;N;;;;; 18A1D;TANGUT COMPONENT-542;Lo;0;L;;;;;N;;;;; 18A1E;TANGUT COMPONENT-543;Lo;0;L;;;;;N;;;;; 18A1F;TANGUT COMPONENT-544;Lo;0;L;;;;;N;;;;; 18A20;TANGUT COMPONENT-545;Lo;0;L;;;;;N;;;;; 18A21;TANGUT COMPONENT-546;Lo;0;L;;;;;N;;;;; 18A22;TANGUT COMPONENT-547;Lo;0;L;;;;;N;;;;; 18A23;TANGUT COMPONENT-548;Lo;0;L;;;;;N;;;;; 18A24;TANGUT COMPONENT-549;Lo;0;L;;;;;N;;;;; 18A25;TANGUT COMPONENT-550;Lo;0;L;;;;;N;;;;; 18A26;TANGUT COMPONENT-551;Lo;0;L;;;;;N;;;;; 18A27;TANGUT COMPONENT-552;Lo;0;L;;;;;N;;;;; 18A28;TANGUT COMPONENT-553;Lo;0;L;;;;;N;;;;; 18A29;TANGUT COMPONENT-554;Lo;0;L;;;;;N;;;;; 18A2A;TANGUT COMPONENT-555;Lo;0;L;;;;;N;;;;; 18A2B;TANGUT COMPONENT-556;Lo;0;L;;;;;N;;;;; 18A2C;TANGUT COMPONENT-557;Lo;0;L;;;;;N;;;;; 18A2D;TANGUT COMPONENT-558;Lo;0;L;;;;;N;;;;; 18A2E;TANGUT COMPONENT-559;Lo;0;L;;;;;N;;;;; 18A2F;TANGUT COMPONENT-560;Lo;0;L;;;;;N;;;;; 18A30;TANGUT COMPONENT-561;Lo;0;L;;;;;N;;;;; 18A31;TANGUT COMPONENT-562;Lo;0;L;;;;;N;;;;; 18A32;TANGUT COMPONENT-563;Lo;0;L;;;;;N;;;;; 18A33;TANGUT COMPONENT-564;Lo;0;L;;;;;N;;;;; 18A34;TANGUT COMPONENT-565;Lo;0;L;;;;;N;;;;; 18A35;TANGUT COMPONENT-566;Lo;0;L;;;;;N;;;;; 18A36;TANGUT COMPONENT-567;Lo;0;L;;;;;N;;;;; 18A37;TANGUT COMPONENT-568;Lo;0;L;;;;;N;;;;; 18A38;TANGUT COMPONENT-569;Lo;0;L;;;;;N;;;;; 18A39;TANGUT COMPONENT-570;Lo;0;L;;;;;N;;;;; 18A3A;TANGUT COMPONENT-571;Lo;0;L;;;;;N;;;;; 18A3B;TANGUT COMPONENT-572;Lo;0;L;;;;;N;;;;; 18A3C;TANGUT COMPONENT-573;Lo;0;L;;;;;N;;;;; 18A3D;TANGUT COMPONENT-574;Lo;0;L;;;;;N;;;;; 18A3E;TANGUT COMPONENT-575;Lo;0;L;;;;;N;;;;; 18A3F;TANGUT COMPONENT-576;Lo;0;L;;;;;N;;;;; 18A40;TANGUT COMPONENT-577;Lo;0;L;;;;;N;;;;; 18A41;TANGUT COMPONENT-578;Lo;0;L;;;;;N;;;;; 18A42;TANGUT COMPONENT-579;Lo;0;L;;;;;N;;;;; 18A43;TANGUT COMPONENT-580;Lo;0;L;;;;;N;;;;; 18A44;TANGUT COMPONENT-581;Lo;0;L;;;;;N;;;;; 18A45;TANGUT COMPONENT-582;Lo;0;L;;;;;N;;;;; 18A46;TANGUT COMPONENT-583;Lo;0;L;;;;;N;;;;; 18A47;TANGUT COMPONENT-584;Lo;0;L;;;;;N;;;;; 18A48;TANGUT COMPONENT-585;Lo;0;L;;;;;N;;;;; 18A49;TANGUT COMPONENT-586;Lo;0;L;;;;;N;;;;; 18A4A;TANGUT COMPONENT-587;Lo;0;L;;;;;N;;;;; 18A4B;TANGUT COMPONENT-588;Lo;0;L;;;;;N;;;;; 18A4C;TANGUT COMPONENT-589;Lo;0;L;;;;;N;;;;; 18A4D;TANGUT COMPONENT-590;Lo;0;L;;;;;N;;;;; 18A4E;TANGUT COMPONENT-591;Lo;0;L;;;;;N;;;;; 18A4F;TANGUT COMPONENT-592;Lo;0;L;;;;;N;;;;; 18A50;TANGUT COMPONENT-593;Lo;0;L;;;;;N;;;;; 18A51;TANGUT COMPONENT-594;Lo;0;L;;;;;N;;;;; 18A52;TANGUT COMPONENT-595;Lo;0;L;;;;;N;;;;; 18A53;TANGUT COMPONENT-596;Lo;0;L;;;;;N;;;;; 18A54;TANGUT COMPONENT-597;Lo;0;L;;;;;N;;;;; 18A55;TANGUT COMPONENT-598;Lo;0;L;;;;;N;;;;; 18A56;TANGUT COMPONENT-599;Lo;0;L;;;;;N;;;;; 18A57;TANGUT COMPONENT-600;Lo;0;L;;;;;N;;;;; 18A58;TANGUT COMPONENT-601;Lo;0;L;;;;;N;;;;; 18A59;TANGUT COMPONENT-602;Lo;0;L;;;;;N;;;;; 18A5A;TANGUT COMPONENT-603;Lo;0;L;;;;;N;;;;; 18A5B;TANGUT COMPONENT-604;Lo;0;L;;;;;N;;;;; 18A5C;TANGUT COMPONENT-605;Lo;0;L;;;;;N;;;;; 18A5D;TANGUT COMPONENT-606;Lo;0;L;;;;;N;;;;; 18A5E;TANGUT COMPONENT-607;Lo;0;L;;;;;N;;;;; 18A5F;TANGUT COMPONENT-608;Lo;0;L;;;;;N;;;;; 18A60;TANGUT COMPONENT-609;Lo;0;L;;;;;N;;;;; 18A61;TANGUT COMPONENT-610;Lo;0;L;;;;;N;;;;; 18A62;TANGUT COMPONENT-611;Lo;0;L;;;;;N;;;;; 18A63;TANGUT COMPONENT-612;Lo;0;L;;;;;N;;;;; 18A64;TANGUT COMPONENT-613;Lo;0;L;;;;;N;;;;; 18A65;TANGUT COMPONENT-614;Lo;0;L;;;;;N;;;;; 18A66;TANGUT COMPONENT-615;Lo;0;L;;;;;N;;;;; 18A67;TANGUT COMPONENT-616;Lo;0;L;;;;;N;;;;; 18A68;TANGUT COMPONENT-617;Lo;0;L;;;;;N;;;;; 18A69;TANGUT COMPONENT-618;Lo;0;L;;;;;N;;;;; 18A6A;TANGUT COMPONENT-619;Lo;0;L;;;;;N;;;;; 18A6B;TANGUT COMPONENT-620;Lo;0;L;;;;;N;;;;; 18A6C;TANGUT COMPONENT-621;Lo;0;L;;;;;N;;;;; 18A6D;TANGUT COMPONENT-622;Lo;0;L;;;;;N;;;;; 18A6E;TANGUT COMPONENT-623;Lo;0;L;;;;;N;;;;; 18A6F;TANGUT COMPONENT-624;Lo;0;L;;;;;N;;;;; 18A70;TANGUT COMPONENT-625;Lo;0;L;;;;;N;;;;; 18A71;TANGUT COMPONENT-626;Lo;0;L;;;;;N;;;;; 18A72;TANGUT COMPONENT-627;Lo;0;L;;;;;N;;;;; 18A73;TANGUT COMPONENT-628;Lo;0;L;;;;;N;;;;; 18A74;TANGUT COMPONENT-629;Lo;0;L;;;;;N;;;;; 18A75;TANGUT COMPONENT-630;Lo;0;L;;;;;N;;;;; 18A76;TANGUT COMPONENT-631;Lo;0;L;;;;;N;;;;; 18A77;TANGUT COMPONENT-632;Lo;0;L;;;;;N;;;;; 18A78;TANGUT COMPONENT-633;Lo;0;L;;;;;N;;;;; 18A79;TANGUT COMPONENT-634;Lo;0;L;;;;;N;;;;; 18A7A;TANGUT COMPONENT-635;Lo;0;L;;;;;N;;;;; 18A7B;TANGUT COMPONENT-636;Lo;0;L;;;;;N;;;;; 18A7C;TANGUT COMPONENT-637;Lo;0;L;;;;;N;;;;; 18A7D;TANGUT COMPONENT-638;Lo;0;L;;;;;N;;;;; 18A7E;TANGUT COMPONENT-639;Lo;0;L;;;;;N;;;;; 18A7F;TANGUT COMPONENT-640;Lo;0;L;;;;;N;;;;; 18A80;TANGUT COMPONENT-641;Lo;0;L;;;;;N;;;;; 18A81;TANGUT COMPONENT-642;Lo;0;L;;;;;N;;;;; 18A82;TANGUT COMPONENT-643;Lo;0;L;;;;;N;;;;; 18A83;TANGUT COMPONENT-644;Lo;0;L;;;;;N;;;;; 18A84;TANGUT COMPONENT-645;Lo;0;L;;;;;N;;;;; 18A85;TANGUT COMPONENT-646;Lo;0;L;;;;;N;;;;; 18A86;TANGUT COMPONENT-647;Lo;0;L;;;;;N;;;;; 18A87;TANGUT COMPONENT-648;Lo;0;L;;;;;N;;;;; 18A88;TANGUT COMPONENT-649;Lo;0;L;;;;;N;;;;; 18A89;TANGUT COMPONENT-650;Lo;0;L;;;;;N;;;;; 18A8A;TANGUT COMPONENT-651;Lo;0;L;;;;;N;;;;; 18A8B;TANGUT COMPONENT-652;Lo;0;L;;;;;N;;;;; 18A8C;TANGUT COMPONENT-653;Lo;0;L;;;;;N;;;;; 18A8D;TANGUT COMPONENT-654;Lo;0;L;;;;;N;;;;; 18A8E;TANGUT COMPONENT-655;Lo;0;L;;;;;N;;;;; 18A8F;TANGUT COMPONENT-656;Lo;0;L;;;;;N;;;;; 18A90;TANGUT COMPONENT-657;Lo;0;L;;;;;N;;;;; 18A91;TANGUT COMPONENT-658;Lo;0;L;;;;;N;;;;; 18A92;TANGUT COMPONENT-659;Lo;0;L;;;;;N;;;;; 18A93;TANGUT COMPONENT-660;Lo;0;L;;;;;N;;;;; 18A94;TANGUT COMPONENT-661;Lo;0;L;;;;;N;;;;; 18A95;TANGUT COMPONENT-662;Lo;0;L;;;;;N;;;;; 18A96;TANGUT COMPONENT-663;Lo;0;L;;;;;N;;;;; 18A97;TANGUT COMPONENT-664;Lo;0;L;;;;;N;;;;; 18A98;TANGUT COMPONENT-665;Lo;0;L;;;;;N;;;;; 18A99;TANGUT COMPONENT-666;Lo;0;L;;;;;N;;;;; 18A9A;TANGUT COMPONENT-667;Lo;0;L;;;;;N;;;;; 18A9B;TANGUT COMPONENT-668;Lo;0;L;;;;;N;;;;; 18A9C;TANGUT COMPONENT-669;Lo;0;L;;;;;N;;;;; 18A9D;TANGUT COMPONENT-670;Lo;0;L;;;;;N;;;;; 18A9E;TANGUT COMPONENT-671;Lo;0;L;;;;;N;;;;; 18A9F;TANGUT COMPONENT-672;Lo;0;L;;;;;N;;;;; 18AA0;TANGUT COMPONENT-673;Lo;0;L;;;;;N;;;;; 18AA1;TANGUT COMPONENT-674;Lo;0;L;;;;;N;;;;; 18AA2;TANGUT COMPONENT-675;Lo;0;L;;;;;N;;;;; 18AA3;TANGUT COMPONENT-676;Lo;0;L;;;;;N;;;;; 18AA4;TANGUT COMPONENT-677;Lo;0;L;;;;;N;;;;; 18AA5;TANGUT COMPONENT-678;Lo;0;L;;;;;N;;;;; 18AA6;TANGUT COMPONENT-679;Lo;0;L;;;;;N;;;;; 18AA7;TANGUT COMPONENT-680;Lo;0;L;;;;;N;;;;; 18AA8;TANGUT COMPONENT-681;Lo;0;L;;;;;N;;;;; 18AA9;TANGUT COMPONENT-682;Lo;0;L;;;;;N;;;;; 18AAA;TANGUT COMPONENT-683;Lo;0;L;;;;;N;;;;; 18AAB;TANGUT COMPONENT-684;Lo;0;L;;;;;N;;;;; 18AAC;TANGUT COMPONENT-685;Lo;0;L;;;;;N;;;;; 18AAD;TANGUT COMPONENT-686;Lo;0;L;;;;;N;;;;; 18AAE;TANGUT COMPONENT-687;Lo;0;L;;;;;N;;;;; 18AAF;TANGUT COMPONENT-688;Lo;0;L;;;;;N;;;;; 18AB0;TANGUT COMPONENT-689;Lo;0;L;;;;;N;;;;; 18AB1;TANGUT COMPONENT-690;Lo;0;L;;;;;N;;;;; 18AB2;TANGUT COMPONENT-691;Lo;0;L;;;;;N;;;;; 18AB3;TANGUT COMPONENT-692;Lo;0;L;;;;;N;;;;; 18AB4;TANGUT COMPONENT-693;Lo;0;L;;;;;N;;;;; 18AB5;TANGUT COMPONENT-694;Lo;0;L;;;;;N;;;;; 18AB6;TANGUT COMPONENT-695;Lo;0;L;;;;;N;;;;; 18AB7;TANGUT COMPONENT-696;Lo;0;L;;;;;N;;;;; 18AB8;TANGUT COMPONENT-697;Lo;0;L;;;;;N;;;;; 18AB9;TANGUT COMPONENT-698;Lo;0;L;;;;;N;;;;; 18ABA;TANGUT COMPONENT-699;Lo;0;L;;;;;N;;;;; 18ABB;TANGUT COMPONENT-700;Lo;0;L;;;;;N;;;;; 18ABC;TANGUT COMPONENT-701;Lo;0;L;;;;;N;;;;; 18ABD;TANGUT COMPONENT-702;Lo;0;L;;;;;N;;;;; 18ABE;TANGUT COMPONENT-703;Lo;0;L;;;;;N;;;;; 18ABF;TANGUT COMPONENT-704;Lo;0;L;;;;;N;;;;; 18AC0;TANGUT COMPONENT-705;Lo;0;L;;;;;N;;;;; 18AC1;TANGUT COMPONENT-706;Lo;0;L;;;;;N;;;;; 18AC2;TANGUT COMPONENT-707;Lo;0;L;;;;;N;;;;; 18AC3;TANGUT COMPONENT-708;Lo;0;L;;;;;N;;;;; 18AC4;TANGUT COMPONENT-709;Lo;0;L;;;;;N;;;;; 18AC5;TANGUT COMPONENT-710;Lo;0;L;;;;;N;;;;; 18AC6;TANGUT COMPONENT-711;Lo;0;L;;;;;N;;;;; 18AC7;TANGUT COMPONENT-712;Lo;0;L;;;;;N;;;;; 18AC8;TANGUT COMPONENT-713;Lo;0;L;;;;;N;;;;; 18AC9;TANGUT COMPONENT-714;Lo;0;L;;;;;N;;;;; 18ACA;TANGUT COMPONENT-715;Lo;0;L;;;;;N;;;;; 18ACB;TANGUT COMPONENT-716;Lo;0;L;;;;;N;;;;; 18ACC;TANGUT COMPONENT-717;Lo;0;L;;;;;N;;;;; 18ACD;TANGUT COMPONENT-718;Lo;0;L;;;;;N;;;;; 18ACE;TANGUT COMPONENT-719;Lo;0;L;;;;;N;;;;; 18ACF;TANGUT COMPONENT-720;Lo;0;L;;;;;N;;;;; 18AD0;TANGUT COMPONENT-721;Lo;0;L;;;;;N;;;;; 18AD1;TANGUT COMPONENT-722;Lo;0;L;;;;;N;;;;; 18AD2;TANGUT COMPONENT-723;Lo;0;L;;;;;N;;;;; 18AD3;TANGUT COMPONENT-724;Lo;0;L;;;;;N;;;;; 18AD4;TANGUT COMPONENT-725;Lo;0;L;;;;;N;;;;; 18AD5;TANGUT COMPONENT-726;Lo;0;L;;;;;N;;;;; 18AD6;TANGUT COMPONENT-727;Lo;0;L;;;;;N;;;;; 18AD7;TANGUT COMPONENT-728;Lo;0;L;;;;;N;;;;; 18AD8;TANGUT COMPONENT-729;Lo;0;L;;;;;N;;;;; 18AD9;TANGUT COMPONENT-730;Lo;0;L;;;;;N;;;;; 18ADA;TANGUT COMPONENT-731;Lo;0;L;;;;;N;;;;; 18ADB;TANGUT COMPONENT-732;Lo;0;L;;;;;N;;;;; 18ADC;TANGUT COMPONENT-733;Lo;0;L;;;;;N;;;;; 18ADD;TANGUT COMPONENT-734;Lo;0;L;;;;;N;;;;; 18ADE;TANGUT COMPONENT-735;Lo;0;L;;;;;N;;;;; 18ADF;TANGUT COMPONENT-736;Lo;0;L;;;;;N;;;;; 18AE0;TANGUT COMPONENT-737;Lo;0;L;;;;;N;;;;; 18AE1;TANGUT COMPONENT-738;Lo;0;L;;;;;N;;;;; 18AE2;TANGUT COMPONENT-739;Lo;0;L;;;;;N;;;;; 18AE3;TANGUT COMPONENT-740;Lo;0;L;;;;;N;;;;; 18AE4;TANGUT COMPONENT-741;Lo;0;L;;;;;N;;;;; 18AE5;TANGUT COMPONENT-742;Lo;0;L;;;;;N;;;;; 18AE6;TANGUT COMPONENT-743;Lo;0;L;;;;;N;;;;; 18AE7;TANGUT COMPONENT-744;Lo;0;L;;;;;N;;;;; 18AE8;TANGUT COMPONENT-745;Lo;0;L;;;;;N;;;;; 18AE9;TANGUT COMPONENT-746;Lo;0;L;;;;;N;;;;; 18AEA;TANGUT COMPONENT-747;Lo;0;L;;;;;N;;;;; 18AEB;TANGUT COMPONENT-748;Lo;0;L;;;;;N;;;;; 18AEC;TANGUT COMPONENT-749;Lo;0;L;;;;;N;;;;; 18AED;TANGUT COMPONENT-750;Lo;0;L;;;;;N;;;;; 18AEE;TANGUT COMPONENT-751;Lo;0;L;;;;;N;;;;; 18AEF;TANGUT COMPONENT-752;Lo;0;L;;;;;N;;;;; 18AF0;TANGUT COMPONENT-753;Lo;0;L;;;;;N;;;;; 18AF1;TANGUT COMPONENT-754;Lo;0;L;;;;;N;;;;; 18AF2;TANGUT COMPONENT-755;Lo;0;L;;;;;N;;;;; 1B000;KATAKANA LETTER ARCHAIC E;Lo;0;L;;;;;N;;;;; 1B001;HIRAGANA LETTER ARCHAIC YE;Lo;0;L;;;;;N;;;;; 1BC00;DUPLOYAN LETTER H;Lo;0;L;;;;;N;;;;; 1BC01;DUPLOYAN LETTER X;Lo;0;L;;;;;N;;;;; 1BC02;DUPLOYAN LETTER P;Lo;0;L;;;;;N;;;;; 1BC03;DUPLOYAN LETTER T;Lo;0;L;;;;;N;;;;; 1BC04;DUPLOYAN LETTER F;Lo;0;L;;;;;N;;;;; 1BC05;DUPLOYAN LETTER K;Lo;0;L;;;;;N;;;;; 1BC06;DUPLOYAN LETTER L;Lo;0;L;;;;;N;;;;; 1BC07;DUPLOYAN LETTER B;Lo;0;L;;;;;N;;;;; 1BC08;DUPLOYAN LETTER D;Lo;0;L;;;;;N;;;;; 1BC09;DUPLOYAN LETTER V;Lo;0;L;;;;;N;;;;; 1BC0A;DUPLOYAN LETTER G;Lo;0;L;;;;;N;;;;; 1BC0B;DUPLOYAN LETTER R;Lo;0;L;;;;;N;;;;; 1BC0C;DUPLOYAN LETTER P N;Lo;0;L;;;;;N;;;;; 1BC0D;DUPLOYAN LETTER D S;Lo;0;L;;;;;N;;;;; 1BC0E;DUPLOYAN LETTER F N;Lo;0;L;;;;;N;;;;; 1BC0F;DUPLOYAN LETTER K M;Lo;0;L;;;;;N;;;;; 1BC10;DUPLOYAN LETTER R S;Lo;0;L;;;;;N;;;;; 1BC11;DUPLOYAN LETTER TH;Lo;0;L;;;;;N;;;;; 1BC12;DUPLOYAN LETTER SLOAN DH;Lo;0;L;;;;;N;;;;; 1BC13;DUPLOYAN LETTER DH;Lo;0;L;;;;;N;;;;; 1BC14;DUPLOYAN LETTER KK;Lo;0;L;;;;;N;;;;; 1BC15;DUPLOYAN LETTER SLOAN J;Lo;0;L;;;;;N;;;;; 1BC16;DUPLOYAN LETTER HL;Lo;0;L;;;;;N;;;;; 1BC17;DUPLOYAN LETTER LH;Lo;0;L;;;;;N;;;;; 1BC18;DUPLOYAN LETTER RH;Lo;0;L;;;;;N;;;;; 1BC19;DUPLOYAN LETTER M;Lo;0;L;;;;;N;;;;; 1BC1A;DUPLOYAN LETTER N;Lo;0;L;;;;;N;;;;; 1BC1B;DUPLOYAN LETTER J;Lo;0;L;;;;;N;;;;; 1BC1C;DUPLOYAN LETTER S;Lo;0;L;;;;;N;;;;; 1BC1D;DUPLOYAN LETTER M N;Lo;0;L;;;;;N;;;;; 1BC1E;DUPLOYAN LETTER N M;Lo;0;L;;;;;N;;;;; 1BC1F;DUPLOYAN LETTER J M;Lo;0;L;;;;;N;;;;; 1BC20;DUPLOYAN LETTER S J;Lo;0;L;;;;;N;;;;; 1BC21;DUPLOYAN LETTER M WITH DOT;Lo;0;L;;;;;N;;;;; 1BC22;DUPLOYAN LETTER N WITH DOT;Lo;0;L;;;;;N;;;;; 1BC23;DUPLOYAN LETTER J WITH DOT;Lo;0;L;;;;;N;;;;; 1BC24;DUPLOYAN LETTER J WITH DOTS INSIDE AND ABOVE;Lo;0;L;;;;;N;;;;; 1BC25;DUPLOYAN LETTER S WITH DOT;Lo;0;L;;;;;N;;;;; 1BC26;DUPLOYAN LETTER S WITH DOT BELOW;Lo;0;L;;;;;N;;;;; 1BC27;DUPLOYAN LETTER M S;Lo;0;L;;;;;N;;;;; 1BC28;DUPLOYAN LETTER N S;Lo;0;L;;;;;N;;;;; 1BC29;DUPLOYAN LETTER J S;Lo;0;L;;;;;N;;;;; 1BC2A;DUPLOYAN LETTER S S;Lo;0;L;;;;;N;;;;; 1BC2B;DUPLOYAN LETTER M N S;Lo;0;L;;;;;N;;;;; 1BC2C;DUPLOYAN LETTER N M S;Lo;0;L;;;;;N;;;;; 1BC2D;DUPLOYAN LETTER J M S;Lo;0;L;;;;;N;;;;; 1BC2E;DUPLOYAN LETTER S J S;Lo;0;L;;;;;N;;;;; 1BC2F;DUPLOYAN LETTER J S WITH DOT;Lo;0;L;;;;;N;;;;; 1BC30;DUPLOYAN LETTER J N;Lo;0;L;;;;;N;;;;; 1BC31;DUPLOYAN LETTER J N S;Lo;0;L;;;;;N;;;;; 1BC32;DUPLOYAN LETTER S T;Lo;0;L;;;;;N;;;;; 1BC33;DUPLOYAN LETTER S T R;Lo;0;L;;;;;N;;;;; 1BC34;DUPLOYAN LETTER S P;Lo;0;L;;;;;N;;;;; 1BC35;DUPLOYAN LETTER S P R;Lo;0;L;;;;;N;;;;; 1BC36;DUPLOYAN LETTER T S;Lo;0;L;;;;;N;;;;; 1BC37;DUPLOYAN LETTER T R S;Lo;0;L;;;;;N;;;;; 1BC38;DUPLOYAN LETTER W;Lo;0;L;;;;;N;;;;; 1BC39;DUPLOYAN LETTER WH;Lo;0;L;;;;;N;;;;; 1BC3A;DUPLOYAN LETTER W R;Lo;0;L;;;;;N;;;;; 1BC3B;DUPLOYAN LETTER S N;Lo;0;L;;;;;N;;;;; 1BC3C;DUPLOYAN LETTER S M;Lo;0;L;;;;;N;;;;; 1BC3D;DUPLOYAN LETTER K R S;Lo;0;L;;;;;N;;;;; 1BC3E;DUPLOYAN LETTER G R S;Lo;0;L;;;;;N;;;;; 1BC3F;DUPLOYAN LETTER S K;Lo;0;L;;;;;N;;;;; 1BC40;DUPLOYAN LETTER S K R;Lo;0;L;;;;;N;;;;; 1BC41;DUPLOYAN LETTER A;Lo;0;L;;;;;N;;;;; 1BC42;DUPLOYAN LETTER SLOAN OW;Lo;0;L;;;;;N;;;;; 1BC43;DUPLOYAN LETTER OA;Lo;0;L;;;;;N;;;;; 1BC44;DUPLOYAN LETTER O;Lo;0;L;;;;;N;;;;; 1BC45;DUPLOYAN LETTER AOU;Lo;0;L;;;;;N;;;;; 1BC46;DUPLOYAN LETTER I;Lo;0;L;;;;;N;;;;; 1BC47;DUPLOYAN LETTER E;Lo;0;L;;;;;N;;;;; 1BC48;DUPLOYAN LETTER IE;Lo;0;L;;;;;N;;;;; 1BC49;DUPLOYAN LETTER SHORT I;Lo;0;L;;;;;N;;;;; 1BC4A;DUPLOYAN LETTER UI;Lo;0;L;;;;;N;;;;; 1BC4B;DUPLOYAN LETTER EE;Lo;0;L;;;;;N;;;;; 1BC4C;DUPLOYAN LETTER SLOAN EH;Lo;0;L;;;;;N;;;;; 1BC4D;DUPLOYAN LETTER ROMANIAN I;Lo;0;L;;;;;N;;;;; 1BC4E;DUPLOYAN LETTER SLOAN EE;Lo;0;L;;;;;N;;;;; 1BC4F;DUPLOYAN LETTER LONG I;Lo;0;L;;;;;N;;;;; 1BC50;DUPLOYAN LETTER YE;Lo;0;L;;;;;N;;;;; 1BC51;DUPLOYAN LETTER U;Lo;0;L;;;;;N;;;;; 1BC52;DUPLOYAN LETTER EU;Lo;0;L;;;;;N;;;;; 1BC53;DUPLOYAN LETTER XW;Lo;0;L;;;;;N;;;;; 1BC54;DUPLOYAN LETTER U N;Lo;0;L;;;;;N;;;;; 1BC55;DUPLOYAN LETTER LONG U;Lo;0;L;;;;;N;;;;; 1BC56;DUPLOYAN LETTER ROMANIAN U;Lo;0;L;;;;;N;;;;; 1BC57;DUPLOYAN LETTER UH;Lo;0;L;;;;;N;;;;; 1BC58;DUPLOYAN LETTER SLOAN U;Lo;0;L;;;;;N;;;;; 1BC59;DUPLOYAN LETTER OOH;Lo;0;L;;;;;N;;;;; 1BC5A;DUPLOYAN LETTER OW;Lo;0;L;;;;;N;;;;; 1BC5B;DUPLOYAN LETTER OU;Lo;0;L;;;;;N;;;;; 1BC5C;DUPLOYAN LETTER WA;Lo;0;L;;;;;N;;;;; 1BC5D;DUPLOYAN LETTER WO;Lo;0;L;;;;;N;;;;; 1BC5E;DUPLOYAN LETTER WI;Lo;0;L;;;;;N;;;;; 1BC5F;DUPLOYAN LETTER WEI;Lo;0;L;;;;;N;;;;; 1BC60;DUPLOYAN LETTER WOW;Lo;0;L;;;;;N;;;;; 1BC61;DUPLOYAN LETTER NASAL U;Lo;0;L;;;;;N;;;;; 1BC62;DUPLOYAN LETTER NASAL O;Lo;0;L;;;;;N;;;;; 1BC63;DUPLOYAN LETTER NASAL I;Lo;0;L;;;;;N;;;;; 1BC64;DUPLOYAN LETTER NASAL A;Lo;0;L;;;;;N;;;;; 1BC65;DUPLOYAN LETTER PERNIN AN;Lo;0;L;;;;;N;;;;; 1BC66;DUPLOYAN LETTER PERNIN AM;Lo;0;L;;;;;N;;;;; 1BC67;DUPLOYAN LETTER SLOAN EN;Lo;0;L;;;;;N;;;;; 1BC68;DUPLOYAN LETTER SLOAN AN;Lo;0;L;;;;;N;;;;; 1BC69;DUPLOYAN LETTER SLOAN ON;Lo;0;L;;;;;N;;;;; 1BC6A;DUPLOYAN LETTER VOCALIC M;Lo;0;L;;;;;N;;;;; 1BC70;DUPLOYAN AFFIX LEFT HORIZONTAL SECANT;Lo;0;L;;;;;N;;;;; 1BC71;DUPLOYAN AFFIX MID HORIZONTAL SECANT;Lo;0;L;;;;;N;;;;; 1BC72;DUPLOYAN AFFIX RIGHT HORIZONTAL SECANT;Lo;0;L;;;;;N;;;;; 1BC73;DUPLOYAN AFFIX LOW VERTICAL SECANT;Lo;0;L;;;;;N;;;;; 1BC74;DUPLOYAN AFFIX MID VERTICAL SECANT;Lo;0;L;;;;;N;;;;; 1BC75;DUPLOYAN AFFIX HIGH VERTICAL SECANT;Lo;0;L;;;;;N;;;;; 1BC76;DUPLOYAN AFFIX ATTACHED SECANT;Lo;0;L;;;;;N;;;;; 1BC77;DUPLOYAN AFFIX ATTACHED LEFT-TO-RIGHT SECANT;Lo;0;L;;;;;N;;;;; 1BC78;DUPLOYAN AFFIX ATTACHED TANGENT;Lo;0;L;;;;;N;;;;; 1BC79;DUPLOYAN AFFIX ATTACHED TAIL;Lo;0;L;;;;;N;;;;; 1BC7A;DUPLOYAN AFFIX ATTACHED E HOOK;Lo;0;L;;;;;N;;;;; 1BC7B;DUPLOYAN AFFIX ATTACHED I HOOK;Lo;0;L;;;;;N;;;;; 1BC7C;DUPLOYAN AFFIX ATTACHED TANGENT HOOK;Lo;0;L;;;;;N;;;;; 1BC80;DUPLOYAN AFFIX HIGH ACUTE;Lo;0;L;;;;;N;;;;; 1BC81;DUPLOYAN AFFIX HIGH TIGHT ACUTE;Lo;0;L;;;;;N;;;;; 1BC82;DUPLOYAN AFFIX HIGH GRAVE;Lo;0;L;;;;;N;;;;; 1BC83;DUPLOYAN AFFIX HIGH LONG GRAVE;Lo;0;L;;;;;N;;;;; 1BC84;DUPLOYAN AFFIX HIGH DOT;Lo;0;L;;;;;N;;;;; 1BC85;DUPLOYAN AFFIX HIGH CIRCLE;Lo;0;L;;;;;N;;;;; 1BC86;DUPLOYAN AFFIX HIGH LINE;Lo;0;L;;;;;N;;;;; 1BC87;DUPLOYAN AFFIX HIGH WAVE;Lo;0;L;;;;;N;;;;; 1BC88;DUPLOYAN AFFIX HIGH VERTICAL;Lo;0;L;;;;;N;;;;; 1BC90;DUPLOYAN AFFIX LOW ACUTE;Lo;0;L;;;;;N;;;;; 1BC91;DUPLOYAN AFFIX LOW TIGHT ACUTE;Lo;0;L;;;;;N;;;;; 1BC92;DUPLOYAN AFFIX LOW GRAVE;Lo;0;L;;;;;N;;;;; 1BC93;DUPLOYAN AFFIX LOW LONG GRAVE;Lo;0;L;;;;;N;;;;; 1BC94;DUPLOYAN AFFIX LOW DOT;Lo;0;L;;;;;N;;;;; 1BC95;DUPLOYAN AFFIX LOW CIRCLE;Lo;0;L;;;;;N;;;;; 1BC96;DUPLOYAN AFFIX LOW LINE;Lo;0;L;;;;;N;;;;; 1BC97;DUPLOYAN AFFIX LOW WAVE;Lo;0;L;;;;;N;;;;; 1BC98;DUPLOYAN AFFIX LOW VERTICAL;Lo;0;L;;;;;N;;;;; 1BC99;DUPLOYAN AFFIX LOW ARROW;Lo;0;L;;;;;N;;;;; 1BC9C;DUPLOYAN SIGN O WITH CROSS;So;0;L;;;;;N;;;;; 1BC9D;DUPLOYAN THICK LETTER SELECTOR;Mn;0;NSM;;;;;N;;;;; 1BC9E;DUPLOYAN DOUBLE MARK;Mn;1;NSM;;;;;N;;;;; 1BC9F;DUPLOYAN PUNCTUATION CHINOOK FULL STOP;Po;0;L;;;;;N;;;;; 1BCA0;SHORTHAND FORMAT LETTER OVERLAP;Cf;0;BN;;;;;N;;;;; 1BCA1;SHORTHAND FORMAT CONTINUING OVERLAP;Cf;0;BN;;;;;N;;;;; 1BCA2;SHORTHAND FORMAT DOWN STEP;Cf;0;BN;;;;;N;;;;; 1BCA3;SHORTHAND FORMAT UP STEP;Cf;0;BN;;;;;N;;;;; 1D000;BYZANTINE MUSICAL SYMBOL PSILI;So;0;L;;;;;N;;;;; 1D001;BYZANTINE MUSICAL SYMBOL DASEIA;So;0;L;;;;;N;;;;; 1D002;BYZANTINE MUSICAL SYMBOL PERISPOMENI;So;0;L;;;;;N;;;;; 1D003;BYZANTINE MUSICAL SYMBOL OXEIA EKFONITIKON;So;0;L;;;;;N;;;;; 1D004;BYZANTINE MUSICAL SYMBOL OXEIA DIPLI;So;0;L;;;;;N;;;;; 1D005;BYZANTINE MUSICAL SYMBOL VAREIA EKFONITIKON;So;0;L;;;;;N;;;;; 1D006;BYZANTINE MUSICAL SYMBOL VAREIA DIPLI;So;0;L;;;;;N;;;;; 1D007;BYZANTINE MUSICAL SYMBOL KATHISTI;So;0;L;;;;;N;;;;; 1D008;BYZANTINE MUSICAL SYMBOL SYRMATIKI;So;0;L;;;;;N;;;;; 1D009;BYZANTINE MUSICAL SYMBOL PARAKLITIKI;So;0;L;;;;;N;;;;; 1D00A;BYZANTINE MUSICAL SYMBOL YPOKRISIS;So;0;L;;;;;N;;;;; 1D00B;BYZANTINE MUSICAL SYMBOL YPOKRISIS DIPLI;So;0;L;;;;;N;;;;; 1D00C;BYZANTINE MUSICAL SYMBOL KREMASTI;So;0;L;;;;;N;;;;; 1D00D;BYZANTINE MUSICAL SYMBOL APESO EKFONITIKON;So;0;L;;;;;N;;;;; 1D00E;BYZANTINE MUSICAL SYMBOL EXO EKFONITIKON;So;0;L;;;;;N;;;;; 1D00F;BYZANTINE MUSICAL SYMBOL TELEIA;So;0;L;;;;;N;;;;; 1D010;BYZANTINE MUSICAL SYMBOL KENTIMATA;So;0;L;;;;;N;;;;; 1D011;BYZANTINE MUSICAL SYMBOL APOSTROFOS;So;0;L;;;;;N;;;;; 1D012;BYZANTINE MUSICAL SYMBOL APOSTROFOS DIPLI;So;0;L;;;;;N;;;;; 1D013;BYZANTINE MUSICAL SYMBOL SYNEVMA;So;0;L;;;;;N;;;;; 1D014;BYZANTINE MUSICAL SYMBOL THITA;So;0;L;;;;;N;;;;; 1D015;BYZANTINE MUSICAL SYMBOL OLIGON ARCHAION;So;0;L;;;;;N;;;;; 1D016;BYZANTINE MUSICAL SYMBOL GORGON ARCHAION;So;0;L;;;;;N;;;;; 1D017;BYZANTINE MUSICAL SYMBOL PSILON;So;0;L;;;;;N;;;;; 1D018;BYZANTINE MUSICAL SYMBOL CHAMILON;So;0;L;;;;;N;;;;; 1D019;BYZANTINE MUSICAL SYMBOL VATHY;So;0;L;;;;;N;;;;; 1D01A;BYZANTINE MUSICAL SYMBOL ISON ARCHAION;So;0;L;;;;;N;;;;; 1D01B;BYZANTINE MUSICAL SYMBOL KENTIMA ARCHAION;So;0;L;;;;;N;;;;; 1D01C;BYZANTINE MUSICAL SYMBOL KENTIMATA ARCHAION;So;0;L;;;;;N;;;;; 1D01D;BYZANTINE MUSICAL SYMBOL SAXIMATA;So;0;L;;;;;N;;;;; 1D01E;BYZANTINE MUSICAL SYMBOL PARICHON;So;0;L;;;;;N;;;;; 1D01F;BYZANTINE MUSICAL SYMBOL STAVROS APODEXIA;So;0;L;;;;;N;;;;; 1D020;BYZANTINE MUSICAL SYMBOL OXEIAI ARCHAION;So;0;L;;;;;N;;;;; 1D021;BYZANTINE MUSICAL SYMBOL VAREIAI ARCHAION;So;0;L;;;;;N;;;;; 1D022;BYZANTINE MUSICAL SYMBOL APODERMA ARCHAION;So;0;L;;;;;N;;;;; 1D023;BYZANTINE MUSICAL SYMBOL APOTHEMA;So;0;L;;;;;N;;;;; 1D024;BYZANTINE MUSICAL SYMBOL KLASMA;So;0;L;;;;;N;;;;; 1D025;BYZANTINE MUSICAL SYMBOL REVMA;So;0;L;;;;;N;;;;; 1D026;BYZANTINE MUSICAL SYMBOL PIASMA ARCHAION;So;0;L;;;;;N;;;;; 1D027;BYZANTINE MUSICAL SYMBOL TINAGMA;So;0;L;;;;;N;;;;; 1D028;BYZANTINE MUSICAL SYMBOL ANATRICHISMA;So;0;L;;;;;N;;;;; 1D029;BYZANTINE MUSICAL SYMBOL SEISMA;So;0;L;;;;;N;;;;; 1D02A;BYZANTINE MUSICAL SYMBOL SYNAGMA ARCHAION;So;0;L;;;;;N;;;;; 1D02B;BYZANTINE MUSICAL SYMBOL SYNAGMA META STAVROU;So;0;L;;;;;N;;;;; 1D02C;BYZANTINE MUSICAL SYMBOL OYRANISMA ARCHAION;So;0;L;;;;;N;;;;; 1D02D;BYZANTINE MUSICAL SYMBOL THEMA;So;0;L;;;;;N;;;;; 1D02E;BYZANTINE MUSICAL SYMBOL LEMOI;So;0;L;;;;;N;;;;; 1D02F;BYZANTINE MUSICAL SYMBOL DYO;So;0;L;;;;;N;;;;; 1D030;BYZANTINE MUSICAL SYMBOL TRIA;So;0;L;;;;;N;;;;; 1D031;BYZANTINE MUSICAL SYMBOL TESSERA;So;0;L;;;;;N;;;;; 1D032;BYZANTINE MUSICAL SYMBOL KRATIMATA;So;0;L;;;;;N;;;;; 1D033;BYZANTINE MUSICAL SYMBOL APESO EXO NEO;So;0;L;;;;;N;;;;; 1D034;BYZANTINE MUSICAL SYMBOL FTHORA ARCHAION;So;0;L;;;;;N;;;;; 1D035;BYZANTINE MUSICAL SYMBOL IMIFTHORA;So;0;L;;;;;N;;;;; 1D036;BYZANTINE MUSICAL SYMBOL TROMIKON ARCHAION;So;0;L;;;;;N;;;;; 1D037;BYZANTINE MUSICAL SYMBOL KATAVA TROMIKON;So;0;L;;;;;N;;;;; 1D038;BYZANTINE MUSICAL SYMBOL PELASTON;So;0;L;;;;;N;;;;; 1D039;BYZANTINE MUSICAL SYMBOL PSIFISTON;So;0;L;;;;;N;;;;; 1D03A;BYZANTINE MUSICAL SYMBOL KONTEVMA;So;0;L;;;;;N;;;;; 1D03B;BYZANTINE MUSICAL SYMBOL CHOREVMA ARCHAION;So;0;L;;;;;N;;;;; 1D03C;BYZANTINE MUSICAL SYMBOL RAPISMA;So;0;L;;;;;N;;;;; 1D03D;BYZANTINE MUSICAL SYMBOL PARAKALESMA ARCHAION;So;0;L;;;;;N;;;;; 1D03E;BYZANTINE MUSICAL SYMBOL PARAKLITIKI ARCHAION;So;0;L;;;;;N;;;;; 1D03F;BYZANTINE MUSICAL SYMBOL ICHADIN;So;0;L;;;;;N;;;;; 1D040;BYZANTINE MUSICAL SYMBOL NANA;So;0;L;;;;;N;;;;; 1D041;BYZANTINE MUSICAL SYMBOL PETASMA;So;0;L;;;;;N;;;;; 1D042;BYZANTINE MUSICAL SYMBOL KONTEVMA ALLO;So;0;L;;;;;N;;;;; 1D043;BYZANTINE MUSICAL SYMBOL TROMIKON ALLO;So;0;L;;;;;N;;;;; 1D044;BYZANTINE MUSICAL SYMBOL STRAGGISMATA;So;0;L;;;;;N;;;;; 1D045;BYZANTINE MUSICAL SYMBOL GRONTHISMATA;So;0;L;;;;;N;;;;; 1D046;BYZANTINE MUSICAL SYMBOL ISON NEO;So;0;L;;;;;N;;;;; 1D047;BYZANTINE MUSICAL SYMBOL OLIGON NEO;So;0;L;;;;;N;;;;; 1D048;BYZANTINE MUSICAL SYMBOL OXEIA NEO;So;0;L;;;;;N;;;;; 1D049;BYZANTINE MUSICAL SYMBOL PETASTI;So;0;L;;;;;N;;;;; 1D04A;BYZANTINE MUSICAL SYMBOL KOUFISMA;So;0;L;;;;;N;;;;; 1D04B;BYZANTINE MUSICAL SYMBOL PETASTOKOUFISMA;So;0;L;;;;;N;;;;; 1D04C;BYZANTINE MUSICAL SYMBOL KRATIMOKOUFISMA;So;0;L;;;;;N;;;;; 1D04D;BYZANTINE MUSICAL SYMBOL PELASTON NEO;So;0;L;;;;;N;;;;; 1D04E;BYZANTINE MUSICAL SYMBOL KENTIMATA NEO ANO;So;0;L;;;;;N;;;;; 1D04F;BYZANTINE MUSICAL SYMBOL KENTIMA NEO ANO;So;0;L;;;;;N;;;;; 1D050;BYZANTINE MUSICAL SYMBOL YPSILI;So;0;L;;;;;N;;;;; 1D051;BYZANTINE MUSICAL SYMBOL APOSTROFOS NEO;So;0;L;;;;;N;;;;; 1D052;BYZANTINE MUSICAL SYMBOL APOSTROFOI SYNDESMOS NEO;So;0;L;;;;;N;;;;; 1D053;BYZANTINE MUSICAL SYMBOL YPORROI;So;0;L;;;;;N;;;;; 1D054;BYZANTINE MUSICAL SYMBOL KRATIMOYPORROON;So;0;L;;;;;N;;;;; 1D055;BYZANTINE MUSICAL SYMBOL ELAFRON;So;0;L;;;;;N;;;;; 1D056;BYZANTINE MUSICAL SYMBOL CHAMILI;So;0;L;;;;;N;;;;; 1D057;BYZANTINE MUSICAL SYMBOL MIKRON ISON;So;0;L;;;;;N;;;;; 1D058;BYZANTINE MUSICAL SYMBOL VAREIA NEO;So;0;L;;;;;N;;;;; 1D059;BYZANTINE MUSICAL SYMBOL PIASMA NEO;So;0;L;;;;;N;;;;; 1D05A;BYZANTINE MUSICAL SYMBOL PSIFISTON NEO;So;0;L;;;;;N;;;;; 1D05B;BYZANTINE MUSICAL SYMBOL OMALON;So;0;L;;;;;N;;;;; 1D05C;BYZANTINE MUSICAL SYMBOL ANTIKENOMA;So;0;L;;;;;N;;;;; 1D05D;BYZANTINE MUSICAL SYMBOL LYGISMA;So;0;L;;;;;N;;;;; 1D05E;BYZANTINE MUSICAL SYMBOL PARAKLITIKI NEO;So;0;L;;;;;N;;;;; 1D05F;BYZANTINE MUSICAL SYMBOL PARAKALESMA NEO;So;0;L;;;;;N;;;;; 1D060;BYZANTINE MUSICAL SYMBOL ETERON PARAKALESMA;So;0;L;;;;;N;;;;; 1D061;BYZANTINE MUSICAL SYMBOL KYLISMA;So;0;L;;;;;N;;;;; 1D062;BYZANTINE MUSICAL SYMBOL ANTIKENOKYLISMA;So;0;L;;;;;N;;;;; 1D063;BYZANTINE MUSICAL SYMBOL TROMIKON NEO;So;0;L;;;;;N;;;;; 1D064;BYZANTINE MUSICAL SYMBOL EKSTREPTON;So;0;L;;;;;N;;;;; 1D065;BYZANTINE MUSICAL SYMBOL SYNAGMA NEO;So;0;L;;;;;N;;;;; 1D066;BYZANTINE MUSICAL SYMBOL SYRMA;So;0;L;;;;;N;;;;; 1D067;BYZANTINE MUSICAL SYMBOL CHOREVMA NEO;So;0;L;;;;;N;;;;; 1D068;BYZANTINE MUSICAL SYMBOL EPEGERMA;So;0;L;;;;;N;;;;; 1D069;BYZANTINE MUSICAL SYMBOL SEISMA NEO;So;0;L;;;;;N;;;;; 1D06A;BYZANTINE MUSICAL SYMBOL XIRON KLASMA;So;0;L;;;;;N;;;;; 1D06B;BYZANTINE MUSICAL SYMBOL TROMIKOPSIFISTON;So;0;L;;;;;N;;;;; 1D06C;BYZANTINE MUSICAL SYMBOL PSIFISTOLYGISMA;So;0;L;;;;;N;;;;; 1D06D;BYZANTINE MUSICAL SYMBOL TROMIKOLYGISMA;So;0;L;;;;;N;;;;; 1D06E;BYZANTINE MUSICAL SYMBOL TROMIKOPARAKALESMA;So;0;L;;;;;N;;;;; 1D06F;BYZANTINE MUSICAL SYMBOL PSIFISTOPARAKALESMA;So;0;L;;;;;N;;;;; 1D070;BYZANTINE MUSICAL SYMBOL TROMIKOSYNAGMA;So;0;L;;;;;N;;;;; 1D071;BYZANTINE MUSICAL SYMBOL PSIFISTOSYNAGMA;So;0;L;;;;;N;;;;; 1D072;BYZANTINE MUSICAL SYMBOL GORGOSYNTHETON;So;0;L;;;;;N;;;;; 1D073;BYZANTINE MUSICAL SYMBOL ARGOSYNTHETON;So;0;L;;;;;N;;;;; 1D074;BYZANTINE MUSICAL SYMBOL ETERON ARGOSYNTHETON;So;0;L;;;;;N;;;;; 1D075;BYZANTINE MUSICAL SYMBOL OYRANISMA NEO;So;0;L;;;;;N;;;;; 1D076;BYZANTINE MUSICAL SYMBOL THEMATISMOS ESO;So;0;L;;;;;N;;;;; 1D077;BYZANTINE MUSICAL SYMBOL THEMATISMOS EXO;So;0;L;;;;;N;;;;; 1D078;BYZANTINE MUSICAL SYMBOL THEMA APLOUN;So;0;L;;;;;N;;;;; 1D079;BYZANTINE MUSICAL SYMBOL THES KAI APOTHES;So;0;L;;;;;N;;;;; 1D07A;BYZANTINE MUSICAL SYMBOL KATAVASMA;So;0;L;;;;;N;;;;; 1D07B;BYZANTINE MUSICAL SYMBOL ENDOFONON;So;0;L;;;;;N;;;;; 1D07C;BYZANTINE MUSICAL SYMBOL YFEN KATO;So;0;L;;;;;N;;;;; 1D07D;BYZANTINE MUSICAL SYMBOL YFEN ANO;So;0;L;;;;;N;;;;; 1D07E;BYZANTINE MUSICAL SYMBOL STAVROS;So;0;L;;;;;N;;;;; 1D07F;BYZANTINE MUSICAL SYMBOL KLASMA ANO;So;0;L;;;;;N;;;;; 1D080;BYZANTINE MUSICAL SYMBOL DIPLI ARCHAION;So;0;L;;;;;N;;;;; 1D081;BYZANTINE MUSICAL SYMBOL KRATIMA ARCHAION;So;0;L;;;;;N;;;;; 1D082;BYZANTINE MUSICAL SYMBOL KRATIMA ALLO;So;0;L;;;;;N;;;;; 1D083;BYZANTINE MUSICAL SYMBOL KRATIMA NEO;So;0;L;;;;;N;;;;; 1D084;BYZANTINE MUSICAL SYMBOL APODERMA NEO;So;0;L;;;;;N;;;;; 1D085;BYZANTINE MUSICAL SYMBOL APLI;So;0;L;;;;;N;;;;; 1D086;BYZANTINE MUSICAL SYMBOL DIPLI;So;0;L;;;;;N;;;;; 1D087;BYZANTINE MUSICAL SYMBOL TRIPLI;So;0;L;;;;;N;;;;; 1D088;BYZANTINE MUSICAL SYMBOL TETRAPLI;So;0;L;;;;;N;;;;; 1D089;BYZANTINE MUSICAL SYMBOL KORONIS;So;0;L;;;;;N;;;;; 1D08A;BYZANTINE MUSICAL SYMBOL LEIMMA ENOS CHRONOU;So;0;L;;;;;N;;;;; 1D08B;BYZANTINE MUSICAL SYMBOL LEIMMA DYO CHRONON;So;0;L;;;;;N;;;;; 1D08C;BYZANTINE MUSICAL SYMBOL LEIMMA TRION CHRONON;So;0;L;;;;;N;;;;; 1D08D;BYZANTINE MUSICAL SYMBOL LEIMMA TESSARON CHRONON;So;0;L;;;;;N;;;;; 1D08E;BYZANTINE MUSICAL SYMBOL LEIMMA IMISEOS CHRONOU;So;0;L;;;;;N;;;;; 1D08F;BYZANTINE MUSICAL SYMBOL GORGON NEO ANO;So;0;L;;;;;N;;;;; 1D090;BYZANTINE MUSICAL SYMBOL GORGON PARESTIGMENON ARISTERA;So;0;L;;;;;N;;;;; 1D091;BYZANTINE MUSICAL SYMBOL GORGON PARESTIGMENON DEXIA;So;0;L;;;;;N;;;;; 1D092;BYZANTINE MUSICAL SYMBOL DIGORGON;So;0;L;;;;;N;;;;; 1D093;BYZANTINE MUSICAL SYMBOL DIGORGON PARESTIGMENON ARISTERA KATO;So;0;L;;;;;N;;;;; 1D094;BYZANTINE MUSICAL SYMBOL DIGORGON PARESTIGMENON ARISTERA ANO;So;0;L;;;;;N;;;;; 1D095;BYZANTINE MUSICAL SYMBOL DIGORGON PARESTIGMENON DEXIA;So;0;L;;;;;N;;;;; 1D096;BYZANTINE MUSICAL SYMBOL TRIGORGON;So;0;L;;;;;N;;;;; 1D097;BYZANTINE MUSICAL SYMBOL ARGON;So;0;L;;;;;N;;;;; 1D098;BYZANTINE MUSICAL SYMBOL IMIDIARGON;So;0;L;;;;;N;;;;; 1D099;BYZANTINE MUSICAL SYMBOL DIARGON;So;0;L;;;;;N;;;;; 1D09A;BYZANTINE MUSICAL SYMBOL AGOGI POLI ARGI;So;0;L;;;;;N;;;;; 1D09B;BYZANTINE MUSICAL SYMBOL AGOGI ARGOTERI;So;0;L;;;;;N;;;;; 1D09C;BYZANTINE MUSICAL SYMBOL AGOGI ARGI;So;0;L;;;;;N;;;;; 1D09D;BYZANTINE MUSICAL SYMBOL AGOGI METRIA;So;0;L;;;;;N;;;;; 1D09E;BYZANTINE MUSICAL SYMBOL AGOGI MESI;So;0;L;;;;;N;;;;; 1D09F;BYZANTINE MUSICAL SYMBOL AGOGI GORGI;So;0;L;;;;;N;;;;; 1D0A0;BYZANTINE MUSICAL SYMBOL AGOGI GORGOTERI;So;0;L;;;;;N;;;;; 1D0A1;BYZANTINE MUSICAL SYMBOL AGOGI POLI GORGI;So;0;L;;;;;N;;;;; 1D0A2;BYZANTINE MUSICAL SYMBOL MARTYRIA PROTOS ICHOS;So;0;L;;;;;N;;;;; 1D0A3;BYZANTINE MUSICAL SYMBOL MARTYRIA ALLI PROTOS ICHOS;So;0;L;;;;;N;;;;; 1D0A4;BYZANTINE MUSICAL SYMBOL MARTYRIA DEYTEROS ICHOS;So;0;L;;;;;N;;;;; 1D0A5;BYZANTINE MUSICAL SYMBOL MARTYRIA ALLI DEYTEROS ICHOS;So;0;L;;;;;N;;;;; 1D0A6;BYZANTINE MUSICAL SYMBOL MARTYRIA TRITOS ICHOS;So;0;L;;;;;N;;;;; 1D0A7;BYZANTINE MUSICAL SYMBOL MARTYRIA TRIFONIAS;So;0;L;;;;;N;;;;; 1D0A8;BYZANTINE MUSICAL SYMBOL MARTYRIA TETARTOS ICHOS;So;0;L;;;;;N;;;;; 1D0A9;BYZANTINE MUSICAL SYMBOL MARTYRIA TETARTOS LEGETOS ICHOS;So;0;L;;;;;N;;;;; 1D0AA;BYZANTINE MUSICAL SYMBOL MARTYRIA LEGETOS ICHOS;So;0;L;;;;;N;;;;; 1D0AB;BYZANTINE MUSICAL SYMBOL MARTYRIA PLAGIOS ICHOS;So;0;L;;;;;N;;;;; 1D0AC;BYZANTINE MUSICAL SYMBOL ISAKIA TELOUS ICHIMATOS;So;0;L;;;;;N;;;;; 1D0AD;BYZANTINE MUSICAL SYMBOL APOSTROFOI TELOUS ICHIMATOS;So;0;L;;;;;N;;;;; 1D0AE;BYZANTINE MUSICAL SYMBOL FANEROSIS TETRAFONIAS;So;0;L;;;;;N;;;;; 1D0AF;BYZANTINE MUSICAL SYMBOL FANEROSIS MONOFONIAS;So;0;L;;;;;N;;;;; 1D0B0;BYZANTINE MUSICAL SYMBOL FANEROSIS DIFONIAS;So;0;L;;;;;N;;;;; 1D0B1;BYZANTINE MUSICAL SYMBOL MARTYRIA VARYS ICHOS;So;0;L;;;;;N;;;;; 1D0B2;BYZANTINE MUSICAL SYMBOL MARTYRIA PROTOVARYS ICHOS;So;0;L;;;;;N;;;;; 1D0B3;BYZANTINE MUSICAL SYMBOL MARTYRIA PLAGIOS TETARTOS ICHOS;So;0;L;;;;;N;;;;; 1D0B4;BYZANTINE MUSICAL SYMBOL GORTHMIKON N APLOUN;So;0;L;;;;;N;;;;; 1D0B5;BYZANTINE MUSICAL SYMBOL GORTHMIKON N DIPLOUN;So;0;L;;;;;N;;;;; 1D0B6;BYZANTINE MUSICAL SYMBOL ENARXIS KAI FTHORA VOU;So;0;L;;;;;N;;;;; 1D0B7;BYZANTINE MUSICAL SYMBOL IMIFONON;So;0;L;;;;;N;;;;; 1D0B8;BYZANTINE MUSICAL SYMBOL IMIFTHORON;So;0;L;;;;;N;;;;; 1D0B9;BYZANTINE MUSICAL SYMBOL FTHORA ARCHAION DEYTEROU ICHOU;So;0;L;;;;;N;;;;; 1D0BA;BYZANTINE MUSICAL SYMBOL FTHORA DIATONIKI PA;So;0;L;;;;;N;;;;; 1D0BB;BYZANTINE MUSICAL SYMBOL FTHORA DIATONIKI NANA;So;0;L;;;;;N;;;;; 1D0BC;BYZANTINE MUSICAL SYMBOL FTHORA NAOS ICHOS;So;0;L;;;;;N;;;;; 1D0BD;BYZANTINE MUSICAL SYMBOL FTHORA DIATONIKI DI;So;0;L;;;;;N;;;;; 1D0BE;BYZANTINE MUSICAL SYMBOL FTHORA SKLIRON DIATONON DI;So;0;L;;;;;N;;;;; 1D0BF;BYZANTINE MUSICAL SYMBOL FTHORA DIATONIKI KE;So;0;L;;;;;N;;;;; 1D0C0;BYZANTINE MUSICAL SYMBOL FTHORA DIATONIKI ZO;So;0;L;;;;;N;;;;; 1D0C1;BYZANTINE MUSICAL SYMBOL FTHORA DIATONIKI NI KATO;So;0;L;;;;;N;;;;; 1D0C2;BYZANTINE MUSICAL SYMBOL FTHORA DIATONIKI NI ANO;So;0;L;;;;;N;;;;; 1D0C3;BYZANTINE MUSICAL SYMBOL FTHORA MALAKON CHROMA DIFONIAS;So;0;L;;;;;N;;;;; 1D0C4;BYZANTINE MUSICAL SYMBOL FTHORA MALAKON CHROMA MONOFONIAS;So;0;L;;;;;N;;;;; 1D0C5;BYZANTINE MUSICAL SYMBOL FHTORA SKLIRON CHROMA VASIS;So;0;L;;;;;N;;;;; 1D0C6;BYZANTINE MUSICAL SYMBOL FTHORA SKLIRON CHROMA SYNAFI;So;0;L;;;;;N;;;;; 1D0C7;BYZANTINE MUSICAL SYMBOL FTHORA NENANO;So;0;L;;;;;N;;;;; 1D0C8;BYZANTINE MUSICAL SYMBOL CHROA ZYGOS;So;0;L;;;;;N;;;;; 1D0C9;BYZANTINE MUSICAL SYMBOL CHROA KLITON;So;0;L;;;;;N;;;;; 1D0CA;BYZANTINE MUSICAL SYMBOL CHROA SPATHI;So;0;L;;;;;N;;;;; 1D0CB;BYZANTINE MUSICAL SYMBOL FTHORA I YFESIS TETARTIMORION;So;0;L;;;;;N;;;;; 1D0CC;BYZANTINE MUSICAL SYMBOL FTHORA ENARMONIOS ANTIFONIA;So;0;L;;;;;N;;;;; 1D0CD;BYZANTINE MUSICAL SYMBOL YFESIS TRITIMORION;So;0;L;;;;;N;;;;; 1D0CE;BYZANTINE MUSICAL SYMBOL DIESIS TRITIMORION;So;0;L;;;;;N;;;;; 1D0CF;BYZANTINE MUSICAL SYMBOL DIESIS TETARTIMORION;So;0;L;;;;;N;;;;; 1D0D0;BYZANTINE MUSICAL SYMBOL DIESIS APLI DYO DODEKATA;So;0;L;;;;;N;;;;; 1D0D1;BYZANTINE MUSICAL SYMBOL DIESIS MONOGRAMMOS TESSERA DODEKATA;So;0;L;;;;;N;;;;; 1D0D2;BYZANTINE MUSICAL SYMBOL DIESIS DIGRAMMOS EX DODEKATA;So;0;L;;;;;N;;;;; 1D0D3;BYZANTINE MUSICAL SYMBOL DIESIS TRIGRAMMOS OKTO DODEKATA;So;0;L;;;;;N;;;;; 1D0D4;BYZANTINE MUSICAL SYMBOL YFESIS APLI DYO DODEKATA;So;0;L;;;;;N;;;;; 1D0D5;BYZANTINE MUSICAL SYMBOL YFESIS MONOGRAMMOS TESSERA DODEKATA;So;0;L;;;;;N;;;;; 1D0D6;BYZANTINE MUSICAL SYMBOL YFESIS DIGRAMMOS EX DODEKATA;So;0;L;;;;;N;;;;; 1D0D7;BYZANTINE MUSICAL SYMBOL YFESIS TRIGRAMMOS OKTO DODEKATA;So;0;L;;;;;N;;;;; 1D0D8;BYZANTINE MUSICAL SYMBOL GENIKI DIESIS;So;0;L;;;;;N;;;;; 1D0D9;BYZANTINE MUSICAL SYMBOL GENIKI YFESIS;So;0;L;;;;;N;;;;; 1D0DA;BYZANTINE MUSICAL SYMBOL DIASTOLI APLI MIKRI;So;0;L;;;;;N;;;;; 1D0DB;BYZANTINE MUSICAL SYMBOL DIASTOLI APLI MEGALI;So;0;L;;;;;N;;;;; 1D0DC;BYZANTINE MUSICAL SYMBOL DIASTOLI DIPLI;So;0;L;;;;;N;;;;; 1D0DD;BYZANTINE MUSICAL SYMBOL DIASTOLI THESEOS;So;0;L;;;;;N;;;;; 1D0DE;BYZANTINE MUSICAL SYMBOL SIMANSIS THESEOS;So;0;L;;;;;N;;;;; 1D0DF;BYZANTINE MUSICAL SYMBOL SIMANSIS THESEOS DISIMOU;So;0;L;;;;;N;;;;; 1D0E0;BYZANTINE MUSICAL SYMBOL SIMANSIS THESEOS TRISIMOU;So;0;L;;;;;N;;;;; 1D0E1;BYZANTINE MUSICAL SYMBOL SIMANSIS THESEOS TETRASIMOU;So;0;L;;;;;N;;;;; 1D0E2;BYZANTINE MUSICAL SYMBOL SIMANSIS ARSEOS;So;0;L;;;;;N;;;;; 1D0E3;BYZANTINE MUSICAL SYMBOL SIMANSIS ARSEOS DISIMOU;So;0;L;;;;;N;;;;; 1D0E4;BYZANTINE MUSICAL SYMBOL SIMANSIS ARSEOS TRISIMOU;So;0;L;;;;;N;;;;; 1D0E5;BYZANTINE MUSICAL SYMBOL SIMANSIS ARSEOS TETRASIMOU;So;0;L;;;;;N;;;;; 1D0E6;BYZANTINE MUSICAL SYMBOL DIGRAMMA GG;So;0;L;;;;;N;;;;; 1D0E7;BYZANTINE MUSICAL SYMBOL DIFTOGGOS OU;So;0;L;;;;;N;;;;; 1D0E8;BYZANTINE MUSICAL SYMBOL STIGMA;So;0;L;;;;;N;;;;; 1D0E9;BYZANTINE MUSICAL SYMBOL ARKTIKO PA;So;0;L;;;;;N;;;;; 1D0EA;BYZANTINE MUSICAL SYMBOL ARKTIKO VOU;So;0;L;;;;;N;;;;; 1D0EB;BYZANTINE MUSICAL SYMBOL ARKTIKO GA;So;0;L;;;;;N;;;;; 1D0EC;BYZANTINE MUSICAL SYMBOL ARKTIKO DI;So;0;L;;;;;N;;;;; 1D0ED;BYZANTINE MUSICAL SYMBOL ARKTIKO KE;So;0;L;;;;;N;;;;; 1D0EE;BYZANTINE MUSICAL SYMBOL ARKTIKO ZO;So;0;L;;;;;N;;;;; 1D0EF;BYZANTINE MUSICAL SYMBOL ARKTIKO NI;So;0;L;;;;;N;;;;; 1D0F0;BYZANTINE MUSICAL SYMBOL KENTIMATA NEO MESO;So;0;L;;;;;N;;;;; 1D0F1;BYZANTINE MUSICAL SYMBOL KENTIMA NEO MESO;So;0;L;;;;;N;;;;; 1D0F2;BYZANTINE MUSICAL SYMBOL KENTIMATA NEO KATO;So;0;L;;;;;N;;;;; 1D0F3;BYZANTINE MUSICAL SYMBOL KENTIMA NEO KATO;So;0;L;;;;;N;;;;; 1D0F4;BYZANTINE MUSICAL SYMBOL KLASMA KATO;So;0;L;;;;;N;;;;; 1D0F5;BYZANTINE MUSICAL SYMBOL GORGON NEO KATO;So;0;L;;;;;N;;;;; 1D100;MUSICAL SYMBOL SINGLE BARLINE;So;0;L;;;;;N;;;;; 1D101;MUSICAL SYMBOL DOUBLE BARLINE;So;0;L;;;;;N;;;;; 1D102;MUSICAL SYMBOL FINAL BARLINE;So;0;L;;;;;N;;;;; 1D103;MUSICAL SYMBOL REVERSE FINAL BARLINE;So;0;L;;;;;N;;;;; 1D104;MUSICAL SYMBOL DASHED BARLINE;So;0;L;;;;;N;;;;; 1D105;MUSICAL SYMBOL SHORT BARLINE;So;0;L;;;;;N;;;;; 1D106;MUSICAL SYMBOL LEFT REPEAT SIGN;So;0;L;;;;;N;;;;; 1D107;MUSICAL SYMBOL RIGHT REPEAT SIGN;So;0;L;;;;;N;;;;; 1D108;MUSICAL SYMBOL REPEAT DOTS;So;0;L;;;;;N;;;;; 1D109;MUSICAL SYMBOL DAL SEGNO;So;0;L;;;;;N;;;;; 1D10A;MUSICAL SYMBOL DA CAPO;So;0;L;;;;;N;;;;; 1D10B;MUSICAL SYMBOL SEGNO;So;0;L;;;;;N;;;;; 1D10C;MUSICAL SYMBOL CODA;So;0;L;;;;;N;;;;; 1D10D;MUSICAL SYMBOL REPEATED FIGURE-1;So;0;L;;;;;N;;;;; 1D10E;MUSICAL SYMBOL REPEATED FIGURE-2;So;0;L;;;;;N;;;;; 1D10F;MUSICAL SYMBOL REPEATED FIGURE-3;So;0;L;;;;;N;;;;; 1D110;MUSICAL SYMBOL FERMATA;So;0;L;;;;;N;;;;; 1D111;MUSICAL SYMBOL FERMATA BELOW;So;0;L;;;;;N;;;;; 1D112;MUSICAL SYMBOL BREATH MARK;So;0;L;;;;;N;;;;; 1D113;MUSICAL SYMBOL CAESURA;So;0;L;;;;;N;;;;; 1D114;MUSICAL SYMBOL BRACE;So;0;L;;;;;N;;;;; 1D115;MUSICAL SYMBOL BRACKET;So;0;L;;;;;N;;;;; 1D116;MUSICAL SYMBOL ONE-LINE STAFF;So;0;L;;;;;N;;;;; 1D117;MUSICAL SYMBOL TWO-LINE STAFF;So;0;L;;;;;N;;;;; 1D118;MUSICAL SYMBOL THREE-LINE STAFF;So;0;L;;;;;N;;;;; 1D119;MUSICAL SYMBOL FOUR-LINE STAFF;So;0;L;;;;;N;;;;; 1D11A;MUSICAL SYMBOL FIVE-LINE STAFF;So;0;L;;;;;N;;;;; 1D11B;MUSICAL SYMBOL SIX-LINE STAFF;So;0;L;;;;;N;;;;; 1D11C;MUSICAL SYMBOL SIX-STRING FRETBOARD;So;0;L;;;;;N;;;;; 1D11D;MUSICAL SYMBOL FOUR-STRING FRETBOARD;So;0;L;;;;;N;;;;; 1D11E;MUSICAL SYMBOL G CLEF;So;0;L;;;;;N;;;;; 1D11F;MUSICAL SYMBOL G CLEF OTTAVA ALTA;So;0;L;;;;;N;;;;; 1D120;MUSICAL SYMBOL G CLEF OTTAVA BASSA;So;0;L;;;;;N;;;;; 1D121;MUSICAL SYMBOL C CLEF;So;0;L;;;;;N;;;;; 1D122;MUSICAL SYMBOL F CLEF;So;0;L;;;;;N;;;;; 1D123;MUSICAL SYMBOL F CLEF OTTAVA ALTA;So;0;L;;;;;N;;;;; 1D124;MUSICAL SYMBOL F CLEF OTTAVA BASSA;So;0;L;;;;;N;;;;; 1D125;MUSICAL SYMBOL DRUM CLEF-1;So;0;L;;;;;N;;;;; 1D126;MUSICAL SYMBOL DRUM CLEF-2;So;0;L;;;;;N;;;;; 1D129;MUSICAL SYMBOL MULTIPLE MEASURE REST;So;0;L;;;;;N;;;;; 1D12A;MUSICAL SYMBOL DOUBLE SHARP;So;0;L;;;;;N;;;;; 1D12B;MUSICAL SYMBOL DOUBLE FLAT;So;0;L;;;;;N;;;;; 1D12C;MUSICAL SYMBOL FLAT UP;So;0;L;;;;;N;;;;; 1D12D;MUSICAL SYMBOL FLAT DOWN;So;0;L;;;;;N;;;;; 1D12E;MUSICAL SYMBOL NATURAL UP;So;0;L;;;;;N;;;;; 1D12F;MUSICAL SYMBOL NATURAL DOWN;So;0;L;;;;;N;;;;; 1D130;MUSICAL SYMBOL SHARP UP;So;0;L;;;;;N;;;;; 1D131;MUSICAL SYMBOL SHARP DOWN;So;0;L;;;;;N;;;;; 1D132;MUSICAL SYMBOL QUARTER TONE SHARP;So;0;L;;;;;N;;;;; 1D133;MUSICAL SYMBOL QUARTER TONE FLAT;So;0;L;;;;;N;;;;; 1D134;MUSICAL SYMBOL COMMON TIME;So;0;L;;;;;N;;;;; 1D135;MUSICAL SYMBOL CUT TIME;So;0;L;;;;;N;;;;; 1D136;MUSICAL SYMBOL OTTAVA ALTA;So;0;L;;;;;N;;;;; 1D137;MUSICAL SYMBOL OTTAVA BASSA;So;0;L;;;;;N;;;;; 1D138;MUSICAL SYMBOL QUINDICESIMA ALTA;So;0;L;;;;;N;;;;; 1D139;MUSICAL SYMBOL QUINDICESIMA BASSA;So;0;L;;;;;N;;;;; 1D13A;MUSICAL SYMBOL MULTI REST;So;0;L;;;;;N;;;;; 1D13B;MUSICAL SYMBOL WHOLE REST;So;0;L;;;;;N;;;;; 1D13C;MUSICAL SYMBOL HALF REST;So;0;L;;;;;N;;;;; 1D13D;MUSICAL SYMBOL QUARTER REST;So;0;L;;;;;N;;;;; 1D13E;MUSICAL SYMBOL EIGHTH REST;So;0;L;;;;;N;;;;; 1D13F;MUSICAL SYMBOL SIXTEENTH REST;So;0;L;;;;;N;;;;; 1D140;MUSICAL SYMBOL THIRTY-SECOND REST;So;0;L;;;;;N;;;;; 1D141;MUSICAL SYMBOL SIXTY-FOURTH REST;So;0;L;;;;;N;;;;; 1D142;MUSICAL SYMBOL ONE HUNDRED TWENTY-EIGHTH REST;So;0;L;;;;;N;;;;; 1D143;MUSICAL SYMBOL X NOTEHEAD;So;0;L;;;;;N;;;;; 1D144;MUSICAL SYMBOL PLUS NOTEHEAD;So;0;L;;;;;N;;;;; 1D145;MUSICAL SYMBOL CIRCLE X NOTEHEAD;So;0;L;;;;;N;;;;; 1D146;MUSICAL SYMBOL SQUARE NOTEHEAD WHITE;So;0;L;;;;;N;;;;; 1D147;MUSICAL SYMBOL SQUARE NOTEHEAD BLACK;So;0;L;;;;;N;;;;; 1D148;MUSICAL SYMBOL TRIANGLE NOTEHEAD UP WHITE;So;0;L;;;;;N;;;;; 1D149;MUSICAL SYMBOL TRIANGLE NOTEHEAD UP BLACK;So;0;L;;;;;N;;;;; 1D14A;MUSICAL SYMBOL TRIANGLE NOTEHEAD LEFT WHITE;So;0;L;;;;;N;;;;; 1D14B;MUSICAL SYMBOL TRIANGLE NOTEHEAD LEFT BLACK;So;0;L;;;;;N;;;;; 1D14C;MUSICAL SYMBOL TRIANGLE NOTEHEAD RIGHT WHITE;So;0;L;;;;;N;;;;; 1D14D;MUSICAL SYMBOL TRIANGLE NOTEHEAD RIGHT BLACK;So;0;L;;;;;N;;;;; 1D14E;MUSICAL SYMBOL TRIANGLE NOTEHEAD DOWN WHITE;So;0;L;;;;;N;;;;; 1D14F;MUSICAL SYMBOL TRIANGLE NOTEHEAD DOWN BLACK;So;0;L;;;;;N;;;;; 1D150;MUSICAL SYMBOL TRIANGLE NOTEHEAD UP RIGHT WHITE;So;0;L;;;;;N;;;;; 1D151;MUSICAL SYMBOL TRIANGLE NOTEHEAD UP RIGHT BLACK;So;0;L;;;;;N;;;;; 1D152;MUSICAL SYMBOL MOON NOTEHEAD WHITE;So;0;L;;;;;N;;;;; 1D153;MUSICAL SYMBOL MOON NOTEHEAD BLACK;So;0;L;;;;;N;;;;; 1D154;MUSICAL SYMBOL TRIANGLE-ROUND NOTEHEAD DOWN WHITE;So;0;L;;;;;N;;;;; 1D155;MUSICAL SYMBOL TRIANGLE-ROUND NOTEHEAD DOWN BLACK;So;0;L;;;;;N;;;;; 1D156;MUSICAL SYMBOL PARENTHESIS NOTEHEAD;So;0;L;;;;;N;;;;; 1D157;MUSICAL SYMBOL VOID NOTEHEAD;So;0;L;;;;;N;;;;; 1D158;MUSICAL SYMBOL NOTEHEAD BLACK;So;0;L;;;;;N;;;;; 1D159;MUSICAL SYMBOL NULL NOTEHEAD;So;0;L;;;;;N;;;;; 1D15A;MUSICAL SYMBOL CLUSTER NOTEHEAD WHITE;So;0;L;;;;;N;;;;; 1D15B;MUSICAL SYMBOL CLUSTER NOTEHEAD BLACK;So;0;L;;;;;N;;;;; 1D15C;MUSICAL SYMBOL BREVE;So;0;L;;;;;N;;;;; 1D15D;MUSICAL SYMBOL WHOLE NOTE;So;0;L;;;;;N;;;;; 1D15E;MUSICAL SYMBOL HALF NOTE;So;0;L;1D157 1D165;;;;N;;;;; 1D15F;MUSICAL SYMBOL QUARTER NOTE;So;0;L;1D158 1D165;;;;N;;;;; 1D160;MUSICAL SYMBOL EIGHTH NOTE;So;0;L;1D15F 1D16E;;;;N;;;;; 1D161;MUSICAL SYMBOL SIXTEENTH NOTE;So;0;L;1D15F 1D16F;;;;N;;;;; 1D162;MUSICAL SYMBOL THIRTY-SECOND NOTE;So;0;L;1D15F 1D170;;;;N;;;;; 1D163;MUSICAL SYMBOL SIXTY-FOURTH NOTE;So;0;L;1D15F 1D171;;;;N;;;;; 1D164;MUSICAL SYMBOL ONE HUNDRED TWENTY-EIGHTH NOTE;So;0;L;1D15F 1D172;;;;N;;;;; 1D165;MUSICAL SYMBOL COMBINING STEM;Mc;216;L;;;;;N;;;;; 1D166;MUSICAL SYMBOL COMBINING SPRECHGESANG STEM;Mc;216;L;;;;;N;;;;; 1D167;MUSICAL SYMBOL COMBINING TREMOLO-1;Mn;1;NSM;;;;;N;;;;; 1D168;MUSICAL SYMBOL COMBINING TREMOLO-2;Mn;1;NSM;;;;;N;;;;; 1D169;MUSICAL SYMBOL COMBINING TREMOLO-3;Mn;1;NSM;;;;;N;;;;; 1D16A;MUSICAL SYMBOL FINGERED TREMOLO-1;So;0;L;;;;;N;;;;; 1D16B;MUSICAL SYMBOL FINGERED TREMOLO-2;So;0;L;;;;;N;;;;; 1D16C;MUSICAL SYMBOL FINGERED TREMOLO-3;So;0;L;;;;;N;;;;; 1D16D;MUSICAL SYMBOL COMBINING AUGMENTATION DOT;Mc;226;L;;;;;N;;;;; 1D16E;MUSICAL SYMBOL COMBINING FLAG-1;Mc;216;L;;;;;N;;;;; 1D16F;MUSICAL SYMBOL COMBINING FLAG-2;Mc;216;L;;;;;N;;;;; 1D170;MUSICAL SYMBOL COMBINING FLAG-3;Mc;216;L;;;;;N;;;;; 1D171;MUSICAL SYMBOL COMBINING FLAG-4;Mc;216;L;;;;;N;;;;; 1D172;MUSICAL SYMBOL COMBINING FLAG-5;Mc;216;L;;;;;N;;;;; 1D173;MUSICAL SYMBOL BEGIN BEAM;Cf;0;BN;;;;;N;;;;; 1D174;MUSICAL SYMBOL END BEAM;Cf;0;BN;;;;;N;;;;; 1D175;MUSICAL SYMBOL BEGIN TIE;Cf;0;BN;;;;;N;;;;; 1D176;MUSICAL SYMBOL END TIE;Cf;0;BN;;;;;N;;;;; 1D177;MUSICAL SYMBOL BEGIN SLUR;Cf;0;BN;;;;;N;;;;; 1D178;MUSICAL SYMBOL END SLUR;Cf;0;BN;;;;;N;;;;; 1D179;MUSICAL SYMBOL BEGIN PHRASE;Cf;0;BN;;;;;N;;;;; 1D17A;MUSICAL SYMBOL END PHRASE;Cf;0;BN;;;;;N;;;;; 1D17B;MUSICAL SYMBOL COMBINING ACCENT;Mn;220;NSM;;;;;N;;;;; 1D17C;MUSICAL SYMBOL COMBINING STACCATO;Mn;220;NSM;;;;;N;;;;; 1D17D;MUSICAL SYMBOL COMBINING TENUTO;Mn;220;NSM;;;;;N;;;;; 1D17E;MUSICAL SYMBOL COMBINING STACCATISSIMO;Mn;220;NSM;;;;;N;;;;; 1D17F;MUSICAL SYMBOL COMBINING MARCATO;Mn;220;NSM;;;;;N;;;;; 1D180;MUSICAL SYMBOL COMBINING MARCATO-STACCATO;Mn;220;NSM;;;;;N;;;;; 1D181;MUSICAL SYMBOL COMBINING ACCENT-STACCATO;Mn;220;NSM;;;;;N;;;;; 1D182;MUSICAL SYMBOL COMBINING LOURE;Mn;220;NSM;;;;;N;;;;; 1D183;MUSICAL SYMBOL ARPEGGIATO UP;So;0;L;;;;;N;;;;; 1D184;MUSICAL SYMBOL ARPEGGIATO DOWN;So;0;L;;;;;N;;;;; 1D185;MUSICAL SYMBOL COMBINING DOIT;Mn;230;NSM;;;;;N;;;;; 1D186;MUSICAL SYMBOL COMBINING RIP;Mn;230;NSM;;;;;N;;;;; 1D187;MUSICAL SYMBOL COMBINING FLIP;Mn;230;NSM;;;;;N;;;;; 1D188;MUSICAL SYMBOL COMBINING SMEAR;Mn;230;NSM;;;;;N;;;;; 1D189;MUSICAL SYMBOL COMBINING BEND;Mn;230;NSM;;;;;N;;;;; 1D18A;MUSICAL SYMBOL COMBINING DOUBLE TONGUE;Mn;220;NSM;;;;;N;;;;; 1D18B;MUSICAL SYMBOL COMBINING TRIPLE TONGUE;Mn;220;NSM;;;;;N;;;;; 1D18C;MUSICAL SYMBOL RINFORZANDO;So;0;L;;;;;N;;;;; 1D18D;MUSICAL SYMBOL SUBITO;So;0;L;;;;;N;;;;; 1D18E;MUSICAL SYMBOL Z;So;0;L;;;;;N;;;;; 1D18F;MUSICAL SYMBOL PIANO;So;0;L;;;;;N;;;;; 1D190;MUSICAL SYMBOL MEZZO;So;0;L;;;;;N;;;;; 1D191;MUSICAL SYMBOL FORTE;So;0;L;;;;;N;;;;; 1D192;MUSICAL SYMBOL CRESCENDO;So;0;L;;;;;N;;;;; 1D193;MUSICAL SYMBOL DECRESCENDO;So;0;L;;;;;N;;;;; 1D194;MUSICAL SYMBOL GRACE NOTE SLASH;So;0;L;;;;;N;;;;; 1D195;MUSICAL SYMBOL GRACE NOTE NO SLASH;So;0;L;;;;;N;;;;; 1D196;MUSICAL SYMBOL TR;So;0;L;;;;;N;;;;; 1D197;MUSICAL SYMBOL TURN;So;0;L;;;;;N;;;;; 1D198;MUSICAL SYMBOL INVERTED TURN;So;0;L;;;;;N;;;;; 1D199;MUSICAL SYMBOL TURN SLASH;So;0;L;;;;;N;;;;; 1D19A;MUSICAL SYMBOL TURN UP;So;0;L;;;;;N;;;;; 1D19B;MUSICAL SYMBOL ORNAMENT STROKE-1;So;0;L;;;;;N;;;;; 1D19C;MUSICAL SYMBOL ORNAMENT STROKE-2;So;0;L;;;;;N;;;;; 1D19D;MUSICAL SYMBOL ORNAMENT STROKE-3;So;0;L;;;;;N;;;;; 1D19E;MUSICAL SYMBOL ORNAMENT STROKE-4;So;0;L;;;;;N;;;;; 1D19F;MUSICAL SYMBOL ORNAMENT STROKE-5;So;0;L;;;;;N;;;;; 1D1A0;MUSICAL SYMBOL ORNAMENT STROKE-6;So;0;L;;;;;N;;;;; 1D1A1;MUSICAL SYMBOL ORNAMENT STROKE-7;So;0;L;;;;;N;;;;; 1D1A2;MUSICAL SYMBOL ORNAMENT STROKE-8;So;0;L;;;;;N;;;;; 1D1A3;MUSICAL SYMBOL ORNAMENT STROKE-9;So;0;L;;;;;N;;;;; 1D1A4;MUSICAL SYMBOL ORNAMENT STROKE-10;So;0;L;;;;;N;;;;; 1D1A5;MUSICAL SYMBOL ORNAMENT STROKE-11;So;0;L;;;;;N;;;;; 1D1A6;MUSICAL SYMBOL HAUPTSTIMME;So;0;L;;;;;N;;;;; 1D1A7;MUSICAL SYMBOL NEBENSTIMME;So;0;L;;;;;N;;;;; 1D1A8;MUSICAL SYMBOL END OF STIMME;So;0;L;;;;;N;;;;; 1D1A9;MUSICAL SYMBOL DEGREE SLASH;So;0;L;;;;;N;;;;; 1D1AA;MUSICAL SYMBOL COMBINING DOWN BOW;Mn;230;NSM;;;;;N;;;;; 1D1AB;MUSICAL SYMBOL COMBINING UP BOW;Mn;230;NSM;;;;;N;;;;; 1D1AC;MUSICAL SYMBOL COMBINING HARMONIC;Mn;230;NSM;;;;;N;;;;; 1D1AD;MUSICAL SYMBOL COMBINING SNAP PIZZICATO;Mn;230;NSM;;;;;N;;;;; 1D1AE;MUSICAL SYMBOL PEDAL MARK;So;0;L;;;;;N;;;;; 1D1AF;MUSICAL SYMBOL PEDAL UP MARK;So;0;L;;;;;N;;;;; 1D1B0;MUSICAL SYMBOL HALF PEDAL MARK;So;0;L;;;;;N;;;;; 1D1B1;MUSICAL SYMBOL GLISSANDO UP;So;0;L;;;;;N;;;;; 1D1B2;MUSICAL SYMBOL GLISSANDO DOWN;So;0;L;;;;;N;;;;; 1D1B3;MUSICAL SYMBOL WITH FINGERNAILS;So;0;L;;;;;N;;;;; 1D1B4;MUSICAL SYMBOL DAMP;So;0;L;;;;;N;;;;; 1D1B5;MUSICAL SYMBOL DAMP ALL;So;0;L;;;;;N;;;;; 1D1B6;MUSICAL SYMBOL MAXIMA;So;0;L;;;;;N;;;;; 1D1B7;MUSICAL SYMBOL LONGA;So;0;L;;;;;N;;;;; 1D1B8;MUSICAL SYMBOL BREVIS;So;0;L;;;;;N;;;;; 1D1B9;MUSICAL SYMBOL SEMIBREVIS WHITE;So;0;L;;;;;N;;;;; 1D1BA;MUSICAL SYMBOL SEMIBREVIS BLACK;So;0;L;;;;;N;;;;; 1D1BB;MUSICAL SYMBOL MINIMA;So;0;L;1D1B9 1D165;;;;N;;;;; 1D1BC;MUSICAL SYMBOL MINIMA BLACK;So;0;L;1D1BA 1D165;;;;N;;;;; 1D1BD;MUSICAL SYMBOL SEMIMINIMA WHITE;So;0;L;1D1BB 1D16E;;;;N;;;;; 1D1BE;MUSICAL SYMBOL SEMIMINIMA BLACK;So;0;L;1D1BC 1D16E;;;;N;;;;; 1D1BF;MUSICAL SYMBOL FUSA WHITE;So;0;L;1D1BB 1D16F;;;;N;;;;; 1D1C0;MUSICAL SYMBOL FUSA BLACK;So;0;L;1D1BC 1D16F;;;;N;;;;; 1D1C1;MUSICAL SYMBOL LONGA PERFECTA REST;So;0;L;;;;;N;;;;; 1D1C2;MUSICAL SYMBOL LONGA IMPERFECTA REST;So;0;L;;;;;N;;;;; 1D1C3;MUSICAL SYMBOL BREVIS REST;So;0;L;;;;;N;;;;; 1D1C4;MUSICAL SYMBOL SEMIBREVIS REST;So;0;L;;;;;N;;;;; 1D1C5;MUSICAL SYMBOL MINIMA REST;So;0;L;;;;;N;;;;; 1D1C6;MUSICAL SYMBOL SEMIMINIMA REST;So;0;L;;;;;N;;;;; 1D1C7;MUSICAL SYMBOL TEMPUS PERFECTUM CUM PROLATIONE PERFECTA;So;0;L;;;;;N;;;;; 1D1C8;MUSICAL SYMBOL TEMPUS PERFECTUM CUM PROLATIONE IMPERFECTA;So;0;L;;;;;N;;;;; 1D1C9;MUSICAL SYMBOL TEMPUS PERFECTUM CUM PROLATIONE PERFECTA DIMINUTION-1;So;0;L;;;;;N;;;;; 1D1CA;MUSICAL SYMBOL TEMPUS IMPERFECTUM CUM PROLATIONE PERFECTA;So;0;L;;;;;N;;;;; 1D1CB;MUSICAL SYMBOL TEMPUS IMPERFECTUM CUM PROLATIONE IMPERFECTA;So;0;L;;;;;N;;;;; 1D1CC;MUSICAL SYMBOL TEMPUS IMPERFECTUM CUM PROLATIONE IMPERFECTA DIMINUTION-1;So;0;L;;;;;N;;;;; 1D1CD;MUSICAL SYMBOL TEMPUS IMPERFECTUM CUM PROLATIONE IMPERFECTA DIMINUTION-2;So;0;L;;;;;N;;;;; 1D1CE;MUSICAL SYMBOL TEMPUS IMPERFECTUM CUM PROLATIONE IMPERFECTA DIMINUTION-3;So;0;L;;;;;N;;;;; 1D1CF;MUSICAL SYMBOL CROIX;So;0;L;;;;;N;;;;; 1D1D0;MUSICAL SYMBOL GREGORIAN C CLEF;So;0;L;;;;;N;;;;; 1D1D1;MUSICAL SYMBOL GREGORIAN F CLEF;So;0;L;;;;;N;;;;; 1D1D2;MUSICAL SYMBOL SQUARE B;So;0;L;;;;;N;;;;; 1D1D3;MUSICAL SYMBOL VIRGA;So;0;L;;;;;N;;;;; 1D1D4;MUSICAL SYMBOL PODATUS;So;0;L;;;;;N;;;;; 1D1D5;MUSICAL SYMBOL CLIVIS;So;0;L;;;;;N;;;;; 1D1D6;MUSICAL SYMBOL SCANDICUS;So;0;L;;;;;N;;;;; 1D1D7;MUSICAL SYMBOL CLIMACUS;So;0;L;;;;;N;;;;; 1D1D8;MUSICAL SYMBOL TORCULUS;So;0;L;;;;;N;;;;; 1D1D9;MUSICAL SYMBOL PORRECTUS;So;0;L;;;;;N;;;;; 1D1DA;MUSICAL SYMBOL PORRECTUS FLEXUS;So;0;L;;;;;N;;;;; 1D1DB;MUSICAL SYMBOL SCANDICUS FLEXUS;So;0;L;;;;;N;;;;; 1D1DC;MUSICAL SYMBOL TORCULUS RESUPINUS;So;0;L;;;;;N;;;;; 1D1DD;MUSICAL SYMBOL PES SUBPUNCTIS;So;0;L;;;;;N;;;;; 1D1DE;MUSICAL SYMBOL KIEVAN C CLEF;So;0;L;;;;;N;;;;; 1D1DF;MUSICAL SYMBOL KIEVAN END OF PIECE;So;0;L;;;;;N;;;;; 1D1E0;MUSICAL SYMBOL KIEVAN FINAL NOTE;So;0;L;;;;;N;;;;; 1D1E1;MUSICAL SYMBOL KIEVAN RECITATIVE MARK;So;0;L;;;;;N;;;;; 1D1E2;MUSICAL SYMBOL KIEVAN WHOLE NOTE;So;0;L;;;;;N;;;;; 1D1E3;MUSICAL SYMBOL KIEVAN HALF NOTE;So;0;L;;;;;N;;;;; 1D1E4;MUSICAL SYMBOL KIEVAN QUARTER NOTE STEM DOWN;So;0;L;;;;;N;;;;; 1D1E5;MUSICAL SYMBOL KIEVAN QUARTER NOTE STEM UP;So;0;L;;;;;N;;;;; 1D1E6;MUSICAL SYMBOL KIEVAN EIGHTH NOTE STEM DOWN;So;0;L;;;;;N;;;;; 1D1E7;MUSICAL SYMBOL KIEVAN EIGHTH NOTE STEM UP;So;0;L;;;;;N;;;;; 1D1E8;MUSICAL SYMBOL KIEVAN FLAT SIGN;So;0;L;;;;;N;;;;; 1D200;GREEK VOCAL NOTATION SYMBOL-1;So;0;ON;;;;;N;;;;; 1D201;GREEK VOCAL NOTATION SYMBOL-2;So;0;ON;;;;;N;;;;; 1D202;GREEK VOCAL NOTATION SYMBOL-3;So;0;ON;;;;;N;;;;; 1D203;GREEK VOCAL NOTATION SYMBOL-4;So;0;ON;;;;;N;;;;; 1D204;GREEK VOCAL NOTATION SYMBOL-5;So;0;ON;;;;;N;;;;; 1D205;GREEK VOCAL NOTATION SYMBOL-6;So;0;ON;;;;;N;;;;; 1D206;GREEK VOCAL NOTATION SYMBOL-7;So;0;ON;;;;;N;;;;; 1D207;GREEK VOCAL NOTATION SYMBOL-8;So;0;ON;;;;;N;;;;; 1D208;GREEK VOCAL NOTATION SYMBOL-9;So;0;ON;;;;;N;;;;; 1D209;GREEK VOCAL NOTATION SYMBOL-10;So;0;ON;;;;;N;;;;; 1D20A;GREEK VOCAL NOTATION SYMBOL-11;So;0;ON;;;;;N;;;;; 1D20B;GREEK VOCAL NOTATION SYMBOL-12;So;0;ON;;;;;N;;;;; 1D20C;GREEK VOCAL NOTATION SYMBOL-13;So;0;ON;;;;;N;;;;; 1D20D;GREEK VOCAL NOTATION SYMBOL-14;So;0;ON;;;;;N;;;;; 1D20E;GREEK VOCAL NOTATION SYMBOL-15;So;0;ON;;;;;N;;;;; 1D20F;GREEK VOCAL NOTATION SYMBOL-16;So;0;ON;;;;;N;;;;; 1D210;GREEK VOCAL NOTATION SYMBOL-17;So;0;ON;;;;;N;;;;; 1D211;GREEK VOCAL NOTATION SYMBOL-18;So;0;ON;;;;;N;;;;; 1D212;GREEK VOCAL NOTATION SYMBOL-19;So;0;ON;;;;;N;;;;; 1D213;GREEK VOCAL NOTATION SYMBOL-20;So;0;ON;;;;;N;;;;; 1D214;GREEK VOCAL NOTATION SYMBOL-21;So;0;ON;;;;;N;;;;; 1D215;GREEK VOCAL NOTATION SYMBOL-22;So;0;ON;;;;;N;;;;; 1D216;GREEK VOCAL NOTATION SYMBOL-23;So;0;ON;;;;;N;;;;; 1D217;GREEK VOCAL NOTATION SYMBOL-24;So;0;ON;;;;;N;;;;; 1D218;GREEK VOCAL NOTATION SYMBOL-50;So;0;ON;;;;;N;;;;; 1D219;GREEK VOCAL NOTATION SYMBOL-51;So;0;ON;;;;;N;;;;; 1D21A;GREEK VOCAL NOTATION SYMBOL-52;So;0;ON;;;;;N;;;;; 1D21B;GREEK VOCAL NOTATION SYMBOL-53;So;0;ON;;;;;N;;;;; 1D21C;GREEK VOCAL NOTATION SYMBOL-54;So;0;ON;;;;;N;;;;; 1D21D;GREEK INSTRUMENTAL NOTATION SYMBOL-1;So;0;ON;;;;;N;;;;; 1D21E;GREEK INSTRUMENTAL NOTATION SYMBOL-2;So;0;ON;;;;;N;;;;; 1D21F;GREEK INSTRUMENTAL NOTATION SYMBOL-4;So;0;ON;;;;;N;;;;; 1D220;GREEK INSTRUMENTAL NOTATION SYMBOL-5;So;0;ON;;;;;N;;;;; 1D221;GREEK INSTRUMENTAL NOTATION SYMBOL-7;So;0;ON;;;;;N;;;;; 1D222;GREEK INSTRUMENTAL NOTATION SYMBOL-8;So;0;ON;;;;;N;;;;; 1D223;GREEK INSTRUMENTAL NOTATION SYMBOL-11;So;0;ON;;;;;N;;;;; 1D224;GREEK INSTRUMENTAL NOTATION SYMBOL-12;So;0;ON;;;;;N;;;;; 1D225;GREEK INSTRUMENTAL NOTATION SYMBOL-13;So;0;ON;;;;;N;;;;; 1D226;GREEK INSTRUMENTAL NOTATION SYMBOL-14;So;0;ON;;;;;N;;;;; 1D227;GREEK INSTRUMENTAL NOTATION SYMBOL-17;So;0;ON;;;;;N;;;;; 1D228;GREEK INSTRUMENTAL NOTATION SYMBOL-18;So;0;ON;;;;;N;;;;; 1D229;GREEK INSTRUMENTAL NOTATION SYMBOL-19;So;0;ON;;;;;N;;;;; 1D22A;GREEK INSTRUMENTAL NOTATION SYMBOL-23;So;0;ON;;;;;N;;;;; 1D22B;GREEK INSTRUMENTAL NOTATION SYMBOL-24;So;0;ON;;;;;N;;;;; 1D22C;GREEK INSTRUMENTAL NOTATION SYMBOL-25;So;0;ON;;;;;N;;;;; 1D22D;GREEK INSTRUMENTAL NOTATION SYMBOL-26;So;0;ON;;;;;N;;;;; 1D22E;GREEK INSTRUMENTAL NOTATION SYMBOL-27;So;0;ON;;;;;N;;;;; 1D22F;GREEK INSTRUMENTAL NOTATION SYMBOL-29;So;0;ON;;;;;N;;;;; 1D230;GREEK INSTRUMENTAL NOTATION SYMBOL-30;So;0;ON;;;;;N;;;;; 1D231;GREEK INSTRUMENTAL NOTATION SYMBOL-32;So;0;ON;;;;;N;;;;; 1D232;GREEK INSTRUMENTAL NOTATION SYMBOL-36;So;0;ON;;;;;N;;;;; 1D233;GREEK INSTRUMENTAL NOTATION SYMBOL-37;So;0;ON;;;;;N;;;;; 1D234;GREEK INSTRUMENTAL NOTATION SYMBOL-38;So;0;ON;;;;;N;;;;; 1D235;GREEK INSTRUMENTAL NOTATION SYMBOL-39;So;0;ON;;;;;N;;;;; 1D236;GREEK INSTRUMENTAL NOTATION SYMBOL-40;So;0;ON;;;;;N;;;;; 1D237;GREEK INSTRUMENTAL NOTATION SYMBOL-42;So;0;ON;;;;;N;;;;; 1D238;GREEK INSTRUMENTAL NOTATION SYMBOL-43;So;0;ON;;;;;N;;;;; 1D239;GREEK INSTRUMENTAL NOTATION SYMBOL-45;So;0;ON;;;;;N;;;;; 1D23A;GREEK INSTRUMENTAL NOTATION SYMBOL-47;So;0;ON;;;;;N;;;;; 1D23B;GREEK INSTRUMENTAL NOTATION SYMBOL-48;So;0;ON;;;;;N;;;;; 1D23C;GREEK INSTRUMENTAL NOTATION SYMBOL-49;So;0;ON;;;;;N;;;;; 1D23D;GREEK INSTRUMENTAL NOTATION SYMBOL-50;So;0;ON;;;;;N;;;;; 1D23E;GREEK INSTRUMENTAL NOTATION SYMBOL-51;So;0;ON;;;;;N;;;;; 1D23F;GREEK INSTRUMENTAL NOTATION SYMBOL-52;So;0;ON;;;;;N;;;;; 1D240;GREEK INSTRUMENTAL NOTATION SYMBOL-53;So;0;ON;;;;;N;;;;; 1D241;GREEK INSTRUMENTAL NOTATION SYMBOL-54;So;0;ON;;;;;N;;;;; 1D242;COMBINING GREEK MUSICAL TRISEME;Mn;230;NSM;;;;;N;;;;; 1D243;COMBINING GREEK MUSICAL TETRASEME;Mn;230;NSM;;;;;N;;;;; 1D244;COMBINING GREEK MUSICAL PENTASEME;Mn;230;NSM;;;;;N;;;;; 1D245;GREEK MUSICAL LEIMMA;So;0;ON;;;;;N;;;;; 1D300;MONOGRAM FOR EARTH;So;0;ON;;;;;N;;;;; 1D301;DIGRAM FOR HEAVENLY EARTH;So;0;ON;;;;;N;;;;; 1D302;DIGRAM FOR HUMAN EARTH;So;0;ON;;;;;N;;;;; 1D303;DIGRAM FOR EARTHLY HEAVEN;So;0;ON;;;;;N;;;;; 1D304;DIGRAM FOR EARTHLY HUMAN;So;0;ON;;;;;N;;;;; 1D305;DIGRAM FOR EARTH;So;0;ON;;;;;N;;;;; 1D306;TETRAGRAM FOR CENTRE;So;0;ON;;;;;N;;;;; 1D307;TETRAGRAM FOR FULL CIRCLE;So;0;ON;;;;;N;;;;; 1D308;TETRAGRAM FOR MIRED;So;0;ON;;;;;N;;;;; 1D309;TETRAGRAM FOR BARRIER;So;0;ON;;;;;N;;;;; 1D30A;TETRAGRAM FOR KEEPING SMALL;So;0;ON;;;;;N;;;;; 1D30B;TETRAGRAM FOR CONTRARIETY;So;0;ON;;;;;N;;;;; 1D30C;TETRAGRAM FOR ASCENT;So;0;ON;;;;;N;;;;; 1D30D;TETRAGRAM FOR OPPOSITION;So;0;ON;;;;;N;;;;; 1D30E;TETRAGRAM FOR BRANCHING OUT;So;0;ON;;;;;N;;;;; 1D30F;TETRAGRAM FOR DEFECTIVENESS OR DISTORTION;So;0;ON;;;;;N;;;;; 1D310;TETRAGRAM FOR DIVERGENCE;So;0;ON;;;;;N;;;;; 1D311;TETRAGRAM FOR YOUTHFULNESS;So;0;ON;;;;;N;;;;; 1D312;TETRAGRAM FOR INCREASE;So;0;ON;;;;;N;;;;; 1D313;TETRAGRAM FOR PENETRATION;So;0;ON;;;;;N;;;;; 1D314;TETRAGRAM FOR REACH;So;0;ON;;;;;N;;;;; 1D315;TETRAGRAM FOR CONTACT;So;0;ON;;;;;N;;;;; 1D316;TETRAGRAM FOR HOLDING BACK;So;0;ON;;;;;N;;;;; 1D317;TETRAGRAM FOR WAITING;So;0;ON;;;;;N;;;;; 1D318;TETRAGRAM FOR FOLLOWING;So;0;ON;;;;;N;;;;; 1D319;TETRAGRAM FOR ADVANCE;So;0;ON;;;;;N;;;;; 1D31A;TETRAGRAM FOR RELEASE;So;0;ON;;;;;N;;;;; 1D31B;TETRAGRAM FOR RESISTANCE;So;0;ON;;;;;N;;;;; 1D31C;TETRAGRAM FOR EASE;So;0;ON;;;;;N;;;;; 1D31D;TETRAGRAM FOR JOY;So;0;ON;;;;;N;;;;; 1D31E;TETRAGRAM FOR CONTENTION;So;0;ON;;;;;N;;;;; 1D31F;TETRAGRAM FOR ENDEAVOUR;So;0;ON;;;;;N;;;;; 1D320;TETRAGRAM FOR DUTIES;So;0;ON;;;;;N;;;;; 1D321;TETRAGRAM FOR CHANGE;So;0;ON;;;;;N;;;;; 1D322;TETRAGRAM FOR DECISIVENESS;So;0;ON;;;;;N;;;;; 1D323;TETRAGRAM FOR BOLD RESOLUTION;So;0;ON;;;;;N;;;;; 1D324;TETRAGRAM FOR PACKING;So;0;ON;;;;;N;;;;; 1D325;TETRAGRAM FOR LEGION;So;0;ON;;;;;N;;;;; 1D326;TETRAGRAM FOR CLOSENESS;So;0;ON;;;;;N;;;;; 1D327;TETRAGRAM FOR KINSHIP;So;0;ON;;;;;N;;;;; 1D328;TETRAGRAM FOR GATHERING;So;0;ON;;;;;N;;;;; 1D329;TETRAGRAM FOR STRENGTH;So;0;ON;;;;;N;;;;; 1D32A;TETRAGRAM FOR PURITY;So;0;ON;;;;;N;;;;; 1D32B;TETRAGRAM FOR FULLNESS;So;0;ON;;;;;N;;;;; 1D32C;TETRAGRAM FOR RESIDENCE;So;0;ON;;;;;N;;;;; 1D32D;TETRAGRAM FOR LAW OR MODEL;So;0;ON;;;;;N;;;;; 1D32E;TETRAGRAM FOR RESPONSE;So;0;ON;;;;;N;;;;; 1D32F;TETRAGRAM FOR GOING TO MEET;So;0;ON;;;;;N;;;;; 1D330;TETRAGRAM FOR ENCOUNTERS;So;0;ON;;;;;N;;;;; 1D331;TETRAGRAM FOR STOVE;So;0;ON;;;;;N;;;;; 1D332;TETRAGRAM FOR GREATNESS;So;0;ON;;;;;N;;;;; 1D333;TETRAGRAM FOR ENLARGEMENT;So;0;ON;;;;;N;;;;; 1D334;TETRAGRAM FOR PATTERN;So;0;ON;;;;;N;;;;; 1D335;TETRAGRAM FOR RITUAL;So;0;ON;;;;;N;;;;; 1D336;TETRAGRAM FOR FLIGHT;So;0;ON;;;;;N;;;;; 1D337;TETRAGRAM FOR VASTNESS OR WASTING;So;0;ON;;;;;N;;;;; 1D338;TETRAGRAM FOR CONSTANCY;So;0;ON;;;;;N;;;;; 1D339;TETRAGRAM FOR MEASURE;So;0;ON;;;;;N;;;;; 1D33A;TETRAGRAM FOR ETERNITY;So;0;ON;;;;;N;;;;; 1D33B;TETRAGRAM FOR UNITY;So;0;ON;;;;;N;;;;; 1D33C;TETRAGRAM FOR DIMINISHMENT;So;0;ON;;;;;N;;;;; 1D33D;TETRAGRAM FOR CLOSED MOUTH;So;0;ON;;;;;N;;;;; 1D33E;TETRAGRAM FOR GUARDEDNESS;So;0;ON;;;;;N;;;;; 1D33F;TETRAGRAM FOR GATHERING IN;So;0;ON;;;;;N;;;;; 1D340;TETRAGRAM FOR MASSING;So;0;ON;;;;;N;;;;; 1D341;TETRAGRAM FOR ACCUMULATION;So;0;ON;;;;;N;;;;; 1D342;TETRAGRAM FOR EMBELLISHMENT;So;0;ON;;;;;N;;;;; 1D343;TETRAGRAM FOR DOUBT;So;0;ON;;;;;N;;;;; 1D344;TETRAGRAM FOR WATCH;So;0;ON;;;;;N;;;;; 1D345;TETRAGRAM FOR SINKING;So;0;ON;;;;;N;;;;; 1D346;TETRAGRAM FOR INNER;So;0;ON;;;;;N;;;;; 1D347;TETRAGRAM FOR DEPARTURE;So;0;ON;;;;;N;;;;; 1D348;TETRAGRAM FOR DARKENING;So;0;ON;;;;;N;;;;; 1D349;TETRAGRAM FOR DIMMING;So;0;ON;;;;;N;;;;; 1D34A;TETRAGRAM FOR EXHAUSTION;So;0;ON;;;;;N;;;;; 1D34B;TETRAGRAM FOR SEVERANCE;So;0;ON;;;;;N;;;;; 1D34C;TETRAGRAM FOR STOPPAGE;So;0;ON;;;;;N;;;;; 1D34D;TETRAGRAM FOR HARDNESS;So;0;ON;;;;;N;;;;; 1D34E;TETRAGRAM FOR COMPLETION;So;0;ON;;;;;N;;;;; 1D34F;TETRAGRAM FOR CLOSURE;So;0;ON;;;;;N;;;;; 1D350;TETRAGRAM FOR FAILURE;So;0;ON;;;;;N;;;;; 1D351;TETRAGRAM FOR AGGRAVATION;So;0;ON;;;;;N;;;;; 1D352;TETRAGRAM FOR COMPLIANCE;So;0;ON;;;;;N;;;;; 1D353;TETRAGRAM FOR ON THE VERGE;So;0;ON;;;;;N;;;;; 1D354;TETRAGRAM FOR DIFFICULTIES;So;0;ON;;;;;N;;;;; 1D355;TETRAGRAM FOR LABOURING;So;0;ON;;;;;N;;;;; 1D356;TETRAGRAM FOR FOSTERING;So;0;ON;;;;;N;;;;; 1D360;COUNTING ROD UNIT DIGIT ONE;No;0;L;;;;1;N;;;;; 1D361;COUNTING ROD UNIT DIGIT TWO;No;0;L;;;;2;N;;;;; 1D362;COUNTING ROD UNIT DIGIT THREE;No;0;L;;;;3;N;;;;; 1D363;COUNTING ROD UNIT DIGIT FOUR;No;0;L;;;;4;N;;;;; 1D364;COUNTING ROD UNIT DIGIT FIVE;No;0;L;;;;5;N;;;;; 1D365;COUNTING ROD UNIT DIGIT SIX;No;0;L;;;;6;N;;;;; 1D366;COUNTING ROD UNIT DIGIT SEVEN;No;0;L;;;;7;N;;;;; 1D367;COUNTING ROD UNIT DIGIT EIGHT;No;0;L;;;;8;N;;;;; 1D368;COUNTING ROD UNIT DIGIT NINE;No;0;L;;;;9;N;;;;; 1D369;COUNTING ROD TENS DIGIT ONE;No;0;L;;;;10;N;;;;; 1D36A;COUNTING ROD TENS DIGIT TWO;No;0;L;;;;20;N;;;;; 1D36B;COUNTING ROD TENS DIGIT THREE;No;0;L;;;;30;N;;;;; 1D36C;COUNTING ROD TENS DIGIT FOUR;No;0;L;;;;40;N;;;;; 1D36D;COUNTING ROD TENS DIGIT FIVE;No;0;L;;;;50;N;;;;; 1D36E;COUNTING ROD TENS DIGIT SIX;No;0;L;;;;60;N;;;;; 1D36F;COUNTING ROD TENS DIGIT SEVEN;No;0;L;;;;70;N;;;;; 1D370;COUNTING ROD TENS DIGIT EIGHT;No;0;L;;;;80;N;;;;; 1D371;COUNTING ROD TENS DIGIT NINE;No;0;L;;;;90;N;;;;; 1D400;MATHEMATICAL BOLD CAPITAL A;Lu;0;L; 0041;;;;N;;;;; 1D401;MATHEMATICAL BOLD CAPITAL B;Lu;0;L; 0042;;;;N;;;;; 1D402;MATHEMATICAL BOLD CAPITAL C;Lu;0;L; 0043;;;;N;;;;; 1D403;MATHEMATICAL BOLD CAPITAL D;Lu;0;L; 0044;;;;N;;;;; 1D404;MATHEMATICAL BOLD CAPITAL E;Lu;0;L; 0045;;;;N;;;;; 1D405;MATHEMATICAL BOLD CAPITAL F;Lu;0;L; 0046;;;;N;;;;; 1D406;MATHEMATICAL BOLD CAPITAL G;Lu;0;L; 0047;;;;N;;;;; 1D407;MATHEMATICAL BOLD CAPITAL H;Lu;0;L; 0048;;;;N;;;;; 1D408;MATHEMATICAL BOLD CAPITAL I;Lu;0;L; 0049;;;;N;;;;; 1D409;MATHEMATICAL BOLD CAPITAL J;Lu;0;L; 004A;;;;N;;;;; 1D40A;MATHEMATICAL BOLD CAPITAL K;Lu;0;L; 004B;;;;N;;;;; 1D40B;MATHEMATICAL BOLD CAPITAL L;Lu;0;L; 004C;;;;N;;;;; 1D40C;MATHEMATICAL BOLD CAPITAL M;Lu;0;L; 004D;;;;N;;;;; 1D40D;MATHEMATICAL BOLD CAPITAL N;Lu;0;L; 004E;;;;N;;;;; 1D40E;MATHEMATICAL BOLD CAPITAL O;Lu;0;L; 004F;;;;N;;;;; 1D40F;MATHEMATICAL BOLD CAPITAL P;Lu;0;L; 0050;;;;N;;;;; 1D410;MATHEMATICAL BOLD CAPITAL Q;Lu;0;L; 0051;;;;N;;;;; 1D411;MATHEMATICAL BOLD CAPITAL R;Lu;0;L; 0052;;;;N;;;;; 1D412;MATHEMATICAL BOLD CAPITAL S;Lu;0;L; 0053;;;;N;;;;; 1D413;MATHEMATICAL BOLD CAPITAL T;Lu;0;L; 0054;;;;N;;;;; 1D414;MATHEMATICAL BOLD CAPITAL U;Lu;0;L; 0055;;;;N;;;;; 1D415;MATHEMATICAL BOLD CAPITAL V;Lu;0;L; 0056;;;;N;;;;; 1D416;MATHEMATICAL BOLD CAPITAL W;Lu;0;L; 0057;;;;N;;;;; 1D417;MATHEMATICAL BOLD CAPITAL X;Lu;0;L; 0058;;;;N;;;;; 1D418;MATHEMATICAL BOLD CAPITAL Y;Lu;0;L; 0059;;;;N;;;;; 1D419;MATHEMATICAL BOLD CAPITAL Z;Lu;0;L; 005A;;;;N;;;;; 1D41A;MATHEMATICAL BOLD SMALL A;Ll;0;L; 0061;;;;N;;;;; 1D41B;MATHEMATICAL BOLD SMALL B;Ll;0;L; 0062;;;;N;;;;; 1D41C;MATHEMATICAL BOLD SMALL C;Ll;0;L; 0063;;;;N;;;;; 1D41D;MATHEMATICAL BOLD SMALL D;Ll;0;L; 0064;;;;N;;;;; 1D41E;MATHEMATICAL BOLD SMALL E;Ll;0;L; 0065;;;;N;;;;; 1D41F;MATHEMATICAL BOLD SMALL F;Ll;0;L; 0066;;;;N;;;;; 1D420;MATHEMATICAL BOLD SMALL G;Ll;0;L; 0067;;;;N;;;;; 1D421;MATHEMATICAL BOLD SMALL H;Ll;0;L; 0068;;;;N;;;;; 1D422;MATHEMATICAL BOLD SMALL I;Ll;0;L; 0069;;;;N;;;;; 1D423;MATHEMATICAL BOLD SMALL J;Ll;0;L; 006A;;;;N;;;;; 1D424;MATHEMATICAL BOLD SMALL K;Ll;0;L; 006B;;;;N;;;;; 1D425;MATHEMATICAL BOLD SMALL L;Ll;0;L; 006C;;;;N;;;;; 1D426;MATHEMATICAL BOLD SMALL M;Ll;0;L; 006D;;;;N;;;;; 1D427;MATHEMATICAL BOLD SMALL N;Ll;0;L; 006E;;;;N;;;;; 1D428;MATHEMATICAL BOLD SMALL O;Ll;0;L; 006F;;;;N;;;;; 1D429;MATHEMATICAL BOLD SMALL P;Ll;0;L; 0070;;;;N;;;;; 1D42A;MATHEMATICAL BOLD SMALL Q;Ll;0;L; 0071;;;;N;;;;; 1D42B;MATHEMATICAL BOLD SMALL R;Ll;0;L; 0072;;;;N;;;;; 1D42C;MATHEMATICAL BOLD SMALL S;Ll;0;L; 0073;;;;N;;;;; 1D42D;MATHEMATICAL BOLD SMALL T;Ll;0;L; 0074;;;;N;;;;; 1D42E;MATHEMATICAL BOLD SMALL U;Ll;0;L; 0075;;;;N;;;;; 1D42F;MATHEMATICAL BOLD SMALL V;Ll;0;L; 0076;;;;N;;;;; 1D430;MATHEMATICAL BOLD SMALL W;Ll;0;L; 0077;;;;N;;;;; 1D431;MATHEMATICAL BOLD SMALL X;Ll;0;L; 0078;;;;N;;;;; 1D432;MATHEMATICAL BOLD SMALL Y;Ll;0;L; 0079;;;;N;;;;; 1D433;MATHEMATICAL BOLD SMALL Z;Ll;0;L; 007A;;;;N;;;;; 1D434;MATHEMATICAL ITALIC CAPITAL A;Lu;0;L; 0041;;;;N;;;;; 1D435;MATHEMATICAL ITALIC CAPITAL B;Lu;0;L; 0042;;;;N;;;;; 1D436;MATHEMATICAL ITALIC CAPITAL C;Lu;0;L; 0043;;;;N;;;;; 1D437;MATHEMATICAL ITALIC CAPITAL D;Lu;0;L; 0044;;;;N;;;;; 1D438;MATHEMATICAL ITALIC CAPITAL E;Lu;0;L; 0045;;;;N;;;;; 1D439;MATHEMATICAL ITALIC CAPITAL F;Lu;0;L; 0046;;;;N;;;;; 1D43A;MATHEMATICAL ITALIC CAPITAL G;Lu;0;L; 0047;;;;N;;;;; 1D43B;MATHEMATICAL ITALIC CAPITAL H;Lu;0;L; 0048;;;;N;;;;; 1D43C;MATHEMATICAL ITALIC CAPITAL I;Lu;0;L; 0049;;;;N;;;;; 1D43D;MATHEMATICAL ITALIC CAPITAL J;Lu;0;L; 004A;;;;N;;;;; 1D43E;MATHEMATICAL ITALIC CAPITAL K;Lu;0;L; 004B;;;;N;;;;; 1D43F;MATHEMATICAL ITALIC CAPITAL L;Lu;0;L; 004C;;;;N;;;;; 1D440;MATHEMATICAL ITALIC CAPITAL M;Lu;0;L; 004D;;;;N;;;;; 1D441;MATHEMATICAL ITALIC CAPITAL N;Lu;0;L; 004E;;;;N;;;;; 1D442;MATHEMATICAL ITALIC CAPITAL O;Lu;0;L; 004F;;;;N;;;;; 1D443;MATHEMATICAL ITALIC CAPITAL P;Lu;0;L; 0050;;;;N;;;;; 1D444;MATHEMATICAL ITALIC CAPITAL Q;Lu;0;L; 0051;;;;N;;;;; 1D445;MATHEMATICAL ITALIC CAPITAL R;Lu;0;L; 0052;;;;N;;;;; 1D446;MATHEMATICAL ITALIC CAPITAL S;Lu;0;L; 0053;;;;N;;;;; 1D447;MATHEMATICAL ITALIC CAPITAL T;Lu;0;L; 0054;;;;N;;;;; 1D448;MATHEMATICAL ITALIC CAPITAL U;Lu;0;L; 0055;;;;N;;;;; 1D449;MATHEMATICAL ITALIC CAPITAL V;Lu;0;L; 0056;;;;N;;;;; 1D44A;MATHEMATICAL ITALIC CAPITAL W;Lu;0;L; 0057;;;;N;;;;; 1D44B;MATHEMATICAL ITALIC CAPITAL X;Lu;0;L; 0058;;;;N;;;;; 1D44C;MATHEMATICAL ITALIC CAPITAL Y;Lu;0;L; 0059;;;;N;;;;; 1D44D;MATHEMATICAL ITALIC CAPITAL Z;Lu;0;L; 005A;;;;N;;;;; 1D44E;MATHEMATICAL ITALIC SMALL A;Ll;0;L; 0061;;;;N;;;;; 1D44F;MATHEMATICAL ITALIC SMALL B;Ll;0;L; 0062;;;;N;;;;; 1D450;MATHEMATICAL ITALIC SMALL C;Ll;0;L; 0063;;;;N;;;;; 1D451;MATHEMATICAL ITALIC SMALL D;Ll;0;L; 0064;;;;N;;;;; 1D452;MATHEMATICAL ITALIC SMALL E;Ll;0;L; 0065;;;;N;;;;; 1D453;MATHEMATICAL ITALIC SMALL F;Ll;0;L; 0066;;;;N;;;;; 1D454;MATHEMATICAL ITALIC SMALL G;Ll;0;L; 0067;;;;N;;;;; 1D456;MATHEMATICAL ITALIC SMALL I;Ll;0;L; 0069;;;;N;;;;; 1D457;MATHEMATICAL ITALIC SMALL J;Ll;0;L; 006A;;;;N;;;;; 1D458;MATHEMATICAL ITALIC SMALL K;Ll;0;L; 006B;;;;N;;;;; 1D459;MATHEMATICAL ITALIC SMALL L;Ll;0;L; 006C;;;;N;;;;; 1D45A;MATHEMATICAL ITALIC SMALL M;Ll;0;L; 006D;;;;N;;;;; 1D45B;MATHEMATICAL ITALIC SMALL N;Ll;0;L; 006E;;;;N;;;;; 1D45C;MATHEMATICAL ITALIC SMALL O;Ll;0;L; 006F;;;;N;;;;; 1D45D;MATHEMATICAL ITALIC SMALL P;Ll;0;L; 0070;;;;N;;;;; 1D45E;MATHEMATICAL ITALIC SMALL Q;Ll;0;L; 0071;;;;N;;;;; 1D45F;MATHEMATICAL ITALIC SMALL R;Ll;0;L; 0072;;;;N;;;;; 1D460;MATHEMATICAL ITALIC SMALL S;Ll;0;L; 0073;;;;N;;;;; 1D461;MATHEMATICAL ITALIC SMALL T;Ll;0;L; 0074;;;;N;;;;; 1D462;MATHEMATICAL ITALIC SMALL U;Ll;0;L; 0075;;;;N;;;;; 1D463;MATHEMATICAL ITALIC SMALL V;Ll;0;L; 0076;;;;N;;;;; 1D464;MATHEMATICAL ITALIC SMALL W;Ll;0;L; 0077;;;;N;;;;; 1D465;MATHEMATICAL ITALIC SMALL X;Ll;0;L; 0078;;;;N;;;;; 1D466;MATHEMATICAL ITALIC SMALL Y;Ll;0;L; 0079;;;;N;;;;; 1D467;MATHEMATICAL ITALIC SMALL Z;Ll;0;L; 007A;;;;N;;;;; 1D468;MATHEMATICAL BOLD ITALIC CAPITAL A;Lu;0;L; 0041;;;;N;;;;; 1D469;MATHEMATICAL BOLD ITALIC CAPITAL B;Lu;0;L; 0042;;;;N;;;;; 1D46A;MATHEMATICAL BOLD ITALIC CAPITAL C;Lu;0;L; 0043;;;;N;;;;; 1D46B;MATHEMATICAL BOLD ITALIC CAPITAL D;Lu;0;L; 0044;;;;N;;;;; 1D46C;MATHEMATICAL BOLD ITALIC CAPITAL E;Lu;0;L; 0045;;;;N;;;;; 1D46D;MATHEMATICAL BOLD ITALIC CAPITAL F;Lu;0;L; 0046;;;;N;;;;; 1D46E;MATHEMATICAL BOLD ITALIC CAPITAL G;Lu;0;L; 0047;;;;N;;;;; 1D46F;MATHEMATICAL BOLD ITALIC CAPITAL H;Lu;0;L; 0048;;;;N;;;;; 1D470;MATHEMATICAL BOLD ITALIC CAPITAL I;Lu;0;L; 0049;;;;N;;;;; 1D471;MATHEMATICAL BOLD ITALIC CAPITAL J;Lu;0;L; 004A;;;;N;;;;; 1D472;MATHEMATICAL BOLD ITALIC CAPITAL K;Lu;0;L; 004B;;;;N;;;;; 1D473;MATHEMATICAL BOLD ITALIC CAPITAL L;Lu;0;L; 004C;;;;N;;;;; 1D474;MATHEMATICAL BOLD ITALIC CAPITAL M;Lu;0;L; 004D;;;;N;;;;; 1D475;MATHEMATICAL BOLD ITALIC CAPITAL N;Lu;0;L; 004E;;;;N;;;;; 1D476;MATHEMATICAL BOLD ITALIC CAPITAL O;Lu;0;L; 004F;;;;N;;;;; 1D477;MATHEMATICAL BOLD ITALIC CAPITAL P;Lu;0;L; 0050;;;;N;;;;; 1D478;MATHEMATICAL BOLD ITALIC CAPITAL Q;Lu;0;L; 0051;;;;N;;;;; 1D479;MATHEMATICAL BOLD ITALIC CAPITAL R;Lu;0;L; 0052;;;;N;;;;; 1D47A;MATHEMATICAL BOLD ITALIC CAPITAL S;Lu;0;L; 0053;;;;N;;;;; 1D47B;MATHEMATICAL BOLD ITALIC CAPITAL T;Lu;0;L; 0054;;;;N;;;;; 1D47C;MATHEMATICAL BOLD ITALIC CAPITAL U;Lu;0;L; 0055;;;;N;;;;; 1D47D;MATHEMATICAL BOLD ITALIC CAPITAL V;Lu;0;L; 0056;;;;N;;;;; 1D47E;MATHEMATICAL BOLD ITALIC CAPITAL W;Lu;0;L; 0057;;;;N;;;;; 1D47F;MATHEMATICAL BOLD ITALIC CAPITAL X;Lu;0;L; 0058;;;;N;;;;; 1D480;MATHEMATICAL BOLD ITALIC CAPITAL Y;Lu;0;L; 0059;;;;N;;;;; 1D481;MATHEMATICAL BOLD ITALIC CAPITAL Z;Lu;0;L; 005A;;;;N;;;;; 1D482;MATHEMATICAL BOLD ITALIC SMALL A;Ll;0;L; 0061;;;;N;;;;; 1D483;MATHEMATICAL BOLD ITALIC SMALL B;Ll;0;L; 0062;;;;N;;;;; 1D484;MATHEMATICAL BOLD ITALIC SMALL C;Ll;0;L; 0063;;;;N;;;;; 1D485;MATHEMATICAL BOLD ITALIC SMALL D;Ll;0;L; 0064;;;;N;;;;; 1D486;MATHEMATICAL BOLD ITALIC SMALL E;Ll;0;L; 0065;;;;N;;;;; 1D487;MATHEMATICAL BOLD ITALIC SMALL F;Ll;0;L; 0066;;;;N;;;;; 1D488;MATHEMATICAL BOLD ITALIC SMALL G;Ll;0;L; 0067;;;;N;;;;; 1D489;MATHEMATICAL BOLD ITALIC SMALL H;Ll;0;L; 0068;;;;N;;;;; 1D48A;MATHEMATICAL BOLD ITALIC SMALL I;Ll;0;L; 0069;;;;N;;;;; 1D48B;MATHEMATICAL BOLD ITALIC SMALL J;Ll;0;L; 006A;;;;N;;;;; 1D48C;MATHEMATICAL BOLD ITALIC SMALL K;Ll;0;L; 006B;;;;N;;;;; 1D48D;MATHEMATICAL BOLD ITALIC SMALL L;Ll;0;L; 006C;;;;N;;;;; 1D48E;MATHEMATICAL BOLD ITALIC SMALL M;Ll;0;L; 006D;;;;N;;;;; 1D48F;MATHEMATICAL BOLD ITALIC SMALL N;Ll;0;L; 006E;;;;N;;;;; 1D490;MATHEMATICAL BOLD ITALIC SMALL O;Ll;0;L; 006F;;;;N;;;;; 1D491;MATHEMATICAL BOLD ITALIC SMALL P;Ll;0;L; 0070;;;;N;;;;; 1D492;MATHEMATICAL BOLD ITALIC SMALL Q;Ll;0;L; 0071;;;;N;;;;; 1D493;MATHEMATICAL BOLD ITALIC SMALL R;Ll;0;L; 0072;;;;N;;;;; 1D494;MATHEMATICAL BOLD ITALIC SMALL S;Ll;0;L; 0073;;;;N;;;;; 1D495;MATHEMATICAL BOLD ITALIC SMALL T;Ll;0;L; 0074;;;;N;;;;; 1D496;MATHEMATICAL BOLD ITALIC SMALL U;Ll;0;L; 0075;;;;N;;;;; 1D497;MATHEMATICAL BOLD ITALIC SMALL V;Ll;0;L; 0076;;;;N;;;;; 1D498;MATHEMATICAL BOLD ITALIC SMALL W;Ll;0;L; 0077;;;;N;;;;; 1D499;MATHEMATICAL BOLD ITALIC SMALL X;Ll;0;L; 0078;;;;N;;;;; 1D49A;MATHEMATICAL BOLD ITALIC SMALL Y;Ll;0;L; 0079;;;;N;;;;; 1D49B;MATHEMATICAL BOLD ITALIC SMALL Z;Ll;0;L; 007A;;;;N;;;;; 1D49C;MATHEMATICAL SCRIPT CAPITAL A;Lu;0;L; 0041;;;;N;;;;; 1D49E;MATHEMATICAL SCRIPT CAPITAL C;Lu;0;L; 0043;;;;N;;;;; 1D49F;MATHEMATICAL SCRIPT CAPITAL D;Lu;0;L; 0044;;;;N;;;;; 1D4A2;MATHEMATICAL SCRIPT CAPITAL G;Lu;0;L; 0047;;;;N;;;;; 1D4A5;MATHEMATICAL SCRIPT CAPITAL J;Lu;0;L; 004A;;;;N;;;;; 1D4A6;MATHEMATICAL SCRIPT CAPITAL K;Lu;0;L; 004B;;;;N;;;;; 1D4A9;MATHEMATICAL SCRIPT CAPITAL N;Lu;0;L; 004E;;;;N;;;;; 1D4AA;MATHEMATICAL SCRIPT CAPITAL O;Lu;0;L; 004F;;;;N;;;;; 1D4AB;MATHEMATICAL SCRIPT CAPITAL P;Lu;0;L; 0050;;;;N;;;;; 1D4AC;MATHEMATICAL SCRIPT CAPITAL Q;Lu;0;L; 0051;;;;N;;;;; 1D4AE;MATHEMATICAL SCRIPT CAPITAL S;Lu;0;L; 0053;;;;N;;;;; 1D4AF;MATHEMATICAL SCRIPT CAPITAL T;Lu;0;L; 0054;;;;N;;;;; 1D4B0;MATHEMATICAL SCRIPT CAPITAL U;Lu;0;L; 0055;;;;N;;;;; 1D4B1;MATHEMATICAL SCRIPT CAPITAL V;Lu;0;L; 0056;;;;N;;;;; 1D4B2;MATHEMATICAL SCRIPT CAPITAL W;Lu;0;L; 0057;;;;N;;;;; 1D4B3;MATHEMATICAL SCRIPT CAPITAL X;Lu;0;L; 0058;;;;N;;;;; 1D4B4;MATHEMATICAL SCRIPT CAPITAL Y;Lu;0;L; 0059;;;;N;;;;; 1D4B5;MATHEMATICAL SCRIPT CAPITAL Z;Lu;0;L; 005A;;;;N;;;;; 1D4B6;MATHEMATICAL SCRIPT SMALL A;Ll;0;L; 0061;;;;N;;;;; 1D4B7;MATHEMATICAL SCRIPT SMALL B;Ll;0;L; 0062;;;;N;;;;; 1D4B8;MATHEMATICAL SCRIPT SMALL C;Ll;0;L; 0063;;;;N;;;;; 1D4B9;MATHEMATICAL SCRIPT SMALL D;Ll;0;L; 0064;;;;N;;;;; 1D4BB;MATHEMATICAL SCRIPT SMALL F;Ll;0;L; 0066;;;;N;;;;; 1D4BD;MATHEMATICAL SCRIPT SMALL H;Ll;0;L; 0068;;;;N;;;;; 1D4BE;MATHEMATICAL SCRIPT SMALL I;Ll;0;L; 0069;;;;N;;;;; 1D4BF;MATHEMATICAL SCRIPT SMALL J;Ll;0;L; 006A;;;;N;;;;; 1D4C0;MATHEMATICAL SCRIPT SMALL K;Ll;0;L; 006B;;;;N;;;;; 1D4C1;MATHEMATICAL SCRIPT SMALL L;Ll;0;L; 006C;;;;N;;;;; 1D4C2;MATHEMATICAL SCRIPT SMALL M;Ll;0;L; 006D;;;;N;;;;; 1D4C3;MATHEMATICAL SCRIPT SMALL N;Ll;0;L; 006E;;;;N;;;;; 1D4C5;MATHEMATICAL SCRIPT SMALL P;Ll;0;L; 0070;;;;N;;;;; 1D4C6;MATHEMATICAL SCRIPT SMALL Q;Ll;0;L; 0071;;;;N;;;;; 1D4C7;MATHEMATICAL SCRIPT SMALL R;Ll;0;L; 0072;;;;N;;;;; 1D4C8;MATHEMATICAL SCRIPT SMALL S;Ll;0;L; 0073;;;;N;;;;; 1D4C9;MATHEMATICAL SCRIPT SMALL T;Ll;0;L; 0074;;;;N;;;;; 1D4CA;MATHEMATICAL SCRIPT SMALL U;Ll;0;L; 0075;;;;N;;;;; 1D4CB;MATHEMATICAL SCRIPT SMALL V;Ll;0;L; 0076;;;;N;;;;; 1D4CC;MATHEMATICAL SCRIPT SMALL W;Ll;0;L; 0077;;;;N;;;;; 1D4CD;MATHEMATICAL SCRIPT SMALL X;Ll;0;L; 0078;;;;N;;;;; 1D4CE;MATHEMATICAL SCRIPT SMALL Y;Ll;0;L; 0079;;;;N;;;;; 1D4CF;MATHEMATICAL SCRIPT SMALL Z;Ll;0;L; 007A;;;;N;;;;; 1D4D0;MATHEMATICAL BOLD SCRIPT CAPITAL A;Lu;0;L; 0041;;;;N;;;;; 1D4D1;MATHEMATICAL BOLD SCRIPT CAPITAL B;Lu;0;L; 0042;;;;N;;;;; 1D4D2;MATHEMATICAL BOLD SCRIPT CAPITAL C;Lu;0;L; 0043;;;;N;;;;; 1D4D3;MATHEMATICAL BOLD SCRIPT CAPITAL D;Lu;0;L; 0044;;;;N;;;;; 1D4D4;MATHEMATICAL BOLD SCRIPT CAPITAL E;Lu;0;L; 0045;;;;N;;;;; 1D4D5;MATHEMATICAL BOLD SCRIPT CAPITAL F;Lu;0;L; 0046;;;;N;;;;; 1D4D6;MATHEMATICAL BOLD SCRIPT CAPITAL G;Lu;0;L; 0047;;;;N;;;;; 1D4D7;MATHEMATICAL BOLD SCRIPT CAPITAL H;Lu;0;L; 0048;;;;N;;;;; 1D4D8;MATHEMATICAL BOLD SCRIPT CAPITAL I;Lu;0;L; 0049;;;;N;;;;; 1D4D9;MATHEMATICAL BOLD SCRIPT CAPITAL J;Lu;0;L; 004A;;;;N;;;;; 1D4DA;MATHEMATICAL BOLD SCRIPT CAPITAL K;Lu;0;L; 004B;;;;N;;;;; 1D4DB;MATHEMATICAL BOLD SCRIPT CAPITAL L;Lu;0;L; 004C;;;;N;;;;; 1D4DC;MATHEMATICAL BOLD SCRIPT CAPITAL M;Lu;0;L; 004D;;;;N;;;;; 1D4DD;MATHEMATICAL BOLD SCRIPT CAPITAL N;Lu;0;L; 004E;;;;N;;;;; 1D4DE;MATHEMATICAL BOLD SCRIPT CAPITAL O;Lu;0;L; 004F;;;;N;;;;; 1D4DF;MATHEMATICAL BOLD SCRIPT CAPITAL P;Lu;0;L; 0050;;;;N;;;;; 1D4E0;MATHEMATICAL BOLD SCRIPT CAPITAL Q;Lu;0;L; 0051;;;;N;;;;; 1D4E1;MATHEMATICAL BOLD SCRIPT CAPITAL R;Lu;0;L; 0052;;;;N;;;;; 1D4E2;MATHEMATICAL BOLD SCRIPT CAPITAL S;Lu;0;L; 0053;;;;N;;;;; 1D4E3;MATHEMATICAL BOLD SCRIPT CAPITAL T;Lu;0;L; 0054;;;;N;;;;; 1D4E4;MATHEMATICAL BOLD SCRIPT CAPITAL U;Lu;0;L; 0055;;;;N;;;;; 1D4E5;MATHEMATICAL BOLD SCRIPT CAPITAL V;Lu;0;L; 0056;;;;N;;;;; 1D4E6;MATHEMATICAL BOLD SCRIPT CAPITAL W;Lu;0;L; 0057;;;;N;;;;; 1D4E7;MATHEMATICAL BOLD SCRIPT CAPITAL X;Lu;0;L; 0058;;;;N;;;;; 1D4E8;MATHEMATICAL BOLD SCRIPT CAPITAL Y;Lu;0;L; 0059;;;;N;;;;; 1D4E9;MATHEMATICAL BOLD SCRIPT CAPITAL Z;Lu;0;L; 005A;;;;N;;;;; 1D4EA;MATHEMATICAL BOLD SCRIPT SMALL A;Ll;0;L; 0061;;;;N;;;;; 1D4EB;MATHEMATICAL BOLD SCRIPT SMALL B;Ll;0;L; 0062;;;;N;;;;; 1D4EC;MATHEMATICAL BOLD SCRIPT SMALL C;Ll;0;L; 0063;;;;N;;;;; 1D4ED;MATHEMATICAL BOLD SCRIPT SMALL D;Ll;0;L; 0064;;;;N;;;;; 1D4EE;MATHEMATICAL BOLD SCRIPT SMALL E;Ll;0;L; 0065;;;;N;;;;; 1D4EF;MATHEMATICAL BOLD SCRIPT SMALL F;Ll;0;L; 0066;;;;N;;;;; 1D4F0;MATHEMATICAL BOLD SCRIPT SMALL G;Ll;0;L; 0067;;;;N;;;;; 1D4F1;MATHEMATICAL BOLD SCRIPT SMALL H;Ll;0;L; 0068;;;;N;;;;; 1D4F2;MATHEMATICAL BOLD SCRIPT SMALL I;Ll;0;L; 0069;;;;N;;;;; 1D4F3;MATHEMATICAL BOLD SCRIPT SMALL J;Ll;0;L; 006A;;;;N;;;;; 1D4F4;MATHEMATICAL BOLD SCRIPT SMALL K;Ll;0;L; 006B;;;;N;;;;; 1D4F5;MATHEMATICAL BOLD SCRIPT SMALL L;Ll;0;L; 006C;;;;N;;;;; 1D4F6;MATHEMATICAL BOLD SCRIPT SMALL M;Ll;0;L; 006D;;;;N;;;;; 1D4F7;MATHEMATICAL BOLD SCRIPT SMALL N;Ll;0;L; 006E;;;;N;;;;; 1D4F8;MATHEMATICAL BOLD SCRIPT SMALL O;Ll;0;L; 006F;;;;N;;;;; 1D4F9;MATHEMATICAL BOLD SCRIPT SMALL P;Ll;0;L; 0070;;;;N;;;;; 1D4FA;MATHEMATICAL BOLD SCRIPT SMALL Q;Ll;0;L; 0071;;;;N;;;;; 1D4FB;MATHEMATICAL BOLD SCRIPT SMALL R;Ll;0;L; 0072;;;;N;;;;; 1D4FC;MATHEMATICAL BOLD SCRIPT SMALL S;Ll;0;L; 0073;;;;N;;;;; 1D4FD;MATHEMATICAL BOLD SCRIPT SMALL T;Ll;0;L; 0074;;;;N;;;;; 1D4FE;MATHEMATICAL BOLD SCRIPT SMALL U;Ll;0;L; 0075;;;;N;;;;; 1D4FF;MATHEMATICAL BOLD SCRIPT SMALL V;Ll;0;L; 0076;;;;N;;;;; 1D500;MATHEMATICAL BOLD SCRIPT SMALL W;Ll;0;L; 0077;;;;N;;;;; 1D501;MATHEMATICAL BOLD SCRIPT SMALL X;Ll;0;L; 0078;;;;N;;;;; 1D502;MATHEMATICAL BOLD SCRIPT SMALL Y;Ll;0;L; 0079;;;;N;;;;; 1D503;MATHEMATICAL BOLD SCRIPT SMALL Z;Ll;0;L; 007A;;;;N;;;;; 1D504;MATHEMATICAL FRAKTUR CAPITAL A;Lu;0;L; 0041;;;;N;;;;; 1D505;MATHEMATICAL FRAKTUR CAPITAL B;Lu;0;L; 0042;;;;N;;;;; 1D507;MATHEMATICAL FRAKTUR CAPITAL D;Lu;0;L; 0044;;;;N;;;;; 1D508;MATHEMATICAL FRAKTUR CAPITAL E;Lu;0;L; 0045;;;;N;;;;; 1D509;MATHEMATICAL FRAKTUR CAPITAL F;Lu;0;L; 0046;;;;N;;;;; 1D50A;MATHEMATICAL FRAKTUR CAPITAL G;Lu;0;L; 0047;;;;N;;;;; 1D50D;MATHEMATICAL FRAKTUR CAPITAL J;Lu;0;L; 004A;;;;N;;;;; 1D50E;MATHEMATICAL FRAKTUR CAPITAL K;Lu;0;L; 004B;;;;N;;;;; 1D50F;MATHEMATICAL FRAKTUR CAPITAL L;Lu;0;L; 004C;;;;N;;;;; 1D510;MATHEMATICAL FRAKTUR CAPITAL M;Lu;0;L; 004D;;;;N;;;;; 1D511;MATHEMATICAL FRAKTUR CAPITAL N;Lu;0;L; 004E;;;;N;;;;; 1D512;MATHEMATICAL FRAKTUR CAPITAL O;Lu;0;L; 004F;;;;N;;;;; 1D513;MATHEMATICAL FRAKTUR CAPITAL P;Lu;0;L; 0050;;;;N;;;;; 1D514;MATHEMATICAL FRAKTUR CAPITAL Q;Lu;0;L; 0051;;;;N;;;;; 1D516;MATHEMATICAL FRAKTUR CAPITAL S;Lu;0;L; 0053;;;;N;;;;; 1D517;MATHEMATICAL FRAKTUR CAPITAL T;Lu;0;L; 0054;;;;N;;;;; 1D518;MATHEMATICAL FRAKTUR CAPITAL U;Lu;0;L; 0055;;;;N;;;;; 1D519;MATHEMATICAL FRAKTUR CAPITAL V;Lu;0;L; 0056;;;;N;;;;; 1D51A;MATHEMATICAL FRAKTUR CAPITAL W;Lu;0;L; 0057;;;;N;;;;; 1D51B;MATHEMATICAL FRAKTUR CAPITAL X;Lu;0;L; 0058;;;;N;;;;; 1D51C;MATHEMATICAL FRAKTUR CAPITAL Y;Lu;0;L; 0059;;;;N;;;;; 1D51E;MATHEMATICAL FRAKTUR SMALL A;Ll;0;L; 0061;;;;N;;;;; 1D51F;MATHEMATICAL FRAKTUR SMALL B;Ll;0;L; 0062;;;;N;;;;; 1D520;MATHEMATICAL FRAKTUR SMALL C;Ll;0;L; 0063;;;;N;;;;; 1D521;MATHEMATICAL FRAKTUR SMALL D;Ll;0;L; 0064;;;;N;;;;; 1D522;MATHEMATICAL FRAKTUR SMALL E;Ll;0;L; 0065;;;;N;;;;; 1D523;MATHEMATICAL FRAKTUR SMALL F;Ll;0;L; 0066;;;;N;;;;; 1D524;MATHEMATICAL FRAKTUR SMALL G;Ll;0;L; 0067;;;;N;;;;; 1D525;MATHEMATICAL FRAKTUR SMALL H;Ll;0;L; 0068;;;;N;;;;; 1D526;MATHEMATICAL FRAKTUR SMALL I;Ll;0;L; 0069;;;;N;;;;; 1D527;MATHEMATICAL FRAKTUR SMALL J;Ll;0;L; 006A;;;;N;;;;; 1D528;MATHEMATICAL FRAKTUR SMALL K;Ll;0;L; 006B;;;;N;;;;; 1D529;MATHEMATICAL FRAKTUR SMALL L;Ll;0;L; 006C;;;;N;;;;; 1D52A;MATHEMATICAL FRAKTUR SMALL M;Ll;0;L; 006D;;;;N;;;;; 1D52B;MATHEMATICAL FRAKTUR SMALL N;Ll;0;L; 006E;;;;N;;;;; 1D52C;MATHEMATICAL FRAKTUR SMALL O;Ll;0;L; 006F;;;;N;;;;; 1D52D;MATHEMATICAL FRAKTUR SMALL P;Ll;0;L; 0070;;;;N;;;;; 1D52E;MATHEMATICAL FRAKTUR SMALL Q;Ll;0;L; 0071;;;;N;;;;; 1D52F;MATHEMATICAL FRAKTUR SMALL R;Ll;0;L; 0072;;;;N;;;;; 1D530;MATHEMATICAL FRAKTUR SMALL S;Ll;0;L; 0073;;;;N;;;;; 1D531;MATHEMATICAL FRAKTUR SMALL T;Ll;0;L; 0074;;;;N;;;;; 1D532;MATHEMATICAL FRAKTUR SMALL U;Ll;0;L; 0075;;;;N;;;;; 1D533;MATHEMATICAL FRAKTUR SMALL V;Ll;0;L; 0076;;;;N;;;;; 1D534;MATHEMATICAL FRAKTUR SMALL W;Ll;0;L; 0077;;;;N;;;;; 1D535;MATHEMATICAL FRAKTUR SMALL X;Ll;0;L; 0078;;;;N;;;;; 1D536;MATHEMATICAL FRAKTUR SMALL Y;Ll;0;L; 0079;;;;N;;;;; 1D537;MATHEMATICAL FRAKTUR SMALL Z;Ll;0;L; 007A;;;;N;;;;; 1D538;MATHEMATICAL DOUBLE-STRUCK CAPITAL A;Lu;0;L; 0041;;;;N;;;;; 1D539;MATHEMATICAL DOUBLE-STRUCK CAPITAL B;Lu;0;L; 0042;;;;N;;;;; 1D53B;MATHEMATICAL DOUBLE-STRUCK CAPITAL D;Lu;0;L; 0044;;;;N;;;;; 1D53C;MATHEMATICAL DOUBLE-STRUCK CAPITAL E;Lu;0;L; 0045;;;;N;;;;; 1D53D;MATHEMATICAL DOUBLE-STRUCK CAPITAL F;Lu;0;L; 0046;;;;N;;;;; 1D53E;MATHEMATICAL DOUBLE-STRUCK CAPITAL G;Lu;0;L; 0047;;;;N;;;;; 1D540;MATHEMATICAL DOUBLE-STRUCK CAPITAL I;Lu;0;L; 0049;;;;N;;;;; 1D541;MATHEMATICAL DOUBLE-STRUCK CAPITAL J;Lu;0;L; 004A;;;;N;;;;; 1D542;MATHEMATICAL DOUBLE-STRUCK CAPITAL K;Lu;0;L; 004B;;;;N;;;;; 1D543;MATHEMATICAL DOUBLE-STRUCK CAPITAL L;Lu;0;L; 004C;;;;N;;;;; 1D544;MATHEMATICAL DOUBLE-STRUCK CAPITAL M;Lu;0;L; 004D;;;;N;;;;; 1D546;MATHEMATICAL DOUBLE-STRUCK CAPITAL O;Lu;0;L; 004F;;;;N;;;;; 1D54A;MATHEMATICAL DOUBLE-STRUCK CAPITAL S;Lu;0;L; 0053;;;;N;;;;; 1D54B;MATHEMATICAL DOUBLE-STRUCK CAPITAL T;Lu;0;L; 0054;;;;N;;;;; 1D54C;MATHEMATICAL DOUBLE-STRUCK CAPITAL U;Lu;0;L; 0055;;;;N;;;;; 1D54D;MATHEMATICAL DOUBLE-STRUCK CAPITAL V;Lu;0;L; 0056;;;;N;;;;; 1D54E;MATHEMATICAL DOUBLE-STRUCK CAPITAL W;Lu;0;L; 0057;;;;N;;;;; 1D54F;MATHEMATICAL DOUBLE-STRUCK CAPITAL X;Lu;0;L; 0058;;;;N;;;;; 1D550;MATHEMATICAL DOUBLE-STRUCK CAPITAL Y;Lu;0;L; 0059;;;;N;;;;; 1D552;MATHEMATICAL DOUBLE-STRUCK SMALL A;Ll;0;L; 0061;;;;N;;;;; 1D553;MATHEMATICAL DOUBLE-STRUCK SMALL B;Ll;0;L; 0062;;;;N;;;;; 1D554;MATHEMATICAL DOUBLE-STRUCK SMALL C;Ll;0;L; 0063;;;;N;;;;; 1D555;MATHEMATICAL DOUBLE-STRUCK SMALL D;Ll;0;L; 0064;;;;N;;;;; 1D556;MATHEMATICAL DOUBLE-STRUCK SMALL E;Ll;0;L; 0065;;;;N;;;;; 1D557;MATHEMATICAL DOUBLE-STRUCK SMALL F;Ll;0;L; 0066;;;;N;;;;; 1D558;MATHEMATICAL DOUBLE-STRUCK SMALL G;Ll;0;L; 0067;;;;N;;;;; 1D559;MATHEMATICAL DOUBLE-STRUCK SMALL H;Ll;0;L; 0068;;;;N;;;;; 1D55A;MATHEMATICAL DOUBLE-STRUCK SMALL I;Ll;0;L; 0069;;;;N;;;;; 1D55B;MATHEMATICAL DOUBLE-STRUCK SMALL J;Ll;0;L; 006A;;;;N;;;;; 1D55C;MATHEMATICAL DOUBLE-STRUCK SMALL K;Ll;0;L; 006B;;;;N;;;;; 1D55D;MATHEMATICAL DOUBLE-STRUCK SMALL L;Ll;0;L; 006C;;;;N;;;;; 1D55E;MATHEMATICAL DOUBLE-STRUCK SMALL M;Ll;0;L; 006D;;;;N;;;;; 1D55F;MATHEMATICAL DOUBLE-STRUCK SMALL N;Ll;0;L; 006E;;;;N;;;;; 1D560;MATHEMATICAL DOUBLE-STRUCK SMALL O;Ll;0;L; 006F;;;;N;;;;; 1D561;MATHEMATICAL DOUBLE-STRUCK SMALL P;Ll;0;L; 0070;;;;N;;;;; 1D562;MATHEMATICAL DOUBLE-STRUCK SMALL Q;Ll;0;L; 0071;;;;N;;;;; 1D563;MATHEMATICAL DOUBLE-STRUCK SMALL R;Ll;0;L; 0072;;;;N;;;;; 1D564;MATHEMATICAL DOUBLE-STRUCK SMALL S;Ll;0;L; 0073;;;;N;;;;; 1D565;MATHEMATICAL DOUBLE-STRUCK SMALL T;Ll;0;L; 0074;;;;N;;;;; 1D566;MATHEMATICAL DOUBLE-STRUCK SMALL U;Ll;0;L; 0075;;;;N;;;;; 1D567;MATHEMATICAL DOUBLE-STRUCK SMALL V;Ll;0;L; 0076;;;;N;;;;; 1D568;MATHEMATICAL DOUBLE-STRUCK SMALL W;Ll;0;L; 0077;;;;N;;;;; 1D569;MATHEMATICAL DOUBLE-STRUCK SMALL X;Ll;0;L; 0078;;;;N;;;;; 1D56A;MATHEMATICAL DOUBLE-STRUCK SMALL Y;Ll;0;L; 0079;;;;N;;;;; 1D56B;MATHEMATICAL DOUBLE-STRUCK SMALL Z;Ll;0;L; 007A;;;;N;;;;; 1D56C;MATHEMATICAL BOLD FRAKTUR CAPITAL A;Lu;0;L; 0041;;;;N;;;;; 1D56D;MATHEMATICAL BOLD FRAKTUR CAPITAL B;Lu;0;L; 0042;;;;N;;;;; 1D56E;MATHEMATICAL BOLD FRAKTUR CAPITAL C;Lu;0;L; 0043;;;;N;;;;; 1D56F;MATHEMATICAL BOLD FRAKTUR CAPITAL D;Lu;0;L; 0044;;;;N;;;;; 1D570;MATHEMATICAL BOLD FRAKTUR CAPITAL E;Lu;0;L; 0045;;;;N;;;;; 1D571;MATHEMATICAL BOLD FRAKTUR CAPITAL F;Lu;0;L; 0046;;;;N;;;;; 1D572;MATHEMATICAL BOLD FRAKTUR CAPITAL G;Lu;0;L; 0047;;;;N;;;;; 1D573;MATHEMATICAL BOLD FRAKTUR CAPITAL H;Lu;0;L; 0048;;;;N;;;;; 1D574;MATHEMATICAL BOLD FRAKTUR CAPITAL I;Lu;0;L; 0049;;;;N;;;;; 1D575;MATHEMATICAL BOLD FRAKTUR CAPITAL J;Lu;0;L; 004A;;;;N;;;;; 1D576;MATHEMATICAL BOLD FRAKTUR CAPITAL K;Lu;0;L; 004B;;;;N;;;;; 1D577;MATHEMATICAL BOLD FRAKTUR CAPITAL L;Lu;0;L; 004C;;;;N;;;;; 1D578;MATHEMATICAL BOLD FRAKTUR CAPITAL M;Lu;0;L; 004D;;;;N;;;;; 1D579;MATHEMATICAL BOLD FRAKTUR CAPITAL N;Lu;0;L; 004E;;;;N;;;;; 1D57A;MATHEMATICAL BOLD FRAKTUR CAPITAL O;Lu;0;L; 004F;;;;N;;;;; 1D57B;MATHEMATICAL BOLD FRAKTUR CAPITAL P;Lu;0;L; 0050;;;;N;;;;; 1D57C;MATHEMATICAL BOLD FRAKTUR CAPITAL Q;Lu;0;L; 0051;;;;N;;;;; 1D57D;MATHEMATICAL BOLD FRAKTUR CAPITAL R;Lu;0;L; 0052;;;;N;;;;; 1D57E;MATHEMATICAL BOLD FRAKTUR CAPITAL S;Lu;0;L; 0053;;;;N;;;;; 1D57F;MATHEMATICAL BOLD FRAKTUR CAPITAL T;Lu;0;L; 0054;;;;N;;;;; 1D580;MATHEMATICAL BOLD FRAKTUR CAPITAL U;Lu;0;L; 0055;;;;N;;;;; 1D581;MATHEMATICAL BOLD FRAKTUR CAPITAL V;Lu;0;L; 0056;;;;N;;;;; 1D582;MATHEMATICAL BOLD FRAKTUR CAPITAL W;Lu;0;L; 0057;;;;N;;;;; 1D583;MATHEMATICAL BOLD FRAKTUR CAPITAL X;Lu;0;L; 0058;;;;N;;;;; 1D584;MATHEMATICAL BOLD FRAKTUR CAPITAL Y;Lu;0;L; 0059;;;;N;;;;; 1D585;MATHEMATICAL BOLD FRAKTUR CAPITAL Z;Lu;0;L; 005A;;;;N;;;;; 1D586;MATHEMATICAL BOLD FRAKTUR SMALL A;Ll;0;L; 0061;;;;N;;;;; 1D587;MATHEMATICAL BOLD FRAKTUR SMALL B;Ll;0;L; 0062;;;;N;;;;; 1D588;MATHEMATICAL BOLD FRAKTUR SMALL C;Ll;0;L; 0063;;;;N;;;;; 1D589;MATHEMATICAL BOLD FRAKTUR SMALL D;Ll;0;L; 0064;;;;N;;;;; 1D58A;MATHEMATICAL BOLD FRAKTUR SMALL E;Ll;0;L; 0065;;;;N;;;;; 1D58B;MATHEMATICAL BOLD FRAKTUR SMALL F;Ll;0;L; 0066;;;;N;;;;; 1D58C;MATHEMATICAL BOLD FRAKTUR SMALL G;Ll;0;L; 0067;;;;N;;;;; 1D58D;MATHEMATICAL BOLD FRAKTUR SMALL H;Ll;0;L; 0068;;;;N;;;;; 1D58E;MATHEMATICAL BOLD FRAKTUR SMALL I;Ll;0;L; 0069;;;;N;;;;; 1D58F;MATHEMATICAL BOLD FRAKTUR SMALL J;Ll;0;L; 006A;;;;N;;;;; 1D590;MATHEMATICAL BOLD FRAKTUR SMALL K;Ll;0;L; 006B;;;;N;;;;; 1D591;MATHEMATICAL BOLD FRAKTUR SMALL L;Ll;0;L; 006C;;;;N;;;;; 1D592;MATHEMATICAL BOLD FRAKTUR SMALL M;Ll;0;L; 006D;;;;N;;;;; 1D593;MATHEMATICAL BOLD FRAKTUR SMALL N;Ll;0;L; 006E;;;;N;;;;; 1D594;MATHEMATICAL BOLD FRAKTUR SMALL O;Ll;0;L; 006F;;;;N;;;;; 1D595;MATHEMATICAL BOLD FRAKTUR SMALL P;Ll;0;L; 0070;;;;N;;;;; 1D596;MATHEMATICAL BOLD FRAKTUR SMALL Q;Ll;0;L; 0071;;;;N;;;;; 1D597;MATHEMATICAL BOLD FRAKTUR SMALL R;Ll;0;L; 0072;;;;N;;;;; 1D598;MATHEMATICAL BOLD FRAKTUR SMALL S;Ll;0;L; 0073;;;;N;;;;; 1D599;MATHEMATICAL BOLD FRAKTUR SMALL T;Ll;0;L; 0074;;;;N;;;;; 1D59A;MATHEMATICAL BOLD FRAKTUR SMALL U;Ll;0;L; 0075;;;;N;;;;; 1D59B;MATHEMATICAL BOLD FRAKTUR SMALL V;Ll;0;L; 0076;;;;N;;;;; 1D59C;MATHEMATICAL BOLD FRAKTUR SMALL W;Ll;0;L; 0077;;;;N;;;;; 1D59D;MATHEMATICAL BOLD FRAKTUR SMALL X;Ll;0;L; 0078;;;;N;;;;; 1D59E;MATHEMATICAL BOLD FRAKTUR SMALL Y;Ll;0;L; 0079;;;;N;;;;; 1D59F;MATHEMATICAL BOLD FRAKTUR SMALL Z;Ll;0;L; 007A;;;;N;;;;; 1D5A0;MATHEMATICAL SANS-SERIF CAPITAL A;Lu;0;L; 0041;;;;N;;;;; 1D5A1;MATHEMATICAL SANS-SERIF CAPITAL B;Lu;0;L; 0042;;;;N;;;;; 1D5A2;MATHEMATICAL SANS-SERIF CAPITAL C;Lu;0;L; 0043;;;;N;;;;; 1D5A3;MATHEMATICAL SANS-SERIF CAPITAL D;Lu;0;L; 0044;;;;N;;;;; 1D5A4;MATHEMATICAL SANS-SERIF CAPITAL E;Lu;0;L; 0045;;;;N;;;;; 1D5A5;MATHEMATICAL SANS-SERIF CAPITAL F;Lu;0;L; 0046;;;;N;;;;; 1D5A6;MATHEMATICAL SANS-SERIF CAPITAL G;Lu;0;L; 0047;;;;N;;;;; 1D5A7;MATHEMATICAL SANS-SERIF CAPITAL H;Lu;0;L; 0048;;;;N;;;;; 1D5A8;MATHEMATICAL SANS-SERIF CAPITAL I;Lu;0;L; 0049;;;;N;;;;; 1D5A9;MATHEMATICAL SANS-SERIF CAPITAL J;Lu;0;L; 004A;;;;N;;;;; 1D5AA;MATHEMATICAL SANS-SERIF CAPITAL K;Lu;0;L; 004B;;;;N;;;;; 1D5AB;MATHEMATICAL SANS-SERIF CAPITAL L;Lu;0;L; 004C;;;;N;;;;; 1D5AC;MATHEMATICAL SANS-SERIF CAPITAL M;Lu;0;L; 004D;;;;N;;;;; 1D5AD;MATHEMATICAL SANS-SERIF CAPITAL N;Lu;0;L; 004E;;;;N;;;;; 1D5AE;MATHEMATICAL SANS-SERIF CAPITAL O;Lu;0;L; 004F;;;;N;;;;; 1D5AF;MATHEMATICAL SANS-SERIF CAPITAL P;Lu;0;L; 0050;;;;N;;;;; 1D5B0;MATHEMATICAL SANS-SERIF CAPITAL Q;Lu;0;L; 0051;;;;N;;;;; 1D5B1;MATHEMATICAL SANS-SERIF CAPITAL R;Lu;0;L; 0052;;;;N;;;;; 1D5B2;MATHEMATICAL SANS-SERIF CAPITAL S;Lu;0;L; 0053;;;;N;;;;; 1D5B3;MATHEMATICAL SANS-SERIF CAPITAL T;Lu;0;L; 0054;;;;N;;;;; 1D5B4;MATHEMATICAL SANS-SERIF CAPITAL U;Lu;0;L; 0055;;;;N;;;;; 1D5B5;MATHEMATICAL SANS-SERIF CAPITAL V;Lu;0;L; 0056;;;;N;;;;; 1D5B6;MATHEMATICAL SANS-SERIF CAPITAL W;Lu;0;L; 0057;;;;N;;;;; 1D5B7;MATHEMATICAL SANS-SERIF CAPITAL X;Lu;0;L; 0058;;;;N;;;;; 1D5B8;MATHEMATICAL SANS-SERIF CAPITAL Y;Lu;0;L; 0059;;;;N;;;;; 1D5B9;MATHEMATICAL SANS-SERIF CAPITAL Z;Lu;0;L; 005A;;;;N;;;;; 1D5BA;MATHEMATICAL SANS-SERIF SMALL A;Ll;0;L; 0061;;;;N;;;;; 1D5BB;MATHEMATICAL SANS-SERIF SMALL B;Ll;0;L; 0062;;;;N;;;;; 1D5BC;MATHEMATICAL SANS-SERIF SMALL C;Ll;0;L; 0063;;;;N;;;;; 1D5BD;MATHEMATICAL SANS-SERIF SMALL D;Ll;0;L; 0064;;;;N;;;;; 1D5BE;MATHEMATICAL SANS-SERIF SMALL E;Ll;0;L; 0065;;;;N;;;;; 1D5BF;MATHEMATICAL SANS-SERIF SMALL F;Ll;0;L; 0066;;;;N;;;;; 1D5C0;MATHEMATICAL SANS-SERIF SMALL G;Ll;0;L; 0067;;;;N;;;;; 1D5C1;MATHEMATICAL SANS-SERIF SMALL H;Ll;0;L; 0068;;;;N;;;;; 1D5C2;MATHEMATICAL SANS-SERIF SMALL I;Ll;0;L; 0069;;;;N;;;;; 1D5C3;MATHEMATICAL SANS-SERIF SMALL J;Ll;0;L; 006A;;;;N;;;;; 1D5C4;MATHEMATICAL SANS-SERIF SMALL K;Ll;0;L; 006B;;;;N;;;;; 1D5C5;MATHEMATICAL SANS-SERIF SMALL L;Ll;0;L; 006C;;;;N;;;;; 1D5C6;MATHEMATICAL SANS-SERIF SMALL M;Ll;0;L; 006D;;;;N;;;;; 1D5C7;MATHEMATICAL SANS-SERIF SMALL N;Ll;0;L; 006E;;;;N;;;;; 1D5C8;MATHEMATICAL SANS-SERIF SMALL O;Ll;0;L; 006F;;;;N;;;;; 1D5C9;MATHEMATICAL SANS-SERIF SMALL P;Ll;0;L; 0070;;;;N;;;;; 1D5CA;MATHEMATICAL SANS-SERIF SMALL Q;Ll;0;L; 0071;;;;N;;;;; 1D5CB;MATHEMATICAL SANS-SERIF SMALL R;Ll;0;L; 0072;;;;N;;;;; 1D5CC;MATHEMATICAL SANS-SERIF SMALL S;Ll;0;L; 0073;;;;N;;;;; 1D5CD;MATHEMATICAL SANS-SERIF SMALL T;Ll;0;L; 0074;;;;N;;;;; 1D5CE;MATHEMATICAL SANS-SERIF SMALL U;Ll;0;L; 0075;;;;N;;;;; 1D5CF;MATHEMATICAL SANS-SERIF SMALL V;Ll;0;L; 0076;;;;N;;;;; 1D5D0;MATHEMATICAL SANS-SERIF SMALL W;Ll;0;L; 0077;;;;N;;;;; 1D5D1;MATHEMATICAL SANS-SERIF SMALL X;Ll;0;L; 0078;;;;N;;;;; 1D5D2;MATHEMATICAL SANS-SERIF SMALL Y;Ll;0;L; 0079;;;;N;;;;; 1D5D3;MATHEMATICAL SANS-SERIF SMALL Z;Ll;0;L; 007A;;;;N;;;;; 1D5D4;MATHEMATICAL SANS-SERIF BOLD CAPITAL A;Lu;0;L; 0041;;;;N;;;;; 1D5D5;MATHEMATICAL SANS-SERIF BOLD CAPITAL B;Lu;0;L; 0042;;;;N;;;;; 1D5D6;MATHEMATICAL SANS-SERIF BOLD CAPITAL C;Lu;0;L; 0043;;;;N;;;;; 1D5D7;MATHEMATICAL SANS-SERIF BOLD CAPITAL D;Lu;0;L; 0044;;;;N;;;;; 1D5D8;MATHEMATICAL SANS-SERIF BOLD CAPITAL E;Lu;0;L; 0045;;;;N;;;;; 1D5D9;MATHEMATICAL SANS-SERIF BOLD CAPITAL F;Lu;0;L; 0046;;;;N;;;;; 1D5DA;MATHEMATICAL SANS-SERIF BOLD CAPITAL G;Lu;0;L; 0047;;;;N;;;;; 1D5DB;MATHEMATICAL SANS-SERIF BOLD CAPITAL H;Lu;0;L; 0048;;;;N;;;;; 1D5DC;MATHEMATICAL SANS-SERIF BOLD CAPITAL I;Lu;0;L; 0049;;;;N;;;;; 1D5DD;MATHEMATICAL SANS-SERIF BOLD CAPITAL J;Lu;0;L; 004A;;;;N;;;;; 1D5DE;MATHEMATICAL SANS-SERIF BOLD CAPITAL K;Lu;0;L; 004B;;;;N;;;;; 1D5DF;MATHEMATICAL SANS-SERIF BOLD CAPITAL L;Lu;0;L; 004C;;;;N;;;;; 1D5E0;MATHEMATICAL SANS-SERIF BOLD CAPITAL M;Lu;0;L; 004D;;;;N;;;;; 1D5E1;MATHEMATICAL SANS-SERIF BOLD CAPITAL N;Lu;0;L; 004E;;;;N;;;;; 1D5E2;MATHEMATICAL SANS-SERIF BOLD CAPITAL O;Lu;0;L; 004F;;;;N;;;;; 1D5E3;MATHEMATICAL SANS-SERIF BOLD CAPITAL P;Lu;0;L; 0050;;;;N;;;;; 1D5E4;MATHEMATICAL SANS-SERIF BOLD CAPITAL Q;Lu;0;L; 0051;;;;N;;;;; 1D5E5;MATHEMATICAL SANS-SERIF BOLD CAPITAL R;Lu;0;L; 0052;;;;N;;;;; 1D5E6;MATHEMATICAL SANS-SERIF BOLD CAPITAL S;Lu;0;L; 0053;;;;N;;;;; 1D5E7;MATHEMATICAL SANS-SERIF BOLD CAPITAL T;Lu;0;L; 0054;;;;N;;;;; 1D5E8;MATHEMATICAL SANS-SERIF BOLD CAPITAL U;Lu;0;L; 0055;;;;N;;;;; 1D5E9;MATHEMATICAL SANS-SERIF BOLD CAPITAL V;Lu;0;L; 0056;;;;N;;;;; 1D5EA;MATHEMATICAL SANS-SERIF BOLD CAPITAL W;Lu;0;L; 0057;;;;N;;;;; 1D5EB;MATHEMATICAL SANS-SERIF BOLD CAPITAL X;Lu;0;L; 0058;;;;N;;;;; 1D5EC;MATHEMATICAL SANS-SERIF BOLD CAPITAL Y;Lu;0;L; 0059;;;;N;;;;; 1D5ED;MATHEMATICAL SANS-SERIF BOLD CAPITAL Z;Lu;0;L; 005A;;;;N;;;;; 1D5EE;MATHEMATICAL SANS-SERIF BOLD SMALL A;Ll;0;L; 0061;;;;N;;;;; 1D5EF;MATHEMATICAL SANS-SERIF BOLD SMALL B;Ll;0;L; 0062;;;;N;;;;; 1D5F0;MATHEMATICAL SANS-SERIF BOLD SMALL C;Ll;0;L; 0063;;;;N;;;;; 1D5F1;MATHEMATICAL SANS-SERIF BOLD SMALL D;Ll;0;L; 0064;;;;N;;;;; 1D5F2;MATHEMATICAL SANS-SERIF BOLD SMALL E;Ll;0;L; 0065;;;;N;;;;; 1D5F3;MATHEMATICAL SANS-SERIF BOLD SMALL F;Ll;0;L; 0066;;;;N;;;;; 1D5F4;MATHEMATICAL SANS-SERIF BOLD SMALL G;Ll;0;L; 0067;;;;N;;;;; 1D5F5;MATHEMATICAL SANS-SERIF BOLD SMALL H;Ll;0;L; 0068;;;;N;;;;; 1D5F6;MATHEMATICAL SANS-SERIF BOLD SMALL I;Ll;0;L; 0069;;;;N;;;;; 1D5F7;MATHEMATICAL SANS-SERIF BOLD SMALL J;Ll;0;L; 006A;;;;N;;;;; 1D5F8;MATHEMATICAL SANS-SERIF BOLD SMALL K;Ll;0;L; 006B;;;;N;;;;; 1D5F9;MATHEMATICAL SANS-SERIF BOLD SMALL L;Ll;0;L; 006C;;;;N;;;;; 1D5FA;MATHEMATICAL SANS-SERIF BOLD SMALL M;Ll;0;L; 006D;;;;N;;;;; 1D5FB;MATHEMATICAL SANS-SERIF BOLD SMALL N;Ll;0;L; 006E;;;;N;;;;; 1D5FC;MATHEMATICAL SANS-SERIF BOLD SMALL O;Ll;0;L; 006F;;;;N;;;;; 1D5FD;MATHEMATICAL SANS-SERIF BOLD SMALL P;Ll;0;L; 0070;;;;N;;;;; 1D5FE;MATHEMATICAL SANS-SERIF BOLD SMALL Q;Ll;0;L; 0071;;;;N;;;;; 1D5FF;MATHEMATICAL SANS-SERIF BOLD SMALL R;Ll;0;L; 0072;;;;N;;;;; 1D600;MATHEMATICAL SANS-SERIF BOLD SMALL S;Ll;0;L; 0073;;;;N;;;;; 1D601;MATHEMATICAL SANS-SERIF BOLD SMALL T;Ll;0;L; 0074;;;;N;;;;; 1D602;MATHEMATICAL SANS-SERIF BOLD SMALL U;Ll;0;L; 0075;;;;N;;;;; 1D603;MATHEMATICAL SANS-SERIF BOLD SMALL V;Ll;0;L; 0076;;;;N;;;;; 1D604;MATHEMATICAL SANS-SERIF BOLD SMALL W;Ll;0;L; 0077;;;;N;;;;; 1D605;MATHEMATICAL SANS-SERIF BOLD SMALL X;Ll;0;L; 0078;;;;N;;;;; 1D606;MATHEMATICAL SANS-SERIF BOLD SMALL Y;Ll;0;L; 0079;;;;N;;;;; 1D607;MATHEMATICAL SANS-SERIF BOLD SMALL Z;Ll;0;L; 007A;;;;N;;;;; 1D608;MATHEMATICAL SANS-SERIF ITALIC CAPITAL A;Lu;0;L; 0041;;;;N;;;;; 1D609;MATHEMATICAL SANS-SERIF ITALIC CAPITAL B;Lu;0;L; 0042;;;;N;;;;; 1D60A;MATHEMATICAL SANS-SERIF ITALIC CAPITAL C;Lu;0;L; 0043;;;;N;;;;; 1D60B;MATHEMATICAL SANS-SERIF ITALIC CAPITAL D;Lu;0;L; 0044;;;;N;;;;; 1D60C;MATHEMATICAL SANS-SERIF ITALIC CAPITAL E;Lu;0;L; 0045;;;;N;;;;; 1D60D;MATHEMATICAL SANS-SERIF ITALIC CAPITAL F;Lu;0;L; 0046;;;;N;;;;; 1D60E;MATHEMATICAL SANS-SERIF ITALIC CAPITAL G;Lu;0;L; 0047;;;;N;;;;; 1D60F;MATHEMATICAL SANS-SERIF ITALIC CAPITAL H;Lu;0;L; 0048;;;;N;;;;; 1D610;MATHEMATICAL SANS-SERIF ITALIC CAPITAL I;Lu;0;L; 0049;;;;N;;;;; 1D611;MATHEMATICAL SANS-SERIF ITALIC CAPITAL J;Lu;0;L; 004A;;;;N;;;;; 1D612;MATHEMATICAL SANS-SERIF ITALIC CAPITAL K;Lu;0;L; 004B;;;;N;;;;; 1D613;MATHEMATICAL SANS-SERIF ITALIC CAPITAL L;Lu;0;L; 004C;;;;N;;;;; 1D614;MATHEMATICAL SANS-SERIF ITALIC CAPITAL M;Lu;0;L; 004D;;;;N;;;;; 1D615;MATHEMATICAL SANS-SERIF ITALIC CAPITAL N;Lu;0;L; 004E;;;;N;;;;; 1D616;MATHEMATICAL SANS-SERIF ITALIC CAPITAL O;Lu;0;L; 004F;;;;N;;;;; 1D617;MATHEMATICAL SANS-SERIF ITALIC CAPITAL P;Lu;0;L; 0050;;;;N;;;;; 1D618;MATHEMATICAL SANS-SERIF ITALIC CAPITAL Q;Lu;0;L; 0051;;;;N;;;;; 1D619;MATHEMATICAL SANS-SERIF ITALIC CAPITAL R;Lu;0;L; 0052;;;;N;;;;; 1D61A;MATHEMATICAL SANS-SERIF ITALIC CAPITAL S;Lu;0;L; 0053;;;;N;;;;; 1D61B;MATHEMATICAL SANS-SERIF ITALIC CAPITAL T;Lu;0;L; 0054;;;;N;;;;; 1D61C;MATHEMATICAL SANS-SERIF ITALIC CAPITAL U;Lu;0;L; 0055;;;;N;;;;; 1D61D;MATHEMATICAL SANS-SERIF ITALIC CAPITAL V;Lu;0;L; 0056;;;;N;;;;; 1D61E;MATHEMATICAL SANS-SERIF ITALIC CAPITAL W;Lu;0;L; 0057;;;;N;;;;; 1D61F;MATHEMATICAL SANS-SERIF ITALIC CAPITAL X;Lu;0;L; 0058;;;;N;;;;; 1D620;MATHEMATICAL SANS-SERIF ITALIC CAPITAL Y;Lu;0;L; 0059;;;;N;;;;; 1D621;MATHEMATICAL SANS-SERIF ITALIC CAPITAL Z;Lu;0;L; 005A;;;;N;;;;; 1D622;MATHEMATICAL SANS-SERIF ITALIC SMALL A;Ll;0;L; 0061;;;;N;;;;; 1D623;MATHEMATICAL SANS-SERIF ITALIC SMALL B;Ll;0;L; 0062;;;;N;;;;; 1D624;MATHEMATICAL SANS-SERIF ITALIC SMALL C;Ll;0;L; 0063;;;;N;;;;; 1D625;MATHEMATICAL SANS-SERIF ITALIC SMALL D;Ll;0;L; 0064;;;;N;;;;; 1D626;MATHEMATICAL SANS-SERIF ITALIC SMALL E;Ll;0;L; 0065;;;;N;;;;; 1D627;MATHEMATICAL SANS-SERIF ITALIC SMALL F;Ll;0;L; 0066;;;;N;;;;; 1D628;MATHEMATICAL SANS-SERIF ITALIC SMALL G;Ll;0;L; 0067;;;;N;;;;; 1D629;MATHEMATICAL SANS-SERIF ITALIC SMALL H;Ll;0;L; 0068;;;;N;;;;; 1D62A;MATHEMATICAL SANS-SERIF ITALIC SMALL I;Ll;0;L; 0069;;;;N;;;;; 1D62B;MATHEMATICAL SANS-SERIF ITALIC SMALL J;Ll;0;L; 006A;;;;N;;;;; 1D62C;MATHEMATICAL SANS-SERIF ITALIC SMALL K;Ll;0;L; 006B;;;;N;;;;; 1D62D;MATHEMATICAL SANS-SERIF ITALIC SMALL L;Ll;0;L; 006C;;;;N;;;;; 1D62E;MATHEMATICAL SANS-SERIF ITALIC SMALL M;Ll;0;L; 006D;;;;N;;;;; 1D62F;MATHEMATICAL SANS-SERIF ITALIC SMALL N;Ll;0;L; 006E;;;;N;;;;; 1D630;MATHEMATICAL SANS-SERIF ITALIC SMALL O;Ll;0;L; 006F;;;;N;;;;; 1D631;MATHEMATICAL SANS-SERIF ITALIC SMALL P;Ll;0;L; 0070;;;;N;;;;; 1D632;MATHEMATICAL SANS-SERIF ITALIC SMALL Q;Ll;0;L; 0071;;;;N;;;;; 1D633;MATHEMATICAL SANS-SERIF ITALIC SMALL R;Ll;0;L; 0072;;;;N;;;;; 1D634;MATHEMATICAL SANS-SERIF ITALIC SMALL S;Ll;0;L; 0073;;;;N;;;;; 1D635;MATHEMATICAL SANS-SERIF ITALIC SMALL T;Ll;0;L; 0074;;;;N;;;;; 1D636;MATHEMATICAL SANS-SERIF ITALIC SMALL U;Ll;0;L; 0075;;;;N;;;;; 1D637;MATHEMATICAL SANS-SERIF ITALIC SMALL V;Ll;0;L; 0076;;;;N;;;;; 1D638;MATHEMATICAL SANS-SERIF ITALIC SMALL W;Ll;0;L; 0077;;;;N;;;;; 1D639;MATHEMATICAL SANS-SERIF ITALIC SMALL X;Ll;0;L; 0078;;;;N;;;;; 1D63A;MATHEMATICAL SANS-SERIF ITALIC SMALL Y;Ll;0;L; 0079;;;;N;;;;; 1D63B;MATHEMATICAL SANS-SERIF ITALIC SMALL Z;Ll;0;L; 007A;;;;N;;;;; 1D63C;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL A;Lu;0;L; 0041;;;;N;;;;; 1D63D;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL B;Lu;0;L; 0042;;;;N;;;;; 1D63E;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL C;Lu;0;L; 0043;;;;N;;;;; 1D63F;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL D;Lu;0;L; 0044;;;;N;;;;; 1D640;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL E;Lu;0;L; 0045;;;;N;;;;; 1D641;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL F;Lu;0;L; 0046;;;;N;;;;; 1D642;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL G;Lu;0;L; 0047;;;;N;;;;; 1D643;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL H;Lu;0;L; 0048;;;;N;;;;; 1D644;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL I;Lu;0;L; 0049;;;;N;;;;; 1D645;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL J;Lu;0;L; 004A;;;;N;;;;; 1D646;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL K;Lu;0;L; 004B;;;;N;;;;; 1D647;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL L;Lu;0;L; 004C;;;;N;;;;; 1D648;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL M;Lu;0;L; 004D;;;;N;;;;; 1D649;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL N;Lu;0;L; 004E;;;;N;;;;; 1D64A;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL O;Lu;0;L; 004F;;;;N;;;;; 1D64B;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL P;Lu;0;L; 0050;;;;N;;;;; 1D64C;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL Q;Lu;0;L; 0051;;;;N;;;;; 1D64D;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL R;Lu;0;L; 0052;;;;N;;;;; 1D64E;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL S;Lu;0;L; 0053;;;;N;;;;; 1D64F;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL T;Lu;0;L; 0054;;;;N;;;;; 1D650;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL U;Lu;0;L; 0055;;;;N;;;;; 1D651;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL V;Lu;0;L; 0056;;;;N;;;;; 1D652;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL W;Lu;0;L; 0057;;;;N;;;;; 1D653;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL X;Lu;0;L; 0058;;;;N;;;;; 1D654;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL Y;Lu;0;L; 0059;;;;N;;;;; 1D655;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL Z;Lu;0;L; 005A;;;;N;;;;; 1D656;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL A;Ll;0;L; 0061;;;;N;;;;; 1D657;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL B;Ll;0;L; 0062;;;;N;;;;; 1D658;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL C;Ll;0;L; 0063;;;;N;;;;; 1D659;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL D;Ll;0;L; 0064;;;;N;;;;; 1D65A;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL E;Ll;0;L; 0065;;;;N;;;;; 1D65B;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL F;Ll;0;L; 0066;;;;N;;;;; 1D65C;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL G;Ll;0;L; 0067;;;;N;;;;; 1D65D;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL H;Ll;0;L; 0068;;;;N;;;;; 1D65E;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL I;Ll;0;L; 0069;;;;N;;;;; 1D65F;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL J;Ll;0;L; 006A;;;;N;;;;; 1D660;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL K;Ll;0;L; 006B;;;;N;;;;; 1D661;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL L;Ll;0;L; 006C;;;;N;;;;; 1D662;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL M;Ll;0;L; 006D;;;;N;;;;; 1D663;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL N;Ll;0;L; 006E;;;;N;;;;; 1D664;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL O;Ll;0;L; 006F;;;;N;;;;; 1D665;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL P;Ll;0;L; 0070;;;;N;;;;; 1D666;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL Q;Ll;0;L; 0071;;;;N;;;;; 1D667;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL R;Ll;0;L; 0072;;;;N;;;;; 1D668;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL S;Ll;0;L; 0073;;;;N;;;;; 1D669;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL T;Ll;0;L; 0074;;;;N;;;;; 1D66A;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL U;Ll;0;L; 0075;;;;N;;;;; 1D66B;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL V;Ll;0;L; 0076;;;;N;;;;; 1D66C;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL W;Ll;0;L; 0077;;;;N;;;;; 1D66D;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL X;Ll;0;L; 0078;;;;N;;;;; 1D66E;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL Y;Ll;0;L; 0079;;;;N;;;;; 1D66F;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL Z;Ll;0;L; 007A;;;;N;;;;; 1D670;MATHEMATICAL MONOSPACE CAPITAL A;Lu;0;L; 0041;;;;N;;;;; 1D671;MATHEMATICAL MONOSPACE CAPITAL B;Lu;0;L; 0042;;;;N;;;;; 1D672;MATHEMATICAL MONOSPACE CAPITAL C;Lu;0;L; 0043;;;;N;;;;; 1D673;MATHEMATICAL MONOSPACE CAPITAL D;Lu;0;L; 0044;;;;N;;;;; 1D674;MATHEMATICAL MONOSPACE CAPITAL E;Lu;0;L; 0045;;;;N;;;;; 1D675;MATHEMATICAL MONOSPACE CAPITAL F;Lu;0;L; 0046;;;;N;;;;; 1D676;MATHEMATICAL MONOSPACE CAPITAL G;Lu;0;L; 0047;;;;N;;;;; 1D677;MATHEMATICAL MONOSPACE CAPITAL H;Lu;0;L; 0048;;;;N;;;;; 1D678;MATHEMATICAL MONOSPACE CAPITAL I;Lu;0;L; 0049;;;;N;;;;; 1D679;MATHEMATICAL MONOSPACE CAPITAL J;Lu;0;L; 004A;;;;N;;;;; 1D67A;MATHEMATICAL MONOSPACE CAPITAL K;Lu;0;L; 004B;;;;N;;;;; 1D67B;MATHEMATICAL MONOSPACE CAPITAL L;Lu;0;L; 004C;;;;N;;;;; 1D67C;MATHEMATICAL MONOSPACE CAPITAL M;Lu;0;L; 004D;;;;N;;;;; 1D67D;MATHEMATICAL MONOSPACE CAPITAL N;Lu;0;L; 004E;;;;N;;;;; 1D67E;MATHEMATICAL MONOSPACE CAPITAL O;Lu;0;L; 004F;;;;N;;;;; 1D67F;MATHEMATICAL MONOSPACE CAPITAL P;Lu;0;L; 0050;;;;N;;;;; 1D680;MATHEMATICAL MONOSPACE CAPITAL Q;Lu;0;L; 0051;;;;N;;;;; 1D681;MATHEMATICAL MONOSPACE CAPITAL R;Lu;0;L; 0052;;;;N;;;;; 1D682;MATHEMATICAL MONOSPACE CAPITAL S;Lu;0;L; 0053;;;;N;;;;; 1D683;MATHEMATICAL MONOSPACE CAPITAL T;Lu;0;L; 0054;;;;N;;;;; 1D684;MATHEMATICAL MONOSPACE CAPITAL U;Lu;0;L; 0055;;;;N;;;;; 1D685;MATHEMATICAL MONOSPACE CAPITAL V;Lu;0;L; 0056;;;;N;;;;; 1D686;MATHEMATICAL MONOSPACE CAPITAL W;Lu;0;L; 0057;;;;N;;;;; 1D687;MATHEMATICAL MONOSPACE CAPITAL X;Lu;0;L; 0058;;;;N;;;;; 1D688;MATHEMATICAL MONOSPACE CAPITAL Y;Lu;0;L; 0059;;;;N;;;;; 1D689;MATHEMATICAL MONOSPACE CAPITAL Z;Lu;0;L; 005A;;;;N;;;;; 1D68A;MATHEMATICAL MONOSPACE SMALL A;Ll;0;L; 0061;;;;N;;;;; 1D68B;MATHEMATICAL MONOSPACE SMALL B;Ll;0;L; 0062;;;;N;;;;; 1D68C;MATHEMATICAL MONOSPACE SMALL C;Ll;0;L; 0063;;;;N;;;;; 1D68D;MATHEMATICAL MONOSPACE SMALL D;Ll;0;L; 0064;;;;N;;;;; 1D68E;MATHEMATICAL MONOSPACE SMALL E;Ll;0;L; 0065;;;;N;;;;; 1D68F;MATHEMATICAL MONOSPACE SMALL F;Ll;0;L; 0066;;;;N;;;;; 1D690;MATHEMATICAL MONOSPACE SMALL G;Ll;0;L; 0067;;;;N;;;;; 1D691;MATHEMATICAL MONOSPACE SMALL H;Ll;0;L; 0068;;;;N;;;;; 1D692;MATHEMATICAL MONOSPACE SMALL I;Ll;0;L; 0069;;;;N;;;;; 1D693;MATHEMATICAL MONOSPACE SMALL J;Ll;0;L; 006A;;;;N;;;;; 1D694;MATHEMATICAL MONOSPACE SMALL K;Ll;0;L; 006B;;;;N;;;;; 1D695;MATHEMATICAL MONOSPACE SMALL L;Ll;0;L; 006C;;;;N;;;;; 1D696;MATHEMATICAL MONOSPACE SMALL M;Ll;0;L; 006D;;;;N;;;;; 1D697;MATHEMATICAL MONOSPACE SMALL N;Ll;0;L; 006E;;;;N;;;;; 1D698;MATHEMATICAL MONOSPACE SMALL O;Ll;0;L; 006F;;;;N;;;;; 1D699;MATHEMATICAL MONOSPACE SMALL P;Ll;0;L; 0070;;;;N;;;;; 1D69A;MATHEMATICAL MONOSPACE SMALL Q;Ll;0;L; 0071;;;;N;;;;; 1D69B;MATHEMATICAL MONOSPACE SMALL R;Ll;0;L; 0072;;;;N;;;;; 1D69C;MATHEMATICAL MONOSPACE SMALL S;Ll;0;L; 0073;;;;N;;;;; 1D69D;MATHEMATICAL MONOSPACE SMALL T;Ll;0;L; 0074;;;;N;;;;; 1D69E;MATHEMATICAL MONOSPACE SMALL U;Ll;0;L; 0075;;;;N;;;;; 1D69F;MATHEMATICAL MONOSPACE SMALL V;Ll;0;L; 0076;;;;N;;;;; 1D6A0;MATHEMATICAL MONOSPACE SMALL W;Ll;0;L; 0077;;;;N;;;;; 1D6A1;MATHEMATICAL MONOSPACE SMALL X;Ll;0;L; 0078;;;;N;;;;; 1D6A2;MATHEMATICAL MONOSPACE SMALL Y;Ll;0;L; 0079;;;;N;;;;; 1D6A3;MATHEMATICAL MONOSPACE SMALL Z;Ll;0;L; 007A;;;;N;;;;; 1D6A4;MATHEMATICAL ITALIC SMALL DOTLESS I;Ll;0;L; 0131;;;;N;;;;; 1D6A5;MATHEMATICAL ITALIC SMALL DOTLESS J;Ll;0;L; 0237;;;;N;;;;; 1D6A8;MATHEMATICAL BOLD CAPITAL ALPHA;Lu;0;L; 0391;;;;N;;;;; 1D6A9;MATHEMATICAL BOLD CAPITAL BETA;Lu;0;L; 0392;;;;N;;;;; 1D6AA;MATHEMATICAL BOLD CAPITAL GAMMA;Lu;0;L; 0393;;;;N;;;;; 1D6AB;MATHEMATICAL BOLD CAPITAL DELTA;Lu;0;L; 0394;;;;N;;;;; 1D6AC;MATHEMATICAL BOLD CAPITAL EPSILON;Lu;0;L; 0395;;;;N;;;;; 1D6AD;MATHEMATICAL BOLD CAPITAL ZETA;Lu;0;L; 0396;;;;N;;;;; 1D6AE;MATHEMATICAL BOLD CAPITAL ETA;Lu;0;L; 0397;;;;N;;;;; 1D6AF;MATHEMATICAL BOLD CAPITAL THETA;Lu;0;L; 0398;;;;N;;;;; 1D6B0;MATHEMATICAL BOLD CAPITAL IOTA;Lu;0;L; 0399;;;;N;;;;; 1D6B1;MATHEMATICAL BOLD CAPITAL KAPPA;Lu;0;L; 039A;;;;N;;;;; 1D6B2;MATHEMATICAL BOLD CAPITAL LAMDA;Lu;0;L; 039B;;;;N;;;;; 1D6B3;MATHEMATICAL BOLD CAPITAL MU;Lu;0;L; 039C;;;;N;;;;; 1D6B4;MATHEMATICAL BOLD CAPITAL NU;Lu;0;L; 039D;;;;N;;;;; 1D6B5;MATHEMATICAL BOLD CAPITAL XI;Lu;0;L; 039E;;;;N;;;;; 1D6B6;MATHEMATICAL BOLD CAPITAL OMICRON;Lu;0;L; 039F;;;;N;;;;; 1D6B7;MATHEMATICAL BOLD CAPITAL PI;Lu;0;L; 03A0;;;;N;;;;; 1D6B8;MATHEMATICAL BOLD CAPITAL RHO;Lu;0;L; 03A1;;;;N;;;;; 1D6B9;MATHEMATICAL BOLD CAPITAL THETA SYMBOL;Lu;0;L; 03F4;;;;N;;;;; 1D6BA;MATHEMATICAL BOLD CAPITAL SIGMA;Lu;0;L; 03A3;;;;N;;;;; 1D6BB;MATHEMATICAL BOLD CAPITAL TAU;Lu;0;L; 03A4;;;;N;;;;; 1D6BC;MATHEMATICAL BOLD CAPITAL UPSILON;Lu;0;L; 03A5;;;;N;;;;; 1D6BD;MATHEMATICAL BOLD CAPITAL PHI;Lu;0;L; 03A6;;;;N;;;;; 1D6BE;MATHEMATICAL BOLD CAPITAL CHI;Lu;0;L; 03A7;;;;N;;;;; 1D6BF;MATHEMATICAL BOLD CAPITAL PSI;Lu;0;L; 03A8;;;;N;;;;; 1D6C0;MATHEMATICAL BOLD CAPITAL OMEGA;Lu;0;L; 03A9;;;;N;;;;; 1D6C1;MATHEMATICAL BOLD NABLA;Sm;0;L; 2207;;;;N;;;;; 1D6C2;MATHEMATICAL BOLD SMALL ALPHA;Ll;0;L; 03B1;;;;N;;;;; 1D6C3;MATHEMATICAL BOLD SMALL BETA;Ll;0;L; 03B2;;;;N;;;;; 1D6C4;MATHEMATICAL BOLD SMALL GAMMA;Ll;0;L; 03B3;;;;N;;;;; 1D6C5;MATHEMATICAL BOLD SMALL DELTA;Ll;0;L; 03B4;;;;N;;;;; 1D6C6;MATHEMATICAL BOLD SMALL EPSILON;Ll;0;L; 03B5;;;;N;;;;; 1D6C7;MATHEMATICAL BOLD SMALL ZETA;Ll;0;L; 03B6;;;;N;;;;; 1D6C8;MATHEMATICAL BOLD SMALL ETA;Ll;0;L; 03B7;;;;N;;;;; 1D6C9;MATHEMATICAL BOLD SMALL THETA;Ll;0;L; 03B8;;;;N;;;;; 1D6CA;MATHEMATICAL BOLD SMALL IOTA;Ll;0;L; 03B9;;;;N;;;;; 1D6CB;MATHEMATICAL BOLD SMALL KAPPA;Ll;0;L; 03BA;;;;N;;;;; 1D6CC;MATHEMATICAL BOLD SMALL LAMDA;Ll;0;L; 03BB;;;;N;;;;; 1D6CD;MATHEMATICAL BOLD SMALL MU;Ll;0;L; 03BC;;;;N;;;;; 1D6CE;MATHEMATICAL BOLD SMALL NU;Ll;0;L; 03BD;;;;N;;;;; 1D6CF;MATHEMATICAL BOLD SMALL XI;Ll;0;L; 03BE;;;;N;;;;; 1D6D0;MATHEMATICAL BOLD SMALL OMICRON;Ll;0;L; 03BF;;;;N;;;;; 1D6D1;MATHEMATICAL BOLD SMALL PI;Ll;0;L; 03C0;;;;N;;;;; 1D6D2;MATHEMATICAL BOLD SMALL RHO;Ll;0;L; 03C1;;;;N;;;;; 1D6D3;MATHEMATICAL BOLD SMALL FINAL SIGMA;Ll;0;L; 03C2;;;;N;;;;; 1D6D4;MATHEMATICAL BOLD SMALL SIGMA;Ll;0;L; 03C3;;;;N;;;;; 1D6D5;MATHEMATICAL BOLD SMALL TAU;Ll;0;L; 03C4;;;;N;;;;; 1D6D6;MATHEMATICAL BOLD SMALL UPSILON;Ll;0;L; 03C5;;;;N;;;;; 1D6D7;MATHEMATICAL BOLD SMALL PHI;Ll;0;L; 03C6;;;;N;;;;; 1D6D8;MATHEMATICAL BOLD SMALL CHI;Ll;0;L; 03C7;;;;N;;;;; 1D6D9;MATHEMATICAL BOLD SMALL PSI;Ll;0;L; 03C8;;;;N;;;;; 1D6DA;MATHEMATICAL BOLD SMALL OMEGA;Ll;0;L; 03C9;;;;N;;;;; 1D6DB;MATHEMATICAL BOLD PARTIAL DIFFERENTIAL;Sm;0;ON; 2202;;;;Y;;;;; 1D6DC;MATHEMATICAL BOLD EPSILON SYMBOL;Ll;0;L; 03F5;;;;N;;;;; 1D6DD;MATHEMATICAL BOLD THETA SYMBOL;Ll;0;L; 03D1;;;;N;;;;; 1D6DE;MATHEMATICAL BOLD KAPPA SYMBOL;Ll;0;L; 03F0;;;;N;;;;; 1D6DF;MATHEMATICAL BOLD PHI SYMBOL;Ll;0;L; 03D5;;;;N;;;;; 1D6E0;MATHEMATICAL BOLD RHO SYMBOL;Ll;0;L; 03F1;;;;N;;;;; 1D6E1;MATHEMATICAL BOLD PI SYMBOL;Ll;0;L; 03D6;;;;N;;;;; 1D6E2;MATHEMATICAL ITALIC CAPITAL ALPHA;Lu;0;L; 0391;;;;N;;;;; 1D6E3;MATHEMATICAL ITALIC CAPITAL BETA;Lu;0;L; 0392;;;;N;;;;; 1D6E4;MATHEMATICAL ITALIC CAPITAL GAMMA;Lu;0;L; 0393;;;;N;;;;; 1D6E5;MATHEMATICAL ITALIC CAPITAL DELTA;Lu;0;L; 0394;;;;N;;;;; 1D6E6;MATHEMATICAL ITALIC CAPITAL EPSILON;Lu;0;L; 0395;;;;N;;;;; 1D6E7;MATHEMATICAL ITALIC CAPITAL ZETA;Lu;0;L; 0396;;;;N;;;;; 1D6E8;MATHEMATICAL ITALIC CAPITAL ETA;Lu;0;L; 0397;;;;N;;;;; 1D6E9;MATHEMATICAL ITALIC CAPITAL THETA;Lu;0;L; 0398;;;;N;;;;; 1D6EA;MATHEMATICAL ITALIC CAPITAL IOTA;Lu;0;L; 0399;;;;N;;;;; 1D6EB;MATHEMATICAL ITALIC CAPITAL KAPPA;Lu;0;L; 039A;;;;N;;;;; 1D6EC;MATHEMATICAL ITALIC CAPITAL LAMDA;Lu;0;L; 039B;;;;N;;;;; 1D6ED;MATHEMATICAL ITALIC CAPITAL MU;Lu;0;L; 039C;;;;N;;;;; 1D6EE;MATHEMATICAL ITALIC CAPITAL NU;Lu;0;L; 039D;;;;N;;;;; 1D6EF;MATHEMATICAL ITALIC CAPITAL XI;Lu;0;L; 039E;;;;N;;;;; 1D6F0;MATHEMATICAL ITALIC CAPITAL OMICRON;Lu;0;L; 039F;;;;N;;;;; 1D6F1;MATHEMATICAL ITALIC CAPITAL PI;Lu;0;L; 03A0;;;;N;;;;; 1D6F2;MATHEMATICAL ITALIC CAPITAL RHO;Lu;0;L; 03A1;;;;N;;;;; 1D6F3;MATHEMATICAL ITALIC CAPITAL THETA SYMBOL;Lu;0;L; 03F4;;;;N;;;;; 1D6F4;MATHEMATICAL ITALIC CAPITAL SIGMA;Lu;0;L; 03A3;;;;N;;;;; 1D6F5;MATHEMATICAL ITALIC CAPITAL TAU;Lu;0;L; 03A4;;;;N;;;;; 1D6F6;MATHEMATICAL ITALIC CAPITAL UPSILON;Lu;0;L; 03A5;;;;N;;;;; 1D6F7;MATHEMATICAL ITALIC CAPITAL PHI;Lu;0;L; 03A6;;;;N;;;;; 1D6F8;MATHEMATICAL ITALIC CAPITAL CHI;Lu;0;L; 03A7;;;;N;;;;; 1D6F9;MATHEMATICAL ITALIC CAPITAL PSI;Lu;0;L; 03A8;;;;N;;;;; 1D6FA;MATHEMATICAL ITALIC CAPITAL OMEGA;Lu;0;L; 03A9;;;;N;;;;; 1D6FB;MATHEMATICAL ITALIC NABLA;Sm;0;L; 2207;;;;N;;;;; 1D6FC;MATHEMATICAL ITALIC SMALL ALPHA;Ll;0;L; 03B1;;;;N;;;;; 1D6FD;MATHEMATICAL ITALIC SMALL BETA;Ll;0;L; 03B2;;;;N;;;;; 1D6FE;MATHEMATICAL ITALIC SMALL GAMMA;Ll;0;L; 03B3;;;;N;;;;; 1D6FF;MATHEMATICAL ITALIC SMALL DELTA;Ll;0;L; 03B4;;;;N;;;;; 1D700;MATHEMATICAL ITALIC SMALL EPSILON;Ll;0;L; 03B5;;;;N;;;;; 1D701;MATHEMATICAL ITALIC SMALL ZETA;Ll;0;L; 03B6;;;;N;;;;; 1D702;MATHEMATICAL ITALIC SMALL ETA;Ll;0;L; 03B7;;;;N;;;;; 1D703;MATHEMATICAL ITALIC SMALL THETA;Ll;0;L; 03B8;;;;N;;;;; 1D704;MATHEMATICAL ITALIC SMALL IOTA;Ll;0;L; 03B9;;;;N;;;;; 1D705;MATHEMATICAL ITALIC SMALL KAPPA;Ll;0;L; 03BA;;;;N;;;;; 1D706;MATHEMATICAL ITALIC SMALL LAMDA;Ll;0;L; 03BB;;;;N;;;;; 1D707;MATHEMATICAL ITALIC SMALL MU;Ll;0;L; 03BC;;;;N;;;;; 1D708;MATHEMATICAL ITALIC SMALL NU;Ll;0;L; 03BD;;;;N;;;;; 1D709;MATHEMATICAL ITALIC SMALL XI;Ll;0;L; 03BE;;;;N;;;;; 1D70A;MATHEMATICAL ITALIC SMALL OMICRON;Ll;0;L; 03BF;;;;N;;;;; 1D70B;MATHEMATICAL ITALIC SMALL PI;Ll;0;L; 03C0;;;;N;;;;; 1D70C;MATHEMATICAL ITALIC SMALL RHO;Ll;0;L; 03C1;;;;N;;;;; 1D70D;MATHEMATICAL ITALIC SMALL FINAL SIGMA;Ll;0;L; 03C2;;;;N;;;;; 1D70E;MATHEMATICAL ITALIC SMALL SIGMA;Ll;0;L; 03C3;;;;N;;;;; 1D70F;MATHEMATICAL ITALIC SMALL TAU;Ll;0;L; 03C4;;;;N;;;;; 1D710;MATHEMATICAL ITALIC SMALL UPSILON;Ll;0;L; 03C5;;;;N;;;;; 1D711;MATHEMATICAL ITALIC SMALL PHI;Ll;0;L; 03C6;;;;N;;;;; 1D712;MATHEMATICAL ITALIC SMALL CHI;Ll;0;L; 03C7;;;;N;;;;; 1D713;MATHEMATICAL ITALIC SMALL PSI;Ll;0;L; 03C8;;;;N;;;;; 1D714;MATHEMATICAL ITALIC SMALL OMEGA;Ll;0;L; 03C9;;;;N;;;;; 1D715;MATHEMATICAL ITALIC PARTIAL DIFFERENTIAL;Sm;0;ON; 2202;;;;Y;;;;; 1D716;MATHEMATICAL ITALIC EPSILON SYMBOL;Ll;0;L; 03F5;;;;N;;;;; 1D717;MATHEMATICAL ITALIC THETA SYMBOL;Ll;0;L; 03D1;;;;N;;;;; 1D718;MATHEMATICAL ITALIC KAPPA SYMBOL;Ll;0;L; 03F0;;;;N;;;;; 1D719;MATHEMATICAL ITALIC PHI SYMBOL;Ll;0;L; 03D5;;;;N;;;;; 1D71A;MATHEMATICAL ITALIC RHO SYMBOL;Ll;0;L; 03F1;;;;N;;;;; 1D71B;MATHEMATICAL ITALIC PI SYMBOL;Ll;0;L; 03D6;;;;N;;;;; 1D71C;MATHEMATICAL BOLD ITALIC CAPITAL ALPHA;Lu;0;L; 0391;;;;N;;;;; 1D71D;MATHEMATICAL BOLD ITALIC CAPITAL BETA;Lu;0;L; 0392;;;;N;;;;; 1D71E;MATHEMATICAL BOLD ITALIC CAPITAL GAMMA;Lu;0;L; 0393;;;;N;;;;; 1D71F;MATHEMATICAL BOLD ITALIC CAPITAL DELTA;Lu;0;L; 0394;;;;N;;;;; 1D720;MATHEMATICAL BOLD ITALIC CAPITAL EPSILON;Lu;0;L; 0395;;;;N;;;;; 1D721;MATHEMATICAL BOLD ITALIC CAPITAL ZETA;Lu;0;L; 0396;;;;N;;;;; 1D722;MATHEMATICAL BOLD ITALIC CAPITAL ETA;Lu;0;L; 0397;;;;N;;;;; 1D723;MATHEMATICAL BOLD ITALIC CAPITAL THETA;Lu;0;L; 0398;;;;N;;;;; 1D724;MATHEMATICAL BOLD ITALIC CAPITAL IOTA;Lu;0;L; 0399;;;;N;;;;; 1D725;MATHEMATICAL BOLD ITALIC CAPITAL KAPPA;Lu;0;L; 039A;;;;N;;;;; 1D726;MATHEMATICAL BOLD ITALIC CAPITAL LAMDA;Lu;0;L; 039B;;;;N;;;;; 1D727;MATHEMATICAL BOLD ITALIC CAPITAL MU;Lu;0;L; 039C;;;;N;;;;; 1D728;MATHEMATICAL BOLD ITALIC CAPITAL NU;Lu;0;L; 039D;;;;N;;;;; 1D729;MATHEMATICAL BOLD ITALIC CAPITAL XI;Lu;0;L; 039E;;;;N;;;;; 1D72A;MATHEMATICAL BOLD ITALIC CAPITAL OMICRON;Lu;0;L; 039F;;;;N;;;;; 1D72B;MATHEMATICAL BOLD ITALIC CAPITAL PI;Lu;0;L; 03A0;;;;N;;;;; 1D72C;MATHEMATICAL BOLD ITALIC CAPITAL RHO;Lu;0;L; 03A1;;;;N;;;;; 1D72D;MATHEMATICAL BOLD ITALIC CAPITAL THETA SYMBOL;Lu;0;L; 03F4;;;;N;;;;; 1D72E;MATHEMATICAL BOLD ITALIC CAPITAL SIGMA;Lu;0;L; 03A3;;;;N;;;;; 1D72F;MATHEMATICAL BOLD ITALIC CAPITAL TAU;Lu;0;L; 03A4;;;;N;;;;; 1D730;MATHEMATICAL BOLD ITALIC CAPITAL UPSILON;Lu;0;L; 03A5;;;;N;;;;; 1D731;MATHEMATICAL BOLD ITALIC CAPITAL PHI;Lu;0;L; 03A6;;;;N;;;;; 1D732;MATHEMATICAL BOLD ITALIC CAPITAL CHI;Lu;0;L; 03A7;;;;N;;;;; 1D733;MATHEMATICAL BOLD ITALIC CAPITAL PSI;Lu;0;L; 03A8;;;;N;;;;; 1D734;MATHEMATICAL BOLD ITALIC CAPITAL OMEGA;Lu;0;L; 03A9;;;;N;;;;; 1D735;MATHEMATICAL BOLD ITALIC NABLA;Sm;0;L; 2207;;;;N;;;;; 1D736;MATHEMATICAL BOLD ITALIC SMALL ALPHA;Ll;0;L; 03B1;;;;N;;;;; 1D737;MATHEMATICAL BOLD ITALIC SMALL BETA;Ll;0;L; 03B2;;;;N;;;;; 1D738;MATHEMATICAL BOLD ITALIC SMALL GAMMA;Ll;0;L; 03B3;;;;N;;;;; 1D739;MATHEMATICAL BOLD ITALIC SMALL DELTA;Ll;0;L; 03B4;;;;N;;;;; 1D73A;MATHEMATICAL BOLD ITALIC SMALL EPSILON;Ll;0;L; 03B5;;;;N;;;;; 1D73B;MATHEMATICAL BOLD ITALIC SMALL ZETA;Ll;0;L; 03B6;;;;N;;;;; 1D73C;MATHEMATICAL BOLD ITALIC SMALL ETA;Ll;0;L; 03B7;;;;N;;;;; 1D73D;MATHEMATICAL BOLD ITALIC SMALL THETA;Ll;0;L; 03B8;;;;N;;;;; 1D73E;MATHEMATICAL BOLD ITALIC SMALL IOTA;Ll;0;L; 03B9;;;;N;;;;; 1D73F;MATHEMATICAL BOLD ITALIC SMALL KAPPA;Ll;0;L; 03BA;;;;N;;;;; 1D740;MATHEMATICAL BOLD ITALIC SMALL LAMDA;Ll;0;L; 03BB;;;;N;;;;; 1D741;MATHEMATICAL BOLD ITALIC SMALL MU;Ll;0;L; 03BC;;;;N;;;;; 1D742;MATHEMATICAL BOLD ITALIC SMALL NU;Ll;0;L; 03BD;;;;N;;;;; 1D743;MATHEMATICAL BOLD ITALIC SMALL XI;Ll;0;L; 03BE;;;;N;;;;; 1D744;MATHEMATICAL BOLD ITALIC SMALL OMICRON;Ll;0;L; 03BF;;;;N;;;;; 1D745;MATHEMATICAL BOLD ITALIC SMALL PI;Ll;0;L; 03C0;;;;N;;;;; 1D746;MATHEMATICAL BOLD ITALIC SMALL RHO;Ll;0;L; 03C1;;;;N;;;;; 1D747;MATHEMATICAL BOLD ITALIC SMALL FINAL SIGMA;Ll;0;L; 03C2;;;;N;;;;; 1D748;MATHEMATICAL BOLD ITALIC SMALL SIGMA;Ll;0;L; 03C3;;;;N;;;;; 1D749;MATHEMATICAL BOLD ITALIC SMALL TAU;Ll;0;L; 03C4;;;;N;;;;; 1D74A;MATHEMATICAL BOLD ITALIC SMALL UPSILON;Ll;0;L; 03C5;;;;N;;;;; 1D74B;MATHEMATICAL BOLD ITALIC SMALL PHI;Ll;0;L; 03C6;;;;N;;;;; 1D74C;MATHEMATICAL BOLD ITALIC SMALL CHI;Ll;0;L; 03C7;;;;N;;;;; 1D74D;MATHEMATICAL BOLD ITALIC SMALL PSI;Ll;0;L; 03C8;;;;N;;;;; 1D74E;MATHEMATICAL BOLD ITALIC SMALL OMEGA;Ll;0;L; 03C9;;;;N;;;;; 1D74F;MATHEMATICAL BOLD ITALIC PARTIAL DIFFERENTIAL;Sm;0;ON; 2202;;;;Y;;;;; 1D750;MATHEMATICAL BOLD ITALIC EPSILON SYMBOL;Ll;0;L; 03F5;;;;N;;;;; 1D751;MATHEMATICAL BOLD ITALIC THETA SYMBOL;Ll;0;L; 03D1;;;;N;;;;; 1D752;MATHEMATICAL BOLD ITALIC KAPPA SYMBOL;Ll;0;L; 03F0;;;;N;;;;; 1D753;MATHEMATICAL BOLD ITALIC PHI SYMBOL;Ll;0;L; 03D5;;;;N;;;;; 1D754;MATHEMATICAL BOLD ITALIC RHO SYMBOL;Ll;0;L; 03F1;;;;N;;;;; 1D755;MATHEMATICAL BOLD ITALIC PI SYMBOL;Ll;0;L; 03D6;;;;N;;;;; 1D756;MATHEMATICAL SANS-SERIF BOLD CAPITAL ALPHA;Lu;0;L; 0391;;;;N;;;;; 1D757;MATHEMATICAL SANS-SERIF BOLD CAPITAL BETA;Lu;0;L; 0392;;;;N;;;;; 1D758;MATHEMATICAL SANS-SERIF BOLD CAPITAL GAMMA;Lu;0;L; 0393;;;;N;;;;; 1D759;MATHEMATICAL SANS-SERIF BOLD CAPITAL DELTA;Lu;0;L; 0394;;;;N;;;;; 1D75A;MATHEMATICAL SANS-SERIF BOLD CAPITAL EPSILON;Lu;0;L; 0395;;;;N;;;;; 1D75B;MATHEMATICAL SANS-SERIF BOLD CAPITAL ZETA;Lu;0;L; 0396;;;;N;;;;; 1D75C;MATHEMATICAL SANS-SERIF BOLD CAPITAL ETA;Lu;0;L; 0397;;;;N;;;;; 1D75D;MATHEMATICAL SANS-SERIF BOLD CAPITAL THETA;Lu;0;L; 0398;;;;N;;;;; 1D75E;MATHEMATICAL SANS-SERIF BOLD CAPITAL IOTA;Lu;0;L; 0399;;;;N;;;;; 1D75F;MATHEMATICAL SANS-SERIF BOLD CAPITAL KAPPA;Lu;0;L; 039A;;;;N;;;;; 1D760;MATHEMATICAL SANS-SERIF BOLD CAPITAL LAMDA;Lu;0;L; 039B;;;;N;;;;; 1D761;MATHEMATICAL SANS-SERIF BOLD CAPITAL MU;Lu;0;L; 039C;;;;N;;;;; 1D762;MATHEMATICAL SANS-SERIF BOLD CAPITAL NU;Lu;0;L; 039D;;;;N;;;;; 1D763;MATHEMATICAL SANS-SERIF BOLD CAPITAL XI;Lu;0;L; 039E;;;;N;;;;; 1D764;MATHEMATICAL SANS-SERIF BOLD CAPITAL OMICRON;Lu;0;L; 039F;;;;N;;;;; 1D765;MATHEMATICAL SANS-SERIF BOLD CAPITAL PI;Lu;0;L; 03A0;;;;N;;;;; 1D766;MATHEMATICAL SANS-SERIF BOLD CAPITAL RHO;Lu;0;L; 03A1;;;;N;;;;; 1D767;MATHEMATICAL SANS-SERIF BOLD CAPITAL THETA SYMBOL;Lu;0;L; 03F4;;;;N;;;;; 1D768;MATHEMATICAL SANS-SERIF BOLD CAPITAL SIGMA;Lu;0;L; 03A3;;;;N;;;;; 1D769;MATHEMATICAL SANS-SERIF BOLD CAPITAL TAU;Lu;0;L; 03A4;;;;N;;;;; 1D76A;MATHEMATICAL SANS-SERIF BOLD CAPITAL UPSILON;Lu;0;L; 03A5;;;;N;;;;; 1D76B;MATHEMATICAL SANS-SERIF BOLD CAPITAL PHI;Lu;0;L; 03A6;;;;N;;;;; 1D76C;MATHEMATICAL SANS-SERIF BOLD CAPITAL CHI;Lu;0;L; 03A7;;;;N;;;;; 1D76D;MATHEMATICAL SANS-SERIF BOLD CAPITAL PSI;Lu;0;L; 03A8;;;;N;;;;; 1D76E;MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA;Lu;0;L; 03A9;;;;N;;;;; 1D76F;MATHEMATICAL SANS-SERIF BOLD NABLA;Sm;0;L; 2207;;;;N;;;;; 1D770;MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA;Ll;0;L; 03B1;;;;N;;;;; 1D771;MATHEMATICAL SANS-SERIF BOLD SMALL BETA;Ll;0;L; 03B2;;;;N;;;;; 1D772;MATHEMATICAL SANS-SERIF BOLD SMALL GAMMA;Ll;0;L; 03B3;;;;N;;;;; 1D773;MATHEMATICAL SANS-SERIF BOLD SMALL DELTA;Ll;0;L; 03B4;;;;N;;;;; 1D774;MATHEMATICAL SANS-SERIF BOLD SMALL EPSILON;Ll;0;L; 03B5;;;;N;;;;; 1D775;MATHEMATICAL SANS-SERIF BOLD SMALL ZETA;Ll;0;L; 03B6;;;;N;;;;; 1D776;MATHEMATICAL SANS-SERIF BOLD SMALL ETA;Ll;0;L; 03B7;;;;N;;;;; 1D777;MATHEMATICAL SANS-SERIF BOLD SMALL THETA;Ll;0;L; 03B8;;;;N;;;;; 1D778;MATHEMATICAL SANS-SERIF BOLD SMALL IOTA;Ll;0;L; 03B9;;;;N;;;;; 1D779;MATHEMATICAL SANS-SERIF BOLD SMALL KAPPA;Ll;0;L; 03BA;;;;N;;;;; 1D77A;MATHEMATICAL SANS-SERIF BOLD SMALL LAMDA;Ll;0;L; 03BB;;;;N;;;;; 1D77B;MATHEMATICAL SANS-SERIF BOLD SMALL MU;Ll;0;L; 03BC;;;;N;;;;; 1D77C;MATHEMATICAL SANS-SERIF BOLD SMALL NU;Ll;0;L; 03BD;;;;N;;;;; 1D77D;MATHEMATICAL SANS-SERIF BOLD SMALL XI;Ll;0;L; 03BE;;;;N;;;;; 1D77E;MATHEMATICAL SANS-SERIF BOLD SMALL OMICRON;Ll;0;L; 03BF;;;;N;;;;; 1D77F;MATHEMATICAL SANS-SERIF BOLD SMALL PI;Ll;0;L; 03C0;;;;N;;;;; 1D780;MATHEMATICAL SANS-SERIF BOLD SMALL RHO;Ll;0;L; 03C1;;;;N;;;;; 1D781;MATHEMATICAL SANS-SERIF BOLD SMALL FINAL SIGMA;Ll;0;L; 03C2;;;;N;;;;; 1D782;MATHEMATICAL SANS-SERIF BOLD SMALL SIGMA;Ll;0;L; 03C3;;;;N;;;;; 1D783;MATHEMATICAL SANS-SERIF BOLD SMALL TAU;Ll;0;L; 03C4;;;;N;;;;; 1D784;MATHEMATICAL SANS-SERIF BOLD SMALL UPSILON;Ll;0;L; 03C5;;;;N;;;;; 1D785;MATHEMATICAL SANS-SERIF BOLD SMALL PHI;Ll;0;L; 03C6;;;;N;;;;; 1D786;MATHEMATICAL SANS-SERIF BOLD SMALL CHI;Ll;0;L; 03C7;;;;N;;;;; 1D787;MATHEMATICAL SANS-SERIF BOLD SMALL PSI;Ll;0;L; 03C8;;;;N;;;;; 1D788;MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA;Ll;0;L; 03C9;;;;N;;;;; 1D789;MATHEMATICAL SANS-SERIF BOLD PARTIAL DIFFERENTIAL;Sm;0;ON; 2202;;;;Y;;;;; 1D78A;MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL;Ll;0;L; 03F5;;;;N;;;;; 1D78B;MATHEMATICAL SANS-SERIF BOLD THETA SYMBOL;Ll;0;L; 03D1;;;;N;;;;; 1D78C;MATHEMATICAL SANS-SERIF BOLD KAPPA SYMBOL;Ll;0;L; 03F0;;;;N;;;;; 1D78D;MATHEMATICAL SANS-SERIF BOLD PHI SYMBOL;Ll;0;L; 03D5;;;;N;;;;; 1D78E;MATHEMATICAL SANS-SERIF BOLD RHO SYMBOL;Ll;0;L; 03F1;;;;N;;;;; 1D78F;MATHEMATICAL SANS-SERIF BOLD PI SYMBOL;Ll;0;L; 03D6;;;;N;;;;; 1D790;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL ALPHA;Lu;0;L; 0391;;;;N;;;;; 1D791;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL BETA;Lu;0;L; 0392;;;;N;;;;; 1D792;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL GAMMA;Lu;0;L; 0393;;;;N;;;;; 1D793;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL DELTA;Lu;0;L; 0394;;;;N;;;;; 1D794;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL EPSILON;Lu;0;L; 0395;;;;N;;;;; 1D795;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL ZETA;Lu;0;L; 0396;;;;N;;;;; 1D796;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL ETA;Lu;0;L; 0397;;;;N;;;;; 1D797;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL THETA;Lu;0;L; 0398;;;;N;;;;; 1D798;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL IOTA;Lu;0;L; 0399;;;;N;;;;; 1D799;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL KAPPA;Lu;0;L; 039A;;;;N;;;;; 1D79A;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL LAMDA;Lu;0;L; 039B;;;;N;;;;; 1D79B;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL MU;Lu;0;L; 039C;;;;N;;;;; 1D79C;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL NU;Lu;0;L; 039D;;;;N;;;;; 1D79D;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL XI;Lu;0;L; 039E;;;;N;;;;; 1D79E;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMICRON;Lu;0;L; 039F;;;;N;;;;; 1D79F;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL PI;Lu;0;L; 03A0;;;;N;;;;; 1D7A0;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL RHO;Lu;0;L; 03A1;;;;N;;;;; 1D7A1;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL THETA SYMBOL;Lu;0;L; 03F4;;;;N;;;;; 1D7A2;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL SIGMA;Lu;0;L; 03A3;;;;N;;;;; 1D7A3;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL TAU;Lu;0;L; 03A4;;;;N;;;;; 1D7A4;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL UPSILON;Lu;0;L; 03A5;;;;N;;;;; 1D7A5;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL PHI;Lu;0;L; 03A6;;;;N;;;;; 1D7A6;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL CHI;Lu;0;L; 03A7;;;;N;;;;; 1D7A7;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL PSI;Lu;0;L; 03A8;;;;N;;;;; 1D7A8;MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA;Lu;0;L; 03A9;;;;N;;;;; 1D7A9;MATHEMATICAL SANS-SERIF BOLD ITALIC NABLA;Sm;0;L; 2207;;;;N;;;;; 1D7AA;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA;Ll;0;L; 03B1;;;;N;;;;; 1D7AB;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL BETA;Ll;0;L; 03B2;;;;N;;;;; 1D7AC;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL GAMMA;Ll;0;L; 03B3;;;;N;;;;; 1D7AD;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL DELTA;Ll;0;L; 03B4;;;;N;;;;; 1D7AE;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL EPSILON;Ll;0;L; 03B5;;;;N;;;;; 1D7AF;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ZETA;Ll;0;L; 03B6;;;;N;;;;; 1D7B0;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ETA;Ll;0;L; 03B7;;;;N;;;;; 1D7B1;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL THETA;Ll;0;L; 03B8;;;;N;;;;; 1D7B2;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL IOTA;Ll;0;L; 03B9;;;;N;;;;; 1D7B3;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL KAPPA;Ll;0;L; 03BA;;;;N;;;;; 1D7B4;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL LAMDA;Ll;0;L; 03BB;;;;N;;;;; 1D7B5;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL MU;Ll;0;L; 03BC;;;;N;;;;; 1D7B6;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL NU;Ll;0;L; 03BD;;;;N;;;;; 1D7B7;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL XI;Ll;0;L; 03BE;;;;N;;;;; 1D7B8;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMICRON;Ll;0;L; 03BF;;;;N;;;;; 1D7B9;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL PI;Ll;0;L; 03C0;;;;N;;;;; 1D7BA;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL RHO;Ll;0;L; 03C1;;;;N;;;;; 1D7BB;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL FINAL SIGMA;Ll;0;L; 03C2;;;;N;;;;; 1D7BC;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL SIGMA;Ll;0;L; 03C3;;;;N;;;;; 1D7BD;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL TAU;Ll;0;L; 03C4;;;;N;;;;; 1D7BE;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL UPSILON;Ll;0;L; 03C5;;;;N;;;;; 1D7BF;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL PHI;Ll;0;L; 03C6;;;;N;;;;; 1D7C0;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL CHI;Ll;0;L; 03C7;;;;N;;;;; 1D7C1;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL PSI;Ll;0;L; 03C8;;;;N;;;;; 1D7C2;MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA;Ll;0;L; 03C9;;;;N;;;;; 1D7C3;MATHEMATICAL SANS-SERIF BOLD ITALIC PARTIAL DIFFERENTIAL;Sm;0;ON; 2202;;;;Y;;;;; 1D7C4;MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL;Ll;0;L; 03F5;;;;N;;;;; 1D7C5;MATHEMATICAL SANS-SERIF BOLD ITALIC THETA SYMBOL;Ll;0;L; 03D1;;;;N;;;;; 1D7C6;MATHEMATICAL SANS-SERIF BOLD ITALIC KAPPA SYMBOL;Ll;0;L; 03F0;;;;N;;;;; 1D7C7;MATHEMATICAL SANS-SERIF BOLD ITALIC PHI SYMBOL;Ll;0;L; 03D5;;;;N;;;;; 1D7C8;MATHEMATICAL SANS-SERIF BOLD ITALIC RHO SYMBOL;Ll;0;L; 03F1;;;;N;;;;; 1D7C9;MATHEMATICAL SANS-SERIF BOLD ITALIC PI SYMBOL;Ll;0;L; 03D6;;;;N;;;;; 1D7CA;MATHEMATICAL BOLD CAPITAL DIGAMMA;Lu;0;L; 03DC;;;;N;;;;; 1D7CB;MATHEMATICAL BOLD SMALL DIGAMMA;Ll;0;L; 03DD;;;;N;;;;; 1D7CE;MATHEMATICAL BOLD DIGIT ZERO;Nd;0;EN; 0030;0;0;0;N;;;;; 1D7CF;MATHEMATICAL BOLD DIGIT ONE;Nd;0;EN; 0031;1;1;1;N;;;;; 1D7D0;MATHEMATICAL BOLD DIGIT TWO;Nd;0;EN; 0032;2;2;2;N;;;;; 1D7D1;MATHEMATICAL BOLD DIGIT THREE;Nd;0;EN; 0033;3;3;3;N;;;;; 1D7D2;MATHEMATICAL BOLD DIGIT FOUR;Nd;0;EN; 0034;4;4;4;N;;;;; 1D7D3;MATHEMATICAL BOLD DIGIT FIVE;Nd;0;EN; 0035;5;5;5;N;;;;; 1D7D4;MATHEMATICAL BOLD DIGIT SIX;Nd;0;EN; 0036;6;6;6;N;;;;; 1D7D5;MATHEMATICAL BOLD DIGIT SEVEN;Nd;0;EN; 0037;7;7;7;N;;;;; 1D7D6;MATHEMATICAL BOLD DIGIT EIGHT;Nd;0;EN; 0038;8;8;8;N;;;;; 1D7D7;MATHEMATICAL BOLD DIGIT NINE;Nd;0;EN; 0039;9;9;9;N;;;;; 1D7D8;MATHEMATICAL DOUBLE-STRUCK DIGIT ZERO;Nd;0;EN; 0030;0;0;0;N;;;;; 1D7D9;MATHEMATICAL DOUBLE-STRUCK DIGIT ONE;Nd;0;EN; 0031;1;1;1;N;;;;; 1D7DA;MATHEMATICAL DOUBLE-STRUCK DIGIT TWO;Nd;0;EN; 0032;2;2;2;N;;;;; 1D7DB;MATHEMATICAL DOUBLE-STRUCK DIGIT THREE;Nd;0;EN; 0033;3;3;3;N;;;;; 1D7DC;MATHEMATICAL DOUBLE-STRUCK DIGIT FOUR;Nd;0;EN; 0034;4;4;4;N;;;;; 1D7DD;MATHEMATICAL DOUBLE-STRUCK DIGIT FIVE;Nd;0;EN; 0035;5;5;5;N;;;;; 1D7DE;MATHEMATICAL DOUBLE-STRUCK DIGIT SIX;Nd;0;EN; 0036;6;6;6;N;;;;; 1D7DF;MATHEMATICAL DOUBLE-STRUCK DIGIT SEVEN;Nd;0;EN; 0037;7;7;7;N;;;;; 1D7E0;MATHEMATICAL DOUBLE-STRUCK DIGIT EIGHT;Nd;0;EN; 0038;8;8;8;N;;;;; 1D7E1;MATHEMATICAL DOUBLE-STRUCK DIGIT NINE;Nd;0;EN; 0039;9;9;9;N;;;;; 1D7E2;MATHEMATICAL SANS-SERIF DIGIT ZERO;Nd;0;EN; 0030;0;0;0;N;;;;; 1D7E3;MATHEMATICAL SANS-SERIF DIGIT ONE;Nd;0;EN; 0031;1;1;1;N;;;;; 1D7E4;MATHEMATICAL SANS-SERIF DIGIT TWO;Nd;0;EN; 0032;2;2;2;N;;;;; 1D7E5;MATHEMATICAL SANS-SERIF DIGIT THREE;Nd;0;EN; 0033;3;3;3;N;;;;; 1D7E6;MATHEMATICAL SANS-SERIF DIGIT FOUR;Nd;0;EN; 0034;4;4;4;N;;;;; 1D7E7;MATHEMATICAL SANS-SERIF DIGIT FIVE;Nd;0;EN; 0035;5;5;5;N;;;;; 1D7E8;MATHEMATICAL SANS-SERIF DIGIT SIX;Nd;0;EN; 0036;6;6;6;N;;;;; 1D7E9;MATHEMATICAL SANS-SERIF DIGIT SEVEN;Nd;0;EN; 0037;7;7;7;N;;;;; 1D7EA;MATHEMATICAL SANS-SERIF DIGIT EIGHT;Nd;0;EN; 0038;8;8;8;N;;;;; 1D7EB;MATHEMATICAL SANS-SERIF DIGIT NINE;Nd;0;EN; 0039;9;9;9;N;;;;; 1D7EC;MATHEMATICAL SANS-SERIF BOLD DIGIT ZERO;Nd;0;EN; 0030;0;0;0;N;;;;; 1D7ED;MATHEMATICAL SANS-SERIF BOLD DIGIT ONE;Nd;0;EN; 0031;1;1;1;N;;;;; 1D7EE;MATHEMATICAL SANS-SERIF BOLD DIGIT TWO;Nd;0;EN; 0032;2;2;2;N;;;;; 1D7EF;MATHEMATICAL SANS-SERIF BOLD DIGIT THREE;Nd;0;EN; 0033;3;3;3;N;;;;; 1D7F0;MATHEMATICAL SANS-SERIF BOLD DIGIT FOUR;Nd;0;EN; 0034;4;4;4;N;;;;; 1D7F1;MATHEMATICAL SANS-SERIF BOLD DIGIT FIVE;Nd;0;EN; 0035;5;5;5;N;;;;; 1D7F2;MATHEMATICAL SANS-SERIF BOLD DIGIT SIX;Nd;0;EN; 0036;6;6;6;N;;;;; 1D7F3;MATHEMATICAL SANS-SERIF BOLD DIGIT SEVEN;Nd;0;EN; 0037;7;7;7;N;;;;; 1D7F4;MATHEMATICAL SANS-SERIF BOLD DIGIT EIGHT;Nd;0;EN; 0038;8;8;8;N;;;;; 1D7F5;MATHEMATICAL SANS-SERIF BOLD DIGIT NINE;Nd;0;EN; 0039;9;9;9;N;;;;; 1D7F6;MATHEMATICAL MONOSPACE DIGIT ZERO;Nd;0;EN; 0030;0;0;0;N;;;;; 1D7F7;MATHEMATICAL MONOSPACE DIGIT ONE;Nd;0;EN; 0031;1;1;1;N;;;;; 1D7F8;MATHEMATICAL MONOSPACE DIGIT TWO;Nd;0;EN; 0032;2;2;2;N;;;;; 1D7F9;MATHEMATICAL MONOSPACE DIGIT THREE;Nd;0;EN; 0033;3;3;3;N;;;;; 1D7FA;MATHEMATICAL MONOSPACE DIGIT FOUR;Nd;0;EN; 0034;4;4;4;N;;;;; 1D7FB;MATHEMATICAL MONOSPACE DIGIT FIVE;Nd;0;EN; 0035;5;5;5;N;;;;; 1D7FC;MATHEMATICAL MONOSPACE DIGIT SIX;Nd;0;EN; 0036;6;6;6;N;;;;; 1D7FD;MATHEMATICAL MONOSPACE DIGIT SEVEN;Nd;0;EN; 0037;7;7;7;N;;;;; 1D7FE;MATHEMATICAL MONOSPACE DIGIT EIGHT;Nd;0;EN; 0038;8;8;8;N;;;;; 1D7FF;MATHEMATICAL MONOSPACE DIGIT NINE;Nd;0;EN; 0039;9;9;9;N;;;;; 1D800;SIGNWRITING HAND-FIST INDEX;So;0;L;;;;;N;;;;; 1D801;SIGNWRITING HAND-CIRCLE INDEX;So;0;L;;;;;N;;;;; 1D802;SIGNWRITING HAND-CUP INDEX;So;0;L;;;;;N;;;;; 1D803;SIGNWRITING HAND-OVAL INDEX;So;0;L;;;;;N;;;;; 1D804;SIGNWRITING HAND-HINGE INDEX;So;0;L;;;;;N;;;;; 1D805;SIGNWRITING HAND-ANGLE INDEX;So;0;L;;;;;N;;;;; 1D806;SIGNWRITING HAND-FIST INDEX BENT;So;0;L;;;;;N;;;;; 1D807;SIGNWRITING HAND-CIRCLE INDEX BENT;So;0;L;;;;;N;;;;; 1D808;SIGNWRITING HAND-FIST THUMB UNDER INDEX BENT;So;0;L;;;;;N;;;;; 1D809;SIGNWRITING HAND-FIST INDEX RAISED KNUCKLE;So;0;L;;;;;N;;;;; 1D80A;SIGNWRITING HAND-FIST INDEX CUPPED;So;0;L;;;;;N;;;;; 1D80B;SIGNWRITING HAND-FIST INDEX HINGED;So;0;L;;;;;N;;;;; 1D80C;SIGNWRITING HAND-FIST INDEX HINGED LOW;So;0;L;;;;;N;;;;; 1D80D;SIGNWRITING HAND-CIRCLE INDEX HINGE;So;0;L;;;;;N;;;;; 1D80E;SIGNWRITING HAND-FIST INDEX MIDDLE;So;0;L;;;;;N;;;;; 1D80F;SIGNWRITING HAND-CIRCLE INDEX MIDDLE;So;0;L;;;;;N;;;;; 1D810;SIGNWRITING HAND-FIST INDEX MIDDLE BENT;So;0;L;;;;;N;;;;; 1D811;SIGNWRITING HAND-FIST INDEX MIDDLE RAISED KNUCKLES;So;0;L;;;;;N;;;;; 1D812;SIGNWRITING HAND-FIST INDEX MIDDLE HINGED;So;0;L;;;;;N;;;;; 1D813;SIGNWRITING HAND-FIST INDEX UP MIDDLE HINGED;So;0;L;;;;;N;;;;; 1D814;SIGNWRITING HAND-FIST INDEX HINGED MIDDLE UP;So;0;L;;;;;N;;;;; 1D815;SIGNWRITING HAND-FIST INDEX MIDDLE CONJOINED;So;0;L;;;;;N;;;;; 1D816;SIGNWRITING HAND-FIST INDEX MIDDLE CONJOINED INDEX BENT;So;0;L;;;;;N;;;;; 1D817;SIGNWRITING HAND-FIST INDEX MIDDLE CONJOINED MIDDLE BENT;So;0;L;;;;;N;;;;; 1D818;SIGNWRITING HAND-FIST INDEX MIDDLE CONJOINED CUPPED;So;0;L;;;;;N;;;;; 1D819;SIGNWRITING HAND-FIST INDEX MIDDLE CONJOINED HINGED;So;0;L;;;;;N;;;;; 1D81A;SIGNWRITING HAND-FIST INDEX MIDDLE CROSSED;So;0;L;;;;;N;;;;; 1D81B;SIGNWRITING HAND-CIRCLE INDEX MIDDLE CROSSED;So;0;L;;;;;N;;;;; 1D81C;SIGNWRITING HAND-FIST MIDDLE BENT OVER INDEX;So;0;L;;;;;N;;;;; 1D81D;SIGNWRITING HAND-FIST INDEX BENT OVER MIDDLE;So;0;L;;;;;N;;;;; 1D81E;SIGNWRITING HAND-FIST INDEX MIDDLE THUMB;So;0;L;;;;;N;;;;; 1D81F;SIGNWRITING HAND-CIRCLE INDEX MIDDLE THUMB;So;0;L;;;;;N;;;;; 1D820;SIGNWRITING HAND-FIST INDEX MIDDLE STRAIGHT THUMB BENT;So;0;L;;;;;N;;;;; 1D821;SIGNWRITING HAND-FIST INDEX MIDDLE BENT THUMB STRAIGHT;So;0;L;;;;;N;;;;; 1D822;SIGNWRITING HAND-FIST INDEX MIDDLE THUMB BENT;So;0;L;;;;;N;;;;; 1D823;SIGNWRITING HAND-FIST INDEX MIDDLE HINGED SPREAD THUMB SIDE;So;0;L;;;;;N;;;;; 1D824;SIGNWRITING HAND-FIST INDEX UP MIDDLE HINGED THUMB SIDE;So;0;L;;;;;N;;;;; 1D825;SIGNWRITING HAND-FIST INDEX UP MIDDLE HINGED THUMB CONJOINED;So;0;L;;;;;N;;;;; 1D826;SIGNWRITING HAND-FIST INDEX HINGED MIDDLE UP THUMB SIDE;So;0;L;;;;;N;;;;; 1D827;SIGNWRITING HAND-FIST INDEX MIDDLE UP SPREAD THUMB FORWARD;So;0;L;;;;;N;;;;; 1D828;SIGNWRITING HAND-FIST INDEX MIDDLE THUMB CUPPED;So;0;L;;;;;N;;;;; 1D829;SIGNWRITING HAND-FIST INDEX MIDDLE THUMB CIRCLED;So;0;L;;;;;N;;;;; 1D82A;SIGNWRITING HAND-FIST INDEX MIDDLE THUMB HOOKED;So;0;L;;;;;N;;;;; 1D82B;SIGNWRITING HAND-FIST INDEX MIDDLE THUMB HINGED;So;0;L;;;;;N;;;;; 1D82C;SIGNWRITING HAND-FIST THUMB BETWEEN INDEX MIDDLE STRAIGHT;So;0;L;;;;;N;;;;; 1D82D;SIGNWRITING HAND-FIST INDEX MIDDLE CONJOINED THUMB SIDE;So;0;L;;;;;N;;;;; 1D82E;SIGNWRITING HAND-FIST INDEX MIDDLE CONJOINED THUMB SIDE CONJOINED;So;0;L;;;;;N;;;;; 1D82F;SIGNWRITING HAND-FIST INDEX MIDDLE CONJOINED THUMB SIDE BENT;So;0;L;;;;;N;;;;; 1D830;SIGNWRITING HAND-FIST MIDDLE THUMB HOOKED INDEX UP;So;0;L;;;;;N;;;;; 1D831;SIGNWRITING HAND-FIST INDEX THUMB HOOKED MIDDLE UP;So;0;L;;;;;N;;;;; 1D832;SIGNWRITING HAND-FIST INDEX MIDDLE CONJOINED HINGED THUMB SIDE;So;0;L;;;;;N;;;;; 1D833;SIGNWRITING HAND-FIST INDEX MIDDLE CROSSED THUMB SIDE;So;0;L;;;;;N;;;;; 1D834;SIGNWRITING HAND-FIST INDEX MIDDLE CONJOINED THUMB FORWARD;So;0;L;;;;;N;;;;; 1D835;SIGNWRITING HAND-FIST INDEX MIDDLE CONJOINED CUPPED THUMB FORWARD;So;0;L;;;;;N;;;;; 1D836;SIGNWRITING HAND-FIST MIDDLE THUMB CUPPED INDEX UP;So;0;L;;;;;N;;;;; 1D837;SIGNWRITING HAND-FIST INDEX THUMB CUPPED MIDDLE UP;So;0;L;;;;;N;;;;; 1D838;SIGNWRITING HAND-FIST MIDDLE THUMB CIRCLED INDEX UP;So;0;L;;;;;N;;;;; 1D839;SIGNWRITING HAND-FIST MIDDLE THUMB CIRCLED INDEX HINGED;So;0;L;;;;;N;;;;; 1D83A;SIGNWRITING HAND-FIST INDEX THUMB ANGLED OUT MIDDLE UP;So;0;L;;;;;N;;;;; 1D83B;SIGNWRITING HAND-FIST INDEX THUMB ANGLED IN MIDDLE UP;So;0;L;;;;;N;;;;; 1D83C;SIGNWRITING HAND-FIST INDEX THUMB CIRCLED MIDDLE UP;So;0;L;;;;;N;;;;; 1D83D;SIGNWRITING HAND-FIST INDEX MIDDLE THUMB CONJOINED HINGED;So;0;L;;;;;N;;;;; 1D83E;SIGNWRITING HAND-FIST INDEX MIDDLE THUMB ANGLED OUT;So;0;L;;;;;N;;;;; 1D83F;SIGNWRITING HAND-FIST INDEX MIDDLE THUMB ANGLED;So;0;L;;;;;N;;;;; 1D840;SIGNWRITING HAND-FIST MIDDLE THUMB ANGLED OUT INDEX UP;So;0;L;;;;;N;;;;; 1D841;SIGNWRITING HAND-FIST MIDDLE THUMB ANGLED OUT INDEX CROSSED;So;0;L;;;;;N;;;;; 1D842;SIGNWRITING HAND-FIST MIDDLE THUMB ANGLED INDEX UP;So;0;L;;;;;N;;;;; 1D843;SIGNWRITING HAND-FIST INDEX THUMB HOOKED MIDDLE HINGED;So;0;L;;;;;N;;;;; 1D844;SIGNWRITING HAND-FLAT FOUR FINGERS;So;0;L;;;;;N;;;;; 1D845;SIGNWRITING HAND-FLAT FOUR FINGERS BENT;So;0;L;;;;;N;;;;; 1D846;SIGNWRITING HAND-FLAT FOUR FINGERS HINGED;So;0;L;;;;;N;;;;; 1D847;SIGNWRITING HAND-FLAT FOUR FINGERS CONJOINED;So;0;L;;;;;N;;;;; 1D848;SIGNWRITING HAND-FLAT FOUR FINGERS CONJOINED SPLIT;So;0;L;;;;;N;;;;; 1D849;SIGNWRITING HAND-CLAW FOUR FINGERS CONJOINED;So;0;L;;;;;N;;;;; 1D84A;SIGNWRITING HAND-FIST FOUR FINGERS CONJOINED BENT;So;0;L;;;;;N;;;;; 1D84B;SIGNWRITING HAND-HINGE FOUR FINGERS CONJOINED;So;0;L;;;;;N;;;;; 1D84C;SIGNWRITING HAND-FLAT FIVE FINGERS SPREAD;So;0;L;;;;;N;;;;; 1D84D;SIGNWRITING HAND-FLAT HEEL FIVE FINGERS SPREAD;So;0;L;;;;;N;;;;; 1D84E;SIGNWRITING HAND-FLAT FIVE FINGERS SPREAD FOUR BENT;So;0;L;;;;;N;;;;; 1D84F;SIGNWRITING HAND-FLAT HEEL FIVE FINGERS SPREAD FOUR BENT;So;0;L;;;;;N;;;;; 1D850;SIGNWRITING HAND-FLAT FIVE FINGERS SPREAD BENT;So;0;L;;;;;N;;;;; 1D851;SIGNWRITING HAND-FLAT HEEL FIVE FINGERS SPREAD BENT;So;0;L;;;;;N;;;;; 1D852;SIGNWRITING HAND-FLAT FIVE FINGERS SPREAD THUMB FORWARD;So;0;L;;;;;N;;;;; 1D853;SIGNWRITING HAND-CUP FIVE FINGERS SPREAD;So;0;L;;;;;N;;;;; 1D854;SIGNWRITING HAND-CUP FIVE FINGERS SPREAD OPEN;So;0;L;;;;;N;;;;; 1D855;SIGNWRITING HAND-HINGE FIVE FINGERS SPREAD OPEN;So;0;L;;;;;N;;;;; 1D856;SIGNWRITING HAND-OVAL FIVE FINGERS SPREAD;So;0;L;;;;;N;;;;; 1D857;SIGNWRITING HAND-FLAT FIVE FINGERS SPREAD HINGED;So;0;L;;;;;N;;;;; 1D858;SIGNWRITING HAND-FLAT FIVE FINGERS SPREAD HINGED THUMB SIDE;So;0;L;;;;;N;;;;; 1D859;SIGNWRITING HAND-FLAT FIVE FINGERS SPREAD HINGED NO THUMB;So;0;L;;;;;N;;;;; 1D85A;SIGNWRITING HAND-FLAT;So;0;L;;;;;N;;;;; 1D85B;SIGNWRITING HAND-FLAT BETWEEN PALM FACINGS;So;0;L;;;;;N;;;;; 1D85C;SIGNWRITING HAND-FLAT HEEL;So;0;L;;;;;N;;;;; 1D85D;SIGNWRITING HAND-FLAT THUMB SIDE;So;0;L;;;;;N;;;;; 1D85E;SIGNWRITING HAND-FLAT HEEL THUMB SIDE;So;0;L;;;;;N;;;;; 1D85F;SIGNWRITING HAND-FLAT THUMB BENT;So;0;L;;;;;N;;;;; 1D860;SIGNWRITING HAND-FLAT THUMB FORWARD;So;0;L;;;;;N;;;;; 1D861;SIGNWRITING HAND-FLAT SPLIT INDEX THUMB SIDE;So;0;L;;;;;N;;;;; 1D862;SIGNWRITING HAND-FLAT SPLIT CENTRE;So;0;L;;;;;N;;;;; 1D863;SIGNWRITING HAND-FLAT SPLIT CENTRE THUMB SIDE;So;0;L;;;;;N;;;;; 1D864;SIGNWRITING HAND-FLAT SPLIT CENTRE THUMB SIDE BENT;So;0;L;;;;;N;;;;; 1D865;SIGNWRITING HAND-FLAT SPLIT LITTLE;So;0;L;;;;;N;;;;; 1D866;SIGNWRITING HAND-CLAW;So;0;L;;;;;N;;;;; 1D867;SIGNWRITING HAND-CLAW THUMB SIDE;So;0;L;;;;;N;;;;; 1D868;SIGNWRITING HAND-CLAW NO THUMB;So;0;L;;;;;N;;;;; 1D869;SIGNWRITING HAND-CLAW THUMB FORWARD;So;0;L;;;;;N;;;;; 1D86A;SIGNWRITING HAND-HOOK CURLICUE;So;0;L;;;;;N;;;;; 1D86B;SIGNWRITING HAND-HOOK;So;0;L;;;;;N;;;;; 1D86C;SIGNWRITING HAND-CUP OPEN;So;0;L;;;;;N;;;;; 1D86D;SIGNWRITING HAND-CUP;So;0;L;;;;;N;;;;; 1D86E;SIGNWRITING HAND-CUP OPEN THUMB SIDE;So;0;L;;;;;N;;;;; 1D86F;SIGNWRITING HAND-CUP THUMB SIDE;So;0;L;;;;;N;;;;; 1D870;SIGNWRITING HAND-CUP OPEN NO THUMB;So;0;L;;;;;N;;;;; 1D871;SIGNWRITING HAND-CUP NO THUMB;So;0;L;;;;;N;;;;; 1D872;SIGNWRITING HAND-CUP OPEN THUMB FORWARD;So;0;L;;;;;N;;;;; 1D873;SIGNWRITING HAND-CUP THUMB FORWARD;So;0;L;;;;;N;;;;; 1D874;SIGNWRITING HAND-CURLICUE OPEN;So;0;L;;;;;N;;;;; 1D875;SIGNWRITING HAND-CURLICUE;So;0;L;;;;;N;;;;; 1D876;SIGNWRITING HAND-CIRCLE;So;0;L;;;;;N;;;;; 1D877;SIGNWRITING HAND-OVAL;So;0;L;;;;;N;;;;; 1D878;SIGNWRITING HAND-OVAL THUMB SIDE;So;0;L;;;;;N;;;;; 1D879;SIGNWRITING HAND-OVAL NO THUMB;So;0;L;;;;;N;;;;; 1D87A;SIGNWRITING HAND-OVAL THUMB FORWARD;So;0;L;;;;;N;;;;; 1D87B;SIGNWRITING HAND-HINGE OPEN;So;0;L;;;;;N;;;;; 1D87C;SIGNWRITING HAND-HINGE OPEN THUMB FORWARD;So;0;L;;;;;N;;;;; 1D87D;SIGNWRITING HAND-HINGE;So;0;L;;;;;N;;;;; 1D87E;SIGNWRITING HAND-HINGE SMALL;So;0;L;;;;;N;;;;; 1D87F;SIGNWRITING HAND-HINGE OPEN THUMB SIDE;So;0;L;;;;;N;;;;; 1D880;SIGNWRITING HAND-HINGE THUMB SIDE;So;0;L;;;;;N;;;;; 1D881;SIGNWRITING HAND-HINGE OPEN NO THUMB;So;0;L;;;;;N;;;;; 1D882;SIGNWRITING HAND-HINGE NO THUMB;So;0;L;;;;;N;;;;; 1D883;SIGNWRITING HAND-HINGE THUMB SIDE TOUCHING INDEX;So;0;L;;;;;N;;;;; 1D884;SIGNWRITING HAND-HINGE THUMB BETWEEN MIDDLE RING;So;0;L;;;;;N;;;;; 1D885;SIGNWRITING HAND-ANGLE;So;0;L;;;;;N;;;;; 1D886;SIGNWRITING HAND-FIST INDEX MIDDLE RING;So;0;L;;;;;N;;;;; 1D887;SIGNWRITING HAND-CIRCLE INDEX MIDDLE RING;So;0;L;;;;;N;;;;; 1D888;SIGNWRITING HAND-HINGE INDEX MIDDLE RING;So;0;L;;;;;N;;;;; 1D889;SIGNWRITING HAND-ANGLE INDEX MIDDLE RING;So;0;L;;;;;N;;;;; 1D88A;SIGNWRITING HAND-HINGE LITTLE;So;0;L;;;;;N;;;;; 1D88B;SIGNWRITING HAND-FIST INDEX MIDDLE RING BENT;So;0;L;;;;;N;;;;; 1D88C;SIGNWRITING HAND-FIST INDEX MIDDLE RING CONJOINED;So;0;L;;;;;N;;;;; 1D88D;SIGNWRITING HAND-HINGE INDEX MIDDLE RING CONJOINED;So;0;L;;;;;N;;;;; 1D88E;SIGNWRITING HAND-FIST LITTLE DOWN;So;0;L;;;;;N;;;;; 1D88F;SIGNWRITING HAND-FIST LITTLE DOWN RIPPLE STRAIGHT;So;0;L;;;;;N;;;;; 1D890;SIGNWRITING HAND-FIST LITTLE DOWN RIPPLE CURVED;So;0;L;;;;;N;;;;; 1D891;SIGNWRITING HAND-FIST LITTLE DOWN OTHERS CIRCLED;So;0;L;;;;;N;;;;; 1D892;SIGNWRITING HAND-FIST LITTLE UP;So;0;L;;;;;N;;;;; 1D893;SIGNWRITING HAND-FIST THUMB UNDER LITTLE UP;So;0;L;;;;;N;;;;; 1D894;SIGNWRITING HAND-CIRCLE LITTLE UP;So;0;L;;;;;N;;;;; 1D895;SIGNWRITING HAND-OVAL LITTLE UP;So;0;L;;;;;N;;;;; 1D896;SIGNWRITING HAND-ANGLE LITTLE UP;So;0;L;;;;;N;;;;; 1D897;SIGNWRITING HAND-FIST LITTLE RAISED KNUCKLE;So;0;L;;;;;N;;;;; 1D898;SIGNWRITING HAND-FIST LITTLE BENT;So;0;L;;;;;N;;;;; 1D899;SIGNWRITING HAND-FIST LITTLE TOUCHES THUMB;So;0;L;;;;;N;;;;; 1D89A;SIGNWRITING HAND-FIST LITTLE THUMB;So;0;L;;;;;N;;;;; 1D89B;SIGNWRITING HAND-HINGE LITTLE THUMB;So;0;L;;;;;N;;;;; 1D89C;SIGNWRITING HAND-FIST LITTLE INDEX THUMB;So;0;L;;;;;N;;;;; 1D89D;SIGNWRITING HAND-HINGE LITTLE INDEX THUMB;So;0;L;;;;;N;;;;; 1D89E;SIGNWRITING HAND-ANGLE LITTLE INDEX THUMB INDEX THUMB OUT;So;0;L;;;;;N;;;;; 1D89F;SIGNWRITING HAND-ANGLE LITTLE INDEX THUMB INDEX THUMB;So;0;L;;;;;N;;;;; 1D8A0;SIGNWRITING HAND-FIST LITTLE INDEX;So;0;L;;;;;N;;;;; 1D8A1;SIGNWRITING HAND-CIRCLE LITTLE INDEX;So;0;L;;;;;N;;;;; 1D8A2;SIGNWRITING HAND-HINGE LITTLE INDEX;So;0;L;;;;;N;;;;; 1D8A3;SIGNWRITING HAND-ANGLE LITTLE INDEX;So;0;L;;;;;N;;;;; 1D8A4;SIGNWRITING HAND-FIST INDEX MIDDLE LITTLE;So;0;L;;;;;N;;;;; 1D8A5;SIGNWRITING HAND-CIRCLE INDEX MIDDLE LITTLE;So;0;L;;;;;N;;;;; 1D8A6;SIGNWRITING HAND-HINGE INDEX MIDDLE LITTLE;So;0;L;;;;;N;;;;; 1D8A7;SIGNWRITING HAND-HINGE RING;So;0;L;;;;;N;;;;; 1D8A8;SIGNWRITING HAND-ANGLE INDEX MIDDLE LITTLE;So;0;L;;;;;N;;;;; 1D8A9;SIGNWRITING HAND-FIST INDEX MIDDLE CROSS LITTLE;So;0;L;;;;;N;;;;; 1D8AA;SIGNWRITING HAND-CIRCLE INDEX MIDDLE CROSS LITTLE;So;0;L;;;;;N;;;;; 1D8AB;SIGNWRITING HAND-FIST RING DOWN;So;0;L;;;;;N;;;;; 1D8AC;SIGNWRITING HAND-HINGE RING DOWN INDEX THUMB HOOK MIDDLE;So;0;L;;;;;N;;;;; 1D8AD;SIGNWRITING HAND-ANGLE RING DOWN MIDDLE THUMB INDEX CROSS;So;0;L;;;;;N;;;;; 1D8AE;SIGNWRITING HAND-FIST RING UP;So;0;L;;;;;N;;;;; 1D8AF;SIGNWRITING HAND-FIST RING RAISED KNUCKLE;So;0;L;;;;;N;;;;; 1D8B0;SIGNWRITING HAND-FIST RING LITTLE;So;0;L;;;;;N;;;;; 1D8B1;SIGNWRITING HAND-CIRCLE RING LITTLE;So;0;L;;;;;N;;;;; 1D8B2;SIGNWRITING HAND-OVAL RING LITTLE;So;0;L;;;;;N;;;;; 1D8B3;SIGNWRITING HAND-ANGLE RING LITTLE;So;0;L;;;;;N;;;;; 1D8B4;SIGNWRITING HAND-FIST RING MIDDLE;So;0;L;;;;;N;;;;; 1D8B5;SIGNWRITING HAND-FIST RING MIDDLE CONJOINED;So;0;L;;;;;N;;;;; 1D8B6;SIGNWRITING HAND-FIST RING MIDDLE RAISED KNUCKLES;So;0;L;;;;;N;;;;; 1D8B7;SIGNWRITING HAND-FIST RING INDEX;So;0;L;;;;;N;;;;; 1D8B8;SIGNWRITING HAND-FIST RING THUMB;So;0;L;;;;;N;;;;; 1D8B9;SIGNWRITING HAND-HOOK RING THUMB;So;0;L;;;;;N;;;;; 1D8BA;SIGNWRITING HAND-FIST INDEX RING LITTLE;So;0;L;;;;;N;;;;; 1D8BB;SIGNWRITING HAND-CIRCLE INDEX RING LITTLE;So;0;L;;;;;N;;;;; 1D8BC;SIGNWRITING HAND-CURLICUE INDEX RING LITTLE ON;So;0;L;;;;;N;;;;; 1D8BD;SIGNWRITING HAND-HOOK INDEX RING LITTLE OUT;So;0;L;;;;;N;;;;; 1D8BE;SIGNWRITING HAND-HOOK INDEX RING LITTLE IN;So;0;L;;;;;N;;;;; 1D8BF;SIGNWRITING HAND-HOOK INDEX RING LITTLE UNDER;So;0;L;;;;;N;;;;; 1D8C0;SIGNWRITING HAND-CUP INDEX RING LITTLE;So;0;L;;;;;N;;;;; 1D8C1;SIGNWRITING HAND-HINGE INDEX RING LITTLE;So;0;L;;;;;N;;;;; 1D8C2;SIGNWRITING HAND-ANGLE INDEX RING LITTLE OUT;So;0;L;;;;;N;;;;; 1D8C3;SIGNWRITING HAND-ANGLE INDEX RING LITTLE;So;0;L;;;;;N;;;;; 1D8C4;SIGNWRITING HAND-FIST MIDDLE DOWN;So;0;L;;;;;N;;;;; 1D8C5;SIGNWRITING HAND-HINGE MIDDLE;So;0;L;;;;;N;;;;; 1D8C6;SIGNWRITING HAND-FIST MIDDLE UP;So;0;L;;;;;N;;;;; 1D8C7;SIGNWRITING HAND-CIRCLE MIDDLE UP;So;0;L;;;;;N;;;;; 1D8C8;SIGNWRITING HAND-FIST MIDDLE RAISED KNUCKLE;So;0;L;;;;;N;;;;; 1D8C9;SIGNWRITING HAND-FIST MIDDLE UP THUMB SIDE;So;0;L;;;;;N;;;;; 1D8CA;SIGNWRITING HAND-HOOK MIDDLE THUMB;So;0;L;;;;;N;;;;; 1D8CB;SIGNWRITING HAND-FIST MIDDLE THUMB LITTLE;So;0;L;;;;;N;;;;; 1D8CC;SIGNWRITING HAND-FIST MIDDLE LITTLE;So;0;L;;;;;N;;;;; 1D8CD;SIGNWRITING HAND-FIST MIDDLE RING LITTLE;So;0;L;;;;;N;;;;; 1D8CE;SIGNWRITING HAND-CIRCLE MIDDLE RING LITTLE;So;0;L;;;;;N;;;;; 1D8CF;SIGNWRITING HAND-CURLICUE MIDDLE RING LITTLE ON;So;0;L;;;;;N;;;;; 1D8D0;SIGNWRITING HAND-CUP MIDDLE RING LITTLE;So;0;L;;;;;N;;;;; 1D8D1;SIGNWRITING HAND-HINGE MIDDLE RING LITTLE;So;0;L;;;;;N;;;;; 1D8D2;SIGNWRITING HAND-ANGLE MIDDLE RING LITTLE OUT;So;0;L;;;;;N;;;;; 1D8D3;SIGNWRITING HAND-ANGLE MIDDLE RING LITTLE IN;So;0;L;;;;;N;;;;; 1D8D4;SIGNWRITING HAND-ANGLE MIDDLE RING LITTLE;So;0;L;;;;;N;;;;; 1D8D5;SIGNWRITING HAND-CIRCLE MIDDLE RING LITTLE BENT;So;0;L;;;;;N;;;;; 1D8D6;SIGNWRITING HAND-CLAW MIDDLE RING LITTLE CONJOINED;So;0;L;;;;;N;;;;; 1D8D7;SIGNWRITING HAND-CLAW MIDDLE RING LITTLE CONJOINED SIDE;So;0;L;;;;;N;;;;; 1D8D8;SIGNWRITING HAND-HOOK MIDDLE RING LITTLE CONJOINED OUT;So;0;L;;;;;N;;;;; 1D8D9;SIGNWRITING HAND-HOOK MIDDLE RING LITTLE CONJOINED IN;So;0;L;;;;;N;;;;; 1D8DA;SIGNWRITING HAND-HOOK MIDDLE RING LITTLE CONJOINED;So;0;L;;;;;N;;;;; 1D8DB;SIGNWRITING HAND-HINGE INDEX HINGED;So;0;L;;;;;N;;;;; 1D8DC;SIGNWRITING HAND-FIST INDEX THUMB SIDE;So;0;L;;;;;N;;;;; 1D8DD;SIGNWRITING HAND-HINGE INDEX THUMB SIDE;So;0;L;;;;;N;;;;; 1D8DE;SIGNWRITING HAND-FIST INDEX THUMB SIDE THUMB DIAGONAL;So;0;L;;;;;N;;;;; 1D8DF;SIGNWRITING HAND-FIST INDEX THUMB SIDE THUMB CONJOINED;So;0;L;;;;;N;;;;; 1D8E0;SIGNWRITING HAND-FIST INDEX THUMB SIDE THUMB BENT;So;0;L;;;;;N;;;;; 1D8E1;SIGNWRITING HAND-FIST INDEX THUMB SIDE INDEX BENT;So;0;L;;;;;N;;;;; 1D8E2;SIGNWRITING HAND-FIST INDEX THUMB SIDE BOTH BENT;So;0;L;;;;;N;;;;; 1D8E3;SIGNWRITING HAND-FIST INDEX THUMB SIDE INDEX HINGE;So;0;L;;;;;N;;;;; 1D8E4;SIGNWRITING HAND-FIST INDEX THUMB FORWARD INDEX STRAIGHT;So;0;L;;;;;N;;;;; 1D8E5;SIGNWRITING HAND-FIST INDEX THUMB FORWARD INDEX BENT;So;0;L;;;;;N;;;;; 1D8E6;SIGNWRITING HAND-FIST INDEX THUMB HOOK;So;0;L;;;;;N;;;;; 1D8E7;SIGNWRITING HAND-FIST INDEX THUMB CURLICUE;So;0;L;;;;;N;;;;; 1D8E8;SIGNWRITING HAND-FIST INDEX THUMB CURVE THUMB INSIDE;So;0;L;;;;;N;;;;; 1D8E9;SIGNWRITING HAND-CLAW INDEX THUMB CURVE THUMB INSIDE;So;0;L;;;;;N;;;;; 1D8EA;SIGNWRITING HAND-FIST INDEX THUMB CURVE THUMB UNDER;So;0;L;;;;;N;;;;; 1D8EB;SIGNWRITING HAND-FIST INDEX THUMB CIRCLE;So;0;L;;;;;N;;;;; 1D8EC;SIGNWRITING HAND-CUP INDEX THUMB;So;0;L;;;;;N;;;;; 1D8ED;SIGNWRITING HAND-CUP INDEX THUMB OPEN;So;0;L;;;;;N;;;;; 1D8EE;SIGNWRITING HAND-HINGE INDEX THUMB OPEN;So;0;L;;;;;N;;;;; 1D8EF;SIGNWRITING HAND-HINGE INDEX THUMB LARGE;So;0;L;;;;;N;;;;; 1D8F0;SIGNWRITING HAND-HINGE INDEX THUMB;So;0;L;;;;;N;;;;; 1D8F1;SIGNWRITING HAND-HINGE INDEX THUMB SMALL;So;0;L;;;;;N;;;;; 1D8F2;SIGNWRITING HAND-ANGLE INDEX THUMB OUT;So;0;L;;;;;N;;;;; 1D8F3;SIGNWRITING HAND-ANGLE INDEX THUMB IN;So;0;L;;;;;N;;;;; 1D8F4;SIGNWRITING HAND-ANGLE INDEX THUMB;So;0;L;;;;;N;;;;; 1D8F5;SIGNWRITING HAND-FIST THUMB;So;0;L;;;;;N;;;;; 1D8F6;SIGNWRITING HAND-FIST THUMB HEEL;So;0;L;;;;;N;;;;; 1D8F7;SIGNWRITING HAND-FIST THUMB SIDE DIAGONAL;So;0;L;;;;;N;;;;; 1D8F8;SIGNWRITING HAND-FIST THUMB SIDE CONJOINED;So;0;L;;;;;N;;;;; 1D8F9;SIGNWRITING HAND-FIST THUMB SIDE BENT;So;0;L;;;;;N;;;;; 1D8FA;SIGNWRITING HAND-FIST THUMB FORWARD;So;0;L;;;;;N;;;;; 1D8FB;SIGNWRITING HAND-FIST THUMB BETWEEN INDEX MIDDLE;So;0;L;;;;;N;;;;; 1D8FC;SIGNWRITING HAND-FIST THUMB BETWEEN MIDDLE RING;So;0;L;;;;;N;;;;; 1D8FD;SIGNWRITING HAND-FIST THUMB BETWEEN RING LITTLE;So;0;L;;;;;N;;;;; 1D8FE;SIGNWRITING HAND-FIST THUMB UNDER TWO FINGERS;So;0;L;;;;;N;;;;; 1D8FF;SIGNWRITING HAND-FIST THUMB OVER TWO FINGERS;So;0;L;;;;;N;;;;; 1D900;SIGNWRITING HAND-FIST THUMB UNDER THREE FINGERS;So;0;L;;;;;N;;;;; 1D901;SIGNWRITING HAND-FIST THUMB UNDER FOUR FINGERS;So;0;L;;;;;N;;;;; 1D902;SIGNWRITING HAND-FIST THUMB OVER FOUR RAISED KNUCKLES;So;0;L;;;;;N;;;;; 1D903;SIGNWRITING HAND-FIST;So;0;L;;;;;N;;;;; 1D904;SIGNWRITING HAND-FIST HEEL;So;0;L;;;;;N;;;;; 1D905;SIGNWRITING TOUCH SINGLE;So;0;L;;;;;N;;;;; 1D906;SIGNWRITING TOUCH MULTIPLE;So;0;L;;;;;N;;;;; 1D907;SIGNWRITING TOUCH BETWEEN;So;0;L;;;;;N;;;;; 1D908;SIGNWRITING GRASP SINGLE;So;0;L;;;;;N;;;;; 1D909;SIGNWRITING GRASP MULTIPLE;So;0;L;;;;;N;;;;; 1D90A;SIGNWRITING GRASP BETWEEN;So;0;L;;;;;N;;;;; 1D90B;SIGNWRITING STRIKE SINGLE;So;0;L;;;;;N;;;;; 1D90C;SIGNWRITING STRIKE MULTIPLE;So;0;L;;;;;N;;;;; 1D90D;SIGNWRITING STRIKE BETWEEN;So;0;L;;;;;N;;;;; 1D90E;SIGNWRITING BRUSH SINGLE;So;0;L;;;;;N;;;;; 1D90F;SIGNWRITING BRUSH MULTIPLE;So;0;L;;;;;N;;;;; 1D910;SIGNWRITING BRUSH BETWEEN;So;0;L;;;;;N;;;;; 1D911;SIGNWRITING RUB SINGLE;So;0;L;;;;;N;;;;; 1D912;SIGNWRITING RUB MULTIPLE;So;0;L;;;;;N;;;;; 1D913;SIGNWRITING RUB BETWEEN;So;0;L;;;;;N;;;;; 1D914;SIGNWRITING SURFACE SYMBOLS;So;0;L;;;;;N;;;;; 1D915;SIGNWRITING SURFACE BETWEEN;So;0;L;;;;;N;;;;; 1D916;SIGNWRITING SQUEEZE LARGE SINGLE;So;0;L;;;;;N;;;;; 1D917;SIGNWRITING SQUEEZE SMALL SINGLE;So;0;L;;;;;N;;;;; 1D918;SIGNWRITING SQUEEZE LARGE MULTIPLE;So;0;L;;;;;N;;;;; 1D919;SIGNWRITING SQUEEZE SMALL MULTIPLE;So;0;L;;;;;N;;;;; 1D91A;SIGNWRITING SQUEEZE SEQUENTIAL;So;0;L;;;;;N;;;;; 1D91B;SIGNWRITING FLICK LARGE SINGLE;So;0;L;;;;;N;;;;; 1D91C;SIGNWRITING FLICK SMALL SINGLE;So;0;L;;;;;N;;;;; 1D91D;SIGNWRITING FLICK LARGE MULTIPLE;So;0;L;;;;;N;;;;; 1D91E;SIGNWRITING FLICK SMALL MULTIPLE;So;0;L;;;;;N;;;;; 1D91F;SIGNWRITING FLICK SEQUENTIAL;So;0;L;;;;;N;;;;; 1D920;SIGNWRITING SQUEEZE FLICK ALTERNATING;So;0;L;;;;;N;;;;; 1D921;SIGNWRITING MOVEMENT-HINGE UP DOWN LARGE;So;0;L;;;;;N;;;;; 1D922;SIGNWRITING MOVEMENT-HINGE UP DOWN SMALL;So;0;L;;;;;N;;;;; 1D923;SIGNWRITING MOVEMENT-HINGE UP SEQUENTIAL;So;0;L;;;;;N;;;;; 1D924;SIGNWRITING MOVEMENT-HINGE DOWN SEQUENTIAL;So;0;L;;;;;N;;;;; 1D925;SIGNWRITING MOVEMENT-HINGE UP DOWN ALTERNATING LARGE;So;0;L;;;;;N;;;;; 1D926;SIGNWRITING MOVEMENT-HINGE UP DOWN ALTERNATING SMALL;So;0;L;;;;;N;;;;; 1D927;SIGNWRITING MOVEMENT-HINGE SIDE TO SIDE SCISSORS;So;0;L;;;;;N;;;;; 1D928;SIGNWRITING MOVEMENT-WALLPLANE FINGER CONTACT;So;0;L;;;;;N;;;;; 1D929;SIGNWRITING MOVEMENT-FLOORPLANE FINGER CONTACT;So;0;L;;;;;N;;;;; 1D92A;SIGNWRITING MOVEMENT-WALLPLANE SINGLE STRAIGHT SMALL;So;0;L;;;;;N;;;;; 1D92B;SIGNWRITING MOVEMENT-WALLPLANE SINGLE STRAIGHT MEDIUM;So;0;L;;;;;N;;;;; 1D92C;SIGNWRITING MOVEMENT-WALLPLANE SINGLE STRAIGHT LARGE;So;0;L;;;;;N;;;;; 1D92D;SIGNWRITING MOVEMENT-WALLPLANE SINGLE STRAIGHT LARGEST;So;0;L;;;;;N;;;;; 1D92E;SIGNWRITING MOVEMENT-WALLPLANE SINGLE WRIST FLEX;So;0;L;;;;;N;;;;; 1D92F;SIGNWRITING MOVEMENT-WALLPLANE DOUBLE STRAIGHT;So;0;L;;;;;N;;;;; 1D930;SIGNWRITING MOVEMENT-WALLPLANE DOUBLE WRIST FLEX;So;0;L;;;;;N;;;;; 1D931;SIGNWRITING MOVEMENT-WALLPLANE DOUBLE ALTERNATING;So;0;L;;;;;N;;;;; 1D932;SIGNWRITING MOVEMENT-WALLPLANE DOUBLE ALTERNATING WRIST FLEX;So;0;L;;;;;N;;;;; 1D933;SIGNWRITING MOVEMENT-WALLPLANE CROSS;So;0;L;;;;;N;;;;; 1D934;SIGNWRITING MOVEMENT-WALLPLANE TRIPLE STRAIGHT MOVEMENT;So;0;L;;;;;N;;;;; 1D935;SIGNWRITING MOVEMENT-WALLPLANE TRIPLE WRIST FLEX;So;0;L;;;;;N;;;;; 1D936;SIGNWRITING MOVEMENT-WALLPLANE TRIPLE ALTERNATING;So;0;L;;;;;N;;;;; 1D937;SIGNWRITING MOVEMENT-WALLPLANE TRIPLE ALTERNATING WRIST FLEX;So;0;L;;;;;N;;;;; 1D938;SIGNWRITING MOVEMENT-WALLPLANE BEND SMALL;So;0;L;;;;;N;;;;; 1D939;SIGNWRITING MOVEMENT-WALLPLANE BEND MEDIUM;So;0;L;;;;;N;;;;; 1D93A;SIGNWRITING MOVEMENT-WALLPLANE BEND LARGE;So;0;L;;;;;N;;;;; 1D93B;SIGNWRITING MOVEMENT-WALLPLANE CORNER SMALL;So;0;L;;;;;N;;;;; 1D93C;SIGNWRITING MOVEMENT-WALLPLANE CORNER MEDIUM;So;0;L;;;;;N;;;;; 1D93D;SIGNWRITING MOVEMENT-WALLPLANE CORNER LARGE;So;0;L;;;;;N;;;;; 1D93E;SIGNWRITING MOVEMENT-WALLPLANE CORNER ROTATION;So;0;L;;;;;N;;;;; 1D93F;SIGNWRITING MOVEMENT-WALLPLANE CHECK SMALL;So;0;L;;;;;N;;;;; 1D940;SIGNWRITING MOVEMENT-WALLPLANE CHECK MEDIUM;So;0;L;;;;;N;;;;; 1D941;SIGNWRITING MOVEMENT-WALLPLANE CHECK LARGE;So;0;L;;;;;N;;;;; 1D942;SIGNWRITING MOVEMENT-WALLPLANE BOX SMALL;So;0;L;;;;;N;;;;; 1D943;SIGNWRITING MOVEMENT-WALLPLANE BOX MEDIUM;So;0;L;;;;;N;;;;; 1D944;SIGNWRITING MOVEMENT-WALLPLANE BOX LARGE;So;0;L;;;;;N;;;;; 1D945;SIGNWRITING MOVEMENT-WALLPLANE ZIGZAG SMALL;So;0;L;;;;;N;;;;; 1D946;SIGNWRITING MOVEMENT-WALLPLANE ZIGZAG MEDIUM;So;0;L;;;;;N;;;;; 1D947;SIGNWRITING MOVEMENT-WALLPLANE ZIGZAG LARGE;So;0;L;;;;;N;;;;; 1D948;SIGNWRITING MOVEMENT-WALLPLANE PEAKS SMALL;So;0;L;;;;;N;;;;; 1D949;SIGNWRITING MOVEMENT-WALLPLANE PEAKS MEDIUM;So;0;L;;;;;N;;;;; 1D94A;SIGNWRITING MOVEMENT-WALLPLANE PEAKS LARGE;So;0;L;;;;;N;;;;; 1D94B;SIGNWRITING TRAVEL-WALLPLANE ROTATION-WALLPLANE SINGLE;So;0;L;;;;;N;;;;; 1D94C;SIGNWRITING TRAVEL-WALLPLANE ROTATION-WALLPLANE DOUBLE;So;0;L;;;;;N;;;;; 1D94D;SIGNWRITING TRAVEL-WALLPLANE ROTATION-WALLPLANE ALTERNATING;So;0;L;;;;;N;;;;; 1D94E;SIGNWRITING TRAVEL-WALLPLANE ROTATION-FLOORPLANE SINGLE;So;0;L;;;;;N;;;;; 1D94F;SIGNWRITING TRAVEL-WALLPLANE ROTATION-FLOORPLANE DOUBLE;So;0;L;;;;;N;;;;; 1D950;SIGNWRITING TRAVEL-WALLPLANE ROTATION-FLOORPLANE ALTERNATING;So;0;L;;;;;N;;;;; 1D951;SIGNWRITING TRAVEL-WALLPLANE SHAKING;So;0;L;;;;;N;;;;; 1D952;SIGNWRITING TRAVEL-WALLPLANE ARM SPIRAL SINGLE;So;0;L;;;;;N;;;;; 1D953;SIGNWRITING TRAVEL-WALLPLANE ARM SPIRAL DOUBLE;So;0;L;;;;;N;;;;; 1D954;SIGNWRITING TRAVEL-WALLPLANE ARM SPIRAL TRIPLE;So;0;L;;;;;N;;;;; 1D955;SIGNWRITING MOVEMENT-DIAGONAL AWAY SMALL;So;0;L;;;;;N;;;;; 1D956;SIGNWRITING MOVEMENT-DIAGONAL AWAY MEDIUM;So;0;L;;;;;N;;;;; 1D957;SIGNWRITING MOVEMENT-DIAGONAL AWAY LARGE;So;0;L;;;;;N;;;;; 1D958;SIGNWRITING MOVEMENT-DIAGONAL AWAY LARGEST;So;0;L;;;;;N;;;;; 1D959;SIGNWRITING MOVEMENT-DIAGONAL TOWARDS SMALL;So;0;L;;;;;N;;;;; 1D95A;SIGNWRITING MOVEMENT-DIAGONAL TOWARDS MEDIUM;So;0;L;;;;;N;;;;; 1D95B;SIGNWRITING MOVEMENT-DIAGONAL TOWARDS LARGE;So;0;L;;;;;N;;;;; 1D95C;SIGNWRITING MOVEMENT-DIAGONAL TOWARDS LARGEST;So;0;L;;;;;N;;;;; 1D95D;SIGNWRITING MOVEMENT-DIAGONAL BETWEEN AWAY SMALL;So;0;L;;;;;N;;;;; 1D95E;SIGNWRITING MOVEMENT-DIAGONAL BETWEEN AWAY MEDIUM;So;0;L;;;;;N;;;;; 1D95F;SIGNWRITING MOVEMENT-DIAGONAL BETWEEN AWAY LARGE;So;0;L;;;;;N;;;;; 1D960;SIGNWRITING MOVEMENT-DIAGONAL BETWEEN AWAY LARGEST;So;0;L;;;;;N;;;;; 1D961;SIGNWRITING MOVEMENT-DIAGONAL BETWEEN TOWARDS SMALL;So;0;L;;;;;N;;;;; 1D962;SIGNWRITING MOVEMENT-DIAGONAL BETWEEN TOWARDS MEDIUM;So;0;L;;;;;N;;;;; 1D963;SIGNWRITING MOVEMENT-DIAGONAL BETWEEN TOWARDS LARGE;So;0;L;;;;;N;;;;; 1D964;SIGNWRITING MOVEMENT-DIAGONAL BETWEEN TOWARDS LARGEST;So;0;L;;;;;N;;;;; 1D965;SIGNWRITING MOVEMENT-FLOORPLANE SINGLE STRAIGHT SMALL;So;0;L;;;;;N;;;;; 1D966;SIGNWRITING MOVEMENT-FLOORPLANE SINGLE STRAIGHT MEDIUM;So;0;L;;;;;N;;;;; 1D967;SIGNWRITING MOVEMENT-FLOORPLANE SINGLE STRAIGHT LARGE;So;0;L;;;;;N;;;;; 1D968;SIGNWRITING MOVEMENT-FLOORPLANE SINGLE STRAIGHT LARGEST;So;0;L;;;;;N;;;;; 1D969;SIGNWRITING MOVEMENT-FLOORPLANE SINGLE WRIST FLEX;So;0;L;;;;;N;;;;; 1D96A;SIGNWRITING MOVEMENT-FLOORPLANE DOUBLE STRAIGHT;So;0;L;;;;;N;;;;; 1D96B;SIGNWRITING MOVEMENT-FLOORPLANE DOUBLE WRIST FLEX;So;0;L;;;;;N;;;;; 1D96C;SIGNWRITING MOVEMENT-FLOORPLANE DOUBLE ALTERNATING;So;0;L;;;;;N;;;;; 1D96D;SIGNWRITING MOVEMENT-FLOORPLANE DOUBLE ALTERNATING WRIST FLEX;So;0;L;;;;;N;;;;; 1D96E;SIGNWRITING MOVEMENT-FLOORPLANE CROSS;So;0;L;;;;;N;;;;; 1D96F;SIGNWRITING MOVEMENT-FLOORPLANE TRIPLE STRAIGHT MOVEMENT;So;0;L;;;;;N;;;;; 1D970;SIGNWRITING MOVEMENT-FLOORPLANE TRIPLE WRIST FLEX;So;0;L;;;;;N;;;;; 1D971;SIGNWRITING MOVEMENT-FLOORPLANE TRIPLE ALTERNATING MOVEMENT;So;0;L;;;;;N;;;;; 1D972;SIGNWRITING MOVEMENT-FLOORPLANE TRIPLE ALTERNATING WRIST FLEX;So;0;L;;;;;N;;;;; 1D973;SIGNWRITING MOVEMENT-FLOORPLANE BEND;So;0;L;;;;;N;;;;; 1D974;SIGNWRITING MOVEMENT-FLOORPLANE CORNER SMALL;So;0;L;;;;;N;;;;; 1D975;SIGNWRITING MOVEMENT-FLOORPLANE CORNER MEDIUM;So;0;L;;;;;N;;;;; 1D976;SIGNWRITING MOVEMENT-FLOORPLANE CORNER LARGE;So;0;L;;;;;N;;;;; 1D977;SIGNWRITING MOVEMENT-FLOORPLANE CHECK;So;0;L;;;;;N;;;;; 1D978;SIGNWRITING MOVEMENT-FLOORPLANE BOX SMALL;So;0;L;;;;;N;;;;; 1D979;SIGNWRITING MOVEMENT-FLOORPLANE BOX MEDIUM;So;0;L;;;;;N;;;;; 1D97A;SIGNWRITING MOVEMENT-FLOORPLANE BOX LARGE;So;0;L;;;;;N;;;;; 1D97B;SIGNWRITING MOVEMENT-FLOORPLANE ZIGZAG SMALL;So;0;L;;;;;N;;;;; 1D97C;SIGNWRITING MOVEMENT-FLOORPLANE ZIGZAG MEDIUM;So;0;L;;;;;N;;;;; 1D97D;SIGNWRITING MOVEMENT-FLOORPLANE ZIGZAG LARGE;So;0;L;;;;;N;;;;; 1D97E;SIGNWRITING MOVEMENT-FLOORPLANE PEAKS SMALL;So;0;L;;;;;N;;;;; 1D97F;SIGNWRITING MOVEMENT-FLOORPLANE PEAKS MEDIUM;So;0;L;;;;;N;;;;; 1D980;SIGNWRITING MOVEMENT-FLOORPLANE PEAKS LARGE;So;0;L;;;;;N;;;;; 1D981;SIGNWRITING TRAVEL-FLOORPLANE ROTATION-FLOORPLANE SINGLE;So;0;L;;;;;N;;;;; 1D982;SIGNWRITING TRAVEL-FLOORPLANE ROTATION-FLOORPLANE DOUBLE;So;0;L;;;;;N;;;;; 1D983;SIGNWRITING TRAVEL-FLOORPLANE ROTATION-FLOORPLANE ALTERNATING;So;0;L;;;;;N;;;;; 1D984;SIGNWRITING TRAVEL-FLOORPLANE ROTATION-WALLPLANE SINGLE;So;0;L;;;;;N;;;;; 1D985;SIGNWRITING TRAVEL-FLOORPLANE ROTATION-WALLPLANE DOUBLE;So;0;L;;;;;N;;;;; 1D986;SIGNWRITING TRAVEL-FLOORPLANE ROTATION-WALLPLANE ALTERNATING;So;0;L;;;;;N;;;;; 1D987;SIGNWRITING TRAVEL-FLOORPLANE SHAKING;So;0;L;;;;;N;;;;; 1D988;SIGNWRITING MOVEMENT-WALLPLANE CURVE QUARTER SMALL;So;0;L;;;;;N;;;;; 1D989;SIGNWRITING MOVEMENT-WALLPLANE CURVE QUARTER MEDIUM;So;0;L;;;;;N;;;;; 1D98A;SIGNWRITING MOVEMENT-WALLPLANE CURVE QUARTER LARGE;So;0;L;;;;;N;;;;; 1D98B;SIGNWRITING MOVEMENT-WALLPLANE CURVE QUARTER LARGEST;So;0;L;;;;;N;;;;; 1D98C;SIGNWRITING MOVEMENT-WALLPLANE CURVE HALF-CIRCLE SMALL;So;0;L;;;;;N;;;;; 1D98D;SIGNWRITING MOVEMENT-WALLPLANE CURVE HALF-CIRCLE MEDIUM;So;0;L;;;;;N;;;;; 1D98E;SIGNWRITING MOVEMENT-WALLPLANE CURVE HALF-CIRCLE LARGE;So;0;L;;;;;N;;;;; 1D98F;SIGNWRITING MOVEMENT-WALLPLANE CURVE HALF-CIRCLE LARGEST;So;0;L;;;;;N;;;;; 1D990;SIGNWRITING MOVEMENT-WALLPLANE CURVE THREE-QUARTER CIRCLE SMALL;So;0;L;;;;;N;;;;; 1D991;SIGNWRITING MOVEMENT-WALLPLANE CURVE THREE-QUARTER CIRCLE MEDIUM;So;0;L;;;;;N;;;;; 1D992;SIGNWRITING MOVEMENT-WALLPLANE HUMP SMALL;So;0;L;;;;;N;;;;; 1D993;SIGNWRITING MOVEMENT-WALLPLANE HUMP MEDIUM;So;0;L;;;;;N;;;;; 1D994;SIGNWRITING MOVEMENT-WALLPLANE HUMP LARGE;So;0;L;;;;;N;;;;; 1D995;SIGNWRITING MOVEMENT-WALLPLANE LOOP SMALL;So;0;L;;;;;N;;;;; 1D996;SIGNWRITING MOVEMENT-WALLPLANE LOOP MEDIUM;So;0;L;;;;;N;;;;; 1D997;SIGNWRITING MOVEMENT-WALLPLANE LOOP LARGE;So;0;L;;;;;N;;;;; 1D998;SIGNWRITING MOVEMENT-WALLPLANE LOOP SMALL DOUBLE;So;0;L;;;;;N;;;;; 1D999;SIGNWRITING MOVEMENT-WALLPLANE WAVE CURVE DOUBLE SMALL;So;0;L;;;;;N;;;;; 1D99A;SIGNWRITING MOVEMENT-WALLPLANE WAVE CURVE DOUBLE MEDIUM;So;0;L;;;;;N;;;;; 1D99B;SIGNWRITING MOVEMENT-WALLPLANE WAVE CURVE DOUBLE LARGE;So;0;L;;;;;N;;;;; 1D99C;SIGNWRITING MOVEMENT-WALLPLANE WAVE CURVE TRIPLE SMALL;So;0;L;;;;;N;;;;; 1D99D;SIGNWRITING MOVEMENT-WALLPLANE WAVE CURVE TRIPLE MEDIUM;So;0;L;;;;;N;;;;; 1D99E;SIGNWRITING MOVEMENT-WALLPLANE WAVE CURVE TRIPLE LARGE;So;0;L;;;;;N;;;;; 1D99F;SIGNWRITING MOVEMENT-WALLPLANE CURVE THEN STRAIGHT;So;0;L;;;;;N;;;;; 1D9A0;SIGNWRITING MOVEMENT-WALLPLANE CURVED CROSS SMALL;So;0;L;;;;;N;;;;; 1D9A1;SIGNWRITING MOVEMENT-WALLPLANE CURVED CROSS MEDIUM;So;0;L;;;;;N;;;;; 1D9A2;SIGNWRITING ROTATION-WALLPLANE SINGLE;So;0;L;;;;;N;;;;; 1D9A3;SIGNWRITING ROTATION-WALLPLANE DOUBLE;So;0;L;;;;;N;;;;; 1D9A4;SIGNWRITING ROTATION-WALLPLANE ALTERNATE;So;0;L;;;;;N;;;;; 1D9A5;SIGNWRITING MOVEMENT-WALLPLANE SHAKING;So;0;L;;;;;N;;;;; 1D9A6;SIGNWRITING MOVEMENT-WALLPLANE CURVE HITTING FRONT WALL;So;0;L;;;;;N;;;;; 1D9A7;SIGNWRITING MOVEMENT-WALLPLANE HUMP HITTING FRONT WALL;So;0;L;;;;;N;;;;; 1D9A8;SIGNWRITING MOVEMENT-WALLPLANE LOOP HITTING FRONT WALL;So;0;L;;;;;N;;;;; 1D9A9;SIGNWRITING MOVEMENT-WALLPLANE WAVE HITTING FRONT WALL;So;0;L;;;;;N;;;;; 1D9AA;SIGNWRITING ROTATION-WALLPLANE SINGLE HITTING FRONT WALL;So;0;L;;;;;N;;;;; 1D9AB;SIGNWRITING ROTATION-WALLPLANE DOUBLE HITTING FRONT WALL;So;0;L;;;;;N;;;;; 1D9AC;SIGNWRITING ROTATION-WALLPLANE ALTERNATING HITTING FRONT WALL;So;0;L;;;;;N;;;;; 1D9AD;SIGNWRITING MOVEMENT-WALLPLANE CURVE HITTING CHEST;So;0;L;;;;;N;;;;; 1D9AE;SIGNWRITING MOVEMENT-WALLPLANE HUMP HITTING CHEST;So;0;L;;;;;N;;;;; 1D9AF;SIGNWRITING MOVEMENT-WALLPLANE LOOP HITTING CHEST;So;0;L;;;;;N;;;;; 1D9B0;SIGNWRITING MOVEMENT-WALLPLANE WAVE HITTING CHEST;So;0;L;;;;;N;;;;; 1D9B1;SIGNWRITING ROTATION-WALLPLANE SINGLE HITTING CHEST;So;0;L;;;;;N;;;;; 1D9B2;SIGNWRITING ROTATION-WALLPLANE DOUBLE HITTING CHEST;So;0;L;;;;;N;;;;; 1D9B3;SIGNWRITING ROTATION-WALLPLANE ALTERNATING HITTING CHEST;So;0;L;;;;;N;;;;; 1D9B4;SIGNWRITING MOVEMENT-WALLPLANE WAVE DIAGONAL PATH SMALL;So;0;L;;;;;N;;;;; 1D9B5;SIGNWRITING MOVEMENT-WALLPLANE WAVE DIAGONAL PATH MEDIUM;So;0;L;;;;;N;;;;; 1D9B6;SIGNWRITING MOVEMENT-WALLPLANE WAVE DIAGONAL PATH LARGE;So;0;L;;;;;N;;;;; 1D9B7;SIGNWRITING MOVEMENT-FLOORPLANE CURVE HITTING CEILING SMALL;So;0;L;;;;;N;;;;; 1D9B8;SIGNWRITING MOVEMENT-FLOORPLANE CURVE HITTING CEILING LARGE;So;0;L;;;;;N;;;;; 1D9B9;SIGNWRITING MOVEMENT-FLOORPLANE HUMP HITTING CEILING SMALL DOUBLE;So;0;L;;;;;N;;;;; 1D9BA;SIGNWRITING MOVEMENT-FLOORPLANE HUMP HITTING CEILING LARGE DOUBLE;So;0;L;;;;;N;;;;; 1D9BB;SIGNWRITING MOVEMENT-FLOORPLANE HUMP HITTING CEILING SMALL TRIPLE;So;0;L;;;;;N;;;;; 1D9BC;SIGNWRITING MOVEMENT-FLOORPLANE HUMP HITTING CEILING LARGE TRIPLE;So;0;L;;;;;N;;;;; 1D9BD;SIGNWRITING MOVEMENT-FLOORPLANE LOOP HITTING CEILING SMALL SINGLE;So;0;L;;;;;N;;;;; 1D9BE;SIGNWRITING MOVEMENT-FLOORPLANE LOOP HITTING CEILING LARGE SINGLE;So;0;L;;;;;N;;;;; 1D9BF;SIGNWRITING MOVEMENT-FLOORPLANE LOOP HITTING CEILING SMALL DOUBLE;So;0;L;;;;;N;;;;; 1D9C0;SIGNWRITING MOVEMENT-FLOORPLANE LOOP HITTING CEILING LARGE DOUBLE;So;0;L;;;;;N;;;;; 1D9C1;SIGNWRITING MOVEMENT-FLOORPLANE WAVE HITTING CEILING SMALL;So;0;L;;;;;N;;;;; 1D9C2;SIGNWRITING MOVEMENT-FLOORPLANE WAVE HITTING CEILING LARGE;So;0;L;;;;;N;;;;; 1D9C3;SIGNWRITING ROTATION-FLOORPLANE SINGLE HITTING CEILING;So;0;L;;;;;N;;;;; 1D9C4;SIGNWRITING ROTATION-FLOORPLANE DOUBLE HITTING CEILING;So;0;L;;;;;N;;;;; 1D9C5;SIGNWRITING ROTATION-FLOORPLANE ALTERNATING HITTING CEILING;So;0;L;;;;;N;;;;; 1D9C6;SIGNWRITING MOVEMENT-FLOORPLANE CURVE HITTING FLOOR SMALL;So;0;L;;;;;N;;;;; 1D9C7;SIGNWRITING MOVEMENT-FLOORPLANE CURVE HITTING FLOOR LARGE;So;0;L;;;;;N;;;;; 1D9C8;SIGNWRITING MOVEMENT-FLOORPLANE HUMP HITTING FLOOR SMALL DOUBLE;So;0;L;;;;;N;;;;; 1D9C9;SIGNWRITING MOVEMENT-FLOORPLANE HUMP HITTING FLOOR LARGE DOUBLE;So;0;L;;;;;N;;;;; 1D9CA;SIGNWRITING MOVEMENT-FLOORPLANE HUMP HITTING FLOOR TRIPLE SMALL TRIPLE;So;0;L;;;;;N;;;;; 1D9CB;SIGNWRITING MOVEMENT-FLOORPLANE HUMP HITTING FLOOR TRIPLE LARGE TRIPLE;So;0;L;;;;;N;;;;; 1D9CC;SIGNWRITING MOVEMENT-FLOORPLANE LOOP HITTING FLOOR SMALL SINGLE;So;0;L;;;;;N;;;;; 1D9CD;SIGNWRITING MOVEMENT-FLOORPLANE LOOP HITTING FLOOR LARGE SINGLE;So;0;L;;;;;N;;;;; 1D9CE;SIGNWRITING MOVEMENT-FLOORPLANE LOOP HITTING FLOOR SMALL DOUBLE;So;0;L;;;;;N;;;;; 1D9CF;SIGNWRITING MOVEMENT-FLOORPLANE LOOP HITTING FLOOR LARGE DOUBLE;So;0;L;;;;;N;;;;; 1D9D0;SIGNWRITING MOVEMENT-FLOORPLANE WAVE HITTING FLOOR SMALL;So;0;L;;;;;N;;;;; 1D9D1;SIGNWRITING MOVEMENT-FLOORPLANE WAVE HITTING FLOOR LARGE;So;0;L;;;;;N;;;;; 1D9D2;SIGNWRITING ROTATION-FLOORPLANE SINGLE HITTING FLOOR;So;0;L;;;;;N;;;;; 1D9D3;SIGNWRITING ROTATION-FLOORPLANE DOUBLE HITTING FLOOR;So;0;L;;;;;N;;;;; 1D9D4;SIGNWRITING ROTATION-FLOORPLANE ALTERNATING HITTING FLOOR;So;0;L;;;;;N;;;;; 1D9D5;SIGNWRITING MOVEMENT-FLOORPLANE CURVE SMALL;So;0;L;;;;;N;;;;; 1D9D6;SIGNWRITING MOVEMENT-FLOORPLANE CURVE MEDIUM;So;0;L;;;;;N;;;;; 1D9D7;SIGNWRITING MOVEMENT-FLOORPLANE CURVE LARGE;So;0;L;;;;;N;;;;; 1D9D8;SIGNWRITING MOVEMENT-FLOORPLANE CURVE LARGEST;So;0;L;;;;;N;;;;; 1D9D9;SIGNWRITING MOVEMENT-FLOORPLANE CURVE COMBINED;So;0;L;;;;;N;;;;; 1D9DA;SIGNWRITING MOVEMENT-FLOORPLANE HUMP SMALL;So;0;L;;;;;N;;;;; 1D9DB;SIGNWRITING MOVEMENT-FLOORPLANE LOOP SMALL;So;0;L;;;;;N;;;;; 1D9DC;SIGNWRITING MOVEMENT-FLOORPLANE WAVE SNAKE;So;0;L;;;;;N;;;;; 1D9DD;SIGNWRITING MOVEMENT-FLOORPLANE WAVE SMALL;So;0;L;;;;;N;;;;; 1D9DE;SIGNWRITING MOVEMENT-FLOORPLANE WAVE LARGE;So;0;L;;;;;N;;;;; 1D9DF;SIGNWRITING ROTATION-FLOORPLANE SINGLE;So;0;L;;;;;N;;;;; 1D9E0;SIGNWRITING ROTATION-FLOORPLANE DOUBLE;So;0;L;;;;;N;;;;; 1D9E1;SIGNWRITING ROTATION-FLOORPLANE ALTERNATING;So;0;L;;;;;N;;;;; 1D9E2;SIGNWRITING MOVEMENT-FLOORPLANE SHAKING PARALLEL;So;0;L;;;;;N;;;;; 1D9E3;SIGNWRITING MOVEMENT-WALLPLANE ARM CIRCLE SMALL SINGLE;So;0;L;;;;;N;;;;; 1D9E4;SIGNWRITING MOVEMENT-WALLPLANE ARM CIRCLE MEDIUM SINGLE;So;0;L;;;;;N;;;;; 1D9E5;SIGNWRITING MOVEMENT-WALLPLANE ARM CIRCLE SMALL DOUBLE;So;0;L;;;;;N;;;;; 1D9E6;SIGNWRITING MOVEMENT-WALLPLANE ARM CIRCLE MEDIUM DOUBLE;So;0;L;;;;;N;;;;; 1D9E7;SIGNWRITING MOVEMENT-FLOORPLANE ARM CIRCLE HITTING WALL SMALL SINGLE;So;0;L;;;;;N;;;;; 1D9E8;SIGNWRITING MOVEMENT-FLOORPLANE ARM CIRCLE HITTING WALL MEDIUM SINGLE;So;0;L;;;;;N;;;;; 1D9E9;SIGNWRITING MOVEMENT-FLOORPLANE ARM CIRCLE HITTING WALL LARGE SINGLE;So;0;L;;;;;N;;;;; 1D9EA;SIGNWRITING MOVEMENT-FLOORPLANE ARM CIRCLE HITTING WALL SMALL DOUBLE;So;0;L;;;;;N;;;;; 1D9EB;SIGNWRITING MOVEMENT-FLOORPLANE ARM CIRCLE HITTING WALL MEDIUM DOUBLE;So;0;L;;;;;N;;;;; 1D9EC;SIGNWRITING MOVEMENT-FLOORPLANE ARM CIRCLE HITTING WALL LARGE DOUBLE;So;0;L;;;;;N;;;;; 1D9ED;SIGNWRITING MOVEMENT-WALLPLANE WRIST CIRCLE FRONT SINGLE;So;0;L;;;;;N;;;;; 1D9EE;SIGNWRITING MOVEMENT-WALLPLANE WRIST CIRCLE FRONT DOUBLE;So;0;L;;;;;N;;;;; 1D9EF;SIGNWRITING MOVEMENT-FLOORPLANE WRIST CIRCLE HITTING WALL SINGLE;So;0;L;;;;;N;;;;; 1D9F0;SIGNWRITING MOVEMENT-FLOORPLANE WRIST CIRCLE HITTING WALL DOUBLE;So;0;L;;;;;N;;;;; 1D9F1;SIGNWRITING MOVEMENT-WALLPLANE FINGER CIRCLES SINGLE;So;0;L;;;;;N;;;;; 1D9F2;SIGNWRITING MOVEMENT-WALLPLANE FINGER CIRCLES DOUBLE;So;0;L;;;;;N;;;;; 1D9F3;SIGNWRITING MOVEMENT-FLOORPLANE FINGER CIRCLES HITTING WALL SINGLE;So;0;L;;;;;N;;;;; 1D9F4;SIGNWRITING MOVEMENT-FLOORPLANE FINGER CIRCLES HITTING WALL DOUBLE;So;0;L;;;;;N;;;;; 1D9F5;SIGNWRITING DYNAMIC ARROWHEAD SMALL;So;0;L;;;;;N;;;;; 1D9F6;SIGNWRITING DYNAMIC ARROWHEAD LARGE;So;0;L;;;;;N;;;;; 1D9F7;SIGNWRITING DYNAMIC FAST;So;0;L;;;;;N;;;;; 1D9F8;SIGNWRITING DYNAMIC SLOW;So;0;L;;;;;N;;;;; 1D9F9;SIGNWRITING DYNAMIC TENSE;So;0;L;;;;;N;;;;; 1D9FA;SIGNWRITING DYNAMIC RELAXED;So;0;L;;;;;N;;;;; 1D9FB;SIGNWRITING DYNAMIC SIMULTANEOUS;So;0;L;;;;;N;;;;; 1D9FC;SIGNWRITING DYNAMIC SIMULTANEOUS ALTERNATING;So;0;L;;;;;N;;;;; 1D9FD;SIGNWRITING DYNAMIC EVERY OTHER TIME;So;0;L;;;;;N;;;;; 1D9FE;SIGNWRITING DYNAMIC GRADUAL;So;0;L;;;;;N;;;;; 1D9FF;SIGNWRITING HEAD;So;0;L;;;;;N;;;;; 1DA00;SIGNWRITING HEAD RIM;Mn;0;NSM;;;;;N;;;;; 1DA01;SIGNWRITING HEAD MOVEMENT-WALLPLANE STRAIGHT;Mn;0;NSM;;;;;N;;;;; 1DA02;SIGNWRITING HEAD MOVEMENT-WALLPLANE TILT;Mn;0;NSM;;;;;N;;;;; 1DA03;SIGNWRITING HEAD MOVEMENT-FLOORPLANE STRAIGHT;Mn;0;NSM;;;;;N;;;;; 1DA04;SIGNWRITING HEAD MOVEMENT-WALLPLANE CURVE;Mn;0;NSM;;;;;N;;;;; 1DA05;SIGNWRITING HEAD MOVEMENT-FLOORPLANE CURVE;Mn;0;NSM;;;;;N;;;;; 1DA06;SIGNWRITING HEAD MOVEMENT CIRCLE;Mn;0;NSM;;;;;N;;;;; 1DA07;SIGNWRITING FACE DIRECTION POSITION NOSE FORWARD TILTING;Mn;0;NSM;;;;;N;;;;; 1DA08;SIGNWRITING FACE DIRECTION POSITION NOSE UP OR DOWN;Mn;0;NSM;;;;;N;;;;; 1DA09;SIGNWRITING FACE DIRECTION POSITION NOSE UP OR DOWN TILTING;Mn;0;NSM;;;;;N;;;;; 1DA0A;SIGNWRITING EYEBROWS STRAIGHT UP;Mn;0;NSM;;;;;N;;;;; 1DA0B;SIGNWRITING EYEBROWS STRAIGHT NEUTRAL;Mn;0;NSM;;;;;N;;;;; 1DA0C;SIGNWRITING EYEBROWS STRAIGHT DOWN;Mn;0;NSM;;;;;N;;;;; 1DA0D;SIGNWRITING DREAMY EYEBROWS NEUTRAL DOWN;Mn;0;NSM;;;;;N;;;;; 1DA0E;SIGNWRITING DREAMY EYEBROWS DOWN NEUTRAL;Mn;0;NSM;;;;;N;;;;; 1DA0F;SIGNWRITING DREAMY EYEBROWS UP NEUTRAL;Mn;0;NSM;;;;;N;;;;; 1DA10;SIGNWRITING DREAMY EYEBROWS NEUTRAL UP;Mn;0;NSM;;;;;N;;;;; 1DA11;SIGNWRITING FOREHEAD NEUTRAL;Mn;0;NSM;;;;;N;;;;; 1DA12;SIGNWRITING FOREHEAD CONTACT;Mn;0;NSM;;;;;N;;;;; 1DA13;SIGNWRITING FOREHEAD WRINKLED;Mn;0;NSM;;;;;N;;;;; 1DA14;SIGNWRITING EYES OPEN;Mn;0;NSM;;;;;N;;;;; 1DA15;SIGNWRITING EYES SQUEEZED;Mn;0;NSM;;;;;N;;;;; 1DA16;SIGNWRITING EYES CLOSED;Mn;0;NSM;;;;;N;;;;; 1DA17;SIGNWRITING EYE BLINK SINGLE;Mn;0;NSM;;;;;N;;;;; 1DA18;SIGNWRITING EYE BLINK MULTIPLE;Mn;0;NSM;;;;;N;;;;; 1DA19;SIGNWRITING EYES HALF OPEN;Mn;0;NSM;;;;;N;;;;; 1DA1A;SIGNWRITING EYES WIDE OPEN;Mn;0;NSM;;;;;N;;;;; 1DA1B;SIGNWRITING EYES HALF CLOSED;Mn;0;NSM;;;;;N;;;;; 1DA1C;SIGNWRITING EYES WIDENING MOVEMENT;Mn;0;NSM;;;;;N;;;;; 1DA1D;SIGNWRITING EYE WINK;Mn;0;NSM;;;;;N;;;;; 1DA1E;SIGNWRITING EYELASHES UP;Mn;0;NSM;;;;;N;;;;; 1DA1F;SIGNWRITING EYELASHES DOWN;Mn;0;NSM;;;;;N;;;;; 1DA20;SIGNWRITING EYELASHES FLUTTERING;Mn;0;NSM;;;;;N;;;;; 1DA21;SIGNWRITING EYEGAZE-WALLPLANE STRAIGHT;Mn;0;NSM;;;;;N;;;;; 1DA22;SIGNWRITING EYEGAZE-WALLPLANE STRAIGHT DOUBLE;Mn;0;NSM;;;;;N;;;;; 1DA23;SIGNWRITING EYEGAZE-WALLPLANE STRAIGHT ALTERNATING;Mn;0;NSM;;;;;N;;;;; 1DA24;SIGNWRITING EYEGAZE-FLOORPLANE STRAIGHT;Mn;0;NSM;;;;;N;;;;; 1DA25;SIGNWRITING EYEGAZE-FLOORPLANE STRAIGHT DOUBLE;Mn;0;NSM;;;;;N;;;;; 1DA26;SIGNWRITING EYEGAZE-FLOORPLANE STRAIGHT ALTERNATING;Mn;0;NSM;;;;;N;;;;; 1DA27;SIGNWRITING EYEGAZE-WALLPLANE CURVED;Mn;0;NSM;;;;;N;;;;; 1DA28;SIGNWRITING EYEGAZE-FLOORPLANE CURVED;Mn;0;NSM;;;;;N;;;;; 1DA29;SIGNWRITING EYEGAZE-WALLPLANE CIRCLING;Mn;0;NSM;;;;;N;;;;; 1DA2A;SIGNWRITING CHEEKS PUFFED;Mn;0;NSM;;;;;N;;;;; 1DA2B;SIGNWRITING CHEEKS NEUTRAL;Mn;0;NSM;;;;;N;;;;; 1DA2C;SIGNWRITING CHEEKS SUCKED;Mn;0;NSM;;;;;N;;;;; 1DA2D;SIGNWRITING TENSE CHEEKS HIGH;Mn;0;NSM;;;;;N;;;;; 1DA2E;SIGNWRITING TENSE CHEEKS MIDDLE;Mn;0;NSM;;;;;N;;;;; 1DA2F;SIGNWRITING TENSE CHEEKS LOW;Mn;0;NSM;;;;;N;;;;; 1DA30;SIGNWRITING EARS;Mn;0;NSM;;;;;N;;;;; 1DA31;SIGNWRITING NOSE NEUTRAL;Mn;0;NSM;;;;;N;;;;; 1DA32;SIGNWRITING NOSE CONTACT;Mn;0;NSM;;;;;N;;;;; 1DA33;SIGNWRITING NOSE WRINKLES;Mn;0;NSM;;;;;N;;;;; 1DA34;SIGNWRITING NOSE WIGGLES;Mn;0;NSM;;;;;N;;;;; 1DA35;SIGNWRITING AIR BLOWING OUT;Mn;0;NSM;;;;;N;;;;; 1DA36;SIGNWRITING AIR SUCKING IN;Mn;0;NSM;;;;;N;;;;; 1DA37;SIGNWRITING AIR BLOW SMALL ROTATIONS;So;0;L;;;;;N;;;;; 1DA38;SIGNWRITING AIR SUCK SMALL ROTATIONS;So;0;L;;;;;N;;;;; 1DA39;SIGNWRITING BREATH INHALE;So;0;L;;;;;N;;;;; 1DA3A;SIGNWRITING BREATH EXHALE;So;0;L;;;;;N;;;;; 1DA3B;SIGNWRITING MOUTH CLOSED NEUTRAL;Mn;0;NSM;;;;;N;;;;; 1DA3C;SIGNWRITING MOUTH CLOSED FORWARD;Mn;0;NSM;;;;;N;;;;; 1DA3D;SIGNWRITING MOUTH CLOSED CONTACT;Mn;0;NSM;;;;;N;;;;; 1DA3E;SIGNWRITING MOUTH SMILE;Mn;0;NSM;;;;;N;;;;; 1DA3F;SIGNWRITING MOUTH SMILE WRINKLED;Mn;0;NSM;;;;;N;;;;; 1DA40;SIGNWRITING MOUTH SMILE OPEN;Mn;0;NSM;;;;;N;;;;; 1DA41;SIGNWRITING MOUTH FROWN;Mn;0;NSM;;;;;N;;;;; 1DA42;SIGNWRITING MOUTH FROWN WRINKLED;Mn;0;NSM;;;;;N;;;;; 1DA43;SIGNWRITING MOUTH FROWN OPEN;Mn;0;NSM;;;;;N;;;;; 1DA44;SIGNWRITING MOUTH OPEN CIRCLE;Mn;0;NSM;;;;;N;;;;; 1DA45;SIGNWRITING MOUTH OPEN FORWARD;Mn;0;NSM;;;;;N;;;;; 1DA46;SIGNWRITING MOUTH OPEN WRINKLED;Mn;0;NSM;;;;;N;;;;; 1DA47;SIGNWRITING MOUTH OPEN OVAL;Mn;0;NSM;;;;;N;;;;; 1DA48;SIGNWRITING MOUTH OPEN OVAL WRINKLED;Mn;0;NSM;;;;;N;;;;; 1DA49;SIGNWRITING MOUTH OPEN OVAL YAWN;Mn;0;NSM;;;;;N;;;;; 1DA4A;SIGNWRITING MOUTH OPEN RECTANGLE;Mn;0;NSM;;;;;N;;;;; 1DA4B;SIGNWRITING MOUTH OPEN RECTANGLE WRINKLED;Mn;0;NSM;;;;;N;;;;; 1DA4C;SIGNWRITING MOUTH OPEN RECTANGLE YAWN;Mn;0;NSM;;;;;N;;;;; 1DA4D;SIGNWRITING MOUTH KISS;Mn;0;NSM;;;;;N;;;;; 1DA4E;SIGNWRITING MOUTH KISS FORWARD;Mn;0;NSM;;;;;N;;;;; 1DA4F;SIGNWRITING MOUTH KISS WRINKLED;Mn;0;NSM;;;;;N;;;;; 1DA50;SIGNWRITING MOUTH TENSE;Mn;0;NSM;;;;;N;;;;; 1DA51;SIGNWRITING MOUTH TENSE FORWARD;Mn;0;NSM;;;;;N;;;;; 1DA52;SIGNWRITING MOUTH TENSE SUCKED;Mn;0;NSM;;;;;N;;;;; 1DA53;SIGNWRITING LIPS PRESSED TOGETHER;Mn;0;NSM;;;;;N;;;;; 1DA54;SIGNWRITING LIP LOWER OVER UPPER;Mn;0;NSM;;;;;N;;;;; 1DA55;SIGNWRITING LIP UPPER OVER LOWER;Mn;0;NSM;;;;;N;;;;; 1DA56;SIGNWRITING MOUTH CORNERS;Mn;0;NSM;;;;;N;;;;; 1DA57;SIGNWRITING MOUTH WRINKLES SINGLE;Mn;0;NSM;;;;;N;;;;; 1DA58;SIGNWRITING MOUTH WRINKLES DOUBLE;Mn;0;NSM;;;;;N;;;;; 1DA59;SIGNWRITING TONGUE STICKING OUT FAR;Mn;0;NSM;;;;;N;;;;; 1DA5A;SIGNWRITING TONGUE LICKING LIPS;Mn;0;NSM;;;;;N;;;;; 1DA5B;SIGNWRITING TONGUE TIP BETWEEN LIPS;Mn;0;NSM;;;;;N;;;;; 1DA5C;SIGNWRITING TONGUE TIP TOUCHING INSIDE MOUTH;Mn;0;NSM;;;;;N;;;;; 1DA5D;SIGNWRITING TONGUE INSIDE MOUTH RELAXED;Mn;0;NSM;;;;;N;;;;; 1DA5E;SIGNWRITING TONGUE MOVES AGAINST CHEEK;Mn;0;NSM;;;;;N;;;;; 1DA5F;SIGNWRITING TONGUE CENTRE STICKING OUT;Mn;0;NSM;;;;;N;;;;; 1DA60;SIGNWRITING TONGUE CENTRE INSIDE MOUTH;Mn;0;NSM;;;;;N;;;;; 1DA61;SIGNWRITING TEETH;Mn;0;NSM;;;;;N;;;;; 1DA62;SIGNWRITING TEETH MOVEMENT;Mn;0;NSM;;;;;N;;;;; 1DA63;SIGNWRITING TEETH ON TONGUE;Mn;0;NSM;;;;;N;;;;; 1DA64;SIGNWRITING TEETH ON TONGUE MOVEMENT;Mn;0;NSM;;;;;N;;;;; 1DA65;SIGNWRITING TEETH ON LIPS;Mn;0;NSM;;;;;N;;;;; 1DA66;SIGNWRITING TEETH ON LIPS MOVEMENT;Mn;0;NSM;;;;;N;;;;; 1DA67;SIGNWRITING TEETH BITE LIPS;Mn;0;NSM;;;;;N;;;;; 1DA68;SIGNWRITING MOVEMENT-WALLPLANE JAW;Mn;0;NSM;;;;;N;;;;; 1DA69;SIGNWRITING MOVEMENT-FLOORPLANE JAW;Mn;0;NSM;;;;;N;;;;; 1DA6A;SIGNWRITING NECK;Mn;0;NSM;;;;;N;;;;; 1DA6B;SIGNWRITING HAIR;Mn;0;NSM;;;;;N;;;;; 1DA6C;SIGNWRITING EXCITEMENT;Mn;0;NSM;;;;;N;;;;; 1DA6D;SIGNWRITING SHOULDER HIP SPINE;So;0;L;;;;;N;;;;; 1DA6E;SIGNWRITING SHOULDER HIP POSITIONS;So;0;L;;;;;N;;;;; 1DA6F;SIGNWRITING WALLPLANE SHOULDER HIP MOVE;So;0;L;;;;;N;;;;; 1DA70;SIGNWRITING FLOORPLANE SHOULDER HIP MOVE;So;0;L;;;;;N;;;;; 1DA71;SIGNWRITING SHOULDER TILTING FROM WAIST;So;0;L;;;;;N;;;;; 1DA72;SIGNWRITING TORSO-WALLPLANE STRAIGHT STRETCH;So;0;L;;;;;N;;;;; 1DA73;SIGNWRITING TORSO-WALLPLANE CURVED BEND;So;0;L;;;;;N;;;;; 1DA74;SIGNWRITING TORSO-FLOORPLANE TWISTING;So;0;L;;;;;N;;;;; 1DA75;SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS;Mn;0;NSM;;;;;N;;;;; 1DA76;SIGNWRITING LIMB COMBINATION;So;0;L;;;;;N;;;;; 1DA77;SIGNWRITING LIMB LENGTH-1;So;0;L;;;;;N;;;;; 1DA78;SIGNWRITING LIMB LENGTH-2;So;0;L;;;;;N;;;;; 1DA79;SIGNWRITING LIMB LENGTH-3;So;0;L;;;;;N;;;;; 1DA7A;SIGNWRITING LIMB LENGTH-4;So;0;L;;;;;N;;;;; 1DA7B;SIGNWRITING LIMB LENGTH-5;So;0;L;;;;;N;;;;; 1DA7C;SIGNWRITING LIMB LENGTH-6;So;0;L;;;;;N;;;;; 1DA7D;SIGNWRITING LIMB LENGTH-7;So;0;L;;;;;N;;;;; 1DA7E;SIGNWRITING FINGER;So;0;L;;;;;N;;;;; 1DA7F;SIGNWRITING LOCATION-WALLPLANE SPACE;So;0;L;;;;;N;;;;; 1DA80;SIGNWRITING LOCATION-FLOORPLANE SPACE;So;0;L;;;;;N;;;;; 1DA81;SIGNWRITING LOCATION HEIGHT;So;0;L;;;;;N;;;;; 1DA82;SIGNWRITING LOCATION WIDTH;So;0;L;;;;;N;;;;; 1DA83;SIGNWRITING LOCATION DEPTH;So;0;L;;;;;N;;;;; 1DA84;SIGNWRITING LOCATION HEAD NECK;Mn;0;NSM;;;;;N;;;;; 1DA85;SIGNWRITING LOCATION TORSO;So;0;L;;;;;N;;;;; 1DA86;SIGNWRITING LOCATION LIMBS DIGITS;So;0;L;;;;;N;;;;; 1DA87;SIGNWRITING COMMA;Po;0;L;;;;;N;;;;; 1DA88;SIGNWRITING FULL STOP;Po;0;L;;;;;N;;;;; 1DA89;SIGNWRITING SEMICOLON;Po;0;L;;;;;N;;;;; 1DA8A;SIGNWRITING COLON;Po;0;L;;;;;N;;;;; 1DA8B;SIGNWRITING PARENTHESIS;Po;0;L;;;;;N;;;;; 1DA9B;SIGNWRITING FILL MODIFIER-2;Mn;0;NSM;;;;;N;;;;; 1DA9C;SIGNWRITING FILL MODIFIER-3;Mn;0;NSM;;;;;N;;;;; 1DA9D;SIGNWRITING FILL MODIFIER-4;Mn;0;NSM;;;;;N;;;;; 1DA9E;SIGNWRITING FILL MODIFIER-5;Mn;0;NSM;;;;;N;;;;; 1DA9F;SIGNWRITING FILL MODIFIER-6;Mn;0;NSM;;;;;N;;;;; 1DAA1;SIGNWRITING ROTATION MODIFIER-2;Mn;0;NSM;;;;;N;;;;; 1DAA2;SIGNWRITING ROTATION MODIFIER-3;Mn;0;NSM;;;;;N;;;;; 1DAA3;SIGNWRITING ROTATION MODIFIER-4;Mn;0;NSM;;;;;N;;;;; 1DAA4;SIGNWRITING ROTATION MODIFIER-5;Mn;0;NSM;;;;;N;;;;; 1DAA5;SIGNWRITING ROTATION MODIFIER-6;Mn;0;NSM;;;;;N;;;;; 1DAA6;SIGNWRITING ROTATION MODIFIER-7;Mn;0;NSM;;;;;N;;;;; 1DAA7;SIGNWRITING ROTATION MODIFIER-8;Mn;0;NSM;;;;;N;;;;; 1DAA8;SIGNWRITING ROTATION MODIFIER-9;Mn;0;NSM;;;;;N;;;;; 1DAA9;SIGNWRITING ROTATION MODIFIER-10;Mn;0;NSM;;;;;N;;;;; 1DAAA;SIGNWRITING ROTATION MODIFIER-11;Mn;0;NSM;;;;;N;;;;; 1DAAB;SIGNWRITING ROTATION MODIFIER-12;Mn;0;NSM;;;;;N;;;;; 1DAAC;SIGNWRITING ROTATION MODIFIER-13;Mn;0;NSM;;;;;N;;;;; 1DAAD;SIGNWRITING ROTATION MODIFIER-14;Mn;0;NSM;;;;;N;;;;; 1DAAE;SIGNWRITING ROTATION MODIFIER-15;Mn;0;NSM;;;;;N;;;;; 1DAAF;SIGNWRITING ROTATION MODIFIER-16;Mn;0;NSM;;;;;N;;;;; 1E000;COMBINING GLAGOLITIC LETTER AZU;Mn;230;NSM;;;;;N;;;;; 1E001;COMBINING GLAGOLITIC LETTER BUKY;Mn;230;NSM;;;;;N;;;;; 1E002;COMBINING GLAGOLITIC LETTER VEDE;Mn;230;NSM;;;;;N;;;;; 1E003;COMBINING GLAGOLITIC LETTER GLAGOLI;Mn;230;NSM;;;;;N;;;;; 1E004;COMBINING GLAGOLITIC LETTER DOBRO;Mn;230;NSM;;;;;N;;;;; 1E005;COMBINING GLAGOLITIC LETTER YESTU;Mn;230;NSM;;;;;N;;;;; 1E006;COMBINING GLAGOLITIC LETTER ZHIVETE;Mn;230;NSM;;;;;N;;;;; 1E008;COMBINING GLAGOLITIC LETTER ZEMLJA;Mn;230;NSM;;;;;N;;;;; 1E009;COMBINING GLAGOLITIC LETTER IZHE;Mn;230;NSM;;;;;N;;;;; 1E00A;COMBINING GLAGOLITIC LETTER INITIAL IZHE;Mn;230;NSM;;;;;N;;;;; 1E00B;COMBINING GLAGOLITIC LETTER I;Mn;230;NSM;;;;;N;;;;; 1E00C;COMBINING GLAGOLITIC LETTER DJERVI;Mn;230;NSM;;;;;N;;;;; 1E00D;COMBINING GLAGOLITIC LETTER KAKO;Mn;230;NSM;;;;;N;;;;; 1E00E;COMBINING GLAGOLITIC LETTER LJUDIJE;Mn;230;NSM;;;;;N;;;;; 1E00F;COMBINING GLAGOLITIC LETTER MYSLITE;Mn;230;NSM;;;;;N;;;;; 1E010;COMBINING GLAGOLITIC LETTER NASHI;Mn;230;NSM;;;;;N;;;;; 1E011;COMBINING GLAGOLITIC LETTER ONU;Mn;230;NSM;;;;;N;;;;; 1E012;COMBINING GLAGOLITIC LETTER POKOJI;Mn;230;NSM;;;;;N;;;;; 1E013;COMBINING GLAGOLITIC LETTER RITSI;Mn;230;NSM;;;;;N;;;;; 1E014;COMBINING GLAGOLITIC LETTER SLOVO;Mn;230;NSM;;;;;N;;;;; 1E015;COMBINING GLAGOLITIC LETTER TVRIDO;Mn;230;NSM;;;;;N;;;;; 1E016;COMBINING GLAGOLITIC LETTER UKU;Mn;230;NSM;;;;;N;;;;; 1E017;COMBINING GLAGOLITIC LETTER FRITU;Mn;230;NSM;;;;;N;;;;; 1E018;COMBINING GLAGOLITIC LETTER HERU;Mn;230;NSM;;;;;N;;;;; 1E01B;COMBINING GLAGOLITIC LETTER SHTA;Mn;230;NSM;;;;;N;;;;; 1E01C;COMBINING GLAGOLITIC LETTER TSI;Mn;230;NSM;;;;;N;;;;; 1E01D;COMBINING GLAGOLITIC LETTER CHRIVI;Mn;230;NSM;;;;;N;;;;; 1E01E;COMBINING GLAGOLITIC LETTER SHA;Mn;230;NSM;;;;;N;;;;; 1E01F;COMBINING GLAGOLITIC LETTER YERU;Mn;230;NSM;;;;;N;;;;; 1E020;COMBINING GLAGOLITIC LETTER YERI;Mn;230;NSM;;;;;N;;;;; 1E021;COMBINING GLAGOLITIC LETTER YATI;Mn;230;NSM;;;;;N;;;;; 1E023;COMBINING GLAGOLITIC LETTER YU;Mn;230;NSM;;;;;N;;;;; 1E024;COMBINING GLAGOLITIC LETTER SMALL YUS;Mn;230;NSM;;;;;N;;;;; 1E026;COMBINING GLAGOLITIC LETTER YO;Mn;230;NSM;;;;;N;;;;; 1E027;COMBINING GLAGOLITIC LETTER IOTATED SMALL YUS;Mn;230;NSM;;;;;N;;;;; 1E028;COMBINING GLAGOLITIC LETTER BIG YUS;Mn;230;NSM;;;;;N;;;;; 1E029;COMBINING GLAGOLITIC LETTER IOTATED BIG YUS;Mn;230;NSM;;;;;N;;;;; 1E02A;COMBINING GLAGOLITIC LETTER FITA;Mn;230;NSM;;;;;N;;;;; 1E800;MENDE KIKAKUI SYLLABLE M001 KI;Lo;0;R;;;;;N;;;;; 1E801;MENDE KIKAKUI SYLLABLE M002 KA;Lo;0;R;;;;;N;;;;; 1E802;MENDE KIKAKUI SYLLABLE M003 KU;Lo;0;R;;;;;N;;;;; 1E803;MENDE KIKAKUI SYLLABLE M065 KEE;Lo;0;R;;;;;N;;;;; 1E804;MENDE KIKAKUI SYLLABLE M095 KE;Lo;0;R;;;;;N;;;;; 1E805;MENDE KIKAKUI SYLLABLE M076 KOO;Lo;0;R;;;;;N;;;;; 1E806;MENDE KIKAKUI SYLLABLE M048 KO;Lo;0;R;;;;;N;;;;; 1E807;MENDE KIKAKUI SYLLABLE M179 KUA;Lo;0;R;;;;;N;;;;; 1E808;MENDE KIKAKUI SYLLABLE M004 WI;Lo;0;R;;;;;N;;;;; 1E809;MENDE KIKAKUI SYLLABLE M005 WA;Lo;0;R;;;;;N;;;;; 1E80A;MENDE KIKAKUI SYLLABLE M006 WU;Lo;0;R;;;;;N;;;;; 1E80B;MENDE KIKAKUI SYLLABLE M126 WEE;Lo;0;R;;;;;N;;;;; 1E80C;MENDE KIKAKUI SYLLABLE M118 WE;Lo;0;R;;;;;N;;;;; 1E80D;MENDE KIKAKUI SYLLABLE M114 WOO;Lo;0;R;;;;;N;;;;; 1E80E;MENDE KIKAKUI SYLLABLE M045 WO;Lo;0;R;;;;;N;;;;; 1E80F;MENDE KIKAKUI SYLLABLE M194 WUI;Lo;0;R;;;;;N;;;;; 1E810;MENDE KIKAKUI SYLLABLE M143 WEI;Lo;0;R;;;;;N;;;;; 1E811;MENDE KIKAKUI SYLLABLE M061 WVI;Lo;0;R;;;;;N;;;;; 1E812;MENDE KIKAKUI SYLLABLE M049 WVA;Lo;0;R;;;;;N;;;;; 1E813;MENDE KIKAKUI SYLLABLE M139 WVE;Lo;0;R;;;;;N;;;;; 1E814;MENDE KIKAKUI SYLLABLE M007 MIN;Lo;0;R;;;;;N;;;;; 1E815;MENDE KIKAKUI SYLLABLE M008 MAN;Lo;0;R;;;;;N;;;;; 1E816;MENDE KIKAKUI SYLLABLE M009 MUN;Lo;0;R;;;;;N;;;;; 1E817;MENDE KIKAKUI SYLLABLE M059 MEN;Lo;0;R;;;;;N;;;;; 1E818;MENDE KIKAKUI SYLLABLE M094 MON;Lo;0;R;;;;;N;;;;; 1E819;MENDE KIKAKUI SYLLABLE M154 MUAN;Lo;0;R;;;;;N;;;;; 1E81A;MENDE KIKAKUI SYLLABLE M189 MUEN;Lo;0;R;;;;;N;;;;; 1E81B;MENDE KIKAKUI SYLLABLE M010 BI;Lo;0;R;;;;;N;;;;; 1E81C;MENDE KIKAKUI SYLLABLE M011 BA;Lo;0;R;;;;;N;;;;; 1E81D;MENDE KIKAKUI SYLLABLE M012 BU;Lo;0;R;;;;;N;;;;; 1E81E;MENDE KIKAKUI SYLLABLE M150 BEE;Lo;0;R;;;;;N;;;;; 1E81F;MENDE KIKAKUI SYLLABLE M097 BE;Lo;0;R;;;;;N;;;;; 1E820;MENDE KIKAKUI SYLLABLE M103 BOO;Lo;0;R;;;;;N;;;;; 1E821;MENDE KIKAKUI SYLLABLE M138 BO;Lo;0;R;;;;;N;;;;; 1E822;MENDE KIKAKUI SYLLABLE M013 I;Lo;0;R;;;;;N;;;;; 1E823;MENDE KIKAKUI SYLLABLE M014 A;Lo;0;R;;;;;N;;;;; 1E824;MENDE KIKAKUI SYLLABLE M015 U;Lo;0;R;;;;;N;;;;; 1E825;MENDE KIKAKUI SYLLABLE M163 EE;Lo;0;R;;;;;N;;;;; 1E826;MENDE KIKAKUI SYLLABLE M100 E;Lo;0;R;;;;;N;;;;; 1E827;MENDE KIKAKUI SYLLABLE M165 OO;Lo;0;R;;;;;N;;;;; 1E828;MENDE KIKAKUI SYLLABLE M147 O;Lo;0;R;;;;;N;;;;; 1E829;MENDE KIKAKUI SYLLABLE M137 EI;Lo;0;R;;;;;N;;;;; 1E82A;MENDE KIKAKUI SYLLABLE M131 IN;Lo;0;R;;;;;N;;;;; 1E82B;MENDE KIKAKUI SYLLABLE M135 IN;Lo;0;R;;;;;N;;;;; 1E82C;MENDE KIKAKUI SYLLABLE M195 AN;Lo;0;R;;;;;N;;;;; 1E82D;MENDE KIKAKUI SYLLABLE M178 EN;Lo;0;R;;;;;N;;;;; 1E82E;MENDE KIKAKUI SYLLABLE M019 SI;Lo;0;R;;;;;N;;;;; 1E82F;MENDE KIKAKUI SYLLABLE M020 SA;Lo;0;R;;;;;N;;;;; 1E830;MENDE KIKAKUI SYLLABLE M021 SU;Lo;0;R;;;;;N;;;;; 1E831;MENDE KIKAKUI SYLLABLE M162 SEE;Lo;0;R;;;;;N;;;;; 1E832;MENDE KIKAKUI SYLLABLE M116 SE;Lo;0;R;;;;;N;;;;; 1E833;MENDE KIKAKUI SYLLABLE M136 SOO;Lo;0;R;;;;;N;;;;; 1E834;MENDE KIKAKUI SYLLABLE M079 SO;Lo;0;R;;;;;N;;;;; 1E835;MENDE KIKAKUI SYLLABLE M196 SIA;Lo;0;R;;;;;N;;;;; 1E836;MENDE KIKAKUI SYLLABLE M025 LI;Lo;0;R;;;;;N;;;;; 1E837;MENDE KIKAKUI SYLLABLE M026 LA;Lo;0;R;;;;;N;;;;; 1E838;MENDE KIKAKUI SYLLABLE M027 LU;Lo;0;R;;;;;N;;;;; 1E839;MENDE KIKAKUI SYLLABLE M084 LEE;Lo;0;R;;;;;N;;;;; 1E83A;MENDE KIKAKUI SYLLABLE M073 LE;Lo;0;R;;;;;N;;;;; 1E83B;MENDE KIKAKUI SYLLABLE M054 LOO;Lo;0;R;;;;;N;;;;; 1E83C;MENDE KIKAKUI SYLLABLE M153 LO;Lo;0;R;;;;;N;;;;; 1E83D;MENDE KIKAKUI SYLLABLE M110 LONG LE;Lo;0;R;;;;;N;;;;; 1E83E;MENDE KIKAKUI SYLLABLE M016 DI;Lo;0;R;;;;;N;;;;; 1E83F;MENDE KIKAKUI SYLLABLE M017 DA;Lo;0;R;;;;;N;;;;; 1E840;MENDE KIKAKUI SYLLABLE M018 DU;Lo;0;R;;;;;N;;;;; 1E841;MENDE KIKAKUI SYLLABLE M089 DEE;Lo;0;R;;;;;N;;;;; 1E842;MENDE KIKAKUI SYLLABLE M180 DOO;Lo;0;R;;;;;N;;;;; 1E843;MENDE KIKAKUI SYLLABLE M181 DO;Lo;0;R;;;;;N;;;;; 1E844;MENDE KIKAKUI SYLLABLE M022 TI;Lo;0;R;;;;;N;;;;; 1E845;MENDE KIKAKUI SYLLABLE M023 TA;Lo;0;R;;;;;N;;;;; 1E846;MENDE KIKAKUI SYLLABLE M024 TU;Lo;0;R;;;;;N;;;;; 1E847;MENDE KIKAKUI SYLLABLE M091 TEE;Lo;0;R;;;;;N;;;;; 1E848;MENDE KIKAKUI SYLLABLE M055 TE;Lo;0;R;;;;;N;;;;; 1E849;MENDE KIKAKUI SYLLABLE M104 TOO;Lo;0;R;;;;;N;;;;; 1E84A;MENDE KIKAKUI SYLLABLE M069 TO;Lo;0;R;;;;;N;;;;; 1E84B;MENDE KIKAKUI SYLLABLE M028 JI;Lo;0;R;;;;;N;;;;; 1E84C;MENDE KIKAKUI SYLLABLE M029 JA;Lo;0;R;;;;;N;;;;; 1E84D;MENDE KIKAKUI SYLLABLE M030 JU;Lo;0;R;;;;;N;;;;; 1E84E;MENDE KIKAKUI SYLLABLE M157 JEE;Lo;0;R;;;;;N;;;;; 1E84F;MENDE KIKAKUI SYLLABLE M113 JE;Lo;0;R;;;;;N;;;;; 1E850;MENDE KIKAKUI SYLLABLE M160 JOO;Lo;0;R;;;;;N;;;;; 1E851;MENDE KIKAKUI SYLLABLE M063 JO;Lo;0;R;;;;;N;;;;; 1E852;MENDE KIKAKUI SYLLABLE M175 LONG JO;Lo;0;R;;;;;N;;;;; 1E853;MENDE KIKAKUI SYLLABLE M031 YI;Lo;0;R;;;;;N;;;;; 1E854;MENDE KIKAKUI SYLLABLE M032 YA;Lo;0;R;;;;;N;;;;; 1E855;MENDE KIKAKUI SYLLABLE M033 YU;Lo;0;R;;;;;N;;;;; 1E856;MENDE KIKAKUI SYLLABLE M109 YEE;Lo;0;R;;;;;N;;;;; 1E857;MENDE KIKAKUI SYLLABLE M080 YE;Lo;0;R;;;;;N;;;;; 1E858;MENDE KIKAKUI SYLLABLE M141 YOO;Lo;0;R;;;;;N;;;;; 1E859;MENDE KIKAKUI SYLLABLE M121 YO;Lo;0;R;;;;;N;;;;; 1E85A;MENDE KIKAKUI SYLLABLE M034 FI;Lo;0;R;;;;;N;;;;; 1E85B;MENDE KIKAKUI SYLLABLE M035 FA;Lo;0;R;;;;;N;;;;; 1E85C;MENDE KIKAKUI SYLLABLE M036 FU;Lo;0;R;;;;;N;;;;; 1E85D;MENDE KIKAKUI SYLLABLE M078 FEE;Lo;0;R;;;;;N;;;;; 1E85E;MENDE KIKAKUI SYLLABLE M075 FE;Lo;0;R;;;;;N;;;;; 1E85F;MENDE KIKAKUI SYLLABLE M133 FOO;Lo;0;R;;;;;N;;;;; 1E860;MENDE KIKAKUI SYLLABLE M088 FO;Lo;0;R;;;;;N;;;;; 1E861;MENDE KIKAKUI SYLLABLE M197 FUA;Lo;0;R;;;;;N;;;;; 1E862;MENDE KIKAKUI SYLLABLE M101 FAN;Lo;0;R;;;;;N;;;;; 1E863;MENDE KIKAKUI SYLLABLE M037 NIN;Lo;0;R;;;;;N;;;;; 1E864;MENDE KIKAKUI SYLLABLE M038 NAN;Lo;0;R;;;;;N;;;;; 1E865;MENDE KIKAKUI SYLLABLE M039 NUN;Lo;0;R;;;;;N;;;;; 1E866;MENDE KIKAKUI SYLLABLE M117 NEN;Lo;0;R;;;;;N;;;;; 1E867;MENDE KIKAKUI SYLLABLE M169 NON;Lo;0;R;;;;;N;;;;; 1E868;MENDE KIKAKUI SYLLABLE M176 HI;Lo;0;R;;;;;N;;;;; 1E869;MENDE KIKAKUI SYLLABLE M041 HA;Lo;0;R;;;;;N;;;;; 1E86A;MENDE KIKAKUI SYLLABLE M186 HU;Lo;0;R;;;;;N;;;;; 1E86B;MENDE KIKAKUI SYLLABLE M040 HEE;Lo;0;R;;;;;N;;;;; 1E86C;MENDE KIKAKUI SYLLABLE M096 HE;Lo;0;R;;;;;N;;;;; 1E86D;MENDE KIKAKUI SYLLABLE M042 HOO;Lo;0;R;;;;;N;;;;; 1E86E;MENDE KIKAKUI SYLLABLE M140 HO;Lo;0;R;;;;;N;;;;; 1E86F;MENDE KIKAKUI SYLLABLE M083 HEEI;Lo;0;R;;;;;N;;;;; 1E870;MENDE KIKAKUI SYLLABLE M128 HOOU;Lo;0;R;;;;;N;;;;; 1E871;MENDE KIKAKUI SYLLABLE M053 HIN;Lo;0;R;;;;;N;;;;; 1E872;MENDE KIKAKUI SYLLABLE M130 HAN;Lo;0;R;;;;;N;;;;; 1E873;MENDE KIKAKUI SYLLABLE M087 HUN;Lo;0;R;;;;;N;;;;; 1E874;MENDE KIKAKUI SYLLABLE M052 HEN;Lo;0;R;;;;;N;;;;; 1E875;MENDE KIKAKUI SYLLABLE M193 HON;Lo;0;R;;;;;N;;;;; 1E876;MENDE KIKAKUI SYLLABLE M046 HUAN;Lo;0;R;;;;;N;;;;; 1E877;MENDE KIKAKUI SYLLABLE M090 NGGI;Lo;0;R;;;;;N;;;;; 1E878;MENDE KIKAKUI SYLLABLE M043 NGGA;Lo;0;R;;;;;N;;;;; 1E879;MENDE KIKAKUI SYLLABLE M082 NGGU;Lo;0;R;;;;;N;;;;; 1E87A;MENDE KIKAKUI SYLLABLE M115 NGGEE;Lo;0;R;;;;;N;;;;; 1E87B;MENDE KIKAKUI SYLLABLE M146 NGGE;Lo;0;R;;;;;N;;;;; 1E87C;MENDE KIKAKUI SYLLABLE M156 NGGOO;Lo;0;R;;;;;N;;;;; 1E87D;MENDE KIKAKUI SYLLABLE M120 NGGO;Lo;0;R;;;;;N;;;;; 1E87E;MENDE KIKAKUI SYLLABLE M159 NGGAA;Lo;0;R;;;;;N;;;;; 1E87F;MENDE KIKAKUI SYLLABLE M127 NGGUA;Lo;0;R;;;;;N;;;;; 1E880;MENDE KIKAKUI SYLLABLE M086 LONG NGGE;Lo;0;R;;;;;N;;;;; 1E881;MENDE KIKAKUI SYLLABLE M106 LONG NGGOO;Lo;0;R;;;;;N;;;;; 1E882;MENDE KIKAKUI SYLLABLE M183 LONG NGGO;Lo;0;R;;;;;N;;;;; 1E883;MENDE KIKAKUI SYLLABLE M155 GI;Lo;0;R;;;;;N;;;;; 1E884;MENDE KIKAKUI SYLLABLE M111 GA;Lo;0;R;;;;;N;;;;; 1E885;MENDE KIKAKUI SYLLABLE M168 GU;Lo;0;R;;;;;N;;;;; 1E886;MENDE KIKAKUI SYLLABLE M190 GEE;Lo;0;R;;;;;N;;;;; 1E887;MENDE KIKAKUI SYLLABLE M166 GUEI;Lo;0;R;;;;;N;;;;; 1E888;MENDE KIKAKUI SYLLABLE M167 GUAN;Lo;0;R;;;;;N;;;;; 1E889;MENDE KIKAKUI SYLLABLE M184 NGEN;Lo;0;R;;;;;N;;;;; 1E88A;MENDE KIKAKUI SYLLABLE M057 NGON;Lo;0;R;;;;;N;;;;; 1E88B;MENDE KIKAKUI SYLLABLE M177 NGUAN;Lo;0;R;;;;;N;;;;; 1E88C;MENDE KIKAKUI SYLLABLE M068 PI;Lo;0;R;;;;;N;;;;; 1E88D;MENDE KIKAKUI SYLLABLE M099 PA;Lo;0;R;;;;;N;;;;; 1E88E;MENDE KIKAKUI SYLLABLE M050 PU;Lo;0;R;;;;;N;;;;; 1E88F;MENDE KIKAKUI SYLLABLE M081 PEE;Lo;0;R;;;;;N;;;;; 1E890;MENDE KIKAKUI SYLLABLE M051 PE;Lo;0;R;;;;;N;;;;; 1E891;MENDE KIKAKUI SYLLABLE M102 POO;Lo;0;R;;;;;N;;;;; 1E892;MENDE KIKAKUI SYLLABLE M066 PO;Lo;0;R;;;;;N;;;;; 1E893;MENDE KIKAKUI SYLLABLE M145 MBI;Lo;0;R;;;;;N;;;;; 1E894;MENDE KIKAKUI SYLLABLE M062 MBA;Lo;0;R;;;;;N;;;;; 1E895;MENDE KIKAKUI SYLLABLE M122 MBU;Lo;0;R;;;;;N;;;;; 1E896;MENDE KIKAKUI SYLLABLE M047 MBEE;Lo;0;R;;;;;N;;;;; 1E897;MENDE KIKAKUI SYLLABLE M188 MBEE;Lo;0;R;;;;;N;;;;; 1E898;MENDE KIKAKUI SYLLABLE M072 MBE;Lo;0;R;;;;;N;;;;; 1E899;MENDE KIKAKUI SYLLABLE M172 MBOO;Lo;0;R;;;;;N;;;;; 1E89A;MENDE KIKAKUI SYLLABLE M174 MBO;Lo;0;R;;;;;N;;;;; 1E89B;MENDE KIKAKUI SYLLABLE M187 MBUU;Lo;0;R;;;;;N;;;;; 1E89C;MENDE KIKAKUI SYLLABLE M161 LONG MBE;Lo;0;R;;;;;N;;;;; 1E89D;MENDE KIKAKUI SYLLABLE M105 LONG MBOO;Lo;0;R;;;;;N;;;;; 1E89E;MENDE KIKAKUI SYLLABLE M142 LONG MBO;Lo;0;R;;;;;N;;;;; 1E89F;MENDE KIKAKUI SYLLABLE M132 KPI;Lo;0;R;;;;;N;;;;; 1E8A0;MENDE KIKAKUI SYLLABLE M092 KPA;Lo;0;R;;;;;N;;;;; 1E8A1;MENDE KIKAKUI SYLLABLE M074 KPU;Lo;0;R;;;;;N;;;;; 1E8A2;MENDE KIKAKUI SYLLABLE M044 KPEE;Lo;0;R;;;;;N;;;;; 1E8A3;MENDE KIKAKUI SYLLABLE M108 KPE;Lo;0;R;;;;;N;;;;; 1E8A4;MENDE KIKAKUI SYLLABLE M112 KPOO;Lo;0;R;;;;;N;;;;; 1E8A5;MENDE KIKAKUI SYLLABLE M158 KPO;Lo;0;R;;;;;N;;;;; 1E8A6;MENDE KIKAKUI SYLLABLE M124 GBI;Lo;0;R;;;;;N;;;;; 1E8A7;MENDE KIKAKUI SYLLABLE M056 GBA;Lo;0;R;;;;;N;;;;; 1E8A8;MENDE KIKAKUI SYLLABLE M148 GBU;Lo;0;R;;;;;N;;;;; 1E8A9;MENDE KIKAKUI SYLLABLE M093 GBEE;Lo;0;R;;;;;N;;;;; 1E8AA;MENDE KIKAKUI SYLLABLE M107 GBE;Lo;0;R;;;;;N;;;;; 1E8AB;MENDE KIKAKUI SYLLABLE M071 GBOO;Lo;0;R;;;;;N;;;;; 1E8AC;MENDE KIKAKUI SYLLABLE M070 GBO;Lo;0;R;;;;;N;;;;; 1E8AD;MENDE KIKAKUI SYLLABLE M171 RA;Lo;0;R;;;;;N;;;;; 1E8AE;MENDE KIKAKUI SYLLABLE M123 NDI;Lo;0;R;;;;;N;;;;; 1E8AF;MENDE KIKAKUI SYLLABLE M129 NDA;Lo;0;R;;;;;N;;;;; 1E8B0;MENDE KIKAKUI SYLLABLE M125 NDU;Lo;0;R;;;;;N;;;;; 1E8B1;MENDE KIKAKUI SYLLABLE M191 NDEE;Lo;0;R;;;;;N;;;;; 1E8B2;MENDE KIKAKUI SYLLABLE M119 NDE;Lo;0;R;;;;;N;;;;; 1E8B3;MENDE KIKAKUI SYLLABLE M067 NDOO;Lo;0;R;;;;;N;;;;; 1E8B4;MENDE KIKAKUI SYLLABLE M064 NDO;Lo;0;R;;;;;N;;;;; 1E8B5;MENDE KIKAKUI SYLLABLE M152 NJA;Lo;0;R;;;;;N;;;;; 1E8B6;MENDE KIKAKUI SYLLABLE M192 NJU;Lo;0;R;;;;;N;;;;; 1E8B7;MENDE KIKAKUI SYLLABLE M149 NJEE;Lo;0;R;;;;;N;;;;; 1E8B8;MENDE KIKAKUI SYLLABLE M134 NJOO;Lo;0;R;;;;;N;;;;; 1E8B9;MENDE KIKAKUI SYLLABLE M182 VI;Lo;0;R;;;;;N;;;;; 1E8BA;MENDE KIKAKUI SYLLABLE M185 VA;Lo;0;R;;;;;N;;;;; 1E8BB;MENDE KIKAKUI SYLLABLE M151 VU;Lo;0;R;;;;;N;;;;; 1E8BC;MENDE KIKAKUI SYLLABLE M173 VEE;Lo;0;R;;;;;N;;;;; 1E8BD;MENDE KIKAKUI SYLLABLE M085 VE;Lo;0;R;;;;;N;;;;; 1E8BE;MENDE KIKAKUI SYLLABLE M144 VOO;Lo;0;R;;;;;N;;;;; 1E8BF;MENDE KIKAKUI SYLLABLE M077 VO;Lo;0;R;;;;;N;;;;; 1E8C0;MENDE KIKAKUI SYLLABLE M164 NYIN;Lo;0;R;;;;;N;;;;; 1E8C1;MENDE KIKAKUI SYLLABLE M058 NYAN;Lo;0;R;;;;;N;;;;; 1E8C2;MENDE KIKAKUI SYLLABLE M170 NYUN;Lo;0;R;;;;;N;;;;; 1E8C3;MENDE KIKAKUI SYLLABLE M098 NYEN;Lo;0;R;;;;;N;;;;; 1E8C4;MENDE KIKAKUI SYLLABLE M060 NYON;Lo;0;R;;;;;N;;;;; 1E8C7;MENDE KIKAKUI DIGIT ONE;No;0;R;;;;1;N;;;;; 1E8C8;MENDE KIKAKUI DIGIT TWO;No;0;R;;;;2;N;;;;; 1E8C9;MENDE KIKAKUI DIGIT THREE;No;0;R;;;;3;N;;;;; 1E8CA;MENDE KIKAKUI DIGIT FOUR;No;0;R;;;;4;N;;;;; 1E8CB;MENDE KIKAKUI DIGIT FIVE;No;0;R;;;;5;N;;;;; 1E8CC;MENDE KIKAKUI DIGIT SIX;No;0;R;;;;6;N;;;;; 1E8CD;MENDE KIKAKUI DIGIT SEVEN;No;0;R;;;;7;N;;;;; 1E8CE;MENDE KIKAKUI DIGIT EIGHT;No;0;R;;;;8;N;;;;; 1E8CF;MENDE KIKAKUI DIGIT NINE;No;0;R;;;;9;N;;;;; 1E8D0;MENDE KIKAKUI COMBINING NUMBER TEENS;Mn;220;NSM;;;;;N;;;;; 1E8D1;MENDE KIKAKUI COMBINING NUMBER TENS;Mn;220;NSM;;;;;N;;;;; 1E8D2;MENDE KIKAKUI COMBINING NUMBER HUNDREDS;Mn;220;NSM;;;;;N;;;;; 1E8D3;MENDE KIKAKUI COMBINING NUMBER THOUSANDS;Mn;220;NSM;;;;;N;;;;; 1E8D4;MENDE KIKAKUI COMBINING NUMBER TEN THOUSANDS;Mn;220;NSM;;;;;N;;;;; 1E8D5;MENDE KIKAKUI COMBINING NUMBER HUNDRED THOUSANDS;Mn;220;NSM;;;;;N;;;;; 1E8D6;MENDE KIKAKUI COMBINING NUMBER MILLIONS;Mn;220;NSM;;;;;N;;;;; 1E900;ADLAM CAPITAL LETTER ALIF;Lu;0;R;;;;;N;;;;1E922; 1E901;ADLAM CAPITAL LETTER DAALI;Lu;0;R;;;;;N;;;;1E923; 1E902;ADLAM CAPITAL LETTER LAAM;Lu;0;R;;;;;N;;;;1E924; 1E903;ADLAM CAPITAL LETTER MIIM;Lu;0;R;;;;;N;;;;1E925; 1E904;ADLAM CAPITAL LETTER BA;Lu;0;R;;;;;N;;;;1E926; 1E905;ADLAM CAPITAL LETTER SINNYIIYHE;Lu;0;R;;;;;N;;;;1E927; 1E906;ADLAM CAPITAL LETTER PE;Lu;0;R;;;;;N;;;;1E928; 1E907;ADLAM CAPITAL LETTER BHE;Lu;0;R;;;;;N;;;;1E929; 1E908;ADLAM CAPITAL LETTER RA;Lu;0;R;;;;;N;;;;1E92A; 1E909;ADLAM CAPITAL LETTER E;Lu;0;R;;;;;N;;;;1E92B; 1E90A;ADLAM CAPITAL LETTER FA;Lu;0;R;;;;;N;;;;1E92C; 1E90B;ADLAM CAPITAL LETTER I;Lu;0;R;;;;;N;;;;1E92D; 1E90C;ADLAM CAPITAL LETTER O;Lu;0;R;;;;;N;;;;1E92E; 1E90D;ADLAM CAPITAL LETTER DHA;Lu;0;R;;;;;N;;;;1E92F; 1E90E;ADLAM CAPITAL LETTER YHE;Lu;0;R;;;;;N;;;;1E930; 1E90F;ADLAM CAPITAL LETTER WAW;Lu;0;R;;;;;N;;;;1E931; 1E910;ADLAM CAPITAL LETTER NUN;Lu;0;R;;;;;N;;;;1E932; 1E911;ADLAM CAPITAL LETTER KAF;Lu;0;R;;;;;N;;;;1E933; 1E912;ADLAM CAPITAL LETTER YA;Lu;0;R;;;;;N;;;;1E934; 1E913;ADLAM CAPITAL LETTER U;Lu;0;R;;;;;N;;;;1E935; 1E914;ADLAM CAPITAL LETTER JIIM;Lu;0;R;;;;;N;;;;1E936; 1E915;ADLAM CAPITAL LETTER CHI;Lu;0;R;;;;;N;;;;1E937; 1E916;ADLAM CAPITAL LETTER HA;Lu;0;R;;;;;N;;;;1E938; 1E917;ADLAM CAPITAL LETTER QAAF;Lu;0;R;;;;;N;;;;1E939; 1E918;ADLAM CAPITAL LETTER GA;Lu;0;R;;;;;N;;;;1E93A; 1E919;ADLAM CAPITAL LETTER NYA;Lu;0;R;;;;;N;;;;1E93B; 1E91A;ADLAM CAPITAL LETTER TU;Lu;0;R;;;;;N;;;;1E93C; 1E91B;ADLAM CAPITAL LETTER NHA;Lu;0;R;;;;;N;;;;1E93D; 1E91C;ADLAM CAPITAL LETTER VA;Lu;0;R;;;;;N;;;;1E93E; 1E91D;ADLAM CAPITAL LETTER KHA;Lu;0;R;;;;;N;;;;1E93F; 1E91E;ADLAM CAPITAL LETTER GBE;Lu;0;R;;;;;N;;;;1E940; 1E91F;ADLAM CAPITAL LETTER ZAL;Lu;0;R;;;;;N;;;;1E941; 1E920;ADLAM CAPITAL LETTER KPO;Lu;0;R;;;;;N;;;;1E942; 1E921;ADLAM CAPITAL LETTER SHA;Lu;0;R;;;;;N;;;;1E943; 1E922;ADLAM SMALL LETTER ALIF;Ll;0;R;;;;;N;;;1E900;;1E900 1E923;ADLAM SMALL LETTER DAALI;Ll;0;R;;;;;N;;;1E901;;1E901 1E924;ADLAM SMALL LETTER LAAM;Ll;0;R;;;;;N;;;1E902;;1E902 1E925;ADLAM SMALL LETTER MIIM;Ll;0;R;;;;;N;;;1E903;;1E903 1E926;ADLAM SMALL LETTER BA;Ll;0;R;;;;;N;;;1E904;;1E904 1E927;ADLAM SMALL LETTER SINNYIIYHE;Ll;0;R;;;;;N;;;1E905;;1E905 1E928;ADLAM SMALL LETTER PE;Ll;0;R;;;;;N;;;1E906;;1E906 1E929;ADLAM SMALL LETTER BHE;Ll;0;R;;;;;N;;;1E907;;1E907 1E92A;ADLAM SMALL LETTER RA;Ll;0;R;;;;;N;;;1E908;;1E908 1E92B;ADLAM SMALL LETTER E;Ll;0;R;;;;;N;;;1E909;;1E909 1E92C;ADLAM SMALL LETTER FA;Ll;0;R;;;;;N;;;1E90A;;1E90A 1E92D;ADLAM SMALL LETTER I;Ll;0;R;;;;;N;;;1E90B;;1E90B 1E92E;ADLAM SMALL LETTER O;Ll;0;R;;;;;N;;;1E90C;;1E90C 1E92F;ADLAM SMALL LETTER DHA;Ll;0;R;;;;;N;;;1E90D;;1E90D 1E930;ADLAM SMALL LETTER YHE;Ll;0;R;;;;;N;;;1E90E;;1E90E 1E931;ADLAM SMALL LETTER WAW;Ll;0;R;;;;;N;;;1E90F;;1E90F 1E932;ADLAM SMALL LETTER NUN;Ll;0;R;;;;;N;;;1E910;;1E910 1E933;ADLAM SMALL LETTER KAF;Ll;0;R;;;;;N;;;1E911;;1E911 1E934;ADLAM SMALL LETTER YA;Ll;0;R;;;;;N;;;1E912;;1E912 1E935;ADLAM SMALL LETTER U;Ll;0;R;;;;;N;;;1E913;;1E913 1E936;ADLAM SMALL LETTER JIIM;Ll;0;R;;;;;N;;;1E914;;1E914 1E937;ADLAM SMALL LETTER CHI;Ll;0;R;;;;;N;;;1E915;;1E915 1E938;ADLAM SMALL LETTER HA;Ll;0;R;;;;;N;;;1E916;;1E916 1E939;ADLAM SMALL LETTER QAAF;Ll;0;R;;;;;N;;;1E917;;1E917 1E93A;ADLAM SMALL LETTER GA;Ll;0;R;;;;;N;;;1E918;;1E918 1E93B;ADLAM SMALL LETTER NYA;Ll;0;R;;;;;N;;;1E919;;1E919 1E93C;ADLAM SMALL LETTER TU;Ll;0;R;;;;;N;;;1E91A;;1E91A 1E93D;ADLAM SMALL LETTER NHA;Ll;0;R;;;;;N;;;1E91B;;1E91B 1E93E;ADLAM SMALL LETTER VA;Ll;0;R;;;;;N;;;1E91C;;1E91C 1E93F;ADLAM SMALL LETTER KHA;Ll;0;R;;;;;N;;;1E91D;;1E91D 1E940;ADLAM SMALL LETTER GBE;Ll;0;R;;;;;N;;;1E91E;;1E91E 1E941;ADLAM SMALL LETTER ZAL;Ll;0;R;;;;;N;;;1E91F;;1E91F 1E942;ADLAM SMALL LETTER KPO;Ll;0;R;;;;;N;;;1E920;;1E920 1E943;ADLAM SMALL LETTER SHA;Ll;0;R;;;;;N;;;1E921;;1E921 1E944;ADLAM ALIF LENGTHENER;Mn;230;NSM;;;;;N;;;;; 1E945;ADLAM VOWEL LENGTHENER;Mn;230;NSM;;;;;N;;;;; 1E946;ADLAM GEMINATION MARK;Mn;230;NSM;;;;;N;;;;; 1E947;ADLAM HAMZA;Mn;230;NSM;;;;;N;;;;; 1E948;ADLAM CONSONANT MODIFIER;Mn;230;NSM;;;;;N;;;;; 1E949;ADLAM GEMINATE CONSONANT MODIFIER;Mn;230;NSM;;;;;N;;;;; 1E94A;ADLAM NUKTA;Mn;7;NSM;;;;;N;;;;; 1E950;ADLAM DIGIT ZERO;Nd;0;R;;0;0;0;N;;;;; 1E951;ADLAM DIGIT ONE;Nd;0;R;;1;1;1;N;;;;; 1E952;ADLAM DIGIT TWO;Nd;0;R;;2;2;2;N;;;;; 1E953;ADLAM DIGIT THREE;Nd;0;R;;3;3;3;N;;;;; 1E954;ADLAM DIGIT FOUR;Nd;0;R;;4;4;4;N;;;;; 1E955;ADLAM DIGIT FIVE;Nd;0;R;;5;5;5;N;;;;; 1E956;ADLAM DIGIT SIX;Nd;0;R;;6;6;6;N;;;;; 1E957;ADLAM DIGIT SEVEN;Nd;0;R;;7;7;7;N;;;;; 1E958;ADLAM DIGIT EIGHT;Nd;0;R;;8;8;8;N;;;;; 1E959;ADLAM DIGIT NINE;Nd;0;R;;9;9;9;N;;;;; 1E95E;ADLAM INITIAL EXCLAMATION MARK;Po;0;R;;;;;N;;;;; 1E95F;ADLAM INITIAL QUESTION MARK;Po;0;R;;;;;N;;;;; 1EE00;ARABIC MATHEMATICAL ALEF;Lo;0;AL; 0627;;;;N;;;;; 1EE01;ARABIC MATHEMATICAL BEH;Lo;0;AL; 0628;;;;N;;;;; 1EE02;ARABIC MATHEMATICAL JEEM;Lo;0;AL; 062C;;;;N;;;;; 1EE03;ARABIC MATHEMATICAL DAL;Lo;0;AL; 062F;;;;N;;;;; 1EE05;ARABIC MATHEMATICAL WAW;Lo;0;AL; 0648;;;;N;;;;; 1EE06;ARABIC MATHEMATICAL ZAIN;Lo;0;AL; 0632;;;;N;;;;; 1EE07;ARABIC MATHEMATICAL HAH;Lo;0;AL; 062D;;;;N;;;;; 1EE08;ARABIC MATHEMATICAL TAH;Lo;0;AL; 0637;;;;N;;;;; 1EE09;ARABIC MATHEMATICAL YEH;Lo;0;AL; 064A;;;;N;;;;; 1EE0A;ARABIC MATHEMATICAL KAF;Lo;0;AL; 0643;;;;N;;;;; 1EE0B;ARABIC MATHEMATICAL LAM;Lo;0;AL; 0644;;;;N;;;;; 1EE0C;ARABIC MATHEMATICAL MEEM;Lo;0;AL; 0645;;;;N;;;;; 1EE0D;ARABIC MATHEMATICAL NOON;Lo;0;AL; 0646;;;;N;;;;; 1EE0E;ARABIC MATHEMATICAL SEEN;Lo;0;AL; 0633;;;;N;;;;; 1EE0F;ARABIC MATHEMATICAL AIN;Lo;0;AL; 0639;;;;N;;;;; 1EE10;ARABIC MATHEMATICAL FEH;Lo;0;AL; 0641;;;;N;;;;; 1EE11;ARABIC MATHEMATICAL SAD;Lo;0;AL; 0635;;;;N;;;;; 1EE12;ARABIC MATHEMATICAL QAF;Lo;0;AL; 0642;;;;N;;;;; 1EE13;ARABIC MATHEMATICAL REH;Lo;0;AL; 0631;;;;N;;;;; 1EE14;ARABIC MATHEMATICAL SHEEN;Lo;0;AL; 0634;;;;N;;;;; 1EE15;ARABIC MATHEMATICAL TEH;Lo;0;AL; 062A;;;;N;;;;; 1EE16;ARABIC MATHEMATICAL THEH;Lo;0;AL; 062B;;;;N;;;;; 1EE17;ARABIC MATHEMATICAL KHAH;Lo;0;AL; 062E;;;;N;;;;; 1EE18;ARABIC MATHEMATICAL THAL;Lo;0;AL; 0630;;;;N;;;;; 1EE19;ARABIC MATHEMATICAL DAD;Lo;0;AL; 0636;;;;N;;;;; 1EE1A;ARABIC MATHEMATICAL ZAH;Lo;0;AL; 0638;;;;N;;;;; 1EE1B;ARABIC MATHEMATICAL GHAIN;Lo;0;AL; 063A;;;;N;;;;; 1EE1C;ARABIC MATHEMATICAL DOTLESS BEH;Lo;0;AL; 066E;;;;N;;;;; 1EE1D;ARABIC MATHEMATICAL DOTLESS NOON;Lo;0;AL; 06BA;;;;N;;;;; 1EE1E;ARABIC MATHEMATICAL DOTLESS FEH;Lo;0;AL; 06A1;;;;N;;;;; 1EE1F;ARABIC MATHEMATICAL DOTLESS QAF;Lo;0;AL; 066F;;;;N;;;;; 1EE21;ARABIC MATHEMATICAL INITIAL BEH;Lo;0;AL; 0628;;;;N;;;;; 1EE22;ARABIC MATHEMATICAL INITIAL JEEM;Lo;0;AL; 062C;;;;N;;;;; 1EE24;ARABIC MATHEMATICAL INITIAL HEH;Lo;0;AL; 0647;;;;N;;;;; 1EE27;ARABIC MATHEMATICAL INITIAL HAH;Lo;0;AL; 062D;;;;N;;;;; 1EE29;ARABIC MATHEMATICAL INITIAL YEH;Lo;0;AL; 064A;;;;N;;;;; 1EE2A;ARABIC MATHEMATICAL INITIAL KAF;Lo;0;AL; 0643;;;;N;;;;; 1EE2B;ARABIC MATHEMATICAL INITIAL LAM;Lo;0;AL; 0644;;;;N;;;;; 1EE2C;ARABIC MATHEMATICAL INITIAL MEEM;Lo;0;AL; 0645;;;;N;;;;; 1EE2D;ARABIC MATHEMATICAL INITIAL NOON;Lo;0;AL; 0646;;;;N;;;;; 1EE2E;ARABIC MATHEMATICAL INITIAL SEEN;Lo;0;AL; 0633;;;;N;;;;; 1EE2F;ARABIC MATHEMATICAL INITIAL AIN;Lo;0;AL; 0639;;;;N;;;;; 1EE30;ARABIC MATHEMATICAL INITIAL FEH;Lo;0;AL; 0641;;;;N;;;;; 1EE31;ARABIC MATHEMATICAL INITIAL SAD;Lo;0;AL; 0635;;;;N;;;;; 1EE32;ARABIC MATHEMATICAL INITIAL QAF;Lo;0;AL; 0642;;;;N;;;;; 1EE34;ARABIC MATHEMATICAL INITIAL SHEEN;Lo;0;AL; 0634;;;;N;;;;; 1EE35;ARABIC MATHEMATICAL INITIAL TEH;Lo;0;AL; 062A;;;;N;;;;; 1EE36;ARABIC MATHEMATICAL INITIAL THEH;Lo;0;AL; 062B;;;;N;;;;; 1EE37;ARABIC MATHEMATICAL INITIAL KHAH;Lo;0;AL; 062E;;;;N;;;;; 1EE39;ARABIC MATHEMATICAL INITIAL DAD;Lo;0;AL; 0636;;;;N;;;;; 1EE3B;ARABIC MATHEMATICAL INITIAL GHAIN;Lo;0;AL; 063A;;;;N;;;;; 1EE42;ARABIC MATHEMATICAL TAILED JEEM;Lo;0;AL; 062C;;;;N;;;;; 1EE47;ARABIC MATHEMATICAL TAILED HAH;Lo;0;AL; 062D;;;;N;;;;; 1EE49;ARABIC MATHEMATICAL TAILED YEH;Lo;0;AL; 064A;;;;N;;;;; 1EE4B;ARABIC MATHEMATICAL TAILED LAM;Lo;0;AL; 0644;;;;N;;;;; 1EE4D;ARABIC MATHEMATICAL TAILED NOON;Lo;0;AL; 0646;;;;N;;;;; 1EE4E;ARABIC MATHEMATICAL TAILED SEEN;Lo;0;AL; 0633;;;;N;;;;; 1EE4F;ARABIC MATHEMATICAL TAILED AIN;Lo;0;AL; 0639;;;;N;;;;; 1EE51;ARABIC MATHEMATICAL TAILED SAD;Lo;0;AL; 0635;;;;N;;;;; 1EE52;ARABIC MATHEMATICAL TAILED QAF;Lo;0;AL; 0642;;;;N;;;;; 1EE54;ARABIC MATHEMATICAL TAILED SHEEN;Lo;0;AL; 0634;;;;N;;;;; 1EE57;ARABIC MATHEMATICAL TAILED KHAH;Lo;0;AL; 062E;;;;N;;;;; 1EE59;ARABIC MATHEMATICAL TAILED DAD;Lo;0;AL; 0636;;;;N;;;;; 1EE5B;ARABIC MATHEMATICAL TAILED GHAIN;Lo;0;AL; 063A;;;;N;;;;; 1EE5D;ARABIC MATHEMATICAL TAILED DOTLESS NOON;Lo;0;AL; 06BA;;;;N;;;;; 1EE5F;ARABIC MATHEMATICAL TAILED DOTLESS QAF;Lo;0;AL; 066F;;;;N;;;;; 1EE61;ARABIC MATHEMATICAL STRETCHED BEH;Lo;0;AL; 0628;;;;N;;;;; 1EE62;ARABIC MATHEMATICAL STRETCHED JEEM;Lo;0;AL; 062C;;;;N;;;;; 1EE64;ARABIC MATHEMATICAL STRETCHED HEH;Lo;0;AL; 0647;;;;N;;;;; 1EE67;ARABIC MATHEMATICAL STRETCHED HAH;Lo;0;AL; 062D;;;;N;;;;; 1EE68;ARABIC MATHEMATICAL STRETCHED TAH;Lo;0;AL; 0637;;;;N;;;;; 1EE69;ARABIC MATHEMATICAL STRETCHED YEH;Lo;0;AL; 064A;;;;N;;;;; 1EE6A;ARABIC MATHEMATICAL STRETCHED KAF;Lo;0;AL; 0643;;;;N;;;;; 1EE6C;ARABIC MATHEMATICAL STRETCHED MEEM;Lo;0;AL; 0645;;;;N;;;;; 1EE6D;ARABIC MATHEMATICAL STRETCHED NOON;Lo;0;AL; 0646;;;;N;;;;; 1EE6E;ARABIC MATHEMATICAL STRETCHED SEEN;Lo;0;AL; 0633;;;;N;;;;; 1EE6F;ARABIC MATHEMATICAL STRETCHED AIN;Lo;0;AL; 0639;;;;N;;;;; 1EE70;ARABIC MATHEMATICAL STRETCHED FEH;Lo;0;AL; 0641;;;;N;;;;; 1EE71;ARABIC MATHEMATICAL STRETCHED SAD;Lo;0;AL; 0635;;;;N;;;;; 1EE72;ARABIC MATHEMATICAL STRETCHED QAF;Lo;0;AL; 0642;;;;N;;;;; 1EE74;ARABIC MATHEMATICAL STRETCHED SHEEN;Lo;0;AL; 0634;;;;N;;;;; 1EE75;ARABIC MATHEMATICAL STRETCHED TEH;Lo;0;AL; 062A;;;;N;;;;; 1EE76;ARABIC MATHEMATICAL STRETCHED THEH;Lo;0;AL; 062B;;;;N;;;;; 1EE77;ARABIC MATHEMATICAL STRETCHED KHAH;Lo;0;AL; 062E;;;;N;;;;; 1EE79;ARABIC MATHEMATICAL STRETCHED DAD;Lo;0;AL; 0636;;;;N;;;;; 1EE7A;ARABIC MATHEMATICAL STRETCHED ZAH;Lo;0;AL; 0638;;;;N;;;;; 1EE7B;ARABIC MATHEMATICAL STRETCHED GHAIN;Lo;0;AL; 063A;;;;N;;;;; 1EE7C;ARABIC MATHEMATICAL STRETCHED DOTLESS BEH;Lo;0;AL; 066E;;;;N;;;;; 1EE7E;ARABIC MATHEMATICAL STRETCHED DOTLESS FEH;Lo;0;AL; 06A1;;;;N;;;;; 1EE80;ARABIC MATHEMATICAL LOOPED ALEF;Lo;0;AL; 0627;;;;N;;;;; 1EE81;ARABIC MATHEMATICAL LOOPED BEH;Lo;0;AL; 0628;;;;N;;;;; 1EE82;ARABIC MATHEMATICAL LOOPED JEEM;Lo;0;AL; 062C;;;;N;;;;; 1EE83;ARABIC MATHEMATICAL LOOPED DAL;Lo;0;AL; 062F;;;;N;;;;; 1EE84;ARABIC MATHEMATICAL LOOPED HEH;Lo;0;AL; 0647;;;;N;;;;; 1EE85;ARABIC MATHEMATICAL LOOPED WAW;Lo;0;AL; 0648;;;;N;;;;; 1EE86;ARABIC MATHEMATICAL LOOPED ZAIN;Lo;0;AL; 0632;;;;N;;;;; 1EE87;ARABIC MATHEMATICAL LOOPED HAH;Lo;0;AL; 062D;;;;N;;;;; 1EE88;ARABIC MATHEMATICAL LOOPED TAH;Lo;0;AL; 0637;;;;N;;;;; 1EE89;ARABIC MATHEMATICAL LOOPED YEH;Lo;0;AL; 064A;;;;N;;;;; 1EE8B;ARABIC MATHEMATICAL LOOPED LAM;Lo;0;AL; 0644;;;;N;;;;; 1EE8C;ARABIC MATHEMATICAL LOOPED MEEM;Lo;0;AL; 0645;;;;N;;;;; 1EE8D;ARABIC MATHEMATICAL LOOPED NOON;Lo;0;AL; 0646;;;;N;;;;; 1EE8E;ARABIC MATHEMATICAL LOOPED SEEN;Lo;0;AL; 0633;;;;N;;;;; 1EE8F;ARABIC MATHEMATICAL LOOPED AIN;Lo;0;AL; 0639;;;;N;;;;; 1EE90;ARABIC MATHEMATICAL LOOPED FEH;Lo;0;AL; 0641;;;;N;;;;; 1EE91;ARABIC MATHEMATICAL LOOPED SAD;Lo;0;AL; 0635;;;;N;;;;; 1EE92;ARABIC MATHEMATICAL LOOPED QAF;Lo;0;AL; 0642;;;;N;;;;; 1EE93;ARABIC MATHEMATICAL LOOPED REH;Lo;0;AL; 0631;;;;N;;;;; 1EE94;ARABIC MATHEMATICAL LOOPED SHEEN;Lo;0;AL; 0634;;;;N;;;;; 1EE95;ARABIC MATHEMATICAL LOOPED TEH;Lo;0;AL; 062A;;;;N;;;;; 1EE96;ARABIC MATHEMATICAL LOOPED THEH;Lo;0;AL; 062B;;;;N;;;;; 1EE97;ARABIC MATHEMATICAL LOOPED KHAH;Lo;0;AL; 062E;;;;N;;;;; 1EE98;ARABIC MATHEMATICAL LOOPED THAL;Lo;0;AL; 0630;;;;N;;;;; 1EE99;ARABIC MATHEMATICAL LOOPED DAD;Lo;0;AL; 0636;;;;N;;;;; 1EE9A;ARABIC MATHEMATICAL LOOPED ZAH;Lo;0;AL; 0638;;;;N;;;;; 1EE9B;ARABIC MATHEMATICAL LOOPED GHAIN;Lo;0;AL; 063A;;;;N;;;;; 1EEA1;ARABIC MATHEMATICAL DOUBLE-STRUCK BEH;Lo;0;AL; 0628;;;;N;;;;; 1EEA2;ARABIC MATHEMATICAL DOUBLE-STRUCK JEEM;Lo;0;AL; 062C;;;;N;;;;; 1EEA3;ARABIC MATHEMATICAL DOUBLE-STRUCK DAL;Lo;0;AL; 062F;;;;N;;;;; 1EEA5;ARABIC MATHEMATICAL DOUBLE-STRUCK WAW;Lo;0;AL; 0648;;;;N;;;;; 1EEA6;ARABIC MATHEMATICAL DOUBLE-STRUCK ZAIN;Lo;0;AL; 0632;;;;N;;;;; 1EEA7;ARABIC MATHEMATICAL DOUBLE-STRUCK HAH;Lo;0;AL; 062D;;;;N;;;;; 1EEA8;ARABIC MATHEMATICAL DOUBLE-STRUCK TAH;Lo;0;AL; 0637;;;;N;;;;; 1EEA9;ARABIC MATHEMATICAL DOUBLE-STRUCK YEH;Lo;0;AL; 064A;;;;N;;;;; 1EEAB;ARABIC MATHEMATICAL DOUBLE-STRUCK LAM;Lo;0;AL; 0644;;;;N;;;;; 1EEAC;ARABIC MATHEMATICAL DOUBLE-STRUCK MEEM;Lo;0;AL; 0645;;;;N;;;;; 1EEAD;ARABIC MATHEMATICAL DOUBLE-STRUCK NOON;Lo;0;AL; 0646;;;;N;;;;; 1EEAE;ARABIC MATHEMATICAL DOUBLE-STRUCK SEEN;Lo;0;AL; 0633;;;;N;;;;; 1EEAF;ARABIC MATHEMATICAL DOUBLE-STRUCK AIN;Lo;0;AL; 0639;;;;N;;;;; 1EEB0;ARABIC MATHEMATICAL DOUBLE-STRUCK FEH;Lo;0;AL; 0641;;;;N;;;;; 1EEB1;ARABIC MATHEMATICAL DOUBLE-STRUCK SAD;Lo;0;AL; 0635;;;;N;;;;; 1EEB2;ARABIC MATHEMATICAL DOUBLE-STRUCK QAF;Lo;0;AL; 0642;;;;N;;;;; 1EEB3;ARABIC MATHEMATICAL DOUBLE-STRUCK REH;Lo;0;AL; 0631;;;;N;;;;; 1EEB4;ARABIC MATHEMATICAL DOUBLE-STRUCK SHEEN;Lo;0;AL; 0634;;;;N;;;;; 1EEB5;ARABIC MATHEMATICAL DOUBLE-STRUCK TEH;Lo;0;AL; 062A;;;;N;;;;; 1EEB6;ARABIC MATHEMATICAL DOUBLE-STRUCK THEH;Lo;0;AL; 062B;;;;N;;;;; 1EEB7;ARABIC MATHEMATICAL DOUBLE-STRUCK KHAH;Lo;0;AL; 062E;;;;N;;;;; 1EEB8;ARABIC MATHEMATICAL DOUBLE-STRUCK THAL;Lo;0;AL; 0630;;;;N;;;;; 1EEB9;ARABIC MATHEMATICAL DOUBLE-STRUCK DAD;Lo;0;AL; 0636;;;;N;;;;; 1EEBA;ARABIC MATHEMATICAL DOUBLE-STRUCK ZAH;Lo;0;AL; 0638;;;;N;;;;; 1EEBB;ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN;Lo;0;AL; 063A;;;;N;;;;; 1EEF0;ARABIC MATHEMATICAL OPERATOR MEEM WITH HAH WITH TATWEEL;Sm;0;ON;;;;;N;;;;; 1EEF1;ARABIC MATHEMATICAL OPERATOR HAH WITH DAL;Sm;0;ON;;;;;N;;;;; 1F000;MAHJONG TILE EAST WIND;So;0;ON;;;;;N;;;;; 1F001;MAHJONG TILE SOUTH WIND;So;0;ON;;;;;N;;;;; 1F002;MAHJONG TILE WEST WIND;So;0;ON;;;;;N;;;;; 1F003;MAHJONG TILE NORTH WIND;So;0;ON;;;;;N;;;;; 1F004;MAHJONG TILE RED DRAGON;So;0;ON;;;;;N;;;;; 1F005;MAHJONG TILE GREEN DRAGON;So;0;ON;;;;;N;;;;; 1F006;MAHJONG TILE WHITE DRAGON;So;0;ON;;;;;N;;;;; 1F007;MAHJONG TILE ONE OF CHARACTERS;So;0;ON;;;;;N;;;;; 1F008;MAHJONG TILE TWO OF CHARACTERS;So;0;ON;;;;;N;;;;; 1F009;MAHJONG TILE THREE OF CHARACTERS;So;0;ON;;;;;N;;;;; 1F00A;MAHJONG TILE FOUR OF CHARACTERS;So;0;ON;;;;;N;;;;; 1F00B;MAHJONG TILE FIVE OF CHARACTERS;So;0;ON;;;;;N;;;;; 1F00C;MAHJONG TILE SIX OF CHARACTERS;So;0;ON;;;;;N;;;;; 1F00D;MAHJONG TILE SEVEN OF CHARACTERS;So;0;ON;;;;;N;;;;; 1F00E;MAHJONG TILE EIGHT OF CHARACTERS;So;0;ON;;;;;N;;;;; 1F00F;MAHJONG TILE NINE OF CHARACTERS;So;0;ON;;;;;N;;;;; 1F010;MAHJONG TILE ONE OF BAMBOOS;So;0;ON;;;;;N;;;;; 1F011;MAHJONG TILE TWO OF BAMBOOS;So;0;ON;;;;;N;;;;; 1F012;MAHJONG TILE THREE OF BAMBOOS;So;0;ON;;;;;N;;;;; 1F013;MAHJONG TILE FOUR OF BAMBOOS;So;0;ON;;;;;N;;;;; 1F014;MAHJONG TILE FIVE OF BAMBOOS;So;0;ON;;;;;N;;;;; 1F015;MAHJONG TILE SIX OF BAMBOOS;So;0;ON;;;;;N;;;;; 1F016;MAHJONG TILE SEVEN OF BAMBOOS;So;0;ON;;;;;N;;;;; 1F017;MAHJONG TILE EIGHT OF BAMBOOS;So;0;ON;;;;;N;;;;; 1F018;MAHJONG TILE NINE OF BAMBOOS;So;0;ON;;;;;N;;;;; 1F019;MAHJONG TILE ONE OF CIRCLES;So;0;ON;;;;;N;;;;; 1F01A;MAHJONG TILE TWO OF CIRCLES;So;0;ON;;;;;N;;;;; 1F01B;MAHJONG TILE THREE OF CIRCLES;So;0;ON;;;;;N;;;;; 1F01C;MAHJONG TILE FOUR OF CIRCLES;So;0;ON;;;;;N;;;;; 1F01D;MAHJONG TILE FIVE OF CIRCLES;So;0;ON;;;;;N;;;;; 1F01E;MAHJONG TILE SIX OF CIRCLES;So;0;ON;;;;;N;;;;; 1F01F;MAHJONG TILE SEVEN OF CIRCLES;So;0;ON;;;;;N;;;;; 1F020;MAHJONG TILE EIGHT OF CIRCLES;So;0;ON;;;;;N;;;;; 1F021;MAHJONG TILE NINE OF CIRCLES;So;0;ON;;;;;N;;;;; 1F022;MAHJONG TILE PLUM;So;0;ON;;;;;N;;;;; 1F023;MAHJONG TILE ORCHID;So;0;ON;;;;;N;;;;; 1F024;MAHJONG TILE BAMBOO;So;0;ON;;;;;N;;;;; 1F025;MAHJONG TILE CHRYSANTHEMUM;So;0;ON;;;;;N;;;;; 1F026;MAHJONG TILE SPRING;So;0;ON;;;;;N;;;;; 1F027;MAHJONG TILE SUMMER;So;0;ON;;;;;N;;;;; 1F028;MAHJONG TILE AUTUMN;So;0;ON;;;;;N;;;;; 1F029;MAHJONG TILE WINTER;So;0;ON;;;;;N;;;;; 1F02A;MAHJONG TILE JOKER;So;0;ON;;;;;N;;;;; 1F02B;MAHJONG TILE BACK;So;0;ON;;;;;N;;;;; 1F030;DOMINO TILE HORIZONTAL BACK;So;0;ON;;;;;N;;;;; 1F031;DOMINO TILE HORIZONTAL-00-00;So;0;ON;;;;;N;;;;; 1F032;DOMINO TILE HORIZONTAL-00-01;So;0;ON;;;;;N;;;;; 1F033;DOMINO TILE HORIZONTAL-00-02;So;0;ON;;;;;N;;;;; 1F034;DOMINO TILE HORIZONTAL-00-03;So;0;ON;;;;;N;;;;; 1F035;DOMINO TILE HORIZONTAL-00-04;So;0;ON;;;;;N;;;;; 1F036;DOMINO TILE HORIZONTAL-00-05;So;0;ON;;;;;N;;;;; 1F037;DOMINO TILE HORIZONTAL-00-06;So;0;ON;;;;;N;;;;; 1F038;DOMINO TILE HORIZONTAL-01-00;So;0;ON;;;;;N;;;;; 1F039;DOMINO TILE HORIZONTAL-01-01;So;0;ON;;;;;N;;;;; 1F03A;DOMINO TILE HORIZONTAL-01-02;So;0;ON;;;;;N;;;;; 1F03B;DOMINO TILE HORIZONTAL-01-03;So;0;ON;;;;;N;;;;; 1F03C;DOMINO TILE HORIZONTAL-01-04;So;0;ON;;;;;N;;;;; 1F03D;DOMINO TILE HORIZONTAL-01-05;So;0;ON;;;;;N;;;;; 1F03E;DOMINO TILE HORIZONTAL-01-06;So;0;ON;;;;;N;;;;; 1F03F;DOMINO TILE HORIZONTAL-02-00;So;0;ON;;;;;N;;;;; 1F040;DOMINO TILE HORIZONTAL-02-01;So;0;ON;;;;;N;;;;; 1F041;DOMINO TILE HORIZONTAL-02-02;So;0;ON;;;;;N;;;;; 1F042;DOMINO TILE HORIZONTAL-02-03;So;0;ON;;;;;N;;;;; 1F043;DOMINO TILE HORIZONTAL-02-04;So;0;ON;;;;;N;;;;; 1F044;DOMINO TILE HORIZONTAL-02-05;So;0;ON;;;;;N;;;;; 1F045;DOMINO TILE HORIZONTAL-02-06;So;0;ON;;;;;N;;;;; 1F046;DOMINO TILE HORIZONTAL-03-00;So;0;ON;;;;;N;;;;; 1F047;DOMINO TILE HORIZONTAL-03-01;So;0;ON;;;;;N;;;;; 1F048;DOMINO TILE HORIZONTAL-03-02;So;0;ON;;;;;N;;;;; 1F049;DOMINO TILE HORIZONTAL-03-03;So;0;ON;;;;;N;;;;; 1F04A;DOMINO TILE HORIZONTAL-03-04;So;0;ON;;;;;N;;;;; 1F04B;DOMINO TILE HORIZONTAL-03-05;So;0;ON;;;;;N;;;;; 1F04C;DOMINO TILE HORIZONTAL-03-06;So;0;ON;;;;;N;;;;; 1F04D;DOMINO TILE HORIZONTAL-04-00;So;0;ON;;;;;N;;;;; 1F04E;DOMINO TILE HORIZONTAL-04-01;So;0;ON;;;;;N;;;;; 1F04F;DOMINO TILE HORIZONTAL-04-02;So;0;ON;;;;;N;;;;; 1F050;DOMINO TILE HORIZONTAL-04-03;So;0;ON;;;;;N;;;;; 1F051;DOMINO TILE HORIZONTAL-04-04;So;0;ON;;;;;N;;;;; 1F052;DOMINO TILE HORIZONTAL-04-05;So;0;ON;;;;;N;;;;; 1F053;DOMINO TILE HORIZONTAL-04-06;So;0;ON;;;;;N;;;;; 1F054;DOMINO TILE HORIZONTAL-05-00;So;0;ON;;;;;N;;;;; 1F055;DOMINO TILE HORIZONTAL-05-01;So;0;ON;;;;;N;;;;; 1F056;DOMINO TILE HORIZONTAL-05-02;So;0;ON;;;;;N;;;;; 1F057;DOMINO TILE HORIZONTAL-05-03;So;0;ON;;;;;N;;;;; 1F058;DOMINO TILE HORIZONTAL-05-04;So;0;ON;;;;;N;;;;; 1F059;DOMINO TILE HORIZONTAL-05-05;So;0;ON;;;;;N;;;;; 1F05A;DOMINO TILE HORIZONTAL-05-06;So;0;ON;;;;;N;;;;; 1F05B;DOMINO TILE HORIZONTAL-06-00;So;0;ON;;;;;N;;;;; 1F05C;DOMINO TILE HORIZONTAL-06-01;So;0;ON;;;;;N;;;;; 1F05D;DOMINO TILE HORIZONTAL-06-02;So;0;ON;;;;;N;;;;; 1F05E;DOMINO TILE HORIZONTAL-06-03;So;0;ON;;;;;N;;;;; 1F05F;DOMINO TILE HORIZONTAL-06-04;So;0;ON;;;;;N;;;;; 1F060;DOMINO TILE HORIZONTAL-06-05;So;0;ON;;;;;N;;;;; 1F061;DOMINO TILE HORIZONTAL-06-06;So;0;ON;;;;;N;;;;; 1F062;DOMINO TILE VERTICAL BACK;So;0;ON;;;;;N;;;;; 1F063;DOMINO TILE VERTICAL-00-00;So;0;ON;;;;;N;;;;; 1F064;DOMINO TILE VERTICAL-00-01;So;0;ON;;;;;N;;;;; 1F065;DOMINO TILE VERTICAL-00-02;So;0;ON;;;;;N;;;;; 1F066;DOMINO TILE VERTICAL-00-03;So;0;ON;;;;;N;;;;; 1F067;DOMINO TILE VERTICAL-00-04;So;0;ON;;;;;N;;;;; 1F068;DOMINO TILE VERTICAL-00-05;So;0;ON;;;;;N;;;;; 1F069;DOMINO TILE VERTICAL-00-06;So;0;ON;;;;;N;;;;; 1F06A;DOMINO TILE VERTICAL-01-00;So;0;ON;;;;;N;;;;; 1F06B;DOMINO TILE VERTICAL-01-01;So;0;ON;;;;;N;;;;; 1F06C;DOMINO TILE VERTICAL-01-02;So;0;ON;;;;;N;;;;; 1F06D;DOMINO TILE VERTICAL-01-03;So;0;ON;;;;;N;;;;; 1F06E;DOMINO TILE VERTICAL-01-04;So;0;ON;;;;;N;;;;; 1F06F;DOMINO TILE VERTICAL-01-05;So;0;ON;;;;;N;;;;; 1F070;DOMINO TILE VERTICAL-01-06;So;0;ON;;;;;N;;;;; 1F071;DOMINO TILE VERTICAL-02-00;So;0;ON;;;;;N;;;;; 1F072;DOMINO TILE VERTICAL-02-01;So;0;ON;;;;;N;;;;; 1F073;DOMINO TILE VERTICAL-02-02;So;0;ON;;;;;N;;;;; 1F074;DOMINO TILE VERTICAL-02-03;So;0;ON;;;;;N;;;;; 1F075;DOMINO TILE VERTICAL-02-04;So;0;ON;;;;;N;;;;; 1F076;DOMINO TILE VERTICAL-02-05;So;0;ON;;;;;N;;;;; 1F077;DOMINO TILE VERTICAL-02-06;So;0;ON;;;;;N;;;;; 1F078;DOMINO TILE VERTICAL-03-00;So;0;ON;;;;;N;;;;; 1F079;DOMINO TILE VERTICAL-03-01;So;0;ON;;;;;N;;;;; 1F07A;DOMINO TILE VERTICAL-03-02;So;0;ON;;;;;N;;;;; 1F07B;DOMINO TILE VERTICAL-03-03;So;0;ON;;;;;N;;;;; 1F07C;DOMINO TILE VERTICAL-03-04;So;0;ON;;;;;N;;;;; 1F07D;DOMINO TILE VERTICAL-03-05;So;0;ON;;;;;N;;;;; 1F07E;DOMINO TILE VERTICAL-03-06;So;0;ON;;;;;N;;;;; 1F07F;DOMINO TILE VERTICAL-04-00;So;0;ON;;;;;N;;;;; 1F080;DOMINO TILE VERTICAL-04-01;So;0;ON;;;;;N;;;;; 1F081;DOMINO TILE VERTICAL-04-02;So;0;ON;;;;;N;;;;; 1F082;DOMINO TILE VERTICAL-04-03;So;0;ON;;;;;N;;;;; 1F083;DOMINO TILE VERTICAL-04-04;So;0;ON;;;;;N;;;;; 1F084;DOMINO TILE VERTICAL-04-05;So;0;ON;;;;;N;;;;; 1F085;DOMINO TILE VERTICAL-04-06;So;0;ON;;;;;N;;;;; 1F086;DOMINO TILE VERTICAL-05-00;So;0;ON;;;;;N;;;;; 1F087;DOMINO TILE VERTICAL-05-01;So;0;ON;;;;;N;;;;; 1F088;DOMINO TILE VERTICAL-05-02;So;0;ON;;;;;N;;;;; 1F089;DOMINO TILE VERTICAL-05-03;So;0;ON;;;;;N;;;;; 1F08A;DOMINO TILE VERTICAL-05-04;So;0;ON;;;;;N;;;;; 1F08B;DOMINO TILE VERTICAL-05-05;So;0;ON;;;;;N;;;;; 1F08C;DOMINO TILE VERTICAL-05-06;So;0;ON;;;;;N;;;;; 1F08D;DOMINO TILE VERTICAL-06-00;So;0;ON;;;;;N;;;;; 1F08E;DOMINO TILE VERTICAL-06-01;So;0;ON;;;;;N;;;;; 1F08F;DOMINO TILE VERTICAL-06-02;So;0;ON;;;;;N;;;;; 1F090;DOMINO TILE VERTICAL-06-03;So;0;ON;;;;;N;;;;; 1F091;DOMINO TILE VERTICAL-06-04;So;0;ON;;;;;N;;;;; 1F092;DOMINO TILE VERTICAL-06-05;So;0;ON;;;;;N;;;;; 1F093;DOMINO TILE VERTICAL-06-06;So;0;ON;;;;;N;;;;; 1F0A0;PLAYING CARD BACK;So;0;ON;;;;;N;;;;; 1F0A1;PLAYING CARD ACE OF SPADES;So;0;ON;;;;;N;;;;; 1F0A2;PLAYING CARD TWO OF SPADES;So;0;ON;;;;;N;;;;; 1F0A3;PLAYING CARD THREE OF SPADES;So;0;ON;;;;;N;;;;; 1F0A4;PLAYING CARD FOUR OF SPADES;So;0;ON;;;;;N;;;;; 1F0A5;PLAYING CARD FIVE OF SPADES;So;0;ON;;;;;N;;;;; 1F0A6;PLAYING CARD SIX OF SPADES;So;0;ON;;;;;N;;;;; 1F0A7;PLAYING CARD SEVEN OF SPADES;So;0;ON;;;;;N;;;;; 1F0A8;PLAYING CARD EIGHT OF SPADES;So;0;ON;;;;;N;;;;; 1F0A9;PLAYING CARD NINE OF SPADES;So;0;ON;;;;;N;;;;; 1F0AA;PLAYING CARD TEN OF SPADES;So;0;ON;;;;;N;;;;; 1F0AB;PLAYING CARD JACK OF SPADES;So;0;ON;;;;;N;;;;; 1F0AC;PLAYING CARD KNIGHT OF SPADES;So;0;ON;;;;;N;;;;; 1F0AD;PLAYING CARD QUEEN OF SPADES;So;0;ON;;;;;N;;;;; 1F0AE;PLAYING CARD KING OF SPADES;So;0;ON;;;;;N;;;;; 1F0B1;PLAYING CARD ACE OF HEARTS;So;0;ON;;;;;N;;;;; 1F0B2;PLAYING CARD TWO OF HEARTS;So;0;ON;;;;;N;;;;; 1F0B3;PLAYING CARD THREE OF HEARTS;So;0;ON;;;;;N;;;;; 1F0B4;PLAYING CARD FOUR OF HEARTS;So;0;ON;;;;;N;;;;; 1F0B5;PLAYING CARD FIVE OF HEARTS;So;0;ON;;;;;N;;;;; 1F0B6;PLAYING CARD SIX OF HEARTS;So;0;ON;;;;;N;;;;; 1F0B7;PLAYING CARD SEVEN OF HEARTS;So;0;ON;;;;;N;;;;; 1F0B8;PLAYING CARD EIGHT OF HEARTS;So;0;ON;;;;;N;;;;; 1F0B9;PLAYING CARD NINE OF HEARTS;So;0;ON;;;;;N;;;;; 1F0BA;PLAYING CARD TEN OF HEARTS;So;0;ON;;;;;N;;;;; 1F0BB;PLAYING CARD JACK OF HEARTS;So;0;ON;;;;;N;;;;; 1F0BC;PLAYING CARD KNIGHT OF HEARTS;So;0;ON;;;;;N;;;;; 1F0BD;PLAYING CARD QUEEN OF HEARTS;So;0;ON;;;;;N;;;;; 1F0BE;PLAYING CARD KING OF HEARTS;So;0;ON;;;;;N;;;;; 1F0BF;PLAYING CARD RED JOKER;So;0;ON;;;;;N;;;;; 1F0C1;PLAYING CARD ACE OF DIAMONDS;So;0;ON;;;;;N;;;;; 1F0C2;PLAYING CARD TWO OF DIAMONDS;So;0;ON;;;;;N;;;;; 1F0C3;PLAYING CARD THREE OF DIAMONDS;So;0;ON;;;;;N;;;;; 1F0C4;PLAYING CARD FOUR OF DIAMONDS;So;0;ON;;;;;N;;;;; 1F0C5;PLAYING CARD FIVE OF DIAMONDS;So;0;ON;;;;;N;;;;; 1F0C6;PLAYING CARD SIX OF DIAMONDS;So;0;ON;;;;;N;;;;; 1F0C7;PLAYING CARD SEVEN OF DIAMONDS;So;0;ON;;;;;N;;;;; 1F0C8;PLAYING CARD EIGHT OF DIAMONDS;So;0;ON;;;;;N;;;;; 1F0C9;PLAYING CARD NINE OF DIAMONDS;So;0;ON;;;;;N;;;;; 1F0CA;PLAYING CARD TEN OF DIAMONDS;So;0;ON;;;;;N;;;;; 1F0CB;PLAYING CARD JACK OF DIAMONDS;So;0;ON;;;;;N;;;;; 1F0CC;PLAYING CARD KNIGHT OF DIAMONDS;So;0;ON;;;;;N;;;;; 1F0CD;PLAYING CARD QUEEN OF DIAMONDS;So;0;ON;;;;;N;;;;; 1F0CE;PLAYING CARD KING OF DIAMONDS;So;0;ON;;;;;N;;;;; 1F0CF;PLAYING CARD BLACK JOKER;So;0;ON;;;;;N;;;;; 1F0D1;PLAYING CARD ACE OF CLUBS;So;0;ON;;;;;N;;;;; 1F0D2;PLAYING CARD TWO OF CLUBS;So;0;ON;;;;;N;;;;; 1F0D3;PLAYING CARD THREE OF CLUBS;So;0;ON;;;;;N;;;;; 1F0D4;PLAYING CARD FOUR OF CLUBS;So;0;ON;;;;;N;;;;; 1F0D5;PLAYING CARD FIVE OF CLUBS;So;0;ON;;;;;N;;;;; 1F0D6;PLAYING CARD SIX OF CLUBS;So;0;ON;;;;;N;;;;; 1F0D7;PLAYING CARD SEVEN OF CLUBS;So;0;ON;;;;;N;;;;; 1F0D8;PLAYING CARD EIGHT OF CLUBS;So;0;ON;;;;;N;;;;; 1F0D9;PLAYING CARD NINE OF CLUBS;So;0;ON;;;;;N;;;;; 1F0DA;PLAYING CARD TEN OF CLUBS;So;0;ON;;;;;N;;;;; 1F0DB;PLAYING CARD JACK OF CLUBS;So;0;ON;;;;;N;;;;; 1F0DC;PLAYING CARD KNIGHT OF CLUBS;So;0;ON;;;;;N;;;;; 1F0DD;PLAYING CARD QUEEN OF CLUBS;So;0;ON;;;;;N;;;;; 1F0DE;PLAYING CARD KING OF CLUBS;So;0;ON;;;;;N;;;;; 1F0DF;PLAYING CARD WHITE JOKER;So;0;ON;;;;;N;;;;; 1F0E0;PLAYING CARD FOOL;So;0;ON;;;;;N;;;;; 1F0E1;PLAYING CARD TRUMP-1;So;0;ON;;;;;N;;;;; 1F0E2;PLAYING CARD TRUMP-2;So;0;ON;;;;;N;;;;; 1F0E3;PLAYING CARD TRUMP-3;So;0;ON;;;;;N;;;;; 1F0E4;PLAYING CARD TRUMP-4;So;0;ON;;;;;N;;;;; 1F0E5;PLAYING CARD TRUMP-5;So;0;ON;;;;;N;;;;; 1F0E6;PLAYING CARD TRUMP-6;So;0;ON;;;;;N;;;;; 1F0E7;PLAYING CARD TRUMP-7;So;0;ON;;;;;N;;;;; 1F0E8;PLAYING CARD TRUMP-8;So;0;ON;;;;;N;;;;; 1F0E9;PLAYING CARD TRUMP-9;So;0;ON;;;;;N;;;;; 1F0EA;PLAYING CARD TRUMP-10;So;0;ON;;;;;N;;;;; 1F0EB;PLAYING CARD TRUMP-11;So;0;ON;;;;;N;;;;; 1F0EC;PLAYING CARD TRUMP-12;So;0;ON;;;;;N;;;;; 1F0ED;PLAYING CARD TRUMP-13;So;0;ON;;;;;N;;;;; 1F0EE;PLAYING CARD TRUMP-14;So;0;ON;;;;;N;;;;; 1F0EF;PLAYING CARD TRUMP-15;So;0;ON;;;;;N;;;;; 1F0F0;PLAYING CARD TRUMP-16;So;0;ON;;;;;N;;;;; 1F0F1;PLAYING CARD TRUMP-17;So;0;ON;;;;;N;;;;; 1F0F2;PLAYING CARD TRUMP-18;So;0;ON;;;;;N;;;;; 1F0F3;PLAYING CARD TRUMP-19;So;0;ON;;;;;N;;;;; 1F0F4;PLAYING CARD TRUMP-20;So;0;ON;;;;;N;;;;; 1F0F5;PLAYING CARD TRUMP-21;So;0;ON;;;;;N;;;;; 1F100;DIGIT ZERO FULL STOP;No;0;EN; 0030 002E;;0;0;N;;;;; 1F101;DIGIT ZERO COMMA;No;0;EN; 0030 002C;;0;0;N;;;;; 1F102;DIGIT ONE COMMA;No;0;EN; 0031 002C;;1;1;N;;;;; 1F103;DIGIT TWO COMMA;No;0;EN; 0032 002C;;2;2;N;;;;; 1F104;DIGIT THREE COMMA;No;0;EN; 0033 002C;;3;3;N;;;;; 1F105;DIGIT FOUR COMMA;No;0;EN; 0034 002C;;4;4;N;;;;; 1F106;DIGIT FIVE COMMA;No;0;EN; 0035 002C;;5;5;N;;;;; 1F107;DIGIT SIX COMMA;No;0;EN; 0036 002C;;6;6;N;;;;; 1F108;DIGIT SEVEN COMMA;No;0;EN; 0037 002C;;7;7;N;;;;; 1F109;DIGIT EIGHT COMMA;No;0;EN; 0038 002C;;8;8;N;;;;; 1F10A;DIGIT NINE COMMA;No;0;EN; 0039 002C;;9;9;N;;;;; 1F10B;DINGBAT CIRCLED SANS-SERIF DIGIT ZERO;No;0;ON;;;;0;N;;;;; 1F10C;DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ZERO;No;0;ON;;;;0;N;;;;; 1F110;PARENTHESIZED LATIN CAPITAL LETTER A;So;0;L; 0028 0041 0029;;;;N;;;;; 1F111;PARENTHESIZED LATIN CAPITAL LETTER B;So;0;L; 0028 0042 0029;;;;N;;;;; 1F112;PARENTHESIZED LATIN CAPITAL LETTER C;So;0;L; 0028 0043 0029;;;;N;;;;; 1F113;PARENTHESIZED LATIN CAPITAL LETTER D;So;0;L; 0028 0044 0029;;;;N;;;;; 1F114;PARENTHESIZED LATIN CAPITAL LETTER E;So;0;L; 0028 0045 0029;;;;N;;;;; 1F115;PARENTHESIZED LATIN CAPITAL LETTER F;So;0;L; 0028 0046 0029;;;;N;;;;; 1F116;PARENTHESIZED LATIN CAPITAL LETTER G;So;0;L; 0028 0047 0029;;;;N;;;;; 1F117;PARENTHESIZED LATIN CAPITAL LETTER H;So;0;L; 0028 0048 0029;;;;N;;;;; 1F118;PARENTHESIZED LATIN CAPITAL LETTER I;So;0;L; 0028 0049 0029;;;;N;;;;; 1F119;PARENTHESIZED LATIN CAPITAL LETTER J;So;0;L; 0028 004A 0029;;;;N;;;;; 1F11A;PARENTHESIZED LATIN CAPITAL LETTER K;So;0;L; 0028 004B 0029;;;;N;;;;; 1F11B;PARENTHESIZED LATIN CAPITAL LETTER L;So;0;L; 0028 004C 0029;;;;N;;;;; 1F11C;PARENTHESIZED LATIN CAPITAL LETTER M;So;0;L; 0028 004D 0029;;;;N;;;;; 1F11D;PARENTHESIZED LATIN CAPITAL LETTER N;So;0;L; 0028 004E 0029;;;;N;;;;; 1F11E;PARENTHESIZED LATIN CAPITAL LETTER O;So;0;L; 0028 004F 0029;;;;N;;;;; 1F11F;PARENTHESIZED LATIN CAPITAL LETTER P;So;0;L; 0028 0050 0029;;;;N;;;;; 1F120;PARENTHESIZED LATIN CAPITAL LETTER Q;So;0;L; 0028 0051 0029;;;;N;;;;; 1F121;PARENTHESIZED LATIN CAPITAL LETTER R;So;0;L; 0028 0052 0029;;;;N;;;;; 1F122;PARENTHESIZED LATIN CAPITAL LETTER S;So;0;L; 0028 0053 0029;;;;N;;;;; 1F123;PARENTHESIZED LATIN CAPITAL LETTER T;So;0;L; 0028 0054 0029;;;;N;;;;; 1F124;PARENTHESIZED LATIN CAPITAL LETTER U;So;0;L; 0028 0055 0029;;;;N;;;;; 1F125;PARENTHESIZED LATIN CAPITAL LETTER V;So;0;L; 0028 0056 0029;;;;N;;;;; 1F126;PARENTHESIZED LATIN CAPITAL LETTER W;So;0;L; 0028 0057 0029;;;;N;;;;; 1F127;PARENTHESIZED LATIN CAPITAL LETTER X;So;0;L; 0028 0058 0029;;;;N;;;;; 1F128;PARENTHESIZED LATIN CAPITAL LETTER Y;So;0;L; 0028 0059 0029;;;;N;;;;; 1F129;PARENTHESIZED LATIN CAPITAL LETTER Z;So;0;L; 0028 005A 0029;;;;N;;;;; 1F12A;TORTOISE SHELL BRACKETED LATIN CAPITAL LETTER S;So;0;L; 3014 0053 3015;;;;N;;;;; 1F12B;CIRCLED ITALIC LATIN CAPITAL LETTER C;So;0;L; 0043;;;;N;;;;; 1F12C;CIRCLED ITALIC LATIN CAPITAL LETTER R;So;0;L; 0052;;;;N;;;;; 1F12D;CIRCLED CD;So;0;L; 0043 0044;;;;N;;;;; 1F12E;CIRCLED WZ;So;0;L; 0057 005A;;;;N;;;;; 1F130;SQUARED LATIN CAPITAL LETTER A;So;0;L; 0041;;;;N;;;;; 1F131;SQUARED LATIN CAPITAL LETTER B;So;0;L; 0042;;;;N;;;;; 1F132;SQUARED LATIN CAPITAL LETTER C;So;0;L; 0043;;;;N;;;;; 1F133;SQUARED LATIN CAPITAL LETTER D;So;0;L; 0044;;;;N;;;;; 1F134;SQUARED LATIN CAPITAL LETTER E;So;0;L; 0045;;;;N;;;;; 1F135;SQUARED LATIN CAPITAL LETTER F;So;0;L; 0046;;;;N;;;;; 1F136;SQUARED LATIN CAPITAL LETTER G;So;0;L; 0047;;;;N;;;;; 1F137;SQUARED LATIN CAPITAL LETTER H;So;0;L; 0048;;;;N;;;;; 1F138;SQUARED LATIN CAPITAL LETTER I;So;0;L; 0049;;;;N;;;;; 1F139;SQUARED LATIN CAPITAL LETTER J;So;0;L; 004A;;;;N;;;;; 1F13A;SQUARED LATIN CAPITAL LETTER K;So;0;L; 004B;;;;N;;;;; 1F13B;SQUARED LATIN CAPITAL LETTER L;So;0;L; 004C;;;;N;;;;; 1F13C;SQUARED LATIN CAPITAL LETTER M;So;0;L; 004D;;;;N;;;;; 1F13D;SQUARED LATIN CAPITAL LETTER N;So;0;L; 004E;;;;N;;;;; 1F13E;SQUARED LATIN CAPITAL LETTER O;So;0;L; 004F;;;;N;;;;; 1F13F;SQUARED LATIN CAPITAL LETTER P;So;0;L; 0050;;;;N;;;;; 1F140;SQUARED LATIN CAPITAL LETTER Q;So;0;L; 0051;;;;N;;;;; 1F141;SQUARED LATIN CAPITAL LETTER R;So;0;L; 0052;;;;N;;;;; 1F142;SQUARED LATIN CAPITAL LETTER S;So;0;L; 0053;;;;N;;;;; 1F143;SQUARED LATIN CAPITAL LETTER T;So;0;L; 0054;;;;N;;;;; 1F144;SQUARED LATIN CAPITAL LETTER U;So;0;L; 0055;;;;N;;;;; 1F145;SQUARED LATIN CAPITAL LETTER V;So;0;L; 0056;;;;N;;;;; 1F146;SQUARED LATIN CAPITAL LETTER W;So;0;L; 0057;;;;N;;;;; 1F147;SQUARED LATIN CAPITAL LETTER X;So;0;L; 0058;;;;N;;;;; 1F148;SQUARED LATIN CAPITAL LETTER Y;So;0;L; 0059;;;;N;;;;; 1F149;SQUARED LATIN CAPITAL LETTER Z;So;0;L; 005A;;;;N;;;;; 1F14A;SQUARED HV;So;0;L; 0048 0056;;;;N;;;;; 1F14B;SQUARED MV;So;0;L; 004D 0056;;;;N;;;;; 1F14C;SQUARED SD;So;0;L; 0053 0044;;;;N;;;;; 1F14D;SQUARED SS;So;0;L; 0053 0053;;;;N;;;;; 1F14E;SQUARED PPV;So;0;L; 0050 0050 0056;;;;N;;;;; 1F14F;SQUARED WC;So;0;L; 0057 0043;;;;N;;;;; 1F150;NEGATIVE CIRCLED LATIN CAPITAL LETTER A;So;0;L;;;;;N;;;;; 1F151;NEGATIVE CIRCLED LATIN CAPITAL LETTER B;So;0;L;;;;;N;;;;; 1F152;NEGATIVE CIRCLED LATIN CAPITAL LETTER C;So;0;L;;;;;N;;;;; 1F153;NEGATIVE CIRCLED LATIN CAPITAL LETTER D;So;0;L;;;;;N;;;;; 1F154;NEGATIVE CIRCLED LATIN CAPITAL LETTER E;So;0;L;;;;;N;;;;; 1F155;NEGATIVE CIRCLED LATIN CAPITAL LETTER F;So;0;L;;;;;N;;;;; 1F156;NEGATIVE CIRCLED LATIN CAPITAL LETTER G;So;0;L;;;;;N;;;;; 1F157;NEGATIVE CIRCLED LATIN CAPITAL LETTER H;So;0;L;;;;;N;;;;; 1F158;NEGATIVE CIRCLED LATIN CAPITAL LETTER I;So;0;L;;;;;N;;;;; 1F159;NEGATIVE CIRCLED LATIN CAPITAL LETTER J;So;0;L;;;;;N;;;;; 1F15A;NEGATIVE CIRCLED LATIN CAPITAL LETTER K;So;0;L;;;;;N;;;;; 1F15B;NEGATIVE CIRCLED LATIN CAPITAL LETTER L;So;0;L;;;;;N;;;;; 1F15C;NEGATIVE CIRCLED LATIN CAPITAL LETTER M;So;0;L;;;;;N;;;;; 1F15D;NEGATIVE CIRCLED LATIN CAPITAL LETTER N;So;0;L;;;;;N;;;;; 1F15E;NEGATIVE CIRCLED LATIN CAPITAL LETTER O;So;0;L;;;;;N;;;;; 1F15F;NEGATIVE CIRCLED LATIN CAPITAL LETTER P;So;0;L;;;;;N;;;;; 1F160;NEGATIVE CIRCLED LATIN CAPITAL LETTER Q;So;0;L;;;;;N;;;;; 1F161;NEGATIVE CIRCLED LATIN CAPITAL LETTER R;So;0;L;;;;;N;;;;; 1F162;NEGATIVE CIRCLED LATIN CAPITAL LETTER S;So;0;L;;;;;N;;;;; 1F163;NEGATIVE CIRCLED LATIN CAPITAL LETTER T;So;0;L;;;;;N;;;;; 1F164;NEGATIVE CIRCLED LATIN CAPITAL LETTER U;So;0;L;;;;;N;;;;; 1F165;NEGATIVE CIRCLED LATIN CAPITAL LETTER V;So;0;L;;;;;N;;;;; 1F166;NEGATIVE CIRCLED LATIN CAPITAL LETTER W;So;0;L;;;;;N;;;;; 1F167;NEGATIVE CIRCLED LATIN CAPITAL LETTER X;So;0;L;;;;;N;;;;; 1F168;NEGATIVE CIRCLED LATIN CAPITAL LETTER Y;So;0;L;;;;;N;;;;; 1F169;NEGATIVE CIRCLED LATIN CAPITAL LETTER Z;So;0;L;;;;;N;;;;; 1F16A;RAISED MC SIGN;So;0;ON; 004D 0043;;;;N;;;;; 1F16B;RAISED MD SIGN;So;0;ON; 004D 0044;;;;N;;;;; 1F170;NEGATIVE SQUARED LATIN CAPITAL LETTER A;So;0;L;;;;;N;;;;; 1F171;NEGATIVE SQUARED LATIN CAPITAL LETTER B;So;0;L;;;;;N;;;;; 1F172;NEGATIVE SQUARED LATIN CAPITAL LETTER C;So;0;L;;;;;N;;;;; 1F173;NEGATIVE SQUARED LATIN CAPITAL LETTER D;So;0;L;;;;;N;;;;; 1F174;NEGATIVE SQUARED LATIN CAPITAL LETTER E;So;0;L;;;;;N;;;;; 1F175;NEGATIVE SQUARED LATIN CAPITAL LETTER F;So;0;L;;;;;N;;;;; 1F176;NEGATIVE SQUARED LATIN CAPITAL LETTER G;So;0;L;;;;;N;;;;; 1F177;NEGATIVE SQUARED LATIN CAPITAL LETTER H;So;0;L;;;;;N;;;;; 1F178;NEGATIVE SQUARED LATIN CAPITAL LETTER I;So;0;L;;;;;N;;;;; 1F179;NEGATIVE SQUARED LATIN CAPITAL LETTER J;So;0;L;;;;;N;;;;; 1F17A;NEGATIVE SQUARED LATIN CAPITAL LETTER K;So;0;L;;;;;N;;;;; 1F17B;NEGATIVE SQUARED LATIN CAPITAL LETTER L;So;0;L;;;;;N;;;;; 1F17C;NEGATIVE SQUARED LATIN CAPITAL LETTER M;So;0;L;;;;;N;;;;; 1F17D;NEGATIVE SQUARED LATIN CAPITAL LETTER N;So;0;L;;;;;N;;;;; 1F17E;NEGATIVE SQUARED LATIN CAPITAL LETTER O;So;0;L;;;;;N;;;;; 1F17F;NEGATIVE SQUARED LATIN CAPITAL LETTER P;So;0;L;;;;;N;;;;; 1F180;NEGATIVE SQUARED LATIN CAPITAL LETTER Q;So;0;L;;;;;N;;;;; 1F181;NEGATIVE SQUARED LATIN CAPITAL LETTER R;So;0;L;;;;;N;;;;; 1F182;NEGATIVE SQUARED LATIN CAPITAL LETTER S;So;0;L;;;;;N;;;;; 1F183;NEGATIVE SQUARED LATIN CAPITAL LETTER T;So;0;L;;;;;N;;;;; 1F184;NEGATIVE SQUARED LATIN CAPITAL LETTER U;So;0;L;;;;;N;;;;; 1F185;NEGATIVE SQUARED LATIN CAPITAL LETTER V;So;0;L;;;;;N;;;;; 1F186;NEGATIVE SQUARED LATIN CAPITAL LETTER W;So;0;L;;;;;N;;;;; 1F187;NEGATIVE SQUARED LATIN CAPITAL LETTER X;So;0;L;;;;;N;;;;; 1F188;NEGATIVE SQUARED LATIN CAPITAL LETTER Y;So;0;L;;;;;N;;;;; 1F189;NEGATIVE SQUARED LATIN CAPITAL LETTER Z;So;0;L;;;;;N;;;;; 1F18A;CROSSED NEGATIVE SQUARED LATIN CAPITAL LETTER P;So;0;L;;;;;N;;;;; 1F18B;NEGATIVE SQUARED IC;So;0;L;;;;;N;;;;; 1F18C;NEGATIVE SQUARED PA;So;0;L;;;;;N;;;;; 1F18D;NEGATIVE SQUARED SA;So;0;L;;;;;N;;;;; 1F18E;NEGATIVE SQUARED AB;So;0;L;;;;;N;;;;; 1F18F;NEGATIVE SQUARED WC;So;0;L;;;;;N;;;;; 1F190;SQUARE DJ;So;0;L; 0044 004A;;;;N;;;;; 1F191;SQUARED CL;So;0;L;;;;;N;;;;; 1F192;SQUARED COOL;So;0;L;;;;;N;;;;; 1F193;SQUARED FREE;So;0;L;;;;;N;;;;; 1F194;SQUARED ID;So;0;L;;;;;N;;;;; 1F195;SQUARED NEW;So;0;L;;;;;N;;;;; 1F196;SQUARED NG;So;0;L;;;;;N;;;;; 1F197;SQUARED OK;So;0;L;;;;;N;;;;; 1F198;SQUARED SOS;So;0;L;;;;;N;;;;; 1F199;SQUARED UP WITH EXCLAMATION MARK;So;0;L;;;;;N;;;;; 1F19A;SQUARED VS;So;0;L;;;;;N;;;;; 1F19B;SQUARED THREE D;So;0;L;;;;;N;;;;; 1F19C;SQUARED SECOND SCREEN;So;0;L;;;;;N;;;;; 1F19D;SQUARED TWO K;So;0;L;;;;;N;;;;; 1F19E;SQUARED FOUR K;So;0;L;;;;;N;;;;; 1F19F;SQUARED EIGHT K;So;0;L;;;;;N;;;;; 1F1A0;SQUARED FIVE POINT ONE;So;0;L;;;;;N;;;;; 1F1A1;SQUARED SEVEN POINT ONE;So;0;L;;;;;N;;;;; 1F1A2;SQUARED TWENTY-TWO POINT TWO;So;0;L;;;;;N;;;;; 1F1A3;SQUARED SIXTY P;So;0;L;;;;;N;;;;; 1F1A4;SQUARED ONE HUNDRED TWENTY P;So;0;L;;;;;N;;;;; 1F1A5;SQUARED LATIN SMALL LETTER D;So;0;L;;;;;N;;;;; 1F1A6;SQUARED HC;So;0;L;;;;;N;;;;; 1F1A7;SQUARED HDR;So;0;L;;;;;N;;;;; 1F1A8;SQUARED HI-RES;So;0;L;;;;;N;;;;; 1F1A9;SQUARED LOSSLESS;So;0;L;;;;;N;;;;; 1F1AA;SQUARED SHV;So;0;L;;;;;N;;;;; 1F1AB;SQUARED UHD;So;0;L;;;;;N;;;;; 1F1AC;SQUARED VOD;So;0;L;;;;;N;;;;; 1F1E6;REGIONAL INDICATOR SYMBOL LETTER A;So;0;L;;;;;N;;;;; 1F1E7;REGIONAL INDICATOR SYMBOL LETTER B;So;0;L;;;;;N;;;;; 1F1E8;REGIONAL INDICATOR SYMBOL LETTER C;So;0;L;;;;;N;;;;; 1F1E9;REGIONAL INDICATOR SYMBOL LETTER D;So;0;L;;;;;N;;;;; 1F1EA;REGIONAL INDICATOR SYMBOL LETTER E;So;0;L;;;;;N;;;;; 1F1EB;REGIONAL INDICATOR SYMBOL LETTER F;So;0;L;;;;;N;;;;; 1F1EC;REGIONAL INDICATOR SYMBOL LETTER G;So;0;L;;;;;N;;;;; 1F1ED;REGIONAL INDICATOR SYMBOL LETTER H;So;0;L;;;;;N;;;;; 1F1EE;REGIONAL INDICATOR SYMBOL LETTER I;So;0;L;;;;;N;;;;; 1F1EF;REGIONAL INDICATOR SYMBOL LETTER J;So;0;L;;;;;N;;;;; 1F1F0;REGIONAL INDICATOR SYMBOL LETTER K;So;0;L;;;;;N;;;;; 1F1F1;REGIONAL INDICATOR SYMBOL LETTER L;So;0;L;;;;;N;;;;; 1F1F2;REGIONAL INDICATOR SYMBOL LETTER M;So;0;L;;;;;N;;;;; 1F1F3;REGIONAL INDICATOR SYMBOL LETTER N;So;0;L;;;;;N;;;;; 1F1F4;REGIONAL INDICATOR SYMBOL LETTER O;So;0;L;;;;;N;;;;; 1F1F5;REGIONAL INDICATOR SYMBOL LETTER P;So;0;L;;;;;N;;;;; 1F1F6;REGIONAL INDICATOR SYMBOL LETTER Q;So;0;L;;;;;N;;;;; 1F1F7;REGIONAL INDICATOR SYMBOL LETTER R;So;0;L;;;;;N;;;;; 1F1F8;REGIONAL INDICATOR SYMBOL LETTER S;So;0;L;;;;;N;;;;; 1F1F9;REGIONAL INDICATOR SYMBOL LETTER T;So;0;L;;;;;N;;;;; 1F1FA;REGIONAL INDICATOR SYMBOL LETTER U;So;0;L;;;;;N;;;;; 1F1FB;REGIONAL INDICATOR SYMBOL LETTER V;So;0;L;;;;;N;;;;; 1F1FC;REGIONAL INDICATOR SYMBOL LETTER W;So;0;L;;;;;N;;;;; 1F1FD;REGIONAL INDICATOR SYMBOL LETTER X;So;0;L;;;;;N;;;;; 1F1FE;REGIONAL INDICATOR SYMBOL LETTER Y;So;0;L;;;;;N;;;;; 1F1FF;REGIONAL INDICATOR SYMBOL LETTER Z;So;0;L;;;;;N;;;;; 1F200;SQUARE HIRAGANA HOKA;So;0;L; 307B 304B;;;;N;;;;; 1F201;SQUARED KATAKANA KOKO;So;0;L; 30B3 30B3;;;;N;;;;; 1F202;SQUARED KATAKANA SA;So;0;L; 30B5;;;;N;;;;; 1F210;SQUARED CJK UNIFIED IDEOGRAPH-624B;So;0;L; 624B;;;;N;;;;; 1F211;SQUARED CJK UNIFIED IDEOGRAPH-5B57;So;0;L; 5B57;;;;N;;;;; 1F212;SQUARED CJK UNIFIED IDEOGRAPH-53CC;So;0;L; 53CC;;;;N;;;;; 1F213;SQUARED KATAKANA DE;So;0;L; 30C7;;;;N;;;;; 1F214;SQUARED CJK UNIFIED IDEOGRAPH-4E8C;So;0;L; 4E8C;;;;N;;;;; 1F215;SQUARED CJK UNIFIED IDEOGRAPH-591A;So;0;L; 591A;;;;N;;;;; 1F216;SQUARED CJK UNIFIED IDEOGRAPH-89E3;So;0;L; 89E3;;;;N;;;;; 1F217;SQUARED CJK UNIFIED IDEOGRAPH-5929;So;0;L; 5929;;;;N;;;;; 1F218;SQUARED CJK UNIFIED IDEOGRAPH-4EA4;So;0;L; 4EA4;;;;N;;;;; 1F219;SQUARED CJK UNIFIED IDEOGRAPH-6620;So;0;L; 6620;;;;N;;;;; 1F21A;SQUARED CJK UNIFIED IDEOGRAPH-7121;So;0;L; 7121;;;;N;;;;; 1F21B;SQUARED CJK UNIFIED IDEOGRAPH-6599;So;0;L; 6599;;;;N;;;;; 1F21C;SQUARED CJK UNIFIED IDEOGRAPH-524D;So;0;L; 524D;;;;N;;;;; 1F21D;SQUARED CJK UNIFIED IDEOGRAPH-5F8C;So;0;L; 5F8C;;;;N;;;;; 1F21E;SQUARED CJK UNIFIED IDEOGRAPH-518D;So;0;L; 518D;;;;N;;;;; 1F21F;SQUARED CJK UNIFIED IDEOGRAPH-65B0;So;0;L; 65B0;;;;N;;;;; 1F220;SQUARED CJK UNIFIED IDEOGRAPH-521D;So;0;L; 521D;;;;N;;;;; 1F221;SQUARED CJK UNIFIED IDEOGRAPH-7D42;So;0;L; 7D42;;;;N;;;;; 1F222;SQUARED CJK UNIFIED IDEOGRAPH-751F;So;0;L; 751F;;;;N;;;;; 1F223;SQUARED CJK UNIFIED IDEOGRAPH-8CA9;So;0;L; 8CA9;;;;N;;;;; 1F224;SQUARED CJK UNIFIED IDEOGRAPH-58F0;So;0;L; 58F0;;;;N;;;;; 1F225;SQUARED CJK UNIFIED IDEOGRAPH-5439;So;0;L; 5439;;;;N;;;;; 1F226;SQUARED CJK UNIFIED IDEOGRAPH-6F14;So;0;L; 6F14;;;;N;;;;; 1F227;SQUARED CJK UNIFIED IDEOGRAPH-6295;So;0;L; 6295;;;;N;;;;; 1F228;SQUARED CJK UNIFIED IDEOGRAPH-6355;So;0;L; 6355;;;;N;;;;; 1F229;SQUARED CJK UNIFIED IDEOGRAPH-4E00;So;0;L; 4E00;;;;N;;;;; 1F22A;SQUARED CJK UNIFIED IDEOGRAPH-4E09;So;0;L; 4E09;;;;N;;;;; 1F22B;SQUARED CJK UNIFIED IDEOGRAPH-904A;So;0;L; 904A;;;;N;;;;; 1F22C;SQUARED CJK UNIFIED IDEOGRAPH-5DE6;So;0;L; 5DE6;;;;N;;;;; 1F22D;SQUARED CJK UNIFIED IDEOGRAPH-4E2D;So;0;L; 4E2D;;;;N;;;;; 1F22E;SQUARED CJK UNIFIED IDEOGRAPH-53F3;So;0;L; 53F3;;;;N;;;;; 1F22F;SQUARED CJK UNIFIED IDEOGRAPH-6307;So;0;L; 6307;;;;N;;;;; 1F230;SQUARED CJK UNIFIED IDEOGRAPH-8D70;So;0;L; 8D70;;;;N;;;;; 1F231;SQUARED CJK UNIFIED IDEOGRAPH-6253;So;0;L; 6253;;;;N;;;;; 1F232;SQUARED CJK UNIFIED IDEOGRAPH-7981;So;0;L; 7981;;;;N;;;;; 1F233;SQUARED CJK UNIFIED IDEOGRAPH-7A7A;So;0;L; 7A7A;;;;N;;;;; 1F234;SQUARED CJK UNIFIED IDEOGRAPH-5408;So;0;L; 5408;;;;N;;;;; 1F235;SQUARED CJK UNIFIED IDEOGRAPH-6E80;So;0;L; 6E80;;;;N;;;;; 1F236;SQUARED CJK UNIFIED IDEOGRAPH-6709;So;0;L; 6709;;;;N;;;;; 1F237;SQUARED CJK UNIFIED IDEOGRAPH-6708;So;0;L; 6708;;;;N;;;;; 1F238;SQUARED CJK UNIFIED IDEOGRAPH-7533;So;0;L; 7533;;;;N;;;;; 1F239;SQUARED CJK UNIFIED IDEOGRAPH-5272;So;0;L; 5272;;;;N;;;;; 1F23A;SQUARED CJK UNIFIED IDEOGRAPH-55B6;So;0;L; 55B6;;;;N;;;;; 1F23B;SQUARED CJK UNIFIED IDEOGRAPH-914D;So;0;L; 914D;;;;N;;;;; 1F240;TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-672C;So;0;L; 3014 672C 3015;;;;N;;;;; 1F241;TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-4E09;So;0;L; 3014 4E09 3015;;;;N;;;;; 1F242;TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-4E8C;So;0;L; 3014 4E8C 3015;;;;N;;;;; 1F243;TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-5B89;So;0;L; 3014 5B89 3015;;;;N;;;;; 1F244;TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-70B9;So;0;L; 3014 70B9 3015;;;;N;;;;; 1F245;TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6253;So;0;L; 3014 6253 3015;;;;N;;;;; 1F246;TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-76D7;So;0;L; 3014 76D7 3015;;;;N;;;;; 1F247;TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-52DD;So;0;L; 3014 52DD 3015;;;;N;;;;; 1F248;TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6557;So;0;L; 3014 6557 3015;;;;N;;;;; 1F250;CIRCLED IDEOGRAPH ADVANTAGE;So;0;L; 5F97;;;;N;;;;; 1F251;CIRCLED IDEOGRAPH ACCEPT;So;0;L; 53EF;;;;N;;;;; 1F300;CYCLONE;So;0;ON;;;;;N;;;;; 1F301;FOGGY;So;0;ON;;;;;N;;;;; 1F302;CLOSED UMBRELLA;So;0;ON;;;;;N;;;;; 1F303;NIGHT WITH STARS;So;0;ON;;;;;N;;;;; 1F304;SUNRISE OVER MOUNTAINS;So;0;ON;;;;;N;;;;; 1F305;SUNRISE;So;0;ON;;;;;N;;;;; 1F306;CITYSCAPE AT DUSK;So;0;ON;;;;;N;;;;; 1F307;SUNSET OVER BUILDINGS;So;0;ON;;;;;N;;;;; 1F308;RAINBOW;So;0;ON;;;;;N;;;;; 1F309;BRIDGE AT NIGHT;So;0;ON;;;;;N;;;;; 1F30A;WATER WAVE;So;0;ON;;;;;N;;;;; 1F30B;VOLCANO;So;0;ON;;;;;N;;;;; 1F30C;MILKY WAY;So;0;ON;;;;;N;;;;; 1F30D;EARTH GLOBE EUROPE-AFRICA;So;0;ON;;;;;N;;;;; 1F30E;EARTH GLOBE AMERICAS;So;0;ON;;;;;N;;;;; 1F30F;EARTH GLOBE ASIA-AUSTRALIA;So;0;ON;;;;;N;;;;; 1F310;GLOBE WITH MERIDIANS;So;0;ON;;;;;N;;;;; 1F311;NEW MOON SYMBOL;So;0;ON;;;;;N;;;;; 1F312;WAXING CRESCENT MOON SYMBOL;So;0;ON;;;;;N;;;;; 1F313;FIRST QUARTER MOON SYMBOL;So;0;ON;;;;;N;;;;; 1F314;WAXING GIBBOUS MOON SYMBOL;So;0;ON;;;;;N;;;;; 1F315;FULL MOON SYMBOL;So;0;ON;;;;;N;;;;; 1F316;WANING GIBBOUS MOON SYMBOL;So;0;ON;;;;;N;;;;; 1F317;LAST QUARTER MOON SYMBOL;So;0;ON;;;;;N;;;;; 1F318;WANING CRESCENT MOON SYMBOL;So;0;ON;;;;;N;;;;; 1F319;CRESCENT MOON;So;0;ON;;;;;N;;;;; 1F31A;NEW MOON WITH FACE;So;0;ON;;;;;N;;;;; 1F31B;FIRST QUARTER MOON WITH FACE;So;0;ON;;;;;N;;;;; 1F31C;LAST QUARTER MOON WITH FACE;So;0;ON;;;;;N;;;;; 1F31D;FULL MOON WITH FACE;So;0;ON;;;;;N;;;;; 1F31E;SUN WITH FACE;So;0;ON;;;;;N;;;;; 1F31F;GLOWING STAR;So;0;ON;;;;;N;;;;; 1F320;SHOOTING STAR;So;0;ON;;;;;N;;;;; 1F321;THERMOMETER;So;0;ON;;;;;N;;;;; 1F322;BLACK DROPLET;So;0;ON;;;;;N;;;;; 1F323;WHITE SUN;So;0;ON;;;;;N;;;;; 1F324;WHITE SUN WITH SMALL CLOUD;So;0;ON;;;;;N;;;;; 1F325;WHITE SUN BEHIND CLOUD;So;0;ON;;;;;N;;;;; 1F326;WHITE SUN BEHIND CLOUD WITH RAIN;So;0;ON;;;;;N;;;;; 1F327;CLOUD WITH RAIN;So;0;ON;;;;;N;;;;; 1F328;CLOUD WITH SNOW;So;0;ON;;;;;N;;;;; 1F329;CLOUD WITH LIGHTNING;So;0;ON;;;;;N;;;;; 1F32A;CLOUD WITH TORNADO;So;0;ON;;;;;N;;;;; 1F32B;FOG;So;0;ON;;;;;N;;;;; 1F32C;WIND BLOWING FACE;So;0;ON;;;;;N;;;;; 1F32D;HOT DOG;So;0;ON;;;;;N;;;;; 1F32E;TACO;So;0;ON;;;;;N;;;;; 1F32F;BURRITO;So;0;ON;;;;;N;;;;; 1F330;CHESTNUT;So;0;ON;;;;;N;;;;; 1F331;SEEDLING;So;0;ON;;;;;N;;;;; 1F332;EVERGREEN TREE;So;0;ON;;;;;N;;;;; 1F333;DECIDUOUS TREE;So;0;ON;;;;;N;;;;; 1F334;PALM TREE;So;0;ON;;;;;N;;;;; 1F335;CACTUS;So;0;ON;;;;;N;;;;; 1F336;HOT PEPPER;So;0;ON;;;;;N;;;;; 1F337;TULIP;So;0;ON;;;;;N;;;;; 1F338;CHERRY BLOSSOM;So;0;ON;;;;;N;;;;; 1F339;ROSE;So;0;ON;;;;;N;;;;; 1F33A;HIBISCUS;So;0;ON;;;;;N;;;;; 1F33B;SUNFLOWER;So;0;ON;;;;;N;;;;; 1F33C;BLOSSOM;So;0;ON;;;;;N;;;;; 1F33D;EAR OF MAIZE;So;0;ON;;;;;N;;;;; 1F33E;EAR OF RICE;So;0;ON;;;;;N;;;;; 1F33F;HERB;So;0;ON;;;;;N;;;;; 1F340;FOUR LEAF CLOVER;So;0;ON;;;;;N;;;;; 1F341;MAPLE LEAF;So;0;ON;;;;;N;;;;; 1F342;FALLEN LEAF;So;0;ON;;;;;N;;;;; 1F343;LEAF FLUTTERING IN WIND;So;0;ON;;;;;N;;;;; 1F344;MUSHROOM;So;0;ON;;;;;N;;;;; 1F345;TOMATO;So;0;ON;;;;;N;;;;; 1F346;AUBERGINE;So;0;ON;;;;;N;;;;; 1F347;GRAPES;So;0;ON;;;;;N;;;;; 1F348;MELON;So;0;ON;;;;;N;;;;; 1F349;WATERMELON;So;0;ON;;;;;N;;;;; 1F34A;TANGERINE;So;0;ON;;;;;N;;;;; 1F34B;LEMON;So;0;ON;;;;;N;;;;; 1F34C;BANANA;So;0;ON;;;;;N;;;;; 1F34D;PINEAPPLE;So;0;ON;;;;;N;;;;; 1F34E;RED APPLE;So;0;ON;;;;;N;;;;; 1F34F;GREEN APPLE;So;0;ON;;;;;N;;;;; 1F350;PEAR;So;0;ON;;;;;N;;;;; 1F351;PEACH;So;0;ON;;;;;N;;;;; 1F352;CHERRIES;So;0;ON;;;;;N;;;;; 1F353;STRAWBERRY;So;0;ON;;;;;N;;;;; 1F354;HAMBURGER;So;0;ON;;;;;N;;;;; 1F355;SLICE OF PIZZA;So;0;ON;;;;;N;;;;; 1F356;MEAT ON BONE;So;0;ON;;;;;N;;;;; 1F357;POULTRY LEG;So;0;ON;;;;;N;;;;; 1F358;RICE CRACKER;So;0;ON;;;;;N;;;;; 1F359;RICE BALL;So;0;ON;;;;;N;;;;; 1F35A;COOKED RICE;So;0;ON;;;;;N;;;;; 1F35B;CURRY AND RICE;So;0;ON;;;;;N;;;;; 1F35C;STEAMING BOWL;So;0;ON;;;;;N;;;;; 1F35D;SPAGHETTI;So;0;ON;;;;;N;;;;; 1F35E;BREAD;So;0;ON;;;;;N;;;;; 1F35F;FRENCH FRIES;So;0;ON;;;;;N;;;;; 1F360;ROASTED SWEET POTATO;So;0;ON;;;;;N;;;;; 1F361;DANGO;So;0;ON;;;;;N;;;;; 1F362;ODEN;So;0;ON;;;;;N;;;;; 1F363;SUSHI;So;0;ON;;;;;N;;;;; 1F364;FRIED SHRIMP;So;0;ON;;;;;N;;;;; 1F365;FISH CAKE WITH SWIRL DESIGN;So;0;ON;;;;;N;;;;; 1F366;SOFT ICE CREAM;So;0;ON;;;;;N;;;;; 1F367;SHAVED ICE;So;0;ON;;;;;N;;;;; 1F368;ICE CREAM;So;0;ON;;;;;N;;;;; 1F369;DOUGHNUT;So;0;ON;;;;;N;;;;; 1F36A;COOKIE;So;0;ON;;;;;N;;;;; 1F36B;CHOCOLATE BAR;So;0;ON;;;;;N;;;;; 1F36C;CANDY;So;0;ON;;;;;N;;;;; 1F36D;LOLLIPOP;So;0;ON;;;;;N;;;;; 1F36E;CUSTARD;So;0;ON;;;;;N;;;;; 1F36F;HONEY POT;So;0;ON;;;;;N;;;;; 1F370;SHORTCAKE;So;0;ON;;;;;N;;;;; 1F371;BENTO BOX;So;0;ON;;;;;N;;;;; 1F372;POT OF FOOD;So;0;ON;;;;;N;;;;; 1F373;COOKING;So;0;ON;;;;;N;;;;; 1F374;FORK AND KNIFE;So;0;ON;;;;;N;;;;; 1F375;TEACUP WITHOUT HANDLE;So;0;ON;;;;;N;;;;; 1F376;SAKE BOTTLE AND CUP;So;0;ON;;;;;N;;;;; 1F377;WINE GLASS;So;0;ON;;;;;N;;;;; 1F378;COCKTAIL GLASS;So;0;ON;;;;;N;;;;; 1F379;TROPICAL DRINK;So;0;ON;;;;;N;;;;; 1F37A;BEER MUG;So;0;ON;;;;;N;;;;; 1F37B;CLINKING BEER MUGS;So;0;ON;;;;;N;;;;; 1F37C;BABY BOTTLE;So;0;ON;;;;;N;;;;; 1F37D;FORK AND KNIFE WITH PLATE;So;0;ON;;;;;N;;;;; 1F37E;BOTTLE WITH POPPING CORK;So;0;ON;;;;;N;;;;; 1F37F;POPCORN;So;0;ON;;;;;N;;;;; 1F380;RIBBON;So;0;ON;;;;;N;;;;; 1F381;WRAPPED PRESENT;So;0;ON;;;;;N;;;;; 1F382;BIRTHDAY CAKE;So;0;ON;;;;;N;;;;; 1F383;JACK-O-LANTERN;So;0;ON;;;;;N;;;;; 1F384;CHRISTMAS TREE;So;0;ON;;;;;N;;;;; 1F385;FATHER CHRISTMAS;So;0;ON;;;;;N;;;;; 1F386;FIREWORKS;So;0;ON;;;;;N;;;;; 1F387;FIREWORK SPARKLER;So;0;ON;;;;;N;;;;; 1F388;BALLOON;So;0;ON;;;;;N;;;;; 1F389;PARTY POPPER;So;0;ON;;;;;N;;;;; 1F38A;CONFETTI BALL;So;0;ON;;;;;N;;;;; 1F38B;TANABATA TREE;So;0;ON;;;;;N;;;;; 1F38C;CROSSED FLAGS;So;0;ON;;;;;N;;;;; 1F38D;PINE DECORATION;So;0;ON;;;;;N;;;;; 1F38E;JAPANESE DOLLS;So;0;ON;;;;;N;;;;; 1F38F;CARP STREAMER;So;0;ON;;;;;N;;;;; 1F390;WIND CHIME;So;0;ON;;;;;N;;;;; 1F391;MOON VIEWING CEREMONY;So;0;ON;;;;;N;;;;; 1F392;SCHOOL SATCHEL;So;0;ON;;;;;N;;;;; 1F393;GRADUATION CAP;So;0;ON;;;;;N;;;;; 1F394;HEART WITH TIP ON THE LEFT;So;0;ON;;;;;N;;;;; 1F395;BOUQUET OF FLOWERS;So;0;ON;;;;;N;;;;; 1F396;MILITARY MEDAL;So;0;ON;;;;;N;;;;; 1F397;REMINDER RIBBON;So;0;ON;;;;;N;;;;; 1F398;MUSICAL KEYBOARD WITH JACKS;So;0;ON;;;;;N;;;;; 1F399;STUDIO MICROPHONE;So;0;ON;;;;;N;;;;; 1F39A;LEVEL SLIDER;So;0;ON;;;;;N;;;;; 1F39B;CONTROL KNOBS;So;0;ON;;;;;N;;;;; 1F39C;BEAMED ASCENDING MUSICAL NOTES;So;0;ON;;;;;N;;;;; 1F39D;BEAMED DESCENDING MUSICAL NOTES;So;0;ON;;;;;N;;;;; 1F39E;FILM FRAMES;So;0;ON;;;;;N;;;;; 1F39F;ADMISSION TICKETS;So;0;ON;;;;;N;;;;; 1F3A0;CAROUSEL HORSE;So;0;ON;;;;;N;;;;; 1F3A1;FERRIS WHEEL;So;0;ON;;;;;N;;;;; 1F3A2;ROLLER COASTER;So;0;ON;;;;;N;;;;; 1F3A3;FISHING POLE AND FISH;So;0;ON;;;;;N;;;;; 1F3A4;MICROPHONE;So;0;ON;;;;;N;;;;; 1F3A5;MOVIE CAMERA;So;0;ON;;;;;N;;;;; 1F3A6;CINEMA;So;0;ON;;;;;N;;;;; 1F3A7;HEADPHONE;So;0;ON;;;;;N;;;;; 1F3A8;ARTIST PALETTE;So;0;ON;;;;;N;;;;; 1F3A9;TOP HAT;So;0;ON;;;;;N;;;;; 1F3AA;CIRCUS TENT;So;0;ON;;;;;N;;;;; 1F3AB;TICKET;So;0;ON;;;;;N;;;;; 1F3AC;CLAPPER BOARD;So;0;ON;;;;;N;;;;; 1F3AD;PERFORMING ARTS;So;0;ON;;;;;N;;;;; 1F3AE;VIDEO GAME;So;0;ON;;;;;N;;;;; 1F3AF;DIRECT HIT;So;0;ON;;;;;N;;;;; 1F3B0;SLOT MACHINE;So;0;ON;;;;;N;;;;; 1F3B1;BILLIARDS;So;0;ON;;;;;N;;;;; 1F3B2;GAME DIE;So;0;ON;;;;;N;;;;; 1F3B3;BOWLING;So;0;ON;;;;;N;;;;; 1F3B4;FLOWER PLAYING CARDS;So;0;ON;;;;;N;;;;; 1F3B5;MUSICAL NOTE;So;0;ON;;;;;N;;;;; 1F3B6;MULTIPLE MUSICAL NOTES;So;0;ON;;;;;N;;;;; 1F3B7;SAXOPHONE;So;0;ON;;;;;N;;;;; 1F3B8;GUITAR;So;0;ON;;;;;N;;;;; 1F3B9;MUSICAL KEYBOARD;So;0;ON;;;;;N;;;;; 1F3BA;TRUMPET;So;0;ON;;;;;N;;;;; 1F3BB;VIOLIN;So;0;ON;;;;;N;;;;; 1F3BC;MUSICAL SCORE;So;0;ON;;;;;N;;;;; 1F3BD;RUNNING SHIRT WITH SASH;So;0;ON;;;;;N;;;;; 1F3BE;TENNIS RACQUET AND BALL;So;0;ON;;;;;N;;;;; 1F3BF;SKI AND SKI BOOT;So;0;ON;;;;;N;;;;; 1F3C0;BASKETBALL AND HOOP;So;0;ON;;;;;N;;;;; 1F3C1;CHEQUERED FLAG;So;0;ON;;;;;N;;;;; 1F3C2;SNOWBOARDER;So;0;ON;;;;;N;;;;; 1F3C3;RUNNER;So;0;ON;;;;;N;;;;; 1F3C4;SURFER;So;0;ON;;;;;N;;;;; 1F3C5;SPORTS MEDAL;So;0;ON;;;;;N;;;;; 1F3C6;TROPHY;So;0;ON;;;;;N;;;;; 1F3C7;HORSE RACING;So;0;ON;;;;;N;;;;; 1F3C8;AMERICAN FOOTBALL;So;0;ON;;;;;N;;;;; 1F3C9;RUGBY FOOTBALL;So;0;ON;;;;;N;;;;; 1F3CA;SWIMMER;So;0;ON;;;;;N;;;;; 1F3CB;WEIGHT LIFTER;So;0;ON;;;;;N;;;;; 1F3CC;GOLFER;So;0;ON;;;;;N;;;;; 1F3CD;RACING MOTORCYCLE;So;0;ON;;;;;N;;;;; 1F3CE;RACING CAR;So;0;ON;;;;;N;;;;; 1F3CF;CRICKET BAT AND BALL;So;0;ON;;;;;N;;;;; 1F3D0;VOLLEYBALL;So;0;ON;;;;;N;;;;; 1F3D1;FIELD HOCKEY STICK AND BALL;So;0;ON;;;;;N;;;;; 1F3D2;ICE HOCKEY STICK AND PUCK;So;0;ON;;;;;N;;;;; 1F3D3;TABLE TENNIS PADDLE AND BALL;So;0;ON;;;;;N;;;;; 1F3D4;SNOW CAPPED MOUNTAIN;So;0;ON;;;;;N;;;;; 1F3D5;CAMPING;So;0;ON;;;;;N;;;;; 1F3D6;BEACH WITH UMBRELLA;So;0;ON;;;;;N;;;;; 1F3D7;BUILDING CONSTRUCTION;So;0;ON;;;;;N;;;;; 1F3D8;HOUSE BUILDINGS;So;0;ON;;;;;N;;;;; 1F3D9;CITYSCAPE;So;0;ON;;;;;N;;;;; 1F3DA;DERELICT HOUSE BUILDING;So;0;ON;;;;;N;;;;; 1F3DB;CLASSICAL BUILDING;So;0;ON;;;;;N;;;;; 1F3DC;DESERT;So;0;ON;;;;;N;;;;; 1F3DD;DESERT ISLAND;So;0;ON;;;;;N;;;;; 1F3DE;NATIONAL PARK;So;0;ON;;;;;N;;;;; 1F3DF;STADIUM;So;0;ON;;;;;N;;;;; 1F3E0;HOUSE BUILDING;So;0;ON;;;;;N;;;;; 1F3E1;HOUSE WITH GARDEN;So;0;ON;;;;;N;;;;; 1F3E2;OFFICE BUILDING;So;0;ON;;;;;N;;;;; 1F3E3;JAPANESE POST OFFICE;So;0;ON;;;;;N;;;;; 1F3E4;EUROPEAN POST OFFICE;So;0;ON;;;;;N;;;;; 1F3E5;HOSPITAL;So;0;ON;;;;;N;;;;; 1F3E6;BANK;So;0;ON;;;;;N;;;;; 1F3E7;AUTOMATED TELLER MACHINE;So;0;ON;;;;;N;;;;; 1F3E8;HOTEL;So;0;ON;;;;;N;;;;; 1F3E9;LOVE HOTEL;So;0;ON;;;;;N;;;;; 1F3EA;CONVENIENCE STORE;So;0;ON;;;;;N;;;;; 1F3EB;SCHOOL;So;0;ON;;;;;N;;;;; 1F3EC;DEPARTMENT STORE;So;0;ON;;;;;N;;;;; 1F3ED;FACTORY;So;0;ON;;;;;N;;;;; 1F3EE;IZAKAYA LANTERN;So;0;ON;;;;;N;;;;; 1F3EF;JAPANESE CASTLE;So;0;ON;;;;;N;;;;; 1F3F0;EUROPEAN CASTLE;So;0;ON;;;;;N;;;;; 1F3F1;WHITE PENNANT;So;0;ON;;;;;N;;;;; 1F3F2;BLACK PENNANT;So;0;ON;;;;;N;;;;; 1F3F3;WAVING WHITE FLAG;So;0;ON;;;;;N;;;;; 1F3F4;WAVING BLACK FLAG;So;0;ON;;;;;N;;;;; 1F3F5;ROSETTE;So;0;ON;;;;;N;;;;; 1F3F6;BLACK ROSETTE;So;0;ON;;;;;N;;;;; 1F3F7;LABEL;So;0;ON;;;;;N;;;;; 1F3F8;BADMINTON RACQUET AND SHUTTLECOCK;So;0;ON;;;;;N;;;;; 1F3F9;BOW AND ARROW;So;0;ON;;;;;N;;;;; 1F3FA;AMPHORA;So;0;ON;;;;;N;;;;; 1F3FB;EMOJI MODIFIER FITZPATRICK TYPE-1-2;Sk;0;ON;;;;;N;;;;; 1F3FC;EMOJI MODIFIER FITZPATRICK TYPE-3;Sk;0;ON;;;;;N;;;;; 1F3FD;EMOJI MODIFIER FITZPATRICK TYPE-4;Sk;0;ON;;;;;N;;;;; 1F3FE;EMOJI MODIFIER FITZPATRICK TYPE-5;Sk;0;ON;;;;;N;;;;; 1F3FF;EMOJI MODIFIER FITZPATRICK TYPE-6;Sk;0;ON;;;;;N;;;;; 1F400;RAT;So;0;ON;;;;;N;;;;; 1F401;MOUSE;So;0;ON;;;;;N;;;;; 1F402;OX;So;0;ON;;;;;N;;;;; 1F403;WATER BUFFALO;So;0;ON;;;;;N;;;;; 1F404;COW;So;0;ON;;;;;N;;;;; 1F405;TIGER;So;0;ON;;;;;N;;;;; 1F406;LEOPARD;So;0;ON;;;;;N;;;;; 1F407;RABBIT;So;0;ON;;;;;N;;;;; 1F408;CAT;So;0;ON;;;;;N;;;;; 1F409;DRAGON;So;0;ON;;;;;N;;;;; 1F40A;CROCODILE;So;0;ON;;;;;N;;;;; 1F40B;WHALE;So;0;ON;;;;;N;;;;; 1F40C;SNAIL;So;0;ON;;;;;N;;;;; 1F40D;SNAKE;So;0;ON;;;;;N;;;;; 1F40E;HORSE;So;0;ON;;;;;N;;;;; 1F40F;RAM;So;0;ON;;;;;N;;;;; 1F410;GOAT;So;0;ON;;;;;N;;;;; 1F411;SHEEP;So;0;ON;;;;;N;;;;; 1F412;MONKEY;So;0;ON;;;;;N;;;;; 1F413;ROOSTER;So;0;ON;;;;;N;;;;; 1F414;CHICKEN;So;0;ON;;;;;N;;;;; 1F415;DOG;So;0;ON;;;;;N;;;;; 1F416;PIG;So;0;ON;;;;;N;;;;; 1F417;BOAR;So;0;ON;;;;;N;;;;; 1F418;ELEPHANT;So;0;ON;;;;;N;;;;; 1F419;OCTOPUS;So;0;ON;;;;;N;;;;; 1F41A;SPIRAL SHELL;So;0;ON;;;;;N;;;;; 1F41B;BUG;So;0;ON;;;;;N;;;;; 1F41C;ANT;So;0;ON;;;;;N;;;;; 1F41D;HONEYBEE;So;0;ON;;;;;N;;;;; 1F41E;LADY BEETLE;So;0;ON;;;;;N;;;;; 1F41F;FISH;So;0;ON;;;;;N;;;;; 1F420;TROPICAL FISH;So;0;ON;;;;;N;;;;; 1F421;BLOWFISH;So;0;ON;;;;;N;;;;; 1F422;TURTLE;So;0;ON;;;;;N;;;;; 1F423;HATCHING CHICK;So;0;ON;;;;;N;;;;; 1F424;BABY CHICK;So;0;ON;;;;;N;;;;; 1F425;FRONT-FACING BABY CHICK;So;0;ON;;;;;N;;;;; 1F426;BIRD;So;0;ON;;;;;N;;;;; 1F427;PENGUIN;So;0;ON;;;;;N;;;;; 1F428;KOALA;So;0;ON;;;;;N;;;;; 1F429;POODLE;So;0;ON;;;;;N;;;;; 1F42A;DROMEDARY CAMEL;So;0;ON;;;;;N;;;;; 1F42B;BACTRIAN CAMEL;So;0;ON;;;;;N;;;;; 1F42C;DOLPHIN;So;0;ON;;;;;N;;;;; 1F42D;MOUSE FACE;So;0;ON;;;;;N;;;;; 1F42E;COW FACE;So;0;ON;;;;;N;;;;; 1F42F;TIGER FACE;So;0;ON;;;;;N;;;;; 1F430;RABBIT FACE;So;0;ON;;;;;N;;;;; 1F431;CAT FACE;So;0;ON;;;;;N;;;;; 1F432;DRAGON FACE;So;0;ON;;;;;N;;;;; 1F433;SPOUTING WHALE;So;0;ON;;;;;N;;;;; 1F434;HORSE FACE;So;0;ON;;;;;N;;;;; 1F435;MONKEY FACE;So;0;ON;;;;;N;;;;; 1F436;DOG FACE;So;0;ON;;;;;N;;;;; 1F437;PIG FACE;So;0;ON;;;;;N;;;;; 1F438;FROG FACE;So;0;ON;;;;;N;;;;; 1F439;HAMSTER FACE;So;0;ON;;;;;N;;;;; 1F43A;WOLF FACE;So;0;ON;;;;;N;;;;; 1F43B;BEAR FACE;So;0;ON;;;;;N;;;;; 1F43C;PANDA FACE;So;0;ON;;;;;N;;;;; 1F43D;PIG NOSE;So;0;ON;;;;;N;;;;; 1F43E;PAW PRINTS;So;0;ON;;;;;N;;;;; 1F43F;CHIPMUNK;So;0;ON;;;;;N;;;;; 1F440;EYES;So;0;ON;;;;;N;;;;; 1F441;EYE;So;0;ON;;;;;N;;;;; 1F442;EAR;So;0;ON;;;;;N;;;;; 1F443;NOSE;So;0;ON;;;;;N;;;;; 1F444;MOUTH;So;0;ON;;;;;N;;;;; 1F445;TONGUE;So;0;ON;;;;;N;;;;; 1F446;WHITE UP POINTING BACKHAND INDEX;So;0;ON;;;;;N;;;;; 1F447;WHITE DOWN POINTING BACKHAND INDEX;So;0;ON;;;;;N;;;;; 1F448;WHITE LEFT POINTING BACKHAND INDEX;So;0;ON;;;;;N;;;;; 1F449;WHITE RIGHT POINTING BACKHAND INDEX;So;0;ON;;;;;N;;;;; 1F44A;FISTED HAND SIGN;So;0;ON;;;;;N;;;;; 1F44B;WAVING HAND SIGN;So;0;ON;;;;;N;;;;; 1F44C;OK HAND SIGN;So;0;ON;;;;;N;;;;; 1F44D;THUMBS UP SIGN;So;0;ON;;;;;N;;;;; 1F44E;THUMBS DOWN SIGN;So;0;ON;;;;;N;;;;; 1F44F;CLAPPING HANDS SIGN;So;0;ON;;;;;N;;;;; 1F450;OPEN HANDS SIGN;So;0;ON;;;;;N;;;;; 1F451;CROWN;So;0;ON;;;;;N;;;;; 1F452;WOMANS HAT;So;0;ON;;;;;N;;;;; 1F453;EYEGLASSES;So;0;ON;;;;;N;;;;; 1F454;NECKTIE;So;0;ON;;;;;N;;;;; 1F455;T-SHIRT;So;0;ON;;;;;N;;;;; 1F456;JEANS;So;0;ON;;;;;N;;;;; 1F457;DRESS;So;0;ON;;;;;N;;;;; 1F458;KIMONO;So;0;ON;;;;;N;;;;; 1F459;BIKINI;So;0;ON;;;;;N;;;;; 1F45A;WOMANS CLOTHES;So;0;ON;;;;;N;;;;; 1F45B;PURSE;So;0;ON;;;;;N;;;;; 1F45C;HANDBAG;So;0;ON;;;;;N;;;;; 1F45D;POUCH;So;0;ON;;;;;N;;;;; 1F45E;MANS SHOE;So;0;ON;;;;;N;;;;; 1F45F;ATHLETIC SHOE;So;0;ON;;;;;N;;;;; 1F460;HIGH-HEELED SHOE;So;0;ON;;;;;N;;;;; 1F461;WOMANS SANDAL;So;0;ON;;;;;N;;;;; 1F462;WOMANS BOOTS;So;0;ON;;;;;N;;;;; 1F463;FOOTPRINTS;So;0;ON;;;;;N;;;;; 1F464;BUST IN SILHOUETTE;So;0;ON;;;;;N;;;;; 1F465;BUSTS IN SILHOUETTE;So;0;ON;;;;;N;;;;; 1F466;BOY;So;0;ON;;;;;N;;;;; 1F467;GIRL;So;0;ON;;;;;N;;;;; 1F468;MAN;So;0;ON;;;;;N;;;;; 1F469;WOMAN;So;0;ON;;;;;N;;;;; 1F46A;FAMILY;So;0;ON;;;;;N;;;;; 1F46B;MAN AND WOMAN HOLDING HANDS;So;0;ON;;;;;N;;;;; 1F46C;TWO MEN HOLDING HANDS;So;0;ON;;;;;N;;;;; 1F46D;TWO WOMEN HOLDING HANDS;So;0;ON;;;;;N;;;;; 1F46E;POLICE OFFICER;So;0;ON;;;;;N;;;;; 1F46F;WOMAN WITH BUNNY EARS;So;0;ON;;;;;N;;;;; 1F470;BRIDE WITH VEIL;So;0;ON;;;;;N;;;;; 1F471;PERSON WITH BLOND HAIR;So;0;ON;;;;;N;;;;; 1F472;MAN WITH GUA PI MAO;So;0;ON;;;;;N;;;;; 1F473;MAN WITH TURBAN;So;0;ON;;;;;N;;;;; 1F474;OLDER MAN;So;0;ON;;;;;N;;;;; 1F475;OLDER WOMAN;So;0;ON;;;;;N;;;;; 1F476;BABY;So;0;ON;;;;;N;;;;; 1F477;CONSTRUCTION WORKER;So;0;ON;;;;;N;;;;; 1F478;PRINCESS;So;0;ON;;;;;N;;;;; 1F479;JAPANESE OGRE;So;0;ON;;;;;N;;;;; 1F47A;JAPANESE GOBLIN;So;0;ON;;;;;N;;;;; 1F47B;GHOST;So;0;ON;;;;;N;;;;; 1F47C;BABY ANGEL;So;0;ON;;;;;N;;;;; 1F47D;EXTRATERRESTRIAL ALIEN;So;0;ON;;;;;N;;;;; 1F47E;ALIEN MONSTER;So;0;ON;;;;;N;;;;; 1F47F;IMP;So;0;ON;;;;;N;;;;; 1F480;SKULL;So;0;ON;;;;;N;;;;; 1F481;INFORMATION DESK PERSON;So;0;ON;;;;;N;;;;; 1F482;GUARDSMAN;So;0;ON;;;;;N;;;;; 1F483;DANCER;So;0;ON;;;;;N;;;;; 1F484;LIPSTICK;So;0;ON;;;;;N;;;;; 1F485;NAIL POLISH;So;0;ON;;;;;N;;;;; 1F486;FACE MASSAGE;So;0;ON;;;;;N;;;;; 1F487;HAIRCUT;So;0;ON;;;;;N;;;;; 1F488;BARBER POLE;So;0;ON;;;;;N;;;;; 1F489;SYRINGE;So;0;ON;;;;;N;;;;; 1F48A;PILL;So;0;ON;;;;;N;;;;; 1F48B;KISS MARK;So;0;ON;;;;;N;;;;; 1F48C;LOVE LETTER;So;0;ON;;;;;N;;;;; 1F48D;RING;So;0;ON;;;;;N;;;;; 1F48E;GEM STONE;So;0;ON;;;;;N;;;;; 1F48F;KISS;So;0;ON;;;;;N;;;;; 1F490;BOUQUET;So;0;ON;;;;;N;;;;; 1F491;COUPLE WITH HEART;So;0;ON;;;;;N;;;;; 1F492;WEDDING;So;0;ON;;;;;N;;;;; 1F493;BEATING HEART;So;0;ON;;;;;N;;;;; 1F494;BROKEN HEART;So;0;ON;;;;;N;;;;; 1F495;TWO HEARTS;So;0;ON;;;;;N;;;;; 1F496;SPARKLING HEART;So;0;ON;;;;;N;;;;; 1F497;GROWING HEART;So;0;ON;;;;;N;;;;; 1F498;HEART WITH ARROW;So;0;ON;;;;;N;;;;; 1F499;BLUE HEART;So;0;ON;;;;;N;;;;; 1F49A;GREEN HEART;So;0;ON;;;;;N;;;;; 1F49B;YELLOW HEART;So;0;ON;;;;;N;;;;; 1F49C;PURPLE HEART;So;0;ON;;;;;N;;;;; 1F49D;HEART WITH RIBBON;So;0;ON;;;;;N;;;;; 1F49E;REVOLVING HEARTS;So;0;ON;;;;;N;;;;; 1F49F;HEART DECORATION;So;0;ON;;;;;N;;;;; 1F4A0;DIAMOND SHAPE WITH A DOT INSIDE;So;0;ON;;;;;N;;;;; 1F4A1;ELECTRIC LIGHT BULB;So;0;ON;;;;;N;;;;; 1F4A2;ANGER SYMBOL;So;0;ON;;;;;N;;;;; 1F4A3;BOMB;So;0;ON;;;;;N;;;;; 1F4A4;SLEEPING SYMBOL;So;0;ON;;;;;N;;;;; 1F4A5;COLLISION SYMBOL;So;0;ON;;;;;N;;;;; 1F4A6;SPLASHING SWEAT SYMBOL;So;0;ON;;;;;N;;;;; 1F4A7;DROPLET;So;0;ON;;;;;N;;;;; 1F4A8;DASH SYMBOL;So;0;ON;;;;;N;;;;; 1F4A9;PILE OF POO;So;0;ON;;;;;N;;;;; 1F4AA;FLEXED BICEPS;So;0;ON;;;;;N;;;;; 1F4AB;DIZZY SYMBOL;So;0;ON;;;;;N;;;;; 1F4AC;SPEECH BALLOON;So;0;ON;;;;;N;;;;; 1F4AD;THOUGHT BALLOON;So;0;ON;;;;;N;;;;; 1F4AE;WHITE FLOWER;So;0;ON;;;;;N;;;;; 1F4AF;HUNDRED POINTS SYMBOL;So;0;ON;;;;;N;;;;; 1F4B0;MONEY BAG;So;0;ON;;;;;N;;;;; 1F4B1;CURRENCY EXCHANGE;So;0;ON;;;;;N;;;;; 1F4B2;HEAVY DOLLAR SIGN;So;0;ON;;;;;N;;;;; 1F4B3;CREDIT CARD;So;0;ON;;;;;N;;;;; 1F4B4;BANKNOTE WITH YEN SIGN;So;0;ON;;;;;N;;;;; 1F4B5;BANKNOTE WITH DOLLAR SIGN;So;0;ON;;;;;N;;;;; 1F4B6;BANKNOTE WITH EURO SIGN;So;0;ON;;;;;N;;;;; 1F4B7;BANKNOTE WITH POUND SIGN;So;0;ON;;;;;N;;;;; 1F4B8;MONEY WITH WINGS;So;0;ON;;;;;N;;;;; 1F4B9;CHART WITH UPWARDS TREND AND YEN SIGN;So;0;ON;;;;;N;;;;; 1F4BA;SEAT;So;0;ON;;;;;N;;;;; 1F4BB;PERSONAL COMPUTER;So;0;ON;;;;;N;;;;; 1F4BC;BRIEFCASE;So;0;ON;;;;;N;;;;; 1F4BD;MINIDISC;So;0;ON;;;;;N;;;;; 1F4BE;FLOPPY DISK;So;0;ON;;;;;N;;;;; 1F4BF;OPTICAL DISC;So;0;ON;;;;;N;;;;; 1F4C0;DVD;So;0;ON;;;;;N;;;;; 1F4C1;FILE FOLDER;So;0;ON;;;;;N;;;;; 1F4C2;OPEN FILE FOLDER;So;0;ON;;;;;N;;;;; 1F4C3;PAGE WITH CURL;So;0;ON;;;;;N;;;;; 1F4C4;PAGE FACING UP;So;0;ON;;;;;N;;;;; 1F4C5;CALENDAR;So;0;ON;;;;;N;;;;; 1F4C6;TEAR-OFF CALENDAR;So;0;ON;;;;;N;;;;; 1F4C7;CARD INDEX;So;0;ON;;;;;N;;;;; 1F4C8;CHART WITH UPWARDS TREND;So;0;ON;;;;;N;;;;; 1F4C9;CHART WITH DOWNWARDS TREND;So;0;ON;;;;;N;;;;; 1F4CA;BAR CHART;So;0;ON;;;;;N;;;;; 1F4CB;CLIPBOARD;So;0;ON;;;;;N;;;;; 1F4CC;PUSHPIN;So;0;ON;;;;;N;;;;; 1F4CD;ROUND PUSHPIN;So;0;ON;;;;;N;;;;; 1F4CE;PAPERCLIP;So;0;ON;;;;;N;;;;; 1F4CF;STRAIGHT RULER;So;0;ON;;;;;N;;;;; 1F4D0;TRIANGULAR RULER;So;0;ON;;;;;N;;;;; 1F4D1;BOOKMARK TABS;So;0;ON;;;;;N;;;;; 1F4D2;LEDGER;So;0;ON;;;;;N;;;;; 1F4D3;NOTEBOOK;So;0;ON;;;;;N;;;;; 1F4D4;NOTEBOOK WITH DECORATIVE COVER;So;0;ON;;;;;N;;;;; 1F4D5;CLOSED BOOK;So;0;ON;;;;;N;;;;; 1F4D6;OPEN BOOK;So;0;ON;;;;;N;;;;; 1F4D7;GREEN BOOK;So;0;ON;;;;;N;;;;; 1F4D8;BLUE BOOK;So;0;ON;;;;;N;;;;; 1F4D9;ORANGE BOOK;So;0;ON;;;;;N;;;;; 1F4DA;BOOKS;So;0;ON;;;;;N;;;;; 1F4DB;NAME BADGE;So;0;ON;;;;;N;;;;; 1F4DC;SCROLL;So;0;ON;;;;;N;;;;; 1F4DD;MEMO;So;0;ON;;;;;N;;;;; 1F4DE;TELEPHONE RECEIVER;So;0;ON;;;;;N;;;;; 1F4DF;PAGER;So;0;ON;;;;;N;;;;; 1F4E0;FAX MACHINE;So;0;ON;;;;;N;;;;; 1F4E1;SATELLITE ANTENNA;So;0;ON;;;;;N;;;;; 1F4E2;PUBLIC ADDRESS LOUDSPEAKER;So;0;ON;;;;;N;;;;; 1F4E3;CHEERING MEGAPHONE;So;0;ON;;;;;N;;;;; 1F4E4;OUTBOX TRAY;So;0;ON;;;;;N;;;;; 1F4E5;INBOX TRAY;So;0;ON;;;;;N;;;;; 1F4E6;PACKAGE;So;0;ON;;;;;N;;;;; 1F4E7;E-MAIL SYMBOL;So;0;ON;;;;;N;;;;; 1F4E8;INCOMING ENVELOPE;So;0;ON;;;;;N;;;;; 1F4E9;ENVELOPE WITH DOWNWARDS ARROW ABOVE;So;0;ON;;;;;N;;;;; 1F4EA;CLOSED MAILBOX WITH LOWERED FLAG;So;0;ON;;;;;N;;;;; 1F4EB;CLOSED MAILBOX WITH RAISED FLAG;So;0;ON;;;;;N;;;;; 1F4EC;OPEN MAILBOX WITH RAISED FLAG;So;0;ON;;;;;N;;;;; 1F4ED;OPEN MAILBOX WITH LOWERED FLAG;So;0;ON;;;;;N;;;;; 1F4EE;POSTBOX;So;0;ON;;;;;N;;;;; 1F4EF;POSTAL HORN;So;0;ON;;;;;N;;;;; 1F4F0;NEWSPAPER;So;0;ON;;;;;N;;;;; 1F4F1;MOBILE PHONE;So;0;ON;;;;;N;;;;; 1F4F2;MOBILE PHONE WITH RIGHTWARDS ARROW AT LEFT;So;0;ON;;;;;N;;;;; 1F4F3;VIBRATION MODE;So;0;ON;;;;;N;;;;; 1F4F4;MOBILE PHONE OFF;So;0;ON;;;;;N;;;;; 1F4F5;NO MOBILE PHONES;So;0;ON;;;;;N;;;;; 1F4F6;ANTENNA WITH BARS;So;0;ON;;;;;N;;;;; 1F4F7;CAMERA;So;0;ON;;;;;N;;;;; 1F4F8;CAMERA WITH FLASH;So;0;ON;;;;;N;;;;; 1F4F9;VIDEO CAMERA;So;0;ON;;;;;N;;;;; 1F4FA;TELEVISION;So;0;ON;;;;;N;;;;; 1F4FB;RADIO;So;0;ON;;;;;N;;;;; 1F4FC;VIDEOCASSETTE;So;0;ON;;;;;N;;;;; 1F4FD;FILM PROJECTOR;So;0;ON;;;;;N;;;;; 1F4FE;PORTABLE STEREO;So;0;ON;;;;;N;;;;; 1F4FF;PRAYER BEADS;So;0;ON;;;;;N;;;;; 1F500;TWISTED RIGHTWARDS ARROWS;So;0;ON;;;;;N;;;;; 1F501;CLOCKWISE RIGHTWARDS AND LEFTWARDS OPEN CIRCLE ARROWS;So;0;ON;;;;;N;;;;; 1F502;CLOCKWISE RIGHTWARDS AND LEFTWARDS OPEN CIRCLE ARROWS WITH CIRCLED ONE OVERLAY;So;0;ON;;;;;N;;;;; 1F503;CLOCKWISE DOWNWARDS AND UPWARDS OPEN CIRCLE ARROWS;So;0;ON;;;;;N;;;;; 1F504;ANTICLOCKWISE DOWNWARDS AND UPWARDS OPEN CIRCLE ARROWS;So;0;ON;;;;;N;;;;; 1F505;LOW BRIGHTNESS SYMBOL;So;0;ON;;;;;N;;;;; 1F506;HIGH BRIGHTNESS SYMBOL;So;0;ON;;;;;N;;;;; 1F507;SPEAKER WITH CANCELLATION STROKE;So;0;ON;;;;;N;;;;; 1F508;SPEAKER;So;0;ON;;;;;N;;;;; 1F509;SPEAKER WITH ONE SOUND WAVE;So;0;ON;;;;;N;;;;; 1F50A;SPEAKER WITH THREE SOUND WAVES;So;0;ON;;;;;N;;;;; 1F50B;BATTERY;So;0;ON;;;;;N;;;;; 1F50C;ELECTRIC PLUG;So;0;ON;;;;;N;;;;; 1F50D;LEFT-POINTING MAGNIFYING GLASS;So;0;ON;;;;;N;;;;; 1F50E;RIGHT-POINTING MAGNIFYING GLASS;So;0;ON;;;;;N;;;;; 1F50F;LOCK WITH INK PEN;So;0;ON;;;;;N;;;;; 1F510;CLOSED LOCK WITH KEY;So;0;ON;;;;;N;;;;; 1F511;KEY;So;0;ON;;;;;N;;;;; 1F512;LOCK;So;0;ON;;;;;N;;;;; 1F513;OPEN LOCK;So;0;ON;;;;;N;;;;; 1F514;BELL;So;0;ON;;;;;N;;;;; 1F515;BELL WITH CANCELLATION STROKE;So;0;ON;;;;;N;;;;; 1F516;BOOKMARK;So;0;ON;;;;;N;;;;; 1F517;LINK SYMBOL;So;0;ON;;;;;N;;;;; 1F518;RADIO BUTTON;So;0;ON;;;;;N;;;;; 1F519;BACK WITH LEFTWARDS ARROW ABOVE;So;0;ON;;;;;N;;;;; 1F51A;END WITH LEFTWARDS ARROW ABOVE;So;0;ON;;;;;N;;;;; 1F51B;ON WITH EXCLAMATION MARK WITH LEFT RIGHT ARROW ABOVE;So;0;ON;;;;;N;;;;; 1F51C;SOON WITH RIGHTWARDS ARROW ABOVE;So;0;ON;;;;;N;;;;; 1F51D;TOP WITH UPWARDS ARROW ABOVE;So;0;ON;;;;;N;;;;; 1F51E;NO ONE UNDER EIGHTEEN SYMBOL;So;0;ON;;;;;N;;;;; 1F51F;KEYCAP TEN;So;0;ON;;;;;N;;;;; 1F520;INPUT SYMBOL FOR LATIN CAPITAL LETTERS;So;0;ON;;;;;N;;;;; 1F521;INPUT SYMBOL FOR LATIN SMALL LETTERS;So;0;ON;;;;;N;;;;; 1F522;INPUT SYMBOL FOR NUMBERS;So;0;ON;;;;;N;;;;; 1F523;INPUT SYMBOL FOR SYMBOLS;So;0;ON;;;;;N;;;;; 1F524;INPUT SYMBOL FOR LATIN LETTERS;So;0;ON;;;;;N;;;;; 1F525;FIRE;So;0;ON;;;;;N;;;;; 1F526;ELECTRIC TORCH;So;0;ON;;;;;N;;;;; 1F527;WRENCH;So;0;ON;;;;;N;;;;; 1F528;HAMMER;So;0;ON;;;;;N;;;;; 1F529;NUT AND BOLT;So;0;ON;;;;;N;;;;; 1F52A;HOCHO;So;0;ON;;;;;N;;;;; 1F52B;PISTOL;So;0;ON;;;;;N;;;;; 1F52C;MICROSCOPE;So;0;ON;;;;;N;;;;; 1F52D;TELESCOPE;So;0;ON;;;;;N;;;;; 1F52E;CRYSTAL BALL;So;0;ON;;;;;N;;;;; 1F52F;SIX POINTED STAR WITH MIDDLE DOT;So;0;ON;;;;;N;;;;; 1F530;JAPANESE SYMBOL FOR BEGINNER;So;0;ON;;;;;N;;;;; 1F531;TRIDENT EMBLEM;So;0;ON;;;;;N;;;;; 1F532;BLACK SQUARE BUTTON;So;0;ON;;;;;N;;;;; 1F533;WHITE SQUARE BUTTON;So;0;ON;;;;;N;;;;; 1F534;LARGE RED CIRCLE;So;0;ON;;;;;N;;;;; 1F535;LARGE BLUE CIRCLE;So;0;ON;;;;;N;;;;; 1F536;LARGE ORANGE DIAMOND;So;0;ON;;;;;N;;;;; 1F537;LARGE BLUE DIAMOND;So;0;ON;;;;;N;;;;; 1F538;SMALL ORANGE DIAMOND;So;0;ON;;;;;N;;;;; 1F539;SMALL BLUE DIAMOND;So;0;ON;;;;;N;;;;; 1F53A;UP-POINTING RED TRIANGLE;So;0;ON;;;;;N;;;;; 1F53B;DOWN-POINTING RED TRIANGLE;So;0;ON;;;;;N;;;;; 1F53C;UP-POINTING SMALL RED TRIANGLE;So;0;ON;;;;;N;;;;; 1F53D;DOWN-POINTING SMALL RED TRIANGLE;So;0;ON;;;;;N;;;;; 1F53E;LOWER RIGHT SHADOWED WHITE CIRCLE;So;0;ON;;;;;N;;;;; 1F53F;UPPER RIGHT SHADOWED WHITE CIRCLE;So;0;ON;;;;;N;;;;; 1F540;CIRCLED CROSS POMMEE;So;0;ON;;;;;N;;;;; 1F541;CROSS POMMEE WITH HALF-CIRCLE BELOW;So;0;ON;;;;;N;;;;; 1F542;CROSS POMMEE;So;0;ON;;;;;N;;;;; 1F543;NOTCHED LEFT SEMICIRCLE WITH THREE DOTS;So;0;ON;;;;;N;;;;; 1F544;NOTCHED RIGHT SEMICIRCLE WITH THREE DOTS;So;0;ON;;;;;N;;;;; 1F545;SYMBOL FOR MARKS CHAPTER;So;0;ON;;;;;N;;;;; 1F546;WHITE LATIN CROSS;So;0;ON;;;;;N;;;;; 1F547;HEAVY LATIN CROSS;So;0;ON;;;;;N;;;;; 1F548;CELTIC CROSS;So;0;ON;;;;;N;;;;; 1F549;OM SYMBOL;So;0;ON;;;;;N;;;;; 1F54A;DOVE OF PEACE;So;0;ON;;;;;N;;;;; 1F54B;KAABA;So;0;ON;;;;;N;;;;; 1F54C;MOSQUE;So;0;ON;;;;;N;;;;; 1F54D;SYNAGOGUE;So;0;ON;;;;;N;;;;; 1F54E;MENORAH WITH NINE BRANCHES;So;0;ON;;;;;N;;;;; 1F54F;BOWL OF HYGIEIA;So;0;ON;;;;;N;;;;; 1F550;CLOCK FACE ONE OCLOCK;So;0;ON;;;;;N;;;;; 1F551;CLOCK FACE TWO OCLOCK;So;0;ON;;;;;N;;;;; 1F552;CLOCK FACE THREE OCLOCK;So;0;ON;;;;;N;;;;; 1F553;CLOCK FACE FOUR OCLOCK;So;0;ON;;;;;N;;;;; 1F554;CLOCK FACE FIVE OCLOCK;So;0;ON;;;;;N;;;;; 1F555;CLOCK FACE SIX OCLOCK;So;0;ON;;;;;N;;;;; 1F556;CLOCK FACE SEVEN OCLOCK;So;0;ON;;;;;N;;;;; 1F557;CLOCK FACE EIGHT OCLOCK;So;0;ON;;;;;N;;;;; 1F558;CLOCK FACE NINE OCLOCK;So;0;ON;;;;;N;;;;; 1F559;CLOCK FACE TEN OCLOCK;So;0;ON;;;;;N;;;;; 1F55A;CLOCK FACE ELEVEN OCLOCK;So;0;ON;;;;;N;;;;; 1F55B;CLOCK FACE TWELVE OCLOCK;So;0;ON;;;;;N;;;;; 1F55C;CLOCK FACE ONE-THIRTY;So;0;ON;;;;;N;;;;; 1F55D;CLOCK FACE TWO-THIRTY;So;0;ON;;;;;N;;;;; 1F55E;CLOCK FACE THREE-THIRTY;So;0;ON;;;;;N;;;;; 1F55F;CLOCK FACE FOUR-THIRTY;So;0;ON;;;;;N;;;;; 1F560;CLOCK FACE FIVE-THIRTY;So;0;ON;;;;;N;;;;; 1F561;CLOCK FACE SIX-THIRTY;So;0;ON;;;;;N;;;;; 1F562;CLOCK FACE SEVEN-THIRTY;So;0;ON;;;;;N;;;;; 1F563;CLOCK FACE EIGHT-THIRTY;So;0;ON;;;;;N;;;;; 1F564;CLOCK FACE NINE-THIRTY;So;0;ON;;;;;N;;;;; 1F565;CLOCK FACE TEN-THIRTY;So;0;ON;;;;;N;;;;; 1F566;CLOCK FACE ELEVEN-THIRTY;So;0;ON;;;;;N;;;;; 1F567;CLOCK FACE TWELVE-THIRTY;So;0;ON;;;;;N;;;;; 1F568;RIGHT SPEAKER;So;0;ON;;;;;N;;;;; 1F569;RIGHT SPEAKER WITH ONE SOUND WAVE;So;0;ON;;;;;N;;;;; 1F56A;RIGHT SPEAKER WITH THREE SOUND WAVES;So;0;ON;;;;;N;;;;; 1F56B;BULLHORN;So;0;ON;;;;;N;;;;; 1F56C;BULLHORN WITH SOUND WAVES;So;0;ON;;;;;N;;;;; 1F56D;RINGING BELL;So;0;ON;;;;;N;;;;; 1F56E;BOOK;So;0;ON;;;;;N;;;;; 1F56F;CANDLE;So;0;ON;;;;;N;;;;; 1F570;MANTELPIECE CLOCK;So;0;ON;;;;;N;;;;; 1F571;BLACK SKULL AND CROSSBONES;So;0;ON;;;;;N;;;;; 1F572;NO PIRACY;So;0;ON;;;;;N;;;;; 1F573;HOLE;So;0;ON;;;;;N;;;;; 1F574;MAN IN BUSINESS SUIT LEVITATING;So;0;ON;;;;;N;;;;; 1F575;SLEUTH OR SPY;So;0;ON;;;;;N;;;;; 1F576;DARK SUNGLASSES;So;0;ON;;;;;N;;;;; 1F577;SPIDER;So;0;ON;;;;;N;;;;; 1F578;SPIDER WEB;So;0;ON;;;;;N;;;;; 1F579;JOYSTICK;So;0;ON;;;;;N;;;;; 1F57A;MAN DANCING;So;0;ON;;;;;N;;;;; 1F57B;LEFT HAND TELEPHONE RECEIVER;So;0;ON;;;;;N;;;;; 1F57C;TELEPHONE RECEIVER WITH PAGE;So;0;ON;;;;;N;;;;; 1F57D;RIGHT HAND TELEPHONE RECEIVER;So;0;ON;;;;;N;;;;; 1F57E;WHITE TOUCHTONE TELEPHONE;So;0;ON;;;;;N;;;;; 1F57F;BLACK TOUCHTONE TELEPHONE;So;0;ON;;;;;N;;;;; 1F580;TELEPHONE ON TOP OF MODEM;So;0;ON;;;;;N;;;;; 1F581;CLAMSHELL MOBILE PHONE;So;0;ON;;;;;N;;;;; 1F582;BACK OF ENVELOPE;So;0;ON;;;;;N;;;;; 1F583;STAMPED ENVELOPE;So;0;ON;;;;;N;;;;; 1F584;ENVELOPE WITH LIGHTNING;So;0;ON;;;;;N;;;;; 1F585;FLYING ENVELOPE;So;0;ON;;;;;N;;;;; 1F586;PEN OVER STAMPED ENVELOPE;So;0;ON;;;;;N;;;;; 1F587;LINKED PAPERCLIPS;So;0;ON;;;;;N;;;;; 1F588;BLACK PUSHPIN;So;0;ON;;;;;N;;;;; 1F589;LOWER LEFT PENCIL;So;0;ON;;;;;N;;;;; 1F58A;LOWER LEFT BALLPOINT PEN;So;0;ON;;;;;N;;;;; 1F58B;LOWER LEFT FOUNTAIN PEN;So;0;ON;;;;;N;;;;; 1F58C;LOWER LEFT PAINTBRUSH;So;0;ON;;;;;N;;;;; 1F58D;LOWER LEFT CRAYON;So;0;ON;;;;;N;;;;; 1F58E;LEFT WRITING HAND;So;0;ON;;;;;N;;;;; 1F58F;TURNED OK HAND SIGN;So;0;ON;;;;;N;;;;; 1F590;RAISED HAND WITH FINGERS SPLAYED;So;0;ON;;;;;N;;;;; 1F591;REVERSED RAISED HAND WITH FINGERS SPLAYED;So;0;ON;;;;;N;;;;; 1F592;REVERSED THUMBS UP SIGN;So;0;ON;;;;;N;;;;; 1F593;REVERSED THUMBS DOWN SIGN;So;0;ON;;;;;N;;;;; 1F594;REVERSED VICTORY HAND;So;0;ON;;;;;N;;;;; 1F595;REVERSED HAND WITH MIDDLE FINGER EXTENDED;So;0;ON;;;;;N;;;;; 1F596;RAISED HAND WITH PART BETWEEN MIDDLE AND RING FINGERS;So;0;ON;;;;;N;;;;; 1F597;WHITE DOWN POINTING LEFT HAND INDEX;So;0;ON;;;;;N;;;;; 1F598;SIDEWAYS WHITE LEFT POINTING INDEX;So;0;ON;;;;;N;;;;; 1F599;SIDEWAYS WHITE RIGHT POINTING INDEX;So;0;ON;;;;;N;;;;; 1F59A;SIDEWAYS BLACK LEFT POINTING INDEX;So;0;ON;;;;;N;;;;; 1F59B;SIDEWAYS BLACK RIGHT POINTING INDEX;So;0;ON;;;;;N;;;;; 1F59C;BLACK LEFT POINTING BACKHAND INDEX;So;0;ON;;;;;N;;;;; 1F59D;BLACK RIGHT POINTING BACKHAND INDEX;So;0;ON;;;;;N;;;;; 1F59E;SIDEWAYS WHITE UP POINTING INDEX;So;0;ON;;;;;N;;;;; 1F59F;SIDEWAYS WHITE DOWN POINTING INDEX;So;0;ON;;;;;N;;;;; 1F5A0;SIDEWAYS BLACK UP POINTING INDEX;So;0;ON;;;;;N;;;;; 1F5A1;SIDEWAYS BLACK DOWN POINTING INDEX;So;0;ON;;;;;N;;;;; 1F5A2;BLACK UP POINTING BACKHAND INDEX;So;0;ON;;;;;N;;;;; 1F5A3;BLACK DOWN POINTING BACKHAND INDEX;So;0;ON;;;;;N;;;;; 1F5A4;BLACK HEART;So;0;ON;;;;;N;;;;; 1F5A5;DESKTOP COMPUTER;So;0;ON;;;;;N;;;;; 1F5A6;KEYBOARD AND MOUSE;So;0;ON;;;;;N;;;;; 1F5A7;THREE NETWORKED COMPUTERS;So;0;ON;;;;;N;;;;; 1F5A8;PRINTER;So;0;ON;;;;;N;;;;; 1F5A9;POCKET CALCULATOR;So;0;ON;;;;;N;;;;; 1F5AA;BLACK HARD SHELL FLOPPY DISK;So;0;ON;;;;;N;;;;; 1F5AB;WHITE HARD SHELL FLOPPY DISK;So;0;ON;;;;;N;;;;; 1F5AC;SOFT SHELL FLOPPY DISK;So;0;ON;;;;;N;;;;; 1F5AD;TAPE CARTRIDGE;So;0;ON;;;;;N;;;;; 1F5AE;WIRED KEYBOARD;So;0;ON;;;;;N;;;;; 1F5AF;ONE BUTTON MOUSE;So;0;ON;;;;;N;;;;; 1F5B0;TWO BUTTON MOUSE;So;0;ON;;;;;N;;;;; 1F5B1;THREE BUTTON MOUSE;So;0;ON;;;;;N;;;;; 1F5B2;TRACKBALL;So;0;ON;;;;;N;;;;; 1F5B3;OLD PERSONAL COMPUTER;So;0;ON;;;;;N;;;;; 1F5B4;HARD DISK;So;0;ON;;;;;N;;;;; 1F5B5;SCREEN;So;0;ON;;;;;N;;;;; 1F5B6;PRINTER ICON;So;0;ON;;;;;N;;;;; 1F5B7;FAX ICON;So;0;ON;;;;;N;;;;; 1F5B8;OPTICAL DISC ICON;So;0;ON;;;;;N;;;;; 1F5B9;DOCUMENT WITH TEXT;So;0;ON;;;;;N;;;;; 1F5BA;DOCUMENT WITH TEXT AND PICTURE;So;0;ON;;;;;N;;;;; 1F5BB;DOCUMENT WITH PICTURE;So;0;ON;;;;;N;;;;; 1F5BC;FRAME WITH PICTURE;So;0;ON;;;;;N;;;;; 1F5BD;FRAME WITH TILES;So;0;ON;;;;;N;;;;; 1F5BE;FRAME WITH AN X;So;0;ON;;;;;N;;;;; 1F5BF;BLACK FOLDER;So;0;ON;;;;;N;;;;; 1F5C0;FOLDER;So;0;ON;;;;;N;;;;; 1F5C1;OPEN FOLDER;So;0;ON;;;;;N;;;;; 1F5C2;CARD INDEX DIVIDERS;So;0;ON;;;;;N;;;;; 1F5C3;CARD FILE BOX;So;0;ON;;;;;N;;;;; 1F5C4;FILE CABINET;So;0;ON;;;;;N;;;;; 1F5C5;EMPTY NOTE;So;0;ON;;;;;N;;;;; 1F5C6;EMPTY NOTE PAGE;So;0;ON;;;;;N;;;;; 1F5C7;EMPTY NOTE PAD;So;0;ON;;;;;N;;;;; 1F5C8;NOTE;So;0;ON;;;;;N;;;;; 1F5C9;NOTE PAGE;So;0;ON;;;;;N;;;;; 1F5CA;NOTE PAD;So;0;ON;;;;;N;;;;; 1F5CB;EMPTY DOCUMENT;So;0;ON;;;;;N;;;;; 1F5CC;EMPTY PAGE;So;0;ON;;;;;N;;;;; 1F5CD;EMPTY PAGES;So;0;ON;;;;;N;;;;; 1F5CE;DOCUMENT;So;0;ON;;;;;N;;;;; 1F5CF;PAGE;So;0;ON;;;;;N;;;;; 1F5D0;PAGES;So;0;ON;;;;;N;;;;; 1F5D1;WASTEBASKET;So;0;ON;;;;;N;;;;; 1F5D2;SPIRAL NOTE PAD;So;0;ON;;;;;N;;;;; 1F5D3;SPIRAL CALENDAR PAD;So;0;ON;;;;;N;;;;; 1F5D4;DESKTOP WINDOW;So;0;ON;;;;;N;;;;; 1F5D5;MINIMIZE;So;0;ON;;;;;N;;;;; 1F5D6;MAXIMIZE;So;0;ON;;;;;N;;;;; 1F5D7;OVERLAP;So;0;ON;;;;;N;;;;; 1F5D8;CLOCKWISE RIGHT AND LEFT SEMICIRCLE ARROWS;So;0;ON;;;;;N;;;;; 1F5D9;CANCELLATION X;So;0;ON;;;;;N;;;;; 1F5DA;INCREASE FONT SIZE SYMBOL;So;0;ON;;;;;N;;;;; 1F5DB;DECREASE FONT SIZE SYMBOL;So;0;ON;;;;;N;;;;; 1F5DC;COMPRESSION;So;0;ON;;;;;N;;;;; 1F5DD;OLD KEY;So;0;ON;;;;;N;;;;; 1F5DE;ROLLED-UP NEWSPAPER;So;0;ON;;;;;N;;;;; 1F5DF;PAGE WITH CIRCLED TEXT;So;0;ON;;;;;N;;;;; 1F5E0;STOCK CHART;So;0;ON;;;;;N;;;;; 1F5E1;DAGGER KNIFE;So;0;ON;;;;;N;;;;; 1F5E2;LIPS;So;0;ON;;;;;N;;;;; 1F5E3;SPEAKING HEAD IN SILHOUETTE;So;0;ON;;;;;N;;;;; 1F5E4;THREE RAYS ABOVE;So;0;ON;;;;;N;;;;; 1F5E5;THREE RAYS BELOW;So;0;ON;;;;;N;;;;; 1F5E6;THREE RAYS LEFT;So;0;ON;;;;;N;;;;; 1F5E7;THREE RAYS RIGHT;So;0;ON;;;;;N;;;;; 1F5E8;LEFT SPEECH BUBBLE;So;0;ON;;;;;N;;;;; 1F5E9;RIGHT SPEECH BUBBLE;So;0;ON;;;;;N;;;;; 1F5EA;TWO SPEECH BUBBLES;So;0;ON;;;;;N;;;;; 1F5EB;THREE SPEECH BUBBLES;So;0;ON;;;;;N;;;;; 1F5EC;LEFT THOUGHT BUBBLE;So;0;ON;;;;;N;;;;; 1F5ED;RIGHT THOUGHT BUBBLE;So;0;ON;;;;;N;;;;; 1F5EE;LEFT ANGER BUBBLE;So;0;ON;;;;;N;;;;; 1F5EF;RIGHT ANGER BUBBLE;So;0;ON;;;;;N;;;;; 1F5F0;MOOD BUBBLE;So;0;ON;;;;;N;;;;; 1F5F1;LIGHTNING MOOD BUBBLE;So;0;ON;;;;;N;;;;; 1F5F2;LIGHTNING MOOD;So;0;ON;;;;;N;;;;; 1F5F3;BALLOT BOX WITH BALLOT;So;0;ON;;;;;N;;;;; 1F5F4;BALLOT SCRIPT X;So;0;ON;;;;;N;;;;; 1F5F5;BALLOT BOX WITH SCRIPT X;So;0;ON;;;;;N;;;;; 1F5F6;BALLOT BOLD SCRIPT X;So;0;ON;;;;;N;;;;; 1F5F7;BALLOT BOX WITH BOLD SCRIPT X;So;0;ON;;;;;N;;;;; 1F5F8;LIGHT CHECK MARK;So;0;ON;;;;;N;;;;; 1F5F9;BALLOT BOX WITH BOLD CHECK;So;0;ON;;;;;N;;;;; 1F5FA;WORLD MAP;So;0;ON;;;;;N;;;;; 1F5FB;MOUNT FUJI;So;0;ON;;;;;N;;;;; 1F5FC;TOKYO TOWER;So;0;ON;;;;;N;;;;; 1F5FD;STATUE OF LIBERTY;So;0;ON;;;;;N;;;;; 1F5FE;SILHOUETTE OF JAPAN;So;0;ON;;;;;N;;;;; 1F5FF;MOYAI;So;0;ON;;;;;N;;;;; 1F600;GRINNING FACE;So;0;ON;;;;;N;;;;; 1F601;GRINNING FACE WITH SMILING EYES;So;0;ON;;;;;N;;;;; 1F602;FACE WITH TEARS OF JOY;So;0;ON;;;;;N;;;;; 1F603;SMILING FACE WITH OPEN MOUTH;So;0;ON;;;;;N;;;;; 1F604;SMILING FACE WITH OPEN MOUTH AND SMILING EYES;So;0;ON;;;;;N;;;;; 1F605;SMILING FACE WITH OPEN MOUTH AND COLD SWEAT;So;0;ON;;;;;N;;;;; 1F606;SMILING FACE WITH OPEN MOUTH AND TIGHTLY-CLOSED EYES;So;0;ON;;;;;N;;;;; 1F607;SMILING FACE WITH HALO;So;0;ON;;;;;N;;;;; 1F608;SMILING FACE WITH HORNS;So;0;ON;;;;;N;;;;; 1F609;WINKING FACE;So;0;ON;;;;;N;;;;; 1F60A;SMILING FACE WITH SMILING EYES;So;0;ON;;;;;N;;;;; 1F60B;FACE SAVOURING DELICIOUS FOOD;So;0;ON;;;;;N;;;;; 1F60C;RELIEVED FACE;So;0;ON;;;;;N;;;;; 1F60D;SMILING FACE WITH HEART-SHAPED EYES;So;0;ON;;;;;N;;;;; 1F60E;SMILING FACE WITH SUNGLASSES;So;0;ON;;;;;N;;;;; 1F60F;SMIRKING FACE;So;0;ON;;;;;N;;;;; 1F610;NEUTRAL FACE;So;0;ON;;;;;N;;;;; 1F611;EXPRESSIONLESS FACE;So;0;ON;;;;;N;;;;; 1F612;UNAMUSED FACE;So;0;ON;;;;;N;;;;; 1F613;FACE WITH COLD SWEAT;So;0;ON;;;;;N;;;;; 1F614;PENSIVE FACE;So;0;ON;;;;;N;;;;; 1F615;CONFUSED FACE;So;0;ON;;;;;N;;;;; 1F616;CONFOUNDED FACE;So;0;ON;;;;;N;;;;; 1F617;KISSING FACE;So;0;ON;;;;;N;;;;; 1F618;FACE THROWING A KISS;So;0;ON;;;;;N;;;;; 1F619;KISSING FACE WITH SMILING EYES;So;0;ON;;;;;N;;;;; 1F61A;KISSING FACE WITH CLOSED EYES;So;0;ON;;;;;N;;;;; 1F61B;FACE WITH STUCK-OUT TONGUE;So;0;ON;;;;;N;;;;; 1F61C;FACE WITH STUCK-OUT TONGUE AND WINKING EYE;So;0;ON;;;;;N;;;;; 1F61D;FACE WITH STUCK-OUT TONGUE AND TIGHTLY-CLOSED EYES;So;0;ON;;;;;N;;;;; 1F61E;DISAPPOINTED FACE;So;0;ON;;;;;N;;;;; 1F61F;WORRIED FACE;So;0;ON;;;;;N;;;;; 1F620;ANGRY FACE;So;0;ON;;;;;N;;;;; 1F621;POUTING FACE;So;0;ON;;;;;N;;;;; 1F622;CRYING FACE;So;0;ON;;;;;N;;;;; 1F623;PERSEVERING FACE;So;0;ON;;;;;N;;;;; 1F624;FACE WITH LOOK OF TRIUMPH;So;0;ON;;;;;N;;;;; 1F625;DISAPPOINTED BUT RELIEVED FACE;So;0;ON;;;;;N;;;;; 1F626;FROWNING FACE WITH OPEN MOUTH;So;0;ON;;;;;N;;;;; 1F627;ANGUISHED FACE;So;0;ON;;;;;N;;;;; 1F628;FEARFUL FACE;So;0;ON;;;;;N;;;;; 1F629;WEARY FACE;So;0;ON;;;;;N;;;;; 1F62A;SLEEPY FACE;So;0;ON;;;;;N;;;;; 1F62B;TIRED FACE;So;0;ON;;;;;N;;;;; 1F62C;GRIMACING FACE;So;0;ON;;;;;N;;;;; 1F62D;LOUDLY CRYING FACE;So;0;ON;;;;;N;;;;; 1F62E;FACE WITH OPEN MOUTH;So;0;ON;;;;;N;;;;; 1F62F;HUSHED FACE;So;0;ON;;;;;N;;;;; 1F630;FACE WITH OPEN MOUTH AND COLD SWEAT;So;0;ON;;;;;N;;;;; 1F631;FACE SCREAMING IN FEAR;So;0;ON;;;;;N;;;;; 1F632;ASTONISHED FACE;So;0;ON;;;;;N;;;;; 1F633;FLUSHED FACE;So;0;ON;;;;;N;;;;; 1F634;SLEEPING FACE;So;0;ON;;;;;N;;;;; 1F635;DIZZY FACE;So;0;ON;;;;;N;;;;; 1F636;FACE WITHOUT MOUTH;So;0;ON;;;;;N;;;;; 1F637;FACE WITH MEDICAL MASK;So;0;ON;;;;;N;;;;; 1F638;GRINNING CAT FACE WITH SMILING EYES;So;0;ON;;;;;N;;;;; 1F639;CAT FACE WITH TEARS OF JOY;So;0;ON;;;;;N;;;;; 1F63A;SMILING CAT FACE WITH OPEN MOUTH;So;0;ON;;;;;N;;;;; 1F63B;SMILING CAT FACE WITH HEART-SHAPED EYES;So;0;ON;;;;;N;;;;; 1F63C;CAT FACE WITH WRY SMILE;So;0;ON;;;;;N;;;;; 1F63D;KISSING CAT FACE WITH CLOSED EYES;So;0;ON;;;;;N;;;;; 1F63E;POUTING CAT FACE;So;0;ON;;;;;N;;;;; 1F63F;CRYING CAT FACE;So;0;ON;;;;;N;;;;; 1F640;WEARY CAT FACE;So;0;ON;;;;;N;;;;; 1F641;SLIGHTLY FROWNING FACE;So;0;ON;;;;;N;;;;; 1F642;SLIGHTLY SMILING FACE;So;0;ON;;;;;N;;;;; 1F643;UPSIDE-DOWN FACE;So;0;ON;;;;;N;;;;; 1F644;FACE WITH ROLLING EYES;So;0;ON;;;;;N;;;;; 1F645;FACE WITH NO GOOD GESTURE;So;0;ON;;;;;N;;;;; 1F646;FACE WITH OK GESTURE;So;0;ON;;;;;N;;;;; 1F647;PERSON BOWING DEEPLY;So;0;ON;;;;;N;;;;; 1F648;SEE-NO-EVIL MONKEY;So;0;ON;;;;;N;;;;; 1F649;HEAR-NO-EVIL MONKEY;So;0;ON;;;;;N;;;;; 1F64A;SPEAK-NO-EVIL MONKEY;So;0;ON;;;;;N;;;;; 1F64B;HAPPY PERSON RAISING ONE HAND;So;0;ON;;;;;N;;;;; 1F64C;PERSON RAISING BOTH HANDS IN CELEBRATION;So;0;ON;;;;;N;;;;; 1F64D;PERSON FROWNING;So;0;ON;;;;;N;;;;; 1F64E;PERSON WITH POUTING FACE;So;0;ON;;;;;N;;;;; 1F64F;PERSON WITH FOLDED HANDS;So;0;ON;;;;;N;;;;; 1F650;NORTH WEST POINTING LEAF;So;0;ON;;;;;N;;;;; 1F651;SOUTH WEST POINTING LEAF;So;0;ON;;;;;N;;;;; 1F652;NORTH EAST POINTING LEAF;So;0;ON;;;;;N;;;;; 1F653;SOUTH EAST POINTING LEAF;So;0;ON;;;;;N;;;;; 1F654;TURNED NORTH WEST POINTING LEAF;So;0;ON;;;;;N;;;;; 1F655;TURNED SOUTH WEST POINTING LEAF;So;0;ON;;;;;N;;;;; 1F656;TURNED NORTH EAST POINTING LEAF;So;0;ON;;;;;N;;;;; 1F657;TURNED SOUTH EAST POINTING LEAF;So;0;ON;;;;;N;;;;; 1F658;NORTH WEST POINTING VINE LEAF;So;0;ON;;;;;N;;;;; 1F659;SOUTH WEST POINTING VINE LEAF;So;0;ON;;;;;N;;;;; 1F65A;NORTH EAST POINTING VINE LEAF;So;0;ON;;;;;N;;;;; 1F65B;SOUTH EAST POINTING VINE LEAF;So;0;ON;;;;;N;;;;; 1F65C;HEAVY NORTH WEST POINTING VINE LEAF;So;0;ON;;;;;N;;;;; 1F65D;HEAVY SOUTH WEST POINTING VINE LEAF;So;0;ON;;;;;N;;;;; 1F65E;HEAVY NORTH EAST POINTING VINE LEAF;So;0;ON;;;;;N;;;;; 1F65F;HEAVY SOUTH EAST POINTING VINE LEAF;So;0;ON;;;;;N;;;;; 1F660;NORTH WEST POINTING BUD;So;0;ON;;;;;N;;;;; 1F661;SOUTH WEST POINTING BUD;So;0;ON;;;;;N;;;;; 1F662;NORTH EAST POINTING BUD;So;0;ON;;;;;N;;;;; 1F663;SOUTH EAST POINTING BUD;So;0;ON;;;;;N;;;;; 1F664;HEAVY NORTH WEST POINTING BUD;So;0;ON;;;;;N;;;;; 1F665;HEAVY SOUTH WEST POINTING BUD;So;0;ON;;;;;N;;;;; 1F666;HEAVY NORTH EAST POINTING BUD;So;0;ON;;;;;N;;;;; 1F667;HEAVY SOUTH EAST POINTING BUD;So;0;ON;;;;;N;;;;; 1F668;HOLLOW QUILT SQUARE ORNAMENT;So;0;ON;;;;;N;;;;; 1F669;HOLLOW QUILT SQUARE ORNAMENT IN BLACK SQUARE;So;0;ON;;;;;N;;;;; 1F66A;SOLID QUILT SQUARE ORNAMENT;So;0;ON;;;;;N;;;;; 1F66B;SOLID QUILT SQUARE ORNAMENT IN BLACK SQUARE;So;0;ON;;;;;N;;;;; 1F66C;LEFTWARDS ROCKET;So;0;ON;;;;;N;;;;; 1F66D;UPWARDS ROCKET;So;0;ON;;;;;N;;;;; 1F66E;RIGHTWARDS ROCKET;So;0;ON;;;;;N;;;;; 1F66F;DOWNWARDS ROCKET;So;0;ON;;;;;N;;;;; 1F670;SCRIPT LIGATURE ET ORNAMENT;So;0;ON;;;;;N;;;;; 1F671;HEAVY SCRIPT LIGATURE ET ORNAMENT;So;0;ON;;;;;N;;;;; 1F672;LIGATURE OPEN ET ORNAMENT;So;0;ON;;;;;N;;;;; 1F673;HEAVY LIGATURE OPEN ET ORNAMENT;So;0;ON;;;;;N;;;;; 1F674;HEAVY AMPERSAND ORNAMENT;So;0;ON;;;;;N;;;;; 1F675;SWASH AMPERSAND ORNAMENT;So;0;ON;;;;;N;;;;; 1F676;SANS-SERIF HEAVY DOUBLE TURNED COMMA QUOTATION MARK ORNAMENT;So;0;ON;;;;;N;;;;; 1F677;SANS-SERIF HEAVY DOUBLE COMMA QUOTATION MARK ORNAMENT;So;0;ON;;;;;N;;;;; 1F678;SANS-SERIF HEAVY LOW DOUBLE COMMA QUOTATION MARK ORNAMENT;So;0;ON;;;;;N;;;;; 1F679;HEAVY INTERROBANG ORNAMENT;So;0;ON;;;;;N;;;;; 1F67A;SANS-SERIF INTERROBANG ORNAMENT;So;0;ON;;;;;N;;;;; 1F67B;HEAVY SANS-SERIF INTERROBANG ORNAMENT;So;0;ON;;;;;N;;;;; 1F67C;VERY HEAVY SOLIDUS;So;0;ON;;;;;N;;;;; 1F67D;VERY HEAVY REVERSE SOLIDUS;So;0;ON;;;;;N;;;;; 1F67E;CHECKER BOARD;So;0;ON;;;;;N;;;;; 1F67F;REVERSE CHECKER BOARD;So;0;ON;;;;;N;;;;; 1F680;ROCKET;So;0;ON;;;;;N;;;;; 1F681;HELICOPTER;So;0;ON;;;;;N;;;;; 1F682;STEAM LOCOMOTIVE;So;0;ON;;;;;N;;;;; 1F683;RAILWAY CAR;So;0;ON;;;;;N;;;;; 1F684;HIGH-SPEED TRAIN;So;0;ON;;;;;N;;;;; 1F685;HIGH-SPEED TRAIN WITH BULLET NOSE;So;0;ON;;;;;N;;;;; 1F686;TRAIN;So;0;ON;;;;;N;;;;; 1F687;METRO;So;0;ON;;;;;N;;;;; 1F688;LIGHT RAIL;So;0;ON;;;;;N;;;;; 1F689;STATION;So;0;ON;;;;;N;;;;; 1F68A;TRAM;So;0;ON;;;;;N;;;;; 1F68B;TRAM CAR;So;0;ON;;;;;N;;;;; 1F68C;BUS;So;0;ON;;;;;N;;;;; 1F68D;ONCOMING BUS;So;0;ON;;;;;N;;;;; 1F68E;TROLLEYBUS;So;0;ON;;;;;N;;;;; 1F68F;BUS STOP;So;0;ON;;;;;N;;;;; 1F690;MINIBUS;So;0;ON;;;;;N;;;;; 1F691;AMBULANCE;So;0;ON;;;;;N;;;;; 1F692;FIRE ENGINE;So;0;ON;;;;;N;;;;; 1F693;POLICE CAR;So;0;ON;;;;;N;;;;; 1F694;ONCOMING POLICE CAR;So;0;ON;;;;;N;;;;; 1F695;TAXI;So;0;ON;;;;;N;;;;; 1F696;ONCOMING TAXI;So;0;ON;;;;;N;;;;; 1F697;AUTOMOBILE;So;0;ON;;;;;N;;;;; 1F698;ONCOMING AUTOMOBILE;So;0;ON;;;;;N;;;;; 1F699;RECREATIONAL VEHICLE;So;0;ON;;;;;N;;;;; 1F69A;DELIVERY TRUCK;So;0;ON;;;;;N;;;;; 1F69B;ARTICULATED LORRY;So;0;ON;;;;;N;;;;; 1F69C;TRACTOR;So;0;ON;;;;;N;;;;; 1F69D;MONORAIL;So;0;ON;;;;;N;;;;; 1F69E;MOUNTAIN RAILWAY;So;0;ON;;;;;N;;;;; 1F69F;SUSPENSION RAILWAY;So;0;ON;;;;;N;;;;; 1F6A0;MOUNTAIN CABLEWAY;So;0;ON;;;;;N;;;;; 1F6A1;AERIAL TRAMWAY;So;0;ON;;;;;N;;;;; 1F6A2;SHIP;So;0;ON;;;;;N;;;;; 1F6A3;ROWBOAT;So;0;ON;;;;;N;;;;; 1F6A4;SPEEDBOAT;So;0;ON;;;;;N;;;;; 1F6A5;HORIZONTAL TRAFFIC LIGHT;So;0;ON;;;;;N;;;;; 1F6A6;VERTICAL TRAFFIC LIGHT;So;0;ON;;;;;N;;;;; 1F6A7;CONSTRUCTION SIGN;So;0;ON;;;;;N;;;;; 1F6A8;POLICE CARS REVOLVING LIGHT;So;0;ON;;;;;N;;;;; 1F6A9;TRIANGULAR FLAG ON POST;So;0;ON;;;;;N;;;;; 1F6AA;DOOR;So;0;ON;;;;;N;;;;; 1F6AB;NO ENTRY SIGN;So;0;ON;;;;;N;;;;; 1F6AC;SMOKING SYMBOL;So;0;ON;;;;;N;;;;; 1F6AD;NO SMOKING SYMBOL;So;0;ON;;;;;N;;;;; 1F6AE;PUT LITTER IN ITS PLACE SYMBOL;So;0;ON;;;;;N;;;;; 1F6AF;DO NOT LITTER SYMBOL;So;0;ON;;;;;N;;;;; 1F6B0;POTABLE WATER SYMBOL;So;0;ON;;;;;N;;;;; 1F6B1;NON-POTABLE WATER SYMBOL;So;0;ON;;;;;N;;;;; 1F6B2;BICYCLE;So;0;ON;;;;;N;;;;; 1F6B3;NO BICYCLES;So;0;ON;;;;;N;;;;; 1F6B4;BICYCLIST;So;0;ON;;;;;N;;;;; 1F6B5;MOUNTAIN BICYCLIST;So;0;ON;;;;;N;;;;; 1F6B6;PEDESTRIAN;So;0;ON;;;;;N;;;;; 1F6B7;NO PEDESTRIANS;So;0;ON;;;;;N;;;;; 1F6B8;CHILDREN CROSSING;So;0;ON;;;;;N;;;;; 1F6B9;MENS SYMBOL;So;0;ON;;;;;N;;;;; 1F6BA;WOMENS SYMBOL;So;0;ON;;;;;N;;;;; 1F6BB;RESTROOM;So;0;ON;;;;;N;;;;; 1F6BC;BABY SYMBOL;So;0;ON;;;;;N;;;;; 1F6BD;TOILET;So;0;ON;;;;;N;;;;; 1F6BE;WATER CLOSET;So;0;ON;;;;;N;;;;; 1F6BF;SHOWER;So;0;ON;;;;;N;;;;; 1F6C0;BATH;So;0;ON;;;;;N;;;;; 1F6C1;BATHTUB;So;0;ON;;;;;N;;;;; 1F6C2;PASSPORT CONTROL;So;0;ON;;;;;N;;;;; 1F6C3;CUSTOMS;So;0;ON;;;;;N;;;;; 1F6C4;BAGGAGE CLAIM;So;0;ON;;;;;N;;;;; 1F6C5;LEFT LUGGAGE;So;0;ON;;;;;N;;;;; 1F6C6;TRIANGLE WITH ROUNDED CORNERS;So;0;ON;;;;;N;;;;; 1F6C7;PROHIBITED SIGN;So;0;ON;;;;;N;;;;; 1F6C8;CIRCLED INFORMATION SOURCE;So;0;ON;;;;;N;;;;; 1F6C9;BOYS SYMBOL;So;0;ON;;;;;N;;;;; 1F6CA;GIRLS SYMBOL;So;0;ON;;;;;N;;;;; 1F6CB;COUCH AND LAMP;So;0;ON;;;;;N;;;;; 1F6CC;SLEEPING ACCOMMODATION;So;0;ON;;;;;N;;;;; 1F6CD;SHOPPING BAGS;So;0;ON;;;;;N;;;;; 1F6CE;BELLHOP BELL;So;0;ON;;;;;N;;;;; 1F6CF;BED;So;0;ON;;;;;N;;;;; 1F6D0;PLACE OF WORSHIP;So;0;ON;;;;;N;;;;; 1F6D1;OCTAGONAL SIGN;So;0;ON;;;;;N;;;;; 1F6D2;SHOPPING TROLLEY;So;0;ON;;;;;N;;;;; 1F6E0;HAMMER AND WRENCH;So;0;ON;;;;;N;;;;; 1F6E1;SHIELD;So;0;ON;;;;;N;;;;; 1F6E2;OIL DRUM;So;0;ON;;;;;N;;;;; 1F6E3;MOTORWAY;So;0;ON;;;;;N;;;;; 1F6E4;RAILWAY TRACK;So;0;ON;;;;;N;;;;; 1F6E5;MOTOR BOAT;So;0;ON;;;;;N;;;;; 1F6E6;UP-POINTING MILITARY AIRPLANE;So;0;ON;;;;;N;;;;; 1F6E7;UP-POINTING AIRPLANE;So;0;ON;;;;;N;;;;; 1F6E8;UP-POINTING SMALL AIRPLANE;So;0;ON;;;;;N;;;;; 1F6E9;SMALL AIRPLANE;So;0;ON;;;;;N;;;;; 1F6EA;NORTHEAST-POINTING AIRPLANE;So;0;ON;;;;;N;;;;; 1F6EB;AIRPLANE DEPARTURE;So;0;ON;;;;;N;;;;; 1F6EC;AIRPLANE ARRIVING;So;0;ON;;;;;N;;;;; 1F6F0;SATELLITE;So;0;ON;;;;;N;;;;; 1F6F1;ONCOMING FIRE ENGINE;So;0;ON;;;;;N;;;;; 1F6F2;DIESEL LOCOMOTIVE;So;0;ON;;;;;N;;;;; 1F6F3;PASSENGER SHIP;So;0;ON;;;;;N;;;;; 1F6F4;SCOOTER;So;0;ON;;;;;N;;;;; 1F6F5;MOTOR SCOOTER;So;0;ON;;;;;N;;;;; 1F6F6;CANOE;So;0;ON;;;;;N;;;;; 1F700;ALCHEMICAL SYMBOL FOR QUINTESSENCE;So;0;ON;;;;;N;;;;; 1F701;ALCHEMICAL SYMBOL FOR AIR;So;0;ON;;;;;N;;;;; 1F702;ALCHEMICAL SYMBOL FOR FIRE;So;0;ON;;;;;N;;;;; 1F703;ALCHEMICAL SYMBOL FOR EARTH;So;0;ON;;;;;N;;;;; 1F704;ALCHEMICAL SYMBOL FOR WATER;So;0;ON;;;;;N;;;;; 1F705;ALCHEMICAL SYMBOL FOR AQUAFORTIS;So;0;ON;;;;;N;;;;; 1F706;ALCHEMICAL SYMBOL FOR AQUA REGIA;So;0;ON;;;;;N;;;;; 1F707;ALCHEMICAL SYMBOL FOR AQUA REGIA-2;So;0;ON;;;;;N;;;;; 1F708;ALCHEMICAL SYMBOL FOR AQUA VITAE;So;0;ON;;;;;N;;;;; 1F709;ALCHEMICAL SYMBOL FOR AQUA VITAE-2;So;0;ON;;;;;N;;;;; 1F70A;ALCHEMICAL SYMBOL FOR VINEGAR;So;0;ON;;;;;N;;;;; 1F70B;ALCHEMICAL SYMBOL FOR VINEGAR-2;So;0;ON;;;;;N;;;;; 1F70C;ALCHEMICAL SYMBOL FOR VINEGAR-3;So;0;ON;;;;;N;;;;; 1F70D;ALCHEMICAL SYMBOL FOR SULFUR;So;0;ON;;;;;N;;;;; 1F70E;ALCHEMICAL SYMBOL FOR PHILOSOPHERS SULFUR;So;0;ON;;;;;N;;;;; 1F70F;ALCHEMICAL SYMBOL FOR BLACK SULFUR;So;0;ON;;;;;N;;;;; 1F710;ALCHEMICAL SYMBOL FOR MERCURY SUBLIMATE;So;0;ON;;;;;N;;;;; 1F711;ALCHEMICAL SYMBOL FOR MERCURY SUBLIMATE-2;So;0;ON;;;;;N;;;;; 1F712;ALCHEMICAL SYMBOL FOR MERCURY SUBLIMATE-3;So;0;ON;;;;;N;;;;; 1F713;ALCHEMICAL SYMBOL FOR CINNABAR;So;0;ON;;;;;N;;;;; 1F714;ALCHEMICAL SYMBOL FOR SALT;So;0;ON;;;;;N;;;;; 1F715;ALCHEMICAL SYMBOL FOR NITRE;So;0;ON;;;;;N;;;;; 1F716;ALCHEMICAL SYMBOL FOR VITRIOL;So;0;ON;;;;;N;;;;; 1F717;ALCHEMICAL SYMBOL FOR VITRIOL-2;So;0;ON;;;;;N;;;;; 1F718;ALCHEMICAL SYMBOL FOR ROCK SALT;So;0;ON;;;;;N;;;;; 1F719;ALCHEMICAL SYMBOL FOR ROCK SALT-2;So;0;ON;;;;;N;;;;; 1F71A;ALCHEMICAL SYMBOL FOR GOLD;So;0;ON;;;;;N;;;;; 1F71B;ALCHEMICAL SYMBOL FOR SILVER;So;0;ON;;;;;N;;;;; 1F71C;ALCHEMICAL SYMBOL FOR IRON ORE;So;0;ON;;;;;N;;;;; 1F71D;ALCHEMICAL SYMBOL FOR IRON ORE-2;So;0;ON;;;;;N;;;;; 1F71E;ALCHEMICAL SYMBOL FOR CROCUS OF IRON;So;0;ON;;;;;N;;;;; 1F71F;ALCHEMICAL SYMBOL FOR REGULUS OF IRON;So;0;ON;;;;;N;;;;; 1F720;ALCHEMICAL SYMBOL FOR COPPER ORE;So;0;ON;;;;;N;;;;; 1F721;ALCHEMICAL SYMBOL FOR IRON-COPPER ORE;So;0;ON;;;;;N;;;;; 1F722;ALCHEMICAL SYMBOL FOR SUBLIMATE OF COPPER;So;0;ON;;;;;N;;;;; 1F723;ALCHEMICAL SYMBOL FOR CROCUS OF COPPER;So;0;ON;;;;;N;;;;; 1F724;ALCHEMICAL SYMBOL FOR CROCUS OF COPPER-2;So;0;ON;;;;;N;;;;; 1F725;ALCHEMICAL SYMBOL FOR COPPER ANTIMONIATE;So;0;ON;;;;;N;;;;; 1F726;ALCHEMICAL SYMBOL FOR SALT OF COPPER ANTIMONIATE;So;0;ON;;;;;N;;;;; 1F727;ALCHEMICAL SYMBOL FOR SUBLIMATE OF SALT OF COPPER;So;0;ON;;;;;N;;;;; 1F728;ALCHEMICAL SYMBOL FOR VERDIGRIS;So;0;ON;;;;;N;;;;; 1F729;ALCHEMICAL SYMBOL FOR TIN ORE;So;0;ON;;;;;N;;;;; 1F72A;ALCHEMICAL SYMBOL FOR LEAD ORE;So;0;ON;;;;;N;;;;; 1F72B;ALCHEMICAL SYMBOL FOR ANTIMONY ORE;So;0;ON;;;;;N;;;;; 1F72C;ALCHEMICAL SYMBOL FOR SUBLIMATE OF ANTIMONY;So;0;ON;;;;;N;;;;; 1F72D;ALCHEMICAL SYMBOL FOR SALT OF ANTIMONY;So;0;ON;;;;;N;;;;; 1F72E;ALCHEMICAL SYMBOL FOR SUBLIMATE OF SALT OF ANTIMONY;So;0;ON;;;;;N;;;;; 1F72F;ALCHEMICAL SYMBOL FOR VINEGAR OF ANTIMONY;So;0;ON;;;;;N;;;;; 1F730;ALCHEMICAL SYMBOL FOR REGULUS OF ANTIMONY;So;0;ON;;;;;N;;;;; 1F731;ALCHEMICAL SYMBOL FOR REGULUS OF ANTIMONY-2;So;0;ON;;;;;N;;;;; 1F732;ALCHEMICAL SYMBOL FOR REGULUS;So;0;ON;;;;;N;;;;; 1F733;ALCHEMICAL SYMBOL FOR REGULUS-2;So;0;ON;;;;;N;;;;; 1F734;ALCHEMICAL SYMBOL FOR REGULUS-3;So;0;ON;;;;;N;;;;; 1F735;ALCHEMICAL SYMBOL FOR REGULUS-4;So;0;ON;;;;;N;;;;; 1F736;ALCHEMICAL SYMBOL FOR ALKALI;So;0;ON;;;;;N;;;;; 1F737;ALCHEMICAL SYMBOL FOR ALKALI-2;So;0;ON;;;;;N;;;;; 1F738;ALCHEMICAL SYMBOL FOR MARCASITE;So;0;ON;;;;;N;;;;; 1F739;ALCHEMICAL SYMBOL FOR SAL-AMMONIAC;So;0;ON;;;;;N;;;;; 1F73A;ALCHEMICAL SYMBOL FOR ARSENIC;So;0;ON;;;;;N;;;;; 1F73B;ALCHEMICAL SYMBOL FOR REALGAR;So;0;ON;;;;;N;;;;; 1F73C;ALCHEMICAL SYMBOL FOR REALGAR-2;So;0;ON;;;;;N;;;;; 1F73D;ALCHEMICAL SYMBOL FOR AURIPIGMENT;So;0;ON;;;;;N;;;;; 1F73E;ALCHEMICAL SYMBOL FOR BISMUTH ORE;So;0;ON;;;;;N;;;;; 1F73F;ALCHEMICAL SYMBOL FOR TARTAR;So;0;ON;;;;;N;;;;; 1F740;ALCHEMICAL SYMBOL FOR TARTAR-2;So;0;ON;;;;;N;;;;; 1F741;ALCHEMICAL SYMBOL FOR QUICK LIME;So;0;ON;;;;;N;;;;; 1F742;ALCHEMICAL SYMBOL FOR BORAX;So;0;ON;;;;;N;;;;; 1F743;ALCHEMICAL SYMBOL FOR BORAX-2;So;0;ON;;;;;N;;;;; 1F744;ALCHEMICAL SYMBOL FOR BORAX-3;So;0;ON;;;;;N;;;;; 1F745;ALCHEMICAL SYMBOL FOR ALUM;So;0;ON;;;;;N;;;;; 1F746;ALCHEMICAL SYMBOL FOR OIL;So;0;ON;;;;;N;;;;; 1F747;ALCHEMICAL SYMBOL FOR SPIRIT;So;0;ON;;;;;N;;;;; 1F748;ALCHEMICAL SYMBOL FOR TINCTURE;So;0;ON;;;;;N;;;;; 1F749;ALCHEMICAL SYMBOL FOR GUM;So;0;ON;;;;;N;;;;; 1F74A;ALCHEMICAL SYMBOL FOR WAX;So;0;ON;;;;;N;;;;; 1F74B;ALCHEMICAL SYMBOL FOR POWDER;So;0;ON;;;;;N;;;;; 1F74C;ALCHEMICAL SYMBOL FOR CALX;So;0;ON;;;;;N;;;;; 1F74D;ALCHEMICAL SYMBOL FOR TUTTY;So;0;ON;;;;;N;;;;; 1F74E;ALCHEMICAL SYMBOL FOR CAPUT MORTUUM;So;0;ON;;;;;N;;;;; 1F74F;ALCHEMICAL SYMBOL FOR SCEPTER OF JOVE;So;0;ON;;;;;N;;;;; 1F750;ALCHEMICAL SYMBOL FOR CADUCEUS;So;0;ON;;;;;N;;;;; 1F751;ALCHEMICAL SYMBOL FOR TRIDENT;So;0;ON;;;;;N;;;;; 1F752;ALCHEMICAL SYMBOL FOR STARRED TRIDENT;So;0;ON;;;;;N;;;;; 1F753;ALCHEMICAL SYMBOL FOR LODESTONE;So;0;ON;;;;;N;;;;; 1F754;ALCHEMICAL SYMBOL FOR SOAP;So;0;ON;;;;;N;;;;; 1F755;ALCHEMICAL SYMBOL FOR URINE;So;0;ON;;;;;N;;;;; 1F756;ALCHEMICAL SYMBOL FOR HORSE DUNG;So;0;ON;;;;;N;;;;; 1F757;ALCHEMICAL SYMBOL FOR ASHES;So;0;ON;;;;;N;;;;; 1F758;ALCHEMICAL SYMBOL FOR POT ASHES;So;0;ON;;;;;N;;;;; 1F759;ALCHEMICAL SYMBOL FOR BRICK;So;0;ON;;;;;N;;;;; 1F75A;ALCHEMICAL SYMBOL FOR POWDERED BRICK;So;0;ON;;;;;N;;;;; 1F75B;ALCHEMICAL SYMBOL FOR AMALGAM;So;0;ON;;;;;N;;;;; 1F75C;ALCHEMICAL SYMBOL FOR STRATUM SUPER STRATUM;So;0;ON;;;;;N;;;;; 1F75D;ALCHEMICAL SYMBOL FOR STRATUM SUPER STRATUM-2;So;0;ON;;;;;N;;;;; 1F75E;ALCHEMICAL SYMBOL FOR SUBLIMATION;So;0;ON;;;;;N;;;;; 1F75F;ALCHEMICAL SYMBOL FOR PRECIPITATE;So;0;ON;;;;;N;;;;; 1F760;ALCHEMICAL SYMBOL FOR DISTILL;So;0;ON;;;;;N;;;;; 1F761;ALCHEMICAL SYMBOL FOR DISSOLVE;So;0;ON;;;;;N;;;;; 1F762;ALCHEMICAL SYMBOL FOR DISSOLVE-2;So;0;ON;;;;;N;;;;; 1F763;ALCHEMICAL SYMBOL FOR PURIFY;So;0;ON;;;;;N;;;;; 1F764;ALCHEMICAL SYMBOL FOR PUTREFACTION;So;0;ON;;;;;N;;;;; 1F765;ALCHEMICAL SYMBOL FOR CRUCIBLE;So;0;ON;;;;;N;;;;; 1F766;ALCHEMICAL SYMBOL FOR CRUCIBLE-2;So;0;ON;;;;;N;;;;; 1F767;ALCHEMICAL SYMBOL FOR CRUCIBLE-3;So;0;ON;;;;;N;;;;; 1F768;ALCHEMICAL SYMBOL FOR CRUCIBLE-4;So;0;ON;;;;;N;;;;; 1F769;ALCHEMICAL SYMBOL FOR CRUCIBLE-5;So;0;ON;;;;;N;;;;; 1F76A;ALCHEMICAL SYMBOL FOR ALEMBIC;So;0;ON;;;;;N;;;;; 1F76B;ALCHEMICAL SYMBOL FOR BATH OF MARY;So;0;ON;;;;;N;;;;; 1F76C;ALCHEMICAL SYMBOL FOR BATH OF VAPOURS;So;0;ON;;;;;N;;;;; 1F76D;ALCHEMICAL SYMBOL FOR RETORT;So;0;ON;;;;;N;;;;; 1F76E;ALCHEMICAL SYMBOL FOR HOUR;So;0;ON;;;;;N;;;;; 1F76F;ALCHEMICAL SYMBOL FOR NIGHT;So;0;ON;;;;;N;;;;; 1F770;ALCHEMICAL SYMBOL FOR DAY-NIGHT;So;0;ON;;;;;N;;;;; 1F771;ALCHEMICAL SYMBOL FOR MONTH;So;0;ON;;;;;N;;;;; 1F772;ALCHEMICAL SYMBOL FOR HALF DRAM;So;0;ON;;;;;N;;;;; 1F773;ALCHEMICAL SYMBOL FOR HALF OUNCE;So;0;ON;;;;;N;;;;; 1F780;BLACK LEFT-POINTING ISOSCELES RIGHT TRIANGLE;So;0;ON;;;;;N;;;;; 1F781;BLACK UP-POINTING ISOSCELES RIGHT TRIANGLE;So;0;ON;;;;;N;;;;; 1F782;BLACK RIGHT-POINTING ISOSCELES RIGHT TRIANGLE;So;0;ON;;;;;N;;;;; 1F783;BLACK DOWN-POINTING ISOSCELES RIGHT TRIANGLE;So;0;ON;;;;;N;;;;; 1F784;BLACK SLIGHTLY SMALL CIRCLE;So;0;ON;;;;;N;;;;; 1F785;MEDIUM BOLD WHITE CIRCLE;So;0;ON;;;;;N;;;;; 1F786;BOLD WHITE CIRCLE;So;0;ON;;;;;N;;;;; 1F787;HEAVY WHITE CIRCLE;So;0;ON;;;;;N;;;;; 1F788;VERY HEAVY WHITE CIRCLE;So;0;ON;;;;;N;;;;; 1F789;EXTREMELY HEAVY WHITE CIRCLE;So;0;ON;;;;;N;;;;; 1F78A;WHITE CIRCLE CONTAINING BLACK SMALL CIRCLE;So;0;ON;;;;;N;;;;; 1F78B;ROUND TARGET;So;0;ON;;;;;N;;;;; 1F78C;BLACK TINY SQUARE;So;0;ON;;;;;N;;;;; 1F78D;BLACK SLIGHTLY SMALL SQUARE;So;0;ON;;;;;N;;;;; 1F78E;LIGHT WHITE SQUARE;So;0;ON;;;;;N;;;;; 1F78F;MEDIUM WHITE SQUARE;So;0;ON;;;;;N;;;;; 1F790;BOLD WHITE SQUARE;So;0;ON;;;;;N;;;;; 1F791;HEAVY WHITE SQUARE;So;0;ON;;;;;N;;;;; 1F792;VERY HEAVY WHITE SQUARE;So;0;ON;;;;;N;;;;; 1F793;EXTREMELY HEAVY WHITE SQUARE;So;0;ON;;;;;N;;;;; 1F794;WHITE SQUARE CONTAINING BLACK VERY SMALL SQUARE;So;0;ON;;;;;N;;;;; 1F795;WHITE SQUARE CONTAINING BLACK MEDIUM SQUARE;So;0;ON;;;;;N;;;;; 1F796;SQUARE TARGET;So;0;ON;;;;;N;;;;; 1F797;BLACK TINY DIAMOND;So;0;ON;;;;;N;;;;; 1F798;BLACK VERY SMALL DIAMOND;So;0;ON;;;;;N;;;;; 1F799;BLACK MEDIUM SMALL DIAMOND;So;0;ON;;;;;N;;;;; 1F79A;WHITE DIAMOND CONTAINING BLACK VERY SMALL DIAMOND;So;0;ON;;;;;N;;;;; 1F79B;WHITE DIAMOND CONTAINING BLACK MEDIUM DIAMOND;So;0;ON;;;;;N;;;;; 1F79C;DIAMOND TARGET;So;0;ON;;;;;N;;;;; 1F79D;BLACK TINY LOZENGE;So;0;ON;;;;;N;;;;; 1F79E;BLACK VERY SMALL LOZENGE;So;0;ON;;;;;N;;;;; 1F79F;BLACK MEDIUM SMALL LOZENGE;So;0;ON;;;;;N;;;;; 1F7A0;WHITE LOZENGE CONTAINING BLACK SMALL LOZENGE;So;0;ON;;;;;N;;;;; 1F7A1;THIN GREEK CROSS;So;0;ON;;;;;N;;;;; 1F7A2;LIGHT GREEK CROSS;So;0;ON;;;;;N;;;;; 1F7A3;MEDIUM GREEK CROSS;So;0;ON;;;;;N;;;;; 1F7A4;BOLD GREEK CROSS;So;0;ON;;;;;N;;;;; 1F7A5;VERY BOLD GREEK CROSS;So;0;ON;;;;;N;;;;; 1F7A6;VERY HEAVY GREEK CROSS;So;0;ON;;;;;N;;;;; 1F7A7;EXTREMELY HEAVY GREEK CROSS;So;0;ON;;;;;N;;;;; 1F7A8;THIN SALTIRE;So;0;ON;;;;;N;;;;; 1F7A9;LIGHT SALTIRE;So;0;ON;;;;;N;;;;; 1F7AA;MEDIUM SALTIRE;So;0;ON;;;;;N;;;;; 1F7AB;BOLD SALTIRE;So;0;ON;;;;;N;;;;; 1F7AC;HEAVY SALTIRE;So;0;ON;;;;;N;;;;; 1F7AD;VERY HEAVY SALTIRE;So;0;ON;;;;;N;;;;; 1F7AE;EXTREMELY HEAVY SALTIRE;So;0;ON;;;;;N;;;;; 1F7AF;LIGHT FIVE SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 1F7B0;MEDIUM FIVE SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 1F7B1;BOLD FIVE SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 1F7B2;HEAVY FIVE SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 1F7B3;VERY HEAVY FIVE SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 1F7B4;EXTREMELY HEAVY FIVE SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 1F7B5;LIGHT SIX SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 1F7B6;MEDIUM SIX SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 1F7B7;BOLD SIX SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 1F7B8;HEAVY SIX SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 1F7B9;VERY HEAVY SIX SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 1F7BA;EXTREMELY HEAVY SIX SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 1F7BB;LIGHT EIGHT SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 1F7BC;MEDIUM EIGHT SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 1F7BD;BOLD EIGHT SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 1F7BE;HEAVY EIGHT SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 1F7BF;VERY HEAVY EIGHT SPOKED ASTERISK;So;0;ON;;;;;N;;;;; 1F7C0;LIGHT THREE POINTED BLACK STAR;So;0;ON;;;;;N;;;;; 1F7C1;MEDIUM THREE POINTED BLACK STAR;So;0;ON;;;;;N;;;;; 1F7C2;THREE POINTED BLACK STAR;So;0;ON;;;;;N;;;;; 1F7C3;MEDIUM THREE POINTED PINWHEEL STAR;So;0;ON;;;;;N;;;;; 1F7C4;LIGHT FOUR POINTED BLACK STAR;So;0;ON;;;;;N;;;;; 1F7C5;MEDIUM FOUR POINTED BLACK STAR;So;0;ON;;;;;N;;;;; 1F7C6;FOUR POINTED BLACK STAR;So;0;ON;;;;;N;;;;; 1F7C7;MEDIUM FOUR POINTED PINWHEEL STAR;So;0;ON;;;;;N;;;;; 1F7C8;REVERSE LIGHT FOUR POINTED PINWHEEL STAR;So;0;ON;;;;;N;;;;; 1F7C9;LIGHT FIVE POINTED BLACK STAR;So;0;ON;;;;;N;;;;; 1F7CA;HEAVY FIVE POINTED BLACK STAR;So;0;ON;;;;;N;;;;; 1F7CB;MEDIUM SIX POINTED BLACK STAR;So;0;ON;;;;;N;;;;; 1F7CC;HEAVY SIX POINTED BLACK STAR;So;0;ON;;;;;N;;;;; 1F7CD;SIX POINTED PINWHEEL STAR;So;0;ON;;;;;N;;;;; 1F7CE;MEDIUM EIGHT POINTED BLACK STAR;So;0;ON;;;;;N;;;;; 1F7CF;HEAVY EIGHT POINTED BLACK STAR;So;0;ON;;;;;N;;;;; 1F7D0;VERY HEAVY EIGHT POINTED BLACK STAR;So;0;ON;;;;;N;;;;; 1F7D1;HEAVY EIGHT POINTED PINWHEEL STAR;So;0;ON;;;;;N;;;;; 1F7D2;LIGHT TWELVE POINTED BLACK STAR;So;0;ON;;;;;N;;;;; 1F7D3;HEAVY TWELVE POINTED BLACK STAR;So;0;ON;;;;;N;;;;; 1F7D4;HEAVY TWELVE POINTED PINWHEEL STAR;So;0;ON;;;;;N;;;;; 1F800;LEFTWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F801;UPWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F802;RIGHTWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F803;DOWNWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F804;LEFTWARDS ARROW WITH MEDIUM TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F805;UPWARDS ARROW WITH MEDIUM TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F806;RIGHTWARDS ARROW WITH MEDIUM TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F807;DOWNWARDS ARROW WITH MEDIUM TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F808;LEFTWARDS ARROW WITH LARGE TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F809;UPWARDS ARROW WITH LARGE TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F80A;RIGHTWARDS ARROW WITH LARGE TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F80B;DOWNWARDS ARROW WITH LARGE TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F810;LEFTWARDS ARROW WITH SMALL EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 1F811;UPWARDS ARROW WITH SMALL EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 1F812;RIGHTWARDS ARROW WITH SMALL EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 1F813;DOWNWARDS ARROW WITH SMALL EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 1F814;LEFTWARDS ARROW WITH EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 1F815;UPWARDS ARROW WITH EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 1F816;RIGHTWARDS ARROW WITH EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 1F817;DOWNWARDS ARROW WITH EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 1F818;HEAVY LEFTWARDS ARROW WITH EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 1F819;HEAVY UPWARDS ARROW WITH EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 1F81A;HEAVY RIGHTWARDS ARROW WITH EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 1F81B;HEAVY DOWNWARDS ARROW WITH EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 1F81C;HEAVY LEFTWARDS ARROW WITH LARGE EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 1F81D;HEAVY UPWARDS ARROW WITH LARGE EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 1F81E;HEAVY RIGHTWARDS ARROW WITH LARGE EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 1F81F;HEAVY DOWNWARDS ARROW WITH LARGE EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 1F820;LEFTWARDS TRIANGLE-HEADED ARROW WITH NARROW SHAFT;So;0;ON;;;;;N;;;;; 1F821;UPWARDS TRIANGLE-HEADED ARROW WITH NARROW SHAFT;So;0;ON;;;;;N;;;;; 1F822;RIGHTWARDS TRIANGLE-HEADED ARROW WITH NARROW SHAFT;So;0;ON;;;;;N;;;;; 1F823;DOWNWARDS TRIANGLE-HEADED ARROW WITH NARROW SHAFT;So;0;ON;;;;;N;;;;; 1F824;LEFTWARDS TRIANGLE-HEADED ARROW WITH MEDIUM SHAFT;So;0;ON;;;;;N;;;;; 1F825;UPWARDS TRIANGLE-HEADED ARROW WITH MEDIUM SHAFT;So;0;ON;;;;;N;;;;; 1F826;RIGHTWARDS TRIANGLE-HEADED ARROW WITH MEDIUM SHAFT;So;0;ON;;;;;N;;;;; 1F827;DOWNWARDS TRIANGLE-HEADED ARROW WITH MEDIUM SHAFT;So;0;ON;;;;;N;;;;; 1F828;LEFTWARDS TRIANGLE-HEADED ARROW WITH BOLD SHAFT;So;0;ON;;;;;N;;;;; 1F829;UPWARDS TRIANGLE-HEADED ARROW WITH BOLD SHAFT;So;0;ON;;;;;N;;;;; 1F82A;RIGHTWARDS TRIANGLE-HEADED ARROW WITH BOLD SHAFT;So;0;ON;;;;;N;;;;; 1F82B;DOWNWARDS TRIANGLE-HEADED ARROW WITH BOLD SHAFT;So;0;ON;;;;;N;;;;; 1F82C;LEFTWARDS TRIANGLE-HEADED ARROW WITH HEAVY SHAFT;So;0;ON;;;;;N;;;;; 1F82D;UPWARDS TRIANGLE-HEADED ARROW WITH HEAVY SHAFT;So;0;ON;;;;;N;;;;; 1F82E;RIGHTWARDS TRIANGLE-HEADED ARROW WITH HEAVY SHAFT;So;0;ON;;;;;N;;;;; 1F82F;DOWNWARDS TRIANGLE-HEADED ARROW WITH HEAVY SHAFT;So;0;ON;;;;;N;;;;; 1F830;LEFTWARDS TRIANGLE-HEADED ARROW WITH VERY HEAVY SHAFT;So;0;ON;;;;;N;;;;; 1F831;UPWARDS TRIANGLE-HEADED ARROW WITH VERY HEAVY SHAFT;So;0;ON;;;;;N;;;;; 1F832;RIGHTWARDS TRIANGLE-HEADED ARROW WITH VERY HEAVY SHAFT;So;0;ON;;;;;N;;;;; 1F833;DOWNWARDS TRIANGLE-HEADED ARROW WITH VERY HEAVY SHAFT;So;0;ON;;;;;N;;;;; 1F834;LEFTWARDS FINGER-POST ARROW;So;0;ON;;;;;N;;;;; 1F835;UPWARDS FINGER-POST ARROW;So;0;ON;;;;;N;;;;; 1F836;RIGHTWARDS FINGER-POST ARROW;So;0;ON;;;;;N;;;;; 1F837;DOWNWARDS FINGER-POST ARROW;So;0;ON;;;;;N;;;;; 1F838;LEFTWARDS SQUARED ARROW;So;0;ON;;;;;N;;;;; 1F839;UPWARDS SQUARED ARROW;So;0;ON;;;;;N;;;;; 1F83A;RIGHTWARDS SQUARED ARROW;So;0;ON;;;;;N;;;;; 1F83B;DOWNWARDS SQUARED ARROW;So;0;ON;;;;;N;;;;; 1F83C;LEFTWARDS COMPRESSED ARROW;So;0;ON;;;;;N;;;;; 1F83D;UPWARDS COMPRESSED ARROW;So;0;ON;;;;;N;;;;; 1F83E;RIGHTWARDS COMPRESSED ARROW;So;0;ON;;;;;N;;;;; 1F83F;DOWNWARDS COMPRESSED ARROW;So;0;ON;;;;;N;;;;; 1F840;LEFTWARDS HEAVY COMPRESSED ARROW;So;0;ON;;;;;N;;;;; 1F841;UPWARDS HEAVY COMPRESSED ARROW;So;0;ON;;;;;N;;;;; 1F842;RIGHTWARDS HEAVY COMPRESSED ARROW;So;0;ON;;;;;N;;;;; 1F843;DOWNWARDS HEAVY COMPRESSED ARROW;So;0;ON;;;;;N;;;;; 1F844;LEFTWARDS HEAVY ARROW;So;0;ON;;;;;N;;;;; 1F845;UPWARDS HEAVY ARROW;So;0;ON;;;;;N;;;;; 1F846;RIGHTWARDS HEAVY ARROW;So;0;ON;;;;;N;;;;; 1F847;DOWNWARDS HEAVY ARROW;So;0;ON;;;;;N;;;;; 1F850;LEFTWARDS SANS-SERIF ARROW;So;0;ON;;;;;N;;;;; 1F851;UPWARDS SANS-SERIF ARROW;So;0;ON;;;;;N;;;;; 1F852;RIGHTWARDS SANS-SERIF ARROW;So;0;ON;;;;;N;;;;; 1F853;DOWNWARDS SANS-SERIF ARROW;So;0;ON;;;;;N;;;;; 1F854;NORTH WEST SANS-SERIF ARROW;So;0;ON;;;;;N;;;;; 1F855;NORTH EAST SANS-SERIF ARROW;So;0;ON;;;;;N;;;;; 1F856;SOUTH EAST SANS-SERIF ARROW;So;0;ON;;;;;N;;;;; 1F857;SOUTH WEST SANS-SERIF ARROW;So;0;ON;;;;;N;;;;; 1F858;LEFT RIGHT SANS-SERIF ARROW;So;0;ON;;;;;N;;;;; 1F859;UP DOWN SANS-SERIF ARROW;So;0;ON;;;;;N;;;;; 1F860;WIDE-HEADED LEFTWARDS LIGHT BARB ARROW;So;0;ON;;;;;N;;;;; 1F861;WIDE-HEADED UPWARDS LIGHT BARB ARROW;So;0;ON;;;;;N;;;;; 1F862;WIDE-HEADED RIGHTWARDS LIGHT BARB ARROW;So;0;ON;;;;;N;;;;; 1F863;WIDE-HEADED DOWNWARDS LIGHT BARB ARROW;So;0;ON;;;;;N;;;;; 1F864;WIDE-HEADED NORTH WEST LIGHT BARB ARROW;So;0;ON;;;;;N;;;;; 1F865;WIDE-HEADED NORTH EAST LIGHT BARB ARROW;So;0;ON;;;;;N;;;;; 1F866;WIDE-HEADED SOUTH EAST LIGHT BARB ARROW;So;0;ON;;;;;N;;;;; 1F867;WIDE-HEADED SOUTH WEST LIGHT BARB ARROW;So;0;ON;;;;;N;;;;; 1F868;WIDE-HEADED LEFTWARDS BARB ARROW;So;0;ON;;;;;N;;;;; 1F869;WIDE-HEADED UPWARDS BARB ARROW;So;0;ON;;;;;N;;;;; 1F86A;WIDE-HEADED RIGHTWARDS BARB ARROW;So;0;ON;;;;;N;;;;; 1F86B;WIDE-HEADED DOWNWARDS BARB ARROW;So;0;ON;;;;;N;;;;; 1F86C;WIDE-HEADED NORTH WEST BARB ARROW;So;0;ON;;;;;N;;;;; 1F86D;WIDE-HEADED NORTH EAST BARB ARROW;So;0;ON;;;;;N;;;;; 1F86E;WIDE-HEADED SOUTH EAST BARB ARROW;So;0;ON;;;;;N;;;;; 1F86F;WIDE-HEADED SOUTH WEST BARB ARROW;So;0;ON;;;;;N;;;;; 1F870;WIDE-HEADED LEFTWARDS MEDIUM BARB ARROW;So;0;ON;;;;;N;;;;; 1F871;WIDE-HEADED UPWARDS MEDIUM BARB ARROW;So;0;ON;;;;;N;;;;; 1F872;WIDE-HEADED RIGHTWARDS MEDIUM BARB ARROW;So;0;ON;;;;;N;;;;; 1F873;WIDE-HEADED DOWNWARDS MEDIUM BARB ARROW;So;0;ON;;;;;N;;;;; 1F874;WIDE-HEADED NORTH WEST MEDIUM BARB ARROW;So;0;ON;;;;;N;;;;; 1F875;WIDE-HEADED NORTH EAST MEDIUM BARB ARROW;So;0;ON;;;;;N;;;;; 1F876;WIDE-HEADED SOUTH EAST MEDIUM BARB ARROW;So;0;ON;;;;;N;;;;; 1F877;WIDE-HEADED SOUTH WEST MEDIUM BARB ARROW;So;0;ON;;;;;N;;;;; 1F878;WIDE-HEADED LEFTWARDS HEAVY BARB ARROW;So;0;ON;;;;;N;;;;; 1F879;WIDE-HEADED UPWARDS HEAVY BARB ARROW;So;0;ON;;;;;N;;;;; 1F87A;WIDE-HEADED RIGHTWARDS HEAVY BARB ARROW;So;0;ON;;;;;N;;;;; 1F87B;WIDE-HEADED DOWNWARDS HEAVY BARB ARROW;So;0;ON;;;;;N;;;;; 1F87C;WIDE-HEADED NORTH WEST HEAVY BARB ARROW;So;0;ON;;;;;N;;;;; 1F87D;WIDE-HEADED NORTH EAST HEAVY BARB ARROW;So;0;ON;;;;;N;;;;; 1F87E;WIDE-HEADED SOUTH EAST HEAVY BARB ARROW;So;0;ON;;;;;N;;;;; 1F87F;WIDE-HEADED SOUTH WEST HEAVY BARB ARROW;So;0;ON;;;;;N;;;;; 1F880;WIDE-HEADED LEFTWARDS VERY HEAVY BARB ARROW;So;0;ON;;;;;N;;;;; 1F881;WIDE-HEADED UPWARDS VERY HEAVY BARB ARROW;So;0;ON;;;;;N;;;;; 1F882;WIDE-HEADED RIGHTWARDS VERY HEAVY BARB ARROW;So;0;ON;;;;;N;;;;; 1F883;WIDE-HEADED DOWNWARDS VERY HEAVY BARB ARROW;So;0;ON;;;;;N;;;;; 1F884;WIDE-HEADED NORTH WEST VERY HEAVY BARB ARROW;So;0;ON;;;;;N;;;;; 1F885;WIDE-HEADED NORTH EAST VERY HEAVY BARB ARROW;So;0;ON;;;;;N;;;;; 1F886;WIDE-HEADED SOUTH EAST VERY HEAVY BARB ARROW;So;0;ON;;;;;N;;;;; 1F887;WIDE-HEADED SOUTH WEST VERY HEAVY BARB ARROW;So;0;ON;;;;;N;;;;; 1F890;LEFTWARDS TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F891;UPWARDS TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F892;RIGHTWARDS TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F893;DOWNWARDS TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F894;LEFTWARDS WHITE ARROW WITHIN TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F895;UPWARDS WHITE ARROW WITHIN TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F896;RIGHTWARDS WHITE ARROW WITHIN TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F897;DOWNWARDS WHITE ARROW WITHIN TRIANGLE ARROWHEAD;So;0;ON;;;;;N;;;;; 1F898;LEFTWARDS ARROW WITH NOTCHED TAIL;So;0;ON;;;;;N;;;;; 1F899;UPWARDS ARROW WITH NOTCHED TAIL;So;0;ON;;;;;N;;;;; 1F89A;RIGHTWARDS ARROW WITH NOTCHED TAIL;So;0;ON;;;;;N;;;;; 1F89B;DOWNWARDS ARROW WITH NOTCHED TAIL;So;0;ON;;;;;N;;;;; 1F89C;HEAVY ARROW SHAFT WIDTH ONE;So;0;ON;;;;;N;;;;; 1F89D;HEAVY ARROW SHAFT WIDTH TWO THIRDS;So;0;ON;;;;;N;;;;; 1F89E;HEAVY ARROW SHAFT WIDTH ONE HALF;So;0;ON;;;;;N;;;;; 1F89F;HEAVY ARROW SHAFT WIDTH ONE THIRD;So;0;ON;;;;;N;;;;; 1F8A0;LEFTWARDS BOTTOM-SHADED WHITE ARROW;So;0;ON;;;;;N;;;;; 1F8A1;RIGHTWARDS BOTTOM SHADED WHITE ARROW;So;0;ON;;;;;N;;;;; 1F8A2;LEFTWARDS TOP SHADED WHITE ARROW;So;0;ON;;;;;N;;;;; 1F8A3;RIGHTWARDS TOP SHADED WHITE ARROW;So;0;ON;;;;;N;;;;; 1F8A4;LEFTWARDS LEFT-SHADED WHITE ARROW;So;0;ON;;;;;N;;;;; 1F8A5;RIGHTWARDS RIGHT-SHADED WHITE ARROW;So;0;ON;;;;;N;;;;; 1F8A6;LEFTWARDS RIGHT-SHADED WHITE ARROW;So;0;ON;;;;;N;;;;; 1F8A7;RIGHTWARDS LEFT-SHADED WHITE ARROW;So;0;ON;;;;;N;;;;; 1F8A8;LEFTWARDS BACK-TILTED SHADOWED WHITE ARROW;So;0;ON;;;;;N;;;;; 1F8A9;RIGHTWARDS BACK-TILTED SHADOWED WHITE ARROW;So;0;ON;;;;;N;;;;; 1F8AA;LEFTWARDS FRONT-TILTED SHADOWED WHITE ARROW;So;0;ON;;;;;N;;;;; 1F8AB;RIGHTWARDS FRONT-TILTED SHADOWED WHITE ARROW;So;0;ON;;;;;N;;;;; 1F8AC;WHITE ARROW SHAFT WIDTH ONE;So;0;ON;;;;;N;;;;; 1F8AD;WHITE ARROW SHAFT WIDTH TWO THIRDS;So;0;ON;;;;;N;;;;; 1F910;ZIPPER-MOUTH FACE;So;0;ON;;;;;N;;;;; 1F911;MONEY-MOUTH FACE;So;0;ON;;;;;N;;;;; 1F912;FACE WITH THERMOMETER;So;0;ON;;;;;N;;;;; 1F913;NERD FACE;So;0;ON;;;;;N;;;;; 1F914;THINKING FACE;So;0;ON;;;;;N;;;;; 1F915;FACE WITH HEAD-BANDAGE;So;0;ON;;;;;N;;;;; 1F916;ROBOT FACE;So;0;ON;;;;;N;;;;; 1F917;HUGGING FACE;So;0;ON;;;;;N;;;;; 1F918;SIGN OF THE HORNS;So;0;ON;;;;;N;;;;; 1F919;CALL ME HAND;So;0;ON;;;;;N;;;;; 1F91A;RAISED BACK OF HAND;So;0;ON;;;;;N;;;;; 1F91B;LEFT-FACING FIST;So;0;ON;;;;;N;;;;; 1F91C;RIGHT-FACING FIST;So;0;ON;;;;;N;;;;; 1F91D;HANDSHAKE;So;0;ON;;;;;N;;;;; 1F91E;HAND WITH INDEX AND MIDDLE FINGERS CROSSED;So;0;ON;;;;;N;;;;; 1F920;FACE WITH COWBOY HAT;So;0;ON;;;;;N;;;;; 1F921;CLOWN FACE;So;0;ON;;;;;N;;;;; 1F922;NAUSEATED FACE;So;0;ON;;;;;N;;;;; 1F923;ROLLING ON THE FLOOR LAUGHING;So;0;ON;;;;;N;;;;; 1F924;DROOLING FACE;So;0;ON;;;;;N;;;;; 1F925;LYING FACE;So;0;ON;;;;;N;;;;; 1F926;FACE PALM;So;0;ON;;;;;N;;;;; 1F927;SNEEZING FACE;So;0;ON;;;;;N;;;;; 1F930;PREGNANT WOMAN;So;0;ON;;;;;N;;;;; 1F933;SELFIE;So;0;ON;;;;;N;;;;; 1F934;PRINCE;So;0;ON;;;;;N;;;;; 1F935;MAN IN TUXEDO;So;0;ON;;;;;N;;;;; 1F936;MOTHER CHRISTMAS;So;0;ON;;;;;N;;;;; 1F937;SHRUG;So;0;ON;;;;;N;;;;; 1F938;PERSON DOING CARTWHEEL;So;0;ON;;;;;N;;;;; 1F939;JUGGLING;So;0;ON;;;;;N;;;;; 1F93A;FENCER;So;0;ON;;;;;N;;;;; 1F93B;MODERN PENTATHLON;So;0;ON;;;;;N;;;;; 1F93C;WRESTLERS;So;0;ON;;;;;N;;;;; 1F93D;WATER POLO;So;0;ON;;;;;N;;;;; 1F93E;HANDBALL;So;0;ON;;;;;N;;;;; 1F940;WILTED FLOWER;So;0;ON;;;;;N;;;;; 1F941;DRUM WITH DRUMSTICKS;So;0;ON;;;;;N;;;;; 1F942;CLINKING GLASSES;So;0;ON;;;;;N;;;;; 1F943;TUMBLER GLASS;So;0;ON;;;;;N;;;;; 1F944;SPOON;So;0;ON;;;;;N;;;;; 1F945;GOAL NET;So;0;ON;;;;;N;;;;; 1F946;RIFLE;So;0;ON;;;;;N;;;;; 1F947;FIRST PLACE MEDAL;So;0;ON;;;;;N;;;;; 1F948;SECOND PLACE MEDAL;So;0;ON;;;;;N;;;;; 1F949;THIRD PLACE MEDAL;So;0;ON;;;;;N;;;;; 1F94A;BOXING GLOVE;So;0;ON;;;;;N;;;;; 1F94B;MARTIAL ARTS UNIFORM;So;0;ON;;;;;N;;;;; 1F950;CROISSANT;So;0;ON;;;;;N;;;;; 1F951;AVOCADO;So;0;ON;;;;;N;;;;; 1F952;CUCUMBER;So;0;ON;;;;;N;;;;; 1F953;BACON;So;0;ON;;;;;N;;;;; 1F954;POTATO;So;0;ON;;;;;N;;;;; 1F955;CARROT;So;0;ON;;;;;N;;;;; 1F956;BAGUETTE BREAD;So;0;ON;;;;;N;;;;; 1F957;GREEN SALAD;So;0;ON;;;;;N;;;;; 1F958;SHALLOW PAN OF FOOD;So;0;ON;;;;;N;;;;; 1F959;STUFFED FLATBREAD;So;0;ON;;;;;N;;;;; 1F95A;EGG;So;0;ON;;;;;N;;;;; 1F95B;GLASS OF MILK;So;0;ON;;;;;N;;;;; 1F95C;PEANUTS;So;0;ON;;;;;N;;;;; 1F95D;KIWIFRUIT;So;0;ON;;;;;N;;;;; 1F95E;PANCAKES;So;0;ON;;;;;N;;;;; 1F980;CRAB;So;0;ON;;;;;N;;;;; 1F981;LION FACE;So;0;ON;;;;;N;;;;; 1F982;SCORPION;So;0;ON;;;;;N;;;;; 1F983;TURKEY;So;0;ON;;;;;N;;;;; 1F984;UNICORN FACE;So;0;ON;;;;;N;;;;; 1F985;EAGLE;So;0;ON;;;;;N;;;;; 1F986;DUCK;So;0;ON;;;;;N;;;;; 1F987;BAT;So;0;ON;;;;;N;;;;; 1F988;SHARK;So;0;ON;;;;;N;;;;; 1F989;OWL;So;0;ON;;;;;N;;;;; 1F98A;FOX FACE;So;0;ON;;;;;N;;;;; 1F98B;BUTTERFLY;So;0;ON;;;;;N;;;;; 1F98C;DEER;So;0;ON;;;;;N;;;;; 1F98D;GORILLA;So;0;ON;;;;;N;;;;; 1F98E;LIZARD;So;0;ON;;;;;N;;;;; 1F98F;RHINOCEROS;So;0;ON;;;;;N;;;;; 1F990;SHRIMP;So;0;ON;;;;;N;;;;; 1F991;SQUID;So;0;ON;;;;;N;;;;; 1F9C0;CHEESE WEDGE;So;0;ON;;;;;N;;;;; 20000;;Lo;0;L;;;;;N;;;;; 2A6D6;;Lo;0;L;;;;;N;;;;; 2A700;;Lo;0;L;;;;;N;;;;; 2B734;;Lo;0;L;;;;;N;;;;; 2B740;;Lo;0;L;;;;;N;;;;; 2B81D;;Lo;0;L;;;;;N;;;;; 2B820;;Lo;0;L;;;;;N;;;;; 2CEA1;;Lo;0;L;;;;;N;;;;; 2F800;CJK COMPATIBILITY IDEOGRAPH-2F800;Lo;0;L;4E3D;;;;N;;;;; 2F801;CJK COMPATIBILITY IDEOGRAPH-2F801;Lo;0;L;4E38;;;;N;;;;; 2F802;CJK COMPATIBILITY IDEOGRAPH-2F802;Lo;0;L;4E41;;;;N;;;;; 2F803;CJK COMPATIBILITY IDEOGRAPH-2F803;Lo;0;L;20122;;;;N;;;;; 2F804;CJK COMPATIBILITY IDEOGRAPH-2F804;Lo;0;L;4F60;;;;N;;;;; 2F805;CJK COMPATIBILITY IDEOGRAPH-2F805;Lo;0;L;4FAE;;;;N;;;;; 2F806;CJK COMPATIBILITY IDEOGRAPH-2F806;Lo;0;L;4FBB;;;;N;;;;; 2F807;CJK COMPATIBILITY IDEOGRAPH-2F807;Lo;0;L;5002;;;;N;;;;; 2F808;CJK COMPATIBILITY IDEOGRAPH-2F808;Lo;0;L;507A;;;;N;;;;; 2F809;CJK COMPATIBILITY IDEOGRAPH-2F809;Lo;0;L;5099;;;;N;;;;; 2F80A;CJK COMPATIBILITY IDEOGRAPH-2F80A;Lo;0;L;50E7;;;;N;;;;; 2F80B;CJK COMPATIBILITY IDEOGRAPH-2F80B;Lo;0;L;50CF;;;;N;;;;; 2F80C;CJK COMPATIBILITY IDEOGRAPH-2F80C;Lo;0;L;349E;;;;N;;;;; 2F80D;CJK COMPATIBILITY IDEOGRAPH-2F80D;Lo;0;L;2063A;;;;N;;;;; 2F80E;CJK COMPATIBILITY IDEOGRAPH-2F80E;Lo;0;L;514D;;;;N;;;;; 2F80F;CJK COMPATIBILITY IDEOGRAPH-2F80F;Lo;0;L;5154;;;;N;;;;; 2F810;CJK COMPATIBILITY IDEOGRAPH-2F810;Lo;0;L;5164;;;;N;;;;; 2F811;CJK COMPATIBILITY IDEOGRAPH-2F811;Lo;0;L;5177;;;;N;;;;; 2F812;CJK COMPATIBILITY IDEOGRAPH-2F812;Lo;0;L;2051C;;;;N;;;;; 2F813;CJK COMPATIBILITY IDEOGRAPH-2F813;Lo;0;L;34B9;;;;N;;;;; 2F814;CJK COMPATIBILITY IDEOGRAPH-2F814;Lo;0;L;5167;;;;N;;;;; 2F815;CJK COMPATIBILITY IDEOGRAPH-2F815;Lo;0;L;518D;;;;N;;;;; 2F816;CJK COMPATIBILITY IDEOGRAPH-2F816;Lo;0;L;2054B;;;;N;;;;; 2F817;CJK COMPATIBILITY IDEOGRAPH-2F817;Lo;0;L;5197;;;;N;;;;; 2F818;CJK COMPATIBILITY IDEOGRAPH-2F818;Lo;0;L;51A4;;;;N;;;;; 2F819;CJK COMPATIBILITY IDEOGRAPH-2F819;Lo;0;L;4ECC;;;;N;;;;; 2F81A;CJK COMPATIBILITY IDEOGRAPH-2F81A;Lo;0;L;51AC;;;;N;;;;; 2F81B;CJK COMPATIBILITY IDEOGRAPH-2F81B;Lo;0;L;51B5;;;;N;;;;; 2F81C;CJK COMPATIBILITY IDEOGRAPH-2F81C;Lo;0;L;291DF;;;;N;;;;; 2F81D;CJK COMPATIBILITY IDEOGRAPH-2F81D;Lo;0;L;51F5;;;;N;;;;; 2F81E;CJK COMPATIBILITY IDEOGRAPH-2F81E;Lo;0;L;5203;;;;N;;;;; 2F81F;CJK COMPATIBILITY IDEOGRAPH-2F81F;Lo;0;L;34DF;;;;N;;;;; 2F820;CJK COMPATIBILITY IDEOGRAPH-2F820;Lo;0;L;523B;;;;N;;;;; 2F821;CJK COMPATIBILITY IDEOGRAPH-2F821;Lo;0;L;5246;;;;N;;;;; 2F822;CJK COMPATIBILITY IDEOGRAPH-2F822;Lo;0;L;5272;;;;N;;;;; 2F823;CJK COMPATIBILITY IDEOGRAPH-2F823;Lo;0;L;5277;;;;N;;;;; 2F824;CJK COMPATIBILITY IDEOGRAPH-2F824;Lo;0;L;3515;;;;N;;;;; 2F825;CJK COMPATIBILITY IDEOGRAPH-2F825;Lo;0;L;52C7;;;;N;;;;; 2F826;CJK COMPATIBILITY IDEOGRAPH-2F826;Lo;0;L;52C9;;;;N;;;;; 2F827;CJK COMPATIBILITY IDEOGRAPH-2F827;Lo;0;L;52E4;;;;N;;;;; 2F828;CJK COMPATIBILITY IDEOGRAPH-2F828;Lo;0;L;52FA;;;;N;;;;; 2F829;CJK COMPATIBILITY IDEOGRAPH-2F829;Lo;0;L;5305;;;;N;;;;; 2F82A;CJK COMPATIBILITY IDEOGRAPH-2F82A;Lo;0;L;5306;;;;N;;;;; 2F82B;CJK COMPATIBILITY IDEOGRAPH-2F82B;Lo;0;L;5317;;;;N;;;;; 2F82C;CJK COMPATIBILITY IDEOGRAPH-2F82C;Lo;0;L;5349;;;;N;;;;; 2F82D;CJK COMPATIBILITY IDEOGRAPH-2F82D;Lo;0;L;5351;;;;N;;;;; 2F82E;CJK COMPATIBILITY IDEOGRAPH-2F82E;Lo;0;L;535A;;;;N;;;;; 2F82F;CJK COMPATIBILITY IDEOGRAPH-2F82F;Lo;0;L;5373;;;;N;;;;; 2F830;CJK COMPATIBILITY IDEOGRAPH-2F830;Lo;0;L;537D;;;;N;;;;; 2F831;CJK COMPATIBILITY IDEOGRAPH-2F831;Lo;0;L;537F;;;;N;;;;; 2F832;CJK COMPATIBILITY IDEOGRAPH-2F832;Lo;0;L;537F;;;;N;;;;; 2F833;CJK COMPATIBILITY IDEOGRAPH-2F833;Lo;0;L;537F;;;;N;;;;; 2F834;CJK COMPATIBILITY IDEOGRAPH-2F834;Lo;0;L;20A2C;;;;N;;;;; 2F835;CJK COMPATIBILITY IDEOGRAPH-2F835;Lo;0;L;7070;;;;N;;;;; 2F836;CJK COMPATIBILITY IDEOGRAPH-2F836;Lo;0;L;53CA;;;;N;;;;; 2F837;CJK COMPATIBILITY IDEOGRAPH-2F837;Lo;0;L;53DF;;;;N;;;;; 2F838;CJK COMPATIBILITY IDEOGRAPH-2F838;Lo;0;L;20B63;;;;N;;;;; 2F839;CJK COMPATIBILITY IDEOGRAPH-2F839;Lo;0;L;53EB;;;;N;;;;; 2F83A;CJK COMPATIBILITY IDEOGRAPH-2F83A;Lo;0;L;53F1;;;;N;;;;; 2F83B;CJK COMPATIBILITY IDEOGRAPH-2F83B;Lo;0;L;5406;;;;N;;;;; 2F83C;CJK COMPATIBILITY IDEOGRAPH-2F83C;Lo;0;L;549E;;;;N;;;;; 2F83D;CJK COMPATIBILITY IDEOGRAPH-2F83D;Lo;0;L;5438;;;;N;;;;; 2F83E;CJK COMPATIBILITY IDEOGRAPH-2F83E;Lo;0;L;5448;;;;N;;;;; 2F83F;CJK COMPATIBILITY IDEOGRAPH-2F83F;Lo;0;L;5468;;;;N;;;;; 2F840;CJK COMPATIBILITY IDEOGRAPH-2F840;Lo;0;L;54A2;;;;N;;;;; 2F841;CJK COMPATIBILITY IDEOGRAPH-2F841;Lo;0;L;54F6;;;;N;;;;; 2F842;CJK COMPATIBILITY IDEOGRAPH-2F842;Lo;0;L;5510;;;;N;;;;; 2F843;CJK COMPATIBILITY IDEOGRAPH-2F843;Lo;0;L;5553;;;;N;;;;; 2F844;CJK COMPATIBILITY IDEOGRAPH-2F844;Lo;0;L;5563;;;;N;;;;; 2F845;CJK COMPATIBILITY IDEOGRAPH-2F845;Lo;0;L;5584;;;;N;;;;; 2F846;CJK COMPATIBILITY IDEOGRAPH-2F846;Lo;0;L;5584;;;;N;;;;; 2F847;CJK COMPATIBILITY IDEOGRAPH-2F847;Lo;0;L;5599;;;;N;;;;; 2F848;CJK COMPATIBILITY IDEOGRAPH-2F848;Lo;0;L;55AB;;;;N;;;;; 2F849;CJK COMPATIBILITY IDEOGRAPH-2F849;Lo;0;L;55B3;;;;N;;;;; 2F84A;CJK COMPATIBILITY IDEOGRAPH-2F84A;Lo;0;L;55C2;;;;N;;;;; 2F84B;CJK COMPATIBILITY IDEOGRAPH-2F84B;Lo;0;L;5716;;;;N;;;;; 2F84C;CJK COMPATIBILITY IDEOGRAPH-2F84C;Lo;0;L;5606;;;;N;;;;; 2F84D;CJK COMPATIBILITY IDEOGRAPH-2F84D;Lo;0;L;5717;;;;N;;;;; 2F84E;CJK COMPATIBILITY IDEOGRAPH-2F84E;Lo;0;L;5651;;;;N;;;;; 2F84F;CJK COMPATIBILITY IDEOGRAPH-2F84F;Lo;0;L;5674;;;;N;;;;; 2F850;CJK COMPATIBILITY IDEOGRAPH-2F850;Lo;0;L;5207;;;;N;;;;; 2F851;CJK COMPATIBILITY IDEOGRAPH-2F851;Lo;0;L;58EE;;;;N;;;;; 2F852;CJK COMPATIBILITY IDEOGRAPH-2F852;Lo;0;L;57CE;;;;N;;;;; 2F853;CJK COMPATIBILITY IDEOGRAPH-2F853;Lo;0;L;57F4;;;;N;;;;; 2F854;CJK COMPATIBILITY IDEOGRAPH-2F854;Lo;0;L;580D;;;;N;;;;; 2F855;CJK COMPATIBILITY IDEOGRAPH-2F855;Lo;0;L;578B;;;;N;;;;; 2F856;CJK COMPATIBILITY IDEOGRAPH-2F856;Lo;0;L;5832;;;;N;;;;; 2F857;CJK COMPATIBILITY IDEOGRAPH-2F857;Lo;0;L;5831;;;;N;;;;; 2F858;CJK COMPATIBILITY IDEOGRAPH-2F858;Lo;0;L;58AC;;;;N;;;;; 2F859;CJK COMPATIBILITY IDEOGRAPH-2F859;Lo;0;L;214E4;;;;N;;;;; 2F85A;CJK COMPATIBILITY IDEOGRAPH-2F85A;Lo;0;L;58F2;;;;N;;;;; 2F85B;CJK COMPATIBILITY IDEOGRAPH-2F85B;Lo;0;L;58F7;;;;N;;;;; 2F85C;CJK COMPATIBILITY IDEOGRAPH-2F85C;Lo;0;L;5906;;;;N;;;;; 2F85D;CJK COMPATIBILITY IDEOGRAPH-2F85D;Lo;0;L;591A;;;;N;;;;; 2F85E;CJK COMPATIBILITY IDEOGRAPH-2F85E;Lo;0;L;5922;;;;N;;;;; 2F85F;CJK COMPATIBILITY IDEOGRAPH-2F85F;Lo;0;L;5962;;;;N;;;;; 2F860;CJK COMPATIBILITY IDEOGRAPH-2F860;Lo;0;L;216A8;;;;N;;;;; 2F861;CJK COMPATIBILITY IDEOGRAPH-2F861;Lo;0;L;216EA;;;;N;;;;; 2F862;CJK COMPATIBILITY IDEOGRAPH-2F862;Lo;0;L;59EC;;;;N;;;;; 2F863;CJK COMPATIBILITY IDEOGRAPH-2F863;Lo;0;L;5A1B;;;;N;;;;; 2F864;CJK COMPATIBILITY IDEOGRAPH-2F864;Lo;0;L;5A27;;;;N;;;;; 2F865;CJK COMPATIBILITY IDEOGRAPH-2F865;Lo;0;L;59D8;;;;N;;;;; 2F866;CJK COMPATIBILITY IDEOGRAPH-2F866;Lo;0;L;5A66;;;;N;;;;; 2F867;CJK COMPATIBILITY IDEOGRAPH-2F867;Lo;0;L;36EE;;;;N;;;;; 2F868;CJK COMPATIBILITY IDEOGRAPH-2F868;Lo;0;L;36FC;;;;N;;;;; 2F869;CJK COMPATIBILITY IDEOGRAPH-2F869;Lo;0;L;5B08;;;;N;;;;; 2F86A;CJK COMPATIBILITY IDEOGRAPH-2F86A;Lo;0;L;5B3E;;;;N;;;;; 2F86B;CJK COMPATIBILITY IDEOGRAPH-2F86B;Lo;0;L;5B3E;;;;N;;;;; 2F86C;CJK COMPATIBILITY IDEOGRAPH-2F86C;Lo;0;L;219C8;;;;N;;;;; 2F86D;CJK COMPATIBILITY IDEOGRAPH-2F86D;Lo;0;L;5BC3;;;;N;;;;; 2F86E;CJK COMPATIBILITY IDEOGRAPH-2F86E;Lo;0;L;5BD8;;;;N;;;;; 2F86F;CJK COMPATIBILITY IDEOGRAPH-2F86F;Lo;0;L;5BE7;;;;N;;;;; 2F870;CJK COMPATIBILITY IDEOGRAPH-2F870;Lo;0;L;5BF3;;;;N;;;;; 2F871;CJK COMPATIBILITY IDEOGRAPH-2F871;Lo;0;L;21B18;;;;N;;;;; 2F872;CJK COMPATIBILITY IDEOGRAPH-2F872;Lo;0;L;5BFF;;;;N;;;;; 2F873;CJK COMPATIBILITY IDEOGRAPH-2F873;Lo;0;L;5C06;;;;N;;;;; 2F874;CJK COMPATIBILITY IDEOGRAPH-2F874;Lo;0;L;5F53;;;;N;;;;; 2F875;CJK COMPATIBILITY IDEOGRAPH-2F875;Lo;0;L;5C22;;;;N;;;;; 2F876;CJK COMPATIBILITY IDEOGRAPH-2F876;Lo;0;L;3781;;;;N;;;;; 2F877;CJK COMPATIBILITY IDEOGRAPH-2F877;Lo;0;L;5C60;;;;N;;;;; 2F878;CJK COMPATIBILITY IDEOGRAPH-2F878;Lo;0;L;5C6E;;;;N;;;;; 2F879;CJK COMPATIBILITY IDEOGRAPH-2F879;Lo;0;L;5CC0;;;;N;;;;; 2F87A;CJK COMPATIBILITY IDEOGRAPH-2F87A;Lo;0;L;5C8D;;;;N;;;;; 2F87B;CJK COMPATIBILITY IDEOGRAPH-2F87B;Lo;0;L;21DE4;;;;N;;;;; 2F87C;CJK COMPATIBILITY IDEOGRAPH-2F87C;Lo;0;L;5D43;;;;N;;;;; 2F87D;CJK COMPATIBILITY IDEOGRAPH-2F87D;Lo;0;L;21DE6;;;;N;;;;; 2F87E;CJK COMPATIBILITY IDEOGRAPH-2F87E;Lo;0;L;5D6E;;;;N;;;;; 2F87F;CJK COMPATIBILITY IDEOGRAPH-2F87F;Lo;0;L;5D6B;;;;N;;;;; 2F880;CJK COMPATIBILITY IDEOGRAPH-2F880;Lo;0;L;5D7C;;;;N;;;;; 2F881;CJK COMPATIBILITY IDEOGRAPH-2F881;Lo;0;L;5DE1;;;;N;;;;; 2F882;CJK COMPATIBILITY IDEOGRAPH-2F882;Lo;0;L;5DE2;;;;N;;;;; 2F883;CJK COMPATIBILITY IDEOGRAPH-2F883;Lo;0;L;382F;;;;N;;;;; 2F884;CJK COMPATIBILITY IDEOGRAPH-2F884;Lo;0;L;5DFD;;;;N;;;;; 2F885;CJK COMPATIBILITY IDEOGRAPH-2F885;Lo;0;L;5E28;;;;N;;;;; 2F886;CJK COMPATIBILITY IDEOGRAPH-2F886;Lo;0;L;5E3D;;;;N;;;;; 2F887;CJK COMPATIBILITY IDEOGRAPH-2F887;Lo;0;L;5E69;;;;N;;;;; 2F888;CJK COMPATIBILITY IDEOGRAPH-2F888;Lo;0;L;3862;;;;N;;;;; 2F889;CJK COMPATIBILITY IDEOGRAPH-2F889;Lo;0;L;22183;;;;N;;;;; 2F88A;CJK COMPATIBILITY IDEOGRAPH-2F88A;Lo;0;L;387C;;;;N;;;;; 2F88B;CJK COMPATIBILITY IDEOGRAPH-2F88B;Lo;0;L;5EB0;;;;N;;;;; 2F88C;CJK COMPATIBILITY IDEOGRAPH-2F88C;Lo;0;L;5EB3;;;;N;;;;; 2F88D;CJK COMPATIBILITY IDEOGRAPH-2F88D;Lo;0;L;5EB6;;;;N;;;;; 2F88E;CJK COMPATIBILITY IDEOGRAPH-2F88E;Lo;0;L;5ECA;;;;N;;;;; 2F88F;CJK COMPATIBILITY IDEOGRAPH-2F88F;Lo;0;L;2A392;;;;N;;;;; 2F890;CJK COMPATIBILITY IDEOGRAPH-2F890;Lo;0;L;5EFE;;;9;N;;;;; 2F891;CJK COMPATIBILITY IDEOGRAPH-2F891;Lo;0;L;22331;;;;N;;;;; 2F892;CJK COMPATIBILITY IDEOGRAPH-2F892;Lo;0;L;22331;;;;N;;;;; 2F893;CJK COMPATIBILITY IDEOGRAPH-2F893;Lo;0;L;8201;;;;N;;;;; 2F894;CJK COMPATIBILITY IDEOGRAPH-2F894;Lo;0;L;5F22;;;;N;;;;; 2F895;CJK COMPATIBILITY IDEOGRAPH-2F895;Lo;0;L;5F22;;;;N;;;;; 2F896;CJK COMPATIBILITY IDEOGRAPH-2F896;Lo;0;L;38C7;;;;N;;;;; 2F897;CJK COMPATIBILITY IDEOGRAPH-2F897;Lo;0;L;232B8;;;;N;;;;; 2F898;CJK COMPATIBILITY IDEOGRAPH-2F898;Lo;0;L;261DA;;;;N;;;;; 2F899;CJK COMPATIBILITY IDEOGRAPH-2F899;Lo;0;L;5F62;;;;N;;;;; 2F89A;CJK COMPATIBILITY IDEOGRAPH-2F89A;Lo;0;L;5F6B;;;;N;;;;; 2F89B;CJK COMPATIBILITY IDEOGRAPH-2F89B;Lo;0;L;38E3;;;;N;;;;; 2F89C;CJK COMPATIBILITY IDEOGRAPH-2F89C;Lo;0;L;5F9A;;;;N;;;;; 2F89D;CJK COMPATIBILITY IDEOGRAPH-2F89D;Lo;0;L;5FCD;;;;N;;;;; 2F89E;CJK COMPATIBILITY IDEOGRAPH-2F89E;Lo;0;L;5FD7;;;;N;;;;; 2F89F;CJK COMPATIBILITY IDEOGRAPH-2F89F;Lo;0;L;5FF9;;;;N;;;;; 2F8A0;CJK COMPATIBILITY IDEOGRAPH-2F8A0;Lo;0;L;6081;;;;N;;;;; 2F8A1;CJK COMPATIBILITY IDEOGRAPH-2F8A1;Lo;0;L;393A;;;;N;;;;; 2F8A2;CJK COMPATIBILITY IDEOGRAPH-2F8A2;Lo;0;L;391C;;;;N;;;;; 2F8A3;CJK COMPATIBILITY IDEOGRAPH-2F8A3;Lo;0;L;6094;;;;N;;;;; 2F8A4;CJK COMPATIBILITY IDEOGRAPH-2F8A4;Lo;0;L;226D4;;;;N;;;;; 2F8A5;CJK COMPATIBILITY IDEOGRAPH-2F8A5;Lo;0;L;60C7;;;;N;;;;; 2F8A6;CJK COMPATIBILITY IDEOGRAPH-2F8A6;Lo;0;L;6148;;;;N;;;;; 2F8A7;CJK COMPATIBILITY IDEOGRAPH-2F8A7;Lo;0;L;614C;;;;N;;;;; 2F8A8;CJK COMPATIBILITY IDEOGRAPH-2F8A8;Lo;0;L;614E;;;;N;;;;; 2F8A9;CJK COMPATIBILITY IDEOGRAPH-2F8A9;Lo;0;L;614C;;;;N;;;;; 2F8AA;CJK COMPATIBILITY IDEOGRAPH-2F8AA;Lo;0;L;617A;;;;N;;;;; 2F8AB;CJK COMPATIBILITY IDEOGRAPH-2F8AB;Lo;0;L;618E;;;;N;;;;; 2F8AC;CJK COMPATIBILITY IDEOGRAPH-2F8AC;Lo;0;L;61B2;;;;N;;;;; 2F8AD;CJK COMPATIBILITY IDEOGRAPH-2F8AD;Lo;0;L;61A4;;;;N;;;;; 2F8AE;CJK COMPATIBILITY IDEOGRAPH-2F8AE;Lo;0;L;61AF;;;;N;;;;; 2F8AF;CJK COMPATIBILITY IDEOGRAPH-2F8AF;Lo;0;L;61DE;;;;N;;;;; 2F8B0;CJK COMPATIBILITY IDEOGRAPH-2F8B0;Lo;0;L;61F2;;;;N;;;;; 2F8B1;CJK COMPATIBILITY IDEOGRAPH-2F8B1;Lo;0;L;61F6;;;;N;;;;; 2F8B2;CJK COMPATIBILITY IDEOGRAPH-2F8B2;Lo;0;L;6210;;;;N;;;;; 2F8B3;CJK COMPATIBILITY IDEOGRAPH-2F8B3;Lo;0;L;621B;;;;N;;;;; 2F8B4;CJK COMPATIBILITY IDEOGRAPH-2F8B4;Lo;0;L;625D;;;;N;;;;; 2F8B5;CJK COMPATIBILITY IDEOGRAPH-2F8B5;Lo;0;L;62B1;;;;N;;;;; 2F8B6;CJK COMPATIBILITY IDEOGRAPH-2F8B6;Lo;0;L;62D4;;;;N;;;;; 2F8B7;CJK COMPATIBILITY IDEOGRAPH-2F8B7;Lo;0;L;6350;;;;N;;;;; 2F8B8;CJK COMPATIBILITY IDEOGRAPH-2F8B8;Lo;0;L;22B0C;;;;N;;;;; 2F8B9;CJK COMPATIBILITY IDEOGRAPH-2F8B9;Lo;0;L;633D;;;;N;;;;; 2F8BA;CJK COMPATIBILITY IDEOGRAPH-2F8BA;Lo;0;L;62FC;;;;N;;;;; 2F8BB;CJK COMPATIBILITY IDEOGRAPH-2F8BB;Lo;0;L;6368;;;;N;;;;; 2F8BC;CJK COMPATIBILITY IDEOGRAPH-2F8BC;Lo;0;L;6383;;;;N;;;;; 2F8BD;CJK COMPATIBILITY IDEOGRAPH-2F8BD;Lo;0;L;63E4;;;;N;;;;; 2F8BE;CJK COMPATIBILITY IDEOGRAPH-2F8BE;Lo;0;L;22BF1;;;;N;;;;; 2F8BF;CJK COMPATIBILITY IDEOGRAPH-2F8BF;Lo;0;L;6422;;;;N;;;;; 2F8C0;CJK COMPATIBILITY IDEOGRAPH-2F8C0;Lo;0;L;63C5;;;;N;;;;; 2F8C1;CJK COMPATIBILITY IDEOGRAPH-2F8C1;Lo;0;L;63A9;;;;N;;;;; 2F8C2;CJK COMPATIBILITY IDEOGRAPH-2F8C2;Lo;0;L;3A2E;;;;N;;;;; 2F8C3;CJK COMPATIBILITY IDEOGRAPH-2F8C3;Lo;0;L;6469;;;;N;;;;; 2F8C4;CJK COMPATIBILITY IDEOGRAPH-2F8C4;Lo;0;L;647E;;;;N;;;;; 2F8C5;CJK COMPATIBILITY IDEOGRAPH-2F8C5;Lo;0;L;649D;;;;N;;;;; 2F8C6;CJK COMPATIBILITY IDEOGRAPH-2F8C6;Lo;0;L;6477;;;;N;;;;; 2F8C7;CJK COMPATIBILITY IDEOGRAPH-2F8C7;Lo;0;L;3A6C;;;;N;;;;; 2F8C8;CJK COMPATIBILITY IDEOGRAPH-2F8C8;Lo;0;L;654F;;;;N;;;;; 2F8C9;CJK COMPATIBILITY IDEOGRAPH-2F8C9;Lo;0;L;656C;;;;N;;;;; 2F8CA;CJK COMPATIBILITY IDEOGRAPH-2F8CA;Lo;0;L;2300A;;;;N;;;;; 2F8CB;CJK COMPATIBILITY IDEOGRAPH-2F8CB;Lo;0;L;65E3;;;;N;;;;; 2F8CC;CJK COMPATIBILITY IDEOGRAPH-2F8CC;Lo;0;L;66F8;;;;N;;;;; 2F8CD;CJK COMPATIBILITY IDEOGRAPH-2F8CD;Lo;0;L;6649;;;;N;;;;; 2F8CE;CJK COMPATIBILITY IDEOGRAPH-2F8CE;Lo;0;L;3B19;;;;N;;;;; 2F8CF;CJK COMPATIBILITY IDEOGRAPH-2F8CF;Lo;0;L;6691;;;;N;;;;; 2F8D0;CJK COMPATIBILITY IDEOGRAPH-2F8D0;Lo;0;L;3B08;;;;N;;;;; 2F8D1;CJK COMPATIBILITY IDEOGRAPH-2F8D1;Lo;0;L;3AE4;;;;N;;;;; 2F8D2;CJK COMPATIBILITY IDEOGRAPH-2F8D2;Lo;0;L;5192;;;;N;;;;; 2F8D3;CJK COMPATIBILITY IDEOGRAPH-2F8D3;Lo;0;L;5195;;;;N;;;;; 2F8D4;CJK COMPATIBILITY IDEOGRAPH-2F8D4;Lo;0;L;6700;;;;N;;;;; 2F8D5;CJK COMPATIBILITY IDEOGRAPH-2F8D5;Lo;0;L;669C;;;;N;;;;; 2F8D6;CJK COMPATIBILITY IDEOGRAPH-2F8D6;Lo;0;L;80AD;;;;N;;;;; 2F8D7;CJK COMPATIBILITY IDEOGRAPH-2F8D7;Lo;0;L;43D9;;;;N;;;;; 2F8D8;CJK COMPATIBILITY IDEOGRAPH-2F8D8;Lo;0;L;6717;;;;N;;;;; 2F8D9;CJK COMPATIBILITY IDEOGRAPH-2F8D9;Lo;0;L;671B;;;;N;;;;; 2F8DA;CJK COMPATIBILITY IDEOGRAPH-2F8DA;Lo;0;L;6721;;;;N;;;;; 2F8DB;CJK COMPATIBILITY IDEOGRAPH-2F8DB;Lo;0;L;675E;;;;N;;;;; 2F8DC;CJK COMPATIBILITY IDEOGRAPH-2F8DC;Lo;0;L;6753;;;;N;;;;; 2F8DD;CJK COMPATIBILITY IDEOGRAPH-2F8DD;Lo;0;L;233C3;;;;N;;;;; 2F8DE;CJK COMPATIBILITY IDEOGRAPH-2F8DE;Lo;0;L;3B49;;;;N;;;;; 2F8DF;CJK COMPATIBILITY IDEOGRAPH-2F8DF;Lo;0;L;67FA;;;;N;;;;; 2F8E0;CJK COMPATIBILITY IDEOGRAPH-2F8E0;Lo;0;L;6785;;;;N;;;;; 2F8E1;CJK COMPATIBILITY IDEOGRAPH-2F8E1;Lo;0;L;6852;;;;N;;;;; 2F8E2;CJK COMPATIBILITY IDEOGRAPH-2F8E2;Lo;0;L;6885;;;;N;;;;; 2F8E3;CJK COMPATIBILITY IDEOGRAPH-2F8E3;Lo;0;L;2346D;;;;N;;;;; 2F8E4;CJK COMPATIBILITY IDEOGRAPH-2F8E4;Lo;0;L;688E;;;;N;;;;; 2F8E5;CJK COMPATIBILITY IDEOGRAPH-2F8E5;Lo;0;L;681F;;;;N;;;;; 2F8E6;CJK COMPATIBILITY IDEOGRAPH-2F8E6;Lo;0;L;6914;;;;N;;;;; 2F8E7;CJK COMPATIBILITY IDEOGRAPH-2F8E7;Lo;0;L;3B9D;;;;N;;;;; 2F8E8;CJK COMPATIBILITY IDEOGRAPH-2F8E8;Lo;0;L;6942;;;;N;;;;; 2F8E9;CJK COMPATIBILITY IDEOGRAPH-2F8E9;Lo;0;L;69A3;;;;N;;;;; 2F8EA;CJK COMPATIBILITY IDEOGRAPH-2F8EA;Lo;0;L;69EA;;;;N;;;;; 2F8EB;CJK COMPATIBILITY IDEOGRAPH-2F8EB;Lo;0;L;6AA8;;;;N;;;;; 2F8EC;CJK COMPATIBILITY IDEOGRAPH-2F8EC;Lo;0;L;236A3;;;;N;;;;; 2F8ED;CJK COMPATIBILITY IDEOGRAPH-2F8ED;Lo;0;L;6ADB;;;;N;;;;; 2F8EE;CJK COMPATIBILITY IDEOGRAPH-2F8EE;Lo;0;L;3C18;;;;N;;;;; 2F8EF;CJK COMPATIBILITY IDEOGRAPH-2F8EF;Lo;0;L;6B21;;;;N;;;;; 2F8F0;CJK COMPATIBILITY IDEOGRAPH-2F8F0;Lo;0;L;238A7;;;;N;;;;; 2F8F1;CJK COMPATIBILITY IDEOGRAPH-2F8F1;Lo;0;L;6B54;;;;N;;;;; 2F8F2;CJK COMPATIBILITY IDEOGRAPH-2F8F2;Lo;0;L;3C4E;;;;N;;;;; 2F8F3;CJK COMPATIBILITY IDEOGRAPH-2F8F3;Lo;0;L;6B72;;;;N;;;;; 2F8F4;CJK COMPATIBILITY IDEOGRAPH-2F8F4;Lo;0;L;6B9F;;;;N;;;;; 2F8F5;CJK COMPATIBILITY IDEOGRAPH-2F8F5;Lo;0;L;6BBA;;;;N;;;;; 2F8F6;CJK COMPATIBILITY IDEOGRAPH-2F8F6;Lo;0;L;6BBB;;;;N;;;;; 2F8F7;CJK COMPATIBILITY IDEOGRAPH-2F8F7;Lo;0;L;23A8D;;;;N;;;;; 2F8F8;CJK COMPATIBILITY IDEOGRAPH-2F8F8;Lo;0;L;21D0B;;;;N;;;;; 2F8F9;CJK COMPATIBILITY IDEOGRAPH-2F8F9;Lo;0;L;23AFA;;;;N;;;;; 2F8FA;CJK COMPATIBILITY IDEOGRAPH-2F8FA;Lo;0;L;6C4E;;;;N;;;;; 2F8FB;CJK COMPATIBILITY IDEOGRAPH-2F8FB;Lo;0;L;23CBC;;;;N;;;;; 2F8FC;CJK COMPATIBILITY IDEOGRAPH-2F8FC;Lo;0;L;6CBF;;;;N;;;;; 2F8FD;CJK COMPATIBILITY IDEOGRAPH-2F8FD;Lo;0;L;6CCD;;;;N;;;;; 2F8FE;CJK COMPATIBILITY IDEOGRAPH-2F8FE;Lo;0;L;6C67;;;;N;;;;; 2F8FF;CJK COMPATIBILITY IDEOGRAPH-2F8FF;Lo;0;L;6D16;;;;N;;;;; 2F900;CJK COMPATIBILITY IDEOGRAPH-2F900;Lo;0;L;6D3E;;;;N;;;;; 2F901;CJK COMPATIBILITY IDEOGRAPH-2F901;Lo;0;L;6D77;;;;N;;;;; 2F902;CJK COMPATIBILITY IDEOGRAPH-2F902;Lo;0;L;6D41;;;;N;;;;; 2F903;CJK COMPATIBILITY IDEOGRAPH-2F903;Lo;0;L;6D69;;;;N;;;;; 2F904;CJK COMPATIBILITY IDEOGRAPH-2F904;Lo;0;L;6D78;;;;N;;;;; 2F905;CJK COMPATIBILITY IDEOGRAPH-2F905;Lo;0;L;6D85;;;;N;;;;; 2F906;CJK COMPATIBILITY IDEOGRAPH-2F906;Lo;0;L;23D1E;;;;N;;;;; 2F907;CJK COMPATIBILITY IDEOGRAPH-2F907;Lo;0;L;6D34;;;;N;;;;; 2F908;CJK COMPATIBILITY IDEOGRAPH-2F908;Lo;0;L;6E2F;;;;N;;;;; 2F909;CJK COMPATIBILITY IDEOGRAPH-2F909;Lo;0;L;6E6E;;;;N;;;;; 2F90A;CJK COMPATIBILITY IDEOGRAPH-2F90A;Lo;0;L;3D33;;;;N;;;;; 2F90B;CJK COMPATIBILITY IDEOGRAPH-2F90B;Lo;0;L;6ECB;;;;N;;;;; 2F90C;CJK COMPATIBILITY IDEOGRAPH-2F90C;Lo;0;L;6EC7;;;;N;;;;; 2F90D;CJK COMPATIBILITY IDEOGRAPH-2F90D;Lo;0;L;23ED1;;;;N;;;;; 2F90E;CJK COMPATIBILITY IDEOGRAPH-2F90E;Lo;0;L;6DF9;;;;N;;;;; 2F90F;CJK COMPATIBILITY IDEOGRAPH-2F90F;Lo;0;L;6F6E;;;;N;;;;; 2F910;CJK COMPATIBILITY IDEOGRAPH-2F910;Lo;0;L;23F5E;;;;N;;;;; 2F911;CJK COMPATIBILITY IDEOGRAPH-2F911;Lo;0;L;23F8E;;;;N;;;;; 2F912;CJK COMPATIBILITY IDEOGRAPH-2F912;Lo;0;L;6FC6;;;;N;;;;; 2F913;CJK COMPATIBILITY IDEOGRAPH-2F913;Lo;0;L;7039;;;;N;;;;; 2F914;CJK COMPATIBILITY IDEOGRAPH-2F914;Lo;0;L;701E;;;;N;;;;; 2F915;CJK COMPATIBILITY IDEOGRAPH-2F915;Lo;0;L;701B;;;;N;;;;; 2F916;CJK COMPATIBILITY IDEOGRAPH-2F916;Lo;0;L;3D96;;;;N;;;;; 2F917;CJK COMPATIBILITY IDEOGRAPH-2F917;Lo;0;L;704A;;;;N;;;;; 2F918;CJK COMPATIBILITY IDEOGRAPH-2F918;Lo;0;L;707D;;;;N;;;;; 2F919;CJK COMPATIBILITY IDEOGRAPH-2F919;Lo;0;L;7077;;;;N;;;;; 2F91A;CJK COMPATIBILITY IDEOGRAPH-2F91A;Lo;0;L;70AD;;;;N;;;;; 2F91B;CJK COMPATIBILITY IDEOGRAPH-2F91B;Lo;0;L;20525;;;;N;;;;; 2F91C;CJK COMPATIBILITY IDEOGRAPH-2F91C;Lo;0;L;7145;;;;N;;;;; 2F91D;CJK COMPATIBILITY IDEOGRAPH-2F91D;Lo;0;L;24263;;;;N;;;;; 2F91E;CJK COMPATIBILITY IDEOGRAPH-2F91E;Lo;0;L;719C;;;;N;;;;; 2F91F;CJK COMPATIBILITY IDEOGRAPH-2F91F;Lo;0;L;243AB;;;;N;;;;; 2F920;CJK COMPATIBILITY IDEOGRAPH-2F920;Lo;0;L;7228;;;;N;;;;; 2F921;CJK COMPATIBILITY IDEOGRAPH-2F921;Lo;0;L;7235;;;;N;;;;; 2F922;CJK COMPATIBILITY IDEOGRAPH-2F922;Lo;0;L;7250;;;;N;;;;; 2F923;CJK COMPATIBILITY IDEOGRAPH-2F923;Lo;0;L;24608;;;;N;;;;; 2F924;CJK COMPATIBILITY IDEOGRAPH-2F924;Lo;0;L;7280;;;;N;;;;; 2F925;CJK COMPATIBILITY IDEOGRAPH-2F925;Lo;0;L;7295;;;;N;;;;; 2F926;CJK COMPATIBILITY IDEOGRAPH-2F926;Lo;0;L;24735;;;;N;;;;; 2F927;CJK COMPATIBILITY IDEOGRAPH-2F927;Lo;0;L;24814;;;;N;;;;; 2F928;CJK COMPATIBILITY IDEOGRAPH-2F928;Lo;0;L;737A;;;;N;;;;; 2F929;CJK COMPATIBILITY IDEOGRAPH-2F929;Lo;0;L;738B;;;;N;;;;; 2F92A;CJK COMPATIBILITY IDEOGRAPH-2F92A;Lo;0;L;3EAC;;;;N;;;;; 2F92B;CJK COMPATIBILITY IDEOGRAPH-2F92B;Lo;0;L;73A5;;;;N;;;;; 2F92C;CJK COMPATIBILITY IDEOGRAPH-2F92C;Lo;0;L;3EB8;;;;N;;;;; 2F92D;CJK COMPATIBILITY IDEOGRAPH-2F92D;Lo;0;L;3EB8;;;;N;;;;; 2F92E;CJK COMPATIBILITY IDEOGRAPH-2F92E;Lo;0;L;7447;;;;N;;;;; 2F92F;CJK COMPATIBILITY IDEOGRAPH-2F92F;Lo;0;L;745C;;;;N;;;;; 2F930;CJK COMPATIBILITY IDEOGRAPH-2F930;Lo;0;L;7471;;;;N;;;;; 2F931;CJK COMPATIBILITY IDEOGRAPH-2F931;Lo;0;L;7485;;;;N;;;;; 2F932;CJK COMPATIBILITY IDEOGRAPH-2F932;Lo;0;L;74CA;;;;N;;;;; 2F933;CJK COMPATIBILITY IDEOGRAPH-2F933;Lo;0;L;3F1B;;;;N;;;;; 2F934;CJK COMPATIBILITY IDEOGRAPH-2F934;Lo;0;L;7524;;;;N;;;;; 2F935;CJK COMPATIBILITY IDEOGRAPH-2F935;Lo;0;L;24C36;;;;N;;;;; 2F936;CJK COMPATIBILITY IDEOGRAPH-2F936;Lo;0;L;753E;;;;N;;;;; 2F937;CJK COMPATIBILITY IDEOGRAPH-2F937;Lo;0;L;24C92;;;;N;;;;; 2F938;CJK COMPATIBILITY IDEOGRAPH-2F938;Lo;0;L;7570;;;;N;;;;; 2F939;CJK COMPATIBILITY IDEOGRAPH-2F939;Lo;0;L;2219F;;;;N;;;;; 2F93A;CJK COMPATIBILITY IDEOGRAPH-2F93A;Lo;0;L;7610;;;;N;;;;; 2F93B;CJK COMPATIBILITY IDEOGRAPH-2F93B;Lo;0;L;24FA1;;;;N;;;;; 2F93C;CJK COMPATIBILITY IDEOGRAPH-2F93C;Lo;0;L;24FB8;;;;N;;;;; 2F93D;CJK COMPATIBILITY IDEOGRAPH-2F93D;Lo;0;L;25044;;;;N;;;;; 2F93E;CJK COMPATIBILITY IDEOGRAPH-2F93E;Lo;0;L;3FFC;;;;N;;;;; 2F93F;CJK COMPATIBILITY IDEOGRAPH-2F93F;Lo;0;L;4008;;;;N;;;;; 2F940;CJK COMPATIBILITY IDEOGRAPH-2F940;Lo;0;L;76F4;;;;N;;;;; 2F941;CJK COMPATIBILITY IDEOGRAPH-2F941;Lo;0;L;250F3;;;;N;;;;; 2F942;CJK COMPATIBILITY IDEOGRAPH-2F942;Lo;0;L;250F2;;;;N;;;;; 2F943;CJK COMPATIBILITY IDEOGRAPH-2F943;Lo;0;L;25119;;;;N;;;;; 2F944;CJK COMPATIBILITY IDEOGRAPH-2F944;Lo;0;L;25133;;;;N;;;;; 2F945;CJK COMPATIBILITY IDEOGRAPH-2F945;Lo;0;L;771E;;;;N;;;;; 2F946;CJK COMPATIBILITY IDEOGRAPH-2F946;Lo;0;L;771F;;;;N;;;;; 2F947;CJK COMPATIBILITY IDEOGRAPH-2F947;Lo;0;L;771F;;;;N;;;;; 2F948;CJK COMPATIBILITY IDEOGRAPH-2F948;Lo;0;L;774A;;;;N;;;;; 2F949;CJK COMPATIBILITY IDEOGRAPH-2F949;Lo;0;L;4039;;;;N;;;;; 2F94A;CJK COMPATIBILITY IDEOGRAPH-2F94A;Lo;0;L;778B;;;;N;;;;; 2F94B;CJK COMPATIBILITY IDEOGRAPH-2F94B;Lo;0;L;4046;;;;N;;;;; 2F94C;CJK COMPATIBILITY IDEOGRAPH-2F94C;Lo;0;L;4096;;;;N;;;;; 2F94D;CJK COMPATIBILITY IDEOGRAPH-2F94D;Lo;0;L;2541D;;;;N;;;;; 2F94E;CJK COMPATIBILITY IDEOGRAPH-2F94E;Lo;0;L;784E;;;;N;;;;; 2F94F;CJK COMPATIBILITY IDEOGRAPH-2F94F;Lo;0;L;788C;;;;N;;;;; 2F950;CJK COMPATIBILITY IDEOGRAPH-2F950;Lo;0;L;78CC;;;;N;;;;; 2F951;CJK COMPATIBILITY IDEOGRAPH-2F951;Lo;0;L;40E3;;;;N;;;;; 2F952;CJK COMPATIBILITY IDEOGRAPH-2F952;Lo;0;L;25626;;;;N;;;;; 2F953;CJK COMPATIBILITY IDEOGRAPH-2F953;Lo;0;L;7956;;;;N;;;;; 2F954;CJK COMPATIBILITY IDEOGRAPH-2F954;Lo;0;L;2569A;;;;N;;;;; 2F955;CJK COMPATIBILITY IDEOGRAPH-2F955;Lo;0;L;256C5;;;;N;;;;; 2F956;CJK COMPATIBILITY IDEOGRAPH-2F956;Lo;0;L;798F;;;;N;;;;; 2F957;CJK COMPATIBILITY IDEOGRAPH-2F957;Lo;0;L;79EB;;;;N;;;;; 2F958;CJK COMPATIBILITY IDEOGRAPH-2F958;Lo;0;L;412F;;;;N;;;;; 2F959;CJK COMPATIBILITY IDEOGRAPH-2F959;Lo;0;L;7A40;;;;N;;;;; 2F95A;CJK COMPATIBILITY IDEOGRAPH-2F95A;Lo;0;L;7A4A;;;;N;;;;; 2F95B;CJK COMPATIBILITY IDEOGRAPH-2F95B;Lo;0;L;7A4F;;;;N;;;;; 2F95C;CJK COMPATIBILITY IDEOGRAPH-2F95C;Lo;0;L;2597C;;;;N;;;;; 2F95D;CJK COMPATIBILITY IDEOGRAPH-2F95D;Lo;0;L;25AA7;;;;N;;;;; 2F95E;CJK COMPATIBILITY IDEOGRAPH-2F95E;Lo;0;L;25AA7;;;;N;;;;; 2F95F;CJK COMPATIBILITY IDEOGRAPH-2F95F;Lo;0;L;7AEE;;;;N;;;;; 2F960;CJK COMPATIBILITY IDEOGRAPH-2F960;Lo;0;L;4202;;;;N;;;;; 2F961;CJK COMPATIBILITY IDEOGRAPH-2F961;Lo;0;L;25BAB;;;;N;;;;; 2F962;CJK COMPATIBILITY IDEOGRAPH-2F962;Lo;0;L;7BC6;;;;N;;;;; 2F963;CJK COMPATIBILITY IDEOGRAPH-2F963;Lo;0;L;7BC9;;;;N;;;;; 2F964;CJK COMPATIBILITY IDEOGRAPH-2F964;Lo;0;L;4227;;;;N;;;;; 2F965;CJK COMPATIBILITY IDEOGRAPH-2F965;Lo;0;L;25C80;;;;N;;;;; 2F966;CJK COMPATIBILITY IDEOGRAPH-2F966;Lo;0;L;7CD2;;;;N;;;;; 2F967;CJK COMPATIBILITY IDEOGRAPH-2F967;Lo;0;L;42A0;;;;N;;;;; 2F968;CJK COMPATIBILITY IDEOGRAPH-2F968;Lo;0;L;7CE8;;;;N;;;;; 2F969;CJK COMPATIBILITY IDEOGRAPH-2F969;Lo;0;L;7CE3;;;;N;;;;; 2F96A;CJK COMPATIBILITY IDEOGRAPH-2F96A;Lo;0;L;7D00;;;;N;;;;; 2F96B;CJK COMPATIBILITY IDEOGRAPH-2F96B;Lo;0;L;25F86;;;;N;;;;; 2F96C;CJK COMPATIBILITY IDEOGRAPH-2F96C;Lo;0;L;7D63;;;;N;;;;; 2F96D;CJK COMPATIBILITY IDEOGRAPH-2F96D;Lo;0;L;4301;;;;N;;;;; 2F96E;CJK COMPATIBILITY IDEOGRAPH-2F96E;Lo;0;L;7DC7;;;;N;;;;; 2F96F;CJK COMPATIBILITY IDEOGRAPH-2F96F;Lo;0;L;7E02;;;;N;;;;; 2F970;CJK COMPATIBILITY IDEOGRAPH-2F970;Lo;0;L;7E45;;;;N;;;;; 2F971;CJK COMPATIBILITY IDEOGRAPH-2F971;Lo;0;L;4334;;;;N;;;;; 2F972;CJK COMPATIBILITY IDEOGRAPH-2F972;Lo;0;L;26228;;;;N;;;;; 2F973;CJK COMPATIBILITY IDEOGRAPH-2F973;Lo;0;L;26247;;;;N;;;;; 2F974;CJK COMPATIBILITY IDEOGRAPH-2F974;Lo;0;L;4359;;;;N;;;;; 2F975;CJK COMPATIBILITY IDEOGRAPH-2F975;Lo;0;L;262D9;;;;N;;;;; 2F976;CJK COMPATIBILITY IDEOGRAPH-2F976;Lo;0;L;7F7A;;;;N;;;;; 2F977;CJK COMPATIBILITY IDEOGRAPH-2F977;Lo;0;L;2633E;;;;N;;;;; 2F978;CJK COMPATIBILITY IDEOGRAPH-2F978;Lo;0;L;7F95;;;;N;;;;; 2F979;CJK COMPATIBILITY IDEOGRAPH-2F979;Lo;0;L;7FFA;;;;N;;;;; 2F97A;CJK COMPATIBILITY IDEOGRAPH-2F97A;Lo;0;L;8005;;;;N;;;;; 2F97B;CJK COMPATIBILITY IDEOGRAPH-2F97B;Lo;0;L;264DA;;;;N;;;;; 2F97C;CJK COMPATIBILITY IDEOGRAPH-2F97C;Lo;0;L;26523;;;;N;;;;; 2F97D;CJK COMPATIBILITY IDEOGRAPH-2F97D;Lo;0;L;8060;;;;N;;;;; 2F97E;CJK COMPATIBILITY IDEOGRAPH-2F97E;Lo;0;L;265A8;;;;N;;;;; 2F97F;CJK COMPATIBILITY IDEOGRAPH-2F97F;Lo;0;L;8070;;;;N;;;;; 2F980;CJK COMPATIBILITY IDEOGRAPH-2F980;Lo;0;L;2335F;;;;N;;;;; 2F981;CJK COMPATIBILITY IDEOGRAPH-2F981;Lo;0;L;43D5;;;;N;;;;; 2F982;CJK COMPATIBILITY IDEOGRAPH-2F982;Lo;0;L;80B2;;;;N;;;;; 2F983;CJK COMPATIBILITY IDEOGRAPH-2F983;Lo;0;L;8103;;;;N;;;;; 2F984;CJK COMPATIBILITY IDEOGRAPH-2F984;Lo;0;L;440B;;;;N;;;;; 2F985;CJK COMPATIBILITY IDEOGRAPH-2F985;Lo;0;L;813E;;;;N;;;;; 2F986;CJK COMPATIBILITY IDEOGRAPH-2F986;Lo;0;L;5AB5;;;;N;;;;; 2F987;CJK COMPATIBILITY IDEOGRAPH-2F987;Lo;0;L;267A7;;;;N;;;;; 2F988;CJK COMPATIBILITY IDEOGRAPH-2F988;Lo;0;L;267B5;;;;N;;;;; 2F989;CJK COMPATIBILITY IDEOGRAPH-2F989;Lo;0;L;23393;;;;N;;;;; 2F98A;CJK COMPATIBILITY IDEOGRAPH-2F98A;Lo;0;L;2339C;;;;N;;;;; 2F98B;CJK COMPATIBILITY IDEOGRAPH-2F98B;Lo;0;L;8201;;;;N;;;;; 2F98C;CJK COMPATIBILITY IDEOGRAPH-2F98C;Lo;0;L;8204;;;;N;;;;; 2F98D;CJK COMPATIBILITY IDEOGRAPH-2F98D;Lo;0;L;8F9E;;;;N;;;;; 2F98E;CJK COMPATIBILITY IDEOGRAPH-2F98E;Lo;0;L;446B;;;;N;;;;; 2F98F;CJK COMPATIBILITY IDEOGRAPH-2F98F;Lo;0;L;8291;;;;N;;;;; 2F990;CJK COMPATIBILITY IDEOGRAPH-2F990;Lo;0;L;828B;;;;N;;;;; 2F991;CJK COMPATIBILITY IDEOGRAPH-2F991;Lo;0;L;829D;;;;N;;;;; 2F992;CJK COMPATIBILITY IDEOGRAPH-2F992;Lo;0;L;52B3;;;;N;;;;; 2F993;CJK COMPATIBILITY IDEOGRAPH-2F993;Lo;0;L;82B1;;;;N;;;;; 2F994;CJK COMPATIBILITY IDEOGRAPH-2F994;Lo;0;L;82B3;;;;N;;;;; 2F995;CJK COMPATIBILITY IDEOGRAPH-2F995;Lo;0;L;82BD;;;;N;;;;; 2F996;CJK COMPATIBILITY IDEOGRAPH-2F996;Lo;0;L;82E6;;;;N;;;;; 2F997;CJK COMPATIBILITY IDEOGRAPH-2F997;Lo;0;L;26B3C;;;;N;;;;; 2F998;CJK COMPATIBILITY IDEOGRAPH-2F998;Lo;0;L;82E5;;;;N;;;;; 2F999;CJK COMPATIBILITY IDEOGRAPH-2F999;Lo;0;L;831D;;;;N;;;;; 2F99A;CJK COMPATIBILITY IDEOGRAPH-2F99A;Lo;0;L;8363;;;;N;;;;; 2F99B;CJK COMPATIBILITY IDEOGRAPH-2F99B;Lo;0;L;83AD;;;;N;;;;; 2F99C;CJK COMPATIBILITY IDEOGRAPH-2F99C;Lo;0;L;8323;;;;N;;;;; 2F99D;CJK COMPATIBILITY IDEOGRAPH-2F99D;Lo;0;L;83BD;;;;N;;;;; 2F99E;CJK COMPATIBILITY IDEOGRAPH-2F99E;Lo;0;L;83E7;;;;N;;;;; 2F99F;CJK COMPATIBILITY IDEOGRAPH-2F99F;Lo;0;L;8457;;;;N;;;;; 2F9A0;CJK COMPATIBILITY IDEOGRAPH-2F9A0;Lo;0;L;8353;;;;N;;;;; 2F9A1;CJK COMPATIBILITY IDEOGRAPH-2F9A1;Lo;0;L;83CA;;;;N;;;;; 2F9A2;CJK COMPATIBILITY IDEOGRAPH-2F9A2;Lo;0;L;83CC;;;;N;;;;; 2F9A3;CJK COMPATIBILITY IDEOGRAPH-2F9A3;Lo;0;L;83DC;;;;N;;;;; 2F9A4;CJK COMPATIBILITY IDEOGRAPH-2F9A4;Lo;0;L;26C36;;;;N;;;;; 2F9A5;CJK COMPATIBILITY IDEOGRAPH-2F9A5;Lo;0;L;26D6B;;;;N;;;;; 2F9A6;CJK COMPATIBILITY IDEOGRAPH-2F9A6;Lo;0;L;26CD5;;;;N;;;;; 2F9A7;CJK COMPATIBILITY IDEOGRAPH-2F9A7;Lo;0;L;452B;;;;N;;;;; 2F9A8;CJK COMPATIBILITY IDEOGRAPH-2F9A8;Lo;0;L;84F1;;;;N;;;;; 2F9A9;CJK COMPATIBILITY IDEOGRAPH-2F9A9;Lo;0;L;84F3;;;;N;;;;; 2F9AA;CJK COMPATIBILITY IDEOGRAPH-2F9AA;Lo;0;L;8516;;;;N;;;;; 2F9AB;CJK COMPATIBILITY IDEOGRAPH-2F9AB;Lo;0;L;273CA;;;;N;;;;; 2F9AC;CJK COMPATIBILITY IDEOGRAPH-2F9AC;Lo;0;L;8564;;;;N;;;;; 2F9AD;CJK COMPATIBILITY IDEOGRAPH-2F9AD;Lo;0;L;26F2C;;;;N;;;;; 2F9AE;CJK COMPATIBILITY IDEOGRAPH-2F9AE;Lo;0;L;455D;;;;N;;;;; 2F9AF;CJK COMPATIBILITY IDEOGRAPH-2F9AF;Lo;0;L;4561;;;;N;;;;; 2F9B0;CJK COMPATIBILITY IDEOGRAPH-2F9B0;Lo;0;L;26FB1;;;;N;;;;; 2F9B1;CJK COMPATIBILITY IDEOGRAPH-2F9B1;Lo;0;L;270D2;;;;N;;;;; 2F9B2;CJK COMPATIBILITY IDEOGRAPH-2F9B2;Lo;0;L;456B;;;;N;;;;; 2F9B3;CJK COMPATIBILITY IDEOGRAPH-2F9B3;Lo;0;L;8650;;;;N;;;;; 2F9B4;CJK COMPATIBILITY IDEOGRAPH-2F9B4;Lo;0;L;865C;;;;N;;;;; 2F9B5;CJK COMPATIBILITY IDEOGRAPH-2F9B5;Lo;0;L;8667;;;;N;;;;; 2F9B6;CJK COMPATIBILITY IDEOGRAPH-2F9B6;Lo;0;L;8669;;;;N;;;;; 2F9B7;CJK COMPATIBILITY IDEOGRAPH-2F9B7;Lo;0;L;86A9;;;;N;;;;; 2F9B8;CJK COMPATIBILITY IDEOGRAPH-2F9B8;Lo;0;L;8688;;;;N;;;;; 2F9B9;CJK COMPATIBILITY IDEOGRAPH-2F9B9;Lo;0;L;870E;;;;N;;;;; 2F9BA;CJK COMPATIBILITY IDEOGRAPH-2F9BA;Lo;0;L;86E2;;;;N;;;;; 2F9BB;CJK COMPATIBILITY IDEOGRAPH-2F9BB;Lo;0;L;8779;;;;N;;;;; 2F9BC;CJK COMPATIBILITY IDEOGRAPH-2F9BC;Lo;0;L;8728;;;;N;;;;; 2F9BD;CJK COMPATIBILITY IDEOGRAPH-2F9BD;Lo;0;L;876B;;;;N;;;;; 2F9BE;CJK COMPATIBILITY IDEOGRAPH-2F9BE;Lo;0;L;8786;;;;N;;;;; 2F9BF;CJK COMPATIBILITY IDEOGRAPH-2F9BF;Lo;0;L;45D7;;;;N;;;;; 2F9C0;CJK COMPATIBILITY IDEOGRAPH-2F9C0;Lo;0;L;87E1;;;;N;;;;; 2F9C1;CJK COMPATIBILITY IDEOGRAPH-2F9C1;Lo;0;L;8801;;;;N;;;;; 2F9C2;CJK COMPATIBILITY IDEOGRAPH-2F9C2;Lo;0;L;45F9;;;;N;;;;; 2F9C3;CJK COMPATIBILITY IDEOGRAPH-2F9C3;Lo;0;L;8860;;;;N;;;;; 2F9C4;CJK COMPATIBILITY IDEOGRAPH-2F9C4;Lo;0;L;8863;;;;N;;;;; 2F9C5;CJK COMPATIBILITY IDEOGRAPH-2F9C5;Lo;0;L;27667;;;;N;;;;; 2F9C6;CJK COMPATIBILITY IDEOGRAPH-2F9C6;Lo;0;L;88D7;;;;N;;;;; 2F9C7;CJK COMPATIBILITY IDEOGRAPH-2F9C7;Lo;0;L;88DE;;;;N;;;;; 2F9C8;CJK COMPATIBILITY IDEOGRAPH-2F9C8;Lo;0;L;4635;;;;N;;;;; 2F9C9;CJK COMPATIBILITY IDEOGRAPH-2F9C9;Lo;0;L;88FA;;;;N;;;;; 2F9CA;CJK COMPATIBILITY IDEOGRAPH-2F9CA;Lo;0;L;34BB;;;;N;;;;; 2F9CB;CJK COMPATIBILITY IDEOGRAPH-2F9CB;Lo;0;L;278AE;;;;N;;;;; 2F9CC;CJK COMPATIBILITY IDEOGRAPH-2F9CC;Lo;0;L;27966;;;;N;;;;; 2F9CD;CJK COMPATIBILITY IDEOGRAPH-2F9CD;Lo;0;L;46BE;;;;N;;;;; 2F9CE;CJK COMPATIBILITY IDEOGRAPH-2F9CE;Lo;0;L;46C7;;;;N;;;;; 2F9CF;CJK COMPATIBILITY IDEOGRAPH-2F9CF;Lo;0;L;8AA0;;;;N;;;;; 2F9D0;CJK COMPATIBILITY IDEOGRAPH-2F9D0;Lo;0;L;8AED;;;;N;;;;; 2F9D1;CJK COMPATIBILITY IDEOGRAPH-2F9D1;Lo;0;L;8B8A;;;;N;;;;; 2F9D2;CJK COMPATIBILITY IDEOGRAPH-2F9D2;Lo;0;L;8C55;;;;N;;;;; 2F9D3;CJK COMPATIBILITY IDEOGRAPH-2F9D3;Lo;0;L;27CA8;;;;N;;;;; 2F9D4;CJK COMPATIBILITY IDEOGRAPH-2F9D4;Lo;0;L;8CAB;;;;N;;;;; 2F9D5;CJK COMPATIBILITY IDEOGRAPH-2F9D5;Lo;0;L;8CC1;;;;N;;;;; 2F9D6;CJK COMPATIBILITY IDEOGRAPH-2F9D6;Lo;0;L;8D1B;;;;N;;;;; 2F9D7;CJK COMPATIBILITY IDEOGRAPH-2F9D7;Lo;0;L;8D77;;;;N;;;;; 2F9D8;CJK COMPATIBILITY IDEOGRAPH-2F9D8;Lo;0;L;27F2F;;;;N;;;;; 2F9D9;CJK COMPATIBILITY IDEOGRAPH-2F9D9;Lo;0;L;20804;;;;N;;;;; 2F9DA;CJK COMPATIBILITY IDEOGRAPH-2F9DA;Lo;0;L;8DCB;;;;N;;;;; 2F9DB;CJK COMPATIBILITY IDEOGRAPH-2F9DB;Lo;0;L;8DBC;;;;N;;;;; 2F9DC;CJK COMPATIBILITY IDEOGRAPH-2F9DC;Lo;0;L;8DF0;;;;N;;;;; 2F9DD;CJK COMPATIBILITY IDEOGRAPH-2F9DD;Lo;0;L;208DE;;;;N;;;;; 2F9DE;CJK COMPATIBILITY IDEOGRAPH-2F9DE;Lo;0;L;8ED4;;;;N;;;;; 2F9DF;CJK COMPATIBILITY IDEOGRAPH-2F9DF;Lo;0;L;8F38;;;;N;;;;; 2F9E0;CJK COMPATIBILITY IDEOGRAPH-2F9E0;Lo;0;L;285D2;;;;N;;;;; 2F9E1;CJK COMPATIBILITY IDEOGRAPH-2F9E1;Lo;0;L;285ED;;;;N;;;;; 2F9E2;CJK COMPATIBILITY IDEOGRAPH-2F9E2;Lo;0;L;9094;;;;N;;;;; 2F9E3;CJK COMPATIBILITY IDEOGRAPH-2F9E3;Lo;0;L;90F1;;;;N;;;;; 2F9E4;CJK COMPATIBILITY IDEOGRAPH-2F9E4;Lo;0;L;9111;;;;N;;;;; 2F9E5;CJK COMPATIBILITY IDEOGRAPH-2F9E5;Lo;0;L;2872E;;;;N;;;;; 2F9E6;CJK COMPATIBILITY IDEOGRAPH-2F9E6;Lo;0;L;911B;;;;N;;;;; 2F9E7;CJK COMPATIBILITY IDEOGRAPH-2F9E7;Lo;0;L;9238;;;;N;;;;; 2F9E8;CJK COMPATIBILITY IDEOGRAPH-2F9E8;Lo;0;L;92D7;;;;N;;;;; 2F9E9;CJK COMPATIBILITY IDEOGRAPH-2F9E9;Lo;0;L;92D8;;;;N;;;;; 2F9EA;CJK COMPATIBILITY IDEOGRAPH-2F9EA;Lo;0;L;927C;;;;N;;;;; 2F9EB;CJK COMPATIBILITY IDEOGRAPH-2F9EB;Lo;0;L;93F9;;;;N;;;;; 2F9EC;CJK COMPATIBILITY IDEOGRAPH-2F9EC;Lo;0;L;9415;;;;N;;;;; 2F9ED;CJK COMPATIBILITY IDEOGRAPH-2F9ED;Lo;0;L;28BFA;;;;N;;;;; 2F9EE;CJK COMPATIBILITY IDEOGRAPH-2F9EE;Lo;0;L;958B;;;;N;;;;; 2F9EF;CJK COMPATIBILITY IDEOGRAPH-2F9EF;Lo;0;L;4995;;;;N;;;;; 2F9F0;CJK COMPATIBILITY IDEOGRAPH-2F9F0;Lo;0;L;95B7;;;;N;;;;; 2F9F1;CJK COMPATIBILITY IDEOGRAPH-2F9F1;Lo;0;L;28D77;;;;N;;;;; 2F9F2;CJK COMPATIBILITY IDEOGRAPH-2F9F2;Lo;0;L;49E6;;;;N;;;;; 2F9F3;CJK COMPATIBILITY IDEOGRAPH-2F9F3;Lo;0;L;96C3;;;;N;;;;; 2F9F4;CJK COMPATIBILITY IDEOGRAPH-2F9F4;Lo;0;L;5DB2;;;;N;;;;; 2F9F5;CJK COMPATIBILITY IDEOGRAPH-2F9F5;Lo;0;L;9723;;;;N;;;;; 2F9F6;CJK COMPATIBILITY IDEOGRAPH-2F9F6;Lo;0;L;29145;;;;N;;;;; 2F9F7;CJK COMPATIBILITY IDEOGRAPH-2F9F7;Lo;0;L;2921A;;;;N;;;;; 2F9F8;CJK COMPATIBILITY IDEOGRAPH-2F9F8;Lo;0;L;4A6E;;;;N;;;;; 2F9F9;CJK COMPATIBILITY IDEOGRAPH-2F9F9;Lo;0;L;4A76;;;;N;;;;; 2F9FA;CJK COMPATIBILITY IDEOGRAPH-2F9FA;Lo;0;L;97E0;;;;N;;;;; 2F9FB;CJK COMPATIBILITY IDEOGRAPH-2F9FB;Lo;0;L;2940A;;;;N;;;;; 2F9FC;CJK COMPATIBILITY IDEOGRAPH-2F9FC;Lo;0;L;4AB2;;;;N;;;;; 2F9FD;CJK COMPATIBILITY IDEOGRAPH-2F9FD;Lo;0;L;29496;;;;N;;;;; 2F9FE;CJK COMPATIBILITY IDEOGRAPH-2F9FE;Lo;0;L;980B;;;;N;;;;; 2F9FF;CJK COMPATIBILITY IDEOGRAPH-2F9FF;Lo;0;L;980B;;;;N;;;;; 2FA00;CJK COMPATIBILITY IDEOGRAPH-2FA00;Lo;0;L;9829;;;;N;;;;; 2FA01;CJK COMPATIBILITY IDEOGRAPH-2FA01;Lo;0;L;295B6;;;;N;;;;; 2FA02;CJK COMPATIBILITY IDEOGRAPH-2FA02;Lo;0;L;98E2;;;;N;;;;; 2FA03;CJK COMPATIBILITY IDEOGRAPH-2FA03;Lo;0;L;4B33;;;;N;;;;; 2FA04;CJK COMPATIBILITY IDEOGRAPH-2FA04;Lo;0;L;9929;;;;N;;;;; 2FA05;CJK COMPATIBILITY IDEOGRAPH-2FA05;Lo;0;L;99A7;;;;N;;;;; 2FA06;CJK COMPATIBILITY IDEOGRAPH-2FA06;Lo;0;L;99C2;;;;N;;;;; 2FA07;CJK COMPATIBILITY IDEOGRAPH-2FA07;Lo;0;L;99FE;;;;N;;;;; 2FA08;CJK COMPATIBILITY IDEOGRAPH-2FA08;Lo;0;L;4BCE;;;;N;;;;; 2FA09;CJK COMPATIBILITY IDEOGRAPH-2FA09;Lo;0;L;29B30;;;;N;;;;; 2FA0A;CJK COMPATIBILITY IDEOGRAPH-2FA0A;Lo;0;L;9B12;;;;N;;;;; 2FA0B;CJK COMPATIBILITY IDEOGRAPH-2FA0B;Lo;0;L;9C40;;;;N;;;;; 2FA0C;CJK COMPATIBILITY IDEOGRAPH-2FA0C;Lo;0;L;9CFD;;;;N;;;;; 2FA0D;CJK COMPATIBILITY IDEOGRAPH-2FA0D;Lo;0;L;4CCE;;;;N;;;;; 2FA0E;CJK COMPATIBILITY IDEOGRAPH-2FA0E;Lo;0;L;4CED;;;;N;;;;; 2FA0F;CJK COMPATIBILITY IDEOGRAPH-2FA0F;Lo;0;L;9D67;;;;N;;;;; 2FA10;CJK COMPATIBILITY IDEOGRAPH-2FA10;Lo;0;L;2A0CE;;;;N;;;;; 2FA11;CJK COMPATIBILITY IDEOGRAPH-2FA11;Lo;0;L;4CF8;;;;N;;;;; 2FA12;CJK COMPATIBILITY IDEOGRAPH-2FA12;Lo;0;L;2A105;;;;N;;;;; 2FA13;CJK COMPATIBILITY IDEOGRAPH-2FA13;Lo;0;L;2A20E;;;;N;;;;; 2FA14;CJK COMPATIBILITY IDEOGRAPH-2FA14;Lo;0;L;2A291;;;;N;;;;; 2FA15;CJK COMPATIBILITY IDEOGRAPH-2FA15;Lo;0;L;9EBB;;;;N;;;;; 2FA16;CJK COMPATIBILITY IDEOGRAPH-2FA16;Lo;0;L;4D56;;;;N;;;;; 2FA17;CJK COMPATIBILITY IDEOGRAPH-2FA17;Lo;0;L;9EF9;;;;N;;;;; 2FA18;CJK COMPATIBILITY IDEOGRAPH-2FA18;Lo;0;L;9EFE;;;;N;;;;; 2FA19;CJK COMPATIBILITY IDEOGRAPH-2FA19;Lo;0;L;9F05;;;;N;;;;; 2FA1A;CJK COMPATIBILITY IDEOGRAPH-2FA1A;Lo;0;L;9F0F;;;;N;;;;; 2FA1B;CJK COMPATIBILITY IDEOGRAPH-2FA1B;Lo;0;L;9F16;;;;N;;;;; 2FA1C;CJK COMPATIBILITY IDEOGRAPH-2FA1C;Lo;0;L;9F3B;;;;N;;;;; 2FA1D;CJK COMPATIBILITY IDEOGRAPH-2FA1D;Lo;0;L;2A600;;;;N;;;;; E0001;LANGUAGE TAG;Cf;0;BN;;;;;N;;;;; E0020;TAG SPACE;Cf;0;BN;;;;;N;;;;; E0021;TAG EXCLAMATION MARK;Cf;0;BN;;;;;N;;;;; E0022;TAG QUOTATION MARK;Cf;0;BN;;;;;N;;;;; E0023;TAG NUMBER SIGN;Cf;0;BN;;;;;N;;;;; E0024;TAG DOLLAR SIGN;Cf;0;BN;;;;;N;;;;; E0025;TAG PERCENT SIGN;Cf;0;BN;;;;;N;;;;; E0026;TAG AMPERSAND;Cf;0;BN;;;;;N;;;;; E0027;TAG APOSTROPHE;Cf;0;BN;;;;;N;;;;; E0028;TAG LEFT PARENTHESIS;Cf;0;BN;;;;;N;;;;; E0029;TAG RIGHT PARENTHESIS;Cf;0;BN;;;;;N;;;;; E002A;TAG ASTERISK;Cf;0;BN;;;;;N;;;;; E002B;TAG PLUS SIGN;Cf;0;BN;;;;;N;;;;; E002C;TAG COMMA;Cf;0;BN;;;;;N;;;;; E002D;TAG HYPHEN-MINUS;Cf;0;BN;;;;;N;;;;; E002E;TAG FULL STOP;Cf;0;BN;;;;;N;;;;; E002F;TAG SOLIDUS;Cf;0;BN;;;;;N;;;;; E0030;TAG DIGIT ZERO;Cf;0;BN;;;;;N;;;;; E0031;TAG DIGIT ONE;Cf;0;BN;;;;;N;;;;; E0032;TAG DIGIT TWO;Cf;0;BN;;;;;N;;;;; E0033;TAG DIGIT THREE;Cf;0;BN;;;;;N;;;;; E0034;TAG DIGIT FOUR;Cf;0;BN;;;;;N;;;;; E0035;TAG DIGIT FIVE;Cf;0;BN;;;;;N;;;;; E0036;TAG DIGIT SIX;Cf;0;BN;;;;;N;;;;; E0037;TAG DIGIT SEVEN;Cf;0;BN;;;;;N;;;;; E0038;TAG DIGIT EIGHT;Cf;0;BN;;;;;N;;;;; E0039;TAG DIGIT NINE;Cf;0;BN;;;;;N;;;;; E003A;TAG COLON;Cf;0;BN;;;;;N;;;;; E003B;TAG SEMICOLON;Cf;0;BN;;;;;N;;;;; E003C;TAG LESS-THAN SIGN;Cf;0;BN;;;;;N;;;;; E003D;TAG EQUALS SIGN;Cf;0;BN;;;;;N;;;;; E003E;TAG GREATER-THAN SIGN;Cf;0;BN;;;;;N;;;;; E003F;TAG QUESTION MARK;Cf;0;BN;;;;;N;;;;; E0040;TAG COMMERCIAL AT;Cf;0;BN;;;;;N;;;;; E0041;TAG LATIN CAPITAL LETTER A;Cf;0;BN;;;;;N;;;;; E0042;TAG LATIN CAPITAL LETTER B;Cf;0;BN;;;;;N;;;;; E0043;TAG LATIN CAPITAL LETTER C;Cf;0;BN;;;;;N;;;;; E0044;TAG LATIN CAPITAL LETTER D;Cf;0;BN;;;;;N;;;;; E0045;TAG LATIN CAPITAL LETTER E;Cf;0;BN;;;;;N;;;;; E0046;TAG LATIN CAPITAL LETTER F;Cf;0;BN;;;;;N;;;;; E0047;TAG LATIN CAPITAL LETTER G;Cf;0;BN;;;;;N;;;;; E0048;TAG LATIN CAPITAL LETTER H;Cf;0;BN;;;;;N;;;;; E0049;TAG LATIN CAPITAL LETTER I;Cf;0;BN;;;;;N;;;;; E004A;TAG LATIN CAPITAL LETTER J;Cf;0;BN;;;;;N;;;;; E004B;TAG LATIN CAPITAL LETTER K;Cf;0;BN;;;;;N;;;;; E004C;TAG LATIN CAPITAL LETTER L;Cf;0;BN;;;;;N;;;;; E004D;TAG LATIN CAPITAL LETTER M;Cf;0;BN;;;;;N;;;;; E004E;TAG LATIN CAPITAL LETTER N;Cf;0;BN;;;;;N;;;;; E004F;TAG LATIN CAPITAL LETTER O;Cf;0;BN;;;;;N;;;;; E0050;TAG LATIN CAPITAL LETTER P;Cf;0;BN;;;;;N;;;;; E0051;TAG LATIN CAPITAL LETTER Q;Cf;0;BN;;;;;N;;;;; E0052;TAG LATIN CAPITAL LETTER R;Cf;0;BN;;;;;N;;;;; E0053;TAG LATIN CAPITAL LETTER S;Cf;0;BN;;;;;N;;;;; E0054;TAG LATIN CAPITAL LETTER T;Cf;0;BN;;;;;N;;;;; E0055;TAG LATIN CAPITAL LETTER U;Cf;0;BN;;;;;N;;;;; E0056;TAG LATIN CAPITAL LETTER V;Cf;0;BN;;;;;N;;;;; E0057;TAG LATIN CAPITAL LETTER W;Cf;0;BN;;;;;N;;;;; E0058;TAG LATIN CAPITAL LETTER X;Cf;0;BN;;;;;N;;;;; E0059;TAG LATIN CAPITAL LETTER Y;Cf;0;BN;;;;;N;;;;; E005A;TAG LATIN CAPITAL LETTER Z;Cf;0;BN;;;;;N;;;;; E005B;TAG LEFT SQUARE BRACKET;Cf;0;BN;;;;;N;;;;; E005C;TAG REVERSE SOLIDUS;Cf;0;BN;;;;;N;;;;; E005D;TAG RIGHT SQUARE BRACKET;Cf;0;BN;;;;;N;;;;; E005E;TAG CIRCUMFLEX ACCENT;Cf;0;BN;;;;;N;;;;; E005F;TAG LOW LINE;Cf;0;BN;;;;;N;;;;; E0060;TAG GRAVE ACCENT;Cf;0;BN;;;;;N;;;;; E0061;TAG LATIN SMALL LETTER A;Cf;0;BN;;;;;N;;;;; E0062;TAG LATIN SMALL LETTER B;Cf;0;BN;;;;;N;;;;; E0063;TAG LATIN SMALL LETTER C;Cf;0;BN;;;;;N;;;;; E0064;TAG LATIN SMALL LETTER D;Cf;0;BN;;;;;N;;;;; E0065;TAG LATIN SMALL LETTER E;Cf;0;BN;;;;;N;;;;; E0066;TAG LATIN SMALL LETTER F;Cf;0;BN;;;;;N;;;;; E0067;TAG LATIN SMALL LETTER G;Cf;0;BN;;;;;N;;;;; E0068;TAG LATIN SMALL LETTER H;Cf;0;BN;;;;;N;;;;; E0069;TAG LATIN SMALL LETTER I;Cf;0;BN;;;;;N;;;;; E006A;TAG LATIN SMALL LETTER J;Cf;0;BN;;;;;N;;;;; E006B;TAG LATIN SMALL LETTER K;Cf;0;BN;;;;;N;;;;; E006C;TAG LATIN SMALL LETTER L;Cf;0;BN;;;;;N;;;;; E006D;TAG LATIN SMALL LETTER M;Cf;0;BN;;;;;N;;;;; E006E;TAG LATIN SMALL LETTER N;Cf;0;BN;;;;;N;;;;; E006F;TAG LATIN SMALL LETTER O;Cf;0;BN;;;;;N;;;;; E0070;TAG LATIN SMALL LETTER P;Cf;0;BN;;;;;N;;;;; E0071;TAG LATIN SMALL LETTER Q;Cf;0;BN;;;;;N;;;;; E0072;TAG LATIN SMALL LETTER R;Cf;0;BN;;;;;N;;;;; E0073;TAG LATIN SMALL LETTER S;Cf;0;BN;;;;;N;;;;; E0074;TAG LATIN SMALL LETTER T;Cf;0;BN;;;;;N;;;;; E0075;TAG LATIN SMALL LETTER U;Cf;0;BN;;;;;N;;;;; E0076;TAG LATIN SMALL LETTER V;Cf;0;BN;;;;;N;;;;; E0077;TAG LATIN SMALL LETTER W;Cf;0;BN;;;;;N;;;;; E0078;TAG LATIN SMALL LETTER X;Cf;0;BN;;;;;N;;;;; E0079;TAG LATIN SMALL LETTER Y;Cf;0;BN;;;;;N;;;;; E007A;TAG LATIN SMALL LETTER Z;Cf;0;BN;;;;;N;;;;; E007B;TAG LEFT CURLY BRACKET;Cf;0;BN;;;;;N;;;;; E007C;TAG VERTICAL LINE;Cf;0;BN;;;;;N;;;;; E007D;TAG RIGHT CURLY BRACKET;Cf;0;BN;;;;;N;;;;; E007E;TAG TILDE;Cf;0;BN;;;;;N;;;;; E007F;CANCEL TAG;Cf;0;BN;;;;;N;;;;; E0100;VARIATION SELECTOR-17;Mn;0;NSM;;;;;N;;;;; E0101;VARIATION SELECTOR-18;Mn;0;NSM;;;;;N;;;;; E0102;VARIATION SELECTOR-19;Mn;0;NSM;;;;;N;;;;; E0103;VARIATION SELECTOR-20;Mn;0;NSM;;;;;N;;;;; E0104;VARIATION SELECTOR-21;Mn;0;NSM;;;;;N;;;;; E0105;VARIATION SELECTOR-22;Mn;0;NSM;;;;;N;;;;; E0106;VARIATION SELECTOR-23;Mn;0;NSM;;;;;N;;;;; E0107;VARIATION SELECTOR-24;Mn;0;NSM;;;;;N;;;;; E0108;VARIATION SELECTOR-25;Mn;0;NSM;;;;;N;;;;; E0109;VARIATION SELECTOR-26;Mn;0;NSM;;;;;N;;;;; E010A;VARIATION SELECTOR-27;Mn;0;NSM;;;;;N;;;;; E010B;VARIATION SELECTOR-28;Mn;0;NSM;;;;;N;;;;; E010C;VARIATION SELECTOR-29;Mn;0;NSM;;;;;N;;;;; E010D;VARIATION SELECTOR-30;Mn;0;NSM;;;;;N;;;;; E010E;VARIATION SELECTOR-31;Mn;0;NSM;;;;;N;;;;; E010F;VARIATION SELECTOR-32;Mn;0;NSM;;;;;N;;;;; E0110;VARIATION SELECTOR-33;Mn;0;NSM;;;;;N;;;;; E0111;VARIATION SELECTOR-34;Mn;0;NSM;;;;;N;;;;; E0112;VARIATION SELECTOR-35;Mn;0;NSM;;;;;N;;;;; E0113;VARIATION SELECTOR-36;Mn;0;NSM;;;;;N;;;;; E0114;VARIATION SELECTOR-37;Mn;0;NSM;;;;;N;;;;; E0115;VARIATION SELECTOR-38;Mn;0;NSM;;;;;N;;;;; E0116;VARIATION SELECTOR-39;Mn;0;NSM;;;;;N;;;;; E0117;VARIATION SELECTOR-40;Mn;0;NSM;;;;;N;;;;; E0118;VARIATION SELECTOR-41;Mn;0;NSM;;;;;N;;;;; E0119;VARIATION SELECTOR-42;Mn;0;NSM;;;;;N;;;;; E011A;VARIATION SELECTOR-43;Mn;0;NSM;;;;;N;;;;; E011B;VARIATION SELECTOR-44;Mn;0;NSM;;;;;N;;;;; E011C;VARIATION SELECTOR-45;Mn;0;NSM;;;;;N;;;;; E011D;VARIATION SELECTOR-46;Mn;0;NSM;;;;;N;;;;; E011E;VARIATION SELECTOR-47;Mn;0;NSM;;;;;N;;;;; E011F;VARIATION SELECTOR-48;Mn;0;NSM;;;;;N;;;;; E0120;VARIATION SELECTOR-49;Mn;0;NSM;;;;;N;;;;; E0121;VARIATION SELECTOR-50;Mn;0;NSM;;;;;N;;;;; E0122;VARIATION SELECTOR-51;Mn;0;NSM;;;;;N;;;;; E0123;VARIATION SELECTOR-52;Mn;0;NSM;;;;;N;;;;; E0124;VARIATION SELECTOR-53;Mn;0;NSM;;;;;N;;;;; E0125;VARIATION SELECTOR-54;Mn;0;NSM;;;;;N;;;;; E0126;VARIATION SELECTOR-55;Mn;0;NSM;;;;;N;;;;; E0127;VARIATION SELECTOR-56;Mn;0;NSM;;;;;N;;;;; E0128;VARIATION SELECTOR-57;Mn;0;NSM;;;;;N;;;;; E0129;VARIATION SELECTOR-58;Mn;0;NSM;;;;;N;;;;; E012A;VARIATION SELECTOR-59;Mn;0;NSM;;;;;N;;;;; E012B;VARIATION SELECTOR-60;Mn;0;NSM;;;;;N;;;;; E012C;VARIATION SELECTOR-61;Mn;0;NSM;;;;;N;;;;; E012D;VARIATION SELECTOR-62;Mn;0;NSM;;;;;N;;;;; E012E;VARIATION SELECTOR-63;Mn;0;NSM;;;;;N;;;;; E012F;VARIATION SELECTOR-64;Mn;0;NSM;;;;;N;;;;; E0130;VARIATION SELECTOR-65;Mn;0;NSM;;;;;N;;;;; E0131;VARIATION SELECTOR-66;Mn;0;NSM;;;;;N;;;;; E0132;VARIATION SELECTOR-67;Mn;0;NSM;;;;;N;;;;; E0133;VARIATION SELECTOR-68;Mn;0;NSM;;;;;N;;;;; E0134;VARIATION SELECTOR-69;Mn;0;NSM;;;;;N;;;;; E0135;VARIATION SELECTOR-70;Mn;0;NSM;;;;;N;;;;; E0136;VARIATION SELECTOR-71;Mn;0;NSM;;;;;N;;;;; E0137;VARIATION SELECTOR-72;Mn;0;NSM;;;;;N;;;;; E0138;VARIATION SELECTOR-73;Mn;0;NSM;;;;;N;;;;; E0139;VARIATION SELECTOR-74;Mn;0;NSM;;;;;N;;;;; E013A;VARIATION SELECTOR-75;Mn;0;NSM;;;;;N;;;;; E013B;VARIATION SELECTOR-76;Mn;0;NSM;;;;;N;;;;; E013C;VARIATION SELECTOR-77;Mn;0;NSM;;;;;N;;;;; E013D;VARIATION SELECTOR-78;Mn;0;NSM;;;;;N;;;;; E013E;VARIATION SELECTOR-79;Mn;0;NSM;;;;;N;;;;; E013F;VARIATION SELECTOR-80;Mn;0;NSM;;;;;N;;;;; E0140;VARIATION SELECTOR-81;Mn;0;NSM;;;;;N;;;;; E0141;VARIATION SELECTOR-82;Mn;0;NSM;;;;;N;;;;; E0142;VARIATION SELECTOR-83;Mn;0;NSM;;;;;N;;;;; E0143;VARIATION SELECTOR-84;Mn;0;NSM;;;;;N;;;;; E0144;VARIATION SELECTOR-85;Mn;0;NSM;;;;;N;;;;; E0145;VARIATION SELECTOR-86;Mn;0;NSM;;;;;N;;;;; E0146;VARIATION SELECTOR-87;Mn;0;NSM;;;;;N;;;;; E0147;VARIATION SELECTOR-88;Mn;0;NSM;;;;;N;;;;; E0148;VARIATION SELECTOR-89;Mn;0;NSM;;;;;N;;;;; E0149;VARIATION SELECTOR-90;Mn;0;NSM;;;;;N;;;;; E014A;VARIATION SELECTOR-91;Mn;0;NSM;;;;;N;;;;; E014B;VARIATION SELECTOR-92;Mn;0;NSM;;;;;N;;;;; E014C;VARIATION SELECTOR-93;Mn;0;NSM;;;;;N;;;;; E014D;VARIATION SELECTOR-94;Mn;0;NSM;;;;;N;;;;; E014E;VARIATION SELECTOR-95;Mn;0;NSM;;;;;N;;;;; E014F;VARIATION SELECTOR-96;Mn;0;NSM;;;;;N;;;;; E0150;VARIATION SELECTOR-97;Mn;0;NSM;;;;;N;;;;; E0151;VARIATION SELECTOR-98;Mn;0;NSM;;;;;N;;;;; E0152;VARIATION SELECTOR-99;Mn;0;NSM;;;;;N;;;;; E0153;VARIATION SELECTOR-100;Mn;0;NSM;;;;;N;;;;; E0154;VARIATION SELECTOR-101;Mn;0;NSM;;;;;N;;;;; E0155;VARIATION SELECTOR-102;Mn;0;NSM;;;;;N;;;;; E0156;VARIATION SELECTOR-103;Mn;0;NSM;;;;;N;;;;; E0157;VARIATION SELECTOR-104;Mn;0;NSM;;;;;N;;;;; E0158;VARIATION SELECTOR-105;Mn;0;NSM;;;;;N;;;;; E0159;VARIATION SELECTOR-106;Mn;0;NSM;;;;;N;;;;; E015A;VARIATION SELECTOR-107;Mn;0;NSM;;;;;N;;;;; E015B;VARIATION SELECTOR-108;Mn;0;NSM;;;;;N;;;;; E015C;VARIATION SELECTOR-109;Mn;0;NSM;;;;;N;;;;; E015D;VARIATION SELECTOR-110;Mn;0;NSM;;;;;N;;;;; E015E;VARIATION SELECTOR-111;Mn;0;NSM;;;;;N;;;;; E015F;VARIATION SELECTOR-112;Mn;0;NSM;;;;;N;;;;; E0160;VARIATION SELECTOR-113;Mn;0;NSM;;;;;N;;;;; E0161;VARIATION SELECTOR-114;Mn;0;NSM;;;;;N;;;;; E0162;VARIATION SELECTOR-115;Mn;0;NSM;;;;;N;;;;; E0163;VARIATION SELECTOR-116;Mn;0;NSM;;;;;N;;;;; E0164;VARIATION SELECTOR-117;Mn;0;NSM;;;;;N;;;;; E0165;VARIATION SELECTOR-118;Mn;0;NSM;;;;;N;;;;; E0166;VARIATION SELECTOR-119;Mn;0;NSM;;;;;N;;;;; E0167;VARIATION SELECTOR-120;Mn;0;NSM;;;;;N;;;;; E0168;VARIATION SELECTOR-121;Mn;0;NSM;;;;;N;;;;; E0169;VARIATION SELECTOR-122;Mn;0;NSM;;;;;N;;;;; E016A;VARIATION SELECTOR-123;Mn;0;NSM;;;;;N;;;;; E016B;VARIATION SELECTOR-124;Mn;0;NSM;;;;;N;;;;; E016C;VARIATION SELECTOR-125;Mn;0;NSM;;;;;N;;;;; E016D;VARIATION SELECTOR-126;Mn;0;NSM;;;;;N;;;;; E016E;VARIATION SELECTOR-127;Mn;0;NSM;;;;;N;;;;; E016F;VARIATION SELECTOR-128;Mn;0;NSM;;;;;N;;;;; E0170;VARIATION SELECTOR-129;Mn;0;NSM;;;;;N;;;;; E0171;VARIATION SELECTOR-130;Mn;0;NSM;;;;;N;;;;; E0172;VARIATION SELECTOR-131;Mn;0;NSM;;;;;N;;;;; E0173;VARIATION SELECTOR-132;Mn;0;NSM;;;;;N;;;;; E0174;VARIATION SELECTOR-133;Mn;0;NSM;;;;;N;;;;; E0175;VARIATION SELECTOR-134;Mn;0;NSM;;;;;N;;;;; E0176;VARIATION SELECTOR-135;Mn;0;NSM;;;;;N;;;;; E0177;VARIATION SELECTOR-136;Mn;0;NSM;;;;;N;;;;; E0178;VARIATION SELECTOR-137;Mn;0;NSM;;;;;N;;;;; E0179;VARIATION SELECTOR-138;Mn;0;NSM;;;;;N;;;;; E017A;VARIATION SELECTOR-139;Mn;0;NSM;;;;;N;;;;; E017B;VARIATION SELECTOR-140;Mn;0;NSM;;;;;N;;;;; E017C;VARIATION SELECTOR-141;Mn;0;NSM;;;;;N;;;;; E017D;VARIATION SELECTOR-142;Mn;0;NSM;;;;;N;;;;; E017E;VARIATION SELECTOR-143;Mn;0;NSM;;;;;N;;;;; E017F;VARIATION SELECTOR-144;Mn;0;NSM;;;;;N;;;;; E0180;VARIATION SELECTOR-145;Mn;0;NSM;;;;;N;;;;; E0181;VARIATION SELECTOR-146;Mn;0;NSM;;;;;N;;;;; E0182;VARIATION SELECTOR-147;Mn;0;NSM;;;;;N;;;;; E0183;VARIATION SELECTOR-148;Mn;0;NSM;;;;;N;;;;; E0184;VARIATION SELECTOR-149;Mn;0;NSM;;;;;N;;;;; E0185;VARIATION SELECTOR-150;Mn;0;NSM;;;;;N;;;;; E0186;VARIATION SELECTOR-151;Mn;0;NSM;;;;;N;;;;; E0187;VARIATION SELECTOR-152;Mn;0;NSM;;;;;N;;;;; E0188;VARIATION SELECTOR-153;Mn;0;NSM;;;;;N;;;;; E0189;VARIATION SELECTOR-154;Mn;0;NSM;;;;;N;;;;; E018A;VARIATION SELECTOR-155;Mn;0;NSM;;;;;N;;;;; E018B;VARIATION SELECTOR-156;Mn;0;NSM;;;;;N;;;;; E018C;VARIATION SELECTOR-157;Mn;0;NSM;;;;;N;;;;; E018D;VARIATION SELECTOR-158;Mn;0;NSM;;;;;N;;;;; E018E;VARIATION SELECTOR-159;Mn;0;NSM;;;;;N;;;;; E018F;VARIATION SELECTOR-160;Mn;0;NSM;;;;;N;;;;; E0190;VARIATION SELECTOR-161;Mn;0;NSM;;;;;N;;;;; E0191;VARIATION SELECTOR-162;Mn;0;NSM;;;;;N;;;;; E0192;VARIATION SELECTOR-163;Mn;0;NSM;;;;;N;;;;; E0193;VARIATION SELECTOR-164;Mn;0;NSM;;;;;N;;;;; E0194;VARIATION SELECTOR-165;Mn;0;NSM;;;;;N;;;;; E0195;VARIATION SELECTOR-166;Mn;0;NSM;;;;;N;;;;; E0196;VARIATION SELECTOR-167;Mn;0;NSM;;;;;N;;;;; E0197;VARIATION SELECTOR-168;Mn;0;NSM;;;;;N;;;;; E0198;VARIATION SELECTOR-169;Mn;0;NSM;;;;;N;;;;; E0199;VARIATION SELECTOR-170;Mn;0;NSM;;;;;N;;;;; E019A;VARIATION SELECTOR-171;Mn;0;NSM;;;;;N;;;;; E019B;VARIATION SELECTOR-172;Mn;0;NSM;;;;;N;;;;; E019C;VARIATION SELECTOR-173;Mn;0;NSM;;;;;N;;;;; E019D;VARIATION SELECTOR-174;Mn;0;NSM;;;;;N;;;;; E019E;VARIATION SELECTOR-175;Mn;0;NSM;;;;;N;;;;; E019F;VARIATION SELECTOR-176;Mn;0;NSM;;;;;N;;;;; E01A0;VARIATION SELECTOR-177;Mn;0;NSM;;;;;N;;;;; E01A1;VARIATION SELECTOR-178;Mn;0;NSM;;;;;N;;;;; E01A2;VARIATION SELECTOR-179;Mn;0;NSM;;;;;N;;;;; E01A3;VARIATION SELECTOR-180;Mn;0;NSM;;;;;N;;;;; E01A4;VARIATION SELECTOR-181;Mn;0;NSM;;;;;N;;;;; E01A5;VARIATION SELECTOR-182;Mn;0;NSM;;;;;N;;;;; E01A6;VARIATION SELECTOR-183;Mn;0;NSM;;;;;N;;;;; E01A7;VARIATION SELECTOR-184;Mn;0;NSM;;;;;N;;;;; E01A8;VARIATION SELECTOR-185;Mn;0;NSM;;;;;N;;;;; E01A9;VARIATION SELECTOR-186;Mn;0;NSM;;;;;N;;;;; E01AA;VARIATION SELECTOR-187;Mn;0;NSM;;;;;N;;;;; E01AB;VARIATION SELECTOR-188;Mn;0;NSM;;;;;N;;;;; E01AC;VARIATION SELECTOR-189;Mn;0;NSM;;;;;N;;;;; E01AD;VARIATION SELECTOR-190;Mn;0;NSM;;;;;N;;;;; E01AE;VARIATION SELECTOR-191;Mn;0;NSM;;;;;N;;;;; E01AF;VARIATION SELECTOR-192;Mn;0;NSM;;;;;N;;;;; E01B0;VARIATION SELECTOR-193;Mn;0;NSM;;;;;N;;;;; E01B1;VARIATION SELECTOR-194;Mn;0;NSM;;;;;N;;;;; E01B2;VARIATION SELECTOR-195;Mn;0;NSM;;;;;N;;;;; E01B3;VARIATION SELECTOR-196;Mn;0;NSM;;;;;N;;;;; E01B4;VARIATION SELECTOR-197;Mn;0;NSM;;;;;N;;;;; E01B5;VARIATION SELECTOR-198;Mn;0;NSM;;;;;N;;;;; E01B6;VARIATION SELECTOR-199;Mn;0;NSM;;;;;N;;;;; E01B7;VARIATION SELECTOR-200;Mn;0;NSM;;;;;N;;;;; E01B8;VARIATION SELECTOR-201;Mn;0;NSM;;;;;N;;;;; E01B9;VARIATION SELECTOR-202;Mn;0;NSM;;;;;N;;;;; E01BA;VARIATION SELECTOR-203;Mn;0;NSM;;;;;N;;;;; E01BB;VARIATION SELECTOR-204;Mn;0;NSM;;;;;N;;;;; E01BC;VARIATION SELECTOR-205;Mn;0;NSM;;;;;N;;;;; E01BD;VARIATION SELECTOR-206;Mn;0;NSM;;;;;N;;;;; E01BE;VARIATION SELECTOR-207;Mn;0;NSM;;;;;N;;;;; E01BF;VARIATION SELECTOR-208;Mn;0;NSM;;;;;N;;;;; E01C0;VARIATION SELECTOR-209;Mn;0;NSM;;;;;N;;;;; E01C1;VARIATION SELECTOR-210;Mn;0;NSM;;;;;N;;;;; E01C2;VARIATION SELECTOR-211;Mn;0;NSM;;;;;N;;;;; E01C3;VARIATION SELECTOR-212;Mn;0;NSM;;;;;N;;;;; E01C4;VARIATION SELECTOR-213;Mn;0;NSM;;;;;N;;;;; E01C5;VARIATION SELECTOR-214;Mn;0;NSM;;;;;N;;;;; E01C6;VARIATION SELECTOR-215;Mn;0;NSM;;;;;N;;;;; E01C7;VARIATION SELECTOR-216;Mn;0;NSM;;;;;N;;;;; E01C8;VARIATION SELECTOR-217;Mn;0;NSM;;;;;N;;;;; E01C9;VARIATION SELECTOR-218;Mn;0;NSM;;;;;N;;;;; E01CA;VARIATION SELECTOR-219;Mn;0;NSM;;;;;N;;;;; E01CB;VARIATION SELECTOR-220;Mn;0;NSM;;;;;N;;;;; E01CC;VARIATION SELECTOR-221;Mn;0;NSM;;;;;N;;;;; E01CD;VARIATION SELECTOR-222;Mn;0;NSM;;;;;N;;;;; E01CE;VARIATION SELECTOR-223;Mn;0;NSM;;;;;N;;;;; E01CF;VARIATION SELECTOR-224;Mn;0;NSM;;;;;N;;;;; E01D0;VARIATION SELECTOR-225;Mn;0;NSM;;;;;N;;;;; E01D1;VARIATION SELECTOR-226;Mn;0;NSM;;;;;N;;;;; E01D2;VARIATION SELECTOR-227;Mn;0;NSM;;;;;N;;;;; E01D3;VARIATION SELECTOR-228;Mn;0;NSM;;;;;N;;;;; E01D4;VARIATION SELECTOR-229;Mn;0;NSM;;;;;N;;;;; E01D5;VARIATION SELECTOR-230;Mn;0;NSM;;;;;N;;;;; E01D6;VARIATION SELECTOR-231;Mn;0;NSM;;;;;N;;;;; E01D7;VARIATION SELECTOR-232;Mn;0;NSM;;;;;N;;;;; E01D8;VARIATION SELECTOR-233;Mn;0;NSM;;;;;N;;;;; E01D9;VARIATION SELECTOR-234;Mn;0;NSM;;;;;N;;;;; E01DA;VARIATION SELECTOR-235;Mn;0;NSM;;;;;N;;;;; E01DB;VARIATION SELECTOR-236;Mn;0;NSM;;;;;N;;;;; E01DC;VARIATION SELECTOR-237;Mn;0;NSM;;;;;N;;;;; E01DD;VARIATION SELECTOR-238;Mn;0;NSM;;;;;N;;;;; E01DE;VARIATION SELECTOR-239;Mn;0;NSM;;;;;N;;;;; E01DF;VARIATION SELECTOR-240;Mn;0;NSM;;;;;N;;;;; E01E0;VARIATION SELECTOR-241;Mn;0;NSM;;;;;N;;;;; E01E1;VARIATION SELECTOR-242;Mn;0;NSM;;;;;N;;;;; E01E2;VARIATION SELECTOR-243;Mn;0;NSM;;;;;N;;;;; E01E3;VARIATION SELECTOR-244;Mn;0;NSM;;;;;N;;;;; E01E4;VARIATION SELECTOR-245;Mn;0;NSM;;;;;N;;;;; E01E5;VARIATION SELECTOR-246;Mn;0;NSM;;;;;N;;;;; E01E6;VARIATION SELECTOR-247;Mn;0;NSM;;;;;N;;;;; E01E7;VARIATION SELECTOR-248;Mn;0;NSM;;;;;N;;;;; E01E8;VARIATION SELECTOR-249;Mn;0;NSM;;;;;N;;;;; E01E9;VARIATION SELECTOR-250;Mn;0;NSM;;;;;N;;;;; E01EA;VARIATION SELECTOR-251;Mn;0;NSM;;;;;N;;;;; E01EB;VARIATION SELECTOR-252;Mn;0;NSM;;;;;N;;;;; E01EC;VARIATION SELECTOR-253;Mn;0;NSM;;;;;N;;;;; E01ED;VARIATION SELECTOR-254;Mn;0;NSM;;;;;N;;;;; E01EE;VARIATION SELECTOR-255;Mn;0;NSM;;;;;N;;;;; E01EF;VARIATION SELECTOR-256;Mn;0;NSM;;;;;N;;;;; F0000;;Co;0;L;;;;;N;;;;; FFFFD;;Co;0;L;;;;;N;;;;; 100000;;Co;0;L;;;;;N;;;;; 10FFFD;;Co;0;L;;;;;N;;;;; dovecot-2.3.21.1/src/lib/mempool.h0000644000000000000000000001352614656633576013502 00000000000000#ifndef MEMPOOL_H #define MEMPOOL_H #include "macros.h" /* When DEBUG is enabled, Dovecot warns whenever a memory pool is grown. This is done so that the initial pool size could be set large enough so that it wouldn't grow in normal use. For some memory pools it's too difficult to calculate a good initial size, so this prefix should be used with those pools to disable the warning. */ #define MEMPOOL_GROWING "GROWING-" /* The maximum allocation size that's allowed. Anything larger than that will panic. No pool ever should need more than 4kB of overhead per allocation. */ #define POOL_MAX_ALLOC_SIZE (SSIZE_T_MAX - 4096) /* Memory allocated and reallocated (the new data in it) in pools is always zeroed, it will cost only a few CPU cycles and may well save some debug time. */ typedef struct pool *pool_t; struct pool_vfuncs { const char *(*get_name)(pool_t pool); void (*ref)(pool_t pool); void (*unref)(pool_t *pool); void *(*malloc)(pool_t pool, size_t size) ATTR_RETURNS_NONNULL; void (*free)(pool_t pool, void *mem); /* memory in old_size..new_size will be zeroed */ void *(*realloc)(pool_t pool, void *mem, size_t old_size, size_t new_size) ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL; /* Frees all the memory in pool. NOTE: system_pool doesn't support this and crashes if it's used */ void (*clear)(pool_t pool); /* Returns the maximum amount of bytes that can be allocated with minimal trouble. If there's no such concept, always returns 0. */ size_t (*get_max_easy_alloc_size)(pool_t pool); }; struct pool { const struct pool_vfuncs *v; bool alloconly_pool:1; bool datastack_pool:1; }; /* system_pool uses calloc() + realloc() + free() */ extern pool_t system_pool; extern struct pool static_system_pool; /* memory allocated from data_stack is valid only until next t_pop() call. No checks are performed. */ extern pool_t unsafe_data_stack_pool; /* Create a new alloc-only pool. Note that `size' specifies the initial malloc()ed block size, part of it is used internally. */ pool_t pool_alloconly_create(const char *name, size_t size); /* Like alloconly pool, but clear the memory before freeing it. The idea is that you could allocate memory for storing sensitive information from this pool, and be sure that it gets cleared from the memory when it's no longer needed. */ pool_t pool_alloconly_create_clean(const char *name, size_t size); /* When allocating memory from returned pool, the data stack frame must be the same as it was when calling this function. pool_unref() also checks that the stack frame is the same. This should make it quite safe to use. */ pool_t pool_datastack_create(void); /* Create new alloc pool. This is very similar to system pool, but it will deallocate all memory on deinit. */ pool_t pool_allocfree_create(const char *name); /* Like alloc pool, but all memory is cleaned before freeing. See pool_alloconly_create_clean. */ pool_t pool_allocfree_create_clean(const char *name); /* Similar to nearest_power(), but try not to exceed buffer's easy allocation size. If you don't have any explicit minimum size, use old_size + 1. */ size_t pool_get_exp_grown_size(pool_t pool, size_t old_size, size_t min_size); /* We require sizeof(type) to be <= UINT_MAX. This allows compiler to optimize away the entire MALLOC_MULTIPLY() call on 64bit systems. */ #define p_new(pool, type, count) \ ((type *) p_malloc(pool, MALLOC_MULTIPLY((unsigned int)sizeof(type), (count))) + \ COMPILE_ERROR_IF_TRUE(sizeof(type) > UINT_MAX)) #define p_realloc_type(pool, mem, type, old_count, new_count) \ ((type *) p_realloc(pool, mem, \ MALLOC_MULTIPLY((unsigned int)sizeof(type), (old_count)), \ MALLOC_MULTIPLY((unsigned int)sizeof(type), (new_count))) + \ COMPILE_ERROR_IF_TRUE(sizeof(type) > UINT_MAX)) static inline void * ATTR_MALLOC ATTR_RETURNS_NONNULL p_malloc(pool_t pool, size_t size) { if (unlikely(size == 0 || size > POOL_MAX_ALLOC_SIZE)) i_panic("Trying to allocate %zu bytes", size); return pool->v->malloc(pool, size); } static inline void * ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL p_realloc(pool_t pool, void *mem, size_t old_size, size_t new_size) { if (unlikely(new_size == 0 || new_size > POOL_MAX_ALLOC_SIZE)) i_panic("Trying to reallocate %zu -> %zu bytes", old_size, new_size); if (mem == NULL) return pool->v->malloc(pool, new_size); return pool->v->realloc(pool, mem, old_size, new_size); } /* Free the memory. p_free() and p_free_and_null() are now guaranteed to both set mem=NULL, so either one of them can be used. */ #define p_free(pool, mem) \ STMT_START { \ p_free_internal(pool, mem); \ (mem) = NULL; \ } STMT_END #define p_free_and_null(pool, mem) p_free(pool, mem) static inline void p_free_internal(pool_t pool, void *mem) { if (mem != NULL) pool->v->free(pool, mem); } static inline void p_clear(pool_t pool) { pool->v->clear(pool); } static inline size_t p_get_max_easy_alloc_size(pool_t pool) { return pool->v->get_max_easy_alloc_size(pool); } static inline const char *pool_get_name(pool_t pool) { return pool->v->get_name(pool); } static inline void pool_ref(pool_t pool) { pool->v->ref(pool); } static inline void pool_unref(pool_t *pool) { if (*pool != NULL) (*pool)->v->unref(pool); } /* These functions are only for pools created with pool_alloconly_create(): */ /* Returns how much memory has been allocated from this pool. */ size_t pool_alloconly_get_total_used_size(pool_t pool); /* Returns how much system memory has been allocated for this pool. */ size_t pool_alloconly_get_total_alloc_size(pool_t pool); /* Returns how much memory has been allocated from this pool. */ size_t pool_allocfree_get_total_used_size(pool_t pool); /* Returns how much system memory has been allocated for this pool. */ size_t pool_allocfree_get_total_alloc_size(pool_t pool); /* private: */ void pool_system_free(pool_t pool, void *mem); #endif dovecot-2.3.21.1/src/lib/numpack.h0000644000000000000000000000055314656633576013464 00000000000000#ifndef NUMPACK_H #define NUMPACK_H /* Numbers are stored by 7 bits at a time. The highest bit specifies if the number continues to next byte. */ void numpack_encode(buffer_t *buf, uint64_t num); int numpack_decode(const uint8_t **p, const uint8_t *end, uint64_t *num_r); int numpack_decode32(const uint8_t **p, const uint8_t *end, uint32_t *num_r); #endif dovecot-2.3.21.1/src/lib/iso8601-date.h0000644000000000000000000000165414656633576014055 00000000000000#ifndef ISO8601_DATE_H #define ISO8601_DATE_H /* Parses ISO8601 (RFC3339) date-time string. timezone_offset is filled with the timezone's difference to UTC in minutes. Returned time_t timestamp is compensated for time zone. */ bool iso8601_date_parse(const unsigned char *data, size_t size, time_t *timestamp_r, int *timezone_offset_r); /* Equal to iso8601_date_parse, but writes uncompensated timestamp to tm_r. */ bool iso8601_date_parse_tm(const unsigned char *data, size_t size, struct tm *tm_r, int *timezone_offset_r); /* Create ISO8601 date-time string from given time struct in specified timezone. A zone offset of zero will not to 'Z', but '+00:00'. If zone_offset == INT_MAX, the time zone will be 'Z'. */ const char *iso8601_date_create_tm(struct tm *tm, int zone_offset); /* Create ISO8601 date-time string from given time in local timezone. */ const char *iso8601_date_create(time_t timestamp); #endif dovecot-2.3.21.1/src/lib/compat.c0000644000000000000000000001226214656633576013304 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "config.h" #undef HAVE_CONFIG_H /* Linux needs the _XOPEN_SOURCE define, but others don't. It needs to be defined before unistd.h, so we need the above config.h include hack.. */ #ifdef PREAD_WRAPPERS # define _XOPEN_SOURCE 500 /* Linux */ #endif #define IN_COMPAT_C #include "lib.h" #include #include #include #include #include #include #ifndef INADDR_NONE # define INADDR_NONE INADDR_BROADCAST #endif #if !defined (HAVE_STRCASECMP) && !defined (HAVE_STRICMP) int i_my_strcasecmp(const char *s1, const char *s2) { while (*s1 != '\0' && i_toupper(*s1) == i_toupper(*s2)) { s1++; s2++; } return i_toupper(*s1) - i_toupper(*s2); } int i_my_strncasecmp(const char *s1, const char *s2, size_t max_chars) { while (max_chars > 1 && *s1 != '\0' && i_toupper(*s1) == i_toupper(*s2)) { s1++; s2++; max_chars--; } return i_toupper(*s1) - i_toupper(*s2); } #endif #ifndef HAVE_INET_ATON int i_my_inet_aton(const char *cp, struct in_addr *inp) { in_addr_t addr; addr = inet_addr(cp); if (addr == INADDR_NONE) return 0; inp->s_addr = addr; return 1; } #endif #ifndef HAVE_VSYSLOG void i_my_vsyslog(int priority, const char *format, va_list args) { T_BEGIN { syslog(priority, "%s", t_strdup_vprintf(format, args)); } T_END; } #endif #ifndef HAVE_GETPAGESIZE int i_my_getpagesize(void) { #ifdef _SC_PAGESIZE return sysconf(_SC_PAGESIZE); #else # ifdef __GNUC__ # warning Guessing page size to be 4096 # endif return 4096; #endif } #endif #ifndef HAVE_WRITEV ssize_t i_my_writev(int fd, const struct iovec *iov, int iov_len) { size_t written; ssize_t ret; int i; written = 0; for (i = 0; i < iov_len; i++, iov++) { ret = write(fd, iov->iov_base, iov->iov_len); if (ret < 0) return -1; written += ret; if ((size_t)ret != iov->iov_len) break; } if (written > SSIZE_T_MAX) { errno = ERANGE; return -1; } return (ssize_t)written; } #endif #if !defined(HAVE_PREAD) || defined(PREAD_BROKEN) ssize_t i_my_pread(int fd, void *buf, size_t count, off_t offset) { ssize_t ret; off_t old_offset; old_offset = lseek(fd, 0, SEEK_CUR); if (old_offset == -1) return -1; if (lseek(fd, offset, SEEK_SET) < 0) return -1; ret = read(fd, buf, count); if (ret < 0) return -1; if (lseek(fd, old_offset, SEEK_SET) < 0) return -1; return ret; } ssize_t i_my_pwrite(int fd, const void *buf, size_t count, off_t offset) { ssize_t ret; off_t old_offset; old_offset = lseek(fd, 0, SEEK_CUR); if (old_offset == -1) return -1; if (lseek(fd, offset, SEEK_SET) < 0) return -1; ret = write(fd, buf, count); if (ret < 0) return -1; if (lseek(fd, old_offset, SEEK_SET) < 0) return -1; return ret; } #elif defined(PREAD_WRAPPERS) ssize_t i_my_pread(int fd, void *buf, size_t count, off_t offset) { ssize_t ret; ret = pread(fd, buf, count, offset); return ret; } ssize_t i_my_pwrite(int fd, const void *buf, size_t count, off_t offset) { return pwrite(fd, buf, count, offset); } #endif #ifndef HAVE_SETEUID int i_my_seteuid(uid_t euid) { #ifdef HAVE_SETREUID /* HP-UX at least doesn't have seteuid() but has setreuid() */ return setreuid(-1, euid); #else # error Missing seteuid functionality #endif } #endif #ifndef HAVE_SETEGID int i_my_setegid(gid_t egid) { #ifdef HAVE_SETRESGID /* HP-UX at least doesn't have setegid() but has setresgid() */ return setresgid(-1, egid, -1); #else # error Missing setegid functionality #endif } #endif #ifndef HAVE_LIBGEN_H char *i_my_basename(char *path) { char *p; /* note that this isn't POSIX-compliant basename() replacement. too much trouble without any gain. */ p = strrchr(path, '/'); return p == NULL ? path : p + 1; } #endif #ifdef HAVE_OLD_VSNPRINTF #undef vsnprintf int i_my_vsnprintf(char *str, size_t size, const char *format, va_list ap) { size_t tmp_size; char *tmp; int ret; /* On overflow HP-UX returns -1, IRIX and Tru64 return size-1. */ ret = vsnprintf(str, size, format, ap); if (ret >= 0 && (size_t)ret+1 != size) return ret; /* see if data stack has enough available space for it */ tmp_size = t_get_bytes_available(); if (tmp_size > size) { tmp = t_buffer_get(tmp_size); ret = vsnprintf(tmp, tmp_size, format, ap); if (ret >= 0 && (size_t)ret+1 != tmp_size) { if (size > 0) { memcpy(str, tmp, size-1); str[size-1] = '\0'; } return ret; } } else { tmp_size = size; } /* try to allocate enough memory to get it to fit. */ do { tmp_size = nearest_power(tmp_size+1); tmp = i_malloc(tmp_size); ret = vsnprintf(tmp, tmp_size, format, ap); if (ret >= 0 && (size_t)ret+1 != tmp_size) { if (size > 0) { memcpy(str, tmp, size-1); str[size-1] = '\0'; } i_free(tmp); return ret; } i_free(tmp); } while (tmp_size < 1024*1024); i_panic("my_vsnprintf(): Output string too big"); } #endif #ifndef HAVE_CLOCK_GETTIME int i_my_clock_gettime(int clk_id, struct timespec *tp) { struct timeval tv; i_assert(clk_id == CLOCK_REALTIME); /* fallback to using microseconds */ if (gettimeofday(&tv, NULL) < 0) return -1; tp->tv_sec = tv.tv_sec; tp->tv_nsec = tv.tv_usec * 1000; return 0; } #endif dovecot-2.3.21.1/src/lib/file-set-size.h0000644000000000000000000000074214656633576014506 00000000000000#ifndef FILE_SET_SIZE_H #define FILE_SET_SIZE_H /* Shrink/grow file. If file is grown, the new data is guaranteed to be zeros. The file offset may be anywhere after this call. Returns -1 if failed, 0 if successful. */ int file_set_size(int fd, off_t size); /* Preallocate file to given size, without actually changing the size reported by stat(). Returns 1 if ok, 0 if not supported by this filesystem, -1 if error. */ int file_preallocate(int fd, off_t size); #endif dovecot-2.3.21.1/src/lib/str-table.c0000644000000000000000000000316414656633576013717 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "hash.h" #include "str-table.h" struct str_table { HASH_TABLE(char *, void *) hash; }; struct str_table *str_table_init(void) { struct str_table *table; table = i_new(struct str_table, 1); hash_table_create(&table->hash, default_pool, 0, str_hash, strcmp); return table; } void str_table_deinit(struct str_table **_table) { struct str_table *table = *_table; struct hash_iterate_context *iter; char *key; void *value; *_table = NULL; iter = hash_table_iterate_init(table->hash); while (hash_table_iterate(iter, table->hash, &key, &value)) i_free(key); hash_table_iterate_deinit(&iter); hash_table_destroy(&table->hash); i_free(table); } bool str_table_is_empty(struct str_table *table) { return hash_table_count(table->hash) == 0; } const char *str_table_ref(struct str_table *table, const char *str) { char *key; void *value; unsigned int ref; if (!hash_table_lookup_full(table->hash, str, &key, &value)) { key = i_strdup(str); ref = 1; } else { ref = POINTER_CAST_TO(value, unsigned int); i_assert(ref > 0); ref++; } hash_table_update(table->hash, key, POINTER_CAST(ref)); return key; } void str_table_unref(struct str_table *table, const char **str) { char *key; void *value; unsigned int ref; if (!hash_table_lookup_full(table->hash, *str, &key, &value)) i_unreached(); ref = POINTER_CAST_TO(value, unsigned int); i_assert(ref > 0); if (--ref > 0) hash_table_update(table->hash, key, POINTER_CAST(ref)); else { hash_table_remove(table->hash, key); i_free(key); } *str = NULL; } dovecot-2.3.21.1/src/lib/buffer.h0000644000000000000000000001735014656633576013302 00000000000000#ifndef BUFFER_H #define BUFFER_H struct buffer { union { struct { const void *data; const size_t used; }; void *priv[9]; }; }; /* WARNING: Be careful with functions that return pointers to data. With dynamic buffers they are valid only as long as buffer is not realloc()ed. You shouldn't rely on it being valid if you have modified buffer in any way. */ /* Create a modifiable buffer from given data. Writes past this size will i_panic(). */ void buffer_create_from_data(buffer_t *buffer, void *data, size_t size); /* Create a non-modifiable buffer from given data. */ void buffer_create_from_const_data(buffer_t *buffer, const void *data, size_t size); #define buffer_create_from_data(b,d,s) \ TYPE_CHECKS(void, \ COMPILE_ERROR_IF_TRUE(__builtin_object_size((d),1) < ((s)>0?(s):1)), \ buffer_create_from_data((b), (d), (s))) #define buffer_create_from_const_data(b,d,s) \ TYPE_CHECKS(void, \ COMPILE_ERROR_IF_TRUE(__builtin_object_size((d),1) < ((s)>0?(s):1)), \ buffer_create_from_const_data((b), (d), (s))) /* Creates a dynamically growing buffer. Whenever write would exceed the current size it's grown. */ buffer_t *buffer_create_dynamic(pool_t pool, size_t init_size); /* Create a dynamically growing buffer with a maximum size. Writes past the maximum size will i_panic(). Internally allow it to grow max_size+1 so str_c() NUL can be used. */ buffer_t *buffer_create_dynamic_max(pool_t pool, size_t init_size, size_t max_size); #define t_buffer_create(init_size) \ buffer_create_dynamic(pool_datastack_create(), (init_size)) /* Free the memory used by buffer. Not needed if the memory is free'd directly from the memory pool. */ void buffer_free(buffer_t **buf); /* Free the memory used by buffer structure, but return the buffer data unfree'd. */ void *buffer_free_without_data(buffer_t **buf); /* Returns the pool buffer was created with. */ pool_t buffer_get_pool(const buffer_t *buf) ATTR_PURE; /* Write data to buffer at specified position. If pos is beyond the buffer's current size, it is zero-filled up to that point (even if data_size==0). */ void buffer_write(buffer_t *buf, size_t pos, const void *data, size_t data_size); /* Append data to buffer. */ void buffer_append(buffer_t *buf, const void *data, size_t data_size); /* Append character to buffer. */ void buffer_append_c(buffer_t *buf, unsigned char chr); /* Insert the provided data into the buffer at position pos. If pos points past the current buffer size, the gap is zero-filled. */ void buffer_insert(buffer_t *buf, size_t pos, const void *data, size_t data_size); /* Delete data with the indicated size from the buffer at position pos. The deleted block may cross the current buffer size boundary, which is ignored. */ void buffer_delete(buffer_t *buf, size_t pos, size_t size); /* Replace the data in the buffer with the indicated size at position pos with the provided data. This is a more optimized version of buffer_delete(buf, pos, size); buffer_insert(buf, pos, data, data_size); */ void buffer_replace(buffer_t *buf, size_t pos, size_t size, const void *data, size_t data_size); /* Fill buffer with zero bytes. */ void buffer_write_zero(buffer_t *buf, size_t pos, size_t data_size); void buffer_append_zero(buffer_t *buf, size_t data_size); void buffer_insert_zero(buffer_t *buf, size_t pos, size_t data_size); /* Copy data from buffer to another. The buffers may be same in which case it's internal copying, possibly with overlapping positions (ie. memmove() like functionality). copy_size may be set to SIZE_MAX to copy the rest of the used data in buffer. */ void buffer_copy(buffer_t *dest, size_t dest_pos, const buffer_t *src, size_t src_pos, size_t copy_size); /* Append data to buffer from another. copy_size may be set to SIZE_MAX to copy the rest of the used data in buffer. */ void buffer_append_buf(buffer_t *dest, const buffer_t *src, size_t src_pos, size_t copy_size); /* Returns pointer to specified position in buffer. WARNING: The returned address may become invalid if you add more data to buffer. */ void *buffer_get_space_unsafe(buffer_t *buf, size_t pos, size_t size); /* Increase the buffer usage by given size, and return a pointer to beginning of it. */ void *buffer_append_space_unsafe(buffer_t *buf, size_t size); /* Like buffer_get_data(), but don't return it as const. Returns NULL if the buffer is non-modifiable. WARNING: The returned address may become invalid if you add more data to buffer. */ void *buffer_get_modifiable_data(const buffer_t *buf, size_t *used_size_r) ATTR_NULL(2); /* Set the "used size" of buffer, ie. 0 would set the buffer empty. Must not be used to grow buffer. The data after the buffer's new size will be effectively lost, because e.g. buffer_get_space_unsafe() will zero out the contents. */ void buffer_set_used_size(buffer_t *buf, size_t used_size); /* Returns the current buffer size. */ size_t buffer_get_size(const buffer_t *buf) ATTR_PURE; /* Returns how many bytes we can write to buffer without increasing its size. With dynamic buffers this is buffer_get_size()-1, because the extra 1 byte is reserved for str_c()'s NUL. */ size_t buffer_get_writable_size(const buffer_t *buf) ATTR_PURE; /* Returns the maximum number of bytes we can append to the buffer. If the buffer is dynamic, this is always near SIZE_MAX. */ size_t buffer_get_avail_size(const buffer_t *buf) ATTR_PURE; /* Returns TRUE if buffer contents are identical. */ bool buffer_cmp(const buffer_t *buf1, const buffer_t *buf2); /* Returns pointer to beginning of buffer data. Current used size of buffer is stored in used_size if it's non-NULL. */ static inline const void * ATTR_NULL(2) buffer_get_data(const buffer_t *buf, size_t *used_size_r) { if (used_size_r != NULL) *used_size_r = buf->used; return buf->data; } /* Returns the current used buffer size. */ static inline size_t ATTR_PURE buffer_get_used_size(const buffer_t *buf) { return buf->used; } /* Crash if buffer was allocated from data stack and stack frame has changed. This can be used as an assert-like check to verify that it's valid to increase the buffer size here, instead of crashing only randomly when the buffer needs to be increased. */ void buffer_verify_pool(buffer_t *buf); /* This will truncate your byte buffer to contain at most given number of bits. 1 bits: 01 00000001 2 bits: 03 00000011 3 bits: 07 00000111 4 bits: 0f 00001111 5 bits: 1f 00011111 6 bits: 3f 00111111 7 bits: 7f 01111111 8 bits: ff 11111111 9 bits: 01ff 0000000111111111 10 bits: 03ff 0000001111111111 11 bits: 07ff 0000011111111111 12 bits: 0fff 0000111111111111 13 bits: 1fff 0001111111111111 14 bits: 3fff 0011111111111111 15 bits: 7fff 0111111111111111 16 bits: ffff 1111111111111111 and so forth */ void buffer_truncate_rshift_bits(buffer_t *buf, size_t bits); enum buffer_append_result { /* Stream reached EOF successfully */ BUFFER_APPEND_OK = 0, /* Error was encountered */ BUFFER_APPEND_READ_ERROR = -1, /* Stream is non-blocking, call again later */ BUFFER_APPEND_READ_MORE = -2, /* Stream was consumed up to max_read_size */ BUFFER_APPEND_READ_MAX_SIZE = -3, }; /* Attempt to fully read a stream. Since this can be a network stream, it can return BUFFER_APPEND_READ_MORE, which means you need to call this function again. It is caller's responsibility to keep track of max_read_size in case more reading is needed. */ enum buffer_append_result buffer_append_full_istream(buffer_t *buf, struct istream *is, size_t max_read_size, const char **error_r); /* Attempt to fully read a file. BUFFER_APPEND_READ_MORE is never returned. */ enum buffer_append_result buffer_append_full_file(buffer_t *buf, const char *file, size_t max_read_size, const char **error_r); #endif dovecot-2.3.21.1/src/lib/rand.c0000644000000000000000000000640114656633576012743 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "randgen.h" #ifdef HAVE_ARC4RANDOM #ifdef HAVE_LIBBSD #include #endif uint32_t i_rand(void) { return arc4random(); } uint32_t i_rand_limit(uint32_t upper_bound) { i_assert(upper_bound > 0); return arc4random_uniform(upper_bound); } #else uint32_t i_rand(void) { uint32_t value; random_fill(&value, sizeof(value)); return value; } /* * The following generates a random number in the range [0, upper_bound) * with each possible value having equal probability of occurring. * * This algorithm is not original, but it is dense enough that a detailed * explanation is in order. * * The big problem is that we want a uniformly random values. If one were * to do `i_rand() % upper_bound`, the result probability distribution would * depend on the value of the upper bound. When the upper bound is a power * of 2, the distribution is uniform. If it is not a power of 2, the * distribution is skewed. * * The naive modulo approach breaks down because the division effectively * splits the whole range of input values into a number of fixed sized * "buckets", but with non-power-of-2 bound the last bucket is not the full * size. * * To fix this bias, we reduce the input range such that the remaining * values can be split exactly into equal sized buckets. * * For example, let's assume that i_rand() produces a uint8_t to simplify * the math, and that we want a random number [0, 9] - in other words, * upper_bound == 10. * * `i_rand() % 10` makes buckets 10 numbers wide, but the last bucket is only * 6 numbers wide (250..255). Therefore, 0..5 will occur more frequently * than 6..9. * * If we reduce the input range to [0, 250), the result of the mod 10 will * be uniform. Interestingly, the same can be accomplished if we reduce the * input range to [6, 255]. * * This minimum value can be calculated as: 256 % 10 = 6. * * Or more generically: (UINT32_MAX + 1) % upper_bound. * * Then, we can pick random numbers until we get one that is >= this * minimum. Once we have it, we can simply mod it by the limit to get our * answer. * * For our example of modding by 10, we pick random numbers until they are * greater than or equal to 6. Once we have one, we have a value in the * range [6, 255] which when modded by 10 yields uniformly distributed * values [0, 9]. * * There are two things to consider while implementing this algorithm: * * 1. Division by 0: Getting called with a 0 upper bound doesn't make sense, * therefore we simply assert that the passed in bound is non-zero. * * 2. 32-bit performance: The above expression to calculate the minimum * value requires 64-bit division. This generally isn't a problem on * 64-bit systems, but 32-bit systems often end up calling a software * implementation (e.g., `__umoddi3`). This is undesirable. * * Therefore, we rewrite the expression as: * * ~(upper_bound - 1) % upper_bound * * This is harder to understand, but it is 100% equivalent. */ uint32_t i_rand_limit(uint32_t upper_bound) { i_assert(upper_bound > 0); uint32_t val; uint32_t min = UNSIGNED_MINUS(upper_bound) % upper_bound; while((val = i_rand()) < min); return val % upper_bound; } #endif dovecot-2.3.21.1/src/lib/istream.h0000644000000000000000000003131114656633576013466 00000000000000#ifndef ISTREAM_H #define ISTREAM_H /* Note that some systems (Solaris) may use a macro to redefine struct stat */ #include struct ioloop; struct istream { uoff_t v_offset; /* Commonly used errors: ENOENT - File/object doesn't exist. EPIPE - Stream ended unexpectedly (or i_stream_close() was called). ESPIPE - i_stream_seek() was used on a stream that can't be seeked. ENOBUFS - i_stream_read_next_line() was used for a too long line. EIO - Internal error. Retrying may work, but it may also be because of a misconfiguration. EINVAL - Stream is corrupted. If stream_errno != 0, eof==TRUE as well. */ int stream_errno; bool mmaped:1; /* be careful when copying data */ bool blocking:1; /* read() shouldn't return 0 */ bool closed:1; bool readable_fd:1; /* fd can be read directly if necessary (for sendfile()) */ bool seekable:1; /* we can seek() backwards */ /* read() has reached to end of file (but there may still be data available in buffer) or stream_errno != 0 */ bool eof:1; struct istream_private *real_stream; }; typedef void istream_callback_t(void *context); struct istream *i_stream_create_fd(int fd, size_t max_buffer_size); /* The fd is set to -1 immediately to avoid accidentally closing it twice. */ struct istream *i_stream_create_fd_autoclose(int *fd, size_t max_buffer_size); /* Open the given path only when something is actually tried to be read from the stream. */ struct istream *i_stream_create_file(const char *path, size_t max_buffer_size); /* Create an input stream using the provided data block. That data block must remain allocated during the full lifetime of the stream. */ struct istream *i_stream_create_from_data(const void *data, size_t size); #define i_stream_create_from_buffer(buf) \ i_stream_create_from_data((buf)->data, (buf)->used) #define i_stream_create_from_string(str) \ i_stream_create_from_data(str_data(str), str_len(str)) /* Create an input stream using a copy of the provided data block. The provided data block may be freed at any time. The copy is freed when the stream is destroyed. */ struct istream * i_stream_create_copy_from_data(const void *data, size_t size); #define i_stream_create_copy_from_buffer(buf) \ i_stream_create_copy_from_data((buf)->data, (buf)->used) #define i_stream_create_copy_from_string(str) \ i_stream_create_copy_from_data(str_data(str), str_len(str)) struct istream *i_stream_create_limit(struct istream *input, uoff_t v_size); struct istream *i_stream_create_range(struct istream *input, uoff_t v_offset, uoff_t v_size); struct istream *i_stream_create_error(int stream_errno); struct istream * i_stream_create_error_str(int stream_errno, const char *fmt, ...) ATTR_FORMAT(2, 3); /* Set name (e.g. path) for input stream. */ void i_stream_set_name(struct istream *stream, const char *name); /* Get input stream's name. If stream itself doesn't have a name, it looks up further into stream's parents until one of them has a name. Returns "" if stream has no name. */ const char *i_stream_get_name(struct istream *stream); /* Close this stream (but not its parents) and unreference it. */ void i_stream_destroy(struct istream **stream); /* Reference counting. References start from 1, so calling i_stream_unref() destroys the stream if i_stream_ref() is never used. */ void i_stream_ref(struct istream *stream); /* Unreferences the stream and sets stream pointer to NULL. */ void i_stream_unref(struct istream **stream); /* Call the given callback function when stream is destroyed. */ void i_stream_add_destroy_callback(struct istream *stream, istream_callback_t *callback, void *context) ATTR_NULL(3); #define i_stream_add_destroy_callback(stream, callback, context) \ i_stream_add_destroy_callback(stream - \ CALLBACK_TYPECHECK(callback, void (*)(typeof(context))), \ (istream_callback_t *)callback, context) /* Remove the destroy callback. */ void i_stream_remove_destroy_callback(struct istream *stream, void (*callback)()); /* Return file descriptor for stream, or -1 if none is available. */ int i_stream_get_fd(struct istream *stream); /* Copy the file descriptor from source istream to destination istream. The readable_fd is preserved. Assert-crashes if source doesn't have a file descriptor. */ void i_stream_copy_fd(struct istream *dest, struct istream *source); /* Returns error string for the last error. It also returns "EOF" in case there is no error, but eof is set. Otherwise it returns "". */ const char *i_stream_get_error(struct istream *stream); /* Returns human-readable reason for why istream was disconnected. The output is either "Connection closed" for clean disconnections or "Connection closed: " for unclean disconnections. This is an alternative to i_stream_get_error(), which is preferred to be used when logging errors about client connections. */ const char *i_stream_get_disconnect_reason(struct istream *stream); /* Mark the stream and all of its parent streams closed. Any reads after this will return -1. The data already read can still be used. */ void i_stream_close(struct istream *stream); /* Sync the stream with the underlying backend, ie. if a file has been modified, flush any cached data. */ void i_stream_sync(struct istream *stream); /* Change the initial size for stream's input buffer. This basically just grows the read buffer size from the default. This function has no effect unless it's called before reading anything. */ void i_stream_set_init_buffer_size(struct istream *stream, size_t size); /* Change the maximum size for stream's input buffer to grow. Useful only for buffered streams (currently only file). This changes also all the parent streams' max buffer size. */ void i_stream_set_max_buffer_size(struct istream *stream, size_t max_size); /* Returns the current max. buffer size for the stream. This function also goes through all of the parent streams and returns the highest seen max buffer size. This is needed because some streams (e.g. istream-chain) change their max buffer size dynamically. */ size_t i_stream_get_max_buffer_size(struct istream *stream); /* Enable/disable i_stream[_read]_next_line() returning the last line if it doesn't end with LF. */ void i_stream_set_return_partial_line(struct istream *stream, bool set); /* Change whether buffers are allocated persistently (default=TRUE). When not, the memory usage is minimized by freeing the stream's buffers whenever they become empty. */ void i_stream_set_persistent_buffers(struct istream *stream, bool set); /* Set the istream blocking or nonblocking, including its parent streams. If any of the istreams have an fd, its O_NONBLOCK flag is changed. */ void i_stream_set_blocking(struct istream *stream, bool blocking); /* Returns number of bytes read if read was ok, 0 if stream is non-blocking and no more data is available, -1 if EOF or error, -2 if the input buffer is full. If <=0 is returned, pointers to existing data returned by the previous i_stream_get_data() will stay valid, although calling it again may return a different pointer. The pointers to old data are invalidated again when return value is >0. */ ssize_t i_stream_read(struct istream *stream); /* Skip forward a number of bytes. Never fails, the next read tells if it was successful. */ void i_stream_skip(struct istream *stream, uoff_t count); /* Seek to specified position from beginning of file. Never fails, the next read tells if it was successful. This works only for files, others will set stream_errno=ESPIPE. */ void i_stream_seek(struct istream *stream, uoff_t v_offset); /* Like i_stream_seek(), but also giving a hint that after reading some data we could be seeking back to this mark or somewhere after it. If input stream's implementation is slow in seeking backwards, it can use this hint to cache some of the data in memory. */ void i_stream_seek_mark(struct istream *stream, uoff_t v_offset); /* Returns 0 if ok, -1 if error. As the underlying stream may not be a file, only some of the fields might be set, others would be zero. st_size is always set, and if it's not known, it's -1. If exact=FALSE, the stream may not return exactly correct values, but the returned values can be compared to see if anything had changed (eg. in compressed stream st_size could be compressed size) */ int i_stream_stat(struct istream *stream, bool exact, const struct stat **st_r); /* Similar to i_stream_stat() call. Returns 1 if size was successfully set, 0 if size is unknown, -1 if error. */ int i_stream_get_size(struct istream *stream, bool exact, uoff_t *size_r); /* Returns TRUE if there are any bytes left to be read or in buffer. */ bool i_stream_have_bytes_left(struct istream *stream); /* Returns TRUE if there are no bytes currently buffered and i_stream_read() returns EOF/error. Usually it's enough to check for stream->eof instead of calling this function. Note that if the stream isn't at EOF, this function has now read data into the stream buffer. */ bool i_stream_read_eof(struct istream *stream); /* Returns the absolute offset of the stream. This is the stream's current v_offset + the parent's absolute offset when the stream was created. */ uoff_t i_stream_get_absolute_offset(struct istream *stream); /* Gets the next line from stream and returns it, or NULL if more data is needed to make a full line. i_stream_set_return_partial_line() specifies if the last line should be returned if it doesn't end with LF. */ char *i_stream_next_line(struct istream *stream); /* Like i_stream_next_line(), but reads for more data if needed. Returns NULL if more data is needed or error occurred. If the input buffer gets full, stream_errno is set to ENOBUFS. */ char *i_stream_read_next_line(struct istream *stream); /* Returns TRUE if the last line read with i_stream_next_line() ended with CRLF (instead of LF). */ bool i_stream_last_line_crlf(struct istream *stream); /* Returns pointer to beginning of read data. */ const unsigned char *i_stream_get_data(struct istream *stream, size_t *size_r); size_t i_stream_get_data_size(struct istream *stream); /* Like i_stream_get_data(), but returns non-const data. This only works with buffered streams (currently only file), others return NULL. */ unsigned char *i_stream_get_modifiable_data(struct istream *stream, size_t *size_r); /* Like i_stream_get_data(), but read more when needed. Returns 1 if more than threshold bytes are available, 0 if as much or less, -1 if error or EOF with no bytes read that weren't already in buffer, or -2 if stream's input buffer is full. */ int i_stream_read_data(struct istream *stream, const unsigned char **data_r, size_t *size_r, size_t threshold); /* Like i_stream_get_data(), but read more when needed. Returns 1 if at least the wanted number of bytes are available, 0 if less, -1 if error or EOF with no bytes read that weren't already in buffer, or -2 if stream's input buffer is full. */ static inline int i_stream_read_bytes(struct istream *stream, const unsigned char **data_r, size_t *size_r, size_t wanted) { i_assert(wanted > 0); return i_stream_read_data(stream, data_r, size_r, wanted - 1); } /* Short-hand for just requesting more data (i.e. even one byte) */ static inline int i_stream_read_more(struct istream *stream, const unsigned char **data_r, size_t *size_r) { int ret = i_stream_read_bytes(stream, data_r, size_r, 1); i_assert(ret != -2); /* stream must have space for at least 1 byte */ return ret; } /* Like i_stream_read_more(), but tries to avoid buffering more than the indicated limit. Use this function to prevent growing the stream buffer beyond what the application is willing to read immediately. Since this function doesn't fully prevent buffering beyond the limit, the amount of data actually buffered can exceed the limit. However, *size_r will allways be <= limit to avoid confusion. */ int i_stream_read_limited(struct istream *stream, const unsigned char **data_r, size_t *size_r, size_t limit); /* Return the timestamp when istream last successfully read something. The timestamp is 0 if nothing has ever been read. */ void i_stream_get_last_read_time(struct istream *stream, struct timeval *tv_r); /* Append external data to input stream. Returns TRUE if successful, FALSE if there is not enough space in the stream. */ bool i_stream_add_data(struct istream *stream, const unsigned char *data, size_t size); void i_stream_set_input_pending(struct istream *stream, bool pending); /* If there are any I/O loop items associated with the stream, move all of them to provided/current ioloop. */ void i_stream_switch_ioloop_to(struct istream *stream, struct ioloop *ioloop); void i_stream_switch_ioloop(struct istream *stream); #endif dovecot-2.3.21.1/src/lib/ioloop-poll.c0000644000000000000000000001405514656633576014270 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ /* @UNSAFE: whole file */ #include "lib.h" #include "ioloop-private.h" #ifdef IOLOOP_POLL #include #include struct ioloop_handler_context { unsigned int fds_count, fds_pos; struct pollfd *fds; unsigned int idx_count; int *fd_index; }; void io_loop_handler_init(struct ioloop *ioloop, unsigned int initial_fd_count) { struct ioloop_handler_context *ctx; ioloop->handler_context = ctx = i_new(struct ioloop_handler_context, 1); ctx->fds_count = initial_fd_count; ctx->fds = i_new(struct pollfd, ctx->fds_count); ctx->idx_count = initial_fd_count; ctx->fd_index = i_new(int, ctx->idx_count); memset(ctx->fd_index, 0xff, sizeof(int) * ctx->idx_count); } void io_loop_handler_deinit(struct ioloop *ioloop) { i_free(ioloop->handler_context->fds); i_free(ioloop->handler_context->fd_index); i_free(ioloop->handler_context); } #define IO_POLL_ERROR (POLLERR | POLLHUP | POLLNVAL) #define IO_POLL_INPUT (POLLIN | POLLPRI | IO_POLL_ERROR) #define IO_POLL_OUTPUT (POLLOUT | IO_POLL_ERROR) void io_loop_handle_add(struct io_file *io) { struct ioloop_handler_context *ctx = io->io.ioloop->handler_context; enum io_condition condition = io->io.condition; unsigned int old_count; int index, old_events, fd = io->fd; if ((unsigned int)fd >= ctx->idx_count) { /* grow the fd -> index array */ old_count = ctx->idx_count; ctx->idx_count = nearest_power((unsigned int) fd+1); ctx->fd_index = i_realloc_type(ctx->fd_index, int, old_count, ctx->idx_count); memset(ctx->fd_index + old_count, 0xff, sizeof(int) * (ctx->idx_count-old_count)); } if (ctx->fds_pos >= ctx->fds_count) { /* grow the fd array */ old_count = ctx->fds_count; ctx->fds_count = nearest_power(ctx->fds_count+1); ctx->fds = i_realloc_type(ctx->fds, struct pollfd, old_count, ctx->fds_count); } if (ctx->fd_index[fd] != -1) { /* update existing pollfd */ index = ctx->fd_index[fd]; } else { /* add new pollfd */ index = ctx->fds_pos++; ctx->fd_index[fd] = index; ctx->fds[index].fd = fd; ctx->fds[index].events = 0; ctx->fds[index].revents = 0; } old_events = ctx->fds[index].events; if ((condition & IO_READ) != 0) ctx->fds[index].events |= IO_POLL_INPUT; if ((condition & IO_WRITE) != 0) ctx->fds[index].events |= IO_POLL_OUTPUT; if ((condition & IO_ERROR) != 0) ctx->fds[index].events |= IO_POLL_ERROR; i_assert(ctx->fds[index].events != old_events); } void io_loop_handle_remove(struct io_file *io, bool closed ATTR_UNUSED) { struct ioloop_handler_context *ctx = io->io.ioloop->handler_context; enum io_condition condition = io->io.condition; int index, fd = io->fd; index = ctx->fd_index[fd]; i_assert(index >= 0 && (unsigned int) index < ctx->fds_count); #ifdef DEBUG if (!closed) { /* io_remove() is required to be called before fd is closed. This is required by epoll/kqueue, but since poll is more commonly used while developing, this check here should catch the error early enough not to cause problems for kqueue users. */ if (fcntl(io->fd, F_GETFD, 0) < 0) { if (errno == EBADF) i_panic("io_remove(%d) called too late", io->fd); else i_error("fcntl(%d, F_GETFD) failed: %m", io->fd); } } #endif i_free(io); if ((condition & IO_READ) != 0) { ctx->fds[index].events &= ENUM_NEGATE(POLLIN | POLLPRI); ctx->fds[index].revents &= ENUM_NEGATE(POLLIN | POLLPRI); } if ((condition & IO_WRITE) != 0) { ctx->fds[index].events &= ENUM_NEGATE(POLLOUT); ctx->fds[index].revents &= ENUM_NEGATE(POLLOUT); } if ((ctx->fds[index].events & (POLLIN|POLLOUT)) == 0) { /* remove the whole pollfd */ ctx->fd_index[ctx->fds[index].fd] = -1; if (--ctx->fds_pos == (unsigned int) index) return; /* removing last one */ /* move the last pollfd over the removed one */ ctx->fds[index] = ctx->fds[ctx->fds_pos]; ctx->fd_index[ctx->fds[index].fd] = index; } } void io_loop_handler_run_internal(struct ioloop *ioloop) { struct ioloop_handler_context *ctx = ioloop->handler_context; struct pollfd *pollfd; struct timeval tv; struct io_file *io; int msecs, ret; bool call; /* get the time left for next timeout task */ msecs = io_loop_run_get_wait_time(ioloop, &tv); #ifdef _AIX if (msecs > 1000) { /* AIX seems to check IO_POLL_ERRORs only at the beginning of the poll() call, not during it. keep timeouts short enough so that we'll notice them pretty quickly. */ msecs = 1000; } #endif ret = poll(ctx->fds, ctx->fds_pos, msecs); if (ret < 0 && errno != EINTR) i_fatal("poll(): %m"); /* execute timeout handlers */ io_loop_handle_timeouts(ioloop); if (ret <= 0 || !ioloop->running) { /* no I/O events */ return; } io = ioloop->io_files; for (; io != NULL && ret > 0; io = ioloop->next_io_file) { ioloop->next_io_file = io->next; if (io->fd == -1) { /* io_add_istream() without fd */ continue; } pollfd = &ctx->fds[ctx->fd_index[io->fd]]; if (pollfd->revents != 0) { if (pollfd->revents & POLLNVAL) { i_error("invalid I/O fd %d, callback %p", io->fd, (void *) io->io.callback); pollfd->events = 0; pollfd->revents = 0; call = TRUE; } else if ((io->io.condition & (IO_READ|IO_WRITE)) == (IO_READ|IO_WRITE)) { call = TRUE; pollfd->revents = 0; } else if ((io->io.condition & IO_READ) != 0) { call = (pollfd->revents & IO_POLL_INPUT) != 0; pollfd->revents &= ENUM_NEGATE(IO_POLL_INPUT); } else if ((io->io.condition & IO_WRITE) != 0) { call = (pollfd->revents & IO_POLL_OUTPUT) != 0; pollfd->revents &= ENUM_NEGATE(IO_POLL_OUTPUT); } else if ((io->io.condition & IO_ERROR) != 0) { call = (pollfd->revents & IO_POLL_ERROR) != 0; pollfd->revents &= ENUM_NEGATE(IO_POLL_ERROR); } else { call = FALSE; } if (pollfd->revents == 0) ret--; if (call) { io_loop_call_io(&io->io); if (!ioloop->running) return; } } } } #endif dovecot-2.3.21.1/src/lib/env-util.c0000644000000000000000000000561614656633576013571 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "env-util.h" #ifdef __APPLE__ # include #endif struct env_backup { pool_t pool; const char **strings; }; void env_put(const char *name, const char *value) { i_assert(strchr(name, '=') == NULL); if (setenv(name, value, 1) != 0) i_fatal("setenv(%s, %s) failed: %m", name, value); } void env_put_array(const char *const *envs) { for (unsigned int i = 0; envs[i] != NULL; i++) { const char *value = strchr(envs[i], '='); i_assert(value != NULL); T_BEGIN { const char *name = t_strdup_until(envs[i], value++); env_put(name, value); } T_END; } } void env_remove(const char *name) { if (unsetenv(name) < 0) i_fatal("unsetenv(%s) failed: %m", name); } void env_clean(void) { #ifdef HAVE_CLEARENV if (clearenv() < 0) i_fatal("clearenv() failed"); #else char ***environ_p = env_get_environ_p(); /* Try to clear the environment. a) environ = NULL crashes on OS X. b) *environ = NULL doesn't work on FreeBSD 7.0. c) environ = emptyenv doesn't work on Haiku OS d) environ = calloc() should work everywhere */ *environ_p = calloc(1, sizeof(**environ_p)); #endif } static void env_clean_except_real(const char *const preserve_envs[]) { ARRAY_TYPE(const_string) copy; const char *value, *const *envp; unsigned int i, count; t_array_init(©, 16); for (i = 0; preserve_envs[i] != NULL; i++) { const char *key = preserve_envs[i]; value = getenv(key); if (value != NULL) { key = t_strdup(key); value = t_strdup(value); array_push_back(©, &key); array_push_back(©, &value); } } /* Note that if the original environment was set with env_put(), the environment strings will be invalid after env_clean(). That's why we t_strdup() them above. */ env_clean(); envp = array_get(©, &count); for (i = 0; i < count; i += 2) env_put(envp[i], envp[i+1]); } void env_clean_except(const char *const preserve_envs[]) { T_BEGIN { env_clean_except_real(preserve_envs); } T_END; } struct env_backup *env_backup_save(void) { char **environ = *env_get_environ_p(); struct env_backup *env; unsigned int i, count; pool_t pool; i_assert(environ != NULL); for (count = 0; environ[count] != NULL; count++) ; pool = pool_alloconly_create("saved environment", 4096); env = p_new(pool, struct env_backup, 1); env->pool = pool; env->strings = p_new(pool, const char *, count + 1); for (i = 0; i < count; i++) env->strings[i] = p_strdup(pool, environ[i]); return env; } void env_backup_restore(struct env_backup *env) { env_clean(); env_put_array(env->strings); } void env_backup_free(struct env_backup **_env) { struct env_backup *env = *_env; *_env = NULL; pool_unref(&env->pool); } char ***env_get_environ_p(void) { #ifdef __APPLE__ return _NSGetEnviron(); #else extern char **environ; return &environ; #endif } dovecot-2.3.21.1/src/lib/module-dir.h0000644000000000000000000000515614656633576014073 00000000000000#ifndef MODULE_DIR_H #define MODULE_DIR_H struct module_dir_load_settings { /* If abi_version is non-NULL and the module contains a version symbol, fail the load if they're different. In both strings ignore anything after the first '(' character, so the version can be e.g.: 2.2.ABIv1(2.2.15) */ const char *abi_version; /* Binary name used for checking if plugin is tried to be loaded for wrong binary. */ const char *binary_name; /* Setting name used in plugin dependency error message */ const char *setting_name; /* If non-NULL, load only modules where filter_callback returns TRUE */ bool (*filter_callback)(const char *name, void *context); void *filter_context; /* Require all plugins to have _init() function */ bool require_init_funcs:1; /* Enable debug logging */ bool debug:1; /* If dlopen() fails for some modules, silently skip it. */ bool ignore_dlopen_errors:1; /* Don't fail if some specified modules weren't found */ bool ignore_missing:1; }; struct module { char *path, *name; void *handle; void (*init)(struct module *module); void (*deinit)(void); bool initialized:1; struct module *next; }; /* Load modules in given directory. module_names is a space separated list of module names to load. */ struct module *module_dir_load(const char *dir, const char *module_names, const struct module_dir_load_settings *set) ATTR_NULL(2); /* Load modules that aren't already loaded. */ struct module * module_dir_load_missing(struct module *old_modules, const char *dir, const char *module_names, const struct module_dir_load_settings *set) ATTR_NULL(1, 3); /* Load modules that aren't already loaded. */ int module_dir_try_load_missing(struct module **modules, const char *dir, const char *module_names, const struct module_dir_load_settings *set, const char **error_r) ATTR_NULL(1, 3); /* Call init() in all modules */ void module_dir_init(struct module *modules); /* Call deinit() in all modules and mark them NULL so module_dir_unload() won't do it again. */ void module_dir_deinit(struct module *modules); /* Unload all modules */ void module_dir_unload(struct module **modules); /* Find a module by name. */ struct module *module_dir_find(struct module *modules, const char *name); void *module_get_symbol(struct module *module, const char *symbol); void *module_get_symbol_quiet(struct module *module, const char *symbol); /* Returns module's base name from the filename. */ const char *module_file_get_name(const char *fname); /* Returns module's name without "_plugin" suffix. */ const char *module_get_plugin_name(struct module *module); #endif dovecot-2.3.21.1/src/lib/event-filter-parser.h0000644000000000000000000000673214656633635015725 00000000000000/* A Bison parser, made by GNU Bison 3.7.5. */ /* Bison interface for Yacc-like parsers in C Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation, Inc. 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 3 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 . */ /* As a special exception, you may create a larger work that contains part or all of the Bison parser skeleton and distribute that work under terms of your choice, so long as that work isn't itself a parser generator using the skeleton or a modified version thereof as a parser skeleton. Alternatively, if you modify or redistribute the parser skeleton itself, you may (at your option) remove this special exception, which will cause the skeleton and the resulting Bison output files to be licensed under the GNU General Public License without this special exception. This special exception was added by the Free Software Foundation in version 2.2 of Bison. */ /* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual, especially those whose name start with YY_ or yy_. They are private implementation details that can be changed or removed. */ #ifndef YY_EVENT_FILTER_PARSER_EVENT_FILTER_PARSER_H_INCLUDED # define YY_EVENT_FILTER_PARSER_EVENT_FILTER_PARSER_H_INCLUDED /* Debug traces. */ #ifndef EVENT_FILTER_PARSER_DEBUG # if defined YYDEBUG #if YYDEBUG # define EVENT_FILTER_PARSER_DEBUG 1 # else # define EVENT_FILTER_PARSER_DEBUG 0 # endif # else /* ! defined YYDEBUG */ # define EVENT_FILTER_PARSER_DEBUG 0 # endif /* ! defined YYDEBUG */ #endif /* ! defined EVENT_FILTER_PARSER_DEBUG */ #if EVENT_FILTER_PARSER_DEBUG extern int event_filter_parser_debug; #endif /* Token kinds. */ #ifndef EVENT_FILTER_PARSER_TOKENTYPE # define EVENT_FILTER_PARSER_TOKENTYPE enum event_filter_parser_tokentype { EVENT_FILTER_PARSER_EMPTY = -2, EVENT_FILTER_PARSER_EOF = 0, /* "end of file" */ EVENT_FILTER_PARSER_error = 256, /* error */ EVENT_FILTER_PARSER_UNDEF = 257, /* "invalid token" */ TOKEN = 258, /* TOKEN */ STRING = 259, /* STRING */ AND = 260, /* AND */ OR = 261, /* OR */ NOT = 262 /* NOT */ }; typedef enum event_filter_parser_tokentype event_filter_parser_token_kind_t; #endif /* Value type. */ #if ! defined EVENT_FILTER_PARSER_STYPE && ! defined EVENT_FILTER_PARSER_STYPE_IS_DECLARED union EVENT_FILTER_PARSER_STYPE { #line 139 "event-filter-parser.y" const char *str; enum event_filter_node_op op; struct event_filter_node *node; #line 85 "event-filter-parser.h" }; typedef union EVENT_FILTER_PARSER_STYPE EVENT_FILTER_PARSER_STYPE; # define EVENT_FILTER_PARSER_STYPE_IS_TRIVIAL 1 # define EVENT_FILTER_PARSER_STYPE_IS_DECLARED 1 #endif int event_filter_parser_parse (struct event_filter_parser_state *state); #endif /* !YY_EVENT_FILTER_PARSER_EVENT_FILTER_PARSER_H_INCLUDED */ dovecot-2.3.21.1/src/lib/istream-seekable.c0000644000000000000000000003626014656633576015242 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "str.h" #include "memarea.h" #include "read-full.h" #include "write-full.h" #include "safe-mkstemp.h" #include "istream-private.h" #include "istream-concat.h" #include "istream-seekable.h" #include #define BUF_INITIAL_SIZE (1024*32) struct seekable_istream { struct istream_private istream; char *temp_path; uoff_t write_peak; uoff_t size; size_t buffer_peak; int (*fd_callback)(const char **path_r, void *context); void *context; struct istream **input, *cur_input; struct istream *fd_input; unsigned int cur_idx; int fd; bool free_context; }; static void i_stream_seekable_close(struct iostream_private *stream, bool close_parent ATTR_UNUSED) { struct seekable_istream *sstream = container_of(stream, struct seekable_istream, istream.iostream); sstream->fd = -1; i_stream_close(sstream->fd_input); } static void unref_streams(struct seekable_istream *sstream) { unsigned int i; for (i = 0; sstream->input[i] != NULL; i++) i_stream_unref(&sstream->input[i]); } static void i_stream_seekable_destroy(struct iostream_private *stream) { struct seekable_istream *sstream = container_of(stream, struct seekable_istream, istream.iostream); i_stream_free_buffer(&sstream->istream); i_stream_unref(&sstream->fd_input); unref_streams(sstream); if (sstream->free_context) i_free(sstream->context); i_free(sstream->temp_path); i_free(sstream->input); } static void i_stream_seekable_set_max_buffer_size(struct iostream_private *stream, size_t max_size) { struct seekable_istream *sstream = container_of(stream, struct seekable_istream, istream.iostream); unsigned int i; sstream->istream.max_buffer_size = max_size; if (sstream->fd_input != NULL) i_stream_set_max_buffer_size(sstream->fd_input, max_size); for (i = 0; sstream->input[i] != NULL; i++) i_stream_set_max_buffer_size(sstream->input[i], max_size); } static int copy_to_temp_file(struct seekable_istream *sstream) { struct istream_private *stream = &sstream->istream; const char *path; const unsigned char *buffer; size_t size; int fd; fd = sstream->fd_callback(&path, sstream->context); if (fd == -1) return -1; /* copy our currently read buffer to it */ i_assert(stream->pos <= sstream->buffer_peak); if (write_full(fd, stream->buffer, sstream->buffer_peak) < 0) { if (!ENOSPACE(errno)) i_error("istream-seekable: write_full(%s) failed: %m", path); i_close_fd(&fd); return -1; } sstream->temp_path = i_strdup(path); sstream->write_peak = sstream->buffer_peak; sstream->fd = fd; sstream->fd_input = i_stream_create_fd_autoclose(&fd, I_MAX(stream->pos, sstream->istream.max_buffer_size)); i_stream_set_name(sstream->fd_input, t_strdup_printf( "(seekable temp-istream for: %s)", i_stream_get_name(&stream->istream))); /* read back the data we just had in our buffer */ for (;;) { buffer = i_stream_get_data(sstream->fd_input, &size); if (size >= stream->pos) break; ssize_t ret; if ((ret = i_stream_read_memarea(sstream->fd_input)) <= 0) { i_assert(ret != 0); i_assert(ret != -2); i_error("istream-seekable: Couldn't read back " "in-memory input %s: %s", i_stream_get_name(&stream->istream), i_stream_get_error(sstream->fd_input)); i_stream_destroy(&sstream->fd_input); sstream->fd = -1; /* autoclosed by fd_input */ return -1; } } /* Set the max buffer size only after we've already read everything into memory. For example with istream-data it's possible that more data exists in buffer than max_buffer_size. */ i_stream_set_max_buffer_size(sstream->fd_input, sstream->istream.max_buffer_size); stream->buffer = buffer; i_stream_free_buffer(&sstream->istream); return 0; } static ssize_t read_more(struct seekable_istream *sstream) { size_t size; ssize_t ret; if (sstream->cur_input == NULL) { sstream->istream.istream.eof = TRUE; return -1; } while ((ret = i_stream_read_memarea(sstream->cur_input)) == -1) { if (sstream->cur_input->stream_errno != 0) { io_stream_set_error(&sstream->istream.iostream, "read(%s) failed: %s", i_stream_get_name(sstream->cur_input), i_stream_get_error(sstream->cur_input)); sstream->istream.istream.eof = TRUE; sstream->istream.istream.stream_errno = sstream->cur_input->stream_errno; return -1; } /* go to next stream */ sstream->cur_input = sstream->input[sstream->cur_idx++]; if (sstream->cur_input == NULL) { /* last one, EOF */ sstream->size = sstream->istream.istream.v_offset + (sstream->istream.pos - sstream->istream.skip); sstream->istream.istream.eof = TRUE; /* Now that EOF is reached, the stream can't return 0 anymore. Callers can now use this stream in places that assert that blocking==TRUE. */ sstream->istream.istream.blocking = TRUE; unref_streams(sstream); return -1; } /* see if stream has pending data */ size = i_stream_get_data_size(sstream->cur_input); if (size != 0) return size; } return ret; } static bool read_from_buffer(struct seekable_istream *sstream, ssize_t *ret_r) { struct istream_private *stream = &sstream->istream; const unsigned char *data; size_t size, avail_size; if (stream->pos < sstream->buffer_peak) { /* This could be the first read() or we could have already seeked backwards. */ i_assert(stream->pos == 0 && stream->skip == 0); stream->skip = stream->istream.v_offset; stream->pos = sstream->buffer_peak; size = stream->pos - stream->skip; if (stream->istream.v_offset == sstream->buffer_peak) { /* this could happen after write to temp file failed */ return read_from_buffer(sstream, ret_r); } } else { /* need to read more */ i_assert(stream->pos == sstream->buffer_peak); size = sstream->cur_input == NULL ? 0 : i_stream_get_data_size(sstream->cur_input); if (size == 0) { /* read more to buffer */ *ret_r = read_more(sstream); if (*ret_r == 0 || *ret_r == -1) return TRUE; } /* we should have more now. */ data = i_stream_get_data(sstream->cur_input, &size); i_assert(size > 0); /* change skip to 0 temporarily so i_stream_try_alloc() won't try to compress the buffer. */ size_t old_skip = stream->skip; stream->skip = 0; bool have_space = i_stream_try_alloc(stream, size, &avail_size); stream->skip = old_skip; if (!have_space) return FALSE; if (size > avail_size) size = avail_size; memcpy(stream->w_buffer + stream->pos, data, size); stream->pos += size; sstream->buffer_peak += size; i_stream_skip(sstream->cur_input, size); } *ret_r = size; i_assert(*ret_r > 0); return TRUE; } static int i_stream_seekable_write_failed(struct seekable_istream *sstream) { struct istream_private *stream = &sstream->istream; void *data; size_t old_pos = stream->pos; i_assert(sstream->fd != -1); i_assert(stream->skip == 0); stream->max_buffer_size = SIZE_MAX; stream->pos = 0; data = i_stream_alloc(stream, sstream->write_peak); stream->pos = old_pos; if (pread_full(sstream->fd, data, sstream->write_peak, 0) < 0) { sstream->istream.istream.stream_errno = errno; sstream->istream.istream.eof = TRUE; io_stream_set_error(&sstream->istream.iostream, "istream-seekable: read(%s) failed: %m", sstream->temp_path); return -1; } i_stream_destroy(&sstream->fd_input); sstream->fd = -1; /* autoclosed by fd_input */ i_free_and_null(sstream->temp_path); return 0; } static ssize_t i_stream_seekable_read(struct istream_private *stream) { struct seekable_istream *sstream = container_of(stream, struct seekable_istream, istream); const unsigned char *data; size_t size, pos; ssize_t ret; if (sstream->fd == -1) { if (read_from_buffer(sstream, &ret)) return ret; /* copy everything to temp file and use it as the stream */ if (copy_to_temp_file(sstream) < 0) { stream->max_buffer_size = SIZE_MAX; if (!read_from_buffer(sstream, &ret)) i_unreached(); return ret; } i_assert(sstream->fd != -1); } stream->buffer = CONST_PTR_OFFSET(stream->buffer, stream->skip); stream->pos -= stream->skip; stream->skip = 0; i_assert(stream->istream.v_offset + stream->pos <= sstream->write_peak); if (stream->istream.v_offset + stream->pos == sstream->write_peak) { /* need to read more */ if (sstream->cur_input == NULL || i_stream_get_data_size(sstream->cur_input) == 0) { ret = read_more(sstream); if (ret == -1 || ret == 0) return ret; } /* save to our file */ data = i_stream_get_data(sstream->cur_input, &size); ret = write(sstream->fd, data, size); if (ret <= 0) { if (ret < 0 && !ENOSPACE(errno)) { i_error("istream-seekable: write_full(%s) failed: %m", sstream->temp_path); } if (i_stream_seekable_write_failed(sstream) < 0) return -1; if (!read_from_buffer(sstream, &ret)) i_unreached(); return ret; } i_stream_sync(sstream->fd_input); i_stream_skip(sstream->cur_input, ret); sstream->write_peak += ret; } i_stream_seek(sstream->fd_input, stream->istream.v_offset); ret = i_stream_read_memarea(sstream->fd_input); if (ret <= 0) { stream->istream.eof = sstream->fd_input->eof; stream->istream.stream_errno = sstream->fd_input->stream_errno; } else { ret = -2; } stream->buffer = i_stream_get_data(sstream->fd_input, &pos); stream->pos -= stream->skip; stream->skip = 0; ret = pos > stream->pos ? (ssize_t)(pos - stream->pos) : ret; stream->pos = pos; return ret; } static int i_stream_seekable_stat(struct istream_private *stream, bool exact) { struct seekable_istream *sstream = container_of(stream, struct seekable_istream, istream); const struct stat *st; uoff_t old_offset, len; ssize_t ret; if (sstream->size != UOFF_T_MAX) { /* we've already reached EOF and know the size */ stream->statbuf.st_size = sstream->size; return 0; } /* we want to know the full size of the file, so read until we're finished */ old_offset = stream->istream.v_offset; do { i_stream_skip(&stream->istream, stream->pos - stream->skip); } while ((ret = i_stream_seekable_read(stream)) > 0); if (ret == 0) { i_panic("i_stream_stat() used for non-blocking " "seekable stream %s offset %"PRIuUOFF_T, i_stream_get_name(sstream->cur_input), sstream->cur_input->v_offset); } i_stream_skip(&stream->istream, stream->pos - stream->skip); len = stream->pos; i_stream_seek(&stream->istream, old_offset); unref_streams(sstream); if (stream->istream.stream_errno != 0) return -1; if (sstream->fd_input != NULL) { /* using a file backed buffer, we can use real fstat() */ if (i_stream_stat(sstream->fd_input, exact, &st) < 0) return -1; stream->statbuf = *st; } else { /* buffer is completely in memory */ i_assert(sstream->fd == -1); stream->statbuf.st_size = len; } return 0; } static void i_stream_seekable_seek(struct istream_private *stream, uoff_t v_offset, bool mark) { if (v_offset <= stream->istream.v_offset) { /* seeking backwards */ stream->istream.v_offset = v_offset; stream->skip = stream->pos = 0; } else { /* we can't skip over data we haven't yet read and written to our buffer/temp file */ i_stream_default_seek_nonseekable(stream, v_offset, mark); } } static struct istream_snapshot * i_stream_seekable_snapshot(struct istream_private *stream, struct istream_snapshot *prev_snapshot) { struct seekable_istream *sstream = container_of(stream, struct seekable_istream, istream); if (sstream->fd == -1) { /* still in memory */ if (stream->memarea == NULL) return prev_snapshot; return i_stream_default_snapshot(stream, prev_snapshot); } else { /* using the fd_input stream */ return sstream->fd_input->real_stream-> snapshot(sstream->fd_input->real_stream, prev_snapshot); } } struct istream * i_streams_merge(struct istream *input[], size_t max_buffer_size, int (*fd_callback)(const char **path_r, void *context), void *context) ATTR_NULL(4) { struct seekable_istream *sstream; const unsigned char *data; unsigned int count; size_t size; bool blocking = TRUE; i_assert(max_buffer_size > 0); /* if any of the streams isn't blocking, set ourself also nonblocking */ for (count = 0; input[count] != NULL; count++) { if (!input[count]->blocking) blocking = FALSE; i_stream_ref(input[count]); } i_assert(count != 0); sstream = i_new(struct seekable_istream, 1); sstream->fd_callback = fd_callback; sstream->context = context; sstream->istream.max_buffer_size = max_buffer_size; sstream->fd = -1; sstream->size = UOFF_T_MAX; sstream->input = i_new(struct istream *, count + 1); memcpy(sstream->input, input, sizeof(*input) * count); sstream->cur_input = sstream->input[0]; sstream->istream.iostream.close = i_stream_seekable_close; sstream->istream.iostream.destroy = i_stream_seekable_destroy; sstream->istream.iostream.set_max_buffer_size = i_stream_seekable_set_max_buffer_size; sstream->istream.read = i_stream_seekable_read; sstream->istream.stat = i_stream_seekable_stat; sstream->istream.seek = i_stream_seekable_seek; sstream->istream.snapshot = i_stream_seekable_snapshot; sstream->istream.istream.readable_fd = FALSE; sstream->istream.istream.blocking = blocking; sstream->istream.istream.seekable = TRUE; (void)i_stream_create(&sstream->istream, NULL, -1, 0); /* initialize our buffer from first stream's pending data */ data = i_stream_get_data(sstream->cur_input, &size); if (size > 0) { memcpy(i_stream_alloc(&sstream->istream, size), data, size); sstream->buffer_peak = size; i_stream_skip(sstream->cur_input, size); } return &sstream->istream.istream; } static bool inputs_are_seekable(struct istream *input[]) { unsigned int count; for (count = 0; input[count] != NULL; count++) { if (!input[count]->seekable) return FALSE; } return TRUE; } struct istream * i_stream_create_seekable(struct istream *input[], size_t max_buffer_size, int (*fd_callback)(const char **path_r, void *context), void *context) { i_assert(max_buffer_size > 0); /* If all input streams are seekable, use concat istream instead */ if (inputs_are_seekable(input)) return i_stream_create_concat(input); return i_streams_merge(input, max_buffer_size, fd_callback, context); } static int seekable_fd_callback(const char **path_r, void *context) { char *temp_path_prefix = context; string_t *path; int fd; path = t_str_new(128); str_append(path, temp_path_prefix); fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1); if (fd == -1) { i_error("istream-seekable: safe_mkstemp(%s) failed: %m", str_c(path)); return -1; } /* we just want the fd, unlink it */ if (i_unlink(str_c(path)) < 0) { /* shouldn't happen.. */ i_close_fd(&fd); return -1; } *path_r = str_c(path); return fd; } struct istream * i_stream_create_seekable_path(struct istream *input[], size_t max_buffer_size, const char *temp_path_prefix) { struct seekable_istream *sstream; struct istream *stream; i_assert(temp_path_prefix != NULL); i_assert(max_buffer_size > 0); if (inputs_are_seekable(input)) return i_stream_create_concat(input); stream = i_stream_create_seekable(input, max_buffer_size, seekable_fd_callback, i_strdup(temp_path_prefix)); sstream = container_of(stream->real_stream, struct seekable_istream, istream); sstream->free_context = TRUE; return stream; } dovecot-2.3.21.1/src/lib/hmac-cram-md5.h0000644000000000000000000000052114656633576014334 00000000000000#ifndef HMAC_CRAM_MD5_H #define HMAC_CRAM_MD5_H #include "hmac.h" #define CRAM_MD5_CONTEXTLEN 32 void hmac_md5_get_cram_context(struct hmac_context *ctx, unsigned char context_digest[CRAM_MD5_CONTEXTLEN]); void hmac_md5_set_cram_context(struct hmac_context *ctx, const unsigned char context_digest[CRAM_MD5_CONTEXTLEN]); #endif dovecot-2.3.21.1/src/lib/test-bits.c0000644000000000000000000002114314656633576013735 00000000000000/* Copyright (c) 2001-2018 Dovecot authors, see the included COPYING file */ /* Unit tests for bit twiddles library */ #include "test-lib.h" #include static void test_bits_unsigned_minus(void) { test_begin("bits_unsigned_minus()"); // 32 bit test_assert(UNSIGNED_MINUS(0x00000000U) == 0x00000000U); test_assert(UNSIGNED_MINUS(0x00000001U) == 0xffffffffU); test_assert(UNSIGNED_MINUS(0x00000002U) == 0xfffffffeU); test_assert(UNSIGNED_MINUS(0x00000003U) == 0xfffffffdU); //.. test_assert(UNSIGNED_MINUS(0x7fffffffU) == 0x80000001U); test_assert(UNSIGNED_MINUS(0x80000000U) == 0x80000000U); test_assert(UNSIGNED_MINUS(0x80000001U) == 0x7fffffffU); //.. test_assert(UNSIGNED_MINUS(0xffffffffU) == 0x00000001U); test_assert(UNSIGNED_MINUS(0xfffffffeU) == 0x00000002U); test_assert(UNSIGNED_MINUS(0xfffffffdU) == 0x00000003U); // 64 bit test_assert(UNSIGNED_MINUS(0x0000000000000000ULL) == 0x0000000000000000ULL); test_assert(UNSIGNED_MINUS(0x0000000000000001ULL) == 0xffffffffffffffffULL); test_assert(UNSIGNED_MINUS(0x0000000000000002ULL) == 0xfffffffffffffffeULL); test_assert(UNSIGNED_MINUS(0x0000000000000003ULL) == 0xfffffffffffffffdULL); //.. test_assert(UNSIGNED_MINUS(0x7fffffffffffffffULL) == 0x8000000000000001ULL); test_assert(UNSIGNED_MINUS(0x8000000000000000ULL) == 0x8000000000000000ULL); test_assert(UNSIGNED_MINUS(0x8000000000000001ULL) == 0x7fffffffffffffffULL); //.. test_assert(UNSIGNED_MINUS(0xffffffffffffffffULL) == 0x0000000000000001ULL); test_assert(UNSIGNED_MINUS(0xfffffffffffffffeULL) == 0x0000000000000002ULL); test_assert(UNSIGNED_MINUS(0xfffffffffffffffdULL) == 0x0000000000000003ULL); test_end(); } /* nearest_power(0) = error bits_requiredXX(0) = 0 nearest_power(1) = 1 = 1<<0 bits_requiredXX(1) = 1 nearest_power(2) = 2 = 1<<1 bits_requiredXX(2) = 2 nearest_power(3) = 4 = 1<<2 bits_requiredXX(3) = 2 nearest_power(4) = 4 = 1<<2 bits_requiredXX(4) = 3 nearest_power(5) = 8 = 1<<3 bits_requiredXX(5) = 3 nearest_power(7) = 8 = 1<<3 bits_requiredXX(7) = 3 nearest_power(8) = 8 = 1<<3 bits_requiredXX(8) = 4 */ /* nearest_power(num) == 1ULL << bits_required64(num-1) */ static void test_nearest_power(void) { unsigned int b; size_t num; test_begin("nearest_power()"); test_assert(nearest_power(1)==1); test_assert(nearest_power(2)==2); for (b = 2; b < CHAR_BIT*sizeof(size_t) - 1; ++b) { /* b=2 tests 3,4,5; b=3 tests 7,8,9; ... b=30 tests ~1G */ num = (size_t)1 << b; test_assert_idx(nearest_power(num-1) == num, b); test_assert_idx(nearest_power(num ) == num, b); test_assert_idx(nearest_power(num+1) == num<<1, b); } /* With 32-bit size_t, now: b=31 tests 2G-1, 2G, not 2G+1. */ num = (size_t)1 << b; test_assert_idx(nearest_power(num-1) == num, b); test_assert_idx(nearest_power(num ) == num, b); /* i_assert()s: test_assert_idx(nearest_power(num+1) == num<<1, b); */ test_end(); } static void test_bits_is_power_of_two(void) { test_begin("bits_is_power_of_two()"); for (unsigned int i = 0; i < 64; i++) test_assert_idx(bits_is_power_of_two(1ULL << i), i); for (unsigned int i = 2; i < 64; i++) { test_assert_idx(!bits_is_power_of_two((1ULL << i) - 1), i); test_assert_idx(!bits_is_power_of_two((1ULL << i) + 1), i); } test_assert(!bits_is_power_of_two(0)); test_assert(!bits_is_power_of_two(0xffffffffffffffffULL)); test_assert( bits_is_power_of_two(0x8000000000000000ULL)); test_end(); } static void test_bits_requiredXX(void) { /* As ..64 depends on ..32 and tests it twice, * and ..32 depends on ..16 and tests it twice, * etc., we only test ..64 */ unsigned int b; test_begin("bits_requiredXX()"); test_assert(bits_required64(0) == 0); test_assert(bits_required64(1) == 1); test_assert(bits_required64(2) == 2); for (b = 2; b < 64; ++b) { /* b=2 tests 3,4,5; b=3 tests 7,8,9; ... */ uint64_t num = 1ULL << b; test_assert_idx(bits_required64(num-1) == b, b); test_assert_idx(bits_required64(num ) == b+1, b); test_assert_idx(bits_required64(num+1) == b+1, b); } test_end(); } static void test_sum_overflows(void) { #define MAX64 (uint64_t)-1 static const struct { uint64_t a, b; bool overflows; } tests[] = { { MAX64-1, 1, FALSE }, { MAX64, 1, TRUE }, { MAX64-1, 1, FALSE }, { MAX64-1, 2, TRUE }, { MAX64-1, MAX64-1, TRUE }, { MAX64-1, MAX64, TRUE }, { MAX64, MAX64, TRUE } }; unsigned int i; test_begin("UINT64_SUM_OVERFLOWS"); for (i = 0; i < N_ELEMENTS(tests); i++) test_assert(UINT64_SUM_OVERFLOWS(tests[i].a, tests[i].b) == tests[i].overflows); test_end(); } static void ATTR_NO_SANITIZE_INTEGER ATTR_NO_SANITIZE_IMPLICIT_CONVERSION test_bits_fraclog(void) { unsigned int fracbits; for (fracbits = 0; fracbits < 6; fracbits++) { static char name[] = "fraclog x-bit"; name[8] = '0'+ fracbits; test_begin(name); unsigned int i; unsigned int last_end = ~0u; for (i = 0; i < BITS_FRACLOG_BUCKETS(fracbits); i++) { unsigned int start = bits_fraclog_bucket_start(i, fracbits); unsigned int end = bits_fraclog_bucket_end(i, fracbits); test_assert_idx(start == last_end + 1, i); last_end = end; test_assert_idx(bits_fraclog(start, fracbits) == i, i); test_assert_idx(bits_fraclog(end, fracbits) == i, i); } test_assert_idx(last_end == ~0u, fracbits); test_end(); } } /* The compiler *should* generate different code when the fracbits parameter is a compile-time constant, so we also need to check that's the case. */ static void ATTR_NO_SANITIZE_INTEGER ATTR_NO_SANITIZE_IMPLICIT_CONVERSION test_bits_fraclog_const(void) { #define FRACBITS 2 #define STR2(s) #s #define STR(s) STR2(s) test_begin("fraclog constant " STR(FRACBITS) " bit"); unsigned int i; unsigned int last_end = ~0u; for (i = 0; i < BITS_FRACLOG_BUCKETS(FRACBITS); i++) { unsigned int start = bits_fraclog_bucket_start(i, FRACBITS); unsigned int end = bits_fraclog_bucket_end(i, FRACBITS); test_assert_idx(start == last_end + 1, i); last_end = end; test_assert_idx(bits_fraclog(start, FRACBITS) == i, i); test_assert_idx(bits_fraclog(end, FRACBITS) == i, i); } test_assert(last_end == ~0u); test_end(); } static void test_bits_rotl32(void) { test_begin("bits_rotl32"); test_assert(bits_rotl32(0x1c00000eU, 3) == 0xe0000070U); test_assert(bits_rotl32(0xe0000070U, 5) == 0x00000e1cU); test_assert(bits_rotl32(0x00000e1cU, 0) == 0x00000e1cU); test_assert(bits_rotl32(0x1c00000eU, 3 + 32) == 0xe0000070U); test_end(); } static void test_bits_rotl64(void) { test_begin("bits_rotl64"); test_assert(bits_rotl64(0x1c0000000000000eUL, 3) == 0xe000000000000070UL); test_assert(bits_rotl64(0xe000000000000070UL, 5) == 0x0000000000000e1cUL); test_assert(bits_rotl64(0x0000000000000e1cUL, 0) == 0x0000000000000e1cUL); test_assert(bits_rotl64(0x1c0000000000000eUL, 3 + 64) == 0xe000000000000070UL); test_end(); } static void test_bits_rotr32(void) { test_begin("bits_rotr32"); test_assert(bits_rotr32(0x1c00000eU, 3) == 0xc3800001U); test_assert(bits_rotr32(0xc3800001U, 5) == 0x0e1c0000U); test_assert(bits_rotr32(0x00000e1cU, 0) == 0x00000e1cU); test_assert(bits_rotr32(0x1c00000eU, 3 + 32) == 0xc3800001U); test_end(); } static void test_bits_rotr64(void) { test_begin("bits_rotr64"); test_assert(bits_rotr64(0x1c0000000000000eUL, 3) == 0xc380000000000001UL); test_assert(bits_rotr64(0xc380000000000001UL, 5) == 0x0e1c000000000000UL); test_assert(bits_rotr64(0x0000000000000e1cUL, 0) == 0x0000000000000e1cUL); test_assert(bits_rotr64(0x1c0000000000000eUL, 3 + 64) == 0xc380000000000001UL); test_end(); } static void test_bit_tests(void) { test_begin("HAS_..._BITS() macro tests"); test_assert(HAS_NO_BITS(1,0)); test_assert(HAS_NO_BITS(2,~2U)); test_assert(!HAS_NO_BITS(2,2)); /* OUCH - this vacuously true expression fails. However, if you are dumb enough to use 0 as bits, then it will also fail in the verbose case that this macro replaces, it's not a regression. */ /* test_assert(HAS_ANY_BITS(6,0)); */ test_assert(HAS_ANY_BITS(3,1)); test_assert(HAS_ANY_BITS(2,3)); test_assert(!HAS_ANY_BITS(7,~(7U|128U))); test_assert(HAS_ALL_BITS(0,0)); test_assert(HAS_ALL_BITS(30,14)); test_assert(!HAS_ALL_BITS(~1U,~0U)); /* Trap double-evaluation */ unsigned int v=10,b=2; test_assert(!HAS_NO_BITS(v++, b++) && v==11 && b==3); test_assert(HAS_ANY_BITS(v++, b++) && v==12 && b==4); test_assert(HAS_ALL_BITS(v++, b++) && v==13 && b==5); test_end(); } void test_bits(void) { test_bits_unsigned_minus(); test_nearest_power(); test_bits_is_power_of_two(); test_bits_requiredXX(); test_bits_fraclog(); test_bits_fraclog_const(); test_bits_rotl32(); test_bits_rotr32(); test_bits_rotl64(); test_bits_rotr64(); test_sum_overflows(); test_bit_tests(); } dovecot-2.3.21.1/src/lib/primes.h0000644000000000000000000000037314656633576013325 00000000000000#ifndef PRIMES_H #define PRIMES_H /* Returns a prime close to specified number, or the number itself if it's a prime. Note that the returned value may be smaller than requested! */ unsigned int primes_closest(unsigned int num) ATTR_CONST; #endif dovecot-2.3.21.1/src/lib/mempool.c0000644000000000000000000000140314656633576013464 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" /* The various implementations of pools API assume that they'll never be asked for more than SSIZE_T_MAX bytes. This is a sanity check to make sure nobody accidentally bumped the define beyond what's expected. */ #if POOL_MAX_ALLOC_SIZE > SSIZE_T_MAX #error "POOL_MAX_ALLOC_SIZE is too large" #endif size_t pool_get_exp_grown_size(pool_t pool, size_t old_size, size_t min_size) { size_t exp_size, easy_size; i_assert(old_size < min_size); exp_size = nearest_power(min_size); easy_size = old_size + p_get_max_easy_alloc_size(pool); if (easy_size < exp_size && easy_size >= min_size) exp_size = easy_size; i_assert(exp_size >= min_size); return exp_size; } dovecot-2.3.21.1/src/lib/hex-dec.h0000644000000000000000000000064314656633576013343 00000000000000#ifndef HEX_DEC_H #define HEX_DEC_H #define DEC2HEX(hexstr, str) \ dec2hex(hexstr, str, sizeof(hexstr)) /* Decimal -> hex string translation. The result isn't NUL-terminated. */ void dec2hex(unsigned char *hexstr, uintmax_t dec, unsigned int hexstr_size); /* Parses hex string and returns its decimal value, or 0 in case of errors */ uintmax_t hex2dec(const unsigned char *data, unsigned int len) ATTR_PURE; #endif dovecot-2.3.21.1/src/lib/str-find.c0000644000000000000000000000757614656633576013563 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ /* @UNSAFE: whole file */ #include "lib.h" #include "str-find.h" struct str_find_context { pool_t pool; unsigned char *key; unsigned int key_len; unsigned int *matches; unsigned int match_count; size_t match_end_pos; int badtab[UCHAR_MAX+1]; int goodtab[FLEXIBLE_ARRAY_MEMBER]; }; static void init_badtab(struct str_find_context *ctx) { unsigned int i, len_1 = ctx->key_len - 1; for (i = 0; i <= UCHAR_MAX; i++) ctx->badtab[i] = ctx->key_len; for (i = 0; i < len_1; i++) ctx->badtab[ctx->key[i]] = len_1 - i; } static void init_suffixes(struct str_find_context *ctx, unsigned int *suffixes) { unsigned int len_1 = ctx->key_len - 1; int f = 0, g, i; suffixes[len_1] = ctx->key_len; g = len_1; for (i = (int)ctx->key_len - 2; i >= 0; i--) { if (i > g && (int)suffixes[i + len_1 - f] < i - g) suffixes[i] = suffixes[i + len_1 - f]; else { if (i < g) g = i; f = i; while (g >= 0 && ctx->key[g] == ctx->key[g + len_1 - f]) g--; suffixes[i] = f - g; } } } static void init_goodtab(struct str_find_context *ctx) { unsigned int *suffixes; int j, i, len_1 = ctx->key_len - 1; suffixes = t_buffer_get(sizeof(*suffixes) * ctx->key_len); init_suffixes(ctx, suffixes); for (i = 0; i < (int)ctx->key_len; i++) ctx->goodtab[i] = ctx->key_len; j = 0; for (i = len_1; i >= -1; i--) { if (i < 0 || suffixes[i] == (unsigned int)i + 1) { for (; j < len_1 - i; j++) { if (ctx->goodtab[j] == (int)ctx->key_len) ctx->goodtab[j] = len_1 - i; } } } for (i = 0; i <= (int)ctx->key_len - 2; i++) ctx->goodtab[len_1 - suffixes[i]] = len_1 - i; } struct str_find_context *str_find_init(pool_t pool, const char *key) { struct str_find_context *ctx; size_t key_len = strlen(key); i_assert(key_len > 0); i_assert(key_len < INT_MAX); ctx = p_malloc(pool, MALLOC_ADD(sizeof(struct str_find_context), MALLOC_MULTIPLY(sizeof(ctx->goodtab[0]), key_len))); ctx->pool = pool; ctx->matches = p_new(pool, unsigned int, key_len); ctx->key_len = key_len; ctx->key = p_malloc(pool, key_len); memcpy(ctx->key, key, key_len); init_goodtab(ctx); init_badtab(ctx); return ctx; } void str_find_deinit(struct str_find_context **_ctx) { struct str_find_context *ctx = *_ctx; *_ctx = NULL; p_free(ctx->pool, ctx->matches); p_free(ctx->pool, ctx->key); p_free(ctx->pool, ctx); } bool str_find_more(struct str_find_context *ctx, const unsigned char *data, size_t size) { unsigned int key_len = ctx->key_len; unsigned int i, j, a, b; int bad_value; for (i = j = 0; i < ctx->match_count; i++) { a = ctx->matches[i]; if (ctx->matches[i] + size >= key_len) { /* we can fully determine this match now */ for (; a < key_len; a++) { if (ctx->key[a] != data[a - ctx->matches[i]]) break; } if (a == key_len) { ctx->match_end_pos = key_len - ctx->matches[i]; return TRUE; } } else { for (b = 0; b < size; b++) { if (ctx->key[a+b] != data[b]) break; } if (b == size) ctx->matches[j++] = a + size; } } if (j > 0) { i_assert(j + size < key_len); ctx->match_count = j; j = 0; } else { /* Boyer-Moore searching */ j = 0; while (j + key_len <= size) { i = key_len - 1; while (ctx->key[i] == data[i + j]) { if (i == 0) { ctx->match_end_pos = j + key_len; return TRUE; } i--; } bad_value = (int)(ctx->badtab[data[i + j]] + i + 1) - (int)key_len; j += I_MAX(ctx->goodtab[i], bad_value); } i_assert(j <= size); ctx->match_count = 0; } for (; j < size; j++) { for (i = j; i < size; i++) { if (ctx->key[i-j] != data[i]) break; } if (i == size) ctx->matches[ctx->match_count++] = size - j; } return FALSE; } size_t str_find_get_match_end_pos(struct str_find_context *ctx) { return ctx->match_end_pos; } void str_find_reset(struct str_find_context *ctx) { ctx->match_count = 0; } dovecot-2.3.21.1/src/lib/str-sanitize.h0000644000000000000000000000202014656633576014451 00000000000000#ifndef STR_SANITIZE_H #define STR_SANITIZE_H /* All control characters in src will be appended as '?'. If src is longer than max_bytes, it's truncated with "..." appended to the end. Note that src is treated as UTF-8 input, but max_bytes is in bytes instead of UTF-8 characters. */ void str_sanitize_append(string_t *dest, const char *src, size_t max_bytes); /* All control characters in src will be appended as the unicode replacement character (U+FFFD). If src has more than max_cps unicode code points, it's truncated with a horizontal ellipsis character (U+2026) appended to the end. */ void str_sanitize_append_utf8(string_t *dest, const char *src, uintmax_t max_cps); /* Return src sanitized. If there are no changes, src pointer is returned. If src is NULL, returns NULL. */ const char *str_sanitize(const char *src, size_t max_bytes); /* The unicode version of str_sanitize() using str_sanitize_append_utf8() internally. */ const char *str_sanitize_utf8(const char *src, uintmax_t max_cps); #endif dovecot-2.3.21.1/src/lib/test-event-filter-merge.c0000644000000000000000000000323514656633576016477 00000000000000/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str.h" #include "event-filter.h" static void filter_merge(const char *parent_str, const char *child_str) { struct event_filter *parent, *child; const char *test_name, *error; string_t *out = t_str_new(128); test_name = t_strdup_printf("parent %s, child %s", (parent_str == NULL) ? "NULL" : parent_str, (child_str == NULL) ? "NULL" : child_str); parent = event_filter_create(); child = event_filter_create(); /* prime the filters with an expression */ if (parent_str != NULL) { test_out_quiet(t_strdup_printf("%s:parent", test_name), event_filter_parse(parent_str, parent, &error) == 0); } if (child_str != NULL) { test_out_quiet(t_strdup_printf("%s:child", test_name), event_filter_parse(child_str, child, &error) == 0); } /* merge */ event_filter_merge(parent, child); /* export - to visit/deref everything in the filter */ event_filter_export(parent, out); event_filter_export(child, out); event_filter_unref(&parent); event_filter_unref(&child); } void test_event_filter_merge(void) { static const char *inputs[] = { NULL, /* event name */ "event=\"bar\"", "event=\"\"", /* category */ "category=\"bar\"", "category=\"\"", /* source location */ "source_location=\"bar:123\"", "source_location=\"bar\"", "source_location=\"\"", /* field */ "foo=\"bar\"", "foo=\"\"", }; unsigned int i, j; test_begin("event filter merge"); for (i = 0; i < N_ELEMENTS(inputs); i++) { for (j = 0; j < N_ELEMENTS(inputs); j++) T_BEGIN { filter_merge(inputs[i], inputs[j]); } T_END; } test_end(); } dovecot-2.3.21.1/src/lib/memarea.c0000644000000000000000000000320514656633576013425 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "memarea.h" struct memarea { const void *data; size_t size; memarea_free_callback_t *callback; void *context; int refcount; }; static struct memarea memarea_empty = { .refcount = 1, }; #undef memarea_init struct memarea * memarea_init(const void *data, size_t size, memarea_free_callback_t *callback, void *context) { struct memarea *area; i_assert(callback != NULL); area = i_new(struct memarea, 1); area->data = data; area->size = size; area->callback = callback; area->context = context; area->refcount = 1; return area; } struct memarea *memarea_init_empty(void) { i_assert(memarea_empty.refcount > 0); memarea_empty.refcount++; return &memarea_empty; } void memarea_ref(struct memarea *area) { i_assert(area->refcount > 0); area->refcount++; } void memarea_unref(struct memarea **_area) { struct memarea *area = *_area; *_area = NULL; i_assert(area->refcount > 0); if (--area->refcount > 0) return; i_assert(area != &memarea_empty); area->callback(area->context); i_free(area); } void memarea_free_without_callback(struct memarea **_area) { struct memarea *area = *_area; *_area = NULL; i_assert(memarea_get_refcount(area) == 1); i_free(area); } unsigned int memarea_get_refcount(struct memarea *area) { i_assert(area->refcount > 0); return area->refcount; } const void *memarea_get(struct memarea *area, size_t *size_r) { *size_r = area->size; return area->data; } size_t memarea_get_size(struct memarea *area) { return area->size; } void memarea_free_callback_noop(void *context ATTR_UNUSED) { } dovecot-2.3.21.1/src/lib/ostream-failure-at.c0000644000000000000000000000704714656633576015527 00000000000000/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "ostream-private.h" #include "ostream-failure-at.h" struct failure_at_ostream { struct ostream_private ostream; char *error_string; uoff_t failure_offset; bool failed; }; static void o_stream_failure_at_destroy(struct iostream_private *stream) { struct failure_at_ostream *fstream = container_of(stream, struct failure_at_ostream, ostream.iostream); i_free(fstream->error_string); o_stream_unref(&fstream->ostream.parent); } static ssize_t o_stream_failure_at_sendv(struct ostream_private *stream, const struct const_iovec *iov, unsigned int iov_count) { struct failure_at_ostream *fstream = container_of(stream, struct failure_at_ostream, ostream); unsigned int i; struct const_iovec *iov_dup; unsigned int iov_dup_count; uoff_t bytes_until_failure, blocking_bytes_count = 0; ssize_t ret; if (stream->ostream.blocking) { /* blocking ostream must return either a full success or a failure. if the current write would go past failure_offset, return a failure now before writing anything. */ for (i = 0; i < iov_count; i++) blocking_bytes_count += iov[i].iov_len; if (blocking_bytes_count > 0) { /* if we're exactly at the failure offset after this write, fail it only on the next write. */ blocking_bytes_count--; } } if (fstream->failure_offset <= stream->ostream.offset + blocking_bytes_count) { io_stream_set_error(&stream->iostream, "%s", fstream->error_string); stream->ostream.stream_errno = errno = EIO; fstream->failed = TRUE; return -1; } bytes_until_failure = fstream->failure_offset - stream->ostream.offset; iov_dup = i_new(struct const_iovec, iov_count); iov_dup_count = iov_count; for (i = 0; i < iov_count; i++) { iov_dup[i] = iov[i]; if (iov_dup[i].iov_len >= bytes_until_failure) { iov_dup[i].iov_len = bytes_until_failure; iov_dup_count = i+1; break; } } ret = o_stream_sendv(stream->parent, iov_dup, iov_dup_count); i_free(iov_dup); if (ret < 0) { o_stream_copy_error_from_parent(stream); return -1; } stream->ostream.offset += ret; return ret; } static int o_stream_failure_at_flush(struct ostream_private *stream) { struct failure_at_ostream *fstream = container_of(stream, struct failure_at_ostream, ostream); if (fstream->failed) { io_stream_set_error(&stream->iostream, "%s", fstream->error_string); stream->ostream.stream_errno = errno = EIO; return -1; } return o_stream_flush_parent(stream); } struct ostream * o_stream_create_failure_at(struct ostream *output, uoff_t failure_offset, const char *error_string) { struct failure_at_ostream *fstream; fstream = i_new(struct failure_at_ostream, 1); fstream->ostream.sendv = o_stream_failure_at_sendv; fstream->ostream.flush = o_stream_failure_at_flush; fstream->ostream.iostream.destroy = o_stream_failure_at_destroy; fstream->failure_offset = failure_offset; fstream->error_string = i_strdup(error_string); return o_stream_create(&fstream->ostream, output, o_stream_get_fd(output)); } struct ostream * o_stream_create_failure_at_flush(struct ostream *output, const char *error_string) { struct failure_at_ostream *fstream; fstream = i_new(struct failure_at_ostream, 1); fstream->ostream.flush = o_stream_failure_at_flush; fstream->ostream.iostream.destroy = o_stream_failure_at_destroy; fstream->error_string = i_strdup(error_string); fstream->failed = TRUE; return o_stream_create(&fstream->ostream, output, o_stream_get_fd(output)); } dovecot-2.3.21.1/src/lib/mempool-alloconly.c0000644000000000000000000003632414656633576015470 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ /* @UNSAFE: whole file */ #include "lib.h" #include "safe-memset.h" #include "mempool.h" /* * As the name implies, alloconly pools support only allocating memory. * Memory freeing is not supported, except as a special case - the pool's * last allocation can be freed. Additionally, p_realloc() also tries to * grow an existing allocation if and only if it is the last allocation, * otherwise it just allocates a new memory area and copies the data there. * * Alloconly pools are commonly used for an object that builds its state * from many memory allocations, but doesn't change (much of) its state. * It is simpler to free such an object by destroying the entire memory * pool. * * Implementation * ============== * * Each alloconly pool contains a pool structure (struct alloconly_pool) to * keep track of alloconly-specific pool information and one or more blocks * (struct pool_block) that keep track of ranges of memory used to back the * allocations. The blocks are kept in a linked list implementing a stack. * The block size decreases the further down the stack one goes. * * +-----------+ * | alloconly | * | pool | * +-----+-----+ * | * | block +------------+ next +------------+ next * \------->| pool block |------>| pool block |------>... * +------------+ +------------+ * | | | | * . . * . . * . | | * . +------------+ * | | * +------------+ * * Creation * -------- * * When an alloconly pool is created, one block is allocated. This block is * large enough to hold the necessary internal structures (struct * alloconly_pool and struct pool_block) and still have enough space to * satisfy allocations for at least the amount of space requested by the * consumer via the size argument to pool_alloconly_create(). * * Allocation * ---------- * * Each allocation (via p_malloc()) checks the top-most block to see whether * or not it has enough space to satisfy the allocation. If there is not * enough space, it allocates a new block (via block_alloc()) to serve as * the new top-most block. This newly-allocated block is guaranteed to have * enough space for the allocation. Then, regardless of whether or not a * new block was allocated, the allocation code reserves enough space in the * top-most block for the allocation and returns a pointer to it to the * caller. * * The free space tracking within each block is very simple. In addition to * keeping track of the size of the block, the block header contains a * "pointer" to the beginning of free space. A new allocation simply moves * this pointer by the number of bytes allocated. * * Reallocation * ------------ * * If the passed in allocation is the last allocation in a block and there * is enough space after it, the allocation is resized. Otherwise, a new * buffer is allocated (see Allocation above) and the contents are copied * over. * * Freeing * ------- * * Freeing of the last allocation moves the "pointer" to free space back by * the size of the last allocation. * * Freeing of any other allocation is a no-op. * * Clearing * -------- * * Clearing the pool is supposed to return the pool to the same state it was * in when it was first created. To that end, the alloconly pool frees all * the blocks allocated since the pool's creation. The remaining block * (allocated during creation) is reset to consider all the space for * allocations as available. * * In other words, the per-block free space tracking variables are set to * indicate that the full block is available and that there have been no * allocations. * * Finally, if the pool was created via pool_alloconly_create_clean(), all * blocks are safe_memset()/memset() to zero before being free()d. * * Destruction * ----------- * * Destroying a pool first clears it (see above). The clearing leaves the * pool in a minimal state with only one block allocated. This remaining * block may be safe_memset() to zero if the pool was created with * pool_alloconly_create_clean(). * * Since the pool structure itself is allocated from the first block, this * final call to free() will release the memory allocated for struct * alloconly_pool and struct pool. */ #ifndef DEBUG # define POOL_ALLOCONLY_MAX_EXTRA MEM_ALIGN(1) #else # define POOL_ALLOCONLY_MAX_EXTRA \ (MEM_ALIGN(sizeof(size_t)) + MEM_ALIGN(1) + MEM_ALIGN(SENTRY_COUNT)) #endif struct alloconly_pool { struct pool pool; int refcount; struct pool_block *block; #ifdef DEBUG const char *name; size_t base_size; bool disable_warning; #endif bool clean_frees; }; struct pool_block { struct pool_block *prev; size_t size; size_t left; size_t last_alloc_size; /* unsigned char data[]; */ }; #define SIZEOF_POOLBLOCK (MEM_ALIGN(sizeof(struct pool_block))) #define POOL_BLOCK_DATA(block) \ ((unsigned char *) (block) + SIZEOF_POOLBLOCK) #define DEFAULT_BASE_SIZE MEM_ALIGN(sizeof(struct alloconly_pool)) #ifdef DEBUG # define CLEAR_CHR 0xde # define SENTRY_COUNT 8 #else # define SENTRY_COUNT 0 # define CLEAR_CHR 0 #endif static const char *pool_alloconly_get_name(pool_t pool); static void pool_alloconly_ref(pool_t pool); static void pool_alloconly_unref(pool_t *pool); static void *pool_alloconly_malloc(pool_t pool, size_t size); static void pool_alloconly_free(pool_t pool, void *mem); static void *pool_alloconly_realloc(pool_t pool, void *mem, size_t old_size, size_t new_size); static void pool_alloconly_clear(pool_t pool); static size_t pool_alloconly_get_max_easy_alloc_size(pool_t pool); static void block_alloc(struct alloconly_pool *pool, size_t size); static const struct pool_vfuncs static_alloconly_pool_vfuncs = { pool_alloconly_get_name, pool_alloconly_ref, pool_alloconly_unref, pool_alloconly_malloc, pool_alloconly_free, pool_alloconly_realloc, pool_alloconly_clear, pool_alloconly_get_max_easy_alloc_size }; static const struct pool static_alloconly_pool = { .v = &static_alloconly_pool_vfuncs, .alloconly_pool = TRUE, .datastack_pool = FALSE }; #ifdef DEBUG static void check_sentries(struct pool_block *block) { const unsigned char *data = POOL_BLOCK_DATA(block); size_t i, max_pos, alloc_size, used_size; used_size = block->size - block->left; for (i = 0; i < used_size; ) { alloc_size = *(size_t *)(data + i); if (alloc_size == 0 || used_size - i < alloc_size) i_panic("mempool-alloconly: saved alloc size broken"); i += MEM_ALIGN(sizeof(alloc_size)); max_pos = i + MEM_ALIGN(alloc_size + SENTRY_COUNT); i += alloc_size; for (; i < max_pos; i++) { if (data[i] != CLEAR_CHR) i_panic("mempool-alloconly: buffer overflow"); } } if (i != used_size) i_panic("mempool-alloconly: used_size wrong"); /* The unused data must be NULs */ for (; i < block->size; i++) { if (data[i] != '\0') i_unreached(); } if (block->prev != NULL) check_sentries(block->prev); } #endif pool_t pool_alloconly_create(const char *name ATTR_UNUSED, size_t size) { struct alloconly_pool apool, *new_apool; size_t min_alloc = SIZEOF_POOLBLOCK + MEM_ALIGN(sizeof(struct alloconly_pool) + SENTRY_COUNT); if (POOL_ALLOCONLY_MAX_EXTRA > (SSIZE_T_MAX - POOL_MAX_ALLOC_SIZE)) i_panic("POOL_MAX_ALLOC_SIZE is too large"); #ifdef DEBUG min_alloc += MEM_ALIGN(strlen(name) + 1 + SENTRY_COUNT) + sizeof(size_t)*2; #endif /* create a fake alloconly_pool so we can call block_alloc() */ i_zero(&apool); apool.pool = static_alloconly_pool; apool.refcount = 1; if (size < min_alloc) size = nearest_power(size + min_alloc); block_alloc(&apool, size); /* now allocate the actual alloconly_pool from the created block */ new_apool = p_new(&apool.pool, struct alloconly_pool, 1); *new_apool = apool; #ifdef DEBUG if (str_begins(name, MEMPOOL_GROWING) || getenv("DEBUG_SILENT") != NULL) { name += strlen(MEMPOOL_GROWING); new_apool->disable_warning = TRUE; } new_apool->name = p_strdup(&new_apool->pool, name); /* set base_size so p_clear() doesn't trash alloconly_pool structure. */ new_apool->base_size = new_apool->block->size - new_apool->block->left; new_apool->block->last_alloc_size = 0; #endif /* the first pool allocations must be from the first block */ i_assert(new_apool->block->prev == NULL); return &new_apool->pool; } pool_t pool_alloconly_create_clean(const char *name, size_t size) { struct alloconly_pool *apool; pool_t pool; pool = pool_alloconly_create(name, size); apool = container_of(pool, struct alloconly_pool, pool); apool->clean_frees = TRUE; return pool; } static void pool_alloconly_free_block(struct alloconly_pool *apool ATTR_UNUSED, struct pool_block *block) { #ifdef DEBUG safe_memset(block, CLEAR_CHR, SIZEOF_POOLBLOCK + block->size); #else if (apool->clean_frees) { safe_memset(block, CLEAR_CHR, SIZEOF_POOLBLOCK + block->size); } #endif free(block); } static void pool_alloconly_free_blocks_until_last(struct alloconly_pool *apool) { struct pool_block *block; /* destroy all blocks but the oldest, which contains the struct alloconly_pool allocation. */ while (apool->block->prev != NULL) { block = apool->block; apool->block = block->prev; pool_alloconly_free_block(apool, block); } } static void pool_alloconly_destroy(struct alloconly_pool *apool) { /* destroy all but the last block */ pool_alloconly_free_blocks_until_last(apool); /* destroy the last block */ pool_alloconly_free_block(apool, apool->block); } static const char *pool_alloconly_get_name(pool_t pool ATTR_UNUSED) { #ifdef DEBUG struct alloconly_pool *apool = container_of(pool, struct alloconly_pool, pool); return apool->name; #else return "alloconly"; #endif } static void pool_alloconly_ref(pool_t pool) { struct alloconly_pool *apool = container_of(pool, struct alloconly_pool, pool); apool->refcount++; } static void pool_alloconly_unref(pool_t *pool) { struct alloconly_pool *apool = container_of(*pool, struct alloconly_pool, pool); /* erase the pointer before freeing anything, as the pointer may exist inside the pool's memory area */ *pool = NULL; if (--apool->refcount > 0) return; pool_alloconly_destroy(apool); } static void block_alloc(struct alloconly_pool *apool, size_t size) { struct pool_block *block; i_assert(size > SIZEOF_POOLBLOCK); i_assert(size <= SSIZE_T_MAX); if (apool->block != NULL) { /* each block is at least twice the size of the previous one */ if (size <= apool->block->size) size += apool->block->size; /* avoid crashing in nearest_power() if size is too large */ size = I_MIN(size, SSIZE_T_MAX); size = nearest_power(size); /* nearest_power() could have grown size to SSIZE_T_MAX+1 */ size = I_MIN(size, SSIZE_T_MAX); #ifdef DEBUG if (!apool->disable_warning) { /* i_debug() overwrites unallocated data in data stack, so make sure everything is allocated before calling it. */ t_buffer_alloc_last_full(); i_debug("Growing pool '%s' with: %zu", apool->name, size); } #endif } block = calloc(size, 1); if (unlikely(block == NULL)) { i_fatal_status(FATAL_OUTOFMEM, "block_alloc(%zu" "): Out of memory", size); } block->prev = apool->block; apool->block = block; block->size = size - SIZEOF_POOLBLOCK; block->left = block->size; } static void *pool_alloconly_malloc(pool_t pool, size_t size) { struct alloconly_pool *apool = container_of(pool, struct alloconly_pool, pool); void *mem; size_t alloc_size; #ifndef DEBUG alloc_size = MEM_ALIGN(size); #else alloc_size = MEM_ALIGN(sizeof(size)) + MEM_ALIGN(size + SENTRY_COUNT); #endif if (apool->block->left < alloc_size) { /* we need a new block */ block_alloc(apool, alloc_size + SIZEOF_POOLBLOCK); } mem = POOL_BLOCK_DATA(apool->block) + (apool->block->size - apool->block->left); apool->block->left -= alloc_size; apool->block->last_alloc_size = alloc_size; #ifdef DEBUG memcpy(mem, &size, sizeof(size)); mem = PTR_OFFSET(mem, MEM_ALIGN(sizeof(size))); /* write CLEAR_CHRs to sentry */ memset(PTR_OFFSET(mem, size), CLEAR_CHR, MEM_ALIGN(size + SENTRY_COUNT) - size); #endif return mem; } static void pool_alloconly_free(pool_t pool, void *mem) { struct alloconly_pool *apool = container_of(pool, struct alloconly_pool, pool); /* we can free only the last allocation */ if (POOL_BLOCK_DATA(apool->block) + (apool->block->size - apool->block->left - apool->block->last_alloc_size) == mem) { memset(mem, 0, apool->block->last_alloc_size); apool->block->left += apool->block->last_alloc_size; apool->block->last_alloc_size = 0; } } static bool pool_alloconly_try_grow(struct alloconly_pool *apool, void *mem, size_t size) { /* see if we want to grow the memory we allocated last */ if (POOL_BLOCK_DATA(apool->block) + (apool->block->size - apool->block->left - apool->block->last_alloc_size) == mem) { /* yeah, see if we can grow */ if (apool->block->left >= size-apool->block->last_alloc_size) { /* just shrink the available size */ apool->block->left -= size - apool->block->last_alloc_size; apool->block->last_alloc_size = size; return TRUE; } } return FALSE; } static void *pool_alloconly_realloc(pool_t pool, void *mem, size_t old_size, size_t new_size) { struct alloconly_pool *apool = container_of(pool, struct alloconly_pool, pool); unsigned char *new_mem; if (new_size <= old_size) return mem; new_size = MEM_ALIGN(new_size); /* see if we can directly grow it */ if (!pool_alloconly_try_grow(apool, mem, new_size)) { /* slow way - allocate + copy */ new_mem = pool_alloconly_malloc(pool, new_size); memcpy(new_mem, mem, old_size); mem = new_mem; } return mem; } static void pool_alloconly_clear(pool_t pool) { struct alloconly_pool *apool = container_of(pool, struct alloconly_pool, pool); size_t base_size, avail_size; #ifdef DEBUG check_sentries(apool->block); #endif pool_alloconly_free_blocks_until_last(apool); /* clear the first block */ #ifdef DEBUG base_size = apool->base_size; #else base_size = DEFAULT_BASE_SIZE; #endif avail_size = apool->block->size - base_size; memset(PTR_OFFSET(POOL_BLOCK_DATA(apool->block), base_size), 0, avail_size - apool->block->left); apool->block->left = avail_size; apool->block->last_alloc_size = 0; } static size_t pool_alloconly_get_max_easy_alloc_size(pool_t pool) { struct alloconly_pool *apool = container_of(pool, struct alloconly_pool, pool); return apool->block->left; } size_t pool_alloconly_get_total_used_size(pool_t pool) { struct alloconly_pool *apool = container_of(pool, struct alloconly_pool, pool); struct pool_block *block; size_t size = 0; i_assert(pool->v == &static_alloconly_pool_vfuncs); for (block = apool->block; block != NULL; block = block->prev) size += block->size - block->left; return size; } size_t pool_alloconly_get_total_alloc_size(pool_t pool) { struct alloconly_pool *apool = container_of(pool, struct alloconly_pool, pool); struct pool_block *block; size_t size = 0; i_assert(pool->v == &static_alloconly_pool_vfuncs); for (block = apool->block; block != NULL; block = block->prev) size += block->size + SIZEOF_POOLBLOCK; return size; } dovecot-2.3.21.1/src/lib/test-event-filter-expr.c0000644000000000000000000001561514656633576016363 00000000000000/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str.h" #include "strescape.h" #include "event-filter.h" #include "event-filter-private.h" #define STRING1 "X" #define STRING2 "Y" /* dummy values, at least for now */ #define SOURCE_FILENAME "blah.c" #define SOURCE_LINE 123 static void check_expr(const char *test_name, struct event *event, struct event_filter *filter, enum event_filter_log_type log_type, bool expected) { struct event_filter_node *expr; unsigned int num_queries; bool got; /* get at the expr inside the filter */ expr = event_filter_get_expr_for_testing(filter, &num_queries); test_out_quiet(t_strdup_printf("%s:num_queries==1", test_name), num_queries == 1); /* should have only one query */ got = event_filter_query_match_eval(expr, event, SOURCE_FILENAME, SOURCE_LINE, log_type); test_out_quiet(t_strdup_printf("%s:got=expected", test_name), got == expected); } static void do_test_expr(const char *filter_string, struct event *event, enum event_filter_log_type log_type, bool expected) { const char *test_name, *error; test_name = t_strdup_printf( "%.*s log type + event {a=%s, b=%s} + filter '%s' (exp %s)", 3, /* truncate the type name to avoid CI seeing 'warning' messages */ event_filter_category_from_log_type(log_type), event_find_field_recursive_str(event, "a"), event_find_field_recursive_str(event, "b"), filter_string, expected ? "true" : "false"); /* set up the filter expression */ struct event_filter *filter = event_filter_create(); test_out_quiet(t_strdup_printf("%s:event_filter_parse()", test_name), event_filter_parse(filter_string, filter, &error) == 0); check_expr(test_name, event, filter, log_type, expected); event_filter_unref(&filter); } static void test_unary_expr(struct event *event, const char *expr, bool truth, enum event_filter_log_type log_type) { /* * The UNARY() macro checks: * * 1. expr * 2. NOT expr * 3. NOT (expr) * * Note that numbers 2 and 3 are equivalent. * * The truth argument specifies the expected truth-iness of the * passed in expression. */ #define UNARY() \ T_BEGIN { \ do_test_expr(expr, \ event, log_type, truth); \ do_test_expr(t_strdup_printf("NOT %s", expr), \ event, log_type, !truth); \ do_test_expr(t_strdup_printf("NOT (%s)", expr), \ event, log_type, !truth); \ } T_END UNARY(); } static void test_binary_expr(struct event *event, const char *expr1, const char *expr2, bool truth1, bool truth2, enum event_filter_log_type log_type) { /* * The BINARY() macro checks: * * 1. expr1 op expr2 * 2. NOT expr1 op expr2 * 3. NOT (expr1) op expr2 * 4. (NOT expr1) op expr2 * 5. expr1 op NOT expr2 * 6. expr1 op NOT (expr2) * 7. expr1 op (NOT expr2) * 8. NOT (expr1 op expr2) * 9. NOT expr1 op NOT expr2 * 10. NOT (expr1) op NOT (expr2) * 11. (NOT expr1) op (NOT expr2) * * Where op is OR or AND. * * Note that: * - numbers 2, 3, and 4 are equivalent * - numbers 5, 6, and 7 are equivalent * - numbers 9, 10, and 11 are equivalent * * The truth arugments specify the expected truth-iness of the * passed in expressions. */ #define BINARY(opstr, op) \ T_BEGIN { \ do_test_expr(t_strdup_printf("%s %s %s", expr1, opstr, expr2),\ event, log_type, \ (truth1) op (truth2)); \ do_test_expr(t_strdup_printf("NOT %s %s %s", expr1, opstr, expr2),\ event, log_type, \ !(truth1) op (truth2)); \ do_test_expr(t_strdup_printf("NOT (%s) %s %s", expr1, opstr, expr2),\ event, log_type, \ !(truth1) op (truth2)); \ do_test_expr(t_strdup_printf("(NOT %s) %s %s", expr1, opstr, expr2),\ event, log_type, \ !(truth1) op (truth2)); \ do_test_expr(t_strdup_printf("%s %s NOT %s", expr1, opstr, expr2),\ event, log_type, \ (truth1) op !(truth2)); \ do_test_expr(t_strdup_printf("%s %s NOT (%s)", expr1, opstr, expr2),\ event, log_type, \ (truth1) op !(truth2)); \ do_test_expr(t_strdup_printf("%s %s (NOT %s)", expr1, opstr, expr2),\ event, log_type, \ (truth1) op !(truth2)); \ do_test_expr(t_strdup_printf("NOT (%s %s %s)", expr1, opstr, expr2),\ event, log_type, \ !((truth1) op (truth2))); \ do_test_expr(t_strdup_printf("NOT %s %s NOT %s", expr1, opstr, expr2),\ event, log_type, \ !(truth1) op !(truth2)); \ do_test_expr(t_strdup_printf("NOT (%s) %s NOT (%s)", expr1, opstr, expr2),\ event, log_type, \ !(truth1) op !(truth2)); \ do_test_expr(t_strdup_printf("(NOT %s) %s (NOT %s)", expr1, opstr, expr2),\ event, log_type, \ !(truth1) op !(truth2)); \ } T_END BINARY("OR", ||); BINARY("AND", &&); } static void test_event_filter_expr_fields(enum event_filter_log_type log_type) { static const char *values[] = { NULL, "", STRING1, STRING2, }; unsigned int a, b; #define STR_IS_EMPTY(v) \ (((v) == NULL) || (strcmp("", (v)) == 0)) #define STR_MATCHES(v, c) \ (((v) != NULL) && (strcmp((c), (v)) == 0)) /* unary */ for (a = 0; a < N_ELEMENTS(values); a++) { /* set up the event to match against */ struct event *event = event_create(NULL); event_add_str(event, "a", values[a]); test_unary_expr(event, "a=\"\"", STR_IS_EMPTY(values[a]), log_type); test_unary_expr(event, "a=" STRING1, STR_MATCHES(values[a], STRING1), log_type); event_unref(&event); } /* binary */ for (a = 0; a < N_ELEMENTS(values); a++) { for (b = 0; b < N_ELEMENTS(values); b++) { /* set up the event to match against */ struct event *event = event_create(NULL); event_add_str(event, "a", values[a]); event_add_str(event, "b", values[b]); test_binary_expr(event, "a=\"\"", "b=\"\"", STR_IS_EMPTY(values[a]), STR_IS_EMPTY(values[b]), log_type); test_binary_expr(event, "a=" STRING1, "b=\"\"", STR_MATCHES(values[a], STRING1), STR_IS_EMPTY(values[b]), log_type); test_binary_expr(event, "a=\"\"", "b=" STRING2, STR_IS_EMPTY(values[a]), STR_MATCHES(values[b], STRING2), log_type); test_binary_expr(event, "a=" STRING1, "b=" STRING2, STR_MATCHES(values[a], STRING1), STR_MATCHES(values[b], STRING2), log_type); event_unref(&event); } } } void test_event_filter_expr(void) { static const enum event_filter_log_type log_types[] = { EVENT_FILTER_LOG_TYPE_DEBUG, EVENT_FILTER_LOG_TYPE_INFO, EVENT_FILTER_LOG_TYPE_WARNING, EVENT_FILTER_LOG_TYPE_ERROR, EVENT_FILTER_LOG_TYPE_FATAL, EVENT_FILTER_LOG_TYPE_PANIC, }; unsigned int i; test_begin("event filter expressions"); for (i = 0; i < N_ELEMENTS(log_types); i++) test_event_filter_expr_fields(log_types[i]); test_end(); } dovecot-2.3.21.1/src/lib/mempool-unsafe-datastack.c0000644000000000000000000000646314656633576016713 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "mempool.h" /* * The unsafe datastack pool is a very thin wrapper around the datastack * API. It is a simpler version of the datastack pool that does not do any * sanity checking, it simply forwards the calls to the datastack API. It * exists to allow some internal APIs to make datastack allocations via the * pool API. * * Note to consumers: Consider using the (safe) datastack pool instead of * this one. * * Implementation * ============== * * Creation * -------- * * The unsafe datastack pool is created statically and therefore is * available at any time after the datastack allocator is initialized. * * Allocation & Reallocation * ------------------------- * * The p_malloc() and p_realloc() calls get directed to t_malloc0() and * t_try_realloc(), respectively. There is no additional per-allocation * information to track. * * Freeing * ------- * * A no-op. * * Clearing * -------- * * A no-op. * * Destruction * ----------- * * It is not possible to destroy the unsafe datastack pool. Any attempt to * unref the pool is a no-op. */ static const char *pool_unsafe_data_stack_get_name(pool_t pool); static void pool_unsafe_data_stack_ref(pool_t pool); static void pool_unsafe_data_stack_unref(pool_t *pool); static void *pool_unsafe_data_stack_malloc(pool_t pool, size_t size); static void pool_unsafe_data_stack_free(pool_t pool, void *mem); static void *pool_unsafe_data_stack_realloc(pool_t pool, void *mem, size_t old_size, size_t new_size); static void pool_unsafe_data_stack_clear(pool_t pool); static size_t pool_unsafe_data_stack_get_max_easy_alloc_size(pool_t pool); static struct pool_vfuncs static_unsafe_data_stack_pool_vfuncs = { pool_unsafe_data_stack_get_name, pool_unsafe_data_stack_ref, pool_unsafe_data_stack_unref, pool_unsafe_data_stack_malloc, pool_unsafe_data_stack_free, pool_unsafe_data_stack_realloc, pool_unsafe_data_stack_clear, pool_unsafe_data_stack_get_max_easy_alloc_size }; static struct pool static_unsafe_data_stack_pool = { .v = &static_unsafe_data_stack_pool_vfuncs, .alloconly_pool = TRUE, .datastack_pool = TRUE }; pool_t unsafe_data_stack_pool = &static_unsafe_data_stack_pool; static const char *pool_unsafe_data_stack_get_name(pool_t pool ATTR_UNUSED) { return "unsafe data stack"; } static void pool_unsafe_data_stack_ref(pool_t pool ATTR_UNUSED) { } static void pool_unsafe_data_stack_unref(pool_t *pool ATTR_UNUSED) { } static void *pool_unsafe_data_stack_malloc(pool_t pool ATTR_UNUSED, size_t size) { return t_malloc0(size); } static void pool_unsafe_data_stack_free(pool_t pool ATTR_UNUSED, void *mem ATTR_UNUSED) { } static void *pool_unsafe_data_stack_realloc(pool_t pool ATTR_UNUSED, void *mem, size_t old_size, size_t new_size) { void *new_mem; /* @UNSAFE */ if (old_size >= new_size) return mem; if (!t_try_realloc(mem, new_size)) { new_mem = t_malloc_no0(new_size); memcpy(new_mem, mem, old_size); mem = new_mem; } memset((char *) mem + old_size, 0, new_size - old_size); return mem; } static void pool_unsafe_data_stack_clear(pool_t pool ATTR_UNUSED) { } static size_t pool_unsafe_data_stack_get_max_easy_alloc_size(pool_t pool ATTR_UNUSED) { return t_get_bytes_available(); } dovecot-2.3.21.1/src/lib/istream-concat.h0000644000000000000000000000026214656633576014734 00000000000000#ifndef ISTREAM_CONCAT_H #define ISTREAM_CONCAT_H /* Concatenate input streams into a single stream. */ struct istream *i_stream_create_concat(struct istream *input[]); #endif dovecot-2.3.21.1/src/lib/test-hash-format.c0000644000000000000000000000273214656633576015210 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str.h" #include "hash-format.h" struct hash_format_test { const char *input; const char *output; }; void test_hash_format(void) { static const char *fail_input[] = { "%", "%A{sha1}", "%{sha1", "%{sha1:8", "%{sha1:8a}", "%{sha1:0}", "%{sha1:168}", NULL }; static const struct hash_format_test tests[] = { { "%{sha1}", "8843d7f92416211de9ebb963ff4ce28125932878" }, { "*%{sha1}*", "*8843d7f92416211de9ebb963ff4ce28125932878*" }, { "*%{sha1:8}*", "*88*" }, { "%{sha1:152}", "8843d7f92416211de9ebb963ff4ce281259328" }, { "%X{size}", "6" }, { "%{sha256:80}", "c3ab8ff13720e8ad9047" }, { "%{sha512:80}", "0a50261ebd1a390fed2b" }, { "%{md4}", "547aefd231dcbaac398625718336f143" }, { "%{md5}", "3858f62230ac3c915f300c664312c63f" }, { "%{sha256:80}-%X{size}", "c3ab8ff13720e8ad9047-6" } }; struct hash_format *format; string_t *str = t_str_new(128); const char *error; unsigned int i; test_begin("hash_format"); for (i = 0; fail_input[i] != NULL; i++) test_assert(hash_format_init(fail_input[i], &format, &error) < 0); for (i = 0; i < N_ELEMENTS(tests); i++) { test_assert(hash_format_init(tests[i].input, &format, &error) == 0); hash_format_loop(format, "foo", 3); hash_format_loop(format, "bar", 3); str_truncate(str, 0); hash_format_deinit(&format, str); test_assert(strcmp(str_c(str), tests[i].output) == 0); } test_end(); } dovecot-2.3.21.1/src/lib/iso8601-date.c0000644000000000000000000001636714656633576014057 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "utc-offset.h" #include "utc-mktime.h" #include "iso8601-date.h" #include /* RFC3339/ISO8601 date-time syntax date-fullyear = 4DIGIT date-month = 2DIGIT ; 01-12 date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on ; month/year time-hour = 2DIGIT ; 00-23 time-minute = 2DIGIT ; 00-59 time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second ; rules time-secfrac = "." 1*DIGIT time-numoffset = ("+" / "-") time-hour ":" time-minute time-offset = "Z" / time-numoffset partial-time = time-hour ":" time-minute ":" time-second [time-secfrac] full-date = date-fullyear "-" date-month "-" date-mday full-time = partial-time time-offset date-time = full-date "T" full-time */ struct iso8601_date_parser { const unsigned char *cur, *end; struct tm tm; int timezone_offset; }; static inline int iso8601_date_parse_number(struct iso8601_date_parser *parser, int digits, int *number_r) { int i; if (parser->cur >= parser->end || !i_isdigit(parser->cur[0])) return 0; *number_r = parser->cur[0] - '0'; parser->cur++; for (i=0; i < digits-1; i++) { if (parser->cur >= parser->end || !i_isdigit(parser->cur[0])) return -1; *number_r = ((*number_r) * 10) + parser->cur[0] - '0'; parser->cur++; } return 1; } static int iso8601_date_parse_secfrac(struct iso8601_date_parser *parser) { /* time-secfrac = "." 1*DIGIT NOTE: Currently not applied anywhere, so fraction is just skipped. */ /* "." */ if (parser->cur >= parser->end || parser->cur[0] != '.') return 0; parser->cur++; /* 1DIGIT */ if (parser->cur >= parser->end || !i_isdigit(parser->cur[0])) return -1; parser->cur++; /* *DIGIT */ while (parser->cur < parser->end && i_isdigit(parser->cur[0])) parser->cur++; return 1; } static int is08601_date_parse_time_offset(struct iso8601_date_parser *parser) { int tz_sign = 1, tz_hour = 0, tz_min = 0; /* time-offset = "Z" / time-numoffset time-numoffset = ("+" / "-") time-hour ":" time-minute time-hour = 2DIGIT ; 00-23 time-minute = 2DIGIT ; 00-59 */ if (parser->cur >= parser->end) return 0; /* time-offset = "Z" / time-numoffset */ switch (parser->cur[0]) { case '-': tz_sign = -1; /* fall through */ case '+': parser->cur++; /* time-hour = 2DIGIT */ if (iso8601_date_parse_number(parser, 2, &tz_hour) <= 0) return -1; if (tz_hour > 23) return -1; /* ":" */ if (parser->cur >= parser->end || parser->cur[0] != ':') return -1; parser->cur++; /* time-minute = 2DIGIT */ if (iso8601_date_parse_number(parser, 2, &tz_min) <= 0) return -1; if (tz_min > 59) return -1; break; case 'Z': case 'z': parser->cur++; break; default: return -1; } parser->timezone_offset = tz_sign*(tz_hour*60 + tz_min); return 1; } static int is08601_date_parse_full_time(struct iso8601_date_parser *parser) { /* full-time = partial-time time-offset partial-time = time-hour ":" time-minute ":" time-second [time-secfrac] time-hour = 2DIGIT ; 00-23 time-minute = 2DIGIT ; 00-59 time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second ; rules */ /* time-hour = 2DIGIT */ if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_hour) <= 0) return -1; /* ":" */ if (parser->cur >= parser->end || parser->cur[0] != ':') return -1; parser->cur++; /* time-minute = 2DIGIT */ if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_min) <= 0) return -1; /* ":" */ if (parser->cur >= parser->end || parser->cur[0] != ':') return -1; parser->cur++; /* time-second = 2DIGIT */ if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_sec) <= 0) return -1; /* [time-secfrac] */ if (iso8601_date_parse_secfrac(parser) < 0) return -1; /* time-offset */ if (is08601_date_parse_time_offset(parser) <= 0) return -1; return 1; } static int is08601_date_parse_full_date(struct iso8601_date_parser *parser) { /* full-date = date-fullyear "-" date-month "-" date-mday date-fullyear = 4DIGIT date-month = 2DIGIT ; 01-12 date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on ; month/year */ /* date-fullyear = 4DIGIT */ if (iso8601_date_parse_number(parser, 4, &parser->tm.tm_year) <= 0) return -1; if (parser->tm.tm_year < 1900) return -1; parser->tm.tm_year -= 1900; /* "-" */ if (parser->cur >= parser->end || parser->cur[0] != '-') return -1; parser->cur++; /* date-month = 2DIGIT */ if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_mon) <= 0) return -1; parser->tm.tm_mon -= 1; /* "-" */ if (parser->cur >= parser->end || parser->cur[0] != '-') return -1; parser->cur++; /* time-second = 2DIGIT */ if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_mday) <= 0) return -1; return 1; } static int iso8601_date_parse_date_time(struct iso8601_date_parser *parser) { /* date-time = full-date "T" full-time */ /* full-date */ if (is08601_date_parse_full_date(parser) <= 0) return -1; /* "T" */ if (parser->cur >= parser->end || (parser->cur[0] != 'T' && parser->cur[0] != 't')) return -1; parser->cur++; /* full-time */ if (is08601_date_parse_full_time(parser) <= 0) return -1; if (parser->cur != parser->end) return -1; return 1; } static bool iso8601_date_do_parse(const unsigned char *data, size_t size, struct tm *tm_r, time_t *timestamp_r, int *timezone_offset_r) { struct iso8601_date_parser parser; time_t timestamp; i_zero(&parser); parser.cur = data; parser.end = data + size; if (iso8601_date_parse_date_time(&parser) <= 0) return FALSE; parser.tm.tm_isdst = -1; timestamp = utc_mktime(&parser.tm); if (timestamp == (time_t)-1) return FALSE; *timezone_offset_r = parser.timezone_offset; *tm_r = parser.tm; *timestamp_r = timestamp - parser.timezone_offset * 60; return TRUE; } bool iso8601_date_parse(const unsigned char *data, size_t size, time_t *timestamp_r, int *timezone_offset_r) { struct tm tm; return iso8601_date_do_parse(data, size, &tm, timestamp_r, timezone_offset_r); } bool iso8601_date_parse_tm(const unsigned char *data, size_t size, struct tm *tm_r, int *timezone_offset_r) { time_t timestamp; return iso8601_date_do_parse(data, size, tm_r, ×tamp, timezone_offset_r); } const char *iso8601_date_create_tm(struct tm *tm, int timezone_offset) { const char *time_offset; if (timezone_offset == INT_MAX) time_offset = "Z"; else { char sign = '+'; if (timezone_offset < 0) { timezone_offset = -timezone_offset; sign = '-'; } time_offset = t_strdup_printf("%c%02d:%02d", sign, timezone_offset / 60, timezone_offset % 60); } return t_strdup_printf("%04d-%02d-%02dT%02d:%02d:%02d%s", tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, time_offset); } const char *iso8601_date_create(time_t timestamp) { struct tm *tm; int timezone_offset; tm = localtime(×tamp); timezone_offset = utc_offset(tm, timestamp); return iso8601_date_create_tm(tm, timezone_offset); } dovecot-2.3.21.1/src/lib/unicodemap.c0000644000000000000000000053503314656633635014147 00000000000000/* This file is automatically generated by unicodemap.pl from UnicodeData.txt NOTE: decompositions for characters having titlecase characters are not included, because we first translate everything to titlecase */ static const uint16_t titlecase8_map[256] = { 0x00000, 0x00001, 0x00002, 0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a, 0x0000b, 0x0000c, 0x0000d, 0x0000e, 0x0000f, 0x00010, 0x00011, 0x00012, 0x00013, 0x00014, 0x00015, 0x00016, 0x00017, 0x00018, 0x00019, 0x0001a, 0x0001b, 0x0001c, 0x0001d, 0x0001e, 0x0001f, 0x00020, 0x00021, 0x00022, 0x00023, 0x00024, 0x00025, 0x00026, 0x00027, 0x00028, 0x00029, 0x0002a, 0x0002b, 0x0002c, 0x0002d, 0x0002e, 0x0002f, 0x00030, 0x00031, 0x00032, 0x00033, 0x00034, 0x00035, 0x00036, 0x00037, 0x00038, 0x00039, 0x0003a, 0x0003b, 0x0003c, 0x0003d, 0x0003e, 0x0003f, 0x00040, 0x00041, 0x00042, 0x00043, 0x00044, 0x00045, 0x00046, 0x00047, 0x00048, 0x00049, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004e, 0x0004f, 0x00050, 0x00051, 0x00052, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x0005a, 0x0005b, 0x0005c, 0x0005d, 0x0005e, 0x0005f, 0x00060, 0x00041, 0x00042, 0x00043, 0x00044, 0x00045, 0x00046, 0x00047, 0x00048, 0x00049, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004e, 0x0004f, 0x00050, 0x00051, 0x00052, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x0005a, 0x0007b, 0x0007c, 0x0007d, 0x0007e, 0x0007f, 0x00080, 0x00081, 0x00082, 0x00083, 0x00084, 0x00085, 0x00086, 0x00087, 0x00088, 0x00089, 0x0008a, 0x0008b, 0x0008c, 0x0008d, 0x0008e, 0x0008f, 0x00090, 0x00091, 0x00092, 0x00093, 0x00094, 0x00095, 0x00096, 0x00097, 0x00098, 0x00099, 0x0009a, 0x0009b, 0x0009c, 0x0009d, 0x0009e, 0x0009f, 0x000a0, 0x000a1, 0x000a2, 0x000a3, 0x000a4, 0x000a5, 0x000a6, 0x000a7, 0x000a8, 0x000a9, 0x000aa, 0x000ab, 0x000ac, 0x000ad, 0x000ae, 0x000af, 0x000b0, 0x000b1, 0x000b2, 0x000b3, 0x000b4, 0x0039c, 0x000b6, 0x000b7, 0x000b8, 0x000b9, 0x000ba, 0x000bb, 0x000bc, 0x000bd, 0x000be, 0x000bf, 0x000c0, 0x000c1, 0x000c2, 0x000c3, 0x000c4, 0x000c5, 0x000c6, 0x000c7, 0x000c8, 0x000c9, 0x000ca, 0x000cb, 0x000cc, 0x000cd, 0x000ce, 0x000cf, 0x000d0, 0x000d1, 0x000d2, 0x000d3, 0x000d4, 0x000d5, 0x000d6, 0x000d7, 0x000d8, 0x000d9, 0x000da, 0x000db, 0x000dc, 0x000dd, 0x000de, 0x000df, 0x000c0, 0x000c1, 0x000c2, 0x000c3, 0x000c4, 0x000c5, 0x000c6, 0x000c7, 0x000c8, 0x000c9, 0x000ca, 0x000cb, 0x000cc, 0x000cd, 0x000ce, 0x000cf, 0x000d0, 0x000d1, 0x000d2, 0x000d3, 0x000d4, 0x000d5, 0x000d6, 0x000f7, 0x000d8, 0x000d9, 0x000da, 0x000db, 0x000dc, 0x000dd, 0x000de, 0x00178 }; static const uint16_t titlecase16_keys[] = { 0x00101, 0x00103, 0x00105, 0x00107, 0x00109, 0x0010b, 0x0010d, 0x0010f, 0x00111, 0x00113, 0x00115, 0x00117, 0x00119, 0x0011b, 0x0011d, 0x0011f, 0x00121, 0x00123, 0x00125, 0x00127, 0x00129, 0x0012b, 0x0012d, 0x0012f, 0x00131, 0x00133, 0x00135, 0x00137, 0x0013a, 0x0013c, 0x0013e, 0x00140, 0x00142, 0x00144, 0x00146, 0x00148, 0x0014b, 0x0014d, 0x0014f, 0x00151, 0x00153, 0x00155, 0x00157, 0x00159, 0x0015b, 0x0015d, 0x0015f, 0x00161, 0x00163, 0x00165, 0x00167, 0x00169, 0x0016b, 0x0016d, 0x0016f, 0x00171, 0x00173, 0x00175, 0x00177, 0x0017a, 0x0017c, 0x0017e, 0x0017f, 0x00180, 0x00183, 0x00185, 0x00188, 0x0018c, 0x00192, 0x00195, 0x00199, 0x0019a, 0x0019e, 0x001a1, 0x001a3, 0x001a5, 0x001a8, 0x001ad, 0x001b0, 0x001b4, 0x001b6, 0x001b9, 0x001bd, 0x001bf, 0x001c4, 0x001c6, 0x001c7, 0x001c9, 0x001ca, 0x001cc, 0x001ce, 0x001d0, 0x001d2, 0x001d4, 0x001d6, 0x001d8, 0x001da, 0x001dc, 0x001dd, 0x001df, 0x001e1, 0x001e3, 0x001e5, 0x001e7, 0x001e9, 0x001eb, 0x001ed, 0x001ef, 0x001f1, 0x001f3, 0x001f5, 0x001f9, 0x001fb, 0x001fd, 0x001ff, 0x00201, 0x00203, 0x00205, 0x00207, 0x00209, 0x0020b, 0x0020d, 0x0020f, 0x00211, 0x00213, 0x00215, 0x00217, 0x00219, 0x0021b, 0x0021d, 0x0021f, 0x00223, 0x00225, 0x00227, 0x00229, 0x0022b, 0x0022d, 0x0022f, 0x00231, 0x00233, 0x0023c, 0x0023f, 0x00240, 0x00242, 0x00247, 0x00249, 0x0024b, 0x0024d, 0x0024f, 0x00250, 0x00251, 0x00252, 0x00253, 0x00254, 0x00256, 0x00257, 0x00259, 0x0025b, 0x0025c, 0x00260, 0x00261, 0x00263, 0x00265, 0x00266, 0x00268, 0x00269, 0x0026a, 0x0026b, 0x0026c, 0x0026f, 0x00271, 0x00272, 0x00275, 0x0027d, 0x00280, 0x00283, 0x00287, 0x00288, 0x00289, 0x0028a, 0x0028b, 0x0028c, 0x00292, 0x0029d, 0x0029e, 0x00345, 0x00371, 0x00373, 0x00377, 0x0037b, 0x0037c, 0x0037d, 0x003ac, 0x003ad, 0x003ae, 0x003af, 0x003b1, 0x003b2, 0x003b3, 0x003b4, 0x003b5, 0x003b6, 0x003b7, 0x003b8, 0x003b9, 0x003ba, 0x003bb, 0x003bc, 0x003bd, 0x003be, 0x003bf, 0x003c0, 0x003c1, 0x003c2, 0x003c3, 0x003c4, 0x003c5, 0x003c6, 0x003c7, 0x003c8, 0x003c9, 0x003ca, 0x003cb, 0x003cc, 0x003cd, 0x003ce, 0x003d0, 0x003d1, 0x003d5, 0x003d6, 0x003d7, 0x003d9, 0x003db, 0x003dd, 0x003df, 0x003e1, 0x003e3, 0x003e5, 0x003e7, 0x003e9, 0x003eb, 0x003ed, 0x003ef, 0x003f0, 0x003f1, 0x003f2, 0x003f3, 0x003f5, 0x003f8, 0x003fb, 0x00430, 0x00431, 0x00432, 0x00433, 0x00434, 0x00435, 0x00436, 0x00437, 0x00438, 0x00439, 0x0043a, 0x0043b, 0x0043c, 0x0043d, 0x0043e, 0x0043f, 0x00440, 0x00441, 0x00442, 0x00443, 0x00444, 0x00445, 0x00446, 0x00447, 0x00448, 0x00449, 0x0044a, 0x0044b, 0x0044c, 0x0044d, 0x0044e, 0x0044f, 0x00450, 0x00451, 0x00452, 0x00453, 0x00454, 0x00455, 0x00456, 0x00457, 0x00458, 0x00459, 0x0045a, 0x0045b, 0x0045c, 0x0045d, 0x0045e, 0x0045f, 0x00461, 0x00463, 0x00465, 0x00467, 0x00469, 0x0046b, 0x0046d, 0x0046f, 0x00471, 0x00473, 0x00475, 0x00477, 0x00479, 0x0047b, 0x0047d, 0x0047f, 0x00481, 0x0048b, 0x0048d, 0x0048f, 0x00491, 0x00493, 0x00495, 0x00497, 0x00499, 0x0049b, 0x0049d, 0x0049f, 0x004a1, 0x004a3, 0x004a5, 0x004a7, 0x004a9, 0x004ab, 0x004ad, 0x004af, 0x004b1, 0x004b3, 0x004b5, 0x004b7, 0x004b9, 0x004bb, 0x004bd, 0x004bf, 0x004c2, 0x004c4, 0x004c6, 0x004c8, 0x004ca, 0x004cc, 0x004ce, 0x004cf, 0x004d1, 0x004d3, 0x004d5, 0x004d7, 0x004d9, 0x004db, 0x004dd, 0x004df, 0x004e1, 0x004e3, 0x004e5, 0x004e7, 0x004e9, 0x004eb, 0x004ed, 0x004ef, 0x004f1, 0x004f3, 0x004f5, 0x004f7, 0x004f9, 0x004fb, 0x004fd, 0x004ff, 0x00501, 0x00503, 0x00505, 0x00507, 0x00509, 0x0050b, 0x0050d, 0x0050f, 0x00511, 0x00513, 0x00515, 0x00517, 0x00519, 0x0051b, 0x0051d, 0x0051f, 0x00521, 0x00523, 0x00525, 0x00527, 0x00529, 0x0052b, 0x0052d, 0x0052f, 0x00561, 0x00562, 0x00563, 0x00564, 0x00565, 0x00566, 0x00567, 0x00568, 0x00569, 0x0056a, 0x0056b, 0x0056c, 0x0056d, 0x0056e, 0x0056f, 0x00570, 0x00571, 0x00572, 0x00573, 0x00574, 0x00575, 0x00576, 0x00577, 0x00578, 0x00579, 0x0057a, 0x0057b, 0x0057c, 0x0057d, 0x0057e, 0x0057f, 0x00580, 0x00581, 0x00582, 0x00583, 0x00584, 0x00585, 0x00586, 0x013f8, 0x013f9, 0x013fa, 0x013fb, 0x013fc, 0x013fd, 0x01c80, 0x01c81, 0x01c82, 0x01c83, 0x01c84, 0x01c85, 0x01c86, 0x01c87, 0x01c88, 0x01d79, 0x01d7d, 0x01e01, 0x01e03, 0x01e05, 0x01e07, 0x01e09, 0x01e0b, 0x01e0d, 0x01e0f, 0x01e11, 0x01e13, 0x01e15, 0x01e17, 0x01e19, 0x01e1b, 0x01e1d, 0x01e1f, 0x01e21, 0x01e23, 0x01e25, 0x01e27, 0x01e29, 0x01e2b, 0x01e2d, 0x01e2f, 0x01e31, 0x01e33, 0x01e35, 0x01e37, 0x01e39, 0x01e3b, 0x01e3d, 0x01e3f, 0x01e41, 0x01e43, 0x01e45, 0x01e47, 0x01e49, 0x01e4b, 0x01e4d, 0x01e4f, 0x01e51, 0x01e53, 0x01e55, 0x01e57, 0x01e59, 0x01e5b, 0x01e5d, 0x01e5f, 0x01e61, 0x01e63, 0x01e65, 0x01e67, 0x01e69, 0x01e6b, 0x01e6d, 0x01e6f, 0x01e71, 0x01e73, 0x01e75, 0x01e77, 0x01e79, 0x01e7b, 0x01e7d, 0x01e7f, 0x01e81, 0x01e83, 0x01e85, 0x01e87, 0x01e89, 0x01e8b, 0x01e8d, 0x01e8f, 0x01e91, 0x01e93, 0x01e95, 0x01e9b, 0x01ea1, 0x01ea3, 0x01ea5, 0x01ea7, 0x01ea9, 0x01eab, 0x01ead, 0x01eaf, 0x01eb1, 0x01eb3, 0x01eb5, 0x01eb7, 0x01eb9, 0x01ebb, 0x01ebd, 0x01ebf, 0x01ec1, 0x01ec3, 0x01ec5, 0x01ec7, 0x01ec9, 0x01ecb, 0x01ecd, 0x01ecf, 0x01ed1, 0x01ed3, 0x01ed5, 0x01ed7, 0x01ed9, 0x01edb, 0x01edd, 0x01edf, 0x01ee1, 0x01ee3, 0x01ee5, 0x01ee7, 0x01ee9, 0x01eeb, 0x01eed, 0x01eef, 0x01ef1, 0x01ef3, 0x01ef5, 0x01ef7, 0x01ef9, 0x01efb, 0x01efd, 0x01eff, 0x01f00, 0x01f01, 0x01f02, 0x01f03, 0x01f04, 0x01f05, 0x01f06, 0x01f07, 0x01f10, 0x01f11, 0x01f12, 0x01f13, 0x01f14, 0x01f15, 0x01f20, 0x01f21, 0x01f22, 0x01f23, 0x01f24, 0x01f25, 0x01f26, 0x01f27, 0x01f30, 0x01f31, 0x01f32, 0x01f33, 0x01f34, 0x01f35, 0x01f36, 0x01f37, 0x01f40, 0x01f41, 0x01f42, 0x01f43, 0x01f44, 0x01f45, 0x01f51, 0x01f53, 0x01f55, 0x01f57, 0x01f60, 0x01f61, 0x01f62, 0x01f63, 0x01f64, 0x01f65, 0x01f66, 0x01f67, 0x01f70, 0x01f71, 0x01f72, 0x01f73, 0x01f74, 0x01f75, 0x01f76, 0x01f77, 0x01f78, 0x01f79, 0x01f7a, 0x01f7b, 0x01f7c, 0x01f7d, 0x01f80, 0x01f81, 0x01f82, 0x01f83, 0x01f84, 0x01f85, 0x01f86, 0x01f87, 0x01f90, 0x01f91, 0x01f92, 0x01f93, 0x01f94, 0x01f95, 0x01f96, 0x01f97, 0x01fa0, 0x01fa1, 0x01fa2, 0x01fa3, 0x01fa4, 0x01fa5, 0x01fa6, 0x01fa7, 0x01fb0, 0x01fb1, 0x01fb3, 0x01fbe, 0x01fc3, 0x01fd0, 0x01fd1, 0x01fe0, 0x01fe1, 0x01fe5, 0x01ff3, 0x0214e, 0x02170, 0x02171, 0x02172, 0x02173, 0x02174, 0x02175, 0x02176, 0x02177, 0x02178, 0x02179, 0x0217a, 0x0217b, 0x0217c, 0x0217d, 0x0217e, 0x0217f, 0x02184, 0x024d0, 0x024d1, 0x024d2, 0x024d3, 0x024d4, 0x024d5, 0x024d6, 0x024d7, 0x024d8, 0x024d9, 0x024da, 0x024db, 0x024dc, 0x024dd, 0x024de, 0x024df, 0x024e0, 0x024e1, 0x024e2, 0x024e3, 0x024e4, 0x024e5, 0x024e6, 0x024e7, 0x024e8, 0x024e9, 0x02c30, 0x02c31, 0x02c32, 0x02c33, 0x02c34, 0x02c35, 0x02c36, 0x02c37, 0x02c38, 0x02c39, 0x02c3a, 0x02c3b, 0x02c3c, 0x02c3d, 0x02c3e, 0x02c3f, 0x02c40, 0x02c41, 0x02c42, 0x02c43, 0x02c44, 0x02c45, 0x02c46, 0x02c47, 0x02c48, 0x02c49, 0x02c4a, 0x02c4b, 0x02c4c, 0x02c4d, 0x02c4e, 0x02c4f, 0x02c50, 0x02c51, 0x02c52, 0x02c53, 0x02c54, 0x02c55, 0x02c56, 0x02c57, 0x02c58, 0x02c59, 0x02c5a, 0x02c5b, 0x02c5c, 0x02c5d, 0x02c5e, 0x02c61, 0x02c65, 0x02c66, 0x02c68, 0x02c6a, 0x02c6c, 0x02c73, 0x02c76, 0x02c81, 0x02c83, 0x02c85, 0x02c87, 0x02c89, 0x02c8b, 0x02c8d, 0x02c8f, 0x02c91, 0x02c93, 0x02c95, 0x02c97, 0x02c99, 0x02c9b, 0x02c9d, 0x02c9f, 0x02ca1, 0x02ca3, 0x02ca5, 0x02ca7, 0x02ca9, 0x02cab, 0x02cad, 0x02caf, 0x02cb1, 0x02cb3, 0x02cb5, 0x02cb7, 0x02cb9, 0x02cbb, 0x02cbd, 0x02cbf, 0x02cc1, 0x02cc3, 0x02cc5, 0x02cc7, 0x02cc9, 0x02ccb, 0x02ccd, 0x02ccf, 0x02cd1, 0x02cd3, 0x02cd5, 0x02cd7, 0x02cd9, 0x02cdb, 0x02cdd, 0x02cdf, 0x02ce1, 0x02ce3, 0x02cec, 0x02cee, 0x02cf3, 0x02d00, 0x02d01, 0x02d02, 0x02d03, 0x02d04, 0x02d05, 0x02d06, 0x02d07, 0x02d08, 0x02d09, 0x02d0a, 0x02d0b, 0x02d0c, 0x02d0d, 0x02d0e, 0x02d0f, 0x02d10, 0x02d11, 0x02d12, 0x02d13, 0x02d14, 0x02d15, 0x02d16, 0x02d17, 0x02d18, 0x02d19, 0x02d1a, 0x02d1b, 0x02d1c, 0x02d1d, 0x02d1e, 0x02d1f, 0x02d20, 0x02d21, 0x02d22, 0x02d23, 0x02d24, 0x02d25, 0x02d27, 0x02d2d, 0x0a641, 0x0a643, 0x0a645, 0x0a647, 0x0a649, 0x0a64b, 0x0a64d, 0x0a64f, 0x0a651, 0x0a653, 0x0a655, 0x0a657, 0x0a659, 0x0a65b, 0x0a65d, 0x0a65f, 0x0a661, 0x0a663, 0x0a665, 0x0a667, 0x0a669, 0x0a66b, 0x0a66d, 0x0a681, 0x0a683, 0x0a685, 0x0a687, 0x0a689, 0x0a68b, 0x0a68d, 0x0a68f, 0x0a691, 0x0a693, 0x0a695, 0x0a697, 0x0a699, 0x0a69b, 0x0a723, 0x0a725, 0x0a727, 0x0a729, 0x0a72b, 0x0a72d, 0x0a72f, 0x0a733, 0x0a735, 0x0a737, 0x0a739, 0x0a73b, 0x0a73d, 0x0a73f, 0x0a741, 0x0a743, 0x0a745, 0x0a747, 0x0a749, 0x0a74b, 0x0a74d, 0x0a74f, 0x0a751, 0x0a753, 0x0a755, 0x0a757, 0x0a759, 0x0a75b, 0x0a75d, 0x0a75f, 0x0a761, 0x0a763, 0x0a765, 0x0a767, 0x0a769, 0x0a76b, 0x0a76d, 0x0a76f, 0x0a77a, 0x0a77c, 0x0a77f, 0x0a781, 0x0a783, 0x0a785, 0x0a787, 0x0a78c, 0x0a791, 0x0a793, 0x0a797, 0x0a799, 0x0a79b, 0x0a79d, 0x0a79f, 0x0a7a1, 0x0a7a3, 0x0a7a5, 0x0a7a7, 0x0a7a9, 0x0a7b5, 0x0a7b7, 0x0ab53, 0x0ab70, 0x0ab71, 0x0ab72, 0x0ab73, 0x0ab74, 0x0ab75, 0x0ab76, 0x0ab77, 0x0ab78, 0x0ab79, 0x0ab7a, 0x0ab7b, 0x0ab7c, 0x0ab7d, 0x0ab7e, 0x0ab7f, 0x0ab80, 0x0ab81, 0x0ab82, 0x0ab83, 0x0ab84, 0x0ab85, 0x0ab86, 0x0ab87, 0x0ab88, 0x0ab89, 0x0ab8a, 0x0ab8b, 0x0ab8c, 0x0ab8d, 0x0ab8e, 0x0ab8f, 0x0ab90, 0x0ab91, 0x0ab92, 0x0ab93, 0x0ab94, 0x0ab95, 0x0ab96, 0x0ab97, 0x0ab98, 0x0ab99, 0x0ab9a, 0x0ab9b, 0x0ab9c, 0x0ab9d, 0x0ab9e, 0x0ab9f, 0x0aba0, 0x0aba1, 0x0aba2, 0x0aba3, 0x0aba4, 0x0aba5, 0x0aba6, 0x0aba7, 0x0aba8, 0x0aba9, 0x0abaa, 0x0abab, 0x0abac, 0x0abad, 0x0abae, 0x0abaf, 0x0abb0, 0x0abb1, 0x0abb2, 0x0abb3, 0x0abb4, 0x0abb5, 0x0abb6, 0x0abb7, 0x0abb8, 0x0abb9, 0x0abba, 0x0abbb, 0x0abbc, 0x0abbd, 0x0abbe, 0x0abbf, 0x0ff41, 0x0ff42, 0x0ff43, 0x0ff44, 0x0ff45, 0x0ff46, 0x0ff47, 0x0ff48, 0x0ff49, 0x0ff4a, 0x0ff4b, 0x0ff4c, 0x0ff4d, 0x0ff4e, 0x0ff4f, 0x0ff50, 0x0ff51, 0x0ff52, 0x0ff53, 0x0ff54, 0x0ff55, 0x0ff56, 0x0ff57, 0x0ff58, 0x0ff59, 0x0ff5a }; static const uint16_t titlecase16_values[] = { 0x00100, 0x00102, 0x00104, 0x00106, 0x00108, 0x0010a, 0x0010c, 0x0010e, 0x00110, 0x00112, 0x00114, 0x00116, 0x00118, 0x0011a, 0x0011c, 0x0011e, 0x00120, 0x00122, 0x00124, 0x00126, 0x00128, 0x0012a, 0x0012c, 0x0012e, 0x00049, 0x00132, 0x00134, 0x00136, 0x00139, 0x0013b, 0x0013d, 0x0013f, 0x00141, 0x00143, 0x00145, 0x00147, 0x0014a, 0x0014c, 0x0014e, 0x00150, 0x00152, 0x00154, 0x00156, 0x00158, 0x0015a, 0x0015c, 0x0015e, 0x00160, 0x00162, 0x00164, 0x00166, 0x00168, 0x0016a, 0x0016c, 0x0016e, 0x00170, 0x00172, 0x00174, 0x00176, 0x00179, 0x0017b, 0x0017d, 0x00053, 0x00243, 0x00182, 0x00184, 0x00187, 0x0018b, 0x00191, 0x001f6, 0x00198, 0x0023d, 0x00220, 0x001a0, 0x001a2, 0x001a4, 0x001a7, 0x001ac, 0x001af, 0x001b3, 0x001b5, 0x001b8, 0x001bc, 0x001f7, 0x001c5, 0x001c5, 0x001c8, 0x001c8, 0x001cb, 0x001cb, 0x001cd, 0x001cf, 0x001d1, 0x001d3, 0x001d5, 0x001d7, 0x001d9, 0x001db, 0x0018e, 0x001de, 0x001e0, 0x001e2, 0x001e4, 0x001e6, 0x001e8, 0x001ea, 0x001ec, 0x001ee, 0x001f2, 0x001f2, 0x001f4, 0x001f8, 0x001fa, 0x001fc, 0x001fe, 0x00200, 0x00202, 0x00204, 0x00206, 0x00208, 0x0020a, 0x0020c, 0x0020e, 0x00210, 0x00212, 0x00214, 0x00216, 0x00218, 0x0021a, 0x0021c, 0x0021e, 0x00222, 0x00224, 0x00226, 0x00228, 0x0022a, 0x0022c, 0x0022e, 0x00230, 0x00232, 0x0023b, 0x02c7e, 0x02c7f, 0x00241, 0x00246, 0x00248, 0x0024a, 0x0024c, 0x0024e, 0x02c6f, 0x02c6d, 0x02c70, 0x00181, 0x00186, 0x00189, 0x0018a, 0x0018f, 0x00190, 0x0a7ab, 0x00193, 0x0a7ac, 0x00194, 0x0a78d, 0x0a7aa, 0x00197, 0x00196, 0x0a7ae, 0x02c62, 0x0a7ad, 0x0019c, 0x02c6e, 0x0019d, 0x0019f, 0x02c64, 0x001a6, 0x001a9, 0x0a7b1, 0x001ae, 0x00244, 0x001b1, 0x001b2, 0x00245, 0x001b7, 0x0a7b2, 0x0a7b0, 0x00399, 0x00370, 0x00372, 0x00376, 0x003fd, 0x003fe, 0x003ff, 0x00386, 0x00388, 0x00389, 0x0038a, 0x00391, 0x00392, 0x00393, 0x00394, 0x00395, 0x00396, 0x00397, 0x00398, 0x00399, 0x0039a, 0x0039b, 0x0039c, 0x0039d, 0x0039e, 0x0039f, 0x003a0, 0x003a1, 0x003a3, 0x003a3, 0x003a4, 0x003a5, 0x003a6, 0x003a7, 0x003a8, 0x003a9, 0x003aa, 0x003ab, 0x0038c, 0x0038e, 0x0038f, 0x00392, 0x00398, 0x003a6, 0x003a0, 0x003cf, 0x003d8, 0x003da, 0x003dc, 0x003de, 0x003e0, 0x003e2, 0x003e4, 0x003e6, 0x003e8, 0x003ea, 0x003ec, 0x003ee, 0x0039a, 0x003a1, 0x003f9, 0x0037f, 0x00395, 0x003f7, 0x003fa, 0x00410, 0x00411, 0x00412, 0x00413, 0x00414, 0x00415, 0x00416, 0x00417, 0x00418, 0x00419, 0x0041a, 0x0041b, 0x0041c, 0x0041d, 0x0041e, 0x0041f, 0x00420, 0x00421, 0x00422, 0x00423, 0x00424, 0x00425, 0x00426, 0x00427, 0x00428, 0x00429, 0x0042a, 0x0042b, 0x0042c, 0x0042d, 0x0042e, 0x0042f, 0x00400, 0x00401, 0x00402, 0x00403, 0x00404, 0x00405, 0x00406, 0x00407, 0x00408, 0x00409, 0x0040a, 0x0040b, 0x0040c, 0x0040d, 0x0040e, 0x0040f, 0x00460, 0x00462, 0x00464, 0x00466, 0x00468, 0x0046a, 0x0046c, 0x0046e, 0x00470, 0x00472, 0x00474, 0x00476, 0x00478, 0x0047a, 0x0047c, 0x0047e, 0x00480, 0x0048a, 0x0048c, 0x0048e, 0x00490, 0x00492, 0x00494, 0x00496, 0x00498, 0x0049a, 0x0049c, 0x0049e, 0x004a0, 0x004a2, 0x004a4, 0x004a6, 0x004a8, 0x004aa, 0x004ac, 0x004ae, 0x004b0, 0x004b2, 0x004b4, 0x004b6, 0x004b8, 0x004ba, 0x004bc, 0x004be, 0x004c1, 0x004c3, 0x004c5, 0x004c7, 0x004c9, 0x004cb, 0x004cd, 0x004c0, 0x004d0, 0x004d2, 0x004d4, 0x004d6, 0x004d8, 0x004da, 0x004dc, 0x004de, 0x004e0, 0x004e2, 0x004e4, 0x004e6, 0x004e8, 0x004ea, 0x004ec, 0x004ee, 0x004f0, 0x004f2, 0x004f4, 0x004f6, 0x004f8, 0x004fa, 0x004fc, 0x004fe, 0x00500, 0x00502, 0x00504, 0x00506, 0x00508, 0x0050a, 0x0050c, 0x0050e, 0x00510, 0x00512, 0x00514, 0x00516, 0x00518, 0x0051a, 0x0051c, 0x0051e, 0x00520, 0x00522, 0x00524, 0x00526, 0x00528, 0x0052a, 0x0052c, 0x0052e, 0x00531, 0x00532, 0x00533, 0x00534, 0x00535, 0x00536, 0x00537, 0x00538, 0x00539, 0x0053a, 0x0053b, 0x0053c, 0x0053d, 0x0053e, 0x0053f, 0x00540, 0x00541, 0x00542, 0x00543, 0x00544, 0x00545, 0x00546, 0x00547, 0x00548, 0x00549, 0x0054a, 0x0054b, 0x0054c, 0x0054d, 0x0054e, 0x0054f, 0x00550, 0x00551, 0x00552, 0x00553, 0x00554, 0x00555, 0x00556, 0x013f0, 0x013f1, 0x013f2, 0x013f3, 0x013f4, 0x013f5, 0x00412, 0x00414, 0x0041e, 0x00421, 0x00422, 0x00422, 0x0042a, 0x00462, 0x0a64a, 0x0a77d, 0x02c63, 0x01e00, 0x01e02, 0x01e04, 0x01e06, 0x01e08, 0x01e0a, 0x01e0c, 0x01e0e, 0x01e10, 0x01e12, 0x01e14, 0x01e16, 0x01e18, 0x01e1a, 0x01e1c, 0x01e1e, 0x01e20, 0x01e22, 0x01e24, 0x01e26, 0x01e28, 0x01e2a, 0x01e2c, 0x01e2e, 0x01e30, 0x01e32, 0x01e34, 0x01e36, 0x01e38, 0x01e3a, 0x01e3c, 0x01e3e, 0x01e40, 0x01e42, 0x01e44, 0x01e46, 0x01e48, 0x01e4a, 0x01e4c, 0x01e4e, 0x01e50, 0x01e52, 0x01e54, 0x01e56, 0x01e58, 0x01e5a, 0x01e5c, 0x01e5e, 0x01e60, 0x01e62, 0x01e64, 0x01e66, 0x01e68, 0x01e6a, 0x01e6c, 0x01e6e, 0x01e70, 0x01e72, 0x01e74, 0x01e76, 0x01e78, 0x01e7a, 0x01e7c, 0x01e7e, 0x01e80, 0x01e82, 0x01e84, 0x01e86, 0x01e88, 0x01e8a, 0x01e8c, 0x01e8e, 0x01e90, 0x01e92, 0x01e94, 0x01e60, 0x01ea0, 0x01ea2, 0x01ea4, 0x01ea6, 0x01ea8, 0x01eaa, 0x01eac, 0x01eae, 0x01eb0, 0x01eb2, 0x01eb4, 0x01eb6, 0x01eb8, 0x01eba, 0x01ebc, 0x01ebe, 0x01ec0, 0x01ec2, 0x01ec4, 0x01ec6, 0x01ec8, 0x01eca, 0x01ecc, 0x01ece, 0x01ed0, 0x01ed2, 0x01ed4, 0x01ed6, 0x01ed8, 0x01eda, 0x01edc, 0x01ede, 0x01ee0, 0x01ee2, 0x01ee4, 0x01ee6, 0x01ee8, 0x01eea, 0x01eec, 0x01eee, 0x01ef0, 0x01ef2, 0x01ef4, 0x01ef6, 0x01ef8, 0x01efa, 0x01efc, 0x01efe, 0x01f08, 0x01f09, 0x01f0a, 0x01f0b, 0x01f0c, 0x01f0d, 0x01f0e, 0x01f0f, 0x01f18, 0x01f19, 0x01f1a, 0x01f1b, 0x01f1c, 0x01f1d, 0x01f28, 0x01f29, 0x01f2a, 0x01f2b, 0x01f2c, 0x01f2d, 0x01f2e, 0x01f2f, 0x01f38, 0x01f39, 0x01f3a, 0x01f3b, 0x01f3c, 0x01f3d, 0x01f3e, 0x01f3f, 0x01f48, 0x01f49, 0x01f4a, 0x01f4b, 0x01f4c, 0x01f4d, 0x01f59, 0x01f5b, 0x01f5d, 0x01f5f, 0x01f68, 0x01f69, 0x01f6a, 0x01f6b, 0x01f6c, 0x01f6d, 0x01f6e, 0x01f6f, 0x01fba, 0x01fbb, 0x01fc8, 0x01fc9, 0x01fca, 0x01fcb, 0x01fda, 0x01fdb, 0x01ff8, 0x01ff9, 0x01fea, 0x01feb, 0x01ffa, 0x01ffb, 0x01f88, 0x01f89, 0x01f8a, 0x01f8b, 0x01f8c, 0x01f8d, 0x01f8e, 0x01f8f, 0x01f98, 0x01f99, 0x01f9a, 0x01f9b, 0x01f9c, 0x01f9d, 0x01f9e, 0x01f9f, 0x01fa8, 0x01fa9, 0x01faa, 0x01fab, 0x01fac, 0x01fad, 0x01fae, 0x01faf, 0x01fb8, 0x01fb9, 0x01fbc, 0x00399, 0x01fcc, 0x01fd8, 0x01fd9, 0x01fe8, 0x01fe9, 0x01fec, 0x01ffc, 0x02132, 0x02160, 0x02161, 0x02162, 0x02163, 0x02164, 0x02165, 0x02166, 0x02167, 0x02168, 0x02169, 0x0216a, 0x0216b, 0x0216c, 0x0216d, 0x0216e, 0x0216f, 0x02183, 0x024b6, 0x024b7, 0x024b8, 0x024b9, 0x024ba, 0x024bb, 0x024bc, 0x024bd, 0x024be, 0x024bf, 0x024c0, 0x024c1, 0x024c2, 0x024c3, 0x024c4, 0x024c5, 0x024c6, 0x024c7, 0x024c8, 0x024c9, 0x024ca, 0x024cb, 0x024cc, 0x024cd, 0x024ce, 0x024cf, 0x02c00, 0x02c01, 0x02c02, 0x02c03, 0x02c04, 0x02c05, 0x02c06, 0x02c07, 0x02c08, 0x02c09, 0x02c0a, 0x02c0b, 0x02c0c, 0x02c0d, 0x02c0e, 0x02c0f, 0x02c10, 0x02c11, 0x02c12, 0x02c13, 0x02c14, 0x02c15, 0x02c16, 0x02c17, 0x02c18, 0x02c19, 0x02c1a, 0x02c1b, 0x02c1c, 0x02c1d, 0x02c1e, 0x02c1f, 0x02c20, 0x02c21, 0x02c22, 0x02c23, 0x02c24, 0x02c25, 0x02c26, 0x02c27, 0x02c28, 0x02c29, 0x02c2a, 0x02c2b, 0x02c2c, 0x02c2d, 0x02c2e, 0x02c60, 0x0023a, 0x0023e, 0x02c67, 0x02c69, 0x02c6b, 0x02c72, 0x02c75, 0x02c80, 0x02c82, 0x02c84, 0x02c86, 0x02c88, 0x02c8a, 0x02c8c, 0x02c8e, 0x02c90, 0x02c92, 0x02c94, 0x02c96, 0x02c98, 0x02c9a, 0x02c9c, 0x02c9e, 0x02ca0, 0x02ca2, 0x02ca4, 0x02ca6, 0x02ca8, 0x02caa, 0x02cac, 0x02cae, 0x02cb0, 0x02cb2, 0x02cb4, 0x02cb6, 0x02cb8, 0x02cba, 0x02cbc, 0x02cbe, 0x02cc0, 0x02cc2, 0x02cc4, 0x02cc6, 0x02cc8, 0x02cca, 0x02ccc, 0x02cce, 0x02cd0, 0x02cd2, 0x02cd4, 0x02cd6, 0x02cd8, 0x02cda, 0x02cdc, 0x02cde, 0x02ce0, 0x02ce2, 0x02ceb, 0x02ced, 0x02cf2, 0x010a0, 0x010a1, 0x010a2, 0x010a3, 0x010a4, 0x010a5, 0x010a6, 0x010a7, 0x010a8, 0x010a9, 0x010aa, 0x010ab, 0x010ac, 0x010ad, 0x010ae, 0x010af, 0x010b0, 0x010b1, 0x010b2, 0x010b3, 0x010b4, 0x010b5, 0x010b6, 0x010b7, 0x010b8, 0x010b9, 0x010ba, 0x010bb, 0x010bc, 0x010bd, 0x010be, 0x010bf, 0x010c0, 0x010c1, 0x010c2, 0x010c3, 0x010c4, 0x010c5, 0x010c7, 0x010cd, 0x0a640, 0x0a642, 0x0a644, 0x0a646, 0x0a648, 0x0a64a, 0x0a64c, 0x0a64e, 0x0a650, 0x0a652, 0x0a654, 0x0a656, 0x0a658, 0x0a65a, 0x0a65c, 0x0a65e, 0x0a660, 0x0a662, 0x0a664, 0x0a666, 0x0a668, 0x0a66a, 0x0a66c, 0x0a680, 0x0a682, 0x0a684, 0x0a686, 0x0a688, 0x0a68a, 0x0a68c, 0x0a68e, 0x0a690, 0x0a692, 0x0a694, 0x0a696, 0x0a698, 0x0a69a, 0x0a722, 0x0a724, 0x0a726, 0x0a728, 0x0a72a, 0x0a72c, 0x0a72e, 0x0a732, 0x0a734, 0x0a736, 0x0a738, 0x0a73a, 0x0a73c, 0x0a73e, 0x0a740, 0x0a742, 0x0a744, 0x0a746, 0x0a748, 0x0a74a, 0x0a74c, 0x0a74e, 0x0a750, 0x0a752, 0x0a754, 0x0a756, 0x0a758, 0x0a75a, 0x0a75c, 0x0a75e, 0x0a760, 0x0a762, 0x0a764, 0x0a766, 0x0a768, 0x0a76a, 0x0a76c, 0x0a76e, 0x0a779, 0x0a77b, 0x0a77e, 0x0a780, 0x0a782, 0x0a784, 0x0a786, 0x0a78b, 0x0a790, 0x0a792, 0x0a796, 0x0a798, 0x0a79a, 0x0a79c, 0x0a79e, 0x0a7a0, 0x0a7a2, 0x0a7a4, 0x0a7a6, 0x0a7a8, 0x0a7b4, 0x0a7b6, 0x0a7b3, 0x013a0, 0x013a1, 0x013a2, 0x013a3, 0x013a4, 0x013a5, 0x013a6, 0x013a7, 0x013a8, 0x013a9, 0x013aa, 0x013ab, 0x013ac, 0x013ad, 0x013ae, 0x013af, 0x013b0, 0x013b1, 0x013b2, 0x013b3, 0x013b4, 0x013b5, 0x013b6, 0x013b7, 0x013b8, 0x013b9, 0x013ba, 0x013bb, 0x013bc, 0x013bd, 0x013be, 0x013bf, 0x013c0, 0x013c1, 0x013c2, 0x013c3, 0x013c4, 0x013c5, 0x013c6, 0x013c7, 0x013c8, 0x013c9, 0x013ca, 0x013cb, 0x013cc, 0x013cd, 0x013ce, 0x013cf, 0x013d0, 0x013d1, 0x013d2, 0x013d3, 0x013d4, 0x013d5, 0x013d6, 0x013d7, 0x013d8, 0x013d9, 0x013da, 0x013db, 0x013dc, 0x013dd, 0x013de, 0x013df, 0x013e0, 0x013e1, 0x013e2, 0x013e3, 0x013e4, 0x013e5, 0x013e6, 0x013e7, 0x013e8, 0x013e9, 0x013ea, 0x013eb, 0x013ec, 0x013ed, 0x013ee, 0x013ef, 0x0ff21, 0x0ff22, 0x0ff23, 0x0ff24, 0x0ff25, 0x0ff26, 0x0ff27, 0x0ff28, 0x0ff29, 0x0ff2a, 0x0ff2b, 0x0ff2c, 0x0ff2d, 0x0ff2e, 0x0ff2f, 0x0ff30, 0x0ff31, 0x0ff32, 0x0ff33, 0x0ff34, 0x0ff35, 0x0ff36, 0x0ff37, 0x0ff38, 0x0ff39, 0x0ff3a }; static const uint32_t titlecase32_keys[] = { 0x10428, 0x10429, 0x1042a, 0x1042b, 0x1042c, 0x1042d, 0x1042e, 0x1042f, 0x10430, 0x10431, 0x10432, 0x10433, 0x10434, 0x10435, 0x10436, 0x10437, 0x10438, 0x10439, 0x1043a, 0x1043b, 0x1043c, 0x1043d, 0x1043e, 0x1043f, 0x10440, 0x10441, 0x10442, 0x10443, 0x10444, 0x10445, 0x10446, 0x10447, 0x10448, 0x10449, 0x1044a, 0x1044b, 0x1044c, 0x1044d, 0x1044e, 0x1044f, 0x104d8, 0x104d9, 0x104da, 0x104db, 0x104dc, 0x104dd, 0x104de, 0x104df, 0x104e0, 0x104e1, 0x104e2, 0x104e3, 0x104e4, 0x104e5, 0x104e6, 0x104e7, 0x104e8, 0x104e9, 0x104ea, 0x104eb, 0x104ec, 0x104ed, 0x104ee, 0x104ef, 0x104f0, 0x104f1, 0x104f2, 0x104f3, 0x104f4, 0x104f5, 0x104f6, 0x104f7, 0x104f8, 0x104f9, 0x104fa, 0x104fb, 0x10cc0, 0x10cc1, 0x10cc2, 0x10cc3, 0x10cc4, 0x10cc5, 0x10cc6, 0x10cc7, 0x10cc8, 0x10cc9, 0x10cca, 0x10ccb, 0x10ccc, 0x10ccd, 0x10cce, 0x10ccf, 0x10cd0, 0x10cd1, 0x10cd2, 0x10cd3, 0x10cd4, 0x10cd5, 0x10cd6, 0x10cd7, 0x10cd8, 0x10cd9, 0x10cda, 0x10cdb, 0x10cdc, 0x10cdd, 0x10cde, 0x10cdf, 0x10ce0, 0x10ce1, 0x10ce2, 0x10ce3, 0x10ce4, 0x10ce5, 0x10ce6, 0x10ce7, 0x10ce8, 0x10ce9, 0x10cea, 0x10ceb, 0x10cec, 0x10ced, 0x10cee, 0x10cef, 0x10cf0, 0x10cf1, 0x10cf2, 0x118c0, 0x118c1, 0x118c2, 0x118c3, 0x118c4, 0x118c5, 0x118c6, 0x118c7, 0x118c8, 0x118c9, 0x118ca, 0x118cb, 0x118cc, 0x118cd, 0x118ce, 0x118cf, 0x118d0, 0x118d1, 0x118d2, 0x118d3, 0x118d4, 0x118d5, 0x118d6, 0x118d7, 0x118d8, 0x118d9, 0x118da, 0x118db, 0x118dc, 0x118dd, 0x118de, 0x118df, 0x1e922, 0x1e923, 0x1e924, 0x1e925, 0x1e926, 0x1e927, 0x1e928, 0x1e929, 0x1e92a, 0x1e92b, 0x1e92c, 0x1e92d, 0x1e92e, 0x1e92f, 0x1e930, 0x1e931, 0x1e932, 0x1e933, 0x1e934, 0x1e935, 0x1e936, 0x1e937, 0x1e938, 0x1e939, 0x1e93a, 0x1e93b, 0x1e93c, 0x1e93d, 0x1e93e, 0x1e93f, 0x1e940, 0x1e941, 0x1e942, 0x1e943 }; static const uint32_t titlecase32_values[] = { 0x10400, 0x10401, 0x10402, 0x10403, 0x10404, 0x10405, 0x10406, 0x10407, 0x10408, 0x10409, 0x1040a, 0x1040b, 0x1040c, 0x1040d, 0x1040e, 0x1040f, 0x10410, 0x10411, 0x10412, 0x10413, 0x10414, 0x10415, 0x10416, 0x10417, 0x10418, 0x10419, 0x1041a, 0x1041b, 0x1041c, 0x1041d, 0x1041e, 0x1041f, 0x10420, 0x10421, 0x10422, 0x10423, 0x10424, 0x10425, 0x10426, 0x10427, 0x104b0, 0x104b1, 0x104b2, 0x104b3, 0x104b4, 0x104b5, 0x104b6, 0x104b7, 0x104b8, 0x104b9, 0x104ba, 0x104bb, 0x104bc, 0x104bd, 0x104be, 0x104bf, 0x104c0, 0x104c1, 0x104c2, 0x104c3, 0x104c4, 0x104c5, 0x104c6, 0x104c7, 0x104c8, 0x104c9, 0x104ca, 0x104cb, 0x104cc, 0x104cd, 0x104ce, 0x104cf, 0x104d0, 0x104d1, 0x104d2, 0x104d3, 0x10c80, 0x10c81, 0x10c82, 0x10c83, 0x10c84, 0x10c85, 0x10c86, 0x10c87, 0x10c88, 0x10c89, 0x10c8a, 0x10c8b, 0x10c8c, 0x10c8d, 0x10c8e, 0x10c8f, 0x10c90, 0x10c91, 0x10c92, 0x10c93, 0x10c94, 0x10c95, 0x10c96, 0x10c97, 0x10c98, 0x10c99, 0x10c9a, 0x10c9b, 0x10c9c, 0x10c9d, 0x10c9e, 0x10c9f, 0x10ca0, 0x10ca1, 0x10ca2, 0x10ca3, 0x10ca4, 0x10ca5, 0x10ca6, 0x10ca7, 0x10ca8, 0x10ca9, 0x10caa, 0x10cab, 0x10cac, 0x10cad, 0x10cae, 0x10caf, 0x10cb0, 0x10cb1, 0x10cb2, 0x118a0, 0x118a1, 0x118a2, 0x118a3, 0x118a4, 0x118a5, 0x118a6, 0x118a7, 0x118a8, 0x118a9, 0x118aa, 0x118ab, 0x118ac, 0x118ad, 0x118ae, 0x118af, 0x118b0, 0x118b1, 0x118b2, 0x118b3, 0x118b4, 0x118b5, 0x118b6, 0x118b7, 0x118b8, 0x118b9, 0x118ba, 0x118bb, 0x118bc, 0x118bd, 0x118be, 0x118bf, 0x1e900, 0x1e901, 0x1e902, 0x1e903, 0x1e904, 0x1e905, 0x1e906, 0x1e907, 0x1e908, 0x1e909, 0x1e90a, 0x1e90b, 0x1e90c, 0x1e90d, 0x1e90e, 0x1e90f, 0x1e910, 0x1e911, 0x1e912, 0x1e913, 0x1e914, 0x1e915, 0x1e916, 0x1e917, 0x1e918, 0x1e919, 0x1e91a, 0x1e91b, 0x1e91c, 0x1e91d, 0x1e91e, 0x1e91f, 0x1e920, 0x1e921 }; static const uint16_t uni8_decomp_map[256] = { 0x00000, 0x00001, 0x00002, 0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a, 0x0000b, 0x0000c, 0x0000d, 0x0000e, 0x0000f, 0x00010, 0x00011, 0x00012, 0x00013, 0x00014, 0x00015, 0x00016, 0x00017, 0x00018, 0x00019, 0x0001a, 0x0001b, 0x0001c, 0x0001d, 0x0001e, 0x0001f, 0x00020, 0x00021, 0x00022, 0x00023, 0x00024, 0x00025, 0x00026, 0x00027, 0x00028, 0x00029, 0x0002a, 0x0002b, 0x0002c, 0x0002d, 0x0002e, 0x0002f, 0x00030, 0x00031, 0x00032, 0x00033, 0x00034, 0x00035, 0x00036, 0x00037, 0x00038, 0x00039, 0x0003a, 0x0003b, 0x0003c, 0x0003d, 0x0003e, 0x0003f, 0x00040, 0x00041, 0x00042, 0x00043, 0x00044, 0x00045, 0x00046, 0x00047, 0x00048, 0x00049, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004e, 0x0004f, 0x00050, 0x00051, 0x00052, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x0005a, 0x0005b, 0x0005c, 0x0005d, 0x0005e, 0x0005f, 0x00060, 0x00061, 0x00062, 0x00063, 0x00064, 0x00065, 0x00066, 0x00067, 0x00068, 0x00069, 0x0006a, 0x0006b, 0x0006c, 0x0006d, 0x0006e, 0x0006f, 0x00070, 0x00071, 0x00072, 0x00073, 0x00074, 0x00075, 0x00076, 0x00077, 0x00078, 0x00079, 0x0007a, 0x0007b, 0x0007c, 0x0007d, 0x0007e, 0x0007f, 0x00080, 0x00081, 0x00082, 0x00083, 0x00084, 0x00085, 0x00086, 0x00087, 0x00088, 0x00089, 0x0008a, 0x0008b, 0x0008c, 0x0008d, 0x0008e, 0x0008f, 0x00090, 0x00091, 0x00092, 0x00093, 0x00094, 0x00095, 0x00096, 0x00097, 0x00098, 0x00099, 0x0009a, 0x0009b, 0x0009c, 0x0009d, 0x0009e, 0x0009f, 0x00020, 0x000a1, 0x000a2, 0x000a3, 0x000a4, 0x000a5, 0x000a6, 0x000a7, 0x000a8, 0x000a9, 0x00061, 0x000ab, 0x000ac, 0x000ad, 0x000ae, 0x000af, 0x000b0, 0x000b1, 0x00032, 0x00033, 0x000b4, 0x000b5, 0x000b6, 0x000b7, 0x000b8, 0x00031, 0x0006f, 0x000bb, 0x000bc, 0x000bd, 0x000be, 0x000bf, 0x000c0, 0x000c1, 0x000c2, 0x000c3, 0x000c4, 0x000c5, 0x000c6, 0x000c7, 0x000c8, 0x000c9, 0x000ca, 0x000cb, 0x000cc, 0x000cd, 0x000ce, 0x000cf, 0x000d0, 0x000d1, 0x000d2, 0x000d3, 0x000d4, 0x000d5, 0x000d6, 0x000d7, 0x000d8, 0x000d9, 0x000da, 0x000db, 0x000dc, 0x000dd, 0x000de, 0x000df, 0x000e0, 0x000e1, 0x000e2, 0x000e3, 0x000e4, 0x000e5, 0x000e6, 0x000e7, 0x000e8, 0x000e9, 0x000ea, 0x000eb, 0x000ec, 0x000ed, 0x000ee, 0x000ef, 0x000f0, 0x000f1, 0x000f2, 0x000f3, 0x000f4, 0x000f5, 0x000f6, 0x000f7, 0x000f8, 0x000f9, 0x000fa, 0x000fb, 0x000fc, 0x000fd, 0x000fe, 0x000ff }; static const uint16_t uni16_decomp_keys[] = { 0x002b0, 0x002b1, 0x002b2, 0x002b3, 0x002b4, 0x002b5, 0x002b6, 0x002b7, 0x002b8, 0x002e0, 0x002e1, 0x002e2, 0x002e3, 0x002e4, 0x00340, 0x00341, 0x00343, 0x00374, 0x0037e, 0x00387, 0x003d2, 0x003f4, 0x003f9, 0x00f0c, 0x010fc, 0x01d2c, 0x01d2d, 0x01d2e, 0x01d30, 0x01d31, 0x01d32, 0x01d33, 0x01d34, 0x01d35, 0x01d36, 0x01d37, 0x01d38, 0x01d39, 0x01d3a, 0x01d3c, 0x01d3d, 0x01d3e, 0x01d3f, 0x01d40, 0x01d41, 0x01d42, 0x01d43, 0x01d44, 0x01d45, 0x01d46, 0x01d47, 0x01d48, 0x01d49, 0x01d4a, 0x01d4b, 0x01d4c, 0x01d4d, 0x01d4f, 0x01d50, 0x01d51, 0x01d52, 0x01d53, 0x01d54, 0x01d55, 0x01d56, 0x01d57, 0x01d58, 0x01d59, 0x01d5a, 0x01d5b, 0x01d5c, 0x01d5d, 0x01d5e, 0x01d5f, 0x01d60, 0x01d61, 0x01d62, 0x01d63, 0x01d64, 0x01d65, 0x01d66, 0x01d67, 0x01d68, 0x01d69, 0x01d6a, 0x01d78, 0x01d9b, 0x01d9c, 0x01d9d, 0x01d9e, 0x01d9f, 0x01da0, 0x01da1, 0x01da2, 0x01da3, 0x01da4, 0x01da5, 0x01da6, 0x01da7, 0x01da8, 0x01da9, 0x01daa, 0x01dab, 0x01dac, 0x01dad, 0x01dae, 0x01daf, 0x01db0, 0x01db1, 0x01db2, 0x01db3, 0x01db4, 0x01db5, 0x01db6, 0x01db7, 0x01db8, 0x01db9, 0x01dba, 0x01dbb, 0x01dbc, 0x01dbd, 0x01dbe, 0x01dbf, 0x01fbb, 0x01fc9, 0x01fcb, 0x01fd3, 0x01fdb, 0x01fe3, 0x01feb, 0x01fee, 0x01fef, 0x01ff9, 0x01ffb, 0x01ffd, 0x02000, 0x02001, 0x02002, 0x02003, 0x02004, 0x02005, 0x02006, 0x02007, 0x02008, 0x02009, 0x0200a, 0x02011, 0x02024, 0x0202f, 0x0205f, 0x02070, 0x02071, 0x02074, 0x02075, 0x02076, 0x02077, 0x02078, 0x02079, 0x0207a, 0x0207b, 0x0207c, 0x0207d, 0x0207e, 0x0207f, 0x02080, 0x02081, 0x02082, 0x02083, 0x02084, 0x02085, 0x02086, 0x02087, 0x02088, 0x02089, 0x0208a, 0x0208b, 0x0208c, 0x0208d, 0x0208e, 0x02090, 0x02091, 0x02092, 0x02093, 0x02094, 0x02095, 0x02096, 0x02097, 0x02098, 0x02099, 0x0209a, 0x0209b, 0x0209c, 0x02102, 0x02107, 0x0210a, 0x0210b, 0x0210c, 0x0210d, 0x0210e, 0x0210f, 0x02110, 0x02111, 0x02112, 0x02113, 0x02115, 0x02119, 0x0211a, 0x0211b, 0x0211c, 0x0211d, 0x02124, 0x02126, 0x02128, 0x0212a, 0x0212b, 0x0212c, 0x0212d, 0x0212f, 0x02130, 0x02131, 0x02133, 0x02134, 0x02135, 0x02136, 0x02137, 0x02138, 0x02139, 0x0213c, 0x0213d, 0x0213e, 0x0213f, 0x02140, 0x02145, 0x02146, 0x02147, 0x02148, 0x02149, 0x02160, 0x02164, 0x02169, 0x0216c, 0x0216d, 0x0216e, 0x0216f, 0x02329, 0x0232a, 0x02460, 0x02461, 0x02462, 0x02463, 0x02464, 0x02465, 0x02466, 0x02467, 0x02468, 0x024b6, 0x024b7, 0x024b8, 0x024b9, 0x024ba, 0x024bb, 0x024bc, 0x024bd, 0x024be, 0x024bf, 0x024c0, 0x024c1, 0x024c2, 0x024c3, 0x024c4, 0x024c5, 0x024c6, 0x024c7, 0x024c8, 0x024c9, 0x024ca, 0x024cb, 0x024cc, 0x024cd, 0x024ce, 0x024cf, 0x024ea, 0x02c7c, 0x02c7d, 0x02d6f, 0x02e9f, 0x02ef3, 0x02f00, 0x02f01, 0x02f02, 0x02f03, 0x02f04, 0x02f05, 0x02f06, 0x02f07, 0x02f08, 0x02f09, 0x02f0a, 0x02f0b, 0x02f0c, 0x02f0d, 0x02f0e, 0x02f0f, 0x02f10, 0x02f11, 0x02f12, 0x02f13, 0x02f14, 0x02f15, 0x02f16, 0x02f17, 0x02f18, 0x02f19, 0x02f1a, 0x02f1b, 0x02f1c, 0x02f1d, 0x02f1e, 0x02f1f, 0x02f20, 0x02f21, 0x02f22, 0x02f23, 0x02f24, 0x02f25, 0x02f26, 0x02f27, 0x02f28, 0x02f29, 0x02f2a, 0x02f2b, 0x02f2c, 0x02f2d, 0x02f2e, 0x02f2f, 0x02f30, 0x02f31, 0x02f32, 0x02f33, 0x02f34, 0x02f35, 0x02f36, 0x02f37, 0x02f38, 0x02f39, 0x02f3a, 0x02f3b, 0x02f3c, 0x02f3d, 0x02f3e, 0x02f3f, 0x02f40, 0x02f41, 0x02f42, 0x02f43, 0x02f44, 0x02f45, 0x02f46, 0x02f47, 0x02f48, 0x02f49, 0x02f4a, 0x02f4b, 0x02f4c, 0x02f4d, 0x02f4e, 0x02f4f, 0x02f50, 0x02f51, 0x02f52, 0x02f53, 0x02f54, 0x02f55, 0x02f56, 0x02f57, 0x02f58, 0x02f59, 0x02f5a, 0x02f5b, 0x02f5c, 0x02f5d, 0x02f5e, 0x02f5f, 0x02f60, 0x02f61, 0x02f62, 0x02f63, 0x02f64, 0x02f65, 0x02f66, 0x02f67, 0x02f68, 0x02f69, 0x02f6a, 0x02f6b, 0x02f6c, 0x02f6d, 0x02f6e, 0x02f6f, 0x02f70, 0x02f71, 0x02f72, 0x02f73, 0x02f74, 0x02f75, 0x02f76, 0x02f77, 0x02f78, 0x02f79, 0x02f7a, 0x02f7b, 0x02f7c, 0x02f7d, 0x02f7e, 0x02f7f, 0x02f80, 0x02f81, 0x02f82, 0x02f83, 0x02f84, 0x02f85, 0x02f86, 0x02f87, 0x02f88, 0x02f89, 0x02f8a, 0x02f8b, 0x02f8c, 0x02f8d, 0x02f8e, 0x02f8f, 0x02f90, 0x02f91, 0x02f92, 0x02f93, 0x02f94, 0x02f95, 0x02f96, 0x02f97, 0x02f98, 0x02f99, 0x02f9a, 0x02f9b, 0x02f9c, 0x02f9d, 0x02f9e, 0x02f9f, 0x02fa0, 0x02fa1, 0x02fa2, 0x02fa3, 0x02fa4, 0x02fa5, 0x02fa6, 0x02fa7, 0x02fa8, 0x02fa9, 0x02faa, 0x02fab, 0x02fac, 0x02fad, 0x02fae, 0x02faf, 0x02fb0, 0x02fb1, 0x02fb2, 0x02fb3, 0x02fb4, 0x02fb5, 0x02fb6, 0x02fb7, 0x02fb8, 0x02fb9, 0x02fba, 0x02fbb, 0x02fbc, 0x02fbd, 0x02fbe, 0x02fbf, 0x02fc0, 0x02fc1, 0x02fc2, 0x02fc3, 0x02fc4, 0x02fc5, 0x02fc6, 0x02fc7, 0x02fc8, 0x02fc9, 0x02fca, 0x02fcb, 0x02fcc, 0x02fcd, 0x02fce, 0x02fcf, 0x02fd0, 0x02fd1, 0x02fd2, 0x02fd3, 0x02fd4, 0x02fd5, 0x03000, 0x03036, 0x03038, 0x03039, 0x0303a, 0x03131, 0x03132, 0x03133, 0x03134, 0x03135, 0x03136, 0x03137, 0x03138, 0x03139, 0x0313a, 0x0313b, 0x0313c, 0x0313d, 0x0313e, 0x0313f, 0x03140, 0x03141, 0x03142, 0x03143, 0x03144, 0x03145, 0x03146, 0x03147, 0x03148, 0x03149, 0x0314a, 0x0314b, 0x0314c, 0x0314d, 0x0314e, 0x0314f, 0x03150, 0x03151, 0x03152, 0x03153, 0x03154, 0x03155, 0x03156, 0x03157, 0x03158, 0x03159, 0x0315a, 0x0315b, 0x0315c, 0x0315d, 0x0315e, 0x0315f, 0x03160, 0x03161, 0x03162, 0x03163, 0x03164, 0x03165, 0x03166, 0x03167, 0x03168, 0x03169, 0x0316a, 0x0316b, 0x0316c, 0x0316d, 0x0316e, 0x0316f, 0x03170, 0x03171, 0x03172, 0x03173, 0x03174, 0x03175, 0x03176, 0x03177, 0x03178, 0x03179, 0x0317a, 0x0317b, 0x0317c, 0x0317d, 0x0317e, 0x0317f, 0x03180, 0x03181, 0x03182, 0x03183, 0x03184, 0x03185, 0x03186, 0x03187, 0x03188, 0x03189, 0x0318a, 0x0318b, 0x0318c, 0x0318d, 0x0318e, 0x03192, 0x03193, 0x03194, 0x03195, 0x03196, 0x03197, 0x03198, 0x03199, 0x0319a, 0x0319b, 0x0319c, 0x0319d, 0x0319e, 0x0319f, 0x03244, 0x03245, 0x03246, 0x03247, 0x03260, 0x03261, 0x03262, 0x03263, 0x03264, 0x03265, 0x03266, 0x03267, 0x03268, 0x03269, 0x0326a, 0x0326b, 0x0326c, 0x0326d, 0x03280, 0x03281, 0x03282, 0x03283, 0x03284, 0x03285, 0x03286, 0x03287, 0x03288, 0x03289, 0x0328a, 0x0328b, 0x0328c, 0x0328d, 0x0328e, 0x0328f, 0x03290, 0x03291, 0x03292, 0x03293, 0x03294, 0x03295, 0x03296, 0x03297, 0x03298, 0x03299, 0x0329a, 0x0329b, 0x0329c, 0x0329d, 0x0329e, 0x0329f, 0x032a0, 0x032a1, 0x032a2, 0x032a3, 0x032a4, 0x032a5, 0x032a6, 0x032a7, 0x032a8, 0x032a9, 0x032aa, 0x032ab, 0x032ac, 0x032ad, 0x032ae, 0x032af, 0x032b0, 0x032d0, 0x032d1, 0x032d2, 0x032d3, 0x032d4, 0x032d5, 0x032d6, 0x032d7, 0x032d8, 0x032d9, 0x032da, 0x032db, 0x032dc, 0x032dd, 0x032de, 0x032df, 0x032e0, 0x032e1, 0x032e2, 0x032e3, 0x032e4, 0x032e5, 0x032e6, 0x032e7, 0x032e8, 0x032e9, 0x032ea, 0x032eb, 0x032ec, 0x032ed, 0x032ee, 0x032ef, 0x032f0, 0x032f1, 0x032f2, 0x032f3, 0x032f4, 0x032f5, 0x032f6, 0x032f7, 0x032f8, 0x032f9, 0x032fa, 0x032fb, 0x032fc, 0x032fd, 0x032fe, 0x0a69c, 0x0a69d, 0x0a770, 0x0a7f8, 0x0a7f9, 0x0ab5c, 0x0ab5d, 0x0ab5e, 0x0ab5f, 0x0f900, 0x0f901, 0x0f902, 0x0f903, 0x0f904, 0x0f905, 0x0f906, 0x0f907, 0x0f908, 0x0f909, 0x0f90a, 0x0f90b, 0x0f90c, 0x0f90d, 0x0f90e, 0x0f90f, 0x0f910, 0x0f911, 0x0f912, 0x0f913, 0x0f914, 0x0f915, 0x0f916, 0x0f917, 0x0f918, 0x0f919, 0x0f91a, 0x0f91b, 0x0f91c, 0x0f91d, 0x0f91e, 0x0f91f, 0x0f920, 0x0f921, 0x0f922, 0x0f923, 0x0f924, 0x0f925, 0x0f926, 0x0f927, 0x0f928, 0x0f929, 0x0f92a, 0x0f92b, 0x0f92c, 0x0f92d, 0x0f92e, 0x0f92f, 0x0f930, 0x0f931, 0x0f932, 0x0f933, 0x0f934, 0x0f935, 0x0f936, 0x0f937, 0x0f938, 0x0f939, 0x0f93a, 0x0f93b, 0x0f93c, 0x0f93d, 0x0f93e, 0x0f93f, 0x0f940, 0x0f941, 0x0f942, 0x0f943, 0x0f944, 0x0f945, 0x0f946, 0x0f947, 0x0f948, 0x0f949, 0x0f94a, 0x0f94b, 0x0f94c, 0x0f94d, 0x0f94e, 0x0f94f, 0x0f950, 0x0f951, 0x0f952, 0x0f953, 0x0f954, 0x0f955, 0x0f956, 0x0f957, 0x0f958, 0x0f959, 0x0f95a, 0x0f95b, 0x0f95c, 0x0f95d, 0x0f95e, 0x0f95f, 0x0f960, 0x0f961, 0x0f962, 0x0f963, 0x0f964, 0x0f965, 0x0f966, 0x0f967, 0x0f968, 0x0f969, 0x0f96a, 0x0f96b, 0x0f96c, 0x0f96d, 0x0f96e, 0x0f96f, 0x0f970, 0x0f971, 0x0f972, 0x0f973, 0x0f974, 0x0f975, 0x0f976, 0x0f977, 0x0f978, 0x0f979, 0x0f97a, 0x0f97b, 0x0f97c, 0x0f97d, 0x0f97e, 0x0f97f, 0x0f980, 0x0f981, 0x0f982, 0x0f983, 0x0f984, 0x0f985, 0x0f986, 0x0f987, 0x0f988, 0x0f989, 0x0f98a, 0x0f98b, 0x0f98c, 0x0f98d, 0x0f98e, 0x0f98f, 0x0f990, 0x0f991, 0x0f992, 0x0f993, 0x0f994, 0x0f995, 0x0f996, 0x0f997, 0x0f998, 0x0f999, 0x0f99a, 0x0f99b, 0x0f99c, 0x0f99d, 0x0f99e, 0x0f99f, 0x0f9a0, 0x0f9a1, 0x0f9a2, 0x0f9a3, 0x0f9a4, 0x0f9a5, 0x0f9a6, 0x0f9a7, 0x0f9a8, 0x0f9a9, 0x0f9aa, 0x0f9ab, 0x0f9ac, 0x0f9ad, 0x0f9ae, 0x0f9af, 0x0f9b0, 0x0f9b1, 0x0f9b2, 0x0f9b3, 0x0f9b4, 0x0f9b5, 0x0f9b6, 0x0f9b7, 0x0f9b8, 0x0f9b9, 0x0f9ba, 0x0f9bb, 0x0f9bc, 0x0f9bd, 0x0f9be, 0x0f9bf, 0x0f9c0, 0x0f9c1, 0x0f9c2, 0x0f9c3, 0x0f9c4, 0x0f9c5, 0x0f9c6, 0x0f9c7, 0x0f9c8, 0x0f9c9, 0x0f9ca, 0x0f9cb, 0x0f9cc, 0x0f9cd, 0x0f9ce, 0x0f9cf, 0x0f9d0, 0x0f9d1, 0x0f9d2, 0x0f9d3, 0x0f9d4, 0x0f9d5, 0x0f9d6, 0x0f9d7, 0x0f9d8, 0x0f9d9, 0x0f9da, 0x0f9db, 0x0f9dc, 0x0f9dd, 0x0f9de, 0x0f9df, 0x0f9e0, 0x0f9e1, 0x0f9e2, 0x0f9e3, 0x0f9e4, 0x0f9e5, 0x0f9e6, 0x0f9e7, 0x0f9e8, 0x0f9e9, 0x0f9ea, 0x0f9eb, 0x0f9ec, 0x0f9ed, 0x0f9ee, 0x0f9ef, 0x0f9f0, 0x0f9f1, 0x0f9f2, 0x0f9f3, 0x0f9f4, 0x0f9f5, 0x0f9f6, 0x0f9f7, 0x0f9f8, 0x0f9f9, 0x0f9fa, 0x0f9fb, 0x0f9fc, 0x0f9fd, 0x0f9fe, 0x0f9ff, 0x0fa00, 0x0fa01, 0x0fa02, 0x0fa03, 0x0fa04, 0x0fa05, 0x0fa06, 0x0fa07, 0x0fa08, 0x0fa09, 0x0fa0a, 0x0fa0b, 0x0fa0c, 0x0fa0d, 0x0fa10, 0x0fa12, 0x0fa15, 0x0fa16, 0x0fa17, 0x0fa18, 0x0fa19, 0x0fa1a, 0x0fa1b, 0x0fa1c, 0x0fa1d, 0x0fa1e, 0x0fa20, 0x0fa22, 0x0fa25, 0x0fa26, 0x0fa2a, 0x0fa2b, 0x0fa2c, 0x0fa2d, 0x0fa2e, 0x0fa2f, 0x0fa30, 0x0fa31, 0x0fa32, 0x0fa33, 0x0fa34, 0x0fa35, 0x0fa36, 0x0fa37, 0x0fa38, 0x0fa39, 0x0fa3a, 0x0fa3b, 0x0fa3c, 0x0fa3d, 0x0fa3e, 0x0fa3f, 0x0fa40, 0x0fa41, 0x0fa42, 0x0fa43, 0x0fa44, 0x0fa45, 0x0fa46, 0x0fa47, 0x0fa48, 0x0fa49, 0x0fa4a, 0x0fa4b, 0x0fa4c, 0x0fa4d, 0x0fa4e, 0x0fa4f, 0x0fa50, 0x0fa51, 0x0fa52, 0x0fa53, 0x0fa54, 0x0fa55, 0x0fa56, 0x0fa57, 0x0fa58, 0x0fa59, 0x0fa5a, 0x0fa5b, 0x0fa5c, 0x0fa5d, 0x0fa5e, 0x0fa5f, 0x0fa60, 0x0fa61, 0x0fa62, 0x0fa63, 0x0fa64, 0x0fa65, 0x0fa66, 0x0fa67, 0x0fa68, 0x0fa69, 0x0fa6a, 0x0fa6b, 0x0fa6c, 0x0fa6d, 0x0fa70, 0x0fa71, 0x0fa72, 0x0fa73, 0x0fa74, 0x0fa75, 0x0fa76, 0x0fa77, 0x0fa78, 0x0fa79, 0x0fa7a, 0x0fa7b, 0x0fa7c, 0x0fa7d, 0x0fa7e, 0x0fa7f, 0x0fa80, 0x0fa81, 0x0fa82, 0x0fa83, 0x0fa84, 0x0fa85, 0x0fa86, 0x0fa87, 0x0fa88, 0x0fa89, 0x0fa8a, 0x0fa8b, 0x0fa8c, 0x0fa8d, 0x0fa8e, 0x0fa8f, 0x0fa90, 0x0fa91, 0x0fa92, 0x0fa93, 0x0fa94, 0x0fa95, 0x0fa96, 0x0fa97, 0x0fa98, 0x0fa99, 0x0fa9a, 0x0fa9b, 0x0fa9c, 0x0fa9d, 0x0fa9e, 0x0fa9f, 0x0faa0, 0x0faa1, 0x0faa2, 0x0faa3, 0x0faa4, 0x0faa5, 0x0faa6, 0x0faa7, 0x0faa8, 0x0faa9, 0x0faaa, 0x0faab, 0x0faac, 0x0faad, 0x0faae, 0x0faaf, 0x0fab0, 0x0fab1, 0x0fab2, 0x0fab3, 0x0fab4, 0x0fab5, 0x0fab6, 0x0fab7, 0x0fab8, 0x0fab9, 0x0faba, 0x0fabb, 0x0fabc, 0x0fabd, 0x0fabe, 0x0fabf, 0x0fac0, 0x0fac1, 0x0fac2, 0x0fac3, 0x0fac4, 0x0fac5, 0x0fac6, 0x0fac7, 0x0fac8, 0x0fac9, 0x0faca, 0x0facb, 0x0facc, 0x0facd, 0x0face, 0x0facf, 0x0fad0, 0x0fad1, 0x0fad2, 0x0fad3, 0x0fad4, 0x0fad5, 0x0fad6, 0x0fad7, 0x0fad8, 0x0fad9, 0x0fb20, 0x0fb21, 0x0fb22, 0x0fb23, 0x0fb24, 0x0fb25, 0x0fb26, 0x0fb27, 0x0fb28, 0x0fb29, 0x0fb50, 0x0fb51, 0x0fb52, 0x0fb53, 0x0fb54, 0x0fb55, 0x0fb56, 0x0fb57, 0x0fb58, 0x0fb59, 0x0fb5a, 0x0fb5b, 0x0fb5c, 0x0fb5d, 0x0fb5e, 0x0fb5f, 0x0fb60, 0x0fb61, 0x0fb62, 0x0fb63, 0x0fb64, 0x0fb65, 0x0fb66, 0x0fb67, 0x0fb68, 0x0fb69, 0x0fb6a, 0x0fb6b, 0x0fb6c, 0x0fb6d, 0x0fb6e, 0x0fb6f, 0x0fb70, 0x0fb71, 0x0fb72, 0x0fb73, 0x0fb74, 0x0fb75, 0x0fb76, 0x0fb77, 0x0fb78, 0x0fb79, 0x0fb7a, 0x0fb7b, 0x0fb7c, 0x0fb7d, 0x0fb7e, 0x0fb7f, 0x0fb80, 0x0fb81, 0x0fb82, 0x0fb83, 0x0fb84, 0x0fb85, 0x0fb86, 0x0fb87, 0x0fb88, 0x0fb89, 0x0fb8a, 0x0fb8b, 0x0fb8c, 0x0fb8d, 0x0fb8e, 0x0fb8f, 0x0fb90, 0x0fb91, 0x0fb92, 0x0fb93, 0x0fb94, 0x0fb95, 0x0fb96, 0x0fb97, 0x0fb98, 0x0fb99, 0x0fb9a, 0x0fb9b, 0x0fb9c, 0x0fb9d, 0x0fb9e, 0x0fb9f, 0x0fba0, 0x0fba1, 0x0fba2, 0x0fba3, 0x0fba4, 0x0fba5, 0x0fba6, 0x0fba7, 0x0fba8, 0x0fba9, 0x0fbaa, 0x0fbab, 0x0fbac, 0x0fbad, 0x0fbae, 0x0fbaf, 0x0fbb0, 0x0fbb1, 0x0fbd3, 0x0fbd4, 0x0fbd5, 0x0fbd6, 0x0fbd7, 0x0fbd8, 0x0fbd9, 0x0fbda, 0x0fbdb, 0x0fbdc, 0x0fbdd, 0x0fbde, 0x0fbdf, 0x0fbe0, 0x0fbe1, 0x0fbe2, 0x0fbe3, 0x0fbe4, 0x0fbe5, 0x0fbe6, 0x0fbe7, 0x0fbe8, 0x0fbe9, 0x0fbfc, 0x0fbfd, 0x0fbfe, 0x0fbff, 0x0fe10, 0x0fe11, 0x0fe12, 0x0fe13, 0x0fe14, 0x0fe15, 0x0fe16, 0x0fe17, 0x0fe18, 0x0fe19, 0x0fe30, 0x0fe31, 0x0fe32, 0x0fe33, 0x0fe34, 0x0fe35, 0x0fe36, 0x0fe37, 0x0fe38, 0x0fe39, 0x0fe3a, 0x0fe3b, 0x0fe3c, 0x0fe3d, 0x0fe3e, 0x0fe3f, 0x0fe40, 0x0fe41, 0x0fe42, 0x0fe43, 0x0fe44, 0x0fe47, 0x0fe48, 0x0fe49, 0x0fe4a, 0x0fe4b, 0x0fe4c, 0x0fe4d, 0x0fe4e, 0x0fe4f, 0x0fe50, 0x0fe51, 0x0fe52, 0x0fe54, 0x0fe55, 0x0fe56, 0x0fe57, 0x0fe58, 0x0fe59, 0x0fe5a, 0x0fe5b, 0x0fe5c, 0x0fe5d, 0x0fe5e, 0x0fe5f, 0x0fe60, 0x0fe61, 0x0fe62, 0x0fe63, 0x0fe64, 0x0fe65, 0x0fe66, 0x0fe68, 0x0fe69, 0x0fe6a, 0x0fe6b, 0x0fe80, 0x0fe81, 0x0fe82, 0x0fe83, 0x0fe84, 0x0fe85, 0x0fe86, 0x0fe87, 0x0fe88, 0x0fe89, 0x0fe8a, 0x0fe8b, 0x0fe8c, 0x0fe8d, 0x0fe8e, 0x0fe8f, 0x0fe90, 0x0fe91, 0x0fe92, 0x0fe93, 0x0fe94, 0x0fe95, 0x0fe96, 0x0fe97, 0x0fe98, 0x0fe99, 0x0fe9a, 0x0fe9b, 0x0fe9c, 0x0fe9d, 0x0fe9e, 0x0fe9f, 0x0fea0, 0x0fea1, 0x0fea2, 0x0fea3, 0x0fea4, 0x0fea5, 0x0fea6, 0x0fea7, 0x0fea8, 0x0fea9, 0x0feaa, 0x0feab, 0x0feac, 0x0fead, 0x0feae, 0x0feaf, 0x0feb0, 0x0feb1, 0x0feb2, 0x0feb3, 0x0feb4, 0x0feb5, 0x0feb6, 0x0feb7, 0x0feb8, 0x0feb9, 0x0feba, 0x0febb, 0x0febc, 0x0febd, 0x0febe, 0x0febf, 0x0fec0, 0x0fec1, 0x0fec2, 0x0fec3, 0x0fec4, 0x0fec5, 0x0fec6, 0x0fec7, 0x0fec8, 0x0fec9, 0x0feca, 0x0fecb, 0x0fecc, 0x0fecd, 0x0fece, 0x0fecf, 0x0fed0, 0x0fed1, 0x0fed2, 0x0fed3, 0x0fed4, 0x0fed5, 0x0fed6, 0x0fed7, 0x0fed8, 0x0fed9, 0x0feda, 0x0fedb, 0x0fedc, 0x0fedd, 0x0fede, 0x0fedf, 0x0fee0, 0x0fee1, 0x0fee2, 0x0fee3, 0x0fee4, 0x0fee5, 0x0fee6, 0x0fee7, 0x0fee8, 0x0fee9, 0x0feea, 0x0feeb, 0x0feec, 0x0feed, 0x0feee, 0x0feef, 0x0fef0, 0x0fef1, 0x0fef2, 0x0fef3, 0x0fef4, 0x0ff01, 0x0ff02, 0x0ff03, 0x0ff04, 0x0ff05, 0x0ff06, 0x0ff07, 0x0ff08, 0x0ff09, 0x0ff0a, 0x0ff0b, 0x0ff0c, 0x0ff0d, 0x0ff0e, 0x0ff0f, 0x0ff10, 0x0ff11, 0x0ff12, 0x0ff13, 0x0ff14, 0x0ff15, 0x0ff16, 0x0ff17, 0x0ff18, 0x0ff19, 0x0ff1a, 0x0ff1b, 0x0ff1c, 0x0ff1d, 0x0ff1e, 0x0ff1f, 0x0ff20, 0x0ff21, 0x0ff22, 0x0ff23, 0x0ff24, 0x0ff25, 0x0ff26, 0x0ff27, 0x0ff28, 0x0ff29, 0x0ff2a, 0x0ff2b, 0x0ff2c, 0x0ff2d, 0x0ff2e, 0x0ff2f, 0x0ff30, 0x0ff31, 0x0ff32, 0x0ff33, 0x0ff34, 0x0ff35, 0x0ff36, 0x0ff37, 0x0ff38, 0x0ff39, 0x0ff3a, 0x0ff3b, 0x0ff3c, 0x0ff3d, 0x0ff3e, 0x0ff3f, 0x0ff40, 0x0ff5b, 0x0ff5c, 0x0ff5d, 0x0ff5e, 0x0ff5f, 0x0ff60, 0x0ff61, 0x0ff62, 0x0ff63, 0x0ff64, 0x0ff65, 0x0ff66, 0x0ff67, 0x0ff68, 0x0ff69, 0x0ff6a, 0x0ff6b, 0x0ff6c, 0x0ff6d, 0x0ff6e, 0x0ff6f, 0x0ff70, 0x0ff71, 0x0ff72, 0x0ff73, 0x0ff74, 0x0ff75, 0x0ff76, 0x0ff77, 0x0ff78, 0x0ff79, 0x0ff7a, 0x0ff7b, 0x0ff7c, 0x0ff7d, 0x0ff7e, 0x0ff7f, 0x0ff80, 0x0ff81, 0x0ff82, 0x0ff83, 0x0ff84, 0x0ff85, 0x0ff86, 0x0ff87, 0x0ff88, 0x0ff89, 0x0ff8a, 0x0ff8b, 0x0ff8c, 0x0ff8d, 0x0ff8e, 0x0ff8f, 0x0ff90, 0x0ff91, 0x0ff92, 0x0ff93, 0x0ff94, 0x0ff95, 0x0ff96, 0x0ff97, 0x0ff98, 0x0ff99, 0x0ff9a, 0x0ff9b, 0x0ff9c, 0x0ff9d, 0x0ff9e, 0x0ff9f, 0x0ffa0, 0x0ffa1, 0x0ffa2, 0x0ffa3, 0x0ffa4, 0x0ffa5, 0x0ffa6, 0x0ffa7, 0x0ffa8, 0x0ffa9, 0x0ffaa, 0x0ffab, 0x0ffac, 0x0ffad, 0x0ffae, 0x0ffaf, 0x0ffb0, 0x0ffb1, 0x0ffb2, 0x0ffb3, 0x0ffb4, 0x0ffb5, 0x0ffb6, 0x0ffb7, 0x0ffb8, 0x0ffb9, 0x0ffba, 0x0ffbb, 0x0ffbc, 0x0ffbd, 0x0ffbe, 0x0ffc2, 0x0ffc3, 0x0ffc4, 0x0ffc5, 0x0ffc6, 0x0ffc7, 0x0ffca, 0x0ffcb, 0x0ffcc, 0x0ffcd, 0x0ffce, 0x0ffcf, 0x0ffd2, 0x0ffd3, 0x0ffd4, 0x0ffd5, 0x0ffd6, 0x0ffd7, 0x0ffda, 0x0ffdb, 0x0ffdc, 0x0ffe0, 0x0ffe1, 0x0ffe2, 0x0ffe3, 0x0ffe4, 0x0ffe5, 0x0ffe6, 0x0ffe8, 0x0ffe9, 0x0ffea, 0x0ffeb, 0x0ffec, 0x0ffed, 0x0ffee }; static const uint32_t uni16_decomp_values[] = { 0x00068, 0x00266, 0x0006a, 0x00072, 0x00279, 0x0027b, 0x00281, 0x00077, 0x00079, 0x00263, 0x0006c, 0x00073, 0x00078, 0x00295, 0x00300, 0x00301, 0x00313, 0x002b9, 0x0003b, 0x000b7, 0x003a5, 0x00398, 0x003a3, 0x00f0b, 0x010dc, 0x00041, 0x000c6, 0x00042, 0x00044, 0x00045, 0x0018e, 0x00047, 0x00048, 0x00049, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004e, 0x0004f, 0x00222, 0x00050, 0x00052, 0x00054, 0x00055, 0x00057, 0x00061, 0x00250, 0x00251, 0x01d02, 0x00062, 0x00064, 0x00065, 0x00259, 0x0025b, 0x0025c, 0x00067, 0x0006b, 0x0006d, 0x0014b, 0x0006f, 0x00254, 0x01d16, 0x01d17, 0x00070, 0x00074, 0x00075, 0x01d1d, 0x0026f, 0x00076, 0x01d25, 0x003b2, 0x003b3, 0x003b4, 0x003c6, 0x003c7, 0x00069, 0x00072, 0x00075, 0x00076, 0x003b2, 0x003b3, 0x003c1, 0x003c6, 0x003c7, 0x0043d, 0x00252, 0x00063, 0x00255, 0x000f0, 0x0025c, 0x00066, 0x0025f, 0x00261, 0x00265, 0x00268, 0x00269, 0x0026a, 0x01d7b, 0x0029d, 0x0026d, 0x01d85, 0x0029f, 0x00271, 0x00270, 0x00272, 0x00273, 0x00274, 0x00275, 0x00278, 0x00282, 0x00283, 0x001ab, 0x00289, 0x0028a, 0x01d1c, 0x0028b, 0x0028c, 0x0007a, 0x00290, 0x00291, 0x00292, 0x003b8, 0x00386, 0x00388, 0x00389, 0x00390, 0x0038a, 0x003b0, 0x0038e, 0x00385, 0x00060, 0x0038c, 0x0038f, 0x000b4, 0x02002, 0x02003, 0x00020, 0x00020, 0x00020, 0x00020, 0x00020, 0x00020, 0x00020, 0x00020, 0x00020, 0x02010, 0x0002e, 0x00020, 0x00020, 0x00030, 0x00069, 0x00034, 0x00035, 0x00036, 0x00037, 0x00038, 0x00039, 0x0002b, 0x02212, 0x0003d, 0x00028, 0x00029, 0x0006e, 0x00030, 0x00031, 0x00032, 0x00033, 0x00034, 0x00035, 0x00036, 0x00037, 0x00038, 0x00039, 0x0002b, 0x02212, 0x0003d, 0x00028, 0x00029, 0x00061, 0x00065, 0x0006f, 0x00078, 0x00259, 0x00068, 0x0006b, 0x0006c, 0x0006d, 0x0006e, 0x00070, 0x00073, 0x00074, 0x00043, 0x00190, 0x00067, 0x00048, 0x00048, 0x00048, 0x00068, 0x00127, 0x00049, 0x00049, 0x0004c, 0x0006c, 0x0004e, 0x00050, 0x00051, 0x00052, 0x00052, 0x00052, 0x0005a, 0x003a9, 0x0005a, 0x0004b, 0x000c5, 0x00042, 0x00043, 0x00065, 0x00045, 0x00046, 0x0004d, 0x0006f, 0x005d0, 0x005d1, 0x005d2, 0x005d3, 0x00069, 0x003c0, 0x003b3, 0x00393, 0x003a0, 0x02211, 0x00044, 0x00064, 0x00065, 0x00069, 0x0006a, 0x00049, 0x00056, 0x00058, 0x0004c, 0x00043, 0x00044, 0x0004d, 0x03008, 0x03009, 0x00031, 0x00032, 0x00033, 0x00034, 0x00035, 0x00036, 0x00037, 0x00038, 0x00039, 0x00041, 0x00042, 0x00043, 0x00044, 0x00045, 0x00046, 0x00047, 0x00048, 0x00049, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004e, 0x0004f, 0x00050, 0x00051, 0x00052, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x0005a, 0x00030, 0x0006a, 0x00056, 0x02d61, 0x06bcd, 0x09f9f, 0x04e00, 0x04e28, 0x04e36, 0x04e3f, 0x04e59, 0x04e85, 0x04e8c, 0x04ea0, 0x04eba, 0x0513f, 0x05165, 0x0516b, 0x05182, 0x05196, 0x051ab, 0x051e0, 0x051f5, 0x05200, 0x0529b, 0x052f9, 0x05315, 0x0531a, 0x05338, 0x05341, 0x0535c, 0x05369, 0x05382, 0x053b6, 0x053c8, 0x053e3, 0x056d7, 0x0571f, 0x058eb, 0x05902, 0x0590a, 0x05915, 0x05927, 0x05973, 0x05b50, 0x05b80, 0x05bf8, 0x05c0f, 0x05c22, 0x05c38, 0x05c6e, 0x05c71, 0x05ddb, 0x05de5, 0x05df1, 0x05dfe, 0x05e72, 0x05e7a, 0x05e7f, 0x05ef4, 0x05efe, 0x05f0b, 0x05f13, 0x05f50, 0x05f61, 0x05f73, 0x05fc3, 0x06208, 0x06236, 0x0624b, 0x0652f, 0x06534, 0x06587, 0x06597, 0x065a4, 0x065b9, 0x065e0, 0x065e5, 0x066f0, 0x06708, 0x06728, 0x06b20, 0x06b62, 0x06b79, 0x06bb3, 0x06bcb, 0x06bd4, 0x06bdb, 0x06c0f, 0x06c14, 0x06c34, 0x0706b, 0x0722a, 0x07236, 0x0723b, 0x0723f, 0x07247, 0x07259, 0x0725b, 0x072ac, 0x07384, 0x07389, 0x074dc, 0x074e6, 0x07518, 0x0751f, 0x07528, 0x07530, 0x0758b, 0x07592, 0x07676, 0x0767d, 0x076ae, 0x076bf, 0x076ee, 0x077db, 0x077e2, 0x077f3, 0x0793a, 0x079b8, 0x079be, 0x07a74, 0x07acb, 0x07af9, 0x07c73, 0x07cf8, 0x07f36, 0x07f51, 0x07f8a, 0x07fbd, 0x08001, 0x0800c, 0x08012, 0x08033, 0x0807f, 0x08089, 0x081e3, 0x081ea, 0x081f3, 0x081fc, 0x0820c, 0x0821b, 0x0821f, 0x0826e, 0x08272, 0x08278, 0x0864d, 0x0866b, 0x08840, 0x0884c, 0x08863, 0x0897e, 0x0898b, 0x089d2, 0x08a00, 0x08c37, 0x08c46, 0x08c55, 0x08c78, 0x08c9d, 0x08d64, 0x08d70, 0x08db3, 0x08eab, 0x08eca, 0x08f9b, 0x08fb0, 0x08fb5, 0x09091, 0x09149, 0x091c6, 0x091cc, 0x091d1, 0x09577, 0x09580, 0x0961c, 0x096b6, 0x096b9, 0x096e8, 0x09751, 0x0975e, 0x09762, 0x09769, 0x097cb, 0x097ed, 0x097f3, 0x09801, 0x098a8, 0x098db, 0x098df, 0x09996, 0x09999, 0x099ac, 0x09aa8, 0x09ad8, 0x09adf, 0x09b25, 0x09b2f, 0x09b32, 0x09b3c, 0x09b5a, 0x09ce5, 0x09e75, 0x09e7f, 0x09ea5, 0x09ebb, 0x09ec3, 0x09ecd, 0x09ed1, 0x09ef9, 0x09efd, 0x09f0e, 0x09f13, 0x09f20, 0x09f3b, 0x09f4a, 0x09f52, 0x09f8d, 0x09f9c, 0x09fa0, 0x00020, 0x03012, 0x05341, 0x05344, 0x05345, 0x01100, 0x01101, 0x011aa, 0x01102, 0x011ac, 0x011ad, 0x01103, 0x01104, 0x01105, 0x011b0, 0x011b1, 0x011b2, 0x011b3, 0x011b4, 0x011b5, 0x0111a, 0x01106, 0x01107, 0x01108, 0x01121, 0x01109, 0x0110a, 0x0110b, 0x0110c, 0x0110d, 0x0110e, 0x0110f, 0x01110, 0x01111, 0x01112, 0x01161, 0x01162, 0x01163, 0x01164, 0x01165, 0x01166, 0x01167, 0x01168, 0x01169, 0x0116a, 0x0116b, 0x0116c, 0x0116d, 0x0116e, 0x0116f, 0x01170, 0x01171, 0x01172, 0x01173, 0x01174, 0x01175, 0x01160, 0x01114, 0x01115, 0x011c7, 0x011c8, 0x011cc, 0x011ce, 0x011d3, 0x011d7, 0x011d9, 0x0111c, 0x011dd, 0x011df, 0x0111d, 0x0111e, 0x01120, 0x01122, 0x01123, 0x01127, 0x01129, 0x0112b, 0x0112c, 0x0112d, 0x0112e, 0x0112f, 0x01132, 0x01136, 0x01140, 0x01147, 0x0114c, 0x011f1, 0x011f2, 0x01157, 0x01158, 0x01159, 0x01184, 0x01185, 0x01188, 0x01191, 0x01192, 0x01194, 0x0119e, 0x011a1, 0x04e00, 0x04e8c, 0x04e09, 0x056db, 0x04e0a, 0x04e2d, 0x04e0b, 0x07532, 0x04e59, 0x04e19, 0x04e01, 0x05929, 0x05730, 0x04eba, 0x0554f, 0x05e7c, 0x06587, 0x07b8f, 0x01100, 0x01102, 0x01103, 0x01105, 0x01106, 0x01107, 0x01109, 0x0110b, 0x0110c, 0x0110e, 0x0110f, 0x01110, 0x01111, 0x01112, 0x04e00, 0x04e8c, 0x04e09, 0x056db, 0x04e94, 0x0516d, 0x04e03, 0x0516b, 0x04e5d, 0x05341, 0x06708, 0x0706b, 0x06c34, 0x06728, 0x091d1, 0x0571f, 0x065e5, 0x0682a, 0x06709, 0x0793e, 0x0540d, 0x07279, 0x08ca1, 0x0795d, 0x052b4, 0x079d8, 0x07537, 0x05973, 0x09069, 0x0512a, 0x05370, 0x06ce8, 0x09805, 0x04f11, 0x05199, 0x06b63, 0x04e0a, 0x04e2d, 0x04e0b, 0x05de6, 0x053f3, 0x0533b, 0x05b97, 0x05b66, 0x076e3, 0x04f01, 0x08cc7, 0x05354, 0x0591c, 0x030a2, 0x030a4, 0x030a6, 0x030a8, 0x030aa, 0x030ab, 0x030ad, 0x030af, 0x030b1, 0x030b3, 0x030b5, 0x030b7, 0x030b9, 0x030bb, 0x030bd, 0x030bf, 0x030c1, 0x030c4, 0x030c6, 0x030c8, 0x030ca, 0x030cb, 0x030cc, 0x030cd, 0x030ce, 0x030cf, 0x030d2, 0x030d5, 0x030d8, 0x030db, 0x030de, 0x030df, 0x030e0, 0x030e1, 0x030e2, 0x030e4, 0x030e6, 0x030e8, 0x030e9, 0x030ea, 0x030eb, 0x030ec, 0x030ed, 0x030ef, 0x030f0, 0x030f1, 0x030f2, 0x0044a, 0x0044c, 0x0a76f, 0x00126, 0x00153, 0x0a727, 0x0ab37, 0x0026b, 0x0ab52, 0x08c48, 0x066f4, 0x08eca, 0x08cc8, 0x06ed1, 0x04e32, 0x053e5, 0x09f9c, 0x09f9c, 0x05951, 0x091d1, 0x05587, 0x05948, 0x061f6, 0x07669, 0x07f85, 0x0863f, 0x087ba, 0x088f8, 0x0908f, 0x06a02, 0x06d1b, 0x070d9, 0x073de, 0x0843d, 0x0916a, 0x099f1, 0x04e82, 0x05375, 0x06b04, 0x0721b, 0x0862d, 0x09e1e, 0x05d50, 0x06feb, 0x085cd, 0x08964, 0x062c9, 0x081d8, 0x0881f, 0x05eca, 0x06717, 0x06d6a, 0x072fc, 0x090ce, 0x04f86, 0x051b7, 0x052de, 0x064c4, 0x06ad3, 0x07210, 0x076e7, 0x08001, 0x08606, 0x0865c, 0x08def, 0x09732, 0x09b6f, 0x09dfa, 0x0788c, 0x0797f, 0x07da0, 0x083c9, 0x09304, 0x09e7f, 0x08ad6, 0x058df, 0x05f04, 0x07c60, 0x0807e, 0x07262, 0x078ca, 0x08cc2, 0x096f7, 0x058d8, 0x05c62, 0x06a13, 0x06dda, 0x06f0f, 0x07d2f, 0x07e37, 0x0964b, 0x052d2, 0x0808b, 0x051dc, 0x051cc, 0x07a1c, 0x07dbe, 0x083f1, 0x09675, 0x08b80, 0x062cf, 0x06a02, 0x08afe, 0x04e39, 0x05be7, 0x06012, 0x07387, 0x07570, 0x05317, 0x078fb, 0x04fbf, 0x05fa9, 0x04e0d, 0x06ccc, 0x06578, 0x07d22, 0x053c3, 0x0585e, 0x07701, 0x08449, 0x08aaa, 0x06bba, 0x08fb0, 0x06c88, 0x062fe, 0x082e5, 0x063a0, 0x07565, 0x04eae, 0x05169, 0x051c9, 0x06881, 0x07ce7, 0x0826f, 0x08ad2, 0x091cf, 0x052f5, 0x05442, 0x05973, 0x05eec, 0x065c5, 0x06ffe, 0x0792a, 0x095ad, 0x09a6a, 0x09e97, 0x09ece, 0x0529b, 0x066c6, 0x06b77, 0x08f62, 0x05e74, 0x06190, 0x06200, 0x0649a, 0x06f23, 0x07149, 0x07489, 0x079ca, 0x07df4, 0x0806f, 0x08f26, 0x084ee, 0x09023, 0x0934a, 0x05217, 0x052a3, 0x054bd, 0x070c8, 0x088c2, 0x08aaa, 0x05ec9, 0x05ff5, 0x0637b, 0x06bae, 0x07c3e, 0x07375, 0x04ee4, 0x056f9, 0x05be7, 0x05dba, 0x0601c, 0x073b2, 0x07469, 0x07f9a, 0x08046, 0x09234, 0x096f6, 0x09748, 0x09818, 0x04f8b, 0x079ae, 0x091b4, 0x096b8, 0x060e1, 0x04e86, 0x050da, 0x05bee, 0x05c3f, 0x06599, 0x06a02, 0x071ce, 0x07642, 0x084fc, 0x0907c, 0x09f8d, 0x06688, 0x0962e, 0x05289, 0x0677b, 0x067f3, 0x06d41, 0x06e9c, 0x07409, 0x07559, 0x0786b, 0x07d10, 0x0985e, 0x0516d, 0x0622e, 0x09678, 0x0502b, 0x05d19, 0x06dea, 0x08f2a, 0x05f8b, 0x06144, 0x06817, 0x07387, 0x09686, 0x05229, 0x0540f, 0x05c65, 0x06613, 0x0674e, 0x068a8, 0x06ce5, 0x07406, 0x075e2, 0x07f79, 0x088cf, 0x088e1, 0x091cc, 0x096e2, 0x0533f, 0x06eba, 0x0541d, 0x071d0, 0x07498, 0x085fa, 0x096a3, 0x09c57, 0x09e9f, 0x06797, 0x06dcb, 0x081e8, 0x07acb, 0x07b20, 0x07c92, 0x072c0, 0x07099, 0x08b58, 0x04ec0, 0x08336, 0x0523a, 0x05207, 0x05ea6, 0x062d3, 0x07cd6, 0x05b85, 0x06d1e, 0x066b4, 0x08f3b, 0x0884c, 0x0964d, 0x0898b, 0x05ed3, 0x05140, 0x055c0, 0x0585a, 0x06674, 0x051de, 0x0732a, 0x076ca, 0x0793c, 0x0795e, 0x07965, 0x0798f, 0x09756, 0x07cbe, 0x07fbd, 0x08612, 0x08af8, 0x09038, 0x090fd, 0x098ef, 0x098fc, 0x09928, 0x09db4, 0x090de, 0x096b7, 0x04fae, 0x050e7, 0x0514d, 0x052c9, 0x052e4, 0x05351, 0x0559d, 0x05606, 0x05668, 0x05840, 0x058a8, 0x05c64, 0x05c6e, 0x06094, 0x06168, 0x0618e, 0x061f2, 0x0654f, 0x065e2, 0x06691, 0x06885, 0x06d77, 0x06e1a, 0x06f22, 0x0716e, 0x0722b, 0x07422, 0x07891, 0x0793e, 0x07949, 0x07948, 0x07950, 0x07956, 0x0795d, 0x0798d, 0x0798e, 0x07a40, 0x07a81, 0x07bc0, 0x07df4, 0x07e09, 0x07e41, 0x07f72, 0x08005, 0x081ed, 0x08279, 0x08279, 0x08457, 0x08910, 0x08996, 0x08b01, 0x08b39, 0x08cd3, 0x08d08, 0x08fb6, 0x09038, 0x096e3, 0x097ff, 0x0983b, 0x06075, 0x242ee, 0x08218, 0x04e26, 0x051b5, 0x05168, 0x04f80, 0x05145, 0x05180, 0x052c7, 0x052fa, 0x0559d, 0x05555, 0x05599, 0x055e2, 0x0585a, 0x058b3, 0x05944, 0x05954, 0x05a62, 0x05b28, 0x05ed2, 0x05ed9, 0x05f69, 0x05fad, 0x060d8, 0x0614e, 0x06108, 0x0618e, 0x06160, 0x061f2, 0x06234, 0x063c4, 0x0641c, 0x06452, 0x06556, 0x06674, 0x06717, 0x0671b, 0x06756, 0x06b79, 0x06bba, 0x06d41, 0x06edb, 0x06ecb, 0x06f22, 0x0701e, 0x0716e, 0x077a7, 0x07235, 0x072af, 0x0732a, 0x07471, 0x07506, 0x0753b, 0x0761d, 0x0761f, 0x076ca, 0x076db, 0x076f4, 0x0774a, 0x07740, 0x078cc, 0x07ab1, 0x07bc0, 0x07c7b, 0x07d5b, 0x07df4, 0x07f3e, 0x08005, 0x08352, 0x083ef, 0x08779, 0x08941, 0x08986, 0x08996, 0x08abf, 0x08af8, 0x08acb, 0x08b01, 0x08afe, 0x08aed, 0x08b39, 0x08b8a, 0x08d08, 0x08f38, 0x09072, 0x09199, 0x09276, 0x0967c, 0x096e3, 0x09756, 0x097db, 0x097ff, 0x0980b, 0x0983b, 0x09b12, 0x09f9c, 0x2284a, 0x22844, 0x233d5, 0x03b9d, 0x04018, 0x04039, 0x25249, 0x25cd0, 0x27ed3, 0x09f43, 0x09f8e, 0x005e2, 0x005d0, 0x005d3, 0x005d4, 0x005db, 0x005dc, 0x005dd, 0x005e8, 0x005ea, 0x0002b, 0x00671, 0x00671, 0x0067b, 0x0067b, 0x0067b, 0x0067b, 0x0067e, 0x0067e, 0x0067e, 0x0067e, 0x00680, 0x00680, 0x00680, 0x00680, 0x0067a, 0x0067a, 0x0067a, 0x0067a, 0x0067f, 0x0067f, 0x0067f, 0x0067f, 0x00679, 0x00679, 0x00679, 0x00679, 0x006a4, 0x006a4, 0x006a4, 0x006a4, 0x006a6, 0x006a6, 0x006a6, 0x006a6, 0x00684, 0x00684, 0x00684, 0x00684, 0x00683, 0x00683, 0x00683, 0x00683, 0x00686, 0x00686, 0x00686, 0x00686, 0x00687, 0x00687, 0x00687, 0x00687, 0x0068d, 0x0068d, 0x0068c, 0x0068c, 0x0068e, 0x0068e, 0x00688, 0x00688, 0x00698, 0x00698, 0x00691, 0x00691, 0x006a9, 0x006a9, 0x006a9, 0x006a9, 0x006af, 0x006af, 0x006af, 0x006af, 0x006b3, 0x006b3, 0x006b3, 0x006b3, 0x006b1, 0x006b1, 0x006b1, 0x006b1, 0x006ba, 0x006ba, 0x006bb, 0x006bb, 0x006bb, 0x006bb, 0x006c0, 0x006c0, 0x006c1, 0x006c1, 0x006c1, 0x006c1, 0x006be, 0x006be, 0x006be, 0x006be, 0x006d2, 0x006d2, 0x006d3, 0x006d3, 0x006ad, 0x006ad, 0x006ad, 0x006ad, 0x006c7, 0x006c7, 0x006c6, 0x006c6, 0x006c8, 0x006c8, 0x00677, 0x006cb, 0x006cb, 0x006c5, 0x006c5, 0x006c9, 0x006c9, 0x006d0, 0x006d0, 0x006d0, 0x006d0, 0x00649, 0x00649, 0x006cc, 0x006cc, 0x006cc, 0x006cc, 0x0002c, 0x03001, 0x03002, 0x0003a, 0x0003b, 0x00021, 0x0003f, 0x03016, 0x03017, 0x02026, 0x02025, 0x02014, 0x02013, 0x0005f, 0x0005f, 0x00028, 0x00029, 0x0007b, 0x0007d, 0x03014, 0x03015, 0x03010, 0x03011, 0x0300a, 0x0300b, 0x03008, 0x03009, 0x0300c, 0x0300d, 0x0300e, 0x0300f, 0x0005b, 0x0005d, 0x0203e, 0x0203e, 0x0203e, 0x0203e, 0x0005f, 0x0005f, 0x0005f, 0x0002c, 0x03001, 0x0002e, 0x0003b, 0x0003a, 0x0003f, 0x00021, 0x02014, 0x00028, 0x00029, 0x0007b, 0x0007d, 0x03014, 0x03015, 0x00023, 0x00026, 0x0002a, 0x0002b, 0x0002d, 0x0003c, 0x0003e, 0x0003d, 0x0005c, 0x00024, 0x00025, 0x00040, 0x00621, 0x00622, 0x00622, 0x00623, 0x00623, 0x00624, 0x00624, 0x00625, 0x00625, 0x00626, 0x00626, 0x00626, 0x00626, 0x00627, 0x00627, 0x00628, 0x00628, 0x00628, 0x00628, 0x00629, 0x00629, 0x0062a, 0x0062a, 0x0062a, 0x0062a, 0x0062b, 0x0062b, 0x0062b, 0x0062b, 0x0062c, 0x0062c, 0x0062c, 0x0062c, 0x0062d, 0x0062d, 0x0062d, 0x0062d, 0x0062e, 0x0062e, 0x0062e, 0x0062e, 0x0062f, 0x0062f, 0x00630, 0x00630, 0x00631, 0x00631, 0x00632, 0x00632, 0x00633, 0x00633, 0x00633, 0x00633, 0x00634, 0x00634, 0x00634, 0x00634, 0x00635, 0x00635, 0x00635, 0x00635, 0x00636, 0x00636, 0x00636, 0x00636, 0x00637, 0x00637, 0x00637, 0x00637, 0x00638, 0x00638, 0x00638, 0x00638, 0x00639, 0x00639, 0x00639, 0x00639, 0x0063a, 0x0063a, 0x0063a, 0x0063a, 0x00641, 0x00641, 0x00641, 0x00641, 0x00642, 0x00642, 0x00642, 0x00642, 0x00643, 0x00643, 0x00643, 0x00643, 0x00644, 0x00644, 0x00644, 0x00644, 0x00645, 0x00645, 0x00645, 0x00645, 0x00646, 0x00646, 0x00646, 0x00646, 0x00647, 0x00647, 0x00647, 0x00647, 0x00648, 0x00648, 0x00649, 0x00649, 0x0064a, 0x0064a, 0x0064a, 0x0064a, 0x00021, 0x00022, 0x00023, 0x00024, 0x00025, 0x00026, 0x00027, 0x00028, 0x00029, 0x0002a, 0x0002b, 0x0002c, 0x0002d, 0x0002e, 0x0002f, 0x00030, 0x00031, 0x00032, 0x00033, 0x00034, 0x00035, 0x00036, 0x00037, 0x00038, 0x00039, 0x0003a, 0x0003b, 0x0003c, 0x0003d, 0x0003e, 0x0003f, 0x00040, 0x00041, 0x00042, 0x00043, 0x00044, 0x00045, 0x00046, 0x00047, 0x00048, 0x00049, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004e, 0x0004f, 0x00050, 0x00051, 0x00052, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x0005a, 0x0005b, 0x0005c, 0x0005d, 0x0005e, 0x0005f, 0x00060, 0x0007b, 0x0007c, 0x0007d, 0x0007e, 0x02985, 0x02986, 0x03002, 0x0300c, 0x0300d, 0x03001, 0x030fb, 0x030f2, 0x030a1, 0x030a3, 0x030a5, 0x030a7, 0x030a9, 0x030e3, 0x030e5, 0x030e7, 0x030c3, 0x030fc, 0x030a2, 0x030a4, 0x030a6, 0x030a8, 0x030aa, 0x030ab, 0x030ad, 0x030af, 0x030b1, 0x030b3, 0x030b5, 0x030b7, 0x030b9, 0x030bb, 0x030bd, 0x030bf, 0x030c1, 0x030c4, 0x030c6, 0x030c8, 0x030ca, 0x030cb, 0x030cc, 0x030cd, 0x030ce, 0x030cf, 0x030d2, 0x030d5, 0x030d8, 0x030db, 0x030de, 0x030df, 0x030e0, 0x030e1, 0x030e2, 0x030e4, 0x030e6, 0x030e8, 0x030e9, 0x030ea, 0x030eb, 0x030ec, 0x030ed, 0x030ef, 0x030f3, 0x03099, 0x0309a, 0x03164, 0x03131, 0x03132, 0x03133, 0x03134, 0x03135, 0x03136, 0x03137, 0x03138, 0x03139, 0x0313a, 0x0313b, 0x0313c, 0x0313d, 0x0313e, 0x0313f, 0x03140, 0x03141, 0x03142, 0x03143, 0x03144, 0x03145, 0x03146, 0x03147, 0x03148, 0x03149, 0x0314a, 0x0314b, 0x0314c, 0x0314d, 0x0314e, 0x0314f, 0x03150, 0x03151, 0x03152, 0x03153, 0x03154, 0x03155, 0x03156, 0x03157, 0x03158, 0x03159, 0x0315a, 0x0315b, 0x0315c, 0x0315d, 0x0315e, 0x0315f, 0x03160, 0x03161, 0x03162, 0x03163, 0x000a2, 0x000a3, 0x000ac, 0x000af, 0x000a6, 0x000a5, 0x020a9, 0x02502, 0x02190, 0x02191, 0x02192, 0x02193, 0x025a0, 0x025cb }; static const uint32_t uni32_decomp_keys[] = { 0x1d400, 0x1d401, 0x1d402, 0x1d403, 0x1d404, 0x1d405, 0x1d406, 0x1d407, 0x1d408, 0x1d409, 0x1d40a, 0x1d40b, 0x1d40c, 0x1d40d, 0x1d40e, 0x1d40f, 0x1d410, 0x1d411, 0x1d412, 0x1d413, 0x1d414, 0x1d415, 0x1d416, 0x1d417, 0x1d418, 0x1d419, 0x1d41a, 0x1d41b, 0x1d41c, 0x1d41d, 0x1d41e, 0x1d41f, 0x1d420, 0x1d421, 0x1d422, 0x1d423, 0x1d424, 0x1d425, 0x1d426, 0x1d427, 0x1d428, 0x1d429, 0x1d42a, 0x1d42b, 0x1d42c, 0x1d42d, 0x1d42e, 0x1d42f, 0x1d430, 0x1d431, 0x1d432, 0x1d433, 0x1d434, 0x1d435, 0x1d436, 0x1d437, 0x1d438, 0x1d439, 0x1d43a, 0x1d43b, 0x1d43c, 0x1d43d, 0x1d43e, 0x1d43f, 0x1d440, 0x1d441, 0x1d442, 0x1d443, 0x1d444, 0x1d445, 0x1d446, 0x1d447, 0x1d448, 0x1d449, 0x1d44a, 0x1d44b, 0x1d44c, 0x1d44d, 0x1d44e, 0x1d44f, 0x1d450, 0x1d451, 0x1d452, 0x1d453, 0x1d454, 0x1d456, 0x1d457, 0x1d458, 0x1d459, 0x1d45a, 0x1d45b, 0x1d45c, 0x1d45d, 0x1d45e, 0x1d45f, 0x1d460, 0x1d461, 0x1d462, 0x1d463, 0x1d464, 0x1d465, 0x1d466, 0x1d467, 0x1d468, 0x1d469, 0x1d46a, 0x1d46b, 0x1d46c, 0x1d46d, 0x1d46e, 0x1d46f, 0x1d470, 0x1d471, 0x1d472, 0x1d473, 0x1d474, 0x1d475, 0x1d476, 0x1d477, 0x1d478, 0x1d479, 0x1d47a, 0x1d47b, 0x1d47c, 0x1d47d, 0x1d47e, 0x1d47f, 0x1d480, 0x1d481, 0x1d482, 0x1d483, 0x1d484, 0x1d485, 0x1d486, 0x1d487, 0x1d488, 0x1d489, 0x1d48a, 0x1d48b, 0x1d48c, 0x1d48d, 0x1d48e, 0x1d48f, 0x1d490, 0x1d491, 0x1d492, 0x1d493, 0x1d494, 0x1d495, 0x1d496, 0x1d497, 0x1d498, 0x1d499, 0x1d49a, 0x1d49b, 0x1d49c, 0x1d49e, 0x1d49f, 0x1d4a2, 0x1d4a5, 0x1d4a6, 0x1d4a9, 0x1d4aa, 0x1d4ab, 0x1d4ac, 0x1d4ae, 0x1d4af, 0x1d4b0, 0x1d4b1, 0x1d4b2, 0x1d4b3, 0x1d4b4, 0x1d4b5, 0x1d4b6, 0x1d4b7, 0x1d4b8, 0x1d4b9, 0x1d4bb, 0x1d4bd, 0x1d4be, 0x1d4bf, 0x1d4c0, 0x1d4c1, 0x1d4c2, 0x1d4c3, 0x1d4c5, 0x1d4c6, 0x1d4c7, 0x1d4c8, 0x1d4c9, 0x1d4ca, 0x1d4cb, 0x1d4cc, 0x1d4cd, 0x1d4ce, 0x1d4cf, 0x1d4d0, 0x1d4d1, 0x1d4d2, 0x1d4d3, 0x1d4d4, 0x1d4d5, 0x1d4d6, 0x1d4d7, 0x1d4d8, 0x1d4d9, 0x1d4da, 0x1d4db, 0x1d4dc, 0x1d4dd, 0x1d4de, 0x1d4df, 0x1d4e0, 0x1d4e1, 0x1d4e2, 0x1d4e3, 0x1d4e4, 0x1d4e5, 0x1d4e6, 0x1d4e7, 0x1d4e8, 0x1d4e9, 0x1d4ea, 0x1d4eb, 0x1d4ec, 0x1d4ed, 0x1d4ee, 0x1d4ef, 0x1d4f0, 0x1d4f1, 0x1d4f2, 0x1d4f3, 0x1d4f4, 0x1d4f5, 0x1d4f6, 0x1d4f7, 0x1d4f8, 0x1d4f9, 0x1d4fa, 0x1d4fb, 0x1d4fc, 0x1d4fd, 0x1d4fe, 0x1d4ff, 0x1d500, 0x1d501, 0x1d502, 0x1d503, 0x1d504, 0x1d505, 0x1d507, 0x1d508, 0x1d509, 0x1d50a, 0x1d50d, 0x1d50e, 0x1d50f, 0x1d510, 0x1d511, 0x1d512, 0x1d513, 0x1d514, 0x1d516, 0x1d517, 0x1d518, 0x1d519, 0x1d51a, 0x1d51b, 0x1d51c, 0x1d51e, 0x1d51f, 0x1d520, 0x1d521, 0x1d522, 0x1d523, 0x1d524, 0x1d525, 0x1d526, 0x1d527, 0x1d528, 0x1d529, 0x1d52a, 0x1d52b, 0x1d52c, 0x1d52d, 0x1d52e, 0x1d52f, 0x1d530, 0x1d531, 0x1d532, 0x1d533, 0x1d534, 0x1d535, 0x1d536, 0x1d537, 0x1d538, 0x1d539, 0x1d53b, 0x1d53c, 0x1d53d, 0x1d53e, 0x1d540, 0x1d541, 0x1d542, 0x1d543, 0x1d544, 0x1d546, 0x1d54a, 0x1d54b, 0x1d54c, 0x1d54d, 0x1d54e, 0x1d54f, 0x1d550, 0x1d552, 0x1d553, 0x1d554, 0x1d555, 0x1d556, 0x1d557, 0x1d558, 0x1d559, 0x1d55a, 0x1d55b, 0x1d55c, 0x1d55d, 0x1d55e, 0x1d55f, 0x1d560, 0x1d561, 0x1d562, 0x1d563, 0x1d564, 0x1d565, 0x1d566, 0x1d567, 0x1d568, 0x1d569, 0x1d56a, 0x1d56b, 0x1d56c, 0x1d56d, 0x1d56e, 0x1d56f, 0x1d570, 0x1d571, 0x1d572, 0x1d573, 0x1d574, 0x1d575, 0x1d576, 0x1d577, 0x1d578, 0x1d579, 0x1d57a, 0x1d57b, 0x1d57c, 0x1d57d, 0x1d57e, 0x1d57f, 0x1d580, 0x1d581, 0x1d582, 0x1d583, 0x1d584, 0x1d585, 0x1d586, 0x1d587, 0x1d588, 0x1d589, 0x1d58a, 0x1d58b, 0x1d58c, 0x1d58d, 0x1d58e, 0x1d58f, 0x1d590, 0x1d591, 0x1d592, 0x1d593, 0x1d594, 0x1d595, 0x1d596, 0x1d597, 0x1d598, 0x1d599, 0x1d59a, 0x1d59b, 0x1d59c, 0x1d59d, 0x1d59e, 0x1d59f, 0x1d5a0, 0x1d5a1, 0x1d5a2, 0x1d5a3, 0x1d5a4, 0x1d5a5, 0x1d5a6, 0x1d5a7, 0x1d5a8, 0x1d5a9, 0x1d5aa, 0x1d5ab, 0x1d5ac, 0x1d5ad, 0x1d5ae, 0x1d5af, 0x1d5b0, 0x1d5b1, 0x1d5b2, 0x1d5b3, 0x1d5b4, 0x1d5b5, 0x1d5b6, 0x1d5b7, 0x1d5b8, 0x1d5b9, 0x1d5ba, 0x1d5bb, 0x1d5bc, 0x1d5bd, 0x1d5be, 0x1d5bf, 0x1d5c0, 0x1d5c1, 0x1d5c2, 0x1d5c3, 0x1d5c4, 0x1d5c5, 0x1d5c6, 0x1d5c7, 0x1d5c8, 0x1d5c9, 0x1d5ca, 0x1d5cb, 0x1d5cc, 0x1d5cd, 0x1d5ce, 0x1d5cf, 0x1d5d0, 0x1d5d1, 0x1d5d2, 0x1d5d3, 0x1d5d4, 0x1d5d5, 0x1d5d6, 0x1d5d7, 0x1d5d8, 0x1d5d9, 0x1d5da, 0x1d5db, 0x1d5dc, 0x1d5dd, 0x1d5de, 0x1d5df, 0x1d5e0, 0x1d5e1, 0x1d5e2, 0x1d5e3, 0x1d5e4, 0x1d5e5, 0x1d5e6, 0x1d5e7, 0x1d5e8, 0x1d5e9, 0x1d5ea, 0x1d5eb, 0x1d5ec, 0x1d5ed, 0x1d5ee, 0x1d5ef, 0x1d5f0, 0x1d5f1, 0x1d5f2, 0x1d5f3, 0x1d5f4, 0x1d5f5, 0x1d5f6, 0x1d5f7, 0x1d5f8, 0x1d5f9, 0x1d5fa, 0x1d5fb, 0x1d5fc, 0x1d5fd, 0x1d5fe, 0x1d5ff, 0x1d600, 0x1d601, 0x1d602, 0x1d603, 0x1d604, 0x1d605, 0x1d606, 0x1d607, 0x1d608, 0x1d609, 0x1d60a, 0x1d60b, 0x1d60c, 0x1d60d, 0x1d60e, 0x1d60f, 0x1d610, 0x1d611, 0x1d612, 0x1d613, 0x1d614, 0x1d615, 0x1d616, 0x1d617, 0x1d618, 0x1d619, 0x1d61a, 0x1d61b, 0x1d61c, 0x1d61d, 0x1d61e, 0x1d61f, 0x1d620, 0x1d621, 0x1d622, 0x1d623, 0x1d624, 0x1d625, 0x1d626, 0x1d627, 0x1d628, 0x1d629, 0x1d62a, 0x1d62b, 0x1d62c, 0x1d62d, 0x1d62e, 0x1d62f, 0x1d630, 0x1d631, 0x1d632, 0x1d633, 0x1d634, 0x1d635, 0x1d636, 0x1d637, 0x1d638, 0x1d639, 0x1d63a, 0x1d63b, 0x1d63c, 0x1d63d, 0x1d63e, 0x1d63f, 0x1d640, 0x1d641, 0x1d642, 0x1d643, 0x1d644, 0x1d645, 0x1d646, 0x1d647, 0x1d648, 0x1d649, 0x1d64a, 0x1d64b, 0x1d64c, 0x1d64d, 0x1d64e, 0x1d64f, 0x1d650, 0x1d651, 0x1d652, 0x1d653, 0x1d654, 0x1d655, 0x1d656, 0x1d657, 0x1d658, 0x1d659, 0x1d65a, 0x1d65b, 0x1d65c, 0x1d65d, 0x1d65e, 0x1d65f, 0x1d660, 0x1d661, 0x1d662, 0x1d663, 0x1d664, 0x1d665, 0x1d666, 0x1d667, 0x1d668, 0x1d669, 0x1d66a, 0x1d66b, 0x1d66c, 0x1d66d, 0x1d66e, 0x1d66f, 0x1d670, 0x1d671, 0x1d672, 0x1d673, 0x1d674, 0x1d675, 0x1d676, 0x1d677, 0x1d678, 0x1d679, 0x1d67a, 0x1d67b, 0x1d67c, 0x1d67d, 0x1d67e, 0x1d67f, 0x1d680, 0x1d681, 0x1d682, 0x1d683, 0x1d684, 0x1d685, 0x1d686, 0x1d687, 0x1d688, 0x1d689, 0x1d68a, 0x1d68b, 0x1d68c, 0x1d68d, 0x1d68e, 0x1d68f, 0x1d690, 0x1d691, 0x1d692, 0x1d693, 0x1d694, 0x1d695, 0x1d696, 0x1d697, 0x1d698, 0x1d699, 0x1d69a, 0x1d69b, 0x1d69c, 0x1d69d, 0x1d69e, 0x1d69f, 0x1d6a0, 0x1d6a1, 0x1d6a2, 0x1d6a3, 0x1d6a4, 0x1d6a5, 0x1d6a8, 0x1d6a9, 0x1d6aa, 0x1d6ab, 0x1d6ac, 0x1d6ad, 0x1d6ae, 0x1d6af, 0x1d6b0, 0x1d6b1, 0x1d6b2, 0x1d6b3, 0x1d6b4, 0x1d6b5, 0x1d6b6, 0x1d6b7, 0x1d6b8, 0x1d6b9, 0x1d6ba, 0x1d6bb, 0x1d6bc, 0x1d6bd, 0x1d6be, 0x1d6bf, 0x1d6c0, 0x1d6c1, 0x1d6c2, 0x1d6c3, 0x1d6c4, 0x1d6c5, 0x1d6c6, 0x1d6c7, 0x1d6c8, 0x1d6c9, 0x1d6ca, 0x1d6cb, 0x1d6cc, 0x1d6cd, 0x1d6ce, 0x1d6cf, 0x1d6d0, 0x1d6d1, 0x1d6d2, 0x1d6d3, 0x1d6d4, 0x1d6d5, 0x1d6d6, 0x1d6d7, 0x1d6d8, 0x1d6d9, 0x1d6da, 0x1d6db, 0x1d6dc, 0x1d6dd, 0x1d6de, 0x1d6df, 0x1d6e0, 0x1d6e1, 0x1d6e2, 0x1d6e3, 0x1d6e4, 0x1d6e5, 0x1d6e6, 0x1d6e7, 0x1d6e8, 0x1d6e9, 0x1d6ea, 0x1d6eb, 0x1d6ec, 0x1d6ed, 0x1d6ee, 0x1d6ef, 0x1d6f0, 0x1d6f1, 0x1d6f2, 0x1d6f3, 0x1d6f4, 0x1d6f5, 0x1d6f6, 0x1d6f7, 0x1d6f8, 0x1d6f9, 0x1d6fa, 0x1d6fb, 0x1d6fc, 0x1d6fd, 0x1d6fe, 0x1d6ff, 0x1d700, 0x1d701, 0x1d702, 0x1d703, 0x1d704, 0x1d705, 0x1d706, 0x1d707, 0x1d708, 0x1d709, 0x1d70a, 0x1d70b, 0x1d70c, 0x1d70d, 0x1d70e, 0x1d70f, 0x1d710, 0x1d711, 0x1d712, 0x1d713, 0x1d714, 0x1d715, 0x1d716, 0x1d717, 0x1d718, 0x1d719, 0x1d71a, 0x1d71b, 0x1d71c, 0x1d71d, 0x1d71e, 0x1d71f, 0x1d720, 0x1d721, 0x1d722, 0x1d723, 0x1d724, 0x1d725, 0x1d726, 0x1d727, 0x1d728, 0x1d729, 0x1d72a, 0x1d72b, 0x1d72c, 0x1d72d, 0x1d72e, 0x1d72f, 0x1d730, 0x1d731, 0x1d732, 0x1d733, 0x1d734, 0x1d735, 0x1d736, 0x1d737, 0x1d738, 0x1d739, 0x1d73a, 0x1d73b, 0x1d73c, 0x1d73d, 0x1d73e, 0x1d73f, 0x1d740, 0x1d741, 0x1d742, 0x1d743, 0x1d744, 0x1d745, 0x1d746, 0x1d747, 0x1d748, 0x1d749, 0x1d74a, 0x1d74b, 0x1d74c, 0x1d74d, 0x1d74e, 0x1d74f, 0x1d750, 0x1d751, 0x1d752, 0x1d753, 0x1d754, 0x1d755, 0x1d756, 0x1d757, 0x1d758, 0x1d759, 0x1d75a, 0x1d75b, 0x1d75c, 0x1d75d, 0x1d75e, 0x1d75f, 0x1d760, 0x1d761, 0x1d762, 0x1d763, 0x1d764, 0x1d765, 0x1d766, 0x1d767, 0x1d768, 0x1d769, 0x1d76a, 0x1d76b, 0x1d76c, 0x1d76d, 0x1d76e, 0x1d76f, 0x1d770, 0x1d771, 0x1d772, 0x1d773, 0x1d774, 0x1d775, 0x1d776, 0x1d777, 0x1d778, 0x1d779, 0x1d77a, 0x1d77b, 0x1d77c, 0x1d77d, 0x1d77e, 0x1d77f, 0x1d780, 0x1d781, 0x1d782, 0x1d783, 0x1d784, 0x1d785, 0x1d786, 0x1d787, 0x1d788, 0x1d789, 0x1d78a, 0x1d78b, 0x1d78c, 0x1d78d, 0x1d78e, 0x1d78f, 0x1d790, 0x1d791, 0x1d792, 0x1d793, 0x1d794, 0x1d795, 0x1d796, 0x1d797, 0x1d798, 0x1d799, 0x1d79a, 0x1d79b, 0x1d79c, 0x1d79d, 0x1d79e, 0x1d79f, 0x1d7a0, 0x1d7a1, 0x1d7a2, 0x1d7a3, 0x1d7a4, 0x1d7a5, 0x1d7a6, 0x1d7a7, 0x1d7a8, 0x1d7a9, 0x1d7aa, 0x1d7ab, 0x1d7ac, 0x1d7ad, 0x1d7ae, 0x1d7af, 0x1d7b0, 0x1d7b1, 0x1d7b2, 0x1d7b3, 0x1d7b4, 0x1d7b5, 0x1d7b6, 0x1d7b7, 0x1d7b8, 0x1d7b9, 0x1d7ba, 0x1d7bb, 0x1d7bc, 0x1d7bd, 0x1d7be, 0x1d7bf, 0x1d7c0, 0x1d7c1, 0x1d7c2, 0x1d7c3, 0x1d7c4, 0x1d7c5, 0x1d7c6, 0x1d7c7, 0x1d7c8, 0x1d7c9, 0x1d7ca, 0x1d7cb, 0x1d7ce, 0x1d7cf, 0x1d7d0, 0x1d7d1, 0x1d7d2, 0x1d7d3, 0x1d7d4, 0x1d7d5, 0x1d7d6, 0x1d7d7, 0x1d7d8, 0x1d7d9, 0x1d7da, 0x1d7db, 0x1d7dc, 0x1d7dd, 0x1d7de, 0x1d7df, 0x1d7e0, 0x1d7e1, 0x1d7e2, 0x1d7e3, 0x1d7e4, 0x1d7e5, 0x1d7e6, 0x1d7e7, 0x1d7e8, 0x1d7e9, 0x1d7ea, 0x1d7eb, 0x1d7ec, 0x1d7ed, 0x1d7ee, 0x1d7ef, 0x1d7f0, 0x1d7f1, 0x1d7f2, 0x1d7f3, 0x1d7f4, 0x1d7f5, 0x1d7f6, 0x1d7f7, 0x1d7f8, 0x1d7f9, 0x1d7fa, 0x1d7fb, 0x1d7fc, 0x1d7fd, 0x1d7fe, 0x1d7ff, 0x1ee00, 0x1ee01, 0x1ee02, 0x1ee03, 0x1ee05, 0x1ee06, 0x1ee07, 0x1ee08, 0x1ee09, 0x1ee0a, 0x1ee0b, 0x1ee0c, 0x1ee0d, 0x1ee0e, 0x1ee0f, 0x1ee10, 0x1ee11, 0x1ee12, 0x1ee13, 0x1ee14, 0x1ee15, 0x1ee16, 0x1ee17, 0x1ee18, 0x1ee19, 0x1ee1a, 0x1ee1b, 0x1ee1c, 0x1ee1d, 0x1ee1e, 0x1ee1f, 0x1ee21, 0x1ee22, 0x1ee24, 0x1ee27, 0x1ee29, 0x1ee2a, 0x1ee2b, 0x1ee2c, 0x1ee2d, 0x1ee2e, 0x1ee2f, 0x1ee30, 0x1ee31, 0x1ee32, 0x1ee34, 0x1ee35, 0x1ee36, 0x1ee37, 0x1ee39, 0x1ee3b, 0x1ee42, 0x1ee47, 0x1ee49, 0x1ee4b, 0x1ee4d, 0x1ee4e, 0x1ee4f, 0x1ee51, 0x1ee52, 0x1ee54, 0x1ee57, 0x1ee59, 0x1ee5b, 0x1ee5d, 0x1ee5f, 0x1ee61, 0x1ee62, 0x1ee64, 0x1ee67, 0x1ee68, 0x1ee69, 0x1ee6a, 0x1ee6c, 0x1ee6d, 0x1ee6e, 0x1ee6f, 0x1ee70, 0x1ee71, 0x1ee72, 0x1ee74, 0x1ee75, 0x1ee76, 0x1ee77, 0x1ee79, 0x1ee7a, 0x1ee7b, 0x1ee7c, 0x1ee7e, 0x1ee80, 0x1ee81, 0x1ee82, 0x1ee83, 0x1ee84, 0x1ee85, 0x1ee86, 0x1ee87, 0x1ee88, 0x1ee89, 0x1ee8b, 0x1ee8c, 0x1ee8d, 0x1ee8e, 0x1ee8f, 0x1ee90, 0x1ee91, 0x1ee92, 0x1ee93, 0x1ee94, 0x1ee95, 0x1ee96, 0x1ee97, 0x1ee98, 0x1ee99, 0x1ee9a, 0x1ee9b, 0x1eea1, 0x1eea2, 0x1eea3, 0x1eea5, 0x1eea6, 0x1eea7, 0x1eea8, 0x1eea9, 0x1eeab, 0x1eeac, 0x1eead, 0x1eeae, 0x1eeaf, 0x1eeb0, 0x1eeb1, 0x1eeb2, 0x1eeb3, 0x1eeb4, 0x1eeb5, 0x1eeb6, 0x1eeb7, 0x1eeb8, 0x1eeb9, 0x1eeba, 0x1eebb, 0x1f12b, 0x1f12c, 0x1f130, 0x1f131, 0x1f132, 0x1f133, 0x1f134, 0x1f135, 0x1f136, 0x1f137, 0x1f138, 0x1f139, 0x1f13a, 0x1f13b, 0x1f13c, 0x1f13d, 0x1f13e, 0x1f13f, 0x1f140, 0x1f141, 0x1f142, 0x1f143, 0x1f144, 0x1f145, 0x1f146, 0x1f147, 0x1f148, 0x1f149, 0x1f202, 0x1f210, 0x1f211, 0x1f212, 0x1f213, 0x1f214, 0x1f215, 0x1f216, 0x1f217, 0x1f218, 0x1f219, 0x1f21a, 0x1f21b, 0x1f21c, 0x1f21d, 0x1f21e, 0x1f21f, 0x1f220, 0x1f221, 0x1f222, 0x1f223, 0x1f224, 0x1f225, 0x1f226, 0x1f227, 0x1f228, 0x1f229, 0x1f22a, 0x1f22b, 0x1f22c, 0x1f22d, 0x1f22e, 0x1f22f, 0x1f230, 0x1f231, 0x1f232, 0x1f233, 0x1f234, 0x1f235, 0x1f236, 0x1f237, 0x1f238, 0x1f239, 0x1f23a, 0x1f23b, 0x1f250, 0x1f251, 0x2f800, 0x2f801, 0x2f802, 0x2f803, 0x2f804, 0x2f805, 0x2f806, 0x2f807, 0x2f808, 0x2f809, 0x2f80a, 0x2f80b, 0x2f80c, 0x2f80d, 0x2f80e, 0x2f80f, 0x2f810, 0x2f811, 0x2f812, 0x2f813, 0x2f814, 0x2f815, 0x2f816, 0x2f817, 0x2f818, 0x2f819, 0x2f81a, 0x2f81b, 0x2f81c, 0x2f81d, 0x2f81e, 0x2f81f, 0x2f820, 0x2f821, 0x2f822, 0x2f823, 0x2f824, 0x2f825, 0x2f826, 0x2f827, 0x2f828, 0x2f829, 0x2f82a, 0x2f82b, 0x2f82c, 0x2f82d, 0x2f82e, 0x2f82f, 0x2f830, 0x2f831, 0x2f832, 0x2f833, 0x2f834, 0x2f835, 0x2f836, 0x2f837, 0x2f838, 0x2f839, 0x2f83a, 0x2f83b, 0x2f83c, 0x2f83d, 0x2f83e, 0x2f83f, 0x2f840, 0x2f841, 0x2f842, 0x2f843, 0x2f844, 0x2f845, 0x2f846, 0x2f847, 0x2f848, 0x2f849, 0x2f84a, 0x2f84b, 0x2f84c, 0x2f84d, 0x2f84e, 0x2f84f, 0x2f850, 0x2f851, 0x2f852, 0x2f853, 0x2f854, 0x2f855, 0x2f856, 0x2f857, 0x2f858, 0x2f859, 0x2f85a, 0x2f85b, 0x2f85c, 0x2f85d, 0x2f85e, 0x2f85f, 0x2f860, 0x2f861, 0x2f862, 0x2f863, 0x2f864, 0x2f865, 0x2f866, 0x2f867, 0x2f868, 0x2f869, 0x2f86a, 0x2f86b, 0x2f86c, 0x2f86d, 0x2f86e, 0x2f86f, 0x2f870, 0x2f871, 0x2f872, 0x2f873, 0x2f874, 0x2f875, 0x2f876, 0x2f877, 0x2f878, 0x2f879, 0x2f87a, 0x2f87b, 0x2f87c, 0x2f87d, 0x2f87e, 0x2f87f, 0x2f880, 0x2f881, 0x2f882, 0x2f883, 0x2f884, 0x2f885, 0x2f886, 0x2f887, 0x2f888, 0x2f889, 0x2f88a, 0x2f88b, 0x2f88c, 0x2f88d, 0x2f88e, 0x2f88f, 0x2f890, 0x2f891, 0x2f892, 0x2f893, 0x2f894, 0x2f895, 0x2f896, 0x2f897, 0x2f898, 0x2f899, 0x2f89a, 0x2f89b, 0x2f89c, 0x2f89d, 0x2f89e, 0x2f89f, 0x2f8a0, 0x2f8a1, 0x2f8a2, 0x2f8a3, 0x2f8a4, 0x2f8a5, 0x2f8a6, 0x2f8a7, 0x2f8a8, 0x2f8a9, 0x2f8aa, 0x2f8ab, 0x2f8ac, 0x2f8ad, 0x2f8ae, 0x2f8af, 0x2f8b0, 0x2f8b1, 0x2f8b2, 0x2f8b3, 0x2f8b4, 0x2f8b5, 0x2f8b6, 0x2f8b7, 0x2f8b8, 0x2f8b9, 0x2f8ba, 0x2f8bb, 0x2f8bc, 0x2f8bd, 0x2f8be, 0x2f8bf, 0x2f8c0, 0x2f8c1, 0x2f8c2, 0x2f8c3, 0x2f8c4, 0x2f8c5, 0x2f8c6, 0x2f8c7, 0x2f8c8, 0x2f8c9, 0x2f8ca, 0x2f8cb, 0x2f8cc, 0x2f8cd, 0x2f8ce, 0x2f8cf, 0x2f8d0, 0x2f8d1, 0x2f8d2, 0x2f8d3, 0x2f8d4, 0x2f8d5, 0x2f8d6, 0x2f8d7, 0x2f8d8, 0x2f8d9, 0x2f8da, 0x2f8db, 0x2f8dc, 0x2f8dd, 0x2f8de, 0x2f8df, 0x2f8e0, 0x2f8e1, 0x2f8e2, 0x2f8e3, 0x2f8e4, 0x2f8e5, 0x2f8e6, 0x2f8e7, 0x2f8e8, 0x2f8e9, 0x2f8ea, 0x2f8eb, 0x2f8ec, 0x2f8ed, 0x2f8ee, 0x2f8ef, 0x2f8f0, 0x2f8f1, 0x2f8f2, 0x2f8f3, 0x2f8f4, 0x2f8f5, 0x2f8f6, 0x2f8f7, 0x2f8f8, 0x2f8f9, 0x2f8fa, 0x2f8fb, 0x2f8fc, 0x2f8fd, 0x2f8fe, 0x2f8ff, 0x2f900, 0x2f901, 0x2f902, 0x2f903, 0x2f904, 0x2f905, 0x2f906, 0x2f907, 0x2f908, 0x2f909, 0x2f90a, 0x2f90b, 0x2f90c, 0x2f90d, 0x2f90e, 0x2f90f, 0x2f910, 0x2f911, 0x2f912, 0x2f913, 0x2f914, 0x2f915, 0x2f916, 0x2f917, 0x2f918, 0x2f919, 0x2f91a, 0x2f91b, 0x2f91c, 0x2f91d, 0x2f91e, 0x2f91f, 0x2f920, 0x2f921, 0x2f922, 0x2f923, 0x2f924, 0x2f925, 0x2f926, 0x2f927, 0x2f928, 0x2f929, 0x2f92a, 0x2f92b, 0x2f92c, 0x2f92d, 0x2f92e, 0x2f92f, 0x2f930, 0x2f931, 0x2f932, 0x2f933, 0x2f934, 0x2f935, 0x2f936, 0x2f937, 0x2f938, 0x2f939, 0x2f93a, 0x2f93b, 0x2f93c, 0x2f93d, 0x2f93e, 0x2f93f, 0x2f940, 0x2f941, 0x2f942, 0x2f943, 0x2f944, 0x2f945, 0x2f946, 0x2f947, 0x2f948, 0x2f949, 0x2f94a, 0x2f94b, 0x2f94c, 0x2f94d, 0x2f94e, 0x2f94f, 0x2f950, 0x2f951, 0x2f952, 0x2f953, 0x2f954, 0x2f955, 0x2f956, 0x2f957, 0x2f958, 0x2f959, 0x2f95a, 0x2f95b, 0x2f95c, 0x2f95d, 0x2f95e, 0x2f95f, 0x2f960, 0x2f961, 0x2f962, 0x2f963, 0x2f964, 0x2f965, 0x2f966, 0x2f967, 0x2f968, 0x2f969, 0x2f96a, 0x2f96b, 0x2f96c, 0x2f96d, 0x2f96e, 0x2f96f, 0x2f970, 0x2f971, 0x2f972, 0x2f973, 0x2f974, 0x2f975, 0x2f976, 0x2f977, 0x2f978, 0x2f979, 0x2f97a, 0x2f97b, 0x2f97c, 0x2f97d, 0x2f97e, 0x2f97f, 0x2f980, 0x2f981, 0x2f982, 0x2f983, 0x2f984, 0x2f985, 0x2f986, 0x2f987, 0x2f988, 0x2f989, 0x2f98a, 0x2f98b, 0x2f98c, 0x2f98d, 0x2f98e, 0x2f98f, 0x2f990, 0x2f991, 0x2f992, 0x2f993, 0x2f994, 0x2f995, 0x2f996, 0x2f997, 0x2f998, 0x2f999, 0x2f99a, 0x2f99b, 0x2f99c, 0x2f99d, 0x2f99e, 0x2f99f, 0x2f9a0, 0x2f9a1, 0x2f9a2, 0x2f9a3, 0x2f9a4, 0x2f9a5, 0x2f9a6, 0x2f9a7, 0x2f9a8, 0x2f9a9, 0x2f9aa, 0x2f9ab, 0x2f9ac, 0x2f9ad, 0x2f9ae, 0x2f9af, 0x2f9b0, 0x2f9b1, 0x2f9b2, 0x2f9b3, 0x2f9b4, 0x2f9b5, 0x2f9b6, 0x2f9b7, 0x2f9b8, 0x2f9b9, 0x2f9ba, 0x2f9bb, 0x2f9bc, 0x2f9bd, 0x2f9be, 0x2f9bf, 0x2f9c0, 0x2f9c1, 0x2f9c2, 0x2f9c3, 0x2f9c4, 0x2f9c5, 0x2f9c6, 0x2f9c7, 0x2f9c8, 0x2f9c9, 0x2f9ca, 0x2f9cb, 0x2f9cc, 0x2f9cd, 0x2f9ce, 0x2f9cf, 0x2f9d0, 0x2f9d1, 0x2f9d2, 0x2f9d3, 0x2f9d4, 0x2f9d5, 0x2f9d6, 0x2f9d7, 0x2f9d8, 0x2f9d9, 0x2f9da, 0x2f9db, 0x2f9dc, 0x2f9dd, 0x2f9de, 0x2f9df, 0x2f9e0, 0x2f9e1, 0x2f9e2, 0x2f9e3, 0x2f9e4, 0x2f9e5, 0x2f9e6, 0x2f9e7, 0x2f9e8, 0x2f9e9, 0x2f9ea, 0x2f9eb, 0x2f9ec, 0x2f9ed, 0x2f9ee, 0x2f9ef, 0x2f9f0, 0x2f9f1, 0x2f9f2, 0x2f9f3, 0x2f9f4, 0x2f9f5, 0x2f9f6, 0x2f9f7, 0x2f9f8, 0x2f9f9, 0x2f9fa, 0x2f9fb, 0x2f9fc, 0x2f9fd, 0x2f9fe, 0x2f9ff, 0x2fa00, 0x2fa01, 0x2fa02, 0x2fa03, 0x2fa04, 0x2fa05, 0x2fa06, 0x2fa07, 0x2fa08, 0x2fa09, 0x2fa0a, 0x2fa0b, 0x2fa0c, 0x2fa0d, 0x2fa0e, 0x2fa0f, 0x2fa10, 0x2fa11, 0x2fa12, 0x2fa13, 0x2fa14, 0x2fa15, 0x2fa16, 0x2fa17, 0x2fa18, 0x2fa19, 0x2fa1a, 0x2fa1b, 0x2fa1c, 0x2fa1d }; static const uint32_t uni32_decomp_values[] = { 0x00041, 0x00042, 0x00043, 0x00044, 0x00045, 0x00046, 0x00047, 0x00048, 0x00049, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004e, 0x0004f, 0x00050, 0x00051, 0x00052, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x0005a, 0x00061, 0x00062, 0x00063, 0x00064, 0x00065, 0x00066, 0x00067, 0x00068, 0x00069, 0x0006a, 0x0006b, 0x0006c, 0x0006d, 0x0006e, 0x0006f, 0x00070, 0x00071, 0x00072, 0x00073, 0x00074, 0x00075, 0x00076, 0x00077, 0x00078, 0x00079, 0x0007a, 0x00041, 0x00042, 0x00043, 0x00044, 0x00045, 0x00046, 0x00047, 0x00048, 0x00049, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004e, 0x0004f, 0x00050, 0x00051, 0x00052, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x0005a, 0x00061, 0x00062, 0x00063, 0x00064, 0x00065, 0x00066, 0x00067, 0x00069, 0x0006a, 0x0006b, 0x0006c, 0x0006d, 0x0006e, 0x0006f, 0x00070, 0x00071, 0x00072, 0x00073, 0x00074, 0x00075, 0x00076, 0x00077, 0x00078, 0x00079, 0x0007a, 0x00041, 0x00042, 0x00043, 0x00044, 0x00045, 0x00046, 0x00047, 0x00048, 0x00049, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004e, 0x0004f, 0x00050, 0x00051, 0x00052, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x0005a, 0x00061, 0x00062, 0x00063, 0x00064, 0x00065, 0x00066, 0x00067, 0x00068, 0x00069, 0x0006a, 0x0006b, 0x0006c, 0x0006d, 0x0006e, 0x0006f, 0x00070, 0x00071, 0x00072, 0x00073, 0x00074, 0x00075, 0x00076, 0x00077, 0x00078, 0x00079, 0x0007a, 0x00041, 0x00043, 0x00044, 0x00047, 0x0004a, 0x0004b, 0x0004e, 0x0004f, 0x00050, 0x00051, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x0005a, 0x00061, 0x00062, 0x00063, 0x00064, 0x00066, 0x00068, 0x00069, 0x0006a, 0x0006b, 0x0006c, 0x0006d, 0x0006e, 0x00070, 0x00071, 0x00072, 0x00073, 0x00074, 0x00075, 0x00076, 0x00077, 0x00078, 0x00079, 0x0007a, 0x00041, 0x00042, 0x00043, 0x00044, 0x00045, 0x00046, 0x00047, 0x00048, 0x00049, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004e, 0x0004f, 0x00050, 0x00051, 0x00052, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x0005a, 0x00061, 0x00062, 0x00063, 0x00064, 0x00065, 0x00066, 0x00067, 0x00068, 0x00069, 0x0006a, 0x0006b, 0x0006c, 0x0006d, 0x0006e, 0x0006f, 0x00070, 0x00071, 0x00072, 0x00073, 0x00074, 0x00075, 0x00076, 0x00077, 0x00078, 0x00079, 0x0007a, 0x00041, 0x00042, 0x00044, 0x00045, 0x00046, 0x00047, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004e, 0x0004f, 0x00050, 0x00051, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x00061, 0x00062, 0x00063, 0x00064, 0x00065, 0x00066, 0x00067, 0x00068, 0x00069, 0x0006a, 0x0006b, 0x0006c, 0x0006d, 0x0006e, 0x0006f, 0x00070, 0x00071, 0x00072, 0x00073, 0x00074, 0x00075, 0x00076, 0x00077, 0x00078, 0x00079, 0x0007a, 0x00041, 0x00042, 0x00044, 0x00045, 0x00046, 0x00047, 0x00049, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004f, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x00061, 0x00062, 0x00063, 0x00064, 0x00065, 0x00066, 0x00067, 0x00068, 0x00069, 0x0006a, 0x0006b, 0x0006c, 0x0006d, 0x0006e, 0x0006f, 0x00070, 0x00071, 0x00072, 0x00073, 0x00074, 0x00075, 0x00076, 0x00077, 0x00078, 0x00079, 0x0007a, 0x00041, 0x00042, 0x00043, 0x00044, 0x00045, 0x00046, 0x00047, 0x00048, 0x00049, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004e, 0x0004f, 0x00050, 0x00051, 0x00052, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x0005a, 0x00061, 0x00062, 0x00063, 0x00064, 0x00065, 0x00066, 0x00067, 0x00068, 0x00069, 0x0006a, 0x0006b, 0x0006c, 0x0006d, 0x0006e, 0x0006f, 0x00070, 0x00071, 0x00072, 0x00073, 0x00074, 0x00075, 0x00076, 0x00077, 0x00078, 0x00079, 0x0007a, 0x00041, 0x00042, 0x00043, 0x00044, 0x00045, 0x00046, 0x00047, 0x00048, 0x00049, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004e, 0x0004f, 0x00050, 0x00051, 0x00052, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x0005a, 0x00061, 0x00062, 0x00063, 0x00064, 0x00065, 0x00066, 0x00067, 0x00068, 0x00069, 0x0006a, 0x0006b, 0x0006c, 0x0006d, 0x0006e, 0x0006f, 0x00070, 0x00071, 0x00072, 0x00073, 0x00074, 0x00075, 0x00076, 0x00077, 0x00078, 0x00079, 0x0007a, 0x00041, 0x00042, 0x00043, 0x00044, 0x00045, 0x00046, 0x00047, 0x00048, 0x00049, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004e, 0x0004f, 0x00050, 0x00051, 0x00052, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x0005a, 0x00061, 0x00062, 0x00063, 0x00064, 0x00065, 0x00066, 0x00067, 0x00068, 0x00069, 0x0006a, 0x0006b, 0x0006c, 0x0006d, 0x0006e, 0x0006f, 0x00070, 0x00071, 0x00072, 0x00073, 0x00074, 0x00075, 0x00076, 0x00077, 0x00078, 0x00079, 0x0007a, 0x00041, 0x00042, 0x00043, 0x00044, 0x00045, 0x00046, 0x00047, 0x00048, 0x00049, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004e, 0x0004f, 0x00050, 0x00051, 0x00052, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x0005a, 0x00061, 0x00062, 0x00063, 0x00064, 0x00065, 0x00066, 0x00067, 0x00068, 0x00069, 0x0006a, 0x0006b, 0x0006c, 0x0006d, 0x0006e, 0x0006f, 0x00070, 0x00071, 0x00072, 0x00073, 0x00074, 0x00075, 0x00076, 0x00077, 0x00078, 0x00079, 0x0007a, 0x00041, 0x00042, 0x00043, 0x00044, 0x00045, 0x00046, 0x00047, 0x00048, 0x00049, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004e, 0x0004f, 0x00050, 0x00051, 0x00052, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x0005a, 0x00061, 0x00062, 0x00063, 0x00064, 0x00065, 0x00066, 0x00067, 0x00068, 0x00069, 0x0006a, 0x0006b, 0x0006c, 0x0006d, 0x0006e, 0x0006f, 0x00070, 0x00071, 0x00072, 0x00073, 0x00074, 0x00075, 0x00076, 0x00077, 0x00078, 0x00079, 0x0007a, 0x00041, 0x00042, 0x00043, 0x00044, 0x00045, 0x00046, 0x00047, 0x00048, 0x00049, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004e, 0x0004f, 0x00050, 0x00051, 0x00052, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x0005a, 0x00061, 0x00062, 0x00063, 0x00064, 0x00065, 0x00066, 0x00067, 0x00068, 0x00069, 0x0006a, 0x0006b, 0x0006c, 0x0006d, 0x0006e, 0x0006f, 0x00070, 0x00071, 0x00072, 0x00073, 0x00074, 0x00075, 0x00076, 0x00077, 0x00078, 0x00079, 0x0007a, 0x00131, 0x00237, 0x00391, 0x00392, 0x00393, 0x00394, 0x00395, 0x00396, 0x00397, 0x00398, 0x00399, 0x0039a, 0x0039b, 0x0039c, 0x0039d, 0x0039e, 0x0039f, 0x003a0, 0x003a1, 0x003f4, 0x003a3, 0x003a4, 0x003a5, 0x003a6, 0x003a7, 0x003a8, 0x003a9, 0x02207, 0x003b1, 0x003b2, 0x003b3, 0x003b4, 0x003b5, 0x003b6, 0x003b7, 0x003b8, 0x003b9, 0x003ba, 0x003bb, 0x003bc, 0x003bd, 0x003be, 0x003bf, 0x003c0, 0x003c1, 0x003c2, 0x003c3, 0x003c4, 0x003c5, 0x003c6, 0x003c7, 0x003c8, 0x003c9, 0x02202, 0x003f5, 0x003d1, 0x003f0, 0x003d5, 0x003f1, 0x003d6, 0x00391, 0x00392, 0x00393, 0x00394, 0x00395, 0x00396, 0x00397, 0x00398, 0x00399, 0x0039a, 0x0039b, 0x0039c, 0x0039d, 0x0039e, 0x0039f, 0x003a0, 0x003a1, 0x003f4, 0x003a3, 0x003a4, 0x003a5, 0x003a6, 0x003a7, 0x003a8, 0x003a9, 0x02207, 0x003b1, 0x003b2, 0x003b3, 0x003b4, 0x003b5, 0x003b6, 0x003b7, 0x003b8, 0x003b9, 0x003ba, 0x003bb, 0x003bc, 0x003bd, 0x003be, 0x003bf, 0x003c0, 0x003c1, 0x003c2, 0x003c3, 0x003c4, 0x003c5, 0x003c6, 0x003c7, 0x003c8, 0x003c9, 0x02202, 0x003f5, 0x003d1, 0x003f0, 0x003d5, 0x003f1, 0x003d6, 0x00391, 0x00392, 0x00393, 0x00394, 0x00395, 0x00396, 0x00397, 0x00398, 0x00399, 0x0039a, 0x0039b, 0x0039c, 0x0039d, 0x0039e, 0x0039f, 0x003a0, 0x003a1, 0x003f4, 0x003a3, 0x003a4, 0x003a5, 0x003a6, 0x003a7, 0x003a8, 0x003a9, 0x02207, 0x003b1, 0x003b2, 0x003b3, 0x003b4, 0x003b5, 0x003b6, 0x003b7, 0x003b8, 0x003b9, 0x003ba, 0x003bb, 0x003bc, 0x003bd, 0x003be, 0x003bf, 0x003c0, 0x003c1, 0x003c2, 0x003c3, 0x003c4, 0x003c5, 0x003c6, 0x003c7, 0x003c8, 0x003c9, 0x02202, 0x003f5, 0x003d1, 0x003f0, 0x003d5, 0x003f1, 0x003d6, 0x00391, 0x00392, 0x00393, 0x00394, 0x00395, 0x00396, 0x00397, 0x00398, 0x00399, 0x0039a, 0x0039b, 0x0039c, 0x0039d, 0x0039e, 0x0039f, 0x003a0, 0x003a1, 0x003f4, 0x003a3, 0x003a4, 0x003a5, 0x003a6, 0x003a7, 0x003a8, 0x003a9, 0x02207, 0x003b1, 0x003b2, 0x003b3, 0x003b4, 0x003b5, 0x003b6, 0x003b7, 0x003b8, 0x003b9, 0x003ba, 0x003bb, 0x003bc, 0x003bd, 0x003be, 0x003bf, 0x003c0, 0x003c1, 0x003c2, 0x003c3, 0x003c4, 0x003c5, 0x003c6, 0x003c7, 0x003c8, 0x003c9, 0x02202, 0x003f5, 0x003d1, 0x003f0, 0x003d5, 0x003f1, 0x003d6, 0x00391, 0x00392, 0x00393, 0x00394, 0x00395, 0x00396, 0x00397, 0x00398, 0x00399, 0x0039a, 0x0039b, 0x0039c, 0x0039d, 0x0039e, 0x0039f, 0x003a0, 0x003a1, 0x003f4, 0x003a3, 0x003a4, 0x003a5, 0x003a6, 0x003a7, 0x003a8, 0x003a9, 0x02207, 0x003b1, 0x003b2, 0x003b3, 0x003b4, 0x003b5, 0x003b6, 0x003b7, 0x003b8, 0x003b9, 0x003ba, 0x003bb, 0x003bc, 0x003bd, 0x003be, 0x003bf, 0x003c0, 0x003c1, 0x003c2, 0x003c3, 0x003c4, 0x003c5, 0x003c6, 0x003c7, 0x003c8, 0x003c9, 0x02202, 0x003f5, 0x003d1, 0x003f0, 0x003d5, 0x003f1, 0x003d6, 0x003dc, 0x003dd, 0x00030, 0x00031, 0x00032, 0x00033, 0x00034, 0x00035, 0x00036, 0x00037, 0x00038, 0x00039, 0x00030, 0x00031, 0x00032, 0x00033, 0x00034, 0x00035, 0x00036, 0x00037, 0x00038, 0x00039, 0x00030, 0x00031, 0x00032, 0x00033, 0x00034, 0x00035, 0x00036, 0x00037, 0x00038, 0x00039, 0x00030, 0x00031, 0x00032, 0x00033, 0x00034, 0x00035, 0x00036, 0x00037, 0x00038, 0x00039, 0x00030, 0x00031, 0x00032, 0x00033, 0x00034, 0x00035, 0x00036, 0x00037, 0x00038, 0x00039, 0x00627, 0x00628, 0x0062c, 0x0062f, 0x00648, 0x00632, 0x0062d, 0x00637, 0x0064a, 0x00643, 0x00644, 0x00645, 0x00646, 0x00633, 0x00639, 0x00641, 0x00635, 0x00642, 0x00631, 0x00634, 0x0062a, 0x0062b, 0x0062e, 0x00630, 0x00636, 0x00638, 0x0063a, 0x0066e, 0x006ba, 0x006a1, 0x0066f, 0x00628, 0x0062c, 0x00647, 0x0062d, 0x0064a, 0x00643, 0x00644, 0x00645, 0x00646, 0x00633, 0x00639, 0x00641, 0x00635, 0x00642, 0x00634, 0x0062a, 0x0062b, 0x0062e, 0x00636, 0x0063a, 0x0062c, 0x0062d, 0x0064a, 0x00644, 0x00646, 0x00633, 0x00639, 0x00635, 0x00642, 0x00634, 0x0062e, 0x00636, 0x0063a, 0x006ba, 0x0066f, 0x00628, 0x0062c, 0x00647, 0x0062d, 0x00637, 0x0064a, 0x00643, 0x00645, 0x00646, 0x00633, 0x00639, 0x00641, 0x00635, 0x00642, 0x00634, 0x0062a, 0x0062b, 0x0062e, 0x00636, 0x00638, 0x0063a, 0x0066e, 0x006a1, 0x00627, 0x00628, 0x0062c, 0x0062f, 0x00647, 0x00648, 0x00632, 0x0062d, 0x00637, 0x0064a, 0x00644, 0x00645, 0x00646, 0x00633, 0x00639, 0x00641, 0x00635, 0x00642, 0x00631, 0x00634, 0x0062a, 0x0062b, 0x0062e, 0x00630, 0x00636, 0x00638, 0x0063a, 0x00628, 0x0062c, 0x0062f, 0x00648, 0x00632, 0x0062d, 0x00637, 0x0064a, 0x00644, 0x00645, 0x00646, 0x00633, 0x00639, 0x00641, 0x00635, 0x00642, 0x00631, 0x00634, 0x0062a, 0x0062b, 0x0062e, 0x00630, 0x00636, 0x00638, 0x0063a, 0x00043, 0x00052, 0x00041, 0x00042, 0x00043, 0x00044, 0x00045, 0x00046, 0x00047, 0x00048, 0x00049, 0x0004a, 0x0004b, 0x0004c, 0x0004d, 0x0004e, 0x0004f, 0x00050, 0x00051, 0x00052, 0x00053, 0x00054, 0x00055, 0x00056, 0x00057, 0x00058, 0x00059, 0x0005a, 0x030b5, 0x0624b, 0x05b57, 0x053cc, 0x030c7, 0x04e8c, 0x0591a, 0x089e3, 0x05929, 0x04ea4, 0x06620, 0x07121, 0x06599, 0x0524d, 0x05f8c, 0x0518d, 0x065b0, 0x0521d, 0x07d42, 0x0751f, 0x08ca9, 0x058f0, 0x05439, 0x06f14, 0x06295, 0x06355, 0x04e00, 0x04e09, 0x0904a, 0x05de6, 0x04e2d, 0x053f3, 0x06307, 0x08d70, 0x06253, 0x07981, 0x07a7a, 0x05408, 0x06e80, 0x06709, 0x06708, 0x07533, 0x05272, 0x055b6, 0x0914d, 0x05f97, 0x053ef, 0x04e3d, 0x04e38, 0x04e41, 0x20122, 0x04f60, 0x04fae, 0x04fbb, 0x05002, 0x0507a, 0x05099, 0x050e7, 0x050cf, 0x0349e, 0x2063a, 0x0514d, 0x05154, 0x05164, 0x05177, 0x2051c, 0x034b9, 0x05167, 0x0518d, 0x2054b, 0x05197, 0x051a4, 0x04ecc, 0x051ac, 0x051b5, 0x291df, 0x051f5, 0x05203, 0x034df, 0x0523b, 0x05246, 0x05272, 0x05277, 0x03515, 0x052c7, 0x052c9, 0x052e4, 0x052fa, 0x05305, 0x05306, 0x05317, 0x05349, 0x05351, 0x0535a, 0x05373, 0x0537d, 0x0537f, 0x0537f, 0x0537f, 0x20a2c, 0x07070, 0x053ca, 0x053df, 0x20b63, 0x053eb, 0x053f1, 0x05406, 0x0549e, 0x05438, 0x05448, 0x05468, 0x054a2, 0x054f6, 0x05510, 0x05553, 0x05563, 0x05584, 0x05584, 0x05599, 0x055ab, 0x055b3, 0x055c2, 0x05716, 0x05606, 0x05717, 0x05651, 0x05674, 0x05207, 0x058ee, 0x057ce, 0x057f4, 0x0580d, 0x0578b, 0x05832, 0x05831, 0x058ac, 0x214e4, 0x058f2, 0x058f7, 0x05906, 0x0591a, 0x05922, 0x05962, 0x216a8, 0x216ea, 0x059ec, 0x05a1b, 0x05a27, 0x059d8, 0x05a66, 0x036ee, 0x036fc, 0x05b08, 0x05b3e, 0x05b3e, 0x219c8, 0x05bc3, 0x05bd8, 0x05be7, 0x05bf3, 0x21b18, 0x05bff, 0x05c06, 0x05f53, 0x05c22, 0x03781, 0x05c60, 0x05c6e, 0x05cc0, 0x05c8d, 0x21de4, 0x05d43, 0x21de6, 0x05d6e, 0x05d6b, 0x05d7c, 0x05de1, 0x05de2, 0x0382f, 0x05dfd, 0x05e28, 0x05e3d, 0x05e69, 0x03862, 0x22183, 0x0387c, 0x05eb0, 0x05eb3, 0x05eb6, 0x05eca, 0x2a392, 0x05efe, 0x22331, 0x22331, 0x08201, 0x05f22, 0x05f22, 0x038c7, 0x232b8, 0x261da, 0x05f62, 0x05f6b, 0x038e3, 0x05f9a, 0x05fcd, 0x05fd7, 0x05ff9, 0x06081, 0x0393a, 0x0391c, 0x06094, 0x226d4, 0x060c7, 0x06148, 0x0614c, 0x0614e, 0x0614c, 0x0617a, 0x0618e, 0x061b2, 0x061a4, 0x061af, 0x061de, 0x061f2, 0x061f6, 0x06210, 0x0621b, 0x0625d, 0x062b1, 0x062d4, 0x06350, 0x22b0c, 0x0633d, 0x062fc, 0x06368, 0x06383, 0x063e4, 0x22bf1, 0x06422, 0x063c5, 0x063a9, 0x03a2e, 0x06469, 0x0647e, 0x0649d, 0x06477, 0x03a6c, 0x0654f, 0x0656c, 0x2300a, 0x065e3, 0x066f8, 0x06649, 0x03b19, 0x06691, 0x03b08, 0x03ae4, 0x05192, 0x05195, 0x06700, 0x0669c, 0x080ad, 0x043d9, 0x06717, 0x0671b, 0x06721, 0x0675e, 0x06753, 0x233c3, 0x03b49, 0x067fa, 0x06785, 0x06852, 0x06885, 0x2346d, 0x0688e, 0x0681f, 0x06914, 0x03b9d, 0x06942, 0x069a3, 0x069ea, 0x06aa8, 0x236a3, 0x06adb, 0x03c18, 0x06b21, 0x238a7, 0x06b54, 0x03c4e, 0x06b72, 0x06b9f, 0x06bba, 0x06bbb, 0x23a8d, 0x21d0b, 0x23afa, 0x06c4e, 0x23cbc, 0x06cbf, 0x06ccd, 0x06c67, 0x06d16, 0x06d3e, 0x06d77, 0x06d41, 0x06d69, 0x06d78, 0x06d85, 0x23d1e, 0x06d34, 0x06e2f, 0x06e6e, 0x03d33, 0x06ecb, 0x06ec7, 0x23ed1, 0x06df9, 0x06f6e, 0x23f5e, 0x23f8e, 0x06fc6, 0x07039, 0x0701e, 0x0701b, 0x03d96, 0x0704a, 0x0707d, 0x07077, 0x070ad, 0x20525, 0x07145, 0x24263, 0x0719c, 0x243ab, 0x07228, 0x07235, 0x07250, 0x24608, 0x07280, 0x07295, 0x24735, 0x24814, 0x0737a, 0x0738b, 0x03eac, 0x073a5, 0x03eb8, 0x03eb8, 0x07447, 0x0745c, 0x07471, 0x07485, 0x074ca, 0x03f1b, 0x07524, 0x24c36, 0x0753e, 0x24c92, 0x07570, 0x2219f, 0x07610, 0x24fa1, 0x24fb8, 0x25044, 0x03ffc, 0x04008, 0x076f4, 0x250f3, 0x250f2, 0x25119, 0x25133, 0x0771e, 0x0771f, 0x0771f, 0x0774a, 0x04039, 0x0778b, 0x04046, 0x04096, 0x2541d, 0x0784e, 0x0788c, 0x078cc, 0x040e3, 0x25626, 0x07956, 0x2569a, 0x256c5, 0x0798f, 0x079eb, 0x0412f, 0x07a40, 0x07a4a, 0x07a4f, 0x2597c, 0x25aa7, 0x25aa7, 0x07aee, 0x04202, 0x25bab, 0x07bc6, 0x07bc9, 0x04227, 0x25c80, 0x07cd2, 0x042a0, 0x07ce8, 0x07ce3, 0x07d00, 0x25f86, 0x07d63, 0x04301, 0x07dc7, 0x07e02, 0x07e45, 0x04334, 0x26228, 0x26247, 0x04359, 0x262d9, 0x07f7a, 0x2633e, 0x07f95, 0x07ffa, 0x08005, 0x264da, 0x26523, 0x08060, 0x265a8, 0x08070, 0x2335f, 0x043d5, 0x080b2, 0x08103, 0x0440b, 0x0813e, 0x05ab5, 0x267a7, 0x267b5, 0x23393, 0x2339c, 0x08201, 0x08204, 0x08f9e, 0x0446b, 0x08291, 0x0828b, 0x0829d, 0x052b3, 0x082b1, 0x082b3, 0x082bd, 0x082e6, 0x26b3c, 0x082e5, 0x0831d, 0x08363, 0x083ad, 0x08323, 0x083bd, 0x083e7, 0x08457, 0x08353, 0x083ca, 0x083cc, 0x083dc, 0x26c36, 0x26d6b, 0x26cd5, 0x0452b, 0x084f1, 0x084f3, 0x08516, 0x273ca, 0x08564, 0x26f2c, 0x0455d, 0x04561, 0x26fb1, 0x270d2, 0x0456b, 0x08650, 0x0865c, 0x08667, 0x08669, 0x086a9, 0x08688, 0x0870e, 0x086e2, 0x08779, 0x08728, 0x0876b, 0x08786, 0x045d7, 0x087e1, 0x08801, 0x045f9, 0x08860, 0x08863, 0x27667, 0x088d7, 0x088de, 0x04635, 0x088fa, 0x034bb, 0x278ae, 0x27966, 0x046be, 0x046c7, 0x08aa0, 0x08aed, 0x08b8a, 0x08c55, 0x27ca8, 0x08cab, 0x08cc1, 0x08d1b, 0x08d77, 0x27f2f, 0x20804, 0x08dcb, 0x08dbc, 0x08df0, 0x208de, 0x08ed4, 0x08f38, 0x285d2, 0x285ed, 0x09094, 0x090f1, 0x09111, 0x2872e, 0x0911b, 0x09238, 0x092d7, 0x092d8, 0x0927c, 0x093f9, 0x09415, 0x28bfa, 0x0958b, 0x04995, 0x095b7, 0x28d77, 0x049e6, 0x096c3, 0x05db2, 0x09723, 0x29145, 0x2921a, 0x04a6e, 0x04a76, 0x097e0, 0x2940a, 0x04ab2, 0x29496, 0x0980b, 0x0980b, 0x09829, 0x295b6, 0x098e2, 0x04b33, 0x09929, 0x099a7, 0x099c2, 0x099fe, 0x04bce, 0x29b30, 0x09b12, 0x09c40, 0x09cfd, 0x04cce, 0x04ced, 0x09d67, 0x2a0ce, 0x04cf8, 0x2a105, 0x2a20e, 0x2a291, 0x09ebb, 0x04d56, 0x09ef9, 0x09efe, 0x09f05, 0x09f0f, 0x09f16, 0x09f3b, 0x2a600 }; static const uint32_t multidecomp_keys[] = { 0x000a8, 0x000af, 0x000b4, 0x000b8, 0x000bc, 0x000bd, 0x000be, 0x000c0, 0x000c1, 0x000c2, 0x000c3, 0x000c4, 0x000c5, 0x000c7, 0x000c8, 0x000c9, 0x000ca, 0x000cb, 0x000cc, 0x000cd, 0x000ce, 0x000cf, 0x000d1, 0x000d2, 0x000d3, 0x000d4, 0x000d5, 0x000d6, 0x000d9, 0x000da, 0x000db, 0x000dc, 0x000dd, 0x00100, 0x00102, 0x00104, 0x00106, 0x00108, 0x0010a, 0x0010c, 0x0010e, 0x00112, 0x00114, 0x00116, 0x00118, 0x0011a, 0x0011c, 0x0011e, 0x00120, 0x00122, 0x00124, 0x00128, 0x0012a, 0x0012c, 0x0012e, 0x00130, 0x00132, 0x00134, 0x00136, 0x00139, 0x0013b, 0x0013d, 0x0013f, 0x00143, 0x00145, 0x00147, 0x00149, 0x0014c, 0x0014e, 0x00150, 0x00154, 0x00156, 0x00158, 0x0015a, 0x0015c, 0x0015e, 0x00160, 0x00162, 0x00164, 0x00168, 0x0016a, 0x0016c, 0x0016e, 0x00170, 0x00172, 0x00174, 0x00176, 0x00178, 0x00179, 0x0017b, 0x0017d, 0x001a0, 0x001af, 0x001cd, 0x001cf, 0x001d1, 0x001d3, 0x001d5, 0x001d7, 0x001d9, 0x001db, 0x001de, 0x001e0, 0x001e2, 0x001e6, 0x001e8, 0x001ea, 0x001ec, 0x001ee, 0x001f0, 0x001f4, 0x001f8, 0x001fa, 0x001fc, 0x001fe, 0x00200, 0x00202, 0x00204, 0x00206, 0x00208, 0x0020a, 0x0020c, 0x0020e, 0x00210, 0x00212, 0x00214, 0x00216, 0x00218, 0x0021a, 0x0021e, 0x00226, 0x00228, 0x0022a, 0x0022c, 0x0022e, 0x00230, 0x00232, 0x002d8, 0x002d9, 0x002da, 0x002db, 0x002dc, 0x002dd, 0x00344, 0x0037a, 0x00384, 0x00385, 0x00386, 0x00388, 0x00389, 0x0038a, 0x0038c, 0x0038e, 0x0038f, 0x00390, 0x003aa, 0x003ab, 0x003b0, 0x003d3, 0x003d4, 0x00400, 0x00401, 0x00403, 0x00407, 0x0040c, 0x0040d, 0x0040e, 0x00419, 0x00476, 0x004c1, 0x004d0, 0x004d2, 0x004d6, 0x004da, 0x004dc, 0x004de, 0x004e2, 0x004e4, 0x004e6, 0x004ea, 0x004ec, 0x004ee, 0x004f0, 0x004f2, 0x004f4, 0x004f8, 0x00587, 0x00622, 0x00623, 0x00624, 0x00625, 0x00626, 0x00675, 0x00676, 0x00677, 0x00678, 0x006c0, 0x006c2, 0x006d3, 0x00929, 0x00931, 0x00934, 0x00958, 0x00959, 0x0095a, 0x0095b, 0x0095c, 0x0095d, 0x0095e, 0x0095f, 0x009cb, 0x009cc, 0x009dc, 0x009dd, 0x009df, 0x00a33, 0x00a36, 0x00a59, 0x00a5a, 0x00a5b, 0x00a5e, 0x00b48, 0x00b4b, 0x00b4c, 0x00b5c, 0x00b5d, 0x00b94, 0x00bca, 0x00bcb, 0x00bcc, 0x00c48, 0x00cc0, 0x00cc7, 0x00cc8, 0x00cca, 0x00ccb, 0x00d4a, 0x00d4b, 0x00d4c, 0x00dda, 0x00ddc, 0x00ddd, 0x00dde, 0x00e33, 0x00eb3, 0x00edc, 0x00edd, 0x00f43, 0x00f4d, 0x00f52, 0x00f57, 0x00f5c, 0x00f69, 0x00f73, 0x00f75, 0x00f76, 0x00f77, 0x00f78, 0x00f79, 0x00f81, 0x00f93, 0x00f9d, 0x00fa2, 0x00fa7, 0x00fac, 0x00fb9, 0x01026, 0x01b06, 0x01b08, 0x01b0a, 0x01b0c, 0x01b0e, 0x01b12, 0x01b3b, 0x01b3d, 0x01b40, 0x01b41, 0x01b43, 0x01e00, 0x01e02, 0x01e04, 0x01e06, 0x01e08, 0x01e0a, 0x01e0c, 0x01e0e, 0x01e10, 0x01e12, 0x01e14, 0x01e16, 0x01e18, 0x01e1a, 0x01e1c, 0x01e1e, 0x01e20, 0x01e22, 0x01e24, 0x01e26, 0x01e28, 0x01e2a, 0x01e2c, 0x01e2e, 0x01e30, 0x01e32, 0x01e34, 0x01e36, 0x01e38, 0x01e3a, 0x01e3c, 0x01e3e, 0x01e40, 0x01e42, 0x01e44, 0x01e46, 0x01e48, 0x01e4a, 0x01e4c, 0x01e4e, 0x01e50, 0x01e52, 0x01e54, 0x01e56, 0x01e58, 0x01e5a, 0x01e5c, 0x01e5e, 0x01e60, 0x01e62, 0x01e64, 0x01e66, 0x01e68, 0x01e6a, 0x01e6c, 0x01e6e, 0x01e70, 0x01e72, 0x01e74, 0x01e76, 0x01e78, 0x01e7a, 0x01e7c, 0x01e7e, 0x01e80, 0x01e82, 0x01e84, 0x01e86, 0x01e88, 0x01e8a, 0x01e8c, 0x01e8e, 0x01e90, 0x01e92, 0x01e94, 0x01e96, 0x01e97, 0x01e98, 0x01e99, 0x01e9a, 0x01ea0, 0x01ea2, 0x01ea4, 0x01ea6, 0x01ea8, 0x01eaa, 0x01eac, 0x01eae, 0x01eb0, 0x01eb2, 0x01eb4, 0x01eb6, 0x01eb8, 0x01eba, 0x01ebc, 0x01ebe, 0x01ec0, 0x01ec2, 0x01ec4, 0x01ec6, 0x01ec8, 0x01eca, 0x01ecc, 0x01ece, 0x01ed0, 0x01ed2, 0x01ed4, 0x01ed6, 0x01ed8, 0x01eda, 0x01edc, 0x01ede, 0x01ee0, 0x01ee2, 0x01ee4, 0x01ee6, 0x01ee8, 0x01eea, 0x01eec, 0x01eee, 0x01ef0, 0x01ef2, 0x01ef4, 0x01ef6, 0x01ef8, 0x01f08, 0x01f09, 0x01f0a, 0x01f0b, 0x01f0c, 0x01f0d, 0x01f0e, 0x01f0f, 0x01f18, 0x01f19, 0x01f1a, 0x01f1b, 0x01f1c, 0x01f1d, 0x01f28, 0x01f29, 0x01f2a, 0x01f2b, 0x01f2c, 0x01f2d, 0x01f2e, 0x01f2f, 0x01f38, 0x01f39, 0x01f3a, 0x01f3b, 0x01f3c, 0x01f3d, 0x01f3e, 0x01f3f, 0x01f48, 0x01f49, 0x01f4a, 0x01f4b, 0x01f4c, 0x01f4d, 0x01f50, 0x01f52, 0x01f54, 0x01f56, 0x01f59, 0x01f5b, 0x01f5d, 0x01f5f, 0x01f68, 0x01f69, 0x01f6a, 0x01f6b, 0x01f6c, 0x01f6d, 0x01f6e, 0x01f6f, 0x01f88, 0x01f89, 0x01f8a, 0x01f8b, 0x01f8c, 0x01f8d, 0x01f8e, 0x01f8f, 0x01f98, 0x01f99, 0x01f9a, 0x01f9b, 0x01f9c, 0x01f9d, 0x01f9e, 0x01f9f, 0x01fa8, 0x01fa9, 0x01faa, 0x01fab, 0x01fac, 0x01fad, 0x01fae, 0x01faf, 0x01fb2, 0x01fb4, 0x01fb6, 0x01fb7, 0x01fb8, 0x01fb9, 0x01fba, 0x01fbc, 0x01fbd, 0x01fbf, 0x01fc0, 0x01fc1, 0x01fc2, 0x01fc4, 0x01fc6, 0x01fc7, 0x01fc8, 0x01fca, 0x01fcc, 0x01fcd, 0x01fce, 0x01fcf, 0x01fd2, 0x01fd6, 0x01fd7, 0x01fd8, 0x01fd9, 0x01fda, 0x01fdd, 0x01fde, 0x01fdf, 0x01fe2, 0x01fe4, 0x01fe6, 0x01fe7, 0x01fe8, 0x01fe9, 0x01fea, 0x01fec, 0x01fed, 0x01ff2, 0x01ff4, 0x01ff6, 0x01ff7, 0x01ff8, 0x01ffa, 0x01ffc, 0x01ffe, 0x02017, 0x02025, 0x02026, 0x02033, 0x02034, 0x02036, 0x02037, 0x0203c, 0x0203e, 0x02047, 0x02048, 0x02049, 0x02057, 0x020a8, 0x02100, 0x02101, 0x02103, 0x02105, 0x02106, 0x02109, 0x02116, 0x02120, 0x02121, 0x02122, 0x0213b, 0x02150, 0x02151, 0x02152, 0x02153, 0x02154, 0x02155, 0x02156, 0x02157, 0x02158, 0x02159, 0x0215a, 0x0215b, 0x0215c, 0x0215d, 0x0215e, 0x0215f, 0x02161, 0x02162, 0x02163, 0x02165, 0x02166, 0x02167, 0x02168, 0x0216a, 0x0216b, 0x02189, 0x0219a, 0x0219b, 0x021ae, 0x021cd, 0x021ce, 0x021cf, 0x02204, 0x02209, 0x0220c, 0x02224, 0x02226, 0x0222c, 0x0222d, 0x0222f, 0x02230, 0x02241, 0x02244, 0x02247, 0x02249, 0x02260, 0x02262, 0x0226d, 0x0226e, 0x0226f, 0x02270, 0x02271, 0x02274, 0x02275, 0x02278, 0x02279, 0x02280, 0x02281, 0x02284, 0x02285, 0x02288, 0x02289, 0x022ac, 0x022ad, 0x022ae, 0x022af, 0x022e0, 0x022e1, 0x022e2, 0x022e3, 0x022ea, 0x022eb, 0x022ec, 0x022ed, 0x02469, 0x0246a, 0x0246b, 0x0246c, 0x0246d, 0x0246e, 0x0246f, 0x02470, 0x02471, 0x02472, 0x02473, 0x02474, 0x02475, 0x02476, 0x02477, 0x02478, 0x02479, 0x0247a, 0x0247b, 0x0247c, 0x0247d, 0x0247e, 0x0247f, 0x02480, 0x02481, 0x02482, 0x02483, 0x02484, 0x02485, 0x02486, 0x02487, 0x02488, 0x02489, 0x0248a, 0x0248b, 0x0248c, 0x0248d, 0x0248e, 0x0248f, 0x02490, 0x02491, 0x02492, 0x02493, 0x02494, 0x02495, 0x02496, 0x02497, 0x02498, 0x02499, 0x0249a, 0x0249b, 0x0249c, 0x0249d, 0x0249e, 0x0249f, 0x024a0, 0x024a1, 0x024a2, 0x024a3, 0x024a4, 0x024a5, 0x024a6, 0x024a7, 0x024a8, 0x024a9, 0x024aa, 0x024ab, 0x024ac, 0x024ad, 0x024ae, 0x024af, 0x024b0, 0x024b1, 0x024b2, 0x024b3, 0x024b4, 0x024b5, 0x02a0c, 0x02a74, 0x02a75, 0x02a76, 0x02adc, 0x0304c, 0x0304e, 0x03050, 0x03052, 0x03054, 0x03056, 0x03058, 0x0305a, 0x0305c, 0x0305e, 0x03060, 0x03062, 0x03065, 0x03067, 0x03069, 0x03070, 0x03071, 0x03073, 0x03074, 0x03076, 0x03077, 0x03079, 0x0307a, 0x0307c, 0x0307d, 0x03094, 0x0309b, 0x0309c, 0x0309e, 0x0309f, 0x030ac, 0x030ae, 0x030b0, 0x030b2, 0x030b4, 0x030b6, 0x030b8, 0x030ba, 0x030bc, 0x030be, 0x030c0, 0x030c2, 0x030c5, 0x030c7, 0x030c9, 0x030d0, 0x030d1, 0x030d3, 0x030d4, 0x030d6, 0x030d7, 0x030d9, 0x030da, 0x030dc, 0x030dd, 0x030f4, 0x030f7, 0x030f8, 0x030f9, 0x030fa, 0x030fe, 0x030ff, 0x03200, 0x03201, 0x03202, 0x03203, 0x03204, 0x03205, 0x03206, 0x03207, 0x03208, 0x03209, 0x0320a, 0x0320b, 0x0320c, 0x0320d, 0x0320e, 0x0320f, 0x03210, 0x03211, 0x03212, 0x03213, 0x03214, 0x03215, 0x03216, 0x03217, 0x03218, 0x03219, 0x0321a, 0x0321b, 0x0321c, 0x0321d, 0x0321e, 0x03220, 0x03221, 0x03222, 0x03223, 0x03224, 0x03225, 0x03226, 0x03227, 0x03228, 0x03229, 0x0322a, 0x0322b, 0x0322c, 0x0322d, 0x0322e, 0x0322f, 0x03230, 0x03231, 0x03232, 0x03233, 0x03234, 0x03235, 0x03236, 0x03237, 0x03238, 0x03239, 0x0323a, 0x0323b, 0x0323c, 0x0323d, 0x0323e, 0x0323f, 0x03240, 0x03241, 0x03242, 0x03243, 0x03250, 0x03251, 0x03252, 0x03253, 0x03254, 0x03255, 0x03256, 0x03257, 0x03258, 0x03259, 0x0325a, 0x0325b, 0x0325c, 0x0325d, 0x0325e, 0x0325f, 0x0326e, 0x0326f, 0x03270, 0x03271, 0x03272, 0x03273, 0x03274, 0x03275, 0x03276, 0x03277, 0x03278, 0x03279, 0x0327a, 0x0327b, 0x0327c, 0x0327d, 0x0327e, 0x032b1, 0x032b2, 0x032b3, 0x032b4, 0x032b5, 0x032b6, 0x032b7, 0x032b8, 0x032b9, 0x032ba, 0x032bb, 0x032bc, 0x032bd, 0x032be, 0x032bf, 0x032c0, 0x032c1, 0x032c2, 0x032c3, 0x032c4, 0x032c5, 0x032c6, 0x032c7, 0x032c8, 0x032c9, 0x032ca, 0x032cb, 0x032cc, 0x032cd, 0x032ce, 0x032cf, 0x03300, 0x03301, 0x03302, 0x03303, 0x03304, 0x03305, 0x03306, 0x03307, 0x03308, 0x03309, 0x0330a, 0x0330b, 0x0330c, 0x0330d, 0x0330e, 0x0330f, 0x03310, 0x03311, 0x03312, 0x03313, 0x03314, 0x03315, 0x03316, 0x03317, 0x03318, 0x03319, 0x0331a, 0x0331b, 0x0331c, 0x0331d, 0x0331e, 0x0331f, 0x03320, 0x03321, 0x03322, 0x03323, 0x03324, 0x03325, 0x03326, 0x03327, 0x03328, 0x03329, 0x0332a, 0x0332b, 0x0332c, 0x0332d, 0x0332e, 0x0332f, 0x03330, 0x03331, 0x03332, 0x03333, 0x03334, 0x03335, 0x03336, 0x03337, 0x03338, 0x03339, 0x0333a, 0x0333b, 0x0333c, 0x0333d, 0x0333e, 0x0333f, 0x03340, 0x03341, 0x03342, 0x03343, 0x03344, 0x03345, 0x03346, 0x03347, 0x03348, 0x03349, 0x0334a, 0x0334b, 0x0334c, 0x0334d, 0x0334e, 0x0334f, 0x03350, 0x03351, 0x03352, 0x03353, 0x03354, 0x03355, 0x03356, 0x03357, 0x03358, 0x03359, 0x0335a, 0x0335b, 0x0335c, 0x0335d, 0x0335e, 0x0335f, 0x03360, 0x03361, 0x03362, 0x03363, 0x03364, 0x03365, 0x03366, 0x03367, 0x03368, 0x03369, 0x0336a, 0x0336b, 0x0336c, 0x0336d, 0x0336e, 0x0336f, 0x03370, 0x03371, 0x03372, 0x03373, 0x03374, 0x03375, 0x03376, 0x03377, 0x03378, 0x03379, 0x0337a, 0x0337b, 0x0337c, 0x0337d, 0x0337e, 0x0337f, 0x03380, 0x03381, 0x03382, 0x03383, 0x03384, 0x03385, 0x03386, 0x03387, 0x03388, 0x03389, 0x0338a, 0x0338b, 0x0338c, 0x0338d, 0x0338e, 0x0338f, 0x03390, 0x03391, 0x03392, 0x03393, 0x03394, 0x03395, 0x03396, 0x03397, 0x03398, 0x03399, 0x0339a, 0x0339b, 0x0339c, 0x0339d, 0x0339e, 0x0339f, 0x033a0, 0x033a1, 0x033a2, 0x033a3, 0x033a4, 0x033a5, 0x033a6, 0x033a7, 0x033a8, 0x033a9, 0x033aa, 0x033ab, 0x033ac, 0x033ad, 0x033ae, 0x033af, 0x033b0, 0x033b1, 0x033b2, 0x033b3, 0x033b4, 0x033b5, 0x033b6, 0x033b7, 0x033b8, 0x033b9, 0x033ba, 0x033bb, 0x033bc, 0x033bd, 0x033be, 0x033bf, 0x033c0, 0x033c1, 0x033c2, 0x033c3, 0x033c4, 0x033c5, 0x033c6, 0x033c7, 0x033c8, 0x033c9, 0x033ca, 0x033cb, 0x033cc, 0x033cd, 0x033ce, 0x033cf, 0x033d0, 0x033d1, 0x033d2, 0x033d3, 0x033d4, 0x033d5, 0x033d6, 0x033d7, 0x033d8, 0x033d9, 0x033da, 0x033db, 0x033dc, 0x033dd, 0x033de, 0x033df, 0x033e0, 0x033e1, 0x033e2, 0x033e3, 0x033e4, 0x033e5, 0x033e6, 0x033e7, 0x033e8, 0x033e9, 0x033ea, 0x033eb, 0x033ec, 0x033ed, 0x033ee, 0x033ef, 0x033f0, 0x033f1, 0x033f2, 0x033f3, 0x033f4, 0x033f5, 0x033f6, 0x033f7, 0x033f8, 0x033f9, 0x033fa, 0x033fb, 0x033fc, 0x033fd, 0x033fe, 0x033ff, 0x0fb00, 0x0fb01, 0x0fb02, 0x0fb03, 0x0fb04, 0x0fb05, 0x0fb06, 0x0fb13, 0x0fb14, 0x0fb15, 0x0fb16, 0x0fb17, 0x0fb1d, 0x0fb1f, 0x0fb2a, 0x0fb2b, 0x0fb2c, 0x0fb2d, 0x0fb2e, 0x0fb2f, 0x0fb30, 0x0fb31, 0x0fb32, 0x0fb33, 0x0fb34, 0x0fb35, 0x0fb36, 0x0fb38, 0x0fb39, 0x0fb3a, 0x0fb3b, 0x0fb3c, 0x0fb3e, 0x0fb40, 0x0fb41, 0x0fb43, 0x0fb44, 0x0fb46, 0x0fb47, 0x0fb48, 0x0fb49, 0x0fb4a, 0x0fb4b, 0x0fb4c, 0x0fb4d, 0x0fb4e, 0x0fb4f, 0x0fbea, 0x0fbeb, 0x0fbec, 0x0fbed, 0x0fbee, 0x0fbef, 0x0fbf0, 0x0fbf1, 0x0fbf2, 0x0fbf3, 0x0fbf4, 0x0fbf5, 0x0fbf6, 0x0fbf7, 0x0fbf8, 0x0fbf9, 0x0fbfa, 0x0fbfb, 0x0fc00, 0x0fc01, 0x0fc02, 0x0fc03, 0x0fc04, 0x0fc05, 0x0fc06, 0x0fc07, 0x0fc08, 0x0fc09, 0x0fc0a, 0x0fc0b, 0x0fc0c, 0x0fc0d, 0x0fc0e, 0x0fc0f, 0x0fc10, 0x0fc11, 0x0fc12, 0x0fc13, 0x0fc14, 0x0fc15, 0x0fc16, 0x0fc17, 0x0fc18, 0x0fc19, 0x0fc1a, 0x0fc1b, 0x0fc1c, 0x0fc1d, 0x0fc1e, 0x0fc1f, 0x0fc20, 0x0fc21, 0x0fc22, 0x0fc23, 0x0fc24, 0x0fc25, 0x0fc26, 0x0fc27, 0x0fc28, 0x0fc29, 0x0fc2a, 0x0fc2b, 0x0fc2c, 0x0fc2d, 0x0fc2e, 0x0fc2f, 0x0fc30, 0x0fc31, 0x0fc32, 0x0fc33, 0x0fc34, 0x0fc35, 0x0fc36, 0x0fc37, 0x0fc38, 0x0fc39, 0x0fc3a, 0x0fc3b, 0x0fc3c, 0x0fc3d, 0x0fc3e, 0x0fc3f, 0x0fc40, 0x0fc41, 0x0fc42, 0x0fc43, 0x0fc44, 0x0fc45, 0x0fc46, 0x0fc47, 0x0fc48, 0x0fc49, 0x0fc4a, 0x0fc4b, 0x0fc4c, 0x0fc4d, 0x0fc4e, 0x0fc4f, 0x0fc50, 0x0fc51, 0x0fc52, 0x0fc53, 0x0fc54, 0x0fc55, 0x0fc56, 0x0fc57, 0x0fc58, 0x0fc59, 0x0fc5a, 0x0fc5b, 0x0fc5c, 0x0fc5d, 0x0fc5e, 0x0fc5f, 0x0fc60, 0x0fc61, 0x0fc62, 0x0fc63, 0x0fc64, 0x0fc65, 0x0fc66, 0x0fc67, 0x0fc68, 0x0fc69, 0x0fc6a, 0x0fc6b, 0x0fc6c, 0x0fc6d, 0x0fc6e, 0x0fc6f, 0x0fc70, 0x0fc71, 0x0fc72, 0x0fc73, 0x0fc74, 0x0fc75, 0x0fc76, 0x0fc77, 0x0fc78, 0x0fc79, 0x0fc7a, 0x0fc7b, 0x0fc7c, 0x0fc7d, 0x0fc7e, 0x0fc7f, 0x0fc80, 0x0fc81, 0x0fc82, 0x0fc83, 0x0fc84, 0x0fc85, 0x0fc86, 0x0fc87, 0x0fc88, 0x0fc89, 0x0fc8a, 0x0fc8b, 0x0fc8c, 0x0fc8d, 0x0fc8e, 0x0fc8f, 0x0fc90, 0x0fc91, 0x0fc92, 0x0fc93, 0x0fc94, 0x0fc95, 0x0fc96, 0x0fc97, 0x0fc98, 0x0fc99, 0x0fc9a, 0x0fc9b, 0x0fc9c, 0x0fc9d, 0x0fc9e, 0x0fc9f, 0x0fca0, 0x0fca1, 0x0fca2, 0x0fca3, 0x0fca4, 0x0fca5, 0x0fca6, 0x0fca7, 0x0fca8, 0x0fca9, 0x0fcaa, 0x0fcab, 0x0fcac, 0x0fcad, 0x0fcae, 0x0fcaf, 0x0fcb0, 0x0fcb1, 0x0fcb2, 0x0fcb3, 0x0fcb4, 0x0fcb5, 0x0fcb6, 0x0fcb7, 0x0fcb8, 0x0fcb9, 0x0fcba, 0x0fcbb, 0x0fcbc, 0x0fcbd, 0x0fcbe, 0x0fcbf, 0x0fcc0, 0x0fcc1, 0x0fcc2, 0x0fcc3, 0x0fcc4, 0x0fcc5, 0x0fcc6, 0x0fcc7, 0x0fcc8, 0x0fcc9, 0x0fcca, 0x0fccb, 0x0fccc, 0x0fccd, 0x0fcce, 0x0fccf, 0x0fcd0, 0x0fcd1, 0x0fcd2, 0x0fcd3, 0x0fcd4, 0x0fcd5, 0x0fcd6, 0x0fcd7, 0x0fcd8, 0x0fcd9, 0x0fcda, 0x0fcdb, 0x0fcdc, 0x0fcdd, 0x0fcde, 0x0fcdf, 0x0fce0, 0x0fce1, 0x0fce2, 0x0fce3, 0x0fce4, 0x0fce5, 0x0fce6, 0x0fce7, 0x0fce8, 0x0fce9, 0x0fcea, 0x0fceb, 0x0fcec, 0x0fced, 0x0fcee, 0x0fcef, 0x0fcf0, 0x0fcf1, 0x0fcf2, 0x0fcf3, 0x0fcf4, 0x0fcf5, 0x0fcf6, 0x0fcf7, 0x0fcf8, 0x0fcf9, 0x0fcfa, 0x0fcfb, 0x0fcfc, 0x0fcfd, 0x0fcfe, 0x0fcff, 0x0fd00, 0x0fd01, 0x0fd02, 0x0fd03, 0x0fd04, 0x0fd05, 0x0fd06, 0x0fd07, 0x0fd08, 0x0fd09, 0x0fd0a, 0x0fd0b, 0x0fd0c, 0x0fd0d, 0x0fd0e, 0x0fd0f, 0x0fd10, 0x0fd11, 0x0fd12, 0x0fd13, 0x0fd14, 0x0fd15, 0x0fd16, 0x0fd17, 0x0fd18, 0x0fd19, 0x0fd1a, 0x0fd1b, 0x0fd1c, 0x0fd1d, 0x0fd1e, 0x0fd1f, 0x0fd20, 0x0fd21, 0x0fd22, 0x0fd23, 0x0fd24, 0x0fd25, 0x0fd26, 0x0fd27, 0x0fd28, 0x0fd29, 0x0fd2a, 0x0fd2b, 0x0fd2c, 0x0fd2d, 0x0fd2e, 0x0fd2f, 0x0fd30, 0x0fd31, 0x0fd32, 0x0fd33, 0x0fd34, 0x0fd35, 0x0fd36, 0x0fd37, 0x0fd38, 0x0fd39, 0x0fd3a, 0x0fd3b, 0x0fd3c, 0x0fd3d, 0x0fd50, 0x0fd51, 0x0fd52, 0x0fd53, 0x0fd54, 0x0fd55, 0x0fd56, 0x0fd57, 0x0fd58, 0x0fd59, 0x0fd5a, 0x0fd5b, 0x0fd5c, 0x0fd5d, 0x0fd5e, 0x0fd5f, 0x0fd60, 0x0fd61, 0x0fd62, 0x0fd63, 0x0fd64, 0x0fd65, 0x0fd66, 0x0fd67, 0x0fd68, 0x0fd69, 0x0fd6a, 0x0fd6b, 0x0fd6c, 0x0fd6d, 0x0fd6e, 0x0fd6f, 0x0fd70, 0x0fd71, 0x0fd72, 0x0fd73, 0x0fd74, 0x0fd75, 0x0fd76, 0x0fd77, 0x0fd78, 0x0fd79, 0x0fd7a, 0x0fd7b, 0x0fd7c, 0x0fd7d, 0x0fd7e, 0x0fd7f, 0x0fd80, 0x0fd81, 0x0fd82, 0x0fd83, 0x0fd84, 0x0fd85, 0x0fd86, 0x0fd87, 0x0fd88, 0x0fd89, 0x0fd8a, 0x0fd8b, 0x0fd8c, 0x0fd8d, 0x0fd8e, 0x0fd8f, 0x0fd92, 0x0fd93, 0x0fd94, 0x0fd95, 0x0fd96, 0x0fd97, 0x0fd98, 0x0fd99, 0x0fd9a, 0x0fd9b, 0x0fd9c, 0x0fd9d, 0x0fd9e, 0x0fd9f, 0x0fda0, 0x0fda1, 0x0fda2, 0x0fda3, 0x0fda4, 0x0fda5, 0x0fda6, 0x0fda7, 0x0fda8, 0x0fda9, 0x0fdaa, 0x0fdab, 0x0fdac, 0x0fdad, 0x0fdae, 0x0fdaf, 0x0fdb0, 0x0fdb1, 0x0fdb2, 0x0fdb3, 0x0fdb4, 0x0fdb5, 0x0fdb6, 0x0fdb7, 0x0fdb8, 0x0fdb9, 0x0fdba, 0x0fdbb, 0x0fdbc, 0x0fdbd, 0x0fdbe, 0x0fdbf, 0x0fdc0, 0x0fdc1, 0x0fdc2, 0x0fdc3, 0x0fdc4, 0x0fdc5, 0x0fdc6, 0x0fdc7, 0x0fdf0, 0x0fdf1, 0x0fdf2, 0x0fdf3, 0x0fdf4, 0x0fdf5, 0x0fdf6, 0x0fdf7, 0x0fdf8, 0x0fdf9, 0x0fdfa, 0x0fdfb, 0x0fdfc, 0x0fe70, 0x0fe71, 0x0fe72, 0x0fe74, 0x0fe76, 0x0fe77, 0x0fe78, 0x0fe79, 0x0fe7a, 0x0fe7b, 0x0fe7c, 0x0fe7d, 0x0fe7e, 0x0fe7f, 0x0fef5, 0x0fef6, 0x0fef7, 0x0fef8, 0x0fef9, 0x0fefa, 0x0fefb, 0x0fefc, 0x1109a, 0x1109c, 0x110ab, 0x1112e, 0x1112f, 0x1134b, 0x1134c, 0x114bb, 0x114bc, 0x114be, 0x115ba, 0x115bb, 0x1d15e, 0x1d15f, 0x1d160, 0x1d161, 0x1d162, 0x1d163, 0x1d164, 0x1d1bb, 0x1d1bc, 0x1d1bd, 0x1d1be, 0x1d1bf, 0x1d1c0, 0x1f100, 0x1f101, 0x1f102, 0x1f103, 0x1f104, 0x1f105, 0x1f106, 0x1f107, 0x1f108, 0x1f109, 0x1f10a, 0x1f110, 0x1f111, 0x1f112, 0x1f113, 0x1f114, 0x1f115, 0x1f116, 0x1f117, 0x1f118, 0x1f119, 0x1f11a, 0x1f11b, 0x1f11c, 0x1f11d, 0x1f11e, 0x1f11f, 0x1f120, 0x1f121, 0x1f122, 0x1f123, 0x1f124, 0x1f125, 0x1f126, 0x1f127, 0x1f128, 0x1f129, 0x1f12a, 0x1f12d, 0x1f12e, 0x1f14a, 0x1f14b, 0x1f14c, 0x1f14d, 0x1f14e, 0x1f14f, 0x1f16a, 0x1f16b, 0x1f190, 0x1f200, 0x1f201, 0x1f240, 0x1f241, 0x1f242, 0x1f243, 0x1f244, 0x1f245, 0x1f246, 0x1f247, 0x1f248 }; static const uint16_t multidecomp_offsets[] = { 0x00000, 0x00003, 0x00006, 0x00009, 0x0000c, 0x00010, 0x00014, 0x00018, 0x0001b, 0x0001e, 0x00021, 0x00024, 0x00027, 0x0002a, 0x0002d, 0x00030, 0x00033, 0x00036, 0x00039, 0x0003c, 0x0003f, 0x00042, 0x00045, 0x00048, 0x0004b, 0x0004e, 0x00051, 0x00054, 0x00057, 0x0005a, 0x0005d, 0x00060, 0x00063, 0x00066, 0x00069, 0x0006c, 0x0006f, 0x00072, 0x00075, 0x00078, 0x0007b, 0x0007e, 0x00081, 0x00084, 0x00087, 0x0008a, 0x0008d, 0x00090, 0x00093, 0x00096, 0x00099, 0x0009c, 0x0009f, 0x000a2, 0x000a5, 0x000a8, 0x000ab, 0x000ae, 0x000b1, 0x000b4, 0x000b7, 0x000ba, 0x000bd, 0x000c0, 0x000c3, 0x000c6, 0x000c9, 0x000cc, 0x000cf, 0x000d2, 0x000d5, 0x000d8, 0x000db, 0x000de, 0x000e1, 0x000e4, 0x000e7, 0x000ea, 0x000ed, 0x000f0, 0x000f3, 0x000f6, 0x000f9, 0x000fc, 0x000ff, 0x00102, 0x00105, 0x00108, 0x0010b, 0x0010e, 0x00111, 0x00114, 0x00117, 0x0011a, 0x0011d, 0x00120, 0x00123, 0x00126, 0x00129, 0x0012c, 0x0012f, 0x00132, 0x00135, 0x00138, 0x0013b, 0x0013e, 0x00141, 0x00144, 0x00147, 0x0014a, 0x0014d, 0x00150, 0x00153, 0x00156, 0x00159, 0x0015c, 0x0015f, 0x00162, 0x00165, 0x00168, 0x0016b, 0x0016e, 0x00171, 0x00174, 0x00177, 0x0017a, 0x0017d, 0x00180, 0x00183, 0x00186, 0x00189, 0x0018c, 0x0018f, 0x00192, 0x00195, 0x00198, 0x0019b, 0x0019e, 0x001a1, 0x001a4, 0x001a7, 0x001aa, 0x001ad, 0x001b0, 0x001b3, 0x001b6, 0x001b9, 0x001bc, 0x001bf, 0x001c2, 0x001c5, 0x001c8, 0x001cb, 0x001ce, 0x001d1, 0x001d4, 0x001d7, 0x001da, 0x001dd, 0x001e0, 0x001e3, 0x001e6, 0x001e9, 0x001ec, 0x001ef, 0x001f2, 0x001f5, 0x001f8, 0x001fb, 0x001fe, 0x00201, 0x00204, 0x00207, 0x0020a, 0x0020d, 0x00210, 0x00213, 0x00216, 0x00219, 0x0021c, 0x0021f, 0x00222, 0x00225, 0x00228, 0x0022b, 0x0022e, 0x00231, 0x00234, 0x00237, 0x0023a, 0x0023d, 0x00240, 0x00243, 0x00246, 0x00249, 0x0024c, 0x0024f, 0x00252, 0x00255, 0x00258, 0x0025b, 0x0025e, 0x00261, 0x00264, 0x00267, 0x0026a, 0x0026d, 0x00270, 0x00273, 0x00276, 0x00279, 0x0027c, 0x0027f, 0x00282, 0x00285, 0x00288, 0x0028b, 0x0028e, 0x00291, 0x00294, 0x00297, 0x0029a, 0x0029d, 0x002a0, 0x002a3, 0x002a6, 0x002a9, 0x002ac, 0x002af, 0x002b2, 0x002b5, 0x002b8, 0x002bb, 0x002be, 0x002c1, 0x002c4, 0x002c7, 0x002ca, 0x002cd, 0x002d0, 0x002d3, 0x002d6, 0x002d9, 0x002dc, 0x002df, 0x002e2, 0x002e5, 0x002e8, 0x002eb, 0x002ee, 0x002f1, 0x002f4, 0x002f7, 0x002fa, 0x002fd, 0x00300, 0x00303, 0x00306, 0x00309, 0x0030c, 0x0030f, 0x00312, 0x00315, 0x00318, 0x0031b, 0x0031e, 0x00321, 0x00324, 0x00327, 0x0032a, 0x0032d, 0x00330, 0x00333, 0x00336, 0x00339, 0x0033c, 0x0033f, 0x00342, 0x00345, 0x00348, 0x0034b, 0x0034e, 0x00351, 0x00354, 0x00357, 0x0035a, 0x0035d, 0x00360, 0x00363, 0x00366, 0x00369, 0x0036c, 0x0036f, 0x00372, 0x00375, 0x00378, 0x0037b, 0x0037e, 0x00381, 0x00384, 0x00387, 0x0038a, 0x0038d, 0x00390, 0x00393, 0x00396, 0x00399, 0x0039c, 0x0039f, 0x003a2, 0x003a5, 0x003a8, 0x003ab, 0x003ae, 0x003b1, 0x003b4, 0x003b7, 0x003ba, 0x003bd, 0x003c0, 0x003c3, 0x003c6, 0x003c9, 0x003cc, 0x003cf, 0x003d2, 0x003d5, 0x003d8, 0x003db, 0x003de, 0x003e1, 0x003e4, 0x003e7, 0x003ea, 0x003ed, 0x003f0, 0x003f3, 0x003f6, 0x003f9, 0x003fc, 0x003ff, 0x00402, 0x00405, 0x00408, 0x0040b, 0x0040e, 0x00411, 0x00414, 0x00417, 0x0041a, 0x0041d, 0x00420, 0x00423, 0x00426, 0x00429, 0x0042c, 0x0042f, 0x00432, 0x00435, 0x00438, 0x0043b, 0x0043e, 0x00441, 0x00444, 0x00447, 0x0044a, 0x0044d, 0x00450, 0x00453, 0x00456, 0x00459, 0x0045c, 0x0045f, 0x00462, 0x00465, 0x00468, 0x0046b, 0x0046e, 0x00471, 0x00474, 0x00477, 0x0047a, 0x0047d, 0x00480, 0x00483, 0x00486, 0x00489, 0x0048c, 0x0048f, 0x00492, 0x00495, 0x00498, 0x0049b, 0x0049e, 0x004a1, 0x004a4, 0x004a7, 0x004aa, 0x004ad, 0x004b0, 0x004b3, 0x004b6, 0x004b9, 0x004bc, 0x004bf, 0x004c2, 0x004c5, 0x004c8, 0x004cb, 0x004ce, 0x004d1, 0x004d4, 0x004d7, 0x004da, 0x004dd, 0x004e0, 0x004e3, 0x004e6, 0x004e9, 0x004ec, 0x004ef, 0x004f2, 0x004f5, 0x004f8, 0x004fb, 0x004fe, 0x00501, 0x00504, 0x00507, 0x0050a, 0x0050d, 0x00510, 0x00513, 0x00516, 0x00519, 0x0051c, 0x0051f, 0x00522, 0x00525, 0x00528, 0x0052b, 0x0052e, 0x00531, 0x00534, 0x00537, 0x0053a, 0x0053d, 0x00540, 0x00543, 0x00546, 0x00549, 0x0054c, 0x0054f, 0x00552, 0x00555, 0x00558, 0x0055b, 0x0055e, 0x00561, 0x00564, 0x00567, 0x0056a, 0x0056d, 0x00570, 0x00573, 0x00576, 0x00579, 0x0057c, 0x0057f, 0x00582, 0x00585, 0x00588, 0x0058b, 0x0058e, 0x00591, 0x00594, 0x00597, 0x0059a, 0x0059d, 0x005a0, 0x005a3, 0x005a6, 0x005a9, 0x005ac, 0x005af, 0x005b2, 0x005b5, 0x005b8, 0x005bb, 0x005be, 0x005c1, 0x005c4, 0x005c7, 0x005ca, 0x005cd, 0x005d0, 0x005d3, 0x005d6, 0x005d9, 0x005dc, 0x005df, 0x005e2, 0x005e5, 0x005e8, 0x005eb, 0x005ee, 0x005f1, 0x005f4, 0x005f7, 0x005fa, 0x005fd, 0x00600, 0x00603, 0x00606, 0x00609, 0x0060c, 0x0060f, 0x00612, 0x00615, 0x00618, 0x0061b, 0x0061e, 0x00621, 0x00624, 0x00627, 0x0062a, 0x0062d, 0x00630, 0x00633, 0x00636, 0x0063a, 0x0063d, 0x00641, 0x00644, 0x00648, 0x0064b, 0x0064e, 0x00651, 0x00654, 0x00657, 0x0065c, 0x0065f, 0x00663, 0x00667, 0x0066a, 0x0066e, 0x00672, 0x00675, 0x00678, 0x0067b, 0x0067f, 0x00682, 0x00686, 0x0068a, 0x0068e, 0x00693, 0x00697, 0x0069b, 0x0069f, 0x006a3, 0x006a7, 0x006ab, 0x006af, 0x006b3, 0x006b7, 0x006bb, 0x006bf, 0x006c3, 0x006c6, 0x006c9, 0x006cd, 0x006d0, 0x006d3, 0x006d7, 0x006dc, 0x006df, 0x006e2, 0x006e6, 0x006ea, 0x006ed, 0x006f0, 0x006f3, 0x006f6, 0x006f9, 0x006fc, 0x006ff, 0x00702, 0x00705, 0x00708, 0x0070b, 0x0070e, 0x00712, 0x00715, 0x00719, 0x0071c, 0x0071f, 0x00722, 0x00725, 0x00728, 0x0072b, 0x0072e, 0x00731, 0x00734, 0x00737, 0x0073a, 0x0073d, 0x00740, 0x00743, 0x00746, 0x00749, 0x0074c, 0x0074f, 0x00752, 0x00755, 0x00758, 0x0075b, 0x0075e, 0x00761, 0x00764, 0x00767, 0x0076a, 0x0076d, 0x00770, 0x00773, 0x00776, 0x00779, 0x0077c, 0x0077f, 0x00782, 0x00785, 0x00788, 0x0078b, 0x0078e, 0x00791, 0x00794, 0x00797, 0x0079a, 0x0079d, 0x007a1, 0x007a5, 0x007a9, 0x007ad, 0x007b1, 0x007b5, 0x007b9, 0x007bd, 0x007c1, 0x007c6, 0x007cb, 0x007d0, 0x007d5, 0x007da, 0x007df, 0x007e4, 0x007e9, 0x007ee, 0x007f3, 0x007f8, 0x007fb, 0x007fe, 0x00801, 0x00804, 0x00807, 0x0080a, 0x0080d, 0x00810, 0x00813, 0x00817, 0x0081b, 0x0081f, 0x00823, 0x00827, 0x0082b, 0x0082f, 0x00833, 0x00837, 0x0083b, 0x0083f, 0x00843, 0x00847, 0x0084b, 0x0084f, 0x00853, 0x00857, 0x0085b, 0x0085f, 0x00863, 0x00867, 0x0086b, 0x0086f, 0x00873, 0x00877, 0x0087b, 0x0087f, 0x00883, 0x00887, 0x0088b, 0x0088f, 0x00893, 0x00897, 0x0089b, 0x0089f, 0x008a3, 0x008a7, 0x008ac, 0x008b0, 0x008b3, 0x008b7, 0x008ba, 0x008bd, 0x008c0, 0x008c3, 0x008c6, 0x008c9, 0x008cc, 0x008cf, 0x008d2, 0x008d5, 0x008d8, 0x008db, 0x008de, 0x008e1, 0x008e4, 0x008e7, 0x008ea, 0x008ed, 0x008f0, 0x008f3, 0x008f6, 0x008f9, 0x008fc, 0x008ff, 0x00902, 0x00905, 0x00908, 0x0090b, 0x0090e, 0x00911, 0x00914, 0x00917, 0x0091a, 0x0091d, 0x00920, 0x00923, 0x00926, 0x00929, 0x0092c, 0x0092f, 0x00932, 0x00935, 0x00938, 0x0093b, 0x0093e, 0x00941, 0x00944, 0x00947, 0x0094a, 0x0094d, 0x00950, 0x00953, 0x00956, 0x00959, 0x0095c, 0x0095f, 0x00962, 0x00965, 0x00968, 0x0096b, 0x0096e, 0x00971, 0x00974, 0x00978, 0x0097c, 0x00980, 0x00984, 0x00988, 0x0098c, 0x00990, 0x00994, 0x00998, 0x0099c, 0x009a0, 0x009a4, 0x009a8, 0x009ac, 0x009b1, 0x009b6, 0x009bb, 0x009c0, 0x009c5, 0x009ca, 0x009cf, 0x009d4, 0x009d9, 0x009de, 0x009e3, 0x009e8, 0x009ed, 0x009f2, 0x009f7, 0x009ff, 0x00a06, 0x00a0a, 0x00a0e, 0x00a12, 0x00a16, 0x00a1a, 0x00a1e, 0x00a22, 0x00a26, 0x00a2a, 0x00a2e, 0x00a32, 0x00a36, 0x00a3a, 0x00a3e, 0x00a42, 0x00a46, 0x00a4a, 0x00a4e, 0x00a52, 0x00a56, 0x00a5a, 0x00a5e, 0x00a62, 0x00a66, 0x00a6a, 0x00a6e, 0x00a72, 0x00a76, 0x00a7a, 0x00a7e, 0x00a82, 0x00a86, 0x00a8a, 0x00a8e, 0x00a92, 0x00a96, 0x00a9a, 0x00a9d, 0x00aa0, 0x00aa3, 0x00aa6, 0x00aa9, 0x00aac, 0x00aaf, 0x00ab2, 0x00ab5, 0x00ab8, 0x00abb, 0x00abe, 0x00ac1, 0x00ac4, 0x00ac7, 0x00aca, 0x00acd, 0x00ad0, 0x00ad3, 0x00ad6, 0x00ad9, 0x00adc, 0x00adf, 0x00ae2, 0x00ae5, 0x00ae8, 0x00aeb, 0x00aee, 0x00af1, 0x00af7, 0x00afc, 0x00aff, 0x00b02, 0x00b05, 0x00b08, 0x00b0b, 0x00b0e, 0x00b11, 0x00b14, 0x00b17, 0x00b1a, 0x00b1d, 0x00b20, 0x00b23, 0x00b26, 0x00b29, 0x00b2c, 0x00b2f, 0x00b32, 0x00b35, 0x00b38, 0x00b3b, 0x00b3e, 0x00b41, 0x00b44, 0x00b47, 0x00b4b, 0x00b4f, 0x00b53, 0x00b56, 0x00b5a, 0x00b5d, 0x00b61, 0x00b66, 0x00b6b, 0x00b70, 0x00b74, 0x00b79, 0x00b7d, 0x00b81, 0x00b87, 0x00b8c, 0x00b90, 0x00b94, 0x00b98, 0x00b9d, 0x00ba2, 0x00ba6, 0x00baa, 0x00bad, 0x00bb1, 0x00bb6, 0x00bbb, 0x00bbe, 0x00bc4, 0x00bcb, 0x00bd1, 0x00bd5, 0x00bdb, 0x00be1, 0x00be6, 0x00bea, 0x00bee, 0x00bf2, 0x00bf7, 0x00bfd, 0x00c02, 0x00c06, 0x00c0a, 0x00c0e, 0x00c11, 0x00c14, 0x00c17, 0x00c1a, 0x00c1e, 0x00c22, 0x00c28, 0x00c2c, 0x00c31, 0x00c37, 0x00c3b, 0x00c3e, 0x00c41, 0x00c47, 0x00c4c, 0x00c52, 0x00c56, 0x00c5c, 0x00c5f, 0x00c63, 0x00c67, 0x00c6b, 0x00c6f, 0x00c73, 0x00c78, 0x00c7c, 0x00c7f, 0x00c83, 0x00c87, 0x00c8b, 0x00c90, 0x00c94, 0x00c98, 0x00c9c, 0x00ca2, 0x00ca7, 0x00caa, 0x00cb0, 0x00cb3, 0x00cb8, 0x00cbd, 0x00cc1, 0x00cc5, 0x00cc9, 0x00cce, 0x00cd1, 0x00cd5, 0x00cda, 0x00cdd, 0x00ce3, 0x00ce7, 0x00cea, 0x00ced, 0x00cf0, 0x00cf3, 0x00cf6, 0x00cf9, 0x00cfc, 0x00cff, 0x00d02, 0x00d05, 0x00d09, 0x00d0d, 0x00d11, 0x00d15, 0x00d19, 0x00d1d, 0x00d21, 0x00d25, 0x00d29, 0x00d2d, 0x00d31, 0x00d35, 0x00d39, 0x00d3d, 0x00d41, 0x00d45, 0x00d48, 0x00d4b, 0x00d4f, 0x00d52, 0x00d55, 0x00d58, 0x00d5c, 0x00d60, 0x00d63, 0x00d66, 0x00d69, 0x00d6c, 0x00d6f, 0x00d74, 0x00d77, 0x00d7a, 0x00d7d, 0x00d80, 0x00d83, 0x00d86, 0x00d89, 0x00d8c, 0x00d90, 0x00d95, 0x00d98, 0x00d9b, 0x00d9e, 0x00da1, 0x00da4, 0x00da7, 0x00daa, 0x00dae, 0x00db2, 0x00db6, 0x00dba, 0x00dbd, 0x00dc0, 0x00dc3, 0x00dc6, 0x00dc9, 0x00dcc, 0x00dcf, 0x00dd2, 0x00dd5, 0x00dd8, 0x00ddc, 0x00de0, 0x00de3, 0x00de7, 0x00deb, 0x00def, 0x00df2, 0x00df6, 0x00dfa, 0x00dff, 0x00e02, 0x00e06, 0x00e0a, 0x00e0e, 0x00e12, 0x00e18, 0x00e1f, 0x00e22, 0x00e25, 0x00e28, 0x00e2b, 0x00e2e, 0x00e31, 0x00e34, 0x00e37, 0x00e3a, 0x00e3d, 0x00e40, 0x00e43, 0x00e46, 0x00e49, 0x00e4c, 0x00e4f, 0x00e52, 0x00e55, 0x00e5a, 0x00e5d, 0x00e60, 0x00e63, 0x00e68, 0x00e6c, 0x00e6f, 0x00e72, 0x00e75, 0x00e78, 0x00e7b, 0x00e7e, 0x00e81, 0x00e84, 0x00e87, 0x00e8a, 0x00e8e, 0x00e91, 0x00e94, 0x00e98, 0x00e9c, 0x00e9f, 0x00ea4, 0x00ea8, 0x00eab, 0x00eae, 0x00eb1, 0x00eb4, 0x00eb8, 0x00ebc, 0x00ebf, 0x00ec2, 0x00ec5, 0x00ec8, 0x00ecb, 0x00ece, 0x00ed1, 0x00ed4, 0x00ed7, 0x00edb, 0x00edf, 0x00ee3, 0x00ee7, 0x00eeb, 0x00eef, 0x00ef3, 0x00ef7, 0x00efb, 0x00eff, 0x00f03, 0x00f07, 0x00f0b, 0x00f0f, 0x00f13, 0x00f17, 0x00f1b, 0x00f1f, 0x00f23, 0x00f27, 0x00f2b, 0x00f2f, 0x00f33, 0x00f36, 0x00f39, 0x00f3c, 0x00f40, 0x00f44, 0x00f47, 0x00f4a, 0x00f4d, 0x00f50, 0x00f53, 0x00f56, 0x00f59, 0x00f5c, 0x00f5f, 0x00f62, 0x00f65, 0x00f68, 0x00f6b, 0x00f6e, 0x00f71, 0x00f74, 0x00f77, 0x00f7a, 0x00f7d, 0x00f80, 0x00f83, 0x00f86, 0x00f89, 0x00f8c, 0x00f8f, 0x00f92, 0x00f95, 0x00f98, 0x00f9b, 0x00f9e, 0x00fa1, 0x00fa4, 0x00fa7, 0x00faa, 0x00fad, 0x00fb0, 0x00fb3, 0x00fb6, 0x00fb9, 0x00fbc, 0x00fbf, 0x00fc2, 0x00fc5, 0x00fc8, 0x00fcb, 0x00fce, 0x00fd1, 0x00fd4, 0x00fd7, 0x00fda, 0x00fdd, 0x00fe0, 0x00fe3, 0x00fe6, 0x00fe9, 0x00fec, 0x00fef, 0x00ff2, 0x00ff5, 0x00ff8, 0x00ffb, 0x00ffe, 0x01001, 0x01004, 0x01007, 0x0100a, 0x0100d, 0x01010, 0x01013, 0x01016, 0x01019, 0x0101c, 0x0101f, 0x01022, 0x01025, 0x01028, 0x0102b, 0x0102e, 0x01031, 0x01034, 0x01037, 0x0103a, 0x0103d, 0x01040, 0x01043, 0x01046, 0x01049, 0x0104c, 0x0104f, 0x01052, 0x01055, 0x01058, 0x0105b, 0x0105e, 0x01061, 0x01064, 0x01067, 0x0106a, 0x0106d, 0x01070, 0x01073, 0x01076, 0x01079, 0x0107c, 0x0107f, 0x01082, 0x01085, 0x01088, 0x0108b, 0x0108e, 0x01091, 0x01094, 0x01097, 0x0109a, 0x0109d, 0x010a0, 0x010a3, 0x010a6, 0x010a9, 0x010ac, 0x010af, 0x010b2, 0x010b5, 0x010b8, 0x010bb, 0x010be, 0x010c1, 0x010c4, 0x010c7, 0x010ca, 0x010cd, 0x010d0, 0x010d3, 0x010d6, 0x010d9, 0x010dc, 0x010df, 0x010e2, 0x010e5, 0x010e8, 0x010eb, 0x010ee, 0x010f1, 0x010f4, 0x010f7, 0x010fa, 0x010fd, 0x01100, 0x01103, 0x01106, 0x01109, 0x0110c, 0x0110f, 0x01112, 0x01116, 0x0111a, 0x0111e, 0x01122, 0x01126, 0x0112a, 0x0112d, 0x01130, 0x01133, 0x01136, 0x01139, 0x0113c, 0x0113f, 0x01142, 0x01145, 0x01148, 0x0114b, 0x0114e, 0x01151, 0x01154, 0x01157, 0x0115a, 0x0115d, 0x01160, 0x01163, 0x01166, 0x01169, 0x0116c, 0x0116f, 0x01172, 0x01175, 0x01178, 0x0117b, 0x0117e, 0x01181, 0x01184, 0x01187, 0x0118a, 0x0118d, 0x01190, 0x01193, 0x01196, 0x01199, 0x0119c, 0x0119f, 0x011a2, 0x011a5, 0x011a8, 0x011ab, 0x011ae, 0x011b1, 0x011b4, 0x011b7, 0x011ba, 0x011bd, 0x011c0, 0x011c3, 0x011c6, 0x011c9, 0x011cc, 0x011cf, 0x011d2, 0x011d5, 0x011d8, 0x011db, 0x011de, 0x011e1, 0x011e4, 0x011e7, 0x011ea, 0x011ed, 0x011f0, 0x011f3, 0x011f6, 0x011f9, 0x011fc, 0x011ff, 0x01202, 0x01205, 0x01208, 0x0120b, 0x0120e, 0x01211, 0x01214, 0x01217, 0x0121a, 0x0121d, 0x01220, 0x01223, 0x01226, 0x01229, 0x0122c, 0x0122f, 0x01232, 0x01235, 0x01238, 0x0123b, 0x0123e, 0x01241, 0x01244, 0x01247, 0x0124a, 0x0124d, 0x01250, 0x01253, 0x01256, 0x01259, 0x0125c, 0x0125f, 0x01262, 0x01265, 0x01268, 0x0126b, 0x0126e, 0x01271, 0x01274, 0x01277, 0x0127a, 0x0127d, 0x01280, 0x01283, 0x01286, 0x01289, 0x0128c, 0x0128f, 0x01292, 0x01295, 0x01298, 0x0129b, 0x0129e, 0x012a1, 0x012a4, 0x012a7, 0x012aa, 0x012ad, 0x012b0, 0x012b3, 0x012b6, 0x012b9, 0x012bc, 0x012bf, 0x012c2, 0x012c5, 0x012c8, 0x012cb, 0x012ce, 0x012d1, 0x012d4, 0x012d8, 0x012dc, 0x012e0, 0x012e3, 0x012e6, 0x012e9, 0x012ec, 0x012ef, 0x012f2, 0x012f5, 0x012f8, 0x012fb, 0x012fe, 0x01301, 0x01304, 0x01307, 0x0130a, 0x0130d, 0x01310, 0x01313, 0x01316, 0x01319, 0x0131c, 0x0131f, 0x01322, 0x01325, 0x01328, 0x0132b, 0x0132e, 0x01331, 0x01334, 0x01337, 0x0133a, 0x0133d, 0x01340, 0x01343, 0x01346, 0x01349, 0x0134c, 0x0134f, 0x01352, 0x01355, 0x01358, 0x0135b, 0x0135e, 0x01361, 0x01364, 0x01367, 0x0136a, 0x0136d, 0x01370, 0x01373, 0x01376, 0x01379, 0x0137c, 0x0137f, 0x01382, 0x01385, 0x01388, 0x0138b, 0x0138e, 0x01391, 0x01394, 0x01397, 0x0139a, 0x0139d, 0x013a0, 0x013a3, 0x013a6, 0x013a9, 0x013ac, 0x013af, 0x013b2, 0x013b5, 0x013b8, 0x013bb, 0x013bf, 0x013c3, 0x013c7, 0x013cb, 0x013cf, 0x013d3, 0x013d7, 0x013db, 0x013df, 0x013e3, 0x013e7, 0x013eb, 0x013ef, 0x013f3, 0x013f7, 0x013fb, 0x013ff, 0x01403, 0x01407, 0x0140b, 0x0140f, 0x01413, 0x01417, 0x0141b, 0x0141f, 0x01423, 0x01427, 0x0142b, 0x0142f, 0x01433, 0x01437, 0x0143b, 0x0143f, 0x01443, 0x01447, 0x0144b, 0x0144f, 0x01453, 0x01457, 0x0145b, 0x0145f, 0x01463, 0x01467, 0x0146b, 0x0146f, 0x01473, 0x01477, 0x0147b, 0x0147f, 0x01483, 0x01487, 0x0148b, 0x0148f, 0x01493, 0x01497, 0x0149b, 0x0149f, 0x014a3, 0x014a7, 0x014ab, 0x014af, 0x014b3, 0x014b7, 0x014bb, 0x014bf, 0x014c3, 0x014c7, 0x014cb, 0x014cf, 0x014d3, 0x014d7, 0x014db, 0x014df, 0x014e3, 0x014e7, 0x014eb, 0x014ef, 0x014f3, 0x014f7, 0x014fb, 0x014ff, 0x01503, 0x01507, 0x0150b, 0x0150f, 0x01513, 0x01517, 0x0151b, 0x0151f, 0x01523, 0x01527, 0x0152b, 0x0152f, 0x01533, 0x01537, 0x0153b, 0x0153f, 0x01543, 0x01547, 0x0154b, 0x0154f, 0x01553, 0x01557, 0x0155b, 0x0155f, 0x01563, 0x01567, 0x0156b, 0x0156f, 0x01573, 0x01577, 0x0157b, 0x0157f, 0x01583, 0x01587, 0x0158b, 0x0158f, 0x01593, 0x01597, 0x0159b, 0x015a0, 0x015a5, 0x015aa, 0x015af, 0x015b4, 0x015b9, 0x015be, 0x015c2, 0x015d5, 0x015de, 0x015e3, 0x015e6, 0x015e9, 0x015ec, 0x015ef, 0x015f2, 0x015f5, 0x015f8, 0x015fb, 0x015fe, 0x01601, 0x01604, 0x01607, 0x0160a, 0x0160d, 0x01610, 0x01613, 0x01616, 0x01619, 0x0161c, 0x0161f, 0x01622, 0x01625, 0x01628, 0x0162b, 0x0162e, 0x01631, 0x01634, 0x01637, 0x0163a, 0x0163d, 0x01640, 0x01643, 0x01646, 0x01649, 0x0164c, 0x0164f, 0x01652, 0x01655, 0x01658, 0x0165b, 0x0165e, 0x01661, 0x01664, 0x01667, 0x0166a, 0x0166d, 0x01670, 0x01673, 0x01676, 0x01679, 0x0167c, 0x0167f, 0x01682, 0x01685, 0x01688, 0x0168b, 0x0168e, 0x01691, 0x01695, 0x01699, 0x0169d, 0x016a1, 0x016a5, 0x016a9, 0x016ad, 0x016b1, 0x016b5, 0x016b9, 0x016bd, 0x016c1, 0x016c5, 0x016c9, 0x016cd, 0x016d1, 0x016d5, 0x016d9, 0x016dd, 0x016e1, 0x016e5, 0x016e9, 0x016ed, 0x016f1, 0x016f5, 0x016f9, 0x016fd, 0x01700, 0x01703, 0x01706, 0x01709, 0x0170c, 0x0170f, 0x01713, 0x01716, 0x01719, 0x0171c, 0x0171f, 0x01722, 0x01725, 0x01729, 0x0172d, 0x01731, 0x01735, 0x01739, 0x0173d, 0x01741, 0x01745 }; static const uint32_t multidecomp_values[] = { 0x00020, 0x00308, 0x00000, 0x00020, 0x00304, 0x00000, 0x00020, 0x00301, 0x00000, 0x00020, 0x00327, 0x00000, 0x00031, 0x02044, 0x00034, 0x00000, 0x00031, 0x02044, 0x00032, 0x00000, 0x00033, 0x02044, 0x00034, 0x00000, 0x00041, 0x00300, 0x00000, 0x00041, 0x00301, 0x00000, 0x00041, 0x00302, 0x00000, 0x00041, 0x00303, 0x00000, 0x00041, 0x00308, 0x00000, 0x00041, 0x0030a, 0x00000, 0x00043, 0x00327, 0x00000, 0x00045, 0x00300, 0x00000, 0x00045, 0x00301, 0x00000, 0x00045, 0x00302, 0x00000, 0x00045, 0x00308, 0x00000, 0x00049, 0x00300, 0x00000, 0x00049, 0x00301, 0x00000, 0x00049, 0x00302, 0x00000, 0x00049, 0x00308, 0x00000, 0x0004e, 0x00303, 0x00000, 0x0004f, 0x00300, 0x00000, 0x0004f, 0x00301, 0x00000, 0x0004f, 0x00302, 0x00000, 0x0004f, 0x00303, 0x00000, 0x0004f, 0x00308, 0x00000, 0x00055, 0x00300, 0x00000, 0x00055, 0x00301, 0x00000, 0x00055, 0x00302, 0x00000, 0x00055, 0x00308, 0x00000, 0x00059, 0x00301, 0x00000, 0x00041, 0x00304, 0x00000, 0x00041, 0x00306, 0x00000, 0x00041, 0x00328, 0x00000, 0x00043, 0x00301, 0x00000, 0x00043, 0x00302, 0x00000, 0x00043, 0x00307, 0x00000, 0x00043, 0x0030c, 0x00000, 0x00044, 0x0030c, 0x00000, 0x00045, 0x00304, 0x00000, 0x00045, 0x00306, 0x00000, 0x00045, 0x00307, 0x00000, 0x00045, 0x00328, 0x00000, 0x00045, 0x0030c, 0x00000, 0x00047, 0x00302, 0x00000, 0x00047, 0x00306, 0x00000, 0x00047, 0x00307, 0x00000, 0x00047, 0x00327, 0x00000, 0x00048, 0x00302, 0x00000, 0x00049, 0x00303, 0x00000, 0x00049, 0x00304, 0x00000, 0x00049, 0x00306, 0x00000, 0x00049, 0x00328, 0x00000, 0x00049, 0x00307, 0x00000, 0x00049, 0x0004a, 0x00000, 0x0004a, 0x00302, 0x00000, 0x0004b, 0x00327, 0x00000, 0x0004c, 0x00301, 0x00000, 0x0004c, 0x00327, 0x00000, 0x0004c, 0x0030c, 0x00000, 0x0004c, 0x000b7, 0x00000, 0x0004e, 0x00301, 0x00000, 0x0004e, 0x00327, 0x00000, 0x0004e, 0x0030c, 0x00000, 0x002bc, 0x0006e, 0x00000, 0x0004f, 0x00304, 0x00000, 0x0004f, 0x00306, 0x00000, 0x0004f, 0x0030b, 0x00000, 0x00052, 0x00301, 0x00000, 0x00052, 0x00327, 0x00000, 0x00052, 0x0030c, 0x00000, 0x00053, 0x00301, 0x00000, 0x00053, 0x00302, 0x00000, 0x00053, 0x00327, 0x00000, 0x00053, 0x0030c, 0x00000, 0x00054, 0x00327, 0x00000, 0x00054, 0x0030c, 0x00000, 0x00055, 0x00303, 0x00000, 0x00055, 0x00304, 0x00000, 0x00055, 0x00306, 0x00000, 0x00055, 0x0030a, 0x00000, 0x00055, 0x0030b, 0x00000, 0x00055, 0x00328, 0x00000, 0x00057, 0x00302, 0x00000, 0x00059, 0x00302, 0x00000, 0x00059, 0x00308, 0x00000, 0x0005a, 0x00301, 0x00000, 0x0005a, 0x00307, 0x00000, 0x0005a, 0x0030c, 0x00000, 0x0004f, 0x0031b, 0x00000, 0x00055, 0x0031b, 0x00000, 0x00041, 0x0030c, 0x00000, 0x00049, 0x0030c, 0x00000, 0x0004f, 0x0030c, 0x00000, 0x00055, 0x0030c, 0x00000, 0x000dc, 0x00304, 0x00000, 0x000dc, 0x00301, 0x00000, 0x000dc, 0x0030c, 0x00000, 0x000dc, 0x00300, 0x00000, 0x000c4, 0x00304, 0x00000, 0x00226, 0x00304, 0x00000, 0x000c6, 0x00304, 0x00000, 0x00047, 0x0030c, 0x00000, 0x0004b, 0x0030c, 0x00000, 0x0004f, 0x00328, 0x00000, 0x001ea, 0x00304, 0x00000, 0x001b7, 0x0030c, 0x00000, 0x0006a, 0x0030c, 0x00000, 0x00047, 0x00301, 0x00000, 0x0004e, 0x00300, 0x00000, 0x000c5, 0x00301, 0x00000, 0x000c6, 0x00301, 0x00000, 0x000d8, 0x00301, 0x00000, 0x00041, 0x0030f, 0x00000, 0x00041, 0x00311, 0x00000, 0x00045, 0x0030f, 0x00000, 0x00045, 0x00311, 0x00000, 0x00049, 0x0030f, 0x00000, 0x00049, 0x00311, 0x00000, 0x0004f, 0x0030f, 0x00000, 0x0004f, 0x00311, 0x00000, 0x00052, 0x0030f, 0x00000, 0x00052, 0x00311, 0x00000, 0x00055, 0x0030f, 0x00000, 0x00055, 0x00311, 0x00000, 0x00053, 0x00326, 0x00000, 0x00054, 0x00326, 0x00000, 0x00048, 0x0030c, 0x00000, 0x00041, 0x00307, 0x00000, 0x00045, 0x00327, 0x00000, 0x000d6, 0x00304, 0x00000, 0x000d5, 0x00304, 0x00000, 0x0004f, 0x00307, 0x00000, 0x0022e, 0x00304, 0x00000, 0x00059, 0x00304, 0x00000, 0x00020, 0x00306, 0x00000, 0x00020, 0x00307, 0x00000, 0x00020, 0x0030a, 0x00000, 0x00020, 0x00328, 0x00000, 0x00020, 0x00303, 0x00000, 0x00020, 0x0030b, 0x00000, 0x00308, 0x00301, 0x00000, 0x00020, 0x00345, 0x00000, 0x00020, 0x00301, 0x00000, 0x000a8, 0x00301, 0x00000, 0x00391, 0x00301, 0x00000, 0x00395, 0x00301, 0x00000, 0x00397, 0x00301, 0x00000, 0x00399, 0x00301, 0x00000, 0x0039f, 0x00301, 0x00000, 0x003a5, 0x00301, 0x00000, 0x003a9, 0x00301, 0x00000, 0x003ca, 0x00301, 0x00000, 0x00399, 0x00308, 0x00000, 0x003a5, 0x00308, 0x00000, 0x003cb, 0x00301, 0x00000, 0x003d2, 0x00301, 0x00000, 0x003d2, 0x00308, 0x00000, 0x00415, 0x00300, 0x00000, 0x00415, 0x00308, 0x00000, 0x00413, 0x00301, 0x00000, 0x00406, 0x00308, 0x00000, 0x0041a, 0x00301, 0x00000, 0x00418, 0x00300, 0x00000, 0x00423, 0x00306, 0x00000, 0x00418, 0x00306, 0x00000, 0x00474, 0x0030f, 0x00000, 0x00416, 0x00306, 0x00000, 0x00410, 0x00306, 0x00000, 0x00410, 0x00308, 0x00000, 0x00415, 0x00306, 0x00000, 0x004d8, 0x00308, 0x00000, 0x00416, 0x00308, 0x00000, 0x00417, 0x00308, 0x00000, 0x00418, 0x00304, 0x00000, 0x00418, 0x00308, 0x00000, 0x0041e, 0x00308, 0x00000, 0x004e8, 0x00308, 0x00000, 0x0042d, 0x00308, 0x00000, 0x00423, 0x00304, 0x00000, 0x00423, 0x00308, 0x00000, 0x00423, 0x0030b, 0x00000, 0x00427, 0x00308, 0x00000, 0x0042b, 0x00308, 0x00000, 0x00565, 0x00582, 0x00000, 0x00627, 0x00653, 0x00000, 0x00627, 0x00654, 0x00000, 0x00648, 0x00654, 0x00000, 0x00627, 0x00655, 0x00000, 0x0064a, 0x00654, 0x00000, 0x00627, 0x00674, 0x00000, 0x00648, 0x00674, 0x00000, 0x006c7, 0x00674, 0x00000, 0x0064a, 0x00674, 0x00000, 0x006d5, 0x00654, 0x00000, 0x006c1, 0x00654, 0x00000, 0x006d2, 0x00654, 0x00000, 0x00928, 0x0093c, 0x00000, 0x00930, 0x0093c, 0x00000, 0x00933, 0x0093c, 0x00000, 0x00915, 0x0093c, 0x00000, 0x00916, 0x0093c, 0x00000, 0x00917, 0x0093c, 0x00000, 0x0091c, 0x0093c, 0x00000, 0x00921, 0x0093c, 0x00000, 0x00922, 0x0093c, 0x00000, 0x0092b, 0x0093c, 0x00000, 0x0092f, 0x0093c, 0x00000, 0x009c7, 0x009be, 0x00000, 0x009c7, 0x009d7, 0x00000, 0x009a1, 0x009bc, 0x00000, 0x009a2, 0x009bc, 0x00000, 0x009af, 0x009bc, 0x00000, 0x00a32, 0x00a3c, 0x00000, 0x00a38, 0x00a3c, 0x00000, 0x00a16, 0x00a3c, 0x00000, 0x00a17, 0x00a3c, 0x00000, 0x00a1c, 0x00a3c, 0x00000, 0x00a2b, 0x00a3c, 0x00000, 0x00b47, 0x00b56, 0x00000, 0x00b47, 0x00b3e, 0x00000, 0x00b47, 0x00b57, 0x00000, 0x00b21, 0x00b3c, 0x00000, 0x00b22, 0x00b3c, 0x00000, 0x00b92, 0x00bd7, 0x00000, 0x00bc6, 0x00bbe, 0x00000, 0x00bc7, 0x00bbe, 0x00000, 0x00bc6, 0x00bd7, 0x00000, 0x00c46, 0x00c56, 0x00000, 0x00cbf, 0x00cd5, 0x00000, 0x00cc6, 0x00cd5, 0x00000, 0x00cc6, 0x00cd6, 0x00000, 0x00cc6, 0x00cc2, 0x00000, 0x00cca, 0x00cd5, 0x00000, 0x00d46, 0x00d3e, 0x00000, 0x00d47, 0x00d3e, 0x00000, 0x00d46, 0x00d57, 0x00000, 0x00dd9, 0x00dca, 0x00000, 0x00dd9, 0x00dcf, 0x00000, 0x00ddc, 0x00dca, 0x00000, 0x00dd9, 0x00ddf, 0x00000, 0x00e4d, 0x00e32, 0x00000, 0x00ecd, 0x00eb2, 0x00000, 0x00eab, 0x00e99, 0x00000, 0x00eab, 0x00ea1, 0x00000, 0x00f42, 0x00fb7, 0x00000, 0x00f4c, 0x00fb7, 0x00000, 0x00f51, 0x00fb7, 0x00000, 0x00f56, 0x00fb7, 0x00000, 0x00f5b, 0x00fb7, 0x00000, 0x00f40, 0x00fb5, 0x00000, 0x00f71, 0x00f72, 0x00000, 0x00f71, 0x00f74, 0x00000, 0x00fb2, 0x00f80, 0x00000, 0x00fb2, 0x00f81, 0x00000, 0x00fb3, 0x00f80, 0x00000, 0x00fb3, 0x00f81, 0x00000, 0x00f71, 0x00f80, 0x00000, 0x00f92, 0x00fb7, 0x00000, 0x00f9c, 0x00fb7, 0x00000, 0x00fa1, 0x00fb7, 0x00000, 0x00fa6, 0x00fb7, 0x00000, 0x00fab, 0x00fb7, 0x00000, 0x00f90, 0x00fb5, 0x00000, 0x01025, 0x0102e, 0x00000, 0x01b05, 0x01b35, 0x00000, 0x01b07, 0x01b35, 0x00000, 0x01b09, 0x01b35, 0x00000, 0x01b0b, 0x01b35, 0x00000, 0x01b0d, 0x01b35, 0x00000, 0x01b11, 0x01b35, 0x00000, 0x01b3a, 0x01b35, 0x00000, 0x01b3c, 0x01b35, 0x00000, 0x01b3e, 0x01b35, 0x00000, 0x01b3f, 0x01b35, 0x00000, 0x01b42, 0x01b35, 0x00000, 0x00041, 0x00325, 0x00000, 0x00042, 0x00307, 0x00000, 0x00042, 0x00323, 0x00000, 0x00042, 0x00331, 0x00000, 0x000c7, 0x00301, 0x00000, 0x00044, 0x00307, 0x00000, 0x00044, 0x00323, 0x00000, 0x00044, 0x00331, 0x00000, 0x00044, 0x00327, 0x00000, 0x00044, 0x0032d, 0x00000, 0x00112, 0x00300, 0x00000, 0x00112, 0x00301, 0x00000, 0x00045, 0x0032d, 0x00000, 0x00045, 0x00330, 0x00000, 0x00228, 0x00306, 0x00000, 0x00046, 0x00307, 0x00000, 0x00047, 0x00304, 0x00000, 0x00048, 0x00307, 0x00000, 0x00048, 0x00323, 0x00000, 0x00048, 0x00308, 0x00000, 0x00048, 0x00327, 0x00000, 0x00048, 0x0032e, 0x00000, 0x00049, 0x00330, 0x00000, 0x000cf, 0x00301, 0x00000, 0x0004b, 0x00301, 0x00000, 0x0004b, 0x00323, 0x00000, 0x0004b, 0x00331, 0x00000, 0x0004c, 0x00323, 0x00000, 0x01e36, 0x00304, 0x00000, 0x0004c, 0x00331, 0x00000, 0x0004c, 0x0032d, 0x00000, 0x0004d, 0x00301, 0x00000, 0x0004d, 0x00307, 0x00000, 0x0004d, 0x00323, 0x00000, 0x0004e, 0x00307, 0x00000, 0x0004e, 0x00323, 0x00000, 0x0004e, 0x00331, 0x00000, 0x0004e, 0x0032d, 0x00000, 0x000d5, 0x00301, 0x00000, 0x000d5, 0x00308, 0x00000, 0x0014c, 0x00300, 0x00000, 0x0014c, 0x00301, 0x00000, 0x00050, 0x00301, 0x00000, 0x00050, 0x00307, 0x00000, 0x00052, 0x00307, 0x00000, 0x00052, 0x00323, 0x00000, 0x01e5a, 0x00304, 0x00000, 0x00052, 0x00331, 0x00000, 0x00053, 0x00307, 0x00000, 0x00053, 0x00323, 0x00000, 0x0015a, 0x00307, 0x00000, 0x00160, 0x00307, 0x00000, 0x01e62, 0x00307, 0x00000, 0x00054, 0x00307, 0x00000, 0x00054, 0x00323, 0x00000, 0x00054, 0x00331, 0x00000, 0x00054, 0x0032d, 0x00000, 0x00055, 0x00324, 0x00000, 0x00055, 0x00330, 0x00000, 0x00055, 0x0032d, 0x00000, 0x00168, 0x00301, 0x00000, 0x0016a, 0x00308, 0x00000, 0x00056, 0x00303, 0x00000, 0x00056, 0x00323, 0x00000, 0x00057, 0x00300, 0x00000, 0x00057, 0x00301, 0x00000, 0x00057, 0x00308, 0x00000, 0x00057, 0x00307, 0x00000, 0x00057, 0x00323, 0x00000, 0x00058, 0x00307, 0x00000, 0x00058, 0x00308, 0x00000, 0x00059, 0x00307, 0x00000, 0x0005a, 0x00302, 0x00000, 0x0005a, 0x00323, 0x00000, 0x0005a, 0x00331, 0x00000, 0x00068, 0x00331, 0x00000, 0x00074, 0x00308, 0x00000, 0x00077, 0x0030a, 0x00000, 0x00079, 0x0030a, 0x00000, 0x00061, 0x002be, 0x00000, 0x00041, 0x00323, 0x00000, 0x00041, 0x00309, 0x00000, 0x000c2, 0x00301, 0x00000, 0x000c2, 0x00300, 0x00000, 0x000c2, 0x00309, 0x00000, 0x000c2, 0x00303, 0x00000, 0x01ea0, 0x00302, 0x00000, 0x00102, 0x00301, 0x00000, 0x00102, 0x00300, 0x00000, 0x00102, 0x00309, 0x00000, 0x00102, 0x00303, 0x00000, 0x01ea0, 0x00306, 0x00000, 0x00045, 0x00323, 0x00000, 0x00045, 0x00309, 0x00000, 0x00045, 0x00303, 0x00000, 0x000ca, 0x00301, 0x00000, 0x000ca, 0x00300, 0x00000, 0x000ca, 0x00309, 0x00000, 0x000ca, 0x00303, 0x00000, 0x01eb8, 0x00302, 0x00000, 0x00049, 0x00309, 0x00000, 0x00049, 0x00323, 0x00000, 0x0004f, 0x00323, 0x00000, 0x0004f, 0x00309, 0x00000, 0x000d4, 0x00301, 0x00000, 0x000d4, 0x00300, 0x00000, 0x000d4, 0x00309, 0x00000, 0x000d4, 0x00303, 0x00000, 0x01ecc, 0x00302, 0x00000, 0x001a0, 0x00301, 0x00000, 0x001a0, 0x00300, 0x00000, 0x001a0, 0x00309, 0x00000, 0x001a0, 0x00303, 0x00000, 0x001a0, 0x00323, 0x00000, 0x00055, 0x00323, 0x00000, 0x00055, 0x00309, 0x00000, 0x001af, 0x00301, 0x00000, 0x001af, 0x00300, 0x00000, 0x001af, 0x00309, 0x00000, 0x001af, 0x00303, 0x00000, 0x001af, 0x00323, 0x00000, 0x00059, 0x00300, 0x00000, 0x00059, 0x00323, 0x00000, 0x00059, 0x00309, 0x00000, 0x00059, 0x00303, 0x00000, 0x00391, 0x00313, 0x00000, 0x00391, 0x00314, 0x00000, 0x01f08, 0x00300, 0x00000, 0x01f09, 0x00300, 0x00000, 0x01f08, 0x00301, 0x00000, 0x01f09, 0x00301, 0x00000, 0x01f08, 0x00342, 0x00000, 0x01f09, 0x00342, 0x00000, 0x00395, 0x00313, 0x00000, 0x00395, 0x00314, 0x00000, 0x01f18, 0x00300, 0x00000, 0x01f19, 0x00300, 0x00000, 0x01f18, 0x00301, 0x00000, 0x01f19, 0x00301, 0x00000, 0x00397, 0x00313, 0x00000, 0x00397, 0x00314, 0x00000, 0x01f28, 0x00300, 0x00000, 0x01f29, 0x00300, 0x00000, 0x01f28, 0x00301, 0x00000, 0x01f29, 0x00301, 0x00000, 0x01f28, 0x00342, 0x00000, 0x01f29, 0x00342, 0x00000, 0x00399, 0x00313, 0x00000, 0x00399, 0x00314, 0x00000, 0x01f38, 0x00300, 0x00000, 0x01f39, 0x00300, 0x00000, 0x01f38, 0x00301, 0x00000, 0x01f39, 0x00301, 0x00000, 0x01f38, 0x00342, 0x00000, 0x01f39, 0x00342, 0x00000, 0x0039f, 0x00313, 0x00000, 0x0039f, 0x00314, 0x00000, 0x01f48, 0x00300, 0x00000, 0x01f49, 0x00300, 0x00000, 0x01f48, 0x00301, 0x00000, 0x01f49, 0x00301, 0x00000, 0x003c5, 0x00313, 0x00000, 0x01f50, 0x00300, 0x00000, 0x01f50, 0x00301, 0x00000, 0x01f50, 0x00342, 0x00000, 0x003a5, 0x00314, 0x00000, 0x01f59, 0x00300, 0x00000, 0x01f59, 0x00301, 0x00000, 0x01f59, 0x00342, 0x00000, 0x003a9, 0x00313, 0x00000, 0x003a9, 0x00314, 0x00000, 0x01f68, 0x00300, 0x00000, 0x01f69, 0x00300, 0x00000, 0x01f68, 0x00301, 0x00000, 0x01f69, 0x00301, 0x00000, 0x01f68, 0x00342, 0x00000, 0x01f69, 0x00342, 0x00000, 0x01f08, 0x00345, 0x00000, 0x01f09, 0x00345, 0x00000, 0x01f0a, 0x00345, 0x00000, 0x01f0b, 0x00345, 0x00000, 0x01f0c, 0x00345, 0x00000, 0x01f0d, 0x00345, 0x00000, 0x01f0e, 0x00345, 0x00000, 0x01f0f, 0x00345, 0x00000, 0x01f28, 0x00345, 0x00000, 0x01f29, 0x00345, 0x00000, 0x01f2a, 0x00345, 0x00000, 0x01f2b, 0x00345, 0x00000, 0x01f2c, 0x00345, 0x00000, 0x01f2d, 0x00345, 0x00000, 0x01f2e, 0x00345, 0x00000, 0x01f2f, 0x00345, 0x00000, 0x01f68, 0x00345, 0x00000, 0x01f69, 0x00345, 0x00000, 0x01f6a, 0x00345, 0x00000, 0x01f6b, 0x00345, 0x00000, 0x01f6c, 0x00345, 0x00000, 0x01f6d, 0x00345, 0x00000, 0x01f6e, 0x00345, 0x00000, 0x01f6f, 0x00345, 0x00000, 0x01f70, 0x00345, 0x00000, 0x003ac, 0x00345, 0x00000, 0x003b1, 0x00342, 0x00000, 0x01fb6, 0x00345, 0x00000, 0x00391, 0x00306, 0x00000, 0x00391, 0x00304, 0x00000, 0x00391, 0x00300, 0x00000, 0x00391, 0x00345, 0x00000, 0x00020, 0x00313, 0x00000, 0x00020, 0x00313, 0x00000, 0x00020, 0x00342, 0x00000, 0x000a8, 0x00342, 0x00000, 0x01f74, 0x00345, 0x00000, 0x003ae, 0x00345, 0x00000, 0x003b7, 0x00342, 0x00000, 0x01fc6, 0x00345, 0x00000, 0x00395, 0x00300, 0x00000, 0x00397, 0x00300, 0x00000, 0x00397, 0x00345, 0x00000, 0x01fbf, 0x00300, 0x00000, 0x01fbf, 0x00301, 0x00000, 0x01fbf, 0x00342, 0x00000, 0x003ca, 0x00300, 0x00000, 0x003b9, 0x00342, 0x00000, 0x003ca, 0x00342, 0x00000, 0x00399, 0x00306, 0x00000, 0x00399, 0x00304, 0x00000, 0x00399, 0x00300, 0x00000, 0x01ffe, 0x00300, 0x00000, 0x01ffe, 0x00301, 0x00000, 0x01ffe, 0x00342, 0x00000, 0x003cb, 0x00300, 0x00000, 0x003c1, 0x00313, 0x00000, 0x003c5, 0x00342, 0x00000, 0x003cb, 0x00342, 0x00000, 0x003a5, 0x00306, 0x00000, 0x003a5, 0x00304, 0x00000, 0x003a5, 0x00300, 0x00000, 0x003a1, 0x00314, 0x00000, 0x000a8, 0x00300, 0x00000, 0x01f7c, 0x00345, 0x00000, 0x003ce, 0x00345, 0x00000, 0x003c9, 0x00342, 0x00000, 0x01ff6, 0x00345, 0x00000, 0x0039f, 0x00300, 0x00000, 0x003a9, 0x00300, 0x00000, 0x003a9, 0x00345, 0x00000, 0x00020, 0x00314, 0x00000, 0x00020, 0x00333, 0x00000, 0x0002e, 0x0002e, 0x00000, 0x0002e, 0x0002e, 0x0002e, 0x00000, 0x02032, 0x02032, 0x00000, 0x02032, 0x02032, 0x02032, 0x00000, 0x02035, 0x02035, 0x00000, 0x02035, 0x02035, 0x02035, 0x00000, 0x00021, 0x00021, 0x00000, 0x00020, 0x00305, 0x00000, 0x0003f, 0x0003f, 0x00000, 0x0003f, 0x00021, 0x00000, 0x00021, 0x0003f, 0x00000, 0x02032, 0x02032, 0x02032, 0x02032, 0x00000, 0x00052, 0x00073, 0x00000, 0x00061, 0x0002f, 0x00063, 0x00000, 0x00061, 0x0002f, 0x00073, 0x00000, 0x000b0, 0x00043, 0x00000, 0x00063, 0x0002f, 0x0006f, 0x00000, 0x00063, 0x0002f, 0x00075, 0x00000, 0x000b0, 0x00046, 0x00000, 0x0004e, 0x0006f, 0x00000, 0x00053, 0x0004d, 0x00000, 0x00054, 0x00045, 0x0004c, 0x00000, 0x00054, 0x0004d, 0x00000, 0x00046, 0x00041, 0x00058, 0x00000, 0x00031, 0x02044, 0x00037, 0x00000, 0x00031, 0x02044, 0x00039, 0x00000, 0x00031, 0x02044, 0x00031, 0x00030, 0x00000, 0x00031, 0x02044, 0x00033, 0x00000, 0x00032, 0x02044, 0x00033, 0x00000, 0x00031, 0x02044, 0x00035, 0x00000, 0x00032, 0x02044, 0x00035, 0x00000, 0x00033, 0x02044, 0x00035, 0x00000, 0x00034, 0x02044, 0x00035, 0x00000, 0x00031, 0x02044, 0x00036, 0x00000, 0x00035, 0x02044, 0x00036, 0x00000, 0x00031, 0x02044, 0x00038, 0x00000, 0x00033, 0x02044, 0x00038, 0x00000, 0x00035, 0x02044, 0x00038, 0x00000, 0x00037, 0x02044, 0x00038, 0x00000, 0x00031, 0x02044, 0x00000, 0x00049, 0x00049, 0x00000, 0x00049, 0x00049, 0x00049, 0x00000, 0x00049, 0x00056, 0x00000, 0x00056, 0x00049, 0x00000, 0x00056, 0x00049, 0x00049, 0x00000, 0x00056, 0x00049, 0x00049, 0x00049, 0x00000, 0x00049, 0x00058, 0x00000, 0x00058, 0x00049, 0x00000, 0x00058, 0x00049, 0x00049, 0x00000, 0x00030, 0x02044, 0x00033, 0x00000, 0x02190, 0x00338, 0x00000, 0x02192, 0x00338, 0x00000, 0x02194, 0x00338, 0x00000, 0x021d0, 0x00338, 0x00000, 0x021d4, 0x00338, 0x00000, 0x021d2, 0x00338, 0x00000, 0x02203, 0x00338, 0x00000, 0x02208, 0x00338, 0x00000, 0x0220b, 0x00338, 0x00000, 0x02223, 0x00338, 0x00000, 0x02225, 0x00338, 0x00000, 0x0222b, 0x0222b, 0x00000, 0x0222b, 0x0222b, 0x0222b, 0x00000, 0x0222e, 0x0222e, 0x00000, 0x0222e, 0x0222e, 0x0222e, 0x00000, 0x0223c, 0x00338, 0x00000, 0x02243, 0x00338, 0x00000, 0x02245, 0x00338, 0x00000, 0x02248, 0x00338, 0x00000, 0x0003d, 0x00338, 0x00000, 0x02261, 0x00338, 0x00000, 0x0224d, 0x00338, 0x00000, 0x0003c, 0x00338, 0x00000, 0x0003e, 0x00338, 0x00000, 0x02264, 0x00338, 0x00000, 0x02265, 0x00338, 0x00000, 0x02272, 0x00338, 0x00000, 0x02273, 0x00338, 0x00000, 0x02276, 0x00338, 0x00000, 0x02277, 0x00338, 0x00000, 0x0227a, 0x00338, 0x00000, 0x0227b, 0x00338, 0x00000, 0x02282, 0x00338, 0x00000, 0x02283, 0x00338, 0x00000, 0x02286, 0x00338, 0x00000, 0x02287, 0x00338, 0x00000, 0x022a2, 0x00338, 0x00000, 0x022a8, 0x00338, 0x00000, 0x022a9, 0x00338, 0x00000, 0x022ab, 0x00338, 0x00000, 0x0227c, 0x00338, 0x00000, 0x0227d, 0x00338, 0x00000, 0x02291, 0x00338, 0x00000, 0x02292, 0x00338, 0x00000, 0x022b2, 0x00338, 0x00000, 0x022b3, 0x00338, 0x00000, 0x022b4, 0x00338, 0x00000, 0x022b5, 0x00338, 0x00000, 0x00031, 0x00030, 0x00000, 0x00031, 0x00031, 0x00000, 0x00031, 0x00032, 0x00000, 0x00031, 0x00033, 0x00000, 0x00031, 0x00034, 0x00000, 0x00031, 0x00035, 0x00000, 0x00031, 0x00036, 0x00000, 0x00031, 0x00037, 0x00000, 0x00031, 0x00038, 0x00000, 0x00031, 0x00039, 0x00000, 0x00032, 0x00030, 0x00000, 0x00028, 0x00031, 0x00029, 0x00000, 0x00028, 0x00032, 0x00029, 0x00000, 0x00028, 0x00033, 0x00029, 0x00000, 0x00028, 0x00034, 0x00029, 0x00000, 0x00028, 0x00035, 0x00029, 0x00000, 0x00028, 0x00036, 0x00029, 0x00000, 0x00028, 0x00037, 0x00029, 0x00000, 0x00028, 0x00038, 0x00029, 0x00000, 0x00028, 0x00039, 0x00029, 0x00000, 0x00028, 0x00031, 0x00030, 0x00029, 0x00000, 0x00028, 0x00031, 0x00031, 0x00029, 0x00000, 0x00028, 0x00031, 0x00032, 0x00029, 0x00000, 0x00028, 0x00031, 0x00033, 0x00029, 0x00000, 0x00028, 0x00031, 0x00034, 0x00029, 0x00000, 0x00028, 0x00031, 0x00035, 0x00029, 0x00000, 0x00028, 0x00031, 0x00036, 0x00029, 0x00000, 0x00028, 0x00031, 0x00037, 0x00029, 0x00000, 0x00028, 0x00031, 0x00038, 0x00029, 0x00000, 0x00028, 0x00031, 0x00039, 0x00029, 0x00000, 0x00028, 0x00032, 0x00030, 0x00029, 0x00000, 0x00031, 0x0002e, 0x00000, 0x00032, 0x0002e, 0x00000, 0x00033, 0x0002e, 0x00000, 0x00034, 0x0002e, 0x00000, 0x00035, 0x0002e, 0x00000, 0x00036, 0x0002e, 0x00000, 0x00037, 0x0002e, 0x00000, 0x00038, 0x0002e, 0x00000, 0x00039, 0x0002e, 0x00000, 0x00031, 0x00030, 0x0002e, 0x00000, 0x00031, 0x00031, 0x0002e, 0x00000, 0x00031, 0x00032, 0x0002e, 0x00000, 0x00031, 0x00033, 0x0002e, 0x00000, 0x00031, 0x00034, 0x0002e, 0x00000, 0x00031, 0x00035, 0x0002e, 0x00000, 0x00031, 0x00036, 0x0002e, 0x00000, 0x00031, 0x00037, 0x0002e, 0x00000, 0x00031, 0x00038, 0x0002e, 0x00000, 0x00031, 0x00039, 0x0002e, 0x00000, 0x00032, 0x00030, 0x0002e, 0x00000, 0x00028, 0x00061, 0x00029, 0x00000, 0x00028, 0x00062, 0x00029, 0x00000, 0x00028, 0x00063, 0x00029, 0x00000, 0x00028, 0x00064, 0x00029, 0x00000, 0x00028, 0x00065, 0x00029, 0x00000, 0x00028, 0x00066, 0x00029, 0x00000, 0x00028, 0x00067, 0x00029, 0x00000, 0x00028, 0x00068, 0x00029, 0x00000, 0x00028, 0x00069, 0x00029, 0x00000, 0x00028, 0x0006a, 0x00029, 0x00000, 0x00028, 0x0006b, 0x00029, 0x00000, 0x00028, 0x0006c, 0x00029, 0x00000, 0x00028, 0x0006d, 0x00029, 0x00000, 0x00028, 0x0006e, 0x00029, 0x00000, 0x00028, 0x0006f, 0x00029, 0x00000, 0x00028, 0x00070, 0x00029, 0x00000, 0x00028, 0x00071, 0x00029, 0x00000, 0x00028, 0x00072, 0x00029, 0x00000, 0x00028, 0x00073, 0x00029, 0x00000, 0x00028, 0x00074, 0x00029, 0x00000, 0x00028, 0x00075, 0x00029, 0x00000, 0x00028, 0x00076, 0x00029, 0x00000, 0x00028, 0x00077, 0x00029, 0x00000, 0x00028, 0x00078, 0x00029, 0x00000, 0x00028, 0x00079, 0x00029, 0x00000, 0x00028, 0x0007a, 0x00029, 0x00000, 0x0222b, 0x0222b, 0x0222b, 0x0222b, 0x00000, 0x0003a, 0x0003a, 0x0003d, 0x00000, 0x0003d, 0x0003d, 0x00000, 0x0003d, 0x0003d, 0x0003d, 0x00000, 0x02add, 0x00338, 0x00000, 0x0304b, 0x03099, 0x00000, 0x0304d, 0x03099, 0x00000, 0x0304f, 0x03099, 0x00000, 0x03051, 0x03099, 0x00000, 0x03053, 0x03099, 0x00000, 0x03055, 0x03099, 0x00000, 0x03057, 0x03099, 0x00000, 0x03059, 0x03099, 0x00000, 0x0305b, 0x03099, 0x00000, 0x0305d, 0x03099, 0x00000, 0x0305f, 0x03099, 0x00000, 0x03061, 0x03099, 0x00000, 0x03064, 0x03099, 0x00000, 0x03066, 0x03099, 0x00000, 0x03068, 0x03099, 0x00000, 0x0306f, 0x03099, 0x00000, 0x0306f, 0x0309a, 0x00000, 0x03072, 0x03099, 0x00000, 0x03072, 0x0309a, 0x00000, 0x03075, 0x03099, 0x00000, 0x03075, 0x0309a, 0x00000, 0x03078, 0x03099, 0x00000, 0x03078, 0x0309a, 0x00000, 0x0307b, 0x03099, 0x00000, 0x0307b, 0x0309a, 0x00000, 0x03046, 0x03099, 0x00000, 0x00020, 0x03099, 0x00000, 0x00020, 0x0309a, 0x00000, 0x0309d, 0x03099, 0x00000, 0x03088, 0x0308a, 0x00000, 0x030ab, 0x03099, 0x00000, 0x030ad, 0x03099, 0x00000, 0x030af, 0x03099, 0x00000, 0x030b1, 0x03099, 0x00000, 0x030b3, 0x03099, 0x00000, 0x030b5, 0x03099, 0x00000, 0x030b7, 0x03099, 0x00000, 0x030b9, 0x03099, 0x00000, 0x030bb, 0x03099, 0x00000, 0x030bd, 0x03099, 0x00000, 0x030bf, 0x03099, 0x00000, 0x030c1, 0x03099, 0x00000, 0x030c4, 0x03099, 0x00000, 0x030c6, 0x03099, 0x00000, 0x030c8, 0x03099, 0x00000, 0x030cf, 0x03099, 0x00000, 0x030cf, 0x0309a, 0x00000, 0x030d2, 0x03099, 0x00000, 0x030d2, 0x0309a, 0x00000, 0x030d5, 0x03099, 0x00000, 0x030d5, 0x0309a, 0x00000, 0x030d8, 0x03099, 0x00000, 0x030d8, 0x0309a, 0x00000, 0x030db, 0x03099, 0x00000, 0x030db, 0x0309a, 0x00000, 0x030a6, 0x03099, 0x00000, 0x030ef, 0x03099, 0x00000, 0x030f0, 0x03099, 0x00000, 0x030f1, 0x03099, 0x00000, 0x030f2, 0x03099, 0x00000, 0x030fd, 0x03099, 0x00000, 0x030b3, 0x030c8, 0x00000, 0x00028, 0x01100, 0x00029, 0x00000, 0x00028, 0x01102, 0x00029, 0x00000, 0x00028, 0x01103, 0x00029, 0x00000, 0x00028, 0x01105, 0x00029, 0x00000, 0x00028, 0x01106, 0x00029, 0x00000, 0x00028, 0x01107, 0x00029, 0x00000, 0x00028, 0x01109, 0x00029, 0x00000, 0x00028, 0x0110b, 0x00029, 0x00000, 0x00028, 0x0110c, 0x00029, 0x00000, 0x00028, 0x0110e, 0x00029, 0x00000, 0x00028, 0x0110f, 0x00029, 0x00000, 0x00028, 0x01110, 0x00029, 0x00000, 0x00028, 0x01111, 0x00029, 0x00000, 0x00028, 0x01112, 0x00029, 0x00000, 0x00028, 0x01100, 0x01161, 0x00029, 0x00000, 0x00028, 0x01102, 0x01161, 0x00029, 0x00000, 0x00028, 0x01103, 0x01161, 0x00029, 0x00000, 0x00028, 0x01105, 0x01161, 0x00029, 0x00000, 0x00028, 0x01106, 0x01161, 0x00029, 0x00000, 0x00028, 0x01107, 0x01161, 0x00029, 0x00000, 0x00028, 0x01109, 0x01161, 0x00029, 0x00000, 0x00028, 0x0110b, 0x01161, 0x00029, 0x00000, 0x00028, 0x0110c, 0x01161, 0x00029, 0x00000, 0x00028, 0x0110e, 0x01161, 0x00029, 0x00000, 0x00028, 0x0110f, 0x01161, 0x00029, 0x00000, 0x00028, 0x01110, 0x01161, 0x00029, 0x00000, 0x00028, 0x01111, 0x01161, 0x00029, 0x00000, 0x00028, 0x01112, 0x01161, 0x00029, 0x00000, 0x00028, 0x0110c, 0x0116e, 0x00029, 0x00000, 0x00028, 0x0110b, 0x01169, 0x0110c, 0x01165, 0x011ab, 0x00029, 0x00000, 0x00028, 0x0110b, 0x01169, 0x01112, 0x0116e, 0x00029, 0x00000, 0x00028, 0x04e00, 0x00029, 0x00000, 0x00028, 0x04e8c, 0x00029, 0x00000, 0x00028, 0x04e09, 0x00029, 0x00000, 0x00028, 0x056db, 0x00029, 0x00000, 0x00028, 0x04e94, 0x00029, 0x00000, 0x00028, 0x0516d, 0x00029, 0x00000, 0x00028, 0x04e03, 0x00029, 0x00000, 0x00028, 0x0516b, 0x00029, 0x00000, 0x00028, 0x04e5d, 0x00029, 0x00000, 0x00028, 0x05341, 0x00029, 0x00000, 0x00028, 0x06708, 0x00029, 0x00000, 0x00028, 0x0706b, 0x00029, 0x00000, 0x00028, 0x06c34, 0x00029, 0x00000, 0x00028, 0x06728, 0x00029, 0x00000, 0x00028, 0x091d1, 0x00029, 0x00000, 0x00028, 0x0571f, 0x00029, 0x00000, 0x00028, 0x065e5, 0x00029, 0x00000, 0x00028, 0x0682a, 0x00029, 0x00000, 0x00028, 0x06709, 0x00029, 0x00000, 0x00028, 0x0793e, 0x00029, 0x00000, 0x00028, 0x0540d, 0x00029, 0x00000, 0x00028, 0x07279, 0x00029, 0x00000, 0x00028, 0x08ca1, 0x00029, 0x00000, 0x00028, 0x0795d, 0x00029, 0x00000, 0x00028, 0x052b4, 0x00029, 0x00000, 0x00028, 0x04ee3, 0x00029, 0x00000, 0x00028, 0x0547c, 0x00029, 0x00000, 0x00028, 0x05b66, 0x00029, 0x00000, 0x00028, 0x076e3, 0x00029, 0x00000, 0x00028, 0x04f01, 0x00029, 0x00000, 0x00028, 0x08cc7, 0x00029, 0x00000, 0x00028, 0x05354, 0x00029, 0x00000, 0x00028, 0x0796d, 0x00029, 0x00000, 0x00028, 0x04f11, 0x00029, 0x00000, 0x00028, 0x081ea, 0x00029, 0x00000, 0x00028, 0x081f3, 0x00029, 0x00000, 0x00050, 0x00054, 0x00045, 0x00000, 0x00032, 0x00031, 0x00000, 0x00032, 0x00032, 0x00000, 0x00032, 0x00033, 0x00000, 0x00032, 0x00034, 0x00000, 0x00032, 0x00035, 0x00000, 0x00032, 0x00036, 0x00000, 0x00032, 0x00037, 0x00000, 0x00032, 0x00038, 0x00000, 0x00032, 0x00039, 0x00000, 0x00033, 0x00030, 0x00000, 0x00033, 0x00031, 0x00000, 0x00033, 0x00032, 0x00000, 0x00033, 0x00033, 0x00000, 0x00033, 0x00034, 0x00000, 0x00033, 0x00035, 0x00000, 0x01100, 0x01161, 0x00000, 0x01102, 0x01161, 0x00000, 0x01103, 0x01161, 0x00000, 0x01105, 0x01161, 0x00000, 0x01106, 0x01161, 0x00000, 0x01107, 0x01161, 0x00000, 0x01109, 0x01161, 0x00000, 0x0110b, 0x01161, 0x00000, 0x0110c, 0x01161, 0x00000, 0x0110e, 0x01161, 0x00000, 0x0110f, 0x01161, 0x00000, 0x01110, 0x01161, 0x00000, 0x01111, 0x01161, 0x00000, 0x01112, 0x01161, 0x00000, 0x0110e, 0x01161, 0x011b7, 0x01100, 0x01169, 0x00000, 0x0110c, 0x0116e, 0x0110b, 0x01174, 0x00000, 0x0110b, 0x0116e, 0x00000, 0x00033, 0x00036, 0x00000, 0x00033, 0x00037, 0x00000, 0x00033, 0x00038, 0x00000, 0x00033, 0x00039, 0x00000, 0x00034, 0x00030, 0x00000, 0x00034, 0x00031, 0x00000, 0x00034, 0x00032, 0x00000, 0x00034, 0x00033, 0x00000, 0x00034, 0x00034, 0x00000, 0x00034, 0x00035, 0x00000, 0x00034, 0x00036, 0x00000, 0x00034, 0x00037, 0x00000, 0x00034, 0x00038, 0x00000, 0x00034, 0x00039, 0x00000, 0x00035, 0x00030, 0x00000, 0x00031, 0x06708, 0x00000, 0x00032, 0x06708, 0x00000, 0x00033, 0x06708, 0x00000, 0x00034, 0x06708, 0x00000, 0x00035, 0x06708, 0x00000, 0x00036, 0x06708, 0x00000, 0x00037, 0x06708, 0x00000, 0x00038, 0x06708, 0x00000, 0x00039, 0x06708, 0x00000, 0x00031, 0x00030, 0x06708, 0x00000, 0x00031, 0x00031, 0x06708, 0x00000, 0x00031, 0x00032, 0x06708, 0x00000, 0x00048, 0x00067, 0x00000, 0x00065, 0x00072, 0x00067, 0x00000, 0x00065, 0x00056, 0x00000, 0x0004c, 0x00054, 0x00044, 0x00000, 0x030a2, 0x030d1, 0x030fc, 0x030c8, 0x00000, 0x030a2, 0x030eb, 0x030d5, 0x030a1, 0x00000, 0x030a2, 0x030f3, 0x030da, 0x030a2, 0x00000, 0x030a2, 0x030fc, 0x030eb, 0x00000, 0x030a4, 0x030cb, 0x030f3, 0x030b0, 0x00000, 0x030a4, 0x030f3, 0x030c1, 0x00000, 0x030a6, 0x030a9, 0x030f3, 0x00000, 0x030a8, 0x030b9, 0x030af, 0x030fc, 0x030c9, 0x00000, 0x030a8, 0x030fc, 0x030ab, 0x030fc, 0x00000, 0x030aa, 0x030f3, 0x030b9, 0x00000, 0x030aa, 0x030fc, 0x030e0, 0x00000, 0x030ab, 0x030a4, 0x030ea, 0x00000, 0x030ab, 0x030e9, 0x030c3, 0x030c8, 0x00000, 0x030ab, 0x030ed, 0x030ea, 0x030fc, 0x00000, 0x030ac, 0x030ed, 0x030f3, 0x00000, 0x030ac, 0x030f3, 0x030de, 0x00000, 0x030ae, 0x030ac, 0x00000, 0x030ae, 0x030cb, 0x030fc, 0x00000, 0x030ad, 0x030e5, 0x030ea, 0x030fc, 0x00000, 0x030ae, 0x030eb, 0x030c0, 0x030fc, 0x00000, 0x030ad, 0x030ed, 0x00000, 0x030ad, 0x030ed, 0x030b0, 0x030e9, 0x030e0, 0x00000, 0x030ad, 0x030ed, 0x030e1, 0x030fc, 0x030c8, 0x030eb, 0x00000, 0x030ad, 0x030ed, 0x030ef, 0x030c3, 0x030c8, 0x00000, 0x030b0, 0x030e9, 0x030e0, 0x00000, 0x030b0, 0x030e9, 0x030e0, 0x030c8, 0x030f3, 0x00000, 0x030af, 0x030eb, 0x030bc, 0x030a4, 0x030ed, 0x00000, 0x030af, 0x030ed, 0x030fc, 0x030cd, 0x00000, 0x030b1, 0x030fc, 0x030b9, 0x00000, 0x030b3, 0x030eb, 0x030ca, 0x00000, 0x030b3, 0x030fc, 0x030dd, 0x00000, 0x030b5, 0x030a4, 0x030af, 0x030eb, 0x00000, 0x030b5, 0x030f3, 0x030c1, 0x030fc, 0x030e0, 0x00000, 0x030b7, 0x030ea, 0x030f3, 0x030b0, 0x00000, 0x030bb, 0x030f3, 0x030c1, 0x00000, 0x030bb, 0x030f3, 0x030c8, 0x00000, 0x030c0, 0x030fc, 0x030b9, 0x00000, 0x030c7, 0x030b7, 0x00000, 0x030c9, 0x030eb, 0x00000, 0x030c8, 0x030f3, 0x00000, 0x030ca, 0x030ce, 0x00000, 0x030ce, 0x030c3, 0x030c8, 0x00000, 0x030cf, 0x030a4, 0x030c4, 0x00000, 0x030d1, 0x030fc, 0x030bb, 0x030f3, 0x030c8, 0x00000, 0x030d1, 0x030fc, 0x030c4, 0x00000, 0x030d0, 0x030fc, 0x030ec, 0x030eb, 0x00000, 0x030d4, 0x030a2, 0x030b9, 0x030c8, 0x030eb, 0x00000, 0x030d4, 0x030af, 0x030eb, 0x00000, 0x030d4, 0x030b3, 0x00000, 0x030d3, 0x030eb, 0x00000, 0x030d5, 0x030a1, 0x030e9, 0x030c3, 0x030c9, 0x00000, 0x030d5, 0x030a3, 0x030fc, 0x030c8, 0x00000, 0x030d6, 0x030c3, 0x030b7, 0x030a7, 0x030eb, 0x00000, 0x030d5, 0x030e9, 0x030f3, 0x00000, 0x030d8, 0x030af, 0x030bf, 0x030fc, 0x030eb, 0x00000, 0x030da, 0x030bd, 0x00000, 0x030da, 0x030cb, 0x030d2, 0x00000, 0x030d8, 0x030eb, 0x030c4, 0x00000, 0x030da, 0x030f3, 0x030b9, 0x00000, 0x030da, 0x030fc, 0x030b8, 0x00000, 0x030d9, 0x030fc, 0x030bf, 0x00000, 0x030dd, 0x030a4, 0x030f3, 0x030c8, 0x00000, 0x030dc, 0x030eb, 0x030c8, 0x00000, 0x030db, 0x030f3, 0x00000, 0x030dd, 0x030f3, 0x030c9, 0x00000, 0x030db, 0x030fc, 0x030eb, 0x00000, 0x030db, 0x030fc, 0x030f3, 0x00000, 0x030de, 0x030a4, 0x030af, 0x030ed, 0x00000, 0x030de, 0x030a4, 0x030eb, 0x00000, 0x030de, 0x030c3, 0x030cf, 0x00000, 0x030de, 0x030eb, 0x030af, 0x00000, 0x030de, 0x030f3, 0x030b7, 0x030e7, 0x030f3, 0x00000, 0x030df, 0x030af, 0x030ed, 0x030f3, 0x00000, 0x030df, 0x030ea, 0x00000, 0x030df, 0x030ea, 0x030d0, 0x030fc, 0x030eb, 0x00000, 0x030e1, 0x030ac, 0x00000, 0x030e1, 0x030ac, 0x030c8, 0x030f3, 0x00000, 0x030e1, 0x030fc, 0x030c8, 0x030eb, 0x00000, 0x030e4, 0x030fc, 0x030c9, 0x00000, 0x030e4, 0x030fc, 0x030eb, 0x00000, 0x030e6, 0x030a2, 0x030f3, 0x00000, 0x030ea, 0x030c3, 0x030c8, 0x030eb, 0x00000, 0x030ea, 0x030e9, 0x00000, 0x030eb, 0x030d4, 0x030fc, 0x00000, 0x030eb, 0x030fc, 0x030d6, 0x030eb, 0x00000, 0x030ec, 0x030e0, 0x00000, 0x030ec, 0x030f3, 0x030c8, 0x030b2, 0x030f3, 0x00000, 0x030ef, 0x030c3, 0x030c8, 0x00000, 0x00030, 0x070b9, 0x00000, 0x00031, 0x070b9, 0x00000, 0x00032, 0x070b9, 0x00000, 0x00033, 0x070b9, 0x00000, 0x00034, 0x070b9, 0x00000, 0x00035, 0x070b9, 0x00000, 0x00036, 0x070b9, 0x00000, 0x00037, 0x070b9, 0x00000, 0x00038, 0x070b9, 0x00000, 0x00039, 0x070b9, 0x00000, 0x00031, 0x00030, 0x070b9, 0x00000, 0x00031, 0x00031, 0x070b9, 0x00000, 0x00031, 0x00032, 0x070b9, 0x00000, 0x00031, 0x00033, 0x070b9, 0x00000, 0x00031, 0x00034, 0x070b9, 0x00000, 0x00031, 0x00035, 0x070b9, 0x00000, 0x00031, 0x00036, 0x070b9, 0x00000, 0x00031, 0x00037, 0x070b9, 0x00000, 0x00031, 0x00038, 0x070b9, 0x00000, 0x00031, 0x00039, 0x070b9, 0x00000, 0x00032, 0x00030, 0x070b9, 0x00000, 0x00032, 0x00031, 0x070b9, 0x00000, 0x00032, 0x00032, 0x070b9, 0x00000, 0x00032, 0x00033, 0x070b9, 0x00000, 0x00032, 0x00034, 0x070b9, 0x00000, 0x00068, 0x00050, 0x00061, 0x00000, 0x00064, 0x00061, 0x00000, 0x00041, 0x00055, 0x00000, 0x00062, 0x00061, 0x00072, 0x00000, 0x0006f, 0x00056, 0x00000, 0x00070, 0x00063, 0x00000, 0x00064, 0x0006d, 0x00000, 0x00064, 0x0006d, 0x000b2, 0x00000, 0x00064, 0x0006d, 0x000b3, 0x00000, 0x00049, 0x00055, 0x00000, 0x05e73, 0x06210, 0x00000, 0x0662d, 0x0548c, 0x00000, 0x05927, 0x06b63, 0x00000, 0x0660e, 0x06cbb, 0x00000, 0x0682a, 0x05f0f, 0x04f1a, 0x0793e, 0x00000, 0x00070, 0x00041, 0x00000, 0x0006e, 0x00041, 0x00000, 0x003bc, 0x00041, 0x00000, 0x0006d, 0x00041, 0x00000, 0x0006b, 0x00041, 0x00000, 0x0004b, 0x00042, 0x00000, 0x0004d, 0x00042, 0x00000, 0x00047, 0x00042, 0x00000, 0x00063, 0x00061, 0x0006c, 0x00000, 0x0006b, 0x00063, 0x00061, 0x0006c, 0x00000, 0x00070, 0x00046, 0x00000, 0x0006e, 0x00046, 0x00000, 0x003bc, 0x00046, 0x00000, 0x003bc, 0x00067, 0x00000, 0x0006d, 0x00067, 0x00000, 0x0006b, 0x00067, 0x00000, 0x00048, 0x0007a, 0x00000, 0x0006b, 0x00048, 0x0007a, 0x00000, 0x0004d, 0x00048, 0x0007a, 0x00000, 0x00047, 0x00048, 0x0007a, 0x00000, 0x00054, 0x00048, 0x0007a, 0x00000, 0x003bc, 0x02113, 0x00000, 0x0006d, 0x02113, 0x00000, 0x00064, 0x02113, 0x00000, 0x0006b, 0x02113, 0x00000, 0x00066, 0x0006d, 0x00000, 0x0006e, 0x0006d, 0x00000, 0x003bc, 0x0006d, 0x00000, 0x0006d, 0x0006d, 0x00000, 0x00063, 0x0006d, 0x00000, 0x0006b, 0x0006d, 0x00000, 0x0006d, 0x0006d, 0x000b2, 0x00000, 0x00063, 0x0006d, 0x000b2, 0x00000, 0x0006d, 0x000b2, 0x00000, 0x0006b, 0x0006d, 0x000b2, 0x00000, 0x0006d, 0x0006d, 0x000b3, 0x00000, 0x00063, 0x0006d, 0x000b3, 0x00000, 0x0006d, 0x000b3, 0x00000, 0x0006b, 0x0006d, 0x000b3, 0x00000, 0x0006d, 0x02215, 0x00073, 0x00000, 0x0006d, 0x02215, 0x00073, 0x000b2, 0x00000, 0x00050, 0x00061, 0x00000, 0x0006b, 0x00050, 0x00061, 0x00000, 0x0004d, 0x00050, 0x00061, 0x00000, 0x00047, 0x00050, 0x00061, 0x00000, 0x00072, 0x00061, 0x00064, 0x00000, 0x00072, 0x00061, 0x00064, 0x02215, 0x00073, 0x00000, 0x00072, 0x00061, 0x00064, 0x02215, 0x00073, 0x000b2, 0x00000, 0x00070, 0x00073, 0x00000, 0x0006e, 0x00073, 0x00000, 0x003bc, 0x00073, 0x00000, 0x0006d, 0x00073, 0x00000, 0x00070, 0x00056, 0x00000, 0x0006e, 0x00056, 0x00000, 0x003bc, 0x00056, 0x00000, 0x0006d, 0x00056, 0x00000, 0x0006b, 0x00056, 0x00000, 0x0004d, 0x00056, 0x00000, 0x00070, 0x00057, 0x00000, 0x0006e, 0x00057, 0x00000, 0x003bc, 0x00057, 0x00000, 0x0006d, 0x00057, 0x00000, 0x0006b, 0x00057, 0x00000, 0x0004d, 0x00057, 0x00000, 0x0006b, 0x003a9, 0x00000, 0x0004d, 0x003a9, 0x00000, 0x00061, 0x0002e, 0x0006d, 0x0002e, 0x00000, 0x00042, 0x00071, 0x00000, 0x00063, 0x00063, 0x00000, 0x00063, 0x00064, 0x00000, 0x00043, 0x02215, 0x0006b, 0x00067, 0x00000, 0x00043, 0x0006f, 0x0002e, 0x00000, 0x00064, 0x00042, 0x00000, 0x00047, 0x00079, 0x00000, 0x00068, 0x00061, 0x00000, 0x00048, 0x00050, 0x00000, 0x00069, 0x0006e, 0x00000, 0x0004b, 0x0004b, 0x00000, 0x0004b, 0x0004d, 0x00000, 0x0006b, 0x00074, 0x00000, 0x0006c, 0x0006d, 0x00000, 0x0006c, 0x0006e, 0x00000, 0x0006c, 0x0006f, 0x00067, 0x00000, 0x0006c, 0x00078, 0x00000, 0x0006d, 0x00062, 0x00000, 0x0006d, 0x00069, 0x0006c, 0x00000, 0x0006d, 0x0006f, 0x0006c, 0x00000, 0x00050, 0x00048, 0x00000, 0x00070, 0x0002e, 0x0006d, 0x0002e, 0x00000, 0x00050, 0x00050, 0x0004d, 0x00000, 0x00050, 0x00052, 0x00000, 0x00073, 0x00072, 0x00000, 0x00053, 0x00076, 0x00000, 0x00057, 0x00062, 0x00000, 0x00056, 0x02215, 0x0006d, 0x00000, 0x00041, 0x02215, 0x0006d, 0x00000, 0x00031, 0x065e5, 0x00000, 0x00032, 0x065e5, 0x00000, 0x00033, 0x065e5, 0x00000, 0x00034, 0x065e5, 0x00000, 0x00035, 0x065e5, 0x00000, 0x00036, 0x065e5, 0x00000, 0x00037, 0x065e5, 0x00000, 0x00038, 0x065e5, 0x00000, 0x00039, 0x065e5, 0x00000, 0x00031, 0x00030, 0x065e5, 0x00000, 0x00031, 0x00031, 0x065e5, 0x00000, 0x00031, 0x00032, 0x065e5, 0x00000, 0x00031, 0x00033, 0x065e5, 0x00000, 0x00031, 0x00034, 0x065e5, 0x00000, 0x00031, 0x00035, 0x065e5, 0x00000, 0x00031, 0x00036, 0x065e5, 0x00000, 0x00031, 0x00037, 0x065e5, 0x00000, 0x00031, 0x00038, 0x065e5, 0x00000, 0x00031, 0x00039, 0x065e5, 0x00000, 0x00032, 0x00030, 0x065e5, 0x00000, 0x00032, 0x00031, 0x065e5, 0x00000, 0x00032, 0x00032, 0x065e5, 0x00000, 0x00032, 0x00033, 0x065e5, 0x00000, 0x00032, 0x00034, 0x065e5, 0x00000, 0x00032, 0x00035, 0x065e5, 0x00000, 0x00032, 0x00036, 0x065e5, 0x00000, 0x00032, 0x00037, 0x065e5, 0x00000, 0x00032, 0x00038, 0x065e5, 0x00000, 0x00032, 0x00039, 0x065e5, 0x00000, 0x00033, 0x00030, 0x065e5, 0x00000, 0x00033, 0x00031, 0x065e5, 0x00000, 0x00067, 0x00061, 0x0006c, 0x00000, 0x00066, 0x00066, 0x00000, 0x00066, 0x00069, 0x00000, 0x00066, 0x0006c, 0x00000, 0x00066, 0x00066, 0x00069, 0x00000, 0x00066, 0x00066, 0x0006c, 0x00000, 0x0017f, 0x00074, 0x00000, 0x00073, 0x00074, 0x00000, 0x00574, 0x00576, 0x00000, 0x00574, 0x00565, 0x00000, 0x00574, 0x0056b, 0x00000, 0x0057e, 0x00576, 0x00000, 0x00574, 0x0056d, 0x00000, 0x005d9, 0x005b4, 0x00000, 0x005f2, 0x005b7, 0x00000, 0x005e9, 0x005c1, 0x00000, 0x005e9, 0x005c2, 0x00000, 0x0fb49, 0x005c1, 0x00000, 0x0fb49, 0x005c2, 0x00000, 0x005d0, 0x005b7, 0x00000, 0x005d0, 0x005b8, 0x00000, 0x005d0, 0x005bc, 0x00000, 0x005d1, 0x005bc, 0x00000, 0x005d2, 0x005bc, 0x00000, 0x005d3, 0x005bc, 0x00000, 0x005d4, 0x005bc, 0x00000, 0x005d5, 0x005bc, 0x00000, 0x005d6, 0x005bc, 0x00000, 0x005d8, 0x005bc, 0x00000, 0x005d9, 0x005bc, 0x00000, 0x005da, 0x005bc, 0x00000, 0x005db, 0x005bc, 0x00000, 0x005dc, 0x005bc, 0x00000, 0x005de, 0x005bc, 0x00000, 0x005e0, 0x005bc, 0x00000, 0x005e1, 0x005bc, 0x00000, 0x005e3, 0x005bc, 0x00000, 0x005e4, 0x005bc, 0x00000, 0x005e6, 0x005bc, 0x00000, 0x005e7, 0x005bc, 0x00000, 0x005e8, 0x005bc, 0x00000, 0x005e9, 0x005bc, 0x00000, 0x005ea, 0x005bc, 0x00000, 0x005d5, 0x005b9, 0x00000, 0x005d1, 0x005bf, 0x00000, 0x005db, 0x005bf, 0x00000, 0x005e4, 0x005bf, 0x00000, 0x005d0, 0x005dc, 0x00000, 0x00626, 0x00627, 0x00000, 0x00626, 0x00627, 0x00000, 0x00626, 0x006d5, 0x00000, 0x00626, 0x006d5, 0x00000, 0x00626, 0x00648, 0x00000, 0x00626, 0x00648, 0x00000, 0x00626, 0x006c7, 0x00000, 0x00626, 0x006c7, 0x00000, 0x00626, 0x006c6, 0x00000, 0x00626, 0x006c6, 0x00000, 0x00626, 0x006c8, 0x00000, 0x00626, 0x006c8, 0x00000, 0x00626, 0x006d0, 0x00000, 0x00626, 0x006d0, 0x00000, 0x00626, 0x006d0, 0x00000, 0x00626, 0x00649, 0x00000, 0x00626, 0x00649, 0x00000, 0x00626, 0x00649, 0x00000, 0x00626, 0x0062c, 0x00000, 0x00626, 0x0062d, 0x00000, 0x00626, 0x00645, 0x00000, 0x00626, 0x00649, 0x00000, 0x00626, 0x0064a, 0x00000, 0x00628, 0x0062c, 0x00000, 0x00628, 0x0062d, 0x00000, 0x00628, 0x0062e, 0x00000, 0x00628, 0x00645, 0x00000, 0x00628, 0x00649, 0x00000, 0x00628, 0x0064a, 0x00000, 0x0062a, 0x0062c, 0x00000, 0x0062a, 0x0062d, 0x00000, 0x0062a, 0x0062e, 0x00000, 0x0062a, 0x00645, 0x00000, 0x0062a, 0x00649, 0x00000, 0x0062a, 0x0064a, 0x00000, 0x0062b, 0x0062c, 0x00000, 0x0062b, 0x00645, 0x00000, 0x0062b, 0x00649, 0x00000, 0x0062b, 0x0064a, 0x00000, 0x0062c, 0x0062d, 0x00000, 0x0062c, 0x00645, 0x00000, 0x0062d, 0x0062c, 0x00000, 0x0062d, 0x00645, 0x00000, 0x0062e, 0x0062c, 0x00000, 0x0062e, 0x0062d, 0x00000, 0x0062e, 0x00645, 0x00000, 0x00633, 0x0062c, 0x00000, 0x00633, 0x0062d, 0x00000, 0x00633, 0x0062e, 0x00000, 0x00633, 0x00645, 0x00000, 0x00635, 0x0062d, 0x00000, 0x00635, 0x00645, 0x00000, 0x00636, 0x0062c, 0x00000, 0x00636, 0x0062d, 0x00000, 0x00636, 0x0062e, 0x00000, 0x00636, 0x00645, 0x00000, 0x00637, 0x0062d, 0x00000, 0x00637, 0x00645, 0x00000, 0x00638, 0x00645, 0x00000, 0x00639, 0x0062c, 0x00000, 0x00639, 0x00645, 0x00000, 0x0063a, 0x0062c, 0x00000, 0x0063a, 0x00645, 0x00000, 0x00641, 0x0062c, 0x00000, 0x00641, 0x0062d, 0x00000, 0x00641, 0x0062e, 0x00000, 0x00641, 0x00645, 0x00000, 0x00641, 0x00649, 0x00000, 0x00641, 0x0064a, 0x00000, 0x00642, 0x0062d, 0x00000, 0x00642, 0x00645, 0x00000, 0x00642, 0x00649, 0x00000, 0x00642, 0x0064a, 0x00000, 0x00643, 0x00627, 0x00000, 0x00643, 0x0062c, 0x00000, 0x00643, 0x0062d, 0x00000, 0x00643, 0x0062e, 0x00000, 0x00643, 0x00644, 0x00000, 0x00643, 0x00645, 0x00000, 0x00643, 0x00649, 0x00000, 0x00643, 0x0064a, 0x00000, 0x00644, 0x0062c, 0x00000, 0x00644, 0x0062d, 0x00000, 0x00644, 0x0062e, 0x00000, 0x00644, 0x00645, 0x00000, 0x00644, 0x00649, 0x00000, 0x00644, 0x0064a, 0x00000, 0x00645, 0x0062c, 0x00000, 0x00645, 0x0062d, 0x00000, 0x00645, 0x0062e, 0x00000, 0x00645, 0x00645, 0x00000, 0x00645, 0x00649, 0x00000, 0x00645, 0x0064a, 0x00000, 0x00646, 0x0062c, 0x00000, 0x00646, 0x0062d, 0x00000, 0x00646, 0x0062e, 0x00000, 0x00646, 0x00645, 0x00000, 0x00646, 0x00649, 0x00000, 0x00646, 0x0064a, 0x00000, 0x00647, 0x0062c, 0x00000, 0x00647, 0x00645, 0x00000, 0x00647, 0x00649, 0x00000, 0x00647, 0x0064a, 0x00000, 0x0064a, 0x0062c, 0x00000, 0x0064a, 0x0062d, 0x00000, 0x0064a, 0x0062e, 0x00000, 0x0064a, 0x00645, 0x00000, 0x0064a, 0x00649, 0x00000, 0x0064a, 0x0064a, 0x00000, 0x00630, 0x00670, 0x00000, 0x00631, 0x00670, 0x00000, 0x00649, 0x00670, 0x00000, 0x00020, 0x0064c, 0x00651, 0x00000, 0x00020, 0x0064d, 0x00651, 0x00000, 0x00020, 0x0064e, 0x00651, 0x00000, 0x00020, 0x0064f, 0x00651, 0x00000, 0x00020, 0x00650, 0x00651, 0x00000, 0x00020, 0x00651, 0x00670, 0x00000, 0x00626, 0x00631, 0x00000, 0x00626, 0x00632, 0x00000, 0x00626, 0x00645, 0x00000, 0x00626, 0x00646, 0x00000, 0x00626, 0x00649, 0x00000, 0x00626, 0x0064a, 0x00000, 0x00628, 0x00631, 0x00000, 0x00628, 0x00632, 0x00000, 0x00628, 0x00645, 0x00000, 0x00628, 0x00646, 0x00000, 0x00628, 0x00649, 0x00000, 0x00628, 0x0064a, 0x00000, 0x0062a, 0x00631, 0x00000, 0x0062a, 0x00632, 0x00000, 0x0062a, 0x00645, 0x00000, 0x0062a, 0x00646, 0x00000, 0x0062a, 0x00649, 0x00000, 0x0062a, 0x0064a, 0x00000, 0x0062b, 0x00631, 0x00000, 0x0062b, 0x00632, 0x00000, 0x0062b, 0x00645, 0x00000, 0x0062b, 0x00646, 0x00000, 0x0062b, 0x00649, 0x00000, 0x0062b, 0x0064a, 0x00000, 0x00641, 0x00649, 0x00000, 0x00641, 0x0064a, 0x00000, 0x00642, 0x00649, 0x00000, 0x00642, 0x0064a, 0x00000, 0x00643, 0x00627, 0x00000, 0x00643, 0x00644, 0x00000, 0x00643, 0x00645, 0x00000, 0x00643, 0x00649, 0x00000, 0x00643, 0x0064a, 0x00000, 0x00644, 0x00645, 0x00000, 0x00644, 0x00649, 0x00000, 0x00644, 0x0064a, 0x00000, 0x00645, 0x00627, 0x00000, 0x00645, 0x00645, 0x00000, 0x00646, 0x00631, 0x00000, 0x00646, 0x00632, 0x00000, 0x00646, 0x00645, 0x00000, 0x00646, 0x00646, 0x00000, 0x00646, 0x00649, 0x00000, 0x00646, 0x0064a, 0x00000, 0x00649, 0x00670, 0x00000, 0x0064a, 0x00631, 0x00000, 0x0064a, 0x00632, 0x00000, 0x0064a, 0x00645, 0x00000, 0x0064a, 0x00646, 0x00000, 0x0064a, 0x00649, 0x00000, 0x0064a, 0x0064a, 0x00000, 0x00626, 0x0062c, 0x00000, 0x00626, 0x0062d, 0x00000, 0x00626, 0x0062e, 0x00000, 0x00626, 0x00645, 0x00000, 0x00626, 0x00647, 0x00000, 0x00628, 0x0062c, 0x00000, 0x00628, 0x0062d, 0x00000, 0x00628, 0x0062e, 0x00000, 0x00628, 0x00645, 0x00000, 0x00628, 0x00647, 0x00000, 0x0062a, 0x0062c, 0x00000, 0x0062a, 0x0062d, 0x00000, 0x0062a, 0x0062e, 0x00000, 0x0062a, 0x00645, 0x00000, 0x0062a, 0x00647, 0x00000, 0x0062b, 0x00645, 0x00000, 0x0062c, 0x0062d, 0x00000, 0x0062c, 0x00645, 0x00000, 0x0062d, 0x0062c, 0x00000, 0x0062d, 0x00645, 0x00000, 0x0062e, 0x0062c, 0x00000, 0x0062e, 0x00645, 0x00000, 0x00633, 0x0062c, 0x00000, 0x00633, 0x0062d, 0x00000, 0x00633, 0x0062e, 0x00000, 0x00633, 0x00645, 0x00000, 0x00635, 0x0062d, 0x00000, 0x00635, 0x0062e, 0x00000, 0x00635, 0x00645, 0x00000, 0x00636, 0x0062c, 0x00000, 0x00636, 0x0062d, 0x00000, 0x00636, 0x0062e, 0x00000, 0x00636, 0x00645, 0x00000, 0x00637, 0x0062d, 0x00000, 0x00638, 0x00645, 0x00000, 0x00639, 0x0062c, 0x00000, 0x00639, 0x00645, 0x00000, 0x0063a, 0x0062c, 0x00000, 0x0063a, 0x00645, 0x00000, 0x00641, 0x0062c, 0x00000, 0x00641, 0x0062d, 0x00000, 0x00641, 0x0062e, 0x00000, 0x00641, 0x00645, 0x00000, 0x00642, 0x0062d, 0x00000, 0x00642, 0x00645, 0x00000, 0x00643, 0x0062c, 0x00000, 0x00643, 0x0062d, 0x00000, 0x00643, 0x0062e, 0x00000, 0x00643, 0x00644, 0x00000, 0x00643, 0x00645, 0x00000, 0x00644, 0x0062c, 0x00000, 0x00644, 0x0062d, 0x00000, 0x00644, 0x0062e, 0x00000, 0x00644, 0x00645, 0x00000, 0x00644, 0x00647, 0x00000, 0x00645, 0x0062c, 0x00000, 0x00645, 0x0062d, 0x00000, 0x00645, 0x0062e, 0x00000, 0x00645, 0x00645, 0x00000, 0x00646, 0x0062c, 0x00000, 0x00646, 0x0062d, 0x00000, 0x00646, 0x0062e, 0x00000, 0x00646, 0x00645, 0x00000, 0x00646, 0x00647, 0x00000, 0x00647, 0x0062c, 0x00000, 0x00647, 0x00645, 0x00000, 0x00647, 0x00670, 0x00000, 0x0064a, 0x0062c, 0x00000, 0x0064a, 0x0062d, 0x00000, 0x0064a, 0x0062e, 0x00000, 0x0064a, 0x00645, 0x00000, 0x0064a, 0x00647, 0x00000, 0x00626, 0x00645, 0x00000, 0x00626, 0x00647, 0x00000, 0x00628, 0x00645, 0x00000, 0x00628, 0x00647, 0x00000, 0x0062a, 0x00645, 0x00000, 0x0062a, 0x00647, 0x00000, 0x0062b, 0x00645, 0x00000, 0x0062b, 0x00647, 0x00000, 0x00633, 0x00645, 0x00000, 0x00633, 0x00647, 0x00000, 0x00634, 0x00645, 0x00000, 0x00634, 0x00647, 0x00000, 0x00643, 0x00644, 0x00000, 0x00643, 0x00645, 0x00000, 0x00644, 0x00645, 0x00000, 0x00646, 0x00645, 0x00000, 0x00646, 0x00647, 0x00000, 0x0064a, 0x00645, 0x00000, 0x0064a, 0x00647, 0x00000, 0x00640, 0x0064e, 0x00651, 0x00000, 0x00640, 0x0064f, 0x00651, 0x00000, 0x00640, 0x00650, 0x00651, 0x00000, 0x00637, 0x00649, 0x00000, 0x00637, 0x0064a, 0x00000, 0x00639, 0x00649, 0x00000, 0x00639, 0x0064a, 0x00000, 0x0063a, 0x00649, 0x00000, 0x0063a, 0x0064a, 0x00000, 0x00633, 0x00649, 0x00000, 0x00633, 0x0064a, 0x00000, 0x00634, 0x00649, 0x00000, 0x00634, 0x0064a, 0x00000, 0x0062d, 0x00649, 0x00000, 0x0062d, 0x0064a, 0x00000, 0x0062c, 0x00649, 0x00000, 0x0062c, 0x0064a, 0x00000, 0x0062e, 0x00649, 0x00000, 0x0062e, 0x0064a, 0x00000, 0x00635, 0x00649, 0x00000, 0x00635, 0x0064a, 0x00000, 0x00636, 0x00649, 0x00000, 0x00636, 0x0064a, 0x00000, 0x00634, 0x0062c, 0x00000, 0x00634, 0x0062d, 0x00000, 0x00634, 0x0062e, 0x00000, 0x00634, 0x00645, 0x00000, 0x00634, 0x00631, 0x00000, 0x00633, 0x00631, 0x00000, 0x00635, 0x00631, 0x00000, 0x00636, 0x00631, 0x00000, 0x00637, 0x00649, 0x00000, 0x00637, 0x0064a, 0x00000, 0x00639, 0x00649, 0x00000, 0x00639, 0x0064a, 0x00000, 0x0063a, 0x00649, 0x00000, 0x0063a, 0x0064a, 0x00000, 0x00633, 0x00649, 0x00000, 0x00633, 0x0064a, 0x00000, 0x00634, 0x00649, 0x00000, 0x00634, 0x0064a, 0x00000, 0x0062d, 0x00649, 0x00000, 0x0062d, 0x0064a, 0x00000, 0x0062c, 0x00649, 0x00000, 0x0062c, 0x0064a, 0x00000, 0x0062e, 0x00649, 0x00000, 0x0062e, 0x0064a, 0x00000, 0x00635, 0x00649, 0x00000, 0x00635, 0x0064a, 0x00000, 0x00636, 0x00649, 0x00000, 0x00636, 0x0064a, 0x00000, 0x00634, 0x0062c, 0x00000, 0x00634, 0x0062d, 0x00000, 0x00634, 0x0062e, 0x00000, 0x00634, 0x00645, 0x00000, 0x00634, 0x00631, 0x00000, 0x00633, 0x00631, 0x00000, 0x00635, 0x00631, 0x00000, 0x00636, 0x00631, 0x00000, 0x00634, 0x0062c, 0x00000, 0x00634, 0x0062d, 0x00000, 0x00634, 0x0062e, 0x00000, 0x00634, 0x00645, 0x00000, 0x00633, 0x00647, 0x00000, 0x00634, 0x00647, 0x00000, 0x00637, 0x00645, 0x00000, 0x00633, 0x0062c, 0x00000, 0x00633, 0x0062d, 0x00000, 0x00633, 0x0062e, 0x00000, 0x00634, 0x0062c, 0x00000, 0x00634, 0x0062d, 0x00000, 0x00634, 0x0062e, 0x00000, 0x00637, 0x00645, 0x00000, 0x00638, 0x00645, 0x00000, 0x00627, 0x0064b, 0x00000, 0x00627, 0x0064b, 0x00000, 0x0062a, 0x0062c, 0x00645, 0x00000, 0x0062a, 0x0062d, 0x0062c, 0x00000, 0x0062a, 0x0062d, 0x0062c, 0x00000, 0x0062a, 0x0062d, 0x00645, 0x00000, 0x0062a, 0x0062e, 0x00645, 0x00000, 0x0062a, 0x00645, 0x0062c, 0x00000, 0x0062a, 0x00645, 0x0062d, 0x00000, 0x0062a, 0x00645, 0x0062e, 0x00000, 0x0062c, 0x00645, 0x0062d, 0x00000, 0x0062c, 0x00645, 0x0062d, 0x00000, 0x0062d, 0x00645, 0x0064a, 0x00000, 0x0062d, 0x00645, 0x00649, 0x00000, 0x00633, 0x0062d, 0x0062c, 0x00000, 0x00633, 0x0062c, 0x0062d, 0x00000, 0x00633, 0x0062c, 0x00649, 0x00000, 0x00633, 0x00645, 0x0062d, 0x00000, 0x00633, 0x00645, 0x0062d, 0x00000, 0x00633, 0x00645, 0x0062c, 0x00000, 0x00633, 0x00645, 0x00645, 0x00000, 0x00633, 0x00645, 0x00645, 0x00000, 0x00635, 0x0062d, 0x0062d, 0x00000, 0x00635, 0x0062d, 0x0062d, 0x00000, 0x00635, 0x00645, 0x00645, 0x00000, 0x00634, 0x0062d, 0x00645, 0x00000, 0x00634, 0x0062d, 0x00645, 0x00000, 0x00634, 0x0062c, 0x0064a, 0x00000, 0x00634, 0x00645, 0x0062e, 0x00000, 0x00634, 0x00645, 0x0062e, 0x00000, 0x00634, 0x00645, 0x00645, 0x00000, 0x00634, 0x00645, 0x00645, 0x00000, 0x00636, 0x0062d, 0x00649, 0x00000, 0x00636, 0x0062e, 0x00645, 0x00000, 0x00636, 0x0062e, 0x00645, 0x00000, 0x00637, 0x00645, 0x0062d, 0x00000, 0x00637, 0x00645, 0x0062d, 0x00000, 0x00637, 0x00645, 0x00645, 0x00000, 0x00637, 0x00645, 0x0064a, 0x00000, 0x00639, 0x0062c, 0x00645, 0x00000, 0x00639, 0x00645, 0x00645, 0x00000, 0x00639, 0x00645, 0x00645, 0x00000, 0x00639, 0x00645, 0x00649, 0x00000, 0x0063a, 0x00645, 0x00645, 0x00000, 0x0063a, 0x00645, 0x0064a, 0x00000, 0x0063a, 0x00645, 0x00649, 0x00000, 0x00641, 0x0062e, 0x00645, 0x00000, 0x00641, 0x0062e, 0x00645, 0x00000, 0x00642, 0x00645, 0x0062d, 0x00000, 0x00642, 0x00645, 0x00645, 0x00000, 0x00644, 0x0062d, 0x00645, 0x00000, 0x00644, 0x0062d, 0x0064a, 0x00000, 0x00644, 0x0062d, 0x00649, 0x00000, 0x00644, 0x0062c, 0x0062c, 0x00000, 0x00644, 0x0062c, 0x0062c, 0x00000, 0x00644, 0x0062e, 0x00645, 0x00000, 0x00644, 0x0062e, 0x00645, 0x00000, 0x00644, 0x00645, 0x0062d, 0x00000, 0x00644, 0x00645, 0x0062d, 0x00000, 0x00645, 0x0062d, 0x0062c, 0x00000, 0x00645, 0x0062d, 0x00645, 0x00000, 0x00645, 0x0062d, 0x0064a, 0x00000, 0x00645, 0x0062c, 0x0062d, 0x00000, 0x00645, 0x0062c, 0x00645, 0x00000, 0x00645, 0x0062e, 0x0062c, 0x00000, 0x00645, 0x0062e, 0x00645, 0x00000, 0x00645, 0x0062c, 0x0062e, 0x00000, 0x00647, 0x00645, 0x0062c, 0x00000, 0x00647, 0x00645, 0x00645, 0x00000, 0x00646, 0x0062d, 0x00645, 0x00000, 0x00646, 0x0062d, 0x00649, 0x00000, 0x00646, 0x0062c, 0x00645, 0x00000, 0x00646, 0x0062c, 0x00645, 0x00000, 0x00646, 0x0062c, 0x00649, 0x00000, 0x00646, 0x00645, 0x0064a, 0x00000, 0x00646, 0x00645, 0x00649, 0x00000, 0x0064a, 0x00645, 0x00645, 0x00000, 0x0064a, 0x00645, 0x00645, 0x00000, 0x00628, 0x0062e, 0x0064a, 0x00000, 0x0062a, 0x0062c, 0x0064a, 0x00000, 0x0062a, 0x0062c, 0x00649, 0x00000, 0x0062a, 0x0062e, 0x0064a, 0x00000, 0x0062a, 0x0062e, 0x00649, 0x00000, 0x0062a, 0x00645, 0x0064a, 0x00000, 0x0062a, 0x00645, 0x00649, 0x00000, 0x0062c, 0x00645, 0x0064a, 0x00000, 0x0062c, 0x0062d, 0x00649, 0x00000, 0x0062c, 0x00645, 0x00649, 0x00000, 0x00633, 0x0062e, 0x00649, 0x00000, 0x00635, 0x0062d, 0x0064a, 0x00000, 0x00634, 0x0062d, 0x0064a, 0x00000, 0x00636, 0x0062d, 0x0064a, 0x00000, 0x00644, 0x0062c, 0x0064a, 0x00000, 0x00644, 0x00645, 0x0064a, 0x00000, 0x0064a, 0x0062d, 0x0064a, 0x00000, 0x0064a, 0x0062c, 0x0064a, 0x00000, 0x0064a, 0x00645, 0x0064a, 0x00000, 0x00645, 0x00645, 0x0064a, 0x00000, 0x00642, 0x00645, 0x0064a, 0x00000, 0x00646, 0x0062d, 0x0064a, 0x00000, 0x00642, 0x00645, 0x0062d, 0x00000, 0x00644, 0x0062d, 0x00645, 0x00000, 0x00639, 0x00645, 0x0064a, 0x00000, 0x00643, 0x00645, 0x0064a, 0x00000, 0x00646, 0x0062c, 0x0062d, 0x00000, 0x00645, 0x0062e, 0x0064a, 0x00000, 0x00644, 0x0062c, 0x00645, 0x00000, 0x00643, 0x00645, 0x00645, 0x00000, 0x00644, 0x0062c, 0x00645, 0x00000, 0x00646, 0x0062c, 0x0062d, 0x00000, 0x0062c, 0x0062d, 0x0064a, 0x00000, 0x0062d, 0x0062c, 0x0064a, 0x00000, 0x00645, 0x0062c, 0x0064a, 0x00000, 0x00641, 0x00645, 0x0064a, 0x00000, 0x00628, 0x0062d, 0x0064a, 0x00000, 0x00643, 0x00645, 0x00645, 0x00000, 0x00639, 0x0062c, 0x00645, 0x00000, 0x00635, 0x00645, 0x00645, 0x00000, 0x00633, 0x0062e, 0x0064a, 0x00000, 0x00646, 0x0062c, 0x0064a, 0x00000, 0x00635, 0x00644, 0x006d2, 0x00000, 0x00642, 0x00644, 0x006d2, 0x00000, 0x00627, 0x00644, 0x00644, 0x00647, 0x00000, 0x00627, 0x00643, 0x00628, 0x00631, 0x00000, 0x00645, 0x0062d, 0x00645, 0x0062f, 0x00000, 0x00635, 0x00644, 0x00639, 0x00645, 0x00000, 0x00631, 0x00633, 0x00648, 0x00644, 0x00000, 0x00639, 0x00644, 0x0064a, 0x00647, 0x00000, 0x00648, 0x00633, 0x00644, 0x00645, 0x00000, 0x00635, 0x00644, 0x00649, 0x00000, 0x00635, 0x00644, 0x00649, 0x00020, 0x00627, 0x00644, 0x00644, 0x00647, 0x00020, 0x00639, 0x00644, 0x0064a, 0x00647, 0x00020, 0x00648, 0x00633, 0x00644, 0x00645, 0x00000, 0x0062c, 0x00644, 0x00020, 0x0062c, 0x00644, 0x00627, 0x00644, 0x00647, 0x00000, 0x00631, 0x006cc, 0x00627, 0x00644, 0x00000, 0x00020, 0x0064b, 0x00000, 0x00640, 0x0064b, 0x00000, 0x00020, 0x0064c, 0x00000, 0x00020, 0x0064d, 0x00000, 0x00020, 0x0064e, 0x00000, 0x00640, 0x0064e, 0x00000, 0x00020, 0x0064f, 0x00000, 0x00640, 0x0064f, 0x00000, 0x00020, 0x00650, 0x00000, 0x00640, 0x00650, 0x00000, 0x00020, 0x00651, 0x00000, 0x00640, 0x00651, 0x00000, 0x00020, 0x00652, 0x00000, 0x00640, 0x00652, 0x00000, 0x00644, 0x00622, 0x00000, 0x00644, 0x00622, 0x00000, 0x00644, 0x00623, 0x00000, 0x00644, 0x00623, 0x00000, 0x00644, 0x00625, 0x00000, 0x00644, 0x00625, 0x00000, 0x00644, 0x00627, 0x00000, 0x00644, 0x00627, 0x00000, 0x11099, 0x110ba, 0x00000, 0x1109b, 0x110ba, 0x00000, 0x110a5, 0x110ba, 0x00000, 0x11131, 0x11127, 0x00000, 0x11132, 0x11127, 0x00000, 0x11347, 0x1133e, 0x00000, 0x11347, 0x11357, 0x00000, 0x114b9, 0x114ba, 0x00000, 0x114b9, 0x114b0, 0x00000, 0x114b9, 0x114bd, 0x00000, 0x115b8, 0x115af, 0x00000, 0x115b9, 0x115af, 0x00000, 0x1d157, 0x1d165, 0x00000, 0x1d158, 0x1d165, 0x00000, 0x1d15f, 0x1d16e, 0x00000, 0x1d15f, 0x1d16f, 0x00000, 0x1d15f, 0x1d170, 0x00000, 0x1d15f, 0x1d171, 0x00000, 0x1d15f, 0x1d172, 0x00000, 0x1d1b9, 0x1d165, 0x00000, 0x1d1ba, 0x1d165, 0x00000, 0x1d1bb, 0x1d16e, 0x00000, 0x1d1bc, 0x1d16e, 0x00000, 0x1d1bb, 0x1d16f, 0x00000, 0x1d1bc, 0x1d16f, 0x00000, 0x00030, 0x0002e, 0x00000, 0x00030, 0x0002c, 0x00000, 0x00031, 0x0002c, 0x00000, 0x00032, 0x0002c, 0x00000, 0x00033, 0x0002c, 0x00000, 0x00034, 0x0002c, 0x00000, 0x00035, 0x0002c, 0x00000, 0x00036, 0x0002c, 0x00000, 0x00037, 0x0002c, 0x00000, 0x00038, 0x0002c, 0x00000, 0x00039, 0x0002c, 0x00000, 0x00028, 0x00041, 0x00029, 0x00000, 0x00028, 0x00042, 0x00029, 0x00000, 0x00028, 0x00043, 0x00029, 0x00000, 0x00028, 0x00044, 0x00029, 0x00000, 0x00028, 0x00045, 0x00029, 0x00000, 0x00028, 0x00046, 0x00029, 0x00000, 0x00028, 0x00047, 0x00029, 0x00000, 0x00028, 0x00048, 0x00029, 0x00000, 0x00028, 0x00049, 0x00029, 0x00000, 0x00028, 0x0004a, 0x00029, 0x00000, 0x00028, 0x0004b, 0x00029, 0x00000, 0x00028, 0x0004c, 0x00029, 0x00000, 0x00028, 0x0004d, 0x00029, 0x00000, 0x00028, 0x0004e, 0x00029, 0x00000, 0x00028, 0x0004f, 0x00029, 0x00000, 0x00028, 0x00050, 0x00029, 0x00000, 0x00028, 0x00051, 0x00029, 0x00000, 0x00028, 0x00052, 0x00029, 0x00000, 0x00028, 0x00053, 0x00029, 0x00000, 0x00028, 0x00054, 0x00029, 0x00000, 0x00028, 0x00055, 0x00029, 0x00000, 0x00028, 0x00056, 0x00029, 0x00000, 0x00028, 0x00057, 0x00029, 0x00000, 0x00028, 0x00058, 0x00029, 0x00000, 0x00028, 0x00059, 0x00029, 0x00000, 0x00028, 0x0005a, 0x00029, 0x00000, 0x03014, 0x00053, 0x03015, 0x00000, 0x00043, 0x00044, 0x00000, 0x00057, 0x0005a, 0x00000, 0x00048, 0x00056, 0x00000, 0x0004d, 0x00056, 0x00000, 0x00053, 0x00044, 0x00000, 0x00053, 0x00053, 0x00000, 0x00050, 0x00050, 0x00056, 0x00000, 0x00057, 0x00043, 0x00000, 0x0004d, 0x00043, 0x00000, 0x0004d, 0x00044, 0x00000, 0x00044, 0x0004a, 0x00000, 0x0307b, 0x0304b, 0x00000, 0x030b3, 0x030b3, 0x00000, 0x03014, 0x0672c, 0x03015, 0x00000, 0x03014, 0x04e09, 0x03015, 0x00000, 0x03014, 0x04e8c, 0x03015, 0x00000, 0x03014, 0x05b89, 0x03015, 0x00000, 0x03014, 0x070b9, 0x03015, 0x00000, 0x03014, 0x06253, 0x03015, 0x00000, 0x03014, 0x076d7, 0x03015, 0x00000, 0x03014, 0x052dd, 0x03015, 0x00000, 0x03014, 0x06557, 0x03015, 0x00000 }; dovecot-2.3.21.1/src/lib/hash-method.c0000644000000000000000000000452514656633576014225 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "md4.h" #include "md5.h" #include "sha1.h" #include "sha2.h" #include "sha3.h" #include "hash-method.h" const struct hash_method *hash_method_lookup(const char *name) { unsigned int i; for (i = 0; hash_methods[i] != NULL; i++) { if (strcmp(hash_methods[i]->name, name) == 0) return hash_methods[i]; } return NULL; } static void hash_method_init_size(void *context) { uint64_t *ctx = context; *ctx = 0; } static void hash_method_loop_size(void *context, const void *data ATTR_UNUSED, size_t size) { uint64_t *ctx = context; *ctx += size; } static void hash_method_result_size(void *context, unsigned char *result_r) { uint64_t *ctx = context; result_r[0] = (*ctx & 0xff00000000000000ULL) >> 56; result_r[1] = (*ctx & 0x00ff000000000000ULL) >> 48; result_r[2] = (*ctx & 0x0000ff0000000000ULL) >> 40; result_r[3] = (*ctx & 0x000000ff00000000ULL) >> 32; result_r[4] = (*ctx & 0x00000000ff000000ULL) >> 24; result_r[5] = (*ctx & 0x0000000000ff0000ULL) >> 16; result_r[6] = (*ctx & 0x000000000000ff00ULL) >> 8; result_r[7] = (*ctx & 0x00000000000000ffULL); } void hash_method_get_digest(const struct hash_method *meth, const void *data, size_t data_len, unsigned char *result_r) { i_assert(meth != NULL); i_assert(data_len == 0 || data != NULL); unsigned char ctx[meth->context_size]; meth->init(ctx); meth->loop(ctx, data == NULL ? "" : data, data_len); meth->result(ctx, result_r); } buffer_t *t_hash_data(const struct hash_method *meth, const void *data, size_t data_len) { i_assert(meth != NULL); buffer_t *result = t_buffer_create(meth->digest_size); unsigned char *resptr = buffer_append_space_unsafe(result, meth->digest_size); hash_method_get_digest(meth, data, data_len, resptr); return result; } static const struct hash_method hash_method_size = { .name = "size", .block_size = 1, .context_size = sizeof(uint64_t), .digest_size = sizeof(uint64_t), .init = hash_method_init_size, .loop = hash_method_loop_size, .result = hash_method_result_size }; const struct hash_method *hash_methods[] = { &hash_method_md4, &hash_method_md5, &hash_method_sha1, &hash_method_sha256, &hash_method_sha384, &hash_method_sha512, &hash_method_sha3_256, &hash_method_sha3_512, &hash_method_size, NULL }; dovecot-2.3.21.1/src/lib/uri-util.h0000644000000000000000000003152114656633576013577 00000000000000#ifndef URI_UTIL_H #define URI_UTIL_H #include "net.h" /* * Generic URI parsing. */ enum uri_parse_flags { /* Scheme part 'scheme:' is already parsed externally. */ URI_PARSE_SCHEME_EXTERNAL = BIT(0), /* Allow '#fragment' part in URI */ URI_PARSE_ALLOW_FRAGMENT_PART = BIT(1), }; struct uri_host { const char *name; struct ip_addr ip; }; struct uri_authority { /* encoded userinfo part; e.g. "user:pass" */ const char *enc_userinfo; struct uri_host host; in_port_t port; /* 0 means no port specified */ }; struct uri_parser { pool_t pool; const char *error; const unsigned char *begin, *cur, *end; string_t *tmpbuf; bool allow_pct_nul:1; }; /* parse one instance of percent encoding. Returns 1 for success, 0 if none is preset at the current parser position, and -1 in case of error. The decoded character is returned in ch_r upon success */ int uri_parse_pct_encoded(struct uri_parser *parser, unsigned char *ch_r); /* parse characters as long as these comply with the the 'unreserved' syntax. Returns 1 if characters were found, 0 if none were found, and -1 if there was an error */ int uri_parse_unreserved(struct uri_parser *parser, string_t *part); /* the same as uri_parse_unreserved(), but the allowed characters are extended to 'unreserved / pct-encoded', meaning that percent encoding is allowed */ int uri_parse_unreserved_pct(struct uri_parser *parser, string_t *part); /* decode percent-encoded data from the 'data' parameter, up until the 'until' parameter. If the latter is NULL, data is decoded up until the '\0' character. The decoded data is allocated on the parser pool and returned in decoded_r. Any errors are written to the parser object. */ bool uri_data_decode(struct uri_parser *parser, const char *data, const char *until, const char **decoded_r) ATTR_NULL(3); /* cut the 'scheme ":"' part from the URI. The uri_p pointer is updated to point just past the ":". Returns 0 on success and -1 on error. The result is returned in the scheme_r parameter. This can be NULL to use this function for merely checking the presence of a valid scheme. */ int uri_cut_scheme(const char **uri_p, const char **scheme_r) ATTR_NULL(2); /* parse the URI 'scheme ":"' part. Returns 1 if successful, 0 if the first character is not valid for a scheme, and -1 in case of error. The result parameter scheme_r can be NULL to use this function for merely checking the presence of a valid scheme. */ int uri_parse_scheme(struct uri_parser *parser, const char **scheme_r) ATTR_NULL(2); /* parse the URI 'reg-name' syntax. Returns 1 if successful, 0 if the first character is not valid for a host name, and -1 in case of error. The result parameter reg_name_r can be NULL to use this function for merely checking the presence of a valid host name. The result is allocated from the data stack. */ int uri_parse_reg_name(struct uri_parser *parser, const char **reg_name_r) ATTR_NULL(2); /* parse the URI 'reg-name' part as an Internet host name, which is a sequence of domain name labels separated by '.', as defined in Section 3.5 of RFC 1034 and Section 2.1 of RFC 1123. Returns 1 if successful, 0 if the first character is not valid for a host name, and -1 in case of error. The result parameter host_name_r can be NULL to use this function for merely checking the presence of a valid host name. The result is allocated from the data stack. */ int uri_parse_host_name(struct uri_parser *parser, const char **host_name_r) ATTR_NULL(2); /* parse the URI 'host' syntax, which is either an IP address literal or a an Internet host name, as defined in Section 3.5 of RFC 1034 and Section 2.1 of RFC 1123. An IP address literal is always allowed. Returns 1 if successful, 0 if the first character is not valid for a host name, and -1 in case of error. The provided host struct is filled in with the parsed data, all allocated from the parser pool. The host parameter can be NULL to use this function for merely checking for valid 'host' syntax. */ int uri_parse_host(struct uri_parser *parser, struct uri_host *host) ATTR_NULL(2); /* parse the URI 'authority' syntax. Returns 1 if successful, 0 if the first character is not valid for the 'authority' syntax and -1 in case of error. The provided uri_authority struct is filled in with the parsed data, all allocated from the parser pool. The auth parameter can be NULL to use this function for merely checking for valid 'authority' syntax. */ int uri_parse_authority(struct uri_parser *parser, struct uri_authority *auth) ATTR_NULL(2); /* identical to uri_parse_authority(), except that this function parses '"//" authority', rather than 'authority'. */ int uri_parse_slashslash_authority(struct uri_parser *parser, struct uri_authority *auth) ATTR_NULL(2); /* identical to uri_parse_authority(), except that this function parses the registered name ('reg-name' syntax) as an Internet host name, as defined in Section 3.5 of RFC 1034 and Section 2.1 of RFC 1123. */ int uri_parse_host_authority(struct uri_parser *parser, struct uri_authority *auth) ATTR_NULL(2); /* identical to uri_parse_slashslash_authority(), except that this function parses the registered name ('reg-name' syntax) as an Internet host name, as defined in Section 3.5 of RFC 1034 and Section 2.1 of RFC 1123. */ int uri_parse_slashslash_host_authority(struct uri_parser *parser, struct uri_authority *auth) ATTR_NULL(2); /* parse the URI 'segment' syntax. Returns 1 if successful, 0 if the first character is not valid for the 'segment' syntax and -1 in case of error. The result is allocated from the parser pool. Percent encoding is not decoded in the result. The result parameter can be NULL to use this function for merely checking for valid 'segment' syntax. */ int uri_parse_path_segment(struct uri_parser *parser, const char **segment_r) ATTR_NULL(2); /* parse the URI 'path' syntax. This also resolves '..' and '.' segments in the path. If the path is relative, the relative_r parameter indicates how many segments the base path must be moved towards root (as caused by leading '..' segments). Returns 1 if successful, 0 if the first character is not valid for the 'segment' syntax and -1 in case of error. The result is a NULL-terminated string list allocated from the parser pool. Percent encoding is not decoded in the result. The result parameter can be NULL to use this function for merely checking for valid 'path' syntax. */ int uri_parse_path(struct uri_parser *parser, int *relative_r, const char *const **path_r) ATTR_NULL(2,3); /* parse the URI 'query' syntax. Returns 1 if successful, 0 if the first character is not valid for the 'query' syntax and -1 in case of error. The result is allocated from the parser pool. Percent encoding is not decoded in the result. The result parameter can be NULL to use this function for merely checking for valid 'query' syntax. */ int uri_parse_query(struct uri_parser *parser, const char **query_r) ATTR_NULL(2); /* parse the URI 'fragment' syntax. Returns 1 if successful, 0 if the first character is not valid for the 'fragment' syntax and -1 in case of error. The result is allocated from the parser pool. Percent encoding is not decoded in the result. The result parameter can be NULL to use this function for merely checking for valid 'fragment' syntax. */ int uri_parse_fragment(struct uri_parser *parser, const char **fragment_r) ATTR_NULL(2); /* initialize the URI parser with the provided data */ void uri_parser_init_data(struct uri_parser *parser, pool_t pool, const unsigned char *data, size_t size); /* initialize the URI parser with the provided '\0'-terminated string */ void uri_parser_init(struct uri_parser *parser, pool_t pool, const char *uri); /* returns the temporary buffer associated with this parser. Can be used for higher-level parsing activities. */ string_t *uri_parser_get_tmpbuf(struct uri_parser *parser, size_t size); /* Parse a generic (RFC3986) absolute URI for validity. Returns 0 if valid and -1 otherwise. Note that some URI formats like "sip", "aix" and "aaa" violate RFC3986 and will currently fail with this function. */ int uri_parse_absolute_generic(struct uri_parser *parser, enum uri_parse_flags flags); /* * Generic URI manipulation */ /* copy uri_host struct from src to dest and allocate it on pool */ void uri_host_copy(pool_t pool, struct uri_host *dest, const struct uri_host *src); /* * Generic URI validation */ /* Check whether the provided data is a valid absolute RFC3986 URI. Returns 0 if valid and -1 otherwise. */ int uri_check_data(const unsigned char *data, size_t size, enum uri_parse_flags flags, const char **error_r); /* Check whether the provided string is a valid absolute RFC3986 URI. Returns 0 if valid and -1 otherwise. */ int uri_check(const char *uri, enum uri_parse_flags, const char **error_r); /* * Generic URI construction */ /* encodes the '\0'-terminated data using the percent encoding. The esc_table is a 256 byte lookup table. If none of the esc_mask bits are set at the character's position in the esc_table, a character needs to be encoded. Also, when esc_extra contains a character, it needs to be encoded. All other characters are copied verbatim to the out buffer. */ void uri_data_encode(string_t *out, const unsigned char esc_table[256], unsigned char esc_mask, const char *esc_extra, const char *data) ATTR_NULL(4); /* append the provided scheme to the out buffer */ void uri_append_scheme(string_t *out, const char *scheme); /* append partial user data (i.e. some part of what comes before '@') to the out buffer. No '@' is produced. Characters are percent-encoded when necessary. Characters in esc are always percent-encoded, even when these are valid 'userinfo' characters. */ void uri_append_user_data(string_t *out, const char *esc, const char *data) ATTR_NULL(2); /* append userinfo and '@' to the out buffer. Characters in userinfo are percent-encoded when necessary.*/ void uri_append_userinfo(string_t *out, const char *userinfo); /* append the host name to the out buffer. Characters are percent-encoded when necessary.*/ void uri_append_host_name(string_t *out, const char *name); /* append the host IP address to the out buffer. */ void uri_append_host_ip(string_t *out, const struct ip_addr *host_ip); /* encode the URI host struct to the out buffer. */ void uri_append_host(string_t *out, const struct uri_host *host); /* append the port to the out buffer. */ void uri_append_port(string_t *out, in_port_t port); /* append partial path segment data to the out buffer. No '/' is produced. Characters are percent-encoded when necessary. Characters in esc are always percent-encoded, even when these are valid 'segment' characters. */ void uri_append_path_segment_data(string_t *out, const char *esc, const char *data) ATTR_NULL(2); /* append a full path segment to the out buffer. A leading '/' is produced. Characters are percent-encoded when necessary. */ void uri_append_path_segment(string_t *out, const char *segment); /* append partial path data to the out buffer. The data may include '/', which is not encoded. Characters are percent-encoded when necessary. Characters in esc are always percent-encoded, even when these are valid 'path' characters.*/ void uri_append_path_data(string_t *out, const char *esc, const char *data) ATTR_NULL(2); /* append a full path to the out buffer. A leading '/' is produced. The data may include more '/', which is not encoded. Characters are percent-encoded when necessary. */ void uri_append_path(string_t *out, const char *path); /* append partial query data to the out buffer. No leading '?' is produced. Characters are percent-encoded when necessary. Characters in esc are always percent-encoded, even when these are valid 'query' characters.*/ void uri_append_query_data(string_t *out, const char *esc, const char *data) ATTR_NULL(2); /* append a full URI query part to the out buffer. A leading '?' is produced. Characters are percent-encoded when necessary. */ void uri_append_query(string_t *out, const char *query); /* append partial fragment data to the out buffer. No leading '#' is produced. Characters are percent-encoded when necessary. Characters in esc are always percent-encoded, even when these are valid 'fragment' characters.*/ void uri_append_fragment_data(string_t *out, const char *esc, const char *data) ATTR_NULL(2); /* append a full URI fragment part to the out buffer. A leading '#' is produced. Characters are percent-encoded when necessary. */ void uri_append_fragment(string_t *out, const char *fragment); /* append data to the out buffer and escape any reserved character */ void uri_append_unreserved(string_t *out, const char *data); /* append data to the out buffer and escape any reserved character except '/' */ void uri_append_unreserved_path(string_t *out, const char *data); #endif dovecot-2.3.21.1/src/lib/ioloop.h0000644000000000000000000003532014656633576013327 00000000000000#ifndef IOLOOP_H #define IOLOOP_H #include #include struct io; struct timeout; struct ioloop; struct istream; enum io_condition { IO_READ = 0x01, IO_WRITE = 0x02, /* IO_ERROR can be used to check when writable pipe's reader side closes the pipe. For other uses IO_READ should work just as well. */ IO_ERROR = 0x04, /* internal */ IO_NOTIFY = 0x08 }; enum io_notify_result { /* Notify added successfully */ IO_NOTIFY_ADDED, /* Specified file doesn't exist, can't wait on it */ IO_NOTIFY_NOTFOUND, /* Can't add notify for specified file. Main reasons for this: a) No notify support at all, b) Only directory notifies supported */ IO_NOTIFY_NOSUPPORT }; typedef void io_callback_t(void *context); typedef void timeout_callback_t(void *context); typedef void io_loop_time_moved_callback_t(const struct timeval *old_time, const struct timeval *new_time); typedef void io_switch_callback_t(struct ioloop *prev_ioloop); typedef void io_destroy_callback_t(struct ioloop *ioloop); /* Time when the I/O loop started calling handlers. Can be used instead of time(NULL). */ extern time_t ioloop_time; extern struct timeval ioloop_timeval; extern struct ioloop *current_ioloop; /* Number of microseconds spent on all the ioloops waiting for themselves. */ extern uint64_t ioloop_global_wait_usecs; /* You can create different handlers for IO_READ and IO_WRITE. IO_READ and IO_ERROR can't use different handlers (and there's no point anyway). Don't try to add multiple handlers for the same type. It's not checked and the behavior will be undefined. */ struct io *io_add(int fd, enum io_condition condition, const char *source_filename, unsigned int source_linenum, io_callback_t *callback, void *context) ATTR_NULL(5); #define io_add(fd, condition, callback, context) \ io_add(fd, condition, __FILE__, __LINE__ - \ CALLBACK_TYPECHECK(callback, void (*)(typeof(context))), \ (io_callback_t *)callback, context) struct io *io_add_to(struct ioloop *ioloop, int fd, enum io_condition condition, const char *source_filename, unsigned int source_linenum, io_callback_t *callback, void *context) ATTR_NULL(5); #define io_add_to(ioloop, fd, condition, callback, context) \ io_add_to(ioloop, fd, condition, __FILE__, __LINE__ - \ CALLBACK_TYPECHECK(callback, void (*)(typeof(context))), \ (io_callback_t *)callback, context) enum io_notify_result io_add_notify(const char *path, const char *source_filename, unsigned int source_linenum, io_callback_t *callback, void *context, struct io **io_r) ATTR_NULL(3); #define io_add_notify(path, callback, context, io_r) \ io_add_notify(path, __FILE__, __LINE__ - \ CALLBACK_TYPECHECK(callback, void (*)(typeof(context))), \ (io_callback_t *)callback, context, io_r) struct io *io_add_istream(struct istream *input, const char *source_filename, unsigned int source_linenum, io_callback_t *callback, void *context) ATTR_NULL(3); #define io_add_istream(input, callback, context) \ io_add_istream(input, __FILE__, __LINE__ - \ CALLBACK_TYPECHECK(callback, void (*)(typeof(context))), \ (io_callback_t *)callback, context) struct io *io_add_istream_to(struct ioloop *ioloop, struct istream *input, const char *source_filename, unsigned int source_linenum, io_callback_t *callback, void *context) ATTR_NULL(3); #define io_add_istream_to(ioloop, input, callback, context) \ io_add_istream_to(ioloop, input, __FILE__, __LINE__ - \ CALLBACK_TYPECHECK(callback, void (*)(typeof(context))), \ (io_callback_t *)callback, context) /* Remove I/O handler, and set io pointer to NULL. */ void io_remove(struct io **io); /* Like io_remove(), but assume that the file descriptor is already closed. With some backends this simply frees the memory. */ void io_remove_closed(struct io **io); /* Make sure the I/O callback is called by io_loop_run() even if there isn't any input actually pending currently as seen by the OS. This may be useful if some of the input has already read into some internal buffer and the caller wants to handle it the same way as if the fd itself had input. */ void io_set_pending(struct io *io); /* Returns TRUE if io_set_pending() has been called for the IO and its callback hasn't been called yet. */ bool io_is_pending(struct io *io); /* If set, this IO shouldn't be the only thing being waited on, because it would just result in infinite wait. In those situations rather just crash to indicate that there's a bug. */ void io_set_never_wait_alone(struct io *io, bool set); /* Timeout handlers */ struct timeout * timeout_add(unsigned int msecs, const char *source_filename, unsigned int source_linenum, timeout_callback_t *callback, void *context) ATTR_NULL(4); #define timeout_add(msecs, callback, context) \ timeout_add(msecs, __FILE__, __LINE__ - \ CALLBACK_TYPECHECK(callback, void (*)(typeof(context))) - \ COMPILE_ERROR_IF_TRUE(__builtin_constant_p(msecs) && \ ((msecs) > 0 && (msecs) < 1000)), \ (io_callback_t *)callback, context) struct timeout * timeout_add_to(struct ioloop *ioloop, unsigned int msecs, const char *source_filename, unsigned int source_linenum, timeout_callback_t *callback, void *context) ATTR_NULL(4); #define timeout_add_to(ioloop, msecs, callback, context) \ timeout_add_to(ioloop, msecs, __FILE__, __LINE__ - \ CALLBACK_TYPECHECK(callback, void (*)(typeof(context))) - \ COMPILE_ERROR_IF_TRUE(__builtin_constant_p(msecs) && \ ((msecs) > 0 && (msecs) < 1000)), \ (io_callback_t *)callback, context) struct timeout * timeout_add_short(unsigned int msecs, const char *source_filename, unsigned int source_linenum, timeout_callback_t *callback, void *context) ATTR_NULL(4); #define timeout_add_short(msecs, callback, context) \ timeout_add_short(msecs, __FILE__, __LINE__ - \ CALLBACK_TYPECHECK(callback, void (*)(typeof(context))), \ (io_callback_t *)callback, context) struct timeout * timeout_add_short_to(struct ioloop *ioloop, unsigned int msecs, const char *source_filename, unsigned int source_linenum, timeout_callback_t *callback, void *context) ATTR_NULL(4); #define timeout_add_short_to(ioloop, msecs, callback, context) \ timeout_add_short_to(ioloop, msecs, __FILE__, __LINE__ - \ CALLBACK_TYPECHECK(callback, void (*)(typeof(context))), \ (io_callback_t *)callback, context) struct timeout * timeout_add_absolute(const struct timeval *time, const char *source_filename, unsigned int source_linenum, timeout_callback_t *callback, void *context) ATTR_NULL(4); #define timeout_add_absolute(time, callback, context) \ timeout_add_absolute(time, __FILE__, __LINE__ - \ CALLBACK_TYPECHECK(callback, void (*)(typeof(context))), \ (io_callback_t *)callback, context) struct timeout * timeout_add_absolute_to(struct ioloop *ioloop, const struct timeval *time, const char *source_filename, unsigned int source_linenum, timeout_callback_t *callback, void *context) ATTR_NULL(4); #define timeout_add_absolute_to(ioloop, time, callback, context) \ timeout_add_absolute_to(ioloop, time, __FILE__, __LINE__ - \ CALLBACK_TYPECHECK(callback, void (*)(typeof(context))), \ (io_callback_t *)callback, context) /* Remove timeout handler, and set timeout pointer to NULL. */ void timeout_remove(struct timeout **timeout); /* Reset timeout so it's next run after now+msecs. */ void timeout_reset(struct timeout *timeout); /* Refresh ioloop_time and ioloop_timeval variables. */ void io_loop_time_refresh(void); void io_loop_run(struct ioloop *ioloop); /* Stop the ioloop immediately. No further IO or timeout callbacks are called. Warning: This is not safe to be called in non-delayed signal handlers. */ void io_loop_stop(struct ioloop *ioloop); /* Stop ioloop after finishing all the pending IOs and timeouts. */ void io_loop_stop_delayed(struct ioloop *ioloop); bool io_loop_is_running(struct ioloop *ioloop); /* call these if you wish to run the iteration only once */ void io_loop_set_running(struct ioloop *ioloop); void io_loop_handler_run(struct ioloop *ioloop); struct ioloop *io_loop_create(void); /* Specify the maximum number of fds we're expecting to use. */ void io_loop_set_max_fd_count(struct ioloop *ioloop, unsigned int max_fds); /* Destroy I/O loop and set ioloop pointer to NULL. */ void io_loop_destroy(struct ioloop **ioloop); /* If time moves backwards or jumps forwards call the callback. */ void io_loop_set_time_moved_callback(struct ioloop *ioloop, io_loop_time_moved_callback_t *callback); /* Change the current_ioloop. */ void io_loop_set_current(struct ioloop *ioloop); /* Return the root ioloop. */ struct ioloop *io_loop_get_root(void); /* Call the callback whenever ioloop is changed. */ void io_loop_add_switch_callback(io_switch_callback_t *callback); void io_loop_remove_switch_callback(io_switch_callback_t *callback); /* Call the callback whenever ioloop is destroyed. */ void io_loop_add_destroy_callback(io_destroy_callback_t *callback); void io_loop_remove_destroy_callback(io_destroy_callback_t *callback); /* Create a new ioloop context. While the context is activated, it's automatically attached to all the following I/Os and timeouts that are added until the context is deactivated (e.g. returning to back to a running ioloop). Whenever such added I/O or timeout callback is called, this context is automatically activated. After the context is created, callbacks should be added to it and the context should be activated with either io_loop_context_activate() or io_loop_context_switch(). */ struct ioloop_context *io_loop_context_new(struct ioloop *ioloop); void io_loop_context_ref(struct ioloop_context *ctx); void io_loop_context_unref(struct ioloop_context **ctx); /* Call the activate callback when this context is activated (I/O callback is about to be called), and the deactivate callback when the context is deactivated (I/O callback has returned). You can add multiple callbacks. The ioloop context is a global state, so only a single context can be active at a time. The callbacks are guaranteed to be called only at their proper states, i.e. activate() callback is called only when switching from no context to the active context, and deactive() is called only when switching from previously activated context into no context. No context is active at a time when the ioloop is destroyed. */ void io_loop_context_add_callbacks(struct ioloop_context *ctx, io_callback_t *activate, io_callback_t *deactivate, void *context); #define io_loop_context_add_callbacks(ctx, activate, deactivate, context) \ io_loop_context_add_callbacks(ctx, 1 ? (io_callback_t *)activate : \ CALLBACK_TYPECHECK(activate, void (*)(typeof(context))) - \ CALLBACK_TYPECHECK(deactivate, void (*)(typeof(context))), \ (io_callback_t *)deactivate, context) /* Remove callbacks with the given callbacks and context. */ void io_loop_context_remove_callbacks(struct ioloop_context *ctx, io_callback_t *activate, io_callback_t *deactivate, void *context); #define io_loop_context_remove_callbacks(ctx, activate, deactivate, context) \ io_loop_context_remove_callbacks(ctx, 1 ? (io_callback_t *)activate : \ CALLBACK_TYPECHECK(activate, void (*)(typeof(context))) - \ CALLBACK_TYPECHECK(deactivate, void (*)(typeof(context))), \ (io_callback_t *)deactivate, context) /* Returns the current context set to ioloop. */ struct ioloop_context *io_loop_get_current_context(struct ioloop *ioloop); /* Explicitly activate an ioloop context. There must not be any context active at the moment, so this most likely shouldn't be called while ioloop is running. An activated context must be explicitly deactivated with io_loop_context_deactivate() before the ioloop is destroyed, or before any ioloop is run. */ void io_loop_context_activate(struct ioloop_context *ctx); /* Explicitly deactivate an ioloop context. The given context must be currently active or it assert-crashes. This should be called only after a context was explicitly activated with io_loop_context_activate(). */ void io_loop_context_deactivate(struct ioloop_context *ctx); /* If there's an active ioloop context, deactivate it. Then activate the given context. Usually used after creating a new context. */ void io_loop_context_switch(struct ioloop_context *ctx); /* Returns fd, which contains all of the ioloop's current notifications. When it becomes readable, there is a new notification. Calling this function stops the existing notifications in the ioloop from working anymore. This function's main idea is that the fd can be passed to another process, which can use it to find out if an interesting notification happens. Returns fd on success, -1 on error. */ int io_loop_extract_notify_fd(struct ioloop *ioloop); /* IO wait timers can be used to track how much time the io_wait_timer has spent on waiting in its ioloops. This is similar to io_loop_get_wait_usecs(), but it's easier to use when the wait time needs to be tracked across multiple ioloops. */ struct io_wait_timer * io_wait_timer_add(const char *source_filename, unsigned int source_linenum); #define io_wait_timer_add() \ io_wait_timer_add(__FILE__, __LINE__) struct io_wait_timer * io_wait_timer_add_to(struct ioloop *ioloop, const char *source_filename, unsigned int source_linenum); #define io_wait_timer_add_to(ioloop) \ io_wait_timer_add_to(ioloop, __FILE__, __LINE__) struct io_wait_timer *io_wait_timer_move(struct io_wait_timer **timer); struct io_wait_timer *io_wait_timer_move_to(struct io_wait_timer **timer, struct ioloop *ioloop); void io_wait_timer_remove(struct io_wait_timer **timer); uint64_t io_wait_timer_get_usecs(struct io_wait_timer *timer); /* Move the given I/O into the provided/current I/O loop if it's not already there. New I/O is returned, while the old one is freed. */ struct io *io_loop_move_io_to(struct ioloop *ioloop, struct io **_io); struct io *io_loop_move_io(struct io **io); /* Like io_loop_move_io(), but for timeouts. */ struct timeout *io_loop_move_timeout_to(struct ioloop *ioloop, struct timeout **timeout); struct timeout *io_loop_move_timeout(struct timeout **timeout); /* Returns TRUE if any IOs have been added to the ioloop. */ bool io_loop_have_ios(struct ioloop *ioloop); /* Returns TRUE if there is a pending timeout that is going to be run immediately. */ bool io_loop_have_immediate_timeouts(struct ioloop *ioloop); /* Returns TRUE if there are no IOs or timeouts in the ioloop. */ bool io_loop_is_empty(struct ioloop *ioloop); /* Returns number of microseconds spent on the ioloop waiting itself. */ uint64_t io_loop_get_wait_usecs(struct ioloop *ioloop); /* Return all io conditions added for the given fd. This needs to scan through all the file ios in the ioloop. */ enum io_condition io_loop_find_fd_conditions(struct ioloop *ioloop, int fd); #endif dovecot-2.3.21.1/src/lib/istream-base64.h0000644000000000000000000000070214656633576014550 00000000000000#ifndef ISTREAM_BASE64_H #define ISTREAM_BASE64_H struct istream * i_stream_create_base64_encoder(struct istream *input, unsigned int chars_per_line, bool crlf); struct istream * i_stream_create_base64url_encoder(struct istream *input, unsigned int chars_per_line, bool crlf); struct istream * i_stream_create_base64_decoder(struct istream *input); struct istream * i_stream_create_base64url_decoder(struct istream *input); #endif dovecot-2.3.21.1/src/lib/istream-timeout.h0000644000000000000000000000043014656633576015150 00000000000000#ifndef ISTREAM_TIMEOUT_H #define ISTREAM_TIMEOUT_H /* Return ETIMEDOUT error if read() doesn't return anything for timeout_msecs. If timeout_msecs=0, there is no timeout. */ struct istream * i_stream_create_timeout(struct istream *input, unsigned int timeout_msecs); #endif dovecot-2.3.21.1/src/lib/restrict-access.c0000644000000000000000000003415314656633576015122 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #define _GNU_SOURCE /* setresgid() */ #include /* for AIX */ #include #include #include "lib.h" #include "str.h" #include "restrict-access.h" #include "env-util.h" #include "ipwd.h" #include #ifdef HAVE_PR_SET_DUMPABLE # include #endif static gid_t process_primary_gid = (gid_t)-1; static gid_t process_privileged_gid = (gid_t)-1; static bool process_using_priv_gid = FALSE; static char *chroot_dir = NULL; void restrict_access_init(struct restrict_access_settings *set) { i_zero(set); set->uid = (uid_t)-1; set->gid = (gid_t)-1; set->privileged_gid = (gid_t)-1; } static const char *get_uid_str(uid_t uid) { struct passwd pw; const char *ret; int old_errno = errno; if (i_getpwuid(uid, &pw) <= 0) ret = dec2str(uid); else ret = t_strdup_printf("%s(%s)", dec2str(uid), pw.pw_name); errno = old_errno; return ret; } static const char *get_gid_str(gid_t gid) { struct group group; const char *ret; int old_errno = errno; if (i_getgrgid(gid, &group) <= 0) ret = dec2str(gid); else ret = t_strdup_printf("%s(%s)", dec2str(gid), group.gr_name); errno = old_errno; return ret; } static void restrict_init_groups(gid_t primary_gid, gid_t privileged_gid, const char *gid_source) { string_t *str; if (privileged_gid == (gid_t)-1) { if (primary_gid == getgid() && primary_gid == getegid()) { /* everything is already set */ return; } if (setgid(primary_gid) == 0) return; str = t_str_new(128); str_printfa(str, "setgid(%s", get_gid_str(primary_gid)); if (gid_source != NULL) str_printfa(str, " from %s", gid_source); str_printfa(str, ") failed with euid=%s, gid=%s, egid=%s: %m " "(This binary should probably be called with " "process group set to %s instead of %s)", get_uid_str(geteuid()), get_gid_str(getgid()), get_gid_str(getegid()), get_gid_str(primary_gid), get_gid_str(getegid())); i_fatal("%s", str_c(str)); } if (getegid() != 0 && primary_gid == getgid() && primary_gid == getegid()) { /* privileged_gid is hopefully in saved ID. if not, there's nothing we can do about it. */ return; } #ifdef HAVE_SETRESGID if (setresgid(primary_gid, primary_gid, privileged_gid) != 0) { i_fatal("setresgid(%s,%s,%s) failed with euid=%s: %m", get_gid_str(primary_gid), get_gid_str(primary_gid), get_gid_str(privileged_gid), get_uid_str(geteuid())); } #else if (geteuid() == 0) { /* real, effective, saved -> privileged_gid */ if (setgid(privileged_gid) < 0) { i_fatal("setgid(%s) failed: %m", get_gid_str(privileged_gid)); } } /* real, effective -> primary_gid saved -> keep */ if (setregid(primary_gid, primary_gid) != 0) { i_fatal("setregid(%s,%s) failed with euid=%s: %m", get_gid_str(primary_gid), get_gid_str(privileged_gid), get_uid_str(geteuid())); } #endif } gid_t *restrict_get_groups_list(unsigned int *gid_count_r) { gid_t *gid_list; int ret, gid_count; if ((gid_count = getgroups(0, NULL)) < 0) i_fatal("getgroups() failed: %m"); /* @UNSAFE */ gid_list = t_new(gid_t, gid_count+1); /* +1 in case gid_count=0 */ if ((ret = getgroups(gid_count, gid_list)) < 0) i_fatal("getgroups() failed: %m"); *gid_count_r = ret; return gid_list; } static void drop_restricted_groups(const struct restrict_access_settings *set, gid_t *gid_list, unsigned int *gid_count, bool *have_root_group) { /* @UNSAFE */ unsigned int i, used; for (i = 0, used = 0; i < *gid_count; i++) { if (gid_list[i] >= set->first_valid_gid && (set->last_valid_gid == 0 || gid_list[i] <= set->last_valid_gid)) { if (gid_list[i] == 0) *have_root_group = TRUE; gid_list[used++] = gid_list[i]; } } *gid_count = used; } static gid_t get_group_id(const char *name) { struct group group; gid_t gid; if (str_to_gid(name, &gid) == 0) return gid; switch (i_getgrnam(name, &group)) { case -1: i_fatal("getgrnam(%s) failed: %m", name); case 0: i_fatal("unknown group name in extra_groups: %s", name); default: return group.gr_gid; } } static void fix_groups_list(const struct restrict_access_settings *set, bool preserve_existing, bool *have_root_group) { gid_t gid, *gid_list, *gid_list2; const char *const *tmp, *empty = NULL; unsigned int i, gid_count; bool add_primary_gid; /* if we're using a privileged GID, we can temporarily drop our effective GID. we still want to be able to use its privileges, so add it to supplementary groups. */ add_primary_gid = process_privileged_gid != (gid_t)-1; tmp = set->extra_groups == NULL ? &empty : t_strsplit_spaces(set->extra_groups, ", "); if (preserve_existing) { gid_list = restrict_get_groups_list(&gid_count); drop_restricted_groups(set, gid_list, &gid_count, have_root_group); /* see if the list already contains the primary GID */ for (i = 0; i < gid_count; i++) { if (gid_list[i] == process_primary_gid) { add_primary_gid = FALSE; break; } } } else { gid_list = NULL; gid_count = 0; } if (gid_count == 0) { /* Some OSes don't like an empty groups list, so use the primary GID as the only one. */ gid_list = t_new(gid_t, 2); gid_list[0] = process_primary_gid; gid_count = 1; add_primary_gid = FALSE; } if (*tmp != NULL || add_primary_gid) { /* @UNSAFE: add extra groups and/or primary GID to gids list */ gid_list2 = t_new(gid_t, gid_count + str_array_length(tmp) + 1); memcpy(gid_list2, gid_list, gid_count * sizeof(gid_t)); for (; *tmp != NULL; tmp++) { gid = get_group_id(*tmp); if (gid != process_primary_gid) gid_list2[gid_count++] = gid; } if (add_primary_gid) gid_list2[gid_count++] = process_primary_gid; gid_list = gid_list2; } if (setgroups(gid_count, gid_list) < 0) { if (errno == EINVAL) { i_fatal("setgroups(%s) failed: Too many extra groups", set->extra_groups == NULL ? "" : set->extra_groups); } else { i_fatal("setgroups() failed: %m"); } } } static const char * get_setuid_error_str(const struct restrict_access_settings *set, uid_t target_uid) { string_t *str = t_str_new(128); str_printfa(str, "setuid(%s", get_uid_str(target_uid)); if (set->uid_source != NULL) str_printfa(str, " from %s", set->uid_source); str_printfa(str, ") failed with euid=%s: %m ", get_uid_str(geteuid())); if (errno == EAGAIN) { str_append(str, "(ulimit -u reached)"); } else { str_printfa(str, "(This binary should probably be called with " "process user set to %s instead of %s)", get_uid_str(target_uid), get_uid_str(geteuid())); } return str_c(str); } void restrict_access(const struct restrict_access_settings *set, enum restrict_access_flags flags, const char *home) { bool is_root, have_root_group, preserve_groups = FALSE; bool allow_root_gid; bool allow_root = (flags & RESTRICT_ACCESS_FLAG_ALLOW_ROOT) != 0; uid_t target_uid = set->uid; is_root = geteuid() == 0; if (!is_root && !set->allow_setuid_root && getuid() == 0) { /* recover current effective UID */ if (target_uid == (uid_t)-1) target_uid = geteuid(); else i_assert(target_uid > 0); /* try to elevate to root */ if (seteuid(0) < 0) i_fatal("seteuid(0) failed: %m"); is_root = TRUE; } /* set the primary/privileged group */ process_primary_gid = set->gid; process_privileged_gid = set->privileged_gid; if (process_privileged_gid == process_primary_gid) { /* a pointless configuration, ignore it */ process_privileged_gid = (gid_t)-1; } have_root_group = process_primary_gid == 0; if (process_primary_gid != (gid_t)-1 || process_privileged_gid != (gid_t)-1) { if (process_primary_gid == (gid_t)-1) process_primary_gid = getegid(); restrict_init_groups(process_primary_gid, process_privileged_gid, set->gid_source); } else { if (process_primary_gid == (gid_t)-1) process_primary_gid = getegid(); } /* set system user's groups */ if (set->system_groups_user != NULL && is_root) { if (initgroups(set->system_groups_user, process_primary_gid) < 0) { i_fatal("initgroups(%s, %s) failed: %m", set->system_groups_user, get_gid_str(process_primary_gid)); } preserve_groups = TRUE; } /* add extra groups. if we set system user's groups, drop the restricted groups at the same time. */ if (is_root) T_BEGIN { fix_groups_list(set, preserve_groups, &have_root_group); } T_END; /* chrooting */ if (set->chroot_dir != NULL) { /* kludge: localtime() must be called before chroot(), or the timezone isn't known */ time_t t = 0; (void)localtime(&t); if (chroot(set->chroot_dir) != 0) i_fatal("chroot(%s) failed: %m", set->chroot_dir); /* makes static analyzers happy, and is more secure */ if (chdir("/") != 0) i_fatal("chdir(/) failed: %m"); chroot_dir = i_strdup(set->chroot_dir); if (home != NULL) { if (chdir(home) < 0) { i_error("chdir(%s) failed: %m", home); } } } /* uid last */ if (target_uid != (uid_t)-1) { if (setuid(target_uid) != 0) i_fatal("%s", get_setuid_error_str(set, target_uid)); } /* verify that we actually dropped the privileges */ if ((target_uid != (uid_t)-1 && target_uid != 0) || !allow_root) { if (setuid(0) == 0) { if (!allow_root && (target_uid == 0 || target_uid == (uid_t)-1)) i_fatal("This process must not be run as root"); i_fatal("We couldn't drop root privileges"); } } if (set->first_valid_gid != 0) allow_root_gid = FALSE; else if (process_primary_gid == 0 || process_privileged_gid == 0) allow_root_gid = TRUE; else allow_root_gid = FALSE; if (!allow_root_gid && target_uid != 0 && (target_uid != (uid_t)-1 || !is_root)) { if (getgid() == 0 || getegid() == 0 || setgid(0) == 0) { if (process_primary_gid == 0) i_fatal("GID 0 isn't permitted"); i_fatal("We couldn't drop root group privileges " "(wanted=%s, gid=%s, egid=%s)", get_gid_str(process_primary_gid), get_gid_str(getgid()), get_gid_str(getegid())); } } } void restrict_access_set_env(const struct restrict_access_settings *set) { if (set->system_groups_user != NULL && *set->system_groups_user != '\0') env_put("RESTRICT_USER", set->system_groups_user); if (set->chroot_dir != NULL && *set->chroot_dir != '\0') env_put("RESTRICT_CHROOT", set->chroot_dir); if (set->uid != (uid_t)-1) env_put("RESTRICT_SETUID", dec2str(set->uid)); if (set->gid != (gid_t)-1) env_put("RESTRICT_SETGID", dec2str(set->gid)); if (set->privileged_gid != (gid_t)-1) env_put("RESTRICT_SETGID_PRIV", dec2str(set->privileged_gid)); if (set->extra_groups != NULL && *set->extra_groups != '\0') env_put("RESTRICT_SETEXTRAGROUPS", set->extra_groups); if (set->first_valid_gid != 0) env_put("RESTRICT_GID_FIRST", dec2str(set->first_valid_gid)); if (set->last_valid_gid != 0) env_put("RESTRICT_GID_LAST", dec2str(set->last_valid_gid)); } static const char *null_if_empty(const char *str) { return str == NULL || *str == '\0' ? NULL : str; } void restrict_access_get_env(struct restrict_access_settings *set_r) { const char *value; restrict_access_init(set_r); if ((value = getenv("RESTRICT_SETUID")) != NULL) { if (str_to_uid(value, &set_r->uid) < 0) i_fatal("Invalid uid: %s", value); } if ((value = getenv("RESTRICT_SETGID")) != NULL) { if (str_to_gid(value, &set_r->gid) < 0) i_fatal("Invalid gid: %s", value); } if ((value = getenv("RESTRICT_SETGID_PRIV")) != NULL) { if (str_to_gid(value, &set_r->privileged_gid) < 0) i_fatal("Invalid privileged_gid: %s", value); } if ((value = getenv("RESTRICT_GID_FIRST")) != NULL) { if (str_to_gid(value, &set_r->first_valid_gid) < 0) i_fatal("Invalid first_valid_gid: %s", value); } if ((value = getenv("RESTRICT_GID_LAST")) != NULL) { if (str_to_gid(value, &set_r->last_valid_gid) < 0) i_fatal("Invalid last_value_gid: %s", value); } set_r->extra_groups = null_if_empty(getenv("RESTRICT_SETEXTRAGROUPS")); set_r->system_groups_user = null_if_empty(getenv("RESTRICT_USER")); set_r->chroot_dir = null_if_empty(getenv("RESTRICT_CHROOT")); } void restrict_access_by_env(enum restrict_access_flags flags, const char *home) { struct restrict_access_settings set; restrict_access_get_env(&set); restrict_access(&set, flags, home); /* clear the environment, so we don't fail if we get back here */ env_remove("RESTRICT_SETUID"); if (process_privileged_gid == (gid_t)-1) { /* if we're dropping privileges before executing and a privileged group is set, the groups must be fixed after exec */ env_remove("RESTRICT_SETGID"); env_remove("RESTRICT_SETGID_PRIV"); } env_remove("RESTRICT_GID_FIRST"); env_remove("RESTRICT_GID_LAST"); if (getuid() != 0) env_remove("RESTRICT_SETEXTRAGROUPS"); else { /* Preserve RESTRICT_SETEXTRAGROUPS, so if we're again dropping more privileges we'll still preserve the extra groups. This mainly means preserving service { extra_groups } for lmtp and doveadm accesses. */ } env_remove("RESTRICT_USER"); env_remove("RESTRICT_CHROOT"); } const char *restrict_access_get_current_chroot(void) { return chroot_dir; } void restrict_access_set_dumpable(bool allow ATTR_UNUSED) { #ifdef HAVE_PR_SET_DUMPABLE if (prctl(PR_SET_DUMPABLE, allow ? 1 : 0, 0, 0, 0) < 0) i_error("prctl(PR_SET_DUMPABLE) failed: %m"); #endif } bool restrict_access_get_dumpable(void) { #ifdef HAVE_PR_SET_DUMPABLE bool allow = FALSE; if (prctl(PR_GET_DUMPABLE, &allow, 0, 0, 0) < 0) i_error("prctl(PR_GET_DUMPABLE) failed: %m"); return allow; #endif return TRUE; } void restrict_access_allow_coredumps(bool allow) { if (getenv("PR_SET_DUMPABLE") != NULL) { restrict_access_set_dumpable(allow); } } int restrict_access_use_priv_gid(void) { i_assert(!process_using_priv_gid); if (process_privileged_gid == (gid_t)-1) return 0; if (setegid(process_privileged_gid) < 0) { i_error("setegid(privileged) failed: %m"); return -1; } process_using_priv_gid = TRUE; return 0; } void restrict_access_drop_priv_gid(void) { if (!process_using_priv_gid) return; if (setegid(process_primary_gid) < 0) i_fatal("setegid(primary) failed: %m"); process_using_priv_gid = FALSE; } bool restrict_access_have_priv_gid(void) { return process_privileged_gid != (gid_t)-1; } void restrict_access_deinit(void) { i_free(chroot_dir); } dovecot-2.3.21.1/src/lib/safe-mkdir.c0000644000000000000000000000400214656633576014034 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "safe-mkdir.h" #include #include #include int safe_mkdir(const char *dir, mode_t mode, uid_t uid, gid_t gid) { struct stat st; int fd, ret = 2, changed_ret = 0; if (lstat(dir, &st) < 0) { if (errno != ENOENT) i_fatal("lstat() failed for %s: %m", dir); if (mkdir(dir, mode) < 0) { if (errno != EEXIST) i_fatal("Can't create directory %s: %m", dir); } else { /* created it */ ret = changed_ret = 1; } } /* use fchown() and fchmod() just to make sure we aren't following symbolic links. */ fd = open(dir, O_RDONLY); if (fd == -1) i_fatal("open() failed for %s: %m", dir); if (fstat(fd, &st) < 0) i_fatal("fstat() failed for %s: %m", dir); if (!S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)) i_fatal("Not a directory %s", dir); /* change the file owner first, since it's the only user one who can mess up with the file mode. */ if ((st.st_uid != uid && uid != (uid_t)-1) || (st.st_gid != gid && gid != (gid_t)-1)) { if (fchown(fd, uid, gid) < 0) i_fatal("fchown() failed for %s: %m", dir); ret = changed_ret; } if ((st.st_mode & 07777) != mode) { if (fchmod(fd, mode) < 0) i_fatal("chmod() failed for %s: %m", dir); ret = changed_ret; } if (close(fd) < 0) i_fatal("close() failed for %s: %m", dir); /* paranoia: make sure we succeeded in everything. */ if (lstat(dir, &st) < 0) i_fatal("lstat() check failed for %s: %m", dir); if (!S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)) i_fatal("Not a directory %s", dir); if ((st.st_mode & 07777) != mode) { i_fatal("safe_mkdir() failed: %s (%o) is still not mode %o", dir, (int)st.st_mode, (int)mode); } if ((st.st_uid != uid && uid != (uid_t)-1) || (st.st_gid != gid && gid != (gid_t)-1)) { i_fatal("safe_mkdir() failed: %s (%s, %s) " "is still not owned by %s.%s", dir, dec2str(st.st_uid), dec2str(st.st_gid), dec2str(uid), dec2str(gid)); } return ret; } dovecot-2.3.21.1/src/lib/istream-unix.h0000644000000000000000000000121314656633576014445 00000000000000#ifndef ISTREAM_UNIX_H #define ISTREAM_UNIX_H struct istream *i_stream_create_unix(int fd, size_t max_buffer_size); /* Start trying to read a file descriptor from the UNIX socket. */ void i_stream_unix_set_read_fd(struct istream *input); /* Stop trying to read a file descriptor from the UNIX socket. */ void i_stream_unix_unset_read_fd(struct istream *input); /* Returns the fd that the last i_stream_read() received, or -1 if no fd was received. This function must be called before i_stream_unix_set_read_fd() is called again after successfully receiving a file descriptor. */ int i_stream_unix_get_read_fd(struct istream *input); #endif dovecot-2.3.21.1/src/lib/seq-set-builder.h0000644000000000000000000000130314656633576015025 00000000000000#ifndef SEQ_SET_BUILDER_H #define SEQ_SET_BUILDER_H /* Append a seqset to the given string. */ struct seqset_builder *seqset_builder_init(string_t *str); /* Add seq to the string. The string must not have been modified before the previous seqset_builder_add() call, since the last sequence in it may be rewritten. */ void seqset_builder_add(struct seqset_builder *builder, uint32_t seq); /* Add the seq to the string, but only if the string length stays below max_len. Returns TRUE if added, FALSE if not. */ bool seqset_builder_try_add(struct seqset_builder *builder, size_t max_len, uint32_t seq); /* Deinitialize the builder */ void seqset_builder_deinit(struct seqset_builder **builder); #endif dovecot-2.3.21.1/src/lib/strnum.h0000644000000000000000000001476014656633576013363 00000000000000#ifndef STRNUM_H #define STRNUM_H /* str_to_*() functions return 0 if string is nothing more than a valid number in valid range. Otherwise -1 is returned and num_r is left untouched str_parse_*() helpers do not require the number to be the entire string and pass back the pointer just past a valid parsed integer in endp_r if it is non-NULL. What is written to endp_r in error cases is undefined. */ /* * Unsigned decimal */ int str_to_uint(const char *str, unsigned int *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_uint(const char *str, unsigned int *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_ulong(const char *str, unsigned long *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_ulong(const char *str, unsigned long *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_ullong(const char *str, unsigned long long *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_ullong(const char *str, unsigned long long *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_uint32(const char *str, uint32_t *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_uint32(const char *str, uint32_t *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_uint64(const char *str, uint64_t *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_uint64(const char *str, uint64_t *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_uintmax(const char *str, uintmax_t *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_uintmax(const char *str, uintmax_t *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); /* Returns TRUE if str is a valid unsigned number that equals to num. */ bool str_uint_equals(const char *str, uintmax_t num); /* * Unsigned hexadecimal */ int str_to_uint_hex(const char *str, unsigned int *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_uint_hex(const char *str, unsigned int *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_ulong_hex(const char *str, unsigned long *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_ulong_hex(const char *str, unsigned long *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_ullong_hex(const char *str, unsigned long long *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_ullong_hex(const char *str, unsigned long long *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_uint32_hex(const char *str, uint32_t *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_uint32_hex(const char *str, uint32_t *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_uint64_hex(const char *str, uint64_t *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_uint64_hex(const char *str, uint64_t *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_uintmax_hex(const char *str, uintmax_t *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_uintmax_hex(const char *str, uintmax_t *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); /* * Unsigned octal */ int str_to_uint_oct(const char *str, unsigned int *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_uint_oct(const char *str, unsigned int *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_ulong_oct(const char *str, unsigned long *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_ulong_oct(const char *str, unsigned long *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_ullong_oct(const char *str, unsigned long long *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_ullong_oct(const char *str, unsigned long long *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_uint32_oct(const char *str, uint32_t *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_uint32_oct(const char *str, uint32_t *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_uint64_oct(const char *str, uint64_t *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_uint64_oct(const char *str, uint64_t *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_uintmax_oct(const char *str, uintmax_t *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_uintmax_oct(const char *str, uintmax_t *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); /* * Signed */ int str_to_int(const char *str, int *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_int(const char *str, int *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_long(const char *str, long *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_long(const char *str, long *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_llong(const char *str, long long *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_llong(const char *str, long long *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_int32(const char *str, int32_t *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_int32(const char *str, int32_t *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_int64(const char *str, int64_t *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_int64(const char *str, int64_t *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_intmax(const char *str, intmax_t *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_intmax(const char *str, intmax_t *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); /* * Special numeric types */ int str_to_uid(const char *str, uid_t *num_r) ATTR_WARN_UNUSED_RESULT; int str_to_gid(const char *str, gid_t *num_r) ATTR_WARN_UNUSED_RESULT; int str_to_pid(const char *str, pid_t *num_r) ATTR_WARN_UNUSED_RESULT; int str_to_ino(const char *str, ino_t *num_r) ATTR_WARN_UNUSED_RESULT; int str_to_uoff(const char *str, uoff_t *num_r) ATTR_WARN_UNUSED_RESULT; int str_parse_uoff(const char *str, uoff_t *num_r, const char **endp_r) ATTR_WARN_UNUSED_RESULT ATTR_NULL(3); int str_to_time(const char *str, time_t *num_r) ATTR_WARN_UNUSED_RESULT; /* * Utility */ /* Return TRUE if all characters in string are numbers. Stop when `end_char' is found from string. */ bool str_is_numeric(const char *str, char end_char) ATTR_PURE; /* Return TRUE when string has one or more numbers, followed with zero or one dot, followed with at least one number. */ bool str_is_float(const char *str, char end_char) ATTR_PURE; /* Returns human readable string about what is wrong with the string. This function assumes that str_to_*() had already returned -1 for the string. */ const char *str_num_error(const char *str); #endif dovecot-2.3.21.1/src/lib/stats-dist.h0000644000000000000000000000315314656633576014124 00000000000000#ifndef STATS_DIST_H #define STATS_DIST_H struct stats_dist *stats_dist_init(void); struct stats_dist *stats_dist_init_with_size(unsigned int sample_count); void stats_dist_deinit(struct stats_dist **stats); /* Reset all events. */ void stats_dist_reset(struct stats_dist *stats); /* Add a new event. */ void stats_dist_add(struct stats_dist *stats, uint64_t value); /* Returns number of events added. */ unsigned int stats_dist_get_count(const struct stats_dist *stats); /* Returns the sum of all events. */ uint64_t stats_dist_get_sum(const struct stats_dist *stats); /* Returns events' minimum. */ uint64_t stats_dist_get_min(const struct stats_dist *stats); /* Returns events' maximum. */ uint64_t stats_dist_get_max(const struct stats_dist *stats); /* Returns events' average. */ double stats_dist_get_avg(const struct stats_dist *stats); /* Returns events' approximate (through random subsampling) median. */ uint64_t stats_dist_get_median(struct stats_dist *stats); /* Returns events' variance */ double stats_dist_get_variance(const struct stats_dist *stats); /* Returns events' approximate (through random subsampling) percentile. fraction parameter is in the range (0., 1.], so 95th %-ile is 0.95. */ uint64_t stats_dist_get_percentile(struct stats_dist *stats, double fraction); /* Returns events' approximate (through random subsampling) 95th percentile. */ static inline uint64_t stats_dist_get_95th(struct stats_dist *stats) { return stats_dist_get_percentile(stats, 0.95); } /* Returns the sample array */ const uint64_t *stats_dist_get_samples(const struct stats_dist *stats, unsigned int *count_r); #endif dovecot-2.3.21.1/src/lib/safe-memset.h0000644000000000000000000000036014656633576014230 00000000000000#ifndef SAFE_MEMSET_H #define SAFE_MEMSET_H /* memset() guaranteed not to get optimized away by compiler. Should be used instead of memset() when clearing any sensitive data. */ void safe_memset(void *data, int c, size_t size); #endif dovecot-2.3.21.1/src/lib/ioloop-notify-fd.c0000644000000000000000000000212714656633576015216 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop-private.h" #include "ioloop-notify-fd.h" #if defined(IOLOOP_NOTIFY_INOTIFY) struct io *io_notify_fd_add(struct ioloop_notify_fd_context *ctx, int fd, io_callback_t *callback, void *context) { struct io_notify *io; io = i_new(struct io_notify, 1); io->io.condition = IO_NOTIFY; io->io.callback = callback; io->io.context = context; io->io.ioloop = current_ioloop; io->fd = fd; if (ctx->notifies != NULL) { ctx->notifies->prev = io; io->next = ctx->notifies; } ctx->notifies = io; return &io->io; } void io_notify_fd_free(struct ioloop_notify_fd_context *ctx, struct io_notify *io) { if (io->prev != NULL) io->prev->next = io->next; else ctx->notifies = io->next; if (io->next != NULL) io->next->prev = io->prev; i_free(io); } struct io_notify * io_notify_fd_find(struct ioloop_notify_fd_context *ctx, int fd) { struct io_notify *io; for (io = ctx->notifies; io != NULL; io = io->next) { if (io->fd == fd) return io; } return NULL; } #endif dovecot-2.3.21.1/src/lib/event-filter.c0000644000000000000000000005735314656633576014437 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "llist.h" #include "str.h" #include "strescape.h" #include "wildcard-match.h" #include "lib-event-private.h" #include "event-filter.h" #include "event-filter-private.h" /* Note: this has to match the regexp behavior in the event filter lexer file */ #define event_filter_append_escaped(dst, str) \ str_append_escaped((dst), (str), strlen(str)) enum event_filter_code { EVENT_FILTER_CODE_NAME = 'n', EVENT_FILTER_CODE_SOURCE = 's', EVENT_FILTER_CODE_CATEGORY = 'c', EVENT_FILTER_CODE_FIELD = 'f', }; /* map to */ static const struct log_type_map { enum event_filter_log_type log_type; const char *name; } event_filter_log_type_map[] = { [LOG_TYPE_DEBUG] = { EVENT_FILTER_LOG_TYPE_DEBUG, "debug" }, [LOG_TYPE_INFO] = { EVENT_FILTER_LOG_TYPE_INFO, "info" }, [LOG_TYPE_WARNING] = { EVENT_FILTER_LOG_TYPE_WARNING, "warning" }, [LOG_TYPE_ERROR] = { EVENT_FILTER_LOG_TYPE_ERROR, "error" }, [LOG_TYPE_FATAL] = { EVENT_FILTER_LOG_TYPE_FATAL, "fatal" }, [LOG_TYPE_PANIC] = { EVENT_FILTER_LOG_TYPE_PANIC, "panic" }, }; struct event_filter_query_internal { struct event_filter_node *expr; void *context; }; static struct event_filter *event_filters = NULL; static struct event_filter *event_filter_create_real(pool_t pool, bool fragment) { struct event_filter *filter; filter = p_new(pool, struct event_filter, 1); filter->pool = pool; filter->refcount = 1; filter->named_queries_only = TRUE; filter->fragment = fragment; p_array_init(&filter->queries, pool, 4); if (!fragment) DLLIST_PREPEND(&event_filters, filter); return filter; } struct event_filter *event_filter_create(void) { return event_filter_create_real(pool_alloconly_create("event filter", 2048), FALSE); } struct event_filter *event_filter_create_fragment(pool_t pool) { return event_filter_create_real(pool, TRUE); } void event_filter_ref(struct event_filter *filter) { i_assert(filter->refcount > 0); filter->refcount++; } void event_filter_unref(struct event_filter **_filter) { struct event_filter *filter = *_filter; if (filter == NULL) return; i_assert(filter->refcount > 0); *_filter = NULL; if (--filter->refcount > 0) return; if (!filter->fragment) { DLLIST_REMOVE(&event_filters, filter); /* fragments' pools are freed by the consumer */ pool_unref(&filter->pool); } } /* * Look for an existing query with the same context pointer and return it. * * If not found, allocate a new internal query and return it. */ static struct event_filter_query_internal * event_filter_get_or_alloc_internal_query(struct event_filter *filter, void *context) { struct event_filter_query_internal *query; array_foreach_modifiable(&filter->queries, query) { if (query->context == context) return query; } /* no matching context, allocate a new query */ query = array_append_space(&filter->queries); query->context = context; query->expr = NULL; return query; } static void add_node(pool_t pool, struct event_filter_node **root, struct event_filter_node *new, enum event_filter_node_op op) { struct event_filter_node *parent; i_assert((op == EVENT_FILTER_OP_AND) || (op == EVENT_FILTER_OP_OR)); if (*root == NULL) { *root = new; return; } parent = p_new(pool, struct event_filter_node, 1); parent->type = EVENT_FILTER_NODE_TYPE_LOGIC; parent->op = op; parent->children[0] = *root; parent->children[1] = new; *root = parent; } static bool filter_node_requires_event_name(struct event_filter_node *node) { switch (node->op) { case EVENT_FILTER_OP_NOT: return filter_node_requires_event_name(node->children[0]); case EVENT_FILTER_OP_AND: return filter_node_requires_event_name(node->children[0]) || filter_node_requires_event_name(node->children[1]); case EVENT_FILTER_OP_OR: return filter_node_requires_event_name(node->children[0]) && filter_node_requires_event_name(node->children[1]); default: return node->type == EVENT_FILTER_NODE_TYPE_EVENT_NAME_WILDCARD || node->type == EVENT_FILTER_NODE_TYPE_EVENT_NAME_EXACT; } } int event_filter_parse(const char *str, struct event_filter *filter, const char **error_r) { struct event_filter_query_internal *int_query; struct event_filter_parser_state state; int ret; i_zero(&state); state.input = str; state.len = strlen(str); state.pos = 0; state.pool = filter->pool; event_filter_parser_lex_init(&state.scanner); event_filter_parser_set_extra(&state, state.scanner); ret = event_filter_parser_parse(&state); event_filter_parser_lex_destroy(state.scanner); if ((ret == 0) && (state.output != NULL)) { /* success - non-NULL expression */ i_assert(state.error == NULL); int_query = event_filter_get_or_alloc_internal_query(filter, NULL); add_node(filter->pool, &int_query->expr, state.output, EVENT_FILTER_OP_OR); filter->named_queries_only = filter->named_queries_only && filter_node_requires_event_name(state.output); } else if (ret != 0) { /* error */ i_assert(state.error != NULL); *error_r = state.error; } /* * Note that success with a NULL expression output is possible, but * turns into a no-op. */ return (ret != 0) ? -1 : 0; } bool event_filter_category_to_log_type(const char *name, enum event_filter_log_type *log_type_r) { unsigned int i; for (i = 0; i < N_ELEMENTS(event_filter_log_type_map); i++) { if (strcmp(name, event_filter_log_type_map[i].name) == 0) { *log_type_r = event_filter_log_type_map[i].log_type; return TRUE; } } return FALSE; } const char * event_filter_category_from_log_type(enum event_filter_log_type log_type) { unsigned int i; for (i = 0; i < N_ELEMENTS(event_filter_log_type_map); i++) { if (event_filter_log_type_map[i].log_type == log_type) return event_filter_log_type_map[i].name; } i_unreached(); } static struct event_filter_node * clone_expr(pool_t pool, struct event_filter_node *old) { struct event_filter_node *new; if (old == NULL) return NULL; new = p_new(pool, struct event_filter_node, 1); new->type = old->type; new->op = old->op; new->children[0] = clone_expr(pool, old->children[0]); new->children[1] = clone_expr(pool, old->children[1]); new->str = p_strdup(pool, old->str); new->intmax = old->intmax; new->category.log_type = old->category.log_type; new->category.name = p_strdup(pool, old->category.name); new->category.ptr = old->category.ptr; new->field.key = p_strdup(pool, old->field.key); new->field.value_type = old->field.value_type; new->field.value.str = p_strdup(pool, old->field.value.str); new->field.value.intmax = old->field.value.intmax; new->field.value.timeval = old->field.value.timeval; return new; } static void event_filter_merge_with_context_internal(struct event_filter *dest, const struct event_filter *src, void *new_context, bool with_context) { const struct event_filter_query_internal *int_query; array_foreach(&src->queries, int_query) T_BEGIN { void *context = with_context ? new_context : int_query->context; struct event_filter_query_internal *new; new = event_filter_get_or_alloc_internal_query(dest, context); add_node(dest->pool, &new->expr, clone_expr(dest->pool, int_query->expr), EVENT_FILTER_OP_OR); } T_END; } bool event_filter_remove_queries_with_context(struct event_filter *filter, void *context) { const struct event_filter_query_internal *int_query; unsigned int idx; array_foreach(&filter->queries, int_query) { if (int_query->context == context) { idx = array_foreach_idx(&filter->queries, int_query); array_delete(&filter->queries, idx, 1); return TRUE; } } return FALSE; } void event_filter_merge(struct event_filter *dest, const struct event_filter *src) { event_filter_merge_with_context_internal(dest, src, NULL, FALSE); } void event_filter_merge_with_context(struct event_filter *dest, const struct event_filter *src, void *new_context) { event_filter_merge_with_context_internal(dest, src, new_context, TRUE); } static const char * event_filter_export_query_expr_op(enum event_filter_node_op op) { switch (op) { case EVENT_FILTER_OP_AND: case EVENT_FILTER_OP_OR: case EVENT_FILTER_OP_NOT: i_unreached(); case EVENT_FILTER_OP_CMP_EQ: return "="; case EVENT_FILTER_OP_CMP_GT: return ">"; case EVENT_FILTER_OP_CMP_LT: return "<"; case EVENT_FILTER_OP_CMP_GE: return ">="; case EVENT_FILTER_OP_CMP_LE: return "<="; } i_unreached(); } static void event_filter_export_query_expr(const struct event_filter_query_internal *query, struct event_filter_node *node, string_t *dest) { switch (node->type) { case EVENT_FILTER_NODE_TYPE_LOGIC: str_append_c(dest, '('); switch (node->op) { case EVENT_FILTER_OP_AND: event_filter_export_query_expr(query, node->children[0], dest); str_append(dest, " AND "); event_filter_export_query_expr(query, node->children[1], dest); break; case EVENT_FILTER_OP_OR: event_filter_export_query_expr(query, node->children[0], dest); str_append(dest, " OR "); event_filter_export_query_expr(query, node->children[1], dest); break; case EVENT_FILTER_OP_NOT: str_append(dest, "NOT "); event_filter_export_query_expr(query, node->children[0], dest); break; case EVENT_FILTER_OP_CMP_EQ: case EVENT_FILTER_OP_CMP_GT: case EVENT_FILTER_OP_CMP_LT: case EVENT_FILTER_OP_CMP_GE: case EVENT_FILTER_OP_CMP_LE: i_unreached(); } str_append_c(dest, ')'); break; case EVENT_FILTER_NODE_TYPE_EVENT_NAME_EXACT: case EVENT_FILTER_NODE_TYPE_EVENT_NAME_WILDCARD: str_append(dest, "event"); str_append(dest, event_filter_export_query_expr_op(node->op)); str_append_c(dest, '"'); event_filter_append_escaped(dest, node->str); str_append_c(dest, '"'); break; case EVENT_FILTER_NODE_TYPE_EVENT_SOURCE_LOCATION: str_append(dest, "source_location"); str_append(dest, event_filter_export_query_expr_op(node->op)); str_append_c(dest, '"'); event_filter_append_escaped(dest, node->str); if (node->intmax != 0) str_printfa(dest, ":%ju", node->intmax); str_append_c(dest, '"'); break; case EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY: str_append(dest, "category"); str_append(dest, event_filter_export_query_expr_op(node->op)); if (node->category.name != NULL) { str_append_c(dest, '"'); event_filter_append_escaped(dest, node->category.name); str_append_c(dest, '"'); } else str_append(dest, event_filter_category_from_log_type(node->category.log_type)); break; case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_EXACT: case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_WILDCARD: str_append_c(dest, '"'); event_filter_append_escaped(dest, node->field.key); str_append_c(dest, '"'); str_append(dest, event_filter_export_query_expr_op(node->op)); str_append_c(dest, '"'); event_filter_append_escaped(dest, node->field.value.str); str_append_c(dest, '"'); break; } } static void event_filter_export_query(const struct event_filter_query_internal *query, string_t *dest) { str_append_c(dest, '('); event_filter_export_query_expr(query, query->expr, dest); str_append_c(dest, ')'); } void event_filter_export(struct event_filter *filter, string_t *dest) { const struct event_filter_query_internal *query; bool first = TRUE; array_foreach(&filter->queries, query) { if (!first) str_append(dest, " OR "); first = FALSE; event_filter_export_query(query, dest); } } struct event_filter_node * event_filter_get_expr_for_testing(struct event_filter *filter, unsigned int *count_r) { const struct event_filter_query_internal *queries; queries = array_get(&filter->queries, count_r); return (*count_r == 0) ? NULL : queries[0].expr; } static bool event_category_match(const struct event_category *category, const struct event_category *wanted_category) { for (; category != NULL; category = category->parent) { if (category->internal == wanted_category->internal) return TRUE; } return FALSE; } static bool event_has_category_nonrecursive(struct event *event, struct event_category *wanted_category) { struct event_category *cat; if (array_is_created(&event->categories)) { array_foreach_elem(&event->categories, cat) { if (event_category_match(cat, wanted_category)) return TRUE; } } return FALSE; } static bool event_has_category(struct event *event, struct event_filter_node *node, enum event_filter_log_type log_type) { struct event_category *wanted_category = node->category.ptr; /* category is a log type */ if (node->category.name == NULL) return (node->category.log_type & log_type) != 0; /* category not registered, therefore the event cannot have it */ if (wanted_category == NULL) return FALSE; while (event != NULL) { if (event_has_category_nonrecursive(event, wanted_category)) return TRUE; /* try also the parent events */ event = event_get_parent(event); } /* check also the global event and its parents */ event = event_get_global(); while (event != NULL) { if (event_has_category_nonrecursive(event, wanted_category)) return TRUE; event = event_get_parent(event); } return FALSE; } static bool event_match_strlist_recursive(struct event *event, const struct event_field *wanted_field, bool use_strcmp, bool *seen) { const char *wanted_value = wanted_field->value.str; const struct event_field *field; const char *value; bool match; if (event == NULL) return FALSE; field = event_find_field_nonrecursive(event, wanted_field->key); if (field != NULL) { i_assert(field->value_type == EVENT_FIELD_VALUE_TYPE_STRLIST); array_foreach_elem(&field->value.strlist, value) { *seen = TRUE; match = use_strcmp ? strcmp(value, wanted_value) == 0 : wildcard_match_icase(value, wanted_value); if (match) return TRUE; } } return event_match_strlist_recursive(event->parent, wanted_field, use_strcmp, seen); } static bool event_match_strlist(struct event *event, const struct event_field *wanted_field, bool use_strcmp) { bool seen = FALSE; if (event_match_strlist_recursive(event, wanted_field, use_strcmp, &seen)) return TRUE; if (event_match_strlist_recursive(event_get_global(), wanted_field, use_strcmp, &seen)) return TRUE; if (wanted_field->value.str[0] == '\0' && !seen) { /* strlist="" matches nonexistent strlist */ return TRUE; } return FALSE; } static bool event_match_field(struct event *event, const struct event_field *wanted_field, enum event_filter_node_op op, bool use_strcmp) { const struct event_field *field; /* wanted_field has the value in all available formats */ field = event_find_field_recursive(event, wanted_field->key); if (field == NULL) { /* field="" matches nonexistent field */ return wanted_field->value.str[0] == '\0'; } switch (field->value_type) { case EVENT_FIELD_VALUE_TYPE_STR: if (op != EVENT_FILTER_OP_CMP_EQ) { /* we only support string equality comparisons */ return FALSE; } if (field->value.str[0] == '\0') { /* field was removed, but it matches field="" filter */ return wanted_field->value.str[0] == '\0'; } if (use_strcmp) return strcasecmp(field->value.str, wanted_field->value.str) == 0; else return wildcard_match_icase(field->value.str, wanted_field->value.str); case EVENT_FIELD_VALUE_TYPE_INTMAX: if (wanted_field->value.intmax > INT_MIN) { /* compare against an integer */ switch (op) { case EVENT_FILTER_OP_CMP_EQ: return field->value.intmax == wanted_field->value.intmax; case EVENT_FILTER_OP_CMP_GT: return field->value.intmax > wanted_field->value.intmax; case EVENT_FILTER_OP_CMP_LT: return field->value.intmax < wanted_field->value.intmax; case EVENT_FILTER_OP_CMP_GE: return field->value.intmax >= wanted_field->value.intmax; case EVENT_FILTER_OP_CMP_LE: return field->value.intmax <= wanted_field->value.intmax; case EVENT_FILTER_OP_AND: case EVENT_FILTER_OP_OR: case EVENT_FILTER_OP_NOT: i_unreached(); } i_unreached(); } else { /* compare against an "integer" with wildcards */ if (op != EVENT_FILTER_OP_CMP_EQ) { /* we only support string equality comparisons */ return FALSE; } char tmp[MAX_INT_STRLEN]; i_snprintf(tmp, sizeof(tmp), "%jd", field->value.intmax); if (use_strcmp) return strcasecmp(field->value.str, wanted_field->value.str) == 0; else return wildcard_match_icase(tmp, wanted_field->value.str); } case EVENT_FIELD_VALUE_TYPE_TIMEVAL: /* there's no point to support matching exact timestamps */ return FALSE; case EVENT_FIELD_VALUE_TYPE_STRLIST: /* check if the value is (or is not) on the list, only string matching makes sense here. */ if (op != EVENT_FILTER_OP_CMP_EQ) return FALSE; return event_match_strlist(event, wanted_field, use_strcmp); } i_unreached(); } static bool event_filter_query_match_cmp(struct event_filter_node *node, struct event *event, const char *source_filename, unsigned int source_linenum, enum event_filter_log_type log_type) { i_assert((node->op == EVENT_FILTER_OP_CMP_EQ) || (node->op == EVENT_FILTER_OP_CMP_GT) || (node->op == EVENT_FILTER_OP_CMP_LT) || (node->op == EVENT_FILTER_OP_CMP_GE) || (node->op == EVENT_FILTER_OP_CMP_LE)); switch (node->type) { case EVENT_FILTER_NODE_TYPE_LOGIC: i_unreached(); case EVENT_FILTER_NODE_TYPE_EVENT_NAME_EXACT: return (event->sending_name != NULL) && strcmp(event->sending_name, node->str) == 0; case EVENT_FILTER_NODE_TYPE_EVENT_NAME_WILDCARD: return (event->sending_name != NULL) && wildcard_match(event->sending_name, node->str); case EVENT_FILTER_NODE_TYPE_EVENT_SOURCE_LOCATION: return !((source_linenum != node->intmax && node->intmax != 0) || source_filename == NULL || strcmp(event->source_filename, node->str) != 0); case EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY: return event_has_category(event, node, log_type); case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_EXACT: return event_match_field(event, &node->field, node->op, TRUE); case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_WILDCARD: return event_match_field(event, &node->field, node->op, FALSE); } i_unreached(); } bool event_filter_query_match_eval(struct event_filter_node *node, struct event *event, const char *source_filename, unsigned int source_linenum, enum event_filter_log_type log_type) { switch (node->op) { case EVENT_FILTER_OP_CMP_EQ: case EVENT_FILTER_OP_CMP_GT: case EVENT_FILTER_OP_CMP_LT: case EVENT_FILTER_OP_CMP_GE: case EVENT_FILTER_OP_CMP_LE: return event_filter_query_match_cmp(node, event, source_filename, source_linenum, log_type); case EVENT_FILTER_OP_AND: return event_filter_query_match_eval(node->children[0], event, source_filename, source_linenum, log_type) && event_filter_query_match_eval(node->children[1], event, source_filename, source_linenum, log_type); case EVENT_FILTER_OP_OR: return event_filter_query_match_eval(node->children[0], event, source_filename, source_linenum, log_type) || event_filter_query_match_eval(node->children[1], event, source_filename, source_linenum, log_type); case EVENT_FILTER_OP_NOT: return !event_filter_query_match_eval(node->children[0], event, source_filename, source_linenum, log_type); } i_unreached(); } static bool event_filter_query_match(const struct event_filter_query_internal *query, struct event *event, const char *source_filename, unsigned int source_linenum, const struct failure_context *ctx) { enum event_filter_log_type log_type; i_assert(ctx->type < N_ELEMENTS(event_filter_log_type_map)); log_type = event_filter_log_type_map[ctx->type].log_type; return event_filter_query_match_eval(query->expr, event, source_filename, source_linenum, log_type); } static bool event_filter_match_fastpath(struct event_filter *filter, struct event *event) { if (filter->named_queries_only && event->sending_name == NULL) { /* No debug logging is enabled. Only named events may be wanted for stats. This event doesn't have a name, so we don't need to check any further. */ return FALSE; } return TRUE; } bool event_filter_match(struct event_filter *filter, struct event *event, const struct failure_context *ctx) { if (filter == NULL) return FALSE; return event_filter_match_source(filter, event, event->source_filename, event->source_linenum, ctx); } bool event_filter_match_source(struct event_filter *filter, struct event *event, const char *source_filename, unsigned int source_linenum, const struct failure_context *ctx) { const struct event_filter_query_internal *query; i_assert(!filter->fragment); if (!event_filter_match_fastpath(filter, event)) return FALSE; array_foreach(&filter->queries, query) { if (event_filter_query_match(query, event, source_filename, source_linenum, ctx)) return TRUE; } return FALSE; } struct event_filter_match_iter { struct event_filter *filter; struct event *event; const struct failure_context *failure_ctx; unsigned int idx; }; struct event_filter_match_iter * event_filter_match_iter_init(struct event_filter *filter, struct event *event, const struct failure_context *ctx) { struct event_filter_match_iter *iter; i_assert(!filter->fragment); iter = i_new(struct event_filter_match_iter, 1); iter->filter = filter; iter->event = event; iter->failure_ctx = ctx; if (!event_filter_match_fastpath(filter, event)) iter->idx = UINT_MAX; return iter; } void *event_filter_match_iter_next(struct event_filter_match_iter *iter) { const struct event_filter_query_internal *queries; unsigned int count; queries = array_get(&iter->filter->queries, &count); while (iter->idx < count) { const struct event_filter_query_internal *query = &queries[iter->idx]; iter->idx++; if (query->context != NULL && event_filter_query_match(query, iter->event, iter->event->source_filename, iter->event->source_linenum, iter->failure_ctx)) return query->context; } return NULL; } void event_filter_match_iter_deinit(struct event_filter_match_iter **_iter) { struct event_filter_match_iter *iter = *_iter; *_iter = NULL; i_free(iter); } static void event_filter_query_update_category(struct event_filter_query_internal *query, struct event_filter_node *node, struct event_category *category, bool add) { if (node == NULL) return; switch (node->type) { case EVENT_FILTER_NODE_TYPE_LOGIC: event_filter_query_update_category(query, node->children[0], category, add); event_filter_query_update_category(query, node->children[1], category, add); break; case EVENT_FILTER_NODE_TYPE_EVENT_NAME_EXACT: case EVENT_FILTER_NODE_TYPE_EVENT_NAME_WILDCARD: case EVENT_FILTER_NODE_TYPE_EVENT_SOURCE_LOCATION: case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_EXACT: case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_WILDCARD: break; case EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY: if (node->category.name == NULL) break; /* log type */ if (add) { if (node->category.ptr != NULL) break; if (strcmp(node->category.name, category->name) == 0) node->category.ptr = category; } else { if (node->category.ptr == category) node->category.ptr = NULL; } break; } } static void event_filter_category_registered(struct event_category *category) { const bool add = category->internal != NULL; struct event_filter_query_internal *query; struct event_filter *filter; for (filter = event_filters; filter != NULL; filter = filter->next) { array_foreach_modifiable(&filter->queries, query) { event_filter_query_update_category(query, query->expr, category, add); } } } void event_filter_init(void) { event_category_register_callback(event_filter_category_registered); } void event_filter_deinit(void) { event_category_unregister_callback(event_filter_category_registered); } dovecot-2.3.21.1/src/lib/ostream-null.c0000644000000000000000000000143414656633576014442 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ostream-private.h" #include "ostream-null.h" static ssize_t o_stream_null_sendv(struct ostream_private *stream, const struct const_iovec *iov, unsigned int iov_count) { unsigned int i; size_t ret = 0; for (i = 0; i < iov_count; i++) ret += iov[i].iov_len; stream->ostream.offset += ret; return ret; } struct ostream *o_stream_create_null(void) { struct ostream_private *stream; struct ostream *output; stream = i_new(struct ostream_private, 1); stream->ostream.blocking = TRUE; stream->sendv = o_stream_null_sendv; output = o_stream_create(stream, NULL, -1); o_stream_set_no_error_handling(output, TRUE); o_stream_set_name(output, "(/dev/null)"); return output; } dovecot-2.3.21.1/src/lib/hash2.h0000644000000000000000000000400114656633576013023 00000000000000#ifndef HASH2_H #define HASH2_H #include "hash.h" struct hash2_iter { struct hash2_value *value, *next_value; unsigned int key_hash; }; /* Returns hash code for the key. */ typedef unsigned int hash2_key_callback_t(const void *key); /* Returns TRUE if the key matches the value. */ typedef bool hash2_cmp_callback_t(const void *key, const void *value, void *context); /* Create a new hash table. If initial_size is 0, the default value is used. */ struct hash2_table * hash2_create(unsigned int initial_size, unsigned int value_size, hash2_key_callback_t *key_hash_cb, hash2_cmp_callback_t *key_compare_cb, void *context) ATTR_NULL(5); void hash2_destroy(struct hash2_table **hash); /* Remove all nodes from hash table. */ void hash2_clear(struct hash2_table *hash); void *hash2_lookup(const struct hash2_table *hash, const void *key) ATTR_PURE; /* Iterate through all nodes with the given hash. iter must initially be zero-filled. */ void *hash2_iterate(const struct hash2_table *hash, unsigned int key_hash, struct hash2_iter *iter); /* Insert node to the hash table and returns pointer to the value that can be written to. Assumes it doesn't already exist (or that a duplicate entry is wanted). */ void *hash2_insert(struct hash2_table *hash, const void *key); /* Like hash2_insert(), but insert directly using a hash. */ void *hash2_insert_hash(struct hash2_table *hash, unsigned int key_hash); /* Remove a node. */ void hash2_remove(struct hash2_table *hash, const void *key); /* Remove the last node iterator returned. Iterating continues from the next node. */ void hash2_remove_iter(struct hash2_table *hash, struct hash2_iter *iter); /* Return the number of nodes in hash table. */ unsigned int hash2_count(const struct hash2_table *hash) ATTR_PURE; /* can be used with string keys */ static inline bool hash2_strcmp(const void *a, const void *b, void *ctx ATTR_UNUSED) { return strcmp(a, b) == 0; } static inline unsigned int hash2_str_hash(const void *key) { return str_hash(key); } #endif dovecot-2.3.21.1/src/lib/istream-callback.c0000644000000000000000000000617014656633576015220 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "istream-private.h" #include "istream-callback.h" struct callback_istream { struct istream_private istream; istream_callback_read_t *callback; void *context; buffer_t *buf; size_t prev_pos; }; static void i_stream_callback_destroy(struct iostream_private *stream) { struct callback_istream *cstream = container_of(stream, struct callback_istream, istream.iostream); buffer_free(&cstream->buf); } static ssize_t i_stream_callback_read(struct istream_private *stream) { struct callback_istream *cstream = container_of(stream, struct callback_istream, istream); size_t pos; if (cstream->callback == NULL) { /* already returned EOF / error */ stream->istream.eof = TRUE; return -1; } if (stream->skip > 0) { buffer_delete(cstream->buf, 0, stream->skip); stream->pos -= stream->skip; cstream->prev_pos -= stream->skip; stream->skip = 0; } i_assert(cstream->buf->used >= cstream->prev_pos); pos = cstream->prev_pos; if (cstream->buf->used > pos) { /* data was added outside the callback */ } else if (!cstream->callback(cstream->buf, cstream->context)) { /* EOF / error */ stream->istream.eof = TRUE; cstream->callback = NULL; if (cstream->buf->used == pos || stream->istream.stream_errno != 0) return -1; /* EOF was returned with some data still added to the buffer. return the buffer first and EOF only on the next call. */ } else if (cstream->buf->used == pos) { /* buffer full */ i_assert(cstream->buf->used > 0); return -2; } i_assert(cstream->buf->used > pos); stream->buffer = cstream->buf->data; cstream->prev_pos = stream->pos = cstream->buf->used; return cstream->buf->used - pos; } #undef i_stream_create_callback struct istream * i_stream_create_callback(istream_callback_read_t *callback, void *context) { struct callback_istream *cstream; struct istream *istream; i_assert(callback != NULL); cstream = i_new(struct callback_istream, 1); cstream->callback = callback; cstream->context = context; cstream->buf = buffer_create_dynamic(default_pool, 1024); cstream->istream.iostream.destroy = i_stream_callback_destroy; cstream->istream.read = i_stream_callback_read; istream = i_stream_create(&cstream->istream, NULL, -1, 0); istream->blocking = TRUE; return istream; } void i_stream_callback_append(struct istream *input, const void *data, size_t size) { struct callback_istream *cstream = container_of(input->real_stream, struct callback_istream, istream); buffer_append(cstream->buf, data, size); } void i_stream_callback_append_str(struct istream *input, const char *str) { i_stream_callback_append(input, str, strlen(str)); } buffer_t *i_stream_callback_get_buffer(struct istream *input) { struct callback_istream *cstream = container_of(input->real_stream, struct callback_istream, istream); return cstream->buf; } void i_stream_callback_set_error(struct istream *input, int stream_errno, const char *error) { input->stream_errno = stream_errno; io_stream_set_error(&input->real_stream->iostream, "%s", error); } dovecot-2.3.21.1/src/lib/test-imem.c0000644000000000000000000000313014656633576013717 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" struct test_struct { uint32_t num[10]; }; static void test_imem_alloc(void) { struct test_struct ab, bc, cd, de; test_begin("imem allocs"); memset(ab.num, 0xab, sizeof(ab.num)); memset(bc.num, 0xbc, sizeof(bc.num)); memset(cd.num, 0xcd, sizeof(cd.num)); memset(de.num, 0xde, sizeof(de.num)); /* regular alloc */ struct test_struct *s1 = i_new(struct test_struct, 2); struct test_struct *s2 = i_malloc(sizeof(struct test_struct) * 2); s1[0] = ab; s2[0] = ab; s1[1] = bc; s2[1] = bc; test_assert(memcmp(s1, s2, sizeof(struct test_struct) * 2) == 0); /* realloc */ s1 = i_realloc_type(s1, struct test_struct, 2, 4); s2 = i_realloc(s2, sizeof(struct test_struct) * 2, sizeof(struct test_struct) * 4); s1[2] = cd; s2[2] = cd; s1[3] = de; s2[3] = de; test_assert(memcmp(&s1[0], &ab, sizeof(ab)) == 0); test_assert(memcmp(&s1[1], &bc, sizeof(bc)) == 0); test_assert(memcmp(&s1[2], &cd, sizeof(cd)) == 0); test_assert(memcmp(&s1[3], &de, sizeof(de)) == 0); test_assert(memcmp(s1, s2, sizeof(struct test_struct) * 4) == 0); /* freeing realloced memory */ i_free(s1); i_free(s2); test_assert(s1 == NULL); test_assert(s2 == NULL); /* allcating new memory with realloc */ s1 = i_realloc_type(NULL, struct test_struct, 0, 2); s2 = i_realloc(NULL, 0, sizeof(struct test_struct) * 2); s1[0] = ab; s2[0] = ab; s1[1] = bc; s2[1] = bc; test_assert(memcmp(s1, s2, sizeof(struct test_struct) * 2) == 0); i_free(s1); i_free(s2); test_end(); } void test_imem(void) { test_imem_alloc(); } dovecot-2.3.21.1/src/lib/Makefile.am0000644000000000000000000002176114656633576013715 00000000000000AM_CPPFLAGS = \ $(LIBUNWIND_CFLAGS) noinst_LTLIBRARIES = liblib.la BUILT_SOURCES = $(srcdir)/unicodemap.c \ event-filter-lexer.c \ event-filter-parser.c \ event-filter-parser.h EXTRA_DIST = unicodemap.c unicodemap.pl UnicodeData.txt # Squelch autoconf error about using .[ly] sources but not defining $(LEX) # and $(YACC). Using false here avoids accidental use. LEX=/bin/false YACC=/bin/false # We use custom rules here because we want to use flex and bison instead # of lex and yacc (or bison in yacc-compatibility mode). Both flex and # bison can handle properly naming the generated files, and it is simpler # and cleaner to make this rule ourselves instead of working around ylwrap # and yywrap's antiquated notion of what is hapenning. .l.c: $(AM_V_GEN)$(FLEX) -o $@ $< .y.c: $(AM_V_GEN)$(BISON) -o $@ $< # Bison generates both a header and a .c file. Without the following # dependency, anything including the header will race the bison process. event-filter-parser.h: event-filter-parser.c $(srcdir)/UnicodeData.txt: test -f $@ || wget -O $@ https://dovecot.org/res/UnicodeData.txt $(srcdir)/unicodemap.c: $(srcdir)/unicodemap.pl $(srcdir)/UnicodeData.txt perl $(srcdir)/unicodemap.pl < $(srcdir)/UnicodeData.txt > $@ liblib_la_LIBADD = $(LIBUNWIND_LIBS) liblib_la_SOURCES = \ array.c \ aqueue.c \ askpass.c \ backtrace-string.c \ base32.c \ base64.c \ bits.c \ bsearch-insert-pos.c \ buffer.c \ buffer-istream.c \ child-wait.c \ compat.c \ connection.c \ cpu-limit.c \ crc32.c \ data-stack.c \ eacces-error.c \ env-util.c \ event-filter.c \ event-filter-lexer.l \ event-filter-parser.y \ event-log.c \ execv-const.c \ failures.c \ fd-util.c \ fdatasync-path.c \ fdpass.c \ file-cache.c \ file-create-locked.c \ file-copy.c \ file-dotlock.c \ file-lock.c \ file-set-size.c \ guid.c \ hash.c \ hash-format.c \ hash-method.c \ hash2.c \ hex-binary.c \ hex-dec.c \ hmac.c \ hmac-cram-md5.c \ home-expand.c \ hook-build.c \ hostpid.c \ imem.c \ ipwd.c \ iostream.c \ iostream-pump.c \ iostream-proxy.c \ iostream-rawlog.c \ iostream-temp.c \ iso8601-date.c \ istream.c \ istream-base64-decoder.c \ istream-base64-encoder.c \ istream-callback.c \ istream-chain.c \ istream-concat.c \ istream-crlf.c \ istream-data.c \ istream-failure-at.c \ istream-file.c \ istream-hash.c \ istream-jsonstr.c \ istream-limit.c \ istream-multiplex.c \ istream-rawlog.c \ istream-seekable.c \ istream-sized.c \ istream-tee.c \ istream-try.c \ istream-timeout.c \ istream-unix.c \ ioloop.c \ ioloop-iolist.c \ ioloop-notify-none.c \ ioloop-notify-fd.c \ ioloop-notify-inotify.c \ ioloop-notify-kqueue.c \ ioloop-poll.c \ ioloop-select.c \ ioloop-epoll.c \ ioloop-kqueue.c \ json-parser.c \ json-tree.c \ lib.c \ lib-event.c \ lib-signals.c \ log-throttle.c \ md4.c \ md5.c \ memarea.c \ mempool.c \ mempool-allocfree.c \ mempool-alloconly.c \ mempool-datastack.c \ mempool-system.c \ mempool-unsafe-datastack.c \ mkdir-parents.c \ mmap-anon.c \ mmap-util.c \ module-dir.c \ mountpoint.c \ net.c \ nfs-workarounds.c \ numpack.c \ ostream.c \ ostream-buffer.c \ ostream-failure-at.c \ ostream-file.c \ ostream-hash.c \ ostream-multiplex.c \ ostream-null.c \ ostream-rawlog.c \ ostream-unix.c \ ostream-wrapper.c \ path-util.c \ pkcs5.c \ primes.c \ printf-format-fix.c \ process-stat.c \ process-title.c \ priorityq.c \ randgen.c \ rand.c \ read-full.c \ restrict-access.c \ restrict-process-size.c \ safe-memset.c \ safe-mkdir.c \ safe-mkstemp.c \ sendfile-util.c \ seq-range-array.c \ seq-set-builder.c \ sha1.c \ sha2.c \ sha3.c \ sleep.c \ sort.c \ stats-dist.c \ str.c \ str-find.c \ str-sanitize.c \ str-table.c \ strescape.c \ strfuncs.c \ strnum.c \ time-util.c \ unix-socket-create.c \ unlink-directory.c \ unlink-old-files.c \ unichar.c \ uri-util.c \ utc-offset.c \ utc-mktime.c \ var-expand.c \ var-expand-if.c \ wildcard-match.c \ write-full.c headers = \ aqueue.h \ array.h \ array-decl.h \ askpass.h \ backtrace-string.h \ base32.h \ base64.h \ bits.h \ bsearch-insert-pos.h \ buffer.h \ byteorder.h \ child-wait.h \ compat.h \ connection.h \ cpu-limit.h \ crc32.h \ data-stack.h \ eacces-error.h \ env-util.h \ event-filter.h \ event-filter-parser.h \ event-filter-private.h \ event-log.h \ execv-const.h \ failures.h \ failures-private.h \ fd-util.h \ fdatasync-path.h \ fdpass.h \ file-cache.h \ file-create-locked.h \ file-copy.h \ file-dotlock.h \ file-lock.h \ file-set-size.h \ fsync-mode.h \ guid.h \ hash.h \ hash-decl.h \ hash-format.h \ hash-method.h \ hash2.h \ hex-binary.h \ hex-dec.h \ hmac.h \ hmac-cram-md5.h \ home-expand.h \ hook-build.h \ hostpid.h \ imem.h \ ipwd.h \ iostream.h \ iostream-private.h \ iostream-pump.h \ iostream-proxy.h \ iostream-rawlog.h \ iostream-rawlog-private.h \ iostream-temp.h \ iso8601-date.h \ istream.h \ istream-base64.h \ istream-callback.h \ istream-chain.h \ istream-concat.h \ istream-crlf.h \ istream-failure-at.h \ istream-file-private.h \ istream-hash.h \ istream-jsonstr.h \ istream-multiplex.h \ istream-private.h \ istream-rawlog.h \ istream-seekable.h \ istream-sized.h \ istream-tee.h \ istream-try.h \ istream-timeout.h \ istream-unix.h \ ioloop.h \ ioloop-iolist.h \ ioloop-private.h \ ioloop-notify-fd.h \ json-parser.h \ json-tree.h \ lib.h \ lib-event.h \ lib-event-private.h \ lib-signals.h \ llist.h \ log-throttle.h \ macros.h \ md4.h \ md5.h \ malloc-overflow.h \ memarea.h \ mempool.h \ mkdir-parents.h \ mmap-util.h \ module-context.h \ module-dir.h \ mountpoint.h \ net.h \ nfs-workarounds.h \ numpack.h \ ostream.h \ ostream-failure-at.h \ ostream-file-private.h \ ostream-hash.h \ ostream-multiplex.h \ ostream-private.h \ ostream-null.h \ ostream-rawlog.h \ ostream-unix.h \ ostream-wrapper.h \ path-util.h \ pkcs5.h \ primes.h \ printf-format-fix.h \ process-stat.h \ process-title.h \ priorityq.h \ randgen.h \ read-full.h \ restrict-access.h \ restrict-process-size.h \ safe-memset.h \ safe-mkdir.h \ safe-mkstemp.h \ sendfile-util.h \ seq-range-array.h \ seq-set-builder.h \ sha-common.h \ sha1.h \ sha2.h \ sha3.h \ sleep.h \ sort.h \ stats-dist.h \ str.h \ str-find.h \ str-sanitize.h \ str-table.h \ strescape.h \ strfuncs.h \ strnum.h \ time-util.h \ unix-socket-create.h \ unlink-directory.h \ unlink-old-files.h \ unichar.h \ uri-util.h \ utc-offset.h \ utc-mktime.h \ var-expand.h \ var-expand-private.h \ wildcard-match.h \ write-full.h test_programs = test-lib noinst_PROGRAMS = $(test_programs) test_lib_CPPFLAGS = \ -I$(top_srcdir)/src/lib-test test_libs = \ ../lib-test/libtest.la \ liblib.la test_lib_SOURCES = \ test-lib.c \ test-array.c \ test-aqueue.c \ test-backtrace.c \ test-base32.c \ test-base64.c \ test-bits.c \ test-bsearch-insert-pos.c \ test-buffer.c \ test-buffer-istream.c \ test-byteorder.c \ test-connection.c \ test-crc32.c \ test-cpu-limit.c \ test-data-stack.c \ test-env-util.c \ test-event-category-register.c \ test-event-filter.c \ test-event-filter-expr.c \ test-event-filter-merge.c \ test-event-filter-parser.c \ test-event-flatten.c \ test-event-log.c \ test-failures.c \ test-fd-util.c \ test-file-cache.c \ test-file-create-locked.c \ test-guid.c \ test-hash.c \ test-hash-format.c \ test-hash-method.c \ test-hmac.c \ test-hex-binary.c \ test-imem.c \ test-ioloop.c \ test-iso8601-date.c \ test-iostream-pump.c \ test-iostream-proxy.c \ test-iostream-temp.c \ test-istream.c \ test-istream-base64-decoder.c \ test-istream-base64-encoder.c \ test-istream-chain.c \ test-istream-concat.c \ test-istream-crlf.c \ test-istream-failure-at.c \ test-istream-jsonstr.c \ test-istream-multiplex.c \ test-istream-seekable.c \ test-istream-sized.c \ test-istream-tee.c \ test-istream-try.c \ test-istream-unix.c \ test-json-parser.c \ test-json-tree.c \ test-lib-event.c \ test-lib-signals.c \ test-llist.c \ test-log-throttle.c \ test-macros.c \ test-malloc-overflow.c \ test-memarea.c \ test-mempool.c \ test-mempool-allocfree.c \ test-mempool-alloconly.c \ test-pkcs5.c \ test-net.c \ test-numpack.c \ test-ostream-buffer.c \ test-ostream-failure-at.c \ test-ostream-file.c \ test-ostream-multiplex.c \ test-multiplex.c \ test-path-util.c \ test-primes.c \ test-printf-format-fix.c \ test-priorityq.c \ test-random.c \ test-seq-range-array.c \ test-seq-set-builder.c \ test-stats-dist.c \ test-str.c \ test-strescape.c \ test-strfuncs.c \ test-strnum.c \ test-str-find.c \ test-str-sanitize.c \ test-str-table.c \ test-time-util.c \ test-unichar.c \ test-utc-mktime.c \ test-uri.c \ test-var-expand.c \ test-wildcard-match.c test_headers = \ test-lib.h \ test-lib.inc test_lib_LDADD = $(test_libs) -lm test_lib_DEPENDENCIES = $(test_libs) check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done pkginc_libdir=$(pkgincludedir) pkginc_lib_HEADERS = $(headers) noinst_HEADERS = $(test_headers) dovecot-2.3.21.1/src/lib/macros.h0000644000000000000000000002342514656633576013315 00000000000000#ifndef MACROS_H #define MACROS_H /* several useful macros, mostly from glib.h */ #ifndef NULL # define NULL ((void *)0) #endif #ifndef FALSE # define FALSE (!1) #endif #ifndef TRUE # define TRUE (!FALSE) #endif #define N_ELEMENTS(arr) \ (sizeof(arr) / sizeof((arr)[0])) #define MEM_ALIGN(size) \ (((size) + MEM_ALIGN_SIZE-1) & ~((size_t) MEM_ALIGN_SIZE-1)) #define PTR_OFFSET(ptr, offset) \ ((void *) (((uintptr_t) (ptr)) + ((size_t) (offset)))) #define CONST_PTR_OFFSET(ptr, offset) \ ((const void *) (((uintptr_t) (ptr)) + ((size_t) (offset)))) #define container_of(ptr, type, name) \ (type *)((char *)(ptr) - offsetof(type, name) + \ COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE(ptr, &((type *) 0)->name)) /* Don't use simply MIN/MAX, as they're often defined elsewhere in include files that are included after this file generating tons of warnings. */ #define I_MIN(a, b) (((a) < (b)) ? (a) : (b)) #define I_MAX(a, b) (((a) > (b)) ? (a) : (b)) /* make it easier to cast from/to pointers. assumes that sizeof(uintptr_t) == sizeof(void *) and they're both the largest datatypes that are allowed to be used. so, long long isn't safe with these. */ #define POINTER_CAST(i) \ ((void *) (((uintptr_t)NULL) + (i))) #define POINTER_CAST_TO(p, type) \ ((type)(uintptr_t)(p)) /* Define VA_COPY() to do the right thing for copying va_list variables. config.h may have already defined VA_COPY as va_copy or __va_copy. */ #ifndef VA_COPY # if defined (__GNUC__) && defined (__PPC__) && \ (defined (_CALL_SYSV) || defined (_WIN32)) # define VA_COPY(ap1, ap2) (*(ap1) = *(ap2)) # elif defined (VA_COPY_AS_ARRAY) # define VA_COPY(ap1, ap2) memmove ((ap1), (ap2), sizeof (va_list)) # else /* va_list is a pointer */ # define VA_COPY(ap1, ap2) ((ap1) = (ap2)) # endif /* va_list is a pointer */ #endif /* Provide convenience macros for handling structure * fields through their offsets. */ #define STRUCT_MEMBER_P(struct_p, struct_offset) \ ((void *) ((char *) (struct_p) + (long) (struct_offset))) #define CONST_STRUCT_MEMBER_P(struct_p, struct_offset) \ ((const void *) ((const char *) (struct_p) + (long) (struct_offset))) /* Provide simple macro statement wrappers: STMT_START { statements; } STMT_END; can be used as a single statement, as in if (x) STMT_START { ... } STMT_END; else ... */ #if !(defined (STMT_START) && defined (STMT_END)) # define STMT_START do # define STMT_END while (0) #endif /* Provide macros to feature the GCC function attribute. */ #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4) # define ATTRS_DEFINED # define ATTR_FORMAT(format_idx, arg_idx) \ __attribute__((format (printf, format_idx, arg_idx))) # define ATTR_FORMAT_ARG(arg_idx) \ __attribute__((format_arg (arg_idx))) # define ATTR_SCANF(format_idx, arg_idx) \ __attribute__((format (scanf, format_idx, arg_idx))) # define ATTR_STRFTIME(format_idx) \ __attribute__((format (strftime, format_idx, 0))) # define ATTR_UNUSED __attribute__((unused)) # define ATTR_NORETURN __attribute__((noreturn)) # define ATTR_CONST __attribute__((const)) # define ATTR_PURE __attribute__((pure)) #else # define ATTR_FORMAT(format_idx, arg_idx) # define ATTR_FORMAT_ARG(arg_idx) # define ATTR_SCANF(format_idx, arg_idx) # define ATTR_STRFTIME(format_idx) # define ATTR_UNUSED # define ATTR_NORETURN # define ATTR_CONST # define ATTR_PURE #endif #ifdef HAVE_ATTR_NULL # define ATTR_NULL(...) __attribute__((null(__VA_ARGS__))) #else # define ATTR_NULL(...) #endif #ifdef HAVE_ATTR_NOWARN_UNUSED_RESULT # define ATTR_NOWARN_UNUSED_RESULT __attribute__((nowarn_unused_result)) #else # define ATTR_NOWARN_UNUSED_RESULT #endif #if __GNUC__ > 2 # define ATTR_MALLOC __attribute__((malloc)) #else # define ATTR_MALLOC #endif #if __GNUC__ > 3 /* GCC 4.0 and later */ # define ATTR_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) # define ATTR_SENTINEL __attribute__((sentinel)) #else # define ATTR_WARN_UNUSED_RESULT # define ATTR_SENTINEL #endif #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3) /* GCC 4.3 and later */ # define ATTR_HOT __attribute__((hot)) # define ATTR_COLD __attribute__((cold)) #else # define ATTR_HOT # define ATTR_COLD #endif #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9) /* GCC 4.9 and later */ # define ATTR_RETURNS_NONNULL __attribute__((returns_nonnull)) #else # define ATTR_RETURNS_NONNULL #endif #ifdef HAVE_ATTR_DEPRECATED # define ATTR_DEPRECATED(str) __attribute__((deprecated(str))) #else # define ATTR_DEPRECATED(str) #endif /* Macros to provide type safety for callback functions' context parameters. This is used like: // safe-api.h file: typedef void safe_callback_t(struct foo *foo); void safe_run(safe_callback_t *callback, void *context); #define safe_run((safe_callback_t *)callback, \ TRUE ? context : CALLBACK_TYPECHECK(callback, void (*)(typeof(context)))) // safe-api.c file: #undef safe_run void safe_run(safe_callback_t *callback, void *context) { callback(context); } // in caller code: static void callback(struct foo *foo); struct foo *foo = ...; safe_run(callback, foo); The first step is to create the callback function in a normal way. Type safety is added to it by creating a macro that overrides the function and checks the callback type safety using CALLBACK_TYPECHECK(). The CALLBACK_TYPECHECK() macro works by giving a compiling failure if the provided callback function isn't compatible with the specified function type parameter. The function type parameter must use typeof(context) in place of the "void *context" parameter, but otherwise use exactly the same function type as what the callback is. The macro then casts the given callback function into the type with "void *context". */ #ifdef HAVE_TYPE_CHECKS # define CALLBACK_TYPECHECK(callback, type) \ (COMPILE_ERROR_IF_TRUE(!__builtin_types_compatible_p( \ typeof(&callback), type)) ? 1 : 0) #else # define CALLBACK_TYPECHECK(callback, type) 0 #endif #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ > 0)) && \ !defined(__cplusplus) && !defined(STATIC_CHECKER) # define COMPILE_ERROR_IF_TRUE(condition) \ (sizeof(char[1 - 2 * ((condition) ? 1 : 0)]) > 0 ? FALSE : FALSE) #else # define COMPILE_ERROR_IF_TRUE(condition) FALSE #endif #ifdef HAVE_TYPE_CHECKS # define COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE(_a, _b) \ COMPILE_ERROR_IF_TRUE( \ !__builtin_types_compatible_p(typeof(_a), typeof(_b))) #define COMPILE_ERROR_IF_TYPES2_NOT_COMPATIBLE(_a1, _a2, _b) \ COMPILE_ERROR_IF_TRUE( \ !__builtin_types_compatible_p(typeof(_a1), typeof(_b)) && \ !__builtin_types_compatible_p(typeof(_a2), typeof(_b))) # define TYPE_CHECKS(return_type, checks, func) \ (FALSE ? (return_type)(checks) : (func)) #else # define COMPILE_ERROR_IF_TYPES_NOT_COMPATIBLE(_a, _b) 0 # define COMPILE_ERROR_IF_TYPES2_NOT_COMPATIBLE(_a1, _a2, _b) 0 # define TYPE_CHECKS(return_type, checks, func) (func) #endif #if __GNUC__ > 2 # define unlikely(expr) (__builtin_expect((expr) ? 1 : 0, 0) != 0) # define likely(expr) (__builtin_expect((expr) ? 1 : 0, 1) != 0) #else # define unlikely(expr) expr # define likely(expr) expr #endif #if defined(__clang__) && ((__clang_major__ > 4) || (__clang_major__ == 3 && __clang_minor__ >= 9)) # define ATTR_UNSIGNED_WRAPS __attribute__((no_sanitize("integer"))) #else # define ATTR_UNSIGNED_WRAPS #endif /* Provide macros for error handling. */ #ifdef DISABLE_ASSERTS # define i_assert(expr) #else # define i_assert(expr) STMT_START{ \ if (unlikely(!(expr))) \ i_panic("file %s: line %d (%s): assertion failed: (%s)", \ __FILE__, \ __LINE__, \ __func__, \ #expr); }STMT_END #endif /* Convenience macro to test the versions of dovecot. */ #define DOVECOT_PREREQ(maj, min, micro) \ ((DOVECOT_VERSION_MAJOR << 24) + \ (DOVECOT_VERSION_MINOR << 16) + \ DOVECOT_VERSION_MICRO >= ((maj) << 24) + ((min) << 16) + (micro)) #ifdef __cplusplus # undef STATIC_ARRAY # define STATIC_ARRAY #endif /* Convenience wrappers for initializing a struct with zeros, although it can be used for replacing other memset()s also. // NOTE: This is the correct way to zero the whole array char arr[5]; i_zero(&arr); // This will give compiler error (or zero only the first element): char arr[5]; i_zero(arr); */ #define i_zero(p) \ memset(p, 0 + COMPILE_ERROR_IF_TRUE(sizeof(p) > sizeof(void *)), sizeof(*(p))) #define i_zero_safe(p) \ safe_memset(p, 0 + COMPILE_ERROR_IF_TRUE(sizeof(p) > sizeof(void *)), sizeof(*(p))) #define ST_CHANGED(st_a, st_b) \ ((st_a).st_mtime != (st_b).st_mtime || \ ST_MTIME_NSEC(st_a) != ST_MTIME_NSEC(st_b) || \ (st_a).st_size != (st_b).st_size || \ (st_a).st_ino != (st_b).st_ino) #ifdef HAVE_UNDEFINED_SANITIZER # define ATTR_NO_SANITIZE(x) __attribute__((no_sanitize((x)))) #else # define ATTR_NO_SANITIZE(x) #endif /* gcc and clang do this differently, see https://gcc.gnu.org/onlinedocs/gcc-10.2.0/gcc/Common-Function-Attributes.html */ #ifdef HAVE_FSANITIZE_UNDEFINED # ifdef __clang__ # define ATTR_NO_SANITIZE_UNDEFINED ATTR_NO_SANITIZE("undefined") # else # define ATTR_NO_SANITIZE_UNDEFINED __attribute__((no_sanitize_undefined)) # endif #else # define ATTR_NO_SANITIZE_UNDEFINED #endif #ifdef HAVE_FSANITIZE_INTEGER # define ATTR_NO_SANITIZE_INTEGER ATTR_NO_SANITIZE("integer") # define ATTR_NO_SANITIZE_IMPLICIT_CONVERSION ATTR_NO_SANITIZE("implicit-conversion") #else # define ATTR_NO_SANITIZE_INTEGER # define ATTR_NO_SANITIZE_IMPLICIT_CONVERSION #endif /* negate enumeration flags in a way that avoids implicit conversion */ #ifndef STATIC_CHECKER # define ENUM_NEGATE(x) \ ((unsigned int)(~(x)) + COMPILE_ERROR_IF_TRUE(sizeof((x)) > sizeof(int) || (x) < 0 || (x) > INT_MAX)) #else /* clang scan-build keeps complaining about x > 2147483647 case, so disable the sizeof check. */ # define ENUM_NEGATE(x) ((unsigned int)(~(x))) #endif #endif dovecot-2.3.21.1/src/lib/mempool-system.c0000644000000000000000000000717614656633576015023 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ /* @UNSAFE: whole file */ #include "lib.h" #include "safe-memset.h" #include "mempool.h" /* * The system pool is a thin wrapper around calloc() and free(). It exists * to allow direct heap usage via the pool API. * * Implementation * ============== * * Creation * -------- * * The system pool is created statically and therefore is available at any * time. * * Allocation, Reallocation & Freeing * ---------------------------------- * * The p_malloc(), p_realloc(), and p_free() calls get directed to calloc(), * realloc(), and free(). There is no additional per-allocation information * to track. * * Clearing * -------- * * Not supported. Attempting to clear the system pool will result in a * panic. * * Destruction * ----------- * * It is not possible to destroy the system pool. Any attempt to unref the * pool is a no-op. */ #ifndef HAVE_MALLOC_USABLE_SIZE /* no extra includes needed */ #elif defined (HAVE_MALLOC_NP_H) # include /* FreeBSD */ #elif defined (HAVE_MALLOC_H) # include /* Linux */ #endif #define CLEAR_CHR 0xde static const char *pool_system_get_name(pool_t pool); static void pool_system_ref(pool_t pool); static void pool_system_unref(pool_t *pool); static void *pool_system_malloc(pool_t pool, size_t size); static void *pool_system_realloc(pool_t pool, void *mem, size_t old_size, size_t new_size); static void pool_system_clear(pool_t pool); static size_t pool_system_get_max_easy_alloc_size(pool_t pool); static struct pool_vfuncs static_system_pool_vfuncs = { pool_system_get_name, pool_system_ref, pool_system_unref, pool_system_malloc, pool_system_free, pool_system_realloc, pool_system_clear, pool_system_get_max_easy_alloc_size }; struct pool static_system_pool = { .v = &static_system_pool_vfuncs, .alloconly_pool = FALSE, .datastack_pool = FALSE }; pool_t system_pool = &static_system_pool; static const char *pool_system_get_name(pool_t pool ATTR_UNUSED) { return "system"; } static void pool_system_ref(pool_t pool ATTR_UNUSED) { } static void pool_system_unref(pool_t *pool ATTR_UNUSED) { } static void *pool_system_malloc(pool_t pool ATTR_UNUSED, size_t size) { void *mem; #ifdef DEBUG int old_errno = errno; #endif mem = calloc(size, 1); if (unlikely(mem == NULL)) { i_fatal_status(FATAL_OUTOFMEM, "pool_system_malloc(%zu): " "Out of memory", size); } #ifdef DEBUG /* we rely on errno not changing. it shouldn't. */ i_assert(errno == old_errno); #endif return mem; } void pool_system_free(pool_t pool ATTR_UNUSED, void *mem ATTR_UNUSED) { #ifdef DEBUG int old_errno = errno; #endif #if defined(HAVE_MALLOC_USABLE_SIZE) && defined(DEBUG) safe_memset(mem, CLEAR_CHR, malloc_usable_size(mem)); #endif free(mem); #ifdef DEBUG /* we rely on errno not changing. it shouldn't. */ i_assert(errno == old_errno); #endif } static void *pool_system_realloc(pool_t pool ATTR_UNUSED, void *mem, size_t old_size, size_t new_size) { #if defined(HAVE_MALLOC_USABLE_SIZE) i_assert(old_size == SIZE_MAX || mem == NULL || old_size <= malloc_usable_size(mem)); #endif mem = realloc(mem, new_size); if (unlikely(mem == NULL)) { i_fatal_status(FATAL_OUTOFMEM, "pool_system_realloc(%zu): " "Out of memory", new_size); } if (old_size < new_size) { /* clear new data */ memset((char *) mem + old_size, 0, new_size - old_size); } return mem; } static void ATTR_NORETURN pool_system_clear(pool_t pool ATTR_UNUSED) { i_panic("pool_system_clear() must not be called"); } static size_t pool_system_get_max_easy_alloc_size(pool_t pool ATTR_UNUSED) { return 0; } dovecot-2.3.21.1/src/lib/buffer-istream.c0000644000000000000000000000235714656633576014740 00000000000000/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "eacces-error.h" #include "istream.h" enum buffer_append_result buffer_append_full_istream(buffer_t *buf, struct istream *is, size_t max_read_size, const char **error_r) { const unsigned char *data; size_t size; ssize_t ret; while ((ret = i_stream_read_more(is, &data, &size)) > 0) { if (max_read_size == 0) return BUFFER_APPEND_READ_MAX_SIZE; size = I_MIN(max_read_size, size); buffer_append(buf, data, size); i_stream_skip(is, size); max_read_size -= size; } if (ret == 0) return BUFFER_APPEND_READ_MORE; i_assert(is->eof); if (is->stream_errno != 0) { *error_r = i_stream_get_error(is); return BUFFER_APPEND_READ_ERROR; } return BUFFER_APPEND_OK; } enum buffer_append_result buffer_append_full_file(buffer_t *buf, const char *file, size_t max_read_size, const char **error_r) { struct istream *is = i_stream_create_file(file, IO_BLOCK_SIZE); enum buffer_append_result res = buffer_append_full_istream(buf, is, max_read_size, error_r); if (is->stream_errno == EACCES) *error_r = eacces_error_get("open", file); i_stream_unref(&is); i_assert(res != BUFFER_APPEND_READ_MORE); return res; } dovecot-2.3.21.1/src/lib/safe-mkstemp.c0000644000000000000000000000507714656633576014423 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "hex-binary.h" #include "randgen.h" #include "hostpid.h" #include "eacces-error.h" #include "safe-mkstemp.h" #include #include #include static int ATTR_NULL(5) safe_mkstemp_full(string_t *prefix, mode_t mode, uid_t uid, gid_t gid, const char *gid_origin) { size_t prefix_len; struct stat st; unsigned char randbuf[8]; mode_t old_umask; int fd; prefix_len = str_len(prefix); for (;;) { do { random_fill(randbuf, sizeof(randbuf)); str_truncate(prefix, prefix_len); str_append(prefix, binary_to_hex(randbuf, sizeof(randbuf))); } while (lstat(str_c(prefix), &st) == 0); if (errno != ENOENT) { i_error("stat(%s) failed: %m", str_c(prefix)); str_truncate(prefix, prefix_len); return -1; } old_umask = umask(0666 ^ mode); fd = open(str_c(prefix), O_RDWR | O_EXCL | O_CREAT, 0666); umask(old_umask); if (fd != -1) break; if (errno != EEXIST) { if (errno != ENOENT && errno != EACCES) i_error("open(%s) failed: %m", str_c(prefix)); str_truncate(prefix, prefix_len); return -1; } } if (uid == (uid_t)-1 && gid == (gid_t)-1) return fd; if (fchown(fd, uid, gid) < 0) { if (errno == EPERM) { i_error("%s", eperm_error_get_chgrp("fchown", str_c(prefix), gid, gid_origin)); } else { i_error("fchown(%s, %ld, %ld) failed: %m", str_c(prefix), uid == (uid_t)-1 ? -1L : (long)uid, gid == (gid_t)-1 ? -1L : (long)gid); } i_close_fd(&fd); i_unlink(str_c(prefix)); str_truncate(prefix, prefix_len); return -1; } return fd; } int safe_mkstemp(string_t *prefix, mode_t mode, uid_t uid, gid_t gid) { return safe_mkstemp_full(prefix, mode, uid, gid, NULL); } int safe_mkstemp_group(string_t *prefix, mode_t mode, gid_t gid, const char *gid_origin) { return safe_mkstemp_full(prefix, mode, (uid_t)-1, gid, gid_origin); } int safe_mkstemp_hostpid(string_t *prefix, mode_t mode, uid_t uid, gid_t gid) { size_t orig_prefix_len = str_len(prefix); int fd; str_printfa(prefix, "%s.%s.", my_hostname, my_pid); if ((fd = safe_mkstemp(prefix, mode, uid, gid)) == -1) str_truncate(prefix, orig_prefix_len); return fd; } int safe_mkstemp_hostpid_group(string_t *prefix, mode_t mode, gid_t gid, const char *gid_origin) { size_t orig_prefix_len = str_len(prefix); int fd; str_printfa(prefix, "%s.%s.", my_hostname, my_pid); if ((fd = safe_mkstemp_group(prefix, mode, gid, gid_origin)) == -1) str_truncate(prefix, orig_prefix_len); return fd; } dovecot-2.3.21.1/src/lib/fdpass.h0000644000000000000000000000117514656633576013307 00000000000000#ifndef FDPASS_H #define FDPASS_H /* Send data and send_fd (unless it's -1) via sendmsg(). Returns number of bytes sent, or -1 on error. If at least 1 byte was sent, the send_fd was also sent. */ ssize_t fd_send(int handle, int send_fd, const void *data, size_t size); /* Receive data and fd via recvmsg(). Returns number of bytes read, 0 on disconnection, or -1 on error. If at least 1 byte was read, the fd is also returned (if it had been sent). If there was no fd received, it's set to -1. See test-istream-unix.c for different test cases. */ ssize_t fd_read(int handle, void *data, size_t size, int *fd_r); #endif dovecot-2.3.21.1/src/lib/istream-callback.h0000644000000000000000000000310614656633576015221 00000000000000#ifndef ISTREAM_CALLBACK_H #define ISTREAM_CALLBACK_H /* istream-callback can be used to implement an istream that returns data by calling the specified callback. The callback needs to do: a) Add data to buffer unless the buffer size is already too large (the callback can decide by itself what is too large). Return TRUE regardless of whether any data was added. b) Return FALSE when it's finished adding data or when it reaches an error. On error i_stream_callback_set_error() must be called before returning. i_stream_add_destroy_callback() can be also added to do any cleanups that the callback may need to do. */ typedef bool istream_callback_read_t(buffer_t *buf, void *context); struct istream * i_stream_create_callback(istream_callback_read_t *callback, void *context); #define i_stream_create_callback(callback, context) \ i_stream_create_callback(1 ? (istream_callback_read_t *)callback : \ CALLBACK_TYPECHECK(callback, bool (*)(buffer_t *buf, typeof(context))), \ context) /* Append data to the istream externally. Typically this is used to add a header to the stream before the callbacks are called. */ void i_stream_callback_append(struct istream *input, const void *data, size_t size); void i_stream_callback_append_str(struct istream *input, const char *str); /* Returns the istream-callback's internal buffer. This buffer can be used to append data to the stream. */ buffer_t *i_stream_callback_get_buffer(struct istream *input); void i_stream_callback_set_error(struct istream *input, int stream_errno, const char *error); #endif dovecot-2.3.21.1/src/lib/test-primes.c0000644000000000000000000000077414656633576014302 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "primes.h" void test_primes(void) { unsigned int i, j, num; bool success; success = primes_closest(0) > 0; for (num = 1; num < 1024; num++) { if (primes_closest(num) < num) success = FALSE; } for (i = 10; i < 32; i++) { num = (1U << i) - 100; for (j = 0; j < 200; j++, num++) { if (primes_closest(num) < num) success = FALSE; } } test_out("primes_closest()", success); } dovecot-2.3.21.1/src/lib/iostream-pump.c0000644000000000000000000001255714656633576014632 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "str.h" #include "iostream-pump.h" #include "istream.h" #include "ostream.h" #include #undef iostream_pump_set_completion_callback struct iostream_pump { int refcount; struct istream *input; struct ostream *output; struct io *io; iostream_pump_callback_t *callback; void *context; bool waiting_output; bool completed; }; static void iostream_pump_copy(struct iostream_pump *pump) { enum ostream_send_istream_result res; size_t old_size; o_stream_cork(pump->output); old_size = o_stream_get_max_buffer_size(pump->output); o_stream_set_max_buffer_size(pump->output, I_MIN(IO_BLOCK_SIZE, o_stream_get_max_buffer_size(pump->output))); res = o_stream_send_istream(pump->output, pump->input); o_stream_set_max_buffer_size(pump->output, old_size); o_stream_uncork(pump->output); switch(res) { case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: io_remove(&pump->io); pump->callback(IOSTREAM_PUMP_STATUS_INPUT_ERROR, pump->context); return; case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: io_remove(&pump->io); pump->callback(IOSTREAM_PUMP_STATUS_OUTPUT_ERROR, pump->context); return; case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: i_assert(!pump->output->blocking); pump->waiting_output = TRUE; io_remove(&pump->io); return; case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: pump->waiting_output = FALSE; io_remove(&pump->io); /* flush it */ switch (o_stream_flush(pump->output)) { case -1: pump->callback(IOSTREAM_PUMP_STATUS_OUTPUT_ERROR, pump->context); break; case 0: pump->waiting_output = TRUE; pump->completed = TRUE; break; default: pump->callback(IOSTREAM_PUMP_STATUS_INPUT_EOF, pump->context); break; } return; case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: i_assert(!pump->input->blocking); pump->waiting_output = FALSE; return; } i_unreached(); } static int iostream_pump_flush(struct iostream_pump *pump) { int ret; if ((ret = o_stream_flush(pump->output)) <= 0) { if (ret < 0) { pump->callback(IOSTREAM_PUMP_STATUS_OUTPUT_ERROR, pump->context); } return ret; } pump->waiting_output = FALSE; if (pump->completed) { pump->callback(IOSTREAM_PUMP_STATUS_INPUT_EOF, pump->context); return 1; } if (pump->input->blocking) iostream_pump_copy(pump); else if (pump->io == NULL) { pump->io = io_add_istream(pump->input, iostream_pump_copy, pump); io_set_pending(pump->io); } return ret; } struct iostream_pump * iostream_pump_create(struct istream *input, struct ostream *output) { struct iostream_pump *pump; i_assert(input != NULL && output != NULL); i_assert(!input->blocking || !output->blocking); /* ref streams */ i_stream_ref(input); o_stream_ref(output); /* create pump */ pump = i_new(struct iostream_pump, 1); pump->refcount = 1; pump->input = input; pump->output = output; return pump; } void iostream_pump_start(struct iostream_pump *pump) { i_assert(pump != NULL); i_assert(pump->callback != NULL); /* add flush handler */ if (!pump->output->blocking) { o_stream_set_flush_callback(pump->output, iostream_pump_flush, pump); } /* make IO objects */ if (pump->input->blocking) { i_assert(!pump->output->blocking); o_stream_set_flush_pending(pump->output, TRUE); } else { pump->io = io_add_istream(pump->input, iostream_pump_copy, pump); io_set_pending(pump->io); } } struct istream *iostream_pump_get_input(struct iostream_pump *pump) { i_assert(pump != NULL); return pump->input; } struct ostream *iostream_pump_get_output(struct iostream_pump *pump) { i_assert(pump != NULL); return pump->output; } void iostream_pump_set_completion_callback(struct iostream_pump *pump, iostream_pump_callback_t *callback, void *context) { i_assert(pump != NULL); pump->callback = callback; pump->context = context; } void iostream_pump_ref(struct iostream_pump *pump) { i_assert(pump != NULL); i_assert(pump->refcount > 0); pump->refcount++; } void iostream_pump_unref(struct iostream_pump **_pump) { i_assert(_pump != NULL); struct iostream_pump *pump = *_pump; if (pump == NULL) return; i_assert(pump->refcount > 0); *_pump = NULL; if (--pump->refcount > 0) return; iostream_pump_stop(pump); o_stream_unref(&pump->output); i_stream_unref(&pump->input); i_free(pump); } void iostream_pump_destroy(struct iostream_pump **_pump) { i_assert(_pump != NULL); struct iostream_pump *pump = *_pump; if (pump == NULL) return; *_pump = NULL; iostream_pump_stop(pump); o_stream_unref(&pump->output); i_stream_unref(&pump->input); iostream_pump_unref(&pump); } void iostream_pump_stop(struct iostream_pump *pump) { i_assert(pump != NULL); if (pump->output != NULL) o_stream_unset_flush_callback(pump->output); io_remove(&pump->io); } bool iostream_pump_is_waiting_output(struct iostream_pump *pump) { return pump->waiting_output; } void iostream_pump_switch_ioloop_to(struct iostream_pump *pump, struct ioloop *ioloop) { i_assert(pump != NULL); if (pump->io != NULL) pump->io = io_loop_move_io_to(ioloop, &pump->io); o_stream_switch_ioloop_to(pump->output, ioloop); i_stream_switch_ioloop_to(pump->input, ioloop); } void iostream_pump_switch_ioloop(struct iostream_pump *pump) { iostream_pump_switch_ioloop_to(pump, current_ioloop); } dovecot-2.3.21.1/src/lib/strfuncs.c0000644000000000000000000004517314656633576013677 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ /* @UNSAFE: whole file */ #include "lib.h" #include "str.h" #include "printf-format-fix.h" #include "strfuncs.h" #include "array.h" #include #include #include #define STRCONCAT_BUFSIZE 512 enum _str_trim_sides { STR_TRIM_LEFT = BIT(0), STR_TRIM_RIGHT = BIT(1), }; const unsigned char uchar_nul = '\0'; const unsigned char *uchar_empty_ptr = &uchar_nul; volatile int timing_safety_unoptimization; int i_snprintf(char *dest, size_t max_chars, const char *format, ...) { va_list args; int ret; i_assert(max_chars < INT_MAX); va_start(args, format); ret = vsnprintf(dest, max_chars, printf_format_fix_unsafe(format), args); va_end(args); i_assert(ret >= 0); return (unsigned int)ret < max_chars ? 0 : -1; } char *p_strdup(pool_t pool, const char *str) { void *mem; size_t len; if (str == NULL) return NULL; len = strlen(str) + 1; mem = p_malloc(pool, len); memcpy(mem, str, len); return mem; } void *p_memdup(pool_t pool, const void *data, size_t size) { void *mem; mem = p_malloc(pool, size); memcpy(mem, data, size); return mem; } char *p_strdup_empty(pool_t pool, const char *str) { if (str == NULL || *str == '\0') return NULL; return p_strdup(pool, str); } char *p_strdup_until(pool_t pool, const void *start, const void *end) { size_t size; char *mem; i_assert((const char *) start <= (const char *) end); size = (size_t) ((const char *) end - (const char *) start); mem = p_malloc(pool, size + 1); memcpy(mem, start, size); return mem; } char *p_strndup(pool_t pool, const void *str, size_t max_chars) { const char *p; char *mem; size_t len; i_assert(str != NULL); i_assert(max_chars != SIZE_MAX); p = memchr(str, '\0', max_chars); if (p == NULL) len = max_chars; else len = p - (const char *)str; mem = p_malloc(pool, len+1); memcpy(mem, str, len); return mem; } char *p_strdup_printf(pool_t pool, const char *format, ...) { va_list args; char *ret; va_start(args, format); ret = p_strdup_vprintf(pool, format, args); va_end(args); return ret; } char *t_noalloc_strdup_vprintf(const char *format, va_list args, unsigned int *size_r) { #define SNPRINTF_INITIAL_EXTRA_SIZE 256 va_list args2; char *tmp; size_t init_size; int ret; #ifdef DEBUG int old_errno = errno; #endif VA_COPY(args2, args); /* the format string is modified only if %m exists in it. it happens only in error conditions, so don't try to t_push() here since it'll just slow down the normal code path. */ format = printf_format_fix_get_len(format, &init_size); init_size += SNPRINTF_INITIAL_EXTRA_SIZE; tmp = t_buffer_get(init_size); ret = vsnprintf(tmp, init_size, format, args); i_assert(ret >= 0); *size_r = ret + 1; if ((unsigned int)ret >= init_size) { /* didn't fit with the first guess. now we know the size, so try again. */ tmp = t_buffer_get(*size_r); ret = vsnprintf(tmp, *size_r, format, args2); i_assert((unsigned int)ret == *size_r-1); } #ifdef DEBUG /* we rely on errno not changing. it shouldn't. */ i_assert(errno == old_errno); #endif va_end(args2); return tmp; } char *p_strdup_vprintf(pool_t pool, const char *format, va_list args) { char *tmp, *buf; unsigned int size; tmp = t_noalloc_strdup_vprintf(format, args, &size); if (pool->datastack_pool) { t_buffer_alloc(size); return tmp; } else { buf = p_malloc(pool, size); memcpy(buf, tmp, size - 1); return buf; } } char *vstrconcat(const char *str1, va_list args, size_t *ret_len) { const char *str; char *temp; size_t bufsize, i, len; i_assert(str1 != NULL); str = str1; bufsize = STRCONCAT_BUFSIZE; temp = t_buffer_get(bufsize); i = 0; do { len = strlen(str); if (i + len >= bufsize) { /* need more memory */ bufsize = nearest_power(i + len + 1); temp = t_buffer_reget(temp, bufsize); } memcpy(temp + i, str, len); i += len; /* next string */ str = va_arg(args, const char *); } while (str != NULL); i_assert(i < bufsize); temp[i++] = '\0'; *ret_len = i; return temp; } char *p_strconcat(pool_t pool, const char *str1, ...) { va_list args; char *temp, *ret; size_t len; i_assert(str1 != NULL); va_start(args, str1); if (pool->datastack_pool) { ret = vstrconcat(str1, args, &len); t_buffer_alloc(len); } else { temp = vstrconcat(str1, args, &len); ret = p_malloc(pool, len); memcpy(ret, temp, len); } va_end(args); return ret; } static void *t_memdup(const void *data, size_t size) { void *mem = t_malloc_no0(size); memcpy(mem, data, size); return mem; } const char *t_strdup(const char *str) { return t_strdup_noconst(str); } char *t_strdup_noconst(const char *str) { if (str == NULL) return NULL; return t_memdup(str, strlen(str) + 1); } const char *t_strdup_empty(const char *str) { if (str == NULL || *str == '\0') return NULL; return t_strdup(str); } const char *t_strdup_until(const void *start, const void *end) { char *mem; size_t size; i_assert((const char *) start <= (const char *) end); size = (size_t)((const char *)end - (const char *)start); mem = t_malloc_no0(size + 1); memcpy(mem, start, size); mem[size] = '\0'; return mem; } const char *t_strndup(const void *str, size_t max_chars) { i_assert(str != NULL); return p_strndup(unsafe_data_stack_pool, str, max_chars); } const char *t_strdup_printf(const char *format, ...) { va_list args; const char *ret; va_start(args, format); ret = p_strdup_vprintf(unsafe_data_stack_pool, format, args); va_end(args); return ret; } const char *t_strdup_vprintf(const char *format, va_list args) { return p_strdup_vprintf(unsafe_data_stack_pool, format, args); } const char *t_strconcat(const char *str1, ...) { va_list args; const char *ret; size_t len; i_assert(str1 != NULL); va_start(args, str1); ret = vstrconcat(str1, args, &len); t_buffer_alloc(len); va_end(args); return ret; } const char *t_strcut(const char *str, char cutchar) { const char *p; for (p = str; *p != '\0'; p++) { if (*p == cutchar) return t_strdup_until(str, p); } return str; } const char *t_str_replace(const char *str, char from, char to) { char *out; size_t i, len; if (strchr(str, from) == NULL) return str; len = strlen(str); out = t_malloc_no0(len + 1); for (i = 0; i < len; i++) { if (str[i] == from) out[i] = to; else out[i] = str[i]; } out[i] = '\0'; return out; } const char *t_str_oneline(const char *str) { string_t *out; size_t len; const char *p, *pend, *poff; bool new_line; if (strpbrk(str, "\r\n") == NULL) return str; len = strlen(str); out = t_str_new(len + 1); new_line = TRUE; p = poff = str; pend = str + len; while (p < pend) { switch (*p) { case '\r': if (p > poff) str_append_data(out, poff, p - poff); /* just drop \r */ poff = p + 1; break; case '\n': if (p > poff) str_append_data(out, poff, p - poff); if (new_line) { /* coalesce multiple \n into a single space */ } else { /* first \n after text */ str_append_c(out, ' '); new_line = TRUE; } poff = p + 1; break; default: new_line = FALSE; break; } p++; } if (new_line && str_len(out) > 0) str_truncate(out, str_len(out) - 1); else if (p > poff) str_append_data(out, poff, p - poff); return str_c(out); } int i_strocpy(char *dest, const char *src, size_t dstsize) { if (dstsize == 0) return -1; while (*src != '\0' && dstsize > 1) { *dest++ = *src++; dstsize--; } *dest = '\0'; return *src == '\0' ? 0 : -1; } char *str_ucase(char *str) { char *p; for (p = str; *p != '\0'; p++) *p = i_toupper(*p); return str; } char *str_lcase(char *str) { char *p; for (p = str; *p != '\0'; p++) *p = i_tolower(*p); return str; } const char *t_str_lcase(const char *str) { i_assert(str != NULL); return str_lcase(t_strdup_noconst(str)); } const char *t_str_ucase(const char *str) { i_assert(str != NULL); return str_ucase(t_strdup_noconst(str)); } const char *i_strstr_arr(const char *haystack, const char *const *needles) { const char *ptr; for(; *needles != NULL; needles++) if ((ptr = strstr(haystack, *needles)) != NULL) return ptr; return NULL; } static void str_trim_parse(const char *str, const char *chars, enum _str_trim_sides sides, const char **begin_r, const char **end_r) { const char *p, *pend, *begin, *end; *begin_r = *end_r = NULL; pend = str + strlen(str); if (pend == str) return; p = str; if ((sides & STR_TRIM_LEFT) != 0) { while (p < pend && strchr(chars, *p) != NULL) p++; if (p == pend) return; } begin = p; p = pend; if ((sides & STR_TRIM_RIGHT) != 0) { while (p > begin && strchr(chars, *(p-1)) != NULL) p--; if (p == begin) return; } end = p; *begin_r = begin; *end_r = end; } const char *t_str_trim(const char *str, const char *chars) { const char *begin, *end; str_trim_parse(str, chars, STR_TRIM_LEFT | STR_TRIM_RIGHT, &begin, &end); if (begin == NULL) return ""; return t_strdup_until(begin, end); } const char *p_str_trim(pool_t pool, const char *str, const char *chars) { const char *begin, *end; str_trim_parse(str, chars, STR_TRIM_LEFT | STR_TRIM_RIGHT, &begin, &end); if (begin == NULL) return ""; return p_strdup_until(pool, begin, end); } const char *str_ltrim(const char *str, const char *chars) { const char *begin, *end; str_trim_parse(str, chars, STR_TRIM_LEFT, &begin, &end); if (begin == NULL) return ""; return begin; } const char *t_str_ltrim(const char *str, const char *chars) { return t_strdup(str_ltrim(str, chars)); } const char *p_str_ltrim(pool_t pool, const char *str, const char *chars) { return p_strdup(pool, str_ltrim(str, chars)); } const char *t_str_rtrim(const char *str, const char *chars) { const char *begin, *end; str_trim_parse(str, chars, STR_TRIM_RIGHT, &begin, &end); if (begin == NULL) return ""; return t_strdup_until(begin, end); } const char *p_str_rtrim(pool_t pool, const char *str, const char *chars) { const char *begin, *end; str_trim_parse(str, chars, STR_TRIM_RIGHT, &begin, &end); if (begin == NULL) return ""; return p_strdup_until(pool, begin, end); } int null_strcmp(const char *s1, const char *s2) { if (s1 == NULL) return s2 == NULL ? 0 : -1; if (s2 == NULL) return 1; return strcmp(s1, s2); } int null_strcasecmp(const char *s1, const char *s2) { if (s1 == NULL) return s2 == NULL ? 0 : -1; if (s2 == NULL) return 1; return strcasecmp(s1, s2); } int i_memcasecmp(const void *p1, const void *p2, size_t size) { const unsigned char *s1 = p1; const unsigned char *s2 = p2; int ret; while (size > 0) { ret = i_toupper(*s1) - i_toupper(*s2); if (ret != 0) return ret; s1++; s2++; size--; } return 0; } int i_strcmp_p(const char *const *p1, const char *const *p2) { return strcmp(*p1, *p2); } int i_strcasecmp_p(const char *const *p1, const char *const *p2) { return strcasecmp(*p1, *p2); } bool mem_equals_timing_safe(const void *p1, const void *p2, size_t size) { const unsigned char *s1 = p1, *s2 = p2; size_t i; int ret = 0; for (i = 0; i < size; i++) ret |= s1[i] ^ s2[i]; /* make sure the compiler optimizer doesn't try to break out of the above loop early. */ timing_safety_unoptimization = ret; return ret == 0; } bool str_equals_timing_almost_safe(const char *s1, const char *s2) { size_t i; int ret = 0; for (i = 0; s1[i] != '\0' && s2[i] != '\0'; i++) ret |= s1[i] ^ s2[i]; ret |= s1[i] ^ s2[i]; /* make sure the compiler optimizer doesn't try to break out of the above loop early. */ timing_safety_unoptimization = ret; return ret == 0; } size_t str_match(const char *p1, const char *p2) { size_t i = 0; while(p1[i] != '\0' && p1[i] == p2[i]) i++; return i; } size_t i_memspn(const void *data, size_t data_len, const void *accept, size_t accept_len) { const unsigned char *start = data; i_assert(data != NULL || data_len == 0); i_assert(accept != NULL || accept_len == 0); size_t pos = 0; /* nothing to accept */ if (accept_len == 0) return 0; for (; pos < data_len; pos++) { if (memchr(accept, start[pos], accept_len) == NULL) break; } return pos; } size_t i_memcspn(const void *data, size_t data_len, const void *reject, size_t reject_len) { const unsigned char *start = data; const unsigned char *r = reject; const unsigned char *ptr = CONST_PTR_OFFSET(data, data_len); i_assert(data != NULL || data_len == 0); i_assert(reject != NULL || reject_len == 0); /* nothing to reject */ if (reject_len == 0 || data_len == 0) return data_len; /* Doing repeated memchr's over the data is faster than going over it once byte by byte, as long as reject is reasonably short. */ for (size_t i = 0; i < reject_len; i++) { const unsigned char *kand = memchr(start, r[i], data_len); if (kand != NULL && kand < ptr) ptr = kand; } return ptr - start; } static char ** split_str_slow(pool_t pool, const char *data, const char *separators, bool spaces) { char **array; char *str; unsigned int count, alloc_count, new_alloc_count; if (spaces) { /* skip leading separators */ while (*data != '\0' && strchr(separators, *data) != NULL) data++; } if (*data == '\0') return p_new(pool, char *, 1); str = p_strdup(pool, data); alloc_count = 32; array = p_new(pool, char *, alloc_count); array[0] = str; count = 1; while (*str != '\0') { if (strchr(separators, *str) != NULL) { /* separator found */ if (count+1 >= alloc_count) { new_alloc_count = nearest_power(alloc_count+1); array = p_realloc(pool, array, sizeof(char *) * alloc_count, sizeof(char *) * new_alloc_count); alloc_count = new_alloc_count; } *str = '\0'; if (spaces) { while (str[1] != '\0' && strchr(separators, str[1]) != NULL) str++; /* ignore trailing separators */ if (str[1] == '\0') break; } array[count++] = str+1; } str++; } i_assert(count < alloc_count); array[count] = NULL; return array; } static char ** split_str_fast(pool_t pool, const char *data, char sep) { char **array, *str; unsigned int count, alloc_count, new_alloc_count; if (*data == '\0') return p_new(pool, char *, 1); str = p_strdup(pool, data); alloc_count = 32; array = p_new(pool, char *, alloc_count); array[0] = str; count = 1; while ((str = strchr(str, sep)) != NULL) { /* separator found */ if (count+1 >= alloc_count) { new_alloc_count = nearest_power(alloc_count+1); array = p_realloc(pool, array, sizeof(char *) * alloc_count, sizeof(char *) * new_alloc_count); alloc_count = new_alloc_count; } *str++ = '\0'; array[count++] = str; } i_assert(count < alloc_count); i_assert(array[count] == NULL); return array; } static char ** split_str(pool_t pool, const char *data, const char *separators, bool spaces) { i_assert(*separators != '\0'); if (separators[1] == '\0' && !spaces) return split_str_fast(pool, data, separators[0]); else return split_str_slow(pool, data, separators, spaces); } const char **t_strsplit(const char *data, const char *separators) { return (const char **)split_str(unsafe_data_stack_pool, data, separators, FALSE); } const char **t_strsplit_spaces(const char *data, const char *separators) { return (const char **)split_str(unsafe_data_stack_pool, data, separators, TRUE); } char **p_strsplit(pool_t pool, const char *data, const char *separators) { return split_str(pool, data, separators, FALSE); } char **p_strsplit_spaces(pool_t pool, const char *data, const char *separators) { return split_str(pool, data, separators, TRUE); } void p_strsplit_free(pool_t pool, char **arr) { p_free(pool, arr[0]); p_free(pool, arr); } unsigned int str_array_length(const char *const *arr) { unsigned int count; if (arr == NULL) return 0; for (count = 0; *arr != NULL; arr++) count++; return count; } static char * p_strarray_join_n(pool_t pool, const char *const *arr, unsigned int arr_len, const char *separator) { size_t alloc_len, sep_len, len, pos, needed_space; unsigned int i; char *str; sep_len = strlen(separator); alloc_len = 64; str = t_buffer_get(alloc_len); pos = 0; for (i = 0; i < arr_len; i++) { len = strlen(arr[i]); needed_space = pos + len + sep_len + 1; if (needed_space > alloc_len) { alloc_len = nearest_power(needed_space); str = t_buffer_reget(str, alloc_len); } if (i != 0) { memcpy(str + pos, separator, sep_len); pos += sep_len; } memcpy(str + pos, arr[i], len); pos += len; } str[pos] = '\0'; if (!pool->datastack_pool) return p_memdup(pool, str, pos + 1); t_buffer_alloc(pos + 1); return str; } const char *t_strarray_join(const char *const *arr, const char *separator) { return p_strarray_join_n(unsafe_data_stack_pool, arr, str_array_length(arr), separator); } bool str_array_remove(const char **arr, const char *value) { const char **dest; for (; *arr != NULL; arr++) { if (strcmp(*arr, value) == 0) { /* found it. now move the rest. */ for (dest = arr, arr++; *arr != NULL; arr++, dest++) *dest = *arr; *dest = NULL; return TRUE; } } return FALSE; } bool str_array_find(const char *const *arr, const char *value) { for (; *arr != NULL; arr++) { if (strcmp(*arr, value) == 0) return TRUE; } return FALSE; } bool str_array_icase_find(const char *const *arr, const char *value) { for (; *arr != NULL; arr++) { if (strcasecmp(*arr, value) == 0) return TRUE; } return FALSE; } const char **p_strarray_dup(pool_t pool, const char *const *arr) { unsigned int i; const char **ret; char *p; size_t len, size = sizeof(const char *); /* @UNSAFE: integer overflow checks are missing */ for (i = 0; arr[i] != NULL; i++) size += sizeof(const char *) + strlen(arr[i]) + 1; ret = p_malloc(pool, size); p = PTR_OFFSET(ret, sizeof(const char *) * (i + 1)); for (i = 0; arr[i] != NULL; i++) { len = strlen(arr[i]) + 1; memcpy(p, arr[i], len); ret[i] = p; p += len; } i_assert(PTR_OFFSET(ret, size) == (void *)p); return ret; } const char *dec2str(uintmax_t number) { return dec2str_buf(t_malloc_no0(MAX_INT_STRLEN), number); } char *dec2str_buf(char buffer[STATIC_ARRAY MAX_INT_STRLEN], uintmax_t number) { int pos; pos = MAX_INT_STRLEN; buffer[--pos] = '\0'; do { buffer[--pos] = (number % 10) + '0'; number /= 10; } while (number != 0 && pos >= 0); i_assert(pos >= 0); return buffer + pos; } char *p_array_const_string_join(pool_t pool, const ARRAY_TYPE(const_string) *arr, const char *separator) { if (array_count(arr) == 0) return ""; return p_strarray_join_n(pool, array_front(arr), array_count(arr), separator); } dovecot-2.3.21.1/src/lib/str.h0000644000000000000000000000622614656633576012641 00000000000000#ifndef STR_H #define STR_H #include "buffer.h" string_t *str_new(pool_t pool, size_t initial_size); string_t *t_str_new(size_t initial_size); /* Allocate a constant string using the given str as the input data. str pointer is saved directly, so it must not be freed until the returned string is no longer used. len must contain strlen(str). */ string_t *str_new_const(pool_t pool, const char *str, size_t len); string_t *t_str_new_const(const char *str, size_t len); void str_free(string_t **str); char *str_free_without_data(string_t **str); const char *str_c(string_t *str); char *str_c_modifiable(string_t *str); bool str_equals(const string_t *str1, const string_t *str2) ATTR_PURE; static inline const unsigned char *str_data(const string_t *str) { return (const unsigned char*)str->data; } static inline size_t str_len(const string_t *str) { return str->used; } /* Append NUL-terminated string. If the trailing NUL isn't found earlier, append a maximum of max_len characters. */ void str_append_max(string_t *str, const char *cstr, size_t max_len); static inline void str_append(string_t *str, const char *cstr) { buffer_append(str, cstr, strlen(cstr)); } static inline void str_append_data(string_t *str, const void *data, size_t len) { buffer_append(str, data, len); } static inline void str_append_c(string_t *str, unsigned char chr) { buffer_append_c(str, chr); } /* This macro ensures we add unsigned char to str to avoid implicit casts which cause errors with clang's implicit integer truncation sanitizier. Issues caught by these sanitizers are not undefined behavior, but are often unintentional. We also need to check that the type we are adding is compatible with char, so that we don't end up doing a narrowing cast. */ #ifdef HAVE_TYPE_CHECKS # define str_append_c(str, chr) \ str_append_c((str), __builtin_choose_expr( \ __builtin_types_compatible_p(typeof((chr)), char), \ (unsigned char)(chr), (chr))) #endif static inline void str_append_str(string_t *dest, const string_t *src) { buffer_append(dest, src->data, src->used); } /* Append printf()-like data */ void str_printfa(string_t *str, const char *fmt, ...) ATTR_FORMAT(2, 3); void str_vprintfa(string_t *str, const char *fmt, va_list args) ATTR_FORMAT(2, 0); static inline void str_insert(string_t *str, size_t pos, const char *cstr) { buffer_insert(str, pos, cstr, strlen(cstr)); } static inline void str_delete(string_t *str, size_t pos, size_t len) { buffer_delete(str, pos, len); } static inline void str_replace(string_t *str, size_t pos, size_t len, const char *cstr) { buffer_replace(str, pos, len, cstr, strlen(cstr)); } /* Truncate the string to specified length. If it's already smaller, do nothing. */ static inline void str_truncate(string_t *str, size_t len) { if (str_len(str) > len) buffer_set_used_size(str, len); } /* Truncate the string to specified length, but also make sure the truncation doesn't happen in the middle of an UTF-8 character sequence. In that case, the string will end up being up to a few bytes smaller than len. If it's already smaller to begin with, do nothing. */ void str_truncate_utf8(string_t *str, size_t len); #endif dovecot-2.3.21.1/src/lib/test-time-util.c0000644000000000000000000002235314656633576014711 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "time-util.h" #include static void test_timeval_cmp(void) { static const struct { struct timeval tv1, tv2; int output; } tests[] = { { .tv1 = { 0, 0 }, .tv2 = { 0, 0 }, .output = 0, }, { .tv1 = { INT_MAX, 999999 }, .tv2 = { INT_MAX, 999999 }, .output = 0, }, { .tv1 = { 0, 0 }, .tv2 = { 0, 1 }, .output = -1, }, { .tv1 = { 0, 0 }, .tv2 = { 1, 0 }, .output = -1, }, { .tv1 = { 0, 999999 }, .tv2 = { 1, 0 }, .output = -1, }, { .tv1 = { 1, 0 }, .tv2 = { 1, 1 }, .output = -1, }, { .tv1 = { -INT_MAX, 0 }, .tv2 = { INT_MAX, 0 }, .output = -1, } }; unsigned int i; test_begin("timeval_cmp()"); for (i = 0; i < N_ELEMENTS(tests); i++) { const struct timeval *tv1 = &tests[i].tv1, *tv2 = &tests[i].tv2; int output = tests[i].output; test_assert(timeval_cmp(tv1, tv2) == output); test_assert(timeval_cmp(tv2, tv1) == -output); } test_end(); } static void test_timeval_cmp_margin(void) { static const struct { struct timeval tv1, tv2; unsigned int margin; int output; } tests[] = { { .tv1 = { 0, 0 }, .tv2 = { 0, 0 }, .output = 0, },{ .tv1 = { INT_MAX, 999999 }, .tv2 = { INT_MAX, 999999 }, .output = 0, },{ .tv1 = { 0, 0 }, .tv2 = { 0, 1 }, .output = -1, },{ .tv1 = { 0, 0 }, .tv2 = { 1, 0 }, .output = -1, },{ .tv1 = { 0, 999999 }, .tv2 = { 1, 0 }, .output = -1, },{ .tv1 = { 1, 0 }, .tv2 = { 1, 1 }, .output = -1, },{ .tv1 = { -INT_MAX, 0 }, .tv2 = { INT_MAX, 0 }, .output = -1, },{ .tv1 = { 0, 999999 }, .tv2 = { 1, 0 }, .margin = 1, .output = 0, },{ .tv1 = { 1, 0 }, .tv2 = { 1, 1 }, .margin = 1, .output = 0, },{ .tv1 = { 0, 999998 }, .tv2 = { 1, 0 }, .margin = 1, .output = -1, },{ .tv1 = { 1, 0 }, .tv2 = { 1, 2 }, .margin = 1, .output = -1, },{ .tv1 = { 0, 998000 }, .tv2 = { 1, 0 }, .margin = 2000, .output = 0, },{ .tv1 = { 1, 0 }, .tv2 = { 1, 2000 }, .margin = 2000, .output = 0, },{ .tv1 = { 0, 997999 }, .tv2 = { 1, 0 }, .margin = 2000, .output = -1, },{ .tv1 = { 1, 0 }, .tv2 = { 1, 2001 }, .margin = 2000, .output = -1, },{ .tv1 = { 0, 1 }, .tv2 = { 1, 0 }, .margin = 999999, .output = 0, },{ .tv1 = { 1, 0 }, .tv2 = { 1, 999999 }, .margin = 999999, .output = 0, },{ .tv1 = { 0, 0 }, .tv2 = { 1, 0 }, .margin = 999999, .output = -1, },{ .tv1 = { 1, 0 }, .tv2 = { 2, 0 }, .margin = 999999, .output = -1, },{ .tv1 = { 10, 0 }, .tv2 = { 11, 500000 }, .margin = 1500000, .output = 0, },{ .tv1 = { 8, 500000 }, .tv2 = { 10, 0 }, .margin = 1500000, .output = 0, },{ .tv1 = { 10, 0 }, .tv2 = { 11, 500001 }, .margin = 1500000, .output = -1, },{ .tv1 = { 8, 499999 }, .tv2 = { 10, 0 }, .margin = 1500000, .output = -1, },{ .tv1 = { 1517925358, 999989 }, .tv2 = { 1517925359, 753 }, .margin = 2000, .output = 0, } }; unsigned int i; test_begin("timeval_cmp_margin()"); for (i = 0; i < N_ELEMENTS(tests); i++) { const struct timeval *tv1 = &tests[i].tv1, *tv2 = &tests[i].tv2; unsigned int margin = tests[i].margin; int output = tests[i].output; test_assert(timeval_cmp_margin(tv1, tv2, margin) == output); test_assert(timeval_cmp_margin(tv2, tv1, margin) == -output); } test_end(); } static void test_timeval_diff(void) { static const struct timeval input[] = { { 1, 0 }, { 0, 999999 }, { 1, 0 }, { 0, 999001 }, { 1, 1 }, { 0, 999001 }, { 2, 1 }, { 1, 0 }, { INT_MAX, 0 }, { INT_MAX-1, 1 } }; static int output[] = { 1, 999, 1000, 1000001, 999999 }; unsigned int i; long long udiff; int mdiff; test_begin("timeval_diff_*()"); for (i = 0; i < N_ELEMENTS(input); i += 2) { udiff = timeval_diff_usecs(&input[i], &input[i+1]); mdiff = timeval_diff_msecs(&input[i], &input[i+1]); test_assert(udiff == output[i/2]); test_assert(mdiff == udiff/1000); udiff = timeval_diff_usecs(&input[i+1], &input[i]); mdiff = timeval_diff_msecs(&input[i+1], &input[i]); test_assert(udiff == -output[i/2]); test_assert(mdiff == udiff/1000); } test_end(); } static void test_time_to_local_day_start(void) { /* Try this around days when DST changes in some of the more popular timezones. If that works, everything else probably works too. */ const struct tm tests[] = { /* Europe winter -> summer */ { .tm_year = 2017-1900, .tm_mon = 2, .tm_mday = 26 }, { .tm_year = 2017-1900, .tm_mon = 2, .tm_mday = 26, .tm_hour = 23, .tm_min = 59, .tm_sec = 59 }, /* Europe summer -> winter */ { .tm_year = 2017-1900, .tm_mon = 9, .tm_mday = 29 }, { .tm_year = 2017-1900, .tm_mon = 9, .tm_mday = 29, .tm_hour = 23, .tm_min = 59, .tm_sec = 59 }, /* USA winter -> summer */ { .tm_year = 2017-1900, .tm_mon = 2, .tm_mday = 12 }, { .tm_year = 2017-1900, .tm_mon = 2, .tm_mday = 12, .tm_hour = 23, .tm_min = 59, .tm_sec = 59 }, /* USA summer -> winter */ { .tm_year = 2017-1900, .tm_mon = 10, .tm_mday = 5 }, { .tm_year = 2017-1900, .tm_mon = 10, .tm_mday = 5, .tm_hour = 23, .tm_min = 59, .tm_sec = 59 }, /* (some of) Australia summer -> winter */ { .tm_year = 2017-1900, .tm_mon = 3, .tm_mday = 2 }, { .tm_year = 2017-1900, .tm_mon = 3, .tm_mday = 2, .tm_hour = 23, .tm_min = 59, .tm_sec = 59 }, /* (some of) Australia winter -> summer */ { .tm_year = 2017-1900, .tm_mon = 9, .tm_mday = 1 }, { .tm_year = 2017-1900, .tm_mon = 9, .tm_mday = 1, .tm_hour = 23, .tm_min = 59, .tm_sec = 59 }, }; const struct tm *tm; struct tm tm_copy; time_t t; test_begin("time_to_local_day_start()"); for (unsigned i = 0; i < N_ELEMENTS(tests); i++) { tm_copy = tests[i]; tm_copy.tm_isdst = -1; t = mktime(&tm_copy); test_assert_idx(t != (time_t)-1, i); t = time_to_local_day_start(t); tm = localtime(&t); test_assert_idx(tm->tm_year == tests[i].tm_year && tm->tm_mon == tests[i].tm_mon && tm->tm_mday == tests[i].tm_mday, i); test_assert_idx(tm->tm_hour == 0 && tm->tm_min == 0 && tm->tm_sec == 0, i); } test_end(); } static void test_timestamp(const char *ts, int idx) { /* %G:%H:%M:%S */ const char **t = t_strsplit(ts, ":"); unsigned len = str_array_length(t); test_assert_idx(len == 4, idx); /* %G - ISO 8601 year */ test_assert_idx(strlen(t[0]) == 4, idx); unsigned v = 0; test_assert_idx(str_to_uint(t[0], &v) == 0, idx); test_assert_idx(1000 <= v, idx); test_assert_idx(v <= 3000, idx); /* %H - hour from 00 to 23 */ test_assert_idx(strlen(t[1]) == 2, idx); test_assert_idx(str_to_uint(t[1], &v) == 0, idx); test_assert_idx(v <= 23, idx); /* %M - minute from 00 to 59 */ test_assert_idx(strlen(t[2]) == 2, idx); test_assert_idx(str_to_uint(t[2], &v) == 0, idx); test_assert_idx(v <= 59, idx); /* %S - second from 00 to 60 */ test_assert_idx(strlen(t[3]) == 2, idx); test_assert_idx(str_to_uint(t[3], &v) == 0, idx); test_assert_idx(v <= 60, idx); } #define TS_FMT "%G:%H:%M:%S" static void test_strftime_now(void) { test_begin("t_strftime and variants now"); time_t now = time(NULL); test_timestamp(t_strftime(TS_FMT, gmtime(&now)), 0); test_timestamp(t_strfgmtime(TS_FMT, now), 1); test_timestamp(t_strflocaltime(TS_FMT, now), 2); test_end(); } #define RFC2822_FMT "%a, %d %b %Y %T" static void test_strftime_fixed(void) { test_begin("t_strftime and variants fixed timestamp"); time_t ts = 1481222536; const char *exp = "Thu, 08 Dec 2016 18:42:16"; test_assert(strcmp(t_strftime(RFC2822_FMT, gmtime(&ts)), exp) == 0); test_assert(strcmp(t_strfgmtime(RFC2822_FMT, ts), exp) == 0); test_end(); } static void test_micro_nanoseconds(void) { uint64_t secs, usecs, nsecs; test_begin("i_microseconds() and i_nanoseconds()"); secs = time(NULL); usecs = i_microseconds(); nsecs = i_nanoseconds(); /* Assume max 1 seconds time difference between the calls. That should be more than enough, while still not failing if there are temporary hangs when running in heavily loaded systems. */ test_assert(usecs/1000000 - secs <= 1); test_assert(nsecs/1000 - usecs <= 1000000); test_end(); } static void test_str_to_timeval(void) { struct { const char *str; time_t tv_sec; suseconds_t tv_usec; } tests[] = { { "0", 0, 0 }, { "0.0", 0, 0 }, { "0.000000", 0, 0 }, { "0.1", 0, 100000 }, { "0.100000", 0, 100000 }, { "0.000001", 0, 1 }, { "0.100000", 0, 100000 }, { "2147483647", 2147483647, 0 }, { "2147483647.999999", 2147483647, 999999 }, }; const char *test_failures[] = { "", "0.", "0.0000000", "1234.-1", "1234.x", "x", "x.100", }; struct timeval tv; test_begin("str_to_timeval"); for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { test_assert_idx(str_to_timeval(tests[i].str, &tv) == 0, i); test_assert_idx(tv.tv_sec == tests[i].tv_sec, i); test_assert_idx(tv.tv_usec == tests[i].tv_usec, i); } for (unsigned int i = 0; i < N_ELEMENTS(test_failures); i++) test_assert_idx(str_to_timeval(test_failures[i], &tv) == -1, i); test_end(); } void test_time_util(void) { test_timeval_cmp(); test_timeval_cmp_margin(); test_timeval_diff(); test_time_to_local_day_start(); test_strftime_now(); test_strftime_fixed(); test_micro_nanoseconds(); test_str_to_timeval(); } dovecot-2.3.21.1/src/lib/test-str-table.c0000644000000000000000000000146414656633576014675 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str-table.h" void test_str_table(void) { struct str_table *table; const char *key1, *key2, *key1_copy, *key2_copy; test_begin("str_table"); table = str_table_init(); key1 = str_table_ref(table, "str1"); key2 = str_table_ref(table, "str2"); test_assert(key1 != key2); key1_copy = str_table_ref(table, "str1"); test_assert(key1_copy == key1); key2_copy = str_table_ref(table, "str2"); test_assert(key2_copy == key2); str_table_unref(table, &key1); test_assert(key1 == NULL); str_table_unref(table, &key1_copy); str_table_unref(table, &key2); str_table_unref(table, &key2_copy); test_assert(str_table_is_empty(table)); str_table_deinit(&table); test_assert(table == NULL); test_end(); } dovecot-2.3.21.1/src/lib/test-numpack.c0000644000000000000000000000370714656633576014440 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "buffer.h" #include "numpack.h" static const struct test { uint64_t input; uint8_t output[10]; unsigned int output_size; } enc_tests[] = { { 0xffffffff, { 0xff, 0xff, 0xff, 0xff, 0xf }, 5 }, { 0, { 0 }, 1 }, { 0x7f, { 0x7f }, 1 }, { 0x80, { 0x80, 1 }, 2 }, { 0x81, { 0x81, 1 }, 2 }, { 0xdeadbeefcafe, { 0xfe, 0x95, 0xbf, 0xf7, 0xdb, 0xd5, 0x37 }, 7 }, { 0xffffffffffffffff, { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 1 }, 10 }, { 0xfffffffe, { 0xfe, 0xff, 0xff, 0xff, 0xf }, 5 }, }; static const struct fail { uint8_t input[11]; unsigned int input_size; } dec_fails[] = { { { 0 }, 0 }, /* has no termination byte */ { { 0x80 }, 1 }, /* ditto */ { { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, 10 }, /* ditto*/ { { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 2 }, 10 }, /* overflow */ { { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f }, 11 }, /* ditto */ }; void test_numpack(void) { buffer_t *buf = t_buffer_create(32); unsigned int i; const uint8_t *p, *end; uint64_t num; uint64_t magic=0x9669699669969669; test_begin("numpack (good)"); for (i = 0; i < N_ELEMENTS(enc_tests); i++) { buffer_set_used_size(buf, 0); numpack_encode(buf, enc_tests[i].input); test_assert_idx(buf->used == enc_tests[i].output_size, i); test_assert_idx(memcmp(buf->data, enc_tests[i].output, enc_tests[i].output_size) == 0, i); p = buf->data; end = p + buf->used; test_assert_idx(numpack_decode(&p, end, &num) == 0, i); test_assert_idx(num == enc_tests[i].input, i); } test_end(); test_begin("numpack (bad)"); for (i = 0; i < N_ELEMENTS(dec_fails); i++) { p = dec_fails[i].input; end = p + dec_fails[i].input_size; num = magic; test_assert_idx(numpack_decode(&p, end, &num) == -1, i); test_assert_idx(p == dec_fails[i].input && num == magic, i); } test_end(); } dovecot-2.3.21.1/src/lib/istream.c0000644000000000000000000010423514656633576013467 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "array.h" #include "str.h" #include "memarea.h" #include "istream-private.h" static bool i_stream_is_buffer_invalid(const struct istream_private *stream); void i_stream_set_name(struct istream *stream, const char *name) { i_free(stream->real_stream->iostream.name); stream->real_stream->iostream.name = i_strdup(name); } const char *i_stream_get_name(struct istream *stream) { while (stream->real_stream->iostream.name == NULL) { stream = stream->real_stream->parent; if (stream == NULL) return ""; } return stream->real_stream->iostream.name; } static void i_stream_close_full(struct istream *stream, bool close_parents) { io_stream_close(&stream->real_stream->iostream, close_parents); stream->closed = TRUE; if (stream->stream_errno == 0) stream->stream_errno = EPIPE; } void i_stream_destroy(struct istream **stream) { if (*stream == NULL) return; i_stream_close_full(*stream, FALSE); i_stream_unref(stream); } void i_stream_ref(struct istream *stream) { io_stream_ref(&stream->real_stream->iostream); } void i_stream_unref(struct istream **stream) { struct istream_private *_stream; if (*stream == NULL) return; _stream = (*stream)->real_stream; if (_stream->iostream.refcount > 1) { if (!io_stream_unref(&_stream->iostream)) i_unreached(); } else { /* The snapshot may contain pointers to the parent istreams. Free it before io_stream_unref() frees the parents. */ i_stream_snapshot_free(&_stream->prev_snapshot); if (io_stream_unref(&_stream->iostream)) i_unreached(); str_free(&_stream->line_str); i_stream_unref(&_stream->parent); io_stream_free(&_stream->iostream); } *stream = NULL; } #undef i_stream_add_destroy_callback void i_stream_add_destroy_callback(struct istream *stream, istream_callback_t *callback, void *context) { io_stream_add_destroy_callback(&stream->real_stream->iostream, callback, context); } void i_stream_remove_destroy_callback(struct istream *stream, void (*callback)()) { io_stream_remove_destroy_callback(&stream->real_stream->iostream, callback); } int i_stream_get_fd(struct istream *stream) { struct istream_private *_stream = stream->real_stream; return _stream->fd; } void i_stream_copy_fd(struct istream *dest, struct istream *source) { int fd = i_stream_get_fd(source); i_assert(fd != -1); i_assert(dest->real_stream->fd == -1); dest->real_stream->fd = fd; dest->readable_fd = source->readable_fd; } const char *i_stream_get_error(struct istream *stream) { struct istream *s; /* we'll only return errors for streams that have stream_errno set or that have reached EOF. we might be returning unintended error otherwise. */ if (stream->stream_errno == 0) return stream->eof ? "EOF" : ""; for (s = stream; s != NULL; s = s->real_stream->parent) { if (s->stream_errno == 0) break; if (s->real_stream->iostream.error != NULL) return s->real_stream->iostream.error; } return strerror(stream->stream_errno); } const char *i_stream_get_disconnect_reason(struct istream *stream) { return io_stream_get_disconnect_reason(stream, NULL); } void i_stream_close(struct istream *stream) { if (stream != NULL) i_stream_close_full(stream, TRUE); } void i_stream_set_init_buffer_size(struct istream *stream, size_t size) { stream->real_stream->init_buffer_size = size; } void i_stream_set_max_buffer_size(struct istream *stream, size_t max_size) { io_stream_set_max_buffer_size(&stream->real_stream->iostream, max_size); } size_t i_stream_get_max_buffer_size(struct istream *stream) { size_t max_size = 0; do { if (max_size < stream->real_stream->max_buffer_size) max_size = stream->real_stream->max_buffer_size; stream = stream->real_stream->parent; } while (stream != NULL); return max_size; } void i_stream_set_return_partial_line(struct istream *stream, bool set) { stream->real_stream->return_nolf_line = set; } void i_stream_set_persistent_buffers(struct istream *stream, bool set) { do { stream->real_stream->nonpersistent_buffers = !set; stream = stream->real_stream->parent; } while (stream != NULL); } void i_stream_set_blocking(struct istream *stream, bool blocking) { int prev_fd = -1; do { stream->blocking = blocking; if (stream->real_stream->fd != -1 && stream->real_stream->fd != prev_fd) { fd_set_nonblock(stream->real_stream->fd, !blocking); prev_fd = stream->real_stream->fd; } stream = stream->real_stream->parent; } while (stream != NULL); } static void i_stream_update(struct istream_private *stream) { if (stream->parent == NULL) stream->access_counter++; else { stream->access_counter = stream->parent->real_stream->access_counter; stream->parent_expected_offset = stream->parent->v_offset; } } static bool snapshot_has_memarea(struct istream_snapshot *snapshot, struct memarea *memarea) { if (snapshot->old_memarea == memarea) return TRUE; if (snapshot->prev_snapshot != NULL) return snapshot_has_memarea(snapshot->prev_snapshot, memarea); return FALSE; } struct istream_snapshot * i_stream_default_snapshot(struct istream_private *stream, struct istream_snapshot *prev_snapshot) { struct istream_snapshot *snapshot; if (stream->memarea != NULL) { if (prev_snapshot != NULL) { if (snapshot_has_memarea(prev_snapshot, stream->memarea)) return prev_snapshot; } /* This stream has a memarea. Reference it, so we can later on rollback if needed. */ snapshot = i_new(struct istream_snapshot, 1); snapshot->old_memarea = stream->memarea; snapshot->prev_snapshot = prev_snapshot; memarea_ref(snapshot->old_memarea); return snapshot; } if (stream->parent == NULL) { if (stream->nonpersistent_buffers) { /* Assume that memarea would be used normally, but now it's NULL because the buffer is empty and empty buffers are freed. */ i_assert(stream->skip == stream->pos); return prev_snapshot; } i_panic("%s is missing istream.snapshot() implementation", i_stream_get_name(&stream->istream)); } struct istream_private *_parent_stream = stream->parent->real_stream; return _parent_stream->snapshot(_parent_stream, prev_snapshot); } void i_stream_snapshot_free(struct istream_snapshot **_snapshot) { struct istream_snapshot *snapshot = *_snapshot; if (*_snapshot == NULL) return; *_snapshot = NULL; i_stream_snapshot_free(&snapshot->prev_snapshot); if (snapshot->free != NULL) snapshot->free(snapshot); else { if (snapshot->old_memarea != NULL) memarea_unref(&snapshot->old_memarea); i_stream_unref(&snapshot->istream); i_free(snapshot); } } static struct istream_snapshot * i_stream_noop_snapshot(struct istream_private *stream ATTR_UNUSED, struct istream_snapshot *prev_snapshot) { return prev_snapshot; } ssize_t i_stream_read(struct istream *stream) { struct istream_private *_stream = stream->real_stream; ssize_t ret; #ifdef DEBUG unsigned char prev_buf[4]; const unsigned char *prev_data = _stream->buffer; size_t prev_skip = _stream->skip, prev_pos = _stream->pos; bool invalid = i_stream_is_buffer_invalid(_stream); i_assert(prev_skip <= prev_pos); if (invalid) ; else if (prev_pos - prev_skip <= 4) memcpy(prev_buf, prev_data + prev_skip, prev_pos - prev_skip); else { memcpy(prev_buf, prev_data + prev_skip, 2); memcpy(prev_buf+2, prev_data + prev_pos - 2, 2); } #endif if (_stream->skip != _stream->pos || _stream->prev_snapshot != NULL) { _stream->prev_snapshot = _stream->snapshot(_stream, _stream->prev_snapshot); } ret = i_stream_read_memarea(stream); if (ret > 0) i_stream_snapshot_free(&_stream->prev_snapshot); #ifdef DEBUG else if (!invalid) { i_assert((_stream->pos - _stream->skip) == (prev_pos - prev_skip) || prev_pos == prev_skip); if (prev_pos - prev_skip <= 4) i_assert(memcmp(prev_buf, prev_data + prev_skip, prev_pos - prev_skip) == 0); else { i_assert(memcmp(prev_buf, prev_data + prev_skip, 2) == 0); i_assert(memcmp(prev_buf+2, prev_data + prev_pos - 2, 2) == 0); } } #endif return ret; } ssize_t i_stream_read_memarea(struct istream *stream) { struct istream_private *_stream = stream->real_stream; size_t old_size; ssize_t ret; if (unlikely(stream->closed || stream->stream_errno != 0)) { stream->eof = TRUE; errno = stream->stream_errno; return -1; } stream->eof = FALSE; if (_stream->parent != NULL) i_stream_seek(_stream->parent, _stream->parent_expected_offset); old_size = _stream->pos - _stream->skip; if (_stream->pos < _stream->high_pos) { /* we're here because we seeked back within the read buffer. */ ret = _stream->high_pos - _stream->pos; _stream->pos = _stream->high_pos; _stream->high_pos = 0; } else { _stream->high_pos = 0; ret = _stream->read(_stream); } i_assert(old_size <= _stream->pos - _stream->skip); switch (ret) { case -2: i_assert(_stream->skip != _stream->pos); break; case -1: if (stream->stream_errno != 0) { /* error handling should be easier if we now just assume the stream is now at EOF */ stream->eof = TRUE; errno = stream->stream_errno; } else { i_assert(stream->eof); i_assert(old_size == _stream->pos - _stream->skip); } break; case 0: i_assert(!stream->blocking); break; default: i_assert(ret > 0); i_assert(_stream->skip < _stream->pos); i_assert((size_t)ret+old_size == _stream->pos - _stream->skip); _stream->last_read_timeval = ioloop_timeval; break; } if (stream->stream_errno != 0) { /* error handling should be easier if we now just assume the stream is now at EOF. Note that we could get here even if read() didn't return -1, although that's a little bit sloppy istream implementation. */ stream->eof = TRUE; } i_stream_update(_stream); /* verify that parents' access_counters are valid. the parent's i_stream_read() should guarantee this. */ i_assert(!i_stream_is_buffer_invalid(_stream)); return ret; } int i_stream_read_more_memarea(struct istream *stream, const unsigned char **data_r, size_t *size_r) { *data_r = i_stream_get_data(stream, size_r); if (*size_r > 0) return 1; int ret = i_stream_read_memarea(stream); *data_r = i_stream_get_data(stream, size_r); return ret; } void i_stream_get_last_read_time(struct istream *stream, struct timeval *tv_r) { *tv_r = stream->real_stream->last_read_timeval; } ssize_t i_stream_read_copy_from_parent(struct istream *istream) { struct istream_private *stream = istream->real_stream; size_t pos; ssize_t ret; stream->pos -= stream->skip; stream->skip = 0; stream->buffer = i_stream_get_data(stream->parent, &pos); if (pos > stream->pos) ret = 0; else do { ret = i_stream_read_memarea(stream->parent); stream->istream.stream_errno = stream->parent->stream_errno; stream->istream.eof = stream->parent->eof; stream->buffer = i_stream_get_data(stream->parent, &pos); /* check again, in case the parent stream had been seeked backwards and the previous read() didn't get us far enough. */ } while (pos <= stream->pos && ret > 0); if (ret == -2) { i_stream_update(stream); return -2; } ret = pos > stream->pos ? (ssize_t)(pos - stream->pos) : (ret == 0 ? 0 : -1); stream->pos = pos; i_assert(ret != -1 || stream->istream.eof || stream->istream.stream_errno != 0); i_stream_update(stream); return ret; } void i_stream_free_buffer(struct istream_private *stream) { if (stream->memarea != NULL) { memarea_unref(&stream->memarea); stream->w_buffer = NULL; } else if (stream->w_buffer != NULL) { i_free_and_null(stream->w_buffer); } else { /* don't know how to free it */ return; } stream->buffer_size = 0; } void i_stream_skip(struct istream *stream, uoff_t count) { struct istream_private *_stream = stream->real_stream; size_t data_size; data_size = _stream->pos - _stream->skip; if (count <= data_size) { /* within buffer */ stream->v_offset += count; _stream->skip += count; if (_stream->nonpersistent_buffers && _stream->skip == _stream->pos) { _stream->skip = _stream->pos = 0; i_stream_free_buffer(_stream); } return; } /* have to seek forward */ count -= data_size; _stream->skip = _stream->pos; stream->v_offset += data_size; if (unlikely(stream->closed || stream->stream_errno != 0)) return; _stream->seek(_stream, stream->v_offset + count, FALSE); } static bool i_stream_can_optimize_seek(struct istream_private *stream) { if (stream->parent == NULL) return TRUE; /* use the fast route only if the parent stream hasn't been changed */ if (stream->access_counter != stream->parent->real_stream->access_counter) return FALSE; return i_stream_can_optimize_seek(stream->parent->real_stream); } void i_stream_seek(struct istream *stream, uoff_t v_offset) { struct istream_private *_stream = stream->real_stream; if (v_offset >= stream->v_offset && i_stream_can_optimize_seek(_stream)) i_stream_skip(stream, v_offset - stream->v_offset); else { if (unlikely(stream->closed || stream->stream_errno != 0)) { stream->eof = TRUE; return; } stream->eof = FALSE; _stream->seek(_stream, v_offset, FALSE); } i_stream_update(_stream); } void i_stream_seek_mark(struct istream *stream, uoff_t v_offset) { struct istream_private *_stream = stream->real_stream; if (unlikely(stream->closed || stream->stream_errno != 0)) return; stream->eof = FALSE; _stream->seek(_stream, v_offset, TRUE); i_stream_update(_stream); } void i_stream_sync(struct istream *stream) { struct istream_private *_stream = stream->real_stream; if (unlikely(stream->closed || stream->stream_errno != 0)) return; if (_stream->sync != NULL) { _stream->sync(_stream); i_stream_update(_stream); } } int i_stream_stat(struct istream *stream, bool exact, const struct stat **st_r) { struct istream_private *_stream = stream->real_stream; if (unlikely(stream->closed || stream->stream_errno != 0)) return -1; if (_stream->stat(_stream, exact) < 0) { stream->eof = TRUE; return -1; } *st_r = &_stream->statbuf; return 0; } int i_stream_get_size(struct istream *stream, bool exact, uoff_t *size_r) { struct istream_private *_stream = stream->real_stream; if (unlikely(stream->closed || stream->stream_errno != 0)) return -1; int ret; if ((ret = _stream->get_size(_stream, exact, size_r)) < 0) stream->eof = TRUE; return ret; } bool i_stream_have_bytes_left(struct istream *stream) { return i_stream_get_data_size(stream) > 0 || !stream->eof; } bool i_stream_read_eof(struct istream *stream) { if (i_stream_get_data_size(stream) == 0) (void)i_stream_read(stream); return !i_stream_have_bytes_left(stream); } uoff_t i_stream_get_absolute_offset(struct istream *stream) { uoff_t abs_offset = stream->v_offset; while (stream != NULL) { abs_offset += stream->real_stream->start_offset; stream = stream->real_stream->parent; } return abs_offset; } static char *i_stream_next_line_finish(struct istream_private *stream, size_t i) { char *ret; size_t end; if (i > stream->skip && stream->buffer[i-1] == '\r') { end = i - 1; stream->line_crlf = TRUE; } else { end = i; stream->line_crlf = FALSE; } if (stream->buffer == stream->w_buffer && end < stream->buffer_size) { /* modify the buffer directly */ stream->w_buffer[end] = '\0'; ret = (char *)stream->w_buffer + stream->skip; } else { /* use a temporary string to return it */ if (stream->line_str == NULL) stream->line_str = str_new(default_pool, 256); str_truncate(stream->line_str, 0); if (stream->skip < end) str_append_data(stream->line_str, stream->buffer + stream->skip, end - stream->skip); ret = str_c_modifiable(stream->line_str); } if (i < stream->pos) i++; stream->istream.v_offset += i - stream->skip; stream->skip = i; return ret; } static char *i_stream_last_line(struct istream_private *_stream) { if (_stream->istream.eof && _stream->skip != _stream->pos && _stream->return_nolf_line) { /* the last line is missing LF and we want to return it. */ return i_stream_next_line_finish(_stream, _stream->pos); } return NULL; } char *i_stream_next_line(struct istream *stream) { struct istream_private *_stream = stream->real_stream; const unsigned char *pos; if (_stream->skip >= _stream->pos) return NULL; pos = memchr(_stream->buffer + _stream->skip, '\n', _stream->pos - _stream->skip); if (pos != NULL) { return i_stream_next_line_finish(_stream, pos - _stream->buffer); } else { return i_stream_last_line(_stream); } } char *i_stream_read_next_line(struct istream *stream) { char *line; for (;;) { line = i_stream_next_line(stream); if (line != NULL) break; switch (i_stream_read(stream)) { case -2: io_stream_set_error(&stream->real_stream->iostream, "Line is too long (over %zu" " bytes at offset %"PRIuUOFF_T")", i_stream_get_data_size(stream), stream->v_offset); stream->stream_errno = errno = ENOBUFS; stream->eof = TRUE; return NULL; case -1: return i_stream_last_line(stream->real_stream); case 0: return NULL; } } return line; } bool i_stream_last_line_crlf(struct istream *stream) { return stream->real_stream->line_crlf; } static bool i_stream_is_buffer_invalid(const struct istream_private *stream) { if (stream->parent == NULL) { /* the buffer can't point to parent, because it doesn't exist */ return FALSE; } if (stream->w_buffer != NULL) { /* we can pretty safely assume that the stream is using its own private buffer, so it can never become invalid. */ return FALSE; } if (stream->access_counter != stream->parent->real_stream->access_counter) { /* parent has been modified behind this stream, we can't trust that our buffer is valid */ return TRUE; } return i_stream_is_buffer_invalid(stream->parent->real_stream); } const unsigned char * i_stream_get_data(struct istream *stream, size_t *size_r) { struct istream_private *_stream = stream->real_stream; if (_stream->skip >= _stream->pos) { *size_r = 0; return uchar_empty_ptr; } if (unlikely(i_stream_is_buffer_invalid(_stream))) { /* This stream may be using parent's buffer directly as _stream->buffer, but the parent stream has already been modified indirectly. This means that the buffer might no longer point to where we assume it points to. So we'll just return the stream as empty until it's read again. It's a bit ugly to suddenly drop data from the stream that was already read, but since this happens only with shared parent istreams the caller is hopefully aware enough that something like this might happen. The other solutions would be to a) try to automatically read the data back (but we can't handle errors..) or b) always copy data to stream's own buffer instead of pointing to parent's buffer (but this causes data copying that is nearly always unnecessary). */ *size_r = 0; /* if we had already read until EOF, mark the stream again as not being at the end of file. */ if (stream->stream_errno == 0) { _stream->skip = _stream->pos = 0; stream->eof = FALSE; } return uchar_empty_ptr; } *size_r = _stream->pos - _stream->skip; return _stream->buffer + _stream->skip; } size_t i_stream_get_data_size(struct istream *stream) { size_t size; (void)i_stream_get_data(stream, &size); return size; } unsigned char *i_stream_get_modifiable_data(struct istream *stream, size_t *size_r) { struct istream_private *_stream = stream->real_stream; if (_stream->skip >= _stream->pos || _stream->w_buffer == NULL) { *size_r = 0; return NULL; } *size_r = _stream->pos - _stream->skip; return _stream->w_buffer + _stream->skip; } int i_stream_read_data(struct istream *stream, const unsigned char **data_r, size_t *size_r, size_t threshold) { ssize_t ret = 0; bool read_more = FALSE; do { *data_r = i_stream_get_data(stream, size_r); if (*size_r > threshold) return 1; /* we need more data */ ret = i_stream_read(stream); if (ret > 0) read_more = TRUE; } while (ret > 0); *data_r = i_stream_get_data(stream, size_r); if (ret == -2) return -2; if (ret == 0) { /* need to read more */ i_assert(!stream->blocking); return 0; } if (stream->eof) { if (read_more) { /* we read at least some new data */ return 0; } } else { i_assert(stream->stream_errno != 0); } return -1; } int i_stream_read_limited(struct istream *stream, const unsigned char **data_r, size_t *size_r, size_t limit) { struct istream_private *_stream = stream->real_stream; int ret; *data_r = i_stream_get_data(stream, size_r); if (*size_r >= limit) { *size_r = limit; return 1; } _stream->data_limit = limit; ret = i_stream_read_more(stream, data_r, size_r); _stream->data_limit = 0; if (*size_r >= limit) *size_r = limit; return ret; } void i_stream_compress(struct istream_private *stream) { i_assert(stream->memarea == NULL || memarea_get_refcount(stream->memarea) == 1); if (stream->skip != stream->pos) { memmove(stream->w_buffer, stream->w_buffer + stream->skip, stream->pos - stream->skip); } stream->pos -= stream->skip; stream->skip = 0; } static void i_stream_w_buffer_free(void *buf) { i_free(buf); } static void i_stream_w_buffer_realloc(struct istream_private *stream, size_t old_size) { void *new_buffer; if (stream->memarea != NULL && memarea_get_refcount(stream->memarea) == 1) { /* Nobody else is referencing the memarea. We can just reallocate it. */ memarea_free_without_callback(&stream->memarea); new_buffer = i_realloc(stream->w_buffer, old_size, stream->buffer_size); } else { new_buffer = i_malloc(stream->buffer_size); if (old_size > 0) { i_assert(stream->w_buffer != NULL); memcpy(new_buffer, stream->w_buffer, old_size); } if (stream->memarea != NULL) memarea_unref(&stream->memarea); } stream->w_buffer = new_buffer; stream->buffer = new_buffer; stream->memarea = memarea_init(stream->w_buffer, stream->buffer_size, i_stream_w_buffer_free, new_buffer); } void i_stream_grow_buffer(struct istream_private *stream, size_t bytes) { size_t old_size, max_size; old_size = stream->buffer_size; stream->buffer_size = stream->pos + bytes; if (stream->buffer_size <= stream->init_buffer_size) stream->buffer_size = stream->init_buffer_size; else stream->buffer_size = nearest_power(stream->buffer_size); max_size = i_stream_get_max_buffer_size(&stream->istream); i_assert(max_size > 0); if (stream->buffer_size > max_size) stream->buffer_size = max_size; if (stream->buffer_size <= old_size) stream->buffer_size = old_size; else i_stream_w_buffer_realloc(stream, old_size); } bool i_stream_try_alloc(struct istream_private *stream, size_t wanted_size, size_t *size_r) { i_assert(wanted_size > 0); i_assert(stream->buffer_size >= stream->pos); if (wanted_size > stream->buffer_size - stream->pos) { if (stream->skip > 0) { /* remove the unused bytes from beginning of buffer */ if (stream->memarea != NULL && memarea_get_refcount(stream->memarea) > 1) { /* The memarea is still referenced. We can't overwrite data until extra references are gone. */ i_stream_w_buffer_realloc(stream, stream->buffer_size); } i_stream_compress(stream); } else if (stream->buffer_size < i_stream_get_max_buffer_size(&stream->istream)) { /* buffer is full - grow it */ i_stream_grow_buffer(stream, I_STREAM_MIN_SIZE); } } if (stream->data_limit == 0 || (stream->buffer_size - stream->skip) < stream->data_limit) *size_r = stream->buffer_size - stream->pos; else { size_t buffered = (stream->pos - stream->skip); if (buffered >= stream->data_limit) *size_r = 0; else *size_r = stream->data_limit - buffered; } i_assert(stream->w_buffer != NULL || *size_r == 0); return *size_r > 0; } bool ATTR_NOWARN_UNUSED_RESULT i_stream_try_alloc_avoid_compress(struct istream_private *stream, size_t wanted_size, size_t *size_r) { size_t old_skip = stream->skip; /* try first with skip=0, so no compression is done */ stream->skip = 0; bool ret = i_stream_try_alloc(stream, wanted_size, size_r); stream->skip = old_skip; if (ret || old_skip == 0) return ret; /* it's full. try with compression. */ return i_stream_try_alloc(stream, wanted_size, size_r); } void *i_stream_alloc(struct istream_private *stream, size_t size) { size_t old_size, avail_size; (void)i_stream_try_alloc(stream, size, &avail_size); if (avail_size < size) { old_size = stream->buffer_size; stream->buffer_size = nearest_power(stream->pos + size); i_stream_w_buffer_realloc(stream, old_size); (void)i_stream_try_alloc(stream, size, &avail_size); i_assert(avail_size >= size); } return stream->w_buffer + stream->pos; } void i_stream_memarea_detach(struct istream_private *stream) { if (stream->memarea != NULL) { /* Don't overwrite data in a snapshot. Allocate a new buffer instead. */ memarea_unref(&stream->memarea); stream->buffer_size = 0; stream->buffer = NULL; stream->w_buffer = NULL; } } bool i_stream_add_data(struct istream *_stream, const unsigned char *data, size_t size) { struct istream_private *stream = _stream->real_stream; size_t size2; (void)i_stream_try_alloc(stream, size, &size2); if (size > size2) return FALSE; memcpy(stream->w_buffer + stream->pos, data, size); stream->pos += size; return TRUE; } struct istream *i_stream_get_root_io(struct istream *stream) { while (stream->real_stream->parent != NULL) { i_assert(stream->real_stream->io == NULL); stream = stream->real_stream->parent; } return stream; } void i_stream_set_input_pending(struct istream *stream, bool pending) { if (!pending) return; stream = i_stream_get_root_io(stream); if (stream->real_stream->io != NULL) io_set_pending(stream->real_stream->io); else stream->real_stream->io_pending = TRUE; } void i_stream_switch_ioloop_to(struct istream *stream, struct ioloop *ioloop) { io_stream_switch_ioloop_to(&stream->real_stream->iostream, ioloop); do { if (stream->real_stream->switch_ioloop_to != NULL) { stream->real_stream->switch_ioloop_to( stream->real_stream, ioloop); } stream = stream->real_stream->parent; } while (stream != NULL); } void i_stream_switch_ioloop(struct istream *stream) { i_stream_switch_ioloop_to(stream, current_ioloop); } void i_stream_set_io(struct istream *stream, struct io *io) { stream = i_stream_get_root_io(stream); i_assert(stream->real_stream->io == NULL); stream->real_stream->io = io; if (stream->real_stream->io_pending) { io_set_pending(io); stream->real_stream->io_pending = FALSE; } } void i_stream_unset_io(struct istream *stream, struct io *io) { stream = i_stream_get_root_io(stream); i_assert(stream->real_stream->io == io); if (io_is_pending(io)) stream->real_stream->io_pending = TRUE; stream->real_stream->io = NULL; } static void i_stream_default_set_max_buffer_size(struct iostream_private *stream, size_t max_size) { struct istream_private *_stream = container_of(stream, struct istream_private, iostream); _stream->max_buffer_size = max_size; if (_stream->parent != NULL) i_stream_set_max_buffer_size(_stream->parent, max_size); } static void i_stream_default_close(struct iostream_private *stream, bool close_parent) { struct istream_private *_stream = container_of(stream, struct istream_private, iostream); if (close_parent) i_stream_close(_stream->parent); } static void i_stream_default_destroy(struct iostream_private *stream) { struct istream_private *_stream = container_of(stream, struct istream_private, iostream); i_stream_free_buffer(_stream); i_stream_unref(&_stream->parent); } static void i_stream_default_seek_seekable(struct istream_private *stream, uoff_t v_offset, bool mark ATTR_UNUSED) { stream->istream.v_offset = v_offset; stream->skip = stream->pos = 0; } void i_stream_default_seek_nonseekable(struct istream_private *stream, uoff_t v_offset, bool mark ATTR_UNUSED) { size_t available; if (stream->istream.v_offset > v_offset) i_panic("stream %s doesn't support seeking backwards", i_stream_get_name(&stream->istream)); while (stream->istream.v_offset < v_offset) { (void)i_stream_read(&stream->istream); available = stream->pos - stream->skip; if (available == 0) { if (stream->istream.stream_errno != 0) { /* read failed */ return; } io_stream_set_error(&stream->iostream, "Can't seek to offset %"PRIuUOFF_T ", because we have data only up to offset %" PRIuUOFF_T" (eof=%d)", v_offset, stream->istream.v_offset, stream->istream.eof ? 1 : 0); stream->istream.stream_errno = ESPIPE; return; } if (available <= v_offset - stream->istream.v_offset) i_stream_skip(&stream->istream, available); else { i_stream_skip(&stream->istream, v_offset - stream->istream.v_offset); } } } bool i_stream_nonseekable_try_seek(struct istream_private *stream, uoff_t v_offset) { uoff_t start_offset = stream->istream.v_offset - stream->skip; if (v_offset < start_offset) { /* have to seek backwards */ i_stream_seek(stream->parent, stream->parent_start_offset); stream->parent_expected_offset = stream->parent_start_offset; stream->skip = stream->pos = 0; stream->istream.v_offset = 0; stream->high_pos = 0; return FALSE; } if (v_offset <= start_offset + stream->pos) { /* seeking backwards within what's already cached */ stream->skip = v_offset - start_offset; stream->istream.v_offset = v_offset; if (stream->high_pos == 0) stream->high_pos = stream->pos; stream->pos = stream->skip; } else { /* read forward */ i_stream_default_seek_nonseekable(stream, v_offset, FALSE); } return TRUE; } static int seekable_i_stream_get_size(struct istream_private *stream) { if (stream->cached_stream_size == UOFF_T_MAX) { uoff_t old_offset = stream->istream.v_offset; ssize_t ret; do { i_stream_skip(&stream->istream, i_stream_get_data_size(&stream->istream)); } while ((ret = i_stream_read(&stream->istream)) > 0); i_assert(ret == -1); if (stream->istream.stream_errno != 0) return -1; stream->cached_stream_size = stream->istream.v_offset; i_stream_seek(&stream->istream, old_offset); } stream->statbuf.st_size = stream->cached_stream_size; return 0; } static int i_stream_default_stat(struct istream_private *stream, bool exact) { const struct stat *st; if (stream->parent == NULL) return stream->istream.stream_errno == 0 ? 0 : -1; if (i_stream_stat(stream->parent, exact, &st) < 0) { stream->istream.stream_errno = stream->parent->stream_errno; return -1; } stream->statbuf = *st; if (exact && !stream->stream_size_passthrough) { /* exact size is not known, even if parent returned something */ stream->statbuf.st_size = -1; if (stream->istream.seekable) { if (seekable_i_stream_get_size(stream) < 0) return -1; } } else { /* When exact=FALSE always return the parent stat's size, even if we know the exact value. This is necessary because otherwise e.g. mbox code can see two different values and think that the mbox file keeps changing. */ } return 0; } static int i_stream_default_get_size(struct istream_private *stream, bool exact, uoff_t *size_r) { if (stream->stat(stream, exact) < 0) return -1; if (stream->statbuf.st_size == -1) return 0; *size_r = stream->statbuf.st_size; return 1; } void i_stream_init_parent(struct istream_private *_stream, struct istream *parent) { _stream->access_counter = parent->real_stream->access_counter; _stream->parent = parent; _stream->parent_start_offset = parent->v_offset; _stream->parent_expected_offset = parent->v_offset; _stream->start_offset = parent->v_offset; /* if parent stream is an istream-error, copy the error */ _stream->istream.stream_errno = parent->stream_errno; _stream->istream.eof = parent->eof; i_stream_ref(parent); } struct istream * i_stream_create(struct istream_private *_stream, struct istream *parent, int fd, enum istream_create_flag flags) { bool noop_snapshot = (flags & ISTREAM_CREATE_FLAG_NOOP_SNAPSHOT) != 0; _stream->fd = fd; if (parent != NULL) i_stream_init_parent(_stream, parent); else if (_stream->memarea == NULL && !noop_snapshot) { /* The stream has no parent and no memarea yet. We'll assume that it wants to be using memareas for the reads. */ _stream->memarea = memarea_init_empty(); } _stream->istream.real_stream = _stream; if (_stream->iostream.close == NULL) _stream->iostream.close = i_stream_default_close; if (_stream->iostream.destroy == NULL) _stream->iostream.destroy = i_stream_default_destroy; if (_stream->seek == NULL) { _stream->seek = _stream->istream.seekable ? i_stream_default_seek_seekable : i_stream_default_seek_nonseekable; } if (_stream->stat == NULL) _stream->stat = i_stream_default_stat; if (_stream->get_size == NULL) _stream->get_size = i_stream_default_get_size; if (_stream->snapshot == NULL) { _stream->snapshot = noop_snapshot ? i_stream_noop_snapshot : i_stream_default_snapshot; } if (_stream->iostream.set_max_buffer_size == NULL) { _stream->iostream.set_max_buffer_size = i_stream_default_set_max_buffer_size; } if (_stream->init_buffer_size == 0) _stream->init_buffer_size = I_STREAM_MIN_SIZE; i_zero(&_stream->statbuf); _stream->statbuf.st_size = -1; _stream->statbuf.st_atime = _stream->statbuf.st_mtime = _stream->statbuf.st_ctime = ioloop_time; _stream->cached_stream_size = UOFF_T_MAX; io_stream_init(&_stream->iostream); if (_stream->istream.stream_errno != 0) _stream->istream.eof = TRUE; return &_stream->istream; } struct istream *i_stream_create_error(int stream_errno) { struct istream_private *stream; stream = i_new(struct istream_private, 1); stream->istream.closed = TRUE; stream->istream.readable_fd = FALSE; stream->istream.blocking = TRUE; stream->istream.seekable = TRUE; stream->istream.eof = TRUE; stream->istream.stream_errno = stream_errno; /* Nothing can ever actually be read from this stream, but set a reasonable max_buffer_size anyway since some filter istreams don't behave properly otherwise. */ stream->max_buffer_size = IO_BLOCK_SIZE; i_stream_create(stream, NULL, -1, 0); i_stream_set_name(&stream->istream, "(error)"); return &stream->istream; } struct istream * i_stream_create_error_str(int stream_errno, const char *fmt, ...) { struct istream *input; va_list args; va_start(args, fmt); input = i_stream_create_error(stream_errno); io_stream_set_verror(&input->real_stream->iostream, fmt, args); va_end(args); return input; } dovecot-2.3.21.1/src/lib/file-copy.c0000644000000000000000000000540314656633576013707 00000000000000/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "istream.h" #include "ostream.h" #include "file-copy.h" #include #include #include #include static int file_copy_to_tmp(const char *srcpath, const char *tmppath, bool try_hardlink) { struct istream *input; struct ostream *output; struct stat st; mode_t old_umask; int fd_in, fd_out; int ret = -1; if (try_hardlink) { /* see if hardlinking works */ if (link(srcpath, tmppath) == 0) return 1; if (errno == EEXIST) { if (i_unlink_if_exists(tmppath) < 0) return -1; if (link(srcpath, tmppath) == 0) return 1; } if (errno == ENOENT) return 0; if (!ECANTLINK(errno)) { i_error("link(%s, %s) failed: %m", srcpath, tmppath); return -1; } /* fallback to manual copying */ } fd_in = open(srcpath, O_RDONLY); if (fd_in == -1) { if (errno == ENOENT) return 0; i_error("open(%s) failed: %m", srcpath); return -1; } if (fstat(fd_in, &st) < 0) { i_error("fstat(%s) failed: %m", srcpath); i_close_fd(&fd_in); return -1; } old_umask = umask(0); fd_out = open(tmppath, O_WRONLY | O_CREAT | O_TRUNC, st.st_mode); umask(old_umask); if (fd_out == -1) { i_error("open(%s, O_CREAT) failed: %m", tmppath); i_close_fd(&fd_in); return -1; } /* try to change the group, don't really care if it fails */ if (fchown(fd_out, (uid_t)-1, st.st_gid) < 0 && errno != EPERM) i_error("fchown(%s) failed: %m", tmppath); input = i_stream_create_fd(fd_in, IO_BLOCK_SIZE); output = o_stream_create_fd_file(fd_out, 0, FALSE); switch (o_stream_send_istream(output, input)) { case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: ret = 0; break; case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: i_unreached(); case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: i_error("read(%s) failed: %s", srcpath, i_stream_get_error(input)); break; case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: i_error("write(%s) failed: %s", tmppath, o_stream_get_error(output)); break; } i_stream_destroy(&input); o_stream_destroy(&output); if (close(fd_in) < 0) { i_error("close(%s) failed: %m", srcpath); ret = -1; } if (close(fd_out) < 0) { i_error("close(%s) failed: %m", tmppath); ret = -1; } return ret < 0 ? -1 : 1; } int file_copy(const char *srcpath, const char *destpath, bool try_hardlink) { int ret; T_BEGIN { const char *tmppath; tmppath = t_strconcat(destpath, ".tmp", NULL); ret = file_copy_to_tmp(srcpath, tmppath, try_hardlink); if (ret > 0) { if (rename(tmppath, destpath) < 0) { i_error("rename(%s, %s) failed: %m", tmppath, destpath); ret = -1; } } if (ret < 0) i_unlink(tmppath); } T_END; return ret; } dovecot-2.3.21.1/src/lib/var-expand.h0000644000000000000000000000443014656633576014071 00000000000000#ifndef VAR_EXPAND_H #define VAR_EXPAND_H struct var_expand_table { char key; const char *value; const char *long_key; }; struct var_expand_func_table { const char *key; /* %{key:data}, or data is "" with %{key}. Returns 1 on success, 0 if data is invalid, -1 on temporary error. */ int (*func)(const char *data, void *context, const char **value_r, const char **error_r); }; /* Expand % variables in src and append the string in dest. table must end with key = 0. Returns 1 on success, 0 if the format string contained invalid/unknown %variables, -1 if one of the functions returned temporary error. Even in case of errors the dest string is still written as fully as possible. */ int var_expand(string_t *dest, const char *str, const struct var_expand_table *table, const char **error_r); /* Like var_expand(), but support also callback functions for variable expansion. */ int var_expand_with_funcs(string_t *dest, const char *str, const struct var_expand_table *table, const struct var_expand_func_table *func_table, void *func_context, const char **error_r) ATTR_NULL(3, 4, 5); /* Returns the actual key character for given string, ie. skip any modifiers that are before it. The string should be the data after the '%' character. For %{long_variable}, '{' is returned. */ char var_get_key(const char *str) ATTR_PURE; /* Similar to var_get_key(), but works for long keys as well. For single char keys size=1, while for e.g. %{key} size=3 and idx points to 'k'. */ void var_get_key_range(const char *str, unsigned int *idx_r, unsigned int *size_r); /* Returns TRUE if key variable is used in the string. If key is '\0', it's ignored. If long_key is NULL, it's ignored. */ bool var_has_key(const char *str, char key, const char *long_key) ATTR_PURE; static inline size_t ATTR_PURE var_expand_table_size(const struct var_expand_table *table) { size_t n = 0; while(table != NULL && (table[n].key != '\0' || table[n].long_key != NULL)) n++; return n; } struct var_expand_table * var_expand_merge_tables(pool_t pool, const struct var_expand_table *a, const struct var_expand_table *b); #define t_var_expand_merge_tables(a, b) \ (const struct var_expand_table *)var_expand_merge_tables(pool_datastack_create(), (a), (b)) #endif dovecot-2.3.21.1/src/lib/ioloop-private.h0000644000000000000000000000561514656633576015003 00000000000000#ifndef IOLOOP_PRIVATE_H #define IOLOOP_PRIVATE_H #include "priorityq.h" #include "ioloop.h" #include "array-decl.h" #ifndef IOLOOP_INITIAL_FD_COUNT # define IOLOOP_INITIAL_FD_COUNT 128 #endif struct ioloop { struct ioloop *prev; struct ioloop_context *cur_ctx; struct io_file *io_files; struct io_file *next_io_file; struct priorityq *timeouts; ARRAY(struct timeout *) timeouts_new; struct io_wait_timer *wait_timers; struct ioloop_handler_context *handler_context; struct ioloop_notify_handler_context *notify_handler_context; unsigned int max_fd_count; io_loop_time_moved_callback_t *time_moved_callback; struct timeval next_max_time; uint64_t ioloop_wait_usecs; struct timeval wait_started; unsigned int io_pending_count; bool running:1; bool iolooping:1; bool stop_after_run_loop:1; }; struct io { enum io_condition condition; const char *source_filename; unsigned int source_linenum; /* trigger I/O callback even if OS doesn't think there is input pending */ bool pending; /* This IO event shouldn't be the only thing being waited on, because it would just result in infinite wait. */ bool never_wait_alone; io_callback_t *callback; void *context; struct ioloop *ioloop; struct ioloop_context *ctx; }; struct io_file { struct io io; /* use a doubly linked list so that io_remove() is quick */ struct io_file *prev, *next; int refcount; int fd; /* only for io_add_istream(), a bit kludgy to be here.. */ struct istream *istream; }; struct timeout { struct priorityq_item item; const char *source_filename; unsigned int source_linenum; unsigned int msecs; struct timeval next_run; timeout_callback_t *callback; void *context; struct ioloop *ioloop; struct ioloop_context *ctx; bool one_shot:1; }; struct io_wait_timer { struct io_wait_timer *prev, *next; const char *source_filename; unsigned int source_linenum; struct ioloop *ioloop; uint64_t usecs; }; struct ioloop_context_callback { io_callback_t *activate; io_callback_t *deactivate; void *context; bool activated; }; struct ioloop_context { int refcount; struct ioloop *ioloop; ARRAY(struct ioloop_context_callback) callbacks; ARRAY(struct event *) global_event_stack; struct event *root_global_event; }; int io_loop_run_get_wait_time(struct ioloop *ioloop, struct timeval *tv_r); void io_loop_handle_timeouts(struct ioloop *ioloop); void io_loop_call_io(struct io *io); void io_loop_handler_run_internal(struct ioloop *ioloop); /* I/O handler calls */ void io_loop_handle_add(struct io_file *io); void io_loop_handle_remove(struct io_file *io, bool closed); void io_loop_handler_init(struct ioloop *ioloop, unsigned int initial_fd_count); void io_loop_handler_deinit(struct ioloop *ioloop); void io_loop_notify_remove(struct io *io); void io_loop_notify_handler_deinit(struct ioloop *ioloop); struct event *io_loop_get_active_global_root(void); #endif dovecot-2.3.21.1/src/lib/bsearch-insert-pos.c0000644000000000000000000000205114656633576015524 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "bsearch-insert-pos.h" #undef bsearch_insert_pos bool bsearch_insert_pos(const void *key, const void *base, unsigned int nmemb, size_t size, int (*cmp)(const void *, const void *), unsigned int *idx_r) { const void *p; unsigned int idx, left_idx, right_idx; int ret; i_assert(nmemb < INT_MAX); idx = 0; left_idx = 0; right_idx = nmemb; while (left_idx < right_idx) { idx = (left_idx + right_idx) / 2; p = CONST_PTR_OFFSET(base, idx * size); ret = cmp(key, p); if (ret > 0) left_idx = idx+1; else if (ret < 0) right_idx = idx; else { *idx_r = idx; return TRUE; } } if (left_idx > idx) idx++; *idx_r = idx; return FALSE; } bool array_bsearch_insert_pos_i(const struct array *array, const void *key, int (*cmp)(const void *, const void *), unsigned int *idx_r) { return bsearch_insert_pos(key, array->buffer->data, array_count_i(array), array->element_size, cmp, idx_r); } dovecot-2.3.21.1/src/lib/unlink-old-files.c0000644000000000000000000000326114656633576015174 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "str.h" #include "unlink-old-files.h" #include #include #include #include #include static int unlink_old_files_real(const char *dir, const char *prefix, time_t min_time) { DIR *dirp; struct dirent *d; struct stat st; string_t *path; size_t prefix_len, dir_len; dirp = opendir(dir); if (dirp == NULL) { if (errno != ENOENT) i_error("opendir(%s) failed: %m", dir); return -1; } /* update atime immediately, so if this scanning is done based on atime it won't be done by multiple processes if the scan is slow */ if (utime(dir, NULL) < 0 && errno != ENOENT) i_error("utime(%s) failed: %m", dir); path = t_str_new(256); str_printfa(path, "%s/", dir); dir_len = str_len(path); prefix_len = strlen(prefix); while ((d = readdir(dirp)) != NULL) { if (d->d_name[0] == '.' && (d->d_name[1] == '\0' || (d->d_name[1] == '.' && d->d_name[2] == '\0'))) { /* skip . and .. */ continue; } if (strncmp(d->d_name, prefix, prefix_len) != 0) continue; str_truncate(path, dir_len); str_append(path, d->d_name); if (stat(str_c(path), &st) < 0) { if (errno != ENOENT) i_error("stat(%s) failed: %m", str_c(path)); } else if (!S_ISDIR(st.st_mode) && st.st_ctime < min_time) { i_unlink_if_exists(str_c(path)); } } if (closedir(dirp) < 0) i_error("closedir(%s) failed: %m", dir); return 0; } int unlink_old_files(const char *dir, const char *prefix, time_t min_time) { int ret; T_BEGIN { ret = unlink_old_files_real(dir, prefix, min_time); } T_END; return ret; } dovecot-2.3.21.1/src/lib/mmap-anon.c0000644000000000000000000000757014656633576013712 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ /* @UNSAFE: whole file */ #include "lib.h" #include "mmap-util.h" #include #ifndef MAP_ANONYMOUS # ifdef MAP_ANON # define MAP_ANONYMOUS MAP_ANON # else # define MAP_ANONYMOUS 0 # endif #endif #ifndef HAVE_LINUX_MREMAP #include #define MMAP_SIGNATURE 0xdeadbeef #define PAGE_ALIGN(size) \ (((size) + (size_t)page_size-1) & ~(size_t)(page_size-1)) struct anon_header { unsigned int signature; size_t size; }; static int page_size = 0; static int header_size = 0; static int zero_fd = -1; static void movable_mmap_init(void) { #if MAP_ANONYMOUS == 0 /* mmap()ing /dev/zero should be the same with some platforms */ zero_fd = open("/dev/zero", O_RDWR); if (zero_fd == -1) i_fatal("Can't open /dev/zero for creating anonymous mmap: %m"); fd_close_on_exec(zero_fd, TRUE); #endif page_size = getpagesize(); header_size = page_size; } void *mmap_anon(size_t length) { struct anon_header *hdr; void *base; if (header_size == 0) movable_mmap_init(); /* we need extra page to store the pieces which construct the full mmap. also allocate only page-aligned mmap sizes. */ length = PAGE_ALIGN(length + header_size); base = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, zero_fd, 0); if (base == MAP_FAILED) return MAP_FAILED; /* initialize the header */ hdr = base; hdr->signature = MMAP_SIGNATURE; hdr->size = length - header_size; return (char *) hdr + header_size; } static void *mremap_move(struct anon_header *hdr, size_t new_size) { void *new_base; char *p; size_t block_size, old_size; new_base = mmap_anon(new_size); if (new_base == MAP_FAILED) return MAP_FAILED; /* If we're moving large memory areas, it takes less memory to copy the memory pages in smaller blocks. */ old_size = hdr->size; block_size = 1024*1024; p = (char *) hdr + header_size + hdr->size; do { if (block_size > old_size) block_size = old_size; p -= block_size; old_size -= block_size; memcpy((char *) new_base + old_size, p, block_size); if (munmap((void *) p, block_size) < 0) i_panic("munmap() failed: %m"); } while (old_size != 0); if (munmap((void *) hdr, header_size) < 0) i_panic("munmap() failed: %m"); return new_base; } void *mremap_anon(void *old_address, size_t old_size ATTR_UNUSED, size_t new_size, unsigned long flags) { struct anon_header *hdr; if (old_address == NULL || old_address == MAP_FAILED) { errno = EINVAL; return MAP_FAILED; } hdr = (struct anon_header *) ((char *) old_address - header_size); if (hdr->signature != MMAP_SIGNATURE) i_panic("movable_mremap(): Invalid old_address"); new_size = PAGE_ALIGN(new_size); if (new_size > hdr->size) { /* grow */ if ((flags & MREMAP_MAYMOVE) == 0) { errno = ENOMEM; return MAP_FAILED; } return mremap_move(hdr, new_size); } if (new_size < hdr->size) { /* shrink */ if (munmap((void *) ((char *) hdr + header_size + new_size), hdr->size - new_size) < 0) i_panic("munmap() failed: %m"); hdr->size = new_size; } return old_address; } int munmap_anon(void *start, size_t length ATTR_UNUSED) { struct anon_header *hdr; if (start == NULL || start == MAP_FAILED) { errno = EINVAL; return -1; } hdr = (struct anon_header *) ((char *) start - header_size); if (hdr->signature != MMAP_SIGNATURE) i_panic("movable_munmap(): Invalid address"); if (munmap((void *) hdr, hdr->size + header_size) < 0) i_panic("munmap() failed: %m"); return 0; } #else void *mmap_anon(size_t length) { return mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); } void *mremap_anon(void *old_address, size_t old_size, size_t new_size, unsigned long flags) { return mremap(old_address, old_size, new_size, flags); } int munmap_anon(void *start, size_t length) { return munmap(start, length); } #endif dovecot-2.3.21.1/src/lib/istream-jsonstr.c0000644000000000000000000001163014656633576015163 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "hex-dec.h" #include "unichar.h" #include "istream-private.h" #include "istream-jsonstr.h" #define MAX_UTF8_LEN 6 struct jsonstr_istream { struct istream_private istream; /* The end '"' was found */ bool str_end:1; }; static int i_stream_jsonstr_read_parent(struct jsonstr_istream *jstream, unsigned int min_bytes) { struct istream_private *stream = &jstream->istream; size_t size, avail; ssize_t ret; size = i_stream_get_data_size(stream->parent); while (size < min_bytes) { ret = i_stream_read_memarea(stream->parent); if (ret <= 0) { if (ret == -2) { /* tiny parent buffer size - shouldn't happen */ return -2; } stream->istream.stream_errno = stream->parent->stream_errno; stream->istream.eof = stream->parent->eof; if (ret == -1 && stream->istream.stream_errno == 0) { io_stream_set_error(&stream->iostream, "EOF before trailing <\"> was seen"); stream->istream.stream_errno = EPIPE; } return ret; } size = i_stream_get_data_size(stream->parent); } if (!i_stream_try_alloc(stream, size, &avail)) return -2; return 1; } static int i_stream_json_unescape(const unsigned char *src, size_t len, unsigned char *dest, unsigned int *src_size_r, unsigned int *dest_size_r) { switch (*src) { case '"': case '\\': case '/': *dest = *src; break; case 'b': *dest = '\b'; break; case 'f': *dest = '\f'; break; case 'n': *dest = '\n'; break; case 'r': *dest = '\r'; break; case 't': *dest = '\t'; break; case 'u': { char chbuf[5] = {0}; unichar_t chr,chr2 = 0; buffer_t buf; if (len < 5) return 5; buffer_create_from_data(&buf, dest, MAX_UTF8_LEN); memcpy(chbuf, src+1, 4); if (str_to_uint32_hex(chbuf, &chr)<0) return -1; if (UTF16_VALID_LOW_SURROGATE(chr)) return -1; /* if we encounter surrogate, we need another \\uxxxx */ if (UTF16_VALID_HIGH_SURROGATE(chr)) { if (len < 5+2+4) return 5+2+4; if (src[5] != '\\' && src[6] != 'u') return -1; memcpy(chbuf, src+7, 4); if (str_to_uint32_hex(chbuf, &chr2)<0) return -1; if (!UTF16_VALID_LOW_SURROGATE(chr2)) return -1; chr = uni_join_surrogate(chr, chr2); } if (!uni_is_valid_ucs4(chr)) return -1; uni_ucs4_to_utf8_c(chr, &buf); *src_size_r = 5 + (chr2>0?6:0); *dest_size_r = buf.used; return 0; } default: return -1; } *src_size_r = 1; *dest_size_r = 1; return 0; } static ssize_t i_stream_jsonstr_read(struct istream_private *stream) { struct jsonstr_istream *jstream = container_of(stream, struct jsonstr_istream, istream); const unsigned char *data; unsigned int srcskip, destskip, extra; size_t i, dest, size; ssize_t ret, ret2; if (jstream->str_end) { stream->istream.eof = TRUE; return -1; } ret = i_stream_jsonstr_read_parent(jstream, 1); if (ret <= 0) return ret; /* @UNSAFE */ dest = stream->pos; extra = 0; data = i_stream_get_data(stream->parent, &size); for (i = 0; i < size && dest < stream->buffer_size; ) { if (data[i] == '"') { jstream->str_end = TRUE; if (dest == stream->pos) { stream->istream.eof = TRUE; return -1; } break; } else if (data[i] == '\\') { if (i+1 == size) { /* not enough input for \x */ extra = 1; break; } if (data[i+1] == 'u' && stream->buffer_size - dest < MAX_UTF8_LEN) { /* UTF8 output is max. 6 chars */ if (dest == stream->pos) return -2; break; } i++; if ((ret2 = i_stream_json_unescape(data + i, size - i, stream->w_buffer + dest, &srcskip, &destskip)) < 0) { /* invalid string */ io_stream_set_error(&stream->iostream, "Invalid JSON string"); stream->istream.stream_errno = EINVAL; return -1; } else if (ret2 > 0) { /* we need to get more bytes, do not consume escape slash */ i--; extra = ret2; break; } i += srcskip; i_assert(i <= size); dest += destskip; i_assert(dest <= stream->buffer_size); } else { stream->w_buffer[dest++] = data[i]; i++; } } i_stream_skip(stream->parent, i); ret = dest - stream->pos; if (ret == 0) { /* not enough input */ i_assert(i == 0); i_assert(extra > 0); ret = i_stream_jsonstr_read_parent(jstream, extra+1); if (ret <= 0) return ret; return i_stream_jsonstr_read(stream); } i_assert(ret > 0); stream->pos = dest; return ret; } struct istream *i_stream_create_jsonstr(struct istream *input) { struct jsonstr_istream *dstream; dstream = i_new(struct jsonstr_istream, 1); dstream->istream.max_buffer_size = input->real_stream->max_buffer_size; dstream->istream.read = i_stream_jsonstr_read; dstream->istream.istream.readable_fd = FALSE; dstream->istream.istream.blocking = input->blocking; dstream->istream.istream.seekable = FALSE; return i_stream_create(&dstream->istream, input, i_stream_get_fd(input), 0); } dovecot-2.3.21.1/src/lib/fsync-mode.h0000644000000000000000000000055414656633576014073 00000000000000#ifndef FSYNC_MODE_H #define FSYNC_MODE_H enum fsync_mode { /* fsync when it's necessary for data safety. */ FSYNC_MODE_OPTIMIZED = 0, /* never fsync (in case of a crash can lose data) */ FSYNC_MODE_NEVER, /* fsync after all writes. this is necessary with NFS to avoid write failures being delayed until file is close(). */ FSYNC_MODE_ALWAYS }; #endif dovecot-2.3.21.1/src/lib/ostream-wrapper.c0000644000000000000000000010106014656633576015144 00000000000000/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "ostream-private.h" #include "ostream-wrapper.h" static int wrapper_ostream_flush(struct ostream_private *stream); static void wrapper_ostream_switch_ioloop_to(struct ostream_private *stream, struct ioloop *ioloop); /* * Buffer */ /* Determine the optimum buffer size for the wrapper stream itself. */ static inline size_t wrapper_ostream_optimal_size(struct wrapper_ostream *wostream) { size_t optimal_size = wostream->ostream.max_buffer_size; if (wostream->output != NULL) { optimal_size = I_MIN( o_stream_get_max_buffer_size(wostream->output), optimal_size); } if (optimal_size == SIZE_MAX) optimal_size = IO_BLOCK_SIZE; return optimal_size; } /* Return the current size of the wrapper output stream buffer. */ static inline size_t wrapper_ostream_size(struct wrapper_ostream *wostream) { buffer_t *buffer = wostream->buffer; if (buffer == NULL) return 0; return buffer->used; } /* Return TRUE when the wrapper stream's internal buffer is empty. */ static inline bool wrapper_ostream_is_empty(struct wrapper_ostream *wostream) { return (wrapper_ostream_size(wostream) == 0); } /* Return TRUE when the wrapper stream's internal buffer is filled to the maximum. */ static inline bool wrapper_ostream_is_full(struct wrapper_ostream *wostream) { return (wrapper_ostream_size(wostream) >= wostream->ostream.max_buffer_size); } /* Return TRUE when the wrapper stream's internal buffer is filled at or beyond the optimum. */ static inline bool wrapper_ostream_is_filled(struct wrapper_ostream *wostream) { return (wrapper_ostream_size(wostream) >= wrapper_ostream_optimal_size(wostream)); } /* * Underlying output */ /* Handle error in the underlying output stream (the parent). */ static void wrapper_ostream_copy_parent_error(struct wrapper_ostream *wostream) { i_assert(wostream->output != NULL); i_assert(wostream->output->stream_errno != 0); wostream->ostream.ostream.stream_errno = wostream->output->stream_errno; wostream->ostream.ostream.overflow = wostream->output->overflow; } static void wrapper_ostream_handle_parent_error(struct wrapper_ostream *wostream) { wrapper_ostream_copy_parent_error(wostream); if (wostream->output->closed) o_stream_close(&wostream->ostream.ostream); if (wostream->output_error != NULL) wostream->output_error(wostream); } static void wrapper_ostream_closed(struct wrapper_ostream *wostream) { wostream->ostream.ostream.closed = TRUE; } /* Drop the underlying output. */ static void wrapper_ostream_output_close(struct wrapper_ostream *wostream) { o_stream_unref(&wostream->output); wostream->output_finished = TRUE; wostream->output_closed = TRUE; wostream->output_closed_api = TRUE; } /* Method calls */ /* Called when the implementation should start making the parent output stream available, e.g. connect to the server (see output_start() method). */ static void wrapper_ostream_output_start(struct wrapper_ostream *wostream) { if (wostream->output_started) return; wostream->output_started = TRUE; if (wostream->output_start != NULL) wostream->output_start(wostream); } /* Returns TRUE when the output is ready for data (see output_ready() method). */ static bool wrapper_ostream_output_ready(struct wrapper_ostream *wostream) { i_assert(wostream->output_ready != NULL); return wostream->output_ready(wostream); } /* Finish the underlying output (see output_finish() method).*/ static int wrapper_ostream_output_finish(struct wrapper_ostream *wostream) { i_assert(wostream->output_finish != NULL); return wostream->output_finish(wostream); } /* Called when the wrapper ostream does not need write to parent output stream. (see output_halt() method). */ static void wrapper_ostream_output_halt(struct wrapper_ostream *wostream) { if (wostream->output_closed) return; if (wostream->output_halt != NULL) wostream->output_halt(wostream); } /* Called when the wrapper ostream has data available for the parent output and wants wrapper_ostream_continue() to be called when the parent stream is writeable (see output_resume() method). */ static void wrapper_ostream_output_resume(struct wrapper_ostream *wostream) { if (wostream->output_closed) return; if (wostream->output_resume != NULL) wostream->output_resume(wostream); } /* Update any timeouts for the underlying (parent) output (see output_update_timeouts() method). */ static void wrapper_ostream_output_update_timeouts(struct wrapper_ostream *wostream) { struct ostream_private *stream = &wostream->ostream; bool sender_blocking; if (wostream->output_closed) return; if (wostream->output_update_timeouts == NULL) return; sender_blocking = (!stream->finished && (wrapper_ostream_is_empty(wostream) || (stream->corked && !wrapper_ostream_is_filled(wostream)))); wostream->output_update_timeouts(wostream, sender_blocking); } /* * Wrapper */ /* Halt/resume the underlying output based on the state of the wrapper stream. */ static void wrapper_ostream_output_manage(struct wrapper_ostream *wostream, bool sending) { struct ostream_private *stream = &wostream->ostream; bool must_flush, no_data; if (wostream->output_closed) return; must_flush = (sending || stream->finished || wostream->flush_pending); no_data = (wrapper_ostream_is_empty(wostream) || (stream->corked && !wrapper_ostream_is_filled(wostream))); if (!must_flush && (no_data || stream->ostream.closed)) wrapper_ostream_output_halt(wostream); else { wrapper_ostream_output_resume(wostream); if (wostream->output != NULL && must_flush) o_stream_set_flush_pending(wostream->output, TRUE); } } /* Handle any pending error by making it available to the application through the output stream API. */ static int wrapper_ostream_handle_pending_error(struct wrapper_ostream *wostream) { struct ostream_private *stream = &wostream->ostream; if (wostream->pending_errno != 0) { if (wostream->pending_error != NULL) { io_stream_set_error(&stream->iostream, "%s", wostream->pending_error); } stream->ostream.stream_errno = wostream->pending_errno; wostream->pending_errno = 0; wostream->returned_error = TRUE; wrapper_ostream_closed(wostream); i_free_and_null(wostream->pending_error); return -1; } return 0; } /* Called when the wrapper stream is first finished using o_stream_finish(). */ static int wrapper_ostream_finish(struct wrapper_ostream *wostream) { int ret; if (wostream->output_closed) { if (wrapper_ostream_handle_pending_error(wostream) < 0) return -1; return 1; } if (!wrapper_ostream_output_ready(wostream)) { return 0; } wostream->output_finished = TRUE; if (wostream->output != NULL) { if (o_stream_uncork_flush(wostream->output) < 0) { wrapper_ostream_handle_parent_error(wostream); o_stream_unref(&wostream->output); return -1; } } /* Finished sending payload; now also finish the underlying output. */ ret = wrapper_ostream_output_finish(wostream); if (ret == 0) return ret; if (ret < 0 && wostream->ostream.ostream.stream_errno != 0) { wrapper_ostream_copy_parent_error(wostream); return -1; } if (wrapper_ostream_handle_pending_error(wostream) < 0 || ret < 0) { i_assert(wostream->ostream.ostream.stream_errno != 0); return -1; } wrapper_ostream_output_close(wostream); return 1; } /* Wait in ioloop until underlying (parent) output can be flushed. This is called only when the wrapper stream is blocking. */ static int wrapper_ostream_flush_wait(struct wrapper_ostream *wostream) { struct ostream_private *stream = &wostream->ostream; struct ioloop *ioloop, *prev_ioloop; bool was_corked = FALSE; wrapper_ostream_output_manage(wostream, !wostream->flushing); /* Cannot be already waiting */ i_assert(!wostream->flush_waiting); i_assert(wostream->flush_ioloop == NULL); i_assert(wostream->wait_begin != NULL); i_assert(wostream->wait_end != NULL); if (wostream->output != NULL && o_stream_is_corked(wostream->output)) { /* Make sure parent is uncorked here to make sure output IO is active. */ if (o_stream_uncork_flush(wostream->output) < 0) { wrapper_ostream_handle_parent_error(wostream); return -1; } was_corked = TRUE; } wostream->flush_ioloop = ioloop = io_loop_create(); prev_ioloop = wostream->wait_begin(wostream, ioloop); o_stream_switch_ioloop_to(&wostream->ostream.ostream, ioloop); /* Either we're waiting for network I/O or we're getting out of a callback using timeout_add_short(0) */ i_assert(io_loop_have_ios(ioloop) || io_loop_have_immediate_timeouts(ioloop)); wostream->flush_waiting = TRUE; do { e_debug(wostream->event, "Waiting for output flush"); io_loop_run(ioloop); } while (wostream->flush_waiting); e_debug(wostream->event, "Can now flush output"); o_stream_switch_ioloop_to(&wostream->ostream.ostream, prev_ioloop); wostream->wait_end(wostream, prev_ioloop); io_loop_destroy(&ioloop); wostream->flush_ioloop = NULL; if (stream->ostream.blocking) wrapper_ostream_output_halt(wostream); if (was_corked && wostream->output != NULL) o_stream_cork(wostream->output); if (wrapper_ostream_handle_pending_error(wostream) < 0) { /* Stream already hit an error */ return -1; } return 0; } /* Try to flush the underlying (parent) output. */ static int wrapper_ostream_flush_parent(struct wrapper_ostream *wostream) { struct ostream *parent; if (wostream->output_closed) { /* Output already dropped; nothing to flush */ return 1; } if (!wrapper_ostream_output_ready(wostream)) { /* There is no parent ostream yet */ return 1; } parent = wostream->output; if (parent == NULL) { /* There is no parent ostream anymore */ i_assert(wostream->buffer == NULL || wostream->buffer->used == 0); return 1; } if (o_stream_get_buffer_used_size(parent) >= IO_BLOCK_SIZE) { /* We already have quite a lot of data in parent stream. unless we can flush it, don't add any more to it or we could keep wasting memory by just increasing the buffer size all the time. */ if (o_stream_flush(parent) < 0) { wrapper_ostream_handle_parent_error(wostream); return -1; } if (o_stream_get_buffer_used_size(parent) >= IO_BLOCK_SIZE) return 0; } return 1; } /* Try to write data to underlying (parent) output. */ static ssize_t wrapper_ostream_writev(struct wrapper_ostream *wostream, const struct const_iovec *iov, unsigned int iov_count) { struct ostream *parent = wostream->output; ssize_t sent; i_assert(!wostream->output_closed); i_assert(!wostream->output_finished); if (!wrapper_ostream_output_ready(wostream)) return 0; /* Send more data to parent ostream */ i_assert(parent != NULL); o_stream_set_max_buffer_size(parent, IO_BLOCK_SIZE); sent = o_stream_sendv(parent, iov, iov_count); o_stream_set_max_buffer_size(parent, SIZE_MAX); if (sent < 0) { wrapper_ostream_handle_parent_error(wostream); return -1; } return sent; } /* Try to write data to underlying (parent) output and implement blocking behavior by running an ioloop. */ static ssize_t wrapper_ostream_writev_full(struct wrapper_ostream *wostream, const struct const_iovec *iov, unsigned int iov_count) { struct ostream_private *stream = &wostream->ostream; unsigned int i; ssize_t sent, sent_total; if (!stream->ostream.blocking) { /* Not blocking; send what we can */ return wrapper_ostream_writev(wostream, iov, iov_count); } /* Blocking; loop and wait until all is sent */ sent_total = 0; for (;;) { struct const_iovec niov; size_t iov_pos; i_assert(iov_count > 0); /* Send iovec with complete entries */ sent = wrapper_ostream_writev(wostream, iov, iov_count); if (sent < 0) return -1; if (sent == 0) { if (wrapper_ostream_flush_wait(wostream) < 0) return -1; i_assert(!wostream->output_closed); continue; } /* Determine what was sent */ sent_total += sent; iov_pos = (size_t)sent; for (i = 0; i < iov_count && iov_pos >= iov[i].iov_len; i++) iov_pos -= iov[i].iov_len; if (i >= iov_count) { /* All sent */ i_assert(iov_pos == 0); return sent_total; } iov = &iov[i]; iov_count -= i; if (iov_pos == 0) { /* Nicely sent until an iovec boundary */ continue; } /* Send partial iovec entry */ i_zero(&niov); niov = iov[0]; i_assert(iov_pos < niov.iov_len); niov.iov_base = CONST_PTR_OFFSET(niov.iov_base, iov_pos); niov.iov_len -= iov_pos; while (niov.iov_len > 0) { sent = wrapper_ostream_writev(wostream, &niov, 1); if (sent < 0) return sent; if (sent == 0) { if (wrapper_ostream_flush_wait(wostream) < 0) return -1; i_assert(!wostream->output_closed); continue; } i_assert((size_t)sent <= niov.iov_len); niov.iov_base = CONST_PTR_OFFSET(niov.iov_base, sent); niov.iov_len -= sent; sent_total += sent; } if (iov_count == 1) { i_assert(sent_total != 0); return sent_total; } /* Now sent until an iovec boundary */ iov = &iov[1]; iov_count--; } i_unreached(); } /* Try to flush wrapper stream's buffer content. */ static int wrapper_ostream_flush_buffer(struct wrapper_ostream *wostream) { struct ostream_private *stream = &wostream->ostream; buffer_t *buffer = wostream->buffer; struct const_iovec iov; ssize_t sent; if (wostream->output_closed) { /* Ostream already finished */ i_assert(wostream->ostream.finished); return 1; } if (buffer == NULL || buffer->used == 0) { /* Buffer already empty */ return 1; } do { /* Try to flush whole buffer */ iov.iov_base = buffer->data; iov.iov_len = buffer->used; sent = wrapper_ostream_writev_full(wostream, &iov, 1); if (sent < 0) return -1; /* Remove sent data from buffer */ buffer_delete(buffer, 0, sent); /* More aggressively flush the buffer when this stream is finished */ } while (wostream->ostream.finished && sent > 0 && buffer->used > 0); if (buffer->used == 0 || (stream->corked && !wrapper_ostream_is_filled(wostream))) wrapper_ostream_output_halt(wostream); return (buffer->used == 0 ? 1 : 0); } static int wrapper_ostream_flush_real(struct wrapper_ostream *wostream) { struct ostream_private *stream = &wostream->ostream; int ret; if (wrapper_ostream_handle_pending_error(wostream) < 0) { /* Stream already hit an error */ return -1; } wrapper_ostream_output_start(wostream); if ((ret = wrapper_ostream_flush_parent(wostream)) <= 0) { /* Try to flush parent stream first to make room for more data */ return ret; } if ((ret = wrapper_ostream_flush_buffer(wostream)) <= 0) { /* Try sending data we already buffered */ return ret; } if (wostream->output_closed || wostream->output_finished) { /* Already finished the ostream */ i_assert(stream->finished); return 1; } if (!wrapper_ostream_output_ready(wostream)) { return ((wostream->buffer == NULL || wostream->buffer->used == 0) ? 1 : 0); } if (wostream->output == NULL) { i_assert(wrapper_ostream_is_empty(wostream)); ret = 1; } else { ret = o_stream_flush(wostream->output); if (ret < 0) wrapper_ostream_handle_parent_error(wostream); } return ret; } static bool wrapper_ostream_send_prepare(struct wrapper_ostream *wostream, size_t size) { struct ostream_private *stream = &wostream->ostream; if (wostream->output_closed || wostream->output_started) return TRUE; if (stream->corked && !stream->finished) { if (wostream->buffer == NULL) return FALSE; if ((wostream->buffer->used + size) < stream->max_buffer_size) return FALSE; } wrapper_ostream_output_start(wostream); return TRUE; } /* Add data to the wrapper stream's internal buffer. */ static size_t wrapper_ostream_add(struct wrapper_ostream *wostream, const struct const_iovec *iov, unsigned int iov_count, unsigned int *iov_idx, size_t *iov_idx_pos) { buffer_t *buffer = wostream->buffer; unsigned int i; size_t added = 0; /* Create buffer */ if (buffer == NULL) { wostream->buffer = buffer = buffer_create_dynamic(default_pool, IO_BLOCK_SIZE); } for (i = *iov_idx; i < iov_count; i++) { size_t iov_len, iov_add, space; const unsigned char *iov_data; iov_len = iov[i].iov_len; iov_data = iov[i].iov_base; space = wostream->ostream.max_buffer_size - buffer->used; i_assert(*iov_idx_pos < iov_len); if (*iov_idx_pos > 0) { iov_len -= *iov_idx_pos; iov_data += *iov_idx_pos; } iov_add = I_MIN(space, iov_len); buffer_append(buffer, iov_data, iov_add); added += iov_add; if (iov_add < iov_len) { /* Buffer is full */ *iov_idx_pos += iov_add; break; } *iov_idx_pos = 0; } *iov_idx = i; return added; } static ssize_t wrapper_ostream_sendv_real(struct wrapper_ostream *wostream, const struct const_iovec *iov, unsigned int iov_count) { struct ostream_private *stream = &wostream->ostream; ssize_t written; size_t size, iov_pos, sent; unsigned int i; int ret; if (wrapper_ostream_handle_pending_error(wostream) < 0) { /* Stream already hit an error */ return -1; } i_assert(!wostream->output_closed); i_assert(!wostream->output_finished); /* Determine total size of data to send */ size = 0; for (i = 0; i < iov_count; i++) size += iov[i].iov_len; /* Flush buffer if required */ if (!wrapper_ostream_is_empty(wostream) && (!stream->corked || wrapper_ostream_is_filled(wostream)) && wrapper_ostream_send_prepare(wostream, size) && wrapper_ostream_flush_buffer(wostream) < 0) return -1; if (!stream->corked && wrapper_ostream_is_full(wostream)) { /* No space in buffer for more data */ i_assert(!stream->ostream.blocking); return 0; } /* Send data to connection directly if possible */ i = 0; sent = iov_pos = 0; if (wrapper_ostream_is_empty(wostream) && (!stream->corked || size >= wrapper_ostream_optimal_size(wostream)) && wrapper_ostream_send_prepare(wostream, size)) { written = wrapper_ostream_writev_full(wostream, iov, iov_count); if (written < 0) return -1; sent += written; if (sent == size) { /* All sent */ return (ssize_t)sent; } i_assert(!stream->ostream.blocking); /* Determine send position */ iov_pos = sent; for (; i < iov_count && iov_pos >= iov[i].iov_len; i++) iov_pos -= iov[i].iov_len; i_assert(i < iov_count); } /* Fill buffer with remainder that was not sent directly */ for (;;) { sent += wrapper_ostream_add(wostream, iov, iov_count, &i, &iov_pos); i_assert(sent <= size); if (!stream->corked || !wrapper_ostream_is_filled(wostream)) break; /* Flush corked full buffer */ wrapper_ostream_output_start(wostream); if ((ret = wrapper_ostream_flush_buffer(wostream)) < 0) return -1; if (ret == 0) break; } i_assert(!stream->ostream.blocking || sent == size); return sent; } /* Run the flush callback for the wrapper stream. */ static int wrapper_ostream_callback(struct wrapper_ostream *wostream) { int ret; if (wostream->ostream.callback != NULL) { if (wostream->callback_pre != NULL) wostream->callback_pre(wostream); ret = wostream->ostream.callback(wostream->ostream.context); if (wostream->callback_post != NULL) wostream->callback_post(wostream); } else { ret = wrapper_ostream_flush(&wostream->ostream); } return ret; } /* Handle an event by running wrapper_ostream_continue(). This called from ioloop on a zero timeout. */ static void wrapper_ostream_handle_event(struct wrapper_ostream *wostream) { timeout_remove(&wostream->to_event); (void)wrapper_ostream_continue(wostream); } /* * iostream methods */ static void wrapper_ostream_close(struct iostream_private *stream, bool close_parent ATTR_UNUSED) { struct wrapper_ostream *wostream = container_of(stream, struct wrapper_ostream, ostream.iostream); timeout_remove(&wostream->to_event); wrapper_ostream_output_close(wostream); if (wostream->close != NULL) wostream->close(wostream); } static void wrapper_ostream_destroy(struct iostream_private *stream) { struct wrapper_ostream *wostream = container_of(stream, struct wrapper_ostream, ostream.iostream); timeout_remove(&wostream->to_event); i_free(wostream->pending_error); if (wostream->destroy != NULL) wostream->destroy(wostream); buffer_free(&wostream->buffer); o_stream_unref(&wostream->output); event_unref(&wostream->event); } /* * ostream methods */ static void wrapper_ostream_cork(struct ostream_private *stream, bool set) { struct wrapper_ostream *wostream = container_of(stream, struct wrapper_ostream, ostream); int ret; if (stream->ostream.closed || wostream->pending_errno != 0) return; if (wostream->output_closed) { i_assert(wostream->ostream.finished); return; } if (set) { if (wostream->output != NULL) o_stream_cork(wostream->output); } else { /* Buffer flushing might close the stream */ ret = wrapper_ostream_flush_buffer(wostream); stream->last_errors_not_checked = TRUE; if (wostream->output != NULL) { if (o_stream_uncork_flush(wostream->output) < 0) { wrapper_ostream_handle_parent_error(wostream); ret = -1; } } if ((ret == 0 || wostream->flush_pending) && !stream->ostream.closed) wrapper_ostream_output_resume(wostream); } stream->corked = set; wrapper_ostream_output_manage(wostream, FALSE); } static ssize_t wrapper_ostream_sendv(struct ostream_private *stream, const struct const_iovec *iov, unsigned int iov_count) { struct wrapper_ostream *wostream = container_of(stream, struct wrapper_ostream, ostream); bool must_uncork = FALSE; ssize_t sret; if (wrapper_ostream_handle_pending_error(wostream) < 0) { /* Stream already hit an error */ return -1; } /* Cork parent ostream if necessary */ if (!wostream->output_closed && wostream->output != NULL && !o_stream_is_corked(wostream->output)) { o_stream_cork(wostream->output); must_uncork = TRUE; } sret = wrapper_ostream_sendv_real(wostream, iov, iov_count); if (sret > 0) stream->ostream.offset += (ssize_t)sret; /* Uncork the parent ostream */ if (must_uncork && !wostream->output_closed && wostream->output != NULL) { if (o_stream_uncork_flush(wostream->output) < 0 && sret >= 0) { wrapper_ostream_handle_parent_error(wostream); sret = -1; } } if (sret >= 0) { wrapper_ostream_output_update_timeouts(wostream); if (!stream->ostream.blocking) wrapper_ostream_output_manage(wostream, FALSE); } return sret; } static int wrapper_ostream_flush(struct ostream_private *stream) { struct wrapper_ostream *wostream = container_of(stream, struct wrapper_ostream, ostream); struct ostream *ostream = &stream->ostream; bool must_uncork = FALSE; int ret; if (wrapper_ostream_handle_pending_error(wostream) < 0) { /* Stream already hit an error */ return -1; } if (wostream->output_closed) { if (!stream->finished || !wrapper_ostream_is_empty(wostream)) { stream->ostream.stream_errno = EPIPE; return -1; } /* Already finished the ostream */ return 1; } if (wostream->flushing) { /* Prevent recursion while finishing output */ return 1; } wostream->flushing = TRUE; o_stream_ref(ostream); /* Cork parent ostream if necessary */ if (wostream->output != NULL && !o_stream_is_corked(wostream->output)) { o_stream_cork(wostream->output); must_uncork = TRUE; } /* If blocking: loop until all is flushed; otherwise try once */ do { /* Try to flush */ if ((ret = wrapper_ostream_flush_real(wostream)) < 0) { ret = -1; break; } if (ret == 0 && stream->ostream.blocking) { /* Block until we can write more */ if (wrapper_ostream_flush_wait(wostream) < 0) { ret = -1; break; } } if (stream->ostream.closed) { /* Ostream was closed in the mean time */ ret = -1; break; } if (wostream->output_closed) { /* Already finished the ostream */ i_assert(stream->finished); ret = 1; break; } } while (ret == 0 && stream->ostream.blocking); if (ret > 0 && stream->finished) { /* This was an o_stream_finish() call or subsequent flush */ i_assert(wrapper_ostream_is_empty(wostream)); while ((ret = wrapper_ostream_finish(wostream)) == 0) { if (!stream->ostream.blocking) { /* Not yet finished completely */ break; } /* Block until we can write more */ if (wrapper_ostream_flush_wait(wostream) < 0) { ret = -1; break; } } } wrapper_ostream_output_update_timeouts(wostream); wostream->flushing = FALSE; if (ret >= 0 && !ostream->blocking) wrapper_ostream_output_manage(wostream, FALSE); if (wostream->output_closed) { i_assert(ret < 0 || ostream->stream_errno == 0 || ostream->closed); i_assert(ret >= 0 || ostream->stream_errno != 0); o_stream_unref(&ostream); return (ret >= 0 ? 1 : -1); } if (!must_uncork || wostream->output == NULL) { /* Nothing */ } else if (ret >= 0) { /* Uncork the parent ostream */ if (o_stream_uncork_flush(wostream->output) < 0) { wrapper_ostream_handle_parent_error(wostream); ret = -1; } } else { o_stream_uncork(wostream->output); } i_assert(ret >= 0 || ostream->stream_errno != 0); o_stream_unref(&ostream); return ret; } static void wrapper_ostream_set_flush_callback(struct ostream_private *stream, stream_flush_callback_t *callback, void *context) { struct wrapper_ostream *wostream = container_of(stream, struct wrapper_ostream, ostream); stream->callback = callback; stream->context = context; if (!stream->ostream.blocking && stream->callback == NULL) { /* Application is currently not interested in flush events and that includes request events like errors. */ timeout_remove(&wostream->to_event); } else if (wostream->pending_error != NULL && wostream->to_event == NULL) { /* Schedule flush callback to notify application of events */ wostream->to_event = timeout_add_short( 0, wrapper_ostream_handle_event, wostream); } } static void wrapper_ostream_flush_pending(struct ostream_private *stream, bool set) { struct wrapper_ostream *wostream = container_of(stream, struct wrapper_ostream, ostream); wostream->flush_pending = set; if (!set) return; if (wostream->output_closed) { i_assert(wostream->ostream.ostream.closed); return; } if (wostream->to_event == NULL) { wostream->to_event = timeout_add_short( 0, wrapper_ostream_handle_event, wostream); } } static size_t wrapper_ostream_get_buffer_used_size(const struct ostream_private *stream) { const struct wrapper_ostream *wostream = container_of(stream, const struct wrapper_ostream, ostream); size_t size = 0; if (wostream->buffer != NULL) size += wostream->buffer->used; if (wostream->output != NULL) size += o_stream_get_buffer_used_size(wostream->output); return size; } static size_t wrapper_ostream_get_buffer_avail_size(const struct ostream_private *stream) { const struct wrapper_ostream *wostream = container_of(stream, const struct wrapper_ostream, ostream); size_t size = 0; if (wostream->ostream.max_buffer_size == SIZE_MAX) return SIZE_MAX; if (wostream->buffer == NULL) size = wostream->ostream.max_buffer_size; else if (wostream->buffer->used < wostream->ostream.max_buffer_size) { size = (wostream->ostream.max_buffer_size - wostream->buffer->used); } if (wostream->output != NULL) size += o_stream_get_buffer_avail_size(wostream->output); return size; } static void wrapper_ostream_switch_ioloop_to(struct ostream_private *stream, struct ioloop *ioloop) { struct wrapper_ostream *wostream = container_of(stream, struct wrapper_ostream, ostream); if (wostream->flush_ioloop != ioloop && wostream->switch_ioloop_to != NULL) wostream->switch_ioloop_to(wostream, ioloop); if (wostream->to_event != NULL) { wostream->to_event = io_loop_move_timeout_to(ioloop, &wostream->to_event); } } /* * API */ struct ostream * wrapper_ostream_create(struct wrapper_ostream *wostream, size_t max_buffer_size, bool blocking, struct event *event) { wostream->ostream.iostream.close = wrapper_ostream_close; wostream->ostream.iostream.destroy = wrapper_ostream_destroy; wostream->ostream.ostream.blocking = blocking; wostream->ostream.max_buffer_size = max_buffer_size; wostream->ostream.cork = wrapper_ostream_cork; wostream->ostream.sendv = wrapper_ostream_sendv; wostream->ostream.flush = wrapper_ostream_flush; wostream->ostream.set_flush_callback = wrapper_ostream_set_flush_callback; wostream->ostream.flush_pending = wrapper_ostream_flush_pending; wostream->ostream.get_buffer_used_size = wrapper_ostream_get_buffer_used_size; wostream->ostream.get_buffer_avail_size = wrapper_ostream_get_buffer_avail_size; wostream->ostream.switch_ioloop_to = wrapper_ostream_switch_ioloop_to; wostream->event = event_create(event); return o_stream_create(&wostream->ostream, NULL, -1); } int wrapper_ostream_continue(struct wrapper_ostream *wostream) { struct ostream_private *stream = &wostream->ostream; struct ostream *ostream = &stream->ostream; struct ioloop *ioloop = NULL; bool use_cork = !stream->corked; int ret = 1; if (wostream->flush_waiting) { /* Inside wrapper_ostream_flush_wait() */ ioloop = wostream->flush_ioloop; } if (stream->ostream.closed || (stream->finished && wrapper_ostream_is_empty(wostream) && wostream->output != NULL && o_stream_get_buffer_used_size(wostream->output) == 0)) { /* Already finished */ ret = wrapper_ostream_finish(wostream); if (ret == 0) return 0; } if (wostream->flush_waiting) { i_assert(ioloop != NULL); io_loop_stop(ioloop); wostream->flush_waiting = FALSE; return ret; } /* Set flush_pending = FALSE first before calling the flush callback, and change it to TRUE only if callback returns 0. That way the callback can call o_stream_set_flush_pending() again and we don't forget it even if flush callback returns 1. */ wostream->flush_pending = FALSE; o_stream_ref(ostream); wostream->continuing = TRUE; for (;;) { if (use_cork) o_stream_cork(ostream); ret = wrapper_ostream_callback(wostream); if (use_cork && !wostream->output_closed) { int fret = o_stream_uncork_flush(ostream); if (ret == 0 && fret > 0) continue; if (fret < 0 && ret >= 0) { i_assert(ostream->stream_errno != 0); (void)wrapper_ostream_callback(wostream); ret = -1; } } break; } wostream->continuing = FALSE; if (wostream->output_closed) o_stream_close(ostream); if (ret == 0) wostream->flush_pending = TRUE; if (!stream->ostream.blocking) wrapper_ostream_output_manage(wostream, FALSE); if (ret < 0 || ostream->stream_errno != 0 || wostream->pending_errno != 0) ret = -1; else if (wostream->output_closed) ret = 1; else if (!wrapper_ostream_is_empty(wostream) && (!stream->corked || wrapper_ostream_is_filled(wostream))) ret = 0; else if (wostream->flush_pending) ret = 0; o_stream_unref(&ostream); return ret; } void wrapper_ostream_trigger_flush(struct wrapper_ostream *wostream) { struct ostream *ostream = &wostream->ostream.ostream; if (ostream->closed) return; if (wostream->to_event != NULL) return; if (!wostream->flush_waiting && wostream->ostream.callback == NULL) return; wostream->to_event = timeout_add_short( 0, wrapper_ostream_handle_event, wostream); } bool wrapper_ostream_get_buffered_size(struct wrapper_ostream *wostream, uoff_t *size_r) { buffer_t *buffer = wostream->buffer; if (!wostream->ostream.finished) return FALSE; *size_r = (buffer == NULL ? 0 : (uoff_t)buffer->used); i_assert(*size_r == wostream->ostream.ostream.offset); return TRUE; } void wrapper_ostream_output_available(struct wrapper_ostream *wostream, struct ostream *output) { i_assert(!wostream->output_closed); i_assert(!wostream->output_finished); i_assert(wostream->output == NULL); wostream->output = output; if (output != NULL) { if (wostream->ostream.corked) o_stream_cork(wostream->output); o_stream_ref(output); } } void wrapper_ostream_output_destroyed(struct wrapper_ostream *wostream) { struct ostream *ostream = &wostream->ostream.ostream; wrapper_ostream_trigger_flush(wostream); o_stream_set_no_error_handling(ostream, TRUE); o_stream_unref(&wostream->output); wostream->output_closed = TRUE; wostream->output_finished = TRUE; } void wrapper_ostream_set_error(struct wrapper_ostream *wostream, int stream_errno, const char *stream_error) { struct ostream *ostream = &wostream->ostream.ostream; if (ostream->closed || wostream->pending_errno != 0 || wostream->returned_error) return; i_assert(wostream->pending_error == NULL); wostream->pending_errno = stream_errno; wostream->pending_error = i_strdup(stream_error); wrapper_ostream_trigger_flush(wostream); } void wrapper_ostream_notify_error(struct wrapper_ostream *wostream) { struct ostream *ostream = &wostream->ostream.ostream; if (ostream->closed || ostream->blocking || wostream->output_closed_api || wostream->returned_error || wostream->continuing) return; if (wostream->pending_errno == 0) return; wostream->returned_error = TRUE; (void)wrapper_ostream_callback(wostream); } dovecot-2.3.21.1/src/lib/test-hash-method.c0000644000000000000000000003137314656633576015203 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "hex-binary.h" #include "mmap-util.h" #include "hash-method.h" #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) # define MAP_ANONYMOUS MAP_ANON #endif static unsigned char *buf; static unsigned int buf_size; static void test_hash_method_one(const struct hash_method *method) { unsigned char *ctx, *digest; unsigned int i; test_begin(t_strdup_printf("hash method %s", method->name)); ctx = i_malloc(method->context_size); digest = i_malloc(method->digest_size); method->init(ctx); /* make sure the code doesn't try to access data past boundaries */ for (i = 0; i < buf_size; i++) method->loop(ctx, buf + buf_size - i, i); method->result(ctx, digest); i_free(ctx); i_free(digest); test_end(); } static void test_hash_method_boundary(void) { unsigned int i; buf_size = mmap_get_page_size(); #ifdef MAP_ANONYMOUS buf = mmap(NULL, buf_size*2, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); mprotect(buf + buf_size, buf_size, PROT_NONE); #else buf = i_malloc(buf_size); #endif memset(buf, 0, buf_size); for (i = 0; hash_methods[i] != NULL; i++) test_hash_method_one(hash_methods[i]); } static void test_hash_methods_fips() { const char *last_method = NULL; struct { const char *method; const void *input; size_t ilen; size_t rounds; const void *output; size_t olen; } test_vectors[] = { { "md4", "", 0, 1, "\x31\xd6\xcf\xe0\xd1\x6a\xe9\x31" "\xb7\x3c\x59\xd7\xe0\xc0\x89\xc0", 128 / 8 }, { "md4", "abc", 3, 1, "\xa4\x48\x01\x7a\xaf\x21\xd8\x52" "\x5f\xc1\x0a\xe8\x7a\xa6\x72\x9d", 128 / 8 }, { "md4", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef" "ghijklmnopqrstuvwxyz0123456789", 62, 1, "\x04\x3f\x85\x82\xf2\x41\xdb\x35" "\x1c\xe6\x27\xe1\x53\xe7\xf0\xe4", 128 / 8 }, { "md5", "", 0, 1, "\xd4\x1d\x8c\xd9\x8f\x00\xb2\x04" "\xe9\x80\x09\x98\xec\xf8\x42\x7e", 128 / 8 }, { "md5", "abc", 3, 1, "\x90\x01\x50\x98\x3c\xd2\x4f\xb0" "\xd6\x96\x3f\x7d\x28\xe1\x7f\x72", 128 / 8 }, { "md5", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef" "ghijklmnopqrstuvwxyz0123456789", 62, 1, "\xd1\x74\xab\x98\xd2\x77\xd9\xf5" "\xa5\x61\x1c\x2c\x9f\x41\x9d\x9f", 128 / 8 }, { "sha1", "", 0, 1, "\xda\x39\xa3\xee\x5e\x6b\x4b\x0d" "\x32\x55\xbf\xef\x95\x60\x18\x90" "\xaf\xd8\x07\x09", 160 / 8 }, { "sha1", "abc", 3, 1, "\xa9\x99\x3e\x36\x47\x06\x81\x6a" "\xba\x3e\x25\x71\x78\x50\xc2\x6c" "\x9c\xd0\xd8\x9d", 160 / 8 }, { "sha1", "abcdbcdecdefdefgefghfghighijhijk" "ijkljklmklmnlmnomnopnopq", 56, 1, "\x84\x98\x3e\x44\x1c\x3b\xd2\x6e" "\xba\xae\x4a\xa1\xf9\x51\x29\xe5" "\xe5\x46\x70\xf1", 160 / 8 }, { "sha256", "", 0, 1, "\xe3\xb0\xc4\x42\x98\xfc\x1c\x14" "\x9a\xfb\xf4\xc8\x99\x6f\xb9\x24" "\x27\xae\x41\xe4\x64\x9b\x93\x4c" "\xa4\x95\x99\x1b\x78\x52\xb8\x55", 256 / 8 }, { "sha256", "abc", 3, 1, "\xba\x78\x16\xbf\x8f\x01\xcf\xea" "\x41\x41\x40\xde\x5d\xae\x22\x23" "\xb0\x03\x61\xa3\x96\x17\x7a\x9c" "\xb4\x10\xff\x61\xf2\x00\x15\xad", 256 / 8 }, { "sha256", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 56, 1, "\x24\x8d\x6a\x61\xd2\x06\x38\xb8" "\xe5\xc0\x26\x93\x0c\x3e\x60\x39" "\xa3\x3c\xe4\x59\x64\xff\x21\x67" "\xf6\xec\xed\xd4\x19\xdb\x06\xc1", 256 / 8 }, { "sha384", "", 0, 1, "\x38\xb0\x60\xa7\x51\xac\x96\x38" "\x4c\xd9\x32\x7e\xb1\xb1\xe3\x6a" "\x21\xfd\xb7\x11\x14\xbe\x07\x43" "\x4c\x0c\xc7\xbf\x63\xf6\xe1\xda" "\x27\x4e\xde\xbf\xe7\x6f\x65\xfb" "\xd5\x1a\xd2\xf1\x48\x98\xb9\x5b", 384 / 8 }, { "sha384", "abc", 3, 1, "\xcb\x00\x75\x3f\x45\xa3\x5e\x8b" "\xb5\xa0\x3d\x69\x9a\xc6\x50\x07" "\x27\x2c\x32\xab\x0e\xde\xd1\x63" "\x1a\x8b\x60\x5a\x43\xff\x5b\xed" "\x80\x86\x07\x2b\xa1\xe7\xcc\x23" "\x58\xba\xec\xa1\x34\xc8\x25\xa7", 384 / 8 }, { "sha384", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 56, 1, "\x33\x91\xfd\xdd\xfc\x8d\xc7\x39" "\x37\x07\xa6\x5b\x1b\x47\x09\x39" "\x7c\xf8\xb1\xd1\x62\xaf\x05\xab" "\xfe\x8f\x45\x0d\xe5\xf3\x6b\xc6" "\xb0\x45\x5a\x85\x20\xbc\x4e\x6f" "\x5f\xe9\x5b\x1f\xe3\xc8\x45\x2b", 384 / 8 }, { "sha512", "", 0, 1, "\xcf\x83\xe1\x35\x7e\xef\xb8\xbd" "\xf1\x54\x28\x50\xd6\x6d\x80\x07" "\xd6\x20\xe4\x05\x0b\x57\x15\xdc" "\x83\xf4\xa9\x21\xd3\x6c\xe9\xce" "\x47\xd0\xd1\x3c\x5d\x85\xf2\xb0" "\xff\x83\x18\xd2\x87\x7e\xec\x2f" "\x63\xb9\x31\xbd\x47\x41\x7a\x81" "\xa5\x38\x32\x7a\xf9\x27\xda\x3e", 512 / 8 }, { "sha512", "abc", 3, 1, "\xdd\xaf\x35\xa1\x93\x61\x7a\xba" "\xcc\x41\x73\x49\xae\x20\x41\x31" "\x12\xe6\xfa\x4e\x89\xa9\x7e\xa2" "\x0a\x9e\xee\xe6\x4b\x55\xd3\x9a" "\x21\x92\x99\x2a\x27\x4f\xc1\xa8" "\x36\xba\x3c\x23\xa3\xfe\xeb\xbd" "\x45\x4d\x44\x23\x64\x3c\xe8\x0e" "\x2a\x9a\xc9\x4f\xa5\x4c\xa4\x9f", 512 / 8 }, { "sha512", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 56, 1, "\x20\x4a\x8f\xc6\xdd\xa8\x2f\x0a" "\x0c\xed\x7b\xeb\x8e\x08\xa4\x16" "\x57\xc1\x6e\xf4\x68\xb2\x28\xa8" "\x27\x9b\xe3\x31\xa7\x03\xc3\x35" "\x96\xfd\x15\xc1\x3b\x1b\x07\xf9" "\xaa\x1d\x3b\xea\x57\x78\x9c\xa0" "\x31\xad\x85\xc7\xa7\x1d\xd7\x03" "\x54\xec\x63\x12\x38\xca\x34\x45", 512 / 8 }, { "sha3-256", "", 0, 1, "\xa7\xff\xc6\xf8\xbf\x1e\xd7\x66" "\x51\xc1\x47\x56\xa0\x61\xd6\x62" "\xf5\x80\xff\x4d\xe4\x3b\x49\xfa" "\x82\xd8\x0a\x4b\x80\xf8\x43\x4a", 256 / 8 }, { "sha3-256", "\xb7\x71\xd5\xce\xf5\xd1\xa4\x1a" "\x93\xd1\x56\x43\xd7\x18\x1d\x2a" "\x2e\xf0\xa8\xe8\x4d\x91\x81\x2f" "\x20\xed\x21\xf1\x47\xbe\xf7\x32" "\xbf\x3a\x60\xef\x40\x67\xc3\x73" "\x4b\x85\xbc\x8c\xd4\x71\x78\x0f" "\x10\xdc\x9e\x82\x91\xb5\x83\x39" "\xa6\x77\xb9\x60\x21\x8f\x71\xe7" "\x93\xf2\x79\x7a\xea\x34\x94\x06" "\x51\x28\x29\x06\x5d\x37\xbb\x55" "\xea\x79\x6f\xa4\xf5\x6f\xd8\x89" "\x6b\x49\xb2\xcd\x19\xb4\x32\x15" "\xad\x96\x7c\x71\x2b\x24\xe5\x03" "\x2d\x06\x52\x32\xe0\x2c\x12\x74" "\x09\xd2\xed\x41\x46\xb9\xd7\x5d" "\x76\x3d\x52\xdb\x98\xd9\x49\xd3" "\xb0\xfe\xd6\xa8\x05\x2f\xbb", 1080 / 8, 1, "\xa1\x9e\xee\x92\xbb\x20\x97\xb6" "\x4e\x82\x3d\x59\x77\x98\xaa\x18" "\xbe\x9b\x7c\x73\x6b\x80\x59\xab" "\xfd\x67\x79\xac\x35\xac\x81\xb5", 256 / 8 }, { "sha3-256", "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3", 200, 1, "\x79\xf3\x8a\xde\xc5\xc2\x03\x07" "\xa9\x8e\xf7\x6e\x83\x24\xaf\xbf" "\xd4\x6c\xfd\x81\xb2\x2e\x39\x73" "\xc6\x5f\xa1\xbd\x9d\xe3\x17\x87", 256 / 8, }, { "sha3-256", "\xa3", 1, 200, "\x79\xf3\x8a\xde\xc5\xc2\x03\x07" "\xa9\x8e\xf7\x6e\x83\x24\xaf\xbf" "\xd4\x6c\xfd\x81\xb2\x2e\x39\x73" "\xc6\x5f\xa1\xbd\x9d\xe3\x17\x87", 256 / 8, }, { "sha3-512", "", 0, 1, "\xa6\x9f\x73\xcc\xa2\x3a\x9a\xc5" "\xc8\xb5\x67\xdc\x18\x5a\x75\x6e" "\x97\xc9\x82\x16\x4f\xe2\x58\x59" "\xe0\xd1\xdc\xc1\x47\x5c\x80\xa6" "\x15\xb2\x12\x3a\xf1\xf5\xf9\x4c" "\x11\xe3\xe9\x40\x2c\x3a\xc5\x58" "\xf5\x00\x19\x9d\x95\xb6\xd3\xe3" "\x01\x75\x85\x86\x28\x1d\xcd\x26", 512 / 8 }, { "sha3-512", "\xb7\x71\xd5\xce\xf5\xd1\xa4\x1a" "\x93\xd1\x56\x43\xd7\x18\x1d\x2a" "\x2e\xf0\xa8\xe8\x4d\x91\x81\x2f" "\x20\xed\x21\xf1\x47\xbe\xf7\x32" "\xbf\x3a\x60\xef\x40\x67\xc3\x73" "\x4b\x85\xbc\x8c\xd4\x71\x78\x0f" "\x10\xdc\x9e\x82\x91\xb5\x83\x39" "\xa6\x77\xb9\x60\x21\x8f\x71\xe7" "\x93\xf2\x79\x7a\xea\x34\x94\x06" "\x51\x28\x29\x06\x5d\x37\xbb\x55" "\xea\x79\x6f\xa4\xf5\x6f\xd8\x89" "\x6b\x49\xb2\xcd\x19\xb4\x32\x15" "\xad\x96\x7c\x71\x2b\x24\xe5\x03" "\x2d\x06\x52\x32\xe0\x2c\x12\x74" "\x09\xd2\xed\x41\x46\xb9\xd7\x5d" "\x76\x3d\x52\xdb\x98\xd9\x49\xd3" "\xb0\xfe\xd6\xa8\x05\x2f\xbb", 1080 / 8, 1, "\x75\x75\xa1\xfb\x4f\xc9\xa8\xf9" "\xc0\x46\x6b\xd5\xfc\xa4\x96\xd1" "\xcb\x78\x69\x67\x73\xa2\x12\xa5" "\xf6\x2d\x02\xd1\x4e\x32\x59\xd1" "\x92\xa8\x7e\xba\x44\x07\xdd\x83" "\x89\x35\x27\x33\x14\x07\xb6\xda" "\xda\xad\x92\x0d\xbc\x46\x48\x9b" "\x67\x74\x93\xce\x5f\x20\xb5\x95", 512 / 8 }, { "sha3-512", "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3" "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3", 200, 1, "\xe7\x6d\xfa\xd2\x20\x84\xa8\xb1" "\x46\x7f\xcf\x2f\xfa\x58\x36\x1b" "\xec\x76\x28\xed\xf5\xf3\xfd\xc0" "\xe4\x80\x5d\xc4\x8c\xae\xec\xa8" "\x1b\x7c\x13\xc3\x0a\xdf\x52\xa3" "\x65\x95\x84\x73\x9a\x2d\xf4\x6b" "\xe5\x89\xc5\x1c\xa1\xa4\xa8\x41" "\x6d\xf6\x54\x5a\x1c\xe8\xba\x00", 512 / 8, }, { "sha3-512", "\xa3", 1, 200, "\xe7\x6d\xfa\xd2\x20\x84\xa8\xb1" "\x46\x7f\xcf\x2f\xfa\x58\x36\x1b" "\xec\x76\x28\xed\xf5\xf3\xfd\xc0" "\xe4\x80\x5d\xc4\x8c\xae\xec\xa8" "\x1b\x7c\x13\xc3\x0a\xdf\x52\xa3" "\x65\x95\x84\x73\x9a\x2d\xf4\x6b" "\xe5\x89\xc5\x1c\xa1\xa4\xa8\x41" "\x6d\xf6\x54\x5a\x1c\xe8\xba\x00", 512 / 8, }, }; for(size_t i = 0; i < N_ELEMENTS(test_vectors); i++) { if (last_method == NULL || strcmp(last_method, test_vectors[i].method) != 0) { if (last_method != NULL) test_end(); last_method = test_vectors[i].method; test_begin(t_strdup_printf("hash method %s (test vectors)", last_method)); } const struct hash_method *method = hash_method_lookup(test_vectors[i].method); unsigned char context[method->context_size]; unsigned char result[method->digest_size]; test_assert_idx(method->digest_size == test_vectors[i].olen, i); method->init(context); for(size_t n = 0; n < test_vectors[i].rounds; n++) method->loop(context, test_vectors[i].input, test_vectors[i].ilen); method->result(context, result); test_assert_idx(memcmp(result, test_vectors[i].output, test_vectors[i].olen) == 0, i); } test_end(); } static void test_hash_methods_large(void) { struct { const char *method; const char *hash; } tests[] = { { "sha256", "1ad0598b790b3acb38876105cc8938c3365f3215fbee3412ac3cd5e96a7dad01" }, { "sha384", "c187c084ffe516fea74b313340a540bc0bab306b1bdc564da21ecdc639e51f194460a0279c04aa40d65cec58698b10c0" }, { "sha512", "556247cfeab056903a3f42cf8496019d9ad90911ded9aa1ede3046b803623e5e2cd2adbd0620e666a927436d125984de9199d643ff21ad1c76e29b116c13ffb2" }, }; unsigned char data[1024]; unsigned int i; test_begin("hash method (large inputs)"); for (i = 0; i < sizeof(data); i++) data[i] = i & 0xFF; for (i = 0; i < N_ELEMENTS(tests); i++) { const struct hash_method *method = hash_method_lookup(tests[i].method); unsigned char context[method->context_size]; unsigned char result[method->digest_size]; method->init(context); for (unsigned int j = 0; j < 600000; j++) method->loop(context, data, sizeof(data)); method->result(context, result); test_assert_strcmp_idx(binary_to_hex(result, method->digest_size), tests[i].hash, i); } test_end(); } void test_hash_method(void) { test_hash_method_boundary(); test_hash_methods_fips(); test_hash_methods_large(); } dovecot-2.3.21.1/src/lib/test-istream-crlf.c0000644000000000000000000000545514656633576015374 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str.h" #include "istream-private.h" #include "istream-crlf.h" static void test_istream_crlf_input(const char *input) { string_t *output; const unsigned char *data; size_t size = 0; ssize_t ret1, ret2; unsigned int i, j, pos, input_len = strlen(input); struct istream *istream, *crlf_istream; output = t_str_new(256); for (j = 0; j < 4; j++) { istream = i_stream_create_from_data(input, input_len); str_truncate(output, 0); if (j%2 == 0) { /* drop CRs */ crlf_istream = i_stream_create_lf(istream); for (i = 0; i < input_len; i++) { if (input[i] == '\r' && (i == input_len-1 || input[i+1] == '\n')) ; else str_append_c(output, input[i]); } } else { /* add missing CRs */ crlf_istream = i_stream_create_crlf(istream); for (i = 0; i < input_len; i++) { if (input[i] == '\n' && (i == 0 || input[i-1] != '\r')) str_append_c(output, '\r'); str_append_c(output, input[i]); } } pos = 0; for (i = 1; i <= input_len; i++) { if (j >= 2) { i_stream_unref(&istream); i_stream_unref(&crlf_istream); istream = i_stream_create_from_data(input, input_len); crlf_istream = j%2 == 0 ? i_stream_create_lf(istream) : i_stream_create_crlf(istream); pos = 0; } istream->real_stream->pos = i; ret1 = i_stream_read(crlf_istream); if (crlf_istream->real_stream->buffer_size != 0) { /* this is pretty evil */ crlf_istream->real_stream->buffer_size = I_MAX(crlf_istream->real_stream->pos, i); } ret2 = i_stream_read(crlf_istream); data = i_stream_get_data(crlf_istream, &size); if (ret1 > 0 || ret2 > 0) { ret1 = I_MAX(ret1, 0) + I_MAX(ret2, 0); test_assert(pos + (unsigned int)ret1 == size); pos += ret1; } if (size > 0) test_assert_idx(memcmp(data, str_data(output), size) == 0, j*10000+i); } test_assert_idx(size == str_len(output), j*10000+i); i_stream_unref(&crlf_istream); i_stream_unref(&istream); } } void test_istream_crlf(void) { const char *input[] = { "\rfoo", "foo\nbar\r\nbaz\r\r\n", "\r\nfoo", "\r\r\n", "\nfoo" }; unsigned int i; test_begin("istream crlf"); for (i = 0; i < N_ELEMENTS(input); i++) test_istream_crlf_input(input[i]); test_end(); #define ISTREAM_CRLF_TEST_REPS 1000 test_begin("istream crlf(random)"); for (i = 0; i < ISTREAM_CRLF_TEST_REPS; i++) T_BEGIN { char buf[100]; size_t len = 0; while (len < sizeof(buf) - 1) { switch(i_rand_limit(16)) { case 0: goto outahere; case 1: buf[len] = '\r'; break; case 2: buf[len] = '\n'; break; default: buf[len]= '.'; break; } len++; } outahere: buf[len] = '\0'; if (len > 0) test_istream_crlf_input(buf); } T_END; test_end(); } dovecot-2.3.21.1/src/lib/test-str.c0000644000000000000000000001012714656633576013604 00000000000000/* Copyright (c) 2012-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "unichar.h" #include "str.h" static void test_str_append(void) { string_t *str = t_str_new(32); string_t *str2 = t_str_new(32); test_begin("str_append_*()"); str_append(str, "foo"); str_append_c(str, '|'); str_append_c(str, '\0'); test_assert(str->used == 5 && memcmp(str_data(str), "foo|\0", 5) == 0); str_append(str2, "sec"); str_append_c(str2, '\0'); str_append(str2, "ond"); str_append_str(str, str2); test_assert(str->used == 5+7 && memcmp(str_data(str), "foo|\0sec\0ond", 5+7) == 0); test_end(); } static void test_str_c(void) { string_t *str; unsigned int i, j; test_begin("str_c()"); str = t_str_new(0); T_BEGIN { (void)str_c(str); } T_END; for (i = 0; i < 32; i++) T_BEGIN { str = t_str_new(15); for (j = 0; j < i; j++) str_append_c(str, 'x'); T_BEGIN { (void)str_c(str); } T_END; } T_END; test_end(); } static void test_str_insert(void) { string_t *str = t_str_new(32); test_begin("str_insert()"); str_insert(str, 0, "foo"); str_insert(str, 3, ">"); str_insert(str, 3, "bar"); str_insert(str, 0, "<"); test_assert(str->used == 8 && memcmp(str_data(str), "", 8) == 0); str_insert(str, 10, "!"); test_assert(str->used == 11 && memcmp(str_data(str), "\0\0!", 11) == 0); test_end(); } static void test_str_delete(void) { string_t *str = t_str_new(32); test_begin("str_delete()"); str_delete(str, 0, 100); str_append(str, "123456"); str_delete(str, 0, 1); str_delete(str, 4, 1); str_delete(str, 1, 1); test_assert(str->used == 3 && memcmp(str_data(str), "245", 3) == 0); str_delete(str, 1, 2); test_assert(str->used == 1 && memcmp(str_data(str), "2", 1) == 0); str_append(str, "bar"); str_delete(str, 1, 100); test_assert(str->used == 1 && memcmp(str_data(str), "2", 1) == 0); test_end(); } static void test_str_append_max(void) { string_t *str = t_str_new(32); test_begin("str_append_max()"); str_append_max(str, "foo", 0); test_assert(str->used == 0); str_append_max(str, "\0foo", 4); test_assert(str->used == 0); str_append_max(str, "foo", 3); test_assert(str->used == 3 && memcmp(str_data(str), "foo", 3) == 0); str_truncate(str, 0); str_append_max(str, "foo", 2); test_assert(str->used == 2 && memcmp(str_data(str), "fo", 2) == 0); str_truncate(str, 0); str_append_max(str, "foo\0bar", 7); test_assert(str->used == 3 && memcmp(str_data(str), "foo", 3) == 0); str_truncate(str, 0); test_end(); } static void test_str_truncate(void) { string_t *str = t_str_new(8); int i; test_begin("str_truncate()"); str_append(str, "123456"); for (i = 100; i >= 6; i--) { str_truncate(str, i); test_assert_idx(str_len(str) == 6, i); } for (; i >= 0; i--) { str_truncate(str, i); test_assert_idx(str_len(str) == (unsigned int)i, i); } test_end(); } static void test_str_truncate_utf8(void) { string_t *str = t_str_new(8); int i; test_begin("str_truncate_utf8()"); str_append(str, "123456"); for (i = 100; i >= 6; i--) { str_truncate_utf8(str, i); test_assert_idx(str_len(str) == 6, i); } for (; i >= 0; i--) { str_truncate_utf8(str, i); test_assert_idx(str_len(str) == (unsigned int)i, i); } str_append(str, "\xE4\xB8\x80\xE4\xBa\x8C\xE4\xB8\x89" "\xE5\x9b\x9b\xE4\xBa\x94\xE5\x85\xAD"); for (i = 100; i >= 18; i--) { str_truncate_utf8(str, i); test_assert_idx(str_len(str) == 18, i); } for (; i >= 0; i--) { str_truncate_utf8(str, i); test_assert_idx(str_len(str) % 3 == 0, i); test_assert_idx((str_len(str) / 3) == ((unsigned int)i / 3), i); } str_append(str, "\xE4\xB8\x80""1""\xE4\xBa\x8C""2""\xE4\xB8\x89""3" "\xE5\x9b\x9b""4""\xE4\xBa\x94""5""\xE5\x85\xAD""6"); for (i = 100; i >= 24; i--) { str_truncate_utf8(str, i); test_assert_idx(str_len(str) == 24, i); } for (; i >= 0; i--) { str_truncate_utf8(str, i); test_assert_idx(uni_utf8_data_is_valid(str_data(str), str_len(str)), i); } test_end(); } void test_str(void) { test_str_append(); test_str_c(); test_str_insert(); test_str_delete(); test_str_append_max(); test_str_truncate(); test_str_truncate_utf8(); } dovecot-2.3.21.1/src/lib/ostream-buffer.c0000644000000000000000000000451114656633576014740 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "ostream-private.h" struct buffer_ostream { struct ostream_private ostream; buffer_t *buf; bool seeked; }; static int o_stream_buffer_seek(struct ostream_private *stream, uoff_t offset) { struct buffer_ostream *bstream = container_of(stream, struct buffer_ostream, ostream); bstream->seeked = TRUE; stream->ostream.offset = offset; return 1; } static int o_stream_buffer_write_at(struct ostream_private *stream, const void *data, size_t size, uoff_t offset) { struct buffer_ostream *bstream = container_of(stream, struct buffer_ostream, ostream); buffer_write(bstream->buf, offset, data, size); return 0; } static ssize_t o_stream_buffer_sendv(struct ostream_private *stream, const struct const_iovec *iov, unsigned int iov_count) { struct buffer_ostream *bstream = container_of(stream, struct buffer_ostream, ostream); size_t left, n, offset; ssize_t ret = 0; unsigned int i; offset = bstream->seeked ? stream->ostream.offset : bstream->buf->used; for (i = 0; i < iov_count; i++) { left = bstream->ostream.max_buffer_size - stream->ostream.offset; n = I_MIN(left, iov[i].iov_len); buffer_write(bstream->buf, offset, iov[i].iov_base, n); stream->ostream.offset += n; offset += n; ret += n; if (n != iov[i].iov_len) break; } return ret; } static size_t o_stream_buffer_get_buffer_used_size(const struct ostream_private *stream) { const struct buffer_ostream *bstream = container_of(stream, const struct buffer_ostream, ostream); return bstream->buf->used; } struct ostream *o_stream_create_buffer(buffer_t *buf) { struct buffer_ostream *bstream; struct ostream *output; bstream = i_new(struct buffer_ostream, 1); /* we don't set buffer as blocking, because if max_buffer_size is changed it can get truncated. this is used in various places in unit tests. */ bstream->ostream.max_buffer_size = SIZE_MAX; bstream->ostream.seek = o_stream_buffer_seek; bstream->ostream.sendv = o_stream_buffer_sendv; bstream->ostream.write_at = o_stream_buffer_write_at; bstream->ostream.get_buffer_used_size = o_stream_buffer_get_buffer_used_size; bstream->buf = buf; output = o_stream_create(&bstream->ostream, NULL, -1); o_stream_set_name(output, "(buffer)"); return output; } dovecot-2.3.21.1/src/lib/istream-data.c0000644000000000000000000000271014656633576014371 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "istream-private.h" static ssize_t i_stream_data_read(struct istream_private *stream) { stream->istream.eof = TRUE; return -1; } static void i_stream_data_seek(struct istream_private *stream, uoff_t v_offset, bool mark ATTR_UNUSED) { stream->skip = v_offset; stream->istream.v_offset = v_offset; } struct istream *i_stream_create_from_data(const void *data, size_t size) { struct istream_private *stream; stream = i_new(struct istream_private, 1); stream->buffer = data; stream->pos = size; stream->max_buffer_size = SIZE_MAX; stream->read = i_stream_data_read; stream->seek = i_stream_data_seek; stream->istream.readable_fd = FALSE; stream->istream.blocking = TRUE; stream->istream.seekable = TRUE; i_stream_create(stream, NULL, -1, ISTREAM_CREATE_FLAG_NOOP_SNAPSHOT); stream->statbuf.st_size = size; i_stream_set_name(&stream->istream, "(buffer)"); return &stream->istream; } static void i_stream_copied_data_free(void *data) { i_free(data); } struct istream * i_stream_create_copy_from_data(const void *data, size_t size) { struct istream *stream; void *buffer; if (size == 0) { buffer = ""; } else { buffer = i_malloc(size); memcpy(buffer, data, size); } stream = i_stream_create_from_data(buffer, size); if (size > 0) { i_stream_add_destroy_callback (stream, i_stream_copied_data_free, buffer); } return stream; } dovecot-2.3.21.1/src/lib/sha1.h0000644000000000000000000000577714656633576012677 00000000000000/* $FreeBSD: src/sys/crypto/sha1.h,v 1.8 2002/03/20 05:13:50 alfred Exp $ */ /* $KAME: sha1.h,v 1.5 2000/03/27 04:36:23 sumikawa Exp $ */ /* * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * FIPS pub 180-1: Secure Hash Algorithm (SHA-1) * based on: http://csrc.nist.gov/fips/fip180-1.txt * implemented by Jun-ichiro itojun Itoh */ #ifndef SHA1_H #define SHA1_H #include "hash-method.h" /* libmysqlclient really should try to keep its internal stuff internal so they won't conflict with the actual programs that are trying to use it. This particular instance has been fixed in 4.1.18 and 5.0.19, but there are others. */ #define sha1_result sha1_result_libmysqlclient_craps_all_over struct sha1_ctxt { union { uint8_t b8[20]; uint32_t b32[5]; } h; union { uint8_t b8[8]; uint64_t b64[1]; } c; union { uint8_t b8[64]; uint32_t b32[16]; } m; uint8_t count; }; extern void sha1_init(struct sha1_ctxt *); extern void sha1_pad(struct sha1_ctxt *); extern void sha1_loop(struct sha1_ctxt *, const void *, size_t); extern void sha1_result(struct sha1_ctxt *, void *); /* compatibility with other SHA1 source codes */ typedef struct sha1_ctxt SHA1_CTX; #define SHA1Init(x) sha1_init((x)) #define SHA1Update(x, y, z) sha1_loop((x), (y), (z)) #define SHA1Final(x, y) sha1_result((y), (x)) #define SHA1_RESULTLEN (160/8) extern void sha1_get_digest(const void *data, size_t size, unsigned char result[STATIC_ARRAY SHA1_RESULTLEN]); extern const struct hash_method hash_method_sha1; #endif dovecot-2.3.21.1/src/lib/seq-range-array.c0000644000000000000000000003434614656633576015026 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "seq-range-array.h" static bool seq_range_is_overflowed(const ARRAY_TYPE(seq_range) *array) { const struct seq_range *range; unsigned int count; range = array_get(array, &count); return count == 1 && range[0].seq1 == 0 && range[0].seq2 == (uint32_t)-1; } static bool ATTR_NOWARN_UNUSED_RESULT seq_range_lookup(const ARRAY_TYPE(seq_range) *array, uint32_t seq, unsigned int *idx_r) { const struct seq_range *data; unsigned int idx, left_idx, right_idx, count; data = array_get(array, &count); i_assert(count < INT_MAX); idx = 0; left_idx = 0; right_idx = count; while (left_idx < right_idx) { idx = (left_idx + right_idx) / 2; if (data[idx].seq1 <= seq) { if (data[idx].seq2 >= seq) { /* it's already in the range */ *idx_r = idx; return TRUE; } left_idx = idx+1; } else { right_idx = idx; } } if (left_idx > idx) idx++; *idx_r = idx; return FALSE; } static bool seq_range_array_add_slow_path(ARRAY_TYPE(seq_range) *array, uint32_t seq) { struct seq_range *data, value; unsigned int idx, count; value.seq1 = value.seq2 = seq; data = array_get_modifiable(array, &count); /* somewhere in the middle, array is sorted so find it with binary search */ if (seq_range_lookup(array, seq, &idx)) return TRUE; /* idx == count couldn't happen because we already handle it above */ i_assert(idx < count && data[idx].seq1 >= seq); i_assert(data[idx].seq1 > seq || data[idx].seq2 < seq); if (data[idx].seq1 == seq+1) { data[idx].seq1 = seq; if (idx > 0 && data[idx-1].seq2 == seq-1) { /* merge */ data[idx-1].seq2 = data[idx].seq2; array_delete(array, idx, 1); } } else { if (idx > 0 && data[idx-1].seq2 == seq-1) idx--; if (data[idx].seq2 == seq-1) { i_assert(idx+1 < count); /* already handled above */ data[idx].seq2 = seq; if (data[idx+1].seq1 == seq+1) { /* merge */ data[idx+1].seq1 = data[idx].seq1; array_delete(array, idx, 1); } } else { array_insert(array, idx, &value, 1); } } return FALSE; } bool seq_range_array_add(ARRAY_TYPE(seq_range) *array, uint32_t seq) { struct seq_range *data, value; unsigned int count; bool exists = FALSE; value.seq1 = value.seq2 = seq; data = array_get_modifiable(array, &count); /* quick checks */ if (count == 0) array_push_back(array, &value); else if (data[count-1].seq2 < seq) { if (data[count-1].seq2 == seq-1) { /* grow last range */ data[count-1].seq2 = seq; } else { array_push_back(array, &value); } } else if (data[0].seq1 > seq) { if (data[0].seq1-1 == seq) { /* grow down first range */ data[0].seq1 = seq; } else { array_push_front(array, &value); } } else { exists = seq_range_array_add_slow_path(array, seq); } i_assert(!seq_range_is_overflowed(array)); return exists; } void seq_range_array_add_with_init(ARRAY_TYPE(seq_range) *array, unsigned int init_count, uint32_t seq) { if (!array_is_created(array)) i_array_init(array, init_count); seq_range_array_add(array, seq); } static void seq_range_array_add_range_internal(ARRAY_TYPE(seq_range) *array, uint32_t seq1, uint32_t seq2, unsigned int *r_count) { struct seq_range *data, value; unsigned int idx1, idx2, count; seq_range_lookup(array, seq1, &idx1); seq_range_lookup(array, seq2, &idx2); data = array_get_modifiable(array, &count); if (r_count != NULL) { /* Find number we're adding by counting the number we're not adding, and subtracting that from the nominal range. */ unsigned int added = seq2+1 - seq1; unsigned int countidx2 = idx2; unsigned int overcounted = 0u, notadded = 0u; unsigned int i; if (idx1 == count) { /* not in a range as too far right */ } else if (seq1 < data[idx1].seq1) { /* not in a range, to the left of a real range */ } else { /* count the whole of this range, which is an overcount */ overcounted += seq1 - data[idx1].seq1; /* fencepost check: equality means the whole range is valid, therefore there's no overcounting. Result = 0 overcount */ } if (idx2 == count) { /* not in a range as too far right */ } else if (seq2 < data[idx2].seq1) { /* not in a range, to the left of a real range */ } else { /* count the whole of this range, which is an overcount */ overcounted += data[idx2].seq2 - seq2; countidx2++; /* may become == count i.e. past the end */ /* fencepost check: equality means the whole range is valid, therefore there's no overcounting. Result = 0 overcount. */ } /* Now count how many we're not adding */ for (i = idx1; i < countidx2; i++) notadded += data[i].seq2+1 - data[i].seq1; /* Maybe the not added tally included some over-counting too */ added -= (notadded - overcounted); *r_count = added; } if (idx1 > 0 && data[idx1-1].seq2+1 == seq1) idx1--; if (idx1 == idx2 && (idx2 == count || (seq2 < (uint32_t)-1 && data[idx2].seq1 > seq2+1)) && (idx1 == 0 || data[idx1-1].seq2 < seq1-1)) { /* no overlapping */ value.seq1 = seq1; value.seq2 = seq2; array_insert(array, idx1, &value, 1); } else { i_assert(idx1 < count); if (seq1 < data[idx1].seq1) data[idx1].seq1 = seq1; if (seq2 > data[idx1].seq2) { /* merge */ if (idx2 == count || data[idx2].seq1 > seq2+1) idx2--; if (seq2 >= data[idx2].seq2) { data[idx1].seq2 = seq2; } else { data[idx1].seq2 = data[idx2].seq2; } array_delete(array, idx1 + 1, idx2 - idx1); } } i_assert(!seq_range_is_overflowed(array)); } void seq_range_array_add_range(ARRAY_TYPE(seq_range) *array, uint32_t seq1, uint32_t seq2) { seq_range_array_add_range_internal(array, seq1, seq2, NULL); } unsigned int seq_range_array_add_range_count(ARRAY_TYPE(seq_range) *array, uint32_t seq1, uint32_t seq2) { unsigned int count; seq_range_array_add_range_internal(array, seq1, seq2, &count); return count; } void seq_range_array_merge(ARRAY_TYPE(seq_range) *dest, const ARRAY_TYPE(seq_range) *src) { const struct seq_range *range; if (array_count(dest) == 0) { array_append_array(dest, src); return; } array_foreach(src, range) seq_range_array_add_range(dest, range->seq1, range->seq2); } void seq_range_array_merge_n(ARRAY_TYPE(seq_range) *dest, const ARRAY_TYPE(seq_range) *src, unsigned int count) { const struct seq_range *src_range; unsigned int src_idx, src_count; unsigned int merge_count = count; src_range = array_get(src, &src_count); for (src_idx = 0; src_idx < src_count && merge_count > 0; src_idx++) { uint32_t first_seq = src_range[src_idx].seq1; uint32_t last_seq = src_range[src_idx].seq2; unsigned int idx_count = last_seq - first_seq + 1; if (idx_count > merge_count) { last_seq = first_seq + merge_count - 1; merge_count = 0; } else { merge_count -= idx_count; } seq_range_array_add_range(dest, first_seq, last_seq); } } bool seq_range_array_remove(ARRAY_TYPE(seq_range) *array, uint32_t seq) { struct seq_range *data, value; unsigned int idx, left_idx, right_idx, count; if (!array_is_created(array)) return FALSE; data = array_get_modifiable(array, &count); if (count == 0) return FALSE; /* quick checks */ if (seq > data[count-1].seq2 || seq < data[0].seq1) { /* outside the range */ return FALSE; } if (data[count-1].seq2 == seq) { /* shrink last range */ if (data[count-1].seq1 != data[count-1].seq2) data[count-1].seq2--; else array_delete(array, count-1, 1); return TRUE; } if (data[0].seq1 == seq) { /* shrink up first range */ if (data[0].seq1 != data[0].seq2) data[0].seq1++; else array_pop_front(array); return TRUE; } /* somewhere in the middle, array is sorted so find it with binary search */ i_assert(count < INT_MAX); left_idx = 0; right_idx = count; while (left_idx < right_idx) { idx = (left_idx + right_idx) / 2; if (data[idx].seq1 > seq) right_idx = idx; else if (data[idx].seq2 < seq) left_idx = idx+1; else { /* found it */ if (data[idx].seq1 == seq) { if (data[idx].seq1 == data[idx].seq2) { /* a single sequence range. remove it entirely */ array_delete(array, idx, 1); } else { /* shrink the range */ data[idx].seq1++; } } else if (data[idx].seq2 == seq) { /* shrink the range */ data[idx].seq2--; } else { /* split the sequence range */ value.seq1 = seq + 1; value.seq2 = data[idx].seq2; data[idx].seq2 = seq - 1; array_insert(array, idx + 1, &value, 1); } return TRUE; } } return FALSE; } unsigned int seq_range_array_remove_range(ARRAY_TYPE(seq_range) *array, uint32_t seq1, uint32_t seq2) { const struct seq_range *data; unsigned int idx, idx2, count, remove_count = 0; /* remove first and last. this makes sure that everything between can simply be deleted with array_delete(). FIXME: it would be faster if we did only one binary lookup here and handled the splitting ourself.. */ if (seq_range_array_remove(array, seq1)) remove_count++; if (seq1 == seq2) return remove_count; seq1++; if (seq_range_array_remove(array, seq2--)) remove_count++; if (seq1 > seq2) return remove_count; /* find the beginning */ data = array_get(array, &count); seq_range_lookup(array, seq1, &idx); if (idx == count) return remove_count; i_assert(data[idx].seq1 >= seq1); for (idx2 = idx; idx2 < count; idx2++) { if (data[idx2].seq1 > seq2) break; i_assert(UINT_MAX - remove_count >= seq_range_length(&data[idx2])); remove_count += seq_range_length(&data[idx2]); } array_delete(array, idx, idx2-idx); return remove_count; } unsigned int seq_range_array_remove_seq_range(ARRAY_TYPE(seq_range) *dest, const ARRAY_TYPE(seq_range) *src) { unsigned int count, full_count = 0; const struct seq_range *src_range; array_foreach(src, src_range) { count = seq_range_array_remove_range(dest, src_range->seq1, src_range->seq2); i_assert(UINT_MAX - full_count >= count); full_count += count; } return full_count; } void seq_range_array_remove_nth(ARRAY_TYPE(seq_range) *array, uint32_t n, uint32_t count) { struct seq_range_iter iter; uint32_t seq1, seq2; if (count == 0) return; seq_range_array_iter_init(&iter, array); if (!seq_range_array_iter_nth(&iter, n, &seq1)) { /* n points beyond array */ return; } if (count-1 >= (uint32_t)-1 - n || !seq_range_array_iter_nth(&iter, n + (count-1), &seq2)) { /* count points beyond array */ seq2 = (uint32_t)-1; } seq_range_array_remove_range(array, seq1, seq2); } unsigned int seq_range_array_intersect(ARRAY_TYPE(seq_range) *dest, const ARRAY_TYPE(seq_range) *src) { const struct seq_range *src_range; unsigned int i, count, remove_count, full_count = 0; uint32_t last_seq = 0; src_range = array_get(src, &count); for (i = 0; i < count; i++) { if (last_seq + 1 < src_range[i].seq1) { remove_count = seq_range_array_remove_range(dest, last_seq + 1, src_range[i].seq1 - 1); i_assert(UINT_MAX - full_count >= remove_count); full_count += remove_count; } last_seq = src_range[i].seq2; } if (last_seq != (uint32_t)-1) { remove_count = seq_range_array_remove_range(dest, last_seq + 1, (uint32_t)-1); i_assert(UINT_MAX - full_count >= remove_count); full_count += remove_count; } return full_count; } bool seq_range_exists(const ARRAY_TYPE(seq_range) *array, uint32_t seq) { unsigned int idx; return seq_range_lookup(array, seq, &idx); } bool seq_range_array_have_common(const ARRAY_TYPE(seq_range) *array1, const ARRAY_TYPE(seq_range) *array2) { const struct seq_range *range1, *range2; unsigned int i1, i2, count1, count2; range1 = array_get(array1, &count1); range2 = array_get(array2, &count2); for (i1 = i2 = 0; i1 < count1 && i2 < count2; ) { if (range1[i1].seq1 <= range2[i2].seq2 && range1[i1].seq2 >= range2[i2].seq1) return TRUE; if (range1[i1].seq1 < range2[i2].seq1) i1++; else i2++; } return FALSE; } unsigned int seq_range_count(const ARRAY_TYPE(seq_range) *array) { const struct seq_range *range; unsigned int seq_count = 0; array_foreach(array, range) { i_assert(UINT_MAX - seq_count >= seq_range_length(range)); seq_count += seq_range_length(range); } return seq_count; } void seq_range_array_invert(ARRAY_TYPE(seq_range) *array, uint32_t min_seq, uint32_t max_seq) { struct seq_range *range, value; unsigned int i, count; uint32_t prev_min_seq; if (array_is_created(array)) range = array_get_modifiable(array, &count); else { range = NULL; count = 0; } if (count == 0) { /* empty -> full */ if (!array_is_created(array)) i_array_init(array, 4); value.seq1 = min_seq; value.seq2 = max_seq; array_push_back(array, &value); return; } i_assert(range[0].seq1 >= min_seq); i_assert(range[count-1].seq2 <= max_seq); if (range[0].seq1 == min_seq && range[0].seq2 == max_seq) { /* full -> empty */ array_clear(array); return; } for (i = 0; i < count; ) { prev_min_seq = min_seq; min_seq = range[i].seq2; if (range[i].seq1 == prev_min_seq) { array_delete(array, i, 1); range = array_get_modifiable(array, &count); } else { range[i].seq2 = range[i].seq1 - 1; range[i].seq1 = prev_min_seq; i++; } if (min_seq >= max_seq) { /* max_seq is reached. the rest of the array should be empty. we'll return here, because min_seq++ may wrap to 0. */ i_assert(min_seq == max_seq); i_assert(i == count); return; } min_seq++; } if (min_seq <= max_seq) { value.seq1 = min_seq; value.seq2 = max_seq; array_push_back(array, &value); } } void seq_range_array_iter_init(struct seq_range_iter *iter_r, const ARRAY_TYPE(seq_range) *array) { i_zero(iter_r); iter_r->array = array; } bool seq_range_array_iter_nth(struct seq_range_iter *iter, unsigned int n, uint32_t *seq_r) { const struct seq_range *range; unsigned int i, count, diff; if (n < iter->prev_n) { /* iterating backwards, don't bother optimizing */ iter->prev_n = 0; iter->prev_idx = 0; } range = array_get(iter->array, &count); for (i = iter->prev_idx; i < count; i++) { diff = range[i].seq2 - range[i].seq1; if (n <= iter->prev_n + diff) { i_assert(n >= iter->prev_n); *seq_r = range[i].seq1 + (n - iter->prev_n); iter->prev_idx = i; return TRUE; } iter->prev_n += diff + 1; } iter->prev_idx = i; return FALSE; } dovecot-2.3.21.1/src/lib/mountpoint.c0000644000000000000000000001772414656633576014245 00000000000000/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "mountpoint.h" #include #ifdef HAVE_SYS_VMOUNT_H # include # include /* AIX */ # define MOUNTPOINT_AIX_MNTCTL #elif defined(HAVE_STATVFS_MNTFROMNAME) # include /* NetBSD 3.0+, FreeBSD 5.0+ */ # define STATVFS_STR "statvfs" # define MOUNTPOINT_STATVFS #elif defined(HAVE_STATFS_MNTFROMNAME) # include /* Older BSDs */ # include # define statvfs statfs # define STATVFS_STR "statfs" # define MOUNTPOINT_STATVFS #elif defined(HAVE_MNTENT_H) # include # include /* Linux */ # define MOUNTPOINT_LINUX #elif defined(HAVE_SYS_MNTTAB_H) # include # include /* Solaris */ # include # define MOUNTPOINT_SOLARIS #else # define MOUNTPOINT_UNKNOWN #endif #ifdef MOUNTPOINT_SOLARIS # define MTAB_PATH MNTTAB /* Solaris */ #else # define MTAB_PATH "/etc/mtab" /* Linux */ #endif /* AIX doesn't have these defined */ #ifndef MNTTYPE_SWAP # define MNTTYPE_SWAP "swap" #endif #ifndef MNTTYPE_IGNORE # define MNTTYPE_IGNORE "ignore" #endif #ifndef MNTTYPE_JFS # define MNTTYPE_JFS "jfs" #endif #ifndef MNTTYPE_NFS # define MNTTYPE_NFS "nfs" #endif /* Linux sometimes has mtab entry for "rootfs" as well as the real root entry. Skip the rootfs. */ #ifndef MNTTYPE_ROOTFS # define MNTTYPE_ROOTFS "rootfs" #endif #ifdef MOUNTPOINT_STATVFS static int mountpoint_get_statvfs(const char *path, pool_t pool, struct mountpoint *point_r) { struct statvfs buf; i_zero(point_r); if (statvfs(path, &buf) < 0) { if (errno == ENOENT) return 0; i_error(STATVFS_STR"(%s) failed: %m", path); return -1; } point_r->device_path = p_strdup(pool, buf.f_mntfromname); point_r->mount_path = p_strdup(pool, buf.f_mntonname); #ifdef __osf__ /* Tru64 */ point_r->type = p_strdup(pool, getvfsbynumber(buf.f_type)); #else point_r->type = p_strdup(pool, buf.f_fstypename); #endif point_r->block_size = buf.f_bsize; return 1; } #endif int mountpoint_get(const char *path, pool_t pool, struct mountpoint *point_r) { #ifdef MOUNTPOINT_UNKNOWN i_zero(point_r); errno = ENOSYS; return -1; #elif defined (MOUNTPOINT_STATVFS) /* BSDs, Tru64 */ return mountpoint_get_statvfs(path, pool, point_r); #else /* find via mount iteration */ struct mountpoint_iter *iter; const struct mountpoint *mnt; struct stat st; i_zero(point_r); if (stat(path, &st) < 0) { if (errno == ENOENT) return 0; i_error("stat(%s) failed: %m", path); return -1; } iter = mountpoint_iter_init(); while ((mnt = mountpoint_iter_next(iter)) != NULL) { if (minor(st.st_dev) == minor(mnt->dev) && major(st.st_dev) == major(mnt->dev)) break; } if (mnt != NULL) { point_r->device_path = p_strdup(pool, mnt->device_path); point_r->mount_path = p_strdup(pool, mnt->mount_path); point_r->type = p_strdup(pool, mnt->type); point_r->dev = mnt->dev; point_r->block_size = st.st_blksize; } if (mountpoint_iter_deinit(&iter) < 0 && mnt == NULL) return -1; return mnt != NULL ? 1 : 0; #endif } struct mountpoint_iter { #ifdef MOUNTPOINT_AIX_MNTCTL char *mtab; struct vmount *vmt; int count; #elif defined(MOUNTPOINT_SOLARIS) || defined(MOUNTPOINT_LINUX) FILE *f; #elif defined(HAVE_GETMNTINFO) /* BSDs */ #ifndef __NetBSD__ struct statfs *fs; #else struct statvfs *fs; #endif int count; #endif struct mountpoint mnt; bool failed; }; struct mountpoint_iter *mountpoint_iter_init(void) { struct mountpoint_iter *iter = i_new(struct mountpoint_iter, 1); #ifdef MOUNTPOINT_AIX_MNTCTL unsigned int size = STATIC_MTAB_SIZE; char *mtab; int count; mtab = t_buffer_get(size); while ((count = mntctl(MCTL_QUERY, size, mtab)) == 0) { size = *(unsigned int *)mtab; mtab = t_buffer_get(size); } if (count < 0) { i_error("mntctl(MCTL_QUERY) failed: %m"); iter->failed = TRUE; return iter; } iter->count = count; iter->mtab = i_malloc(size); memcpy(iter->mtab, mtab, size); iter->vmt = (void *)iter->mtab; #elif defined(MOUNTPOINT_SOLARIS) iter->f = fopen(MTAB_PATH, "r"); if (iter->f == NULL) { i_error("fopen(%s) failed: %m", MTAB_PATH); iter->failed = TRUE; return iter; } resetmnttab(iter->f); #elif defined(MOUNTPOINT_LINUX) iter->f = setmntent(MTAB_PATH, "r"); if (iter->f == NULL) { i_error("setmntent(%s) failed: %m", MTAB_PATH); iter->failed = TRUE; } #elif defined(HAVE_GETMNTINFO) /* BSDs */ iter->count = getmntinfo(&iter->fs, MNT_NOWAIT); if (iter->count < 0) { i_error("getmntinfo() failed: %m"); iter->failed = TRUE; } #else iter->failed = TRUE; #endif return iter; } const struct mountpoint *mountpoint_iter_next(struct mountpoint_iter *iter) { #ifdef MOUNTPOINT_AIX_MNTCTL struct vmount *vmt = iter->vmt; char *vmt_base = (char *)vmt; char *vmt_object, *vmt_stub, *vmt_hostname; struct stat vst; if (iter->count == 0) return NULL; iter->count--; iter->vmt = PTR_OFFSET(vmt, vmt->vmt_length); vmt_hostname = vmt_base + vmt->vmt_data[VMT_HOSTNAME].vmt_off; vmt_object = vmt_base + vmt->vmt_data[VMT_OBJECT].vmt_off; vmt_stub = vmt_base + vmt->vmt_data[VMT_STUB].vmt_off; i_zero(&iter->mnt); switch (vmt->vmt_gfstype) { case MNT_NFS: case MNT_NFS3: case MNT_NFS4: case MNT_RFS4: iter->mnt.device_path = t_strconcat(vmt_hostname, ":", vmt_object, NULL); iter->mnt.mount_path = vmt_stub; iter->mnt.type = MNTTYPE_NFS; break; case MNT_J2: case MNT_JFS: iter->mnt.device_path = vmt_object; iter->mnt.mount_path = vmt_stub; iter->mnt.type = MNTTYPE_JFS; break; default: /* unwanted filesystem */ return mountpoint_iter_next(iter); } if (stat(iter->mnt.mount_path, &vst) == 0) { iter->mnt.dev = vst.st_dev; iter->mnt.block_size = vst.st_blksize; } return &iter->mnt; #elif defined (MOUNTPOINT_SOLARIS) union { struct mnttab ent; struct extmnttab ext; } ent; if (iter->f == NULL) return NULL; i_zero(&iter->mnt); while ((getextmntent(iter->f, &ent.ext, sizeof(ent.ext))) == 0) { if (hasmntopt(&ent.ent, MNTOPT_IGNORE) != NULL) continue; /* mnt_type contains tmpfs with swap */ if (strcmp(ent.ent.mnt_special, MNTTYPE_SWAP) == 0) continue; iter->mnt.device_path = ent.ent.mnt_special; iter->mnt.mount_path = ent.ent.mnt_mountp; iter->mnt.type = ent.ent.mnt_fstype; iter->mnt.dev = makedev(ent.ext.mnt_major, ent.ext.mnt_minor); return &iter->mnt; } return NULL; #elif defined (MOUNTPOINT_LINUX) const struct mntent *ent; struct stat st; if (iter->f == NULL) return NULL; i_zero(&iter->mnt); while ((ent = getmntent(iter->f)) != NULL) { if (strcmp(ent->mnt_type, MNTTYPE_SWAP) == 0 || strcmp(ent->mnt_type, MNTTYPE_IGNORE) == 0 || strcmp(ent->mnt_type, MNTTYPE_ROOTFS) == 0) continue; iter->mnt.device_path = ent->mnt_fsname; iter->mnt.mount_path = ent->mnt_dir; iter->mnt.type = ent->mnt_type; if (stat(ent->mnt_dir, &st) == 0) { iter->mnt.dev = st.st_dev; iter->mnt.block_size = st.st_blksize; } return &iter->mnt; } return NULL; #elif defined(HAVE_GETMNTINFO) /* BSDs */ while (iter->count > 0) { #ifndef __NetBSD__ struct statfs *fs = iter->fs; #else struct statvfs *fs = iter->fs; #endif iter->fs++; iter->count--; iter->mnt.device_path = fs->f_mntfromname; iter->mnt.mount_path = fs->f_mntonname; #ifdef __osf__ /* Tru64 */ iter->mnt.type = getvfsbynumber(fs->f_type); #else iter->mnt.type = fs->f_fstypename; #endif iter->mnt.block_size = fs->f_bsize; return &iter->mnt; } return NULL; #else return NULL; #endif } int mountpoint_iter_deinit(struct mountpoint_iter **_iter) { struct mountpoint_iter *iter = *_iter; int ret = iter->failed ? -1 : 0; *_iter = NULL; #ifdef MOUNTPOINT_AIX_MNTCTL i_free(iter->mtab); #elif defined (MOUNTPOINT_SOLARIS) if (iter->f != NULL) fclose(iter->f); #elif defined (MOUNTPOINT_LINUX) if (iter->f != NULL) endmntent(iter->f); #endif i_free(iter); return ret; } dovecot-2.3.21.1/src/lib/test-strnum.c0000644000000000000000000002641414656633576014332 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #define INVALID(n) { #n, -1, 0 } #define VALID(n) { #n, 0, n } /* always pads with leading zeros to a size of 9 digits */ static int crappy_uintmax_to_str(char *into, uintmax_t val) { #define BIGBASE 1000000000ull #define STRINGIFY(s) #s #define STRINGIFY2(s) STRINGIFY(s) int len = 0; if(val >= BIGBASE) { len = crappy_uintmax_to_str(into, val/BIGBASE); } i_snprintf(into + len, 10, "%09llu", (unsigned long long)(val % BIGBASE)); return len + strlen(STRINGIFY2(BIGBASE))-4; #undef STRINGIFY2 #undef STRINGIFY #undef BIGBASE } static void test_str_to_uintmax(void) { unsigned int i=0; int randrange = i_rand_minmax(1, 15); /* when 1, will max out on 1s */ uintmax_t value = 0, valbase = i_rand() * 1000ull; int len, ret; char buff[50]; /* totally assumes < 159 bits */ test_begin("str_to_uintmax in range"); while (i < sizeof(uintmax_t)*CHAR_BIT) { uintmax_t value_back; const char *endp; value = (value << 1) + 1; if (value >= 64) value -= i_rand_limit(randrange); /* don't always test the same numbers */ len = crappy_uintmax_to_str(buff, value); ret = str_to_uintmax(buff, &value_back); test_assert_idx(ret == 0, i); test_assert_idx(value == value_back, i); /* test with trailing noise */ buff[len] = 'x'; /* don't even null-terminate, let's be evil */ value_back = 0x1234567890123456; ret = str_to_uintmax(buff, &value_back); test_assert_idx(ret < 0, i); test_assert_idx(value_back == 0x1234567890123456, i); ret = str_parse_uintmax(buff, &value_back, &endp); test_assert_idx(ret == 0, i); test_assert_idx(value_back == value, i); test_assert_idx(endp == &buff[len], i); i++; } test_end(); /* not knowing exactly how large a uintmax_t is, we have to construct the troublesome near-10/9*MAX strings manually by appending digits to a MAX/9 string which we can easily create. Do a wider range of 30 rather than the obvious 10, just in case - all are too large.*/ test_begin("str_to_uintmax overflow corner case"); value = UINTMAX_MAX/9-1; len = crappy_uintmax_to_str(buff, value); buff[len] = '0'; buff[len+1] = '\0'; for(i = 0; i <= 30; ++i) { int j = len + 1; while (buff[--j] == '9') buff[j] = '0'; buff[j]++; value = valbase + i; ret = str_to_uintmax(buff, &value); test_assert_idx(ret < 0 && value == valbase + i, i); } test_end(); } /* always pads with leading zeros to a size of 9 digits */ static int crappy_uintmax_to_str_hex(char *into, uintmax_t val) { #define BIGBASE 0x1000000000ull #define STRINGIFY(s) #s #define STRINGIFY2(s) STRINGIFY(s) int len = 0; if(val >= BIGBASE) { len = crappy_uintmax_to_str_hex(into, val/BIGBASE); } i_snprintf(into + len, 10, "%09llx", (unsigned long long)(val % BIGBASE)); return len + strlen(STRINGIFY2(BIGBASE))-6; #undef STRINGIFY2 #undef STRINGIFY #undef BIGBASE } static void test_str_to_uintmax_hex(void) { unsigned int i=0; int randrange = i_rand_minmax(1, 15); /* when 1, will max out on 1s */ uintmax_t value = 0, valbase = i_rand() * 1000ull; int len, ret; char buff[52]; /* totally assumes < 200 bits */ test_begin("str_to_uintmax_hex in range"); while (i < sizeof(uintmax_t)*CHAR_BIT) { uintmax_t value_back; const char *endp; value = (value << 1) + 1; if (value >= 64) value -= i_rand_limit(randrange); /* don't always test the same numbers */ len = crappy_uintmax_to_str_hex(buff, value); ret = str_to_uintmax_hex(buff, &value_back); test_assert_idx(ret == 0, i); test_assert_idx(value == value_back, i); /* test with trailing noise */ buff[len] = 'x'; /* don't even null-terminate, let's be evil */ value_back = 0x1234567890123456; ret = str_to_uintmax_hex(buff, &value_back); test_assert_idx(ret < 0, i); test_assert_idx(value_back == 0x1234567890123456, i); ret = str_parse_uintmax_hex(buff, &value_back, &endp); test_assert_idx(ret == 0, i); test_assert_idx(value_back == value, i); test_assert_idx(endp == &buff[len], i); i++; } test_end(); /* not knowing exactly how large a uintmax_t is, we have to construct the troublesome near-0x10/0x0F*MAX strings manually by appending digits to a MAX/0x0f string which we can easily create. Do a wider range of 0x30 rather than the obvious 0x10, just in case - all are too large.*/ test_begin("str_to_uintmax_hex overflow corner case"); value = (UINTMAX_MAX/0x0f)-1; len = crappy_uintmax_to_str_hex(buff, value); buff[len] = '0'; buff[len+1] = '\0'; for(i = 0; i <= 0x30; ++i) { int j = len + 1; while (buff[--j] == 'f') buff[j] = '0'; if (buff[j] == '9') buff[j] = 'a'; else buff[j]++; value = valbase + i; ret = str_to_uintmax_hex(buff, &value); test_assert_idx(ret < 0 && value == valbase + i, i); } test_end(); } /* always pads with leading zeros to a size of 9 digits */ static int crappy_uintmax_to_str_oct(char *into, uintmax_t val) { #define BIGBASE 01000000000ull #define STRINGIFY(s) #s #define STRINGIFY2(s) STRINGIFY(s) int len = 0; if(val >= BIGBASE) { len = crappy_uintmax_to_str_oct(into, val/BIGBASE); } i_snprintf(into + len, 10, "%09llo", (unsigned long long)(val % BIGBASE)); return len + strlen(STRINGIFY2(BIGBASE))-5; #undef STRINGIFY2 #undef STRINGIFY #undef BIGBASE } static void test_str_to_uintmax_oct(void) { unsigned int i=0; int randrange = i_rand_minmax(1, 15); /* when 1, will max out on 1s */ uintmax_t value = 0, valbase = i_rand() * 1000ull; int len, ret; char buff[69]; /* totally assumes < 200 bits */ test_begin("str_to_uintmax_oct in range"); while (i < sizeof(uintmax_t)*CHAR_BIT) { uintmax_t value_back; const char *endp = NULL; value = (value << 1) + 1; if (value >= 64) value -= i_rand_limit(randrange); /* don't always test the same numbers */ len = crappy_uintmax_to_str_oct(buff, value); ret = str_to_uintmax_oct(buff, &value_back); test_assert_idx(ret == 0, i); test_assert_idx(value == value_back, i); /* test with trailing noise */ buff[len] = 'x'; /* don't even null-terminate, let's be evil */ value_back = 0x1234567890123456; ret = str_to_uintmax_oct(buff, &value_back); test_assert_idx(ret < 0, i); test_assert_idx(value_back == 0x1234567890123456, i); ret = str_parse_uintmax_oct(buff, &value_back, &endp); test_assert_idx(ret == 0, i); test_assert_idx(value_back == value, i); test_assert_idx(endp == &buff[len], i); i++; } test_end(); /* not knowing exactly how large a uintmax_t is, we have to construct the troublesome near-010/007*MAX strings manually by appending digits to a MAX/007 string which we can easily create. Do a wider range of 030 rather than the obvious 010, just in case - all are too large.*/ test_begin("str_to_uintmax_oct overflow corner case"); value = (UINTMAX_MAX/007)-1; len = crappy_uintmax_to_str_oct(buff, value); buff[len] = '0'; buff[len+1] = '\0'; for(i = 0; i <= 030; ++i) { int j = len + 1; while (buff[--j] == '7') buff[j] = '0'; buff[j]++; value = valbase + i; ret = str_to_uintmax_oct(buff, &value); test_assert_idx(ret < 0 && value == valbase + i, i); } test_end(); } static void test_str_to_u64(void) { unsigned int i; const struct { const char *input; int ret; uint64_t val; } u64tests[] = { INVALID(-1), INVALID(foo), VALID(0), VALID(000000000000000000000000000000000000000000000000000000000000000), { "000000000000000000000000000000000000000000000000000001000000001", 0, 1000000001 }, { "18446744073709551615", 0, 18446744073709551615ULL }, INVALID(18446744073709551616), INVALID(20496382304121724010), /* 2^64*10/9 doesn't wrap */ INVALID(20496382304121724017), /* 2^64*10/9 wraps only after addition */ INVALID(20496382304121724020), /* 2^64*10/9 wraps on multiply*/ }; test_begin("str_to_uint64"); for (i = 0; i < N_ELEMENTS(u64tests); ++i) { uint64_t val = 0xBADBEEF15BADF00D; int ret = str_to_uint64(u64tests[i].input, &val); test_assert_idx(ret == u64tests[i].ret, i); if (ret == 0) test_assert_idx(val == u64tests[i].val, i); else test_assert_idx(val == 0xBADBEEF15BADF00D, i); if (ret == 0) T_BEGIN { const char *longer = t_strconcat(u64tests[i].input, "x", NULL); ret = str_to_uint64(longer, &val); test_assert_idx(ret < 0, i); } T_END; } test_end(); } static void test_str_to_u32(void) { unsigned int i; const struct { const char *input; int ret; uint32_t val; } u32tests[] = { VALID(0), INVALID(-0), VALID(4294967295), INVALID(4294967296), INVALID(4772185880), INVALID(4772185884), INVALID(4772185890), }; test_begin("str_to_uint32"); for (i = 0; i < N_ELEMENTS(u32tests); ++i) { uint32_t val = 0xDEADF00D; int ret = str_to_uint32(u32tests[i].input, &val); test_assert_idx(ret == u32tests[i].ret, i); if (ret == 0) test_assert_idx(val == u32tests[i].val, i); else test_assert_idx(val == 0xDEADF00D, i); } test_end(); } /* Assumes long long is 64 bit, 2's complement */ static void test_str_to_llong(void) { unsigned int i; const struct { const char *input; int ret; long long val; } i64tests[] = { VALID(0), VALID(-0), INVALID(--0), VALID(2147483648), VALID(-2147483649), VALID(9223372036854775807), { "-9223372036854775808", 0, -9223372036854775807-1 }, INVALID(9223372036854775808), INVALID(-9223372036854775809), }; test_begin("str_to_llong"); for (i = 0; i < N_ELEMENTS(i64tests); ++i) { long long val = 123456789; int ret = str_to_llong(i64tests[i].input, &val); test_assert_idx(ret == i64tests[i].ret, i); if (ret == 0) test_assert_idx(val == i64tests[i].val, i); else test_assert_idx(val == 123456789, i); } test_end(); } /* Assumes int is 32 bit, 2's complement */ static void test_str_to_i32(void) { unsigned int i; const struct { const char *input; int ret; int val; } i32tests[] = { VALID(0), VALID(-0), INVALID(--0), VALID(2147483647), VALID(-2147483648), INVALID(2147483648), INVALID(-2147483649), }; test_begin("str_to_int"); for (i = 0; i < N_ELEMENTS(i32tests); ++i) { int val = 123456789; int ret = str_to_int(i32tests[i].input, &val); test_assert_idx(ret == i32tests[i].ret, i); if (ret == 0) test_assert_idx(val == i32tests[i].val, i); else test_assert_idx(val == 123456789, i); } test_end(); } static void test_str_is_float(void) { test_begin("str_is_float accepts integer"); /* accepts integer */ test_assert(str_is_float("0",'\0')); test_assert(str_is_float("1234",'\0')); test_end(); test_begin("str_is_float accepts float"); test_assert(str_is_float("0.0",'\0')); test_assert(str_is_float("1234.0",'\0')); test_assert(str_is_float("0.1234",'\0')); test_assert(str_is_float("1234.1234",'\0')); test_assert(str_is_float("0.1234 ",' ')); test_assert(str_is_float("1234.1234",'.')); test_end(); test_begin("str_is_float refuses invalid values"); test_assert(!str_is_float(".",'\0')); test_assert(!str_is_float(".1234",'\0')); test_assert(!str_is_float("1234.",'\0')); test_assert(!str_is_float("i am not a float at all",'\0')); test_assert(!str_is_float("0x1234.0x1234",'\0')); test_assert(!str_is_float(".0",'\0')); test_assert(!str_is_float("0.",'\0')); test_end(); } void test_strnum(void) { /* If the above isn't true, then we do expect some failures possibly */ test_str_to_uintmax(); test_str_to_uintmax_hex(); test_str_to_uintmax_oct(); test_str_to_u64(); test_str_to_u32(); test_str_to_llong(); test_str_to_i32(); test_str_is_float(); } dovecot-2.3.21.1/src/lib/test-mempool.c0000644000000000000000000000725514656633576014454 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #if SIZEOF_VOID_P == 8 typedef char uint32max_array_t[4294967295]; #else typedef char uint32max_array_t[65535]; #endif #define BIG_MAX POOL_MAX_ALLOC_SIZE #if defined(_LP64) #define LITTLE_MAX ((unsigned long long) INT32_MAX) #else #define LITTLE_MAX ((unsigned long long) INT16_MAX) #endif extern struct pool test_pool; /* Checks allocations & reallocations for a given type. */ #define CHECK_OVERFLOW(type, nelem, _maxsize) \ do { \ const size_t maxsize = (_maxsize); \ test_begin("mempool overflow - " #type); \ type *ptr = p_new(&test_pool, type, (nelem)); \ test_assert(ptr == POINTER_CAST(maxsize)); \ /* grow: */ \ test_assert(p_realloc_type(&test_pool, ptr, type, (nelem) - 1, (nelem)) == POINTER_CAST(maxsize)); \ /* shrink: */ \ test_assert(p_realloc_type(&test_pool, ptr, type, (nelem), (nelem) - 1) == POINTER_CAST(maxsize - sizeof(type))); \ test_end(); \ } while (0) static void test_mempool_overflow(void) { CHECK_OVERFLOW(uint32max_array_t, LITTLE_MAX, sizeof(uint32max_array_t) * LITTLE_MAX); CHECK_OVERFLOW(char, BIG_MAX, BIG_MAX); CHECK_OVERFLOW(uint32_t, BIG_MAX / sizeof(uint32_t), BIG_MAX - 3); } enum fatal_test_state fatal_mempool(unsigned int stage) { static uint32max_array_t *m1; static uint32_t *m2; switch(stage) { case 0: test_expect_fatal_string("Trying to allocate"); test_begin("fatal mempool overflow"); m1 = p_new(&test_pool, uint32max_array_t, LITTLE_MAX + 3); return FATAL_TEST_FAILURE; case 1: test_expect_fatal_string("Trying to allocate"); m2 = p_new(&test_pool, uint32_t, BIG_MAX / sizeof(uint32_t) + 1); return FATAL_TEST_FAILURE; case 2: /* grow */ test_expect_fatal_string("Trying to reallocate"); m1 = p_realloc_type(&test_pool, m1, uint32max_array_t, LITTLE_MAX + 2, LITTLE_MAX + 3); return FATAL_TEST_FAILURE; case 3: test_expect_fatal_string("Trying to reallocate"); m2 = p_realloc_type(&test_pool, m2, uint32_t, BIG_MAX / sizeof(uint32_t), BIG_MAX / sizeof(uint32_t) + 1); return FATAL_TEST_FAILURE; case 4: /* shrink */ test_expect_fatal_string("Trying to reallocate"); m1 = p_realloc_type(&test_pool, m1, uint32max_array_t, LITTLE_MAX + 3, LITTLE_MAX + 2); return FATAL_TEST_FAILURE; case 5: test_expect_fatal_string("Trying to reallocate"); m2 = p_realloc_type(&test_pool, m2, uint32_t, BIG_MAX / sizeof(uint32_t) + 2, BIG_MAX / sizeof(uint32_t) + 1); return FATAL_TEST_FAILURE; } test_expect_fatal_string(NULL); test_end(); return FATAL_TEST_FINISHED; } static const char *pool_test_get_name(pool_t pool ATTR_UNUSED) { return "test"; } static void pool_test_ref(pool_t pool ATTR_UNUSED) { } static void pool_test_unref(pool_t *pool) { *pool = NULL; } static void *pool_test_malloc(pool_t pool ATTR_UNUSED, size_t size) { return POINTER_CAST(size); } static void pool_test_free(pool_t pool ATTR_UNUSED, void *mem ATTR_UNUSED) { } static void *pool_test_realloc(pool_t pool ATTR_UNUSED, void *mem ATTR_UNUSED, size_t old_size ATTR_UNUSED, size_t new_size) { return POINTER_CAST(new_size); } static void pool_test_clear(pool_t pool ATTR_UNUSED) { } static size_t pool_test_get_max_easy_alloc_size(pool_t pool ATTR_UNUSED) { return 12345; } static const struct pool_vfuncs test_pool_vfuncs = { pool_test_get_name, pool_test_ref, pool_test_unref, pool_test_malloc, pool_test_free, pool_test_realloc, pool_test_clear, pool_test_get_max_easy_alloc_size }; struct pool test_pool = { .v = &test_pool_vfuncs, .alloconly_pool = TRUE, .datastack_pool = FALSE, }; void test_mempool(void) { test_mempool_overflow(); } dovecot-2.3.21.1/src/lib/bits.c0000644000000000000000000000150014656633576012753 00000000000000/* Copyright (c) 2001-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" /* * We could use bits_required64() unconditionally, but that's unnecessary * and way more heavy weight on 32-bit systems. */ #ifdef _LP64 #define BITS_REQUIRED(x) bits_required64(x) #else #define BITS_REQUIRED(x) bits_required32(x) #endif size_t nearest_power(size_t num) { i_assert(num <= ((size_t)1 << (CHAR_BIT*sizeof(size_t) - 1))); if (num == 0) return 1; return 1UL << BITS_REQUIRED(num - 1); } #if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) /* Lucky you, it's all inline intrinsics */ #else unsigned int bits_required8(uint8_t num) { int ret = 0; if (num > 0xf) { ret += 4; num >>= 4; } if (num > 0x3) { ret += 2; num >>= 2; } num &= ~(num>>1); /* 3->2, else unchanged */ return ret + num; } #endif dovecot-2.3.21.1/src/lib/test-iostream-proxy.c0000644000000000000000000000551514656633576016003 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "istream.h" #include "ostream.h" #include "buffer.h" #include "ioloop.h" #include "iostream-proxy.h" #include #include #include static void completed(enum iostream_proxy_side side ATTR_UNUSED, enum iostream_proxy_status status ATTR_UNUSED, int *u0) { i_assert(*u0 > 0); if (--*u0 == 0) io_loop_stop(current_ioloop); } static void test_iostream_proxy_simple(void) { size_t bytes; test_begin("iostream_proxy"); int sfdl[2]; int sfdr[2]; int counter; test_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, sfdl) == 0); test_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, sfdr) == 0); fd_set_nonblock(sfdl[0], TRUE); fd_set_nonblock(sfdl[1], TRUE); fd_set_nonblock(sfdr[0], TRUE); fd_set_nonblock(sfdr[1], TRUE); struct ioloop *ioloop = io_loop_create(); struct istream *left_in = i_stream_create_fd(sfdl[1], IO_BLOCK_SIZE); struct ostream *left_out = o_stream_create_fd(sfdl[1], IO_BLOCK_SIZE); struct istream *right_in = i_stream_create_fd(sfdr[1], IO_BLOCK_SIZE); struct ostream *right_out = o_stream_create_fd(sfdr[1], IO_BLOCK_SIZE); struct iostream_proxy *proxy; proxy = iostream_proxy_create(left_in, left_out, right_in, right_out); i_stream_unref(&left_in); o_stream_unref(&left_out); i_stream_unref(&right_in); o_stream_unref(&right_out); iostream_proxy_set_completion_callback(proxy, completed, &counter); iostream_proxy_start(proxy); left_in = i_stream_create_fd(sfdl[0], IO_BLOCK_SIZE); left_out = o_stream_create_fd(sfdl[0], IO_BLOCK_SIZE); right_in = i_stream_create_fd(sfdr[0], IO_BLOCK_SIZE); right_out = o_stream_create_fd(sfdr[0], IO_BLOCK_SIZE); test_assert(proxy != NULL); test_assert(o_stream_send_str(left_out, "hello, world") > 0); test_assert(o_stream_flush(left_out) > 0); o_stream_unref(&left_out); test_assert(shutdown(sfdl[0], SHUT_WR) == 0); counter = 1; io_loop_run(ioloop); test_assert(i_stream_read(right_in) > 0); test_assert(strcmp((const char*)i_stream_get_data(right_in, &bytes), "hello, world") == 0); i_stream_skip(right_in, bytes); test_assert(o_stream_send_str(right_out, "hello, world") > 0); test_assert(o_stream_flush(right_out) > 0); o_stream_unref(&right_out); test_assert(shutdown(sfdr[0], SHUT_WR) == 0); counter = 1; io_loop_run(ioloop); test_assert(i_stream_read(left_in) > 0); test_assert(strcmp((const char*)i_stream_get_data(left_in, &bytes), "hello, world") == 0); i_stream_skip(left_in, bytes); iostream_proxy_unref(&proxy); io_loop_destroy(&ioloop); i_stream_unref(&left_in); i_stream_unref(&right_in); /* close fd */ i_close_fd(&sfdl[0]); i_close_fd(&sfdl[1]); i_close_fd(&sfdr[0]); i_close_fd(&sfdr[1]); test_end(); } void test_iostream_proxy(void) { T_BEGIN { test_iostream_proxy_simple(); } T_END; } dovecot-2.3.21.1/src/lib/file-dotlock.c0000644000000000000000000005535314656633576014405 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "str.h" #include "hex-binary.h" #include "hostpid.h" #include "file-lock.h" #include "eacces-error.h" #include "write-full.h" #include "safe-mkstemp.h" #include "nfs-workarounds.h" #include "file-dotlock.h" #include "sleep.h" #include #include #include #include #include #define DEFAULT_LOCK_SUFFIX ".lock" /* 0.1 .. 0.2msec */ #define LOCK_RANDOM_USLEEP_TIME (100000 + (unsigned int)i_rand() % 100000) /* Maximum 3 second wait between dotlock checks */ #define LOCK_MAX_WAIT_USECS (1000000 * 3) /* If the dotlock is newer than this, don't verify that the PID it contains is valid (since it most likely is). */ #define STALE_PID_CHECK_SECS 2 /* Maximum difference between current time and create file's ctime before logging a warning. Should be less than a second in normal operation. */ #define MAX_TIME_DIFF 30 /* NFS may return a cached mtime in stat(). A later non-cached stat() may return a slightly different mtime. Allow the difference to be this much and still consider it to be the same mtime. */ #define FILE_DOTLOCK_MAX_STAT_MTIME_DIFF 1 struct dotlock { struct dotlock_settings settings; dev_t dev; ino_t ino; time_t mtime; char *path; char *lock_path; int fd; time_t lock_time; }; struct file_change_info { dev_t dev; ino_t ino; off_t size; time_t ctime, mtime; }; struct lock_info { const struct dotlock_settings *set; const char *path, *lock_path, *temp_path; int fd; struct file_change_info lock_info; struct file_change_info file_info; time_t last_pid_check; time_t last_change; unsigned int wait_usecs; bool have_pid:1; bool pid_read:1; bool use_io_notify:1; bool lock_stated:1; }; static struct dotlock * file_dotlock_alloc(const struct dotlock_settings *settings, const char *path) { struct dotlock *dotlock; dotlock = i_new(struct dotlock, 1); dotlock->settings = *settings; if (dotlock->settings.lock_suffix == NULL) dotlock->settings.lock_suffix = DEFAULT_LOCK_SUFFIX; dotlock->path = i_strdup(path); dotlock->fd = -1; return dotlock; } static pid_t read_local_pid(const char *lock_path) { char buf[512], *host; int fd; ssize_t ret; pid_t pid; fd = open(lock_path, O_RDONLY); if (fd == -1) return -1; /* ignore the actual error */ /* read line */ ret = read(fd, buf, sizeof(buf)-1); i_close_fd(&fd); if (ret <= 0) return -1; /* fix the string */ if (buf[ret-1] == '\n') ret--; buf[ret] = '\0'; /* it should contain pid:host */ host = strchr(buf, ':'); if (host == NULL) return -1; *host++ = '\0'; /* host must be ours */ if (strcmp(host, my_hostname) != 0) return -1; if (str_to_pid(buf, &pid) < 0) return -1; if (pid <= 0) return -1; return pid; } static bool update_change_info(const struct stat *st, struct file_change_info *change, time_t *last_change_r, time_t now, bool check_ctime) { /* ctime is checked only if we're not doing NFS attribute cache flushes. it changes them. */ if (change->ino != st->st_ino || !CMP_DEV_T(change->dev, st->st_dev) || (change->ctime != st->st_ctime && check_ctime) || change->mtime != st->st_mtime || change->size != st->st_size) { time_t change_time = now; if (change->ctime == 0) { /* First check, set last_change to file's change time. Use mtime instead if it's higher, but only if it's not higher than current time, because the mtime can also be used for keeping metadata. */ change_time = st->st_mtime <= now && (st->st_mtime > st->st_ctime || !check_ctime) ? st->st_mtime : st->st_ctime; } if (*last_change_r < change_time) *last_change_r = change_time; change->ino = st->st_ino; change->dev = st->st_dev; change->ctime = st->st_ctime; change->mtime = st->st_mtime; change->size = st->st_size; return TRUE; } return FALSE; } static int update_lock_info(time_t now, struct lock_info *lock_info, bool *changed_r) { struct stat st; /* don't waste time flushing attribute cache the first time we're here. if it's stale we'll get back here soon. */ if (lock_info->set->nfs_flush && lock_info->lock_stated) { nfs_flush_file_handle_cache(lock_info->lock_path); nfs_flush_attr_cache_unlocked(lock_info->lock_path); } lock_info->lock_stated = TRUE; if (nfs_safe_lstat(lock_info->lock_path, &st) < 0) { if (errno != ENOENT) { i_error("lstat(%s) failed: %m", lock_info->lock_path); return -1; } return 1; } *changed_r = update_change_info(&st, &lock_info->lock_info, &lock_info->last_change, now, !lock_info->set->nfs_flush); return 0; } static int dotlock_override(struct lock_info *lock_info) { if (i_unlink_if_exists(lock_info->lock_path) < 0) return -1; /* make sure we sleep for a while after overriding the lock file. otherwise another process might try to override it at the same time and unlink our newly created dotlock. */ if (lock_info->use_io_notify) i_sleep_usecs(LOCK_RANDOM_USLEEP_TIME); return 0; } static int check_lock(time_t now, struct lock_info *lock_info) { time_t stale_timeout = lock_info->set->stale_timeout; pid_t pid = -1; bool changed; int ret; if ((ret = update_lock_info(now, lock_info, &changed)) != 0) return ret; if (changed || !lock_info->pid_read) { /* either our first check or someone else got the lock file. if the dotlock was created only a couple of seconds ago, don't bother to read its PID. */ if (lock_info->lock_info.mtime >= now - STALE_PID_CHECK_SECS) lock_info->pid_read = FALSE; else { pid = read_local_pid(lock_info->lock_path); lock_info->pid_read = TRUE; } lock_info->have_pid = pid != -1; } else if (!lock_info->have_pid) { /* no pid checking */ } else { if (lock_info->last_pid_check == now) { /* we just checked the pid */ return 0; } /* re-read the pid. even if all times and inodes are the same, the PID in the file might have changed if lock files were rapidly being recreated. */ pid = read_local_pid(lock_info->lock_path); lock_info->have_pid = pid != -1; } if (lock_info->have_pid) { /* we've local PID. Check if it exists. */ if (kill(pid, 0) == 0 || errno != ESRCH) { if (pid != getpid()) { /* process exists, don't override */ return 0; } /* it's us. either we're locking it again, or it's a stale lock file with same pid than us. either way, recreate it.. */ } /* doesn't exist - now check again if the dotlock was just deleted or replaced */ if ((ret = update_lock_info(now, lock_info, &changed)) != 0) return ret; if (!changed) { /* still there, go ahead and override it */ return dotlock_override(lock_info); } return 1; } if (stale_timeout == 0) { /* no change checking */ return 0; } if (now > lock_info->last_change + stale_timeout) { struct stat st; /* possibly stale lock file. check also the timestamp of the file we're protecting. */ if (lock_info->set->nfs_flush) { nfs_flush_file_handle_cache(lock_info->path); nfs_flush_attr_cache_maybe_locked(lock_info->path); } if (nfs_safe_stat(lock_info->path, &st) < 0) { if (errno == ENOENT) { /* file doesn't exist. treat it as if it hasn't changed */ } else { i_error("stat(%s) failed: %m", lock_info->path); return -1; } } else { (void)update_change_info(&st, &lock_info->file_info, &lock_info->last_change, now, !lock_info->set->nfs_flush); } } if (now > lock_info->last_change + stale_timeout) { /* no changes for a while, assume stale lock */ return dotlock_override(lock_info); } return 0; } static int file_write_pid(int fd, const char *path, bool nfs_flush) { const char *str; /* write our pid and host, if possible */ str = t_strdup_printf("%s:%s", my_pid, my_hostname); if (write_full(fd, str, strlen(str)) < 0 || (nfs_flush && fdatasync(fd) < 0)) { /* failed, leave it empty then */ if (ftruncate(fd, 0) < 0) { i_error("ftruncate(%s) failed: %m", path); return -1; } } return 0; } static int try_create_lock_hardlink(struct lock_info *lock_info, bool write_pid, string_t *tmp_path, time_t now) { const char *temp_prefix = lock_info->set->temp_prefix; const char *p; mode_t old_mask; struct stat st; if (lock_info->temp_path == NULL) { /* we'll need our temp file first. */ i_assert(lock_info->fd == -1); p = strrchr(lock_info->lock_path, '/'); str_truncate(tmp_path, 0); if (temp_prefix != NULL) { if (*temp_prefix != '/' && p != NULL) { /* add directory */ str_append_data(tmp_path, lock_info->lock_path, p - lock_info->lock_path); str_append_c(tmp_path, '/'); } str_append(tmp_path, temp_prefix); } else { if (p != NULL) { /* add directory */ str_append_data(tmp_path, lock_info->lock_path, p - lock_info->lock_path); str_append_c(tmp_path, '/'); } str_printfa(tmp_path, ".temp.%s.%s.", my_hostname, my_pid); } old_mask = umask(0666); lock_info->fd = safe_mkstemp(tmp_path, 0666 ^ old_mask, (uid_t)-1, (gid_t)-1); umask(old_mask); if (lock_info->fd == -1) return -1; if (write_pid) { if (file_write_pid(lock_info->fd, str_c(tmp_path), lock_info->set->nfs_flush) < 0) { i_close_fd(&lock_info->fd); return -1; } } lock_info->temp_path = str_c(tmp_path); } else if (fstat(lock_info->fd, &st) < 0) { i_error("fstat(%s) failed: %m", lock_info->temp_path); return -1; } else if (st.st_ctime < now) { /* we've been waiting for a while. refresh the file's timestamp. */ if (utime(lock_info->temp_path, NULL) < 0) i_error("utime(%s) failed: %m", lock_info->temp_path); } if (nfs_safe_link(lock_info->temp_path, lock_info->lock_path, TRUE) < 0) { if (errno == EEXIST) return 0; if (errno != EACCES) { i_error("link(%s, %s) failed: %m", lock_info->temp_path, lock_info->lock_path); } return -1; } if (i_unlink(lock_info->temp_path) < 0) { /* non-fatal, continue */ } lock_info->temp_path = NULL; return 1; } static int try_create_lock_excl(struct lock_info *lock_info, bool write_pid) { int fd; fd = open(lock_info->lock_path, O_RDWR | O_EXCL | O_CREAT, 0666); if (fd == -1) { if (errno == EEXIST) return 0; if (errno != ENOENT && errno != EACCES) i_error("open(%s) failed: %m", lock_info->lock_path); return -1; } if (write_pid) { if (file_write_pid(fd, lock_info->lock_path, lock_info->set->nfs_flush) < 0) { i_close_fd(&fd); return -1; } } lock_info->fd = fd; return 1; } static void dotlock_wait_end(struct ioloop *ioloop) { io_loop_stop(ioloop); } static void dotlock_wait(struct lock_info *lock_info) { struct ioloop *ioloop; struct io *io; struct timeout *to; if (!lock_info->use_io_notify) { i_sleep_usecs(lock_info->wait_usecs); return; } ioloop = io_loop_create(); switch (io_add_notify(lock_info->lock_path, dotlock_wait_end, ioloop, &io)) { case IO_NOTIFY_ADDED: break; case IO_NOTIFY_NOTFOUND: /* the lock file doesn't exist anymore, don't sleep */ io_loop_destroy(&ioloop); return; case IO_NOTIFY_NOSUPPORT: /* listening for files not supported */ io_loop_destroy(&ioloop); lock_info->use_io_notify = FALSE; i_sleep_usecs(LOCK_RANDOM_USLEEP_TIME); return; } /* timeout after a random time even when using notify, since it doesn't work reliably with e.g. NFS. */ to = timeout_add(lock_info->wait_usecs/1000, dotlock_wait_end, ioloop); io_loop_run(ioloop); io_remove(&io); timeout_remove(&to); io_loop_destroy(&ioloop); } static int dotlock_create(struct dotlock *dotlock, enum dotlock_create_flags flags, bool write_pid, const char **lock_path_r) { const struct dotlock_settings *set = &dotlock->settings; const char *lock_path; struct lock_info lock_info; struct stat st; unsigned int stale_notify_threshold; unsigned int change_secs, wait_left; time_t now, max_wait_time, last_notify; time_t prev_last_change = 0, prev_wait_update = 0; string_t *tmp_path; int ret; bool do_wait; now = time(NULL); lock_path = *lock_path_r = t_strconcat(dotlock->path, set->lock_suffix, NULL); stale_notify_threshold = set->stale_timeout / 2; max_wait_time = (flags & DOTLOCK_CREATE_FLAG_NONBLOCK) != 0 ? 0 : now + set->timeout; tmp_path = t_str_new(256); i_zero(&lock_info); lock_info.path = dotlock->path; lock_info.set = set; lock_info.lock_path = lock_path; lock_info.fd = -1; lock_info.use_io_notify = set->use_io_notify; last_notify = 0; do_wait = FALSE; file_lock_wait_start(); do { if (do_wait) { if (prev_last_change != lock_info.last_change) { /* dotlock changed since last check, reset the wait time */ lock_info.wait_usecs = LOCK_RANDOM_USLEEP_TIME; prev_last_change = lock_info.last_change; prev_wait_update = now; } else if (prev_wait_update != now && lock_info.wait_usecs < LOCK_MAX_WAIT_USECS) { /* we've been waiting for a while now, increase the wait time to avoid wasting CPU */ prev_wait_update = now; lock_info.wait_usecs += lock_info.wait_usecs/2; } dotlock_wait(&lock_info); now = time(NULL); } ret = check_lock(now, &lock_info); if (ret < 0) break; if (ret == 1) { if ((flags & DOTLOCK_CREATE_FLAG_CHECKONLY) != 0) break; ret = set->use_excl_lock ? try_create_lock_excl(&lock_info, write_pid) : try_create_lock_hardlink(&lock_info, write_pid, tmp_path, now); if (ret != 0) { /* if we succeeded, get the current time once more in case disk I/O usage was really high and it took a long time to create the lock */ now = time(NULL); break; } } if (last_notify != now && set->callback != NULL && now < max_wait_time) { last_notify = now; change_secs = now - lock_info.last_change; wait_left = max_wait_time - now; if (change_secs >= stale_notify_threshold && change_secs <= wait_left) { unsigned int secs_left = set->stale_timeout < change_secs ? 0 : set->stale_timeout - change_secs; if (!set->callback(secs_left, TRUE, set->context)) { /* we don't want to override */ lock_info.last_change = now; } } else if (wait_left > 0) { (void)set->callback(wait_left, FALSE, set->context); } } do_wait = TRUE; now = time(NULL); } while (now < max_wait_time); file_lock_wait_end(dotlock->path); if (ret > 0) { i_assert(lock_info.fd != -1); if (fstat(lock_info.fd, &st) < 0) { i_error("fstat(%s) failed: %m", lock_path); ret = -1; } else { /* successful dotlock creation */ dotlock->dev = st.st_dev; dotlock->ino = st.st_ino; dotlock->fd = lock_info.fd; dotlock->lock_time = now; lock_info.fd = -1; if (st.st_ctime + MAX_TIME_DIFF < now || st.st_ctime - MAX_TIME_DIFF > now) { i_warning("Created dotlock file's timestamp is " "different than current time " "(%s vs %s): %s", dec2str(st.st_ctime), dec2str(now), dotlock->path); } } } if (lock_info.fd != -1) { int old_errno = errno; if (close(lock_info.fd) < 0) i_error("close(%s) failed: %m", lock_path); errno = old_errno; } if (lock_info.temp_path != NULL) i_unlink(lock_info.temp_path); if (ret == 0) errno = EAGAIN; return ret; } static void file_dotlock_free(struct dotlock **_dotlock) { struct dotlock *dotlock = *_dotlock; int old_errno; *_dotlock = NULL; if (dotlock->fd != -1) { old_errno = errno; if (close(dotlock->fd) < 0) i_error("close(%s) failed: %m", dotlock->path); dotlock->fd = -1; errno = old_errno; } i_free(dotlock->path); i_free(dotlock->lock_path); i_free(dotlock); } static int file_dotlock_create_real(struct dotlock *dotlock, enum dotlock_create_flags flags) { const char *lock_path; struct stat st; int fd, ret; ret = dotlock_create(dotlock, flags, TRUE, &lock_path); if (ret <= 0 || (flags & DOTLOCK_CREATE_FLAG_CHECKONLY) != 0) return ret; fd = dotlock->fd; dotlock->fd = -1; if (close(fd) < 0) { i_error("close(%s) failed: %m", lock_path); return -1; } /* With NFS the writes may have been flushed only when closing the file. Get the mtime again after that to avoid "dotlock was modified" errors. */ if (lstat(lock_path, &st) < 0) { if (errno != ENOENT) i_error("stat(%s) failed: %m", lock_path); else { i_error("dotlock %s was immediately deleted under us", lock_path); } return -1; } /* extra sanity check won't hurt.. */ if (st.st_dev != dotlock->dev || st.st_ino != dotlock->ino) { errno = ENOENT; i_error("dotlock %s was immediately recreated under us", lock_path); return -1; } dotlock->mtime = st.st_mtime; return 1; } int file_dotlock_create(const struct dotlock_settings *set, const char *path, enum dotlock_create_flags flags, struct dotlock **dotlock_r) { struct dotlock *dotlock; int ret; dotlock = file_dotlock_alloc(set, path); T_BEGIN { ret = file_dotlock_create_real(dotlock, flags); } T_END; if (ret <= 0 || (flags & DOTLOCK_CREATE_FLAG_CHECKONLY) != 0) file_dotlock_free(&dotlock); *dotlock_r = dotlock; return ret; } static void dotlock_replaced_warning(struct dotlock *dotlock, bool deleted) { const char *lock_path; time_t now = time(NULL); lock_path = file_dotlock_get_lock_path(dotlock); if (dotlock->mtime == dotlock->lock_time) { i_warning("Our dotlock file %s was %s " "(locked %d secs ago, touched %d secs ago)", lock_path, deleted ? "deleted" : "overridden", (int)(now - dotlock->lock_time), (int)(now - dotlock->mtime)); } else { i_warning("Our dotlock file %s was %s " "(kept it %d secs)", lock_path, deleted ? "deleted" : "overridden", (int)(now - dotlock->lock_time)); } } static bool file_dotlock_has_mtime_changed(time_t t1, time_t t2) { time_t diff; if (t1 == t2) return FALSE; /* with NFS t1 may have been looked up from local cache. allow it to be a little bit different. */ diff = t1 > t2 ? t1-t2 : t2-t1; return diff > FILE_DOTLOCK_MAX_STAT_MTIME_DIFF; } int file_dotlock_delete(struct dotlock **dotlock_p) { struct dotlock *dotlock; const char *lock_path; struct stat st; int ret; dotlock = *dotlock_p; *dotlock_p = NULL; lock_path = file_dotlock_get_lock_path(dotlock); if (nfs_safe_lstat(lock_path, &st) < 0) { if (errno == ENOENT) { dotlock_replaced_warning(dotlock, TRUE); file_dotlock_free(&dotlock); return 0; } i_error("lstat(%s) failed: %m", lock_path); file_dotlock_free(&dotlock); return -1; } if (dotlock->ino != st.st_ino || !CMP_DEV_T(dotlock->dev, st.st_dev)) { dotlock_replaced_warning(dotlock, FALSE); errno = EEXIST; file_dotlock_free(&dotlock); return 0; } if (file_dotlock_has_mtime_changed(dotlock->mtime, st.st_mtime) && dotlock->fd == -1) { i_warning("Our dotlock file %s was modified (%s vs %s), " "assuming it wasn't overridden (kept it %d secs)", lock_path, dec2str(dotlock->mtime), dec2str(st.st_mtime), (int)(time(NULL) - dotlock->lock_time)); } if ((ret = i_unlink_if_exists(lock_path)) == 0) dotlock_replaced_warning(dotlock, TRUE); file_dotlock_free(&dotlock); return ret; } int file_dotlock_open(const struct dotlock_settings *set, const char *path, enum dotlock_create_flags flags, struct dotlock **dotlock_r) { struct dotlock *dotlock; int ret; dotlock = file_dotlock_alloc(set, path); T_BEGIN { const char *lock_path; ret = dotlock_create(dotlock, flags, FALSE, &lock_path); } T_END; if (ret <= 0) { file_dotlock_free(&dotlock); *dotlock_r = NULL; return -1; } *dotlock_r = dotlock; return dotlock->fd; } static int ATTR_NULL(7) file_dotlock_open_mode_full(const struct dotlock_settings *set, const char *path, enum dotlock_create_flags flags, mode_t mode, uid_t uid, gid_t gid, const char *gid_origin, struct dotlock **dotlock_r) { struct dotlock *dotlock; mode_t old_mask; int fd; old_mask = umask(0666 ^ mode); fd = file_dotlock_open(set, path, flags, &dotlock); umask(old_mask); if (fd != -1 && (uid != (uid_t)-1 || gid != (gid_t)-1)) { if (fchown(fd, uid, gid) < 0) { if (errno == EPERM && uid == (uid_t)-1) { i_error("%s", eperm_error_get_chgrp("fchown", file_dotlock_get_lock_path(dotlock), gid, gid_origin)); } else { i_error("fchown(%s, %ld, %ld) failed: %m", file_dotlock_get_lock_path(dotlock), (long)uid, (long)gid); } file_dotlock_delete(&dotlock); return -1; } } *dotlock_r = dotlock; return fd; } int file_dotlock_open_mode(const struct dotlock_settings *set, const char *path, enum dotlock_create_flags flags, mode_t mode, uid_t uid, gid_t gid, struct dotlock **dotlock_r) { return file_dotlock_open_mode_full(set, path, flags, mode, uid, gid, NULL, dotlock_r); } int file_dotlock_open_group(const struct dotlock_settings *set, const char *path, enum dotlock_create_flags flags, mode_t mode, gid_t gid, const char *gid_origin, struct dotlock **dotlock_r) { return file_dotlock_open_mode_full(set, path, flags, mode, (uid_t)-1, gid, gid_origin, dotlock_r); } int file_dotlock_replace(struct dotlock **dotlock_p, enum dotlock_replace_flags flags) { struct dotlock *dotlock; const char *lock_path; bool is_locked; dotlock = *dotlock_p; *dotlock_p = NULL; is_locked = (flags & DOTLOCK_REPLACE_FLAG_VERIFY_OWNER) == 0 ? TRUE : file_dotlock_is_locked(dotlock); if ((flags & DOTLOCK_REPLACE_FLAG_DONT_CLOSE_FD) != 0) dotlock->fd = -1; if (!is_locked) { dotlock_replaced_warning(dotlock, FALSE); errno = EEXIST; file_dotlock_free(&dotlock); return 0; } lock_path = file_dotlock_get_lock_path(dotlock); if (rename(lock_path, dotlock->path) < 0) { i_error("rename(%s, %s) failed: %m", lock_path, dotlock->path); if (errno == ENOENT) dotlock_replaced_warning(dotlock, TRUE); file_dotlock_free(&dotlock); return -1; } file_dotlock_free(&dotlock); return 1; } int file_dotlock_touch(struct dotlock *dotlock) { time_t now = time(NULL); struct utimbuf buf; int ret = 0; if (dotlock->mtime == now) return 0; dotlock->mtime = now; buf.actime = buf.modtime = now; T_BEGIN { const char *lock_path = file_dotlock_get_lock_path(dotlock); if (utime(lock_path, &buf) < 0) { i_error("utime(%s) failed: %m", lock_path); ret = -1; } } T_END; return ret; } bool file_dotlock_is_locked(struct dotlock *dotlock) { struct stat st, st2; const char *lock_path; lock_path = file_dotlock_get_lock_path(dotlock); if (fstat(dotlock->fd, &st) < 0) { i_error("fstat(%s) failed: %m", lock_path); return FALSE; } if (nfs_safe_lstat(lock_path, &st2) < 0) { i_error("lstat(%s) failed: %m", lock_path); return FALSE; } return st.st_ino == st2.st_ino && CMP_DEV_T(st.st_dev, st2.st_dev); } const char *file_dotlock_get_lock_path(struct dotlock *dotlock) { if (dotlock->lock_path == NULL) { dotlock->lock_path = i_strconcat(dotlock->path, dotlock->settings.lock_suffix, NULL); } return dotlock->lock_path; } dovecot-2.3.21.1/src/lib/process-stat.h0000644000000000000000000000105714656633576014455 00000000000000#ifndef PROCESS_STAT_H #define PROCESS_STAT_H struct process_stat { uint64_t utime; uint64_t stime; uint64_t minor_faults; uint64_t major_faults; uint64_t vol_cs; uint64_t invol_cs; uint64_t rss; uint64_t vsz; uint64_t rchar; uint64_t wchar; uint64_t syscr; uint64_t syscw; bool proc_io_failed:1; bool rusage_failed:1; bool proc_stat_failed:1; bool proc_status_failed:1; }; void process_stat_read_start(struct process_stat *stat_r, struct event *event); void process_stat_read_finish(struct process_stat *stat, struct event *event); #endif dovecot-2.3.21.1/src/lib/time-util.c0000644000000000000000000000742414656633576013736 00000000000000/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "time-util.h" #include #define STRFTIME_MAX_BUFSIZE (1024*64) void i_gettimeofday(struct timeval *tv_r) { if (gettimeofday(tv_r, NULL) < 0) i_fatal("gettimeofday() failed: %m"); } uint64_t i_nanoseconds(void) { struct timespec ts; if (clock_gettime(CLOCK_REALTIME, &ts) < 0) i_fatal("clock_gettime() failed: %m"); return ts.tv_sec * 1000000000ULL + ts.tv_nsec; } int timeval_cmp(const struct timeval *tv1, const struct timeval *tv2) { if (tv1->tv_sec < tv2->tv_sec) return -1; if (tv1->tv_sec > tv2->tv_sec) return 1; if (tv1->tv_usec < tv2->tv_usec) return -1; if (tv1->tv_usec > tv2->tv_usec) return 1; return 0; } int timeval_cmp_margin(const struct timeval *tv1, const struct timeval *tv2, unsigned int usec_margin) { long long secs_diff, usecs_diff; int sec_margin, ret; if (tv1->tv_sec < tv2->tv_sec) { sec_margin = ((int)usec_margin / 1000000) + 1; secs_diff = (long long)tv2->tv_sec - (long long)tv1->tv_sec; if (secs_diff > sec_margin) return -1; usecs_diff = secs_diff * 1000000LL + (tv2->tv_usec - tv1->tv_usec); ret = -1; } else if (tv1->tv_sec > tv2->tv_sec) { sec_margin = ((int)usec_margin / 1000000) + 1; secs_diff = (long long)tv1->tv_sec - (long long)tv2->tv_sec; if (secs_diff > sec_margin) return 1; usecs_diff = secs_diff * 1000000LL + (tv1->tv_usec - tv2->tv_usec); ret = 1; } else if (tv1->tv_usec < tv2->tv_usec) { usecs_diff = tv2->tv_usec - tv1->tv_usec; ret = -1; } else { usecs_diff = tv1->tv_usec - tv2->tv_usec; ret = 1; } i_assert(usecs_diff >= 0); return (unsigned long long)usecs_diff > usec_margin ? ret : 0; } int timeval_diff_msecs(const struct timeval *tv1, const struct timeval *tv2) { long long diff = timeval_diff_usecs(tv1, tv2) / 1000LL; #ifdef DEBUG /* FIXME v2.4: Remove the ifdef */ i_assert(diff <= INT_MAX); #endif return (int)diff; } long long timeval_diff_usecs(const struct timeval *tv1, const struct timeval *tv2) { time_t secs; int usecs; secs = tv1->tv_sec - tv2->tv_sec; usecs = tv1->tv_usec - tv2->tv_usec; if (usecs < 0) { secs--; usecs += 1000000; } return ((long long)secs * 1000000LL) + usecs; } time_t time_to_local_day_start(time_t t) { const struct tm *day_tm; struct tm tm; time_t new_start_time; day_tm = localtime(&t); i_zero(&tm); tm.tm_year = day_tm->tm_year; tm.tm_mon = day_tm->tm_mon; tm.tm_mday = day_tm->tm_mday; tm.tm_isdst = -1; new_start_time = mktime(&tm); i_assert(new_start_time != (time_t)-1); return new_start_time; } static const char *strftime_real(const char *fmt, const struct tm *tm) { size_t bufsize = strlen(fmt) + 32; char *buf = t_buffer_get(bufsize); size_t ret; while ((ret = strftime(buf, bufsize, fmt, tm)) == 0) { bufsize *= 2; i_assert(bufsize <= STRFTIME_MAX_BUFSIZE); buf = t_buffer_get(bufsize); } t_buffer_alloc(ret + 1); return buf; } const char *t_strftime(const char *fmt, const struct tm *tm) { return strftime_real(fmt, tm); } const char *t_strflocaltime(const char *fmt, time_t t) { return strftime_real(fmt, localtime(&t)); } const char *t_strfgmtime(const char *fmt, time_t t) { return strftime_real(fmt, gmtime(&t)); } int str_to_timeval(const char *str, struct timeval *tv_r) { tv_r->tv_usec = 0; const char *p = strchr(str, '.'); if (p == NULL) return str_to_time(str, &tv_r->tv_sec); int ret; T_BEGIN { ret = str_to_time(t_strdup_until(str, p++), &tv_r->tv_sec); } T_END; if (ret < 0 || p[0] == '\0') return -1; unsigned int len = strlen(p); if (len > 6) return -1; /* we don't support sub-microseconds */ char usecs_str[7] = "000000"; memcpy(usecs_str, p, len); unsigned int usec; if (str_to_uint(usecs_str, &usec) < 0) return -1; tv_r->tv_usec = usec; return 0; } dovecot-2.3.21.1/src/lib/wildcard-match.h0000644000000000000000000000077314656633576014715 00000000000000#ifndef WILDCARD_MATCH_H #define WILDCARD_MATCH_H /* Returns TRUE if mask matches data. mask can contain '*' and '?' wildcards. */ bool wildcard_match(const char *data, const char *mask); /* Like wildcard_match(), but match ASCII characters case-insensitively. */ bool wildcard_match_icase(const char *data, const char *mask); /* Returns TRUE if mask does *not* contain any '*' or '?' wildcards. */ static inline bool wildcard_is_literal(const char *mask) { return strpbrk(mask, "*?") == NULL; } #endif dovecot-2.3.21.1/src/lib/test-mempool-allocfree.c0000644000000000000000000000573314656633576016405 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #define SENSE 0xAB /* produces 10101011 */ static bool mem_has_bytes(const void *mem, size_t size, uint8_t b) { const uint8_t *bytes = mem; unsigned int i; for (i = 0; i < size; i++) { if (bytes[i] != b) { i_debug("bytes[%u] != %u", i, b); return FALSE; } } return TRUE; } void test_mempool_allocfree(void) { pool_t pool; unsigned int i; size_t last_alloc = 0; size_t used = 0; void *mem = NULL; test_begin("mempool_allocfree"); pool = pool_allocfree_create("test"); for(i = 0; i <= 1000; i++) { /* release previous allocation */ if ((i % 3) == 0) { if (mem != NULL) { test_assert_idx(mem_has_bytes(mem, last_alloc, SENSE), i); used -= last_alloc; } last_alloc = 0; p_free(pool, mem); /* grow previous allocation */ } else if ((i % 5) == 0) { if (mem != NULL) used -= last_alloc; mem = p_realloc(pool, mem, last_alloc, i*2); if (last_alloc > 0) test_assert_idx(mem_has_bytes(mem, last_alloc, SENSE), i); memset(mem, SENSE, i*2); last_alloc = i*2; used += i*2; /* shrink previous allocation */ } else if ((i % 7) == 0) { if (mem != NULL) used -= last_alloc; mem = p_realloc(pool, mem, last_alloc, i-2); if (last_alloc > 0) test_assert_idx(mem_has_bytes(mem, i-2, SENSE), i); memset(mem, SENSE, i-2); last_alloc = i-2; used += i-2; /* allocate some memory */ } else { mem = p_malloc(pool, i); /* fill it with sense marker */ memset(mem, SENSE, i); used += i; last_alloc = i; } } test_assert(pool_allocfree_get_total_used_size(pool) == used); pool_unref(&pool); /* make sure realloc works correctly */ pool = pool_allocfree_create("test"); mem = NULL; for(i = 1; i < 1000; i++) { mem = p_realloc(pool, mem, i-1, i); test_assert_idx(mem_has_bytes(mem, i-1, 0xde), i); memset(mem, 0xde, i); } pool_unref(&pool); test_end(); } enum fatal_test_state fatal_mempool_allocfree(unsigned int stage) { static pool_t pool; if (pool == NULL && stage != 0) return FATAL_TEST_FAILURE; switch(stage) { case 0: /* forbidden size */ test_begin("fatal_mempool_allocfree"); pool = pool_allocfree_create("fatal"); test_expect_fatal_string("Trying to allocate 0 bytes"); (void)p_malloc(pool, 0); return FATAL_TEST_FAILURE; case 1: /* logically impossible size */ test_expect_fatal_string("Trying to allocate"); (void)p_malloc(pool, POOL_MAX_ALLOC_SIZE + 1ULL); return FATAL_TEST_FAILURE; #ifdef _LP64 /* malloc(POOL_MAX_ALLOC_SIZE) may succeed with 32bit */ case 2: /* physically impossible size */ test_expect_fatal_string("Out of memory"); (void)p_malloc(pool, POOL_MAX_ALLOC_SIZE); return FATAL_TEST_FAILURE; #endif /* Continue with other tests as follows: case 3: something_fatal(); return FATAL_TEST_FAILURE; */ } /* Either our tests have finished, or the test suite has got confused. */ pool_unref(&pool); test_end(); return FATAL_TEST_FINISHED; } dovecot-2.3.21.1/src/lib/lib-event-private.h0000644000000000000000000000723114656633576015363 00000000000000#ifndef LIB_EVENT_PRIVATE_H #define LIB_EVENT_PRIVATE_H #include struct event_pointer { const char *key; void *value; }; struct event { /* linked list of all events, newest first */ struct event *prev, *next; int refcount; pool_t pool; struct event *parent; uint64_t id; /* Avoid sending the event to stats over and over. The 'change_id' increments every time something about this event changes. If 'sent_to_stats_id' matches 'change_id', we skip sending this event out. If it doesn't match, we send it and set 'sent_to_stats_id' to 'change_id'. sent_to_stats_id=0 is reserved for "event hasn't been sent". 'change_id' can never be 0. */ uint32_t change_id; uint32_t sent_to_stats_id; char *log_prefix; unsigned int log_prefixes_dropped; /* sending_debug_log can be used if this value matches event_filter_replace_counter. */ unsigned int debug_level_checked_filter_counter; event_log_prefix_callback_t *log_prefix_callback; void *log_prefix_callback_context; event_log_message_callback_t *log_message_callback; void *log_message_callback_context; ARRAY(struct event_pointer) pointers; /* If the event's log level is at least this high, log it. If it's lower, check for debug log filters etc. */ enum log_type min_log_level; bool log_prefix_from_system_pool:1; bool log_prefix_replace:1; bool passthrough:1; bool forced_debug:1; bool always_log_source:1; bool sending_debug_log:1; /* Fields that are exported & imported: */ struct timeval tv_created_ioloop; struct timeval tv_created; struct timeval tv_last_sent; struct rusage ru_last; const char *source_filename; unsigned int source_linenum; /* This is the event's name while it's being sent. It'll be removed after the event is sent. */ char *sending_name; ARRAY(struct event_category *) categories; ARRAY(struct event_field) fields; }; enum event_callback_type { /* Event was just created */ EVENT_CALLBACK_TYPE_CREATE, /* Event is being sent */ EVENT_CALLBACK_TYPE_SEND, /* Event is being freed */ EVENT_CALLBACK_TYPE_FREE, }; /* Returns TRUE if the event should continue to the next handler. Unless stopped, the final handler logs the event if it matches the log filter. */ typedef bool event_callback_t(struct event *event, enum event_callback_type type, struct failure_context *ctx, const char *fmt, va_list args); /* Called when category is registered or unregistered. The parent category is always already registered. */ typedef void event_category_callback_t(struct event_category *category); void event_send(struct event *event, struct failure_context *ctx, const char *fmt, ...) ATTR_FORMAT(3, 4); void event_vsend(struct event *event, struct failure_context *ctx, const char *fmt, va_list args) ATTR_FORMAT(3, 0); struct event *events_get_head(void); /* Find event category by name. This only finds registered categories. */ struct event_category *event_category_find_registered(const char *name); /* Return all registered categories. */ struct event_category *const * event_get_registered_categories(unsigned int *count_r); /* Register callback to be called for event's different states. */ void event_register_callback(event_callback_t *callback); void event_unregister_callback(event_callback_t *callback); /* Register callback to be called whenever categories are registered or unregistered. */ void event_category_register_callback(event_category_callback_t *callback); void event_category_unregister_callback(event_category_callback_t *callback); static inline void event_recalculate_debug_level(struct event *event) { event->debug_level_checked_filter_counter = event_filter_replace_counter - 1; } #endif dovecot-2.3.21.1/src/lib/ostream.h0000644000000000000000000003211514656633576013477 00000000000000#ifndef OSTREAM_H #define OSTREAM_H #include "ioloop.h" enum ostream_send_istream_result { /* All of the istream was successfully sent to ostream. */ OSTREAM_SEND_ISTREAM_RESULT_FINISHED, /* Caller needs to wait for more input from non-blocking istream. */ OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT, /* Caller needs to wait for output to non-blocking ostream. o_stream_set_flush_pending() is automatically called. */ OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT, /* Read from istream failed. See istream->stream_errno. */ OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT, /* Write to ostream failed. See ostream->stream_errno. */ OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT }; enum ostream_create_file_flags { /* without append, file is truncated */ OSTREAM_CREATE_FILE_FLAG_APPEND = BIT(0), }; struct ostream { /* Number of bytes sent via o_stream_send*() and similar functions. This is counting the input data. For example with a compressed ostream this is counting the uncompressed bytes. The compressed bytes could be counted from the parent ostream's offset. Seeking to a specified offset only makes sense if there is no difference between input and output data sizes (e.g. there are no wrapper ostreams changing the data). */ uoff_t offset; /* errno for the last operation send/seek operation. cleared before each call. */ int stream_errno; /* overflow is set when some of the data given to send() functions was neither sent nor buffered. It's never unset inside ostream code. */ bool overflow:1; /* o_stream_send() writes all the data or returns failure */ bool blocking:1; bool closed:1; struct ostream_private *real_stream; }; /* Returns 1 if all data is sent (not necessarily flushed), 0 if not. Pretty much the only real reason to return 0 is if you wish to send more data to client which isn't buffered, eg. o_stream_send_istream(). */ typedef int stream_flush_callback_t(void *context); typedef void ostream_callback_t(void *context); /* Create new output stream from given file descriptor. If max_buffer_size is 0, an "optimal" buffer size is used (max 128kB). */ struct ostream *o_stream_create_fd(int fd, size_t max_buffer_size); /* The fd is set to -1 immediately to avoid accidentally closing it twice. */ struct ostream *o_stream_create_fd_autoclose(int *fd, size_t max_buffer_size); /* Create an output stream from a regular file which begins at given offset. If offset==UOFF_T_MAX, the current offset isn't known. */ struct ostream * o_stream_create_fd_file(int fd, uoff_t offset, bool autoclose_fd); struct ostream *o_stream_create_fd_file_autoclose(int *fd, uoff_t offset); /* Create ostream for file. If append flag is not set, file will be truncated. */ struct ostream *o_stream_create_file(const char *path, uoff_t offset, mode_t mode, enum ostream_create_file_flags flags); /* Create an output stream to a buffer. Note that the buffer is treated as the ostream's internal buffer. This means that o_stream_get_buffer_used_size() returns buf->used, and _get_buffer_avail_size() returns how many bytes can be written until the buffer's max size is reached. This behavior may make ostream-buffer unsuitable for code that assumes that having bytes in the internal buffer means that ostream isn't finished flushing its internal buffer. Especially o_stream_flush_parent_if_needed() (used by lib-compression ostreams) don't work with this. */ struct ostream *o_stream_create_buffer(buffer_t *buf); /* Create an output streams that always fails the writes. */ struct ostream *o_stream_create_error(int stream_errno); struct ostream * o_stream_create_error_str(int stream_errno, const char *fmt, ...) ATTR_FORMAT(2, 3); /* Create an output stream that simply passes through data. This is mainly useful as a wrapper when combined with destroy callbacks. */ struct ostream *o_stream_create_passthrough(struct ostream *output); /* Set name (e.g. path) for output stream. */ void o_stream_set_name(struct ostream *stream, const char *name); /* Get output stream's name. Returns "" if stream has no name. */ const char *o_stream_get_name(struct ostream *stream); /* Return file descriptor for stream, or -1 if none is available. */ int o_stream_get_fd(struct ostream *stream); /* Returns error string for the previous error. */ const char *o_stream_get_error(struct ostream *stream); /* Returns human-readable reason for why ostream was disconnected. The output is either "Connection closed" for clean disconnections or "Connection closed: " for unclean disconnections. This is an alternative to o_stream_get_error(), which is preferred to be used when logging errors about client connections. */ const char *o_stream_get_disconnect_reason(struct ostream *stream); /* Close this stream (but not its parents) and unreference it. */ void o_stream_destroy(struct ostream **stream); /* Reference counting. References start from 1, so calling o_stream_unref() destroys the stream if o_stream_ref() is never used. */ void o_stream_ref(struct ostream *stream); /* Unreferences the stream and sets stream pointer to NULL. */ void o_stream_unref(struct ostream **stream); /* Call the given callback function when stream is destroyed. */ void o_stream_add_destroy_callback(struct ostream *stream, ostream_callback_t *callback, void *context) ATTR_NULL(3); #define o_stream_add_destroy_callback(stream, callback, context) \ o_stream_add_destroy_callback(stream - \ CALLBACK_TYPECHECK(callback, void (*)(typeof(context))), \ (ostream_callback_t *)callback, context) /* Remove the destroy callback. */ void o_stream_remove_destroy_callback(struct ostream *stream, void (*callback)()); /* Mark the stream and all of its parent streams closed. Nothing will be sent after this call. When using ostreams that require writing a trailer, o_stream_finish() must be used before the stream is closed. When ostream is destroyed, it's also closed but its parents aren't. Closing the ostream (also via destroy) will first flush the ostream, and afterwards requires one of: a) stream has failed, b) there is no more buffered data, c) o_stream_set_no_error_handling() has been called. */ void o_stream_close(struct ostream *stream); /* Set IO_WRITE callback. Default will just try to flush the output and finishes when the buffer is empty. */ void o_stream_set_flush_callback(struct ostream *stream, stream_flush_callback_t *callback, void *context) ATTR_NULL(3); #define o_stream_set_flush_callback(stream, callback, context) \ o_stream_set_flush_callback(stream - \ CALLBACK_TYPECHECK(callback, int (*)(typeof(context))), \ (stream_flush_callback_t *)callback, context) void o_stream_unset_flush_callback(struct ostream *stream); /* Change the maximum size for stream's output buffer to grow. */ void o_stream_set_max_buffer_size(struct ostream *stream, size_t max_size); /* Returns the current max. buffer size. */ size_t o_stream_get_max_buffer_size(struct ostream *stream); /* Delays sending as far as possible, writing only full buffers. Also sets TCP_CORK on if supported. */ void o_stream_cork(struct ostream *stream); /* Try to flush the buffer by calling o_stream_flush() and remove TCP_CORK. Note that after this o_stream_flush() must be called, unless the stream ignores errors. */ void o_stream_uncork(struct ostream *stream); bool o_stream_is_corked(struct ostream *stream); /* Try to flush the output stream. If o_stream_nsend*() had been used and the stream had overflown, return error. Returns 1 if all data is sent, 0 there's still buffered data, -1 if error. */ int o_stream_flush(struct ostream *stream); /* Wrapper to easily both uncork and flush. */ static inline int o_stream_uncork_flush(struct ostream *stream) { o_stream_uncork(stream); return o_stream_flush(stream); } /* Set "flush pending" state of stream. If set, the flush callback is called when more data is allowed to be sent, even if the buffer itself is empty. Note that if the stream is corked, the flush callback won't be called until the stream is first uncorked. */ void o_stream_set_flush_pending(struct ostream *stream, bool set); /* Returns the number of bytes currently in all the pending write buffers of this ostream, including its parent streams. This function is commonly used by callers to determine when they've filled up the ostream so they can stop writing to it. Because of this, the return value shouldn't include buffers that are expected to be filled up before they send anything to their parent stream. Otherwise the callers may stop writing to the stream too early and hang. Such an example could be a compression ostream that won't send anything to its parent stream before an internal compression buffer is full. */ size_t o_stream_get_buffer_used_size(const struct ostream *stream) ATTR_PURE; /* Returns the (minimum) number of bytes we can still write without failing. This is commonly used by callers to find out how many bytes they're guaranteed to be able to send, and then generate that much data and send it. */ size_t o_stream_get_buffer_avail_size(const struct ostream *stream) ATTR_PURE; /* Seek to specified position from beginning of file. This works only for files. Returns 1 if successful, -1 if error. */ int o_stream_seek(struct ostream *stream, uoff_t offset); /* Returns number of bytes sent, -1 = error */ ssize_t o_stream_send(struct ostream *stream, const void *data, size_t size) ATTR_WARN_UNUSED_RESULT; ssize_t o_stream_sendv(struct ostream *stream, const struct const_iovec *iov, unsigned int iov_count) ATTR_WARN_UNUSED_RESULT; ssize_t o_stream_send_str(struct ostream *stream, const char *str) ATTR_WARN_UNUSED_RESULT; /* Send with delayed error handling. o_stream_flush() or o_stream_ignore_last_errors() must be called after these functions before the stream is destroyed. If any of the data can't be sent due to stream's buffer getting full, all further nsends are ignores and o_stream_flush() will fail. */ void o_stream_nsend(struct ostream *stream, const void *data, size_t size); void o_stream_nsendv(struct ostream *stream, const struct const_iovec *iov, unsigned int iov_count); void o_stream_nsend_str(struct ostream *stream, const char *str); /* Mark the ostream as finished and flush it. If the ostream has a footer, it's written here. Any further write attempts to the ostream will assert-crash. Returns the same as o_stream_flush(). Afterwards any calls to this function are identical to o_stream_flush(). */ int o_stream_finish(struct ostream *stream); /* Specify whether calling o_stream_finish() will cause the parent stream to be finished as well. The default is yes. */ void o_stream_set_finish_also_parent(struct ostream *stream, bool set); /* Specify whether calling o_stream_finish() on a child stream will cause this stream to be finished as well. The default is yes. */ void o_stream_set_finish_via_child(struct ostream *stream, bool set); /* Marks the stream's error handling as completed to avoid i_panic() on destroy. */ void o_stream_ignore_last_errors(struct ostream *stream); /* Abort writing to the ostream, also marking any previous error handling as completed. If the stream hasn't already failed, sets the stream_errno=EPIPE. This is necessary when aborting write to streams that require finishing. */ void o_stream_abort(struct ostream *stream); /* If error handling is disabled, the i_panic() on destroy is never called. This function can be called immediately after the stream is created. When creating wrapper streams, they copy this behavior from the parent stream. */ void o_stream_set_no_error_handling(struct ostream *stream, bool set); /* Send all of the instream to outstream. On non-failure instream is skips over all data written to outstream. This means that the number of bytes written to outstream is always equal to the number of bytes skipped in instream. It's also possible to use this function to copy data within same file descriptor, even if the source and destination overlaps. If the file must be grown, you have to do it manually before calling this function. */ enum ostream_send_istream_result ATTR_WARN_UNUSED_RESULT o_stream_send_istream(struct ostream *outstream, struct istream *instream); /* Same as o_stream_send_istream(), but assume that reads and writes will succeed. If not, o_stream_flush() will fail with the correct error message (even istream's). */ void o_stream_nsend_istream(struct ostream *outstream, struct istream *instream); /* Write data to specified offset. Returns 0 if successful, -1 if error. */ int o_stream_pwrite(struct ostream *stream, const void *data, size_t size, uoff_t offset); /* Return the last timestamp when something was successfully sent to the ostream's internal buffers (no guarantees that anything was sent further). The timestamp is 0 if nothing has ever been written. */ void o_stream_get_last_write_time(struct ostream *stream, struct timeval *tv_r); /* If there are any I/O loop items associated with the stream, move all of them to provided/current ioloop. */ void o_stream_switch_ioloop_to(struct ostream *stream, struct ioloop *ioloop); void o_stream_switch_ioloop(struct ostream *stream); #endif dovecot-2.3.21.1/src/lib/data-stack.h0000644000000000000000000001437014656633576014044 00000000000000#ifndef DATA_STACK_H #define DATA_STACK_H /* Data stack makes it very easy to implement functions returning dynamic data without having to worry much about memory management like freeing the result or having large enough buffers for the result. t_ prefix was chosen to describe functions allocating memory from data stack. "t" meaning temporary. Advantages over control stack and alloca(): - Functions can return a value allocated from data stack - We can portably specify how much data we want to allocate at runtime Advantages over malloc(): - FAST, most of the time allocating memory means only updating a couple of pointers and integers. Freeing the memory all at once also is a fast operation. - No need to free() each allocation resulting in prettier code - No memory leaks - No memory fragmentation Disadvantages: - Allocating memory inside loops can accidentally allocate a lot of memory if the loops are long and you forgot to place t_push() and t_pop() there. - t_malloc()ed data could be accidentally stored into permanent location and accessed after it's already been freed. const'ing the return values helps for most uses though (see the t_malloc() description). - Debugging invalid memory usage may be difficult using existing tools, although compiling with DEBUG enabled helps finding simple buffer overflows. */ #ifndef STATIC_CHECKER typedef unsigned int data_stack_frame_t; #else typedef struct data_stack_frame *data_stack_frame_t; #endif extern unsigned int data_stack_frame_id; /* All t_..() allocations between t_push*() and t_pop() are freed after t_pop() is called. Returns the current stack frame number, which can be used to detect missing t_pop() calls: x = t_push(marker); .. if (!t_pop(x)) abort(); In DEBUG mode, t_push_named() makes a temporary allocation for the name, but is safe to call in a loop as it performs the allocation within its own frame. However, you should always prefer to use T_BEGIN { ... } T_END below. */ data_stack_frame_t t_push(const char *marker) ATTR_HOT; data_stack_frame_t t_push_named(const char *format, ...) ATTR_HOT ATTR_FORMAT(1, 2); /* Returns TRUE on success, FALSE if t_pop() call was leaked. The caller should panic. */ bool t_pop(data_stack_frame_t *id) ATTR_HOT; /* Same as t_pop(), but move str out of the stack frame if it is inside. This can be used to easily move e.g. error strings outside stack frames. */ bool t_pop_pass_str(data_stack_frame_t *id, const char **str); /* Pop the last data stack frame. This shouldn't be called outside test code. */ void t_pop_last_unsafe(void); /* Usage: T_BEGIN { code } T_END */ #define T_STRING(x) #x #define T_XSTRING(x) T_STRING(x) /* expand and then stringify */ #define T_BEGIN \ STMT_START { \ data_stack_frame_t _data_stack_cur_id = t_push(__FILE__ ":" T_XSTRING(__LINE__)); #define T_END \ STMT_START { \ if (unlikely(!t_pop(&_data_stack_cur_id))) \ i_panic("Leaked t_pop() call"); \ } STMT_END; \ } STMT_END /* Usage: const char *error; T_BEGIN { ... if (ret < 0) error = t_strdup_printf("foo() failed: %m"); } T_END_PASS_STR_IF(ret < 0, &error); // error is still valid */ #define T_END_PASS_STR_IF(pass_condition, str) \ STMT_START { \ if (unlikely(!t_pop_pass_str(&_data_stack_cur_id, (pass_condition) ? (str) : NULL))) \ i_panic("Leaked t_pop() call"); \ } STMT_END; \ } STMT_END /* Usage: const char *result; T_BEGIN { ... result = t_strdup_printf(...); } T_END_PASS_STR(&result); // result is still valid */ #define T_END_PASS_STR(str) \ T_END_PASS_STR_IF(TRUE, str) /* WARNING: Be careful when using these functions, it's too easy to accidentally save the returned value somewhere permanently. You probably should never use these functions directly, rather create functions that return 'const xxx*' types and use t_malloc() internally in them. This is a lot safer, since usually compiler warns if you try to place them in xxx*. See strfuncs.c for examples. t_malloc() calls never fail. If there's not enough memory left, i_panic() will be called. */ void *t_malloc_no0(size_t size) ATTR_MALLOC ATTR_RETURNS_NONNULL; void *t_malloc0(size_t size) ATTR_MALLOC ATTR_RETURNS_NONNULL; /* Try growing allocated memory. Returns TRUE if successful. Works only for last allocated memory in current stack frame. */ bool t_try_realloc(void *mem, size_t size); /* Returns the number of bytes available in data stack without allocating more memory. */ size_t t_get_bytes_available(void) ATTR_PURE; #define t_new(type, count) \ ((type *) t_malloc0(MALLOC_MULTIPLY((unsigned int)sizeof(type), (count))) + \ COMPILE_ERROR_IF_TRUE(sizeof(type) > UINT_MAX)) /* Returns pointer to a temporary buffer you can use. The buffer will be invalid as soon as next t_malloc() is called! If you wish to grow the buffer, you must give the full wanted size in the size parameter. If return value doesn't point to the same value as last time, you need to memcpy() data from the old buffer to the new one (or do some other trickery). See t_buffer_reget(). */ void *t_buffer_get(size_t size) ATTR_RETURNS_NONNULL; /* Grow the buffer, memcpy()ing the memory to new location if needed. */ void *t_buffer_reget(void *buffer, size_t size) ATTR_RETURNS_NONNULL; /* Make the last t_buffer_get()ed buffer permanent. Note that size MUST be less or equal than the size you gave with last t_buffer_get() or the result will be undefined. */ void t_buffer_alloc(size_t size); /* Allocate the last t_buffer_get()ed data entirely. */ void t_buffer_alloc_last_full(void); /* Returns TRUE if ptr is allocated within the given data stack frame. Currently this assert-crashes if the data stack frame isn't the latest. */ bool data_stack_frame_contains(data_stack_frame_t *id, const void *ptr); /* Returns the number of bytes malloc()ated for data stack. */ size_t data_stack_get_alloc_size(void); /* Returns the number of bytes currently used in data stack. */ size_t data_stack_get_used_size(void); /* Free all the memory that is currently unused (i.e. reserved for growing data stack quickly). */ void data_stack_free_unused(void); void data_stack_init(void); void data_stack_deinit_event(void); void data_stack_deinit(void); #endif dovecot-2.3.21.1/src/lib/test-ostream-multiplex.c0000644000000000000000000002554114656633576016475 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "ioloop.h" #include "str.h" #include "istream.h" #include "ostream-private.h" #include "istream-multiplex.h" #include "ostream-multiplex.h" #include "ostream.h" #include #include "hex-binary.h" static void test_ostream_multiplex_simple(void) { test_begin("ostream multiplex (simple)"); const unsigned char expected[] = { '\x00','\x00','\x00','\x00','\x05','\x68','\x65', '\x6c','\x6c','\x6f','\x01','\x00','\x00','\x00', '\x05','\x77','\x6f','\x72','\x6c','\x64' }; buffer_t *result = t_str_new(64); struct ostream *os = test_ostream_create(result); struct ostream *os2 = o_stream_create_multiplex(os, SIZE_MAX); struct ostream *os3 = o_stream_multiplex_add_channel(os2, 1); test_assert(o_stream_send_str(os2, "hello") == 5); test_assert(o_stream_send_str(os3, "world") == 5); o_stream_unref(&os3); o_stream_unref(&os2); test_assert(o_stream_finish(os) == 1); o_stream_unref(&os); test_assert(sizeof(expected) == result->used); test_assert(memcmp(result->data, expected, I_MIN(sizeof(expected), result->used)) == 0); test_end(); } static unsigned int channel_counter[2] = {0, 0}; static struct ostream *chan0, *chan1; static const char *msgs[] = { "", "a", "bb", "ccc", "dddd", "eeeee", "ffffff" }; static void test_ostream_multiplex_stream_read(struct istream *is) { uint8_t cid; const unsigned char *data; size_t siz,dlen=0,pos=0; if (i_stream_read_more(is, &data, &siz)>0) { /* parse stream */ for(;pos 0) { if (dlen < N_ELEMENTS(msgs)) { test_assert_idx(memcmp(&data[pos], msgs[dlen], dlen)==0, channel_counter[data[0] % 2]); } channel_counter[data[0] % 2]++; pos += dlen; dlen = 0; } else if (dlen == 0) { cid = data[pos] % 2; test_assert_idx(data[pos] < 2, channel_counter[cid]); pos++; dlen = be32_to_cpu_unaligned(&data[pos]); pos += 4; test_assert(dlen > 0 && dlen < N_ELEMENTS(msgs)); } } i_stream_skip(is, siz); } if (channel_counter[0] > 100 && channel_counter[1] > 100) io_loop_stop(current_ioloop); } static void test_ostream_multiplex_stream_write(struct ostream *channel ATTR_UNUSED) { size_t rounds = 1 + i_rand_limit(10); for(size_t i = 0; i < rounds; i++) { if ((i_rand_limit(2)) != 0) { o_stream_cork(chan1); /* send one byte at a time */ for(const char *p = msgs[i_rand_limit(N_ELEMENTS(msgs))]; *p != '\0'; p++) { o_stream_nsend(chan1, p, 1); } o_stream_uncork(chan1); } else { o_stream_nsend_str(chan0, msgs[i_rand_limit(N_ELEMENTS(msgs))]); } } } static void test_ostream_multiplex_stream(void) { test_begin("ostream multiplex (stream)"); struct ioloop *ioloop = io_loop_create(); io_loop_set_current(ioloop); int fds[2]; test_assert(pipe(fds) == 0); fd_set_nonblock(fds[0], TRUE); fd_set_nonblock(fds[1], TRUE); struct ostream *os = o_stream_create_fd(fds[1], SIZE_MAX); struct istream *is = i_stream_create_fd(fds[0], SIZE_MAX); chan0 = o_stream_create_multiplex(os, SIZE_MAX); chan1 = o_stream_multiplex_add_channel(chan0, 1); struct io *io0 = io_add_istream(is, test_ostream_multiplex_stream_read, is); struct io *io1 = io_add(fds[1], IO_WRITE, test_ostream_multiplex_stream_write, os); io_loop_run(current_ioloop); io_remove(&io0); io_remove(&io1); test_assert(o_stream_finish(chan1) > 0); o_stream_unref(&chan1); test_assert(o_stream_finish(chan0) > 0); o_stream_unref(&chan0); i_stream_unref(&is); o_stream_unref(&os); io_loop_destroy(&ioloop); i_close_fd(&fds[0]); i_close_fd(&fds[1]); test_end(); } static void test_ostream_multiplex_cork(void) { test_begin("ostream multiplex (corking)"); buffer_t *output = t_buffer_create(128); struct ostream *os = test_ostream_create(output); struct ostream *chan0 = o_stream_create_multiplex(os, SIZE_MAX); const struct const_iovec iov[] = { { "hello", 5 }, { " ", 1 }, { "world", 5 }, { "!", 1 } }; /* send data in parts, expect to see single blob */ o_stream_cork(chan0); o_stream_nsendv(chan0, iov, N_ELEMENTS(iov)); o_stream_uncork(chan0); test_assert(o_stream_flush(os) == 1); /* check output */ test_assert(memcmp(output->data, "\0\0\0\0\f", 5) == 0); test_assert(strcmp(str_c(output)+5, "hello world!") == 0); test_assert(o_stream_finish(chan0) > 0); o_stream_unref(&chan0); o_stream_unref(&os); test_end(); } struct test_hang_context { struct istream *input1, *input2; size_t sent_bytes, sent2_bytes; size_t read_bytes, read2_bytes; }; static void test_hang_input(struct test_hang_context *ctx) { ssize_t ret, ret2; do { ret = i_stream_read(ctx->input1); if (ret > 0) { i_stream_skip(ctx->input1, ret); ctx->read_bytes += ret; } ret2 = i_stream_read(ctx->input2); if (ret2 > 0) { i_stream_skip(ctx->input2, ret2); ctx->read2_bytes += ret2; } } while (ret > 0 || ret2 > 0); test_assert(ret == 0 && ret2 == 0); if (ctx->read_bytes == ctx->sent_bytes && ctx->read2_bytes == ctx->sent2_bytes) io_loop_stop(current_ioloop); } static void test_ostream_multiplex_hang(void) { int fd[2]; test_begin("ostream multiplex hang"); if (pipe(fd) < 0) i_fatal("pipe() failed: %m"); fd_set_nonblock(fd[0], TRUE); fd_set_nonblock(fd[1], TRUE); struct ioloop *ioloop = io_loop_create(); struct ostream *file_output = o_stream_create_fd(fd[1], 1024); o_stream_set_no_error_handling(file_output, TRUE); struct ostream *channel = o_stream_create_multiplex(file_output, 4096); struct ostream *channel2 = o_stream_multiplex_add_channel(channel, 1); char buf[256]; /* send multiplex output until the buffer is full */ ssize_t ret, ret2; size_t sent_bytes = 0, sent2_bytes = 0; i_zero(&buf); o_stream_cork(channel); o_stream_cork(channel2); while ((ret = o_stream_send(channel, buf, sizeof(buf))) > 0) { sent_bytes += ret; ret2 = o_stream_send(channel2, buf, sizeof(buf)); if (ret2 <= 0) break; sent2_bytes += ret2; } test_assert(o_stream_finish(channel) == 0); test_assert(o_stream_finish(channel2) == 0); o_stream_uncork(channel); o_stream_uncork(channel2); /* We expect the first channel to have data buffered */ test_assert(o_stream_get_buffer_used_size(channel) >= o_stream_get_buffer_used_size(file_output)); test_assert(o_stream_get_buffer_used_size(channel) - o_stream_get_buffer_used_size(file_output) > 0); /* read everything that was already sent */ struct istream *file_input = i_stream_create_fd(fd[0], 1024); struct istream *input = i_stream_create_multiplex(file_input, 4096); struct istream *input2 = i_stream_multiplex_add_channel(input, 1); struct test_hang_context ctx = { .input1 = input, .input2 = input2, .sent_bytes = sent_bytes, .sent2_bytes = sent2_bytes, }; struct timeout *to = timeout_add(5000, io_loop_stop, current_ioloop); struct io *io = io_add_istream(file_input, test_hang_input, &ctx); io_loop_run(ioloop); io_remove(&io); timeout_remove(&to); /* everything that was sent should have been received now. ostream-multiplex's internal buffer is also supposed to have been sent. */ test_assert(input->v_offset == sent_bytes); test_assert(input2->v_offset == sent2_bytes); test_assert(o_stream_get_buffer_used_size(channel) == 0); test_assert(o_stream_get_buffer_used_size(channel2) == 0); i_stream_unref(&file_input); i_stream_unref(&input); i_stream_unref(&input2); o_stream_unref(&channel); o_stream_unref(&channel2); o_stream_unref(&file_output); io_loop_destroy(&ioloop); test_end(); } #define FLUSH_CALLBACK_TOTAL_BYTES 10240 struct test_flush_context { struct ostream *output1, *output2; struct istream *input1, *input2; }; static int flush_callback1(struct test_flush_context *ctx) { char buf[32]; i_assert(ctx->output1->offset <= FLUSH_CALLBACK_TOTAL_BYTES); size_t bytes_left = FLUSH_CALLBACK_TOTAL_BYTES - ctx->output1->offset; memset(buf, '1', sizeof(buf)); if (o_stream_send(ctx->output1, buf, I_MIN(sizeof(buf), bytes_left)) < 0) return -1; return ctx->output1->offset < FLUSH_CALLBACK_TOTAL_BYTES ? 0 : 1; } static int flush_callback2(struct test_flush_context *ctx) { char buf[64]; i_assert(ctx->output2->offset <= FLUSH_CALLBACK_TOTAL_BYTES); size_t bytes_left = FLUSH_CALLBACK_TOTAL_BYTES - ctx->output2->offset; memset(buf, '2', sizeof(buf)); if (o_stream_send(ctx->output2, buf, I_MIN(sizeof(buf), bytes_left)) < 0) return -1; return ctx->output2->offset < FLUSH_CALLBACK_TOTAL_BYTES ? 0 : 1; } static void test_flush_input(struct test_flush_context *ctx) { ssize_t ret, ret2; do { ret = i_stream_read(ctx->input1); if (ret > 0) i_stream_skip(ctx->input1, ret); ret2 = i_stream_read(ctx->input2); if (ret2 > 0) i_stream_skip(ctx->input2, ret2); } while (ret > 0 || ret2 > 0); test_assert(ret == 0 && ret2 == 0); if (ctx->input1->v_offset == FLUSH_CALLBACK_TOTAL_BYTES && ctx->input2->v_offset == FLUSH_CALLBACK_TOTAL_BYTES) io_loop_stop(current_ioloop); } static void test_ostream_multiplex_flush_callback(void) { int fd[2]; test_begin("ostream multiplex flush callback"); if (pipe(fd) < 0) i_fatal("pipe() failed: %m"); fd_set_nonblock(fd[0], TRUE); fd_set_nonblock(fd[1], TRUE); struct ioloop *ioloop = io_loop_create(); struct ostream *file_output = o_stream_create_fd(fd[1], 1024); o_stream_set_no_error_handling(file_output, TRUE); struct ostream *channel = o_stream_create_multiplex(file_output, 4096); struct ostream *channel2 = o_stream_multiplex_add_channel(channel, 1); struct istream *file_input = i_stream_create_fd(fd[0], 1024); struct istream *input = i_stream_create_multiplex(file_input, 4096); struct istream *input2 = i_stream_multiplex_add_channel(input, 1); struct test_flush_context ctx = { .output1 = channel, .output2 = channel2, .input1 = input, .input2 = input2, }; o_stream_set_flush_callback(channel, flush_callback1, &ctx); o_stream_set_flush_callback(channel2, flush_callback2, &ctx); o_stream_set_flush_pending(channel, TRUE); o_stream_set_flush_pending(channel2, TRUE); struct timeout *to = timeout_add(5000, io_loop_stop, current_ioloop); struct io *io = io_add_istream(file_input, test_flush_input, &ctx); io_loop_run(ioloop); io_remove(&io); timeout_remove(&to); test_assert(channel->offset == FLUSH_CALLBACK_TOTAL_BYTES); test_assert(channel2->offset == FLUSH_CALLBACK_TOTAL_BYTES); test_assert(input->v_offset == FLUSH_CALLBACK_TOTAL_BYTES); test_assert(input2->v_offset == FLUSH_CALLBACK_TOTAL_BYTES); test_assert(o_stream_finish(channel) == 1); test_assert(o_stream_finish(channel2) == 1); i_stream_unref(&file_input); i_stream_unref(&input); i_stream_unref(&input2); o_stream_unref(&channel); o_stream_unref(&channel2); o_stream_unref(&file_output); io_loop_destroy(&ioloop); test_end(); } void test_ostream_multiplex(void) { test_ostream_multiplex_simple(); test_ostream_multiplex_stream(); test_ostream_multiplex_cork(); test_ostream_multiplex_hang(); test_ostream_multiplex_flush_callback(); } dovecot-2.3.21.1/src/lib/connection.c0000644000000000000000000006320514656633576014163 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "ioloop.h" #include "istream.h" #include "istream-unix.h" #include "ostream.h" #include "ostream-unix.h" #include "iostream.h" #include "net.h" #include "strescape.h" #include "llist.h" #include "time-util.h" #include "connection.h" #include #include static void connection_handshake_ready(struct connection *conn) { conn->handshake_received = TRUE; if (conn->v.handshake_ready != NULL) conn->v.handshake_ready(conn); } static void connection_closed(struct connection *conn, enum connection_disconnect_reason reason) { conn->disconnect_reason = reason; conn->v.destroy(conn); } static void connection_idle_timeout(struct connection *conn) { connection_closed(conn, CONNECTION_DISCONNECT_IDLE_TIMEOUT); } static void connection_connect_timeout(struct connection *conn) { connection_closed(conn, CONNECTION_DISCONNECT_CONNECT_TIMEOUT); } void connection_input_default(struct connection *conn) { const char *line; struct istream *input; struct ostream *output; int ret = 0; if (!conn->handshake_received && conn->v.handshake != NULL) { if ((ret = conn->v.handshake(conn)) < 0) { connection_closed( conn, CONNECTION_DISCONNECT_HANDSHAKE_FAILED); return; } else if (ret == 0) { return; } else { connection_handshake_ready(conn); } } switch (connection_input_read(conn)) { case -1: return; case 0: /* allow calling this function for buffered input */ case 1: break; default: i_unreached(); } input = conn->input; output = conn->output; i_stream_ref(input); if (output != NULL) { o_stream_ref(output); o_stream_cork(output); } while (!input->closed && (line = i_stream_next_line(input)) != NULL) { T_BEGIN { if (!conn->handshake_received && conn->v.handshake_line != NULL) { ret = conn->v.handshake_line(conn, line); if (ret > 0) connection_handshake_ready(conn); else if (ret == 0) /* continue reading */ ret = 1; else conn->disconnect_reason = CONNECTION_DISCONNECT_HANDSHAKE_FAILED; } else { ret = conn->v.input_line(conn, line); } } T_END; if (ret <= 0) break; } if (output != NULL) { o_stream_uncork(output); o_stream_unref(&output); } if (ret < 0 && !input->closed) { enum connection_disconnect_reason reason = conn->disconnect_reason; if (reason == CONNECTION_DISCONNECT_NOT) reason = CONNECTION_DISCONNECT_DEINIT; connection_closed(conn, reason); } i_stream_unref(&input); } int connection_verify_version(struct connection *conn, const char *service_name, unsigned int major_version, unsigned int minor_version) { i_assert(!conn->version_received); if (strcmp(service_name, conn->list->set.service_name_in) != 0) { e_error(conn->event, "Connected to wrong socket type. " "We want '%s', but received '%s'", conn->list->set.service_name_in, service_name); return -1; } if (major_version != conn->list->set.major_version) { e_error(conn->event, "Socket supports major version %u, " "but we support only %u (mixed old and new binaries?)", major_version, conn->list->set.major_version); return -1; } conn->minor_version = minor_version; conn->version_received = TRUE; return 0; } int connection_handshake_args_default(struct connection *conn, const char *const *args) { unsigned int major_version, minor_version; if (conn->version_received) return 1; /* VERSION service_name major version minor version */ if (str_array_length(args) != 4 || strcmp(args[0], "VERSION") != 0 || str_to_uint(args[2], &major_version) < 0 || str_to_uint(args[3], &minor_version) < 0) { e_error(conn->event, "didn't reply with a valid VERSION line: %s", t_strarray_join(args, "\t")); return -1; } if (connection_verify_version(conn, args[1], major_version, minor_version) < 0) return -1; return 1; } int connection_input_line_default(struct connection *conn, const char *line) { const char *const *args; args = t_strsplit_tabescaped(line); if (args[0] == NULL && !conn->list->set.allow_empty_args_input) { e_error(conn->event, "Unexpectedly received empty line"); return -1; } if (!conn->handshake_received && (conn->v.handshake_args != connection_handshake_args_default || conn->list->set.major_version != 0)) { int ret; if ((ret = conn->v.handshake_args(conn, args)) == 0) ret = 1; /* continue reading */ else if (ret > 0) connection_handshake_ready(conn); else { conn->disconnect_reason = CONNECTION_DISCONNECT_HANDSHAKE_FAILED; } return ret; } else if (!conn->handshake_received) { /* we don't do handshakes */ connection_handshake_ready(conn); } /* version must be handled though, by something */ i_assert(conn->version_received); return conn->v.input_args(conn, args); } void connection_input_halt(struct connection *conn) { io_remove(&conn->io); timeout_remove(&conn->to); } static void connection_input_resume_full(struct connection *conn, bool set_io_pending) { i_assert(!conn->disconnected); if (conn->io != NULL) { /* do nothing */ } else if (conn->input != NULL) { conn->io = io_add_istream_to(conn->ioloop, conn->input, *conn->v.input, conn); if (set_io_pending) io_set_pending(conn->io); } else if (conn->fd_in != -1) { conn->io = io_add_to(conn->ioloop, conn->fd_in, IO_READ, *conn->v.input, conn); if (set_io_pending) io_set_pending(conn->io); } if (conn->input_idle_timeout_secs != 0 && conn->to == NULL) { conn->to = timeout_add_to(conn->ioloop, conn->input_idle_timeout_secs*1000, *conn->v.idle_timeout, conn); } } void connection_input_resume(struct connection *conn) { connection_input_resume_full(conn, TRUE); } static void connection_update_property_label(struct connection *conn) { const char *label; if (conn->remote_ip.family == 0) { if (conn->remote_uid == (uid_t)-1) label = NULL; else if (conn->remote_pid != (pid_t)-1) { label = t_strdup_printf("pid=%ld,uid=%ld", (long)conn->remote_pid, (long)conn->remote_uid); } else { label = t_strdup_printf("uid=%ld", (long)conn->remote_uid); } } else if (conn->remote_ip.family == AF_INET6) { label = t_strdup_printf("[%s]:%u", net_ip2addr(&conn->remote_ip), conn->remote_port); } else { label = t_strdup_printf("%s:%u", net_ip2addr(&conn->remote_ip), conn->remote_port); } i_assert(label != NULL || conn->property_label == NULL); if (conn->property_label != NULL && strcmp(conn->property_label, label) != 0) { e_debug(conn->event, "Updated peer address to %s", label); } i_free(conn->property_label); conn->property_label = i_strdup(label); } static void connection_update_label(struct connection *conn) { bool unix_socket = conn->unix_socket || (conn->remote_ip.family == 0 && conn->remote_uid != (uid_t)-1); string_t *label; label = t_str_new(64); if (conn->base_name != NULL) str_append(label, conn->base_name); if (conn->property_label != NULL) { if (str_len(label) == 0) str_append(label, conn->property_label); else { str_append(label, " ("); str_append(label, conn->property_label); str_append(label, ")"); } } if (str_len(label) == 0) { if (conn->fd_in >= 0 && (conn->fd_in == conn->fd_out || conn->fd_out < 0)) str_printfa(label, "fd=%d", conn->fd_in); else if (conn->fd_in < 0 && conn->fd_out >= 0) str_printfa(label, "fd=%d", conn->fd_out); else if (conn->fd_in >= 0 && conn->fd_out >= 0) { str_printfa(label, "fd_in=%d,fd_out=%d", conn->fd_in, conn->fd_out); } } if (unix_socket && str_len(label) > 0) str_insert(label, 0, "unix:"); if (conn->list->set.log_connection_id) { if (str_len(label) > 0) str_append_c(label, ' '); str_printfa(label, "[%u]", conn->id); } i_free(conn->label); conn->label = i_strdup(str_c(label)); } static const char * connection_create_stream_name(struct connection *conn, int fd) { string_t *name; name = t_str_new(64); str_append(name, "(conn"); if (conn->unix_socket || (conn->remote_ip.family == 0 && conn->remote_uid != (uid_t)-1)) str_append(name, ":unix"); if (conn->base_name != NULL) { str_append_c(name, ':'); str_append(name, conn->base_name); } else if (conn->property_label != NULL) { str_append_c(name, ':'); str_append(name, conn->property_label); } else if (fd >= 0) { str_printfa(name, ":fd=%d", fd); } if (conn->list->set.log_connection_id) { if (str_len(name) == 5) str_append_c(name, ':'); else str_append_c(name, ','); str_printfa(name, "id=%u", conn->id); } str_append_c(name, ')'); return str_c(name); } static void connection_update_stream_names(struct connection *conn) { if (conn->input != NULL) { i_stream_set_name( conn->input, connection_create_stream_name(conn, conn->fd_in)); } if (conn->output != NULL) { o_stream_set_name( conn->output, connection_create_stream_name(conn, conn->fd_out)); } } void connection_update_event(struct connection *conn) { string_t *prefix; prefix = t_str_new(64); str_append(prefix, "conn"); if (strlen(conn->label) > 0) { str_append_c(prefix, ' '); str_append(prefix, conn->label); } str_append(prefix, ": "); event_set_append_log_prefix(conn->event, str_c(prefix)); if (conn->local_ip.family > 0) { event_add_str(conn->event, conn->list->set.client ? "source_ip" : "local_ip", net_ip2addr(&conn->local_ip)); } if (conn->remote_ip.family > 0) { event_add_str(conn->event, conn->list->set.client ? "dest_ip" : "remote_ip", net_ip2addr(&conn->remote_ip)); } if (conn->remote_port > 0) { event_add_int(conn->event, conn->list->set.client ? "dest_port" : "remote_port", conn->remote_port); } if (conn->remote_pid != (pid_t)-1) event_add_int(conn->event, "remote_pid", conn->remote_pid); if (conn->remote_uid != (uid_t)-1) event_add_int(conn->event, "remote_uid", conn->remote_uid); } void connection_update_properties(struct connection *conn) { int fd = (conn->fd_in < 0 ? conn->fd_out : conn->fd_in); struct net_unix_cred cred; if (conn->remote_ip.family != 0) { /* remote IP was already set */ } else if (conn->unix_peer_checked) { /* already checked */ } else if (fd < 0) { /* not connected yet - wait */ } else { if (net_getpeername(fd, &conn->remote_ip, &conn->remote_port) == 0) { /* either TCP or UNIX socket connection */ errno = 0; } if (conn->remote_ip.family != 0) { /* TCP connection */ i_assert(conn->remote_port != 0); } else if (errno == ENOTSOCK) { /* getpeername() already found out this can't be a UNIX socket connection */ } else if (net_getunixcred(fd, &cred) == 0) { conn->remote_pid = cred.pid; conn->remote_uid = cred.uid; } conn->unix_peer_checked = TRUE; } connection_update_property_label(conn); connection_update_label(conn); connection_update_stream_names(conn); connection_update_event(conn); conn->name = (conn->base_name != NULL ? conn->base_name : conn->property_label); } static void connection_init_streams(struct connection *conn) { const struct connection_settings *set = &conn->list->set; i_assert(conn->io == NULL); i_assert(conn->input == NULL); i_assert(conn->output == NULL); i_assert(conn->to == NULL); conn->handshake_received = FALSE; conn->version_received = set->major_version == 0; if (set->input_max_size != 0) { if (conn->unix_socket) conn->input = i_stream_create_unix(conn->fd_in, set->input_max_size); else conn->input = i_stream_create_fd(conn->fd_in, set->input_max_size); i_stream_switch_ioloop_to(conn->input, conn->ioloop); } if (set->output_max_size != 0) { if (conn->unix_socket) conn->output = o_stream_create_unix(conn->fd_out, set->output_max_size); else conn->output = o_stream_create_fd(conn->fd_out, set->output_max_size); o_stream_set_no_error_handling(conn->output, TRUE); o_stream_set_finish_via_child(conn->output, FALSE); o_stream_switch_ioloop_to(conn->output, conn->ioloop); } connection_update_stream_names(conn); conn->disconnected = FALSE; i_assert(conn->to == NULL); connection_input_resume_full(conn, FALSE); i_assert(conn->to != NULL || conn->input_idle_timeout_secs == 0); if (set->major_version != 0 && !set->dont_send_version) { e_debug(conn->event, "Sending version handshake"); o_stream_nsend_str(conn->output, t_strdup_printf( "VERSION\t%s\t%u\t%u\n", set->service_name_out, set->major_version, set->minor_version)); } } void connection_streams_changed(struct connection *conn) { const struct connection_settings *set = &conn->list->set; if (set->input_max_size != 0 && conn->io != NULL) { connection_input_halt(conn); connection_input_resume(conn); } } static void connection_client_connected(struct connection *conn, bool success) { i_assert(conn->list->set.client); connection_update_properties(conn); conn->connect_finished = ioloop_timeval; struct event_passthrough *e = event_create_passthrough(conn->event)-> set_name("server_connection_connected"); if (success) { e_debug(e->event(), "Client connected (fd=%d)", conn->fd_in); } else { e_debug(e->event(), "Client connection failed (fd=%d)", conn->fd_in); } if (success) connection_init_streams(conn); if (conn->v.client_connected != NULL) conn->v.client_connected(conn, success); if (!success) { connection_closed(conn, CONNECTION_DISCONNECT_CONN_CLOSED); } } static void connection_init_full(struct connection_list *list, struct connection *conn, const char *name, int fd_in, int fd_out) { if (conn->id == 0) { if (list->id_counter == 0) list->id_counter++; conn->id = list->id_counter++; } conn->ioloop = current_ioloop; conn->fd_in = fd_in; conn->fd_out = fd_out; conn->disconnected = TRUE; conn->remote_uid = (uid_t)-1; conn->remote_pid = (pid_t)-1; i_free(conn->base_name); conn->base_name = i_strdup(name); if (list->set.input_idle_timeout_secs != 0 && conn->input_idle_timeout_secs == 0) { conn->input_idle_timeout_secs = list->set.input_idle_timeout_secs; } if (conn->event == NULL) conn->event = event_create(conn->event_parent); if (list->set.debug) event_set_forced_debug(conn->event, TRUE); if (conn->list != NULL) { i_assert(conn->list == list); } else { conn->list = list; DLLIST_PREPEND(&list->connections, conn); list->connections_count++; } connection_update_properties(conn); connection_set_default_handlers(conn); } void connection_init(struct connection_list *list, struct connection *conn, const char *name) { connection_init_full(list, conn, name, -1, -1); } void connection_init_server(struct connection_list *list, struct connection *conn, const char *name, int fd_in, int fd_out) { i_assert(!list->set.client); connection_init_full(list, conn, name, fd_in, fd_out); struct event_passthrough *e = event_create_passthrough(conn->event)-> set_name("client_connection_connected"); /* fd_out differs from fd_in only for stdin/stdout. Keep the logging output nice and clean by logging only the fd_in. If it's 0, it'll also be obvious that fd_out=1. */ e_debug(e->event(), "Server accepted connection (fd=%d)", fd_in); connection_init_streams(conn); if (conn->v.init != NULL) conn->v.init(conn); } void connection_init_server_ip(struct connection_list *list, struct connection *conn, const char *name, int fd_in, int fd_out, const struct ip_addr *remote_ip, in_port_t remote_port) { if (remote_ip != NULL && remote_ip->family != 0) conn->remote_ip = *remote_ip; if (remote_port != 0) conn->remote_port = remote_port; connection_init_server(list, conn, name, fd_in, fd_out); } void connection_init_client_fd(struct connection_list *list, struct connection *conn, const char *name, int fd_in, int fd_out) { i_assert(list->set.client); connection_init_full(list, conn, name, fd_in, fd_out); struct event_passthrough *e = event_create_passthrough(conn->event)-> set_name("server_connection_connected"); /* fd_out differs from fd_in only for stdin/stdout. Keep the logging output nice and clean by logging only the fd_in. If it's 0, it'll also be obvious that fd_out=1. */ e_debug(e->event(), "Client connected (fd=%d)", fd_in); if (conn->v.init != NULL) conn->v.init(conn); connection_client_connected(conn, TRUE); } void connection_init_client_ip_from(struct connection_list *list, struct connection *conn, const char *hostname, const struct ip_addr *ip, in_port_t port, const struct ip_addr *my_ip) { const char *name = NULL; if (hostname != NULL) name = t_strdup_printf("%s:%u", hostname, port); i_assert(list->set.client); conn->remote_ip = *ip; conn->remote_port = port; if (my_ip != NULL) conn->local_ip = *my_ip; else i_zero(&conn->local_ip); connection_init(list, conn, name); if (hostname != NULL) event_add_str(conn->event, "dest_host", hostname); connection_update_event(conn); if (conn->v.init != NULL) conn->v.init(conn); } void connection_init_client_ip(struct connection_list *list, struct connection *conn, const char *hostname, const struct ip_addr *ip, in_port_t port) { connection_init_client_ip_from(list, conn, hostname, ip, port, NULL); } void connection_init_client_unix(struct connection_list *list, struct connection *conn, const char *path) { i_assert(list->set.client); conn->unix_socket = TRUE; connection_init(list, conn, path); event_add_str(conn->event, "socket_path", path); if (conn->v.init != NULL) conn->v.init(conn); } void connection_init_from_streams(struct connection_list *list, struct connection *conn, const char *name, struct istream *input, struct ostream *output) { connection_init_full(list, conn, name, i_stream_get_fd(input), o_stream_get_fd(output)); i_assert(conn->fd_in >= 0); i_assert(conn->fd_out >= 0); i_assert(conn->io == NULL); i_assert(conn->input == NULL); i_assert(conn->output == NULL); i_assert(conn->to == NULL); conn->input = input; i_stream_ref(conn->input); conn->output = output; o_stream_ref(conn->output); o_stream_set_no_error_handling(conn->output, TRUE); connection_update_stream_names(conn); conn->disconnected = FALSE; connection_input_resume_full(conn, FALSE); if (conn->v.client_connected != NULL) conn->v.client_connected(conn, TRUE); } static void connection_socket_connected(struct connection *conn) { io_remove(&conn->io); timeout_remove(&conn->to); errno = net_geterror(conn->fd_in); connection_client_connected(conn, errno == 0); } int connection_client_connect(struct connection *conn) { const struct connection_settings *set = &conn->list->set; int fd; i_assert(conn->list->set.client); i_assert(conn->fd_in == -1); e_debug(conn->event, "Connecting"); if (conn->remote_port != 0) { fd = net_connect_ip(&conn->remote_ip, conn->remote_port, (conn->local_ip.family != 0 ? &conn->local_ip : NULL)); } else if (conn->list->set.unix_client_connect_msecs == 0) { fd = net_connect_unix(conn->base_name); } else { fd = net_connect_unix_with_retries( conn->base_name, conn->list->set.unix_client_connect_msecs); } if (fd == -1) return -1; conn->fd_in = conn->fd_out = fd; conn->connect_started = ioloop_timeval; conn->disconnected = FALSE; if (conn->remote_port != 0 || conn->list->set.delayed_unix_client_connected_callback) { connection_update_properties(conn); conn->io = io_add_to(conn->ioloop, conn->fd_out, IO_WRITE, connection_socket_connected, conn); e_debug(conn->event, "Waiting for connect (fd=%d) to finish for max %u msecs", fd, set->client_connect_timeout_msecs); if (set->client_connect_timeout_msecs != 0) { conn->to = timeout_add_to(conn->ioloop, set->client_connect_timeout_msecs, *conn->v.connect_timeout, conn); } } else { connection_client_connected(conn, TRUE); } return 0; } static void connection_update_counters(struct connection *conn) { if (conn->input != NULL) event_add_int(conn->event, "bytes_in", conn->input->v_offset); if (conn->output != NULL) event_add_int(conn->event, "bytes_out", conn->output->offset); } void connection_disconnect(struct connection *conn) { if (conn->disconnected) return; connection_update_counters(conn); /* client connects to a Server, and Server gets connection from Client */ const char *ename = conn->list->set.client ? "server_connection_disconnected" : "client_connection_disconnected"; struct event_passthrough *e = event_create_passthrough(conn->event)-> set_name(ename)-> add_str("reason", connection_disconnect_reason(conn)); e_debug(e->event(), "Disconnected: %s (fd=%d)", connection_disconnect_reason(conn), conn->fd_in); conn->last_input = 0; i_zero(&conn->last_input_tv); timeout_remove(&conn->to); io_remove(&conn->io); i_stream_close(conn->input); i_stream_destroy(&conn->input); o_stream_close(conn->output); o_stream_destroy(&conn->output); fd_close_maybe_stdio(&conn->fd_in, &conn->fd_out); conn->disconnected = TRUE; } void connection_deinit(struct connection *conn) { i_assert(conn->list->connections_count > 0); conn->list->connections_count--; DLLIST_REMOVE(&conn->list->connections, conn); connection_disconnect(conn); i_free(conn->base_name); i_free(conn->label); i_free(conn->property_label); event_unref(&conn->event); conn->list = NULL; } int connection_input_read(struct connection *conn) { conn->last_input = ioloop_time; conn->last_input_tv = ioloop_timeval; if (conn->to != NULL) timeout_reset(conn->to); switch (i_stream_read(conn->input)) { case -2: /* buffer full */ switch (conn->list->set.input_full_behavior) { case CONNECTION_BEHAVIOR_DESTROY: connection_closed(conn, CONNECTION_DISCONNECT_BUFFER_FULL); return -1; case CONNECTION_BEHAVIOR_ALLOW: return -2; } i_unreached(); case -1: /* disconnected */ connection_closed(conn, CONNECTION_DISCONNECT_CONN_CLOSED); return -1; case 0: /* nothing new read */ return 0; default: /* something was read */ return 1; } } const char *connection_disconnect_reason(struct connection *conn) { switch (conn->disconnect_reason) { case CONNECTION_DISCONNECT_DEINIT: return "Deinitializing"; case CONNECTION_DISCONNECT_CONNECT_TIMEOUT: { unsigned int msecs = conn->list->set.client_connect_timeout_msecs; return t_strdup_printf("connect() timed out in %u.%03u secs", msecs/1000, msecs%1000); } case CONNECTION_DISCONNECT_IDLE_TIMEOUT: return "Idle timeout"; case CONNECTION_DISCONNECT_CONN_CLOSED: if (conn->input == NULL) return t_strdup_printf("connect() failed: %m"); /* fall through */ case CONNECTION_DISCONNECT_NOT: case CONNECTION_DISCONNECT_BUFFER_FULL: return io_stream_get_disconnect_reason(conn->input, conn->output); case CONNECTION_DISCONNECT_HANDSHAKE_FAILED: return "Handshake failed"; } i_unreached(); } const char *connection_input_timeout_reason(struct connection *conn) { if (conn->last_input_tv.tv_sec != 0) { int diff = timeval_diff_msecs(&ioloop_timeval, &conn->last_input_tv); return t_strdup_printf("No input for %u.%03u secs", diff/1000, diff%1000); } else if (conn->connect_finished.tv_sec != 0) { int diff = timeval_diff_msecs(&ioloop_timeval, &conn->connect_finished); return t_strdup_printf( "No input since connected %u.%03u secs ago", diff/1000, diff%1000); } else { int diff = timeval_diff_msecs(&ioloop_timeval, &conn->connect_started); return t_strdup_printf("connect() timed out after %u.%03u secs", diff/1000, diff%1000); } } void connection_set_handlers(struct connection *conn, const struct connection_vfuncs *vfuncs) { connection_input_halt(conn); i_assert(vfuncs->destroy != NULL); conn->v = *vfuncs; if (conn->v.input == NULL) conn->v.input = connection_input_default; if (conn->v.input_line == NULL) conn->v.input_line = connection_input_line_default; if (conn->v.handshake_args == NULL) conn->v.handshake_args = connection_handshake_args_default; if (conn->v.idle_timeout == NULL) conn->v.idle_timeout = connection_idle_timeout; if (conn->v.connect_timeout == NULL) conn->v.connect_timeout = connection_connect_timeout; if (!conn->disconnected) connection_input_resume_full(conn, FALSE); } void connection_set_default_handlers(struct connection *conn) { connection_set_handlers(conn, &conn->list->v); } void connection_switch_ioloop_to(struct connection *conn, struct ioloop *ioloop) { conn->ioloop = ioloop; if (conn->io != NULL) conn->io = io_loop_move_io_to(ioloop, &conn->io); if (conn->to != NULL) conn->to = io_loop_move_timeout_to(ioloop, &conn->to); if (conn->input != NULL) i_stream_switch_ioloop_to(conn->input, ioloop); if (conn->output != NULL) o_stream_switch_ioloop_to(conn->output, ioloop); } void connection_switch_ioloop(struct connection *conn) { connection_switch_ioloop_to(conn, current_ioloop); } struct connection_list * connection_list_init(const struct connection_settings *set, const struct connection_vfuncs *vfuncs) { struct connection_list *list; i_assert(vfuncs->input != NULL || set->input_full_behavior != CONNECTION_BEHAVIOR_ALLOW); i_assert(set->major_version == 0 || (set->service_name_in != NULL && set->service_name_out != NULL && set->output_max_size != 0)); list = i_new(struct connection_list, 1); list->set = *set; list->v = *vfuncs; return list; } void connection_list_deinit(struct connection_list **_list) { struct connection_list *list = *_list; struct connection *conn; *_list = NULL; while (list->connections != NULL) { conn = list->connections; connection_closed(conn, CONNECTION_DISCONNECT_DEINIT); i_assert(conn != list->connections); } i_free(list); } dovecot-2.3.21.1/src/lib/utc-mktime.h0000644000000000000000000000052114656633576014100 00000000000000#ifndef UTC_MKTIME_H #define UTC_MKTIME_H #include /* Like mktime(), but assume that tm is in UTC. Unlike mktime(), values in tm fields must be in valid range. Leap second is accepted any time though since utc_mktime is often used before applying the time zone offset. */ time_t utc_mktime(const struct tm *tm); #endif dovecot-2.3.21.1/src/lib/test-istream-jsonstr.c0000644000000000000000000001006014656633576016134 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str.h" #include "istream-private.h" #include "istream-jsonstr.h" static const struct { const char *input; const char *output; int stream_errno; } tests[] = { { "foo\\\\\\\"\\b\\f\\n\\r\\t\\u0001\\uffff\"", "foo\\\"\b\f\n\r\t\001\xEF\xBF\xBF", 0 }, { "\\ud801\\udc37\"", "\xf0\x90\x90\xb7", 0 }, /* valid codepoint */ { "\"", "", 0 }, { "foo\\?\"", "foo", EINVAL }, { "foo\\?\"", "foo", EINVAL }, { "", "", EPIPE }, { "\\\"", "\"", EPIPE }, { "foo", "foo", EPIPE }, { "\\ud801", "", EPIPE }, /* high surrogate alone */ { "\\udced\\udc37\"", "", EINVAL }, /* low surrogate before high */ { "\\ud8011\\udc37\"", "", EINVAL }, /* has extra 1 in middle */ { "hello \\udc37\"", "hello ", EINVAL }, /* low surrogate before high with valid prefix*/ { "hello \\ud801", "hello ", EPIPE }, /* high surrogate alone with valid prefix */ { "\\uabcg", "", EINVAL }, /* invalid hex value */ }; static void run_test_buffer(const char *json_input, const char *output, int stream_errno, unsigned int skip_count) { size_t json_input_len = strlen(json_input); struct istream *input_data, *input; const unsigned char *data; size_t i, size; ssize_t ret = 0; input_data = test_istream_create_data(json_input, json_input_len); test_istream_set_allow_eof(input_data, FALSE); input = i_stream_create_jsonstr(input_data); for (i = 1; i < json_input_len;) { test_istream_set_size(input_data, i); while ((ret = i_stream_read(input)) > 0) ; if (ret == -1 && stream_errno != 0) break; test_assert_idx(ret == 0, i); if (i + skip_count < json_input_len) i += skip_count; else i++; } test_istream_set_allow_eof(input_data, TRUE); test_istream_set_size(input_data, json_input_len); ret = i_stream_read(input); while (ret > 0 && stream_errno != 0) ret = i_stream_read(input); test_assert(ret == -1); test_assert(input->stream_errno == stream_errno); if (stream_errno == 0) { data = i_stream_get_data(input, &size); test_assert(size == strlen(output)); if (size > 0) test_assert(memcmp(data, output, size) == 0); } i_stream_unref(&input); i_stream_unref(&input_data); } static void run_test(const char *json_input, const char *output, int stream_errno) { for (unsigned int i = 1; i <= 5; i++) run_test_buffer(json_input, output, stream_errno, i); } static void test_istream_jsonstr_autoretry(void) { const char *json_input = "\\u0001\""; const size_t json_input_len = strlen(json_input); struct istream *input_data, *input; test_begin("istream-jsonstr autoretry"); input_data = test_istream_create_data(json_input, json_input_len); input = i_stream_create_jsonstr(input_data); test_istream_set_size(input_data, 2); test_assert(i_stream_read(input_data) == 2); test_istream_set_size(input_data, json_input_len); test_assert(i_stream_read(input) == 1); test_assert(i_stream_read(input) == -1); i_stream_unref(&input); i_stream_unref(&input_data); test_end(); } static void test_istream_jsonstr_partial(void) { size_t len = 0; const char *json_input = "hello\\u0060x\""; const char *output = "hello`x"; const size_t json_input_len = strlen(json_input); struct istream *input_data, *input; test_begin("istream-jsonstr partial"); input_data = test_istream_create_data(json_input, json_input_len); input = i_stream_create_jsonstr(input_data); test_istream_set_size(input_data, 9); test_assert(i_stream_read(input) == 5); test_istream_set_size(input_data, json_input_len); test_assert(i_stream_read(input) == 2); test_assert(i_stream_read(input) == -1); test_assert(memcmp(i_stream_get_data(input, &len), output, I_MIN(len, strlen(output))) == 0 && len == strlen(output)); i_stream_unref(&input); i_stream_unref(&input_data); test_end(); } void test_istream_jsonstr(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(tests); i++) { test_begin(t_strdup_printf("istream-jsonstr %u", i+1)); run_test(tests[i].input, tests[i].output, tests[i].stream_errno); test_end(); } test_istream_jsonstr_autoretry(); test_istream_jsonstr_partial(); } dovecot-2.3.21.1/src/lib/process-title.h0000644000000000000000000000127514656633576014625 00000000000000#ifndef PROCESS_TITLE_H #define PROCESS_TITLE_H /* Initialize title changing. */ void process_title_init(int argc, char **argv[]); /* Change the process title if possible. */ void process_title_set(const char *title); /* Return the previously set process title. NULL means that it's either not set, or the title was explicitly set to NULL previously. */ const char *process_title_get(void); /* Return the number of times process_title_set() has been called. */ unsigned int process_title_get_counter(void); /* Free all memory used by process title hacks. This should be the last function called by the process, since it frees argv and environment. */ void process_title_deinit(void); #endif dovecot-2.3.21.1/src/lib/test-istream-try.c0000644000000000000000000001513214656633576015255 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "istream-private.h" #include "istream-base64.h" #include "istream-try.h" #define MIN_FULL_SIZE 1 static void test_istream_try_normal(void) { bool finished = FALSE; test_begin("istream try"); for (unsigned int test = 0; test <= 10; test++) { struct istream *test_inputs[3], *try_input; test_inputs[0] = test_istream_create("1"); test_inputs[1] = test_istream_create("2"); test_inputs[2] = NULL; test_istream_set_size(test_inputs[0], 0); test_istream_set_size(test_inputs[1], 0); try_input = istream_try_create(test_inputs, MIN_FULL_SIZE); /* nonblocking read */ test_assert_idx(i_stream_read(try_input) == 0, test); switch (test) { case 0: /* stream 0 is available */ test_istream_set_size(test_inputs[0], 1); test_assert_idx(i_stream_read(try_input) == 1, test); test_assert_idx(i_stream_get_data_size(test_inputs[0]) == 1, test); test_assert_idx(i_stream_get_data_size(test_inputs[1]) == 0, test); break; case 1: /* stream 1 is available, but not used before 0 */ test_istream_set_size(test_inputs[1], 1); test_assert_idx(i_stream_read(try_input) == 0, test); test_assert_idx(i_stream_get_data_size(test_inputs[0]) == 0, test); test_assert_idx(i_stream_get_data_size(test_inputs[1]) == 0, test); /* continue failing stream 0 -> 1 is available */ test_inputs[0]->stream_errno = EINVAL; test_assert_idx(i_stream_read(try_input) == 1, test); test_assert_idx(i_stream_get_data_size(test_inputs[0]) == 0, test); test_assert_idx(i_stream_get_data_size(test_inputs[1]) == 1, test); break; case 2: /* both streams are available - stream 0 is read */ test_istream_set_size(test_inputs[0], 1); test_istream_set_size(test_inputs[1], 1); test_assert_idx(i_stream_read(try_input) == 1, test); test_assert_idx(i_stream_get_data_size(test_inputs[0]) == 1, test); test_assert_idx(i_stream_get_data_size(test_inputs[1]) == 0, test); break; case 3: /* stream 0 fails */ test_inputs[0]->stream_errno = EINVAL; test_assert_idx(i_stream_read(try_input) == 0, test); test_assert_idx(i_stream_get_data_size(test_inputs[0]) == 0, test); test_assert_idx(i_stream_get_data_size(test_inputs[1]) == 0, test); /* continue making stream 1 available */ test_istream_set_size(test_inputs[1], 1); test_assert_idx(i_stream_read(try_input) == 1, test); test_assert_idx(i_stream_get_data_size(test_inputs[0]) == 0, test); test_assert_idx(i_stream_get_data_size(test_inputs[1]) == 1, test); break; case 4: /* stream 1 fails */ test_inputs[1]->stream_errno = EINVAL; test_assert_idx(i_stream_read(try_input) == 0, test); test_assert_idx(i_stream_get_data_size(test_inputs[0]) == 0, test); test_assert_idx(i_stream_get_data_size(test_inputs[1]) == 0, test); break; case 5: /* stream 0 fails, stream 1 is available */ test_inputs[0]->stream_errno = EINVAL; test_istream_set_size(test_inputs[1], 1); test_assert_idx(i_stream_read(try_input) == 1, test); test_assert_idx(i_stream_get_data_size(test_inputs[0]) == 0, test); test_assert_idx(i_stream_get_data_size(test_inputs[1]) == 1, test); break; case 6: /* stream 0 is available, stream 1 fails */ test_inputs[1]->stream_errno = EINVAL; test_istream_set_size(test_inputs[0], 1); test_assert_idx(i_stream_read(try_input) == 1, test); test_assert_idx(i_stream_get_data_size(test_inputs[0]) == 1, test); test_assert_idx(i_stream_get_data_size(test_inputs[1]) == 0, test); break; case 7: /* both streams fail */ test_inputs[0]->stream_errno = EINVAL; test_inputs[1]->stream_errno = EINVAL; test_assert_idx(i_stream_read(try_input) == -1, test); test_assert_idx(try_input->stream_errno == EINVAL, test); break; case 8: /* stream 0 fails with EINVAL, stream 1 with EIO */ test_inputs[0]->stream_errno = EINVAL; test_inputs[1]->stream_errno = EIO; test_assert_idx(i_stream_read(try_input) == -1, test); test_assert_idx(try_input->stream_errno == EIO, test); break; case 9: /* stream 0 fails with EIO, stream 1 with EINVAL */ test_inputs[0]->stream_errno = EIO; test_inputs[1]->stream_errno = EINVAL; test_assert_idx(i_stream_read(try_input) == -1, test); test_assert_idx(try_input->stream_errno == EIO, test); break; case 10: /* stream 0 fails with EIO, stream 1 would work.. */ test_inputs[0]->stream_errno = EIO; test_istream_set_size(test_inputs[1], 1); test_assert_idx(i_stream_read(try_input) == -1, test); test_assert_idx(try_input->stream_errno == EIO, test); test_assert_idx(i_stream_get_data_size(test_inputs[1]) == 0, test); finished = TRUE; break; } test_assert_idx(test_inputs[0]->v_offset == 0, test); test_assert_idx(test_inputs[1]->v_offset == 0, test); i_stream_unref(&test_inputs[0]); i_stream_unref(&test_inputs[1]); i_stream_unref(&try_input); } i_assert(finished); test_end(); } static void test_istream_try_empty(void) { test_begin("istream try empty stream"); struct istream *test_inputs[] = { test_istream_create(""), test_istream_create(""), NULL }; struct istream *try_input = istream_try_create(test_inputs, MIN_FULL_SIZE); test_assert(i_stream_read(try_input) == -1); test_assert(try_input->eof); test_assert(try_input->stream_errno == 0); i_stream_unref(&test_inputs[0]); i_stream_unref(&test_inputs[1]); i_stream_unref(&try_input); test_end(); } static void test_istream_try_buffer_full(void) { const char *test_strings[] = { "Zm9v", "YmFy" }; struct istream *test_inputs[3], *try_input, *input, *input2; test_begin("istream try buffer full"); for (unsigned int i = 0; i < 2; i++) { input = test_istream_create(test_strings[i]); test_istream_set_size(input, 1); test_istream_set_max_buffer_size(input, 1); input2 = i_stream_create_base64_decoder(input); i_stream_unref(&input); test_inputs[i] = input2; }; test_inputs[2] = NULL; try_input = istream_try_create(test_inputs, MIN_FULL_SIZE); test_assert(i_stream_read(try_input) == 0); test_assert(try_input->real_stream->parent != NULL); test_assert(i_stream_get_data_size(test_inputs[0]) == 0); test_assert(i_stream_get_data_size(test_inputs[1]) == 0); test_istream_set_size(test_inputs[0], 2); test_assert(i_stream_read(try_input) == 1); test_assert(i_stream_get_data_size(test_inputs[0]) == 1); test_assert(i_stream_get_data_size(test_inputs[1]) == 0); i_stream_unref(&test_inputs[0]); i_stream_unref(&test_inputs[1]); i_stream_unref(&try_input); test_end(); } void test_istream_try(void) { test_istream_try_normal(); test_istream_try_empty(); test_istream_try_buffer_full(); } dovecot-2.3.21.1/src/lib/fd-util.c0000644000000000000000000000723714656633576013373 00000000000000/* Copyright (c) 1999-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "net.h" #include #include #include #include void fd_close_on_exec(int fd, bool set) { int flags; flags = fcntl(fd, F_GETFD, 0); if (flags < 0) i_fatal("fcntl(F_GETFD, %d) failed: %m", fd); flags = set ? (flags | FD_CLOEXEC) : (flags & ~FD_CLOEXEC); if (fcntl(fd, F_SETFD, flags) < 0) i_fatal("fcntl(F_SETFD, %d) failed: %m", fd); } void fd_debug_verify_leaks(int first_fd, int last_fd) { struct ip_addr addr, raddr; in_port_t port, rport; struct stat st; int old_errno; bool leaks = FALSE; for (int fd = first_fd; fd <= last_fd; ++fd) { if (fcntl(fd, F_GETFD, 0) == -1 && errno == EBADF) continue; old_errno = errno; if (net_getsockname(fd, &addr, &port) == 0) { if (addr.family == AF_UNIX) { struct sockaddr_un sa; socklen_t socklen = sizeof(sa); if (getsockname(fd, (void *)&sa, &socklen) < 0) sa.sun_path[0] = '\0'; i_error("Leaked UNIX socket fd %d: %s", fd, sa.sun_path); leaks = TRUE; continue; } if (net_getpeername(fd, &raddr, &rport) < 0) { i_zero(&raddr); rport = 0; } i_error("Leaked socket fd %d: %s:%u -> %s:%u", fd, net_ip2addr(&addr), port, net_ip2addr(&raddr), rport); leaks = TRUE; continue; } if (fstat(fd, &st) == 0) { #ifdef __APPLE__ /* OSX workaround: gettimeofday() calls shm_open() internally and the fd won't get closed on exec. We'll just skip all ino/dev=0 files and hope they weren't anything else. */ if (st.st_ino == 0 && st.st_dev == 0) continue; #endif #ifdef HAVE_SYS_SYSMACROS_H i_error("Leaked file fd %d: dev %s.%s inode %s", fd, dec2str(major(st.st_dev)), dec2str(minor(st.st_dev)), dec2str(st.st_ino)); leaks = TRUE; continue; #else i_error("Leaked file fd %d: dev %s inode %s", fd, dec2str(st.st_dev), dec2str(st.st_ino)); leaks = TRUE; continue; #endif } i_error("Leaked unknown fd %d (errno = %s)", fd, strerror(old_errno)); leaks = TRUE; continue; } if (leaks) i_fatal("fd leak found"); } void fd_set_nonblock(int fd, bool nonblock) { int flags; i_assert(fd > -1); flags = fcntl(fd, F_GETFL, 0); if (flags < 0) i_fatal("fcntl(%d, F_GETFL) failed: %m", fd); if (nonblock) flags |= O_NONBLOCK; else flags &= ENUM_NEGATE(O_NONBLOCK); if (fcntl(fd, F_SETFL, flags) < 0) i_fatal("fcntl(%d, F_SETFL) failed: %m", fd); } void fd_close_maybe_stdio(int *fd_in, int *fd_out) { int *fdp[2] = { fd_in, fd_out }; if (*fd_in == *fd_out) *fd_in = -1; for (unsigned int i = 0; i < N_ELEMENTS(fdp); i++) { if (*fdp[i] == -1) ; else if (*fdp[i] > 1) i_close_fd(fdp[i]); else if (dup2(dev_null_fd, *fdp[i]) == *fdp[i]) *fdp[i] = -1; else i_fatal("dup2(/dev/null, %d) failed: %m", *fdp[i]); } } #undef i_close_fd_path void i_close_fd_path(int *fd, const char *path, const char *arg, const char *func, const char *file, int line) { int saved_errno; if (*fd == -1) return; if (unlikely(*fd <= 0)) { i_panic("%s: close(%s%s%s) @ %s:%d attempted with fd=%d", func, arg, (path == NULL) ? "" : " = ", (path == NULL) ? "" : path, file, line, *fd); } saved_errno = errno; /* Ignore ECONNRESET because we don't really care about it here, as we are closing the socket down in any case. There might be unsent data but nothing we can do about that. */ if (unlikely(close(*fd) < 0 && errno != ECONNRESET)) i_error("%s: close(%s%s%s) @ %s:%d failed (fd=%d): %m", func, arg, (path == NULL) ? "" : " = ", (path == NULL) ? "" : path, file, line, *fd); errno = saved_errno; *fd = -1; } dovecot-2.3.21.1/src/lib/md5.h0000644000000000000000000000152714656633576012515 00000000000000/* * This is an OpenSSL-compatible implementation of the RSA Data Security, * Inc. MD5 Message-Digest Algorithm. * * Written by Solar Designer in 2001, and placed in * the public domain. See md5.c for more information. */ #ifndef MD5_H #define MD5_H #include "hash-method.h" #define MD5_RESULTLEN (128/8) struct md5_context { uint_fast32_t lo, hi; uint_fast32_t a, b, c, d; unsigned char buffer[64]; uint_fast32_t block[MD5_RESULTLEN]; }; void md5_init(struct md5_context *ctx); void md5_update(struct md5_context *ctx, const void *data, size_t size); void md5_final(struct md5_context *ctx, unsigned char result[STATIC_ARRAY MD5_RESULTLEN]); void md5_get_digest(const void *data, size_t size, unsigned char result[STATIC_ARRAY MD5_RESULTLEN]); extern const struct hash_method hash_method_md5; #endif dovecot-2.3.21.1/src/lib/test-stats-dist.c0000644000000000000000000000707414656633576015102 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "stats-dist.h" #include "sort.h" #include "math.h" #define DBL_EQ(a, b) (fabs((a)-(b)) < 0.001) static void test_stats_dist_verify(struct stats_dist *t, const int64_t *input, unsigned int input_size) { uint64_t min = INT_MAX, max = 0, sum = 0; uint64_t *copy; unsigned int i; i_assert(input_size > 0); copy = i_new(uint64_t, input_size); for (i = 0; i < input_size; i++) { uint64_t value = input[i]; if (min > value) min = value; if (max < value) max = value; sum += value; copy[i] = value; } i_qsort(copy, input_size, sizeof(*copy), uint64_cmp); test_assert_idx(stats_dist_get_count(t) == input_size, input_size); test_assert_idx(stats_dist_get_sum(t) == sum, input_size); test_assert_idx(stats_dist_get_min(t) == min, input_size); test_assert_idx(stats_dist_get_max(t) == max, input_size); test_assert_idx(DBL_EQ(stats_dist_get_avg(t), (double)sum/input_size), input_size); /* these aren't always fully accurate: */ test_assert_idx(stats_dist_get_median(t) >= copy[(input_size-1)/2] && stats_dist_get_median(t) <= copy[input_size/2], input_size); /* when we have 20 elements, [19] is the max, not the 95th %ile, so subtract 1 */ test_assert_idx(stats_dist_get_95th(t) == copy[input_size*95/100 - ((input_size%20) == 0 ? 1 : 0)], input_size); i_free(copy); } static void test_stats_dist_get_variance(void) { static const struct { int64_t in[10]; double out; } tests[] = { { .in = { 2, 2, 2, -1 }, .out = 0.0 }, { .in = { -1 }, .out = 0.0 }, { .in = { 1, 2, 3, 4, 5, 6, 7, 8, -1 }, .out = 5.25 }, }; struct stats_dist *t; unsigned int i, j; test_begin("stats_dists_get_variance"); for (i = 0; i < N_ELEMENTS(tests); i++) { t = stats_dist_init(); for (j = 0; tests[i].in[j] >= 0; j++) { stats_dist_add(t, tests[i].in[j]); test_stats_dist_verify(t, tests[i].in, j+1); } test_assert_idx(DBL_EQ(stats_dist_get_variance(t), tests[i].out), i); stats_dist_deinit(&t); } test_end(); } void test_stats_dist(void) { static int64_t test_input1[] = { 20, 19, 18, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, -1 }; static int64_t test_input2[] = { 20, 21, 19, 18, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, -1 }; static int64_t test_input3[] = { 20, 21, 19, 18, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 22, -1 }; static int64_t *test_inputs[] = { test_input1, test_input2, test_input3 }; struct stats_dist *t; unsigned int i, j; for (i = 0; i < N_ELEMENTS(test_inputs); i++) { test_begin(t_strdup_printf("stats_dists %u", i)); t = stats_dist_init(); for (j = 0; test_inputs[i][j] >= 0; j++) { stats_dist_add(t, test_inputs[i][j]); test_stats_dist_verify(t, test_inputs[i], j+1); } stats_dist_reset(t); test_assert(stats_dist_get_count(t) == 0); test_assert(stats_dist_get_max(t) == 0); stats_dist_deinit(&t); test_end(); } test_begin("stats_dists large"); t = stats_dist_init(); for (i = 0; i < 10000; i++) stats_dist_add(t, i); test_assert(stats_dist_get_count(t) == i); test_assert(stats_dist_get_sum(t) == (i-1)*i/2); test_assert(stats_dist_get_min(t) == 0); test_assert(stats_dist_get_max(t) == i-1); test_assert(DBL_EQ(stats_dist_get_avg(t), 4999.500000)); /* just test that these work: */ test_assert(stats_dist_get_median(t) > 0 && stats_dist_get_median(t) < i-1); test_assert(stats_dist_get_95th(t) > 0 && stats_dist_get_95th(t) < i-1); stats_dist_deinit(&t); test_end(); test_stats_dist_get_variance(); } dovecot-2.3.21.1/src/lib/ioloop-notify-none.c0000644000000000000000000000127614656633576015570 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop-private.h" #ifdef IOLOOP_NOTIFY_NONE #undef io_add_notify enum io_notify_result io_add_notify(const char *path ATTR_UNUSED, const char *source_filename ATTR_UNUSED, unsigned int source_linenum ATTR_UNUSED, io_callback_t *callback ATTR_UNUSED, void *context ATTR_UNUSED, struct io **io_r) { *io_r = NULL; return IO_NOTIFY_NOSUPPORT; } void io_loop_notify_remove(struct io *io ATTR_UNUSED) { } void io_loop_notify_handler_deinit(struct ioloop *ioloop ATTR_UNUSED) { } int io_loop_extract_notify_fd(struct ioloop *ioloop ATTR_UNUSED) { return -1; } #endif dovecot-2.3.21.1/src/lib/test-cpu-limit.c0000644000000000000000000000726114656633576014704 00000000000000#include "test-lib.h" #include "lib-signals.h" #include "guid.h" #include "time-util.h" #include "cpu-limit.h" #include #include #include #include /* The CPU limits aren't exact. Allow this much leniency in the time comparisons. Note that system CPU usage can grow very large on loaded systems, so we're not checking its upper limit at all. */ #define ALLOW_MSECS_BELOW 500 #define ALLOW_MSECS_ABOVE 3000 static const char *const test_path = ".test.cpulimit"; static struct timeval get_cpu_time(enum cpu_limit_type type) { struct rusage rusage; struct timeval cpu_usage = { 0, 0 }; /* Query cpu usage so far */ if (getrusage(RUSAGE_SELF, &rusage) < 0) i_fatal("getrusage() failed: %m"); if ((type & CPU_LIMIT_TYPE_USER) != 0) timeval_add(&cpu_usage, &rusage.ru_utime); if ((type & CPU_LIMIT_TYPE_SYSTEM) != 0) timeval_add(&cpu_usage, &rusage.ru_stime); return cpu_usage; } static void test_cpu_loop_once(void) { guid_128_t guid; /* consume some user CPU */ for (unsigned int i = 0; i < 10000; i++) guid_128_generate(guid); /* consume some system CPU */ int fd = creat(test_path, 0600); if (fd == -1) i_fatal("creat(%s) failed: %m", test_path); if (write(fd, guid, sizeof(guid)) < 0) i_fatal("write(%s) failed: %m", test_path); i_close_fd(&fd); } static void test_cpu_limit_simple(enum cpu_limit_type type, const char *type_str) { struct cpu_limit *climit; struct timeval usage, cpu; int diff_msecs; test_begin(t_strdup_printf("cpu limit - simple (%s)", type_str)); lib_signals_init(); climit = cpu_limit_init(2, type); usage = get_cpu_time(type); while (!cpu_limit_exceeded(climit)) test_cpu_loop_once(); cpu_limit_deinit(&climit); cpu = get_cpu_time(type); diff_msecs = timeval_diff_msecs(&cpu, &usage); test_assert_cmp(diff_msecs, >=, 2000 - ALLOW_MSECS_BELOW); lib_signals_deinit(); test_end(); } static void test_cpu_limit_nested(enum cpu_limit_type type, const char *type_str) { struct cpu_limit *climit1, *climit2; struct timeval usage1, cpu; unsigned int n; int diff_msecs; test_begin(t_strdup_printf("cpu limit - nested (%s)", type_str)); lib_signals_init(); climit1 = cpu_limit_init(3, type); usage1 = get_cpu_time(type); while (!cpu_limit_exceeded(climit1) && !test_has_failed()) { climit2 = cpu_limit_init(1, type); while (!cpu_limit_exceeded(climit2) && !test_has_failed()) test_cpu_loop_once(); cpu_limit_deinit(&climit2); } cpu_limit_deinit(&climit1); cpu = get_cpu_time(type); diff_msecs = timeval_diff_msecs(&cpu, &usage1); test_assert_cmp(diff_msecs, >=, 3000 - ALLOW_MSECS_BELOW); lib_signals_deinit(); test_end(); test_begin(t_strdup_printf("cpu limit - nested2 (%s)", type_str)); lib_signals_init(); climit1 = cpu_limit_init(3, type); usage1 = get_cpu_time(type); n = 0; while (!cpu_limit_exceeded(climit1) && !test_has_failed()) { if (++n >= 3) { /* Consume last second in top cpu limit */ test_cpu_loop_once(); continue; } climit2 = cpu_limit_init(1, type); while (!cpu_limit_exceeded(climit2) && !test_has_failed()) test_cpu_loop_once(); cpu_limit_deinit(&climit2); } cpu_limit_deinit(&climit1); cpu = get_cpu_time(type); diff_msecs = timeval_diff_msecs(&cpu, &usage1); test_assert_cmp(diff_msecs, >=, 3000 - ALLOW_MSECS_BELOW); i_unlink_if_exists(test_path); lib_signals_deinit(); test_end(); } void test_cpu_limit(void) { test_cpu_limit_simple(CPU_LIMIT_TYPE_USER, "user"); test_cpu_limit_simple(CPU_LIMIT_TYPE_SYSTEM, "system"); test_cpu_limit_simple(CPU_LIMIT_TYPE_ALL, "all"); test_cpu_limit_nested(CPU_LIMIT_TYPE_USER, "user"); test_cpu_limit_nested(CPU_LIMIT_TYPE_SYSTEM, "system"); test_cpu_limit_nested(CPU_LIMIT_TYPE_ALL, "all"); } dovecot-2.3.21.1/src/lib/ioloop-notify-inotify.c0000644000000000000000000001350314656633576016306 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #define _GNU_SOURCE #include "lib.h" #ifdef IOLOOP_NOTIFY_INOTIFY #include "ioloop-private.h" #include "ioloop-notify-fd.h" #include "buffer.h" #include "net.h" #include "ipwd.h" #include "time-util.h" #include #include #include #include #include #define INOTIFY_BUFLEN (32*1024) struct ioloop_notify_handler_context { struct ioloop_notify_fd_context fd_ctx; int inotify_fd; struct io *event_io; bool disabled; }; static struct ioloop_notify_handler_context *io_loop_notify_handler_init(void); static bool inotify_input_more(struct ioloop *ioloop) { struct ioloop_notify_handler_context *ctx = ioloop->notify_handler_context; const struct inotify_event *event; unsigned char event_buf[INOTIFY_BUFLEN]; struct io_notify *io; ssize_t ret, pos; /* read as many events as there is available and fit into our buffer. only full events are returned by the kernel. */ ret = read(ctx->inotify_fd, event_buf, sizeof(event_buf)); if (ret <= 0) { if (ret == 0 || errno == EAGAIN) { /* nothing more to read */ return FALSE; } i_fatal("read(inotify) failed: %m"); } i_gettimeofday(&ioloop_timeval); ioloop_time = ioloop_timeval.tv_sec; for (pos = 0; pos < ret; ) { if ((size_t)(ret - pos) < sizeof(*event)) break; event = (struct inotify_event *)(event_buf + pos); i_assert(event->len < (size_t)ret); pos += sizeof(*event) + event->len; io = io_notify_fd_find(&ctx->fd_ctx, event->wd); if (io != NULL) { if ((event->mask & IN_IGNORED) != 0) { /* calling inotify_rm_watch() would now give EINVAL */ io->fd = -1; } io_loop_call_io(&io->io); } } if (pos != ret) i_error("read(inotify) returned partial event"); return (size_t)ret >= sizeof(event_buf)-512; } static void inotify_input(struct ioloop *ioloop) { while (inotify_input_more(ioloop)) ; } #undef io_add_notify enum io_notify_result io_add_notify(const char *path, const char *source_filename, unsigned int source_linenum, io_callback_t *callback, void *context, struct io **io_r) { struct ioloop_notify_handler_context *ctx = current_ioloop->notify_handler_context; int wd; *io_r = NULL; if (ctx == NULL) ctx = io_loop_notify_handler_init(); if (ctx->disabled) return IO_NOTIFY_NOSUPPORT; wd = inotify_add_watch(ctx->inotify_fd, path, IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MOVE | IN_MODIFY); if (wd < 0) { /* ESTALE could happen with NFS. Don't bother giving an error message then. */ if (errno == ENOENT || errno == ESTALE) return IO_NOTIFY_NOTFOUND; if (errno != ENOSPC) i_error("inotify_add_watch(%s) failed: %m", path); else { i_warning("Inotify watch limit for user exceeded, " "disabling. Increase " "/proc/sys/fs/inotify/max_user_watches"); } ctx->disabled = TRUE; return IO_NOTIFY_NOSUPPORT; } if (ctx->event_io == NULL) { ctx->event_io = io_add(ctx->inotify_fd, IO_READ, inotify_input, current_ioloop); } *io_r = io_notify_fd_add(&ctx->fd_ctx, wd, callback, context); (*io_r)->source_filename = source_filename; (*io_r)->source_linenum = source_linenum; return IO_NOTIFY_ADDED; } void io_loop_notify_remove(struct io *_io) { struct ioloop_notify_handler_context *ctx = _io->ioloop->notify_handler_context; struct io_notify *io = (struct io_notify *)_io; if (io->fd != -1) { /* ernro=EINVAL happens if the file itself is deleted and kernel has sent IN_IGNORED event which we haven't read. */ if (inotify_rm_watch(ctx->inotify_fd, io->fd) < 0 && errno != EINVAL) i_error("inotify_rm_watch() failed: %m"); } io_notify_fd_free(&ctx->fd_ctx, io); if (ctx->fd_ctx.notifies == NULL && ctx->event_io != NULL) io_remove(&ctx->event_io); } static void ioloop_inotify_user_limit_exceeded(void) { struct passwd pw; const char *name; uid_t uid = geteuid(); if (i_getpwuid(uid, &pw) <= 0) name = t_strdup_printf("UID %s", dec2str(uid)); else { name = t_strdup_printf("%s (UID %s)", dec2str(uid), pw.pw_name); } i_warning("Inotify instance limit for user %s exceeded, disabling. " "Increase /proc/sys/fs/inotify/max_user_instances", name); } static struct ioloop_notify_handler_context *io_loop_notify_handler_init(void) { struct ioloop *ioloop = current_ioloop; struct ioloop_notify_handler_context *ctx; ctx = ioloop->notify_handler_context = i_new(struct ioloop_notify_handler_context, 1); ctx->inotify_fd = inotify_init(); if (ctx->inotify_fd == -1) { if (errno != EMFILE) i_error("inotify_init() failed: %m"); else ioloop_inotify_user_limit_exceeded(); ctx->disabled = TRUE; } else { fd_close_on_exec(ctx->inotify_fd, TRUE); fd_set_nonblock(ctx->inotify_fd, TRUE); } return ctx; } void io_loop_notify_handler_deinit(struct ioloop *ioloop) { struct ioloop_notify_handler_context *ctx = ioloop->notify_handler_context; while (ctx->fd_ctx.notifies != NULL) { struct io_notify *io = ctx->fd_ctx.notifies; struct io *_io = &io->io; i_warning("I/O notify leak: %p (%s:%u, fd %d)", (void *)_io->callback, _io->source_filename, _io->source_linenum, io->fd); io_remove(&_io); } i_close_fd(&ctx->inotify_fd); i_free(ctx); } int io_loop_extract_notify_fd(struct ioloop *ioloop) { struct ioloop_notify_handler_context *ctx = ioloop->notify_handler_context; struct io_notify *io; int fd, new_inotify_fd; if (ctx == NULL || ctx->inotify_fd == -1) return -1; new_inotify_fd = inotify_init(); if (new_inotify_fd == -1) { if (errno != EMFILE) i_error("inotify_init() failed: %m"); else ioloop_inotify_user_limit_exceeded(); return -1; } for (io = ctx->fd_ctx.notifies; io != NULL; io = io->next) io->fd = -1; io_remove(&ctx->event_io); fd = ctx->inotify_fd; ctx->inotify_fd = new_inotify_fd; return fd; } #endif dovecot-2.3.21.1/src/lib/test-buffer-istream.c0000644000000000000000000000617014656633576015712 00000000000000/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "istream.h" #include "str.h" #include "write-full.h" #include #include #define TEST_FILENAME ".test_buffer_append_full_file" static void test_buffer_append_full_file(void) { const char *test_string = "this is a test string\n"; test_begin("buffer_append_full_file"); buffer_t *result = t_buffer_create(32); const char *error; int fd = open(TEST_FILENAME, O_WRONLY|O_CREAT, 0600); i_assert(fd > -1); test_assert(write_full(fd, test_string, strlen(test_string)) == 0); i_close_fd(&fd); test_assert(buffer_append_full_file(result, TEST_FILENAME, SIZE_MAX, &error) == BUFFER_APPEND_OK); test_assert_strcmp(str_c(result), test_string); /* test max_read_size */ for (size_t max = 0; max < strlen(test_string)-1; max++) { buffer_set_used_size(result, 0); test_assert(buffer_append_full_file(result, TEST_FILENAME, max, &error) == BUFFER_APPEND_READ_MAX_SIZE); test_assert(result->used == max && memcmp(result->data, test_string, max) == 0); } fd = open(TEST_FILENAME, O_WRONLY|O_TRUNC); i_assert(fd > -1); /* write it enough many times */ for (size_t i = 0; i < IO_BLOCK_SIZE; i += strlen(test_string)) { test_assert(write_full(fd, test_string, strlen(test_string)) == 0); } i_close_fd(&fd); buffer_set_used_size(result, 0); test_assert(buffer_append_full_file(result, TEST_FILENAME, SIZE_MAX, &error) == BUFFER_APPEND_OK); for (size_t i = 0; i < result->used; i += strlen(test_string)) { const char *data = result->data; data += i; test_assert(memcmp(data, test_string, strlen(test_string)) == 0); } buffer_set_used_size(result, 0); test_assert(chmod(TEST_FILENAME, 0) == 0); error = NULL; test_assert(buffer_append_full_file(result, TEST_FILENAME, SIZE_MAX, &error) == BUFFER_APPEND_READ_ERROR); test_assert(error != NULL && *error != '\0'); buffer_set_used_size(result, 0); test_assert(chmod(TEST_FILENAME, 0700) == 0); /* test permission problems */ i_unlink(TEST_FILENAME); test_assert(buffer_append_full_file(result, TEST_FILENAME, SIZE_MAX, &error) == BUFFER_APPEND_READ_ERROR); test_assert_strcmp(str_c(result), ""); test_end(); } static void test_buffer_append_full_istream(void) { int fds[2]; const char *error; test_begin("buffer_append_full_istream"); buffer_t *result = t_buffer_create(32); test_assert(pipe(fds) == 0); fd_set_nonblock(fds[0], TRUE); fd_set_nonblock(fds[1], TRUE); struct istream *is = i_stream_create_fd(fds[0], SIZE_MAX); /* test just the READ_MORE stuff */ test_assert(write_full(fds[1], "some data ", 10) == 0); test_assert(buffer_append_full_istream(result, is, SIZE_MAX, &error) == BUFFER_APPEND_READ_MORE); test_assert(write_full(fds[1], "final read", 10) == 0); i_close_fd(&fds[1]); test_assert(buffer_append_full_istream(result, is, SIZE_MAX, &error) == BUFFER_APPEND_OK); test_assert_strcmp(str_c(result), "some data final read"); i_stream_unref(&is); i_close_fd(&fds[0]); test_end(); } void test_buffer_append_full(void) { test_buffer_append_full_file(); test_buffer_append_full_istream(); } dovecot-2.3.21.1/src/lib/test-var-expand.c0000644000000000000000000003253014656633576015043 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str.h" #include "env-util.h" #include "hostpid.h" #include "var-expand.h" #include "var-expand-private.h" struct var_expand_test { const char *in; const char *out; int ret; }; struct var_get_key_range_test { const char *in; unsigned int idx, size; }; static void test_var_expand_ranges(void) { static const struct var_expand_test tests[] = { { "%v", "value1234", 1 }, { "%3v", "val", 1 }, { "%3.2v", "ue", 1 }, { "%3.-2v", "ue12", 1 }, { "%-3.2v", "23", 1 }, { "%0.-1v", "value123", 1 }, { "%-4.-1v", "123", 1 } }; static const struct var_expand_table table[] = { { 'v', "value1234", NULL }, { '\0', NULL, NULL } }; string_t *str = t_str_new(128); const char *error; unsigned int i; test_begin("var_expand - ranges"); for (i = 0; i < N_ELEMENTS(tests); i++) { str_truncate(str, 0); test_assert(var_expand(str, tests[i].in, table, &error) == tests[i].ret); test_assert(strcmp(tests[i].out, str_c(str)) == 0); } test_end(); } static void test_var_expand_builtin(void) { static struct var_expand_test tests[] = { { "%{hostname}", NULL, 1 }, { "%{pid}", NULL, 1 }, { "a%{env:FOO}b", "abaRb", 1 }, { "%50Hv", "1f", 1 }, { "%50Hw", "2e", 1 }, { "%50Nv", "25", 1 }, { "%50Nw", "e", 1 }, { "%{nonexistent}", "UNSUPPORTED_VARIABLE_nonexistent", 0 }, { "%1.2M{nonexistent:default}", "UNSUPPORTED_VARIABLE_nonexistent", 0 }, { "%x", "UNSUPPORTED_VARIABLE_x", 0 }, { "%5Mm", "UNSUPPORTED_VARIABLE_m", 0 }, }; static const struct var_expand_table table[] = { { 'v', "value", NULL }, { 'w', "value2", NULL }, { '\0', NULL, NULL } }; string_t *str = t_str_new(128); const char *error; unsigned int i; tests[0].out = my_hostname; tests[1].out = my_pid; env_put("FOO", "baR"); test_begin("var_expand - builtin"); for (i = 0; i < N_ELEMENTS(tests); i++) { str_truncate(str, 0); test_assert_idx(var_expand(str, tests[i].in, table, &error) == tests[i].ret, i); test_assert_idx(strcmp(tests[i].out, str_c(str)) == 0, i); } test_end(); } static void test_var_get_key_range(void) { static const struct var_get_key_range_test tests[] = { { "", 0, 0 }, { "{", 1, 0 }, { "k", 0, 1 }, { "{key}", 1, 3 }, { "5.5Rk", 4, 1 }, { "5.5R{key}", 5, 3 }, { "{key", 1, 3 }, { "{if;%{if;%{value};eq;value;t;f};eq;t;t;f}", 1, 39 }, }; unsigned int i, idx, size; test_begin("var_get_key_range"); for (i = 0; i < N_ELEMENTS(tests); i++) { var_get_key_range(tests[i].in, &idx, &size); test_assert_idx(tests[i].idx == idx, i); test_assert_idx(tests[i].size == size, i); if (tests[i].size == 1) test_assert_idx(tests[i].in[idx] == var_get_key(tests[i].in), i); } test_end(); } static int test_var_expand_func1(const char *data, void *context, const char **value_r, const char **error_r ATTR_UNUSED) { test_assert(*(int *)context == 0xabcdef); *value_r = t_strdup_printf("<%s>", data); return 1; } static int test_var_expand_func2(const char *data ATTR_UNUSED, void *context ATTR_UNUSED, const char **value_r, const char **error_r ATTR_UNUSED) { *value_r = ""; return 1; } static int test_var_expand_func3(const char *data ATTR_UNUSED, void *context ATTR_UNUSED, const char **value_r, const char **error_r ATTR_UNUSED) { *value_r = NULL; return 1; } static int test_var_expand_func4(const char *data, void *context ATTR_UNUSED, const char **value_r ATTR_UNUSED, const char **error_r) { *error_r = t_strdup_printf("Unknown data %s", data == NULL ? "" : data); return 0; } static int test_var_expand_func5(const char *data ATTR_UNUSED, void *context ATTR_UNUSED, const char **value_r ATTR_UNUSED, const char **error_r) { *error_r = "Internal error"; return -1; } static void test_var_expand_with_funcs(void) { static const struct var_expand_test tests[] = { { "%{func1}", "<>", 1 }, { "%{func1:foo}", "", 1 }, { "%{func2}", "", 1 }, { "%{func3}", "", 1 }, { "%{func4}", "", 0 }, { "%{func5}", "", -1 }, { "%{func4}%{func5}", "", -1 }, { "%{func5}%{func4}%{func3}", "", -1 }, }; static const struct var_expand_table table[] = { { '\0', NULL, NULL } }; static const struct var_expand_func_table func_table[] = { { "func1", test_var_expand_func1 }, { "func2", test_var_expand_func2 }, { "func3", test_var_expand_func3 }, { "func4", test_var_expand_func4 }, { "func5", test_var_expand_func5 }, { NULL, NULL } }; string_t *str = t_str_new(128); const char *error; unsigned int i; int ctx = 0xabcdef; test_begin("var_expand_with_funcs"); for (i = 0; i < N_ELEMENTS(tests); i++) { str_truncate(str, 0); test_assert_idx(var_expand_with_funcs(str, tests[i].in, table, func_table, &ctx, &error) == tests[i].ret, i); test_assert_idx(strcmp(tests[i].out, str_c(str)) == 0, i); } test_end(); } static void test_var_get_key(void) { static const struct { const char *str; char key; } tests[] = { { "x", 'x' }, { "2.5Mx", 'x' }, { "200MDx", 'x' }, { "200MD{foo}", '{' }, { "{foo}", '{' }, { "", '\0' }, }; test_begin("var_get_key"); for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) test_assert_idx(var_get_key(tests[i].str) == tests[i].key, i); test_end(); } static void test_var_has_key(void) { static const struct { const char *str; char key; const char *long_key; bool result; } tests[] = { { "%x%y", 'x', NULL, TRUE }, { "%x%y", 'y', NULL, TRUE }, { "%x%y", 'z', NULL, FALSE }, { "%{foo}", 'f', NULL, FALSE }, { "%{foo}", 'o', NULL, FALSE }, { "%{foo}", '\0', "foo", TRUE }, { "%{foo}", 'o', "foo", TRUE }, { "%2.5Mx%y", 'x', NULL, TRUE }, { "%2.5M{foo}", '\0', "foo", TRUE }, }; test_begin("var_has_key"); for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) test_assert_idx(var_has_key(tests[i].str, tests[i].key, tests[i].long_key) == tests[i].result, i); test_end(); } static int test_var_expand_hashing_func1(const char *data, void *context ATTR_UNUSED, const char **value_r, const char **error_r ATTR_UNUSED) { *value_r = data; return 1; } static int test_var_expand_bad_func(struct var_expand_context *ctx ATTR_UNUSED, const char *key, const char *field ATTR_UNUSED, const char **result_r ATTR_UNUSED, const char **error_r) { if (strcmp(key, "notfound") == 0) return 0; *error_r = "Bad parameters"; return -1; } static const struct var_expand_extension_func_table test_extension_funcs[] = { { "notfound", test_var_expand_bad_func }, { "badparam", test_var_expand_bad_func }, { NULL, NULL } }; static void test_var_expand_extensions(void) { const char *error; test_begin("var_expand_extensions"); var_expand_register_func_array(test_extension_funcs); static const struct var_expand_table table[] = { {'\0', "example", "value" }, {'\0', "other-example", "other-value" }, {'\0', NULL, NULL} }; static const struct { const char *in; const char *out; } tests[] = { { "md5: %M{value} %{md5:value}", "md5: 1a79a4d60de6718e8e5b326e338ae533 1a79a4d60de6718e8e5b326e338ae533" }, { "sha1: %{sha1:value}", "sha1: c3499c2729730a7f807efb8676a92dcb6f8a3f8f" }, { "sha1: %{sha1:func1:example}", "sha1: c3499c2729730a7f807efb8676a92dcb6f8a3f8f" }, { "truncate: %{sha1;truncate=12:value}", "truncate: 0c34" }, { "truncate: %{sha1;truncate=16:value}", "truncate: c349" }, { "rounds,salt: %{sha1;rounds=1000,salt=seawater:value}", "rounds,salt: b515c85884f6b82dc7588279f3643a73e55d2289" }, { "rounds,salt,expand: %{sha1;rounds=1000,salt=%{other-value}:value} %{other-value}", "rounds,salt,expand: 49a598ee110af615e175f2e4511cc5d7ccff96ab other-example" }, { "format: %4.8{sha1:value}", "format: 9c272973" }, { "base64: %{sha1;format=base64:value}", "base64: w0mcJylzCn+AfvuGdqkty2+KP48=" }, }; static const struct var_expand_func_table func_table[] = { { "func1", test_var_expand_hashing_func1 }, { NULL, NULL } }; string_t *str = t_str_new(128); for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { str_truncate(str, 0); error = NULL; test_assert(var_expand_with_funcs(str, tests[i].in, table, func_table, NULL, &error) == 1); test_assert_idx(strcmp(str_c(str), tests[i].out) == 0, i); if (error != NULL) { i_debug("Error: %s", error); } } test_assert(var_expand_with_funcs(str, "notfound: %{notfound:field}", table, func_table, NULL, &error) == 0); error = NULL; test_assert(var_expand_with_funcs(str, "notfound: %{badparam:field}", table, func_table, NULL, &error) == -1); test_assert(error != NULL); var_expand_unregister_func_array(test_extension_funcs); test_end(); } static void test_var_expand_if(void) { static const struct var_expand_table table[] = { { 'a', "alpha", "alpha" }, { 'b', "beta", "beta" }, { 'o', "1", "one" }, { 't', "2", "two" }, { '\0', ";:", "evil1" }, { '\0', ";test;", "evil2" }, { '\0', NULL, NULL } }; const char *error; string_t *dest = t_str_new(64); test_begin("var_expand_if"); static const struct var_expand_test tests[] = { /* basic numeric operand test */ { "%{if;1;==;1;yes;no}", "yes", 1 }, { "%{if;1;==;2;yes;no}", "no", 1 }, { "%{if;1;<;1;yes;no}", "no", 1 }, { "%{if;1;<;2;yes;no}", "yes", 1 }, { "%{if;1;<=;1;yes;no}", "yes", 1 }, { "%{if;1;<=;2;yes;no}", "yes", 1 }, { "%{if;1;>;1;yes;no}", "no", 1 }, { "%{if;1;>;2;yes;no}", "no", 1 }, { "%{if;1;>=;1;yes;no}", "yes", 1 }, { "%{if;1;>=;2;yes;no}", "no", 1 }, { "%{if;1;!=;1;yes;no}", "no", 1 }, { "%{if;1;!=;2;yes;no}", "yes", 1 }, /* basic string operand test */ { "%{if;a;eq;a;yes;no}", "yes", 1 }, { "%{if;a;eq;b;yes;no}", "no", 1 }, { "%{if;a;lt;a;yes;no}", "no", 1 }, { "%{if;a;lt;b;yes;no}", "yes", 1 }, { "%{if;a;le;a;yes;no}", "yes", 1 }, { "%{if;a;le;b;yes;no}", "yes", 1 }, { "%{if;a;gt;a;yes;no}", "no", 1 }, { "%{if;a;gt;b;yes;no}", "no", 1 }, { "%{if;a;ge;a;yes;no}", "yes", 1 }, { "%{if;a;ge;b;yes;no}", "no", 1 }, { "%{if;a;ne;a;yes;no}", "no", 1 }, { "%{if;a;ne;b;yes;no}", "yes", 1 }, { "%{if;a;*;a;yes;no}", "yes", 1 }, { "%{if;a;*;b;yes;no}", "no", 1 }, { "%{if;a;*;*a*;yes;no}", "yes", 1 }, { "%{if;a;*;*b*;yes;no}", "no", 1 }, { "%{if;a;*;*;yes;no}", "yes", 1 }, { "%{if;a;!*;a;yes;no}", "no", 1 }, { "%{if;a;!*;b;yes;no}", "yes", 1 }, { "%{if;a;!*;*a*;yes;no}", "no", 1 }, { "%{if;a;!*;*b*;yes;no}", "yes", 1 }, { "%{if;a;!*;*;yes;no}", "no", 1 }, { "%{if;a;~;a;yes;no}", "yes", 1 }, { "%{if;a;~;b;yes;no}", "no", 1 }, { "%{if;a;~;.*a.*;yes;no}", "yes", 1 }, { "%{if;a;~;.*b.*;yes;no}", "no", 1 }, { "%{if;a;~;.*;yes;no}", "yes", 1 }, { "%{if;a;!~;a;yes;no}", "no", 1 }, { "%{if;a;!~;b;yes;no}", "yes", 1 }, { "%{if;a;!~;.*a.*;yes;no}", "no", 1 }, { "%{if;a;!~;.*b.*;yes;no}", "yes", 1 }, { "%{if;a;!~;.*;yes;no}", "no", 1 }, { "%{if;this is test;~;^test;yes;no}", "no", 1 }, { "%{if;this is test;~;.*test;yes;no}", "yes", 1 }, /* variable expansion */ { "%{if;%a;eq;%a;yes;no}", "yes", 1 }, { "%{if;%a;eq;%b;yes;no}", "no", 1 }, { "%{if;%{alpha};eq;%{alpha};yes;no}", "yes", 1 }, { "%{if;%{alpha};eq;%{beta};yes;no}", "no", 1 }, { "%{if;%o;eq;%o;yes;no}", "yes", 1 }, { "%{if;%o;eq;%t;yes;no}", "no", 1 }, { "%{if;%{one};eq;%{one};yes;no}", "yes", 1 }, { "%{if;%{one};eq;%{two};yes;no}", "no", 1 }, { "%{if;%{one};eq;%{one};%{one};%{two}}", "1", 1 }, { "%{if;%{one};gt;%{two};%{one};%{two}}", "2", 1 }, { "%{if;%{evil1};eq;\\;\\:;%{evil2};no}", ";test;", 1 }, /* inner if */ { "%{if;%{if;%{one};eq;1;1;0};eq;%{if;%{two};eq;2;2;3};yes;no}", "no", 1 }, /* no false */ { "%{if;1;==;1;yes}", "yes", 1 }, { "%{if;1;==;2;yes}", "", 1 }, /* invalid input */ { "%{if;}", "", -1 }, { "%{if;1;}", "", -1 }, { "%{if;1;==;}", "", -1 }, { "%{if;1;==;2;}", "", -1 }, { "%{if;1;fu;2;yes;no}", "", -1 }, /* missing variables */ { "%{if;%{missing1};==;%{missing2};yes;no}", "", 0 }, }; for(size_t i = 0; i < N_ELEMENTS(tests); i++) { int ret; error = NULL; str_truncate(dest, 0); ret = var_expand(dest, tests[i].in, table, &error); test_assert_idx(tests[i].ret == ret, i); test_assert_idx(strcmp(tests[i].out, str_c(dest)) == 0, i); } test_end(); } static void test_var_expand_merge_tables(void) { const struct var_expand_table one[] = { { 'a', "1", "alpha" }, { '\0', "2", "beta" }, { '\0', NULL, NULL } }, two[] = { { 't', "3", "theta" }, { '\0', "4", "phi" }, { '\0', NULL, NULL } }, *merged = NULL; test_begin("var_expand_merge_tables"); merged = t_var_expand_merge_tables(one, two); test_assert(var_expand_table_size(merged) == 4); for(unsigned int i = 0; i < var_expand_table_size(merged); i++) { if (i < 2) { test_assert_idx(merged[i].key == one[i].key, i); test_assert_idx(merged[i].value == one[i].value || strcmp(merged[i].value, one[i].value) == 0, i); test_assert_idx(merged[i].long_key == one[i].long_key || strcmp(merged[i].long_key, one[i].long_key) == 0, i); } else if (i < 4) { test_assert_idx(merged[i].key == two[i-2].key, i); test_assert_idx(merged[i].value == two[i-2].value || strcmp(merged[i].value, two[i-2].value) == 0, i); test_assert_idx(merged[i].long_key == two[i-2].long_key || strcmp(merged[i].long_key, two[i-2].long_key) == 0, i); } else { break; } } test_end(); } void test_var_expand(void) { test_var_expand_ranges(); test_var_expand_builtin(); test_var_get_key_range(); test_var_expand_with_funcs(); test_var_get_key(); test_var_has_key(); test_var_expand_extensions(); test_var_expand_if(); test_var_expand_merge_tables(); } dovecot-2.3.21.1/src/lib/test-seq-range-array.c0000644000000000000000000002767014656633576016005 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "array.h" #include "seq-range-array.h" static void boundaries_permute(uint32_t *input, unsigned int i, unsigned int count) { ARRAY_TYPE(seq_range) range; const struct seq_range *seqs; unsigned int seqs_count; uint32_t tmp; unsigned int j; if (i+1 < count) { for (j = i; j < count; j++) { tmp = input[i]; input[i] = input[j]; input[j] = tmp; boundaries_permute(input, i+1, count); tmp = input[i]; input[i] = input[j]; input[j] = tmp; } return; } t_array_init(&range, 4); for (i = 0; i < count; i++) seq_range_array_add(&range, input[i]); seqs = array_get(&range, &seqs_count); test_assert(seqs_count == 2); test_assert(seqs[0].seq1 == 0); test_assert(seqs[0].seq2 == 1); test_assert(seqs[1].seq1 == (uint32_t)-2); test_assert(seqs[1].seq2 == (uint32_t)-1); } static void test_seq_range_array_add_boundaries(void) { static uint32_t input[] = { 0, 1, (uint32_t)-2, (uint32_t)-1 }; boundaries_permute(input, 0, N_ELEMENTS(input)); } static void test_seq_range_array_add_merge(void) { ARRAY_TYPE(seq_range) range; test_begin("seq_range_array_add() merging"); t_array_init(&range, 8); seq_range_array_add(&range, 4); seq_range_array_add(&range, 1); seq_range_array_add(&range, 2); test_assert(array_count(&range) == 2); seq_range_array_add_range(&range, 1, (uint32_t)-1); test_assert(array_count(&range) == 1); seq_range_array_add_range(&range, 1, (uint32_t)-1); test_assert(array_count(&range) == 1); test_end(); } static void test_seq_range_array_merge_n(void) { ARRAY_TYPE(seq_range) src, dest, dest2; struct seq_range_iter iter; const uint32_t seqs[] = { 4, 5, 7, 8, 9, 11 }; uint32_t seq; test_begin("seq_range_array_merge_n()"); t_array_init(&src, 16); t_array_init(&dest, 16); t_array_init(&dest2, 16); for (unsigned int i = 0; i < N_ELEMENTS(seqs); i++) seq_range_array_add(&src, seqs[i]); for (unsigned int i = 0; i <= N_ELEMENTS(seqs); i++) { array_clear(&dest); array_clear(&dest2); seq_range_array_merge_n(&dest, &src, i); test_assert_idx(seq_range_count(&dest) == I_MIN(i, N_ELEMENTS(seqs)), i); seq_range_array_iter_init(&iter, &src); for (unsigned int j = 0; j < i; j++) { test_assert_idx(seq_range_array_iter_nth(&iter, j, &seq), i); seq_range_array_add(&dest2, seq); } seq_range_array_invert(&dest2, 1, UINT32_MAX); seq_range_array_intersect(&dest2, &dest); test_assert_idx(array_count(&dest2) == 0, i); } test_end(); } static void test_seq_range_array_remove_nth(void) { ARRAY_TYPE(seq_range) range; const struct seq_range *r; test_begin("seq_range_array_remove_nth()"); t_array_init(&range, 8); seq_range_array_add_range(&range, 1, 5); seq_range_array_add(&range, 7); seq_range_array_add_range(&range, 10,20); test_assert(array_count(&range) == 3); seq_range_array_remove_nth(&range, 0, 2); r = array_front(&range); test_assert(r->seq1 == 3 && r->seq2 == 5); seq_range_array_remove_nth(&range, 1, 4); r = array_front(&range); test_assert(r->seq1 == 3 && r->seq2 == 3); r = array_idx(&range, 1); test_assert(r->seq1 == 11 && r->seq2 == 20); seq_range_array_remove_nth(&range, 5, (uint32_t)-1); r = array_idx(&range, 1); test_assert(r->seq1 == 11 && r->seq2 == 14); test_assert(array_count(&range) == 2); test_end(); } static void test_seq_range_array_remove_range(void) { ARRAY_TYPE(seq_range) range; const struct seq_range *r; test_begin("seq_range_array_remove_range()"); t_array_init(&range, 8); seq_range_array_add_range(&range, 0, (uint32_t)-2); test_assert(seq_range_array_remove_range(&range, 0, 2) == 3); r = array_front(&range); test_assert(r->seq1 == 3 && r->seq2 == (uint32_t)-2); seq_range_array_add_range(&range, 0, (uint32_t)-2); test_assert(array_count(&range) == 1); test_assert(seq_range_array_remove_range(&range, 0, (uint32_t)-2) == UINT_MAX); test_assert(array_count(&range) == 0); seq_range_array_add_range(&range, (uint32_t)-1, (uint32_t)-1); test_assert(seq_range_array_remove_range(&range, (uint32_t)-1, (uint32_t)-1) == 1); test_assert(array_count(&range) == 0); seq_range_array_add_range(&range, (uint32_t)-1, (uint32_t)-1); test_assert(seq_range_array_remove_range(&range, 1, (uint32_t)-1) == 1); test_assert(array_count(&range) == 0); seq_range_array_add_range(&range, 1, 10); test_assert(seq_range_array_remove_range(&range, 5, 6) == 2); test_assert(seq_range_array_remove_range(&range, 4, 7) == 2); test_assert(seq_range_array_remove_range(&range, 1, 4) == 3); test_assert(seq_range_array_remove_range(&range, 8, 10) == 3); test_assert(array_count(&range) == 0); test_end(); } static void test_seq_range_array_random(void) { #define SEQ_RANGE_TEST_BUFSIZE 100 #define SEQ_RANGE_TEST_COUNT 20000 unsigned char shadowbuf[SEQ_RANGE_TEST_BUFSIZE]; ARRAY_TYPE(seq_range) range; const struct seq_range *seqs; uint32_t seq1, seq2; unsigned int i, j, ret, ret2, count; int test = -1; ret = ret2 = 0; i_array_init(&range, 1); memset(shadowbuf, 0, sizeof(shadowbuf)); for (i = 0; i < SEQ_RANGE_TEST_COUNT; i++) { seq1 = i_rand_limit(SEQ_RANGE_TEST_BUFSIZE); seq2 = seq1 + i_rand_limit(SEQ_RANGE_TEST_BUFSIZE - seq1); test = i_rand_limit(4); switch (test) { case 0: ret = seq_range_array_add(&range, seq1) ? 0 : 1; /* FALSE == added */ ret2 = shadowbuf[seq1] == 0 ? 1 : 0; shadowbuf[seq1] = 1; break; case 1: ret = seq_range_array_add_range_count(&range, seq1, seq2); for (ret2 = 0; seq1 <= seq2; seq1++) { if (shadowbuf[seq1] == 0) { ret2++; shadowbuf[seq1] = 1; } } break; case 2: ret = seq_range_array_remove(&range, seq1) ? 1 : 0; ret2 = shadowbuf[seq1] != 0 ? 1 : 0; shadowbuf[seq1] = 0; break; case 3: ret = seq_range_array_remove_range(&range, seq1, seq2); for (ret2 = 0; seq1 <= seq2; seq1++) { if (shadowbuf[seq1] != 0) { ret2++; shadowbuf[seq1] = 0; } } break; } if (ret != ret2) break; seqs = array_get(&range, &count); for (j = 0, seq1 = 0; j < count; j++) { if (j > 0 && seqs[j-1].seq2+1 >= seqs[j].seq1) goto fail; for (; seq1 < seqs[j].seq1; seq1++) { if (shadowbuf[seq1] != 0) goto fail; } for (; seq1 <= seqs[j].seq2; seq1++) { if (shadowbuf[seq1] == 0) goto fail; } } i_assert(seq1 <= SEQ_RANGE_TEST_BUFSIZE); for (; seq1 < SEQ_RANGE_TEST_BUFSIZE; seq1++) { if (shadowbuf[seq1] != 0) goto fail; } } fail: if (i == SEQ_RANGE_TEST_COUNT) test_out("seq_range_array random", TRUE); else { test_out_reason("seq_range_array random", FALSE, t_strdup_printf("round %u test %d failed", i, test)); } array_free(&range); } static void test_seq_range_array_invert_minmax(uint32_t min, uint32_t max) { ARRAY_TYPE(seq_range) range = ARRAY_INIT; struct seq_range_iter iter; unsigned int n, inverse_mask, mask_inside, mask_size = max-min+1; uint32_t seq; i_assert(mask_size <= sizeof(unsigned int)*8); t_array_init(&range, 16); for (unsigned int mask = 0; mask < mask_size; mask++) { array_clear(&range); for (unsigned int i = 0; i < mask_size; i++) { if ((mask & (1 << i)) != 0) seq_range_array_add(&range, min+i); } seq_range_array_invert(&range, min, max); inverse_mask = 0; seq_range_array_iter_init(&iter, &range); n = 0; while (seq_range_array_iter_nth(&iter, n++, &seq)) { test_assert(seq >= min && seq <= max); inverse_mask |= 1 << (seq-min); } mask_inside = ((1 << mask_size)-1); test_assert_idx((inverse_mask & ~mask_inside) == 0, mask); test_assert_idx(inverse_mask == (mask ^ mask_inside), mask); } } static void test_seq_range_array_invert(void) { test_begin("seq_range_array_invert()"); /* first numbers */ for (unsigned int min = 0; min <= 7; min++) { for (unsigned int max = min; max <= 7; max++) T_BEGIN { test_seq_range_array_invert_minmax(min, max); } T_END; } /* last numbers */ for (uint64_t min = 0xffffffff-7; min <= 0xffffffff; min++) { for (uint64_t max = min; max <= 0xffffffff; max++) T_BEGIN { test_seq_range_array_invert_minmax(min, max); } T_END; } test_end(); } static void test_seq_range_array_invert_edges(void) { static const struct { int64_t a_seq1, a_seq2, b_seq1, b_seq2; int64_t resa_seq1, resa_seq2, resb_seq1, resb_seq2; } tests[] = { { -1, -1, -1, -1, 0, 0xffffffff, -1, -1 }, /*{ 0, 0xffffffff, -1, -1, too large, will assert-crash -1, -1, -1, -1 }, */ { 0, 0xfffffffe, -1, -1, 0xffffffff, 0xffffffff, -1, -1 }, { 1, 0xfffffffe, -1, -1, 0, 0, 0xffffffff, 0xffffffff }, { 1, 0xffffffff, -1, -1, 0, 0, -1, -1 }, { 0, 0, 0xffffffff, 0xffffffff, 1, 0xfffffffe, -1, -1 }, { 0xffffffff, 0xffffffff, -1, -1, 0, 0xfffffffe, -1, -1 }, }; ARRAY_TYPE(seq_range) range = ARRAY_INIT; const struct seq_range *result; unsigned int count; test_begin("seq_range_array_invert() edges"); for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) T_BEGIN { t_array_init(&range, 10); if (tests[i].a_seq1 != -1) seq_range_array_add_range(&range, tests[i].a_seq1, tests[i].a_seq2); if (tests[i].b_seq1 != -1) seq_range_array_add_range(&range, tests[i].b_seq1, tests[i].b_seq2); seq_range_array_invert(&range, 0, 0xffffffff); result = array_get(&range, &count); if (tests[i].resa_seq1 == -1) test_assert_idx(count == 0, i); else { test_assert(result[0].seq1 == tests[i].resa_seq1); test_assert(result[0].seq2 == tests[i].resa_seq2); if (tests[i].resb_seq1 == -1) test_assert_idx(count == 1, i); else { test_assert(result[1].seq1 == tests[i].resb_seq1); test_assert(result[1].seq2 == tests[i].resb_seq2); } } } T_END; test_end(); } static void test_seq_range_create(ARRAY_TYPE(seq_range) *array, uint8_t byte) { unsigned int i; array_clear(array); for (i = 0; i < 8; i++) { if ((byte & (1 << i)) != 0) seq_range_array_add(array, i + 1); } } static void test_seq_range_array_have_common(void) { ARRAY_TYPE(seq_range) arr1, arr2; unsigned int i, j; bool ret1, ret2, success = TRUE; t_array_init(&arr1, 8); t_array_init(&arr2, 8); for (i = 0; i < 256; i++) { test_seq_range_create(&arr1, i); for (j = 0; j < 256; j++) { test_seq_range_create(&arr2, j); ret1 = seq_range_array_have_common(&arr1, &arr2); ret2 = (i & j) != 0; if (ret1 != ret2) success = FALSE; } } test_out("seq_range_array_have_common()", success); } void test_seq_range_array(void) { test_seq_range_array_add_boundaries(); test_seq_range_array_add_merge(); test_seq_range_array_merge_n(); test_seq_range_array_remove_nth(); test_seq_range_array_remove_range(); test_seq_range_array_invert(); test_seq_range_array_invert_edges(); test_seq_range_array_have_common(); test_seq_range_array_random(); } enum fatal_test_state fatal_seq_range_array(unsigned int stage) { ARRAY_TYPE(seq_range) arr; struct seq_range *range; t_array_init(&arr, 2); switch (stage) { case 0: test_begin("seq_range_array fatals"); test_expect_fatal_string("!seq_range_is_overflowed(array)"); seq_range_array_add_range(&arr, 0, (uint32_t)-1); return FATAL_TEST_FAILURE; case 1: seq_range_array_add_range(&arr, 1, (uint32_t)-1); test_expect_fatal_string("!seq_range_is_overflowed(array)"); seq_range_array_add(&arr, 0); return FATAL_TEST_FAILURE; case 2: seq_range_array_add_range(&arr, 0, (uint32_t)-2); test_expect_fatal_string("!seq_range_is_overflowed(array)"); seq_range_array_add(&arr, (uint32_t)-1); return FATAL_TEST_FAILURE; case 3: range = array_append_space(&arr); range->seq2 = (uint32_t)-1; test_expect_fatal_string("range->seq1 > 0 || range->seq2 < (uint32_t)-1"); i_error("This shouldn't return: %u", seq_range_count(&arr)); return FATAL_TEST_FAILURE; case 4: range = array_append_space(&arr); range->seq2 = (uint32_t)-2; test_assert(seq_range_count(&arr) == (uint32_t)-1); range = array_append_space(&arr); range->seq1 = (uint32_t)-2; range->seq2 = (uint32_t)-1; test_expect_fatal_string("UINT_MAX - seq_count >= seq_range_length(range)"); i_error("This shouldn't return: %u", seq_range_count(&arr)); return FATAL_TEST_FAILURE; } test_end(); return FATAL_TEST_FINISHED; } dovecot-2.3.21.1/src/lib/seq-range-array.h0000644000000000000000000000674314656633576015033 00000000000000#ifndef SEQ_RANGE_ARRAY_H #define SEQ_RANGE_ARRAY_H /* NOTE: A full 0..UINT_MAX sequence range isn't valid to use here, because its size would become UINT_MAX+1, which can't be returned by e.g. seq_range_count() and similar functions. Attempting to use such sequence ranges will result in assert-crash. */ struct seq_range { uint32_t seq1, seq2; }; ARRAY_DEFINE_TYPE(seq_range, struct seq_range); struct seq_range_iter { const ARRAY_TYPE(seq_range) *array; unsigned int prev_n, prev_idx; }; static inline uint32_t ATTR_PURE seq_range_length(const struct seq_range *range) { i_assert(range->seq2 >= range->seq1); i_assert(range->seq1 > 0 || range->seq2 < (uint32_t)-1); return range->seq2 - range->seq1 + 1; } /* Add sequence to range. If the array isn't created yet, create it with initial size of init_count. */ bool ATTR_NOWARN_UNUSED_RESULT seq_range_array_add(ARRAY_TYPE(seq_range) *array, uint32_t seq); /* Like seq_range_array_add(), but if the array isn't already initialized do it with i_array_init(). */ void seq_range_array_add_with_init(ARRAY_TYPE(seq_range) *array, unsigned int init_count, uint32_t seq); void seq_range_array_add_range(ARRAY_TYPE(seq_range) *array, uint32_t seq1, uint32_t seq2); unsigned int seq_range_array_add_range_count(ARRAY_TYPE(seq_range) *array, uint32_t seq1, uint32_t seq2); void seq_range_array_merge(ARRAY_TYPE(seq_range) *dest, const ARRAY_TYPE(seq_range) *src); /* Merge the first n sequences from src into dest. */ void seq_range_array_merge_n(ARRAY_TYPE(seq_range) *dest, const ARRAY_TYPE(seq_range) *src, unsigned int count); /* Remove the given sequence from range. Returns TRUE if it was found. */ bool ATTR_NOWARN_UNUSED_RESULT seq_range_array_remove(ARRAY_TYPE(seq_range) *array, uint32_t seq); /* Remove a sequence range. Returns number of sequences actually removed. */ unsigned int ATTR_NOWARN_UNUSED_RESULT seq_range_array_remove_range(ARRAY_TYPE(seq_range) *array, uint32_t seq1, uint32_t seq2); unsigned int ATTR_NOWARN_UNUSED_RESULT seq_range_array_remove_seq_range(ARRAY_TYPE(seq_range) *dest, const ARRAY_TYPE(seq_range) *src); /* Remove count number of sequences from the nth sequence (0 = first). */ void seq_range_array_remove_nth(ARRAY_TYPE(seq_range) *array, uint32_t n, uint32_t count); /* Remove sequences from dest that don't exist in src. */ unsigned int ATTR_NOWARN_UNUSED_RESULT seq_range_array_intersect(ARRAY_TYPE(seq_range) *dest, const ARRAY_TYPE(seq_range) *src); /* Returns TRUE if sequence exists in the range. */ bool seq_range_exists(const ARRAY_TYPE(seq_range) *array, uint32_t seq) ATTR_PURE; /* Returns TRUE if arrays have common sequences. */ bool seq_range_array_have_common(const ARRAY_TYPE(seq_range) *array1, const ARRAY_TYPE(seq_range) *array2) ATTR_PURE; /* Return number of sequences in the range. */ unsigned int seq_range_count(const ARRAY_TYPE(seq_range) *array) ATTR_PURE; /* Invert the sequence range. For example 5:6 -> min_seq:4,7:max_seq. The array must not have any sequences outside min_seq..max_seq or this function will assert-crash. */ void seq_range_array_invert(ARRAY_TYPE(seq_range) *array, uint32_t min_seq, uint32_t max_seq); void seq_range_array_iter_init(struct seq_range_iter *iter_r, const ARRAY_TYPE(seq_range) *array); /* Get the nth sequence (0 = first). Returns FALSE if idx is too large. */ bool seq_range_array_iter_nth(struct seq_range_iter *iter, unsigned int n, uint32_t *seq_r); #endif dovecot-2.3.21.1/src/lib/test-lib-event.c0000644000000000000000000000556514656633576014673 00000000000000/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ #include "test-lib.h" static void test_event_strlist(void) { test_begin("event strlist"); struct event *e1 = event_create(NULL); event_strlist_append(e1, "key", "s1"); event_strlist_append(e1, "key", "s2"); struct event *e2 = event_create(e1); event_strlist_append(e2, "key", "s3"); event_strlist_append(e2, "key", "s2"); test_assert_strcmp(event_find_field_recursive_str(e1, "key"), "s1,s2"); test_assert_strcmp(event_find_field_recursive_str(e2, "key"), "s3,s2,s1"); const char *new_strlist[] = { "new1", "new2", "new2", "s2" }; event_strlist_replace(e2, "key", new_strlist, N_ELEMENTS(new_strlist)); test_assert_strcmp(event_find_field_recursive_str(e2, "key"), "new1,new2,s2,s1"); struct event *e3 = event_create(NULL); event_strlist_copy_recursive(e3, e2, "key"); test_assert_strcmp(event_find_field_recursive_str(e3, "key"), "new1,new2,s2,s1"); event_unref(&e3); event_unref(&e1); event_unref(&e2); test_end(); } static void test_lib_event_reason_code(void) { test_begin("event reason codes"); test_assert_strcmp(event_reason_code("foo", "bar"), "foo:bar"); test_assert_strcmp(event_reason_code("foo", "B A-r"), "foo:b_a_r"); test_assert_strcmp(event_reason_code_prefix("foo", "x", "bar"), "foo:xbar"); test_assert_strcmp(event_reason_code_prefix("foo", "", "bar"), "foo:bar"); test_end(); } void test_lib_event(void) { test_event_strlist(); test_lib_event_reason_code(); } enum fatal_test_state fatal_lib_event(unsigned int stage) { switch (stage) { case 0: test_begin("event reason codes - asserts"); /* module: uppercase */ test_expect_fatal_string("Invalid module"); (void)event_reason_code("FOO", "bar"); return FATAL_TEST_FAILURE; case 1: /* module: space */ test_expect_fatal_string("Invalid module"); (void)event_reason_code("f oo", "bar"); return FATAL_TEST_FAILURE; case 2: /* module: - */ test_expect_fatal_string("Invalid module"); (void)event_reason_code("f-oo", "bar"); return FATAL_TEST_FAILURE; case 3: /* module: empty */ test_expect_fatal_string("module[0] != '\\0'"); (void)event_reason_code("", "bar"); return FATAL_TEST_FAILURE; case 4: /* name_prefix: uppercase */ test_expect_fatal_string("Invalid name_prefix"); (void)event_reason_code_prefix("module", "FOO", "bar"); return FATAL_TEST_FAILURE; case 5: /* name_prefix: space */ test_expect_fatal_string("Invalid name_prefix"); (void)event_reason_code_prefix("module", "f oo", "bar"); return FATAL_TEST_FAILURE; case 6: /* name_prefix: - */ test_expect_fatal_string("Invalid name_prefix"); (void)event_reason_code_prefix("module", "f-oo", "bar"); return FATAL_TEST_FAILURE; case 7: /* name: empty */ test_expect_fatal_string("(name[0] != '\\0')"); (void)event_reason_code("foo:", ""); return FATAL_TEST_FAILURE; default: test_end(); return FATAL_TEST_FINISHED; } } dovecot-2.3.21.1/src/lib/ioloop-kqueue.c0000644000000000000000000001075314656633576014622 00000000000000/* * BSD kqueue() based ioloop handler. * * Copyright (c) 2005 Vaclav Haisman */ #include "lib.h" #ifdef IOLOOP_KQUEUE #include "array.h" #include "sleep.h" #include "ioloop-private.h" #include #include #include #include #include /* kevent.udata's type just has to be different in NetBSD than in FreeBSD and OpenBSD.. */ #ifdef __NetBSD__ # define MY_EV_SET(a, b, c, d, e, f, g) \ EV_SET(a, b, c, d, e, f, (intptr_t)g) #else # define MY_EV_SET(a, b, c, d, e, f, g) \ EV_SET(a, b, c, d, e, f, g) #endif struct ioloop_handler_context { int kq; unsigned int deleted_count; ARRAY(struct kevent) events; }; void io_loop_handler_init(struct ioloop *ioloop, unsigned int initial_fd_count) { struct ioloop_handler_context *ctx; ioloop->handler_context = ctx = i_new(struct ioloop_handler_context, 1); ctx->kq = kqueue(); if (ctx->kq < 0) i_fatal("kqueue() in io_loop_handler_init() failed: %m"); fd_close_on_exec(ctx->kq, TRUE); i_array_init(&ctx->events, initial_fd_count); } void io_loop_handler_deinit(struct ioloop *ioloop) { if (close(ioloop->handler_context->kq) < 0) i_error("close(kqueue) in io_loop_handler_deinit() failed: %m"); array_free(&ioloop->handler_context->events); i_free(ioloop->handler_context); } void io_loop_handle_add(struct io_file *io) { struct ioloop_handler_context *ctx = io->io.ioloop->handler_context; struct kevent ev; if ((io->io.condition & (IO_READ | IO_ERROR)) != 0) { MY_EV_SET(&ev, io->fd, EVFILT_READ, EV_ADD, 0, 0, io); if (kevent(ctx->kq, &ev, 1, NULL, 0, NULL) < 0) i_panic("kevent(EV_ADD, READ, %d) failed: %m", io->fd); } if ((io->io.condition & IO_WRITE) != 0) { MY_EV_SET(&ev, io->fd, EVFILT_WRITE, EV_ADD, 0, 0, io); if (kevent(ctx->kq, &ev, 1, NULL, 0, NULL) < 0) i_panic("kevent(EV_ADD, WRITE, %d) failed: %m", io->fd); } /* allow kevent() to return the maximum number of events by keeping space allocated for each handle */ if (ctx->deleted_count > 0) ctx->deleted_count--; else array_append_zero(&ctx->events); } void io_loop_handle_remove(struct io_file *io, bool closed) { struct ioloop_handler_context *ctx = io->io.ioloop->handler_context; struct kevent ev; i_assert(io->io.condition != 0); if ((io->io.condition & (IO_READ | IO_ERROR)) != 0 && !closed) { MY_EV_SET(&ev, io->fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); if (kevent(ctx->kq, &ev, 1, NULL, 0, NULL) < 0) i_error("kevent(EV_DELETE, %d) failed: %m", io->fd); } if ((io->io.condition & IO_WRITE) != 0 && !closed) { MY_EV_SET(&ev, io->fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); if (kevent(ctx->kq, &ev, 1, NULL, 0, NULL) < 0) i_error("kevent(EV_DELETE, %d) failed: %m", io->fd); } io->io.condition = 0; /* since we're not freeing memory in any case, just increase deleted counter so next handle_add() can just decrease it instead of appending to the events array */ ctx->deleted_count++; i_assert(io->refcount > 0); if (--io->refcount == 0) i_free(io); } void io_loop_handler_run_internal(struct ioloop *ioloop) { struct ioloop_handler_context *ctx = ioloop->handler_context; struct kevent *events; const struct kevent *event; struct timeval tv; struct timespec ts; struct io_file *io; unsigned int events_count; int ret, i, msecs; /* get the time left for next timeout task */ msecs = io_loop_run_get_wait_time(ioloop, &tv); ts.tv_sec = tv.tv_sec; ts.tv_nsec = tv.tv_usec * 1000; /* wait for events */ events = array_get_modifiable(&ctx->events, &events_count); if (events_count > 0) { ret = kevent (ctx->kq, NULL, 0, events, events_count, &ts); if (ret < 0 && errno != EINTR) { i_panic("kevent(events=%u, ts=%ld.%u) failed: %m", events_count, (long)ts.tv_sec, (unsigned int)ts.tv_nsec); } } else { i_assert(msecs >= 0); i_sleep_intr_msecs(msecs); ret = 0; } /* reference all IOs */ for (i = 0; i < ret; i++) { io = (void *)events[i].udata; i_assert(io->refcount > 0); io->refcount++; } /* execute timeout handlers */ io_loop_handle_timeouts(ioloop); if (!ioloop->running) return; for (i = 0; i < ret; i++) { /* io_loop_handle_add() may cause events array reallocation, so we have use array_idx() */ event = array_idx(&ctx->events, i); io = (void *)event->udata; /* callback is NULL if io_remove() was already called */ if (io->io.callback != NULL) { io_loop_call_io(&io->io); if (!ioloop->running) break; } i_assert(io->refcount > 0); if (--io->refcount == 0) i_free(io); } } #endif dovecot-2.3.21.1/src/lib/iostream-temp.h0000644000000000000000000000233014656633576014607 00000000000000#ifndef IOSTREAM_TEMP_H #define IOSTREAM_TEMP_H enum iostream_temp_flags { /* if o_stream_send_istream() is called with a readable fd, don't actually copy the input stream, just have iostream_temp_finish() return a new iostream pointing to the fd dup()ed */ IOSTREAM_TEMP_FLAG_TRY_FD_DUP = 0x01 }; /* Start writing to given output stream. The data is initially written to memory, and later to a temporary file that is immediately unlinked. */ struct ostream *iostream_temp_create(const char *temp_path_prefix, enum iostream_temp_flags flags); struct ostream *iostream_temp_create_named(const char *temp_path_prefix, enum iostream_temp_flags flags, const char *name); struct ostream *iostream_temp_create_sized(const char *temp_path_prefix, enum iostream_temp_flags flags, const char *name, size_t max_mem_size); /* Finished writing to stream. Return input stream for it and free the output stream. (It's also possible to abort iostream-temp by simply destroying the ostream.) */ struct istream *iostream_temp_finish(struct ostream **output, size_t max_buffer_size); /* For internal testing: */ int o_stream_temp_move_to_memory(struct ostream *output); #endif dovecot-2.3.21.1/src/lib/iostream-private.h0000644000000000000000000000355614656633576015327 00000000000000#ifndef IOSTREAM_PRIVATE_H #define IOSTREAM_PRIVATE_H #include "iostream.h" /* This file is private to input stream and output stream implementations */ struct iostream_destroy_callback { void (*callback)(void *context); void *context; }; struct iostream_private { int refcount; char *name; char *error; struct ioloop *ioloop; void (*close)(struct iostream_private *streami, bool close_parent); void (*destroy)(struct iostream_private *stream); void (*set_max_buffer_size)(struct iostream_private *stream, size_t max_size); ARRAY(struct iostream_destroy_callback) destroy_callbacks; }; void io_stream_init(struct iostream_private *stream); void io_stream_ref(struct iostream_private *stream); bool io_stream_unref(struct iostream_private *stream); void io_stream_free(struct iostream_private *stream); void io_stream_close(struct iostream_private *stream, bool close_parent); void io_stream_set_max_buffer_size(struct iostream_private *stream, size_t max_size); void io_stream_add_destroy_callback(struct iostream_private *stream, void (*callback)(void *), void *context); void io_stream_remove_destroy_callback(struct iostream_private *stream, void (*callback)(void *)); /* Set a specific error for the stream. This shouldn't be used for regular syscall errors where stream's errno is enough, since it's used by default. The stream errno must always be set even if the error string is also set. Setting this error replaces the previously set error. */ void io_stream_set_error(struct iostream_private *stream, const char *fmt, ...) ATTR_FORMAT(2, 3); void io_stream_set_verror(struct iostream_private *stream, const char *fmt, va_list args) ATTR_FORMAT(2, 0); void io_stream_switch_ioloop_to(struct iostream_private *stream, struct ioloop *ioloop); struct ioloop *io_stream_get_ioloop(struct iostream_private *stream); #endif dovecot-2.3.21.1/src/lib/memarea.h0000644000000000000000000000207414656633576013435 00000000000000#ifndef MEMAREA_H #define MEMAREA_H typedef void memarea_free_callback_t(void *context); /* Create reference counted memory area. The callback is called when the refcount drops to 0. */ struct memarea * memarea_init(const void *data, size_t size, memarea_free_callback_t *callback, void *context); #define memarea_init(data, size, callback, context) \ memarea_init(data, size - \ CALLBACK_TYPECHECK(callback, void (*)(typeof(context))), \ (memarea_free_callback_t *)callback, context) /* Returns an empty memory area. */ struct memarea *memarea_init_empty(void); void memarea_ref(struct memarea *area); void memarea_unref(struct memarea **area); /* Free the memory area without calling the callback. This is allowed only when refcount==1. */ void memarea_free_without_callback(struct memarea **area); unsigned int memarea_get_refcount(struct memarea *area); const void *memarea_get(struct memarea *area, size_t *size_r); size_t memarea_get_size(struct memarea *area); /* free-callback that does nothing */ void memarea_free_callback_noop(void *context); #endif dovecot-2.3.21.1/src/lib/sleep.h0000644000000000000000000000245114656633576013135 00000000000000#ifndef SLEEP_H #define SLEEP_H /* Sleep for the indicated number of microseconds. Signal interruptions are handled and ignored internally. */ void i_sleep_usecs(unsigned long long usecs); /* Sleep for the indicated number of milliseconds. Signal interruptions are handled and ignored internally. */ void i_sleep_msecs(unsigned int msecs); /* Sleep for the indicated number of seconds. Signal interruptions are handled and ignored internally. */ void i_sleep_secs(time_t secs); /* Sleep for the indicated number of microseconds while allowing signal interruptions. This function returns FALSE when it is interrupted by a signal. Otherwise, this function always returns TRUE. */ bool ATTR_NOWARN_UNUSED_RESULT i_sleep_intr_usecs(unsigned long long usecs); /* Sleep for the indicated number of milliseconds while allowing signal interruptions. This function returns FALSE when it is interrupted by a signal. Otherwise, this function always returns TRUE. */ bool ATTR_NOWARN_UNUSED_RESULT i_sleep_intr_msecs(unsigned int msecs); /* Sleep for the indicated number of seconds while allowing signal interruptions. This function returns FALSE when it is interrupted by a signal. Otherwise, this function always returns TRUE. */ bool ATTR_NOWARN_UNUSED_RESULT i_sleep_intr_secs(time_t secs); #endif dovecot-2.3.21.1/src/lib/net.h0000644000000000000000000001732614656633576012622 00000000000000#ifndef NET_H #define NET_H #ifndef WIN32 # include # include # include # include #endif #ifdef HAVE_SOCKS_H #include #endif #ifndef AF_INET6 # ifdef PF_INET6 # define AF_INET6 PF_INET6 # else # define AF_INET6 10 # endif #endif struct ip_addr { unsigned short family; union { struct in6_addr ip6; struct in_addr ip4; } u; }; ARRAY_DEFINE_TYPE(ip_addr, struct ip_addr); struct net_unix_cred { uid_t uid; gid_t gid; pid_t pid; }; /* maximum string length of IP address */ #define MAX_IP_LEN INET6_ADDRSTRLEN #define IPADDR_IS_V4(ip) ((ip)->family == AF_INET) #define IPADDR_IS_V6(ip) ((ip)->family == AF_INET6) #define IPADDR_BITS(ip) (IPADDR_IS_V4(ip) ? 32 : 128) enum net_listen_flags { /* Try to use SO_REUSEPORT if available. If it's not, this flag is cleared on return. */ NET_LISTEN_FLAG_REUSEPORT = 0x01 }; enum net_hosterror_type { /* Internal error - should be logged as an error */ NET_HOSTERROR_TYPE_INTERNAL_ERROR, /* Host not found or no valid IP addresses found */ NET_HOSTERROR_TYPE_NOT_FOUND, /* Nameserver returned an error */ NET_HOSTERROR_TYPE_NAMESERVER, }; /* INADDR_ANY for IPv4 or IPv6. The IPv6 any address may include IPv4 depending on the system (Linux yes, BSD no). */ extern const struct ip_addr net_ip4_any; extern const struct ip_addr net_ip6_any; extern const struct ip_addr net_ip4_loopback; extern const struct ip_addr net_ip6_loopback; /* Returns TRUE if IPs are the same */ bool net_ip_compare(const struct ip_addr *ip1, const struct ip_addr *ip2); /* Returns 0 if IPs are the same, -1 or 1 otherwise. */ int net_ip_cmp(const struct ip_addr *ip1, const struct ip_addr *ip2); unsigned int net_ip_hash(const struct ip_addr *ip); /* Connect to TCP socket with ip address. The socket and connect() is non-blocking. */ int net_connect_ip(const struct ip_addr *ip, in_port_t port, const struct ip_addr *my_ip) ATTR_NULL(3); /* Like net_connect_ip(), but do a blocking connect(). */ int net_connect_ip_blocking(const struct ip_addr *ip, in_port_t port, const struct ip_addr *my_ip) ATTR_NULL(3); /* Like net_connect_ip(), but open a UDP socket. */ int net_connect_udp(const struct ip_addr *ip, in_port_t port, const struct ip_addr *my_ip); /* Returns 0 if we can bind() as given IP, -1 if not. */ int net_try_bind(const struct ip_addr *ip); /* Connect to named UNIX socket */ int net_connect_unix(const char *path); /* Try to connect to UNIX socket for give number of seconds when connect() returns EAGAIN or ECONNREFUSED. */ int net_connect_unix_with_retries(const char *path, unsigned int msecs); /* Disconnect socket */ void net_disconnect(int fd); /* Set socket blocking/nonblocking */ void net_set_nonblock(int fd, bool nonblock); /* Set TCP_CORK if supported, ie. don't send out partial frames. Returns 0 if ok, -1 if failed. */ int net_set_cork(int fd, bool cork) ATTR_NOWARN_UNUSED_RESULT; /* Set TCP_NODELAY, which disables the Nagle algorithm. */ int net_set_tcp_nodelay(int fd, bool nodelay); /* Set TCP_QUICKACK, which tells the kernel to not delay ACKs. Note that the kernel can (and will) re-enable delayed ACKs while processing the TCP stack. This means that this function needs to be called repeatedly. */ int net_set_tcp_quickack(int fd, bool quickack); /* Set socket kernel buffer sizes */ int net_set_send_buffer_size(int fd, size_t size); int net_set_recv_buffer_size(int fd, size_t size); /* Listen for connections on a socket */ int net_listen(const struct ip_addr *my_ip, in_port_t *port, int backlog); int net_listen_full(const struct ip_addr *my_ip, in_port_t *port, enum net_listen_flags *flags, int backlog); /* Listen for connections on an UNIX socket */ int net_listen_unix(const char *path, int backlog); /* Like net_listen_unix(), but if socket already exists, try to connect to it. If it fails with ECONNREFUSED, unlink the socket and try creating it again. */ int net_listen_unix_unlink_stale(const char *path, int backlog); /* Accept a connection on a socket. Returns -1 if the connection got closed, -2 for other failures. For UNIX sockets addr_r->family=port=0. */ int net_accept(int fd, struct ip_addr *addr_r, in_port_t *port_r) ATTR_NULL(2, 3); /* Read data from socket, return number of bytes read, -1 = error, -2 = disconnected */ ssize_t net_receive(int fd, void *buf, size_t len); /* Get IP addresses for host. ips contains ips_count of IPs, they don't need to be free'd. Returns 0 = ok, others = error code for net_gethosterror() */ int net_gethostbyname(const char *addr, struct ip_addr **ips, unsigned int *ips_count); /* Return host for the IP address. Returns 0 = ok, others = error code for net_gethosterror(). */ int net_gethostbyaddr(const struct ip_addr *ip, const char **name_r); /* get error of net_gethostname() */ const char *net_gethosterror(int error) ATTR_CONST; /* Return type of the error returned by net_gethostname() */ enum net_hosterror_type net_get_hosterror_type(int error); /* return TRUE if host lookup failed because it didn't exist (ie. not some error with name server) */ int net_hosterror_notfound(int error) ATTR_CONST; /* Get socket local address/port. For UNIX sockets addr->family=port=0. */ int net_getsockname(int fd, struct ip_addr *addr, in_port_t *port) ATTR_NULL(2, 3); /* Get socket remote address/port. For UNIX sockets addr->family=port=0. */ int net_getpeername(int fd, struct ip_addr *addr, in_port_t *port) ATTR_NULL(2, 3); /* Get UNIX socket name. */ int net_getunixname(int fd, const char **name_r); /* Get UNIX socket peer process's credentials. The pid may be (pid_t)-1 if unavailable. */ int net_getunixcred(int fd, struct net_unix_cred *cred_r); /* Returns ip_addr as string, or "" if ip isn't valid IPv4 or IPv6 address. */ const char *net_ip2addr(const struct ip_addr *ip); /* char* -> struct ip_addr translation. */ int net_addr2ip(const char *addr, struct ip_addr *ip); /* char* -> in_port_t translation */ int net_str2port(const char *str, in_port_t *port_r); /* char* -> in_port_t translation (allows port zero) */ int net_str2port_zero(const char *str, in_port_t *port_r); /* Parse "host", "host:port", "IPv4", "IPv4:port", "IPv6", "[IPv6]" or "[IPv6]:port" to its host and port components. [IPv6] address is returned without []. If no port is given, return default_port. The :port in the parsed string isn't allowed to be zero, but default_port=0 is passed through. */ int net_str2hostport(const char *str, in_port_t default_port, const char **host_r, in_port_t *port_r); /* Converts ip and port to ipv4:port or [ipv6]:port. Returns -1 if ip is not valid IPv4 or IPv6 address. */ int net_ipport2str(const struct ip_addr *ip, in_port_t port, const char **str_r); /* Convert IPv6 mapped IPv4 address to an actual IPv4 address. Returns 0 if successful, -1 if the source address isn't IPv6 mapped IPv4 address. */ int net_ipv6_mapped_ipv4_convert(const struct ip_addr *src, struct ip_addr *dest); /* Get socket error */ int net_geterror(int fd); /* Get name of TCP service */ const char *net_getservbyport(in_port_t port) ATTR_CONST; bool is_ipv4_address(const char *addr) ATTR_PURE; bool is_ipv6_address(const char *addr) ATTR_PURE; /* Parse network as ip/bits. Returns 0 if successful, -1 if invalid input. */ int net_parse_range(const char *network, struct ip_addr *ip_r, unsigned int *bits_r); /* Returns TRUE if ip is in net_ip/bits network. IPv4-mapped IPv6 addresses in "ip" parameter are converted to plain IPv4 addresses before matching. No conversion is done to net_ip though, so using IPv4-mapped IPv6 addresses there will always fail. Invalid IPs (family=0) never match anything. */ bool net_is_in_network(const struct ip_addr *ip, const struct ip_addr *net_ip, unsigned int bits) ATTR_PURE; #endif dovecot-2.3.21.1/src/lib/numpack.c0000644000000000000000000000177114656633576013462 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "numpack.h" void numpack_encode(buffer_t *buf, uint64_t num) { /* number continues as long as the highest bit is set */ while (num >= 0x80) { buffer_append_c(buf, (num & 0x7f) | 0x80); num >>= 7; } buffer_append_c(buf, num); } int numpack_decode(const uint8_t **p, const uint8_t *end, uint64_t *num_r) ATTR_UNSIGNED_WRAPS { const uint8_t *c = *p; uint64_t value = 0; unsigned int bits = 0; while (bits < 64) { if (c == end) return -1; value |= (uint64_t)(*c & 0x7f) << bits; if (*c < 0x80) break; bits += 7; c++; } bits += bits_required8(*c); if (bits > 64) /* overflow */ return -1; *p = c + 1; *num_r = value; return 0; } int numpack_decode32(const uint8_t **p, const uint8_t *end, uint32_t *num_r) { uint64_t num; if (numpack_decode(p, end, &num) < 0) return -1; if (num > 4294967295U) return -1; *num_r = (uint32_t)num; return 0; } dovecot-2.3.21.1/src/lib/unlink-old-files.h0000644000000000000000000000052114656633576015175 00000000000000#ifndef UNLINK_OLD_FILES_H #define UNLINK_OLD_FILES_H /* Unlink all files from directory beginning with given prefix and having ctime older than min_time. Makes sure that the directory's atime is updated. Returns -1 if there were some errors. */ int unlink_old_files(const char *dir, const char *prefix, time_t min_time); #endif dovecot-2.3.21.1/src/lib/sha1.c0000644000000000000000000002232714656633576012660 00000000000000/* $KAME: sha1.c,v 1.5 2000/11/08 06:13:08 itojun Exp $ */ /* * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * FIPS pub 180-1: Secure Hash Algorithm (SHA-1) * based on: http://csrc.nist.gov/fips/fip180-1.txt * implemented by Jun-ichiro itojun Itoh */ #include "lib.h" #include "sha1.h" #include "safe-memset.h" /* constant table */ static uint32_t SHA1_K[] = { 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 }; #define K(t) SHA1_K[(t) / 20] #define F0(b, c, d) (((b) & (c)) | ((~(b)) & (d))) #define F1(b, c, d) (((b) ^ (c)) ^ (d)) #define F2(b, c, d) (((b) & (c)) | ((b) & (d)) | ((c) & (d))) #define F3(b, c, d) (((b) ^ (c)) ^ (d)) #define S(n, x) (((x) << (n)) | ((x) >> (32 - n))) #define H(n) (ctxt->h.b32[(n)]) #define COUNT (ctxt->count) #define BCOUNT (ctxt->c.b64[0] / 8) #define W(n) (ctxt->m.b32[(n)]) #define PUTBYTE(x) { \ ctxt->m.b8[(COUNT % 64)] = (x); \ COUNT++; \ COUNT %= 64; \ ctxt->c.b64[0] += 8; \ if (COUNT % 64 == 0) \ sha1_step(ctxt); \ } #define PUTPAD(x) { \ ctxt->m.b8[(COUNT % 64)] = (x); \ COUNT++; \ COUNT %= 64; \ if (COUNT % 64 == 0) \ sha1_step(ctxt); \ } static void sha1_step(struct sha1_ctxt *); static void ATTR_UNSIGNED_WRAPS sha1_step(struct sha1_ctxt *ctxt) { uint32_t a, b, c, d, e; size_t t, s; uint32_t tmp; #ifndef WORDS_BIGENDIAN struct sha1_ctxt tctxt; memmove(&tctxt.m.b8[0], &ctxt->m.b8[0], 64); ctxt->m.b8[0] = tctxt.m.b8[3]; ctxt->m.b8[1] = tctxt.m.b8[2]; ctxt->m.b8[2] = tctxt.m.b8[1]; ctxt->m.b8[3] = tctxt.m.b8[0]; ctxt->m.b8[4] = tctxt.m.b8[7]; ctxt->m.b8[5] = tctxt.m.b8[6]; ctxt->m.b8[6] = tctxt.m.b8[5]; ctxt->m.b8[7] = tctxt.m.b8[4]; ctxt->m.b8[8] = tctxt.m.b8[11]; ctxt->m.b8[9] = tctxt.m.b8[10]; ctxt->m.b8[10] = tctxt.m.b8[9]; ctxt->m.b8[11] = tctxt.m.b8[8]; ctxt->m.b8[12] = tctxt.m.b8[15]; ctxt->m.b8[13] = tctxt.m.b8[14]; ctxt->m.b8[14] = tctxt.m.b8[13]; ctxt->m.b8[15] = tctxt.m.b8[12]; ctxt->m.b8[16] = tctxt.m.b8[19]; ctxt->m.b8[17] = tctxt.m.b8[18]; ctxt->m.b8[18] = tctxt.m.b8[17]; ctxt->m.b8[19] = tctxt.m.b8[16]; ctxt->m.b8[20] = tctxt.m.b8[23]; ctxt->m.b8[21] = tctxt.m.b8[22]; ctxt->m.b8[22] = tctxt.m.b8[21]; ctxt->m.b8[23] = tctxt.m.b8[20]; ctxt->m.b8[24] = tctxt.m.b8[27]; ctxt->m.b8[25] = tctxt.m.b8[26]; ctxt->m.b8[26] = tctxt.m.b8[25]; ctxt->m.b8[27] = tctxt.m.b8[24]; ctxt->m.b8[28] = tctxt.m.b8[31]; ctxt->m.b8[29] = tctxt.m.b8[30]; ctxt->m.b8[30] = tctxt.m.b8[29]; ctxt->m.b8[31] = tctxt.m.b8[28]; ctxt->m.b8[32] = tctxt.m.b8[35]; ctxt->m.b8[33] = tctxt.m.b8[34]; ctxt->m.b8[34] = tctxt.m.b8[33]; ctxt->m.b8[35] = tctxt.m.b8[32]; ctxt->m.b8[36] = tctxt.m.b8[39]; ctxt->m.b8[37] = tctxt.m.b8[38]; ctxt->m.b8[38] = tctxt.m.b8[37]; ctxt->m.b8[39] = tctxt.m.b8[36]; ctxt->m.b8[40] = tctxt.m.b8[43]; ctxt->m.b8[41] = tctxt.m.b8[42]; ctxt->m.b8[42] = tctxt.m.b8[41]; ctxt->m.b8[43] = tctxt.m.b8[40]; ctxt->m.b8[44] = tctxt.m.b8[47]; ctxt->m.b8[45] = tctxt.m.b8[46]; ctxt->m.b8[46] = tctxt.m.b8[45]; ctxt->m.b8[47] = tctxt.m.b8[44]; ctxt->m.b8[48] = tctxt.m.b8[51]; ctxt->m.b8[49] = tctxt.m.b8[50]; ctxt->m.b8[50] = tctxt.m.b8[49]; ctxt->m.b8[51] = tctxt.m.b8[48]; ctxt->m.b8[52] = tctxt.m.b8[55]; ctxt->m.b8[53] = tctxt.m.b8[54]; ctxt->m.b8[54] = tctxt.m.b8[53]; ctxt->m.b8[55] = tctxt.m.b8[52]; ctxt->m.b8[56] = tctxt.m.b8[59]; ctxt->m.b8[57] = tctxt.m.b8[58]; ctxt->m.b8[58] = tctxt.m.b8[57]; ctxt->m.b8[59] = tctxt.m.b8[56]; ctxt->m.b8[60] = tctxt.m.b8[63]; ctxt->m.b8[61] = tctxt.m.b8[62]; ctxt->m.b8[62] = tctxt.m.b8[61]; ctxt->m.b8[63] = tctxt.m.b8[60]; #endif a = H(0); b = H(1); c = H(2); d = H(3); e = H(4); for (t = 0; t < 20; t++) { s = t & 0x0f; if (t >= 16) { W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ W((s+2) & 0x0f) ^ W(s)); } tmp = S(5, a) + F0(b, c, d) + e + W(s) + K(t); e = d; d = c; c = S(30, b); b = a; a = tmp; } for (t = 20; t < 40; t++) { s = t & 0x0f; W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ W((s+2) & 0x0f) ^ W(s)); tmp = S(5, a) + F1(b, c, d) + e + W(s) + K(t); e = d; d = c; c = S(30, b); b = a; a = tmp; } for (t = 40; t < 60; t++) { s = t & 0x0f; W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ W((s+2) & 0x0f) ^ W(s)); tmp = S(5, a) + F2(b, c, d) + e + W(s) + K(t); e = d; d = c; c = S(30, b); b = a; a = tmp; } for (t = 60; t < 80; t++) { s = t & 0x0f; W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ W((s+2) & 0x0f) ^ W(s)); tmp = S(5, a) + F3(b, c, d) + e + W(s) + K(t); e = d; d = c; c = S(30, b); b = a; a = tmp; } H(0) = H(0) + a; H(1) = H(1) + b; H(2) = H(2) + c; H(3) = H(3) + d; H(4) = H(4) + e; memset(&ctxt->m.b8[0], 0, 64); } /*------------------------------------------------------------*/ void sha1_init(struct sha1_ctxt *ctxt) { memset(ctxt, 0, sizeof(struct sha1_ctxt)); H(0) = 0x67452301; H(1) = 0xefcdab89; H(2) = 0x98badcfe; H(3) = 0x10325476; H(4) = 0xc3d2e1f0; } void sha1_pad(struct sha1_ctxt *ctxt) { size_t padlen; /*pad length in bytes*/ size_t padstart; PUTPAD(0x80); padstart = COUNT % 64; padlen = 64 - padstart; if (padlen < 8) { memset(&ctxt->m.b8[padstart], 0, padlen); COUNT += padlen; COUNT %= 64; sha1_step(ctxt); padstart = COUNT % 64; /* should be 0 */ padlen = 64 - padstart; /* should be 64 */ } memset(&ctxt->m.b8[padstart], 0, padlen - 8); COUNT += (padlen - 8); COUNT %= 64; #ifdef WORDS_BIGENDIAN PUTPAD(ctxt->c.b8[0]); PUTPAD(ctxt->c.b8[1]); PUTPAD(ctxt->c.b8[2]); PUTPAD(ctxt->c.b8[3]); PUTPAD(ctxt->c.b8[4]); PUTPAD(ctxt->c.b8[5]); PUTPAD(ctxt->c.b8[6]); PUTPAD(ctxt->c.b8[7]); #else PUTPAD(ctxt->c.b8[7]); PUTPAD(ctxt->c.b8[6]); PUTPAD(ctxt->c.b8[5]); PUTPAD(ctxt->c.b8[4]); PUTPAD(ctxt->c.b8[3]); PUTPAD(ctxt->c.b8[2]); PUTPAD(ctxt->c.b8[1]); PUTPAD(ctxt->c.b8[0]); #endif } void sha1_loop(struct sha1_ctxt *ctxt, const void *input, size_t len) { const unsigned char *input_c = input; size_t gaplen; size_t gapstart; size_t off; size_t copysiz; off = 0; while (off < len) { gapstart = COUNT % 64; gaplen = 64 - gapstart; copysiz = (gaplen < len - off) ? gaplen : len - off; memmove(&ctxt->m.b8[gapstart], &input_c[off], copysiz); COUNT += copysiz; COUNT %= 64; ctxt->c.b64[0] += copysiz * 8; if (COUNT % 64 == 0) sha1_step(ctxt); off += copysiz; } } void sha1_result(struct sha1_ctxt *ctxt, void *digest0) { uint8_t *digest; digest = (uint8_t *)digest0; sha1_pad(ctxt); #ifdef WORDS_BIGENDIAN memmove(digest, &ctxt->h.b8[0], 20); #else digest[0] = ctxt->h.b8[3]; digest[1] = ctxt->h.b8[2]; digest[2] = ctxt->h.b8[1]; digest[3] = ctxt->h.b8[0]; digest[4] = ctxt->h.b8[7]; digest[5] = ctxt->h.b8[6]; digest[6] = ctxt->h.b8[5]; digest[7] = ctxt->h.b8[4]; digest[8] = ctxt->h.b8[11]; digest[9] = ctxt->h.b8[10]; digest[10] = ctxt->h.b8[9]; digest[11] = ctxt->h.b8[8]; digest[12] = ctxt->h.b8[15]; digest[13] = ctxt->h.b8[14]; digest[14] = ctxt->h.b8[13]; digest[15] = ctxt->h.b8[12]; digest[16] = ctxt->h.b8[19]; digest[17] = ctxt->h.b8[18]; digest[18] = ctxt->h.b8[17]; digest[19] = ctxt->h.b8[16]; #endif safe_memset(ctxt, 0, sizeof(struct sha1_ctxt)); } void sha1_get_digest(const void *data, size_t size, unsigned char result[STATIC_ARRAY SHA1_RESULTLEN]) { struct sha1_ctxt ctx; sha1_init(&ctx); sha1_loop(&ctx, data, size); sha1_result(&ctx, result); } static void hash_method_init_sha1(void *context) { sha1_init(context); } static void hash_method_loop_sha1(void *context, const void *data, size_t size) { sha1_loop(context, data, size); } static void hash_method_result_sha1(void *context, unsigned char *result_r) { sha1_result(context, result_r); } const struct hash_method hash_method_sha1 = { .name = "sha1", .block_size = 64, /* block size is 512 bits */ .context_size = sizeof(struct sha1_ctxt), .digest_size = SHA1_RESULTLEN, .init = hash_method_init_sha1, .loop = hash_method_loop_sha1, .result = hash_method_result_sha1, }; dovecot-2.3.21.1/src/lib/test-guid.c0000644000000000000000000001765614656633576013742 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "guid.h" #include "ioloop.h" /* * We want earlier timestamps to compare as < with later timestamps, but * guid_128_cmp() doesn't do that because the timestamps in the guid are * stored in little-endian byte order. */ static int reverse_guid_128_cmp(const guid_128_t a, const guid_128_t b) { int i; for (i = GUID_128_SIZE - 1; i >= 0; i--) if (a[i] != b[i]) return (int)a[i] - (int)b[i]; return 0; } static bool guid_128_has_sane_nsecs(const guid_128_t g) { unsigned long nsecs; nsecs = le32_to_cpu_unaligned(g); return nsecs < 1000000000UL; } static inline void set_fake_time(time_t sec, long usec) { ioloop_timeval.tv_sec = sec; ioloop_timeval.tv_usec = usec; } /* * We muck with the ioloop_timeval in various ways and make sure that the * guids that get generated make sense. To make sure that the guid * generation code takes up our faked timestamp, we use a far-away time (Jan * 1 2038) as the base time. We don't want to go beyond 32-bit signed * time_t for the base time to avoid issues on systems with 32-bit signed * time_t. * * While guids really only need to be unique, here we actually enforce that * they are increasing (as defined by reverse_guid_128_cmp()). If guids are * always increasing, they will always be unique. */ static void test_ioloop_guid_128_generate(void) { const time_t basetime = 2145909600; /* Jan 1 2038 */ struct timeval saved_ioloop_timeval; guid_128_t guids[2]; int i; /* save the ioloop_timeval before we start messing with it */ saved_ioloop_timeval = ioloop_timeval; /* * Generating multiple guids within a microsecond should keep * incrementing them. */ test_begin("guid_128_generate() increasing guid within a usec"); set_fake_time(basetime, 0); guid_128_generate(guids[1]); for (i = 0; i < 10; i++) { const int this = i % 2; const int prev = 1 - this; guid_128_generate(guids[this]); test_assert(reverse_guid_128_cmp(guids[prev], guids[this]) < 0); test_assert(guid_128_has_sane_nsecs(guids[this])); } test_end(); /* * If the current time changes by +1 usec, so should the guids. */ test_begin("guid_128_generate() increasing guid with usec fast-forward"); for (i = 0; i < 10; i++) { const int this = i % 2; const int prev = 1 - this; set_fake_time(basetime, 1 + i); guid_128_generate(guids[this]); test_assert(reverse_guid_128_cmp(guids[prev], guids[this]) < 0); test_assert(guid_128_has_sane_nsecs(guids[this])); } test_end(); /* * If the current time changes by +1 sec, so should the guids. */ test_begin("guid_128_generate() increasing guid with sec fast-forward"); for (i = 0; i < 10; i++) { const int this = i % 2; const int prev = 1 - this; set_fake_time(basetime + 1 + i, 0); guid_128_generate(guids[this]); test_assert(reverse_guid_128_cmp(guids[prev], guids[this]) < 0); test_assert(guid_128_has_sane_nsecs(guids[this])); } test_end(); /* * Requesting enough guids should increment the seconds but always * produce valid nsecs. * * (Set a time that leaves us 1000 guids before seconds overflow and * then ask for 2500 guids.) */ test_begin("guid_128_generate() proper guid nsec overflow"); set_fake_time(basetime + 11, 999999L); for (i = 0; i < 2500; i++) { const int this = i % 2; const int prev = 1 - this; guid_128_generate(guids[this]); test_assert(reverse_guid_128_cmp(guids[prev], guids[this]) < 0); test_assert(guid_128_has_sane_nsecs(guids[this])); } test_end(); /* * When ahead by 1500 guids (see previous test), +1 usec shouldn't * have any effect. */ test_begin("guid_128_generate() no effect with increasing time when ahead"); set_fake_time(basetime + 12, 0); guid_128_generate(guids[0]); test_assert(reverse_guid_128_cmp(guids[1], guids[0]) < 0); test_assert(guid_128_has_sane_nsecs(guids[0])); test_end(); /* not a test - just set a more convenient time */ set_fake_time(basetime + 15, 500); guid_128_generate(guids[1]); test_assert(reverse_guid_128_cmp(guids[0], guids[1]) < 0); test_assert(guid_128_has_sane_nsecs(guids[1])); /* * Time going backwards by 1 usec should have no effect on guids. */ test_begin("guid_128_generate() usec time-travel still increasing"); set_fake_time(basetime + 15, 499); guid_128_generate(guids[0]); test_assert(reverse_guid_128_cmp(guids[1], guids[0]) < 0); test_assert(guid_128_has_sane_nsecs(guids[0])); test_end(); /* * Time going backwards by 1 sec should have no effect on guids. */ test_begin("guid_128_generate() sec time-travel still increasing"); set_fake_time(basetime + 14, 499); guid_128_generate(guids[1]); test_assert(reverse_guid_128_cmp(guids[0], guids[1]) < 0); test_assert(guid_128_has_sane_nsecs(guids[1])); test_end(); /* restore the previously saved value just in case */ ioloop_timeval = saved_ioloop_timeval; } void test_guid(void) { static const guid_128_t test_guid = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xAB, 0xCD, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00 }; guid_128_t guid1, guid2, guid3; const char *str; char guidbuf[GUID_128_SIZE*2 + 2]; unsigned int i; test_begin("guid_128_generate()"); guid_128_generate(guid1); guid_128_generate(guid2); test_assert(!guid_128_equals(guid1, guid2)); test_assert(guid_128_cmp(guid1, guid2) != 0); test_end(); test_begin("guid_128_is_empty()"); test_assert(!guid_128_is_empty(guid1)); test_assert(!guid_128_is_empty(guid2)); guid_128_generate(guid3); guid_128_empty(guid3); test_assert(guid_128_is_empty(guid3)); test_end(); test_begin("guid_128_copy()"); guid_128_copy(guid3, guid1); test_assert(guid_128_equals(guid3, guid1)); test_assert(!guid_128_equals(guid3, guid2)); guid_128_copy(guid3, guid2); test_assert(!guid_128_equals(guid3, guid1)); test_assert(guid_128_equals(guid3, guid2)); test_end(); test_begin("guid_128_to_string()"); str = guid_128_to_string(guid1); test_assert(guid_128_from_string(str, guid3) == 0); test_assert(guid_128_equals(guid3, guid1)); test_end(); test_begin("guid_128_from_string()"); /* empty */ memset(guidbuf, '0', GUID_128_SIZE*2); guidbuf[GUID_128_SIZE*2] = '\0'; guidbuf[GUID_128_SIZE*2+1] = '\0'; test_assert(guid_128_from_string(guidbuf, guid3) == 0); test_assert(guid_128_is_empty(guid3)); /* too large */ guidbuf[GUID_128_SIZE*2] = '0'; test_assert(guid_128_from_string(guidbuf, guid3) < 0); /* too small */ guidbuf[GUID_128_SIZE*2-1] = '\0'; test_assert(guid_128_from_string(guidbuf, guid3) < 0); /* reset to normal */ guidbuf[GUID_128_SIZE*2-1] = '0'; guidbuf[GUID_128_SIZE*2] = '\0'; test_assert(guid_128_from_string(guidbuf, guid3) == 0); /* upper + lowercase hex chars */ i_assert(GUID_128_SIZE*2 > 16 + 6); for (i = 0; i < 10; i++) guidbuf[i] = '0' + i; for (i = 0; i < 6; i++) guidbuf[10 + i] = 'a' + i; for (i = 0; i < 6; i++) guidbuf[16 + i] = 'A' + i; test_assert(guid_128_from_string(guidbuf, guid3) == 0); test_assert(guid_128_equals(guid3, test_guid)); /* non-hex chars */ guidbuf[0] = 'g'; test_assert(guid_128_from_string(guidbuf, guid3) < 0); guidbuf[0] = ' '; test_assert(guid_128_from_string(guidbuf, guid3) < 0); test_assert(guid_128_from_uuid_string("fee0ceac-0327-11e7-ad39-52540078f374", guid3) == 0); test_assert(guid_128_from_uuid_string("fee0ceac032711e7ad3952540078f374", guid2) == 0); test_assert(guid_128_cmp(guid3, guid2) == 0); test_assert(guid_128_from_uuid_string("{fee0ceac-0327-11e7-ad39-52540078f374}", guid2) == 0); test_assert(guid_128_cmp(guid3, guid2) == 0); test_assert(strcmp(guid_128_to_uuid_string(guid3, FORMAT_RECORD), "fee0ceac-0327-11e7-ad39-52540078f374")==0); test_assert(strcmp(guid_128_to_uuid_string(guid3, FORMAT_COMPACT), "fee0ceac032711e7ad3952540078f374")==0); test_assert(strcmp(guid_128_to_uuid_string(guid3, FORMAT_MICROSOFT), "{fee0ceac-0327-11e7-ad39-52540078f374}")==0); /* failure test */ test_assert(guid_128_from_uuid_string("fe-e0ceac-0327-11e7-ad39-52540078f374", guid3) < 0); test_end(); test_ioloop_guid_128_generate(); } dovecot-2.3.21.1/src/lib/test-backtrace.c0000644000000000000000000000352114656633576014713 00000000000000#include "test-lib.h" #include "str.h" #include "backtrace-string.h" static void test_backtrace_append(void) { test_begin("backtrace_append"); string_t *bt = t_str_new(128); #if (defined(HAVE_LIBUNWIND)) test_assert(backtrace_append(bt) == 0); /* Check that there's a usable function in the backtrace. Note that this function may be inlined, so don't check for test_backtrace_get() */ test_assert(strstr(str_c(bt), "test_backtrace") != NULL); /* make sure the backtrace_append is not */ test_assert(strstr(str_c(bt), " backtrace_append") == NULL); #elif (defined(HAVE_BACKTRACE_SYMBOLS) && defined(HAVE_EXECINFO_H)) || \ (defined(HAVE_WALKCONTEXT) && defined(HAVE_UCONTEXT_H)) test_assert(backtrace_append(bt) == 0); /* it should have some kind of main in it */ test_assert(strstr(str_c(bt), "main") != NULL); #else /* should not work in this context */ test_assert(backtrace_append(bt) == -1); #endif test_end(); } static void test_backtrace_get(void) { test_begin("backtrace_get"); const char *bt = NULL; #if (defined(HAVE_LIBUNWIND)) test_assert(backtrace_get(&bt) == 0); /* Check that there's a usable function in the backtrace. Note that this function may be inlined, so don't check for test_backtrace_get() */ test_assert(strstr(bt, "test_backtrace") != NULL); /* make sure the backtrace_get is not */ test_assert(strstr(bt, " backtrace_get") == NULL); #elif (defined(HAVE_BACKTRACE_SYMBOLS) && defined(HAVE_EXECINFO_H)) || \ (defined(HAVE_WALKCONTEXT) && defined(HAVE_UCONTEXT_H)) test_assert(backtrace_get(&bt) == 0); /* it should have some kind of main in it */ test_assert(strstr(bt, "main") != NULL); #else /* should not work in this context */ test_assert(backtrace_get(&bt) == -1); #endif test_end(); } void test_backtrace(void) { test_backtrace_append(); test_backtrace_get(); } dovecot-2.3.21.1/src/lib/event-filter-parser.c0000644000000000000000000016412114656633635015715 00000000000000/* A Bison parser, made by GNU Bison 3.7.5. */ /* Bison implementation for Yacc-like parsers in C Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation, Inc. 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 3 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 . */ /* As a special exception, you may create a larger work that contains part or all of the Bison parser skeleton and distribute that work under terms of your choice, so long as that work isn't itself a parser generator using the skeleton or a modified version thereof as a parser skeleton. Alternatively, if you modify or redistribute the parser skeleton itself, you may (at your option) remove this special exception, which will cause the skeleton and the resulting Bison output files to be licensed under the GNU General Public License without this special exception. This special exception was added by the Free Software Foundation in version 2.2 of Bison. */ /* C LALR(1) parser skeleton written by Richard Stallman, by simplifying the original so-called "semantic" parser. */ /* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual, especially those whose name start with YY_ or yy_. They are private implementation details that can be changed or removed. */ /* All symbols defined below should begin with yy or YY, to avoid infringing on user name space. This should be done even for local variables, as they might otherwise be expanded by user macros. There are some unavoidable exceptions within include files to define necessary library symbols; they are noted "INFRINGES ON USER NAME SPACE" below. */ /* Identify Bison output, and Bison version. */ #define YYBISON 30705 /* Bison version string. */ #define YYBISON_VERSION "3.7.5" /* Skeleton name. */ #define YYSKELETON_NAME "yacc.c" /* Pure parsers. */ #define YYPURE 1 /* Push parsers. */ #define YYPUSH 0 /* Pull parsers. */ #define YYPULL 1 /* Substitute the type names. */ #define YYSTYPE EVENT_FILTER_PARSER_STYPE /* Substitute the variable and function names. */ #define yyparse event_filter_parser_parse #define yylex event_filter_parser_lex #define yyerror event_filter_parser_error #define yydebug event_filter_parser_debug #define yynerrs event_filter_parser_nerrs /* First part of user prologue. */ #line 11 "event-filter-parser.y" #include "lib.h" #include "wildcard-match.h" #include "lib-event-private.h" #include "event-filter-private.h" #define scanner state->scanner #define YYERROR_VERBOSE extern int event_filter_parser_lex(void *, void *); void event_filter_parser_error(void *scan, const char *e) { struct event_filter_parser_state *state = scan; state->error = t_strdup_printf("event filter: %s", e); } static struct event_filter_node *key_value(struct event_filter_parser_state *state, const char *a, const char *b, enum event_filter_node_op op) { struct event_filter_node *node; enum event_filter_node_type type; if (strcmp(a, "event") == 0) type = EVENT_FILTER_NODE_TYPE_EVENT_NAME_WILDCARD; else if (strcmp(a, "category") == 0) type = EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY; else if (strcmp(a, "source_location") == 0) type = EVENT_FILTER_NODE_TYPE_EVENT_SOURCE_LOCATION; else type = EVENT_FILTER_NODE_TYPE_EVENT_FIELD_WILDCARD; /* only fields support comparators other than EQ */ if ((type != EVENT_FILTER_NODE_TYPE_EVENT_FIELD_WILDCARD) && (op != EVENT_FILTER_OP_CMP_EQ)) { state->error = "Only fields support inequality comparisons"; return NULL; } node = p_new(state->pool, struct event_filter_node, 1); node->type = type; node->op = op; switch (type) { case EVENT_FILTER_NODE_TYPE_LOGIC: i_unreached(); case EVENT_FILTER_NODE_TYPE_EVENT_NAME_WILDCARD: node->str = p_strdup(state->pool, b); if (wildcard_is_literal(node->str)) node->type = EVENT_FILTER_NODE_TYPE_EVENT_NAME_EXACT; break; case EVENT_FILTER_NODE_TYPE_EVENT_SOURCE_LOCATION: { const char *colon = strrchr(b, ':'); const char *file; uintmax_t line; /* split "filename:line-number", but also handle "filename" */ if (colon != NULL) { if (str_to_uintmax(colon + 1, &line) < 0) { file = p_strdup(state->pool, b); line = 0; } else { file = p_strdup_until(state->pool, b, colon); } } else { file = p_strdup(state->pool, b); line = 0; } node->str = file; node->intmax = line; break; } case EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY: if (!event_filter_category_to_log_type(b, &node->category.log_type)) { node->category.name = p_strdup(state->pool, b); node->category.ptr = event_category_find_registered(b); } break; case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_WILDCARD: node->field.key = p_strdup(state->pool, a); node->field.value.str = p_strdup(state->pool, b); /* Filter currently supports only comparing strings and numbers. */ if (str_to_intmax(b, &node->field.value.intmax) < 0) { /* not a number - no problem Either we have a string, or a number with wildcards */ node->field.value.intmax = INT_MIN; } if (wildcard_is_literal(node->field.value.str)) node->type = EVENT_FILTER_NODE_TYPE_EVENT_FIELD_EXACT; break; case EVENT_FILTER_NODE_TYPE_EVENT_NAME_EXACT: case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_EXACT: i_unreached(); } return node; } static struct event_filter_node *logic(struct event_filter_parser_state *state, struct event_filter_node *a, struct event_filter_node *b, enum event_filter_node_op op) { struct event_filter_node *node; node = p_new(state->pool, struct event_filter_node, 1); node->type = EVENT_FILTER_NODE_TYPE_LOGIC; node->op = op; node->children[0] = a; node->children[1] = b; return node; } /* ignore strict bool warnings in generated code */ #ifdef HAVE_STRICT_BOOL # pragma GCC diagnostic ignored "-Wstrict-bool" #endif #line 205 "event-filter-parser.c" # ifndef YY_CAST # ifdef __cplusplus # define YY_CAST(Type, Val) static_cast (Val) # define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast (Val) # else # define YY_CAST(Type, Val) ((Type) (Val)) # define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val)) # endif # endif # ifndef YY_NULLPTR # if defined __cplusplus # if 201103L <= __cplusplus # define YY_NULLPTR nullptr # else # define YY_NULLPTR 0 # endif # else # define YY_NULLPTR ((void*)0) # endif # endif #include "event-filter-parser.h" /* Symbol kind. */ enum yysymbol_kind_t { YYSYMBOL_YYEMPTY = -2, YYSYMBOL_YYEOF = 0, /* "end of file" */ YYSYMBOL_YYerror = 1, /* error */ YYSYMBOL_YYUNDEF = 2, /* "invalid token" */ YYSYMBOL_TOKEN = 3, /* TOKEN */ YYSYMBOL_STRING = 4, /* STRING */ YYSYMBOL_AND = 5, /* AND */ YYSYMBOL_OR = 6, /* OR */ YYSYMBOL_NOT = 7, /* NOT */ YYSYMBOL_8_ = 8, /* '(' */ YYSYMBOL_9_ = 9, /* ')' */ YYSYMBOL_10_ = 10, /* '=' */ YYSYMBOL_11_ = 11, /* '>' */ YYSYMBOL_12_ = 12, /* '<' */ YYSYMBOL_YYACCEPT = 13, /* $accept */ YYSYMBOL_filter = 14, /* filter */ YYSYMBOL_expr = 15, /* expr */ YYSYMBOL_key_value = 16, /* key_value */ YYSYMBOL_key = 17, /* key */ YYSYMBOL_value = 18, /* value */ YYSYMBOL_op = 19 /* op */ }; typedef enum yysymbol_kind_t yysymbol_kind_t; #ifdef short # undef short #endif /* On compilers that do not define __PTRDIFF_MAX__ etc., make sure and (if available) are included so that the code can choose integer types of a good width. */ #ifndef __PTRDIFF_MAX__ # include /* INFRINGES ON USER NAME SPACE */ # if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ # include /* INFRINGES ON USER NAME SPACE */ # define YY_STDINT_H # endif #endif /* Narrow types that promote to a signed type and that can represent a signed or unsigned integer of at least N bits. In tables they can save space and decrease cache pressure. Promoting to a signed type helps avoid bugs in integer arithmetic. */ #ifdef __INT_LEAST8_MAX__ typedef __INT_LEAST8_TYPE__ yytype_int8; #elif defined YY_STDINT_H typedef int_least8_t yytype_int8; #else typedef signed char yytype_int8; #endif #ifdef __INT_LEAST16_MAX__ typedef __INT_LEAST16_TYPE__ yytype_int16; #elif defined YY_STDINT_H typedef int_least16_t yytype_int16; #else typedef short yytype_int16; #endif /* Work around bug in HP-UX 11.23, which defines these macros incorrectly for preprocessor constants. This workaround can likely be removed in 2023, as HPE has promised support for HP-UX 11.23 (aka HP-UX 11i v2) only through the end of 2022; see Table 2 of . */ #ifdef __hpux # undef UINT_LEAST8_MAX # undef UINT_LEAST16_MAX # define UINT_LEAST8_MAX 255 # define UINT_LEAST16_MAX 65535 #endif #if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__ typedef __UINT_LEAST8_TYPE__ yytype_uint8; #elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \ && UINT_LEAST8_MAX <= INT_MAX) typedef uint_least8_t yytype_uint8; #elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX typedef unsigned char yytype_uint8; #else typedef short yytype_uint8; #endif #if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__ typedef __UINT_LEAST16_TYPE__ yytype_uint16; #elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \ && UINT_LEAST16_MAX <= INT_MAX) typedef uint_least16_t yytype_uint16; #elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX typedef unsigned short yytype_uint16; #else typedef int yytype_uint16; #endif #ifndef YYPTRDIFF_T # if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__ # define YYPTRDIFF_T __PTRDIFF_TYPE__ # define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__ # elif defined PTRDIFF_MAX # ifndef ptrdiff_t # include /* INFRINGES ON USER NAME SPACE */ # endif # define YYPTRDIFF_T ptrdiff_t # define YYPTRDIFF_MAXIMUM PTRDIFF_MAX # else # define YYPTRDIFF_T long # define YYPTRDIFF_MAXIMUM LONG_MAX # endif #endif #ifndef YYSIZE_T # ifdef __SIZE_TYPE__ # define YYSIZE_T __SIZE_TYPE__ # elif defined size_t # define YYSIZE_T size_t # elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ # include /* INFRINGES ON USER NAME SPACE */ # define YYSIZE_T size_t # else # define YYSIZE_T unsigned # endif #endif #define YYSIZE_MAXIMUM \ YY_CAST (YYPTRDIFF_T, \ (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \ ? YYPTRDIFF_MAXIMUM \ : YY_CAST (YYSIZE_T, -1))) #define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X)) /* Stored state numbers (used for stacks). */ typedef yytype_int8 yy_state_t; /* State numbers in computations. */ typedef int yy_state_fast_t; #ifndef YY_ # if defined YYENABLE_NLS && YYENABLE_NLS # if ENABLE_NLS # include /* INFRINGES ON USER NAME SPACE */ # define YY_(Msgid) dgettext ("bison-runtime", Msgid) # endif # endif # ifndef YY_ # define YY_(Msgid) Msgid # endif #endif #ifndef YY_ATTRIBUTE_PURE # if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__) # define YY_ATTRIBUTE_PURE __attribute__ ((__pure__)) # else # define YY_ATTRIBUTE_PURE # endif #endif #ifndef YY_ATTRIBUTE_UNUSED # if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__) # define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__)) # else # define YY_ATTRIBUTE_UNUSED # endif #endif /* Suppress unused-variable warnings by "using" E. */ #if ! defined lint || defined __GNUC__ # define YY_USE(E) ((void) (E)) #else # define YY_USE(E) /* empty */ #endif #if defined __GNUC__ && ! defined __ICC && 407 <= __GNUC__ * 100 + __GNUC_MINOR__ /* Suppress an incorrect diagnostic about yylval being uninitialized. */ # define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ _Pragma ("GCC diagnostic push") \ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") # define YY_IGNORE_MAYBE_UNINITIALIZED_END \ _Pragma ("GCC diagnostic pop") #else # define YY_INITIAL_VALUE(Value) Value #endif #ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN # define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN # define YY_IGNORE_MAYBE_UNINITIALIZED_END #endif #ifndef YY_INITIAL_VALUE # define YY_INITIAL_VALUE(Value) /* Nothing. */ #endif #if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__ # define YY_IGNORE_USELESS_CAST_BEGIN \ _Pragma ("GCC diagnostic push") \ _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"") # define YY_IGNORE_USELESS_CAST_END \ _Pragma ("GCC diagnostic pop") #endif #ifndef YY_IGNORE_USELESS_CAST_BEGIN # define YY_IGNORE_USELESS_CAST_BEGIN # define YY_IGNORE_USELESS_CAST_END #endif #define YY_ASSERT(E) ((void) (0 && (E))) #if 1 /* The parser invokes alloca or malloc; define the necessary symbols. */ # ifdef YYSTACK_USE_ALLOCA # if YYSTACK_USE_ALLOCA # ifdef __GNUC__ # define YYSTACK_ALLOC __builtin_alloca # elif defined __BUILTIN_VA_ARG_INCR # include /* INFRINGES ON USER NAME SPACE */ # elif defined _AIX # define YYSTACK_ALLOC __alloca # elif defined _MSC_VER # include /* INFRINGES ON USER NAME SPACE */ # define alloca _alloca # else # define YYSTACK_ALLOC alloca # if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS # include /* INFRINGES ON USER NAME SPACE */ /* Use EXIT_SUCCESS as a witness for stdlib.h. */ # ifndef EXIT_SUCCESS # define EXIT_SUCCESS 0 # endif # endif # endif # endif # endif # ifdef YYSTACK_ALLOC /* Pacify GCC's 'empty if-body' warning. */ # define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) # ifndef YYSTACK_ALLOC_MAXIMUM /* The OS might guarantee only one guard page at the bottom of the stack, and a page size can be as small as 4096 bytes. So we cannot safely invoke alloca (N) if N exceeds 4096. Use a slightly smaller number to allow for a few compiler-allocated temporary stack slots. */ # define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ # endif # else # define YYSTACK_ALLOC YYMALLOC # define YYSTACK_FREE YYFREE # ifndef YYSTACK_ALLOC_MAXIMUM # define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM # endif # if (defined __cplusplus && ! defined EXIT_SUCCESS \ && ! ((defined YYMALLOC || defined malloc) \ && (defined YYFREE || defined free))) # include /* INFRINGES ON USER NAME SPACE */ # ifndef EXIT_SUCCESS # define EXIT_SUCCESS 0 # endif # endif # ifndef YYMALLOC # define YYMALLOC malloc # if ! defined malloc && ! defined EXIT_SUCCESS void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ # endif # endif # ifndef YYFREE # define YYFREE free # if ! defined free && ! defined EXIT_SUCCESS void free (void *); /* INFRINGES ON USER NAME SPACE */ # endif # endif # endif #endif /* 1 */ #if (! defined yyoverflow \ && (! defined __cplusplus \ || (defined EVENT_FILTER_PARSER_STYPE_IS_TRIVIAL && EVENT_FILTER_PARSER_STYPE_IS_TRIVIAL))) /* A type that is properly aligned for any stack member. */ union yyalloc { yy_state_t yyss_alloc; YYSTYPE yyvs_alloc; }; /* The size of the maximum gap between one aligned stack and the next. */ # define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1) /* The size of an array large to enough to hold all stacks, each with N elements. */ # define YYSTACK_BYTES(N) \ ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE)) \ + YYSTACK_GAP_MAXIMUM) # define YYCOPY_NEEDED 1 /* Relocate STACK from its old location to the new one. The local variables YYSIZE and YYSTACKSIZE give the old and new number of elements in the stack, and YYPTR gives the new location of the stack. Advance YYPTR to a properly aligned location for the next stack. */ # define YYSTACK_RELOCATE(Stack_alloc, Stack) \ do \ { \ YYPTRDIFF_T yynewbytes; \ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ Stack = &yyptr->Stack_alloc; \ yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \ yyptr += yynewbytes / YYSIZEOF (*yyptr); \ } \ while (0) #endif #if defined YYCOPY_NEEDED && YYCOPY_NEEDED /* Copy COUNT objects from SRC to DST. The source and destination do not overlap. */ # ifndef YYCOPY # if defined __GNUC__ && 1 < __GNUC__ # define YYCOPY(Dst, Src, Count) \ __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src))) # else # define YYCOPY(Dst, Src, Count) \ do \ { \ YYPTRDIFF_T yyi; \ for (yyi = 0; yyi < (Count); yyi++) \ (Dst)[yyi] = (Src)[yyi]; \ } \ while (0) # endif # endif #endif /* !YYCOPY_NEEDED */ /* YYFINAL -- State number of the termination state. */ #define YYFINAL 11 /* YYLAST -- Last index in YYTABLE. */ #define YYLAST 24 /* YYNTOKENS -- Number of terminals. */ #define YYNTOKENS 13 /* YYNNTS -- Number of nonterminals. */ #define YYNNTS 7 /* YYNRULES -- Number of rules. */ #define YYNRULES 21 /* YYNSTATES -- Number of states. */ #define YYNSTATES 29 /* YYMAXUTOK -- Last valid token kind. */ #define YYMAXUTOK 262 /* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM as returned by yylex, with out-of-bounds checking. */ #define YYTRANSLATE(YYX) \ (0 <= (YYX) && (YYX) <= YYMAXUTOK \ ? YY_CAST (yysymbol_kind_t, yytranslate[YYX]) \ : YYSYMBOL_YYUNDEF) /* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM as returned by yylex. */ static const yytype_int8 yytranslate[] = { 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 8, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 12, 10, 11, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, 5, 6, 7 }; #if EVENT_FILTER_PARSER_DEBUG /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ static const yytype_uint8 yyrline[] = { 0, 156, 156, 157, 160, 161, 162, 163, 164, 167, 178, 179, 182, 183, 184, 185, 186, 189, 190, 191, 192, 193 }; #endif /** Accessing symbol of state STATE. */ #define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State]) #if 1 /* The user-facing name of the symbol whose (internal) number is YYSYMBOL. No bounds checking. */ static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED; /* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. First, the terminals, then, starting at YYNTOKENS, nonterminals. */ static const char *const yytname[] = { "\"end of file\"", "error", "\"invalid token\"", "TOKEN", "STRING", "AND", "OR", "NOT", "'('", "')'", "'='", "'>'", "'<'", "$accept", "filter", "expr", "key_value", "key", "value", "op", YY_NULLPTR }; static const char * yysymbol_name (yysymbol_kind_t yysymbol) { return yytname[yysymbol]; } #endif #ifdef YYPRINT /* YYTOKNUM[NUM] -- (External) token number corresponding to the (internal) symbol number NUM (which must be that of a token). */ static const yytype_int16 yytoknum[] = { 0, 256, 257, 258, 259, 260, 261, 262, 40, 41, 61, 62, 60 }; #endif #define YYPACT_NINF (-6) #define yypact_value_is_default(Yyn) \ ((Yyn) == YYPACT_NINF) #define YYTABLE_NINF (-1) #define yytable_value_is_error(Yyn) \ 0 /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing STATE-NUM. */ static const yytype_int8 yypact[] = { -1, -6, -6, -1, -1, 4, 13, -6, 12, -6, 11, -6, -1, -1, -6, -5, -2, 8, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6 }; /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. Performed when YYTABLE does not specify something else to do. Zero means the default is an error. */ static const yytype_int8 yydefact[] = { 3, 10, 11, 0, 0, 0, 2, 8, 0, 6, 0, 1, 0, 0, 17, 18, 19, 0, 7, 4, 5, 20, 21, 12, 13, 14, 15, 16, 9 }; /* YYPGOTO[NTERM-NUM]. */ static const yytype_int8 yypgoto[] = { -6, -6, -3, -6, -6, -6, -6 }; /* YYDEFGOTO[NTERM-NUM]. */ static const yytype_int8 yydefgoto[] = { 0, 5, 6, 7, 8, 28, 17 }; /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If positive, shift that token. If negative, reduce the rule whose number is the opposite. If YYTABLE_NINF, syntax error. */ static const yytype_int8 yytable[] = { 9, 10, 1, 2, 11, 21, 3, 4, 22, 19, 20, 23, 24, 25, 26, 27, 12, 13, 12, 13, 18, 0, 14, 15, 16 }; static const yytype_int8 yycheck[] = { 3, 4, 3, 4, 0, 10, 7, 8, 10, 12, 13, 3, 4, 5, 6, 7, 5, 6, 5, 6, 9, -1, 10, 11, 12 }; /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing symbol of state STATE-NUM. */ static const yytype_int8 yystos[] = { 0, 3, 4, 7, 8, 14, 15, 16, 17, 15, 15, 0, 5, 6, 10, 11, 12, 19, 9, 15, 15, 10, 10, 3, 4, 5, 6, 7, 18 }; /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ static const yytype_int8 yyr1[] = { 0, 13, 14, 14, 15, 15, 15, 15, 15, 16, 17, 17, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19 }; /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */ static const yytype_int8 yyr2[] = { 0, 2, 1, 0, 3, 3, 2, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2 }; enum { YYENOMEM = -2 }; #define yyerrok (yyerrstatus = 0) #define yyclearin (yychar = EVENT_FILTER_PARSER_EMPTY) #define YYACCEPT goto yyacceptlab #define YYABORT goto yyabortlab #define YYERROR goto yyerrorlab #define YYRECOVERING() (!!yyerrstatus) #define YYBACKUP(Token, Value) \ do \ if (yychar == EVENT_FILTER_PARSER_EMPTY) \ { \ yychar = (Token); \ yylval = (Value); \ YYPOPSTACK (yylen); \ yystate = *yyssp; \ goto yybackup; \ } \ else \ { \ yyerror (state, YY_("syntax error: cannot back up")); \ YYERROR; \ } \ while (0) /* Backward compatibility with an undocumented macro. Use EVENT_FILTER_PARSER_error or EVENT_FILTER_PARSER_UNDEF. */ #define YYERRCODE EVENT_FILTER_PARSER_UNDEF /* Enable debugging if requested. */ #if EVENT_FILTER_PARSER_DEBUG # ifndef YYFPRINTF # include /* INFRINGES ON USER NAME SPACE */ # define YYFPRINTF fprintf # endif # define YYDPRINTF(Args) \ do { \ if (yydebug) \ YYFPRINTF Args; \ } while (0) /* This macro is provided for backward compatibility. */ # ifndef YY_LOCATION_PRINT # define YY_LOCATION_PRINT(File, Loc) ((void) 0) # endif # define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \ do { \ if (yydebug) \ { \ YYFPRINTF (stderr, "%s ", Title); \ yy_symbol_print (stderr, \ Kind, Value, state); \ YYFPRINTF (stderr, "\n"); \ } \ } while (0) /*-----------------------------------. | Print this symbol's value on YYO. | `-----------------------------------*/ static void yy_symbol_value_print (FILE *yyo, yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep, struct event_filter_parser_state *state) { FILE *yyoutput = yyo; YY_USE (yyoutput); YY_USE (state); if (!yyvaluep) return; # ifdef YYPRINT if (yykind < YYNTOKENS) YYPRINT (yyo, yytoknum[yykind], *yyvaluep); # endif YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN YY_USE (yykind); YY_IGNORE_MAYBE_UNINITIALIZED_END } /*---------------------------. | Print this symbol on YYO. | `---------------------------*/ static void yy_symbol_print (FILE *yyo, yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep, struct event_filter_parser_state *state) { YYFPRINTF (yyo, "%s %s (", yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind)); yy_symbol_value_print (yyo, yykind, yyvaluep, state); YYFPRINTF (yyo, ")"); } /*------------------------------------------------------------------. | yy_stack_print -- Print the state stack from its BOTTOM up to its | | TOP (included). | `------------------------------------------------------------------*/ static void yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop) { YYFPRINTF (stderr, "Stack now"); for (; yybottom <= yytop; yybottom++) { int yybot = *yybottom; YYFPRINTF (stderr, " %d", yybot); } YYFPRINTF (stderr, "\n"); } # define YY_STACK_PRINT(Bottom, Top) \ do { \ if (yydebug) \ yy_stack_print ((Bottom), (Top)); \ } while (0) /*------------------------------------------------. | Report that the YYRULE is going to be reduced. | `------------------------------------------------*/ static void yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp, int yyrule, struct event_filter_parser_state *state) { int yylno = yyrline[yyrule]; int yynrhs = yyr2[yyrule]; int yyi; YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n", yyrule - 1, yylno); /* The symbols being reduced. */ for (yyi = 0; yyi < yynrhs; yyi++) { YYFPRINTF (stderr, " $%d = ", yyi + 1); yy_symbol_print (stderr, YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]), &yyvsp[(yyi + 1) - (yynrhs)], state); YYFPRINTF (stderr, "\n"); } } # define YY_REDUCE_PRINT(Rule) \ do { \ if (yydebug) \ yy_reduce_print (yyssp, yyvsp, Rule, state); \ } while (0) /* Nonzero means print parse trace. It is left uninitialized so that multiple parsers can coexist. */ int yydebug; #else /* !EVENT_FILTER_PARSER_DEBUG */ # define YYDPRINTF(Args) ((void) 0) # define YY_SYMBOL_PRINT(Title, Kind, Value, Location) # define YY_STACK_PRINT(Bottom, Top) # define YY_REDUCE_PRINT(Rule) #endif /* !EVENT_FILTER_PARSER_DEBUG */ /* YYINITDEPTH -- initial size of the parser's stacks. */ #ifndef YYINITDEPTH # define YYINITDEPTH 200 #endif /* YYMAXDEPTH -- maximum size the stacks can grow to (effective only if the built-in stack extension method is used). Do not make this value too large; the results are undefined if YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) evaluated with infinite-precision integer arithmetic. */ #ifndef YYMAXDEPTH # define YYMAXDEPTH 10000 #endif /* Context of a parse error. */ typedef struct { yy_state_t *yyssp; yysymbol_kind_t yytoken; } yypcontext_t; /* Put in YYARG at most YYARGN of the expected tokens given the current YYCTX, and return the number of tokens stored in YYARG. If YYARG is null, return the number of expected tokens (guaranteed to be less than YYNTOKENS). Return YYENOMEM on memory exhaustion. Return 0 if there are more than YYARGN expected tokens, yet fill YYARG up to YYARGN. */ static int yypcontext_expected_tokens (const yypcontext_t *yyctx, yysymbol_kind_t yyarg[], int yyargn) { /* Actual size of YYARG. */ int yycount = 0; int yyn = yypact[+*yyctx->yyssp]; if (!yypact_value_is_default (yyn)) { /* Start YYX at -YYN if negative to avoid negative indexes in YYCHECK. In other words, skip the first -YYN actions for this state because they are default actions. */ int yyxbegin = yyn < 0 ? -yyn : 0; /* Stay within bounds of both yycheck and yytname. */ int yychecklim = YYLAST - yyn + 1; int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; int yyx; for (yyx = yyxbegin; yyx < yyxend; ++yyx) if (yycheck[yyx + yyn] == yyx && yyx != YYSYMBOL_YYerror && !yytable_value_is_error (yytable[yyx + yyn])) { if (!yyarg) ++yycount; else if (yycount == yyargn) return 0; else yyarg[yycount++] = YY_CAST (yysymbol_kind_t, yyx); } } if (yyarg && yycount == 0 && 0 < yyargn) yyarg[0] = YYSYMBOL_YYEMPTY; return yycount; } #ifndef yystrlen # if defined __GLIBC__ && defined _STRING_H # define yystrlen(S) (YY_CAST (YYPTRDIFF_T, strlen (S))) # else /* Return the length of YYSTR. */ static YYPTRDIFF_T yystrlen (const char *yystr) { YYPTRDIFF_T yylen; for (yylen = 0; yystr[yylen]; yylen++) continue; return yylen; } # endif #endif #ifndef yystpcpy # if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE # define yystpcpy stpcpy # else /* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in YYDEST. */ static char * yystpcpy (char *yydest, const char *yysrc) { char *yyd = yydest; const char *yys = yysrc; while ((*yyd++ = *yys++) != '\0') continue; return yyd - 1; } # endif #endif #ifndef yytnamerr /* Copy to YYRES the contents of YYSTR after stripping away unnecessary quotes and backslashes, so that it's suitable for yyerror. The heuristic is that double-quoting is unnecessary unless the string contains an apostrophe, a comma, or backslash (other than backslash-backslash). YYSTR is taken from yytname. If YYRES is null, do not copy; instead, return the length of what the result would have been. */ static YYPTRDIFF_T yytnamerr (char *yyres, const char *yystr) { if (*yystr == '"') { YYPTRDIFF_T yyn = 0; char const *yyp = yystr; for (;;) switch (*++yyp) { case '\'': case ',': goto do_not_strip_quotes; case '\\': if (*++yyp != '\\') goto do_not_strip_quotes; else goto append; append: default: if (yyres) yyres[yyn] = *yyp; yyn++; break; case '"': if (yyres) yyres[yyn] = '\0'; return yyn; } do_not_strip_quotes: ; } if (yyres) return yystpcpy (yyres, yystr) - yyres; else return yystrlen (yystr); } #endif static int yy_syntax_error_arguments (const yypcontext_t *yyctx, yysymbol_kind_t yyarg[], int yyargn) { /* Actual size of YYARG. */ int yycount = 0; /* There are many possibilities here to consider: - If this state is a consistent state with a default action, then the only way this function was invoked is if the default action is an error action. In that case, don't check for expected tokens because there are none. - The only way there can be no lookahead present (in yychar) is if this state is a consistent state with a default action. Thus, detecting the absence of a lookahead is sufficient to determine that there is no unexpected or expected token to report. In that case, just report a simple "syntax error". - Don't assume there isn't a lookahead just because this state is a consistent state with a default action. There might have been a previous inconsistent state, consistent state with a non-default action, or user semantic action that manipulated yychar. - Of course, the expected token list depends on states to have correct lookahead information, and it depends on the parser not to perform extra reductions after fetching a lookahead from the scanner and before detecting a syntax error. Thus, state merging (from LALR or IELR) and default reductions corrupt the expected token list. However, the list is correct for canonical LR with one exception: it will still contain any token that will not be accepted due to an error action in a later state. */ if (yyctx->yytoken != YYSYMBOL_YYEMPTY) { int yyn; if (yyarg) yyarg[yycount] = yyctx->yytoken; ++yycount; yyn = yypcontext_expected_tokens (yyctx, yyarg ? yyarg + 1 : yyarg, yyargn - 1); if (yyn == YYENOMEM) return YYENOMEM; else yycount += yyn; } return yycount; } /* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message about the unexpected token YYTOKEN for the state stack whose top is YYSSP. Return 0 if *YYMSG was successfully written. Return -1 if *YYMSG is not large enough to hold the message. In that case, also set *YYMSG_ALLOC to the required number of bytes. Return YYENOMEM if the required number of bytes is too large to store. */ static int yysyntax_error (YYPTRDIFF_T *yymsg_alloc, char **yymsg, const yypcontext_t *yyctx) { enum { YYARGS_MAX = 5 }; /* Internationalized format string. */ const char *yyformat = YY_NULLPTR; /* Arguments of yyformat: reported tokens (one for the "unexpected", one per "expected"). */ yysymbol_kind_t yyarg[YYARGS_MAX]; /* Cumulated lengths of YYARG. */ YYPTRDIFF_T yysize = 0; /* Actual size of YYARG. */ int yycount = yy_syntax_error_arguments (yyctx, yyarg, YYARGS_MAX); if (yycount == YYENOMEM) return YYENOMEM; switch (yycount) { #define YYCASE_(N, S) \ case N: \ yyformat = S; \ break default: /* Avoid compiler warnings. */ YYCASE_(0, YY_("syntax error")); YYCASE_(1, YY_("syntax error, unexpected %s")); YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s")); YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s")); YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); #undef YYCASE_ } /* Compute error message size. Don't count the "%s"s, but reserve room for the terminator. */ yysize = yystrlen (yyformat) - 2 * yycount + 1; { int yyi; for (yyi = 0; yyi < yycount; ++yyi) { YYPTRDIFF_T yysize1 = yysize + yytnamerr (YY_NULLPTR, yytname[yyarg[yyi]]); if (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM) yysize = yysize1; else return YYENOMEM; } } if (*yymsg_alloc < yysize) { *yymsg_alloc = 2 * yysize; if (! (yysize <= *yymsg_alloc && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM)) *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM; return -1; } /* Avoid sprintf, as that infringes on the user's name space. Don't have undefined behavior even if the translation produced a string with the wrong number of "%s"s. */ { char *yyp = *yymsg; int yyi = 0; while ((*yyp = *yyformat) != '\0') if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount) { yyp += yytnamerr (yyp, yytname[yyarg[yyi++]]); yyformat += 2; } else { ++yyp; ++yyformat; } } return 0; } /*-----------------------------------------------. | Release the memory associated to this symbol. | `-----------------------------------------------*/ static void yydestruct (const char *yymsg, yysymbol_kind_t yykind, YYSTYPE *yyvaluep, struct event_filter_parser_state *state) { YY_USE (yyvaluep); YY_USE (state); if (!yymsg) yymsg = "Deleting"; YY_SYMBOL_PRINT (yymsg, yykind, yyvaluep, yylocationp); YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN YY_USE (yykind); YY_IGNORE_MAYBE_UNINITIALIZED_END } /*----------. | yyparse. | `----------*/ int yyparse (struct event_filter_parser_state *state) { /* Lookahead token kind. */ int yychar; /* The semantic value of the lookahead symbol. */ /* Default value used for initialization, for pacifying older GCCs or non-GCC compilers. */ YY_INITIAL_VALUE (static YYSTYPE yyval_default;) YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); /* Number of syntax errors so far. */ int yynerrs = 0; yy_state_fast_t yystate = 0; /* Number of tokens to shift before error messages enabled. */ int yyerrstatus = 0; /* Refer to the stacks through separate pointers, to allow yyoverflow to reallocate them elsewhere. */ /* Their size. */ YYPTRDIFF_T yystacksize = YYINITDEPTH; /* The state stack: array, bottom, top. */ yy_state_t yyssa[YYINITDEPTH]; yy_state_t *yyss = yyssa; yy_state_t *yyssp = yyss; /* The semantic value stack: array, bottom, top. */ YYSTYPE yyvsa[YYINITDEPTH]; YYSTYPE *yyvs = yyvsa; YYSTYPE *yyvsp = yyvs; int yyn; /* The return value of yyparse. */ int yyresult; /* Lookahead symbol kind. */ yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY; /* The variables used to return semantic value and location from the action routines. */ YYSTYPE yyval; /* Buffer for error messages, and its allocated size. */ char yymsgbuf[128]; char *yymsg = yymsgbuf; YYPTRDIFF_T yymsg_alloc = sizeof yymsgbuf; #define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) /* The number of symbols on the RHS of the reduced rule. Keep to zero when no symbol should be popped. */ int yylen = 0; YYDPRINTF ((stderr, "Starting parse\n")); yychar = EVENT_FILTER_PARSER_EMPTY; /* Cause a token to be read. */ goto yysetstate; /*------------------------------------------------------------. | yynewstate -- push a new state, which is found in yystate. | `------------------------------------------------------------*/ yynewstate: /* In all cases, when you get here, the value and location stacks have just been pushed. So pushing a state here evens the stacks. */ yyssp++; /*--------------------------------------------------------------------. | yysetstate -- set current state (the top of the stack) to yystate. | `--------------------------------------------------------------------*/ yysetstate: YYDPRINTF ((stderr, "Entering state %d\n", yystate)); YY_ASSERT (0 <= yystate && yystate < YYNSTATES); YY_IGNORE_USELESS_CAST_BEGIN *yyssp = YY_CAST (yy_state_t, yystate); YY_IGNORE_USELESS_CAST_END YY_STACK_PRINT (yyss, yyssp); if (yyss + yystacksize - 1 <= yyssp) #if !defined yyoverflow && !defined YYSTACK_RELOCATE goto yyexhaustedlab; #else { /* Get the current used size of the three stacks, in elements. */ YYPTRDIFF_T yysize = yyssp - yyss + 1; # if defined yyoverflow { /* Give user a chance to reallocate the stack. Use copies of these so that the &'s don't force the real ones into memory. */ yy_state_t *yyss1 = yyss; YYSTYPE *yyvs1 = yyvs; /* Each stack pointer address is followed by the size of the data in use in that stack, in bytes. This used to be a conditional around just the two extra args, but that might be undefined if yyoverflow is a macro. */ yyoverflow (YY_("memory exhausted"), &yyss1, yysize * YYSIZEOF (*yyssp), &yyvs1, yysize * YYSIZEOF (*yyvsp), &yystacksize); yyss = yyss1; yyvs = yyvs1; } # else /* defined YYSTACK_RELOCATE */ /* Extend the stack our own way. */ if (YYMAXDEPTH <= yystacksize) goto yyexhaustedlab; yystacksize *= 2; if (YYMAXDEPTH < yystacksize) yystacksize = YYMAXDEPTH; { yy_state_t *yyss1 = yyss; union yyalloc *yyptr = YY_CAST (union yyalloc *, YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize)))); if (! yyptr) goto yyexhaustedlab; YYSTACK_RELOCATE (yyss_alloc, yyss); YYSTACK_RELOCATE (yyvs_alloc, yyvs); # undef YYSTACK_RELOCATE if (yyss1 != yyssa) YYSTACK_FREE (yyss1); } # endif yyssp = yyss + yysize - 1; yyvsp = yyvs + yysize - 1; YY_IGNORE_USELESS_CAST_BEGIN YYDPRINTF ((stderr, "Stack size increased to %ld\n", YY_CAST (long, yystacksize))); YY_IGNORE_USELESS_CAST_END if (yyss + yystacksize - 1 <= yyssp) YYABORT; } #endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */ if (yystate == YYFINAL) YYACCEPT; goto yybackup; /*-----------. | yybackup. | `-----------*/ yybackup: /* Do appropriate processing given the current state. Read a lookahead token if we need one and don't already have one. */ /* First try to decide what to do without reference to lookahead token. */ yyn = yypact[yystate]; if (yypact_value_is_default (yyn)) goto yydefault; /* Not known => get a lookahead token if don't already have one. */ /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */ if (yychar == EVENT_FILTER_PARSER_EMPTY) { YYDPRINTF ((stderr, "Reading a token\n")); yychar = yylex (&yylval, scanner); } if (yychar <= EVENT_FILTER_PARSER_EOF) { yychar = EVENT_FILTER_PARSER_EOF; yytoken = YYSYMBOL_YYEOF; YYDPRINTF ((stderr, "Now at end of input.\n")); } else if (yychar == EVENT_FILTER_PARSER_error) { /* The scanner already issued an error message, process directly to error recovery. But do not keep the error token as lookahead, it is too special and may lead us to an endless loop in error recovery. */ yychar = EVENT_FILTER_PARSER_UNDEF; yytoken = YYSYMBOL_YYerror; goto yyerrlab1; } else { yytoken = YYTRANSLATE (yychar); YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); } /* If the proper action on seeing token YYTOKEN is to reduce or to detect an error, take that action. */ yyn += yytoken; if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) goto yydefault; yyn = yytable[yyn]; if (yyn <= 0) { if (yytable_value_is_error (yyn)) goto yyerrlab; yyn = -yyn; goto yyreduce; } /* Count tokens shifted since error; after three, turn off error status. */ if (yyerrstatus) yyerrstatus--; /* Shift the lookahead token. */ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); yystate = yyn; YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN *++yyvsp = yylval; YY_IGNORE_MAYBE_UNINITIALIZED_END /* Discard the shifted token. */ yychar = EVENT_FILTER_PARSER_EMPTY; goto yynewstate; /*-----------------------------------------------------------. | yydefault -- do the default action for the current state. | `-----------------------------------------------------------*/ yydefault: yyn = yydefact[yystate]; if (yyn == 0) goto yyerrlab; goto yyreduce; /*-----------------------------. | yyreduce -- do a reduction. | `-----------------------------*/ yyreduce: /* yyn is the number of a rule to reduce with. */ yylen = yyr2[yyn]; /* If YYLEN is nonzero, implement the default value of the action: '$$ = $1'. Otherwise, the following line sets YYVAL to garbage. This behavior is undocumented and Bison users should not rely upon it. Assigning to YYVAL unconditionally makes the parser a bit smaller, and it avoids a GCC warning that YYVAL may be used uninitialized. */ yyval = yyvsp[1-yylen]; YY_REDUCE_PRINT (yyn); switch (yyn) { case 2: /* filter: expr */ #line 156 "event-filter-parser.y" { state->output = (yyvsp[0].node); } #line 1501 "event-filter-parser.c" break; case 3: /* filter: %empty */ #line 157 "event-filter-parser.y" { state->output = NULL; } #line 1507 "event-filter-parser.c" break; case 4: /* expr: expr AND expr */ #line 160 "event-filter-parser.y" { (yyval.node) = logic(state, (yyvsp[-2].node), (yyvsp[0].node), EVENT_FILTER_OP_AND); } #line 1513 "event-filter-parser.c" break; case 5: /* expr: expr OR expr */ #line 161 "event-filter-parser.y" { (yyval.node) = logic(state, (yyvsp[-2].node), (yyvsp[0].node), EVENT_FILTER_OP_OR); } #line 1519 "event-filter-parser.c" break; case 6: /* expr: NOT expr */ #line 162 "event-filter-parser.y" { (yyval.node) = logic(state, (yyvsp[0].node), NULL, EVENT_FILTER_OP_NOT); } #line 1525 "event-filter-parser.c" break; case 7: /* expr: '(' expr ')' */ #line 163 "event-filter-parser.y" { (yyval.node) = (yyvsp[-1].node); } #line 1531 "event-filter-parser.c" break; case 8: /* expr: key_value */ #line 164 "event-filter-parser.y" { (yyval.node) = (yyvsp[0].node); } #line 1537 "event-filter-parser.c" break; case 9: /* key_value: key op value */ #line 167 "event-filter-parser.y" { (yyval.node) = key_value(state, (yyvsp[-2].str), (yyvsp[0].str), (yyvsp[-1].op)); if ((yyval.node) == NULL) { yyerror(state, state->error); /* avoid compiler warning about yynerrs being set, but not used */ (void)yynerrs; YYERROR; } } #line 1551 "event-filter-parser.c" break; case 10: /* key: TOKEN */ #line 178 "event-filter-parser.y" { (yyval.str) = (yyvsp[0].str); } #line 1557 "event-filter-parser.c" break; case 11: /* key: STRING */ #line 179 "event-filter-parser.y" { (yyval.str) = (yyvsp[0].str); } #line 1563 "event-filter-parser.c" break; case 12: /* value: TOKEN */ #line 182 "event-filter-parser.y" { (yyval.str) = (yyvsp[0].str); } #line 1569 "event-filter-parser.c" break; case 13: /* value: STRING */ #line 183 "event-filter-parser.y" { (yyval.str) = (yyvsp[0].str); } #line 1575 "event-filter-parser.c" break; case 14: /* value: AND */ #line 184 "event-filter-parser.y" { (yyval.str) = "and"; } #line 1581 "event-filter-parser.c" break; case 15: /* value: OR */ #line 185 "event-filter-parser.y" { (yyval.str) = "or"; } #line 1587 "event-filter-parser.c" break; case 16: /* value: NOT */ #line 186 "event-filter-parser.y" { (yyval.str) = "not"; } #line 1593 "event-filter-parser.c" break; case 17: /* op: '=' */ #line 189 "event-filter-parser.y" { (yyval.op) = EVENT_FILTER_OP_CMP_EQ; } #line 1599 "event-filter-parser.c" break; case 18: /* op: '>' */ #line 190 "event-filter-parser.y" { (yyval.op) = EVENT_FILTER_OP_CMP_GT; } #line 1605 "event-filter-parser.c" break; case 19: /* op: '<' */ #line 191 "event-filter-parser.y" { (yyval.op) = EVENT_FILTER_OP_CMP_LT; } #line 1611 "event-filter-parser.c" break; case 20: /* op: '>' '=' */ #line 192 "event-filter-parser.y" { (yyval.op) = EVENT_FILTER_OP_CMP_GE; } #line 1617 "event-filter-parser.c" break; case 21: /* op: '<' '=' */ #line 193 "event-filter-parser.y" { (yyval.op) = EVENT_FILTER_OP_CMP_LE; } #line 1623 "event-filter-parser.c" break; #line 1627 "event-filter-parser.c" default: break; } /* User semantic actions sometimes alter yychar, and that requires that yytoken be updated with the new translation. We take the approach of translating immediately before every use of yytoken. One alternative is translating here after every semantic action, but that translation would be missed if the semantic action invokes YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an incorrect destructor might then be invoked immediately. In the case of YYERROR or YYBACKUP, subsequent parser actions might lead to an incorrect destructor call or verbose syntax error message before the lookahead is translated. */ YY_SYMBOL_PRINT ("-> $$ =", YY_CAST (yysymbol_kind_t, yyr1[yyn]), &yyval, &yyloc); YYPOPSTACK (yylen); yylen = 0; *++yyvsp = yyval; /* Now 'shift' the result of the reduction. Determine what state that goes to, based on the state we popped back to and the rule number reduced by. */ { const int yylhs = yyr1[yyn] - YYNTOKENS; const int yyi = yypgoto[yylhs] + *yyssp; yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp ? yytable[yyi] : yydefgoto[yylhs]); } goto yynewstate; /*--------------------------------------. | yyerrlab -- here on detecting error. | `--------------------------------------*/ yyerrlab: /* Make sure we have latest lookahead translation. See comments at user semantic actions for why this is necessary. */ yytoken = yychar == EVENT_FILTER_PARSER_EMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar); /* If not already recovering from an error, report this error. */ if (!yyerrstatus) { ++yynerrs; { yypcontext_t yyctx = {yyssp, yytoken}; char const *yymsgp = YY_("syntax error"); int yysyntax_error_status; yysyntax_error_status = yysyntax_error (&yymsg_alloc, &yymsg, &yyctx); if (yysyntax_error_status == 0) yymsgp = yymsg; else if (yysyntax_error_status == -1) { if (yymsg != yymsgbuf) YYSTACK_FREE (yymsg); yymsg = YY_CAST (char *, YYSTACK_ALLOC (YY_CAST (YYSIZE_T, yymsg_alloc))); if (yymsg) { yysyntax_error_status = yysyntax_error (&yymsg_alloc, &yymsg, &yyctx); yymsgp = yymsg; } else { yymsg = yymsgbuf; yymsg_alloc = sizeof yymsgbuf; yysyntax_error_status = YYENOMEM; } } yyerror (state, yymsgp); if (yysyntax_error_status == YYENOMEM) goto yyexhaustedlab; } } if (yyerrstatus == 3) { /* If just tried and failed to reuse lookahead token after an error, discard it. */ if (yychar <= EVENT_FILTER_PARSER_EOF) { /* Return failure if at end of input. */ if (yychar == EVENT_FILTER_PARSER_EOF) YYABORT; } else { yydestruct ("Error: discarding", yytoken, &yylval, state); yychar = EVENT_FILTER_PARSER_EMPTY; } } /* Else will try to reuse lookahead token after shifting the error token. */ goto yyerrlab1; /*---------------------------------------------------. | yyerrorlab -- error raised explicitly by YYERROR. | `---------------------------------------------------*/ yyerrorlab: /* Pacify compilers when the user code never invokes YYERROR and the label yyerrorlab therefore never appears in user code. */ if (0) YYERROR; /* Do not reclaim the symbols of the rule whose action triggered this YYERROR. */ YYPOPSTACK (yylen); yylen = 0; YY_STACK_PRINT (yyss, yyssp); yystate = *yyssp; goto yyerrlab1; /*-------------------------------------------------------------. | yyerrlab1 -- common code for both syntax error and YYERROR. | `-------------------------------------------------------------*/ yyerrlab1: yyerrstatus = 3; /* Each real token shifted decrements this. */ /* Pop stack until we find a state that shifts the error token. */ for (;;) { yyn = yypact[yystate]; if (!yypact_value_is_default (yyn)) { yyn += YYSYMBOL_YYerror; if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror) { yyn = yytable[yyn]; if (0 < yyn) break; } } /* Pop the current state because it cannot handle the error token. */ if (yyssp == yyss) YYABORT; yydestruct ("Error: popping", YY_ACCESSING_SYMBOL (yystate), yyvsp, state); YYPOPSTACK (1); yystate = *yyssp; YY_STACK_PRINT (yyss, yyssp); } YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN *++yyvsp = yylval; YY_IGNORE_MAYBE_UNINITIALIZED_END /* Shift the error token. */ YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp); yystate = yyn; goto yynewstate; /*-------------------------------------. | yyacceptlab -- YYACCEPT comes here. | `-------------------------------------*/ yyacceptlab: yyresult = 0; goto yyreturn; /*-----------------------------------. | yyabortlab -- YYABORT comes here. | `-----------------------------------*/ yyabortlab: yyresult = 1; goto yyreturn; #if 1 /*-------------------------------------------------. | yyexhaustedlab -- memory exhaustion comes here. | `-------------------------------------------------*/ yyexhaustedlab: yyerror (state, YY_("memory exhausted")); yyresult = 2; goto yyreturn; #endif /*-------------------------------------------------------. | yyreturn -- parsing is finished, clean up and return. | `-------------------------------------------------------*/ yyreturn: if (yychar != EVENT_FILTER_PARSER_EMPTY) { /* Make sure we have latest lookahead translation. See comments at user semantic actions for why this is necessary. */ yytoken = YYTRANSLATE (yychar); yydestruct ("Cleanup: discarding lookahead", yytoken, &yylval, state); } /* Do not reclaim the symbols of the rule whose action triggered this YYABORT or YYACCEPT. */ YYPOPSTACK (yylen); YY_STACK_PRINT (yyss, yyssp); while (yyssp != yyss) { yydestruct ("Cleanup: popping", YY_ACCESSING_SYMBOL (+*yyssp), yyvsp, state); YYPOPSTACK (1); } #ifndef yyoverflow if (yyss != yyssa) YYSTACK_FREE (yyss); #endif if (yymsg != yymsgbuf) YYSTACK_FREE (yymsg); return yyresult; } #line 195 "event-filter-parser.y" dovecot-2.3.21.1/src/lib/unix-socket-create.c0000644000000000000000000000135714656633576015536 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "net.h" #include "unix-socket-create.h" #include #include int unix_socket_create(const char *path, int mode, uid_t uid, gid_t gid, int backlog) { mode_t old_umask; int fd; old_umask = umask(0777 ^ mode); fd = net_listen_unix_unlink_stale(path, backlog); umask(old_umask); if (fd < 0) { i_error("net_listen_unix(%s) failed: %m", path); return -1; } if (uid != (uid_t)-1 || gid != (gid_t)-1) { /* set correct permissions */ if (chown(path, uid, gid) < 0) { i_error("chown(%s, %s, %s) failed: %m", path, dec2str(uid), dec2str(gid)); i_close_fd(&fd); return -1; } } return fd; } dovecot-2.3.21.1/src/lib/test-seq-set-builder.c0000644000000000000000000001201514656633576015777 00000000000000/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "str.h" #include "seq-set-builder.h" static void test_seq_set_builder_add(void) { struct seqset_builder *seq_set_builder; test_begin("seq set builder add"); string_t *test_str = t_str_new(128); str_append(test_str, "UID COPY "); seq_set_builder = seqset_builder_init(test_str); seqset_builder_add(seq_set_builder, 1); seqset_builder_add(seq_set_builder, 3); seqset_builder_add(seq_set_builder, 6); seqset_builder_add(seq_set_builder, 7); seqset_builder_add(seq_set_builder, 8); seqset_builder_add(seq_set_builder, 9); seqset_builder_add(seq_set_builder, 10); seqset_builder_add(seq_set_builder, 12); seqset_builder_deinit(&seq_set_builder); test_assert_strcmp(str_c(test_str), "UID COPY 1,3,6:10,12"); str_truncate(test_str, 0); seq_set_builder = seqset_builder_init(test_str); seqset_builder_add(seq_set_builder, 99999); seqset_builder_add(seq_set_builder, 100000); seqset_builder_add(seq_set_builder, 5); seqset_builder_add(seq_set_builder, 7); seqset_builder_add(seq_set_builder, 9); seqset_builder_add(seq_set_builder, 10); seqset_builder_add(seq_set_builder, 120); seqset_builder_add(seq_set_builder, 121); seqset_builder_add(seq_set_builder, 122); seqset_builder_add(seq_set_builder, 125); seqset_builder_deinit(&seq_set_builder); test_assert_strcmp(str_c(test_str), "99999:100000,5,7,9:10,120:122,125"); str_truncate(test_str, 0); str_append(test_str, "UID COPY "); seq_set_builder = seqset_builder_init(test_str); seqset_builder_add(seq_set_builder, 287409); seqset_builder_add(seq_set_builder, 287410); seqset_builder_deinit(&seq_set_builder); test_assert_strcmp(str_c(test_str), "UID COPY 287409:287410"); str_truncate(test_str, 0); str_append(test_str, "UID COPY 287409,"); seq_set_builder = seqset_builder_init(test_str); seqset_builder_add(seq_set_builder, 287410); seqset_builder_add(seq_set_builder, 287411); test_assert_strcmp(str_c(test_str), "UID COPY 287409,287410:287411,"); seqset_builder_deinit(&seq_set_builder); test_assert_strcmp(str_c(test_str), "UID COPY 287409,287410:287411"); str_truncate(test_str, 0); seq_set_builder = seqset_builder_init(test_str); seqset_builder_add(seq_set_builder, 4294967289); seqset_builder_add(seq_set_builder, 4294967291); seqset_builder_add(seq_set_builder, 4294967293); seqset_builder_add(seq_set_builder, 4294967294); seqset_builder_add(seq_set_builder, 4294967295); test_assert_strcmp(str_c(test_str), "4294967289,4294967291,4294967293:4294967295,"); seqset_builder_deinit(&seq_set_builder); test_assert_strcmp(str_c(test_str), "4294967289,4294967291,4294967293:4294967295"); str_truncate(test_str, 0); str_append(test_str, ";j;,"); seq_set_builder = seqset_builder_init(test_str); test_assert_strcmp(str_c(test_str), ";j;,"); seqset_builder_deinit(&seq_set_builder); test_assert_strcmp(str_c(test_str), ";j;,"); test_end(); } static void test_seq_set_builder_try_add(void) { struct seqset_builder *seq_set_builder; test_begin("seq set builder try add"); string_t *test_str = t_str_new(128); str_append(test_str, "UID MOVE "); seq_set_builder = seqset_builder_init(test_str); test_assert(seqset_builder_try_add(seq_set_builder, 20, 1)); test_assert(seqset_builder_try_add(seq_set_builder, 20, 3)); test_assert(seqset_builder_try_add(seq_set_builder, 20, 5)); test_assert(seqset_builder_try_add(seq_set_builder, 20, 7)); test_assert(seqset_builder_try_add(seq_set_builder, 20, 9)); test_assert(19 == str_len(test_str)); test_assert_strcmp(str_c(test_str), "UID MOVE 1,3,5,7,9,"); test_assert(!seqset_builder_try_add(seq_set_builder, 20, 11)); test_assert(str_len(test_str) <= 20); test_assert_strcmp(str_c(test_str), "UID MOVE 1,3,5,7,9,"); test_assert(seqset_builder_try_add(seq_set_builder, 21, 2)); test_assert(str_len(test_str) <= 21); test_assert_strcmp(str_c(test_str), "UID MOVE 1,3,5,7,9,2,"); test_assert(!seqset_builder_try_add(seq_set_builder, 20, 15)); test_assert(seqset_builder_try_add(seq_set_builder, 24, 13)); test_assert(!seqset_builder_try_add(seq_set_builder, 24, 17)); test_assert(str_len(test_str) <= 24); test_assert_strcmp(str_c(test_str), "UID MOVE 1,3,5,7,9,2,13,"); seqset_builder_deinit(&seq_set_builder); str_truncate(test_str, 0); seq_set_builder = seqset_builder_init(test_str); test_assert(seqset_builder_try_add(seq_set_builder, 32, 4294967289)); test_assert(seqset_builder_try_add(seq_set_builder, 32, 4294967291)); test_assert(seqset_builder_try_add(seq_set_builder, 32, 4294967292)); test_assert(!seqset_builder_try_add(seq_set_builder, 32, 4294967293)); test_assert(seqset_builder_try_add(seq_set_builder, 50, 4294967293)); test_assert(seqset_builder_try_add(seq_set_builder, 50, 4294967295)); test_assert_strcmp(str_c(test_str), "4294967289,4294967291:4294967293,4294967295,"); seqset_builder_deinit(&seq_set_builder); test_assert_strcmp(str_c(test_str), "4294967289,4294967291:4294967293,4294967295"); test_end(); } void test_seq_set_builder(void) { test_seq_set_builder_add(); test_seq_set_builder_try_add(); } dovecot-2.3.21.1/src/lib/guid.c0000644000000000000000000001111114656633576012741 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "buffer.h" #include "str.h" #include "sha1.h" #include "hash.h" #include "hex-binary.h" #include "hostpid.h" #include "guid.h" #include #include const char *guid_generate(void) { static struct timespec ts = { 0, 0 }; static unsigned int pid = 0; /* we'll use the current time in nanoseconds as the initial 64bit counter. */ if (ts.tv_sec == 0) { if (clock_gettime(CLOCK_REALTIME, &ts) < 0) i_fatal("clock_gettime() failed: %m"); pid = getpid(); } else if ((uint32_t)ts.tv_nsec < (uint32_t)-1) { ts.tv_nsec++; } else { ts.tv_sec++; ts.tv_nsec = 0; } return t_strdup_printf("%08x%08lx.%x.%s", (unsigned int)ts.tv_nsec, (unsigned long)ts.tv_sec, pid, my_hostname); } void guid_128_host_hash_get(const char *host, unsigned char hash_r[STATIC_ARRAY GUID_128_HOST_HASH_SIZE]) { unsigned char full_hash[SHA1_RESULTLEN]; sha1_get_digest(host, strlen(host), full_hash); memcpy(hash_r, full_hash + sizeof(full_hash)-GUID_128_HOST_HASH_SIZE, GUID_128_HOST_HASH_SIZE); } void guid_128_generate(guid_128_t guid_r) { #if GUID_128_HOST_HASH_SIZE != 4 # error GUID_128_HOST_HASH_SIZE must be 4 #endif static struct timespec ts = { 0, 0 }; static uint8_t guid_static[8]; uint32_t pid; /* we'll use the current time in nanoseconds as the initial 64bit counter. */ if (ts.tv_sec == 0) { if (clock_gettime(CLOCK_REALTIME, &ts) < 0) i_fatal("clock_gettime() failed: %m"); pid = getpid(); guid_static[0] = (pid & 0x000000ff); guid_static[1] = (pid & 0x0000ff00) >> 8; guid_static[2] = (pid & 0x00ff0000) >> 16; guid_static[3] = (pid & 0xff000000) >> 24; guid_128_host_hash_get(my_hostdomain(), guid_static+4); } else if (ioloop_timeval.tv_sec > ts.tv_sec || (ioloop_timeval.tv_sec == ts.tv_sec && ioloop_timeval.tv_usec * 1000 > ts.tv_nsec)) { /* use ioloop's time since we have it. it doesn't provide any more uniqueness, but it allows finding out more reliably when a GUID was created. */ ts.tv_sec = ioloop_timeval.tv_sec; ts.tv_nsec = ioloop_timeval.tv_usec*1000; } else if (ts.tv_nsec < 999999999L) { ts.tv_nsec++; } else { ts.tv_sec++; ts.tv_nsec = 0; } guid_r[0] = (ts.tv_nsec & 0x000000ff); guid_r[1] = (ts.tv_nsec & 0x0000ff00) >> 8; guid_r[2] = (ts.tv_nsec & 0x00ff0000) >> 16; guid_r[3] = (ts.tv_nsec & 0xff000000) >> 24; guid_r[4] = (ts.tv_sec & 0x000000ff); guid_r[5] = (ts.tv_sec & 0x0000ff00) >> 8; guid_r[6] = (ts.tv_sec & 0x00ff0000) >> 16; guid_r[7] = (ts.tv_sec & 0xff000000) >> 24; memcpy(guid_r + 8, guid_static, 8); } bool guid_128_is_empty(const guid_128_t guid) { unsigned int i; for (i = 0; i < GUID_128_SIZE; i++) { if (guid[i] != 0) return FALSE; } return TRUE; } bool guid_128_equals(const guid_128_t guid1, const guid_128_t guid2) { return memcmp(guid1, guid2, GUID_128_SIZE) == 0; } int guid_128_from_string(const char *str, guid_128_t guid_r) { buffer_t buf; buffer_create_from_data(&buf, guid_r, GUID_128_SIZE); return strlen(str) == GUID_128_SIZE*2 && hex_to_binary(str, &buf) == 0 && buf.used == GUID_128_SIZE ? 0 : -1; } const char *guid_128_to_string(const guid_128_t guid) { return binary_to_hex(guid, GUID_128_SIZE); } unsigned int guid_128_hash(const guid_128_t guid) { return mem_hash(guid, GUID_128_SIZE); } int guid_128_cmp(const guid_128_t guid1, const guid_128_t guid2) { return memcmp(guid1, guid2, GUID_128_SIZE); } const char *guid_128_to_uuid_string(const guid_128_t guid, enum uuid_format format) { switch(format) { case FORMAT_COMPACT: return guid_128_to_string(guid); case FORMAT_RECORD: return t_strdup_printf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", guid[0], guid[1], guid[2], guid[3], guid[4], guid[5], guid[6], guid[7], guid[8], guid[9], guid[10], guid[11], guid[12], guid[13], guid[14], guid[15]); case FORMAT_MICROSOFT: return t_strdup_printf("{%s}", guid_128_to_uuid_string(guid, FORMAT_RECORD)); } i_unreached(); } int guid_128_from_uuid_string(const char *str, guid_128_t guid_r) { size_t i,len,m=0; int ret; T_BEGIN { len = strlen(str); string_t *str2 = t_str_new(len); for(i=0; i < len; i++) { /* Microsoft format */ if (i==0 && str[i] == '{') { m=1; continue; } else if (i == len-1 && str[i] == '}') continue; /* 8-4-4-4-12 */ if (((i==8+m) || (i==13+m) || (i==18+m) || (i==23+m)) && str[i] == '-') continue; str_append_c(str2, str[i]); } ret = guid_128_from_string(str_c(str2), guid_r); } T_END; return ret; } dovecot-2.3.21.1/src/lib/utc-offset.c0000644000000000000000000000151214656633576014074 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "utc-offset.h" #include int utc_offset(struct tm *tm, time_t t ATTR_UNUSED) { #ifdef HAVE_TM_GMTOFF return (int) (tm->tm_gmtoff/60); #else struct tm ltm, gtm; int offset; /* gmtime() overwrites tm, so we need to copy it elsewhere */ ltm = *tm; tm = gmtime(&t); gtm = *tm; /* max offset of 24 hours */ if ((ltm.tm_yday < gtm.tm_yday && ltm.tm_year == gtm.tm_year) || ltm.tm_year < gtm.tm_year) offset = -24 * 60; else if ((ltm.tm_yday > gtm.tm_yday && ltm.tm_year == gtm.tm_year) || ltm.tm_year > gtm.tm_year) offset = 24 * 60; else offset = 0; offset += (ltm.tm_hour - gtm.tm_hour) * 60; offset += (ltm.tm_min - gtm.tm_min); /* restore overwritten tm */ *tm = ltm; return offset; #endif } dovecot-2.3.21.1/src/lib/ostream-null.h0000644000000000000000000000023614656633576014446 00000000000000#ifndef OSTREAM_NULL_H #define OSTREAM_NULL_H /* Create an output stream that ignores all the writes. */ struct ostream *o_stream_create_null(void); #endif dovecot-2.3.21.1/src/lib/failures-private.h0000644000000000000000000000141614656633576015307 00000000000000#ifndef FAILURES_PRIVATE_H #define FAILURES_PRIVATE_H typedef int failure_write_to_file_t(enum log_type type, string_t *data, size_t prefix_len); typedef string_t * failure_format_str_t(const struct failure_context *ctx, size_t *prefix_len_r, const char *format, va_list args); typedef void failure_on_handler_failure_t(const struct failure_context *ctx); typedef void failure_post_handler_t(const struct failure_context *ctx); struct failure_handler_vfuncs { failure_write_to_file_t *write; failure_format_str_t *format; failure_on_handler_failure_t *on_handler_failure; failure_post_handler_t *post_handler; }; struct failure_handler_config { int fatal_err_reset; struct failure_handler_vfuncs *v; }; extern struct failure_handler_config failure_handler; #endif dovecot-2.3.21.1/src/lib/test-array.c0000644000000000000000000002763714656633576014130 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "array.h" struct foo { unsigned int a, b, c; }; static void test_array_elem(void) { ARRAY(struct foo *) foos; struct foo *nfoo; struct foo *foo; struct foo local_foo; unsigned int i; test_begin("array elem"); t_array_init(&foos, 32); foo = &local_foo; array_foreach_elem(&foos, foo) test_assert(FALSE); test_assert(foo == &local_foo); for (i = 1; i <= 3; i++) { nfoo = t_new(struct foo, 1); nfoo->a = i; array_push_back(&foos, &nfoo); } struct foo *const *foo_p = array_idx(&foos, 1); unsigned int idx = 1; foo = array_idx_elem(&foos, idx++); /* make sure idx isn't expanded multiple times in the macro */ test_assert(idx == 2); test_assert(*foo_p == foo); i = 1; array_foreach_elem(&foos, foo) { test_assert(foo->a == i); i++; } test_assert(foo->a == i-1); test_end(); } static void test_array_count(void) { ARRAY(struct foo) foos; struct foo nfoo; test_begin("array count/empty"); t_array_init(&foos, 32); test_assert(array_count(&foos) == 0); test_assert(array_is_empty(&foos)); test_assert(!array_not_empty(&foos)); nfoo.a = nfoo.b = nfoo.c = 9; array_push_back(&foos, &nfoo); test_assert(array_count(&foos) == 1); test_assert(!array_is_empty(&foos)); test_assert(array_not_empty(&foos)); test_end(); } static void test_array_foreach(void) { ARRAY(struct foo) foos; const struct foo *foo; struct foo nfoo; unsigned int i; test_begin("array foreach"); t_array_init(&foos, 32); for (i = 0; i < 10; i++) { nfoo.a = nfoo.b = nfoo.c = i; array_push_back(&foos, &nfoo); } array_foreach(&foos, foo) { i = array_foreach_idx(&foos, foo); test_assert(foo->a == i); test_assert(foo->b == i); test_assert(foo->c == i); } /* points past the last element */ test_assert(foo == array_idx(&foos, i)+1); test_end(); } static void test_array_foreach_reverse(void) { ARRAY(unsigned int) arr; const unsigned int *i_p; unsigned int i, i2, *imod_p; test_begin("array foreach reverse"); t_array_init(&arr, 32); /* first test that array_foreach() + array_delete() doesn't really work as we might hope.. */ for (i = 1; i <= 5; i++) array_push_back(&arr, &i); array_foreach(&arr, i_p) { i = array_foreach_idx(&arr, i_p); array_delete(&arr, i, 1); } test_assert(array_count(&arr) == 2); /* but using array_foreach_reverse() + array_delete() does work: */ array_clear(&arr); i2 = 5; for (i = 1; i <= i2; i++) array_push_back(&arr, &i); array_foreach_reverse(&arr, i_p) { i = array_foreach_idx(&arr, i_p); test_assert(*i_p == i2); test_assert(*i_p == i + 1); array_delete(&arr, i, 1); i2--; } test_assert(array_count(&arr) == 0); /* also array_foreach_reverse_modifiable() + array_delete() works: */ i2 = 5; for (i = 1; i <= i2; i++) array_push_back(&arr, &i); array_foreach_reverse_modifiable(&arr, imod_p) { i = array_foreach_idx(&arr, imod_p); test_assert(*imod_p == i2); test_assert(*imod_p == i + 1); array_delete(&arr, i, 1); i2--; } test_assert(array_count(&arr) == 0); test_end(); } static void test_array_foreach_elem_string(void) { ARRAY(char *) blurbs; ARRAY(const char *) cblurbs; char *string; const char *cstring; int i; test_begin("array foreach_elem ro/rw strings"); t_array_init(&blurbs, 32); t_array_init(&cblurbs, 32); for (i = 0; i < 10; i++) { cstring = t_strdup_printf("x%iy", i); string = t_strdup_noconst(cstring); array_push_back(&blurbs, &string); array_push_back(&cblurbs, &cstring); } i = 0; array_foreach_elem(&blurbs, string) { test_assert_idx(string[0] == 'x' && string[1]-'0' == i && string[2] == 'y', i); i++; } i = 0; array_foreach_elem(&cblurbs, cstring) { test_assert_idx(cstring[0] == 'x' && cstring[1]-'0' == i && cstring[2] == 'y', i); i++; } test_end(); } static int test_int_compare(const int *key, const int *elem) { return (*key < *elem) ? -1 : (*key > *elem) ? 1 : 0; } static void test_array_reverse(void) { ARRAY(int) intarr; int input[] = { -1234567890, -272585721, 272485922, 824725652 }; const int tmpi = 999, *output; unsigned int i, j; test_begin("array reverse"); t_array_init(&intarr, 5); for (i = 0; i <= N_ELEMENTS(input); i++) { array_clear(&intarr); array_append(&intarr, input, i); array_reverse(&intarr); output = i == 0 ? NULL : array_front(&intarr); for (j = 0; j < i; j++) test_assert(input[i-j-1] == output[j]); } test_end(); test_begin("array_lsearch"); for (i = 0; i < N_ELEMENTS(input); i++) { output = array_lsearch(&intarr, &input[i], test_int_compare); test_assert(output != NULL); j = array_ptr_to_idx(&intarr, output); test_assert_idx(j == N_ELEMENTS(input) - 1 - i, i); } output = array_lsearch(&intarr, &tmpi, test_int_compare); test_assert(output == NULL); test_end(); } static int test_compare_ushort(const unsigned short *c1, const unsigned short *c2) { return *c1 > *c2 ? 1 : *c1 < *c2 ? -1 : 0; } static int test_compare_ushort_fuzz(const unsigned short *c1, const unsigned short *c2, const int *pfuzz) { int d = (int)*c1 - (int)*c2; if (d <= *pfuzz && -d <= *pfuzz) return 0; return d; } static void test_array_cmp(void) { static const unsigned short deltas[] = { 0x8000, 0xc000, 0xfe00, 0xff00, 0xff80, 0xffc0, 0xfffe, 0xffff, 0, 1, 2, 64, 128, 256, 512, 16384, 32768 }; #define NELEMS 5u ARRAY(unsigned short) arr1, arr2; unsigned short elems[NELEMS+1]; unsigned int i; int fuzz; test_begin("array compare (ushort)"); t_array_init(&arr1, NELEMS); t_array_init(&arr2, NELEMS); for (i = 0; i < NELEMS; i++) { elems[i] = i_rand_ushort(); array_push_back(&arr2, &elems[i]); } array_append(&arr1, elems, NELEMS); test_assert(array_cmp(&arr1, &arr2) == TRUE); test_assert(array_equal_fn(&arr1, &arr2, test_compare_ushort) == TRUE); fuzz = 0; test_assert(array_equal_fn_ctx(&arr1, &arr2, test_compare_ushort_fuzz, &fuzz) == TRUE); for (i = 0; i < 256; i++) { unsigned int j = i_rand_limit(NELEMS); const unsigned short *ptmp = array_idx(&arr2, j); unsigned short tmp = *ptmp; unsigned short repl = ((unsigned int)tmp + deltas[i_rand_limit(N_ELEMENTS(deltas))]) & 0xffff; array_idx_set(&arr2, j, &repl); test_assert_idx(array_cmp(&arr1, &arr2) == (tmp == repl), i); test_assert_idx(array_equal_fn(&arr1, &arr2, test_compare_ushort) == (tmp == repl), i); fuzz = (int)tmp - (int)repl; if (fuzz < 0) fuzz = -fuzz; test_assert_idx(array_equal_fn_ctx(&arr1, &arr2, test_compare_ushort_fuzz, &fuzz) == TRUE, i); if (fuzz > 0) { fuzz--; test_assert_idx(array_equal_fn_ctx(&arr1, &arr2, test_compare_ushort_fuzz, &fuzz) == FALSE, i); } array_idx_set(&arr2, j, &tmp); test_assert_idx(array_cmp(&arr1, &arr2) == TRUE, i); test_assert_idx(array_equal_fn(&arr1, &arr2, test_compare_ushort) == TRUE, i); fuzz = 0; test_assert_idx(array_equal_fn_ctx(&arr1, &arr2, test_compare_ushort_fuzz, &fuzz) == TRUE, i); } elems[NELEMS] = 0; array_push_back(&arr2, &elems[NELEMS]); test_assert(array_cmp(&arr1, &arr2) == FALSE); test_assert(array_equal_fn(&arr1, &arr2, test_compare_ushort) == FALSE); test_assert_idx(array_equal_fn_ctx(&arr1, &arr2, test_compare_ushort_fuzz, &fuzz) == FALSE, i); test_end(); } static void test_array_cmp_str(void) { #define NELEMS 5u ARRAY(const char *) arr1, arr2; const char *elemstrs[NELEMS+1]; unsigned int i; test_begin("array compare (char*)"); t_array_init(&arr1, NELEMS); t_array_init(&arr2, NELEMS); for (i = 0; i < NELEMS; i++) { elemstrs[i] = t_strdup_printf("%x", i_rand()); /* never 0-length */ array_push_back(&arr2, &elemstrs[i]); } array_append(&arr1, elemstrs, NELEMS); test_assert(array_cmp(&arr1, &arr2) == TRUE); /* pointers shared, so identical */ test_assert(array_equal_fn(&arr1, &arr2, i_strcmp_p) == TRUE); /* therefore value same */ for (i = 0; i < 2560; i++) { unsigned int j = i_rand_limit(NELEMS); const char *const *ostr_p = array_idx(&arr2, j); const char *ostr = *ostr_p; unsigned int olen = strlen(ostr); unsigned int rc = i_rand_limit(olen + 1); char ochar = ostr[rc]; char buf[12]; const char *bufp = buf; memcpy(buf, ostr, olen+1); buf[rc] = (int32_t)i_rand_limit(CHAR_MAX + 1 - CHAR_MIN) + CHAR_MIN; if(rc == olen) buf[rc+1] = '\0'; array_idx_set(&arr2, j, &bufp); test_assert(array_cmp(&arr1, &arr2) == FALSE); /* pointers now differ */ test_assert_idx(array_equal_fn(&arr1, &arr2, i_strcmp_p) == (strcmp(ostr, buf) == 0), i); /* sometimes still the same */ test_assert_idx(array_equal_fn(&arr1, &arr2, i_strcmp_p) == (ochar == buf[rc]), i); /* ditto */ array_idx_set(&arr2, j, &ostr); test_assert(array_cmp(&arr1, &arr2) == TRUE); /* pointers now same again */ test_assert_idx(array_equal_fn(&arr1, &arr2, i_strcmp_p) == TRUE, i); /* duh! */ } /* length differences being detected are tested in other tests */ test_end(); } static void test_array_free_case(bool keep) { pool_t pool = pool_allocfree_create("array test"); ARRAY(int) r; int *p; test_begin(keep ? "array_free" : "array_free_without_data"); p_array_init(&r, pool, 100); array_append_zero(&r); if (keep) { p = array_free_without_data(&r); test_assert(pool_allocfree_get_total_used_size(pool)>=400); p_free(pool, p); } else { array_free(&r); test_assert(pool_allocfree_get_total_used_size(pool)==0); } pool_unref(&pool); test_end(); } static void test_array_free(void) { test_array_free_case(FALSE); test_array_free_case(TRUE); } void test_array(void) { test_array_elem(); test_array_count(); test_array_foreach(); test_array_foreach_reverse(); test_array_foreach_elem_string(); test_array_reverse(); test_array_cmp(); test_array_cmp_str(); test_array_free(); } enum fatal_test_state fatal_array(unsigned int stage) { double tmpd[2] = { 42., -42. }; short tmps[8] = {1,2,3,4,5,6,7,8}; static const void *useless_ptr; /* persuade gcc to not optimise the tests */ switch(stage) { case 0: { ARRAY(double) ad; test_begin("fatal_array"); t_array_init(&ad, 3); /* allocation big enough, but memory not initialised */ test_expect_fatal_string("(array_idx_i): assertion failed: (idx < array->buffer->used / array->element_size)"); useless_ptr = array_front(&ad); return FATAL_TEST_FAILURE; } case 1: { ARRAY(double) ad; t_array_init(&ad, 2); array_append(&ad, tmpd, 2); /* actual out of range address requested */ test_expect_fatal_string("(array_idx_i): assertion failed: (idx < array->buffer->used / array->element_size)"); useless_ptr = array_idx(&ad, 2); return FATAL_TEST_FAILURE; } case 2: { ARRAY(double) ad; ARRAY(short) as; t_array_init(&ad, 2); t_array_init(&as, 8); array_append(&as, tmps, 2); /* can't copy different array sizes */ test_expect_fatal_string("(array_copy): assertion failed: (dest->element_size == src->element_size)"); array_copy(&ad.arr, 1, &as.arr, 0, 4); return FATAL_TEST_FAILURE; } case 3: { ARRAY(uint8_t) arr; /* Allocate value dynamically, so compiler won't know the allocated memory size and output a warning that it's too small for array_append(). */ uint8_t *value = t_malloc0(1); t_array_init(&arr, 2); array_push_back(&arr, value); test_expect_fatal_string("Buffer write out of range"); /* this is supposed to assert-crash before it even attempts to access value */ array_append(&arr, value, UINT_MAX); return FATAL_TEST_FAILURE; } case 4: { ARRAY(uint32_t) arr; /* Allocate value dynamically (see above for reasoning). */ uint32_t *value = t_malloc0(1); t_array_init(&arr, 2); array_push_back(&arr, value); test_expect_fatal_string("Buffer write out of range"); /* this is supposed to assert-crash before it even attempts to access value */ array_append(&arr, value, UINT_MAX); return FATAL_TEST_FAILURE; } } test_end(); /* Forces the compiler to check the value of useless_ptr, so that it must call array_idx (which is marked as pure, and gcc was desperate to optimise out. Of course, gcc is unaware stage is never UINT_MAX.*/ return (useless_ptr != NULL && stage == UINT_MAX) ? FATAL_TEST_FAILURE : FATAL_TEST_FINISHED; } dovecot-2.3.21.1/src/lib/bsearch-insert-pos.h0000644000000000000000000000425214656633576015536 00000000000000#ifndef BSEARCH_INSERT_POS_H #define BSEARCH_INSERT_POS_H /* Binary search template - getdata must be the name of a pure function or a function-like macro that takes the two obvious parameters. */ #define BINARY_NUMERIC_SEARCH(getdata, data, count, value, idx_r) \ unsigned int idx, left_idx, right_idx; \ \ i_assert((count) < INT_MAX); \ left_idx = 0; right_idx = (count); \ while (left_idx < right_idx) { \ idx = (left_idx + right_idx) / 2; \ \ if (getdata(data, idx) < (value)) \ left_idx = idx+1; \ else if (getdata(data, idx) > (value))\ right_idx = idx; \ else { \ *(idx_r) = idx; \ return TRUE; \ } \ } \ return FALSE #define BINARY_SEARCH_ARRAY_GET(array, index) ((array)[(index)]) #define BINARY_NUMBER_SEARCH(data, count, value, idx_r) \ BINARY_NUMERIC_SEARCH(BINARY_SEARCH_ARRAY_GET, data, count, value, idx_r); /* If key is found, returns TRUE and sets idx_r to the position where the key was found. If key isn't found, returns FALSE and sets idx_r to the position where the key should be inserted. */ bool ATTR_NOWARN_UNUSED_RESULT bsearch_insert_pos(const void *key, const void *base, unsigned int nmemb, size_t size, int (*cmp)(const void *, const void *), unsigned int *idx_r); #define bsearch_insert_pos(key, base, nmemb, size, cmp, idx_r) \ bsearch_insert_pos(key, base, nmemb, size - \ CALLBACK_TYPECHECK(cmp, int (*)(typeof(const typeof(*key) *), \ typeof(const typeof(*base) *))), \ (int (*)(const void *, const void *))cmp, idx_r) bool ATTR_NOWARN_UNUSED_RESULT array_bsearch_insert_pos_i(const struct array *array, const void *key, int (*cmp)(const void *, const void *), unsigned int *idx_r); #define array_bsearch_insert_pos(array, key, cmp, idx_r) \ array_bsearch_insert_pos_i(&(array)->arr - \ CALLBACK_TYPECHECK(cmp, int (*)(typeof(const typeof(*key) *), \ typeof(*(array)->v))), \ (const void *)key, (int (*)(const void *, const void *))cmp, idx_r) #endif dovecot-2.3.21.1/src/lib/json-parser.h0000644000000000000000000000421514656633576014270 00000000000000#ifndef JSON_PARSER_H #define JSON_PARSER_H #include "unichar.h" enum json_type { /* { key: */ JSON_TYPE_OBJECT_KEY, /* : { new object */ JSON_TYPE_OBJECT, /* } (not returned for the root object) */ JSON_TYPE_OBJECT_END, JSON_TYPE_ARRAY, JSON_TYPE_ARRAY_END, JSON_TYPE_STRING, JSON_TYPE_NUMBER, JSON_TYPE_TRUE, JSON_TYPE_FALSE, JSON_TYPE_NULL }; enum json_parser_flags { /* By default we assume that the input is an object and parsing skips the root level "{" and "}". If this flag is set, it's possible to parse any other type of JSON values directly. */ JSON_PARSER_NO_ROOT_OBJECT = 0x01 }; /* Parse JSON tokens from the input stream. */ struct json_parser *json_parser_init(struct istream *input); struct json_parser *json_parser_init_flags(struct istream *input, enum json_parser_flags flags); int json_parser_deinit(struct json_parser **parser, const char **error_r); /* Parse the next token. Returns 1 if found, 0 if more input stream is non-blocking and needs more input, -1 if input stream is at EOF. */ int json_parse_next(struct json_parser *parser, enum json_type *type_r, const char **value_r); /* Skip the next object value. If it's an object, its members are also skipped. */ void json_parse_skip_next(struct json_parser *parser); /* Skip the remainder of the value parsed earlier by json_parse_next(). */ void json_parse_skip(struct json_parser *parser); /* Return the following string as input stream. Returns 1 if ok, 0 if input stream is non-blocking and needs more input, -1 if the next token isn't a string (call json_parse_next()). */ int json_parse_next_stream(struct json_parser *parser, struct istream **input_r); /* Append UCS4 to already opened JSON string. */ void json_append_escaped_ucs4(string_t *dest, unichar_t chr); /* Append data to already opened JSON string. src should be valid UTF-8 data. */ void json_append_escaped(string_t *dest, const char *src); /* Same as json_append_escaped(), but append non-\0 terminated input. */ void json_append_escaped_data(string_t *dest, const unsigned char *src, size_t size); void ostream_escaped_json_format(string_t *dest, unsigned char src); #endif dovecot-2.3.21.1/src/lib/iostream-proxy.h0000644000000000000000000000621314656633576015027 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #ifndef IOSTREAM_PROXY_H #define IOSTREAM_PROXY_H 1 /** iostream-proxy ============= This construct will proxy data between two pairs of istream and ostream. Data is proxied from left to right and right to left using iostream-pump. The proxy requires you to provide completion callback. The completion callback is called with success parameter to indicate whether it ended with error. The istreams and ostreams are reffed on creation and unreffed on unref. **/ struct istream; struct ostream; struct iostream_proxy; enum iostream_proxy_side { /* Input is coming from left side's istream and is proxied to right side's ostream. */ IOSTREAM_PROXY_SIDE_LEFT, /* Input is coming from right side's istream and is proxied to left side's ostream. */ IOSTREAM_PROXY_SIDE_RIGHT }; enum iostream_proxy_status { /* proxy succeeded - EOF received from istream and all output was written successfully to ostream. */ IOSTREAM_PROXY_STATUS_INPUT_EOF, /* proxy failed - istream returned an error */ IOSTREAM_PROXY_STATUS_INPUT_ERROR, /* proxy failed - other side's ostream returned an error */ IOSTREAM_PROXY_STATUS_OTHER_SIDE_OUTPUT_ERROR, }; /* The callback maybe be called once or twice. Usually the first call should destroy the proxy, but it's possible for it to just wait for the other direction of the proxy to finish as well and call the callback. Note that the sides mean which side is the reader side. If the failure is in ostream, it's the other side's ostream that failed. So for example if side=left, the write failed to the right side's ostream. The callback is called when the proxy succeeds or fails due to iostreams. (It's not called if proxy is destroyed.) */ typedef void iostream_proxy_callback_t(enum iostream_proxy_side side, enum iostream_proxy_status status, void *context); struct iostream_proxy * iostream_proxy_create(struct istream *left_input, struct ostream *left_output, struct istream *right_input, struct ostream *right_output); struct istream *iostream_proxy_get_istream(struct iostream_proxy *proxy, enum iostream_proxy_side); struct ostream *iostream_proxy_get_ostream(struct iostream_proxy *proxy, enum iostream_proxy_side); void iostream_proxy_start(struct iostream_proxy *proxy); void iostream_proxy_stop(struct iostream_proxy *proxy); /* See iostream_pump_is_waiting_output() */ bool iostream_proxy_is_waiting_output(struct iostream_proxy *proxy, enum iostream_proxy_side side); void iostream_proxy_set_completion_callback(struct iostream_proxy *proxy, iostream_proxy_callback_t *callback, void *context); #define iostream_proxy_set_completion_callback(proxy, callback, context) \ iostream_proxy_set_completion_callback(proxy, (iostream_proxy_callback_t *)callback, \ TRUE ? context : \ CALLBACK_TYPECHECK(callback, void (*)(enum iostream_proxy_side side, enum iostream_proxy_status, typeof(context)))) void iostream_proxy_ref(struct iostream_proxy *proxy); void iostream_proxy_unref(struct iostream_proxy **proxy_r); void iostream_proxy_switch_ioloop(struct iostream_proxy *proxy); #endif dovecot-2.3.21.1/src/lib/istream-tee.h0000644000000000000000000000132314656633576014241 00000000000000#ifndef ISTREAM_TEE_H #define ISTREAM_TEE_H /* Tee can be used to create multiple child input streams which can access a single non-blocking input stream in a way that data isn't removed from memory until all child streams have consumed the input. If the stream's buffer gets full because some child isn't consuming the data, other streams get returned 0 by i_stream_read(). */ struct tee_istream *tee_i_stream_create(struct istream *input); /* Returns TRUE if last read() operation returned 0, because it was waiting for another tee stream to read more of its data. */ bool tee_i_stream_child_is_waiting(struct istream *input); struct istream *tee_i_stream_create_child(struct tee_istream *tee); #endif dovecot-2.3.21.1/src/lib/write-full.c0000644000000000000000000000200314656633576014103 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "write-full.h" #include int write_full(int fd, const void *data, size_t size) { ssize_t ret; while (size > 0) { ret = write(fd, data, size < SSIZE_T_MAX ? size : SSIZE_T_MAX); if (unlikely(ret < 0)) return -1; if (unlikely(ret == 0)) { /* nothing was written, only reason for this should be out of disk space */ errno = ENOSPC; return -1; } data = CONST_PTR_OFFSET(data, ret); size -= ret; } return 0; } int pwrite_full(int fd, const void *data, size_t size, off_t offset) { ssize_t ret; while (size > 0) { ret = pwrite(fd, data, size < SSIZE_T_MAX ? size : SSIZE_T_MAX, offset); if (unlikely(ret < 0)) return -1; if (unlikely(ret == 0)) { /* nothing was written, only reason for this should be out of disk space */ errno = ENOSPC; return -1; } data = CONST_PTR_OFFSET(data, ret); size -= ret; offset += ret; } return 0; } dovecot-2.3.21.1/src/lib/istream-file.c0000644000000000000000000001651214656633576014404 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ /* @UNSAFE: whole file */ #include "lib.h" #include "ioloop.h" #include "istream-file-private.h" #include "net.h" #include #include #include #include void i_stream_file_close(struct iostream_private *stream, bool close_parent ATTR_UNUSED) { struct istream_private *_stream = container_of(stream, struct istream_private, iostream); struct file_istream *fstream = container_of(_stream, struct file_istream, istream); if (fstream->autoclose_fd && _stream->fd != -1) { /* Ignore ECONNRESET because we don't really care about it here, as we are closing the socket down in any case. There might be unsent data but nothing we can do about that. */ if (unlikely(close(_stream->fd) < 0 && errno != ECONNRESET)) { i_error("file_istream.close(%s) failed: %m", i_stream_get_name(&_stream->istream)); } } _stream->fd = -1; } static int i_stream_file_open(struct istream_private *stream) { const char *path = i_stream_get_name(&stream->istream); stream->fd = open(path, O_RDONLY); if (stream->fd == -1) { io_stream_set_error(&stream->iostream, "open(%s) failed: %m", path); stream->istream.stream_errno = errno; return -1; } return 0; } ssize_t i_stream_file_read(struct istream_private *stream) { struct file_istream *fstream = container_of(stream, struct file_istream, istream); uoff_t offset; size_t size; ssize_t ret; if (!i_stream_try_alloc(stream, 1, &size)) return -2; if (stream->fd == -1) { if (i_stream_file_open(stream) < 0) return -1; i_assert(stream->fd != -1); } offset = stream->istream.v_offset + (stream->pos - stream->skip); if (fstream->file) { ret = pread(stream->fd, stream->w_buffer + stream->pos, size, offset); } else if (fstream->seen_eof) { /* don't try to read() again. EOF from keyboard (^D) requires this to work right. */ ret = 0; } else { ret = read(stream->fd, stream->w_buffer + stream->pos, size); } if (ret == 0) { /* EOF */ stream->istream.eof = TRUE; fstream->seen_eof = TRUE; return -1; } if (unlikely(ret < 0)) { if ((errno == EINTR || errno == EAGAIN) && !stream->istream.blocking) { ret = 0; } else { i_assert(errno != 0); /* if we get EBADF for a valid fd, it means something's really wrong and we'd better just crash. */ i_assert(errno != EBADF); if (fstream->file) { io_stream_set_error(&stream->iostream, "pread(size=%zu offset=%"PRIuUOFF_T") failed: %m", size, offset); } else { io_stream_set_error(&stream->iostream, "read(size=%zu) failed: %m", size); } stream->istream.stream_errno = errno; return -1; } } if (ret > 0 && fstream->skip_left > 0) { i_assert(!fstream->file); i_assert(stream->skip == stream->pos); if (fstream->skip_left >= (size_t)ret) { fstream->skip_left -= ret; ret = 0; } else { ret -= fstream->skip_left; stream->pos += fstream->skip_left; stream->skip += fstream->skip_left; fstream->skip_left = 0; } } stream->pos += ret; i_assert(ret != 0 || !fstream->file); i_assert(ret != -1); return ret; } static void i_stream_file_seek(struct istream_private *stream, uoff_t v_offset, bool mark ATTR_UNUSED) { struct file_istream *fstream = container_of(stream, struct file_istream, istream); if (!stream->istream.seekable) { if (v_offset < stream->istream.v_offset) i_panic("stream doesn't support seeking backwards"); fstream->skip_left += v_offset - stream->istream.v_offset; } stream->istream.v_offset = v_offset; stream->skip = stream->pos = 0; fstream->seen_eof = FALSE; } static void i_stream_file_sync(struct istream_private *stream) { if (!stream->istream.seekable) { /* can't do anything or data would be lost */ return; } stream->skip = stream->pos = 0; stream->istream.eof = FALSE; } static int i_stream_file_stat(struct istream_private *stream, bool exact ATTR_UNUSED) { struct file_istream *fstream = container_of(stream, struct file_istream, istream); const char *name = i_stream_get_name(&stream->istream); if (!fstream->file) { /* return defaults */ } else if (stream->fd != -1) { if (fstat(stream->fd, &stream->statbuf) < 0) { stream->istream.stream_errno = errno; io_stream_set_error(&stream->iostream, "file_istream.fstat(%s) failed: %m", name); i_error("%s", i_stream_get_error(&stream->istream)); return -1; } } else { if (stat(name, &stream->statbuf) < 0) { stream->istream.stream_errno = errno; io_stream_set_error(&stream->iostream, "file_istream.stat(%s) failed: %m", name); i_error("%s", i_stream_get_error(&stream->istream)); return -1; } } return 0; } struct istream * i_stream_create_file_common(struct file_istream *fstream, int fd, const char *path, size_t max_buffer_size, bool autoclose_fd) { struct istream *input; struct stat st; bool is_file; int flags; fstream->autoclose_fd = autoclose_fd; fstream->istream.iostream.close = i_stream_file_close; fstream->istream.max_buffer_size = max_buffer_size; fstream->istream.read = i_stream_file_read; fstream->istream.seek = i_stream_file_seek; fstream->istream.sync = i_stream_file_sync; fstream->istream.stat = i_stream_file_stat; /* if it's a file, set the flags properly */ if (fd == -1) { /* only the path is known for now - the fd is opened later */ is_file = TRUE; } else if (fstat(fd, &st) < 0) is_file = FALSE; else if (S_ISREG(st.st_mode)) is_file = TRUE; else if (!S_ISDIR(st.st_mode)) is_file = FALSE; else { /* we're trying to open a directory. we're not designed for it. */ io_stream_set_error(&fstream->istream.iostream, "%s is a directory, can't read it as file", path != NULL ? path : t_strdup_printf("", fd)); fstream->istream.istream.stream_errno = EISDIR; is_file = FALSE; } if (is_file) { fstream->file = TRUE; fstream->istream.istream.blocking = TRUE; fstream->istream.istream.seekable = TRUE; } else if ((flags = fcntl(fd, F_GETFL, 0)) < 0) { i_assert(fd > -1); /* shouldn't happen */ fstream->istream.istream.stream_errno = errno; io_stream_set_error(&fstream->istream.iostream, "fcntl(%d, F_GETFL) failed: %m", fd); } else if ((flags & O_NONBLOCK) == 0) { /* blocking socket/fifo */ fstream->istream.istream.blocking = TRUE; } fstream->istream.istream.readable_fd = TRUE; input = i_stream_create(&fstream->istream, NULL, fd, 0); i_stream_set_name(input, is_file ? "(file)" : "(fd)"); return input; } struct istream *i_stream_create_fd(int fd, size_t max_buffer_size) { struct file_istream *fstream; i_assert(fd != -1); fstream = i_new(struct file_istream, 1); return i_stream_create_file_common(fstream, fd, NULL, max_buffer_size, FALSE); } struct istream *i_stream_create_fd_autoclose(int *fd, size_t max_buffer_size) { struct istream *input; struct file_istream *fstream; i_assert(*fd != -1); fstream = i_new(struct file_istream, 1); input = i_stream_create_file_common(fstream, *fd, NULL, max_buffer_size, TRUE); *fd = -1; return input; } struct istream *i_stream_create_file(const char *path, size_t max_buffer_size) { struct file_istream *fstream; struct istream *input; fstream = i_new(struct file_istream, 1); input = i_stream_create_file_common(fstream, -1, path, max_buffer_size, TRUE); i_stream_set_name(input, path); return input; } dovecot-2.3.21.1/src/lib/test-strfuncs.c0000644000000000000000000004736314656633576014657 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "array.h" static void test_p_strdup(void) { test_begin("p_strdup()"); test_assert(p_strdup(default_pool, NULL) == NULL); const char *src = "foo"; char *str = p_strdup(default_pool, src); test_assert(str != src && str != NULL && strcmp(src, str) == 0); p_free(default_pool, str); test_end(); } static void test_p_strndup(void) { struct { const char *input; const char *output; size_t len; } tests[] = { { "foo", "fo", 2 }, { "foo", "foo", 3 }, { "foo", "foo", 4 }, { "foo\0more", "foo", 8 }, }; test_begin("p_strndup()"); for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { char *str = p_strndup(default_pool, tests[i].input, tests[i].len); test_assert_strcmp_idx(str, tests[i].output, i); p_free(default_pool, str); } test_end(); } static void test_p_strdup_empty(void) { test_begin("p_strdup_empty()"); test_assert(p_strdup_empty(default_pool, NULL) == NULL); test_assert(p_strdup_empty(default_pool, "") == NULL); const char *src = "foo"; char *str = p_strdup_empty(default_pool, src); test_assert(str != src && str != NULL && strcmp(src, str) == 0); p_free(default_pool, str); test_end(); } static void test_p_strdup_until(void) { const char src[] = "foo\0bar"; char *str; test_begin("p_strdup_until()"); str = p_strdup_until(default_pool, src, src+2); test_assert(strcmp(str, "fo") == 0); p_free(default_pool, str); str = p_strdup_until(default_pool, src, src+3); test_assert(strcmp(str, "foo") == 0); p_free(default_pool, str); /* \0 is ignored */ str = p_strdup_until(default_pool, src, src+7); test_assert(memcmp(str, src, sizeof(src)) == 0); p_free(default_pool, str); str = p_strdup_until(default_pool, src, src+8); test_assert(memcmp(str, src, sizeof(src)) == 0); p_free(default_pool, str); test_end(); } static void test_p_strarray_dup(void) { const char *input[][3] = { { NULL }, { "a", NULL }, { "foobar", NULL }, { "a", "foo", NULL } }; const char **ret; unsigned int i, j; test_begin("p_strarray_dup"); for (i = 0; i < N_ELEMENTS(input); i++) { ret = p_strarray_dup(default_pool, input[i]); for (j = 0; input[i][j] != NULL; j++) { test_assert(strcmp(input[i][j], ret[j]) == 0); test_assert(input[i][j] != ret[j]); } test_assert(ret[j] == NULL); i_free(ret); } test_end(); } static void test_t_strsplit(void) { struct { const char *input; const char *const *output; } tests[] = { /* empty string -> empty array. was this perhaps a mistake for the API to do this originally?.. can't really change now anyway. */ { "", (const char *const []) { NULL } }, { "\n", (const char *const []) { "", "", NULL } }, { "\n\n", (const char *const []) { "", "", "", NULL } }, { "foo", (const char *const []) { "foo", NULL } }, { "foo\n", (const char *const []) { "foo", "", NULL } }, { "foo\nbar", (const char *const []) { "foo", "bar", NULL } }, { "foo\nbar\n", (const char *const []) { "foo", "bar", "", NULL } }, { "\nfoo\n\nbar\n\n", (const char *const []) { "", "foo", "", "bar", "", "", NULL } }, }; const char *const *args, *const *args2, *const *args3; test_begin("t_strsplit"); for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { /* split_str_fast() with single separator */ args = t_strsplit(tests[i].input, "\n"); /* split_str_slow() with a secondary separator */ args2 = t_strsplit(tests[i].input, "\r\n"); /* also as suffix */ args3 = t_strsplit(tests[i].input, "\n\r"); for (unsigned int j = 0; tests[i].output[j] != NULL; j++) { test_assert_idx(null_strcmp(tests[i].output[j], args[j]) == 0, i); test_assert_idx(null_strcmp(args[j], args2[j]) == 0, i); test_assert_idx(null_strcmp(args[j], args3[j]) == 0, i); } } test_end(); } static void test_t_strsplit_spaces(void) { struct { const char *input; const char *const *output; } tests[] = { /* empty strings */ { "", (const char *const []) { NULL } }, { "\n", (const char *const []) { NULL } }, { "\n\n", (const char *const []) { NULL } }, /* normal */ { "foo", (const char *const []) { "foo", NULL } }, { "foo\n", (const char *const []) { "foo", NULL } }, { "foo\nbar", (const char *const []) { "foo", "bar", NULL } }, { "foo\nbar\n", (const char *const []) { "foo", "bar", NULL } }, { "\nfoo\n\nbar\n\n", (const char *const []) { "foo", "bar", NULL } }, }; const char *const *args, *const *args2, *const *args3; test_begin("t_strsplit_spaces"); for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { args = t_strsplit_spaces(tests[i].input, "\n"); /* test also with a secondary nonexistent separator */ args2 = t_strsplit_spaces(tests[i].input, "\r\n"); /* also as suffix */ args3 = t_strsplit_spaces(tests[i].input, "\n\r"); for (unsigned int j = 0; tests[i].output[j] != NULL; j++) { test_assert_idx(null_strcmp(tests[i].output[j], args[j]) == 0, i); test_assert_idx(null_strcmp(args[j], args2[j]) == 0, i); test_assert_idx(null_strcmp(args[j], args3[j]) == 0, i); } } /* multiple separators */ args = t_strsplit_spaces(" , , ,str1 , ,,, , str2 , ", " ,"); test_assert(strcmp(args[0], "str1") == 0); test_assert(strcmp(args[1], "str2") == 0); test_assert(args[2] == NULL); test_end(); } static void test_t_str_replace(void) { test_begin("t_str_replace"); test_assert(strcmp(t_str_replace("foo", 'a', 'b'), "foo") == 0); test_assert(strcmp(t_str_replace("fooa", 'a', 'b'), "foob") == 0); test_assert(strcmp(t_str_replace("afooa", 'a', 'b'), "bfoob") == 0); test_assert(strcmp(t_str_replace("", 'a', 'b'), "") == 0); test_assert(strcmp(t_str_replace("a", 'a', 'b'), "b") == 0); test_assert(strcmp(t_str_replace("aaa", 'a', 'b'), "bbb") == 0); test_assert(strcmp(t_str_replace("bbb", 'a', 'b'), "bbb") == 0); test_assert(strcmp(t_str_replace("aba", 'a', 'b'), "bbb") == 0); test_end(); } static void test_t_str_oneline(void) { test_begin("t_str_oneline"); test_assert(strcmp(t_str_oneline("\n"), "") == 0); test_assert(strcmp(t_str_oneline("\r"), "") == 0); test_assert(strcmp(t_str_oneline("\n\n"), "") == 0); test_assert(strcmp(t_str_oneline("\r\r"), "") == 0); test_assert(strcmp(t_str_oneline("\r\n"), "") == 0); test_assert(strcmp(t_str_oneline("\r\n\r\n"), "") == 0); test_assert(strcmp(t_str_oneline("\n\r"), "") == 0); test_assert(strcmp(t_str_oneline("\n\r\n\r"), "") == 0); test_assert(strcmp(t_str_oneline("foo"), "foo") == 0); test_assert(strcmp(t_str_oneline("\nfoo"), "foo") == 0); test_assert(strcmp(t_str_oneline("foo\n"), "foo") == 0); test_assert(strcmp(t_str_oneline("\nfoo\n"), "foo") == 0); test_assert(strcmp(t_str_oneline("foo\nbar"), "foo bar") == 0); test_assert(strcmp(t_str_oneline("foo\n\nbar"), "foo bar") == 0); test_assert(strcmp(t_str_oneline("\nfoo\nbar"), "foo bar") == 0); test_assert(strcmp(t_str_oneline("foo\nbar\n"), "foo bar") == 0); test_assert(strcmp(t_str_oneline("foo\nbar\nbaz"), "foo bar baz") == 0); test_assert(strcmp(t_str_oneline("\rfoo"), "foo") == 0); test_assert(strcmp(t_str_oneline("foo\r"), "foo") == 0); test_assert(strcmp(t_str_oneline("\rfoo\r"), "foo") == 0); test_assert(strcmp(t_str_oneline("foo\rbar"), "foobar") == 0); test_assert(strcmp(t_str_oneline("foo\r\rbar"), "foobar") == 0); test_assert(strcmp(t_str_oneline("\rfoo\rbar"), "foobar") == 0); test_assert(strcmp(t_str_oneline("foo\rbar\r"), "foobar") == 0); test_assert(strcmp(t_str_oneline("foo\rbar\rbaz"), "foobarbaz") == 0); test_assert(strcmp(t_str_oneline("\r\nfoo\r\n"), "foo") == 0); test_assert(strcmp(t_str_oneline("foo\r\n"), "foo") == 0); test_assert(strcmp(t_str_oneline("\r\nfoo"), "foo") == 0); test_assert(strcmp(t_str_oneline("foo\r\nbar"), "foo bar") == 0); test_assert(strcmp(t_str_oneline("foo\r\n\r\nbar"), "foo bar") == 0); test_assert(strcmp(t_str_oneline("\r\nfoo\r\nbar"), "foo bar") == 0); test_assert(strcmp(t_str_oneline("foo\r\nbar\r\n"), "foo bar") == 0); test_assert(strcmp(t_str_oneline("foo\r\nbar\r\nbaz"), "foo bar baz") == 0); test_end(); } static void test_t_str_trim(void) { test_begin("t_str_trim"); test_assert(strcmp(t_str_trim("", " "), "") == 0); test_assert(strcmp(t_str_trim(" ", " "), "") == 0); test_assert(strcmp(t_str_trim(" \t ", "\t "), "") == 0); test_assert(strcmp(t_str_trim("f \t ", "\t "), "f") == 0); test_assert(strcmp(t_str_trim("foo", ""), "foo") == 0); test_assert(strcmp(t_str_trim("foo", " "), "foo") == 0); test_assert(strcmp(t_str_trim("foo ", " "), "foo") == 0); test_assert(strcmp(t_str_trim(" foo", " "), "foo") == 0); test_assert(strcmp(t_str_trim(" foo ", " "), "foo") == 0); test_assert(strcmp(t_str_trim("\tfoo ", "\t "), "foo") == 0); test_assert(strcmp(t_str_trim(" \tfoo\t ", "\t "), "foo") == 0); test_assert(strcmp(t_str_trim("\r \tfoo\t \r", "\t \r"), "foo") == 0); test_assert(strcmp(t_str_trim("\r \tfoo foo\t \r", "\t \r"), "foo foo") == 0); test_assert(strcmp(t_str_trim("\tfoo\tfoo\t", "\t \r"), "foo\tfoo") == 0); test_end(); } static void test_t_str_ltrim(void) { test_begin("t_str_ltrim"); test_assert(strcmp(t_str_ltrim("", " "), "") == 0); test_assert(strcmp(t_str_ltrim(" ", " "), "") == 0); test_assert(strcmp(t_str_ltrim(" \t ", "\t "), "") == 0); test_assert(strcmp(t_str_ltrim(" \t f", "\t "), "f") == 0); test_assert(strcmp(t_str_ltrim("foo", ""), "foo") == 0); test_assert(strcmp(t_str_ltrim("foo", " "), "foo") == 0); test_assert(strcmp(t_str_ltrim("foo ", " "), "foo ") == 0); test_assert(strcmp(t_str_ltrim(" foo", " "), "foo") == 0); test_assert(strcmp(t_str_ltrim(" foo ", " "), "foo ") == 0); test_assert(strcmp(t_str_ltrim("\tfoo ", "\t "), "foo ") == 0); test_assert(strcmp(t_str_ltrim(" \tfoo\t ", "\t "), "foo\t ") == 0); test_assert(strcmp(t_str_ltrim("\r \tfoo\t \r", "\t \r"), "foo\t \r") == 0); test_assert(strcmp(t_str_ltrim("\r \tfoo foo\t \r", "\t \r"), "foo foo\t \r") == 0); test_assert(strcmp(t_str_ltrim("\tfoo\tfoo\t", "\t \r"), "foo\tfoo\t") == 0); test_end(); } static void test_t_str_rtrim(void) { test_begin("t_str_rtrim"); test_assert(strcmp(t_str_rtrim("", " "), "") == 0); test_assert(strcmp(t_str_rtrim(" ", " "), "") == 0); test_assert(strcmp(t_str_rtrim(" \t ", "\t "), "") == 0); test_assert(strcmp(t_str_rtrim("f \t ", "\t "), "f") == 0); test_assert(strcmp(t_str_rtrim("foo", ""), "foo") == 0); test_assert(strcmp(t_str_rtrim("foo", " "), "foo") == 0); test_assert(strcmp(t_str_rtrim("foo ", " "), "foo") == 0); test_assert(strcmp(t_str_rtrim(" foo", " "), " foo") == 0); test_assert(strcmp(t_str_rtrim(" foo ", " "), " foo") == 0); test_assert(strcmp(t_str_rtrim("\tfoo ", "\t "), "\tfoo") == 0); test_assert(strcmp(t_str_rtrim(" \tfoo\t ", "\t "), " \tfoo") == 0); test_assert(strcmp(t_str_rtrim("\r \tfoo\t \r", "\t \r"), "\r \tfoo") == 0); test_assert(strcmp(t_str_rtrim("\r \tfoo foo\t \r", "\t \r"), "\r \tfoo foo") == 0); test_assert(strcmp(t_str_rtrim("\tfoo\tfoo\t", "\t \r"), "\tfoo\tfoo") == 0); test_end(); } static const char *const test_strarray_input[] = { "", "hello", "world", "", "yay", "", NULL }; static const struct { const char *separator; const char *output; } test_strarray_outputs[] = { { "", "helloworldyay" }, { " ", " hello world yay " }, { "!-?", "!-?hello!-?world!-?!-?yay!-?" } }; static const char *const test_strarray_input2[] = { "", "", "hello", "world", "", "yay", "", NULL }; static struct { const char *separator; const char *output; } test_strarray_outputs2[] = { { "", "helloworldyay" }, { " ", " hello world yay " }, { "!-?", "!-?!-?hello!-?world!-?!-?yay!-?" } }; static const char *const test_strarray_input3[] = { "hello", "", "", "yay", NULL }; static struct { const char *separator; const char *output; } test_strarray_outputs3[] = { { "", "helloyay" }, { " ", "hello yay" }, { "!-?", "hello!-?!-?!-?yay" } }; static void test_t_strarray_join(void) { const char *null = NULL; unsigned int i; test_begin("t_strarray_join()"); /* empty array -> empty string */ test_assert(strcmp(t_strarray_join(&null, " "), "") == 0); for (i = 0; i < N_ELEMENTS(test_strarray_outputs); i++) { test_assert_idx(strcmp(t_strarray_join(test_strarray_input, test_strarray_outputs[i].separator), test_strarray_outputs[i].output) == 0, i); } for (i = 0; i < N_ELEMENTS(test_strarray_outputs2); i++) { test_assert_idx(strcmp(t_strarray_join(test_strarray_input2, test_strarray_outputs2[i].separator), test_strarray_outputs2[i].output) == 0, i); } for (i = 0; i < N_ELEMENTS(test_strarray_outputs3); i++) { test_assert_idx(strcmp(t_strarray_join(test_strarray_input3, test_strarray_outputs3[i].separator), test_strarray_outputs3[i].output) == 0, i); } test_end(); } static void test_p_array_const_string_join(void) { ARRAY_TYPE(const_string) arr; unsigned int i; char *res; test_begin("p_array_const_string_join()"); i_array_init(&arr, 2); /* empty array -> empty string */ test_assert(strcmp(t_array_const_string_join(&arr, " "), "") == 0); array_append(&arr, test_strarray_input, str_array_length(test_strarray_input)); for (i = 0; i < N_ELEMENTS(test_strarray_outputs); i++) { res = p_array_const_string_join(default_pool, &arr, test_strarray_outputs[i].separator); test_assert_idx(strcmp(res, test_strarray_outputs[i].output) == 0, i); i_free(res); } array_free(&arr); test_end(); } static void test_mem_equals_timing_safe(void) { const struct { const char *a, *b; } tests[] = { { "", "" }, { "a", "a" }, { "b", "a" }, { "ab", "ab" }, { "ab", "ba" }, { "ab", "bc" }, }; test_begin("mem_equals_timing_safe()"); for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { size_t len = strlen(tests[i].a); i_assert(len == strlen(tests[i].b)); test_assert((memcmp(tests[i].a, tests[i].b, len) == 0) == mem_equals_timing_safe(tests[i].a, tests[i].b, len)); test_assert((memcmp(tests[i].a, tests[i].b, len) == 0) == mem_equals_timing_safe(tests[i].b, tests[i].a, len)); } test_end(); } static void test_str_equals_timing_almost_safe(void) { const struct { const char *a, *b; } tests[] = { { "", "" }, { "a", "a" }, { "b", "a" }, { "ab", "ab" }, { "ab", "ba" }, { "ab", "bc" }, { "a", "" }, { "a", "ab" }, { "a", "abc" }, { "ab", "abc" }, }; test_begin("str_equals_timing_almost_safe()"); for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { test_assert((strcmp(tests[i].a, tests[i].b) == 0) == str_equals_timing_almost_safe(tests[i].a, tests[i].b)); test_assert((strcmp(tests[i].a, tests[i].b) == 0) == str_equals_timing_almost_safe(tests[i].b, tests[i].a)); } test_end(); } static void test_dec2str_buf(void) { const uintmax_t test_input[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 99, 999, 9999, 65535, 65536, 99999, 999999, 9999999, 99999999, 999999999, 4294967295, 4294967296ULL, 9999999999999999999ULL, 18446744073709551615ULL }; char buf[MAX_INT_STRLEN], buf2[MAX_INT_STRLEN]; test_begin("dec2str_buf()"); for (unsigned int i = 0; i < N_ELEMENTS(test_input); i++) { i_snprintf(buf2, sizeof(buf2), "%ju", test_input[i]); test_assert_idx(strcmp(dec2str_buf(buf, test_input[i]), buf2) == 0, i); } test_end(); } static void test_str_match(void) { static const struct { const char*s1, *s2; size_t match; } tests[] = { #define MATCH_TEST(common, left, right) { common left, common right, sizeof(common)-1 } MATCH_TEST("", "", ""), MATCH_TEST("", "x", ""), MATCH_TEST("", "", "x"), MATCH_TEST("", "foo", "bar"), MATCH_TEST("x", "", ""), MATCH_TEST("x", "y", "z"), MATCH_TEST("blahblahblah", "", ""), MATCH_TEST("blahblahblah", "", "bar"), MATCH_TEST("blahblahblah", "foo", ""), MATCH_TEST("blahblahblah", "foo", "bar"), #undef MATCH_TEST }; unsigned int i; test_begin("str_match"); for (i = 0; i < N_ELEMENTS(tests); i++) test_assert_idx(str_match(tests[i].s1, tests[i].s2) == tests[i].match, i); test_end(); test_begin("str_begins"); for (i = 0; i < N_ELEMENTS(tests); i++) { /* This is just 2 ways of wording the same test, but that also sanity tests the match values above. */ test_assert_idx(str_begins(tests[i].s1, tests[i].s2) == (str_begins(tests[i].s1, tests[i].s2)), i); test_assert_idx(str_begins(tests[i].s1, tests[i].s2) == (strlen(tests[i].s2) == tests[i].match), i); } test_end(); } static void test_memspn(void) { #undef TEST_CASE /* we substract 1 to ensure we don't include the final \0 byte */ #define TEST_CASE(a, b, r) { \ .input = (const unsigned char*)((a)), .input_len = sizeof((a))-1, \ .accept = (const unsigned char*)((b)), .accept_len = sizeof((b))-1, \ .result = r, \ } static struct { const unsigned char *input; size_t input_len; const unsigned char *accept; size_t accept_len; size_t result; } tests[] = { TEST_CASE("", "", 0), TEST_CASE("", "123456789", 0), TEST_CASE("123456789", "", 0), TEST_CASE("hello, world", "helo", 5), TEST_CASE("hello, uuuuu", "helo", 5), TEST_CASE("\0\0\0\0\0hello", "\0", 5), TEST_CASE("\r\r\r\r", "\r", 4), TEST_CASE("aaa", "a", 3), TEST_CASE("bbb", "a", 0), /* null safety test */ { .input = NULL, .accept = NULL, .input_len = 0, .accept_len = 0, .result = 0, } }; test_begin("i_memspn"); for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { size_t a = i_memspn(tests[i].input, tests[i].input_len, tests[i].accept, tests[i].accept_len); test_assert_ucmp_idx(a, ==, tests[i].result, i); if (tests[i].input == NULL) continue; a = i_memspn(tests[i].input, strlen((const char*)tests[i].input), tests[i].accept, strlen((const char*)tests[i].accept)); size_t b = strspn((const char*)tests[i].input, (const char*)tests[i].accept); test_assert_ucmp_idx(a, ==, b, i); } test_end(); } static void test_memcspn(void) { #undef TEST_CASE /* we substract 1 to ensure we don't include the final \0 byte */ #define TEST_CASE(a, b, r) { \ .input = (const unsigned char*)((a)), .input_len = sizeof((a))-1, \ .reject = (const unsigned char*)((b)), .reject_len = sizeof((b))-1, \ .result = r, \ } static struct { const unsigned char *input; size_t input_len; const unsigned char *reject; size_t reject_len; size_t result; } tests[] = { TEST_CASE("", "", 0), TEST_CASE("hello", "", 5), TEST_CASE("uuuuu, hello", "helo", 7), TEST_CASE("\0\0\0\0\0\0hello", "u", 11), TEST_CASE("this\0is\0test", "\0", 4), TEST_CASE("hello, world\r", "\r", 12), TEST_CASE("aaa", "a", 0), TEST_CASE("bbb", "a", 3), /* null safety test */ { .input = NULL, .reject = NULL, .input_len = 0, .reject_len = 0, .result = 0, } }; test_begin("i_memcspn"); for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { size_t a = i_memcspn(tests[i].input, tests[i].input_len, tests[i].reject, tests[i].reject_len); test_assert_ucmp_idx(a, ==, tests[i].result, i); if (tests[i].input == NULL) continue; a = i_memcspn(tests[i].input, strlen((const char*)tests[i].input), tests[i].reject, strlen((const char*)tests[i].reject)); size_t b = strcspn((const char*)tests[i].input, (const char*)tests[i].reject); test_assert_ucmp_idx(a, ==, b, i); } test_end(); } void test_strfuncs(void) { test_p_strdup(); test_p_strndup(); test_p_strdup_empty(); test_p_strdup_until(); test_p_strarray_dup(); test_t_strsplit(); test_t_strsplit_spaces(); test_t_str_replace(); test_t_str_oneline(); test_t_str_trim(); test_t_str_ltrim(); test_t_str_rtrim(); test_t_strarray_join(); test_p_array_const_string_join(); test_mem_equals_timing_safe(); test_str_equals_timing_almost_safe(); test_dec2str_buf(); test_str_match(); test_memspn(); test_memcspn(); } enum fatal_test_state fatal_strfuncs(unsigned int stage) { switch (stage) { case 0: test_begin("fatal p_strndup()"); test_expect_fatal_string("(str != NULL)"); (void)p_strndup(default_pool, NULL, 100); return FATAL_TEST_FAILURE; case 1: test_expect_fatal_string("(max_chars != SIZE_MAX)"); (void)p_strndup(default_pool, "foo", SIZE_MAX); return FATAL_TEST_FAILURE; } test_end(); return FATAL_TEST_FINISHED; } dovecot-2.3.21.1/src/lib/strfuncs.h0000644000000000000000000001763614656633576013707 00000000000000#ifndef STRFUNC_H #define STRFUNC_H /* Maximum number of bytes needed for the largest uintmax_t or the lowest intmax_t number in base 10. This value includes the trailing \0. */ #define MAX_INT_STRLEN ((sizeof(uintmax_t) * CHAR_BIT + 2) / 3 + 1) extern const unsigned char uchar_nul; /* (const unsigned char *)"" */ extern const unsigned char *uchar_empty_ptr; /* non-NULL pointer that shouldn't be dereferenced. */ /* Returns -1 if dest wasn't large enough, 0 if not. */ int i_snprintf(char *dest, size_t max_chars, const char *format, ...) ATTR_FORMAT(3, 4); char *p_strdup(pool_t pool, const char *str) ATTR_MALLOC; void *p_memdup(pool_t pool, const void *data, size_t size) ATTR_MALLOC; /* return NULL if str = "" */ char *p_strdup_empty(pool_t pool, const char *str) ATTR_MALLOC; /* *end isn't included */ char *p_strdup_until(pool_t pool, const void *start, const void *end) ATTR_MALLOC ATTR_RETURNS_NONNULL; char *p_strndup(pool_t pool, const void *str, size_t max_chars) ATTR_MALLOC; char *p_strdup_printf(pool_t pool, const char *format, ...) ATTR_FORMAT(2, 3) ATTR_MALLOC ATTR_RETURNS_NONNULL; char *p_strdup_vprintf(pool_t pool, const char *format, va_list args) ATTR_FORMAT(2, 0) ATTR_MALLOC ATTR_RETURNS_NONNULL; char *p_strconcat(pool_t pool, const char *str1, ...) ATTR_SENTINEL ATTR_MALLOC; /* same with temporary memory allocations: */ const char *t_strdup(const char *str) ATTR_MALLOC; char *t_strdup_noconst(const char *str) ATTR_MALLOC; /* return NULL if str = "" */ const char *t_strdup_empty(const char *str) ATTR_MALLOC; /* *end isn't included */ const char *t_strdup_until(const void *start, const void *end) ATTR_MALLOC ATTR_RETURNS_NONNULL; const char *t_strndup(const void *str, size_t max_chars) ATTR_MALLOC; const char *t_strdup_printf(const char *format, ...) ATTR_FORMAT(1, 2) ATTR_MALLOC ATTR_RETURNS_NONNULL; const char *t_strdup_vprintf(const char *format, va_list args) ATTR_FORMAT(1, 0) ATTR_MALLOC ATTR_RETURNS_NONNULL; const char *t_strconcat(const char *str1, ...) ATTR_SENTINEL ATTR_MALLOC; /* Like t_strdup(), but stop at cutchar. */ const char *t_strcut(const char *str, char cutchar); /* Replace all from->to chars in the string. */ const char *t_str_replace(const char *str, char from, char to); /* Put the string on a single line by replacing all newlines with spaces and dropping any carriage returns. Sequences of several newlines are merged into one space and newlines at the beginning and end of the string are dropped. */ const char *t_str_oneline(const char *str); /* Like strlcpy(), but return -1 if buffer was overflown, 0 if not. */ int i_strocpy(char *dest, const char *src, size_t dstsize); char *str_ucase(char *str); char *str_lcase(char *str); const char *t_str_lcase(const char *str); const char *t_str_ucase(const char *str); /* Return pointer to first matching needle */ const char *i_strstr_arr(const char *haystack, const char *const *needles); /* Trim matching chars from either side of the string */ const char *t_str_trim(const char *str, const char *chars); const char *p_str_trim(pool_t pool, const char *str, const char *chars); const char *str_ltrim(const char *str, const char *chars); const char *t_str_ltrim(const char *str, const char *chars); const char *p_str_ltrim(pool_t pool, const char *str, const char *chars); const char *t_str_rtrim(const char *str, const char *chars); const char *p_str_rtrim(pool_t pool, const char *str, const char *chars); int null_strcmp(const char *s1, const char *s2) ATTR_PURE; int null_strcasecmp(const char *s1, const char *s2) ATTR_PURE; int i_memcasecmp(const void *p1, const void *p2, size_t size) ATTR_PURE; int i_strcmp_p(const char *const *p1, const char *const *p2) ATTR_PURE; int i_strcasecmp_p(const char *const *p1, const char *const *p2) ATTR_PURE; /* Returns TRUE if the two memory areas are equal. This function is safe against timing attacks, so it compares all the bytes every time. */ bool mem_equals_timing_safe(const void *p1, const void *p2, size_t size); /* Returns TRUE if the two strings are equal. Similar to mem_equals_timing_safe() this function is safe against timing attacks when the string lengths are the same. If not, the length of the secret string may be leaked, but otherwise the contents won't be. */ bool str_equals_timing_almost_safe(const char *s1, const char *s2); size_t str_match(const char *p1, const char *p2) ATTR_PURE; static inline ATTR_PURE bool str_begins(const char *haystack, const char *needle) { return needle[str_match(haystack, needle)] == '\0'; } #if defined(__GNUC__) && (__GNUC__ >= 2) /* GCC (and Clang) are known to have a compile-time strlen("literal") shortcut, and an optimised strncmp(), so use that by default. Macro is multi-evaluation safe. */ # define str_begins(h, n) (__builtin_constant_p(n) ? strncmp((h), (n), strlen(n))==0 : (str_begins)((h), (n))) #endif /* Get length of a prefix segment. Calculates the length (in bytes) of the initial segment of s which consists entirely of bytes in accept. */ size_t i_memspn(const void *data, size_t data_len, const void *accept, size_t accept_len); /* Get length of a prefix segment. Calculates the length of the initial segment of s which consists entirely of bytes not in reject. */ size_t i_memcspn(const void *data, size_t data_len, const void *reject, size_t reject_len); static inline char *i_strchr_to_next(const char *str, char chr) { char *tmp = (char *)strchr(str, chr); return tmp == NULL ? NULL : tmp+1; } /* separators is an array of separator characters, not a separator string. an empty data string results in an array containing only NULL. */ char **p_strsplit(pool_t pool, const char *data, const char *separators) ATTR_MALLOC ATTR_RETURNS_NONNULL; const char **t_strsplit(const char *data, const char *separators) ATTR_MALLOC ATTR_RETURNS_NONNULL; /* like p_strsplit(), but treats multiple adjacent separators as a single separator. separators at the beginning or at the end of the string are also ignored, so it's not possible for the result to have any empty strings. */ char **p_strsplit_spaces(pool_t pool, const char *data, const char *separators) ATTR_MALLOC ATTR_RETURNS_NONNULL; const char **t_strsplit_spaces(const char *data, const char *separators) ATTR_MALLOC ATTR_RETURNS_NONNULL; void p_strsplit_free(pool_t pool, char **arr); const char *dec2str(uintmax_t number); /* Use the given buffer to write out the number. Returns pointer to the written number in the buffer. Note that this isn't the same as the beginning of the buffer. */ char *dec2str_buf(char buffer[STATIC_ARRAY MAX_INT_STRLEN], uintmax_t number); /* Return length of NULL-terminated list string array */ unsigned int str_array_length(const char *const *arr) ATTR_PURE; /* Return all strings from array joined into one string. */ const char *t_strarray_join(const char *const *arr, const char *separator) ATTR_MALLOC ATTR_RETURNS_NONNULL; /* Removes a value from NULL-terminated string array. Returns TRUE if found. */ bool str_array_remove(const char **arr, const char *value); /* Returns TRUE if value exists in NULL-terminated string array. */ bool str_array_find(const char *const *arr, const char *value); /* Like str_array_find(), but use strcasecmp(). */ bool str_array_icase_find(const char *const *arr, const char *value); /* Duplicate array of strings. The memory can be freed by freeing the return value. */ const char **p_strarray_dup(pool_t pool, const char *const *arr) ATTR_MALLOC ATTR_RETURNS_NONNULL; /* Join ARRAY_TYPE(const_string) to a string, similar to t_strarray_join() */ char *p_array_const_string_join(pool_t pool, const ARRAY_TYPE(const_string) *arr, const char *separator); #define t_array_const_string_join(arr, separator) \ ((const char *)p_array_const_string_join(unsafe_data_stack_pool, arr, separator)) /* INTERNAL */ char *t_noalloc_strdup_vprintf(const char *format, va_list args, unsigned int *size_r) ATTR_FORMAT(1, 0) ATTR_RETURNS_NONNULL; char *vstrconcat(const char *str1, va_list args, size_t *ret_len) ATTR_MALLOC; #endif dovecot-2.3.21.1/src/lib/unichar.h0000644000000000000000000001356714656633576013470 00000000000000#ifndef UNICHAR_H #define UNICHAR_H /* Character used to replace invalid input. */ #define UNICODE_REPLACEMENT_CHAR 0xfffd #define UNICODE_REPLACEMENT_CHAR_UTF8 "\xEF\xBF\xBD" #define UNICODE_REPLACEMENT_CHAR_UTF8_LEN \ (sizeof(UNICODE_REPLACEMENT_CHAR_UTF8) - 1); /* Horizontal ellipsis character ('...') */ #define UNICODE_HORIZONTAL_ELLIPSIS_CHAR 0x2026 #define UNICODE_HORIZONTAL_ELLIPSIS_CHAR_UTF8 "\xE2\x80\xA6" #define UNICODE_HORIZONTAL_ELLIPSIS_CHAR_UTF8_LEN \ (sizeof(UNICODE_HORIZONTAL_ELLIPSIS_CHAR_UTF8) - 1); /* Characters >= base require surrogates */ #define UTF16_SURROGATE_BASE 0x10000 #define UTF16_SURROGATE_SHIFT 10 #define UTF16_SURROGATE_MASK 0x03ff #define UTF16_SURROGATE_HIGH_FIRST 0xd800 #define UTF16_SURROGATE_HIGH_LAST 0xdbff #define UTF16_SURROGATE_HIGH_MAX 0xdfff #define UTF16_SURROGATE_LOW_FIRST 0xdc00 #define UTF16_SURROGATE_LOW_LAST 0xdfff #define UTF16_SURROGATE_HIGH(chr) \ (UTF16_SURROGATE_HIGH_FIRST + \ (((chr) - UTF16_SURROGATE_BASE) >> UTF16_SURROGATE_SHIFT)) #define UTF16_SURROGATE_LOW(chr) \ (UTF16_SURROGATE_LOW_FIRST + \ (((chr) - UTF16_SURROGATE_BASE) & UTF16_SURROGATE_MASK)) /* Returns TRUE if given byte is ASCII character or the beginning of a multibyte UTF-8 sequence */ #define UTF8_IS_START_SEQ(b) \ (((b) & 0x80) == 0 || ((b) & 0xC0) == 0xC0) #define UTF8_REPLACEMENT_CHAR_LEN 3 #define UNICHAR_T_MAX 0x10ffff #define UTF16_VALID_HIGH_SURROGATE(chr) (((chr) & 0xfffc00) == UTF16_SURROGATE_HIGH_FIRST) #define UTF16_VALID_LOW_SURROGATE(chr) (((chr) & 0xfffc00) == UTF16_SURROGATE_LOW_FIRST) typedef uint32_t unichar_t; ARRAY_DEFINE_TYPE(unichars, unichar_t); /* Normalize UTF8 input and append it to output buffer. Returns 0 if ok, -1 if input was invalid. Even if input was invalid, as much as possible should be added to output. */ typedef int normalizer_func_t(const void *input, size_t size, buffer_t *output); extern const unsigned char utf8_replacement_char[UTF8_REPLACEMENT_CHAR_LEN]; extern const uint8_t *const uni_utf8_non1_bytes; static inline bool ATTR_PURE uni_is_valid_ucs4(unichar_t chr) { return (!UTF16_VALID_HIGH_SURROGATE(chr) && !UTF16_VALID_LOW_SURROGATE(chr) && chr <= UNICHAR_T_MAX); }; /* Returns number of characters in a NUL-terminated unicode string */ unsigned int uni_strlen(const unichar_t *str) ATTR_PURE; /* Translates UTF-8 input to UCS-4 output. Returns 0 if ok, -1 if input was invalid */ int uni_utf8_to_ucs4(const char *input, ARRAY_TYPE(unichars) *output); int uni_utf8_to_ucs4_n(const unsigned char *input, size_t size, ARRAY_TYPE(unichars) *output); /* Translates UCS-4 input to UTF-8 output. */ void uni_ucs4_to_utf8(const unichar_t *input, size_t len, buffer_t *output); void uni_ucs4_to_utf8_c(unichar_t chr, buffer_t *output); /* Returns char_bytes (>0) if *chr_r is set, 0 for incomplete trailing character, -1 for invalid input. */ int uni_utf8_get_char(const char *input, unichar_t *chr_r); int uni_utf8_get_char_n(const void *input, size_t max_len, unichar_t *chr_r); /* Returns number of characters in UTF-8 string. */ unsigned int uni_utf8_strlen(const char *input) ATTR_PURE; /* Returns number of characters in UTF-8 input of specified size. */ unsigned int uni_utf8_strlen_n(const void *input, size_t size) ATTR_PURE; /* Same as uni_utf8_strlen_n(), but if input ends with a partial UTF-8 character, don't include it in the return value and set partial_pos_r to where the character begins. Otherwise partial_pos_r is set to the end of the input. */ unsigned int uni_utf8_partial_strlen_n(const void *input, size_t size, size_t *partial_pos_r); /* Returns the number of bytes belonging to this UTF-8 character. The given parameter is the first byte of the UTF-8 sequence. Invalid input is returned with length 1. */ static inline unsigned int ATTR_CONST uni_utf8_char_bytes(unsigned char chr) { /* 0x00 .. 0x7f are ASCII. 0x80 .. 0xC1 are invalid. */ if (chr < (192 + 2)) return 1; return uni_utf8_non1_bytes[chr - (192 + 2)]; } /* Return given character in titlecase. */ unichar_t uni_ucs4_to_titlecase(unichar_t chr) ATTR_CONST; /* Convert UTF-8 input to titlecase and decompose the titlecase characters to output buffer. Returns 0 if ok, -1 if input was invalid. This generates output that's compatible with i;unicode-casemap comparator. Invalid input is replaced with unicode replacement character (0xfffd). */ int uni_utf8_to_decomposed_titlecase(const void *input, size_t size, buffer_t *output); /* If input contains only valid UTF-8 characters, return TRUE without updating buf. If input contains invalid UTF-8 characters, replace them with unicode replacement character (0xfffd), write the output to buf and return FALSE. */ bool uni_utf8_get_valid_data(const unsigned char *input, size_t size, buffer_t *buf) ATTR_WARN_UNUSED_RESULT; /* Returns TRUE if string is valid UTF-8 input. */ bool uni_utf8_str_is_valid(const char *str); /* Returns TRUE if data contains only valid UTF-8 input. */ bool uni_utf8_data_is_valid(const unsigned char *data, size_t size); /* Returns the size of the data when truncated to be less than or equal to max_new_size, making sure UTF-8 character boundaries are respected. This only looks at the last character at the new boundary. */ size_t uni_utf8_data_truncate(const unsigned char *data, size_t old_size, size_t max_new_size); /* surrogate handling */ static inline unichar_t uni_join_surrogate(unichar_t high, unichar_t low) { i_assert(UTF16_VALID_HIGH_SURROGATE(high) && UTF16_VALID_LOW_SURROGATE(low)); return ((high - UTF16_SURROGATE_HIGH_FIRST)<<10) + (low - UTF16_SURROGATE_LOW_FIRST) + UTF16_SURROGATE_BASE; } static inline void uni_split_surrogate(unichar_t chr, unichar_t *high_r, unichar_t *low_r) { i_assert(chr >= UTF16_SURROGATE_BASE && chr <= UNICHAR_T_MAX); i_assert(high_r != NULL && low_r != NULL); *high_r = UTF16_SURROGATE_HIGH(chr); *low_r = UTF16_SURROGATE_LOW(chr); } #endif dovecot-2.3.21.1/src/lib/execv-const.h0000644000000000000000000000053414656633576014263 00000000000000#ifndef EXECV_CONST_H #define EXECV_CONST_H /* Just like execv() and execvp(), except argv points to const strings. Also if calling execv*() fails, these functions call i_fatal(). */ void execv_const(const char *path, const char *const argv[]) ATTR_NORETURN; void execvp_const(const char *file, const char *const argv[]) ATTR_NORETURN; #endif dovecot-2.3.21.1/src/lib/aqueue.h0000644000000000000000000000205514656633576013312 00000000000000#ifndef AQUEUE_H #define AQUEUE_H /* Dynamically growing queue. Use the array directly to access the data, for example: count = queue_count(queue); for (i = 0; i < count; i++) { data = array[queue_idx(i)]; } */ struct aqueue { struct array *arr; unsigned int head, tail, area_size; bool full; }; struct aqueue *aqueue_init(struct array *array); void aqueue_deinit(struct aqueue **aqueue); /* Append item to head */ void aqueue_append(struct aqueue *aqueue, const void *data); /* Delete last item from tail */ void aqueue_delete_tail(struct aqueue *aqueue); /* Remove item from n'th position */ void aqueue_delete(struct aqueue *aqueue, unsigned int n); /* Clear the entire aqueue */ void aqueue_clear(struct aqueue *aqueue); /* Returns the number of items in aqueue. */ unsigned int aqueue_count(const struct aqueue *aqueue) ATTR_PURE; /* Returns array index of n'th element in aqueue. */ static inline unsigned int ATTR_PURE aqueue_idx(const struct aqueue *aqueue, unsigned int n) { return (aqueue->tail + n) % aqueue->area_size; } #endif dovecot-2.3.21.1/src/lib/file-cache.h0000644000000000000000000000312514656633576014004 00000000000000#ifndef FILE_CACHE_H #define FILE_CACHE_H /* Create a new file cache. It works very much like file-backed mmap()ed memory, but it works more nicely with remote filesystems (no SIGBUS). */ struct file_cache *file_cache_new(int fd); struct file_cache *file_cache_new_path(int fd, const char *path); /* Destroy the cache and set cache pointer to NULL. */ void file_cache_free(struct file_cache **cache); /* Change cached file descriptor. Invalidates the whole cache. */ void file_cache_set_fd(struct file_cache *cache, int fd); /* Change the memory allocated for the cache. This can be used to immediately set the maximum size so there's no need to grow the memory area with possibly slow copying. */ int file_cache_set_size(struct file_cache *cache, uoff_t size); /* Read data from file, returns how many bytes was actually read or -1 if error occurred. */ ssize_t file_cache_read(struct file_cache *cache, uoff_t offset, size_t size); /* Returns pointer to beginning of cached file. Only parts of the returned memory that are valid are the ones that have been file_cache_read(). Note that the pointer may become invalid after calling file_cache_read(). */ const void *file_cache_get_map(struct file_cache *cache, size_t *size_r); /* Update cached memory area. Mark fully written pages as cached. */ void file_cache_write(struct file_cache *cache, const void *data, size_t size, uoff_t offset); /* Invalidate cached memory area. It will be read again next time it's tried to be accessed. */ void file_cache_invalidate(struct file_cache *cache, uoff_t offset, uoff_t size); #endif dovecot-2.3.21.1/src/lib/var-expand-private.h0000644000000000000000000000333414656633576015543 00000000000000#ifndef VAR_EXPAND_PRIVATE_H #define VAR_EXPAND_PRIVATE_H 1 struct var_expand_context { /* current variables */ const struct var_expand_table *table; /* caller provided function table */ const struct var_expand_func_table *func_table; /* caller provided context */ void *context; /* last offset, negative counts from end*/ int offset; /* last width, negative counts from end */ int width; /* last zero padding */ bool zero_padding:1; }; /* this can be used to register a *global* function that is prepended to function table. These can be used to register some special handling for keys. you can call var_expand_with_funcs if you need to expand something inside here. return -1 on error, 0 on unknown variable, 1 on success */ typedef int var_expand_extension_func_t(struct var_expand_context *ctx, const char *key, const char *field, const char **result_r, const char **error_r); struct var_expand_extension_func_table { const char *key; var_expand_extension_func_t *func; }; int var_expand_long(struct var_expand_context *ctx, const void *key_start, size_t key_len, const char **var_r, const char **error_r); void var_expand_extensions_init(void); void var_expand_extensions_deinit(void); /* Functions registered here are placed before in-built functions, so you can include your own implementation of something. Be careful. Use NULL terminated list. */ void var_expand_register_func_array(const struct var_expand_extension_func_table *funcs); void var_expand_unregister_func_array(const struct var_expand_extension_func_table *funcs); int var_expand_if(struct var_expand_context *ctx, const char *key, const char *field, const char **result_r, const char **error_r); #endif dovecot-2.3.21.1/src/replication/0000755000000000000000000000000014656633640013465 500000000000000dovecot-2.3.21.1/src/replication/aggregator/0000755000000000000000000000000014656633640015607 500000000000000dovecot-2.3.21.1/src/replication/aggregator/notify-connection.h0000644000000000000000000000034214656633576021354 00000000000000#ifndef NOTIFY_CONNECTION_H #define NOTIFY_CONNECTION_H void notify_connection_create(int fd, bool fifo); void notify_connections_destroy_all(void); void notify_connection_sync_callback(bool success, void *context); #endif dovecot-2.3.21.1/src/replication/aggregator/notify-connection.c0000644000000000000000000000662114656633576021355 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "net.h" #include "istream.h" #include "ostream.h" #include "llist.h" #include "strescape.h" #include "master-service.h" #include "replication-common.h" #include "replicator-connection.h" #include "notify-connection.h" #define MAX_INBUF_SIZE 8192 #define CONNECTION_IS_FIFO(conn) \ ((conn)->output == NULL) struct notify_connection { struct notify_connection *prev, *next; int refcount; int fd; struct io *io; struct istream *input; struct ostream *output; }; static struct notify_connection *conns = NULL; static void notify_connection_unref(struct notify_connection *conn); static void notify_connection_destroy(struct notify_connection *conn); static bool notify_input_error(struct notify_connection *conn) { if (CONNECTION_IS_FIFO(conn)) return TRUE; notify_connection_destroy(conn); return FALSE; } void notify_connection_sync_callback(bool success, void *context) { struct notify_connection *conn = context; o_stream_nsend_str(conn->output, success ? "+\n" : "-\n"); notify_connection_unref(conn); } static int notify_input_line(struct notify_connection *conn, const char *line) { const char *const *args; enum replication_priority priority; /* \t */ args = t_strsplit_tabescaped(line); if (str_array_length(args) < 2) { i_error("Client sent invalid input"); return -1; } if (replication_priority_parse(args[1], &priority) < 0) { i_error("Client sent invalid priority: %s", args[1]); return -1; } if (priority != REPLICATION_PRIORITY_SYNC) replicator_connection_notify(replicator, args[0], priority); else { conn->refcount++; replicator_connection_notify_sync(replicator, args[0], conn); } return 0; } static void notify_input(struct notify_connection *conn) { const char *line; int ret; switch (i_stream_read(conn->input)) { case -2: /* buffer full */ i_error("Client sent too long line"); (void)notify_input_error(conn); return; case -1: /* disconnected */ notify_connection_destroy(conn); return; } while ((line = i_stream_next_line(conn->input)) != NULL) { T_BEGIN { ret = notify_input_line(conn, line); } T_END; if (ret < 0) { if (!notify_input_error(conn)) return; } } } void notify_connection_create(int fd, bool fifo) { struct notify_connection *conn; conn = i_new(struct notify_connection, 1); conn->refcount = 1; conn->fd = fd; conn->io = io_add(fd, IO_READ, notify_input, conn); conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE); if (!fifo) { conn->output = o_stream_create_fd(fd, SIZE_MAX); o_stream_set_no_error_handling(conn->output, TRUE); } DLLIST_PREPEND(&conns, conn); } static void notify_connection_unref(struct notify_connection *conn) { i_assert(conn->refcount > 0); if (--conn->refcount > 0) return; i_stream_destroy(&conn->input); o_stream_destroy(&conn->output); i_free(conn); } static void notify_connection_destroy(struct notify_connection *conn) { i_assert(conn->fd != -1); if (!CONNECTION_IS_FIFO(conn)) master_service_client_connection_destroyed(master_service); DLLIST_REMOVE(&conns, conn); io_remove(&conn->io); i_stream_close(conn->input); o_stream_close(conn->output); net_disconnect(conn->fd); conn->fd = -1; notify_connection_unref(conn); } void notify_connections_destroy_all(void) { while (conns != NULL) notify_connection_destroy(conns); } dovecot-2.3.21.1/src/replication/aggregator/Makefile.in0000644000000000000000000006455514656633613017613 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ pkglibexec_PROGRAMS = aggregator$(EXEEXT) subdir = src/replication/aggregator ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(pkglibexecdir)" PROGRAMS = $(pkglibexec_PROGRAMS) am_aggregator_OBJECTS = aggregator.$(OBJEXT) \ aggregator-settings.$(OBJEXT) notify-connection.$(OBJEXT) \ replicator-connection.$(OBJEXT) aggregator_OBJECTS = $(am_aggregator_OBJECTS) am__DEPENDENCIES_1 = AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = aggregator_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(aggregator_LDFLAGS) $(LDFLAGS) -o $@ AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/aggregator-settings.Po \ ./$(DEPDIR)/aggregator.Po ./$(DEPDIR)/notify-connection.Po \ ./$(DEPDIR)/replicator-connection.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(aggregator_SOURCES) DIST_SOURCES = $(aggregator_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac HEADERS = $(noinst_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) pkglibexecdir = $(libexecdir)/dovecot ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/replication \ -DPKG_STATEDIR=\""$(statedir)"\" \ $(BINARY_CFLAGS) aggregator_LDFLAGS = -export-dynamic \ $(BINARY_LDFLAGS) aggregator_LDADD = $(LIBDOVECOT) aggregator_DEPENDENCIES = $(LIBDOVECOT_DEPS) aggregator_SOURCES = \ aggregator.c \ aggregator-settings.c \ notify-connection.c \ replicator-connection.c noinst_HEADERS = \ aggregator-settings.h \ notify-connection.h \ replicator-connection.h all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/replication/aggregator/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/replication/aggregator/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files clean-pkglibexecPROGRAMS: @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list aggregator$(EXEEXT): $(aggregator_OBJECTS) $(aggregator_DEPENDENCIES) $(EXTRA_aggregator_DEPENDENCIES) @rm -f aggregator$(EXEEXT) $(AM_V_CCLD)$(aggregator_LINK) $(aggregator_OBJECTS) $(aggregator_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/aggregator-settings.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/aggregator.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/notify-connection.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/replicator-connection.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am check: check-am all-am: Makefile $(PROGRAMS) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkglibexecdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-pkglibexecPROGRAMS \ mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/aggregator-settings.Po -rm -f ./$(DEPDIR)/aggregator.Po -rm -f ./$(DEPDIR)/notify-connection.Po -rm -f ./$(DEPDIR)/replicator-connection.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-pkglibexecPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/aggregator-settings.Po -rm -f ./$(DEPDIR)/aggregator.Po -rm -f ./$(DEPDIR)/notify-connection.Po -rm -f ./$(DEPDIR)/replicator-connection.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkglibexecPROGRAMS .MAKE: install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ clean-generic clean-libtool clean-pkglibexecPROGRAMS \ cscopelist-am ctags ctags-am distclean distclean-compile \ distclean-generic distclean-libtool distclean-tags distdir dvi \ dvi-am html html-am info info-am install install-am \ install-data install-data-am install-dvi install-dvi-am \ install-exec install-exec-am install-html install-html-am \ install-info install-info-am install-man install-pdf \ install-pdf-am install-pkglibexecPROGRAMS install-ps \ install-ps-am install-strip installcheck installcheck-am \ installdirs maintainer-clean maintainer-clean-generic \ mostlyclean mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ uninstall-am uninstall-pkglibexecPROGRAMS .PRECIOUS: Makefile # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/replication/aggregator/aggregator.c0000644000000000000000000000400414656633576020023 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "restrict-access.h" #include "master-service.h" #include "master-service-settings.h" #include "aggregator-settings.h" #include "notify-connection.h" #include "replicator-connection.h" struct replicator_connection *replicator; static void client_connected(struct master_service_connection *conn) { master_service_client_connection_accept(conn); notify_connection_create(conn->fd, conn->fifo); } static void main_preinit(void) { struct ip_addr *ips; unsigned int ips_count; const struct aggregator_settings *set; void **sets; int ret; sets = master_service_settings_get_others(master_service); set = sets[0]; if (set->replicator_port != 0) { ret = net_gethostbyname(set->replicator_host, &ips, &ips_count); if (ret != 0) { i_fatal("replicator_host: gethostbyname(%s) failed: %s", set->replicator_host, net_gethosterror(ret)); } replicator = replicator_connection_create_inet(ips, ips_count, set->replicator_port, notify_connection_sync_callback); } else { replicator = replicator_connection_create_unix(set->replicator_host, notify_connection_sync_callback); } } int main(int argc, char *argv[]) { const struct setting_parser_info *set_roots[] = { &aggregator_setting_parser_info, NULL }; const char *error; master_service = master_service_init("aggregator", 0, &argc, &argv, ""); if (master_getopt(master_service) > 0) return FATAL_DEFAULT; if (master_service_settings_read_simple(master_service, set_roots, &error) < 0) i_fatal("Error reading configuration: %s", error); master_service_init_log(master_service); main_preinit(); restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL); restrict_access_allow_coredumps(TRUE); master_service_init_finish(master_service); master_service_run(master_service, client_connected); notify_connections_destroy_all(); replicator_connection_destroy(&replicator); master_service_deinit(&master_service); return 0; } dovecot-2.3.21.1/src/replication/aggregator/aggregator-settings.c0000644000000000000000000000447614656633576021676 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "settings-parser.h" #include "service-settings.h" #include "aggregator-settings.h" /* */ static struct file_listener_settings aggregator_unix_listeners_array[] = { { "replication-notify", 0600, "", "" } }; static struct file_listener_settings *aggregator_unix_listeners[] = { &aggregator_unix_listeners_array[0] }; static buffer_t aggregator_unix_listeners_buf = { { { aggregator_unix_listeners, sizeof(aggregator_unix_listeners) } } }; static struct file_listener_settings aggregator_fifo_listeners_array[] = { { "replication-notify-fifo", 0600, "", "" } }; static struct file_listener_settings *aggregator_fifo_listeners[] = { &aggregator_fifo_listeners_array[0] }; static buffer_t aggregator_fifo_listeners_buf = { { { aggregator_fifo_listeners, sizeof(aggregator_fifo_listeners) } } }; /* */ struct service_settings aggregator_service_settings = { .name = "aggregator", .protocol = "", .type = "", .executable = "aggregator", .user = "$default_internal_user", .group = "", .privileged_group = "", .extra_groups = "", .chroot = ".", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 0, .client_limit = 0, .service_count = 0, .idle_kill = 0, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &aggregator_unix_listeners_buf, sizeof(aggregator_unix_listeners[0]) } }, .fifo_listeners = { { &aggregator_fifo_listeners_buf, sizeof(aggregator_fifo_listeners[0]) } }, .inet_listeners = ARRAY_INIT }; #undef DEF #define DEF(type, name) \ SETTING_DEFINE_STRUCT_##type(#name, name, struct aggregator_settings) static const struct setting_define aggregator_setting_defines[] = { DEF(STR, replicator_host), DEF(IN_PORT, replicator_port), SETTING_DEFINE_LIST_END }; const struct aggregator_settings aggregator_default_settings = { .replicator_host = "replicator", .replicator_port = 0 }; const struct setting_parser_info aggregator_setting_parser_info = { .module_name = "aggregator", .defines = aggregator_setting_defines, .defaults = &aggregator_default_settings, .type_offset = SIZE_MAX, .struct_size = sizeof(struct aggregator_settings), .parent_offset = SIZE_MAX }; const struct aggregator_settings *aggregator_settings; dovecot-2.3.21.1/src/replication/aggregator/aggregator-settings.h0000644000000000000000000000044614656633576021674 00000000000000#ifndef AGGREGATOR_SETTINGS_H #define AGGREGATOR_SETTINGS_H struct aggregator_settings { const char *replicator_host; in_port_t replicator_port; }; extern const struct setting_parser_info aggregator_setting_parser_info; extern const struct aggregator_settings *aggregator_settings; #endif dovecot-2.3.21.1/src/replication/aggregator/Makefile.am0000644000000000000000000000121314656633576017570 00000000000000pkglibexecdir = $(libexecdir)/dovecot pkglibexec_PROGRAMS = aggregator AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/replication \ -DPKG_STATEDIR=\""$(statedir)"\" \ $(BINARY_CFLAGS) aggregator_LDFLAGS = -export-dynamic \ $(BINARY_LDFLAGS) aggregator_LDADD = $(LIBDOVECOT) aggregator_DEPENDENCIES = $(LIBDOVECOT_DEPS) aggregator_SOURCES = \ aggregator.c \ aggregator-settings.c \ notify-connection.c \ replicator-connection.c noinst_HEADERS = \ aggregator-settings.h \ notify-connection.h \ replicator-connection.h dovecot-2.3.21.1/src/replication/aggregator/replicator-connection.h0000644000000000000000000000155514656633576022217 00000000000000#ifndef REPLICATOR_CONNECTION_H #define REPLICATOR_CONNECTION_H #include "replication-common.h" typedef void replicator_sync_callback_t(bool success, void *context); struct replicator_connection * replicator_connection_create_unix(const char *path, replicator_sync_callback_t *callback); struct replicator_connection * replicator_connection_create_inet(const struct ip_addr *ips, unsigned int ips_count, in_port_t port, replicator_sync_callback_t *callback); void replicator_connection_destroy(struct replicator_connection **conn); void replicator_connection_notify(struct replicator_connection *conn, const char *username, enum replication_priority priority); void replicator_connection_notify_sync(struct replicator_connection *conn, const char *username, void *context); extern struct replicator_connection *replicator; #endif dovecot-2.3.21.1/src/replication/aggregator/replicator-connection.c0000644000000000000000000002002014656633576022176 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "net.h" #include "istream.h" #include "ostream.h" #include "buffer.h" #include "hash.h" #include "llist.h" #include "strescape.h" #include "replicator-connection.h" #define MAX_INBUF_SIZE 1024 #define REPLICATOR_RECONNECT_MSECS 5000 #define REPLICATOR_MEMBUF_MAX_SIZE 1024*1024 #define REPLICATOR_HANDSHAKE "VERSION\treplicator-notify\t1\t0\n" struct replicator_connection { char *path; struct ip_addr *ips; unsigned int ips_count, ip_idx; in_port_t port; int fd; struct io *io; struct istream *input; struct ostream *output; struct timeout *to; buffer_t *queue[REPLICATION_PRIORITY_SYNC + 1]; HASH_TABLE(void *, void *) requests; unsigned int request_id_counter; replicator_sync_callback_t *callback; }; static void replicator_connection_disconnect(struct replicator_connection *conn); static int replicator_input_line(struct replicator_connection *conn, const char *line) { void *context; unsigned int id; /* <+|-> \t */ if ((line[0] != '+' && line[0] != '-') || line[1] != '\t' || str_to_uint(line+2, &id) < 0 || id == 0) { i_error("Replicator sent invalid input: %s", line); return -1; } context = hash_table_lookup(conn->requests, POINTER_CAST(id)); if (context == NULL) { i_error("Replicator sent invalid ID: %u", id); return -1; } hash_table_remove(conn->requests, POINTER_CAST(id)); conn->callback(line[0] == '+', context); return 0; } static void replicator_input(struct replicator_connection *conn) { const char *line; switch (i_stream_read(conn->input)) { case -2: /* buffer full */ i_error("Replicator sent too long line"); replicator_connection_disconnect(conn); return; case -1: /* disconnected */ replicator_connection_disconnect(conn); return; } while ((line = i_stream_next_line(conn->input)) != NULL) (void)replicator_input_line(conn, line); } static bool replicator_send_buf(struct replicator_connection *conn, buffer_t *buf) { const unsigned char *data = buf->data; size_t len = IO_BLOCK_SIZE; /* try to send about IO_BLOCK_SIZE amount of data, but only full lines */ if (len > buf->used) len = buf->used; for (;; len++) { i_assert(len < buf->used); /* there is always LF */ if (data[len] == '\n') { len++; break; } } if (o_stream_send(conn->output, data, len) < 0) { replicator_connection_disconnect(conn); return FALSE; } buffer_delete(buf, 0, len); return TRUE; } static int replicator_output(struct replicator_connection *conn) { enum replication_priority p; if (o_stream_flush(conn->output) < 0) { replicator_connection_disconnect(conn); return 1; } for (p = REPLICATION_PRIORITY_SYNC;;) { if (o_stream_get_buffer_used_size(conn->output) > 0) { o_stream_set_flush_pending(conn->output, TRUE); break; } /* output buffer is empty, send more data */ if (conn->queue[p]->used > 0) { if (!replicator_send_buf(conn, conn->queue[p])) break; } else { if (p == REPLICATION_PRIORITY_LOW) break; p--; } } return 1; } static void replicator_connection_connect(struct replicator_connection *conn) { unsigned int n; int fd = -1; if (conn->fd != -1) return; if (conn->port == 0) { fd = net_connect_unix(conn->path); if (fd == -1) i_error("net_connect_unix(%s) failed: %m", conn->path); } else { for (n = 0; n < conn->ips_count; n++) { unsigned int idx = conn->ip_idx; conn->ip_idx = (conn->ip_idx + 1) % conn->ips_count; fd = net_connect_ip(&conn->ips[idx], conn->port, NULL); if (fd != -1) break; i_error("connect(%s, %u) failed: %m", net_ip2addr(&conn->ips[idx]), conn->port); } } if (fd == -1) { if (conn->to == NULL) { conn->to = timeout_add(REPLICATOR_RECONNECT_MSECS, replicator_connection_connect, conn); } return; } timeout_remove(&conn->to); conn->fd = fd; conn->io = io_add(fd, IO_READ, replicator_input, conn); conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE); conn->output = o_stream_create_fd(fd, SIZE_MAX); o_stream_set_no_error_handling(conn->output, TRUE); o_stream_nsend_str(conn->output, REPLICATOR_HANDSHAKE); o_stream_set_flush_callback(conn->output, replicator_output, conn); } static void replicator_abort_all_requests(struct replicator_connection *conn) { struct hash_iterate_context *iter; void *key, *value; iter = hash_table_iterate_init(conn->requests); while (hash_table_iterate(iter, conn->requests, &key, &value)) conn->callback(FALSE, value); hash_table_iterate_deinit(&iter); hash_table_clear(conn->requests, TRUE); } static void replicator_connection_disconnect(struct replicator_connection *conn) { if (conn->fd == -1) return; replicator_abort_all_requests(conn); io_remove(&conn->io); i_stream_destroy(&conn->input); o_stream_destroy(&conn->output); net_disconnect(conn->fd); conn->fd = -1; } static struct replicator_connection *replicator_connection_create(void) { struct replicator_connection *conn; unsigned int i; conn = i_new(struct replicator_connection, 1); conn->fd = -1; hash_table_create_direct(&conn->requests, default_pool, 0); for (i = REPLICATION_PRIORITY_LOW; i <= REPLICATION_PRIORITY_SYNC; i++) conn->queue[i] = buffer_create_dynamic(default_pool, 1024); return conn; } struct replicator_connection * replicator_connection_create_unix(const char *path, replicator_sync_callback_t *callback) { struct replicator_connection *conn; conn = replicator_connection_create(); conn->callback = callback; conn->path = i_strdup(path); return conn; } struct replicator_connection * replicator_connection_create_inet(const struct ip_addr *ips, unsigned int ips_count, in_port_t port, replicator_sync_callback_t *callback) { struct replicator_connection *conn; conn = replicator_connection_create(); conn->callback = callback; conn->ips = i_new(struct ip_addr, ips_count); memcpy(conn->ips, ips, sizeof(*ips) * ips_count); conn->ips_count = ips_count; conn->port = port; return conn; } void replicator_connection_destroy(struct replicator_connection **_conn) { struct replicator_connection *conn = *_conn; unsigned int i; *_conn = NULL; replicator_connection_disconnect(conn); for (i = REPLICATION_PRIORITY_LOW; i <= REPLICATION_PRIORITY_SYNC; i++) buffer_free(&conn->queue[i]); timeout_remove(&conn->to); hash_table_destroy(&conn->requests); i_free(conn->ips); i_free(conn->path); i_free(conn); } static void replicator_send(struct replicator_connection *conn, enum replication_priority priority, const char *data) { size_t data_len = strlen(data); if (conn->fd != -1 && o_stream_get_buffer_used_size(conn->output) == 0) { /* we can send data immediately */ o_stream_nsend(conn->output, data, data_len); } else if (conn->queue[priority]->used + data_len >= REPLICATOR_MEMBUF_MAX_SIZE) { /* FIXME: compress duplicates, start writing to file */ } else { /* queue internally to separate queues */ buffer_append(conn->queue[priority], data, data_len); if (conn->output != NULL) o_stream_set_flush_pending(conn->output, TRUE); } } void replicator_connection_notify(struct replicator_connection *conn, const char *username, enum replication_priority priority) { const char *priority_str = ""; replicator_connection_connect(conn); switch (priority) { case REPLICATION_PRIORITY_NONE: case REPLICATION_PRIORITY_SYNC: i_unreached(); case REPLICATION_PRIORITY_LOW: priority_str = "low"; break; case REPLICATION_PRIORITY_HIGH: priority_str = "high"; break; } T_BEGIN { replicator_send(conn, priority, t_strdup_printf( "U\t%s\t%s\n", str_tabescape(username), priority_str)); } T_END; } void replicator_connection_notify_sync(struct replicator_connection *conn, const char *username, void *context) { unsigned int id; replicator_connection_connect(conn); id = ++conn->request_id_counter; if (id == 0) id++; hash_table_insert(conn->requests, POINTER_CAST(id), context); T_BEGIN { replicator_send(conn, REPLICATION_PRIORITY_SYNC, t_strdup_printf( "U\t%s\tsync\t%u\n", str_tabescape(username), id)); } T_END; } dovecot-2.3.21.1/src/replication/Makefile.in0000644000000000000000000005714414656633613015465 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ pkglibexecdir = $(libexecdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ subdir = src/replication ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = SOURCES = DIST_SOURCES = RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ ctags-recursive dvi-recursive html-recursive info-recursive \ install-data-recursive install-dvi-recursive \ install-exec-recursive install-html-recursive \ install-info-recursive install-pdf-recursive \ install-ps-recursive install-recursive installcheck-recursive \ installdirs-recursive pdf-recursive ps-recursive \ tags-recursive uninstall-recursive am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac HEADERS = $(noinst_HEADERS) RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ distclean-recursive maintainer-clean-recursive am__recursive_targets = \ $(RECURSIVE_TARGETS) \ $(RECURSIVE_CLEAN_TARGETS) \ $(am__extra_recursive_targets) AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ distdir distdir-am am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags DIST_SUBDIRS = $(SUBDIRS) am__DIST_COMMON = $(srcdir)/Makefile.in DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) am__relativize = \ dir0=`pwd`; \ sed_first='s,^\([^/]*\)/.*$$,\1,'; \ sed_rest='s,^[^/]*/*,,'; \ sed_last='s,^.*/\([^/]*\)$$,\1,'; \ sed_butlast='s,/*[^/]*$$,,'; \ while test -n "$$dir1"; do \ first=`echo "$$dir1" | sed -e "$$sed_first"`; \ if test "$$first" != "."; then \ if test "$$first" = ".."; then \ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ else \ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ if test "$$first2" = "$$first"; then \ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ else \ dir2="../$$dir2"; \ fi; \ dir0="$$dir0"/"$$first"; \ fi; \ fi; \ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ done; \ reldir="$$dir2" ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ SUBDIRS = aggregator replicator noinst_HEADERS = \ replication-common.h all: all-recursive .SUFFIXES: $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/replication/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/replication/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs # This directory's subdirectories are mostly independent; you can cd # into them and run 'make' without going through this Makefile. # To change the values of 'make' variables: instead of editing Makefiles, # (1) if the variable is set in 'config.status', edit 'config.status' # (which will cause the Makefiles to be regenerated when you run 'make'); # (2) otherwise, pass the desired values on the 'make' command line. $(am__recursive_targets): @fail=; \ if $(am__make_keepgoing); then \ failcom='fail=yes'; \ else \ failcom='exit 1'; \ fi; \ dot_seen=no; \ target=`echo $@ | sed s/-recursive//`; \ case "$@" in \ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ *) list='$(SUBDIRS)' ;; \ esac; \ for subdir in $$list; do \ echo "Making $$target in $$subdir"; \ if test "$$subdir" = "."; then \ dot_seen=yes; \ local_target="$$target-am"; \ else \ local_target="$$target"; \ fi; \ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ || eval $$failcom; \ done; \ if test "$$dot_seen" = "no"; then \ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ fi; test -z "$$fail" ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-recursive TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ include_option=--etags-include; \ empty_fix=.; \ else \ include_option=--include; \ empty_fix=; \ fi; \ list='$(SUBDIRS)'; for subdir in $$list; do \ if test "$$subdir" = .; then :; else \ test ! -f $$subdir/TAGS || \ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ fi; \ done; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-recursive CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-recursive cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ if test "$$subdir" = .; then :; else \ $(am__make_dryrun) \ || test -d "$(distdir)/$$subdir" \ || $(MKDIR_P) "$(distdir)/$$subdir" \ || exit 1; \ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ $(am__relativize); \ new_distdir=$$reldir; \ dir1=$$subdir; dir2="$(top_distdir)"; \ $(am__relativize); \ new_top_distdir=$$reldir; \ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ ($(am__cd) $$subdir && \ $(MAKE) $(AM_MAKEFLAGS) \ top_distdir="$$new_top_distdir" \ distdir="$$new_distdir" \ am__remove_distdir=: \ am__skip_length_check=: \ am__skip_mode_fix=: \ distdir) \ || exit 1; \ fi; \ done check-am: all-am check: check-recursive all-am: Makefile $(HEADERS) installdirs: installdirs-recursive installdirs-am: install: install-recursive install-exec: install-exec-recursive install-data: install-data-recursive uninstall: uninstall-recursive install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-recursive install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-recursive clean-am: clean-generic clean-libtool mostlyclean-am distclean: distclean-recursive -rm -f Makefile distclean-am: clean-am distclean-generic distclean-tags dvi: dvi-recursive dvi-am: html: html-recursive html-am: info: info-recursive info-am: install-data-am: install-dvi: install-dvi-recursive install-dvi-am: install-exec-am: install-html: install-html-recursive install-html-am: install-info: install-info-recursive install-info-am: install-man: install-pdf: install-pdf-recursive install-pdf-am: install-ps: install-ps-recursive install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-recursive -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-recursive mostlyclean-am: mostlyclean-generic mostlyclean-libtool pdf: pdf-recursive pdf-am: ps: ps-recursive ps-am: uninstall-am: .MAKE: $(am__recursive_targets) install-am install-strip .PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \ check-am clean clean-generic clean-libtool cscopelist-am ctags \ ctags-am distclean distclean-generic distclean-libtool \ distclean-tags distdir dvi dvi-am html html-am info info-am \ install install-am install-data install-data-am install-dvi \ install-dvi-am install-exec install-exec-am install-html \ install-html-am install-info install-info-am install-man \ install-pdf install-pdf-am install-ps install-ps-am \ install-strip installcheck installcheck-am installdirs \ installdirs-am maintainer-clean maintainer-clean-generic \ mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \ ps ps-am tags tags-am uninstall uninstall-am .PRECIOUS: Makefile # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/replication/replicator/0000755000000000000000000000000014656633640015631 500000000000000dovecot-2.3.21.1/src/replication/replicator/test-replicator-queue.c0000644000000000000000000002303014656633576022166 00000000000000/* Copyright (c) 2022 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "test-common.h" #include "replicator-queue.h" #define TEST_REPLICATION_FULL_SYNC_INTERVAL 60 #define TEST_REPLICATION_FAILURE_RESYNC_INTERVAL 10 static void test_replicator_queue(void) { struct replicator_queue *queue; struct replicator_user *user1, *user2, *user3, *user4; unsigned int next_secs; test_begin("replicator queue"); queue = replicator_queue_init(TEST_REPLICATION_FULL_SYNC_INTERVAL, TEST_REPLICATION_FAILURE_RESYNC_INTERVAL); ioloop_time = time(NULL); /* 1) Add users */ /* add the 1st user with priority=none */ user1 = replicator_queue_get(queue, "user1"); replicator_queue_update(queue, user1, REPLICATION_PRIORITY_NONE); replicator_queue_add(queue, user1); test_assert(replicator_queue_count(queue) == 1); test_assert(replicator_queue_peek(queue, &next_secs) == user1 && next_secs == 0); /* add the 2nd user with priority=none */ user2 = replicator_queue_get(queue, "user2"); replicator_queue_update(queue, user2, REPLICATION_PRIORITY_NONE); replicator_queue_add(queue, user2); test_assert(replicator_queue_count(queue) == 2); test_assert(replicator_queue_peek(queue, &next_secs) == user1 && next_secs == 0); /* add the 3rd user with priority=none */ user3 = replicator_queue_get(queue, "user3"); replicator_queue_update(queue, user3, REPLICATION_PRIORITY_NONE); replicator_queue_add(queue, user3); test_assert(replicator_queue_count(queue) == 3); test_assert(replicator_queue_peek(queue, &next_secs) == user1 && next_secs == 0); /* 2) User hasn't been synced yet, but priority is updated */ /* update the 2nd user's priority to low */ user2 = replicator_queue_get(queue, "user2"); replicator_queue_update(queue, user2, REPLICATION_PRIORITY_LOW); replicator_queue_add(queue, user2); test_assert(replicator_queue_peek(queue, &next_secs) == user2 && next_secs == 0); /* update the 1st user's priority to high */ user1 = replicator_queue_get(queue, "user1"); replicator_queue_update(queue, user1, REPLICATION_PRIORITY_HIGH); replicator_queue_add(queue, user1); test_assert(replicator_queue_peek(queue, &next_secs) == user1 && next_secs == 0); /* update the 2nd user's priority to sync */ user2 = replicator_queue_get(queue, "user2"); replicator_queue_update(queue, user2, REPLICATION_PRIORITY_SYNC); replicator_queue_add(queue, user2); test_assert(replicator_queue_peek(queue, &next_secs) == user2 && next_secs == 0); /* 3) User hasn't been synced, and priority is being updated. user1 was synced 1 second before user2. */ user1->last_fast_sync = ioloop_time; user1->last_full_sync = ioloop_time; user1->priority = REPLICATION_PRIORITY_NONE; replicator_queue_add(queue, user1); ioloop_time++; user2->last_fast_sync = ioloop_time; user2->last_full_sync = ioloop_time; user2->priority = REPLICATION_PRIORITY_NONE; replicator_queue_add(queue, user2); ioloop_time++; user3->last_fast_sync = ioloop_time; user3->last_full_sync = ioloop_time; user3->priority = REPLICATION_PRIORITY_NONE; replicator_queue_add(queue, user3); test_assert(replicator_queue_peek(queue, &next_secs) == user1 && next_secs > 0); /* update the 2nd user's priority to low */ user2 = replicator_queue_get(queue, "user2"); replicator_queue_update(queue, user2, REPLICATION_PRIORITY_LOW); replicator_queue_add(queue, user2); test_assert(replicator_queue_peek(queue, &next_secs) == user2 && next_secs == 0); /* update the 1st user's priority to high */ user1 = replicator_queue_get(queue, "user1"); replicator_queue_update(queue, user1, REPLICATION_PRIORITY_HIGH); replicator_queue_add(queue, user1); test_assert(replicator_queue_peek(queue, &next_secs) == user1 && next_secs == 0); /* update the 2nd user's priority to sync */ user2 = replicator_queue_get(queue, "user2"); replicator_queue_update(queue, user2, REPLICATION_PRIORITY_SYNC); replicator_queue_add(queue, user2); test_assert(replicator_queue_peek(queue, &next_secs) == user2 && next_secs == 0); /* 4) Test failed sync with a new user */ user1->priority = REPLICATION_PRIORITY_NONE; replicator_queue_add(queue, user1); user2->priority = REPLICATION_PRIORITY_NONE; replicator_queue_add(queue, user2); user4 = replicator_queue_get(queue, "user4"); user4->last_fast_sync = ioloop_time - 5; user4->last_sync_failed = TRUE; replicator_queue_add(queue, user4); test_assert(replicator_queue_count(queue) == 4); test_assert(replicator_queue_peek(queue, &next_secs) == user4 && next_secs == TEST_REPLICATION_FAILURE_RESYNC_INTERVAL - 5); /* low priority sync is prioritized over failed sync */ replicator_queue_update(queue, user1, REPLICATION_PRIORITY_LOW); replicator_queue_add(queue, user1); test_assert(replicator_queue_peek(queue, &next_secs) == user1 && next_secs == 0); /* However, if the last failure was old enough it will be before the low priority one. Test the edge case. */ user4->last_fast_sync = ioloop_time - TEST_REPLICATION_FAILURE_RESYNC_INTERVAL - (60*15) - 1; replicator_queue_add(queue, user4); test_assert(replicator_queue_peek(queue, &next_secs) == user4 && next_secs == 0); user4->last_fast_sync++; replicator_queue_add(queue, user4); test_assert(replicator_queue_peek(queue, &next_secs) == user1 && next_secs == 0); /* 5) Test priority starvation */ /* high priority is normally prioritized over low priority */ i_assert(user1->priority == REPLICATION_PRIORITY_LOW); user2 = replicator_queue_get(queue, "user2"); replicator_queue_update(queue, user2, REPLICATION_PRIORITY_HIGH); replicator_queue_add(queue, user2); test_assert(replicator_queue_peek(queue, &next_secs) == user2 && next_secs == 0); /* if low priority is old enough, it gets prioritized over high */ user1->last_update = ioloop_time - (60*15) - 1; replicator_queue_add(queue, user1); test_assert(replicator_queue_peek(queue, &next_secs) == user1 && next_secs == 0); user1->last_update++; replicator_queue_add(queue, user1); test_assert(replicator_queue_peek(queue, &next_secs) == user2 && next_secs == 0); /* similarly low priority eventually gets prioritized over sync priority */ replicator_queue_update(queue, user2, REPLICATION_PRIORITY_SYNC); replicator_queue_add(queue, user2); user1->last_update = ioloop_time - (60*30) - 1; replicator_queue_add(queue, user1); test_assert(replicator_queue_peek(queue, &next_secs) == user1 && next_secs == 0); user1->last_update++; replicator_queue_add(queue, user1); test_assert(replicator_queue_peek(queue, &next_secs) == user2 && next_secs == 0); /* likewise for none priority also */ user1->priority = REPLICATION_PRIORITY_NONE; user1->last_update = ioloop_time; user1->last_fast_sync = ioloop_time; user1->last_full_sync = ioloop_time - (60*45) - TEST_REPLICATION_FULL_SYNC_INTERVAL - 1; replicator_queue_add(queue, user1); test_assert(replicator_queue_peek(queue, &next_secs) == user1 && next_secs == 0); user1->last_full_sync++; replicator_queue_add(queue, user1); test_assert(replicator_queue_peek(queue, &next_secs) == user2 && next_secs == 0); replicator_queue_deinit(&queue); test_end(); } static void test_replicator_queue_verify_drained(struct replicator_queue *queue) { struct replicator_queue_iter *iter = replicator_queue_iter_init(queue); struct replicator_user *user; while ((user = replicator_queue_iter_next(iter)) != NULL) { i_assert(user->priority == REPLICATION_PRIORITY_NONE); i_assert(user->last_sync_failed || ioloop_time - user->last_full_sync < TEST_REPLICATION_FULL_SYNC_INTERVAL); } replicator_queue_iter_deinit(&iter); } static void test_replicator_queue_drain(struct replicator_queue *queue) { struct replicator_user *user; unsigned int next_secs; enum replication_priority prev_priority = REPLICATION_PRIORITY_SYNC; time_t prev_sync = INT_MAX; while ((user = replicator_queue_pop(queue, &next_secs)) != NULL) { if (user->priority < prev_priority) { prev_sync = INT_MAX; } else { test_assert(user->priority == prev_priority); if (user->priority == REPLICATION_PRIORITY_NONE) { test_assert(user->last_full_sync <= prev_sync); prev_sync = user->last_full_sync; } else { test_assert(user->last_fast_sync <= prev_sync); prev_sync = user->last_fast_sync; } } user->priority = REPLICATION_PRIORITY_NONE; user->last_fast_sync = user->last_full_sync = ioloop_time-1; /* dsync runs here */ if (i_rand_limit(5) == 0) user->last_sync_failed = TRUE; else { user->last_successful_sync = ioloop_time; user->last_sync_failed = FALSE; } replicator_queue_push(queue, user); } test_replicator_queue_verify_drained(queue); } static void test_replicator_queue_random(void) { struct replicator_queue *queue; struct replicator_user *user; test_begin("replicator queue random"); queue = replicator_queue_init(TEST_REPLICATION_FULL_SYNC_INTERVAL, TEST_REPLICATION_FAILURE_RESYNC_INTERVAL); /* fill some users */ ioloop_time = time(NULL); for (unsigned int i = 0; i < 1000; i++) T_BEGIN { enum replication_priority priority = i_rand_minmax(REPLICATION_PRIORITY_NONE, REPLICATION_PRIORITY_SYNC); const char *username = t_strdup_printf("test%u", i_rand_minmax(1, 200)); user = replicator_queue_get(queue, username); replicator_queue_update(queue, user, priority); replicator_queue_add(queue, user); ioloop_time++; } T_END; for (unsigned int i = 0; i < 1000; i++) { test_replicator_queue_drain(queue); ioloop_time++; } replicator_queue_deinit(&queue); test_end(); } int main(void) { static void (*const test_functions[])(void) = { test_replicator_queue, test_replicator_queue_random, NULL }; return test_run(test_functions); } dovecot-2.3.21.1/src/replication/replicator/doveadm-connection.h0000644000000000000000000000036214656633576021507 00000000000000#ifndef DOVEADM_CONNECTION_H #define DOVEADM_CONNECTION_H struct replicator_brain; void doveadm_connection_create(struct replicator_brain *brain, int fd); void doveadm_connections_init(void); void doveadm_connections_deinit(void); #endif dovecot-2.3.21.1/src/replication/replicator/replicator.c0000644000000000000000000000665514656633576020105 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "restrict-access.h" #include "auth-master.h" #include "master-service.h" #include "master-service-settings.h" #include "notify-connection.h" #include "doveadm-connection.h" #include "replicator-brain.h" #include "replicator-queue.h" #include "replicator-settings.h" #define REPLICATOR_DB_DUMP_INTERVAL_MSECS (1000*60*15) /* if syncing fails, try again in 5 minutes */ #define REPLICATOR_FAILURE_RESYNC_INTERVAL_SECS (60*5) #define REPLICATOR_DB_FNAME "replicator.db" static struct replicator_queue *queue; static struct replicator_brain *brain; static const struct master_service_settings *service_set; static const struct replicator_settings *set; static struct timeout *to_dump; static void client_connected(struct master_service_connection *conn) { master_service_client_connection_accept(conn); if (strcmp(conn->name, "replicator-doveadm") == 0) doveadm_connection_create(brain, conn->fd); else (void)notify_connection_create(conn->fd, queue); } static void replication_add_users(struct replicator_queue *queue) { const char *path; replicator_queue_add_auth_users(queue, set->auth_socket_path, "*", 0); /* add updates from replicator db, if it exists */ path = t_strconcat(service_set->state_dir, "/"REPLICATOR_DB_FNAME, NULL); (void)replicator_queue_import(queue, path); } static void ATTR_NULL(1) replicator_dump_timeout(void *context ATTR_UNUSED) { const char *path; path = t_strconcat(service_set->state_dir, "/"REPLICATOR_DB_FNAME, NULL); (void)replicator_queue_export(queue, path); } static void main_init(void) { void **sets; service_set = master_service_settings_get(master_service); sets = master_service_settings_get_others(master_service); set = sets[0]; queue = replicator_queue_init(set->replication_full_sync_interval, REPLICATOR_FAILURE_RESYNC_INTERVAL_SECS); replication_add_users(queue); to_dump = timeout_add(REPLICATOR_DB_DUMP_INTERVAL_MSECS, replicator_dump_timeout, NULL); brain = replicator_brain_init(queue, set); doveadm_connections_init(); } static void main_deinit(void) { const char *path; doveadm_connections_deinit(); notify_connections_destroy_all(); replicator_brain_deinit(&brain); timeout_remove(&to_dump); path = t_strconcat(service_set->state_dir, "/"REPLICATOR_DB_FNAME, NULL); (void)replicator_queue_export(queue, path); replicator_queue_deinit(&queue); } int main(int argc, char *argv[]) { const struct setting_parser_info *set_roots[] = { &replicator_setting_parser_info, NULL }; const enum master_service_flags service_flags = MASTER_SERVICE_FLAG_NO_IDLE_DIE; const char *error; master_service = master_service_init("replicator", service_flags, &argc, &argv, ""); if (master_getopt(master_service) > 0) return FATAL_DEFAULT; if (master_service_settings_read_simple(master_service, set_roots, &error) < 0) i_fatal("Error reading configuration: %s", error); master_service_init_log(master_service); restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL); restrict_access_allow_coredumps(TRUE); /* finish init before we get list of users from auth, because that can take long enough for master process to kill us otherwise. */ master_service_init_finish(master_service); main_init(); master_service_run(master_service, client_connected); main_deinit(); master_service_deinit(&master_service); return 0; } dovecot-2.3.21.1/src/replication/replicator/notify-connection.h0000644000000000000000000000053714656633576021404 00000000000000#ifndef NOTIFY_CONNECTION_H #define NOTIFY_CONNECTION_H struct replicator_queue; struct notify_connection * notify_connection_create(int fd, struct replicator_queue *queue); void notify_connection_ref(struct notify_connection *conn); void notify_connection_unref(struct notify_connection **conn); void notify_connections_destroy_all(void); #endif dovecot-2.3.21.1/src/replication/replicator/notify-connection.c0000644000000000000000000001163514656633576021400 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "llist.h" #include "istream.h" #include "ostream.h" #include "strescape.h" #include "master-service.h" #include "replicator-queue.h" #include "notify-connection.h" #include #define MAX_INBUF_SIZE (1024*64) #define NOTIFY_CLIENT_PROTOCOL_MAJOR_VERSION 1 #define NOTIFY_CLIENT_PROTOCOL_MINOR_VERSION 0 struct notify_connection { struct notify_connection *prev, *next; int refcount; int fd; struct io *io; struct istream *input; struct ostream *output; struct replicator_queue *queue; bool version_received:1; bool destroyed:1; }; struct notify_sync_request { struct notify_connection *conn; unsigned int id; }; static struct notify_connection *connections; static void notify_connection_destroy(struct notify_connection *conn); static void notify_sync_callback(bool success, void *context) { struct notify_sync_request *request = context; o_stream_nsend_str(request->conn->output, t_strdup_printf( "%c\t%u\n", success ? '+' : '-', request->id)); notify_connection_unref(&request->conn); i_free(request); } static int notify_connection_input_line(struct notify_connection *conn, const char *line) { struct notify_sync_request *request; const char *const *args; enum replication_priority priority; unsigned int id; /* U \t \t [\t ] */ args = t_strsplit_tabescaped(line); if (str_array_length(args) < 2) { i_error("notify client sent invalid input: %s", line); return -1; } if (strcmp(args[0], "U") != 0) { i_error("notify client sent unknown command: %s", args[0]); return -1; } if (replication_priority_parse(args[2], &priority) < 0) { i_error("notify client sent invalid priority: %s", args[2]); return -1; } if (priority != REPLICATION_PRIORITY_SYNC) { struct replicator_user *user = replicator_queue_get(conn->queue, args[1]); replicator_queue_update(conn->queue, user, priority); replicator_queue_add(conn->queue, user); } else if (args[3] == NULL || str_to_uint(args[3], &id) < 0) { i_error("notify client sent invalid sync id: %s", line); return -1; } else { request = i_new(struct notify_sync_request, 1); request->conn = conn; request->id = id; notify_connection_ref(conn); struct replicator_user *user = replicator_queue_get(conn->queue, args[1]); replicator_queue_update(conn->queue, user, REPLICATION_PRIORITY_SYNC); replicator_queue_add_sync_callback(conn->queue, user, notify_sync_callback, request); } return 0; } static void notify_connection_input(struct notify_connection *conn) { const char *line; int ret; switch (i_stream_read(conn->input)) { case -2: i_error("BUG: Client connection sent too much data"); notify_connection_destroy(conn); return; case -1: notify_connection_destroy(conn); return; } if (!conn->version_received) { if ((line = i_stream_next_line(conn->input)) == NULL) return; if (!version_string_verify(line, "replicator-notify", NOTIFY_CLIENT_PROTOCOL_MAJOR_VERSION)) { i_error("Notify client not compatible with this server " "(mixed old and new binaries?)"); notify_connection_destroy(conn); return; } conn->version_received = TRUE; } while ((line = i_stream_next_line(conn->input)) != NULL) { T_BEGIN { ret = notify_connection_input_line(conn, line); } T_END; if (ret < 0) { notify_connection_destroy(conn); break; } } } struct notify_connection * notify_connection_create(int fd, struct replicator_queue *queue) { struct notify_connection *conn; i_assert(fd >= 0); conn = i_new(struct notify_connection, 1); conn->refcount = 1; conn->queue = queue; conn->fd = fd; conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE); conn->output = o_stream_create_fd(fd, SIZE_MAX); o_stream_set_no_error_handling(conn->output, TRUE); conn->io = io_add(fd, IO_READ, notify_connection_input, conn); conn->queue = queue; DLLIST_PREPEND(&connections, conn); return conn; } static void notify_connection_destroy(struct notify_connection *conn) { if (conn->destroyed) return; conn->destroyed = TRUE; DLLIST_REMOVE(&connections, conn); io_remove(&conn->io); i_stream_close(conn->input); o_stream_close(conn->output); if (close(conn->fd) < 0) i_error("close(notify connection) failed: %m"); conn->fd = -1; notify_connection_unref(&conn); master_service_client_connection_destroyed(master_service); } void notify_connection_ref(struct notify_connection *conn) { i_assert(conn->refcount > 0); conn->refcount++; } void notify_connection_unref(struct notify_connection **_conn) { struct notify_connection *conn = *_conn; i_assert(conn->refcount > 0); *_conn = NULL; if (--conn->refcount > 0) return; notify_connection_destroy(conn); i_stream_unref(&conn->input); o_stream_unref(&conn->output); i_free(conn); } void notify_connections_destroy_all(void) { while (connections != NULL) notify_connection_destroy(connections); } dovecot-2.3.21.1/src/replication/replicator/Makefile.in0000644000000000000000000007220314656633613017622 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ pkglibexec_PROGRAMS = replicator$(EXEEXT) noinst_PROGRAMS = $(am__EXEEXT_1) subdir = src/replication/replicator ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__EXEEXT_1 = test-replicator-queue$(EXEEXT) am__installdirs = "$(DESTDIR)$(pkglibexecdir)" PROGRAMS = $(noinst_PROGRAMS) $(pkglibexec_PROGRAMS) am_replicator_OBJECTS = doveadm-connection.$(OBJEXT) \ dsync-client.$(OBJEXT) replicator.$(OBJEXT) \ replicator-brain.$(OBJEXT) replicator-queue.$(OBJEXT) \ replicator-queue-auth.$(OBJEXT) replicator-settings.$(OBJEXT) \ notify-connection.$(OBJEXT) replicator_OBJECTS = $(am_replicator_OBJECTS) am__DEPENDENCIES_1 = AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = replicator_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(replicator_LDFLAGS) $(LDFLAGS) -o $@ am_test_replicator_queue_OBJECTS = replicator-queue.$(OBJEXT) \ replicator-settings.$(OBJEXT) test-replicator-queue.$(OBJEXT) test_replicator_queue_OBJECTS = $(am_test_replicator_queue_OBJECTS) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/doveadm-connection.Po \ ./$(DEPDIR)/dsync-client.Po ./$(DEPDIR)/notify-connection.Po \ ./$(DEPDIR)/replicator-brain.Po \ ./$(DEPDIR)/replicator-queue-auth.Po \ ./$(DEPDIR)/replicator-queue.Po \ ./$(DEPDIR)/replicator-settings.Po ./$(DEPDIR)/replicator.Po \ ./$(DEPDIR)/test-replicator-queue.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(replicator_SOURCES) $(test_replicator_queue_SOURCES) DIST_SOURCES = $(replicator_SOURCES) $(test_replicator_queue_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac HEADERS = $(noinst_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) pkglibexecdir = $(libexecdir)/dovecot ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/replication \ -DPKG_STATEDIR=\""$(statedir)"\" \ $(BINARY_CFLAGS) replicator_LDFLAGS = -export-dynamic \ $(BINARY_LDFLAGS) replicator_LDADD = $(LIBDOVECOT) replicator_DEPENDENCIES = $(LIBDOVECOT_DEPS) replicator_SOURCES = \ doveadm-connection.c \ dsync-client.c \ replicator.c \ replicator-brain.c \ replicator-queue.c \ replicator-queue-auth.c \ replicator-settings.c \ notify-connection.c noinst_HEADERS = \ doveadm-connection.h \ dsync-client.h \ replicator-brain.h \ replicator-queue.h \ replicator-settings.h \ notify-connection.h test_programs = \ test-replicator-queue test_libs = \ ../../lib-test/libtest.la \ ../../lib/liblib.la test_deps = $(test_libs) test_replicator_queue_SOURCES = \ replicator-queue.c \ replicator-settings.c \ test-replicator-queue.c test_replicator_queue_LDADD = $(test_libs) test_replicator_queue_DEPENDENCIES = $(test_deps) all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/replication/replicator/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/replication/replicator/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): clean-noinstPROGRAMS: @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files clean-pkglibexecPROGRAMS: @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list replicator$(EXEEXT): $(replicator_OBJECTS) $(replicator_DEPENDENCIES) $(EXTRA_replicator_DEPENDENCIES) @rm -f replicator$(EXEEXT) $(AM_V_CCLD)$(replicator_LINK) $(replicator_OBJECTS) $(replicator_LDADD) $(LIBS) test-replicator-queue$(EXEEXT): $(test_replicator_queue_OBJECTS) $(test_replicator_queue_DEPENDENCIES) $(EXTRA_test_replicator_queue_DEPENDENCIES) @rm -f test-replicator-queue$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_replicator_queue_OBJECTS) $(test_replicator_queue_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-connection.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/notify-connection.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/replicator-brain.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/replicator-queue-auth.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/replicator-queue.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/replicator-settings.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/replicator.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-replicator-queue.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am $(MAKE) $(AM_MAKEFLAGS) check-local check: check-am all-am: Makefile $(PROGRAMS) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkglibexecdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \ clean-pkglibexecPROGRAMS mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/doveadm-connection.Po -rm -f ./$(DEPDIR)/dsync-client.Po -rm -f ./$(DEPDIR)/notify-connection.Po -rm -f ./$(DEPDIR)/replicator-brain.Po -rm -f ./$(DEPDIR)/replicator-queue-auth.Po -rm -f ./$(DEPDIR)/replicator-queue.Po -rm -f ./$(DEPDIR)/replicator-settings.Po -rm -f ./$(DEPDIR)/replicator.Po -rm -f ./$(DEPDIR)/test-replicator-queue.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-pkglibexecPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/doveadm-connection.Po -rm -f ./$(DEPDIR)/dsync-client.Po -rm -f ./$(DEPDIR)/notify-connection.Po -rm -f ./$(DEPDIR)/replicator-brain.Po -rm -f ./$(DEPDIR)/replicator-queue-auth.Po -rm -f ./$(DEPDIR)/replicator-queue.Po -rm -f ./$(DEPDIR)/replicator-settings.Po -rm -f ./$(DEPDIR)/replicator.Po -rm -f ./$(DEPDIR)/test-replicator-queue.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkglibexecPROGRAMS .MAKE: check-am install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ check-local clean clean-generic clean-libtool \ clean-noinstPROGRAMS clean-pkglibexecPROGRAMS cscopelist-am \ ctags ctags-am distclean distclean-compile distclean-generic \ distclean-libtool distclean-tags distdir dvi dvi-am html \ html-am info info-am install install-am install-data \ install-data-am install-dvi install-dvi-am install-exec \ install-exec-am install-html install-html-am install-info \ install-info-am install-man install-pdf install-pdf-am \ install-pkglibexecPROGRAMS install-ps install-ps-am \ install-strip installcheck installcheck-am installdirs \ maintainer-clean maintainer-clean-generic mostlyclean \ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ uninstall-pkglibexecPROGRAMS .PRECIOUS: Makefile check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/replication/replicator/replicator-settings.h0000644000000000000000000000065414656633576021741 00000000000000#ifndef REPLICATOR_SETTINGS_H #define REPLICATOR_SETTINGS_H struct replicator_settings { const char *auth_socket_path; const char *doveadm_socket_path; const char *replication_dsync_parameters; unsigned int replication_full_sync_interval; unsigned int replication_max_conns; }; extern const struct setting_parser_info replicator_setting_parser_info; extern const struct replicator_settings *replicator_settings; #endif dovecot-2.3.21.1/src/replication/replicator/replicator-settings.c0000644000000000000000000000442714656633576021736 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "settings-parser.h" #include "service-settings.h" #include "replicator-settings.h" /* */ static struct file_listener_settings replicator_unix_listeners_array[] = { { "replicator", 0600, "$default_internal_user", "" }, { "replicator-doveadm", 0, "$default_internal_user", "" } }; static struct file_listener_settings *replicator_unix_listeners[] = { &replicator_unix_listeners_array[0], &replicator_unix_listeners_array[1] }; static buffer_t replicator_unix_listeners_buf = { { { replicator_unix_listeners, sizeof(replicator_unix_listeners) } } }; /* */ struct service_settings replicator_service_settings = { .name = "replicator", .protocol = "", .type = "", .executable = "replicator", .user = "", .group = "", .privileged_group = "", .extra_groups = "", .chroot = "", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 1, .client_limit = 0, .service_count = 0, .idle_kill = UINT_MAX, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &replicator_unix_listeners_buf, sizeof(replicator_unix_listeners[0]) } }, .fifo_listeners = ARRAY_INIT, .inet_listeners = ARRAY_INIT, .process_limit_1 = TRUE }; #undef DEF #define DEF(type, name) \ SETTING_DEFINE_STRUCT_##type(#name, name, struct replicator_settings) static const struct setting_define replicator_setting_defines[] = { DEF(STR, auth_socket_path), DEF(STR, doveadm_socket_path), DEF(STR, replication_dsync_parameters), DEF(TIME, replication_full_sync_interval), DEF(UINT, replication_max_conns), SETTING_DEFINE_LIST_END }; const struct replicator_settings replicator_default_settings = { .auth_socket_path = "auth-userdb", .doveadm_socket_path = "doveadm-server", .replication_dsync_parameters = "-d -N -l 30 -U", .replication_full_sync_interval = 60*60*24, .replication_max_conns = 10 }; const struct setting_parser_info replicator_setting_parser_info = { .module_name = "replicator", .defines = replicator_setting_defines, .defaults = &replicator_default_settings, .type_offset = SIZE_MAX, .struct_size = sizeof(struct replicator_settings), .parent_offset = SIZE_MAX }; const struct replicator_settings *replicator_settings; dovecot-2.3.21.1/src/replication/replicator/replicator-queue-auth.c0000644000000000000000000000242014656633576022150 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "auth-master.h" #include "replicator-queue.h" #define REPLICATOR_AUTH_SERVICE_NAME "replicator" void replicator_queue_add_auth_users(struct replicator_queue *queue, const char *auth_socket_path, const char *usermask, time_t last_update) { struct auth_master_connection *auth_conn; struct auth_master_user_list_ctx *ctx; struct auth_user_info user_info; struct replicator_user *user; const char *username; auth_conn = auth_master_init(auth_socket_path, AUTH_MASTER_FLAG_NO_IDLE_TIMEOUT); i_zero(&user_info); user_info.service = REPLICATOR_AUTH_SERVICE_NAME; /* add all users into replication queue, so that we can start doing full syncs for everyone whose state can't be found */ ctx = auth_master_user_list_init(auth_conn, usermask, &user_info); while ((username = auth_master_user_list_next(ctx)) != NULL) { user = replicator_queue_get(queue, username); replicator_queue_update(queue, user, REPLICATION_PRIORITY_NONE); replicator_queue_add(queue, user); user->last_update = last_update; } if (auth_master_user_list_deinit(&ctx) < 0) i_error("listing users failed, can't replicate existing data"); auth_master_deinit(&auth_conn); } dovecot-2.3.21.1/src/replication/replicator/dsync-client.c0000644000000000000000000001523114656633576020323 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "net.h" #include "istream.h" #include "ostream.h" #include "str.h" #include "strescape.h" #include "dsync-client.h" #include #define DSYNC_FAIL_TIMEOUT_MSECS (1000*5) #define DOVEADM_HANDSHAKE "VERSION\tdoveadm-server\t1\t0\n" struct dsync_client { char *path; int fd; struct io *io; struct istream *input; struct ostream *output; struct timeout *to; char *dsync_params; char *username; char *state; enum dsync_type sync_type; dsync_callback_t *callback; void *context; time_t last_connect_failure; bool handshaked:1; bool cmd_sent:1; }; struct dsync_client * dsync_client_init(const char *path, const char *dsync_params) { struct dsync_client *client; client = i_new(struct dsync_client, 1); client->path = i_strdup(path); client->fd = -1; client->dsync_params = i_strdup(dsync_params); return client; } static void dsync_callback(struct dsync_client *client, const char *state, enum dsync_reply reply) { dsync_callback_t *callback = client->callback; void *context = client->context; timeout_remove(&client->to); client->callback = NULL; client->context = NULL; /* make sure callback doesn't try to reuse this connection, since we can't currently handle it */ i_assert(!client->cmd_sent); client->cmd_sent = TRUE; callback(reply, state, context); client->cmd_sent = FALSE; } static void dsync_close(struct dsync_client *client) { client->cmd_sent = FALSE; client->handshaked = FALSE; i_free_and_null(client->state); i_free_and_null(client->username); if (client->fd == -1) return; io_remove(&client->io); o_stream_destroy(&client->output); i_stream_destroy(&client->input); if (close(client->fd) < 0) i_error("close(dsync) failed: %m"); client->fd = -1; } static void dsync_disconnect(struct dsync_client *client) { dsync_close(client); if (client->callback != NULL) dsync_callback(client, "", DSYNC_REPLY_FAIL); } void dsync_client_deinit(struct dsync_client **_client) { struct dsync_client *client = *_client; *_client = NULL; dsync_disconnect(client); i_free(client->dsync_params); i_free(client->path); i_free(client); } static int dsync_input_line(struct dsync_client *client, const char *line) { const char *state; if (!client->handshaked) { if (strcmp(line, "+") != 0) { i_error("%s: Unexpected handshake: %s", client->path, line); return -1; } client->handshaked = TRUE; return 0; } if (client->callback == NULL) { i_error("%s: Unexpected input: %s", client->path, line); return -1; } if (client->state == NULL) { client->state = i_strdup(t_strcut(line, '\t')); return 0; } state = t_strdup(client->state); line = t_strdup(line); dsync_close(client); if (line[0] == '+') dsync_callback(client, state, DSYNC_REPLY_OK); else if (line[0] == '-') { if (strcmp(line+1, "NOUSER") == 0) dsync_callback(client, "", DSYNC_REPLY_NOUSER); else if (strcmp(line+1, "NOREPLICATE") == 0) dsync_callback(client, "", DSYNC_REPLY_NOREPLICATE); else dsync_callback(client, "", DSYNC_REPLY_FAIL); } else { i_error("%s: Invalid input: %s", client->path, line); return -1; } /* FIXME: disconnect after each request for now. doveadm server's getopt() handling seems to break otherwise. also with multiple UIDs doveadm-server fails because setid() fails */ return -1; } static void dsync_input(struct dsync_client *client) { const char *line; while ((line = i_stream_read_next_line(client->input)) != NULL) { if (dsync_input_line(client, line) < 0) { dsync_disconnect(client); return; } } if (client->input->eof) dsync_disconnect(client); } static int dsync_connect(struct dsync_client *client) { if (client->fd != -1) return 0; if (client->last_connect_failure == ioloop_time) return -1; client->fd = net_connect_unix(client->path); if (client->fd == -1) { i_error("net_connect_unix(%s) failed: %m", client->path); client->last_connect_failure = ioloop_time; return -1; } client->last_connect_failure = 0; client->io = io_add(client->fd, IO_READ, dsync_input, client); client->input = i_stream_create_fd(client->fd, SIZE_MAX); client->output = o_stream_create_fd(client->fd, SIZE_MAX); o_stream_set_no_error_handling(client->output, TRUE); o_stream_nsend_str(client->output, DOVEADM_HANDSHAKE); return 0; } static void dsync_fail_timeout(struct dsync_client *client) { dsync_disconnect(client); } void dsync_client_sync(struct dsync_client *client, const char *username, const char *state, bool full, dsync_callback_t *callback, void *context) { string_t *cmd; unsigned int pos; char *p; i_assert(callback != NULL); i_assert(!dsync_client_is_busy(client)); client->username = i_strdup(username); client->cmd_sent = TRUE; client->callback = callback; client->context = context; if (full) client->sync_type = DSYNC_TYPE_FULL; else if (state != NULL && state[0] != '\0') client->sync_type = DSYNC_TYPE_INCREMENTAL; else client->sync_type = DSYNC_TYPE_NORMAL; if (dsync_connect(client) < 0) { i_assert(client->to == NULL); client->to = timeout_add(DSYNC_FAIL_TIMEOUT_MSECS, dsync_fail_timeout, client); } else { /* [] */ cmd = t_str_new(256); str_append_c(cmd, '\t'); str_append_tabescaped(cmd, username); str_append(cmd, "\tsync\t"); pos = str_len(cmd); /* insert the parameters. we can do it simply by converting spaces into tabs, it's unlikely we'll ever need anything more complex here. */ str_append(cmd, client->dsync_params); p = str_c_modifiable(cmd) + pos; for (; *p != '\0'; p++) { if (*p == ' ') *p = '\t'; } if (full) str_append(cmd, "\t-f"); str_append(cmd, "\t-s\t"); if (state != NULL) str_append(cmd, state); str_append_c(cmd, '\n'); o_stream_nsend(client->output, str_data(cmd), str_len(cmd)); } } bool dsync_client_is_busy(struct dsync_client *client) { return client->cmd_sent; } const char *dsync_client_get_username(struct dsync_client *conn) { return conn->username; } enum dsync_type dsync_client_get_type(struct dsync_client *conn) { return conn->sync_type; } const char *dsync_client_get_state(struct dsync_client *conn) { if (conn->fd == -1) { if (conn->last_connect_failure == 0) return "Not connected"; return t_strdup_printf("Failed to connect to '%s' - last attempt %ld secs ago", conn->path, (long)(ioloop_time - conn->last_connect_failure)); } if (!dsync_client_is_busy(conn)) return "Idle"; if (!conn->handshaked) return "Waiting for handshake"; if (conn->state == NULL) return "Waiting for dsync to finish"; else return "Waiting for dsync to finish (second line)"; } dovecot-2.3.21.1/src/replication/replicator/dsync-client.h0000644000000000000000000000172114656633576020327 00000000000000#ifndef DSYNC_CLIENT_H #define DSYNC_CLIENT_H struct dsync_client; enum dsync_reply { DSYNC_REPLY_OK, DSYNC_REPLY_FAIL, DSYNC_REPLY_NOUSER, DSYNC_REPLY_NOREPLICATE, }; enum dsync_type { DSYNC_TYPE_FULL, DSYNC_TYPE_NORMAL, DSYNC_TYPE_INCREMENTAL }; ARRAY_DEFINE_TYPE(dsync_client, struct dsync_client *); typedef void dsync_callback_t(enum dsync_reply reply, const char *state, void *context); struct dsync_client * dsync_client_init(const char *path, const char *dsync_params); void dsync_client_deinit(struct dsync_client **conn); void dsync_client_sync(struct dsync_client *conn, const char *username, const char *state, bool full, dsync_callback_t *callback, void *context); bool dsync_client_is_busy(struct dsync_client *conn); const char *dsync_client_get_username(struct dsync_client *conn); enum dsync_type dsync_client_get_type(struct dsync_client *conn); const char *dsync_client_get_state(struct dsync_client *conn); #endif dovecot-2.3.21.1/src/replication/replicator/replicator-brain.h0000644000000000000000000000112314656633576021164 00000000000000#ifndef REPLICATOR_BRAIN_H #define REPLICATOR_BRAIN_H struct replicator_settings; struct replicator_queue; struct replicator_brain * replicator_brain_init(struct replicator_queue *queue, const struct replicator_settings *set); void replicator_brain_deinit(struct replicator_brain **brain); struct replicator_queue * replicator_brain_get_queue(struct replicator_brain *brain); const struct replicator_settings * replicator_brain_get_settings(struct replicator_brain *brain); const ARRAY_TYPE(dsync_client) * replicator_brain_get_dsync_clients(struct replicator_brain *brain); #endif dovecot-2.3.21.1/src/replication/replicator/replicator-queue.c0000644000000000000000000003450714656633576021224 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "str.h" #include "strescape.h" #include "hash.h" #include "replicator-queue.h" #include #include struct replicator_sync_lookup { struct replicator_user *user; replicator_sync_callback_t *callback; void *context; bool wait_for_next_push; }; struct replicator_queue { struct priorityq *user_queue; /* username => struct replicator_user* */ HASH_TABLE(char *, struct replicator_user *) user_hash; ARRAY(struct replicator_sync_lookup) sync_lookups; unsigned int full_sync_interval; unsigned int failure_resync_interval; void (*change_callback)(void *context); void *change_context; }; struct replicator_queue_iter { struct replicator_queue *queue; struct hash_iterate_context *iter; }; static unsigned int replicator_full_sync_interval = 0; static unsigned int replicator_failure_resync_interval = 0; static time_t replicator_user_next_sync_time(const struct replicator_user *user) { /* The idea is that the higher the priority, the more likely it will be prioritized over low priority syncs. However, to avoid permanent starvation of lower priority users, the priority boost is only temporary. The REPLICATION_PRIORITY_*_SECS macros effectively specify how long lower priority requests are allowed to be waiting. */ #define REPLICATION_PRIORITY_LOW_SECS (60*15) #define REPLICATION_PRIORITY_HIGH_SECS (60*30) #define REPLICATION_PRIORITY_SYNC_SECS (60*45) /* When priority != none, user needs to be replicated ASAP. The question is just whether the queue is already busy and other users need to be synced even more faster. */ if (user->last_fast_sync == 0) { /* User has never been synced yet. These will be replicated first. Still, try to replicate higher priority users faster than lower priority users. */ if (user->priority != REPLICATION_PRIORITY_NONE) return REPLICATION_PRIORITY_SYNC - user->priority; } switch (user->priority) { case REPLICATION_PRIORITY_NONE: break; case REPLICATION_PRIORITY_LOW: i_assert(user->last_update >= REPLICATION_PRIORITY_LOW_SECS); return user->last_update - REPLICATION_PRIORITY_LOW_SECS; case REPLICATION_PRIORITY_HIGH: i_assert(user->last_update >= REPLICATION_PRIORITY_HIGH_SECS); return user->last_update - REPLICATION_PRIORITY_HIGH_SECS; case REPLICATION_PRIORITY_SYNC: i_assert(user->last_update >= REPLICATION_PRIORITY_HIGH_SECS); return user->last_update - REPLICATION_PRIORITY_SYNC_SECS; } if (user->last_sync_failed) { /* failures need to be retried at specific intervals */ return user->last_fast_sync + replicator_failure_resync_interval; } /* full resyncs should be done at configured intervals */ return user->last_full_sync + replicator_full_sync_interval; } static int user_priority_cmp(const void *p1, const void *p2) { const struct replicator_user *user1 = p1, *user2 = p2; time_t next_sync1 = replicator_user_next_sync_time(user1); time_t next_sync2 = replicator_user_next_sync_time(user2); if (next_sync1 < next_sync2) return -1; if (next_sync1 > next_sync2) return 1; return 0; } struct replicator_queue * replicator_queue_init(unsigned int full_sync_interval, unsigned int failure_resync_interval) { struct replicator_queue *queue; /* priorityq callback needs to access these */ i_assert(replicator_full_sync_interval == 0 || replicator_full_sync_interval == full_sync_interval); replicator_full_sync_interval = full_sync_interval; i_assert(replicator_failure_resync_interval == 0 || replicator_failure_resync_interval == failure_resync_interval); replicator_full_sync_interval = full_sync_interval; replicator_failure_resync_interval = failure_resync_interval; queue = i_new(struct replicator_queue, 1); queue->full_sync_interval = full_sync_interval; queue->failure_resync_interval = failure_resync_interval; queue->user_queue = priorityq_init(user_priority_cmp, 1024); hash_table_create(&queue->user_hash, default_pool, 1024, str_hash, strcmp); i_array_init(&queue->sync_lookups, 32); return queue; } void replicator_queue_deinit(struct replicator_queue **_queue) { struct replicator_queue *queue = *_queue; struct priorityq_item *item; *_queue = NULL; queue->change_callback = NULL; while ((item = priorityq_pop(queue->user_queue)) != NULL) { struct replicator_user *user = (struct replicator_user *)item; user->popped = TRUE; replicator_queue_remove(queue, &user); } priorityq_deinit(&queue->user_queue); hash_table_destroy(&queue->user_hash); i_assert(array_count(&queue->sync_lookups) == 0); array_free(&queue->sync_lookups); i_free(queue); } void replicator_queue_set_change_callback(struct replicator_queue *queue, void (*callback)(void *context), void *context) { queue->change_callback = callback; queue->change_context = context; } void replicator_user_ref(struct replicator_user *user) { i_assert(user->refcount > 0); user->refcount++; } bool replicator_user_unref(struct replicator_user **_user) { struct replicator_user *user = *_user; i_assert(user->refcount > 0); *_user = NULL; if (--user->refcount > 0) return TRUE; i_free(user->state); i_free(user->username); i_free(user); return FALSE; } struct replicator_user * replicator_queue_lookup(struct replicator_queue *queue, const char *username) { return hash_table_lookup(queue->user_hash, username); } struct replicator_user * replicator_queue_get(struct replicator_queue *queue, const char *username) { struct replicator_user *user; user = replicator_queue_lookup(queue, username); if (user == NULL) { user = i_new(struct replicator_user, 1); user->refcount = 1; user->username = i_strdup(username); user->last_update = ioloop_time; hash_table_insert(queue->user_hash, user->username, user); if (!user->popped) priorityq_add(queue->user_queue, &user->item); } return user; } void replicator_queue_update(struct replicator_queue *queue ATTR_UNUSED, struct replicator_user *user, enum replication_priority priority) { if (user->priority >= priority) { /* user already has at least this high priority */ return; } user->priority = priority; user->last_update = ioloop_time; } void replicator_queue_add(struct replicator_queue *queue, struct replicator_user *user) { if (!user->popped) { priorityq_remove(queue->user_queue, &user->item); priorityq_add(queue->user_queue, &user->item); } if (queue->change_callback != NULL) queue->change_callback(queue->change_context); } void replicator_queue_add_sync_callback(struct replicator_queue *queue, struct replicator_user *user, replicator_sync_callback_t *callback, void *context) { struct replicator_sync_lookup *lookup; i_assert(user->priority == REPLICATION_PRIORITY_SYNC); lookup = array_append_space(&queue->sync_lookups); lookup->user = user; lookup->callback = callback; lookup->context = context; lookup->wait_for_next_push = user->popped; replicator_queue_add(queue, user); } void replicator_queue_remove(struct replicator_queue *queue, struct replicator_user **_user) { struct replicator_user *user = *_user; *_user = NULL; if (!user->popped) priorityq_remove(queue->user_queue, &user->item); hash_table_remove(queue->user_hash, user->username); replicator_user_unref(&user); if (queue->change_callback != NULL) queue->change_callback(queue->change_context); } unsigned int replicator_queue_count(struct replicator_queue *queue) { return priorityq_count(queue->user_queue); } bool replicator_queue_want_sync_now(struct replicator_user *user, unsigned int *next_secs_r) { time_t next_sync = replicator_user_next_sync_time(user); if (next_sync <= ioloop_time) { *next_secs_r = 0; return TRUE; } *next_secs_r = next_sync - ioloop_time; return FALSE; } struct replicator_user * replicator_queue_peek(struct replicator_queue *queue, unsigned int *next_secs_r) { struct priorityq_item *item; struct replicator_user *user; item = priorityq_peek(queue->user_queue); if (item == NULL) { /* no users defined. we shouldn't normally get here */ *next_secs_r = 3600; return NULL; } user = (struct replicator_user *)item; (void)replicator_queue_want_sync_now(user, next_secs_r); return user; } struct replicator_user * replicator_queue_pop(struct replicator_queue *queue, unsigned int *next_secs_r) { struct replicator_user *user; user = replicator_queue_peek(queue, next_secs_r); if (*next_secs_r > 0) { /* we don't want to sync the user yet */ return NULL; } if (user != NULL) { priorityq_remove(queue->user_queue, &user->item); user->popped = TRUE; } return user; } static void replicator_queue_handle_sync_lookups(struct replicator_queue *queue, struct replicator_user *user) { struct replicator_sync_lookup *lookups; ARRAY(struct replicator_sync_lookup) callbacks; unsigned int i, count; bool success = !user->last_sync_failed; t_array_init(&callbacks, 8); lookups = array_get_modifiable(&queue->sync_lookups, &count); for (i = 0; i < count; ) { if (lookups[i].user != user) i++; else if (lookups[i].wait_for_next_push) { /* another sync request came while user was being replicated */ i_assert(user->priority == REPLICATION_PRIORITY_SYNC); lookups[i].wait_for_next_push = FALSE; i++; } else { array_push_back(&callbacks, &lookups[i]); array_delete(&queue->sync_lookups, i, 1); } } array_foreach_modifiable(&callbacks, lookups) lookups->callback(success, lookups->context); } void replicator_queue_push(struct replicator_queue *queue, struct replicator_user *user) { i_assert(user->popped); priorityq_add(queue->user_queue, &user->item); user->popped = FALSE; T_BEGIN { replicator_queue_handle_sync_lookups(queue, user); } T_END; } static int replicator_queue_import_line(struct replicator_queue *queue, const char *line) { const char *const *args, *username, *state; unsigned int priority; struct replicator_user *user, tmp_user; /* */ args = t_strsplit_tabescaped(line); if (str_array_length(args) < 7) return -1; i_zero(&tmp_user); username = args[0]; state = t_strdup_noconst(args[6]); if (username[0] == '\0' || str_to_uint(args[1], &priority) < 0 || str_to_time(args[2], &tmp_user.last_update) < 0 || str_to_time(args[3], &tmp_user.last_fast_sync) < 0 || str_to_time(args[4], &tmp_user.last_full_sync) < 0) return -1; tmp_user.priority = priority; tmp_user.last_sync_failed = args[5][0] != '0'; if (str_array_length(args) >= 8) { if (str_to_time(args[7], &tmp_user.last_successful_sync) < 0) return -1; } else { tmp_user.last_successful_sync = 0; /* On-disk format didn't have this yet */ } user = hash_table_lookup(queue->user_hash, username); if (user != NULL) { if (user->last_update > tmp_user.last_update) { /* we already have a newer state */ return 0; } if (user->last_update == tmp_user.last_update) { /* either one of these could be newer. use the one with higher priority. */ if (user->priority > tmp_user.priority) return 0; } } else { user = replicator_queue_get(queue, username); } user->priority = tmp_user.priority; user->last_update = tmp_user.last_update; user->last_fast_sync = tmp_user.last_fast_sync; user->last_full_sync = tmp_user.last_full_sync; user->last_successful_sync = tmp_user.last_successful_sync; user->last_sync_failed = tmp_user.last_sync_failed; i_free(user->state); user->state = i_strdup(state); replicator_queue_add(queue, user); return 0; } int replicator_queue_import(struct replicator_queue *queue, const char *path) { struct istream *input; const char *line; int fd, ret = 0; fd = open(path, O_RDONLY); if (fd == -1) { if (errno == ENOENT) return 0; i_error("open(%s) failed: %m", path); return -1; } input = i_stream_create_fd_autoclose(&fd, SIZE_MAX); while ((line = i_stream_read_next_line(input)) != NULL) { T_BEGIN { ret = replicator_queue_import_line(queue, line); } T_END; if (ret < 0) { i_error("Corrupted replicator record in %s: %s", path, line); break; } } if (input->stream_errno != 0) { i_error("read(%s) failed: %s", path, i_stream_get_error(input)); ret = -1; } i_stream_destroy(&input); return ret; } static void replicator_queue_export_user(struct replicator_user *user, string_t *str) { str_append_tabescaped(str, user->username); str_printfa(str, "\t%d\t%lld\t%lld\t%lld\t%d\t", (int)user->priority, (long long)user->last_update, (long long)user->last_fast_sync, (long long)user->last_full_sync, user->last_sync_failed ? 1 : 0); if (user->state != NULL) str_append_tabescaped(str, user->state); str_printfa(str, "\t%lld\n", (long long)user->last_successful_sync); } int replicator_queue_export(struct replicator_queue *queue, const char *path) { struct replicator_queue_iter *iter; struct replicator_user *user; struct ostream *output; string_t *str; int fd, ret = 0; fd = creat(path, 0600); if (fd == -1) { i_error("creat(%s) failed: %m", path); return -1; } output = o_stream_create_fd_file_autoclose(&fd, 0); o_stream_cork(output); str = t_str_new(128); iter = replicator_queue_iter_init(queue); while ((user = replicator_queue_iter_next(iter)) != NULL) { str_truncate(str, 0); replicator_queue_export_user(user, str); if (o_stream_send(output, str_data(str), str_len(str)) < 0) break; } replicator_queue_iter_deinit(&iter); if (o_stream_finish(output) < 0) { i_error("write(%s) failed: %s", path, o_stream_get_error(output)); ret = -1; } o_stream_destroy(&output); return ret; } struct replicator_queue_iter * replicator_queue_iter_init(struct replicator_queue *queue) { struct replicator_queue_iter *iter; iter = i_new(struct replicator_queue_iter, 1); iter->queue = queue; iter->iter = hash_table_iterate_init(queue->user_hash); return iter; } struct replicator_user * replicator_queue_iter_next(struct replicator_queue_iter *iter) { struct replicator_user *user; char *username; if (!hash_table_iterate(iter->iter, iter->queue->user_hash, &username, &user)) return NULL; return user; } void replicator_queue_iter_deinit(struct replicator_queue_iter **_iter) { struct replicator_queue_iter *iter = *_iter; *_iter = NULL; hash_table_iterate_deinit(&iter->iter); i_free(iter); } dovecot-2.3.21.1/src/replication/replicator/replicator-brain.c0000644000000000000000000001247014656633576021166 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "ioloop.h" #include "dsync-client.h" #include "replicator-settings.h" #include "replicator-queue.h" #include "replicator-brain.h" struct replicator_sync_context { struct replicator_brain *brain; struct replicator_user *user; }; struct replicator_brain { pool_t pool; struct replicator_queue *queue; const struct replicator_settings *set; struct timeout *to; ARRAY_TYPE(dsync_client) dsync_clients; bool deinitializing:1; }; static void replicator_brain_fill(struct replicator_brain *brain); static void replicator_brain_timeout(struct replicator_brain *brain) { timeout_remove(&brain->to); replicator_brain_fill(brain); } static void replicator_brain_queue_changed(void *context) { struct replicator_brain *brain = context; /* Delay a bit filling the replication. We could have gotten here before the replicator_user change was fully filled out. */ timeout_remove(&brain->to); brain->to = timeout_add_short(0, replicator_brain_timeout, brain); } struct replicator_brain * replicator_brain_init(struct replicator_queue *queue, const struct replicator_settings *set) { struct replicator_brain *brain; pool_t pool; pool = pool_alloconly_create("replication brain", 1024); brain = p_new(pool, struct replicator_brain, 1); brain->pool = pool; brain->queue = queue; brain->set = set; p_array_init(&brain->dsync_clients, pool, 16); replicator_queue_set_change_callback(queue, replicator_brain_queue_changed, brain); replicator_brain_fill(brain); return brain; } void replicator_brain_deinit(struct replicator_brain **_brain) { struct replicator_brain *brain = *_brain; struct dsync_client *conn; *_brain = NULL; brain->deinitializing = TRUE; array_foreach_elem(&brain->dsync_clients, conn) dsync_client_deinit(&conn); timeout_remove(&brain->to); pool_unref(&brain->pool); } struct replicator_queue * replicator_brain_get_queue(struct replicator_brain *brain) { return brain->queue; } const struct replicator_settings * replicator_brain_get_settings(struct replicator_brain *brain) { return brain->set; } const ARRAY_TYPE(dsync_client) * replicator_brain_get_dsync_clients(struct replicator_brain *brain) { return &brain->dsync_clients; } static struct dsync_client * get_dsync_client(struct replicator_brain *brain) { struct dsync_client *conn; array_foreach_elem(&brain->dsync_clients, conn) { if (!dsync_client_is_busy(conn)) return conn; } if (array_count(&brain->dsync_clients) == brain->set->replication_max_conns) return NULL; conn = dsync_client_init(brain->set->doveadm_socket_path, brain->set->replication_dsync_parameters); array_push_back(&brain->dsync_clients, &conn); return conn; } static void dsync_callback(enum dsync_reply reply, const char *state, void *context) { struct replicator_sync_context *ctx = context; struct replicator_user *user = ctx->user; if (!replicator_user_unref(&user)) { /* user was already removed */ } else if (reply == DSYNC_REPLY_NOUSER || reply == DSYNC_REPLY_NOREPLICATE) { /* user no longer exists, or is not wanted for replication, remove from replication */ replicator_queue_remove(ctx->brain->queue, &ctx->user); } else { i_free(ctx->user->state); ctx->user->state = i_strdup_empty(state); ctx->user->last_sync_failed = reply != DSYNC_REPLY_OK; if (reply == DSYNC_REPLY_OK) ctx->user->last_successful_sync = ioloop_time; replicator_queue_push(ctx->brain->queue, ctx->user); } if (!ctx->brain->deinitializing) replicator_brain_fill(ctx->brain); i_free(ctx); } static bool dsync_replicate(struct replicator_brain *brain, struct replicator_user *user) { struct replicator_sync_context *ctx; struct dsync_client *conn; time_t next_full_sync; bool full; conn = get_dsync_client(brain); if (conn == NULL) return FALSE; next_full_sync = user->last_full_sync + brain->set->replication_full_sync_interval; full = next_full_sync <= ioloop_time; /* update the sync times immediately. if the replication fails we still wouldn't want it to be retried immediately. */ user->last_fast_sync = ioloop_time; if (full || user->force_full_sync) { user->last_full_sync = ioloop_time; user->force_full_sync = FALSE; } /* reset priority also. if more updates arrive during replication we'll do another replication to make sure nothing gets lost */ user->priority = REPLICATION_PRIORITY_NONE; ctx = i_new(struct replicator_sync_context, 1); ctx->brain = brain; ctx->user = user; replicator_user_ref(user); dsync_client_sync(conn, user->username, user->state, full, dsync_callback, ctx); return TRUE; } static bool replicator_brain_fill_next(struct replicator_brain *brain) { struct replicator_user *user; unsigned int next_secs; user = replicator_queue_pop(brain->queue, &next_secs); if (user == NULL) { /* nothing more to do */ timeout_remove(&brain->to); brain->to = timeout_add(next_secs * 1000, replicator_brain_timeout, brain); return FALSE; } if (!dsync_replicate(brain, user)) { /* all connections were full, put the user back to queue */ replicator_queue_push(brain->queue, user); return FALSE; } /* replication started for the user */ return TRUE; } static void replicator_brain_fill(struct replicator_brain *brain) { while (replicator_brain_fill_next(brain)) ; } dovecot-2.3.21.1/src/replication/replicator/Makefile.am0000644000000000000000000000242414656633576017617 00000000000000pkglibexecdir = $(libexecdir)/dovecot pkglibexec_PROGRAMS = replicator AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/replication \ -DPKG_STATEDIR=\""$(statedir)"\" \ $(BINARY_CFLAGS) replicator_LDFLAGS = -export-dynamic \ $(BINARY_LDFLAGS) replicator_LDADD = $(LIBDOVECOT) replicator_DEPENDENCIES = $(LIBDOVECOT_DEPS) replicator_SOURCES = \ doveadm-connection.c \ dsync-client.c \ replicator.c \ replicator-brain.c \ replicator-queue.c \ replicator-queue-auth.c \ replicator-settings.c \ notify-connection.c noinst_HEADERS = \ doveadm-connection.h \ dsync-client.h \ replicator-brain.h \ replicator-queue.h \ replicator-settings.h \ notify-connection.h test_programs = \ test-replicator-queue noinst_PROGRAMS = $(test_programs) test_libs = \ ../../lib-test/libtest.la \ ../../lib/liblib.la test_deps = $(test_libs) test_replicator_queue_SOURCES = \ replicator-queue.c \ replicator-settings.c \ test-replicator-queue.c test_replicator_queue_LDADD = $(test_libs) test_replicator_queue_DEPENDENCIES = $(test_deps) check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done dovecot-2.3.21.1/src/replication/replicator/replicator-queue.h0000644000000000000000000001024614656633576021223 00000000000000#ifndef REPLICATOR_QUEUE_H #define REPLICATOR_QUEUE_H #include "priorityq.h" #include "replication-common.h" struct replicator_user { struct priorityq_item item; char *username; /* dsync state for incremental syncing */ char *state; /* last time this user's state was updated */ time_t last_update; /* last_fast_sync is always >= last_full_sync. */ time_t last_fast_sync, last_full_sync, last_successful_sync; int refcount; enum replication_priority priority; /* User isn't currently in replication queue */ bool popped:1; /* Last replication sync failed */ bool last_sync_failed:1; /* Force a full sync on the next replication */ bool force_full_sync:1; }; typedef void replicator_sync_callback_t(bool success, void *context); struct replicator_queue * replicator_queue_init(unsigned int full_sync_interval, unsigned int failure_resync_interval); void replicator_queue_deinit(struct replicator_queue **queue); /* Call the specified callback when data is added/removed/moved in queue via _add(), _add_sync() or _remove() functions (not push/pop). */ void replicator_queue_set_change_callback(struct replicator_queue *queue, void (*callback)(void *context), void *context); /* Reference the user */ void replicator_user_ref(struct replicator_user *user); /* Unreference the user. Returns TRUE if refcount is still >0. */ bool replicator_user_unref(struct replicator_user **user); /* Lookup an existing user */ struct replicator_user * replicator_queue_lookup(struct replicator_queue *queue, const char *username); /* Lookup or create a user and return it. Afterwards replicator_queue_add() must be called to add/move the user to the proper place in the queue. */ struct replicator_user * replicator_queue_get(struct replicator_queue *queue, const char *username); /* Update user's priority if it's currently lower. */ void replicator_queue_update(struct replicator_queue *queue, struct replicator_user *user, enum replication_priority priority); void replicator_queue_add(struct replicator_queue *queue, struct replicator_user *user); /* Call the callback when user with SYNC priority has finished syncing. */ void replicator_queue_add_sync_callback(struct replicator_queue *queue, struct replicator_user *user, replicator_sync_callback_t *callback, void *context); /* Remove user from replication queue and free it. */ void replicator_queue_remove(struct replicator_queue *queue, struct replicator_user **user); /* Return the number of users in the queue. */ unsigned int replicator_queue_count(struct replicator_queue *queue); /* Return the next user from replication queue and how many seconds from now the returned user should be synced (0 = immediately). Returns NULL only if there are no users in the queue. */ struct replicator_user * replicator_queue_peek(struct replicator_queue *queue, unsigned int *next_secs_r); /* Return the next user from replication queue, and remove it from the queue. If there's nothing to be replicated currently, returns NULL and sets next_secs_r to when there should be more work to do. */ struct replicator_user * replicator_queue_pop(struct replicator_queue *queue, unsigned int *next_secs_r); /* Add user back to queue. */ void replicator_queue_push(struct replicator_queue *queue, struct replicator_user *user); int replicator_queue_import(struct replicator_queue *queue, const char *path); int replicator_queue_export(struct replicator_queue *queue, const char *path); /* Returns TRUE if user replication can be started now, FALSE if not. When returning FALSE, next_secs_r is set to user's next replication time. */ bool replicator_queue_want_sync_now(struct replicator_user *user, unsigned int *next_secs_r); /* Iterate through all users in the queue. */ struct replicator_queue_iter * replicator_queue_iter_init(struct replicator_queue *queue); struct replicator_user * replicator_queue_iter_next(struct replicator_queue_iter *iter); void replicator_queue_iter_deinit(struct replicator_queue_iter **iter); void replicator_queue_add_auth_users(struct replicator_queue *queue, const char *auth_socket_path, const char *usermask, time_t last_update); #endif dovecot-2.3.21.1/src/replication/replicator/doveadm-connection.c0000644000000000000000000002377214656633576021514 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "connection.h" #include "ostream.h" #include "str.h" #include "strescape.h" #include "wildcard-match.h" #include "master-service.h" #include "replicator-brain.h" #include "replicator-queue.h" #include "replicator-settings.h" #include "dsync-client.h" #include "doveadm-connection.h" #include #define REPLICATOR_DOVEADM_MAJOR_VERSION 1 #define REPLICATOR_DOVEADM_MINOR_VERSION 0 struct doveadm_connection { struct connection conn; struct replicator_brain *brain; }; static struct connection_list *doveadm_connections; static int client_input_status_overview(struct doveadm_connection *client) { struct replicator_queue *queue = replicator_brain_get_queue(client->brain); struct replicator_queue_iter *iter; struct replicator_user *user; enum replication_priority priority; unsigned int pending_counts[REPLICATION_PRIORITY_SYNC+1]; unsigned int user_count, next_secs, pending_failed_count; unsigned int pending_full_resync_count, waiting_failed_count; string_t *str = t_str_new(256); memset(pending_counts, 0, sizeof(pending_counts)); pending_failed_count = 0; waiting_failed_count = 0; pending_full_resync_count = 0; user_count = 0; iter = replicator_queue_iter_init(queue); while ((user = replicator_queue_iter_next(iter)) != NULL) { if (user->priority != REPLICATION_PRIORITY_NONE) pending_counts[user->priority]++; else if (replicator_queue_want_sync_now(user, &next_secs)) { if (user->last_sync_failed) pending_failed_count++; else pending_full_resync_count++; } else { if (user->last_sync_failed) waiting_failed_count++; } user_count++; } replicator_queue_iter_deinit(&iter); for (priority = REPLICATION_PRIORITY_SYNC; priority > 0; priority--) { str_printfa(str, "Queued '%s' requests\t%u\n", replicator_priority_to_str(priority), pending_counts[priority]); } str_printfa(str, "Queued 'failed' requests\t%u\n", pending_failed_count); str_printfa(str, "Queued 'full resync' requests\t%u\n", pending_full_resync_count); str_printfa(str, "Waiting 'failed' requests\t%u\n", waiting_failed_count); str_printfa(str, "Total number of known users\t%u\n", user_count); str_append_c(str, '\n'); o_stream_nsend(client->conn.output, str_data(str), str_len(str)); return 0; } static int client_input_status(struct doveadm_connection *client, const char *const *args) { struct replicator_queue *queue = replicator_brain_get_queue(client->brain); struct replicator_queue_iter *iter; struct replicator_user *user; const char *mask = args[0]; unsigned int next_secs; string_t *str = t_str_new(128); if (mask == NULL) return client_input_status_overview(client); iter = replicator_queue_iter_init(queue); while ((user = replicator_queue_iter_next(iter)) != NULL) { if (!wildcard_match(user->username, mask)) continue; str_truncate(str, 0); str_append_tabescaped(str, user->username); str_append_c(str, '\t'); str_append(str, replicator_priority_to_str(user->priority)); if (replicator_queue_want_sync_now(user, &next_secs)) next_secs = 0; str_printfa(str, "\t%lld\t%lld\t%d\t%lld\t%u\n", (long long)user->last_fast_sync, (long long)user->last_full_sync, user->last_sync_failed ? 1 : 0, (long long)user->last_successful_sync, next_secs); o_stream_nsend(client->conn.output, str_data(str), str_len(str)); } replicator_queue_iter_deinit(&iter); o_stream_nsend(client->conn.output, "\n", 1); return 0; } static int client_input_status_dsyncs(struct doveadm_connection *client) { string_t *str = t_str_new(256); const ARRAY_TYPE(dsync_client) *clients; struct dsync_client *dsync_client; const char *username; clients = replicator_brain_get_dsync_clients(client->brain); array_foreach_elem(clients, dsync_client) { username = dsync_client_get_username(dsync_client); if (username != NULL) { str_append_tabescaped(str, username); str_append_c(str, '\t'); switch (dsync_client_get_type(dsync_client)) { case DSYNC_TYPE_FULL: str_append(str, "full"); break; case DSYNC_TYPE_NORMAL: str_append(str, "normal"); break; case DSYNC_TYPE_INCREMENTAL: str_append(str, "incremental"); break; } } else { str_append(str, "\t-"); } str_append_c(str, '\t'); str_append_tabescaped(str, dsync_client_get_state(dsync_client)); str_append_c(str, '\n'); } str_append_c(str, '\n'); o_stream_nsend(client->conn.output, str_data(str), str_len(str)); return 0; } static int client_input_replicate(struct doveadm_connection *client, const char *const *args) { struct replicator_queue *queue = replicator_brain_get_queue(client->brain); struct replicator_queue_iter *iter; struct replicator_user *user; const char *usermask; enum replication_priority priority; unsigned int match_count; bool full; /* | */ if (str_array_length(args) != 3) { i_error("%s: REPLICATE: Invalid parameters", client->conn.name); return -1; } if (replication_priority_parse(args[0], &priority) < 0) { o_stream_nsend_str(client->conn.output, "-Invalid priority\n"); return 0; } full = strchr(args[1], 'f') != NULL; usermask = args[2]; if (strchr(usermask, '*') == NULL && strchr(usermask, '?') == NULL) { struct replicator_user *user = replicator_queue_get(queue, usermask); if (full) user->force_full_sync = TRUE; replicator_queue_update(queue, user, priority); replicator_queue_add(queue, user); o_stream_nsend_str(client->conn.output, "+1\n"); return 0; } match_count = 0; iter = replicator_queue_iter_init(queue); while ((user = replicator_queue_iter_next(iter)) != NULL) { if (!wildcard_match(user->username, usermask)) continue; if (full) user->force_full_sync = TRUE; replicator_queue_update(queue, user, priority); replicator_queue_add(queue, user); match_count++; } replicator_queue_iter_deinit(&iter); o_stream_nsend_str(client->conn.output, t_strdup_printf("+%u\n", match_count)); return 0; } static int client_input_add(struct doveadm_connection *client, const char *const *args) { struct replicator_queue *queue = replicator_brain_get_queue(client->brain); const struct replicator_settings *set = replicator_brain_get_settings(client->brain); /* */ if (str_array_length(args) != 1) { i_error("%s: ADD: Invalid parameters", client->conn.name); return -1; } if (strchr(args[0], '*') == NULL && strchr(args[0], '?') == NULL) { struct replicator_user *user = replicator_queue_get(queue, args[0]); replicator_queue_add(queue, user); } else { replicator_queue_add_auth_users(queue, set->auth_socket_path, args[0], ioloop_time); } o_stream_nsend_str(client->conn.output, "+\n"); return 0; } static int client_input_remove(struct doveadm_connection *client, const char *const *args) { struct replicator_queue *queue = replicator_brain_get_queue(client->brain); struct replicator_user *user; /* */ if (str_array_length(args) != 1) { i_error("%s: REMOVE: Invalid parameters", client->conn.name); return -1; } user = replicator_queue_lookup(queue, args[0]); if (user == NULL) o_stream_nsend_str(client->conn.output, "-User not found\n"); else { replicator_queue_remove(queue, &user); o_stream_nsend_str(client->conn.output, "+\n"); } return 0; } static int client_input_notify(struct doveadm_connection *client, const char *const *args) { struct replicator_queue *queue = replicator_brain_get_queue(client->brain); struct replicator_user *user; /* */ if (str_array_length(args) < 3) { i_error("%s: NOTIFY: Invalid parameters", client->conn.name); return -1; } user = replicator_queue_get(queue, args[0]); if (args[1][0] == 'f') user->last_full_sync = ioloop_time; user->last_fast_sync = ioloop_time; user->last_update = ioloop_time; replicator_queue_add(queue, user); if (args[2][0] != '\0') { i_free(user->state); user->state = i_strdup(args[2]); } o_stream_nsend_str(client->conn.output, "+\n"); return 0; } static int client_input_args(struct connection *conn, const char *const *args) { struct doveadm_connection *client = (struct doveadm_connection *)conn; const char *cmd = args[0]; if (cmd == NULL) { i_error("%s: Empty command", conn->name); return 0; } args++; if (strcmp(cmd, "STATUS") == 0) return client_input_status(client, args); else if (strcmp(cmd, "STATUS-DSYNC") == 0) return client_input_status_dsyncs(client); else if (strcmp(cmd, "REPLICATE") == 0) return client_input_replicate(client, args); else if (strcmp(cmd, "ADD") == 0) return client_input_add(client, args); else if (strcmp(cmd, "REMOVE") == 0) return client_input_remove(client, args); else if (strcmp(cmd, "NOTIFY") == 0) return client_input_notify(client, args); i_error("%s: Unknown command: %s", conn->name, cmd); return -1; } static void client_destroy(struct connection *conn) { struct doveadm_connection *client = (struct doveadm_connection *)conn; connection_deinit(&client->conn); i_free(client); master_service_client_connection_destroyed(master_service); } void doveadm_connection_create(struct replicator_brain *brain, int fd) { struct doveadm_connection *client; client = i_new(struct doveadm_connection, 1); client->brain = brain; connection_init_server(doveadm_connections, &client->conn, "doveadm-client", fd, fd); } static struct connection_settings doveadm_conn_set = { .service_name_in = "replicator-doveadm-client", .service_name_out = "replicator-doveadm-server", .major_version = REPLICATOR_DOVEADM_MAJOR_VERSION, .minor_version = REPLICATOR_DOVEADM_MINOR_VERSION, .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, .client = FALSE }; static const struct connection_vfuncs doveadm_conn_vfuncs = { .destroy = client_destroy, .input_args = client_input_args }; void doveadm_connections_init(void) { doveadm_connections = connection_list_init(&doveadm_conn_set, &doveadm_conn_vfuncs); } void doveadm_connections_deinit(void) { connection_list_deinit(&doveadm_connections); } dovecot-2.3.21.1/src/replication/Makefile.am0000644000000000000000000000011214656633576015443 00000000000000SUBDIRS = aggregator replicator noinst_HEADERS = \ replication-common.h dovecot-2.3.21.1/src/replication/replication-common.h0000644000000000000000000000220714656633576017366 00000000000000#ifndef REPLICATION_COMMON_H #define REPLICATION_COMMON_H enum replication_priority { /* user is fully replicated, as far as we know */ REPLICATION_PRIORITY_NONE = 0, /* flag changes, expunges, etc. */ REPLICATION_PRIORITY_LOW, /* new emails */ REPLICATION_PRIORITY_HIGH, /* synchronously wait for new emails to be replicated */ REPLICATION_PRIORITY_SYNC }; static inline const char * replicator_priority_to_str(enum replication_priority priority) { switch (priority) { case REPLICATION_PRIORITY_NONE: return "none"; case REPLICATION_PRIORITY_LOW: return "low"; case REPLICATION_PRIORITY_HIGH: return "high"; case REPLICATION_PRIORITY_SYNC: return "sync"; } i_unreached(); } static inline int replication_priority_parse(const char *str, enum replication_priority *priority_r) { if (strcmp(str, "none") == 0) *priority_r = REPLICATION_PRIORITY_NONE; else if (strcmp(str, "low") == 0) *priority_r = REPLICATION_PRIORITY_LOW; else if (strcmp(str, "high") == 0) *priority_r = REPLICATION_PRIORITY_HIGH; else if (strcmp(str, "sync") == 0) *priority_r = REPLICATION_PRIORITY_SYNC; else return -1; return 0; } #endif dovecot-2.3.21.1/src/indexer/0000755000000000000000000000000014656633640012612 500000000000000dovecot-2.3.21.1/src/indexer/indexer-worker.c0000644000000000000000000000451514656633576015660 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "restrict-access.h" #include "mail-storage-service.h" #include "mail-storage-settings.h" #include "master-service.h" #include "master-service-settings.h" #include "master-connection.h" static struct mail_storage_service_ctx *storage_service; static void client_connected(struct master_service_connection *conn) { master_service_client_connection_accept(conn); if (!master_connection_create(conn, storage_service)) { i_error("indexer-worker must be configured with client_limit=1"); i_close_fd(&conn->fd); master_service_client_connection_destroyed(master_service); } } static void drop_privileges(void) { struct restrict_access_settings set; const char *error; /* by default we don't drop any privileges, but keep running as root. */ restrict_access_get_env(&set); if (set.uid != 0) { /* open config connection before dropping privileges */ struct master_service_settings_input input; struct master_service_settings_output output; i_zero(&input); input.module = "mail"; input.service = "indexer-worker"; (void)master_service_settings_read(master_service, &input, &output, &error); } restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL); } int main(int argc, char *argv[]) { enum master_service_flags service_flags = MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN; enum mail_storage_service_flags storage_service_flags = MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP | MAIL_STORAGE_SERVICE_FLAG_TEMP_PRIV_DROP | MAIL_STORAGE_SERVICE_FLAG_NO_IDLE_TIMEOUT; int c; master_service = master_service_init("indexer-worker", service_flags, &argc, &argv, "D"); while ((c = master_getopt(master_service)) > 0) { switch (c) { case 'D': storage_service_flags |= MAIL_STORAGE_SERVICE_FLAG_ENABLE_CORE_DUMPS; break; default: return FATAL_DEFAULT; } } drop_privileges(); master_service_init_log_with_pid(master_service); storage_service = mail_storage_service_init(master_service, NULL, storage_service_flags); restrict_access_allow_coredumps(TRUE); master_service_init_finish(master_service); master_service_run(master_service, client_connected); master_connections_destroy(); mail_storage_service_deinit(&storage_service); master_service_deinit(&master_service); return 0; } dovecot-2.3.21.1/src/indexer/indexer-worker-settings.c0000644000000000000000000000234314656633576017513 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "settings-parser.h" #include "service-settings.h" #include /* */ static struct file_listener_settings indexer_worker_unix_listeners_array[] = { { "indexer-worker", 0600, "$default_internal_user", "" } }; static struct file_listener_settings *indexer_worker_unix_listeners[] = { &indexer_worker_unix_listeners_array[0] }; static buffer_t indexer_worker_unix_listeners_buf = { { { indexer_worker_unix_listeners, sizeof(indexer_worker_unix_listeners) } } }; /* */ struct service_settings indexer_worker_service_settings = { .name = "indexer-worker", .protocol = "", .type = "worker", .executable = "indexer-worker", .user = "", .group = "", .privileged_group = "", .extra_groups = "$default_internal_group", .chroot = "", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 10, .client_limit = 1, .service_count = 0, .idle_kill = 0, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &indexer_worker_unix_listeners_buf, sizeof(indexer_worker_unix_listeners[0]) } }, .fifo_listeners = ARRAY_INIT, .inet_listeners = ARRAY_INIT }; dovecot-2.3.21.1/src/indexer/indexer-queue.h0000644000000000000000000000501214656633576015471 00000000000000#ifndef INDEXER_QUEUE_H #define INDEXER_QUEUE_H #include "indexer.h" typedef void indexer_queue_callback_t(int status, void *context); struct indexer_request { struct indexer_request *prev, *next; char *username; char *mailbox; char *session_id; unsigned int max_recent_msgs; /* index messages in this mailbox */ bool index:1; /* optimize this mailbox */ bool optimize:1; /* currently indexing this mailbox */ bool working:1; /* after indexing is finished, add this request back to the queue and reindex it (i.e. a new indexing request came while we were working.) */ bool reindex_head:1; bool reindex_tail:1; /* when working finished, call this number of contexts and leave the rest to the reindexing. */ unsigned int working_context_idx; ARRAY(void *) contexts; }; struct indexer_queue *indexer_queue_init(indexer_queue_callback_t *callback); void indexer_queue_deinit(struct indexer_queue **queue); /* The callback is called whenever a new request is added to the queue. */ void indexer_queue_set_listen_callback(struct indexer_queue *queue, void (*callback)(struct indexer_queue *)); void indexer_queue_append(struct indexer_queue *queue, bool append, const char *username, const char *mailbox, const char *session_id, unsigned int max_recent_msgs, void *context); void indexer_queue_append_optimize(struct indexer_queue *queue, const char *username, const char *mailbox, void *context); void indexer_queue_cancel_all(struct indexer_queue *queue); bool indexer_queue_is_empty(struct indexer_queue *queue); unsigned int indexer_queue_count(struct indexer_queue *queue); /* Return the next request from the queue, without removing it. */ struct indexer_request *indexer_queue_request_peek(struct indexer_queue *queue); /* Remove the next request from the queue. You must call indexer_queue_request_finish() to free its memory. */ void indexer_queue_request_remove(struct indexer_queue *queue); /* Give a status update about how far the indexing is going on. */ void indexer_queue_request_status(struct indexer_queue *queue, struct indexer_request *request, int percentage); /* Move the next request to the end of the queue. */ void indexer_queue_move_head_to_tail(struct indexer_queue *queue); /* Start working on a request */ void indexer_queue_request_work(struct indexer_request *request); /* Finish the request and free its memory. */ void indexer_queue_request_finish(struct indexer_queue *queue, struct indexer_request **request, bool success); #endif dovecot-2.3.21.1/src/indexer/Makefile.in0000644000000000000000000006762314656633610014612 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ pkglibexec_PROGRAMS = indexer$(EXEEXT) indexer-worker$(EXEEXT) subdir = src/indexer ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(pkglibexecdir)" PROGRAMS = $(pkglibexec_PROGRAMS) am_indexer_OBJECTS = indexer.$(OBJEXT) indexer-client.$(OBJEXT) \ indexer-queue.$(OBJEXT) indexer-settings.$(OBJEXT) \ worker-connection.$(OBJEXT) worker-pool.$(OBJEXT) indexer_OBJECTS = $(am_indexer_OBJECTS) am__DEPENDENCIES_1 = AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = am_indexer_worker_OBJECTS = indexer-worker.$(OBJEXT) \ indexer-worker-settings.$(OBJEXT) master-connection.$(OBJEXT) indexer_worker_OBJECTS = $(am_indexer_worker_OBJECTS) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/indexer-client.Po \ ./$(DEPDIR)/indexer-queue.Po ./$(DEPDIR)/indexer-settings.Po \ ./$(DEPDIR)/indexer-worker-settings.Po \ ./$(DEPDIR)/indexer-worker.Po ./$(DEPDIR)/indexer.Po \ ./$(DEPDIR)/master-connection.Po \ ./$(DEPDIR)/worker-connection.Po ./$(DEPDIR)/worker-pool.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(indexer_SOURCES) $(indexer_worker_SOURCES) DIST_SOURCES = $(indexer_SOURCES) $(indexer_worker_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac HEADERS = $(noinst_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) pkglibexecdir = $(libexecdir)/dovecot ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-storage \ -DPKG_RUNDIR=\""$(rundir)"\" \ $(BINARY_CFLAGS) indexer_LDADD = $(LIBDOVECOT) \ $(BINARY_LDFLAGS) indexer_DEPENDENCIES = $(LIBDOVECOT_DEPS) indexer_SOURCES = \ indexer.c \ indexer-client.c \ indexer-queue.c \ indexer-settings.c \ worker-connection.c \ worker-pool.c indexer_worker_LDADD = \ $(LIBDOVECOT_STORAGE) \ $(LIBDOVECOT) \ $(BINARY_LDFLAGS) indexer_worker_DEPENDENCIES = \ $(LIBDOVECOT_STORAGE_DEPS) \ $(LIBDOVECOT_DEPS) indexer_worker_SOURCES = \ indexer-worker.c \ indexer-worker-settings.c \ master-connection.c noinst_HEADERS = \ indexer.h \ indexer-client.h \ indexer-queue.h \ master-connection.h \ worker-connection.h \ worker-pool.h all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/indexer/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/indexer/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files clean-pkglibexecPROGRAMS: @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list indexer$(EXEEXT): $(indexer_OBJECTS) $(indexer_DEPENDENCIES) $(EXTRA_indexer_DEPENDENCIES) @rm -f indexer$(EXEEXT) $(AM_V_CCLD)$(LINK) $(indexer_OBJECTS) $(indexer_LDADD) $(LIBS) indexer-worker$(EXEEXT): $(indexer_worker_OBJECTS) $(indexer_worker_DEPENDENCIES) $(EXTRA_indexer_worker_DEPENDENCIES) @rm -f indexer-worker$(EXEEXT) $(AM_V_CCLD)$(LINK) $(indexer_worker_OBJECTS) $(indexer_worker_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/indexer-client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/indexer-queue.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/indexer-settings.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/indexer-worker-settings.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/indexer-worker.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/indexer.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/master-connection.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/worker-connection.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/worker-pool.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am check: check-am all-am: Makefile $(PROGRAMS) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkglibexecdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-pkglibexecPROGRAMS \ mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/indexer-client.Po -rm -f ./$(DEPDIR)/indexer-queue.Po -rm -f ./$(DEPDIR)/indexer-settings.Po -rm -f ./$(DEPDIR)/indexer-worker-settings.Po -rm -f ./$(DEPDIR)/indexer-worker.Po -rm -f ./$(DEPDIR)/indexer.Po -rm -f ./$(DEPDIR)/master-connection.Po -rm -f ./$(DEPDIR)/worker-connection.Po -rm -f ./$(DEPDIR)/worker-pool.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-pkglibexecPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/indexer-client.Po -rm -f ./$(DEPDIR)/indexer-queue.Po -rm -f ./$(DEPDIR)/indexer-settings.Po -rm -f ./$(DEPDIR)/indexer-worker-settings.Po -rm -f ./$(DEPDIR)/indexer-worker.Po -rm -f ./$(DEPDIR)/indexer.Po -rm -f ./$(DEPDIR)/master-connection.Po -rm -f ./$(DEPDIR)/worker-connection.Po -rm -f ./$(DEPDIR)/worker-pool.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkglibexecPROGRAMS .MAKE: install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ clean-generic clean-libtool clean-pkglibexecPROGRAMS \ cscopelist-am ctags ctags-am distclean distclean-compile \ distclean-generic distclean-libtool distclean-tags distdir dvi \ dvi-am html html-am info info-am install install-am \ install-data install-data-am install-dvi install-dvi-am \ install-exec install-exec-am install-html install-html-am \ install-info install-info-am install-man install-pdf \ install-pdf-am install-pkglibexecPROGRAMS install-ps \ install-ps-am install-strip installcheck installcheck-am \ installdirs maintainer-clean maintainer-clean-generic \ mostlyclean mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ uninstall-am uninstall-pkglibexecPROGRAMS .PRECIOUS: Makefile # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/indexer/indexer-client.h0000644000000000000000000000052414656633576015626 00000000000000#ifndef INDEXER_CLIENT_H #define INDEXER_CLIENT_H struct indexer_queue; void indexer_client_create(struct master_service_connection *conn, struct indexer_queue *queue); void indexer_client_status_callback(int percentage, void *context); unsigned int indexer_clients_get_count(void); void indexer_clients_destroy_all(void); #endif dovecot-2.3.21.1/src/indexer/indexer.h0000644000000000000000000000041614656633576014352 00000000000000#ifndef INDEXER_H #define INDEXER_H struct indexer_request; /* percentage: -1 = failed, 0..99 = indexing in progress, 100 = done */ typedef void indexer_status_callback_t(int percentage, struct indexer_request *request); void indexer_refresh_proctitle(void); #endif dovecot-2.3.21.1/src/indexer/indexer-settings.c0000644000000000000000000000232214656633576016201 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "settings-parser.h" #include "service-settings.h" #include extern const struct setting_parser_info service_setting_parser_info; /* */ static struct file_listener_settings indexer_unix_listeners_array[] = { { "indexer", 0666, "", "" } }; static struct file_listener_settings *indexer_unix_listeners[] = { &indexer_unix_listeners_array[0] }; static buffer_t indexer_unix_listeners_buf = { { { indexer_unix_listeners, sizeof(indexer_unix_listeners) } } }; /* */ struct service_settings indexer_service_settings = { .name = "indexer", .protocol = "", .type = "", .executable = "indexer", .user = "$default_internal_user", .group = "", .privileged_group = "", .extra_groups = "", .chroot = "", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 1, .client_limit = 0, .service_count = 0, .idle_kill = 0, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &indexer_unix_listeners_buf, sizeof(indexer_unix_listeners[0]) } }, .fifo_listeners = ARRAY_INIT, .inet_listeners = ARRAY_INIT, .process_limit_1 = TRUE }; dovecot-2.3.21.1/src/indexer/indexer-queue.c0000644000000000000000000001657114656633576015500 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "llist.h" #include "hash.h" #include "indexer-queue.h" struct indexer_queue { indexer_queue_callback_t *callback; void (*listen_callback)(struct indexer_queue *); /* username+mailbox -> indexer_request */ HASH_TABLE(struct indexer_request *, struct indexer_request *) requests; struct indexer_request *head, *tail; }; static unsigned int indexer_request_hash(const struct indexer_request *request) { return str_hash(request->username) ^ str_hash(request->mailbox); } static int indexer_request_cmp(const struct indexer_request *r1, const struct indexer_request *r2) { return strcmp(r1->username, r2->username) == 0 && strcmp(r1->mailbox, r2->mailbox) == 0 ? 0 : 1; } struct indexer_queue * indexer_queue_init(indexer_queue_callback_t *callback) { struct indexer_queue *queue; queue = i_new(struct indexer_queue, 1); queue->callback = callback; hash_table_create(&queue->requests, default_pool, 0, indexer_request_hash, indexer_request_cmp); return queue; } void indexer_queue_deinit(struct indexer_queue **_queue) { struct indexer_queue *queue = *_queue; *_queue = NULL; i_assert(indexer_queue_is_empty(queue)); hash_table_destroy(&queue->requests); i_free(queue); } void indexer_queue_set_listen_callback(struct indexer_queue *queue, void (*callback)(struct indexer_queue *)) { queue->listen_callback = callback; } static struct indexer_request * indexer_queue_lookup(struct indexer_queue *queue, const char *username, const char *mailbox) { struct indexer_request lookup_request; lookup_request.username = (void *)username; lookup_request.mailbox = (void *)mailbox; return hash_table_lookup(queue->requests, &lookup_request); } static void request_add_context(struct indexer_request *request, void *context) { if (context == NULL) return; if (!array_is_created(&request->contexts)) i_array_init(&request->contexts, 2); array_push_back(&request->contexts, &context); } static struct indexer_request * indexer_queue_append_request(struct indexer_queue *queue, bool append, const char *username, const char *mailbox, const char *session_id, unsigned int max_recent_msgs, void *context) { struct indexer_request *request; request = indexer_queue_lookup(queue, username, mailbox); if (request == NULL) { request = i_new(struct indexer_request, 1); request->username = i_strdup(username); request->mailbox = i_strdup(mailbox); request->session_id = i_strdup(session_id); request->max_recent_msgs = max_recent_msgs; request_add_context(request, context); hash_table_insert(queue->requests, request, request); } else { if (request->max_recent_msgs > max_recent_msgs) request->max_recent_msgs = max_recent_msgs; request_add_context(request, context); if (request->working) { /* we're already indexing this mailbox. */ if (append) request->reindex_tail = TRUE; else request->reindex_head = TRUE; return request; } if (append) { /* keep the request in its old position */ return request; } /* move request to beginning of the queue */ DLLIST2_REMOVE(&queue->head, &queue->tail, request); } if (append) DLLIST2_APPEND(&queue->head, &queue->tail, request); else DLLIST2_PREPEND(&queue->head, &queue->tail, request); return request; } static void indexer_queue_append_finish(struct indexer_queue *queue) { if (queue->listen_callback != NULL) queue->listen_callback(queue); indexer_refresh_proctitle(); } void indexer_queue_append(struct indexer_queue *queue, bool append, const char *username, const char *mailbox, const char *session_id, unsigned int max_recent_msgs, void *context) { struct indexer_request *request; request = indexer_queue_append_request(queue, append, username, mailbox, session_id, max_recent_msgs, context); request->index = TRUE; indexer_queue_append_finish(queue); } void indexer_queue_append_optimize(struct indexer_queue *queue, const char *username, const char *mailbox, void *context) { struct indexer_request *request; request = indexer_queue_append_request(queue, TRUE, username, mailbox, NULL, 0, context); request->optimize = TRUE; indexer_queue_append_finish(queue); } struct indexer_request *indexer_queue_request_peek(struct indexer_queue *queue) { return queue->head; } void indexer_queue_request_remove(struct indexer_queue *queue) { struct indexer_request *request = queue->head; i_assert(request != NULL); DLLIST2_REMOVE(&queue->head, &queue->tail, request); } static void indexer_queue_request_status_int(struct indexer_queue *queue, struct indexer_request *request, int percentage) { void *context; unsigned int i; for (i = 0; i < request->working_context_idx; i++) { context = array_idx_elem(&request->contexts, i); queue->callback(percentage, context); } } void indexer_queue_request_status(struct indexer_queue *queue, struct indexer_request *request, int percentage) { i_assert(percentage >= 0 && percentage < 100); indexer_queue_request_status_int(queue, request, percentage); } void indexer_queue_move_head_to_tail(struct indexer_queue *queue) { struct indexer_request *request = queue->head; indexer_queue_request_remove(queue); DLLIST2_APPEND(&queue->head, &queue->tail, request); } void indexer_queue_request_work(struct indexer_request *request) { request->working = TRUE; request->working_context_idx = !array_is_created(&request->contexts) ? 0 : array_count(&request->contexts); } void indexer_queue_request_finish(struct indexer_queue *queue, struct indexer_request **_request, bool success) { struct indexer_request *request = *_request; *_request = NULL; indexer_queue_request_status_int(queue, request, success ? 100 : -1); if (request->reindex_head || request->reindex_tail) { i_assert(request->working); request->working = FALSE; request->reindex_head = FALSE; request->reindex_tail = FALSE; if (request->working_context_idx > 0) { array_delete(&request->contexts, 0, request->working_context_idx); } if (request->reindex_head) DLLIST2_PREPEND(&queue->head, &queue->tail, request); else DLLIST2_APPEND(&queue->head, &queue->tail, request); return; } hash_table_remove(queue->requests, request); if (array_is_created(&request->contexts)) array_free(&request->contexts); i_free(request->username); i_free(request->mailbox); i_free(request->session_id); i_free(request); indexer_refresh_proctitle(); } void indexer_queue_cancel_all(struct indexer_queue *queue) { struct indexer_request *request; struct hash_iterate_context *iter; /* remove all reindex-markers so when the current requests finish (or are cancelled) we don't try to retry them (especially during deinit where it crashes) */ iter = hash_table_iterate_init(queue->requests); while (hash_table_iterate(iter, queue->requests, &request, &request)) request->reindex_head = request->reindex_tail = FALSE; hash_table_iterate_deinit(&iter); while ((request = indexer_queue_request_peek(queue)) != NULL) { indexer_queue_request_remove(queue); indexer_queue_request_finish(queue, &request, FALSE); } } bool indexer_queue_is_empty(struct indexer_queue *queue) { return queue->head == NULL; } unsigned int indexer_queue_count(struct indexer_queue *queue) { return hash_table_count(queue->requests); } dovecot-2.3.21.1/src/indexer/indexer.c0000644000000000000000000000736114656633576014353 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "restrict-access.h" #include "process-title.h" #include "master-service.h" #include "master-service-settings.h" #include "indexer-client.h" #include "indexer-queue.h" #include "worker-pool.h" #include "worker-connection.h" static const struct master_service_settings *set; static struct indexer_queue *queue; static struct worker_pool *worker_pool; void indexer_refresh_proctitle(void) { if (!set->verbose_proctitle) return; process_title_set(t_strdup_printf("[%u clients, %u requests]", indexer_clients_get_count(), indexer_queue_count(queue))); } static bool idle_die(void) { return indexer_queue_is_empty(queue) && !worker_pool_have_connections(worker_pool); } static void client_connected(struct master_service_connection *conn) { master_service_client_connection_accept(conn); indexer_client_create(conn, queue); } static void worker_send_request(struct connection *conn, struct indexer_request *request) { indexer_queue_request_work(request); worker_connection_request(conn, request); } static void queue_try_send_more(struct indexer_queue *queue) { struct connection *conn; struct indexer_request *request, *first_moved_request = NULL; while ((request = indexer_queue_request_peek(queue)) != NULL) { conn = worker_pool_find_username_connection(worker_pool, request->username); if (conn != NULL) { /* There is already a connection handling a request * for this user. Move the request to the back of the * queue and handle requests from other users. * Terminate if we went through all requests. */ if (request == first_moved_request) { /* all requests are waiting for existing users to finish. */ break; } if (first_moved_request == NULL) first_moved_request = request; indexer_queue_move_head_to_tail(queue); continue; } else { /* create a new connection to a worker */ if (!worker_pool_get_connection(worker_pool, &conn)) break; } indexer_queue_request_remove(queue); worker_send_request(conn, request); } } static void queue_listen_callback(struct indexer_queue *queue) { queue_try_send_more(queue); } static void worker_status_callback(int percentage, struct indexer_request *request) { if (percentage >= 0 && percentage < 100) { indexer_queue_request_status(queue, request, percentage); return; } indexer_queue_request_finish(queue, &request, percentage == 100); } static void worker_avail_callback(void) { /* A new worker became available. Try to shrink the queue. */ queue_try_send_more(queue); } int main(int argc, char *argv[]) { const char *error; master_service = master_service_init("indexer", 0, &argc, &argv, ""); if (master_getopt(master_service) > 0) return FATAL_DEFAULT; if (master_service_settings_read_simple(master_service, NULL, &error) < 0) i_fatal("Error reading configuration: %s", error); set = master_service_settings_get(master_service); master_service_init_log(master_service); restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL); restrict_access_allow_coredumps(TRUE); master_service_set_idle_die_callback(master_service, idle_die); queue = indexer_queue_init(indexer_client_status_callback); indexer_queue_set_listen_callback(queue, queue_listen_callback); worker_pool = worker_pool_init("indexer-worker", worker_status_callback, worker_avail_callback); master_service_init_finish(master_service); master_service_run(master_service, client_connected); indexer_queue_cancel_all(queue); indexer_clients_destroy_all(); worker_pool_deinit(&worker_pool); indexer_queue_deinit(&queue); master_service_deinit(&master_service); return 0; } dovecot-2.3.21.1/src/indexer/worker-connection.c0000644000000000000000000001166314656633576016363 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "aqueue.h" #include "connection.h" #include "ioloop.h" #include "istream.h" #include "llist.h" #include "ostream.h" #include "str.h" #include "strescape.h" #include "master-service.h" #include "indexer-queue.h" #include "worker-connection.h" #include #define INDEXER_PROTOCOL_MAJOR_VERSION 1 #define INDEXER_PROTOCOL_MINOR_VERSION 0 #define INDEXER_MASTER_NAME "indexer-master-worker" #define INDEXER_WORKER_NAME "indexer-worker-master" struct worker_connection { struct connection conn; indexer_status_callback_t *callback; worker_available_callback_t *avail_callback; char *request_username; struct indexer_request *request; }; static unsigned int worker_last_process_limit = 0; static void worker_connection_call_callback(struct worker_connection *worker, int percentage) { if (worker->request != NULL) worker->callback(percentage, worker->request); if (percentage < 0 || percentage == 100) worker->request = NULL; } void worker_connection_destroy(struct connection *conn) { struct worker_connection *worker = container_of(conn, struct worker_connection, conn); worker_connection_call_callback(worker, -1); i_free_and_null(worker->request_username); connection_deinit(conn); worker->avail_callback(); i_free(conn); } static int worker_connection_handshake_args(struct connection *conn, const char *const *args) { unsigned int process_limit; int ret; if (!conn->version_received) { if ((ret = connection_handshake_args_default(conn, args)) < 1) return ret; /* we are not done yet */ return 0; } if (str_to_uint(args[0], &process_limit) < 0 || process_limit == 0) { e_error(conn->event, "Worker sent invalid process limit '%s'", args[0]); return -1; } worker_last_process_limit = process_limit; return 1; } static int worker_connection_input_args(struct connection *conn, const char *const *args) { struct worker_connection *worker = container_of(conn, struct worker_connection, conn); int percentage; int ret = 1; if (str_to_int(args[0], &percentage) < 0 || percentage < -1 || percentage > 100) { e_error(conn->event, "Worker sent invalid progress '%s'", args[0]); return -1; } if (percentage < 0) ret = -1; worker_connection_call_callback(worker, percentage); if (worker->request == NULL) { /* disconnect after each request */ ret = -1; } return ret; } bool worker_connection_is_connected(struct connection *conn) { return !conn->disconnected; } unsigned int worker_connections_get_process_limit(void) { return worker_last_process_limit; } void worker_connection_request(struct connection *conn, struct indexer_request *request) { struct worker_connection *worker = container_of(conn, struct worker_connection, conn); i_assert(worker_connection_is_connected(conn)); i_assert(request->index || request->optimize); if (worker->request_username == NULL) worker->request_username = i_strdup(request->username); else { i_assert(strcmp(worker->request_username, request->username) == 0); } worker->request = request; T_BEGIN { string_t *str = t_str_new(128); str_append_tabescaped(str, request->username); str_append_c(str, '\t'); str_append_tabescaped(str, request->mailbox); str_append_c(str, '\t'); if (request->session_id != NULL) str_append_tabescaped(str, request->session_id); str_printfa(str, "\t%u\t", request->max_recent_msgs); if (request->index) str_append_c(str, 'i'); if (request->optimize) str_append_c(str, 'o'); str_append_c(str, '\n'); o_stream_nsend(conn->output, str_data(str), str_len(str)); } T_END; } const char *worker_connection_get_username(struct connection *conn) { struct worker_connection *worker = container_of(conn, struct worker_connection, conn); return worker->request_username; } static const struct connection_vfuncs worker_connection_vfuncs = { .destroy = worker_connection_destroy, .input_args = worker_connection_input_args, .handshake_args = worker_connection_handshake_args, }; static const struct connection_settings worker_connection_set = { .service_name_in = INDEXER_WORKER_NAME, .service_name_out = INDEXER_MASTER_NAME, .major_version = INDEXER_PROTOCOL_MAJOR_VERSION, .minor_version = INDEXER_PROTOCOL_MINOR_VERSION, .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, .client = TRUE, }; struct connection_list *worker_connection_list_create(void) { return connection_list_init(&worker_connection_set, &worker_connection_vfuncs); } struct connection * worker_connection_create(const char *socket_path, indexer_status_callback_t *callback, worker_available_callback_t *avail_callback, struct connection_list *list) { struct worker_connection *conn; conn = i_new(struct worker_connection, 1); conn->callback = callback; conn->avail_callback = avail_callback; connection_init_client_unix(list, &conn->conn, socket_path); return &conn->conn; } dovecot-2.3.21.1/src/indexer/worker-pool.c0000644000000000000000000000453214656633576015172 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "llist.h" #include "connection.h" #include "master-service.h" #include "worker-connection.h" #include "worker-pool.h" #define MAX_WORKER_IDLE_SECS (60*5) struct worker_pool { char *socket_path; indexer_status_callback_t *callback; worker_available_callback_t *avail_callback; struct connection_list *connection_list; }; struct worker_pool * worker_pool_init(const char *socket_path, indexer_status_callback_t *callback, worker_available_callback_t *avail_callback) { struct worker_pool *pool; pool = i_new(struct worker_pool, 1); pool->socket_path = i_strdup(socket_path); pool->callback = callback; pool->avail_callback = avail_callback; pool->connection_list = worker_connection_list_create(); return pool; } void worker_pool_deinit(struct worker_pool **_pool) { struct worker_pool *pool = *_pool; *_pool = NULL; if (pool->connection_list != NULL) connection_list_deinit(&pool->connection_list); i_free(pool->connection_list); i_free(pool->socket_path); i_free(pool); } bool worker_pool_have_connections(struct worker_pool *pool) { return pool->connection_list->connections != NULL; } static int worker_pool_add_connection(struct worker_pool *pool, struct connection **conn_r) { struct connection *conn; conn = worker_connection_create(pool->socket_path, pool->callback, pool->avail_callback, pool->connection_list); if (connection_client_connect(conn) < 0) { worker_connection_destroy(conn); return -1; } *conn_r = conn; return 0; } bool worker_pool_get_connection(struct worker_pool *pool, struct connection **conn_r) { unsigned int max_connections; max_connections = I_MAX(1, worker_connections_get_process_limit()); if (pool->connection_list->connections_count >= max_connections) return FALSE; if (worker_pool_add_connection(pool, conn_r) < 0) return FALSE; return TRUE; } struct connection * worker_pool_find_username_connection(struct worker_pool *pool, const char *username) { struct connection *list; const char *worker_user; for (list = pool->connection_list->connections; list != NULL; list = list->next) { worker_user = worker_connection_get_username(list); if (worker_user != NULL && strcmp(worker_user, username) == 0) return list; } return NULL; } dovecot-2.3.21.1/src/indexer/master-connection.c0000644000000000000000000002620214656633576016340 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "connection.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "strescape.h" #include "process-title.h" #include "master-service.h" #include "master-service-settings.h" #include "mail-namespace.h" #include "mail-storage-private.h" #include "mail-storage-service.h" #include "mail-search-build.h" #include "master-connection.h" #include #define INDEXER_PROTOCOL_MAJOR_VERSION 1 #define INDEXER_PROTOCOL_MINOR_VERSION 0 #define INDEXER_MASTER_NAME "indexer-master-worker" #define INDEXER_WORKER_NAME "indexer-worker-master" static struct event_category event_category_indexer_worker = { .name = "indexer-worker", }; struct master_connection { struct connection conn; struct mail_storage_service_ctx *storage_service; bool version_received:1; }; static void ATTR_NULL(1, 2) indexer_worker_refresh_proctitle(const char *username, const char *mailbox, uint32_t seq1, uint32_t seq2) { if (!master_service_settings_get(master_service)->verbose_proctitle) return; if (username == NULL) process_title_set("[idling]"); else if (seq1 == 0) process_title_set(t_strdup_printf("[%s %s]", username, mailbox)); else { process_title_set(t_strdup_printf("[%s %s - %u/%u]", username, mailbox, seq1, seq2)); } } static const char * get_attempt_error(unsigned int counter, uint32_t first_uid, uint32_t last_uid) { if (counter == 0) return " (no mails indexed)"; return t_strdup_printf( " (attempted to index %u messages between UIDs %u..%u)", counter, first_uid, last_uid); } static int index_mailbox_precache(struct master_connection *conn, struct mailbox *box) { struct mail_storage *storage = mailbox_get_storage(box); const char *username = mail_storage_get_user(storage)->username; const char *box_vname = mailbox_get_vname(box); const char *errstr; enum mail_error error; struct mailbox_status status; struct mailbox_transaction_context *trans; struct mail_search_args *search_args; struct mail_search_context *ctx; struct mail *mail; struct mailbox_metadata metadata; uint32_t seq, first_uid = 0, last_uid = 0; char percentage_str[2+1+1]; unsigned int counter = 0, max, percentage, percentage_sent = 0; int ret = 0; struct event *index_event = event_create(box->event); event_add_category(index_event, &event_category_indexer_worker); if (mailbox_get_metadata(box, MAILBOX_METADATA_PRECACHE_FIELDS, &metadata) < 0) { e_error(index_event, "Precache-fields lookup failed: %s", mailbox_get_last_internal_error(box, NULL)); event_unref(&index_event); return -1; } if (mailbox_get_status(box, STATUS_MESSAGES | STATUS_LAST_CACHED_SEQ, &status) < 0) { e_error(index_event, "Status lookup failed: %s", mailbox_get_last_internal_error(box, NULL)); event_unref(&index_event); return -1; } seq = status.last_cached_seq + 1; trans = mailbox_transaction_begin(box, MAILBOX_TRANSACTION_FLAG_NO_CACHE_DEC, "indexing"); search_args = mail_search_build_init(); mail_search_build_add_seqset(search_args, seq, status.messages); event_enable_user_cpu_usecs(index_event); ctx = mailbox_search_init(trans, search_args, NULL, metadata.precache_fields, NULL); mail_search_args_unref(&search_args); max = status.messages + 1 - seq; while (mailbox_search_next(ctx, &mail)) { if (first_uid == 0) first_uid = mail->uid; last_uid = mail->uid; e_debug(index_event, "Indexing UID=%u", mail->uid); if (mail_precache(mail) < 0) { e_error(index_event, "Precache for UID=%u failed: %s%s", mail->uid, mailbox_get_last_internal_error(box, NULL), get_attempt_error(counter, first_uid, last_uid)); ret = -1; break; } if (++counter % 100 == 0) { percentage = counter*100 / max; if (percentage != percentage_sent && percentage < 100) { percentage_sent = percentage; if (i_snprintf(percentage_str, sizeof(percentage_str), "%u\n", percentage) < 0) i_unreached(); o_stream_nsend_str(conn->conn.output, percentage_str); } indexer_worker_refresh_proctitle(username, box_vname, counter, max); } } if (mailbox_search_deinit(&ctx) < 0) { e_error(index_event, "Mail search failed: %s%s", mailbox_get_last_internal_error(box, NULL), get_attempt_error(counter, first_uid, last_uid)); ret = -1; } const char *uids = first_uid == 0 ? "" : t_strdup_printf(" (UIDs %u..%u)", first_uid, last_uid); event_add_int(index_event, "message_count", counter); event_add_int(index_event, "first_uid", first_uid); event_add_int(index_event, "last_uid", last_uid); #define FINISHED_EVENT_NAME "indexer_worker_indexing_finished" if (mailbox_transaction_commit(&trans) < 0) { struct event_passthrough *e = event_create_passthrough(index_event)-> set_name(FINISHED_EVENT_NAME); errstr = t_strdup_printf("Transaction commit failed: %s", mailbox_get_last_internal_error(box, &error)); e->add_str("error", errstr); const char *log_error = t_strdup_printf("%s (attempted to index %u messages%s)", errstr, counter, uids); if (error != MAIL_ERROR_NOTFOUND) e_error(e->event(), "%s", log_error); else e_debug(e->event(), "%s", log_error); ret = -1; } else { struct event_passthrough *e = event_create_passthrough(index_event)-> set_name(FINISHED_EVENT_NAME); e_debug(e->event(), "Indexed %u messages%s", counter, uids); } event_unref(&index_event); return ret; } static int index_mailbox(struct master_connection *conn, struct mail_user *user, const char *mailbox, unsigned int max_recent_msgs, const char *what) { struct mail_namespace *ns; struct mailbox *box; struct mailbox_status status; const char *path, *errstr; enum mail_error error; enum mailbox_sync_flags sync_flags = MAILBOX_SYNC_FLAG_FULL_READ; int ret; ns = mail_namespace_find(user->namespaces, mailbox); box = mailbox_alloc(ns->list, mailbox, 0); ret = mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_INDEX, &path); if (ret < 0) { errstr = mailbox_get_last_internal_error(box, &error); if (error != MAIL_ERROR_NOTFOUND) e_error(box->event, "Getting path failed: %s", errstr); mailbox_free(&box); return -1; } if (ret == 0) { e_info(box->event, "Indexes disabled, skipping"); mailbox_free(&box); return 0; } ret = 0; if (max_recent_msgs != 0) { /* index only if there aren't too many recent messages. don't bother syncing the mailbox, that alone can take a while with large maildirs. */ if (mailbox_open(box) < 0) { errstr = mailbox_get_last_internal_error(box, &error); if (error != MAIL_ERROR_NOTFOUND) e_error(box->event, "Opening failed: %s", errstr); ret = -1; } else { mailbox_get_open_status(box, STATUS_RECENT, &status); } if (ret < 0 || status.recent > max_recent_msgs) { mailbox_free(&box); return ret; } } if (strchr(what, 'o') != NULL) sync_flags |= MAILBOX_SYNC_FLAG_OPTIMIZE; if (mailbox_sync(box, sync_flags) < 0) { errstr = mailbox_get_last_internal_error(box, &error); if (error != MAIL_ERROR_NOTFOUND) { e_error(box->event, "Syncing failed: %s", errstr); } else { e_debug(box->event, "Syncing failed: %s", errstr); } ret = -1; } else if (strchr(what, 'i') != NULL) { if (index_mailbox_precache(conn, box) < 0) ret = -1; } mailbox_free(&box); return ret; } static int master_connection_input_args(struct connection *_conn, const char *const *args) { struct master_connection *conn = container_of(_conn, struct master_connection, conn); struct mail_storage_service_input input; struct mail_storage_service_user *service_user; struct mail_user *user; const char *str, *error; unsigned int max_recent_msgs; int ret; /* [i][o] */ if (str_array_length(args) != 5 || str_to_uint(args[3], &max_recent_msgs) < 0 || args[4][0] == '\0') { e_error(conn->conn.event, "Invalid input from master: %s", t_strarray_join(args, "\t")); return -1; } i_zero(&input); input.module = "mail"; input.service = "indexer-worker"; input.username = args[0]; /* if session-id is given, use it as a prefix to a unique session ID. we can't use the session-id directly or stats process will complain about duplicates. (especially LMTP would use the same session-id for multiple users' indexing at the same time.) */ if (args[2][0] != '\0') input.session_id_prefix = args[2]; if (mail_storage_service_lookup_next(conn->storage_service, &input, &service_user, &user, &error) <= 0) { e_error(conn->conn.event, "User %s lookup failed: %s", args[0], error); ret = -1; } else { indexer_worker_refresh_proctitle(user->username, args[1], 0, 0); struct event_reason *reason = event_reason_begin("indexer:index_mailbox"); ret = index_mailbox(conn, user, args[1], max_recent_msgs, args[4]); event_reason_end(&reason); /* refresh proctitle before a potentially long-running user unref */ indexer_worker_refresh_proctitle(user->username, "(deinit)", 0, 0); mail_user_deinit(&user); mail_storage_service_user_unref(&service_user); indexer_worker_refresh_proctitle(NULL, NULL, 0, 0); } str = ret < 0 ? "-1\n" : "100\n"; o_stream_nsend_str(conn->conn.output, str); return ret; } static void master_connection_destroy(struct connection *connection) { connection_deinit(connection); i_free(connection); master_service_client_connection_destroyed(master_service); } static int master_connection_handshake_args(struct connection *connection, const char *const *args) { int ret; if ((ret = connection_handshake_args_default(connection, args)) < 1) return ret; const char *limit = t_strdup_printf("%u\n", master_service_get_process_limit(master_service)); o_stream_nsend_str(connection->output, limit); return 1; } static struct connection_list *master_connection_list = NULL; static const struct connection_vfuncs master_connection_vfuncs = { .destroy = master_connection_destroy, .input_args = master_connection_input_args, .handshake_args = master_connection_handshake_args, }; static const struct connection_settings master_connection_set = { .service_name_in = INDEXER_MASTER_NAME, .service_name_out = INDEXER_WORKER_NAME, .major_version = INDEXER_PROTOCOL_MAJOR_VERSION, .minor_version = INDEXER_PROTOCOL_MINOR_VERSION, .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, }; bool master_connection_create(struct master_service_connection *master, struct mail_storage_service_ctx *storage_service) { struct master_connection *conn; if (master_connection_list == NULL) { master_connection_list = connection_list_init(&master_connection_set, &master_connection_vfuncs); } else if (master_connection_list->connections_count > 0) { return FALSE; } conn = i_new(struct master_connection, 1); conn->storage_service = storage_service; conn->conn.event_parent = event_create(NULL); event_add_category(conn->conn.event_parent, &event_category_indexer_worker); connection_init_server(master_connection_list, &conn->conn, master->name, master->fd, master->fd); event_unref(&conn->conn.event_parent); return TRUE; } void master_connections_destroy(void) { if (master_connection_list != NULL) connection_list_deinit(&master_connection_list); } dovecot-2.3.21.1/src/indexer/Makefile.am0000644000000000000000000000172014656633576014576 00000000000000pkglibexecdir = $(libexecdir)/dovecot pkglibexec_PROGRAMS = indexer indexer-worker AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-storage \ -DPKG_RUNDIR=\""$(rundir)"\" \ $(BINARY_CFLAGS) indexer_LDADD = $(LIBDOVECOT) \ $(BINARY_LDFLAGS) indexer_DEPENDENCIES = $(LIBDOVECOT_DEPS) indexer_SOURCES = \ indexer.c \ indexer-client.c \ indexer-queue.c \ indexer-settings.c \ worker-connection.c \ worker-pool.c indexer_worker_LDADD = \ $(LIBDOVECOT_STORAGE) \ $(LIBDOVECOT) \ $(BINARY_LDFLAGS) indexer_worker_DEPENDENCIES = \ $(LIBDOVECOT_STORAGE_DEPS) \ $(LIBDOVECOT_DEPS) indexer_worker_SOURCES = \ indexer-worker.c \ indexer-worker-settings.c \ master-connection.c noinst_HEADERS = \ indexer.h \ indexer-client.h \ indexer-queue.h \ master-connection.h \ worker-connection.h \ worker-pool.h dovecot-2.3.21.1/src/indexer/worker-connection.h0000644000000000000000000000234514656633576016365 00000000000000#ifndef WORKER_CONNECTION_H #define WORKER_CONNECTION_H #include "indexer.h" struct indexer_request; struct connection_list; typedef void worker_available_callback_t(void); struct connection * worker_connection_create(const char *socket_path, indexer_status_callback_t *callback, worker_available_callback_t *avail_callback, struct connection_list *list); void worker_connection_destroy(struct connection *conn); struct connection_list *worker_connection_list_create(void); /* Returns TRUE if worker is connected to (not necessarily handshaked yet) */ bool worker_connection_is_connected(struct connection *conn); /* Returns the last process_limit returned by a worker connection handshake. If no handshakes have been received yet, returns 0. */ unsigned int worker_connections_get_process_limit(void); /* Send a new indexing request for username+mailbox. The status callback is called as necessary. Requests can be queued, but only for the same username. */ void worker_connection_request(struct connection *conn, struct indexer_request *request); /* Returns username of the currently pending requests, or NULL if there are none. */ const char *worker_connection_get_username(struct connection *conn); #endif dovecot-2.3.21.1/src/indexer/master-connection.h0000644000000000000000000000035214656633576016343 00000000000000#ifndef MASTER_CONNECTION_H #define MASTER_CONNECTION_H bool master_connection_create(struct master_service_connection *conn, struct mail_storage_service_ctx *storage_service); void master_connections_destroy(void); #endif dovecot-2.3.21.1/src/indexer/worker-pool.h0000644000000000000000000000112514656633576015172 00000000000000#ifndef WORKER_POOL_H #define WORKER_POOL_H #include "indexer.h" #include "worker-connection.h" struct connection; struct worker_pool * worker_pool_init(const char *socket_path, indexer_status_callback_t *callback, worker_available_callback_t *avail_callback); void worker_pool_deinit(struct worker_pool **pool); bool worker_pool_have_connections(struct worker_pool *pool); bool worker_pool_get_connection(struct worker_pool *pool, struct connection **conn_r); struct connection * worker_pool_find_username_connection(struct worker_pool *pool, const char *username); #endif dovecot-2.3.21.1/src/indexer/indexer-client.c0000644000000000000000000001276714656633576015635 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "connection.h" #include "istream.h" #include "ostream.h" #include "strescape.h" #include "master-service.h" #include "indexer-queue.h" #include "indexer-client.h" #include #define MAX_INBUF_SIZE (1024*64) #define INDEXER_CLIENT_PROTOCOL_MAJOR_VERSION 1 #define INDEXER_CLIENT_PROTOCOL_MINOR_VERSION 0 struct indexer_client { struct connection conn; int refcount; struct indexer_queue *queue; }; struct indexer_client_request { struct indexer_client *client; unsigned int tag; }; static void indexer_client_destroy(struct connection *conn); static void indexer_client_ref(struct indexer_client *client); static void indexer_client_unref(struct indexer_client *client); static int indexer_client_request_queue(struct indexer_client *client, bool append, const char *const *args, const char **error_r) { struct indexer_client_request *ctx = NULL; const char *session_id = NULL; unsigned int tag, max_recent_msgs; /* [ []] */ if (str_array_length(args) < 3) { *error_r = "Wrong parameter count"; return -1; } if (str_to_uint(args[0], &tag) < 0) { *error_r = "Invalid tag"; return -1; } if (args[3] == NULL) max_recent_msgs = 0; else if (str_to_uint(args[3], &max_recent_msgs) < 0) { *error_r = "Invalid max_recent_msgs"; return -1; } else { session_id = args[4]; } if (tag != 0) { ctx = i_new(struct indexer_client_request, 1); ctx->client = client; ctx->tag = tag; indexer_client_ref(client); } indexer_queue_append(client->queue, append, args[1], args[2], session_id, max_recent_msgs, ctx); o_stream_nsend_str(client->conn.output, t_strdup_printf("%u\tOK\n", tag)); return 0; } static int indexer_client_request_optimize(struct indexer_client *client, const char *const *args, const char **error_r) { struct indexer_client_request *ctx = NULL; unsigned int tag; /* */ if (str_array_length(args) != 3) { *error_r = "Wrong parameter count"; return -1; } if (str_to_uint(args[0], &tag) < 0) { *error_r = "Invalid tag"; return -1; } if (tag != 0) { ctx = i_new(struct indexer_client_request, 1); ctx->client = client; ctx->tag = tag; indexer_client_ref(client); } indexer_queue_append_optimize(client->queue, args[1], args[2], ctx); o_stream_nsend_str(client->conn.output, t_strdup_printf("%u\tOK\n", tag)); return 0; } static int indexer_client_request(struct indexer_client *client, const char *const *args, const char **error_r) { const char *cmd = args[0]; args++; if (strcmp(cmd, "APPEND") == 0) return indexer_client_request_queue(client, TRUE, args, error_r); else if (strcmp(cmd, "PREPEND") == 0) return indexer_client_request_queue(client, FALSE, args, error_r); else if (strcmp(cmd, "OPTIMIZE") == 0) return indexer_client_request_optimize(client, args, error_r); else { *error_r = t_strconcat("Unknown command: ", cmd, NULL); return -1; } } static int indexer_client_input_args(struct connection *conn, const char *const *args) { struct indexer_client *client = container_of(conn, struct indexer_client, conn); const char *error; if (indexer_client_request(client, args, &error) < 0) { i_error("Client input error: %s", error); return -1; } return 1; } void indexer_client_status_callback(int percentage, void *context) { struct indexer_client_request *ctx = context; if (ctx->client->conn.output != NULL) T_BEGIN { o_stream_nsend_str(ctx->client->conn.output, t_strdup_printf("%u\t%d\n", ctx->tag, percentage)); } T_END; if (percentage < 0 || percentage == 100) { indexer_client_unref(ctx->client); i_free(ctx); } } static struct connection_list *indexer_client_list = NULL; static const struct connection_vfuncs indexer_client_vfuncs = { .destroy = indexer_client_destroy, .input_args = indexer_client_input_args, }; static const struct connection_settings indexer_client_set = { .service_name_in = "indexer", .service_name_out = "indexer", .major_version = INDEXER_CLIENT_PROTOCOL_MAJOR_VERSION, .minor_version = INDEXER_CLIENT_PROTOCOL_MINOR_VERSION, .input_max_size = SIZE_MAX, .output_max_size = IO_BLOCK_SIZE, }; void indexer_client_create(struct master_service_connection *conn, struct indexer_queue *queue) { struct indexer_client *client; if (indexer_client_list == NULL) { indexer_client_list = connection_list_init(&indexer_client_set, &indexer_client_vfuncs); } client = i_new(struct indexer_client, 1); client->refcount = 1; client->queue = queue; connection_init_server(indexer_client_list, &client->conn, conn->name, conn->fd, conn->fd); indexer_refresh_proctitle(); } static void indexer_client_destroy(struct connection *conn) { struct indexer_client *client = container_of(conn, struct indexer_client, conn); connection_deinit(&client->conn); master_service_client_connection_destroyed(master_service); indexer_client_unref(client); indexer_refresh_proctitle(); } static void indexer_client_ref(struct indexer_client *client) { i_assert(client->refcount > 0); client->refcount++; } static void indexer_client_unref(struct indexer_client *client) { i_assert(client->refcount > 0); if (--client->refcount > 0) return; i_free(client); } unsigned int indexer_clients_get_count(void) { if (indexer_client_list == NULL) return 0; return indexer_client_list->connections_count; } void indexer_clients_destroy_all(void) { connection_list_deinit(&indexer_client_list); } dovecot-2.3.21.1/src/doveadm/0000755000000000000000000000000014656633641012574 500000000000000dovecot-2.3.21.1/src/doveadm/doveadm-util.h0000644000000000000000000000213214656633576015264 00000000000000#ifndef DOVEADM_UTIL_H #define DOVEADM_UTIL_H #include "net.h" #define DOVEADM_SERVER_PROTOCOL_VERSION_MAJOR 1 #define DOVEADM_SERVER_PROTOCOL_VERSION_MINOR 2 #define DOVEADM_SERVER_PROTOCOL_VERSION_LINE "VERSION\tdoveadm-server\t1\t2" #define DOVEADM_CLIENT_PROTOCOL_VERSION_LINE "VERSION\tdoveadm-client\t1\t2" extern bool doveadm_verbose, doveadm_debug, doveadm_server; const char *unixdate2str(time_t timestamp); const char *doveadm_plugin_getenv(const char *name); int doveadm_connect(const char *path); int doveadm_tcp_connect(const char *target, in_port_t default_port); int doveadm_connect_with_default_port(const char *path, in_port_t default_port); void doveadm_load_modules(void); void doveadm_unload_modules(void); bool doveadm_has_unloaded_plugin(const char *name); char doveadm_log_type_to_char(enum log_type type) ATTR_PURE; bool doveadm_log_type_from_char(char c, enum log_type *type_r); /* Similar to strcmp(), except "camel case" == "camel-case" == "camelCase". Otherwise the comparison is case-sensitive. */ int i_strccdascmp(const char *a, const char *b) ATTR_PURE; #endif dovecot-2.3.21.1/src/doveadm/doveadm-settings.h0000644000000000000000000000366214656633576016160 00000000000000#ifndef DOVEADM_SETTINGS_H #define DOVEADM_SETTINGS_H #include "net.h" struct ssl_iostream_settings; /* */ enum dsync_features { DSYNC_FEATURE_EMPTY_HDR_WORKAROUND = 0x1, DSYNC_FEATURE_NO_HEADER_HASHES = 0x2, }; /* */ struct doveadm_settings { const char *base_dir; const char *libexec_dir; const char *mail_plugins; const char *mail_plugin_dir; const char *mail_temp_dir; bool auth_debug; const char *auth_socket_path; const char *doveadm_socket_path; unsigned int doveadm_worker_count; in_port_t doveadm_port; const char *doveadm_ssl; const char *doveadm_username; const char *doveadm_password; const char *doveadm_allowed_commands; const char *dsync_alt_char; const char *dsync_remote_cmd; const char *director_username_hash; const char *doveadm_api_key; const char *dsync_features; const char *dsync_hashed_headers; unsigned int dsync_commit_msgs_interval; const char *doveadm_http_rawlog_dir; enum dsync_features parsed_features; ARRAY(const char *) plugin_envs; }; struct doveadm_setting_root { const struct setting_parser_info *info; void *settings; }; ARRAY_DEFINE_TYPE(doveadm_setting_root, struct doveadm_setting_root); extern const struct setting_parser_info doveadm_setting_parser_info; extern struct doveadm_settings *doveadm_settings; extern const struct master_service_settings *service_set; extern const struct master_service_ssl_settings *doveadm_ssl_set; extern ARRAY_TYPE(doveadm_setting_root) doveadm_setting_roots; extern bool doveadm_verbose_proctitle; void doveadm_get_ssl_settings(struct ssl_iostream_settings *set_r, pool_t pool); void doveadm_settings_expand(struct doveadm_settings *set, pool_t pool); void doveadm_setting_roots_add(const struct setting_parser_info *info); void *doveadm_setting_roots_get_settings(const struct setting_parser_info *info); void doveadm_read_settings(void); void doveadm_settings_init(void); void doveadm_settings_deinit(void); #endif dovecot-2.3.21.1/src/doveadm/doveadm-util.c0000644000000000000000000001160314656633576015262 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "net.h" #include "time-util.h" #include "master-service.h" #include "module-dir.h" #include "doveadm-settings.h" #include "doveadm-mail.h" #include "doveadm-util.h" #include #include #include #include #define DOVEADM_TCP_CONNECT_TIMEOUT_SECS 30 bool doveadm_verbose = FALSE, doveadm_debug = FALSE, doveadm_server = FALSE; static struct module *modules = NULL; void doveadm_load_modules(void) { struct module_dir_load_settings mod_set; /* some doveadm plugins have dependencies to mail plugins. we can load only those whose dependencies have been loaded earlier, the rest are ignored. */ i_zero(&mod_set); mod_set.abi_version = DOVECOT_ABI_VERSION; mod_set.require_init_funcs = TRUE; mod_set.debug = doveadm_debug; mod_set.ignore_dlopen_errors = TRUE; modules = module_dir_load_missing(modules, DOVEADM_MODULEDIR, NULL, &mod_set); module_dir_init(modules); } void doveadm_unload_modules(void) { module_dir_unload(&modules); } bool doveadm_has_unloaded_plugin(const char *name) { struct module *module; DIR *dir; struct dirent *d; const char *plugin_name; size_t name_len = strlen(name); bool found = FALSE; /* first check that it's not actually loaded */ for (module = modules; module != NULL; module = module->next) { if (strcmp(module_get_plugin_name(module), name) == 0) return FALSE; } dir = opendir(DOVEADM_MODULEDIR); if (dir == NULL) return FALSE; while ((d = readdir(dir)) != NULL) { plugin_name = module_file_get_name(d->d_name); if (str_begins(plugin_name, "doveadm_")) plugin_name += 8; if (strncmp(plugin_name, name, name_len) == 0 && (plugin_name[name_len] == '\0' || strcmp(plugin_name + name_len, "_plugin") == 0)) { found = TRUE; break; } } (void)closedir(dir); return found; } const char *unixdate2str(time_t timestamp) { return t_strflocaltime("%Y-%m-%d %H:%M:%S", timestamp); } const char *doveadm_plugin_getenv(const char *name) { const char *const *envs; unsigned int i, count; if (!array_is_created(&doveadm_settings->plugin_envs)) return NULL; envs = array_get(&doveadm_settings->plugin_envs, &count); for (i = 0; i < count; i += 2) { if (strcmp(envs[i], name) == 0) return envs[i+1]; } return NULL; } static int doveadm_tcp_connect_port(const char *host, in_port_t port) { struct ip_addr *ips; unsigned int ips_count; int ret, fd; alarm(DOVEADM_TCP_CONNECT_TIMEOUT_SECS); ret = net_gethostbyname(host, &ips, &ips_count); if (ret != 0) { i_fatal("Lookup of host %s failed: %s", host, net_gethosterror(ret)); } fd = net_connect_ip_blocking(&ips[0], port, NULL); if (fd == -1) { i_fatal("connect(%s:%u) failed: %m", net_ip2addr(&ips[0]), port); } alarm(0); return fd; } int doveadm_tcp_connect(const char *target, in_port_t default_port) { const char *host; in_port_t port; if (net_str2hostport(target, default_port, &host, &port) < 0) { i_fatal("Port not known for %s. Either set proxy_port " "or use %s:port", target, target); } return doveadm_tcp_connect_port(host, port); } int doveadm_connect_with_default_port(const char *path, in_port_t default_port) { int fd; /* we'll assume UNIX sockets typically have an absolute path, or at the very least '/' somewhere. */ if (strchr(path, '/') == NULL) fd = doveadm_tcp_connect(path, default_port); else { fd = net_connect_unix(path); if (fd == -1) i_fatal("net_connect_unix(%s) failed: %m", path); } return fd; } int doveadm_connect(const char *path) { return doveadm_connect_with_default_port(path, 0); } int i_strccdascmp(const char *a, const char *b) { while(*a != '\0' && *b != '\0') { if ((*a == ' ' || *a == '-') && *a != *b && *b != ' ' && *b != '-') { if (i_toupper(*(a+1)) == *(b)) a++; else break; } else if ((*b == ' ' || *b == '-') && *a != *b && *a != ' ' && *a != '-') { if (*a == i_toupper(*(b+1))) b++; else break; } else if (!((*a == ' ' || *a == '-') && (*b == ' ' || *b == '-')) && (*a != *b)) break; a++; b++; } return *a-*b; } char doveadm_log_type_to_char(enum log_type type) { switch(type) { case LOG_TYPE_DEBUG: return '\x01'; case LOG_TYPE_INFO: return '\x02'; case LOG_TYPE_WARNING: return '\x03'; case LOG_TYPE_ERROR: return '\x04'; case LOG_TYPE_FATAL: return '\x05'; case LOG_TYPE_PANIC: return '\x06'; default: i_unreached(); } } bool doveadm_log_type_from_char(char c, enum log_type *type_r) { switch(c) { case '\x01': *type_r = LOG_TYPE_DEBUG; break; case '\x02': *type_r = LOG_TYPE_INFO; break; case '\x03': *type_r = LOG_TYPE_WARNING; break; case '\x04': *type_r = LOG_TYPE_ERROR; break; case '\x05': *type_r = LOG_TYPE_FATAL; break; case '\x06': *type_r = LOG_TYPE_PANIC; break; default: *type_r = LOG_TYPE_WARNING; return FALSE; } return TRUE; } dovecot-2.3.21.1/src/doveadm/doveadm-cmd.c0000644000000000000000000002716214656633576015057 00000000000000/* Copyright (c) 2009-2r016 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "istream.h" #include "str.h" #include "net.h" #include "doveadm.h" #include "doveadm-cmd.h" #include #include #include static struct doveadm_cmd_ver2 *doveadm_commands_ver2[] = { &doveadm_cmd_mailbox_mutf7, &doveadm_cmd_service_stop_ver2, &doveadm_cmd_service_status_ver2, &doveadm_cmd_sis_deduplicate, &doveadm_cmd_sis_find, &doveadm_cmd_process_status_ver2, &doveadm_cmd_stop_ver2, &doveadm_cmd_reload_ver2, &doveadm_cmd_stats_dump_ver2, &doveadm_cmd_stats_add_ver2, &doveadm_cmd_stats_remove_ver2, &doveadm_cmd_oldstats_dump_ver2, &doveadm_cmd_oldstats_reset_ver2, &doveadm_cmd_penalty_ver2, &doveadm_cmd_kick_ver2, &doveadm_cmd_who_ver2 }; static const struct exit_code_str { int code; const char *str; } exit_code_strings[] = { { DOVEADM_EX_UNKNOWN, "UNKNOWN" }, { EX_TEMPFAIL, "TEMPFAIL" }, { EX_USAGE, "USAGE" }, { EX_NOUSER, "NOUSER" }, { EX_NOPERM, "NOPERM" }, { EX_PROTOCOL, "PROTOCOL" }, { EX_DATAERR, "DATAERR" }, { DOVEADM_EX_NOREPLICATE, "NOREPLICATE" }, { DOVEADM_EX_NOTFOUND, "NOTFOUND" } }; ARRAY_TYPE(doveadm_cmd_ver2) doveadm_cmds_ver2; ARRAY_DEFINE_TYPE(getopt_option_array, struct option); const char *doveadm_exit_code_to_str(int code) { for(size_t i = 0; i < N_ELEMENTS(exit_code_strings); i++) { const struct exit_code_str *ptr = &exit_code_strings[i]; if (ptr->code == code) return ptr->str; } return "UNKNOWN"; } int doveadm_str_to_exit_code(const char *reason) { for(size_t i = 0; i < N_ELEMENTS(exit_code_strings); i++) { const struct exit_code_str *ptr = &exit_code_strings[i]; if (strcmp(ptr->str, reason) == 0) return ptr->code; } return DOVEADM_EX_UNKNOWN; } void doveadm_cmd_register_ver2(struct doveadm_cmd_ver2 *cmd) { if (cmd->cmd == NULL) { if (cmd->mail_cmd != NULL) cmd->cmd = doveadm_cmd_ver2_to_mail_cmd_wrapper; else i_unreached(); } array_push_back(&doveadm_cmds_ver2, cmd); } const struct doveadm_cmd_ver2 *doveadm_cmd_find_ver2(const char *cmd_name) { const struct doveadm_cmd_ver2 *cmd; array_foreach(&doveadm_cmds_ver2, cmd) { if (strcmp(cmd_name, cmd->name) == 0) return cmd; } return NULL; } const struct doveadm_cmd_ver2 * doveadm_cmd_find_with_args_ver2(const char *cmd_name, int *argc, const char *const *argv[]) { int i, k; const struct doveadm_cmd_ver2 *cmd; const char *cptr; for (i = 0; i < *argc; i++) { if (strcmp((*argv)[i], cmd_name) == 0) break; } i_assert(i != *argc); array_foreach(&doveadm_cmds_ver2, cmd) { cptr = cmd->name; /* cannot reuse i here because this needs be done more than once */ for (k = 0; *cptr != '\0' && i + k < *argc; k++) { size_t alen = strlen((*argv)[i + k]); /* make sure we don't overstep */ if (strlen(cptr) < alen) break; /* did not match */ if (strncmp(cptr, (*argv)[i+k], alen) != 0) break; /* do not accept abbreviations */ if (cptr[alen] != ' ' && cptr[alen] != '\0') break; cptr += alen; if (*cptr != '\0') cptr++; /* consume space */ } /* name was fully consumed */ if (*cptr == '\0') { if (k > 1) { *argc -= k-1; *argv += k-1; } return cmd; } } return NULL; } void doveadm_cmds_init(void) { unsigned int i; i_array_init(&doveadm_cmds_ver2, 2); for (i = 0; i < N_ELEMENTS(doveadm_commands_ver2); i++) doveadm_cmd_register_ver2(doveadm_commands_ver2[i]); doveadm_register_director_commands(); doveadm_register_instance_commands(); doveadm_register_proxy_commands(); doveadm_register_log_commands(); doveadm_register_replicator_commands(); doveadm_register_dict_commands(); doveadm_register_fs_commands(); } void doveadm_cmds_deinit(void) { array_free(&doveadm_cmds_ver2); } static const struct doveadm_cmd_param * doveadm_cmd_param_get(const struct doveadm_cmd_context *cctx, const char *name) { i_assert(cctx != NULL); i_assert(cctx->argv != NULL); for(int i = 0; i < cctx->argc; i++) { if (strcmp(cctx->argv[i].name, name) == 0 && cctx->argv[i].value_set) return &cctx->argv[i]; } return NULL; } bool doveadm_cmd_param_bool(const struct doveadm_cmd_context *cctx, const char *name, bool *value_r) { const struct doveadm_cmd_param *param; if ((param = doveadm_cmd_param_get(cctx, name)) == NULL) return FALSE; if (param->type == CMD_PARAM_BOOL) { *value_r = param->value.v_bool; return TRUE; } return FALSE; } bool doveadm_cmd_param_int64(const struct doveadm_cmd_context *cctx, const char *name, int64_t *value_r) { const struct doveadm_cmd_param *param; if ((param = doveadm_cmd_param_get(cctx, name)) == NULL) return FALSE; if (param->type == CMD_PARAM_INT64) { *value_r = param->value.v_int64; return TRUE; } return FALSE; } bool doveadm_cmd_param_str(const struct doveadm_cmd_context *cctx, const char *name, const char **value_r) { const struct doveadm_cmd_param *param; if ((param = doveadm_cmd_param_get(cctx, name)) == NULL) return FALSE; if (param->type == CMD_PARAM_STR) { *value_r = param->value.v_string; return TRUE; } return FALSE; } bool doveadm_cmd_param_ip(const struct doveadm_cmd_context *cctx, const char *name, struct ip_addr *value_r) { const struct doveadm_cmd_param *param; if ((param = doveadm_cmd_param_get(cctx, name)) == NULL) return FALSE; if (param->type == CMD_PARAM_IP) { memcpy(value_r, ¶m->value.v_ip, sizeof(struct ip_addr)); return TRUE; } return FALSE; } bool doveadm_cmd_param_array(const struct doveadm_cmd_context *cctx, const char *name, const char *const **value_r) { const struct doveadm_cmd_param *param; unsigned int count; if ((param = doveadm_cmd_param_get(cctx, name)) == NULL) return FALSE; if (param->type == CMD_PARAM_ARRAY) { *value_r = array_get(¶m->value.v_array, &count); /* doveadm_cmd_params_null_terminate_arrays() should have been called, which guarantees that we're NULL-terminated */ i_assert((*value_r)[count] == NULL); return TRUE; } return FALSE; } bool doveadm_cmd_param_istream(const struct doveadm_cmd_context *cctx, const char *name, struct istream **value_r) { const struct doveadm_cmd_param *param; if ((param = doveadm_cmd_param_get(cctx, name)) == NULL) return FALSE; if (param->type == CMD_PARAM_ISTREAM) { *value_r = param->value.v_istream; return TRUE; } return FALSE; } void doveadm_cmd_params_clean(ARRAY_TYPE(doveadm_cmd_param_arr_t) *pargv) { struct doveadm_cmd_param *param; array_foreach_modifiable(pargv, param) { if (param->type == CMD_PARAM_ISTREAM && param->value.v_istream != NULL) i_stream_destroy(¶m->value.v_istream); } array_clear(pargv); } void doveadm_cmd_params_null_terminate_arrays( ARRAY_TYPE(doveadm_cmd_param_arr_t) *pargv) { struct doveadm_cmd_param *param; array_foreach_modifiable(pargv, param) { if (param->type == CMD_PARAM_ARRAY && array_is_created(¶m->value.v_array)) { array_append_zero(¶m->value.v_array); array_pop_back(¶m->value.v_array); } } } static void doveadm_build_options(const struct doveadm_cmd_param par[], string_t *shortopts, ARRAY_TYPE(getopt_option_array) *longopts) { for (size_t i = 0; par[i].name != NULL; i++) { struct option longopt; i_zero(&longopt); longopt.name = par[i].name; if (par[i].short_opt != '\0') { longopt.val = par[i].short_opt; str_append_c(shortopts, par[i].short_opt); if (par[i].type != CMD_PARAM_BOOL) str_append_c(shortopts, ':'); } if (par[i].type != CMD_PARAM_BOOL) longopt.has_arg = 1; array_push_back(longopts, &longopt); } array_append_zero(longopts); } static void doveadm_fill_param(struct doveadm_cmd_param *param, const char *value, pool_t pool) { param->value_set = TRUE; switch (param->type) { case CMD_PARAM_BOOL: param->value.v_bool = TRUE; break; case CMD_PARAM_INT64: if (str_to_int64(value, ¶m->value.v_int64) != 0) param->value_set = FALSE; break; case CMD_PARAM_IP: if (net_addr2ip(value, ¶m->value.v_ip) != 0) param->value_set = FALSE; break; case CMD_PARAM_STR: param->value.v_string = p_strdup(pool, value); break; case CMD_PARAM_ARRAY: if (!array_is_created(¶m->value.v_array)) p_array_init(¶m->value.v_array, pool, 8); const char *val = p_strdup(pool, value); array_push_back(¶m->value.v_array, &val); break; case CMD_PARAM_ISTREAM: { struct istream *is; if (strcmp(value,"-") == 0) is = i_stream_create_fd(STDIN_FILENO, IO_BLOCK_SIZE); else is = i_stream_create_file(value, IO_BLOCK_SIZE); param->value.v_istream = is; break; } } } bool doveadm_cmd_try_run_ver2(const char *cmd_name, int argc, const char *const argv[], struct doveadm_cmd_context *cctx) { const struct doveadm_cmd_ver2 *cmd; cmd = doveadm_cmd_find_with_args_ver2(cmd_name, &argc, &argv); if (cmd == NULL) return FALSE; cctx->cmd = cmd; if (doveadm_cmd_run_ver2(argc, argv, cctx) < 0) doveadm_exit_code = EX_USAGE; return TRUE; } static int doveadm_cmd_process_options(int argc, const char *const argv[], struct doveadm_cmd_context *cctx, pool_t pool, ARRAY_TYPE(doveadm_cmd_param_arr_t) *pargv) { struct doveadm_cmd_param *param; ARRAY_TYPE(getopt_option_array) opts; string_t *optbuf = str_new(pool, 64); p_array_init(&opts, pool, 4); // build parameters if ((cctx->cmd->flags & CMD_FLAG_NO_UNORDERED_OPTIONS) != 0) str_append_c(optbuf, '+'); doveadm_build_options(cctx->cmd->parameters, optbuf, &opts); unsigned int pargc; for (pargc = 0; cctx->cmd->parameters[pargc].name != NULL; pargc++) { param = array_append_space(pargv); memcpy(param, &cctx->cmd->parameters[pargc], sizeof(struct doveadm_cmd_param)); param->value_set = FALSE; } i_assert(pargc == array_count(&opts)-1); /* opts is NULL-terminated */ if ((cctx->cmd->flags & CMD_FLAG_NO_OPTIONS) != 0) { /* process -parameters as if they were regular parameters */ optind = 1; return 0; } int c, li; while ((c = getopt_long(argc, (char *const *)argv, str_c(optbuf), array_front(&opts), &li)) > -1) { switch (c) { case 0: for (unsigned int i = 0; i < array_count(pargv); i++) { const struct option *opt = array_idx(&opts, li); param = array_idx_modifiable(pargv, i); if (opt->name == param->name) doveadm_fill_param(param, optarg, pool); } break; case '?': case ':': doveadm_cmd_params_clean(pargv); return -1; default: // hunt the option for (unsigned int i = 0; i < pargc; i++) { const struct option *longopt = array_idx(&opts, i); if (longopt->val == c) doveadm_fill_param(array_idx_modifiable(pargv, i), optarg, pool); } } } return 0; } int doveadm_cmd_run_ver2(int argc, const char *const argv[], struct doveadm_cmd_context *cctx) { ARRAY_TYPE(doveadm_cmd_param_arr_t) pargv; unsigned int pargc; pool_t pool = pool_datastack_create(); p_array_init(&pargv, pool, 20); if (doveadm_cmd_process_options(argc, argv, cctx, pool, &pargv) < 0) return -1; /* process positional arguments */ for (; optind < argc; optind++) { struct doveadm_cmd_param *ptr; bool found = FALSE; array_foreach_modifiable(&pargv, ptr) { if ((ptr->flags & CMD_PARAM_FLAG_POSITIONAL) != 0 && (ptr->value_set == FALSE || ptr->type == CMD_PARAM_ARRAY)) { doveadm_fill_param(ptr, argv[optind], pool); found = TRUE; break; } } if (!found) { i_error("Extraneous arguments found: %s", t_strarray_join(argv + optind, " ")); doveadm_cmd_params_clean(&pargv); return -1; } } doveadm_cmd_params_null_terminate_arrays(&pargv); cctx->argv = array_get_modifiable(&pargv, &pargc); cctx->argc = pargc; cctx->cmd->cmd(cctx); doveadm_cmd_params_clean(&pargv); return 0; } dovecot-2.3.21.1/src/doveadm/doveadm-mail-rebuild.c0000644000000000000000000000517014656633576016655 00000000000000/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "mail-storage.h" #include "doveadm-print.h" #include "doveadm-mailbox-list-iter.h" #include "doveadm-mail-iter.h" #include "doveadm-mail.h" #include "mail-storage-private.h" static int cmd_rebuild_attachment_box(struct doveadm_mail_cmd_context *ctx, const struct mailbox_info *info) { struct doveadm_mail_iter *iter; struct mail *mail; int ret = 0; if (doveadm_mail_iter_init(ctx, info, ctx->search_args, MAIL_FETCH_IMAP_BODYSTRUCTURE| MAIL_FETCH_MESSAGE_PARTS, NULL, 0, &iter) < 0) return -1; while (doveadm_mail_iter_next(iter, &mail) && ret >= 0) { T_BEGIN { doveadm_print(dec2str(mail->uid)); switch(mail_set_attachment_keywords(mail)) { case -1: doveadm_print("error"); doveadm_mail_failed_mailbox(ctx, mail->box); ret = -1; break; case 0: doveadm_print("no"); break; case 1: doveadm_print("yes"); break; default: i_unreached(); } } T_END; } if (doveadm_mail_iter_deinit(&iter) < 0) ret = -1; return ret; } static int cmd_rebuild_attachment_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) { const enum mailbox_list_iter_flags iter_flags = MAILBOX_LIST_ITER_NO_AUTO_BOXES | MAILBOX_LIST_ITER_RETURN_NO_FLAGS; struct doveadm_mailbox_list_iter *iter; const struct mailbox_info *info; int ret = 0; iter = doveadm_mailbox_list_iter_init(ctx, user, ctx->search_args, iter_flags); while ((info = doveadm_mailbox_list_iter_next(iter)) != NULL) T_BEGIN { if (cmd_rebuild_attachment_box(ctx, info) < 0) ret = -1; } T_END; if (doveadm_mailbox_list_iter_deinit(&iter) < 0) ret = -1; return ret; } static void cmd_rebuild_attachment_init(struct doveadm_mail_cmd_context *ctx, const char *const args[]) { doveadm_print_header_simple("uid"); doveadm_print_header_simple("attachment"); ctx->search_args = doveadm_mail_build_search_args(args); } static struct doveadm_mail_cmd_context *cmd_rebuild_attachment_alloc(void) { struct doveadm_mail_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); ctx->v.init = cmd_rebuild_attachment_init; ctx->v.run = cmd_rebuild_attachment_run; doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); return ctx; } struct doveadm_cmd_ver2 doveadm_cmd_rebuild_attachments = { .name = "rebuild attachments", .mail_cmd = cmd_rebuild_attachment_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-mail-deduplicate.c0000644000000000000000000000753414656633576017520 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "hash.h" #include "mail-storage.h" #include "mail-search-build.h" #include "doveadm-mailbox-list-iter.h" #include "doveadm-mail-iter.h" #include "doveadm-mail.h" struct deduplicate_cmd_context { struct doveadm_mail_cmd_context ctx; bool by_msgid; }; static int cmd_deduplicate_box(struct doveadm_mail_cmd_context *_ctx, const struct mailbox_info *info, struct mail_search_args *search_args) { struct deduplicate_cmd_context *ctx = (struct deduplicate_cmd_context *)_ctx; struct doveadm_mail_iter *iter; struct mail *mail; enum mail_error error; pool_t pool; HASH_TABLE(const char *, void *) hash; const char *key, *errstr; int ret = 0; if (doveadm_mail_iter_init(_ctx, info, search_args, 0, NULL, 0, &iter) < 0) return -1; pool = pool_alloconly_create("deduplicate", 10240); hash_table_create(&hash, pool, 0, str_hash, strcmp); while (doveadm_mail_iter_next(iter, &mail)) { if (ctx->by_msgid) { if (mail_get_first_header(mail, "Message-ID", &key) < 0) { errstr = mailbox_get_last_internal_error(mail->box, &error); if (error == MAIL_ERROR_NOTFOUND) continue; i_error("Couldn't lookup Message-ID: for UID=%u: %s", mail->uid, errstr); doveadm_mail_failed_error(_ctx, error); ret = -1; break; } } else { if (mail_get_special(mail, MAIL_FETCH_GUID, &key) < 0) { errstr = mailbox_get_last_internal_error(mail->box, &error); if (error == MAIL_ERROR_NOTFOUND) continue; i_error("Couldn't lookup GUID: for UID=%u: %s", mail->uid, errstr); doveadm_mail_failed_error(_ctx, error); ret = -1; break; } } if (key != NULL && *key != '\0') { if (hash_table_lookup(hash, key) != NULL) mail_expunge(mail); else { key = p_strdup(pool, key); hash_table_insert(hash, key, POINTER_CAST(1)); } } } if (doveadm_mail_iter_deinit_sync(&iter) < 0) ret = -1; hash_table_destroy(&hash); pool_unref(&pool); return ret; } static int cmd_deduplicate_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) { const enum mailbox_list_iter_flags iter_flags = MAILBOX_LIST_ITER_NO_AUTO_BOXES | MAILBOX_LIST_ITER_RETURN_NO_FLAGS; struct doveadm_mailbox_list_iter *iter; const struct mailbox_info *info; int ret = 0; iter = doveadm_mailbox_list_iter_init(ctx, user, ctx->search_args, iter_flags); while ((info = doveadm_mailbox_list_iter_next(iter)) != NULL) T_BEGIN { if (cmd_deduplicate_box(ctx, info, ctx->search_args) < 0) ret = -1; } T_END; if (doveadm_mailbox_list_iter_deinit(&iter) < 0) ret = -1; return ret; } static void cmd_deduplicate_init(struct doveadm_mail_cmd_context *ctx, const char *const args[]) { if (args[0] == NULL) doveadm_mail_help_name("deduplicate"); ctx->search_args = doveadm_mail_build_search_args(args); } static bool cmd_deduplicate_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct deduplicate_cmd_context *ctx = (struct deduplicate_cmd_context *)_ctx; switch (c) { case 'm': ctx->by_msgid = TRUE; break; default: return FALSE; } return TRUE; } static struct doveadm_mail_cmd_context *cmd_deduplicate_alloc(void) { struct deduplicate_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct deduplicate_cmd_context); ctx->ctx.getopt_args = "m"; ctx->ctx.v.parse_arg = cmd_deduplicate_parse_arg; ctx->ctx.v.init = cmd_deduplicate_init; ctx->ctx.v.run = cmd_deduplicate_run; return &ctx->ctx; } struct doveadm_cmd_ver2 doveadm_cmd_deduplicate_ver2 = { .name = "deduplicate", .mail_cmd = cmd_deduplicate_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-m] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('m', "by-msgid", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-print.h0000644000000000000000000000327614656633576015455 00000000000000#ifndef DOVEADM_PRINT_H #define DOVEADM_PRINT_H #define DOVEADM_PRINT_TYPE_TAB "tab" #define DOVEADM_PRINT_TYPE_FLOW "flow" #define DOVEADM_PRINT_TYPE_PAGER "pager" #define DOVEADM_PRINT_TYPE_TABLE "table" #define DOVEADM_PRINT_TYPE_SERVER "server" #define DOVEADM_PRINT_TYPE_JSON "json" #define DOVEADM_PRINT_TYPE_FORMATTED "formatted" enum doveadm_print_header_flags { DOVEADM_PRINT_HEADER_FLAG_RIGHT_JUSTIFY = 0x01, DOVEADM_PRINT_HEADER_FLAG_STICKY = 0x02, DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE = 0x04, DOVEADM_PRINT_HEADER_FLAG_EXPAND = 0x08, DOVEADM_PRINT_HEADER_FLAG_NUMBER = 0x10 }; extern const struct doveadm_print_vfuncs *doveadm_print_vfuncs_all[]; extern bool doveadm_print_hide_titles; /* points to either stdout or to doveadm-server's TCP connection */ extern struct ostream *doveadm_print_ostream; bool doveadm_print_is_initialized(void); void doveadm_print_header(const char *key, const char *title, enum doveadm_print_header_flags flags); void doveadm_print_header_simple(const char *key_title); unsigned int doveadm_print_get_headers_count(void); void doveadm_print(const char *value); void doveadm_print_num(uintmax_t value); /* Stream for same field continues until len=0 */ void doveadm_print_stream(const void *value, size_t size); /* Print the whole input stream. Returns 0 if ok, -1 if stream read() failed. The caller must log the error. */ int doveadm_print_istream(struct istream *input); void doveadm_print_sticky(const char *key, const char *value); void doveadm_print_flush(void); void doveadm_print_unstick_headers(void); void doveadm_print_init(const char *name); void doveadm_print_deinit(void); void doveadm_print_formatted_set_format(const char *format); #endif dovecot-2.3.21.1/src/doveadm/doveadm-mail-altmove.c0000644000000000000000000001067014656633576016677 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "mail-index.h" #include "mail-storage.h" #include "mail-namespace.h" #include "doveadm-mailbox-list-iter.h" #include "doveadm-mail-iter.h" #include "doveadm-mail.h" struct altmove_cmd_context { struct doveadm_mail_cmd_context ctx; bool reverse; }; static int cmd_altmove_box(struct doveadm_mail_cmd_context *ctx, const struct mailbox_info *info, struct mail_search_args *search_args, bool reverse) { struct doveadm_mail_iter *iter; struct mail *mail; enum modify_type modify_type = !reverse ? MODIFY_ADD : MODIFY_REMOVE; if (doveadm_mail_iter_init(ctx, info, search_args, 0, NULL, 0, &iter) < 0) return -1; while (doveadm_mail_iter_next(iter, &mail)) { if (doveadm_debug) { i_debug("altmove: box=%s uid=%u", info->vname, mail->uid); } mail_update_flags(mail, modify_type, (enum mail_flags)MAIL_INDEX_MAIL_FLAG_BACKEND); } return doveadm_mail_iter_deinit_sync(&iter); } static int ns_purge(struct doveadm_mail_cmd_context *ctx, struct mail_namespace *ns, struct mail_storage *storage) { if (mail_storage_purge(storage) < 0) { i_error("Purging namespace '%s' failed: %s", ns->prefix, mail_storage_get_last_internal_error(storage, NULL)); doveadm_mail_failed_storage(ctx, storage); return -1; } return 0; } static int cmd_altmove_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct altmove_cmd_context *ctx = (struct altmove_cmd_context *)_ctx; const enum mailbox_list_iter_flags iter_flags = MAILBOX_LIST_ITER_NO_AUTO_BOXES | MAILBOX_LIST_ITER_RETURN_NO_FLAGS; struct doveadm_mailbox_list_iter *iter; const struct mailbox_info *info; struct mail_namespace *ns, *prev_ns = NULL; ARRAY(struct mail_storage *) purged_storages; struct mail_storage *const *storages, *ns_storage, *prev_storage = NULL; unsigned int i, count; int ret = 0; t_array_init(&purged_storages, 8); iter = doveadm_mailbox_list_iter_init(_ctx, user, _ctx->search_args, iter_flags); while ((info = doveadm_mailbox_list_iter_next(iter)) != NULL) T_BEGIN { ns_storage = mail_namespace_get_default_storage(info->ns); if (ns_storage != prev_storage) { if (prev_storage != NULL) { if (ns_purge(_ctx, prev_ns, prev_storage) < 0) ret = -1; array_push_back(&purged_storages, &prev_storage); } prev_storage = ns_storage; prev_ns = info->ns; } if (cmd_altmove_box(_ctx, info, _ctx->search_args, ctx->reverse) < 0) ret = -1; } T_END; if (doveadm_mailbox_list_iter_deinit(&iter) < 0) ret = -1; if (prev_storage != NULL) { if (ns_purge(_ctx, prev_ns, prev_storage) < 0) ret = -1; array_push_back(&purged_storages, &prev_storage); } /* make sure all private storages have been purged */ storages = array_get(&purged_storages, &count); for (ns = user->namespaces; ns != NULL; ns = ns->next) { if (ns->type != MAIL_NAMESPACE_TYPE_PRIVATE) continue; ns_storage = mail_namespace_get_default_storage(ns); for (i = 0; i < count; i++) { if (ns_storage == storages[i]) break; } if (i == count) { if (ns_purge(_ctx, ns, ns_storage) < 0) ret = -1; array_push_back(&purged_storages, &ns_storage); storages = array_get(&purged_storages, &count); } } return ret; } static void cmd_altmove_init(struct doveadm_mail_cmd_context *ctx, const char *const args[]) { if (args[0] == NULL) doveadm_mail_help_name("altmove"); ctx->search_args = doveadm_mail_build_search_args(args); } static bool cmd_mailbox_altmove_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct altmove_cmd_context *ctx = (struct altmove_cmd_context *)_ctx; switch (c) { case 'r': ctx->reverse = TRUE; break; default: return FALSE; } return TRUE; } static struct doveadm_mail_cmd_context *cmd_altmove_alloc(void) { struct altmove_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct altmove_cmd_context); ctx->ctx.getopt_args = "r"; ctx->ctx.v.parse_arg = cmd_mailbox_altmove_parse_arg; ctx->ctx.v.init = cmd_altmove_init; ctx->ctx.v.run = cmd_altmove_run; return &ctx->ctx; } struct doveadm_cmd_ver2 doveadm_cmd_altmove_ver2 = { .name = "altmove", .mail_cmd = cmd_altmove_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-r] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('r', "reverse", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-dump-index.c0000644000000000000000000006351614656633576016371 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "hex-binary.h" #include "file-lock.h" #include "message-parser.h" #include "message-part-serialize.h" #include "mail-cache-private.h" #include "mail-index-modseq.h" #include "mail-storage-private.h" #include "doveadm-dump.h" #include #include struct index_vsize_header { uint64_t vsize; uint32_t highest_uid; uint32_t message_count; }; struct maildir_index_header { uint32_t new_check_time, new_mtime, new_mtime_nsecs; uint32_t cur_check_time, cur_mtime, cur_mtime_nsecs; uint32_t uidlist_mtime, uidlist_mtime_nsecs, uidlist_size; }; struct mbox_index_header { uint64_t sync_size; uint32_t sync_mtime; uint8_t dirty_flag; uint8_t unused[3]; uint8_t mailbox_guid[16]; }; struct sdbox_index_header { uint32_t rebuild_count; guid_128_t mailbox_guid; uint8_t flags; uint8_t unused[3]; }; struct mdbox_index_header { uint32_t map_uid_validity; guid_128_t mailbox_guid; uint8_t flags; uint8_t unused[3]; }; struct mdbox_mail_index_record { uint32_t map_uid; uint32_t save_date; }; struct obox_mail_index_record { unsigned char guid[GUID_128_SIZE]; unsigned char oid[GUID_128_SIZE]; }; struct mobox_mail_index_header { uint32_t rebuild_count; uint32_t map_uid_validity; uint8_t unused[4]; guid_128_t mailbox_guid; }; struct mobox_mail_index_record { uint32_t map_uid; uint32_t save_date; }; struct mobox_map_mail_index_header { uint32_t rebuild_count; }; struct mobox_map_mail_index_record { uint32_t offset; uint32_t size; guid_128_t oid; }; struct mailbox_list_index_header { uint8_t refresh_flag; /* array of { uint32_t id; char name[]; } */ }; struct mailbox_list_index_record { uint32_t name_id; uint32_t parent_uid; guid_128_t guid; uint32_t uid_validity; }; struct mailbox_list_index_msgs_record { uint32_t messages; uint32_t unseen; uint32_t recent; uint32_t uidnext; }; struct fts_index_header { uint32_t last_indexed_uid; uint32_t settings_checksum; uint32_t unused; }; struct virtual_mail_index_header { uint32_t change_counter; uint32_t mailbox_count; uint32_t highest_mailbox_id; uint32_t search_args_crc32; }; struct virtual_mail_index_mailbox_record { uint32_t id; uint32_t name_len; uint32_t uid_validity; uint32_t next_uid; uint64_t highest_modseq; }; struct virtual_mail_index_record { uint32_t mailbox_id; uint32_t real_uid; }; struct mdbox_mail_index_map_record { uint32_t file_id; uint32_t offset; uint32_t size; }; static void dump_hdr(struct mail_index *index) { const struct mail_index_header *hdr = &index->map->hdr; unsigned int i; printf("version .................. = %u.%u\n", hdr->major_version, hdr->minor_version); printf("base header size ......... = %u\n", hdr->base_header_size); printf("header size .............. = %u\n", hdr->header_size); printf("record size .............. = %u\n", hdr->record_size); printf("compat flags ............. = %u\n", hdr->compat_flags); printf("index id ................. = %u (%s)\n", hdr->indexid, unixdate2str(hdr->indexid)); printf("flags .................... = %u\n", hdr->flags); printf("uid validity ............. = %u (%s)\n", hdr->uid_validity, unixdate2str(hdr->uid_validity)); printf("next uid ................. = %u\n", hdr->next_uid); printf("messages count ........... = %u\n", hdr->messages_count); printf("seen messages count ...... = %u\n", hdr->seen_messages_count); printf("deleted messages count ... = %u\n", hdr->deleted_messages_count); printf("first recent uid ......... = %u\n", hdr->first_recent_uid); printf("first unseen uid lowwater = %u\n", hdr->first_unseen_uid_lowwater); printf("first deleted uid lowwater = %u\n", hdr->first_deleted_uid_lowwater); printf("log file seq ............. = %u\n", hdr->log_file_seq); if (hdr->minor_version == 0) { printf("log file int offset ...... = %u\n", hdr->log_file_tail_offset); printf("log file ext offset ...... = %u\n", hdr->log_file_head_offset); } else { printf("log file tail offset ..... = %u\n", hdr->log_file_tail_offset); printf("log file head offset ..... = %u\n", hdr->log_file_head_offset); } if (hdr->minor_version >= 3) { printf("log2 rotate time ......... = %u (%s)\n", hdr->log2_rotate_time, unixdate2str(hdr->log2_rotate_time)); printf("last temp file scan ...... = %u (%s)\n", hdr->last_temp_file_scan, unixdate2str(hdr->last_temp_file_scan)); } printf("day stamp ................ = %u (%s)\n", hdr->day_stamp, unixdate2str(hdr->day_stamp)); for (i = 0; i < N_ELEMENTS(hdr->day_first_uid); i++) printf("day first uid[%u] ......... = %u\n", i, hdr->day_first_uid[i]); } static void dump_list_header(const void *data, size_t size) { const struct mailbox_list_index_header *hdr = data; const void *name_start, *p; size_t i, len; uint32_t id; printf(" - refresh_flag = %d\n", hdr->refresh_flag); for (i = sizeof(*hdr); i < size; ) { /* get id */ if (i + sizeof(id) > size) { printf(" - corrupted\n"); break; } memcpy(&id, CONST_PTR_OFFSET(data, i), sizeof(id)); i += sizeof(id); if (id == 0) break; /* get name */ p = memchr(CONST_PTR_OFFSET(data, i), '\0', size-i); if (p == NULL) { printf(" - corrupted\n"); break; } name_start = CONST_PTR_OFFSET(data, i); len = (const char *)p - (const char *)name_start; printf(" - %d : %.*s\n", id, (int)len, (const char *)name_start); i += len + 1; } } static void dump_box_name_header(const void *data, size_t size) { char *dest = t_malloc0(size + 1); memcpy(dest, data, size); for (size_t i = 0; i < size; i++) { if (dest[i] == '\0') dest[i] = '\n'; } printf(" %s\n", t_strarray_join(t_strsplit(dest, "\n"), "\n ")); } static void dump_extension_header(struct mail_index *index, const struct mail_index_ext *ext) { const void *data; void *buf; if (strcmp(ext->name, MAIL_INDEX_EXT_KEYWORDS) == 0) return; /* add some padding, since we don't bother to handle undersized headers correctly */ buf = t_malloc0(MALLOC_ADD(ext->hdr_size, 128)); data = MAIL_INDEX_MAP_HDR_OFFSET(index->map, ext->hdr_offset); memcpy(buf, data, ext->hdr_size); data = buf; if (strcmp(ext->name, "hdr-vsize") == 0) { const struct index_vsize_header *hdr = data; printf("header\n"); printf(" - highest uid . = %u\n", hdr->highest_uid); printf(" - message count = %u\n", hdr->message_count); printf(" - vsize ....... = %"PRIu64"\n", hdr->vsize); } else if (strcmp(ext->name, "maildir") == 0) { const struct maildir_index_header *hdr = data; printf("header\n"); printf(" - new_check_time .... = %s\n", unixdate2str(hdr->new_check_time)); printf(" - new_mtime ......... = %s\n", unixdate2str(hdr->new_mtime)); printf(" - new_mtime_nsecs ... = %u\n", hdr->new_mtime_nsecs); printf(" - cur_check_time .... = %s\n", unixdate2str(hdr->cur_check_time)); printf(" - cur_mtime ......... = %s\n", unixdate2str(hdr->cur_mtime)); printf(" - cur_mtime_nsecs.... = %u\n", hdr->cur_mtime_nsecs); printf(" - uidlist_mtime ..... = %s\n", unixdate2str(hdr->uidlist_mtime)); printf(" - uidlist_mtime_nsecs = %u\n", hdr->uidlist_mtime_nsecs); printf(" - uidlist_size ...... = %u\n", hdr->uidlist_size); } else if (strcmp(ext->name, "mbox") == 0) { const struct mbox_index_header *hdr = data; printf("header\n"); printf(" - sync_mtime . = %s\n", unixdate2str(hdr->sync_mtime)); printf(" - sync_size .. = %"PRIu64"\n", hdr->sync_size); printf(" - dirty_flag . = %d\n", hdr->dirty_flag); printf(" - mailbox_guid = %s\n", guid_128_to_string(hdr->mailbox_guid)); } else if (strcmp(ext->name, "mdbox-hdr") == 0) { const struct mdbox_index_header *hdr = data; printf("header\n"); printf(" - map_uid_validity .. = %u\n", hdr->map_uid_validity); printf(" - mailbox_guid ...... = %s\n", guid_128_to_string(hdr->mailbox_guid)); printf(" - flags ............. = 0x%x\n", hdr->flags); } else if (strcmp(ext->name, "dbox-hdr") == 0) { const struct sdbox_index_header *hdr = data; printf("header\n"); printf(" - rebuild_count . = %u\n", hdr->rebuild_count); printf(" - mailbox_guid .. = %s\n", guid_128_to_string(hdr->mailbox_guid)); printf(" - flags ......... = 0x%x\n", hdr->flags); } else if (strcmp(ext->name, "mobox-hdr") == 0) { const struct mobox_mail_index_header *hdr = data; printf("header\n"); printf(" - rebuild_count .. = %u\n", hdr->rebuild_count); printf(" - map_uid_validity .. = %u\n", hdr->map_uid_validity); printf(" - mailbox_guid ...... = %s\n", guid_128_to_string(hdr->mailbox_guid)); } else if (strcmp(ext->name, "mobox-map") == 0) { const struct mobox_map_mail_index_header *hdr = data; printf("header\n"); printf(" - rebuild_count .. = %u\n", hdr->rebuild_count); } else if (strcmp(ext->name, "modseq") == 0) { const struct mail_index_modseq_header *hdr = data; printf("header\n"); printf(" - highest_modseq = %"PRIu64"\n", hdr->highest_modseq); printf(" - log_seq ...... = %u\n", hdr->log_seq); printf(" - log_offset ... = %u\n", hdr->log_offset); } else if (strcmp(ext->name, "fts") == 0) { const struct fts_index_header *hdr = data; printf("header\n"); printf(" - last_indexed_uid ..... = %u\n", hdr->last_indexed_uid); printf(" - settings_checksum .... = %u\n", hdr->settings_checksum); } else if (strcmp(ext->name, "virtual") == 0) { const struct virtual_mail_index_header *hdr = data; const struct virtual_mail_index_mailbox_record *rec; const unsigned char *name; unsigned int i; printf("header\n"); printf(" - change_counter ... = %u\n", hdr->change_counter); printf(" - mailbox_count .... = %u\n", hdr->mailbox_count); printf(" - highest_mailbox_id = %u\n", hdr->highest_mailbox_id); printf(" - search_args_crc32 = %u\n", hdr->search_args_crc32); rec = CONST_PTR_OFFSET(hdr, sizeof(*hdr)); name = CONST_PTR_OFFSET(rec, sizeof(*rec) * hdr->mailbox_count); for (i = 0; i < hdr->mailbox_count; i++, rec++) { printf("mailbox %s:\n", t_strndup(name, rec->name_len)); printf(" - id ........... = %u\n", rec->id); printf(" - uid_validity . = %u\n", rec->uid_validity); printf(" - next_uid ..... = %u\n", rec->next_uid); printf(" - highest_modseq = %"PRIu64"\n", rec->highest_modseq); name += rec->name_len; } } else if (strcmp(ext->name, "list") == 0) { printf("header ........ = %s\n", binary_to_hex(data, ext->hdr_size)); dump_list_header(data, ext->hdr_size); } else if (strcmp(ext->name, "box-name") == 0) { printf("header ........ = %s\n", binary_to_hex(data, ext->hdr_size)); dump_box_name_header(data, ext->hdr_size); } else if (strcmp(ext->name, "hdr-pop3-uidl") == 0) { const struct mailbox_index_pop3_uidl *hdr = data; printf("header\n"); printf(" - max_uid_with_pop3_uidl = %u\n", hdr->max_uid_with_pop3_uidl); } else { printf("header ........ = %s\n", binary_to_hex(data, ext->hdr_size)); } } static void dump_extensions(struct mail_index *index) { const struct mail_index_ext *extensions; unsigned int i, count; if (array_is_created(&index->map->extensions)) extensions = array_get(&index->map->extensions, &count); else count = 0; if (count == 0) { printf("no extensions\n"); return; } for (i = 0; i < count; i++) { const struct mail_index_ext *ext = &extensions[i]; printf("-- Extension %u --\n", i); printf("name ........ = %s\n", ext->name); printf("hdr_size .... = %u\n", ext->hdr_size); printf("reset_id .... = %u\n", ext->reset_id); printf("record_offset = %u\n", ext->record_offset); printf("record_size . = %u\n", ext->record_size); printf("record_align = %u\n", ext->record_align); if (ext->hdr_size > 0) T_BEGIN { dump_extension_header(index, ext); } T_END; } } static void dump_keywords(struct mail_index *index) { const unsigned int *kw_indexes; const char *const *keywords; unsigned int i, count; printf("-- Keywords --\n"); if (!array_is_created(&index->map->keyword_idx_map)) return; kw_indexes = array_get(&index->map->keyword_idx_map, &count); if (count == 0) return; keywords = array_front(&index->keywords); for (i = 0; i < count; i++) printf("%3u = %s\n", i, keywords[kw_indexes[i]]); } static const char *cache_decision2str(enum mail_cache_decision_type type) { const char *str; switch (type & ENUM_NEGATE(MAIL_CACHE_DECISION_FORCED)) { case MAIL_CACHE_DECISION_NO: str = "no"; break; case MAIL_CACHE_DECISION_TEMP: str = "tmp"; break; case MAIL_CACHE_DECISION_YES: str = "yes"; break; default: return t_strdup_printf("0x%x", type); } if ((type & MAIL_CACHE_DECISION_FORCED) != 0) str = t_strconcat(str, "!", NULL); return str; } #define CACHE_TYPE_IS_FIXED_SIZE(type) \ ((type) == MAIL_CACHE_FIELD_FIXED_SIZE || \ (type) == MAIL_CACHE_FIELD_BITMASK) static const char *cache_type2str(enum mail_cache_field_type type) { switch (type) { case MAIL_CACHE_FIELD_FIXED_SIZE: return "fix"; case MAIL_CACHE_FIELD_VARIABLE_SIZE: return "var"; case MAIL_CACHE_FIELD_STRING: return "str"; case MAIL_CACHE_FIELD_BITMASK: return "bit"; case MAIL_CACHE_FIELD_HEADER: return "hdr"; default: return t_strdup_printf("0x%x", type); } } static void dump_cache_hdr(struct mail_cache *cache) { const struct mail_cache_header *hdr; const struct mail_cache_field *fields, *field; unsigned int i, count, cache_idx; (void)mail_cache_open_and_verify(cache); if (MAIL_CACHE_IS_UNUSABLE(cache)) { printf("cache is unusable\n"); return; } hdr = cache->hdr; printf("major version ........ = %u\n", hdr->major_version); printf("minor version ........ = %u\n", hdr->minor_version); printf("indexid .............. = %u (%s)\n", hdr->indexid, unixdate2str(hdr->indexid)); printf("file_seq ............. = %u (%s) (%d purges)\n", hdr->file_seq, unixdate2str(hdr->file_seq), hdr->file_seq - hdr->indexid); printf("continued_record_count = %u\n", hdr->continued_record_count); printf("record_count ......... = %u\n", hdr->record_count); printf("used_file_size (old) . = %u\n", hdr->backwards_compat_used_file_size); printf("deleted_record_count . = %u\n", hdr->deleted_record_count); printf("field_header_offset .. = %u (0x%08x nontranslated)\n", mail_index_offset_to_uint32(hdr->field_header_offset), hdr->field_header_offset); printf("-- Cache fields --\n"); fields = mail_cache_register_get_list(cache, pool_datastack_create(), &count); printf( " # Name Type Size Dec Last used\n"); for (i = 0; i < cache->file_fields_count; i++) { cache_idx = cache->file_field_map[i]; field = &fields[cache_idx]; printf("%2u: %-44s %-4s ", i, field->name, cache_type2str(field->type)); if (field->field_size != (uint32_t)-1 || CACHE_TYPE_IS_FIXED_SIZE(field->type)) printf("%4u ", field->field_size); else printf(" - "); printf("%-4s %.16s\n", cache_decision2str(field->decision), unixdate2str(field->last_used)); } } static void dump_message_part(string_t *str, const struct message_part *part) { for (; part != NULL; part = part->next) { str_append_c(str, '('); str_printfa(str, "pos=%"PRIuUOFF_T" ", part->physical_pos); str_printfa(str, "hdr.p=%"PRIuUOFF_T" ", part->header_size.physical_size); str_printfa(str, "hdr.v=%"PRIuUOFF_T" ", part->header_size.virtual_size); str_printfa(str, "body.p=%"PRIuUOFF_T" ", part->body_size.physical_size); str_printfa(str, "body.v=%"PRIuUOFF_T" ", part->body_size.virtual_size); str_printfa(str, "flags=%x", part->flags); if (part->children != NULL) { str_append_c(str, ' '); dump_message_part(str, part->children); } str_append_c(str, ')'); } } static void dump_cache_mime_parts(string_t *str, const void *data, unsigned int size) { const struct message_part *part; const char *error; str_append_c(str, ' '); part = message_part_deserialize(pool_datastack_create(), data, size, &error); if (part == NULL) { str_printfa(str, "error: %s", error); return; } dump_message_part(str, part); } static void dump_cache_append_string(string_t *str, const unsigned char *data, unsigned int size) { /* cached strings end with NUL */ if (size > 0 && data[size-1] == '\0') size--; str_append_data(str, data, size); } static void dump_cache_snippet(string_t *str, const unsigned char *data, unsigned int size) { if (size == 0) return; str_printfa(str, " (version=%u: ", data[0]); dump_cache_append_string(str, data+1, size-1); str_append_c(str, ')'); } static void dump_cache(struct mail_cache_view *cache_view, unsigned int seq) { struct mail_cache_lookup_iterate_ctx iter; const struct mail_cache_record *prev_rec = NULL; const struct mail_cache_field *field; struct mail_cache_iterate_field iter_field; const void *data; unsigned int size; string_t *str; int ret; str = t_str_new(512); mail_cache_lookup_iter_init(cache_view, seq, &iter); while ((ret = mail_cache_lookup_iter_next(&iter, &iter_field)) > 0) { if (iter.rec != prev_rec) { printf(" - cache offset=%u size=%u, prev_offset = %u\n", iter.offset, iter.rec->size, iter.rec->prev_offset); prev_rec = iter.rec; } field = &cache_view->cache->fields[iter_field.field_idx].field; data = iter_field.data; size = iter_field.size; str_truncate(str, 0); str_printfa(str, " - %s: ", field->name); switch (field->type) { case MAIL_CACHE_FIELD_FIXED_SIZE: if (size == sizeof(uint32_t)) { uint32_t value; memcpy(&value, data, sizeof(value)); str_printfa(str, "%u ", value); } else if (size == sizeof(uint64_t)) { uint64_t value; memcpy(&value, data, sizeof(value)); str_printfa(str, "%"PRIu64, value); } /* fall through */ case MAIL_CACHE_FIELD_VARIABLE_SIZE: case MAIL_CACHE_FIELD_BITMASK: str_printfa(str, "(%s)", binary_to_hex(data, size)); if (strcmp(field->name, "mime.parts") == 0) dump_cache_mime_parts(str, data, size); else if (strcmp(field->name, "body.snippet") == 0) dump_cache_snippet(str, data, size); break; case MAIL_CACHE_FIELD_STRING: dump_cache_append_string(str, data, size); break; case MAIL_CACHE_FIELD_HEADER: { const uint32_t *lines = data; int i; for (i = 0;; i++) { if (size < sizeof(uint32_t)) { if (i == 0 && size == 0) { /* header doesn't exist */ break; } str_append(str, "\n - BROKEN: header field doesn't end with 0 line"); size = 0; break; } size -= sizeof(uint32_t); data = CONST_PTR_OFFSET(data, sizeof(uint32_t)); if (lines[i] == 0) break; if (i > 0) str_append(str, ", "); str_printfa(str, "%u", lines[i]); } if (i == 1 && size > 0 && ((const char *)data)[size-1] == '\n') size--; if (size > 0) str_printfa(str, ": %.*s", (int)size, (const char *)data); break; } case MAIL_CACHE_FIELD_COUNT: i_unreached(); break; } fwrite(str_data(str), 1, str_len(str), stdout); putchar('\n'); } if (ret < 0) printf(" - broken cache\n"); } static const char *flags2str(enum mail_flags flags) { string_t *str; str = t_str_new(64); str_append_c(str, '('); if ((flags & MAIL_SEEN) != 0) str_append(str, "Seen "); if ((flags & MAIL_ANSWERED) != 0) str_append(str, "Answered "); if ((flags & MAIL_FLAGGED) != 0) str_append(str, "Flagged "); if ((flags & MAIL_DELETED) != 0) str_append(str, "Deleted "); if ((flags & MAIL_DRAFT) != 0) str_append(str, "Draft "); if (str_len(str) == 1) return ""; str_truncate(str, str_len(str)-1); str_append_c(str, ')'); return str_c(str); } static void dump_record(struct mail_index_view *view, unsigned int seq) { struct mail_index *index = mail_index_view_get_index(view); const struct mail_index_record *rec; const struct mail_index_registered_ext *ext; const void *data; unsigned int i, ext_count; string_t *str; bool expunged; rec = mail_index_lookup(view, seq); printf("RECORD: seq=%u, uid=%u, flags=0x%02x %s\n", seq, rec->uid, rec->flags, flags2str(rec->flags)); str = t_str_new(256); ext = array_get(&index->extensions, &ext_count); for (i = 0; i < ext_count; i++) { mail_index_lookup_ext(view, seq, i, &data, &expunged); if (data == NULL || ext[i].record_size == 0) continue; str_truncate(str, 0); str_printfa(str, " - ext %d %-10s: ", i, ext[i].name); if (ext[i].record_size == sizeof(uint16_t) && ext[i].record_align == sizeof(uint16_t)) str_printfa(str, "%10u", *((const uint16_t *)data)); else if (ext[i].record_size == sizeof(uint32_t) && ext[i].record_align == sizeof(uint32_t)) str_printfa(str, "%10u", *((const uint32_t *)data)); else if (ext[i].record_size == sizeof(uint64_t) && ext[i].record_align == sizeof(uint64_t)) { uint64_t value = *((const uint64_t *)data); str_printfa(str, "%10"PRIu64, value); } else { str_append(str, " "); } str_printfa(str, " (%s)", binary_to_hex(data, ext[i].record_size)); printf("%s\n", str_c(str)); if (strcmp(ext[i].name, "virtual") == 0) { const struct virtual_mail_index_record *vrec = data; printf(" : mailbox_id = %u\n", vrec->mailbox_id); printf(" : real_uid = %u\n", vrec->real_uid); } else if (strcmp(ext[i].name, "map") == 0) { const struct mdbox_mail_index_map_record *mrec = data; printf(" : file_id = %u\n", mrec->file_id); printf(" : offset = %u\n", mrec->offset); printf(" : size = %u\n", mrec->size); } else if (strcmp(ext[i].name, "mdbox") == 0) { const struct mdbox_mail_index_record *drec = data; printf(" : map_uid = %u\n", drec->map_uid); printf(" : save_date = %u (%s)\n", drec->save_date, unixdate2str(drec->save_date)); } else if (strcmp(ext[i].name, "obox") == 0) { const struct obox_mail_index_record *orec = data; printf(" : guid = %s\n", guid_128_to_string(orec->guid)); printf(" : oid = %s\n", binary_to_hex(orec->oid, ext[i].record_size - sizeof(orec->guid))); } else if (strcmp(ext[i].name, "mobox") == 0) { const struct mobox_mail_index_record *orec = data; printf(" : map_uid = %u\n", orec->map_uid); printf(" : save_date = %u (%s)\n", orec->save_date, unixdate2str(orec->save_date)); } else if (strcmp(ext[i].name, "mobox-map") == 0) { const struct mobox_map_mail_index_record *orec = data; printf(" : offset = %u\n", orec->offset); printf(" : size = %u\n", orec->size); printf(" : oid = %s\n", guid_128_to_string(orec->oid)); } else if (strcmp(ext[i].name, "list") == 0) { const struct mailbox_list_index_record *lrec = data; printf(" : name_id = %u\n", lrec->name_id); printf(" : parent_uid = %u\n", lrec->parent_uid); printf(" : guid = %s\n", guid_128_to_string(lrec->guid)); printf(" : uid_validity = %u\n", lrec->uid_validity); } else if (strcmp(ext[i].name, "msgs") == 0) { const struct mailbox_list_index_msgs_record *lrec = data; printf(" : messages = %u\n", lrec->messages); printf(" : unseen = %u\n", lrec->unseen); printf(" : recent = %u\n", lrec->recent); printf(" : uidnext = %u\n", lrec->uidnext); } else if (strcmp(ext[i].name, "vsize") == 0 && ext[i].record_size >= sizeof(struct mailbox_index_vsize)) { /* this is "vsize" in dovecot.list.index, not the 32bit "vsize" in dovecot.index */ const struct mailbox_index_vsize *vrec = data; printf(" : vsize = %"PRIu64"\n", vrec->vsize); printf(" : highest_uid = %u\n", vrec->highest_uid); printf(" : message_count = %u\n", vrec->message_count); } } } static bool dir_has_index(const char *dir, const char *name) { struct stat st; return stat(t_strconcat(dir, "/", name, NULL), &st) == 0 || stat(t_strconcat(dir, "/", name, ".log", NULL), &st) == 0; } static struct mail_index *path_open_index(const char *path) { struct stat st; const char *p; if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { if (dir_has_index(path, "dovecot.index")) return mail_index_alloc(NULL, path, "dovecot.index"); else if (dir_has_index(path, "dovecot.map.index")) return mail_index_alloc(NULL, path, "dovecot.map.index"); else return NULL; } else if ((p = strrchr(path, '/')) != NULL) return mail_index_alloc(NULL, t_strdup_until(path, p), p + 1); else return mail_index_alloc(NULL, ".", path); } static void cmd_dump_index(const char *path, const char *const *args) { struct mail_index *index; struct mail_index_view *view; struct mail_cache_view *cache_view; unsigned int seq, uid = 0; index = path_open_index(path); if (index == NULL || mail_index_open(index, MAIL_INDEX_OPEN_FLAG_READONLY) <= 0) i_fatal("Couldn't open index %s", path); if (args[0] != NULL) { if (str_to_uint(args[0], &uid) < 0) i_fatal("Invalid uid number %s", args[0]); } view = mail_index_view_open(index); cache_view = mail_cache_view_open(index->cache, view); if (uid == 0) { printf("-- INDEX: %s\n", index->filepath); dump_hdr(index); dump_extensions(index); dump_keywords(index); printf("\n-- CACHE: %s\n", index->cache->filepath); dump_cache_hdr(index->cache); printf("\n-- RECORDS: %u\n", index->map->hdr.messages_count); } for (seq = 1; seq <= index->map->hdr.messages_count; seq++) { if (uid == 0 || mail_index_lookup(view, seq)->uid == uid) { T_BEGIN { dump_record(view, seq); dump_cache(cache_view, seq); printf("\n"); } T_END; } } mail_cache_view_close(&cache_view); mail_index_view_close(&view); mail_index_close(index); mail_index_free(&index); } static bool test_dump_index(const char *path) { struct mail_index *index; bool ret; index = path_open_index(path); if (index == NULL) return FALSE; ret = mail_index_open(index, MAIL_INDEX_OPEN_FLAG_READONLY) > 0; if (ret) mail_index_close(index); mail_index_free(&index); return ret; } struct doveadm_cmd_dump doveadm_cmd_dump_index = { "index", test_dump_index, cmd_dump_index }; dovecot-2.3.21.1/src/doveadm/doveadm-settings.c0000644000000000000000000002203514656633576016146 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "var-expand.h" #include "settings-parser.h" #include "service-settings.h" #include "mail-storage-settings.h" #include "master-service.h" #include "master-service-settings.h" #include "master-service-ssl-settings.h" #include "iostream-ssl.h" #include "doveadm-settings.h" ARRAY_TYPE(doveadm_setting_root) doveadm_setting_roots; bool doveadm_verbose_proctitle; static pool_t doveadm_settings_pool = NULL; static bool doveadm_settings_check(void *_set, pool_t pool, const char **error_r); /* */ static struct file_listener_settings doveadm_unix_listeners_array[] = { { "doveadm-server", 0600, "", "" } }; static struct file_listener_settings *doveadm_unix_listeners[] = { &doveadm_unix_listeners_array[0] }; static buffer_t doveadm_unix_listeners_buf = { { { doveadm_unix_listeners, sizeof(doveadm_unix_listeners) } } }; /* */ struct service_settings doveadm_service_settings = { .name = "doveadm", .protocol = "", .type = "", .executable = "doveadm-server", .user = "", .group = "", .privileged_group = "", .extra_groups = "$default_internal_group", .chroot = "", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 0, .client_limit = 1, .service_count = 1, .idle_kill = 0, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &doveadm_unix_listeners_buf, sizeof(doveadm_unix_listeners[0]) } }, .fifo_listeners = ARRAY_INIT, .inet_listeners = ARRAY_INIT }; #undef DEF #define DEF(type, name) \ SETTING_DEFINE_STRUCT_##type(#name, name, struct doveadm_settings) static const struct setting_define doveadm_setting_defines[] = { DEF(STR, base_dir), DEF(STR, libexec_dir), DEF(STR, mail_plugins), DEF(STR, mail_plugin_dir), DEF(STR_VARS, mail_temp_dir), DEF(BOOL, auth_debug), DEF(STR, auth_socket_path), DEF(STR, doveadm_socket_path), DEF(UINT, doveadm_worker_count), DEF(IN_PORT, doveadm_port), { .type = SET_ALIAS, .key = "doveadm_proxy_port" }, DEF(ENUM, doveadm_ssl), DEF(STR, doveadm_username), DEF(STR, doveadm_password), DEF(STR, doveadm_allowed_commands), DEF(STR, dsync_alt_char), DEF(STR, dsync_remote_cmd), DEF(STR, director_username_hash), DEF(STR, doveadm_api_key), DEF(STR, dsync_features), DEF(UINT, dsync_commit_msgs_interval), DEF(STR, doveadm_http_rawlog_dir), DEF(STR, dsync_hashed_headers), { .type = SET_STRLIST, .key = "plugin", .offset = offsetof(struct doveadm_settings, plugin_envs) }, SETTING_DEFINE_LIST_END }; const struct doveadm_settings doveadm_default_settings = { .base_dir = PKG_RUNDIR, .libexec_dir = PKG_LIBEXECDIR, .mail_plugins = "", .mail_plugin_dir = MODULEDIR, .mail_temp_dir = "/tmp", .auth_debug = FALSE, .auth_socket_path = "auth-userdb", .doveadm_socket_path = "doveadm-server", .doveadm_worker_count = 0, .doveadm_port = 0, .doveadm_ssl = "no:ssl:starttls", .doveadm_username = "doveadm", .doveadm_password = "", .doveadm_allowed_commands = "", .dsync_alt_char = "_", .dsync_remote_cmd = "ssh -l%{login} %{host} doveadm dsync-server -u%u -U", .dsync_features = "", .dsync_hashed_headers = "Date Message-ID", .dsync_commit_msgs_interval = 100, .director_username_hash = "%Lu", .doveadm_api_key = "", .doveadm_http_rawlog_dir = "", .plugin_envs = ARRAY_INIT }; static const struct setting_parser_info *doveadm_setting_dependencies[] = { &mail_user_setting_parser_info, NULL }; const struct setting_parser_info doveadm_setting_parser_info = { .module_name = "doveadm", .defines = doveadm_setting_defines, .defaults = &doveadm_default_settings, .type_offset = SIZE_MAX, .struct_size = sizeof(struct doveadm_settings), .parent_offset = SIZE_MAX, .check_func = doveadm_settings_check, .dependencies = doveadm_setting_dependencies }; struct doveadm_settings *doveadm_settings; const struct master_service_settings *service_set; static void fix_base_path(struct doveadm_settings *set, pool_t pool, const char **str) { if (*str != NULL && **str != '\0' && **str != '/') *str = p_strconcat(pool, set->base_dir, "/", *str, NULL); } /* */ struct dsync_feature_list { const char *name; enum dsync_features num; }; static const struct dsync_feature_list dsync_feature_list[] = { { "empty-header-workaround", DSYNC_FEATURE_EMPTY_HDR_WORKAROUND }, { "no-header-hashes", DSYNC_FEATURE_NO_HEADER_HASHES }, { NULL, 0 } }; static int dsync_settings_parse_features(struct doveadm_settings *set, const char **error_r) { enum dsync_features features = 0; const struct dsync_feature_list *list; const char *const *str; str = t_strsplit_spaces(set->dsync_features, " ,"); for (; *str != NULL; str++) { list = dsync_feature_list; for (; list->name != NULL; list++) { if (strcasecmp(*str, list->name) == 0) { features |= list->num; break; } } if (list->name == NULL) { *error_r = t_strdup_printf("dsync_features: " "Unknown feature: %s", *str); return -1; } } set->parsed_features = features; return 0; } static bool doveadm_settings_check(void *_set, pool_t pool ATTR_UNUSED, const char **error_r) { struct doveadm_settings *set = _set; #ifndef CONFIG_BINARY fix_base_path(set, pool, &set->auth_socket_path); fix_base_path(set, pool, &set->doveadm_socket_path); #endif if (*set->dsync_hashed_headers == '\0') { *error_r = "dsync_hashed_headers must not be empty"; return FALSE; } if (*set->dsync_alt_char == '\0') { *error_r = "dsync_alt_char must not be empty"; return FALSE; } if (dsync_settings_parse_features(set, error_r) != 0) return FALSE; return TRUE; } /* */ const struct master_service_ssl_settings *doveadm_ssl_set = NULL; void doveadm_get_ssl_settings(struct ssl_iostream_settings *set_r, pool_t pool) { master_service_ssl_client_settings_to_iostream_set(doveadm_ssl_set, pool, set_r); } void doveadm_settings_expand(struct doveadm_settings *set, pool_t pool) { struct var_expand_table tab[] = { { '\0', NULL, NULL } }; const char *error; if (settings_var_expand(&doveadm_setting_parser_info, set, pool, tab, &error) <= 0) i_fatal("Failed to expand settings: %s", error); } void doveadm_read_settings(void) { static const struct setting_parser_info *default_set_roots[] = { &master_service_ssl_setting_parser_info, &doveadm_setting_parser_info, }; struct master_service_settings_input input; struct master_service_settings_output output; const struct doveadm_settings *set; struct doveadm_setting_root *root; ARRAY(const struct setting_parser_info *) set_roots; ARRAY_TYPE(const_string) module_names; void **sets; const char *error; t_array_init(&set_roots, N_ELEMENTS(default_set_roots) + array_count(&doveadm_setting_roots) + 1); array_append(&set_roots, default_set_roots, N_ELEMENTS(default_set_roots)); t_array_init(&module_names, 4); array_foreach_modifiable(&doveadm_setting_roots, root) { array_push_back(&module_names, &root->info->module_name); array_push_back(&set_roots, &root->info); } array_append_zero(&module_names); array_append_zero(&set_roots); i_zero(&input); input.roots = array_front(&set_roots); input.module = "doveadm"; input.extra_modules = array_front(&module_names); input.service = "doveadm"; input.preserve_user = TRUE; input.preserve_home = TRUE; if (master_service_settings_read(master_service, &input, &output, &error) < 0) i_fatal("Error reading configuration: %s", error); doveadm_settings_pool = pool_alloconly_create("doveadm settings", 1024); service_set = master_service_settings_get(master_service); service_set = settings_dup(&master_service_setting_parser_info, service_set, doveadm_settings_pool); doveadm_verbose_proctitle = service_set->verbose_proctitle; sets = master_service_settings_get_others(master_service); set = sets[1]; doveadm_settings = settings_dup(&doveadm_setting_parser_info, set, doveadm_settings_pool); doveadm_ssl_set = settings_dup(&master_service_ssl_setting_parser_info, master_service_ssl_settings_get(master_service), doveadm_settings_pool); doveadm_settings_expand(doveadm_settings, doveadm_settings_pool); doveadm_settings->parsed_features = set->parsed_features; /* copy this value by hand */ array_foreach_modifiable(&doveadm_setting_roots, root) { unsigned int idx = array_foreach_idx(&doveadm_setting_roots, root); root->settings = settings_dup(root->info, sets[2+idx], doveadm_settings_pool); } } void doveadm_setting_roots_add(const struct setting_parser_info *info) { struct doveadm_setting_root *root; root = array_append_space(&doveadm_setting_roots); root->info = info; } void *doveadm_setting_roots_get_settings(const struct setting_parser_info *info) { const struct doveadm_setting_root *root; array_foreach(&doveadm_setting_roots, root) { if (root->info == info) return root->settings; } i_panic("Failed to find settings for module %s", info->module_name); } void doveadm_settings_init(void) { i_array_init(&doveadm_setting_roots, 8); } void doveadm_settings_deinit(void) { array_free(&doveadm_setting_roots); pool_unref(&doveadm_settings_pool); } dovecot-2.3.21.1/src/doveadm/doveadm-mail-mailbox-status.c0000644000000000000000000002121114656633576020175 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "mail-namespace.h" #include "mail-storage.h" #include "mail-search.h" #include "doveadm-print.h" #include "doveadm-mail.h" #include "doveadm-mailbox-list-iter.h" #define ALL_STATUS_ITEMS \ (STATUS_MESSAGES | STATUS_RECENT | \ STATUS_UIDNEXT | STATUS_UIDVALIDITY | \ STATUS_UNSEEN | STATUS_HIGHESTMODSEQ) #define ALL_METADATA_ITEMS \ (MAILBOX_METADATA_VIRTUAL_SIZE | MAILBOX_METADATA_GUID | \ MAILBOX_METADATA_FIRST_SAVE_DATE) #define TOTAL_STATUS_ITEMS \ (STATUS_MESSAGES | STATUS_RECENT | STATUS_UNSEEN) #define TOTAL_METADATA_ITEMS \ (MAILBOX_METADATA_VIRTUAL_SIZE) struct status_cmd_context { struct doveadm_mail_cmd_context ctx; struct mail_search_args *search_args; enum mailbox_status_items status_items; enum mailbox_metadata_items metadata_items; struct mailbox_status total_status; struct mailbox_metadata total_metadata; bool total_sum:1; }; static void status_parse_fields(struct status_cmd_context *ctx, const char *const *fields) { if (*fields == NULL) i_fatal_status(EX_USAGE, "No status fields"); for (; *fields != NULL; fields++) { const char *field = *fields; if (strcmp(field, "all") == 0) { if (ctx->total_sum) { ctx->status_items |= TOTAL_STATUS_ITEMS; ctx->metadata_items |= TOTAL_METADATA_ITEMS; } else { ctx->status_items |= ALL_STATUS_ITEMS; ctx->metadata_items |= ALL_METADATA_ITEMS; } } else if (strcmp(field, "messages") == 0) ctx->status_items |= STATUS_MESSAGES; else if (strcmp(field, "recent") == 0) ctx->status_items |= STATUS_RECENT; else if (strcmp(field, "uidnext") == 0) ctx->status_items |= STATUS_UIDNEXT; else if (strcmp(field, "uidvalidity") == 0) ctx->status_items |= STATUS_UIDVALIDITY; else if (strcmp(field, "unseen") == 0) ctx->status_items |= STATUS_UNSEEN; else if (strcmp(field, "highestmodseq") == 0) ctx->status_items |= STATUS_HIGHESTMODSEQ; else if (strcmp(field, "vsize") == 0) ctx->metadata_items |= MAILBOX_METADATA_VIRTUAL_SIZE; else if (strcmp(field, "guid") == 0) ctx->metadata_items |= MAILBOX_METADATA_GUID; else if (strcmp(field, "firstsaved") == 0) ctx->metadata_items |= MAILBOX_METADATA_FIRST_SAVE_DATE; else { i_fatal_status(EX_USAGE, "Unknown status field: %s", field); } if (ctx->total_sum && ((ctx->status_items & ENUM_NEGATE(TOTAL_STATUS_ITEMS)) != 0 || (ctx->metadata_items & ENUM_NEGATE(TOTAL_METADATA_ITEMS)) != 0)) { i_fatal_status(EX_USAGE, "Status field %s can't be used with -t", field); } } } static void ATTR_NULL(2) status_output(struct status_cmd_context *ctx, struct mailbox *box, const struct mailbox_status *status, const struct mailbox_metadata *metadata) { if (box != NULL) doveadm_print(mailbox_get_vname(box)); if ((ctx->status_items & STATUS_MESSAGES) != 0) doveadm_print_num(status->messages); if ((ctx->status_items & STATUS_RECENT) != 0) doveadm_print_num(status->recent); if ((ctx->status_items & STATUS_UIDNEXT) != 0) doveadm_print_num(status->uidnext); if ((ctx->status_items & STATUS_UIDVALIDITY) != 0) doveadm_print_num(status->uidvalidity); if ((ctx->status_items & STATUS_UNSEEN) != 0) doveadm_print_num(status->unseen); if ((ctx->status_items & STATUS_HIGHESTMODSEQ) != 0) doveadm_print_num(status->highest_modseq); if ((ctx->metadata_items & MAILBOX_METADATA_VIRTUAL_SIZE) != 0) doveadm_print_num(metadata->virtual_size); if ((ctx->metadata_items & MAILBOX_METADATA_GUID) != 0) doveadm_print(guid_128_to_string(metadata->guid)); if ((ctx->metadata_items & MAILBOX_METADATA_FIRST_SAVE_DATE) > 0) { if (metadata->first_save_date > -1) doveadm_print_num(metadata->first_save_date); else doveadm_print("never"); } } static void status_sum(struct status_cmd_context *ctx, const struct mailbox_status *status, const struct mailbox_metadata *metadata) { struct mailbox_status *dest = &ctx->total_status; dest->messages += status->messages; dest->recent += status->recent; dest->unseen += status->unseen; ctx->total_metadata.virtual_size += metadata->virtual_size; } static int status_mailbox(struct status_cmd_context *ctx, const struct mailbox_info *info) { struct mailbox *box; struct mailbox_status status; struct mailbox_metadata metadata; box = doveadm_mailbox_find(ctx->ctx.cur_mail_user, info->vname); if (mailbox_get_status(box, ctx->status_items, &status) < 0 || mailbox_get_metadata(box, ctx->metadata_items, &metadata) < 0) { i_error("Mailbox %s: Failed to lookup mailbox status: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(&ctx->ctx, box); mailbox_free(&box); return -1; } if (!ctx->total_sum) status_output(ctx, box, &status, &metadata); else status_sum(ctx, &status, &metadata); mailbox_free(&box); return 0; } static int cmd_mailbox_status_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct status_cmd_context *ctx = (struct status_cmd_context *)_ctx; enum mailbox_list_iter_flags iter_flags = MAILBOX_LIST_ITER_NO_AUTO_BOXES | MAILBOX_LIST_ITER_RETURN_NO_FLAGS; struct doveadm_mailbox_list_iter *iter; const struct mailbox_info *info; int ret = 0; i_zero(&ctx->total_status); i_zero(&ctx->total_metadata); iter = doveadm_mailbox_list_iter_init(_ctx, user, ctx->search_args, iter_flags); while ((info = doveadm_mailbox_list_iter_next(iter)) != NULL) { T_BEGIN { if (status_mailbox(ctx, info) < 0) ret = -1; } T_END; } if (doveadm_mailbox_list_iter_deinit(&iter) < 0) ret = -1; if (ctx->total_sum) { status_output(ctx, NULL, &ctx->total_status, &ctx->total_metadata); } return ret; } static void cmd_mailbox_status_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct status_cmd_context *ctx = (struct status_cmd_context *)_ctx; const char *fields = args[0]; if (fields == NULL || args[1] == NULL) doveadm_mail_help_name("mailbox status"); status_parse_fields(ctx, t_strsplit_spaces(fields, " ")); ctx->search_args = doveadm_mail_mailbox_search_args_build(args+1); if (!ctx->total_sum) { doveadm_print_header("mailbox", "mailbox", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); } if ((ctx->status_items & STATUS_MESSAGES) != 0) doveadm_print_header_simple("messages"); if ((ctx->status_items & STATUS_RECENT) != 0) doveadm_print_header_simple("recent"); if ((ctx->status_items & STATUS_UIDNEXT) != 0) doveadm_print_header_simple("uidnext"); if ((ctx->status_items & STATUS_UIDVALIDITY) != 0) doveadm_print_header_simple("uidvalidity"); if ((ctx->status_items & STATUS_UNSEEN) != 0) doveadm_print_header_simple("unseen"); if ((ctx->status_items & STATUS_HIGHESTMODSEQ) != 0) doveadm_print_header_simple("highestmodseq"); if ((ctx->metadata_items & MAILBOX_METADATA_VIRTUAL_SIZE) != 0) doveadm_print_header_simple("vsize"); if ((ctx->metadata_items & MAILBOX_METADATA_GUID) != 0) doveadm_print_header_simple("guid"); if ((ctx->metadata_items & MAILBOX_METADATA_FIRST_SAVE_DATE) != 0) doveadm_print_header_simple("firstsaved"); } static void cmd_mailbox_status_deinit(struct doveadm_mail_cmd_context *_ctx) { struct status_cmd_context *ctx = (struct status_cmd_context *)_ctx; if (ctx->search_args != NULL) mail_search_args_unref(&ctx->search_args); } static bool cmd_mailbox_status_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct status_cmd_context *ctx = (struct status_cmd_context *)_ctx; switch (c) { case 't': ctx->total_sum = TRUE; break; case 'f': break; default: return FALSE; } return TRUE; } static struct doveadm_mail_cmd_context *cmd_mailbox_status_alloc(void) { struct status_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct status_cmd_context); ctx->ctx.getopt_args = "t"; ctx->ctx.v.parse_arg = cmd_mailbox_status_parse_arg; ctx->ctx.v.init = cmd_mailbox_status_init; ctx->ctx.v.deinit = cmd_mailbox_status_deinit; ctx->ctx.v.run = cmd_mailbox_status_run; doveadm_print_init(DOVEADM_PRINT_TYPE_FLOW); return &ctx->ctx; } struct doveadm_cmd_ver2 doveadm_cmd_mailbox_status_ver2 = { .name = "mailbox status", .mail_cmd = cmd_mailbox_status_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX" [...]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('t', "total-sum", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('f', "field", CMD_PARAM_ARRAY, 0) DOVEADM_CMD_PARAM('\0', "fieldstr", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL | CMD_PARAM_FLAG_DO_NOT_EXPOSE) /* FIXME: horrible hack, remove me when possible */ DOVEADM_CMD_PARAM('\0', "mailbox-mask", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-sis.c0000644000000000000000000002006514656633576015105 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "hex-binary.h" #include "hostpid.h" #include "randgen.h" #include "read-full.h" #include "fs-sis-common.h" #include "doveadm.h" #include "doveadm-print.h" #include #include #include #include #include /* Files are in /ha/sh/- They may be hard linked to hashes/ */ static const char *sis_get_dir(const char *rootdir, const char *hash) { if (strlen(hash) < 4 || strchr(hash, '/') != NULL) i_fatal("Invalid hash in filename: %s", hash); return t_strdup_printf("%s/%c%c/%c%c", rootdir, hash[0], hash[1], hash[2], hash[3]); } static int file_contents_equal(const char *path1, const char *path2, ino_t *path2_inode_r) { struct stat st1, st2; int fd1, fd2, ret = -1; *path2_inode_r = 0; /* do a byte-by-byte comparison for the files to find out if they're the same or if this is a hash collision */ fd1 = open(path1, O_RDONLY); if (fd1 == -1) { if (errno != ENOENT) i_error("open(%s) failed: %m", path1); return -1; } fd2 = open(path2, O_RDONLY); if (fd2 == -1) { if (errno != ENOENT) i_error("open(%s) failed: %m", path2); i_close_fd(&fd1); return -1; } if (fstat(fd1, &st1) < 0) i_error("fstat(%s) failed: %m", path1); else if (fstat(fd2, &st2) < 0) i_error("fstat(%s) failed: %m", path1); else if (st1.st_size != st2.st_size) ret = 0; else { /* @UNSAFE: sizes match. compare. */ unsigned char buf1[IO_BLOCK_SIZE], buf2[IO_BLOCK_SIZE]; ssize_t ret1; int ret2; while ((ret1 = read(fd1, buf1, sizeof(buf1))) > 0) { i_assert((size_t)ret1 <= sizeof(buf2)); if ((ret2 = read_full(fd2, buf2, ret1)) <= 0) { if (ret2 < 0) i_error("read(%s) failed: %m", path2); else ret = 0; break; } if (memcmp(buf1, buf2, ret1) != 0) { ret = 0; break; } } if (ret1 < 0) i_error("read(%s) failed: %m", path1); else if (ret1 == 0) ret = 1; *path2_inode_r = st2.st_ino; } if (close(fd1) < 0) i_error("close(%s) failed: %m", path1); if (close(fd2) < 0) i_error("close(%s) failed: %m", path2); return ret; } static int hardlink_replace(const char *src, const char *dest, ino_t src_inode) { const char *p, *destdir, *tmppath; unsigned char randbuf[8]; struct stat st; p = strrchr(dest, '/'); i_assert(p != NULL); destdir = t_strdup_until(dest, p); random_fill(randbuf, sizeof(randbuf)); tmppath = t_strdup_printf("%s/temp.%s.%s.%s", destdir, my_hostname, my_pid, binary_to_hex(randbuf, sizeof(randbuf))); if (link(src, tmppath) < 0) { if (errno == EMLINK) return 0; i_error("link(%s, %s) failed: %m", src, tmppath); return -1; } if (stat(tmppath, &st) < 0) { i_error("stat(%s) failed: %m", tmppath); return -1; } if (st.st_ino != src_inode) { i_unlink(tmppath); return 0; } if (rename(tmppath, dest) < 0) { i_error("rename(%s, %s) failed: %m", src, tmppath); i_unlink(tmppath); return -1; } return 1; } static int sis_try_deduplicate(const char *rootdir, const char *fname) { const char *p, *hash, *hashdir, *path, *hashes_dir, *hashes_path; struct stat st; ino_t inode; int ret; /* fname should be in - format */ p = strchr(fname, '-'); i_assert(p != NULL); hash = t_strdup_until(fname, p); hashdir = sis_get_dir(rootdir, hash); path = t_strdup_printf("%s/%s", hashdir, fname); hashes_dir = t_strconcat(hashdir, "/", HASH_DIR_NAME, NULL); hashes_path = t_strconcat(hashes_dir, "/", hash, NULL); if (link(path, hashes_path) == 0) { /* first file with this hash. we're done */ return 0; } if (errno == ENOENT) { /* either path was already deleted or hashes dir doesn't exist */ if (mkdir(hashes_dir, 0700) < 0) { if (errno == EEXIST) return 0; i_error("mkdir(%s) failed: %m", hashes_dir); return -1; } /* try again */ if (link(path, hashes_path) == 0 || errno == ENOENT) return 0; } if (errno != EEXIST) { i_error("link(%s, %s) failed: %m", path, hashes_path); return -1; } /* need to do a byte-by-byte comparison. but check first if someone else already had deduplicated the file. */ if (stat(path, &st) < 0) { if (errno == ENOENT) { /* just got deleted */ return 0; } i_error("stat(%s) failed: %m", path); return -1; } if (st.st_nlink > 1) { /* already deduplicated */ return 0; } ret = file_contents_equal(path, hashes_path, &inode); if (ret < 0) { if (errno == ENOENT) { /* either path or hashes_path was deleted. */ return sis_try_deduplicate(rootdir, fname); } return -1; } if (ret > 0) { /* equal, replace with hard link */ ret = hardlink_replace(hashes_path, path, inode); if (ret > 0) return 0; else if (ret < 0) return -1; /* too many hard links or inode changed */ } /* replace hashes link with this */ return hardlink_replace(path, hashes_path, st.st_ino) < 0 ? -1 : 0; } static void cmd_sis_deduplicate(struct doveadm_cmd_context *cctx) { const char *rootdir, *queuedir; DIR *dir; struct dirent *d; struct stat st, first_st; string_t *path; size_t dir_len; int ret; if (!doveadm_cmd_param_str(cctx, "root-dir", &rootdir) || !doveadm_cmd_param_str(cctx, "queue-dir", &queuedir)) help_ver2(&doveadm_cmd_sis_deduplicate); /* go through the filenames in the queue dir and see if we can deduplicate them. */ if (stat(rootdir, &st) < 0) i_fatal("stat(%s) failed: %m", rootdir); path = t_str_new(256); str_append(path, queuedir); str_append_c(path, '/'); dir_len = str_len(path); dir = opendir(queuedir); if (dir == NULL) i_fatal("opendir(%s) failed: %m", queuedir); first_st.st_size = -1; while ((d = readdir(dir)) != NULL) { if (d->d_name[0] == '.') continue; str_truncate(path, dir_len); str_append(path, d->d_name); if (first_st.st_size < 0) { if (stat(str_c(path), &first_st) < 0) i_fatal("stat(%s) failed: %m", str_c(path)); } if (strchr(d->d_name, '-') == NULL || first_st.st_size != 0) { i_fatal("%s is not a valid sis-queue file, " "is the queue directory correct?", str_c(path)); } T_BEGIN { ret = sis_try_deduplicate(rootdir, d->d_name); } T_END; if (ret == 0) i_unlink(str_c(path)); } if (closedir(dir) < 0) i_error("closedir(%s) failed: %m", queuedir); } static void cmd_sis_find(struct doveadm_cmd_context *cctx) { const char *rootdir, *path, *hash; DIR *dir; struct dirent *d; struct stat st; string_t *str; size_t dir_len, hash_len; if (!doveadm_cmd_param_str(cctx, "root-dir", &rootdir) || !doveadm_cmd_param_str(cctx, "hash", &hash) || strlen(hash) < 4) help_ver2(&doveadm_cmd_sis_find); if (stat(rootdir, &st) < 0) { if (errno == ENOENT) i_fatal("Attachment dir doesn't exist: %s", rootdir); i_fatal("stat(%s) failed: %m", rootdir); } hash_len = strlen(hash); path = sis_get_dir(rootdir, hash); str = t_str_new(256); str_append(str, path); str_append_c(str, '/'); dir_len = str_len(str); dir = opendir(path); if (dir == NULL) { if (errno == ENOENT) return; i_fatal("opendir(%s) failed: %m", path); } doveadm_print_init(DOVEADM_PRINT_TYPE_FLOW); doveadm_print_header("path", "path", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); while ((d = readdir(dir)) != NULL) { if (strncmp(d->d_name, hash, hash_len) == 0) { str_truncate(str, dir_len); str_append(str, d->d_name); doveadm_print(str_c(str)); } } if (closedir(dir) < 0) i_error("closedir(%s) failed: %m", path); } struct doveadm_cmd_ver2 doveadm_cmd_sis_deduplicate = { .name = "sis deduplicate", .cmd = cmd_sis_deduplicate, .usage = " ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', "root-dir", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "queue-dir", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_sis_find = { .name = "sis find", .cmd = cmd_sis_find, .usage = " ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', "root-dir", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "hash", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-dump-dcrypt-file.c0000644000000000000000000000474714656633576017505 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "dcrypt.h" #include "istream.h" #include "istream-decrypt.h" #include "dcrypt-iostream.h" #include "doveadm-dump.h" #include static int get_digest(const char *digest, struct dcrypt_private_key **priv_key_r ATTR_UNUSED, const char **error_r ATTR_UNUSED, void *context) { const char **digest_r = (const char**)context; *digest_r = t_strdup(digest); return 0; } static void dcrypt_istream_dump_metadata(const struct istream *stream) { enum io_stream_encrypt_flags flags = i_stream_encrypt_get_flags(stream); if ((flags & IO_STREAM_ENC_INTEGRITY_HMAC) != 0) printf("flags: IO_STREAM_ENC_INTEGRITY_HMAC\n"); if ((flags & IO_STREAM_ENC_INTEGRITY_AEAD) != 0) printf("flags: IO_STREAM_ENC_INTEGRITY_AEAD\n"); if ((flags & IO_STREAM_ENC_INTEGRITY_NONE) != 0) printf("flags: IO_STREAM_ENC_INTEGRITY_NONE\n"); if ((flags & IO_STREAM_ENC_VERSION_1) != 0) printf("flags: IO_STREAM_ENC_VERSION_1\n"); enum decrypt_istream_format format = i_stream_encrypt_get_format(stream); switch (format) { case DECRYPT_FORMAT_V1: printf("format: DECRYPT_FORMAT_V1\n"); break; case DECRYPT_FORMAT_V2: printf("format: DECRYPT_FORMAT_V2\n"); break; } } static bool dcrypt_file_dump_metadata(const char *filename, bool print) { bool ret = FALSE; struct istream *is = i_stream_create_file(filename, IO_BLOCK_SIZE); const char *key_digest = NULL; struct istream *ds = i_stream_create_decrypt_callback(is, get_digest, &key_digest); ssize_t size = i_stream_read(ds); i_assert(size < 0); if (key_digest != NULL) { ret = TRUE; if (print) { dcrypt_istream_dump_metadata(ds); printf("decrypt key digest: %s\n", key_digest); } } else if (print && ds->stream_errno != 0) { i_error("read(%s) failed: %s", i_stream_get_name(ds), i_stream_get_error(ds)); } i_stream_unref(&ds); i_stream_unref(&is); return ret; } static bool test_dump_dcrypt_file(const char *path) { if (!dcrypt_initialize("openssl", NULL, NULL)) return FALSE; bool ret = dcrypt_file_dump_metadata(path, FALSE); return ret; } static void cmd_dump_dcrypt_file(const char *path, const char *const *args ATTR_UNUSED) { const char *error = NULL; if (!dcrypt_initialize("openssl", NULL, &error)) i_fatal("dcrypt_initialize failed: %s", error); (void)dcrypt_file_dump_metadata(path, TRUE); } struct doveadm_cmd_dump doveadm_cmd_dump_dcrypt_file = { "dcrypt-file", test_dump_dcrypt_file, cmd_dump_dcrypt_file }; dovecot-2.3.21.1/src/doveadm/doveadm-mail-copymove.c0000644000000000000000000001564314656633576017076 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "mail-storage.h" #include "mail-namespace.h" #include "doveadm-print.h" #include "doveadm-mailbox-list-iter.h" #include "doveadm-mail-iter.h" #include "doveadm-mail.h" #include struct copy_cmd_context { struct doveadm_mail_cmd_context ctx; const char *source_username; struct mail_storage_service_user *source_service_user; struct mail_user *source_user; const char *destname; bool move; }; static int cmd_copy_box(struct copy_cmd_context *ctx, struct mailbox *destbox, const struct mailbox_info *info) { struct doveadm_mail_iter *iter; struct mailbox_transaction_context *desttrans; struct mail_save_context *save_ctx; struct mail *mail; int ret = 0, ret2; if (doveadm_mail_iter_init(&ctx->ctx, info, ctx->ctx.search_args, 0, NULL, 0, &iter) < 0) return -1; /* use a separately committed transaction for each mailbox. this guarantees that mails aren't expunged without actually having been copied. */ desttrans = mailbox_transaction_begin(destbox, MAILBOX_TRANSACTION_FLAG_EXTERNAL | ctx->ctx.transaction_flags, __func__); while (doveadm_mail_iter_next(iter, &mail)) { save_ctx = mailbox_save_alloc(desttrans); mailbox_save_copy_flags(save_ctx, mail); if (ctx->move) ret2 = mailbox_move(&save_ctx, mail); else ret2 = mailbox_copy(&save_ctx, mail); if (ret2 < 0) { i_error("%s message UID %u from '%s' failed: %s", ctx->move ? "Moving" : "Copying", mail->uid, info->vname, mailbox_get_last_internal_error(destbox, NULL)); doveadm_mail_failed_mailbox(&ctx->ctx, destbox); ret = -1; } } if (mailbox_transaction_commit(&desttrans) < 0) { i_error("Committing %s mails failed: %s", ctx->move ? "moved" : "copied", mailbox_get_last_internal_error(destbox, NULL)); doveadm_mail_failed_mailbox(&ctx->ctx, destbox); /* rollback expunges */ doveadm_mail_iter_deinit_rollback(&iter); ret = -1; } else { if (doveadm_mail_iter_deinit_sync(&iter) < 0) ret = -1; } return ret; } static void cmd_copy_alloc_source_user(struct copy_cmd_context *ctx) { struct mail_storage_service_input input; const char *error; input = ctx->ctx.storage_service_input; input.username = ctx->source_username; mail_storage_service_io_deactivate_user(ctx->ctx.cur_service_user); if (mail_storage_service_lookup_next(ctx->ctx.storage_service, &input, &ctx->source_service_user, &ctx->source_user, &error) < 0) i_fatal("Couldn't lookup user %s: %s", input.username, error); mail_storage_service_io_deactivate_user(ctx->source_service_user); mail_storage_service_io_activate_user(ctx->ctx.cur_service_user); } static int cmd_copy_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct copy_cmd_context *ctx = (struct copy_cmd_context *)_ctx; const enum mailbox_list_iter_flags iter_flags = MAILBOX_LIST_ITER_NO_AUTO_BOXES | MAILBOX_LIST_ITER_RETURN_NO_FLAGS; struct doveadm_mailbox_list_iter *iter; struct mail_user *src_user; struct mail_namespace *ns; struct mailbox *destbox; const struct mailbox_info *info; int ret = 0; if (ctx->source_username != NULL && ctx->source_user == NULL) cmd_copy_alloc_source_user(ctx); ns = mail_namespace_find(user->namespaces, ctx->destname); destbox = mailbox_alloc(ns->list, ctx->destname, MAILBOX_FLAG_SAVEONLY); if (mailbox_open(destbox) < 0) { i_error("Can't open mailbox '%s': %s", ctx->destname, mailbox_get_last_internal_error(destbox, NULL)); doveadm_mail_failed_mailbox(&ctx->ctx, destbox); mailbox_free(&destbox); return -1; } src_user = ctx->source_user != NULL ? ctx->source_user : user; iter = doveadm_mailbox_list_iter_init(_ctx, src_user, _ctx->search_args, iter_flags); while ((info = doveadm_mailbox_list_iter_next(iter)) != NULL) T_BEGIN { if (cmd_copy_box(ctx, destbox, info) < 0) ret = -1; } T_END; if (doveadm_mailbox_list_iter_deinit(&iter) < 0) ret = -1; if (mailbox_sync(destbox, 0) < 0) { i_error("Syncing mailbox '%s' failed: %s", ctx->destname, mailbox_get_last_internal_error(destbox, NULL)); doveadm_mail_failed_mailbox(&ctx->ctx, destbox); ret = -1; } mailbox_free(&destbox); return ret; } static void cmd_copy_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct copy_cmd_context *ctx = (struct copy_cmd_context *)_ctx; const char *destname = args[0], *cmdname = ctx->move ? "move" : "copy"; if (destname == NULL || args[1] == NULL) doveadm_mail_help_name(cmdname); args++; if (args[0] != NULL && args[1] != NULL && strcasecmp(args[0], "user") == 0) { if ((_ctx->service_flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) == 0) i_fatal("Use -u parameter to specify destination user"); ctx->source_username = p_strdup(_ctx->pool, args[1]); args += 2; } ctx->destname = p_strdup(ctx->ctx.pool, destname); _ctx->search_args = doveadm_mail_build_search_args(args); if (ctx->move) expunge_search_args_check(ctx->ctx.search_args, cmdname); } static void cmd_copy_deinit(struct doveadm_mail_cmd_context *_ctx) { struct copy_cmd_context *ctx = (struct copy_cmd_context *)_ctx; if (ctx->source_user != NULL) { mail_storage_service_user_unref(&ctx->source_service_user); mail_user_deinit(&ctx->source_user); } } static struct doveadm_mail_cmd_context *cmd_copy_alloc(void) { struct copy_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct copy_cmd_context); ctx->ctx.v.init = cmd_copy_init; ctx->ctx.v.deinit = cmd_copy_deinit; ctx->ctx.v.run = cmd_copy_run; doveadm_print_init(DOVEADM_PRINT_TYPE_FLOW); return &ctx->ctx; } static struct doveadm_mail_cmd_context *cmd_move_alloc(void) { struct copy_cmd_context *ctx; ctx = (struct copy_cmd_context *)cmd_copy_alloc(); ctx->move = TRUE; return &ctx->ctx; } struct doveadm_cmd_ver2 doveadm_cmd_copy_ver2 = { .name = "copy", .mail_cmd = cmd_copy_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX " [user ] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('\0', "destination-mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "source-type", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "source-user", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_move_ver2 = { .name = "move", .mail_cmd = cmd_move_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX " [user ] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('\0', "destination-mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "source-type", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "source-user", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-penalty.c0000644000000000000000000000644614656633576015772 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "net.h" #include "istream.h" #include "hash.h" #include "strescape.h" #include "time-util.h" #include "doveadm.h" #include "doveadm-print.h" #include struct penalty_line { struct ip_addr ip; unsigned int penalty; time_t last_penalty, last_update; }; struct penalty_context { const char *anvil_path; struct ip_addr net_ip; unsigned int net_bits; }; static void penalty_parse_line(const char *line, struct penalty_line *line_r) { const char *const *args = t_strsplit_tabescaped(line); const char *ident = args[0]; const char *penalty_str = args[1]; const char *last_penalty_str = args[2]; const char *last_update_str = args[3]; i_zero(line_r); (void)net_addr2ip(ident, &line_r->ip); if (str_to_uint(penalty_str, &line_r->penalty) < 0 || str_to_time(last_penalty_str, &line_r->last_penalty) < 0 || str_to_time(last_update_str, &line_r->last_update) < 0) i_fatal("Read invalid penalty line: %s", line); } static void penalty_print_line(struct penalty_context *ctx, const struct penalty_line *line) { if (ctx->net_bits > 0) { if (!net_is_in_network(&line->ip, &ctx->net_ip, ctx->net_bits)) return; } doveadm_print(net_ip2addr(&line->ip)); doveadm_print(dec2str(line->penalty)); doveadm_print(unixdate2str(line->last_penalty)); doveadm_print(t_strflocaltime("%H:%M:%S", line->last_update)); } static void penalty_lookup(struct penalty_context *ctx) { #define ANVIL_HANDSHAKE "VERSION\tanvil\t1\t0\n" #define ANVIL_CMD ANVIL_HANDSHAKE"PENALTY-DUMP\n" struct istream *input; const char *line; int fd; fd = doveadm_connect(ctx->anvil_path); net_set_nonblock(fd, FALSE); if (write(fd, ANVIL_CMD, strlen(ANVIL_CMD)) < 0) i_fatal("write(%s) failed: %m", ctx->anvil_path); input = i_stream_create_fd_autoclose(&fd, SIZE_MAX); while ((line = i_stream_read_next_line(input)) != NULL) { if (*line == '\0') break; T_BEGIN { struct penalty_line penalty_line; penalty_parse_line(line, &penalty_line); penalty_print_line(ctx, &penalty_line); } T_END; } if (input->stream_errno != 0) { i_fatal("read(%s) failed: %s", ctx->anvil_path, i_stream_get_error(input)); } i_stream_destroy(&input); } static void cmd_penalty(struct doveadm_cmd_context *cctx) { struct penalty_context ctx; const char *netmask; i_zero(&ctx); if (!doveadm_cmd_param_str(cctx, "socket-path", &(ctx.anvil_path))) ctx.anvil_path = t_strconcat(doveadm_settings->base_dir, "/anvil", NULL); if (doveadm_cmd_param_str(cctx, "netmask", &netmask)) { if (net_parse_range(netmask, &ctx.net_ip, &ctx.net_bits) != 0) { doveadm_exit_code = EX_USAGE; i_error("Invalid netmask '%s' given", netmask); return; } } doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); doveadm_print_header_simple("IP"); doveadm_print_header_simple("penalty"); doveadm_print_header_simple("last_penalty"); doveadm_print_header_simple("last_update"); penalty_lookup(&ctx); } struct doveadm_cmd_ver2 doveadm_cmd_penalty_ver2 = { .name = "penalty", .cmd = cmd_penalty, .usage = "[-a ] []", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a',"socket-path", CMD_PARAM_STR,0) DOVEADM_CMD_PARAM('\0',"netmask", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-proxy.c0000644000000000000000000001240314656633576015465 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "str.h" #include "strescape.h" #include "ipc-client.h" #include "doveadm.h" #include "doveadm-print.h" #include #include struct proxy_context { struct ipc_client *ipc; const char *username_field; const char *kick_hosts; }; extern struct doveadm_cmd_ver2 doveadm_cmd_proxy[]; static void proxy_cmd_help(struct doveadm_cmd_context *cctx) ATTR_NORETURN; static struct proxy_context * cmd_proxy_init(struct doveadm_cmd_context *cctx) { struct proxy_context *ctx; const char *socket_path; ctx = t_new(struct proxy_context, 1); if (!doveadm_cmd_param_str(cctx, "socket-path", &socket_path)) { socket_path = t_strconcat(doveadm_settings->base_dir, "/ipc", NULL); } (void)doveadm_cmd_param_str(cctx, "passdb-field", &ctx->username_field); (void)doveadm_cmd_param_str(cctx, "host", &ctx->kick_hosts); ctx->ipc = ipc_client_init(socket_path); return ctx; } static void cmd_proxy_list_header(const char *const *args) { struct { const char *key; const char *title; } header_map[] = { { "service", "proto" }, { "src-ip", "src ip" }, { "dest-ip", "dest ip" }, { "dest-port", "port" }, }; for (unsigned int i = 0; args[i] != NULL; i++) { const char *arg = args[i]; if (strcmp(arg, "username") == 0 || str_begins(arg, "user_")) { doveadm_print_header(arg, arg, DOVEADM_PRINT_HEADER_FLAG_EXPAND); continue; } const char *title = arg; for (unsigned int j = 0; j < N_ELEMENTS(header_map); j++) { if (strcmp(header_map[j].key, arg) == 0) { title = header_map[j].title; break; } } doveadm_print_header(arg, title, 0); } } static void cmd_proxy_list_callback(enum ipc_client_cmd_state state, const char *data, void *context) { bool *seen_header = context; switch (state) { case IPC_CLIENT_CMD_STATE_REPLY: { const char *const *args = t_strsplit_tabescaped(data); if (!*seen_header) { cmd_proxy_list_header(args); *seen_header = TRUE; } else { for (; *args != NULL; args++) doveadm_print(*args); } return; } case IPC_CLIENT_CMD_STATE_OK: break; case IPC_CLIENT_CMD_STATE_ERROR: i_error("LIST-FULL failed: %s", data); break; } io_loop_stop(current_ioloop); } static void cmd_proxy_list(struct doveadm_cmd_context *cctx) { struct proxy_context *ctx; bool seen_header = FALSE; ctx = cmd_proxy_init(cctx); doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); io_loop_set_running(current_ioloop); ipc_client_cmd(ctx->ipc, "proxy\t*\tLIST-FULL", cmd_proxy_list_callback, &seen_header); if (io_loop_is_running(current_ioloop)) io_loop_run(current_ioloop); ipc_client_deinit(&ctx->ipc); } static void cmd_proxy_kick_callback(enum ipc_client_cmd_state state, const char *data, void *context ATTR_UNUSED) { switch (state) { case IPC_CLIENT_CMD_STATE_REPLY: return; case IPC_CLIENT_CMD_STATE_OK: if (data[0] == '\0') data = "0"; doveadm_print(data); break; case IPC_CLIENT_CMD_STATE_ERROR: i_error("KICK failed: %s", data); doveadm_exit_code = EX_TEMPFAIL; break; } io_loop_stop(current_ioloop); } static void cmd_proxy_kick(struct doveadm_cmd_context *cctx) { struct proxy_context *ctx; const char *const *users = NULL; string_t *cmd; ctx = cmd_proxy_init(cctx); (void)doveadm_cmd_param_array(cctx, "user", &users); if (users == NULL && ctx->kick_hosts == NULL) { proxy_cmd_help(cctx); return; } doveadm_print_init(DOVEADM_PRINT_TYPE_FORMATTED); doveadm_print_formatted_set_format("%{count} connections kicked\n"); doveadm_print_header_simple("count"); cmd = t_str_new(128); str_append(cmd, "proxy\t*\t"); if (ctx->kick_hosts != NULL) { str_append(cmd, "KICK-HOST\t"); str_append(cmd, ctx->kick_hosts); } else if (ctx->username_field == NULL) str_append(cmd, "KICK"); else { str_append(cmd, "KICK-ALT\t"); str_append_tabescaped(cmd, ctx->username_field); } if (users != NULL) { for (unsigned int i = 0; users[i] != NULL; i++) { str_append_c(cmd, '\t'); str_append_tabescaped(cmd, users[i]); } } ipc_client_cmd(ctx->ipc, str_c(cmd), cmd_proxy_kick_callback, NULL); io_loop_run(current_ioloop); ipc_client_deinit(&ctx->ipc); } struct doveadm_cmd_ver2 doveadm_cmd_proxy[] = { { .name = "proxy list", .usage = "[-a ]", .cmd = cmd_proxy_list, DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAMS_END }, { .name = "proxy kick", .usage = "[-a ] [-f ] [-h [...] | [...]]", .cmd = cmd_proxy_kick, DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('f', "passdb-field", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('h', "host", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "user", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END } }; static void proxy_cmd_help(struct doveadm_cmd_context *cctx) { unsigned int i; for (i = 0; i < N_ELEMENTS(doveadm_cmd_proxy); i++) { if (doveadm_cmd_proxy[i].cmd == cctx->cmd->cmd) help_ver2(&doveadm_cmd_proxy[i]); } i_unreached(); } void doveadm_register_proxy_commands(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(doveadm_cmd_proxy); i++) doveadm_cmd_register_ver2(&doveadm_cmd_proxy[i]); } dovecot-2.3.21.1/src/doveadm/client-connection-tcp.c0000644000000000000000000003623614656633576017100 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "lib-signals.h" #include "str.h" #include "base64.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "strescape.h" #include "iostream-ssl.h" #include "ostream-multiplex.h" #include "master-service.h" #include "master-service-ssl.h" #include "mail-storage-service.h" #include "doveadm-util.h" #include "doveadm-mail.h" #include "doveadm-print.h" #include "doveadm-server.h" #include "client-connection-private.h" #include #define MAX_INBUF_SIZE (1024*1024) struct client_connection_tcp { struct client_connection conn; int fd; struct io *io; struct istream *input; struct ostream *output; struct ostream *log_out; struct ssl_iostream *ssl_iostream; struct ioloop *ioloop; bool handshaked:1; bool preauthenticated:1; bool authenticated:1; bool io_setup:1; bool use_multiplex:1; }; static void client_connection_tcp_input(struct client_connection_tcp *conn); static void client_connection_tcp_send_auth_handshake(struct client_connection_tcp *conn); static void client_connection_tcp_destroy(struct client_connection_tcp **_conn); static int client_connection_tcp_init_ssl(struct client_connection_tcp *conn); static failure_callback_t *orig_error_callback, *orig_fatal_callback; static failure_callback_t *orig_info_callback, *orig_debug_callback = NULL; static bool log_recursing = FALSE; static void ATTR_FORMAT(2, 0) doveadm_server_log_handler(const struct failure_context *ctx, const char *format, va_list args) { struct client_connection_tcp *conn = NULL; if (doveadm_client != NULL && doveadm_client->type == DOVEADM_CONNECTION_TYPE_TCP) conn = (struct client_connection_tcp *)doveadm_client; if (!log_recursing && conn != NULL && conn->log_out != NULL) T_BEGIN { struct ioloop *prev_ioloop = current_ioloop; struct ostream *log_out = conn->log_out; char c; const char *ptr; bool corked; va_list va; /* prevent re-entering this code if any of the following code causes logging */ log_recursing = TRUE; /* since we can get here from just about anywhere, make sure the log ostream uses the connection's ioloop. */ if (conn->ioloop != NULL) io_loop_set_current(conn->ioloop); const char *log_prefix = ctx->log_prefix != NULL ? ctx->log_prefix : i_get_failure_prefix(); size_t log_prefix_len = strlen(log_prefix); c = doveadm_log_type_to_char(ctx->type); corked = o_stream_is_corked(log_out); va_copy(va, args); const char *str = t_strdup_vprintf(format, va); va_end(va); if (!corked) o_stream_cork(log_out); for (;;) { ptr = strchr(str, '\n'); size_t len = ptr == NULL ? strlen(str) : (size_t)(ptr - str); o_stream_nsend(log_out, &c, 1); o_stream_nsend(log_out, log_prefix, log_prefix_len); o_stream_nsend(log_out, str, len); o_stream_nsend(log_out, "\n", 1); if (ptr == NULL) break; str = ptr+1; } o_stream_uncork(log_out); if (corked) o_stream_cork(log_out); io_loop_set_current(prev_ioloop); log_recursing = FALSE; } T_END; switch(ctx->type) { case LOG_TYPE_DEBUG: orig_debug_callback(ctx, format, args); break; case LOG_TYPE_INFO: orig_info_callback(ctx, format, args); break; case LOG_TYPE_WARNING: case LOG_TYPE_ERROR: orig_error_callback(ctx, format, args); break; default: i_unreached(); } } static void doveadm_server_capture_logs(void) { i_assert(orig_debug_callback == NULL); i_get_failure_handlers(&orig_fatal_callback, &orig_error_callback, &orig_info_callback, &orig_debug_callback); i_set_error_handler(doveadm_server_log_handler); i_set_info_handler(doveadm_server_log_handler); i_set_debug_handler(doveadm_server_log_handler); } static void doveadm_server_restore_logs(void) { i_assert(orig_debug_callback != NULL); i_set_error_handler(orig_error_callback); i_set_info_handler(orig_info_callback); i_set_debug_handler(orig_debug_callback); orig_fatal_callback = NULL; orig_error_callback = NULL; orig_info_callback = NULL; orig_debug_callback = NULL; } static void doveadm_cmd_server_post(struct client_connection_tcp *conn, const char *cmd_name) { const char *str = NULL; if (doveadm_exit_code == 0) { o_stream_nsend(conn->output, "\n+\n", 3); return; } str = doveadm_exit_code_to_str(doveadm_exit_code); if (str != NULL) { o_stream_nsend_str(conn->output, t_strdup_printf("\n-%s\n", str)); } else { o_stream_nsend_str(conn->output, "\n-\n"); i_error("BUG: Command '%s' returned unknown error code %d", cmd_name, doveadm_exit_code); } } static void doveadm_cmd_server_run_ver2(struct client_connection_tcp *conn, int argc, const char *const argv[], struct doveadm_cmd_context *cctx) { i_getopt_reset(); if (doveadm_cmd_run_ver2(argc, argv, cctx) < 0) doveadm_exit_code = EX_USAGE; doveadm_cmd_server_post(conn, cctx->cmd->name); } static int doveadm_cmd_handle(struct client_connection_tcp *conn, const char *cmd_name, int argc, const char *const argv[], struct doveadm_cmd_context *cctx) { struct ioloop *prev_ioloop = current_ioloop; const struct doveadm_cmd_ver2 *cmd_ver2; if ((cmd_ver2 = doveadm_cmd_find_with_args_ver2(cmd_name, &argc, &argv)) == NULL) { i_error("doveadm: Client sent unknown command: %s", cmd_name); return -1; } cctx->cmd = cmd_ver2; /* some commands will want to call io_loop_run(), but we're already running one and we can't call the original one recursively, so create a new ioloop. */ conn->ioloop = io_loop_create(); o_stream_switch_ioloop(conn->output); if (conn->log_out != NULL) o_stream_switch_ioloop(conn->log_out); doveadm_cmd_server_run_ver2(conn, argc, argv, cctx); o_stream_switch_ioloop_to(conn->output, prev_ioloop); if (conn->log_out != NULL) o_stream_switch_ioloop_to(conn->log_out, prev_ioloop); io_loop_destroy(&conn->ioloop); /* clear all headers */ doveadm_print_deinit(); doveadm_print_init(DOVEADM_PRINT_TYPE_SERVER); /* We already sent the success/failure reply to the client. Return 0 so caller never adds another failure reply. */ return 0; } static bool client_handle_command(struct client_connection_tcp *conn, const char *const *args) { struct doveadm_cmd_context cctx; const char *flags, *cmd_name; unsigned int argc = str_array_length(args); if (argc < 3) { i_error("doveadm client: No command given"); return FALSE; } i_zero(&cctx); cctx.conn_type = conn->conn.type; cctx.input = conn->input; cctx.output = conn->output; cctx.local_ip = conn->conn.local_ip; cctx.remote_ip = conn->conn.remote_ip; cctx.local_port = conn->conn.local_port; cctx.remote_port = conn->conn.remote_port; doveadm_exit_code = 0; flags = args[0]; cctx.username = args[1]; cmd_name = args[2]; doveadm_debug = FALSE; doveadm_verbose = FALSE; for (; *flags != '\0'; flags++) { switch (*flags) { case 'D': doveadm_debug = TRUE; doveadm_verbose = TRUE; break; case 'v': doveadm_verbose = TRUE; break; default: i_error("doveadm client: Unknown flag: %c", *flags); return FALSE; } } if (!doveadm_client_is_allowed_command(conn->conn.set, cmd_name)) { i_error("doveadm client isn't allowed to use command: %s", cmd_name); return FALSE; } client_connection_set_proctitle(&conn->conn, cmd_name); o_stream_cork(conn->output); /* Disable IO while running a command. This is required for commands that do IO themselves (e.g. dsync-server). */ io_remove(&conn->io); if (doveadm_cmd_handle(conn, cmd_name, argc-2, args+2, &cctx) < 0) o_stream_nsend(conn->output, "\n-\n", 3); o_stream_uncork(conn->output); conn->io = io_add_istream(conn->input, client_connection_tcp_input, conn); client_connection_set_proctitle(&conn->conn, ""); /* Try to flush the output. It might finish later. */ (void)o_stream_flush(conn->output); return TRUE; } static int client_connection_tcp_authenticate(struct client_connection_tcp *conn) { const struct doveadm_settings *set = conn->conn.set; const char *line, *pass; buffer_t *plain; const unsigned char *data; size_t size; if ((line = i_stream_read_next_line(conn->input)) == NULL) { if (conn->input->eof) return -1; return 0; } if (*set->doveadm_password == '\0') { i_error("doveadm_password not set, " "remote authentication disabled"); return -1; } if (strcmp(line, "STARTTLS") == 0) { io_remove(&conn->io); if (client_connection_tcp_init_ssl(conn) < 0) return -1; conn->io = io_add_istream(conn->input, client_connection_tcp_input, conn); return 0; } /* FIXME: some day we should probably let auth process do this and support all kinds of authentication */ if (!str_begins(line, "PLAIN\t")) { i_error("doveadm client attempted non-PLAIN authentication: %s", line); return -1; } plain = t_buffer_create(128); if (base64_decode(line + 6, strlen(line + 6), NULL, plain) < 0) { i_error("doveadm client sent invalid base64 auth PLAIN data"); return -1; } data = plain->data; size = plain->used; if (size < 10 || data[0] != '\0' || memcmp(data+1, "doveadm", 7) != 0 || data[8] != '\0') { i_error("doveadm client didn't authenticate as 'doveadm'"); return -1; } pass = t_strndup(data + 9, size - 9); if (strlen(pass) != strlen(set->doveadm_password) || !mem_equals_timing_safe(pass, set->doveadm_password, strlen(pass))) { i_error("doveadm client authenticated with wrong password"); return -1; } return 1; } static void client_log_disconnect_error(struct client_connection_tcp *conn) { const char *error; error = conn->ssl_iostream == NULL ? NULL : ssl_iostream_get_last_error(conn->ssl_iostream); if (error == NULL) { error = conn->input->stream_errno == 0 ? "EOF" : strerror(conn->input->stream_errno); } i_error("doveadm client disconnected before handshake: %s", error); } static void client_connection_tcp_input(struct client_connection_tcp *conn) { const char *line; bool ok = TRUE; int ret; unsigned int minor; if (!conn->handshaked) { if ((line = i_stream_read_next_line(conn->input)) == NULL) { if (conn->input->eof || conn->input->stream_errno != 0) { client_log_disconnect_error(conn); client_connection_tcp_destroy(&conn); } return; } if (!version_string_verify_full(line, "doveadm-server", DOVEADM_SERVER_PROTOCOL_VERSION_MAJOR, &minor)) { i_error("doveadm client not compatible with this server " "(mixed old and new binaries?)"); client_connection_tcp_destroy(&conn); return; } if (minor > 0) { /* send version reply */ o_stream_nsend_str(conn->output, DOVEADM_CLIENT_PROTOCOL_VERSION_LINE"\n"); conn->use_multiplex = TRUE; } client_connection_tcp_send_auth_handshake(conn); conn->handshaked = TRUE; } if (!conn->authenticated) { if ((ret = client_connection_tcp_authenticate(conn)) <= 0) { if (ret < 0) { o_stream_nsend(conn->output, "-\n", 2); client_connection_tcp_destroy(&conn); } return; } o_stream_nsend(conn->output, "+\n", 2); conn->authenticated = TRUE; } if (!conn->io_setup) { conn->io_setup = TRUE; if (conn->use_multiplex) { struct ostream *os = conn->output; conn->output = o_stream_create_multiplex(os, SIZE_MAX); o_stream_set_name(conn->output, o_stream_get_name(os)); o_stream_set_no_error_handling(conn->output, TRUE); o_stream_unref(&os); conn->log_out = o_stream_multiplex_add_channel(conn->output, DOVEADM_LOG_CHANNEL_ID); o_stream_set_no_error_handling(conn->log_out, TRUE); o_stream_set_name(conn->log_out, t_strdup_printf("%s (log)", o_stream_get_name(conn->output))); doveadm_server_capture_logs(); } doveadm_print_ostream = conn->output; } while (ok && !conn->input->closed && (line = i_stream_read_next_line(conn->input)) != NULL) { T_BEGIN { const char *const *args; args = t_strsplit_tabescaped(line); ok = client_handle_command(conn, args); } T_END; } if (conn->input->eof || conn->input->stream_errno != 0 || !ok) client_connection_tcp_destroy(&conn); } static int client_connection_tcp_init_ssl(struct client_connection_tcp *conn) { const char *error; if (master_service_ssl_init(master_service, &conn->input, &conn->output, &conn->ssl_iostream, &error) < 0) { i_error("SSL init failed: %s", error); return -1; } if (ssl_iostream_handshake(conn->ssl_iostream) < 0) { i_error("SSL handshake failed: %s", ssl_iostream_get_last_error(conn->ssl_iostream)); return -1; } return 0; } static bool client_connection_is_preauthenticated(int listen_fd) { const char *listen_path; struct stat st; /* we'll have to do this with stat(), because at least in Linux fstat() always returns mode as 0777 */ return net_getunixname(listen_fd, &listen_path) == 0 && stat(listen_path, &st) == 0 && S_ISSOCK(st.st_mode) && (st.st_mode & 0777) == 0600; } static void client_connection_tcp_send_auth_handshake(struct client_connection_tcp *conn) { if (conn->preauthenticated) { /* no need for client to authenticate */ conn->authenticated = TRUE; o_stream_nsend(conn->output, "+\n", 2); } else { o_stream_nsend(conn->output, "-\n", 2); } } static void client_connection_tcp_free(struct client_connection *_conn) { struct client_connection_tcp *conn = (struct client_connection_tcp *)_conn; i_assert(_conn->type == DOVEADM_CONNECTION_TYPE_TCP); doveadm_print_deinit(); doveadm_print_ostream = NULL; if (conn->log_out != NULL) { doveadm_server_restore_logs(); o_stream_unref(&conn->log_out); } ssl_iostream_destroy(&conn->ssl_iostream); io_remove(&conn->io); o_stream_destroy(&conn->output); i_stream_destroy(&conn->input); i_close_fd(&conn->fd); } struct client_connection * client_connection_tcp_create(int fd, int listen_fd, bool ssl) { struct client_connection_tcp *conn; pool_t pool; pool = pool_alloconly_create("doveadm client", 1024*16); conn = p_new(pool, struct client_connection_tcp, 1); conn->fd = fd; if (client_connection_init(&conn->conn, DOVEADM_CONNECTION_TYPE_TCP, pool, fd) < 0) { client_connection_tcp_destroy(&conn); return NULL; } conn->conn.free = client_connection_tcp_free; doveadm_print_init(DOVEADM_PRINT_TYPE_SERVER); conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE); conn->output = o_stream_create_fd(fd, SIZE_MAX); i_stream_set_name(conn->input, conn->conn.name); o_stream_set_name(conn->output, conn->conn.name); o_stream_set_no_error_handling(conn->output, TRUE); if (ssl) { if (client_connection_tcp_init_ssl(conn) < 0) { client_connection_tcp_destroy(&conn); return NULL; } } /* add IO after SSL istream is created */ conn->io = io_add_istream(conn->input, client_connection_tcp_input, conn); conn->preauthenticated = client_connection_is_preauthenticated(listen_fd); client_connection_set_proctitle(&conn->conn, ""); return &conn->conn; } static void client_connection_tcp_destroy(struct client_connection_tcp **_conn) { struct client_connection_tcp *conn = *_conn; struct client_connection *bconn = &conn->conn; *_conn = NULL; client_connection_destroy(&bconn); } dovecot-2.3.21.1/src/doveadm/doveadm-oldstats.c0000644000000000000000000003651214656633576016150 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "net.h" #include "ioloop.h" #include "istream.h" #include "hash.h" #include "str.h" #include "strescape.h" #include "strescape.h" #include "write-full.h" #include "doveadm.h" #include "doveadm-print.h" #include #include #include #include struct top_line { char *id; /* [headers_count] */ const char **prev_values, **cur_values; bool flip:1; }; struct top_context { const char *path; int fd; struct istream *input; const char *sort_type; char **headers; unsigned int headers_count; pool_t prev_pool, cur_pool; /* id => struct top_line. */ HASH_TABLE(char *, struct top_line *) sessions; ARRAY(struct top_line *) lines; int (*lines_sort)(struct top_line *const *, struct top_line *const *); unsigned int last_update_idx, user_idx; unsigned int sort_idx1, sort_idx2; bool flip:1; }; static struct top_context *sort_ctx = NULL; static const char *disk_input_field = "disk_input"; static const char *disk_output_field = "disk_output"; static char ** p_read_next_line(pool_t pool, struct istream *input) { const char *line; line = i_stream_read_next_line(input); if (line == NULL) return NULL; return p_strsplit_tabescaped(pool, line); } static const char *const* read_next_line(struct istream *input) { return (const void *)p_read_next_line(pool_datastack_create(), input); } static void stats_dump(const char *path, const char *cmd) { struct istream *input; const char *const *args; unsigned int i; int fd; fd = doveadm_connect(path); net_set_nonblock(fd, FALSE); if (write_full(fd, cmd, strlen(cmd)) < 0) i_fatal("write(%s) failed: %m", path); input = i_stream_create_fd_autoclose(&fd, SIZE_MAX); /* read header */ args = read_next_line(input); if (args == NULL) i_fatal("read(%s) unexpectedly disconnected", path); if (*args == NULL) i_info("no statistics available"); else { for (; *args != NULL; args++) doveadm_print_header_simple(*args); /* read lines */ do { T_BEGIN { args = read_next_line(input); if (args != NULL && args[0] == NULL) args = NULL; if (args != NULL) { for (i = 0; args[i] != NULL; i++) doveadm_print(args[i]); } } T_END; } while (args != NULL); } if (input->stream_errno != 0) i_fatal("read(%s) failed: %s", path, i_stream_get_error(input)); i_stream_destroy(&input); } static void doveadm_cmd_stats_dump(struct doveadm_cmd_context* cctx) { const char *path, *cmd; const char *args[3] = {0}; if (!doveadm_cmd_param_str(cctx, "socket-path", &path)) path = t_strconcat(doveadm_settings->base_dir, "/old-stats", NULL); if (!doveadm_cmd_param_str(cctx, "type", &args[0])) { i_error("Missing type parameter"); doveadm_exit_code = EX_USAGE; return; } /* purely optional */ if (!doveadm_cmd_param_str(cctx, "filter", &args[1])) args[1] = NULL; cmd = t_strdup_printf("EXPORT\t%s\n", t_strarray_join(args, "\t")); doveadm_print_init(DOVEADM_PRINT_TYPE_TAB); stats_dump(path, cmd); return; } static void stats_line_set_prev_values(struct top_context *ctx, const struct top_line *old_line, struct top_line *line) { const char **values; unsigned int i; if (old_line->prev_values == NULL || strcmp(old_line->cur_values[ctx->last_update_idx], line->cur_values[ctx->last_update_idx]) != 0) { values = old_line->cur_values; } else { values = old_line->prev_values; } /* line hasn't been updated, keep old values */ line->prev_values = p_new(ctx->cur_pool, const char *, ctx->headers_count); for (i = 0; i < ctx->headers_count; i++) line->prev_values[i] = p_strdup(ctx->cur_pool, values[i]); } static void stats_read(struct top_context *ctx) { struct top_line *old_line, *line; unsigned int i; char **args; /* read lines */ while ((args = p_read_next_line(ctx->cur_pool, ctx->input)) != NULL) { if (args[0] == NULL) { /* end of stats */ return; } if (str_array_length((void *)args) != ctx->headers_count) i_fatal("read(%s): invalid stats line", ctx->path); line = p_new(ctx->cur_pool, struct top_line, 1); line->id = args[0]; line->flip = ctx->flip; line->cur_values = p_new(ctx->cur_pool, const char *, ctx->headers_count); for (i = 0; i < ctx->headers_count; i++) line->cur_values[i] = args[i]; old_line = hash_table_lookup(ctx->sessions, line->id); if (old_line != NULL) { stats_line_set_prev_values(ctx, old_line, line); array_push_back(&ctx->lines, &line); } hash_table_update(ctx->sessions, line->id, line); } if (ctx->input->stream_errno != 0) { i_fatal("read(%s) failed: %s", ctx->path, i_stream_get_error(ctx->input)); } i_fatal("read(%s): unexpected EOF", ctx->path); } static void stats_drop_stale(struct top_context *ctx) { struct hash_iterate_context *iter; char *id; struct top_line *line; iter = hash_table_iterate_init(ctx->sessions); while (hash_table_iterate(iter, ctx->sessions, &id, &line)) { if (line->flip != ctx->flip) hash_table_remove(ctx->sessions, id); } hash_table_iterate_deinit(&iter); } static int get_double(const char *str, double *num_r) { char *p; *num_r = strtod(str, &p); return *p == '\0' ? 0 : -1; } static double sort_cpu_diff(const struct top_line *line) { double prev, cur, diff, prev_time, cur_time, time_multiplier; if (get_double(line->prev_values[sort_ctx->last_update_idx], &prev_time) < 0 || get_double(line->cur_values[sort_ctx->last_update_idx], &cur_time) < 0) i_fatal("sorting: invalid last_update value"); time_multiplier = (cur_time - prev_time) * 100; if (get_double(line->prev_values[sort_ctx->sort_idx1], &prev) < 0 || get_double(line->cur_values[sort_ctx->sort_idx1], &cur) < 0) i_fatal("sorting: not a double"); diff = cur - prev; if (sort_ctx->sort_idx2 != 0) { if (get_double(line->prev_values[sort_ctx->sort_idx2], &prev) < 0 || get_double(line->cur_values[sort_ctx->sort_idx2], &cur) < 0) i_fatal("sorting: not a double"); diff += cur - prev; } return diff * time_multiplier; } static int sort_cpu(struct top_line *const *l1, struct top_line *const *l2) { double d1, d2; d1 = sort_cpu_diff(*l1); d2 = sort_cpu_diff(*l2); if (d1 < d2) return -1; if (d1 > d2) return 1; return strcmp((*l1)->cur_values[sort_ctx->user_idx], (*l2)->cur_values[sort_ctx->user_idx]); } static double sort_num_diff(const struct top_line *line) { uint64_t prev, cur, diff; if (str_to_uint64(line->prev_values[sort_ctx->sort_idx1], &prev) < 0 || str_to_uint64(line->cur_values[sort_ctx->sort_idx1], &cur) < 0) i_fatal("sorting: not a number"); diff = cur - prev; if (sort_ctx->sort_idx2 != 0) { if (str_to_uint64(line->prev_values[sort_ctx->sort_idx2], &prev) < 0 || str_to_uint64(line->cur_values[sort_ctx->sort_idx2], &cur) < 0) i_fatal("sorting: not a number"); diff += cur - prev; } return diff; } static int sort_num(struct top_line *const *l1, struct top_line *const *l2) { uint64_t n1, n2; n1 = sort_num_diff(*l1); n2 = sort_num_diff(*l2); if (n1 < n2) return -1; if (n1 > n2) return 1; return strcmp((*l1)->cur_values[sort_ctx->user_idx], (*l2)->cur_values[sort_ctx->user_idx]); } static bool stats_header_find(struct top_context *ctx, const char *name, unsigned int *idx_r) { unsigned int i; for (i = 0; ctx->headers[i] != NULL; i++) { if (strcmp(ctx->headers[i], name) == 0) { *idx_r = i; return TRUE; } } return FALSE; } static void stats_top_get_sorting(struct top_context *ctx) { if (stats_header_find(ctx, ctx->sort_type, &ctx->sort_idx1)) return; if (strcmp(ctx->sort_type, "cpu") == 0) { if (!stats_header_find(ctx, "user_cpu", &ctx->sort_idx1) || !stats_header_find(ctx, "sys_cpu", &ctx->sort_idx2)) i_fatal("cpu sort type is missing fields"); return; } if (strcmp(ctx->sort_type, "disk") == 0) { if (!stats_header_find(ctx, disk_input_field, &ctx->sort_idx1) || !stats_header_find(ctx, disk_output_field, &ctx->sort_idx2)) i_fatal("disk sort type is missing fields"); return; } i_fatal("unknown sort type: %s", ctx->sort_type); } static bool stats_top_round(struct top_context *ctx) { #define TOP_CMD "EXPORT\tsession\tconnected\n" const char *const *args; pool_t tmp_pool; if (write_full(ctx->fd, TOP_CMD, strlen(TOP_CMD)) < 0) i_fatal("write(%s) failed: %m", ctx->path); /* read header */ if (ctx->headers != NULL) { args = read_next_line(ctx->input); if (args == NULL) i_fatal("read(%s) unexpectedly disconnected", ctx->path); if (*args == NULL) return TRUE; if (str_array_length(args) != ctx->headers_count) i_fatal("headers changed"); } else { ctx->headers = p_read_next_line(default_pool, ctx->input); if (ctx->headers == NULL) i_fatal("read(%s) unexpectedly disconnected", ctx->path); if (*ctx->headers == NULL) { i_free_and_null(ctx->headers); return FALSE; } ctx->headers_count = str_array_length((void *)ctx->headers); if (!stats_header_find(ctx, "last_update", &ctx->last_update_idx)) i_fatal("last_update header missing"); if (!stats_header_find(ctx, "user", &ctx->user_idx)) i_fatal("user header missing"); stats_top_get_sorting(ctx); } array_clear(&ctx->lines); p_clear(ctx->prev_pool); tmp_pool = ctx->prev_pool; ctx->prev_pool = ctx->cur_pool; ctx->cur_pool = tmp_pool; ctx->flip = !ctx->flip; stats_read(ctx); stats_drop_stale(ctx); sort_ctx = ctx; array_sort(&ctx->lines, *ctx->lines_sort); sort_ctx = NULL; return TRUE; } static void stats_top_output_diff(struct top_context *ctx, const struct top_line *line, unsigned int i) { uint64_t prev_num, cur_num; double prev_double, cur_double, prev_time, cur_time; char numstr[MAX_INT_STRLEN]; if (str_to_uint64(line->prev_values[i], &prev_num) == 0 && str_to_uint64(line->cur_values[i], &cur_num) == 0) { if (i_snprintf(numstr, sizeof(numstr), "%"PRIu64, (cur_num - prev_num)) < 0) i_unreached(); doveadm_print(numstr); } else if (get_double(line->prev_values[i], &prev_double) == 0 && get_double(line->cur_values[i], &cur_double) == 0 && get_double(line->prev_values[ctx->last_update_idx], &prev_time) == 0 && get_double(line->cur_values[ctx->last_update_idx], &cur_time) == 0) { /* %CPU */ if (i_snprintf(numstr, sizeof(numstr), "%d", (int)((cur_double - prev_double) * (cur_time - prev_time) * 100)) < 0) i_unreached(); doveadm_print(numstr); } else { doveadm_print(line->cur_values[i]); } } static void stats_top_output(struct top_context *ctx) { static const char *names[] = { "user", "service", "user_cpu", "sys_cpu", "", "" }; struct winsize ws; struct top_line *const *lines; unsigned int i, j, row, maxrow, count, indexes[N_ELEMENTS(names)]; names[4] = disk_input_field; names[5] = disk_output_field; /* ANSI clear screen and move cursor to top of screen */ printf("\x1b[2J\x1b[1;1H"); fflush(stdout); doveadm_print_deinit(); doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); doveadm_print_header("USER", "USER", DOVEADM_PRINT_HEADER_FLAG_EXPAND); doveadm_print_header_simple("SERVICE"); doveadm_print_header_simple("%CPU"); doveadm_print_header_simple("%SYS"); doveadm_print_header_simple("DISKIN"); doveadm_print_header_simple("DISKOUT"); if (!stats_top_round(ctx)) { /* no connections yet */ return; } for (i = 0; i < N_ELEMENTS(names); i++) { if (!stats_header_find(ctx, names[i], &indexes[i])) indexes[i] = UINT_MAX; } if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0) ws.ws_row = 24; maxrow = ws.ws_row-1; lines = array_get(&ctx->lines, &count); for (i = 0, row = 1; row < maxrow && i < count; i++, row++) { for (j = 0; j < N_ELEMENTS(names); j++) { if (indexes[j] == UINT_MAX) doveadm_print("?"); else stats_top_output_diff(ctx, lines[i], indexes[j]); } } } static void stats_top_start(struct top_context *ctx) { struct timeout *to; stats_top_output(ctx); to = timeout_add(1000, stats_top_output, ctx); io_loop_run(current_ioloop); timeout_remove(&to); } static void stats_top(const char *path, const char *sort_type) { struct top_context ctx; i_zero(&ctx); ctx.path = path; ctx.fd = doveadm_connect(path); ctx.prev_pool = pool_alloconly_create("stats top", 1024*16); ctx.cur_pool = pool_alloconly_create("stats top", 1024*16); i_array_init(&ctx.lines, 128); hash_table_create(&ctx.sessions, default_pool, 0, str_hash, strcmp); net_set_nonblock(ctx.fd, FALSE); ctx.input = i_stream_create_fd(ctx.fd, SIZE_MAX); if (strstr(sort_type, "cpu") != NULL) ctx.lines_sort = sort_cpu; else ctx.lines_sort = sort_num; ctx.sort_type = sort_type; stats_top_start(&ctx); i_stream_destroy(&ctx.input); hash_table_destroy(&ctx.sessions); array_free(&ctx.lines); pool_unref(&ctx.prev_pool); pool_unref(&ctx.cur_pool); i_close_fd(&ctx.fd); } static void stats_reset(const char *path) { const char **ptr ATTR_UNUSED; int fd,ret; string_t *cmd; struct istream *input; const char *line; fd = doveadm_connect(path); net_set_nonblock(fd, FALSE); input = i_stream_create_fd(fd, SIZE_MAX); cmd = t_str_new(10); str_append(cmd, "RESET"); /* XXX: Not supported yet. for(ptr = items; *ptr; ptr++) { str_append_c(cmd, '\t'); str_append(cmd, *ptr); } */ str_append_c(cmd, '\n'); /* send command */ ret = write_full(fd, str_c(cmd), str_len(cmd)); if (ret < 0) { i_close_fd(&fd); i_error("write(%s) failed: %m", path); return; } line = i_stream_read_next_line(input); if (line == NULL) { i_error("read(%s) failed: %s", path, i_stream_get_error(input)); } else if (!str_begins(line, "OK")) { i_error("%s",line); } else { i_info("Stats reset"); } i_stream_destroy(&input); i_close_fd(&fd); } static void cmd_stats_top(struct doveadm_cmd_context *cctx) { const char *path, *sort_type; bool b; if (!doveadm_cmd_param_str(cctx, "socket-path", &path)) { path = t_strconcat(doveadm_settings->base_dir, "/old-stats", NULL); } if (!doveadm_cmd_param_bool(cctx, "show-disk-io", &b) && b) { disk_input_field = "read_bytes"; disk_output_field = "write_bytes"; } if (!doveadm_cmd_param_str(cctx, "sort-field", &sort_type)) sort_type = "disk"; doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); stats_top(path, sort_type); } static void cmd_stats_reset(struct doveadm_cmd_context *cctx) { const char *path; if (!doveadm_cmd_param_str(cctx, "socket-path", &path)) { path = t_strconcat(doveadm_settings->base_dir, "/old-stats", NULL); } stats_reset(path); } struct doveadm_cmd_ver2 doveadm_cmd_oldstats_dump_ver2 = { .cmd = doveadm_cmd_stats_dump, .name = "oldstats dump", .usage = "[-s ] []", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('s', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "type", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "filter", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_oldstats_top_ver2 = { .cmd = cmd_stats_top, .name = "oldstats top", .usage = "[-s ] [-b] []", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('s', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('b', "show-disk-io", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "sort-field", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_oldstats_reset_ver2 = { .cmd = cmd_stats_reset, .name = "oldstats reset", .usage = "[-s ]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('s', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm.c0000644000000000000000000002326614656633576014317 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "sort.h" #include "ostream.h" #include "env-util.h" #include "execv-const.h" #include "dict.h" #include "master-service-private.h" #include "master-service-settings.h" #include "settings-parser.h" #include "doveadm-print-private.h" #include "doveadm-dump.h" #include "doveadm-mail.h" #include "doveadm-settings.h" #include "doveadm-dsync.h" #include "doveadm.h" #include const struct doveadm_print_vfuncs *doveadm_print_vfuncs_all[] = { &doveadm_print_flow_vfuncs, &doveadm_print_tab_vfuncs, &doveadm_print_table_vfuncs, &doveadm_print_pager_vfuncs, &doveadm_print_json_vfuncs, &doveadm_print_formatted_vfuncs, NULL }; int doveadm_exit_code = 0; static void failure_exit_callback(int *status) { enum fatal_exit_status fatal_status = *status; switch (fatal_status) { case FATAL_LOGWRITE: case FATAL_LOGERROR: case FATAL_LOGOPEN: case FATAL_OUTOFMEM: case FATAL_EXEC: case FATAL_DEFAULT: *status = EX_TEMPFAIL; break; } } static void doveadm_usage_compress_lines(FILE *out, const char *str, const char *prefix) { const char *cmd, *args, *p, *short_name, *sub_name; const char *prev_name = "", *prev_sub_name = ""; const char **lines; unsigned int i, count; size_t prefix_len = strlen(prefix); /* split lines */ lines = (void *)p_strsplit(pool_datastack_create(), str, "\n"); for (count = 0; lines[count] != NULL; count++) ; /* sort lines */ i_qsort(lines, count, sizeof(*lines), i_strcmp_p); /* print lines, compress subcommands into a single line */ for (i = 0; i < count; i++) { args = strchr(lines[i], '\t'); if (args == NULL) { cmd = lines[i]; args = ""; } else { cmd = t_strdup_until(lines[i], args); args++; } if (*prefix != '\0') { if (strncmp(cmd, prefix, prefix_len) != 0 || cmd[prefix_len] != ' ') continue; cmd += prefix_len + 1; } p = strchr(cmd, ' '); if (p == NULL) { if (*prev_name != '\0') { fprintf(out, "\n"); prev_name = ""; } fprintf(out, USAGE_CMDNAME_FMT" %s\n", cmd, args); } else { short_name = t_strdup_until(cmd, p); if (strcmp(prev_name, short_name) != 0) { if (*prev_name != '\0') fprintf(out, "\n"); fprintf(out, USAGE_CMDNAME_FMT" %s", short_name, t_strcut(p + 1, ' ')); prev_name = short_name; prev_sub_name = ""; } else { sub_name = t_strcut(p + 1, ' '); if (strcmp(prev_sub_name, sub_name) != 0) { fprintf(out, "|%s", sub_name); prev_sub_name = sub_name; } } } } if (*prev_name != '\0') fprintf(out, "\n"); } static void ATTR_NORETURN usage_prefix(const char *prefix) { const struct doveadm_cmd_ver2 *cmd2; string_t *str = t_str_new(1024); fprintf(stderr, "usage: doveadm [-Dv] [-f ] "); if (*prefix != '\0') fprintf(stderr, "%s ", prefix); fprintf(stderr, " []\n"); array_foreach(&doveadm_cmds_ver2, cmd2) str_printfa(str, "%s\t%s\n", cmd2->name, cmd2->usage); doveadm_usage_compress_lines(stderr, str_c(str), prefix); lib_exit(EX_USAGE); } void usage(void) { usage_prefix(""); } void help_ver2(const struct doveadm_cmd_ver2 *cmd) { fprintf(stderr, "doveadm %s %s\n", cmd->name, cmd->usage); lib_exit(EX_USAGE); } static void cmd_help(struct doveadm_cmd_context *cctx) { const char *cmd, *man_argv[3]; if (!doveadm_cmd_param_str(cctx, "cmd", &cmd)) usage_prefix(""); env_put("MANPATH", MANDIR); man_argv[0] = "man"; man_argv[1] = t_strconcat("doveadm-", cmd, NULL); man_argv[2] = NULL; execvp_const(man_argv[0], man_argv); } static struct doveadm_cmd_ver2 doveadm_cmd_help = { .name = "help", .cmd = cmd_help, .usage = "[]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', "cmd", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; static void cmd_config(struct doveadm_cmd_context *cctx) { const char *const *args, **argv; if (!doveadm_cmd_param_array(cctx, "args", &args)) args = NULL; env_put(MASTER_CONFIG_FILE_ENV, master_service_get_config_path(master_service)); unsigned int len = str_array_length(args); argv = t_new(const char *, len + 2); argv[0] = BINDIR"/doveconf"; if (len > 0) { i_assert(args != NULL); memcpy(argv+1, args, len * sizeof(args[0])); } execv_const(argv[0], argv); } static struct doveadm_cmd_ver2 doveadm_cmd_config = { .name = "config", .cmd = cmd_config, .usage = "[doveconf parameters]", .flags = CMD_FLAG_NO_OPTIONS, DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', "args", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; static void cmd_exec(struct doveadm_cmd_context *cctx); static struct doveadm_cmd_ver2 doveadm_cmd_exec = { .name = "exec", .cmd = cmd_exec, .usage = " [binary parameters]", .flags = CMD_FLAG_NO_OPTIONS, DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', "binary", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "args", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; static void cmd_exec(struct doveadm_cmd_context *cctx) { const char *path, *binary, *const *args, **argv; if (!doveadm_cmd_param_str(cctx, "binary", &binary)) help_ver2(&doveadm_cmd_exec); if (!doveadm_cmd_param_array(cctx, "args", &args)) args = NULL; path = t_strdup_printf("%s/%s", doveadm_settings->libexec_dir, binary); unsigned int len = str_array_length(args); argv = t_new(const char *, len + 2); argv[0] = path; if (len > 0) { i_assert(args != NULL); memcpy(argv+1, args, len * sizeof(args[0])); } execv_const(argv[0], argv); } static bool doveadm_has_subcommands(const char *cmd_name) { const struct doveadm_cmd_ver2 *cmd2; size_t len = strlen(cmd_name); array_foreach(&doveadm_cmds_ver2, cmd2) { if (strncmp(cmd2->name, cmd_name, len) == 0 && cmd2->name[len] == ' ') return TRUE; } return FALSE; } static struct doveadm_cmd_ver2 *doveadm_cmdline_commands_ver2[] = { &doveadm_cmd_config, &doveadm_cmd_dump, &doveadm_cmd_exec, &doveadm_cmd_help, &doveadm_cmd_oldstats_top_ver2, &doveadm_cmd_pw, &doveadm_cmd_zlibconnect, }; int main(int argc, char *argv[]) { enum master_service_flags service_flags = MASTER_SERVICE_FLAG_STANDALONE | MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN | MASTER_SERVICE_FLAG_NO_SSL_INIT | MASTER_SERVICE_FLAG_NO_INIT_DATASTACK_FRAME; struct doveadm_cmd_context cctx; const char *cmd_name; unsigned int i; bool quick_init = FALSE; int c; i_zero(&cctx); cctx.conn_type = DOVEADM_CONNECTION_TYPE_CLI; i_set_failure_exit_callback(failure_exit_callback); doveadm_dsync_main(&argc, &argv); /* "+" is GNU extension to stop at the first non-option. others just accept -+ option. */ master_service = master_service_init("doveadm", service_flags, &argc, &argv, "+Df:hv"); while ((c = master_getopt(master_service)) > 0) { switch (c) { case 'D': doveadm_debug = TRUE; doveadm_verbose = TRUE; break; case 'f': doveadm_print_init(optarg); break; case 'h': doveadm_print_hide_titles = TRUE; break; case 'v': doveadm_verbose = TRUE; break; default: return FATAL_DEFAULT; } } cmd_name = argv[optind]; if (cmd_name != NULL && strcmp(cmd_name, "help") == 0 && argv[optind+1] != NULL) { /* "help cmd" doesn't need any configuration */ quick_init = TRUE; } master_service_init_log(master_service); doveadm_settings_init(); doveadm_cmds_init(); for (i = 0; i < N_ELEMENTS(doveadm_cmdline_commands_ver2); i++) doveadm_cmd_register_ver2(doveadm_cmdline_commands_ver2[i]); doveadm_register_auth_commands(); if (cmd_name != NULL && (quick_init || strcmp(cmd_name, "config") == 0 || strcmp(cmd_name, "stop") == 0 || strcmp(cmd_name, "reload") == 0)) { /* special case commands: even if there is something wrong with the config (e.g. mail_plugins), don't fail these commands */ if (strcmp(cmd_name, "help") != 0) doveadm_read_settings(); quick_init = TRUE; } else { quick_init = FALSE; doveadm_print_ostream = o_stream_create_fd(STDOUT_FILENO, 0); o_stream_set_no_error_handling(doveadm_print_ostream, TRUE); o_stream_cork(doveadm_print_ostream); doveadm_dump_init(); doveadm_mail_init(); dict_drivers_register_builtin(); doveadm_load_modules(); /* read settings only after loading doveadm plugins, which may modify what settings are read */ doveadm_read_settings(); if (doveadm_debug && getenv("LOG_STDERR_TIMESTAMP") == NULL) i_set_failure_timestamp_format(master_service->set->log_timestamp); master_service_init_stats_client(master_service, TRUE); /* Load mail_plugins */ doveadm_mail_init_finish(); /* kludgy: Load the rest of the doveadm plugins after mail_plugins have been loaded. */ doveadm_load_modules(); if (cmd_name == NULL) { /* show usage after registering all plugins */ usage_prefix(""); } } argc -= optind; argv += optind; i_getopt_reset(); master_service_init_finish(master_service); if (!doveadm_debug) { /* disable debugging unless -D is given */ i_set_debug_file("/dev/null"); } /* this has to be done here because proctitle hack can break the env pointer */ cctx.username = getenv("USER"); if (!doveadm_cmd_try_run_ver2(cmd_name, argc, (const char**)argv, &cctx)) { if (doveadm_has_subcommands(cmd_name)) usage_prefix(cmd_name); if (doveadm_has_unloaded_plugin(cmd_name)) { i_fatal("Unknown command '%s', but plugin %s exists. " "Try to set mail_plugins=%s", cmd_name, cmd_name, cmd_name); } usage(); } if (!quick_init) { doveadm_mail_deinit(); doveadm_dump_deinit(); doveadm_unload_modules(); dict_drivers_unregister_builtin(); doveadm_print_deinit(); o_stream_unref(&doveadm_print_ostream); } doveadm_cmds_deinit(); doveadm_settings_deinit(); master_service_deinit(&master_service); return doveadm_exit_code; } dovecot-2.3.21.1/src/doveadm/Makefile.in0000644000000000000000000015577014656633607014602 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ bin_PROGRAMS = doveadm$(EXEEXT) pkglibexec_PROGRAMS = doveadm-server$(EXEEXT) noinst_PROGRAMS = $(am__EXEEXT_1) subdir = src/doveadm ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(pkginc_lib_HEADERS) $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(pkglibexecdir)" \ "$(DESTDIR)$(pkginc_libdir)" am__EXEEXT_1 = test-doveadm-util$(EXEEXT) PROGRAMS = $(bin_PROGRAMS) $(noinst_PROGRAMS) $(pkglibexec_PROGRAMS) am__objects_1 = doveadm-auth.$(OBJEXT) doveadm-dict.$(OBJEXT) \ doveadm-director.$(OBJEXT) doveadm-fs.$(OBJEXT) \ doveadm-instance.$(OBJEXT) doveadm-kick.$(OBJEXT) \ doveadm-log.$(OBJEXT) doveadm-master.$(OBJEXT) \ doveadm-mutf7.$(OBJEXT) doveadm-penalty.$(OBJEXT) \ doveadm-proxy.$(OBJEXT) doveadm-replicator.$(OBJEXT) \ doveadm-sis.$(OBJEXT) doveadm-stats.$(OBJEXT) \ doveadm-oldstats.$(OBJEXT) doveadm-who.$(OBJEXT) am__objects_2 = doveadm-dsync.$(OBJEXT) doveadm-mail.$(OBJEXT) \ doveadm-mail-altmove.$(OBJEXT) doveadm-mail-batch.$(OBJEXT) \ doveadm-mail-deduplicate.$(OBJEXT) \ doveadm-mail-expunge.$(OBJEXT) doveadm-mail-fetch.$(OBJEXT) \ doveadm-mail-flags.$(OBJEXT) doveadm-mail-import.$(OBJEXT) \ doveadm-mail-index.$(OBJEXT) doveadm-mail-iter.$(OBJEXT) \ doveadm-mail-mailbox.$(OBJEXT) \ doveadm-mail-mailbox-metadata.$(OBJEXT) \ doveadm-mail-mailbox-status.$(OBJEXT) \ doveadm-mail-copymove.$(OBJEXT) \ doveadm-mailbox-list-iter.$(OBJEXT) \ doveadm-mail-save.$(OBJEXT) doveadm-mail-search.$(OBJEXT) \ doveadm-mail-server.$(OBJEXT) \ doveadm-mail-mailbox-cache.$(OBJEXT) \ doveadm-mail-rebuild.$(OBJEXT) am__objects_3 = doveadm-dump.$(OBJEXT) doveadm-dump-dbox.$(OBJEXT) \ doveadm-dump-index.$(OBJEXT) doveadm-dump-log.$(OBJEXT) \ doveadm-dump-mailboxlog.$(OBJEXT) \ doveadm-dump-thread.$(OBJEXT) \ doveadm-dump-dcrypt-file.$(OBJEXT) \ doveadm-dump-dcrypt-key.$(OBJEXT) doveadm-zlib.$(OBJEXT) am__objects_4 = $(am__objects_1) $(am__objects_2) $(am__objects_3) \ doveadm-cmd.$(OBJEXT) doveadm-print.$(OBJEXT) \ doveadm-settings.$(OBJEXT) doveadm-util.$(OBJEXT) \ server-connection.$(OBJEXT) doveadm-print-formatted.$(OBJEXT) am_doveadm_OBJECTS = $(am__objects_4) doveadm.$(OBJEXT) \ doveadm-print-flow.$(OBJEXT) doveadm-print-pager.$(OBJEXT) \ doveadm-print-tab.$(OBJEXT) doveadm-print-table.$(OBJEXT) \ doveadm-print-json.$(OBJEXT) doveadm-pw.$(OBJEXT) doveadm_OBJECTS = $(am_doveadm_OBJECTS) am__DEPENDENCIES_1 = AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = am_doveadm_server_OBJECTS = $(am__objects_4) \ doveadm-auth-server.$(OBJEXT) client-connection.$(OBJEXT) \ client-connection-tcp.$(OBJEXT) \ client-connection-http.$(OBJEXT) \ doveadm-print-server.$(OBJEXT) doveadm-print-json.$(OBJEXT) \ main.$(OBJEXT) doveadm_server_OBJECTS = $(am_doveadm_server_OBJECTS) am_test_doveadm_util_OBJECTS = doveadm-util.$(OBJEXT) \ test-doveadm-util.$(OBJEXT) test_doveadm_util_OBJECTS = $(am_test_doveadm_util_OBJECTS) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/client-connection-http.Po \ ./$(DEPDIR)/client-connection-tcp.Po \ ./$(DEPDIR)/client-connection.Po \ ./$(DEPDIR)/doveadm-auth-server.Po ./$(DEPDIR)/doveadm-auth.Po \ ./$(DEPDIR)/doveadm-cmd.Po ./$(DEPDIR)/doveadm-dict.Po \ ./$(DEPDIR)/doveadm-director.Po ./$(DEPDIR)/doveadm-dsync.Po \ ./$(DEPDIR)/doveadm-dump-dbox.Po \ ./$(DEPDIR)/doveadm-dump-dcrypt-file.Po \ ./$(DEPDIR)/doveadm-dump-dcrypt-key.Po \ ./$(DEPDIR)/doveadm-dump-index.Po \ ./$(DEPDIR)/doveadm-dump-log.Po \ ./$(DEPDIR)/doveadm-dump-mailboxlog.Po \ ./$(DEPDIR)/doveadm-dump-thread.Po ./$(DEPDIR)/doveadm-dump.Po \ ./$(DEPDIR)/doveadm-fs.Po ./$(DEPDIR)/doveadm-instance.Po \ ./$(DEPDIR)/doveadm-kick.Po ./$(DEPDIR)/doveadm-log.Po \ ./$(DEPDIR)/doveadm-mail-altmove.Po \ ./$(DEPDIR)/doveadm-mail-batch.Po \ ./$(DEPDIR)/doveadm-mail-copymove.Po \ ./$(DEPDIR)/doveadm-mail-deduplicate.Po \ ./$(DEPDIR)/doveadm-mail-expunge.Po \ ./$(DEPDIR)/doveadm-mail-fetch.Po \ ./$(DEPDIR)/doveadm-mail-flags.Po \ ./$(DEPDIR)/doveadm-mail-import.Po \ ./$(DEPDIR)/doveadm-mail-index.Po \ ./$(DEPDIR)/doveadm-mail-iter.Po \ ./$(DEPDIR)/doveadm-mail-mailbox-cache.Po \ ./$(DEPDIR)/doveadm-mail-mailbox-metadata.Po \ ./$(DEPDIR)/doveadm-mail-mailbox-status.Po \ ./$(DEPDIR)/doveadm-mail-mailbox.Po \ ./$(DEPDIR)/doveadm-mail-rebuild.Po \ ./$(DEPDIR)/doveadm-mail-save.Po \ ./$(DEPDIR)/doveadm-mail-search.Po \ ./$(DEPDIR)/doveadm-mail-server.Po ./$(DEPDIR)/doveadm-mail.Po \ ./$(DEPDIR)/doveadm-mailbox-list-iter.Po \ ./$(DEPDIR)/doveadm-master.Po ./$(DEPDIR)/doveadm-mutf7.Po \ ./$(DEPDIR)/doveadm-oldstats.Po ./$(DEPDIR)/doveadm-penalty.Po \ ./$(DEPDIR)/doveadm-print-flow.Po \ ./$(DEPDIR)/doveadm-print-formatted.Po \ ./$(DEPDIR)/doveadm-print-json.Po \ ./$(DEPDIR)/doveadm-print-pager.Po \ ./$(DEPDIR)/doveadm-print-server.Po \ ./$(DEPDIR)/doveadm-print-tab.Po \ ./$(DEPDIR)/doveadm-print-table.Po \ ./$(DEPDIR)/doveadm-print.Po ./$(DEPDIR)/doveadm-proxy.Po \ ./$(DEPDIR)/doveadm-pw.Po ./$(DEPDIR)/doveadm-replicator.Po \ ./$(DEPDIR)/doveadm-settings.Po ./$(DEPDIR)/doveadm-sis.Po \ ./$(DEPDIR)/doveadm-stats.Po ./$(DEPDIR)/doveadm-util.Po \ ./$(DEPDIR)/doveadm-who.Po ./$(DEPDIR)/doveadm-zlib.Po \ ./$(DEPDIR)/doveadm.Po ./$(DEPDIR)/main.Po \ ./$(DEPDIR)/server-connection.Po \ ./$(DEPDIR)/test-doveadm-util.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(doveadm_SOURCES) $(doveadm_server_SOURCES) \ $(test_doveadm_util_SOURCES) DIST_SOURCES = $(doveadm_SOURCES) $(doveadm_server_SOURCES) \ $(test_doveadm_util_SOURCES) RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ ctags-recursive dvi-recursive html-recursive info-recursive \ install-data-recursive install-dvi-recursive \ install-exec-recursive install-html-recursive \ install-info-recursive install-pdf-recursive \ install-ps-recursive install-recursive installcheck-recursive \ installdirs-recursive pdf-recursive ps-recursive \ tags-recursive uninstall-recursive am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ *) f=$$p;; \ esac; am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; am__install_max = 40 am__nobase_strip_setup = \ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` am__nobase_strip = \ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" am__nobase_list = $(am__nobase_strip_setup); \ for p in $$list; do echo "$$p $$p"; done | \ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ if (++n[$$2] == $(am__install_max)) \ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ END { for (dir in files) print dir, files[dir] }' am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' am__uninstall_files_from_dir = { \ test -z "$$files" \ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ $(am__cd) "$$dir" && rm -f $$files; }; \ } HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS) RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ distclean-recursive maintainer-clean-recursive am__recursive_targets = \ $(RECURSIVE_TARGETS) \ $(RECURSIVE_CLEAN_TARGETS) \ $(am__extra_recursive_targets) AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ distdir distdir-am am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags DIST_SUBDIRS = $(SUBDIRS) am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) am__relativize = \ dir0=`pwd`; \ sed_first='s,^\([^/]*\)/.*$$,\1,'; \ sed_rest='s,^[^/]*/*,,'; \ sed_last='s,^.*/\([^/]*\)$$,\1,'; \ sed_butlast='s,/*[^/]*$$,,'; \ while test -n "$$dir1"; do \ first=`echo "$$dir1" | sed -e "$$sed_first"`; \ if test "$$first" != "."; then \ if test "$$first" = ".."; then \ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ else \ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ if test "$$first2" = "$$first"; then \ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ else \ dir2="../$$dir2"; \ fi; \ dir0="$$dir0"/"$$first"; \ fi; \ fi; \ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ done; \ reldir="$$dir2" pkglibexecdir = $(libexecdir)/dovecot ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ doveadm_moduledir = $(moduledir)/doveadm SUBDIRS = dsync AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-compression \ -I$(top_srcdir)/src/lib-dict \ -I$(top_srcdir)/src/lib-fs \ -I$(top_srcdir)/src/lib-ssl-iostream \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-storage \ -I$(top_srcdir)/src/lib-imap-storage \ -I$(top_srcdir)/src/lib-http \ -I$(top_srcdir)/src/lib-dcrypt \ -I$(top_srcdir)/src/auth \ -I$(top_srcdir)/src/stats \ -DMODULEDIR=\""$(moduledir)"\" \ -DAUTH_MODULE_DIR=\""$(moduledir)/auth"\" \ -DDOVEADM_MODULEDIR=\""$(doveadm_moduledir)"\" \ -DPKG_RUNDIR=\""$(rundir)"\" \ -DPKG_STATEDIR=\""$(statedir)"\" \ -DPKG_LIBEXECDIR=\""$(pkglibexecdir)"\" \ -DBINDIR=\""$(bindir)"\" \ -DMANDIR=\""$(mandir)"\" \ $(BINARY_CFLAGS) cmd_pw_libs = \ ../auth/libpassword.la \ ../lib-otp/libotp.la libs = \ dsync/libdsync.la \ ../lib-compression/libcompression.la doveadm_LDADD = \ $(libs) \ $(cmd_pw_libs) \ $(CRYPT_LIBS) \ $(LIBDOVECOT_STORAGE) \ $(LIBDOVECOT) \ $(LIBSODIUM_LIBS) \ $(BINARY_LDFLAGS) \ -lm doveadm_DEPENDENCIES = \ $(libs) \ $(cmd_pw_libs) \ $(LIBDOVECOT_STORAGE_DEPS) \ $(LIBDOVECOT_DEPS) doveadm_server_LDADD = \ $(libs) \ $(LIBDOVECOT_STORAGE) \ $(LIBDOVECOT) \ $(BINARY_LDFLAGS) \ -lm doveadm_server_DEPENDENCIES = \ $(libs) \ $(LIBDOVECOT_STORAGE_DEPS) \ $(LIBDOVECOT_DEPS) doveadm_common_cmds = \ doveadm-auth.c \ doveadm-dict.c \ doveadm-director.c \ doveadm-fs.c \ doveadm-instance.c \ doveadm-kick.c \ doveadm-log.c \ doveadm-master.c \ doveadm-mutf7.c \ doveadm-penalty.c \ doveadm-proxy.c \ doveadm-replicator.c \ doveadm-sis.c \ doveadm-stats.c \ doveadm-oldstats.c \ doveadm-who.c doveadm_common_mail_cmds = \ doveadm-dsync.c \ doveadm-mail.c \ doveadm-mail-altmove.c \ doveadm-mail-batch.c \ doveadm-mail-deduplicate.c \ doveadm-mail-expunge.c \ doveadm-mail-fetch.c \ doveadm-mail-flags.c \ doveadm-mail-import.c \ doveadm-mail-index.c \ doveadm-mail-iter.c \ doveadm-mail-mailbox.c \ doveadm-mail-mailbox-metadata.c \ doveadm-mail-mailbox-status.c \ doveadm-mail-copymove.c \ doveadm-mailbox-list-iter.c \ doveadm-mail-save.c \ doveadm-mail-search.c \ doveadm-mail-server.c \ doveadm-mail-mailbox-cache.c \ doveadm-mail-rebuild.c # these aren't actually useful in doveadm-server, but plugins may implement # both dumping and some other commands inside a single plugin. not having the # dump functions in doveadm-server fails to load such plugins. doveadm_common_dump_cmds = \ doveadm-dump.c \ doveadm-dump-dbox.c \ doveadm-dump-index.c \ doveadm-dump-log.c \ doveadm-dump-mailboxlog.c \ doveadm-dump-thread.c \ doveadm-dump-dcrypt-file.c \ doveadm-dump-dcrypt-key.c \ doveadm-zlib.c common = \ $(doveadm_common_cmds) \ $(doveadm_common_mail_cmds) \ $(doveadm_common_dump_cmds) \ doveadm-cmd.c \ doveadm-print.c \ doveadm-settings.c \ doveadm-util.c \ server-connection.c \ doveadm-print-formatted.c doveadm_SOURCES = \ $(common) \ doveadm.c \ doveadm-print-flow.c \ doveadm-print-pager.c \ doveadm-print-tab.c \ doveadm-print-table.c \ doveadm-print-json.c \ doveadm-pw.c doveadm_server_SOURCES = \ $(common) \ doveadm-auth-server.c \ client-connection.c \ client-connection-tcp.c \ client-connection-http.c \ doveadm-print-server.c \ doveadm-print-json.c \ main.c pkginc_libdir = $(pkgincludedir) pkginc_lib_HEADERS = \ doveadm.h \ doveadm-cmd.h \ doveadm-dsync.h \ doveadm-dump.h \ doveadm-mail.h \ doveadm-mail-iter.h \ doveadm-mailbox-list-iter.h \ doveadm-print.h \ doveadm-print-private.h \ doveadm-settings.h \ doveadm-util.h noinst_HEADERS = \ client-connection.h \ client-connection-private.h \ server-connection.h \ doveadm-server.h \ doveadm-who.h test_programs = \ test-doveadm-util test_libs = \ ../lib-test/libtest.la \ ../lib/liblib.la test_deps = $(noinst_LTLIBRARIES) $(test_libs) test_doveadm_util_SOURCES = doveadm-util.c test-doveadm-util.c test_doveadm_util_LDADD = $(test_libs) $(MODULE_LIBS) test_doveadm_util_DEPENDENCIES = $(test_deps) all: all-recursive .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/doveadm/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/doveadm/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): install-binPROGRAMS: $(bin_PROGRAMS) @$(NORMAL_INSTALL) @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ } \ ; done uninstall-binPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(bindir)" && rm -f $$files clean-binPROGRAMS: @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list clean-noinstPROGRAMS: @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files clean-pkglibexecPROGRAMS: @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list doveadm$(EXEEXT): $(doveadm_OBJECTS) $(doveadm_DEPENDENCIES) $(EXTRA_doveadm_DEPENDENCIES) @rm -f doveadm$(EXEEXT) $(AM_V_CCLD)$(LINK) $(doveadm_OBJECTS) $(doveadm_LDADD) $(LIBS) doveadm-server$(EXEEXT): $(doveadm_server_OBJECTS) $(doveadm_server_DEPENDENCIES) $(EXTRA_doveadm_server_DEPENDENCIES) @rm -f doveadm-server$(EXEEXT) $(AM_V_CCLD)$(LINK) $(doveadm_server_OBJECTS) $(doveadm_server_LDADD) $(LIBS) test-doveadm-util$(EXEEXT): $(test_doveadm_util_OBJECTS) $(test_doveadm_util_DEPENDENCIES) $(EXTRA_test_doveadm_util_DEPENDENCIES) @rm -f test-doveadm-util$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_doveadm_util_OBJECTS) $(test_doveadm_util_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client-connection-http.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client-connection-tcp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client-connection.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-auth-server.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-auth.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-cmd.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-dict.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-director.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-dsync.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-dump-dbox.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-dump-dcrypt-file.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-dump-dcrypt-key.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-dump-index.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-dump-log.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-dump-mailboxlog.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-dump-thread.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-dump.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-fs.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-instance.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-kick.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-log.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-altmove.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-batch.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-copymove.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-deduplicate.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-expunge.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-fetch.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-flags.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-import.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-index.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-iter.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-mailbox-cache.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-mailbox-metadata.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-mailbox-status.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-mailbox.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-rebuild.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-save.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-search.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail-server.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mail.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mailbox-list-iter.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-master.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-mutf7.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-oldstats.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-penalty.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-print-flow.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-print-formatted.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-print-json.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-print-pager.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-print-server.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-print-tab.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-print-table.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-print.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-proxy.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-pw.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-replicator.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-settings.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-sis.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-stats.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-util.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-who.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm-zlib.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doveadm.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server-connection.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-doveadm-util.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) @$(NORMAL_INSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ done uninstall-pkginc_libHEADERS: @$(NORMAL_UNINSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) # This directory's subdirectories are mostly independent; you can cd # into them and run 'make' without going through this Makefile. # To change the values of 'make' variables: instead of editing Makefiles, # (1) if the variable is set in 'config.status', edit 'config.status' # (which will cause the Makefiles to be regenerated when you run 'make'); # (2) otherwise, pass the desired values on the 'make' command line. $(am__recursive_targets): @fail=; \ if $(am__make_keepgoing); then \ failcom='fail=yes'; \ else \ failcom='exit 1'; \ fi; \ dot_seen=no; \ target=`echo $@ | sed s/-recursive//`; \ case "$@" in \ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ *) list='$(SUBDIRS)' ;; \ esac; \ for subdir in $$list; do \ echo "Making $$target in $$subdir"; \ if test "$$subdir" = "."; then \ dot_seen=yes; \ local_target="$$target-am"; \ else \ local_target="$$target"; \ fi; \ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ || eval $$failcom; \ done; \ if test "$$dot_seen" = "no"; then \ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ fi; test -z "$$fail" ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-recursive TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ include_option=--etags-include; \ empty_fix=.; \ else \ include_option=--include; \ empty_fix=; \ fi; \ list='$(SUBDIRS)'; for subdir in $$list; do \ if test "$$subdir" = .; then :; else \ test ! -f $$subdir/TAGS || \ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ fi; \ done; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-recursive CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-recursive cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ if test "$$subdir" = .; then :; else \ $(am__make_dryrun) \ || test -d "$(distdir)/$$subdir" \ || $(MKDIR_P) "$(distdir)/$$subdir" \ || exit 1; \ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ $(am__relativize); \ new_distdir=$$reldir; \ dir1=$$subdir; dir2="$(top_distdir)"; \ $(am__relativize); \ new_top_distdir=$$reldir; \ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ ($(am__cd) $$subdir && \ $(MAKE) $(AM_MAKEFLAGS) \ top_distdir="$$new_top_distdir" \ distdir="$$new_distdir" \ am__remove_distdir=: \ am__skip_length_check=: \ am__skip_mode_fix=: \ distdir) \ || exit 1; \ fi; \ done check-am: all-am $(MAKE) $(AM_MAKEFLAGS) check-local check: check-recursive all-am: Makefile $(PROGRAMS) $(HEADERS) installdirs: installdirs-recursive installdirs-am: for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(pkginc_libdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-recursive install-exec: install-exec-recursive install-data: install-data-recursive uninstall: uninstall-recursive install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-recursive install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-recursive clean-am: clean-binPROGRAMS clean-generic clean-libtool \ clean-noinstPROGRAMS clean-pkglibexecPROGRAMS mostlyclean-am distclean: distclean-recursive -rm -f ./$(DEPDIR)/client-connection-http.Po -rm -f ./$(DEPDIR)/client-connection-tcp.Po -rm -f ./$(DEPDIR)/client-connection.Po -rm -f ./$(DEPDIR)/doveadm-auth-server.Po -rm -f ./$(DEPDIR)/doveadm-auth.Po -rm -f ./$(DEPDIR)/doveadm-cmd.Po -rm -f ./$(DEPDIR)/doveadm-dict.Po -rm -f ./$(DEPDIR)/doveadm-director.Po -rm -f ./$(DEPDIR)/doveadm-dsync.Po -rm -f ./$(DEPDIR)/doveadm-dump-dbox.Po -rm -f ./$(DEPDIR)/doveadm-dump-dcrypt-file.Po -rm -f ./$(DEPDIR)/doveadm-dump-dcrypt-key.Po -rm -f ./$(DEPDIR)/doveadm-dump-index.Po -rm -f ./$(DEPDIR)/doveadm-dump-log.Po -rm -f ./$(DEPDIR)/doveadm-dump-mailboxlog.Po -rm -f ./$(DEPDIR)/doveadm-dump-thread.Po -rm -f ./$(DEPDIR)/doveadm-dump.Po -rm -f ./$(DEPDIR)/doveadm-fs.Po -rm -f ./$(DEPDIR)/doveadm-instance.Po -rm -f ./$(DEPDIR)/doveadm-kick.Po -rm -f ./$(DEPDIR)/doveadm-log.Po -rm -f ./$(DEPDIR)/doveadm-mail-altmove.Po -rm -f ./$(DEPDIR)/doveadm-mail-batch.Po -rm -f ./$(DEPDIR)/doveadm-mail-copymove.Po -rm -f ./$(DEPDIR)/doveadm-mail-deduplicate.Po -rm -f ./$(DEPDIR)/doveadm-mail-expunge.Po -rm -f ./$(DEPDIR)/doveadm-mail-fetch.Po -rm -f ./$(DEPDIR)/doveadm-mail-flags.Po -rm -f ./$(DEPDIR)/doveadm-mail-import.Po -rm -f ./$(DEPDIR)/doveadm-mail-index.Po -rm -f ./$(DEPDIR)/doveadm-mail-iter.Po -rm -f ./$(DEPDIR)/doveadm-mail-mailbox-cache.Po -rm -f ./$(DEPDIR)/doveadm-mail-mailbox-metadata.Po -rm -f ./$(DEPDIR)/doveadm-mail-mailbox-status.Po -rm -f ./$(DEPDIR)/doveadm-mail-mailbox.Po -rm -f ./$(DEPDIR)/doveadm-mail-rebuild.Po -rm -f ./$(DEPDIR)/doveadm-mail-save.Po -rm -f ./$(DEPDIR)/doveadm-mail-search.Po -rm -f ./$(DEPDIR)/doveadm-mail-server.Po -rm -f ./$(DEPDIR)/doveadm-mail.Po -rm -f ./$(DEPDIR)/doveadm-mailbox-list-iter.Po -rm -f ./$(DEPDIR)/doveadm-master.Po -rm -f ./$(DEPDIR)/doveadm-mutf7.Po -rm -f ./$(DEPDIR)/doveadm-oldstats.Po -rm -f ./$(DEPDIR)/doveadm-penalty.Po -rm -f ./$(DEPDIR)/doveadm-print-flow.Po -rm -f ./$(DEPDIR)/doveadm-print-formatted.Po -rm -f ./$(DEPDIR)/doveadm-print-json.Po -rm -f ./$(DEPDIR)/doveadm-print-pager.Po -rm -f ./$(DEPDIR)/doveadm-print-server.Po -rm -f ./$(DEPDIR)/doveadm-print-tab.Po -rm -f ./$(DEPDIR)/doveadm-print-table.Po -rm -f ./$(DEPDIR)/doveadm-print.Po -rm -f ./$(DEPDIR)/doveadm-proxy.Po -rm -f ./$(DEPDIR)/doveadm-pw.Po -rm -f ./$(DEPDIR)/doveadm-replicator.Po -rm -f ./$(DEPDIR)/doveadm-settings.Po -rm -f ./$(DEPDIR)/doveadm-sis.Po -rm -f ./$(DEPDIR)/doveadm-stats.Po -rm -f ./$(DEPDIR)/doveadm-util.Po -rm -f ./$(DEPDIR)/doveadm-who.Po -rm -f ./$(DEPDIR)/doveadm-zlib.Po -rm -f ./$(DEPDIR)/doveadm.Po -rm -f ./$(DEPDIR)/main.Po -rm -f ./$(DEPDIR)/server-connection.Po -rm -f ./$(DEPDIR)/test-doveadm-util.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-recursive dvi-am: html: html-recursive html-am: info: info-recursive info-am: install-data-am: install-pkginc_libHEADERS install-dvi: install-dvi-recursive install-dvi-am: install-exec-am: install-binPROGRAMS install-exec-local \ install-pkglibexecPROGRAMS install-html: install-html-recursive install-html-am: install-info: install-info-recursive install-info-am: install-man: install-pdf: install-pdf-recursive install-pdf-am: install-ps: install-ps-recursive install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-recursive -rm -f ./$(DEPDIR)/client-connection-http.Po -rm -f ./$(DEPDIR)/client-connection-tcp.Po -rm -f ./$(DEPDIR)/client-connection.Po -rm -f ./$(DEPDIR)/doveadm-auth-server.Po -rm -f ./$(DEPDIR)/doveadm-auth.Po -rm -f ./$(DEPDIR)/doveadm-cmd.Po -rm -f ./$(DEPDIR)/doveadm-dict.Po -rm -f ./$(DEPDIR)/doveadm-director.Po -rm -f ./$(DEPDIR)/doveadm-dsync.Po -rm -f ./$(DEPDIR)/doveadm-dump-dbox.Po -rm -f ./$(DEPDIR)/doveadm-dump-dcrypt-file.Po -rm -f ./$(DEPDIR)/doveadm-dump-dcrypt-key.Po -rm -f ./$(DEPDIR)/doveadm-dump-index.Po -rm -f ./$(DEPDIR)/doveadm-dump-log.Po -rm -f ./$(DEPDIR)/doveadm-dump-mailboxlog.Po -rm -f ./$(DEPDIR)/doveadm-dump-thread.Po -rm -f ./$(DEPDIR)/doveadm-dump.Po -rm -f ./$(DEPDIR)/doveadm-fs.Po -rm -f ./$(DEPDIR)/doveadm-instance.Po -rm -f ./$(DEPDIR)/doveadm-kick.Po -rm -f ./$(DEPDIR)/doveadm-log.Po -rm -f ./$(DEPDIR)/doveadm-mail-altmove.Po -rm -f ./$(DEPDIR)/doveadm-mail-batch.Po -rm -f ./$(DEPDIR)/doveadm-mail-copymove.Po -rm -f ./$(DEPDIR)/doveadm-mail-deduplicate.Po -rm -f ./$(DEPDIR)/doveadm-mail-expunge.Po -rm -f ./$(DEPDIR)/doveadm-mail-fetch.Po -rm -f ./$(DEPDIR)/doveadm-mail-flags.Po -rm -f ./$(DEPDIR)/doveadm-mail-import.Po -rm -f ./$(DEPDIR)/doveadm-mail-index.Po -rm -f ./$(DEPDIR)/doveadm-mail-iter.Po -rm -f ./$(DEPDIR)/doveadm-mail-mailbox-cache.Po -rm -f ./$(DEPDIR)/doveadm-mail-mailbox-metadata.Po -rm -f ./$(DEPDIR)/doveadm-mail-mailbox-status.Po -rm -f ./$(DEPDIR)/doveadm-mail-mailbox.Po -rm -f ./$(DEPDIR)/doveadm-mail-rebuild.Po -rm -f ./$(DEPDIR)/doveadm-mail-save.Po -rm -f ./$(DEPDIR)/doveadm-mail-search.Po -rm -f ./$(DEPDIR)/doveadm-mail-server.Po -rm -f ./$(DEPDIR)/doveadm-mail.Po -rm -f ./$(DEPDIR)/doveadm-mailbox-list-iter.Po -rm -f ./$(DEPDIR)/doveadm-master.Po -rm -f ./$(DEPDIR)/doveadm-mutf7.Po -rm -f ./$(DEPDIR)/doveadm-oldstats.Po -rm -f ./$(DEPDIR)/doveadm-penalty.Po -rm -f ./$(DEPDIR)/doveadm-print-flow.Po -rm -f ./$(DEPDIR)/doveadm-print-formatted.Po -rm -f ./$(DEPDIR)/doveadm-print-json.Po -rm -f ./$(DEPDIR)/doveadm-print-pager.Po -rm -f ./$(DEPDIR)/doveadm-print-server.Po -rm -f ./$(DEPDIR)/doveadm-print-tab.Po -rm -f ./$(DEPDIR)/doveadm-print-table.Po -rm -f ./$(DEPDIR)/doveadm-print.Po -rm -f ./$(DEPDIR)/doveadm-proxy.Po -rm -f ./$(DEPDIR)/doveadm-pw.Po -rm -f ./$(DEPDIR)/doveadm-replicator.Po -rm -f ./$(DEPDIR)/doveadm-settings.Po -rm -f ./$(DEPDIR)/doveadm-sis.Po -rm -f ./$(DEPDIR)/doveadm-stats.Po -rm -f ./$(DEPDIR)/doveadm-util.Po -rm -f ./$(DEPDIR)/doveadm-who.Po -rm -f ./$(DEPDIR)/doveadm-zlib.Po -rm -f ./$(DEPDIR)/doveadm.Po -rm -f ./$(DEPDIR)/main.Po -rm -f ./$(DEPDIR)/server-connection.Po -rm -f ./$(DEPDIR)/test-doveadm-util.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-recursive mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-recursive pdf-am: ps: ps-recursive ps-am: uninstall-am: uninstall-binPROGRAMS uninstall-pkginc_libHEADERS \ uninstall-pkglibexecPROGRAMS .MAKE: $(am__recursive_targets) check-am install-am install-strip .PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \ am--depfiles check check-am check-local clean \ clean-binPROGRAMS clean-generic clean-libtool \ clean-noinstPROGRAMS clean-pkglibexecPROGRAMS cscopelist-am \ ctags ctags-am distclean distclean-compile distclean-generic \ distclean-libtool distclean-tags distdir dvi dvi-am html \ html-am info info-am install install-am install-binPROGRAMS \ install-data install-data-am install-dvi install-dvi-am \ install-exec install-exec-am install-exec-local install-html \ install-html-am install-info install-info-am install-man \ install-pdf install-pdf-am install-pkginc_libHEADERS \ install-pkglibexecPROGRAMS install-ps install-ps-am \ install-strip installcheck installcheck-am installdirs \ installdirs-am maintainer-clean maintainer-clean-generic \ mostlyclean mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ uninstall-am uninstall-binPROGRAMS uninstall-pkginc_libHEADERS \ uninstall-pkglibexecPROGRAMS .PRECIOUS: Makefile install-exec-local: rm -f $(DESTDIR)$(bindir)/dsync $(LN_S) doveadm $(DESTDIR)$(bindir)/dsync check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/doveadm/doveadm.h0000644000000000000000000000140414656633576014312 00000000000000#ifndef DOVEADM_H #define DOVEADM_H #include #include "doveadm-util.h" #include "doveadm-settings.h" #define USAGE_CMDNAME_FMT " %-12s" #define DOVEADM_EX_NOTFOUND EX_NOHOST #define DOVEADM_EX_NOTPOSSIBLE EX_DATAERR #define DOVEADM_EX_UNKNOWN -1 #define DOVEADM_EX_NOREPLICATE 1001 enum doveadm_client_type { DOVEADM_CONNECTION_TYPE_CLI = 0, DOVEADM_CONNECTION_TYPE_TCP, DOVEADM_CONNECTION_TYPE_HTTP, }; #include "doveadm-cmd.h" extern bool doveadm_verbose_proctitle; extern int doveadm_exit_code; void usage(void) ATTR_NORETURN; void help_ver2(const struct doveadm_cmd_ver2 *cmd) ATTR_NORETURN; void doveadm_master_send_signal(int signo); const char *doveadm_exit_code_to_str(int code); int doveadm_str_to_exit_code(const char *reason); #endif dovecot-2.3.21.1/src/doveadm/doveadm-replicator.c0000644000000000000000000002442414656633576016456 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "str.h" #include "strescape.h" #include "istream.h" #include "write-full.h" #include "master-service.h" #include "doveadm.h" #include "doveadm-print.h" #include #include #include struct replicator_context { const char *socket_path; const char *priority; const char *user_mask, *username; struct istream *input; bool full_sync; }; extern struct doveadm_cmd_ver2 doveadm_cmd_replicator[]; static void replicator_cmd_help(const struct doveadm_cmd_ver2 *cmd) ATTR_NORETURN; static void replicator_send(struct replicator_context *ctx, const char *data) { if (write_full(i_stream_get_fd(ctx->input), data, strlen(data)) < 0) i_fatal("write(%s) failed: %m", ctx->socket_path); } static void replicator_connect(struct replicator_context *ctx) { #define REPLICATOR_HANDSHAKE "VERSION\treplicator-doveadm-client\t1\t0\n" const char *line; int fd; fd = doveadm_connect(ctx->socket_path); net_set_nonblock(fd, FALSE); ctx->input = i_stream_create_fd_autoclose(&fd, SIZE_MAX); replicator_send(ctx, REPLICATOR_HANDSHAKE); alarm(5); line = i_stream_read_next_line(ctx->input); alarm(0); if (line == NULL) { if (ctx->input->stream_errno != 0) { i_fatal("read(%s) failed: %s", ctx->socket_path, i_stream_get_error(ctx->input)); } else if (ctx->input->eof) i_fatal("%s disconnected", ctx->socket_path); else i_fatal("read(%s) timed out", ctx->socket_path); } if (!version_string_verify(line, "replicator-doveadm-server", 1)) { i_fatal_status(EX_PROTOCOL, "%s not a compatible replicator-doveadm socket", ctx->socket_path); } } static void replicator_disconnect(struct replicator_context *ctx) { if (ctx->input->stream_errno != 0) { i_fatal("read(%s) failed: %s", ctx->socket_path, i_stream_get_error(ctx->input)); } i_stream_destroy(&ctx->input); } static struct replicator_context * cmd_replicator_init(struct doveadm_cmd_context *cctx) { struct replicator_context *ctx; ctx = t_new(struct replicator_context, 1); ctx->socket_path = t_strconcat(doveadm_settings->base_dir, "/replicator-doveadm", NULL); (void)doveadm_cmd_param_str(cctx, "socket-path", &ctx->socket_path); (void)doveadm_cmd_param_bool(cctx, "full-sync", &ctx->full_sync); (void)doveadm_cmd_param_str(cctx, "priority", &ctx->priority); (void)doveadm_cmd_param_str(cctx, "user-mask", &ctx->user_mask); (void)doveadm_cmd_param_str(cctx, "user", &ctx->username); replicator_connect(ctx); return ctx; } static const char *time_formatted_hms(unsigned int secs) { return t_strdup_printf("%02d:%02d:%02d", secs/3600, (secs/60)%60, secs%60); } static const char *time_ago(time_t t) { int diff = ioloop_time - t; if (t == 0) return "-"; return time_formatted_hms(diff); } static void cmd_replicator_status_overview(struct replicator_context *ctx) { char *line, *value; doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); doveadm_print_header("field", "field", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); doveadm_print_header("value", "value", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); replicator_send(ctx, "STATUS\n"); while ((line = i_stream_read_next_line(ctx->input)) != NULL) { if (*line == '\0') break; value = strchr(line, '\t'); if (value != NULL) *value++ = '\0'; else value = ""; doveadm_print(line); doveadm_print(value); } replicator_disconnect(ctx); } static void cmd_replicator_status(struct doveadm_cmd_context *cctx) { struct replicator_context *ctx; const char *line, *const *args; time_t last_fast, last_full, last_success; unsigned int next_secs; ctx = cmd_replicator_init(cctx); if (ctx->user_mask == NULL) { cmd_replicator_status_overview(ctx); return; } doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); doveadm_print_header("username", "username", DOVEADM_PRINT_HEADER_FLAG_EXPAND); doveadm_print_header_simple("priority"); doveadm_print_header_simple("fast sync"); doveadm_print_header_simple("full sync"); doveadm_print_header_simple("success sync"); doveadm_print_header_simple("failed"); doveadm_print_header_simple("next sync secs"); replicator_send(ctx, t_strdup_printf("STATUS\t%s\n", str_tabescape(ctx->user_mask))); while ((line = i_stream_read_next_line(ctx->input)) != NULL) { if (*line == '\0') break; T_BEGIN { args = t_strsplit_tabescaped(line); if (str_array_length(args) >= 6 && str_to_time(args[2], &last_fast) == 0 && str_to_time(args[3], &last_full) == 0 && str_to_time(args[5], &last_success) == 0 && str_to_uint(args[6], &next_secs) == 0) { doveadm_print(args[0]); doveadm_print(args[1]); doveadm_print(time_ago(last_fast)); doveadm_print(time_ago(last_full)); doveadm_print(time_ago(last_success)); doveadm_print(args[4][0] == '0' ? "-" : "y"); doveadm_print(time_formatted_hms(next_secs)); } } T_END; } if (line == NULL) { i_error("Replicator disconnected unexpectedly"); doveadm_exit_code = EX_TEMPFAIL; } replicator_disconnect(ctx); } static void cmd_replicator_dsync_status(struct doveadm_cmd_context *cctx) { struct replicator_context *ctx; const char *line; unsigned int i; ctx = cmd_replicator_init(cctx); doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); doveadm_print_header("username", "username", DOVEADM_PRINT_HEADER_FLAG_EXPAND); doveadm_print_header_simple("type"); doveadm_print_header_simple("status"); replicator_send(ctx, "STATUS-DSYNC\n"); while ((line = i_stream_read_next_line(ctx->input)) != NULL) { if (*line == '\0') break; T_BEGIN { const char *const *args = t_strsplit_tabescaped(line); for (i = 0; i < 3; i++) { if (args[i] == NULL) break; doveadm_print(args[i]); } for (; i < 3; i++) doveadm_print(""); } T_END; } replicator_disconnect(ctx); } static void cmd_replicator_replicate(struct doveadm_cmd_context *cctx) { struct replicator_context *ctx; string_t *str; const char *line; ctx = cmd_replicator_init(cctx); if (ctx->user_mask == NULL) replicator_cmd_help(cctx->cmd); str = t_str_new(128); str_append(str, "REPLICATE\t"); if (ctx->priority == NULL) str_append_tabescaped(str, "low"); else str_append_tabescaped(str, ctx->priority); str_append_c(str, '\t'); if (ctx->full_sync) str_append_c(str, 'f'); str_append_c(str, '\t'); str_append_tabescaped(str, ctx->user_mask); str_append_c(str, '\n'); replicator_send(ctx, str_c(str)); doveadm_print_init(DOVEADM_PRINT_TYPE_FLOW); doveadm_print_header("result", "result", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); line = i_stream_read_next_line(ctx->input); if (line == NULL) { i_error("Replicator disconnected unexpectedly"); doveadm_exit_code = EX_TEMPFAIL; } else if (line[0] != '+') { i_error("Replicator failed: %s", line+1); doveadm_exit_code = EX_USAGE; } else { doveadm_print(t_strdup_printf("%s users updated", line+1)); } replicator_disconnect(ctx); } static void cmd_replicator_add(struct doveadm_cmd_context *cctx) { struct replicator_context *ctx; string_t *str; const char *line; ctx = cmd_replicator_init(cctx); if (ctx->user_mask == NULL) replicator_cmd_help(cctx->cmd); str = t_str_new(128); str_append(str, "ADD\t"); str_append_tabescaped(str, ctx->user_mask); str_append_c(str, '\n'); replicator_send(ctx, str_c(str)); line = i_stream_read_next_line(ctx->input); if (line == NULL) { i_error("Replicator disconnected unexpectedly"); doveadm_exit_code = EX_TEMPFAIL; } else if (line[0] != '+') { i_error("Replicator failed: %s", line+1); doveadm_exit_code = EX_USAGE; } replicator_disconnect(ctx); } static void cmd_replicator_remove(struct doveadm_cmd_context *cctx) { struct replicator_context *ctx; string_t *str; const char *line; ctx = cmd_replicator_init(cctx); if (ctx->username == NULL) replicator_cmd_help(cctx->cmd); str = t_str_new(128); str_append(str, "REMOVE\t"); str_append_tabescaped(str, ctx->username); str_append_c(str, '\n'); replicator_send(ctx, str_c(str)); line = i_stream_read_next_line(ctx->input); if (line == NULL) { i_error("Replicator disconnected unexpectedly"); doveadm_exit_code = EX_TEMPFAIL; } else if (line[0] != '+') { i_error("Replicator failed: %s", line+1); doveadm_exit_code = EX_USAGE; } replicator_disconnect(ctx); } struct doveadm_cmd_ver2 doveadm_cmd_replicator[] = { { .name = "replicator status", .cmd = cmd_replicator_status, .usage = "[-a ] []", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "user-mask", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "replicator dsync-status", .cmd = cmd_replicator_dsync_status, .usage = "[-a ]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAMS_END }, { .name = "replicator replicate", .cmd = cmd_replicator_replicate, .usage = "[-a ] [-f] [-p ] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('f', "full-sync", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('p', "priority", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "user-mask", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "replicator add", .cmd = cmd_replicator_add, .usage = "[-a ] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "user-mask", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "replicator remove", .cmd = cmd_replicator_remove, .usage = "[-a ] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "user", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, }; static void replicator_cmd_help(const struct doveadm_cmd_ver2 *cmd) { unsigned int i; for (i = 0; i < N_ELEMENTS(doveadm_cmd_replicator); i++) { if (doveadm_cmd_replicator[i].cmd == cmd->cmd) help_ver2(&doveadm_cmd_replicator[i]); } i_unreached(); } void doveadm_register_replicator_commands(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(doveadm_cmd_replicator); i++) doveadm_cmd_register_ver2(&doveadm_cmd_replicator[i]); } dovecot-2.3.21.1/src/doveadm/doveadm-dict.c0000644000000000000000000002257314656633576015240 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "dict.h" #include "doveadm.h" #include "doveadm-print.h" #include #include static int cmd_dict_init_full(struct doveadm_cmd_context *cctx, doveadm_command_ver2_t *cmd ATTR_UNUSED, enum dict_iterate_flags *iter_flags, struct dict **dict_r, struct dict_op_settings *dopset_r) { struct dict_settings dict_set; struct dict *dict; bool set = FALSE; const char *dict_uri, *error, *key, *username = ""; i_zero(dopset_r); if (doveadm_cmd_param_bool(cctx, "exact", &set) && set) *iter_flags |= DICT_ITERATE_FLAG_EXACT_KEY; if (doveadm_cmd_param_bool(cctx, "recurse", &set) && set) *iter_flags |= DICT_ITERATE_FLAG_RECURSE; if (doveadm_cmd_param_bool(cctx, "no-value", &set) && set) *iter_flags |= DICT_ITERATE_FLAG_NO_VALUE; (void)doveadm_cmd_param_str(cctx, "user", &username); dopset_r->username = username; if (!doveadm_cmd_param_str(cctx, "dict-uri", &dict_uri)) { i_error("dictionary URI must be specified"); doveadm_exit_code = EX_USAGE; return -1; } if (!doveadm_cmd_param_str(cctx, "prefix", &key) && !doveadm_cmd_param_str(cctx, "key", &key)) key = ""; if (!str_begins(key, DICT_PATH_PRIVATE) && !str_begins(key, DICT_PATH_SHARED)) { i_error("Key must begin with '"DICT_PATH_PRIVATE "' or '"DICT_PATH_SHARED"': %s", key); doveadm_exit_code = EX_USAGE; return -1; } if (username[0] == '\0' && str_begins(key, DICT_PATH_PRIVATE)) { i_error("-u must be specified for "DICT_PATH_PRIVATE" keys"); doveadm_exit_code = EX_USAGE; return -1; } dict_drivers_register_builtin(); i_zero(&dict_set); dict_set.base_dir = doveadm_settings->base_dir; if (dict_init(dict_uri, &dict_set, &dict, &error) < 0) { i_error("dict_init(%s) failed: %s", dict_uri, error); doveadm_exit_code = EX_TEMPFAIL; return -1; } *dict_r = dict; return 0; } static int cmd_dict_init(struct doveadm_cmd_context *cctx, doveadm_command_ver2_t *cmd, struct dict **dict_r, struct dict_op_settings *set_r) { enum dict_iterate_flags iter_flags = 0; return cmd_dict_init_full(cctx, cmd, &iter_flags, dict_r, set_r); } struct doveadm_dict_ctx { pool_t pool; int ret; const char *const *values; const char *error; }; static void dict_lookup_callback(const struct dict_lookup_result *result, struct doveadm_dict_ctx *ctx) { ctx->ret = result->ret; ctx->values = result->values == NULL ? NULL : p_strarray_dup(ctx->pool, result->values); ctx->error = p_strdup(ctx->pool, result->error); } static void cmd_dict_get(struct doveadm_cmd_context *cctx) { struct doveadm_dict_ctx ctx; struct dict *dict; const char *key; struct dict_op_settings set; if (!doveadm_cmd_param_str(cctx, "key", &key)) { i_error("dict-get: Missing key"); doveadm_exit_code = EX_USAGE; return; } if (cmd_dict_init(cctx, cmd_dict_get, &dict, &set) < 0) return; doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); doveadm_print_header("value", "", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); i_zero(&ctx); ctx.pool = pool_alloconly_create("doveadm dict lookup", 512); ctx.ret = -2; dict_lookup_async(dict, &set, key, dict_lookup_callback, &ctx); while (ctx.ret == -2) dict_wait(dict); if (ctx.ret < 0) { i_error("dict_lookup(%s) failed: %s", key, ctx.error); doveadm_exit_code = EX_TEMPFAIL; } else if (ctx.ret == 0) { i_error("%s doesn't exist", key); doveadm_exit_code = DOVEADM_EX_NOTFOUND; } else { unsigned int i, values_count = str_array_length(ctx.values); for (i = 1; i < values_count; i++) doveadm_print_header("value", "", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); for (i = 0; i < values_count; i++) doveadm_print(ctx.values[i]); } pool_unref(&ctx.pool); dict_deinit(&dict); } static void cmd_dict_set(struct doveadm_cmd_context *cctx) { struct dict *dict; struct dict_transaction_context *trans; const char *error; const char *key, *value = ""; struct dict_op_settings set; if (!doveadm_cmd_param_str(cctx, "key", &key) || !doveadm_cmd_param_str(cctx, "value", &value)) { i_error("dict set: Missing parameters"); doveadm_exit_code = EX_USAGE; return; } if (cmd_dict_init(cctx, cmd_dict_set, &dict, &set) < 0) return; trans = dict_transaction_begin(dict, &set); dict_set(trans, key, value); if (dict_transaction_commit(&trans, &error) <= 0) { i_error("dict_transaction_commit() failed: %s", error); doveadm_exit_code = EX_TEMPFAIL; } dict_deinit(&dict); } static void cmd_dict_unset(struct doveadm_cmd_context *cctx) { struct dict *dict; struct dict_transaction_context *trans; const char *error; const char *key; struct dict_op_settings set; if (!doveadm_cmd_param_str(cctx, "key", &key)) { i_error("dict unset: Missing key"); doveadm_exit_code = EX_USAGE; return; } if (cmd_dict_init(cctx, cmd_dict_unset, &dict, &set) < 0) return; trans = dict_transaction_begin(dict, &set); dict_unset(trans, key); if (dict_transaction_commit(&trans, &error) <= 0) { i_error("dict_transaction_commit() failed: %s", error); doveadm_exit_code = EX_TEMPFAIL; } dict_deinit(&dict); } static void cmd_dict_inc(struct doveadm_cmd_context *cctx) { struct dict *dict; struct dict_transaction_context *trans; const char *error; const char *key; int64_t diff; int ret; struct dict_op_settings set; if (!doveadm_cmd_param_str(cctx, "key", &key) || !doveadm_cmd_param_int64(cctx, "difference", &diff)) { i_error("dict-inc: Missing parameters"); doveadm_exit_code = EX_USAGE; return; } if (cmd_dict_init(cctx, cmd_dict_inc, &dict, &set) < 0) return; trans = dict_transaction_begin(dict, &set); dict_atomic_inc(trans, key, diff); ret = dict_transaction_commit(&trans, &error); if (ret < 0) { i_error("dict_transaction_commit() failed: %s", error); doveadm_exit_code = EX_TEMPFAIL; } else if (ret == 0) { i_error("%s doesn't exist", key); doveadm_exit_code = DOVEADM_EX_NOTFOUND; } dict_deinit(&dict); } static void cmd_dict_iter(struct doveadm_cmd_context *cctx) { struct dict *dict; struct dict_iterate_context *iter; enum dict_iterate_flags iter_flags = 0; const char *prefix, *key, *const *values, *error; bool header_printed = FALSE; struct dict_op_settings set; if (!doveadm_cmd_param_str(cctx, "prefix", &prefix)) { i_error("dict-iter: Missing prefix"); doveadm_exit_code = EX_USAGE; return; } if (cmd_dict_init_full(cctx, cmd_dict_iter, &iter_flags, &dict, &set) < 0) return; doveadm_print_init(DOVEADM_PRINT_TYPE_TAB); doveadm_print_header_simple("key"); if ((iter_flags & DICT_ITERATE_FLAG_NO_VALUE) == 0) doveadm_print_header_simple("value"); iter = dict_iterate_init(dict, &set, prefix, iter_flags); while (dict_iterate_values(iter, &key, &values)) { unsigned int values_count = str_array_length(values); if (!header_printed) { for (unsigned int i = 1; i < values_count; i++) doveadm_print_header_simple("value"); header_printed = TRUE; } doveadm_print(key); if ((iter_flags & DICT_ITERATE_FLAG_NO_VALUE) == 0) { for (unsigned int i = 0; i < values_count; i++) doveadm_print(values[i]); } } if (dict_iterate_deinit(&iter, &error) < 0) { i_error("dict_iterate_deinit(%s) failed: %s", prefix, error); doveadm_exit_code = EX_TEMPFAIL; } dict_deinit(&dict); } static struct doveadm_cmd_ver2 doveadm_cmd_dict[] = { { .name = "dict get", .cmd = cmd_dict_get, .usage = "[-u ] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('u', "user", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "dict-uri", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "key", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "dict set", .cmd = cmd_dict_set, .usage = "[-u ] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('u', "user", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "dict-uri", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "key", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "value", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "dict unset", .cmd = cmd_dict_unset, .usage = "[-u ] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('u', "user", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "dict-uri", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "key", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "dict inc", .cmd = cmd_dict_inc, .usage = "[-u ] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('u', "user", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "dict-uri", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "key", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "difference", CMD_PARAM_INT64, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "dict iter", .cmd = cmd_dict_iter, .usage = "[-u ] [-1RV] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('u', "user", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('1', "exact", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('R', "recurse", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('V', "no-value", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "dict-uri", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "prefix", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END } }; void doveadm_register_dict_commands(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(doveadm_cmd_dict); i++) doveadm_cmd_register_ver2(&doveadm_cmd_dict[i]); } dovecot-2.3.21.1/src/doveadm/doveadm-instance.c0000644000000000000000000001022314656633576016106 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "master-instance.h" #include "master-service-settings.h" #include "doveadm.h" #include "doveadm-print.h" #include #include #include #include extern struct doveadm_cmd_ver2 doveadm_cmd_instance[]; static void instance_cmd_help(const struct doveadm_cmd_ver2 *cmd) ATTR_NORETURN; static bool pid_file_read(const char *path) { char buf[32]; int fd; ssize_t ret; pid_t pid; bool found = FALSE; fd = open(path, O_RDONLY); if (fd == -1) { if (errno != ENOENT) i_error("open(%s) failed: %m", path); return FALSE; } ret = read(fd, buf, sizeof(buf)); if (ret < 0) i_error("read(%s) failed: %m", path); else if (ret > 0 && buf[ret-1] == '\n') { buf[ret-1] = '\0'; if (str_to_pid(buf, &pid) == 0) { found = !(pid == getpid() || (kill(pid, 0) < 0 && errno == ESRCH)); } } i_close_fd(&fd); return found; } static void cmd_instance_list(struct doveadm_cmd_context *cctx) { struct master_instance_list *list; struct master_instance_list_iter *iter; const struct master_instance *inst; const char *instance_path, *pidfile_path; bool show_config = FALSE; const char *name = NULL; (void)doveadm_cmd_param_bool(cctx, "show-config", &show_config); (void)doveadm_cmd_param_str(cctx, "name", &name); if (!show_config) { doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); doveadm_print_header("path", "path", DOVEADM_PRINT_HEADER_FLAG_EXPAND); doveadm_print_header_simple("name"); doveadm_print_header_simple("last used"); doveadm_print_header_simple("running"); } instance_path = t_strconcat(service_set->state_dir, "/"MASTER_INSTANCE_FNAME, NULL); list = master_instance_list_init(instance_path); iter = master_instance_list_iterate_init(list); while ((inst = master_instance_iterate_list_next(iter)) != NULL) { if (name != NULL && strcmp(name, inst->name) != 0) continue; if (show_config) { printf("%s\n", inst->config_path == NULL ? "" : inst->config_path); continue; } doveadm_print(inst->base_dir); doveadm_print(inst->name); doveadm_print(unixdate2str(inst->last_used)); pidfile_path = t_strconcat(inst->base_dir, "/master.pid", NULL); if (pid_file_read(pidfile_path)) doveadm_print("yes"); else doveadm_print("no"); } master_instance_iterate_list_deinit(&iter); master_instance_list_deinit(&list); } static void cmd_instance_remove(struct doveadm_cmd_context *cctx) { struct master_instance_list *list; const struct master_instance *inst; const char *base_dir, *instance_path, *name; int ret; if (!doveadm_cmd_param_str(cctx, "name", &name)) instance_cmd_help(cctx->cmd); instance_path = t_strconcat(service_set->state_dir, "/"MASTER_INSTANCE_FNAME, NULL); list = master_instance_list_init(instance_path); inst = master_instance_list_find_by_name(list, name); base_dir = inst != NULL ? inst->base_dir : name; if ((ret = master_instance_list_remove(list, base_dir)) < 0) { i_error("Failed to remove instance"); doveadm_exit_code = EX_TEMPFAIL; } else if (ret == 0) { i_error("Instance already didn't exist"); doveadm_exit_code = DOVEADM_EX_NOTFOUND; } master_instance_list_deinit(&list); } struct doveadm_cmd_ver2 doveadm_cmd_instance[] = { { .name = "instance list", .cmd = cmd_instance_list, .usage = "[-c] []", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('c', "show-config", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "name", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "instance remove", .cmd = cmd_instance_remove, .usage = " | ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', "name", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END } }; static void instance_cmd_help(const struct doveadm_cmd_ver2 *cmd) { unsigned int i; for (i = 0; i < N_ELEMENTS(doveadm_cmd_instance); i++) { if (doveadm_cmd_instance[i].cmd == cmd->cmd) help_ver2(&doveadm_cmd_instance[i]); } i_unreached(); } void doveadm_register_instance_commands(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(doveadm_cmd_instance); i++) doveadm_cmd_register_ver2(&doveadm_cmd_instance[i]); } dovecot-2.3.21.1/src/doveadm/doveadm-dump-dbox.c0000644000000000000000000001310114656633576016177 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "hex-dec.h" #include "istream.h" #include "index/dbox-common/dbox-file.h" #include "doveadm-dump.h" #include #include #include static void dump_timestamp(struct istream *input, const char *name, const char *value) { time_t t; if (strcmp(value, "0") == 0) t = 0; else { t = hex2dec((const void *)value, strlen(value)); if (t == 0) { i_fatal("Invalid %s at %"PRIuUOFF_T": %s", name, input->v_offset, value); } } printf("%s = %ld (%s)\n", name, (long)t, unixdate2str(t)); } static uoff_t dump_size(struct istream *input, const char *name, const char *value) { uoff_t size; if (strcmp(value, "0") == 0) size = 0; else { size = hex2dec((const void *)value, strlen(value)); if (size == 0) { i_fatal("Invalid %s at %"PRIuUOFF_T": %s", name, input->v_offset, value); } } printf("%s = %"PRIuUOFF_T"\n", name, size); return size; } static unsigned int dump_file_hdr(struct istream *input) { const char *line, *const *arg, *version; unsigned int msg_hdr_size = 0; if ((line = i_stream_read_next_line(input)) == NULL) i_fatal("Empty file"); arg = t_strsplit(line, " "); /* check version */ version = *arg; if (version == NULL || !str_is_numeric(version, ' ')) i_fatal("%s is not a dbox file", i_stream_get_name(input)); if (strcmp(version, "2") != 0) i_fatal("Unsupported dbox file version %s", version); arg++; for (; *arg != NULL; arg++) { switch (**arg) { case DBOX_HEADER_MSG_HEADER_SIZE: msg_hdr_size = hex2dec((const void *)(*arg + 1), strlen(*arg + 1)); if (msg_hdr_size == 0) { i_fatal("Invalid msg_header_size header: %s", *arg + 1); } printf("file.msg_header_size = %u\n", msg_hdr_size); break; case DBOX_HEADER_CREATE_STAMP: dump_timestamp(input, "file.create_stamp", *arg + 1); break; default: printf("file.unknown-%c = %s\n", **arg, *arg + 1); break; } } if (msg_hdr_size == 0) i_fatal("Missing msg_header_size in file header"); return msg_hdr_size; } static bool dump_msg_hdr(struct istream *input, unsigned int hdr_size, uoff_t *msg_size_r) { struct dbox_message_header hdr; const unsigned char *data; size_t size; uoff_t msg_size; if (i_stream_read_bytes(input, &data, &size, hdr_size) <= 0) { if (size == 0) return FALSE; i_fatal("Partial message header read at %"PRIuUOFF_T": %zu bytes", input->v_offset, size); } printf("offset %"PRIuUOFF_T":\n", input->v_offset); if (hdr_size < sizeof(hdr)) i_fatal("file.hdr_size too small: %u", hdr_size); memcpy(&hdr, data, sizeof(hdr)); if (memcmp(hdr.magic_pre, DBOX_MAGIC_PRE, sizeof(hdr.magic_pre)) != 0) i_fatal("dbox wrong pre-magic at %"PRIuUOFF_T, input->v_offset); msg_size = dump_size(input, "msg.size", t_strndup(hdr.message_size_hex, sizeof(hdr.message_size_hex))); i_stream_skip(input, hdr_size); *msg_size_r = msg_size; return TRUE; } static void dump_msg_metadata(struct istream *input) { struct dbox_metadata_header hdr; const unsigned char *data; size_t size; const char *line; /* verify magic */ if (i_stream_read_bytes(input, &data, &size, sizeof(hdr)) <= 0) { i_fatal("dbox missing metadata at %"PRIuUOFF_T, input->v_offset); } memcpy(&hdr, data, sizeof(hdr)); if (memcmp(hdr.magic_post, DBOX_MAGIC_POST, sizeof(hdr.magic_post)) != 0) i_fatal("dbox wrong post-magic at %"PRIuUOFF_T, input->v_offset); i_stream_skip(input, sizeof(hdr)); /* dump the metadata */ for (;;) { if ((line = i_stream_read_next_line(input)) == NULL) i_fatal("dbox metadata ended unexpectedly at EOF"); if (*line == '\0') break; switch (*line) { case DBOX_METADATA_GUID: printf("msg.guid = %s\n", line + 1); break; case DBOX_METADATA_POP3_UIDL: printf("msg.pop3-uidl = %s\n", line + 1); break; case DBOX_METADATA_POP3_ORDER: printf("msg.pop3-order = %s\n", line + 1); break; case DBOX_METADATA_RECEIVED_TIME: dump_timestamp(input, "msg.received", line + 1); break; case DBOX_METADATA_PHYSICAL_SIZE: (void)dump_size(input, "msg.physical-size", line + 1); break; case DBOX_METADATA_VIRTUAL_SIZE: (void)dump_size(input, "msg.virtual-size", line + 1); break; case DBOX_METADATA_EXT_REF: printf("msg.ext-ref = %s\n", line + 1); break; case DBOX_METADATA_ORIG_MAILBOX: printf("msg.orig-mailbox = %s\n", line + 1); break; case DBOX_METADATA_OLDV1_EXPUNGED: case DBOX_METADATA_OLDV1_FLAGS: case DBOX_METADATA_OLDV1_KEYWORDS: case DBOX_METADATA_OLDV1_SAVE_TIME: case DBOX_METADATA_OLDV1_SPACE: printf("msg.obsolete-%c = %s\n", *line, line + 1); break; } } } static bool dump_msg(struct istream *input, unsigned int hdr_size) { uoff_t msg_size; if (!dump_msg_hdr(input, hdr_size, &msg_size)) return FALSE; i_stream_skip(input, msg_size); dump_msg_metadata(input); return TRUE; } static void cmd_dump_dbox(const char *path, const char *const *args ATTR_UNUSED) { struct istream *input; int fd; unsigned int hdr_size; bool ret; fd = open(path, O_RDONLY); if (fd < 0) i_fatal("open(%s) failed: %m", path); input = i_stream_create_fd_autoclose(&fd, SIZE_MAX); i_stream_set_name(input, path); hdr_size = dump_file_hdr(input); do { printf("\n"); T_BEGIN { ret = dump_msg(input, hdr_size); } T_END; } while (ret); i_stream_destroy(&input); } static bool test_dump_dbox(const char *path) { const char *p; p = strrchr(path, '/'); if (p == NULL) p = path; else p++; return str_begins(p, "m.") || str_begins(p, "u."); } struct doveadm_cmd_dump doveadm_cmd_dump_dbox = { "dbox", test_dump_dbox, cmd_dump_dbox }; dovecot-2.3.21.1/src/doveadm/server-connection.c0000644000000000000000000004436414656633576016345 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "base64.h" #include "ioloop.h" #include "net.h" #include "istream.h" #include "istream-multiplex.h" #include "ostream.h" #include "ostream-dot.h" #include "str.h" #include "strescape.h" #include "iostream-ssl.h" #include "master-service.h" #include "master-service-settings.h" #include "settings-parser.h" #include "doveadm.h" #include "doveadm-print.h" #include "doveadm-util.h" #include "doveadm-server.h" #include "doveadm-settings.h" #include "server-connection.h" #include #include #define DOVEADM_LOG_CHANNEL_ID 'L' #define MAX_INBUF_SIZE (1024*32) enum server_reply_state { SERVER_REPLY_STATE_DONE = 0, SERVER_REPLY_STATE_PRINT, SERVER_REPLY_STATE_RET }; struct server_connection { struct doveadm_server *server; pool_t pool; struct doveadm_settings *set; int fd; unsigned int minor; struct io *io; struct io *io_log; struct istream *input; struct istream *log_input; struct ostream *output; struct ssl_iostream *ssl_iostream; struct istream *cmd_input; struct ostream *cmd_output; const char *delayed_cmd; server_cmd_callback_t *callback; void *context; enum server_reply_state state; bool version_received:1; bool authenticate_sent:1; bool authenticated:1; bool streaming:1; bool ssl_done:1; }; static struct server_connection *printing_conn = NULL; static ARRAY(struct doveadm_server *) print_pending_servers = ARRAY_INIT; static void server_connection_input(struct server_connection *conn); static bool server_connection_input_one(struct server_connection *conn); static int server_connection_init_ssl(struct server_connection *conn, const char **error_r); static void server_set_print_pending(struct doveadm_server *server) { struct doveadm_server *pending_server; if (!array_is_created(&print_pending_servers)) i_array_init(&print_pending_servers, 16); array_foreach_elem(&print_pending_servers, pending_server) { if (pending_server == server) return; } array_push_back(&print_pending_servers, &server); } static void server_print_connection_released(struct doveadm_server *server) { struct server_connection *const *conns; unsigned int i, count; conns = array_get(&server->connections, &count); for (i = 0; i < count; i++) { if (conns[i]->io != NULL) continue; conns[i]->io = io_add(conns[i]->fd, IO_READ, server_connection_input, conns[i]); io_set_pending(conns[i]->io); } } static void print_connection_released(void) { struct doveadm_server *server; printing_conn = NULL; if (!array_is_created(&print_pending_servers)) return; array_foreach_elem(&print_pending_servers, server) server_print_connection_released(server); array_free(&print_pending_servers); } static int server_connection_send_cmd_input_more(struct server_connection *conn) { enum ostream_send_istream_result res; int ret = -1; /* ostream-dot writes only up to max buffer size, so keep it non-zero */ o_stream_set_max_buffer_size(conn->cmd_output, IO_BLOCK_SIZE); res = o_stream_send_istream(conn->cmd_output, conn->cmd_input); o_stream_set_max_buffer_size(conn->cmd_output, SIZE_MAX); switch (res) { case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: break; case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: return 1; case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: return 0; case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: i_error("read(%s) failed: %s", i_stream_get_name(conn->cmd_input), i_stream_get_error(conn->cmd_input)); break; case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: i_error("write(%s) failed: %s", o_stream_get_name(conn->cmd_output), o_stream_get_error(conn->cmd_output)); break; } if (res == OSTREAM_SEND_ISTREAM_RESULT_FINISHED) { if ((ret = o_stream_finish(conn->cmd_output)) == 0) return 0; else if (ret < 0) { i_error("write(%s) failed: %s", o_stream_get_name(conn->cmd_output), o_stream_get_error(conn->cmd_output)); } } i_stream_destroy(&conn->cmd_input); o_stream_destroy(&conn->cmd_output); return ret; } static void server_connection_send_cmd_input(struct server_connection *conn) { if (conn->cmd_input == NULL) return; conn->cmd_output = o_stream_create_dot(conn->output, TRUE); (void)server_connection_send_cmd_input_more(conn); } static int server_connection_output(struct server_connection *conn) { int ret; ret = o_stream_flush(conn->output); if (ret > 0 && conn->cmd_input != NULL && conn->delayed_cmd == NULL) ret = server_connection_send_cmd_input_more(conn); if (ret < 0) server_connection_destroy(&conn); return ret; } static void server_connection_callback(struct server_connection *conn, int exit_code, const char *error) { server_cmd_callback_t *callback = conn->callback; conn->callback = NULL; callback(exit_code, error, conn->context); } static void stream_data(string_t *str, const unsigned char *data, size_t size) { str_truncate(str, 0); str_append_tabunescaped(str, data, size); doveadm_print_stream(str->data, str->used); } static void server_flush_field(struct server_connection *conn, string_t *str, const unsigned char *data, size_t size) { if (conn->streaming) { conn->streaming = FALSE; if (size > 0) stream_data(str, data, size); doveadm_print_stream("", 0); } else { str_truncate(str, 0); str_append_tabunescaped(str, data, size); doveadm_print(str_c(str)); } } static void server_handle_input(struct server_connection *conn, const unsigned char *data, size_t size) { string_t *str; size_t i, start; if (printing_conn == conn) { /* continue printing */ } else if (printing_conn == NULL) { printing_conn = conn; } else { /* someone else is printing. don't continue until it goes away */ server_set_print_pending(conn->server); io_remove(&conn->io); return; } if (data[size-1] == '\001') { /* last character is an escape */ size--; } str = t_str_new(128); for (i = start = 0; i < size; i++) { if (data[i] == '\n') { if (i != start) { i_error("doveadm server sent broken print input"); server_connection_destroy(&conn); return; } conn->state = SERVER_REPLY_STATE_RET; i_stream_skip(conn->input, i + 1); print_connection_released(); return; } if (data[i] == '\t') { server_flush_field(conn, str, data + start, i - start); start = i + 1; } } if (start != size) { conn->streaming = TRUE; stream_data(str, data + start, size - start); } i_stream_skip(conn->input, size); } static void server_connection_authenticated(struct server_connection *conn) { conn->authenticated = TRUE; if (conn->delayed_cmd != NULL) { o_stream_nsend_str(conn->output, conn->delayed_cmd); conn->delayed_cmd = NULL; server_connection_send_cmd_input(conn); } } static int server_connection_authenticate(struct server_connection *conn) { string_t *plain = t_str_new(128); string_t *cmd = t_str_new(128); if (*conn->set->doveadm_password == '\0') { i_error("doveadm_password not set, " "can't authenticate to remote server"); return -1; } str_append_c(plain, '\0'); str_append(plain, conn->set->doveadm_username); str_append_c(plain, '\0'); str_append(plain, conn->set->doveadm_password); str_append(cmd, "PLAIN\t"); base64_encode(plain->data, plain->used, cmd); str_append_c(cmd, '\n'); o_stream_nsend(conn->output, cmd->data, cmd->used); conn->authenticate_sent = TRUE; return 0; } static void server_log_disconnect_error(struct server_connection *conn) { const char *error; error = conn->ssl_iostream == NULL ? NULL : ssl_iostream_get_last_error(conn->ssl_iostream); if (error == NULL) { error = conn->input->stream_errno == 0 ? "EOF" : strerror(conn->input->stream_errno); } i_error("doveadm server disconnected before handshake: %s", error); } static void server_connection_print_log(struct server_connection *conn) { const char *line; struct failure_context ctx; i_zero(&ctx); while((line = i_stream_read_next_line(conn->log_input))!=NULL) { /* skip empty lines */ if (*line == '\0') continue; if (!doveadm_log_type_from_char(line[0], &ctx.type)) i_warning("Doveadm server sent invalid log type 0x%02x", line[0]); line++; i_log_type(&ctx, "remote(%s): %s", conn->server->name, line); } } static void server_connection_start_multiplex(struct server_connection *conn) { struct istream *is = conn->input; conn->input = i_stream_create_multiplex(is, MAX_INBUF_SIZE); i_stream_unref(&is); io_remove(&conn->io); conn->io = io_add_istream(conn->input, server_connection_input, conn); conn->log_input = i_stream_multiplex_add_channel(conn->input, DOVEADM_LOG_CHANNEL_ID); conn->io_log = io_add_istream(conn->log_input, server_connection_print_log, conn); i_stream_set_return_partial_line(conn->log_input, TRUE); } static void server_connection_input(struct server_connection *conn) { const char *line; const char *error; if (i_stream_read(conn->input) < 0) { /* disconnected */ server_log_disconnect_error(conn); server_connection_destroy(&conn); return; } while (!conn->authenticated) { if ((line = i_stream_next_line(conn->input)) == NULL) { if (conn->input->eof) { /* we'll also get here if the line is too long */ server_log_disconnect_error(conn); server_connection_destroy(&conn); } return; } /* Allow VERSION before or after the "+" or "-" line, because v2.2.33 sent the version after and newer versions send before. */ if (!conn->version_received && str_begins(line, "VERSION\t")) { if (!version_string_verify_full(line, "doveadm-client", DOVEADM_SERVER_PROTOCOL_VERSION_MAJOR, &conn->minor)) { i_error("doveadm server not compatible with this client" "(mixed old and new binaries?)"); server_connection_destroy(&conn); return; } conn->version_received = TRUE; } else if (strcmp(line, "+") == 0) { if (conn->minor > 0) server_connection_start_multiplex(conn); server_connection_authenticated(conn); } else if (strcmp(line, "-") == 0) { if (conn->authenticate_sent) { i_error("doveadm authentication failed (%s)", line+1); server_connection_destroy(&conn); return; } if (!conn->ssl_done && (conn->server->ssl_flags & PROXY_SSL_FLAG_STARTTLS) != 0) { io_remove(&conn->io); if (conn->minor < 2) { i_error("doveadm STARTTLS failed: Server does not support it"); server_connection_destroy(&conn); return; } /* send STARTTLS */ o_stream_nsend_str(conn->output, "STARTTLS\n"); if (server_connection_init_ssl(conn, &error) < 0) { i_error("doveadm STARTTLS failed: %s", error); server_connection_destroy(&conn); return; } conn->ssl_done = TRUE; conn->io = io_add_istream(conn->input, server_connection_input, conn); } if (server_connection_authenticate(conn) < 0) { server_connection_destroy(&conn); return; } } else { i_error("doveadm server sent invalid handshake: %s", line); server_connection_destroy(&conn); return; } } while (server_connection_input_one(conn)) ; } static bool server_connection_input_one(struct server_connection *conn) { const unsigned char *data; size_t size; const char *line; int exit_code; /* check logs - NOTE: must be before i_stream_get_data() since checking for logs may add data to our channel. */ if (conn->log_input != NULL) (void)server_connection_print_log(conn); data = i_stream_get_data(conn->input, &size); if (size == 0) return FALSE; switch (conn->state) { case SERVER_REPLY_STATE_DONE: i_error("doveadm server sent unexpected input"); server_connection_destroy(&conn); return FALSE; case SERVER_REPLY_STATE_PRINT: server_handle_input(conn, data, size); if (conn->state != SERVER_REPLY_STATE_RET) return FALSE; /* fall through */ case SERVER_REPLY_STATE_RET: line = i_stream_next_line(conn->input); if (line == NULL) return FALSE; if (line[0] == '+') server_connection_callback(conn, 0, ""); else if (line[0] == '-') { line++; exit_code = doveadm_str_to_exit_code(line); if (exit_code == DOVEADM_EX_UNKNOWN && str_to_int(line, &exit_code) < 0) { /* old doveadm-server */ exit_code = EX_TEMPFAIL; } server_connection_callback(conn, exit_code, line); } else { i_error("doveadm server sent broken input " "(expected cmd reply): %s", line); server_connection_destroy(&conn); return FALSE; } if (conn->callback == NULL) { /* we're finished, close the connection */ server_connection_destroy(&conn); return FALSE; } return TRUE; } i_unreached(); } static int server_connection_read_settings(struct server_connection *conn, const char **error_r) { const struct setting_parser_info *set_roots[] = { &doveadm_setting_parser_info, NULL }; struct master_service_settings_input input; struct master_service_settings_output output; const char *error; in_port_t port; void *set; i_zero(&input); input.roots = set_roots; input.service = "doveadm"; (void)net_getsockname(conn->fd, &input.local_ip, &port); (void)net_getpeername(conn->fd, &input.remote_ip, &port); if (master_service_settings_read(master_service, &input, &output, &error) < 0) { *error_r = t_strdup_printf( "Error reading configuration: %s", error); return -1; } set = master_service_settings_get_others(master_service)[0]; conn->set = settings_dup(&doveadm_setting_parser_info, set, conn->pool); return 0; } static int server_connection_init_ssl(struct server_connection *conn, const char **error_r) { struct ssl_iostream_settings ssl_set; const char *error; if (conn->server->ssl_flags == 0) return 0; doveadm_get_ssl_settings(&ssl_set, pool_datastack_create()); if ((conn->server->ssl_flags & PROXY_SSL_FLAG_ANY_CERT) != 0) ssl_set.allow_invalid_cert = TRUE; if (ssl_set.allow_invalid_cert) ssl_set.verbose_invalid_cert = TRUE; if (conn->server->ssl_ctx == NULL && ssl_iostream_client_context_cache_get(&ssl_set, &conn->server->ssl_ctx, &error) < 0) { *error_r = t_strdup_printf( "Couldn't initialize SSL client: %s", error); return -1; } if (io_stream_create_ssl_client(conn->server->ssl_ctx, conn->server->hostname, &ssl_set, &conn->input, &conn->output, &conn->ssl_iostream, &error) < 0) { *error_r = t_strdup_printf( "Couldn't initialize SSL client: %s", error); return -1; } if (ssl_iostream_handshake(conn->ssl_iostream) < 0) { *error_r = t_strdup_printf( "SSL handshake failed: %s", ssl_iostream_get_last_error(conn->ssl_iostream)); return -1; } return 0; } int server_connection_create(struct doveadm_server *server, struct server_connection **conn_r, const char **error_r) { const char *target; struct server_connection *conn; pool_t pool; pool = pool_alloconly_create("doveadm server connection", 1024*16); conn = p_new(pool, struct server_connection, 1); conn->pool = pool; conn->server = server; if (server->ip.family != 0) { (void)net_ipport2str(&server->ip, server->port, &target); } else { target = server->name; } conn->fd = doveadm_connect_with_default_port(target, doveadm_settings->doveadm_port); net_set_nonblock(conn->fd, TRUE); conn->input = i_stream_create_fd(conn->fd, MAX_INBUF_SIZE); conn->output = o_stream_create_fd(conn->fd, SIZE_MAX); o_stream_set_flush_callback(conn->output, server_connection_output, conn); o_stream_set_no_error_handling(conn->output, TRUE); i_stream_set_name(conn->input, server->name); o_stream_set_name(conn->output, server->name); array_push_back(&conn->server->connections, &conn); if (server_connection_read_settings(conn, error_r) < 0 || ((server->ssl_flags & PROXY_SSL_FLAG_STARTTLS) == 0 && server_connection_init_ssl(conn, error_r) < 0)) { server_connection_destroy(&conn); return -1; } conn->io = io_add_istream(conn->input, server_connection_input, conn); conn->state = SERVER_REPLY_STATE_DONE; o_stream_nsend_str(conn->output, DOVEADM_SERVER_PROTOCOL_VERSION_LINE"\n"); *conn_r = conn; return 0; } void server_connection_destroy(struct server_connection **_conn) { struct server_connection *conn = *_conn; struct server_connection *const *conns; const char *error; unsigned int i, count; *_conn = NULL; conns = array_get(&conn->server->connections, &count); for (i = 0; i < count; i++) { if (conns[i] == conn) { array_delete(&conn->server->connections, i, 1); break; } } if (conn->callback != NULL) { error = conn->ssl_iostream == NULL ? NULL : ssl_iostream_get_last_error(conn->ssl_iostream); if (error == NULL) { error = conn->input->stream_errno == 0 ? "EOF" : strerror(conn->input->stream_errno); } server_connection_callback(conn, SERVER_EXIT_CODE_DISCONNECTED, error); } if (printing_conn == conn) print_connection_released(); i_stream_destroy(&conn->input); o_stream_destroy(&conn->output); i_stream_destroy(&conn->cmd_input); /* close cmd_output after its parent, so the "." isn't sent */ o_stream_destroy(&conn->cmd_output); ssl_iostream_destroy(&conn->ssl_iostream); io_remove(&conn->io_log); /* make sure all logs got consumed */ if (conn->log_input != NULL) server_connection_print_log(conn); i_stream_unref(&conn->log_input); io_remove(&conn->io); i_close_fd(&conn->fd); pool_unref(&conn->pool); } struct doveadm_server * server_connection_get_server(struct server_connection *conn) { return conn->server; } void server_connection_cmd(struct server_connection *conn, const char *line, struct istream *cmd_input, server_cmd_callback_t *callback, void *context) { i_assert(conn->delayed_cmd == NULL); conn->state = SERVER_REPLY_STATE_PRINT; if (cmd_input != NULL) { i_assert(conn->cmd_input == NULL); i_stream_ref(cmd_input); conn->cmd_input = cmd_input; } if (!conn->authenticated) conn->delayed_cmd = p_strdup(conn->pool, line); else { o_stream_nsend_str(conn->output, line); server_connection_send_cmd_input(conn); } conn->callback = callback; conn->context = context; } bool server_connection_is_idle(struct server_connection *conn) { return conn->callback == NULL; } void server_connection_extract(struct server_connection *conn, struct istream **istream_r, struct ostream **ostream_r, struct ssl_iostream **ssl_iostream_r) { *istream_r = conn->input; *ostream_r = conn->output; *ssl_iostream_r = conn->ssl_iostream; conn->input = NULL; conn->output = NULL; conn->ssl_iostream = NULL; io_remove(&conn->io); conn->fd = -1; } dovecot-2.3.21.1/src/doveadm/doveadm-mail.c0000644000000000000000000006653114656633576015241 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "lib-signals.h" #include "ioloop.h" #include "istream.h" #include "istream-dot.h" #include "istream-seekable.h" #include "str.h" #include "unichar.h" #include "module-dir.h" #include "wildcard-match.h" #include "master-service.h" #include "mail-user.h" #include "mail-namespace.h" #include "mail-storage.h" #include "mail-storage-settings.h" #include "mail-storage-service.h" #include "mail-storage-hooks.h" #include "mail-search-build.h" #include "mail-search-parser.h" #include "mailbox-list-iter.h" #include "doveadm.h" #include "client-connection.h" #include "doveadm-settings.h" #include "doveadm-print.h" #include "doveadm-dsync.h" #include "doveadm-mail.h" #include #define DOVEADM_MAIL_CMD_INPUT_TIMEOUT_MSECS (5*60*1000) struct force_resync_cmd_context { struct doveadm_mail_cmd_context ctx; bool fsck; }; void (*hook_doveadm_mail_init)(struct doveadm_mail_cmd_context *ctx); struct doveadm_mail_cmd_module_register doveadm_mail_cmd_module_register = { 0 }; char doveadm_mail_cmd_hide = '\0'; static int killed_signo = 0; bool doveadm_is_killed(void) { return killed_signo != 0; } int doveadm_killed_signo(void) { return killed_signo; } void doveadm_mail_failed_error(struct doveadm_mail_cmd_context *ctx, enum mail_error error) { int exit_code = EX_TEMPFAIL; switch (error) { case MAIL_ERROR_NONE: i_unreached(); case MAIL_ERROR_TEMP: case MAIL_ERROR_UNAVAILABLE: break; case MAIL_ERROR_NOTPOSSIBLE: case MAIL_ERROR_EXISTS: case MAIL_ERROR_CONVERSION: case MAIL_ERROR_INVALIDDATA: exit_code = DOVEADM_EX_NOTPOSSIBLE; break; case MAIL_ERROR_PARAMS: exit_code = EX_USAGE; break; case MAIL_ERROR_PERM: exit_code = EX_NOPERM; break; case MAIL_ERROR_NOQUOTA: exit_code = EX_CANTCREAT; break; case MAIL_ERROR_NOTFOUND: exit_code = DOVEADM_EX_NOTFOUND; break; case MAIL_ERROR_EXPUNGED: break; case MAIL_ERROR_INUSE: case MAIL_ERROR_LIMIT: exit_code = DOVEADM_EX_NOTPOSSIBLE; break; case MAIL_ERROR_LOOKUP_ABORTED: break; } /* tempfail overrides all other exit codes, otherwise use whatever error happened first */ if (ctx->exit_code == 0 || exit_code == EX_TEMPFAIL) ctx->exit_code = exit_code; } void doveadm_mail_failed_storage(struct doveadm_mail_cmd_context *ctx, struct mail_storage *storage) { enum mail_error error; mail_storage_get_last_error(storage, &error); doveadm_mail_failed_error(ctx, error); } void doveadm_mail_failed_mailbox(struct doveadm_mail_cmd_context *ctx, struct mailbox *box) { doveadm_mail_failed_storage(ctx, mailbox_get_storage(box)); } void doveadm_mail_failed_list(struct doveadm_mail_cmd_context *ctx, struct mailbox_list *list) { enum mail_error error; mailbox_list_get_last_error(list, &error); doveadm_mail_failed_error(ctx, error); } struct doveadm_mail_cmd_context * doveadm_mail_cmd_alloc_size(size_t size) { struct doveadm_mail_cmd_context *ctx; pool_t pool; i_assert(size >= sizeof(struct doveadm_mail_cmd_context)); pool = pool_alloconly_create("doveadm mail cmd", 1024); ctx = p_malloc(pool, size); ctx->pool = pool; ctx->cmd_input_fd = -1; return ctx; } static int cmd_purge_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) { struct mail_namespace *ns; struct mail_storage *storage; int ret = 0; for (ns = user->namespaces; ns != NULL; ns = ns->next) { if (ns->type != MAIL_NAMESPACE_TYPE_PRIVATE || ns->alias_for != NULL) continue; storage = mail_namespace_get_default_storage(ns); if (mail_storage_purge(storage) < 0) { i_error("Purging namespace '%s' failed: %s", ns->prefix, mail_storage_get_last_internal_error(storage, NULL)); doveadm_mail_failed_storage(ctx, storage); ret = -1; } } return ret; } static struct doveadm_mail_cmd_context *cmd_purge_alloc(void) { struct doveadm_mail_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); ctx->v.run = cmd_purge_run; return ctx; } static void doveadm_mail_cmd_input_input(struct doveadm_mail_cmd_context *ctx) { const unsigned char *data; size_t size; while (i_stream_read_more(ctx->cmd_input, &data, &size) > 0) i_stream_skip(ctx->cmd_input, size); if (!ctx->cmd_input->eof) return; if (ctx->cmd_input->stream_errno != 0) { i_error("read(%s) failed: %s", i_stream_get_name(ctx->cmd_input), i_stream_get_error(ctx->cmd_input)); } io_loop_stop(current_ioloop); } static void doveadm_mail_cmd_input_timeout(struct doveadm_mail_cmd_context *ctx) { struct istream *input; input = i_stream_create_error_str(ETIMEDOUT, "Timed out in %u secs", DOVEADM_MAIL_CMD_INPUT_TIMEOUT_MSECS/1000); i_stream_set_name(input, i_stream_get_name(ctx->cmd_input)); i_stream_destroy(&ctx->cmd_input); ctx->cmd_input = input; ctx->exit_code = EX_TEMPFAIL; io_loop_stop(current_ioloop); } static void doveadm_mail_cmd_input_read(struct doveadm_mail_cmd_context *ctx) { struct ioloop *ioloop; struct io *io; struct timeout *to; ioloop = io_loop_create(); /* Read the pending input from stream. Delay adding the IO in case we're reading from a file. That would cause a panic with epoll. */ io_loop_set_running(ioloop); doveadm_mail_cmd_input_input(ctx); if (io_loop_is_running(ioloop)) { io = io_add(ctx->cmd_input_fd, IO_READ, doveadm_mail_cmd_input_input, ctx); to = timeout_add(DOVEADM_MAIL_CMD_INPUT_TIMEOUT_MSECS, doveadm_mail_cmd_input_timeout, ctx); io_loop_run(ioloop); io_remove(&io); timeout_remove(&to); } io_loop_destroy(&ioloop); i_assert(ctx->cmd_input->eof); i_stream_seek(ctx->cmd_input, 0); } void doveadm_mail_get_input(struct doveadm_mail_cmd_context *ctx) { const struct doveadm_cmd_context *cctx = ctx->cctx; bool cli = (cctx->conn_type == DOVEADM_CONNECTION_TYPE_CLI); struct istream *inputs[2]; if (ctx->cmd_input != NULL) return; if (!cli && cctx->input == NULL) { ctx->cmd_input = i_stream_create_error_str(EINVAL, "Input stream missing (provide with file parameter)"); return; } if (!cli) inputs[0] = i_stream_create_dot(cctx->input, FALSE); else { inputs[0] = i_stream_create_fd(STDIN_FILENO, 1024*1024); i_stream_set_name(inputs[0], "stdin"); } inputs[1] = NULL; ctx->cmd_input_fd = i_stream_get_fd(inputs[0]); ctx->cmd_input = i_stream_create_seekable_path(inputs, 1024*256, "/tmp/doveadm."); i_stream_set_name(ctx->cmd_input, i_stream_get_name(inputs[0])); i_stream_unref(&inputs[0]); doveadm_mail_cmd_input_read(ctx); } struct mailbox * doveadm_mailbox_find(struct mail_user *user, const char *mailbox) { struct mail_namespace *ns; if (!uni_utf8_str_is_valid(mailbox)) { i_fatal_status(EX_DATAERR, "Mailbox name not valid UTF-8: %s", mailbox); } ns = mail_namespace_find(user->namespaces, mailbox); return mailbox_alloc(ns->list, mailbox, MAILBOX_FLAG_IGNORE_ACLS); } struct mail_search_args * doveadm_mail_build_search_args(const char *const args[]) { struct mail_search_parser *parser; struct mail_search_args *sargs; const char *error, *charset = "UTF-8"; parser = mail_search_parser_init_cmdline(args); if (mail_search_build(mail_search_register_get_human(), parser, &charset, &sargs, &error) < 0) i_fatal("%s", error); mail_search_parser_deinit(&parser); return sargs; } static int cmd_force_resync_box(struct doveadm_mail_cmd_context *_ctx, const struct mailbox_info *info) { struct force_resync_cmd_context *ctx = (struct force_resync_cmd_context *)_ctx; enum mailbox_flags flags = MAILBOX_FLAG_IGNORE_ACLS; struct mailbox *box; int ret = 0; if (ctx->fsck) flags |= MAILBOX_FLAG_FSCK; box = mailbox_alloc(info->ns->list, info->vname, flags); if (mailbox_open(box) < 0) { i_error("Opening mailbox %s failed: %s", info->vname, mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(_ctx, box); ret = -1; } else if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FORCE_RESYNC | MAILBOX_SYNC_FLAG_FIX_INCONSISTENT) < 0) { i_error("Forcing a resync on mailbox %s failed: %s", info->vname, mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(_ctx, box); ret = -1; } mailbox_free(&box); return ret; } static int cmd_force_resync_prerun(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, struct mail_storage_service_user *service_user, const char **error_r) { if (mail_storage_service_user_set_setting(service_user, "mailbox_list_index_very_dirty_syncs", "no", error_r) <= 0) i_unreached(); return 0; } static int cmd_force_resync_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) { const enum mailbox_list_iter_flags iter_flags = MAILBOX_LIST_ITER_NO_AUTO_BOXES | MAILBOX_LIST_ITER_RETURN_NO_FLAGS | MAILBOX_LIST_ITER_STAR_WITHIN_NS; const enum mail_namespace_type ns_mask = MAIL_NAMESPACE_TYPE_MASK_ALL; struct mailbox_list_iterate_context *iter; const struct mailbox_info *info; int ret = 0; iter = mailbox_list_iter_init_namespaces(user->namespaces, ctx->args, ns_mask, iter_flags); while ((info = mailbox_list_iter_next(iter)) != NULL) { if ((info->flags & (MAILBOX_NOSELECT | MAILBOX_NONEXISTENT)) == 0) T_BEGIN { if (cmd_force_resync_box(ctx, info) < 0) ret = -1; } T_END; } if (mailbox_list_iter_deinit(&iter) < 0) { i_error("Listing mailboxes failed: %s", mailbox_list_get_last_internal_error(user->namespaces->list, NULL)); doveadm_mail_failed_list(ctx, user->namespaces->list); ret = -1; } return ret; } static void cmd_force_resync_init(struct doveadm_mail_cmd_context *_ctx ATTR_UNUSED, const char *const args[]) { if (args[0] == NULL) doveadm_mail_help_name("force-resync"); } static bool cmd_force_resync_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct force_resync_cmd_context *ctx = (struct force_resync_cmd_context *)_ctx; switch (c) { case 'f': ctx->fsck = TRUE; break; default: return FALSE; } return TRUE; } static struct doveadm_mail_cmd_context *cmd_force_resync_alloc(void) { struct force_resync_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct force_resync_cmd_context); ctx->ctx.getopt_args = "f"; ctx->ctx.v.parse_arg = cmd_force_resync_parse_arg; ctx->ctx.v.init = cmd_force_resync_init; ctx->ctx.v.run = cmd_force_resync_run; ctx->ctx.v.prerun = cmd_force_resync_prerun; return &ctx->ctx; } static void doveadm_cctx_to_storage_service_input(const struct doveadm_cmd_context *cctx, struct mail_storage_service_input *input_r) { i_zero(input_r); input_r->service = "doveadm"; input_r->remote_ip = cctx->remote_ip; input_r->remote_port = cctx->remote_port; input_r->local_ip = cctx->local_ip; input_r->local_port = cctx->local_port; input_r->username = cctx->username; } static int doveadm_mail_next_user(struct doveadm_mail_cmd_context *ctx, const char **error_r) { const struct doveadm_cmd_context *cctx = ctx->cctx; struct mail_storage_service_input input; const char *error, *ip; int ret; i_assert(cctx != NULL); ip = net_ip2addr(&cctx->remote_ip); if (ip[0] == '\0') i_set_failure_prefix("doveadm(%s): ", cctx->username); else i_set_failure_prefix("doveadm(%s,%s): ", ip, cctx->username); doveadm_cctx_to_storage_service_input(cctx, &input); if (ctx->cmd_input != NULL) i_stream_seek(ctx->cmd_input, 0); /* see if we want to execute this command via (another) doveadm server */ ret = doveadm_mail_server_user(ctx, &input, error_r); if (ret != 0) return ret; ret = mail_storage_service_lookup(ctx->storage_service, &input, &ctx->cur_service_user, &error); if (ret <= 0) { if (ret < 0) { *error_r = t_strdup_printf("User lookup failed: %s", error); } return ret; } if (ctx->v.prerun != NULL) { if (ctx->v.prerun(ctx, ctx->cur_service_user, error_r) < 0) { mail_storage_service_user_unref(&ctx->cur_service_user); return -1; } } ret = mail_storage_service_next(ctx->storage_service, ctx->cur_service_user, &ctx->cur_mail_user, error_r); if (ret < 0) { mail_storage_service_user_unref(&ctx->cur_service_user); return ret; } /* Create the event outside the active ioloop context, so if run() switches the ioloop context it won't try to pop out the event_reason from global events. */ struct ioloop_context *cur_ctx = io_loop_get_current_context(current_ioloop); io_loop_context_deactivate(cur_ctx); struct event_reason *reason = event_reason_begin(event_reason_code_prefix("doveadm", "cmd_", ctx->cmd->name)); io_loop_context_activate(cur_ctx); T_BEGIN { if (ctx->v.run(ctx, ctx->cur_mail_user) < 0) { i_assert(ctx->exit_code != 0); } } T_END; mail_user_deinit(&ctx->cur_mail_user); mail_storage_service_user_unref(&ctx->cur_service_user); /* User deinit may still do some work, so finish the reason after it. Also, this needs to be after the ioloop context is deactivated. */ event_reason_end(&reason); return 1; } static void sig_die(const siginfo_t *si, void *context ATTR_UNUSED) { killed_signo = si->si_signo; } int doveadm_mail_single_user(struct doveadm_mail_cmd_context *ctx, const char **error_r) { const struct doveadm_cmd_context *cctx = ctx->cctx; i_assert(cctx->username != NULL); doveadm_cctx_to_storage_service_input(cctx, &ctx->storage_service_input); ctx->storage_service = mail_storage_service_init(master_service, NULL, ctx->service_flags); ctx->v.init(ctx, ctx->args); if (hook_doveadm_mail_init != NULL) hook_doveadm_mail_init(ctx); lib_signals_set_handler(SIGINT, 0, sig_die, NULL); lib_signals_set_handler(SIGTERM, 0, sig_die, NULL); return doveadm_mail_next_user(ctx, error_r); } static void doveadm_mail_all_users(struct doveadm_mail_cmd_context *ctx, const char *wildcard_user) { struct doveadm_cmd_context *cctx = ctx->cctx; unsigned int user_idx; const char *ip, *user, *error; int ret; ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP; doveadm_cctx_to_storage_service_input(cctx, &ctx->storage_service_input); ctx->storage_service = mail_storage_service_init(master_service, NULL, ctx->service_flags); lib_signals_set_handler(SIGINT, 0, sig_die, NULL); lib_signals_set_handler(SIGTERM, 0, sig_die, NULL); ctx->v.init(ctx, ctx->args); mail_storage_service_all_init_mask(ctx->storage_service, wildcard_user != NULL ? wildcard_user : ""); if (hook_doveadm_mail_init != NULL) hook_doveadm_mail_init(ctx); user_idx = 0; while ((ret = ctx->v.get_next_user(ctx, &user)) > 0) { if (wildcard_user != NULL) { if (!wildcard_match_icase(user, wildcard_user)) continue; } cctx->username = user; doveadm_print_sticky("username", user); T_BEGIN { ret = doveadm_mail_next_user(ctx, &error); if (ret < 0) i_error("%s", error); else if (ret == 0) i_info("User no longer exists, skipping"); } T_END; if (ret == -1) break; if (doveadm_verbose) { if (++user_idx % 100 == 0) { printf("\r%d", user_idx); fflush(stdout); } } if (killed_signo != 0) { i_warning("Killed with signal %d", killed_signo); ret = -1; break; } } if (doveadm_verbose) printf("\n"); ip = net_ip2addr(&cctx->remote_ip); if (ip[0] == '\0') i_set_failure_prefix("doveadm: "); else i_set_failure_prefix("doveadm(%s): ", ip); if (ret < 0) { i_error("Failed to iterate through some users"); ctx->exit_code = EX_TEMPFAIL; } } static void doveadm_mail_cmd_init_noop(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED, const char *const args[] ATTR_UNUSED) { } static int doveadm_mail_cmd_get_next_user(struct doveadm_mail_cmd_context *ctx, const char **username_r) { if (ctx->users_list_input == NULL) return mail_storage_service_all_next(ctx->storage_service, username_r); *username_r = i_stream_read_next_line(ctx->users_list_input); if (ctx->users_list_input->stream_errno != 0) { i_error("read(%s) failed: %s", i_stream_get_name(ctx->users_list_input), i_stream_get_error(ctx->users_list_input)); return -1; } return *username_r != NULL ? 1 : 0; } static void doveadm_mail_cmd_deinit_noop(struct doveadm_mail_cmd_context *ctx ATTR_UNUSED) { } struct doveadm_mail_cmd_context * doveadm_mail_cmd_init(const struct doveadm_mail_cmd *cmd, const struct doveadm_settings *set) { struct doveadm_mail_cmd_context *ctx; ctx = cmd->alloc(); ctx->set = set; ctx->cmd = cmd; if (ctx->v.init == NULL) ctx->v.init = doveadm_mail_cmd_init_noop; if (ctx->v.get_next_user == NULL) ctx->v.get_next_user = doveadm_mail_cmd_get_next_user; if (ctx->v.deinit == NULL) ctx->v.deinit = doveadm_mail_cmd_deinit_noop; p_array_init(&ctx->module_contexts, ctx->pool, 5); return ctx; } static struct doveadm_mail_cmd_context * doveadm_mail_cmdline_init(const struct doveadm_mail_cmd *cmd) { struct doveadm_mail_cmd_context *ctx; ctx = doveadm_mail_cmd_init(cmd, doveadm_settings); ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT; if (doveadm_debug) ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_DEBUG; return ctx; } static void doveadm_mail_cmd_exec(struct doveadm_mail_cmd_context *ctx, const char *wildcard_user) { const struct doveadm_cmd_context *cctx = ctx->cctx; bool cli = (cctx->conn_type == DOVEADM_CONNECTION_TYPE_CLI); int ret; const char *error; if (ctx->v.preinit != NULL) ctx->v.preinit(ctx); ctx->iterate_single_user = !ctx->iterate_all_users && wildcard_user == NULL; if (doveadm_print_is_initialized() && (!ctx->iterate_single_user || ctx->add_username_header)) { doveadm_print_header("username", "Username", DOVEADM_PRINT_HEADER_FLAG_STICKY | DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); } if (ctx->iterate_single_user) { if (cctx->username == NULL) i_fatal_status(EX_USAGE, "USER environment is missing and -u option not used"); if (!cli) { /* we may access multiple users */ ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_TEMP_PRIV_DROP; } if (ctx->add_username_header) doveadm_print_sticky("username", cctx->username); ret = doveadm_mail_single_user(ctx, &error); if (ret < 0) { /* user lookup/init failed somehow */ doveadm_exit_code = EX_TEMPFAIL; i_error("%s", error); } else if (ret == 0) { doveadm_exit_code = EX_NOUSER; i_error("User doesn't exist"); } } else { ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_TEMP_PRIV_DROP; doveadm_mail_all_users(ctx, wildcard_user); } doveadm_mail_server_flush(); doveadm_mail_cmd_deinit(ctx); doveadm_print_flush(); /* service deinit unloads mail plugins, so do it late */ mail_storage_service_deinit(&ctx->storage_service); if (ctx->exit_code != 0) doveadm_exit_code = ctx->exit_code; } void doveadm_mail_cmd_deinit(struct doveadm_mail_cmd_context *ctx) { ctx->v.deinit(ctx); if (ctx->search_args != NULL) mail_search_args_unref(&ctx->search_args); } void doveadm_mail_cmd_free(struct doveadm_mail_cmd_context *ctx) { i_stream_unref(&ctx->users_list_input); i_stream_unref(&ctx->cmd_input); pool_unref(&ctx->pool); } void doveadm_mail_help(const struct doveadm_mail_cmd *cmd) { fprintf(stderr, "doveadm %s "DOVEADM_CMD_MAIL_USAGE_PREFIX" %s\n", cmd->name, cmd->usage_args == NULL ? "" : cmd->usage_args); lib_exit(EX_USAGE); } void doveadm_mail_try_help_name(const char *cmd_name) { const struct doveadm_cmd_ver2 *cmd2; cmd2 = doveadm_cmd_find_ver2(cmd_name); if (cmd2 != NULL) help_ver2(cmd2); } void doveadm_mail_help_name(const char *cmd_name) { doveadm_mail_try_help_name(cmd_name); i_fatal("Missing help for command %s", cmd_name); } static struct doveadm_cmd_ver2 doveadm_cmd_force_resync_ver2 = { .name = "force-resync", .mail_cmd = cmd_force_resync_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-f] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('f', "fsck", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "mailbox-mask", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; static struct doveadm_cmd_ver2 doveadm_cmd_purge_ver2 = { .name = "purge", .mail_cmd = cmd_purge_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX, DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAMS_END }; static struct doveadm_cmd_ver2 *mail_commands_ver2[] = { &doveadm_cmd_batch, &doveadm_cmd_dsync_backup, &doveadm_cmd_dsync_mirror, &doveadm_cmd_dsync_server, &doveadm_cmd_mailbox_metadata_set_ver2, &doveadm_cmd_mailbox_metadata_unset_ver2, &doveadm_cmd_mailbox_metadata_get_ver2, &doveadm_cmd_mailbox_metadata_list_ver2, &doveadm_cmd_mailbox_status_ver2, &doveadm_cmd_mailbox_list_ver2, &doveadm_cmd_mailbox_create_ver2, &doveadm_cmd_mailbox_delete_ver2, &doveadm_cmd_mailbox_rename_ver2, &doveadm_cmd_mailbox_subscribe_ver2, &doveadm_cmd_mailbox_unsubscribe_ver2, &doveadm_cmd_mailbox_update_ver2, &doveadm_cmd_mailbox_path_ver2, &doveadm_cmd_fetch_ver2, &doveadm_cmd_save_ver2, &doveadm_cmd_index_ver2, &doveadm_cmd_altmove_ver2, &doveadm_cmd_deduplicate_ver2, &doveadm_cmd_expunge_ver2, &doveadm_cmd_flags_add_ver2, &doveadm_cmd_flags_remove_ver2, &doveadm_cmd_flags_replace_ver2, &doveadm_cmd_import_ver2, &doveadm_cmd_force_resync_ver2, &doveadm_cmd_purge_ver2, &doveadm_cmd_search_ver2, &doveadm_cmd_copy_ver2, &doveadm_cmd_move_ver2, &doveadm_cmd_mailbox_cache_decision, &doveadm_cmd_mailbox_cache_remove, &doveadm_cmd_mailbox_cache_purge, &doveadm_cmd_rebuild_attachments, }; void doveadm_mail_init(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(mail_commands_ver2); i++) doveadm_cmd_register_ver2(mail_commands_ver2[i]); } void doveadm_mail_init_finish(void) { struct module_dir_load_settings mod_set; i_zero(&mod_set); mod_set.abi_version = DOVECOT_ABI_VERSION; mod_set.require_init_funcs = TRUE; mod_set.debug = doveadm_debug; mod_set.binary_name = "doveadm"; /* load all configured mail plugins */ mail_storage_service_modules = module_dir_load_missing(mail_storage_service_modules, doveadm_settings->mail_plugin_dir, doveadm_settings->mail_plugins, &mod_set); /* keep mail_storage_init() referenced so that its _deinit() doesn't try to free doveadm plugins' hooks too early. */ mail_storage_init(); } void doveadm_mail_deinit(void) { mail_storage_deinit(); module_dir_unload(&mail_storage_service_modules); } static int doveadm_cmd_parse_arg(struct doveadm_mail_cmd_context *mctx, const struct doveadm_cmd_param *arg, ARRAY_TYPE(const_string) *full_args) { const char *short_opt_str = p_strdup_printf(mctx->pool, "-%c", arg->short_opt); const char *arg_value = NULL; switch (arg->type) { case CMD_PARAM_BOOL: break; case CMD_PARAM_INT64: arg_value = dec2str(arg->value.v_int64); break; case CMD_PARAM_IP: arg_value = net_ip2addr(&arg->value.v_ip); break; case CMD_PARAM_STR: arg_value = arg->value.v_string; break; case CMD_PARAM_ARRAY: { const char *str; array_foreach_elem(&arg->value.v_array, str) { optarg = (char *)str; if (!mctx->v.parse_arg(mctx, arg->short_opt)) return -1; array_push_back(full_args, &short_opt_str); array_push_back(full_args, &str); } return 0; } default: i_panic("Cannot convert parameter %s to short opt", arg->name); } optarg = (char *)arg_value; if (!mctx->v.parse_arg(mctx, arg->short_opt)) return -1; array_push_back(full_args, &short_opt_str); if (arg_value != NULL) array_push_back(full_args, &arg_value); return 0; } void doveadm_cmd_ver2_to_mail_cmd_wrapper(struct doveadm_cmd_context *cctx) { struct doveadm_mail_cmd_context *mctx; const char *wildcard_user; const char *fieldstr; ARRAY_TYPE(const_string) pargv, full_args; int i; bool cli = (cctx->conn_type == DOVEADM_CONNECTION_TYPE_CLI); bool tcp_server = (cctx->conn_type == DOVEADM_CONNECTION_TYPE_TCP); struct doveadm_mail_cmd mail_cmd = { cctx->cmd->mail_cmd, cctx->cmd->name, cctx->cmd->usage }; if (!cli) { mctx = doveadm_mail_cmd_init(&mail_cmd, doveadm_settings); /* doveadm-server always does userdb lookups */ mctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP; } else { mctx = doveadm_mail_cmdline_init(&mail_cmd); } mctx->cctx = cctx; mctx->iterate_all_users = FALSE; wildcard_user = NULL; p_array_init(&full_args, mctx->pool, 8); p_array_init(&pargv, mctx->pool, 8); for(i=0;iargc;i++) { const struct doveadm_cmd_param *arg = &cctx->argv[i]; if (!arg->value_set) continue; if (strcmp(arg->name, "all-users") == 0) { if (tcp_server) mctx->add_username_header = TRUE; else mctx->iterate_all_users = arg->value.v_bool; fieldstr = "-A"; array_push_back(&full_args, &fieldstr); } else if (strcmp(arg->name, "socket-path") == 0) { doveadm_settings->doveadm_socket_path = arg->value.v_string; if (doveadm_settings->doveadm_worker_count == 0) doveadm_settings->doveadm_worker_count = 1; } else if (strcmp(arg->name, "user") == 0) { mctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP; if (!tcp_server) cctx->username = arg->value.v_string; fieldstr = "-u"; array_push_back(&full_args, &fieldstr); array_push_back(&full_args, &arg->value.v_string); if (strchr(arg->value.v_string, '*') != NULL || strchr(arg->value.v_string, '?') != NULL) { if (tcp_server) mctx->add_username_header = TRUE; else { wildcard_user = arg->value.v_string; cctx->username = NULL; } } } else if (strcmp(arg->name, "user-file") == 0) { mctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP; wildcard_user = "*"; mctx->users_list_input = arg->value.v_istream; fieldstr = "-F"; array_push_back(&full_args, &fieldstr); fieldstr = ""; /* value doesn't really matter */ array_push_back(&full_args, &fieldstr); i_stream_ref(mctx->users_list_input); } else if (strcmp(arg->name, "field") == 0 || strcmp(arg->name, "flag") == 0) { /* mailbox status, fetch, flags: convert an array into a single space-separated parameter (alternative to fieldstr) */ fieldstr = p_array_const_string_join(mctx->pool, &arg->value.v_array, " "); array_push_back(&pargv, &fieldstr); } else if (strcmp(arg->name, "file") == 0) { /* input for doveadm_mail_get_input(), used by e.g. save */ if (mctx->cmd_input != NULL) { i_error("Only one file input allowed: %s", arg->name); doveadm_mail_cmd_free(mctx); doveadm_exit_code = EX_USAGE; return; } mctx->cmd_input = arg->value.v_istream; i_stream_ref(mctx->cmd_input); } else if (strcmp(arg->name, "trans-flags") == 0) { /* This parameter allows to set additional * mailbox transaction flags. */ mctx->transaction_flags = arg->value.v_int64; /* Keep all named special parameters above this line */ } else if (mctx->v.parse_arg != NULL && arg->short_opt != '\0') { if (doveadm_cmd_parse_arg(mctx, arg, &full_args) < 0) { i_error("Invalid parameter %c", arg->short_opt); doveadm_mail_cmd_free(mctx); doveadm_exit_code = EX_USAGE; } } else if ((arg->flags & CMD_PARAM_FLAG_POSITIONAL) != 0) { /* feed this into pargv */ if (arg->type == CMD_PARAM_ARRAY) array_append_array(&pargv, &arg->value.v_array); else if (arg->type == CMD_PARAM_STR) array_push_back(&pargv, &arg->value.v_string); } else { doveadm_exit_code = EX_USAGE; i_error("invalid parameter: %s", arg->name); doveadm_mail_cmd_free(mctx); return; } } const char *dashdash = "--"; array_push_back(&full_args, &dashdash); array_append_zero(&pargv); /* All the -parameters need to be included in full_args so that they're sent to doveadm-server. */ unsigned int args_pos = array_count(&full_args); array_append_array(&full_args, &pargv); mctx->args = array_idx(&full_args, args_pos); mctx->full_args = array_front(&full_args); doveadm_mail_cmd_exec(mctx, wildcard_user); doveadm_mail_cmd_free(mctx); } dovecot-2.3.21.1/src/doveadm/doveadm-mail-iter.c0000644000000000000000000001125514656633576016173 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ostream.h" #include "mail-storage.h" #include "mail-namespace.h" #include "mail-search.h" #include "doveadm-print.h" #include "doveadm-mail.h" #include "doveadm-mail-iter.h" struct doveadm_mail_iter { struct doveadm_mail_cmd_context *ctx; struct mail_search_args *search_args; enum doveadm_mail_iter_flags flags; struct mailbox *box; struct mailbox_transaction_context *t; struct mail_search_context *search_ctx; bool killed; }; int doveadm_mail_iter_init(struct doveadm_mail_cmd_context *ctx, const struct mailbox_info *info, struct mail_search_args *search_args, enum mail_fetch_field wanted_fields, const char *const *wanted_headers, enum doveadm_mail_iter_flags flags, struct doveadm_mail_iter **iter_r) { struct doveadm_mail_iter *iter; struct mailbox_header_lookup_ctx *headers_ctx; const char *errstr; enum mail_error error; enum mailbox_flags readonly_flag = (flags & DOVEADM_MAIL_ITER_FLAG_READONLY) != 0 ? MAILBOX_FLAG_READONLY : 0; iter = i_new(struct doveadm_mail_iter, 1); iter->ctx = ctx; iter->flags = flags; iter->box = mailbox_alloc(info->ns->list, info->vname, MAILBOX_FLAG_IGNORE_ACLS | readonly_flag); iter->search_args = search_args; if (mailbox_sync(iter->box, MAILBOX_SYNC_FLAG_FULL_READ) < 0) { errstr = mailbox_get_last_internal_error(iter->box, &error); if (error == MAIL_ERROR_NOTFOUND) { /* just ignore this mailbox */ *iter_r = iter; return 0; } i_error("Syncing mailbox %s failed: %s", info->vname, errstr); doveadm_mail_failed_mailbox(ctx, iter->box); mailbox_free(&iter->box); i_free(iter); return -1; } headers_ctx = wanted_headers == NULL || wanted_headers[0] == NULL ? NULL : mailbox_header_lookup_init(iter->box, wanted_headers); mail_search_args_init(search_args, iter->box, FALSE, NULL); iter->t = mailbox_transaction_begin(iter->box, ctx->transaction_flags, ctx->cmd->name); iter->search_ctx = mailbox_search_init(iter->t, search_args, NULL, wanted_fields, headers_ctx); mailbox_header_lookup_unref(&headers_ctx); *iter_r = iter; return 0; } static int doveadm_mail_iter_deinit_transaction(struct doveadm_mail_iter *iter, bool commit) { int ret = 0; if (iter->search_ctx != NULL) { if (mailbox_search_deinit(&iter->search_ctx) < 0) { i_error("Searching mailbox %s failed: %s", mailbox_get_vname(iter->box), mailbox_get_last_internal_error(iter->box, NULL)); ret = -1; } } if (iter->t == NULL) ; else if (commit) { if (mailbox_transaction_commit(&iter->t) < 0) { i_error("Committing mailbox %s failed: %s", mailbox_get_vname(iter->box), mailbox_get_last_internal_error(iter->box, NULL)); ret = -1; } } else { mailbox_transaction_rollback(&iter->t); } mail_search_args_deinit(iter->search_args); return ret; } static int doveadm_mail_iter_deinit_full(struct doveadm_mail_iter **_iter, bool sync, bool commit, bool keep_box) { struct doveadm_mail_iter *iter = *_iter; int ret; *_iter = NULL; ret = doveadm_mail_iter_deinit_transaction(iter, commit); if (ret == 0 && sync) { ret = mailbox_sync(iter->box, 0); if (ret < 0) { i_error("Mailbox %s: Mailbox sync failed: %s", mailbox_get_vname(iter->box), mailbox_get_last_internal_error(iter->box, NULL)); } } if (ret < 0) doveadm_mail_failed_mailbox(iter->ctx, iter->box); else if (iter->killed) { iter->ctx->exit_code = EX_TEMPFAIL; ret = -1; } if (!keep_box) mailbox_free(&iter->box); i_free(iter); return ret; } int doveadm_mail_iter_deinit(struct doveadm_mail_iter **_iter) { return doveadm_mail_iter_deinit_full(_iter, FALSE, TRUE, FALSE); } int doveadm_mail_iter_deinit_sync(struct doveadm_mail_iter **_iter) { return doveadm_mail_iter_deinit_full(_iter, TRUE, TRUE, FALSE); } int doveadm_mail_iter_deinit_keep_box(struct doveadm_mail_iter **iter, struct mailbox **box_r) { *box_r = (*iter)->box; return doveadm_mail_iter_deinit_full(iter, FALSE, TRUE, TRUE); } void doveadm_mail_iter_deinit_rollback(struct doveadm_mail_iter **_iter) { (void)doveadm_mail_iter_deinit_full(_iter, FALSE, FALSE, FALSE); } bool doveadm_mail_iter_next(struct doveadm_mail_iter *iter, struct mail **mail_r) { if (iter->search_ctx == NULL) return FALSE; if (doveadm_is_killed()) { iter->killed = TRUE; return FALSE; } if ((iter->flags & DOVEADM_MAIL_ITER_FLAG_STOP_WITH_CLIENT) != 0 && doveadm_print_ostream->stream_errno != 0) { iter->killed = TRUE; return FALSE; } return mailbox_search_next(iter->search_ctx, mail_r); } struct mailbox *doveadm_mail_iter_get_mailbox(struct doveadm_mail_iter *iter) { return iter->box; } dovecot-2.3.21.1/src/doveadm/doveadm-mail-mailbox.c0000644000000000000000000005751614656633576016675 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "unichar.h" #include "imap-utf7.h" #include "mail-namespace.h" #include "mail-storage.h" #include "mail-search-build.h" #include "doveadm-print.h" #include "doveadm-mailbox-list-iter.h" #include "doveadm-mail.h" #include struct doveadm_mailbox_cmd_context { struct doveadm_mail_cmd_context ctx; bool subscriptions; }; struct mailbox_cmd_context { struct doveadm_mailbox_cmd_context ctx; ARRAY_TYPE(const_string) mailboxes; }; struct create_cmd_context { struct doveadm_mailbox_cmd_context ctx; ARRAY_TYPE(const_string) mailboxes; struct mailbox_update update; }; struct delete_cmd_context { struct doveadm_mailbox_cmd_context ctx; ARRAY_TYPE(const_string) mailboxes; bool recursive; bool require_empty; bool unsafe; }; struct rename_cmd_context { struct doveadm_mailbox_cmd_context ctx; const char *oldname, *newname; }; struct list_cmd_context { struct doveadm_mailbox_cmd_context ctx; struct mail_search_args *search_args; bool mutf7; }; struct update_cmd_context { struct doveadm_mailbox_cmd_context ctx; const char *mailbox; struct mailbox_update update; }; struct path_cmd_context { struct doveadm_mailbox_cmd_context ctx; const char *mailbox; enum mailbox_list_path_type path_type; }; static const char *mailbox_list_path_type_names[] = { "dir", "alt-dir", "mailbox", "alt-mailbox", "control", "index", "index-private" }; void doveadm_mailbox_args_check(const char *const args[]) { unsigned int i; for (i = 0; args[i] != NULL; i++) { if (!uni_utf8_str_is_valid(args[i])) { i_fatal_status(EX_DATAERR, "Mailbox name not valid UTF-8: %s", args[i]); } } } static bool cmd_mailbox_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct doveadm_mailbox_cmd_context *ctx = (struct doveadm_mailbox_cmd_context *)_ctx; switch (c) { case 's': ctx->subscriptions = TRUE; break; default: return FALSE; } return TRUE; } #define doveadm_mailbox_cmd_alloc(type) \ (type *)doveadm_mailbox_cmd_alloc_size(sizeof(type)) static struct doveadm_mail_cmd_context * doveadm_mailbox_cmd_alloc_size(size_t size) { struct doveadm_mail_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc_size(size); ctx->getopt_args = "s"; ctx->v.parse_arg = cmd_mailbox_parse_arg; return ctx; } static bool cmd_mailbox_list_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct list_cmd_context *ctx = (struct list_cmd_context *)_ctx; switch (c) { case '7': ctx->mutf7 = TRUE; break; case '8': ctx->mutf7 = FALSE; break; case 's': ctx->ctx.subscriptions = TRUE; break; default: return FALSE; } return TRUE; } static int cmd_mailbox_list_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct list_cmd_context *ctx = (struct list_cmd_context *)_ctx; enum mailbox_list_iter_flags iter_flags = MAILBOX_LIST_ITER_RETURN_NO_FLAGS; struct doveadm_mailbox_list_iter *iter; const struct mailbox_info *info; string_t *str = t_str_new(256); if (ctx->ctx.subscriptions) iter_flags |= MAILBOX_LIST_ITER_SELECT_SUBSCRIBED; iter = doveadm_mailbox_list_iter_full_init(_ctx, user, ctx->search_args, iter_flags); while ((info = doveadm_mailbox_list_iter_next(iter)) != NULL) { if (!ctx->mutf7) doveadm_print(info->vname); else { str_truncate(str, 0); if (imap_utf8_to_utf7(info->vname, str) < 0) i_unreached(); doveadm_print(str_c(str)); } } if (doveadm_mailbox_list_iter_deinit(&iter) < 0) return -1; return 0; } struct mail_search_args * doveadm_mail_mailbox_search_args_build(const char *const args[]) { struct mail_search_args *search_args; struct mail_search_arg *arg; enum mail_search_arg_type type; unsigned int i; doveadm_mailbox_args_check(args); search_args = mail_search_build_init(); for (i = 0; args[i] != NULL; i++) { if (strchr(args[i], '*') != NULL || strchr(args[i], '%') != NULL) type = SEARCH_MAILBOX_GLOB; else type = SEARCH_MAILBOX; arg = mail_search_build_add(search_args, type); arg->value.str = p_strdup(search_args->pool, args[i]); } if (i > 1) { struct mail_search_arg *subargs = search_args->args; search_args->args = NULL; arg = mail_search_build_add(search_args, SEARCH_OR); arg->value.subargs = subargs; } return search_args; } static void cmd_mailbox_list_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct list_cmd_context *ctx = (struct list_cmd_context *)_ctx; doveadm_print_header("mailbox", "mailbox", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); ctx->search_args = doveadm_mail_mailbox_search_args_build(args); } static void cmd_mailbox_list_deinit(struct doveadm_mail_cmd_context *_ctx) { struct list_cmd_context *ctx = (struct list_cmd_context *)_ctx; if (ctx->search_args != NULL) mail_search_args_unref(&ctx->search_args); } static struct doveadm_mail_cmd_context *cmd_mailbox_list_alloc(void) { struct list_cmd_context *ctx; ctx = doveadm_mailbox_cmd_alloc(struct list_cmd_context); ctx->ctx.ctx.v.init = cmd_mailbox_list_init; ctx->ctx.ctx.v.deinit = cmd_mailbox_list_deinit; ctx->ctx.ctx.v.run = cmd_mailbox_list_run; ctx->ctx.ctx.v.parse_arg = cmd_mailbox_list_parse_arg; ctx->ctx.ctx.getopt_args = "78s"; doveadm_print_init(DOVEADM_PRINT_TYPE_FLOW); return &ctx->ctx.ctx; } static int cmd_mailbox_create_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct create_cmd_context *ctx = (struct create_cmd_context *)_ctx; struct mail_namespace *ns; struct mailbox *box; const char *name; int ret = 0; array_foreach_elem(&ctx->mailboxes, name) { size_t len; bool directory = FALSE; ns = mail_namespace_find(user->namespaces, name); len = strlen(name); if (len > 0 && name[len-1] == mail_namespace_get_sep(ns)) { name = t_strndup(name, len-1); directory = TRUE; } box = mailbox_alloc(ns->list, name, 0); if (mailbox_create(box, &ctx->update, directory) < 0) { i_error("Can't create mailbox %s: %s", name, mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(_ctx, box); ret = -1; } if (ctx->ctx.subscriptions) { if (mailbox_set_subscribed(box, TRUE) < 0) { i_error("Can't subscribe to mailbox %s: %s", name, mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(_ctx, box); ret = -1; } } mailbox_free(&box); } return ret; } static void cmd_mailbox_create_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct mailbox_cmd_context *ctx = (struct mailbox_cmd_context *)_ctx; const char *name; unsigned int i; if (args[0] == NULL) doveadm_mail_help_name("mailbox create"); doveadm_mailbox_args_check(args); for (i = 0; args[i] != NULL; i++) { name = p_strdup(ctx->ctx.ctx.pool, args[i]); array_push_back(&ctx->mailboxes, &name); } } static bool cmd_mailbox_create_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct create_cmd_context *ctx = (struct create_cmd_context *)_ctx; switch (c) { case 'g': if (guid_128_from_string(optarg, ctx->update.mailbox_guid) < 0) doveadm_mail_help_name("mailbox create"); break; case 's': ctx->ctx.subscriptions = TRUE; break; default: return FALSE; } return TRUE; } static struct doveadm_mail_cmd_context *cmd_mailbox_create_alloc(void) { struct create_cmd_context *ctx; ctx = doveadm_mailbox_cmd_alloc(struct create_cmd_context); ctx->ctx.ctx.v.init = cmd_mailbox_create_init; ctx->ctx.ctx.v.run = cmd_mailbox_create_run; ctx->ctx.ctx.v.parse_arg = cmd_mailbox_create_parse_arg; ctx->ctx.ctx.getopt_args = "g:s"; p_array_init(&ctx->mailboxes, ctx->ctx.ctx.pool, 16); return &ctx->ctx.ctx; } static int i_strcmp_reverse_p(const char *const *s1, const char *const *s2) { return -strcmp(*s1, *s2); } static int get_child_mailboxes(struct mail_user *user, ARRAY_TYPE(const_string) *mailboxes, const char *name) { struct mailbox_list_iterate_context *iter; struct mail_namespace *ns; const struct mailbox_info *info; const char *pattern, *child_name; ns = mail_namespace_find(user->namespaces, name); pattern = name[0] == '\0' ? "*" : t_strdup_printf("%s%c*", name, mail_namespace_get_sep(ns)); iter = mailbox_list_iter_init(ns->list, pattern, MAILBOX_LIST_ITER_RETURN_NO_FLAGS); while ((info = mailbox_list_iter_next(iter)) != NULL) { child_name = t_strdup(info->vname); array_push_back(mailboxes, &child_name); } return mailbox_list_iter_deinit(&iter); } static int cmd_mailbox_delete_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct delete_cmd_context *ctx = (struct delete_cmd_context *)_ctx; struct mail_namespace *ns; struct mailbox *box; struct mail_storage *storage; const char *name; ARRAY_TYPE(const_string) recursive_mailboxes; const ARRAY_TYPE(const_string) *mailboxes = &ctx->mailboxes; enum mailbox_flags mailbox_flags = 0; int ret = 0, ret2; if (ctx->unsafe) mailbox_flags |= MAILBOX_FLAG_DELETE_UNSAFE; if (ctx->recursive) { t_array_init(&recursive_mailboxes, 32); array_foreach_elem(&ctx->mailboxes, name) { if (get_child_mailboxes(user, &recursive_mailboxes, name) < 0) { doveadm_mail_failed_error(_ctx, MAIL_ERROR_TEMP); ret = -1; } if (name[0] != '\0') array_push_back(&recursive_mailboxes, &name); } array_sort(&recursive_mailboxes, i_strcmp_reverse_p); mailboxes = &recursive_mailboxes; } array_foreach_elem(mailboxes, name) { ns = mail_namespace_find(user->namespaces, name); box = mailbox_alloc(ns->list, name, mailbox_flags); storage = mailbox_get_storage(box); ret2 = ctx->require_empty ? mailbox_delete_empty(box) : mailbox_delete(box); if (ret2 < 0) { i_error("Can't delete mailbox %s: %s", name, mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(_ctx, box); ret = -1; } if (ctx->ctx.subscriptions) { if (mailbox_set_subscribed(box, FALSE) < 0) { i_error("Can't unsubscribe mailbox %s: %s", name, mail_storage_get_last_internal_error(storage, NULL)); doveadm_mail_failed_mailbox(_ctx, box); ret = -1; } } mailbox_free(&box); } return ret; } static void cmd_mailbox_delete_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct delete_cmd_context *ctx = (struct delete_cmd_context *)_ctx; const char *name; unsigned int i; if (args[0] == NULL) doveadm_mail_help_name("mailbox delete"); doveadm_mailbox_args_check(args); for (i = 0; args[i] != NULL; i++) { name = p_strdup(ctx->ctx.ctx.pool, args[i]); array_push_back(&ctx->mailboxes, &name); } array_sort(&ctx->mailboxes, i_strcmp_reverse_p); } static bool cmd_mailbox_delete_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct delete_cmd_context *ctx = (struct delete_cmd_context *)_ctx; switch (c) { case 'r': ctx->recursive = TRUE; break; case 's': ctx->ctx.subscriptions = TRUE; break; case 'e': ctx->require_empty = TRUE; break; case 'Z': ctx->unsafe = TRUE; break; default: return FALSE; } return TRUE; } static struct doveadm_mail_cmd_context *cmd_mailbox_delete_alloc(void) { struct delete_cmd_context *ctx; ctx = doveadm_mailbox_cmd_alloc(struct delete_cmd_context); ctx->ctx.ctx.v.init = cmd_mailbox_delete_init; ctx->ctx.ctx.v.run = cmd_mailbox_delete_run; ctx->ctx.ctx.v.parse_arg = cmd_mailbox_delete_parse_arg; ctx->ctx.ctx.getopt_args = "ersZ"; p_array_init(&ctx->mailboxes, ctx->ctx.ctx.pool, 16); return &ctx->ctx.ctx; } static int cmd_mailbox_rename_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct rename_cmd_context *ctx = (struct rename_cmd_context *)_ctx; struct mail_namespace *oldns, *newns; struct mailbox *oldbox, *newbox; const char *oldname = ctx->oldname; const char *newname = ctx->newname; int ret = 0; oldns = mail_namespace_find(user->namespaces, oldname); newns = mail_namespace_find(user->namespaces, newname); oldbox = mailbox_alloc(oldns->list, oldname, 0); newbox = mailbox_alloc(newns->list, newname, 0); if (mailbox_rename(oldbox, newbox) < 0) { i_error("Can't rename mailbox %s to %s: %s", oldname, newname, mailbox_get_last_internal_error(oldbox, NULL)); doveadm_mail_failed_mailbox(_ctx, oldbox); ret = -1; } if (ctx->ctx.subscriptions) { if (mailbox_set_subscribed(oldbox, FALSE) < 0) { i_error("Can't unsubscribe mailbox %s: %s", ctx->oldname, mailbox_get_last_internal_error(oldbox, NULL)); doveadm_mail_failed_mailbox(_ctx, oldbox); ret = -1; } if (mailbox_set_subscribed(newbox, TRUE) < 0) { i_error("Can't subscribe to mailbox %s: %s", ctx->newname, mailbox_get_last_internal_error(newbox, NULL)); doveadm_mail_failed_mailbox(_ctx, newbox); ret = -1; } } mailbox_free(&oldbox); mailbox_free(&newbox); return ret; } static void cmd_mailbox_rename_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct rename_cmd_context *ctx = (struct rename_cmd_context *)_ctx; if (str_array_length(args) != 2) doveadm_mail_help_name("mailbox rename"); doveadm_mailbox_args_check(args); ctx->oldname = p_strdup(ctx->ctx.ctx.pool, args[0]); ctx->newname = p_strdup(ctx->ctx.ctx.pool, args[1]); } static struct doveadm_mail_cmd_context *cmd_mailbox_rename_alloc(void) { struct rename_cmd_context *ctx; ctx = doveadm_mailbox_cmd_alloc(struct rename_cmd_context); ctx->ctx.ctx.v.init = cmd_mailbox_rename_init; ctx->ctx.ctx.v.run = cmd_mailbox_rename_run; return &ctx->ctx.ctx; } static int cmd_mailbox_subscribe_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct mailbox_cmd_context *ctx = (struct mailbox_cmd_context *)_ctx; struct mail_namespace *ns; struct mailbox *box; const char *name; int ret = 0; array_foreach_elem(&ctx->mailboxes, name) { ns = mail_namespace_find(user->namespaces, name); box = mailbox_alloc(ns->list, name, 0); if (mailbox_set_subscribed(box, ctx->ctx.subscriptions) < 0) { i_error("Can't %s mailbox %s: %s", name, ctx->ctx.subscriptions ? "subscribe to" : "unsubscribe", mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(_ctx, box); ret = -1; } mailbox_free(&box); } return ret; } static void cmd_mailbox_subscribe_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct mailbox_cmd_context *ctx = (struct mailbox_cmd_context *)_ctx; const char *name; unsigned int i; if (args[0] == NULL) { doveadm_mail_help_name(ctx->ctx.subscriptions ? "mailbox subscribe" : "mailbox unsubscribe"); } doveadm_mailbox_args_check(args); for (i = 0; args[i] != NULL; i++) { name = p_strdup(ctx->ctx.ctx.pool, args[i]); array_push_back(&ctx->mailboxes, &name); } } static struct doveadm_mail_cmd_context * cmd_mailbox_subscriptions_alloc(bool subscriptions) { struct mailbox_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct mailbox_cmd_context); ctx->ctx.subscriptions = subscriptions; ctx->ctx.ctx.v.parse_arg = cmd_mailbox_parse_arg; ctx->ctx.ctx.v.init = cmd_mailbox_subscribe_init; ctx->ctx.ctx.v.run = cmd_mailbox_subscribe_run; p_array_init(&ctx->mailboxes, ctx->ctx.ctx.pool, 16); return &ctx->ctx.ctx; } static struct doveadm_mail_cmd_context *cmd_mailbox_subscribe_alloc(void) { return cmd_mailbox_subscriptions_alloc(TRUE); } static struct doveadm_mail_cmd_context *cmd_mailbox_unsubscribe_alloc(void) { return cmd_mailbox_subscriptions_alloc(FALSE); } static void cmd_mailbox_update_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct update_cmd_context *ctx = (struct update_cmd_context *)_ctx; if (str_array_length(args) != 1) doveadm_mail_help_name("mailbox update"); doveadm_mailbox_args_check(args); ctx->mailbox = args[0]; if ((ctx->update.min_first_recent_uid != 0 || ctx->update.min_next_uid != 0) && ctx->update.min_first_recent_uid > ctx->update.min_next_uid) { i_fatal_status(EX_DATAERR, "min_first_recent_uid > min_next_uid"); } } static bool cmd_mailbox_update_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct update_cmd_context *ctx = (struct update_cmd_context *)_ctx; switch (c) { case 'g': if (guid_128_from_string(optarg, ctx->update.mailbox_guid) < 0) doveadm_mail_help_name("mailbox update"); break; case 'V': if (str_to_uint32(optarg, &(ctx->update.uid_validity)) < 0) doveadm_mail_help_name("mailbox update"); break; case 'N': if (str_to_uint32(optarg, &(ctx->update.min_next_uid)) < 0) doveadm_mail_help_name("mailbox update"); break; case 'R': if (str_to_uint32(optarg, &(ctx->update.min_first_recent_uid)) < 0) doveadm_mail_help_name("mailbox update"); break; case 'H': if (str_to_uint64(optarg, &(ctx->update.min_highest_modseq)) < 0) doveadm_mail_help_name("mailbox update"); break; case 'P': if (str_to_uint64(optarg, &(ctx->update.min_highest_pvt_modseq)) < 0) doveadm_mail_help_name("mailbox update"); break; default: return FALSE; } return TRUE; } static int cmd_mailbox_update_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct update_cmd_context *ctx = (struct update_cmd_context *)_ctx; struct mail_namespace *ns; struct mailbox *box; enum mail_error mail_error; int ret = 0; ns = mail_namespace_find(user->namespaces, ctx->mailbox); box = mailbox_alloc(ns->list, ctx->mailbox, 0); if ((ret = mailbox_update(box, &(ctx->update))) != 0) { i_error("Cannot update %s: %s", ctx->mailbox, mailbox_get_last_internal_error(box, &mail_error)); doveadm_mail_failed_error(_ctx, mail_error); } mailbox_free(&box); return ret; } static struct doveadm_mail_cmd_context *cmd_mailbox_update_alloc(void) { struct update_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct update_cmd_context); ctx->ctx.ctx.v.parse_arg = cmd_mailbox_update_parse_arg; ctx->ctx.ctx.v.init = cmd_mailbox_update_init; ctx->ctx.ctx.v.run = cmd_mailbox_update_run; return &ctx->ctx.ctx; } static void cmd_mailbox_path_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct update_cmd_context *ctx = (struct update_cmd_context *)_ctx; if (str_array_length(args) != 1) doveadm_mail_help_name("mailbox path"); doveadm_mailbox_args_check(args); ctx->mailbox = args[0]; doveadm_print_header("path", "path", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); } static bool mailbox_list_path_type_name_parse(const char *name, enum mailbox_list_path_type *type_r) { enum mailbox_list_path_type type; for (type = 0; type < N_ELEMENTS(mailbox_list_path_type_names); type++) { if (strcmp(mailbox_list_path_type_names[type], name) == 0) { *type_r = type; return TRUE; } } return FALSE; } static bool cmd_mailbox_path_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct path_cmd_context *ctx = (struct path_cmd_context *)_ctx; switch (c) { case 't': if (!mailbox_list_path_type_name_parse(optarg, &ctx->path_type)) doveadm_mail_help_name("mailbox path"); break; default: return FALSE; } return TRUE; } static int cmd_mailbox_path_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct path_cmd_context *ctx = (struct path_cmd_context *)_ctx; struct mail_namespace *ns; enum mail_error mail_error; const char *storage_name, *path; int ret; ns = mail_namespace_find(user->namespaces, ctx->mailbox); storage_name = mailbox_list_get_storage_name(ns->list, ctx->mailbox); ret = mailbox_list_get_path(ns->list, storage_name, ctx->path_type, &path); if (ret < 0) { i_error("Failed to lookup mailbox %s path: %s", ctx->mailbox, mailbox_list_get_last_internal_error(ns->list, &mail_error)); doveadm_mail_failed_error(_ctx, mail_error); } else if (ret > 0) { doveadm_print(path); } return ret; } static struct doveadm_mail_cmd_context *cmd_mailbox_path_alloc(void) { struct path_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct path_cmd_context); ctx->path_type = MAILBOX_LIST_PATH_TYPE_INDEX; ctx->ctx.ctx.v.parse_arg = cmd_mailbox_path_parse_arg; ctx->ctx.ctx.v.init = cmd_mailbox_path_init; ctx->ctx.ctx.v.run = cmd_mailbox_path_run; doveadm_print_init(DOVEADM_PRINT_TYPE_FLOW); return &ctx->ctx.ctx; } struct doveadm_cmd_ver2 doveadm_cmd_mailbox_list_ver2 = { .name = "mailbox list", .mail_cmd = cmd_mailbox_list_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"[-7|-8] [-s] [ [...]]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('7', "mutf7", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('8', "utf8", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('s', "subscriptions", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "mailbox-mask", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_mailbox_create_ver2 = { .name = "mailbox create", .mail_cmd = cmd_mailbox_create_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"[-s] [-g ] [...]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('s', "subscriptions", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('g', "guid", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_mailbox_delete_ver2 = { .name = "mailbox delete", .mail_cmd = cmd_mailbox_delete_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"[-e] [-r] [-s] [-Z] [...]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('e', "require-empty", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('s', "subscriptions", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('r', "recursive", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('Z', "unsafe", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_mailbox_rename_ver2 = { .name = "mailbox rename", .mail_cmd = cmd_mailbox_rename_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"[-s] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('s', "subscriptions", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "new-name", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_mailbox_subscribe_ver2 = { .name = "mailbox subscribe", .mail_cmd = cmd_mailbox_subscribe_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX" [...]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_mailbox_unsubscribe_ver2 = { .name = "mailbox unsubscribe", .mail_cmd = cmd_mailbox_unsubscribe_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX" [...]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_mailbox_update_ver2 = { .name = "mailbox update", .mail_cmd = cmd_mailbox_update_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"[--mailbox-guid guid] [--uid-validity uid] [--min-next-uid uid] [--min-first-recent-uid uid] [--min-highest-modseq seq] [--min-highest-pvt-modseq seq] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('g', "mailbox-guid", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('V', "uid-validity", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('N', "min-next-uid", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('R', "min-first-recent-uid", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('H', "min-highest-modseq", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('P', "min-highest-pvt-modseq", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_mailbox_path_ver2 = { .name = "mailbox path", .mail_cmd = cmd_mailbox_path_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"[-t ] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('t', "type", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-mail-save.c0000644000000000000000000000754514656633576016175 00000000000000/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "istream.h" #include "mail-storage.h" #include "doveadm-mail.h" struct save_cmd_context { struct doveadm_mail_cmd_context ctx; const char *mailbox; }; static int cmd_save_to_mailbox(struct save_cmd_context *ctx, struct mailbox *box, struct istream *input) { struct mail_storage *storage = mailbox_get_storage(box); struct mailbox_transaction_context *trans; struct mail_save_context *save_ctx; ssize_t ret; bool save_failed = FALSE; if (input->stream_errno != 0) { i_error("open(%s) failed: %s", i_stream_get_name(input), i_stream_get_error(input)); ctx->ctx.exit_code = EX_TEMPFAIL; return -1; } if (mailbox_open(box) < 0) { i_error("Failed to open mailbox %s: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_storage(&ctx->ctx, storage); return -1; } trans = mailbox_transaction_begin(box, MAILBOX_TRANSACTION_FLAG_EXTERNAL | ctx->ctx.transaction_flags, __func__); save_ctx = mailbox_save_alloc(trans); if (mailbox_save_begin(&save_ctx, input) < 0) { i_error("Saving failed: %s", mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_storage(&ctx->ctx, storage); mailbox_transaction_rollback(&trans); return -1; } do { if (mailbox_save_continue(save_ctx) < 0) { save_failed = TRUE; ret = -1; break; } } while ((ret = i_stream_read(input)) > 0); i_assert(ret == -1); if (input->stream_errno != 0) { i_error("read(msg input) failed: %s", i_stream_get_error(input)); doveadm_mail_failed_error(&ctx->ctx, MAIL_ERROR_TEMP); } else if (save_failed) { i_error("Saving failed: %s", mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_storage(&ctx->ctx, storage); } else if (mailbox_save_finish(&save_ctx) < 0) { i_error("Saving failed: %s", mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_storage(&ctx->ctx, storage); } else if (mailbox_transaction_commit(&trans) < 0) { i_error("Save transaction commit failed: %s", mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_storage(&ctx->ctx, storage); } else { ret = 0; } if (save_ctx != NULL) mailbox_save_cancel(&save_ctx); if (trans != NULL) mailbox_transaction_rollback(&trans); i_assert(input->eof); return ret < 0 ? -1 : 0; } static int cmd_save_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct save_cmd_context *ctx = (struct save_cmd_context *)_ctx; struct mail_namespace *ns; struct mailbox *box; int ret; ns = mail_namespace_find(user->namespaces, ctx->mailbox); box = mailbox_alloc(ns->list, ctx->mailbox, MAILBOX_FLAG_SAVEONLY); ret = cmd_save_to_mailbox(ctx, box, _ctx->cmd_input); mailbox_free(&box); return ret; } static void cmd_save_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[] ATTR_UNUSED) { doveadm_mail_get_input(_ctx); } static bool cmd_mailbox_save_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct save_cmd_context *ctx = (struct save_cmd_context *)_ctx; switch (c) { case 'm': ctx->mailbox = optarg; break; default: return FALSE; } return TRUE; } static struct doveadm_mail_cmd_context *cmd_save_alloc(void) { struct save_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct save_cmd_context); ctx->ctx.getopt_args = "m:"; ctx->ctx.v.parse_arg = cmd_mailbox_save_parse_arg; ctx->ctx.v.init = cmd_save_init; ctx->ctx.v.run = cmd_save_run; ctx->mailbox = "INBOX"; return &ctx->ctx; } struct doveadm_cmd_ver2 doveadm_cmd_save_ver2 = { .name = "save", .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"[-m mailbox]", .mail_cmd = cmd_save_alloc, DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('m', "mailbox", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "file", CMD_PARAM_ISTREAM, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-print-formatted.c0000644000000000000000000000416314656633576017427 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "ostream.h" #include "doveadm.h" #include "doveadm-server.h" #include "doveadm-print.h" #include "doveadm-print-private.h" #include "client-connection.h" #include "var-expand.h" struct doveadm_print_formatted_context { pool_t pool; const char *format; ARRAY(struct var_expand_table) headers; string_t *buf; string_t *vbuf; unsigned int idx; }; static struct doveadm_print_formatted_context ctx; void doveadm_print_formatted_set_format(const char *format) { ctx.format = format; } static void doveadm_print_formatted_init(void) { i_zero(&ctx); ctx.pool = pool_alloconly_create("doveadm formatted print", 1024); ctx.buf = str_new(ctx.pool, 256); p_array_init(&ctx.headers, ctx.pool, 8); ctx.idx = 0; } static void doveadm_print_formatted_header(const struct doveadm_print_header *hdr) { struct var_expand_table entry; i_zero(&entry); entry.key = '\0'; entry.long_key = p_strdup(ctx.pool, hdr->key); entry.value = NULL; array_push_back(&ctx.headers, &entry); } static void doveadm_print_formatted_flush(void) { o_stream_nsend(doveadm_print_ostream, str_data(ctx.buf), str_len(ctx.buf)); str_truncate(ctx.buf, 0); } static void doveadm_print_formatted_print(const char *value) { if (ctx.format == NULL) { i_fatal("formatted formatter cannot be used without a format."); } const char *error; struct var_expand_table *entry = array_idx_modifiable(&ctx.headers, ctx.idx++); entry->value = value; if (ctx.idx >= array_count(&ctx.headers)) { if (var_expand(ctx.buf, ctx.format, array_front(&ctx.headers), &error) <= 0) { i_error("Failed to expand print format '%s': %s", ctx.format, error); } doveadm_print_formatted_flush(); ctx.idx = 0; } } static void doveadm_print_formatted_deinit(void) { pool_unref(&ctx.pool); } struct doveadm_print_vfuncs doveadm_print_formatted_vfuncs = { "formatted", doveadm_print_formatted_init, doveadm_print_formatted_deinit, doveadm_print_formatted_header, doveadm_print_formatted_print, NULL, doveadm_print_formatted_flush }; dovecot-2.3.21.1/src/doveadm/doveadm-auth.c0000644000000000000000000005667214656633576015265 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "array.h" #include "askpass.h" #include "base64.h" #include "hex-binary.h" #include "str.h" #include "strescape.h" #include "var-expand.h" #include "wildcard-match.h" #include "settings-parser.h" #include "master-service.h" #include "master-service-settings.h" #include "auth-client.h" #include "auth-master.h" #include "master-auth.h" #include "master-login-auth.h" #include "mail-storage-service.h" #include "mail-user.h" #include "doveadm.h" #include "doveadm-print.h" #include #include static struct event_category event_category_auth = { .name = "auth", }; struct authtest_input { pool_t pool; const char *username; const char *master_user; const char *password; struct auth_user_info info; bool success; struct auth_client_request *request; struct master_auth_request master_auth_req; unsigned int auth_id; unsigned int auth_pid; const char *auth_cookie; }; static void auth_cmd_help(struct doveadm_cmd_context *cctx); static struct auth_master_connection * doveadm_get_auth_master_conn(const char *auth_socket_path) { enum auth_master_flags flags = 0; if (doveadm_debug) flags |= AUTH_MASTER_FLAG_DEBUG; return auth_master_init(auth_socket_path, flags); } static int cmd_user_input(struct auth_master_connection *conn, const struct authtest_input *input, const char *show_field, bool userdb) { const char *lookup_name = userdb ? "userdb lookup" : "passdb lookup"; pool_t pool; const char *updated_username = NULL, *const *fields, *p; int ret; pool = pool_alloconly_create("auth master lookup", 1024); if (userdb) { ret = auth_master_user_lookup(conn, input->username, &input->info, pool, &updated_username, &fields); } else { ret = auth_master_pass_lookup(conn, input->username, &input->info, pool, &fields); } if (ret < 0) { if (fields[0] == NULL) i_error("%s failed for %s", lookup_name, input->username); else { i_error("%s failed for %s: %s", lookup_name, input->username, fields[0]); } ret = -1; } else if (ret == 0) { fprintf(show_field == NULL ? stdout : stderr, "%s: user %s doesn't exist\n", lookup_name, input->username); } else if (show_field != NULL) { size_t show_field_len = strlen(show_field); for (; *fields != NULL; fields++) { if (strncmp(*fields, show_field, show_field_len) == 0 && (*fields)[show_field_len] == '=') printf("%s\n", *fields + show_field_len + 1); } } else { printf("%s: %s\n", userdb ? "userdb" : "passdb", input->username); if (updated_username != NULL) printf(" %-10s: %s\n", "user", updated_username); for (; *fields != NULL; fields++) { p = strchr(*fields, '='); if (p == NULL) printf(" %-10s\n", *fields); else { printf(" %-10s: %s\n", t_strcut(*fields, '='), p + 1); } } } pool_unref(&pool); return ret; } static void auth_callback(struct auth_client_request *request ATTR_UNUSED, enum auth_request_status status, const char *data_base64 ATTR_UNUSED, const char *const *args, void *context) { struct authtest_input *input = context; input->request = NULL; input->auth_id = auth_client_request_get_id(request); input->auth_pid = auth_client_request_get_server_pid(request); input->auth_cookie = input->pool == NULL ? NULL : p_strdup(input->pool, auth_client_request_get_cookie(request)); if (!io_loop_is_running(current_ioloop)) return; if (status == 0) i_fatal("passdb expects SASL continuation"); switch (status) { case AUTH_REQUEST_STATUS_ABORT: i_unreached(); case AUTH_REQUEST_STATUS_INTERNAL_FAIL: case AUTH_REQUEST_STATUS_FAIL: printf("passdb: %s auth failed\n", input->username); break; case AUTH_REQUEST_STATUS_CONTINUE: printf("passdb: %s auth unexpectedly requested continuation\n", input->username); break; case AUTH_REQUEST_STATUS_OK: input->success = TRUE; printf("passdb: %s auth succeeded\n", input->username); break; } if (args != NULL && *args != NULL) { printf("extra fields:\n"); for (; *args != NULL; args++) printf(" %s\n", *args); } io_loop_stop(current_ioloop); } static void auth_connected(struct auth_client *client, bool connected, void *context) { struct event *event_auth; struct authtest_input *input = context; struct auth_request_info info; string_t *init_resp, *base64_resp; if (!connected) i_fatal("Couldn't connect to auth socket"); event_auth = event_create(NULL); event_add_category(event_auth, &event_category_auth); init_resp = t_str_new(128); str_append(init_resp, input->username); str_append_c(init_resp, '\0'); if (input->master_user != NULL) str_append(init_resp, input->master_user); else str_append(init_resp, input->username); str_append_c(init_resp, '\0'); str_append(init_resp, input->password); base64_resp = t_str_new(128); base64_encode(str_data(init_resp), str_len(init_resp), base64_resp); i_zero(&info); info.mech = "PLAIN"; info.service = input->info.service; info.session_id = input->info.session_id; info.local_name = input->info.local_name; info.local_ip = input->info.local_ip; info.local_port = input->info.local_port; info.remote_ip = input->info.remote_ip; info.remote_port = input->info.remote_port; info.real_local_ip = input->info.real_local_ip; info.real_remote_ip = input->info.real_remote_ip; info.real_local_port = input->info.real_local_port; info.real_remote_port = input->info.real_remote_port; info.extra_fields = input->info.extra_fields; info.forward_fields = input->info.forward_fields; info.initial_resp_base64 = str_c(base64_resp); if (doveadm_settings->auth_debug || event_want_debug_log(event_auth)) info.flags |= AUTH_REQUEST_FLAG_DEBUG; input->request = auth_client_request_new(client, &info, auth_callback, input); event_unref(&event_auth); } static void cmd_auth_input(const char *auth_socket_path, struct authtest_input *input) { struct auth_client *client; if (auth_socket_path == NULL) { auth_socket_path = t_strconcat(doveadm_settings->base_dir, "/auth-client", NULL); } client = auth_client_init(auth_socket_path, getpid(), FALSE); auth_client_connect(client); auth_client_set_connect_notify(client, auth_connected, input); if (!auth_client_is_disconnected(client)) io_loop_run(current_ioloop); auth_client_set_connect_notify(client, NULL, NULL); auth_client_deinit(&client); } static void auth_user_info_parse_arg(struct auth_user_info *info, const char *arg) { if (str_begins(arg, "service=")) info->service = arg + 8; else if (str_begins(arg, "session=")) info->session_id = arg + 8; else if (str_begins(arg, "local_name=")) info->local_name = arg + 11; else if (str_begins(arg, "lip=")) { if (net_addr2ip(arg + 4, &info->local_ip) < 0) i_fatal("lip: Invalid ip"); } else if (str_begins(arg, "rip=")) { if (net_addr2ip(arg + 4, &info->remote_ip) < 0) i_fatal("rip: Invalid ip"); } else if (str_begins(arg, "lport=")) { if (net_str2port(arg + 6, &info->local_port) < 0) i_fatal("lport: Invalid port number"); } else if (str_begins(arg, "rport=")) { if (net_str2port(arg + 6, &info->remote_port) < 0) i_fatal("rport: Invalid port number"); } else if (str_begins(arg, "real_lip=")) { if (net_addr2ip(arg + 9, &info->real_local_ip) < 0) i_fatal("real_lip: Invalid ip"); } else if (str_begins(arg, "real_rip=")) { if (net_addr2ip(arg + 9, &info->real_remote_ip) < 0) i_fatal("real_rip: Invalid ip"); } else if (str_begins(arg, "real_lport=")) { if (net_str2port(arg + 11, &info->real_local_port) < 0) i_fatal("real_lport: Invalid port number"); } else if (str_begins(arg, "real_rport=")) { if (net_str2port(arg + 11, &info->real_remote_port) < 0) i_fatal("real_rport: Invalid port number"); } else if (str_begins(arg, "forward_")) { const char *key = arg+8; const char *value = strchr(arg+8, '='); if (value == NULL) value = ""; else key = t_strdup_until(key, value++); key = str_tabescape(key); value = str_tabescape(value); if (info->forward_fields == NULL) { info->forward_fields = t_strdup_printf("%s=%s", key, value); } else { info->forward_fields = t_strdup_printf("%s\t%s=%s", info->forward_fields, key, value); } } else { if (!array_is_created(&info->extra_fields)) t_array_init(&info->extra_fields, 4); array_push_back(&info->extra_fields, &arg); } } static void auth_user_info_parse(struct auth_user_info *info, const char *const *args) { for (unsigned int i = 0; args[i] != NULL; i++) auth_user_info_parse_arg(info, args[i]); } static void cmd_user_list(struct auth_master_connection *conn, const struct authtest_input *input, const char *const *users) { struct auth_master_user_list_ctx *ctx; const char *username, *user_mask = "*"; unsigned int i; if (users[0] != NULL && users[1] == NULL) user_mask = users[0]; ctx = auth_master_user_list_init(conn, user_mask, &input->info); while ((username = auth_master_user_list_next(ctx)) != NULL) { for (i = 0; users[i] != NULL; i++) { if (wildcard_match_icase(username, users[i])) break; } if (users[i] != NULL) printf("%s\n", username); } if (auth_master_user_list_deinit(&ctx) < 0) i_fatal("user listing failed"); } static void cmd_auth_cache_flush(struct doveadm_cmd_context *cctx) { const char *master_socket_path; struct auth_master_connection *conn; const char *const *users = NULL; unsigned int count; if (!doveadm_cmd_param_str(cctx, "socket-path", &master_socket_path)) { master_socket_path = t_strconcat(doveadm_settings->base_dir, "/auth-master", NULL); } (void)doveadm_cmd_param_array(cctx, "user", &users); conn = doveadm_get_auth_master_conn(master_socket_path); if (auth_master_cache_flush(conn, users, &count) < 0) { i_error("Cache flush failed"); doveadm_exit_code = EX_TEMPFAIL; } else { printf("%u cache entries flushed\n", count); } auth_master_deinit(&conn); } static void authtest_input_init(struct authtest_input *input) { i_zero(input); input->info.service = "doveadm"; input->info.debug = doveadm_settings->auth_debug; } static void cmd_auth_test(struct doveadm_cmd_context *cctx) { const char *auth_socket_path = NULL; const char *const *auth_info; struct authtest_input input; authtest_input_init(&input); (void)doveadm_cmd_param_str(cctx, "socket-path", &auth_socket_path); (void)doveadm_cmd_param_str(cctx, "master-user", &input.master_user); if (doveadm_cmd_param_array(cctx, "auth-info", &auth_info)) auth_user_info_parse(&input.info, auth_info); if (!doveadm_cmd_param_str(cctx, "user", &input.username)) auth_cmd_help(cctx); if (!doveadm_cmd_param_str(cctx, "password", &input.password)) input.password = t_askpass("Password: "); cmd_auth_input(auth_socket_path, &input); if (!input.success) doveadm_exit_code = EX_NOPERM; } static void master_auth_callback(const char *const *auth_args, const char *errormsg, void *context) { struct authtest_input *input = context; unsigned int i; io_loop_stop(current_ioloop); if (errormsg != NULL) { i_error("userdb lookup failed: %s", errormsg); return; } printf("userdb extra fields:\n"); for (i = 0; auth_args[i] != NULL; i++) printf(" %s\n", auth_args[i]); input->success = TRUE; } static void cmd_auth_master_input(const char *auth_master_socket_path, struct authtest_input *input) { struct master_login_auth *master_auth; struct master_auth_request master_auth_req; buffer_t buf; i_zero(&master_auth_req); master_auth_req.tag = 1; master_auth_req.auth_pid = input->auth_pid; master_auth_req.auth_id = input->auth_id; master_auth_req.client_pid = getpid(); master_auth_req.local_ip = input->info.local_ip; master_auth_req.remote_ip = input->info.remote_ip; buffer_create_from_data(&buf, master_auth_req.cookie, sizeof(master_auth_req.cookie)); if (strlen(input->auth_cookie) == MASTER_AUTH_COOKIE_SIZE*2) (void)hex_to_binary(input->auth_cookie, &buf); input->success = FALSE; master_auth = master_login_auth_init(auth_master_socket_path, FALSE); io_loop_set_running(current_ioloop); master_login_auth_request(master_auth, &master_auth_req, master_auth_callback, input); if (io_loop_is_running(current_ioloop)) io_loop_run(current_ioloop); master_login_auth_deinit(&master_auth); } static void cmd_auth_login(struct doveadm_cmd_context *cctx) { const char *auth_login_socket_path, *auth_master_socket_path; const char *const *auth_info; struct auth_client *auth_client; struct authtest_input input; authtest_input_init(&input); if (!doveadm_cmd_param_str(cctx, "auth-login-socket-path", &auth_login_socket_path)) { auth_login_socket_path = t_strconcat(doveadm_settings->base_dir, "/auth-login", NULL); } if (!doveadm_cmd_param_str(cctx, "auth-master-socket-path", &auth_master_socket_path)) { auth_master_socket_path = t_strconcat(doveadm_settings->base_dir, "/auth-master", NULL); } (void)doveadm_cmd_param_str(cctx, "master-user", &input.master_user); if (doveadm_cmd_param_array(cctx, "auth-info", &auth_info)) auth_user_info_parse(&input.info, auth_info); if (!doveadm_cmd_param_str(cctx, "user", &input.username)) auth_cmd_help(cctx); if (!doveadm_cmd_param_str(cctx, "password", &input.password)) input.password = t_askpass("Password: "); input.pool = pool_alloconly_create("auth login", 256); /* authenticate */ auth_client = auth_client_init(auth_login_socket_path, getpid(), FALSE); auth_client_connect(auth_client); auth_client_set_connect_notify(auth_client, auth_connected, &input); if (!auth_client_is_disconnected(auth_client)) io_loop_run(current_ioloop); auth_client_set_connect_notify(auth_client, NULL, NULL); /* finish login with userdb lookup */ if (input.success) cmd_auth_master_input(auth_master_socket_path, &input); if (!input.success) doveadm_exit_code = EX_NOPERM; auth_client_deinit(&auth_client); pool_unref(&input.pool); } static void cmd_auth_lookup(struct doveadm_cmd_context *cctx) { const char *auth_socket_path; struct auth_master_connection *conn; struct authtest_input input; const char *show_field = NULL; const char *const *auth_info, *const *users; bool first = TRUE; int ret; authtest_input_init(&input); if (!doveadm_cmd_param_str(cctx, "socket-path", &auth_socket_path)) auth_socket_path = doveadm_settings->auth_socket_path; (void)doveadm_cmd_param_str(cctx, "field", &show_field); if (doveadm_cmd_param_array(cctx, "auth-info", &auth_info)) auth_user_info_parse(&input.info, auth_info); if (!doveadm_cmd_param_array(cctx, "user", &users)) auth_cmd_help(cctx); conn = doveadm_get_auth_master_conn(auth_socket_path); for (unsigned int i = 0; users[i] != NULL; i++) { input.username = users[i]; if (first) first = FALSE; else putchar('\n'); ret = cmd_user_input(conn, &input, show_field, FALSE); switch (ret) { case -1: doveadm_exit_code = EX_TEMPFAIL; break; case 0: doveadm_exit_code = EX_NOUSER; break; } } auth_master_deinit(&conn); } static void cmd_user_mail_input_field(const char *key, const char *value, const char *show_field) { if (show_field == NULL) { doveadm_print(key); doveadm_print(value); } else if (strcmp(show_field, key) == 0) { printf("%s\n", value); } } static void cmd_user_mail_print_fields(const struct authtest_input *input, struct mail_user *user, const char *const *userdb_fields, const char *show_field) { const struct mail_storage_settings *mail_set; const char *key, *value; unsigned int i; if (strcmp(input->username, user->username) != 0) cmd_user_mail_input_field("user", user->username, show_field); cmd_user_mail_input_field("uid", user->set->mail_uid, show_field); cmd_user_mail_input_field("gid", user->set->mail_gid, show_field); cmd_user_mail_input_field("home", user->set->mail_home, show_field); mail_set = mail_user_set_get_storage_set(user); cmd_user_mail_input_field("mail", mail_set->mail_location, show_field); if (userdb_fields != NULL) { for (i = 0; userdb_fields[i] != NULL; i++) { value = strchr(userdb_fields[i], '='); if (value != NULL) key = t_strdup_until(userdb_fields[i], value++); else { key = userdb_fields[i]; value = ""; } if (strcmp(key, "uid") != 0 && strcmp(key, "gid") != 0 && strcmp(key, "home") != 0 && strcmp(key, "mail") != 0) cmd_user_mail_input_field(key, value, show_field); } } } static int cmd_user_mail_input(struct mail_storage_service_ctx *storage_service, const struct authtest_input *input, const char *show_field, const char *expand_field) { struct mail_storage_service_input service_input; struct mail_storage_service_user *service_user; struct mail_user *user; const char *error, *const *userdb_fields; pool_t pool; int ret; i_zero(&service_input); service_input.module = "mail"; service_input.service = input->info.service; service_input.username = input->username; service_input.local_ip = input->info.local_ip; service_input.local_port = input->info.local_port; service_input.remote_ip = input->info.remote_ip; service_input.remote_port = input->info.remote_port; service_input.debug = input->info.debug; pool = pool_alloconly_create("userdb fields", 1024); mail_storage_service_save_userdb_fields(storage_service, pool, &userdb_fields); if ((ret = mail_storage_service_lookup_next(storage_service, &service_input, &service_user, &user, &error)) <= 0) { pool_unref(&pool); if (ret < 0) return -1; fprintf(show_field == NULL && expand_field == NULL ? stdout : stderr, "\nuserdb lookup: user %s doesn't exist\n", input->username); return 0; } if (expand_field == NULL) cmd_user_mail_print_fields(input, user, userdb_fields, show_field); else { string_t *str = t_str_new(128); if (var_expand_with_funcs(str, expand_field, mail_user_var_expand_table(user), mail_user_var_expand_func_table, user, &error) <= 0) { i_error("Failed to expand %s: %s", expand_field, error); } else { printf("%s\n", str_c(str)); } } mail_user_deinit(&user); mail_storage_service_user_unref(&service_user); pool_unref(&pool); return 1; } static void cmd_user(struct doveadm_cmd_context *cctx) { const char *auth_socket_path; struct auth_master_connection *conn; struct authtest_input input; const char *show_field = NULL, *expand_field = NULL; const char *const *user_masks, *const *auth_info; struct mail_storage_service_ctx *storage_service = NULL; unsigned int i; bool have_wildcards, userdb_only = FALSE, first = TRUE; int ret; authtest_input_init(&input); if (!doveadm_cmd_param_str(cctx, "socket-path", &auth_socket_path)) auth_socket_path = doveadm_settings->auth_socket_path; (void)doveadm_cmd_param_str(cctx, "field", &show_field); (void)doveadm_cmd_param_str(cctx, "expand-field", &expand_field); (void)doveadm_cmd_param_bool(cctx, "userdb-only", &userdb_only); if (doveadm_cmd_param_array(cctx, "auth-info", &auth_info)) auth_user_info_parse(&input.info, auth_info); if (!doveadm_cmd_param_array(cctx, "user-mask", &user_masks)) auth_cmd_help(cctx); if (expand_field != NULL && userdb_only) { i_error("-e can't be used with -u"); doveadm_exit_code = EX_USAGE; return; } if (expand_field != NULL && show_field != NULL) { i_error("-e can't be used with -f"); doveadm_exit_code = EX_USAGE; return; } conn = doveadm_get_auth_master_conn(auth_socket_path); have_wildcards = FALSE; for (i = 0; user_masks[i] != NULL; i++) { if (strchr(user_masks[i], '*') != NULL || strchr(user_masks[i], '?') != NULL) { have_wildcards = TRUE; break; } } if (have_wildcards) { cmd_user_list(conn, &input, user_masks); auth_master_deinit(&conn); return; } if (!userdb_only) { storage_service = mail_storage_service_init(master_service, NULL, MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP | MAIL_STORAGE_SERVICE_FLAG_NO_CHDIR | MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT | MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS | MAIL_STORAGE_SERVICE_FLAG_NO_NAMESPACES | MAIL_STORAGE_SERVICE_FLAG_NO_RESTRICT_ACCESS); mail_storage_service_set_auth_conn(storage_service, conn); conn = NULL; if (show_field == NULL && expand_field == NULL) { doveadm_print_init(DOVEADM_PRINT_TYPE_TAB); doveadm_print_header_simple("field"); doveadm_print_header_simple("value"); } } for (i = 0; user_masks[i] != NULL; i++) { input.username = user_masks[i]; if (first) first = FALSE; else putchar('\n'); ret = !userdb_only ? cmd_user_mail_input(storage_service, &input, show_field, expand_field) : cmd_user_input(conn, &input, show_field, TRUE); switch (ret) { case -1: doveadm_exit_code = EX_TEMPFAIL; break; case 0: doveadm_exit_code = EX_NOUSER; break; } } if (storage_service != NULL) mail_storage_service_deinit(&storage_service); if (conn != NULL) auth_master_deinit(&conn); } struct doveadm_cmd_ver2 doveadm_cmd_auth[] = { { .cmd = cmd_auth_test, .name = "auth test", .usage = "[-a ] [-x ] [-M ] []", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('x', "auth-info", CMD_PARAM_ARRAY, 0) DOVEADM_CMD_PARAM('M', "master-user", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "user", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "password", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .cmd = cmd_auth_login, .name = "auth login", .usage = "[-a ] [-m ] [-x ] [-M ] []", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "auth-login-socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('m', "auth-master-socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('x', "auth-info", CMD_PARAM_ARRAY, 0) DOVEADM_CMD_PARAM('M', "master-user", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "user", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "password", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .cmd = cmd_auth_lookup, .name = "auth lookup", .usage = "[-a ] [-x ] [-f field] [ [...]]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('x', "auth-info", CMD_PARAM_ARRAY, 0) DOVEADM_CMD_PARAM('f', "field", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "user", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .cmd = cmd_auth_cache_flush, .name = "auth cache flush", .usage = "[-a ] [ [...]]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "user", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .cmd = cmd_user, .name = "user", .usage = "[-a ] [-x ] [-f field] [-e ] [-u] [ [...]]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('x', "auth-info", CMD_PARAM_ARRAY, 0) DOVEADM_CMD_PARAM('f', "field", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('e', "expand-field", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('u', "userdb-only", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "user-mask", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END } }; static void auth_cmd_help(struct doveadm_cmd_context *cctx) { unsigned int i; for (i = 0; i < N_ELEMENTS(doveadm_cmd_auth); i++) { if (doveadm_cmd_auth[i].cmd == cctx->cmd->cmd) help_ver2(&doveadm_cmd_auth[i]); } i_unreached(); } void doveadm_register_auth_commands(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(doveadm_cmd_auth); i++) doveadm_cmd_register_ver2(&doveadm_cmd_auth[i]); } dovecot-2.3.21.1/src/doveadm/doveadm-master.c0000644000000000000000000001714114656633576015603 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "strescape.h" #include "istream.h" #include "write-full.h" #include "master-service.h" #include "sleep.h" #include "doveadm.h" #include "doveadm-print.h" #include #include #include #define MASTER_PID_FILE_NAME "master.pid" static bool pid_file_read(const char *path, pid_t *pid_r) { char buf[32]; int fd; ssize_t ret; bool found; fd = open(path, O_RDONLY); if (fd == -1) { if (errno == ENOENT) return FALSE; i_fatal("open(%s) failed: %m", path); } ret = read(fd, buf, sizeof(buf)-1); if (ret <= 0) { if (ret == 0) i_error("Empty PID file in %s", path); else i_fatal("read(%s) failed: %m", path); found = FALSE; } else { if (buf[ret-1] == '\n') ret--; buf[ret] = '\0'; if (str_to_pid(buf, pid_r) < 0) found = FALSE; else { found = !(*pid_r == getpid() || (kill(*pid_r, 0) < 0 && errno == ESRCH)); } } i_close_fd(&fd); return found; } void doveadm_master_send_signal(int signo) { const char *pidfile_path; unsigned int i; pid_t pid; pidfile_path = t_strconcat(doveadm_settings->base_dir, "/"MASTER_PID_FILE_NAME, NULL); if (!pid_file_read(pidfile_path, &pid)) i_fatal("Dovecot is not running (read from %s)", pidfile_path); if (kill(pid, signo) < 0) i_fatal("kill(%s, %d) failed: %m", dec2str(pid), signo); if (signo == SIGTERM) { /* wait for a while for the process to die */ i_sleep_msecs(1); for (i = 0; i < 30; i++) { if (kill(pid, 0) < 0) { if (errno != ESRCH) i_error("kill() failed: %m"); break; } i_sleep_msecs(100); } } } static void cmd_stop(struct doveadm_cmd_context *cctx ATTR_UNUSED) { doveadm_master_send_signal(SIGTERM); } static void cmd_reload(struct doveadm_cmd_context *cctx ATTR_UNUSED) { doveadm_master_send_signal(SIGHUP); } static struct istream *master_service_send_cmd(const char *cmd) { struct istream *input; const char *path, *line; path = t_strconcat(doveadm_settings->base_dir, "/master", NULL); int fd = net_connect_unix(path); if (fd == -1) i_fatal("net_connect_unix(%s) failed: %m", path); net_set_nonblock(fd, FALSE); const char *str = t_strdup_printf("VERSION\tmaster-client\t1\t0\n%s\n", cmd); if (write_full(fd, str, strlen(str)) < 0) i_fatal("write(%s) failed: %m", path); input = i_stream_create_fd_autoclose(&fd, IO_BLOCK_SIZE); alarm(5); if ((line = i_stream_read_next_line(input)) == NULL) i_fatal("read(%s) failed: %m", path); if (!version_string_verify(line, "master-server", 1)) { i_fatal_status(EX_PROTOCOL, "%s is not a compatible master socket", path); } alarm(0); return input; } static struct istream * master_service_send_cmd_with_args(const char *cmd, const char *const *args) { string_t *str = t_str_new(128); str_append(str, cmd); if (args != NULL) { for (unsigned int i = 0; args[i] != NULL; i++) { str_append_c(str, '\t'); str_append_tabescaped(str, args[i]); } } return master_service_send_cmd(str_c(str)); } static void cmd_service_stop(struct doveadm_cmd_context *cctx) { const char *line, *const *services; if (!doveadm_cmd_param_array(cctx, "service", &services)) i_fatal("service parameter missing"); struct istream *input = master_service_send_cmd_with_args("STOP", services); alarm(5); if ((line = i_stream_read_next_line(input)) == NULL) { i_error("read(%s) failed: %s", i_stream_get_name(input), i_stream_get_error(input)); doveadm_exit_code = EX_TEMPFAIL; } else if (line[0] == '-') { doveadm_exit_code = DOVEADM_EX_NOTFOUND; i_error("%s", line+1); } else if (line[0] != '+') { i_error("Unexpected input from %s: %s", i_stream_get_name(input), line); doveadm_exit_code = EX_TEMPFAIL; } alarm(0); i_stream_destroy(&input); } static void cmd_service_status(struct doveadm_cmd_context *cctx) { const char *line, *const *services; unsigned int fields_count; if (!doveadm_cmd_param_array(cctx, "service", &services)) services = NULL; struct istream *input = master_service_send_cmd("SERVICE-STATUS"); doveadm_print_init(DOVEADM_PRINT_TYPE_PAGER); doveadm_print_header_simple("name"); doveadm_print_header_simple("process_count"); doveadm_print_header_simple("process_avail"); doveadm_print_header_simple("process_limit"); doveadm_print_header_simple("client_limit"); doveadm_print_header_simple("throttle_secs"); doveadm_print_header_simple("exit_failure_last"); doveadm_print_header_simple("exit_failures_in_sec"); doveadm_print_header_simple("last_drop_warning"); doveadm_print_header_simple("listen_pending"); doveadm_print_header_simple("listening"); doveadm_print_header_simple("doveadm_stop"); doveadm_print_header_simple("process_total"); fields_count = doveadm_print_get_headers_count(); alarm(5); while ((line = i_stream_read_next_line(input)) != NULL) { if (line[0] == '\0') break; T_BEGIN { const char *const *args = t_strsplit_tabescaped(line); if (args[0] != NULL && (services == NULL || str_array_find(services, args[0]))) { unsigned int i; for (i = 0; i < fields_count && args[i] != NULL; i++) doveadm_print(args[i]); for (; i < fields_count; i++) doveadm_print(""); } } T_END; } if (line == NULL) { i_error("read(%s) failed: %s", i_stream_get_name(input), i_stream_get_error(input)); doveadm_exit_code = EX_TEMPFAIL; } alarm(0); i_stream_destroy(&input); } static void cmd_process_status(struct doveadm_cmd_context *cctx) { const char *line, *const *services; if (!doveadm_cmd_param_array(cctx, "service", &services)) services = NULL; struct istream *input = master_service_send_cmd_with_args("PROCESS-STATUS", services); doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); doveadm_print_header_simple("name"); doveadm_print_header_simple("pid"); doveadm_print_header_simple("available_count"); doveadm_print_header_simple("total_count"); doveadm_print_header_simple("idle_start"); doveadm_print_header_simple("last_status_update"); doveadm_print_header_simple("last_kill_sent"); alarm(5); while ((line = i_stream_read_next_line(input)) != NULL) { if (line[0] == '\0') break; T_BEGIN { const char *const *args = t_strsplit_tabescaped(line); if (str_array_length(args) >= 7) { for (unsigned int i = 0; i < 7; i++) doveadm_print(args[i]); } } T_END; } if (line == NULL) { i_error("read(%s) failed: %s", i_stream_get_name(input), i_stream_get_error(input)); doveadm_exit_code = EX_TEMPFAIL; } alarm(0); i_stream_destroy(&input); } struct doveadm_cmd_ver2 doveadm_cmd_stop_ver2 = { .cmd = cmd_stop, .name = "stop", .usage = "", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_reload_ver2 = { .cmd = cmd_reload, .name = "reload", .usage = "", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_service_stop_ver2 = { .cmd = cmd_service_stop, .name = "service stop", .usage = " [ [...]]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', "service", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_service_status_ver2 = { .cmd = cmd_service_status, .name = "service status", .usage = "[ [...]]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', "service", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_process_status_ver2 = { .cmd = cmd_process_status, .name = "process status", .usage = "[ [...]]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', "service", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-mail-mailbox-metadata.c0000644000000000000000000003121414656633576020436 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "istream.h" #include "mail-namespace.h" #include "mail-storage.h" #include "doveadm-print.h" #include "doveadm-mail.h" #include "doveadm-mailbox-list-iter.h" #include "imap-metadata.h" enum doveadm_metadata_op { DOVEADM_METADATA_OP_SET = 0, DOVEADM_METADATA_OP_GET, DOVEADM_METADATA_OP_LIST, }; const char *doveadm_metadata_op_names[] = { "set attribute", "get attribute", "list attribute", }; struct metadata_cmd_context { struct doveadm_mail_cmd_context ctx; const char *mailbox; enum mail_attribute_type key_type; const char *key; struct mail_attribute_value value; bool empty_mailbox_name; bool allow_empty_mailbox_name; bool prepend_prefix; }; static int cmd_mailbox_metadata_get_mailbox(struct metadata_cmd_context *mctx, struct mail_user *user, enum doveadm_metadata_op op, struct mail_namespace **ns_r, struct mailbox **box_r) { mctx->empty_mailbox_name = mctx->mailbox[0] == '\0'; if (mctx->empty_mailbox_name) { if (!mctx->allow_empty_mailbox_name) { const char *op_str = doveadm_metadata_op_names[op]; i_error("Failed to %s: %s", op_str, "mailbox name cannot be empty"); mctx->ctx.exit_code = EX_USAGE; return -1; } /* Server attribute. It shouldn't depend on INBOX's ACLs, so ignore them. */ *ns_r = mail_namespace_find_inbox(user->namespaces); *box_r = mailbox_alloc((*ns_r)->list, "INBOX", MAILBOX_FLAG_IGNORE_ACLS | MAILBOX_FLAG_ATTRIBUTE_SESSION); mctx->key = t_strconcat(MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER, mctx->key, NULL); } else { /* mailbox attributes */ *ns_r = mail_namespace_find(user->namespaces, mctx->mailbox); *box_r = mailbox_alloc((*ns_r)->list, mctx->mailbox, MAILBOX_FLAG_ATTRIBUTE_SESSION); } if (op == DOVEADM_METADATA_OP_SET && mailbox_open(*box_r) < 0) { i_error("Failed to open mailbox: %s", mailbox_get_last_internal_error(*box_r, NULL)); doveadm_mail_failed_mailbox(&mctx->ctx, *box_r); mailbox_free(box_r); return -1; } return 0; } static int cmd_mailbox_metadata_set_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct metadata_cmd_context *ctx = (struct metadata_cmd_context *)_ctx; struct mail_namespace *ns; struct mailbox *box; struct mailbox_transaction_context *trans; int ret; ret = cmd_mailbox_metadata_get_mailbox(ctx, user, DOVEADM_METADATA_OP_SET, &ns, &box); if (ret != 0) return ret; trans = mailbox_transaction_begin(box, (ctx->empty_mailbox_name ? MAILBOX_TRANSACTION_FLAG_EXTERNAL : 0) | ctx->ctx.transaction_flags, __func__); ret = ctx->value.value == NULL ? mailbox_attribute_unset(trans, ctx->key_type, ctx->key) : mailbox_attribute_set(trans, ctx->key_type, ctx->key, &ctx->value); if (ret < 0) { i_error("Failed to set attribute: %s", mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(_ctx, box); mailbox_transaction_rollback(&trans); } else if (mailbox_transaction_commit(&trans) < 0) { i_error("Failed to commit transaction: %s", mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(_ctx, box); ret = -1; } mailbox_free(&box); return ret; } static void cmd_mailbox_metadata_parse_key(const char *arg, enum mail_attribute_type *type_r, const char **key_r) { arg = t_str_lcase(arg); if (str_begins(arg, "/private/")) { *type_r = MAIL_ATTRIBUTE_TYPE_PRIVATE; *key_r = arg + 9; } else if (str_begins(arg, "/shared/")) { *type_r = MAIL_ATTRIBUTE_TYPE_SHARED; *key_r = arg + 8; } else if (strcmp(arg, "/private") == 0) { *type_r = MAIL_ATTRIBUTE_TYPE_PRIVATE; *key_r = ""; } else if (strcmp(arg, "/shared") == 0) { *type_r = MAIL_ATTRIBUTE_TYPE_SHARED; *key_r = ""; } else { i_fatal_status(EX_USAGE, "Invalid metadata key '%s': " "Must begin with /private or /shared", arg); } } static void cmd_mailbox_metadata_set_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct metadata_cmd_context *ctx = (struct metadata_cmd_context *)_ctx; const char *key; if (str_array_length(args) != 3) doveadm_mail_help_name("mailbox metadata set"); cmd_mailbox_metadata_parse_key(args[1], &ctx->key_type, &key); ctx->mailbox = p_strdup(_ctx->pool, args[0]); ctx->key = p_strdup(_ctx->pool, key); ctx->value.value = p_strdup(_ctx->pool, args[2]); } static bool cmd_mailbox_metadata_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct metadata_cmd_context *ctx = (struct metadata_cmd_context *)_ctx; switch (c) { case 's': ctx->allow_empty_mailbox_name = TRUE; break; case 'p': ctx->prepend_prefix = TRUE; break; default: return FALSE; } return TRUE; } static struct doveadm_mail_cmd_context *cmd_mailbox_metadata_set_alloc(void) { struct metadata_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct metadata_cmd_context); ctx->ctx.v.init = cmd_mailbox_metadata_set_init; ctx->ctx.v.parse_arg = cmd_mailbox_metadata_parse_arg; ctx->ctx.v.run = cmd_mailbox_metadata_set_run; return &ctx->ctx; } static void cmd_mailbox_metadata_unset_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct metadata_cmd_context *ctx = (struct metadata_cmd_context *)_ctx; const char *key; if (str_array_length(args) != 2) doveadm_mail_help_name("mailbox metadata unset"); cmd_mailbox_metadata_parse_key(args[1], &ctx->key_type, &key); ctx->mailbox = p_strdup(_ctx->pool, args[0]); ctx->key = p_strdup(_ctx->pool, key); } static struct doveadm_mail_cmd_context *cmd_mailbox_metadata_unset_alloc(void) { struct metadata_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct metadata_cmd_context); ctx->ctx.v.init = cmd_mailbox_metadata_unset_init; ctx->ctx.v.parse_arg = cmd_mailbox_metadata_parse_arg; ctx->ctx.v.run = cmd_mailbox_metadata_set_run; return &ctx->ctx; } static int cmd_mailbox_metadata_get_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct metadata_cmd_context *ctx = (struct metadata_cmd_context *)_ctx; struct mail_namespace *ns; struct mailbox *box; struct mail_attribute_value value; int ret; ret = cmd_mailbox_metadata_get_mailbox(ctx, user, DOVEADM_METADATA_OP_GET, &ns, &box); if (ret != 0) return ret; ret = mailbox_attribute_get_stream(box, ctx->key_type, ctx->key, &value); if (ret < 0) { i_error("Failed to get attribute: %s", mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(_ctx, box); } else if (ret == 0) { /* not found, print as empty */ doveadm_print(""); } else if (value.value_stream != NULL) { if (doveadm_print_istream(value.value_stream) < 0) { i_error("read(%s) failed: %s", i_stream_get_name(value.value_stream), i_stream_get_error(value.value_stream)); ret = -1; } } else { doveadm_print(value.value); } mailbox_free(&box); return ret; } static void cmd_mailbox_metadata_get_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct metadata_cmd_context *ctx = (struct metadata_cmd_context *)_ctx; const char *key; if (str_array_length(args) != 2) doveadm_mail_help_name("mailbox metadata get"); cmd_mailbox_metadata_parse_key(args[1], &ctx->key_type, &key); ctx->mailbox = p_strdup(_ctx->pool, args[0]); ctx->key = p_strdup(_ctx->pool, key); doveadm_print_header("value", "value", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); } static struct doveadm_mail_cmd_context *cmd_mailbox_metadata_get_alloc(void) { struct metadata_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct metadata_cmd_context); ctx->ctx.v.init = cmd_mailbox_metadata_get_init; ctx->ctx.v.parse_arg = cmd_mailbox_metadata_parse_arg; ctx->ctx.v.run = cmd_mailbox_metadata_get_run; doveadm_print_init(DOVEADM_PRINT_TYPE_FLOW); return &ctx->ctx; } static int cmd_mailbox_metadata_list_run_iter(struct metadata_cmd_context *ctx, struct mailbox *box, enum mail_attribute_type type) { struct mailbox_attribute_iter *iter; const char *key; string_t *outp = t_str_new(64); iter = mailbox_attribute_iter_init(box, type, ctx->key); while ((key = mailbox_attribute_iter_next(iter)) != NULL) { if (ctx->prepend_prefix) { if (type == MAIL_ATTRIBUTE_TYPE_PRIVATE) str_append(outp, IMAP_METADATA_PRIVATE_PREFIX"/"); else if (type == MAIL_ATTRIBUTE_TYPE_SHARED) str_append(outp, IMAP_METADATA_SHARED_PREFIX"/"); else i_unreached(); str_append(outp, key); doveadm_print(str_c(outp)); str_truncate(outp, 0); } else { doveadm_print(key); } } if (mailbox_attribute_iter_deinit(&iter) < 0) { i_error("Mailbox %s: Failed to iterate mailbox attributes: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, NULL)); return -1; } return 0; } static int cmd_mailbox_metadata_list_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct metadata_cmd_context *ctx = (struct metadata_cmd_context *)_ctx; struct mail_namespace *ns; struct mailbox *box; int ret = 0; ret = cmd_mailbox_metadata_get_mailbox(ctx, user, DOVEADM_METADATA_OP_LIST, &ns, &box); if (ret != 0) return ret; if (ctx->key[0] == '\0' || ctx->key_type == MAIL_ATTRIBUTE_TYPE_PRIVATE) { if (cmd_mailbox_metadata_list_run_iter(ctx, box, MAIL_ATTRIBUTE_TYPE_PRIVATE) < 0) { doveadm_mail_failed_mailbox(_ctx, box); ret = -1; } } if (ctx->key[0] == '\0' || ctx->key_type == MAIL_ATTRIBUTE_TYPE_SHARED) { if (cmd_mailbox_metadata_list_run_iter(ctx, box, MAIL_ATTRIBUTE_TYPE_SHARED) < 0) { doveadm_mail_failed_mailbox(_ctx, box); ret = -1; } } mailbox_free(&box); return ret; } static void cmd_mailbox_metadata_list_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct metadata_cmd_context *ctx = (struct metadata_cmd_context *)_ctx; const char *key = NULL; if (args[0] == NULL) doveadm_mail_help_name("mailbox metadata list"); if (args[1] != NULL) cmd_mailbox_metadata_parse_key(args[1], &ctx->key_type, &key); ctx->mailbox = p_strdup(_ctx->pool, args[0]); ctx->key = key == NULL ? "" : p_strdup(_ctx->pool, key); doveadm_print_header("key", "key", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); } static struct doveadm_mail_cmd_context *cmd_mailbox_metadata_list_alloc(void) { struct metadata_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct metadata_cmd_context); ctx->ctx.v.init = cmd_mailbox_metadata_list_init; ctx->ctx.v.parse_arg = cmd_mailbox_metadata_parse_arg; ctx->ctx.v.run = cmd_mailbox_metadata_list_run; doveadm_print_init(DOVEADM_PRINT_TYPE_FLOW); return &ctx->ctx; } struct doveadm_cmd_ver2 doveadm_cmd_mailbox_metadata_set_ver2 = { .name = "mailbox metadata set", .mail_cmd = cmd_mailbox_metadata_set_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"[-s] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('s', "allow-empty-mailbox-name", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "key", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "value", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_mailbox_metadata_unset_ver2 = { .name = "mailbox metadata unset", .mail_cmd = cmd_mailbox_metadata_unset_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"[-s] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('s', "allow-empty-mailbox-name", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "key", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_mailbox_metadata_get_ver2 = { .name = "mailbox metadata get", .mail_cmd = cmd_mailbox_metadata_get_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"[-s] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('s', "allow-empty-mailbox-name", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "key", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_mailbox_metadata_list_ver2 = { .name = "mailbox metadata list", .mail_cmd = cmd_mailbox_metadata_list_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"[-s] [-p] []", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('s', "allow-empty-mailbox-name", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('p', "prepend-prefix", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "key-prefix", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-print-private.h0000644000000000000000000000154414656633576017121 00000000000000#ifndef DOVEADM_PRINT_PRIVATE_H #define DOVEADM_PRINT_PRIVATE_H #include "doveadm-print.h" struct doveadm_print_header { const char *key; const char *title; enum doveadm_print_header_flags flags; }; struct doveadm_print_vfuncs { const char *name; void (*init)(void); void (*deinit)(void); void (*header)(const struct doveadm_print_header *hdr); void (*print)(const char *value); void (*print_stream)(const unsigned char *value, size_t size); void (*flush)(void); }; extern struct doveadm_print_vfuncs doveadm_print_flow_vfuncs; extern struct doveadm_print_vfuncs doveadm_print_tab_vfuncs; extern struct doveadm_print_vfuncs doveadm_print_table_vfuncs; extern struct doveadm_print_vfuncs doveadm_print_pager_vfuncs; extern struct doveadm_print_vfuncs doveadm_print_json_vfuncs; extern struct doveadm_print_vfuncs doveadm_print_formatted_vfuncs; #endif dovecot-2.3.21.1/src/doveadm/doveadm-mail-mailbox-cache.c0000644000000000000000000003055114656633576017724 00000000000000/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "time-util.h" #include "mail-index-private.h" #include "mail-cache-private.h" #include "mail-namespace.h" #include "mail-storage-private.h" #include "doveadm-print.h" #include "doveadm-mail-iter.h" #include "doveadm-mailbox-list-iter.h" #include "doveadm-mail.h" struct mailbox_cache_cmd_context { struct doveadm_mail_cmd_context ctx; const char *const *boxes; const char *const *fields; uint64_t last_used; enum mail_cache_decision_type decision; bool all_fields; bool set_decision; bool set_last_used; bool remove; }; static int cmd_mailbox_cache_open_box(struct doveadm_mail_cmd_context *ctx, struct mail_user *user, const char *boxname, struct mailbox **box_r) { struct mailbox *box = doveadm_mailbox_find(user, boxname); if (mailbox_open(box) < 0 || mailbox_sync(box, 0) < 0) { i_error("Cannot open mailbox %s: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(ctx, box); mailbox_free(&box); return -1; } *box_r = box; return 0; } static void cmd_mailbox_cache_decision_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct mailbox_cache_cmd_context *ctx = container_of(_ctx, struct mailbox_cache_cmd_context, ctx); const char *fields; doveadm_print_header("mailbox", "mailbox", DOVEADM_PRINT_HEADER_FLAG_STICKY); doveadm_print_header_simple("field"); doveadm_print_header_simple("decision"); doveadm_print_header_simple("last-used"); if (!ctx->all_fields && !doveadm_cmd_param_str(_ctx->cctx, "fieldstr", &fields)) { i_fatal("Missing fields parameter"); } else if (!ctx->all_fields) { ctx->fields = t_strsplit_spaces(fields, ", "); } ctx->boxes = args; } static bool cmd_mailbox_cache_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct mailbox_cache_cmd_context *ctx = container_of(_ctx, struct mailbox_cache_cmd_context, ctx); switch(c) { case 'a': ctx->all_fields = TRUE; return TRUE; /* this is handled in doveadm-mail as 'fieldstr' field */ case 'f': return TRUE; case 'l': if (str_to_uint64(optarg, &ctx->last_used) < 0) { i_error("Invalid last-used '%s': not a number", optarg); return FALSE; } ctx->set_last_used = TRUE; return TRUE; case 'd': if (ctx->set_decision) { i_error("Only one decision flag allowed"); return FALSE; } if (strcmp(optarg, "no") == 0) { ctx->decision = MAIL_CACHE_DECISION_NO; } else if (strcmp(optarg, "temp") == 0) { ctx->decision = MAIL_CACHE_DECISION_TEMP; } else if (strcmp(optarg, "yes") == 0) { ctx->decision = MAIL_CACHE_DECISION_YES; } else { i_error("Invalid decision '%s': " \ "must be one of yes, temp, no", optarg); return FALSE; } ctx->set_decision = TRUE; return TRUE; } return FALSE; } static const char * cmd_mailbox_cache_decision_to_str(enum mail_cache_decision_type decision) { string_t *ret = t_str_new(10); switch((decision & ENUM_NEGATE(MAIL_CACHE_DECISION_FORCED))) { case MAIL_CACHE_DECISION_NO: str_append(ret, "no"); break; case MAIL_CACHE_DECISION_TEMP: str_append(ret, "temp"); break; case MAIL_CACHE_DECISION_YES: str_append(ret, "yes"); break; } return str_c(ret); } static void cmd_mailbox_cache_decision_process_field(struct mailbox_cache_cmd_context *ctx, struct mail_cache_field_private *field) { if (ctx->set_decision) { field->field.decision = ctx->decision; field->decision_dirty = TRUE; } if (ctx->set_last_used) { field->field.last_used = (time_t)ctx->last_used; field->decision_dirty = TRUE; } doveadm_print(cmd_mailbox_cache_decision_to_str(field->field.decision)); doveadm_print(t_strflocaltime("%F %T %Z", field->field.last_used)); } static void cmd_mailbox_cache_decision_run_per_field(struct mailbox_cache_cmd_context *ctx, struct mail_cache *cache) { const char *const *field_name; for(field_name = ctx->fields; *field_name != NULL; field_name++) { doveadm_print(*field_name); /* see if the field exists */ unsigned int idx = mail_cache_register_lookup(cache, *field_name); if (idx == UINT_MAX) { doveadm_print(""); doveadm_print(""); continue; } cmd_mailbox_cache_decision_process_field(ctx, &cache->fields[idx]); } } static void cmd_mailbox_cache_decision_run_all_fields(struct mailbox_cache_cmd_context *ctx, struct mail_cache *cache) { /* get all fields */ for(unsigned int i = 0; i < cache->fields_count; i++) { doveadm_print(cache->fields[i].field.name); cmd_mailbox_cache_decision_process_field(ctx, &cache->fields[i]); } } static int cmd_mailbox_cache_decision_run_box(struct mailbox_cache_cmd_context *ctx, struct mailbox *box) { struct mailbox_transaction_context *t = mailbox_transaction_begin(box, ctx->ctx.transaction_flags, "mailbox cache decision"); struct mail_cache *cache = t->box->cache; struct mail_cache_view *view; if (mail_cache_open_and_verify(cache) < 0 || MAIL_CACHE_IS_UNUSABLE(cache)) { mailbox_transaction_rollback(&t); i_error("Cache is unusable"); ctx->ctx.exit_code = EX_TEMPFAIL; return -1; } view = mail_cache_view_open(cache, t->box->view); if (ctx->all_fields) cmd_mailbox_cache_decision_run_all_fields(ctx, cache); else cmd_mailbox_cache_decision_run_per_field(ctx, cache); /* update headers */ if (ctx->set_decision || ctx->set_last_used) mail_cache_header_fields_update(cache); mail_cache_view_close(&view); if (mailbox_transaction_commit(&t) < 0) { i_error("mailbox_transaction_commit() failed: %s", mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(&ctx->ctx, box); return -1; } return 0; } static int cmd_mailbox_cache_decision_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct mailbox_cache_cmd_context *ctx = container_of(_ctx, struct mailbox_cache_cmd_context, ctx); const char *const *boxname; int ret = 0; if (_ctx->exit_code != 0) return -1; for(boxname = ctx->boxes; ret == 0 && *boxname != NULL; boxname++) { struct mailbox *box; if ((ret = cmd_mailbox_cache_open_box(_ctx, user, *boxname, &box)) < 0) break; doveadm_print_sticky("mailbox", mailbox_get_vname(box)); ret = cmd_mailbox_cache_decision_run_box(ctx, box); mailbox_free(&box); } return ret; } static int cmd_mailbox_cache_remove_box(struct mailbox_cache_cmd_context *ctx, const struct mailbox_info *info) { struct doveadm_mail_iter *iter; struct mailbox *box; struct mail *mail; void *empty = NULL; int ret = 0, count = 0; if (doveadm_mail_iter_init(&ctx->ctx, info, ctx->ctx.search_args, 0, NULL, 0, &iter) < 0) return -1; box = doveadm_mail_iter_get_mailbox(iter); struct mail_index_transaction *t = mail_index_transaction_begin(box->view, MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL); struct mail_cache_view *view = mail_cache_view_open(box->cache, box->view); while (doveadm_mail_iter_next(iter, &mail)) { count++; doveadm_print(mailbox_get_vname(box)); doveadm_print(dec2str(mail->uid)); /* drop cache pointer */ mail_index_update_ext(t, mail->seq, view->cache->ext_id, &empty, NULL); doveadm_print("ok"); } if (mail_index_transaction_commit(&t) < 0) { i_error("mail_index_transaction_commit() failed: %s", mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(&ctx->ctx, box); ret = -1; } else { mail_cache_expunge_count(view->cache, count); } mail_cache_view_close(&view); if (doveadm_mail_iter_deinit(&iter) < 0) ret = -1; return ret; } static int cmd_mailbox_cache_remove_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct mailbox_cache_cmd_context *ctx = container_of(_ctx, struct mailbox_cache_cmd_context, ctx); const enum mailbox_list_iter_flags iter_flags = MAILBOX_LIST_ITER_NO_AUTO_BOXES | MAILBOX_LIST_ITER_RETURN_NO_FLAGS; struct doveadm_mailbox_list_iter *iter; const struct mailbox_info *info; int ret = 0; iter = doveadm_mailbox_list_iter_init(&ctx->ctx, user, ctx->ctx.search_args, iter_flags); while ((info = doveadm_mailbox_list_iter_next(iter)) != NULL) T_BEGIN { if (cmd_mailbox_cache_remove_box(ctx, info) < 0) ret = -1; } T_END; if (doveadm_mailbox_list_iter_deinit(&iter) < 0) ret = -1; return ret; } static void cmd_mailbox_cache_remove_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct mailbox_cache_cmd_context *ctx = container_of(_ctx, struct mailbox_cache_cmd_context, ctx); if (args[0] == NULL) doveadm_mail_help_name("mailbox cache remove"); doveadm_print_header_simple("mailbox"); doveadm_print_header_simple("uid"); doveadm_print_header_simple("result"); ctx->ctx.search_args = doveadm_mail_build_search_args(args); } static int cmd_mailbox_cache_purge_run_box(struct mailbox_cache_cmd_context *ctx, struct mailbox *box) { if (mail_cache_purge(box->cache, (uint32_t)-1, "doveadm mailbox cache purge") < 0) { mailbox_set_index_error(box); doveadm_mail_failed_mailbox(&ctx->ctx, box); return -1; } return 0; } static int cmd_mailbox_cache_purge_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct mailbox_cache_cmd_context *ctx = container_of(_ctx, struct mailbox_cache_cmd_context, ctx); const char *const *boxname; int ret = 0; if (_ctx->exit_code != 0) return -1; for(boxname = ctx->boxes; ret == 0 && *boxname != NULL; boxname++) { struct mailbox *box; if ((ret = cmd_mailbox_cache_open_box(_ctx, user, *boxname, &box)) < 0) break; ret = cmd_mailbox_cache_purge_run_box(ctx, box); mailbox_free(&box); } return ret; } static void cmd_mailbox_cache_purge_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct mailbox_cache_cmd_context *ctx = container_of(_ctx, struct mailbox_cache_cmd_context, ctx); ctx->boxes = args; } static struct doveadm_mail_cmd_context *cmd_mailbox_cache_decision_alloc(void) { struct mailbox_cache_cmd_context *ctx = doveadm_mail_cmd_alloc(struct mailbox_cache_cmd_context); ctx->ctx.v.init = cmd_mailbox_cache_decision_init; ctx->ctx.v.parse_arg = cmd_mailbox_cache_parse_arg; ctx->ctx.v.run = cmd_mailbox_cache_decision_run; ctx->ctx.getopt_args = "al:f:d:"; doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); return &ctx->ctx; } static struct doveadm_mail_cmd_context *cmd_mailbox_cache_remove_alloc(void) { struct mailbox_cache_cmd_context *ctx = doveadm_mail_cmd_alloc(struct mailbox_cache_cmd_context); ctx->ctx.v.init = cmd_mailbox_cache_remove_init; ctx->ctx.v.parse_arg = cmd_mailbox_cache_parse_arg; ctx->ctx.v.run = cmd_mailbox_cache_remove_run; ctx->ctx.getopt_args = ""; doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); return &ctx->ctx; } static struct doveadm_mail_cmd_context *cmd_mailbox_cache_purge_alloc(void) { struct mailbox_cache_cmd_context *ctx = doveadm_mail_cmd_alloc(struct mailbox_cache_cmd_context); ctx->ctx.v.init = cmd_mailbox_cache_purge_init; ctx->ctx.v.run = cmd_mailbox_cache_purge_run; ctx->ctx.getopt_args = ""; doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); return &ctx->ctx; } struct doveadm_cmd_ver2 doveadm_cmd_mailbox_cache_decision = { .name = "mailbox cache decision", .mail_cmd = cmd_mailbox_cache_decision_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"--all --fields " \ "--last-used --decision " \ " [ ... ]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('a', "all", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('f', "fieldstr", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('l', "last-used", CMD_PARAM_INT64, 0) DOVEADM_CMD_PARAM('d', "decision", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_mailbox_cache_remove = { .name = "mailbox cache remove", .mail_cmd = cmd_mailbox_cache_remove_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_mailbox_cache_purge = { .name = "mailbox cache purge", .mail_cmd = cmd_mailbox_cache_purge_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX" [...]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('\0', "mailbox", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-dump-log.c0000644000000000000000000003447214656633576016042 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "istream.h" #include "hex-binary.h" #include "mail-index-private.h" #include "mail-transaction-log-private.h" #include "doveadm-dump.h" #include static struct mail_transaction_ext_intro prev_intro; static void dump_hdr(struct istream *input, uint64_t *modseq_r, unsigned int *version_r) { struct mail_transaction_log_header hdr; const unsigned char *data; size_t size; int ret; ret = i_stream_read_bytes(input, &data, &size, sizeof(hdr)); if (ret < 0 && input->stream_errno != 0) i_fatal("read() failed: %s", i_stream_get_error(input)); if (ret <= 0) { i_fatal("file hdr read() %zu != %zu", size, sizeof(hdr)); } memcpy(&hdr, data, sizeof(hdr)); if (hdr.hdr_size < sizeof(hdr)) { memset(PTR_OFFSET(&hdr, hdr.hdr_size), 0, sizeof(hdr) - hdr.hdr_size); } i_stream_skip(input, hdr.hdr_size); printf("version = %u.%u\n", hdr.major_version, hdr.minor_version); printf("hdr size = %u\n", hdr.hdr_size); printf("index id = %u\n", hdr.indexid); printf("file seq = %u\n", hdr.file_seq); printf("prev file = %u/%u\n", hdr.prev_file_seq, hdr.prev_file_offset); printf("create stamp = %u\n", hdr.create_stamp); printf("initial modseq = %"PRIu64"\n", hdr.initial_modseq); printf("compat flags = %x\n", hdr.compat_flags); *modseq_r = hdr.initial_modseq; *version_r = MAIL_TRANSACTION_LOG_HDR_VERSION(&hdr); } static const char *log_record_type(unsigned int type) { const char *name; switch (type & MAIL_TRANSACTION_TYPE_MASK) { case MAIL_TRANSACTION_EXPUNGE|MAIL_TRANSACTION_EXPUNGE_PROT: name = "expunge"; break; case MAIL_TRANSACTION_EXPUNGE_GUID|MAIL_TRANSACTION_EXPUNGE_PROT: name = "expunge-guid"; break; case MAIL_TRANSACTION_APPEND: name = "append"; break; case MAIL_TRANSACTION_FLAG_UPDATE: name = "flag-update"; break; case MAIL_TRANSACTION_HEADER_UPDATE: name = "header-update"; break; case MAIL_TRANSACTION_EXT_INTRO: name = "ext-intro"; break; case MAIL_TRANSACTION_EXT_RESET: name = "ext-reset"; break; case MAIL_TRANSACTION_EXT_HDR_UPDATE: name = "ext-hdr"; break; case MAIL_TRANSACTION_EXT_HDR_UPDATE32: name = "ext-hdr32"; break; case MAIL_TRANSACTION_EXT_REC_UPDATE: name = "ext-rec"; break; case MAIL_TRANSACTION_KEYWORD_UPDATE: name = "keyword-update"; break; case MAIL_TRANSACTION_KEYWORD_RESET: name = "keyword-reset"; break; case MAIL_TRANSACTION_EXT_ATOMIC_INC: name = "ext-atomic-inc"; break; case MAIL_TRANSACTION_MODSEQ_UPDATE: name = "modseq-update"; break; case MAIL_TRANSACTION_INDEX_DELETED: name = "index-deleted"; break; case MAIL_TRANSACTION_INDEX_UNDELETED: name = "index-undeleted"; break; case MAIL_TRANSACTION_BOUNDARY: name = "boundary"; break; case MAIL_TRANSACTION_ATTRIBUTE_UPDATE: name = "attribute-update"; break; default: name = t_strdup_printf("unknown: %x", type); break; } if ((type & MAIL_TRANSACTION_EXTERNAL) != 0) name = t_strconcat(name, " (ext)", NULL); if ((type & MAIL_TRANSACTION_SYNC) != 0) name = t_strconcat(name, " (sync)", NULL); return name; } static void print_data(const void *data, size_t size) { size_t i; for (i = 0; i < size; i++) printf("%02x", ((const unsigned char *)data)[i]); if (size == 4) { const uint32_t *n = (const uint32_t *)data; printf(" (dec=%u)", *n); } } static void print_try_uint(const void *data, size_t size) { size_t i; switch (size) { case 1: { const uint8_t *n = data; printf("%u", *n); break; } case 2: { const uint16_t *n = data; uint32_t n16; memcpy(&n16, n, sizeof(n16)); printf("%u", n16); break; } case 4: { const uint32_t *n = data; uint32_t n32; memcpy(&n32, n, sizeof(n32)); printf("%u", n32); break; } case 8: { const uint64_t *n = data; uint64_t n64; memcpy(&n64, n, sizeof(n64)); printf("%"PRIu64, n64); break; } default: for (i = 0; i < size; i++) printf("%02x", ((const unsigned char *)data)[i]); } } #define HDRF(field) { \ #field, offsetof(struct mail_index_header, field), \ sizeof(((struct mail_index_header *)0)->field) } static struct { const char *name; unsigned int offset, size; } header_fields[] = { HDRF(minor_version), HDRF(base_header_size), HDRF(header_size), HDRF(record_size), HDRF(compat_flags), HDRF(indexid), HDRF(flags), HDRF(uid_validity), HDRF(next_uid), HDRF(messages_count), HDRF(unused_old_recent_messages_count), HDRF(seen_messages_count), HDRF(deleted_messages_count), HDRF(first_recent_uid), HDRF(first_unseen_uid_lowwater), HDRF(first_deleted_uid_lowwater), HDRF(log_file_seq), HDRF(log_file_tail_offset), HDRF(log_file_head_offset), HDRF(day_stamp) }; static void log_header_update(const struct mail_transaction_header_update *u, size_t data_size) { const void *data = u + 1; unsigned int offset = u->offset, size = u->size; unsigned int i; if (sizeof(*u) + size > data_size) { printf(" - offset = %u, size = %u (too large)\n", offset, size); return; } while (size > 0) { /* don't bother trying to handle header updates that include unknown/unexpected fields offsets/sizes */ for (i = 0; i < N_ELEMENTS(header_fields); i++) { if (header_fields[i].offset == offset && header_fields[i].size <= size) break; } if (i == N_ELEMENTS(header_fields)) { printf(" - offset = %u, size = %u: ", offset, size); print_data(data, size); printf("\n"); break; } printf(" - %s = ", header_fields[i].name); print_try_uint(data, header_fields[i].size); printf("\n"); data = CONST_PTR_OFFSET(data, header_fields[i].size); offset += header_fields[i].size; size -= header_fields[i].size; } } static void log_record_print(const struct mail_transaction_header *hdr, const void *data, size_t data_size, uint64_t *modseq) { unsigned int size = mail_index_offset_to_uint32(hdr->size) - sizeof(*hdr); switch (hdr->type & MAIL_TRANSACTION_TYPE_MASK) { case MAIL_TRANSACTION_EXPUNGE|MAIL_TRANSACTION_EXPUNGE_PROT: { const struct mail_transaction_expunge *exp = data; printf(" - uids="); for (; size > 0; size -= sizeof(*exp), exp++) { printf("%u-%u,", exp->uid1, exp->uid2); } printf("\n"); break; } case MAIL_TRANSACTION_EXPUNGE_GUID|MAIL_TRANSACTION_EXPUNGE_PROT: { const struct mail_transaction_expunge_guid *exp = data; for (; size > 0; size -= sizeof(*exp), exp++) { printf(" - uid=%u (guid ", exp->uid); print_data(exp->guid_128, sizeof(exp->guid_128)); printf(")\n"); } break; } case MAIL_TRANSACTION_APPEND: { const struct mail_index_record *rec = data; printf(" - uids="); for (; size > 0; size -= sizeof(*rec), rec++) { printf("%u", rec->uid); if (rec->flags != 0) printf(" (flags=%x)", rec->flags); printf(","); } printf("\n"); break; } case MAIL_TRANSACTION_FLAG_UPDATE: { const struct mail_transaction_flag_update *u = data; for (; size > 0; size -= sizeof(*u), u++) { printf(" - uids=%u-%u (flags +%x-%x, modseq_inc_flag=%d)\n", u->uid1, u->uid2, u->add_flags, u->remove_flags, u->modseq_inc_flag); } break; } case MAIL_TRANSACTION_HEADER_UPDATE: { const struct mail_transaction_header_update *u = data; log_header_update(u, data_size); break; } case MAIL_TRANSACTION_EXT_INTRO: { const struct mail_transaction_ext_intro *intro = data; prev_intro = *intro; printf(" - ext_id = %u\n", intro->ext_id); printf(" - reset_id = %u\n", intro->reset_id); printf(" - hdr_size = %u\n", intro->hdr_size); printf(" - record_size = %u\n", intro->record_size); printf(" - record_align = %u\n", intro->record_align); printf(" - flags = %u\n", intro->flags); printf(" - name_size = %u\n", intro->name_size); if (intro->name_size > 0) { const char *name = (const char *)(intro+1); printf(" - name = '%.*s'\n", intro->name_size, name); if (*modseq == 0 && intro->name_size == 6 && memcmp(name, "modseq", 6) == 0) *modseq = 1; } break; } case MAIL_TRANSACTION_EXT_RESET: { const struct mail_transaction_ext_reset *reset = data; printf(" - new_reset_id = %u\n", reset->new_reset_id); printf(" - preserve_data = %u\n", reset->preserve_data); break; } case MAIL_TRANSACTION_EXT_HDR_UPDATE: { const struct mail_transaction_ext_hdr_update *u = data; printf(" - offset = %u, size = %u", u->offset, u->size); if (sizeof(*u) + u->size <= data_size) { printf(": "); print_data(u + 1, u->size); } else { printf(" (too large)"); } printf("\n"); break; } case MAIL_TRANSACTION_EXT_HDR_UPDATE32: { const struct mail_transaction_ext_hdr_update32 *u = data; printf(" - offset = %u, size = %u", u->offset, u->size); if (sizeof(*u) + u->size <= data_size) { printf(": "); print_data(u + 1, u->size); } else { printf(" (too large)"); } printf("\n"); break; } case MAIL_TRANSACTION_EXT_REC_UPDATE: { const struct mail_transaction_ext_rec_update *rec = data, *end; size_t record_size; end = CONST_PTR_OFFSET(data, size); record_size = (sizeof(*rec) + prev_intro.record_size + 3) & ~3U; while (rec < end) { printf(" - uid=%u: ", rec->uid); size_t bytes_left = (const char *)end - (const char *)(rec + 1); if (prev_intro.record_size <= bytes_left) print_data(rec + 1, prev_intro.record_size); else printf("(record_size too large)"); printf("\n"); rec = CONST_PTR_OFFSET(rec, record_size); } break; } case MAIL_TRANSACTION_EXT_ATOMIC_INC: { const struct mail_transaction_ext_atomic_inc *rec = data, *end; end = CONST_PTR_OFFSET(data, size); for (; rec < end; rec++) { printf(" - uid=%u: ", rec->uid); if (rec->diff > 0) printf("+%d\n", rec->diff); else printf("%d\n", rec->diff); } break; } case MAIL_TRANSACTION_KEYWORD_UPDATE: { const struct mail_transaction_keyword_update *u = data; const uint32_t *uid; unsigned int uid_offset; printf(" - modify=%d, name=%.*s, uids=", u->modify_type, u->name_size, (const char *)(u+1)); uid_offset = sizeof(*u) + u->name_size + ((u->name_size % 4) == 0 ? 0 : 4 - (u->name_size%4)); uid = (const uint32_t *)((const char *)u + uid_offset); size -= uid_offset; for (; size > 0; size -= sizeof(*uid)*2, uid += 2) { printf("%u-%u,", uid[0], uid[1]); } printf("\n"); break; } case MAIL_TRANSACTION_KEYWORD_RESET: { const struct mail_transaction_keyword_reset *u = data; printf(" - uids="); for (; size > 0; size -= sizeof(*u), u++) { printf("%u-%u, ", u->uid1, u->uid2); } printf("\n"); break; } case MAIL_TRANSACTION_MODSEQ_UPDATE: { const struct mail_transaction_modseq_update *rec, *end; end = CONST_PTR_OFFSET(data, size); for (rec = data; rec < end; rec++) { printf(" - uid=%u modseq=%"PRIu64"\n", rec->uid, ((uint64_t)rec->modseq_high32 << 32) | rec->modseq_low32); } break; } case MAIL_TRANSACTION_INDEX_DELETED: case MAIL_TRANSACTION_INDEX_UNDELETED: break; case MAIL_TRANSACTION_BOUNDARY: { const struct mail_transaction_boundary *rec = data; printf(" - size=%u\n", rec->size); break; } case MAIL_TRANSACTION_ATTRIBUTE_UPDATE: { const char *keys = data; const uint32_t *extra; unsigned int i, extra_pos, extra_count = 0; for (i = 0; i < size && keys[i] != '\0'; ) { if (keys[i] == '+') extra_count++; extra_count++; i += strlen(keys+i) + 1; } if (i % sizeof(uint32_t) != 0) i += sizeof(uint32_t) - i%sizeof(uint32_t); extra = (const void *)(keys+i); if ((size-i) != extra_count*sizeof(uint32_t)) { printf(" - broken entry\n"); break; } extra_pos = 0; for (i = 0; i < size && keys[i] != '\0'; ) { printf(" - %s: %s/%s : timestamp=%s", keys[i] == '+' ? "add" : keys[i] == '-' ? "remove" : "?", keys[i+1] == 'p' ? "private" : keys[i+1] == 's' ? "shared" : "?error?", keys+i+2, unixdate2str(extra[extra_pos++])); if (keys[i] == '+') printf(" value_len=%u", extra[extra_pos++]); printf("\n"); i += strlen(keys+i) + 1; } break; } default: break; } } static int dump_record(struct istream *input, uint64_t *modseq, unsigned int version) { struct mail_transaction_header hdr; unsigned int hdr_size; const unsigned char *data; size_t size; int ret; ret = i_stream_read_bytes(input, &data, &size, sizeof(hdr)); if (ret < 0 && input->stream_errno != 0) i_fatal("read() failed: %s", i_stream_get_error(input)); if (ret <= 0) { if (size == 0) return 0; i_fatal("rec hdr read() %zu != %zu", size, sizeof(hdr)); } memcpy(&hdr, data, sizeof(hdr)); hdr_size = mail_index_offset_to_uint32(hdr.size); if (hdr_size < sizeof(hdr)) { printf("record: offset=%"PRIuUOFF_T", " "type=%s, size=broken (%x)\n", input->v_offset, log_record_type(hdr.type), hdr.size); return 0; } printf("record: offset=%"PRIuUOFF_T", type=%s, size=%u", input->v_offset, log_record_type(hdr.type), hdr_size); i_stream_skip(input, sizeof(hdr)); size_t data_size = hdr_size - sizeof(hdr); ret = i_stream_read_bytes(input, &data, &size, data_size); if (ret < 0 && input->stream_errno != 0) i_fatal("read() failed: %s", i_stream_get_error(input)); if (ret <= 0) { i_fatal("rec data read() %zu != %zu", size, data_size); } uint64_t prev_modseq = *modseq; mail_transaction_update_modseq(&hdr, data, modseq, version); if (*modseq > prev_modseq) printf(", modseq=%"PRIu64, *modseq); printf("\n"); log_record_print(&hdr, data, data_size, modseq); i_stream_skip(input, data_size); return 1; } static void cmd_dump_log(const char *path, const char *const *args ATTR_UNUSED) { struct istream *input; uint64_t modseq; unsigned int version; int ret; input = i_stream_create_file(path, SIZE_MAX); dump_hdr(input, &modseq, &version); do { T_BEGIN { ret = dump_record(input, &modseq, version); } T_END; } while (ret > 0); i_stream_unref(&input); } static bool test_dump_log(const char *path) { struct mail_transaction_log_header hdr; const char *p; bool ret = FALSE; int fd; p = strrchr(path, '/'); if (p == NULL) p = path; p = strstr(p, ".log"); if (p == NULL || !(p[4] == '\0' || p[4] == '.')) return FALSE; fd = open(path, O_RDONLY); if (fd == -1) return FALSE; if (read(fd, &hdr, sizeof(hdr)) >= MAIL_TRANSACTION_LOG_HEADER_MIN_SIZE && hdr.major_version == MAIL_TRANSACTION_LOG_MAJOR_VERSION && hdr.hdr_size >= MAIL_TRANSACTION_LOG_HEADER_MIN_SIZE) ret = TRUE; i_close_fd(&fd); return ret; } struct doveadm_cmd_dump doveadm_cmd_dump_log = { "log", test_dump_log, cmd_dump_log }; dovecot-2.3.21.1/src/doveadm/doveadm-mail-index.c0000644000000000000000000002022114656633576016330 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "strescape.h" #include "net.h" #include "write-full.h" #include "mail-namespace.h" #include "mail-storage.h" #include "mail-search-build.h" #include "mailbox-list-iter.h" #include "doveadm-settings.h" #include "doveadm-mail.h" #include #define INDEXER_SOCKET_NAME "indexer" #define INDEXER_HANDSHAKE "VERSION\tindexer\t1\t0\n" struct index_cmd_context { struct doveadm_mail_cmd_context ctx; int queue_fd; unsigned int max_recent_msgs; bool queue:1; bool have_wildcards:1; }; static int cmd_index_box_precache(struct doveadm_mail_cmd_context *dctx, struct mailbox *box) { struct mailbox_status status; struct mailbox_transaction_context *trans; struct mail_search_args *search_args; struct mail_search_context *ctx; struct mail *mail; struct mailbox_metadata metadata; uint32_t seq; unsigned int counter = 0, max; int ret = 0; if (mailbox_get_metadata(box, MAILBOX_METADATA_PRECACHE_FIELDS, &metadata) < 0) { i_error("Mailbox %s: Precache-fields lookup failed: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, NULL)); } if (mailbox_get_status(box, STATUS_MESSAGES | STATUS_LAST_CACHED_SEQ, &status) < 0) { i_error("Mailbox %s: Status lookup failed: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, NULL)); return -1; } seq = status.last_cached_seq + 1; if (seq > status.messages) { if (doveadm_verbose) { i_info("%s: Cache is already up to date", mailbox_get_vname(box)); } return 0; } if (doveadm_verbose) { i_info("%s: Caching mails seq=%u..%u", mailbox_get_vname(box), seq, status.messages); } trans = mailbox_transaction_begin(box, MAILBOX_TRANSACTION_FLAG_NO_CACHE_DEC | dctx->transaction_flags, __func__); search_args = mail_search_build_init(); mail_search_build_add_seqset(search_args, seq, status.messages); ctx = mailbox_search_init(trans, search_args, NULL, metadata.precache_fields, NULL); mail_search_args_unref(&search_args); max = status.messages - seq + 1; while (mailbox_search_next(ctx, &mail)) { if (mail_precache(mail) < 0) { i_error("Mailbox %s: Precache for UID=%u failed: %s", mailbox_get_vname(box), mail->uid, mailbox_get_last_internal_error(box, NULL)); ret = -1; break; } if (doveadm_verbose && ++counter % 100 == 0) { printf("\r%u/%u", counter, max); fflush(stdout); } } if (doveadm_verbose) printf("\r%u/%u\n", counter, max); if (mailbox_search_deinit(&ctx) < 0) { i_error("Mailbox %s: Mail search failed: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, NULL)); ret = -1; } if (mailbox_transaction_commit(&trans) < 0) { i_error("Mailbox %s: Transaction commit failed: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, NULL)); ret = -1; } return ret; } static int cmd_index_box(struct index_cmd_context *ctx, const struct mailbox_info *info) { struct mailbox *box; struct mailbox_status status; int ret = 0; box = mailbox_alloc(info->ns->list, info->vname, MAILBOX_FLAG_IGNORE_ACLS); if (ctx->max_recent_msgs != 0) { /* index only if there aren't too many recent messages. don't bother syncing the mailbox, that alone can take a while with large maildirs. */ if (mailbox_open(box) < 0) { i_error("Opening mailbox %s failed: %s", info->vname, mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(&ctx->ctx, box); mailbox_free(&box); return -1; } mailbox_get_open_status(box, STATUS_RECENT, &status); if (status.recent > ctx->max_recent_msgs) { mailbox_free(&box); return 0; } } if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ) < 0) { i_error("Syncing mailbox %s failed: %s", info->vname, mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(&ctx->ctx, box); ret = -1; } else { if (cmd_index_box_precache(&ctx->ctx, box) < 0) { doveadm_mail_failed_mailbox(&ctx->ctx, box); ret = -1; } } mailbox_free(&box); return ret; } static void index_queue_connect(struct index_cmd_context *ctx) { const char *path; path = t_strconcat(doveadm_settings->base_dir, "/"INDEXER_SOCKET_NAME, NULL); ctx->queue_fd = net_connect_unix(path); if (ctx->queue_fd == -1) i_fatal("net_connect_unix(%s) failed: %m", path); if (write_full(ctx->queue_fd, INDEXER_HANDSHAKE, strlen(INDEXER_HANDSHAKE)) < 0) i_fatal("write(indexer) failed: %m"); } static void cmd_index_queue(struct index_cmd_context *ctx, struct mail_user *user, const char *mailbox) { if (ctx->queue_fd == -1) index_queue_connect(ctx); i_assert(ctx->queue_fd != -1); T_BEGIN { string_t *str = t_str_new(256); str_append(str, "APPEND\t0\t"); str_append_tabescaped(str, user->username); str_append_c(str, '\t'); str_append_tabescaped(str, mailbox); str_printfa(str, "\t%u\n", ctx->max_recent_msgs); if (write_full(ctx->queue_fd, str_data(str), str_len(str)) < 0) i_fatal("write(indexer) failed: %m"); } T_END; } static int cmd_index_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct index_cmd_context *ctx = (struct index_cmd_context *)_ctx; const enum mailbox_list_iter_flags iter_flags = MAILBOX_LIST_ITER_NO_AUTO_BOXES | MAILBOX_LIST_ITER_RETURN_NO_FLAGS | MAILBOX_LIST_ITER_STAR_WITHIN_NS; const enum mail_namespace_type ns_mask = MAIL_NAMESPACE_TYPE_MASK_ALL; struct mailbox_list_iterate_context *iter; const struct mailbox_info *info; unsigned int i; int ret = 0; if (ctx->queue && !ctx->have_wildcards) { /* we can do this quickly without going through the mailboxes */ for (i = 0; _ctx->args[i] != NULL; i++) cmd_index_queue(ctx, user, _ctx->args[i]); return 0; } iter = mailbox_list_iter_init_namespaces(user->namespaces, _ctx->args, ns_mask, iter_flags); while ((info = mailbox_list_iter_next(iter)) != NULL) { if ((info->flags & (MAILBOX_NOSELECT | MAILBOX_NONEXISTENT)) == 0) T_BEGIN { if (ctx->queue) cmd_index_queue(ctx, user, info->vname); else { if (cmd_index_box(ctx, info) < 0) ret = -1; } } T_END; } if (mailbox_list_iter_deinit(&iter) < 0) { i_error("Listing mailboxes failed: %s", mailbox_list_get_last_internal_error(user->namespaces->list, NULL)); doveadm_mail_failed_error(_ctx, MAIL_ERROR_TEMP); ret = -1; } return ret; } static void cmd_index_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct index_cmd_context *ctx = (struct index_cmd_context *)_ctx; unsigned int i; if (args[0] == NULL) doveadm_mail_help_name("index"); for (i = 0; args[i] != NULL; i++) { if (strchr(args[i], '*') != NULL || strchr(args[i], '%') != NULL) { ctx->have_wildcards = TRUE; break; } } } static void cmd_index_deinit(struct doveadm_mail_cmd_context *_ctx) { struct index_cmd_context *ctx = (struct index_cmd_context *)_ctx; if (ctx->queue_fd != -1) { net_disconnect(ctx->queue_fd); ctx->queue_fd = -1; } } static bool cmd_index_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct index_cmd_context *ctx = (struct index_cmd_context *)_ctx; switch (c) { case 'q': ctx->queue = TRUE; break; case 'n': if (str_to_uint(optarg, &ctx->max_recent_msgs) < 0) { i_fatal_status(EX_USAGE, "Invalid -n parameter number: %s", optarg); } break; default: return FALSE; } return TRUE; } static struct doveadm_mail_cmd_context *cmd_index_alloc(void) { struct index_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct index_cmd_context); ctx->queue_fd = -1; ctx->ctx.getopt_args = "qn:"; ctx->ctx.v.parse_arg = cmd_index_parse_arg; ctx->ctx.v.init = cmd_index_init; ctx->ctx.v.deinit = cmd_index_deinit; ctx->ctx.v.run = cmd_index_run; return &ctx->ctx; } struct doveadm_cmd_ver2 doveadm_cmd_index_ver2 = { .name = "index", .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"[-q] [-n ] ", .mail_cmd = cmd_index_alloc, DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('q',"queue",CMD_PARAM_BOOL,0) DOVEADM_CMD_PARAM('n',"max-recent",CMD_PARAM_STR,0) DOVEADM_CMD_PARAM('\0',"mailbox-mask",CMD_PARAM_STR,CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-fs.c0000644000000000000000000004175514656633576014730 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "md5.h" #include "sha2.h" #include "hash-method.h" #include "hex-binary.h" #include "istream.h" #include "ostream.h" #include "iostream-ssl.h" #include "fs-api.h" #include "doveadm.h" #include "doveadm-print.h" #include #include static void fs_cmd_help(struct doveadm_cmd_context *cctx) ATTR_NORETURN; static void cmd_fs_delete(struct doveadm_cmd_context *cctx); static struct fs * cmd_fs_init(struct doveadm_cmd_context *cctx) { struct ssl_iostream_settings ssl_set; struct fs_settings fs_set; struct fs *fs; const char *fs_driver, *fs_args, *error; if (!doveadm_cmd_param_str(cctx, "fs-driver", &fs_driver) || !doveadm_cmd_param_str(cctx, "fs-args", &fs_args)) fs_cmd_help(cctx); doveadm_get_ssl_settings(&ssl_set, pool_datastack_create()); ssl_set.verbose = doveadm_debug; i_zero(&fs_set); fs_set.ssl_client_set = &ssl_set; fs_set.temp_dir = doveadm_settings->mail_temp_dir; fs_set.base_dir = doveadm_settings->base_dir; fs_set.debug = doveadm_debug; if (fs_init(fs_driver, fs_args, &fs_set, &fs, &error) < 0) i_fatal("fs_init() failed: %s", error); return fs; } static void cmd_fs_get(struct doveadm_cmd_context *cctx) { struct fs *fs; struct fs_file *file; struct istream *input; const char *path; const unsigned char *data; size_t size; ssize_t ret; doveadm_print_init(DOVEADM_PRINT_TYPE_PAGER); doveadm_print_header("content", "content", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); fs = cmd_fs_init(cctx); if (!doveadm_cmd_param_str(cctx, "path", &path)) fs_cmd_help(cctx); file = fs_file_init(fs, path, FS_OPEN_MODE_READONLY); input = fs_read_stream(file, IO_BLOCK_SIZE); while ((ret = i_stream_read_more(input, &data, &size)) > 0) { doveadm_print_stream(data, size); i_stream_skip(input, size); } doveadm_print_stream("", 0); i_assert(ret == -1); if (input->stream_errno == ENOENT) { i_error("%s doesn't exist: %s", fs_file_path(file), i_stream_get_error(input)); doveadm_exit_code = DOVEADM_EX_NOTFOUND; } else if (input->stream_errno != 0) { i_error("read(%s) failed: %s", fs_file_path(file), i_stream_get_error(input)); doveadm_exit_code = EX_TEMPFAIL; } i_stream_unref(&input); fs_file_deinit(&file); fs_deinit(&fs); } static void cmd_fs_put(struct doveadm_cmd_context *cctx) { struct fs *fs; enum fs_properties props; const char *hash_str, *src_path, *dest_path; struct fs_file *file; struct istream *input; struct ostream *output; buffer_t *hash = NULL; fs = cmd_fs_init(cctx); if (!doveadm_cmd_param_str(cctx, "input-path", &src_path) || !doveadm_cmd_param_str(cctx, "path", &dest_path)) fs_cmd_help(cctx); if (doveadm_cmd_param_str(cctx, "hash", &hash_str)) { hash = t_buffer_create(32); if (hex_to_binary(optarg, hash) < 0) i_fatal("Invalid -h parameter: Hash not in hex"); } file = fs_file_init(fs, dest_path, FS_OPEN_MODE_REPLACE); props = fs_get_properties(fs); if (hash == NULL) ; else if (hash->used == hash_method_md5.digest_size) { if ((props & FS_PROPERTY_WRITE_HASH_MD5) == 0) i_fatal("fs backend doesn't support MD5 hashes"); fs_write_set_hash(file, hash_method_lookup(hash_method_md5.name), hash->data); } else if (hash->used == hash_method_sha256.digest_size) { if ((props & FS_PROPERTY_WRITE_HASH_SHA256) == 0) i_fatal("fs backend doesn't support SHA256 hashes"); fs_write_set_hash(file, hash_method_lookup(hash_method_sha256.name), hash->data); } output = fs_write_stream(file); input = i_stream_create_file(src_path, IO_BLOCK_SIZE); o_stream_nsend_istream(output, input); i_stream_destroy(&input); if (fs_write_stream_finish(file, &output) < 0) { i_error("fs_write_stream_finish() failed: %s", fs_file_last_error(file)); doveadm_exit_code = EX_TEMPFAIL; } fs_file_deinit(&file); fs_deinit(&fs); } static void cmd_fs_copy(struct doveadm_cmd_context *cctx) { struct fs *fs; struct fs_file *src_file, *dest_file; const char *src_path, *dest_path; fs = cmd_fs_init(cctx); if (!doveadm_cmd_param_str(cctx, "source-path", &src_path) || !doveadm_cmd_param_str(cctx, "destination-path", &dest_path)) fs_cmd_help(cctx); src_file = fs_file_init(fs, src_path, FS_OPEN_MODE_READONLY); dest_file = fs_file_init(fs, dest_path, FS_OPEN_MODE_REPLACE); if (fs_copy(src_file, dest_file) == 0) ; else if (errno == ENOENT) { i_error("%s doesn't exist: %s", src_path, fs_file_last_error(dest_file)); doveadm_exit_code = DOVEADM_EX_NOTFOUND; } else { i_error("fs_copy(%s, %s) failed: %s", src_path, dest_path, fs_file_last_error(dest_file)); doveadm_exit_code = EX_TEMPFAIL; } fs_file_deinit(&src_file); fs_file_deinit(&dest_file); fs_deinit(&fs); } static void cmd_fs_stat(struct doveadm_cmd_context *cctx) { struct fs *fs; struct fs_file *file; struct stat st; const char *path; fs = cmd_fs_init(cctx); if (!doveadm_cmd_param_str(cctx, "path", &path)) fs_cmd_help(cctx); file = fs_file_init(fs, path, FS_OPEN_MODE_READONLY); doveadm_print_init(DOVEADM_PRINT_TYPE_FORMATTED); doveadm_print_formatted_set_format("%{path} size=%{size}"); doveadm_print_header_simple("path"); doveadm_print_header("size", "size", DOVEADM_PRINT_HEADER_FLAG_NUMBER); if (fs_stat(file, &st) == 0) { doveadm_print(fs_file_path(file)); doveadm_print(dec2str(st.st_size)); } else if (errno == ENOENT) { i_error("%s doesn't exist: %s", fs_file_path(file), fs_file_last_error(file)); doveadm_exit_code = DOVEADM_EX_NOTFOUND; } else { i_error("fs_stat(%s) failed: %s", fs_file_path(file), fs_file_last_error(file)); doveadm_exit_code = EX_TEMPFAIL; } fs_file_deinit(&file); fs_deinit(&fs); } static void cmd_fs_metadata(struct doveadm_cmd_context *cctx) { struct fs *fs; struct fs_file *file; const struct fs_metadata *m; const ARRAY_TYPE(fs_metadata) *metadata; const char *path; fs = cmd_fs_init(cctx); if (!doveadm_cmd_param_str(cctx, "path", &path)) fs_cmd_help(cctx); file = fs_file_init(fs, path, FS_OPEN_MODE_READONLY); doveadm_print_init(DOVEADM_PRINT_TYPE_FORMATTED); doveadm_print_formatted_set_format("%{key}=%{value}\n"); doveadm_print_header_simple("key"); doveadm_print_header_simple("value"); if (fs_get_metadata(file, &metadata) == 0) { array_foreach(metadata, m) { doveadm_print(m->key); doveadm_print(m->value); } } else if (errno == ENOENT) { i_error("%s doesn't exist: %s", fs_file_path(file), fs_file_last_error(file)); doveadm_exit_code = DOVEADM_EX_NOTFOUND; } else { i_error("fs_stat(%s) failed: %s", fs_file_path(file), fs_file_last_error(file)); doveadm_exit_code = EX_TEMPFAIL; } fs_file_deinit(&file); fs_deinit(&fs); } struct fs_delete_ctx { struct fs *fs; const char *path_prefix; unsigned int files_count; struct fs_file **files; }; static int cmd_fs_delete_ctx_run(struct fs_delete_ctx *ctx) { unsigned int i; int ret = 0; for (i = 0; i < ctx->files_count; i++) { if (ctx->files[i] == NULL) ; else if (fs_delete(ctx->files[i]) == 0) fs_file_deinit(&ctx->files[i]); else if (errno == EAGAIN) { if (ret == 0) ret = 1; } else if (errno == ENOENT) { i_error("%s doesn't exist: %s", fs_file_path(ctx->files[i]), fs_file_last_error(ctx->files[i])); doveadm_exit_code = DOVEADM_EX_NOTFOUND; ret = -1; } else { i_error("fs_delete(%s) failed: %s", fs_file_path(ctx->files[i]), fs_file_last_error(ctx->files[i])); doveadm_exit_code = EX_TEMPFAIL; ret = -1; } } return ret; } static int doveadm_fs_delete_async_fname(struct fs_delete_ctx *ctx, const char *fname) { unsigned int i; int ret; for (i = 0; i < ctx->files_count; i++) { if (ctx->files[i] != NULL) continue; ctx->files[i] = fs_file_init(ctx->fs, t_strdup_printf("%s%s", ctx->path_prefix, fname), FS_OPEN_MODE_READONLY | FS_OPEN_FLAG_ASYNC | FS_OPEN_FLAG_ASYNC_NOQUEUE); fname = NULL; break; } if ((ret = cmd_fs_delete_ctx_run(ctx)) < 0) return -1; if (fname != NULL) { if (ret > 0) fs_wait_async(ctx->fs); return doveadm_fs_delete_async_fname(ctx, fname); } return 0; } static void doveadm_fs_delete_async_finish(struct fs_delete_ctx *ctx) { unsigned int i; while (doveadm_exit_code == 0 && cmd_fs_delete_ctx_run(ctx) > 0) { fs_wait_async(ctx->fs); } for (i = 0; i < ctx->files_count; i++) { fs_file_deinit(&ctx->files[i]); } } static void cmd_fs_delete_dir_recursive(struct fs *fs, unsigned int async_count, const char *path_prefix) { struct fs_iter *iter; ARRAY_TYPE(const_string) fnames; struct fs_delete_ctx ctx; const char *fname, *error; int ret; i_zero(&ctx); ctx.fs = fs; ctx.path_prefix = path_prefix; ctx.files_count = I_MAX(async_count, 1); ctx.files = t_new(struct fs_file *, ctx.files_count); /* delete subdirs first. all fs backends can't handle recursive lookups, so save the list first. */ t_array_init(&fnames, 8); iter = fs_iter_init(fs, path_prefix, FS_ITER_FLAG_DIRS); while ((fname = fs_iter_next(iter)) != NULL) { /* append "/" so that if FS_PROPERTY_DIRECTORIES is set, we'll include the "/" suffix in the filename when deleting it. */ fname = t_strconcat(fname, "/", NULL); array_push_back(&fnames, &fname); } if (fs_iter_deinit(&iter, &error) < 0) { i_error("fs_iter_deinit(%s) failed: %s", path_prefix, error); doveadm_exit_code = EX_TEMPFAIL; } array_foreach_elem(&fnames, fname) T_BEGIN { cmd_fs_delete_dir_recursive(fs, async_count, t_strdup_printf("%s%s", path_prefix, fname)); } T_END; /* delete files. again because we're doing this asynchronously finish the iteration first. */ if ((fs_get_properties(fs) & FS_PROPERTY_DIRECTORIES) != 0) { /* we need to explicitly delete also the directories */ } else { array_clear(&fnames); } iter = fs_iter_init(fs, path_prefix, 0); while ((fname = fs_iter_next(iter)) != NULL) { fname = t_strdup(fname); array_push_back(&fnames, &fname); } if (fs_iter_deinit(&iter, &error) < 0) { i_error("fs_iter_deinit(%s) failed: %s", path_prefix, error); doveadm_exit_code = EX_TEMPFAIL; } array_foreach_elem(&fnames, fname) { T_BEGIN { ret = doveadm_fs_delete_async_fname(&ctx, fname); } T_END; if (ret < 0) break; } doveadm_fs_delete_async_finish(&ctx); } static void cmd_fs_delete_recursive_path(struct fs *fs, const char *path, unsigned int async_count) { struct fs_file *file; size_t path_len; path_len = strlen(path); if (path_len > 0 && path[path_len-1] != '/') path = t_strconcat(path, "/", NULL); cmd_fs_delete_dir_recursive(fs, async_count, path); if ((fs_get_properties(fs) & FS_PROPERTY_DIRECTORIES) != 0) { /* delete the root itself */ file = fs_file_init(fs, path, FS_OPEN_MODE_READONLY); if (fs_delete(file) < 0) { i_error("fs_delete(%s) failed: %s", fs_file_path(file), fs_file_last_error(file)); doveadm_exit_code = EX_TEMPFAIL; } fs_file_deinit(&file); } } static void cmd_fs_delete_recursive(struct doveadm_cmd_context *cctx, unsigned int async_count) { struct fs *fs; const char *const *paths; unsigned int i; fs = cmd_fs_init(cctx); if (!doveadm_cmd_param_array(cctx, "path", &paths)) fs_cmd_help(cctx); for (i = 0; paths[i] != NULL; i++) cmd_fs_delete_recursive_path(fs, paths[i], async_count); fs_deinit(&fs); } static void cmd_fs_delete_paths(struct doveadm_cmd_context *cctx, unsigned int async_count) { struct fs *fs; struct fs_delete_ctx ctx; const char *const *paths; unsigned int i; int ret; fs = cmd_fs_init(cctx); if (!doveadm_cmd_param_array(cctx, "path", &paths)) fs_cmd_help(cctx); i_zero(&ctx); ctx.fs = fs; ctx.path_prefix = ""; ctx.files_count = I_MAX(async_count, 1); ctx.files = t_new(struct fs_file *, ctx.files_count); for (i = 0; paths[i] != NULL; i++) { T_BEGIN { ret = doveadm_fs_delete_async_fname(&ctx, paths[i]); } T_END; if (ret < 0) break; } doveadm_fs_delete_async_finish(&ctx); fs_deinit(&fs); } static void cmd_fs_delete(struct doveadm_cmd_context *cctx) { bool recursive = FALSE; int64_t async_count = 0; (void)doveadm_cmd_param_bool(cctx, "recursive", &recursive); (void)doveadm_cmd_param_int64(cctx, "max-parallel", &async_count); if (recursive) cmd_fs_delete_recursive(cctx, async_count); else cmd_fs_delete_paths(cctx, async_count); } static void cmd_fs_iter_full(struct doveadm_cmd_context *cctx, enum fs_iter_flags flags) { struct fs *fs; struct fs_iter *iter; const char *path, *fname, *error; bool b; if (doveadm_cmd_param_bool(cctx, "no-cache", &b) && b) flags |= FS_ITER_FLAG_NOCACHE; if (doveadm_cmd_param_bool(cctx, "object-ids", &b) && b) flags |= FS_ITER_FLAG_OBJECTIDS; fs = cmd_fs_init(cctx); if (!doveadm_cmd_param_str(cctx, "path", &path)) fs_cmd_help(cctx); doveadm_print_init(DOVEADM_PRINT_TYPE_FORMATTED); doveadm_print_formatted_set_format("%{path}\n"); doveadm_print_header_simple("path"); iter = fs_iter_init(fs, path, flags); while ((fname = fs_iter_next(iter)) != NULL) { doveadm_print(fname); } if (fs_iter_deinit(&iter, &error) < 0) { i_error("fs_iter_deinit(%s) failed: %s", path, error); doveadm_exit_code = EX_TEMPFAIL; } fs_deinit(&fs); } static void cmd_fs_iter(struct doveadm_cmd_context *cctx) { cmd_fs_iter_full(cctx, 0); } static void cmd_fs_iter_dirs(struct doveadm_cmd_context *cctx) { cmd_fs_iter_full(cctx, FS_ITER_FLAG_DIRS); } struct doveadm_cmd_ver2 doveadm_cmd_fs[] = { { .name = "fs get", .cmd = cmd_fs_get, .usage = " ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', "fs-driver", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "fs-args", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "path", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "fs put", .cmd = cmd_fs_put, .usage = "[-h ] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('h', "hash", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "fs-driver", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "fs-args", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "input-path", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "path", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "fs copy", .cmd = cmd_fs_copy, .usage = " ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', "fs-driver", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "fs-args", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "source-path", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "destination-path", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "fs stat", .cmd = cmd_fs_stat, .usage = " ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', "fs-driver", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "fs-args", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "path", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "fs metadata", .cmd = cmd_fs_metadata, .usage = " ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', "fs-driver", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "fs-args", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "path", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "fs delete", .cmd = cmd_fs_delete, .usage = "[-R] [-n ] [ ...]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('R', "recursive", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('n', "max-parallel", CMD_PARAM_INT64, 0) DOVEADM_CMD_PARAM('\0', "fs-driver", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "fs-args", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "path", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "fs iter", .cmd = cmd_fs_iter, .usage = "[--no-cache] [--object-ids] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('C', "no-cache", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('O', "object-ids", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "fs-driver", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "fs-args", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "path", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "fs iter-dirs", .cmd = cmd_fs_iter_dirs, .usage = " ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', "fs-driver", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "fs-args", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "path", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END } }; static void fs_cmd_help(struct doveadm_cmd_context *cctx) { unsigned int i; for (i = 0; i < N_ELEMENTS(doveadm_cmd_fs); i++) { if (doveadm_cmd_fs[i].cmd == cctx->cmd->cmd) help_ver2(&doveadm_cmd_fs[i]); } i_unreached(); } void doveadm_register_fs_commands(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(doveadm_cmd_fs); i++) doveadm_cmd_register_ver2(&doveadm_cmd_fs[i]); } dovecot-2.3.21.1/src/doveadm/client-connection.h0000644000000000000000000000103514656633576016306 00000000000000#ifndef CLIENT_CONNECTION_H #define CLIENT_CONNECTION_H #include "net.h" #define DOVEADM_LOG_CHANNEL_ID 'L' struct client_connection { pool_t pool; enum doveadm_client_type type; const char *name; struct ip_addr local_ip, remote_ip; in_port_t local_port, remote_port; const struct doveadm_settings *set; void (*free)(struct client_connection *conn); }; struct client_connection * client_connection_tcp_create(int fd, int listen_fd, bool ssl); struct client_connection * client_connection_http_create(int fd, bool ssl); #endif dovecot-2.3.21.1/src/doveadm/doveadm-dsync.h0000644000000000000000000000043214656633576015430 00000000000000#ifndef DOVEADM_DSYNC_H #define DOVEADM_DSYNC_H extern struct doveadm_cmd_ver2 doveadm_cmd_dsync_mirror; extern struct doveadm_cmd_ver2 doveadm_cmd_dsync_backup; extern struct doveadm_cmd_ver2 doveadm_cmd_dsync_server; void doveadm_dsync_main(int *_argc, char **_argv[]); #endif dovecot-2.3.21.1/src/doveadm/doveadm-mailbox-list-iter.h0000644000000000000000000000154014656633576017656 00000000000000#ifndef DOVEADM_MAILBOX_LIST_ITER_H #define DOVEADM_MAILBOX_LIST_ITER_H #include "mailbox-list-iter.h" struct doveadm_mail_cmd_context; /* List only selectable mailboxes */ struct doveadm_mailbox_list_iter * doveadm_mailbox_list_iter_init(struct doveadm_mail_cmd_context *ctx, struct mail_user *user, struct mail_search_args *search_args, enum mailbox_list_iter_flags iter_flags); /* List all mailboxes */ struct doveadm_mailbox_list_iter * doveadm_mailbox_list_iter_full_init(struct doveadm_mail_cmd_context *ctx, struct mail_user *user, struct mail_search_args *search_args, enum mailbox_list_iter_flags iter_flags); int doveadm_mailbox_list_iter_deinit(struct doveadm_mailbox_list_iter **iter); const struct mailbox_info * doveadm_mailbox_list_iter_next(struct doveadm_mailbox_list_iter *iter); #endif dovecot-2.3.21.1/src/doveadm/doveadm-who.h0000644000000000000000000000140114656633576015102 00000000000000#ifndef DOVEADM_WHO_H #define DOVEADM_WHO_H struct who_line { const char *username; const char *service; struct ip_addr ip; pid_t pid; unsigned int refcount; }; struct who_filter { const char *username; struct ip_addr net_ip; unsigned int net_bits; }; struct who_context { const char *anvil_path; struct who_filter filter; pool_t pool; HASH_TABLE(struct who_user *, struct who_user *) users; }; typedef void who_callback_t(struct who_context *ctx, const struct who_line *line); int who_parse_args(struct who_context *ctx, const char *const *masks); void who_lookup(struct who_context *ctx, who_callback_t *callback); bool who_line_filter_match(const struct who_line *line, const struct who_filter *filter); #endif /* DOVEADM_WHO_H */ dovecot-2.3.21.1/src/doveadm/doveadm-print-flow.c0000644000000000000000000000502014656633576016402 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "ostream.h" #include "doveadm-print-private.h" struct doveadm_print_flow_header { const char *title; enum doveadm_print_header_flags flags; }; struct doveadm_print_flow_context { pool_t pool; ARRAY(struct doveadm_print_flow_header) headers; unsigned int header_idx; bool streaming:1; }; static struct doveadm_print_flow_context *ctx; static void doveadm_print_flow_header(const struct doveadm_print_header *hdr) { struct doveadm_print_flow_header *fhdr; fhdr = array_append_space(&ctx->headers); fhdr->title = p_strdup(ctx->pool, hdr->title); fhdr->flags = hdr->flags; } static void flow_next_hdr(void) { if (++ctx->header_idx < array_count(&ctx->headers)) o_stream_nsend(doveadm_print_ostream, " ", 1); else { ctx->header_idx = 0; o_stream_nsend(doveadm_print_ostream, "\n", 1); } } static void doveadm_print_flow_print_header(const struct doveadm_print_flow_header *hdr) { if ((hdr->flags & DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE) == 0) { o_stream_nsend_str(doveadm_print_ostream, hdr->title); o_stream_nsend(doveadm_print_ostream, "=", 1); } } static void doveadm_print_flow_print(const char *value) { const struct doveadm_print_flow_header *hdr = array_idx(&ctx->headers, ctx->header_idx); doveadm_print_flow_print_header(hdr); o_stream_nsend_str(doveadm_print_ostream, value); flow_next_hdr(); } static void doveadm_print_flow_print_stream(const unsigned char *value, size_t size) { const struct doveadm_print_flow_header *hdr = array_idx(&ctx->headers, ctx->header_idx); if (!ctx->streaming) { ctx->streaming = TRUE; doveadm_print_flow_print_header(hdr); } o_stream_nsend(doveadm_print_ostream, value, size); if (size == 0) { flow_next_hdr(); ctx->streaming = FALSE; } } static void doveadm_print_flow_init(void) { pool_t pool; pool = pool_alloconly_create("doveadm print flow", 1024); ctx = p_new(pool, struct doveadm_print_flow_context, 1); ctx->pool = pool; p_array_init(&ctx->headers, pool, 16); } static void doveadm_print_flow_flush(void) { if (ctx->header_idx != 0) { o_stream_nsend(doveadm_print_ostream, "\n", 1); ctx->header_idx = 0; } } static void doveadm_print_flow_deinit(void) { pool_unref(&ctx->pool); ctx = NULL; } struct doveadm_print_vfuncs doveadm_print_flow_vfuncs = { "flow", doveadm_print_flow_init, doveadm_print_flow_deinit, doveadm_print_flow_header, doveadm_print_flow_print, doveadm_print_flow_print_stream, doveadm_print_flow_flush }; dovecot-2.3.21.1/src/doveadm/doveadm-dump.h0000644000000000000000000000147314656633576015263 00000000000000#ifndef DOVEADM_DUMP_H #define DOVEADM_DUMP_H #include "doveadm.h" struct doveadm_cmd_dump { const char *name; bool (*test)(const char *path); void (*cmd)(const char *path, const char *const *args); }; extern struct doveadm_cmd_dump doveadm_cmd_dump_dbox; extern struct doveadm_cmd_dump doveadm_cmd_dump_index; extern struct doveadm_cmd_dump doveadm_cmd_dump_log; extern struct doveadm_cmd_dump doveadm_cmd_dump_mailboxlog; extern struct doveadm_cmd_dump doveadm_cmd_dump_thread; extern struct doveadm_cmd_dump doveadm_cmd_dump_zlib; extern struct doveadm_cmd_dump doveadm_cmd_dump_dcrypt_file; extern struct doveadm_cmd_dump doveadm_cmd_dump_dcrypt_key; void doveadm_dump_register(const struct doveadm_cmd_dump *dump); void print_dump_types(void); void doveadm_dump_init(void); void doveadm_dump_deinit(void); #endif dovecot-2.3.21.1/src/doveadm/doveadm-mail-import.c0000644000000000000000000002005014656633576016533 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "mail-storage.h" #include "mail-storage-service.h" #include "mail-namespace.h" #include "doveadm-mailbox-list-iter.h" #include "doveadm-mail-iter.h" #include "doveadm-mail.h" struct import_cmd_context { struct doveadm_mail_cmd_context ctx; const char *src_location; const char *src_username; struct mail_user *src_user; const char *dest_parent; bool subscribe; }; static const char * convert_vname_separators(const char *vname, char src_sep, char dest_sep) { string_t *str = t_str_new(128); for (; *vname != '\0'; vname++) { if (*vname == src_sep) str_append_c(str, dest_sep); else if (*vname == dest_sep) str_append_c(str, '_'); else str_append_c(str, *vname); } return str_c(str); } static int dest_mailbox_open_or_create(struct import_cmd_context *ctx, struct mail_user *user, const struct mailbox_info *info, struct mailbox **box_r) { struct mail_namespace *ns; struct mailbox *box; enum mail_error error; const char *name, *errstr; if (*ctx->dest_parent != '\0') { /* prefix destination mailbox name with given parent mailbox */ ns = mail_namespace_find(user->namespaces, ctx->dest_parent); } else { ns = mail_namespace_find(user->namespaces, info->vname); } name = convert_vname_separators(info->vname, mail_namespace_get_sep(info->ns), mail_namespace_get_sep(ns)); if (*ctx->dest_parent != '\0') { name = t_strdup_printf("%s%c%s", ctx->dest_parent, mail_namespace_get_sep(ns), name); } box = mailbox_alloc(ns->list, name, MAILBOX_FLAG_SAVEONLY); if (mailbox_create(box, NULL, FALSE) < 0) { errstr = mailbox_get_last_internal_error(box, &error); if (error != MAIL_ERROR_EXISTS) { i_error("Couldn't create mailbox %s: %s", name, errstr); doveadm_mail_failed_mailbox(&ctx->ctx, box); mailbox_free(&box); return -1; } } if (ctx->subscribe) { if (mailbox_set_subscribed(box, TRUE) < 0) { i_error("Couldn't subscribe to mailbox %s: %s", name, mailbox_get_last_internal_error(box, NULL)); } } if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ) < 0) { i_error("Syncing mailbox %s failed: %s", name, mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(&ctx->ctx, box); mailbox_free(&box); return -1; } *box_r = box; return 0; } static int cmd_import_box_contents(struct doveadm_mail_cmd_context *ctx, struct doveadm_mail_iter *iter, struct mail *src_mail, struct mailbox *dest_box) { struct mail_save_context *save_ctx; struct mailbox_transaction_context *dest_trans; const char *mailbox = mailbox_get_vname(dest_box); int ret = 0; dest_trans = mailbox_transaction_begin(dest_box, MAILBOX_TRANSACTION_FLAG_EXTERNAL | ctx->transaction_flags, __func__); do { if (doveadm_debug) { i_debug("import: box=%s uid=%u", mailbox, src_mail->uid); } save_ctx = mailbox_save_alloc(dest_trans); mailbox_save_copy_flags(save_ctx, src_mail); if (mailbox_copy(&save_ctx, src_mail) < 0) { i_error("Copying box=%s uid=%u failed: %s", mailbox, src_mail->uid, mailbox_get_last_internal_error(dest_box, NULL)); ret = -1; } } while (doveadm_mail_iter_next(iter, &src_mail)); if (mailbox_transaction_commit(&dest_trans) < 0) { i_error("Committing copied mails to %s failed: %s", mailbox, mailbox_get_last_internal_error(dest_box, NULL)); ret = -1; } return ret; } static int cmd_import_box(struct import_cmd_context *ctx, struct mail_user *dest_user, const struct mailbox_info *info, struct mail_search_args *search_args) { struct doveadm_mail_iter *iter; struct mailbox *box; struct mail *mail; int ret = 0; if (doveadm_mail_iter_init(&ctx->ctx, info, search_args, 0, NULL, DOVEADM_MAIL_ITER_FLAG_READONLY, &iter) < 0) return -1; if (doveadm_mail_iter_next(iter, &mail)) { /* at least one mail matches in this mailbox */ if (dest_mailbox_open_or_create(ctx, dest_user, info, &box) < 0) ret = -1; else { if (cmd_import_box_contents(&ctx->ctx, iter, mail, box) < 0) { doveadm_mail_failed_mailbox(&ctx->ctx, mail->box); ret = -1; } mailbox_free(&box); } } if (doveadm_mail_iter_deinit_sync(&iter) < 0) ret = -1; return ret; } static void cmd_import_init_source_user(struct import_cmd_context *ctx, struct mail_user *dest_user) { struct mail_storage_service_input input; struct mail_storage_service_user *service_user; struct mail_user *user; const char *error; /* create a user for accessing the source storage */ i_zero(&input); input.module = "mail"; input.username = ctx->src_username != NULL ? ctx->src_username : dest_user->username; mail_storage_service_io_deactivate_user(ctx->ctx.cur_service_user); input.flags_override_add = MAIL_STORAGE_SERVICE_FLAG_NO_NAMESPACES | MAIL_STORAGE_SERVICE_FLAG_NO_RESTRICT_ACCESS; if (mail_storage_service_lookup_next(ctx->ctx.storage_service, &input, &service_user, &user, &error) < 0) i_fatal("Import user initialization failed: %s", error); if (mail_namespaces_init_location(user, ctx->src_location, &error) < 0) i_fatal("Import namespace initialization failed: %s", error); ctx->src_user = user; mail_storage_service_io_deactivate_user(service_user); mail_storage_service_user_unref(&service_user); mail_storage_service_io_activate_user(ctx->ctx.cur_service_user); } static int cmd_import_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct import_cmd_context *ctx = (struct import_cmd_context *)_ctx; const enum mailbox_list_iter_flags iter_flags = MAILBOX_LIST_ITER_NO_AUTO_BOXES | MAILBOX_LIST_ITER_RETURN_NO_FLAGS; struct doveadm_mailbox_list_iter *iter; const struct mailbox_info *info; int ret = 0; if (ctx->src_user == NULL) cmd_import_init_source_user(ctx, user); iter = doveadm_mailbox_list_iter_init(_ctx, ctx->src_user, _ctx->search_args, iter_flags); while ((info = doveadm_mailbox_list_iter_next(iter)) != NULL) T_BEGIN { if (cmd_import_box(ctx, user, info, _ctx->search_args) < 0) ret = -1; } T_END; if (doveadm_mailbox_list_iter_deinit(&iter) < 0) ret = -1; return ret; } static void cmd_import_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct import_cmd_context *ctx = (struct import_cmd_context *)_ctx; if (str_array_length(args) < 3) doveadm_mail_help_name("import"); ctx->src_location = p_strdup(_ctx->pool, args[0]); ctx->dest_parent = p_strdup(_ctx->pool, args[1]); ctx->ctx.search_args = doveadm_mail_build_search_args(args+2); } static void cmd_import_deinit(struct doveadm_mail_cmd_context *_ctx) { struct import_cmd_context *ctx = (struct import_cmd_context *)_ctx; if (ctx->src_user != NULL) mail_user_deinit(&ctx->src_user); } static bool cmd_import_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct import_cmd_context *ctx = (struct import_cmd_context *)_ctx; switch (c) { case 'U': ctx->src_username = p_strdup(_ctx->pool, optarg); break; case 's': ctx->subscribe = TRUE; break; default: return FALSE; } return TRUE; } static struct doveadm_mail_cmd_context *cmd_import_alloc(void) { struct import_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct import_cmd_context); ctx->ctx.getopt_args = "s"; ctx->ctx.v.parse_arg = cmd_import_parse_arg; ctx->ctx.v.init = cmd_import_init; ctx->ctx.v.deinit = cmd_import_deinit; ctx->ctx.v.run = cmd_import_run; return &ctx->ctx; } struct doveadm_cmd_ver2 doveadm_cmd_import_ver2 = { .name = "import", .mail_cmd = cmd_import_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-U source-user] [-s] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('U', "source-user", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('s', "subscribe", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "source-location", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "dest-parent-mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-who.c0000644000000000000000000002166314656633576015111 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "net.h" #include "istream.h" #include "wildcard-match.h" #include "hash.h" #include "str.h" #include "strescape.h" #include "doveadm.h" #include "doveadm-print.h" #include "doveadm-who.h" #include #include struct who_user { const char *username; const char *service; ARRAY(struct ip_addr) ips; ARRAY(pid_t) pids; unsigned int connection_count; }; static void who_user_ip(const struct who_user *user, struct ip_addr *ip_r) { if (array_count(&user->ips) == 0) i_zero(ip_r); else { const struct ip_addr *ip = array_front(&user->ips); *ip_r = *ip; } } static unsigned int who_user_hash(const struct who_user *user) { struct ip_addr ip; unsigned int hash = str_hash(user->service); if (user->username[0] != '\0') hash += str_hash(user->username); else { who_user_ip(user, &ip); hash += net_ip_hash(&ip); } return hash; } static int who_user_cmp(const struct who_user *user1, const struct who_user *user2) { if (strcmp(user1->username, user2->username) != 0) return 1; if (strcmp(user1->service, user2->service) != 0) return 1; if (user1->username[0] == '\0') { /* tracking only IP addresses, not usernames */ struct ip_addr ip1, ip2; who_user_ip(user1, &ip1); who_user_ip(user2, &ip2); return net_ip_cmp(&ip1, &ip2); } return 0; } static bool who_user_has_ip(const struct who_user *user, const struct ip_addr *ip) { const struct ip_addr *ex_ip; array_foreach(&user->ips, ex_ip) { if (net_ip_compare(ex_ip, ip)) return TRUE; } return FALSE; } static int who_parse_line(const char *line, struct who_line *line_r) { const char *const *args = t_strsplit_tabescaped(line); const char *ident = args[0]; const char *pid_str = args[1]; const char *refcount_str = args[2]; const char *p, *ip_str; i_zero(line_r); /* ident = service/ip/username (imap, pop3) or service/username (lmtp) */ p = strchr(ident, '/'); if (p == NULL) return -1; if (str_to_pid(pid_str, &line_r->pid) < 0) return -1; line_r->service = t_strdup_until(ident, p++); line_r->username = strchr(p, '/'); if (line_r->username == NULL) { /* no IP */ line_r->username = p; } else { ip_str = t_strdup_until(p, line_r->username++); (void)net_addr2ip(ip_str, &line_r->ip); } if (str_to_uint(refcount_str, &line_r->refcount) < 0) return -1; return 0; } static bool who_user_has_pid(struct who_user *user, pid_t pid) { pid_t ex_pid; array_foreach_elem(&user->pids, ex_pid) { if (ex_pid == pid) return TRUE; } return FALSE; } static void who_aggregate_line(struct who_context *ctx, const struct who_line *line) { struct who_user *user, lookup_user; lookup_user.username = line->username; lookup_user.service = line->service; user = hash_table_lookup(ctx->users, &lookup_user); if (user == NULL) { user = p_new(ctx->pool, struct who_user, 1); user->username = p_strdup(ctx->pool, line->username); user->service = p_strdup(ctx->pool, line->service); p_array_init(&user->ips, ctx->pool, 3); p_array_init(&user->pids, ctx->pool, 8); hash_table_insert(ctx->users, user, user); } user->connection_count += line->refcount; if (line->ip.family != 0 && !who_user_has_ip(user, &line->ip)) array_push_back(&user->ips, &line->ip); if (!who_user_has_pid(user, line->pid)) array_push_back(&user->pids, &line->pid); } int who_parse_args(struct who_context *ctx, const char *const *masks) { struct ip_addr net_ip; unsigned int i, net_bits; for (i = 0; masks[i] != NULL; i++) { if (!str_is_numeric(masks[i], '\0') && net_parse_range(masks[i], &net_ip, &net_bits) == 0) { if (ctx->filter.net_bits != 0) { i_error("Multiple network masks not supported"); doveadm_exit_code = EX_USAGE; return -1; } ctx->filter.net_ip = net_ip; ctx->filter.net_bits = net_bits; } else { if (ctx->filter.username != NULL) { i_error("Multiple username masks not supported"); doveadm_exit_code = EX_USAGE; return -1; } ctx->filter.username = masks[i]; } } return 0; } void who_lookup(struct who_context *ctx, who_callback_t *callback) { #define ANVIL_HANDSHAKE "VERSION\tanvil\t1\t0\n" #define ANVIL_CMD ANVIL_HANDSHAKE"CONNECT-DUMP\n" struct istream *input; const char *line; int fd; fd = doveadm_connect(ctx->anvil_path); net_set_nonblock(fd, FALSE); if (write(fd, ANVIL_CMD, strlen(ANVIL_CMD)) < 0) i_fatal("write(%s) failed: %m", ctx->anvil_path); input = i_stream_create_fd_autoclose(&fd, SIZE_MAX); while ((line = i_stream_read_next_line(input)) != NULL) { if (*line == '\0') break; T_BEGIN { struct who_line who_line; if (who_parse_line(line, &who_line) < 0) i_error("Invalid input: %s", line); else callback(ctx, &who_line); } T_END; } if (input->stream_errno != 0) { i_fatal("read(%s) failed: %s", ctx->anvil_path, i_stream_get_error(input)); } i_stream_destroy(&input); } static bool who_user_filter_match(const struct who_user *user, const struct who_filter *filter) { if (filter->username != NULL) { if (!wildcard_match_icase(user->username, filter->username)) return FALSE; } if (filter->net_bits > 0) { const struct ip_addr *ip; bool ret = FALSE; array_foreach(&user->ips, ip) { if (net_is_in_network(ip, &filter->net_ip, filter->net_bits)) { ret = TRUE; break; } } if (!ret) return FALSE; } return TRUE; } static void who_print_user(const struct who_user *user) { const struct ip_addr *ip; pid_t pid; string_t *str = t_str_new(256); doveadm_print(user->username); doveadm_print(dec2str(user->connection_count)); doveadm_print(user->service); str_append_c(str, '('); array_foreach_elem(&user->pids, pid) str_printfa(str, "%ld ", (long)pid); if (str_len(str) > 1) str_truncate(str, str_len(str)-1); str_append_c(str, ')'); doveadm_print(str_c(str)); str_truncate(str, 0); str_append_c(str, '('); array_foreach(&user->ips, ip) str_printfa(str, "%s ", net_ip2addr(ip)); if (str_len(str) > 1) str_truncate(str, str_len(str)-1); str_append_c(str, ')'); doveadm_print(str_c(str)); doveadm_print_flush(); } static void who_print(struct who_context *ctx) { struct hash_iterate_context *iter; struct who_user *user; doveadm_print_header("username", "username", 0); doveadm_print_header("connections", "#", DOVEADM_PRINT_HEADER_FLAG_RIGHT_JUSTIFY); doveadm_print_header("service", "proto", 0); doveadm_print_header("pids", "(pids)", 0); doveadm_print_header("ips", "(ips)", 0); iter = hash_table_iterate_init(ctx->users); while (hash_table_iterate(iter, ctx->users, &user, &user)) { if (who_user_filter_match(user, &ctx->filter)) T_BEGIN { who_print_user(user); } T_END; } hash_table_iterate_deinit(&iter); } bool who_line_filter_match(const struct who_line *line, const struct who_filter *filter) { if (filter->username != NULL) { if (!wildcard_match_icase(line->username, filter->username)) return FALSE; } if (filter->net_bits > 0) { if (!net_is_in_network(&line->ip, &filter->net_ip, filter->net_bits)) return FALSE; } return TRUE; } static void who_print_line(struct who_context *ctx, const struct who_line *line) { unsigned int i; if (!who_line_filter_match(line, &ctx->filter)) return; for (i = 0; i < line->refcount; i++) T_BEGIN { doveadm_print(line->username); doveadm_print(line->service); doveadm_print(dec2str(line->pid)); doveadm_print(net_ip2addr(&line->ip)); } T_END; } static void cmd_who(struct doveadm_cmd_context *cctx) { const char *const *masks; struct who_context ctx; bool separate_connections = FALSE; i_zero(&ctx); if (!doveadm_cmd_param_str(cctx, "socket-path", &(ctx.anvil_path))) ctx.anvil_path = t_strconcat(doveadm_settings->base_dir, "/anvil", NULL); (void)doveadm_cmd_param_bool(cctx, "separate-connections", &separate_connections); ctx.pool = pool_alloconly_create("who users", 10240); hash_table_create(&ctx.users, ctx.pool, 0, who_user_hash, who_user_cmp); if (doveadm_cmd_param_array(cctx, "mask", &masks)) { if (who_parse_args(&ctx, masks) != 0) { hash_table_destroy(&ctx.users); pool_unref(&ctx.pool); return; } } doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); if (!separate_connections) { who_lookup(&ctx, who_aggregate_line); who_print(&ctx); } else { doveadm_print_header("username", "username", DOVEADM_PRINT_HEADER_FLAG_EXPAND); doveadm_print_header("service", "proto", 0); doveadm_print_header_simple("pid"); doveadm_print_header_simple("ip"); who_lookup(&ctx, who_print_line); } hash_table_destroy(&ctx.users); pool_unref(&ctx.pool); } struct doveadm_cmd_ver2 doveadm_cmd_who_ver2 = { .name = "who", .cmd = cmd_who, .usage = "[-a ] [-1] [] []", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a',"socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('1',"separate-connections", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0',"mask", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-log.c0000644000000000000000000002351214656633576015070 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "istream.h" #include "hash.h" #include "str.h" #include "strescape.h" #include "time-util.h" #include "master-service-private.h" #include "master-service-settings.h" #include "doveadm.h" #include "doveadm-print.h" #include #include #include #include #include #include #define LAST_LOG_TYPE LOG_TYPE_PANIC #define TEST_LOG_MSG_PREFIX "This is Dovecot's " #define LOG_ERRORS_FNAME "log-errors" #define LOG_TIMESTAMP_FORMAT "%b %d %H:%M:%S" static void ATTR_NULL(2) cmd_log_test(struct doveadm_cmd_context *cctx ATTR_UNUSED) { struct failure_context ctx; unsigned int i; master_service->log_initialized = FALSE; master_service->flags |= MASTER_SERVICE_FLAG_DONT_LOG_TO_STDERR; master_service_init_log(master_service); i_zero(&ctx); for (i = 0; i < LAST_LOG_TYPE; i++) { const char *prefix = failure_log_type_prefixes[i]; /* add timestamp so that syslog won't just write "repeated message" text */ ctx.type = i; i_log_type(&ctx, TEST_LOG_MSG_PREFIX"%s log (%u)", t_str_lcase(t_strcut(prefix, ':')), (unsigned int)ioloop_time); } } static void cmd_log_reopen(struct doveadm_cmd_context *cctx ATTR_UNUSED) { doveadm_master_send_signal(SIGUSR1); } struct log_find_file { const char *path; uoff_t size; /* 1 << enum log_type */ unsigned int mask; }; struct log_find_context { pool_t pool; HASH_TABLE(char *, struct log_find_file *) files; }; static void cmd_log_find_add(struct log_find_context *ctx, const char *path, enum log_type type) { struct log_find_file *file; char *key; file = hash_table_lookup(ctx->files, path); if (file == NULL) { file = p_new(ctx->pool, struct log_find_file, 1); file->path = key = p_strdup(ctx->pool, path); hash_table_insert(ctx->files, key, file); } file->mask |= 1 << type; } static void cmd_log_find_syslog_files(struct log_find_context *ctx, const char *path) { struct log_find_file *file; DIR *dir; struct dirent *d; struct stat st; char *key; string_t *full_path; size_t dir_len; dir = opendir(path); if (dir == NULL) { i_error("opendir(%s) failed: %m", path); return; } full_path = t_str_new(256); str_append(full_path, path); str_append_c(full_path, '/'); dir_len = str_len(full_path); while ((d = readdir(dir)) != NULL) { if (d->d_name[0] == '.') continue; str_truncate(full_path, dir_len); str_append(full_path, d->d_name); if (stat(str_c(full_path), &st) < 0) continue; if (S_ISDIR(st.st_mode)) { /* recursively go through all subdirectories */ cmd_log_find_syslog_files(ctx, str_c(full_path)); } else if (hash_table_lookup(ctx->files, str_c(full_path)) == NULL) { file = p_new(ctx->pool, struct log_find_file, 1); file->size = st.st_size; file->path = key = p_strdup(ctx->pool, str_c(full_path)); hash_table_insert(ctx->files, key, file); } } (void)closedir(dir); } static bool log_type_find(const char *str, enum log_type *type_r) { unsigned int i; size_t len = strlen(str); for (i = 0; i < LAST_LOG_TYPE; i++) { if (strncasecmp(str, failure_log_type_prefixes[i], len) == 0 && failure_log_type_prefixes[i][len] == ':') { *type_r = i; return TRUE; } } return FALSE; } static void cmd_log_find_syslog_file_messages(struct log_find_file *file) { struct istream *input; const char *line, *p; enum log_type type; int fd; fd = open(file->path, O_RDONLY); if (fd == -1) return; input = i_stream_create_fd_autoclose(&fd, 1024); i_stream_seek(input, file->size); while ((line = i_stream_read_next_line(input)) != NULL) { p = strstr(line, TEST_LOG_MSG_PREFIX); if (p == NULL) continue; p += strlen(TEST_LOG_MSG_PREFIX); /* log */ T_BEGIN { if (log_type_find(t_strcut(p, ' '), &type)) file->mask |= 1 << type; } T_END; } i_stream_destroy(&input); } static void cmd_log_find_syslog_messages(struct log_find_context *ctx) { struct hash_iterate_context *iter; struct stat st; char *key; struct log_find_file *file; iter = hash_table_iterate_init(ctx->files); while (hash_table_iterate(iter, ctx->files, &key, &file)) { if (stat(file->path, &st) < 0 || (uoff_t)st.st_size <= file->size) continue; cmd_log_find_syslog_file_messages(file); } hash_table_iterate_deinit(&iter); } static void cmd_log_find_syslog(struct log_find_context *ctx, struct doveadm_cmd_context *cctx) { const char *log_dir; struct stat st; if (doveadm_cmd_param_str(cctx, "log-dir", &log_dir)) ; else if (stat("/var/log", &st) == 0 && S_ISDIR(st.st_mode)) log_dir = "/var/log"; else if (stat("/var/adm", &st) == 0 && S_ISDIR(st.st_mode)) log_dir = "/var/adm"; else return; printf("Looking for log files from %s\n", log_dir); cmd_log_find_syslog_files(ctx, log_dir); cmd_log_test(cctx); /* give syslog some time to write the messages to files */ sleep(1); cmd_log_find_syslog_messages(ctx); } static void cmd_log_find(struct doveadm_cmd_context *cctx) { const struct master_service_settings *set; const char *log_file_path; struct log_find_context ctx; unsigned int i; i_zero(&ctx); ctx.pool = pool_alloconly_create("log file", 1024*32); hash_table_create(&ctx.files, ctx.pool, 0, str_hash, strcmp); /* first get the paths that we know are used */ set = master_service_settings_get(master_service); log_file_path = set->log_path; if (strcmp(log_file_path, "syslog") == 0) log_file_path = ""; if (*log_file_path != '\0') { cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_WARNING); cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_ERROR); cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_FATAL); } if (strcmp(set->info_log_path, "syslog") != 0) { if (*set->info_log_path != '\0') log_file_path = set->info_log_path; if (*log_file_path != '\0') cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_INFO); } if (strcmp(set->debug_log_path, "syslog") != 0) { if (*set->debug_log_path != '\0') log_file_path = set->debug_log_path; if (*log_file_path != '\0') cmd_log_find_add(&ctx, log_file_path, LOG_TYPE_DEBUG); } if (*set->log_path == '\0' || strcmp(set->log_path, "syslog") == 0 || strcmp(set->info_log_path, "syslog") == 0 || strcmp(set->debug_log_path, "syslog") == 0) { /* at least some logs were logged via syslog */ cmd_log_find_syslog(&ctx, cctx); } /* print them */ for (i = 0; i < LAST_LOG_TYPE; i++) { struct hash_iterate_context *iter; char *key; struct log_find_file *file; bool found = FALSE; iter = hash_table_iterate_init(ctx.files); while (hash_table_iterate(iter, ctx.files, &key, &file)) { if ((file->mask & (1 << i)) != 0) { printf("%s%s\n", failure_log_type_prefixes[i], file->path); found = TRUE; } } hash_table_iterate_deinit(&iter); if (!found) printf("%sNot found\n", failure_log_type_prefixes[i]); } hash_table_destroy(&ctx.files); pool_unref(&ctx.pool); } static const char *t_cmd_log_error_trim(const char *orig) { size_t pos; /* Trim whitespace from suffix and remove ':' if it exists */ for (pos = strlen(orig); pos > 0; pos--) { if (orig[pos-1] != ' ') { if (orig[pos-1] == ':') pos--; break; } } return orig[pos] == '\0' ? orig : t_strndup(orig, pos); } static void cmd_log_error_write(const char *const *args, time_t min_timestamp) { /* */ const char *type_prefix = "?"; unsigned int type; time_t t; /* find type's prefix */ for (type = 0; type < LOG_TYPE_COUNT; type++) { if (strcmp(args[0], failure_log_type_names[type]) == 0) { type_prefix = failure_log_type_prefixes[type]; break; } } if (str_to_time(args[1], &t) < 0) { i_error("Invalid timestamp: %s", args[1]); t = 0; } if (t >= min_timestamp) { doveadm_print(t_strflocaltime(LOG_TIMESTAMP_FORMAT, t)); doveadm_print(t_cmd_log_error_trim(args[2])); doveadm_print(t_cmd_log_error_trim(type_prefix)); doveadm_print(args[3]); } } static void cmd_log_errors(struct doveadm_cmd_context *cctx) { struct istream *input; const char *path, *line, *const *args; time_t min_timestamp = 0; int64_t since_int64; int fd; if (doveadm_cmd_param_int64(cctx, "since", &since_int64)) min_timestamp = since_int64; path = t_strconcat(doveadm_settings->base_dir, "/"LOG_ERRORS_FNAME, NULL); fd = net_connect_unix(path); if (fd == -1) i_fatal("net_connect_unix(%s) failed: %m", path); net_set_nonblock(fd, FALSE); input = i_stream_create_fd_autoclose(&fd, SIZE_MAX); doveadm_print_init(DOVEADM_PRINT_TYPE_FORMATTED); doveadm_print_formatted_set_format("%{timestamp} %{type}: %{prefix}: %{text}\n"); doveadm_print_header_simple("timestamp"); doveadm_print_header_simple("prefix"); doveadm_print_header_simple("type"); doveadm_print_header_simple("text"); while ((line = i_stream_read_next_line(input)) != NULL) T_BEGIN { args = t_strsplit_tabescaped(line); if (str_array_length(args) == 4) cmd_log_error_write(args, min_timestamp); else { i_error("Invalid input from log: %s", line); doveadm_exit_code = EX_PROTOCOL; } } T_END; i_stream_destroy(&input); } struct doveadm_cmd_ver2 doveadm_cmd_log[] = { { .name = "log test", .cmd = cmd_log_test, .usage = "", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAMS_END }, { .name = "log reopen", .cmd = cmd_log_reopen, .usage = "", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAMS_END }, { .name = "log find", .cmd = cmd_log_find, .usage = "[]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', "log-dir", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "log errors", .usage = "[-s ]", .cmd = cmd_log_errors, DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('s', "since", CMD_PARAM_INT64, 0) DOVEADM_CMD_PARAMS_END } }; void doveadm_register_log_commands(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(doveadm_cmd_log); i++) doveadm_cmd_register_ver2(&doveadm_cmd_log[i]); } dovecot-2.3.21.1/src/doveadm/client-connection-private.h0000644000000000000000000000117214656633576017760 00000000000000#ifndef CLIENT_CONNECTION_PRIVATE_H #define CLIENT_CONNECTION_PRIVATE_H #include "client-connection.h" bool doveadm_client_is_allowed_command(const struct doveadm_settings *set, const char *cmd_name); int client_connection_init(struct client_connection *conn, enum doveadm_client_type type, pool_t pool, int fd); void client_connection_destroy(struct client_connection **_conn); void client_connection_set_proctitle(struct client_connection *conn, const char *text); void doveadm_http_server_init(void); void doveadm_http_server_deinit(void); void doveadm_server_init(void); void doveadm_server_deinit(void); #endif dovecot-2.3.21.1/src/doveadm/doveadm-mail-flags.c0000644000000000000000000001222114656633576016316 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "imap-util.h" #include "mail-storage.h" #include "doveadm-mailbox-list-iter.h" #include "doveadm-mail-iter.h" #include "doveadm-mail.h" #include struct flags_cmd_context { struct doveadm_mail_cmd_context ctx; enum modify_type modify_type; enum mail_flags flags; const char *const *keywords; }; static int cmd_flags_run_box(struct flags_cmd_context *ctx, const struct mailbox_info *info) { struct doveadm_mail_iter *iter; struct mailbox *box; struct mail *mail; struct mail_keywords *kw = NULL; if (doveadm_mail_iter_init(&ctx->ctx, info, ctx->ctx.search_args, 0, NULL, 0, &iter) < 0) return -1; box = doveadm_mail_iter_get_mailbox(iter); if (ctx->keywords != NULL) { if (mailbox_keywords_create(box, ctx->keywords, &kw) < 0) { i_error("Invalid keywords: %s", mailbox_get_last_internal_error(box, NULL)); (void)doveadm_mail_iter_deinit(&iter); ctx->ctx.exit_code = DOVEADM_EX_NOTPOSSIBLE; return -1; } } while (doveadm_mail_iter_next(iter, &mail)) { mail_update_flags(mail, ctx->modify_type, ctx->flags); if (kw != NULL) mail_update_keywords(mail, ctx->modify_type, kw); } if (kw != NULL) mailbox_keywords_unref(&kw); return doveadm_mail_iter_deinit_sync(&iter); } static int cmd_flags_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct flags_cmd_context *ctx = (struct flags_cmd_context *)_ctx; const enum mailbox_list_iter_flags iter_flags = MAILBOX_LIST_ITER_NO_AUTO_BOXES | MAILBOX_LIST_ITER_RETURN_NO_FLAGS; struct doveadm_mailbox_list_iter *iter; const struct mailbox_info *info; int ret = 0; iter = doveadm_mailbox_list_iter_init(_ctx, user, _ctx->search_args, iter_flags); while ((info = doveadm_mailbox_list_iter_next(iter)) != NULL) T_BEGIN { if (cmd_flags_run_box(ctx, info) < 0) ret = -1; } T_END; if (doveadm_mailbox_list_iter_deinit(&iter) < 0) ret = -1; return ret; } static void cmd_flags_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct flags_cmd_context *ctx = (struct flags_cmd_context *)_ctx; const char *const *tmp; enum mail_flags flag; ARRAY_TYPE(const_string) keywords; if (args[0] == NULL || args[1] == NULL) { switch (ctx->modify_type) { case MODIFY_ADD: doveadm_mail_help_name("flags add"); case MODIFY_REMOVE: doveadm_mail_help_name("flags remove"); case MODIFY_REPLACE: doveadm_mail_help_name("flags replace"); } i_unreached(); } p_array_init(&keywords, _ctx->pool, 8); for (tmp = t_strsplit(args[0], " "); *tmp != NULL; tmp++) { const char *str = *tmp; if (str[0] == '\\') { flag = imap_parse_system_flag(str); if (flag == 0) i_fatal("Invalid system flag: %s", str); ctx->flags |= flag; } else { str = p_strdup(_ctx->pool, str); array_push_back(&keywords, &str); } } if (array_count(&keywords) > 0 || ctx->modify_type == MODIFY_REPLACE) { array_append_zero(&keywords); ctx->keywords = array_front(&keywords); } _ctx->search_args = doveadm_mail_build_search_args(args+1); } static struct doveadm_mail_cmd_context * cmd_flag_alloc(enum modify_type modify_type) { struct flags_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct flags_cmd_context); ctx->modify_type = modify_type; ctx->ctx.v.init = cmd_flags_init; ctx->ctx.v.run = cmd_flags_run; return &ctx->ctx; } static struct doveadm_mail_cmd_context *cmd_flags_add_alloc(void) { return cmd_flag_alloc(MODIFY_ADD); } static struct doveadm_mail_cmd_context *cmd_flags_remove_alloc(void) { return cmd_flag_alloc(MODIFY_REMOVE); } static struct doveadm_mail_cmd_context *cmd_flags_replace_alloc(void) { return cmd_flag_alloc(MODIFY_REPLACE); } struct doveadm_cmd_ver2 doveadm_cmd_flags_add_ver2 = { .name = "flags add", .mail_cmd = cmd_flags_add_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX " ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('\0', "flag", CMD_PARAM_ARRAY, 0) DOVEADM_CMD_PARAM('\0', "flagstr", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL|CMD_PARAM_FLAG_DO_NOT_EXPOSE) DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_flags_remove_ver2 = { .name = "flags remove", .mail_cmd = cmd_flags_remove_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX " ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('\0', "flag", CMD_PARAM_ARRAY, 0) DOVEADM_CMD_PARAM('\0', "flagstr", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL|CMD_PARAM_FLAG_DO_NOT_EXPOSE) DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_flags_replace_ver2 = { .name = "flags replace", .mail_cmd = cmd_flags_replace_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX " ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('\0', "flag", CMD_PARAM_ARRAY, 0) DOVEADM_CMD_PARAM('\0', "flagstr", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL|CMD_PARAM_FLAG_DO_NOT_EXPOSE) DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-mail-server.c0000644000000000000000000002710414656633576016536 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "hash.h" #include "str.h" #include "strescape.h" #include "ioloop.h" #include "master-service.h" #include "iostream-ssl.h" #include "auth-master.h" #include "mail-storage.h" #include "mail-storage-service.h" #include "server-connection.h" #include "doveadm-settings.h" #include "doveadm-print.h" #include "doveadm-server.h" #include "doveadm-mail.h" #define DOVEADM_SERVER_CONNECTIONS_MAX 4 #define DOVEADM_SERVER_QUEUE_MAX 16 #define DOVEADM_MAIL_SERVER_FAILED() \ (internal_failure || master_service_is_killed(master_service)) struct doveadm_mail_server_cmd { struct server_connection *conn; char *username; }; static HASH_TABLE(char *, struct doveadm_server *) servers; static pool_t server_pool; static struct doveadm_mail_cmd_context *cmd_ctx; static bool internal_failure = FALSE; static void doveadm_mail_server_handle(struct server_connection *conn, const char *username); static struct doveadm_server * doveadm_server_get(struct doveadm_mail_cmd_context *ctx, const char *name) { struct doveadm_server *server; const char *p; char *dup_name; if (!hash_table_is_created(servers)) { server_pool = pool_alloconly_create("doveadm servers", 1024*16); hash_table_create(&servers, server_pool, 0, str_hash, strcmp); } server = hash_table_lookup(servers, name); if (server == NULL) { server = p_new(server_pool, struct doveadm_server, 1); server->name = dup_name = p_strdup(server_pool, name); p = strrchr(server->name, ':'); server->hostname = p == NULL ? server->name : p_strdup_until(server_pool, server->name, p); p_array_init(&server->connections, server_pool, ctx->set->doveadm_worker_count); p_array_init(&server->queue, server_pool, DOVEADM_SERVER_QUEUE_MAX); hash_table_insert(servers, dup_name, server); } return server; } static struct server_connection * doveadm_server_find_unused_conn(struct doveadm_server *server) { struct server_connection *conn; array_foreach_elem(&server->connections, conn) { if (server_connection_is_idle(conn)) return conn; } return NULL; } static bool doveadm_server_have_used_connections(struct doveadm_server *server) { struct server_connection *conn; array_foreach_elem(&server->connections, conn) { if (!server_connection_is_idle(conn)) return TRUE; } return FALSE; } static void doveadm_cmd_callback(int exit_code, const char *error, void *context) { struct doveadm_mail_server_cmd *servercmd = context; struct doveadm_server *server = server_connection_get_server(servercmd->conn); const char *username = t_strdup(servercmd->username); i_free(servercmd->username); i_free(servercmd); switch (exit_code) { case 0: break; case SERVER_EXIT_CODE_DISCONNECTED: i_error("%s: Command %s failed for %s: %s", server->name, cmd_ctx->cmd->name, username, error); internal_failure = TRUE; io_loop_stop(current_ioloop); return; case EX_NOUSER: i_error("%s: No such user: %s", server->name, username); if (cmd_ctx->exit_code == 0) cmd_ctx->exit_code = EX_NOUSER; break; default: if (cmd_ctx->exit_code == 0 || exit_code == EX_TEMPFAIL) cmd_ctx->exit_code = exit_code; break; } if (array_count(&server->queue) > 0) { struct server_connection *conn; char *const *usernamep = array_front(&server->queue); char *username = *usernamep; conn = doveadm_server_find_unused_conn(server); if (conn != NULL) { array_pop_front(&server->queue); doveadm_mail_server_handle(conn, username); i_free(username); } } io_loop_stop(current_ioloop); } static void doveadm_mail_server_handle(struct server_connection *conn, const char *username) { struct doveadm_mail_server_cmd *servercmd; string_t *cmd; unsigned int i; /* [] */ cmd = t_str_new(256); if (doveadm_debug) str_append_c(cmd, 'D'); else if (doveadm_verbose) str_append_c(cmd, 'v'); str_append_c(cmd, '\t'); str_append_tabescaped(cmd, username); str_append_c(cmd, '\t'); str_append_tabescaped(cmd, cmd_ctx->cmd->name); for (i = 0; cmd_ctx->full_args[i] != NULL; i++) { str_append_c(cmd, '\t'); str_append_tabescaped(cmd, cmd_ctx->full_args[i]); } str_append_c(cmd, '\n'); servercmd = i_new(struct doveadm_mail_server_cmd, 1); servercmd->conn = conn; servercmd->username = i_strdup(username); server_connection_cmd(conn, str_c(cmd), cmd_ctx->cmd_input, doveadm_cmd_callback, servercmd); } static void doveadm_server_flush_one(struct doveadm_server *server) { unsigned int count = array_count(&server->queue); do { io_loop_run(current_ioloop); } while (array_count(&server->queue) == count && doveadm_server_have_used_connections(server) && !DOVEADM_MAIL_SERVER_FAILED()); } static int doveadm_mail_server_user_get_host(struct doveadm_mail_cmd_context *ctx, const struct mail_storage_service_input *input, const char **user_r, const char **host_r, struct ip_addr *hostip_r, in_port_t *port_r, enum doveadm_proxy_ssl_flags *ssl_flags_r, const char **error_r) { struct auth_master_connection *auth_conn; struct auth_user_info info; pool_t pool; const char *auth_socket_path, *proxy_host, *proxy_hostip, *const *fields; unsigned int i; in_port_t proxy_port; bool proxying; int ret; *user_r = input->username; *host_r = ctx->set->doveadm_socket_path; *port_r = ctx->set->doveadm_port; if (ctx->set->doveadm_port == 0) return 0; if (strcmp(ctx->set->doveadm_ssl, "ssl") == 0) *ssl_flags_r |= PROXY_SSL_FLAG_YES; else if (strcmp(ctx->set->doveadm_ssl, "starttls") == 0) *ssl_flags_r |= PROXY_SSL_FLAG_YES | PROXY_SSL_FLAG_STARTTLS; /* make sure we have an auth connection */ mail_storage_service_init_settings(ctx->storage_service, input); i_zero(&info); info.service = master_service_get_name(master_service); info.local_ip = input->local_ip; info.remote_ip = input->remote_ip; info.local_port = input->local_port; info.remote_port = input->remote_port; pool = pool_alloconly_create("auth lookup", 1024); auth_conn = mail_storage_service_get_auth_conn(ctx->storage_service); auth_socket_path = auth_master_get_socket_path(auth_conn); ret = auth_master_pass_lookup(auth_conn, input->username, &info, pool, &fields); if (ret < 0) { *error_r = fields[0] != NULL ? t_strdup(fields[0]) : "passdb lookup failed"; *error_r = t_strdup_printf("%s: %s (to see if user is proxied, " "because doveadm_port is set)", auth_socket_path, *error_r); } else if (ret == 0) { /* user not found from passdb. it could be in userdb though, so just continue with the default host */ } else { proxy_host = NULL; proxy_hostip = NULL; proxying = FALSE; proxy_port = ctx->set->doveadm_port; for (i = 0; fields[i] != NULL; i++) { if (str_begins(fields[i], "proxy") && (fields[i][5] == '\0' || fields[i][5] == '=')) proxying = TRUE; else if (str_begins(fields[i], "host=")) proxy_host = fields[i]+5; else if (str_begins(fields[i], "hostip=")) proxy_hostip = fields[i]+7; else if (str_begins(fields[i], "user=")) *user_r = t_strdup(fields[i]+5); else if (str_begins(fields[i], "destuser=")) *user_r = t_strdup(fields[i]+9); else if (str_begins(fields[i], "port=")) { if (net_str2port(fields[i]+5, &proxy_port) < 0) proxy_port = 0; } else if (str_begins(fields[i], "ssl=")) { *ssl_flags_r |= PROXY_SSL_FLAG_YES; if (strcmp(fields[i]+4, "any-cert") == 0) *ssl_flags_r |= PROXY_SSL_FLAG_ANY_CERT; } else if (str_begins(fields[i], "starttls=")) { *ssl_flags_r |= PROXY_SSL_FLAG_YES | PROXY_SSL_FLAG_STARTTLS; if (strcmp(fields[i]+9, "any-cert") == 0) *ssl_flags_r |= PROXY_SSL_FLAG_ANY_CERT; } } if (proxy_hostip != NULL && net_addr2ip(proxy_hostip, hostip_r) < 0) { *error_r = t_strdup_printf("%s Invalid hostip value '%s'", auth_socket_path, proxy_hostip); ret = -1; } if (!proxying) ret = 0; else if (proxy_host == NULL) { *error_r = t_strdup_printf("%s: Proxy is missing destination host", auth_socket_path); if (strstr(auth_socket_path, "/auth-userdb") != NULL) { *error_r = t_strdup_printf( "%s (maybe set auth_socket_path=director-userdb)", *error_r); } ret = -1; } else { *port_r = proxy_port; *host_r = t_strdup_printf("%s:%u", proxy_host, proxy_port); } } pool_unref(&pool); return ret; } int doveadm_mail_server_user(struct doveadm_mail_cmd_context *ctx, const struct mail_storage_service_input *input, const char **error_r) { struct doveadm_server *server; struct server_connection *conn; const char *user, *host; struct ip_addr hostip; enum doveadm_proxy_ssl_flags ssl_flags = 0; char *username_dup; int ret; in_port_t port; i_assert(cmd_ctx == ctx || cmd_ctx == NULL); cmd_ctx = ctx; i_zero(&hostip); ret = doveadm_mail_server_user_get_host(ctx, input, &user, &host, &hostip, &port, &ssl_flags, error_r); if (ret < 0) return ret; if (ret == 0 && (ctx->set->doveadm_worker_count == 0 || doveadm_server)) { /* run it ourself */ return 0; } /* server sends the sticky headers for each row as well, so undo any sticks we might have added already */ doveadm_print_unstick_headers(); server = doveadm_server_get(ctx, host); server->ip = hostip; server->ssl_flags = ssl_flags; server->port = port; conn = doveadm_server_find_unused_conn(server); if (conn != NULL) doveadm_mail_server_handle(conn, user); else if (array_count(&server->connections) < I_MAX(ctx->set->doveadm_worker_count, 1)) { if (server_connection_create(server, &conn, error_r) < 0) { internal_failure = TRUE; return -1; } else { doveadm_mail_server_handle(conn, user); } } else { if (array_count(&server->queue) >= DOVEADM_SERVER_QUEUE_MAX) doveadm_server_flush_one(server); username_dup = i_strdup(user); array_push_back(&server->queue, &username_dup); } *error_r = "doveadm server failure"; return DOVEADM_MAIL_SERVER_FAILED() ? -1 : 1; } static struct doveadm_server *doveadm_server_find_used(void) { struct hash_iterate_context *iter; struct doveadm_server *ret = NULL; char *key; struct doveadm_server *server; iter = hash_table_iterate_init(servers); while (hash_table_iterate(iter, servers, &key, &server)) { if (doveadm_server_have_used_connections(server)) { ret = server; break; } } hash_table_iterate_deinit(&iter); return ret; } static void doveadm_servers_destroy_all_connections(void) { struct hash_iterate_context *iter; char *key; struct doveadm_server *server; iter = hash_table_iterate_init(servers); while (hash_table_iterate(iter, servers, &key, &server)) { while (array_count(&server->connections) > 0) { struct server_connection *const *connp, *conn; connp = array_front(&server->connections); conn = *connp; server_connection_destroy(&conn); } ssl_iostream_context_unref(&server->ssl_ctx); } hash_table_iterate_deinit(&iter); } void doveadm_mail_server_flush(void) { struct doveadm_server *server; if (!hash_table_is_created(servers)) { cmd_ctx = NULL; return; } while ((server = doveadm_server_find_used()) != NULL && !DOVEADM_MAIL_SERVER_FAILED()) doveadm_server_flush_one(server); doveadm_servers_destroy_all_connections(); if (master_service_is_killed(master_service)) i_error("Aborted"); if (DOVEADM_MAIL_SERVER_FAILED()) doveadm_mail_failed_error(cmd_ctx, MAIL_ERROR_TEMP); hash_table_destroy(&servers); pool_unref(&server_pool); cmd_ctx = NULL; } dovecot-2.3.21.1/src/doveadm/doveadm-director.c0000644000000000000000000007272314656633576016132 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "md5.h" #include "hash.h" #include "str.h" #include "strescape.h" #include "net.h" #include "istream.h" #include "write-full.h" #include "master-service.h" #include "auth-master.h" #include "mail-user-hash.h" #include "doveadm.h" #include "doveadm-print.h" #include #include #include struct director_context { const char *socket_path; const char *users_path; const char *tag; const char *user; const char *host; const char *ip; const char *port; const char *vhost_count; const char *passdb_field; struct istream *users_input; struct istream *input; bool explicit_socket_path; bool hash_map, user_map, force_flush; int64_t max_parallel; }; struct user_list { struct user_list *next; const char *name; }; HASH_TABLE_DEFINE_TYPE(user_list, void *, struct user_list *); static void director_cmd_help(const struct doveadm_cmd_ver2 *); static int director_get_host(const char *host, struct ip_addr **ips_r, unsigned int *ips_count_r) ATTR_WARN_UNUSED_RESULT; static void director_send(struct director_context *ctx, const char *data) { if (write_full(i_stream_get_fd(ctx->input), data, strlen(data)) < 0) i_fatal("write(%s) failed: %m", ctx->socket_path); } static void director_connect(struct director_context *ctx) { #define DIRECTOR_HANDSHAKE "VERSION\tdirector-doveadm\t1\t0\n" const char *line; int fd; fd = doveadm_connect(ctx->socket_path); net_set_nonblock(fd, FALSE); ctx->input = i_stream_create_fd_autoclose(&fd, SIZE_MAX); director_send(ctx, DIRECTOR_HANDSHAKE); alarm(5); line = i_stream_read_next_line(ctx->input); alarm(0); if (line == NULL) { if (ctx->input->stream_errno != 0) { i_fatal("read(%s) failed: %s", ctx->socket_path, i_stream_get_error(ctx->input)); } else if (ctx->input->eof) { i_fatal("%s disconnected", ctx->socket_path); } else { i_fatal("read(%s) timed out (is director configured?)", ctx->socket_path); } } if (!version_string_verify(line, "director-doveadm", 1)) { i_fatal_status(EX_PROTOCOL, "%s not a compatible director-doveadm socket", ctx->socket_path); } } static void director_disconnect(struct director_context *ctx) { if (ctx->input != NULL) { if (ctx->input->stream_errno != 0) { i_fatal("read(%s) failed: %s", ctx->socket_path, i_stream_get_error(ctx->input)); } i_stream_destroy(&ctx->input); } } static struct director_context * cmd_director_init(struct doveadm_cmd_context *cctx) { struct director_context *ctx; ctx = t_new(struct director_context, 1); if (!doveadm_cmd_param_str(cctx, "socket-path", &(ctx->socket_path))) ctx->socket_path = t_strconcat(doveadm_settings->base_dir, "/director-admin", NULL); else ctx->explicit_socket_path = TRUE; if (!doveadm_cmd_param_bool(cctx, "user-map", &(ctx->user_map))) ctx->user_map = FALSE; if (!doveadm_cmd_param_bool(cctx, "hash-map", &(ctx->hash_map))) ctx->hash_map = FALSE; if (!doveadm_cmd_param_bool(cctx, "force-flush", &(ctx->force_flush))) ctx->force_flush = FALSE; if (!doveadm_cmd_param_istream(cctx, "users-file", &(ctx->users_input))) ctx->users_input = NULL; if (!doveadm_cmd_param_str(cctx, "tag", &(ctx->tag))) ctx->tag = NULL; if (!doveadm_cmd_param_str(cctx, "user", &(ctx->user))) ctx->user = NULL; if (!doveadm_cmd_param_str(cctx, "host", &(ctx->host))) ctx->host = NULL; if (!doveadm_cmd_param_str(cctx, "ip", &(ctx->ip))) ctx->ip = NULL; if (!doveadm_cmd_param_str(cctx, "port", &(ctx->port))) ctx->port = NULL; if (!doveadm_cmd_param_str(cctx, "vhost-count", &(ctx->vhost_count))) ctx->vhost_count = NULL; if (!doveadm_cmd_param_str(cctx, "passdb-field", &(ctx->passdb_field))) ctx->passdb_field = NULL; if (!doveadm_cmd_param_int64(cctx, "max-parallel", &(ctx->max_parallel))) ctx->max_parallel = 0; if (!ctx->user_map) director_connect(ctx); return ctx; } static void director_disconnected(struct director_context *ctx) { i_assert(ctx->input->eof); if (ctx->input->stream_errno != 0) { i_error("read(%s) failed: %s", ctx->socket_path, i_stream_get_error(ctx->input)); } else { i_error("%s unexpectedly disconnected", ctx->socket_path); } doveadm_exit_code = EX_TEMPFAIL; } static void cmd_director_status_user(struct director_context *ctx) { const char *line, *const *args; time_t expires; director_send(ctx, t_strdup_printf("USER-LOOKUP\t%s\t%s\n", ctx->user, ctx->tag != NULL ? ctx->tag : "")); line = i_stream_read_next_line(ctx->input); if (line == NULL) { director_disconnected(ctx); return; } args = t_strsplit_tabescaped(line); if (str_array_length(args) != 4 || str_to_time(args[1], &expires) < 0) { i_error("Invalid reply from director"); doveadm_exit_code = EX_PROTOCOL; return; } doveadm_print_init(DOVEADM_PRINT_TYPE_FORMATTED); doveadm_print_header_simple("status"); doveadm_print_header_simple("expires"); doveadm_print_header_simple("hashed"); doveadm_print_header_simple("initial-config"); doveadm_print_formatted_set_format("Current: %{status} (expires %{expires})\n" \ "Hashed: %{hashed}\n" \ "Initial config: %{initial-config}\n"); if (args[0][0] != '\0') { doveadm_print(args[0]); doveadm_print(unixdate2str(expires)); } else { doveadm_print("n/a"); doveadm_print("-1"); } doveadm_print(args[2]); doveadm_print(args[3]); director_disconnect(ctx); } static void cmd_director_status(struct doveadm_cmd_context *cctx) { struct director_context *ctx; const char *line, *const *args; ctx = cmd_director_init(cctx); if (ctx->user != NULL) { cmd_director_status_user(ctx); return; } doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); doveadm_print_header_simple("mail server ip"); doveadm_print_header_simple("tag"); doveadm_print_header_simple("vhosts"); doveadm_print_header_simple("state"); doveadm_print_header("state-changed", "state changed", 0); doveadm_print_header_simple("users"); director_send(ctx, "HOST-LIST\n"); while ((line = i_stream_read_next_line(ctx->input)) != NULL) { if (*line == '\0') break; T_BEGIN { unsigned int arg_count; time_t ts; args = t_strsplit_tabescaped(line); arg_count = str_array_length(args); if (arg_count >= 6) { /* ip vhosts users tag updown updown-ts */ doveadm_print(args[0]); doveadm_print(args[3]); doveadm_print(args[1]); doveadm_print(args[4][0] == 'D' ? "down" : "up"); if (str_to_time(args[5], &ts) < 0 || ts <= 0) doveadm_print("-"); else doveadm_print(unixdate2str(ts)); doveadm_print(args[2]); } } T_END; } if (line == NULL) director_disconnected(ctx); director_disconnect(ctx); } static bool user_hash_expand(const char *username, unsigned int *hash_r) { const char *error; if (!mail_user_hash(username, doveadm_settings->director_username_hash, hash_r, &error)) { i_error("Failed to expand director_username_hash=%s: %s", doveadm_settings->director_username_hash, error); return FALSE; } return TRUE; } static void user_list_add(const char *username, pool_t pool, HASH_TABLE_TYPE(user_list) users) { struct user_list *user, *old_user; unsigned int user_hash; if (!user_hash_expand(username, &user_hash)) return; user = p_new(pool, struct user_list, 1); user->name = p_strdup(pool, username); old_user = hash_table_lookup(users, POINTER_CAST(user_hash)); if (old_user != NULL) user->next = old_user; hash_table_update(users, POINTER_CAST(user_hash), user); } static void ATTR_NULL(1) userdb_get_user_list(const char *auth_socket_path, pool_t pool, HASH_TABLE_TYPE(user_list) users) { struct auth_master_user_list_ctx *ctx; struct auth_master_connection *conn; const char *username; if (auth_socket_path == NULL) { auth_socket_path = t_strconcat(doveadm_settings->base_dir, "/auth-userdb", NULL); } conn = auth_master_init(auth_socket_path, 0); ctx = auth_master_user_list_init(conn, "", NULL); while ((username = auth_master_user_list_next(ctx)) != NULL) user_list_add(username, pool, users); if (auth_master_user_list_deinit(&ctx) < 0) { i_error("user listing failed"); doveadm_exit_code = EX_TEMPFAIL; } auth_master_deinit(&conn); } static void user_file_get_user_list(struct istream *input, pool_t pool, HASH_TABLE_TYPE(user_list) users) { const char *username; while ((username = i_stream_read_next_line(input)) != NULL) user_list_add(username, pool, users); } static int director_get_host(const char *host, struct ip_addr **ips_r, unsigned int *ips_count_r) { struct ip_addr ip; int ret = 0; if (net_addr2ip(host, &ip) == 0) { *ips_r = t_new(struct ip_addr, 1); **ips_r = ip; *ips_count_r = 1; } else { ret = net_gethostbyname(host, ips_r, ips_count_r); if (ret != 0) { i_error("gethostname(%s) failed: %s", host, net_gethosterror(ret)); doveadm_exit_code = EX_TEMPFAIL; return ret; } } return ret; } static bool ip_find(const struct ip_addr *ips, unsigned int ips_count, const struct ip_addr *match_ip) { unsigned int i; for (i = 0; i < ips_count; i++) { if (net_ip_compare(&ips[i], match_ip)) return TRUE; } return FALSE; } static void cmd_director_map(struct doveadm_cmd_context *cctx) { struct director_context *ctx; const char *line, *const *args; struct ip_addr *ips, user_ip; pool_t pool; HASH_TABLE_TYPE(user_list) users; struct user_list *user; unsigned int ips_count, user_hash; time_t expires; ctx = cmd_director_init(cctx); if ((ctx->hash_map || ctx->user_map) && ctx->host == NULL) { director_cmd_help(cctx->cmd); return; } if (ctx->user_map) { /* user -> hash mapping */ if (user_hash_expand(ctx->host, &user_hash)) { doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); doveadm_print_header("hash", "hash", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); doveadm_print(t_strdup_printf("%u", user_hash)); } director_disconnect(ctx); return; } if (ctx->host == NULL || ctx->hash_map) ips_count = 0; else if (director_get_host(ctx->host, &ips, &ips_count) != 0) { director_disconnect(ctx); return; } pool = pool_alloconly_create("director map users", 1024*128); hash_table_create_direct(&users, pool, 0); if (ctx->users_input == NULL) userdb_get_user_list(NULL, pool, users); else user_file_get_user_list(ctx->users_input, pool, users); if (ctx->hash_map) { /* hash -> usernames mapping */ if (str_to_uint(ctx->host, &user_hash) < 0) i_fatal("Invalid username hash: %s", ctx->host); doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); doveadm_print_header("user", "user", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); user = hash_table_lookup(users, POINTER_CAST(user_hash)); for (; user != NULL; user = user->next) doveadm_print(user->name); goto deinit; } doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); doveadm_print_header("user", "user", DOVEADM_PRINT_HEADER_FLAG_EXPAND); doveadm_print_header_simple("hash"); doveadm_print_header_simple("mail server ip"); doveadm_print_header_simple("expire time"); if (ips_count != 1) director_send(ctx, "USER-LIST\n"); else { director_send(ctx, t_strdup_printf( "USER-LIST\t%s\n", net_ip2addr(&ips[0]))); } while ((line = i_stream_read_next_line(ctx->input)) != NULL) { if (*line == '\0') break; T_BEGIN { args = t_strsplit_tabescaped(line); if (str_array_length(args) < 3 || str_to_uint(args[0], &user_hash) < 0 || str_to_time(args[1], &expires) < 0 || net_addr2ip(args[2], &user_ip) < 0) { i_error("Invalid USER-LIST reply: %s", line); doveadm_exit_code = EX_PROTOCOL; } else if (ips_count == 0 || ip_find(ips, ips_count, &user_ip)) { user = hash_table_lookup(users, POINTER_CAST(user_hash)); if (user == NULL) { doveadm_print(""); doveadm_print(args[0]); doveadm_print(args[2]); doveadm_print(unixdate2str(expires)); } for (; user != NULL; user = user->next) { doveadm_print(user->name); doveadm_print(args[0]); doveadm_print(args[2]); doveadm_print(unixdate2str(expires)); } } } T_END; } if (line == NULL) director_disconnected(ctx); deinit: director_disconnect(ctx); hash_table_destroy(&users); pool_unref(&pool); } static void cmd_director_add_or_update(struct doveadm_cmd_context *cctx, bool update) { const char *director_cmd = update ? "HOST-UPDATE" : "HOST-SET"; struct director_context *ctx; struct ip_addr *ips; unsigned int i, ips_count, vhost_count = UINT_MAX; const char *line, *host; string_t *cmd; ctx = cmd_director_init(cctx); if (ctx->tag != NULL && ctx->tag[0] == '\0') ctx->tag = NULL; if (ctx->host == NULL) { director_cmd_help(cctx->cmd); return; } if (ctx->vhost_count != NULL) { if (str_to_uint(ctx->vhost_count, &vhost_count) < 0) { director_cmd_help(cctx->cmd); return; } } else if (update) { director_cmd_help(cctx->cmd); return; } if (str_to_uint(ctx->host, &i) == 0) { /* host is a number. this would translate to an IP address, which is probably a mistake. */ i_error("Invalid host '%s'", ctx->host); director_cmd_help(cctx->cmd); return; } host = ctx->host; if (ctx->tag == NULL) { ctx->tag = strchr(ctx->host, '@'); if (ctx->tag != NULL) host = t_strdup_until(ctx->host, ctx->tag++); } if (director_get_host(host, &ips, &ips_count) != 0) { director_disconnect(ctx); return; } cmd = t_str_new(128); for (i = 0; i < ips_count; i++) { str_truncate(cmd, 0); str_printfa(cmd, "%s\t%s", director_cmd, net_ip2addr(&ips[i])); if (ctx->tag != NULL) str_printfa(cmd, "@%s", ctx->tag); if (vhost_count != UINT_MAX) str_printfa(cmd, "\t%u", vhost_count); str_append_c(cmd, '\n'); director_send(ctx, str_c(cmd)); } for (i = 0; i < ips_count; i++) { line = i_stream_read_next_line(ctx->input); if (line == NULL) director_disconnected(ctx); else if (strcmp(line, "OK") != 0) { i_error("%s: %s", net_ip2addr(&ips[i]), strcmp(line, "NOTFOUND") == 0 ? "doesn't exist" : line); doveadm_exit_code = EX_TEMPFAIL; } else if (doveadm_verbose) { i_info("%s: OK", net_ip2addr(&ips[i])); } } director_disconnect(ctx); } static void cmd_director_add(struct doveadm_cmd_context *cctx) { cmd_director_add_or_update(cctx, FALSE); } static void cmd_director_update(struct doveadm_cmd_context *cctx) { cmd_director_add_or_update(cctx, TRUE); } static void cmd_director_ipcmd(const char *cmd_name, const char *success_result, struct doveadm_cmd_context *cctx) { struct director_context *ctx; struct ip_addr *ips; unsigned int i, ips_count; const char *host, *line; ctx = cmd_director_init(cctx); host = ctx->host; if (host == NULL) { director_cmd_help(cctx->cmd); return; } if (director_get_host(host, &ips, &ips_count) != 0) { director_disconnect(ctx); return; } for (i = 0; i < ips_count; i++) { director_send(ctx, t_strdup_printf( "%s\t%s\n", cmd_name, net_ip2addr(&ips[i]))); } for (i = 0; i < ips_count; i++) { line = i_stream_read_next_line(ctx->input); if (line != NULL && strcmp(line, "NOTFOUND") == 0) { i_error("%s: doesn't exist", net_ip2addr(&ips[i])); if (doveadm_exit_code == 0) doveadm_exit_code = DOVEADM_EX_NOTFOUND; } else if (line == NULL) { director_disconnected(ctx); } else if (strcmp(line, "OK") != 0) { i_error("%s: %s", net_ip2addr(&ips[i]), line); doveadm_exit_code = EX_TEMPFAIL; } else if (doveadm_verbose) { i_info("%s: %s", net_ip2addr(&ips[i]), success_result); } } director_disconnect(ctx); } static void cmd_director_remove(struct doveadm_cmd_context *cctx) { cmd_director_ipcmd("HOST-REMOVE", "removed", cctx); } static void cmd_director_up(struct doveadm_cmd_context *cctx) { cmd_director_ipcmd("HOST-UP", "up", cctx); } static void cmd_director_down(struct doveadm_cmd_context *cctx) { cmd_director_ipcmd("HOST-DOWN", "down", cctx); } static void cmd_director_move(struct doveadm_cmd_context *cctx) { struct director_context *ctx; struct ip_addr *ips; unsigned int ips_count, user_hash; const char *line, *ip_str; ctx = cmd_director_init(cctx); if (ctx->user == NULL || ctx->host == NULL) { director_cmd_help(cctx->cmd); return; } if (!user_hash_expand(ctx->user, &user_hash) || director_get_host(ctx->host, &ips, &ips_count) != 0) { director_disconnect(ctx); return; } ip_str = net_ip2addr(&ips[0]); director_send(ctx, t_strdup_printf( "USER-MOVE\t%u\t%s\n", user_hash, ip_str)); line = i_stream_read_next_line(ctx->input); if (line == NULL) { director_disconnected(ctx); } else if (strcmp(line, "OK") == 0) { if (doveadm_verbose) i_info("User hash %u moved to %s", user_hash, ip_str); } else if (strcmp(line, "NOTFOUND") == 0) { i_error("Host '%s' doesn't exist", ip_str); doveadm_exit_code = DOVEADM_EX_NOTFOUND; } else if (strcmp(line, "TRYAGAIN") == 0) { i_error("User is already being moved, " "wait a while for it to be finished"); doveadm_exit_code = EX_TEMPFAIL; } else { i_error("failed: %s", line); doveadm_exit_code = EX_TEMPFAIL; } director_disconnect(ctx); } static void cmd_director_kick(struct doveadm_cmd_context *cctx) { struct director_context *ctx; const char *line; string_t *cmd = t_str_new(64); ctx = cmd_director_init(cctx); if (ctx->user == NULL) { director_cmd_help(cctx->cmd); return; } if (ctx->passdb_field == NULL) { str_append(cmd, "USER-KICK\t"); str_append_tabescaped(cmd, ctx->user); str_append_c(cmd, '\n'); } else { str_append(cmd, "USER-KICK-ALT\t"); str_append_tabescaped(cmd, ctx->passdb_field); str_append_c(cmd, '\t'); str_append_tabescaped(cmd, ctx->user); str_append_c(cmd, '\n'); } director_send(ctx, str_c(cmd)); line = i_stream_read_next_line(ctx->input); if (line == NULL) { director_disconnected(ctx); } else if (strcmp(line, "OK") == 0) { if (doveadm_verbose) i_info("User %s kicked", ctx->user); } else { i_error("failed: %s", line); doveadm_exit_code = EX_TEMPFAIL; } director_disconnect(ctx); } static void cmd_director_flush_all(struct director_context *ctx) { const char *line; if (ctx->force_flush) line = "HOST-FLUSH\n"; else if (ctx->max_parallel > 0) { line = t_strdup_printf("HOST-RESET-USERS\t\t%lld\n", (long long)ctx->max_parallel); } else { line = "HOST-RESET-USERS\n"; } director_send(ctx, line); line = i_stream_read_next_line(ctx->input); if (line == NULL) { director_disconnected(ctx); } else if (strcmp(line, "OK") != 0) { i_error("failed: %s", line); doveadm_exit_code = EX_TEMPFAIL; } else if (doveadm_verbose) i_info("flushed"); director_disconnect(ctx); } static void cmd_director_flush(struct doveadm_cmd_context *cctx) { struct director_context *ctx; struct ip_addr *ips; unsigned int i, ips_count; struct ip_addr ip; const char *line; string_t *cmd; ctx = cmd_director_init(cctx); if (ctx->host == NULL) { director_cmd_help(cctx->cmd); return; } if (strcmp(ctx->host, "all") == 0) { cmd_director_flush_all(ctx); return; } if (net_addr2ip(ctx->host, &ip) == 0) { ips = &ip; ips_count = 1; } else if (director_get_host(ctx->host, &ips, &ips_count) != 0) { director_disconnect(ctx); return; } cmd = t_str_new(64); for (i = 0; i < ips_count; i++) { ip = ips[i]; str_truncate(cmd, 0); if (ctx->force_flush) str_printfa(cmd, "HOST-FLUSH\t%s\n", net_ip2addr(&ip)); else { str_printfa(cmd, "HOST-RESET-USERS\t%s", net_ip2addr(&ip)); if (ctx->max_parallel > 0) { str_printfa(cmd, "\t%lld", (long long)ctx->max_parallel); } str_append_c(cmd, '\n'); } director_send(ctx, str_c(cmd)); } for (i = 0; i < ips_count; i++) { line = i_stream_read_next_line(ctx->input); if (line != NULL && strcmp(line, "NOTFOUND") == 0) { i_warning("%s: doesn't exist", net_ip2addr(&ips[i])); if (doveadm_exit_code == 0) doveadm_exit_code = DOVEADM_EX_NOTFOUND; } else if (line == NULL) { director_disconnected(ctx); } else if (strcmp(line, "OK") != 0) { i_warning("%s: %s", net_ip2addr(&ips[i]), line); doveadm_exit_code = EX_TEMPFAIL; } else if (doveadm_verbose) { i_info("%s: flushed", net_ip2addr(&ips[i])); } } director_disconnect(ctx); } static void cmd_director_dump(struct doveadm_cmd_context *cctx) { struct director_context *ctx; const char *line, *const *args; ctx = cmd_director_init(cctx); doveadm_print_init(DOVEADM_PRINT_TYPE_FORMATTED); if (ctx->explicit_socket_path) doveadm_print_formatted_set_format("doveadm director %{command} -a %{socket-path} %{host} %{vhost_count}\n"); else doveadm_print_formatted_set_format("doveadm director %{command} %{host} %{vhost_count}\n"); doveadm_print_header_simple("command"); doveadm_print_header_simple("socket-path"); doveadm_print_header_simple("host"); doveadm_print_header_simple("vhost_count"); director_send(ctx, "HOST-LIST\n"); while ((line = i_stream_read_next_line(ctx->input)) != NULL) { if (*line == '\0') break; T_BEGIN { args = t_strsplit_tabescaped(line); if (str_array_length(args) >= 2) { const char *host = args[0]; const char *tag = args[3]; /* this is guaranteed to be at least NULL */ if (tag != NULL && *tag != '\0') host = t_strdup_printf("%s@%s", host, tag); doveadm_print("add"); doveadm_print(ctx->socket_path); doveadm_print(host); doveadm_print(args[1]); } } T_END; } director_send(ctx, "HOST-LIST-REMOVED\n"); while ((line = i_stream_read_next_line(ctx->input)) != NULL) { if (*line == '\0') break; doveadm_print("remove"); doveadm_print(ctx->socket_path); doveadm_print(line); doveadm_print(""); } if (line == NULL) director_disconnected(ctx); director_disconnect(ctx); } static void director_read_ok_reply(struct director_context *ctx) { const char *line; line = i_stream_read_next_line(ctx->input); if (line == NULL) { director_disconnected(ctx); } else if (strcmp(line, "NOTFOUND") == 0) { i_error("Not found"); doveadm_exit_code = DOVEADM_EX_NOTFOUND; } else if (strcmp(line, "OK") != 0) { i_error("Failed: %s", line); doveadm_exit_code = EX_TEMPFAIL; } } static void cmd_director_ring_add(struct doveadm_cmd_context *cctx) { struct director_context *ctx; struct ip_addr ip; in_port_t port = 0; string_t *str = t_str_new(64); ctx = cmd_director_init(cctx); if (ctx->ip == NULL || net_addr2ip(ctx->ip, &ip) < 0 || (ctx->port != 0 && net_str2port(ctx->port, &port) < 0)) { director_cmd_help(cctx->cmd); return; } str_printfa(str, "DIRECTOR-ADD\t%s", net_ip2addr(&ip)); if (port != 0) str_printfa(str, "\t%u", port); str_append_c(str, '\n'); director_send(ctx, str_c(str)); director_read_ok_reply(ctx); director_disconnect(ctx); } static void cmd_director_ring_remove(struct doveadm_cmd_context *cctx) { struct director_context *ctx; struct ip_addr ip; string_t *str = t_str_new(64); in_port_t port = 0; ctx = cmd_director_init(cctx); if (ctx->ip == NULL || net_addr2ip(ctx->ip, &ip) < 0 || (ctx->port != NULL && net_str2port(ctx->port, &port) < 0)) { director_cmd_help(cctx->cmd); return; } str_printfa(str, "DIRECTOR-REMOVE\t%s", net_ip2addr(&ip)); if (port != 0) str_printfa(str, "\t%u", port); str_append_c(str, '\n'); director_send(ctx, str_c(str)); director_read_ok_reply(ctx); director_disconnect(ctx); } static void cmd_director_ring_status(struct doveadm_cmd_context *cctx) { struct director_context *ctx; const char *line, *const *args; ctx = cmd_director_init(cctx); doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); doveadm_print_header_simple("director ip"); doveadm_print_header_simple("port"); doveadm_print_header_simple("type"); doveadm_print_header_simple("last failed"); doveadm_print_header_simple("status"); doveadm_print_header_simple("ping ms"); doveadm_print_header_simple("input"); doveadm_print_header_simple("output"); doveadm_print_header_simple("buffered"); doveadm_print_header_simple("buffered peak"); doveadm_print_header_simple("last read"); doveadm_print_header_simple("last write"); director_send(ctx, "DIRECTOR-LIST\n"); while ((line = i_stream_read_next_line(ctx->input)) != NULL) { if (*line == '\0') break; T_BEGIN { unsigned int i; time_t ts; args = t_strsplit_tabescaped(line); for (i = 0; i < 12 && args[i] != NULL; i++) { if ((i == 3 || i == 10 || i == 11) && str_to_time(args[i], &ts) == 0) { if (ts == 0) doveadm_print("never"); else doveadm_print(unixdate2str(ts)); } else { doveadm_print(args[i]); } } for (; i < 12; i++) doveadm_print("-"); } T_END; } if (line == NULL) director_disconnected(ctx); director_disconnect(ctx); } struct doveadm_cmd_ver2 doveadm_cmd_director[] = { { .name = "director status", .cmd = cmd_director_status, .usage = "[-a ] [] []", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "user", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "tag", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "director map", .cmd = cmd_director_map, .usage = "[-a ] [-f ] [-h | -u] []", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('f', "users-file", CMD_PARAM_ISTREAM, 0) DOVEADM_CMD_PARAM('h', "hash-map", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('u', "user-map", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "host", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "director add", .cmd = cmd_director_add, .usage = "[-a ] [-t ] []", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('t', "tag", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "host", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "vhost-count", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "director update", .cmd = cmd_director_update, .usage = "[-a ] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "host", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "vhost-count", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "director up", .cmd = cmd_director_up, .usage = "[-a ] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "host", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "director down", .cmd = cmd_director_down, .usage = "[-a ] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "host", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "director remove", .cmd = cmd_director_remove, .usage = "[-a ] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "host", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "director move", .cmd = cmd_director_move, .usage = "[-a ] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "user", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "host", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "director kick", .cmd = cmd_director_kick, .usage = "[-a ] [-f ] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('f', "passdb-field", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "user", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "director flush", .cmd = cmd_director_flush, .usage = "[-a ] [-F] [--max-parallel ] |all", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('F', "force-flush", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "max-parallel", CMD_PARAM_INT64, 0) DOVEADM_CMD_PARAM('\0', "host", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "director dump", .cmd = cmd_director_dump, .usage = "[-a ]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAMS_END }, { .name = "director ring add", .cmd = cmd_director_ring_add, .usage = "[-a ] []", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "ip", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "port", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "director ring remove", .cmd = cmd_director_ring_remove, .usage = "[-a ] []", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "ip", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "port", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "director ring status", .cmd = cmd_director_ring_status, .usage = "[-a ]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAMS_END } }; static void director_cmd_help(const struct doveadm_cmd_ver2 *cmd) { unsigned int i; for (i = 0; i < N_ELEMENTS(doveadm_cmd_director); i++) { if (doveadm_cmd_director[i].cmd == cmd->cmd) help_ver2(&doveadm_cmd_director[i]); } i_unreached(); } void doveadm_register_director_commands(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(doveadm_cmd_director); i++) doveadm_cmd_register_ver2(&doveadm_cmd_director[i]); } dovecot-2.3.21.1/src/doveadm/client-connection-http.c0000644000000000000000000010130214656633576017254 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "compat.h" #include "lib-signals.h" #include "base64.h" #include "ioloop.h" #include "str.h" #include "str-sanitize.h" #include "istream.h" #include "ostream.h" #include "strescape.h" #include "settings-parser.h" #include "iostream-ssl.h" #include "iostream-temp.h" #include "istream-seekable.h" #include "master-service.h" #include "master-service-ssl.h" #include "master-service-settings.h" #include "mail-storage-service.h" #include "http-server.h" #include "http-request.h" #include "http-response.h" #include "http-url.h" #include "doveadm-util.h" #include "doveadm-server.h" #include "doveadm-mail.h" #include "doveadm-print.h" #include "doveadm-settings.h" #include "client-connection-private.h" #include "json-parser.h" #include #include enum client_request_parse_state { CLIENT_REQUEST_PARSE_INIT, CLIENT_REQUEST_PARSE_CMD, CLIENT_REQUEST_PARSE_CMD_NAME, CLIENT_REQUEST_PARSE_CMD_PARAMS, CLIENT_REQUEST_PARSE_CMD_PARAM_KEY, CLIENT_REQUEST_PARSE_CMD_PARAM_VALUE, CLIENT_REQUEST_PARSE_CMD_PARAM_ARRAY, CLIENT_REQUEST_PARSE_CMD_PARAM_ISTREAM, CLIENT_REQUEST_PARSE_CMD_ID, CLIENT_REQUEST_PARSE_CMD_DONE, CLIENT_REQUEST_PARSE_DONE }; struct client_request_http { pool_t pool; struct client_connection_http *conn; struct http_server_request *http_request; struct io *io; struct istream *input; struct ostream *output; struct json_parser *json_parser; const struct doveadm_cmd_ver2 *cmd; struct doveadm_cmd_param *cmd_param; struct ioloop *ioloop; ARRAY_TYPE(doveadm_cmd_param_arr_t) pargv; int method_err; char *method_id; bool first_row; bool value_is_array; enum client_request_parse_state parse_state; }; struct client_connection_http { struct client_connection conn; struct http_server_connection *http_conn; struct client_request_http *request; }; typedef void doveadm_server_handler_t(struct client_request_http *req); struct doveadm_http_server_mount { const char *verb; const char *path; doveadm_server_handler_t *handler; bool auth; }; static struct http_server *doveadm_http_server; static void doveadm_http_server_send_response(struct client_request_http *req); /* * API */ static void doveadm_http_server_options_handler(struct client_request_http *); static void doveadm_http_server_print_mounts(struct client_request_http *); static void doveadm_http_server_send_api_v1(struct client_request_http *); static void doveadm_http_server_read_request_v1(struct client_request_http *); static struct doveadm_http_server_mount doveadm_http_server_mounts[] = { { .verb = "OPTIONS", .path = NULL, .handler = doveadm_http_server_options_handler, .auth = FALSE },{ .verb = "GET", .path = "/", .handler = doveadm_http_server_print_mounts, .auth = TRUE },{ .verb = "GET", .path = "/doveadm/v1", .handler = doveadm_http_server_send_api_v1, .auth = TRUE },{ .verb = "POST", .path = "/doveadm/v1", .handler = doveadm_http_server_read_request_v1, .auth = TRUE } }; static void doveadm_http_server_json_error(void *context, const char *error) { struct client_request_http *req = context; struct ostream *output = req->output; string_t *escaped; escaped = str_new(req->pool, 10); o_stream_nsend_str(output, "[\"error\",{\"type\":\""); json_append_escaped(escaped, error); o_stream_nsend_str(output, str_c(escaped)); o_stream_nsend_str(output, "\", \"exitCode\":"); str_truncate(escaped,0); str_printfa(escaped, "%d", doveadm_exit_code); o_stream_nsend_str(output, str_c(escaped)); o_stream_nsend_str(output, "},\""); str_truncate(escaped,0); if (req->method_id != NULL) { json_append_escaped(escaped, req->method_id); o_stream_nsend_str(output, str_c(escaped)); } o_stream_nsend_str(output, "\"]"); } static void doveadm_http_server_json_success(void *context, struct istream *result) { struct client_request_http *req = context; struct ostream *output = req->output; string_t *escaped; escaped = str_new(req->pool, 10); o_stream_nsend_str(output, "[\"doveadmResponse\","); o_stream_nsend_istream(output, result); o_stream_nsend_str(output, ",\""); if (req->method_id != NULL) { json_append_escaped(escaped, req->method_id); o_stream_nsend_str(output, str_c(escaped)); } o_stream_nsend_str(output, "\"]"); } static void doveadm_http_server_command_execute(struct client_request_http *req) { struct client_connection_http *conn = req->conn; struct doveadm_cmd_context cctx; struct istream *is; const char *user; struct ioloop *ioloop, *prev_ioloop; /* final preflight check */ if (req->method_err == 0 && !doveadm_client_is_allowed_command(conn->conn.set, req->cmd->name)) req->method_err = 403; if (req->method_err != 0) { if (req->method_err == 404) { doveadm_http_server_json_error(req, "unknownMethod"); } else if (req->method_err == 403) { doveadm_http_server_json_error(req, "unAuthorized"); } else if (req->method_err == 400) { doveadm_http_server_json_error(req, "invalidRequest"); } else { doveadm_http_server_json_error(req, "internalError"); } return; } prev_ioloop = current_ioloop; i_zero(&cctx); cctx.conn_type = conn->conn.type; cctx.input = req->input; cctx.output = req->output; // create iostream doveadm_print_ostream = iostream_temp_create("/tmp/doveadm.", 0); cctx.cmd = req->cmd; if ((cctx.cmd->flags & CMD_FLAG_NO_PRINT) == 0) doveadm_print_init(DOVEADM_PRINT_TYPE_JSON); /* then call it */ doveadm_cmd_params_null_terminate_arrays(&req->pargv); cctx.argv = array_get(&req->pargv, (unsigned int*)&cctx.argc); ioloop = io_loop_create(); doveadm_exit_code = 0; cctx.local_ip = conn->conn.local_ip; cctx.local_port = conn->conn.local_port; cctx.remote_ip = conn->conn.remote_ip; cctx.remote_port = conn->conn.remote_port; if (doveadm_cmd_param_str(&cctx, "user", &user)) i_info("Executing command '%s' as '%s'", cctx.cmd->name, user); else i_info("Executing command '%s'", cctx.cmd->name); client_connection_set_proctitle(&conn->conn, cctx.cmd->name); cctx.cmd->cmd(&cctx); client_connection_set_proctitle(&conn->conn, ""); o_stream_switch_ioloop_to(req->output, prev_ioloop); io_loop_destroy(&ioloop); if ((cctx.cmd->flags & CMD_FLAG_NO_PRINT) == 0) doveadm_print_deinit(); if (o_stream_finish(doveadm_print_ostream) < 0) { i_info("Error writing output in command %s: %s", req->cmd->name, o_stream_get_error(req->output)); doveadm_exit_code = EX_TEMPFAIL; } is = iostream_temp_finish(&doveadm_print_ostream, 4096); if (req->first_row == TRUE) req->first_row = FALSE; else o_stream_nsend_str(req->output,","); if (doveadm_exit_code != 0) { if (doveadm_exit_code == 0 || doveadm_exit_code == EX_TEMPFAIL) i_error("Command %s failed", req->cmd->name); doveadm_http_server_json_error(req, "exitCode"); } else { doveadm_http_server_json_success(req, is); } i_stream_unref(&is); } static int request_json_parse_init(struct client_request_http *req) { struct http_server_request *http_sreq = req->http_request; enum json_type type; const char *value; int ret; if ((ret=json_parse_next(req->json_parser, &type, &value)) <= 0) return ret; if (type != JSON_TYPE_ARRAY) { /* request must be a JSON array */ http_server_request_fail_text(http_sreq, 400, "Bad Request", "Request must be a JSON array"); return -1; } req->first_row = TRUE; o_stream_nsend_str(req->output,"["); /* next: parse the next command */ req->parse_state = CLIENT_REQUEST_PARSE_CMD; return 1; } static int request_json_parse_cmd(struct client_request_http *req) { struct http_server_request *http_sreq = req->http_request; enum json_type type; const char *value; int ret; if ((ret=json_parse_next(req->json_parser, &type, &value)) <= 0) return ret; if (type == JSON_TYPE_ARRAY_END) { /* end of command list */ req->parse_state = CLIENT_REQUEST_PARSE_DONE; return 1; } if (type != JSON_TYPE_ARRAY) { /* command must be an array */ http_server_request_fail_text(http_sreq, 400, "Bad Request", "Command must be a JSON array"); return -1; } req->method_err = 0; p_free_and_null(req->pool, req->method_id); req->cmd = NULL; doveadm_cmd_params_clean(&req->pargv); /* next: parse the command name */ req->parse_state = CLIENT_REQUEST_PARSE_CMD_NAME; return 1; } static int request_json_parse_cmd_name(struct client_request_http *req) { struct http_server_request *http_sreq = req->http_request; enum json_type type; const char *value; const struct doveadm_cmd_ver2 *ccmd; struct doveadm_cmd_param *param; bool found; int pargc, ret; if ((ret=json_parse_next(req->json_parser, &type, &value)) <= 0) return ret; if (type != JSON_TYPE_STRING) { /* command name must be a string */ http_server_request_fail_text(http_sreq, 400, "Bad Request", "Command name must be a string"); return -1; } /* see if we can find it */ found = FALSE; array_foreach(&doveadm_cmds_ver2, ccmd) { if (i_strccdascmp(ccmd->name, value) == 0) { req->cmd = ccmd; found = TRUE; break; } } if (!found) { /* command not found; skip to the command ID */ json_parse_skip_next(req->json_parser); req->method_err = 404; req->parse_state = CLIENT_REQUEST_PARSE_CMD_ID; return 1; } /* initialize pargv */ for (pargc = 0; req->cmd->parameters[pargc].name != NULL; pargc++) { param = array_append_space(&req->pargv); *param = req->cmd->parameters[pargc]; param->value_set = FALSE; } /* next: parse the command parameters */ req->parse_state = CLIENT_REQUEST_PARSE_CMD_PARAMS; return 1; } static int request_json_parse_cmd_params(struct client_request_http *req) { struct http_server_request *http_sreq = req->http_request; enum json_type type; const char *value; int ret; if ((ret=json_parse_next(req->json_parser, &type, &value)) <= 0) return ret; if (type == JSON_TYPE_OBJECT_END) { /* empty command parameters object; parse command ID next */ req->parse_state = CLIENT_REQUEST_PARSE_CMD_ID; return 1; } if (type != JSON_TYPE_OBJECT) { /* parameters must be contained in an object */ http_server_request_fail_text(http_sreq, 400, "Bad Request", "Parameters must be contained in a JSON object"); return -1; } /* next: parse parameter key */ req->parse_state = CLIENT_REQUEST_PARSE_CMD_PARAM_KEY; return 1; } static int request_json_parse_cmd_param_key(struct client_request_http *req) { struct http_server_request *http_sreq = req->http_request; enum json_type type; const char *value; struct doveadm_cmd_param *par; bool found; int ret; if ((ret=json_parse_next(req->json_parser, &type, &value)) <= 0) return ret; if (type == JSON_TYPE_OBJECT_END) { /* end of parameters; parse command ID next */ req->parse_state = CLIENT_REQUEST_PARSE_CMD_ID; return 1; } i_assert(type == JSON_TYPE_OBJECT_KEY); /* find the parameter */ found = FALSE; array_foreach_modifiable(&req->pargv, par) { if (i_strccdascmp(par->name, value) == 0) { req->cmd_param = par; found = TRUE; break; } } if (found && req->cmd_param->value_set) { /* it's already set, cannot have same key twice in json */ http_server_request_fail_text(http_sreq, 400, "Bad Request", "Parameter `%s' is duplicated", req->cmd_param->name); return -1; } /* skip remaining parameters if error has already occurred */ if (!found || req->method_err != 0) { json_parse_skip_next(req->json_parser); req->method_err = 400; req->parse_state = CLIENT_REQUEST_PARSE_CMD_PARAM_KEY; return 1; } /* next: parse parameter value */ req->value_is_array = FALSE; req->parse_state = CLIENT_REQUEST_PARSE_CMD_PARAM_VALUE; return 1; } static int request_json_parse_param_value(struct client_request_http *req) { struct http_server_request *http_sreq = req->http_request; enum json_type type; const char *value; int ret; if (req->cmd_param->type == CMD_PARAM_ISTREAM) { struct istream* is[2] = {0}; /* read the value as a stream */ ret = json_parse_next_stream(req->json_parser, &is[0]); if (ret <= 0) return ret; req->cmd_param->value.v_istream = i_stream_create_seekable_path(is, IO_BLOCK_SIZE, "/tmp/doveadm."); i_stream_unref(&is[0]); req->cmd_param->value_set = TRUE; /* read the seekable stream to its end so that the underlying json istream is read to its conclusion. */ req->parse_state = CLIENT_REQUEST_PARSE_CMD_PARAM_ISTREAM; return 1; } if ((ret=json_parse_next(req->json_parser, &type, &value)) <= 0) return ret; if (req->cmd_param->type == CMD_PARAM_ARRAY) { const char *tmp; /* expects either a singular value or an array of values */ p_array_init(&req->cmd_param->value.v_array, req->pool, 1); req->cmd_param->value_set = TRUE; if (type == JSON_TYPE_ARRAY) { /* start of array */ req->value_is_array = TRUE; req->parse_state = CLIENT_REQUEST_PARSE_CMD_PARAM_ARRAY; return 1; } /* singular value */ if (type != JSON_TYPE_STRING) { /* FIXME: should handle other than string too */ http_server_request_fail_text(http_sreq, 400, "Bad Request", "Parameter `%s' must be string or array", req->cmd_param->name); return -1; } tmp = p_strdup(req->pool, value); array_push_back(&req->cmd_param->value.v_array, &tmp); /* next: continue with the next parameter */ req->parse_state = CLIENT_REQUEST_PARSE_CMD_PARAM_KEY; return 1; } /* expects just a value */ req->cmd_param->value_set = TRUE; switch(req->cmd_param->type) { case CMD_PARAM_BOOL: req->cmd_param->value.v_bool = (strcmp(value, "true") == 0); break; case CMD_PARAM_INT64: if (str_to_int64(value, &req->cmd_param->value.v_int64) != 0) { req->method_err = 400; } break; case CMD_PARAM_IP: if (net_addr2ip(value, &req->cmd_param->value.v_ip) != 0) { req->method_err = 400; } break; case CMD_PARAM_STR: req->cmd_param->value.v_string = p_strdup(req->pool, value); break; default: break; } /* next: continue with the next parameter */ req->parse_state = CLIENT_REQUEST_PARSE_CMD_PARAM_KEY; return 1; } static int request_json_parse_param_array(struct client_request_http *req) { struct http_server_request *http_sreq = req->http_request; enum json_type type; const char *value; int ret; if ((ret=json_parse_next(req->json_parser, &type, &value)) <= 0) return ret; if (type == JSON_TYPE_ARRAY_END) { /* end of array: continue with next parameter */ req->parse_state = CLIENT_REQUEST_PARSE_CMD_PARAM_KEY; return 1; } if (type != JSON_TYPE_STRING) { /* array items must be string */ http_server_request_fail_text(http_sreq, 400, "Bad Request", "Command parameter array can only contain" "string values"); return -1; } /* record entry */ value = p_strdup(req->pool, value); array_push_back(&req->cmd_param->value.v_array, &value); /* next: continue with the next array item */ return 1; } static int request_json_parse_param_istream(struct client_request_http *req) { struct http_server_request *http_sreq = req->http_request; struct istream *v_input = req->cmd_param->value.v_istream; const unsigned char *data; size_t size; while (i_stream_read_more(v_input, &data, &size) > 0) i_stream_skip(v_input, size); if (!v_input->eof) { /* more to read */ return 0; } if (v_input->stream_errno != 0) { i_error("read(%s) failed: %s", i_stream_get_name(v_input), i_stream_get_error(v_input)); req->method_err = 400; if (req->input->stream_errno == 0) { http_server_request_fail_text(http_sreq, 400, "Bad Request", "Failed to read command parameter data"); } return -1; } /* next: continue with the next parameter */ req->parse_state = CLIENT_REQUEST_PARSE_CMD_PARAM_KEY; return 1; } static int request_json_parse_cmd_id(struct client_request_http *req) { struct http_server_request *http_sreq = req->http_request; enum json_type type; const char *value; int ret; if ((ret=json_parse_next(req->json_parser, &type, &value)) <= 0) return ret; if (type != JSON_TYPE_STRING) { /* command ID must be a string */ http_server_request_fail_text(http_sreq, 400, "Bad Request", "Command ID must be a string"); return -1; } /* next: parse end of command */ req->method_id = p_strdup(req->pool, value); req->parse_state = CLIENT_REQUEST_PARSE_CMD_DONE; return 1; } static int request_json_parse_cmd_done(struct client_request_http *req) { struct http_server_request *http_sreq = req->http_request; enum json_type type; const char *value; int ret; if ((ret=json_parse_next(req->json_parser, &type, &value)) <= 0) return ret; if (type != JSON_TYPE_ARRAY_END) { /* command array must end here */ http_server_request_fail_text(http_sreq, 400, "Bad Request", "Unexpected JSON element at end of command"); return -1; } /* execute command */ doveadm_http_server_command_execute(req); /* next: parse next command */ req->parse_state = CLIENT_REQUEST_PARSE_CMD; return 1; } static int request_json_parse_done(struct client_request_http *req) { struct http_server_request *http_sreq = req->http_request; enum json_type type; const char *value; int ret; if ((ret=json_parse_next(req->json_parser, &type, &value)) <= 0) return ret; /* only gets here when there is spurious additional JSON */ http_server_request_fail_text(http_sreq, 400, "Bad Request", "Unexpected JSON element in input"); return -1; } static int doveadm_http_server_json_parse_v1(struct client_request_http *req) { /* parser state machine */ switch (req->parse_state) { /* command list: '[' */ case CLIENT_REQUEST_PARSE_INIT: return request_json_parse_init(req); /* command begin: '[' */ case CLIENT_REQUEST_PARSE_CMD: return request_json_parse_cmd(req); /* command name: string */ case CLIENT_REQUEST_PARSE_CMD_NAME: return request_json_parse_cmd_name(req); /* command parameters: '{' */ case CLIENT_REQUEST_PARSE_CMD_PARAMS: return request_json_parse_cmd_params(req); /* parameter key: string */ case CLIENT_REQUEST_PARSE_CMD_PARAM_KEY: return request_json_parse_cmd_param_key(req); /* parameter value */ case CLIENT_REQUEST_PARSE_CMD_PARAM_VALUE: return request_json_parse_param_value(req); /* parameter array value */ case CLIENT_REQUEST_PARSE_CMD_PARAM_ARRAY: return request_json_parse_param_array(req); /* parameter istream value */ case CLIENT_REQUEST_PARSE_CMD_PARAM_ISTREAM: return request_json_parse_param_istream(req); /* command ID: string */ case CLIENT_REQUEST_PARSE_CMD_ID: return request_json_parse_cmd_id(req); /* command end: ']' */ case CLIENT_REQUEST_PARSE_CMD_DONE: return request_json_parse_cmd_done(req); /* finished parsing request (seen final ']') */ case CLIENT_REQUEST_PARSE_DONE: return request_json_parse_done(req); default: break; } i_unreached(); } static void doveadm_http_server_read_request_v1(struct client_request_http *req) { struct http_server_request *http_sreq = req->http_request; const char *error; int ret; if (req->json_parser == NULL) { req->json_parser = json_parser_init_flags( req->input, JSON_PARSER_NO_ROOT_OBJECT); } while ((ret=doveadm_http_server_json_parse_v1(req)) > 0); if (http_server_request_get_response(http_sreq) != NULL) { /* already responded */ io_remove(&req->io); i_stream_destroy(&req->input); return; } if (!req->input->eof && ret == 0) return; io_remove(&req->io); doveadm_cmd_params_clean(&req->pargv); if (req->input->stream_errno != 0) { http_server_request_fail_close(http_sreq, 400, "Client disconnected"); i_info("read(%s) failed: %s", i_stream_get_name(req->input), i_stream_get_error(req->input)); return; } if (json_parser_deinit(&req->json_parser, &error) != 0) { http_server_request_fail_text(http_sreq, 400, "Bad Request", "JSON parse error: %s", error); return; } i_stream_destroy(&req->input); o_stream_nsend_str(req->output,"]"); doveadm_http_server_send_response(req); } static void doveadm_http_server_camelcase_value(string_t *value) { size_t i, k; char *ptr = str_c_modifiable(value); for (i = 0, k = 0; i < strlen(ptr);) { if (ptr[i] == ' ' || ptr[i] == '-') { i++; ptr[k++] = i_toupper(ptr[i++]); } else { ptr[k++] = ptr[i++]; } } str_truncate(value, k); } static void doveadm_http_server_send_api_v1(struct client_request_http *req) { struct ostream *output = req->output; const struct doveadm_cmd_ver2 *cmd; const struct doveadm_cmd_param *par; unsigned int i, k; string_t *tmp; bool sent; tmp = str_new(req->pool, 8); o_stream_nsend_str(output,"[\n"); for (i = 0; i < array_count(&doveadm_cmds_ver2); i++) { cmd = array_idx(&doveadm_cmds_ver2, i); if (i > 0) o_stream_nsend_str(output, ",\n"); o_stream_nsend_str(output, "\t{\"command\":\""); json_append_escaped(tmp, cmd->name); doveadm_http_server_camelcase_value(tmp); o_stream_nsend_str(output, str_c(tmp)); o_stream_nsend_str(output, "\", \"parameters\":["); sent = FALSE; for (k = 0; cmd->parameters[k].name != NULL; k++) { str_truncate(tmp, 0); par = &(cmd->parameters[k]); if ((par->flags & CMD_PARAM_FLAG_DO_NOT_EXPOSE) != 0) continue; if (sent) o_stream_nsend_str(output, ",\n"); else o_stream_nsend_str(output, "\n"); sent = TRUE; o_stream_nsend_str(output, "\t\t{\"name\":\""); json_append_escaped(tmp, par->name); doveadm_http_server_camelcase_value(tmp); o_stream_nsend_str(output, str_c(tmp)); o_stream_nsend_str(output, "\",\"type\":\""); switch(par->type) { case CMD_PARAM_BOOL: o_stream_nsend_str(output, "boolean"); break; case CMD_PARAM_INT64: o_stream_nsend_str(output, "integer"); break; case CMD_PARAM_ARRAY: o_stream_nsend_str(output, "array"); break; case CMD_PARAM_IP: case CMD_PARAM_ISTREAM: case CMD_PARAM_STR: o_stream_nsend_str(output, "string"); } o_stream_nsend_str(output, "\"}"); } if (k > 0) o_stream_nsend_str(output,"\n\t"); o_stream_nsend_str(output,"]}"); str_truncate(tmp, 0); } o_stream_nsend_str(output,"\n]"); doveadm_http_server_send_response(req); } static void doveadm_http_server_options_handler(struct client_request_http *req) { struct http_server_request *http_sreq = req->http_request; struct http_server_response *http_resp; http_resp = http_server_response_create(http_sreq, 200, "OK"); http_server_response_add_header(http_resp, "Access-Control-Allow-Origin", "*"); http_server_response_add_header(http_resp, "Access-Control-Allow-Methods", "GET, POST, OPTIONS"); http_server_response_add_header(http_resp, "Access-Control-Allow-Request-Headers", "Content-Type, X-API-Key, Authorization"); http_server_response_add_header(http_resp, "Access-Control-Allow-Headers", "Content-Type, WWW-Authenticate"); http_server_response_submit(http_resp); } static void doveadm_http_server_print_mounts(struct client_request_http *req) { struct ostream *output = req->output; unsigned int i; o_stream_nsend_str(output, "[\n"); for (i = 0; i < N_ELEMENTS(doveadm_http_server_mounts); i++) { if (i > 0) o_stream_nsend_str(output, ",\n"); o_stream_nsend_str(output, "{\"method\":\""); if (doveadm_http_server_mounts[i].verb == NULL) o_stream_nsend_str(output, "*"); else { o_stream_nsend_str(output, doveadm_http_server_mounts[i].verb); } o_stream_nsend_str(output, "\",\"path\":\""); if (doveadm_http_server_mounts[i].path == NULL) o_stream_nsend_str(output, "*"); else { o_stream_nsend_str(output, doveadm_http_server_mounts[i].path); } o_stream_nsend_str(output, "\"}"); } o_stream_nsend_str(output, "\n]"); doveadm_http_server_send_response(req); } /* * Request */ static void doveadm_http_server_send_response(struct client_request_http *req) { struct http_server_request *http_sreq = req->http_request; struct http_server_response *http_resp; struct istream *payload = NULL; if (req->output != NULL) { if (o_stream_finish(req->output) == -1) { i_info("error writing output: %s", o_stream_get_error(req->output)); o_stream_destroy(&req->output); http_server_request_fail(http_sreq, 500, "Internal server error"); return; } payload = iostream_temp_finish(&req->output, IO_BLOCK_SIZE); } http_resp = http_server_response_create(http_sreq, 200, "OK"); http_server_response_add_header(http_resp, "Content-Type", "application/json; charset=utf-8"); if (payload != NULL) { http_server_response_set_payload(http_resp, payload); i_stream_unref(&payload); } http_server_response_submit(http_resp); } static void doveadm_http_server_request_destroy(struct client_request_http *req) { struct client_connection_http *conn = req->conn; struct http_server_request *http_sreq = req->http_request; const struct http_request *http_req = http_server_request_get(http_sreq); struct http_server_response *http_resp = http_server_request_get_response(http_sreq); i_assert(conn->request == req); if (http_resp != NULL) { const char *agent, *url, *reason; uoff_t size; int status; http_server_response_get_status(http_resp, &status, &reason); size = http_server_response_get_total_size(http_resp); agent = http_request_header_get(http_req, "User-Agent"); if (agent == NULL) agent = ""; url = http_url_create(http_req->target.url); i_info("doveadm: %s %s %s \"%s %s " "HTTP/%d.%d\" %d %"PRIuUOFF_T" \"%s\" \"%s\"", net_ip2addr(&conn->conn.remote_ip), "-", "-", http_req->method, http_req->target.url->path, http_req->version_major, http_req->version_minor, status, size, url, agent); } if (req->json_parser != NULL) { const char *error ATTR_UNUSED; (void)json_parser_deinit(&req->json_parser, &error); // we've already failed, ignore error } if (req->output != NULL) o_stream_set_no_error_handling(req->output, TRUE); io_remove(&req->io); o_stream_destroy(&req->output); i_stream_destroy(&req->input); http_server_request_unref(&req->http_request); http_server_switch_ioloop(doveadm_http_server); pool_unref(&req->pool); conn->request = NULL; } static bool doveadm_http_server_auth_basic(struct client_request_http *req, const struct http_auth_credentials *creds) { struct client_connection_http *conn = req->conn; const struct doveadm_settings *set = conn->conn.set; string_t *b64_value; char *value; if (*set->doveadm_password == '\0') { i_error("Invalid authentication attempt to HTTP API: " "Basic authentication scheme not enabled"); return FALSE; } b64_value = str_new(conn->conn.pool, 32); value = p_strdup_printf(conn->conn.pool, "doveadm:%s", set->doveadm_password); base64_encode(value, strlen(value), b64_value); if (creds->data != NULL && strcmp(creds->data, str_c(b64_value)) == 0) return TRUE; i_error("Invalid authentication attempt to HTTP API " "(using Basic authentication scheme)"); return FALSE; } static bool doveadm_http_server_auth_api_key(struct client_request_http *req, const struct http_auth_credentials *creds) { struct client_connection_http *conn = req->conn; const struct doveadm_settings *set = doveadm_settings; string_t *b64_value; if (*set->doveadm_api_key == '\0') { i_error("Invalid authentication attempt to HTTP API: " "X-Dovecot-API authentication scheme not enabled"); return FALSE; } b64_value = str_new(conn->conn.pool, 32); base64_encode(set->doveadm_api_key, strlen(set->doveadm_api_key), b64_value); if (creds->data != NULL && strcmp(creds->data, str_c(b64_value)) == 0) return TRUE; i_error("Invalid authentication attempt to HTTP API " "(using X-Dovecot-API authentication scheme)"); return FALSE; } static bool doveadm_http_server_auth_verify(struct client_request_http *req, const struct http_auth_credentials *creds) { /* see if the mech is supported */ if (strcasecmp(creds->scheme, "Basic") == 0) return doveadm_http_server_auth_basic(req, creds); if (strcasecmp(creds->scheme, "X-Dovecot-API") == 0) return doveadm_http_server_auth_api_key(req, creds); i_error("Unsupported authentication scheme to HTTP API: %s", str_sanitize(creds->scheme, 128)); return FALSE; } static bool doveadm_http_server_authorize_request(struct client_request_http *req) { struct client_connection_http *conn = req->conn; struct http_server_request *http_sreq = req->http_request; bool auth = FALSE; struct http_auth_credentials creds; /* no authentication specified */ if (doveadm_settings->doveadm_api_key[0] == '\0' && *conn->conn.set->doveadm_password == '\0') { http_server_request_fail(http_sreq, 500, "Internal Server Error"); i_error("No authentication defined in configuration. " "Add API key or password"); return FALSE; } if (http_server_request_get_auth(http_sreq, &creds) > 0) auth = doveadm_http_server_auth_verify(req, &creds); if (!auth) { struct http_server_response *http_resp; http_resp = http_server_response_create(http_sreq, 401, "Authentication required"); if (doveadm_settings->doveadm_api_key[0] != '\0') { http_server_response_add_header(http_resp, "WWW-Authenticate", "X-Dovecot-API" ); } if (*conn->conn.set->doveadm_password != '\0') { http_server_response_add_header(http_resp, "WWW-Authenticate", "Basic Realm=\"doveadm\"" ); } http_server_response_submit(http_resp); } return auth; } static void doveadm_http_server_handle_request(void *context, struct http_server_request *http_sreq) { struct client_connection_http *conn = context; struct client_request_http *req; const struct http_request *http_req = http_server_request_get(http_sreq); struct doveadm_http_server_mount *ep = NULL; pool_t pool; unsigned int i; /* no pipelining possible due to synchronous handling of requests */ i_assert(conn->request == NULL); pool = pool_alloconly_create("doveadm request", 1024*16); req = p_new(pool, struct client_request_http, 1); req->pool = pool; req->conn = conn; req->http_request = http_sreq; http_server_request_ref(req->http_request); http_server_request_connection_close(http_sreq, TRUE); http_server_request_set_destroy_callback(http_sreq, doveadm_http_server_request_destroy, req); conn->request = req; for (i = 0; i < N_ELEMENTS(doveadm_http_server_mounts); i++) { if (doveadm_http_server_mounts[i].verb == NULL || strcmp(http_req->method, doveadm_http_server_mounts[i].verb) == 0) { if (doveadm_http_server_mounts[i].path == NULL || strcmp(http_req->target.url->path, doveadm_http_server_mounts[i].path) == 0) { ep = &doveadm_http_server_mounts[i]; break; } } } if (ep == NULL) { http_server_request_fail(http_sreq, 404, "Path Not Found"); return; } if (ep->auth == TRUE && !doveadm_http_server_authorize_request(req)) return; if (strcmp(http_req->method, "POST") == 0) { /* handle request */ req->input = http_req->payload; i_stream_set_name(req->input, net_ip2addr(&conn->conn.remote_ip)); i_stream_ref(req->input); req->io = io_add_istream(req->input, *ep->handler, req); req->output = iostream_temp_create_named( "/tmp/doveadm.", 0, net_ip2addr(&conn->conn.remote_ip)); p_array_init(&req->pargv, req->pool, 5); ep->handler(req); } else { req->output = iostream_temp_create_named( "/tmp/doveadm.", 0, net_ip2addr(&conn->conn.remote_ip)); ep->handler(req); } } /* * Connection */ static void doveadm_http_server_connection_destroy(void *context, const char *reason); static const struct http_server_callbacks doveadm_http_callbacks = { .connection_destroy = doveadm_http_server_connection_destroy, .handle_request = doveadm_http_server_handle_request }; static void client_connection_http_free(struct client_connection *_conn) { struct client_connection_http *conn = (struct client_connection_http *)_conn; if (conn->http_conn != NULL) { /* We're not in the lib-http/server's connection destroy callback. */ http_server_connection_close(&conn->http_conn, "Server shutting down"); } } struct client_connection * client_connection_http_create(int fd, bool ssl) { struct client_connection_http *conn; pool_t pool; pool = pool_alloconly_create("doveadm client", 1024); conn = p_new(pool, struct client_connection_http, 1); if (client_connection_init(&conn->conn, DOVEADM_CONNECTION_TYPE_HTTP, pool, fd) < 0) { pool_unref(&conn->conn.pool); return NULL; } conn->conn.free = client_connection_http_free; conn->http_conn = http_server_connection_create(doveadm_http_server, fd, fd, ssl, &doveadm_http_callbacks, conn); return &conn->conn; } static void doveadm_http_server_connection_destroy(void *context, const char *reason ATTR_UNUSED) { struct client_connection_http *conn = (struct client_connection_http *)context; struct client_connection *bconn = &conn->conn; if (conn->http_conn == NULL) { /* already destroying client directly */ return; } /* HTTP connection is destroyed already now */ conn->http_conn = NULL; /* destroy the connection itself */ client_connection_destroy(&bconn); } /* * Server */ void doveadm_http_server_init(void) { struct http_server_settings http_set = { .rawlog_dir = doveadm_settings->doveadm_http_rawlog_dir, }; doveadm_http_server = http_server_init(&http_set); } void doveadm_http_server_deinit(void) { http_server_deinit(&doveadm_http_server); } dovecot-2.3.21.1/src/doveadm/doveadm-dump-thread.c0000644000000000000000000000613014656633576016516 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "mmap-util.h" #include "mail-index-private.h" #include "mail-index-strmap.h" #include "doveadm-dump.h" #include #include #include #include static uint32_t max_likely_index; static size_t dump_hdr(const struct mail_index_strmap_header *hdr) { printf("version = %u\n", hdr->version); printf("uid validity = %u\n", hdr->uid_validity); return sizeof(*hdr); } static int dump_record(const uint8_t **p, const uint8_t *end, uint32_t *uid) { uint32_t uid_diff, n, i, count, crc32, idx; size_t size; /* *count *count */ if (mail_index_unpack_num(p, end, &uid_diff) < 0) return -1; *uid += uid_diff; if (mail_index_unpack_num(p, end, &n) < 0) return -1; printf(" - uid %u: n=%u\n", *uid, n); count = n < 2 ? n + 1 : n; size = sizeof(crc32)*count + sizeof(idx)*count; if (*p + size > end) return -1; for (i = 0; i < count; i++) { if (i == 0) printf(" - message-id: "); else if (i == 1) { if (n == 1) printf(" - in-reply-to: "); else printf(" - references[1]: "); } else { printf(" - references[%u]: ", i); } memcpy(&crc32, *p + sizeof(crc32)*i, sizeof(crc32)); memcpy(&idx, *p + sizeof(crc32)*count + sizeof(idx)*i, sizeof(idx)); printf("crc32=%08x index=%u\n", crc32, idx); if (idx > max_likely_index) printf(" - index probably broken\n"); } *p += size; return 0; } static int dump_block(const uint8_t *data, const uint8_t *end, uint32_t *uid) { const uint8_t *p; uint32_t block_size; if (data + 4 >= end) return -1; memcpy(&block_size, data, sizeof(block_size)); block_size = mail_index_offset_to_uint32(block_size) >> 2; printf(" - block_size=%u\n", block_size); if (block_size == 0) { /* finished */ return -1; } if (data + sizeof(block_size) + block_size > end) { printf(" - broken!\n"); return -1; } p = data + sizeof(block_size); end = p + block_size; *uid += 1; while (p != end) { if (dump_record(&p, end, uid) < 0) { printf(" - broken\n"); return -1; } } return p - data; } static void cmd_dump_thread(const char *path, const char *const *args ATTR_UNUSED) { unsigned int pos; const void *map, *end; struct stat st; uint32_t uid; int fd, ret; fd = open(path, O_RDONLY); if (fd < 0) i_fatal("open(%s) failed: %m", path); if (fstat(fd, &st) < 0) i_fatal("fstat(%s) failed: %m", path); max_likely_index = (st.st_size / 8) * 2; map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (map == MAP_FAILED) i_fatal("mmap() failed: %m"); end = CONST_PTR_OFFSET(map, st.st_size); pos = dump_hdr(map); uid = 0; do { printf("block at offset %u:\n", pos); T_BEGIN { ret = dump_block(CONST_PTR_OFFSET(map, pos), end, &uid); pos += ret; } T_END; } while (ret > 0); i_close_fd(&fd); } static bool test_dump_thread(const char *path) { const char *p; p = strrchr(path, '.'); return p != NULL && strcmp(p, ".thread") == 0; } struct doveadm_cmd_dump doveadm_cmd_dump_thread = { "thread", test_dump_thread, cmd_dump_thread }; dovecot-2.3.21.1/src/doveadm/doveadm-dsync.c0000644000000000000000000013273114656633576015433 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "lib-signals.h" #include "array.h" #include "execv-const.h" #include "child-wait.h" #include "istream.h" #include "ostream.h" #include "iostream-ssl.h" #include "iostream-rawlog.h" #include "write-full.h" #include "str.h" #include "strescape.h" #include "var-expand.h" #include "process-title.h" #include "settings-parser.h" #include "imap-util.h" #include "master-service.h" #include "master-service-settings.h" #include "master-service-ssl-settings.h" #include "mail-storage.h" #include "mail-storage-service.h" #include "mail-user.h" #include "mail-namespace.h" #include "mailbox-list-private.h" #include "doveadm-settings.h" #include "doveadm-mail.h" #include "doveadm-print.h" #include "doveadm-server.h" #include "client-connection.h" #include "server-connection.h" #include "dsync/dsync-brain.h" #include "dsync/dsync-ibc.h" #include "doveadm-dsync.h" #include #include #include #include #define DSYNC_COMMON_GETOPT_ARGS "+1a:dDEfg:I:l:m:n:NO:Pr:Rs:t:e:T:Ux:" #define DSYNC_REMOTE_CMD_EXIT_WAIT_SECS 30 /* The default vname_escape_char to use unless overridden by BROKENCHAR setting. Note that it's only used for internal dsync names, so it won't end up in permanent storage names. The only requirement for it is that it's not the same as the hierarchy separator. */ #define DSYNC_LIST_VNAME_ESCAPE_CHAR '%' /* In case DSYNC_LIST_VNAME_ESCAPE_CHAR is the hierarchy separator, use this instead. */ #define DSYNC_LIST_VNAME_ALT_ESCAPE_CHAR '~' #define DSYNC_DEFAULT_IO_STREAM_TIMEOUT_SECS (60*10) enum dsync_run_type { DSYNC_RUN_TYPE_LOCAL, DSYNC_RUN_TYPE_STREAM, DSYNC_RUN_TYPE_CMD }; struct dsync_cmd_context { struct doveadm_mail_cmd_context ctx; enum dsync_brain_sync_type sync_type; const char *mailbox; const char *sync_flags; const char *virtual_all_box; guid_128_t mailbox_guid; const char *state_input, *rawlog_path; ARRAY_TYPE(const_string) exclude_mailboxes; ARRAY_TYPE(const_string) namespace_prefixes; time_t sync_since_timestamp; time_t sync_until_timestamp; uoff_t sync_max_size; unsigned int io_timeout_secs; const char *remote_name; const char *local_location; pid_t remote_pid; const char *const *remote_cmd_args; struct child_wait *child_wait; int exit_status; int fd_in, fd_out, fd_err; struct io *io_err; struct istream *input, *err_stream; struct ostream *output; size_t input_orig_bufsize, output_orig_bufsize; struct ssl_iostream_context *ssl_ctx; struct ssl_iostream *ssl_iostream; enum dsync_run_type run_type; struct server_connection *tcp_conn; const char *error; unsigned int lock_timeout; unsigned int import_commit_msgs_interval; bool lock:1; bool purge_remote:1; bool sync_visible_namespaces:1; bool default_replica_location:1; bool oneway:1; bool backup:1; bool reverse_backup:1; bool remote_user_prefix:1; bool no_mail_sync:1; bool local_location_from_arg:1; bool replicator_notify:1; bool exited:1; bool empty_hdr_workaround:1; bool no_header_hashes:1; }; static bool legacy_dsync = FALSE; static void dsync_cmd_switch_ioloop_to(struct dsync_cmd_context *ctx, struct ioloop *ioloop) { if (ctx->input != NULL) i_stream_switch_ioloop_to(ctx->input, ioloop); if (ctx->output != NULL) o_stream_switch_ioloop_to(ctx->output, ioloop); } static void remote_error_input(struct dsync_cmd_context *ctx) { const unsigned char *data; size_t size; const char *line; switch (i_stream_read(ctx->err_stream)) { case -2: data = i_stream_get_data(ctx->err_stream, &size); fprintf(stderr, "%.*s", (int)size, data); i_stream_skip(ctx->err_stream, size); break; case -1: io_remove(&ctx->io_err); break; default: while ((line = i_stream_next_line(ctx->err_stream)) != NULL) fprintf(stderr, "%s\n", line); break; } } static void run_cmd(struct dsync_cmd_context *ctx, const char *const *args) { struct doveadm_cmd_context *cctx = ctx->ctx.cctx; int fd_in[2], fd_out[2], fd_err[2]; ctx->remote_cmd_args = p_strarray_dup(ctx->ctx.pool, args); if (pipe(fd_in) < 0 || pipe(fd_out) < 0 || pipe(fd_err) < 0) i_fatal("pipe() failed: %m"); ctx->remote_pid = fork(); switch (ctx->remote_pid) { case -1: i_fatal("fork() failed: %m"); case 0: /* child, which will execute the proxy server. stdin/stdout goes to pipes which we'll pass to proxy client. */ if (dup2(fd_in[0], STDIN_FILENO) < 0 || dup2(fd_out[1], STDOUT_FILENO) < 0 || dup2(fd_err[1], STDERR_FILENO) < 0) i_fatal("dup2() failed: %m"); i_close_fd(&fd_in[0]); i_close_fd(&fd_in[1]); i_close_fd(&fd_out[0]); i_close_fd(&fd_out[1]); i_close_fd(&fd_err[0]); i_close_fd(&fd_err[1]); execvp_const(args[0], args); default: /* parent */ break; } i_close_fd(&fd_in[0]); i_close_fd(&fd_out[1]); i_close_fd(&fd_err[1]); ctx->fd_in = fd_out[0]; ctx->fd_out = fd_in[1]; ctx->fd_err = fd_err[0]; if (ctx->remote_user_prefix) { const char *prefix = t_strdup_printf("%s\n", cctx->username); if (write_full(ctx->fd_out, prefix, strlen(prefix)) < 0) i_fatal("write(remote out) failed: %m"); } fd_set_nonblock(ctx->fd_err, TRUE); ctx->err_stream = i_stream_create_fd(ctx->fd_err, IO_BLOCK_SIZE); i_stream_set_return_partial_line(ctx->err_stream, TRUE); } static void mirror_get_remote_cmd_line(const char *const *argv, const char *const **cmd_args_r) { ARRAY_TYPE(const_string) cmd_args; unsigned int i; const char *p; i_assert(argv[0] != NULL); t_array_init(&cmd_args, 16); for (i = 0; argv[i] != NULL; i++) { p = argv[i]; array_push_back(&cmd_args, &p); } if (legacy_dsync) { /* we're executing dsync */ p = "server"; } else if (i > 0 && strcmp(argv[i-1], "dsync-server") == 0) { /* Caller already specified dsync-server in parameters. This is a common misconfiguration, so just allow it. */ p = NULL; } else { /* we're executing doveadm */ p = "dsync-server"; } if (p != NULL) array_push_back(&cmd_args, &p); array_append_zero(&cmd_args); *cmd_args_r = array_front(&cmd_args); } static const char *const * get_ssh_cmd_args(const char *host, const char *login, const char *mail_user) { static struct var_expand_table static_tab[] = { { 'u', NULL, "user" }, { '\0', NULL, "login" }, { '\0', NULL, "host" }, { '\0', NULL, NULL } }; struct var_expand_table *tab; ARRAY_TYPE(const_string) cmd_args; string_t *str, *str2; const char *value, *const *args, *error; tab = t_malloc_no0(sizeof(static_tab)); memcpy(tab, static_tab, sizeof(static_tab)); tab[0].value = mail_user; tab[1].value = login; tab[2].value = host; t_array_init(&cmd_args, 8); str = t_str_new(128); str2 = t_str_new(128); args = t_strsplit(doveadm_settings->dsync_remote_cmd, " "); for (; *args != NULL; args++) { if (strchr(*args, '%') == NULL) value = *args; else { /* some automation: if parameter's all %variables expand to empty, but the %variable isn't the only text in the parameter, skip it. */ str_truncate(str, 0); str_truncate(str2, 0); if (var_expand(str, *args, tab, &error) <= 0 || var_expand(str2, *args, static_tab, &error) <= 0) { i_error("Failed to expand dsync_remote_cmd=%s: %s", *args, error); } if (strcmp(str_c(str), str_c(str2)) == 0 && str_len(str) > 0) continue; value = t_strdup(str_c(str)); } array_push_back(&cmd_args, &value); } array_append_zero(&cmd_args); return array_front(&cmd_args); } static bool mirror_get_remote_cmd(struct dsync_cmd_context *ctx, const char *user, const char *const **cmd_args_r) { const char *p, *host, *const *argv = ctx->ctx.args; if (argv[1] != NULL) { /* more than one parameter, so it contains a full command (e.g. ssh host dsync) */ mirror_get_remote_cmd_line(argv, cmd_args_r); return TRUE; } /* if it begins with /[a-z0-9]+:/, it's a mail location (e.g. mdbox:~/mail) */ for (p = argv[0]; *p != '\0'; p++) { if (!i_isalnum(*p)) { if (*p == ':') return FALSE; break; } } if (strchr(argv[0], ' ') != NULL || strchr(argv[0], '/') != NULL) { /* a) the whole command is in one string. this is mainly for backwards compatibility. b) script/path */ mirror_get_remote_cmd_line(t_strsplit(argv[0], " "), cmd_args_r); return TRUE; } /* [user@]host */ host = strchr(argv[0], '@'); if (host != NULL) user = t_strdup_until(argv[0], host++); else host = argv[0]; /* we'll assume virtual users, so in user@host it really means not to give ssh a username, but to give dsync -u user parameter. */ *cmd_args_r = get_ssh_cmd_args(host, "", user); return TRUE; } static void doveadm_user_init_dsync(struct mail_user *user) { struct mail_namespace *ns; char ns_sep = mail_namespaces_get_root_sep(user->namespaces); user->dsyncing = TRUE; for (ns = user->namespaces; ns != NULL; ns = ns->next) { struct dsync_mailbox_list *dlist = p_new(ns->list->pool, struct dsync_mailbox_list, 1); MODULE_CONTEXT_SET(ns->list, dsync_mailbox_list_module, dlist); if (ns->list->set.vname_escape_char == '\0') { ns->list->set.vname_escape_char = ns_sep != DSYNC_LIST_VNAME_ESCAPE_CHAR ? DSYNC_LIST_VNAME_ESCAPE_CHAR : DSYNC_LIST_VNAME_ALT_ESCAPE_CHAR; } else { dlist->have_orig_escape_char = TRUE; } } } static bool paths_are_equal(struct mail_namespace *ns1, struct mail_namespace *ns2, enum mailbox_list_path_type type) { const char *path1, *path2; return mailbox_list_get_root_path(ns1->list, type, &path1) && mailbox_list_get_root_path(ns2->list, type, &path2) && strcmp(path1, path2) == 0; } static int get_dsync_verify_namespace(struct dsync_cmd_context *ctx, struct mail_user *user, struct mail_namespace **ns_r) { struct mail_namespace *ns; /* Use the first -n namespace if given */ if (array_count(&ctx->namespace_prefixes) > 0) { const char *prefix = array_idx_elem(&ctx->namespace_prefixes, 0); ns = mail_namespace_find(user->namespaces, prefix); if (ns == NULL) { i_error("Namespace not found: '%s'", prefix); ctx->ctx.exit_code = DOVEADM_EX_NOTFOUND; return -1; } *ns_r = ns; return 0; } /* Prefer prefix="" namespace over inbox=yes namespace. Either it uses the global mail_location, which is good, or it might have overwritten location in case of e.g. using subscriptions file for all namespaces. This isn't necessarily obvious, so lets make it clearer by failing if it happens. */ if ((user->namespaces->flags & NAMESPACE_FLAG_UNUSABLE) == 0) { *ns_r = user->namespaces; i_assert((*ns_r)->prefix_len == 0); } else { /* fallback to inbox=yes */ *ns_r = mail_namespace_find_inbox(user->namespaces); } return 0; } static int cmd_dsync_run_local(struct dsync_cmd_context *ctx, struct mail_user *user, struct dsync_brain *brain, struct dsync_ibc *ibc2, const char **changes_during_sync_r, enum mail_error *mail_error_r) { struct dsync_brain *brain2; struct mail_user *user2; struct mail_namespace *ns, *ns2; struct setting_parser_context *set_parser; const char *location, *error; bool brain1_running, brain2_running, changed1, changed2; bool remote_only_changes; int ret; *mail_error_r = 0; if (ctx->local_location_from_arg) location = ctx->ctx.args[0]; else { i_assert(ctx->local_location != NULL); location = ctx->local_location; } i_set_failure_prefix("dsync(%s): ", user->username); /* update mail_location and create another user for the second location. */ set_parser = mail_storage_service_user_get_settings_parser(ctx->ctx.cur_service_user); if (settings_parse_keyvalue(set_parser, "mail_location", location) < 0) i_unreached(); ret = mail_storage_service_next(ctx->ctx.storage_service, ctx->ctx.cur_service_user, &user2, &error); if (ret < 0) { i_error("Failed to initialize user: %s", error); ctx->ctx.exit_code = ret == -1 ? EX_TEMPFAIL : EX_CONFIG; return -1; } doveadm_user_init_dsync(user2); if (get_dsync_verify_namespace(ctx, user, &ns) < 0 || get_dsync_verify_namespace(ctx, user2, &ns2) < 0) return -1; if (mail_namespace_get_sep(ns) != mail_namespace_get_sep(ns2)) { i_error("Mail locations must use the same hierarchy separator " "(specify namespace prefix=\"%s\" " "{ separator } explicitly)", ns->prefix); ctx->ctx.exit_code = EX_CONFIG; mail_user_deinit(&user2); return -1; } if (paths_are_equal(ns, ns2, MAILBOX_LIST_PATH_TYPE_MAILBOX) && paths_are_equal(ns, ns2, MAILBOX_LIST_PATH_TYPE_INDEX)) { i_error("Both source and destination mail_location " "points to same directory: %s (namespace " "prefix=\"%s\" { location } is set explicitly?)", mailbox_list_get_root_forced(user->namespaces->list, MAILBOX_LIST_PATH_TYPE_MAILBOX), ns->prefix); ctx->ctx.exit_code = EX_CONFIG; mail_user_deinit(&user2); return -1; } brain2 = dsync_brain_slave_init(user2, ibc2, TRUE, "", doveadm_settings->dsync_alt_char[0]); mail_user_unref(&user2); brain1_running = brain2_running = TRUE; changed1 = changed2 = TRUE; while (brain1_running || brain2_running) { if (dsync_brain_has_failed(brain) || dsync_brain_has_failed(brain2)) break; if (doveadm_is_killed()) { i_warning("Killed with signal %d", doveadm_killed_signo()); break; } i_assert(changed1 || changed2); brain1_running = dsync_brain_run(brain, &changed1); brain2_running = dsync_brain_run(brain2, &changed2); } *changes_during_sync_r = t_strdup(dsync_brain_get_unexpected_changes_reason(brain2, &remote_only_changes)); if (dsync_brain_deinit(&brain2, mail_error_r) < 0) return -1; return doveadm_is_killed() ? -1 : 0; } static void cmd_dsync_remote_exited(const struct child_wait_status *status, struct dsync_cmd_context *ctx) { ctx->exited = TRUE; ctx->exit_status = status->status; io_loop_stop(current_ioloop); } static void cmd_dsync_wait_remote(struct dsync_cmd_context *ctx) { struct timeout *to; /* wait in ioloop for the remote process to die. while we're running we're also reading and printing all errors that still coming from it. */ to = timeout_add(DSYNC_REMOTE_CMD_EXIT_WAIT_SECS*1000, io_loop_stop, current_ioloop); io_loop_run(current_ioloop); timeout_remove(&to); /* io_loop_run() deactivates the context - put it back */ mail_storage_service_io_activate_user(ctx->ctx.cur_service_user); if (!ctx->exited) { i_error("Remote command process isn't dying, killing it"); if (kill(ctx->remote_pid, SIGKILL) < 0 && errno != ESRCH) { i_error("kill(%ld, SIGKILL) failed: %m", (long)ctx->remote_pid); } } } static void cmd_dsync_log_remote_status(int status, bool remote_errors_logged, const char *const *remote_cmd_args) { if (status == -1) ; else if (WIFSIGNALED(status)) { i_error("Remote command died with signal %d: %s", WTERMSIG(status), t_strarray_join(remote_cmd_args, " ")); } else if (!WIFEXITED(status)) { i_error("Remote command failed with status %d: %s", status, t_strarray_join(remote_cmd_args, " ")); } else if (WEXITSTATUS(status) == EX_TEMPFAIL && remote_errors_logged) { /* remote most likely already logged the error. don't bother logging another line about it */ } else if (WEXITSTATUS(status) != 0) { i_error("Remote command returned error %d: %s", WEXITSTATUS(status), t_strarray_join(remote_cmd_args, " ")); } } static void cmd_dsync_run_remote(struct mail_user *user) { i_set_failure_prefix("dsync-local(%s)<%s>: ", user->username, user->session_id); io_loop_run(current_ioloop); } static const char *const * parse_ssh_location(const char *location, const char *username) { const char *host, *login; host = strrchr(location, '@'); if (host != NULL) login = t_strdup_until(location, host++); else { host = location; login = ""; } return get_ssh_cmd_args(host, login, username); } static struct dsync_ibc * cmd_dsync_ibc_stream_init(struct dsync_cmd_context *ctx, const char *name, const char *temp_prefix) { if (ctx->input == NULL) { fd_set_nonblock(ctx->fd_in, TRUE); fd_set_nonblock(ctx->fd_out, TRUE); ctx->input = i_stream_create_fd(ctx->fd_in, SIZE_MAX); ctx->output = o_stream_create_fd(ctx->fd_out, SIZE_MAX); } else { i_assert(ctx->fd_in == -1 && ctx->fd_out == -1); ctx->fd_in = i_stream_get_fd(ctx->input); ctx->fd_out = o_stream_get_fd(ctx->output); ctx->input_orig_bufsize = i_stream_get_max_buffer_size(ctx->input); ctx->output_orig_bufsize = o_stream_get_max_buffer_size(ctx->output); i_stream_set_max_buffer_size(ctx->input, SIZE_MAX); o_stream_set_max_buffer_size(ctx->output, SIZE_MAX); } if (ctx->rawlog_path != NULL) { iostream_rawlog_create_path(ctx->rawlog_path, &ctx->input, &ctx->output); } return dsync_ibc_init_stream(ctx->input, ctx->output, name, temp_prefix, ctx->io_timeout_secs); } static void dsync_replicator_notify(struct dsync_cmd_context *ctx, enum dsync_brain_sync_type sync_type, const char *state_str) { #define REPLICATOR_HANDSHAKE "VERSION\treplicator-doveadm-client\t1\t0\n" const char *path; string_t *str; int fd; path = t_strdup_printf("%s/replicator-doveadm", ctx->ctx.cur_mail_user->set->base_dir); fd = net_connect_unix(path); if (fd == -1) { if (errno == ECONNREFUSED || errno == ENOENT) { /* replicator not running on this server. ignore. */ return; } i_error("net_connect_unix(%s) failed: %m", path); return; } fd_set_nonblock(fd, FALSE); str = t_str_new(128); str_append(str, REPLICATOR_HANDSHAKE"NOTIFY\t"); str_append_tabescaped(str, ctx->ctx.cur_mail_user->username); str_append_c(str, '\t'); if (sync_type == DSYNC_BRAIN_SYNC_TYPE_FULL) str_append_c(str, 'f'); str_append_c(str, '\t'); str_append_tabescaped(str, state_str); str_append_c(str, '\n'); if (write_full(fd, str_data(str), str_len(str)) < 0) i_error("write(%s) failed: %m", path); /* we only wanted to notify replicator. we don't care enough about the answer to wait for it. */ if (close(fd) < 0) i_error("close(%s) failed: %m", path); } static int cmd_dsync_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct dsync_cmd_context *ctx = (struct dsync_cmd_context *)_ctx; struct doveadm_cmd_context *cctx = _ctx->cctx; struct dsync_ibc *ibc, *ibc2 = NULL; struct dsync_brain *brain; struct dsync_brain_settings set; struct mail_namespace *ns; const char *const *strp; enum dsync_brain_flags brain_flags; enum mail_error mail_error = 0, mail_error2; bool remote_errors_logged = FALSE; bool cli = (cctx->conn_type == DOVEADM_CONNECTION_TYPE_CLI); const char *changes_during_sync, *changes_during_sync2 = NULL; bool remote_only_changes; int ret = 0; /* replicator_notify indicates here automated attempt, we still want to allow manual sync/backup */ if (!cli && ctx->replicator_notify && mail_user_plugin_getenv_bool(_ctx->cur_mail_user, "noreplicate")) { ctx->ctx.exit_code = DOVEADM_EX_NOREPLICATE; return -1; } i_zero(&set); if (cctx->remote_ip.family != 0) { /* include the doveadm client's IP address in the ps output */ set.process_title_prefix = t_strdup_printf( "%s ", net_ip2addr(&cctx->remote_ip)); } set.sync_since_timestamp = ctx->sync_since_timestamp; set.sync_until_timestamp = ctx->sync_until_timestamp; if (set.sync_since_timestamp > 0 && set.sync_until_timestamp > 0 && set.sync_since_timestamp > set.sync_until_timestamp) { i_fatal("start date is later than end date"); } set.sync_max_size = ctx->sync_max_size; set.sync_box = ctx->mailbox; set.sync_flag = ctx->sync_flags; set.virtual_all_box = ctx->virtual_all_box; memcpy(set.sync_box_guid, ctx->mailbox_guid, sizeof(set.sync_box_guid)); set.lock_timeout_secs = ctx->lock_timeout; set.import_commit_msgs_interval = ctx->import_commit_msgs_interval; set.state = ctx->state_input; set.mailbox_alt_char = doveadm_settings->dsync_alt_char[0]; if (*doveadm_settings->dsync_hashed_headers == '\0') { i_error("dsync_hashed_headers must not be empty"); ctx->ctx.exit_code = EX_USAGE; return -1; } set.hashed_headers = t_strsplit_spaces(doveadm_settings->dsync_hashed_headers, " ,"); if (array_count(&ctx->exclude_mailboxes) > 0) { /* array is NULL-terminated in init() */ set.exclude_mailboxes = array_front(&ctx->exclude_mailboxes); } doveadm_user_init_dsync(user); t_array_init(&set.sync_namespaces, array_count(&ctx->namespace_prefixes)); array_foreach(&ctx->namespace_prefixes, strp) { ns = mail_namespace_find(user->namespaces, *strp); if (ns == NULL) { i_error("Namespace not found: '%s'", *strp); ctx->ctx.exit_code = EX_USAGE; return -1; } array_push_back(&set.sync_namespaces, &ns); } if (ctx->run_type == DSYNC_RUN_TYPE_LOCAL) dsync_ibc_init_pipe(&ibc, &ibc2); else { string_t *temp_prefix = t_str_new(64); mail_user_set_get_temp_prefix(temp_prefix, user->set); ibc = cmd_dsync_ibc_stream_init(ctx, ctx->remote_name, str_c(temp_prefix)); if (ctx->fd_err != -1) { ctx->io_err = io_add(ctx->fd_err, IO_READ, remote_error_input, ctx); } } brain_flags = DSYNC_BRAIN_FLAG_SEND_MAIL_REQUESTS; if (ctx->sync_visible_namespaces) brain_flags |= DSYNC_BRAIN_FLAG_SYNC_VISIBLE_NAMESPACES; if (ctx->purge_remote) brain_flags |= DSYNC_BRAIN_FLAG_PURGE_REMOTE; if (ctx->backup) { if (ctx->reverse_backup) brain_flags |= DSYNC_BRAIN_FLAG_BACKUP_RECV; else brain_flags |= DSYNC_BRAIN_FLAG_BACKUP_SEND; } if (ctx->no_mail_sync) brain_flags |= DSYNC_BRAIN_FLAG_NO_MAIL_SYNC; if (ctx->oneway) brain_flags |= DSYNC_BRAIN_FLAG_NO_BACKUP_OVERWRITE; if (ctx->empty_hdr_workaround) brain_flags |= DSYNC_BRAIN_FLAG_EMPTY_HDR_WORKAROUND; if (ctx->no_header_hashes) brain_flags |= DSYNC_BRAIN_FLAG_NO_HEADER_HASHES; if (doveadm_debug) brain_flags |= DSYNC_BRAIN_FLAG_DEBUG; child_wait_init(); brain = dsync_brain_master_init(user, ibc, ctx->sync_type, brain_flags, &set); switch (ctx->run_type) { case DSYNC_RUN_TYPE_LOCAL: if (cmd_dsync_run_local(ctx, user, brain, ibc2, &changes_during_sync2, &mail_error) < 0) ret = -1; break; case DSYNC_RUN_TYPE_CMD: ctx->child_wait = child_wait_new_with_pid(ctx->remote_pid, cmd_dsync_remote_exited, ctx); /* fall through */ case DSYNC_RUN_TYPE_STREAM: cmd_dsync_run_remote(user); /* io_loop_run() deactivates the context - put it back */ mail_storage_service_io_activate_user(ctx->ctx.cur_service_user); break; } if (ctx->state_input != NULL) { string_t *state_str = t_str_new(128); dsync_brain_get_state(brain, state_str); doveadm_print(str_c(state_str)); } changes_during_sync = dsync_brain_get_unexpected_changes_reason(brain, &remote_only_changes); if (changes_during_sync != NULL || changes_during_sync2 != NULL) { /* don't log a warning when running via doveadm server (e.g. called by replicator) */ if (cctx->conn_type == DOVEADM_CONNECTION_TYPE_CLI) { i_warning("Mailbox changes caused a desync. " "You may want to run dsync again: %s", changes_during_sync == NULL || (remote_only_changes && changes_during_sync2 != NULL) ? changes_during_sync2 : changes_during_sync); } ctx->ctx.exit_code = 2; } if (dsync_brain_deinit(&brain, &mail_error2) < 0) ret = -1; if (ret < 0) { /* tempfail is the default error. prefer to use a non-tempfail if that exists. */ if (mail_error2 != 0 && (mail_error == 0 || mail_error == MAIL_ERROR_TEMP)) mail_error = mail_error2; doveadm_mail_failed_error(&ctx->ctx, mail_error); } dsync_ibc_deinit(&ibc); if (ibc2 != NULL) dsync_ibc_deinit(&ibc2); ssl_iostream_destroy(&ctx->ssl_iostream); if (ctx->ssl_ctx != NULL) ssl_iostream_context_unref(&ctx->ssl_ctx); if (ctx->input != NULL) { i_stream_set_max_buffer_size(ctx->input, ctx->input_orig_bufsize); i_stream_unref(&ctx->input); } if (ctx->output != NULL) { o_stream_set_max_buffer_size(ctx->output, ctx->output_orig_bufsize); o_stream_unref(&ctx->output); } if (ctx->fd_in != -1) { if (ctx->fd_out != ctx->fd_in) i_close_fd(&ctx->fd_out); i_close_fd(&ctx->fd_in); } /* print any final errors after the process has died. not closing stdin/stdout before wait() may cause the process to hang, but stderr shouldn't (at least with ssh) and we need stderr to be open to be able to print the final errors */ if (ctx->run_type == DSYNC_RUN_TYPE_CMD) { cmd_dsync_wait_remote(ctx); remote_error_input(ctx); remote_errors_logged = ctx->err_stream->v_offset > 0; i_stream_destroy(&ctx->err_stream); cmd_dsync_log_remote_status(ctx->exit_status, remote_errors_logged, ctx->remote_cmd_args); } else { i_assert(ctx->err_stream == NULL); } io_remove(&ctx->io_err); i_close_fd(&ctx->fd_err); if (ctx->child_wait != NULL) child_wait_free(&ctx->child_wait); child_wait_deinit(); return ret; } static void dsync_connected_callback(int exit_code, const char *error, void *context) { struct dsync_cmd_context *ctx = context; ctx->ctx.exit_code = exit_code; switch (exit_code) { case 0: server_connection_extract(ctx->tcp_conn, &ctx->input, &ctx->output, &ctx->ssl_iostream); break; case SERVER_EXIT_CODE_DISCONNECTED: ctx->ctx.exit_code = EX_TEMPFAIL; ctx->error = p_strdup_printf(ctx->ctx.pool, "Disconnected from remote: %s", error); break; case EX_NOUSER: ctx->error = "Unknown user in remote"; break; case DOVEADM_EX_NOREPLICATE: if (doveadm_debug) i_debug("user is disabled for replication"); break; default: ctx->error = p_strdup_printf(ctx->ctx.pool, "Failed to start remote dsync-server command: " "Remote exit_code=%u %s", exit_code, error == NULL ? "" : error); break; } io_loop_stop(current_ioloop); } static int dsync_init_ssl_ctx(struct dsync_cmd_context *ctx, const struct master_service_ssl_settings *ssl_set, const char **error_r) { struct ssl_iostream_settings ssl_ctx_set; if (ctx->ssl_ctx != NULL) return 0; master_service_ssl_client_settings_to_iostream_set(ssl_set, pool_datastack_create(), &ssl_ctx_set); return ssl_iostream_client_context_cache_get(&ssl_ctx_set, &ctx->ssl_ctx, error_r); } static void dsync_server_run_command(struct dsync_cmd_context *ctx, struct server_connection *conn) { struct doveadm_cmd_context *cctx = ctx->ctx.cctx; /* [] */ string_t *cmd = t_str_new(256); if (doveadm_debug) str_append_c(cmd, 'D'); str_append_c(cmd, '\t'); str_append_tabescaped(cmd, cctx->username); str_append(cmd, "\tdsync-server\t-u"); str_append_tabescaped(cmd, cctx->username); if (ctx->replicator_notify) str_append(cmd, "\t-U"); str_append_c(cmd, '\n'); ctx->tcp_conn = conn; server_connection_cmd(conn, str_c(cmd), NULL, dsync_connected_callback, ctx); io_loop_run(current_ioloop); ctx->tcp_conn = NULL; } static int dsync_connect_tcp(struct dsync_cmd_context *ctx, const struct master_service_ssl_settings *ssl_set, const char *target, bool ssl, const char **error_r) { struct doveadm_server *server; struct server_connection *conn; struct ioloop *prev_ioloop, *ioloop; const char *p, *error; server = p_new(ctx->ctx.pool, struct doveadm_server, 1); server->name = p_strdup(ctx->ctx.pool, target); p = strrchr(server->name, ':'); server->hostname = p == NULL ? server->name : p_strdup_until(ctx->ctx.pool, server->name, p); if (ssl) { if (dsync_init_ssl_ctx(ctx, ssl_set, &error) < 0) { *error_r = t_strdup_printf( "Couldn't initialize SSL context: %s", error); return -1; } server->ssl_flags = PROXY_SSL_FLAG_YES; server->ssl_ctx = ctx->ssl_ctx; } p_array_init(&server->connections, ctx->ctx.pool, 1); p_array_init(&server->queue, ctx->ctx.pool, 1); prev_ioloop = current_ioloop; ioloop = io_loop_create(); dsync_cmd_switch_ioloop_to(ctx, ioloop); if (doveadm_verbose_proctitle) { process_title_set(t_strdup_printf( "[dsync - connecting to %s]", server->name)); } if (server_connection_create(server, &conn, &error) < 0) { ctx->error = p_strdup_printf(ctx->ctx.pool, "Couldn't create server connection: %s", error); } else { if (doveadm_verbose_proctitle) { process_title_set(t_strdup_printf( "[dsync - running dsync-server on %s]", server->name)); } dsync_server_run_command(ctx, conn); } if (array_count(&server->connections) > 0) server_connection_destroy(&conn); dsync_cmd_switch_ioloop_to(ctx, prev_ioloop); io_loop_destroy(&ioloop); if (ctx->error != NULL) { ssl_iostream_context_unref(&ctx->ssl_ctx); *error_r = ctx->error; ctx->error = NULL; return -1; } ctx->run_type = DSYNC_RUN_TYPE_STREAM; return 0; } static int parse_location(struct dsync_cmd_context *ctx, const struct master_service_ssl_settings *ssl_set, const char *location, const char *const **remote_cmd_args_r, const char **error_r) { struct doveadm_cmd_context *cctx = ctx->ctx.cctx; if (str_begins(location, "tcp:")) { /* TCP connection to remote dsync */ ctx->remote_name = location+4; return dsync_connect_tcp(ctx, ssl_set, ctx->remote_name, FALSE, error_r); } if (str_begins(location, "tcps:")) { /* TCP+SSL connection to remote dsync */ ctx->remote_name = location+5; return dsync_connect_tcp(ctx, ssl_set, ctx->remote_name, TRUE, error_r); } if (str_begins(location, "remote:")) { /* this is a remote (ssh) command */ ctx->remote_name = location+7; } else if (str_begins(location, "remoteprefix:")) { /* this is a remote (ssh) command with a "user\n" prefix sent before dsync actually starts */ ctx->remote_name = location+13; ctx->remote_user_prefix = TRUE; } else { /* local with e.g. maildir:path */ ctx->remote_name = NULL; return 0; } *remote_cmd_args_r = parse_ssh_location(ctx->remote_name, cctx->username); return 0; } static int cmd_dsync_prerun(struct doveadm_mail_cmd_context *_ctx, struct mail_storage_service_user *service_user, const char **error_r) { struct dsync_cmd_context *ctx = (struct dsync_cmd_context *)_ctx; struct doveadm_cmd_context *cctx = _ctx->cctx; const char *const *remote_cmd_args = NULL; const struct mail_user_settings *user_set; const struct master_service_ssl_settings *ssl_set; const char *username = ""; user_set = mail_storage_service_user_get_set(service_user)[0]; ssl_set = mail_storage_service_user_get_ssl_settings(service_user); ctx->fd_in = -1; ctx->fd_out = -1; ctx->fd_err = -1; ctx->run_type = DSYNC_RUN_TYPE_LOCAL; ctx->remote_name = "remote"; if (ctx->default_replica_location) { ctx->local_location = mail_user_set_plugin_getenv(user_set, "mail_replica"); if (ctx->local_location == NULL || *ctx->local_location == '\0') { *error_r = "User has no mail_replica in userdb"; _ctx->exit_code = DOVEADM_EX_NOTFOUND; return -1; } } else { /* if we're executing remotely, give -u parameter if we also did a userdb lookup. */ if ((_ctx->service_flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) != 0) username = cctx->username; if (!mirror_get_remote_cmd(ctx, username, &remote_cmd_args)) { /* it's a mail_location */ if (_ctx->args[1] != NULL) doveadm_mail_help_name(_ctx->cmd->name); ctx->local_location = _ctx->args[0]; ctx->local_location_from_arg = TRUE; } } if (remote_cmd_args == NULL && ctx->local_location != NULL) { if (parse_location(ctx, ssl_set, ctx->local_location, &remote_cmd_args, error_r) < 0) return -1; } if (remote_cmd_args != NULL) { /* do this before mail_storage_service_next() in case it drops process privileges */ run_cmd(ctx, remote_cmd_args); ctx->run_type = DSYNC_RUN_TYPE_CMD; } if (ctx->sync_visible_namespaces && ctx->run_type == DSYNC_RUN_TYPE_LOCAL) i_fatal("-N parameter requires syncing with remote host"); return 0; } static void cmd_dsync_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct dsync_cmd_context *ctx = (struct dsync_cmd_context *)_ctx; if (ctx->default_replica_location) { if (args[0] != NULL) i_error("Don't give mail location with -d parameter"); } else { if (args[0] == NULL) doveadm_mail_help_name(_ctx->cmd->name); } if (array_count(&ctx->exclude_mailboxes) > 0) array_append_zero(&ctx->exclude_mailboxes); lib_signals_ignore(SIGHUP, TRUE); } static void cmd_dsync_preinit(struct doveadm_mail_cmd_context *ctx) { if ((ctx->service_flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) == 0) ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_NO_CHDIR; } static bool cmd_mailbox_dsync_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct dsync_cmd_context *ctx = (struct dsync_cmd_context *)_ctx; const char *str, *error; bool utc; switch (c) { case '1': ctx->oneway = TRUE; ctx->backup = TRUE; break; case 'a': ctx->virtual_all_box = optarg; break; case 'd': ctx->default_replica_location = TRUE; break; case 'E': /* dsync wrapper detection flag */ legacy_dsync = TRUE; break; case 'f': ctx->sync_type = DSYNC_BRAIN_SYNC_TYPE_FULL; break; case 'O': { const char *str = optarg; if (strchr(str, ' ') != NULL) i_fatal("-O parameter doesn't support multiple flags currently"); if (str[0] == '-') str++; if (str[0] == '\\' && imap_parse_system_flag(str) == 0) i_fatal("Invalid system flag given for -O parameter: '%s'", str); ctx->sync_flags = optarg; break; } case 'g': if (optarg[0] == '\0') ctx->no_mail_sync = TRUE; else if (guid_128_from_string(optarg, ctx->mailbox_guid) < 0 || guid_128_is_empty(ctx->mailbox_guid)) i_fatal("Invalid -g parameter: %s", optarg); break; case 'l': ctx->lock = TRUE; if (str_to_uint(optarg, &ctx->lock_timeout) < 0) i_fatal("Invalid -l parameter: %s", optarg); break; case 'm': if (optarg[0] == '\0') ctx->no_mail_sync = TRUE; else ctx->mailbox = optarg; break; case 'x': str = optarg; array_push_back(&ctx->exclude_mailboxes, &str); break; case 'n': str = optarg; array_push_back(&ctx->namespace_prefixes, &str); break; case 'N': ctx->sync_visible_namespaces = TRUE; break; case 'P': ctx->purge_remote = TRUE; break; case 'r': ctx->rawlog_path = optarg; break; case 'R': ctx->reverse_backup = TRUE; break; case 's': if (ctx->sync_type != DSYNC_BRAIN_SYNC_TYPE_FULL && *optarg != '\0') ctx->sync_type = DSYNC_BRAIN_SYNC_TYPE_STATE; ctx->state_input = optarg; break; case 't': if (mail_parse_human_timestamp(optarg, &ctx->sync_since_timestamp, &utc) < 0) i_fatal("Invalid -t parameter: %s", optarg); break; case 'e': if (mail_parse_human_timestamp(optarg, &ctx->sync_until_timestamp, &utc) < 0) i_fatal("Invalid -e parameter: %s", optarg); break; case 'I': if (settings_get_size(optarg, &ctx->sync_max_size, &error) < 0) i_fatal("Invalid -I parameter '%s': %s", optarg, error); break; case 'T': if (str_to_uint(optarg, &ctx->io_timeout_secs) < 0) i_fatal("Invalid -T parameter: %s", optarg); break; case 'U': ctx->replicator_notify = TRUE; break; default: return FALSE; } return TRUE; } static struct doveadm_mail_cmd_context *cmd_dsync_alloc(void) { struct dsync_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct dsync_cmd_context); ctx->io_timeout_secs = DSYNC_DEFAULT_IO_STREAM_TIMEOUT_SECS; ctx->ctx.getopt_args = DSYNC_COMMON_GETOPT_ARGS; ctx->ctx.v.parse_arg = cmd_mailbox_dsync_parse_arg; ctx->ctx.v.preinit = cmd_dsync_preinit; ctx->ctx.v.init = cmd_dsync_init; ctx->ctx.v.prerun = cmd_dsync_prerun; ctx->ctx.v.run = cmd_dsync_run; ctx->sync_type = DSYNC_BRAIN_SYNC_TYPE_CHANGED; doveadm_print_init(DOVEADM_PRINT_TYPE_FLOW); doveadm_print_header("state", "state", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); p_array_init(&ctx->exclude_mailboxes, ctx->ctx.pool, 4); p_array_init(&ctx->namespace_prefixes, ctx->ctx.pool, 4); if ((doveadm_settings->parsed_features & DSYNC_FEATURE_EMPTY_HDR_WORKAROUND) != 0) ctx->empty_hdr_workaround = TRUE; if ((doveadm_settings->parsed_features & DSYNC_FEATURE_NO_HEADER_HASHES) != 0) ctx->no_header_hashes = TRUE; ctx->import_commit_msgs_interval = doveadm_settings->dsync_commit_msgs_interval; return &ctx->ctx; } static struct doveadm_mail_cmd_context *cmd_dsync_backup_alloc(void) { struct doveadm_mail_cmd_context *_ctx; struct dsync_cmd_context *ctx; _ctx = cmd_dsync_alloc(); ctx = (struct dsync_cmd_context *)_ctx; ctx->backup = TRUE; return _ctx; } static int cmd_dsync_server_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct dsync_cmd_context *ctx = (struct dsync_cmd_context *)_ctx; struct doveadm_cmd_context *cctx = _ctx->cctx; bool cli = (cctx->conn_type == DOVEADM_CONNECTION_TYPE_CLI); struct dsync_ibc *ibc; struct dsync_brain *brain; string_t *temp_prefix, *state_str = NULL; enum dsync_brain_sync_type sync_type; const char *name, *process_title_prefix = ""; enum mail_error mail_error; if (!cli) { /* replicator_notify indicates here automated attempt, we still want to allow manual sync/backup */ if (ctx->replicator_notify && mail_user_plugin_getenv_bool(_ctx->cur_mail_user, "noreplicate")) { _ctx->exit_code = DOVEADM_EX_NOREPLICATE; return -1; } /* doveadm-server connection. start with a success reply. after that follows the regular dsync protocol. */ ctx->fd_in = ctx->fd_out = -1; ctx->input = cctx->input; ctx->output = cctx->output; i_stream_ref(ctx->input); o_stream_ref(ctx->output); o_stream_set_finish_also_parent(ctx->output, FALSE); o_stream_nsend(ctx->output, "\n+\n", 3); i_set_failure_prefix("dsync-server(%s): ", user->username); name = i_stream_get_name(ctx->input); if (cctx->remote_ip.family != 0) { /* include the doveadm client's IP address in the ps output */ process_title_prefix = t_strdup_printf( "%s ", net_ip2addr(&cctx->remote_ip)); } } else { /* the log messages go via stderr to the remote dsync, so the names are reversed */ i_set_failure_prefix("dsync-remote(%s)<%s>: ", user->username, user->session_id); name = "local"; } doveadm_user_init_dsync(user); temp_prefix = t_str_new(64); mail_user_set_get_temp_prefix(temp_prefix, user->set); ibc = cmd_dsync_ibc_stream_init(ctx, name, str_c(temp_prefix)); brain = dsync_brain_slave_init(user, ibc, FALSE, process_title_prefix, doveadm_settings->dsync_alt_char[0]); io_loop_run(current_ioloop); /* io_loop_run() deactivates the context - put it back */ mail_storage_service_io_activate_user(ctx->ctx.cur_service_user); if (ctx->replicator_notify) { state_str = t_str_new(128); dsync_brain_get_state(brain, state_str); } sync_type = dsync_brain_get_sync_type(brain); if (dsync_brain_deinit(&brain, &mail_error) < 0) doveadm_mail_failed_error(_ctx, mail_error); dsync_ibc_deinit(&ibc); if (!cli) { /* make sure nothing more is written by the generic doveadm connection code */ o_stream_close(cctx->output); } i_stream_unref(&ctx->input); o_stream_unref(&ctx->output); if (ctx->replicator_notify && _ctx->exit_code == 0) dsync_replicator_notify(ctx, sync_type, str_c(state_str)); return _ctx->exit_code == 0 ? 0 : -1; } static bool cmd_mailbox_dsync_server_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct dsync_cmd_context *ctx = (struct dsync_cmd_context *)_ctx; switch (c) { case 'E': /* dsync wrapper detection flag */ legacy_dsync = TRUE; break; case 'r': ctx->rawlog_path = optarg; break; case 'T': if (str_to_uint(optarg, &ctx->io_timeout_secs) < 0) i_fatal("Invalid -T parameter: %s", optarg); break; case 'U': ctx->replicator_notify = TRUE; break; default: return FALSE; } return TRUE; } static struct doveadm_mail_cmd_context *cmd_dsync_server_alloc(void) { struct dsync_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct dsync_cmd_context); ctx->io_timeout_secs = DSYNC_DEFAULT_IO_STREAM_TIMEOUT_SECS; ctx->ctx.getopt_args = "Er:T:U"; ctx->ctx.v.parse_arg = cmd_mailbox_dsync_server_parse_arg; ctx->ctx.v.run = cmd_dsync_server_run; ctx->sync_type = DSYNC_BRAIN_SYNC_TYPE_CHANGED; ctx->fd_in = STDIN_FILENO; ctx->fd_out = STDOUT_FILENO; return &ctx->ctx; } #define DSYNC_COMMON_PARAMS \ DOVEADM_CMD_MAIL_COMMON \ DOVEADM_CMD_PARAM('f', "full-sync", CMD_PARAM_BOOL, 0) \ DOVEADM_CMD_PARAM('P', "purge-remote", CMD_PARAM_BOOL, 0) \ DOVEADM_CMD_PARAM('R', "reverse-sync", CMD_PARAM_BOOL, 0) \ DOVEADM_CMD_PARAM('U', "replicator-notify", CMD_PARAM_BOOL, 0) \ DOVEADM_CMD_PARAM('l', "lock-timeout", CMD_PARAM_INT64, 0) \ DOVEADM_CMD_PARAM('r', "rawlog", CMD_PARAM_STR, 0) \ DOVEADM_CMD_PARAM('m', "mailbox", CMD_PARAM_STR, 0) \ DOVEADM_CMD_PARAM('g', "mailbox-guid", CMD_PARAM_STR, 0) \ DOVEADM_CMD_PARAM('n', "namespace", CMD_PARAM_ARRAY, 0) \ DOVEADM_CMD_PARAM('N', "all-namespaces", CMD_PARAM_BOOL, 0) \ DOVEADM_CMD_PARAM('x', "exclude-mailbox", CMD_PARAM_ARRAY, 0) \ DOVEADM_CMD_PARAM('a', "all-mailbox", CMD_PARAM_STR, 0) \ DOVEADM_CMD_PARAM('s', "state", CMD_PARAM_STR, 0) \ DOVEADM_CMD_PARAM('t', "sync-since-time", CMD_PARAM_STR, 0) \ DOVEADM_CMD_PARAM('e', "sync-until-time", CMD_PARAM_STR, 0) \ DOVEADM_CMD_PARAM('O', "sync-flags", CMD_PARAM_STR, 0) \ DOVEADM_CMD_PARAM('I', "sync-max-size", CMD_PARAM_STR, 0) \ DOVEADM_CMD_PARAM('T', "timeout", CMD_PARAM_INT64, 0) \ DOVEADM_CMD_PARAM('d', "default-destination", CMD_PARAM_BOOL, 0) \ DOVEADM_CMD_PARAM('E', "legacy-dsync", CMD_PARAM_BOOL, 0) \ DOVEADM_CMD_PARAM('\0', "destination", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) #define DSYNC_COMMON_USAGE \ "[-l ] [-r ] " \ "[-m ] [-g ] [-n | -N] " \ "[-x ] [-a ] [-s ] [-T ] " \ "[-t ] [-e ] [-O ] [-I ] " \ "-d|" struct doveadm_cmd_ver2 doveadm_cmd_dsync_mirror = { .mail_cmd = cmd_dsync_alloc, .name = "sync", .usage = "[-1fPRU] "DSYNC_COMMON_USAGE, .flags = CMD_FLAG_NO_UNORDERED_OPTIONS, DOVEADM_CMD_PARAMS_START DSYNC_COMMON_PARAMS DOVEADM_CMD_PARAM('1', "oneway-sync", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_dsync_backup = { .mail_cmd = cmd_dsync_backup_alloc, .name = "backup", .usage = "[-fPRU] "DSYNC_COMMON_USAGE, .flags = CMD_FLAG_NO_UNORDERED_OPTIONS, DOVEADM_CMD_PARAMS_START DSYNC_COMMON_PARAMS DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_dsync_server = { .mail_cmd = cmd_dsync_server_alloc, .name = "dsync-server", .usage = "[-E] [-r ] [-T ] [-U]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('E', "legacy-dsync", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('r', "rawlog", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('T', "timeout", CMD_PARAM_INT64, 0) DOVEADM_CMD_PARAM('U', "replicator-notify", CMD_PARAM_BOOL, 0) /* previously dsync-server could have been added twice to the parameters */ DOVEADM_CMD_PARAM('\0', "ignore-arg", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; void doveadm_dsync_main(int *_argc, char **_argv[]) { int argc = *_argc; const char *getopt_str; char **argv = *_argv; char **new_argv, *mailbox = NULL, *alt_char = NULL, *username = NULL; char *p, *dup, new_flags[6]; int max_argc, src, dest, i, j; bool flag_f = FALSE, flag_R = FALSE, flag_m, flag_u, flag_C, has_arg; bool dsync_server = FALSE; p = strrchr(argv[0], '/'); if (p == NULL) p = argv[0]; if (strstr(p, "dsync") == NULL) return; /* @UNSAFE: this is called when the "doveadm" binary is called as "dsync" (for backwards compatibility) */ max_argc = argc + 7; new_argv = t_new(char *, max_argc); new_argv[0] = argv[0]; dest = 1; getopt_str = master_service_getopt_string(); /* add global doveadm flags */ for (src = 1; src < argc; src++) { if (argv[src][0] != '-') break; flag_m = FALSE; flag_C = FALSE; has_arg = FALSE; flag_u = FALSE; dup = t_strdup_noconst(argv[src]); for (i = j = 1; argv[src][i] != '\0'; i++) { switch (argv[src][i]) { case 'C': flag_C = TRUE; break; case 'f': flag_f = TRUE; break; case 'R': flag_R = TRUE; break; case 'm': flag_m = TRUE; break; case 'u': flag_u = TRUE; break; default: p = strchr(getopt_str, argv[src][i]); if (p != NULL && p[1] == ':') has_arg = TRUE; dup[j++] = argv[src][i]; break; } } if (j > 1) { dup[j++] = '\0'; new_argv[dest++] = dup; if (has_arg && src+1 < argc) new_argv[dest++] = argv[++src]; } if (flag_m) { if (src+1 == argc) i_fatal("-m missing parameter"); mailbox = argv[++src]; } if (flag_u) { if (src+1 == argc) i_fatal("-u missing parameter"); username = argv[++src]; } if (flag_C) { if (src+1 == argc) i_fatal("-C missing parameter"); alt_char = argv[++src]; } } if (alt_char != NULL) { new_argv[dest++] = "-o"; new_argv[dest++] = p_strconcat(pool_datastack_create(), "dsync_alt_char=", alt_char, NULL); } /* mirror|backup|server */ if (src == argc) i_fatal("Missing mirror or backup parameter"); if (strcmp(argv[src], "sync") == 0 || strcmp(argv[src], "dsync-server") == 0) { /* we're re-executing dsync due to doveconf. "backup" re-exec detection is later. */ return; } if (strcmp(argv[src], "mirror") == 0) new_argv[dest] = "sync"; else if (strcmp(argv[src], "backup") == 0) new_argv[dest] = "backup"; else if (strcmp(argv[src], "server") == 0) { new_argv[dest] = "dsync-server"; dsync_server = TRUE; } else i_fatal("Invalid parameter: %s", argv[src]); src++; dest++; if (src < argc && str_begins(argv[src], "-E")) { /* we're re-executing dsync due to doveconf */ return; } /* dsync flags */ new_flags[0] = '-'; new_flags[1] = 'E'; i = 2; if (!dsync_server) { if (flag_f) new_flags[i++] = 'f'; if (flag_R) new_flags[i++] = 'R'; if (mailbox != NULL) new_flags[i++] = 'm'; } i_assert((unsigned int)i < sizeof(new_flags)); new_flags[i] = '\0'; if (i > 1) { new_argv[dest++] = strdup(new_flags); if (mailbox != NULL) new_argv[dest++] = mailbox; } if (username != NULL) { new_argv[dest++] = "-u"; new_argv[dest++] = username; } /* rest of the parameters */ for (; src < argc; src++) new_argv[dest++] = argv[src]; i_assert(dest < max_argc); new_argv[dest] = NULL; legacy_dsync = TRUE; *_argc = dest; *_argv = new_argv; i_getopt_reset(); } dovecot-2.3.21.1/src/doveadm/doveadm-pw.c0000644000000000000000000000751614656633576014743 00000000000000/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "password-scheme.h" #include "randgen.h" #include "doveadm.h" #include "askpass.h" #include "module-dir.h" #include #include #include #include #define DEFAULT_SCHEME "CRYPT" static struct module *modules = NULL; static void cmd_pw(struct doveadm_cmd_context *cctx) { const char *hash = NULL; const char *scheme = NULL; const char *plaintext = NULL; const char *test_hash = NULL; bool list_schemes = FALSE, reverse_verify = FALSE; int64_t rounds_int64; struct module_dir_load_settings mod_set; struct password_generate_params gen_params; i_zero(&gen_params); password_schemes_init(); i_zero(&mod_set); mod_set.abi_version = DOVECOT_ABI_VERSION; mod_set.require_init_funcs = TRUE; mod_set.ignore_dlopen_errors = TRUE; mod_set.debug = doveadm_debug; modules = module_dir_load_missing(modules, AUTH_MODULE_DIR, NULL, &mod_set); module_dir_init(modules); (void)doveadm_cmd_param_bool(cctx, "list", &list_schemes); (void)doveadm_cmd_param_str(cctx, "plaintext", &plaintext); if (doveadm_cmd_param_int64(cctx, "rounds", &rounds_int64)) { if (rounds_int64 > UINT_MAX) i_fatal("Invalid number of rounds: %"PRId64, rounds_int64); gen_params.rounds = rounds_int64; } (void)doveadm_cmd_param_str(cctx, "scheme", &scheme); if (doveadm_cmd_param_str(cctx, "test-hash", &test_hash)) reverse_verify = TRUE; (void)doveadm_cmd_param_str(cctx, "user", &gen_params.user); (void)doveadm_cmd_param_bool(cctx, "reverse-verify", &reverse_verify); if (list_schemes) { ARRAY_TYPE(password_scheme_p) arr; const struct password_scheme *const *schemes; unsigned int i, count; t_array_init(&arr, 30); password_schemes_get(&arr); schemes = array_get(&arr, &count); for (i = 0; i < count; i++) printf("%s ", schemes[i]->name); printf("\n"); module_dir_unload(&modules); password_schemes_deinit(); return; } scheme = scheme == NULL ? DEFAULT_SCHEME : t_str_ucase(scheme); if (test_hash != NULL && plaintext == NULL) plaintext = t_askpass("Enter password to verify: "); while (plaintext == NULL) { const char *check; static int lives = 3; plaintext = t_askpass("Enter new password: "); check = t_askpass("Retype new password: "); if (strcmp(plaintext, check) != 0) { i_error("Passwords don't match!"); if (--lives == 0) lib_exit(1); plaintext = NULL; } } if (!password_generate_encoded(plaintext, &gen_params, scheme, &hash)) i_fatal("Unknown scheme: %s", scheme); if (reverse_verify) { const unsigned char *raw_password; size_t size; const char *error; if (test_hash != NULL) { scheme = password_get_scheme(&test_hash); if (scheme == NULL) i_fatal("Missing {scheme} prefix from hash"); hash = test_hash; } if (password_decode(hash, scheme, &raw_password, &size, &error) <= 0) i_fatal("reverse decode check failed: %s", error); if (password_verify(plaintext, &gen_params, scheme, raw_password, size, &error) <= 0) { i_fatal("reverse password verification check failed: %s", error); } printf("{%s}%s (verified)\n", scheme, hash); } else { printf("{%s}%s\n", scheme, hash); } module_dir_unload(&modules); password_schemes_deinit(); } struct doveadm_cmd_ver2 doveadm_cmd_pw = { .name = "pw", .cmd = cmd_pw, .usage = "[-l] [-p plaintext] [-r rounds] [-s scheme] [-t hash] [-u user] [-V]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('l', "list", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('p', "plaintext", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('r', "rounds", CMD_PARAM_INT64, 0) DOVEADM_CMD_PARAM('s', "scheme", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('t', "test-hash", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('u', "user", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('V', "reverse-verify", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-server.h0000644000000000000000000000152714656633576015624 00000000000000#ifndef DOVEADM_SERVER_H #define DOVEADM_SERVER_H extern struct client_connection *doveadm_client; extern struct doveadm_print_vfuncs doveadm_print_server_vfuncs; enum doveadm_proxy_ssl_flags { /* Use SSL/TLS enabled */ PROXY_SSL_FLAG_YES = 0x01, /* Don't do SSL handshake immediately after connected */ PROXY_SSL_FLAG_STARTTLS = 0x02, /* Don't require that the received certificate is valid */ PROXY_SSL_FLAG_ANY_CERT = 0x04 }; struct doveadm_server { /* hostname:port or socket name for logging */ const char *name; /* hostname without port */ const char *hostname; /* host ip to use */ struct ip_addr ip; /* port to use */ in_port_t port; /* ssl related settings */ enum doveadm_proxy_ssl_flags ssl_flags; struct ssl_iostream_context *ssl_ctx; ARRAY(struct server_connection *) connections; ARRAY_TYPE(string) queue; }; #endif dovecot-2.3.21.1/src/doveadm/doveadm-mail-iter.h0000644000000000000000000000227714656633576016204 00000000000000#ifndef DOVEADM_MAIL_ITER_H #define DOVEADM_MAIL_ITER_H #include "mailbox-list-iter.h" enum doveadm_mail_iter_flags { /* Open the mailbox with MAILBOX_FLAG_READONLY */ DOVEADM_MAIL_ITER_FLAG_READONLY = BIT(0), /* Stop the iteration if client is detected to be disconnected. */ DOVEADM_MAIL_ITER_FLAG_STOP_WITH_CLIENT = BIT(1), }; struct doveadm_mail_iter; struct doveadm_mail_cmd_context; int doveadm_mail_iter_init(struct doveadm_mail_cmd_context *ctx, const struct mailbox_info *info, struct mail_search_args *search_args, enum mail_fetch_field wanted_fields, const char *const *wanted_headers, enum doveadm_mail_iter_flags flags, struct doveadm_mail_iter **iter_r) ATTR_NULL(6); int doveadm_mail_iter_deinit(struct doveadm_mail_iter **iter); int doveadm_mail_iter_deinit_sync(struct doveadm_mail_iter **iter); int doveadm_mail_iter_deinit_keep_box(struct doveadm_mail_iter **iter, struct mailbox **box_r); void doveadm_mail_iter_deinit_rollback(struct doveadm_mail_iter **iter); struct mailbox *doveadm_mail_iter_get_mailbox(struct doveadm_mail_iter *iter); bool doveadm_mail_iter_next(struct doveadm_mail_iter *iter, struct mail **mail_r); #endif dovecot-2.3.21.1/src/doveadm/doveadm-print-table.c0000644000000000000000000001437414656633576016536 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "unichar.h" #include "doveadm-print-private.h" #include #include #include #include #define DEFAULT_COLUMNS 80 #define MIN_COLUMNS 30 #define MAX_BUFFER_LINES 100 struct doveadm_print_table_header { const char *key; const char *title; enum doveadm_print_header_flags flags; size_t min_length, max_length, length; }; struct doveadm_print_table_context { pool_t pool; ARRAY(struct doveadm_print_table_header) headers; ARRAY_TYPE(const_string) buffered_values; string_t *stream; unsigned int hdr_idx; unsigned int columns; bool lengths_set:1; }; static struct doveadm_print_table_context *ctx; static void doveadm_print_table_header(const struct doveadm_print_header *hdr) { struct doveadm_print_table_header *thdr; thdr = array_append_space(&ctx->headers); thdr->key = p_strdup(ctx->pool, hdr->key); thdr->title = p_strdup(ctx->pool, hdr->title); thdr->length = thdr->max_length = thdr->min_length = strlen(hdr->title); thdr->flags = hdr->flags; } static void doveadm_calc_header_length(void) { struct doveadm_print_table_header *headers; const char *value, *const *values; unsigned int i, line, hdr_count, value_count, line_count; size_t len, max_length, orig_length, diff; ctx->lengths_set = TRUE; headers = array_get_modifiable(&ctx->headers, &hdr_count); values = array_get(&ctx->buffered_values, &value_count); i_assert((value_count % hdr_count) == 0); line_count = value_count / hdr_count; /* find min and max lengths of fields */ for (line = 0; line < line_count; line++) { for (i = 0; i < hdr_count; i++) { value = values[line*hdr_count + i]; len = value == NULL ? 0 : uni_utf8_strlen(value); if (headers[i].min_length > len) headers[i].min_length = len; if (headers[i].max_length < len) { headers[i].max_length = len; headers[i].length = len; } } } /* +1 for space between fields */ max_length = 0; for (i = 0; i < hdr_count; i++) max_length += headers[i].max_length + 1; max_length--; while (max_length > ctx->columns) { /* shrink something so we'll fit */ orig_length = max_length; for (i = hdr_count - 1;; i--) { diff = headers[i].length - headers[i].min_length; if (max_length - diff <= ctx->columns) { /* we can finish with this */ diff = max_length - ctx->columns; headers[i].length -= diff; max_length -= diff; break; } if (diff > 0) { /* take a bit off from it */ headers[i].length -= diff == 1 ? 1 : diff/2; } if (i == 0) break; } if (max_length == orig_length) { /* can't shrink it any more */ break; } } if (max_length < ctx->columns) { for (i = 0; i < hdr_count; i++) { if ((headers[i].flags & DOVEADM_PRINT_HEADER_FLAG_EXPAND) != 0) { i++; break; } } headers[i-1].length += (ctx->columns - max_length) / 2; } } static size_t utf8_correction(const char *str) { size_t i, len = 0; for (i = 0; str[i] != '\0'; i++) { if ((str[i] & 0xc0) == 0x80) len++; } return len; } static void doveadm_print_next(const char *value) { const struct doveadm_print_table_header *hdr; int value_padded_len; hdr = array_idx(&ctx->headers, ctx->hdr_idx); value_padded_len = hdr->length + utf8_correction(value); if ((hdr->flags & DOVEADM_PRINT_HEADER_FLAG_RIGHT_JUSTIFY) == 0) printf("%-*s", value_padded_len, value); else printf("%*s", value_padded_len, value); if (++ctx->hdr_idx == array_count(&ctx->headers)) { ctx->hdr_idx = 0; printf("\n"); } else { printf(" "); } } static void doveadm_print_headers(void) { const struct doveadm_print_table_header *headers; unsigned int i, count; if (doveadm_print_hide_titles) return; headers = array_get(&ctx->headers, &count); /* if all headers are hidden, don't print any of them */ for (i = 0; i < count; i++) { if ((headers[i].flags & DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE) == 0) break; } if (i == count) return; for (i = 0; i < count; i++) { if (i > 0) printf(" "); if ((headers[i].flags & DOVEADM_PRINT_HEADER_FLAG_RIGHT_JUSTIFY) == 0) { printf("%-*s", (int)headers[i].length, headers[i].title); } else { printf("%*s", (int)headers[i].length, headers[i].title); } } printf("\n"); } static void doveadm_buffer_flush(void) { const char *value; doveadm_calc_header_length(); doveadm_print_headers(); array_foreach_elem(&ctx->buffered_values, value) doveadm_print_next(value); array_clear(&ctx->buffered_values); } static void doveadm_print_table_print(const char *value) { unsigned int line_count; if (!ctx->lengths_set) { line_count = array_count(&ctx->buffered_values) / array_count(&ctx->headers); if (line_count < MAX_BUFFER_LINES) { value = p_strdup(ctx->pool, value); array_push_back(&ctx->buffered_values, &value); return; } doveadm_buffer_flush(); } doveadm_print_next(value); } static void doveadm_print_table_print_stream(const unsigned char *value, size_t size) { if (memchr(value, '\n', size) != NULL) i_fatal("table formatter doesn't support multi-line values"); if (size != 0) str_append_data(ctx->stream, value, size); else { doveadm_print_table_print(str_c(ctx->stream)); str_truncate(ctx->stream, 0); } } static void doveadm_print_table_flush(void) { if (!ctx->lengths_set && array_count(&ctx->headers) > 0) doveadm_buffer_flush(); } static void doveadm_print_table_init(void) { pool_t pool; struct winsize ws; pool = pool_alloconly_create("doveadm print table", 2048); ctx = p_new(pool, struct doveadm_print_table_context, 1); ctx->pool = pool; ctx->stream = str_new(default_pool, 128); p_array_init(&ctx->headers, pool, 16); i_array_init(&ctx->buffered_values, 64); ctx->columns = DEFAULT_COLUMNS; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) { ctx->columns = ws.ws_col < MIN_COLUMNS ? MIN_COLUMNS : ws.ws_col; } } static void doveadm_print_table_deinit(void) { str_free(&ctx->stream); array_free(&ctx->buffered_values); pool_unref(&ctx->pool); ctx = NULL; } struct doveadm_print_vfuncs doveadm_print_table_vfuncs = { "table", doveadm_print_table_init, doveadm_print_table_deinit, doveadm_print_table_header, doveadm_print_table_print, doveadm_print_table_print_stream, doveadm_print_table_flush }; dovecot-2.3.21.1/src/doveadm/main.c0000644000000000000000000000612414656633576013616 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "restrict-access.h" #include "process-title.h" #include "master-service.h" #include "settings-parser.h" #include "dict.h" #include "doveadm.h" #include "client-connection.h" #include "client-connection-private.h" #include "doveadm-settings.h" #include "doveadm-dump.h" #include "doveadm-mail.h" #include "doveadm-print-private.h" #include "doveadm-server.h" #include "ostream.h" const struct doveadm_print_vfuncs *doveadm_print_vfuncs_all[] = { &doveadm_print_server_vfuncs, &doveadm_print_json_vfuncs, NULL }; struct client_connection *doveadm_client; int doveadm_exit_code = 0; static void doveadm_die(void) { /* do nothing. doveadm connections should be over soon. */ } static void client_connected(struct master_service_connection *conn) { if (doveadm_client != NULL) { i_error("doveadm server can handle only a single client"); return; } master_service_client_connection_accept(conn); if (strcmp(conn->name, "http") == 0) { doveadm_client = client_connection_http_create(conn->fd, conn->ssl); } else { doveadm_client = client_connection_tcp_create(conn->fd, conn->listen_fd, conn->ssl); } } void help_ver2(const struct doveadm_cmd_ver2 *cmd) { i_fatal("Client sent invalid command. Usage: %s %s", cmd->name, cmd->usage); } static void main_preinit(void) { restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL); restrict_access_allow_coredumps(TRUE); } static void main_init(void) { doveadm_server = TRUE; doveadm_settings_init(); doveadm_cmds_init(); doveadm_register_auth_server_commands(); doveadm_dump_init(); doveadm_mail_init(); dict_drivers_register_builtin(); doveadm_load_modules(); /* read settings only after loading doveadm plugins, which may modify what settings are read */ doveadm_read_settings(); /* Load mail_plugins */ doveadm_mail_init_finish(); /* kludgy: Load the rest of the doveadm plugins after mail_plugins have been loaded. */ doveadm_load_modules(); doveadm_server_init(); if (doveadm_verbose_proctitle) process_title_set("[idling]"); } static void main_deinit(void) { doveadm_server_deinit(); doveadm_mail_deinit(); doveadm_dump_deinit(); doveadm_unload_modules(); dict_drivers_unregister_builtin(); doveadm_print_deinit(); doveadm_cmds_deinit(); doveadm_settings_deinit(); } int main(int argc, char *argv[]) { enum master_service_flags service_flags = MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN | MASTER_SERVICE_FLAG_HAVE_STARTTLS; int c; master_service = master_service_init("doveadm", service_flags, &argc, &argv, "D"); while ((c = master_getopt(master_service)) > 0) { switch (c) { case 'D': doveadm_debug = TRUE; doveadm_verbose = TRUE; break; default: return FATAL_DEFAULT; } } master_service_init_log(master_service); main_preinit(); master_service_set_die_callback(master_service, doveadm_die); main_init(); master_service_init_finish(master_service); master_service_run(master_service, client_connected); main_deinit(); master_service_deinit(&master_service); return 0; } dovecot-2.3.21.1/src/doveadm/Makefile.am0000644000000000000000000001100314656633576014552 00000000000000doveadm_moduledir = $(moduledir)/doveadm pkglibexecdir = $(libexecdir)/dovecot SUBDIRS = dsync bin_PROGRAMS = doveadm pkglibexec_PROGRAMS = doveadm-server AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-compression \ -I$(top_srcdir)/src/lib-dict \ -I$(top_srcdir)/src/lib-fs \ -I$(top_srcdir)/src/lib-ssl-iostream \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-storage \ -I$(top_srcdir)/src/lib-imap-storage \ -I$(top_srcdir)/src/lib-http \ -I$(top_srcdir)/src/lib-dcrypt \ -I$(top_srcdir)/src/auth \ -I$(top_srcdir)/src/stats \ -DMODULEDIR=\""$(moduledir)"\" \ -DAUTH_MODULE_DIR=\""$(moduledir)/auth"\" \ -DDOVEADM_MODULEDIR=\""$(doveadm_moduledir)"\" \ -DPKG_RUNDIR=\""$(rundir)"\" \ -DPKG_STATEDIR=\""$(statedir)"\" \ -DPKG_LIBEXECDIR=\""$(pkglibexecdir)"\" \ -DBINDIR=\""$(bindir)"\" \ -DMANDIR=\""$(mandir)"\" \ $(BINARY_CFLAGS) cmd_pw_libs = \ ../auth/libpassword.la \ ../lib-otp/libotp.la libs = \ dsync/libdsync.la \ ../lib-compression/libcompression.la doveadm_LDADD = \ $(libs) \ $(cmd_pw_libs) \ $(CRYPT_LIBS) \ $(LIBDOVECOT_STORAGE) \ $(LIBDOVECOT) \ $(LIBSODIUM_LIBS) \ $(BINARY_LDFLAGS) \ -lm doveadm_DEPENDENCIES = \ $(libs) \ $(cmd_pw_libs) \ $(LIBDOVECOT_STORAGE_DEPS) \ $(LIBDOVECOT_DEPS) doveadm_server_LDADD = \ $(libs) \ $(LIBDOVECOT_STORAGE) \ $(LIBDOVECOT) \ $(BINARY_LDFLAGS) \ -lm doveadm_server_DEPENDENCIES = \ $(libs) \ $(LIBDOVECOT_STORAGE_DEPS) \ $(LIBDOVECOT_DEPS) doveadm_common_cmds = \ doveadm-auth.c \ doveadm-dict.c \ doveadm-director.c \ doveadm-fs.c \ doveadm-instance.c \ doveadm-kick.c \ doveadm-log.c \ doveadm-master.c \ doveadm-mutf7.c \ doveadm-penalty.c \ doveadm-proxy.c \ doveadm-replicator.c \ doveadm-sis.c \ doveadm-stats.c \ doveadm-oldstats.c \ doveadm-who.c doveadm_common_mail_cmds = \ doveadm-dsync.c \ doveadm-mail.c \ doveadm-mail-altmove.c \ doveadm-mail-batch.c \ doveadm-mail-deduplicate.c \ doveadm-mail-expunge.c \ doveadm-mail-fetch.c \ doveadm-mail-flags.c \ doveadm-mail-import.c \ doveadm-mail-index.c \ doveadm-mail-iter.c \ doveadm-mail-mailbox.c \ doveadm-mail-mailbox-metadata.c \ doveadm-mail-mailbox-status.c \ doveadm-mail-copymove.c \ doveadm-mailbox-list-iter.c \ doveadm-mail-save.c \ doveadm-mail-search.c \ doveadm-mail-server.c \ doveadm-mail-mailbox-cache.c \ doveadm-mail-rebuild.c # these aren't actually useful in doveadm-server, but plugins may implement # both dumping and some other commands inside a single plugin. not having the # dump functions in doveadm-server fails to load such plugins. doveadm_common_dump_cmds = \ doveadm-dump.c \ doveadm-dump-dbox.c \ doveadm-dump-index.c \ doveadm-dump-log.c \ doveadm-dump-mailboxlog.c \ doveadm-dump-thread.c \ doveadm-dump-dcrypt-file.c \ doveadm-dump-dcrypt-key.c \ doveadm-zlib.c common = \ $(doveadm_common_cmds) \ $(doveadm_common_mail_cmds) \ $(doveadm_common_dump_cmds) \ doveadm-cmd.c \ doveadm-print.c \ doveadm-settings.c \ doveadm-util.c \ server-connection.c \ doveadm-print-formatted.c doveadm_SOURCES = \ $(common) \ doveadm.c \ doveadm-print-flow.c \ doveadm-print-pager.c \ doveadm-print-tab.c \ doveadm-print-table.c \ doveadm-print-json.c \ doveadm-pw.c doveadm_server_SOURCES = \ $(common) \ doveadm-auth-server.c \ client-connection.c \ client-connection-tcp.c \ client-connection-http.c \ doveadm-print-server.c \ doveadm-print-json.c \ main.c pkginc_libdir = $(pkgincludedir) pkginc_lib_HEADERS = \ doveadm.h \ doveadm-cmd.h \ doveadm-dsync.h \ doveadm-dump.h \ doveadm-mail.h \ doveadm-mail-iter.h \ doveadm-mailbox-list-iter.h \ doveadm-print.h \ doveadm-print-private.h \ doveadm-settings.h \ doveadm-util.h noinst_HEADERS = \ client-connection.h \ client-connection-private.h \ server-connection.h \ doveadm-server.h \ doveadm-who.h install-exec-local: rm -f $(DESTDIR)$(bindir)/dsync $(LN_S) doveadm $(DESTDIR)$(bindir)/dsync test_programs = \ test-doveadm-util noinst_PROGRAMS = $(test_programs) test_libs = \ ../lib-test/libtest.la \ ../lib/liblib.la test_deps = $(noinst_LTLIBRARIES) $(test_libs) test_doveadm_util_SOURCES = doveadm-util.c test-doveadm-util.c test_doveadm_util_LDADD = $(test_libs) $(MODULE_LIBS) test_doveadm_util_DEPENDENCIES = $(test_deps) check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done dovecot-2.3.21.1/src/doveadm/doveadm-zlib.c0000644000000000000000000001703514656633576015252 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "net.h" #include "istream.h" #include "ostream.h" #include "istream-zlib.h" #include "ostream-zlib.h" #include "module-dir.h" #include "master-service.h" #include "compression.h" #include "doveadm-dump.h" #include #include #include static bool test_dump_imapzlib(const char *path) { const char *p; char buf[4096]; int fd, ret; bool match = FALSE; p = strrchr(path, '.'); if (p == NULL || (strcmp(p, ".in") != 0 && strcmp(p, ".out") != 0)) return FALSE; fd = open(path, O_RDONLY); if (fd == -1) return FALSE; ret = read(fd, buf, sizeof(buf)-1); if (ret > 0) { buf[ret] = '\0'; (void)str_lcase(buf); match = strstr(buf, " ok begin compression.") != NULL || strstr(buf, " compress deflate") != NULL; } i_close_fd(&fd); return match; } #ifdef HAVE_ZLIB static void cmd_dump_imapzlib(const char *path, const char *const *args ATTR_UNUSED) { struct istream *input, *input2; const unsigned char *data; size_t size; const char *line; int fd; fd = open(path, O_RDONLY); if (fd < 0) i_fatal("open(%s) failed: %m", path); input = i_stream_create_fd_autoclose(&fd, 1024*32); while ((line = i_stream_read_next_line(input)) != NULL) { /* skip tag */ printf("%s\r\n", line); while (*line != ' ' && *line != '\0') line++; if (*line == '\0') continue; line++; if (str_begins(line, "OK Begin compression") || strcasecmp(line, "COMPRESS DEFLATE") == 0) break; } input2 = i_stream_create_deflate(input); i_stream_unref(&input); while (i_stream_read_more(input2, &data, &size) != -1) { if (fwrite(data, 1, size, stdout) != size) break; i_stream_skip(input2, size); } if (input2->stream_errno != 0) i_error("read(%s) failed: %s", path, i_stream_get_error(input)); i_stream_unref(&input2); fflush(stdout); } struct client { int fd; struct io *io_client, *io_server; struct istream *input, *stdin_input; struct ostream *output; const struct compression_handler *handler; char *algorithm; bool compressed; bool compress_waiting; }; static bool client_input_get_compress_algorithm(struct client *client, const char *line) { /* skip tag */ while (*line != ' ' && *line != '\0') line++; if (strncasecmp(line, " COMPRESS ", 10) != 0) return FALSE; if (compression_lookup_handler(t_str_lcase(line+10), &client->handler) <= 0) i_fatal("Unsupported compression mechanism: %s", line+10); return TRUE; } static bool client_input_uncompressed(struct client *client) { const char *line; if (client->compress_waiting) { /* just read all the pipelined input for now */ (void)i_stream_read(client->stdin_input); return TRUE; } while ((line = i_stream_read_next_line(client->stdin_input)) != NULL) { o_stream_nsend_str(client->output, line); o_stream_nsend(client->output, "\n", 1); if (client_input_get_compress_algorithm(client, line)) return TRUE; } return FALSE; } static void client_input(struct client *client) { const unsigned char *data; size_t size; if (!client->compressed && client_input_uncompressed(client)) { /* stop until server has sent reply to COMPRESS command. */ client->compress_waiting = TRUE; return; } if (client->compressed) { if (i_stream_read_more(client->stdin_input, &data, &size) > 0) { o_stream_nsend(client->output, data, size); i_stream_skip(client->stdin_input, size); } if (o_stream_flush(client->output) < 0) { i_fatal("write() failed: %s", o_stream_get_error(client->output)); } } if (client->stdin_input->eof) { if (client->stdin_input->stream_errno != 0) { i_fatal("read(stdin) failed: %s", i_stream_get_error(client->stdin_input)); } master_service_stop(master_service); } } static bool server_input_is_compress_reply(const char *line) { /* skip tag */ while (*line != ' ' && *line != '\0') line++; return str_begins(line, " OK Begin compression"); } static bool server_input_uncompressed(struct client *client) { const char *line; while ((line = i_stream_read_next_line(client->input)) != NULL) { if (write(STDOUT_FILENO, line, strlen(line)) < 0) i_fatal("write(stdout) failed: %m"); if (write(STDOUT_FILENO, "\n", 1) < 0) i_fatal("write(stdout) failed: %m"); if (server_input_is_compress_reply(line)) return TRUE; } return FALSE; } static void server_input(struct client *client) { const unsigned char *data; size_t size; if (i_stream_read(client->input) == -1) { if (client->input->stream_errno != 0) { i_fatal("read(server) failed: %s", i_stream_get_error(client->input)); } i_info("Server disconnected"); master_service_stop(master_service); return; } if (!client->compressed && server_input_uncompressed(client)) { /* start compression */ struct istream *input; struct ostream *output; i_info(""); input = client->handler->create_istream(client->input); output = client->handler->create_ostream(client->output, 6); i_stream_unref(&client->input); o_stream_unref(&client->output); client->input = input; client->output = output; client->compressed = TRUE; client->compress_waiting = FALSE; i_stream_set_input_pending(client->stdin_input, TRUE); } data = i_stream_get_data(client->input, &size); if (write(STDOUT_FILENO, data, size) < 0) i_fatal("write(stdout) failed: %m"); i_stream_skip(client->input, size); } static void cmd_zlibconnect(struct doveadm_cmd_context *cctx) { struct client client; const char *host; struct ip_addr *ips; unsigned int ips_count; int64_t port_int64; in_port_t port = 143; int fd, ret; if (!doveadm_cmd_param_str(cctx, "host", &host)) help_ver2(&doveadm_cmd_zlibconnect); if (doveadm_cmd_param_int64(cctx, "port", &port_int64)) { if (port_int64 == 0 || port_int64 > 65535) i_fatal("Invalid port: %"PRId64, port_int64); port = (in_port_t)port_int64; } ret = net_gethostbyname(host, &ips, &ips_count); if (ret != 0) { i_fatal("Host %s lookup failed: %s", host, net_gethosterror(ret)); } if ((fd = net_connect_ip(&ips[0], port, NULL)) == -1) i_fatal("connect(%s, %u) failed: %m", host, port); i_info("Connected to %s port %u.", net_ip2addr(&ips[0]), port); i_zero(&client); client.fd = fd; fd_set_nonblock(STDIN_FILENO, TRUE); client.stdin_input = i_stream_create_fd(STDIN_FILENO, SIZE_MAX); client.input = i_stream_create_fd(fd, SIZE_MAX); client.output = o_stream_create_fd(fd, 0); o_stream_set_no_error_handling(client.output, TRUE); client.io_client = io_add_istream(client.stdin_input, client_input, &client); client.io_server = io_add_istream(client.input, server_input, &client); master_service_run(master_service, NULL); io_remove(&client.io_client); io_remove(&client.io_server); i_stream_unref(&client.stdin_input); i_stream_unref(&client.input); o_stream_unref(&client.output); if (close(fd) < 0) i_fatal("close() failed: %m"); } #else static void cmd_dump_imapzlib(const char *path ATTR_UNUSED, const char *const *args ATTR_UNUSED) { i_fatal("Dovecot compiled without zlib support"); } static void cmd_zlibconnect(struct doveadm_cmd_context *cctx ATTR_UNUSED) { i_fatal("Dovecot compiled without zlib support"); } #endif struct doveadm_cmd_dump doveadm_cmd_dump_zlib = { "imapzlib", test_dump_imapzlib, cmd_dump_imapzlib }; struct doveadm_cmd_ver2 doveadm_cmd_zlibconnect = { .name = "zlibconnect", .cmd = cmd_zlibconnect, .usage = " []", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', "host", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "port", CMD_PARAM_INT64, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-mail-batch.c0000644000000000000000000001217414656633576016312 00000000000000/* Copyright (c) 2012-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "doveadm-mail.h" #include struct batch_cmd_context { struct doveadm_mail_cmd_context ctx; ARRAY(struct doveadm_mail_cmd_context *) commands; }; static int cmd_batch_prerun(struct doveadm_mail_cmd_context *_ctx, struct mail_storage_service_user *service_user, const char **error_r) { struct batch_cmd_context *ctx = (struct batch_cmd_context *)_ctx; struct doveadm_mail_cmd_context *cmd; int ret = 0; array_foreach_elem(&ctx->commands, cmd) { if (cmd->v.prerun != NULL && cmd->v.prerun(cmd, service_user, error_r) < 0) { ret = -1; break; } } return ret; } static int cmd_batch_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct batch_cmd_context *ctx = (struct batch_cmd_context *)_ctx; struct doveadm_mail_cmd_context *cmd; int ret = 0; array_foreach_elem(&ctx->commands, cmd) { cmd->cur_mail_user = user; const char *reason_code = event_reason_code_prefix("doveadm", "cmd_", cmd->cmd->name); struct event_reason *reason = event_reason_begin(reason_code); ret = cmd->v.run(cmd, user); event_reason_end(&reason); if (ret < 0) { i_assert(cmd->exit_code != 0); _ctx->exit_code = cmd->exit_code; break; } cmd->cur_mail_user = NULL; } return ret; } static void cmd_batch_add(struct batch_cmd_context *batchctx, int argc, const char *const *argv) { struct doveadm_mail_cmd_context *subctx; const struct doveadm_cmd_ver2 *cmd_ver2; const struct doveadm_mail_cmd *cmd; const char *getopt_args; int c; cmd_ver2 = doveadm_cmd_find_with_args_ver2(argv[0], &argc, &argv); if (cmd_ver2 == NULL) i_fatal_status(EX_USAGE, "doveadm batch: '%s' mail command doesn't exist", argv[0]); struct doveadm_mail_cmd *dyncmd = p_new(batchctx->ctx.pool, struct doveadm_mail_cmd, 1); dyncmd->usage_args = cmd_ver2->usage; dyncmd->name = cmd_ver2->name; dyncmd->alloc = cmd_ver2->mail_cmd; cmd = dyncmd; subctx = doveadm_mail_cmd_init(cmd, doveadm_settings); subctx->full_args = argv + 1; subctx->service_flags |= batchctx->ctx.service_flags; i_getopt_reset(); getopt_args = subctx->getopt_args != NULL ? subctx->getopt_args : ""; while ((c = getopt(argc, (void *)argv, getopt_args)) > 0) { if (subctx->v.parse_arg == NULL || !subctx->v.parse_arg(subctx, c)) doveadm_mail_help(cmd); } argv += optind; if (argv[0] != NULL && cmd->usage_args == NULL) { i_fatal_status(EX_USAGE, "doveadm %s: Unknown parameter: %s", cmd->name, argv[0]); } subctx->args = argv; if (subctx->v.preinit != NULL) subctx->v.preinit(subctx); array_push_back(&batchctx->commands, &subctx); } static void cmd_batch_preinit(struct doveadm_mail_cmd_context *_ctx) { const char *const *args = _ctx->args; struct batch_cmd_context *ctx = (struct batch_cmd_context *)_ctx; ARRAY_TYPE(const_string) sep_args; const char *sep = args[0]; unsigned int i, start; int argc; const char *const *argv; if (sep == NULL || args[1] == NULL) doveadm_mail_help_name("batch"); args++; p_array_init(&ctx->commands, _ctx->pool, 8); p_array_init(&sep_args, _ctx->pool, 16); for (i = start = 0;; i++) { if (args[i] != NULL && strcmp(args[i], sep) != 0) { array_push_back(&sep_args, &args[i]); continue; } if (i > start) { (void)array_append_space(&sep_args); argc = i - start; argv = array_idx(&sep_args, start); cmd_batch_add(ctx, argc, argv); start = i+1; } if (args[i] == NULL) break; } (void)array_append_space(&sep_args); } static void cmd_batch_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[] ATTR_UNUSED) { struct batch_cmd_context *ctx = (struct batch_cmd_context *)_ctx; struct doveadm_mail_cmd_context *cmd; struct batch_cmd_context *subctx; array_foreach_elem(&ctx->commands, cmd) { subctx = (struct batch_cmd_context *)cmd; subctx->ctx.storage_service = _ctx->storage_service; if (subctx->ctx.v.init != NULL) subctx->ctx.v.init(&subctx->ctx, subctx->ctx.args); } } static void cmd_batch_deinit(struct doveadm_mail_cmd_context *_ctx) { struct batch_cmd_context *ctx = (struct batch_cmd_context *)_ctx; struct doveadm_mail_cmd_context *cmd; array_foreach_elem(&ctx->commands, cmd) { doveadm_mail_cmd_deinit(cmd); doveadm_mail_cmd_free(cmd); } } static struct doveadm_mail_cmd_context *cmd_batch_alloc(void) { struct batch_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct batch_cmd_context); ctx->ctx.getopt_args = "+"; /* disable processing -args in the middle */ ctx->ctx.v.preinit = cmd_batch_preinit; ctx->ctx.v.init = cmd_batch_init; ctx->ctx.v.prerun = cmd_batch_prerun; ctx->ctx.v.run = cmd_batch_run; ctx->ctx.v.deinit = cmd_batch_deinit; return &ctx->ctx; } struct doveadm_cmd_ver2 doveadm_cmd_batch = { .name = "batch", .mail_cmd = cmd_batch_alloc, .usage = " [ [..]]", .flags = CMD_FLAG_NO_UNORDERED_OPTIONS, DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('\0', "separator", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "args", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-print-tab.c0000644000000000000000000000330114656633576016201 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "ostream.h" #include "doveadm-print-private.h" struct doveadm_print_tab_context { unsigned int header_idx, header_count; bool header_written:1; }; static struct doveadm_print_tab_context ctx; static void doveadm_print_tab_flush_header(void) { if (!ctx.header_written) { if (!doveadm_print_hide_titles) o_stream_nsend(doveadm_print_ostream, "\n", 1); ctx.header_written = TRUE; } } static void doveadm_print_tab_header(const struct doveadm_print_header *hdr) { ctx.header_count++; if (!doveadm_print_hide_titles) { if (ctx.header_count > 1) o_stream_nsend(doveadm_print_ostream, "\t", 1); o_stream_nsend_str(doveadm_print_ostream, hdr->title); } } static void doveadm_print_tab_print(const char *value) { doveadm_print_tab_flush_header(); if (ctx.header_idx > 0) o_stream_nsend(doveadm_print_ostream, "\t", 1); o_stream_nsend_str(doveadm_print_ostream, value); if (++ctx.header_idx == ctx.header_count) { ctx.header_idx = 0; o_stream_nsend(doveadm_print_ostream, "\n", 1); } } static void doveadm_print_tab_print_stream(const unsigned char *value, size_t size) { if (size == 0) { doveadm_print_tab_print(""); return; } doveadm_print_tab_flush_header(); if (ctx.header_idx > 0) o_stream_nsend(doveadm_print_ostream, "\t", 1); o_stream_nsend(doveadm_print_ostream, value, size); } static void doveadm_print_tab_flush(void) { doveadm_print_tab_flush_header(); } struct doveadm_print_vfuncs doveadm_print_tab_vfuncs = { "tab", NULL, NULL, doveadm_print_tab_header, doveadm_print_tab_print, doveadm_print_tab_print_stream, doveadm_print_tab_flush }; dovecot-2.3.21.1/src/doveadm/test-doveadm-util.c0000644000000000000000000000334314656633576016241 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "test-common.h" #include "doveadm-settings.h" #include "doveadm-util.h" struct doveadm_settings *doveadm_settings; /* just to avoid linker error */ static void test_i_strccdascmp(void) { test_begin("i_strccdascmp()"); test_assert(i_strccdascmp("", "")==0); test_assert(i_strccdascmp("", "-")!=0); test_assert(i_strccdascmp("-", "")!=0); test_assert(i_strccdascmp("-", "-")==0); test_assert(i_strccdascmp("-\0baz", "-\0bar")==0); test_assert(i_strccdascmp("", "a")!=0); test_assert(i_strccdascmp("a", "")!=0); test_assert(i_strccdascmp("a", "a")==0); test_assert(i_strccdascmp("a-", "a-")==0); test_assert(i_strccdascmp("a-a", "a-a")==0); test_assert(i_strccdascmp("ca", "ba")!=0); test_assert(i_strccdascmp("camel case", "camel case")==0); test_assert(i_strccdascmp("camel case", "camel-case")==0); test_assert(i_strccdascmp("camel case", "camelCase")==0); test_assert(i_strccdascmp("camel case", "camel-case")==0); test_assert(i_strccdascmp("camel-case", "camel-case")==0); test_assert(i_strccdascmp("camelCase", "camel-case")==0); test_assert(i_strccdascmp("camel case", "camel Case")==-i_strccdascmp("camel Case", "camel case")); test_assert(i_strccdascmp("camel-case", "camel Case")==-i_strccdascmp("camel Case", "camel-case")); test_assert(i_strccdascmp("camel dase", "camel case")==-i_strccdascmp("camel case", "camel dase")); test_end(); } int main(void) { static void (*const test_functions[])(void) = { test_i_strccdascmp, NULL }; return test_run(test_functions); } dovecot-2.3.21.1/src/doveadm/client-connection.c0000644000000000000000000000565314656633576016313 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "process-title.h" #include "settings-parser.h" #include "master-service.h" #include "master-service-settings.h" #include "doveadm.h" #include "doveadm-settings.h" #include "doveadm-server.h" #include "client-connection-private.h" bool doveadm_client_is_allowed_command(const struct doveadm_settings *set, const char *cmd_name) { bool ret = FALSE; if (*set->doveadm_allowed_commands == '\0') return TRUE; T_BEGIN { const char *const *cmds = t_strsplit(set->doveadm_allowed_commands, ","); for (; *cmds != NULL; cmds++) { if (strcmp(*cmds, cmd_name) == 0) { ret = TRUE; break; } } } T_END; return ret; } static int client_connection_read_settings(struct client_connection *conn) { const struct setting_parser_info *set_roots[] = { &doveadm_setting_parser_info, NULL }; struct master_service_settings_input input; struct master_service_settings_output output; const char *error; void *set; i_zero(&input); input.roots = set_roots; input.service = "doveadm"; input.local_ip = conn->local_ip; input.remote_ip = conn->remote_ip; if (master_service_settings_read(master_service, &input, &output, &error) < 0) { i_error("Error reading configuration: %s", error); return -1; } set = master_service_settings_get_others(master_service)[0]; conn->set = settings_dup(&doveadm_setting_parser_info, set, conn->pool); return 0; } int client_connection_init(struct client_connection *conn, enum doveadm_client_type type, pool_t pool, int fd) { const char *ip; i_assert(type != DOVEADM_CONNECTION_TYPE_CLI); conn->type = type; conn->pool = pool; (void)net_getsockname(fd, &conn->local_ip, &conn->local_port); (void)net_getpeername(fd, &conn->remote_ip, &conn->remote_port); ip = net_ip2addr(&conn->remote_ip); if (ip[0] != '\0') i_set_failure_prefix("doveadm(%s): ", ip); conn->name = conn->remote_ip.family == 0 ? "" : p_strdup(pool, net_ip2addr(&conn->remote_ip)); return client_connection_read_settings(conn); } void client_connection_destroy(struct client_connection **_conn) { struct client_connection *conn = *_conn; *_conn = NULL; if (conn->free != NULL) conn->free(conn); doveadm_client = NULL; master_service_client_connection_destroyed(master_service); if (doveadm_verbose_proctitle) process_title_set("[idling]"); pool_unref(&conn->pool); } void client_connection_set_proctitle(struct client_connection *conn, const char *text) { const char *str; if (!doveadm_verbose_proctitle) return; if (text[0] == '\0') str = t_strdup_printf("[%s]", conn->name); else str = t_strdup_printf("[%s %s]", conn->name, text); process_title_set(str); } void doveadm_server_init(void) { doveadm_http_server_init(); } void doveadm_server_deinit(void) { if (doveadm_client != NULL) client_connection_destroy(&doveadm_client); doveadm_http_server_deinit(); } dovecot-2.3.21.1/src/doveadm/doveadm-mail-expunge.c0000644000000000000000000001610414656633576016701 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "mail-index.h" #include "mail-storage.h" #include "mail-search.h" #include "doveadm-mailbox-list-iter.h" #include "doveadm-mail-iter.h" #include "doveadm-mail.h" struct expunge_cmd_context { struct doveadm_mail_cmd_context ctx; bool delete_empty_mailbox; }; static int cmd_expunge_box(struct doveadm_mail_cmd_context *_ctx, const struct mailbox_info *info, struct mail_search_args *search_args) { struct expunge_cmd_context *ctx = (struct expunge_cmd_context *)_ctx; struct doveadm_mail_iter *iter; struct mailbox *box; struct mail *mail; enum mail_error error; int ret = 0; if (doveadm_mail_iter_init(_ctx, info, search_args, 0, NULL, 0, &iter) < 0) return -1; while (doveadm_mail_iter_next(iter, &mail)) { if (doveadm_debug) { i_debug("expunge: box=%s uid=%u", info->vname, mail->uid); } mail_expunge(mail); } if (doveadm_mail_iter_deinit_keep_box(&iter, &box) < 0) ret = -1; else if (mailbox_sync(box, 0) < 0) { i_error("Syncing mailbox '%s' failed: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(_ctx, box); ret = -1; } if (ctx->delete_empty_mailbox && ret == 0) { if (mailbox_delete_empty(box) < 0) { error = mailbox_get_last_mail_error(box); if (error != MAIL_ERROR_EXISTS) { i_error("Deleting mailbox '%s' failed: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(_ctx, box); ret = -1; } } else { if (mailbox_set_subscribed(box, FALSE) < 0) { i_error("Unsubscribing mailbox '%s' failed: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, NULL)); doveadm_mail_failed_mailbox(_ctx, box); ret = -1; } } } mailbox_free(&box); return ret; } static bool expunge_search_args_is_mailbox_ok(struct mail_search_arg *args); static bool expunge_search_args_is_mailbox_or_ok(struct mail_search_arg *args) { struct mail_search_arg *arg; for (arg = args; arg != NULL; arg = arg->next) { switch (arg->type) { case SEARCH_OR: if (!expunge_search_args_is_mailbox_or_ok(arg->value.subargs)) return FALSE; break; case SEARCH_SUB: case SEARCH_INTHREAD: if (!expunge_search_args_is_mailbox_ok(arg->value.subargs)) return FALSE; break; case SEARCH_MAILBOX: case SEARCH_MAILBOX_GUID: case SEARCH_MAILBOX_GLOB: break; default: return FALSE; } } return TRUE; } static bool expunge_search_args_is_mailbox_ok(struct mail_search_arg *args) { struct mail_search_arg *arg; bool have_or = FALSE; /* a) we find one mailbox here in the SUB block */ for (arg = args; arg != NULL; arg = arg->next) { switch (arg->type) { case SEARCH_MAILBOX: case SEARCH_MAILBOX_GUID: case SEARCH_MAILBOX_GLOB: return TRUE; case SEARCH_OR: have_or = TRUE; break; case SEARCH_SUB: case SEARCH_INTHREAD: if (expunge_search_args_is_mailbox_ok(arg->value.subargs)) return TRUE; break; default: break; } } /* b) there is at least one OR block, and all of the ORs must have mailbox */ if (!have_or) return FALSE; for (arg = args; arg != NULL; arg = arg->next) { if (arg->type == SEARCH_OR && !expunge_search_args_is_mailbox_or_ok(arg->value.subargs)) return FALSE; } return TRUE; } static bool expunge_search_args_is_msgset_ok(struct mail_search_arg *args); static bool expunge_search_args_is_msgset_or_ok(struct mail_search_arg *args) { struct mail_search_arg *arg; /* we're done if all OR branches contain something else besides MAILBOXes */ for (arg = args; arg != NULL; arg = arg->next) { switch (arg->type) { case SEARCH_MAILBOX: case SEARCH_MAILBOX_GUID: case SEARCH_MAILBOX_GLOB: return FALSE; case SEARCH_OR: if (!expunge_search_args_is_msgset_or_ok(arg->value.subargs)) return FALSE; break; case SEARCH_SUB: if (!expunge_search_args_is_msgset_ok(arg->value.subargs)) return FALSE; break; default: break; } } return TRUE; } static bool expunge_search_args_is_msgset_ok(struct mail_search_arg *args) { struct mail_search_arg *arg; /* all args can't be just MAILBOXes */ for (arg = args; arg != NULL; arg = arg->next) { switch (arg->type) { case SEARCH_MAILBOX: case SEARCH_MAILBOX_GUID: case SEARCH_MAILBOX_GLOB: break; case SEARCH_OR: /* if each OR branch has something else than just MAILBOXes, we're ok */ if (expunge_search_args_is_msgset_or_ok(arg->value.subargs)) return TRUE; break; case SEARCH_SUB: if (expunge_search_args_is_msgset_ok(arg->value.subargs)) return TRUE; break; default: return TRUE; } } return FALSE; } static int cmd_expunge_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) { const enum mailbox_list_iter_flags iter_flags = MAILBOX_LIST_ITER_NO_AUTO_BOXES | MAILBOX_LIST_ITER_RETURN_NO_FLAGS; struct doveadm_mailbox_list_iter *iter; const struct mailbox_info *info; int ret = 0; iter = doveadm_mailbox_list_iter_init(ctx, user, ctx->search_args, iter_flags); while ((info = doveadm_mailbox_list_iter_next(iter)) != NULL) T_BEGIN { if (cmd_expunge_box(ctx, info, ctx->search_args) < 0) ret = -1; } T_END; if (doveadm_mailbox_list_iter_deinit(&iter) < 0) ret = -1; return ret; } void expunge_search_args_check(struct mail_search_args *args, const char *cmd) { if (!expunge_search_args_is_mailbox_ok(args->args)) { i_fatal_status(EX_USAGE, "%s: To avoid accidents, search query " "must contain MAILBOX in all search branches", cmd); } if (!expunge_search_args_is_msgset_ok(args->args)) { i_fatal_status(EX_USAGE, "%s: To avoid accidents, each branch in search query " "must contain something else besides MAILBOX " "(e.g. just add \"all\" if you want everything)", cmd); } mail_search_args_simplify(args); } static void cmd_expunge_init(struct doveadm_mail_cmd_context *ctx, const char *const args[]) { if (args[0] == NULL) doveadm_mail_help_name("expunge"); ctx->search_args = doveadm_mail_build_search_args(args); expunge_search_args_check(ctx->search_args, "expunge"); } static bool cmd_expunge_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c) { struct expunge_cmd_context *ctx = (struct expunge_cmd_context *)_ctx; switch (c) { case 'd': ctx->delete_empty_mailbox = TRUE; break; default: return FALSE; } return TRUE; } static struct doveadm_mail_cmd_context *cmd_expunge_alloc(void) { struct expunge_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct expunge_cmd_context); ctx->ctx.getopt_args = "d"; ctx->ctx.v.parse_arg = cmd_expunge_parse_arg; ctx->ctx.v.init = cmd_expunge_init; ctx->ctx.v.run = cmd_expunge_run; return &ctx->ctx; } struct doveadm_cmd_ver2 doveadm_cmd_expunge_ver2 = { .name = "expunge", .mail_cmd = cmd_expunge_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "[-m] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('d', "delete-empty-mailbox", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/server-connection.h0000644000000000000000000000224614656633576016343 00000000000000#ifndef SERVER_CONNECTION_H #define SERVER_CONNECTION_H #define SERVER_EXIT_CODE_DISCONNECTED 1000 struct doveadm_server; struct server_connection; struct ssl_iostream; typedef void server_cmd_callback_t(int exit_code, const char *error, void *context); int server_connection_create(struct doveadm_server *server, struct server_connection **conn_r, const char **error_r); void server_connection_destroy(struct server_connection **conn); /* Return the server given to create() */ struct doveadm_server * server_connection_get_server(struct server_connection *conn); void server_connection_cmd(struct server_connection *conn, const char *line, struct istream *cmd_input, server_cmd_callback_t *callback, void *context); /* Returns TRUE if no command is being processed */ bool server_connection_is_idle(struct server_connection *conn); /* Extract iostreams from connection. Afterwards the server_connection simply waits for itself to be destroyed. */ void server_connection_extract(struct server_connection *conn, struct istream **istream_r, struct ostream **ostream_r, struct ssl_iostream **ssl_iostream_r); #endif dovecot-2.3.21.1/src/doveadm/doveadm-auth-server.c0000644000000000000000000003702314656633576016556 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "array.h" #include "str.h" #include "var-expand.h" #include "wildcard-match.h" #include "settings-parser.h" #include "master-service.h" #include "master-service-settings.h" #include "auth-client.h" #include "auth-master.h" #include "master-auth.h" #include "master-login-auth.h" #include "mail-storage-service.h" #include "mail-user.h" #include "ostream.h" #include "json-parser.h" #include "doveadm.h" #include "doveadm-print.h" #include #include struct authtest_input { pool_t pool; const char *username; const char *master_user; const char *password; struct auth_user_info info; bool success; struct auth_client_request *request; struct master_auth_request master_auth_req; unsigned int auth_id; unsigned int auth_pid; const char *auth_cookie; }; static struct auth_master_connection * doveadm_get_auth_master_conn(const char *auth_socket_path) { enum auth_master_flags flags = 0; if (doveadm_debug) flags |= AUTH_MASTER_FLAG_DEBUG; return auth_master_init(auth_socket_path, flags); } static int cmd_user_input(struct auth_master_connection *conn, const struct authtest_input *input, const char *show_field, bool userdb) { const char *lookup_name = userdb ? "userdb lookup" : "passdb lookup"; pool_t pool; const char *updated_username = NULL, *const *fields, *p; int ret; pool = pool_alloconly_create("auth master lookup", 1024); if (userdb) { ret = auth_master_user_lookup(conn, input->username, &input->info, pool, &updated_username, &fields); } else { ret = auth_master_pass_lookup(conn, input->username, &input->info, pool, &fields); } if (ret < 0) { const char *msg; if (fields[0] == NULL) { msg = t_strdup_printf("\"error\":\"%s failed\"", lookup_name); } else { msg = t_strdup_printf("\"error\":\"%s failed: %s\"", lookup_name, fields[0]); } o_stream_nsend_str(doveadm_print_ostream, msg); ret = -1; } else if (ret == 0) { o_stream_nsend_str(doveadm_print_ostream, t_strdup_printf("\"error\":\"%s: user doesn't exist\"", lookup_name)); } else if (show_field != NULL) { size_t show_field_len = strlen(show_field); string_t *json_field = t_str_new(show_field_len+1); json_append_escaped(json_field, show_field); o_stream_nsend_str(doveadm_print_ostream, t_strdup_printf("\"%s\":", str_c(json_field))); for (; *fields != NULL; fields++) { if (strncmp(*fields, show_field, show_field_len) == 0 && (*fields)[show_field_len] == '=') { string_t *jsonval = t_str_new(32); json_append_escaped(jsonval, *fields + show_field_len + 1); o_stream_nsend_str(doveadm_print_ostream, "\""); o_stream_nsend_str(doveadm_print_ostream, str_c(jsonval)); o_stream_nsend_str(doveadm_print_ostream, "\""); } } } else { string_t *jsonval = t_str_new(64); o_stream_nsend_str(doveadm_print_ostream, "\"source\":\""); o_stream_nsend_str(doveadm_print_ostream, userdb ? "userdb\"" : "passdb\""); if (updated_username != NULL) { o_stream_nsend_str(doveadm_print_ostream, ",\"updated_username\":\""); str_truncate(jsonval, 0); json_append_escaped(jsonval, updated_username); o_stream_nsend_str(doveadm_print_ostream, str_c(jsonval)); o_stream_nsend_str(doveadm_print_ostream, "\""); } for (; *fields != NULL; fields++) { const char *field = *fields; if (*field == '\0') continue; p = strchr(*fields, '='); str_truncate(jsonval, 0); if (p != NULL) { field = t_strcut(*fields, '='); } str_truncate(jsonval, 0); json_append_escaped(jsonval, field); o_stream_nsend_str(doveadm_print_ostream, ",\""); o_stream_nsend_str(doveadm_print_ostream, str_c(jsonval)); o_stream_nsend_str(doveadm_print_ostream, "\":"); if (p != NULL) { str_truncate(jsonval, 0); json_append_escaped(jsonval, p+1); o_stream_nsend_str(doveadm_print_ostream, "\""); o_stream_nsend_str(doveadm_print_ostream, str_c(jsonval)); o_stream_nsend_str(doveadm_print_ostream, "\""); } else { o_stream_nsend_str(doveadm_print_ostream, "true"); } } } return ret; } static void auth_user_info_parse(struct auth_user_info *info, const char *arg) { if (str_begins(arg, "service=")) info->service = arg + 8; else if (str_begins(arg, "lip=")) { if (net_addr2ip(arg + 4, &info->local_ip) < 0) i_fatal("lip: Invalid ip"); } else if (str_begins(arg, "rip=")) { if (net_addr2ip(arg + 4, &info->remote_ip) < 0) i_fatal("rip: Invalid ip"); } else if (str_begins(arg, "lport=")) { if (net_str2port(arg + 6, &info->local_port) < 0) i_fatal("lport: Invalid port number"); } else if (str_begins(arg, "rport=")) { if (net_str2port(arg + 6, &info->remote_port) < 0) i_fatal("rport: Invalid port number"); } else { i_fatal("Unknown -x argument: %s", arg); } } static void cmd_user_list(struct auth_master_connection *conn, const struct authtest_input *input, char *const *users) { struct auth_master_user_list_ctx *ctx; const char *username, *user_mask = "*"; string_t *escaped = t_str_new(256); bool first = TRUE; unsigned int i; if (users[0] != NULL && users[1] == NULL) user_mask = users[0]; o_stream_nsend_str(doveadm_print_ostream, "{\"userList\":["); ctx = auth_master_user_list_init(conn, user_mask, &input->info); while ((username = auth_master_user_list_next(ctx)) != NULL) { for (i = 0; users[i] != NULL; i++) { if (wildcard_match_icase(username, users[i])) break; } if (users[i] != NULL) { if (first) first = FALSE; else o_stream_nsend_str(doveadm_print_ostream, ","); str_truncate(escaped, 0); str_append_c(escaped, '"'); json_append_escaped(escaped, username); str_append_c(escaped, '"'); o_stream_nsend(doveadm_print_ostream, escaped->data, escaped->used); } } if (auth_master_user_list_deinit(&ctx) < 0) { i_error("user listing failed"); doveadm_exit_code = EX_DATAERR; } o_stream_nsend_str(doveadm_print_ostream, "]}"); } static void cmd_auth_cache_flush(struct doveadm_cmd_context *cctx) { const char *master_socket_path, *const *users; struct auth_master_connection *conn; unsigned int count; if (!doveadm_cmd_param_str(cctx, "socket-path", &master_socket_path)) { master_socket_path = t_strconcat(doveadm_settings->base_dir, "/auth-master", NULL); } if (!doveadm_cmd_param_array(cctx, "user", &users)) i_fatal("Missing user parameter"); conn = doveadm_get_auth_master_conn(master_socket_path); if (auth_master_cache_flush(conn, users, &count) < 0) { i_error("Cache flush failed"); doveadm_exit_code = EX_TEMPFAIL; } else { doveadm_print_init("formatted"); doveadm_print_formatted_set_format("%{entries} cache entries flushed\n"); doveadm_print_header_simple("entries"); doveadm_print_num(count); } auth_master_deinit(&conn); } static void cmd_user_mail_input_field(const char *key, const char *value, const char *show_field, bool *first) { string_t *jvalue = t_str_new(128); if (show_field != NULL && strcmp(show_field, key) != 0) return; /* do not emit comma on first field. we need to keep track of when the first field actually gets printed as it might change due to show_field */ if (!*first) o_stream_nsend_str(doveadm_print_ostream, ","); *first = FALSE; json_append_escaped(jvalue, key); o_stream_nsend_str(doveadm_print_ostream, "\""); o_stream_nsend_str(doveadm_print_ostream, str_c(jvalue)); o_stream_nsend_str(doveadm_print_ostream, "\":\""); str_truncate(jvalue, 0); json_append_escaped(jvalue, value); o_stream_nsend_str(doveadm_print_ostream, str_c(jvalue)); o_stream_nsend_str(doveadm_print_ostream, "\""); } static void cmd_user_mail_print_fields(const struct authtest_input *input, struct mail_user *user, const char *const *userdb_fields, const char *show_field) { const struct mail_storage_settings *mail_set; const char *key, *value; unsigned int i; bool first = TRUE; if (strcmp(input->username, user->username) != 0) cmd_user_mail_input_field("user", user->username, show_field, &first); cmd_user_mail_input_field("uid", user->set->mail_uid, show_field, &first); cmd_user_mail_input_field("gid", user->set->mail_gid, show_field, &first); cmd_user_mail_input_field("home", user->set->mail_home, show_field, &first); mail_set = mail_user_set_get_storage_set(user); cmd_user_mail_input_field("mail", mail_set->mail_location, show_field, &first); if (userdb_fields != NULL) { for (i = 0; userdb_fields[i] != NULL; i++) { value = strchr(userdb_fields[i], '='); if (value != NULL) key = t_strdup_until(userdb_fields[i], value++); else { key = userdb_fields[i]; value = ""; } if (strcmp(key, "uid") != 0 && strcmp(key, "gid") != 0 && strcmp(key, "home") != 0 && strcmp(key, "mail") != 0 && *key != '\0') { cmd_user_mail_input_field(key, value, show_field, &first); } } } } static int cmd_user_mail_input(struct mail_storage_service_ctx *storage_service, const struct authtest_input *input, const char *show_field, const char *expand_field) { struct mail_storage_service_input service_input; struct mail_storage_service_user *service_user; struct mail_user *user; const char *error, *const *userdb_fields; pool_t pool; int ret; i_zero(&service_input); service_input.module = "mail"; service_input.service = input->info.service; service_input.username = input->username; service_input.local_ip = input->info.local_ip; service_input.local_port = input->info.local_port; service_input.remote_ip = input->info.remote_ip; service_input.remote_port = input->info.remote_port; service_input.debug = input->info.debug; pool = pool_alloconly_create("userdb fields", 1024); mail_storage_service_save_userdb_fields(storage_service, pool, &userdb_fields); if ((ret = mail_storage_service_lookup_next(storage_service, &service_input, &service_user, &user, &error)) <= 0) { pool_unref(&pool); if (ret < 0) return -1; string_t *username = t_str_new(32); json_append_escaped(username, input->username); o_stream_nsend_str(doveadm_print_ostream, t_strdup_printf("\"error\":\"userdb lookup: user %s doesn't exist\"", str_c(username)) ); return 0; } if (expand_field == NULL) cmd_user_mail_print_fields(input, user, userdb_fields, show_field); else { string_t *str = t_str_new(128); if (var_expand_with_funcs(str, expand_field, mail_user_var_expand_table(user), mail_user_var_expand_func_table, user, &error) <= 0) { string_t *str = t_str_new(128); str_printfa(str, "\"error\":\"Failed to expand field: "); json_append_escaped(str, error); str_append_c(str, '"'); o_stream_nsend(doveadm_print_ostream, str_data(str), str_len(str)); } else { string_t *value = t_str_new(128); json_append_escaped(value, expand_field); o_stream_nsend_str(doveadm_print_ostream, "\""); o_stream_nsend_str(doveadm_print_ostream, str_c(value)); o_stream_nsend_str(doveadm_print_ostream, "\":\""); str_truncate(value, 0); json_append_escaped(value, str_c(str)); o_stream_nsend_str(doveadm_print_ostream, str_c(value)); o_stream_nsend_str(doveadm_print_ostream, "\""); } } mail_user_deinit(&user); mail_storage_service_user_unref(&service_user); pool_unref(&pool); return 1; } static void cmd_user_ver2(struct doveadm_cmd_context *cctx) { const char * const *optval; const char *auth_socket_path = NULL; struct auth_master_connection *conn; struct authtest_input input; const char *show_field = NULL, *expand_field = NULL; struct mail_storage_service_ctx *storage_service = NULL; bool have_wildcards, userdb_only = FALSE, first = TRUE; int ret; if (!doveadm_cmd_param_str(cctx, "socket-path", &auth_socket_path)) auth_socket_path = doveadm_settings->auth_socket_path; (void)doveadm_cmd_param_str(cctx, "expand-field", &expand_field); (void)doveadm_cmd_param_str(cctx, "field", &show_field); (void)doveadm_cmd_param_bool(cctx, "userdb-only", &userdb_only); i_zero(&input); if (doveadm_cmd_param_array(cctx, "auth-info", &optval)) for(;*optval != NULL; optval++) auth_user_info_parse(&input.info, *optval); if (!doveadm_cmd_param_array(cctx, "user-mask", &optval)) { doveadm_exit_code = EX_USAGE; i_error("No user(s) specified"); return; } if (expand_field != NULL && userdb_only) { i_error("-e can't be used with -u"); doveadm_exit_code = EX_USAGE; return; } if (expand_field != NULL && show_field != NULL) { i_error("-e can't be used with -f"); doveadm_exit_code = EX_USAGE; return; } conn = doveadm_get_auth_master_conn(auth_socket_path); have_wildcards = FALSE; for(const char *const *val = optval; *val != NULL; val++) { if (strchr(*val, '*') != NULL || strchr(*val, '?') != NULL) { have_wildcards = TRUE; break; } } if (have_wildcards) { cmd_user_list(conn, &input, (char*const*)optval); auth_master_deinit(&conn); return; } if (!userdb_only) { storage_service = mail_storage_service_init(master_service, NULL, MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP | MAIL_STORAGE_SERVICE_FLAG_NO_CHDIR | MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT | MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS | MAIL_STORAGE_SERVICE_FLAG_NO_NAMESPACES | MAIL_STORAGE_SERVICE_FLAG_NO_RESTRICT_ACCESS); mail_storage_service_set_auth_conn(storage_service, conn); conn = NULL; } string_t *json = t_str_new(64); o_stream_nsend_str(doveadm_print_ostream, "{"); input.info.local_ip = cctx->local_ip; input.info.local_port = cctx->local_port; input.info.remote_ip = cctx->remote_ip; input.info.remote_port = cctx->remote_port; for(const char *const *val = optval; *val != NULL; val++) { str_truncate(json, 0); json_append_escaped(json, *val); input.username = *val; if (first) first = FALSE; else o_stream_nsend_str(doveadm_print_ostream, ","); o_stream_nsend_str(doveadm_print_ostream, "\""); o_stream_nsend_str(doveadm_print_ostream, str_c(json)); o_stream_nsend_str(doveadm_print_ostream, "\""); o_stream_nsend_str(doveadm_print_ostream, ":{"); ret = !userdb_only ? cmd_user_mail_input(storage_service, &input, show_field, expand_field) : cmd_user_input(conn, &input, show_field, TRUE); o_stream_nsend_str(doveadm_print_ostream, "}"); switch (ret) { case -1: doveadm_exit_code = EX_TEMPFAIL; break; case 0: doveadm_exit_code = EX_NOUSER; break; } } o_stream_nsend_str(doveadm_print_ostream,"}"); if (storage_service != NULL) mail_storage_service_deinit(&storage_service); if (conn != NULL) auth_master_deinit(&conn); } static struct doveadm_cmd_ver2 doveadm_cmd_auth_server[] = { { .name = "auth cache flush", .cmd = cmd_auth_cache_flush, .usage = "[-a ] [ [...]]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "user", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }, { .name = "user", .cmd = cmd_user_ver2, .usage = "[-a ] [-x ] [-f field] [-e ] [-u] [...]", .flags = CMD_FLAG_NO_PRINT, DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('x', "auth-info", CMD_PARAM_ARRAY, 0) DOVEADM_CMD_PARAM('f', "field", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('e', "expand-field", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('u', "userdb-only", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "user-mask", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END } }; void doveadm_register_auth_server_commands(void) { unsigned int i; for (i = 0; i < N_ELEMENTS(doveadm_cmd_auth_server); i++) { doveadm_cmd_register_ver2(&doveadm_cmd_auth_server[i]); } } dovecot-2.3.21.1/src/doveadm/doveadm-mail.h0000644000000000000000000002020314656633576015230 00000000000000#ifndef DOVEADM_MAIL_H #define DOVEADM_MAIL_H #include #include "doveadm.h" #include "doveadm-util.h" #include "module-context.h" #include "mail-error.h" #include "mail-storage.h" #include "mail-storage-service.h" struct mailbox; struct mailbox_list; struct mail_storage; struct mail_user; struct doveadm_mail_cmd_context; struct doveadm_mail_cmd_vfuncs { /* Parse one getopt() parameter. This is called for each parameter. */ bool (*parse_arg)(struct doveadm_mail_cmd_context *ctx, int c); /* Usually not needed. The preinit() is called just after parsing all parameters, but before any userdb lookups are done. This allows the preinit() to alter the userdb lookup behavior (especially service_flags). */ void (*preinit)(struct doveadm_mail_cmd_context *ctx); /* Initialize the command. Most importantly if the function prints anything, this should initialize the headers. It shouldn't however do any actual work. The init() is called also when doveadm is performing the work via doveadm-server, which could be running remotely with completely different Dovecot configuration. */ void (*init)(struct doveadm_mail_cmd_context *ctx, const char *const args[]); /* Usually not needed. When iterating through multiple users, use this function to get the next username. Overriding this is usually done only when there's a known username filter, such as the expire plugin. */ int (*get_next_user)(struct doveadm_mail_cmd_context *ctx, const char **username_r); /* Usually not needed. This is called between mail_storage_service_lookup() and mail_storage_service_next() for each user. */ int (*prerun)(struct doveadm_mail_cmd_context *ctx, struct mail_storage_service_user *service_user, const char **error_r); /* This is the main function which performs all the work for the command. This is called once per each user. */ int (*run)(struct doveadm_mail_cmd_context *ctx, struct mail_user *mail_user); /* Deinitialize the command. Called once at the end - even if preinit() or init() was never called. */ void (*deinit)(struct doveadm_mail_cmd_context *ctx); }; struct doveadm_mail_cmd_module_register { unsigned int id; }; union doveadm_mail_cmd_module_context { struct doveadm_mail_cmd_vfuncs super; struct doveadm_mail_cmd_module_register *reg; }; struct doveadm_mail_cmd_context { pool_t pool; struct doveadm_cmd_context *cctx; const struct doveadm_mail_cmd *cmd; const char *const *args; /* args including -options */ const char *const *full_args; const char *getopt_args; const struct doveadm_settings *set; enum mail_storage_service_flags service_flags; enum mailbox_transaction_flags transaction_flags; struct mail_storage_service_ctx *storage_service; struct mail_storage_service_input storage_service_input; /* search args aren't set for all mail commands */ struct mail_search_args *search_args; struct istream *users_list_input; struct mail_storage_service_user *cur_service_user; struct mail_user *cur_mail_user; struct doveadm_mail_cmd_vfuncs v; struct istream *cmd_input; int cmd_input_fd; ARRAY(union doveadm_mail_cmd_module_context *) module_contexts; /* if non-zero, exit with this code */ int exit_code; /* This command is being called by a remote doveadm client. */ bool proxying:1; /* We're handling only a single user */ bool iterate_single_user:1; /* We're going through all users (not set for wildcard usernames) */ bool iterate_all_users:1; /* Add username header to all replies */ bool add_username_header:1; }; struct doveadm_mail_cmd { struct doveadm_mail_cmd_context *(*alloc)(void); const char *name; const char *usage_args; }; extern void (*hook_doveadm_mail_init)(struct doveadm_mail_cmd_context *ctx); extern struct doveadm_mail_cmd_module_register doveadm_mail_cmd_module_register; extern char doveadm_mail_cmd_hide; bool doveadm_is_killed(void); int doveadm_killed_signo(void); void doveadm_mail_help(const struct doveadm_mail_cmd *cmd) ATTR_NORETURN; void doveadm_mail_help_name(const char *cmd_name) ATTR_NORETURN; void doveadm_mail_try_help_name(const char *cmd_name); void doveadm_mail_init(void); void doveadm_mail_init_finish(void); void doveadm_mail_deinit(void); struct doveadm_mail_cmd_context * doveadm_mail_cmd_init(const struct doveadm_mail_cmd *cmd, const struct doveadm_settings *set); void doveadm_mail_cmd_deinit(struct doveadm_mail_cmd_context *ctx); void doveadm_mail_cmd_free(struct doveadm_mail_cmd_context *ctx); int doveadm_mail_single_user(struct doveadm_mail_cmd_context *ctx, const char **error_r); int doveadm_mail_server_user(struct doveadm_mail_cmd_context *ctx, const struct mail_storage_service_input *input, const char **error_r); void doveadm_mail_server_flush(void); /* Request input stream to be read (from stdin). This must be called from the command's init() function. */ void doveadm_mail_get_input(struct doveadm_mail_cmd_context *ctx); struct mailbox * doveadm_mailbox_find(struct mail_user *user, const char *mailbox); struct mail_search_args * doveadm_mail_build_search_args(const char *const args[]); void doveadm_mailbox_args_check(const char *const args[]); struct mail_search_args * doveadm_mail_mailbox_search_args_build(const char *const args[]); void expunge_search_args_check(struct mail_search_args *args, const char *cmd); struct doveadm_mail_cmd_context * doveadm_mail_cmd_alloc_size(size_t size); #define doveadm_mail_cmd_alloc(type) \ (type *)doveadm_mail_cmd_alloc_size(sizeof(type)) void doveadm_mail_failed_error(struct doveadm_mail_cmd_context *ctx, enum mail_error error); void doveadm_mail_failed_storage(struct doveadm_mail_cmd_context *ctx, struct mail_storage *storage); void doveadm_mail_failed_mailbox(struct doveadm_mail_cmd_context *ctx, struct mailbox *box); void doveadm_mail_failed_list(struct doveadm_mail_cmd_context *ctx, struct mailbox_list *list); extern struct doveadm_cmd_ver2 doveadm_cmd_batch; extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_metadata_set_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_metadata_unset_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_metadata_get_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_metadata_list_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_status_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_list_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_create_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_delete_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_rename_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_subscribe_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_unsubscribe_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_fetch_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_save_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_index_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_altmove_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_deduplicate_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_expunge_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_flags_add_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_flags_remove_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_flags_replace_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_import_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_search_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_copy_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_move_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_update_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_path_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_cache_decision; extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_cache_remove; extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_cache_purge; extern struct doveadm_cmd_ver2 doveadm_cmd_rebuild_attachments; #define DOVEADM_CMD_MAIL_COMMON \ DOVEADM_CMD_PARAM('A', "all-users", CMD_PARAM_BOOL, 0) \ DOVEADM_CMD_PARAM('S', "socket-path", CMD_PARAM_STR, 0) \ DOVEADM_CMD_PARAM('u', "user", CMD_PARAM_STR, 0) \ DOVEADM_CMD_PARAM('\0', "trans-flags", CMD_PARAM_INT64, 0) \ DOVEADM_CMD_PARAM('F', "user-file", CMD_PARAM_ISTREAM, 0) #define DOVEADM_CMD_MAIL_USAGE_PREFIX \ "[-u |-A] [-S ] " #endif dovecot-2.3.21.1/src/doveadm/doveadm-kick.c0000644000000000000000000001410614656633576015227 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "net.h" #include "hash.h" #include "doveadm.h" #include "doveadm-who.h" #include "doveadm-print.h" #include #include #include #include struct kick_user { const char *username; bool kick_me; /* true if username and/or ip[/mask] matches. ignored when the -f switch is given. */ }; struct kick_pid { pid_t pid; ARRAY(struct kick_user) users; bool kick; }; struct kick_context { struct who_context who; HASH_TABLE(void *, struct kick_pid *) pids; enum doveadm_client_type conn_type; bool force_kick; ARRAY(const char *) kicked_users; }; static void kick_aggregate_line(struct who_context *_ctx, const struct who_line *line) { struct kick_context *ctx = (struct kick_context *)_ctx; const bool user_match = who_line_filter_match(line, &ctx->who.filter); struct kick_pid *k_pid; struct kick_user new_user, *user; i_zero(&new_user); k_pid = hash_table_lookup(ctx->pids, POINTER_CAST(line->pid)); if (k_pid == NULL) { k_pid = p_new(ctx->who.pool, struct kick_pid, 1); k_pid->pid = line->pid; p_array_init(&k_pid->users, ctx->who.pool, 5); hash_table_insert(ctx->pids, POINTER_CAST(line->pid), k_pid); } array_foreach_modifiable(&k_pid->users, user) { if (strcmp(line->username, user->username) == 0) { if (user_match) user->kick_me = TRUE; return; } } new_user.username = p_strdup(ctx->who.pool, line->username); new_user.kick_me = user_match; array_push_back(&k_pid->users, &new_user); } static bool kick_pid_want_kicked(struct kick_context *ctx, const struct kick_pid *k_pid, bool *show_warning) { unsigned int kick_count = 0; const struct kick_user *user; if (array_count(&k_pid->users) == 1) { user = array_front(&k_pid->users); if (!user->kick_me) return FALSE; } else { array_foreach(&k_pid->users, user) { if (user->kick_me) kick_count++; } if (kick_count == 0) return FALSE; if (kick_count < array_count(&k_pid->users) && !ctx->force_kick) { array_foreach(&k_pid->users, user) { if (!user->kick_me) { array_push_back(&ctx->kicked_users, &user->username); } } *show_warning = TRUE; return FALSE; } } return TRUE; } static void kick_print_kicked(struct kick_context *ctx, const bool show_warning) { unsigned int i, count; const char *const *users; bool cli = (ctx->conn_type == DOVEADM_CONNECTION_TYPE_CLI); if (array_count(&ctx->kicked_users) == 0) { if (cli) printf("no users kicked\n"); doveadm_exit_code = DOVEADM_EX_NOTFOUND; return; } if (cli) { if (show_warning) { printf("warning: other connections would also be " "kicked from following users:\n"); } else { printf("kicked connections from the following users:\n"); } } array_sort(&ctx->kicked_users, i_strcmp_p); users = array_get(&ctx->kicked_users, &count); doveadm_print(users[0]); for (i = 1; i < count; i++) { if (strcmp(users[i-1], users[i]) != 0) doveadm_print(users[i]); } doveadm_print_flush(); if (cli) printf("\n"); if (show_warning) printf("Use the '-f' option to enforce the disconnect.\n"); } static void kick_users(struct kick_context *ctx) { bool show_enforce_warning = FALSE; struct hash_iterate_context *iter; void *key; struct kick_pid *k_pid; const struct kick_user *user; p_array_init(&ctx->kicked_users, ctx->who.pool, 10); iter = hash_table_iterate_init(ctx->pids); while (hash_table_iterate(iter, ctx->pids, &key, &k_pid)) { if (kick_pid_want_kicked(ctx, k_pid, &show_enforce_warning)) k_pid->kick = TRUE; } hash_table_iterate_deinit(&iter); if (show_enforce_warning) { kick_print_kicked(ctx, show_enforce_warning); return; } iter = hash_table_iterate_init(ctx->pids); while (hash_table_iterate(iter, ctx->pids, &key, &k_pid)) { if (!k_pid->kick) continue; if (kill(k_pid->pid, SIGTERM) < 0 && errno != ESRCH) { fprintf(stderr, "kill(%s, SIGTERM) failed: %m\n", dec2str(k_pid->pid)); } else { array_foreach(&k_pid->users, user) { array_push_back(&ctx->kicked_users, &user->username); } } } hash_table_iterate_deinit(&iter); kick_print_kicked(ctx, show_enforce_warning); } static void cmd_kick(struct doveadm_cmd_context *cctx) { const char *const *masks; struct kick_context ctx; i_zero(&ctx); if (!doveadm_cmd_param_str(cctx, "socket-path", &(ctx.who.anvil_path))) ctx.who.anvil_path = t_strconcat(doveadm_settings->base_dir, "/anvil", NULL); (void)doveadm_cmd_param_bool(cctx, "force", &(ctx.force_kick)); if (!doveadm_cmd_param_array(cctx, "mask", &masks)) { doveadm_exit_code = EX_USAGE; i_error("user and/or ip[/bits] must be specified."); return; } ctx.conn_type = cctx->conn_type; if (ctx.conn_type != DOVEADM_CONNECTION_TYPE_CLI) { /* force-kick is a pretty ugly option. its output can't be nicely translated to an API reply. it also wouldn't be very useful in scripts, only for preventing a new admin from accidentally kicking too many users. it's also useful only in a non-recommended setup where processes are handling multiple connections. so for now we'll preserve the option for CLI, but always do a force-kick with non-CLI. */ ctx.force_kick = TRUE; } ctx.who.pool = pool_alloconly_create("kick pids", 10240); hash_table_create_direct(&ctx.pids, ctx.who.pool, 0); if (who_parse_args(&ctx.who, masks)!=0) { hash_table_destroy(&ctx.pids); pool_unref(&ctx.who.pool); return; } doveadm_print_init(DOVEADM_PRINT_TYPE_FORMATTED); doveadm_print_formatted_set_format("%{result} "); doveadm_print_header_simple("result"); who_lookup(&ctx.who, kick_aggregate_line); kick_users(&ctx); hash_table_destroy(&ctx.pids); pool_unref(&ctx.who.pool); } struct doveadm_cmd_ver2 doveadm_cmd_kick_ver2 = { .name = "kick", .cmd = cmd_kick, .usage = "[-a ] [|]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('a',"socket-path",CMD_PARAM_STR,0) DOVEADM_CMD_PARAM('f',"force",CMD_PARAM_BOOL,0) DOVEADM_CMD_PARAM('\0',"mask",CMD_PARAM_ARRAY,CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/dsync/0000755000000000000000000000000014656633641013714 500000000000000dovecot-2.3.21.1/src/doveadm/dsync/dsync-transaction-log-scan.c0000644000000000000000000004057014656633576021161 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "hash.h" #include "mail-index-modseq.h" #include "mail-storage-private.h" #include "dsync-mail.h" #include "dsync-mailbox.h" #include "dsync-transaction-log-scan.h" struct dsync_transaction_log_scan { pool_t pool; HASH_TABLE_TYPE(dsync_uid_mail_change) changes; HASH_TABLE_TYPE(dsync_attr_change) attr_changes; struct mail_index_view *view; uint32_t highest_wanted_uid; uint32_t last_log_seq; uoff_t last_log_offset; bool returned_all_changes; }; static bool ATTR_NOWARN_UNUSED_RESULT export_change_get(struct dsync_transaction_log_scan *ctx, uint32_t uid, enum dsync_mail_change_type type, struct dsync_mail_change **change_r) { struct dsync_mail_change *change; const char *orig_guid; i_assert(uid > 0); i_assert(type != DSYNC_MAIL_CHANGE_TYPE_SAVE); *change_r = NULL; if (uid > ctx->highest_wanted_uid) return FALSE; change = hash_table_lookup(ctx->changes, POINTER_CAST(uid)); if (change == NULL) { /* first change for this UID */ change = p_new(ctx->pool, struct dsync_mail_change, 1); change->uid = uid; change->type = type; hash_table_insert(ctx->changes, POINTER_CAST(uid), change); } else if (type == DSYNC_MAIL_CHANGE_TYPE_EXPUNGE) { /* expunge overrides flag changes */ orig_guid = change->guid; i_zero(change); change->type = type; change->uid = uid; change->guid = orig_guid; } else if (change->type == DSYNC_MAIL_CHANGE_TYPE_EXPUNGE) { /* already expunged, this change doesn't matter */ return FALSE; } else { /* another flag update */ } *change_r = change; return TRUE; } static void log_add_expunge(struct dsync_transaction_log_scan *ctx, const void *data, const struct mail_transaction_header *hdr) { const struct mail_transaction_expunge *rec = data, *end; struct dsync_mail_change *change; uint32_t uid; if ((hdr->type & MAIL_TRANSACTION_EXTERNAL) == 0) { /* this is simply a request for expunge */ return; } end = CONST_PTR_OFFSET(data, hdr->size); for (; rec != end; rec++) { for (uid = rec->uid1; uid <= rec->uid2; uid++) { export_change_get(ctx, uid, DSYNC_MAIL_CHANGE_TYPE_EXPUNGE, &change); } } } static bool log_add_expunge_uid(struct dsync_transaction_log_scan *ctx, const void *data, const struct mail_transaction_header *hdr, uint32_t uid) { const struct mail_transaction_expunge *rec = data, *end; struct dsync_mail_change *change; if ((hdr->type & MAIL_TRANSACTION_EXTERNAL) == 0) { /* this is simply a request for expunge */ return FALSE; } end = CONST_PTR_OFFSET(data, hdr->size); for (; rec != end; rec++) { if (uid >= rec->uid1 && uid <= rec->uid2) { export_change_get(ctx, uid, DSYNC_MAIL_CHANGE_TYPE_EXPUNGE, &change); return TRUE; } } return FALSE; } static void log_add_expunge_guid(struct dsync_transaction_log_scan *ctx, struct mail_index_view *view, const void *data, const struct mail_transaction_header *hdr) { const struct mail_transaction_expunge_guid *rec = data, *end; struct dsync_mail_change *change; uint32_t seq; bool external; external = (hdr->type & MAIL_TRANSACTION_EXTERNAL) != 0; end = CONST_PTR_OFFSET(data, hdr->size); for (; rec != end; rec++) { if (!external && mail_index_lookup_seq(view, rec->uid, &seq)) { /* expunge request that hasn't been actually done yet. we check non-external ones because they might have the GUID while external ones don't. */ continue; } if (export_change_get(ctx, rec->uid, DSYNC_MAIL_CHANGE_TYPE_EXPUNGE, &change) && !guid_128_is_empty(rec->guid_128)) T_BEGIN { change->guid = p_strdup(ctx->pool, guid_128_to_string(rec->guid_128)); } T_END; } } static bool log_add_expunge_guid_uid(struct dsync_transaction_log_scan *ctx, const void *data, const struct mail_transaction_header *hdr, uint32_t uid) { const struct mail_transaction_expunge_guid *rec = data, *end; struct dsync_mail_change *change; /* we're assuming UID is already known to be expunged */ end = CONST_PTR_OFFSET(data, hdr->size); for (; rec != end; rec++) { if (rec->uid != uid) continue; if (!export_change_get(ctx, rec->uid, DSYNC_MAIL_CHANGE_TYPE_EXPUNGE, &change)) i_unreached(); if (!guid_128_is_empty(rec->guid_128)) T_BEGIN { change->guid = p_strdup(ctx->pool, guid_128_to_string(rec->guid_128)); } T_END; return TRUE; } return FALSE; } static void log_add_flag_update(struct dsync_transaction_log_scan *ctx, const void *data, const struct mail_transaction_header *hdr) { const struct mail_transaction_flag_update *rec = data, *end; struct dsync_mail_change *change; uint32_t uid; end = CONST_PTR_OFFSET(data, hdr->size); for (; rec != end; rec++) { for (uid = rec->uid1; uid <= rec->uid2; uid++) { if (export_change_get(ctx, uid, DSYNC_MAIL_CHANGE_TYPE_FLAG_CHANGE, &change)) { change->add_flags |= rec->add_flags; change->remove_flags &= ENUM_NEGATE(rec->add_flags); change->remove_flags |= rec->remove_flags; change->add_flags &= ENUM_NEGATE(rec->remove_flags); } } } } static void log_add_keyword_reset(struct dsync_transaction_log_scan *ctx, const void *data, const struct mail_transaction_header *hdr) { const struct mail_transaction_keyword_reset *rec = data, *end; struct dsync_mail_change *change; uint32_t uid; end = CONST_PTR_OFFSET(data, hdr->size); for (; rec != end; rec++) { for (uid = rec->uid1; uid <= rec->uid2; uid++) { if (!export_change_get(ctx, uid, DSYNC_MAIL_CHANGE_TYPE_FLAG_CHANGE, &change)) continue; change->keywords_reset = TRUE; if (array_is_created(&change->keyword_changes)) array_clear(&change->keyword_changes); } } } static void keywords_change_remove(struct dsync_mail_change *change, const char *name) { const char *const *changes; unsigned int i, count; changes = array_get(&change->keyword_changes, &count); for (i = 0; i < count; i++) { if (strcmp(changes[i]+1, name) == 0) { array_delete(&change->keyword_changes, i, 1); break; } } } static void log_add_keyword_update(struct dsync_transaction_log_scan *ctx, const void *data, const struct mail_transaction_header *hdr) { const struct mail_transaction_keyword_update *rec = data; struct dsync_mail_change *change; const char *kw_name, *change_str; const uint32_t *uids, *end; unsigned int uids_offset; uint32_t uid; uids_offset = sizeof(*rec) + rec->name_size; if ((uids_offset % 4) != 0) uids_offset += 4 - (uids_offset % 4); kw_name = t_strndup((const void *)(rec+1), rec->name_size); switch (rec->modify_type) { case MODIFY_ADD: change_str = p_strdup_printf(ctx->pool, "%c%s", KEYWORD_CHANGE_ADD, kw_name); break; case MODIFY_REMOVE: change_str = p_strdup_printf(ctx->pool, "%c%s", KEYWORD_CHANGE_REMOVE, kw_name); break; default: i_unreached(); } uids = CONST_PTR_OFFSET(rec, uids_offset); end = CONST_PTR_OFFSET(rec, hdr->size); for (; uids < end; uids += 2) { for (uid = uids[0]; uid <= uids[1]; uid++) { if (!export_change_get(ctx, uid, DSYNC_MAIL_CHANGE_TYPE_FLAG_CHANGE, &change)) continue; if (!array_is_created(&change->keyword_changes)) { p_array_init(&change->keyword_changes, ctx->pool, 4); } else { keywords_change_remove(change, kw_name); } array_push_back(&change->keyword_changes, &change_str); } } } static void log_add_modseq_update(struct dsync_transaction_log_scan *ctx, const void *data, const struct mail_transaction_header *hdr, bool pvt_scan) { const struct mail_transaction_modseq_update *rec = data, *end; struct dsync_mail_change *change; uint64_t modseq; /* update message's modseq, possibly by creating an empty flag change */ end = CONST_PTR_OFFSET(rec, hdr->size); for (; rec != end; rec++) { if (rec->uid == 0) { /* highestmodseq update */ continue; } if (!export_change_get(ctx, rec->uid, DSYNC_MAIL_CHANGE_TYPE_FLAG_CHANGE, &change)) continue; modseq = rec->modseq_low32 | ((uint64_t)rec->modseq_high32 << 32); if (!pvt_scan) { if (change->modseq < modseq) change->modseq = modseq; } else { if (change->pvt_modseq < modseq) change->pvt_modseq = modseq; } } } static void log_add_attribute_update_key(struct dsync_transaction_log_scan *ctx, const char *attr_change, uint64_t modseq) { struct dsync_mailbox_attribute lookup_attr, *attr; i_assert(strlen(attr_change) > 2); /* checked by lib-index */ lookup_attr.type = attr_change[1] == 'p' ? MAIL_ATTRIBUTE_TYPE_PRIVATE : MAIL_ATTRIBUTE_TYPE_SHARED; lookup_attr.key = attr_change+2; attr = hash_table_lookup(ctx->attr_changes, &lookup_attr); if (attr == NULL) { attr = p_new(ctx->pool, struct dsync_mailbox_attribute, 1); attr->type = lookup_attr.type; attr->key = p_strdup(ctx->pool, lookup_attr.key); hash_table_insert(ctx->attr_changes, attr, attr); } attr->deleted = attr_change[0] == '-'; attr->modseq = modseq; } static void log_add_attribute_update(struct dsync_transaction_log_scan *ctx, const void *data, const struct mail_transaction_header *hdr, uint64_t modseq) { const char *attr_changes = data; unsigned int i; for (i = 0; i < hdr->size && attr_changes[i] != '\0'; ) { log_add_attribute_update_key(ctx, attr_changes+i, modseq); i += strlen(attr_changes+i) + 1; } } static int dsync_log_set(struct dsync_transaction_log_scan *ctx, struct mail_index_view *view, bool pvt_scan, struct mail_transaction_log_view *log_view, uint64_t modseq) { uint32_t log_seq, end_seq; uoff_t log_offset, end_offset; const char *reason; bool reset; int ret; end_seq = view->log_file_head_seq; end_offset = view->log_file_head_offset; if (modseq != 0 && mail_index_modseq_get_next_log_offset(view, modseq, &log_seq, &log_offset)) { /* scan the view only up to end of the current view. if there are more changes, we don't care about them until the next sync. */ ret = mail_transaction_log_view_set(log_view, log_seq, log_offset, end_seq, end_offset, &reset, &reason); if (ret != 0) return ret; } /* return everything we've got (until the end of the view) */ if (!pvt_scan) ctx->returned_all_changes = TRUE; if (mail_transaction_log_view_set_all(log_view) < 0) return -1; mail_transaction_log_view_get_prev_pos(log_view, &log_seq, &log_offset); if (log_seq > end_seq || (log_seq == end_seq && log_offset > end_offset)) { end_seq = log_seq; end_offset = log_offset; } ret = mail_transaction_log_view_set(log_view, log_seq, log_offset, end_seq, end_offset, &reset, &reason); if (ret == 0) { /* we shouldn't get here. _view_set_all() already reserved all the log files, the _view_set() only removed unwanted ones. */ i_error("%s: Couldn't set transaction log view (seq %u..%u): %s", view->index->filepath, log_seq, end_seq, reason); ret = -1; } if (ret < 0) return -1; if (modseq != 0) { /* we didn't see all the changes that we wanted to */ return 0; } return 1; } static int dsync_log_scan(struct dsync_transaction_log_scan *ctx, struct mail_index_view *view, uint64_t modseq, bool pvt_scan) { struct mail_transaction_log_view *log_view; const struct mail_transaction_header *hdr; const void *data; uint32_t file_seq, max_seq; uoff_t file_offset, max_offset; uint64_t cur_modseq; int ret; log_view = mail_transaction_log_view_open(view->index->log); if ((ret = dsync_log_set(ctx, view, pvt_scan, log_view, modseq)) < 0) { mail_transaction_log_view_close(&log_view); return -1; } /* read the log only up to current position in view */ max_seq = view->log_file_expunge_seq; max_offset = view->log_file_expunge_offset; mail_transaction_log_view_get_prev_pos(log_view, &file_seq, &file_offset); while (mail_transaction_log_view_next(log_view, &hdr, &data) > 0) { mail_transaction_log_view_get_prev_pos(log_view, &file_seq, &file_offset); if (file_offset >= max_offset && file_seq == max_seq) break; if ((hdr->type & MAIL_TRANSACTION_SYNC) != 0) { /* ignore changes done by dsync, unless we can get expunged message's GUID from it */ if ((hdr->type & MAIL_TRANSACTION_TYPE_MASK) != MAIL_TRANSACTION_EXPUNGE_GUID) continue; } switch (hdr->type & MAIL_TRANSACTION_TYPE_MASK) { case MAIL_TRANSACTION_EXPUNGE: if (!pvt_scan) log_add_expunge(ctx, data, hdr); break; case MAIL_TRANSACTION_EXPUNGE_GUID: if (!pvt_scan) log_add_expunge_guid(ctx, view, data, hdr); break; case MAIL_TRANSACTION_FLAG_UPDATE: log_add_flag_update(ctx, data, hdr); break; case MAIL_TRANSACTION_KEYWORD_RESET: log_add_keyword_reset(ctx, data, hdr); break; case MAIL_TRANSACTION_KEYWORD_UPDATE: T_BEGIN { log_add_keyword_update(ctx, data, hdr); } T_END; break; case MAIL_TRANSACTION_MODSEQ_UPDATE: log_add_modseq_update(ctx, data, hdr, pvt_scan); break; case MAIL_TRANSACTION_ATTRIBUTE_UPDATE: cur_modseq = mail_transaction_log_view_get_prev_modseq(log_view); log_add_attribute_update(ctx, data, hdr, cur_modseq); break; } } if (!pvt_scan) { ctx->last_log_seq = file_seq; ctx->last_log_offset = file_offset; } mail_transaction_log_view_close(&log_view); return ret; } static int dsync_mailbox_attribute_cmp(const struct dsync_mailbox_attribute *attr1, const struct dsync_mailbox_attribute *attr2) { if (attr1->type < attr2->type) return -1; if (attr1->type > attr2->type) return 1; return strcmp(attr1->key, attr2->key); } static unsigned int dsync_mailbox_attribute_hash(const struct dsync_mailbox_attribute *attr) { return str_hash(attr->key) ^ attr->type; } int dsync_transaction_log_scan_init(struct mail_index_view *view, struct mail_index_view *pvt_view, uint32_t highest_wanted_uid, uint64_t modseq, uint64_t pvt_modseq, struct dsync_transaction_log_scan **scan_r, bool *pvt_too_old_r) { struct dsync_transaction_log_scan *ctx; pool_t pool; int ret, ret2; *pvt_too_old_r = FALSE; pool = pool_alloconly_create(MEMPOOL_GROWING"dsync transaction log scan", 10240); ctx = p_new(pool, struct dsync_transaction_log_scan, 1); ctx->pool = pool; hash_table_create_direct(&ctx->changes, pool, 0); hash_table_create(&ctx->attr_changes, pool, 0, dsync_mailbox_attribute_hash, dsync_mailbox_attribute_cmp); ctx->view = view; ctx->highest_wanted_uid = highest_wanted_uid; if ((ret = dsync_log_scan(ctx, view, modseq, FALSE)) < 0) return -1; if (pvt_view != NULL) { if ((ret2 = dsync_log_scan(ctx, pvt_view, pvt_modseq, TRUE)) < 0) return -1; if (ret2 == 0) { ret = 0; *pvt_too_old_r = TRUE; } } *scan_r = ctx; return ret; } HASH_TABLE_TYPE(dsync_uid_mail_change) dsync_transaction_log_scan_get_hash(struct dsync_transaction_log_scan *scan) { return scan->changes; } HASH_TABLE_TYPE(dsync_attr_change) dsync_transaction_log_scan_get_attr_hash(struct dsync_transaction_log_scan *scan) { return scan->attr_changes; } bool dsync_transaction_log_scan_has_all_changes(struct dsync_transaction_log_scan *scan) { return scan->returned_all_changes; } struct dsync_mail_change * dsync_transaction_log_scan_find_new_expunge(struct dsync_transaction_log_scan *scan, uint32_t uid) { struct mail_transaction_log_view *log_view; const struct mail_transaction_header *hdr; const void *data; const char *reason; bool reset, found = FALSE; i_assert(uid > 0); if (scan->highest_wanted_uid < uid) scan->highest_wanted_uid = uid; log_view = mail_transaction_log_view_open(scan->view->index->log); if (mail_transaction_log_view_set(log_view, scan->last_log_seq, scan->last_log_offset, (uint32_t)-1, UOFF_T_MAX, &reset, &reason) > 0) { while (!found && mail_transaction_log_view_next(log_view, &hdr, &data) > 0) { switch (hdr->type & MAIL_TRANSACTION_TYPE_MASK) { case MAIL_TRANSACTION_EXPUNGE: if (log_add_expunge_uid(scan, data, hdr, uid)) found = TRUE; break; case MAIL_TRANSACTION_EXPUNGE_GUID: if (log_add_expunge_guid_uid(scan, data, hdr, uid)) found = TRUE; break; } } } mail_transaction_log_view_close(&log_view); return !found ? NULL : hash_table_lookup(scan->changes, POINTER_CAST(uid)); } void dsync_transaction_log_scan_deinit(struct dsync_transaction_log_scan **_scan) { struct dsync_transaction_log_scan *scan = *_scan; *_scan = NULL; hash_table_destroy(&scan->changes); hash_table_destroy(&scan->attr_changes); pool_unref(&scan->pool); } dovecot-2.3.21.1/src/doveadm/dsync/dsync-brain.h0000644000000000000000000001323614656633576016232 00000000000000#ifndef DSYNC_BRAIN_H #define DSYNC_BRAIN_H #include "module-context.h" #include "guid.h" #include "mail-error.h" #include "mailbox-list-private.h" struct mail_namespace; struct mail_user; struct dsync_ibc; enum dsync_brain_flags { DSYNC_BRAIN_FLAG_SEND_MAIL_REQUESTS = 0x01, DSYNC_BRAIN_FLAG_BACKUP_SEND = 0x02, DSYNC_BRAIN_FLAG_BACKUP_RECV = 0x04, DSYNC_BRAIN_FLAG_DEBUG = 0x08, DSYNC_BRAIN_FLAG_SYNC_VISIBLE_NAMESPACES= 0x10, /* Sync everything but the actual mails (e.g. mailbox creates, deletes) */ DSYNC_BRAIN_FLAG_NO_MAIL_SYNC = 0x20, /* Used with BACKUP_SEND/RECV: Don't force the Use the two-way syncing algorithm, but don't actually modify anything locally. (Useful during migration.) */ DSYNC_BRAIN_FLAG_NO_BACKUP_OVERWRITE = 0x40, /* Run storage purge on the remote after syncing. Useful with e.g. a nightly doveadm backup. */ DSYNC_BRAIN_FLAG_PURGE_REMOTE = 0x80, /* Don't prefetch mail bodies until they're actually needed. This works only with pipe ibc. It's useful if most of the mails can be copied directly within filesystem without having to read them. */ DSYNC_BRAIN_FLAG_NO_MAIL_PREFETCH = 0x100, /* Add MAILBOX_TRANSACTION_FLAG_NO_NOTIFY to transactions. */ DSYNC_BRAIN_FLAG_NO_NOTIFY = 0x400, /* Workaround missing Date/Message-ID headers */ DSYNC_BRAIN_FLAG_EMPTY_HDR_WORKAROUND = 0x800, /* If mail GUIDs aren't supported, don't emulate them with header hashes either. This trusts that all the existing mails with identical UIDs have the same email content. This makes it slightly less safe, but can have huge performance improvement with imapc if the remote server doesn't have a fast header cache. */ DSYNC_BRAIN_FLAG_NO_HEADER_HASHES = 0x1000, }; enum dsync_brain_sync_type { DSYNC_BRAIN_SYNC_TYPE_UNKNOWN, /* Go through all mailboxes to make sure everything is synced */ DSYNC_BRAIN_SYNC_TYPE_FULL, /* Go through all mailboxes that have changed (based on UIDVALIDITY, UIDNEXT, HIGHESTMODSEQ). If both sides have had equal amount of changes in some mailbox, it may get incorrectly skipped. */ DSYNC_BRAIN_SYNC_TYPE_CHANGED, /* Use saved state to find out what has changed. */ DSYNC_BRAIN_SYNC_TYPE_STATE }; struct dsync_brain_settings { const char *process_title_prefix; /* Sync only these namespaces */ ARRAY(struct mail_namespace *) sync_namespaces; /* Sync only this mailbox name */ const char *sync_box; /* Use this virtual \All mailbox to be able to copy mails with the same GUID instead of saving them twice. With most storages this results in less disk space usage. */ const char *virtual_all_box; /* Sync only this mailbox GUID */ guid_128_t sync_box_guid; /* Exclude these mailboxes from the sync. They can contain '*' wildcards and be \special-use flags. */ const char *const *exclude_mailboxes; /* Alternative character to use in mailbox names where the original character cannot be used. */ char mailbox_alt_char; /* Sync only mails with received timestamp at least this high. */ time_t sync_since_timestamp; /* Sync only mails with received timestamp less or equal than this */ time_t sync_until_timestamp; /* Don't sync mails larger than this. */ uoff_t sync_max_size; /* Sync only mails which contains / doesn't contain this flag. '-' at the beginning means this flag must not exist. */ const char *sync_flag; /* Headers to hash (defaults to Date, Message-ID) */ const char *const *hashed_headers; /* If non-zero, use dsync lock file for this user */ unsigned int lock_timeout_secs; /* If non-zero, importing will attempt to commit transaction after saving this many messages. */ unsigned int import_commit_msgs_interval; /* Input state for DSYNC_BRAIN_SYNC_TYPE_STATE */ const char *state; }; #define DSYNC_LIST_CONTEXT(obj) \ MODULE_CONTEXT(obj, dsync_mailbox_list_module) struct dsync_mailbox_list { union mailbox_list_module_context module_ctx; bool have_orig_escape_char; }; extern MODULE_CONTEXT_DEFINE(dsync_mailbox_list_module, &mailbox_list_module_register); struct dsync_brain * dsync_brain_master_init(struct mail_user *user, struct dsync_ibc *ibc, enum dsync_brain_sync_type sync_type, enum dsync_brain_flags flags, const struct dsync_brain_settings *set); struct dsync_brain * dsync_brain_slave_init(struct mail_user *user, struct dsync_ibc *ibc, bool local, const char *process_title_prefix, char default_alt_char); /* Returns 0 if everything was successful, -1 if syncing failed in some way */ int dsync_brain_deinit(struct dsync_brain **brain, enum mail_error *error_r); /* Returns TRUE if brain needs to run more, FALSE if it's finished. changed_r is TRUE if anything happened during this run. */ bool dsync_brain_run(struct dsync_brain *brain, bool *changed_r); /* Returns TRUE if brain has failed, and there's no point in continuing. */ bool dsync_brain_has_failed(struct dsync_brain *brain); /* Returns the current sync state string, which can be given as parameter to dsync_brain_master_init() to quickly sync only the new changes. */ void dsync_brain_get_state(struct dsync_brain *brain, string_t *output); /* Returns the sync type that was used. Mainly useful with slave brain. */ enum dsync_brain_sync_type dsync_brain_get_sync_type(struct dsync_brain *brain); /* If there were any unexpected changes during the sync, return the reason for them. Otherwise return NULL. If remote_only_r=TRUE, this brain itself didn't see any changes, but the remote brain did. */ const char *dsync_brain_get_unexpected_changes_reason(struct dsync_brain *brain, bool *remote_only_r); /* Returns TRUE if we want to sync this namespace. */ bool dsync_brain_want_namespace(struct dsync_brain *brain, struct mail_namespace *ns); #endif dovecot-2.3.21.1/src/doveadm/dsync/dsync-mailbox-tree.c0000644000000000000000000003645714656633576017534 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "hash.h" #include "str.h" #include "sort.h" #include "mailbox-list-private.h" #include "dsync-mailbox-tree-private.h" struct dsync_mailbox_tree_iter { struct dsync_mailbox_tree *tree; struct dsync_mailbox_node *cur; string_t *name; }; struct dsync_mailbox_tree * dsync_mailbox_tree_init(char sep, char escape_char, char alt_char) { struct dsync_mailbox_tree *tree; pool_t pool; i_assert(sep != '\0'); i_assert(alt_char != '\0'); pool = pool_alloconly_create(MEMPOOL_GROWING"dsync mailbox tree", 4096); tree = p_new(pool, struct dsync_mailbox_tree, 1); tree->pool = pool; tree->sep = tree->sep_str[0] = sep; tree->escape_char = escape_char; tree->alt_char = alt_char; tree->root.name = ""; i_array_init(&tree->deletes, 32); return tree; } void dsync_mailbox_tree_deinit(struct dsync_mailbox_tree **_tree) { struct dsync_mailbox_tree *tree = *_tree; *_tree = NULL; hash_table_destroy(&tree->name128_hash); hash_table_destroy(&tree->name128_remotesep_hash); hash_table_destroy(&tree->guid_hash); array_free(&tree->deletes); pool_unref(&tree->pool); } static struct dsync_mailbox_node * dsync_mailbox_node_find(struct dsync_mailbox_node *nodes, const char *name) { for (; nodes != NULL; nodes = nodes->next) { if (strcmp(name, nodes->name) == 0) return nodes; } return NULL; } struct dsync_mailbox_node * dsync_mailbox_tree_lookup(struct dsync_mailbox_tree *tree, const char *full_name) { struct dsync_mailbox_node *node = &tree->root; T_BEGIN { const char *const *path; path = t_strsplit(full_name, tree->sep_str); for (; *path != NULL && node != NULL; path++) node = dsync_mailbox_node_find(node->first_child, *path); } T_END; return node; } void dsync_mailbox_tree_node_attach(struct dsync_mailbox_node *node, struct dsync_mailbox_node *parent) { node->parent = parent; node->next = parent->first_child; parent->first_child = node; } void dsync_mailbox_tree_node_detach(struct dsync_mailbox_node *node) { struct dsync_mailbox_node **p; for (p = &node->parent->first_child;; p = &(*p)->next) { if (*p == node) { *p = node->next; break; } } node->parent = NULL; } struct dsync_mailbox_node * dsync_mailbox_tree_get(struct dsync_mailbox_tree *tree, const char *full_name) { struct dsync_mailbox_node *parent = NULL, *node = &tree->root; i_assert(tree->iter_count == 0); T_BEGIN { const char *const *path; /* find the existing part */ path = t_strsplit(full_name, tree->sep_str); for (; *path != NULL; path++) { parent = node; node = dsync_mailbox_node_find(node->first_child, *path); if (node == NULL) break; } /* create the rest */ for (; *path != NULL; path++) { node = p_new(tree->pool, struct dsync_mailbox_node, 1); node->name = p_strdup(tree->pool, *path); node->ns = parent->ns; dsync_mailbox_tree_node_attach(node, parent); parent = node; } } T_END; return node; } static void node_get_full_name_recurse(const struct dsync_mailbox_tree *tree, const struct dsync_mailbox_node *node, string_t *str) { if (node->parent != &tree->root) node_get_full_name_recurse(tree, node->parent, str); str_append(str, node->name); str_append_c(str, tree->sep); } const char *dsync_mailbox_node_get_full_name(const struct dsync_mailbox_tree *tree, const struct dsync_mailbox_node *node) { string_t *str = t_str_new(128); dsync_mailbox_node_append_full_name(str, tree, node); return str_c(str); } void dsync_mailbox_node_append_full_name(string_t *str, const struct dsync_mailbox_tree *tree, const struct dsync_mailbox_node *node) { i_assert(node->parent != NULL); node_get_full_name_recurse(tree, node, str); /* remove the trailing separator */ str_truncate(str, str_len(str)-1); } void dsync_mailbox_node_copy_data(struct dsync_mailbox_node *dest, const struct dsync_mailbox_node *src) { memcpy(dest->mailbox_guid, src->mailbox_guid, sizeof(dest->mailbox_guid)); dest->uid_validity = src->uid_validity; dest->uid_next = src->uid_next; dest->existence = src->existence; dest->last_renamed_or_created = src->last_renamed_or_created; dest->subscribed = src->subscribed; dest->last_subscription_change = src->last_subscription_change; } struct dsync_mailbox_tree_iter * dsync_mailbox_tree_iter_init(struct dsync_mailbox_tree *tree) { struct dsync_mailbox_tree_iter *iter; iter = i_new(struct dsync_mailbox_tree_iter, 1); iter->tree = tree; iter->name = str_new(default_pool, 128); iter->cur = &tree->root; tree->iter_count++; return iter; } static size_t node_get_full_name_length(struct dsync_mailbox_node *node) { if (node->parent->parent == NULL) return strlen(node->name); else { return strlen(node->name) + 1 + node_get_full_name_length(node->parent); } } bool dsync_mailbox_tree_iter_next(struct dsync_mailbox_tree_iter *iter, const char **full_name_r, struct dsync_mailbox_node **node_r) { size_t len; if (iter->cur->first_child != NULL) iter->cur = iter->cur->first_child; else { while (iter->cur->next == NULL) { if (iter->cur == &iter->tree->root) return FALSE; iter->cur = iter->cur->parent; } iter->cur = iter->cur->next; len = iter->cur->parent == &iter->tree->root ? 0 : node_get_full_name_length(iter->cur->parent); str_truncate(iter->name, len); } if (str_len(iter->name) > 0) str_append_c(iter->name, iter->tree->sep); str_append(iter->name, iter->cur->name); *full_name_r = str_c(iter->name); *node_r = iter->cur; return TRUE; } void dsync_mailbox_tree_iter_deinit(struct dsync_mailbox_tree_iter **_iter) { struct dsync_mailbox_tree_iter *iter = *_iter; *_iter = NULL; i_assert(iter->tree->iter_count > 0); iter->tree->iter_count--; str_free(&iter->name); i_free(iter); } void dsync_mailbox_tree_build_name128_hash(struct dsync_mailbox_tree *tree) { struct dsync_mailbox_tree_iter *iter; struct dsync_mailbox_node *node; const char *name; guid_128_t *sha128; uint8_t *guid_p; i_assert(!hash_table_is_created(tree->name128_hash)); hash_table_create(&tree->name128_hash, tree->pool, 0, guid_128_hash, guid_128_cmp); iter = dsync_mailbox_tree_iter_init(tree); while (dsync_mailbox_tree_iter_next(iter, &name, &node)) { sha128 = p_new(tree->pool, guid_128_t, 1); mailbox_name_get_sha128(name, *sha128); guid_p = *sha128; hash_table_insert(tree->name128_hash, guid_p, node); } dsync_mailbox_tree_iter_deinit(&iter); } static const char * convert_name_to_remote_sep(struct dsync_mailbox_tree *tree, const char *name) { string_t *str = t_str_new(128); char remote_escape_chars[3] = { tree->remote_escape_char, tree->remote_sep, '\0' }; for (;;) { const char *end = strchr(name, tree->sep); const char *name_part = end == NULL ? name : t_strdup_until(name, end++); name = end; if (tree->escape_char != '\0') mailbox_list_name_unescape(&name_part, tree->escape_char); if (remote_escape_chars[0] != '\0') { /* The local name can be fully escaped to remote name and back. */ mailbox_list_name_escape(name_part, remote_escape_chars, str); } else { /* There is no remote escape char, so for conflicting separator use the alt_char. */ for (; *name_part != '\0'; name_part++) { if (*name_part == tree->remote_sep) str_append_c(str, tree->alt_char); else str_append_c(str, *name_part); } } if (end == NULL) break; str_append_c(str, tree->remote_sep); } return str_c(str); } static void dsync_mailbox_tree_build_name128_remotesep_hash(struct dsync_mailbox_tree *tree) { struct dsync_mailbox_tree_iter *iter; struct dsync_mailbox_node *node; const char *name; guid_128_t *sha128; uint8_t *guid_p; i_assert(tree->sep != tree->remote_sep); i_assert(!hash_table_is_created(tree->name128_remotesep_hash)); hash_table_create(&tree->name128_remotesep_hash, tree->pool, 0, guid_128_hash, guid_128_cmp); iter = dsync_mailbox_tree_iter_init(tree); while (dsync_mailbox_tree_iter_next(iter, &name, &node)) { sha128 = p_new(tree->pool, guid_128_t, 1); T_BEGIN { const char *remote_name = convert_name_to_remote_sep(tree, name); mailbox_name_get_sha128(remote_name, *sha128); } T_END; guid_p = *sha128; hash_table_insert(tree->name128_remotesep_hash, guid_p, node); } dsync_mailbox_tree_iter_deinit(&iter); } int dsync_mailbox_tree_guid_hash_add(struct dsync_mailbox_tree *tree, struct dsync_mailbox_node *node, struct dsync_mailbox_node **old_node_r) { struct dsync_mailbox_node *old_node; uint8_t *guid = node->mailbox_guid; if (guid_128_is_empty(node->mailbox_guid)) return 0; *old_node_r = old_node = hash_table_lookup(tree->guid_hash, guid); if (old_node == NULL) hash_table_insert(tree->guid_hash, guid, node); else if (old_node != node) return -1; return 0; } int dsync_mailbox_tree_build_guid_hash(struct dsync_mailbox_tree *tree, struct dsync_mailbox_node **dup_node1_r, struct dsync_mailbox_node **dup_node2_r) { struct dsync_mailbox_tree_iter *iter; struct dsync_mailbox_node *node, *old_node; const char *name; int ret = 0; if (!hash_table_is_created(tree->guid_hash)) { hash_table_create(&tree->guid_hash, tree->pool, 0, guid_128_hash, guid_128_cmp); } iter = dsync_mailbox_tree_iter_init(tree); while (dsync_mailbox_tree_iter_next(iter, &name, &node)) { if (dsync_mailbox_tree_guid_hash_add(tree, node, &old_node) < 0) { *dup_node1_r = node; *dup_node2_r = old_node; ret = -1; } } dsync_mailbox_tree_iter_deinit(&iter); return ret; } struct dsync_mailbox_node * dsync_mailbox_tree_lookup_guid(struct dsync_mailbox_tree *tree, const guid_128_t guid) { const uint8_t *guid_p = guid; return hash_table_lookup(tree->guid_hash, guid_p); } const struct dsync_mailbox_delete * dsync_mailbox_tree_get_deletes(struct dsync_mailbox_tree *tree, unsigned int *count_r) { return array_get(&tree->deletes, count_r); } struct dsync_mailbox_node * dsync_mailbox_tree_find_delete(struct dsync_mailbox_tree *tree, const struct dsync_mailbox_delete *del) { const uint8_t *guid_p = del->guid; i_assert(hash_table_is_created(tree->guid_hash)); i_assert(tree->remote_sep != '\0'); if (del->type == DSYNC_MAILBOX_DELETE_TYPE_MAILBOX) { /* find node by GUID */ return hash_table_lookup(tree->guid_hash, guid_p); } /* find node by name. this is a bit tricky, since the hierarchy separator may differ from ours. */ if (tree->sep == tree->remote_sep) { if (!hash_table_is_created(tree->name128_hash)) dsync_mailbox_tree_build_name128_hash(tree); return hash_table_lookup(tree->name128_hash, guid_p); } else { if (!hash_table_is_created(tree->name128_remotesep_hash)) dsync_mailbox_tree_build_name128_remotesep_hash(tree); return hash_table_lookup(tree->name128_remotesep_hash, guid_p); } } void dsync_mailbox_tree_set_remote_chars(struct dsync_mailbox_tree *tree, char remote_sep, char escape_char) { i_assert(tree->remote_sep == '\0'); i_assert(tree->remote_escape_char == '\0'); tree->remote_sep = remote_sep; tree->remote_escape_char = escape_char; } static void dsync_mailbox_tree_dup_nodes(struct dsync_mailbox_tree *dest_tree, const struct dsync_mailbox_node *src, string_t *path) { size_t prefix_len = str_len(path); struct dsync_mailbox_node *node; if (prefix_len > 0) { str_append_c(path, dest_tree->sep); prefix_len++; } for (; src != NULL; src = src->next) { str_truncate(path, prefix_len); str_append(path, src->name); node = dsync_mailbox_tree_get(dest_tree, str_c(path)); node->ns = src->ns; memcpy(node->mailbox_guid, src->mailbox_guid, sizeof(node->mailbox_guid)); node->uid_validity = src->uid_validity; node->uid_next = src->uid_next; node->existence = src->existence; node->last_renamed_or_created = src->last_renamed_or_created; node->subscribed = src->subscribed; node->last_subscription_change = src->last_subscription_change; if (src->first_child != NULL) { dsync_mailbox_tree_dup_nodes(dest_tree, src->first_child, path); } } } struct dsync_mailbox_tree * dsync_mailbox_tree_dup(const struct dsync_mailbox_tree *src) { struct dsync_mailbox_tree *dest; string_t *str = t_str_new(128); dest = dsync_mailbox_tree_init(src->sep, src->escape_char, src->alt_char); dsync_mailbox_tree_dup_nodes(dest, &src->root, str); return dest; } int dsync_mailbox_node_name_cmp(struct dsync_mailbox_node *const *n1, struct dsync_mailbox_node *const *n2) { return strcmp((*n1)->name, (*n2)->name); } static bool dsync_mailbox_nodes_equal(const struct dsync_mailbox_node *node1, const struct dsync_mailbox_node *node2) { return strcmp(node1->name, node2->name) == 0 && node1->ns == node2->ns && memcmp(node1->mailbox_guid, node2->mailbox_guid, sizeof(node1->mailbox_guid)) == 0 && node1->uid_validity == node2->uid_validity && node1->existence == node2->existence && node1->subscribed == node2->subscribed; } static bool dsync_mailbox_branches_equal(struct dsync_mailbox_node *node1, struct dsync_mailbox_node *node2) { /* this function is used only for unit tests, so performance doesn't really matter */ struct dsync_mailbox_node *n, **snodes1, **snodes2; unsigned int i, count; for (n = node1, count = 0; n != NULL; n = n->next) count++; for (n = node2, i = 0; n != NULL; n = n->next) i++; if (i != count) return FALSE; if (count == 0) return TRUE; /* sort the trees by name */ snodes1 = t_new(struct dsync_mailbox_node *, count); snodes2 = t_new(struct dsync_mailbox_node *, count); for (n = node1, i = 0; n != NULL; n = n->next, i++) snodes1[i] = n; for (n = node2, i = 0; n != NULL; n = n->next, i++) snodes2[i] = n; i_qsort(snodes1, count, sizeof(*snodes1), dsync_mailbox_node_name_cmp); i_qsort(snodes2, count, sizeof(*snodes2), dsync_mailbox_node_name_cmp); for (i = 0; i < count; i++) { if (!dsync_mailbox_nodes_equal(snodes1[i], snodes2[i])) return FALSE; if (!dsync_mailbox_branches_equal(snodes1[i]->first_child, snodes2[i]->first_child)) return FALSE; } return TRUE; } bool dsync_mailbox_trees_equal(struct dsync_mailbox_tree *tree1, struct dsync_mailbox_tree *tree2) { bool ret; T_BEGIN { ret = dsync_mailbox_branches_equal(&tree1->root, &tree2->root); } T_END; return ret; } const char *dsync_mailbox_node_to_string(const struct dsync_mailbox_node *node) { return t_strdup_printf("guid=%s uid_validity=%u uid_next=%u subs=%s last_change=%ld last_subs=%ld", guid_128_to_string(node->mailbox_guid), node->uid_validity, node->uid_next, node->subscribed ? "yes" : "no", (long)node->last_renamed_or_created, (long)node->last_subscription_change); } const char * dsync_mailbox_delete_type_to_string(enum dsync_mailbox_delete_type type) { switch (type) { case DSYNC_MAILBOX_DELETE_TYPE_MAILBOX: return "mailbox"; case DSYNC_MAILBOX_DELETE_TYPE_DIR: return "dir"; case DSYNC_MAILBOX_DELETE_TYPE_UNSUBSCRIBE: return "unsubscribe"; } i_unreached(); } const char *const * dsync_mailbox_name_to_parts(const char *name, char hierarchy_sep, char escape_char) { const char sep[] = { hierarchy_sep, '\0' }; char **parts = p_strsplit(unsafe_data_stack_pool, name, sep); if (escape_char != '\0') { for (unsigned int i = 0; parts[i] != NULL; i++) { mailbox_list_name_unescape((const char **)&parts[i], escape_char); } } return (const char *const *)parts; } dovecot-2.3.21.1/src/doveadm/dsync/dsync-brain-mails.c0000644000000000000000000003123314656633576017325 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "istream.h" #include "dsync-ibc.h" #include "dsync-mail.h" #include "dsync-mailbox-import.h" #include "dsync-mailbox-export.h" #include "dsync-brain-private.h" const char *dsync_box_state_names[DSYNC_BOX_STATE_DONE+1] = { "mailbox", "changes", "attributes", "mail_requests", "mails", "recv_last_common", "done" }; static bool dsync_brain_master_sync_recv_mailbox(struct dsync_brain *brain) { const struct dsync_mailbox *dsync_box; const char *resync_reason, *reason; enum dsync_ibc_recv_ret ret; bool resync; i_assert(brain->master_brain); if ((ret = dsync_ibc_recv_mailbox(brain->ibc, &dsync_box)) == 0) return FALSE; if (ret == DSYNC_IBC_RECV_RET_FINISHED) { i_error("Remote sent end-of-list instead of a mailbox"); brain->failed = TRUE; return TRUE; } if (memcmp(dsync_box->mailbox_guid, brain->local_dsync_box.mailbox_guid, sizeof(dsync_box->mailbox_guid)) != 0) { i_error("Remote sent mailbox with a wrong GUID"); brain->failed = TRUE; return TRUE; } if (dsync_box->mailbox_ignore) { /* ignore this box */ if (brain->debug) i_debug("brain %c: Ignoring missing remote box GUID %s", brain->master_brain ? 'M' : 'S', guid_128_to_string(dsync_box->mailbox_guid)); dsync_brain_sync_mailbox_deinit(brain); return TRUE; } if (dsync_box->mailbox_lost) { /* remote lost the mailbox. it's probably already deleted, but verify it on next sync just to be sure */ dsync_brain_set_changes_during_sync(brain, t_strdup_printf( "Remote lost mailbox GUID %s (maybe it was just deleted?)", guid_128_to_string(dsync_box->mailbox_guid))); brain->require_full_resync = TRUE; dsync_brain_sync_mailbox_deinit(brain); return TRUE; } resync = !dsync_brain_mailbox_update_pre(brain, brain->box, &brain->local_dsync_box, dsync_box, &resync_reason); if (!dsync_boxes_need_sync(brain, &brain->local_dsync_box, dsync_box, &reason)) { /* no fields appear to have changed, skip this mailbox */ dsync_brain_sync_mailbox_deinit(brain); return TRUE; } if (brain->debug) { i_debug("brain %c: Syncing mailbox %s: %s", brain->master_brain ? 'M' : 'S', guid_128_to_string(dsync_box->mailbox_guid), reason); } if ((ret = dsync_brain_sync_mailbox_open(brain, dsync_box)) < 0) return TRUE; if (resync) dsync_brain_set_changes_during_sync(brain, resync_reason); if (ret == 0 || resync) { brain->require_full_resync = TRUE; brain->failed = TRUE; dsync_brain_sync_mailbox_deinit(brain); return TRUE; } dsync_brain_sync_init_box_states(brain); return TRUE; } static bool dsync_brain_recv_mailbox_attribute(struct dsync_brain *brain) { const struct dsync_mailbox_attribute *attr; struct istream *input; enum dsync_ibc_recv_ret ret; if ((ret = dsync_ibc_recv_mailbox_attribute(brain->ibc, &attr)) == 0) return FALSE; if (ret == DSYNC_IBC_RECV_RET_FINISHED) { brain->box_recv_state = DSYNC_BOX_STATE_CHANGES; return TRUE; } if (dsync_mailbox_import_attribute(brain->box_importer, attr) < 0) brain->failed = TRUE; input = attr->value_stream; i_stream_unref(&input); return TRUE; } static void dsync_brain_send_end_of_list(struct dsync_brain *brain, enum dsync_ibc_eol_type type) { i_assert(!brain->failed); dsync_ibc_send_end_of_list(brain->ibc, type); } static int dsync_brain_export_deinit(struct dsync_brain *brain) { const char *errstr; enum mail_error error; if (dsync_mailbox_export_deinit(&brain->box_exporter, &errstr, &error) < 0) { i_error("Exporting mailbox %s failed: %s", mailbox_get_vname(brain->box), errstr); brain->mail_error = error; brain->failed = TRUE; return -1; } return 0; } static void dsync_brain_send_mailbox_attribute(struct dsync_brain *brain) { const struct dsync_mailbox_attribute *attr; int ret; while ((ret = dsync_mailbox_export_next_attr(brain->box_exporter, &attr)) > 0) { if (dsync_ibc_send_mailbox_attribute(brain->ibc, attr) == 0) return; } if (ret < 0) { if (dsync_brain_export_deinit(brain) == 0) i_unreached(); return; } dsync_brain_send_end_of_list(brain, DSYNC_IBC_EOL_MAILBOX_ATTRIBUTE); brain->box_send_state = DSYNC_BOX_STATE_CHANGES; } static bool dsync_brain_recv_mail_change(struct dsync_brain *brain) { const struct dsync_mail_change *change; enum dsync_ibc_recv_ret ret; if ((ret = dsync_ibc_recv_change(brain->ibc, &change)) == 0) return FALSE; if (ret == DSYNC_IBC_RECV_RET_FINISHED) { if (dsync_mailbox_import_changes_finish(brain->box_importer) < 0) brain->failed = TRUE; if (brain->mail_requests && brain->box_exporter != NULL) brain->box_recv_state = DSYNC_BOX_STATE_MAIL_REQUESTS; else brain->box_recv_state = DSYNC_BOX_STATE_MAILS; return TRUE; } if (dsync_mailbox_import_change(brain->box_importer, change) < 0) brain->failed = TRUE; return TRUE; } static void dsync_brain_send_mail_change(struct dsync_brain *brain) { const struct dsync_mail_change *change; int ret; while ((ret = dsync_mailbox_export_next(brain->box_exporter, &change)) > 0) { if (dsync_ibc_send_change(brain->ibc, change) == 0) return; } if (ret < 0) { if (dsync_brain_export_deinit(brain) == 0) i_unreached(); return; } dsync_brain_send_end_of_list(brain, DSYNC_IBC_EOL_MAIL_CHANGES); if (brain->mail_requests && brain->box_importer != NULL) brain->box_send_state = DSYNC_BOX_STATE_MAIL_REQUESTS; else brain->box_send_state = DSYNC_BOX_STATE_MAILS; } static bool dsync_brain_recv_mail_request(struct dsync_brain *brain) { const struct dsync_mail_request *request; enum dsync_ibc_recv_ret ret; i_assert(brain->mail_requests); i_assert(brain->box_exporter != NULL); if ((ret = dsync_ibc_recv_mail_request(brain->ibc, &request)) == 0) return FALSE; if (ret == DSYNC_IBC_RECV_RET_FINISHED) { brain->box_recv_state = brain->box_importer != NULL ? DSYNC_BOX_STATE_MAILS : DSYNC_BOX_STATE_RECV_LAST_COMMON; return TRUE; } dsync_mailbox_export_want_mail(brain->box_exporter, request); return TRUE; } static bool dsync_brain_send_mail_request(struct dsync_brain *brain) { const struct dsync_mail_request *request; i_assert(brain->mail_requests); while ((request = dsync_mailbox_import_next_request(brain->box_importer)) != NULL) { if (dsync_ibc_send_mail_request(brain->ibc, request) == 0) return TRUE; } if (brain->box_recv_state < DSYNC_BOX_STATE_MAIL_REQUESTS) return FALSE; dsync_brain_send_end_of_list(brain, DSYNC_IBC_EOL_MAIL_REQUESTS); if (brain->box_exporter != NULL) brain->box_send_state = DSYNC_BOX_STATE_MAILS; else { i_assert(brain->box_recv_state != DSYNC_BOX_STATE_DONE); brain->box_send_state = DSYNC_BOX_STATE_DONE; } return TRUE; } static void dsync_brain_sync_half_finished(struct dsync_brain *brain) { struct dsync_mailbox_state state; const char *changes_during_sync; bool require_full_resync; if (brain->box_recv_state < DSYNC_BOX_STATE_RECV_LAST_COMMON || brain->box_send_state < DSYNC_BOX_STATE_RECV_LAST_COMMON) return; /* finished with this mailbox */ i_zero(&state); memcpy(state.mailbox_guid, brain->local_dsync_box.mailbox_guid, sizeof(state.mailbox_guid)); state.last_uidvalidity = brain->local_dsync_box.uid_validity; if (brain->box_importer == NULL) { /* this mailbox didn't exist on remote */ state.last_common_uid = brain->local_dsync_box.uid_next-1; state.last_common_modseq = brain->local_dsync_box.highest_modseq; state.last_common_pvt_modseq = brain->local_dsync_box.highest_pvt_modseq; state.last_messages_count = brain->local_dsync_box.messages_count; } else { if (dsync_mailbox_import_deinit(&brain->box_importer, !brain->failed, &state.last_common_uid, &state.last_common_modseq, &state.last_common_pvt_modseq, &state.last_messages_count, &changes_during_sync, &require_full_resync, &brain->mail_error) < 0) { if (require_full_resync) { /* don't treat this as brain failure or the state won't be sent to the other brain. this also means we'll continue syncing the following mailboxes. */ brain->require_full_resync = TRUE; } else { brain->failed = TRUE; } } if (changes_during_sync != NULL) { state.changes_during_sync = TRUE; dsync_brain_set_changes_during_sync(brain, changes_during_sync); } } if (brain->require_full_resync) { state.last_uidvalidity = 0; state.changes_during_sync = TRUE; } brain->mailbox_state = state; dsync_ibc_send_mailbox_state(brain->ibc, &state); } static bool dsync_brain_recv_mail(struct dsync_brain *brain) { struct dsync_mail *mail; enum dsync_ibc_recv_ret ret; if ((ret = dsync_ibc_recv_mail(brain->ibc, &mail)) == 0) return FALSE; if (ret == DSYNC_IBC_RECV_RET_FINISHED) { brain->box_recv_state = DSYNC_BOX_STATE_RECV_LAST_COMMON; if (brain->box_exporter != NULL && brain->box_send_state >= DSYNC_BOX_STATE_RECV_LAST_COMMON) { if (dsync_brain_export_deinit(brain) < 0) return TRUE; } dsync_brain_sync_half_finished(brain); return TRUE; } if (brain->debug) { i_debug("brain %c: import mail uid %u guid %s", brain->master_brain ? 'M' : 'S', mail->uid, mail->guid); } if (dsync_mailbox_import_mail(brain->box_importer, mail) < 0) brain->failed = TRUE; i_stream_unref(&mail->input); return TRUE; } static bool dsync_brain_send_mail(struct dsync_brain *brain) { const struct dsync_mail *mail; if (brain->mail_requests && brain->box_recv_state < DSYNC_BOX_STATE_MAILS) { /* wait for mail requests to finish. we could already start exporting, but then we're going to do quite a lot of separate searches. especially with pipe backend we'd do a separate search for each mail. */ return FALSE; } while (dsync_mailbox_export_next_mail(brain->box_exporter, &mail) > 0) { if (dsync_ibc_send_mail(brain->ibc, mail) == 0) return TRUE; } if (dsync_brain_export_deinit(brain) < 0) return TRUE; brain->box_send_state = DSYNC_BOX_STATE_DONE; dsync_brain_send_end_of_list(brain, DSYNC_IBC_EOL_MAILS); dsync_brain_sync_half_finished(brain); return TRUE; } static bool dsync_brain_recv_last_common(struct dsync_brain *brain) { enum dsync_ibc_recv_ret ret; struct dsync_mailbox_state state; if ((ret = dsync_ibc_recv_mailbox_state(brain->ibc, &state)) == 0) return FALSE; if (ret == DSYNC_IBC_RECV_RET_FINISHED) { i_error("Remote sent end-of-list instead of a mailbox state"); brain->failed = TRUE; return TRUE; } i_assert(brain->box_send_state == DSYNC_BOX_STATE_DONE); i_assert(memcmp(state.mailbox_guid, brain->local_dsync_box.mailbox_guid, sizeof(state.mailbox_guid)) == 0); /* normally the last_common_* values should be the same in local and remote, but during unexpected changes they may differ. use the values that are lower as the final state. */ if (brain->mailbox_state.last_common_uid > state.last_common_uid) brain->mailbox_state.last_common_uid = state.last_common_uid; if (brain->mailbox_state.last_common_modseq > state.last_common_modseq) brain->mailbox_state.last_common_modseq = state.last_common_modseq; if (brain->mailbox_state.last_common_pvt_modseq > state.last_common_pvt_modseq) brain->mailbox_state.last_common_pvt_modseq = state.last_common_pvt_modseq; if (state.changes_during_sync) brain->changes_during_remote_sync = TRUE; dsync_brain_sync_mailbox_deinit(brain); return TRUE; } bool dsync_brain_sync_mails(struct dsync_brain *brain) { bool changed = FALSE; i_assert(brain->box != NULL); switch (brain->box_recv_state) { case DSYNC_BOX_STATE_MAILBOX: changed = dsync_brain_master_sync_recv_mailbox(brain); break; case DSYNC_BOX_STATE_ATTRIBUTES: changed = dsync_brain_recv_mailbox_attribute(brain); break; case DSYNC_BOX_STATE_CHANGES: changed = dsync_brain_recv_mail_change(brain); break; case DSYNC_BOX_STATE_MAIL_REQUESTS: changed = dsync_brain_recv_mail_request(brain); break; case DSYNC_BOX_STATE_MAILS: changed = dsync_brain_recv_mail(brain); break; case DSYNC_BOX_STATE_RECV_LAST_COMMON: changed = dsync_brain_recv_last_common(brain); break; case DSYNC_BOX_STATE_DONE: break; } if (!dsync_ibc_is_send_queue_full(brain->ibc) && !brain->failed) { switch (brain->box_send_state) { case DSYNC_BOX_STATE_MAILBOX: /* wait for mailbox to be received first */ break; case DSYNC_BOX_STATE_ATTRIBUTES: dsync_brain_send_mailbox_attribute(brain); changed = TRUE; break; case DSYNC_BOX_STATE_CHANGES: dsync_brain_send_mail_change(brain); changed = TRUE; break; case DSYNC_BOX_STATE_MAIL_REQUESTS: if (dsync_brain_send_mail_request(brain)) changed = TRUE; break; case DSYNC_BOX_STATE_MAILS: if (dsync_brain_send_mail(brain)) changed = TRUE; break; case DSYNC_BOX_STATE_RECV_LAST_COMMON: i_unreached(); case DSYNC_BOX_STATE_DONE: break; } } return changed; } dovecot-2.3.21.1/src/doveadm/dsync/dsync-ibc-private.h0000644000000000000000000000573114656633576017345 00000000000000#ifndef DSYNC_IBC_PRIVATE_H #define DSYNC_IBC_PRIVATE_H #include "dsync-ibc.h" struct dsync_ibc_vfuncs { void (*deinit)(struct dsync_ibc *ibc); void (*send_handshake)(struct dsync_ibc *ibc, const struct dsync_ibc_settings *set); enum dsync_ibc_recv_ret (*recv_handshake)(struct dsync_ibc *ibc, const struct dsync_ibc_settings **set_r); void (*send_end_of_list)(struct dsync_ibc *ibc, enum dsync_ibc_eol_type type); void (*send_mailbox_state)(struct dsync_ibc *ibc, const struct dsync_mailbox_state *state); enum dsync_ibc_recv_ret (*recv_mailbox_state)(struct dsync_ibc *ibc, struct dsync_mailbox_state *state_r); void (*send_mailbox_tree_node)(struct dsync_ibc *ibc, const char *const *name, const struct dsync_mailbox_node *node); enum dsync_ibc_recv_ret (*recv_mailbox_tree_node)(struct dsync_ibc *ibc, const char *const **name_r, const struct dsync_mailbox_node **node_r); void (*send_mailbox_deletes)(struct dsync_ibc *ibc, const struct dsync_mailbox_delete *deletes, unsigned int count, char hierarchy_sep, char escape_char); enum dsync_ibc_recv_ret (*recv_mailbox_deletes)(struct dsync_ibc *ibc, const struct dsync_mailbox_delete **deletes_r, unsigned int *count_r, char *hierarchy_sep_r, char *escape_char_r); void (*send_mailbox)(struct dsync_ibc *ibc, const struct dsync_mailbox *dsync_box); enum dsync_ibc_recv_ret (*recv_mailbox)(struct dsync_ibc *ibc, const struct dsync_mailbox **dsync_box_r); void (*send_mailbox_attribute)(struct dsync_ibc *ibc, const struct dsync_mailbox_attribute *attr); enum dsync_ibc_recv_ret (*recv_mailbox_attribute)(struct dsync_ibc *ibc, const struct dsync_mailbox_attribute **attr_r); void (*send_change)(struct dsync_ibc *ibc, const struct dsync_mail_change *change); enum dsync_ibc_recv_ret (*recv_change)(struct dsync_ibc *ibc, const struct dsync_mail_change **change_r); void (*send_mail_request)(struct dsync_ibc *ibc, const struct dsync_mail_request *request); enum dsync_ibc_recv_ret (*recv_mail_request)(struct dsync_ibc *ibc, const struct dsync_mail_request **request_r); void (*send_mail)(struct dsync_ibc *ibc, const struct dsync_mail *mail); enum dsync_ibc_recv_ret (*recv_mail)(struct dsync_ibc *ibc, struct dsync_mail **mail_r); void (*send_finish)(struct dsync_ibc *ibc, const char *error, enum mail_error mail_error, bool require_full_resync); enum dsync_ibc_recv_ret (*recv_finish)(struct dsync_ibc *ibc, const char **error_r, enum mail_error *mail_error_r, bool *require_full_resync_r); void (*close_mail_streams)(struct dsync_ibc *ibc); bool (*is_send_queue_full)(struct dsync_ibc *ibc); bool (*has_pending_data)(struct dsync_ibc *ibc); }; struct dsync_ibc { struct dsync_ibc_vfuncs v; io_callback_t *io_callback; void *io_context; bool failed:1; bool timeout:1; }; #endif dovecot-2.3.21.1/src/doveadm/dsync/dsync-ibc-stream.c0000644000000000000000000020511114656633576017153 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "safe-mkstemp.h" #include "ioloop.h" #include "istream.h" #include "istream-seekable.h" #include "istream-dot.h" #include "ostream.h" #include "str.h" #include "strescape.h" #include "master-service.h" #include "mail-cache.h" #include "mail-storage-private.h" #include "dsync-serializer.h" #include "dsync-deserializer.h" #include "dsync-mail.h" #include "dsync-mailbox.h" #include "dsync-mailbox-state.h" #include "dsync-mailbox-tree.h" #include "dsync-ibc-private.h" #define DSYNC_IBC_STREAM_OUTBUF_THROTTLE_SIZE (1024*128) #define DSYNC_PROTOCOL_VERSION_MAJOR 3 #define DSYNC_PROTOCOL_VERSION_MINOR 5 #define DSYNC_HANDSHAKE_VERSION "VERSION\tdsync\t3\t5\n" #define DSYNC_PROTOCOL_MINOR_HAVE_ATTRIBUTES 1 #define DSYNC_PROTOCOL_MINOR_HAVE_SAVE_GUID 2 #define DSYNC_PROTOCOL_MINOR_HAVE_FINISH 3 #define DSYNC_PROTOCOL_MINOR_HAVE_HDR_HASH_V2 4 #define DSYNC_PROTOCOL_MINOR_HAVE_HDR_HASH_V3 5 enum item_type { ITEM_NONE, ITEM_DONE, ITEM_HANDSHAKE, ITEM_MAILBOX_STATE, ITEM_MAILBOX_TREE_NODE, ITEM_MAILBOX_DELETE, ITEM_MAILBOX, ITEM_MAILBOX_ATTRIBUTE, ITEM_MAIL_CHANGE, ITEM_MAIL_REQUEST, ITEM_MAIL, ITEM_FINISH, ITEM_MAILBOX_CACHE_FIELD, ITEM_END_OF_LIST }; #define END_OF_LIST_LINE "." static const struct { /* full human readable name of the item */ const char *name; /* unique character identifying the item */ char chr; const char *required_keys; const char *optional_keys; unsigned int min_minor_version; } items[ITEM_END_OF_LIST+1] = { { NULL, '\0', NULL, NULL, 0 }, { .name = "done", .chr = 'X', .optional_keys = "" }, { .name = "handshake", .chr = 'H', .required_keys = "hostname", .optional_keys = "sync_ns_prefix sync_box sync_box_guid sync_type " "debug sync_visible_namespaces exclude_mailboxes " "send_mail_requests backup_send backup_recv lock_timeout " "no_mail_sync no_backup_overwrite purge_remote " "no_notify sync_since_timestamp sync_max_size sync_flags sync_until_timestamp " "virtual_all_box empty_hdr_workaround import_commit_msgs_interval " "hashed_headers alt_char" }, { .name = "mailbox_state", .chr = 'S', .required_keys = "mailbox_guid last_uidvalidity last_common_uid " "last_common_modseq last_common_pvt_modseq", .optional_keys = "last_messages_count changes_during_sync" }, { .name = "mailbox_tree_node", .chr = 'N', .required_keys = "name existence", .optional_keys = "mailbox_guid uid_validity uid_next " "last_renamed_or_created subscribed last_subscription_change" }, { .name = "mailbox_delete", .chr = 'D', .required_keys = "hierarchy_sep", .optional_keys = "escape_char mailboxes dirs unsubscribes" }, { .name = "mailbox", .chr = 'B', .required_keys = "mailbox_guid uid_validity uid_next messages_count " "first_recent_uid highest_modseq highest_pvt_modseq", .optional_keys = "mailbox_lost mailbox_ignore " "cache_fields have_guids have_save_guids have_only_guid128" }, { .name = "mailbox_attribute", .chr = 'A', .required_keys = "type key", .optional_keys = "value stream deleted last_change modseq", .min_minor_version = DSYNC_PROTOCOL_MINOR_HAVE_ATTRIBUTES }, { .name = "mail_change", .chr = 'C', .required_keys = "type uid", .optional_keys = "guid hdr_hash modseq pvt_modseq " "add_flags remove_flags final_flags " "keywords_reset keyword_changes received_timestamp virtual_size" }, { .name = "mail_request", .chr = 'R', .optional_keys = "guid uid" }, { .name = "mail", .chr = 'M', .optional_keys = "guid uid pop3_uidl pop3_order received_date saved_date stream" }, { .name = "finish", .chr = 'F', .optional_keys = "error mail_error require_full_resync", .min_minor_version = DSYNC_PROTOCOL_MINOR_HAVE_FINISH }, { .name = "mailbox_cache_field", .chr = 'c', .required_keys = "name decision", .optional_keys = "last_used" }, { "end_of_list", '\0', NULL, NULL, 0 } }; struct dsync_ibc_stream { struct dsync_ibc ibc; char *name, *temp_path_prefix; unsigned int timeout_secs; struct istream *input; struct ostream *output; struct io *io; struct timeout *to; unsigned int minor_version; struct dsync_serializer *serializers[ITEM_END_OF_LIST]; struct dsync_deserializer *deserializers[ITEM_END_OF_LIST]; pool_t ret_pool; struct dsync_deserializer_decoder *cur_decoder; struct istream *value_output, *value_input; struct dsync_mail *cur_mail; struct dsync_mailbox_attribute *cur_attr; char value_output_last; enum item_type last_recv_item, last_sent_item; bool last_recv_item_eol:1; bool last_sent_item_eol:1; bool version_received:1; bool handshake_received:1; bool has_pending_data:1; bool finish_received:1; bool done_received:1; bool stopped:1; }; static const char *dsync_ibc_stream_get_state(struct dsync_ibc_stream *ibc) { if (!ibc->version_received) return "version not received"; else if (!ibc->handshake_received) return "handshake not received"; return t_strdup_printf("last sent=%s%s, last recv=%s%s", items[ibc->last_sent_item].name, ibc->last_sent_item_eol ? " (EOL)" : "", items[ibc->last_recv_item].name, ibc->last_recv_item_eol ? " (EOL)" : ""); } static void dsync_ibc_stream_stop(struct dsync_ibc_stream *ibc) { ibc->stopped = TRUE; i_stream_close(ibc->input); o_stream_close(ibc->output); io_loop_stop(current_ioloop); } static int dsync_ibc_stream_read_mail_stream(struct dsync_ibc_stream *ibc) { do { i_stream_skip(ibc->value_input, i_stream_get_data_size(ibc->value_input)); } while (i_stream_read(ibc->value_input) > 0); if (ibc->value_input->eof) { if (ibc->value_input->stream_errno != 0) { i_error("dsync(%s): read(%s) failed: %s (%s)", ibc->name, i_stream_get_name(ibc->value_input), i_stream_get_error(ibc->value_input), dsync_ibc_stream_get_state(ibc)); dsync_ibc_stream_stop(ibc); return -1; } /* finished reading the mail stream */ i_assert(ibc->value_input->eof); i_stream_seek(ibc->value_input, 0); ibc->has_pending_data = TRUE; ibc->value_input = NULL; return 1; } return 0; } static void dsync_ibc_stream_input(struct dsync_ibc_stream *ibc) { timeout_reset(ibc->to); if (ibc->value_input != NULL) { if (dsync_ibc_stream_read_mail_stream(ibc) == 0) return; } o_stream_cork(ibc->output); ibc->ibc.io_callback(ibc->ibc.io_context); o_stream_uncork(ibc->output); } static int dsync_ibc_stream_send_value_stream(struct dsync_ibc_stream *ibc) { const unsigned char *data; unsigned char add; size_t i, size; int ret; while ((ret = i_stream_read_more(ibc->value_output, &data, &size)) > 0) { add = '\0'; for (i = 0; i < size; i++) { if (data[i] == '.' && ((i == 0 && ibc->value_output_last == '\n') || (i > 0 && data[i-1] == '\n'))) { /* escape the dot */ add = '.'; break; } } if (i > 0) { o_stream_nsend(ibc->output, data, i); ibc->value_output_last = data[i-1]; i_stream_skip(ibc->value_output, i); } if (o_stream_get_buffer_used_size(ibc->output) >= 4096) { if ((ret = o_stream_flush(ibc->output)) < 0) { dsync_ibc_stream_stop(ibc); return -1; } if (ret == 0) { /* continue later */ o_stream_set_flush_pending(ibc->output, TRUE); return 0; } } if (add != '\0') { o_stream_nsend(ibc->output, &add, 1); ibc->value_output_last = add; } } i_assert(ret == -1); if (ibc->value_output->stream_errno != 0) { i_error("dsync(%s): read(%s) failed: %s (%s)", ibc->name, i_stream_get_name(ibc->value_output), i_stream_get_error(ibc->value_output), dsync_ibc_stream_get_state(ibc)); dsync_ibc_stream_stop(ibc); return -1; } /* finished sending the stream. use "CRLF." instead of "LF." just in case we're sending binary data that ends with CR. */ o_stream_nsend_str(ibc->output, "\r\n.\r\n"); i_stream_unref(&ibc->value_output); return 1; } static int dsync_ibc_stream_output(struct dsync_ibc_stream *ibc) { struct ostream *output = ibc->output; int ret; if ((ret = o_stream_flush(output)) < 0) ret = 1; else if (ibc->value_output != NULL) { if (dsync_ibc_stream_send_value_stream(ibc) < 0) ret = 1; } timeout_reset(ibc->to); if (!dsync_ibc_is_send_queue_full(&ibc->ibc)) ibc->ibc.io_callback(ibc->ibc.io_context); return ret; } static void dsync_ibc_stream_timeout(struct dsync_ibc_stream *ibc) { i_error("dsync(%s): I/O has stalled, no activity for %u seconds (%s)", ibc->name, ibc->timeout_secs, dsync_ibc_stream_get_state(ibc)); ibc->ibc.timeout = TRUE; dsync_ibc_stream_stop(ibc); } static void dsync_ibc_stream_init(struct dsync_ibc_stream *ibc) { unsigned int i; ibc->io = io_add_istream(ibc->input, dsync_ibc_stream_input, ibc); io_set_pending(ibc->io); o_stream_set_no_error_handling(ibc->output, TRUE); o_stream_set_flush_callback(ibc->output, dsync_ibc_stream_output, ibc); ibc->to = timeout_add(ibc->timeout_secs * 1000, dsync_ibc_stream_timeout, ibc); o_stream_cork(ibc->output); o_stream_nsend_str(ibc->output, DSYNC_HANDSHAKE_VERSION); /* initialize serializers and send their headers to remote */ for (i = ITEM_DONE + 1; i < ITEM_END_OF_LIST; i++) T_BEGIN { const char *keys; keys = items[i].required_keys == NULL ? items[i].optional_keys : t_strconcat(items[i].required_keys, " ", items[i].optional_keys, NULL); if (keys != NULL) { i_assert(items[i].chr != '\0'); ibc->serializers[i] = dsync_serializer_init(t_strsplit_spaces(keys, " ")); o_stream_nsend(ibc->output, &items[i].chr, 1); o_stream_nsend_str(ibc->output, dsync_serializer_encode_header_line(ibc->serializers[i])); } } T_END; o_stream_nsend_str(ibc->output, ".\n"); o_stream_uncork(ibc->output); } static void dsync_ibc_stream_deinit(struct dsync_ibc *_ibc) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; unsigned int i; for (i = ITEM_DONE + 1; i < ITEM_END_OF_LIST; i++) { if (ibc->serializers[i] != NULL) dsync_serializer_deinit(&ibc->serializers[i]); if (ibc->deserializers[i] != NULL) dsync_deserializer_deinit(&ibc->deserializers[i]); } if (ibc->cur_decoder != NULL) dsync_deserializer_decode_finish(&ibc->cur_decoder); if (ibc->value_output != NULL) i_stream_unref(&ibc->value_output); else { /* If the remote has not told us that they are closing we notify remote that we're closing. this is mainly to avoid "read() failed: EOF" errors on failing dsyncs. Avoid a race condition: We do not tell the remote we are closing if they have already told us because they close the connection after sending ITEM_DONE and will not be ever receive anything else from us unless it just happens to get combined into the same packet as a previous response and is already in the buffer. */ if (!ibc->done_received && !ibc->finish_received) { o_stream_nsend_str(ibc->output, t_strdup_printf("%c\n", items[ITEM_DONE].chr)); } (void)o_stream_finish(ibc->output); } timeout_remove(&ibc->to); io_remove(&ibc->io); i_stream_destroy(&ibc->input); o_stream_destroy(&ibc->output); pool_unref(&ibc->ret_pool); i_free(ibc->temp_path_prefix); i_free(ibc->name); i_free(ibc); } static int dsync_ibc_stream_next_line(struct dsync_ibc_stream *ibc, const char **line_r) { string_t *error; const char *line; ssize_t ret; line = i_stream_next_line(ibc->input); if (line != NULL) { *line_r = line; return 1; } /* try reading some */ if ((ret = i_stream_read(ibc->input)) == -1) { if (ibc->stopped) return -1; error = t_str_new(128); if (ibc->input->stream_errno != 0) { str_printfa(error, "read(%s) failed: %s", ibc->name, i_stream_get_error(ibc->input)); } else { i_assert(ibc->input->eof); str_printfa(error, "read(%s) failed: EOF", ibc->name); } str_printfa(error, " (%s)", dsync_ibc_stream_get_state(ibc)); i_error("%s", str_c(error)); dsync_ibc_stream_stop(ibc); return -1; } i_assert(ret >= 0); *line_r = i_stream_next_line(ibc->input); if (*line_r == NULL) { ibc->has_pending_data = FALSE; return 0; } ibc->has_pending_data = TRUE; return 1; } static void ATTR_FORMAT(3, 4) ATTR_NULL(2) dsync_ibc_input_error(struct dsync_ibc_stream *ibc, struct dsync_deserializer_decoder *decoder, const char *fmt, ...) { va_list args; const char *error; va_start(args, fmt); error = t_strdup_vprintf(fmt, args); if (decoder == NULL) i_error("dsync(%s): %s", ibc->name, error); else { i_error("dsync(%s): %s: %s", ibc->name, dsync_deserializer_decoder_get_name(decoder), error); } va_end(args); dsync_ibc_stream_stop(ibc); } static void dsync_ibc_stream_send_string(struct dsync_ibc_stream *ibc, const string_t *str) { i_assert(ibc->value_output == NULL); o_stream_nsend(ibc->output, str_data(str), str_len(str)); } static int seekable_fd_callback(const char **path_r, void *context) { struct dsync_ibc_stream *ibc = context; string_t *path; int fd; path = t_str_new(128); str_append(path, ibc->temp_path_prefix); fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1); if (fd == -1) { i_error("safe_mkstemp(%s) failed: %m", str_c(path)); return -1; } /* we just want the fd, unlink it */ if (i_unlink(str_c(path)) < 0) { /* shouldn't happen.. */ i_close_fd(&fd); return -1; } *path_r = str_c(path); return fd; } static struct istream * dsync_ibc_stream_input_stream(struct dsync_ibc_stream *ibc) { struct istream *inputs[2]; inputs[0] = i_stream_create_dot(ibc->input, FALSE); inputs[1] = NULL; ibc->value_input = i_stream_create_seekable(inputs, MAIL_READ_FULL_BLOCK_SIZE, seekable_fd_callback, ibc); i_stream_unref(&inputs[0]); return ibc->value_input; } static int dsync_ibc_check_missing_deserializers(struct dsync_ibc_stream *ibc) { unsigned int i; int ret = 0; for (i = ITEM_DONE + 1; i < ITEM_END_OF_LIST; i++) { if (ibc->deserializers[i] == NULL && ibc->minor_version >= items[i].min_minor_version && (items[i].required_keys != NULL || items[i].optional_keys != NULL)) { dsync_ibc_input_error(ibc, NULL, "Remote didn't handshake deserializer for %s", items[i].name); ret = -1; } } return ret; } static bool dsync_ibc_stream_handshake(struct dsync_ibc_stream *ibc, const char *line) { enum item_type item = ITEM_NONE; const char *const *required_keys, *error; unsigned int i; if (ibc->handshake_received) return TRUE; if (!ibc->version_received) { if (!version_string_verify_full(line, "dsync", DSYNC_PROTOCOL_VERSION_MAJOR, &ibc->minor_version)) { dsync_ibc_input_error(ibc, NULL, "Remote dsync doesn't use compatible protocol"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } ibc->version_received = TRUE; return FALSE; } if (strcmp(line, END_OF_LIST_LINE) == 0) { /* finished handshaking */ if (dsync_ibc_check_missing_deserializers(ibc) < 0) return FALSE; ibc->handshake_received = TRUE; ibc->last_recv_item = ITEM_HANDSHAKE; return FALSE; } for (i = 1; i < ITEM_END_OF_LIST; i++) { if (items[i].chr == line[0]) { item = i; break; } } if (item == ITEM_NONE) { /* unknown deserializer, ignore */ return FALSE; } required_keys = items[item].required_keys == NULL ? NULL : t_strsplit(items[item].required_keys, " "); if (dsync_deserializer_init(items[item].name, required_keys, line + 1, &ibc->deserializers[item], &error) < 0) { dsync_ibc_input_error(ibc, NULL, "Remote sent invalid handshake for %s: %s", items[item].name, error); } return FALSE; } static enum dsync_ibc_recv_ret dsync_ibc_stream_input_next(struct dsync_ibc_stream *ibc, enum item_type item, struct dsync_deserializer_decoder **decoder_r) { enum item_type line_item = ITEM_NONE; const char *line, *error; unsigned int i; i_assert(ibc->value_input == NULL); timeout_reset(ibc->to); do { if (dsync_ibc_stream_next_line(ibc, &line) <= 0) return DSYNC_IBC_RECV_RET_TRYAGAIN; } while (!dsync_ibc_stream_handshake(ibc, line)); ibc->last_recv_item = item; ibc->last_recv_item_eol = FALSE; if (strcmp(line, END_OF_LIST_LINE) == 0) { /* end of this list */ ibc->last_recv_item_eol = TRUE; return DSYNC_IBC_RECV_RET_FINISHED; } if (line[0] == items[ITEM_DONE].chr) { /* remote cleanly closed the connection, possibly because of some failure (which it should have logged). we don't want to log any stream errors anyway after this. */ ibc->done_received = TRUE; dsync_ibc_stream_stop(ibc); return DSYNC_IBC_RECV_RET_TRYAGAIN; } for (i = 1; i < ITEM_END_OF_LIST; i++) { if (*line == items[i].chr) { line_item = i; break; } } if (line_item != item) { dsync_ibc_input_error(ibc, NULL, "Received unexpected input %c != %c", *line, items[item].chr); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (ibc->cur_decoder != NULL) dsync_deserializer_decode_finish(&ibc->cur_decoder); if (dsync_deserializer_decode_begin(ibc->deserializers[item], line+1, &ibc->cur_decoder, &error) < 0) { dsync_ibc_input_error(ibc, NULL, "Invalid input to %s: %s", items[item].name, error); return DSYNC_IBC_RECV_RET_TRYAGAIN; } *decoder_r = ibc->cur_decoder; return DSYNC_IBC_RECV_RET_OK; } static struct dsync_serializer_encoder * dsync_ibc_send_encode_begin(struct dsync_ibc_stream *ibc, enum item_type item) { ibc->last_sent_item = item; ibc->last_sent_item_eol = FALSE; return dsync_serializer_encode_begin(ibc->serializers[item]); } static void dsync_ibc_stream_send_handshake(struct dsync_ibc *_ibc, const struct dsync_ibc_settings *set) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; struct dsync_serializer_encoder *encoder; string_t *str = t_str_new(128); char sync_type[2]; str_append_c(str, items[ITEM_HANDSHAKE].chr); encoder = dsync_ibc_send_encode_begin(ibc, ITEM_HANDSHAKE); dsync_serializer_encode_add(encoder, "hostname", set->hostname); if (set->sync_ns_prefixes != NULL) { dsync_serializer_encode_add(encoder, "sync_ns_prefix", set->sync_ns_prefixes); } if (set->sync_box != NULL) dsync_serializer_encode_add(encoder, "sync_box", set->sync_box); if (set->virtual_all_box != NULL) { dsync_serializer_encode_add(encoder, "virtual_all_box", set->virtual_all_box); } if (set->exclude_mailboxes != NULL) { string_t *substr = t_str_new(64); unsigned int i; for (i = 0; set->exclude_mailboxes[i] != NULL; i++) { if (i != 0) str_append_c(substr, '\t'); str_append_tabescaped(substr, set->exclude_mailboxes[i]); } dsync_serializer_encode_add(encoder, "exclude_mailboxes", str_c(substr)); } if (!guid_128_is_empty(set->sync_box_guid)) { dsync_serializer_encode_add(encoder, "sync_box_guid", guid_128_to_string(set->sync_box_guid)); } sync_type[0] = sync_type[1] = '\0'; switch (set->sync_type) { case DSYNC_BRAIN_SYNC_TYPE_UNKNOWN: break; case DSYNC_BRAIN_SYNC_TYPE_FULL: sync_type[0] = 'f'; break; case DSYNC_BRAIN_SYNC_TYPE_CHANGED: sync_type[0] = 'c'; break; case DSYNC_BRAIN_SYNC_TYPE_STATE: sync_type[0] = 's'; break; } if (sync_type[0] != '\0') dsync_serializer_encode_add(encoder, "sync_type", sync_type); if (set->lock_timeout > 0) { dsync_serializer_encode_add(encoder, "lock_timeout", t_strdup_printf("%u", set->lock_timeout)); } if (set->import_commit_msgs_interval > 0) { dsync_serializer_encode_add(encoder, "import_commit_msgs_interval", t_strdup_printf("%u", set->import_commit_msgs_interval)); } if (set->sync_since_timestamp > 0) { dsync_serializer_encode_add(encoder, "sync_since_timestamp", t_strdup_printf("%ld", (long)set->sync_since_timestamp)); } if (set->sync_until_timestamp > 0) { dsync_serializer_encode_add(encoder, "sync_until_timestamp", t_strdup_printf("%ld", (long)set->sync_since_timestamp)); } if (set->sync_max_size > 0) { dsync_serializer_encode_add(encoder, "sync_max_size", t_strdup_printf("%"PRIu64, set->sync_max_size)); } if (set->sync_flags != NULL) { dsync_serializer_encode_add(encoder, "sync_flags", set->sync_flags); } if (set->alt_char != '\0') { dsync_serializer_encode_add(encoder, "alt_char", t_strdup_printf("%c", set->alt_char)); } if ((set->brain_flags & DSYNC_BRAIN_FLAG_SEND_MAIL_REQUESTS) != 0) dsync_serializer_encode_add(encoder, "send_mail_requests", ""); if ((set->brain_flags & DSYNC_BRAIN_FLAG_BACKUP_SEND) != 0) dsync_serializer_encode_add(encoder, "backup_send", ""); if ((set->brain_flags & DSYNC_BRAIN_FLAG_BACKUP_RECV) != 0) dsync_serializer_encode_add(encoder, "backup_recv", ""); if ((set->brain_flags & DSYNC_BRAIN_FLAG_DEBUG) != 0) dsync_serializer_encode_add(encoder, "debug", ""); if ((set->brain_flags & DSYNC_BRAIN_FLAG_SYNC_VISIBLE_NAMESPACES) != 0) dsync_serializer_encode_add(encoder, "sync_visible_namespaces", ""); if ((set->brain_flags & DSYNC_BRAIN_FLAG_NO_MAIL_SYNC) != 0) dsync_serializer_encode_add(encoder, "no_mail_sync", ""); if ((set->brain_flags & DSYNC_BRAIN_FLAG_NO_BACKUP_OVERWRITE) != 0) dsync_serializer_encode_add(encoder, "no_backup_overwrite", ""); if ((set->brain_flags & DSYNC_BRAIN_FLAG_PURGE_REMOTE) != 0) dsync_serializer_encode_add(encoder, "purge_remote", ""); if ((set->brain_flags & DSYNC_BRAIN_FLAG_NO_NOTIFY) != 0) dsync_serializer_encode_add(encoder, "no_notify", ""); if ((set->brain_flags & DSYNC_BRAIN_FLAG_EMPTY_HDR_WORKAROUND) != 0) dsync_serializer_encode_add(encoder, "empty_hdr_workaround", ""); /* this can be NULL in slave */ string_t *str2 = t_str_new(32); if (set->hashed_headers != NULL) { for(const char *const *ptr = set->hashed_headers; *ptr != NULL; ptr++) { str_append_tabescaped(str2, *ptr); str_append_c(str2, '\t'); } } dsync_serializer_encode_add(encoder, "hashed_headers", str_c(str2)); dsync_serializer_encode_finish(&encoder, str); dsync_ibc_stream_send_string(ibc, str); } static enum dsync_ibc_recv_ret dsync_ibc_stream_recv_handshake(struct dsync_ibc *_ibc, const struct dsync_ibc_settings **set_r) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; struct dsync_deserializer_decoder *decoder; struct dsync_ibc_settings *set; const char *value; pool_t pool = ibc->ret_pool; enum dsync_ibc_recv_ret ret; ret = dsync_ibc_stream_input_next(ibc, ITEM_HANDSHAKE, &decoder); if (ret != DSYNC_IBC_RECV_RET_OK) { if (ret != DSYNC_IBC_RECV_RET_TRYAGAIN) { i_error("dsync(%s): Unexpected input in handshake", ibc->name); dsync_ibc_stream_stop(ibc); } return DSYNC_IBC_RECV_RET_TRYAGAIN; } p_clear(pool); set = p_new(pool, struct dsync_ibc_settings, 1); value = dsync_deserializer_decode_get(decoder, "hostname"); set->hostname = p_strdup(pool, value); /* now that we know the remote's hostname, use it for the stream's name */ i_free(ibc->name); ibc->name = i_strdup(set->hostname); if (dsync_deserializer_decode_try(decoder, "sync_ns_prefix", &value)) set->sync_ns_prefixes = p_strdup(pool, value); if (dsync_deserializer_decode_try(decoder, "sync_box", &value)) set->sync_box = p_strdup(pool, value); if (dsync_deserializer_decode_try(decoder, "virtual_all_box", &value)) set->virtual_all_box = p_strdup(pool, value); if (dsync_deserializer_decode_try(decoder, "sync_box_guid", &value) && guid_128_from_string(value, set->sync_box_guid) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid sync_box_guid: %s", value); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "exclude_mailboxes", &value) && *value != '\0') { char **boxes = p_strsplit_tabescaped(pool, value); set->exclude_mailboxes = (const void *)boxes; } if (dsync_deserializer_decode_try(decoder, "sync_type", &value)) { switch (value[0]) { case 'f': set->sync_type = DSYNC_BRAIN_SYNC_TYPE_FULL; break; case 'c': set->sync_type = DSYNC_BRAIN_SYNC_TYPE_CHANGED; break; case 's': set->sync_type = DSYNC_BRAIN_SYNC_TYPE_STATE; break; default: dsync_ibc_input_error(ibc, decoder, "Unknown sync_type: %s", value); return DSYNC_IBC_RECV_RET_TRYAGAIN; } } if (dsync_deserializer_decode_try(decoder, "lock_timeout", &value)) { if (str_to_uint(value, &set->lock_timeout) < 0 || set->lock_timeout == 0) { dsync_ibc_input_error(ibc, decoder, "Invalid lock_timeout: %s", value); return DSYNC_IBC_RECV_RET_TRYAGAIN; } } if (dsync_deserializer_decode_try(decoder, "import_commit_msgs_interval", &value)) { if (str_to_uint(value, &set->import_commit_msgs_interval) < 0 || set->import_commit_msgs_interval == 0) { dsync_ibc_input_error(ibc, decoder, "Invalid import_commit_msgs_interval: %s", value); return DSYNC_IBC_RECV_RET_TRYAGAIN; } } if (dsync_deserializer_decode_try(decoder, "sync_since_timestamp", &value)) { if (str_to_time(value, &set->sync_since_timestamp) < 0 || set->sync_since_timestamp == 0) { dsync_ibc_input_error(ibc, decoder, "Invalid sync_since_timestamp: %s", value); return DSYNC_IBC_RECV_RET_TRYAGAIN; } } if (dsync_deserializer_decode_try(decoder, "sync_until_timestamp", &value)) { if (str_to_time(value, &set->sync_until_timestamp) < 0 || set->sync_until_timestamp == 0) { dsync_ibc_input_error(ibc, decoder, "Invalid sync_until_timestamp: %s", value); return DSYNC_IBC_RECV_RET_TRYAGAIN; } } if (dsync_deserializer_decode_try(decoder, "sync_max_size", &value)) { if (str_to_uoff(value, &set->sync_max_size) < 0 || set->sync_max_size == 0) { dsync_ibc_input_error(ibc, decoder, "Invalid sync_max_size: %s", value); return DSYNC_IBC_RECV_RET_TRYAGAIN; } } if (dsync_deserializer_decode_try(decoder, "sync_flags", &value)) set->sync_flags = p_strdup(pool, value); if (dsync_deserializer_decode_try(decoder, "alt_char", &value)) set->alt_char = value[0]; if (dsync_deserializer_decode_try(decoder, "send_mail_requests", &value)) set->brain_flags |= DSYNC_BRAIN_FLAG_SEND_MAIL_REQUESTS; if (dsync_deserializer_decode_try(decoder, "backup_send", &value)) set->brain_flags |= DSYNC_BRAIN_FLAG_BACKUP_SEND; if (dsync_deserializer_decode_try(decoder, "backup_recv", &value)) set->brain_flags |= DSYNC_BRAIN_FLAG_BACKUP_RECV; if (dsync_deserializer_decode_try(decoder, "debug", &value)) set->brain_flags |= DSYNC_BRAIN_FLAG_DEBUG; if (dsync_deserializer_decode_try(decoder, "sync_visible_namespaces", &value)) set->brain_flags |= DSYNC_BRAIN_FLAG_SYNC_VISIBLE_NAMESPACES; if (dsync_deserializer_decode_try(decoder, "no_mail_sync", &value)) set->brain_flags |= DSYNC_BRAIN_FLAG_NO_MAIL_SYNC; if (dsync_deserializer_decode_try(decoder, "no_backup_overwrite", &value)) set->brain_flags |= DSYNC_BRAIN_FLAG_NO_BACKUP_OVERWRITE; if (dsync_deserializer_decode_try(decoder, "purge_remote", &value)) set->brain_flags |= DSYNC_BRAIN_FLAG_PURGE_REMOTE; if (dsync_deserializer_decode_try(decoder, "no_notify", &value)) set->brain_flags |= DSYNC_BRAIN_FLAG_NO_NOTIFY; if (dsync_deserializer_decode_try(decoder, "empty_hdr_workaround", &value)) set->brain_flags |= DSYNC_BRAIN_FLAG_EMPTY_HDR_WORKAROUND; if (dsync_deserializer_decode_try(decoder, "hashed_headers", &value)) set->hashed_headers = (const char*const*)p_strsplit_tabescaped(pool, value); set->hdr_hash_v2 = ibc->minor_version >= DSYNC_PROTOCOL_MINOR_HAVE_HDR_HASH_V2; set->hdr_hash_v3 = ibc->minor_version >= DSYNC_PROTOCOL_MINOR_HAVE_HDR_HASH_V3; *set_r = set; return DSYNC_IBC_RECV_RET_OK; } static void dsync_ibc_stream_send_end_of_list(struct dsync_ibc *_ibc, enum dsync_ibc_eol_type type) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; i_assert(ibc->value_output == NULL); switch (type) { case DSYNC_IBC_EOL_MAILBOX_ATTRIBUTE: if (ibc->minor_version < DSYNC_PROTOCOL_MINOR_HAVE_ATTRIBUTES) return; break; default: break; } ibc->last_sent_item_eol = TRUE; o_stream_nsend_str(ibc->output, END_OF_LIST_LINE"\n"); } static void dsync_ibc_stream_send_mailbox_state(struct dsync_ibc *_ibc, const struct dsync_mailbox_state *state) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; struct dsync_serializer_encoder *encoder; string_t *str = t_str_new(128); str_append_c(str, items[ITEM_MAILBOX_STATE].chr); encoder = dsync_ibc_send_encode_begin(ibc, ITEM_MAILBOX_STATE); dsync_serializer_encode_add(encoder, "mailbox_guid", guid_128_to_string(state->mailbox_guid)); dsync_serializer_encode_add(encoder, "last_uidvalidity", dec2str(state->last_uidvalidity)); dsync_serializer_encode_add(encoder, "last_common_uid", dec2str(state->last_common_uid)); dsync_serializer_encode_add(encoder, "last_common_modseq", dec2str(state->last_common_modseq)); dsync_serializer_encode_add(encoder, "last_common_pvt_modseq", dec2str(state->last_common_pvt_modseq)); dsync_serializer_encode_add(encoder, "last_messages_count", dec2str(state->last_messages_count)); if (state->changes_during_sync) dsync_serializer_encode_add(encoder, "changes_during_sync", ""); dsync_serializer_encode_finish(&encoder, str); dsync_ibc_stream_send_string(ibc, str); } static enum dsync_ibc_recv_ret dsync_ibc_stream_recv_mailbox_state(struct dsync_ibc *_ibc, struct dsync_mailbox_state *state_r) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; struct dsync_deserializer_decoder *decoder; const char *value; enum dsync_ibc_recv_ret ret; i_zero(state_r); ret = dsync_ibc_stream_input_next(ibc, ITEM_MAILBOX_STATE, &decoder); if (ret != DSYNC_IBC_RECV_RET_OK) return ret; value = dsync_deserializer_decode_get(decoder, "mailbox_guid"); if (guid_128_from_string(value, state_r->mailbox_guid) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid mailbox_guid"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } value = dsync_deserializer_decode_get(decoder, "last_uidvalidity"); if (str_to_uint32(value, &state_r->last_uidvalidity) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid last_uidvalidity"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } value = dsync_deserializer_decode_get(decoder, "last_common_uid"); if (str_to_uint32(value, &state_r->last_common_uid) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid last_common_uid"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } value = dsync_deserializer_decode_get(decoder, "last_common_modseq"); if (str_to_uint64(value, &state_r->last_common_modseq) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid last_common_modseq"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } value = dsync_deserializer_decode_get(decoder, "last_common_pvt_modseq"); if (str_to_uint64(value, &state_r->last_common_pvt_modseq) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid last_common_pvt_modseq"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "last_messages_count", &value) && str_to_uint32(value, &state_r->last_messages_count) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid last_messages_count"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "changes_during_sync", &value)) state_r->changes_during_sync = TRUE; return DSYNC_IBC_RECV_RET_OK; } static void dsync_ibc_stream_send_mailbox_tree_node(struct dsync_ibc *_ibc, const char *const *name, const struct dsync_mailbox_node *node) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; struct dsync_serializer_encoder *encoder; string_t *str, *namestr; i_assert(*name != NULL); str = t_str_new(128); str_append_c(str, items[ITEM_MAILBOX_TREE_NODE].chr); /* convert all hierarchy separators to tabs. mailbox names really aren't supposed to have any tabs, but escape them anyway if there are. */ namestr = t_str_new(128); for (; *name != NULL; name++) { str_append_tabescaped(namestr, *name); str_append_c(namestr, '\t'); } str_truncate(namestr, str_len(namestr)-1); encoder = dsync_ibc_send_encode_begin(ibc, ITEM_MAILBOX_TREE_NODE); dsync_serializer_encode_add(encoder, "name", str_c(namestr)); switch (node->existence) { case DSYNC_MAILBOX_NODE_NONEXISTENT: dsync_serializer_encode_add(encoder, "existence", "n"); break; case DSYNC_MAILBOX_NODE_EXISTS: dsync_serializer_encode_add(encoder, "existence", "y"); break; case DSYNC_MAILBOX_NODE_DELETED: dsync_serializer_encode_add(encoder, "existence", "d"); break; } if (!guid_128_is_empty(node->mailbox_guid)) { dsync_serializer_encode_add(encoder, "mailbox_guid", guid_128_to_string(node->mailbox_guid)); } if (node->uid_validity != 0) { dsync_serializer_encode_add(encoder, "uid_validity", dec2str(node->uid_validity)); } if (node->uid_next != 0) { dsync_serializer_encode_add(encoder, "uid_next", dec2str(node->uid_next)); } if (node->last_renamed_or_created != 0) { dsync_serializer_encode_add(encoder, "last_renamed_or_created", dec2str(node->last_renamed_or_created)); } if (node->last_subscription_change != 0) { dsync_serializer_encode_add(encoder, "last_subscription_change", dec2str(node->last_subscription_change)); } if (node->subscribed) dsync_serializer_encode_add(encoder, "subscribed", ""); dsync_serializer_encode_finish(&encoder, str); dsync_ibc_stream_send_string(ibc, str); } static enum dsync_ibc_recv_ret dsync_ibc_stream_recv_mailbox_tree_node(struct dsync_ibc *_ibc, const char *const **name_r, const struct dsync_mailbox_node **node_r) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; struct dsync_deserializer_decoder *decoder; struct dsync_mailbox_node *node; const char *value; enum dsync_ibc_recv_ret ret; ret = dsync_ibc_stream_input_next(ibc, ITEM_MAILBOX_TREE_NODE, &decoder); if (ret != DSYNC_IBC_RECV_RET_OK) return ret; p_clear(ibc->ret_pool); node = p_new(ibc->ret_pool, struct dsync_mailbox_node, 1); value = dsync_deserializer_decode_get(decoder, "name"); if (*value == '\0') { dsync_ibc_input_error(ibc, decoder, "Empty name"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } *name_r = (void *)p_strsplit_tabescaped(ibc->ret_pool, value); value = dsync_deserializer_decode_get(decoder, "existence"); switch (*value) { case 'n': node->existence = DSYNC_MAILBOX_NODE_NONEXISTENT; break; case 'y': node->existence = DSYNC_MAILBOX_NODE_EXISTS; break; case 'd': node->existence = DSYNC_MAILBOX_NODE_DELETED; break; } if (dsync_deserializer_decode_try(decoder, "mailbox_guid", &value) && guid_128_from_string(value, node->mailbox_guid) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid mailbox_guid"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "uid_validity", &value) && str_to_uint32(value, &node->uid_validity) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid uid_validity"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "uid_next", &value) && str_to_uint32(value, &node->uid_next) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid uid_next"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "last_renamed_or_created", &value) && str_to_time(value, &node->last_renamed_or_created) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid last_renamed_or_created"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "last_subscription_change", &value) && str_to_time(value, &node->last_subscription_change) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid last_subscription_change"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "subscribed", &value)) node->subscribed = TRUE; *node_r = node; return DSYNC_IBC_RECV_RET_OK; } static void dsync_ibc_stream_encode_delete(string_t *str, struct dsync_serializer_encoder *encoder, const struct dsync_mailbox_delete *deletes, unsigned int count, const char *key, enum dsync_mailbox_delete_type type) { unsigned int i; str_truncate(str, 0); for (i = 0; i < count; i++) { if (deletes[i].type == type) { str_append(str, guid_128_to_string(deletes[i].guid)); str_printfa(str, " %ld ", (long)deletes[i].timestamp); } } if (str_len(str) > 0) { str_truncate(str, str_len(str)-1); dsync_serializer_encode_add(encoder, key, str_c(str)); } } static void dsync_ibc_stream_send_mailbox_deletes(struct dsync_ibc *_ibc, const struct dsync_mailbox_delete *deletes, unsigned int count, char hierarchy_sep, char escape_char) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; struct dsync_serializer_encoder *encoder; string_t *str, *substr; char sep[2]; str = t_str_new(128); str_append_c(str, items[ITEM_MAILBOX_DELETE].chr); encoder = dsync_ibc_send_encode_begin(ibc, ITEM_MAILBOX_DELETE); sep[0] = hierarchy_sep; sep[1] = '\0'; dsync_serializer_encode_add(encoder, "hierarchy_sep", sep); sep[0] = escape_char; sep[1] = '\0'; dsync_serializer_encode_add(encoder, "escape_char", sep); substr = t_str_new(128); dsync_ibc_stream_encode_delete(substr, encoder, deletes, count, "mailboxes", DSYNC_MAILBOX_DELETE_TYPE_MAILBOX); dsync_ibc_stream_encode_delete(substr, encoder, deletes, count, "dirs", DSYNC_MAILBOX_DELETE_TYPE_DIR); dsync_ibc_stream_encode_delete(substr, encoder, deletes, count, "unsubscribes", DSYNC_MAILBOX_DELETE_TYPE_UNSUBSCRIBE); dsync_serializer_encode_finish(&encoder, str); dsync_ibc_stream_send_string(ibc, str); } ARRAY_DEFINE_TYPE(dsync_mailbox_delete, struct dsync_mailbox_delete); static int decode_mailbox_deletes(ARRAY_TYPE(dsync_mailbox_delete) *deletes, const char *value, enum dsync_mailbox_delete_type type) { struct dsync_mailbox_delete *del; const char *const *tmp; unsigned int i; tmp = t_strsplit(value, " "); for (i = 0; tmp[i] != NULL; i += 2) { del = array_append_space(deletes); del->type = type; if (guid_128_from_string(tmp[i], del->guid) < 0) return -1; if (tmp[i+1] == NULL || str_to_time(tmp[i+1], &del->timestamp) < 0) return -1; } return 0; } static enum dsync_ibc_recv_ret dsync_ibc_stream_recv_mailbox_deletes(struct dsync_ibc *_ibc, const struct dsync_mailbox_delete **deletes_r, unsigned int *count_r, char *hierarchy_sep_r, char *escape_char_r) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; struct dsync_deserializer_decoder *decoder; ARRAY_TYPE(dsync_mailbox_delete) deletes; const char *value; enum dsync_ibc_recv_ret ret; ret = dsync_ibc_stream_input_next(ibc, ITEM_MAILBOX_DELETE, &decoder); if (ret != DSYNC_IBC_RECV_RET_OK) return ret; p_clear(ibc->ret_pool); p_array_init(&deletes, ibc->ret_pool, 16); value = dsync_deserializer_decode_get(decoder, "hierarchy_sep"); if (strlen(value) != 1) { dsync_ibc_input_error(ibc, decoder, "Invalid hierarchy_sep"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } *hierarchy_sep_r = value[0]; if (!dsync_deserializer_decode_try(decoder, "escape_char", &value)) *escape_char_r = '\0'; else { if (strlen(value) > 1) { dsync_ibc_input_error(ibc, decoder, "Invalid escape_char '%s'", value); return DSYNC_IBC_RECV_RET_TRYAGAIN; } *escape_char_r = value[0]; } if (dsync_deserializer_decode_try(decoder, "mailboxes", &value) && decode_mailbox_deletes(&deletes, value, DSYNC_MAILBOX_DELETE_TYPE_MAILBOX) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid mailboxes"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "dirs", &value) && decode_mailbox_deletes(&deletes, value, DSYNC_MAILBOX_DELETE_TYPE_DIR) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid dirs"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "unsubscribes", &value) && decode_mailbox_deletes(&deletes, value, DSYNC_MAILBOX_DELETE_TYPE_UNSUBSCRIBE) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid dirs"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } *deletes_r = array_get(&deletes, count_r); return DSYNC_IBC_RECV_RET_OK; } static const char * get_cache_fields(struct dsync_ibc_stream *ibc, const struct dsync_mailbox *dsync_box) { struct dsync_serializer_encoder *encoder; string_t *str; const struct mailbox_cache_field *cache_fields; unsigned int i, count; char decision[3]; cache_fields = array_get(&dsync_box->cache_fields, &count); if (count == 0) return ""; str = t_str_new(128); for (i = 0; i < count; i++) { const struct mailbox_cache_field *field = &cache_fields[i]; encoder = dsync_serializer_encode_begin(ibc->serializers[ITEM_MAILBOX_CACHE_FIELD]); dsync_serializer_encode_add(encoder, "name", field->name); memset(decision, 0, sizeof(decision)); switch (field->decision & ENUM_NEGATE(MAIL_CACHE_DECISION_FORCED)) { case MAIL_CACHE_DECISION_NO: decision[0] = 'n'; break; case MAIL_CACHE_DECISION_TEMP: decision[0] = 't'; break; case MAIL_CACHE_DECISION_YES: decision[0] = 'y'; break; } i_assert(decision[0] != '\0'); if ((field->decision & MAIL_CACHE_DECISION_FORCED) != 0) decision[1] = 'F'; dsync_serializer_encode_add(encoder, "decision", decision); if (field->last_used != 0) { dsync_serializer_encode_add(encoder, "last_used", dec2str(field->last_used)); } dsync_serializer_encode_finish(&encoder, str); } if (i > 0) { /* remove the trailing LF */ str_truncate(str, str_len(str)-1); } return str_c(str); } static void dsync_ibc_stream_send_mailbox(struct dsync_ibc *_ibc, const struct dsync_mailbox *dsync_box) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; struct dsync_serializer_encoder *encoder; string_t *str = t_str_new(128); const char *value; str_append_c(str, items[ITEM_MAILBOX].chr); encoder = dsync_ibc_send_encode_begin(ibc, ITEM_MAILBOX); dsync_serializer_encode_add(encoder, "mailbox_guid", guid_128_to_string(dsync_box->mailbox_guid)); if (dsync_box->mailbox_lost) dsync_serializer_encode_add(encoder, "mailbox_lost", ""); if (dsync_box->mailbox_ignore) dsync_serializer_encode_add(encoder, "mailbox_ignore", ""); if (dsync_box->have_guids) dsync_serializer_encode_add(encoder, "have_guids", ""); if (dsync_box->have_save_guids) dsync_serializer_encode_add(encoder, "have_save_guids", ""); if (dsync_box->have_only_guid128) dsync_serializer_encode_add(encoder, "have_only_guid128", ""); dsync_serializer_encode_add(encoder, "uid_validity", dec2str(dsync_box->uid_validity)); dsync_serializer_encode_add(encoder, "uid_next", dec2str(dsync_box->uid_next)); dsync_serializer_encode_add(encoder, "messages_count", dec2str(dsync_box->messages_count)); dsync_serializer_encode_add(encoder, "first_recent_uid", dec2str(dsync_box->first_recent_uid)); dsync_serializer_encode_add(encoder, "highest_modseq", dec2str(dsync_box->highest_modseq)); dsync_serializer_encode_add(encoder, "highest_pvt_modseq", dec2str(dsync_box->highest_pvt_modseq)); value = get_cache_fields(ibc, dsync_box); if (value != NULL) dsync_serializer_encode_add(encoder, "cache_fields", value); dsync_serializer_encode_finish(&encoder, str); dsync_ibc_stream_send_string(ibc, str); } static int parse_cache_field(struct dsync_ibc_stream *ibc, struct dsync_mailbox *box, const char *value) { struct dsync_deserializer_decoder *decoder; struct mailbox_cache_field field; const char *error; int ret = 0; if (dsync_deserializer_decode_begin(ibc->deserializers[ITEM_MAILBOX_CACHE_FIELD], value, &decoder, &error) < 0) { dsync_ibc_input_error(ibc, NULL, "cache_field: Invalid input: %s", error); return -1; } i_zero(&field); value = dsync_deserializer_decode_get(decoder, "name"); field.name = p_strdup(ibc->ret_pool, value); value = dsync_deserializer_decode_get(decoder, "decision"); switch (*value) { case 'n': field.decision = MAIL_CACHE_DECISION_NO; break; case 't': field.decision = MAIL_CACHE_DECISION_TEMP; break; case 'y': field.decision = MAIL_CACHE_DECISION_YES; break; default: dsync_ibc_input_error(ibc, decoder, "Invalid decision: %s", value); ret = -1; break; } if (value[1] == 'F') field.decision |= MAIL_CACHE_DECISION_FORCED; if (dsync_deserializer_decode_try(decoder, "last_used", &value) && str_to_time(value, &field.last_used) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid last_used"); ret = -1; } array_push_back(&box->cache_fields, &field); dsync_deserializer_decode_finish(&decoder); return ret; } static enum dsync_ibc_recv_ret dsync_ibc_stream_recv_mailbox(struct dsync_ibc *_ibc, const struct dsync_mailbox **dsync_box_r) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; pool_t pool = ibc->ret_pool; struct dsync_deserializer_decoder *decoder; struct dsync_mailbox *box; const char *value; enum dsync_ibc_recv_ret ret; p_clear(pool); box = p_new(pool, struct dsync_mailbox, 1); ret = dsync_ibc_stream_input_next(ibc, ITEM_MAILBOX, &decoder); if (ret != DSYNC_IBC_RECV_RET_OK) return ret; value = dsync_deserializer_decode_get(decoder, "mailbox_guid"); if (guid_128_from_string(value, box->mailbox_guid) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid mailbox_guid"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "mailbox_lost", &value)) box->mailbox_lost = TRUE; if (dsync_deserializer_decode_try(decoder, "mailbox_ignore", &value)) box->mailbox_ignore = TRUE; if (dsync_deserializer_decode_try(decoder, "have_guids", &value)) box->have_guids = TRUE; if (dsync_deserializer_decode_try(decoder, "have_save_guids", &value) || (box->have_guids && ibc->minor_version < DSYNC_PROTOCOL_MINOR_HAVE_SAVE_GUID)) box->have_save_guids = TRUE; if (dsync_deserializer_decode_try(decoder, "have_only_guid128", &value)) box->have_only_guid128 = TRUE; value = dsync_deserializer_decode_get(decoder, "uid_validity"); if (str_to_uint32(value, &box->uid_validity) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid uid_validity"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } value = dsync_deserializer_decode_get(decoder, "uid_next"); if (str_to_uint32(value, &box->uid_next) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid uid_next"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } value = dsync_deserializer_decode_get(decoder, "messages_count"); if (str_to_uint32(value, &box->messages_count) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid messages_count"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } value = dsync_deserializer_decode_get(decoder, "first_recent_uid"); if (str_to_uint32(value, &box->first_recent_uid) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid first_recent_uid"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } value = dsync_deserializer_decode_get(decoder, "highest_modseq"); if (str_to_uint64(value, &box->highest_modseq) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid highest_modseq"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } value = dsync_deserializer_decode_get(decoder, "highest_pvt_modseq"); if (str_to_uint64(value, &box->highest_pvt_modseq) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid highest_pvt_modseq"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } p_array_init(&box->cache_fields, pool, 32); if (dsync_deserializer_decode_try(decoder, "cache_fields", &value)) { const char *const *fields = t_strsplit(value, "\n"); for (; *fields != NULL; fields++) { if (parse_cache_field(ibc, box, *fields) < 0) return DSYNC_IBC_RECV_RET_TRYAGAIN; } } *dsync_box_r = box; return DSYNC_IBC_RECV_RET_OK; } static void dsync_ibc_stream_send_mailbox_attribute(struct dsync_ibc *_ibc, const struct dsync_mailbox_attribute *attr) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; struct dsync_serializer_encoder *encoder; string_t *str = t_str_new(128); char type[2]; if (ibc->minor_version < DSYNC_PROTOCOL_MINOR_HAVE_ATTRIBUTES) return; str_append_c(str, items[ITEM_MAILBOX_ATTRIBUTE].chr); encoder = dsync_ibc_send_encode_begin(ibc, ITEM_MAILBOX_ATTRIBUTE); type[0] = type[1] = '\0'; switch (attr->type) { case MAIL_ATTRIBUTE_TYPE_PRIVATE: type[0] = 'p'; break; case MAIL_ATTRIBUTE_TYPE_SHARED: type[0] = 's'; break; } i_assert(type[0] != '\0'); dsync_serializer_encode_add(encoder, "type", type); dsync_serializer_encode_add(encoder, "key", attr->key); if (attr->value != NULL) dsync_serializer_encode_add(encoder, "value", attr->value); else if (attr->value_stream != NULL) dsync_serializer_encode_add(encoder, "stream", ""); if (attr->deleted) dsync_serializer_encode_add(encoder, "deleted", ""); if (attr->last_change != 0) { dsync_serializer_encode_add(encoder, "last_change", dec2str(attr->last_change)); } if (attr->modseq != 0) { dsync_serializer_encode_add(encoder, "modseq", dec2str(attr->modseq)); } dsync_serializer_encode_finish(&encoder, str); dsync_ibc_stream_send_string(ibc, str); if (attr->value_stream != NULL) { ibc->value_output_last = '\0'; ibc->value_output = attr->value_stream; i_stream_ref(ibc->value_output); (void)dsync_ibc_stream_send_value_stream(ibc); } } static enum dsync_ibc_recv_ret dsync_ibc_stream_recv_mailbox_attribute(struct dsync_ibc *_ibc, const struct dsync_mailbox_attribute **attr_r) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; pool_t pool = ibc->ret_pool; struct dsync_deserializer_decoder *decoder; struct dsync_mailbox_attribute *attr; const char *value; enum dsync_ibc_recv_ret ret; if (ibc->minor_version < DSYNC_PROTOCOL_MINOR_HAVE_ATTRIBUTES) return DSYNC_IBC_RECV_RET_FINISHED; if (ibc->value_input != NULL) { /* wait until the mail's stream has been read */ return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (ibc->cur_attr != NULL) { /* finished reading the stream, return the mail now */ *attr_r = ibc->cur_attr; ibc->cur_attr = NULL; return DSYNC_IBC_RECV_RET_OK; } p_clear(pool); attr = p_new(pool, struct dsync_mailbox_attribute, 1); ret = dsync_ibc_stream_input_next(ibc, ITEM_MAILBOX_ATTRIBUTE, &decoder); if (ret != DSYNC_IBC_RECV_RET_OK) return ret; value = dsync_deserializer_decode_get(decoder, "type"); switch (*value) { case 'p': attr->type = MAIL_ATTRIBUTE_TYPE_PRIVATE; break; case 's': attr->type = MAIL_ATTRIBUTE_TYPE_SHARED; break; default: dsync_ibc_input_error(ibc, decoder, "Invalid type: %s", value); return DSYNC_IBC_RECV_RET_TRYAGAIN; } value = dsync_deserializer_decode_get(decoder, "key"); attr->key = p_strdup(pool, value); if (dsync_deserializer_decode_try(decoder, "deleted", &value)) attr->deleted = TRUE; if (dsync_deserializer_decode_try(decoder, "last_change", &value) && str_to_time(value, &attr->last_change) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid last_change"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "modseq", &value) && str_to_uint64(value, &attr->modseq) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid modseq"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } /* NOTE: stream reading must be the last here, because reading a large stream will be finished later by return TRYAGAIN. We need to deserialize all the other fields before that or they'll get lost. */ if (dsync_deserializer_decode_try(decoder, "stream", &value)) { attr->value_stream = dsync_ibc_stream_input_stream(ibc); if (dsync_ibc_stream_read_mail_stream(ibc) <= 0) { ibc->cur_attr = attr; return DSYNC_IBC_RECV_RET_TRYAGAIN; } /* already finished reading the stream */ i_assert(ibc->value_input == NULL); } else if (dsync_deserializer_decode_try(decoder, "value", &value)) attr->value = p_strdup(pool, value); *attr_r = attr; return DSYNC_IBC_RECV_RET_OK; } static void dsync_ibc_stream_send_change(struct dsync_ibc *_ibc, const struct dsync_mail_change *change) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; struct dsync_serializer_encoder *encoder; string_t *str = t_str_new(128); char type[2]; str_append_c(str, items[ITEM_MAIL_CHANGE].chr); encoder = dsync_ibc_send_encode_begin(ibc, ITEM_MAIL_CHANGE); type[0] = type[1] = '\0'; switch (change->type) { case DSYNC_MAIL_CHANGE_TYPE_SAVE: type[0] = 's'; break; case DSYNC_MAIL_CHANGE_TYPE_EXPUNGE: type[0] = 'e'; break; case DSYNC_MAIL_CHANGE_TYPE_FLAG_CHANGE: type[0] = 'f'; break; } i_assert(type[0] != '\0'); dsync_serializer_encode_add(encoder, "type", type); dsync_serializer_encode_add(encoder, "uid", dec2str(change->uid)); if (change->guid != NULL) dsync_serializer_encode_add(encoder, "guid", change->guid); if (change->hdr_hash != NULL) { dsync_serializer_encode_add(encoder, "hdr_hash", change->hdr_hash); } if (change->modseq != 0) { dsync_serializer_encode_add(encoder, "modseq", dec2str(change->modseq)); } if (change->pvt_modseq != 0) { dsync_serializer_encode_add(encoder, "pvt_modseq", dec2str(change->pvt_modseq)); } if (change->add_flags != 0) { dsync_serializer_encode_add(encoder, "add_flags", t_strdup_printf("%x", change->add_flags)); } if (change->remove_flags != 0) { dsync_serializer_encode_add(encoder, "remove_flags", t_strdup_printf("%x", change->remove_flags)); } if (change->final_flags != 0) { dsync_serializer_encode_add(encoder, "final_flags", t_strdup_printf("%x", change->final_flags)); } if (change->keywords_reset) dsync_serializer_encode_add(encoder, "keywords_reset", ""); if (array_is_created(&change->keyword_changes) && array_count(&change->keyword_changes) > 0) { string_t *kw_str = t_str_new(128); const char *const *changes; unsigned int i, count; changes = array_get(&change->keyword_changes, &count); str_append_tabescaped(kw_str, changes[0]); for (i = 1; i < count; i++) { str_append_c(kw_str, '\t'); str_append_tabescaped(kw_str, changes[i]); } dsync_serializer_encode_add(encoder, "keyword_changes", str_c(kw_str)); } if (change->received_timestamp > 0) { dsync_serializer_encode_add(encoder, "received_timestamp", t_strdup_printf("%"PRIxTIME_T, change->received_timestamp)); } if (change->virtual_size > 0) { dsync_serializer_encode_add(encoder, "virtual_size", t_strdup_printf("%llx", (unsigned long long)change->virtual_size)); } dsync_serializer_encode_finish(&encoder, str); dsync_ibc_stream_send_string(ibc, str); } static enum dsync_ibc_recv_ret dsync_ibc_stream_recv_change(struct dsync_ibc *_ibc, const struct dsync_mail_change **change_r) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; pool_t pool = ibc->ret_pool; struct dsync_deserializer_decoder *decoder; struct dsync_mail_change *change; const char *value; unsigned int uintval; unsigned long long ullongval; enum dsync_ibc_recv_ret ret; p_clear(pool); change = p_new(pool, struct dsync_mail_change, 1); ret = dsync_ibc_stream_input_next(ibc, ITEM_MAIL_CHANGE, &decoder); if (ret != DSYNC_IBC_RECV_RET_OK) return ret; value = dsync_deserializer_decode_get(decoder, "type"); switch (*value) { case 's': change->type = DSYNC_MAIL_CHANGE_TYPE_SAVE; break; case 'e': change->type = DSYNC_MAIL_CHANGE_TYPE_EXPUNGE; break; case 'f': change->type = DSYNC_MAIL_CHANGE_TYPE_FLAG_CHANGE; break; default: dsync_ibc_input_error(ibc, decoder, "Invalid type: %s", value); return DSYNC_IBC_RECV_RET_TRYAGAIN; } value = dsync_deserializer_decode_get(decoder, "uid"); if (str_to_uint32(value, &change->uid) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid uid"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "guid", &value)) change->guid = p_strdup(pool, value); if (dsync_deserializer_decode_try(decoder, "hdr_hash", &value)) change->hdr_hash = p_strdup(pool, value); if (dsync_deserializer_decode_try(decoder, "modseq", &value) && str_to_uint64(value, &change->modseq) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid modseq"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "pvt_modseq", &value) && str_to_uint64(value, &change->pvt_modseq) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid pvt_modseq"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "add_flags", &value)) { if (str_to_uint_hex(value, &uintval) < 0 || uintval > (uint8_t)-1) { dsync_ibc_input_error(ibc, decoder, "Invalid add_flags: %s", value); return DSYNC_IBC_RECV_RET_TRYAGAIN; } change->add_flags = uintval; } if (dsync_deserializer_decode_try(decoder, "remove_flags", &value)) { if (str_to_uint_hex(value, &uintval) < 0 || uintval > (uint8_t)-1) { dsync_ibc_input_error(ibc, decoder, "Invalid remove_flags: %s", value); return DSYNC_IBC_RECV_RET_TRYAGAIN; } change->remove_flags = uintval; } if (dsync_deserializer_decode_try(decoder, "final_flags", &value)) { if (str_to_uint_hex(value, &uintval) < 0 || uintval > (uint8_t)-1) { dsync_ibc_input_error(ibc, decoder, "Invalid final_flags: %s", value); return DSYNC_IBC_RECV_RET_TRYAGAIN; } change->final_flags = uintval; } if (dsync_deserializer_decode_try(decoder, "keywords_reset", &value)) change->keywords_reset = TRUE; if (dsync_deserializer_decode_try(decoder, "keyword_changes", &value) && *value != '\0') { const char *const *changes = t_strsplit_tabescaped(value); unsigned int i, count = str_array_length(changes); p_array_init(&change->keyword_changes, pool, count); for (i = 0; i < count; i++) { value = p_strdup(pool, changes[i]); array_push_back(&change->keyword_changes, &value); } } if (dsync_deserializer_decode_try(decoder, "received_timestamp", &value)) { if (str_to_ullong_hex(value, &ullongval) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid received_timestamp"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } change->received_timestamp = ullongval; } if (dsync_deserializer_decode_try(decoder, "virtual_size", &value)) { if (str_to_ullong_hex(value, &ullongval) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid virtual_size"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } change->virtual_size = ullongval; } *change_r = change; return DSYNC_IBC_RECV_RET_OK; } static void dsync_ibc_stream_send_mail_request(struct dsync_ibc *_ibc, const struct dsync_mail_request *request) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; struct dsync_serializer_encoder *encoder; string_t *str = t_str_new(128); str_append_c(str, items[ITEM_MAIL_REQUEST].chr); encoder = dsync_ibc_send_encode_begin(ibc, ITEM_MAIL_REQUEST); if (request->guid != NULL) dsync_serializer_encode_add(encoder, "guid", request->guid); if (request->uid != 0) { dsync_serializer_encode_add(encoder, "uid", dec2str(request->uid)); } dsync_serializer_encode_finish(&encoder, str); dsync_ibc_stream_send_string(ibc, str); } static enum dsync_ibc_recv_ret dsync_ibc_stream_recv_mail_request(struct dsync_ibc *_ibc, const struct dsync_mail_request **request_r) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; struct dsync_deserializer_decoder *decoder; struct dsync_mail_request *request; const char *value; enum dsync_ibc_recv_ret ret; p_clear(ibc->ret_pool); request = p_new(ibc->ret_pool, struct dsync_mail_request, 1); ret = dsync_ibc_stream_input_next(ibc, ITEM_MAIL_REQUEST, &decoder); if (ret != DSYNC_IBC_RECV_RET_OK) return ret; if (dsync_deserializer_decode_try(decoder, "guid", &value)) request->guid = p_strdup(ibc->ret_pool, value); if (dsync_deserializer_decode_try(decoder, "uid", &value) && str_to_uint32(value, &request->uid) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid uid"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } *request_r = request; return DSYNC_IBC_RECV_RET_OK; } static void dsync_ibc_stream_send_mail(struct dsync_ibc *_ibc, const struct dsync_mail *mail) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; struct dsync_serializer_encoder *encoder; string_t *str = t_str_new(128); i_assert(!mail->minimal_fields); i_assert(ibc->value_output == NULL); str_append_c(str, items[ITEM_MAIL].chr); encoder = dsync_ibc_send_encode_begin(ibc, ITEM_MAIL); if (mail->guid != NULL) dsync_serializer_encode_add(encoder, "guid", mail->guid); if (mail->uid != 0) dsync_serializer_encode_add(encoder, "uid", dec2str(mail->uid)); if (mail->pop3_uidl != NULL) { dsync_serializer_encode_add(encoder, "pop3_uidl", mail->pop3_uidl); } if (mail->pop3_order > 0) { dsync_serializer_encode_add(encoder, "pop3_order", dec2str(mail->pop3_order)); } if (mail->received_date > 0) { dsync_serializer_encode_add(encoder, "received_date", dec2str(mail->received_date)); } if (mail->saved_date != 0) { dsync_serializer_encode_add(encoder, "saved_date", dec2str(mail->saved_date)); } if (mail->input != NULL) dsync_serializer_encode_add(encoder, "stream", ""); dsync_serializer_encode_finish(&encoder, str); dsync_ibc_stream_send_string(ibc, str); if (mail->input != NULL) { ibc->value_output_last = '\0'; ibc->value_output = mail->input; i_stream_ref(ibc->value_output); (void)dsync_ibc_stream_send_value_stream(ibc); } } static enum dsync_ibc_recv_ret dsync_ibc_stream_recv_mail(struct dsync_ibc *_ibc, struct dsync_mail **mail_r) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; pool_t pool = ibc->ret_pool; struct dsync_deserializer_decoder *decoder; struct dsync_mail *mail; const char *value; enum dsync_ibc_recv_ret ret; if (ibc->value_input != NULL) { /* wait until the mail's stream has been read */ return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (ibc->cur_mail != NULL) { /* finished reading the stream, return the mail now */ *mail_r = ibc->cur_mail; ibc->cur_mail = NULL; return DSYNC_IBC_RECV_RET_OK; } p_clear(pool); mail = p_new(pool, struct dsync_mail, 1); ret = dsync_ibc_stream_input_next(ibc, ITEM_MAIL, &decoder); if (ret != DSYNC_IBC_RECV_RET_OK) return ret; if (dsync_deserializer_decode_try(decoder, "guid", &value)) mail->guid = p_strdup(pool, value); if (dsync_deserializer_decode_try(decoder, "uid", &value) && str_to_uint32(value, &mail->uid) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid uid"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "pop3_uidl", &value)) mail->pop3_uidl = p_strdup(pool, value); if (dsync_deserializer_decode_try(decoder, "pop3_order", &value) && str_to_uint32(value, &mail->pop3_order) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid pop3_order"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "received_date", &value) && str_to_time(value, &mail->received_date) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid received_date"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "saved_date", &value) && str_to_time(value, &mail->saved_date) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid saved_date"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "stream", &value)) { mail->input = dsync_ibc_stream_input_stream(ibc); if (dsync_ibc_stream_read_mail_stream(ibc) <= 0) { ibc->cur_mail = mail; return DSYNC_IBC_RECV_RET_TRYAGAIN; } /* already finished reading the stream */ i_assert(ibc->value_input == NULL); } *mail_r = mail; return DSYNC_IBC_RECV_RET_OK; } static void dsync_ibc_stream_send_finish(struct dsync_ibc *_ibc, const char *error, enum mail_error mail_error, bool require_full_resync) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; struct dsync_serializer_encoder *encoder; string_t *str = t_str_new(128); str_append_c(str, items[ITEM_FINISH].chr); encoder = dsync_ibc_send_encode_begin(ibc, ITEM_FINISH); if (error != NULL) dsync_serializer_encode_add(encoder, "error", error); if (mail_error != 0) { dsync_serializer_encode_add(encoder, "mail_error", dec2str(mail_error)); } if (require_full_resync) dsync_serializer_encode_add(encoder, "require_full_resync", ""); dsync_serializer_encode_finish(&encoder, str); dsync_ibc_stream_send_string(ibc, str); } static enum dsync_ibc_recv_ret dsync_ibc_stream_recv_finish(struct dsync_ibc *_ibc, const char **error_r, enum mail_error *mail_error_r, bool *require_full_resync_r) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; struct dsync_deserializer_decoder *decoder; const char *value; enum dsync_ibc_recv_ret ret; int i = 0; *error_r = NULL; *mail_error_r = 0; *require_full_resync_r = FALSE; p_clear(ibc->ret_pool); if (ibc->minor_version < DSYNC_PROTOCOL_MINOR_HAVE_FINISH) return DSYNC_IBC_RECV_RET_OK; ret = dsync_ibc_stream_input_next(ibc, ITEM_FINISH, &decoder); if (ret != DSYNC_IBC_RECV_RET_OK) return ret; if (dsync_deserializer_decode_try(decoder, "error", &value)) *error_r = p_strdup(ibc->ret_pool, value); if (dsync_deserializer_decode_try(decoder, "mail_error", &value) && str_to_int(value, &i) < 0) { dsync_ibc_input_error(ibc, decoder, "Invalid mail_error"); return DSYNC_IBC_RECV_RET_TRYAGAIN; } if (dsync_deserializer_decode_try(decoder, "require_full_resync", &value)) *require_full_resync_r = TRUE; *mail_error_r = i; ibc->finish_received = TRUE; return DSYNC_IBC_RECV_RET_OK; } static void dsync_ibc_stream_close_mail_streams(struct dsync_ibc *_ibc) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; if (ibc->value_output != NULL) { i_stream_unref(&ibc->value_output); dsync_ibc_stream_stop(ibc); } } static bool dsync_ibc_stream_is_send_queue_full(struct dsync_ibc *_ibc) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; size_t bytes; if (ibc->value_output != NULL) return TRUE; bytes = o_stream_get_buffer_used_size(ibc->output); if (bytes < DSYNC_IBC_STREAM_OUTBUF_THROTTLE_SIZE) return FALSE; o_stream_set_flush_pending(ibc->output, TRUE); return TRUE; } static bool dsync_ibc_stream_has_pending_data(struct dsync_ibc *_ibc) { struct dsync_ibc_stream *ibc = (struct dsync_ibc_stream *)_ibc; return ibc->has_pending_data; } static const struct dsync_ibc_vfuncs dsync_ibc_stream_vfuncs = { dsync_ibc_stream_deinit, dsync_ibc_stream_send_handshake, dsync_ibc_stream_recv_handshake, dsync_ibc_stream_send_end_of_list, dsync_ibc_stream_send_mailbox_state, dsync_ibc_stream_recv_mailbox_state, dsync_ibc_stream_send_mailbox_tree_node, dsync_ibc_stream_recv_mailbox_tree_node, dsync_ibc_stream_send_mailbox_deletes, dsync_ibc_stream_recv_mailbox_deletes, dsync_ibc_stream_send_mailbox, dsync_ibc_stream_recv_mailbox, dsync_ibc_stream_send_mailbox_attribute, dsync_ibc_stream_recv_mailbox_attribute, dsync_ibc_stream_send_change, dsync_ibc_stream_recv_change, dsync_ibc_stream_send_mail_request, dsync_ibc_stream_recv_mail_request, dsync_ibc_stream_send_mail, dsync_ibc_stream_recv_mail, dsync_ibc_stream_send_finish, dsync_ibc_stream_recv_finish, dsync_ibc_stream_close_mail_streams, dsync_ibc_stream_is_send_queue_full, dsync_ibc_stream_has_pending_data }; struct dsync_ibc * dsync_ibc_init_stream(struct istream *input, struct ostream *output, const char *name, const char *temp_path_prefix, unsigned int timeout_secs) { struct dsync_ibc_stream *ibc; ibc = i_new(struct dsync_ibc_stream, 1); ibc->ibc.v = dsync_ibc_stream_vfuncs; ibc->input = input; ibc->output = output; i_stream_ref(ibc->input); o_stream_ref(ibc->output); ibc->name = i_strdup(name); ibc->temp_path_prefix = i_strdup(temp_path_prefix); ibc->timeout_secs = timeout_secs; ibc->ret_pool = pool_alloconly_create("ibc stream data", 2048); dsync_ibc_stream_init(ibc); return &ibc->ibc; } dovecot-2.3.21.1/src/doveadm/dsync/dsync-mailbox-tree.h0000644000000000000000000001746414656633576017536 00000000000000#ifndef DSYNC_MAILBOX_TREE_H #define DSYNC_MAILBOX_TREE_H #include "guid.h" #include "mail-error.h" struct mail_namespace; struct dsync_brain; enum dsync_mailbox_trees_sync_type { /* two-way sync for both mailboxes */ DSYNC_MAILBOX_TREES_SYNC_TYPE_TWOWAY, /* make remote tree look exactly like the local tree */ DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_LOCAL, /* make local tree look exactly like the remote tree */ DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_REMOTE }; enum dsync_mailbox_trees_sync_flags { /* Enable debugging */ DSYNC_MAILBOX_TREES_SYNC_FLAG_DEBUG = 0x01, /* Show ourself as "master brain" in the debug output */ DSYNC_MAILBOX_TREES_SYNC_FLAG_MASTER_BRAIN = 0x02, /* Disable mailbox renaming logic. This is just a kludge that should be removed once the renaming logic has no more bugs.. */ DSYNC_MAILBOX_TREES_SYNC_FLAG_NO_RENAMES = 0x04 }; enum dsync_mailbox_node_existence { /* this is just a filler node for children or for subscription deletion */ DSYNC_MAILBOX_NODE_NONEXISTENT = 0, /* if mailbox GUID is set, the mailbox exists. otherwise the directory exists. */ DSYNC_MAILBOX_NODE_EXISTS, /* if mailbox GUID is set, the mailbox has been deleted. otherwise the directory has been deleted. */ DSYNC_MAILBOX_NODE_DELETED }; struct dsync_mailbox_node { struct dsync_mailbox_node *parent, *next, *first_child; /* namespace where this node belongs to */ struct mail_namespace *ns; /* this node's name (not including parents) */ const char *name; /* mailbox GUID, or full of zeros if this is about a directory name */ guid_128_t mailbox_guid; /* mailbox's UIDVALIDITY/UIDNEXT (may be 0 if not assigned yet) */ uint32_t uid_validity, uid_next; /* existence of this mailbox/directory. doesn't affect subscription state. */ enum dsync_mailbox_node_existence existence; /* last time the mailbox/directory was created/renamed, 0 if not known */ time_t last_renamed_or_created; /* last time the subscription state was changed, 0 if not known */ time_t last_subscription_change; /* is this mailbox or directory subscribed? */ bool subscribed:1; /* Internal syncing flags: */ bool sync_delayed_guid_change:1; bool sync_temporary_name:1; }; ARRAY_DEFINE_TYPE(dsync_mailbox_node, struct dsync_mailbox_node *); #define dsync_mailbox_node_guids_equal(node1, node2) \ (memcmp((node1)->mailbox_guid, (node2)->mailbox_guid, \ sizeof(guid_128_t)) == 0) #define dsync_mailbox_node_is_dir(node) \ guid_128_is_empty((node)->mailbox_guid) enum dsync_mailbox_delete_type { /* Delete mailbox by given GUID */ DSYNC_MAILBOX_DELETE_TYPE_MAILBOX = 1, /* Delete mailbox directory by given SHA1 name */ DSYNC_MAILBOX_DELETE_TYPE_DIR, /* Unsubscribe mailbox by given SHA1 name */ DSYNC_MAILBOX_DELETE_TYPE_UNSUBSCRIBE, }; struct dsync_mailbox_delete { enum dsync_mailbox_delete_type type; guid_128_t guid; time_t timestamp; }; enum dsync_mailbox_tree_sync_type { DSYNC_MAILBOX_TREE_SYNC_TYPE_CREATE_BOX, DSYNC_MAILBOX_TREE_SYNC_TYPE_CREATE_DIR, DSYNC_MAILBOX_TREE_SYNC_TYPE_DELETE_BOX, DSYNC_MAILBOX_TREE_SYNC_TYPE_DELETE_DIR, /* Rename given mailbox name and its children */ DSYNC_MAILBOX_TREE_SYNC_TYPE_RENAME, DSYNC_MAILBOX_TREE_SYNC_TYPE_SUBSCRIBE, DSYNC_MAILBOX_TREE_SYNC_TYPE_UNSUBSCRIBE }; struct dsync_mailbox_tree_sync_change { enum dsync_mailbox_tree_sync_type type; /* for all types: */ struct mail_namespace *ns; const char *full_name; /* for create_box and delete_box: */ guid_128_t mailbox_guid; /* for create_box: */ uint32_t uid_validity; /* for rename: */ const char *rename_dest_name; }; struct dsync_mailbox_tree * dsync_mailbox_tree_init(char sep, char escape_char, char alt_char); void dsync_mailbox_tree_deinit(struct dsync_mailbox_tree **tree); /* Lookup a mailbox node by name. Returns NULL if not known. */ struct dsync_mailbox_node * dsync_mailbox_tree_lookup(struct dsync_mailbox_tree *tree, const char *full_name); /* Lookup a mailbox node by GUID. Returns NULL if not known. The mailbox GUID hash must have been build before calling this. */ struct dsync_mailbox_node * dsync_mailbox_tree_lookup_guid(struct dsync_mailbox_tree *tree, const guid_128_t guid); /* Lookup or create a mailbox node by name. */ struct dsync_mailbox_node * dsync_mailbox_tree_get(struct dsync_mailbox_tree *tree, const char *full_name); /* Returns full name for the given mailbox node. */ const char *dsync_mailbox_node_get_full_name(const struct dsync_mailbox_tree *tree, const struct dsync_mailbox_node *node); void dsync_mailbox_node_append_full_name(string_t *str, const struct dsync_mailbox_tree *tree, const struct dsync_mailbox_node *node); /* Copy everything from src to dest, except name and hierarchy pointers */ void dsync_mailbox_node_copy_data(struct dsync_mailbox_node *dest, const struct dsync_mailbox_node *src); /* Split mailbox name into its hierarchical parts. The mailbox name is unescaped if the escape_char is not '\0'. */ const char *const * dsync_mailbox_name_to_parts(const char *name, char hierarchy_sep, char escape_char); /* Add nodes to tree from the given namespace. If box_name or box_guid is non-NULL, add only that mailbox to the tree. */ int dsync_mailbox_tree_fill(struct dsync_mailbox_tree *tree, struct mail_namespace *ns, const char *box_name, const guid_128_t box_guid, const char *const *exclude_mailboxes, char alt_char, enum mail_error *error_r); /* Return all known deleted mailboxes and directories. */ const struct dsync_mailbox_delete * dsync_mailbox_tree_get_deletes(struct dsync_mailbox_tree *tree, unsigned int *count_r); /* Return mailbox node for a given delete record, or NULL if it doesn't exist. The delete record is intended to come from another tree, possibly with a different hierarchy separator. dsync_mailbox_tree_build_guid_hash() must have been called before this. */ struct dsync_mailbox_node * dsync_mailbox_tree_find_delete(struct dsync_mailbox_tree *tree, const struct dsync_mailbox_delete *del); /* Build GUID lookup hash, if it's not already built. Returns 0 if ok, -1 if there are duplicate GUIDs. The nodes with the duplicate GUIDs are returned. */ int dsync_mailbox_tree_build_guid_hash(struct dsync_mailbox_tree *tree, struct dsync_mailbox_node **dup_node1_r, struct dsync_mailbox_node **dup_node2_r); /* Manually add a new node to hash. */ int dsync_mailbox_tree_guid_hash_add(struct dsync_mailbox_tree *tree, struct dsync_mailbox_node *node, struct dsync_mailbox_node **old_node_r); /* Set remote separator used for directory deletions in dsync_mailbox_tree_find_delete() */ void dsync_mailbox_tree_set_remote_chars(struct dsync_mailbox_tree *tree, char remote_sep, char remote_escape_char); /* Iterate through all nodes in a tree (depth-first) */ struct dsync_mailbox_tree_iter * dsync_mailbox_tree_iter_init(struct dsync_mailbox_tree *tree); bool dsync_mailbox_tree_iter_next(struct dsync_mailbox_tree_iter *iter, const char **full_name_r, struct dsync_mailbox_node **node_r); void dsync_mailbox_tree_iter_deinit(struct dsync_mailbox_tree_iter **iter); /* Sync local and remote trees so at the end they're exactly the same. Return changes done to local tree. */ struct dsync_mailbox_tree_sync_ctx * dsync_mailbox_trees_sync_init(struct dsync_mailbox_tree *local_tree, struct dsync_mailbox_tree *remote_tree, enum dsync_mailbox_trees_sync_type sync_type, enum dsync_mailbox_trees_sync_flags sync_flags); const struct dsync_mailbox_tree_sync_change * dsync_mailbox_trees_sync_next(struct dsync_mailbox_tree_sync_ctx *ctx); int dsync_mailbox_trees_sync_deinit(struct dsync_mailbox_tree_sync_ctx **ctx); const char *dsync_mailbox_node_to_string(const struct dsync_mailbox_node *node); const char * dsync_mailbox_delete_type_to_string(enum dsync_mailbox_delete_type type); #endif dovecot-2.3.21.1/src/doveadm/dsync/test-dsync-mailbox-tree-sync.c0000644000000000000000000005151714656633576021455 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "sha1.h" #include "str.h" #include "mailbox-list-private.h" #include "dsync-mailbox-tree-private.h" #include "test-common.h" #include #define MAX_DEPTH 4 #define TEST_NAMESPACE_NAME "INBOX" static struct mail_namespace inbox_namespace = { .prefix = TEST_NAMESPACE_NAME"/", .prefix_len = sizeof(TEST_NAMESPACE_NAME)-1 + 1 }; char mail_namespace_get_sep(struct mail_namespace *ns ATTR_UNUSED) { return '/'; } void mailbox_name_get_sha128(const char *name, guid_128_t guid_128_r) { unsigned char sha[SHA1_RESULTLEN]; sha1_get_digest(name, strlen(name), sha); memcpy(guid_128_r, sha, I_MIN(GUID_128_SIZE, sizeof(sha))); } void mailbox_list_name_unescape(const char **name ATTR_UNUSED, char escape_char ATTR_UNUSED) { } void mailbox_list_name_escape(const char *name, const char *escape_chars ATTR_UNUSED, string_t *dest) { str_append(dest, name); } static struct dsync_mailbox_node * node_create(struct dsync_mailbox_tree *tree, unsigned int counter, const char *name, unsigned int last_renamed_or_created) { struct dsync_mailbox_node *node; node = dsync_mailbox_tree_get(tree, name); memcpy(node->mailbox_guid, &counter, sizeof(counter)); node->uid_validity = counter; node->existence = DSYNC_MAILBOX_NODE_EXISTS; node->last_renamed_or_created = last_renamed_or_created; return node; } static struct dsync_mailbox_node * random_node_create(struct dsync_mailbox_tree *tree, unsigned int counter, const char *name) { return node_create(tree, counter, name, i_rand_limit(10)); } static void nodes_create(struct dsync_mailbox_tree *tree, unsigned int *counter, const char *const *names) { for (; *names != NULL; names++) { *counter += 1; node_create(tree, *counter, *names, 0); } } static void nodes_delete(struct dsync_mailbox_tree *tree, unsigned int *counter, const char *const *names) { struct dsync_mailbox_node *node; for (; *names != NULL; names++) { *counter += 1; node = node_create(tree, *counter, *names, 0); node->existence = DSYNC_MAILBOX_NODE_DELETED; } } static void create_random_nodes(struct dsync_mailbox_tree *tree, const char *parent_name, unsigned int depth, unsigned int *counter) { unsigned int parent_len, i, nodes_count = i_rand_minmax(1, 3); string_t *str; if (depth == MAX_DEPTH) return; str = t_str_new(32); if (*parent_name != '\0') str_printfa(str, "%s/", parent_name); parent_len = str_len(str); for (i = 0; i < nodes_count; i++) { *counter += 1; str_truncate(str, parent_len); str_printfa(str, "%u.%u", depth, i); random_node_create(tree, *counter, str_c(str)); create_random_nodes(tree, str_c(str), depth+1, counter); } } static struct dsync_mailbox_tree *create_random_tree(void) { struct dsync_mailbox_tree *tree; unsigned int counter = 0; tree = dsync_mailbox_tree_init('/', '\0', '_'); create_random_nodes(tree, "", 0, &counter); return tree; } static void test_tree_nodes_fixup(struct dsync_mailbox_node **pos, unsigned int *newguid_counter) { struct dsync_mailbox_node *node; for (node = *pos; node != NULL; node = node->next) { if (node->sync_delayed_guid_change) { /* the real code will pick one of the GUIDs. we don't really care which one gets picked, so we'll just change them to the same new one */ memcpy(node->mailbox_guid, newguid_counter, sizeof(*newguid_counter)); node->uid_validity = *newguid_counter; *newguid_counter += 1; } if (node->existence == DSYNC_MAILBOX_NODE_DELETED) node->existence = DSYNC_MAILBOX_NODE_NONEXISTENT; test_tree_nodes_fixup(&node->first_child, newguid_counter); if (node->existence != DSYNC_MAILBOX_NODE_EXISTS && node->first_child == NULL) { /* nonexistent node, drop it */ *pos = node->next; } else { pos = &node->next; } } } static void test_tree_fixup(struct dsync_mailbox_tree *tree) { unsigned int newguid_counter = INT_MAX; test_tree_nodes_fixup(&tree->root.first_child, &newguid_counter); } static void nodes_dump(const struct dsync_mailbox_node *node, unsigned int depth) { unsigned int i; for (; node != NULL; node = node->next) { for (i = 0; i < depth; i++) printf(" "); printf("%-*s guid:%.5s uidv:%u %d%d %ld\n", 40-depth, node->name, guid_128_to_string(node->mailbox_guid), node->uid_validity, node->existence, node->subscribed ? 1 : 0, (long)node->last_renamed_or_created); nodes_dump(node->first_child, depth+1); } } static void trees_dump(struct dsync_mailbox_tree *tree1, struct dsync_mailbox_tree *tree2) { printf("tree1:\n"); nodes_dump(tree1->root.first_child, 1); printf("tree2:\n"); nodes_dump(tree2->root.first_child, 1); } static void test_trees_nofree(struct dsync_mailbox_tree *tree1, struct dsync_mailbox_tree **_tree2) { struct dsync_mailbox_tree *tree2 = *_tree2; struct dsync_mailbox_tree *orig_tree1, *orig_tree2; struct dsync_mailbox_tree_sync_ctx *ctx; struct dsync_mailbox_node *dup_node1, *dup_node2; orig_tree1 = dsync_mailbox_tree_dup(tree1); orig_tree2 = dsync_mailbox_tree_dup(tree2); /* test tree1 -> tree2 */ dsync_mailbox_tree_build_guid_hash(tree1, &dup_node1, &dup_node2); dsync_mailbox_tree_build_guid_hash(tree2, &dup_node1, &dup_node2); ctx = dsync_mailbox_trees_sync_init(tree1, tree2, DSYNC_MAILBOX_TREES_SYNC_TYPE_TWOWAY, DSYNC_MAILBOX_TREES_SYNC_FLAG_DEBUG); while (dsync_mailbox_trees_sync_next(ctx) != NULL) { } dsync_mailbox_trees_sync_deinit(&ctx); test_tree_fixup(tree1); test_tree_fixup(tree2); if (!dsync_mailbox_trees_equal(tree1, tree2)) { test_assert(FALSE); trees_dump(tree1, tree2); } /* test tree2 -> tree1 */ dsync_mailbox_tree_build_guid_hash(orig_tree1, &dup_node1, &dup_node2); dsync_mailbox_tree_build_guid_hash(orig_tree2, &dup_node1, &dup_node2); ctx = dsync_mailbox_trees_sync_init(orig_tree2, orig_tree1, DSYNC_MAILBOX_TREES_SYNC_TYPE_TWOWAY, 0); while (dsync_mailbox_trees_sync_next(ctx) != NULL) { } dsync_mailbox_trees_sync_deinit(&ctx); test_tree_fixup(orig_tree1); test_tree_fixup(orig_tree2); if (!dsync_mailbox_trees_equal(orig_tree1, orig_tree2)) { test_assert(FALSE); trees_dump(orig_tree1, orig_tree2); } /* make sure both directions produced equal trees */ if (!dsync_mailbox_trees_equal(tree1, orig_tree1)) { test_assert(FALSE); trees_dump(tree1, orig_tree1); } dsync_mailbox_tree_deinit(_tree2); dsync_mailbox_tree_deinit(&orig_tree1); dsync_mailbox_tree_deinit(&orig_tree2); } static void test_tree_nodes_add_namespace(struct dsync_mailbox_node *node, struct mail_namespace *ns) { for (; node != NULL; node = node->next) { node->ns = ns; test_tree_nodes_add_namespace(node->first_child, ns); } } static void test_tree_add_namespace(struct dsync_mailbox_tree *tree, struct mail_namespace *ns) { struct dsync_mailbox_node *node, *n; node = dsync_mailbox_tree_get(tree, TEST_NAMESPACE_NAME); node->existence = DSYNC_MAILBOX_NODE_EXISTS; i_assert(tree->root.first_child == node); i_assert(node->first_child == NULL); node->first_child = node->next; for (n = node->first_child; n != NULL; n = n->next) n->parent = node; node->next = NULL; test_tree_nodes_add_namespace(&tree->root, ns); } static void test_trees(struct dsync_mailbox_tree *tree1, struct dsync_mailbox_tree *tree2) { struct dsync_mailbox_tree *tree1_dup, *tree2_dup; tree1_dup = dsync_mailbox_tree_dup(tree1); tree2_dup = dsync_mailbox_tree_dup(tree2); /* test without namespace prefix */ test_trees_nofree(tree1, &tree2); dsync_mailbox_tree_deinit(&tree1); /* test with namespace prefix */ test_tree_add_namespace(tree1_dup, &inbox_namespace); test_tree_add_namespace(tree2_dup, &inbox_namespace); test_trees_nofree(tree1_dup, &tree2_dup); dsync_mailbox_tree_deinit(&tree1_dup); } static void test_dsync_mailbox_tree_sync_creates(void) { static const char *common_nodes[] = { "foo", "foo/bar", NULL }; static const char *create1_nodes[] = { "bar", "foo/baz", NULL }; static const char *create2_nodes[] = { "foo/xyz", "foo/bar/3", NULL }; struct dsync_mailbox_tree *tree1, *tree2; unsigned int counter = 0; test_begin("dsync mailbox tree sync creates"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); nodes_create(tree1, &counter, common_nodes); tree2 = dsync_mailbox_tree_dup(tree1); nodes_create(tree1, &counter, create1_nodes); nodes_create(tree2, &counter, create2_nodes); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_deletes(void) { static const char *common_nodes[] = { "1", "2", "3", "2/s1", "2/s2", "x/y", NULL }; static const char *delete1_nodes[] = { "1", "2", NULL }; static const char *delete2_nodes[] = { "2/s1", "x/y", NULL }; struct dsync_mailbox_tree *tree1, *tree2; unsigned int counter = 0; test_begin("dsync mailbox tree sync deletes"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); nodes_create(tree1, &counter, common_nodes); tree2 = dsync_mailbox_tree_dup(tree1); nodes_delete(tree1, &counter, delete1_nodes); nodes_delete(tree2, &counter, delete2_nodes); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames1(void) { static const char *common_nodes[] = { "1", "2", "3", "2/s1", "2/s2", "x/y", "3/s3", NULL }; struct dsync_mailbox_tree *tree1, *tree2; struct dsync_mailbox_node *node; unsigned int counter = 0; test_begin("dsync mailbox tree sync renames 1"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); nodes_create(tree1, &counter, common_nodes); tree2 = dsync_mailbox_tree_dup(tree1); node = dsync_mailbox_tree_get(tree1, "1"); node->name = "a"; node->last_renamed_or_created = 1000; node = dsync_mailbox_tree_get(tree2, "2"); node->name = "b"; node->last_renamed_or_created = 1000; node = dsync_mailbox_tree_get(tree1, "3/s3"); node->name = "z"; node->last_renamed_or_created = 1000; dsync_mailbox_tree_node_detach(node); dsync_mailbox_tree_node_attach(node, &tree1->root); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames2(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 2"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 1, "0/1", 1); node_create(tree1, 2, "0/1/2", 3); node_create(tree2, 1, "0", 0); node_create(tree2, 2, "0/1/2", 0); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames3(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 3"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 1, "0/2", 1); node_create(tree1, 2, "0/3", 1); node_create(tree2, 1, "0/4/5", 0); node_create(tree2, 2, "1", 0); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames4(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 4"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 1, "0/b", 0); node_create(tree1, 2, "c", 2); node_create(tree2, 2, "0/a", 0); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames5(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 5"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 1, "b", 0); node_create(tree1, 2, "c", 2); node_create(tree2, 2, "0/a", 0); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames6(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 6"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 1, "0/1", 0); node_create(tree1, 2, "0/2", 1); node_create(tree2, 1, "0", 1); node_create(tree2, 2, "0/3", 0); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames7(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 7"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 1, "0/2", 0); node_create(tree2, 1, "1/2", 0); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames8(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 8"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 1, "0/1", 0); node_create(tree1, 2, "0/2", 1); node_create(tree2, 1, "0", 1); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames9(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 9"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 1, "0/1/2", 0); node_create(tree1, 2, "0/3", 1); node_create(tree2, 1, "0", 1); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames10(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 10"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 1, "0/1", 0); node_create(tree1, 3, "0/2/3", 0); node_create(tree2, 1, "0", 1); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames11(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 11"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 1, "0/1", 2); node_create(tree1, 0, "0/1/2", 0); node_create(tree2, 1, "0", 1); node_create(tree2, 0, "0/1/2", 0); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames12(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 12"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 1, "0/2", 0); node_create(tree1, 2, "1", 0); node_create(tree1, 3, "1/4", 0); node_create(tree1, 4, "1/4/5", 1); node_create(tree2, 1, "1", 2); node_create(tree2, 2, "1/4", 3); node_create(tree2, 3, "1/4/6", 4); node_create(tree2, 4, "1/3", 0); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames13(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 13"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 4, "0.0/1.0/2.1", 0); node_create(tree1, 5, "0.1", 2); node_create(tree1, 6, "0.1/1.0", 2); node_create(tree1, 7, "0.1/1.0/2.0", 8); node_create(tree2, 5, "0.1/1.0", 5); node_create(tree2, 6, "0.1/1.0/2.0", 8); node_create(tree2, 7, "0.1/1.1", 1); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames14(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 14"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 1, "1", 0); node_create(tree1, 2, "1/2", 0); node_create(tree1, 3, "1/2/4", 1); node_create(tree2, 1, "1/2", 3); node_create(tree2, 2, "1/2/5", 4); node_create(tree2, 3, "1/2/4", 0); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames15(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 15"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 1, "1", 0); node_create(tree2, 2, "1", 1); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames16(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 16"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 1, "1/2", 4); node_create(tree1, 2, "1", 2); node_create(tree2, 1, "2", 1); node_create(tree2, 2, "1/2", 3); node_create(tree2, 3, "1", 5); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames17(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 17"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 1, "1", 1); node_create(tree2, 1, "1/2", 0); node_create(tree2, 2, "1", 2); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames18(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 18"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 2, "a", 5); node_create(tree1, 4, "a/c", 2); node_create(tree1, 5, "b", 6); node_create(tree2, 1, "a", 7); node_create(tree2, 2, "b", 3); node_create(tree2, 3, "b/c", 4); node_create(tree2, 4, "d", 1); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames19(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 19"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 1, "0/2/1", 1); node_create(tree1, 2, "0/4", 3); node_create(tree1, 3, "0/2", 2); node_create(tree2, 1, "1", 0); node_create(tree2, 2, "1/3", 4); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_renames20(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 20"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 1, "1", 0); node_create(tree1, 2, "0", 0); node_create(tree1, 3, "0/2", 0); /* rename 0 -> 1/0 */ node_create(tree2, 1, "1", 0); node_create(tree2, 2, "1/0", 1); node_create(tree2, 3, "1/0/2", 0); test_trees_nofree(tree1, &tree2); test_assert(tree1->root.first_child != NULL && tree1->root.first_child->next == NULL); dsync_mailbox_tree_deinit(&tree1); test_end(); } static void test_dsync_mailbox_tree_sync_renames21(void) { #if 0 /* FIXME: we can't currently test this without crashing */ struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 21"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 1, "INBOX", 0); node_create(tree1, 2, "foo", 0); /* swap INBOX and foo - the INBOX name is important since it's treated specially */ node_create(tree2, 1, "foo", 0); node_create(tree2, 2, "INBOX", 1); test_trees(tree1, tree2); test_end(); #endif } static void test_dsync_mailbox_tree_sync_renames22(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync renames 22"); tree1 = dsync_mailbox_tree_init('/', '\0', '_'); tree2 = dsync_mailbox_tree_init('/', '\0', '_'); node_create(tree1, 3, "p/a", 0); node_create(tree1, 0, "p/2", 0); node_create(tree1, 5, "p/2/h", 0); node_create(tree2, 4, "p/1/z", 0); node_create(tree2, 1, "p/2", 0); node_create(tree2, 2, "p/2/a", 0); node_create(tree2, 5, "p/2/y", 0); node_create(tree2, 3, "p/3", 0); test_trees(tree1, tree2); test_end(); } static void test_dsync_mailbox_tree_sync_random(void) { struct dsync_mailbox_tree *tree1, *tree2; test_begin("dsync mailbox tree sync random"); tree1 = create_random_tree(); tree2 = create_random_tree(); test_trees(tree1, tree2); test_end(); } int main(void) { static void (*const test_functions[])(void) = { test_dsync_mailbox_tree_sync_creates, test_dsync_mailbox_tree_sync_deletes, test_dsync_mailbox_tree_sync_renames1, test_dsync_mailbox_tree_sync_renames2, test_dsync_mailbox_tree_sync_renames3, test_dsync_mailbox_tree_sync_renames4, test_dsync_mailbox_tree_sync_renames5, test_dsync_mailbox_tree_sync_renames6, test_dsync_mailbox_tree_sync_renames7, test_dsync_mailbox_tree_sync_renames8, test_dsync_mailbox_tree_sync_renames9, test_dsync_mailbox_tree_sync_renames10, test_dsync_mailbox_tree_sync_renames11, test_dsync_mailbox_tree_sync_renames12, test_dsync_mailbox_tree_sync_renames13, test_dsync_mailbox_tree_sync_renames14, test_dsync_mailbox_tree_sync_renames15, test_dsync_mailbox_tree_sync_renames16, test_dsync_mailbox_tree_sync_renames17, test_dsync_mailbox_tree_sync_renames18, test_dsync_mailbox_tree_sync_renames19, test_dsync_mailbox_tree_sync_renames20, test_dsync_mailbox_tree_sync_renames21, test_dsync_mailbox_tree_sync_renames22, test_dsync_mailbox_tree_sync_random, NULL }; return test_run(test_functions); } dovecot-2.3.21.1/src/doveadm/dsync/dsync-mailbox-tree-fill.c0000644000000000000000000003302714656633576020446 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "hash.h" #include "guid.h" #include "str.h" #include "wildcard-match.h" #include "mailbox-log.h" #include "mail-namespace.h" #include "mail-storage.h" #include "mailbox-list-iter.h" #include "dsync-brain.h" #include "dsync-mailbox-tree-private.h" static const char * dsync_mailbox_tree_name_unescape(struct mail_namespace *ns, const char *old_vname, char alt_char) { const char ns_sep = mail_namespace_get_sep(ns); const char escape_char = mailbox_list_get_settings(ns->list)->vname_escape_char; const char *const *old_vname_parts = dsync_mailbox_name_to_parts(old_vname, ns_sep, escape_char); string_t *new_vname = t_str_new(128); for (; *old_vname_parts != NULL; old_vname_parts++) { for (const char *p = *old_vname_parts; *p != '\0'; p++) { if (*p != ns_sep) str_append_c(new_vname, *p); else str_append_c(new_vname, alt_char); } str_append_c(new_vname, ns_sep); } str_truncate(new_vname, str_len(new_vname)-1); return str_c(new_vname); }; static int dsync_mailbox_tree_add_node(struct dsync_mailbox_tree *tree, const struct mailbox_info *info, char alt_char, struct dsync_mailbox_node **node_r) { struct dsync_mailbox_node *node; const char *vname = info->vname; struct dsync_mailbox_list *dlist = DSYNC_LIST_CONTEXT(info->ns->list); if (dlist != NULL && !dlist->have_orig_escape_char) { /* The escape character was added only for dsync internally. Normally there is no escape character configured. Change the mailbox names so that it doesn't rely on it. */ vname = dsync_mailbox_tree_name_unescape(info->ns, vname, alt_char); } node = dsync_mailbox_tree_get(tree, vname); if (node->ns == info->ns) ; else if (node->ns == NULL) { i_assert(tree->root.ns == NULL); node->ns = info->ns; } else { i_error("Mailbox '%s' exists in two namespaces: '%s' and '%s'", info->vname, node->ns->prefix, info->ns->prefix); return -1; } *node_r = node; return 0; } static int dsync_mailbox_tree_add_exists_node(struct dsync_mailbox_tree *tree, const struct mailbox_info *info, struct dsync_mailbox_node **node_r, char alt_char, enum mail_error *error_r) { if (dsync_mailbox_tree_add_node(tree, info, alt_char, node_r) < 0) { *error_r = MAIL_ERROR_TEMP; return -1; } (*node_r)->existence = DSYNC_MAILBOX_NODE_EXISTS; return 0; } static int dsync_mailbox_tree_get_selectable(struct mailbox *box, struct mailbox_metadata *metadata_r, struct mailbox_status *status_r) { /* try the fast path */ if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, metadata_r) < 0) return -1; if (mailbox_get_status(box, STATUS_UIDVALIDITY | STATUS_UIDNEXT, status_r) < 0) return -1; i_assert(!guid_128_is_empty(metadata_r->guid)); if (status_r->uidvalidity != 0) return 0; /* no UIDVALIDITY assigned yet. syncing a mailbox should add it. */ if (mailbox_sync(box, 0) < 0) return -1; if (mailbox_get_status(box, STATUS_UIDVALIDITY | STATUS_UIDNEXT, status_r) < 0) return -1; i_assert(status_r->uidvalidity != 0); return 0; } static int dsync_mailbox_tree_add(struct dsync_mailbox_tree *tree, const struct mailbox_info *info, const guid_128_t box_guid, char alt_char, enum mail_error *error_r) { struct dsync_mailbox_node *node; struct mailbox *box; enum mailbox_existence existence; struct mailbox_metadata metadata; struct mailbox_status status; const char *errstr; enum mail_error error; int ret = 0; if ((info->flags & MAILBOX_NONEXISTENT) != 0) return 0; if ((info->flags & MAILBOX_NOSELECT) != 0) { return !guid_128_is_empty(box_guid) ? 0 : dsync_mailbox_tree_add_exists_node( tree, info, &node, alt_char, error_r); } /* get GUID and UIDVALIDITY for selectable mailbox */ box = mailbox_alloc(info->ns->list, info->vname, MAILBOX_FLAG_READONLY); ret = mailbox_exists(box, FALSE, &existence); if (ret == 0 && existence != MAILBOX_EXISTENCE_SELECT) { /* autocreated mailbox doesn't exist yet */ mailbox_free(&box); if (existence == MAILBOX_EXISTENCE_NOSELECT) { return !guid_128_is_empty(box_guid) ? 0 : dsync_mailbox_tree_add_exists_node( tree, info, &node, alt_char, error_r); } else { return 0; } } if (ret == 0) ret = dsync_mailbox_tree_get_selectable(box, &metadata, &status); if (ret < 0) { errstr = mailbox_get_last_internal_error(box, &error); ret = 0; switch (error) { case MAIL_ERROR_NOTFOUND: /* mailbox was just deleted? */ break; case MAIL_ERROR_NOTPOSSIBLE: /* invalid mbox files? ignore */ break; default: i_error("Failed to access mailbox %s: %s", info->vname, errstr); *error_r = error; ret = -1; } mailbox_free(&box); return ret; } mailbox_free(&box); if (!guid_128_is_empty(box_guid) && !guid_128_equals(box_guid, metadata.guid)) { /* unwanted mailbox */ return 0; } if (dsync_mailbox_tree_add_exists_node( tree, info, &node, alt_char, error_r) < 0) return -1; memcpy(node->mailbox_guid, metadata.guid, sizeof(node->mailbox_guid)); node->uid_validity = status.uidvalidity; node->uid_next = status.uidnext; return 0; } static struct dsync_mailbox_node * dsync_mailbox_tree_find_sha(struct dsync_mailbox_tree *tree, struct mail_namespace *ns, const guid_128_t sha128) { struct dsync_mailbox_node *node; if (!hash_table_is_created(tree->name128_hash)) dsync_mailbox_tree_build_name128_hash(tree); node = hash_table_lookup(tree->name128_hash, sha128); return node == NULL || node->ns != ns ? NULL : node; } static int dsync_mailbox_tree_add_change_timestamps(struct dsync_mailbox_tree *tree, struct mail_namespace *ns) { struct dsync_mailbox_node *node; struct dsync_mailbox_delete *del; struct mailbox_log *log; struct mailbox_log_iter *iter; const struct mailbox_log_record *rec; const uint8_t *guid_p; time_t timestamp; log = mailbox_list_get_changelog(ns->list); if (log == NULL) return 0; iter = mailbox_log_iter_init(log); while ((rec = mailbox_log_iter_next(iter)) != NULL) { /* For DELETE_MAILBOX the record_guid is the mailbox GUID. Otherwise it's 128bit SHA1 of the mailbox vname. */ node = rec->type == MAILBOX_LOG_RECORD_DELETE_MAILBOX ? NULL : dsync_mailbox_tree_find_sha(tree, ns, rec->mailbox_guid); timestamp = mailbox_log_record_get_timestamp(rec); switch (rec->type) { case MAILBOX_LOG_RECORD_DELETE_MAILBOX: guid_p = rec->mailbox_guid; if (hash_table_lookup(tree->guid_hash, guid_p) != NULL) { /* mailbox still exists. maybe it was restored from backup or something. */ break; } del = array_append_space(&tree->deletes); del->type = DSYNC_MAILBOX_DELETE_TYPE_MAILBOX; del->timestamp = timestamp; memcpy(del->guid, rec->mailbox_guid, sizeof(del->guid)); break; case MAILBOX_LOG_RECORD_DELETE_DIR: if (node != NULL && node->existence == DSYNC_MAILBOX_NODE_EXISTS) { /* directory exists again, skip it */ break; } /* we don't know what directory name was deleted, just its hash. if the name still exists on the other dsync side, it can match this deletion to the name. */ del = array_append_space(&tree->deletes); del->type = DSYNC_MAILBOX_DELETE_TYPE_DIR; del->timestamp = timestamp; memcpy(del->guid, rec->mailbox_guid, sizeof(del->guid)); break; case MAILBOX_LOG_RECORD_CREATE_DIR: if (node == NULL) { /* directory has been deleted again, skip it */ break; } /* notify the remote that we want to keep this directory created (unless remote has a newer delete timestamp) */ node->last_renamed_or_created = timestamp; break; case MAILBOX_LOG_RECORD_RENAME: if (node != NULL) node->last_renamed_or_created = timestamp; break; case MAILBOX_LOG_RECORD_SUBSCRIBE: if (node != NULL) node->last_subscription_change = timestamp; break; case MAILBOX_LOG_RECORD_UNSUBSCRIBE: if (node != NULL) { node->last_subscription_change = timestamp; break; } /* The mailbox is already deleted, but it may still exist on the other side (even the subscription alone). */ del = array_append_space(&tree->deletes); del->type = DSYNC_MAILBOX_DELETE_TYPE_UNSUBSCRIBE; del->timestamp = timestamp; memcpy(del->guid, rec->mailbox_guid, sizeof(del->guid)); break; } } if (mailbox_log_iter_deinit(&iter) < 0) { i_error("Mailbox log iteration for namespace '%s' failed", ns->prefix); return -1; } return 0; } static int dsync_mailbox_tree_fix_guid_duplicate(struct dsync_mailbox_tree *tree, struct dsync_mailbox_node *node1, struct dsync_mailbox_node *node2) { struct mailbox *box; struct mailbox_update update; struct dsync_mailbox_node *change_node; const char *change_vname; int ret = 0; i_zero(&update); guid_128_generate(update.mailbox_guid); /* just in case the duplication exists in both sides, make them choose the same node */ if (strcmp(dsync_mailbox_node_get_full_name(tree, node1), dsync_mailbox_node_get_full_name(tree, node2)) <= 0) change_node = node1; else change_node = node2; change_vname = dsync_mailbox_node_get_full_name(tree, change_node); i_error("Duplicate mailbox GUID %s for mailboxes %s and %s - " "giving a new GUID %s to %s", guid_128_to_string(node1->mailbox_guid), dsync_mailbox_node_get_full_name(tree, node1), dsync_mailbox_node_get_full_name(tree, node2), guid_128_to_string(update.mailbox_guid), change_vname); i_assert(node1->ns != NULL && node2->ns != NULL); box = mailbox_alloc(change_node->ns->list, change_vname, 0); if (mailbox_update(box, &update) < 0) { i_error("Couldn't update mailbox %s GUID: %s", change_vname, mailbox_get_last_internal_error(box, NULL)); ret = -1; } else { memcpy(change_node->mailbox_guid, update.mailbox_guid, sizeof(change_node->mailbox_guid)); } mailbox_free(&box); return ret; } static bool dsync_mailbox_info_is_wanted(const struct mailbox_info *info, const char *box_name, const char *const *exclude_mailboxes) { const char *const *info_specialuses; unsigned int i; if (exclude_mailboxes == NULL && (box_name == NULL || box_name[0] != '\\')) return TRUE; info_specialuses = info->special_use == NULL ? NULL : t_strsplit(info->special_use, " "); /* include */ if (box_name != NULL && box_name[0] == '\\') { if (info_specialuses == NULL || !str_array_icase_find(info_specialuses, box_name)) return FALSE; } /* exclude */ if (exclude_mailboxes == NULL) return TRUE; for (i = 0; exclude_mailboxes[i] != NULL; i++) { const char *exclude = exclude_mailboxes[i]; if (exclude[0] == '\\') { /* special-use */ if (info_specialuses != NULL && str_array_icase_find(info_specialuses, exclude)) return FALSE; } else { /* mailbox with wildcards */ if (wildcard_match(info->vname, exclude)) return FALSE; } } return TRUE; } int dsync_mailbox_tree_fill(struct dsync_mailbox_tree *tree, struct mail_namespace *ns, const char *box_name, const guid_128_t box_guid, const char *const *exclude_mailboxes, char alt_char, enum mail_error *error_r) { const enum mailbox_list_iter_flags list_flags = /* FIXME: we'll skip symlinks, because we can't handle them currently. in future we could detect them and create them by creating the symlink. */ MAILBOX_LIST_ITER_SKIP_ALIASES | MAILBOX_LIST_ITER_NO_AUTO_BOXES; const enum mailbox_list_iter_flags subs_list_flags = MAILBOX_LIST_ITER_NO_AUTO_BOXES | MAILBOX_LIST_ITER_SELECT_SUBSCRIBED | MAILBOX_LIST_ITER_RETURN_NO_FLAGS; struct mailbox_list_iterate_context *iter; struct dsync_mailbox_node *node, *dup_node1, *dup_node2; const struct mailbox_info *info; const char *list_pattern = box_name != NULL && box_name[0] != '\\' ? box_name : "*"; int ret = 0; i_assert(mail_namespace_get_sep(ns) == tree->sep); /* assign namespace to its root, so it gets copied to children */ if (ns->prefix_len > 0) { const char *vname = t_strndup(ns->prefix, ns->prefix_len-1); node = dsync_mailbox_tree_get(tree, vname); node->ns = ns; struct mailbox_info ns_info = { .vname = vname, .ns = ns, }; if (dsync_mailbox_tree_add( tree, &ns_info, box_guid, alt_char, error_r) < 0) return -1; } else { tree->root.ns = ns; } /* first add all of the existing mailboxes */ iter = mailbox_list_iter_init(ns->list, list_pattern, list_flags); while ((info = mailbox_list_iter_next(iter)) != NULL) T_BEGIN { if (dsync_mailbox_info_is_wanted(info, box_name, exclude_mailboxes)) { if (dsync_mailbox_tree_add( tree, info, box_guid, alt_char, error_r) < 0) ret = -1; } } T_END; if (mailbox_list_iter_deinit(&iter) < 0) { i_error("Mailbox listing for namespace '%s' failed: %s", ns->prefix, mailbox_list_get_last_internal_error(ns->list, error_r)); ret = -1; } /* add subscriptions */ iter = mailbox_list_iter_init(ns->list, list_pattern, subs_list_flags); while ((info = mailbox_list_iter_next(iter)) != NULL) { if (dsync_mailbox_tree_add_node(tree, info, alt_char, &node) == 0) node->subscribed = TRUE; else { *error_r = MAIL_ERROR_TEMP; ret = -1; } } if (mailbox_list_iter_deinit(&iter) < 0) { i_error("Mailbox listing for namespace '%s' failed: %s", ns->prefix, mailbox_list_get_last_internal_error(ns->list, error_r)); ret = -1; } if (ret < 0) return -1; while (dsync_mailbox_tree_build_guid_hash(tree, &dup_node1, &dup_node2) < 0) { if (dsync_mailbox_tree_fix_guid_duplicate(tree, dup_node1, dup_node2) < 0) return -1; } /* add timestamps */ if (dsync_mailbox_tree_add_change_timestamps(tree, ns) < 0) return -1; return 0; } dovecot-2.3.21.1/src/doveadm/dsync/dsync-brain-mailbox.c0000644000000000000000000007150014656633576017654 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "hash.h" #include "mail-cache-private.h" #include "mail-namespace.h" #include "mail-storage-private.h" #include "dsync-ibc.h" #include "dsync-mailbox-tree.h" #include "dsync-mailbox-import.h" #include "dsync-mailbox-export.h" #include "dsync-transaction-log-scan.h" #include "dsync-brain-private.h" static int ns_mailbox_try_alloc(struct dsync_brain *brain, struct mail_namespace *ns, const guid_128_t guid, struct mailbox **box_r, const char **errstr_r, enum mail_error *error_r) { enum mailbox_flags flags = 0; struct mailbox *box; enum mailbox_existence existence; int ret; if (brain->backup_send) { /* make sure mailbox isn't modified */ flags |= MAILBOX_FLAG_READONLY; } box = mailbox_alloc_guid(ns->list, guid, flags); ret = mailbox_exists(box, FALSE, &existence); if (ret < 0) { *errstr_r = mailbox_get_last_internal_error(box, error_r); mailbox_free(&box); return -1; } if (existence != MAILBOX_EXISTENCE_SELECT) { mailbox_free(&box); *errstr_r = existence == MAILBOX_EXISTENCE_NONE ? "Mailbox was already deleted" : "Mailbox is no longer selectable"; return 0; } *box_r = box; return 1; } int dsync_brain_mailbox_alloc(struct dsync_brain *brain, const guid_128_t guid, struct mailbox **box_r, const char **errstr_r, enum mail_error *error_r) { struct mail_namespace *ns; int ret; *box_r = NULL; for (ns = brain->user->namespaces; ns != NULL; ns = ns->next) { if (!dsync_brain_want_namespace(brain, ns)) continue; if ((ret = ns_mailbox_try_alloc(brain, ns, guid, box_r, errstr_r, error_r)) != 0) return ret; } return 0; } static void dsync_mailbox_cache_field_dup(ARRAY_TYPE(mailbox_cache_field) *dest, const ARRAY_TYPE(mailbox_cache_field) *src, pool_t pool) { const struct mailbox_cache_field *src_field; struct mailbox_cache_field *dest_field; p_array_init(dest, pool, array_count(src)); array_foreach(src, src_field) { dest_field = array_append_space(dest); dest_field->name = p_strdup(pool, src_field->name); dest_field->decision = src_field->decision; dest_field->last_used = src_field->last_used; } } static const struct dsync_mailbox_state * dsync_mailbox_state_find(struct dsync_brain *brain, const guid_128_t mailbox_guid) { const uint8_t *guid_p; guid_p = mailbox_guid; return hash_table_lookup(brain->mailbox_states, guid_p); } static void dsync_mailbox_state_remove(struct dsync_brain *brain, const guid_128_t mailbox_guid) { const uint8_t *guid_p; guid_p = mailbox_guid; if (hash_table_lookup(brain->mailbox_states, guid_p) != NULL) hash_table_remove(brain->mailbox_states, guid_p); } void dsync_brain_sync_init_box_states(struct dsync_brain *brain) { if (brain->backup_send) { /* we have an exporter, but no importer. */ brain->box_send_state = DSYNC_BOX_STATE_ATTRIBUTES; brain->box_recv_state = brain->mail_requests ? DSYNC_BOX_STATE_MAIL_REQUESTS : DSYNC_BOX_STATE_RECV_LAST_COMMON; } else if (brain->backup_recv) { /* we have an importer, but no exporter */ brain->box_send_state = brain->mail_requests ? DSYNC_BOX_STATE_MAIL_REQUESTS : DSYNC_BOX_STATE_DONE; brain->box_recv_state = DSYNC_BOX_STATE_ATTRIBUTES; } else { brain->box_send_state = DSYNC_BOX_STATE_ATTRIBUTES; brain->box_recv_state = DSYNC_BOX_STATE_ATTRIBUTES; } } static void dsync_brain_sync_mailbox_init(struct dsync_brain *brain, struct mailbox *box, struct file_lock *lock, const struct dsync_mailbox *local_dsync_box, bool wait_for_remote_box) { const struct dsync_mailbox_state *state; i_assert(brain->box_importer == NULL); i_assert(brain->box_exporter == NULL); i_assert(box->synced); brain->box = box; brain->box_lock = lock; brain->pre_box_state = brain->state; if (wait_for_remote_box) { brain->box_send_state = DSYNC_BOX_STATE_MAILBOX; brain->box_recv_state = DSYNC_BOX_STATE_MAILBOX; } else { dsync_brain_sync_init_box_states(brain); } brain->local_dsync_box = *local_dsync_box; if (brain->dsync_box_pool != NULL) p_clear(brain->dsync_box_pool); else { brain->dsync_box_pool = pool_alloconly_create(MEMPOOL_GROWING"dsync brain box pool", 2048); } dsync_mailbox_cache_field_dup(&brain->local_dsync_box.cache_fields, &local_dsync_box->cache_fields, brain->dsync_box_pool); i_zero(&brain->remote_dsync_box); state = dsync_mailbox_state_find(brain, local_dsync_box->mailbox_guid); if (state != NULL) brain->mailbox_state = *state; else { i_zero(&brain->mailbox_state); memcpy(brain->mailbox_state.mailbox_guid, local_dsync_box->mailbox_guid, sizeof(brain->mailbox_state.mailbox_guid)); brain->mailbox_state.last_uidvalidity = local_dsync_box->uid_validity; } } static void dsync_brain_sync_mailbox_init_remote(struct dsync_brain *brain, const struct dsync_mailbox *remote_dsync_box) { enum dsync_mailbox_import_flags import_flags = 0; const struct dsync_mailbox_state *state; uint32_t last_common_uid; uint64_t last_common_modseq, last_common_pvt_modseq; i_assert(brain->box_importer == NULL); i_assert(brain->log_scan != NULL); i_assert(memcmp(brain->local_dsync_box.mailbox_guid, remote_dsync_box->mailbox_guid, sizeof(remote_dsync_box->mailbox_guid)) == 0); brain->remote_dsync_box = *remote_dsync_box; dsync_mailbox_cache_field_dup(&brain->remote_dsync_box.cache_fields, &remote_dsync_box->cache_fields, brain->dsync_box_pool); state = dsync_mailbox_state_find(brain, remote_dsync_box->mailbox_guid); if (state != NULL) { last_common_uid = state->last_common_uid; last_common_modseq = state->last_common_modseq; last_common_pvt_modseq = state->last_common_pvt_modseq; } else { last_common_uid = 0; last_common_modseq = 0; last_common_pvt_modseq = 0; } if (brain->mail_requests) import_flags |= DSYNC_MAILBOX_IMPORT_FLAG_WANT_MAIL_REQUESTS; if (brain->master_brain) import_flags |= DSYNC_MAILBOX_IMPORT_FLAG_MASTER_BRAIN; if (brain->backup_recv && !brain->no_backup_overwrite) import_flags |= DSYNC_MAILBOX_IMPORT_FLAG_REVERT_LOCAL_CHANGES; if (brain->debug) import_flags |= DSYNC_MAILBOX_IMPORT_FLAG_DEBUG; if (brain->local_dsync_box.have_save_guids && (remote_dsync_box->have_save_guids || (brain->backup_recv && remote_dsync_box->have_guids))) import_flags |= DSYNC_MAILBOX_IMPORT_FLAG_MAILS_HAVE_GUIDS; if (brain->local_dsync_box.have_only_guid128 || remote_dsync_box->have_only_guid128) import_flags |= DSYNC_MAILBOX_IMPORT_FLAG_MAILS_USE_GUID128; if (brain->no_notify) import_flags |= DSYNC_MAILBOX_IMPORT_FLAG_NO_NOTIFY; if (brain->empty_hdr_workaround) import_flags |= DSYNC_MAILBOX_IMPORT_FLAG_EMPTY_HDR_WORKAROUND; if (brain->no_header_hashes) import_flags |= DSYNC_MAILBOX_IMPORT_FLAG_NO_HEADER_HASHES; brain->box_importer = brain->backup_send ? NULL : dsync_mailbox_import_init(brain->box, brain->virtual_all_box, brain->log_scan, last_common_uid, last_common_modseq, last_common_pvt_modseq, remote_dsync_box->uid_next, remote_dsync_box->first_recent_uid, remote_dsync_box->highest_modseq, remote_dsync_box->highest_pvt_modseq, brain->sync_since_timestamp, brain->sync_until_timestamp, brain->sync_max_size, brain->sync_flag, brain->import_commit_msgs_interval, import_flags, brain->hdr_hash_version, brain->hashed_headers); } int dsync_brain_sync_mailbox_open(struct dsync_brain *brain, const struct dsync_mailbox *remote_dsync_box) { struct mailbox_status status; enum dsync_mailbox_exporter_flags exporter_flags = 0; uint32_t last_common_uid, highest_wanted_uid; uint64_t last_common_modseq, last_common_pvt_modseq; const char *desync_reason = ""; bool pvt_too_old; int ret; i_assert(brain->log_scan == NULL); i_assert(brain->box_exporter == NULL); last_common_uid = brain->mailbox_state.last_common_uid; last_common_modseq = brain->mailbox_state.last_common_modseq; last_common_pvt_modseq = brain->mailbox_state.last_common_pvt_modseq; highest_wanted_uid = last_common_uid == 0 ? (uint32_t)-1 : last_common_uid; ret = dsync_transaction_log_scan_init(brain->box->view, brain->box->view_pvt, highest_wanted_uid, last_common_modseq, last_common_pvt_modseq, &brain->log_scan, &pvt_too_old); if (ret < 0) { i_error("Failed to read transaction log for mailbox %s", mailbox_get_vname(brain->box)); brain->failed = TRUE; return -1; } mailbox_get_open_status(brain->box, STATUS_UIDNEXT | STATUS_HIGHESTMODSEQ | STATUS_HIGHESTPVTMODSEQ, &status); if (status.nonpermanent_modseqs) status.highest_modseq = 0; if (ret == 0) { if (pvt_too_old) { desync_reason = t_strdup_printf( "Private modseq %"PRIu64" no longer in transaction log " "(highest=%"PRIu64", last_common_uid=%u, nextuid=%u)", last_common_pvt_modseq, status.highest_pvt_modseq, last_common_uid, status.uidnext); } else { desync_reason = t_strdup_printf( "Modseq %"PRIu64" no longer in transaction log " "(highest=%"PRIu64", last_common_uid=%u, nextuid=%u)", last_common_modseq, status.highest_modseq, last_common_uid, status.uidnext); } } if (last_common_uid != 0) { /* if last_common_* is higher than our current ones it means that the incremental sync state is stale and we need to do a full resync */ if (status.uidnext < last_common_uid) { desync_reason = t_strdup_printf("uidnext %u < %u", status.uidnext, last_common_uid); ret = 0; } else if (status.highest_modseq < last_common_modseq) { desync_reason = t_strdup_printf("highest_modseq %"PRIu64" < %"PRIu64, status.highest_modseq, last_common_modseq); ret = 0; } else if (status.highest_pvt_modseq < last_common_pvt_modseq) { desync_reason = t_strdup_printf("highest_pvt_modseq %"PRIu64" < %"PRIu64, status.highest_pvt_modseq, last_common_pvt_modseq); ret = 0; } } if (ret == 0) { i_warning("Failed to do incremental sync for mailbox %s, " "retry with a full sync (%s)", mailbox_get_vname(brain->box), desync_reason); dsync_brain_set_changes_during_sync(brain, t_strdup_printf( "Incremental sync failed: %s", desync_reason)); brain->require_full_resync = TRUE; return 0; } if (!brain->mail_requests) exporter_flags |= DSYNC_MAILBOX_EXPORTER_FLAG_AUTO_EXPORT_MAILS; if (remote_dsync_box->have_save_guids && (brain->local_dsync_box.have_save_guids || (brain->backup_send && brain->local_dsync_box.have_guids))) exporter_flags |= DSYNC_MAILBOX_EXPORTER_FLAG_MAILS_HAVE_GUIDS; if (brain->no_mail_prefetch) exporter_flags |= DSYNC_MAILBOX_EXPORTER_FLAG_MINIMAL_DMAIL_FILL; if (brain->sync_since_timestamp > 0 || brain->sync_until_timestamp > 0) exporter_flags |= DSYNC_MAILBOX_EXPORTER_FLAG_TIMESTAMPS; if (brain->sync_max_size > 0) exporter_flags |= DSYNC_MAILBOX_EXPORTER_FLAG_VSIZES; if (remote_dsync_box->messages_count == 0 || brain->no_header_hashes) { /* remote mailbox is empty - we don't really need to export header hashes since they're not going to match anything anyway. */ exporter_flags |= DSYNC_MAILBOX_EXPORTER_FLAG_NO_HDR_HASHES; } brain->box_exporter = brain->backup_recv ? NULL : dsync_mailbox_export_init(brain->box, brain->log_scan, last_common_uid, exporter_flags, brain->hdr_hash_version, brain->hashed_headers); dsync_brain_sync_mailbox_init_remote(brain, remote_dsync_box); return 1; } void dsync_brain_sync_mailbox_deinit(struct dsync_brain *brain) { enum mail_error error; i_assert(brain->box != NULL); array_push_back(&brain->remote_mailbox_states, &brain->mailbox_state); if (brain->box_exporter != NULL) { const char *errstr; i_assert(brain->failed || brain->require_full_resync || brain->sync_type == DSYNC_BRAIN_SYNC_TYPE_CHANGED); if (dsync_mailbox_export_deinit(&brain->box_exporter, &errstr, &error) < 0) i_error("Mailbox export failed: %s", errstr); } if (brain->box_importer != NULL) { uint32_t last_common_uid, last_messages_count; uint64_t last_common_modseq, last_common_pvt_modseq; const char *changes_during_sync; bool require_full_resync; i_assert(brain->failed); (void)dsync_mailbox_import_deinit(&brain->box_importer, FALSE, &last_common_uid, &last_common_modseq, &last_common_pvt_modseq, &last_messages_count, &changes_during_sync, &require_full_resync, &brain->mail_error); if (require_full_resync) brain->require_full_resync = TRUE; } if (brain->log_scan != NULL) dsync_transaction_log_scan_deinit(&brain->log_scan); file_lock_free(&brain->box_lock); mailbox_free(&brain->box); brain->state = brain->pre_box_state; } static int dsync_box_get(struct mailbox *box, struct dsync_mailbox *dsync_box_r, enum mail_error *error_r) { const enum mailbox_status_items status_items = STATUS_UIDVALIDITY | STATUS_UIDNEXT | STATUS_MESSAGES | STATUS_FIRST_RECENT_UID | STATUS_HIGHESTMODSEQ | STATUS_HIGHESTPVTMODSEQ; const enum mailbox_metadata_items metadata_items = MAILBOX_METADATA_CACHE_FIELDS | MAILBOX_METADATA_GUID; struct mailbox_status status; struct mailbox_metadata metadata; const char *errstr; enum mail_error error; /* get metadata first, since it may autocreate the mailbox */ if (mailbox_get_metadata(box, metadata_items, &metadata) < 0 || mailbox_get_status(box, status_items, &status) < 0) { errstr = mailbox_get_last_internal_error(box, &error); if (error == MAIL_ERROR_NOTFOUND || error == MAIL_ERROR_NOTPOSSIBLE) { /* Mailbox isn't selectable, try the next one. We should have already caught \Noselect mailboxes, but check them anyway here. The NOTPOSSIBLE check is mainly for invalid mbox files. */ return 0; } i_error("Failed to access mailbox %s: %s", mailbox_get_vname(box), errstr); *error_r = error; return -1; } if (status.nonpermanent_modseqs) status.highest_modseq = 0; i_assert(status.uidvalidity != 0 || status.messages == 0); i_zero(dsync_box_r); memcpy(dsync_box_r->mailbox_guid, metadata.guid, sizeof(dsync_box_r->mailbox_guid)); dsync_box_r->uid_validity = status.uidvalidity; dsync_box_r->uid_next = status.uidnext; dsync_box_r->messages_count = status.messages; dsync_box_r->first_recent_uid = status.first_recent_uid; dsync_box_r->highest_modseq = status.highest_modseq; dsync_box_r->highest_pvt_modseq = status.highest_pvt_modseq; dsync_mailbox_cache_field_dup(&dsync_box_r->cache_fields, metadata.cache_fields, pool_datastack_create()); dsync_box_r->have_guids = status.have_guids; dsync_box_r->have_save_guids = status.have_save_guids; dsync_box_r->have_only_guid128 = status.have_only_guid128; return 1; } static bool dsync_brain_has_mailbox_state_changed(struct dsync_brain *brain, const struct dsync_mailbox *dsync_box) { const struct dsync_mailbox_state *state; if (brain->sync_type != DSYNC_BRAIN_SYNC_TYPE_STATE) return TRUE; state = dsync_mailbox_state_find(brain, dsync_box->mailbox_guid); return state == NULL || state->last_uidvalidity != dsync_box->uid_validity || state->last_common_uid+1 != dsync_box->uid_next || state->last_common_modseq != dsync_box->highest_modseq || state->last_common_pvt_modseq != dsync_box->highest_pvt_modseq || state->last_messages_count != dsync_box->messages_count; } static int dsync_brain_try_next_mailbox(struct dsync_brain *brain, struct mailbox **box_r, struct file_lock **lock_r, struct dsync_mailbox *dsync_box_r) { enum mailbox_flags flags = 0; struct dsync_mailbox dsync_box; struct mailbox *box; struct file_lock *lock = NULL; struct dsync_mailbox_node *node; const char *vname = NULL; enum mail_error error; bool synced = FALSE; int ret; *box_r = NULL; while (dsync_mailbox_tree_iter_next(brain->local_tree_iter, &vname, &node)) { if (node->existence == DSYNC_MAILBOX_NODE_EXISTS && !guid_128_is_empty(node->mailbox_guid)) break; vname = NULL; } if (vname == NULL) { /* no more mailboxes */ dsync_mailbox_tree_iter_deinit(&brain->local_tree_iter); return -1; } if (brain->backup_send) { /* make sure mailbox isn't modified */ flags |= MAILBOX_FLAG_READONLY; } box = mailbox_alloc(node->ns->list, vname, flags); for (;;) { if ((ret = dsync_box_get(box, &dsync_box, &error)) <= 0) { if (ret < 0) { brain->mail_error = error; brain->failed = TRUE; } mailbox_free(&box); file_lock_free(&lock); return ret; } /* if mailbox's last_common_* state equals the current state, we can skip the mailbox */ if (!dsync_brain_has_mailbox_state_changed(brain, &dsync_box)) { if (brain->debug) { i_debug("brain %c: Skipping mailbox %s with unchanged state " "uidvalidity=%u uidnext=%u highestmodseq=%"PRIu64" highestpvtmodseq=%"PRIu64" messages=%u", brain->master_brain ? 'M' : 'S', guid_128_to_string(dsync_box.mailbox_guid), dsync_box.uid_validity, dsync_box.uid_next, dsync_box.highest_modseq, dsync_box.highest_pvt_modseq, dsync_box.messages_count); } mailbox_free(&box); file_lock_free(&lock); return 0; } if (synced) { /* ok, the mailbox really changed */ break; } /* mailbox appears to have changed. do a full sync here and get the state again. Lock before syncing. */ if (dsync_mailbox_lock(brain, box, &lock) < 0) { brain->failed = TRUE; mailbox_free(&box); return -1; } if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ) < 0) { i_error("Can't sync mailbox %s: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, &brain->mail_error)); brain->failed = TRUE; mailbox_free(&box); file_lock_free(&lock); return -1; } synced = TRUE; } *box_r = box; *lock_r = lock; *dsync_box_r = dsync_box; return 1; } static bool dsync_brain_next_mailbox(struct dsync_brain *brain, struct mailbox **box_r, struct file_lock **lock_r, struct dsync_mailbox *dsync_box_r) { int ret; if (brain->no_mail_sync) return FALSE; while ((ret = dsync_brain_try_next_mailbox(brain, box_r, lock_r, dsync_box_r)) == 0) ; return ret > 0; } void dsync_brain_master_send_mailbox(struct dsync_brain *brain) { struct dsync_mailbox dsync_box; struct mailbox *box; struct file_lock *lock; i_assert(brain->master_brain); i_assert(brain->box == NULL); if (!dsync_brain_next_mailbox(brain, &box, &lock, &dsync_box)) { brain->state = DSYNC_STATE_FINISH; dsync_ibc_send_end_of_list(brain->ibc, DSYNC_IBC_EOL_MAILBOX); return; } /* start exporting this mailbox (wait for remote to start importing) */ dsync_ibc_send_mailbox(brain->ibc, &dsync_box); dsync_brain_sync_mailbox_init(brain, box, lock, &dsync_box, TRUE); brain->state = DSYNC_STATE_SYNC_MAILS; } bool dsync_boxes_need_sync(struct dsync_brain *brain, const struct dsync_mailbox *box1, const struct dsync_mailbox *box2, const char **reason_r) { if (brain->no_mail_sync) return FALSE; if (brain->sync_type != DSYNC_BRAIN_SYNC_TYPE_CHANGED) { *reason_r = "Full sync"; return TRUE; } if (box1->uid_validity != box2->uid_validity) *reason_r = t_strdup_printf("UIDVALIDITY changed: %u -> %u", box1->uid_validity, box2->uid_validity); else if (box1->uid_next != box2->uid_next) *reason_r = t_strdup_printf("UIDNEXT changed: %u -> %u", box1->uid_next, box2->uid_next); else if (box1->messages_count != box2->messages_count) *reason_r = t_strdup_printf("Message count changed: %u -> %u", box1->messages_count, box2->messages_count); else if (box1->highest_modseq != box2->highest_modseq) { *reason_r = t_strdup_printf("HIGHESTMODSEQ changed %" PRIu64" -> %"PRIu64, box1->highest_modseq, box2->highest_modseq); if (box1->highest_modseq == 0 || box2->highest_modseq == 0) { *reason_r = t_strdup_printf( "%s (Permanent MODSEQs aren't supported)", *reason_r); } } else if (box1->highest_pvt_modseq != box2->highest_pvt_modseq) *reason_r = t_strdup_printf("Private HIGHESTMODSEQ changed %" PRIu64" -> %"PRIu64, box1->highest_pvt_modseq, box2->highest_pvt_modseq); else if (box1->first_recent_uid != box2->first_recent_uid) *reason_r = t_strdup_printf("First RECENT UID changed: %u -> %u", box1->first_recent_uid, box2->first_recent_uid); else return FALSE; return TRUE; } static int mailbox_cache_field_name_cmp(const struct mailbox_cache_field *f1, const struct mailbox_cache_field *f2) { return strcmp(f1->name, f2->name); } static void dsync_cache_fields_update(const struct dsync_mailbox *local_box, const struct dsync_mailbox *remote_box, struct mailbox *box, struct mailbox_update *update) { ARRAY_TYPE(mailbox_cache_field) local_sorted, remote_sorted, changes; const struct mailbox_cache_field *local_fields, *remote_fields; unsigned int li, ri, local_count, remote_count; time_t drop_older_timestamp; int ret; if (array_count(&remote_box->cache_fields) == 0) { /* remote has no cached fields. there's nothing to update. */ return; } t_array_init(&local_sorted, array_count(&local_box->cache_fields)); t_array_init(&remote_sorted, array_count(&remote_box->cache_fields)); array_append_array(&local_sorted, &local_box->cache_fields); array_append_array(&remote_sorted, &remote_box->cache_fields); array_sort(&local_sorted, mailbox_cache_field_name_cmp); array_sort(&remote_sorted, mailbox_cache_field_name_cmp); if (array_count(&local_sorted) == 0) { /* local has no cached fields. set them to same as remote. */ array_append_zero(&remote_sorted); update->cache_updates = array_front(&remote_sorted); return; } /* figure out what to change */ local_fields = array_get(&local_sorted, &local_count); remote_fields = array_get(&remote_sorted, &remote_count); t_array_init(&changes, local_count + remote_count); drop_older_timestamp = ioloop_time - box->index->optimization_set.cache.unaccessed_field_drop_secs; for (li = ri = 0; li < local_count || ri < remote_count; ) { ret = li == local_count ? 1 : ri == remote_count ? -1 : strcmp(local_fields[li].name, remote_fields[ri].name); if (ret == 0) { /* field exists in both local and remote */ const struct mailbox_cache_field *lf = &local_fields[li]; const struct mailbox_cache_field *rf = &remote_fields[ri]; if (lf->last_used > rf->last_used || (lf->last_used == rf->last_used && lf->decision > rf->decision)) { /* use local decision and timestamp */ } else { array_push_back(&changes, rf); } li++; ri++; } else if (ret < 0) { /* remote field doesn't exist */ li++; } else { /* local field doesn't exist */ if (remote_fields[ri].last_used < drop_older_timestamp) { /* field hasn't be used for a long time, remote will probably drop this soon as well */ } else { array_push_back(&changes, &remote_fields[ri]); } ri++; } } i_assert(li == local_count && ri == remote_count); if (array_count(&changes) > 0) { array_append_zero(&changes); update->cache_updates = array_front(&changes); } } bool dsync_brain_mailbox_update_pre(struct dsync_brain *brain, struct mailbox *box, const struct dsync_mailbox *local_box, const struct dsync_mailbox *remote_box, const char **reason_r) { struct mailbox_update update; const struct dsync_mailbox_state *state; bool ret = TRUE; *reason_r = NULL; i_zero(&update); if (local_box->uid_validity != remote_box->uid_validity) { /* Keep the UIDVALIDITY for the mailbox that has more messages. If they equal, use the higher UIDVALIDITY. */ if (remote_box->messages_count > local_box->messages_count || (remote_box->messages_count == local_box->messages_count && remote_box->uid_validity > local_box->uid_validity)) update.uid_validity = remote_box->uid_validity; state = dsync_mailbox_state_find(brain, local_box->mailbox_guid); if (state != NULL && state->last_common_uid > 0) { /* we can't continue syncing this mailbox in this session, because the other side already started sending mailbox changes, but not for all mails. */ dsync_mailbox_state_remove(brain, local_box->mailbox_guid); *reason_r = "UIDVALIDITY changed during a stateful sync, need to restart"; brain->failed = TRUE; ret = FALSE; } } dsync_cache_fields_update(local_box, remote_box, box, &update); if (update.uid_validity == 0 && update.cache_updates == NULL) { /* no changes */ return ret; } if (mailbox_update(box, &update) < 0) { i_error("Couldn't update mailbox %s metadata: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, &brain->mail_error)); brain->failed = TRUE; } return ret; } static void dsync_brain_slave_send_mailbox_lost(struct dsync_brain *brain, const struct dsync_mailbox *dsync_box, bool ignore) { struct dsync_mailbox delete_box; if (brain->debug) { i_debug("brain %c: We don't have mailbox %s", brain->master_brain ? 'M' : 'S', guid_128_to_string(dsync_box->mailbox_guid)); } i_zero(&delete_box); memcpy(delete_box.mailbox_guid, dsync_box->mailbox_guid, sizeof(delete_box.mailbox_guid)); t_array_init(&delete_box.cache_fields, 0); if (ignore) delete_box.mailbox_ignore = TRUE; else delete_box.mailbox_lost = TRUE; dsync_ibc_send_mailbox(brain->ibc, &delete_box); } bool dsync_brain_slave_recv_mailbox(struct dsync_brain *brain) { const struct dsync_mailbox *dsync_box; struct dsync_mailbox local_dsync_box; struct mailbox *box; struct file_lock *lock; const char *errstr, *resync_reason, *reason; enum mail_error error; int ret; bool resync; i_assert(!brain->master_brain); i_assert(brain->box == NULL); if ((ret = dsync_ibc_recv_mailbox(brain->ibc, &dsync_box)) == 0) return FALSE; if (ret < 0) { brain->state = DSYNC_STATE_FINISH; return TRUE; } if (dsync_brain_mailbox_alloc(brain, dsync_box->mailbox_guid, &box, &errstr, &error) < 0) { i_error("Couldn't allocate mailbox GUID %s: %s", guid_128_to_string(dsync_box->mailbox_guid), errstr); brain->mail_error = error; brain->failed = TRUE; return TRUE; } if (box == NULL) { /* mailbox was probably deleted/renamed during sync */ if (brain->backup_send && brain->no_backup_overwrite) { if (brain->debug) { i_debug("brain %c: Ignore nonexistent " "mailbox GUID %s with -1 sync", brain->master_brain ? 'M' : 'S', guid_128_to_string(dsync_box->mailbox_guid)); } dsync_brain_slave_send_mailbox_lost(brain, dsync_box, TRUE); return TRUE; } //FIXME: verify this from log, and if not log an error. dsync_brain_set_changes_during_sync(brain, t_strdup_printf( "Mailbox GUID %s was lost", guid_128_to_string(dsync_box->mailbox_guid))); dsync_brain_slave_send_mailbox_lost(brain, dsync_box, FALSE); return TRUE; } /* Lock before syncing */ if (dsync_mailbox_lock(brain, box, &lock) < 0) { mailbox_free(&box); brain->failed = TRUE; return TRUE; } if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ) < 0) { i_error("Can't sync mailbox %s: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, &brain->mail_error)); file_lock_free(&lock); mailbox_free(&box); brain->failed = TRUE; return TRUE; } if ((ret = dsync_box_get(box, &local_dsync_box, &error)) <= 0) { file_lock_free(&lock); mailbox_free(&box); if (ret < 0) { brain->mail_error = error; brain->failed = TRUE; return TRUE; } /* another process just deleted this mailbox? */ if (brain->debug) { i_debug("brain %c: Skipping lost mailbox %s", brain->master_brain ? 'M' : 'S', guid_128_to_string(dsync_box->mailbox_guid)); } dsync_brain_slave_send_mailbox_lost(brain, dsync_box, FALSE); return TRUE; } i_assert(local_dsync_box.uid_validity != 0); i_assert(memcmp(dsync_box->mailbox_guid, local_dsync_box.mailbox_guid, sizeof(dsync_box->mailbox_guid)) == 0); resync = !dsync_brain_mailbox_update_pre(brain, box, &local_dsync_box, dsync_box, &resync_reason); if (!dsync_boxes_need_sync(brain, &local_dsync_box, dsync_box, &reason)) { /* no fields appear to have changed, skip this mailbox */ if (brain->debug) { i_debug("brain %c: Skipping unchanged mailbox %s", brain->master_brain ? 'M' : 'S', guid_128_to_string(dsync_box->mailbox_guid)); } dsync_ibc_send_mailbox(brain->ibc, &local_dsync_box); file_lock_free(&lock); mailbox_free(&box); return TRUE; } if (brain->debug) { i_debug("brain %c: Syncing mailbox %s: %s", brain->master_brain ? 'M' : 'S', guid_128_to_string(dsync_box->mailbox_guid), reason); } /* start export/import */ dsync_brain_sync_mailbox_init(brain, box, lock, &local_dsync_box, FALSE); if ((ret = dsync_brain_sync_mailbox_open(brain, dsync_box)) < 0) return TRUE; if (resync) dsync_brain_set_changes_during_sync(brain, resync_reason); if (ret == 0 || resync) { brain->require_full_resync = TRUE; dsync_brain_sync_mailbox_deinit(brain); dsync_brain_slave_send_mailbox_lost(brain, dsync_box, FALSE); return TRUE; } dsync_ibc_send_mailbox(brain->ibc, &local_dsync_box); brain->state = DSYNC_STATE_SYNC_MAILS; return TRUE; } dovecot-2.3.21.1/src/doveadm/dsync/Makefile.in0000644000000000000000000010600014656633607015700 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ pkglibexecdir = $(libexecdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ noinst_PROGRAMS = $(am__EXEEXT_1) subdir = src/doveadm/dsync ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(pkginc_lib_HEADERS) $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__EXEEXT_1 = test-dsync-mailbox-tree-sync$(EXEEXT) PROGRAMS = $(noinst_PROGRAMS) am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ *) f=$$p;; \ esac; am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; am__install_max = 40 am__nobase_strip_setup = \ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` am__nobase_strip = \ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" am__nobase_list = $(am__nobase_strip_setup); \ for p in $$list; do echo "$$p $$p"; done | \ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ if (++n[$$2] == $(am__install_max)) \ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ END { for (dir in files) print dir, files[dir] }' am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' am__uninstall_files_from_dir = { \ test -z "$$files" \ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ $(am__cd) "$$dir" && rm -f $$files; }; \ } am__installdirs = "$(DESTDIR)$(pkglibdir)" \ "$(DESTDIR)$(pkginc_libdir)" LTLIBRARIES = $(noinst_LTLIBRARIES) $(pkglib_LTLIBRARIES) am_libdovecot_dsync_la_OBJECTS = libdovecot_dsync_la_OBJECTS = $(am_libdovecot_dsync_la_OBJECTS) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = libdovecot_dsync_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(libdovecot_dsync_la_LDFLAGS) \ $(LDFLAGS) -o $@ libdsync_la_LIBADD = am_libdsync_la_OBJECTS = dsync-brain.lo dsync-brain-mailbox.lo \ dsync-brain-mailbox-tree.lo dsync-brain-mailbox-tree-sync.lo \ dsync-brain-mails.lo dsync-deserializer.lo dsync-mail.lo \ dsync-mailbox.lo dsync-mailbox-import.lo \ dsync-mailbox-export.lo dsync-mailbox-state.lo \ dsync-mailbox-tree.lo dsync-mailbox-tree-fill.lo \ dsync-mailbox-tree-sync.lo dsync-serializer.lo dsync-ibc.lo \ dsync-ibc-stream.lo dsync-ibc-pipe.lo \ dsync-transaction-log-scan.lo libdsync_la_OBJECTS = $(am_libdsync_la_OBJECTS) am_test_dsync_mailbox_tree_sync_OBJECTS = \ test-dsync-mailbox-tree-sync.$(OBJEXT) test_dsync_mailbox_tree_sync_OBJECTS = \ $(am_test_dsync_mailbox_tree_sync_OBJECTS) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/dsync-brain-mailbox-tree-sync.Plo \ ./$(DEPDIR)/dsync-brain-mailbox-tree.Plo \ ./$(DEPDIR)/dsync-brain-mailbox.Plo \ ./$(DEPDIR)/dsync-brain-mails.Plo ./$(DEPDIR)/dsync-brain.Plo \ ./$(DEPDIR)/dsync-deserializer.Plo \ ./$(DEPDIR)/dsync-ibc-pipe.Plo \ ./$(DEPDIR)/dsync-ibc-stream.Plo ./$(DEPDIR)/dsync-ibc.Plo \ ./$(DEPDIR)/dsync-mail.Plo \ ./$(DEPDIR)/dsync-mailbox-export.Plo \ ./$(DEPDIR)/dsync-mailbox-import.Plo \ ./$(DEPDIR)/dsync-mailbox-state.Plo \ ./$(DEPDIR)/dsync-mailbox-tree-fill.Plo \ ./$(DEPDIR)/dsync-mailbox-tree-sync.Plo \ ./$(DEPDIR)/dsync-mailbox-tree.Plo \ ./$(DEPDIR)/dsync-mailbox.Plo ./$(DEPDIR)/dsync-serializer.Plo \ ./$(DEPDIR)/dsync-transaction-log-scan.Plo \ ./$(DEPDIR)/test-dsync-mailbox-tree-sync.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(libdovecot_dsync_la_SOURCES) $(libdsync_la_SOURCES) \ $(test_dsync_mailbox_tree_sync_SOURCES) DIST_SOURCES = $(libdovecot_dsync_la_SOURCES) $(libdsync_la_SOURCES) \ $(test_dsync_mailbox_tree_sync_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ pkglib_LTLIBRARIES = libdovecot-dsync.la noinst_LTLIBRARIES = libdsync.la AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-ssl-iostream \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-storage libdsync_la_SOURCES = \ dsync-brain.c \ dsync-brain-mailbox.c \ dsync-brain-mailbox-tree.c \ dsync-brain-mailbox-tree-sync.c \ dsync-brain-mails.c \ dsync-deserializer.c \ dsync-mail.c \ dsync-mailbox.c \ dsync-mailbox-import.c \ dsync-mailbox-export.c \ dsync-mailbox-state.c \ dsync-mailbox-tree.c \ dsync-mailbox-tree-fill.c \ dsync-mailbox-tree-sync.c \ dsync-serializer.c \ dsync-ibc.c \ dsync-ibc-stream.c \ dsync-ibc-pipe.c \ dsync-transaction-log-scan.c libdovecot_dsync_la_SOURCES = libdovecot_dsync_la_LIBADD = libdsync.la ../../lib-storage/libdovecot-storage.la ../../lib-dovecot/libdovecot.la libdovecot_dsync_la_DEPENDENCIES = libdsync.la libdovecot_dsync_la_LDFLAGS = -export-dynamic pkginc_libdir = $(pkgincludedir) pkginc_lib_HEADERS = \ dsync-brain.h \ dsync-ibc.h noinst_HEADERS = \ dsync-brain-private.h \ dsync-mail.h \ dsync-mailbox.h \ dsync-mailbox-import.h \ dsync-mailbox-export.h \ dsync-mailbox-state.h \ dsync-mailbox-tree.h \ dsync-mailbox-tree-private.h \ dsync-serializer.h \ dsync-deserializer.h \ dsync-ibc-private.h \ dsync-transaction-log-scan.h test_programs = \ test-dsync-mailbox-tree-sync test_libs = \ ../../lib-test/libtest.la \ ../../lib/liblib.la test_dsync_mailbox_tree_sync_SOURCES = test-dsync-mailbox-tree-sync.c test_dsync_mailbox_tree_sync_LDADD = dsync-mailbox-tree-sync.lo dsync-mailbox-tree.lo $(test_libs) test_dsync_mailbox_tree_sync_DEPENDENCIES = $(pkglib_LTLIBRARIES) $(test_libs) all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/doveadm/dsync/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/doveadm/dsync/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): clean-noinstPROGRAMS: @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list clean-noinstLTLIBRARIES: -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) @list='$(noinst_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } install-pkglibLTLIBRARIES: $(pkglib_LTLIBRARIES) @$(NORMAL_INSTALL) @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ list2=; for p in $$list; do \ if test -f $$p; then \ list2="$$list2 $$p"; \ else :; fi; \ done; \ test -z "$$list2" || { \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibdir)" || exit 1; \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(pkglibdir)'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(pkglibdir)"; \ } uninstall-pkglibLTLIBRARIES: @$(NORMAL_UNINSTALL) @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ for p in $$list; do \ $(am__strip_dir) \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(pkglibdir)/$$f'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(pkglibdir)/$$f"; \ done clean-pkglibLTLIBRARIES: -test -z "$(pkglib_LTLIBRARIES)" || rm -f $(pkglib_LTLIBRARIES) @list='$(pkglib_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } libdovecot-dsync.la: $(libdovecot_dsync_la_OBJECTS) $(libdovecot_dsync_la_DEPENDENCIES) $(EXTRA_libdovecot_dsync_la_DEPENDENCIES) $(AM_V_CCLD)$(libdovecot_dsync_la_LINK) -rpath $(pkglibdir) $(libdovecot_dsync_la_OBJECTS) $(libdovecot_dsync_la_LIBADD) $(LIBS) libdsync.la: $(libdsync_la_OBJECTS) $(libdsync_la_DEPENDENCIES) $(EXTRA_libdsync_la_DEPENDENCIES) $(AM_V_CCLD)$(LINK) $(libdsync_la_OBJECTS) $(libdsync_la_LIBADD) $(LIBS) test-dsync-mailbox-tree-sync$(EXEEXT): $(test_dsync_mailbox_tree_sync_OBJECTS) $(test_dsync_mailbox_tree_sync_DEPENDENCIES) $(EXTRA_test_dsync_mailbox_tree_sync_DEPENDENCIES) @rm -f test-dsync-mailbox-tree-sync$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_dsync_mailbox_tree_sync_OBJECTS) $(test_dsync_mailbox_tree_sync_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-brain-mailbox-tree-sync.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-brain-mailbox-tree.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-brain-mailbox.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-brain-mails.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-brain.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-deserializer.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-ibc-pipe.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-ibc-stream.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-ibc.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-mail.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-mailbox-export.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-mailbox-import.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-mailbox-state.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-mailbox-tree-fill.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-mailbox-tree-sync.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-mailbox-tree.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-mailbox.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-serializer.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsync-transaction-log-scan.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-dsync-mailbox-tree-sync.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) @$(NORMAL_INSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ done uninstall-pkginc_libHEADERS: @$(NORMAL_UNINSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am $(MAKE) $(AM_MAKEFLAGS) check-local check: check-am all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkglibdir)" "$(DESTDIR)$(pkginc_libdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ clean-noinstPROGRAMS clean-pkglibLTLIBRARIES mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/dsync-brain-mailbox-tree-sync.Plo -rm -f ./$(DEPDIR)/dsync-brain-mailbox-tree.Plo -rm -f ./$(DEPDIR)/dsync-brain-mailbox.Plo -rm -f ./$(DEPDIR)/dsync-brain-mails.Plo -rm -f ./$(DEPDIR)/dsync-brain.Plo -rm -f ./$(DEPDIR)/dsync-deserializer.Plo -rm -f ./$(DEPDIR)/dsync-ibc-pipe.Plo -rm -f ./$(DEPDIR)/dsync-ibc-stream.Plo -rm -f ./$(DEPDIR)/dsync-ibc.Plo -rm -f ./$(DEPDIR)/dsync-mail.Plo -rm -f ./$(DEPDIR)/dsync-mailbox-export.Plo -rm -f ./$(DEPDIR)/dsync-mailbox-import.Plo -rm -f ./$(DEPDIR)/dsync-mailbox-state.Plo -rm -f ./$(DEPDIR)/dsync-mailbox-tree-fill.Plo -rm -f ./$(DEPDIR)/dsync-mailbox-tree-sync.Plo -rm -f ./$(DEPDIR)/dsync-mailbox-tree.Plo -rm -f ./$(DEPDIR)/dsync-mailbox.Plo -rm -f ./$(DEPDIR)/dsync-serializer.Plo -rm -f ./$(DEPDIR)/dsync-transaction-log-scan.Plo -rm -f ./$(DEPDIR)/test-dsync-mailbox-tree-sync.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-pkginc_libHEADERS install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-pkglibLTLIBRARIES install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/dsync-brain-mailbox-tree-sync.Plo -rm -f ./$(DEPDIR)/dsync-brain-mailbox-tree.Plo -rm -f ./$(DEPDIR)/dsync-brain-mailbox.Plo -rm -f ./$(DEPDIR)/dsync-brain-mails.Plo -rm -f ./$(DEPDIR)/dsync-brain.Plo -rm -f ./$(DEPDIR)/dsync-deserializer.Plo -rm -f ./$(DEPDIR)/dsync-ibc-pipe.Plo -rm -f ./$(DEPDIR)/dsync-ibc-stream.Plo -rm -f ./$(DEPDIR)/dsync-ibc.Plo -rm -f ./$(DEPDIR)/dsync-mail.Plo -rm -f ./$(DEPDIR)/dsync-mailbox-export.Plo -rm -f ./$(DEPDIR)/dsync-mailbox-import.Plo -rm -f ./$(DEPDIR)/dsync-mailbox-state.Plo -rm -f ./$(DEPDIR)/dsync-mailbox-tree-fill.Plo -rm -f ./$(DEPDIR)/dsync-mailbox-tree-sync.Plo -rm -f ./$(DEPDIR)/dsync-mailbox-tree.Plo -rm -f ./$(DEPDIR)/dsync-mailbox.Plo -rm -f ./$(DEPDIR)/dsync-serializer.Plo -rm -f ./$(DEPDIR)/dsync-transaction-log-scan.Plo -rm -f ./$(DEPDIR)/test-dsync-mailbox-tree-sync.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkginc_libHEADERS uninstall-pkglibLTLIBRARIES .MAKE: check-am install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ check-local clean clean-generic clean-libtool \ clean-noinstLTLIBRARIES clean-noinstPROGRAMS \ clean-pkglibLTLIBRARIES cscopelist-am ctags ctags-am distclean \ distclean-compile distclean-generic distclean-libtool \ distclean-tags distdir dvi dvi-am html html-am info info-am \ install install-am install-data install-data-am install-dvi \ install-dvi-am install-exec install-exec-am install-html \ install-html-am install-info install-info-am install-man \ install-pdf install-pdf-am install-pkginc_libHEADERS \ install-pkglibLTLIBRARIES install-ps install-ps-am \ install-strip installcheck installcheck-am installdirs \ maintainer-clean maintainer-clean-generic mostlyclean \ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ uninstall-pkginc_libHEADERS uninstall-pkglibLTLIBRARIES .PRECIOUS: Makefile check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/doveadm/dsync/dsync-mailbox-import.h0000644000000000000000000000455214656633576020103 00000000000000#ifndef DSYNC_MAILBOX_IMPORT_H #define DSYNC_MAILBOX_IMPORT_H #include "mail-error.h" enum dsync_mailbox_import_flags { DSYNC_MAILBOX_IMPORT_FLAG_MASTER_BRAIN = 0x01, DSYNC_MAILBOX_IMPORT_FLAG_WANT_MAIL_REQUESTS = 0x02, DSYNC_MAILBOX_IMPORT_FLAG_REVERT_LOCAL_CHANGES = 0x04, DSYNC_MAILBOX_IMPORT_FLAG_DEBUG = 0x08, DSYNC_MAILBOX_IMPORT_FLAG_MAILS_HAVE_GUIDS = 0x10, DSYNC_MAILBOX_IMPORT_FLAG_MAILS_USE_GUID128 = 0x20, DSYNC_MAILBOX_IMPORT_FLAG_NO_NOTIFY = 0x40, DSYNC_MAILBOX_IMPORT_FLAG_EMPTY_HDR_WORKAROUND = 0x100, DSYNC_MAILBOX_IMPORT_FLAG_NO_HEADER_HASHES = 0x200, }; struct mailbox; struct dsync_mailbox_attribute; struct dsync_mail; struct dsync_mail_change; struct dsync_transaction_log_scan; struct dsync_mailbox_importer * dsync_mailbox_import_init(struct mailbox *box, struct mailbox *virtual_all_box, struct dsync_transaction_log_scan *log_scan, uint32_t last_common_uid, uint64_t last_common_modseq, uint64_t last_common_pvt_modseq, uint32_t remote_uid_next, uint32_t remote_first_recent_uid, uint64_t remote_highest_modseq, uint64_t remote_highest_pvt_modseq, time_t sync_since_timestamp, time_t sync_until_timestamp, uoff_t sync_max_size, const char *sync_flag, unsigned int commit_msgs_interval, enum dsync_mailbox_import_flags flags, unsigned int hdr_hash_version, const char *const *hashed_headers); int dsync_mailbox_import_attribute(struct dsync_mailbox_importer *importer, const struct dsync_mailbox_attribute *attr); int dsync_mailbox_import_change(struct dsync_mailbox_importer *importer, const struct dsync_mail_change *change); int dsync_mailbox_import_changes_finish(struct dsync_mailbox_importer *importer); const struct dsync_mail_request * dsync_mailbox_import_next_request(struct dsync_mailbox_importer *importer); int dsync_mailbox_import_mail(struct dsync_mailbox_importer *importer, const struct dsync_mail *mail); int dsync_mailbox_import_deinit(struct dsync_mailbox_importer **importer, bool success, uint32_t *last_common_uid_r, uint64_t *last_common_modseq_r, uint64_t *last_common_pvt_modseq_r, uint32_t *last_messages_count_r, const char **changes_during_sync_r, bool *require_full_resync_r, enum mail_error *error_r); const char *dsync_mailbox_import_get_proctitle(struct dsync_mailbox_importer *importer); #endif dovecot-2.3.21.1/src/doveadm/dsync/dsync-mailbox-state.h0000644000000000000000000000132614656633576017705 00000000000000#ifndef DSYNC_MAILBOX_STATE_H #define DSYNC_MAILBOX_STATE_H #include "guid.h" struct dsync_mailbox_state { guid_128_t mailbox_guid; uint32_t last_uidvalidity; uint32_t last_common_uid; uint64_t last_common_modseq; uint64_t last_common_pvt_modseq; uint32_t last_messages_count; bool changes_during_sync; }; ARRAY_DEFINE_TYPE(dsync_mailbox_state, struct dsync_mailbox_state); HASH_TABLE_DEFINE_TYPE(dsync_mailbox_state, uint8_t *, struct dsync_mailbox_state *); void dsync_mailbox_states_export(const HASH_TABLE_TYPE(dsync_mailbox_state) states, string_t *output); int dsync_mailbox_states_import(HASH_TABLE_TYPE(dsync_mailbox_state) states, pool_t pool, const char *input, const char **error_r); #endif dovecot-2.3.21.1/src/doveadm/dsync/dsync-serializer.h0000644000000000000000000000120314656633576017277 00000000000000#ifndef DSYNC_SERIALIZER_H #define DSYNC_SERIALIZER_H #define NULL_CHR '\002' struct dsync_serializer *dsync_serializer_init(const char *const keys[]); void dsync_serializer_deinit(struct dsync_serializer **serializer); const char * dsync_serializer_encode_header_line(struct dsync_serializer *serializer); struct dsync_serializer_encoder * dsync_serializer_encode_begin(struct dsync_serializer *serializer); void dsync_serializer_encode_add(struct dsync_serializer_encoder *encoder, const char *key, const char *value); void dsync_serializer_encode_finish(struct dsync_serializer_encoder **encoder, string_t *output); #endif dovecot-2.3.21.1/src/doveadm/dsync/dsync-ibc.h0000644000000000000000000001414414656633576015673 00000000000000#ifndef DSYNC_IBC_H #define DSYNC_IBC_H /* dsync inter-brain communicator */ #include "ioloop.h" #include "guid.h" #include "mail-error.h" #include "dsync-brain.h" struct dsync_mailbox; struct dsync_mailbox_state; struct dsync_mailbox_node; struct dsync_mailbox_delete; struct dsync_mailbox_attribute; struct dsync_mail; struct dsync_mail_change; struct dsync_mail_request; enum dsync_ibc_send_ret { DSYNC_IBC_SEND_RET_OK = 1, /* send queue is full, stop sending more */ DSYNC_IBC_SEND_RET_FULL = 0 }; enum dsync_ibc_recv_ret { DSYNC_IBC_RECV_RET_FINISHED = -1, /* try again / error (the error handling delayed until io callback) */ DSYNC_IBC_RECV_RET_TRYAGAIN = 0, DSYNC_IBC_RECV_RET_OK = 1 }; enum dsync_ibc_eol_type { DSYNC_IBC_EOL_MAILBOX_STATE, DSYNC_IBC_EOL_MAILBOX_TREE, DSYNC_IBC_EOL_MAILBOX_ATTRIBUTE, DSYNC_IBC_EOL_MAILBOX, DSYNC_IBC_EOL_MAIL_CHANGES, DSYNC_IBC_EOL_MAIL_REQUESTS, DSYNC_IBC_EOL_MAILS }; struct dsync_ibc_settings { /* Server hostname. Used for determining which server does the locking. */ const char *hostname; /* if non-NULL, sync only these namespaces (LF-separated) */ const char *sync_ns_prefixes; /* if non-NULL, sync only this mailbox name */ const char *sync_box; /* if non-NULL, use this mailbox for finding messages with GUIDs and copying them instead of saving them again. */ const char *virtual_all_box; /* if non-empty, sync only this mailbox GUID */ guid_128_t sync_box_guid; /* Exclude these mailboxes from the sync. They can contain '*' wildcards and be \special-use flags. */ const char *const *exclude_mailboxes; /* Sync only mails with received timestamp at least this high. */ time_t sync_since_timestamp; /* Sync only mails with received timestamp less or equal than this */ time_t sync_until_timestamp; /* Don't sync mails larger than this. */ uoff_t sync_max_size; /* Sync only mails with specified flags. */ const char *sync_flags; /* Hashed headers */ const char *const *hashed_headers; char alt_char; enum dsync_brain_sync_type sync_type; enum dsync_brain_flags brain_flags; bool hdr_hash_v2; bool hdr_hash_v3; unsigned int lock_timeout; unsigned int import_commit_msgs_interval; }; void dsync_ibc_init_pipe(struct dsync_ibc **ibc1_r, struct dsync_ibc **ibc2_r); struct dsync_ibc * dsync_ibc_init_stream(struct istream *input, struct ostream *output, const char *name, const char *temp_path_prefix, unsigned int timeout_secs); void dsync_ibc_deinit(struct dsync_ibc **ibc); /* I/O callback is called whenever new data is available. It's also called on errors, so check first the error status. */ void dsync_ibc_set_io_callback(struct dsync_ibc *ibc, io_callback_t *callback, void *context); void dsync_ibc_send_handshake(struct dsync_ibc *ibc, const struct dsync_ibc_settings *set); enum dsync_ibc_recv_ret dsync_ibc_recv_handshake(struct dsync_ibc *ibc, const struct dsync_ibc_settings **set_r); enum dsync_ibc_send_ret ATTR_NOWARN_UNUSED_RESULT dsync_ibc_send_end_of_list(struct dsync_ibc *ibc, enum dsync_ibc_eol_type type); enum dsync_ibc_send_ret ATTR_NOWARN_UNUSED_RESULT dsync_ibc_send_mailbox_state(struct dsync_ibc *ibc, const struct dsync_mailbox_state *state); enum dsync_ibc_recv_ret dsync_ibc_recv_mailbox_state(struct dsync_ibc *ibc, struct dsync_mailbox_state *state_r); enum dsync_ibc_send_ret ATTR_NOWARN_UNUSED_RESULT dsync_ibc_send_mailbox_tree_node(struct dsync_ibc *ibc, const char *const *name, const struct dsync_mailbox_node *node); enum dsync_ibc_recv_ret dsync_ibc_recv_mailbox_tree_node(struct dsync_ibc *ibc, const char *const **name_r, const struct dsync_mailbox_node **node_r); enum dsync_ibc_send_ret ATTR_NOWARN_UNUSED_RESULT dsync_ibc_send_mailbox_deletes(struct dsync_ibc *ibc, const struct dsync_mailbox_delete *deletes, unsigned int count, char hierarchy_sep, char escape_char); enum dsync_ibc_recv_ret dsync_ibc_recv_mailbox_deletes(struct dsync_ibc *ibc, const struct dsync_mailbox_delete **deletes_r, unsigned int *count_r, char *hierarchy_sep_r, char *escape_char_r); enum dsync_ibc_send_ret ATTR_NOWARN_UNUSED_RESULT dsync_ibc_send_mailbox(struct dsync_ibc *ibc, const struct dsync_mailbox *dsync_box); enum dsync_ibc_recv_ret dsync_ibc_recv_mailbox(struct dsync_ibc *ibc, const struct dsync_mailbox **dsync_box_r); enum dsync_ibc_send_ret ATTR_NOWARN_UNUSED_RESULT dsync_ibc_send_mailbox_attribute(struct dsync_ibc *ibc, const struct dsync_mailbox_attribute *attr); enum dsync_ibc_recv_ret dsync_ibc_recv_mailbox_attribute(struct dsync_ibc *ibc, const struct dsync_mailbox_attribute **attr_r); enum dsync_ibc_send_ret ATTR_NOWARN_UNUSED_RESULT dsync_ibc_send_change(struct dsync_ibc *ibc, const struct dsync_mail_change *change); enum dsync_ibc_recv_ret dsync_ibc_recv_change(struct dsync_ibc *ibc, const struct dsync_mail_change **change_r); enum dsync_ibc_send_ret ATTR_NOWARN_UNUSED_RESULT dsync_ibc_send_mail_request(struct dsync_ibc *ibc, const struct dsync_mail_request *request); enum dsync_ibc_recv_ret dsync_ibc_recv_mail_request(struct dsync_ibc *ibc, const struct dsync_mail_request **request_r); enum dsync_ibc_send_ret ATTR_NOWARN_UNUSED_RESULT dsync_ibc_send_mail(struct dsync_ibc *ibc, const struct dsync_mail *mail); enum dsync_ibc_recv_ret dsync_ibc_recv_mail(struct dsync_ibc *ibc, struct dsync_mail **mail_r); void dsync_ibc_send_finish(struct dsync_ibc *ibc, const char *error, enum mail_error mail_error, bool require_full_resync); enum dsync_ibc_recv_ret dsync_ibc_recv_finish(struct dsync_ibc *ibc, const char **error_r, enum mail_error *mail_error_r, bool *require_full_resync_r); /* Close any mail input streams that are kept open. This needs to be called before the mail is attempted to be freed (usually on error conditions). */ void dsync_ibc_close_mail_streams(struct dsync_ibc *ibc); bool dsync_ibc_has_failed(struct dsync_ibc *ibc); bool dsync_ibc_has_timed_out(struct dsync_ibc *ibc); bool dsync_ibc_is_send_queue_full(struct dsync_ibc *ibc); bool dsync_ibc_has_pending_data(struct dsync_ibc *ibc); #endif dovecot-2.3.21.1/src/doveadm/dsync/dsync-mailbox-export.h0000644000000000000000000000316014656633576020104 00000000000000#ifndef DSYNC_MAILBOX_EXPORT_H #define DSYNC_MAILBOX_EXPORT_H enum dsync_mailbox_exporter_flags { DSYNC_MAILBOX_EXPORTER_FLAG_AUTO_EXPORT_MAILS = 0x01, DSYNC_MAILBOX_EXPORTER_FLAG_MAILS_HAVE_GUIDS = 0x02, DSYNC_MAILBOX_EXPORTER_FLAG_MINIMAL_DMAIL_FILL = 0x04, DSYNC_MAILBOX_EXPORTER_FLAG_TIMESTAMPS = 0x08, DSYNC_MAILBOX_EXPORTER_FLAG_NO_HDR_HASHES = 0x20, DSYNC_MAILBOX_EXPORTER_FLAG_VSIZES = 0x40, }; struct dsync_mailbox_exporter * dsync_mailbox_export_init(struct mailbox *box, struct dsync_transaction_log_scan *log_scan, uint32_t last_common_uid, enum dsync_mailbox_exporter_flags flags, unsigned int hdr_hash_version, const char *const *hashed_headers); /* Returns 1 if attribute was returned, 0 if no more attributes, -1 on error */ int dsync_mailbox_export_next_attr(struct dsync_mailbox_exporter *exporter, const struct dsync_mailbox_attribute **attr_r); /* Returns 1 if change was returned, 0 if no more changes, -1 on error */ int dsync_mailbox_export_next(struct dsync_mailbox_exporter *exporter, const struct dsync_mail_change **change_r); void dsync_mailbox_export_want_mail(struct dsync_mailbox_exporter *exporter, const struct dsync_mail_request *request); /* Returns 1 if mail was returned, 0 if no more mails, -1 on error */ int dsync_mailbox_export_next_mail(struct dsync_mailbox_exporter *exporter, const struct dsync_mail **mail_r); int dsync_mailbox_export_deinit(struct dsync_mailbox_exporter **exporter, const char **errstr_r, enum mail_error *error_r); const char *dsync_mailbox_export_get_proctitle(struct dsync_mailbox_exporter *exporter); #endif dovecot-2.3.21.1/src/doveadm/dsync/dsync-deserializer.h0000644000000000000000000000206614656633576017620 00000000000000#ifndef DSYNC_DESERIALIZER_H #define DSYNC_DESERIALIZER_H struct dsync_deserializer; struct dsync_deserializer_decoder; int dsync_deserializer_init(const char *name, const char *const *required_fields, const char *header_line, struct dsync_deserializer **deserializer_r, const char **error_r); void dsync_deserializer_deinit(struct dsync_deserializer **deserializer); int dsync_deserializer_decode_begin(struct dsync_deserializer *deserializer, const char *input, struct dsync_deserializer_decoder **decoder_r, const char **error_r); bool dsync_deserializer_decode_try(struct dsync_deserializer_decoder *decoder, const char *key, const char **value_r); /* key must be in required fields. The return value is never NULL. */ const char * dsync_deserializer_decode_get(struct dsync_deserializer_decoder *decoder, const char *key); const char * dsync_deserializer_decoder_get_name(struct dsync_deserializer_decoder *decoder); void dsync_deserializer_decode_finish(struct dsync_deserializer_decoder **decoder); #endif dovecot-2.3.21.1/src/doveadm/dsync/dsync-brain-private.h0000644000000000000000000001247414656633576017705 00000000000000#ifndef DSYNC_BRAIN_PRIVATE_H #define DSYNC_BRAIN_PRIVATE_H #include "hash.h" #include "dsync-brain.h" #include "dsync-mailbox.h" #include "dsync-mailbox-state.h" #define DSYNC_LOCK_FILENAME ".dovecot-sync.lock" #define DSYNC_MAILBOX_LOCK_FILENAME ".dovecot-box-sync.lock" #define DSYNC_MAILBOX_DEFAULT_LOCK_TIMEOUT_SECS 30 struct dsync_mailbox_tree_sync_change; enum dsync_state { DSYNC_STATE_MASTER_RECV_HANDSHAKE, DSYNC_STATE_SLAVE_RECV_HANDSHAKE, /* if sync_type=STATE, the master brain knows the saved "last common mailbox state". this state is sent to the slave. */ DSYNC_STATE_MASTER_SEND_LAST_COMMON, DSYNC_STATE_SLAVE_RECV_LAST_COMMON, /* both sides send their mailbox trees */ DSYNC_STATE_SEND_MAILBOX_TREE, DSYNC_STATE_SEND_MAILBOX_TREE_DELETES, DSYNC_STATE_RECV_MAILBOX_TREE, DSYNC_STATE_RECV_MAILBOX_TREE_DELETES, /* master decides in which order mailboxes are synced (it knows the slave's mailboxes by looking at the received mailbox tree) */ DSYNC_STATE_MASTER_SEND_MAILBOX, DSYNC_STATE_SLAVE_RECV_MAILBOX, /* once mailbox is selected, the mails inside it are synced. after the mails are synced, another mailbox is synced. */ DSYNC_STATE_SYNC_MAILS, DSYNC_STATE_FINISH, DSYNC_STATE_DONE }; enum dsync_box_state { DSYNC_BOX_STATE_MAILBOX, DSYNC_BOX_STATE_CHANGES, DSYNC_BOX_STATE_ATTRIBUTES, DSYNC_BOX_STATE_MAIL_REQUESTS, DSYNC_BOX_STATE_MAILS, DSYNC_BOX_STATE_RECV_LAST_COMMON, DSYNC_BOX_STATE_DONE }; struct dsync_brain { pool_t pool; struct mail_user *user; struct dsync_ibc *ibc; const char *process_title_prefix; ARRAY(struct mail_namespace *) sync_namespaces; const char *sync_box; struct mailbox *virtual_all_box; guid_128_t sync_box_guid; const char *const *exclude_mailboxes; enum dsync_brain_sync_type sync_type; time_t sync_since_timestamp; time_t sync_until_timestamp; uoff_t sync_max_size; const char *sync_flag; char alt_char; unsigned int import_commit_msgs_interval; unsigned int hdr_hash_version; unsigned int lock_timeout; int lock_fd; const char *lock_path; struct file_lock *lock; char hierarchy_sep, escape_char; struct dsync_mailbox_tree *local_mailbox_tree; struct dsync_mailbox_tree *remote_mailbox_tree; struct dsync_mailbox_tree_iter *local_tree_iter; enum dsync_state state, pre_box_state; enum dsync_box_state box_recv_state; enum dsync_box_state box_send_state; unsigned int proctitle_update_counter; struct dsync_transaction_log_scan *log_scan; struct dsync_mailbox_importer *box_importer; struct dsync_mailbox_exporter *box_exporter; struct mailbox *box; struct file_lock *box_lock; unsigned int mailbox_lock_timeout_secs; struct dsync_mailbox local_dsync_box, remote_dsync_box; pool_t dsync_box_pool; /* list of mailbox states for master brain: given to brain at init and for slave brain: received from DSYNC_STATE_SLAVE_RECV_LAST_COMMON */ HASH_TABLE_TYPE(dsync_mailbox_state) mailbox_states; /* DSYNC_STATE_MASTER_SEND_LAST_COMMON: current send position */ struct hash_iterate_context *mailbox_states_iter; /* state of the mailbox we're currently syncing, changed at init and deinit */ struct dsync_mailbox_state mailbox_state; /* new states for synced mailboxes */ ARRAY_TYPE(dsync_mailbox_state) remote_mailbox_states; const char *changes_during_sync; enum mail_error mail_error; const char *const *hashed_headers; bool master_brain:1; bool mail_requests:1; bool backup_send:1; bool backup_recv:1; bool purge:1; bool debug:1; bool sync_visible_namespaces:1; bool no_mail_sync:1; bool no_backup_overwrite:1; bool no_mail_prefetch:1; bool changes_during_remote_sync:1; bool require_full_resync:1; bool verbose_proctitle:1; bool no_notify:1; bool failed:1; bool empty_hdr_workaround:1; bool no_header_hashes:1; }; extern const char *dsync_box_state_names[DSYNC_BOX_STATE_DONE+1]; void dsync_brain_mailbox_trees_init(struct dsync_brain *brain); void dsync_brain_send_mailbox_tree(struct dsync_brain *brain); void dsync_brain_send_mailbox_tree_deletes(struct dsync_brain *brain); bool dsync_brain_recv_mailbox_tree(struct dsync_brain *brain); bool dsync_brain_recv_mailbox_tree_deletes(struct dsync_brain *brain); int dsync_brain_mailbox_tree_sync_change(struct dsync_brain *brain, const struct dsync_mailbox_tree_sync_change *change, enum mail_error *error_r); void dsync_brain_sync_mailbox_deinit(struct dsync_brain *brain); int dsync_brain_mailbox_alloc(struct dsync_brain *brain, const guid_128_t guid, struct mailbox **box_r, const char **errstr_r, enum mail_error *error_r); bool dsync_brain_mailbox_update_pre(struct dsync_brain *brain, struct mailbox *box, const struct dsync_mailbox *local_box, const struct dsync_mailbox *remote_box, const char **reason_r); bool dsync_boxes_need_sync(struct dsync_brain *brain, const struct dsync_mailbox *box1, const struct dsync_mailbox *box2, const char **reason_r); void dsync_brain_sync_init_box_states(struct dsync_brain *brain); void dsync_brain_set_changes_during_sync(struct dsync_brain *brain, const char *reason); void dsync_brain_master_send_mailbox(struct dsync_brain *brain); bool dsync_brain_slave_recv_mailbox(struct dsync_brain *brain); int dsync_brain_sync_mailbox_open(struct dsync_brain *brain, const struct dsync_mailbox *remote_dsync_box); bool dsync_brain_sync_mails(struct dsync_brain *brain); #endif dovecot-2.3.21.1/src/doveadm/dsync/dsync-mailbox.c0000644000000000000000000000340214656633576016557 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "istream.h" #include "mail-storage-private.h" #include "dsync-brain-private.h" #include "dsync-mailbox.h" void dsync_mailbox_attribute_dup(pool_t pool, const struct dsync_mailbox_attribute *src, struct dsync_mailbox_attribute *dest_r) { dest_r->type = src->type; dest_r->key = p_strdup(pool, src->key); dest_r->value = p_strdup(pool, src->value); if (src->value_stream != NULL) { dest_r->value_stream = src->value_stream; i_stream_ref(dest_r->value_stream); } dest_r->deleted = src->deleted; dest_r->last_change = src->last_change; dest_r->modseq = src->modseq; } int dsync_mailbox_lock(struct dsync_brain *brain, struct mailbox *box, struct file_lock **lock_r) { const char *path, *error; int ret; /* Make sure the mailbox is open - locking requires it */ if (mailbox_open(box) < 0) { i_error("Can't open mailbox %s: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, &brain->mail_error)); return -1; } ret = mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_INDEX, &path); if (ret < 0) { i_error("Can't get mailbox %s path: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, &brain->mail_error)); return -1; } if (ret == 0) { /* No index files - don't do any locking. In theory we still could, but this lock is mainly meant to prevent replication problems, and replication wouldn't work without indexes. */ *lock_r = NULL; return 0; } if (mailbox_lock_file_create(box, DSYNC_MAILBOX_LOCK_FILENAME, brain->mailbox_lock_timeout_secs, lock_r, &error) <= 0) { i_error("Failed to lock mailbox %s for dsyncing: %s", box->vname, error); return -1; } return 0; } dovecot-2.3.21.1/src/doveadm/dsync/dsync-serializer.c0000644000000000000000000000552014656633576017300 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "strescape.h" #include "dsync-serializer.h" struct dsync_serializer { pool_t pool; const char *const *keys; unsigned int keys_count; }; struct dsync_serializer_encoder { pool_t pool; struct dsync_serializer *serializer; ARRAY_TYPE(const_string) values; }; struct dsync_serializer *dsync_serializer_init(const char *const keys[]) { struct dsync_serializer *serializer; pool_t pool; const char **dup_keys; unsigned int i, count; pool = pool_alloconly_create("dsync serializer", 512); serializer = p_new(pool, struct dsync_serializer, 1); serializer->pool = pool; count = str_array_length(keys); dup_keys = p_new(pool, const char *, count + 1); for (i = 0; i < count; i++) dup_keys[i] = p_strdup(pool, keys[i]); serializer->keys = dup_keys; serializer->keys_count = count; return serializer; } void dsync_serializer_deinit(struct dsync_serializer **_serializer) { struct dsync_serializer *serializer = *_serializer; *_serializer = NULL; pool_unref(&serializer->pool); } const char * dsync_serializer_encode_header_line(struct dsync_serializer *serializer) { string_t *str = t_str_new(128); unsigned int i; for (i = 0; serializer->keys[i] != NULL; i++) { if (i > 0) str_append_c(str, '\t'); str_append_tabescaped(str, serializer->keys[i]); } str_append_c(str, '\n'); return str_c(str); } struct dsync_serializer_encoder * dsync_serializer_encode_begin(struct dsync_serializer *serializer) { struct dsync_serializer_encoder *encoder; pool_t pool = pool_alloconly_create("dsync serializer encode", 1024); encoder = p_new(pool, struct dsync_serializer_encoder, 1); encoder->pool = pool; encoder->serializer = serializer; p_array_init(&encoder->values, pool, serializer->keys_count); return encoder; } void dsync_serializer_encode_add(struct dsync_serializer_encoder *encoder, const char *key, const char *value) { unsigned int i; for (i = 0; encoder->serializer->keys[i] != NULL; i++) { if (strcmp(encoder->serializer->keys[i], key) == 0) { value = p_strdup(encoder->pool, value); array_idx_set(&encoder->values, i, &value); return; } } i_panic("Unknown key: %s", key); } void dsync_serializer_encode_finish(struct dsync_serializer_encoder **_encoder, string_t *output) { struct dsync_serializer_encoder *encoder = *_encoder; const char *const *values; unsigned int i, count; *_encoder = NULL; values = array_get(&encoder->values, &count); for (i = 0; i < count; i++) { if (i > 0) str_append_c(output, '\t'); if (values[i] == NULL) str_append_c(output, NULL_CHR); else { if (values[i][0] == NULL_CHR) str_append_c(output, NULL_CHR); str_append_tabescaped(output, values[i]); } } str_append_c(output, '\n'); pool_unref(&encoder->pool); } dovecot-2.3.21.1/src/doveadm/dsync/dsync-mail.h0000644000000000000000000000626614656633576016066 00000000000000#ifndef DSYNC_MAIL_H #define DSYNC_MAIL_H #include "mail-types.h" struct md5_context; struct mail; struct mailbox; struct dsync_mail { /* either GUID="" or uid=0 */ const char *guid; uint32_t uid; time_t saved_date; /* If non-NULL, we're syncing within the dsync process using ibc-pipe. This mail can be used to mailbox_copy() the mail. */ struct mail *input_mail; /* Verify that this equals to input_mail->uid */ uint32_t input_mail_uid; /* TRUE if the following fields aren't set, because minimal_fill=TRUE parameter was used. */ bool minimal_fields; const char *pop3_uidl; uint32_t pop3_order; time_t received_date; /* Input stream containing the message text, or NULL if all instances of the message were already expunged from this mailbox. */ struct istream *input; }; struct dsync_mail_request { /* either GUID=NULL or uid=0 */ const char *guid; uint32_t uid; }; enum dsync_mail_change_type { DSYNC_MAIL_CHANGE_TYPE_SAVE, DSYNC_MAIL_CHANGE_TYPE_EXPUNGE, DSYNC_MAIL_CHANGE_TYPE_FLAG_CHANGE }; #define KEYWORD_CHANGE_ADD '+' #define KEYWORD_CHANGE_REMOVE '-' #define KEYWORD_CHANGE_FINAL '=' #define KEYWORD_CHANGE_ADD_AND_FINAL '&' struct dsync_mail_change { enum dsync_mail_change_type type; uint32_t uid; /* Message's GUID: - for expunges either 128bit hex or NULL if unknown - "" if backend doesn't support GUIDs */ const char *guid; /* If GUID is "", this contains hash of the message header, otherwise NULL */ const char *hdr_hash; /* Message's current modseq (saves, flag changes) */ uint64_t modseq; /* Message's current private modseq (for private flags in shared mailboxes, otherwise 0) */ uint64_t pvt_modseq; /* List of flag/keyword changes: (saves, flag changes) */ /* Flags added/removed since last sync, and final flags containing flags that exist now but haven't changed */ uint8_t add_flags, remove_flags, final_flags; uint8_t add_pvt_flags, remove_pvt_flags; /* Remove all keywords before applying changes. This is used only with old transaction logs, new ones never reset keywords (just explicitly remove unwanted keywords) */ bool keywords_reset; /* +add, -remove, =final, &add_and_final. */ ARRAY_TYPE(const_string) keyword_changes; /* Received timestamp for saves, if brain.sync_since/until_timestamp is set */ time_t received_timestamp; /* Mail's size for saves if brain.sync_max_size is set, UOFF_T_MAX otherwise. */ uoff_t virtual_size; }; struct mailbox_header_lookup_ctx * dsync_mail_get_hash_headers(struct mailbox *box, const char *const *hashed_headers); int dsync_mail_get_hdr_hash(struct mail *mail, unsigned int version, const char *const *hashed_headers, const char **hdr_hash_r); static inline bool dsync_mail_hdr_hash_is_empty(const char *hdr_hash) { /* md5(\n) */ return strcmp(hdr_hash, "68b329da9893e34099c7d8ad5cb9c940") == 0; } int dsync_mail_fill(struct mail *mail, bool minimal_fill, struct dsync_mail *dmail_r, const char **error_field_r); int dsync_mail_fill_nonminimal(struct mail *mail, struct dsync_mail *dmail_r, const char **error_field_r); void dsync_mail_change_dup(pool_t pool, const struct dsync_mail_change *src, struct dsync_mail_change *dest_r); #endif dovecot-2.3.21.1/src/doveadm/dsync/dsync-mailbox-tree-private.h0000644000000000000000000000237514656633576021201 00000000000000#ifndef DSYNC_MAILBOX_TREE_PRIVATE_H #define DSYNC_MAILBOX_TREE_PRIVATE_H #include "dsync-mailbox-tree.h" struct dsync_mailbox_tree { pool_t pool; char sep, sep_str[2], remote_sep, alt_char; char escape_char, remote_escape_char; /* root node isn't part of the real mailbox tree. its name is "" and it has no siblings */ struct dsync_mailbox_node root; unsigned int iter_count; ARRAY(struct dsync_mailbox_delete) deletes; /* guid_128_t => struct dsync_mailbox_node */ HASH_TABLE(uint8_t *, struct dsync_mailbox_node *) name128_hash; HASH_TABLE(uint8_t *, struct dsync_mailbox_node *) name128_remotesep_hash; HASH_TABLE(uint8_t *, struct dsync_mailbox_node *) guid_hash; }; void dsync_mailbox_tree_build_name128_hash(struct dsync_mailbox_tree *tree); int dsync_mailbox_node_name_cmp(struct dsync_mailbox_node *const *n1, struct dsync_mailbox_node *const *n2); void dsync_mailbox_tree_node_attach(struct dsync_mailbox_node *node, struct dsync_mailbox_node *parent); void dsync_mailbox_tree_node_detach(struct dsync_mailbox_node *node); struct dsync_mailbox_tree * dsync_mailbox_tree_dup(const struct dsync_mailbox_tree *src); bool dsync_mailbox_trees_equal(struct dsync_mailbox_tree *tree1, struct dsync_mailbox_tree *tree2); #endif dovecot-2.3.21.1/src/doveadm/dsync/dsync-mailbox-tree-sync.c0000644000000000000000000013707014656633576020477 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "buffer.h" #include "str.h" #include "md5.h" #include "hex-binary.h" #include "aqueue.h" #include "hash.h" #include "dsync-brain-private.h" #include "dsync-mailbox-tree-private.h" #define TEMP_MAX_NAME_LEN 100 #define TEMP_SUFFIX_MAX_LEN (sizeof("temp-")-1 + 8) #define TEMP_SUFFIX_FORMAT "temp-%x" struct dsync_mailbox_tree_bfs_iter { struct dsync_mailbox_tree *tree; ARRAY(struct dsync_mailbox_node *) queue_arr; struct aqueue *queue; struct dsync_mailbox_node *cur; }; struct dsync_mailbox_tree_sync_ctx { pool_t pool; struct dsync_mailbox_tree *local_tree, *remote_tree; enum dsync_mailbox_trees_sync_type sync_type; enum dsync_mailbox_trees_sync_flags sync_flags; unsigned int combined_mailboxes_count; ARRAY(struct dsync_mailbox_tree_sync_change) changes; unsigned int change_idx; bool failed; }; static struct dsync_mailbox_tree_bfs_iter * dsync_mailbox_tree_bfs_iter_init(struct dsync_mailbox_tree *tree) { struct dsync_mailbox_tree_bfs_iter *iter; iter = i_new(struct dsync_mailbox_tree_bfs_iter, 1); iter->tree = tree; i_array_init(&iter->queue_arr, 32); iter->queue = aqueue_init(&iter->queue_arr.arr); iter->cur = tree->root.first_child; return iter; } static bool dsync_mailbox_tree_bfs_iter_next(struct dsync_mailbox_tree_bfs_iter *iter, struct dsync_mailbox_node **node_r) { if (iter->cur == NULL) { if (aqueue_count(iter->queue) == 0) return FALSE; iter->cur = array_idx_elem(&iter->queue_arr, aqueue_idx(iter->queue, 0)); aqueue_delete_tail(iter->queue); } *node_r = iter->cur; if (iter->cur->first_child != NULL) aqueue_append(iter->queue, &iter->cur->first_child); iter->cur = iter->cur->next; return TRUE; } static void dsync_mailbox_tree_bfs_iter_deinit(struct dsync_mailbox_tree_bfs_iter **_iter) { struct dsync_mailbox_tree_bfs_iter *iter = *_iter; *_iter = NULL; aqueue_deinit(&iter->queue); array_free(&iter->queue_arr); i_free(iter); } static void sync_add_dir_change(struct dsync_mailbox_tree_sync_ctx *ctx, const struct dsync_mailbox_node *node, enum dsync_mailbox_tree_sync_type type) { struct dsync_mailbox_tree_sync_change *change; const char *name; i_assert(ctx->sync_type != DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_LOCAL); name = dsync_mailbox_node_get_full_name(ctx->local_tree, node); change = array_append_space(&ctx->changes); change->type = type; change->ns = node->ns; change->full_name = p_strdup(ctx->pool, name); } static void sync_add_create_change(struct dsync_mailbox_tree_sync_ctx *ctx, const struct dsync_mailbox_node *node, const char *name) { struct dsync_mailbox_tree_sync_change *change; i_assert(ctx->sync_type != DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_LOCAL); change = array_append_space(&ctx->changes); change->type = DSYNC_MAILBOX_TREE_SYNC_TYPE_CREATE_BOX; change->ns = node->ns; change->full_name = p_strdup(ctx->pool, name); memcpy(change->mailbox_guid, node->mailbox_guid, sizeof(change->mailbox_guid)); change->uid_validity = node->uid_validity; } static void sort_siblings(ARRAY_TYPE(dsync_mailbox_node) *siblings) { struct dsync_mailbox_node *const *nodes; unsigned int i, count; array_sort(siblings, dsync_mailbox_node_name_cmp); nodes = array_get(siblings, &count); if (count == 0) return; nodes[0]->parent->first_child = nodes[0]; for (i = 1; i < count; i++) nodes[i-1]->next = nodes[i]; nodes[count-1]->next = NULL; } static void sync_set_node_deleted(struct dsync_mailbox_tree *tree, struct dsync_mailbox_node *node) { const uint8_t *guid_p; /* for the rest of this sync assume that the mailbox has already been deleted */ guid_p = node->mailbox_guid; hash_table_remove(tree->guid_hash, guid_p); node->existence = DSYNC_MAILBOX_NODE_DELETED; memset(node->mailbox_guid, 0, sizeof(node->mailbox_guid)); node->uid_validity = 0; } static void sync_delete_mailbox_node(struct dsync_mailbox_tree_sync_ctx *ctx, struct dsync_mailbox_tree *tree, struct dsync_mailbox_node *node, const char *reason) { struct dsync_mailbox_tree_sync_change *change; const char *name; if ((ctx->sync_flags & DSYNC_MAILBOX_TREES_SYNC_FLAG_DEBUG) != 0 && tree == ctx->local_tree) { i_debug("brain %c: Deleting mailbox '%s' (GUID %s): %s", (ctx->sync_flags & DSYNC_MAILBOX_TREES_SYNC_FLAG_MASTER_BRAIN) != 0 ? 'M' : 'S', dsync_mailbox_node_get_full_name(tree, node), guid_128_to_string(node->mailbox_guid), reason); } if (tree == ctx->local_tree && node->existence != DSYNC_MAILBOX_NODE_DELETED) { /* delete this mailbox locally */ i_assert(ctx->sync_type != DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_LOCAL); change = array_append_space(&ctx->changes); change->type = DSYNC_MAILBOX_TREE_SYNC_TYPE_DELETE_BOX; change->ns = node->ns; name = dsync_mailbox_node_get_full_name(tree, node); change->full_name = p_strdup(ctx->pool, name); memcpy(change->mailbox_guid, node->mailbox_guid, sizeof(change->mailbox_guid)); } sync_set_node_deleted(tree, node); } static void sync_delete_mailbox(struct dsync_mailbox_tree_sync_ctx *ctx, struct dsync_mailbox_tree *tree, struct dsync_mailbox_node *node, const char *reason) { struct dsync_mailbox_tree *other_tree; struct dsync_mailbox_node *other_node; const uint8_t *guid_p; other_tree = tree == ctx->local_tree ? ctx->remote_tree : ctx->local_tree; guid_p = node->mailbox_guid; other_node = hash_table_lookup(other_tree->guid_hash, guid_p); if (other_node == NULL) { /* doesn't exist / already deleted */ } else { sync_delete_mailbox_node(ctx, other_tree, other_node, reason); } sync_delete_mailbox_node(ctx, tree, node, reason); } static void sync_tree_sort_and_delete_mailboxes(struct dsync_mailbox_tree_sync_ctx *ctx, struct dsync_mailbox_tree *tree, bool twoway_sync) { struct dsync_mailbox_tree_bfs_iter *iter; struct dsync_mailbox_node *node, *parent = NULL; ARRAY_TYPE(dsync_mailbox_node) siblings; t_array_init(&siblings, 64); iter = dsync_mailbox_tree_bfs_iter_init(tree); while (dsync_mailbox_tree_bfs_iter_next(iter, &node)) { if (node->parent != parent) { sort_siblings(&siblings); array_clear(&siblings); parent = node->parent; } if (node->existence == DSYNC_MAILBOX_NODE_DELETED && !dsync_mailbox_node_is_dir(node)) { if (twoway_sync) { /* this mailbox was deleted. delete it from the other side as well */ sync_delete_mailbox(ctx, tree, node, "Mailbox has been deleted"); } else { /* treat the node as if it didn't exist. it'll get either recreated or deleted later. in any case this function must handle all existence=DELETED mailbox nodes by changing them into directories (setting GUID=0) or we'll assert-crash later */ sync_set_node_deleted(tree, node); } } ctx->combined_mailboxes_count++; array_push_back(&siblings, &node); } sort_siblings(&siblings); dsync_mailbox_tree_bfs_iter_deinit(&iter); } static bool node_names_equal(const struct dsync_mailbox_node *n1, const struct dsync_mailbox_node *n2) { while (n1 != NULL && n2 != NULL) { if (strcmp(n1->name, n2->name) != 0) return FALSE; n1 = n1->parent; n2 = n2->parent; } return n1 == NULL && n2 == NULL; } static void dsync_mailbox_tree_node_attach_sorted(struct dsync_mailbox_node *node, struct dsync_mailbox_node *parent) { struct dsync_mailbox_node **p; node->parent = parent; for (p = &parent->first_child; *p != NULL; p = &(*p)->next) { if (dsync_mailbox_node_name_cmp(p, &node) > 0) break; } node->next = *p; *p = node; } static void dsync_mailbox_tree_node_move_sorted(struct dsync_mailbox_node *node, struct dsync_mailbox_node *parent) { /* detach from old parent */ dsync_mailbox_tree_node_detach(node); /* attach to new parent */ dsync_mailbox_tree_node_attach_sorted(node, parent); } static struct dsync_mailbox_node * sorted_tree_get(struct dsync_mailbox_tree *tree, const char *name) { struct dsync_mailbox_node *node, *parent, *ret; node = ret = dsync_mailbox_tree_get(tree, name); while (node->parent != NULL && node->existence == DSYNC_MAILBOX_NODE_NONEXISTENT) { parent = node->parent; dsync_mailbox_tree_node_detach(node); dsync_mailbox_tree_node_attach_sorted(node, parent); node = parent; } return ret; } static struct dsync_mailbox_node * sync_node_new(struct dsync_mailbox_tree *tree, struct dsync_mailbox_node **pos, struct dsync_mailbox_node *parent, const struct dsync_mailbox_node *src) { struct dsync_mailbox_node *node; node = p_new(tree->pool, struct dsync_mailbox_node, 1); node->existence = DSYNC_MAILBOX_NODE_NONEXISTENT; node->name = p_strdup(tree->pool, src->name); node->sync_temporary_name = src->sync_temporary_name; node->ns = src->ns; node->parent = parent; node->next = *pos; *pos = node; return node; } static struct dsync_mailbox_node * sorted_tree_get_by_node_name(struct dsync_mailbox_tree *tree, struct dsync_mailbox_tree *other_tree, struct dsync_mailbox_node *other_node) { const char *parent_name; if (other_node == &other_tree->root) return &tree->root; parent_name = dsync_mailbox_node_get_full_name(other_tree, other_node); return sorted_tree_get(tree, parent_name); } static bool node_has_child(struct dsync_mailbox_node *parent, const char *name) { struct dsync_mailbox_node *node; for (node = parent->first_child; node != NULL; node = node->next) { if (strcmp(node->name, name) == 0) return TRUE; } return FALSE; } static bool node_has_existent_children(struct dsync_mailbox_node *node, bool dirs_ok) { for (node = node->first_child; node != NULL; node = node->next) { if (node->existence == DSYNC_MAILBOX_NODE_EXISTS && (dirs_ok || !dsync_mailbox_node_is_dir(node))) return TRUE; if (node_has_existent_children(node, dirs_ok)) return TRUE; } return FALSE; } static bool node_is_existent(struct dsync_mailbox_node *node) { if (node->existence == DSYNC_MAILBOX_NODE_EXISTS) return TRUE; return node_has_existent_children(node, TRUE); } static bool sync_node_is_namespace_prefix(struct dsync_mailbox_tree *tree, struct dsync_mailbox_node *node) { const char *full_name; size_t prefix_len = node->ns == NULL ? 0 : node->ns->prefix_len; if (strcmp(node->name, "INBOX") == 0 && node->parent == &tree->root) return TRUE; if (prefix_len == 0) return FALSE; full_name = dsync_mailbox_node_get_full_name(tree, node); if (node->ns->prefix[prefix_len-1] == mail_namespace_get_sep(node->ns)) prefix_len--; return strncmp(full_name, node->ns->prefix, prefix_len) == 0 && full_name[prefix_len] == '\0'; } static void sync_rename_node_to_temp(struct dsync_mailbox_tree_sync_ctx *ctx, struct dsync_mailbox_tree *tree, struct dsync_mailbox_node *node, struct dsync_mailbox_node *new_parent, const char **reason_r) { struct dsync_mailbox_tree_sync_change *change; const char *old_name, *new_name, *p; char name[TEMP_MAX_NAME_LEN+1]; buffer_t buf; size_t prefix_len, max_prefix_len; unsigned int counter = 1; i_assert(!sync_node_is_namespace_prefix(tree, node)); buffer_create_from_data(&buf, name, sizeof(name)); max_prefix_len = TEMP_MAX_NAME_LEN - TEMP_SUFFIX_MAX_LEN - 1; if (node->sync_temporary_name) { /* the source name was also a temporary name. drop the - from it */ p = strrchr(node->name, '-'); i_assert(p != NULL); if (max_prefix_len > (size_t)(p - node->name)) max_prefix_len = p - node->name; } str_append_max(&buf, node->name, max_prefix_len); str_append_c(&buf, '-'); prefix_len = buf.used; do { str_truncate(&buf, prefix_len); str_printfa(&buf, TEMP_SUFFIX_FORMAT, counter++); /* the generated name is quite unlikely to exist, but check anyway.. */ } while (node_has_child(node->parent, str_c(&buf))); old_name = tree != ctx->local_tree ? NULL : dsync_mailbox_node_get_full_name(tree, node); *reason_r = t_strdup_printf("Renamed '%s' to '%s'", node->name, str_c(&buf)); node->name = p_strdup(tree->pool, str_c(&buf)); node->sync_temporary_name = TRUE; node->last_renamed_or_created = 0; dsync_mailbox_tree_node_move_sorted(node, new_parent); if (tree == ctx->local_tree && node_is_existent(node)) { /* we're modifying a local tree. remember this change. */ new_name = dsync_mailbox_node_get_full_name(tree, node); i_assert(ctx->sync_type != DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_LOCAL); i_assert(strcmp(old_name, "INBOX") != 0); change = array_append_space(&ctx->changes); change->type = DSYNC_MAILBOX_TREE_SYNC_TYPE_RENAME; change->ns = node->ns; change->full_name = p_strdup(ctx->pool, old_name); change->rename_dest_name = p_strdup(ctx->pool, new_name); } } static bool node_has_parent(struct dsync_mailbox_node *node, struct dsync_mailbox_node *parent) { for (; node != NULL; node = node->parent) { if (node == parent) return TRUE; } return FALSE; } static void sync_rename_node(struct dsync_mailbox_tree_sync_ctx *ctx, struct dsync_mailbox_tree *tree, struct dsync_mailbox_node *temp_node, struct dsync_mailbox_node *node, const struct dsync_mailbox_node *other_node, const char **reason_r) { struct dsync_mailbox_tree_sync_change *change; struct dsync_mailbox_tree *other_tree; struct dsync_mailbox_node *parent; const char *name, *other_name; i_assert(node != NULL); i_assert(other_node != NULL); /* move/rename node in the tree, so that its position/name is identical to other_node (in other_tree). temp_node's name is changed to temporary name (i.e. it assumes that node's name becomes temp_node's original name) */ other_tree = tree == ctx->local_tree ? ctx->remote_tree : ctx->local_tree; parent = sorted_tree_get_by_node_name(tree, other_tree, other_node->parent); if (node_has_parent(parent, node)) { /* don't introduce a loop. temporarily rename node under root. */ sync_rename_node_to_temp(ctx, tree, node, &tree->root, reason_r); *reason_r = t_strconcat(*reason_r, " (Don't introduce loop)", NULL); return; } sync_rename_node_to_temp(ctx, tree, temp_node, temp_node->parent, reason_r); /* get the old name before it's modified */ name = dsync_mailbox_node_get_full_name(tree, node); /* set the new name */ *reason_r = t_strdup_printf("%s + Renamed '%s' to '%s'", *reason_r, name, other_node->name); node->name = p_strdup(tree->pool, other_node->name); node->sync_temporary_name = other_node->sync_temporary_name; node->last_renamed_or_created = other_node->last_renamed_or_created; /* change node's parent if necessary. in any case detach+reattach it sorted, because the nodes must be sorted by name, and the node's name (or its parent) changed. */ dsync_mailbox_tree_node_move_sorted(node, parent); if (tree == ctx->local_tree && node_is_existent(node)) { /* we're modifying a local tree. remember this change. */ other_name = dsync_mailbox_node_get_full_name(other_tree, other_node); i_assert(ctx->sync_type != DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_LOCAL); i_assert(strcmp(name, "INBOX") != 0); change = array_append_space(&ctx->changes); change->type = DSYNC_MAILBOX_TREE_SYNC_TYPE_RENAME; change->ns = node->ns; change->full_name = p_strdup(ctx->pool, name); change->rename_dest_name = p_strdup(ctx->pool, other_name); } } static int node_mailbox_guids_cmp(struct dsync_mailbox_node *node1, struct dsync_mailbox_node *node2) { int ret; while (node1 != NULL && node2 != NULL) { if (node1->existence == DSYNC_MAILBOX_NODE_EXISTS && node2->existence != DSYNC_MAILBOX_NODE_EXISTS) return -1; if (node2->existence == DSYNC_MAILBOX_NODE_EXISTS && node1->existence != DSYNC_MAILBOX_NODE_EXISTS) return 1; ret = memcmp(node1->mailbox_guid, node2->mailbox_guid, sizeof(node1->mailbox_guid)); if (ret != 0) return ret; ret = node_mailbox_guids_cmp(node1->first_child, node2->first_child); if (ret != 0) return ret; node1 = node1->next; node2 = node2->next; } if (node1 == NULL && node2 == NULL) return 0; return node1 != NULL ? -1 : 1; } static int node_mailbox_names_cmp(struct dsync_mailbox_node *node1, struct dsync_mailbox_node *node2) { int ret; while (node1 != NULL && node2 != NULL) { ret = strcmp(node1->name, node2->name); if (ret != 0) return ret; ret = node_mailbox_names_cmp(node1->first_child, node2->first_child); if (ret != 0) return ret; node1 = node1->next; node2 = node2->next; } if (node1 == NULL && node2 == NULL) return 0; return node1 != NULL ? -1 : 1; } static int node_mailbox_trees_cmp(struct dsync_mailbox_node *node1, struct dsync_mailbox_node *node2) { int ret; ret = node_mailbox_guids_cmp(node1, node2); if (ret == 0) { /* only a directory name changed and all the timestamps are equal. just pick the alphabetically smaller. */ ret = node_mailbox_names_cmp(node1, node2); } i_assert(ret != 0); return ret; } static time_t nodes_get_timestamp(struct dsync_mailbox_node *node1, struct dsync_mailbox_node *node2) { time_t ts = 0; /* avoid using temporary names in case all the timestamps are 0 */ if (node1 != NULL && !node1->sync_temporary_name) ts = node1->last_renamed_or_created + 1; if (node2 != NULL && !node2->sync_temporary_name && ts <= node2->last_renamed_or_created) ts = node2->last_renamed_or_created + 1; return ts; } static bool sync_node_is_namespace_root(struct dsync_mailbox_tree *tree, struct dsync_mailbox_node *node) { if (node == NULL) return FALSE; if (node == &tree->root) return TRUE; return sync_node_is_namespace_prefix(tree, node); } static bool ATTR_NULL(3, 4) sync_rename_lower_ts(struct dsync_mailbox_tree_sync_ctx *ctx, struct dsync_mailbox_node *local_node1, struct dsync_mailbox_node *remote_node1, struct dsync_mailbox_node *local_node2, struct dsync_mailbox_node *remote_node2, const char **reason_r) { time_t local_ts, remote_ts; /* We're scanning the tree at the position of local_node1 and remote_node2. They have identical names. We also know that local_node1&remote_node1 and local_node2&remote_node2 are "the same" either because their GUIDs or (in case of one being a directory) their childrens' GUIDs match. We don't know where local_node2 or remote_node1 are located in the mailbox tree, or if they exist at all. Note that node1 and node2 may be the same node pointers. */ i_assert(strcmp(local_node1->name, remote_node2->name) == 0); if (sync_node_is_namespace_root(ctx->remote_tree, remote_node1) || sync_node_is_namespace_root(ctx->remote_tree, remote_node2) || sync_node_is_namespace_root(ctx->local_tree, local_node1) || sync_node_is_namespace_root(ctx->local_tree, local_node2)) { local_node1->sync_delayed_guid_change = TRUE; remote_node2->sync_delayed_guid_change = TRUE; *reason_r = "Can't rename namespace prefixes - will be merged later"; return FALSE; } local_ts = nodes_get_timestamp(local_node1, local_node2); remote_ts = nodes_get_timestamp(remote_node1, remote_node2); if (ctx->sync_type == DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_LOCAL) local_ts = remote_ts+1; else if (ctx->sync_type == DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_REMOTE) remote_ts = local_ts+1; /* The algorithm must be deterministic regardless of the sync direction, so in case the timestamps are equal we need to resort to looking at the other data. We'll start by looking at the nodes' mailbox GUIDs, but if both of them don't exist continue looking into their children. */ if (local_ts > remote_ts || (local_ts == remote_ts && node_mailbox_trees_cmp(local_node1, remote_node2) < 0)) { /* local nodes have a higher timestamp. we only want to do renames where the destination parent is the current node's (local_node1/remote_node2) parent. */ /* Numbers are GUIDs, letters are mailbox names: local 1A <-name conflict-> remote 2A local 2B <- potentially -> remote 1[BC] Here we want to preserve the local 1A & 2B names: */ if (local_node2 == NULL) { /* local : 1A remote: 1B, 2A -> 2A-temp, 1A */ sync_rename_node(ctx, ctx->remote_tree, remote_node2, remote_node1, local_node1, reason_r); *reason_r = t_strconcat(*reason_r, "(local: local_node2=NULL)", NULL); return TRUE; } else if (remote_node1 == remote_node2) { /* FIXME: this fixes an infinite loop when in rename2 test, think it through why :) */ *reason_r = "local: remote_node1=remote_node2"; } else if (remote_node1 != NULL) { /* a) local_node1->parent == local_node2->parent local : 1A, 2B remote: 1B, 2A -> 2A-temp, 1A(, 2B) remote: 1C, 2A -> 2B, 1A remote: 1C, 2A, 3B -> 2A-temp, 1A(, 3B-temp, 2B) b) local_node1->parent != local_node2->parent local : 1X/A, 2Y/B remote: 1Y/B, 2X/A -> 2X/A-temp, 1X/A(, 2Y/B) remote: 1Z/C, 2X/A -> 2X/A-temp, 1X/A remote: 1Z/C, 2X/A, 3Y/B -> 2X/A-temp, 1X/A We can handle all of these more easily by simply always renaming 2 to a temporary name and handling it when we reach B handling. */ sync_rename_node(ctx, ctx->remote_tree, remote_node2, remote_node1, local_node1, reason_r); *reason_r = t_strconcat(*reason_r, "(local: remote_node1=NULL)", NULL); return TRUE; } else if (node_has_parent(local_node1, local_node2) && ctx->sync_type != DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_LOCAL) { /* node2 is a parent of node1, but it should be vice versa */ sync_rename_node_to_temp(ctx, ctx->local_tree, local_node1, local_node2->parent, reason_r); *reason_r = t_strconcat(*reason_r, "(local: node2 parent of node1)", NULL); return TRUE; } else if (node_has_parent(local_node2, local_node1) && ctx->sync_type != DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_LOCAL) { /* node1 is a parent of node2, but it should be vice versa */ sync_rename_node_to_temp(ctx, ctx->local_tree, local_node2, local_node1->parent, reason_r); *reason_r = t_strconcat(*reason_r, "(local: node1 parent of node2)", NULL); return TRUE; } else if (local_node1->existence == DSYNC_MAILBOX_NODE_EXISTS) { sync_rename_node_to_temp(ctx, ctx->remote_tree, remote_node2, remote_node2->parent, reason_r); *reason_r = t_strconcat(*reason_r, "(local: local_node1 exists)", NULL); return TRUE; } else { /* local : 1A, 2B remote: 2A -> (2B) remote: 2A, 3B -> (3B-temp, 2B) */ *reason_r = "local: unchanged"; } } else { /* remote nodes have a higher timestamp */ if (remote_node1 == NULL) { sync_rename_node(ctx, ctx->local_tree, local_node1, local_node2, remote_node2, reason_r); *reason_r = t_strconcat(*reason_r, "(remote: remote_node1=NULL)", NULL); return TRUE; } else if (local_node2 == local_node1) { *reason_r = "remote: remote_node2=remote_node1"; } else if (local_node2 != NULL) { sync_rename_node(ctx, ctx->local_tree, local_node1, local_node2, remote_node2, reason_r); *reason_r = t_strconcat(*reason_r, "(remote: local_node2=NULL)", NULL); return TRUE; } else if (node_has_parent(remote_node1, remote_node2) && ctx->sync_type != DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_REMOTE) { sync_rename_node_to_temp(ctx, ctx->remote_tree, remote_node1, remote_node2->parent, reason_r); *reason_r = t_strconcat(*reason_r, "(remote: node2 parent of node1)", NULL); return TRUE; } else if (node_has_parent(remote_node2, remote_node1) && ctx->sync_type != DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_REMOTE) { sync_rename_node_to_temp(ctx, ctx->remote_tree, remote_node2, remote_node1->parent, reason_r); *reason_r = t_strconcat(*reason_r, "(remote: node1 parent of node2)", NULL); return TRUE; } else if (remote_node2->existence == DSYNC_MAILBOX_NODE_EXISTS) { sync_rename_node_to_temp(ctx, ctx->local_tree, local_node1, local_node1->parent, reason_r); *reason_r = t_strconcat(*reason_r, "(remote: remote_node2 exists)", NULL); return TRUE; } else { *reason_r = "remote: unchanged"; } } return FALSE; } static bool sync_rename_conflict(struct dsync_mailbox_tree_sync_ctx *ctx, struct dsync_mailbox_node *local_node1, struct dsync_mailbox_node *remote_node2, const char **reason_r) { struct dsync_mailbox_node *local_node2, *remote_node1; const uint8_t *guid_p; bool ret; guid_p = local_node1->mailbox_guid; remote_node1 = hash_table_lookup(ctx->remote_tree->guid_hash, guid_p); guid_p = remote_node2->mailbox_guid; local_node2 = hash_table_lookup(ctx->local_tree->guid_hash, guid_p); if ((remote_node1 != NULL && remote_node1->existence == DSYNC_MAILBOX_NODE_EXISTS) || (local_node2 != NULL && local_node2->existence == DSYNC_MAILBOX_NODE_EXISTS)) { /* conflicting name, rename the one with lower timestamp */ ret = sync_rename_lower_ts(ctx, local_node1, remote_node1, local_node2, remote_node2, reason_r); *reason_r = t_strconcat("conflicting name: ", *reason_r, NULL); return ret; } else if (dsync_mailbox_node_is_dir(local_node1) || dsync_mailbox_node_is_dir(remote_node2)) { /* one of the nodes is a directory, and the other is a mailbox that doesn't exist on the other side. there is no conflict, we'll just need to create the mailbox later. */ *reason_r = "mailbox not selectable yet"; return FALSE; } else { /* both nodes are mailboxes that don't exist on the other side. we'll merge these mailboxes together later and change their GUIDs and UIDVALIDITYs to be the same */ local_node1->sync_delayed_guid_change = TRUE; remote_node2->sync_delayed_guid_change = TRUE; *reason_r = "GUIDs conflict - will be merged later"; return FALSE; } } static struct dsync_mailbox_node * sync_find_branch(struct dsync_mailbox_tree *tree, struct dsync_mailbox_tree *other_tree, struct dsync_mailbox_node *dir_node) { struct dsync_mailbox_node *node, *other_node; const uint8_t *guid_p; for (node = dir_node->first_child; node != NULL; node = node->next) { if (dsync_mailbox_node_is_dir(node)) { other_node = sync_find_branch(tree, other_tree, node); if (other_node != NULL) return other_node; } else { guid_p = node->mailbox_guid; other_node = hash_table_lookup(other_tree->guid_hash, guid_p); if (other_node != NULL) return other_node->parent; } } return NULL; } static bool sync_rename_directory(struct dsync_mailbox_tree_sync_ctx *ctx, struct dsync_mailbox_node *local_node1, struct dsync_mailbox_node *remote_node2, const char **reason_r) { struct dsync_mailbox_node *remote_node1, *local_node2; /* see if we can find matching mailbox branches based on the nodes' child mailboxes (with GUIDs). we can then rename the entire branch. don't try to do this for namespace prefixes though. */ remote_node1 = sync_find_branch(ctx->local_tree, ctx->remote_tree, local_node1); local_node2 = sync_find_branch(ctx->remote_tree, ctx->local_tree, remote_node2); if (remote_node1 == NULL || local_node2 == NULL) { *reason_r = "Directory rename branch not found"; return FALSE; } if (node_names_equal(remote_node1, local_node2)) { *reason_r = "Directory name paths are equal"; return FALSE; } return sync_rename_lower_ts(ctx, local_node1, remote_node1, local_node2, remote_node2, reason_r); } static bool sync_rename_mailboxes(struct dsync_mailbox_tree_sync_ctx *ctx, struct dsync_mailbox_node *local_parent, struct dsync_mailbox_node *remote_parent) { struct dsync_mailbox_node **local_nodep = &local_parent->first_child; struct dsync_mailbox_node **remote_nodep = &remote_parent->first_child; struct dsync_mailbox_node *local_node, *remote_node; const char *reason; string_t *debug = NULL; bool changed; if ((ctx->sync_flags & DSYNC_MAILBOX_TREES_SYNC_FLAG_DEBUG) != 0) debug = t_str_new(128); /* the nodes are sorted by their names. */ while (*local_nodep != NULL || *remote_nodep != NULL) { local_node = *local_nodep; remote_node = *remote_nodep; if (local_node == NULL || (remote_node != NULL && strcmp(local_node->name, remote_node->name) > 0)) { /* add a missing local node */ local_node = sync_node_new(ctx->local_tree, local_nodep, local_parent, remote_node); } if (remote_node == NULL || strcmp(remote_node->name, local_node->name) > 0) { /* add a missing remote node */ remote_node = sync_node_new(ctx->remote_tree, remote_nodep, remote_parent, local_node); } i_assert(strcmp(local_node->name, remote_node->name) == 0); if (debug != NULL) { str_truncate(debug, 0); str_append(debug, "Mailbox "); dsync_mailbox_node_append_full_name(debug, ctx->local_tree, local_node); str_printfa(debug, ": local=%s/%ld/%d, remote=%s/%ld/%d", guid_128_to_string(local_node->mailbox_guid), (long)local_node->last_renamed_or_created, local_node->existence, guid_128_to_string(remote_node->mailbox_guid), (long)remote_node->last_renamed_or_created, remote_node->existence); } if (dsync_mailbox_node_is_dir(local_node) && dsync_mailbox_node_is_dir(remote_node)) { /* both nodes are directories (or other side is nonexistent). see if we can match them by their child mailboxes */ changed = sync_rename_directory(ctx, local_node, remote_node, &reason); } else if (dsync_mailbox_node_guids_equal(local_node, remote_node)) { /* mailboxes are equal, no need to rename */ reason = "Mailboxes are equal"; changed = FALSE; } else { /* mailbox naming conflict */ changed = sync_rename_conflict(ctx, local_node, remote_node, &reason); } /* handle children, if there are any */ if (debug != NULL) { i_debug("brain %c: %s: %s", (ctx->sync_flags & DSYNC_MAILBOX_TREES_SYNC_FLAG_MASTER_BRAIN) != 0 ? 'M' : 'S', str_c(debug), reason); } if (!changed) T_BEGIN { changed = sync_rename_mailboxes(ctx, local_node, remote_node); } T_END; if (changed) return TRUE; local_nodep = &local_node->next; remote_nodep = &remote_node->next; } return FALSE; } static bool mailbox_node_hash_first_child(struct dsync_mailbox_node *node, struct md5_context *md5) { for (node = node->first_child; node != NULL; node = node->next) { if (node->existence == DSYNC_MAILBOX_NODE_EXISTS) { md5_update(md5, node->mailbox_guid, sizeof(node->mailbox_guid)); md5_update(md5, node->name, strlen(node->name)); return TRUE; } if (node->first_child != NULL) { if (mailbox_node_hash_first_child(node, md5)) return TRUE; } } return FALSE; } static const char * mailbox_node_generate_suffix(struct dsync_mailbox_node *node) { struct md5_context md5; unsigned char digest[MD5_RESULTLEN]; if (!dsync_mailbox_node_is_dir(node)) return guid_128_to_string(node->mailbox_guid); md5_init(&md5); if (!mailbox_node_hash_first_child(node, &md5)) i_unreached(); /* we would have deleted it */ md5_final(&md5, digest); return binary_to_hex(digest, sizeof(digest)); } static void suffix_inc(string_t *str) { char *data; size_t i; data = str_c_modifiable(str) + str_len(str)-1; for (i = str_len(str); i > 0; i--, data--) { if ((*data >= '0' && *data < '9') || (*data >= 'a' && *data < 'f')) { *data += 1; return; } else if (*data == '9') { *data = 'a'; return; } else if (*data != 'f') { i_unreached(); } } i_unreached(); } static void sync_rename_temp_mailbox_node(struct dsync_mailbox_tree *tree, struct dsync_mailbox_node *node, const char **reason_r) { const char *p, *new_suffix; string_t *str = t_str_new(256); size_t max_prefix_len; /* The name is currently -. Both sides need to use equivalent names, so we'll replace the if possible with a) mailbox GUID, b) sha1 of childrens' (GUID|name)s. In the very unlikely case of such name already existing, just increase the last letters until it's not found. */ new_suffix = mailbox_node_generate_suffix(node); p = strrchr(node->name, '-'); i_assert(p != NULL); p++; max_prefix_len = TEMP_MAX_NAME_LEN - strlen(new_suffix) - 1; if (max_prefix_len > (size_t)(p-node->name)) max_prefix_len = p-node->name; str_append_max(str, node->name, max_prefix_len); str_append(str, new_suffix); while (node_has_child(node->parent, str_c(str))) suffix_inc(str); *reason_r = t_strdup_printf("Renamed '%s' to '%s'", dsync_mailbox_node_get_full_name(tree, node), str_c(str)); node->name = p_strdup(tree->pool, str_c(str)); dsync_mailbox_tree_node_move_sorted(node, node->parent); node->sync_temporary_name = FALSE; } static void sync_rename_delete_node_dirs(struct dsync_mailbox_tree_sync_ctx *ctx, struct dsync_mailbox_tree *tree, struct dsync_mailbox_node *node) { struct dsync_mailbox_node *child; for (child = node->first_child; child != NULL; child = child->next) sync_rename_delete_node_dirs(ctx, tree, child); if (tree == ctx->local_tree && ctx->sync_type != DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_LOCAL && node->existence != DSYNC_MAILBOX_NODE_NONEXISTENT) { sync_add_dir_change(ctx, node, DSYNC_MAILBOX_TREE_SYNC_TYPE_DELETE_DIR); } node->existence = DSYNC_MAILBOX_NODE_NONEXISTENT; node->sync_temporary_name = FALSE; } static bool sync_rename_temp_mailboxes(struct dsync_mailbox_tree_sync_ctx *ctx, struct dsync_mailbox_tree *tree, struct dsync_mailbox_node *node, bool *renames_r) { const char *reason; for (; node != NULL; node = node->next) { while (sync_rename_temp_mailboxes(ctx, tree, node->first_child, renames_r)) ; if (!node->sync_temporary_name) { } else if (dsync_mailbox_node_is_dir(node) && (node->first_child == NULL || !node_has_existent_children(node, FALSE))) { /* we can just delete this directory and any child directories it may have */ if ((ctx->sync_flags & DSYNC_MAILBOX_TREES_SYNC_FLAG_DEBUG) != 0) { i_debug("brain %c: %s mailbox %s: Delete directory-only tree", (ctx->sync_flags & DSYNC_MAILBOX_TREES_SYNC_FLAG_MASTER_BRAIN) != 0 ? 'M' : 'S', tree == ctx->local_tree ? "local" : "remote", dsync_mailbox_node_get_full_name(tree, node)); } sync_rename_delete_node_dirs(ctx, tree, node); } else { T_BEGIN { *renames_r = TRUE; sync_rename_temp_mailbox_node(tree, node, &reason); if ((ctx->sync_flags & DSYNC_MAILBOX_TREES_SYNC_FLAG_DEBUG) != 0) { i_debug("brain %c: %s mailbox %s: %s", (ctx->sync_flags & DSYNC_MAILBOX_TREES_SYNC_FLAG_MASTER_BRAIN) != 0 ? 'M' : 'S', tree == ctx->local_tree ? "local" : "remote", dsync_mailbox_node_get_full_name(tree, node), reason); } } T_END; return TRUE; } } return FALSE; } static int dsync_mailbox_tree_handle_renames(struct dsync_mailbox_tree_sync_ctx *ctx, bool *renames_r) { unsigned int max_renames, count = 0; bool changed; *renames_r = FALSE; max_renames = ctx->combined_mailboxes_count * 3; do { T_BEGIN { changed = sync_rename_mailboxes(ctx, &ctx->local_tree->root, &ctx->remote_tree->root); } T_END; if ((ctx->sync_flags & DSYNC_MAILBOX_TREES_SYNC_FLAG_DEBUG) != 0 && changed) { i_debug("brain %c: -- Mailbox renamed, restart sync --", (ctx->sync_flags & DSYNC_MAILBOX_TREES_SYNC_FLAG_MASTER_BRAIN) != 0 ? 'M' : 'S'); } } while (changed && ++count <= max_renames); if (changed) { i_error("BUG: Mailbox renaming algorithm got into a potentially infinite loop, aborting"); return -1; } while (sync_rename_temp_mailboxes(ctx, ctx->local_tree, &ctx->local_tree->root, renames_r)) ; while (sync_rename_temp_mailboxes(ctx, ctx->remote_tree, &ctx->remote_tree->root, renames_r)) ; return 0; } static bool sync_is_wrong_mailbox(struct dsync_mailbox_node *node, const struct dsync_mailbox_node *wanted_node, const char **reason_r) { if (wanted_node->existence != DSYNC_MAILBOX_NODE_EXISTS) { *reason_r = wanted_node->existence == DSYNC_MAILBOX_NODE_DELETED ? "Mailbox has been deleted" : "Mailbox doesn't exist"; return TRUE; } if (node->uid_validity != wanted_node->uid_validity) { *reason_r = t_strdup_printf("UIDVALIDITY changed (%u -> %u)", wanted_node->uid_validity, node->uid_validity); return TRUE; } if (node->uid_next > wanted_node->uid_next) { /* we can't lower the UIDNEXT */ *reason_r = t_strdup_printf("UIDNEXT is too high (%u > %u)", node->uid_next, wanted_node->uid_next); return TRUE; } if (memcmp(node->mailbox_guid, wanted_node->mailbox_guid, sizeof(node->mailbox_guid)) != 0) { *reason_r = "GUID changed"; return TRUE; } return FALSE; } static void sync_delete_wrong_mailboxes_branch(struct dsync_mailbox_tree_sync_ctx *ctx, struct dsync_mailbox_tree *tree, const struct dsync_mailbox_tree *wanted_tree, struct dsync_mailbox_node *node, const struct dsync_mailbox_node *wanted_node) { const char *reason; int ret; while (node != NULL && wanted_node != NULL) { if (node->first_child != NULL) { sync_delete_wrong_mailboxes_branch(ctx, tree, wanted_tree, node->first_child, wanted_node->first_child); } ret = strcmp(node->name, wanted_node->name); if (ret < 0) { /* node shouldn't exist */ if (node->existence == DSYNC_MAILBOX_NODE_EXISTS && !dsync_mailbox_node_is_dir(node)) { sync_delete_mailbox_node(ctx, tree, node, "Mailbox doesn't exist"); } node = node->next; } else if (ret > 0) { /* wanted_node doesn't exist. it's created later. */ wanted_node = wanted_node->next; } else T_BEGIN { if (sync_is_wrong_mailbox(node, wanted_node, &reason) && node->existence == DSYNC_MAILBOX_NODE_EXISTS && !dsync_mailbox_node_is_dir(node)) sync_delete_mailbox_node(ctx, tree, node, reason); node = node->next; wanted_node = wanted_node->next; } T_END; } for (; node != NULL; node = node->next) { /* node and its children shouldn't exist */ if (node->first_child != NULL) { sync_delete_wrong_mailboxes_branch(ctx, tree, wanted_tree, node->first_child, NULL); } if (node->existence == DSYNC_MAILBOX_NODE_EXISTS && !dsync_mailbox_node_is_dir(node)) sync_delete_mailbox_node(ctx, tree, node, "Mailbox doesn't exist"); } } static void sync_delete_wrong_mailboxes(struct dsync_mailbox_tree_sync_ctx *ctx, struct dsync_mailbox_tree *tree, const struct dsync_mailbox_tree *wanted_tree) { sync_delete_wrong_mailboxes_branch(ctx, tree, wanted_tree, tree->root.first_child, wanted_tree->root.first_child); } static void sync_create_mailboxes(struct dsync_mailbox_tree_sync_ctx *ctx, struct dsync_mailbox_tree *tree) { struct dsync_mailbox_tree *other_tree; struct dsync_mailbox_tree_iter *iter; struct dsync_mailbox_node *node, *other_node; const char *name; const uint8_t *guid_p; other_tree = tree == ctx->local_tree ? ctx->remote_tree : ctx->local_tree; iter = dsync_mailbox_tree_iter_init(tree); while (dsync_mailbox_tree_iter_next(iter, &name, &node)) { /* make sure the renaming handled everything */ i_assert(!node->sync_temporary_name); if (dsync_mailbox_node_is_dir(node)) continue; i_assert(node->existence == DSYNC_MAILBOX_NODE_EXISTS); guid_p = node->mailbox_guid; other_node = hash_table_lookup(other_tree->guid_hash, guid_p); if (other_node == NULL) other_node = sorted_tree_get(other_tree, name); if (dsync_mailbox_node_is_dir(other_node)) { /* create a missing mailbox */ other_node->existence = DSYNC_MAILBOX_NODE_EXISTS; other_node->ns = node->ns; other_node->uid_validity = node->uid_validity; memcpy(other_node->mailbox_guid, node->mailbox_guid, sizeof(other_node->mailbox_guid)); if (other_tree == ctx->local_tree) sync_add_create_change(ctx, other_node, name); } else if (!guid_128_equals(node->mailbox_guid, other_node->mailbox_guid)) { /* mailbox with same name exists both locally and remotely, but they have different GUIDs and neither side has the other's GUID. typically this means that both sides had autocreated some mailboxes (e.g. INBOX). we'll just change the GUID for one of them. */ i_assert(node->existence == DSYNC_MAILBOX_NODE_EXISTS); i_assert(node->ns == other_node->ns); if (other_tree == ctx->local_tree) sync_add_create_change(ctx, node, name); } else { /* existing mailbox. mismatching UIDVALIDITY is handled later while syncing the mailbox. */ i_assert(node->existence == DSYNC_MAILBOX_NODE_EXISTS); i_assert(node->ns == other_node->ns); } } dsync_mailbox_tree_iter_deinit(&iter); } static void sync_subscription(struct dsync_mailbox_tree_sync_ctx *ctx, struct dsync_mailbox_node *local_node, struct dsync_mailbox_node *remote_node) { bool use_local; if (ctx->sync_type == DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_LOCAL) use_local = TRUE; else if (ctx->sync_type == DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_REMOTE) use_local = FALSE; else if (local_node->last_subscription_change > remote_node->last_subscription_change) use_local = TRUE; else if (local_node->last_subscription_change < remote_node->last_subscription_change) use_local = FALSE; else { /* local and remote have equal timestamps. prefer to subscribe rather than unsubscribe. */ use_local = local_node->subscribed; } if (use_local) { /* use local subscription state */ remote_node->subscribed = local_node->subscribed; } else { /* use remote subscription state */ local_node->subscribed = remote_node->subscribed; sync_add_dir_change(ctx, local_node, local_node->subscribed ? DSYNC_MAILBOX_TREE_SYNC_TYPE_SUBSCRIBE : DSYNC_MAILBOX_TREE_SYNC_TYPE_UNSUBSCRIBE); } } static void sync_mailbox_child_dirs(struct dsync_mailbox_tree_sync_ctx *ctx, struct dsync_mailbox_node *local_parent, struct dsync_mailbox_node *remote_parent) { struct dsync_mailbox_node **local_nodep = &local_parent->first_child; struct dsync_mailbox_node **remote_nodep = &remote_parent->first_child; struct dsync_mailbox_node *local_node, *remote_node; int ret; /* NOTE: the nodes are always sorted. renaming created all of the interesting nodes, but it may have left some extra nonexistent nodes lying around, which we will delete. */ while (*local_nodep != NULL && *remote_nodep != NULL) { local_node = *local_nodep; remote_node = *remote_nodep; ret = strcmp(local_node->name, remote_node->name); if (ret < 0) { i_assert(!node_is_existent(local_node)); *local_nodep = local_node->next; continue; } if (ret > 0) { i_assert(!node_is_existent(remote_node)); *remote_nodep = remote_node->next; continue; } if (local_node->existence == DSYNC_MAILBOX_NODE_EXISTS && remote_node->existence == DSYNC_MAILBOX_NODE_NONEXISTENT && ctx->sync_type != DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_REMOTE) { /* create to remote */ remote_node->existence = DSYNC_MAILBOX_NODE_EXISTS; } if (remote_node->existence == DSYNC_MAILBOX_NODE_EXISTS && local_node->existence == DSYNC_MAILBOX_NODE_NONEXISTENT && ctx->sync_type != DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_LOCAL) { /* create to local */ local_node->existence = DSYNC_MAILBOX_NODE_EXISTS; sync_add_dir_change(ctx, local_node, DSYNC_MAILBOX_TREE_SYNC_TYPE_CREATE_DIR); } /* create/delete child dirs */ sync_mailbox_child_dirs(ctx, local_node, remote_node); if (local_node->subscribed != remote_node->subscribed) sync_subscription(ctx, local_node, remote_node); if (local_node->existence == DSYNC_MAILBOX_NODE_DELETED && !node_has_existent_children(local_node, TRUE) && remote_node->existence == DSYNC_MAILBOX_NODE_EXISTS && ctx->sync_type != DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_REMOTE) { /* delete from remote */ i_assert(!node_has_existent_children(remote_node, TRUE)); remote_node->existence = DSYNC_MAILBOX_NODE_NONEXISTENT; } if (remote_node->existence == DSYNC_MAILBOX_NODE_DELETED && !node_has_existent_children(remote_node, TRUE) && local_node->existence == DSYNC_MAILBOX_NODE_EXISTS && ctx->sync_type != DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_LOCAL) { /* delete from local */ i_assert(!node_has_existent_children(local_node, TRUE)); local_node->existence = DSYNC_MAILBOX_NODE_NONEXISTENT; sync_add_dir_change(ctx, local_node, DSYNC_MAILBOX_TREE_SYNC_TYPE_DELETE_DIR); } local_nodep = &local_node->next; remote_nodep = &remote_node->next; } while (*local_nodep != NULL) { i_assert(!node_is_existent(*local_nodep)); *local_nodep = (*local_nodep)->next; } while (*remote_nodep != NULL) { i_assert(!node_is_existent(*remote_nodep)); *remote_nodep = (*remote_nodep)->next; } } static void sync_mailbox_dirs(struct dsync_mailbox_tree_sync_ctx *ctx) { sync_mailbox_child_dirs(ctx, &ctx->local_tree->root, &ctx->remote_tree->root); } static void dsync_mailbox_tree_update_child_timestamps(struct dsync_mailbox_node *node, time_t parent_timestamp) { time_t ts; if (node->last_renamed_or_created < parent_timestamp) node->last_renamed_or_created = parent_timestamp; ts = node->last_renamed_or_created; for (node = node->first_child; node != NULL; node = node->next) dsync_mailbox_tree_update_child_timestamps(node, ts); } struct dsync_mailbox_tree_sync_ctx * dsync_mailbox_trees_sync_init(struct dsync_mailbox_tree *local_tree, struct dsync_mailbox_tree *remote_tree, enum dsync_mailbox_trees_sync_type sync_type, enum dsync_mailbox_trees_sync_flags sync_flags) { struct dsync_mailbox_tree_sync_ctx *ctx; unsigned int rename_counter = 0; bool renames; pool_t pool; i_assert(hash_table_is_created(local_tree->guid_hash)); i_assert(hash_table_is_created(remote_tree->guid_hash)); pool = pool_alloconly_create(MEMPOOL_GROWING"dsync mailbox trees sync", 1024*64); ctx = p_new(pool, struct dsync_mailbox_tree_sync_ctx, 1); ctx->pool = pool; ctx->local_tree = local_tree; ctx->remote_tree = remote_tree; ctx->sync_type = sync_type; ctx->sync_flags = sync_flags; i_array_init(&ctx->changes, 128); again: renames = FALSE; ctx->combined_mailboxes_count = 0; sync_tree_sort_and_delete_mailboxes(ctx, remote_tree, sync_type == DSYNC_MAILBOX_TREES_SYNC_TYPE_TWOWAY); sync_tree_sort_and_delete_mailboxes(ctx, local_tree, sync_type == DSYNC_MAILBOX_TREES_SYNC_TYPE_TWOWAY); dsync_mailbox_tree_update_child_timestamps(&local_tree->root, 0); dsync_mailbox_tree_update_child_timestamps(&remote_tree->root, 0); if ((sync_flags & DSYNC_MAILBOX_TREES_SYNC_FLAG_NO_RENAMES) == 0) { if (dsync_mailbox_tree_handle_renames(ctx, &renames) < 0) { ctx->failed = TRUE; return ctx; } } /* if we're not doing a two-way sync, delete now any mailboxes, which a) shouldn't exist, b) doesn't have a matching GUID/UIDVALIDITY, c) has a too high UIDNEXT */ if (sync_type == DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_LOCAL) sync_delete_wrong_mailboxes(ctx, remote_tree, local_tree); else if (sync_type == DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_REMOTE) sync_delete_wrong_mailboxes(ctx, local_tree, remote_tree); if (sync_type != DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_LOCAL) sync_create_mailboxes(ctx, remote_tree); if (sync_type != DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_REMOTE) sync_create_mailboxes(ctx, local_tree); if (renames && rename_counter++ <= ctx->combined_mailboxes_count*3) { /* this rename algorithm is just horrible. we're retrying this because the final sync_rename_temp_mailbox_node() calls give different names to local & remote mailbox trees. something's not right here, but this looping is better than a crash in sync_mailbox_dirs() due to trees not matching. */ goto again; } sync_mailbox_dirs(ctx); return ctx; } const struct dsync_mailbox_tree_sync_change * dsync_mailbox_trees_sync_next(struct dsync_mailbox_tree_sync_ctx *ctx) { if (ctx->change_idx == array_count(&ctx->changes)) return NULL; return array_idx(&ctx->changes, ctx->change_idx++); } int dsync_mailbox_trees_sync_deinit(struct dsync_mailbox_tree_sync_ctx **_ctx) { struct dsync_mailbox_tree_sync_ctx *ctx = *_ctx; int ret = ctx->failed ? -1 : 0; *_ctx = NULL; array_free(&ctx->changes); pool_unref(&ctx->pool); return ret; } dovecot-2.3.21.1/src/doveadm/dsync/dsync-brain-mailbox-tree.c0000644000000000000000000004362614656633576020621 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "mail-namespace.h" #include "mailbox-list-private.h" #include "dsync-ibc.h" #include "dsync-mailbox-tree.h" #include "dsync-brain-private.h" #include static void dsync_brain_check_namespaces(struct dsync_brain *brain) { struct mail_namespace *ns, *first_ns = NULL; char sep, escape_char; i_assert(brain->hierarchy_sep == '\0'); i_assert(brain->escape_char == '\0'); for (ns = brain->user->namespaces; ns != NULL; ns = ns->next) { if (!dsync_brain_want_namespace(brain, ns)) continue; sep = mail_namespace_get_sep(ns); escape_char = mailbox_list_get_settings(ns->list)->vname_escape_char; if (first_ns == NULL) { brain->hierarchy_sep = sep; brain->escape_char = escape_char; first_ns = ns; } else if (brain->hierarchy_sep != sep) { i_fatal("Synced namespaces have conflicting separators " "('%c' for prefix=\"%s\", '%c' for prefix=\"%s\")", brain->hierarchy_sep, first_ns->prefix, sep, ns->prefix); } else if (brain->escape_char != escape_char) { i_fatal("Synced namespaces have conflicting escape chars " "('%c' for prefix=\"%s\", '%c' for prefix=\"%s\")", brain->escape_char, first_ns->prefix, escape_char, ns->prefix); } } if (brain->hierarchy_sep != '\0') return; i_fatal("All your namespaces have a location setting. " "Only namespaces with empty location settings are converted. " "(One namespace should default to mail_location setting)"); } void dsync_brain_mailbox_trees_init(struct dsync_brain *brain) { struct mail_namespace *ns; dsync_brain_check_namespaces(brain); brain->local_mailbox_tree = dsync_mailbox_tree_init(brain->hierarchy_sep, brain->escape_char, brain->alt_char); /* we'll convert remote mailbox names to use our own separator */ brain->remote_mailbox_tree = dsync_mailbox_tree_init(brain->hierarchy_sep, brain->escape_char, brain->alt_char); /* fill the local mailbox tree */ for (ns = brain->user->namespaces; ns != NULL; ns = ns->next) { if (!dsync_brain_want_namespace(brain, ns)) continue; if (brain->debug) i_debug("brain %c: Namespace %s has location %s", brain->master_brain ? 'M' : 'S', ns->prefix, ns->set->location); if (dsync_mailbox_tree_fill(brain->local_mailbox_tree, ns, brain->sync_box, brain->sync_box_guid, brain->exclude_mailboxes, brain->alt_char, &brain->mail_error) < 0) { brain->failed = TRUE; break; } } brain->local_tree_iter = dsync_mailbox_tree_iter_init(brain->local_mailbox_tree); } void dsync_brain_send_mailbox_tree(struct dsync_brain *brain) { struct dsync_mailbox_node *node; enum dsync_ibc_send_ret ret; const char *full_name; while (dsync_mailbox_tree_iter_next(brain->local_tree_iter, &full_name, &node)) { if (node->ns == NULL) { /* This node was created when adding a namespace prefix to the tree that has multiple hierarchical names, but the parent names don't belong to any synced namespace. For example when syncing "-n Shared/user/" so "Shared/" is skipped. Or if there is e.g. "Public/files/" namespace prefix, but no "Public/" namespace at all. */ continue; } T_BEGIN { const char *const *parts; if (brain->debug) { i_debug("brain %c: Local mailbox tree: %s %s", brain->master_brain ? 'M' : 'S', full_name, dsync_mailbox_node_to_string(node)); } parts = dsync_mailbox_name_to_parts(full_name, brain->hierarchy_sep, brain->escape_char); ret = dsync_ibc_send_mailbox_tree_node(brain->ibc, parts, node); } T_END; if (ret == DSYNC_IBC_SEND_RET_FULL) return; } dsync_mailbox_tree_iter_deinit(&brain->local_tree_iter); dsync_ibc_send_end_of_list(brain->ibc, DSYNC_IBC_EOL_MAILBOX_TREE); brain->state = DSYNC_STATE_SEND_MAILBOX_TREE_DELETES; } void dsync_brain_send_mailbox_tree_deletes(struct dsync_brain *brain) { const struct dsync_mailbox_delete *deletes; unsigned int count; deletes = dsync_mailbox_tree_get_deletes(brain->local_mailbox_tree, &count); dsync_ibc_send_mailbox_deletes(brain->ibc, deletes, count, brain->hierarchy_sep, brain->escape_char); brain->state = DSYNC_STATE_RECV_MAILBOX_TREE; } static bool dsync_namespace_match_parts(struct mail_namespace *ns, const char *const *name_parts) { const char *part, *prefix = ns->prefix; size_t part_len; char ns_sep = mail_namespace_get_sep(ns); if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 && strcmp(name_parts[0], "INBOX") == 0 && name_parts[1] == NULL) return TRUE; for (; *name_parts != NULL && *prefix != '\0'; name_parts++) { part = *name_parts; part_len = strlen(part); if (!str_begins(prefix, part)) return FALSE; if (prefix[part_len] != ns_sep) return FALSE; prefix += part_len + 1; } if (*name_parts != NULL) { /* namespace prefix found with a mailbox */ return TRUE; } if (*prefix == '\0') { /* namespace prefix itself matched */ return TRUE; } return FALSE; } static struct mail_namespace * dsync_find_namespace(struct dsync_brain *brain, const char *const *name_parts) { struct mail_namespace *ns, *best_ns = NULL; for (ns = brain->user->namespaces; ns != NULL; ns = ns->next) { if (!dsync_brain_want_namespace(brain, ns)) continue; if (ns->prefix_len == 0) { /* prefix="" is the fallback namespace */ if (best_ns == NULL) best_ns = ns; } else if (dsync_namespace_match_parts(ns, name_parts)) { if (best_ns == NULL || best_ns->prefix_len < ns->prefix_len) best_ns = ns; } } return best_ns; } static bool dsync_is_valid_name(struct mail_namespace *ns, const char *vname) { struct mailbox *box; bool ret; box = mailbox_alloc(ns->list, vname, MAILBOX_FLAG_READONLY); ret = mailbox_verify_create_name(box) == 0; mailbox_free(&box); return ret; } static bool dsync_is_valid_name_until(struct mail_namespace *ns, string_t *vname_full, unsigned int end_pos) { const char *vname; if (end_pos == str_len(vname_full)) vname = str_c(vname_full); else vname = t_strndup(str_c(vname_full), end_pos); return dsync_is_valid_name(ns, vname); } static bool dsync_fix_mailbox_name_until(struct mail_namespace *ns, string_t *vname_full, char alt_char, unsigned int start_pos, unsigned int *_end_pos) { unsigned int end_pos = *_end_pos; unsigned int i; if (dsync_is_valid_name_until(ns, vname_full, end_pos)) return TRUE; /* 1) change any real separators to alt separators (this wouldn't be necessary with listescape, but don't bother detecting it) */ char list_sep = mailbox_list_get_hierarchy_sep(ns->list); char ns_sep = mail_namespace_get_sep(ns); if (list_sep != ns_sep) { char *v = str_c_modifiable(vname_full); for (i = start_pos; i < end_pos; i++) { if (v[i] == list_sep) v[i] = alt_char; } if (dsync_is_valid_name_until(ns, vname_full, end_pos)) return TRUE; } /* 2) '/' characters aren't valid without listescape */ if (ns_sep != '/' && list_sep != '/') { char *v = str_c_modifiable(vname_full); for (i = start_pos; i < end_pos; i++) { if (v[i] == '/') v[i] = alt_char; } if (dsync_is_valid_name_until(ns, vname_full, end_pos)) return TRUE; } /* 3) probably some reserved name (e.g. dbox-Mails or ..) */ str_insert(vname_full, start_pos, "_"); end_pos++; *_end_pos += 1; if (dsync_is_valid_name_until(ns, vname_full, end_pos)) return TRUE; return FALSE; } static void dsync_fix_mailbox_name(struct mail_namespace *ns, string_t *vname_str, char alt_char) { const char *old_vname; char *vname; char ns_sep = mail_namespace_get_sep(ns); guid_128_t guid; unsigned int i, start_pos; vname = str_c_modifiable(vname_str); if (strncmp(vname, ns->prefix, ns->prefix_len) == 0) start_pos = ns->prefix_len; else start_pos = 0; /* replace control chars */ for (i = start_pos; vname[i] != '\0'; i++) { if ((unsigned char)vname[i] < ' ') vname[i] = alt_char; } /* make it valid UTF8 */ if (!uni_utf8_str_is_valid(vname)) { old_vname = t_strdup(vname + start_pos); str_truncate(vname_str, start_pos); if (uni_utf8_get_valid_data((const void *)old_vname, strlen(old_vname), vname_str)) i_unreached(); vname = str_c_modifiable(vname_str); } if (dsync_is_valid_name(ns, vname)) return; /* Check/fix each hierarchical name separately */ const char *p; do { i_assert(start_pos <= str_len(vname_str)); p = strchr(str_c(vname_str) + start_pos, ns_sep); unsigned int end_pos; if (p == NULL) end_pos = str_len(vname_str); else end_pos = p - str_c(vname_str); if (!dsync_fix_mailbox_name_until(ns, vname_str, alt_char, start_pos, &end_pos)) { /* Couldn't fix it. Name is too long? Just give up and generate a unique name. */ guid_128_generate(guid); str_truncate(vname_str, 0); str_append(vname_str, ns->prefix); str_append(vname_str, guid_128_to_string(guid)); i_assert(dsync_is_valid_name(ns, str_c(vname_str))); break; } start_pos = end_pos + 1; } while (p != NULL); } static int dsync_get_mailbox_name(struct dsync_brain *brain, const char *const *name_parts, const char **name_r, struct mail_namespace **ns_r) { struct mail_namespace *ns; const char *p; string_t *vname; char ns_sep; i_assert(*name_parts != NULL); ns = dsync_find_namespace(brain, name_parts); if (ns == NULL) return -1; ns_sep = mail_namespace_get_sep(ns); /* build the mailbox name */ char escape_chars[] = { brain->escape_char, ns_sep, '\0' }; struct dsync_mailbox_list *dlist = DSYNC_LIST_CONTEXT(ns->list); if (dlist != NULL && !dlist->have_orig_escape_char) { /* The escape character was added only for dsync internally. Normally there is no escape character configured. Change the mailbox names so that it doesn't rely on it. */ escape_chars[0] = '\0'; } vname = t_str_new(128); for (; *name_parts != NULL; name_parts++) { if (escape_chars[0] != '\0') { mailbox_list_name_escape(*name_parts, escape_chars, vname); } else { for (p = *name_parts; *p != '\0'; p++) { if (*p != ns_sep) str_append_c(vname, *p); else str_append_c(vname, brain->alt_char); } } str_append_c(vname, ns_sep); } str_truncate(vname, str_len(vname)-1); dsync_fix_mailbox_name(ns, vname, brain->alt_char); *name_r = str_c(vname); *ns_r = ns; return 0; } static void dsync_brain_mailbox_trees_sync(struct dsync_brain *brain) { struct dsync_mailbox_tree_sync_ctx *ctx; const struct dsync_mailbox_tree_sync_change *change; enum dsync_mailbox_trees_sync_type sync_type; enum dsync_mailbox_trees_sync_flags sync_flags = (brain->debug ? DSYNC_MAILBOX_TREES_SYNC_FLAG_DEBUG : 0) | (brain->master_brain ? DSYNC_MAILBOX_TREES_SYNC_FLAG_MASTER_BRAIN : 0); int ret; if (brain->no_backup_overwrite) sync_type = DSYNC_MAILBOX_TREES_SYNC_TYPE_TWOWAY; else if (brain->backup_send) sync_type = DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_LOCAL; else if (brain->backup_recv) sync_type = DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_REMOTE; else sync_type = DSYNC_MAILBOX_TREES_SYNC_TYPE_TWOWAY; ctx = dsync_mailbox_trees_sync_init(brain->local_mailbox_tree, brain->remote_mailbox_tree, sync_type, sync_flags); while ((change = dsync_mailbox_trees_sync_next(ctx)) != NULL) { T_BEGIN { ret = dsync_brain_mailbox_tree_sync_change( brain, change, &brain->mail_error); } T_END; if (ret < 0) { brain->failed = TRUE; break; } } if (dsync_mailbox_trees_sync_deinit(&ctx) < 0) brain->failed = TRUE; } static int dsync_brain_recv_mailbox_tree_add(struct dsync_brain *brain, const char *const *parts, const struct dsync_mailbox_node *remote_node, const char *sep) { struct dsync_mailbox_node *node; struct mail_namespace *ns; const char *name; if (dsync_get_mailbox_name(brain, parts, &name, &ns) < 0) return -1; if (brain->debug) { i_debug("brain %c: Remote mailbox tree: %s %s", brain->master_brain ? 'M' : 'S', t_strarray_join(parts, sep), dsync_mailbox_node_to_string(remote_node)); } node = dsync_mailbox_tree_get(brain->remote_mailbox_tree, name); node->ns = ns; dsync_mailbox_node_copy_data(node, remote_node); return 0; } bool dsync_brain_recv_mailbox_tree(struct dsync_brain *brain) { const struct dsync_mailbox_node *remote_node; struct dsync_mailbox_node *dup_node1, *dup_node2; const char *const *parts; enum dsync_ibc_recv_ret ret; int ret2; char sep[2]; bool changed = FALSE; sep[0] = brain->hierarchy_sep; sep[1] = '\0'; while ((ret = dsync_ibc_recv_mailbox_tree_node(brain->ibc, &parts, &remote_node)) > 0) { T_BEGIN { ret2 = dsync_brain_recv_mailbox_tree_add( brain, parts, remote_node, sep); } T_END; if (ret2 < 0) { i_error("Couldn't find namespace for mailbox %s", t_strarray_join(parts, sep)); brain->failed = TRUE; return TRUE; } } if (ret != DSYNC_IBC_RECV_RET_FINISHED) return changed; if (dsync_mailbox_tree_build_guid_hash(brain->remote_mailbox_tree, &dup_node1, &dup_node2) < 0) { i_error("Remote sent duplicate mailbox GUID %s for mailboxes %s and %s", guid_128_to_string(dup_node1->mailbox_guid), dsync_mailbox_node_get_full_name(brain->remote_mailbox_tree, dup_node1), dsync_mailbox_node_get_full_name(brain->remote_mailbox_tree, dup_node2)); brain->failed = TRUE; } brain->state = DSYNC_STATE_RECV_MAILBOX_TREE_DELETES; return TRUE; } static void dsync_brain_mailbox_tree_add_delete(struct dsync_mailbox_tree *tree, struct dsync_mailbox_tree *other_tree, const struct dsync_mailbox_delete *other_del, const struct dsync_mailbox_node **node_r, const char **status_r) { const struct dsync_mailbox_node *node; struct dsync_mailbox_node *other_node, *old_node; const char *name; /* see if we can find the deletion based on mailbox tree that should still have the mailbox */ node = *node_r = dsync_mailbox_tree_find_delete(tree, other_del); if (node == NULL) { *status_r = "not found"; return; } switch (other_del->type) { case DSYNC_MAILBOX_DELETE_TYPE_MAILBOX: /* mailbox is always deleted */ break; case DSYNC_MAILBOX_DELETE_TYPE_DIR: if (other_del->timestamp <= node->last_renamed_or_created) { /* we don't want to delete this directory, we already have a newer timestamp for it */ *status_r = "keep directory, we have a newer timestamp"; return; } break; case DSYNC_MAILBOX_DELETE_TYPE_UNSUBSCRIBE: if (other_del->timestamp <= node->last_subscription_change) { /* we don't want to unsubscribe, since we already have a newer subscription timestamp */ *status_r = "keep subscription, we have a newer timestamp"; return; } break; } /* make a node for it in the other mailbox tree */ name = dsync_mailbox_node_get_full_name(tree, node); other_node = dsync_mailbox_tree_get(other_tree, name); if (other_node->existence == DSYNC_MAILBOX_NODE_EXISTS && (!guid_128_is_empty(other_node->mailbox_guid) || other_del->type != DSYNC_MAILBOX_DELETE_TYPE_MAILBOX)) { /* other side has already created a new mailbox or directory with this name, we can't delete it */ *status_r = "name has already been recreated"; return; } /* ok, mark the other node deleted */ if (other_del->type == DSYNC_MAILBOX_DELETE_TYPE_MAILBOX) { memcpy(other_node->mailbox_guid, node->mailbox_guid, sizeof(other_node->mailbox_guid)); } if (other_node->ns != node->ns && other_node->ns != NULL) { /* namespace mismatch for this node. this shouldn't happen normally, but especially during some misconfigurations it's possible that one side has created mailboxes that conflict with another namespace's prefix. since we're here because one of the mailboxes was deleted, we'll just ignore this. */ *status_r = "namespace mismatch"; return; } other_node->ns = node->ns; if (other_del->type != DSYNC_MAILBOX_DELETE_TYPE_UNSUBSCRIBE) { other_node->existence = DSYNC_MAILBOX_NODE_DELETED; *status_r = "marked as deleted"; } else { other_node->last_subscription_change = other_del->timestamp; other_node->subscribed = FALSE; *status_r = "marked as unsubscribed"; } if (dsync_mailbox_tree_guid_hash_add(other_tree, other_node, &old_node) < 0) i_unreached(); } bool dsync_brain_recv_mailbox_tree_deletes(struct dsync_brain *brain) { const struct dsync_mailbox_node *node; const char *status; const struct dsync_mailbox_delete *deletes; unsigned int i, count; char sep, escape_char; if (dsync_ibc_recv_mailbox_deletes(brain->ibc, &deletes, &count, &sep, &escape_char) == 0) return FALSE; /* apply remote's mailbox deletions based on our local tree */ dsync_mailbox_tree_set_remote_chars(brain->local_mailbox_tree, sep, escape_char); for (i = 0; i < count; i++) { dsync_brain_mailbox_tree_add_delete(brain->local_mailbox_tree, brain->remote_mailbox_tree, &deletes[i], &node, &status); if (brain->debug) { const char *node_name = node == NULL ? "" : dsync_mailbox_node_get_full_name(brain->local_mailbox_tree, node); i_debug("brain %c: Remote mailbox tree deletion: guid=%s type=%s timestamp=%ld name=%s local update=%s", brain->master_brain ? 'M' : 'S', guid_128_to_string(deletes[i].guid), dsync_mailbox_delete_type_to_string(deletes[i].type), deletes[i].timestamp, node_name, status); } } /* apply local mailbox deletions based on remote tree */ deletes = dsync_mailbox_tree_get_deletes(brain->local_mailbox_tree, &count); dsync_mailbox_tree_set_remote_chars(brain->remote_mailbox_tree, brain->hierarchy_sep, brain->escape_char); for (i = 0; i < count; i++) { dsync_brain_mailbox_tree_add_delete(brain->remote_mailbox_tree, brain->local_mailbox_tree, &deletes[i], &node, &status); } dsync_brain_mailbox_trees_sync(brain); brain->state = brain->master_brain ? DSYNC_STATE_MASTER_SEND_MAILBOX : DSYNC_STATE_SLAVE_RECV_MAILBOX; i_assert(brain->local_tree_iter == NULL); brain->local_tree_iter = dsync_mailbox_tree_iter_init(brain->local_mailbox_tree); return TRUE; } dovecot-2.3.21.1/src/doveadm/dsync/dsync-brain-mailbox-tree-sync.c0000644000000000000000000001605314656633576021565 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "mail-namespace.h" #include "mail-storage.h" #include "dsync-mailbox-tree.h" #include "dsync-brain-private.h" static int sync_create_box(struct dsync_brain *brain, struct mailbox *box, const guid_128_t mailbox_guid, uint32_t uid_validity, enum mail_error *error_r) { struct mailbox_metadata metadata; struct mailbox_update update; enum mail_error error; const char *errstr; int ret; i_zero(&update); memcpy(update.mailbox_guid, mailbox_guid, sizeof(update.mailbox_guid)); update.uid_validity = uid_validity; if (mailbox_create(box, &update, FALSE) < 0) { errstr = mailbox_get_last_internal_error(box, &error); if (error != MAIL_ERROR_EXISTS) { i_error("Can't create mailbox %s: %s", mailbox_get_vname(box), errstr); *error_r = error; return -1; } } if (brain->no_mail_sync) { /* trust that create worked, we can't actually open it and verify. */ return 0; } /* sync the mailbox so we can look up its latest status */ if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ) < 0) { i_error("Can't sync mailbox %s: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, error_r)); return -1; } /* verify that the GUID is what we wanted. if it's not, it probably means that the mailbox had already been created. then we'll use the GUID that is higher. mismatching UIDVALIDITY is handled later, because we choose it by checking which mailbox has more messages */ if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) < 0) { i_error("Can't get mailbox GUID %s: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, error_r)); return -1; } ret = memcmp(mailbox_guid, metadata.guid, sizeof(metadata.guid)); /* if THEIR guid is bigger than OUR guid, and we are not doing backup in either direction, OR GUID did not match and we are receiving backup, try change the mailbox GUID. */ if ((ret > 0 && !brain->backup_recv && !brain->backup_send) || (ret != 0 && brain->backup_recv)) { if (brain->debug) { i_debug("brain %c: Changing mailbox %s GUID %s -> %s", brain->master_brain ? 'M' : 'S', mailbox_get_vname(box), guid_128_to_string(metadata.guid), guid_128_to_string(mailbox_guid)); } i_zero(&update); memcpy(update.mailbox_guid, mailbox_guid, sizeof(update.mailbox_guid)); if (mailbox_update(box, &update) < 0) { i_error("Can't update mailbox GUID %s: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, error_r)); return -1; } /* verify that the update worked */ if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) < 0) { i_error("Can't get mailbox GUID %s: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, error_r)); return -1; } if (memcmp(mailbox_guid, metadata.guid, sizeof(metadata.guid)) != 0) { i_error("Backend didn't update mailbox %s GUID", mailbox_get_vname(box)); *error_r = MAIL_ERROR_TEMP; return -1; } } else if (ret < 0) { if (brain->debug) { i_debug("brain %c: Other brain should change mailbox " "%s GUID %s -> %s", brain->master_brain ? 'M' : 'S', mailbox_get_vname(box), guid_128_to_string(mailbox_guid), guid_128_to_string(metadata.guid)); } } return 0; } int dsync_brain_mailbox_tree_sync_change(struct dsync_brain *brain, const struct dsync_mailbox_tree_sync_change *change, enum mail_error *error_r) { struct mailbox *box = NULL, *destbox; const char *errstr, *func_name = NULL, *storage_name; enum mail_error error; int ret = -1; if (brain->backup_send) { i_assert(brain->no_backup_overwrite); return 0; } switch (change->type) { case DSYNC_MAILBOX_TREE_SYNC_TYPE_DELETE_BOX: /* make sure we're deleting the correct mailbox */ ret = dsync_brain_mailbox_alloc(brain, change->mailbox_guid, &box, &errstr, error_r); if (ret < 0) { i_error("Mailbox sync: Couldn't allocate mailbox %s GUID %s: %s", change->full_name, guid_128_to_string(change->mailbox_guid), errstr); return -1; } if (ret == 0) { dsync_brain_set_changes_during_sync(brain, t_strdup_printf( "Mailbox %s GUID %s deletion conflict: %s", change->full_name, guid_128_to_string(change->mailbox_guid), errstr)); return 0; } break; case DSYNC_MAILBOX_TREE_SYNC_TYPE_DELETE_DIR: storage_name = mailbox_list_get_storage_name(change->ns->list, change->full_name); if (mailbox_list_delete_dir(change->ns->list, storage_name) == 0) return 0; errstr = mailbox_list_get_last_internal_error(change->ns->list, &error); if (error == MAIL_ERROR_NOTFOUND || error == MAIL_ERROR_EXISTS) { dsync_brain_set_changes_during_sync(brain, t_strdup_printf( "Mailbox %s mailbox_list_delete_dir conflict: %s", change->full_name, errstr)); return 0; } else { i_error("Mailbox sync: mailbox_list_delete_dir failed: %s", errstr); *error_r = error; return -1; } default: box = mailbox_alloc(change->ns->list, change->full_name, 0); break; } mailbox_skip_create_name_restrictions(box, TRUE); switch (change->type) { case DSYNC_MAILBOX_TREE_SYNC_TYPE_CREATE_BOX: ret = sync_create_box(brain, box, change->mailbox_guid, change->uid_validity, error_r); mailbox_free(&box); return ret; case DSYNC_MAILBOX_TREE_SYNC_TYPE_CREATE_DIR: ret = mailbox_create(box, NULL, TRUE); if (ret < 0 && mailbox_get_last_mail_error(box) == MAIL_ERROR_EXISTS) { /* it doesn't matter if somebody else created this directory or we automatically did while creating its child mailbox. it's there now anyway and we don't gain anything by treating this failure any differently from success. */ ret = 0; } func_name = "mailbox_create"; break; case DSYNC_MAILBOX_TREE_SYNC_TYPE_DELETE_BOX: ret = mailbox_delete(box); func_name = "mailbox_delete"; break; case DSYNC_MAILBOX_TREE_SYNC_TYPE_DELETE_DIR: i_unreached(); case DSYNC_MAILBOX_TREE_SYNC_TYPE_RENAME: destbox = mailbox_alloc(change->ns->list, change->rename_dest_name, 0); mailbox_skip_create_name_restrictions(destbox, TRUE); ret = mailbox_rename(box, destbox); func_name = "mailbox_rename"; mailbox_free(&destbox); break; case DSYNC_MAILBOX_TREE_SYNC_TYPE_SUBSCRIBE: ret = mailbox_set_subscribed(box, TRUE); func_name = "mailbox_set_subscribed"; break; case DSYNC_MAILBOX_TREE_SYNC_TYPE_UNSUBSCRIBE: ret = mailbox_set_subscribed(box, FALSE); func_name = "mailbox_set_subscribed"; break; } if (ret < 0) { errstr = mailbox_get_last_internal_error(box, &error); if (error == MAIL_ERROR_EXISTS || error == MAIL_ERROR_NOTFOUND) { /* mailbox was already created or was already deleted. let the next sync figure out what to do */ dsync_brain_set_changes_during_sync(brain, t_strdup_printf( "Mailbox %s %s conflict: %s", mailbox_get_vname(box), func_name, errstr)); ret = 0; } else { i_error("Mailbox %s sync: %s failed: %s", mailbox_get_vname(box), func_name, errstr); *error_r = error; } } mailbox_free(&box); return ret; } dovecot-2.3.21.1/src/doveadm/dsync/dsync-deserializer.c0000644000000000000000000001137314656633576017614 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "strescape.h" #include "dsync-serializer.h" #include "dsync-deserializer.h" struct dsync_deserializer { pool_t pool; const char *name; const char *const *required_fields; const char *const *keys; unsigned int *required_field_indexes; unsigned int required_field_count; }; struct dsync_deserializer_decoder { pool_t pool; struct dsync_deserializer *deserializer; const char *const *values; unsigned int values_count; }; static bool field_find(const char *const *names, const char *name, unsigned int *idx_r) { unsigned int i; for (i = 0; names[i] != NULL; i++) { if (strcmp(names[i], name) == 0) { *idx_r = i; return TRUE; } } return FALSE; } int dsync_deserializer_init(const char *name, const char *const *required_fields, const char *header_line, struct dsync_deserializer **deserializer_r, const char **error_r) { struct dsync_deserializer *deserializer; const char **dup_required_fields; unsigned int i, required_count; pool_t pool; *deserializer_r = NULL; pool = pool_alloconly_create("dsync deserializer", 1024); deserializer = p_new(pool, struct dsync_deserializer, 1); deserializer->pool = pool; deserializer->name = p_strdup(pool, name); deserializer->keys = (void *)p_strsplit_tabescaped(pool, header_line); deserializer->required_field_count = required_count = required_fields == NULL ? 0 : str_array_length(required_fields); dup_required_fields = p_new(pool, const char *, required_count + 1); deserializer->required_field_indexes = p_new(pool, unsigned int, required_count + 1); for (i = 0; i < required_count; i++) { dup_required_fields[i] = p_strdup(pool, required_fields[i]); if (!field_find(deserializer->keys, required_fields[i], &deserializer->required_field_indexes[i])) { *error_r = t_strdup_printf( "Header missing required field %s", required_fields[i]); pool_unref(&pool); return -1; } } deserializer->required_fields = dup_required_fields; *deserializer_r = deserializer; return 0; } void dsync_deserializer_deinit(struct dsync_deserializer **_deserializer) { struct dsync_deserializer *deserializer = *_deserializer; *_deserializer = NULL; pool_unref(&deserializer->pool); } int dsync_deserializer_decode_begin(struct dsync_deserializer *deserializer, const char *input, struct dsync_deserializer_decoder **decoder_r, const char **error_r) { struct dsync_deserializer_decoder *decoder; unsigned int i; char **values; pool_t pool; *decoder_r = NULL; pool = pool_alloconly_create("dsync deserializer decode", 1024); decoder = p_new(pool, struct dsync_deserializer_decoder, 1); decoder->pool = pool; decoder->deserializer = deserializer; values = p_strsplit_tabescaped(pool, input); /* fix NULLs */ for (i = 0; values[i] != NULL; i++) { if (values[i][0] == NULL_CHR) { /* NULL? */ if (values[i][1] == '\0') values[i] = NULL; else values[i] += 1; } } decoder->values_count = i; /* see if all required fields exist */ for (i = 0; i < deserializer->required_field_count; i++) { unsigned int ridx = deserializer->required_field_indexes[i]; if (ridx >= decoder->values_count || values[ridx] == NULL) { *error_r = t_strdup_printf("Missing required field %s", deserializer->required_fields[i]); pool_unref(&pool); return -1; } } decoder->values = (void *)values; *decoder_r = decoder; return 0; } static bool dsync_deserializer_find_field(struct dsync_deserializer *deserializer, const char *key, unsigned int *idx_r) { unsigned int i; for (i = 0; deserializer->keys[i] != NULL; i++) { if (strcmp(deserializer->keys[i], key) == 0) { *idx_r = i; return TRUE; } } return FALSE; } bool dsync_deserializer_decode_try(struct dsync_deserializer_decoder *decoder, const char *key, const char **value_r) { unsigned int idx; if (!dsync_deserializer_find_field(decoder->deserializer, key, &idx) || idx >= decoder->values_count) { *value_r = NULL; return FALSE; } else { *value_r = decoder->values[idx]; return *value_r != NULL; } } const char * dsync_deserializer_decode_get(struct dsync_deserializer_decoder *decoder, const char *key) { const char *value; if (!dsync_deserializer_decode_try(decoder, key, &value)) { i_panic("dsync_deserializer_decode_get() " "used for non-required key %s", key); } return value; } const char * dsync_deserializer_decoder_get_name(struct dsync_deserializer_decoder *decoder) { return decoder->deserializer->name; } void dsync_deserializer_decode_finish(struct dsync_deserializer_decoder **_decoder) { struct dsync_deserializer_decoder *decoder = *_decoder; *_decoder = NULL; pool_unref(&decoder->pool); } dovecot-2.3.21.1/src/doveadm/dsync/dsync-ibc.c0000644000000000000000000001307414656633576015667 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "dsync-mail.h" #include "dsync-ibc-private.h" void dsync_ibc_deinit(struct dsync_ibc **_ibc) { struct dsync_ibc *ibc = *_ibc; *_ibc = NULL; ibc->v.deinit(ibc); } void dsync_ibc_set_io_callback(struct dsync_ibc *ibc, io_callback_t *callback, void *context) { ibc->io_callback = callback; ibc->io_context = context; } void dsync_ibc_send_handshake(struct dsync_ibc *ibc, const struct dsync_ibc_settings *set) { ibc->v.send_handshake(ibc, set); } enum dsync_ibc_recv_ret dsync_ibc_recv_handshake(struct dsync_ibc *ibc, const struct dsync_ibc_settings **set_r) { return ibc->v.recv_handshake(ibc, set_r); } static enum dsync_ibc_send_ret dsync_ibc_send_ret(struct dsync_ibc *ibc) { return ibc->v.is_send_queue_full(ibc) ? DSYNC_IBC_SEND_RET_FULL : DSYNC_IBC_SEND_RET_OK; } enum dsync_ibc_send_ret dsync_ibc_send_end_of_list(struct dsync_ibc *ibc, enum dsync_ibc_eol_type type) { ibc->v.send_end_of_list(ibc, type); return dsync_ibc_send_ret(ibc); } enum dsync_ibc_send_ret dsync_ibc_send_mailbox_state(struct dsync_ibc *ibc, const struct dsync_mailbox_state *state) { T_BEGIN { ibc->v.send_mailbox_state(ibc, state); } T_END; return dsync_ibc_send_ret(ibc); } enum dsync_ibc_recv_ret dsync_ibc_recv_mailbox_state(struct dsync_ibc *ibc, struct dsync_mailbox_state *state_r) { return ibc->v.recv_mailbox_state(ibc, state_r); } enum dsync_ibc_send_ret dsync_ibc_send_mailbox_tree_node(struct dsync_ibc *ibc, const char *const *name, const struct dsync_mailbox_node *node) { i_assert(*name != NULL); T_BEGIN { ibc->v.send_mailbox_tree_node(ibc, name, node); } T_END; return dsync_ibc_send_ret(ibc); } enum dsync_ibc_recv_ret dsync_ibc_recv_mailbox_tree_node(struct dsync_ibc *ibc, const char *const **name_r, const struct dsync_mailbox_node **node_r) { return ibc->v.recv_mailbox_tree_node(ibc, name_r, node_r); } enum dsync_ibc_send_ret dsync_ibc_send_mailbox_deletes(struct dsync_ibc *ibc, const struct dsync_mailbox_delete *deletes, unsigned int count, char hierarchy_sep, char escape_char) { T_BEGIN { ibc->v.send_mailbox_deletes(ibc, deletes, count, hierarchy_sep, escape_char); } T_END; return dsync_ibc_send_ret(ibc); } enum dsync_ibc_recv_ret dsync_ibc_recv_mailbox_deletes(struct dsync_ibc *ibc, const struct dsync_mailbox_delete **deletes_r, unsigned int *count_r, char *hierarchy_sep_r, char *escape_char_r) { return ibc->v.recv_mailbox_deletes(ibc, deletes_r, count_r, hierarchy_sep_r, escape_char_r); } enum dsync_ibc_send_ret dsync_ibc_send_mailbox(struct dsync_ibc *ibc, const struct dsync_mailbox *dsync_box) { T_BEGIN { ibc->v.send_mailbox(ibc, dsync_box); } T_END; return dsync_ibc_send_ret(ibc); } enum dsync_ibc_recv_ret dsync_ibc_recv_mailbox(struct dsync_ibc *ibc, const struct dsync_mailbox **dsync_box_r) { return ibc->v.recv_mailbox(ibc, dsync_box_r); } enum dsync_ibc_send_ret ATTR_NOWARN_UNUSED_RESULT dsync_ibc_send_mailbox_attribute(struct dsync_ibc *ibc, const struct dsync_mailbox_attribute *attr) { T_BEGIN { ibc->v.send_mailbox_attribute(ibc, attr); } T_END; return dsync_ibc_send_ret(ibc); } enum dsync_ibc_recv_ret dsync_ibc_recv_mailbox_attribute(struct dsync_ibc *ibc, const struct dsync_mailbox_attribute **attr_r) { return ibc->v.recv_mailbox_attribute(ibc, attr_r); } enum dsync_ibc_send_ret dsync_ibc_send_change(struct dsync_ibc *ibc, const struct dsync_mail_change *change) { i_assert(change->uid > 0); T_BEGIN { ibc->v.send_change(ibc, change); } T_END; return dsync_ibc_send_ret(ibc); } enum dsync_ibc_recv_ret dsync_ibc_recv_change(struct dsync_ibc *ibc, const struct dsync_mail_change **change_r) { return ibc->v.recv_change(ibc, change_r); } enum dsync_ibc_send_ret dsync_ibc_send_mail_request(struct dsync_ibc *ibc, const struct dsync_mail_request *request) { i_assert(request->guid != NULL || request->uid != 0); T_BEGIN { ibc->v.send_mail_request(ibc, request); } T_END; return dsync_ibc_send_ret(ibc); } enum dsync_ibc_recv_ret dsync_ibc_recv_mail_request(struct dsync_ibc *ibc, const struct dsync_mail_request **request_r) { return ibc->v.recv_mail_request(ibc, request_r); } enum dsync_ibc_send_ret dsync_ibc_send_mail(struct dsync_ibc *ibc, const struct dsync_mail *mail) { i_assert(*mail->guid != '\0' || mail->uid != 0); T_BEGIN { ibc->v.send_mail(ibc, mail); } T_END; return dsync_ibc_send_ret(ibc); } enum dsync_ibc_recv_ret dsync_ibc_recv_mail(struct dsync_ibc *ibc, struct dsync_mail **mail_r) { return ibc->v.recv_mail(ibc, mail_r); } void dsync_ibc_send_finish(struct dsync_ibc *ibc, const char *error, enum mail_error mail_error, bool require_full_resync) { ibc->v.send_finish(ibc, error, mail_error, require_full_resync); } enum dsync_ibc_recv_ret dsync_ibc_recv_finish(struct dsync_ibc *ibc, const char **error_r, enum mail_error *mail_error_r, bool *require_full_resync_r) { return ibc->v.recv_finish(ibc, error_r, mail_error_r, require_full_resync_r); } void dsync_ibc_close_mail_streams(struct dsync_ibc *ibc) { ibc->v.close_mail_streams(ibc); } bool dsync_ibc_has_failed(struct dsync_ibc *ibc) { return ibc->failed; } bool dsync_ibc_has_timed_out(struct dsync_ibc *ibc) { return ibc->timeout; } bool dsync_ibc_is_send_queue_full(struct dsync_ibc *ibc) { return ibc->v.is_send_queue_full(ibc); } bool dsync_ibc_has_pending_data(struct dsync_ibc *ibc) { return ibc->v.has_pending_data(ibc); } dovecot-2.3.21.1/src/doveadm/dsync/Makefile.am0000644000000000000000000000373214656633576015704 00000000000000pkglib_LTLIBRARIES = libdovecot-dsync.la noinst_LTLIBRARIES = libdsync.la AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-ssl-iostream \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-storage libdsync_la_SOURCES = \ dsync-brain.c \ dsync-brain-mailbox.c \ dsync-brain-mailbox-tree.c \ dsync-brain-mailbox-tree-sync.c \ dsync-brain-mails.c \ dsync-deserializer.c \ dsync-mail.c \ dsync-mailbox.c \ dsync-mailbox-import.c \ dsync-mailbox-export.c \ dsync-mailbox-state.c \ dsync-mailbox-tree.c \ dsync-mailbox-tree-fill.c \ dsync-mailbox-tree-sync.c \ dsync-serializer.c \ dsync-ibc.c \ dsync-ibc-stream.c \ dsync-ibc-pipe.c \ dsync-transaction-log-scan.c libdovecot_dsync_la_SOURCES = libdovecot_dsync_la_LIBADD = libdsync.la ../../lib-storage/libdovecot-storage.la ../../lib-dovecot/libdovecot.la libdovecot_dsync_la_DEPENDENCIES = libdsync.la libdovecot_dsync_la_LDFLAGS = -export-dynamic pkginc_libdir = $(pkgincludedir) pkginc_lib_HEADERS = \ dsync-brain.h \ dsync-ibc.h noinst_HEADERS = \ dsync-brain-private.h \ dsync-mail.h \ dsync-mailbox.h \ dsync-mailbox-import.h \ dsync-mailbox-export.h \ dsync-mailbox-state.h \ dsync-mailbox-tree.h \ dsync-mailbox-tree-private.h \ dsync-serializer.h \ dsync-deserializer.h \ dsync-ibc-private.h \ dsync-transaction-log-scan.h test_programs = \ test-dsync-mailbox-tree-sync noinst_PROGRAMS = $(test_programs) test_libs = \ ../../lib-test/libtest.la \ ../../lib/liblib.la test_dsync_mailbox_tree_sync_SOURCES = test-dsync-mailbox-tree-sync.c test_dsync_mailbox_tree_sync_LDADD = dsync-mailbox-tree-sync.lo dsync-mailbox-tree.lo $(test_libs) test_dsync_mailbox_tree_sync_DEPENDENCIES = $(pkglib_LTLIBRARIES) $(test_libs) check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done dovecot-2.3.21.1/src/doveadm/dsync/dsync-mailbox-export.c0000644000000000000000000007221714656633576020110 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "hash.h" #include "istream.h" #include "mail-index-modseq.h" #include "mail-storage-private.h" #include "mail-search-build.h" #include "dsync-transaction-log-scan.h" #include "dsync-mail.h" #include "dsync-mailbox.h" #include "dsync-mailbox-export.h" struct dsync_mail_guid_instances { ARRAY_TYPE(seq_range) seqs; bool requested; bool searched; }; struct dsync_mailbox_exporter { pool_t pool; struct mailbox *box; struct dsync_transaction_log_scan *log_scan; uint32_t last_common_uid; struct mailbox_header_lookup_ctx *wanted_headers; struct mailbox_transaction_context *trans; struct mail_search_context *search_ctx; unsigned int search_pos, search_count; unsigned int hdr_hash_version; const char *const *hashed_headers; /* GUID => instances */ HASH_TABLE(char *, struct dsync_mail_guid_instances *) export_guids; ARRAY_TYPE(seq_range) requested_uids; ARRAY_TYPE(seq_range) search_uids; ARRAY_TYPE(seq_range) expunged_seqs; ARRAY_TYPE(const_string) expunged_guids; unsigned int expunged_guid_idx; /* uint32_t UID => struct dsync_mail_change */ HASH_TABLE(void *, struct dsync_mail_change *) changes; /* changes sorted by UID */ ARRAY(struct dsync_mail_change *) sorted_changes; unsigned int change_idx; uint32_t highest_changed_uid; struct mailbox_attribute_iter *attr_iter; struct hash_iterate_context *attr_change_iter; enum mail_attribute_type attr_type; struct dsync_mailbox_attribute attr; struct dsync_mail_change change; struct dsync_mail dsync_mail; const char *error; enum mail_error mail_error; bool body_search_initialized:1; bool auto_export_mails:1; bool mails_have_guids:1; bool minimal_dmail_fill:1; bool return_all_mails:1; bool export_received_timestamps:1; bool export_virtual_sizes:1; bool no_hdr_hashes:1; }; static int dsync_mail_error(struct dsync_mailbox_exporter *exporter, struct mail *mail, const char *field) { const char *errstr; enum mail_error error; errstr = mailbox_get_last_internal_error(exporter->box, &error); if (error == MAIL_ERROR_EXPUNGED) return 0; exporter->mail_error = error; exporter->error = p_strdup_printf(exporter->pool, "Can't lookup %s for UID=%u: %s", field, mail->uid, errstr); return -1; } static bool final_keyword_check(struct dsync_mail_change *change, const char *name, char *type_r) { const char *const *changes; unsigned int i, count; *type_r = KEYWORD_CHANGE_FINAL; changes = array_get(&change->keyword_changes, &count); for (i = 0; i < count; i++) { if (strcmp(changes[i]+1, name) != 0) continue; switch (changes[i][0]) { case KEYWORD_CHANGE_ADD: /* replace with ADD_AND_FINAL */ array_delete(&change->keyword_changes, i, 1); *type_r = KEYWORD_CHANGE_ADD_AND_FINAL; return FALSE; case KEYWORD_CHANGE_REMOVE: /* a final keyword is marked as removed. this shouldn't normally happen. */ array_delete(&change->keyword_changes, i, 1); return FALSE; case KEYWORD_CHANGE_ADD_AND_FINAL: case KEYWORD_CHANGE_FINAL: /* no change */ return TRUE; } } return FALSE; } static void search_update_flag_changes(struct dsync_mailbox_exporter *exporter, struct mail *mail, struct dsync_mail_change *change) { const char *const *keywords; unsigned int i; char type; i_assert((change->add_flags & change->remove_flags) == 0); change->modseq = mail_get_modseq(mail); change->pvt_modseq = mail_get_pvt_modseq(mail); change->final_flags = mail_get_flags(mail); keywords = mail_get_keywords(mail); if (!array_is_created(&change->keyword_changes) && keywords[0] != NULL) { p_array_init(&change->keyword_changes, exporter->pool, str_array_length(keywords)); } for (i = 0; keywords[i] != NULL; i++) { /* add the final keyword if it's not already there as +keyword */ if (!final_keyword_check(change, keywords[i], &type)) { const char *keyword_change = p_strdup_printf(exporter->pool, "%c%s", type, keywords[i]); array_push_back(&change->keyword_changes, &keyword_change); } } } static int exporter_get_guids(struct dsync_mailbox_exporter *exporter, struct mail *mail, const char **guid_r, const char **hdr_hash_r) { *guid_r = ""; *hdr_hash_r = NULL; /* always try to get GUID, even if we're also getting header hash */ if (mail_get_special(mail, MAIL_FETCH_GUID, guid_r) < 0) return dsync_mail_error(exporter, mail, "GUID"); if (!exporter->mails_have_guids) { /* get header hash also */ if (exporter->no_hdr_hashes) { *hdr_hash_r = ""; return 1; } if (dsync_mail_get_hdr_hash(mail, exporter->hdr_hash_version, exporter->hashed_headers, hdr_hash_r) < 0) return dsync_mail_error(exporter, mail, "hdr-stream"); return 1; } else if (**guid_r == '\0') { exporter->mail_error = MAIL_ERROR_TEMP; exporter->error = "Backend doesn't support GUIDs, " "sync with header hashes instead"; return -1; } else { /* GUIDs are required, we don't need header hash */ return 1; } } static int search_update_flag_change_guid(struct dsync_mailbox_exporter *exporter, struct mail *mail) { struct dsync_mail_change *change, *log_change; const char *guid, *hdr_hash; int ret; change = hash_table_lookup(exporter->changes, POINTER_CAST(mail->uid)); if (change != NULL) { i_assert(change->type == DSYNC_MAIL_CHANGE_TYPE_FLAG_CHANGE); } else { i_assert(exporter->return_all_mails); change = p_new(exporter->pool, struct dsync_mail_change, 1); change->uid = mail->uid; change->type = DSYNC_MAIL_CHANGE_TYPE_FLAG_CHANGE; hash_table_insert(exporter->changes, POINTER_CAST(mail->uid), change); } if ((ret = exporter_get_guids(exporter, mail, &guid, &hdr_hash)) < 0) return -1; if (ret == 0) { /* the message was expunged during export */ i_zero(change); change->type = DSYNC_MAIL_CHANGE_TYPE_EXPUNGE; change->uid = mail->uid; /* find its GUID from log if possible */ log_change = dsync_transaction_log_scan_find_new_expunge( exporter->log_scan, mail->uid); if (log_change != NULL) change->guid = log_change->guid; } else { change->guid = *guid == '\0' ? "" : p_strdup(exporter->pool, guid); change->hdr_hash = p_strdup(exporter->pool, hdr_hash); search_update_flag_changes(exporter, mail, change); } return 0; } static struct dsync_mail_change * export_save_change_get(struct dsync_mailbox_exporter *exporter, uint32_t uid) { struct dsync_mail_change *change; change = hash_table_lookup(exporter->changes, POINTER_CAST(uid)); if (change == NULL) { change = p_new(exporter->pool, struct dsync_mail_change, 1); change->uid = uid; hash_table_insert(exporter->changes, POINTER_CAST(uid), change); } else { /* move flag changes into a save. this happens only when last_common_uid isn't known */ i_assert(change->type == DSYNC_MAIL_CHANGE_TYPE_FLAG_CHANGE); i_assert(exporter->last_common_uid == 0); } change->type = DSYNC_MAIL_CHANGE_TYPE_SAVE; return change; } static void export_add_mail_instance(struct dsync_mailbox_exporter *exporter, struct dsync_mail_change *change, uint32_t seq) { struct dsync_mail_guid_instances *instances; if (exporter->auto_export_mails && !exporter->mails_have_guids) { /* GUIDs not supported, mail is requested by UIDs */ seq_range_array_add(&exporter->requested_uids, change->uid); return; } if (*change->guid == '\0') { /* mail UIDs are manually requested */ i_assert(!exporter->mails_have_guids); return; } instances = hash_table_lookup(exporter->export_guids, change->guid); if (instances == NULL) { instances = p_new(exporter->pool, struct dsync_mail_guid_instances, 1); p_array_init(&instances->seqs, exporter->pool, 2); hash_table_insert(exporter->export_guids, p_strdup(exporter->pool, change->guid), instances); if (exporter->auto_export_mails) instances->requested = TRUE; } seq_range_array_add(&instances->seqs, seq); } static int search_add_save(struct dsync_mailbox_exporter *exporter, struct mail *mail) { struct dsync_mail_change *change; const char *guid, *hdr_hash; enum mail_fetch_field wanted_fields = MAIL_FETCH_GUID; time_t received_timestamp = 0; uoff_t virtual_size = UOFF_T_MAX; int ret; /* update wanted fields in case we didn't already set them for the search */ if (exporter->export_received_timestamps) wanted_fields |= MAIL_FETCH_RECEIVED_DATE; if (exporter->export_virtual_sizes) wanted_fields |= MAIL_FETCH_VIRTUAL_SIZE; mail_add_temp_wanted_fields(mail, wanted_fields, exporter->wanted_headers); /* If message is already expunged here, just skip it */ if ((ret = exporter_get_guids(exporter, mail, &guid, &hdr_hash)) <= 0) return ret; if (exporter->export_received_timestamps) { if (mail_get_received_date(mail, &received_timestamp) < 0) return dsync_mail_error(exporter, mail, "received-time"); if (received_timestamp == 0) { /* don't allow timestamps to be zero. we want to have asserts verify that the timestamp is set properly. */ received_timestamp = 1; } } if (exporter->export_virtual_sizes) { if (mail_get_virtual_size(mail, &virtual_size) < 0) return dsync_mail_error(exporter, mail, "virtual-size"); i_assert(virtual_size != UOFF_T_MAX); } change = export_save_change_get(exporter, mail->uid); change->guid = *guid == '\0' ? "" : p_strdup(exporter->pool, guid); change->hdr_hash = p_strdup(exporter->pool, hdr_hash); change->received_timestamp = received_timestamp; change->virtual_size = virtual_size; search_update_flag_changes(exporter, mail, change); export_add_mail_instance(exporter, change, mail->seq); return 1; } static void dsync_mailbox_export_add_flagchange_uids(struct dsync_mailbox_exporter *exporter, ARRAY_TYPE(seq_range) *uids) { struct hash_iterate_context *iter; void *key; struct dsync_mail_change *change; iter = hash_table_iterate_init(exporter->changes); while (hash_table_iterate(iter, exporter->changes, &key, &change)) { if (change->type == DSYNC_MAIL_CHANGE_TYPE_FLAG_CHANGE) seq_range_array_add(uids, change->uid); } hash_table_iterate_deinit(&iter); } static void dsync_mailbox_export_drop_expunged_flag_changes(struct dsync_mailbox_exporter *exporter) { struct hash_iterate_context *iter; void *key; struct dsync_mail_change *change; /* any flag changes for UIDs above last_common_uid weren't found by mailbox search, which means they were already expunged. for some reason the log scanner found flag changes for the message, but not the expunge. just remove these. */ iter = hash_table_iterate_init(exporter->changes); while (hash_table_iterate(iter, exporter->changes, &key, &change)) { if (change->type == DSYNC_MAIL_CHANGE_TYPE_FLAG_CHANGE && change->uid > exporter->last_common_uid) hash_table_remove(exporter->changes, key); } hash_table_iterate_deinit(&iter); } static void dsync_mailbox_export_search(struct dsync_mailbox_exporter *exporter) { struct mail_search_context *search_ctx; struct mail_search_args *search_args; struct mail_search_arg *sarg; struct mail *mail; enum mail_fetch_field wanted_fields = 0; struct mailbox_header_lookup_ctx *wanted_headers = NULL; int ret = 0; search_args = mail_search_build_init(); sarg = mail_search_build_add(search_args, SEARCH_UIDSET); p_array_init(&sarg->value.seqset, search_args->pool, 1); if (exporter->return_all_mails || exporter->last_common_uid == 0) { /* we want to know about all mails */ seq_range_array_add_range(&sarg->value.seqset, 1, (uint32_t)-1); } else { /* lookup GUIDs for messages with flag changes */ dsync_mailbox_export_add_flagchange_uids(exporter, &sarg->value.seqset); /* lookup new messages */ seq_range_array_add_range(&sarg->value.seqset, exporter->last_common_uid + 1, (uint32_t)-1); } if (exporter->last_common_uid == 0) { /* we're syncing all mails, so we can request the wanted fields for all the mails */ wanted_fields = MAIL_FETCH_GUID; wanted_headers = exporter->wanted_headers; } exporter->trans = mailbox_transaction_begin(exporter->box, MAILBOX_TRANSACTION_FLAG_SYNC, __func__); search_ctx = mailbox_search_init(exporter->trans, search_args, NULL, wanted_fields, wanted_headers); mail_search_args_unref(&search_args); while (mailbox_search_next(search_ctx, &mail)) { T_BEGIN { if (mail->uid <= exporter->last_common_uid) ret = search_update_flag_change_guid(exporter, mail); else ret = search_add_save(exporter, mail); } T_END; if (ret < 0) break; } i_assert(ret >= 0 || exporter->error != NULL); dsync_mailbox_export_drop_expunged_flag_changes(exporter); if (mailbox_search_deinit(&search_ctx) < 0 && exporter->error == NULL) { exporter->error = p_strdup_printf(exporter->pool, "Mail search failed: %s", mailbox_get_last_internal_error(exporter->box, &exporter->mail_error)); } } static int dsync_mail_change_p_uid_cmp(struct dsync_mail_change *const *c1, struct dsync_mail_change *const *c2) { if ((*c1)->uid < (*c2)->uid) return -1; if ((*c1)->uid > (*c2)->uid) return 1; return 0; } static void dsync_mailbox_export_sort_changes(struct dsync_mailbox_exporter *exporter) { struct hash_iterate_context *iter; void *key; struct dsync_mail_change *change; p_array_init(&exporter->sorted_changes, exporter->pool, hash_table_count(exporter->changes)); iter = hash_table_iterate_init(exporter->changes); while (hash_table_iterate(iter, exporter->changes, &key, &change)) array_push_back(&exporter->sorted_changes, &change); hash_table_iterate_deinit(&iter); array_sort(&exporter->sorted_changes, dsync_mail_change_p_uid_cmp); } static void dsync_mailbox_export_attr_init(struct dsync_mailbox_exporter *exporter, enum mail_attribute_type type) { exporter->attr_iter = mailbox_attribute_iter_init(exporter->box, type, ""); exporter->attr_type = type; } static void dsync_mailbox_export_log_scan(struct dsync_mailbox_exporter *exporter, struct dsync_transaction_log_scan *log_scan) { HASH_TABLE_TYPE(dsync_uid_mail_change) log_changes; struct hash_iterate_context *iter; void *key; struct dsync_mail_change *change, *dup_change; log_changes = dsync_transaction_log_scan_get_hash(log_scan); if (dsync_transaction_log_scan_has_all_changes(log_scan)) { /* we tried to access too old/invalid modseqs. to make sure no changes get lost, we need to send all of the messages */ exporter->return_all_mails = TRUE; } /* clone the hash table, since we're changing it. */ hash_table_create_direct(&exporter->changes, exporter->pool, hash_table_count(log_changes)); iter = hash_table_iterate_init(log_changes); while (hash_table_iterate(iter, log_changes, &key, &change)) { dup_change = p_new(exporter->pool, struct dsync_mail_change, 1); *dup_change = *change; hash_table_insert(exporter->changes, key, dup_change); if (exporter->highest_changed_uid < change->uid) exporter->highest_changed_uid = change->uid; } hash_table_iterate_deinit(&iter); } struct dsync_mailbox_exporter * dsync_mailbox_export_init(struct mailbox *box, struct dsync_transaction_log_scan *log_scan, uint32_t last_common_uid, enum dsync_mailbox_exporter_flags flags, unsigned int hdr_hash_version, const char *const *hashed_headers) { struct dsync_mailbox_exporter *exporter; pool_t pool; pool = pool_alloconly_create(MEMPOOL_GROWING"dsync mailbox export", 4096); exporter = p_new(pool, struct dsync_mailbox_exporter, 1); exporter->pool = pool; exporter->box = box; exporter->log_scan = log_scan; exporter->last_common_uid = last_common_uid; exporter->auto_export_mails = (flags & DSYNC_MAILBOX_EXPORTER_FLAG_AUTO_EXPORT_MAILS) != 0; exporter->mails_have_guids = (flags & DSYNC_MAILBOX_EXPORTER_FLAG_MAILS_HAVE_GUIDS) != 0; exporter->minimal_dmail_fill = (flags & DSYNC_MAILBOX_EXPORTER_FLAG_MINIMAL_DMAIL_FILL) != 0; exporter->export_received_timestamps = (flags & DSYNC_MAILBOX_EXPORTER_FLAG_TIMESTAMPS) != 0; exporter->export_virtual_sizes = (flags & DSYNC_MAILBOX_EXPORTER_FLAG_VSIZES) != 0; exporter->hdr_hash_version = hdr_hash_version; exporter->no_hdr_hashes = (flags & DSYNC_MAILBOX_EXPORTER_FLAG_NO_HDR_HASHES) != 0; exporter->hashed_headers = hashed_headers; p_array_init(&exporter->requested_uids, pool, 16); p_array_init(&exporter->search_uids, pool, 16); hash_table_create(&exporter->export_guids, pool, 0, str_hash, strcmp); p_array_init(&exporter->expunged_seqs, pool, 16); p_array_init(&exporter->expunged_guids, pool, 16); if (!exporter->mails_have_guids && !exporter->no_hdr_hashes) exporter->wanted_headers = dsync_mail_get_hash_headers(box, exporter->hashed_headers); /* first scan transaction log and save any expunges and flag changes */ dsync_mailbox_export_log_scan(exporter, log_scan); /* get saves and also find GUIDs for flag changes */ dsync_mailbox_export_search(exporter); /* get the changes sorted by UID */ dsync_mailbox_export_sort_changes(exporter); dsync_mailbox_export_attr_init(exporter, MAIL_ATTRIBUTE_TYPE_PRIVATE); return exporter; } static int dsync_mailbox_export_iter_next_nonexistent_attr(struct dsync_mailbox_exporter *exporter) { struct dsync_mailbox_attribute *attr; struct mail_attribute_value value; while (hash_table_iterate(exporter->attr_change_iter, dsync_transaction_log_scan_get_attr_hash(exporter->log_scan), &attr, &attr)) { if (attr->exported || !attr->deleted) continue; /* lookup the value mainly to get its last_change value. */ if (mailbox_attribute_get_stream(exporter->box, attr->type, attr->key, &value) < 0) { exporter->error = p_strdup_printf(exporter->pool, "Mailbox attribute %s lookup failed: %s", attr->key, mailbox_get_last_internal_error(exporter->box, &exporter->mail_error)); break; } if ((value.flags & MAIL_ATTRIBUTE_VALUE_FLAG_READONLY) != 0) { i_stream_unref(&value.value_stream); continue; } attr->last_change = value.last_change; if (value.value != NULL || value.value_stream != NULL) { attr->value = p_strdup(exporter->pool, value.value); attr->value_stream = value.value_stream; attr->deleted = FALSE; } attr->exported = TRUE; exporter->attr = *attr; return 1; } hash_table_iterate_deinit(&exporter->attr_change_iter); return 0; } static int dsync_mailbox_export_iter_next_attr(struct dsync_mailbox_exporter *exporter) { HASH_TABLE_TYPE(dsync_attr_change) attr_changes; struct dsync_mailbox_attribute lookup_attr, *attr; struct dsync_mailbox_attribute *attr_change; const char *key; struct mail_attribute_value value; bool export_all_attrs; export_all_attrs = exporter->return_all_mails || exporter->last_common_uid == 0; attr_changes = dsync_transaction_log_scan_get_attr_hash(exporter->log_scan); lookup_attr.type = exporter->attr_type; /* note that the order of processing may be important for some attributes. for example sieve can't set a script active until it's first been created */ while ((key = mailbox_attribute_iter_next(exporter->attr_iter)) != NULL) { lookup_attr.key = key; attr_change = hash_table_lookup(attr_changes, &lookup_attr); if (attr_change == NULL && !export_all_attrs) continue; if (mailbox_attribute_get_stream(exporter->box, exporter->attr_type, key, &value) < 0) { exporter->error = p_strdup_printf(exporter->pool, "Mailbox attribute %s lookup failed: %s", key, mailbox_get_last_internal_error(exporter->box, &exporter->mail_error)); return -1; } if ((value.flags & MAIL_ATTRIBUTE_VALUE_FLAG_READONLY) != 0) { /* readonly attributes can't be changed, no point in exporting them */ if (value.value_stream != NULL) i_stream_unref(&value.value_stream); continue; } if (value.value == NULL && value.value_stream == NULL && (attr_change == NULL || !attr_change->deleted)) { /* the attribute was just deleted? skip for this sync. */ continue; } if (attr_change != NULL && attr_change->exported) { /* duplicate attribute returned. shouldn't normally happen, but don't crash. */ i_warning("Ignoring duplicate attributes '%s'", key); continue; } attr = &exporter->attr; i_zero(attr); attr->type = exporter->attr_type; attr->value = p_strdup(exporter->pool, value.value); attr->value_stream = value.value_stream; attr->last_change = value.last_change; if (attr_change != NULL) { attr_change->exported = TRUE; attr->key = attr_change->key; attr->deleted = attr_change->deleted && !DSYNC_ATTR_HAS_VALUE(attr); attr->modseq = attr_change->modseq; } else { attr->key = p_strdup(exporter->pool, key); } return 1; } if (mailbox_attribute_iter_deinit(&exporter->attr_iter) < 0) { exporter->error = p_strdup_printf(exporter->pool, "Mailbox attribute iteration failed: %s", mailbox_get_last_internal_error(exporter->box, &exporter->mail_error)); return -1; } if (exporter->attr_type == MAIL_ATTRIBUTE_TYPE_PRIVATE) { /* export shared attributes */ dsync_mailbox_export_attr_init(exporter, MAIL_ATTRIBUTE_TYPE_SHARED); return dsync_mailbox_export_iter_next_attr(exporter); } exporter->attr_change_iter = hash_table_iterate_init(attr_changes); return dsync_mailbox_export_iter_next_nonexistent_attr(exporter); } int dsync_mailbox_export_next_attr(struct dsync_mailbox_exporter *exporter, const struct dsync_mailbox_attribute **attr_r) { int ret; if (exporter->error != NULL) return -1; i_stream_unref(&exporter->attr.value_stream); if (exporter->attr_iter != NULL) { ret = dsync_mailbox_export_iter_next_attr(exporter); } else { ret = dsync_mailbox_export_iter_next_nonexistent_attr(exporter); } if (ret > 0) *attr_r = &exporter->attr; return ret; } int dsync_mailbox_export_next(struct dsync_mailbox_exporter *exporter, const struct dsync_mail_change **change_r) { struct dsync_mail_change *const *changes; unsigned int count; if (exporter->error != NULL) return -1; changes = array_get(&exporter->sorted_changes, &count); if (exporter->change_idx == count) return 0; *change_r = changes[exporter->change_idx++]; return 1; } static int dsync_mailbox_export_body_search_init(struct dsync_mailbox_exporter *exporter) { struct mail_search_args *search_args; struct mail_search_arg *sarg; struct hash_iterate_context *iter; const struct seq_range *uids; char *guid; const char *const_guid; enum mail_fetch_field wanted_fields; struct dsync_mail_guid_instances *instances; const struct seq_range *range; unsigned int i, count; uint32_t seq, seq1, seq2; i_assert(exporter->search_ctx == NULL); search_args = mail_search_build_init(); sarg = mail_search_build_add(search_args, SEARCH_SEQSET); p_array_init(&sarg->value.seqset, search_args->pool, 128); /* get a list of messages we want to fetch. if there are more than one instance for a GUID, use the first one. */ iter = hash_table_iterate_init(exporter->export_guids); while (hash_table_iterate(iter, exporter->export_guids, &guid, &instances)) { if (!instances->requested || array_count(&instances->seqs) == 0) continue; uids = array_front(&instances->seqs); seq = uids[0].seq1; if (!instances->searched) { instances->searched = TRUE; seq_range_array_add(&sarg->value.seqset, seq); } else if (seq_range_exists(&exporter->expunged_seqs, seq)) { /* we're on a second round, refetching expunged messages */ seq_range_array_remove(&instances->seqs, seq); seq_range_array_remove(&exporter->expunged_seqs, seq); if (array_count(&instances->seqs) == 0) { /* no instances left */ const_guid = guid; array_push_back(&exporter->expunged_guids, &const_guid); continue; } uids = array_front(&instances->seqs); seq = uids[0].seq1; seq_range_array_add(&sarg->value.seqset, seq); } } hash_table_iterate_deinit(&iter); /* add requested UIDs */ range = array_get(&exporter->requested_uids, &count); for (i = 0; i < count; i++) { mailbox_get_seq_range(exporter->box, range[i].seq1, range[i].seq2, &seq1, &seq2); seq_range_array_add_range(&sarg->value.seqset, seq1, seq2); } array_clear(&exporter->search_uids); array_append_array(&exporter->search_uids, &exporter->requested_uids); array_clear(&exporter->requested_uids); wanted_fields = MAIL_FETCH_GUID | MAIL_FETCH_SAVE_DATE; if (!exporter->minimal_dmail_fill) { wanted_fields |= MAIL_FETCH_RECEIVED_DATE | MAIL_FETCH_UIDL_BACKEND | MAIL_FETCH_POP3_ORDER | MAIL_FETCH_STREAM_HEADER | MAIL_FETCH_STREAM_BODY; } exporter->search_count += seq_range_count(&sarg->value.seqset); exporter->search_ctx = mailbox_search_init(exporter->trans, search_args, NULL, wanted_fields, NULL); mail_search_args_unref(&search_args); return array_count(&sarg->value.seqset) > 0 ? 1 : 0; } static void dsync_mailbox_export_body_search_deinit(struct dsync_mailbox_exporter *exporter) { if (exporter->search_ctx == NULL) return; if (mailbox_search_deinit(&exporter->search_ctx) < 0 && exporter->error == NULL) { exporter->error = p_strdup_printf(exporter->pool, "Mail search failed: %s", mailbox_get_last_internal_error(exporter->box, &exporter->mail_error)); } } static int dsync_mailbox_export_mail(struct dsync_mailbox_exporter *exporter, struct mail *mail) { struct dsync_mail_guid_instances *instances; const char *error_field; if (dsync_mail_fill(mail, exporter->minimal_dmail_fill, &exporter->dsync_mail, &error_field) < 0) return dsync_mail_error(exporter, mail, error_field); instances = *exporter->dsync_mail.guid == '\0' ? NULL : hash_table_lookup(exporter->export_guids, exporter->dsync_mail.guid); if (instances != NULL) { /* GUID found */ } else if (exporter->dsync_mail.uid != 0) { /* mail requested by UID */ } else { exporter->mail_error = MAIL_ERROR_TEMP; exporter->error = p_strdup_printf(exporter->pool, "GUID unexpectedly changed for UID=%u GUID=%s", mail->uid, exporter->dsync_mail.guid); return -1; } if (!seq_range_exists(&exporter->search_uids, mail->uid)) exporter->dsync_mail.uid = 0; else exporter->dsync_mail.guid = ""; /* this message was successfully returned, don't try retrying it */ if (instances != NULL) array_clear(&instances->seqs); return 1; } void dsync_mailbox_export_want_mail(struct dsync_mailbox_exporter *exporter, const struct dsync_mail_request *request) { struct dsync_mail_guid_instances *instances; i_assert(!exporter->auto_export_mails); if (request->guid == NULL) { i_assert(request->uid > 0); seq_range_array_add(&exporter->requested_uids, request->uid); return; } instances = hash_table_lookup(exporter->export_guids, request->guid); if (instances == NULL) { exporter->mail_error = MAIL_ERROR_TEMP; exporter->error = p_strdup_printf(exporter->pool, "Remote requested unexpected GUID %s", request->guid); return; } instances->requested = TRUE; } int dsync_mailbox_export_next_mail(struct dsync_mailbox_exporter *exporter, const struct dsync_mail **mail_r) { struct mail *mail; const char *const *guids; unsigned int count; int ret; if (exporter->error != NULL) return -1; if (!exporter->body_search_initialized) { exporter->body_search_initialized = TRUE; if (dsync_mailbox_export_body_search_init(exporter) < 0) { i_assert(exporter->error != NULL); return -1; } } while (mailbox_search_next(exporter->search_ctx, &mail)) { exporter->search_pos++; if ((ret = dsync_mailbox_export_mail(exporter, mail)) > 0) { *mail_r = &exporter->dsync_mail; return 1; } if (ret < 0) { i_assert(exporter->error != NULL); return -1; } /* the message was expunged. if the GUID has another instance, try sending it later. */ seq_range_array_add(&exporter->expunged_seqs, mail->seq); } /* if some instances of messages were expunged, retry fetching them with other instances */ dsync_mailbox_export_body_search_deinit(exporter); if ((ret = dsync_mailbox_export_body_search_init(exporter)) < 0) { i_assert(exporter->error != NULL); return -1; } if (ret > 0) { /* not finished yet */ return dsync_mailbox_export_next_mail(exporter, mail_r); } /* finished with messages. if there are any expunged messages, return them */ guids = array_get(&exporter->expunged_guids, &count); if (exporter->expunged_guid_idx < count) { i_zero(&exporter->dsync_mail); exporter->dsync_mail.guid = guids[exporter->expunged_guid_idx++]; *mail_r = &exporter->dsync_mail; return 1; } return 0; } int dsync_mailbox_export_deinit(struct dsync_mailbox_exporter **_exporter, const char **errstr_r, enum mail_error *error_r) { struct dsync_mailbox_exporter *exporter = *_exporter; *_exporter = NULL; if (exporter->attr_iter != NULL) (void)mailbox_attribute_iter_deinit(&exporter->attr_iter); dsync_mailbox_export_body_search_deinit(exporter); (void)mailbox_transaction_commit(&exporter->trans); mailbox_header_lookup_unref(&exporter->wanted_headers); i_stream_unref(&exporter->attr.value_stream); hash_table_destroy(&exporter->export_guids); hash_table_destroy(&exporter->changes); i_assert((exporter->error != NULL) == (exporter->mail_error != 0)); *error_r = exporter->mail_error; *errstr_r = t_strdup(exporter->error); pool_unref(&exporter->pool); return *errstr_r != NULL ? -1 : 0; } const char *dsync_mailbox_export_get_proctitle(struct dsync_mailbox_exporter *exporter) { if (exporter->search_ctx == NULL) return ""; return t_strdup_printf("%u/%u", exporter->search_pos, exporter->search_count); } dovecot-2.3.21.1/src/doveadm/dsync/dsync-transaction-log-scan.h0000644000000000000000000000252614656633576021165 00000000000000#ifndef DSYNC_TRANSACTION_LOG_SCAN_H #define DSYNC_TRANSACTION_LOG_SCAN_H HASH_TABLE_DEFINE_TYPE(dsync_uid_mail_change, void *, struct dsync_mail_change *); HASH_TABLE_DEFINE_TYPE(dsync_attr_change, struct dsync_mailbox_attribute *, struct dsync_mailbox_attribute *); struct mail_index_view; struct dsync_transaction_log_scan; int dsync_transaction_log_scan_init(struct mail_index_view *view, struct mail_index_view *pvt_view, uint32_t highest_wanted_uid, uint64_t modseq, uint64_t pvt_modseq, struct dsync_transaction_log_scan **scan_r, bool *pvt_too_old_r); HASH_TABLE_TYPE(dsync_uid_mail_change) dsync_transaction_log_scan_get_hash(struct dsync_transaction_log_scan *scan); HASH_TABLE_TYPE(dsync_attr_change) dsync_transaction_log_scan_get_attr_hash(struct dsync_transaction_log_scan *scan); /* Returns TRUE if the entire transaction log was scanned */ bool dsync_transaction_log_scan_has_all_changes(struct dsync_transaction_log_scan *scan); /* If the given UID has been expunged after the initial log scan, create/update a change record for it and return it. */ struct dsync_mail_change * dsync_transaction_log_scan_find_new_expunge(struct dsync_transaction_log_scan *scan, uint32_t uid); void dsync_transaction_log_scan_deinit(struct dsync_transaction_log_scan **scan); #endif dovecot-2.3.21.1/src/doveadm/dsync/dsync-brain.c0000644000000000000000000006536114656633576016233 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "hash.h" #include "hostpid.h" #include "str.h" #include "file-create-locked.h" #include "process-title.h" #include "settings-parser.h" #include "master-service.h" #include "master-service-settings.h" #include "mail-namespace.h" #include "dsync-mailbox-tree.h" #include "dsync-ibc.h" #include "dsync-brain-private.h" #include "dsync-mailbox-import.h" #include "dsync-mailbox-export.h" #include enum dsync_brain_title { DSYNC_BRAIN_TITLE_NONE = 0, DSYNC_BRAIN_TITLE_LOCKING, }; static const char *dsync_state_names[] = { "master_recv_handshake", "slave_recv_handshake", "master_send_last_common", "slave_recv_last_common", "send_mailbox_tree", "send_mailbox_tree_deletes", "recv_mailbox_tree", "recv_mailbox_tree_deletes", "master_send_mailbox", "slave_recv_mailbox", "sync_mails", "finish", "done" }; struct dsync_mailbox_list_module dsync_mailbox_list_module = MODULE_CONTEXT_INIT(&mailbox_list_module_register); static void dsync_brain_mailbox_states_dump(struct dsync_brain *brain); static const char * dsync_brain_get_proctitle_full(struct dsync_brain *brain, enum dsync_brain_title title) { string_t *str = t_str_new(128); const char *import_title, *export_title; str_append_c(str, '['); if (brain->process_title_prefix != NULL) str_append(str, brain->process_title_prefix); str_append(str, brain->user->username); if (brain->box == NULL) { str_append_c(str, ' '); str_append(str, dsync_state_names[brain->state]); } else { str_append_c(str, ' '); str_append(str, mailbox_get_vname(brain->box)); import_title = brain->box_importer == NULL ? "" : dsync_mailbox_import_get_proctitle(brain->box_importer); export_title = brain->box_exporter == NULL ? "" : dsync_mailbox_export_get_proctitle(brain->box_exporter); if (import_title[0] == '\0' && export_title[0] == '\0') { str_printfa(str, " send:%s recv:%s", dsync_box_state_names[brain->box_send_state], dsync_box_state_names[brain->box_recv_state]); } else { if (import_title[0] != '\0') { str_append(str, " import:"); str_append(str, import_title); } if (export_title[0] != '\0') { str_append(str, " export:"); str_append(str, export_title); } } } switch (title) { case DSYNC_BRAIN_TITLE_NONE: break; case DSYNC_BRAIN_TITLE_LOCKING: str_append(str, " locking "DSYNC_LOCK_FILENAME); break; } str_append_c(str, ']'); return str_c(str); } static const char *dsync_brain_get_proctitle(struct dsync_brain *brain) { return dsync_brain_get_proctitle_full(brain, DSYNC_BRAIN_TITLE_NONE); } static void dsync_brain_run_io(void *context) { struct dsync_brain *brain = context; bool changed, try_pending; if (dsync_ibc_has_failed(brain->ibc)) { io_loop_stop(current_ioloop); brain->failed = TRUE; return; } try_pending = TRUE; do { if (!dsync_brain_run(brain, &changed)) { io_loop_stop(current_ioloop); break; } if (changed) try_pending = TRUE; else if (try_pending) { if (dsync_ibc_has_pending_data(brain->ibc)) changed = TRUE; try_pending = FALSE; } } while (changed); } static struct dsync_brain * dsync_brain_common_init(struct mail_user *user, struct dsync_ibc *ibc) { struct dsync_brain *brain; const struct master_service_settings *service_set; pool_t pool; service_set = master_service_settings_get(master_service); mail_user_ref(user); pool = pool_alloconly_create("dsync brain", 10240); brain = p_new(pool, struct dsync_brain, 1); brain->pool = pool; brain->user = user; brain->ibc = ibc; brain->sync_type = DSYNC_BRAIN_SYNC_TYPE_UNKNOWN; brain->lock_fd = -1; brain->verbose_proctitle = service_set->verbose_proctitle; hash_table_create(&brain->mailbox_states, pool, 0, guid_128_hash, guid_128_cmp); p_array_init(&brain->remote_mailbox_states, pool, 64); return brain; } static void dsync_brain_set_flags(struct dsync_brain *brain, enum dsync_brain_flags flags) { brain->mail_requests = (flags & DSYNC_BRAIN_FLAG_SEND_MAIL_REQUESTS) != 0; brain->backup_send = (flags & DSYNC_BRAIN_FLAG_BACKUP_SEND) != 0; brain->backup_recv = (flags & DSYNC_BRAIN_FLAG_BACKUP_RECV) != 0; brain->debug = (flags & DSYNC_BRAIN_FLAG_DEBUG) != 0; brain->sync_visible_namespaces = (flags & DSYNC_BRAIN_FLAG_SYNC_VISIBLE_NAMESPACES) != 0; brain->no_mail_sync = (flags & DSYNC_BRAIN_FLAG_NO_MAIL_SYNC) != 0; brain->no_backup_overwrite = (flags & DSYNC_BRAIN_FLAG_NO_BACKUP_OVERWRITE) != 0; brain->no_mail_prefetch = (flags & DSYNC_BRAIN_FLAG_NO_MAIL_PREFETCH) != 0; brain->no_notify = (flags & DSYNC_BRAIN_FLAG_NO_NOTIFY) != 0; brain->empty_hdr_workaround = (flags & DSYNC_BRAIN_FLAG_EMPTY_HDR_WORKAROUND) != 0; brain->no_header_hashes = (flags & DSYNC_BRAIN_FLAG_NO_HEADER_HASHES) != 0; } static void dsync_brain_open_virtual_all_box(struct dsync_brain *brain, const char *vname) { struct mail_namespace *ns; ns = mail_namespace_find(brain->user->namespaces, vname); brain->virtual_all_box = mailbox_alloc(ns->list, vname, MAILBOX_FLAG_READONLY); } struct dsync_brain * dsync_brain_master_init(struct mail_user *user, struct dsync_ibc *ibc, enum dsync_brain_sync_type sync_type, enum dsync_brain_flags flags, const struct dsync_brain_settings *set) { struct dsync_ibc_settings ibc_set; struct dsync_brain *brain; struct mail_namespace *ns; string_t *sync_ns_str = NULL; const char *error; i_assert(sync_type != DSYNC_BRAIN_SYNC_TYPE_UNKNOWN); i_assert(sync_type != DSYNC_BRAIN_SYNC_TYPE_STATE || (set->state != NULL && *set->state != '\0')); i_assert(N_ELEMENTS(dsync_state_names) == DSYNC_STATE_DONE+1); brain = dsync_brain_common_init(user, ibc); brain->process_title_prefix = p_strdup(brain->pool, set->process_title_prefix); brain->sync_type = sync_type; if (array_count(&set->sync_namespaces) > 0) { sync_ns_str = t_str_new(128); p_array_init(&brain->sync_namespaces, brain->pool, array_count(&set->sync_namespaces)); array_foreach_elem(&set->sync_namespaces, ns) { str_append(sync_ns_str, ns->prefix); str_append_c(sync_ns_str, '\n'); array_push_back(&brain->sync_namespaces, &ns); } str_delete(sync_ns_str, str_len(sync_ns_str)-1, 1); } brain->alt_char = set->mailbox_alt_char == '\0' ? '_' : set->mailbox_alt_char; brain->sync_since_timestamp = set->sync_since_timestamp; brain->sync_until_timestamp = set->sync_until_timestamp; brain->sync_max_size = set->sync_max_size; brain->sync_flag = p_strdup(brain->pool, set->sync_flag); brain->sync_box = p_strdup(brain->pool, set->sync_box); brain->exclude_mailboxes = set->exclude_mailboxes == NULL ? NULL : p_strarray_dup(brain->pool, set->exclude_mailboxes); memcpy(brain->sync_box_guid, set->sync_box_guid, sizeof(brain->sync_box_guid)); brain->lock_timeout = set->lock_timeout_secs; if (brain->lock_timeout != 0) brain->mailbox_lock_timeout_secs = brain->lock_timeout; else brain->mailbox_lock_timeout_secs = DSYNC_MAILBOX_DEFAULT_LOCK_TIMEOUT_SECS; brain->import_commit_msgs_interval = set->import_commit_msgs_interval; brain->master_brain = TRUE; brain->hashed_headers = (const char*const*)p_strarray_dup(brain->pool, set->hashed_headers); dsync_brain_set_flags(brain, flags); if (set->virtual_all_box != NULL) dsync_brain_open_virtual_all_box(brain, set->virtual_all_box); if (sync_type != DSYNC_BRAIN_SYNC_TYPE_STATE) ; else if (dsync_mailbox_states_import(brain->mailbox_states, brain->pool, set->state, &error) < 0) { hash_table_clear(brain->mailbox_states, FALSE); i_error("Saved sync state is invalid, " "falling back to full sync: %s", error); brain->sync_type = sync_type = DSYNC_BRAIN_SYNC_TYPE_FULL; } else { if (brain->debug) { i_debug("brain %c: Imported mailbox states:", brain->master_brain ? 'M' : 'S'); dsync_brain_mailbox_states_dump(brain); } } dsync_brain_mailbox_trees_init(brain); i_zero(&ibc_set); ibc_set.hostname = my_hostdomain(); ibc_set.sync_ns_prefixes = sync_ns_str == NULL ? NULL : str_c(sync_ns_str); ibc_set.sync_box = set->sync_box; ibc_set.virtual_all_box = set->virtual_all_box; ibc_set.exclude_mailboxes = set->exclude_mailboxes; ibc_set.sync_since_timestamp = set->sync_since_timestamp; ibc_set.sync_until_timestamp = set->sync_until_timestamp; ibc_set.sync_max_size = set->sync_max_size; ibc_set.sync_flags = set->sync_flag; memcpy(ibc_set.sync_box_guid, set->sync_box_guid, sizeof(ibc_set.sync_box_guid)); ibc_set.alt_char = brain->alt_char; ibc_set.sync_type = sync_type; ibc_set.hdr_hash_v2 = TRUE; ibc_set.lock_timeout = set->lock_timeout_secs; ibc_set.import_commit_msgs_interval = set->import_commit_msgs_interval; ibc_set.hashed_headers = set->hashed_headers; /* reverse the backup direction for the slave */ ibc_set.brain_flags = flags & ENUM_NEGATE(DSYNC_BRAIN_FLAG_BACKUP_SEND | DSYNC_BRAIN_FLAG_BACKUP_RECV); if ((flags & DSYNC_BRAIN_FLAG_BACKUP_SEND) != 0) ibc_set.brain_flags |= DSYNC_BRAIN_FLAG_BACKUP_RECV; else if ((flags & DSYNC_BRAIN_FLAG_BACKUP_RECV) != 0) ibc_set.brain_flags |= DSYNC_BRAIN_FLAG_BACKUP_SEND; dsync_ibc_send_handshake(ibc, &ibc_set); dsync_ibc_set_io_callback(ibc, dsync_brain_run_io, brain); brain->state = DSYNC_STATE_MASTER_RECV_HANDSHAKE; if (brain->verbose_proctitle) process_title_set(dsync_brain_get_proctitle(brain)); return brain; } struct dsync_brain * dsync_brain_slave_init(struct mail_user *user, struct dsync_ibc *ibc, bool local, const char *process_title_prefix, char default_alt_char) { struct dsync_ibc_settings ibc_set; struct dsync_brain *brain; i_assert(default_alt_char != '\0'); brain = dsync_brain_common_init(user, ibc); brain->alt_char = default_alt_char; brain->process_title_prefix = p_strdup(brain->pool, process_title_prefix); brain->state = DSYNC_STATE_SLAVE_RECV_HANDSHAKE; if (local) { /* both master and slave are running within the same process, update the proctitle only for master. */ brain->verbose_proctitle = FALSE; } i_zero(&ibc_set); ibc_set.hdr_hash_v2 = TRUE; ibc_set.hostname = my_hostdomain(); dsync_ibc_send_handshake(ibc, &ibc_set); if (brain->verbose_proctitle) process_title_set(dsync_brain_get_proctitle(brain)); dsync_ibc_set_io_callback(ibc, dsync_brain_run_io, brain); return brain; } static void dsync_brain_purge(struct dsync_brain *brain) { struct mail_namespace *ns; struct mail_storage *storage; for (ns = brain->user->namespaces; ns != NULL; ns = ns->next) { if (!dsync_brain_want_namespace(brain, ns)) continue; storage = mail_namespace_get_default_storage(ns); if (mail_storage_purge(storage) < 0) { i_error("Purging namespace '%s' failed: %s", ns->prefix, mail_storage_get_last_internal_error(storage, NULL)); } } } int dsync_brain_deinit(struct dsync_brain **_brain, enum mail_error *error_r) { struct dsync_brain *brain = *_brain; int ret; *_brain = NULL; if (dsync_ibc_has_timed_out(brain->ibc)) { i_error("Timeout during state=%s%s", dsync_state_names[brain->state], brain->state != DSYNC_STATE_SYNC_MAILS ? "" : t_strdup_printf(" (send=%s recv=%s)", dsync_box_state_names[brain->box_send_state], dsync_box_state_names[brain->box_recv_state])); } if (dsync_ibc_has_failed(brain->ibc) || brain->state != DSYNC_STATE_DONE) brain->failed = TRUE; dsync_ibc_close_mail_streams(brain->ibc); if (brain->purge && !brain->failed) dsync_brain_purge(brain); if (brain->box != NULL) dsync_brain_sync_mailbox_deinit(brain); if (brain->virtual_all_box != NULL) mailbox_free(&brain->virtual_all_box); if (brain->local_tree_iter != NULL) dsync_mailbox_tree_iter_deinit(&brain->local_tree_iter); if (brain->local_mailbox_tree != NULL) dsync_mailbox_tree_deinit(&brain->local_mailbox_tree); if (brain->remote_mailbox_tree != NULL) dsync_mailbox_tree_deinit(&brain->remote_mailbox_tree); hash_table_iterate_deinit(&brain->mailbox_states_iter); hash_table_destroy(&brain->mailbox_states); pool_unref(&brain->dsync_box_pool); if (brain->lock_fd != -1) { /* unlink the lock file before it gets unlocked */ i_unlink(brain->lock_path); if (brain->debug) { i_debug("brain %c: Unlocked %s", brain->master_brain ? 'M' : 'S', brain->lock_path); } file_lock_free(&brain->lock); i_close_fd(&brain->lock_fd); } ret = brain->failed ? -1 : 0; mail_user_unref(&brain->user); *error_r = !brain->failed ? 0 : (brain->mail_error == 0 ? MAIL_ERROR_TEMP : brain->mail_error); pool_unref(&brain->pool); return ret; } static int dsync_brain_lock(struct dsync_brain *brain, const char *remote_hostname) { const struct file_create_settings lock_set = { .lock_timeout_secs = brain->lock_timeout, .lock_settings = { .lock_method = FILE_LOCK_METHOD_FCNTL, }, }; const char *home, *error, *local_hostname = my_hostdomain(); bool created; int ret; if ((ret = strcmp(remote_hostname, local_hostname)) < 0) { /* locking done by remote */ if (brain->debug) { i_debug("brain %c: Locking done by remote " "(local hostname=%s, remote hostname=%s)", brain->master_brain ? 'M' : 'S', local_hostname, remote_hostname); } return 0; } if (ret == 0 && !brain->master_brain) { /* running dsync within the same server. locking done by master brain. */ if (brain->debug) { i_debug("brain %c: Locking done by local master-brain " "(local hostname=%s, remote hostname=%s)", brain->master_brain ? 'M' : 'S', local_hostname, remote_hostname); } return 0; } if ((ret = mail_user_get_home(brain->user, &home)) < 0) { i_error("Couldn't look up user's home dir"); return -1; } if (ret == 0) { i_error("User has no home directory"); return -1; } if (brain->verbose_proctitle) process_title_set(dsync_brain_get_proctitle_full(brain, DSYNC_BRAIN_TITLE_LOCKING)); brain->lock_path = p_strconcat(brain->pool, home, "/"DSYNC_LOCK_FILENAME, NULL); brain->lock_fd = file_create_locked(brain->lock_path, &lock_set, &brain->lock, &created, &error); if (brain->lock_fd == -1 && errno == ENOENT) { /* home directory not created */ if (mail_user_home_mkdir(brain->user) < 0) return -1; brain->lock_fd = file_create_locked(brain->lock_path, &lock_set, &brain->lock, &created, &error); } if (brain->lock_fd == -1) i_error("Couldn't lock %s: %s", brain->lock_path, error); else if (brain->debug) { i_debug("brain %c: Locking done locally in %s " "(local hostname=%s, remote hostname=%s)", brain->master_brain ? 'M' : 'S', brain->lock_path, local_hostname, remote_hostname); } if (brain->verbose_proctitle) process_title_set(dsync_brain_get_proctitle(brain)); return brain->lock_fd == -1 ? -1 : 0; } static void dsync_brain_set_hdr_hash_version(struct dsync_brain *brain, const struct dsync_ibc_settings *ibc_set) { if (ibc_set->hdr_hash_v3) brain->hdr_hash_version = 3; else if (ibc_set->hdr_hash_v2) brain->hdr_hash_version = 3; else brain->hdr_hash_version = 1; } static bool dsync_brain_master_recv_handshake(struct dsync_brain *brain) { const struct dsync_ibc_settings *ibc_set; i_assert(brain->master_brain); if (dsync_ibc_recv_handshake(brain->ibc, &ibc_set) == 0) return FALSE; if (brain->lock_timeout > 0) { if (dsync_brain_lock(brain, ibc_set->hostname) < 0) { brain->failed = TRUE; return FALSE; } } dsync_brain_set_hdr_hash_version(brain, ibc_set); brain->state = brain->sync_type == DSYNC_BRAIN_SYNC_TYPE_STATE ? DSYNC_STATE_MASTER_SEND_LAST_COMMON : DSYNC_STATE_SEND_MAILBOX_TREE; return TRUE; } static bool dsync_brain_slave_recv_handshake(struct dsync_brain *brain) { const struct dsync_ibc_settings *ibc_set; struct mail_namespace *ns; const char *const *prefixes; i_assert(!brain->master_brain); if (dsync_ibc_recv_handshake(brain->ibc, &ibc_set) == 0) return FALSE; dsync_brain_set_hdr_hash_version(brain, ibc_set); if (ibc_set->lock_timeout > 0) { brain->lock_timeout = ibc_set->lock_timeout; brain->mailbox_lock_timeout_secs = brain->lock_timeout; if (dsync_brain_lock(brain, ibc_set->hostname) < 0) { brain->failed = TRUE; return FALSE; } } else { brain->mailbox_lock_timeout_secs = DSYNC_MAILBOX_DEFAULT_LOCK_TIMEOUT_SECS; } if (ibc_set->sync_ns_prefixes != NULL) { p_array_init(&brain->sync_namespaces, brain->pool, 4); prefixes = t_strsplit(ibc_set->sync_ns_prefixes, "\n"); if (prefixes[0] == NULL) { /* ugly workaround for strsplit API: there was one prefix="" entry */ static const char *empty_prefix[] = { "", NULL }; prefixes = empty_prefix; } for (; *prefixes != NULL; prefixes++) { ns = mail_namespace_find(brain->user->namespaces, *prefixes); if (ns == NULL) { i_error("Namespace not found: '%s'", *prefixes); brain->failed = TRUE; return FALSE; } array_push_back(&brain->sync_namespaces, &ns); } } brain->sync_box = p_strdup(brain->pool, ibc_set->sync_box); brain->exclude_mailboxes = ibc_set->exclude_mailboxes == NULL ? NULL : p_strarray_dup(brain->pool, ibc_set->exclude_mailboxes); brain->sync_since_timestamp = ibc_set->sync_since_timestamp; brain->sync_until_timestamp = ibc_set->sync_until_timestamp; brain->sync_max_size = ibc_set->sync_max_size; brain->sync_flag = p_strdup(brain->pool, ibc_set->sync_flags); memcpy(brain->sync_box_guid, ibc_set->sync_box_guid, sizeof(brain->sync_box_guid)); if (ibc_set->alt_char != '\0') brain->alt_char = ibc_set->alt_char; i_assert(brain->sync_type == DSYNC_BRAIN_SYNC_TYPE_UNKNOWN); brain->sync_type = ibc_set->sync_type; dsync_brain_set_flags(brain, ibc_set->brain_flags); if (ibc_set->hashed_headers != NULL) brain->hashed_headers = p_strarray_dup(brain->pool, (const char*const*)ibc_set->hashed_headers); /* this flag is only set on the remote slave brain */ brain->purge = (ibc_set->brain_flags & DSYNC_BRAIN_FLAG_PURGE_REMOTE) != 0; if (ibc_set->virtual_all_box != NULL) dsync_brain_open_virtual_all_box(brain, ibc_set->virtual_all_box); dsync_brain_mailbox_trees_init(brain); if (brain->sync_type == DSYNC_BRAIN_SYNC_TYPE_STATE) brain->state = DSYNC_STATE_SLAVE_RECV_LAST_COMMON; else brain->state = DSYNC_STATE_SEND_MAILBOX_TREE; return TRUE; } static void dsync_brain_master_send_last_common(struct dsync_brain *brain) { struct dsync_mailbox_state *state; uint8_t *guid; enum dsync_ibc_send_ret ret = DSYNC_IBC_SEND_RET_OK; i_assert(brain->master_brain); if (brain->mailbox_states_iter == NULL) { brain->mailbox_states_iter = hash_table_iterate_init(brain->mailbox_states); } for (;;) { if (ret == DSYNC_IBC_SEND_RET_FULL) return; if (!hash_table_iterate(brain->mailbox_states_iter, brain->mailbox_states, &guid, &state)) break; ret = dsync_ibc_send_mailbox_state(brain->ibc, state); } hash_table_iterate_deinit(&brain->mailbox_states_iter); dsync_ibc_send_end_of_list(brain->ibc, DSYNC_IBC_EOL_MAILBOX_STATE); brain->state = DSYNC_STATE_SEND_MAILBOX_TREE; } static void dsync_mailbox_state_add(struct dsync_brain *brain, const struct dsync_mailbox_state *state) { struct dsync_mailbox_state *dupstate; uint8_t *guid_p; dupstate = p_new(brain->pool, struct dsync_mailbox_state, 1); *dupstate = *state; guid_p = dupstate->mailbox_guid; hash_table_insert(brain->mailbox_states, guid_p, dupstate); } static bool dsync_brain_slave_recv_last_common(struct dsync_brain *brain) { struct dsync_mailbox_state state; enum dsync_ibc_recv_ret ret; bool changed = FALSE; i_assert(!brain->master_brain); while ((ret = dsync_ibc_recv_mailbox_state(brain->ibc, &state)) > 0) { dsync_mailbox_state_add(brain, &state); changed = TRUE; } if (ret == DSYNC_IBC_RECV_RET_FINISHED) { brain->state = DSYNC_STATE_SEND_MAILBOX_TREE; changed = TRUE; } return changed; } static bool dsync_brain_finish(struct dsync_brain *brain) { const char *error; enum mail_error mail_error; bool require_full_resync; enum dsync_ibc_recv_ret ret; if (!brain->master_brain) { dsync_ibc_send_finish(brain->ibc, brain->failed ? "dsync failed" : NULL, brain->mail_error, brain->require_full_resync); brain->state = DSYNC_STATE_DONE; return TRUE; } ret = dsync_ibc_recv_finish(brain->ibc, &error, &mail_error, &require_full_resync); if (ret == DSYNC_IBC_RECV_RET_TRYAGAIN) return FALSE; if (error != NULL) { i_error("Remote dsync failed: %s", error); brain->failed = TRUE; if (mail_error != 0 && (brain->mail_error == 0 || brain->mail_error == MAIL_ERROR_TEMP)) brain->mail_error = mail_error; } if (require_full_resync) brain->require_full_resync = TRUE; brain->state = DSYNC_STATE_DONE; return TRUE; } static bool dsync_brain_run_real(struct dsync_brain *brain, bool *changed_r) { enum dsync_state orig_state = brain->state; enum dsync_box_state orig_box_recv_state = brain->box_recv_state; enum dsync_box_state orig_box_send_state = brain->box_send_state; bool changed = FALSE, ret = TRUE; if (brain->failed) return FALSE; switch (brain->state) { case DSYNC_STATE_MASTER_RECV_HANDSHAKE: changed = dsync_brain_master_recv_handshake(brain); break; case DSYNC_STATE_SLAVE_RECV_HANDSHAKE: changed = dsync_brain_slave_recv_handshake(brain); break; case DSYNC_STATE_MASTER_SEND_LAST_COMMON: dsync_brain_master_send_last_common(brain); changed = TRUE; break; case DSYNC_STATE_SLAVE_RECV_LAST_COMMON: changed = dsync_brain_slave_recv_last_common(brain); break; case DSYNC_STATE_SEND_MAILBOX_TREE: dsync_brain_send_mailbox_tree(brain); changed = TRUE; break; case DSYNC_STATE_RECV_MAILBOX_TREE: changed = dsync_brain_recv_mailbox_tree(brain); break; case DSYNC_STATE_SEND_MAILBOX_TREE_DELETES: dsync_brain_send_mailbox_tree_deletes(brain); changed = TRUE; break; case DSYNC_STATE_RECV_MAILBOX_TREE_DELETES: changed = dsync_brain_recv_mailbox_tree_deletes(brain); break; case DSYNC_STATE_MASTER_SEND_MAILBOX: dsync_brain_master_send_mailbox(brain); changed = TRUE; break; case DSYNC_STATE_SLAVE_RECV_MAILBOX: changed = dsync_brain_slave_recv_mailbox(brain); break; case DSYNC_STATE_SYNC_MAILS: changed = dsync_brain_sync_mails(brain); break; case DSYNC_STATE_FINISH: changed = dsync_brain_finish(brain); break; case DSYNC_STATE_DONE: changed = TRUE; ret = FALSE; break; } if (brain->verbose_proctitle) { if (orig_state != brain->state || orig_box_recv_state != brain->box_recv_state || orig_box_send_state != brain->box_send_state || ++brain->proctitle_update_counter % 100 == 0) process_title_set(dsync_brain_get_proctitle(brain)); } *changed_r = changed; return brain->failed ? FALSE : ret; } bool dsync_brain_run(struct dsync_brain *brain, bool *changed_r) { bool ret; *changed_r = FALSE; if (dsync_ibc_has_failed(brain->ibc)) { brain->failed = TRUE; return FALSE; } T_BEGIN { ret = dsync_brain_run_real(brain, changed_r); } T_END; return ret; } static void dsync_brain_mailbox_states_dump(struct dsync_brain *brain) { struct hash_iterate_context *iter; struct dsync_mailbox_state *state; uint8_t *guid; iter = hash_table_iterate_init(brain->mailbox_states); while (hash_table_iterate(iter, brain->mailbox_states, &guid, &state)) { i_debug("brain %c: Mailbox %s state: uidvalidity=%u uid=%u modseq=%"PRIu64" pvt_modseq=%"PRIu64" messages=%u changes_during_sync=%d", brain->master_brain ? 'M' : 'S', guid_128_to_string(guid), state->last_uidvalidity, state->last_common_uid, state->last_common_modseq, state->last_common_pvt_modseq, state->last_messages_count, state->changes_during_sync ? 1 : 0); } hash_table_iterate_deinit(&iter); } void dsync_brain_get_state(struct dsync_brain *brain, string_t *output) { struct hash_iterate_context *iter; struct dsync_mailbox_node *node; const struct dsync_mailbox_state *new_state; struct dsync_mailbox_state *state; const uint8_t *guid_p; uint8_t *guid; if (brain->require_full_resync) return; /* update mailbox states */ array_foreach(&brain->remote_mailbox_states, new_state) { guid_p = new_state->mailbox_guid; state = hash_table_lookup(brain->mailbox_states, guid_p); if (state != NULL) *state = *new_state; else dsync_mailbox_state_add(brain, new_state); } /* remove nonexistent mailboxes */ iter = hash_table_iterate_init(brain->mailbox_states); while (hash_table_iterate(iter, brain->mailbox_states, &guid, &state)) { node = dsync_mailbox_tree_lookup_guid(brain->local_mailbox_tree, guid); if (node == NULL || node->existence != DSYNC_MAILBOX_NODE_EXISTS) { if (brain->debug) { i_debug("brain %c: Removed state for deleted mailbox %s", brain->master_brain ? 'M' : 'S', guid_128_to_string(guid)); } hash_table_remove(brain->mailbox_states, guid); } } hash_table_iterate_deinit(&iter); if (brain->debug) { i_debug("brain %c: Exported mailbox states:", brain->master_brain ? 'M' : 'S'); dsync_brain_mailbox_states_dump(brain); } dsync_mailbox_states_export(brain->mailbox_states, output); } enum dsync_brain_sync_type dsync_brain_get_sync_type(struct dsync_brain *brain) { return brain->sync_type; } bool dsync_brain_has_failed(struct dsync_brain *brain) { return brain->failed; } const char *dsync_brain_get_unexpected_changes_reason(struct dsync_brain *brain, bool *remote_only_r) { if (brain->changes_during_sync == NULL && brain->changes_during_remote_sync) { *remote_only_r = TRUE; return "Remote notified that changes happened during sync"; } *remote_only_r = FALSE; return brain->changes_during_sync; } static bool dsync_brain_want_shared_namespace(const struct mail_namespace *ns, const struct mail_namespace *sync_ns) { /* Include shared namespaces and all its children in the sync (e.g. "Shared/example.com" will be synced to "Shared/"). This also allows "dsync -n Shared/example.com/" with "Shared/example.com/username/" style shared namespace config. */ return (ns->type == MAIL_NAMESPACE_TYPE_SHARED) && (sync_ns->type == MAIL_NAMESPACE_TYPE_SHARED) && str_begins(ns->prefix, sync_ns->prefix); } bool dsync_brain_want_namespace(struct dsync_brain *brain, struct mail_namespace *ns) { struct mail_namespace *sync_ns; if (array_is_created(&brain->sync_namespaces)) { array_foreach_elem(&brain->sync_namespaces, sync_ns) { if (ns == sync_ns) return TRUE; if (dsync_brain_want_shared_namespace(ns, sync_ns)) return TRUE; } return FALSE; } if (ns->alias_for != NULL) { /* always skip aliases */ return FALSE; } if (brain->sync_visible_namespaces) { if ((ns->flags & NAMESPACE_FLAG_HIDDEN) == 0) return TRUE; if ((ns->flags & (NAMESPACE_FLAG_LIST_PREFIX | NAMESPACE_FLAG_LIST_CHILDREN)) != 0) return TRUE; return FALSE; } else { return strcmp(ns->unexpanded_set->location, SETTING_STRVAR_UNEXPANDED) == 0; } } void dsync_brain_set_changes_during_sync(struct dsync_brain *brain, const char *reason) { if (brain->debug) { i_debug("brain %c: Change during sync: %s", brain->master_brain ? 'M' : 'S', reason); } if (brain->changes_during_sync == NULL) brain->changes_during_sync = p_strdup(brain->pool, reason); } dovecot-2.3.21.1/src/doveadm/dsync/dsync-mail.c0000644000000000000000000000776714656633576016070 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "hex-binary.h" #include "md5.h" #include "istream.h" #include "istream-crlf.h" #include "message-header-hash.h" #include "message-size.h" #include "mail-storage.h" #include "dsync-mail.h" struct mailbox_header_lookup_ctx * dsync_mail_get_hash_headers(struct mailbox *box, const char *const *hashed_headers) { return mailbox_header_lookup_init(box, hashed_headers); } int dsync_mail_get_hdr_hash(struct mail *mail, unsigned int version, const char *const *hashed_headers, const char **hdr_hash_r) { struct istream *hdr_input, *input; struct mailbox_header_lookup_ctx *hdr_ctx; struct message_header_hash_context hash_ctx; struct md5_context md5_ctx; unsigned char md5_result[MD5_RESULTLEN]; const unsigned char *data; size_t size; ssize_t sret; int ret = 0; hdr_ctx = mailbox_header_lookup_init(mail->box, hashed_headers); ret = mail_get_header_stream(mail, hdr_ctx, &hdr_input); mailbox_header_lookup_unref(&hdr_ctx); if (ret < 0) return -1; input = i_stream_create_lf(hdr_input); md5_init(&md5_ctx); i_zero(&hash_ctx); while ((sret = i_stream_read_more(input, &data, &size)) > 0) { message_header_hash_more(&hash_ctx, &hash_method_md5, &md5_ctx, version, data, size); i_stream_skip(input, size); } i_assert(sret == -1); if (input->stream_errno != 0) ret = -1; i_stream_unref(&input); md5_final(&md5_ctx, md5_result); *hdr_hash_r = binary_to_hex(md5_result, sizeof(md5_result)); return ret; } int dsync_mail_fill(struct mail *mail, bool minimal_fill, struct dsync_mail *dmail_r, const char **error_field_r) { const char *guid; i_zero(dmail_r); if (mail_get_special(mail, MAIL_FETCH_GUID, &guid) < 0) { *error_field_r = "GUID"; return -1; } dmail_r->guid = guid; dmail_r->uid = mail->uid; dmail_r->input_mail = mail; dmail_r->input_mail_uid = mail->uid; if (mail_get_save_date(mail, &dmail_r->saved_date) < 0) { *error_field_r = "saved-date"; return -1; } if (!minimal_fill) return dsync_mail_fill_nonminimal(mail, dmail_r, error_field_r); dmail_r->minimal_fields = TRUE; return 0; } int dsync_mail_fill_nonminimal(struct mail *mail, struct dsync_mail *dmail_r, const char **error_field_r) { const char *str; if (mail_get_stream(mail, NULL, NULL, &dmail_r->input) < 0) { *error_field_r = "body"; return -1; } if (mail_get_special(mail, MAIL_FETCH_UIDL_BACKEND, &dmail_r->pop3_uidl) < 0) { *error_field_r = "pop3-uidl"; return -1; } if (mail_get_special(mail, MAIL_FETCH_POP3_ORDER, &str) < 0) { *error_field_r = "pop3-order"; return -1; } if (*str != '\0') { if (str_to_uint32(str, &dmail_r->pop3_order) < 0) i_unreached(); } if (mail_get_received_date(mail, &dmail_r->received_date) < 0) { *error_field_r = "received-date"; return -1; } return 0; } static void const_string_array_dup(pool_t pool, const ARRAY_TYPE(const_string) *src, ARRAY_TYPE(const_string) *dest) { const char *const *strings, *str; unsigned int i, count; if (!array_is_created(src)) return; strings = array_get(src, &count); if (count == 0) return; p_array_init(dest, pool, count); for (i = 0; i < count; i++) { str = p_strdup(pool, strings[i]); array_push_back(dest, &str); } } void dsync_mail_change_dup(pool_t pool, const struct dsync_mail_change *src, struct dsync_mail_change *dest_r) { dest_r->type = src->type; dest_r->uid = src->uid; if (src->guid != NULL) { dest_r->guid = *src->guid == '\0' ? "" : p_strdup(pool, src->guid); } dest_r->hdr_hash = p_strdup(pool, src->hdr_hash); dest_r->modseq = src->modseq; dest_r->pvt_modseq = src->pvt_modseq; dest_r->add_flags = src->add_flags; dest_r->remove_flags = src->remove_flags; dest_r->final_flags = src->final_flags; dest_r->keywords_reset = src->keywords_reset; const_string_array_dup(pool, &src->keyword_changes, &dest_r->keyword_changes); dest_r->received_timestamp = src->received_timestamp; dest_r->virtual_size = src->virtual_size; } dovecot-2.3.21.1/src/doveadm/dsync/dsync-ibc-pipe.c0000644000000000000000000004001214656633576016612 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "istream.h" #include "dsync-mail.h" #include "dsync-mailbox.h" #include "dsync-mailbox-state.h" #include "dsync-mailbox-tree.h" #include "dsync-ibc-private.h" enum item_type { ITEM_END_OF_LIST, ITEM_HANDSHAKE, ITEM_MAILBOX_STATE, ITEM_MAILBOX_TREE_NODE, ITEM_MAILBOX_DELETE, ITEM_MAILBOX, ITEM_MAILBOX_ATTRIBUTE, ITEM_MAIL_CHANGE, ITEM_MAIL_REQUEST, ITEM_MAIL, ITEM_FINISH }; struct item { enum item_type type; pool_t pool; union { struct dsync_ibc_settings set; struct dsync_mailbox_state state; struct dsync_mailbox_node node; guid_128_t mailbox_guid; struct dsync_mailbox dsync_box; struct dsync_mailbox_attribute attr; struct dsync_mail_change change; struct dsync_mail_request request; struct dsync_mail mail; struct { const struct dsync_mailbox_delete *deletes; unsigned int count; char hierarchy_sep; char escape_char; } mailbox_delete; struct { const char *error; enum mail_error mail_error; bool require_full_resync; } finish; } u; }; struct dsync_ibc_pipe { struct dsync_ibc ibc; ARRAY(pool_t) pools; ARRAY(struct item) item_queue; struct dsync_ibc_pipe *remote; pool_t pop_pool; struct item pop_item; }; static pool_t dsync_ibc_pipe_get_pool(struct dsync_ibc_pipe *pipe) { pool_t *pools, ret; unsigned int count; pools = array_get_modifiable(&pipe->pools, &count); if (count == 0) return pool_alloconly_create(MEMPOOL_GROWING"pipe item pool", 1024); ret = pools[count-1]; array_delete(&pipe->pools, count-1, 1); p_clear(ret); return ret; } static struct item * ATTR_NOWARN_UNUSED_RESULT dsync_ibc_pipe_push_item(struct dsync_ibc_pipe *pipe, enum item_type type) { struct item *item; item = array_append_space(&pipe->item_queue); item->type = type; switch (type) { case ITEM_END_OF_LIST: case ITEM_MAILBOX_STATE: case ITEM_MAILBOX_DELETE: break; case ITEM_HANDSHAKE: case ITEM_MAILBOX: case ITEM_MAILBOX_TREE_NODE: case ITEM_MAILBOX_ATTRIBUTE: case ITEM_MAIL_CHANGE: case ITEM_MAIL_REQUEST: case ITEM_MAIL: case ITEM_FINISH: item->pool = dsync_ibc_pipe_get_pool(pipe); break; } return item; } static struct item * dsync_ibc_pipe_pop_item(struct dsync_ibc_pipe *pipe, enum item_type type) { struct item *item; if (array_count(&pipe->item_queue) == 0) return NULL; item = array_front_modifiable(&pipe->item_queue); i_assert(item->type == type); pipe->pop_item = *item; array_pop_front(&pipe->item_queue); item = NULL; pool_unref(&pipe->pop_pool); pipe->pop_pool = pipe->pop_item.pool; return &pipe->pop_item; } static bool dsync_ibc_pipe_try_pop_eol(struct dsync_ibc_pipe *pipe) { const struct item *item; if (array_count(&pipe->item_queue) == 0) return FALSE; item = array_front(&pipe->item_queue); if (item->type != ITEM_END_OF_LIST) return FALSE; array_pop_front(&pipe->item_queue); return TRUE; } static void dsync_ibc_pipe_deinit(struct dsync_ibc *ibc) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; pool_t pool; if (pipe->remote != NULL) { i_assert(pipe->remote->remote == pipe); pipe->remote->remote = NULL; } pool_unref(&pipe->pop_pool); array_foreach_modifiable(&pipe->item_queue, item) { pool_unref(&item->pool); } array_foreach_elem(&pipe->pools, pool) pool_unref(&pool); array_free(&pipe->pools); array_free(&pipe->item_queue); i_free(pipe); } static void dsync_ibc_pipe_send_handshake(struct dsync_ibc *ibc, const struct dsync_ibc_settings *set) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; item = dsync_ibc_pipe_push_item(pipe->remote, ITEM_HANDSHAKE); item->u.set = *set; item->u.set.sync_ns_prefixes = p_strdup(item->pool, set->sync_ns_prefixes); item->u.set.sync_box = p_strdup(item->pool, set->sync_box); item->u.set.virtual_all_box = p_strdup(item->pool, set->virtual_all_box); item->u.set.exclude_mailboxes = set->exclude_mailboxes == NULL ? NULL : p_strarray_dup(item->pool, set->exclude_mailboxes); memcpy(item->u.set.sync_box_guid, set->sync_box_guid, sizeof(item->u.set.sync_box_guid)); item->u.set.sync_since_timestamp = set->sync_since_timestamp; item->u.set.sync_until_timestamp = set->sync_until_timestamp; item->u.set.sync_max_size = set->sync_max_size; item->u.set.sync_flags = p_strdup(item->pool, set->sync_flags); } static enum dsync_ibc_recv_ret dsync_ibc_pipe_recv_handshake(struct dsync_ibc *ibc, const struct dsync_ibc_settings **set_r) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; item = dsync_ibc_pipe_pop_item(pipe, ITEM_HANDSHAKE); if (item == NULL) return DSYNC_IBC_RECV_RET_TRYAGAIN; *set_r = &item->u.set; return DSYNC_IBC_RECV_RET_OK; } static bool dsync_ibc_pipe_is_send_queue_full(struct dsync_ibc *ibc) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; return array_count(&pipe->remote->item_queue) > 0; } static bool dsync_ibc_pipe_has_pending_data(struct dsync_ibc *ibc) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; return array_count(&pipe->item_queue) > 0; } static void dsync_ibc_pipe_send_end_of_list(struct dsync_ibc *ibc, enum dsync_ibc_eol_type type ATTR_UNUSED) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; dsync_ibc_pipe_push_item(pipe->remote, ITEM_END_OF_LIST); } static void dsync_ibc_pipe_send_mailbox_state(struct dsync_ibc *ibc, const struct dsync_mailbox_state *state) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; item = dsync_ibc_pipe_push_item(pipe->remote, ITEM_MAILBOX_STATE); item->u.state = *state; } static enum dsync_ibc_recv_ret dsync_ibc_pipe_recv_mailbox_state(struct dsync_ibc *ibc, struct dsync_mailbox_state *state_r) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; if (dsync_ibc_pipe_try_pop_eol(pipe)) return DSYNC_IBC_RECV_RET_FINISHED; item = dsync_ibc_pipe_pop_item(pipe, ITEM_MAILBOX_STATE); if (item == NULL) return DSYNC_IBC_RECV_RET_TRYAGAIN; *state_r = item->u.state; return DSYNC_IBC_RECV_RET_OK; } static void dsync_ibc_pipe_send_mailbox_tree_node(struct dsync_ibc *ibc, const char *const *name, const struct dsync_mailbox_node *node) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; item = dsync_ibc_pipe_push_item(pipe->remote, ITEM_MAILBOX_TREE_NODE); /* a little bit kludgy way to send it */ item->u.node.name = (void *)p_strarray_dup(item->pool, name); dsync_mailbox_node_copy_data(&item->u.node, node); } static enum dsync_ibc_recv_ret dsync_ibc_pipe_recv_mailbox_tree_node(struct dsync_ibc *ibc, const char *const **name_r, const struct dsync_mailbox_node **node_r) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; if (dsync_ibc_pipe_try_pop_eol(pipe)) return DSYNC_IBC_RECV_RET_FINISHED; item = dsync_ibc_pipe_pop_item(pipe, ITEM_MAILBOX_TREE_NODE); if (item == NULL) return DSYNC_IBC_RECV_RET_TRYAGAIN; *name_r = (const void *)item->u.node.name; item->u.node.name = NULL; *node_r = &item->u.node; return DSYNC_IBC_RECV_RET_OK; } static void dsync_ibc_pipe_send_mailbox_deletes(struct dsync_ibc *ibc, const struct dsync_mailbox_delete *deletes, unsigned int count, char hierarchy_sep, char escape_char) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; item = dsync_ibc_pipe_push_item(pipe->remote, ITEM_MAILBOX_DELETE); /* we'll assume that the deletes are permanent. this works for now.. */ /* a little bit kludgy way to send it */ item->u.mailbox_delete.deletes = deletes; item->u.mailbox_delete.count = count; item->u.mailbox_delete.hierarchy_sep = hierarchy_sep; item->u.mailbox_delete.escape_char = escape_char; } static enum dsync_ibc_recv_ret dsync_ibc_pipe_recv_mailbox_deletes(struct dsync_ibc *ibc, const struct dsync_mailbox_delete **deletes_r, unsigned int *count_r, char *hierarchy_sep_r, char *escape_char_r) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; if (dsync_ibc_pipe_try_pop_eol(pipe)) return DSYNC_IBC_RECV_RET_FINISHED; item = dsync_ibc_pipe_pop_item(pipe, ITEM_MAILBOX_DELETE); if (item == NULL) return DSYNC_IBC_RECV_RET_TRYAGAIN; *deletes_r = item->u.mailbox_delete.deletes; *count_r = item->u.mailbox_delete.count; *hierarchy_sep_r = item->u.mailbox_delete.hierarchy_sep; *escape_char_r = item->u.mailbox_delete.escape_char; return DSYNC_IBC_RECV_RET_OK; } static void dsync_ibc_pipe_send_mailbox(struct dsync_ibc *ibc, const struct dsync_mailbox *dsync_box) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; const struct mailbox_cache_field *cf; struct mailbox_cache_field *ncf; item = dsync_ibc_pipe_push_item(pipe->remote, ITEM_MAILBOX); item->u.dsync_box = *dsync_box; p_array_init(&item->u.dsync_box.cache_fields, item->pool, array_count(&dsync_box->cache_fields)); array_foreach(&dsync_box->cache_fields, cf) { ncf = array_append_space(&item->u.dsync_box.cache_fields); ncf->name = p_strdup(item->pool, cf->name); ncf->decision = cf->decision; ncf->last_used = cf->last_used; } } static enum dsync_ibc_recv_ret dsync_ibc_pipe_recv_mailbox(struct dsync_ibc *ibc, const struct dsync_mailbox **dsync_box_r) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; if (dsync_ibc_pipe_try_pop_eol(pipe)) return DSYNC_IBC_RECV_RET_FINISHED; item = dsync_ibc_pipe_pop_item(pipe, ITEM_MAILBOX); if (item == NULL) return DSYNC_IBC_RECV_RET_TRYAGAIN; *dsync_box_r = &item->u.dsync_box; return DSYNC_IBC_RECV_RET_OK; } static void dsync_ibc_pipe_send_mailbox_attribute(struct dsync_ibc *ibc, const struct dsync_mailbox_attribute *attr) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; item = dsync_ibc_pipe_push_item(pipe->remote, ITEM_MAILBOX_ATTRIBUTE); dsync_mailbox_attribute_dup(item->pool, attr, &item->u.attr); } static enum dsync_ibc_recv_ret dsync_ibc_pipe_recv_mailbox_attribute(struct dsync_ibc *ibc, const struct dsync_mailbox_attribute **attr_r) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; if (dsync_ibc_pipe_try_pop_eol(pipe)) return DSYNC_IBC_RECV_RET_FINISHED; item = dsync_ibc_pipe_pop_item(pipe, ITEM_MAILBOX_ATTRIBUTE); if (item == NULL) return DSYNC_IBC_RECV_RET_TRYAGAIN; *attr_r = &item->u.attr; return DSYNC_IBC_RECV_RET_OK; } static void dsync_ibc_pipe_send_change(struct dsync_ibc *ibc, const struct dsync_mail_change *change) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; item = dsync_ibc_pipe_push_item(pipe->remote, ITEM_MAIL_CHANGE); dsync_mail_change_dup(item->pool, change, &item->u.change); } static enum dsync_ibc_recv_ret dsync_ibc_pipe_recv_change(struct dsync_ibc *ibc, const struct dsync_mail_change **change_r) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; if (dsync_ibc_pipe_try_pop_eol(pipe)) return DSYNC_IBC_RECV_RET_FINISHED; item = dsync_ibc_pipe_pop_item(pipe, ITEM_MAIL_CHANGE); if (item == NULL) return DSYNC_IBC_RECV_RET_TRYAGAIN; *change_r = &item->u.change; return DSYNC_IBC_RECV_RET_OK; } static void dsync_ibc_pipe_send_mail_request(struct dsync_ibc *ibc, const struct dsync_mail_request *request) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; item = dsync_ibc_pipe_push_item(pipe->remote, ITEM_MAIL_REQUEST); item->u.request.guid = p_strdup(item->pool, request->guid); item->u.request.uid = request->uid; } static enum dsync_ibc_recv_ret dsync_ibc_pipe_recv_mail_request(struct dsync_ibc *ibc, const struct dsync_mail_request **request_r) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; if (dsync_ibc_pipe_try_pop_eol(pipe)) return DSYNC_IBC_RECV_RET_FINISHED; item = dsync_ibc_pipe_pop_item(pipe, ITEM_MAIL_REQUEST); if (item == NULL) return DSYNC_IBC_RECV_RET_TRYAGAIN; *request_r = &item->u.request; return DSYNC_IBC_RECV_RET_OK; } static void dsync_ibc_pipe_send_mail(struct dsync_ibc *ibc, const struct dsync_mail *mail) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; item = dsync_ibc_pipe_push_item(pipe->remote, ITEM_MAIL); item->u.mail.guid = p_strdup(item->pool, mail->guid); item->u.mail.uid = mail->uid; item->u.mail.pop3_uidl = p_strdup(item->pool, mail->pop3_uidl); item->u.mail.pop3_order = mail->pop3_order; item->u.mail.received_date = mail->received_date; if (mail->input != NULL) { item->u.mail.input = mail->input; i_stream_ref(mail->input); } item->u.mail.input_mail = mail->input_mail; item->u.mail.input_mail_uid = mail->input_mail_uid; } static enum dsync_ibc_recv_ret dsync_ibc_pipe_recv_mail(struct dsync_ibc *ibc, struct dsync_mail **mail_r) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; if (dsync_ibc_pipe_try_pop_eol(pipe)) return DSYNC_IBC_RECV_RET_FINISHED; item = dsync_ibc_pipe_pop_item(pipe, ITEM_MAIL); if (item == NULL) return DSYNC_IBC_RECV_RET_TRYAGAIN; *mail_r = &item->u.mail; return DSYNC_IBC_RECV_RET_OK; } static void dsync_ibc_pipe_send_finish(struct dsync_ibc *ibc, const char *error, enum mail_error mail_error, bool require_full_resync) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; item = dsync_ibc_pipe_push_item(pipe->remote, ITEM_FINISH); item->u.finish.error = p_strdup(item->pool, error); item->u.finish.mail_error = mail_error; item->u.finish.require_full_resync = require_full_resync; } static enum dsync_ibc_recv_ret dsync_ibc_pipe_recv_finish(struct dsync_ibc *ibc, const char **error_r, enum mail_error *mail_error_r, bool *require_full_resync_r) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; struct item *item; item = dsync_ibc_pipe_pop_item(pipe, ITEM_FINISH); if (item == NULL) return DSYNC_IBC_RECV_RET_TRYAGAIN; *error_r = item->u.finish.error; *mail_error_r = item->u.finish.mail_error; *require_full_resync_r = item->u.finish.require_full_resync; return DSYNC_IBC_RECV_RET_OK; } static void pipe_close_mail_streams(struct dsync_ibc_pipe *pipe) { struct item *item; if (array_count(&pipe->item_queue) > 0) { item = array_front_modifiable(&pipe->item_queue); if (item->type == ITEM_MAIL && item->u.mail.input != NULL) i_stream_unref(&item->u.mail.input); } } static void dsync_ibc_pipe_close_mail_streams(struct dsync_ibc *ibc) { struct dsync_ibc_pipe *pipe = (struct dsync_ibc_pipe *)ibc; pipe_close_mail_streams(pipe); pipe_close_mail_streams(pipe->remote); } static const struct dsync_ibc_vfuncs dsync_ibc_pipe_vfuncs = { dsync_ibc_pipe_deinit, dsync_ibc_pipe_send_handshake, dsync_ibc_pipe_recv_handshake, dsync_ibc_pipe_send_end_of_list, dsync_ibc_pipe_send_mailbox_state, dsync_ibc_pipe_recv_mailbox_state, dsync_ibc_pipe_send_mailbox_tree_node, dsync_ibc_pipe_recv_mailbox_tree_node, dsync_ibc_pipe_send_mailbox_deletes, dsync_ibc_pipe_recv_mailbox_deletes, dsync_ibc_pipe_send_mailbox, dsync_ibc_pipe_recv_mailbox, dsync_ibc_pipe_send_mailbox_attribute, dsync_ibc_pipe_recv_mailbox_attribute, dsync_ibc_pipe_send_change, dsync_ibc_pipe_recv_change, dsync_ibc_pipe_send_mail_request, dsync_ibc_pipe_recv_mail_request, dsync_ibc_pipe_send_mail, dsync_ibc_pipe_recv_mail, dsync_ibc_pipe_send_finish, dsync_ibc_pipe_recv_finish, dsync_ibc_pipe_close_mail_streams, dsync_ibc_pipe_is_send_queue_full, dsync_ibc_pipe_has_pending_data }; static struct dsync_ibc_pipe * dsync_ibc_pipe_alloc(void) { struct dsync_ibc_pipe *pipe; pipe = i_new(struct dsync_ibc_pipe, 1); pipe->ibc.v = dsync_ibc_pipe_vfuncs; i_array_init(&pipe->pools, 4); i_array_init(&pipe->item_queue, 4); return pipe; } void dsync_ibc_init_pipe(struct dsync_ibc **ibc1_r, struct dsync_ibc **ibc2_r) { struct dsync_ibc_pipe *pipe1, *pipe2; pipe1 = dsync_ibc_pipe_alloc(); pipe2 = dsync_ibc_pipe_alloc(); pipe1->remote = pipe2; pipe2->remote = pipe1; *ibc1_r = &pipe1->ibc; *ibc2_r = &pipe2->ibc; } dovecot-2.3.21.1/src/doveadm/dsync/dsync-mailbox-state.c0000644000000000000000000000747414656633576017712 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "base64.h" #include "crc32.h" #include "hash.h" #include "dsync-mailbox-state.h" #define DSYNC_STATE_MAJOR_VERSION 1 #define DSYNC_STATE_MINOR_VERSION 0 #define V0_MAILBOX_SIZE (GUID_128_SIZE + 4 + 4 + 8 + 8) #define MAILBOX_SIZE (GUID_128_SIZE + 4 + 4 + 8 + 8 + 4) static void put_uint32(buffer_t *output, uint32_t num) { uint8_t tmp[sizeof(uint32_t)]; cpu32_to_le_unaligned(num, tmp); buffer_append(output, tmp, sizeof(tmp)); } void dsync_mailbox_states_export(const HASH_TABLE_TYPE(dsync_mailbox_state) states, string_t *output) { struct hash_iterate_context *iter; struct dsync_mailbox_state *state; uint8_t *guid; buffer_t *buf = t_buffer_create(128); uint32_t crc = 0; buffer_append_c(buf, DSYNC_STATE_MAJOR_VERSION); buffer_append_c(buf, DSYNC_STATE_MINOR_VERSION); buffer_append_c(buf, '\0'); buffer_append_c(buf, '\0'); iter = hash_table_iterate_init(states); while (hash_table_iterate(iter, states, &guid, &state)) { buffer_append(buf, state->mailbox_guid, sizeof(state->mailbox_guid)); put_uint32(buf, state->last_uidvalidity); put_uint32(buf, state->last_common_uid); put_uint32(buf, state->last_common_modseq & 0xffffffffU); put_uint32(buf, state->last_common_modseq >> 32); put_uint32(buf, state->last_common_pvt_modseq & 0xffffffffU); put_uint32(buf, state->last_common_pvt_modseq >> 32); put_uint32(buf, state->last_messages_count); /* v1 */ if (buf->used % 3 == 0) { crc = crc32_data_more(crc, buf->data, buf->used); base64_encode(buf->data, buf->used, output); buffer_set_used_size(buf, 0); } } hash_table_iterate_deinit(&iter); crc = crc32_data_more(crc, buf->data, buf->used); put_uint32(buf, crc); base64_encode(buf->data, buf->used, output); } static int dsync_mailbox_states_retry_import_v0(const buffer_t *buf) { const unsigned char *data = buf->data; /* v0 had no version header and no last_messages_count */ if ((buf->used-4) % V0_MAILBOX_SIZE != 0 || le32_to_cpu_unaligned(data + buf->used-4) != crc32_data(data, buf->used-4)) return -1; /* looks like valid v0 format, silently treat it as empty state */ return 0; } int dsync_mailbox_states_import(HASH_TABLE_TYPE(dsync_mailbox_state) states, pool_t pool, const char *input, const char **error_r) { struct dsync_mailbox_state *state; buffer_t *buf; uint8_t *guid_p; const unsigned char *data; unsigned int i, count; buf = t_buffer_create(strlen(input)); if (base64_decode(input, strlen(input), NULL, buf) < 0) { *error_r = "Invalid base64 data"; return -1; } /* v1: 4 byte header, mailboxes[], CRC32 */ data = buf->data; if (buf->used == 4 && le32_to_cpu_unaligned(data) == 0) { /* v0: Empty state */ return 0; } if (buf->used < 8) { *error_r = "Input too small"; return -1; } if ((buf->used-8) % MAILBOX_SIZE != 0) { *error_r = "Invalid input size"; return dsync_mailbox_states_retry_import_v0(buf); } if (le32_to_cpu_unaligned(data + buf->used-4) != crc32_data(data, buf->used-4)) { *error_r = "CRC32 mismatch"; return dsync_mailbox_states_retry_import_v0(buf); } data += 4; count = (buf->used-8) / MAILBOX_SIZE; for (i = 0; i < count; i++, data += MAILBOX_SIZE) { state = p_new(pool, struct dsync_mailbox_state, 1); memcpy(state->mailbox_guid, data, GUID_128_SIZE); state->last_uidvalidity = le32_to_cpu_unaligned(data + GUID_128_SIZE); state->last_common_uid = le32_to_cpu_unaligned(data + GUID_128_SIZE + 4); state->last_common_modseq = le64_to_cpu_unaligned(data + GUID_128_SIZE + 8); state->last_common_pvt_modseq = le64_to_cpu_unaligned(data + GUID_128_SIZE + 16); state->last_messages_count = le32_to_cpu_unaligned(data + GUID_128_SIZE + 24); guid_p = state->mailbox_guid; hash_table_insert(states, guid_p, state); } return 0; } dovecot-2.3.21.1/src/doveadm/dsync/dsync-mailbox.h0000644000000000000000000000232714656633576016571 00000000000000#ifndef DSYNC_MAILBOX_H #define DSYNC_MAILBOX_H #include "mail-storage.h" struct dsync_brain; /* Mailbox that is going to be synced. Its name was already sent in the mailbox tree. */ struct dsync_mailbox { guid_128_t mailbox_guid; bool mailbox_lost; bool mailbox_ignore; bool have_guids, have_save_guids, have_only_guid128; uint32_t uid_validity, uid_next, messages_count, first_recent_uid; uint64_t highest_modseq, highest_pvt_modseq; ARRAY_TYPE(mailbox_cache_field) cache_fields; }; struct dsync_mailbox_attribute { enum mail_attribute_type type; const char *key; /* if both values are NULL = not looked up yet / deleted */ const char *value; struct istream *value_stream; time_t last_change; /* 0 = unknown */ uint64_t modseq; /* 0 = unknown */ bool deleted; /* attribute is known to have been deleted */ bool exported; /* internally used by exporting */ }; #define DSYNC_ATTR_HAS_VALUE(attr) \ ((attr)->value != NULL || (attr)->value_stream != NULL) void dsync_mailbox_attribute_dup(pool_t pool, const struct dsync_mailbox_attribute *src, struct dsync_mailbox_attribute *dest_r); int dsync_mailbox_lock(struct dsync_brain *brain, struct mailbox *box, struct file_lock **lock_r); #endif dovecot-2.3.21.1/src/doveadm/dsync/dsync-mailbox-import.c0000644000000000000000000027305114656633576020100 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "hash.h" #include "str.h" #include "hex-binary.h" #include "istream.h" #include "seq-range-array.h" #include "imap-util.h" #include "mail-storage-private.h" #include "mail-search-build.h" #include "dsync-transaction-log-scan.h" #include "dsync-mail.h" #include "dsync-mailbox.h" #include "dsync-mailbox-import.h" struct importer_mail { const char *guid; uint32_t uid; }; struct importer_new_mail { /* linked list of mails for this GUID */ struct importer_new_mail *next; /* if non-NULL, this mail exists in both local and remote. this link points to the other side. */ struct importer_new_mail *link; const char *guid; struct dsync_mail_change *change; /* the final UID for the message */ uint32_t final_uid; /* the original local UID, or 0 if exists only remotely */ uint32_t local_uid; /* the original remote UID, or 0 if exists only remotely */ uint32_t remote_uid; /* UID for the mail in the virtual \All mailbox */ uint32_t virtual_all_uid; bool uid_in_local:1; bool uid_is_usable:1; bool skip:1; bool expunged:1; bool copy_failed:1; bool saved:1; }; /* for quickly testing that two-way sync doesn't actually do any unexpected modifications. */ #define IMPORTER_DEBUG_CHANGE(importer) /*i_assert(!importer->master_brain)*/ HASH_TABLE_DEFINE_TYPE(guid_new_mail, const char *, struct importer_new_mail *); HASH_TABLE_DEFINE_TYPE(uid_new_mail, void *, struct importer_new_mail *); struct dsync_mailbox_importer { pool_t pool; struct mailbox *box; uint32_t last_common_uid; uint64_t last_common_modseq, last_common_pvt_modseq; uint32_t remote_uid_next; uint32_t remote_first_recent_uid; uint64_t remote_highest_modseq, remote_highest_pvt_modseq; time_t sync_since_timestamp; time_t sync_until_timestamp; uoff_t sync_max_size; enum mailbox_transaction_flags transaction_flags; unsigned int hdr_hash_version; unsigned int commit_msgs_interval; const char *const *hashed_headers; enum mail_flags sync_flag; const char *sync_keyword; bool sync_flag_dontwant; struct mailbox_transaction_context *trans, *ext_trans; struct mail_search_context *search_ctx; struct mail *mail, *ext_mail; struct mailbox *virtual_all_box; struct mailbox_transaction_context *virtual_trans; struct mail *virtual_mail; struct mail *cur_mail; const char *cur_guid; const char *cur_hdr_hash; /* UID => struct dsync_mail_change */ HASH_TABLE_TYPE(dsync_uid_mail_change) local_changes; HASH_TABLE_TYPE(dsync_attr_change) local_attr_changes; ARRAY_TYPE(seq_range) maybe_expunge_uids; ARRAY(struct dsync_mail_change *) maybe_saves; /* GUID => struct importer_new_mail */ HASH_TABLE_TYPE(guid_new_mail) import_guids; /* UID => struct importer_new_mail */ HASH_TABLE_TYPE(uid_new_mail) import_uids; ARRAY(struct importer_new_mail *) newmails; ARRAY_TYPE(uint32_t) wanted_uids; ARRAY_TYPE(uint32_t) saved_uids; uint32_t highest_wanted_uid; ARRAY(struct dsync_mail_request) mail_requests; unsigned int mail_request_idx; uint32_t prev_uid, next_local_seq, local_uid_next; uint64_t local_initial_highestmodseq, local_initial_highestpvtmodseq; unsigned int import_pos, import_count; unsigned int first_unsaved_idx, saves_since_commit; enum mail_error mail_error; bool failed:1; bool require_full_resync:1; bool debug:1; bool stateful_import:1; bool last_common_uid_found:1; bool cur_uid_has_change:1; bool cur_mail_skip:1; bool local_expunged_guids_set:1; bool new_uids_assigned:1; bool want_mail_requests:1; bool master_brain:1; bool revert_local_changes:1; bool mails_have_guids:1; bool mails_use_guid128:1; bool delete_mailbox:1; bool empty_hdr_workaround:1; bool no_header_hashes:1; }; static const char *dsync_mail_change_type_names[] = { "save", "expunge", "flag-change" }; static bool dsync_mailbox_save_newmails(struct dsync_mailbox_importer *importer, const struct dsync_mail *mail, struct importer_new_mail *all_newmails, bool remote_mail); static int dsync_mailbox_import_commit(struct dsync_mailbox_importer *importer, bool final); static void ATTR_FORMAT(2, 3) imp_debug(struct dsync_mailbox_importer *importer, const char *fmt, ...) { va_list args; if (importer->debug) T_BEGIN { va_start(args, fmt); i_debug("brain %c: Import %s: %s", importer->master_brain ? 'M' : 'S', mailbox_get_vname(importer->box), t_strdup_vprintf(fmt, args)); va_end(args); } T_END; } static void dsync_import_unexpected_state(struct dsync_mailbox_importer *importer, const char *error) { if (!importer->stateful_import) { i_error("Mailbox %s: %s", mailbox_get_vname(importer->box), error); } else { i_warning("Mailbox %s doesn't match previous state: %s " "(dsync must be run again without the state)", mailbox_get_vname(importer->box), error); } importer->require_full_resync = TRUE; } static void dsync_mailbox_import_search_init(struct dsync_mailbox_importer *importer) { struct mail_search_args *search_args; struct mail_search_arg *sarg; search_args = mail_search_build_init(); sarg = mail_search_build_add(search_args, SEARCH_UIDSET); p_array_init(&sarg->value.seqset, search_args->pool, 128); seq_range_array_add_range(&sarg->value.seqset, importer->last_common_uid+1, (uint32_t)-1); importer->search_ctx = mailbox_search_init(importer->trans, search_args, NULL, 0, NULL); mail_search_args_unref(&search_args); if (mailbox_search_next(importer->search_ctx, &importer->cur_mail)) importer->next_local_seq = importer->cur_mail->seq; /* this flag causes cur_guid to be looked up later */ importer->cur_mail_skip = TRUE; } static void dsync_mailbox_import_transaction_begin(struct dsync_mailbox_importer *importer) { const enum mailbox_transaction_flags ext_trans_flags = importer->transaction_flags | MAILBOX_TRANSACTION_FLAG_EXTERNAL | MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS; importer->trans = mailbox_transaction_begin(importer->box, importer->transaction_flags, "dsync import"); importer->ext_trans = mailbox_transaction_begin(importer->box, ext_trans_flags, "dsync ext import"); importer->mail = mail_alloc(importer->trans, 0, NULL); importer->ext_mail = mail_alloc(importer->ext_trans, 0, NULL); } struct dsync_mailbox_importer * dsync_mailbox_import_init(struct mailbox *box, struct mailbox *virtual_all_box, struct dsync_transaction_log_scan *log_scan, uint32_t last_common_uid, uint64_t last_common_modseq, uint64_t last_common_pvt_modseq, uint32_t remote_uid_next, uint32_t remote_first_recent_uid, uint64_t remote_highest_modseq, uint64_t remote_highest_pvt_modseq, time_t sync_since_timestamp, time_t sync_until_timestamp, uoff_t sync_max_size, const char *sync_flag, unsigned int commit_msgs_interval, enum dsync_mailbox_import_flags flags, unsigned int hdr_hash_version, const char *const *hashed_headers) { struct dsync_mailbox_importer *importer; struct mailbox_status status; pool_t pool; pool = pool_alloconly_create(MEMPOOL_GROWING"dsync mailbox importer", 10240); importer = p_new(pool, struct dsync_mailbox_importer, 1); importer->pool = pool; importer->box = box; importer->virtual_all_box = virtual_all_box; importer->last_common_uid = last_common_uid; importer->last_common_modseq = last_common_modseq; importer->last_common_pvt_modseq = last_common_pvt_modseq; importer->last_common_uid_found = last_common_uid != 0 || last_common_modseq != 0; importer->remote_uid_next = remote_uid_next; importer->remote_first_recent_uid = remote_first_recent_uid; importer->remote_highest_modseq = remote_highest_modseq; importer->remote_highest_pvt_modseq = remote_highest_pvt_modseq; importer->sync_since_timestamp = sync_since_timestamp; importer->sync_until_timestamp = sync_until_timestamp; importer->sync_max_size = sync_max_size; importer->stateful_import = importer->last_common_uid_found; importer->hashed_headers = hashed_headers; if (sync_flag != NULL) { if (sync_flag[0] == '-') { importer->sync_flag_dontwant = TRUE; sync_flag++; } if (sync_flag[0] == '\\') importer->sync_flag = imap_parse_system_flag(sync_flag); else importer->sync_keyword = p_strdup(pool, sync_flag); } importer->commit_msgs_interval = commit_msgs_interval; importer->transaction_flags = MAILBOX_TRANSACTION_FLAG_SYNC; if ((flags & DSYNC_MAILBOX_IMPORT_FLAG_NO_NOTIFY) != 0) importer->transaction_flags |= MAILBOX_TRANSACTION_FLAG_NO_NOTIFY; hash_table_create(&importer->import_guids, pool, 0, str_hash, strcmp); hash_table_create_direct(&importer->import_uids, pool, 0); i_array_init(&importer->maybe_expunge_uids, 16); i_array_init(&importer->maybe_saves, 128); i_array_init(&importer->newmails, 128); i_array_init(&importer->wanted_uids, 128); i_array_init(&importer->saved_uids, 128); dsync_mailbox_import_transaction_begin(importer); if ((flags & DSYNC_MAILBOX_IMPORT_FLAG_WANT_MAIL_REQUESTS) != 0) { i_array_init(&importer->mail_requests, 128); importer->want_mail_requests = TRUE; } importer->master_brain = (flags & DSYNC_MAILBOX_IMPORT_FLAG_MASTER_BRAIN) != 0; importer->revert_local_changes = (flags & DSYNC_MAILBOX_IMPORT_FLAG_REVERT_LOCAL_CHANGES) != 0; importer->debug = (flags & DSYNC_MAILBOX_IMPORT_FLAG_DEBUG) != 0; importer->mails_have_guids = (flags & DSYNC_MAILBOX_IMPORT_FLAG_MAILS_HAVE_GUIDS) != 0; importer->mails_use_guid128 = (flags & DSYNC_MAILBOX_IMPORT_FLAG_MAILS_USE_GUID128) != 0; importer->hdr_hash_version = hdr_hash_version; importer->empty_hdr_workaround = (flags & DSYNC_MAILBOX_IMPORT_FLAG_EMPTY_HDR_WORKAROUND) != 0; importer->no_header_hashes = (flags & DSYNC_MAILBOX_IMPORT_FLAG_NO_HEADER_HASHES) != 0; mailbox_get_open_status(importer->box, STATUS_UIDNEXT | STATUS_HIGHESTMODSEQ | STATUS_HIGHESTPVTMODSEQ, &status); if (status.nonpermanent_modseqs) status.highest_modseq = 0; importer->local_uid_next = status.uidnext; importer->local_initial_highestmodseq = status.highest_modseq; importer->local_initial_highestpvtmodseq = status.highest_pvt_modseq; dsync_mailbox_import_search_init(importer); if (!importer->stateful_import) ; else if (importer->local_uid_next <= last_common_uid) { dsync_import_unexpected_state(importer, t_strdup_printf( "local UIDNEXT %u <= last common UID %u", importer->local_uid_next, last_common_uid)); } else if (importer->local_initial_highestmodseq < last_common_modseq) { dsync_import_unexpected_state(importer, t_strdup_printf( "local HIGHESTMODSEQ %"PRIu64" < last common HIGHESTMODSEQ %"PRIu64, importer->local_initial_highestmodseq, last_common_modseq)); } else if (importer->local_initial_highestpvtmodseq < last_common_pvt_modseq) { dsync_import_unexpected_state(importer, t_strdup_printf( "local HIGHESTMODSEQ %"PRIu64" < last common HIGHESTMODSEQ %"PRIu64, importer->local_initial_highestpvtmodseq, last_common_pvt_modseq)); } importer->local_changes = dsync_transaction_log_scan_get_hash(log_scan); importer->local_attr_changes = dsync_transaction_log_scan_get_attr_hash(log_scan); return importer; } static int dsync_mailbox_import_lookup_attr(struct dsync_mailbox_importer *importer, enum mail_attribute_type type, const char *key, struct dsync_mailbox_attribute **attr_r) { struct dsync_mailbox_attribute lookup_attr, *attr; const struct dsync_mailbox_attribute *attr_change; struct mail_attribute_value value; *attr_r = NULL; if (mailbox_attribute_get_stream(importer->box, type, key, &value) < 0) { i_error("Mailbox %s: Failed to get attribute %s: %s", mailbox_get_vname(importer->box), key, mailbox_get_last_internal_error(importer->box, &importer->mail_error)); importer->failed = TRUE; return -1; } lookup_attr.type = type; lookup_attr.key = key; attr_change = hash_table_lookup(importer->local_attr_changes, &lookup_attr); if (attr_change == NULL && value.value == NULL && value.value_stream == NULL) { /* we have no knowledge of this attribute */ return 0; } attr = t_new(struct dsync_mailbox_attribute, 1); attr->type = type; attr->key = key; attr->value = value.value; attr->value_stream = value.value_stream; attr->last_change = value.last_change; if (attr_change != NULL) { attr->deleted = attr_change->deleted && !DSYNC_ATTR_HAS_VALUE(attr); attr->modseq = attr_change->modseq; } *attr_r = attr; return 0; } static int dsync_istreams_cmp(struct istream *input1, struct istream *input2, int *cmp_r) { const unsigned char *data1, *data2; size_t size1, size2, size; *cmp_r = -1; /* quiet gcc */ for (;;) { (void)i_stream_read_more(input1, &data1, &size1); (void)i_stream_read_more(input2, &data2, &size2); if (size1 == 0 || size2 == 0) break; size = I_MIN(size1, size2); *cmp_r = memcmp(data1, data2, size); if (*cmp_r != 0) return 0; i_stream_skip(input1, size); i_stream_skip(input2, size); } if (input1->stream_errno != 0) { i_error("read(%s) failed: %s", i_stream_get_name(input1), i_stream_get_error(input1)); return -1; } if (input2->stream_errno != 0) { i_error("read(%s) failed: %s", i_stream_get_name(input2), i_stream_get_error(input2)); return -1; } if (size1 == 0 && size2 == 0) *cmp_r = 0; else *cmp_r = size1 == 0 ? -1 : 1; return 0; } static int dsync_attributes_cmp_values(const struct dsync_mailbox_attribute *attr1, const struct dsync_mailbox_attribute *attr2, int *cmp_r) { struct istream *input1, *input2; int ret; i_assert(attr1->value_stream != NULL || attr1->value != NULL); i_assert(attr2->value_stream != NULL || attr2->value != NULL); if (attr1->value != NULL && attr2->value != NULL) { *cmp_r = strcmp(attr1->value, attr2->value); return 0; } /* at least one of them is a stream. make both of them streams. */ input1 = attr1->value_stream != NULL ? attr1->value_stream : i_stream_create_from_data(attr1->value, strlen(attr1->value)); input2 = attr2->value_stream != NULL ? attr2->value_stream : i_stream_create_from_data(attr2->value, strlen(attr2->value)); i_stream_seek(input1, 0); i_stream_seek(input2, 0); ret = dsync_istreams_cmp(input1, input2, cmp_r); if (attr1->value_stream == NULL) i_stream_unref(&input1); if (attr2->value_stream == NULL) i_stream_unref(&input2); return ret; } static int dsync_attributes_cmp(const struct dsync_mailbox_attribute *attr, const struct dsync_mailbox_attribute *local_attr, int *cmp_r) { if (DSYNC_ATTR_HAS_VALUE(attr) && !DSYNC_ATTR_HAS_VALUE(local_attr)) { /* remote has a value and local doesn't -> use it */ *cmp_r = 1; return 0; } else if (!DSYNC_ATTR_HAS_VALUE(attr) && DSYNC_ATTR_HAS_VALUE(local_attr)) { /* remote doesn't have a value, bt local does -> skip */ *cmp_r = -1; return 0; } return dsync_attributes_cmp_values(attr, local_attr, cmp_r); } static int dsync_mailbox_import_attribute_real(struct dsync_mailbox_importer *importer, const struct dsync_mailbox_attribute *attr, const struct dsync_mailbox_attribute *local_attr, const char **result_r) { struct mail_attribute_value value; int cmp; bool ignore = FALSE; i_assert(DSYNC_ATTR_HAS_VALUE(attr) || attr->deleted); if (attr->deleted && (local_attr == NULL || !DSYNC_ATTR_HAS_VALUE(local_attr))) { /* attribute doesn't exist on either side -> ignore */ *result_r = "Nonexistent in both sides"; return 0; } if (local_attr == NULL) { /* we haven't seen this locally -> use whatever remote has */ *result_r = "Nonexistent locally"; } else if (local_attr->modseq <= importer->last_common_modseq && attr->modseq > importer->last_common_modseq && importer->last_common_modseq > 0) { /* we're doing incremental syncing, and we can see that the attribute was changed remotely, but not locally -> use it */ *result_r = "Changed remotely"; } else if (local_attr->modseq > importer->last_common_modseq && attr->modseq <= importer->last_common_modseq && importer->last_common_modseq > 0) { /* we're doing incremental syncing, and we can see that the attribute was changed locally, but not remotely -> ignore */ *result_r = "Changed locally"; ignore = TRUE; } else if (attr->last_change > local_attr->last_change) { /* remote has a newer timestamp -> use it */ *result_r = "Remote has newer timestamp"; } else if (attr->last_change < local_attr->last_change) { /* remote has an older timestamp -> ignore */ *result_r = "Local has newer timestamp"; ignore = TRUE; } else { /* the timestamps are the same. now we're down to guessing the right answer, unless the values are actually equal, so check that first. next try to use modseqs, but if even they are the same, fallback to just picking one based on the value. */ if (dsync_attributes_cmp(attr, local_attr, &cmp) < 0) { importer->mail_error = MAIL_ERROR_TEMP; importer->failed = TRUE; return -1; } if (cmp == 0) { /* identical scripts */ *result_r = "Unchanged value"; return 0; } if (attr->modseq > local_attr->modseq) { /* remote has a higher modseq -> use it */ *result_r = "Remote has newer modseq"; } else if (attr->modseq < local_attr->modseq) { /* remote has an older modseq -> ignore */ *result_r = "Local has newer modseq"; ignore = TRUE; } else if (cmp < 0) { ignore = TRUE; *result_r = "Value changed, but unknown which is newer - picking local"; } else { *result_r = "Value changed, but unknown which is newer - picking remote"; } } if (ignore) return 0; i_zero(&value); value.value = attr->value; value.value_stream = attr->value_stream; value.last_change = attr->last_change; if (mailbox_attribute_set(importer->trans, attr->type, attr->key, &value) < 0) { i_error("Mailbox %s: Failed to set attribute %s: %s", mailbox_get_vname(importer->box), attr->key, mailbox_get_last_internal_error(importer->box, NULL)); /* the attributes aren't vital, don't fail everything just because of them. */ } return 0; } int dsync_mailbox_import_attribute(struct dsync_mailbox_importer *importer, const struct dsync_mailbox_attribute *attr) { struct dsync_mailbox_attribute *local_attr; const char *result = ""; int ret; if (dsync_mailbox_import_lookup_attr(importer, attr->type, attr->key, &local_attr) < 0) ret = -1; else { ret = dsync_mailbox_import_attribute_real(importer, attr, local_attr, &result); if (local_attr != NULL && local_attr->value_stream != NULL) i_stream_unref(&local_attr->value_stream); } imp_debug(importer, "Import attribute %s: %s", attr->key, ret < 0 ? "failed" : result); return ret; } static void dsync_mail_error(struct dsync_mailbox_importer *importer, struct mail *mail, const char *field) { const char *errstr; enum mail_error error; errstr = mailbox_get_last_internal_error(importer->box, &error); if (error == MAIL_ERROR_EXPUNGED) return; i_error("Mailbox %s: Can't lookup %s for UID=%u: %s", mailbox_get_vname(mail->box), field, mail->uid, errstr); importer->mail_error = error; importer->failed = TRUE; } static bool dsync_mail_change_guid_equals(struct dsync_mailbox_importer *importer, const struct dsync_mail_change *change, const char *guid, const char **cmp_guid_r) { guid_128_t guid_128, change_guid_128; if (change->type == DSYNC_MAIL_CHANGE_TYPE_EXPUNGE) { if (guid_128_from_string(change->guid, change_guid_128) < 0) i_unreached(); } else if (importer->mails_use_guid128) { mail_generate_guid_128_hash(change->guid, change_guid_128); } else { if (cmp_guid_r != NULL) *cmp_guid_r = change->guid; return strcmp(change->guid, guid) == 0; } mail_generate_guid_128_hash(guid, guid_128); if (memcmp(change_guid_128, guid_128, GUID_128_SIZE) != 0) { if (cmp_guid_r != NULL) { *cmp_guid_r = t_strdup_printf("%s(guid128, orig=%s)", binary_to_hex(change_guid_128, sizeof(change_guid_128)), change->guid); } return FALSE; } return TRUE; } static int importer_try_next_mail(struct dsync_mailbox_importer *importer, uint32_t wanted_uid) { struct mail_private *pmail; const char *hdr_hash; if (importer->cur_mail == NULL) { /* end of search */ return -1; } while (importer->cur_mail->seq < importer->next_local_seq || importer->cur_mail->uid < wanted_uid) { if (!importer->cur_uid_has_change && !importer->last_common_uid_found) { /* this message exists locally, but remote didn't send expunge-change for it. if the message's uid <= last-common-uid, it should be deleted */ seq_range_array_add(&importer->maybe_expunge_uids, importer->cur_mail->uid); } importer->cur_mail_skip = FALSE; if (!mailbox_search_next(importer->search_ctx, &importer->cur_mail)) { importer->cur_mail = NULL; importer->cur_guid = NULL; importer->cur_hdr_hash = NULL; return -1; } importer->cur_uid_has_change = FALSE; } importer->cur_uid_has_change = importer->cur_mail->uid == wanted_uid; if (importer->mails_have_guids) { if (mail_get_special(importer->cur_mail, MAIL_FETCH_GUID, &importer->cur_guid) < 0) { dsync_mail_error(importer, importer->cur_mail, "GUID"); return 0; } } else { if (dsync_mail_get_hdr_hash(importer->cur_mail, importer->hdr_hash_version, importer->hashed_headers, &hdr_hash) < 0) { dsync_mail_error(importer, importer->cur_mail, "header hash"); return 0; } pmail = (struct mail_private *)importer->cur_mail; importer->cur_hdr_hash = p_strdup(pmail->pool, hdr_hash); importer->cur_guid = ""; } /* make sure next_local_seq gets updated in case we came here because of min_uid */ importer->next_local_seq = importer->cur_mail->seq; return 1; } static bool importer_next_mail(struct dsync_mailbox_importer *importer, uint32_t wanted_uid) { int ret; for (;;) { T_BEGIN { ret = importer_try_next_mail(importer, wanted_uid); } T_END; if (ret != 0 || importer->failed) break; importer->next_local_seq = importer->cur_mail->seq + 1; } return ret > 0; } static int importer_mail_cmp(const struct importer_mail *m1, const struct importer_mail *m2) { int ret; if (m1->guid == NULL) return 1; if (m2->guid == NULL) return -1; ret = strcmp(m1->guid, m2->guid); if (ret != 0) return ret; if (m1->uid < m2->uid) return -1; if (m1->uid > m2->uid) return 1; return 0; } static void newmail_link(struct dsync_mailbox_importer *importer, struct importer_new_mail *newmail, uint32_t remote_uid) { struct importer_new_mail *first_mail, **last, *mail, *link = NULL; if (*newmail->guid != '\0') { first_mail = hash_table_lookup(importer->import_guids, newmail->guid); if (first_mail == NULL) { /* first mail for this GUID */ hash_table_insert(importer->import_guids, newmail->guid, newmail); return; } } else { if (remote_uid == 0) { /* mail exists locally. we don't want to request it, and we'll assume it has no duplicate instances. */ return; } first_mail = hash_table_lookup(importer->import_uids, POINTER_CAST(remote_uid)); if (first_mail == NULL) { /* first mail for this UID */ hash_table_insert(importer->import_uids, POINTER_CAST(remote_uid), newmail); return; } } /* 1) add the newmail to the end of the linked list 2) find our link FIXME: this loop is slow if the same GUID has a ton of instances. Could it be improved in some way? */ last = &first_mail->next; for (mail = first_mail; mail != NULL; mail = mail->next) { if (mail->final_uid == newmail->final_uid) mail->uid_is_usable = TRUE; if (link == NULL && mail->link == NULL && mail->uid_in_local != newmail->uid_in_local) link = mail; last = &mail->next; } *last = newmail; if (link != NULL && newmail->link == NULL) { link->link = newmail; newmail->link = link; } } static void dsync_mailbox_revert_existing_uid(struct dsync_mailbox_importer *importer, uint32_t uid, const char *reason) { i_assert(importer->revert_local_changes); /* UID either already exists or UIDNEXT is too high. we can't set the wanted UID, so we'll need to delete the whole mailbox and resync */ i_warning("Deleting mailbox '%s': UID=%u already exists locally for a different mail: %s", mailbox_get_vname(importer->box), uid, reason); importer->delete_mailbox = TRUE; importer->mail_error = MAIL_ERROR_TEMP; importer->failed = TRUE; } static bool dsync_mailbox_try_save_cur(struct dsync_mailbox_importer *importer, struct dsync_mail_change *save_change) { struct importer_mail m1, m2; struct importer_new_mail *newmail; int diff; bool remote_saved; i_zero(&m1); if (importer->cur_mail != NULL) { m1.guid = importer->mails_have_guids ? importer->cur_guid : importer->cur_hdr_hash; m1.uid = importer->cur_mail->uid; } i_zero(&m2); if (save_change != NULL) { m2.guid = importer->mails_have_guids ? save_change->guid : save_change->hdr_hash; m2.uid = save_change->uid; i_assert(save_change->type != DSYNC_MAIL_CHANGE_TYPE_EXPUNGE); } if (importer->empty_hdr_workaround && !importer->mails_have_guids && importer->cur_mail != NULL && save_change != NULL && (dsync_mail_hdr_hash_is_empty(m1.guid) || dsync_mail_hdr_hash_is_empty(m2.guid))) { /* one of the headers is empty. assume it's broken and that the header matches what we have currently. */ diff = 0; } else if (importer->no_header_hashes && m1.uid == m2.uid && !importer->mails_have_guids && importer->cur_mail != NULL && save_change != NULL && (m1.guid[0] == '\0' || m2.guid[0] == '\0')) { /* Header hashes aren't known. Assume that the mails match if their UIDs match. */ diff = 0; } else { diff = importer_mail_cmp(&m1, &m2); } if (diff < 0) { /* add a record for local mail */ i_assert(importer->cur_mail != NULL); if (importer->revert_local_changes) { if (save_change == NULL && importer->cur_mail->uid >= importer->remote_uid_next) { dsync_mailbox_revert_existing_uid(importer, importer->cur_mail->uid, t_strdup_printf("higher than remote's UIDs (remote UIDNEXT=%u)", importer->remote_uid_next)); return TRUE; } mail_expunge(importer->cur_mail); importer->cur_mail_skip = TRUE; importer->next_local_seq++; return FALSE; } newmail = p_new(importer->pool, struct importer_new_mail, 1); newmail->guid = p_strdup(importer->pool, importer->cur_guid); newmail->final_uid = importer->cur_mail->uid; newmail->local_uid = importer->cur_mail->uid; newmail->uid_in_local = TRUE; newmail->uid_is_usable = newmail->final_uid >= importer->remote_uid_next; remote_saved = FALSE; } else if (diff > 0) { i_assert(save_change != NULL); newmail = p_new(importer->pool, struct importer_new_mail, 1); newmail->guid = save_change->guid; newmail->final_uid = save_change->uid; newmail->remote_uid = save_change->uid; newmail->uid_in_local = FALSE; newmail->uid_is_usable = newmail->final_uid >= importer->local_uid_next; if (!newmail->uid_is_usable && importer->revert_local_changes) { dsync_mailbox_revert_existing_uid(importer, newmail->final_uid, t_strdup_printf("UID >= local UIDNEXT=%u", importer->local_uid_next)); return TRUE; } remote_saved = TRUE; } else { /* identical */ i_assert(importer->cur_mail != NULL); i_assert(save_change != NULL); newmail = p_new(importer->pool, struct importer_new_mail, 1); newmail->guid = save_change->guid; newmail->final_uid = importer->cur_mail->uid; newmail->local_uid = importer->cur_mail->uid; newmail->remote_uid = save_change->uid; newmail->uid_in_local = TRUE; newmail->uid_is_usable = TRUE; newmail->link = newmail; remote_saved = TRUE; } if (newmail->uid_in_local) { importer->cur_mail_skip = TRUE; importer->next_local_seq++; } /* NOTE: assumes save_change is allocated from importer pool */ newmail->change = save_change; array_push_back(&importer->newmails, &newmail); if (newmail->uid_in_local) newmail_link(importer, newmail, 0); else { i_assert(save_change != NULL); newmail_link(importer, newmail, save_change->uid); } return remote_saved; } static bool ATTR_NULL(2) dsync_mailbox_try_save(struct dsync_mailbox_importer *importer, struct dsync_mail_change *save_change) { if (importer->cur_mail_skip) { if (!importer_next_mail(importer, 0) && save_change == NULL) return FALSE; } return dsync_mailbox_try_save_cur(importer, save_change); } static void dsync_mailbox_save(struct dsync_mailbox_importer *importer, struct dsync_mail_change *save_change) { while (!dsync_mailbox_try_save(importer, save_change)) ; } static bool dsync_import_set_mail(struct dsync_mailbox_importer *importer, const struct dsync_mail_change *change) { const char *guid, *cmp_guid; if (!mail_set_uid(importer->mail, change->uid)) return FALSE; if (change->guid == NULL) { /* GUID is unknown */ return TRUE; } if (*change->guid == '\0') { /* backend doesn't support GUIDs. if hdr_hash is set, we could verify it, but since this message really is supposed to match, it's probably too much trouble. */ return TRUE; } /* verify that GUID matches, just in case */ if (mail_get_special(importer->mail, MAIL_FETCH_GUID, &guid) < 0) { dsync_mail_error(importer, importer->mail, "GUID"); return FALSE; } if (!dsync_mail_change_guid_equals(importer, change, guid, &cmp_guid)) { dsync_import_unexpected_state(importer, t_strdup_printf( "Unexpected GUID mismatch for UID=%u: %s != %s", change->uid, guid, cmp_guid)); return FALSE; } return TRUE; } static bool dsync_check_cur_guid(struct dsync_mailbox_importer *importer, const struct dsync_mail_change *change) { const char *cmp_guid; if (change->guid == NULL || change->guid[0] == '\0' || importer->cur_guid[0] == '\0') return TRUE; if (!dsync_mail_change_guid_equals(importer, change, importer->cur_guid, &cmp_guid)) { dsync_import_unexpected_state(importer, t_strdup_printf( "Unexpected GUID mismatch (2) for UID=%u: %s != %s", change->uid, importer->cur_guid, cmp_guid)); return FALSE; } return TRUE; } static void merge_flags(uint32_t local_final, uint32_t local_add, uint32_t local_remove, uint32_t remote_final, uint32_t remote_add, uint32_t remote_remove, uint32_t pvt_mask, bool prefer_remote, bool prefer_pvt_remote, uint32_t *change_add_r, uint32_t *change_remove_r, bool *remote_changed, bool *remote_pvt_changed) { uint32_t combined_add, combined_remove, conflict_flags; uint32_t local_wanted, remote_wanted, conflict_pvt_flags; /* resolve conflicts */ conflict_flags = local_add & remote_remove; if (conflict_flags != 0) { conflict_pvt_flags = conflict_flags & pvt_mask; conflict_flags &= ~pvt_mask; if (prefer_remote) local_add &= ~conflict_flags; else remote_remove &= ~conflict_flags; if (prefer_pvt_remote) local_add &= ~conflict_pvt_flags; else remote_remove &= ~conflict_pvt_flags; } conflict_flags = local_remove & remote_add; if (conflict_flags != 0) { conflict_pvt_flags = conflict_flags & pvt_mask; conflict_flags &= ~pvt_mask; if (prefer_remote) local_remove &= ~conflict_flags; else remote_add &= ~conflict_flags; if (prefer_pvt_remote) local_remove &= ~conflict_pvt_flags; else remote_add &= ~conflict_pvt_flags; } combined_add = local_add|remote_add; combined_remove = local_remove|remote_remove; i_assert((combined_add & combined_remove) == 0); /* don't change flags that are currently identical in both sides */ conflict_flags = local_final ^ remote_final; combined_add &= conflict_flags; combined_remove &= conflict_flags; /* see if there are conflicting final flags */ local_wanted = (local_final|combined_add) & ~combined_remove; remote_wanted = (remote_final|combined_add) & ~combined_remove; conflict_flags = local_wanted ^ remote_wanted; if (conflict_flags != 0) { if (prefer_remote && prefer_pvt_remote) local_wanted = remote_wanted; else if (prefer_remote && !prefer_pvt_remote) { local_wanted = (local_wanted & pvt_mask) | (remote_wanted & ~pvt_mask); } else if (!prefer_remote && prefer_pvt_remote) { local_wanted = (local_wanted & ~pvt_mask) | (remote_wanted & pvt_mask); } } *change_add_r = local_wanted & ~local_final; *change_remove_r = local_final & ~local_wanted; if ((local_wanted & ~pvt_mask) != (remote_final & ~pvt_mask)) *remote_changed = TRUE; if ((local_wanted & pvt_mask) != (remote_final & pvt_mask)) *remote_pvt_changed = TRUE; } static bool keyword_find(ARRAY_TYPE(const_string) *keywords, const char *name, unsigned int *idx_r) { const char *const *names; unsigned int i, count; names = array_get(keywords, &count); for (i = 0; i < count; i++) { if (strcmp(names[i], name) == 0) { *idx_r = i; return TRUE; } } return FALSE; } static void keywords_append(ARRAY_TYPE(const_string) *dest, const ARRAY_TYPE(const_string) *keywords, uint32_t bits, unsigned int start_idx) { const char *name; unsigned int i; for (i = 0; i < 32; i++) { if ((bits & (1U << i)) == 0) continue; name = array_idx_elem(keywords, start_idx+i); array_push_back(dest, &name); } } static void merge_keywords(struct mail *mail, const ARRAY_TYPE(const_string) *local_changes, const ARRAY_TYPE(const_string) *remote_changes, bool prefer_remote, bool *remote_changed, bool *remote_pvt_changed) { /* local_changes and remote_changes are assumed to have no duplicates names */ uint32_t *local_add, *local_remove, *local_final; uint32_t *remote_add, *remote_remove, *remote_final; uint32_t *change_add, *change_remove; ARRAY_TYPE(const_string) all_keywords, add_keywords, remove_keywords; const char *const *changes, *name, *const *local_keywords; struct mail_keywords *kw; unsigned int i, count, name_idx, array_size; local_keywords = mail_get_keywords(mail); /* we'll assign a common index for each keyword name and place the changes to separate bit arrays. */ if (array_is_created(remote_changes)) changes = array_get(remote_changes, &count); else { changes = NULL; count = 0; } array_size = str_array_length(local_keywords) + count; if (array_is_created(local_changes)) array_size += array_count(local_changes); if (array_size == 0) { /* this message has no keywords */ return; } t_array_init(&all_keywords, array_size); t_array_init(&add_keywords, array_size); t_array_init(&remove_keywords, array_size); /* @UNSAFE: create large enough arrays to fit all keyword indexes. */ array_size = (array_size+31)/32; local_add = t_new(uint32_t, array_size); local_remove = t_new(uint32_t, array_size); local_final = t_new(uint32_t, array_size); remote_add = t_new(uint32_t, array_size); remote_remove = t_new(uint32_t, array_size); remote_final = t_new(uint32_t, array_size); change_add = t_new(uint32_t, array_size); change_remove = t_new(uint32_t, array_size); /* get remote changes */ for (i = 0; i < count; i++) { name = changes[i]+1; name_idx = array_count(&all_keywords); array_push_back(&all_keywords, &name); switch (changes[i][0]) { case KEYWORD_CHANGE_ADD: remote_add[name_idx/32] |= 1U << (name_idx%32); break; case KEYWORD_CHANGE_REMOVE: remote_remove[name_idx/32] |= 1U << (name_idx%32); break; case KEYWORD_CHANGE_FINAL: remote_final[name_idx/32] |= 1U << (name_idx%32); break; case KEYWORD_CHANGE_ADD_AND_FINAL: remote_add[name_idx/32] |= 1U << (name_idx%32); remote_final[name_idx/32] |= 1U << (name_idx%32); break; } } /* get local changes. use existing indexes for names when they exist. */ if (array_is_created(local_changes)) changes = array_get(local_changes, &count); else { changes = NULL; count = 0; } for (i = 0; i < count; i++) { name = changes[i]+1; if (!keyword_find(&all_keywords, name, &name_idx)) { name_idx = array_count(&all_keywords); array_push_back(&all_keywords, &name); } switch (changes[i][0]) { case KEYWORD_CHANGE_ADD: case KEYWORD_CHANGE_ADD_AND_FINAL: local_add[name_idx/32] |= 1U << (name_idx%32); break; case KEYWORD_CHANGE_REMOVE: local_remove[name_idx/32] |= 1U << (name_idx%32); break; case KEYWORD_CHANGE_FINAL: break; } } for (i = 0; local_keywords[i] != NULL; i++) { name = local_keywords[i]; if (!keyword_find(&all_keywords, name, &name_idx)) { name_idx = array_count(&all_keywords); array_push_back(&all_keywords, &name); } local_final[name_idx/32] |= 1U << (name_idx%32); } i_assert(array_count(&all_keywords) <= array_size*32); array_size = (array_count(&all_keywords)+31) / 32; /* merge keywords */ for (i = 0; i < array_size; i++) { merge_flags(local_final[i], local_add[i], local_remove[i], remote_final[i], remote_add[i], remote_remove[i], 0, prefer_remote, prefer_remote, &change_add[i], &change_remove[i], remote_changed, remote_pvt_changed); if (change_add[i] != 0) { keywords_append(&add_keywords, &all_keywords, change_add[i], i*32); } if (change_remove[i] != 0) { keywords_append(&remove_keywords, &all_keywords, change_remove[i], i*32); } } /* apply changes */ if (array_count(&add_keywords) > 0) { array_append_zero(&add_keywords); kw = mailbox_keywords_create_valid(mail->box, array_front(&add_keywords)); mail_update_keywords(mail, MODIFY_ADD, kw); mailbox_keywords_unref(&kw); } if (array_count(&remove_keywords) > 0) { array_append_zero(&remove_keywords); kw = mailbox_keywords_create_valid(mail->box, array_front(&remove_keywords)); mail_update_keywords(mail, MODIFY_REMOVE, kw); mailbox_keywords_unref(&kw); } } static void dsync_mailbox_import_replace_flags(struct mail *mail, const struct dsync_mail_change *change) { ARRAY_TYPE(const_string) keywords; struct mail_keywords *kw; const char *const *changes, *name; unsigned int i, count; if (array_is_created(&change->keyword_changes)) changes = array_get(&change->keyword_changes, &count); else { changes = NULL; count = 0; } t_array_init(&keywords, count+1); for (i = 0; i < count; i++) { switch (changes[i][0]) { case KEYWORD_CHANGE_ADD: case KEYWORD_CHANGE_FINAL: case KEYWORD_CHANGE_ADD_AND_FINAL: name = changes[i]+1; array_push_back(&keywords, &name); break; case KEYWORD_CHANGE_REMOVE: break; } } array_append_zero(&keywords); kw = mailbox_keywords_create_valid(mail->box, array_front(&keywords)); mail_update_keywords(mail, MODIFY_REPLACE, kw); mailbox_keywords_unref(&kw); mail_update_flags(mail, MODIFY_REPLACE, change->add_flags | change->final_flags); if (mail_get_modseq(mail) < change->modseq) mail_update_modseq(mail, change->modseq); if (mail_get_pvt_modseq(mail) < change->pvt_modseq) mail_update_pvt_modseq(mail, change->pvt_modseq); } static void dsync_mailbox_import_flag_change(struct dsync_mailbox_importer *importer, const struct dsync_mail_change *change) { const struct dsync_mail_change *local_change; enum mail_flags local_add, local_remove; uint32_t change_add, change_remove; uint64_t new_modseq; ARRAY_TYPE(const_string) local_keyword_changes = ARRAY_INIT; struct mail *mail; bool prefer_remote, prefer_pvt_remote; bool remote_changed = FALSE, remote_pvt_changed = FALSE; i_assert((change->add_flags & change->remove_flags) == 0); if (importer->cur_mail != NULL && importer->cur_mail->uid == change->uid) { if (!dsync_check_cur_guid(importer, change)) return; mail = importer->cur_mail; } else { if (!dsync_import_set_mail(importer, change)) return; mail = importer->mail; } if (importer->revert_local_changes) { /* dsync backup: just make the local look like remote. */ dsync_mailbox_import_replace_flags(mail, change); return; } local_change = hash_table_lookup(importer->local_changes, POINTER_CAST(change->uid)); if (local_change == NULL) { local_add = local_remove = 0; } else { local_add = local_change->add_flags; local_remove = local_change->remove_flags; local_keyword_changes = local_change->keyword_changes; } if (mail_get_modseq(mail) < change->modseq) prefer_remote = TRUE; else if (mail_get_modseq(mail) > change->modseq) prefer_remote = FALSE; else { /* identical modseq, we'll just have to pick one. Note that both brains need to pick the same one, otherwise they become unsynced. */ prefer_remote = !importer->master_brain; } if (mail_get_pvt_modseq(mail) < change->pvt_modseq) prefer_pvt_remote = TRUE; else if (mail_get_pvt_modseq(mail) > change->pvt_modseq) prefer_pvt_remote = FALSE; else prefer_pvt_remote = !importer->master_brain; /* merge flags */ merge_flags(mail_get_flags(mail), local_add, local_remove, change->final_flags, change->add_flags, change->remove_flags, mailbox_get_private_flags_mask(mail->box), prefer_remote, prefer_pvt_remote, &change_add, &change_remove, &remote_changed, &remote_pvt_changed); if (change_add != 0) mail_update_flags(mail, MODIFY_ADD, change_add); if (change_remove != 0) mail_update_flags(mail, MODIFY_REMOVE, change_remove); /* merge keywords */ merge_keywords(mail, &local_keyword_changes, &change->keyword_changes, prefer_remote, &remote_changed, &remote_pvt_changed); /* update modseqs. try to anticipate when we have to increase modseq to get it closer to what remote has (although we can't guess it exactly correctly) */ new_modseq = change->modseq; if (remote_changed && new_modseq <= importer->remote_highest_modseq) new_modseq = importer->remote_highest_modseq+1; if (mail_get_modseq(mail) < new_modseq) mail_update_modseq(mail, new_modseq); new_modseq = change->pvt_modseq; if (remote_pvt_changed && new_modseq <= importer->remote_highest_pvt_modseq) new_modseq = importer->remote_highest_pvt_modseq+1; if (mail_get_pvt_modseq(mail) < new_modseq) mail_update_pvt_modseq(mail, new_modseq); } static bool dsync_mail_change_have_keyword(const struct dsync_mail_change *change, const char *keyword) { const char *str; if (!array_is_created(&change->keyword_changes)) return FALSE; array_foreach_elem(&change->keyword_changes, str) { switch (str[0]) { case KEYWORD_CHANGE_FINAL: case KEYWORD_CHANGE_ADD_AND_FINAL: if (strcasecmp(str+1, keyword) == 0) return TRUE; break; default: break; } } return FALSE; } static bool dsync_mailbox_import_want_change(struct dsync_mailbox_importer *importer, const struct dsync_mail_change *change, const char **result_r) { if (importer->sync_since_timestamp > 0) { i_assert(change->received_timestamp > 0); if (change->received_timestamp < importer->sync_since_timestamp) { /* mail has too old timestamp - skip it */ *result_r = "Ignoring missing local mail with too old timestamp"; return FALSE; } } if (importer->sync_until_timestamp > 0) { i_assert(change->received_timestamp > 0); if (change->received_timestamp > importer->sync_until_timestamp) { /* mail has too new timestamp - skip it */ *result_r = "Ignoring missing local mail with too new timestamp"; return FALSE; } } if (importer->sync_max_size > 0) { i_assert(change->virtual_size != UOFF_T_MAX); if (change->virtual_size > importer->sync_max_size) { /* mail is too large - skip it */ *result_r = "Ignoring missing local mail with too large size"; return FALSE; } } if (importer->sync_flag != 0) { bool have_flag = (change->final_flags & importer->sync_flag) != 0; if (have_flag && importer->sync_flag_dontwant) { *result_r = "Ignoring missing local mail that doesn't have wanted flags"; return FALSE; } if (!have_flag && !importer->sync_flag_dontwant) { *result_r = "Ignoring missing local mail that has unwanted flags"; return FALSE; } } if (importer->sync_keyword != NULL) { bool have_kw = dsync_mail_change_have_keyword(change, importer->sync_keyword); if (have_kw && importer->sync_flag_dontwant) { *result_r = "Ignoring missing local mail that doesn't have wanted keywords"; return FALSE; } if (!have_kw && !importer->sync_flag_dontwant) { *result_r = "Ignoring missing local mail that has unwanted keywords"; return FALSE; } } return TRUE; } static void dsync_mailbox_import_save(struct dsync_mailbox_importer *importer, const struct dsync_mail_change *change) { struct dsync_mail_change *save; const char *result; i_assert(change->guid != NULL); if (change->uid == importer->last_common_uid) { /* we've already verified that the GUID matches. apply flag changes if there are any. */ i_assert(!importer->last_common_uid_found); dsync_mailbox_import_flag_change(importer, change); return; } if (!dsync_mailbox_import_want_change(importer, change, &result)) return; save = p_new(importer->pool, struct dsync_mail_change, 1); dsync_mail_change_dup(importer->pool, change, save); if (importer->last_common_uid_found) { /* this is a new mail. its UID may or may not conflict with an existing local mail, we'll figure it out later. */ i_assert(change->uid > importer->last_common_uid); dsync_mailbox_save(importer, save); } else { /* the local mail is expunged. we'll decide later if we want to save this mail locally or expunge it form remote. */ i_assert(change->uid > importer->last_common_uid); i_assert(importer->cur_mail == NULL || change->uid < importer->cur_mail->uid); array_push_back(&importer->maybe_saves, &save); } } static void dsync_mailbox_import_expunge(struct dsync_mailbox_importer *importer, const struct dsync_mail_change *change) { if (importer->last_common_uid_found) { /* expunge the message, unless its GUID unexpectedly doesn't match */ i_assert(change->uid <= importer->last_common_uid); if (dsync_import_set_mail(importer, change)) mail_expunge(importer->mail); } else if (importer->cur_mail == NULL || change->uid < importer->cur_mail->uid) { /* already expunged locally, we can ignore this. uid=last_common_uid if we managed to verify from transaction log that the GUIDs match */ i_assert(change->uid >= importer->last_common_uid); } else if (change->uid == importer->last_common_uid) { /* already verified that the GUID matches */ i_assert(importer->cur_mail->uid == change->uid); if (dsync_check_cur_guid(importer, change)) mail_expunge(importer->cur_mail); } else { /* we don't know yet if we should expunge this message or not. queue it until we do. */ i_assert(change->uid > importer->last_common_uid); seq_range_array_add(&importer->maybe_expunge_uids, change->uid); } } static void dsync_mailbox_rewind_search(struct dsync_mailbox_importer *importer) { /* If there are local mails after last_common_uid which we skipped while trying to match the next message, we need to now go back */ if (importer->cur_mail != NULL && importer->cur_mail->uid <= importer->last_common_uid+1) return; importer->cur_mail = NULL; importer->cur_guid = NULL; importer->cur_hdr_hash = NULL; importer->next_local_seq = 0; (void)mailbox_search_deinit(&importer->search_ctx); dsync_mailbox_import_search_init(importer); } static void dsync_mailbox_common_uid_found(struct dsync_mailbox_importer *importer) { struct dsync_mail_change *const *saves; struct seq_range_iter iter; unsigned int n, i, count; uint32_t uid; if (importer->debug) T_BEGIN { string_t *expunges = t_str_new(64); imap_write_seq_range(expunges, &importer->maybe_expunge_uids); imp_debug(importer, "Last common UID=%u. Delayed expunges=%s", importer->last_common_uid, str_c(expunges)); } T_END; importer->last_common_uid_found = TRUE; dsync_mailbox_rewind_search(importer); /* expunge the messages whose expunge-decision we delayed previously */ seq_range_array_iter_init(&iter, &importer->maybe_expunge_uids); n = 0; while (seq_range_array_iter_nth(&iter, n++, &uid)) { if (uid > importer->last_common_uid) { /* we expunge messages only up to last_common_uid, ignore the rest */ break; } if (mail_set_uid(importer->mail, uid)) mail_expunge(importer->mail); } /* handle pending saves */ saves = array_get(&importer->maybe_saves, &count); for (i = 0; i < count; i++) { if (saves[i]->uid > importer->last_common_uid) { imp_debug(importer, "Delayed save UID=%u: Save", saves[i]->uid); dsync_mailbox_save(importer, saves[i]); } else { imp_debug(importer, "Delayed save UID=%u: Ignore", saves[i]->uid); } } } static int dsync_mailbox_import_match_msg(struct dsync_mailbox_importer *importer, const struct dsync_mail_change *change, const char **result_r) { const char *hdr_hash, *cmp_guid; if (*change->guid != '\0' && *importer->cur_guid != '\0') { /* we have GUIDs, verify them */ if (dsync_mail_change_guid_equals(importer, change, importer->cur_guid, &cmp_guid)) { *result_r = "GUIDs match"; return 1; } else { *result_r = t_strdup_printf("GUIDs don't match (%s vs %s)", importer->cur_guid, cmp_guid); return 0; } } /* verify hdr_hash if it exists */ if (change->hdr_hash == NULL) { i_assert(*importer->cur_guid == '\0'); if (change->type == DSYNC_MAIL_CHANGE_TYPE_EXPUNGE) { /* the message was already expunged, so we don't know its header. return "unknown". */ *result_r = "Unknown match for expunge"; return -1; } i_error("Mailbox %s: GUIDs not supported, " "sync with header hashes instead", mailbox_get_vname(importer->box)); importer->mail_error = MAIL_ERROR_TEMP; importer->failed = TRUE; *result_r = "Error, invalid parameters"; return -1; } if (dsync_mail_get_hdr_hash(importer->cur_mail, importer->hdr_hash_version, importer->hashed_headers, &hdr_hash) < 0) { dsync_mail_error(importer, importer->cur_mail, "hdr-stream"); *result_r = "Error fetching header stream"; return -1; } if (importer->empty_hdr_workaround && (dsync_mail_hdr_hash_is_empty(change->hdr_hash) || dsync_mail_hdr_hash_is_empty(hdr_hash))) { *result_r = "Empty headers found with workaround enabled - assuming a match"; return 1; } else if (importer->no_header_hashes && (change->hdr_hash[0] == '\0' || hdr_hash[0] == '\0')) { *result_r = "Header hashing disabled - assuming a match"; return 1; } else if (strcmp(change->hdr_hash, hdr_hash) == 0) { *result_r = "Headers hashes match"; return 1; } else { *result_r = t_strdup_printf("Headers hashes don't match (%s vs %s)", change->hdr_hash, hdr_hash); return 0; } } static bool dsync_mailbox_find_common_expunged_uid(struct dsync_mailbox_importer *importer, const struct dsync_mail_change *change, const char **result_r) { const struct dsync_mail_change *local_change; if (*change->guid == '\0') { /* remote doesn't support GUIDs, can't verify expunge */ *result_r = "GUIDs not supported, can't verify expunge"; return FALSE; } /* local message is expunged. see if we can find its GUID from transaction log and check if the GUIDs match. The GUID in log is a 128bit GUID, so we may need to convert the remote's GUID string to 128bit GUID first. */ local_change = hash_table_lookup(importer->local_changes, POINTER_CAST(change->uid)); if (local_change == NULL || local_change->guid == NULL) { *result_r = "Expunged local mail's GUID not found"; return FALSE; } i_assert(local_change->type == DSYNC_MAIL_CHANGE_TYPE_EXPUNGE); if (dsync_mail_change_guid_equals(importer, local_change, change->guid, NULL)) { importer->last_common_uid = change->uid; *result_r = "Expunged local mail's GUID matches remote"; } else if (change->type != DSYNC_MAIL_CHANGE_TYPE_EXPUNGE) { dsync_mailbox_common_uid_found(importer); *result_r = "Expunged local mail's GUID doesn't match remote GUID"; } else { /* GUID mismatch for two expunged mails. dsync can't update GUIDs for already expunged messages, so we can't immediately determine that the rest of the messages are a mismatch. so for now we'll just skip over this pair. */ *result_r = "Expunged mails' GUIDs don't match - delaying decision"; /* NOTE: the return value here doesn't matter, because the only caller that checks for it never reaches this code path */ } return TRUE; } static void dsync_mailbox_revert_missing(struct dsync_mailbox_importer *importer, const struct dsync_mail_change *change) { i_assert(importer->revert_local_changes); /* mail exists on remote, but not locally. we'll need to insert this mail back, which means deleting the whole mailbox and resyncing. */ i_warning("Deleting mailbox '%s': UID=%u GUID=%s is missing locally", mailbox_get_vname(importer->box), change->uid, change->guid); importer->delete_mailbox = TRUE; importer->mail_error = MAIL_ERROR_TEMP; importer->failed = TRUE; } static void dsync_mailbox_find_common_uid(struct dsync_mailbox_importer *importer, const struct dsync_mail_change *change, const char **result_r) { int ret; i_assert(change->type == DSYNC_MAIL_CHANGE_TYPE_EXPUNGE || ((change->received_timestamp > 0 || (importer->sync_since_timestamp == 0 && importer->sync_until_timestamp == 0)) && (change->virtual_size != UOFF_T_MAX || importer->sync_max_size == 0))); /* try to find the matching local mail */ if (!importer_next_mail(importer, change->uid)) { /* no more local mails. we can still try to match expunged mails though. */ if (change->type == DSYNC_MAIL_CHANGE_TYPE_EXPUNGE) { /* mail doesn't exist remotely either, don't bother looking it up locally. */ *result_r = "Expunged mail not found locally"; return; } i_assert(change->guid != NULL); if (!dsync_mailbox_import_want_change(importer, change, result_r)) ; else if (importer->local_uid_next <= change->uid) { dsync_mailbox_common_uid_found(importer); *result_r = "Mail's UID is above local UIDNEXT"; } else if (importer->revert_local_changes) { dsync_mailbox_revert_missing(importer, change); *result_r = "Reverting local change by deleting mailbox"; } else if (!dsync_mailbox_find_common_expunged_uid(importer, change, result_r)) { /* it's unknown if this mail existed locally and was expunged. since we don't want to lose any mails, assume that we need to preserve the mail. use the last message with a matching GUID as the last common UID. */ dsync_mailbox_common_uid_found(importer); } *result_r = t_strdup_printf("%s - No more local mails found", *result_r); return; } if (change->guid == NULL) { /* we can't know if this UID matches */ i_assert(change->type == DSYNC_MAIL_CHANGE_TYPE_EXPUNGE); *result_r = "Expunged mail has no GUID, can't verify it"; return; } if (importer->cur_mail->uid == change->uid) { /* we have a matching local UID. check GUID to see if it's really the same mail or not */ if ((ret = dsync_mailbox_import_match_msg(importer, change, result_r)) < 0) { /* unknown */ return; } if (ret > 0) { importer->last_common_uid = change->uid; imp_debug(importer, "Last UID matched - " "last_common_uid=%u", importer->last_common_uid); } else if (!importer->revert_local_changes) { /* mismatch - found the first non-common UID */ imp_debug(importer, "Last UID mismatch - " "last_common_uid=%u", importer->last_common_uid); dsync_mailbox_common_uid_found(importer); } else { /* mismatch and we want to revert local changes - need to delete the mailbox. */ imp_debug(importer, "Last UID %u mismatch - " "revert local changes", change->uid); dsync_mailbox_revert_existing_uid(importer, change->uid, *result_r); } return; } /* mail exists remotely, but doesn't exist locally. */ if (!dsync_mailbox_import_want_change(importer, change, result_r)) return; if (importer->revert_local_changes && change->type != DSYNC_MAIL_CHANGE_TYPE_EXPUNGE) { dsync_mailbox_revert_missing(importer, change); *result_r = "Reverting local change by deleting mailbox"; } else { (void)dsync_mailbox_find_common_expunged_uid(importer, change, result_r); } *result_r = t_strdup_printf("%s (next local mail UID=%u)", *result_r, importer->cur_mail == NULL ? 0 : importer->cur_mail->uid); } int dsync_mailbox_import_change(struct dsync_mailbox_importer *importer, const struct dsync_mail_change *change) { const char *result; i_assert(!importer->new_uids_assigned); i_assert(importer->prev_uid < change->uid); importer->prev_uid = change->uid; if (importer->failed) return -1; if (importer->require_full_resync) return 0; if (!importer->last_common_uid_found) { result = NULL; dsync_mailbox_find_common_uid(importer, change, &result); i_assert(result != NULL); } else { result = "New mail"; } imp_debug(importer, "Import change type=%s GUID=%s UID=%u hdr_hash=%s result=%s", dsync_mail_change_type_names[change->type], change->guid != NULL ? change->guid : "", change->uid, change->hdr_hash != NULL ? change->hdr_hash : "", result); if (importer->failed) return -1; if (importer->require_full_resync) return 0; if (importer->last_common_uid_found) { /* a) uid <= last_common_uid for flag changes and expunges. this happens only when last_common_uid was originally given as parameter to importer. when we're finding the last_common_uid ourself, uid>last_common_uid always in here, because last_common_uid_found=TRUE only after we find the first mismatch. b) uid > last_common_uid for i) new messages, ii) expunges that were sent "just in case" */ if (change->uid <= importer->last_common_uid) { i_assert(change->type != DSYNC_MAIL_CHANGE_TYPE_SAVE); } else if (change->type == DSYNC_MAIL_CHANGE_TYPE_EXPUNGE) { /* ignore */ return 0; } else { i_assert(change->type == DSYNC_MAIL_CHANGE_TYPE_SAVE); } } else { /* a) uid < last_common_uid can never happen */ i_assert(change->uid >= importer->last_common_uid); /* b) uid = last_common_uid if we've verified that the messages' GUIDs match so far. c) uid > last_common_uid: i) TYPE_EXPUNGE change has GUID=NULL, so we couldn't verify yet if it matches our local message, ii) local message is expunged and we couldn't find its GUID */ if (change->uid > importer->last_common_uid) { i_assert(change->type == DSYNC_MAIL_CHANGE_TYPE_EXPUNGE || importer->cur_mail == NULL || change->uid < importer->cur_mail->uid); } } switch (change->type) { case DSYNC_MAIL_CHANGE_TYPE_SAVE: dsync_mailbox_import_save(importer, change); break; case DSYNC_MAIL_CHANGE_TYPE_EXPUNGE: dsync_mailbox_import_expunge(importer, change); break; case DSYNC_MAIL_CHANGE_TYPE_FLAG_CHANGE: i_assert(importer->last_common_uid_found); dsync_mailbox_import_flag_change(importer, change); break; } return importer->failed ? -1 : 0; } static int importer_new_mail_final_uid_cmp(struct importer_new_mail *const *newmail1, struct importer_new_mail *const *newmail2) { if ((*newmail1)->final_uid < (*newmail2)->final_uid) return -1; if ((*newmail1)->final_uid > (*newmail2)->final_uid) return 1; return 0; } static void dsync_mailbox_import_assign_new_uids(struct dsync_mailbox_importer *importer) { struct importer_new_mail *newmail; uint32_t common_uid_next, new_uid; common_uid_next = I_MAX(importer->local_uid_next, importer->remote_uid_next); array_foreach_elem(&importer->newmails, newmail) { if (newmail->skip) { /* already assigned */ i_assert(newmail->final_uid != 0); continue; } /* figure out what UID to use for the mail */ if (newmail->uid_is_usable) { /* keep the UID */ new_uid = newmail->final_uid; } else if (newmail->link != NULL && newmail->link->uid_is_usable) { /* we can use the linked message's UID and expunge this mail */ new_uid = newmail->link->final_uid; } else { i_assert(!importer->revert_local_changes); new_uid = common_uid_next++; imp_debug(importer, "UID %u isn't usable, assigning new UID %u", newmail->final_uid, new_uid); } newmail->final_uid = new_uid; if (newmail->link != NULL && newmail->link != newmail) { /* skip processing the linked mail */ newmail->link->skip = TRUE; } } importer->last_common_uid = common_uid_next-1; importer->new_uids_assigned = TRUE; /* Sort the newmails by their final_uid. This is used for tracking whether an intermediate commit is allowed. */ array_sort(&importer->newmails, importer_new_mail_final_uid_cmp); } static int dsync_mailbox_import_local_uid(struct dsync_mailbox_importer *importer, struct mail *mail, uint32_t uid, const char *guid, struct dsync_mail *dmail_r) { const char *error_field, *errstr; enum mail_error error; if (!mail_set_uid(mail, uid)) return 0; /* NOTE: Errors are logged, but they don't cause the entire import to fail. */ if (dsync_mail_fill(mail, TRUE, dmail_r, &error_field) < 0) { errstr = mailbox_get_last_internal_error(mail->box, &error); if (error == MAIL_ERROR_EXPUNGED) return 0; i_error("Mailbox %s: Can't lookup %s for UID=%u: %s", mailbox_get_vname(importer->box), error_field, uid, errstr); return -1; } if (*guid != '\0' && strcmp(guid, dmail_r->guid) != 0) { dsync_import_unexpected_state(importer, t_strdup_printf( "Unexpected GUID mismatch (3) for UID=%u: %s != %s", uid, dmail_r->guid, guid)); return -1; } return 1; } static void dsync_mailbox_import_saved_uid(struct dsync_mailbox_importer *importer, uint32_t uid) { i_assert(importer->search_ctx == NULL); if (importer->highest_wanted_uid < uid) importer->highest_wanted_uid = uid; array_push_back(&importer->wanted_uids, &uid); } static void dsync_mailbox_import_update_first_saved(struct dsync_mailbox_importer *importer) { struct importer_new_mail *const *newmails; unsigned int count; newmails = array_get(&importer->newmails, &count); while (importer->first_unsaved_idx < count) { if (!newmails[importer->first_unsaved_idx]->saved) break; importer->first_unsaved_idx++; } } static void dsync_mailbox_import_saved_newmail(struct dsync_mailbox_importer *importer, struct importer_new_mail *newmail) { dsync_mailbox_import_saved_uid(importer, newmail->final_uid); newmail->saved = TRUE; dsync_mailbox_import_update_first_saved(importer); importer->saves_since_commit++; /* we can commit only if all the upcoming mails will have UIDs that are larger than we're committing. Note that if any existing UIDs have been changed, the new UID is usually higher than anything that is being saved so we can't do an intermediate commit. It's too much extra work to try to handle that situation. So here this never happens, because then array_count(wanted_uids) is always higher than first_unsaved_idx. */ if (importer->saves_since_commit >= importer->commit_msgs_interval && importer->first_unsaved_idx == array_count(&importer->wanted_uids)) { if (dsync_mailbox_import_commit(importer, FALSE) < 0) importer->failed = TRUE; importer->saves_since_commit = 0; } } static bool dsync_msg_change_uid(struct dsync_mailbox_importer *importer, uint32_t old_uid, uint32_t new_uid) { struct mail_save_context *save_ctx; IMPORTER_DEBUG_CHANGE(importer); if (!mail_set_uid(importer->mail, old_uid)) return FALSE; save_ctx = mailbox_save_alloc(importer->ext_trans); mailbox_save_copy_flags(save_ctx, importer->mail); mailbox_save_set_uid(save_ctx, new_uid); if (mailbox_move(&save_ctx, importer->mail) < 0) return FALSE; dsync_mailbox_import_saved_uid(importer, new_uid); return TRUE; } static bool dsync_mailbox_import_change_uid(struct dsync_mailbox_importer *importer, ARRAY_TYPE(seq_range) *unwanted_uids, uint32_t wanted_uid) { const struct seq_range *range; unsigned int count, n; struct seq_range_iter iter; uint32_t uid; /* optimize by first trying to use the latest UID */ range = array_get(unwanted_uids, &count); if (count == 0) return FALSE; if (dsync_msg_change_uid(importer, range[count-1].seq2, wanted_uid)) { seq_range_array_remove(unwanted_uids, range[count-1].seq2); return TRUE; } if (mailbox_get_last_mail_error(importer->box) == MAIL_ERROR_EXPUNGED) seq_range_array_remove(unwanted_uids, range[count-1].seq2); /* now try to use any of them by iterating through them. (would be easier&faster to just iterate backwards, but probably too much trouble to add such API) */ n = 0; seq_range_array_iter_init(&iter, unwanted_uids); while (seq_range_array_iter_nth(&iter, n++, &uid)) { if (dsync_msg_change_uid(importer, uid, wanted_uid)) { seq_range_array_remove(unwanted_uids, uid); return TRUE; } if (mailbox_get_last_mail_error(importer->box) == MAIL_ERROR_EXPUNGED) seq_range_array_remove(unwanted_uids, uid); } return FALSE; } static bool dsync_mailbox_import_try_local(struct dsync_mailbox_importer *importer, struct importer_new_mail *all_newmails, ARRAY_TYPE(seq_range) *local_uids, ARRAY_TYPE(seq_range) *wanted_uids) { ARRAY_TYPE(seq_range) assigned_uids, unwanted_uids; struct seq_range_iter local_iter, wanted_iter; unsigned int local_n, wanted_n; uint32_t local_uid, wanted_uid; struct importer_new_mail *mail; struct dsync_mail dmail; if (array_count(local_uids) == 0) return FALSE; local_n = wanted_n = 0; seq_range_array_iter_init(&local_iter, local_uids); seq_range_array_iter_init(&wanted_iter, wanted_uids); /* wanted_uids contains UIDs that need to exist at the end. those that don't already exist in local_uids have a higher UID than any existing local UID */ t_array_init(&assigned_uids, array_count(wanted_uids)); t_array_init(&unwanted_uids, 8); while (seq_range_array_iter_nth(&local_iter, local_n++, &local_uid)) { if (seq_range_array_iter_nth(&wanted_iter, wanted_n, &wanted_uid)) { if (local_uid == wanted_uid) { /* we have exactly the UID we want. keep it. */ seq_range_array_add(&assigned_uids, wanted_uid); wanted_n++; continue; } i_assert(local_uid < wanted_uid); } /* we no longer want this local UID. */ seq_range_array_add(&unwanted_uids, local_uid); } /* reuse as many existing messages as possible by changing their UIDs */ while (seq_range_array_iter_nth(&wanted_iter, wanted_n, &wanted_uid)) { if (!dsync_mailbox_import_change_uid(importer, &unwanted_uids, wanted_uid)) break; seq_range_array_add(&assigned_uids, wanted_uid); wanted_n++; } /* expunge all unwanted messages */ local_n = 0; seq_range_array_iter_init(&local_iter, &unwanted_uids); while (seq_range_array_iter_nth(&local_iter, local_n++, &local_uid)) { IMPORTER_DEBUG_CHANGE(importer); if (mail_set_uid(importer->mail, local_uid)) mail_expunge(importer->mail); } /* mark mails whose UIDs we got to be skipped over later */ for (mail = all_newmails; mail != NULL; mail = mail->next) { if (!mail->skip && seq_range_exists(&assigned_uids, mail->final_uid)) mail->skip = TRUE; } if (!seq_range_array_iter_nth(&wanted_iter, wanted_n, &wanted_uid)) { /* we've assigned all wanted UIDs */ return TRUE; } /* try to find one existing message that we can use to copy to the other instances */ local_n = 0; seq_range_array_iter_init(&local_iter, local_uids); while (seq_range_array_iter_nth(&local_iter, local_n++, &local_uid)) { if (dsync_mailbox_import_local_uid(importer, importer->mail, local_uid, all_newmails->guid, &dmail) > 0) { if (dsync_mailbox_save_newmails(importer, &dmail, all_newmails, FALSE)) return TRUE; } } return FALSE; } static bool dsync_mailbox_import_try_virtual_all(struct dsync_mailbox_importer *importer, struct importer_new_mail *all_newmails) { struct dsync_mail dmail; if (all_newmails->virtual_all_uid == 0) return FALSE; if (dsync_mailbox_import_local_uid(importer, importer->virtual_mail, all_newmails->virtual_all_uid, all_newmails->guid, &dmail) > 0) { if (dsync_mailbox_save_newmails(importer, &dmail, all_newmails, FALSE)) return TRUE; } return FALSE; } static bool dsync_mailbox_import_handle_mail(struct dsync_mailbox_importer *importer, struct importer_new_mail *all_newmails) { ARRAY_TYPE(seq_range) local_uids, wanted_uids; struct dsync_mail_request *request; struct importer_new_mail *mail; const char *request_guid = NULL; uint32_t request_uid = 0; i_assert(all_newmails != NULL); /* get the list of the current local UIDs and the wanted UIDs. find the first remote instance that we can request in case there are no local instances */ t_array_init(&local_uids, 8); t_array_init(&wanted_uids, 8); for (mail = all_newmails; mail != NULL; mail = mail->next) { if (mail->uid_in_local) seq_range_array_add(&local_uids, mail->local_uid); else if (request_guid == NULL) { if (*mail->guid != '\0') request_guid = mail->guid; request_uid = mail->remote_uid; i_assert(request_uid != 0); } if (!mail->skip) seq_range_array_add(&wanted_uids, mail->final_uid); } i_assert(array_count(&wanted_uids) > 0); if (!dsync_mailbox_import_try_local(importer, all_newmails, &local_uids, &wanted_uids) && !dsync_mailbox_import_try_virtual_all(importer, all_newmails)) { /* no local instance. request from remote */ IMPORTER_DEBUG_CHANGE(importer); if (importer->want_mail_requests) { request = array_append_space(&importer->mail_requests); request->guid = request_guid; request->uid = request_uid; } return FALSE; } /* successfully handled all the mails locally */ importer->import_pos++; return TRUE; } static void dsync_mailbox_import_find_virtual_uids(struct dsync_mailbox_importer *importer) { struct mail_search_context *search_ctx; struct mail_search_args *search_args; struct importer_new_mail *newmail; struct mail *mail; const char *guid; if (mailbox_sync(importer->virtual_all_box, 0) < 0) { i_error("Couldn't sync \\All mailbox '%s': %s", mailbox_get_vname(importer->virtual_all_box), mailbox_get_last_internal_error(importer->virtual_all_box, NULL)); return; } search_args = mail_search_build_init(); mail_search_build_add_all(search_args); importer->virtual_trans = mailbox_transaction_begin(importer->virtual_all_box, importer->transaction_flags, __func__); search_ctx = mailbox_search_init(importer->virtual_trans, search_args, NULL, MAIL_FETCH_GUID, NULL); mail_search_args_unref(&search_args); while (mailbox_search_next(search_ctx, &mail)) { if (mail_get_special(mail, MAIL_FETCH_GUID, &guid) < 0) { /* ignore errors */ continue; } newmail = hash_table_lookup(importer->import_guids, guid); if (newmail != NULL && newmail->virtual_all_uid == 0) newmail->virtual_all_uid = mail->uid; } if (mailbox_search_deinit(&search_ctx) < 0) { i_error("Couldn't search \\All mailbox '%s': %s", mailbox_get_vname(importer->virtual_all_box), mailbox_get_last_internal_error(importer->virtual_all_box, NULL)); } importer->virtual_mail = mail_alloc(importer->virtual_trans, 0, NULL); } static void dsync_mailbox_import_handle_local_mails(struct dsync_mailbox_importer *importer) { struct hash_iterate_context *iter; const char *key; void *key2; struct importer_new_mail *mail; if (importer->virtual_all_box != NULL && hash_table_count(importer->import_guids) > 0) { /* find UIDs in \All mailbox for all wanted GUIDs. */ dsync_mailbox_import_find_virtual_uids(importer); } iter = hash_table_iterate_init(importer->import_guids); while (hash_table_iterate(iter, importer->import_guids, &key, &mail)) { T_BEGIN { if (dsync_mailbox_import_handle_mail(importer, mail)) hash_table_remove(importer->import_guids, key); } T_END; } hash_table_iterate_deinit(&iter); iter = hash_table_iterate_init(importer->import_uids); while (hash_table_iterate(iter, importer->import_uids, &key2, &mail)) { T_BEGIN { if (dsync_mailbox_import_handle_mail(importer, mail)) hash_table_remove(importer->import_uids, key2); } T_END; } hash_table_iterate_deinit(&iter); if (!importer->mails_have_guids) { array_foreach_elem(&importer->newmails, mail) { if (mail->uid_in_local) (void)dsync_mailbox_import_handle_mail(importer, mail); } } } int dsync_mailbox_import_changes_finish(struct dsync_mailbox_importer *importer) { i_assert(!importer->new_uids_assigned); if (!importer->last_common_uid_found) { /* handle pending expunges and flag updates */ dsync_mailbox_common_uid_found(importer); } /* skip common local mails */ (void)importer_next_mail(importer, importer->last_common_uid+1); /* if there are any local mails left, add them to newmails list */ while (importer->cur_mail != NULL && !importer->failed) (void)dsync_mailbox_try_save(importer, NULL); if (importer->search_ctx != NULL) { if (mailbox_search_deinit(&importer->search_ctx) < 0) { i_error("Mailbox %s: Search failed: %s", mailbox_get_vname(importer->box), mailbox_get_last_internal_error(importer->box, &importer->mail_error)); importer->failed = TRUE; } } importer->import_count = hash_table_count(importer->import_guids) + hash_table_count(importer->import_uids); dsync_mailbox_import_assign_new_uids(importer); /* save mails from local sources where possible, request the rest from remote */ if (!importer->failed) dsync_mailbox_import_handle_local_mails(importer); return importer->failed ? -1 : 0; } const struct dsync_mail_request * dsync_mailbox_import_next_request(struct dsync_mailbox_importer *importer) { const struct dsync_mail_request *requests; unsigned int count; requests = array_get(&importer->mail_requests, &count); if (importer->mail_request_idx == count) return NULL; return &requests[importer->mail_request_idx++]; } static const char *const * dsync_mailbox_get_final_keywords(const struct dsync_mail_change *change) { ARRAY_TYPE(const_string) keywords; const char *const *changes; unsigned int i, count; if (!array_is_created(&change->keyword_changes)) return NULL; changes = array_get(&change->keyword_changes, &count); t_array_init(&keywords, count); for (i = 0; i < count; i++) { if (changes[i][0] == KEYWORD_CHANGE_ADD || changes[i][0] == KEYWORD_CHANGE_FINAL || changes[i][0] == KEYWORD_CHANGE_ADD_AND_FINAL) { const char *name = changes[i]+1; array_push_back(&keywords, &name); } } if (array_count(&keywords) == 0) return NULL; array_append_zero(&keywords); return array_front(&keywords); } static void dsync_mailbox_save_set_metadata(struct dsync_mailbox_importer *importer, struct mail_save_context *save_ctx, const struct dsync_mail_change *change) { const char *const *keyword_names; struct mail_keywords *keywords; keyword_names = dsync_mailbox_get_final_keywords(change); keywords = keyword_names == NULL ? NULL : mailbox_keywords_create_valid(importer->box, keyword_names); mailbox_save_set_flags(save_ctx, change->final_flags, keywords); if (keywords != NULL) mailbox_keywords_unref(&keywords); if (change->modseq > 1) { (void)mailbox_enable(importer->box, MAILBOX_FEATURE_CONDSTORE); mailbox_save_set_min_modseq(save_ctx, change->modseq); } /* FIXME: if there already are private flags, they get lost because saving can't handle updating private index. they get added on the next sync though. if this is fixed here, set min_pvt_modseq also. */ } static int dsync_msg_try_copy(struct dsync_mailbox_importer *importer, struct mail_save_context **save_ctx_p, struct importer_new_mail **all_newmails_forcopy) { struct importer_new_mail *inst; for (inst = *all_newmails_forcopy; inst != NULL; inst = inst->next) { if (inst->uid_in_local && !inst->copy_failed && mail_set_uid(importer->mail, inst->local_uid)) { if (mailbox_copy(save_ctx_p, importer->mail) < 0) { enum mail_error error; const char *errstr; errstr = mailbox_get_last_internal_error(importer->box, &error); if (error != MAIL_ERROR_EXPUNGED) { i_warning("Failed to copy mail from UID=%u: " "%s - falling back to other means", inst->local_uid, errstr); } inst->copy_failed = TRUE; return -1; } *all_newmails_forcopy = inst; return 1; } } *all_newmails_forcopy = NULL; return 0; } static void dsync_mailbox_save_set_nonminimal(struct mail_save_context *save_ctx, const struct dsync_mail *mail) { if (mail->pop3_uidl != NULL && *mail->pop3_uidl != '\0') mailbox_save_set_pop3_uidl(save_ctx, mail->pop3_uidl); if (mail->pop3_order > 0) mailbox_save_set_pop3_order(save_ctx, mail->pop3_order); mailbox_save_set_received_date(save_ctx, mail->received_date, 0); } static struct mail_save_context * dsync_mailbox_save_init(struct dsync_mailbox_importer *importer, const struct dsync_mail *mail, struct importer_new_mail *newmail) { struct mail_save_context *save_ctx; save_ctx = mailbox_save_alloc(importer->ext_trans); mailbox_save_set_uid(save_ctx, newmail->final_uid); if (*mail->guid != '\0') mailbox_save_set_guid(save_ctx, mail->guid); if (mail->saved_date != 0) mailbox_save_set_save_date(save_ctx, mail->saved_date); dsync_mailbox_save_set_metadata(importer, save_ctx, newmail->change); if (!mail->minimal_fields) dsync_mailbox_save_set_nonminimal(save_ctx, mail); return save_ctx; } static bool dsync_mailbox_save_body(struct dsync_mailbox_importer *importer, const struct dsync_mail *mail, struct importer_new_mail *newmail, struct importer_new_mail **all_newmails_forcopy, bool remote_mail) { struct mail_save_context *save_ctx; struct istream *input; ssize_t ret; bool save_failed = FALSE; /* try to save the mail by copying an existing mail */ save_ctx = dsync_mailbox_save_init(importer, mail, newmail); if ((ret = dsync_msg_try_copy(importer, &save_ctx, all_newmails_forcopy)) < 0) { if (save_ctx == NULL) save_ctx = dsync_mailbox_save_init(importer, mail, newmail); } if (ret <= 0 && mail->input_mail != NULL) { /* copy using the source mail */ i_assert(mail->input_mail->uid == mail->input_mail_uid); if (mailbox_copy(&save_ctx, mail->input_mail) == 0) ret = 1; else { enum mail_error error; const char *errstr; errstr = mailbox_get_last_internal_error(importer->box, &error); if (error != MAIL_ERROR_EXPUNGED) { i_warning("Failed to copy source UID=%u mail: " "%s - falling back to regular saving", mail->input_mail->uid, errstr); } ret = -1; save_ctx = dsync_mailbox_save_init(importer, mail, newmail); } } if (ret > 0) { i_assert(save_ctx == NULL); dsync_mailbox_import_saved_newmail(importer, newmail); return TRUE; } /* fallback to saving from remote stream */ if (!remote_mail) { /* the mail isn't remote yet. we were just trying to copy a local mail to avoid downloading the remote mail. */ mailbox_save_cancel(&save_ctx); return FALSE; } if (mail->minimal_fields) { struct dsync_mail mail2; const char *error_field; i_assert(mail->input_mail != NULL); if (dsync_mail_fill_nonminimal(mail->input_mail, &mail2, &error_field) < 0) { i_error("Mailbox %s: Failed to read mail %s uid=%u: %s", mailbox_get_vname(importer->box), error_field, mail->uid, mailbox_get_last_internal_error(importer->box, &importer->mail_error)); importer->failed = TRUE; mailbox_save_cancel(&save_ctx); return TRUE; } dsync_mailbox_save_set_nonminimal(save_ctx, &mail2); input = mail2.input; } else { input = mail->input; } if (input == NULL) { /* it was just expunged in remote, skip it */ mailbox_save_cancel(&save_ctx); return TRUE; } i_stream_seek(input, 0); if (mailbox_save_begin(&save_ctx, input) < 0) { i_error("Mailbox %s: Saving failed: %s", mailbox_get_vname(importer->box), mailbox_get_last_internal_error(importer->box, &importer->mail_error)); importer->failed = TRUE; return TRUE; } while ((ret = i_stream_read(input)) > 0 || ret == -2) { if (mailbox_save_continue(save_ctx) < 0) { save_failed = TRUE; ret = -1; break; } } i_assert(ret == -1); if (input->stream_errno != 0) { i_error("Mailbox %s: read(msg input) failed: %s", mailbox_get_vname(importer->box), i_stream_get_error(input)); mailbox_save_cancel(&save_ctx); importer->mail_error = MAIL_ERROR_TEMP; importer->failed = TRUE; } else if (save_failed) { i_error("Mailbox %s: Saving failed: %s", mailbox_get_vname(importer->box), mailbox_get_last_internal_error(importer->box, &importer->mail_error)); mailbox_save_cancel(&save_ctx); importer->failed = TRUE; } else { i_assert(input->eof); if (mailbox_save_finish(&save_ctx) < 0) { i_error("Mailbox %s: Saving failed: %s", mailbox_get_vname(importer->box), mailbox_get_last_internal_error(importer->box, &importer->mail_error)); importer->failed = TRUE; } else { dsync_mailbox_import_saved_newmail(importer, newmail); } } return TRUE; } static bool dsync_mailbox_save_newmails(struct dsync_mailbox_importer *importer, const struct dsync_mail *mail, struct importer_new_mail *all_newmails, bool remote_mail) { struct importer_new_mail *newmail, *all_newmails_forcopy; bool ret = TRUE; /* if all_newmails list is large, avoid scanning through the uninteresting ones for each newmail */ all_newmails_forcopy = all_newmails; /* save all instances of the message */ for (newmail = all_newmails; newmail != NULL && ret; newmail = newmail->next) { if (!newmail->skip) T_BEGIN { if (!dsync_mailbox_save_body(importer, mail, newmail, &all_newmails_forcopy, remote_mail)) ret = FALSE; } T_END; } return ret; } int dsync_mailbox_import_mail(struct dsync_mailbox_importer *importer, const struct dsync_mail *mail) { struct importer_new_mail *all_newmails; i_assert(mail->input == NULL || mail->input->seekable); i_assert(importer->new_uids_assigned); if (importer->failed) return -1; if (importer->require_full_resync) return 0; imp_debug(importer, "Import mail body for GUID=%s UID=%u", mail->guid, mail->uid); all_newmails = *mail->guid != '\0' ? hash_table_lookup(importer->import_guids, mail->guid) : hash_table_lookup(importer->import_uids, POINTER_CAST(mail->uid)); if (all_newmails == NULL) { if (importer->want_mail_requests) { i_error("Mailbox %s: Remote sent unwanted message body for " "GUID=%s UID=%u", mailbox_get_vname(importer->box), mail->guid, mail->uid); } else { imp_debug(importer, "Skip unwanted mail body for " "GUID=%s UID=%u", mail->guid, mail->uid); } return 0; } if (*mail->guid != '\0') hash_table_remove(importer->import_guids, mail->guid); else { hash_table_remove(importer->import_uids, POINTER_CAST(mail->uid)); } importer->import_pos++; if (!dsync_mailbox_save_newmails(importer, mail, all_newmails, TRUE)) i_unreached(); return importer->failed ? -1 : 0; } static int reassign_uids_in_seq_range(struct dsync_mailbox_importer *importer, const ARRAY_TYPE(seq_range) *unwanted_uids) { struct mailbox *box = importer->box; const enum mailbox_transaction_flags trans_flags = importer->transaction_flags | MAILBOX_TRANSACTION_FLAG_EXTERNAL | MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS; struct mailbox_transaction_context *trans; struct mail_search_args *search_args; struct mail_search_arg *arg; struct mail_search_context *search_ctx; struct mail_save_context *save_ctx; struct mail *mail; unsigned int renumber_count = 0; int ret = 1; if (array_count(unwanted_uids) == 0) return 1; if (importer->debug) T_BEGIN { string_t *str = t_str_new(256); imap_write_seq_range(str, unwanted_uids); imp_debug(importer, "Reassign UIDs: %s", str_c(str)); } T_END; search_args = mail_search_build_init(); arg = mail_search_build_add(search_args, SEARCH_UIDSET); p_array_init(&arg->value.seqset, search_args->pool, array_count(unwanted_uids)); array_append_array(&arg->value.seqset, unwanted_uids); trans = mailbox_transaction_begin(box, trans_flags, __func__); search_ctx = mailbox_search_init(trans, search_args, NULL, 0, NULL); mail_search_args_unref(&search_args); while (mailbox_search_next(search_ctx, &mail)) { save_ctx = mailbox_save_alloc(trans); mailbox_save_copy_flags(save_ctx, mail); if (mailbox_move(&save_ctx, mail) < 0) { i_error("Mailbox %s: Couldn't move mail within mailbox: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, &importer->mail_error)); ret = -1; } else if (ret > 0) { ret = 0; } renumber_count++; } if (mailbox_search_deinit(&search_ctx) < 0) { i_error("Mailbox %s: mail search failed: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, &importer->mail_error)); ret = -1; } if (mailbox_transaction_commit(&trans) < 0) { i_error("Mailbox %s: UID reassign commit failed: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, &importer->mail_error)); ret = -1; } if (ret == 0) { imp_debug(importer, "Mailbox %s: Change during sync: " "Renumbered %u of %u unwanted UIDs", mailbox_get_vname(box), renumber_count, array_count(unwanted_uids)); } return ret; } static int reassign_unwanted_uids(struct dsync_mailbox_importer *importer, const char **changes_during_sync_r) { ARRAY_TYPE(seq_range) unwanted_uids; const uint32_t *wanted_uids, *saved_uids; uint32_t highest_seen_uid; unsigned int i, wanted_count, saved_count; int ret = 0; wanted_uids = array_get(&importer->wanted_uids, &wanted_count); saved_uids = array_get(&importer->saved_uids, &saved_count); i_assert(wanted_count == saved_count); if (wanted_count == 0) return 0; /* wanted_uids contains the UIDs we tried to save mails with. if nothing changed during dsync, we should have the expected UIDs (saved_uids) and all is well. if any new messages got inserted during dsync, we'll need to fix up the UIDs and let the next dsync fix up the other side. for example: remote uids = 5,7,9 = wanted_uids remote uidnext = 12 locally added new uid=5 -> saved_uids = 10,7,9 we'll now need to reassign UIDs 5 and 10. to be fully future-proof we'll reassign all UIDs between [original local uidnext .. highest UID we think we know] that aren't in saved_uids. */ /* create uidset for the list of UIDs we don't want to exist */ t_array_init(&unwanted_uids, 8); highest_seen_uid = I_MAX(importer->remote_uid_next-1, importer->highest_wanted_uid); i_assert(importer->local_uid_next <= highest_seen_uid); seq_range_array_add_range(&unwanted_uids, importer->local_uid_next, highest_seen_uid); for (i = 0; i < wanted_count; i++) { i_assert(i < wanted_count); if (saved_uids[i] == wanted_uids[i]) seq_range_array_remove(&unwanted_uids, saved_uids[i]); } ret = reassign_uids_in_seq_range(importer, &unwanted_uids); if (ret == 0) { *changes_during_sync_r = t_strdup_printf( "%u UIDs changed due to UID conflicts", seq_range_count(&unwanted_uids)); /* conflicting changes during sync, revert our last-common-uid back to a safe value. */ importer->last_common_uid = importer->local_uid_next - 1; } return ret < 0 ? -1 : 0; } static int dsync_mailbox_import_commit(struct dsync_mailbox_importer *importer, bool final) { struct mail_transaction_commit_changes changes; struct seq_range_iter iter; uint32_t uid; unsigned int n; int ret = importer->failed ? -1 : 0; mail_free(&importer->mail); mail_free(&importer->ext_mail); /* commit saves */ if (mailbox_transaction_commit_get_changes(&importer->ext_trans, &changes) < 0) { i_error("Mailbox %s: Save commit failed: %s", mailbox_get_vname(importer->box), mailbox_get_last_internal_error(importer->box, &importer->mail_error)); /* removed wanted_uids that weren't actually saved */ array_delete(&importer->wanted_uids, array_count(&importer->saved_uids), array_count(&importer->wanted_uids) - array_count(&importer->saved_uids)); mailbox_transaction_rollback(&importer->trans); ret = -1; } else { /* remember the UIDs that were successfully saved */ if (importer->debug) T_BEGIN { string_t *str = t_str_new(256); imap_write_seq_range(str, &changes.saved_uids); imp_debug(importer, "Saved UIDs: %s", str_c(str)); } T_END; seq_range_array_iter_init(&iter, &changes.saved_uids); n = 0; while (seq_range_array_iter_nth(&iter, n++, &uid)) array_push_back(&importer->saved_uids, &uid); pool_unref(&changes.pool); /* commit flag changes and expunges */ if (mailbox_transaction_commit(&importer->trans) < 0) { i_error("Mailbox %s: Commit failed: %s", mailbox_get_vname(importer->box), mailbox_get_last_internal_error(importer->box, &importer->mail_error)); ret = -1; } } if (!final) dsync_mailbox_import_transaction_begin(importer); return ret; } static int dsync_mailbox_import_finish(struct dsync_mailbox_importer *importer, const char **changes_during_sync_r) { struct mailbox_update update; int ret; ret = dsync_mailbox_import_commit(importer, TRUE); if (ret == 0) { /* update mailbox metadata if we successfully saved everything. */ i_zero(&update); update.min_next_uid = importer->remote_uid_next; update.min_first_recent_uid = I_MIN(importer->last_common_uid+1, importer->remote_first_recent_uid); update.min_highest_modseq = importer->remote_highest_modseq; update.min_highest_pvt_modseq = importer->remote_highest_pvt_modseq; imp_debug(importer, "Finish update: min_next_uid=%u " "min_first_recent_uid=%u min_highest_modseq=%"PRIu64" " "min_highest_pvt_modseq=%"PRIu64, update.min_next_uid, update.min_first_recent_uid, update.min_highest_modseq, update.min_highest_pvt_modseq); if (mailbox_update(importer->box, &update) < 0) { i_error("Mailbox %s: Update failed: %s", mailbox_get_vname(importer->box), mailbox_get_last_internal_error(importer->box, &importer->mail_error)); ret = -1; } } /* sync mailbox to finish flag changes and expunges. */ if (mailbox_sync(importer->box, 0) < 0) { i_error("Mailbox %s: Sync failed: %s", mailbox_get_vname(importer->box), mailbox_get_last_internal_error(importer->box, &importer->mail_error)); ret = -1; } if (ret == 0) { /* give new UIDs to messages that got saved with unwanted UIDs. do it only if the whole transaction succeeded. */ if (reassign_unwanted_uids(importer, changes_during_sync_r) < 0) ret = -1; } return ret; } static void dsync_mailbox_import_check_missing_guid_imports(struct dsync_mailbox_importer *importer) { struct hash_iterate_context *iter; const char *key; struct importer_new_mail *mail; iter = hash_table_iterate_init(importer->import_guids); while (hash_table_iterate(iter, importer->import_guids, &key, &mail)) { for (; mail != NULL; mail = mail->next) { if (mail->skip) continue; i_error("Mailbox %s: Remote didn't send mail GUID=%s (UID=%u)", mailbox_get_vname(importer->box), mail->guid, mail->remote_uid); importer->mail_error = MAIL_ERROR_TEMP; importer->failed = TRUE; } } hash_table_iterate_deinit(&iter); } static void dsync_mailbox_import_check_missing_uid_imports(struct dsync_mailbox_importer *importer) { struct hash_iterate_context *iter; void *key; struct importer_new_mail *mail; iter = hash_table_iterate_init(importer->import_uids); while (hash_table_iterate(iter, importer->import_uids, &key, &mail)) { for (; mail != NULL; mail = mail->next) { if (mail->skip) continue; i_error("Mailbox %s: Remote didn't send mail UID=%u", mailbox_get_vname(importer->box), mail->remote_uid); importer->mail_error = MAIL_ERROR_TEMP; importer->failed = TRUE; } } hash_table_iterate_deinit(&iter); } int dsync_mailbox_import_deinit(struct dsync_mailbox_importer **_importer, bool success, uint32_t *last_common_uid_r, uint64_t *last_common_modseq_r, uint64_t *last_common_pvt_modseq_r, uint32_t *last_messages_count_r, const char **changes_during_sync_r, bool *require_full_resync_r, enum mail_error *error_r) { struct dsync_mailbox_importer *importer = *_importer; struct mailbox_status status; int ret; *_importer = NULL; *changes_during_sync_r = NULL; *require_full_resync_r = importer->require_full_resync; if ((!success || importer->require_full_resync) && !importer->failed) { importer->mail_error = MAIL_ERROR_TEMP; importer->failed = TRUE; } if (!importer->new_uids_assigned && !importer->failed) dsync_mailbox_import_assign_new_uids(importer); if (!importer->failed) { dsync_mailbox_import_check_missing_guid_imports(importer); dsync_mailbox_import_check_missing_uid_imports(importer); } if (importer->search_ctx != NULL) { if (mailbox_search_deinit(&importer->search_ctx) < 0) { i_error("Mailbox %s: Search failed: %s", mailbox_get_vname(importer->box), mailbox_get_last_internal_error(importer->box, &importer->mail_error)); importer->failed = TRUE; } } if (dsync_mailbox_import_finish(importer, changes_during_sync_r) < 0) importer->failed = TRUE; if (importer->virtual_mail != NULL) mail_free(&importer->virtual_mail); if (importer->virtual_trans != NULL) (void)mailbox_transaction_commit(&importer->virtual_trans); hash_table_destroy(&importer->import_guids); hash_table_destroy(&importer->import_uids); array_free(&importer->maybe_expunge_uids); array_free(&importer->maybe_saves); array_free(&importer->wanted_uids); array_free(&importer->saved_uids); array_free(&importer->newmails); if (array_is_created(&importer->mail_requests)) array_free(&importer->mail_requests); *last_common_uid_r = importer->last_common_uid; if (*changes_during_sync_r == NULL) { *last_common_modseq_r = importer->remote_highest_modseq; *last_common_pvt_modseq_r = importer->remote_highest_pvt_modseq; } else { /* local changes occurred during dsync. we exported changes up to local_initial_highestmodseq, so all of the changes have happened after it. we want the next run to see those changes, so return it as the last common modseq */ *last_common_modseq_r = importer->local_initial_highestmodseq; *last_common_pvt_modseq_r = importer->local_initial_highestpvtmodseq; } if (importer->delete_mailbox) { if (mailbox_delete(importer->box) < 0) { i_error("Couldn't delete mailbox %s: %s", mailbox_get_vname(importer->box), mailbox_get_last_internal_error(importer->box, &importer->mail_error)); importer->failed = TRUE; } *last_messages_count_r = 0; } else { mailbox_get_open_status(importer->box, STATUS_MESSAGES, &status); *last_messages_count_r = status.messages; } i_assert(importer->failed == (importer->mail_error != 0)); ret = importer->failed ? -1 : 0; *error_r = importer->mail_error; pool_unref(&importer->pool); return ret; } const char *dsync_mailbox_import_get_proctitle(struct dsync_mailbox_importer *importer) { if (importer->search_ctx != NULL) return ""; return t_strdup_printf("%u/%u", importer->import_pos, importer->import_count); } dovecot-2.3.21.1/src/doveadm/doveadm-print-server.c0000644000000000000000000000612414656633576016747 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "strescape.h" #include "ostream.h" #include "doveadm.h" #include "doveadm-print-private.h" #include "client-connection.h" #define DOVEADM_PRINT_FLUSH_TIMEOUT_SECS 60 struct doveadm_print_server_context { unsigned int header_idx, header_count; string_t *str; }; static struct doveadm_print_server_context ctx; static void doveadm_print_server_flush(void); static void doveadm_print_server_init(void) { ctx.str = str_new(default_pool, 256); } static void doveadm_print_server_deinit(void) { str_free(&ctx.str); } static void doveadm_print_server_header(const struct doveadm_print_header *hdr ATTR_UNUSED) { /* no need to transfer these. the client should already know what it's getting */ ctx.header_count++; } static void doveadm_print_server_print(const char *value) { str_append_tabescaped(ctx.str, value); str_append_c(ctx.str, '\t'); if (++ctx.header_idx == ctx.header_count) { ctx.header_idx = 0; doveadm_print_server_flush(); } } static void doveadm_print_server_print_stream(const unsigned char *value, size_t size) { if (size == 0) { doveadm_print_server_print(""); return; } str_append_tabescaped_n(ctx.str, value, size); if (str_len(ctx.str) >= IO_BLOCK_SIZE) doveadm_print_server_flush(); } static int flush_callback(struct doveadm_print_server_context *ctx ATTR_UNUSED) { int ret; /* Keep flushing until everything is sent */ if ((ret = o_stream_flush(doveadm_print_ostream)) != 0) io_loop_stop(current_ioloop); return ret; } static void handle_flush_timeout(struct doveadm_print_server_context *ctx ATTR_UNUSED) { io_loop_stop(current_ioloop); o_stream_close(doveadm_print_ostream); i_error("write(%s) failed: Timed out after %u seconds", o_stream_get_name(doveadm_print_ostream), DOVEADM_PRINT_FLUSH_TIMEOUT_SECS); } static void doveadm_print_server_flush(void) { o_stream_nsend(doveadm_print_ostream, str_data(ctx.str), str_len(ctx.str)); str_truncate(ctx.str, 0); o_stream_uncork(doveadm_print_ostream); if (o_stream_get_buffer_used_size(doveadm_print_ostream) < IO_BLOCK_SIZE || doveadm_print_ostream->stream_errno != 0) return; /* Wait until buffer is flushed to avoid it growing too large */ struct ioloop *prev_loop = current_ioloop; struct ioloop *loop = io_loop_create(); /* Ensure we don't get stuck here forever */ struct timeout *to = timeout_add(DOVEADM_PRINT_FLUSH_TIMEOUT_SECS*1000, handle_flush_timeout, &ctx); o_stream_switch_ioloop_to(doveadm_print_ostream, loop); o_stream_set_flush_callback(doveadm_print_ostream, flush_callback, &ctx); io_loop_run(loop); timeout_remove(&to); o_stream_unset_flush_callback(doveadm_print_ostream); o_stream_switch_ioloop_to(doveadm_print_ostream, prev_loop); io_loop_destroy(&loop); } struct doveadm_print_vfuncs doveadm_print_server_vfuncs = { DOVEADM_PRINT_TYPE_SERVER, doveadm_print_server_init, doveadm_print_server_deinit, doveadm_print_server_header, doveadm_print_server_print, doveadm_print_server_print_stream, doveadm_print_server_flush }; dovecot-2.3.21.1/src/doveadm/doveadm-print-json.c0000644000000000000000000000715714656633576016421 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "strescape.h" #include "ostream.h" #include "json-parser.h" #include "doveadm.h" #include "doveadm-server.h" #include "doveadm-print.h" #include "doveadm-print-private.h" #include "client-connection.h" struct doveadm_print_json_context { unsigned int header_idx, header_count; bool first_row; bool in_stream; bool flushed; ARRAY(struct doveadm_print_header) headers; pool_t pool; string_t *str; }; static struct doveadm_print_json_context ctx; static void doveadm_print_json_flush_internal(void); static void doveadm_print_json_init(void) { i_zero(&ctx); ctx.pool = pool_alloconly_create("doveadm json print", 1024); ctx.str = str_new(ctx.pool, 256); p_array_init(&ctx.headers, ctx.pool, 1); ctx.first_row = TRUE; ctx.in_stream = FALSE; } static void doveadm_print_json_header(const struct doveadm_print_header *hdr) { struct doveadm_print_header *lhdr; lhdr = array_append_space(&ctx.headers); lhdr->key = p_strdup(ctx.pool, hdr->key); lhdr->flags = hdr->flags; ctx.header_count++; } static void doveadm_print_json_value_header(const struct doveadm_print_header *hdr) { // get header name if (ctx.header_idx == 0) { if (ctx.first_row == TRUE) { ctx.first_row = FALSE; str_append_c(ctx.str, '['); } else { str_append_c(ctx.str, ','); } str_append_c(ctx.str, '{'); } else { str_append_c(ctx.str, ','); } str_append_c(ctx.str, '"'); json_append_escaped(ctx.str, hdr->key); str_append_c(ctx.str, '"'); str_append_c(ctx.str, ':'); } static void doveadm_print_json_value_footer(void) { if (++ctx.header_idx == ctx.header_count) { ctx.header_idx = 0; str_append_c(ctx.str, '}'); doveadm_print_json_flush_internal(); } } static void doveadm_print_json_print(const char *value) { const struct doveadm_print_header *hdr = array_idx(&ctx.headers, ctx.header_idx); doveadm_print_json_value_header(hdr); if (value == NULL) { str_append(ctx.str, "null"); } else if ((hdr->flags & DOVEADM_PRINT_HEADER_FLAG_NUMBER) != 0) { i_assert(str_is_float(value, '\0')); str_append(ctx.str, value); } else { str_append_c(ctx.str, '"'); json_append_escaped(ctx.str, value); str_append_c(ctx.str, '"'); } doveadm_print_json_value_footer(); } static void doveadm_print_json_print_stream(const unsigned char *value, size_t size) { if (!ctx.in_stream) { const struct doveadm_print_header *hdr = array_idx(&ctx.headers, ctx.header_idx); doveadm_print_json_value_header(hdr); i_assert((hdr->flags & DOVEADM_PRINT_HEADER_FLAG_NUMBER) == 0); str_append_c(ctx.str, '"'); ctx.in_stream = TRUE; } if (size == 0) { str_append_c(ctx.str, '"'); doveadm_print_json_value_footer(); ctx.in_stream = FALSE; return; } json_append_escaped_data(ctx.str, value, size); if (str_len(ctx.str) >= IO_BLOCK_SIZE) doveadm_print_json_flush_internal(); } static void doveadm_print_json_flush_internal(void) { o_stream_nsend(doveadm_print_ostream, str_data(ctx.str), str_len(ctx.str)); str_truncate(ctx.str, 0); } static void doveadm_print_json_flush(void) { if (ctx.flushed) return; ctx.flushed = TRUE; if (ctx.first_row == FALSE) str_append_c(ctx.str,']'); else { str_append_c(ctx.str,'['); str_append_c(ctx.str,']'); } doveadm_print_json_flush_internal(); } static void doveadm_print_json_deinit(void) { pool_unref(&ctx.pool); } struct doveadm_print_vfuncs doveadm_print_json_vfuncs = { "json", doveadm_print_json_init, doveadm_print_json_deinit, doveadm_print_json_header, doveadm_print_json_print, doveadm_print_json_print_stream, doveadm_print_json_flush }; dovecot-2.3.21.1/src/doveadm/doveadm-print-pager.c0000644000000000000000000000527314656633576016543 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "ostream.h" #include "doveadm-print-private.h" struct doveadm_print_pager_header { const char *title; enum doveadm_print_header_flags flags; }; struct doveadm_print_pager_context { pool_t pool; ARRAY(struct doveadm_print_pager_header) headers; unsigned int header_idx; bool streaming:1; bool first_page:1; }; static struct doveadm_print_pager_context *ctx; static void doveadm_print_pager_header(const struct doveadm_print_header *hdr) { struct doveadm_print_pager_header *fhdr; fhdr = array_append_space(&ctx->headers); fhdr->flags = hdr->flags; fhdr->title = p_strdup(ctx->pool, hdr->title); } static void pager_next_hdr(void) { if (++ctx->header_idx == array_count(&ctx->headers)) { ctx->header_idx = 0; } } static void doveadm_print_pager_print(const char *value) { const struct doveadm_print_pager_header *hdr = array_idx(&ctx->headers, ctx->header_idx); if (ctx->header_idx == 0 && !ctx->first_page) { o_stream_nsend(doveadm_print_ostream, "\f\n", 2); } ctx->first_page = FALSE; if ((hdr->flags & DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE) == 0) { o_stream_nsend_str(doveadm_print_ostream, hdr->title); o_stream_nsend(doveadm_print_ostream, ": ", 2); } o_stream_nsend_str(doveadm_print_ostream, value); o_stream_nsend(doveadm_print_ostream, "\n", 1); pager_next_hdr(); } static void doveadm_print_pager_print_stream(const unsigned char *value, size_t size) { const struct doveadm_print_pager_header *hdr = array_idx(&ctx->headers, ctx->header_idx); if (!ctx->streaming) { ctx->streaming = TRUE; if ((hdr->flags & DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE) == 0) { o_stream_nsend_str(doveadm_print_ostream, hdr->title); o_stream_nsend(doveadm_print_ostream, ":\n", 2); } } o_stream_nsend(doveadm_print_ostream, value, size); if (size == 0) { pager_next_hdr(); ctx->streaming = FALSE; } } static void doveadm_print_pager_init(void) { pool_t pool; pool = pool_alloconly_create("doveadm print pager", 1024); ctx = p_new(pool, struct doveadm_print_pager_context, 1); ctx->pool = pool; ctx->first_page = TRUE; p_array_init(&ctx->headers, pool, 16); } static void doveadm_print_pager_flush(void) { if (ctx->header_idx != 0) { o_stream_nsend(doveadm_print_ostream, "\n", 1); ctx->header_idx = 0; } } static void doveadm_print_pager_deinit(void) { pool_unref(&ctx->pool); ctx = NULL; } struct doveadm_print_vfuncs doveadm_print_pager_vfuncs = { DOVEADM_PRINT_TYPE_PAGER, doveadm_print_pager_init, doveadm_print_pager_deinit, doveadm_print_pager_header, doveadm_print_pager_print, doveadm_print_pager_print_stream, doveadm_print_pager_flush }; dovecot-2.3.21.1/src/doveadm/doveadm-dump-dcrypt-key.c0000644000000000000000000001313514656633576017345 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "dcrypt.h" #include "dcrypt-iostream.h" #include "ostream-encrypt.h" #include "istream-private.h" #include "istream-decrypt.h" #include "doveadm-dump.h" #include "hex-binary.h" #include "str.h" #include #include #include #include #include #define KEY_BUF_SIZE 4096 static void dcrypt_dump_public_key_metadata(const char *buf) { const char *error = NULL; struct dcrypt_public_key *pub_key; bool ret = dcrypt_key_load_public(&pub_key, buf, &error); if (ret == FALSE) { i_error("dcrypt_key_load_public failed: %s", error); return; } enum dcrypt_key_type key_type = dcrypt_key_type_public(pub_key); if (key_type == DCRYPT_KEY_RSA) printf("key type: DCRYPT_KEY_RSA\n"); else if (key_type == DCRYPT_KEY_EC) printf("key type: DCRYPT_KEY_EC\n"); string_t *hash = t_str_new(128); if (!dcrypt_key_id_public(pub_key, "sha256", hash, &error)) { i_error("dcrypt_key_id_public failed: %s", error); } else { const char *v2_hash = binary_to_hex(hash->data, hash->used); printf("v2 hash: %s\n", v2_hash); if (key_type == DCRYPT_KEY_EC) { buffer_set_used_size(hash, 0); if (!dcrypt_key_id_public_old(pub_key, hash, &error)) { i_error("dcrypt_key_id_public_old failed: %s", error); } else { const char *v1_hash = binary_to_hex(hash->data, hash->used); printf("v1 hash: %s\n", v1_hash); } } } dcrypt_key_unref_public(&pub_key); } static void dcrypt_dump_private_key_metadata(const char *buf) { const char *error = NULL; struct dcrypt_private_key *priv_key; bool ret = dcrypt_key_load_private(&priv_key, buf, NULL, NULL, &error); if (ret == FALSE) { i_error("dcrypt_key_load_private failed: %s", error); return; } enum dcrypt_key_type key_type = dcrypt_key_type_private(priv_key); if (key_type == DCRYPT_KEY_RSA) printf("key type: DCRYPT_KEY_RSA\n"); else if (key_type == DCRYPT_KEY_EC) printf("key type: DCRYPT_KEY_EC\n"); string_t *hash = t_str_new(128); if (!dcrypt_key_id_private(priv_key, "sha256", hash, &error)) { i_error("dcrypt_key_id_private failed: %s", error); } else { const char *v2_hash = binary_to_hex(hash->data, hash->used); printf("v2 hash: %s\n", v2_hash); if (key_type == DCRYPT_KEY_EC) { buffer_set_used_size(hash, 0); if (!dcrypt_key_id_private_old(priv_key, hash, &error)) { i_error("dcrypt_key_id_private_old failed: %s", error); } else { const char *v1_hash = binary_to_hex(hash->data, hash->used); printf("v1 hash: %s\n", v1_hash); } } } dcrypt_key_unref_private(&priv_key); } static bool dcrypt_key_dump_metadata(const char *filename, bool print) { bool ret = TRUE; int fd = open(filename, O_RDONLY); if (fd < 0) { if (print) i_error("open(%s) failed: %m", filename); return FALSE; } char buf[KEY_BUF_SIZE+1]; ssize_t res = read(fd, buf, KEY_BUF_SIZE); if (res < 0) { if (print) i_error("read(%s) failed: %m", filename); i_close_fd(&fd); return FALSE; } i_close_fd(&fd); buf[res] = '\0'; enum dcrypt_key_format format; enum dcrypt_key_version version; enum dcrypt_key_kind kind; enum dcrypt_key_encryption_type encryption_type; const char *encryption_key_hash; const char *key_hash; const char *error; ret = dcrypt_key_string_get_info(buf, &format, &version, &kind, &encryption_type, &encryption_key_hash, &key_hash, &error); if (ret == FALSE) { if (print) i_error("dcrypt_key_string_get_info failed: %s", error); return FALSE; } if (!print) return TRUE; switch (format) { case DCRYPT_FORMAT_PEM: printf("format: DCRYPT_FORMAT_PEM\n"); break; case DCRYPT_FORMAT_DOVECOT: printf("format: DCRYPT_FORMAT_DOVECOT\n"); break; case DCRYPT_FORMAT_JWK: printf("format: DCRYPT_FORMAT_JWK\n"); } switch (version) { case DCRYPT_KEY_VERSION_1: printf("version: DCRYPT_KEY_VERSION_1\n"); break; case DCRYPT_KEY_VERSION_2: printf("version: DCRYPT_KEY_VERSION_2\n"); break; case DCRYPT_KEY_VERSION_NA: printf("version: DCRYPT_KEY_VERSION_NA\n"); break; } switch (kind) { case DCRYPT_KEY_KIND_PUBLIC: printf("kind: DCRYPT_KEY_KIND_PUBLIC\n"); break; case DCRYPT_KEY_KIND_PRIVATE: printf("kind: DCRYPT_KEY_KIND_PRIVATE\n"); break; } switch (encryption_type) { case DCRYPT_KEY_ENCRYPTION_TYPE_NONE: printf("encryption_type: DCRYPT_KEY_ENCRYPTION_TYPE_NONE\n"); break; case DCRYPT_KEY_ENCRYPTION_TYPE_KEY: printf("encryption_type: DCRYPT_KEY_ENCRYPTION_TYPE_KEY\n"); break; case DCRYPT_KEY_ENCRYPTION_TYPE_PASSWORD: printf("encryption_type: DCRYPT_KEY_ENCRYPTION_TYPE_PASSWORD\n"); break; } if (encryption_key_hash != NULL) printf("encryption_key_hash: %s\n", encryption_key_hash); if (key_hash != NULL) printf("key_hash: %s\n", key_hash); const char *data = t_str_rtrim(buf, "\r\n\t "); switch (kind) { case DCRYPT_KEY_KIND_PUBLIC: dcrypt_dump_public_key_metadata(data); break; case DCRYPT_KEY_KIND_PRIVATE: if (encryption_type == DCRYPT_KEY_ENCRYPTION_TYPE_NONE) dcrypt_dump_private_key_metadata(data); break; } return TRUE; } static bool test_dump_dcrypt_key(const char *path) { if (!dcrypt_initialize("openssl", NULL, NULL)) return FALSE; bool ret = dcrypt_key_dump_metadata(path, FALSE); return ret; } static void cmd_dump_dcrypt_key(const char *path, const char *const *args ATTR_UNUSED) { const char *error = NULL; if (!dcrypt_initialize("openssl", NULL, &error)) i_fatal("dcrypt_initialize: %s", error); (void)dcrypt_key_dump_metadata(path, TRUE); } struct doveadm_cmd_dump doveadm_cmd_dump_dcrypt_key = { "dcrypt-key", test_dump_dcrypt_key, cmd_dump_dcrypt_key }; dovecot-2.3.21.1/src/doveadm/doveadm-print.c0000644000000000000000000001051214656633576015437 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "istream.h" #include "ostream.h" #include "doveadm-print-private.h" struct doveadm_print_header_context { const char *key; char *sticky_value; bool sticky; }; struct doveadm_print_context { pool_t pool; ARRAY(struct doveadm_print_header_context) headers; const struct doveadm_print_vfuncs *v; unsigned int header_idx; bool print_stream_open; }; bool doveadm_print_hide_titles = FALSE; struct ostream *doveadm_print_ostream = NULL; static struct doveadm_print_context *ctx; bool doveadm_print_is_initialized(void) { return ctx != NULL; } void doveadm_print_header(const char *key, const char *title, enum doveadm_print_header_flags flags) { struct doveadm_print_header hdr; struct doveadm_print_header_context *hdr_ctx; i_assert(title != NULL); i_zero(&hdr); hdr.key = key; hdr.title = title; hdr.flags = flags; ctx->v->header(&hdr); hdr_ctx = array_append_space(&ctx->headers); hdr_ctx->key = p_strdup(ctx->pool, key); hdr_ctx->sticky = (flags & DOVEADM_PRINT_HEADER_FLAG_STICKY) != 0; } void doveadm_print_header_simple(const char *key_title) { doveadm_print_header(key_title, key_title, 0); } unsigned int doveadm_print_get_headers_count(void) { return array_count(&ctx->headers); } static void doveadm_print_sticky_headers(void) { const struct doveadm_print_header_context *headers; unsigned int count; headers = array_get(&ctx->headers, &count); i_assert(count > 0); for (;;) { if (ctx->header_idx == count) ctx->header_idx = 0; else if (headers[ctx->header_idx].sticky) { ctx->v->print(headers[ctx->header_idx].sticky_value); ctx->header_idx++; } else { break; } } } void doveadm_print(const char *value) { i_assert(!ctx->print_stream_open); doveadm_print_sticky_headers(); ctx->v->print(value); ctx->header_idx++; } void doveadm_print_num(uintmax_t value) { T_BEGIN { doveadm_print(dec2str(value)); } T_END; } void doveadm_print_stream(const void *value, size_t size) { if (!ctx->print_stream_open) { doveadm_print_sticky_headers(); ctx->print_stream_open = TRUE; } ctx->v->print_stream(value, size); if (size == 0) { ctx->header_idx++; ctx->print_stream_open = FALSE; } } int doveadm_print_istream(struct istream *input) { const unsigned char *data; size_t size; ssize_t ret; while ((ret = i_stream_read_more(input, &data, &size)) > 0) { doveadm_print_stream(data, size); i_stream_skip(input, size); } i_assert(ret == -1); doveadm_print_stream("", 0); if (input->stream_errno != 0) { /* caller will log the error */ return -1; } return 0; } void doveadm_print_sticky(const char *key, const char *value) { struct doveadm_print_header_context *hdr; if (ctx == NULL) { /* command doesn't really print anything */ return; } array_foreach_modifiable(&ctx->headers, hdr) { if (strcmp(hdr->key, key) == 0) { i_free(hdr->sticky_value); hdr->sticky_value = i_strdup(value); return; } } i_unreached(); } void doveadm_print_flush(void) { if (ctx != NULL && ctx->v->flush != NULL) ctx->v->flush(); o_stream_uncork(doveadm_print_ostream); o_stream_cork(doveadm_print_ostream); } void doveadm_print_unstick_headers(void) { struct doveadm_print_header_context *hdr; if (ctx != NULL) { array_foreach_modifiable(&ctx->headers, hdr) hdr->sticky = FALSE; } } void doveadm_print_init(const char *name) { pool_t pool; unsigned int i; if (ctx != NULL) { /* already forced the type */ return; } pool = pool_alloconly_create("doveadm print", 1024); ctx = p_new(pool, struct doveadm_print_context, 1); ctx->pool = pool; p_array_init(&ctx->headers, pool, 16); for (i = 0; doveadm_print_vfuncs_all[i] != NULL; i++) { if (strcmp(doveadm_print_vfuncs_all[i]->name, name) == 0) { ctx->v = doveadm_print_vfuncs_all[i]; break; } } if (ctx->v == NULL) i_fatal("Unknown print formatter: %s", name); if (ctx->v->init != NULL) ctx->v->init(); } void doveadm_print_deinit(void) { struct doveadm_print_header_context *hdr; if (ctx == NULL) return; if (ctx->v->flush != NULL && doveadm_print_ostream != NULL) { ctx->v->flush(); o_stream_uncork(doveadm_print_ostream); } if (ctx->v->deinit != NULL) ctx->v->deinit(); array_foreach_modifiable(&ctx->headers, hdr) i_free(hdr->sticky_value); pool_unref(&ctx->pool); ctx = NULL; } dovecot-2.3.21.1/src/doveadm/doveadm-dump.c0000644000000000000000000001015414656633576015252 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "istream.h" #include "istream-multiplex.h" #include "doveadm.h" #include "doveadm-dump.h" #include #include static ARRAY(const struct doveadm_cmd_dump *) dumps; void doveadm_dump_register(const struct doveadm_cmd_dump *dump) { array_push_back(&dumps, &dump); } static const struct doveadm_cmd_dump * dump_find_name(const char *name) { const struct doveadm_cmd_dump *dump; array_foreach_elem(&dumps, dump) { if (strcmp(dump->name, name) == 0) return dump; } return NULL; } static const struct doveadm_cmd_dump * dump_find_test(const char *path) { const struct doveadm_cmd_dump *dump; array_foreach_elem(&dumps, dump) { if (dump->test != NULL && dump->test(path)) return dump; } return NULL; } static void cmd_dump(struct doveadm_cmd_context *cctx) { const struct doveadm_cmd_dump *dump; const char *path, *type = NULL, *const *args = NULL; const char *no_args = NULL; if (!doveadm_cmd_param_str(cctx, "path", &path)) help_ver2(&doveadm_cmd_dump); (void)doveadm_cmd_param_str(cctx, "type", &type); (void)doveadm_cmd_param_array(cctx, "args", &args); dump = type != NULL ? dump_find_name(type) : dump_find_test(path); if (dump == NULL) { if (type != NULL) { print_dump_types(); i_fatal_status(EX_USAGE, "Unknown type: %s", type); } else { i_fatal_status(EX_DATAERR, "Can't autodetect file type: %s", path); } } else { if (type == NULL) printf("Detected file type: %s\n", dump->name); } dump->cmd(path, args != NULL ? args : &no_args); } struct doveadm_cmd_ver2 doveadm_cmd_dump = { .name = "dump", .cmd = cmd_dump, .usage = "[-t ] []", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('t', "type", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', "path", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', "args", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; static void cmd_dump_multiplex(const char *path, const char *const *args ATTR_UNUSED) { const unsigned int channels_count = 256; struct istream *file_input, *channels[channels_count]; const unsigned char *data; size_t size; unsigned int i; file_input = i_stream_create_file(path, IO_BLOCK_SIZE); /* A bit kludgy: istream-multiplex returns 0 if a wrong channel is being read from. This causes a panic with blocking istreams. Work around this by assuming that the file istream isn't blocking. */ file_input->blocking = FALSE; channels[0] = i_stream_create_multiplex(file_input, IO_BLOCK_SIZE); i_stream_unref(&file_input); for (i = 1; i < channels_count; i++) channels[i] = i_stream_multiplex_add_channel(channels[0], i); bool have_input; do { have_input = FALSE; for (i = 0; i < channels_count; i++) { if (i_stream_read_more(channels[i], &data, &size) > 0) { printf("CHANNEL %u: %zu bytes:\n", i, size); fwrite(data, 1, size, stdout); printf("\n"); have_input = TRUE; i_stream_skip(channels[i], size); } } } while (have_input); if (channels[0]->stream_errno != 0) i_error("read() failed: %s", i_stream_get_error(channels[0])); for (i = 0; i < channels_count; i++) i_stream_unref(&channels[i]); } struct doveadm_cmd_dump doveadm_cmd_dump_multiplex = { "multiplex", NULL, cmd_dump_multiplex }; static const struct doveadm_cmd_dump *dumps_builtin[] = { &doveadm_cmd_dump_dbox, &doveadm_cmd_dump_index, &doveadm_cmd_dump_log, &doveadm_cmd_dump_mailboxlog, &doveadm_cmd_dump_thread, &doveadm_cmd_dump_zlib, &doveadm_cmd_dump_dcrypt_file, &doveadm_cmd_dump_dcrypt_key, &doveadm_cmd_dump_multiplex, }; void print_dump_types(void) { unsigned int i; fprintf(stderr, "Available dump types: %s", dumps_builtin[0]->name); for (i = 1; i < N_ELEMENTS(dumps_builtin); i++) fprintf(stderr, " %s", dumps_builtin[i]->name); fprintf(stderr, "\n"); } void doveadm_dump_init(void) { unsigned int i; i_array_init(&dumps, N_ELEMENTS(dumps_builtin) + 8); for (i = 0; i < N_ELEMENTS(dumps_builtin); i++) doveadm_dump_register(dumps_builtin[i]); } void doveadm_dump_deinit(void) { array_free(&dumps); } dovecot-2.3.21.1/src/doveadm/doveadm-mail-search.c0000644000000000000000000000544214656633576016476 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "mail-storage.h" #include "doveadm-print.h" #include "doveadm-mailbox-list-iter.h" #include "doveadm-mail-iter.h" #include "doveadm-mail.h" #include static int cmd_search_box(struct doveadm_mail_cmd_context *ctx, const struct mailbox_info *info) { struct doveadm_mail_iter *iter; struct mailbox *box; struct mail *mail; struct mailbox_metadata metadata; const char *guid_str; int ret = 0; if (doveadm_mail_iter_init(ctx, info, ctx->search_args, 0, NULL, DOVEADM_MAIL_ITER_FLAG_STOP_WITH_CLIENT, &iter) < 0) return -1; box = doveadm_mail_iter_get_mailbox(iter); if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) < 0) { i_error("Couldn't get mailbox '%s' GUID: %s", mailbox_get_vname(box), mailbox_get_last_internal_error(box, NULL)); ret = -1; doveadm_mail_failed_mailbox(ctx, box); } else { guid_str = guid_128_to_string(metadata.guid); while (doveadm_mail_iter_next(iter, &mail)) { doveadm_print(guid_str); T_BEGIN { doveadm_print(dec2str(mail->uid)); } T_END; } } if (doveadm_mail_iter_deinit(&iter) < 0) ret = -1; return ret; } static int cmd_search_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user) { const enum mailbox_list_iter_flags iter_flags = MAILBOX_LIST_ITER_NO_AUTO_BOXES | MAILBOX_LIST_ITER_RETURN_NO_FLAGS; struct doveadm_mailbox_list_iter *iter; const struct mailbox_info *info; int ret = 0; iter = doveadm_mailbox_list_iter_init(ctx, user, ctx->search_args, iter_flags); while ((info = doveadm_mailbox_list_iter_next(iter)) != NULL) T_BEGIN { if (cmd_search_box(ctx, info) < 0) ret = -1; } T_END; if (doveadm_mailbox_list_iter_deinit(&iter) < 0) ret = -1; return ret; } static void cmd_search_init(struct doveadm_mail_cmd_context *ctx, const char *const args[]) { if (args[0] == NULL) doveadm_mail_help_name("search"); doveadm_print_header("mailbox-guid", "mailbox-guid", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); doveadm_print_header("uid", "uid", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE); ctx->search_args = doveadm_mail_build_search_args(args); } static struct doveadm_mail_cmd_context *cmd_search_alloc(void) { struct doveadm_mail_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context); ctx->v.init = cmd_search_init; ctx->v.run = cmd_search_run; doveadm_print_init(DOVEADM_PRINT_TYPE_FLOW); return ctx; } struct doveadm_cmd_ver2 doveadm_cmd_search_ver2 = { .name = "search", .mail_cmd = cmd_search_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-mailbox-list-iter.c0000644000000000000000000001241514656633576017654 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "str.h" #include "mail-search.h" #include "mail-namespace.h" #include "mailbox-list.h" #include "doveadm-mail.h" #include "doveadm-mailbox-list-iter.h" struct doveadm_mailbox_list_iter { struct mail_user *user; struct doveadm_mail_cmd_context *ctx; struct mail_search_args *search_args; enum mailbox_list_iter_flags iter_flags; struct mailbox_list_iterate_context *iter; struct mailbox_info info; ARRAY_TYPE(const_string) patterns; unsigned int pattern_idx; bool only_selectable; }; static bool search_args_get_mailbox_patterns(const struct mail_search_arg *args, ARRAY_TYPE(const_string) *patterns, bool *have_guid, bool *have_wildcards) { const struct mail_search_arg *subargs; for (; args != NULL; args = args->next) { switch (args->type) { case SEARCH_OR: /* we don't currently try to optimize OR. */ break; case SEARCH_SUB: case SEARCH_INTHREAD: subargs = args->value.subargs; for (; subargs != NULL; subargs = subargs->next) { if (!search_args_get_mailbox_patterns(subargs, patterns, have_guid, have_wildcards)) return FALSE; } break; case SEARCH_MAILBOX_GLOB: *have_wildcards = TRUE; /* fall through */ case SEARCH_MAILBOX: if (args->match_not) { array_clear(patterns); return FALSE; } array_push_back(patterns, &args->value.str); break; case SEARCH_MAILBOX_GUID: *have_guid = TRUE; break; default: break; } } return TRUE; } static struct doveadm_mailbox_list_iter * doveadm_mailbox_list_iter_init_nsmask(struct doveadm_mail_cmd_context *ctx, struct mail_user *user, struct mail_search_args *search_args, enum mailbox_list_iter_flags iter_flags, enum mail_namespace_type ns_mask) { static const char *all_pattern = "*"; struct doveadm_mailbox_list_iter *iter; bool have_guid = FALSE, have_wildcards = FALSE; iter = i_new(struct doveadm_mailbox_list_iter, 1); iter->ctx = ctx; iter->search_args = search_args; iter->user = user; i_array_init(&iter->patterns, 16); (void)search_args_get_mailbox_patterns(search_args->args, &iter->patterns, &have_guid, &have_wildcards); if (array_count(&iter->patterns) == 0) { iter_flags |= MAILBOX_LIST_ITER_SKIP_ALIASES; if (have_guid) { ns_mask |= MAIL_NAMESPACE_TYPE_SHARED | MAIL_NAMESPACE_TYPE_PUBLIC; } array_push_back(&iter->patterns, &all_pattern); } else if (have_wildcards) { iter_flags |= MAILBOX_LIST_ITER_STAR_WITHIN_NS; ns_mask |= MAIL_NAMESPACE_TYPE_SHARED | MAIL_NAMESPACE_TYPE_PUBLIC; } else { /* just return the listed mailboxes without actually iterating through. this also allows accessing mailboxes without lookup ACL right */ return iter; } array_append_zero(&iter->patterns); iter->only_selectable = TRUE; iter->iter_flags = iter_flags; iter->iter = mailbox_list_iter_init_namespaces(user->namespaces, array_front(&iter->patterns), ns_mask, iter_flags); return iter; } struct doveadm_mailbox_list_iter * doveadm_mailbox_list_iter_init(struct doveadm_mail_cmd_context *ctx, struct mail_user *user, struct mail_search_args *search_args, enum mailbox_list_iter_flags iter_flags) { enum mail_namespace_type ns_mask = MAIL_NAMESPACE_TYPE_PRIVATE; return doveadm_mailbox_list_iter_init_nsmask(ctx, user, search_args, iter_flags, ns_mask); } struct doveadm_mailbox_list_iter * doveadm_mailbox_list_iter_full_init(struct doveadm_mail_cmd_context *ctx, struct mail_user *user, struct mail_search_args *search_args, enum mailbox_list_iter_flags iter_flags) { enum mail_namespace_type ns_mask = MAIL_NAMESPACE_TYPE_MASK_ALL; struct doveadm_mailbox_list_iter *iter; iter = doveadm_mailbox_list_iter_init_nsmask(ctx, user, search_args, iter_flags, ns_mask); iter->only_selectable = FALSE; return iter; } int doveadm_mailbox_list_iter_deinit(struct doveadm_mailbox_list_iter **_iter) { struct doveadm_mailbox_list_iter *iter = *_iter; enum mail_error error; int ret; *_iter = NULL; if (iter->iter == NULL) ret = 0; else if ((ret = mailbox_list_iter_deinit(&iter->iter)) < 0) { i_error("Listing mailboxes failed: %s", mailbox_list_get_last_internal_error(iter->user->namespaces->list, &error)); doveadm_mail_failed_error(iter->ctx, error); } array_free(&iter->patterns); i_free(iter); return ret; } const struct mailbox_info * doveadm_mailbox_list_iter_next(struct doveadm_mailbox_list_iter *iter) { const struct mailbox_info *info; const char *const *patterns; unsigned int count; while (iter->iter == NULL) { patterns = array_get(&iter->patterns, &count); if (iter->pattern_idx == count) return NULL; iter->info.vname = patterns[iter->pattern_idx++]; iter->info.ns = mail_namespace_find(iter->user->namespaces, iter->info.vname); return &iter->info; } while ((info = mailbox_list_iter_next(iter->iter)) != NULL) { char sep = mail_namespace_get_sep(info->ns); if ((info->flags & (MAILBOX_NOSELECT | MAILBOX_NONEXISTENT)) != 0) { if (iter->only_selectable) continue; } if (mail_search_args_match_mailbox(iter->search_args, info->vname, sep)) break; } return info; } dovecot-2.3.21.1/src/doveadm/doveadm-cmd.h0000644000000000000000000001325514656633576015062 00000000000000#ifndef DOVEADM_CMD_H #define DOVEADM_CMD_H #include "net.h" #define DOVEADM_CMD_PARAMS_START .parameters = (const struct doveadm_cmd_param[]){ #define DOVEADM_CMD_PARAM(optP, nameP, typeP, flagP ) { .short_opt = optP, .name = nameP, .type = typeP, .flags = flagP }, #define DOVEADM_CMD_PARAMS_END { .short_opt = '\0', .name = NULL, .type = CMD_PARAM_BOOL, .flags = CMD_PARAM_FLAG_NONE } } struct doveadm_cmd_ver2; struct doveadm_cmd_context; struct doveadm_mail_cmd_context; typedef void doveadm_command_t(int argc, char *argv[]); typedef enum { CMD_PARAM_BOOL = 0, /* value will contain 1 (not pointer) */ CMD_PARAM_INT64, /* ditto but contains number (not pointer) */ CMD_PARAM_IP, /* value contains struct ip_addr */ CMD_PARAM_STR, /* value contains const char* */ CMD_PARAM_ARRAY, /* value contains const char*[] */ CMD_PARAM_ISTREAM /* value contains struct istream* */ } doveadm_cmd_param_t; typedef enum { CMD_PARAM_FLAG_NONE = 0x0, CMD_PARAM_FLAG_POSITIONAL = 0x1, CMD_PARAM_FLAG_DO_NOT_EXPOSE = 0x2, } doveadm_cmd_param_flag_t; typedef enum { CMD_FLAG_NONE = 0x0, CMD_FLAG_HIDDEN = 0x1, CMD_FLAG_NO_PRINT = 0x2, /* Don't parse any -options for the command. */ CMD_FLAG_NO_OPTIONS = 0x4, /* Prevent GNU getopt() from finding options after the first non-option is seen (e.g. "-1 arg -2" would parse -1 but not -2 as option). */ CMD_FLAG_NO_UNORDERED_OPTIONS = 0x8, } doveadm_cmd_flag_t; struct doveadm_cmd_param { char short_opt; const char *name; doveadm_cmd_param_t type; bool value_set; struct { bool v_bool; int64_t v_int64; const char* v_string; ARRAY_TYPE(const_string) v_array; struct ip_addr v_ip; struct istream* v_istream; } value; doveadm_cmd_param_flag_t flags; }; ARRAY_DEFINE_TYPE(doveadm_cmd_param_arr_t, struct doveadm_cmd_param); typedef void doveadm_command_ver2_t(struct doveadm_cmd_context *cctx); struct doveadm_cmd_ver2 { doveadm_command_ver2_t *cmd; struct doveadm_mail_cmd_context *(*mail_cmd)(void); const char *name; const char *usage; doveadm_cmd_flag_t flags; const struct doveadm_cmd_param *parameters; }; struct doveadm_cmd_context { const struct doveadm_cmd_ver2 *cmd; /* for help */ int argc; const struct doveadm_cmd_param *argv; const char *username; struct ip_addr local_ip, remote_ip; in_port_t local_port, remote_port; enum doveadm_client_type conn_type; struct istream *input; struct ostream *output; }; ARRAY_DEFINE_TYPE(doveadm_cmd_ver2, struct doveadm_cmd_ver2); extern ARRAY_TYPE(doveadm_cmd_ver2) doveadm_cmds_ver2; void doveadm_register_auth_commands(void); void doveadm_register_auth_server_commands(void); void doveadm_register_director_commands(void); void doveadm_register_proxy_commands(void); void doveadm_register_log_commands(void); void doveadm_register_instance_commands(void); void doveadm_register_mount_commands(void); void doveadm_register_replicator_commands(void); void doveadm_register_dict_commands(void); void doveadm_register_fs_commands(void); void doveadm_cmds_init(void); void doveadm_cmds_deinit(void); void doveadm_cmd_ver2_to_mail_cmd_wrapper(struct doveadm_cmd_context *cctx); void doveadm_cmd_register_ver2(struct doveadm_cmd_ver2 *cmd); const struct doveadm_cmd_ver2 * doveadm_cmd_find_with_args_ver2(const char *cmd_name, int *argc, const char *const *argv[]); const struct doveadm_cmd_ver2 *doveadm_cmd_find_ver2(const char *cmd_name); /* Returns FALSE if cmd_name doesn't exist, TRUE if it exists. */ bool doveadm_cmd_try_run_ver2(const char *cmd_name, int argc, const char *const argv[], struct doveadm_cmd_context *cctx); /* Returns 0 if success, -1 if parameters were invalid. */ int doveadm_cmd_run_ver2(int argc, const char *const argv[], struct doveadm_cmd_context *cctx); bool doveadm_cmd_param_bool(const struct doveadm_cmd_context *cctx, const char *name, bool *value_r); bool doveadm_cmd_param_int64(const struct doveadm_cmd_context *cctx, const char *name, int64_t *value_r); bool doveadm_cmd_param_str(const struct doveadm_cmd_context *cctx, const char *name, const char **value_r); bool doveadm_cmd_param_ip(const struct doveadm_cmd_context *cctx, const char *name, struct ip_addr *value_r); bool doveadm_cmd_param_array(const struct doveadm_cmd_context *cctx, const char *name, const char *const **value_r); bool doveadm_cmd_param_istream(const struct doveadm_cmd_context *cctx, const char *name, struct istream **value_r); void doveadm_cmd_params_clean(ARRAY_TYPE(doveadm_cmd_param_arr_t) *pargv); void doveadm_cmd_params_null_terminate_arrays(ARRAY_TYPE(doveadm_cmd_param_arr_t) *pargv); extern struct doveadm_cmd_ver2 doveadm_cmd_dump; extern struct doveadm_cmd_ver2 doveadm_cmd_service_stop_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_service_status_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_process_status_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_stop_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_reload_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_stats_dump_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_stats_add_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_stats_remove_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_mutf7; extern struct doveadm_cmd_ver2 doveadm_cmd_oldstats_reset_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_oldstats_dump_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_oldstats_top_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_penalty_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_pw; extern struct doveadm_cmd_ver2 doveadm_cmd_kick_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_who_ver2; extern struct doveadm_cmd_ver2 doveadm_cmd_sis_deduplicate; extern struct doveadm_cmd_ver2 doveadm_cmd_sis_find; extern struct doveadm_cmd_ver2 doveadm_cmd_zlibconnect; #endif dovecot-2.3.21.1/src/doveadm/doveadm-dump-mailboxlog.c0000644000000000000000000000451714656633576017413 00000000000000/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "hex-binary.h" #include "mailbox-log.h" #include "doveadm-dump.h" #include #include #include #include static int dump_record(int fd) { off_t offset; ssize_t ret; struct mailbox_log_record rec; time_t timestamp; offset = lseek(fd, 0, SEEK_CUR); ret = read(fd, &rec, sizeof(rec)); if (ret == 0) return 0; if (ret != sizeof(rec)) { i_fatal("rec read() %zu != %zu", ret, sizeof(rec)); } printf("#%"PRIuUOFF_T": ", offset); switch (rec.type) { case MAILBOX_LOG_RECORD_DELETE_MAILBOX: printf("delete-mailbox"); break; case MAILBOX_LOG_RECORD_DELETE_DIR: printf("delete-dir"); break; case MAILBOX_LOG_RECORD_RENAME: printf("rename"); break; case MAILBOX_LOG_RECORD_SUBSCRIBE: printf("subscribe"); break; case MAILBOX_LOG_RECORD_UNSUBSCRIBE: printf("unsubscribe"); break; case MAILBOX_LOG_RECORD_CREATE_DIR: printf("create-dir"); break; } printf(" %s", binary_to_hex(rec.mailbox_guid, sizeof(rec.mailbox_guid))); timestamp = be32_to_cpu_unaligned(rec.timestamp); printf(" (%s)\n", unixdate2str(timestamp)); return 1; } static void cmd_dump_mailboxlog(const char *path, const char *const *args ATTR_UNUSED) { int fd, ret; fd = open(path, O_RDONLY); if (fd < 0) i_fatal("open(%s) failed: %m", path); do { T_BEGIN { ret = dump_record(fd); } T_END; } while (ret > 0); i_close_fd(&fd); } static bool test_dump_mailboxlog(const char *path) { const char *p; int fd; struct mailbox_log_record rec; bool ret = FALSE; p = strrchr(path, '.'); if (p == NULL || strcmp(p, ".log") != 0) return FALSE; fd = open(path, O_RDONLY); if (fd == -1) return FALSE; if (read(fd, &rec, sizeof(rec)) == sizeof(rec) && rec.padding[0] == 0 && rec.padding[1] == 0 && rec.padding[2] == 0) { enum mailbox_log_record_type type = rec.type; switch (type) { case MAILBOX_LOG_RECORD_DELETE_MAILBOX: case MAILBOX_LOG_RECORD_DELETE_DIR: case MAILBOX_LOG_RECORD_RENAME: case MAILBOX_LOG_RECORD_SUBSCRIBE: case MAILBOX_LOG_RECORD_UNSUBSCRIBE: case MAILBOX_LOG_RECORD_CREATE_DIR: ret = TRUE; break; } } i_close_fd(&fd); return ret; } struct doveadm_cmd_dump doveadm_cmd_dump_mailboxlog = { "mailboxlog", test_dump_mailboxlog, cmd_dump_mailboxlog }; dovecot-2.3.21.1/src/doveadm/doveadm-stats.c0000644000000000000000000002312114656633576015441 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "net.h" #include "istream.h" #include "str.h" #include "strescape.h" #include "write-full.h" #include "master-service.h" #include "doveadm.h" #include "doveadm-print.h" #include "stats-settings.h" #include #define DOVEADM_DUMP_DEFAULT_FIELDS \ "count sum min max avg median stddev %95" #define ADD_NAME_PARAM "name" #define ADD_DESCR_PARAM "description" #define ADD_FIELDS_PARAM "fields" #define ADD_GROUPBY_PARAM "group-by" #define ADD_FILTER_PARAM "filter" #define ADD_EXPORTER_PARAM "exporter" #define ADD_EXPORTERINCL_PARAM "exporter-include" enum doveadm_dump_field_type { DOVEADM_DUMP_FIELD_TYPE_PASSTHROUGH = 0, DOVEADM_DUMP_FIELD_TYPE_STDDEV, }; struct stats_cmd_context { string_t *cmd; struct doveadm_cmd_context *cctx; struct istream *input; const char *path; void *data; }; struct dump_data { const char **fields; unsigned int field_count; enum doveadm_dump_field_type *field_types; }; struct stats_cmd_vfuncs { int (*build_cmd)(struct stats_cmd_context *ctx, const char **error_r); void (*process_response)(struct stats_cmd_context *ctx); }; static int build_stats_dump_cmd(struct stats_cmd_context *ctx, const char **error_r); static int build_stats_add_cmd(struct stats_cmd_context *ctx, const char **error_r); static int build_stats_remove_cmd(struct stats_cmd_context *ctx, const char **error_r); static void stats_dump_process_response(struct stats_cmd_context *ctx); static void stats_modify_process_response(struct stats_cmd_context *ctx); static void stats_send_cmd(struct stats_cmd_context *ctx); static struct stats_cmd_vfuncs dump_vfuncs = { .build_cmd = build_stats_dump_cmd, .process_response = stats_dump_process_response }; static struct stats_cmd_vfuncs add_vfuncs = { .build_cmd = build_stats_add_cmd, .process_response = stats_modify_process_response }; static struct stats_cmd_vfuncs remove_vfuncs = { .build_cmd = build_stats_remove_cmd, .process_response = stats_modify_process_response }; static string_t *init_stats_cmd(void) { string_t *cmd = t_str_new(128); str_append(cmd, "VERSION\tstats-reader-client\t2\t0\n"); return cmd; } static void stats_exec_cmd(struct doveadm_cmd_context *cctx, struct stats_cmd_vfuncs *vfuncs) { struct stats_cmd_context ctx; const char *build_cmd_error; ctx.cctx = cctx; if (vfuncs->build_cmd(&ctx, &build_cmd_error) < 0) { i_error("%s", build_cmd_error); return; } stats_send_cmd(&ctx); vfuncs->process_response(&ctx); i_stream_destroy(&ctx.input); } static void handle_disconnection(struct stats_cmd_context *ctx) { i_error("read(%s) failed: %s", ctx->path, i_stream_get_disconnect_reason(ctx->input)); } static void stats_send_cmd(struct stats_cmd_context *ctx) { int fd; const char *line; if (!doveadm_cmd_param_str(ctx->cctx, "socket-path", &ctx->path)) ctx->path = t_strconcat(doveadm_settings->base_dir, "/stats-reader", NULL); fd = doveadm_connect(ctx->path); net_set_nonblock(fd, FALSE); if (write_full(fd, str_data(ctx->cmd), str_len(ctx->cmd)) < 0) i_fatal("write(%s) failed %m", ctx->path); ctx->input = i_stream_create_fd_autoclose(&fd, SIZE_MAX); if ((line = i_stream_read_next_line(ctx->input)) == NULL) i_fatal("%s: Failed to read VERSION line", ctx->path); else if (!version_string_verify(line, "stats-reader-server", 2)) { i_fatal_status(EX_PROTOCOL, "%s is not a compatible stats-reader socket", ctx->path); } } static void dump_timing(const char *const **args, const enum doveadm_dump_field_type field_types[], unsigned int fields_count) { unsigned int i, args_count = str_array_length(*args); if (args_count > fields_count) args_count = fields_count; for (i = 0; i < args_count; i++) { const char *value = (*args)[i]; switch (field_types[i]) { case DOVEADM_DUMP_FIELD_TYPE_PASSTHROUGH: break; case DOVEADM_DUMP_FIELD_TYPE_STDDEV: { double variance = strtod(value, NULL); value = t_strdup_printf("%.02f", sqrt(variance)); break; } } doveadm_print(value); } *args += args_count; } static int build_stats_dump_cmd(struct stats_cmd_context *ctx, const char **error_r ATTR_UNUSED) { bool reset; struct dump_data *data = t_new(struct dump_data, 1); const char *fields_raw; const char **fields; if (!doveadm_cmd_param_bool(ctx->cctx, "reset", &reset)) reset = FALSE; if (!doveadm_cmd_param_str(ctx->cctx, "fields", &fields_raw)) fields_raw = DOVEADM_DUMP_DEFAULT_FIELDS; fields = t_strsplit_spaces(fields_raw, ", "); data->fields = fields; data->field_count = str_array_length(fields); data->field_types = t_malloc0(sizeof(enum doveadm_dump_field_type) * data->field_count); ctx->data = data; ctx->cmd = init_stats_cmd(); str_append(ctx->cmd, reset ? "DUMP-RESET" : "DUMP"); unsigned int i; for (i = 0; i < data->field_count; i++) { str_append_c(ctx->cmd, '\t'); if (strcmp(fields[i], "stddev") == 0) { data->field_types[i] = DOVEADM_DUMP_FIELD_TYPE_STDDEV; str_append(ctx->cmd, "variance"); } else { str_append_tabescaped(ctx->cmd, fields[i]); } } str_append_c(ctx->cmd, '\n'); return 0; } static void stats_dump_process_response(struct stats_cmd_context *ctx) { unsigned int i; char *line; struct dump_data *data = ctx->data; doveadm_print_init(DOVEADM_PRINT_TYPE_TAB); doveadm_print_header_simple("metric_name"); doveadm_print_header_simple("field"); for (i = 0; i < data->field_count; i++) doveadm_print_header(data->fields[i], data->fields[i], DOVEADM_PRINT_HEADER_FLAG_NUMBER); while ((line = i_stream_read_next_line(ctx->input)) != NULL) { if (line[0] == '\0') break; T_BEGIN { const char *const *args = t_strsplit_tabescaped_inplace(line); const char *metric_name = args[0]; doveadm_print(metric_name); args++; doveadm_print("duration"); dump_timing(&args, data->field_types, data->field_count); while (*args != NULL) { doveadm_print(metric_name); doveadm_print(*args); args++; dump_timing(&args, data->field_types, data->field_count); } } T_END; } if (line == NULL) handle_disconnection(ctx); } static int build_stats_add_cmd(struct stats_cmd_context *ctx, const char **error_r) { unsigned int i; const char *parameter; struct { const char *name; const char *default_val; } params[] = { { ADD_NAME_PARAM, "" }, { ADD_DESCR_PARAM, "" }, { ADD_FIELDS_PARAM, "" }, { ADD_GROUPBY_PARAM, "" }, { ADD_FILTER_PARAM, "" }, { ADD_EXPORTER_PARAM, "" }, /* Default exporter-include is to be modified together with stats-settings */ { ADD_EXPORTERINCL_PARAM, STATS_METRIC_SETTINGS_DEFAULT_EXPORTER_INCLUDE }, }; ctx->cmd = init_stats_cmd(); str_append(ctx->cmd, "METRICS-ADD"); for (i = 0; i < N_ELEMENTS(params); i++) { if (!doveadm_cmd_param_str(ctx->cctx, params[i].name, ¶meter)) parameter = params[i].default_val; if (parameter[0] == '\0' && (strcmp(params[i].name, "name") == 0 || strcmp(params[i].name, "filter") == 0)) { *error_r = t_strdup_printf("stats add: missing %s parameter", params[i].name); return -1; } str_append_c(ctx->cmd, '\t'); str_append_tabescaped(ctx->cmd, parameter); } str_append_c(ctx->cmd, '\n'); return 0; } static void stats_modify_process_response(struct stats_cmd_context *ctx) { const char *line = i_stream_read_next_line(ctx->input); if (line == NULL) { handle_disconnection(ctx); return; } if (line[0] == '-') i_error("%s", ++line); else if (line[0] != '+') i_error("Invalid response: %s", line); } static int build_stats_remove_cmd(struct stats_cmd_context *ctx, const char **error_r) { const char *name; ctx->cmd = init_stats_cmd(); str_append(ctx->cmd, "METRICS-REMOVE\t"); if (!doveadm_cmd_param_str(ctx->cctx, "name", &name)) { *error_r = "stats remove: missing name parameter"; return -1; } str_append_tabescaped(ctx->cmd, name); str_append_c(ctx->cmd, '\n'); return 0; } static void doveadm_cmd_stats_dump(struct doveadm_cmd_context *cctx) { stats_exec_cmd(cctx, &dump_vfuncs); } static void doveadm_cmd_stats_add(struct doveadm_cmd_context *cctx) { stats_exec_cmd(cctx, &add_vfuncs); } static void doveadm_cmd_stats_remove(struct doveadm_cmd_context *cctx) { stats_exec_cmd(cctx, &remove_vfuncs); } struct doveadm_cmd_ver2 doveadm_cmd_stats_dump_ver2 = { .cmd = doveadm_cmd_stats_dump, .name = "stats dump", .usage = "[-s ] [-r] [-f ]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('s', "socket-path", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('r', "reset", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('f', "fields", CMD_PARAM_STR, 0) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_stats_add_ver2 = { .cmd = doveadm_cmd_stats_add, .name = "stats add", .usage = "[--"ADD_DESCR_PARAM" ] " "[--"ADD_EXPORTER_PARAM" [--"ADD_EXPORTERINCL_PARAM" ]] " "[--"ADD_FIELDS_PARAM" ] " "[--"ADD_GROUPBY_PARAM" ] ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', ADD_NAME_PARAM, CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', ADD_FILTER_PARAM, CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAM('\0', ADD_EXPORTER_PARAM, CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', ADD_EXPORTERINCL_PARAM, CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', ADD_DESCR_PARAM, CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', ADD_FIELDS_PARAM, CMD_PARAM_STR, 0) DOVEADM_CMD_PARAM('\0', ADD_GROUPBY_PARAM, CMD_PARAM_STR, 0) DOVEADM_CMD_PARAMS_END }; struct doveadm_cmd_ver2 doveadm_cmd_stats_remove_ver2 = { .cmd = doveadm_cmd_stats_remove, .name = "stats remove", .usage = "", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('\0', "name", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-mail-fetch.c0000644000000000000000000004307414656633576016325 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "istream.h" #include "ostream.h" #include "str.h" #include "message-address.h" #include "message-size.h" #include "message-parser.h" #include "message-header-decode.h" #include "message-decoder.h" #include "imap-util.h" #include "mail-user.h" #include "mail-storage.h" #include "mail-search.h" #include "mail-namespace.h" #include "imap-msgpart.h" #include "doveadm-print.h" #include "doveadm-mail.h" #include "doveadm-mailbox-list-iter.h" #include "doveadm-mail-iter.h" #include struct fetch_cmd_context { struct doveadm_mail_cmd_context ctx; struct mail *mail; ARRAY(struct fetch_field) fields; ARRAY_TYPE(const_string) header_fields; enum mail_fetch_field wanted_fields; const struct fetch_field *cur_field; /* if print() returns -1, log this error if non-NULL. otherwise log the storage error. */ const char *print_error; }; struct fetch_field { const char *name; enum mail_fetch_field wanted_fields; int (*print)(struct fetch_cmd_context *ctx); }; static int fetch_user(struct fetch_cmd_context *ctx) { doveadm_print(ctx->ctx.cur_mail_user->username); return 0; } static int fetch_mailbox(struct fetch_cmd_context *ctx) { const char *value; if (mail_get_special(ctx->mail, MAIL_FETCH_MAILBOX_NAME, &value) < 0) return -1; doveadm_print(value); return 0; } static int fetch_mailbox_guid(struct fetch_cmd_context *ctx) { struct mailbox_metadata metadata; if (mailbox_get_metadata(ctx->mail->box, MAILBOX_METADATA_GUID, &metadata) < 0) return -1; doveadm_print(guid_128_to_string(metadata.guid)); return 0; } static int fetch_seq(struct fetch_cmd_context *ctx) { doveadm_print_num(ctx->mail->seq); return 0; } static int fetch_uid(struct fetch_cmd_context *ctx) { doveadm_print_num(ctx->mail->uid); return 0; } static int fetch_guid(struct fetch_cmd_context *ctx) { const char *value; if (mail_get_special(ctx->mail, MAIL_FETCH_GUID, &value) < 0) return -1; doveadm_print(value); return 0; } static int fetch_flags(struct fetch_cmd_context *ctx) { string_t *str = t_str_new(64); imap_write_flags(str, mail_get_flags(ctx->mail), mail_get_keywords(ctx->mail)); doveadm_print(str_c(str)); return 0; } static int fetch_modseq(struct fetch_cmd_context *ctx) { doveadm_print_num(mail_get_modseq(ctx->mail)); return 0; } static void fetch_set_istream_error(struct fetch_cmd_context *ctx, struct istream *input) { ctx->print_error = t_strdup_printf("read(%s) failed: %s", i_stream_get_name(input), i_stream_get_error(input)); } static int fetch_hdr(struct fetch_cmd_context *ctx) { struct istream *input; struct message_size hdr_size; int ret; if (mail_get_hdr_stream(ctx->mail, &hdr_size, &input) < 0) return -1; input = i_stream_create_limit(input, hdr_size.physical_size); if ((ret = doveadm_print_istream(input)) < 0) fetch_set_istream_error(ctx, input); i_stream_unref(&input); return ret; } static int fetch_hdr_field(struct fetch_cmd_context *ctx) { const char *const *value, *filter, *name = ctx->cur_field->name; string_t *str = t_str_new(256); bool add_lf = FALSE; filter = strchr(name, '.'); if (filter != NULL) name = t_strdup_until(name, filter++); if (filter != NULL && strcmp(filter, "utf8") == 0) { if (mail_get_headers_utf8(ctx->mail, name, &value) < 0) return -1; } else { if (mail_get_headers(ctx->mail, name, &value) < 0) return -1; } for (; *value != NULL; value++) { if (add_lf) str_append_c(str, '\n'); str_append(str, *value); add_lf = TRUE; } if (filter == NULL || strcmp(filter, "utf8") == 0) { /* print the header as-is */ } else if (strcmp(filter, "address") == 0 || strcmp(filter, "address_name") == 0 || strcmp(filter, "address_name.utf8") == 0) { struct message_address *addr; addr = message_address_parse(pool_datastack_create(), str_data(str), str_len(str), UINT_MAX, 0); str_truncate(str, 0); add_lf = FALSE; for (; addr != NULL; addr = addr->next) { if (add_lf) str_append_c(str, '\n'); if (strcmp(filter, "address") == 0) { if (addr->mailbox != NULL) str_append(str, addr->mailbox); if (addr->domain != NULL) { str_append_c(str, '@'); str_append(str, addr->domain); } } else if (addr->name != NULL) { if (strcmp(filter, "address_name") == 0) str_append(str, addr->name); else { message_header_decode_utf8( (const void *)addr->name, strlen(addr->name), str, NULL); } } add_lf = TRUE; } } else { i_fatal("Unknown header filter: %s", filter); } doveadm_print(str_c(str)); return 0; } static int fetch_body_field(struct fetch_cmd_context *ctx) { const char *name = ctx->cur_field->name; struct imap_msgpart *msgpart; struct imap_msgpart_open_result result; bool binary; int ret; binary = str_begins(name, "binary."); name += binary ? 7 : 5; if (imap_msgpart_parse(name, &msgpart) < 0) i_unreached(); /* we already verified this was ok */ if (binary) imap_msgpart_set_decode_to_binary(msgpart); if (imap_msgpart_open(ctx->mail, msgpart, &result) < 0) { imap_msgpart_free(&msgpart); return -1; } if ((ret = doveadm_print_istream(result.input)) < 0) fetch_set_istream_error(ctx, result.input); i_stream_unref(&result.input); imap_msgpart_free(&msgpart); return ret; } static int fetch_body(struct fetch_cmd_context *ctx) { struct istream *input; struct message_size hdr_size; int ret; if (mail_get_stream(ctx->mail, &hdr_size, NULL, &input) < 0) return -1; i_stream_skip(input, hdr_size.physical_size); if ((ret = doveadm_print_istream(input)) < 0) fetch_set_istream_error(ctx, input); return ret; } static int fetch_body_snippet(struct fetch_cmd_context *ctx) { const char *value; if (mail_get_special(ctx->mail, MAIL_FETCH_BODY_SNIPPET, &value) < 0) return -1; /* [0] contains the snippet algorithm, skip over it */ i_assert(value[0] != '\0'); doveadm_print(value + 1); return 0; } static int fetch_text(struct fetch_cmd_context *ctx) { struct istream *input; int ret; if (mail_get_stream(ctx->mail, NULL, NULL, &input) < 0) return -1; if ((ret = doveadm_print_istream(input)) < 0) fetch_set_istream_error(ctx, input); return ret; } static int fetch_text_utf8(struct fetch_cmd_context *ctx) { const struct message_parser_settings parser_set = { .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE, }; struct istream *input; struct message_parser_ctx *parser; struct message_decoder_context *decoder; struct message_block raw_block, block; struct message_part *parts; int ret = 0; if (mail_get_stream(ctx->mail, NULL, NULL, &input) < 0) return -1; parser = message_parser_init(pool_datastack_create(), input, &parser_set); decoder = message_decoder_init(NULL, 0); while ((ret = message_parser_parse_next_block(parser, &raw_block)) > 0) { if (!message_decoder_decode_next_block(decoder, &raw_block, &block)) continue; if (block.hdr == NULL) { if (block.size > 0) doveadm_print_stream(block.data, block.size); } else if (block.hdr->eoh) doveadm_print_stream("\n", 1); else { i_assert(block.hdr->name_len > 0); doveadm_print_stream(block.hdr->name, block.hdr->name_len); doveadm_print_stream(": ", 2); if (block.hdr->full_value_len > 0) { doveadm_print_stream(block.hdr->full_value, block.hdr->full_value_len); } doveadm_print_stream("\n", 1); } } i_assert(ret != 0); message_decoder_deinit(&decoder); message_parser_deinit(&parser, &parts); doveadm_print_stream("", 0); if (input->stream_errno != 0) { i_error("read(%s) failed: %s", i_stream_get_name(input), i_stream_get_error(input)); return -1; } return 0; } static int fetch_size_physical(struct fetch_cmd_context *ctx) { uoff_t size; if (mail_get_physical_size(ctx->mail, &size) < 0) return -1; doveadm_print_num(size); return 0; } static int fetch_size_virtual(struct fetch_cmd_context *ctx) { uoff_t size; if (mail_get_virtual_size(ctx->mail, &size) < 0) return -1; doveadm_print_num(size); return 0; } static int fetch_date_received(struct fetch_cmd_context *ctx) { time_t t; if (mail_get_received_date(ctx->mail, &t) < 0) return -1; doveadm_print(unixdate2str(t)); return 0; } static int fetch_date_sent(struct fetch_cmd_context *ctx) { time_t t; int tz; char chr; if (mail_get_date(ctx->mail, &t, &tz) < 0) return -1; chr = tz < 0 ? '-' : '+'; if (tz < 0) tz = -tz; doveadm_print(t_strdup_printf("%s (%c%02u%02u)", unixdate2str(t), chr, tz/60, tz%60)); return 0; } static int fetch_date_saved(struct fetch_cmd_context *ctx) { time_t t; if (mail_get_save_date(ctx->mail, &t) < 0) return -1; doveadm_print(unixdate2str(t)); return 0; } static int fetch_date_received_unixtime(struct fetch_cmd_context *ctx) { time_t t; if (mail_get_received_date(ctx->mail, &t) < 0) return -1; doveadm_print(dec2str(t)); return 0; } static int fetch_date_sent_unixtime(struct fetch_cmd_context *ctx) { time_t t; int tz; if (mail_get_date(ctx->mail, &t, &tz) < 0) return -1; doveadm_print(dec2str(t)); return 0; } static int fetch_date_saved_unixtime(struct fetch_cmd_context *ctx) { time_t t; if (mail_get_save_date(ctx->mail, &t) < 0) return -1; doveadm_print(dec2str(t)); return 0; } static int fetch_imap_envelope(struct fetch_cmd_context *ctx) { const char *value; if (mail_get_special(ctx->mail, MAIL_FETCH_IMAP_ENVELOPE, &value) < 0) return -1; doveadm_print(value); return 0; } static int fetch_imap_body(struct fetch_cmd_context *ctx) { const char *value; if (mail_get_special(ctx->mail, MAIL_FETCH_IMAP_BODY, &value) < 0) return -1; doveadm_print(value); return 0; } static int fetch_imap_bodystructure(struct fetch_cmd_context *ctx) { const char *value; if (mail_get_special(ctx->mail, MAIL_FETCH_IMAP_BODYSTRUCTURE, &value) < 0) return -1; doveadm_print(value); return 0; } static int fetch_pop3_uidl(struct fetch_cmd_context *ctx) { const char *value; if (mail_get_special(ctx->mail, MAIL_FETCH_UIDL_BACKEND, &value) < 0) return -1; doveadm_print(value); return 0; } static int fetch_pop3_order(struct fetch_cmd_context *ctx) { const char *value; if (mail_get_special(ctx->mail, MAIL_FETCH_POP3_ORDER, &value) < 0) return -1; doveadm_print(value); return 0; } static int fetch_refcount(struct fetch_cmd_context *ctx) { const char *value; if (mail_get_special(ctx->mail, MAIL_FETCH_REFCOUNT, &value) < 0) return -1; doveadm_print(value); return 0; } static int fetch_storageid(struct fetch_cmd_context *ctx) { const char *value; if (mail_get_special(ctx->mail, MAIL_FETCH_STORAGE_ID, &value) < 0) return -1; doveadm_print(value); return 0; } static const struct fetch_field fetch_fields[] = { { "user", 0, fetch_user }, { "mailbox", 0, fetch_mailbox }, { "mailbox-guid", 0, fetch_mailbox_guid }, { "seq", 0, fetch_seq }, { "uid", 0, fetch_uid }, { "guid", 0, fetch_guid }, { "flags", MAIL_FETCH_FLAGS, fetch_flags }, { "modseq", 0, fetch_modseq }, { "hdr", MAIL_FETCH_STREAM_HEADER, fetch_hdr }, { "body", MAIL_FETCH_STREAM_BODY, fetch_body }, { "body.preview", MAIL_FETCH_BODY_SNIPPET, fetch_body_snippet }, { "body.snippet", MAIL_FETCH_BODY_SNIPPET, fetch_body_snippet }, { "text", MAIL_FETCH_STREAM_HEADER | MAIL_FETCH_STREAM_BODY, fetch_text }, { "text.utf8", MAIL_FETCH_STREAM_HEADER | MAIL_FETCH_STREAM_BODY, fetch_text_utf8 }, { "size.physical", MAIL_FETCH_PHYSICAL_SIZE, fetch_size_physical }, { "size.virtual", MAIL_FETCH_VIRTUAL_SIZE, fetch_size_virtual }, { "date.received", MAIL_FETCH_RECEIVED_DATE, fetch_date_received }, { "date.sent", MAIL_FETCH_DATE, fetch_date_sent }, { "date.saved", MAIL_FETCH_SAVE_DATE, fetch_date_saved }, { "date.received.unixtime", MAIL_FETCH_RECEIVED_DATE, fetch_date_received_unixtime }, { "date.sent.unixtime", MAIL_FETCH_DATE, fetch_date_sent_unixtime }, { "date.saved.unixtime", MAIL_FETCH_SAVE_DATE, fetch_date_saved_unixtime }, { "imap.envelope", MAIL_FETCH_IMAP_ENVELOPE, fetch_imap_envelope }, { "imap.body", MAIL_FETCH_IMAP_BODY, fetch_imap_body }, { "imap.bodystructure", MAIL_FETCH_IMAP_BODYSTRUCTURE, fetch_imap_bodystructure }, { "pop3.uidl", MAIL_FETCH_UIDL_BACKEND, fetch_pop3_uidl }, { "pop3.order", MAIL_FETCH_POP3_ORDER, fetch_pop3_order }, { "refcount", MAIL_FETCH_REFCOUNT, fetch_refcount }, { "storageid", MAIL_FETCH_STORAGE_ID, fetch_storageid } }; static const struct fetch_field *fetch_field_find(const char *name) { unsigned int i; for (i = 0; i < N_ELEMENTS(fetch_fields); i++) { if (strcmp(fetch_fields[i].name, name) == 0) return &fetch_fields[i]; } return NULL; } static void print_fetch_fields(void) { unsigned int i; fprintf(stderr, "Available fetch fields: hdr. body.
binary.
%s", fetch_fields[0].name); for (i = 1; i < N_ELEMENTS(fetch_fields); i++) fprintf(stderr, " %s", fetch_fields[i].name); fprintf(stderr, "\n"); } static void parse_fetch_fields(struct fetch_cmd_context *ctx, const char *str) { const char *const *fields, *name; const struct fetch_field *field; struct fetch_field hdr_field, body_field; struct imap_msgpart *msgpart; i_zero(&hdr_field); hdr_field.print = fetch_hdr_field; i_zero(&body_field); body_field.print = fetch_body_field; t_array_init(&ctx->fields, 32); t_array_init(&ctx->header_fields, 32); fields = t_strsplit_spaces(str, " "); for (; *fields != NULL; fields++) { name = t_str_lcase(*fields); doveadm_print_header_simple(name); if ((field = fetch_field_find(name)) != NULL) { ctx->wanted_fields |= field->wanted_fields; array_push_back(&ctx->fields, field); } else if (str_begins(name, "hdr.")) { name += 4; hdr_field.name = name; array_push_back(&ctx->fields, &hdr_field); name = t_strcut(name, '.'); array_push_back(&ctx->header_fields, &name); } else if (str_begins(name, "body.") || str_begins(name, "binary.")) { bool binary = str_begins(name, "binary."); body_field.name = t_strarray_join(t_strsplit(name, ","), " "); name += binary ? 7 : 5; if (imap_msgpart_parse(name, &msgpart) < 0) { print_fetch_fields(); i_fatal("Unknown fetch section: %s", name); } array_push_back(&ctx->fields, &body_field); ctx->wanted_fields |= imap_msgpart_get_fetch_data(msgpart); imap_msgpart_free(&msgpart); } else { print_fetch_fields(); i_fatal("Unknown fetch field: %s", name); } } array_append_zero(&ctx->header_fields); } static int cmd_fetch_mail(struct fetch_cmd_context *ctx) { const struct fetch_field *field; struct mail *mail = ctx->mail; int ret = 0; array_foreach(&ctx->fields, field) { ctx->cur_field = field; if (field->print(ctx) < 0) { i_error("fetch(%s) failed for box=%s uid=%u: %s", field->name, mailbox_get_vname(mail->box), mail->uid, ctx->print_error != NULL ? ctx->print_error : mailbox_get_last_internal_error(mail->box, NULL)); doveadm_mail_failed_mailbox(&ctx->ctx, mail->box); ctx->print_error = NULL; ret = -1; } } return ret; } static int cmd_fetch_box(struct fetch_cmd_context *ctx, const struct mailbox_info *info) { struct doveadm_mail_iter *iter; int ret = 0; if (doveadm_mail_iter_init(&ctx->ctx, info, ctx->ctx.search_args, ctx->wanted_fields, array_front(&ctx->header_fields), DOVEADM_MAIL_ITER_FLAG_STOP_WITH_CLIENT, &iter) < 0) return -1; while (doveadm_mail_iter_next(iter, &ctx->mail)) { T_BEGIN { if (cmd_fetch_mail(ctx) < 0) ret = -1; } T_END; } if (doveadm_mail_iter_deinit(&iter) < 0) ret = -1; return ret; } static int cmd_fetch_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user) { struct fetch_cmd_context *ctx = (struct fetch_cmd_context *)_ctx; const enum mailbox_list_iter_flags iter_flags = MAILBOX_LIST_ITER_NO_AUTO_BOXES | MAILBOX_LIST_ITER_RETURN_NO_FLAGS; struct doveadm_mailbox_list_iter *iter; const struct mailbox_info *info; int ret = 0; iter = doveadm_mailbox_list_iter_init(_ctx, user, _ctx->search_args, iter_flags); while ((info = doveadm_mailbox_list_iter_next(iter)) != NULL) T_BEGIN { if (cmd_fetch_box(ctx, info) < 0) ret = -1; } T_END; if (doveadm_mailbox_list_iter_deinit(&iter) < 0) ret = -1; return ret; } static void cmd_fetch_init(struct doveadm_mail_cmd_context *_ctx, const char *const args[]) { struct fetch_cmd_context *ctx = (struct fetch_cmd_context *)_ctx; const char *fetch_fields = args[0]; if (fetch_fields == NULL || args[1] == NULL) doveadm_mail_help_name("fetch"); parse_fetch_fields(ctx, fetch_fields); _ctx->search_args = doveadm_mail_build_search_args(args + 1); } static struct doveadm_mail_cmd_context *cmd_fetch_alloc(void) { struct fetch_cmd_context *ctx; ctx = doveadm_mail_cmd_alloc(struct fetch_cmd_context); ctx->ctx.v.init = cmd_fetch_init; ctx->ctx.v.run = cmd_fetch_run; doveadm_print_init(DOVEADM_PRINT_TYPE_PAGER); return &ctx->ctx; } struct doveadm_cmd_ver2 doveadm_cmd_fetch_ver2 = { .name = "fetch", .mail_cmd = cmd_fetch_alloc, .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX" ", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_MAIL_COMMON DOVEADM_CMD_PARAM('\0', "field", CMD_PARAM_ARRAY, 0) DOVEADM_CMD_PARAM('\0', "fieldstr", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL | CMD_PARAM_FLAG_DO_NOT_EXPOSE) /* FIXME: horrible hack, remove me when possible */ DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/doveadm/doveadm-mutf7.c0000644000000000000000000000266214656633576015354 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "imap-utf7.h" #include "doveadm.h" #include #include static void cmd_mailbox_mutf7(struct doveadm_cmd_context *cctx) { string_t *str; const char *const *names; bool from_utf8, to_utf8; unsigned int i; if (!doveadm_cmd_param_array(cctx, "name", &names)) help_ver2(&doveadm_cmd_mailbox_mutf7); if (!doveadm_cmd_param_bool(cctx, "from-utf8", &from_utf8)) { if (!doveadm_cmd_param_bool(cctx, "to-utf8", &to_utf8)) from_utf8 = TRUE; else from_utf8 = !to_utf8; } str = t_str_new(128); for (i = 0; names[i] != NULL; i++) { str_truncate(str, 0); if (from_utf8) { if (imap_utf8_to_utf7(names[i], str) < 0) { i_error("Mailbox name not valid UTF-8: %s", names[i]); doveadm_exit_code = EX_DATAERR; } } else { if (imap_utf7_to_utf8(names[i], str) < 0) { i_error("Mailbox name not valid mUTF-7: %s", names[i]); doveadm_exit_code = EX_DATAERR; } } printf("%s\n", str_c(str)); } } struct doveadm_cmd_ver2 doveadm_cmd_mailbox_mutf7 = { .name = "mailbox mutf7", .cmd = cmd_mailbox_mutf7, .usage = "[-7|-8] [...]", DOVEADM_CMD_PARAMS_START DOVEADM_CMD_PARAM('7', "to-utf8", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('8', "from-utf8", CMD_PARAM_BOOL, 0) DOVEADM_CMD_PARAM('\0', "name", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL) DOVEADM_CMD_PARAMS_END }; dovecot-2.3.21.1/src/lib-dict/0000755000000000000000000000000014656633636012650 500000000000000dovecot-2.3.21.1/src/lib-dict/dict-transaction-memory.c0000644000000000000000000000340014656633576017510 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "dict-transaction-memory.h" void dict_transaction_memory_init(struct dict_transaction_memory_context *ctx, struct dict *dict, pool_t pool) { ctx->ctx.dict = dict; ctx->pool = pool; p_array_init(&ctx->changes, pool, 32); } void dict_transaction_memory_rollback(struct dict_transaction_context *_ctx) { struct dict_transaction_memory_context *ctx = (struct dict_transaction_memory_context *)_ctx; pool_unref(&ctx->pool); } void dict_transaction_memory_set(struct dict_transaction_context *_ctx, const char *key, const char *value) { struct dict_transaction_memory_context *ctx = (struct dict_transaction_memory_context *)_ctx; struct dict_transaction_memory_change *change; change = array_append_space(&ctx->changes); change->type = DICT_CHANGE_TYPE_SET; change->key = p_strdup(ctx->pool, key); change->value.str = p_strdup(ctx->pool, value); } void dict_transaction_memory_unset(struct dict_transaction_context *_ctx, const char *key) { struct dict_transaction_memory_context *ctx = (struct dict_transaction_memory_context *)_ctx; struct dict_transaction_memory_change *change; change = array_append_space(&ctx->changes); change->type = DICT_CHANGE_TYPE_UNSET; change->key = p_strdup(ctx->pool, key); } void dict_transaction_memory_atomic_inc(struct dict_transaction_context *_ctx, const char *key, long long diff) { struct dict_transaction_memory_context *ctx = (struct dict_transaction_memory_context *)_ctx; struct dict_transaction_memory_change *change; change = array_append_space(&ctx->changes); change->type = DICT_CHANGE_TYPE_INC; change->key = p_strdup(ctx->pool, key); change->value.diff = diff; } dovecot-2.3.21.1/src/lib-dict/dict-iter-lua.c0000644000000000000000000001074414656633576015410 00000000000000/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "dict.h" #include "dlua-script-private.h" #include "dict-lua-private.h" #include "dlua-wrapper.h" struct lua_dict_iter { pool_t pool; struct dict_iterate_context *iter; ARRAY(int) refs; int error_ref; lua_State *L; bool yielded:1; }; static void lua_dict_iter_unref(struct lua_dict_iter *iter) { const char *error; /* deinit iteration if it hasn't been done yet */ if (dict_iterate_deinit(&iter->iter, &error) < 0) { e_error(dlua_script_from_state(iter->L)->event, "Dict iteration failed: %s", error); } pool_unref(&iter->pool); } DLUA_WRAP_C_DATA(dict_iter, struct lua_dict_iter, lua_dict_iter_unref, NULL); static int lua_dict_iterate_step(lua_State *L); /* resume after a yield */ static int lua_dict_iterate_step_continue(lua_State *L, int status ATTR_UNUSED, lua_KContext ctx ATTR_UNUSED) { return lua_dict_iterate_step(L); } static void lua_dict_iterate_more(struct lua_dict_iter *iter); /* * Iteration step function * * Takes two args (a userdata state, and previous value) and returns the * next value. */ static int lua_dict_iterate_step(lua_State *L) { struct lua_dict_iter *iter; const int *refs; unsigned nrefs; DLUA_REQUIRE_ARGS(L, 2); iter = xlua_dict_iter_getptr(L, 1, NULL); iter->yielded = FALSE; lua_dict_iterate_more(iter); if (iter->iter != NULL) { /* iteration didn't end yet - yield */ return lua_dict_iterate_step_continue(L, lua_yieldk(L, 0, 0, lua_dict_iterate_step_continue), 0); } /* dict iteration ended - return first key-value pair */ refs = array_get(&iter->refs, &nrefs); i_assert(nrefs % 2 == 0); if (nrefs == 0) { if (iter->error_ref != 0) { /* dict iteration generated an error - raise it now */ lua_rawgeti(L, LUA_REGISTRYINDEX, iter->error_ref); luaL_unref(L, LUA_REGISTRYINDEX, iter->error_ref); return lua_error(L); } return 0; /* return nil */ } /* get the key & value from the registry */ lua_rawgeti(L, LUA_REGISTRYINDEX, refs[0]); lua_rawgeti(L, LUA_REGISTRYINDEX, refs[1]); luaL_unref(L, LUA_REGISTRYINDEX, refs[0]); luaL_unref(L, LUA_REGISTRYINDEX, refs[1]); array_delete(&iter->refs, 0, 2); return 2; } static void lua_dict_iterate_more(struct lua_dict_iter *iter) { const char *key, *const *values; lua_State *L = iter->L; const char *error; if (iter->iter == NULL) return; /* done iterating the dict */ while (dict_iterate_values(iter->iter, &key, &values)) { int ref; /* stash key */ lua_pushstring(L, key); ref = luaL_ref(L, LUA_REGISTRYINDEX); array_push_back(&iter->refs, &ref); /* stash values */ lua_newtable(L); for (unsigned int i = 0; values[i] != NULL; i++) { lua_pushstring(L, values[i]); lua_seti(L, -2, i + 1); } ref = luaL_ref(L, LUA_REGISTRYINDEX); array_push_back(&iter->refs, &ref); } if (dict_iterate_has_more(iter->iter)) return; if (dict_iterate_deinit(&iter->iter, &error) < 0) { lua_pushstring(L, error); iter->error_ref = luaL_ref(L, LUA_REGISTRYINDEX); } } /* dict iter callback */ static void lua_dict_iterate_callback(struct lua_dict_iter *iter) { if (iter->yielded) return; iter->yielded = TRUE; dlua_pcall_yieldable_resume(iter->L, 1); } /* * Iterate a dict at key [-(3|4),+2,e] * * Args: * 1) userdata: sturct dict *dict * 2) string: key * 3) integer: flags * 4*) string: username * * Returns: * Returns a iteration step function and dict iter userdata. * Username will be NULL if not provided in args. */ int lua_dict_iterate(lua_State *L) { enum dict_iterate_flags flags; struct lua_dict_iter *iter; struct dict *dict; const char *path, *username = NULL; pool_t pool; DLUA_REQUIRE_ARGS_IN(L, 3, 4); dict = dlua_check_dict(L, 1); path = luaL_checkstring(L, 2); flags = luaL_checkinteger(L, 3); if (lua_gettop(L) >= 4) username = luaL_checkstring(L, 4); lua_dict_check_key_prefix(L, path, username); struct dict_op_settings set = { .username = username, }; /* set up iteration */ pool = pool_alloconly_create("lua dict iter", 128); iter = p_new(pool, struct lua_dict_iter, 1); iter->pool = pool; iter->iter = dict_iterate_init(dict, &set, path, flags | DICT_ITERATE_FLAG_ASYNC); p_array_init(&iter->refs, iter->pool, 32); iter->L = L; dict_iterate_set_async_callback(iter->iter, lua_dict_iterate_callback, iter); /* push return values: func, state */ lua_pushcfunction(L, lua_dict_iterate_step); xlua_pushdict_iter(L, iter, FALSE); return 2; } dovecot-2.3.21.1/src/lib-dict/dict-transaction-memory.h0000644000000000000000000000200114656633576017511 00000000000000#ifndef DICT_TRANSACTION_MEMORY_H #define DICT_TRANSACTION_MEMORY_H #include "dict-private.h" enum dict_change_type { DICT_CHANGE_TYPE_SET, DICT_CHANGE_TYPE_UNSET, DICT_CHANGE_TYPE_INC }; struct dict_transaction_memory_change { enum dict_change_type type; const char *key; union { const char *str; long long diff; } value; }; struct dict_transaction_memory_context { struct dict_transaction_context ctx; pool_t pool; ARRAY(struct dict_transaction_memory_change) changes; }; void dict_transaction_memory_init(struct dict_transaction_memory_context *ctx, struct dict *dict, pool_t pool); void dict_transaction_memory_rollback(struct dict_transaction_context *ctx); void dict_transaction_memory_set(struct dict_transaction_context *ctx, const char *key, const char *value); void dict_transaction_memory_unset(struct dict_transaction_context *ctx, const char *key); void dict_transaction_memory_atomic_inc(struct dict_transaction_context *ctx, const char *key, long long diff); #endif dovecot-2.3.21.1/src/lib-dict/dict-fail.c0000644000000000000000000000715514656633576014603 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "dict.h" #include "dict-private.h" struct dict_iterate_context dict_iter_unsupported = { .dict = &dict_driver_fail, }; struct dict_transaction_context dict_transaction_unsupported = { .dict = &dict_driver_fail, }; static int dict_fail_init(struct dict *dict_driver ATTR_UNUSED, const char *uri ATTR_UNUSED, const struct dict_settings *set ATTR_UNUSED, struct dict **dict_r ATTR_UNUSED, const char **error_r) { *error_r = "Unsupported operation (dict does not support this feature)"; return -1; } static void dict_fail_deinit(struct dict *dict ATTR_UNUSED) { } static void dict_fail_wait(struct dict *dict ATTR_UNUSED) { } static int dict_fail_lookup(struct dict *dict ATTR_UNUSED, const struct dict_op_settings *set ATTR_UNUSED, pool_t pool ATTR_UNUSED, const char *key ATTR_UNUSED, const char **value_r ATTR_UNUSED, const char **error_r) { *error_r = "Unsupported operation (dict does not support this feature)"; return -1; } static struct dict_iterate_context * dict_fail_iterate_init(struct dict *dict ATTR_UNUSED, const struct dict_op_settings *set ATTR_UNUSED, const char *path ATTR_UNUSED, enum dict_iterate_flags flags ATTR_UNUSED) { return &dict_iter_unsupported; } static bool dict_fail_iterate(struct dict_iterate_context *ctx ATTR_UNUSED, const char **key_r ATTR_UNUSED, const char *const **values_r ATTR_UNUSED) { return FALSE; } static int dict_fail_iterate_deinit(struct dict_iterate_context *ctx ATTR_UNUSED, const char **error_r) { *error_r = "Unsupported operation (dict does not support this feature)"; return -1; } static struct dict_transaction_context *dict_fail_transaction_init(struct dict *dict ATTR_UNUSED) { return &dict_transaction_unsupported; } static void dict_fail_transaction_commit(struct dict_transaction_context *ctx ATTR_UNUSED, bool async ATTR_UNUSED, dict_transaction_commit_callback_t *callback, void *context) { struct dict_commit_result res = { .ret = DICT_COMMIT_RET_FAILED, .error = "Unsupported operation (dict does not support this feature)" }; if (callback != NULL) callback(&res, context); } static void dict_fail_transaction_rollback(struct dict_transaction_context *ctx ATTR_UNUSED) { } static void dict_fail_set(struct dict_transaction_context *ctx ATTR_UNUSED, const char *key ATTR_UNUSED, const char *value ATTR_UNUSED) { } static void dict_fail_unset(struct dict_transaction_context *ctx ATTR_UNUSED, const char *key ATTR_UNUSED) { } static void dict_fail_atomic_inc(struct dict_transaction_context *ctx ATTR_UNUSED, const char *key ATTR_UNUSED, long long diff ATTR_UNUSED) { } static bool dict_fail_switch_ioloop(struct dict *dict ATTR_UNUSED) { return TRUE; } static void dict_fail_set_timestamp(struct dict_transaction_context *ctx ATTR_UNUSED, const struct timespec *ts ATTR_UNUSED) { } struct dict dict_driver_fail = { .name = "fail", .v = { .init = dict_fail_init, .deinit = dict_fail_deinit, .wait = dict_fail_wait, .lookup = dict_fail_lookup, .iterate_init = dict_fail_iterate_init, .iterate = dict_fail_iterate, .iterate_deinit = dict_fail_iterate_deinit, .transaction_init = dict_fail_transaction_init, .transaction_commit = dict_fail_transaction_commit, .transaction_rollback = dict_fail_transaction_rollback, .set = dict_fail_set, .unset = dict_fail_unset, .atomic_inc = dict_fail_atomic_inc, .lookup_async = NULL, .switch_ioloop = dict_fail_switch_ioloop, .set_timestamp = dict_fail_set_timestamp }, }; dovecot-2.3.21.1/src/lib-dict/dict-lua.c0000644000000000000000000000526214656633576014446 00000000000000/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "dict.h" #include "dlua-script-private.h" #include "dict-lua-private.h" #include "dlua-wrapper.h" static int lua_dict_lookup(lua_State *); static luaL_Reg lua_dict_methods[] = { { "lookup", lua_dict_lookup }, { "iterate", lua_dict_iterate }, { "transaction_begin", lua_dict_transaction_begin }, { NULL, NULL }, }; /* no actual ref counting */ static void lua_dict_unref(struct dict *dict ATTR_UNUSED) { } DLUA_WRAP_C_DATA(dict, struct dict, lua_dict_unref, lua_dict_methods); static int lua_dict_async_continue(lua_State *L, int status ATTR_UNUSED, lua_KContext ctx ATTR_UNUSED) { /* * lua_dict_*_callback() already pushed the result table/nil or error * string. We simply need to return/error out. */ if (lua_istable(L, -1) || lua_isnil(L, -1)) return 1; else return lua_error(L); } static void lua_dict_lookup_callback(const struct dict_lookup_result *result, lua_State *L) { if (result->ret < 0) { lua_pushstring(L, result->error); } else if (result->ret == 0) { lua_pushnil(L); } else { unsigned int i; lua_newtable(L); for (i = 0; i < str_array_length(result->values); i++) { lua_pushstring(L, result->values[i]); lua_seti(L, -2, i + 1); } } dlua_pcall_yieldable_resume(L, 1); } void lua_dict_check_key_prefix(lua_State *L, const char *key, const char *username) { if (str_begins(key, DICT_PATH_SHARED)) ; else if (str_begins(key, DICT_PATH_PRIVATE)) { if (username == NULL || username[0] == '\0') luaL_error(L, DICT_PATH_PRIVATE" dict key prefix requires username"); } else { luaL_error(L, "Invalid dict key prefix"); } } /* * Lookup a key in dict [-(2|3),+1,e] * * Args: * 1) userdata: struct dict *dict * 2) string: key * 3*) string: username * * Returns: * If key is found, returns a table with values. If key is not found, * returns nil. * Username will be NULL if not provided in args. */ static int lua_dict_lookup(lua_State *L) { struct dict *dict; const char *key, *username = NULL; DLUA_REQUIRE_ARGS_IN(L, 2, 3); dict = xlua_dict_getptr(L, 1, NULL); key = luaL_checkstring(L, 2); if (lua_gettop(L) >= 3) username = luaL_checkstring(L, 3); lua_dict_check_key_prefix(L, key, username); struct dict_op_settings set = { .username = username, }; dict_lookup_async(dict, &set, key, lua_dict_lookup_callback, L); return lua_dict_async_continue(L, lua_yieldk(L, 0, 0, lua_dict_async_continue), 0); } void dlua_push_dict(lua_State *L, struct dict *dict) { xlua_pushdict(L, dict, FALSE); } struct dict *dlua_check_dict(lua_State *L, int idx) { return xlua_dict_getptr(L, idx, NULL); } dovecot-2.3.21.1/src/lib-dict/Makefile.in0000644000000000000000000010565514656633610014641 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ pkglibexecdir = $(libexecdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ # Internally, the dict methods yield via lua_yieldk() as implemented in Lua # 5.3 and newer. @DLUA_WITH_YIELDS_TRUE@am__append_1 = libdict_lua.la @DLUA_WITH_YIELDS_FALSE@libdict_lua_la_DEPENDENCIES = @DLUA_WITH_YIELDS_TRUE@am__append_2 = \ @DLUA_WITH_YIELDS_TRUE@ dict-lua.h \ @DLUA_WITH_YIELDS_TRUE@ dict-lua-private.h noinst_PROGRAMS = $(am__EXEEXT_1) test-dict-client$(EXEEXT) subdir = src/lib-dict ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(am__pkginc_lib_HEADERS_DIST) \ $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__EXEEXT_1 = test-dict$(EXEEXT) PROGRAMS = $(noinst_PROGRAMS) LTLIBRARIES = $(noinst_LTLIBRARIES) libdict_la_LIBADD = am__objects_1 = dict.lo dict-client.lo dict-file.lo dict-memcached.lo \ dict-memcached-ascii.lo dict-redis.lo dict-fail.lo \ dict-transaction-memory.lo am_libdict_la_OBJECTS = $(am__objects_1) libdict_la_OBJECTS = $(am_libdict_la_OBJECTS) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = am__libdict_lua_la_SOURCES_DIST = dict-lua.c dict-iter-lua.c \ dict-txn-lua.c @DLUA_WITH_YIELDS_TRUE@am_libdict_lua_la_OBJECTS = \ @DLUA_WITH_YIELDS_TRUE@ libdict_lua_la-dict-lua.lo \ @DLUA_WITH_YIELDS_TRUE@ libdict_lua_la-dict-iter-lua.lo \ @DLUA_WITH_YIELDS_TRUE@ libdict_lua_la-dict-txn-lua.lo libdict_lua_la_OBJECTS = $(am_libdict_lua_la_OBJECTS) @DLUA_WITH_YIELDS_TRUE@am_libdict_lua_la_rpath = am_test_dict_OBJECTS = test-dict.$(OBJEXT) test_dict_OBJECTS = $(am_test_dict_OBJECTS) am_test_dict_client_OBJECTS = test-dict-client.$(OBJEXT) test_dict_client_OBJECTS = $(am_test_dict_client_OBJECTS) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/dict-client.Plo \ ./$(DEPDIR)/dict-fail.Plo ./$(DEPDIR)/dict-file.Plo \ ./$(DEPDIR)/dict-memcached-ascii.Plo \ ./$(DEPDIR)/dict-memcached.Plo ./$(DEPDIR)/dict-redis.Plo \ ./$(DEPDIR)/dict-transaction-memory.Plo ./$(DEPDIR)/dict.Plo \ ./$(DEPDIR)/libdict_lua_la-dict-iter-lua.Plo \ ./$(DEPDIR)/libdict_lua_la-dict-lua.Plo \ ./$(DEPDIR)/libdict_lua_la-dict-txn-lua.Plo \ ./$(DEPDIR)/test-dict-client.Po ./$(DEPDIR)/test-dict.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(libdict_la_SOURCES) $(libdict_lua_la_SOURCES) \ $(test_dict_SOURCES) $(test_dict_client_SOURCES) DIST_SOURCES = $(libdict_la_SOURCES) \ $(am__libdict_lua_la_SOURCES_DIST) $(test_dict_SOURCES) \ $(test_dict_client_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac am__pkginc_lib_HEADERS_DIST = dict.h dict-client.h dict-private.h \ dict-transaction-memory.h dict-lua.h dict-lua-private.h am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ *) f=$$p;; \ esac; am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; am__install_max = 40 am__nobase_strip_setup = \ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` am__nobase_strip = \ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" am__nobase_list = $(am__nobase_strip_setup); \ for p in $$list; do echo "$$p $$p"; done | \ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ if (++n[$$2] == $(am__install_max)) \ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ END { for (dir in files) print dir, files[dir] }' am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' am__uninstall_files_from_dir = { \ test -z "$$files" \ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ $(am__cd) "$$dir" && rm -f $$files; }; \ } am__installdirs = "$(DESTDIR)$(pkginc_libdir)" HEADERS = $(pkginc_lib_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ noinst_LTLIBRARIES = libdict.la $(am__append_1) AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-settings base_sources = \ dict.c \ dict-client.c \ dict-file.c \ dict-memcached.c \ dict-memcached-ascii.c \ dict-redis.c \ dict-fail.c \ dict-transaction-memory.c libdict_la_SOURCES = \ $(base_sources) headers = dict.h dict-client.h dict-private.h \ dict-transaction-memory.h $(am__append_2) @DLUA_WITH_YIELDS_TRUE@libdict_lua_la_SOURCES = \ @DLUA_WITH_YIELDS_TRUE@ dict-lua.c \ @DLUA_WITH_YIELDS_TRUE@ dict-iter-lua.c \ @DLUA_WITH_YIELDS_TRUE@ dict-txn-lua.c @DLUA_WITH_YIELDS_TRUE@libdict_lua_la_CPPFLAGS = \ @DLUA_WITH_YIELDS_TRUE@ $(AM_CPPFLAGS) \ @DLUA_WITH_YIELDS_TRUE@ $(LUA_CFLAGS) \ @DLUA_WITH_YIELDS_TRUE@ -I$(top_srcdir)/src/lib-lua @DLUA_WITH_YIELDS_TRUE@libdict_lua_la_LIBADD = @DLUA_WITH_YIELDS_TRUE@libdict_lua_la_DEPENDENCIES = \ @DLUA_WITH_YIELDS_TRUE@ libdict.la pkginc_libdir = $(pkgincludedir) pkginc_lib_HEADERS = $(headers) test_programs = \ test-dict test_libs = \ ../lib-test/libtest.la \ ../lib/liblib.la test_dict_SOURCES = test-dict.c test_dict_LDADD = libdict.la $(test_libs) test_dict_DEPENDENCIES = $(noinst_LTLIBRARIES) $(test_libs) test_dict_client_SOURCES = test-dict-client.c test_dict_client_LDADD = $(noinst_LTLIBRARIES) ../lib/liblib.la test_dict_client_DEPENDENCIES = $(noinst_LTLIBRARIES) $(test_libs) all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-dict/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/lib-dict/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): clean-noinstPROGRAMS: @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list clean-noinstLTLIBRARIES: -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) @list='$(noinst_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } libdict.la: $(libdict_la_OBJECTS) $(libdict_la_DEPENDENCIES) $(EXTRA_libdict_la_DEPENDENCIES) $(AM_V_CCLD)$(LINK) $(libdict_la_OBJECTS) $(libdict_la_LIBADD) $(LIBS) libdict_lua.la: $(libdict_lua_la_OBJECTS) $(libdict_lua_la_DEPENDENCIES) $(EXTRA_libdict_lua_la_DEPENDENCIES) $(AM_V_CCLD)$(LINK) $(am_libdict_lua_la_rpath) $(libdict_lua_la_OBJECTS) $(libdict_lua_la_LIBADD) $(LIBS) test-dict$(EXEEXT): $(test_dict_OBJECTS) $(test_dict_DEPENDENCIES) $(EXTRA_test_dict_DEPENDENCIES) @rm -f test-dict$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_dict_OBJECTS) $(test_dict_LDADD) $(LIBS) test-dict-client$(EXEEXT): $(test_dict_client_OBJECTS) $(test_dict_client_DEPENDENCIES) $(EXTRA_test_dict_client_DEPENDENCIES) @rm -f test-dict-client$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_dict_client_OBJECTS) $(test_dict_client_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-client.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-fail.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-file.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-memcached-ascii.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-memcached.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-redis.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-transaction-memory.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdict_lua_la-dict-iter-lua.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdict_lua_la-dict-lua.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdict_lua_la-dict-txn-lua.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-dict-client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-dict.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< libdict_lua_la-dict-lua.lo: dict-lua.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdict_lua_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdict_lua_la-dict-lua.lo -MD -MP -MF $(DEPDIR)/libdict_lua_la-dict-lua.Tpo -c -o libdict_lua_la-dict-lua.lo `test -f 'dict-lua.c' || echo '$(srcdir)/'`dict-lua.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdict_lua_la-dict-lua.Tpo $(DEPDIR)/libdict_lua_la-dict-lua.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dict-lua.c' object='libdict_lua_la-dict-lua.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdict_lua_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdict_lua_la-dict-lua.lo `test -f 'dict-lua.c' || echo '$(srcdir)/'`dict-lua.c libdict_lua_la-dict-iter-lua.lo: dict-iter-lua.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdict_lua_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdict_lua_la-dict-iter-lua.lo -MD -MP -MF $(DEPDIR)/libdict_lua_la-dict-iter-lua.Tpo -c -o libdict_lua_la-dict-iter-lua.lo `test -f 'dict-iter-lua.c' || echo '$(srcdir)/'`dict-iter-lua.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdict_lua_la-dict-iter-lua.Tpo $(DEPDIR)/libdict_lua_la-dict-iter-lua.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dict-iter-lua.c' object='libdict_lua_la-dict-iter-lua.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdict_lua_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdict_lua_la-dict-iter-lua.lo `test -f 'dict-iter-lua.c' || echo '$(srcdir)/'`dict-iter-lua.c libdict_lua_la-dict-txn-lua.lo: dict-txn-lua.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdict_lua_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libdict_lua_la-dict-txn-lua.lo -MD -MP -MF $(DEPDIR)/libdict_lua_la-dict-txn-lua.Tpo -c -o libdict_lua_la-dict-txn-lua.lo `test -f 'dict-txn-lua.c' || echo '$(srcdir)/'`dict-txn-lua.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdict_lua_la-dict-txn-lua.Tpo $(DEPDIR)/libdict_lua_la-dict-txn-lua.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dict-txn-lua.c' object='libdict_lua_la-dict-txn-lua.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdict_lua_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libdict_lua_la-dict-txn-lua.lo `test -f 'dict-txn-lua.c' || echo '$(srcdir)/'`dict-txn-lua.c mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) @$(NORMAL_INSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ done uninstall-pkginc_libHEADERS: @$(NORMAL_UNINSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am $(MAKE) $(AM_MAKEFLAGS) check-local check: check-am all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkginc_libdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ clean-noinstPROGRAMS mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/dict-client.Plo -rm -f ./$(DEPDIR)/dict-fail.Plo -rm -f ./$(DEPDIR)/dict-file.Plo -rm -f ./$(DEPDIR)/dict-memcached-ascii.Plo -rm -f ./$(DEPDIR)/dict-memcached.Plo -rm -f ./$(DEPDIR)/dict-redis.Plo -rm -f ./$(DEPDIR)/dict-transaction-memory.Plo -rm -f ./$(DEPDIR)/dict.Plo -rm -f ./$(DEPDIR)/libdict_lua_la-dict-iter-lua.Plo -rm -f ./$(DEPDIR)/libdict_lua_la-dict-lua.Plo -rm -f ./$(DEPDIR)/libdict_lua_la-dict-txn-lua.Plo -rm -f ./$(DEPDIR)/test-dict-client.Po -rm -f ./$(DEPDIR)/test-dict.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-pkginc_libHEADERS install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/dict-client.Plo -rm -f ./$(DEPDIR)/dict-fail.Plo -rm -f ./$(DEPDIR)/dict-file.Plo -rm -f ./$(DEPDIR)/dict-memcached-ascii.Plo -rm -f ./$(DEPDIR)/dict-memcached.Plo -rm -f ./$(DEPDIR)/dict-redis.Plo -rm -f ./$(DEPDIR)/dict-transaction-memory.Plo -rm -f ./$(DEPDIR)/dict.Plo -rm -f ./$(DEPDIR)/libdict_lua_la-dict-iter-lua.Plo -rm -f ./$(DEPDIR)/libdict_lua_la-dict-lua.Plo -rm -f ./$(DEPDIR)/libdict_lua_la-dict-txn-lua.Plo -rm -f ./$(DEPDIR)/test-dict-client.Po -rm -f ./$(DEPDIR)/test-dict.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkginc_libHEADERS .MAKE: check-am install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ check-local clean clean-generic clean-libtool \ clean-noinstLTLIBRARIES clean-noinstPROGRAMS cscopelist-am \ ctags ctags-am distclean distclean-compile distclean-generic \ distclean-libtool distclean-tags distdir dvi dvi-am html \ html-am info info-am install install-am install-data \ install-data-am install-dvi install-dvi-am install-exec \ install-exec-am install-html install-html-am install-info \ install-info-am install-man install-pdf install-pdf-am \ install-pkginc_libHEADERS install-ps install-ps-am \ install-strip installcheck installcheck-am installdirs \ maintainer-clean maintainer-clean-generic mostlyclean \ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ uninstall-pkginc_libHEADERS .PRECIOUS: Makefile check-local: for bin in $(test_programs) $(check_PROGRAMS); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/lib-dict/dict-client.h0000644000000000000000000000270514656633576015147 00000000000000#ifndef DICT_CLIENT_H #define DICT_CLIENT_H #include "dict.h" #define DEFAULT_DICT_SERVER_SOCKET_FNAME "dict" #define DICT_CLIENT_PROTOCOL_MAJOR_VERSION 3 #define DICT_CLIENT_PROTOCOL_MINOR_VERSION 2 #define DICT_CLIENT_PROTOCOL_VERSION_MIN_MULTI_OK 2 #define DICT_CLIENT_MAX_LINE_LENGTH (64*1024) enum dict_protocol_cmd { /* */ DICT_PROTOCOL_CMD_HELLO = 'H', DICT_PROTOCOL_CMD_LOOKUP = 'L', /* */ DICT_PROTOCOL_CMD_ITERATE = 'I', /* */ DICT_PROTOCOL_CMD_BEGIN = 'B', /* */ DICT_PROTOCOL_CMD_COMMIT = 'C', /* */ DICT_PROTOCOL_CMD_COMMIT_ASYNC = 'D', /* */ DICT_PROTOCOL_CMD_ROLLBACK = 'R', /* */ DICT_PROTOCOL_CMD_SET = 'S', /* */ DICT_PROTOCOL_CMD_UNSET = 'U', /* */ DICT_PROTOCOL_CMD_ATOMIC_INC = 'A', /* */ DICT_PROTOCOL_CMD_TIMESTAMP = 'T', /* */ DICT_PROTOCOL_CMD_HIDE_LOG_VALUES = 'V', /* */ }; enum dict_protocol_reply { DICT_PROTOCOL_REPLY_ERROR = -1, DICT_PROTOCOL_REPLY_OK = 'O', /* */ DICT_PROTOCOL_REPLY_MULTI_OK = 'M', /* protocol v2.2+ */ DICT_PROTOCOL_REPLY_NOTFOUND = 'N', DICT_PROTOCOL_REPLY_FAIL = 'F', DICT_PROTOCOL_REPLY_WRITE_UNCERTAIN = 'W', DICT_PROTOCOL_REPLY_ASYNC_COMMIT = 'A', DICT_PROTOCOL_REPLY_ITER_FINISHED = '\0', DICT_PROTOCOL_REPLY_ASYNC_ID = '*', DICT_PROTOCOL_REPLY_ASYNC_REPLY = '+', }; #endif dovecot-2.3.21.1/src/lib-dict/dict.h0000644000000000000000000002102314656633576013665 00000000000000#ifndef DICT_H #define DICT_H #define DICT_PATH_PRIVATE "priv/" #define DICT_PATH_SHARED "shared/" struct timespec; struct dict; struct dict_iterate_context; enum dict_iterate_flags { /* Recurse to all the sub-hierarchies (e.g. iterating "foo/" will return "foo/a", but should it return "foo/a/b"?) */ DICT_ITERATE_FLAG_RECURSE = 0x01, /* Sort returned results by key */ DICT_ITERATE_FLAG_SORT_BY_KEY = 0x02, /* Sort returned results by value */ DICT_ITERATE_FLAG_SORT_BY_VALUE = 0x04, /* Don't return values, only keys */ DICT_ITERATE_FLAG_NO_VALUE = 0x08, /* Don't recurse at all. This is basically the same as dict_lookup(), but it'll return all the rows instead of only the first one. */ DICT_ITERATE_FLAG_EXACT_KEY = 0x10, /* Perform iteration asynchronously. */ DICT_ITERATE_FLAG_ASYNC = 0x20 }; enum dict_data_type { DICT_DATA_TYPE_STRING = 0, DICT_DATA_TYPE_UINT32, DICT_DATA_TYPE_LAST }; struct dict_settings { const char *base_dir; /* set to parent event, if exists */ struct event *event_parent; }; struct dict_op_settings { const char *username; /* home directory for the user, if known */ const char *home_dir; /* Hide values when logging about this transaction. */ bool hide_log_values; }; struct dict_lookup_result { int ret; /* First returned value (ret > 0) */ const char *value; /* NULL-terminated list of all returned values (ret > 0) */ const char *const *values; /* Error message for a failed lookup (ret < 0) */ const char *error; }; enum dict_commit_ret { DICT_COMMIT_RET_OK = 1, DICT_COMMIT_RET_NOTFOUND = 0, DICT_COMMIT_RET_FAILED = -1, /* write may or may not have succeeded (e.g. write timeout or disconnected from server) */ DICT_COMMIT_RET_WRITE_UNCERTAIN = -2, }; struct dict_commit_result { enum dict_commit_ret ret; const char *error; }; typedef void dict_lookup_callback_t(const struct dict_lookup_result *result, void *context); typedef void dict_iterate_callback_t(void *context); typedef void dict_transaction_commit_callback_t(const struct dict_commit_result *result, void *context); void dict_driver_register(struct dict *driver); void dict_driver_unregister(struct dict *driver); void dict_drivers_register_builtin(void); void dict_drivers_unregister_builtin(void); void dict_drivers_register_all(void); void dict_drivers_unregister_all(void); /* Open dictionary with given URI (type:data). Returns 0 if ok, -1 if URI is invalid. */ int dict_init(const char *uri, const struct dict_settings *set, struct dict **dict_r, const char **error_r); /* Close dictionary. */ void dict_deinit(struct dict **dict); /* Wait for all pending asynchronous operations to finish. */ void dict_wait(struct dict *dict); /* Switch the dict to the current ioloop. This can be used to do dict_wait() among other IO work. Returns TRUE if there is actually some work that can be waited on. */ bool dict_switch_ioloop(struct dict *dict) ATTR_NOWARN_UNUSED_RESULT; /* Lookup value for key. Set it to NULL if it's not found. Returns 1 if found, 0 if not found and -1 if lookup failed. */ int dict_lookup(struct dict *dict, const struct dict_op_settings *set, pool_t pool, const char *key, const char **value_r, const char **error_r); void dict_lookup_async(struct dict *dict, const struct dict_op_settings *set, const char *key, dict_lookup_callback_t *callback, void *context); #define dict_lookup_async(dict, set, key, callback, context) \ dict_lookup_async(dict, set, key, (dict_lookup_callback_t *)(callback), \ 1 ? (context) : \ CALLBACK_TYPECHECK(callback, \ void (*)(const struct dict_lookup_result *, typeof(context)))) /* Iterate through all values in a path. flag indicates how iteration is carried out */ struct dict_iterate_context * dict_iterate_init(struct dict *dict, const struct dict_op_settings *set, const char *path, enum dict_iterate_flags flags); /* Set async callback. Note that if dict_iterate_init() already did all the work, this callback may never be called. So after dict_iterate_init() you should call dict_iterate() in any case to see if all the results are already available. */ void dict_iterate_set_async_callback(struct dict_iterate_context *ctx, dict_iterate_callback_t *callback, void *context); #define dict_iterate_set_async_callback(ctx, callback, context) \ dict_iterate_set_async_callback(ctx, (dict_iterate_callback_t *)(callback), \ 1 ? (context) : \ CALLBACK_TYPECHECK(callback, void (*)(typeof(context)))) /* Limit how many rows will be returned by the iteration (0 = unlimited). This allows backends to optimize the query (e.g. use LIMIT 1 with SQL). */ void dict_iterate_set_limit(struct dict_iterate_context *ctx, uint64_t max_rows); /* If dict_iterate() returns FALSE, the iteration may be finished or if this is an async iteration it may be waiting for more data. If this function returns TRUE, the dict callback is called again with more data. If dict supports multiple values, dict_iterate_values() can be used to return all of them. dict_iterate() returns only the first value and ignores the rest. */ bool dict_iterate_has_more(struct dict_iterate_context *ctx); bool dict_iterate(struct dict_iterate_context *ctx, const char **key_r, const char **value_r); bool dict_iterate_values(struct dict_iterate_context *ctx, const char **key_r, const char *const **values_r); /* Returns 0 = ok, -1 = iteration failed */ int dict_iterate_deinit(struct dict_iterate_context **ctx, const char **error_r); /* Start a new dictionary transaction. */ struct dict_transaction_context * dict_transaction_begin(struct dict *dict, const struct dict_op_settings *set); /* Don't log a warning if the transaction commit took a long time. This is needed if there are no guarantees that an asynchronous commit will finish up anytime soon. Mainly useful for transactions which aren't especially important whether they finish or not. */ void dict_transaction_no_slowness_warning(struct dict_transaction_context *ctx); /* Set write timestamp for the entire transaction. This must be set before any changes are done and can't be changed afterwards. Currently only dict-sql with Cassandra backend does anything with this. */ void dict_transaction_set_timestamp(struct dict_transaction_context *ctx, const struct timespec *ts); /* Set hide_log_values for the transaction. Currently only dict-sql with Cassandra backend does anything with this. */ void dict_transaction_set_hide_log_values(struct dict_transaction_context *ctx, bool hide_log_values); /* Commit the transaction. Returns 1 if ok, 0 if dict_atomic_inc() was used on a nonexistent key, -1 if failed. */ int dict_transaction_commit(struct dict_transaction_context **ctx, const char **error_r); /* Commit the transaction, but don't wait to see if it finishes successfully. The callback is called when the transaction is finished. If it's not called by the time you want to deinitialize dict, call dict_flush() to wait for the result. */ void dict_transaction_commit_async(struct dict_transaction_context **ctx, dict_transaction_commit_callback_t *callback, void *context) ATTR_NULL(2, 3); #define dict_transaction_commit_async(ctx, callback, context) \ dict_transaction_commit_async(ctx, (dict_transaction_commit_callback_t *)(callback), \ 1 ? (context) : \ CALLBACK_TYPECHECK(callback, \ void (*)(const struct dict_commit_result *, typeof(context)))) /* Same as dict_transaction_commit_async(), but don't call a callback. */ void dict_transaction_commit_async_nocallback( struct dict_transaction_context **ctx); /* Rollback all changes made in transaction. */ void dict_transaction_rollback(struct dict_transaction_context **ctx); /* Set key=value in dictionary. */ void dict_set(struct dict_transaction_context *ctx, const char *key, const char *value); /* Unset a record in dictionary, identified by key*/ void dict_unset(struct dict_transaction_context *ctx, const char *key); /* Increase/decrease a numeric value in dictionary. Note that the value is changed when transaction is being committed, so you can't know beforehand what the value will become. The value is updated only if it already exists, otherwise commit() will return 0. */ void dict_atomic_inc(struct dict_transaction_context *ctx, const char *key, long long diff); /* Escape/unescape '/' characters in a string, so that it can be safely added into path components in dict keys. */ const char *dict_escape_string(const char *str); const char *dict_unescape_string(const char *str); #endif dovecot-2.3.21.1/src/lib-dict/dict-file.c0000644000000000000000000004366314656633576014613 00000000000000/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "hash.h" #include "str.h" #include "strescape.h" #include "home-expand.h" #include "mkdir-parents.h" #include "eacces-error.h" #include "file-lock.h" #include "file-dotlock.h" #include "nfs-workarounds.h" #include "istream.h" #include "ostream.h" #include "dict-transaction-memory.h" #include "dict-private.h" #include #include #include #include struct file_dict { struct dict dict; pool_t hash_pool; enum file_lock_method lock_method; char *path; char *home_dir; bool dict_path_checked; HASH_TABLE(char *, char *) hash; int fd; bool refreshed; }; struct file_dict_iterate_context { struct dict_iterate_context ctx; pool_t pool; struct hash_iterate_context *iter; const char *path; size_t path_len; enum dict_iterate_flags flags; const char *values[2]; const char *error; }; static struct dotlock_settings file_dict_dotlock_settings = { .timeout = 60*2, .stale_timeout = 60, .use_io_notify = TRUE }; static int file_dict_ensure_path_home_dir(struct file_dict *dict, const char *home_dir, const char **error_r) { if (null_strcmp(dict->home_dir, home_dir) == 0) return 0; if (dict->dict_path_checked) { *error_r = t_strdup_printf("home_dir changed from %s to %s " "(requested dict was: %s)", dict->home_dir, home_dir, dict->path); return -1; } char *_p = dict->path; dict->path = i_strdup(home_expand_tilde(dict->path, home_dir)); dict->home_dir = i_strdup(home_dir); i_free(_p); dict->dict_path_checked = TRUE; return 0; } static int file_dict_init(struct dict *driver, const char *uri, const struct dict_settings *set ATTR_UNUSED, struct dict **dict_r, const char **error_r) { struct file_dict *dict; const char *p, *path; dict = i_new(struct file_dict, 1); dict->lock_method = FILE_LOCK_METHOD_DOTLOCK; p = strchr(uri, ':'); if (p == NULL) { /* no parameters */ path = uri; } else { path = t_strdup_until(uri, p++); if (strcmp(p, "lock=fcntl") == 0) dict->lock_method = FILE_LOCK_METHOD_FCNTL; else if (strcmp(p, "lock=flock") == 0) dict->lock_method = FILE_LOCK_METHOD_FLOCK; else { *error_r = t_strdup_printf("Invalid parameter: %s", p+1); i_free(dict); return -1; } } /* keep the path for now, later in dict operations check if home_dir should be prepended. */ dict->path = i_strdup(path); dict->dict = *driver; dict->hash_pool = pool_alloconly_create("file dict", 1024); hash_table_create(&dict->hash, dict->hash_pool, 0, str_hash, strcmp); dict->fd = -1; *dict_r = &dict->dict; return 0; } static void file_dict_deinit(struct dict *_dict) { struct file_dict *dict = (struct file_dict *)_dict; i_close_fd_path(&dict->fd, dict->path); hash_table_destroy(&dict->hash); pool_unref(&dict->hash_pool); i_free(dict->path); i_free(dict->home_dir); i_free(dict); } static bool file_dict_need_refresh(struct file_dict *dict) { struct stat st1, st2; if (dict->dict.iter_count > 0) { /* Change nothing while there are iterators or they can crash because the hash table content recreated. */ return FALSE; } if (dict->fd == -1) return TRUE; /* Disable NFS flushing for now since it can cause unnecessary problems and there's no easy way for us to know here if mail_nfs_storage=yes. In any case it's pretty much an unsupported setting nowadays. */ /*nfs_flush_file_handle_cache(dict->path);*/ if (nfs_safe_stat(dict->path, &st1) < 0) { e_error(dict->dict.event, "stat(%s) failed: %m", dict->path); return FALSE; } if (fstat(dict->fd, &st2) < 0) { if (errno != ESTALE) e_error(dict->dict.event, "fstat(%s) failed: %m", dict->path); return TRUE; } if (st1.st_ino != st2.st_ino || !CMP_DEV_T(st1.st_dev, st2.st_dev)) { /* file changed */ return TRUE; } return FALSE; } static int file_dict_open_latest(struct file_dict *dict, const char **error_r) { int open_type; if (!file_dict_need_refresh(dict)) return 0; i_close_fd_path(&dict->fd, dict->path); open_type = dict->lock_method == FILE_LOCK_METHOD_DOTLOCK ? O_RDONLY : O_RDWR; dict->fd = open(dict->path, open_type); if (dict->fd == -1) { if (errno == ENOENT) return 0; if (errno == EACCES) *error_r = eacces_error_get("open", dict->path); else *error_r = t_strdup_printf("open(%s) failed: %m", dict->path); return -1; } dict->refreshed = FALSE; return 1; } static int file_dict_refresh(struct file_dict *dict, const char **error_r) { struct istream *input; char *key, *value; if (file_dict_open_latest(dict, error_r) < 0) return -1; if (dict->refreshed || dict->dict.iter_count > 0) return 0; hash_table_clear(dict->hash, TRUE); p_clear(dict->hash_pool); if (dict->fd != -1) { input = i_stream_create_fd(dict->fd, SIZE_MAX); while ((key = i_stream_read_next_line(input)) != NULL) { /* strdup() before the second read */ key = str_tabunescape(p_strdup(dict->hash_pool, key)); if ((value = i_stream_read_next_line(input)) == NULL) break; value = str_tabunescape(p_strdup(dict->hash_pool, value)); hash_table_update(dict->hash, key, value); } i_stream_destroy(&input); } dict->refreshed = TRUE; return 0; } static int file_dict_lookup(struct dict *_dict, const struct dict_op_settings *set, pool_t pool, const char *key, const char **value_r, const char **error_r) { struct file_dict *dict = (struct file_dict *)_dict; if (file_dict_ensure_path_home_dir(dict, set->home_dir, error_r) < 0) return -1; if (file_dict_refresh(dict, error_r) < 0) return -1; *value_r = p_strdup(pool, hash_table_lookup(dict->hash, key)); return *value_r == NULL ? 0 : 1; } static struct dict_iterate_context * file_dict_iterate_init(struct dict *_dict, const struct dict_op_settings *set ATTR_UNUSED, const char *path, enum dict_iterate_flags flags) { struct file_dict_iterate_context *ctx; struct file_dict *dict = (struct file_dict *)_dict; const char *error; pool_t pool; pool = pool_alloconly_create("file dict iterate", 256); ctx = p_new(pool, struct file_dict_iterate_context, 1); ctx->ctx.dict = _dict; ctx->pool = pool; ctx->path = p_strdup(pool, path); ctx->path_len = strlen(path); ctx->flags = flags; if (file_dict_ensure_path_home_dir(dict, set->home_dir, &error) < 0 || file_dict_refresh(dict, &error) < 0) ctx->error = p_strdup(pool, error); ctx->iter = hash_table_iterate_init(dict->hash); return &ctx->ctx; } static bool file_dict_iterate_key_matches(struct file_dict_iterate_context *ctx, const char *key) { if (strncmp(ctx->path, key, ctx->path_len) == 0) return TRUE; return FALSE; } static bool file_dict_iterate(struct dict_iterate_context *_ctx, const char **key_r, const char *const **values_r) { struct file_dict_iterate_context *ctx = (struct file_dict_iterate_context *)_ctx; char *key, *value; while (hash_table_iterate(ctx->iter, ((struct file_dict *)_ctx->dict)->hash, &key, &value)) { if (!file_dict_iterate_key_matches(ctx, key)) continue; if ((ctx->flags & DICT_ITERATE_FLAG_RECURSE) != 0) { /* match everything */ } else if ((ctx->flags & DICT_ITERATE_FLAG_EXACT_KEY) != 0) { if (key[ctx->path_len] != '\0') continue; } else { if (strchr(key + ctx->path_len, '/') != NULL) continue; } *key_r = key; ctx->values[0] = value; *values_r = ctx->values; return TRUE; } return FALSE; } static int file_dict_iterate_deinit(struct dict_iterate_context *_ctx, const char **error_r) { struct file_dict_iterate_context *ctx = (struct file_dict_iterate_context *)_ctx; int ret = ctx->error != NULL ? -1 : 0; *error_r = t_strdup(ctx->error); hash_table_iterate_deinit(&ctx->iter); pool_unref(&ctx->pool); return ret; } static struct dict_transaction_context * file_dict_transaction_init(struct dict *_dict) { struct dict_transaction_memory_context *ctx; pool_t pool; pool = pool_alloconly_create("file dict transaction", 2048); ctx = p_new(pool, struct dict_transaction_memory_context, 1); dict_transaction_memory_init(ctx, _dict, pool); return &ctx->ctx; } static void file_dict_apply_changes(struct dict_transaction_memory_context *ctx, bool *atomic_inc_not_found_r) { struct file_dict *dict = (struct file_dict *)ctx->ctx.dict; const char *tmp; char *key, *value, *old_value; char *orig_key, *orig_value; const struct dict_transaction_memory_change *change; size_t new_len; long long diff; array_foreach(&ctx->changes, change) { if (hash_table_lookup_full(dict->hash, change->key, &orig_key, &orig_value)) { key = orig_key; old_value = orig_value; } else { key = NULL; old_value = NULL; } value = NULL; switch (change->type) { case DICT_CHANGE_TYPE_INC: if (old_value == NULL) { *atomic_inc_not_found_r = TRUE; break; } if (str_to_llong(old_value, &diff) < 0) i_unreached(); diff += change->value.diff; tmp = t_strdup_printf("%lld", diff); new_len = strlen(tmp); if (old_value == NULL || new_len > strlen(old_value)) value = p_strdup(dict->hash_pool, tmp); else { memcpy(old_value, tmp, new_len + 1); value = old_value; } /* fall through */ case DICT_CHANGE_TYPE_SET: if (key == NULL) key = p_strdup(dict->hash_pool, change->key); if (value == NULL) { value = p_strdup(dict->hash_pool, change->value.str); } hash_table_update(dict->hash, key, value); break; case DICT_CHANGE_TYPE_UNSET: if (old_value != NULL) hash_table_remove(dict->hash, key); break; } } } static int fd_copy_stat_permissions(const struct stat *src_st, int dest_fd, const char *dest_path, const char **error_r) { struct stat dest_st; if (fstat(dest_fd, &dest_st) < 0) { *error_r = t_strdup_printf("fstat(%s) failed: %m", dest_path); return -1; } if (src_st->st_gid != dest_st.st_gid && ((src_st->st_mode & 0070) >> 3 != (src_st->st_mode & 0007))) { /* group has different permissions from world. preserve the group. */ if (fchown(dest_fd, (uid_t)-1, src_st->st_gid) < 0) { *error_r = t_strdup_printf("fchown(%s, -1, %s) failed: %m", dest_path, dec2str(src_st->st_gid)); return -1; } } if ((src_st->st_mode & 07777) != (dest_st.st_mode & 07777)) { if (fchmod(dest_fd, src_st->st_mode & 07777) < 0) { *error_r = t_strdup_printf("fchmod(%s, %o) failed: %m", dest_path, (int)(src_st->st_mode & 0777)); return -1; } } return 0; } static int fd_copy_permissions(int src_fd, const char *src_path, int dest_fd, const char *dest_path, const char **error_r) { struct stat src_st; if (fstat(src_fd, &src_st) < 0) { *error_r = t_strdup_printf("fstat(%s) failed: %m", src_path); return -1; } return fd_copy_stat_permissions(&src_st, dest_fd, dest_path, error_r); } static int fd_copy_parent_dir_permissions(const char *src_path, int dest_fd, const char *dest_path, const char **error_r) { struct stat src_st; const char *src_dir, *p; p = strrchr(src_path, '/'); if (p == NULL) src_dir = "."; else src_dir = t_strdup_until(src_path, p); if (stat(src_dir, &src_st) < 0) { *error_r = t_strdup_printf("stat(%s) failed: %m", src_dir); return -1; } src_st.st_mode &= 0666; return fd_copy_stat_permissions(&src_st, dest_fd, dest_path, error_r); } static int file_dict_mkdir(struct file_dict *dict, const char **error_r) { const char *path, *p, *root; struct stat st; mode_t mode = 0700; p = strrchr(dict->path, '/'); if (p == NULL) return 0; path = t_strdup_until(dict->path, p); if (stat_first_parent(path, &root, &st) < 0) { if (errno == EACCES) *error_r = eacces_error_get("stat", root); else *error_r = t_strdup_printf("stat(%s) failed: %m", root); return -1; } if ((st.st_mode & S_ISGID) != 0) { /* preserve parent's permissions when it has setgid bit */ mode = st.st_mode; } if (mkdir_parents(path, mode) < 0 && errno != EEXIST) { if (errno == EACCES) *error_r = eacces_error_get("mkdir_parents", path); else *error_r = t_strdup_printf("mkdir_parents(%s) failed: %m", path); return -1; } return 0; } static int file_dict_lock(struct file_dict *dict, struct file_lock **lock_r, const char **error_r) { int ret; const char *error; if (file_dict_open_latest(dict, error_r) < 0) return -1; if (dict->fd == -1) { /* quota file doesn't exist yet, we need to create it */ dict->fd = open(dict->path, O_CREAT | O_RDWR, 0600); if (dict->fd == -1 && errno == ENOENT) { if (file_dict_mkdir(dict, error_r) < 0) return -1; dict->fd = open(dict->path, O_CREAT | O_RDWR, 0600); } if (dict->fd == -1) { if (errno == EACCES) *error_r = eacces_error_get("creat", dict->path); else { *error_r = t_strdup_printf( "creat(%s) failed: %m", dict->path); } return -1; } if (fd_copy_parent_dir_permissions(dict->path, dict->fd, dict->path, &error) < 0) e_error(dict->dict.event, "%s", error); } *lock_r = NULL; struct file_lock_settings lock_set = { .lock_method = dict->lock_method, }; do { file_lock_free(lock_r); if (file_wait_lock(dict->fd, dict->path, F_WRLCK, &lock_set, file_dict_dotlock_settings.timeout, lock_r, &error) <= 0) { *error_r = t_strdup_printf( "file_wait_lock(%s) failed: %s", dict->path, error); return -1; } /* check again if we need to reopen the file because it was just replaced */ } while ((ret = file_dict_open_latest(dict, error_r)) > 0); return ret < 0 ? -1 : 0; } static int file_dict_write_changes(struct dict_transaction_memory_context *ctx, bool *atomic_inc_not_found_r, const char **error_r) { struct file_dict *dict = (struct file_dict *)ctx->ctx.dict; struct dotlock *dotlock = NULL; struct file_lock *lock = NULL; const char *temp_path = NULL; const char *error; struct hash_iterate_context *iter; struct ostream *output; char *key, *value; string_t *str; int fd = -1; *atomic_inc_not_found_r = FALSE; if (file_dict_ensure_path_home_dir(dict, ctx->ctx.set.home_dir, error_r) < 0) return -1; switch (dict->lock_method) { case FILE_LOCK_METHOD_FCNTL: case FILE_LOCK_METHOD_FLOCK: if (file_dict_lock(dict, &lock, error_r) < 0) return -1; temp_path = t_strdup_printf("%s.tmp", dict->path); fd = creat(temp_path, 0600); if (fd == -1) { *error_r = t_strdup_printf( "dict-file: creat(%s) failed: %m", temp_path); file_unlock(&lock); return -1; } break; case FILE_LOCK_METHOD_DOTLOCK: fd = file_dotlock_open(&file_dict_dotlock_settings, dict->path, 0, &dotlock); if (fd == -1 && errno == ENOENT) { if (file_dict_mkdir(dict, error_r) < 0) return -1; fd = file_dotlock_open(&file_dict_dotlock_settings, dict->path, 0, &dotlock); } if (fd == -1) { *error_r = t_strdup_printf( "dict-file: file_dotlock_open(%s) failed: %m", dict->path); return -1; } temp_path = file_dotlock_get_lock_path(dotlock); break; } /* refresh once more now that we're locked */ if (file_dict_refresh(dict, error_r) < 0) { if (dotlock != NULL) file_dotlock_delete(&dotlock); else { i_close_fd(&fd); file_unlock(&lock); } return -1; } if (dict->fd != -1) { /* preserve the permissions */ if (fd_copy_permissions(dict->fd, dict->path, fd, temp_path, &error) < 0) e_error(ctx->ctx.event, "%s", error); } else { /* get initial permissions from parent directory */ if (fd_copy_parent_dir_permissions(dict->path, fd, temp_path, &error) < 0) e_error(ctx->ctx.event, "%s", error); } file_dict_apply_changes(ctx, atomic_inc_not_found_r); output = o_stream_create_fd(fd, 0); o_stream_cork(output); iter = hash_table_iterate_init(dict->hash); str = t_str_new(256); while (hash_table_iterate(iter, dict->hash, &key, &value)) { str_truncate(str, 0); str_append_tabescaped(str, key); str_append_c(str, '\n'); str_append_tabescaped(str, value); str_append_c(str, '\n'); o_stream_nsend(output, str_data(str), str_len(str)); } hash_table_iterate_deinit(&iter); if (o_stream_finish(output) <= 0) { *error_r = t_strdup_printf("write(%s) failed: %s", temp_path, o_stream_get_error(output)); o_stream_destroy(&output); if (dotlock != NULL) file_dotlock_delete(&dotlock); else { i_close_fd(&fd); file_unlock(&lock); } return -1; } o_stream_destroy(&output); if (dotlock != NULL) { if (file_dotlock_replace(&dotlock, DOTLOCK_REPLACE_FLAG_DONT_CLOSE_FD) < 0) { *error_r = t_strdup_printf("file_dotlock_replace() failed: %m"); i_close_fd(&fd); return -1; } } else { if (rename(temp_path, dict->path) < 0) { *error_r = t_strdup_printf("rename(%s, %s) failed: %m", temp_path, dict->path); file_unlock(&lock); i_close_fd(&fd); return -1; } /* dict->fd is locked, not the new fd. We're closing dict->fd so we can just free the lock struct. */ file_lock_free(&lock); } i_close_fd(&dict->fd); dict->fd = fd; return 0; } static void file_dict_transaction_commit(struct dict_transaction_context *_ctx, bool async ATTR_UNUSED, dict_transaction_commit_callback_t *callback, void *context) { struct dict_transaction_memory_context *ctx = (struct dict_transaction_memory_context *)_ctx; struct dict_commit_result result; bool atomic_inc_not_found; i_zero(&result); if (file_dict_write_changes(ctx, &atomic_inc_not_found, &result.error) < 0) result.ret = DICT_COMMIT_RET_FAILED; else if (atomic_inc_not_found) result.ret = DICT_COMMIT_RET_NOTFOUND; else result.ret = DICT_COMMIT_RET_OK; pool_unref(&ctx->pool); callback(&result, context); } struct dict dict_driver_file = { .name = "file", { .init = file_dict_init, .deinit = file_dict_deinit, .lookup = file_dict_lookup, .iterate_init = file_dict_iterate_init, .iterate = file_dict_iterate, .iterate_deinit = file_dict_iterate_deinit, .transaction_init = file_dict_transaction_init, .transaction_commit = file_dict_transaction_commit, .transaction_rollback = dict_transaction_memory_rollback, .set = dict_transaction_memory_set, .unset = dict_transaction_memory_unset, .atomic_inc = dict_transaction_memory_atomic_inc, } }; dovecot-2.3.21.1/src/lib-dict/dict-private.h0000644000000000000000000000731614656633576015346 00000000000000#ifndef DICT_PRIVATE_H #define DICT_PRIVATE_H #include #include "dict.h" struct ioloop; struct dict_vfuncs { int (*init)(struct dict *dict_driver, const char *uri, const struct dict_settings *set, struct dict **dict_r, const char **error_r); void (*deinit)(struct dict *dict); void (*wait)(struct dict *dict); int (*lookup)(struct dict *dict, const struct dict_op_settings *set, pool_t pool, const char *key, const char **value_r, const char **error_r); struct dict_iterate_context * (*iterate_init)(struct dict *dict, const struct dict_op_settings *set, const char *path, enum dict_iterate_flags flags); bool (*iterate)(struct dict_iterate_context *ctx, const char **key_r, const char *const **values_r); int (*iterate_deinit)(struct dict_iterate_context *ctx, const char **error_r); struct dict_transaction_context *(*transaction_init)(struct dict *dict); /* call the callback before returning if non-async commits */ void (*transaction_commit)(struct dict_transaction_context *ctx, bool async, dict_transaction_commit_callback_t *callback, void *context); void (*transaction_rollback)(struct dict_transaction_context *ctx); void (*set)(struct dict_transaction_context *ctx, const char *key, const char *value); void (*unset)(struct dict_transaction_context *ctx, const char *key); void (*atomic_inc)(struct dict_transaction_context *ctx, const char *key, long long diff); void (*lookup_async)(struct dict *dict, const struct dict_op_settings *set, const char *key, dict_lookup_callback_t *callback, void *context); bool (*switch_ioloop)(struct dict *dict); void (*set_timestamp)(struct dict_transaction_context *ctx, const struct timespec *ts); void (*set_hide_log_values)(struct dict_transaction_context *ctx, bool hide_log_values); }; struct dict_commit_callback_ctx; struct dict_op_settings_private { char *username; char *home_dir; bool hide_log_values; }; struct dict { const char *name; struct dict_vfuncs v; unsigned int iter_count; unsigned int transaction_count; struct dict_transaction_context *transactions; int refcount; struct event *event; struct ioloop *ioloop, *prev_ioloop; struct dict_commit_callback_ctx *commits; }; struct dict_iterate_context { struct dict *dict; struct event *event; struct dict_op_settings_private set; enum dict_iterate_flags flags; dict_iterate_callback_t *async_callback; void *async_context; uint64_t row_count, max_rows; bool has_more:1; }; struct dict_transaction_context { struct dict *dict; struct dict_op_settings_private set; struct dict_transaction_context *prev, *next; struct event *event; struct timespec timestamp; bool changed:1; bool no_slowness_warning:1; }; void dict_transaction_commit_async_noop_callback( const struct dict_commit_result *result, void *context); extern struct dict dict_driver_client; extern struct dict dict_driver_file; extern struct dict dict_driver_fs; extern struct dict dict_driver_memcached; extern struct dict dict_driver_memcached_ascii; extern struct dict dict_driver_redis; extern struct dict dict_driver_cdb; extern struct dict dict_driver_fail; extern struct dict_iterate_context dict_iter_unsupported; extern struct dict_transaction_context dict_transaction_unsupported; void dict_pre_api_callback(struct dict *dict); void dict_post_api_callback(struct dict *dict); /* Duplicate an object of type dict_op_settings. Used for initializing/freeing iterator and transaction contexts. */ void dict_op_settings_dup(const struct dict_op_settings *source, struct dict_op_settings_private *dest_r); void dict_op_settings_private_free(struct dict_op_settings_private *set); #endif dovecot-2.3.21.1/src/lib-dict/test-dict.c0000644000000000000000000000211314656633576014634 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "dict-private.h" #include "test-common.h" struct dict dict_driver_client; struct dict dict_driver_file; struct dict dict_driver_memcached; struct dict dict_driver_memcached_ascii; struct dict dict_driver_redis; static void test_dict_escape(void) { static const char *input[] = { "", "", "foo", "foo", "foo\\", "foo\\\\", "foo\\bar", "foo\\\\bar", "\\bar", "\\\\bar", "foo/", "foo\\|", "foo/bar", "foo\\|bar", "/bar", "\\|bar", "////", "\\|\\|\\|\\|", "/", "\\|" }; unsigned int i; test_begin("dict escape"); for (i = 0; i < N_ELEMENTS(input); i += 2) { test_assert(strcmp(dict_escape_string(input[i]), input[i+1]) == 0); test_assert(strcmp(dict_unescape_string(input[i+1]), input[i]) == 0); } test_assert(strcmp(dict_unescape_string("x\\"), "x") == 0); test_assert(strcmp(dict_unescape_string("\\"), "") == 0); test_end(); } int main(void) { static void (*const test_functions[])(void) = { test_dict_escape, NULL }; return test_run(test_functions); } dovecot-2.3.21.1/src/lib-dict/dict-memcached-ascii.c0000644000000000000000000004531114656633576016660 00000000000000/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING memcached_ascii */ #include "lib.h" #include "array.h" #include "str.h" #include "istream.h" #include "ostream.h" #include "connection.h" #include "dict-transaction-memory.h" #include "dict-private.h" #define MEMCACHED_DEFAULT_PORT 11211 #define MEMCACHED_DEFAULT_LOOKUP_TIMEOUT_MSECS (1000*30) #define DICT_USERNAME_SEPARATOR '/' enum memcached_ascii_input_state { /* GET: expecting VALUE or END */ MEMCACHED_INPUT_STATE_GET, /* SET: expecting STORED / NOT_STORED */ MEMCACHED_INPUT_STATE_STORED, /* DELETE: expecting DELETED */ MEMCACHED_INPUT_STATE_DELETED, /* (INCR+ADD)/DECR: expecting number / NOT_FOUND / STORED / NOT_STORED */ MEMCACHED_INPUT_STATE_INCRDECR }; struct memcached_ascii_connection { struct connection conn; struct memcached_ascii_dict *dict; string_t *reply_str; unsigned int reply_bytes_left; bool value_received; bool value_waiting_end; }; struct memcached_ascii_dict_reply { unsigned int reply_count; dict_transaction_commit_callback_t *callback; void *context; }; struct dict_memcached_ascii_commit_ctx { struct memcached_ascii_dict *dict; struct dict_transaction_memory_context *memctx; string_t *str; dict_transaction_commit_callback_t *callback; void *context; }; struct memcached_ascii_dict { struct dict dict; struct ip_addr ip; char *key_prefix; in_port_t port; unsigned int timeout_msecs; struct timeout *to; struct memcached_ascii_connection conn; ARRAY(enum memcached_ascii_input_state) input_states; ARRAY(struct memcached_ascii_dict_reply) replies; }; static struct connection_list *memcached_ascii_connections; static void memcached_ascii_callback(struct memcached_ascii_dict *dict, const struct memcached_ascii_dict_reply *reply, const struct dict_commit_result *result) { if (reply->callback != NULL) { if (dict->dict.prev_ioloop != NULL) { /* Don't let callback see that we've created our internal ioloop in case it wants to add some ios or timeouts. */ current_ioloop = dict->dict.prev_ioloop; } reply->callback(result, reply->context); if (dict->dict.prev_ioloop != NULL) current_ioloop = dict->dict.ioloop; } } static void memcached_ascii_disconnected(struct memcached_ascii_connection *conn, const char *reason) { const struct dict_commit_result result = { DICT_COMMIT_RET_FAILED, reason }; const struct memcached_ascii_dict_reply *reply; connection_disconnect(&conn->conn); if (conn->dict->dict.ioloop != NULL) io_loop_stop(conn->dict->dict.ioloop); array_foreach(&conn->dict->replies, reply) memcached_ascii_callback(conn->dict, reply, &result); array_clear(&conn->dict->replies); array_clear(&conn->dict->input_states); conn->reply_bytes_left = 0; } static void memcached_ascii_conn_destroy(struct connection *_conn) { struct memcached_ascii_connection *conn = (struct memcached_ascii_connection *)_conn; memcached_ascii_disconnected(conn, connection_disconnect_reason(_conn)); } static bool memcached_ascii_input_value(struct memcached_ascii_connection *conn) { const unsigned char *data; size_t size; data = i_stream_get_data(conn->conn.input, &size); if (size > conn->reply_bytes_left) size = conn->reply_bytes_left; conn->reply_bytes_left -= size; str_append_data(conn->reply_str, data, size); i_stream_skip(conn->conn.input, size); if (conn->reply_bytes_left > 0) return FALSE; /* finished. drop the trailing CRLF */ str_truncate(conn->reply_str, str_len(conn->reply_str)-2); conn->value_received = TRUE; return TRUE; } static int memcached_ascii_input_reply_read(struct memcached_ascii_dict *dict, const char **error_r) { struct memcached_ascii_connection *conn = &dict->conn; const enum memcached_ascii_input_state *states; const char *line, *p; unsigned int count; long long num; if (conn->reply_bytes_left > 0) { /* continue reading bulk reply */ if (!memcached_ascii_input_value(conn)) return 0; conn->value_waiting_end = TRUE; } else if (conn->value_waiting_end) { conn->value_waiting_end = FALSE; } else { str_truncate(conn->reply_str, 0); conn->value_received = FALSE; } line = i_stream_next_line(conn->conn.input); if (line == NULL) return 0; states = array_get(&dict->input_states, &count); if (count == 0) { *error_r = t_strdup_printf( "memcached_ascii: Unexpected input (expected nothing): %s", line); return -1; } switch (states[0]) { case MEMCACHED_INPUT_STATE_GET: /* VALUE END */ if (str_begins(line, "VALUE ")) { p = strrchr(line, ' '); if (str_to_uint(p+1, &conn->reply_bytes_left) < 0) break; conn->reply_bytes_left += 2; /* CRLF */ return memcached_ascii_input_reply_read(dict, error_r); } else if (strcmp(line, "END") == 0) return 1; break; case MEMCACHED_INPUT_STATE_STORED: if (strcmp(line, "STORED") != 0 && strcmp(line, "NOT_STORED") != 0) break; return 1; case MEMCACHED_INPUT_STATE_DELETED: if (strcmp(line, "DELETED") != 0) break; return 1; case MEMCACHED_INPUT_STATE_INCRDECR: if (strcmp(line, "NOT_FOUND") != 0 && strcmp(line, "STORED") != 0 && strcmp(line, "NOT_STORED") != 0 && str_to_llong(line, &num) < 0) break; return 1; } *error_r = t_strdup_printf( "memcached_ascii: Unexpected input (state=%d): %s", states[0], line); return -1; } static int memcached_ascii_input_reply(struct memcached_ascii_dict *dict, const char **error_r) { const struct dict_commit_result result = { DICT_COMMIT_RET_OK, NULL }; struct memcached_ascii_dict_reply *replies; unsigned int count; int ret; if ((ret = memcached_ascii_input_reply_read(dict, error_r)) <= 0) return ret; /* finished a reply */ array_pop_front(&dict->input_states); replies = array_get_modifiable(&dict->replies, &count); i_assert(count > 0); i_assert(replies[0].reply_count > 0); if (--replies[0].reply_count == 0) { memcached_ascii_callback(dict, &replies[0], &result); array_pop_front(&dict->replies); } return 1; } static void memcached_ascii_conn_input(struct connection *_conn) { struct memcached_ascii_connection *conn = (struct memcached_ascii_connection *)_conn; const char *error; int ret; switch (i_stream_read(_conn->input)) { case 0: return; case -1: memcached_ascii_disconnected(conn, i_stream_get_disconnect_reason(_conn->input)); return; default: break; } while ((ret = memcached_ascii_input_reply(conn->dict, &error)) > 0) ; if (ret < 0) memcached_ascii_disconnected(conn, error); io_loop_stop(conn->dict->dict.ioloop); } static int memcached_ascii_input_wait(struct memcached_ascii_dict *dict, const char **error_r) { i_assert(io_loop_is_empty(dict->dict.ioloop)); dict->dict.prev_ioloop = current_ioloop; io_loop_set_current(dict->dict.ioloop); if (dict->to != NULL) dict->to = io_loop_move_timeout(&dict->to); connection_switch_ioloop(&dict->conn.conn); io_loop_run(dict->dict.ioloop); io_loop_set_current(dict->dict.prev_ioloop); dict->dict.prev_ioloop = NULL; if (dict->to != NULL) dict->to = io_loop_move_timeout(&dict->to); connection_switch_ioloop(&dict->conn.conn); i_assert(io_loop_is_empty(dict->dict.ioloop)); if (dict->conn.conn.fd_in == -1) { *error_r = "memcached_ascii: Communication failure"; return -1; } return 0; } static void memcached_ascii_input_timeout(struct memcached_ascii_dict *dict) { const char *reason = t_strdup_printf( "memcached_ascii: Request timed out in %u.%03u secs", dict->timeout_msecs/1000, dict->timeout_msecs%1000); memcached_ascii_disconnected(&dict->conn, reason); } static int memcached_ascii_wait_replies(struct memcached_ascii_dict *dict, const char **error_r) { int ret = 0; dict->to = timeout_add(dict->timeout_msecs, memcached_ascii_input_timeout, dict); while (array_count(&dict->input_states) > 0) { i_assert(array_count(&dict->replies) > 0); if ((ret = memcached_ascii_input_reply(dict, error_r)) != 0) { if (ret < 0) memcached_ascii_disconnected(&dict->conn, *error_r); break; } ret = memcached_ascii_input_wait(dict, error_r); if (ret != 0) break; } timeout_remove(&dict->to); return ret < 0 ? -1 : 0; } static int memcached_ascii_wait(struct memcached_ascii_dict *dict, const char **error_r) { int ret; i_assert(dict->conn.conn.fd_in != -1); if (dict->conn.conn.input == NULL) { /* waiting for connection to finish */ dict->to = timeout_add(dict->timeout_msecs, memcached_ascii_input_timeout, dict); ret = memcached_ascii_input_wait(dict, error_r); timeout_remove(&dict->to); if (ret < 0) return -1; } if (memcached_ascii_wait_replies(dict, error_r) < 0) return -1; i_assert(array_count(&dict->input_states) == 0); i_assert(array_count(&dict->replies) == 0); return 0; } static void memcached_ascii_conn_connected(struct connection *_conn, bool success) { struct memcached_ascii_connection *conn = (struct memcached_ascii_connection *)_conn; if (!success) { e_error(conn->conn.event, "connect() failed: %m"); } if (conn->dict->dict.ioloop != NULL) io_loop_stop(conn->dict->dict.ioloop); } static const struct connection_settings memcached_ascii_conn_set = { .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, .client = TRUE }; static const struct connection_vfuncs memcached_ascii_conn_vfuncs = { .destroy = memcached_ascii_conn_destroy, .input = memcached_ascii_conn_input, .client_connected = memcached_ascii_conn_connected }; static const char *memcached_ascii_escape_username(const char *username) { const char *p; string_t *str = t_str_new(64); for (p = username; *p != '\0'; p++) { switch (*p) { case DICT_USERNAME_SEPARATOR: str_append(str, "\\-"); break; case '\\': str_append(str, "\\\\"); break; default: str_append_c(str, *p); } } return str_c(str); } static int memcached_ascii_dict_init(struct dict *driver, const char *uri, const struct dict_settings *set, struct dict **dict_r, const char **error_r) { struct memcached_ascii_dict *dict; const char *const *args; struct ioloop *old_ioloop = current_ioloop; int ret = 0; if (memcached_ascii_connections == NULL) { memcached_ascii_connections = connection_list_init(&memcached_ascii_conn_set, &memcached_ascii_conn_vfuncs); } dict = i_new(struct memcached_ascii_dict, 1); if (net_addr2ip("127.0.0.1", &dict->ip) < 0) i_unreached(); dict->port = MEMCACHED_DEFAULT_PORT; dict->timeout_msecs = MEMCACHED_DEFAULT_LOOKUP_TIMEOUT_MSECS; dict->key_prefix = i_strdup(""); args = t_strsplit(uri, ":"); for (; *args != NULL; args++) { if (str_begins(*args, "host=")) { if (net_addr2ip(*args+5, &dict->ip) < 0) { *error_r = t_strdup_printf("Invalid IP: %s", *args+5); ret = -1; } } else if (str_begins(*args, "port=")) { if (net_str2port(*args+5, &dict->port) < 0) { *error_r = t_strdup_printf("Invalid port: %s", *args+5); ret = -1; } } else if (str_begins(*args, "prefix=")) { i_free(dict->key_prefix); dict->key_prefix = i_strdup(*args + 7); } else if (str_begins(*args, "timeout_msecs=")) { if (str_to_uint(*args+14, &dict->timeout_msecs) < 0) { *error_r = t_strdup_printf( "Invalid timeout_msecs: %s", *args+14); ret = -1; } } else { *error_r = t_strdup_printf("Unknown parameter: %s", *args); ret = -1; } } if (ret < 0) { i_free(dict->key_prefix); i_free(dict); return -1; } dict->conn.conn.event_parent = set->event_parent; connection_init_client_ip(memcached_ascii_connections, &dict->conn.conn, NULL, &dict->ip, dict->port); event_set_append_log_prefix(dict->conn.conn.event, "memcached: "); dict->dict = *driver; dict->conn.reply_str = str_new(default_pool, 256); dict->conn.dict = dict; i_array_init(&dict->input_states, 4); i_array_init(&dict->replies, 4); dict->dict.ioloop = io_loop_create(); io_loop_set_current(old_ioloop); *dict_r = &dict->dict; return 0; } static void memcached_ascii_dict_deinit(struct dict *_dict) { struct memcached_ascii_dict *dict = (struct memcached_ascii_dict *)_dict; struct ioloop *old_ioloop = current_ioloop; const char *error; if (array_count(&dict->input_states) > 0) { if (memcached_ascii_wait(dict, &error) < 0) i_error("%s", error); } connection_deinit(&dict->conn.conn); io_loop_set_current(dict->dict.ioloop); io_loop_destroy(&dict->dict.ioloop); io_loop_set_current(old_ioloop); str_free(&dict->conn.reply_str); array_free(&dict->replies); array_free(&dict->input_states); i_free(dict->key_prefix); i_free(dict); if (memcached_ascii_connections->connections == NULL) connection_list_deinit(&memcached_ascii_connections); } static int memcached_ascii_connect(struct memcached_ascii_dict *dict, const char **error_r) { if (dict->conn.conn.input != NULL) return 0; if (dict->conn.conn.fd_in == -1) { if (connection_client_connect(&dict->conn.conn) < 0) { *error_r = t_strdup_printf( "memcached_ascii: Couldn't connect to %s:%u", net_ip2addr(&dict->ip), dict->port); return -1; } } return memcached_ascii_wait(dict, error_r); } static const char * memcached_ascii_dict_get_full_key(struct memcached_ascii_dict *dict, const char *username, const char *key) { if (str_begins(key, DICT_PATH_SHARED)) key += strlen(DICT_PATH_SHARED); else if (str_begins(key, DICT_PATH_PRIVATE)) { if (strchr(username, DICT_USERNAME_SEPARATOR) == NULL) { key = t_strdup_printf("%s%c%s", username, DICT_USERNAME_SEPARATOR, key + strlen(DICT_PATH_PRIVATE)); } else { /* escape the username */ key = t_strdup_printf("%s%c%s", memcached_ascii_escape_username(username), DICT_USERNAME_SEPARATOR, key + strlen(DICT_PATH_PRIVATE)); } } else { i_unreached(); } if (*dict->key_prefix != '\0') key = t_strconcat(dict->key_prefix, key, NULL); return key; } static int memcached_ascii_dict_lookup(struct dict *_dict, const struct dict_op_settings *set, pool_t pool, const char *key, const char **value_r, const char **error_r) { struct memcached_ascii_dict *dict = (struct memcached_ascii_dict *)_dict; struct memcached_ascii_dict_reply *reply; enum memcached_ascii_input_state state = MEMCACHED_INPUT_STATE_GET; if (memcached_ascii_connect(dict, error_r) < 0) return -1; key = memcached_ascii_dict_get_full_key(dict, set->username, key); o_stream_nsend_str(dict->conn.conn.output, t_strdup_printf("get %s\r\n", key)); array_push_back(&dict->input_states, &state); reply = array_append_space(&dict->replies); reply->reply_count = 1; if (memcached_ascii_wait(dict, error_r) < 0) return -1; *value_r = p_strdup(pool, str_c(dict->conn.reply_str)); return dict->conn.value_received ? 1 : 0; } static struct dict_transaction_context * memcached_ascii_transaction_init(struct dict *_dict) { struct dict_transaction_memory_context *ctx; pool_t pool; pool = pool_alloconly_create("file dict transaction", 2048); ctx = p_new(pool, struct dict_transaction_memory_context, 1); dict_transaction_memory_init(ctx, _dict, pool); return &ctx->ctx; } static void memcached_send_change(struct dict_memcached_ascii_commit_ctx *ctx, const struct dict_op_settings_private *set, const struct dict_transaction_memory_change *change) { enum memcached_ascii_input_state state; const char *key, *value; key = memcached_ascii_dict_get_full_key(ctx->dict, set->username, change->key); str_truncate(ctx->str, 0); switch (change->type) { case DICT_CHANGE_TYPE_SET: state = MEMCACHED_INPUT_STATE_STORED; str_printfa(ctx->str, "set %s 0 0 %zu\r\n%s\r\n", key, strlen(change->value.str), change->value.str); break; case DICT_CHANGE_TYPE_UNSET: state = MEMCACHED_INPUT_STATE_DELETED; str_printfa(ctx->str, "delete %s\r\n", key); break; case DICT_CHANGE_TYPE_INC: state = MEMCACHED_INPUT_STATE_INCRDECR; if (change->value.diff > 0) { str_printfa(ctx->str, "incr %s %lld\r\n", key, change->value.diff); array_push_back(&ctx->dict->input_states, &state); /* same kludge as with append */ value = t_strdup_printf("%lld", change->value.diff); str_printfa(ctx->str, "add %s 0 0 %u\r\n%s\r\n", key, (unsigned int)strlen(value), value); } else { str_printfa(ctx->str, "decr %s %lld\r\n", key, -change->value.diff); } break; } array_push_back(&ctx->dict->input_states, &state); o_stream_nsend(ctx->dict->conn.conn.output, str_data(ctx->str), str_len(ctx->str)); } static int memcached_ascii_transaction_send(struct dict_memcached_ascii_commit_ctx *ctx, const struct dict_op_settings_private *set, const char **error_r) { struct memcached_ascii_dict *dict = ctx->dict; struct memcached_ascii_dict_reply *reply; const struct dict_transaction_memory_change *changes; unsigned int i, count, old_state_count; if (memcached_ascii_connect(dict, error_r) < 0) return -1; old_state_count = array_count(&dict->input_states); changes = array_get(&ctx->memctx->changes, &count); i_assert(count > 0); o_stream_cork(dict->conn.conn.output); for (i = 0; i < count; i++) T_BEGIN { memcached_send_change(ctx, set, &changes[i]); } T_END; o_stream_uncork(dict->conn.conn.output); reply = array_append_space(&dict->replies); reply->callback = ctx->callback; reply->context = ctx->context; reply->reply_count = array_count(&dict->input_states) - old_state_count; return 0; } static void memcached_ascii_transaction_commit(struct dict_transaction_context *_ctx, bool async, dict_transaction_commit_callback_t *callback, void *context) { struct dict_transaction_memory_context *ctx = (struct dict_transaction_memory_context *)_ctx; struct memcached_ascii_dict *dict = (struct memcached_ascii_dict *)_ctx->dict; struct dict_memcached_ascii_commit_ctx commit_ctx; struct dict_commit_result result = { DICT_COMMIT_RET_OK, NULL }; const struct dict_op_settings_private *set = &_ctx->set; if (_ctx->changed) { i_zero(&commit_ctx); commit_ctx.dict = dict; commit_ctx.memctx = ctx; commit_ctx.callback = callback; commit_ctx.context = context; commit_ctx.str = str_new(default_pool, 128); result.ret = memcached_ascii_transaction_send(&commit_ctx, set, &result.error); str_free(&commit_ctx.str); if (async && result.ret == 0) { pool_unref(&ctx->pool); return; } if (result.ret == 0) { if (memcached_ascii_wait(dict, &result.error) < 0) result.ret = -1; } } callback(&result, context); pool_unref(&ctx->pool); } struct dict dict_driver_memcached_ascii = { .name = "memcached_ascii", { .init = memcached_ascii_dict_init, .deinit = memcached_ascii_dict_deinit, .lookup = memcached_ascii_dict_lookup, .transaction_init = memcached_ascii_transaction_init, .transaction_commit = memcached_ascii_transaction_commit, .transaction_rollback = dict_transaction_memory_rollback, .set = dict_transaction_memory_set, .unset = dict_transaction_memory_unset, .atomic_inc = dict_transaction_memory_atomic_inc, } }; dovecot-2.3.21.1/src/lib-dict/dict-lua-private.h0000644000000000000000000000025014656633576016113 00000000000000#ifndef DICT_LUA_PRIVATE_H #define DICT_LUA_PRIVATE_H #include "dict-lua.h" int lua_dict_iterate(lua_State *l); int lua_dict_transaction_begin(lua_State *l); #endif dovecot-2.3.21.1/src/lib-dict/dict-lua.h0000644000000000000000000000060114656633576014443 00000000000000#ifndef DICT_LUA_H #define DICT_LUA_H #ifdef DLUA_WITH_YIELDS /* * Internally, the dict methods yield via lua_yieldk() as implemented in Lua * 5.3 and newer. */ void lua_dict_check_key_prefix(lua_State *L, const char *key, const char *username); void dlua_push_dict(lua_State *L, struct dict *dict); struct dict *dlua_check_dict(lua_State *L, int idx); #endif #endif dovecot-2.3.21.1/src/lib-dict/Makefile.am0000644000000000000000000000275514656633576014640 00000000000000noinst_LTLIBRARIES = \ libdict.la AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-settings base_sources = \ dict.c \ dict-client.c \ dict-file.c \ dict-memcached.c \ dict-memcached-ascii.c \ dict-redis.c \ dict-fail.c \ dict-transaction-memory.c libdict_la_SOURCES = \ $(base_sources) headers = \ dict.h \ dict-client.h \ dict-private.h \ dict-transaction-memory.h # Internally, the dict methods yield via lua_yieldk() as implemented in Lua # 5.3 and newer. if DLUA_WITH_YIELDS noinst_LTLIBRARIES += libdict_lua.la libdict_lua_la_SOURCES = \ dict-lua.c \ dict-iter-lua.c \ dict-txn-lua.c libdict_lua_la_CPPFLAGS = \ $(AM_CPPFLAGS) \ $(LUA_CFLAGS) \ -I$(top_srcdir)/src/lib-lua libdict_lua_la_LIBADD = libdict_lua_la_DEPENDENCIES = \ libdict.la headers += \ dict-lua.h \ dict-lua-private.h endif pkginc_libdir=$(pkgincludedir) pkginc_lib_HEADERS = $(headers) test_programs = \ test-dict noinst_PROGRAMS = $(test_programs) test-dict-client test_libs = \ ../lib-test/libtest.la \ ../lib/liblib.la test_dict_SOURCES = test-dict.c test_dict_LDADD = libdict.la $(test_libs) test_dict_DEPENDENCIES = $(noinst_LTLIBRARIES) $(test_libs) test_dict_client_SOURCES = test-dict-client.c test_dict_client_LDADD = $(noinst_LTLIBRARIES) ../lib/liblib.la test_dict_client_DEPENDENCIES = $(noinst_LTLIBRARIES) $(test_libs) check-local: for bin in $(test_programs) $(check_PROGRAMS); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done dovecot-2.3.21.1/src/lib-dict/dict-redis.c0000644000000000000000000005442614656633576015001 00000000000000/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING redis */ #include "lib.h" #include "array.h" #include "str.h" #include "istream.h" #include "ostream.h" #include "connection.h" #include "dict-private.h" #define REDIS_DEFAULT_PORT 6379 #define REDIS_DEFAULT_LOOKUP_TIMEOUT_MSECS (1000*30) #define DICT_USERNAME_SEPARATOR '/' enum redis_input_state { /* expecting +OK reply for AUTH */ REDIS_INPUT_STATE_AUTH, /* expecting +OK reply for SELECT */ REDIS_INPUT_STATE_SELECT, /* expecting $-1 / $ followed by GET reply */ REDIS_INPUT_STATE_GET, /* expecting +QUEUED */ REDIS_INPUT_STATE_MULTI, /* expecting +OK reply for DISCARD */ REDIS_INPUT_STATE_DISCARD, /* expecting * */ REDIS_INPUT_STATE_EXEC, /* expecting EXEC reply */ REDIS_INPUT_STATE_EXEC_REPLY }; struct redis_connection { struct connection conn; struct redis_dict *dict; string_t *last_reply; unsigned int bytes_left; bool value_not_found; bool value_received; }; struct redis_dict_reply { unsigned int reply_count; dict_transaction_commit_callback_t *callback; void *context; }; struct redis_dict { struct dict dict; char *password, *key_prefix, *expire_value; unsigned int timeout_msecs, db_id; struct redis_connection conn; ARRAY(enum redis_input_state) input_states; ARRAY(struct redis_dict_reply) replies; bool connected; bool transaction_open; bool db_id_set; }; struct redis_dict_transaction_context { struct dict_transaction_context ctx; unsigned int cmd_count; char *error; }; static struct connection_list *redis_connections; static void redis_input_state_add(struct redis_dict *dict, enum redis_input_state state) { array_push_back(&dict->input_states, &state); } static void redis_input_state_remove(struct redis_dict *dict) { array_pop_front(&dict->input_states); } static void redis_reply_callback(struct redis_connection *conn, const struct redis_dict_reply *reply, const struct dict_commit_result *result) { i_assert(reply->callback != NULL); if (conn->dict->dict.prev_ioloop != NULL) io_loop_set_current(conn->dict->dict.prev_ioloop); reply->callback(result, reply->context); if (conn->dict->dict.prev_ioloop != NULL) io_loop_set_current(conn->dict->dict.ioloop); } static void redis_disconnected(struct redis_connection *conn, const char *reason) { const struct dict_commit_result result = { DICT_COMMIT_RET_FAILED, reason }; const struct redis_dict_reply *reply; conn->dict->db_id_set = FALSE; conn->dict->connected = FALSE; connection_disconnect(&conn->conn); array_foreach(&conn->dict->replies, reply) redis_reply_callback(conn, reply, &result); array_clear(&conn->dict->replies); array_clear(&conn->dict->input_states); if (conn->dict->dict.ioloop != NULL) io_loop_stop(conn->dict->dict.ioloop); } static void redis_conn_destroy(struct connection *_conn) { struct redis_connection *conn = (struct redis_connection *)_conn; redis_disconnected(conn, connection_disconnect_reason(_conn)); } static void redis_dict_wait_timeout(struct redis_dict *dict) { const char *reason = t_strdup_printf( "redis: Commit timed out in %u.%03u secs", dict->timeout_msecs/1000, dict->timeout_msecs%1000); redis_disconnected(&dict->conn, reason); } static void redis_wait(struct redis_dict *dict) { struct timeout *to; i_assert(dict->dict.ioloop == NULL); dict->dict.prev_ioloop = current_ioloop; dict->dict.ioloop = io_loop_create(); to = timeout_add(dict->timeout_msecs, redis_dict_wait_timeout, dict); connection_switch_ioloop(&dict->conn.conn); do { io_loop_run(dict->dict.ioloop); } while (array_count(&dict->input_states) > 0); timeout_remove(&to); io_loop_set_current(dict->dict.prev_ioloop); connection_switch_ioloop(&dict->conn.conn); io_loop_set_current(dict->dict.ioloop); io_loop_destroy(&dict->dict.ioloop); dict->dict.prev_ioloop = NULL; } static int redis_input_get(struct redis_connection *conn, const char **error_r) { const unsigned char *data; size_t size; const char *line; if (conn->bytes_left == 0) { /* read the size first */ line = i_stream_next_line(conn->conn.input); if (line == NULL) return 0; if (strcmp(line, "$-1") == 0) { conn->value_received = TRUE; conn->value_not_found = TRUE; if (conn->dict->dict.ioloop != NULL) io_loop_stop(conn->dict->dict.ioloop); redis_input_state_remove(conn->dict); return 1; } if (line[0] != '$' || str_to_uint(line+1, &conn->bytes_left) < 0) { *error_r = t_strdup_printf( "redis: Unexpected input (wanted $size): %s", line); return -1; } conn->bytes_left += 2; /* include trailing CRLF */ } data = i_stream_get_data(conn->conn.input, &size); if (size > conn->bytes_left) size = conn->bytes_left; str_append_data(conn->last_reply, data, size); conn->bytes_left -= size; i_stream_skip(conn->conn.input, size); if (conn->bytes_left > 0) return 0; /* reply fully read - drop trailing CRLF */ conn->value_received = TRUE; str_truncate(conn->last_reply, str_len(conn->last_reply)-2); if (conn->dict->dict.ioloop != NULL) io_loop_stop(conn->dict->dict.ioloop); redis_input_state_remove(conn->dict); return 1; } static int redis_conn_input_more(struct redis_connection *conn, const char **error_r) { struct redis_dict *dict = conn->dict; struct redis_dict_reply *reply; const enum redis_input_state *states; enum redis_input_state state; unsigned int count, num_replies; const char *line; states = array_get(&dict->input_states, &count); if (count == 0) { line = i_stream_next_line(conn->conn.input); if (line == NULL) return 0; *error_r = t_strdup_printf( "redis: Unexpected input (expected nothing): %s", line); return -1; } state = states[0]; if (state == REDIS_INPUT_STATE_GET) return redis_input_get(conn, error_r); line = i_stream_next_line(conn->conn.input); if (line == NULL) return 0; redis_input_state_remove(dict); switch (state) { case REDIS_INPUT_STATE_GET: i_unreached(); case REDIS_INPUT_STATE_AUTH: case REDIS_INPUT_STATE_SELECT: case REDIS_INPUT_STATE_MULTI: case REDIS_INPUT_STATE_DISCARD: if (line[0] != '+') break; return 1; case REDIS_INPUT_STATE_EXEC: if (line[0] != '*' || str_to_uint(line+1, &num_replies) < 0) break; reply = array_front_modifiable(&dict->replies); i_assert(reply->reply_count > 0); if (reply->reply_count != num_replies) { *error_r = t_strdup_printf( "redis: EXEC expected %u replies, not %u", reply->reply_count, num_replies); return -1; } return 1; case REDIS_INPUT_STATE_EXEC_REPLY: if (*line != '+' && *line != ':') break; /* success, just ignore the actual reply */ reply = array_front_modifiable(&dict->replies); i_assert(reply->reply_count > 0); if (--reply->reply_count == 0) { const struct dict_commit_result result = { DICT_COMMIT_RET_OK, NULL }; redis_reply_callback(conn, reply, &result); array_pop_front(&dict->replies); /* if we're running in a dict-ioloop, we're handling a synchronous commit and need to stop now */ if (array_count(&dict->replies) == 0 && conn->dict->dict.ioloop != NULL) io_loop_stop(conn->dict->dict.ioloop); } return 1; } str_truncate(dict->conn.last_reply, 0); str_append(dict->conn.last_reply, line); *error_r = t_strdup_printf("redis: Unexpected input (state=%d): %s", state, line); return -1; } static void redis_conn_input(struct connection *_conn) { struct redis_connection *conn = (struct redis_connection *)_conn; const char *error = NULL; int ret; switch (i_stream_read(_conn->input)) { case 0: return; case -1: redis_disconnected(conn, i_stream_get_error(_conn->input)); return; default: break; } while ((ret = redis_conn_input_more(conn, &error)) > 0) ; if (ret < 0) { i_assert(error != NULL); redis_disconnected(conn, error); } } static void redis_conn_connected(struct connection *_conn, bool success) { struct redis_connection *conn = (struct redis_connection *)_conn; if (!success) { e_error(conn->conn.event, "connect() failed: %m"); } else { conn->dict->connected = TRUE; } if (conn->dict->dict.ioloop != NULL) io_loop_stop(conn->dict->dict.ioloop); } static const struct connection_settings redis_conn_set = { .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, .client = TRUE }; static const struct connection_vfuncs redis_conn_vfuncs = { .destroy = redis_conn_destroy, .input = redis_conn_input, .client_connected = redis_conn_connected }; static const char *redis_escape_username(const char *username) { const char *p; string_t *str = t_str_new(64); for (p = username; *p != '\0'; p++) { switch (*p) { case DICT_USERNAME_SEPARATOR: str_append(str, "\\-"); break; case '\\': str_append(str, "\\\\"); break; default: str_append_c(str, *p); } } return str_c(str); } static int redis_dict_init(struct dict *driver, const char *uri, const struct dict_settings *set, struct dict **dict_r, const char **error_r) { struct redis_dict *dict; struct ip_addr ip; unsigned int secs; in_port_t port = REDIS_DEFAULT_PORT; const char *const *args, *unix_path = NULL; int ret = 0; if (redis_connections == NULL) { redis_connections = connection_list_init(&redis_conn_set, &redis_conn_vfuncs); } dict = i_new(struct redis_dict, 1); if (net_addr2ip("127.0.0.1", &ip) < 0) i_unreached(); dict->timeout_msecs = REDIS_DEFAULT_LOOKUP_TIMEOUT_MSECS; dict->key_prefix = i_strdup(""); dict->password = i_strdup(""); args = t_strsplit(uri, ":"); for (; *args != NULL; args++) { if (str_begins(*args, "path=")) { unix_path = *args + 5; } else if (str_begins(*args, "host=")) { if (net_addr2ip(*args+5, &ip) < 0) { *error_r = t_strdup_printf("Invalid IP: %s", *args+5); ret = -1; } } else if (str_begins(*args, "port=")) { if (net_str2port(*args+5, &port) < 0) { *error_r = t_strdup_printf("Invalid port: %s", *args+5); ret = -1; } } else if (str_begins(*args, "prefix=")) { i_free(dict->key_prefix); dict->key_prefix = i_strdup(*args + 7); } else if (str_begins(*args, "db=")) { if (str_to_uint(*args+3, &dict->db_id) < 0) { *error_r = t_strdup_printf( "Invalid db number: %s", *args+3); ret = -1; } } else if (str_begins(*args, "expire_secs=")) { const char *value = *args + 12; if (str_to_uint(value, &secs) < 0 || secs == 0) { *error_r = t_strdup_printf( "Invalid expire_secs: %s", value); ret = -1; } i_free(dict->expire_value); dict->expire_value = i_strdup(value); } else if (str_begins(*args, "timeout_msecs=")) { if (str_to_uint(*args+14, &dict->timeout_msecs) < 0) { *error_r = t_strdup_printf( "Invalid timeout_msecs: %s", *args+14); ret = -1; } } else if (str_begins(*args, "password=")) { i_free(dict->password); dict->password = i_strdup(*args + 9); } else { *error_r = t_strdup_printf("Unknown parameter: %s", *args); ret = -1; } } if (ret < 0) { i_free(dict->password); i_free(dict->key_prefix); i_free(dict); return -1; } dict->conn.conn.event_parent = set->event_parent; if (unix_path != NULL) { connection_init_client_unix(redis_connections, &dict->conn.conn, unix_path); } else { connection_init_client_ip(redis_connections, &dict->conn.conn, NULL, &ip, port); } event_set_append_log_prefix(dict->conn.conn.event, "redis: "); dict->dict = *driver; dict->conn.last_reply = str_new(default_pool, 256); dict->conn.dict = dict; i_array_init(&dict->input_states, 4); i_array_init(&dict->replies, 4); *dict_r = &dict->dict; return 0; } static void redis_dict_deinit(struct dict *_dict) { struct redis_dict *dict = (struct redis_dict *)_dict; if (array_count(&dict->input_states) > 0) { i_assert(dict->connected); redis_wait(dict); } connection_deinit(&dict->conn.conn); str_free(&dict->conn.last_reply); array_free(&dict->replies); array_free(&dict->input_states); i_free(dict->expire_value); i_free(dict->key_prefix); i_free(dict->password); i_free(dict); if (redis_connections->connections == NULL) connection_list_deinit(&redis_connections); } static void redis_dict_wait(struct dict *_dict) { struct redis_dict *dict = (struct redis_dict *)_dict; if (array_count(&dict->input_states) > 0) redis_wait(dict); } static void redis_dict_lookup_timeout(struct redis_dict *dict) { const char *reason = t_strdup_printf( "redis: Lookup timed out in %u.%03u secs", dict->timeout_msecs/1000, dict->timeout_msecs%1000); redis_disconnected(&dict->conn, reason); } static const char * redis_dict_get_full_key(struct redis_dict *dict, const char *username, const char *key) { const char *username_sp = strchr(username, DICT_USERNAME_SEPARATOR); if (str_begins(key, DICT_PATH_SHARED)) key += strlen(DICT_PATH_SHARED); else if (str_begins(key, DICT_PATH_PRIVATE)) { key = t_strdup_printf("%s%c%s", username_sp == NULL ? username : redis_escape_username(username), DICT_USERNAME_SEPARATOR, key + strlen(DICT_PATH_PRIVATE)); } else { i_unreached(); } if (*dict->key_prefix != '\0') key = t_strconcat(dict->key_prefix, key, NULL); return key; } static void redis_dict_auth(struct redis_dict *dict) { const char *cmd; if (*dict->password == '\0') return; cmd = t_strdup_printf("*2\r\n$4\r\nAUTH\r\n$%d\r\n%s\r\n", (int)strlen(dict->password), dict->password); o_stream_nsend_str(dict->conn.conn.output, cmd); redis_input_state_add(dict, REDIS_INPUT_STATE_AUTH); } static void redis_dict_select_db(struct redis_dict *dict) { const char *cmd, *db_str; if (dict->db_id_set) return; dict->db_id_set = TRUE; if (dict->db_id == 0) { /* 0 is the default */ return; } db_str = dec2str(dict->db_id); cmd = t_strdup_printf("*2\r\n$6\r\nSELECT\r\n$%d\r\n%s\r\n", (int)strlen(db_str), db_str); o_stream_nsend_str(dict->conn.conn.output, cmd); redis_input_state_add(dict, REDIS_INPUT_STATE_SELECT); } static int redis_dict_lookup(struct dict *_dict, const struct dict_op_settings *set, pool_t pool, const char *key, const char **value_r, const char **error_r) { struct redis_dict *dict = (struct redis_dict *)_dict; struct timeout *to; const char *cmd; key = redis_dict_get_full_key(dict, set->username, key); dict->conn.value_received = FALSE; dict->conn.value_not_found = FALSE; i_assert(dict->dict.ioloop == NULL); dict->dict.prev_ioloop = current_ioloop; dict->dict.ioloop = io_loop_create(); connection_switch_ioloop(&dict->conn.conn); if (dict->conn.conn.fd_in == -1 && connection_client_connect(&dict->conn.conn) < 0) { e_error(dict->conn.conn.event, "Couldn't connect"); } else { to = timeout_add(dict->timeout_msecs, redis_dict_lookup_timeout, dict); if (!dict->connected) { /* wait for connection */ io_loop_run(dict->dict.ioloop); if (dict->connected) redis_dict_auth(dict); } if (dict->connected) { redis_dict_select_db(dict); cmd = t_strdup_printf("*2\r\n$3\r\nGET\r\n$%d\r\n%s\r\n", (int)strlen(key), key); o_stream_nsend_str(dict->conn.conn.output, cmd); str_truncate(dict->conn.last_reply, 0); redis_input_state_add(dict, REDIS_INPUT_STATE_GET); do { io_loop_run(dict->dict.ioloop); } while (array_count(&dict->input_states) > 0); } timeout_remove(&to); } io_loop_set_current(dict->dict.prev_ioloop); connection_switch_ioloop(&dict->conn.conn); io_loop_set_current(dict->dict.ioloop); io_loop_destroy(&dict->dict.ioloop); dict->dict.prev_ioloop = NULL; if (!dict->conn.value_received) { /* we failed in some way. make sure we disconnect since the connection state isn't known anymore */ *error_r = t_strdup_printf("redis: Communication failure (last reply: %s)", str_c(dict->conn.last_reply)); redis_disconnected(&dict->conn, *error_r); return -1; } if (dict->conn.value_not_found) return 0; *value_r = p_strdup(pool, str_c(dict->conn.last_reply)); return 1; } static struct dict_transaction_context * redis_transaction_init(struct dict *_dict) { struct redis_dict *dict = (struct redis_dict *)_dict; struct redis_dict_transaction_context *ctx; i_assert(!dict->transaction_open); dict->transaction_open = TRUE; ctx = i_new(struct redis_dict_transaction_context, 1); ctx->ctx.dict = _dict; if (dict->conn.conn.fd_in == -1 && connection_client_connect(&dict->conn.conn) < 0) { e_error(dict->conn.conn.event, "Couldn't connect"); } else if (!dict->connected) { /* wait for connection */ redis_wait(dict); if (dict->connected) redis_dict_auth(dict); } if (dict->connected) redis_dict_select_db(dict); return &ctx->ctx; } static void redis_transaction_commit(struct dict_transaction_context *_ctx, bool async, dict_transaction_commit_callback_t *callback, void *context) { struct redis_dict_transaction_context *ctx = (struct redis_dict_transaction_context *)_ctx; struct redis_dict *dict = (struct redis_dict *)_ctx->dict; struct redis_dict_reply *reply; unsigned int i; struct dict_commit_result result = { .ret = DICT_COMMIT_RET_OK }; i_assert(dict->transaction_open); dict->transaction_open = FALSE; if (ctx->error != NULL) { /* make sure we're disconnected */ redis_disconnected(&dict->conn, ctx->error); result.ret = -1; result.error = ctx->error; callback(&result, context); } else if (_ctx->changed) { i_assert(ctx->cmd_count > 0); o_stream_nsend_str(dict->conn.conn.output, "*1\r\n$4\r\nEXEC\r\n"); reply = array_append_space(&dict->replies); reply->callback = callback; reply->context = context; reply->reply_count = ctx->cmd_count; redis_input_state_add(dict, REDIS_INPUT_STATE_EXEC); for (i = 0; i < ctx->cmd_count; i++) redis_input_state_add(dict, REDIS_INPUT_STATE_EXEC_REPLY); if (async) { i_free(ctx); return; } redis_wait(dict); } else { callback(&result, context); } i_free(ctx->error); i_free(ctx); } static void redis_transaction_rollback(struct dict_transaction_context *_ctx) { struct redis_dict_transaction_context *ctx = (struct redis_dict_transaction_context *)_ctx; struct redis_dict *dict = (struct redis_dict *)_ctx->dict; i_assert(dict->transaction_open); dict->transaction_open = FALSE; if (ctx->error != NULL) { /* make sure we're disconnected */ redis_disconnected(&dict->conn, ctx->error); } else if (_ctx->changed) { o_stream_nsend_str(dict->conn.conn.output, "*1\r\n$7\r\nDISCARD\r\n"); redis_input_state_add(dict, REDIS_INPUT_STATE_DISCARD); } i_free(ctx->error); i_free(ctx); } static int redis_check_transaction(struct redis_dict_transaction_context *ctx) { struct redis_dict *dict = (struct redis_dict *)ctx->ctx.dict; if (ctx->error != NULL) return -1; if (!dict->connected) { ctx->error = i_strdup("Disconnected during transaction"); return -1; } if (ctx->ctx.changed) return 0; redis_input_state_add(dict, REDIS_INPUT_STATE_MULTI); if (o_stream_send_str(dict->conn.conn.output, "*1\r\n$5\r\nMULTI\r\n") < 0) { ctx->error = i_strdup_printf("write() failed: %s", o_stream_get_error(dict->conn.conn.output)); return -1; } return 0; } static void redis_append_expire(struct redis_dict_transaction_context *ctx, string_t *cmd, const char *key) { struct redis_dict *dict = (struct redis_dict *)ctx->ctx.dict; if (dict->expire_value == NULL) return; str_printfa(cmd, "*3\r\n$6\r\nEXPIRE\r\n$%u\r\n%s\r\n$%u\r\n%s\r\n", (unsigned int)strlen(key), key, (unsigned int)strlen(dict->expire_value), dict->expire_value); redis_input_state_add(dict, REDIS_INPUT_STATE_MULTI); ctx->cmd_count++; } static void redis_set(struct dict_transaction_context *_ctx, const char *key, const char *value) { struct redis_dict_transaction_context *ctx = (struct redis_dict_transaction_context *)_ctx; struct redis_dict *dict = (struct redis_dict *)_ctx->dict; const struct dict_op_settings_private *set = &_ctx->set; string_t *cmd; if (redis_check_transaction(ctx) < 0) return; key = redis_dict_get_full_key(dict, set->username, key); cmd = t_str_new(128); str_printfa(cmd, "*3\r\n$3\r\nSET\r\n$%u\r\n%s\r\n$%u\r\n%s\r\n", (unsigned int)strlen(key), key, (unsigned int)strlen(value), value); redis_input_state_add(dict, REDIS_INPUT_STATE_MULTI); ctx->cmd_count++; redis_append_expire(ctx, cmd, key); if (o_stream_send(dict->conn.conn.output, str_data(cmd), str_len(cmd)) < 0) { ctx->error = i_strdup_printf("write() failed: %s", o_stream_get_error(dict->conn.conn.output)); } } static void redis_unset(struct dict_transaction_context *_ctx, const char *key) { struct redis_dict_transaction_context *ctx = (struct redis_dict_transaction_context *)_ctx; struct redis_dict *dict = (struct redis_dict *)_ctx->dict; const struct dict_op_settings_private *set = &_ctx->set; const char *cmd; if (redis_check_transaction(ctx) < 0) return; key = redis_dict_get_full_key(dict, set->username, key); cmd = t_strdup_printf("*2\r\n$3\r\nDEL\r\n$%u\r\n%s\r\n", (unsigned int)strlen(key), key); if (o_stream_send_str(dict->conn.conn.output, cmd) < 0) { ctx->error = i_strdup_printf("write() failed: %s", o_stream_get_error(dict->conn.conn.output)); } redis_input_state_add(dict, REDIS_INPUT_STATE_MULTI); ctx->cmd_count++; } static void redis_atomic_inc(struct dict_transaction_context *_ctx, const char *key, long long diff) { struct redis_dict_transaction_context *ctx = (struct redis_dict_transaction_context *)_ctx; struct redis_dict *dict = (struct redis_dict *)_ctx->dict; const struct dict_op_settings_private *set = &_ctx->set; const char *diffstr; string_t *cmd; if (redis_check_transaction(ctx) < 0) return; key = redis_dict_get_full_key(dict, set->username, key); diffstr = t_strdup_printf("%lld", diff); cmd = t_str_new(128); str_printfa(cmd, "*3\r\n$6\r\nINCRBY\r\n$%u\r\n%s\r\n$%u\r\n%s\r\n", (unsigned int)strlen(key), key, (unsigned int)strlen(diffstr), diffstr); redis_input_state_add(dict, REDIS_INPUT_STATE_MULTI); ctx->cmd_count++; redis_append_expire(ctx, cmd, key); if (o_stream_send(dict->conn.conn.output, str_data(cmd), str_len(cmd)) < 0) { ctx->error = i_strdup_printf("write() failed: %s", o_stream_get_error(dict->conn.conn.output)); } } struct dict dict_driver_redis = { .name = "redis", { .init = redis_dict_init, .deinit = redis_dict_deinit, .wait = redis_dict_wait, .lookup = redis_dict_lookup, .transaction_init = redis_transaction_init, .transaction_commit = redis_transaction_commit, .transaction_rollback = redis_transaction_rollback, .set = redis_set, .unset = redis_unset, .atomic_inc = redis_atomic_inc, } }; dovecot-2.3.21.1/src/lib-dict/dict-client.c0000644000000000000000000012236614656633576015150 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "llist.h" #include "str.h" #include "strescape.h" #include "file-lock.h" #include "time-util.h" #include "connection.h" #include "ostream.h" #include "eacces-error.h" #include "dict-private.h" #include "dict-client.h" #include #include /* Disconnect from dict server after this many milliseconds of idling after sending a command. Because dict server does blocking dict accesses, it can handle only one client at a time. This is why the default timeout is zero, so that there won't be many dict processes just doing nothing. Zero means that the socket is disconnected immediately after returning to ioloop. */ #define DICT_CLIENT_DEFAULT_TIMEOUT_MSECS 0 /* Abort dict lookup after this many seconds. */ #define DICT_CLIENT_REQUEST_TIMEOUT_MSECS 30000 /* When dict lookup timeout is reached, wait a bit longer if the last dict ioloop wait was shorter than this. */ #define DICT_CLIENT_REQUEST_TIMEOUT_MIN_LAST_IOLOOP_WAIT_MSECS 1000 /* Log a warning if dict lookup takes longer than this many milliseconds. */ #define DICT_CLIENT_DEFAULT_WARN_SLOW_MSECS 5000 struct client_dict_cmd { int refcount; struct client_dict *dict; struct timeval start_time; char *query; unsigned int async_id; struct timeval async_id_received_time; uint64_t start_global_ioloop_usecs; uint64_t start_dict_ioloop_usecs; uint64_t start_lock_usecs; bool reconnected; bool retry_errors; bool no_replies; bool unfinished; bool background; void (*callback)(struct client_dict_cmd *cmd, enum dict_protocol_reply reply, const char *value, const char *const *extra_args, const char *error, bool disconnected); struct client_dict_iterate_context *iter; struct client_dict_transaction_context *trans; struct { dict_lookup_callback_t *lookup; dict_transaction_commit_callback_t *commit; void *context; } api_callback; }; struct dict_client_connection { struct connection conn; struct client_dict *dict; }; struct client_dict { struct dict dict; struct dict_client_connection conn; char *uri; enum dict_data_type value_type; unsigned warn_slow_msecs; time_t last_failed_connect; char *last_connect_error; struct io_wait_timer *wait_timer; uint64_t last_timer_switch_usecs; struct timeout *to_requests; struct timeout *to_idle; unsigned int idle_msecs; ARRAY(struct client_dict_cmd *) cmds; struct client_dict_transaction_context *transactions; unsigned int transaction_id_counter; }; struct client_dict_iter_result { const char *key, *const *values; }; struct client_dict_iterate_context { struct dict_iterate_context ctx; char *error; char *path; enum dict_iterate_flags flags; int refcount; pool_t results_pool; ARRAY(struct client_dict_iter_result) results; unsigned int result_idx; bool cmd_sent; bool seen_results; bool finished; bool deinit; }; struct client_dict_transaction_context { struct dict_transaction_context ctx; struct client_dict_transaction_context *prev, *next; char *first_query; char *error; unsigned int id; unsigned int query_count; bool sent_begin:1; }; static struct connection_list *dict_connections; static int client_dict_connect(struct client_dict *dict, const char **error_r); static int client_dict_reconnect(struct client_dict *dict, const char *reason, const char **error_r); static void client_dict_disconnect(struct client_dict *dict, const char *reason); static const char *dict_wait_warnings(const struct client_dict_cmd *cmd); static struct client_dict_cmd * client_dict_cmd_init(struct client_dict *dict, const char *query) { struct client_dict_cmd *cmd; io_loop_time_refresh(); cmd = i_new(struct client_dict_cmd, 1); cmd->refcount = 1; cmd->dict = dict; cmd->query = i_strdup(query); cmd->start_time = ioloop_timeval; cmd->start_global_ioloop_usecs = ioloop_global_wait_usecs; cmd->start_dict_ioloop_usecs = io_wait_timer_get_usecs(dict->wait_timer); cmd->start_lock_usecs = file_lock_wait_get_total_usecs(); return cmd; } static void client_dict_cmd_ref(struct client_dict_cmd *cmd) { i_assert(cmd->refcount > 0); cmd->refcount++; } static bool client_dict_cmd_unref(struct client_dict_cmd *cmd) { i_assert(cmd->refcount > 0); if (--cmd->refcount > 0) return TRUE; i_assert(cmd->trans == NULL); i_free(cmd->query); i_free(cmd); return FALSE; } static bool dict_cmd_callback_line(struct client_dict_cmd *cmd, const char *const *args) { const char *value = args[0]; enum dict_protocol_reply reply; if (value == NULL) { /* "" is a valid iteration reply */ reply = DICT_PROTOCOL_REPLY_ITER_FINISHED; } else { reply = value[0]; value++; args++; } cmd->unfinished = FALSE; cmd->callback(cmd, reply, value, args, NULL, FALSE); return !cmd->unfinished; } static void dict_cmd_callback_error(struct client_dict_cmd *cmd, const char *error, bool disconnected) { const char *null_arg = NULL; cmd->unfinished = FALSE; if (cmd->callback != NULL) { cmd->callback(cmd, DICT_PROTOCOL_REPLY_ERROR, "", &null_arg, error, disconnected); } i_assert(!cmd->unfinished); } static struct client_dict_cmd * client_dict_cmd_first_nonbg(struct client_dict *dict) { struct client_dict_cmd *const *cmds; unsigned int i, count; cmds = array_get(&dict->cmds, &count); for (i = 0; i < count; i++) { if (!cmds[i]->background) return cmds[i]; } return NULL; } static void client_dict_input_timeout(struct client_dict *dict) { struct client_dict_cmd *cmd; const char *error; uint64_t msecs_in_last_dict_ioloop_wait; int cmd_diff; /* find the first non-background command. there must be at least one. */ cmd = client_dict_cmd_first_nonbg(dict); i_assert(cmd != NULL); cmd_diff = timeval_diff_msecs(&ioloop_timeval, &cmd->start_time); if (cmd_diff < DICT_CLIENT_REQUEST_TIMEOUT_MSECS) { /* need to re-create this timeout. the currently-oldest command was added when another command was still running with an older timeout. */ timeout_remove(&dict->to_requests); dict->to_requests = timeout_add(DICT_CLIENT_REQUEST_TIMEOUT_MSECS - cmd_diff, client_dict_input_timeout, dict); return; } /* If we've gotten here because all the time was spent in other ioloops or locks, make sure there's a bit of time waiting for the dict ioloop as well. There's a good chance that the reply can be read. */ msecs_in_last_dict_ioloop_wait = (io_wait_timer_get_usecs(dict->wait_timer) - dict->last_timer_switch_usecs + 999) / 1000; if (msecs_in_last_dict_ioloop_wait < DICT_CLIENT_REQUEST_TIMEOUT_MIN_LAST_IOLOOP_WAIT_MSECS) { timeout_remove(&dict->to_requests); dict->to_requests = timeout_add(DICT_CLIENT_REQUEST_TIMEOUT_MIN_LAST_IOLOOP_WAIT_MSECS - msecs_in_last_dict_ioloop_wait, client_dict_input_timeout, dict); return; } (void)client_dict_reconnect(dict, t_strdup_printf( "Dict server timeout: %s " "(%u commands pending, oldest sent %u.%03u secs ago: %s, %s)", connection_input_timeout_reason(&dict->conn.conn), array_count(&dict->cmds), cmd_diff/1000, cmd_diff%1000, cmd->query, dict_wait_warnings(cmd)), &error); } static int client_dict_cmd_query_send(struct client_dict *dict, const char *query) { struct const_iovec iov[2]; ssize_t ret; iov[0].iov_base = query; iov[0].iov_len = strlen(query); iov[1].iov_base = "\n"; iov[1].iov_len = 1; ret = o_stream_sendv(dict->conn.conn.output, iov, 2); if (ret < 0) return -1; i_assert((size_t)ret == iov[0].iov_len + 1); return 0; } static bool client_dict_cmd_send(struct client_dict *dict, struct client_dict_cmd **_cmd, const char **error_r) { struct client_dict_cmd *cmd = *_cmd; const char *error = NULL; bool retry = cmd->retry_errors; int ret; *_cmd = NULL; /* we're no longer idling. even with no_replies=TRUE we're going to wait for COMMIT/ROLLBACK. */ timeout_remove(&dict->to_idle); if (client_dict_connect(dict, &error) < 0) { retry = FALSE; ret = -1; } else { ret = client_dict_cmd_query_send(dict, cmd->query); if (ret < 0) { error = t_strdup_printf("write(%s) failed: %s", dict->conn.conn.name, o_stream_get_error(dict->conn.conn.output)); } } if (ret < 0 && retry) { /* Reconnect and try again. */ if (client_dict_reconnect(dict, error, &error) < 0) ; else if (client_dict_cmd_query_send(dict, cmd->query) < 0) { error = t_strdup_printf("write(%s) failed: %s", dict->conn.conn.name, o_stream_get_error(dict->conn.conn.output)); } else { ret = 0; } } if (cmd->no_replies) { /* just send and forget */ client_dict_cmd_unref(cmd); return TRUE; } else if (ret < 0) { i_assert(error != NULL); /* we didn't successfully send this command to dict */ dict_cmd_callback_error(cmd, error, cmd->reconnected); client_dict_cmd_unref(cmd); if (error_r != NULL) *error_r = error; return FALSE; } else { if (dict->to_requests == NULL && !cmd->background) { dict->to_requests = timeout_add(DICT_CLIENT_REQUEST_TIMEOUT_MSECS, client_dict_input_timeout, dict); } array_push_back(&dict->cmds, &cmd); return TRUE; } } static bool client_dict_transaction_send_begin(struct client_dict_transaction_context *ctx, const struct dict_op_settings_private *set) { struct client_dict *dict = (struct client_dict *)ctx->ctx.dict; struct client_dict_cmd *cmd; const char *query, *error; i_assert(ctx->error == NULL); ctx->sent_begin = TRUE; /* transactions commands don't have replies. only COMMIT has. */ query = t_strdup_printf("%c%u\t%s", DICT_PROTOCOL_CMD_BEGIN, ctx->id, set->username == NULL ? "" : str_tabescape(set->username)); cmd = client_dict_cmd_init(dict, query); cmd->no_replies = TRUE; cmd->retry_errors = TRUE; if (!client_dict_cmd_send(dict, &cmd, &error)) { ctx->error = i_strdup(error); return FALSE; } return TRUE; } static void client_dict_send_transaction_query(struct client_dict_transaction_context *ctx, const char *query) { struct client_dict *dict = (struct client_dict *)ctx->ctx.dict; const struct dict_op_settings_private *set = &ctx->ctx.set; struct client_dict_cmd *cmd; const char *error; if (ctx->error != NULL) return; if (!ctx->sent_begin) { if (!client_dict_transaction_send_begin(ctx, set)) return; } ctx->query_count++; if (ctx->first_query == NULL) ctx->first_query = i_strdup(query); cmd = client_dict_cmd_init(dict, query); cmd->no_replies = TRUE; if (!client_dict_cmd_send(dict, &cmd, &error)) ctx->error = i_strdup(error); } static bool client_dict_is_finished(struct client_dict *dict) { return dict->transactions == NULL && array_count(&dict->cmds) == 0; } static void client_dict_timeout(struct client_dict *dict) { if (client_dict_is_finished(dict)) client_dict_disconnect(dict, "Idle disconnection"); else timeout_remove(&dict->to_idle); } static bool client_dict_have_nonbackground_cmds(struct client_dict *dict) { struct client_dict_cmd *cmd; array_foreach_elem(&dict->cmds, cmd) { if (!cmd->background) return TRUE; } return FALSE; } static void client_dict_add_timeout(struct client_dict *dict) { if (dict->to_idle != NULL) { if (dict->idle_msecs > 0) timeout_reset(dict->to_idle); } else if (client_dict_is_finished(dict)) { dict->to_idle = timeout_add(dict->idle_msecs, client_dict_timeout, dict); timeout_remove(&dict->to_requests); } else if (dict->transactions == NULL && !client_dict_have_nonbackground_cmds(dict)) { /* we had non-background commands, but now we're back to having only background commands. remove timeouts. */ timeout_remove(&dict->to_requests); } } static void client_dict_cmd_backgrounded(struct client_dict *dict) { if (dict->to_requests == NULL) return; if (!client_dict_have_nonbackground_cmds(dict)) { /* we only have background-commands. remove the request timeout. */ timeout_remove(&dict->to_requests); } } static int dict_conn_assign_next_async_id(struct dict_client_connection *conn, const char *line) { struct client_dict_cmd *const *cmds; unsigned int i, count, async_id; i_assert(line[0] == DICT_PROTOCOL_REPLY_ASYNC_ID); if (str_to_uint(line+1, &async_id) < 0 || async_id == 0) { e_error(conn->conn.event, "Received invalid async-id line: %s", line); return -1; } cmds = array_get(&conn->dict->cmds, &count); for (i = 0; i < count; i++) { if (cmds[i]->async_id == 0) { cmds[i]->async_id = async_id; cmds[i]->async_id_received_time = ioloop_timeval; return 0; } } e_error(conn->conn.event, "Received async-id line, but all %u " "commands already have it: %s", count, line); return -1; } static int dict_conn_find_async_id(struct dict_client_connection *conn, const char *async_arg, const char *line, unsigned int *idx_r) { struct client_dict_cmd *const *cmds; unsigned int i, count, async_id; i_assert(async_arg[0] == DICT_PROTOCOL_REPLY_ASYNC_REPLY); if (str_to_uint(async_arg+1, &async_id) < 0 || async_id == 0) { e_error(conn->conn.event, "Received invalid async-reply line: %s", line); return -1; } cmds = array_get(&conn->dict->cmds, &count); for (i = 0; i < count; i++) { if (cmds[i]->async_id == async_id) { *idx_r = i; return 0; } } e_error(conn->conn.event, "Received reply for nonexistent async-id %u: %s", async_id, line); return -1; } static int dict_conn_input_line(struct connection *_conn, const char *line) { struct dict_client_connection *conn = (struct dict_client_connection *)_conn; struct client_dict *dict = conn->dict; struct client_dict_cmd *const *cmds; const char *const *args; unsigned int i, count; bool finished; if (dict->to_requests != NULL) timeout_reset(dict->to_requests); if (line[0] == DICT_PROTOCOL_REPLY_ASYNC_ID) return dict_conn_assign_next_async_id(conn, line) < 0 ? -1 : 1; cmds = array_get(&conn->dict->cmds, &count); if (count == 0) { e_error(conn->conn.event, "Received reply without pending commands: %s", line); return -1; } args = t_strsplit_tabescaped(line); if (args[0] != NULL && args[0][0] == DICT_PROTOCOL_REPLY_ASYNC_REPLY) { if (dict_conn_find_async_id(conn, args[0], line, &i) < 0) return -1; args++; } else { i = 0; } i_assert(!cmds[i]->no_replies); client_dict_cmd_ref(cmds[i]); finished = dict_cmd_callback_line(cmds[i], args); if (!client_dict_cmd_unref(cmds[i])) { /* disconnected during command handling */ return -1; } if (!finished) { /* more lines needed for this command */ return 1; } client_dict_cmd_unref(cmds[i]); array_delete(&dict->cmds, i, 1); client_dict_add_timeout(dict); return 1; } static int client_dict_connect(struct client_dict *dict, const char **error_r) { const char *query, *error; if (dict->conn.conn.fd_in != -1) return 0; if (dict->last_failed_connect == ioloop_time) { /* Try again later */ *error_r = dict->last_connect_error; return -1; } if (connection_client_connect(&dict->conn.conn) < 0) { dict->last_failed_connect = ioloop_time; if (errno == EACCES) { error = eacces_error_get("net_connect_unix", dict->conn.conn.name); } else { error = t_strdup_printf( "net_connect_unix(%s) failed: %m", dict->conn.conn.name); } i_free(dict->last_connect_error); dict->last_connect_error = i_strdup(error); *error_r = error; return -1; } query = t_strdup_printf("%c%u\t%u\t%d\t%s\t%s\n", DICT_PROTOCOL_CMD_HELLO, DICT_CLIENT_PROTOCOL_MAJOR_VERSION, DICT_CLIENT_PROTOCOL_MINOR_VERSION, dict->value_type, "", str_tabescape(dict->uri)); o_stream_nsend_str(dict->conn.conn.output, query); client_dict_add_timeout(dict); return 0; } static void client_dict_abort_commands(struct client_dict *dict, const char *reason) { ARRAY(struct client_dict_cmd *) cmds_copy; struct client_dict_cmd *cmd; /* abort all commands */ t_array_init(&cmds_copy, array_count(&dict->cmds)); array_append_array(&cmds_copy, &dict->cmds); array_clear(&dict->cmds); array_foreach_elem(&cmds_copy, cmd) { dict_cmd_callback_error(cmd, reason, TRUE); client_dict_cmd_unref(cmd); } } static void client_dict_disconnect(struct client_dict *dict, const char *reason) { struct client_dict_transaction_context *ctx, *next; client_dict_abort_commands(dict, reason); /* all transactions that have sent BEGIN are no longer valid */ for (ctx = dict->transactions; ctx != NULL; ctx = next) { next = ctx->next; if (ctx->sent_begin && ctx->error == NULL) ctx->error = i_strdup(reason); } timeout_remove(&dict->to_idle); timeout_remove(&dict->to_requests); connection_disconnect(&dict->conn.conn); } static int client_dict_reconnect(struct client_dict *dict, const char *reason, const char **error_r) { ARRAY(struct client_dict_cmd *) retry_cmds; struct client_dict_cmd *cmd; const char *error; int ret; t_array_init(&retry_cmds, array_count(&dict->cmds)); for (unsigned int i = 0; i < array_count(&dict->cmds); ) { cmd = array_idx_elem(&dict->cmds, i); if (!cmd->retry_errors) { i++; } else if (cmd->iter != NULL && cmd->iter->seen_results) { /* don't retry iteration that already returned something to the caller. otherwise we'd return duplicates. */ i++; } else { array_push_back(&retry_cmds, &cmd); array_delete(&dict->cmds, i, 1); } } client_dict_disconnect(dict, reason); if (client_dict_connect(dict, error_r) < 0) { reason = t_strdup_printf("%s - reconnect failed: %s", reason, *error_r); array_foreach_elem(&retry_cmds, cmd) { dict_cmd_callback_error(cmd, reason, TRUE); client_dict_cmd_unref(cmd); } return -1; } if (array_count(&retry_cmds) == 0) return 0; e_warning(dict->conn.conn.event, "%s - reconnected", reason); ret = 0; error = ""; array_foreach_elem(&retry_cmds, cmd) { cmd->reconnected = TRUE; cmd->async_id = 0; /* if it fails again, don't retry anymore */ cmd->retry_errors = FALSE; if (ret < 0) { dict_cmd_callback_error(cmd, error, TRUE); client_dict_cmd_unref(cmd); } else if (!client_dict_cmd_send(dict, &cmd, &error)) ret = -1; } return ret; } static void dict_conn_destroy(struct connection *_conn) { struct dict_client_connection *conn = (struct dict_client_connection *)_conn; client_dict_disconnect(conn->dict, connection_disconnect_reason(_conn)); } static const struct connection_settings dict_conn_set = { .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, .unix_client_connect_msecs = 1000, .client = TRUE }; static const struct connection_vfuncs dict_conn_vfuncs = { .destroy = dict_conn_destroy, .input_line = dict_conn_input_line }; static int client_dict_init(struct dict *driver, const char *uri, const struct dict_settings *set, struct dict **dict_r, const char **error_r) { struct ioloop *old_ioloop = current_ioloop; struct client_dict *dict; const char *p, *dest_uri, *path; unsigned int idle_msecs = DICT_CLIENT_DEFAULT_TIMEOUT_MSECS; unsigned int warn_slow_msecs = DICT_CLIENT_DEFAULT_WARN_SLOW_MSECS; /* uri = [idle_msecs=:] [warn_slow_msecs=:] [] ":" */ for (;;) { if (str_begins(uri, "idle_msecs=")) { p = strchr(uri+11, ':'); if (p == NULL) { *error_r = t_strdup_printf("Invalid URI: %s", uri); return -1; } if (str_to_uint(t_strdup_until(uri+11, p), &idle_msecs) < 0) { *error_r = "Invalid idle_msecs"; return -1; } uri = p+1; } else if (str_begins(uri, "warn_slow_msecs=")) { p = strchr(uri+11, ':'); if (p == NULL) { *error_r = t_strdup_printf("Invalid URI: %s", uri); return -1; } if (str_to_uint(t_strdup_until(uri+16, p), &warn_slow_msecs) < 0) { *error_r = "Invalid warn_slow_msecs"; return -1; } uri = p+1; } else { break; } } dest_uri = strchr(uri, ':'); if (dest_uri == NULL) { *error_r = t_strdup_printf("Invalid URI: %s", uri); return -1; } if (dict_connections == NULL) { dict_connections = connection_list_init(&dict_conn_set, &dict_conn_vfuncs); } dict = i_new(struct client_dict, 1); dict->dict = *driver; dict->conn.dict = dict; dict->conn.conn.event_parent = set->event_parent; dict->idle_msecs = idle_msecs; dict->warn_slow_msecs = warn_slow_msecs; i_array_init(&dict->cmds, 32); if (uri[0] == ':') { /* default path */ path = t_strconcat(set->base_dir, "/"DEFAULT_DICT_SERVER_SOCKET_FNAME, NULL); } else if (uri[0] == '/') { /* absolute path */ path = t_strdup_until(uri, dest_uri); } else { /* relative path to base_dir */ path = t_strconcat(set->base_dir, "/", t_strdup_until(uri, dest_uri), NULL); } connection_init_client_unix(dict_connections, &dict->conn.conn, path); dict->uri = i_strdup(dest_uri + 1); dict->dict.ioloop = io_loop_create(); dict->wait_timer = io_wait_timer_add(); io_loop_set_current(old_ioloop); *dict_r = &dict->dict; return 0; } static void client_dict_deinit(struct dict *_dict) { struct client_dict *dict = (struct client_dict *)_dict; struct ioloop *old_ioloop = current_ioloop; client_dict_disconnect(dict, "Deinit"); connection_deinit(&dict->conn.conn); io_wait_timer_remove(&dict->wait_timer); i_assert(dict->transactions == NULL); i_assert(array_count(&dict->cmds) == 0); io_loop_set_current(dict->dict.ioloop); io_loop_destroy(&dict->dict.ioloop); io_loop_set_current(old_ioloop); array_free(&dict->cmds); i_free(dict->last_connect_error); i_free(dict->uri); i_free(dict); if (dict_connections->connections == NULL) connection_list_deinit(&dict_connections); } static void client_dict_wait(struct dict *_dict) { struct client_dict *dict = (struct client_dict *)_dict; if (array_count(&dict->cmds) == 0) return; i_assert(io_loop_is_empty(dict->dict.ioloop)); dict->dict.prev_ioloop = current_ioloop; io_loop_set_current(dict->dict.ioloop); dict_switch_ioloop(_dict); while (array_count(&dict->cmds) > 0) io_loop_run(dict->dict.ioloop); io_loop_set_current(dict->dict.prev_ioloop); dict->dict.prev_ioloop = NULL; dict_switch_ioloop(_dict); i_assert(io_loop_is_empty(dict->dict.ioloop)); } static bool client_dict_switch_ioloop(struct dict *_dict) { struct client_dict *dict = (struct client_dict *)_dict; dict->last_timer_switch_usecs = io_wait_timer_get_usecs(dict->wait_timer); dict->wait_timer = io_wait_timer_move(&dict->wait_timer); if (dict->to_idle != NULL) dict->to_idle = io_loop_move_timeout(&dict->to_idle); if (dict->to_requests != NULL) dict->to_requests = io_loop_move_timeout(&dict->to_requests); connection_switch_ioloop(&dict->conn.conn); return array_count(&dict->cmds) > 0; } static const char *dict_wait_warnings(const struct client_dict_cmd *cmd) { int global_ioloop_msecs = (ioloop_global_wait_usecs - cmd->start_global_ioloop_usecs + 999) / 1000; int dict_ioloop_msecs = (io_wait_timer_get_usecs(cmd->dict->wait_timer) - cmd->start_dict_ioloop_usecs + 999) / 1000; int other_ioloop_msecs = global_ioloop_msecs - dict_ioloop_msecs; int lock_msecs = (file_lock_wait_get_total_usecs() - cmd->start_lock_usecs + 999) / 1000; return t_strdup_printf( "%d.%03d in dict wait, %d.%03d in other ioloops, %d.%03d in locks", dict_ioloop_msecs/1000, dict_ioloop_msecs%1000, other_ioloop_msecs/1000, other_ioloop_msecs%1000, lock_msecs/1000, lock_msecs%1000); } static const char * dict_warnings_sec(const struct client_dict_cmd *cmd, int msecs, const char *const *extra_args) { string_t *str = t_str_new(64); struct timeval tv_start, tv_end; unsigned int tv_start_usec, tv_end_usec; str_printfa(str, "%d.%03d secs (%s", msecs/1000, msecs%1000, dict_wait_warnings(cmd)); if (cmd->reconnected) { int reconnected_msecs = timeval_diff_msecs(&ioloop_timeval, &cmd->dict->conn.conn.connect_started); str_printfa(str, ", reconnected %u.%03u secs ago", reconnected_msecs/1000, reconnected_msecs%1000); } if (cmd->async_id != 0) { int async_reply_msecs = timeval_diff_msecs(&ioloop_timeval, &cmd->async_id_received_time); str_printfa(str, ", async-id reply %u.%03u secs ago", async_reply_msecs/1000, async_reply_msecs%1000); } if (extra_args != NULL && str_array_length(extra_args) >= 4 && str_to_time(extra_args[0], &tv_start.tv_sec) == 0 && str_to_uint(extra_args[1], &tv_start_usec) == 0 && str_to_time(extra_args[2], &tv_end.tv_sec) == 0 && str_to_uint(extra_args[3], &tv_end_usec) == 0) { tv_start.tv_usec = tv_start_usec; tv_end.tv_usec = tv_end_usec; int server_msecs_since_start = timeval_diff_msecs(&ioloop_timeval, &tv_start); int server_msecs = timeval_diff_msecs(&tv_end, &tv_start); str_printfa(str, ", started on dict-server %u.%03d secs ago, " "took %u.%03d secs", server_msecs_since_start/1000, server_msecs_since_start%1000, server_msecs/1000, server_msecs%1000); } str_append_c(str, ')'); return str_c(str); } static void client_dict_lookup_async_callback(struct client_dict_cmd *cmd, enum dict_protocol_reply reply, const char *value, const char *const *extra_args, const char *error, bool disconnected ATTR_UNUSED) { struct client_dict *dict = cmd->dict; struct dict_lookup_result result; const char *const values[] = { value, NULL }; i_zero(&result); if (error != NULL) { result.ret = -1; result.error = error; } else switch (reply) { case DICT_PROTOCOL_REPLY_OK: result.value = value; result.values = values; result.ret = 1; break; case DICT_PROTOCOL_REPLY_MULTI_OK: result.values = t_strsplit_tabescaped(value); result.value = result.values[0]; result.ret = 1; break; case DICT_PROTOCOL_REPLY_NOTFOUND: result.ret = 0; break; case DICT_PROTOCOL_REPLY_FAIL: result.error = value[0] == '\0' ? "dict-server returned failure" : t_strdup_printf("dict-server returned failure: %s", value); result.ret = -1; break; default: result.error = t_strdup_printf( "dict-client: Invalid lookup '%s' reply: %c%s", cmd->query, reply, value); client_dict_disconnect(dict, result.error); result.ret = -1; break; } int diff = timeval_diff_msecs(&ioloop_timeval, &cmd->start_time); if (result.error != NULL) { /* include timing info always in error messages */ result.error = t_strdup_printf("%s (reply took %s)", result.error, dict_warnings_sec(cmd, diff, extra_args)); } else if (!cmd->background && diff >= (int)dict->warn_slow_msecs) { e_warning(dict->conn.conn.event, "dict lookup took %s: %s", dict_warnings_sec(cmd, diff, extra_args), cmd->query); } dict_pre_api_callback(&dict->dict); cmd->api_callback.lookup(&result, cmd->api_callback.context); dict_post_api_callback(&dict->dict); } static void client_dict_lookup_async(struct dict *_dict, const struct dict_op_settings *set, const char *key, dict_lookup_callback_t *callback, void *context) { struct client_dict *dict = (struct client_dict *)_dict; struct client_dict_cmd *cmd; const char *query; query = t_strdup_printf("%c%s\t%s", DICT_PROTOCOL_CMD_LOOKUP, str_tabescape(key), set->username == NULL ? "" : str_tabescape(set->username)); cmd = client_dict_cmd_init(dict, query); cmd->callback = client_dict_lookup_async_callback; cmd->api_callback.lookup = callback; cmd->api_callback.context = context; cmd->retry_errors = TRUE; client_dict_cmd_send(dict, &cmd, NULL); } struct client_dict_sync_lookup { char *error; char *value; int ret; }; static void client_dict_lookup_callback(const struct dict_lookup_result *result, struct client_dict_sync_lookup *lookup) { lookup->ret = result->ret; if (result->ret == -1) lookup->error = i_strdup(result->error); else if (result->ret == 1) lookup->value = i_strdup(result->value); } static int client_dict_lookup(struct dict *_dict, const struct dict_op_settings *set, pool_t pool, const char *key, const char **value_r, const char **error_r) { struct client_dict_sync_lookup lookup; i_zero(&lookup); lookup.ret = -2; dict_lookup_async(_dict, set, key, client_dict_lookup_callback, &lookup); if (lookup.ret == -2) client_dict_wait(_dict); switch (lookup.ret) { case -1: *error_r = t_strdup(lookup.error); i_free(lookup.error); return -1; case 0: i_assert(lookup.value == NULL); *value_r = NULL; return 0; case 1: *value_r = p_strdup(pool, lookup.value); i_free(lookup.value); return 1; } i_unreached(); } static void client_dict_iterate_unref(struct client_dict_iterate_context *ctx) { i_assert(ctx->refcount > 0); if (--ctx->refcount > 0) return; i_free(ctx->error); i_free(ctx); } static void client_dict_iter_api_callback(struct client_dict_iterate_context *ctx, struct client_dict_cmd *cmd, const char *const *extra_args) { struct client_dict *dict = cmd->dict; if (ctx->deinit) { /* Iterator was already deinitialized. Stop if we're in client_dict_wait(). */ dict_post_api_callback(&dict->dict); return; } if (ctx->finished) { int diff = timeval_diff_msecs(&ioloop_timeval, &cmd->start_time); if (ctx->error != NULL) { /* include timing info always in error messages */ char *new_error = i_strdup_printf("%s (reply took %s)", ctx->error, dict_warnings_sec(cmd, diff, extra_args)); i_free(ctx->error); ctx->error = new_error; } else if (!cmd->background && diff >= (int)dict->warn_slow_msecs) { e_warning(dict->conn.conn.event, "dict iteration took %s: %s", dict_warnings_sec(cmd, diff, extra_args), cmd->query); } } if (ctx->ctx.async_callback != NULL) { dict_pre_api_callback(&dict->dict); ctx->ctx.async_callback(ctx->ctx.async_context); dict_post_api_callback(&dict->dict); } else { /* synchronous lookup */ io_loop_stop(dict->dict.ioloop); } } static void client_dict_iter_async_callback(struct client_dict_cmd *cmd, enum dict_protocol_reply reply, const char *value, const char *const *extra_args, const char *error, bool disconnected ATTR_UNUSED) { struct client_dict_iterate_context *ctx = cmd->iter; struct client_dict *dict = cmd->dict; struct client_dict_iter_result *result; const char *iter_key = NULL, *const *iter_values = NULL; if (ctx->deinit) { cmd->background = TRUE; client_dict_cmd_backgrounded(dict); } if (error != NULL) { /* failed */ } else switch (reply) { case DICT_PROTOCOL_REPLY_ITER_FINISHED: /* end of iteration */ i_assert(!ctx->finished); ctx->finished = TRUE; client_dict_iter_api_callback(ctx, cmd, extra_args); client_dict_iterate_unref(ctx); return; case DICT_PROTOCOL_REPLY_OK: /* key \t value */ iter_key = value; iter_values = extra_args; extra_args++; break; case DICT_PROTOCOL_REPLY_FAIL: error = t_strdup_printf("dict-server returned failure: %s", value); break; default: break; } if ((iter_values == NULL || iter_values[0] == NULL) && error == NULL) { /* broken protocol */ error = t_strdup_printf("dict client (%s) sent broken iterate reply: %c%s", dict->conn.conn.name, reply, value); client_dict_disconnect(dict, error); } if (error != NULL) { if (ctx->error == NULL) ctx->error = i_strdup(error); i_assert(!ctx->finished); ctx->finished = TRUE; client_dict_iter_api_callback(ctx, cmd, extra_args); client_dict_iterate_unref(ctx); return; } cmd->unfinished = TRUE; if (ctx->deinit) { /* iterator was already deinitialized */ return; } result = array_append_space(&ctx->results); result->key = p_strdup(ctx->results_pool, iter_key); result->values = p_strarray_dup(ctx->results_pool, iter_values); client_dict_iter_api_callback(ctx, cmd, NULL); } static struct dict_iterate_context * client_dict_iterate_init(struct dict *_dict, const struct dict_op_settings *set ATTR_UNUSED, const char *path, enum dict_iterate_flags flags) { struct client_dict_iterate_context *ctx; ctx = i_new(struct client_dict_iterate_context, 1); ctx->ctx.dict = _dict; ctx->results_pool = pool_alloconly_create("client dict iteration", 512); ctx->flags = flags; ctx->path = i_strdup(path); ctx->refcount = 1; i_array_init(&ctx->results, 64); return &ctx->ctx; } static void client_dict_iterate_cmd_send(struct client_dict_iterate_context *ctx) { struct client_dict *dict = (struct client_dict *)ctx->ctx.dict; const struct dict_op_settings_private *set = &ctx->ctx.set; struct client_dict_cmd *cmd; string_t *query = t_str_new(256); /* we can't do this query in _iterate_init(), because _set_limit() hasn't been called yet at that point. */ str_printfa(query, "%c%d\t%"PRIu64, DICT_PROTOCOL_CMD_ITERATE, ctx->flags, ctx->ctx.max_rows); str_append_c(query, '\t'); str_append_tabescaped(query, ctx->path); str_append_c(query, '\t'); str_append_tabescaped(query, set->username == NULL ? "" : set->username); cmd = client_dict_cmd_init(dict, str_c(query)); cmd->iter = ctx; cmd->callback = client_dict_iter_async_callback; cmd->retry_errors = TRUE; ctx->refcount++; client_dict_cmd_send(dict, &cmd, NULL); } static bool client_dict_iterate(struct dict_iterate_context *_ctx, const char **key_r, const char *const **values_r) { struct client_dict_iterate_context *ctx = (struct client_dict_iterate_context *)_ctx; const struct client_dict_iter_result *results; unsigned int count; if (ctx->error != NULL) { ctx->ctx.has_more = FALSE; return FALSE; } results = array_get(&ctx->results, &count); if (ctx->result_idx < count) { *key_r = results[ctx->result_idx].key; *values_r = results[ctx->result_idx].values; ctx->ctx.has_more = TRUE; ctx->result_idx++; ctx->seen_results = TRUE; return TRUE; } if (!ctx->cmd_sent) { ctx->cmd_sent = TRUE; client_dict_iterate_cmd_send(ctx); return client_dict_iterate(_ctx, key_r, values_r); } ctx->ctx.has_more = !ctx->finished; ctx->result_idx = 0; array_clear(&ctx->results); p_clear(ctx->results_pool); if ((ctx->flags & DICT_ITERATE_FLAG_ASYNC) == 0 && ctx->ctx.has_more) { client_dict_wait(_ctx->dict); return client_dict_iterate(_ctx, key_r, values_r); } return FALSE; } static int client_dict_iterate_deinit(struct dict_iterate_context *_ctx, const char **error_r) { struct client_dict *dict = (struct client_dict *)_ctx->dict; struct client_dict_iterate_context *ctx = (struct client_dict_iterate_context *)_ctx; int ret = ctx->error != NULL ? -1 : 0; i_assert(!ctx->deinit); ctx->deinit = TRUE; *error_r = t_strdup(ctx->error); array_free(&ctx->results); pool_unref(&ctx->results_pool); i_free(ctx->path); client_dict_iterate_unref(ctx); client_dict_add_timeout(dict); return ret; } static struct dict_transaction_context * client_dict_transaction_init(struct dict *_dict) { struct client_dict *dict = (struct client_dict *)_dict; struct client_dict_transaction_context *ctx; ctx = i_new(struct client_dict_transaction_context, 1); ctx->ctx.dict = _dict; ctx->id = ++dict->transaction_id_counter; DLLIST_PREPEND(&dict->transactions, ctx); return &ctx->ctx; } static void client_dict_transaction_free(struct client_dict_transaction_context **_ctx) { struct client_dict_transaction_context *ctx = *_ctx; *_ctx = NULL; i_free(ctx->first_query); i_free(ctx->error); i_free(ctx); } static void client_dict_transaction_commit_callback(struct client_dict_cmd *cmd, enum dict_protocol_reply reply, const char *value, const char *const *extra_args, const char *error, bool disconnected) { struct client_dict *dict = cmd->dict; struct dict_commit_result result = { .ret = DICT_COMMIT_RET_FAILED, .error = NULL }; i_assert(cmd->trans != NULL); if (error != NULL) { /* failed */ if (disconnected) result.ret = DICT_COMMIT_RET_WRITE_UNCERTAIN; result.error = error; } else switch (reply) { case DICT_PROTOCOL_REPLY_OK: result.ret = DICT_COMMIT_RET_OK; break; case DICT_PROTOCOL_REPLY_NOTFOUND: result.ret = DICT_COMMIT_RET_NOTFOUND; break; case DICT_PROTOCOL_REPLY_WRITE_UNCERTAIN: result.ret = DICT_COMMIT_RET_WRITE_UNCERTAIN; /* fallthrough */ case DICT_PROTOCOL_REPLY_FAIL: { /* value contains the obsolete trans_id */ const char *error = extra_args[0]; result.error = t_strdup_printf("dict-server returned failure: %s", error != NULL ? t_str_tabunescape(error) : ""); if (error != NULL) extra_args++; break; } default: result.ret = DICT_COMMIT_RET_FAILED; result.error = t_strdup_printf( "dict-client: Invalid commit reply: %c%s", reply, value); client_dict_disconnect(dict, result.error); break; } int diff = timeval_diff_msecs(&ioloop_timeval, &cmd->start_time); if (result.error != NULL) { /* include timing info always in error messages */ result.error = t_strdup_printf("%s (reply took %s)", result.error, dict_warnings_sec(cmd, diff, extra_args)); } else if (!cmd->background && !cmd->trans->ctx.no_slowness_warning && diff >= (int)dict->warn_slow_msecs) { e_warning(dict->conn.conn.event, "dict commit took %s: " "%s (%u commands, first: %s)", dict_warnings_sec(cmd, diff, extra_args), cmd->query, cmd->trans->query_count, cmd->trans->first_query); } client_dict_transaction_free(&cmd->trans); dict_pre_api_callback(&dict->dict); cmd->api_callback.commit(&result, cmd->api_callback.context); dict_post_api_callback(&dict->dict); } static void client_dict_transaction_commit(struct dict_transaction_context *_ctx, bool async, dict_transaction_commit_callback_t *callback, void *context) { struct client_dict_transaction_context *ctx = (struct client_dict_transaction_context *)_ctx; struct client_dict *dict = (struct client_dict *)_ctx->dict; struct client_dict_cmd *cmd; const char *query; DLLIST_REMOVE(&dict->transactions, ctx); if (ctx->sent_begin && ctx->error == NULL) { query = t_strdup_printf("%c%u", DICT_PROTOCOL_CMD_COMMIT, ctx->id); cmd = client_dict_cmd_init(dict, query); cmd->trans = ctx; cmd->callback = client_dict_transaction_commit_callback; cmd->api_callback.commit = callback; cmd->api_callback.context = context; if (callback == dict_transaction_commit_async_noop_callback) cmd->background = TRUE; if (client_dict_cmd_send(dict, &cmd, NULL)) { if (!async) client_dict_wait(_ctx->dict); } } else if (ctx->error != NULL) { /* already failed */ struct dict_commit_result result = { .ret = DICT_COMMIT_RET_FAILED, .error = ctx->error }; callback(&result, context); client_dict_transaction_free(&ctx); } else { /* nothing changed */ struct dict_commit_result result = { .ret = DICT_COMMIT_RET_OK, .error = NULL }; callback(&result, context); client_dict_transaction_free(&ctx); } client_dict_add_timeout(dict); } static void client_dict_transaction_rollback(struct dict_transaction_context *_ctx) { struct client_dict_transaction_context *ctx = (struct client_dict_transaction_context *)_ctx; struct client_dict *dict = (struct client_dict *)_ctx->dict; if (ctx->sent_begin) { const char *query; query = t_strdup_printf("%c%u", DICT_PROTOCOL_CMD_ROLLBACK, ctx->id); client_dict_send_transaction_query(ctx, query); } DLLIST_REMOVE(&dict->transactions, ctx); client_dict_transaction_free(&ctx); client_dict_add_timeout(dict); } static void client_dict_set(struct dict_transaction_context *_ctx, const char *key, const char *value) { struct client_dict_transaction_context *ctx = (struct client_dict_transaction_context *)_ctx; const char *query; query = t_strdup_printf("%c%u\t%s\t%s", DICT_PROTOCOL_CMD_SET, ctx->id, str_tabescape(key), str_tabescape(value)); client_dict_send_transaction_query(ctx, query); } static void client_dict_unset(struct dict_transaction_context *_ctx, const char *key) { struct client_dict_transaction_context *ctx = (struct client_dict_transaction_context *)_ctx; const char *query; query = t_strdup_printf("%c%u\t%s", DICT_PROTOCOL_CMD_UNSET, ctx->id, str_tabescape(key)); client_dict_send_transaction_query(ctx, query); } static void client_dict_atomic_inc(struct dict_transaction_context *_ctx, const char *key, long long diff) { struct client_dict_transaction_context *ctx = (struct client_dict_transaction_context *)_ctx; const char *query; query = t_strdup_printf("%c%u\t%s\t%lld", DICT_PROTOCOL_CMD_ATOMIC_INC, ctx->id, str_tabescape(key), diff); client_dict_send_transaction_query(ctx, query); } static void client_dict_set_timestamp(struct dict_transaction_context *_ctx, const struct timespec *ts) { struct client_dict_transaction_context *ctx = (struct client_dict_transaction_context *)_ctx; const char *query; query = t_strdup_printf("%c%u\t%s\t%u", DICT_PROTOCOL_CMD_TIMESTAMP, ctx->id, dec2str(ts->tv_sec), (unsigned int)ts->tv_nsec); client_dict_send_transaction_query(ctx, query); } static void client_dict_set_hide_log_values(struct dict_transaction_context *_ctx, bool hide_log_values) { struct client_dict_transaction_context *ctx = container_of(_ctx, struct client_dict_transaction_context, ctx); const char *query; query = t_strdup_printf("%c%u\t%s", DICT_PROTOCOL_CMD_HIDE_LOG_VALUES, ctx->id, hide_log_values ? "yes" : "no"); client_dict_send_transaction_query(ctx, query); } struct dict dict_driver_client = { .name = "proxy", { .init = client_dict_init, .deinit = client_dict_deinit, .wait = client_dict_wait, .lookup = client_dict_lookup, .iterate_init = client_dict_iterate_init, .iterate = client_dict_iterate, .iterate_deinit = client_dict_iterate_deinit, .transaction_init = client_dict_transaction_init, .transaction_commit = client_dict_transaction_commit, .transaction_rollback = client_dict_transaction_rollback, .set = client_dict_set, .unset = client_dict_unset, .atomic_inc = client_dict_atomic_inc, .lookup_async = client_dict_lookup_async, .switch_ioloop = client_dict_switch_ioloop, .set_timestamp = client_dict_set_timestamp, .set_hide_log_values = client_dict_set_hide_log_values, } }; dovecot-2.3.21.1/src/lib-dict/dict.c0000644000000000000000000004754214656633576013676 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "guid.h" #include "llist.h" #include "ioloop.h" #include "str.h" #include "ioloop.h" #include "dict-private.h" struct dict_commit_callback_ctx { pool_t pool; struct dict_commit_callback_ctx *prev, *next; struct dict *dict; struct event *event; dict_transaction_commit_callback_t *callback; struct dict_op_settings_private set; struct timeout *to; void *context; struct dict_commit_result result; bool delayed_callback:1; }; struct dict_lookup_callback_ctx { struct dict *dict; struct event *event; dict_lookup_callback_t *callback; void *context; }; static ARRAY(struct dict *) dict_drivers; static void dict_commit_async_timeout(struct dict_commit_callback_ctx *ctx); static struct event_category event_category_dict = { .name = "dict", }; static struct dict *dict_driver_lookup(const char *name) { struct dict *dict; array_foreach_elem(&dict_drivers, dict) { if (strcmp(dict->name, name) == 0) return dict; } return NULL; } void dict_transaction_commit_async_noop_callback( const struct dict_commit_result *result ATTR_UNUSED, void *context ATTR_UNUSED) { /* do nothing */ } void dict_driver_register(struct dict *driver) { if (!array_is_created(&dict_drivers)) i_array_init(&dict_drivers, 8); if (dict_driver_lookup(driver->name) != NULL) { i_fatal("dict_driver_register(%s): Already registered", driver->name); } array_push_back(&dict_drivers, &driver); } void dict_driver_unregister(struct dict *driver) { struct dict *const *dicts; unsigned int idx = UINT_MAX; array_foreach(&dict_drivers, dicts) { if (*dicts == driver) { idx = array_foreach_idx(&dict_drivers, dicts); break; } } i_assert(idx != UINT_MAX); array_delete(&dict_drivers, idx, 1); if (array_count(&dict_drivers) == 0) array_free(&dict_drivers); } int dict_init(const char *uri, const struct dict_settings *set, struct dict **dict_r, const char **error_r) { struct dict_settings set_dup = *set; struct dict *dict; const char *p, *name, *error; p = strchr(uri, ':'); if (p == NULL) { *error_r = t_strdup_printf("Dictionary URI is missing ':': %s", uri); return -1; } name = t_strdup_until(uri, p); dict = dict_driver_lookup(name); if (dict == NULL) { *error_r = t_strdup_printf("Unknown dict module: %s", name); return -1; } struct event *event = event_create(set->event_parent); event_add_category(event, &event_category_dict); event_add_str(event, "driver", dict->name); event_set_append_log_prefix(event, t_strdup_printf("dict(%s): ", dict->name)); set_dup.event_parent = event; if (dict->v.init(dict, p+1, &set_dup, dict_r, &error) < 0) { *error_r = t_strdup_printf("dict %s: %s", name, error); event_unref(&event); return -1; } i_assert(*dict_r != NULL); (*dict_r)->refcount++; (*dict_r)->event = event; e_debug(event_create_passthrough(event)->set_name("dict_created")->event(), "dict created (uri=%s, base_dir=%s)", uri, set->base_dir); return 0; } static void dict_ref(struct dict *dict) { i_assert(dict->refcount > 0); dict->refcount++; } static void dict_unref(struct dict **_dict) { struct dict *dict = *_dict; *_dict = NULL; if (dict == NULL) return; struct event *event = dict->event; i_assert(dict->refcount > 0); if (--dict->refcount == 0) { dict->v.deinit(dict); e_debug(event_create_passthrough(event)-> set_name("dict_destroyed")->event(), "dict destroyed"); event_unref(&event); } } void dict_deinit(struct dict **_dict) { struct dict *dict = *_dict; *_dict = NULL; i_assert(dict->iter_count == 0); i_assert(dict->transaction_count == 0); i_assert(dict->transactions == NULL); i_assert(dict->commits == NULL); dict_unref(&dict); } void dict_wait(struct dict *dict) { struct dict_commit_callback_ctx *commit, *next; e_debug(dict->event, "Waiting for dict to finish pending operations"); if (dict->v.wait != NULL) dict->v.wait(dict); for (commit = dict->commits; commit != NULL; commit = next) { next = commit->next; dict_commit_async_timeout(commit); } } bool dict_switch_ioloop(struct dict *dict) { struct dict_commit_callback_ctx *commit; bool ret = FALSE; for (commit = dict->commits; commit != NULL; commit = commit->next) { commit->to = io_loop_move_timeout(&commit->to); ret = TRUE; } if (dict->v.switch_ioloop != NULL) { if (dict->v.switch_ioloop(dict)) return TRUE; } return ret; } static bool dict_key_prefix_is_valid(const char *key, const char *username) { if (str_begins(key, DICT_PATH_SHARED)) return TRUE; if (str_begins(key, DICT_PATH_PRIVATE)) { i_assert(username != NULL && username[0] != '\0'); return TRUE; } return FALSE; } void dict_pre_api_callback(struct dict *dict) { if (dict->prev_ioloop != NULL) { /* Don't let callback see that we've created our internal ioloop in case it wants to add some ios or timeouts. */ io_loop_set_current(dict->prev_ioloop); } } void dict_post_api_callback(struct dict *dict) { if (dict->prev_ioloop != NULL) { io_loop_set_current(dict->ioloop); io_loop_stop(dict->ioloop); } } static void dict_lookup_finished(struct event *event, int ret, const char *error) { i_assert(ret >= 0 || error != NULL); const char *key = event_find_field_recursive_str(event, "key"); if (ret < 0) event_add_str(event, "error", error); else if (ret == 0) event_add_str(event, "key_not_found", "yes"); event_set_name(event, "dict_lookup_finished"); e_debug(event, "Lookup finished for '%s': %s", key, ret > 0 ? "found" : "not found"); } static void dict_transaction_finished(struct event *event, enum dict_commit_ret ret, bool rollback, const char *error) { i_assert(ret > DICT_COMMIT_RET_FAILED || error != NULL); if (ret == DICT_COMMIT_RET_FAILED || ret == DICT_COMMIT_RET_WRITE_UNCERTAIN) { if (ret == DICT_COMMIT_RET_WRITE_UNCERTAIN) event_add_str(event, "write_uncertain", "yes"); event_add_str(event, "error", error); } else if (rollback) { event_add_str(event, "rollback", "yes"); } else if (ret == 0) { event_add_str(event, "key_not_found", "yes"); } event_set_name(event, "dict_transaction_finished"); e_debug(event, "Dict transaction finished"); } static void dict_lookup_callback(const struct dict_lookup_result *result, void *context) { struct dict_lookup_callback_ctx *ctx = context; dict_pre_api_callback(ctx->dict); ctx->callback(result, ctx->context); dict_post_api_callback(ctx->dict); dict_lookup_finished(ctx->event, result->ret, result->error); event_unref(&ctx->event); dict_unref(&ctx->dict); i_free(ctx); } static void dict_commit_async_timeout(struct dict_commit_callback_ctx *ctx) { DLLIST_REMOVE(&ctx->dict->commits, ctx); timeout_remove(&ctx->to); dict_pre_api_callback(ctx->dict); if (ctx->callback != NULL) ctx->callback(&ctx->result, ctx->context); else if (ctx->result.ret < 0) e_error(ctx->event, "Commit failed: %s", ctx->result.error); dict_post_api_callback(ctx->dict); dict_transaction_finished(ctx->event, ctx->result.ret, FALSE, ctx->result.error); dict_op_settings_private_free(&ctx->set); event_unref(&ctx->event); dict_unref(&ctx->dict); pool_unref(&ctx->pool); } static void dict_commit_callback(const struct dict_commit_result *result, void *context) { struct dict_commit_callback_ctx *ctx = context; i_assert(result->ret >= 0 || result->error != NULL); ctx->result = *result; if (ctx->delayed_callback) { ctx->result.error = p_strdup(ctx->pool, ctx->result.error); ctx->to = timeout_add_short(0, dict_commit_async_timeout, ctx); } else { dict_commit_async_timeout(ctx); } } static struct event * dict_event_create(struct dict *dict, const struct dict_op_settings *set) { struct event *event = event_create(dict->event); if (set->username != NULL) event_add_str(event, "user", set->username); return event; } int dict_lookup(struct dict *dict, const struct dict_op_settings *set, pool_t pool, const char *key, const char **value_r, const char **error_r) { struct event *event = dict_event_create(dict, set); int ret; i_assert(dict_key_prefix_is_valid(key, set->username)); e_debug(event, "Looking up '%s'", key); event_add_str(event, "key", key); ret = dict->v.lookup(dict, set, pool, key, value_r, error_r); dict_lookup_finished(event, ret, *error_r); event_unref(&event); return ret; } #undef dict_lookup_async void dict_lookup_async(struct dict *dict, const struct dict_op_settings *set, const char *key, dict_lookup_callback_t *callback, void *context) { i_assert(dict_key_prefix_is_valid(key, set->username)); if (dict->v.lookup_async == NULL) { struct dict_lookup_result result; i_zero(&result); /* event is going to be sent by dict_lookup */ result.ret = dict_lookup(dict, set, pool_datastack_create(), key, &result.value, &result.error); const char *const values[] = { result.value, NULL }; result.values = values; callback(&result, context); return; } struct dict_lookup_callback_ctx *lctx = i_new(struct dict_lookup_callback_ctx, 1); lctx->dict = dict; dict_ref(lctx->dict); lctx->callback = callback; lctx->context = context; lctx->event = dict_event_create(dict, set); event_add_str(lctx->event, "key", key); e_debug(lctx->event, "Looking up (async) '%s'", key); dict->v.lookup_async(dict, set, key, dict_lookup_callback, lctx); } struct dict_iterate_context * dict_iterate_init(struct dict *dict, const struct dict_op_settings *set, const char *path, enum dict_iterate_flags flags) { struct dict_iterate_context *ctx; i_assert(path != NULL); i_assert(dict_key_prefix_is_valid(path, set->username)); if (dict->v.iterate_init == NULL) { /* not supported by backend */ ctx = &dict_iter_unsupported; } else { ctx = dict->v.iterate_init(dict, set, path, flags); } /* the dict in context can differ from the dict passed as parameter, e.g. it can be dict-fail when iteration is not supported. */ ctx->event = dict_event_create(dict, set); ctx->flags = flags; dict_op_settings_dup(set, &ctx->set); event_add_str(ctx->event, "key", path); event_set_name(ctx->event, "dict_iteration_started"); e_debug(ctx->event, "Iterating prefix %s", path); ctx->dict->iter_count++; return ctx; } bool dict_iterate(struct dict_iterate_context *ctx, const char **key_r, const char **value_r) { const char *const *values; if (!dict_iterate_values(ctx, key_r, &values)) return FALSE; if ((ctx->flags & DICT_ITERATE_FLAG_NO_VALUE) == 0) *value_r = values[0]; return TRUE; } bool dict_iterate_values(struct dict_iterate_context *ctx, const char **key_r, const char *const **values_r) { if (ctx->max_rows > 0 && ctx->row_count >= ctx->max_rows) { e_debug(ctx->event, "Maximum row count (%"PRIu64") reached", ctx->max_rows); /* row count was limited */ ctx->has_more = FALSE; return FALSE; } if (!ctx->dict->v.iterate(ctx, key_r, values_r)) return FALSE; if ((ctx->flags & DICT_ITERATE_FLAG_NO_VALUE) != 0) { /* always return value as NULL to be consistent across drivers */ *values_r = NULL; } else { i_assert(values_r[0] != NULL); } ctx->row_count++; return TRUE; } #undef dict_iterate_set_async_callback void dict_iterate_set_async_callback(struct dict_iterate_context *ctx, dict_iterate_callback_t *callback, void *context) { ctx->async_callback = callback; ctx->async_context = context; } void dict_iterate_set_limit(struct dict_iterate_context *ctx, uint64_t max_rows) { ctx->max_rows = max_rows; } bool dict_iterate_has_more(struct dict_iterate_context *ctx) { return ctx->has_more; } int dict_iterate_deinit(struct dict_iterate_context **_ctx, const char **error_r) { struct dict_iterate_context *ctx = *_ctx; if (ctx == NULL) return 0; struct event *event = ctx->event; int ret; uint64_t rows; i_assert(ctx->dict->iter_count > 0); ctx->dict->iter_count--; *_ctx = NULL; rows = ctx->row_count; struct dict_op_settings_private set_copy = ctx->set; ret = ctx->dict->v.iterate_deinit(ctx, error_r); dict_op_settings_private_free(&set_copy); event_add_int(event, "rows", rows); event_set_name(event, "dict_iteration_finished"); if (ret < 0) { event_add_str(event, "error", *error_r); e_debug(event, "Iteration finished: %s", *error_r); } else { if (rows == 0) event_add_str(event, "key_not_found", "yes"); e_debug(event, "Iteration finished, got %"PRIu64" rows", rows); } event_unref(&event); return ret; } struct dict_transaction_context * dict_transaction_begin(struct dict *dict, const struct dict_op_settings *set) { struct dict_transaction_context *ctx; guid_128_t guid; if (dict->v.transaction_init == NULL) ctx = &dict_transaction_unsupported; else ctx = dict->v.transaction_init(dict); /* the dict in context can differ from the dict passed as parameter, e.g. it can be dict-fail when transactions are not supported. */ ctx->dict->transaction_count++; DLLIST_PREPEND(&ctx->dict->transactions, ctx); ctx->event = dict_event_create(dict, set); dict_op_settings_dup(set, &ctx->set); guid_128_generate(guid); event_add_str(ctx->event, "txid", guid_128_to_string(guid)); event_set_name(ctx->event, "dict_transaction_started"); e_debug(ctx->event, "Starting transaction"); return ctx; } void dict_transaction_no_slowness_warning(struct dict_transaction_context *ctx) { ctx->no_slowness_warning = TRUE; } void dict_transaction_set_hide_log_values(struct dict_transaction_context *ctx, bool hide_log_values) { /* Apply hide_log_values to the current transactions dict op settings */ ctx->set.hide_log_values = hide_log_values; if (ctx->dict->v.set_hide_log_values != NULL) ctx->dict->v.set_hide_log_values(ctx, hide_log_values); } void dict_transaction_set_timestamp(struct dict_transaction_context *ctx, const struct timespec *ts) { /* These asserts are mainly here to guarantee a possibility in future to change the API to support multiple timestamps within the same transaction, so this call would apply only to the following changes. */ i_assert(!ctx->changed); i_assert(ctx->timestamp.tv_sec == 0); i_assert(ts->tv_sec > 0); ctx->timestamp = *ts; struct event_passthrough *e = event_create_passthrough(ctx->event)-> set_name("dict_set_timestamp"); e_debug(e->event(), "Setting timestamp on transaction to (%"PRIdTIME_T", %ld)", ts->tv_sec, ts->tv_nsec); if (ctx->dict->v.set_timestamp != NULL) ctx->dict->v.set_timestamp(ctx, ts); } struct dict_commit_sync_result { int ret; char *error; }; static void dict_transaction_commit_sync_callback(const struct dict_commit_result *result, void *context) { struct dict_commit_sync_result *sync_result = context; sync_result->ret = result->ret; sync_result->error = i_strdup(result->error); } int dict_transaction_commit(struct dict_transaction_context **_ctx, const char **error_r) { pool_t pool = pool_alloconly_create("dict_commit_callback_ctx", 64); struct dict_commit_callback_ctx *cctx = p_new(pool, struct dict_commit_callback_ctx, 1); struct dict_transaction_context *ctx = *_ctx; struct dict_commit_sync_result result; *_ctx = NULL; cctx->pool = pool; i_zero(&result); i_assert(ctx->dict->transaction_count > 0); ctx->dict->transaction_count--; DLLIST_REMOVE(&ctx->dict->transactions, ctx); DLLIST_PREPEND(&ctx->dict->commits, cctx); cctx->dict = ctx->dict; dict_ref(cctx->dict); cctx->callback = dict_transaction_commit_sync_callback; cctx->context = &result; cctx->event = ctx->event; cctx->set = ctx->set; ctx->dict->v.transaction_commit(ctx, FALSE, dict_commit_callback, cctx); *error_r = t_strdup(result.error); i_free(result.error); return result.ret; } #undef dict_transaction_commit_async void dict_transaction_commit_async(struct dict_transaction_context **_ctx, dict_transaction_commit_callback_t *callback, void *context) { pool_t pool = pool_alloconly_create("dict_commit_callback_ctx", 64); struct dict_commit_callback_ctx *cctx = p_new(pool, struct dict_commit_callback_ctx, 1); struct dict_transaction_context *ctx = *_ctx; *_ctx = NULL; i_assert(ctx->dict->transaction_count > 0); ctx->dict->transaction_count--; DLLIST_REMOVE(&ctx->dict->transactions, ctx); DLLIST_PREPEND(&ctx->dict->commits, cctx); if (callback == NULL) callback = dict_transaction_commit_async_noop_callback; cctx->pool = pool; cctx->dict = ctx->dict; dict_ref(cctx->dict); cctx->callback = callback; cctx->context = context; cctx->event = ctx->event; cctx->set = ctx->set; cctx->delayed_callback = TRUE; ctx->dict->v.transaction_commit(ctx, TRUE, dict_commit_callback, cctx); cctx->delayed_callback = FALSE; } void dict_transaction_commit_async_nocallback( struct dict_transaction_context **ctx) { dict_transaction_commit_async(ctx, NULL, NULL); } void dict_transaction_rollback(struct dict_transaction_context **_ctx) { struct dict_transaction_context *ctx = *_ctx; if (ctx == NULL) return; struct event *event = ctx->event; *_ctx = NULL; i_assert(ctx->dict->transaction_count > 0); ctx->dict->transaction_count--; DLLIST_REMOVE(&ctx->dict->transactions, ctx); struct dict_op_settings_private set_copy = ctx->set; ctx->dict->v.transaction_rollback(ctx); dict_transaction_finished(event, DICT_COMMIT_RET_OK, TRUE, NULL); dict_op_settings_private_free(&set_copy); event_unref(&event); } void dict_set(struct dict_transaction_context *ctx, const char *key, const char *value) { i_assert(dict_key_prefix_is_valid(key, ctx->set.username)); struct event_passthrough *e = event_create_passthrough(ctx->event)-> set_name("dict_set_key")-> add_str("key", key); e_debug(e->event(), "Setting '%s' to '%s'", key, value); T_BEGIN { ctx->dict->v.set(ctx, key, value); } T_END; ctx->changed = TRUE; } void dict_unset(struct dict_transaction_context *ctx, const char *key) { i_assert(dict_key_prefix_is_valid(key, ctx->set.username)); struct event_passthrough *e = event_create_passthrough(ctx->event)-> set_name("dict_unset_key")-> add_str("key", key); e_debug(e->event(), "Unsetting '%s'", key); T_BEGIN { ctx->dict->v.unset(ctx, key); } T_END; ctx->changed = TRUE; } void dict_atomic_inc(struct dict_transaction_context *ctx, const char *key, long long diff) { i_assert(dict_key_prefix_is_valid(key, ctx->set.username)); struct event_passthrough *e = event_create_passthrough(ctx->event)-> set_name("dict_increment_key")-> add_str("key", key); e_debug(e->event(), "Incrementing '%s' with %lld", key, diff); if (diff != 0) T_BEGIN { ctx->dict->v.atomic_inc(ctx, key, diff); ctx->changed = TRUE; } T_END; } const char *dict_escape_string(const char *str) { const char *p; string_t *ret; /* see if we need to escape it */ for (p = str; *p != '\0'; p++) { if (*p == '/' || *p == '\\') break; } if (*p == '\0') return str; /* escape */ ret = t_str_new((size_t) (p - str) + 128); str_append_data(ret, str, (size_t) (p - str)); for (; *p != '\0'; p++) { switch (*p) { case '/': str_append_c(ret, '\\'); str_append_c(ret, '|'); break; case '\\': str_append_c(ret, '\\'); str_append_c(ret, '\\'); break; default: str_append_c(ret, *p); break; } } return str_c(ret); } const char *dict_unescape_string(const char *str) { const char *p; string_t *ret; /* see if we need to unescape it */ for (p = str; *p != '\0'; p++) { if (*p == '\\') break; } if (*p == '\0') return str; /* unescape */ ret = t_str_new((size_t) (p - str) + strlen(p) + 1); str_append_data(ret, str, (size_t) (p - str)); for (; *p != '\0'; p++) { if (*p != '\\') str_append_c(ret, *p); else { if (*++p == '|') str_append_c(ret, '/'); else if (*p == '\0') break; else str_append_c(ret, *p); } } return str_c(ret); } void dict_op_settings_dup(const struct dict_op_settings *source, struct dict_op_settings_private *dest_r) { i_zero(dest_r); dest_r->username = i_strdup(source->username); dest_r->home_dir = i_strdup(source->home_dir); dest_r->hide_log_values = source->hide_log_values; } void dict_op_settings_private_free(struct dict_op_settings_private *set) { i_free(set->username); i_free(set->home_dir); } dovecot-2.3.21.1/src/lib-dict/dict-memcached.c0000644000000000000000000002351614656633576015575 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING memcached */ #include "lib.h" #include "array.h" #include "str.h" #include "istream.h" #include "ostream.h" #include "connection.h" #include "dict-private.h" #define MEMCACHED_DEFAULT_PORT 11211 #define MEMCACHED_DEFAULT_LOOKUP_TIMEOUT_MSECS (1000*30) /* we need only very limited memcached functionality, so just define the binary protocol ourself instead requiring protocol_binary.h */ #define MEMCACHED_REQUEST_HDR_MAGIC 0x80 #define MEMCACHED_REPLY_HDR_MAGIC 0x81 #define MEMCACHED_REQUEST_HDR_LENGTH 24 #define MEMCACHED_REPLY_HDR_LENGTH 24 #define MEMCACHED_CMD_GET 0x00 #define MEMCACHED_DATA_TYPE_RAW 0x00 enum memcached_response { MEMCACHED_RESPONSE_OK = 0x0000, MEMCACHED_RESPONSE_NOTFOUND = 0x0001, MEMCACHED_RESPONSE_INTERNALERROR= 0x0084, MEMCACHED_RESPONSE_BUSY = 0x0085, MEMCACHED_RESPONSE_TEMPFAILURE = 0x0086, }; struct memcached_connection { struct connection conn; struct memcached_dict *dict; buffer_t *cmd; struct { const unsigned char *value; size_t value_len; uint16_t status; /* enum memcached_response */ bool reply_received; } reply; }; struct memcached_dict { struct dict dict; struct ip_addr ip; char *key_prefix; in_port_t port; unsigned int timeout_msecs; struct memcached_connection conn; bool connected; }; static struct connection_list *memcached_connections; static void memcached_conn_destroy(struct connection *_conn) { struct memcached_connection *conn = (struct memcached_connection *)_conn; conn->dict->connected = FALSE; connection_disconnect(_conn); if (conn->dict->dict.ioloop != NULL) io_loop_stop(conn->dict->dict.ioloop); } static int memcached_input_get(struct memcached_connection *conn) { const unsigned char *data; size_t size; uint32_t body_len, value_pos; uint16_t key_len, key_pos, status; uint8_t extras_len, data_type; data = i_stream_get_data(conn->conn.input, &size); if (size < MEMCACHED_REPLY_HDR_LENGTH) return 0; if (data[0] != MEMCACHED_REPLY_HDR_MAGIC) { e_error(conn->conn.event, "Invalid reply magic: %u != %u", data[0], MEMCACHED_REPLY_HDR_MAGIC); return -1; } memcpy(&body_len, data+8, 4); body_len = ntohl(body_len); body_len += MEMCACHED_REPLY_HDR_LENGTH; if (size < body_len) { /* we haven't read the whole response yet */ return 0; } memcpy(&key_len, data+2, 2); key_len = ntohs(key_len); extras_len = data[4]; data_type = data[5]; memcpy(&status, data+6, 2); status = ntohs(status); if (data_type != MEMCACHED_DATA_TYPE_RAW) { e_error(conn->conn.event, "Unsupported data type: %u != %u", data[0], MEMCACHED_DATA_TYPE_RAW); return -1; } key_pos = MEMCACHED_REPLY_HDR_LENGTH + extras_len; value_pos = key_pos + key_len; if (value_pos > body_len) { e_error(conn->conn.event, "Invalid key/extras lengths"); return -1; } conn->reply.value = data + value_pos; conn->reply.value_len = body_len - value_pos; conn->reply.status = status; i_stream_skip(conn->conn.input, body_len); conn->reply.reply_received = TRUE; if (conn->dict->dict.ioloop != NULL) io_loop_stop(conn->dict->dict.ioloop); return 1; } static void memcached_conn_input(struct connection *_conn) { struct memcached_connection *conn = (struct memcached_connection *)_conn; switch (i_stream_read(_conn->input)) { case 0: return; case -1: memcached_conn_destroy(_conn); return; default: break; } if (memcached_input_get(conn) < 0) memcached_conn_destroy(_conn); } static void memcached_conn_connected(struct connection *_conn, bool success) { struct memcached_connection *conn = (struct memcached_connection *)_conn; if (!success) { e_error(conn->conn.event, "connect() failed: %m"); } else { conn->dict->connected = TRUE; } if (conn->dict->dict.ioloop != NULL) io_loop_stop(conn->dict->dict.ioloop); } static const struct connection_settings memcached_conn_set = { .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, .client = TRUE }; static const struct connection_vfuncs memcached_conn_vfuncs = { .destroy = memcached_conn_destroy, .input = memcached_conn_input, .client_connected = memcached_conn_connected }; static int memcached_dict_init(struct dict *driver, const char *uri, const struct dict_settings *set, struct dict **dict_r, const char **error_r) { struct memcached_dict *dict; const char *const *args; int ret = 0; if (memcached_connections == NULL) { memcached_connections = connection_list_init(&memcached_conn_set, &memcached_conn_vfuncs); } dict = i_new(struct memcached_dict, 1); if (net_addr2ip("127.0.0.1", &dict->ip) < 0) i_unreached(); dict->port = MEMCACHED_DEFAULT_PORT; dict->timeout_msecs = MEMCACHED_DEFAULT_LOOKUP_TIMEOUT_MSECS; dict->key_prefix = i_strdup(""); args = t_strsplit(uri, ":"); for (; *args != NULL; args++) { if (str_begins(*args, "host=")) { if (net_addr2ip(*args+5, &dict->ip) < 0) { *error_r = t_strdup_printf("Invalid IP: %s", *args+5); ret = -1; } } else if (str_begins(*args, "port=")) { if (net_str2port(*args+5, &dict->port) < 0) { *error_r = t_strdup_printf("Invalid port: %s", *args+5); ret = -1; } } else if (str_begins(*args, "prefix=")) { i_free(dict->key_prefix); dict->key_prefix = i_strdup(*args + 7); } else if (str_begins(*args, "timeout_msecs=")) { if (str_to_uint(*args+14, &dict->timeout_msecs) < 0) { *error_r = t_strdup_printf( "Invalid timeout_msecs: %s", *args+14); ret = -1; } } else { *error_r = t_strdup_printf("Unknown parameter: %s", *args); ret = -1; } } if (ret < 0) { i_free(dict->key_prefix); i_free(dict); return -1; } dict->conn.conn.event_parent = set->event_parent; connection_init_client_ip(memcached_connections, &dict->conn.conn, NULL, &dict->ip, dict->port); event_set_append_log_prefix(dict->conn.conn.event, "memcached: "); dict->dict = *driver; dict->conn.cmd = buffer_create_dynamic(default_pool, 256); dict->conn.dict = dict; *dict_r = &dict->dict; return 0; } static void memcached_dict_deinit(struct dict *_dict) { struct memcached_dict *dict = (struct memcached_dict *)_dict; connection_deinit(&dict->conn.conn); buffer_free(&dict->conn.cmd); i_free(dict->key_prefix); i_free(dict); if (memcached_connections->connections == NULL) connection_list_deinit(&memcached_connections); } static void memcached_dict_lookup_timeout(struct memcached_dict *dict) { e_error(dict->dict.event, "Lookup timed out in %u.%03u secs", dict->timeout_msecs/1000, dict->timeout_msecs%1000); io_loop_stop(dict->dict.ioloop); } static void memcached_add_header(buffer_t *buf, unsigned int key_len) { uint32_t body_len = htonl(key_len); i_assert(key_len <= 0xffff); buffer_append_c(buf, MEMCACHED_REQUEST_HDR_MAGIC); buffer_append_c(buf, MEMCACHED_CMD_GET); buffer_append_c(buf, (key_len >> 8) & 0xff); buffer_append_c(buf, key_len & 0xff); buffer_append_c(buf, 0); /* extras length */ buffer_append_c(buf, MEMCACHED_DATA_TYPE_RAW); buffer_append_zero(buf, 2); /* vbucket id - we probably don't care? */ buffer_append(buf, &body_len, sizeof(body_len)); buffer_append_zero(buf, 4+8); /* opaque + cas */ i_assert(buf->used == MEMCACHED_REQUEST_HDR_LENGTH); } static int memcached_dict_lookup(struct dict *_dict, const struct dict_op_settings *set ATTR_UNUSED, pool_t pool, const char *key, const char **value_r, const char **error_r) { struct memcached_dict *dict = (struct memcached_dict *)_dict; struct ioloop *prev_ioloop = current_ioloop; struct timeout *to; size_t key_len; if (str_begins(key, DICT_PATH_SHARED)) key += strlen(DICT_PATH_SHARED); else { *error_r = t_strdup_printf("memcached: Only shared keys supported currently"); return -1; } if (*dict->key_prefix != '\0') key = t_strconcat(dict->key_prefix, key, NULL); key_len = strlen(key); if (key_len > 0xffff) { *error_r = t_strdup_printf( "memcached: Key is too long (%zu bytes): %s", key_len, key); return -1; } i_assert(dict->dict.ioloop == NULL); dict->dict.ioloop = io_loop_create(); connection_switch_ioloop(&dict->conn.conn); if (dict->conn.conn.fd_in == -1 && connection_client_connect(&dict->conn.conn) < 0) { e_error(dict->conn.conn.event, "Couldn't connect"); } else { to = timeout_add(dict->timeout_msecs, memcached_dict_lookup_timeout, dict); if (!dict->connected) { /* wait for connection */ io_loop_run(dict->dict.ioloop); } if (dict->connected) { buffer_set_used_size(dict->conn.cmd, 0); memcached_add_header(dict->conn.cmd, key_len); buffer_append(dict->conn.cmd, key, key_len); o_stream_nsend(dict->conn.conn.output, dict->conn.cmd->data, dict->conn.cmd->used); i_zero(&dict->conn.reply); io_loop_run(dict->dict.ioloop); } timeout_remove(&to); } io_loop_set_current(prev_ioloop); connection_switch_ioloop(&dict->conn.conn); io_loop_set_current(dict->dict.ioloop); io_loop_destroy(&dict->dict.ioloop); if (!dict->conn.reply.reply_received) { /* we failed in some way. make sure we disconnect since the connection state isn't known anymore */ memcached_conn_destroy(&dict->conn.conn); *error_r = "Communication failure"; return -1; } switch (dict->conn.reply.status) { case MEMCACHED_RESPONSE_OK: *value_r = p_strndup(pool, dict->conn.reply.value, dict->conn.reply.value_len); return 1; case MEMCACHED_RESPONSE_NOTFOUND: return 0; case MEMCACHED_RESPONSE_INTERNALERROR: *error_r = "Lookup failed: Internal error"; return -1; case MEMCACHED_RESPONSE_BUSY: *error_r = "Lookup failed: Busy"; return -1; case MEMCACHED_RESPONSE_TEMPFAILURE: *error_r = "Lookup failed: Temporary failure"; return -1; } *error_r = t_strdup_printf("Lookup failed: Error code=%u", dict->conn.reply.status); return -1; } struct dict dict_driver_memcached = { .name = "memcached", { .init = memcached_dict_init, .deinit = memcached_dict_deinit, .lookup = memcached_dict_lookup, } }; dovecot-2.3.21.1/src/lib-dict/test-dict-client.c0000644000000000000000000000531614656633576016120 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "lib-signals.h" #include "ioloop.h" #include "dict-private.h" #include static int pending = 0; static volatile bool stop = FALSE; static void sig_die(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED) { stop = TRUE; } static void lookup_callback(const struct dict_lookup_result *result, void *context ATTR_UNUSED) { if (result->error != NULL) i_error("%s", result->error); /*else if (result->ret == 0) i_info("not found"); else i_info("%s", result->value);*/ pending--; } static void commit_callback(const struct dict_commit_result *result, void *context ATTR_UNUSED) { if (result->ret < 0) i_error("commit %d", result->ret); pending--; } int main(int argc, char *argv[]) { const char *prefix, *uri; struct dict *dict; struct dict_settings set; struct dict_op_settings opset; struct ioloop *ioloop; const char *error; char key[1000], value[100]; lib_init(); lib_signals_init(); ioloop = io_loop_create(); lib_signals_set_handler(SIGINT, LIBSIG_FLAG_RESTART, sig_die, NULL); dict_driver_register(&dict_driver_client); if (argc < 3) i_fatal("Usage: "); prefix = argv[1]; uri = argv[2]; i_zero(&set); i_zero(&opset); set.base_dir = "/var/run/dovecot"; opset.username = "testuser"; if (dict_init(uri, &set, &dict, &error) < 0) i_fatal("dict_init(%s) failed: %s", argv[1], error); while (!stop) { i_snprintf(key, sizeof(key), "%s/%02x", prefix, i_rand_limit(0xff)); i_snprintf(value, sizeof(value), "%04x", i_rand_limit(0xffff)); switch (i_rand_limit(4)) { case 0: pending++; dict_lookup_async(dict, &opset, key, lookup_callback, NULL); break; case 1: { struct dict_transaction_context *trans; pending++; trans = dict_transaction_begin(dict, &opset); dict_set(trans, key, value); dict_transaction_commit_async(&trans, commit_callback, NULL); break; } case 2: { struct dict_transaction_context *trans; pending++; trans = dict_transaction_begin(dict, &opset); dict_unset(trans, key); dict_transaction_commit_async(&trans, commit_callback, NULL); break; } case 3: { struct dict_iterate_context *iter; const char *k, *v; iter = dict_iterate_init(dict, &opset, prefix, DICT_ITERATE_FLAG_EXACT_KEY); while (dict_iterate(iter, &k, &v)) ; if (dict_iterate_deinit(&iter, &error) < 0) i_error("iter failed: %s", error); break; } } while (pending > 100) { dict_wait(dict); printf("%d\n", pending); fflush(stdout); } } dict_wait(dict); dict_deinit(&dict); dict_driver_unregister(&dict_driver_client); io_loop_destroy(&ioloop); lib_signals_deinit(); lib_deinit(); } dovecot-2.3.21.1/src/lib-dict/dict-txn-lua.c0000644000000000000000000001315114656633576015251 00000000000000/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "dict.h" #include "dlua-script-private.h" #include "dict-lua-private.h" #include "dlua-wrapper.h" struct lua_dict_txn { pool_t pool; struct dict_transaction_context *txn; enum { STATE_OPEN, STATE_COMMITTED, STATE_ABORTED, } state; lua_State *L; const char *username; }; static int lua_dict_transaction_rollback(lua_State *L); static int lua_dict_transaction_commit(lua_State *L); static int lua_dict_set(lua_State *L); static int lua_dict_unset(lua_State *L); static int lua_dict_set_timestamp(lua_State *L); static luaL_Reg lua_dict_txn_methods[] = { { "rollback", lua_dict_transaction_rollback }, { "commit", lua_dict_transaction_commit }, { "set", lua_dict_set }, { "unset", lua_dict_unset }, { "set_timestamp", lua_dict_set_timestamp }, { NULL, NULL }, }; static void sanity_check_txn(lua_State *L, struct lua_dict_txn *txn) { switch (txn->state) { case STATE_OPEN: return; case STATE_COMMITTED: luaL_error(L, "dict transaction already committed"); return; case STATE_ABORTED: luaL_error(L, "dict transaction already aborted"); return; } i_unreached(); } /* no actual ref counting, but we use it for clean up */ static void lua_dict_txn_unref(struct lua_dict_txn *txn) { /* rollback any transactions that were forgotten about */ dict_transaction_rollback(&txn->txn); pool_unref(&txn->pool); } DLUA_WRAP_C_DATA(dict_txn, struct lua_dict_txn, lua_dict_txn_unref, lua_dict_txn_methods); /* * Abort a transaction [-1,+0,e] * * Args: * 1) userdata: struct lua_dict_txn * */ static int lua_dict_transaction_rollback(lua_State *L) { struct lua_dict_txn *txn; DLUA_REQUIRE_ARGS(L, 1); txn = xlua_dict_txn_getptr(L, 1, NULL); sanity_check_txn(L, txn); txn->state = STATE_ABORTED; dict_transaction_rollback(&txn->txn); return 0; } static int lua_dict_transaction_commit_continue(lua_State *L, int status ATTR_UNUSED, lua_KContext ctx ATTR_UNUSED) { if (!lua_isnil(L, -1)) lua_error(L); /* commit failed */ lua_pop(L, 1); /* pop the nil indicating the lack of error */ return 0; } static void lua_dict_transaction_commit_callback(const struct dict_commit_result *result, struct lua_dict_txn *txn) { switch (result->ret) { case DICT_COMMIT_RET_OK: /* push a nil to indicate everything is ok */ lua_pushnil(txn->L); break; case DICT_COMMIT_RET_NOTFOUND: /* we don't expose dict_atomic_inc(), so this should never happen */ i_unreached(); case DICT_COMMIT_RET_FAILED: case DICT_COMMIT_RET_WRITE_UNCERTAIN: /* push the error we'll raise when we resume */ i_assert(result->error != NULL); lua_pushfstring(txn->L, "dict transaction commit failed: %s", result->error); break; } dlua_pcall_yieldable_resume(txn->L, 1); } /* * Commit a transaction [-1,+0,e] * * Args: * 1) userdata: struct lua_dict_txn * */ static int lua_dict_transaction_commit(lua_State *L) { struct lua_dict_txn *txn; DLUA_REQUIRE_ARGS(L, 1); txn = xlua_dict_txn_getptr(L, 1, NULL); sanity_check_txn(L, txn); txn->state = STATE_COMMITTED; dict_transaction_commit_async(&txn->txn, lua_dict_transaction_commit_callback, txn); return lua_dict_transaction_commit_continue(L, lua_yieldk(L, 0, 0, lua_dict_transaction_commit_continue), 0); } /* * Set key to value [-3,+0,e] * * Args: * 1) userdata: struct lua_dict_txn * * 2) string: key * 3) string: value */ static int lua_dict_set(lua_State *L) { struct lua_dict_txn *txn; const char *key, *value; DLUA_REQUIRE_ARGS(L, 3); txn = xlua_dict_txn_getptr(L, 1, NULL); key = luaL_checkstring(L, 2); value = luaL_checkstring(L, 3); lua_dict_check_key_prefix(L, key, txn->username); dict_set(txn->txn, key, value); return 0; } /* * Unset key [-2,+0,e] * * Args: * 1) userdata: struct lua_dict_txn * * 2) string: key */ static int lua_dict_unset(lua_State *L) { struct lua_dict_txn *txn; const char *key; DLUA_REQUIRE_ARGS(L, 2); txn = xlua_dict_txn_getptr(L, 1, NULL); key = luaL_checkstring(L, 2); lua_dict_check_key_prefix(L, key, txn->username); dict_unset(txn->txn, key); return 0; } /* * Start a dict transaction [-(1|2),+1,e] * * Args: * 1) userdata: struct dict * * 2*) string: username * * Returns: * Returns a new transaction object. * Username will be NULL if not provided in args. */ int lua_dict_transaction_begin(lua_State *L) { struct lua_dict_txn *txn; struct dict *dict; const char *username = NULL; pool_t pool; DLUA_REQUIRE_ARGS_IN(L, 1, 2); dict = dlua_check_dict(L, 1); if (lua_gettop(L) >= 2) username = luaL_checkstring(L, 2); pool = pool_alloconly_create("lua dict txn", 128); txn = p_new(pool, struct lua_dict_txn, 1); txn->pool = pool; struct dict_op_settings set = { .username = username, }; txn->txn = dict_transaction_begin(dict, &set); txn->state = STATE_OPEN; txn->L = L; txn->username = p_strdup(txn->pool, username); xlua_pushdict_txn(L, txn, FALSE); return 1; } /* * Set timestamp to the transaction [-2,+0,e] * * Args: * 1) userdata: struct lua_dict_txn * * 2) PosixTimespec : { tv_sec, tv_nsec } */ static int lua_dict_set_timestamp(lua_State *L) { struct lua_dict_txn *txn; lua_Number tv_sec, tv_nsec; DLUA_REQUIRE_ARGS(L, 2); txn = xlua_dict_txn_getptr(L, 1, NULL); if (dlua_table_get_number_by_str(L, 2, "tv_sec", &tv_sec) <= 0) luaL_error(L, "tv_sec missing from table"); if (dlua_table_get_number_by_str(L, 2, "tv_nsec", &tv_nsec) <= 0) luaL_error(L, "tv_nsec missing from table"); struct timespec ts = { .tv_sec = tv_sec, .tv_nsec = tv_nsec }; dict_transaction_set_timestamp(txn->txn, &ts); return 0; } dovecot-2.3.21.1/src/dict/0000755000000000000000000000000014656633640012077 500000000000000dovecot-2.3.21.1/src/dict/dict-init-cache.c0000644000000000000000000000722514656633576015126 00000000000000/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "dict.h" #include "dict-private.h" #include "dict-init-cache.h" #include "llist.h" /* How many seconds to keep dict opened for reuse after it's been closed */ #define DICT_CACHE_TIMEOUT_SECS 30 /* How many closed dicts to keep */ #define DICT_CACHE_MAX_COUNT 10 struct dict_init_cache_list { struct dict_init_cache_list *prev, *next; struct dict *dict; char *dict_name; int refcount; time_t destroy_time; }; static struct dict_init_cache_list *dicts = NULL; static struct timeout *to_dict = NULL; static struct dict_init_cache_list * dict_init_cache_add(const char *dict_name, struct dict *dict) { struct dict_init_cache_list *list; list = i_new(struct dict_init_cache_list, 1); list->refcount = 1; list->dict = dict; list->dict_name = i_strdup(dict_name); DLLIST_PREPEND(&dicts, list); return list; } static void dict_init_cache_list_free(struct dict_init_cache_list *list) { i_assert(list->refcount == 0); DLLIST_REMOVE(&dicts, list); dict_deinit(&list->dict); i_free(list->dict_name); i_free(list); } static struct dict_init_cache_list *dict_init_cache_find(const char *dict_name) { struct dict_init_cache_list *listp = dicts, *next = NULL, *match = NULL; unsigned int ref0_count = 0; while (listp != NULL) { next = listp->next; if (match != NULL) { /* already found the dict. we're just going through the rest of them to drop 0 refcounts */ } else if (strcmp(dict_name, listp->dict_name) == 0) match = listp; if (listp->refcount == 0 && listp != match) { if (listp->destroy_time <= ioloop_time || ref0_count >= DICT_CACHE_MAX_COUNT - 1) dict_init_cache_list_free(listp); else ref0_count++; } listp = next; } return match; } int dict_init_cache_get(const char *dict_name, const char *uri, const struct dict_settings *set, struct dict **dict_r, const char **error_r) { struct dict_init_cache_list *match; int ret = 0; match = dict_init_cache_find(dict_name); if (match == NULL) { if (dict_init(uri, set, dict_r, error_r) < 0) return -1; match = dict_init_cache_add(dict_name, *dict_r); } else { match->refcount++; *dict_r = match->dict; } i_assert(match->dict != NULL); return ret; } static void destroy_unrefed(void) { struct dict_init_cache_list *listp, *next = NULL; bool seen_ref0 = FALSE; for (listp = dicts; listp != NULL; listp = next) { next = listp->next; i_assert(listp->refcount >= 0); if (listp->refcount > 0) ; else if (listp->destroy_time <= ioloop_time) dict_init_cache_list_free(listp); else seen_ref0 = TRUE; } if (!seen_ref0 && to_dict != NULL) timeout_remove(&to_dict); } static void dict_removal_timeout(void *context ATTR_UNUSED) { destroy_unrefed(); } void dict_init_cache_unref(struct dict **_dict) { struct dict *dict = *_dict; struct dict_init_cache_list *listp; if (dict == NULL) return; *_dict = NULL; for (listp = dicts; listp != NULL; listp = listp->next) { if (listp->dict == dict) break; } i_assert(listp != NULL && listp->dict == dict); i_assert(listp->refcount > 0); listp->refcount--; listp->destroy_time = ioloop_time + DICT_CACHE_TIMEOUT_SECS; if (to_dict == NULL) { to_dict = timeout_add_to(io_loop_get_root(), DICT_CACHE_TIMEOUT_SECS*1000/2, dict_removal_timeout, NULL); } } void dict_init_cache_wait_all(void) { struct dict_init_cache_list *listp; for (listp = dicts; listp != NULL; listp = listp->next) dict_wait(listp->dict); } void dict_init_cache_destroy_all(void) { timeout_remove(&to_dict); while (dicts != NULL) dict_init_cache_list_free(dicts); } dovecot-2.3.21.1/src/dict/dict-commands.c0000644000000000000000000005263614656633576014731 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "ostream.h" #include "str.h" #include "strescape.h" #include "stats-dist.h" #include "time-util.h" #include "dict-private.h" #include "dict-client.h" #include "dict-settings.h" #include "dict-connection.h" #include "dict-commands.h" #include "main.h" #define DICT_CLIENT_PROTOCOL_TIMINGS_MIN_VERSION 1 #define DICT_CLIENT_PROTOCOL_UNORDERED_MIN_VERSION 1 #define DICT_OUTPUT_OPTIMAL_SIZE 1024 struct dict_cmd_func { enum dict_protocol_cmd cmd; int (*func)(struct dict_connection_cmd *cmd, const char *const *args); }; struct dict_connection_cmd { const struct dict_cmd_func *cmd; struct dict_connection *conn; struct timeval start_timeval; struct event *event; char *reply; struct dict_iterate_context *iter; enum dict_iterate_flags iter_flags; unsigned int async_reply_id; unsigned int trans_id; /* obsolete */ unsigned int rows; bool uncork_pending; }; struct dict_command_stats cmd_stats; static int cmd_iterate_flush(struct dict_connection_cmd *cmd); static bool dict_connection_cmd_output_more(struct dict_connection_cmd *cmd); static void dict_connection_cmd_free(struct dict_connection_cmd *cmd) { const char *error; if (dict_iterate_deinit(&cmd->iter, &error) < 0) e_error(cmd->event, "dict_iterate() failed: %s", error); i_free(cmd->reply); if (cmd->uncork_pending) o_stream_uncork(cmd->conn->conn.output); if (dict_connection_unref(cmd->conn) && !cmd->conn->destroyed) connection_input_resume(&cmd->conn->conn); event_unref(&cmd->event); i_free(cmd); } static void dict_connection_cmd_remove(struct dict_connection_cmd *cmd) { struct dict_connection_cmd *const *cmds; unsigned int i, count; cmds = array_get(&cmd->conn->cmds, &count); for (i = 0; i < count; i++) { if (cmds[i] == cmd) { array_delete(&cmd->conn->cmds, i, 1); dict_connection_cmd_free(cmd); return; } } i_unreached(); } static void dict_connection_cmds_flush(struct dict_connection *conn) { struct dict_connection_cmd *cmd, *const *first_cmdp; i_assert(conn->conn.minor_version < DICT_CLIENT_PROTOCOL_UNORDERED_MIN_VERSION); dict_connection_ref(conn); while (array_count(&conn->cmds) > 0) { first_cmdp = array_front(&conn->cmds); cmd = *first_cmdp; i_assert(cmd->async_reply_id == 0); /* we may be able to start outputting iterations now. */ if (cmd->iter != NULL) (void)cmd_iterate_flush(cmd); if (cmd->reply == NULL) { /* command not finished yet */ break; } o_stream_nsend_str(conn->conn.output, cmd->reply); dict_connection_cmd_remove(cmd); } dict_connection_unref_safe(conn); } static void dict_connection_cmd_try_flush(struct dict_connection_cmd **_cmd) { struct dict_connection_cmd *cmd = *_cmd; *_cmd = NULL; if (cmd->conn->conn.minor_version < DICT_CLIENT_PROTOCOL_UNORDERED_MIN_VERSION) { dict_connection_cmds_flush(cmd->conn); return; } i_assert(cmd->async_reply_id != 0); i_assert(cmd->reply != NULL); o_stream_nsend_str(cmd->conn->conn.output, t_strdup_printf("%c%u\t%s", DICT_PROTOCOL_REPLY_ASYNC_REPLY, cmd->async_reply_id, cmd->reply)); dict_connection_cmd_remove(cmd); } static void dict_connection_cmd_async(struct dict_connection_cmd *cmd) { if (cmd->conn->conn.minor_version < DICT_CLIENT_PROTOCOL_UNORDERED_MIN_VERSION) return; i_assert(cmd->async_reply_id == 0); cmd->async_reply_id = ++cmd->conn->async_id_counter; if (cmd->async_reply_id == 0) cmd->async_reply_id = ++cmd->conn->async_id_counter; o_stream_nsend_str(cmd->conn->conn.output, t_strdup_printf("%c%u\n", DICT_PROTOCOL_REPLY_ASYNC_ID, cmd->async_reply_id)); } static void cmd_stats_update(struct dict_connection_cmd *cmd, struct stats_dist *stats) { long long diff; if (!dict_settings->verbose_proctitle) return; diff = timeval_diff_usecs(&ioloop_timeval, &cmd->start_timeval); if (diff < 0) diff = 0; stats_dist_add(stats, diff); dict_proctitle_update_later(); } static void dict_cmd_reply_handle_stats(struct dict_connection_cmd *cmd, string_t *str, struct stats_dist *stats) { io_loop_time_refresh(); cmd_stats_update(cmd, stats); if (cmd->conn->conn.minor_version < DICT_CLIENT_PROTOCOL_TIMINGS_MIN_VERSION) return; str_printfa(str, "\t%ld\t%u\t%ld\t%u", (long)cmd->start_timeval.tv_sec, (unsigned int)cmd->start_timeval.tv_usec, (long)ioloop_timeval.tv_sec, (unsigned int)ioloop_timeval.tv_usec); } static void cmd_lookup_write_reply(struct dict_connection_cmd *cmd, const char *const *values, string_t *str) { string_t *tmp; i_assert(values[0] != NULL); if (cmd->conn->conn.minor_version < DICT_CLIENT_PROTOCOL_VERSION_MIN_MULTI_OK || values[1] == NULL) { str_append_c(str, DICT_PROTOCOL_REPLY_OK); str_append_tabescaped(str, values[0]); return; } /* the results get double-tabescaped so they end up becoming a single parameter */ tmp = t_str_new(128); for (unsigned int i = 0; values[i] != NULL; i++) { str_append_c(tmp, '\t'); str_append_tabescaped(tmp, values[i]); } str_append_c(str, DICT_PROTOCOL_REPLY_MULTI_OK); str_append_tabescaped(str, str_c(tmp) + 1); } static void cmd_lookup_callback(const struct dict_lookup_result *result, struct dict_connection_cmd *cmd) { string_t *str = t_str_new(128); event_set_name(cmd->event, "dict_server_lookup_finished"); if (result->ret > 0) { cmd_lookup_write_reply(cmd, result->values, str); e_debug(cmd->event, "Lookup finished"); } else if (result->ret == 0) { event_add_str(cmd->event, "key_not_found", "yes"); str_append_c(str, DICT_PROTOCOL_REPLY_NOTFOUND); e_debug(cmd->event, "Lookup finished without results"); } else { event_add_str(cmd->event, "error", result->error); e_error(cmd->event, "Lookup failed: %s", result->error); str_append_c(str, DICT_PROTOCOL_REPLY_FAIL); str_append_tabescaped(str, result->error); } dict_cmd_reply_handle_stats(cmd, str, cmd_stats.lookups); str_append_c(str, '\n'); cmd->reply = i_strdup(str_c(str)); dict_connection_cmd_try_flush(&cmd); } static int cmd_lookup(struct dict_connection_cmd *cmd, const char *const *args) { const char *username; if (str_array_length(args) < 1) { e_error(cmd->event, "LOOKUP: broken input"); return -1; } username = args[1]; /* [] */ dict_connection_cmd_async(cmd); event_add_str(cmd->event, "key", args[0]); event_add_str(cmd->event, "user", username); const struct dict_op_settings set = { .username = username, }; dict_lookup_async(cmd->conn->dict, &set, args[0], cmd_lookup_callback, cmd); return 1; } static bool dict_connection_flush_if_full(struct dict_connection *conn) { if (o_stream_get_buffer_used_size(conn->conn.output) > DICT_OUTPUT_OPTIMAL_SIZE) { if (o_stream_flush(conn->conn.output) <= 0) { /* continue later when there's more space in output buffer */ o_stream_set_flush_pending(conn->conn.output, TRUE); conn->iter_flush_pending = TRUE; return FALSE; } /* flushed everything, continue */ } return TRUE; } static void cmd_iterate_flush_finish(struct dict_connection_cmd *cmd, string_t *str) { const char *error; event_set_name(cmd->event, "dict_server_iteration_finished"); str_truncate(str, 0); if (dict_iterate_deinit(&cmd->iter, &error) < 0) { event_add_str(cmd->event, "error", error); e_error(cmd->event, "dict_iterate() failed: %s", error); str_printfa(str, "%c%s", DICT_PROTOCOL_REPLY_FAIL, error); } else { event_add_int(cmd->event, "rows", cmd->rows); e_debug(cmd->event, "Iteration finished"); } dict_cmd_reply_handle_stats(cmd, str, cmd_stats.iterations); str_append_c(str, '\n'); cmd->reply = i_strdup(str_c(str)); } static int cmd_iterate_flush(struct dict_connection_cmd *cmd) { string_t *str = t_str_new(256); const char *key, *const *values; if (cmd->conn->destroyed) { cmd_iterate_flush_finish(cmd, str); return 1; } if (!dict_connection_flush_if_full(cmd->conn)) return 0; while (dict_iterate_values(cmd->iter, &key, &values)) { cmd->rows++; str_truncate(str, 0); if (cmd->async_reply_id != 0) { str_append_c(str, DICT_PROTOCOL_REPLY_ASYNC_REPLY); str_printfa(str, "%u\t", cmd->async_reply_id); } str_append_c(str, DICT_PROTOCOL_REPLY_OK); str_append_tabescaped(str, key); str_append_c(str, '\t'); if ((cmd->iter_flags & DICT_ITERATE_FLAG_NO_VALUE) == 0) { str_append_tabescaped(str, values[0]); for (unsigned int i = 1; values[i] != NULL; i++) { str_append_c(str, '\t'); str_append_tabescaped(str, values[i]); } } str_append_c(str, '\n'); o_stream_nsend(cmd->conn->conn.output, str_data(str), str_len(str)); if (!dict_connection_flush_if_full(cmd->conn)) return 0; } if (dict_iterate_has_more(cmd->iter)) { /* wait for the next iteration callback */ return 0; } cmd_iterate_flush_finish(cmd, str); return 1; } static void cmd_iterate_callback(struct dict_connection_cmd *cmd) { struct dict_connection *conn = cmd->conn; dict_connection_ref(conn); o_stream_cork(conn->conn.output); /* Don't uncork if we're just waiting for more input from the dict driver. Some dict drivers (e.g. dict-client) don't do any kind of buffering internally, so this callback can write out only a single iteration. By leaving the ostream corked it doesn't result in many tiny writes. However, we could be here also because the connection output buffer is full already, in which case don't want to leave a cork. */ conn->iter_flush_pending = FALSE; cmd->uncork_pending = FALSE; if (dict_connection_cmd_output_more(cmd)) { /* NOTE: cmd may be freed now */ o_stream_uncork(conn->conn.output); } else if (conn->iter_flush_pending) { /* Don't leave the stream uncorked or we might get stuck. */ o_stream_uncork(conn->conn.output); } else { /* It's possible that the command gets finished via some other code path. To make sure this doesn't cause hangs, uncork the output when command gets freed. */ cmd->uncork_pending = TRUE; } dict_connection_unref_safe(conn); } static int cmd_iterate(struct dict_connection_cmd *cmd, const char *const *args) { const char *username; unsigned int flags; uint64_t max_rows; if (str_array_length(args) < 3 || str_to_uint(args[0], &flags) < 0 || str_to_uint64(args[1], &max_rows) < 0) { e_error(cmd->event, "ITERATE: broken input"); return -1; } dict_connection_cmd_async(cmd); username = args[3]; const struct dict_op_settings set = { .username = username, }; /* [] */ flags |= DICT_ITERATE_FLAG_ASYNC; event_add_str(cmd->event, "key", args[2]); event_add_str(cmd->event, "user", username); cmd->iter = dict_iterate_init(cmd->conn->dict, &set, args[2], flags); cmd->iter_flags = flags; if (max_rows > 0) dict_iterate_set_limit(cmd->iter, max_rows); dict_iterate_set_async_callback(cmd->iter, cmd_iterate_callback, cmd); (void)dict_connection_cmd_output_more(cmd); return 1; } static struct dict_connection_transaction * dict_connection_transaction_lookup(struct dict_connection *conn, unsigned int id) { struct dict_connection_transaction *transaction; if (!array_is_created(&conn->transactions)) return NULL; array_foreach_modifiable(&conn->transactions, transaction) { if (transaction->id == id) return transaction; } return NULL; } static void dict_connection_transaction_array_remove(struct dict_connection *conn, unsigned int id) { const struct dict_connection_transaction *transactions; unsigned int i, count; transactions = array_get(&conn->transactions, &count); for (i = 0; i < count; i++) { if (transactions[i].id == id) { i_assert(transactions[i].ctx == NULL); array_delete(&conn->transactions, i, 1); return; } } i_unreached(); } static int cmd_begin(struct dict_connection_cmd *cmd, const char *const *args) { struct dict_connection_transaction *trans; unsigned int id; const char *username; if (str_array_length(args) < 1) { e_error(cmd->event, "BEGIN: broken input"); return -1; } username = args[1]; /* [] */ if (str_to_uint(args[0], &id) < 0) { e_error(cmd->event, "Invalid transaction ID %s", args[0]); return -1; } if (dict_connection_transaction_lookup(cmd->conn, id) != NULL) { e_error(cmd->event, "Transaction ID %u already exists", id); return -1; } if (!array_is_created(&cmd->conn->transactions)) i_array_init(&cmd->conn->transactions, 4); struct dict_op_settings set = { .username = username, }; trans = array_append_space(&cmd->conn->transactions); trans->id = id; trans->conn = cmd->conn; trans->ctx = dict_transaction_begin(cmd->conn->dict, &set); return 0; } static int dict_connection_transaction_lookup_parse(struct dict_connection *conn, const char *id_str, struct dict_connection_transaction **trans_r) { unsigned int id; if (str_to_uint(id_str, &id) < 0) { e_error(conn->conn.event, "Invalid transaction ID %s", id_str); return -1; } *trans_r = dict_connection_transaction_lookup(conn, id); if (*trans_r == NULL) { e_error(conn->conn.event, "Transaction ID %u doesn't exist", id); return -1; } return 0; } static void cmd_commit_finish(struct dict_connection_cmd *cmd, const struct dict_commit_result *result, bool async) { string_t *str = t_str_new(64); char chr; event_set_name(cmd->event, "dict_server_transaction_finished"); switch (result->ret) { case DICT_COMMIT_RET_OK: chr = DICT_PROTOCOL_REPLY_OK; break; case DICT_COMMIT_RET_NOTFOUND: event_add_str(cmd->event, "key_not_found", "yes"); chr = DICT_PROTOCOL_REPLY_NOTFOUND; break; case DICT_COMMIT_RET_WRITE_UNCERTAIN: i_assert(result->error != NULL); event_add_str(cmd->event, "write_uncertain", "yes"); event_add_str(cmd->event, "error", result->error); chr = DICT_PROTOCOL_REPLY_WRITE_UNCERTAIN; break; case DICT_COMMIT_RET_FAILED: default: i_assert(result->error != NULL); event_add_str(cmd->event, "error", result->error); chr = DICT_PROTOCOL_REPLY_FAIL; break; } if (async) str_append_c(str, DICT_PROTOCOL_REPLY_ASYNC_COMMIT); str_printfa(str, "%c%u", chr, cmd->trans_id); if (chr != DICT_PROTOCOL_REPLY_OK && chr != DICT_PROTOCOL_REPLY_NOTFOUND) { str_append_c(str, '\t'); str_append_tabescaped(str, result->error); } dict_cmd_reply_handle_stats(cmd, str, cmd_stats.commits); str_append_c(str, '\n'); cmd->reply = i_strdup(str_c(str)); if (result->ret < 0) e_debug(cmd->event, "Transaction finished: %s", result->error); else e_debug(cmd->event, "Transaction finished"); dict_connection_transaction_array_remove(cmd->conn, cmd->trans_id); dict_connection_cmd_try_flush(&cmd); } static void cmd_commit_callback(const struct dict_commit_result *result, struct dict_connection_cmd *cmd) { cmd_commit_finish(cmd, result, FALSE); } static void cmd_commit_callback_async(const struct dict_commit_result *result, struct dict_connection_cmd *cmd) { cmd_commit_finish(cmd, result, TRUE); } static int cmd_commit(struct dict_connection_cmd *cmd, const char *const *args) { struct dict_connection_transaction *trans; if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) return -1; cmd->trans_id = trans->id; event_add_str(cmd->event, "user", trans->ctx->set.username); dict_connection_cmd_async(cmd); dict_transaction_commit_async(&trans->ctx, cmd_commit_callback, cmd); return 1; } static int cmd_commit_async(struct dict_connection_cmd *cmd, const char *const *args) { struct dict_connection_transaction *trans; if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) return -1; cmd->trans_id = trans->id; event_add_str(cmd->event, "user", trans->ctx->set.username); dict_connection_cmd_async(cmd); dict_transaction_commit_async(&trans->ctx, cmd_commit_callback_async, cmd); return 1; } static int cmd_rollback(struct dict_connection_cmd *cmd, const char *const *args) { struct dict_connection_transaction *trans; if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) return -1; event_add_str(cmd->event, "user", trans->ctx->set.username); dict_transaction_rollback(&trans->ctx); dict_connection_transaction_array_remove(cmd->conn, trans->id); return 0; } static int cmd_set(struct dict_connection_cmd *cmd, const char *const *args) { struct dict_connection_transaction *trans; /* */ if (str_array_length(args) != 3) { e_error(cmd->event, "SET: broken input"); return -1; } if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) return -1; event_add_str(cmd->event, "user", trans->ctx->set.username); dict_set(trans->ctx, args[1], args[2]); return 0; } static int cmd_unset(struct dict_connection_cmd *cmd, const char *const *args) { struct dict_connection_transaction *trans; /* */ if (str_array_length(args) != 2) { e_error(cmd->event, "UNSET: broken input"); return -1; } if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) return -1; dict_unset(trans->ctx, args[1]); return 0; } static int cmd_atomic_inc(struct dict_connection_cmd *cmd, const char *const *args) { struct dict_connection_transaction *trans; long long diff; /* */ if (str_array_length(args) != 3 || str_to_llong(args[2], &diff) < 0) { e_error(cmd->event, "ATOMIC_INC: broken input"); return -1; } if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) return -1; dict_atomic_inc(trans->ctx, args[1], diff); return 0; } static int cmd_timestamp(struct dict_connection_cmd *cmd, const char *const *args) { struct dict_connection_transaction *trans; long long tv_sec; unsigned int tv_nsec; /* */ if (str_array_length(args) != 3 || str_to_llong(args[1], &tv_sec) < 0 || str_to_uint(args[2], &tv_nsec) < 0) { e_error(cmd->event, "TIMESTAMP: broken input"); return -1; } if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) return -1; struct timespec ts = { .tv_sec = tv_sec, .tv_nsec = tv_nsec }; dict_transaction_set_timestamp(trans->ctx, &ts); return 0; } static int cmd_hide_log_values(struct dict_connection_cmd *cmd, const char *const *args) { struct dict_connection_transaction *trans; bool value; /* */ if (str_array_length(args) != 2 ) { e_error(cmd->event, "HIDE_LOG_VALUES: broken input"); return -1; } if (strcasecmp(args[1], "yes") == 0 || strcasecmp(args[1], "y") == 0 || strcmp(args[1], "1") == 0) value = TRUE; else if (strcasecmp(args[1], "no") == 0) value = FALSE; else { e_error(cmd->event, "HIDE_LOG_VALUES: broken input"); return -1; } if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0) return -1; dict_transaction_set_hide_log_values(trans->ctx, value); return 0; } static const struct dict_cmd_func cmds[] = { { DICT_PROTOCOL_CMD_LOOKUP, cmd_lookup }, { DICT_PROTOCOL_CMD_ITERATE, cmd_iterate }, { DICT_PROTOCOL_CMD_BEGIN, cmd_begin }, { DICT_PROTOCOL_CMD_COMMIT, cmd_commit }, { DICT_PROTOCOL_CMD_COMMIT_ASYNC, cmd_commit_async }, { DICT_PROTOCOL_CMD_ROLLBACK, cmd_rollback }, { DICT_PROTOCOL_CMD_SET, cmd_set }, { DICT_PROTOCOL_CMD_UNSET, cmd_unset }, { DICT_PROTOCOL_CMD_ATOMIC_INC, cmd_atomic_inc }, { DICT_PROTOCOL_CMD_TIMESTAMP, cmd_timestamp }, { DICT_PROTOCOL_CMD_HIDE_LOG_VALUES, cmd_hide_log_values }, { 0, NULL } }; static const struct dict_cmd_func *dict_command_find(enum dict_protocol_cmd cmd) { unsigned int i; for (i = 0; cmds[i].cmd != '\0'; i++) { if (cmds[i].cmd == cmd) return &cmds[i]; } return NULL; } int dict_command_input(struct dict_connection *conn, const char *line) { const struct dict_cmd_func *cmd_func; struct dict_connection_cmd *cmd; int ret; const char *const *args; cmd_func = dict_command_find((enum dict_protocol_cmd)*line); if (cmd_func == NULL) { e_error(conn->conn.event, "Unknown command %c", *line); return -1; } cmd = i_new(struct dict_connection_cmd, 1); cmd->conn = conn; cmd->event = event_create(cmd->conn->conn.event); cmd->cmd = cmd_func; cmd->start_timeval = ioloop_timeval; array_push_back(&conn->cmds, &cmd); dict_connection_ref(conn); args = t_strsplit_tabescaped(line + 1); if ((ret = cmd_func->func(cmd, args)) <= 0) { dict_connection_cmd_remove(cmd); return ret; } return 0; } static bool dict_connection_cmds_try_output_more(struct dict_connection *conn) { struct dict_connection_cmd *cmd; /* only iterators may be returning a lot of data */ array_foreach_elem(&conn->cmds, cmd) { if (cmd->iter == NULL) { /* not an iterator */ } else if (cmd_iterate_flush(cmd) == 0) { /* unfinished */ } else { dict_connection_cmd_try_flush(&cmd); /* cmd should be freed now, restart output */ return TRUE; } if (conn->conn.minor_version < DICT_CLIENT_PROTOCOL_TIMINGS_MIN_VERSION) break; /* try to flush the rest */ } return FALSE; } void dict_connection_cmds_output_more(struct dict_connection *conn) { while (array_count(&conn->cmds) > 0) { if (!dict_connection_cmds_try_output_more(conn)) break; } } static bool dict_connection_cmd_output_more(struct dict_connection_cmd *cmd) { struct dict_connection_cmd *const *first_cmdp; if (cmd->conn->conn.minor_version < DICT_CLIENT_PROTOCOL_TIMINGS_MIN_VERSION) { first_cmdp = array_front(&cmd->conn->cmds); if (*first_cmdp != cmd) return TRUE; } return dict_connection_cmds_try_output_more(cmd->conn); } void dict_commands_init(void) { cmd_stats.lookups = stats_dist_init(); cmd_stats.iterations = stats_dist_init(); cmd_stats.commits = stats_dist_init(); } void dict_commands_deinit(void) { stats_dist_deinit(&cmd_stats.lookups); stats_dist_deinit(&cmd_stats.iterations); stats_dist_deinit(&cmd_stats.commits); } dovecot-2.3.21.1/src/dict/dict-settings.c0000644000000000000000000000557514656633576014770 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "settings-parser.h" #include "service-settings.h" #include "dict-settings.h" /* */ static struct file_listener_settings dict_unix_listeners_array[] = { { "dict", 0660, "", "$default_internal_group" } }; static struct file_listener_settings *dict_unix_listeners[] = { &dict_unix_listeners_array[0] }; static buffer_t dict_unix_listeners_buf = { { { dict_unix_listeners, sizeof(dict_unix_listeners) } } }; static struct file_listener_settings dict_async_unix_listeners_array[] = { { "dict-async", 0660, "", "$default_internal_group" } }; static struct file_listener_settings *dict_async_unix_listeners[] = { &dict_async_unix_listeners_array[0] }; static buffer_t dict_async_unix_listeners_buf = { { { dict_async_unix_listeners, sizeof(dict_async_unix_listeners) } } }; /* */ struct service_settings dict_service_settings = { .name = "dict", .protocol = "", .type = "", .executable = "dict", .user = "$default_internal_user", .group = "", .privileged_group = "", .extra_groups = "", .chroot = "", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 0, .client_limit = 1, .service_count = 0, .idle_kill = 0, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &dict_unix_listeners_buf, sizeof(dict_unix_listeners[0]) } }, .fifo_listeners = ARRAY_INIT, .inet_listeners = ARRAY_INIT }; struct service_settings dict_async_service_settings = { .name = "dict-async", .protocol = "", .type = "", .executable = "dict", .user = "$default_internal_user", .group = "", .privileged_group = "", .extra_groups = "", .chroot = "", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 0, .client_limit = 0, .service_count = 0, .idle_kill = 0, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &dict_async_unix_listeners_buf, sizeof(dict_async_unix_listeners[0]) } }, .fifo_listeners = ARRAY_INIT, .inet_listeners = ARRAY_INIT }; #undef DEF #define DEF(type, name) \ SETTING_DEFINE_STRUCT_##type(#name, name, struct dict_server_settings) static const struct setting_define dict_setting_defines[] = { DEF(STR, base_dir), DEF(BOOL, verbose_proctitle), DEF(STR, dict_db_config), { .type = SET_STRLIST, .key = "dict", .offset = offsetof(struct dict_server_settings, dicts) }, SETTING_DEFINE_LIST_END }; const struct dict_server_settings dict_default_settings = { .base_dir = PKG_RUNDIR, .verbose_proctitle = FALSE, .dict_db_config = "", .dicts = ARRAY_INIT }; const struct setting_parser_info dict_setting_parser_info = { .module_name = "dict", .defines = dict_setting_defines, .defaults = &dict_default_settings, .type_offset = SIZE_MAX, .struct_size = sizeof(struct dict_server_settings), .parent_offset = SIZE_MAX }; const struct dict_server_settings *dict_settings; dovecot-2.3.21.1/src/dict/Makefile.in0000644000000000000000000006511014656633607014072 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ pkglibexec_PROGRAMS = dict$(EXEEXT) subdir = src/dict ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(pkglibexecdir)" PROGRAMS = $(pkglibexec_PROGRAMS) am_dict_OBJECTS = dict-connection.$(OBJEXT) dict-commands.$(OBJEXT) \ dict-settings.$(OBJEXT) dict-init-cache.$(OBJEXT) \ main.$(OBJEXT) dict_OBJECTS = $(am_dict_OBJECTS) am__DEPENDENCIES_1 = am__DEPENDENCIES_2 = ../lib-dict-backend/libdict_backend.la \ $(am__DEPENDENCIES_1) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = dict_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(dict_LDFLAGS) $(LDFLAGS) -o $@ AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/dict-commands.Po \ ./$(DEPDIR)/dict-connection.Po ./$(DEPDIR)/dict-init-cache.Po \ ./$(DEPDIR)/dict-settings.Po ./$(DEPDIR)/main.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(dict_SOURCES) DIST_SOURCES = $(dict_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac HEADERS = $(noinst_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) pkglibexecdir = $(libexecdir)/dovecot ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-dict \ -I$(top_srcdir)/src/lib-sql \ -DDICT_MODULE_DIR=\""$(moduledir)/dict"\" \ -DPKG_RUNDIR=\""$(rundir)"\" \ $(BINARY_CFLAGS) dict_LDFLAGS = -export-dynamic \ $(BINARY_LDFLAGS) libs = \ ../lib-dict-backend/libdict_backend.la \ $(LIBDOVECOT_SQL) dict_LDADD = \ $(libs) \ $(LIBDOVECOT) \ $(DICT_LIBS) \ $(SQL_LIBS) \ -lm dict_DEPENDENCIES = $(libs) $(LIBDOVECOT_DEPS) dict_SOURCES = \ dict-connection.c \ dict-commands.c \ dict-settings.c \ dict-init-cache.c \ main.c noinst_HEADERS = \ dict-connection.h \ dict-commands.h \ dict-settings.h \ dict-init-cache.h \ main.h all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/dict/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/dict/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files clean-pkglibexecPROGRAMS: @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list dict$(EXEEXT): $(dict_OBJECTS) $(dict_DEPENDENCIES) $(EXTRA_dict_DEPENDENCIES) @rm -f dict$(EXEEXT) $(AM_V_CCLD)$(dict_LINK) $(dict_OBJECTS) $(dict_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-commands.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-connection.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-init-cache.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-settings.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am check: check-am all-am: Makefile $(PROGRAMS) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkglibexecdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-pkglibexecPROGRAMS \ mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/dict-commands.Po -rm -f ./$(DEPDIR)/dict-connection.Po -rm -f ./$(DEPDIR)/dict-init-cache.Po -rm -f ./$(DEPDIR)/dict-settings.Po -rm -f ./$(DEPDIR)/main.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-pkglibexecPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/dict-commands.Po -rm -f ./$(DEPDIR)/dict-connection.Po -rm -f ./$(DEPDIR)/dict-init-cache.Po -rm -f ./$(DEPDIR)/dict-settings.Po -rm -f ./$(DEPDIR)/main.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkglibexecPROGRAMS .MAKE: install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ clean-generic clean-libtool clean-pkglibexecPROGRAMS \ cscopelist-am ctags ctags-am distclean distclean-compile \ distclean-generic distclean-libtool distclean-tags distdir dvi \ dvi-am html html-am info info-am install install-am \ install-data install-data-am install-dvi install-dvi-am \ install-exec install-exec-am install-html install-html-am \ install-info install-info-am install-man install-pdf \ install-pdf-am install-pkglibexecPROGRAMS install-ps \ install-ps-am install-strip installcheck installcheck-am \ installdirs maintainer-clean maintainer-clean-generic \ mostlyclean mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ uninstall-am uninstall-pkglibexecPROGRAMS .PRECIOUS: Makefile # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/dict/main.h0000644000000000000000000000011714656633576013123 00000000000000#ifndef MAIN_H #define MAIN_H void dict_proctitle_update_later(void); #endif dovecot-2.3.21.1/src/dict/dict-connection.h0000644000000000000000000000222614656633576015262 00000000000000#ifndef DICT_CONNECTION_H #define DICT_CONNECTION_H #include "dict.h" #include "connection.h" struct dict_connection_transaction { unsigned int id; struct dict_connection *conn; struct dict_transaction_context *ctx; }; struct dict_connection { struct connection conn; struct dict_connection *prev, *next; struct dict_server *server; int refcount; char *name; struct dict *dict; enum dict_data_type value_type; struct timeout *to_unref; /* There are only a few transactions per client, so keeping them in array is fast enough */ ARRAY(struct dict_connection_transaction) transactions; ARRAY(struct dict_connection_cmd *) cmds; unsigned int async_id_counter; bool iter_flush_pending; bool destroyed; }; struct master_service_connection; struct dict_connection * dict_connection_create(struct master_service_connection *master_conn); void dict_connection_ref(struct dict_connection *conn); bool dict_connection_unref(struct dict_connection *conn); void dict_connection_unref_safe(struct dict_connection *conn); unsigned int dict_connections_current_count(void); void dict_connections_init(void); void dict_connections_destroy_all(void); #endif dovecot-2.3.21.1/src/dict/main.c0000644000000000000000000001060314656633576013117 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "restrict-access.h" #include "ioloop.h" #include "randgen.h" #include "str.h" #include "stats-dist.h" #include "process-title.h" #include "env-util.h" #include "module-dir.h" #include "master-service.h" #include "master-service-settings.h" #include "sql-api.h" #include "dict.h" #include "dict-client.h" #include "dict-commands.h" #include "dict-connection.h" #include "dict-settings.h" #include "dict-init-cache.h" #include "main.h" #include static struct module *modules; static struct timeout *to_proctitle; static bool proctitle_updated; static struct ioloop *main_ioloop; static void add_stats_string(string_t *str, struct stats_dist *stats, const char *name) { uint64_t min, max, p95; double avg; min = stats_dist_get_min(stats); avg = stats_dist_get_avg(stats); p95 = stats_dist_get_95th(stats); max = stats_dist_get_max(stats); str_printfa(str, ", %u %s:%llu/%lld/%llu/%llu", stats_dist_get_count(stats), name, (unsigned long long)min/1000, llrint(avg/1000), (unsigned long long)p95/1000, (unsigned long long)max/1000); stats_dist_reset(stats); } static void dict_proctitle_update(void *context ATTR_UNUSED) { string_t *str = t_str_new(128); if (!proctitle_updated) timeout_remove(&to_proctitle); str_printfa(str, "[%u clients", dict_connections_current_count()); add_stats_string(str, cmd_stats.lookups, "lookups"); add_stats_string(str, cmd_stats.iterations, "iters"); add_stats_string(str, cmd_stats.commits, "commits"); str_append_c(str, ']'); process_title_set(str_c(str)); proctitle_updated = FALSE; } void dict_proctitle_update_later(void) { if (!dict_settings->verbose_proctitle) return; if (to_proctitle == NULL) to_proctitle = timeout_add_to(main_ioloop, 1000, dict_proctitle_update, NULL); proctitle_updated = TRUE; } static void dict_die(void) { /* hope that other processes relying on us will die first. */ } static void client_connected(struct master_service_connection *conn) { master_service_client_connection_accept(conn); (void)dict_connection_create(conn); } static void main_preinit(void) { /* Load built-in SQL drivers (if any) */ sql_drivers_init(); sql_drivers_register_all(); #ifdef HAVE_CDB dict_driver_register(&dict_driver_cdb); #endif restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL); restrict_access_allow_coredumps(TRUE); } static void main_init(void) { struct module_dir_load_settings mod_set; void **sets; sets = master_service_settings_get_others(master_service); dict_settings = sets[0]; if (*dict_settings->dict_db_config != '\0') { /* for berkeley db library */ env_put("DB_CONFIG", dict_settings->dict_db_config); } i_zero(&mod_set); mod_set.abi_version = DOVECOT_ABI_VERSION; mod_set.require_init_funcs = TRUE; modules = module_dir_load(DICT_MODULE_DIR, NULL, &mod_set); module_dir_init(modules); /* Register only after loading modules. They may contain SQL drivers, which we'll need to register. */ dict_drivers_register_all(); dict_commands_init(); dict_connections_init(); if (dict_settings->verbose_proctitle) dict_proctitle_update(NULL); } static void main_deinit(void) { /* wait for all dict operations to finish */ dict_init_cache_wait_all(); /* connections should no longer have any extra refcounts */ dict_connections_destroy_all(); dict_init_cache_destroy_all(); dict_drivers_unregister_all(); dict_commands_deinit(); module_dir_unload(&modules); sql_drivers_deinit(); timeout_remove(&to_proctitle); } int main(int argc, char *argv[]) { const enum master_service_flags service_flags = 0; const struct setting_parser_info *set_roots[] = { &dict_setting_parser_info, NULL }; const char *error; master_service = master_service_init("dict", service_flags, &argc, &argv, ""); if (master_getopt(master_service) > 0) return FATAL_DEFAULT; if (master_service_settings_read_simple(master_service, set_roots, &error) < 0) i_fatal("Error reading configuration: %s", error); master_service_init_log_with_pid(master_service); main_preinit(); master_service_set_die_callback(master_service, dict_die); main_ioloop = current_ioloop; main_init(); master_service_init_finish(master_service); master_service_run(master_service, client_connected); /* clean up cached dicts */ main_deinit(); master_service_deinit(&master_service); return 0; } dovecot-2.3.21.1/src/dict/Makefile.am0000644000000000000000000000144414656633576014066 00000000000000pkglibexecdir = $(libexecdir)/dovecot pkglibexec_PROGRAMS = dict AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-dict \ -I$(top_srcdir)/src/lib-sql \ -DDICT_MODULE_DIR=\""$(moduledir)/dict"\" \ -DPKG_RUNDIR=\""$(rundir)"\" \ $(BINARY_CFLAGS) dict_LDFLAGS = -export-dynamic \ $(BINARY_LDFLAGS) libs = \ ../lib-dict-backend/libdict_backend.la \ $(LIBDOVECOT_SQL) dict_LDADD = \ $(libs) \ $(LIBDOVECOT) \ $(DICT_LIBS) \ $(SQL_LIBS) \ -lm dict_DEPENDENCIES = $(libs) $(LIBDOVECOT_DEPS) dict_SOURCES = \ dict-connection.c \ dict-commands.c \ dict-settings.c \ dict-init-cache.c \ main.c noinst_HEADERS = \ dict-connection.h \ dict-commands.h \ dict-settings.h \ dict-init-cache.h \ main.h dovecot-2.3.21.1/src/dict/dict-init-cache.h0000644000000000000000000000051714656633576015130 00000000000000#ifndef DICT_INIT_CACHE_H #define DICT_INIT_CACHE_H int dict_init_cache_get(const char *dict_name, const char *uri, const struct dict_settings *set, struct dict **dict_r, const char **error_r); void dict_init_cache_unref(struct dict **dict); void dict_init_cache_wait_all(void); void dict_init_cache_destroy_all(void); #endif dovecot-2.3.21.1/src/dict/dict-commands.h0000644000000000000000000000071014656633576014720 00000000000000#ifndef DICT_COMMANDS_H #define DICT_COMMANDS_H struct dict_connection; struct dict_command_stats { struct stats_dist *lookups; struct stats_dist *iterations; struct stats_dist *commits; }; extern struct dict_command_stats cmd_stats; int dict_command_input(struct dict_connection *conn, const char *line); void dict_connection_cmds_output_more(struct dict_connection *conn); void dict_commands_init(void); void dict_commands_deinit(void); #endif dovecot-2.3.21.1/src/dict/dict-connection.c0000644000000000000000000001617314656633576015263 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "llist.h" #include "strescape.h" #include "master-service.h" #include "dict-client.h" #include "dict-settings.h" #include "dict-commands.h" #include "dict-connection.h" #include "dict-init-cache.h" #include #define DICT_CONN_MAX_PENDING_COMMANDS 1000 static int dict_connection_dict_init(struct dict_connection *conn); static void dict_connection_destroy(struct connection *_conn); struct connection_list *dict_connections = NULL; static struct event_category dict_server_event_category = { .name = "dict-server", }; static int dict_connection_handshake_args(struct connection *_conn, const char *const *args) { unsigned int major, value_type_num; struct dict_connection *conn = container_of(_conn, struct dict_connection, conn); /* protocol handshake is Hmajor minor value_type */ if (str_array_length(args) < 5 || **args != 'H') return -1; /* check major version which comes right after 'H' in the first parameter, store minor version. */ if (str_to_uint(args[0]+1, &major) < 0 || str_to_uint(args[1], &conn->conn.minor_version) < 0 || major != DICT_CLIENT_PROTOCOL_MAJOR_VERSION) return -1; /* check value type */ if (str_to_uint(args[2], &value_type_num) < 0 || value_type_num >= DICT_DATA_TYPE_LAST) return -1; conn->value_type = (enum dict_data_type)value_type_num; conn->name = i_strdup(args[4]); /* try initialize the given dict */ if (dict_connection_dict_init(conn) < 0) return -1; return 1; } static int dict_connection_handshake_line(struct connection *conn, const char *line) { const char *const *args = t_strsplit_tabescaped(line); return dict_connection_handshake_args(conn, args); } static int dict_connection_dict_init(struct dict_connection *conn) { struct dict_settings dict_set; const char *const *strlist; unsigned int i, count; const char *uri, *error; if (!array_is_created(&dict_settings->dicts)) { e_error(conn->conn.event, "No dictionaries configured"); return -1; } strlist = array_get(&dict_settings->dicts, &count); for (i = 0; i < count; i += 2) { if (strcmp(strlist[i], conn->name) == 0) break; } if (i == count) { e_error(conn->conn.event, "Unconfigured dictionary name '%s'", conn->name); return -1; } event_set_append_log_prefix(conn->conn.event, t_strdup_printf("%s: ", conn->name)); event_add_str(conn->conn.event, "dict_name", conn->name); uri = strlist[i+1]; i_zero(&dict_set); dict_set.base_dir = dict_settings->base_dir; dict_set.event_parent = conn->conn.event; if (dict_init_cache_get(conn->name, uri, &dict_set, &conn->dict, &error) < 0) { /* dictionary initialization failed */ e_error(conn->conn.event, "Failed to initialize dictionary '%s': %s", conn->name, error); return -1; } return 0; } static int dict_connection_output(struct connection *_conn) { struct dict_connection *conn = container_of(_conn, struct dict_connection, conn); int ret; if ((ret = o_stream_flush(conn->conn.output)) < 0) { dict_connection_destroy(&conn->conn); return 1; } if (ret > 0) dict_connection_cmds_output_more(conn); return ret; } struct dict_connection * dict_connection_create(struct master_service_connection *master_conn) { struct dict_connection *conn; conn = i_new(struct dict_connection, 1); conn->refcount = 1; connection_init_server(dict_connections, &conn->conn, master_conn->name, master_conn->fd, master_conn->fd); event_add_category(conn->conn.event, &dict_server_event_category); o_stream_set_flush_callback(conn->conn.output, dict_connection_output, &conn->conn); i_array_init(&conn->cmds, DICT_CONN_MAX_PENDING_COMMANDS); return conn; } void dict_connection_ref(struct dict_connection *conn) { i_assert(conn->refcount > 0); conn->refcount++; } bool dict_connection_unref(struct dict_connection *conn) { struct dict_connection_transaction *transaction; i_assert(conn->refcount > 0); if (--conn->refcount > 0) return TRUE; i_assert(array_count(&conn->cmds) == 0); /* we should have only transactions that haven't been committed or rollbacked yet. close those before dict is deinitialized. */ if (array_is_created(&conn->transactions)) { array_foreach_modifiable(&conn->transactions, transaction) dict_transaction_rollback(&transaction->ctx); } if (conn->dict != NULL) dict_init_cache_unref(&conn->dict); if (array_is_created(&conn->transactions)) array_free(&conn->transactions); array_free(&conn->cmds); connection_deinit(&conn->conn); i_free(conn->name); i_free(conn); master_service_client_connection_destroyed(master_service); return FALSE; } static int dict_connection_input_line(struct connection *_conn, const char *line) { struct dict_connection *conn = container_of(_conn, struct dict_connection, conn); i_assert(conn->dict != NULL); if (dict_command_input(conn, line) < 0) return -1; if (array_count(&conn->cmds) >= DICT_CONN_MAX_PENDING_COMMANDS) { connection_input_halt(_conn); return 0; } return 1; } static void dict_connection_unref_safe_callback(struct dict_connection *conn) { timeout_remove(&conn->to_unref); (void)dict_connection_unref(conn); } void dict_connection_unref_safe(struct dict_connection *conn) { if (conn->refcount == 1) { /* delayed unref to make sure we don't try to call dict_deinit() from a dict-callback. that's too much trouble for each dict driver to be able to handle. */ if (conn->to_unref == NULL) { conn->to_unref = timeout_add_short(0, dict_connection_unref_safe_callback, conn); } } else { (void)dict_connection_unref(conn); } } static void dict_connection_destroy(struct connection *_conn) { struct dict_connection *conn = container_of(_conn, struct dict_connection, conn); /* If there are commands still running, we delay disconnecting can may come back here. Track this so we unreference the connection only once. */ if (conn->destroyed) return; conn->destroyed = TRUE; /* the connection is closed, but there may still be commands left running. finish them, even if the calling client can't be notified about whether they succeeded (clients may not even care). flush the command output here in case we were waiting on iteration output. */ i_stream_close(conn->conn.input); o_stream_close(conn->conn.output); dict_connection_cmds_output_more(conn); io_remove(&conn->conn.io); dict_connection_unref(conn); } unsigned int dict_connections_current_count(void) { return dict_connections->connections_count; } void dict_connections_destroy_all(void) { connection_list_deinit(&dict_connections); } static struct connection_settings dict_connections_set = { .dont_send_version = TRUE, .input_max_size = DICT_CLIENT_MAX_LINE_LENGTH, .output_max_size = 128*1024, }; static struct connection_vfuncs dict_connections_vfuncs = { .destroy = dict_connection_destroy, .handshake_line = dict_connection_handshake_line, .input_line = dict_connection_input_line, }; void dict_connections_init(void) { dict_connections = connection_list_init(&dict_connections_set, &dict_connections_vfuncs); } dovecot-2.3.21.1/src/dict/dict-settings.h0000644000000000000000000000050014656633576014754 00000000000000#ifndef DICT_SETTINGS_H #define DICT_SETTINGS_H struct dict_server_settings { const char *base_dir; bool verbose_proctitle; const char *dict_db_config; ARRAY(const char *) dicts; }; extern const struct setting_parser_info dict_setting_parser_info; extern const struct dict_server_settings *dict_settings; #endif dovecot-2.3.21.1/src/submission-login/0000755000000000000000000000000014656633640014455 500000000000000dovecot-2.3.21.1/src/submission-login/submission-proxy.c0000644000000000000000000005070714656633576020134 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "login-common.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "base64.h" #include "safe-memset.h" #include "str.h" #include "str-sanitize.h" #include "strescape.h" #include "dsasl-client.h" #include "client.h" #include "smtp-syntax.h" #include "submission-login-settings.h" #include "submission-proxy.h" #include static const char *submission_proxy_state_names[SUBMISSION_PROXY_STATE_COUNT] = { "banner", "ehlo", "starttls", "tls-ehlo", "xclient", "xclient-ehlo", "authenticate" }; static void submission_proxy_success_reply_sent( struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct submission_client *subm_client) { client_proxy_finish_destroy_client(&subm_client->common); } static int proxy_send_starttls(struct submission_client *client, struct ostream *output) { enum login_proxy_ssl_flags ssl_flags; ssl_flags = login_proxy_get_ssl_flags(client->common.login_proxy); if ((ssl_flags & PROXY_SSL_FLAG_STARTTLS) == 0) return 0; if ((client->proxy_capability & SMTP_CAPABILITY_STARTTLS) == 0) { login_proxy_failed( client->common.login_proxy, login_proxy_get_event(client->common.login_proxy), LOGIN_PROXY_FAILURE_TYPE_REMOTE_CONFIG, "STARTTLS not supported"); return -1; } o_stream_nsend_str(output, "STARTTLS\r\n"); client->proxy_state = SUBMISSION_PROXY_STARTTLS; return 1; } static buffer_t * proxy_compose_xclient_forward(struct submission_client *client) { const char *const *arg; string_t *str; if (*client->common.auth_passdb_args == NULL) return NULL; str = t_str_new(128); for (arg = client->common.auth_passdb_args; *arg != NULL; arg++) { if (strncasecmp(*arg, "forward_", 8) == 0) { if (str_len(str) > 0) str_append_c(str, '\t'); str_append_tabescaped(str, (*arg)+8); } } if (str_len(str) == 0) return NULL; return t_base64_encode(0, 0, str_data(str), str_len(str)); } static void proxy_send_xclient_more_data(struct submission_client *client, struct ostream *output, string_t *buf, const char *field, const unsigned char *value, size_t value_size) { const size_t cmd_len = strlen("XCLIENT"); size_t prev_len = str_len(buf); str_append_c(buf, ' '); str_append(buf, field); str_append_c(buf, '='); smtp_xtext_encode(buf, value, value_size); if (str_len(buf) > 512) { if (prev_len <= cmd_len) prev_len = str_len(buf); o_stream_nsend(output, str_data(buf), prev_len); o_stream_nsend(output, "\r\n", 2); client->proxy_xclient_replies_expected++; str_delete(buf, cmd_len, prev_len - cmd_len); } } static void proxy_send_xclient_more(struct submission_client *client, struct ostream *output, string_t *buf, const char *field, const char *value) { proxy_send_xclient_more_data(client, output, buf, field, (const unsigned char *)value, strlen(value)); } static int proxy_send_xclient(struct submission_client *client, struct ostream *output) { string_t *str; if ((client->proxy_capability & SMTP_CAPABILITY_XCLIENT) == 0 || client->common.proxy_not_trusted) return 0; struct smtp_proxy_data proxy_data; smtp_server_connection_get_proxy_data(client->conn, &proxy_data); i_assert(client->common.proxy_ttl > 1); /* remote supports XCLIENT, send it */ client->proxy_xclient_replies_expected = 0; str = t_str_new(128); str_append(str, "XCLIENT"); if (str_array_icase_find(client->proxy_xclient, "HELO")) { if (proxy_data.helo != NULL) { proxy_send_xclient_more(client, output, str, "HELO", proxy_data.helo); } else { proxy_send_xclient_more(client, output, str, "HELO", "[UNAVAILABLE]"); } } if (str_array_icase_find(client->proxy_xclient, "PROTO")) { const char *proto = "[UNAVAILABLE]"; switch (proxy_data.proto) { case SMTP_PROXY_PROTOCOL_UNKNOWN: break; case SMTP_PROXY_PROTOCOL_SMTP: proto = "SMTP"; break; case SMTP_PROXY_PROTOCOL_ESMTP: proto = "ESMTP"; break; case SMTP_PROXY_PROTOCOL_LMTP: proto = "LMTP"; break; } proxy_send_xclient_more(client, output, str, "PROTO", proto); } if (client->common.proxy_noauth && str_array_icase_find(client->proxy_xclient, "LOGIN")) { if (proxy_data.login != NULL) { proxy_send_xclient_more(client, output, str, "LOGIN", proxy_data.login); } else if (client->common.virtual_user != NULL) { proxy_send_xclient_more(client, output, str, "LOGIN", client->common.virtual_user); } else { proxy_send_xclient_more(client, output, str, "LOGIN", "[UNAVAILABLE]"); } } if (str_array_icase_find(client->proxy_xclient, "TTL")) { proxy_send_xclient_more( client, output, str, "TTL", t_strdup_printf("%u",client->common.proxy_ttl - 1)); } if (str_array_icase_find(client->proxy_xclient, "PORT")) { proxy_send_xclient_more( client, output, str, "PORT", t_strdup_printf("%u", client->common.remote_port)); } if (str_array_icase_find(client->proxy_xclient, "ADDR")) { proxy_send_xclient_more(client, output, str, "ADDR", net_ip2addr(&client->common.ip)); } if (str_array_icase_find(client->proxy_xclient, "SESSION")) { proxy_send_xclient_more(client, output, str, "SESSION", client_get_session_id(&client->common)); } if (str_array_icase_find(client->proxy_xclient, "FORWARD")) { buffer_t *fwd = proxy_compose_xclient_forward(client); if (fwd != NULL) { proxy_send_xclient_more_data( client, output, str, "FORWARD", fwd->data, fwd->used); } } str_append(str, "\r\n"); o_stream_nsend(output, str_data(str), str_len(str)); client->proxy_state = SUBMISSION_PROXY_XCLIENT; client->proxy_xclient_replies_expected++; return 1; } static int proxy_send_login(struct submission_client *client, struct ostream *output) { struct dsasl_client_settings sasl_set; const unsigned char *sasl_output; size_t sasl_output_len; const char *mech_name, *error; string_t *str; if ((client->proxy_capability & SMTP_CAPABILITY_AUTH) == 0) { /* Prevent sending credentials to a server that has login disabled; i.e., due to the lack of TLS */ login_proxy_failed(client->common.login_proxy, login_proxy_get_event(client->common.login_proxy), LOGIN_PROXY_FAILURE_TYPE_REMOTE_CONFIG, "Authentication support not advertised (TLS required?)"); return -1; } str = t_str_new(128); if (client->common.proxy_mech == NULL) client->common.proxy_mech = &dsasl_client_mech_plain; i_assert(client->common.proxy_sasl_client == NULL); i_zero(&sasl_set); sasl_set.authid = client->common.proxy_master_user != NULL ? client->common.proxy_master_user : client->common.proxy_user; sasl_set.authzid = client->common.proxy_user; sasl_set.password = client->common.proxy_password; client->common.proxy_sasl_client = dsasl_client_new(client->common.proxy_mech, &sasl_set); mech_name = dsasl_client_mech_get_name(client->common.proxy_mech); str_printfa(str, "AUTH %s", mech_name); if (dsasl_client_output(client->common.proxy_sasl_client, &sasl_output, &sasl_output_len, &error) < 0) { const char *reason = t_strdup_printf( "SASL mechanism %s init failed: %s", mech_name, error); login_proxy_failed(client->common.login_proxy, login_proxy_get_event(client->common.login_proxy), LOGIN_PROXY_FAILURE_TYPE_INTERNAL, reason); return -1; } string_t *sasl_output_base64 = t_str_new( MAX_BASE64_ENCODED_SIZE(sasl_output_len)); base64_encode(sasl_output, sasl_output_len, sasl_output_base64); /* RFC 4954, Section 4: Note that the AUTH command is still subject to the line length limitations defined in [SMTP]. If use of the initial response argument would cause the AUTH command to exceed this length, the client MUST NOT use the initial response parameter (and instead proceed as defined in Section 5.1 of [SASL]). If the client is transmitting an initial response of zero length, it MUST instead transmit the response as a single equals sign ("="). This indicates that the response is present, but contains no data. */ i_assert(client->proxy_sasl_ir == NULL); if (str_len(sasl_output_base64) == 0) str_append(str, " ="); else if ((5 + strlen(mech_name) + 1 + str_len(sasl_output_base64)) > SMTP_BASE_LINE_LENGTH_LIMIT) client->proxy_sasl_ir = i_strdup(str_c(sasl_output_base64)); else { str_append_c(str, ' '); str_append_str(str, sasl_output_base64); } str_append(str, "\r\n"); o_stream_nsend(output, str_data(str), str_len(str)); client->proxy_state = SUBMISSION_PROXY_AUTHENTICATE; return 0; } static int proxy_handle_ehlo_reply(struct submission_client *client, struct ostream *output) { struct smtp_server_cmd_ctx *cmd = client->pending_auth; int ret; switch (client->proxy_state) { case SUBMISSION_PROXY_EHLO: ret = proxy_send_starttls(client, output); if (ret < 0) return -1; if (ret != 0) return 0; /* Fall through */ case SUBMISSION_PROXY_TLS_EHLO: ret = proxy_send_xclient(client, output); if (ret < 0) return -1; if (ret != 0) { client->proxy_capability = 0; i_free_and_null(client->proxy_xclient); o_stream_nsend_str(output, t_strdup_printf( "EHLO %s\r\n", client->set->hostname)); return 0; } break; case SUBMISSION_PROXY_XCLIENT_EHLO: break; default: i_unreached(); } if (client->common.proxy_noauth) { smtp_server_connection_input_lock(cmd->conn); smtp_server_command_add_hook( cmd->cmd, SMTP_SERVER_COMMAND_HOOK_DESTROY, submission_proxy_success_reply_sent, client); client->pending_auth = NULL; smtp_server_reply(cmd, 235, "2.7.0", "Logged in."); return 1; } return proxy_send_login(client, output); } static int submission_proxy_continue_sasl_auth(struct client *client, struct ostream *output, const char *line, bool last_line) { struct submission_client *subm_client = container_of(client, struct submission_client, common); string_t *str; const unsigned char *data; size_t data_len; const char *error; int ret; if (!last_line) { const char *reason = t_strdup_printf( "Server returned multi-line challenge: 334 %s", str_sanitize(line, 1024)); login_proxy_failed(client->login_proxy, login_proxy_get_event(client->login_proxy), LOGIN_PROXY_FAILURE_TYPE_PROTOCOL, reason); return -1; } if (subm_client->proxy_sasl_ir != NULL) { if (*line == '\0') { /* Send initial response */ o_stream_nsend(output, subm_client->proxy_sasl_ir, strlen(subm_client->proxy_sasl_ir)); o_stream_nsend_str(output, "\r\n"); i_free(subm_client->proxy_sasl_ir); return 0; } const char *reason = t_strdup_printf( "Server sent unexpected server-first challenge: " "334 %s", str_sanitize(line, 1024)); login_proxy_failed(client->login_proxy, login_proxy_get_event(client->login_proxy), LOGIN_PROXY_FAILURE_TYPE_PROTOCOL, reason); return -1; } str = t_str_new(128); if (base64_decode(line, strlen(line), NULL, str) < 0) { login_proxy_failed(client->login_proxy, login_proxy_get_event(client->login_proxy), LOGIN_PROXY_FAILURE_TYPE_PROTOCOL, "Invalid base64 data in AUTH response"); return -1; } ret = dsasl_client_input(client->proxy_sasl_client, str_data(str), str_len(str), &error); if (ret == 0) { ret = dsasl_client_output(client->proxy_sasl_client, &data, &data_len, &error); } if (ret < 0) { const char *reason = t_strdup_printf( "Invalid authentication data: %s", error); login_proxy_failed(client->login_proxy, login_proxy_get_event(client->login_proxy), LOGIN_PROXY_FAILURE_TYPE_PROTOCOL, reason); return -1; } i_assert(ret == 0); str_truncate(str, 0); base64_encode(data, data_len, str); str_append(str, "\r\n"); o_stream_nsend(output, str_data(str), str_len(str)); return 0; } static const char * strip_enhanced_code(const char *text, const char **enh_code_r) { const char *p = text; unsigned int digits; *enh_code_r = NULL; if (*p != '2' && *p != '4' && *p != '5') return text; p++; if (*p != '.') return text; p++; digits = 0; while (i_isdigit(*p) && digits < 3) { p++; digits++; } if (*p != '.') return text; p++; digits = 0; while (i_isdigit(*p) && digits < 3) { p++; digits++; } if (*p != ' ') return text; *enh_code_r = t_strdup_until(text, p); p++; return p; } int submission_proxy_parse_line(struct client *client, const char *line) { struct submission_client *subm_client = container_of(client, struct submission_client, common); struct smtp_server_cmd_ctx *cmd = subm_client->pending_auth; struct smtp_server_command *command = cmd->cmd; struct ostream *output; bool last_line = FALSE, invalid_line = FALSE; const char *text = NULL, *enh_code = NULL; unsigned int status = 0; i_assert(!client->destroyed); i_assert(cmd != NULL); if ((line[3] != ' ' && line[3] != '-') || str_parse_uint(line, &status, &text) < 0 || status < 200 || status >= 560) { invalid_line = TRUE; } else { text++; if ((subm_client->proxy_capability & SMTP_CAPABILITY_ENHANCEDSTATUSCODES) != 0) text = strip_enhanced_code(text, &enh_code); } if (subm_client->proxy_reply_status != 0 && subm_client->proxy_reply_status != status) { const char *reason = t_strdup_printf( "Inconsistent SMTP reply: %s (status != %u)", str_sanitize(line, 160), subm_client->proxy_reply_status); login_proxy_failed(client->login_proxy, login_proxy_get_event(client->login_proxy), LOGIN_PROXY_FAILURE_TYPE_PROTOCOL, reason); return -1; } if (line[3] == ' ') { last_line = TRUE; subm_client->proxy_reply_status = 0; } else { subm_client->proxy_reply_status = status; } output = login_proxy_get_ostream(client->login_proxy); switch (subm_client->proxy_state) { case SUBMISSION_PROXY_BANNER: /* this is a banner */ if (invalid_line || status != 220) { const char *reason = t_strdup_printf( "Invalid banner: %s", str_sanitize(line, 160)); login_proxy_failed(client->login_proxy, login_proxy_get_event(client->login_proxy), LOGIN_PROXY_FAILURE_TYPE_PROTOCOL, reason); return -1; } if (!last_line) return 0; subm_client->proxy_state = SUBMISSION_PROXY_EHLO; o_stream_nsend_str(output, t_strdup_printf("EHLO %s\r\n", subm_client->set->hostname)); return 0; case SUBMISSION_PROXY_EHLO: case SUBMISSION_PROXY_TLS_EHLO: case SUBMISSION_PROXY_XCLIENT_EHLO: if (invalid_line || (status / 100) != 2) { const char *reason = t_strdup_printf( "Invalid EHLO line: %s", str_sanitize(line, 160)); login_proxy_failed(client->login_proxy, login_proxy_get_event(client->login_proxy), LOGIN_PROXY_FAILURE_TYPE_PROTOCOL, reason); return -1; } if (strncasecmp(text, "XCLIENT ", 8) == 0) { subm_client->proxy_capability |= SMTP_CAPABILITY_XCLIENT; i_free_and_null(subm_client->proxy_xclient); subm_client->proxy_xclient = p_strarray_dup( default_pool, t_strsplit_spaces(text + 8, " ")); } else if (strncasecmp(text, "STARTTLS", 9) == 0) { subm_client->proxy_capability |= SMTP_CAPABILITY_STARTTLS; } else if (strncasecmp(text, "AUTH", 4) == 0 && text[4] == ' ' && text[5] != '\0') { subm_client->proxy_capability |= SMTP_CAPABILITY_AUTH; } else if (strcasecmp(text, "ENHANCEDSTATUSCODES") == 0) { subm_client->proxy_capability |= SMTP_CAPABILITY_ENHANCEDSTATUSCODES; } if (!last_line) return 0; return proxy_handle_ehlo_reply(subm_client, output); case SUBMISSION_PROXY_STARTTLS: if (invalid_line || status != 220) { const char *reason = t_strdup_printf( "STARTTLS failed: %s", str_sanitize(line, 160)); login_proxy_failed(client->login_proxy, login_proxy_get_event(client->login_proxy), LOGIN_PROXY_FAILURE_TYPE_REMOTE, reason); return -1; } if (!last_line) return 0; if (login_proxy_starttls(client->login_proxy) < 0) return -1; /* i/ostreams changed. */ output = login_proxy_get_ostream(client->login_proxy); subm_client->proxy_capability = 0; i_free_and_null(subm_client->proxy_xclient); subm_client->proxy_state = SUBMISSION_PROXY_TLS_EHLO; o_stream_nsend_str(output, t_strdup_printf( "EHLO %s\r\n", subm_client->set->hostname)); return 0; case SUBMISSION_PROXY_XCLIENT: if (invalid_line || (status / 100) != 2) { const char *reason = t_strdup_printf( "XCLIENT failed: %s", str_sanitize(line, 160)); login_proxy_failed(client->login_proxy, login_proxy_get_event(client->login_proxy), LOGIN_PROXY_FAILURE_TYPE_REMOTE, reason); return -1; } if (!last_line) return 0; i_assert(subm_client->proxy_xclient_replies_expected > 0); if (--subm_client->proxy_xclient_replies_expected > 0) return 0; subm_client->proxy_state = SUBMISSION_PROXY_XCLIENT_EHLO; return 0; case SUBMISSION_PROXY_AUTHENTICATE: if (invalid_line) break; if (status == 334 && client->proxy_sasl_client != NULL) { /* continue SASL authentication */ if (submission_proxy_continue_sasl_auth( client, output, text, last_line) < 0) return -1; return 0; } i_assert(subm_client->proxy_reply == NULL); subm_client->proxy_reply = smtp_server_reply_create( command, status, enh_code); smtp_server_reply_add_text(subm_client->proxy_reply, text); if (!last_line) return 0; if ((status / 100) != 2) break; smtp_server_connection_input_lock(cmd->conn); smtp_server_command_add_hook( command, SMTP_SERVER_COMMAND_HOOK_DESTROY, submission_proxy_success_reply_sent, subm_client); subm_client->pending_auth = NULL; /* Login successful. Send this reply to client. */ smtp_server_reply_submit(subm_client->proxy_reply); return 1; case SUBMISSION_PROXY_STATE_COUNT: i_unreached(); } /* Login failed. Pass through the error message to client. If the backend server isn't Dovecot, the error message may be different from Dovecot's "user doesn't exist" error. This would allow an attacker to find out what users exist in the system. The optimal way to handle this would be to replace the backend's "password failed" error message with Dovecot's AUTH_FAILED_MSG, but this would require a new setting and the sysadmin to actually bother setting it properly. So for now we'll just forward the error message. This shouldn't be a real problem since of course everyone will be using only Dovecot as their backend :) */ enum login_proxy_failure_type failure_type = LOGIN_PROXY_FAILURE_TYPE_AUTH; if ((status / 100) == 4) failure_type = LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL; else { i_assert((status / 100) != 2); i_assert(subm_client->proxy_reply != NULL); smtp_server_reply_submit(subm_client->proxy_reply); subm_client->pending_auth = NULL; } login_proxy_failed(client->login_proxy, login_proxy_get_event(client->login_proxy), failure_type, text); return -1; } void submission_proxy_reset(struct client *client) { struct submission_client *subm_client = container_of(client, struct submission_client, common); subm_client->proxy_state = SUBMISSION_PROXY_BANNER; subm_client->proxy_capability = 0; i_free_and_null(subm_client->proxy_xclient); i_free(subm_client->proxy_sasl_ir); subm_client->proxy_reply_status = 0; subm_client->proxy_reply = NULL; } static void submission_proxy_send_failure_reply(struct submission_client *subm_client, enum login_proxy_failure_type type, const char *reason ATTR_UNUSED) { struct smtp_server_cmd_ctx *cmd = subm_client->pending_auth; switch (type) { case LOGIN_PROXY_FAILURE_TYPE_CONNECT: case LOGIN_PROXY_FAILURE_TYPE_INTERNAL: case LOGIN_PROXY_FAILURE_TYPE_INTERNAL_CONFIG: case LOGIN_PROXY_FAILURE_TYPE_REMOTE: case LOGIN_PROXY_FAILURE_TYPE_REMOTE_CONFIG: case LOGIN_PROXY_FAILURE_TYPE_PROTOCOL: i_assert(cmd != NULL); subm_client->pending_auth = NULL; smtp_server_reply(cmd, 454, "4.7.0", LOGIN_PROXY_FAILURE_MSG); break; case LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL: i_assert(cmd != NULL); subm_client->pending_auth = NULL; i_assert(subm_client->proxy_reply != NULL); smtp_server_reply_submit(subm_client->proxy_reply); break; case LOGIN_PROXY_FAILURE_TYPE_AUTH: /* reply was already sent */ i_assert(cmd == NULL); break; } } void submission_proxy_failed(struct client *client, enum login_proxy_failure_type type, const char *reason, bool reconnecting) { struct submission_client *subm_client = container_of(client, struct submission_client, common); if (!reconnecting) submission_proxy_send_failure_reply(subm_client, type, reason); client_common_proxy_failed(client, type, reason, reconnecting); } const char *submission_proxy_get_state(struct client *client) { struct submission_client *subm_client = container_of(client, struct submission_client, common); i_assert(subm_client->proxy_state < SUBMISSION_PROXY_STATE_COUNT); return submission_proxy_state_names[subm_client->proxy_state]; } dovecot-2.3.21.1/src/submission-login/submission-login-settings.h0000644000000000000000000000117614656633576021722 00000000000000#ifndef SUBMISSION_LOGIN_SETTINGS_H #define SUBMISSION_LOGIN_SETTINGS_H /* */ enum submission_login_client_workarounds { SUBMISSION_LOGIN_WORKAROUND_IMPLICIT_AUTH_EXTERNAL = BIT(0), SUBMISSION_LOGIN_WORKAROUND_EXOTIC_BACKEND = BIT(1), }; /* */ struct submission_login_settings { const char *hostname; /* submission: */ uoff_t submission_max_mail_size; const char *submission_client_workarounds; const char *submission_backend_capabilities; enum submission_login_client_workarounds parsed_workarounds; }; extern const struct setting_parser_info *submission_login_setting_roots[]; #endif dovecot-2.3.21.1/src/submission-login/client-authenticate.c0000644000000000000000000002665314656633576020517 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "login-common.h" #include "base64.h" #include "buffer.h" #include "hex-binary.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "safe-memset.h" #include "str.h" #include "str-sanitize.h" #include "auth-client.h" #include "master-service-ssl-settings.h" #include "client.h" #include "client-authenticate.h" #include "submission-proxy.h" #include "submission-login-settings.h" static void cmd_helo_reply(struct submission_client *subm_client, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_helo *data) { struct client *client = &subm_client->common; const struct submission_login_settings *set = subm_client->set; enum smtp_capability backend_caps = subm_client->backend_capabilities; bool exotic_backend = HAS_ALL_BITS(set->parsed_workarounds, SUBMISSION_LOGIN_WORKAROUND_EXOTIC_BACKEND); struct smtp_server_reply *reply; reply = smtp_server_reply_create_ehlo(cmd->cmd); if (!data->helo.old_smtp) { if ((backend_caps & SMTP_CAPABILITY_8BITMIME) != 0) smtp_server_reply_ehlo_add(reply, "8BITMIME"); if (client->secured || strcmp(client->ssl_set->ssl, "required") != 0) { const struct auth_mech_desc *mechs; unsigned int count, i; string_t *param = t_str_new(128); mechs = sasl_server_get_advertised_mechs(client, &count); for (i = 0; i < count; i++) { if (i > 0) str_append_c(param, ' '); str_append(param, mechs[i].name); } smtp_server_reply_ehlo_add_param(reply, "AUTH", "%s", str_c(param)); } if ((backend_caps & SMTP_CAPABILITY_BINARYMIME) != 0 && (backend_caps & SMTP_CAPABILITY_CHUNKING) != 0) smtp_server_reply_ehlo_add(reply, "BINARYMIME"); if (!exotic_backend || (backend_caps & SMTP_CAPABILITY_BURL) != 0) { smtp_server_reply_ehlo_add_param(reply, "BURL", "imap"); } if (!exotic_backend || (backend_caps & SMTP_CAPABILITY_CHUNKING) != 0) { smtp_server_reply_ehlo_add(reply, "CHUNKING"); } if ((backend_caps & SMTP_CAPABILITY_DSN) != 0) smtp_server_reply_ehlo_add(reply, "DSN"); if (!exotic_backend || (backend_caps & SMTP_CAPABILITY_ENHANCEDSTATUSCODES) != 0) { smtp_server_reply_ehlo_add( reply, "ENHANCEDSTATUSCODES"); } if (subm_client->set->submission_max_mail_size > 0) { smtp_server_reply_ehlo_add_param(reply, "SIZE", "%"PRIuUOFF_T, subm_client->set->submission_max_mail_size); } else if (!exotic_backend || (backend_caps & SMTP_CAPABILITY_SIZE) != 0) { smtp_server_reply_ehlo_add(reply, "SIZE"); } if (client_is_tls_enabled(client) && !client->tls) smtp_server_reply_ehlo_add(reply, "STARTTLS"); if (!exotic_backend || (backend_caps & SMTP_CAPABILITY_PIPELINING) != 0) smtp_server_reply_ehlo_add(reply, "PIPELINING"); if ((backend_caps & SMTP_CAPABILITY_VRFY) != 0) smtp_server_reply_ehlo_add(reply, "VRFY"); smtp_server_reply_ehlo_add_xclient(reply); } smtp_server_reply_submit(reply); } int cmd_helo(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_helo *data) { struct submission_client *subm_client = conn_ctx; T_BEGIN { cmd_helo_reply(subm_client, cmd, data); } T_END; return 1; } void submission_client_auth_result(struct client *client, enum client_auth_result result, const struct client_auth_reply *reply ATTR_UNUSED, const char *text) { struct submission_client *subm_client = container_of(client, struct submission_client, common); struct smtp_server_cmd_ctx *cmd = subm_client->pending_auth; if (subm_client->conn == NULL) return; subm_client->pending_auth = NULL; i_assert(cmd != NULL); switch (result) { case CLIENT_AUTH_RESULT_SUCCESS: /* nothing to be done for SMTP */ if (client->login_proxy != NULL) subm_client->pending_auth = cmd; break; case CLIENT_AUTH_RESULT_TEMPFAIL: /* RFC4954, Section 6: 454 4.7.0 Temporary authentication failure This response to the AUTH command indicates that the authentication failed due to a temporary server failure. */ smtp_server_reply(cmd, 454, "4.7.0", "%s", text); break; case CLIENT_AUTH_RESULT_ABORTED: /* RFC4954, Section 4: If the client wishes to cancel the authentication exchange, it issues a line with a single "*". If the server receives such a response, it MUST reject the AUTH command by sending a 501 reply. */ smtp_server_reply(cmd, 501, "5.5.2", "%s", text); break; case CLIENT_AUTH_RESULT_INVALID_BASE64: /* RFC4954, Section 4: If the server cannot [BASE64] decode any client response, it MUST reject the AUTH command with a 501 reply (and an enhanced status code of 5.5.2). */ smtp_server_reply(cmd, 501, "5.5.2", "%s", text); break; case CLIENT_AUTH_RESULT_SSL_REQUIRED: /* RFC3207, Section 4: A SMTP server that is not publicly referenced may choose to require that the client perform a TLS negotiation before accepting any commands. In this case, the server SHOULD return the reply code: 530 Must issue a STARTTLS command first to every command other than NOOP, EHLO, STARTTLS, or QUIT. If the client and server are using the ENHANCEDSTATUSCODES ESMTP extension [RFC2034], the status code to be returned SHOULD be 5.7.0. */ smtp_server_reply(cmd, 530, "5.7.0", "%s", text); break; case CLIENT_AUTH_RESULT_MECH_SSL_REQUIRED: /* RFC5248, Section 2.4: 523 X.7.10 Encryption Needed This indicates that an external strong privacy layer is needed in order to use the requested authentication mechanism. This is primarily intended for use with clear text authentication mechanisms. A client that receives this may activate a security layer such as TLS prior to authenticating, or attempt to use a stronger mechanism. */ smtp_server_reply(cmd, 523, "5.7.10", "%s", text); break; case CLIENT_AUTH_RESULT_MECH_INVALID: /* RFC4954, Section 4: If the requested authentication mechanism is invalid (e.g., is not supported or requires an encryption layer), the server rejects the AUTH command with a 504 reply. If the server supports the [ESMTP-CODES] extension, it SHOULD return a 5.5.4 enhanced response code. */ smtp_server_reply(cmd, 504, "5.5.4", "%s", text); break; case CLIENT_AUTH_RESULT_LOGIN_DISABLED: case CLIENT_AUTH_RESULT_ANONYMOUS_DENIED: /* RFC5248, Section 2.4: 525 X.7.13 User Account Disabled Sometimes a system administrator will have to disable a user's account (e.g., due to lack of payment, abuse, evidence of a break-in attempt, etc.). This error code occurs after a successful authentication to a disabled account. This informs the client that the failure is permanent until the user contacts their system administrator to get the account re-enabled. */ smtp_server_reply(cmd, 525, "5.7.13", "%s", text); break; case CLIENT_AUTH_RESULT_PASS_EXPIRED: default: /* FIXME: RFC4954, Section 4: If the client uses an initial-response argument to the AUTH command with a SASL mechanism in which the client does not begin the authentication exchange, the server MUST reject the AUTH command with a 501 reply. Servers using the enhanced status codes extension [ESMTP-CODES] SHOULD return an enhanced status code of 5.7.0 in this case. >> Currently, this is checked at the server side, but only a generic error is ever produced. */ /* NOTE: RFC4954, Section 4: If, during an authentication exchange, the server receives a line that is longer than the server's authentication buffer, the server fails the AUTH command with the 500 reply. Servers using the enhanced status codes extension [ESMTP-CODES] SHOULD return an enhanced status code of 5.5.6 in this case. >> Currently, client is disconnected from login-common. */ /* RFC4954, Section 4: If the server is unable to authenticate the client, it SHOULD reject the AUTH command with a 535 reply unless a more specific error code is appropriate. RFC4954, Section 6: 535 5.7.8 Authentication credentials invalid This response to the AUTH command indicates that the authentication failed due to invalid or insufficient authentication credentials. */ smtp_server_reply(cmd, 535, "5.7.8", "%s", text); break; } } int cmd_auth_continue(void *conn_ctx, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, const char *response) { struct submission_client *subm_client = conn_ctx; struct client *client = &subm_client->common; if (strcmp(response, "*") == 0) { client_auth_abort(client); return 0; } client_auth_respond(client, response); return 0; } void submission_client_auth_send_challenge(struct client *client, const char *data) { struct submission_client *subm_client = container_of(client, struct submission_client, common); struct smtp_server_cmd_ctx *cmd = subm_client->pending_auth; i_assert(cmd != NULL); smtp_server_cmd_auth_send_challenge(cmd, data); } static void cmd_auth_set_master_data_prefix(struct submission_client *subm_client, const char *mail_params) ATTR_NULL(2) { struct client *client = &subm_client->common; struct smtp_server_helo_data *helo; struct smtp_proxy_data proxy; buffer_t *buf = buffer_create_dynamic(default_pool, 2048); /* pass ehlo parameter to post-login service upon successful login */ helo = smtp_server_connection_get_helo_data(subm_client->conn); if (helo->domain_valid) { i_assert(helo->domain != NULL); buffer_append(buf, helo->domain, strlen(helo->domain)); } buffer_append_c(buf, '\0'); /* pass proxied ehlo parameter to post-login service upon successful login */ smtp_server_connection_get_proxy_data(subm_client->conn, &proxy); if (proxy.helo != NULL) buffer_append(buf, proxy.helo, strlen(proxy.helo)); buffer_append_c(buf, '\0'); /* Pass MAIL command to post-login service if any. */ if (mail_params != NULL) { buffer_append(buf, "MAIL ", 5); buffer_append(buf, mail_params, strlen(mail_params)); buffer_append(buf, "\r\n", 2); } i_free(client->master_data_prefix); client->master_data_prefix_len = buf->used; client->master_data_prefix = buffer_free_without_data(&buf); } int cmd_auth(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_auth *data) { struct submission_client *subm_client = conn_ctx; struct client *client = &subm_client->common; cmd_auth_set_master_data_prefix(subm_client, NULL); i_assert(subm_client->pending_auth == NULL); subm_client->pending_auth = cmd; (void)client_auth_begin(client, data->sasl_mech, data->initial_response); return 0; } void cmd_mail(struct smtp_server_cmd_ctx *cmd, const char *params) { struct smtp_server_connection *conn = cmd->conn; struct submission_client *subm_client = smtp_server_connection_get_context(conn); struct client *client = &subm_client->common; enum submission_login_client_workarounds workarounds = subm_client->set->parsed_workarounds; if (HAS_NO_BITS(workarounds, SUBMISSION_LOGIN_WORKAROUND_IMPLICIT_AUTH_EXTERNAL) || sasl_server_find_available_mech(client, "EXTERNAL") == NULL) { smtp_server_command_fail(cmd->cmd, 530, "5.7.0", "Authentication required."); return; } e_debug(cmd->event, "Performing implicit EXTERNAL authentication"); smtp_server_command_input_lock(cmd); cmd_auth_set_master_data_prefix(subm_client, params); i_assert(subm_client->pending_auth == NULL); subm_client->pending_auth = cmd; (void)client_auth_begin_implicit(client, "EXTERNAL", "="); } dovecot-2.3.21.1/src/submission-login/submission-login-settings.c0000644000000000000000000001122014656633576021704 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "hostpid.h" #include "buffer.h" #include "settings-parser.h" #include "service-settings.h" #include "login-settings.h" #include "submission-login-settings.h" #include static bool submission_login_settings_check(void *_set, pool_t pool, const char **error_r); /* */ static struct inet_listener_settings submission_login_inet_listeners_array[] = { { .name = "submission", .address = "", .port = 587 }, { .name = "submissions", .address = "", .port = 465, .ssl = TRUE } }; static struct inet_listener_settings *submission_login_inet_listeners[] = { &submission_login_inet_listeners_array[0] }; static buffer_t submission_login_inet_listeners_buf = { { { submission_login_inet_listeners, sizeof(submission_login_inet_listeners) } } }; /* */ struct service_settings submission_login_service_settings = { .name = "submission-login", .protocol = "submission", .type = "login", .executable = "submission-login", .user = "$default_login_user", .group = "", .privileged_group = "", .extra_groups = "", .chroot = "login", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 0, .client_limit = 0, .service_count = 1, .idle_kill = 0, .vsz_limit = UOFF_T_MAX, .unix_listeners = ARRAY_INIT, .fifo_listeners = ARRAY_INIT, .inet_listeners = { { &submission_login_inet_listeners_buf, sizeof(submission_login_inet_listeners[0]) } } }; #undef DEF #define DEF(type, name) \ SETTING_DEFINE_STRUCT_##type(#name, name, struct submission_login_settings) static const struct setting_define submission_login_setting_defines[] = { DEF(STR, hostname), DEF(SIZE, submission_max_mail_size), DEF(STR, submission_client_workarounds), DEF(STR, submission_backend_capabilities), SETTING_DEFINE_LIST_END }; static const struct submission_login_settings submission_login_default_settings = { .hostname = "", .submission_max_mail_size = 0, .submission_client_workarounds = "", .submission_backend_capabilities = NULL }; static const struct setting_parser_info *submission_login_setting_dependencies[] = { &login_setting_parser_info, NULL }; const struct setting_parser_info submission_login_setting_parser_info = { .module_name = "submission-login", .defines = submission_login_setting_defines, .defaults = &submission_login_default_settings, .type_offset = SIZE_MAX, .struct_size = sizeof(struct submission_login_settings), .parent_offset = SIZE_MAX, .check_func = submission_login_settings_check, .dependencies = submission_login_setting_dependencies }; const struct setting_parser_info *submission_login_setting_roots[] = { &login_setting_parser_info, &submission_login_setting_parser_info, NULL }; /* */ struct submission_login_client_workaround_list { const char *name; enum submission_login_client_workarounds num; }; /* These definitions need to be kept in sync with equivalent definitions present in src/submission/submission-settings.c. Workarounds that are not relevant to the submission-login service are defined as 0 here to prevent "Unknown workaround" errors below. */ static const struct submission_login_client_workaround_list submission_login_client_workaround_list[] = { { "whitespace-before-path", 0}, { "mailbox-for-path", 0 }, { "implicit-auth-external", SUBMISSION_LOGIN_WORKAROUND_IMPLICIT_AUTH_EXTERNAL }, { "exotic-backend", SUBMISSION_LOGIN_WORKAROUND_EXOTIC_BACKEND }, { NULL, 0 } }; static int submission_login_settings_parse_workarounds( struct submission_login_settings *set, const char **error_r) { enum submission_login_client_workarounds client_workarounds = 0; const struct submission_login_client_workaround_list *list; const char *const *str; str = t_strsplit_spaces(set->submission_client_workarounds, " ,"); for (; *str != NULL; str++) { list = submission_login_client_workaround_list; for (; list->name != NULL; list++) { if (strcasecmp(*str, list->name) == 0) { client_workarounds |= list->num; break; } } if (list->name == NULL) { *error_r = t_strdup_printf( "submission_client_workarounds: " "Unknown workaround: %s", *str); return -1; } } set->parsed_workarounds = client_workarounds; return 0; } static bool submission_login_settings_check(void *_set, pool_t pool ATTR_UNUSED, const char **error_r) { struct submission_login_settings *set = _set; if (submission_login_settings_parse_workarounds(set, error_r) < 0) return FALSE; #ifndef CONFIG_BINARY if (*set->hostname == '\0') set->hostname = p_strdup(pool, my_hostdomain()); #endif return TRUE; } /* */ dovecot-2.3.21.1/src/submission-login/Makefile.in0000644000000000000000000006444014656633613016452 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ pkglibexec_PROGRAMS = submission-login$(EXEEXT) subdir = src/submission-login ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(pkglibexecdir)" PROGRAMS = $(pkglibexec_PROGRAMS) am_submission_login_OBJECTS = client.$(OBJEXT) \ client-authenticate.$(OBJEXT) \ submission-login-settings.$(OBJEXT) submission-proxy.$(OBJEXT) submission_login_OBJECTS = $(am_submission_login_OBJECTS) am__DEPENDENCIES_1 = AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/client-authenticate.Po \ ./$(DEPDIR)/client.Po ./$(DEPDIR)/submission-login-settings.Po \ ./$(DEPDIR)/submission-proxy.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(submission_login_SOURCES) DIST_SOURCES = $(submission_login_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac HEADERS = $(noinst_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) pkglibexecdir = $(libexecdir)/dovecot ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-sasl \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-smtp \ -I$(top_srcdir)/src/login-common submission_login_LDADD = \ $(LIBDOVECOT_LOGIN) \ $(LIBDOVECOT) \ $(SSL_LIBS) submission_login_DEPENDENCIES = \ $(LIBDOVECOT_LOGIN) \ $(LIBDOVECOT_DEPS) submission_login_SOURCES = \ client.c \ client-authenticate.c \ submission-login-settings.c \ submission-proxy.c noinst_HEADERS = \ client.h \ client-authenticate.h \ submission-login-settings.h \ submission-proxy.h all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/submission-login/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/submission-login/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files clean-pkglibexecPROGRAMS: @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list submission-login$(EXEEXT): $(submission_login_OBJECTS) $(submission_login_DEPENDENCIES) $(EXTRA_submission_login_DEPENDENCIES) @rm -f submission-login$(EXEEXT) $(AM_V_CCLD)$(LINK) $(submission_login_OBJECTS) $(submission_login_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client-authenticate.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/submission-login-settings.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/submission-proxy.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am check: check-am all-am: Makefile $(PROGRAMS) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkglibexecdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-pkglibexecPROGRAMS \ mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/client-authenticate.Po -rm -f ./$(DEPDIR)/client.Po -rm -f ./$(DEPDIR)/submission-login-settings.Po -rm -f ./$(DEPDIR)/submission-proxy.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-pkglibexecPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/client-authenticate.Po -rm -f ./$(DEPDIR)/client.Po -rm -f ./$(DEPDIR)/submission-login-settings.Po -rm -f ./$(DEPDIR)/submission-proxy.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkglibexecPROGRAMS .MAKE: install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ clean-generic clean-libtool clean-pkglibexecPROGRAMS \ cscopelist-am ctags ctags-am distclean distclean-compile \ distclean-generic distclean-libtool distclean-tags distdir dvi \ dvi-am html html-am info info-am install install-am \ install-data install-data-am install-dvi install-dvi-am \ install-exec install-exec-am install-html install-html-am \ install-info install-info-am install-man install-pdf \ install-pdf-am install-pkglibexecPROGRAMS install-ps \ install-ps-am install-strip installcheck installcheck-am \ installdirs maintainer-clean maintainer-clean-generic \ mostlyclean mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ uninstall-am uninstall-pkglibexecPROGRAMS .PRECIOUS: Makefile # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/submission-login/client.h0000644000000000000000000000156314656633576016041 00000000000000#ifndef CLIENT_H #define CLIENT_H #include "net.h" #include "client-common.h" #include "auth-client.h" #include "smtp-server.h" enum submission_proxy_state { SUBMISSION_PROXY_BANNER = 0, SUBMISSION_PROXY_EHLO, SUBMISSION_PROXY_STARTTLS, SUBMISSION_PROXY_TLS_EHLO, SUBMISSION_PROXY_XCLIENT, SUBMISSION_PROXY_XCLIENT_EHLO, SUBMISSION_PROXY_AUTHENTICATE, SUBMISSION_PROXY_STATE_COUNT }; struct submission_client { struct client common; const struct submission_login_settings *set; enum smtp_capability backend_capabilities; struct smtp_server_connection *conn; struct smtp_server_cmd_ctx *pending_auth; enum submission_proxy_state proxy_state; enum smtp_capability proxy_capability; char *proxy_sasl_ir; unsigned int proxy_reply_status; struct smtp_server_reply *proxy_reply; const char **proxy_xclient; unsigned int proxy_xclient_replies_expected; }; #endif dovecot-2.3.21.1/src/submission-login/client-authenticate.h0000644000000000000000000000131014656633576020503 00000000000000#ifndef CLIENT_AUTHENTICATE_H #define CLIENT_AUTHENTICATE_H void submission_client_auth_result(struct client *client, enum client_auth_result result, const struct client_auth_reply *reply, const char *text); void submission_client_auth_send_challenge(struct client *client, const char *data); int cmd_helo(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_helo *data); int cmd_auth_continue(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, const char *response); int cmd_auth(void *conn_ctx, struct smtp_server_cmd_ctx *cmd, struct smtp_server_cmd_auth *data); void cmd_mail(struct smtp_server_cmd_ctx *cmd, const char *params); #endif dovecot-2.3.21.1/src/submission-login/submission-proxy.h0000644000000000000000000000061514656633576020132 00000000000000#ifndef SUBMISSION_PROXY_H #define SUBMISSION_PROXY_H void submission_proxy_reset(struct client *client); int submission_proxy_parse_line(struct client *client, const char *line); void submission_proxy_failed(struct client *client, enum login_proxy_failure_type type, const char *reason, bool reconnecting); const char *submission_proxy_get_state(struct client *client); #endif dovecot-2.3.21.1/src/submission-login/Makefile.am0000644000000000000000000000130214656633576016435 00000000000000pkglibexecdir = $(libexecdir)/dovecot pkglibexec_PROGRAMS = submission-login AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-sasl \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-smtp \ -I$(top_srcdir)/src/login-common submission_login_LDADD = \ $(LIBDOVECOT_LOGIN) \ $(LIBDOVECOT) \ $(SSL_LIBS) submission_login_DEPENDENCIES = \ $(LIBDOVECOT_LOGIN) \ $(LIBDOVECOT_DEPS) submission_login_SOURCES = \ client.c \ client-authenticate.c \ submission-login-settings.c \ submission-proxy.c noinst_HEADERS = \ client.h \ client-authenticate.h \ submission-login-settings.h \ submission-proxy.h dovecot-2.3.21.1/src/submission-login/client.c0000644000000000000000000002227314656633576016035 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "login-common.h" #include "base64.h" #include "buffer.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "randgen.h" #include "hostpid.h" #include "safe-memset.h" #include "str.h" #include "strescape.h" #include "master-service.h" #include "master-service-ssl-settings.h" #include "client.h" #include "client-authenticate.h" #include "auth-client.h" #include "submission-proxy.h" #include "submission-login-settings.h" /* Disconnect client when it sends too many bad commands */ #define CLIENT_MAX_BAD_COMMANDS 10 static const struct smtp_server_callbacks smtp_callbacks; static struct smtp_server *smtp_server = NULL; static void client_parse_backend_capabilities(struct submission_client *subm_client ) { const struct submission_login_settings *set = subm_client->set; const char *const *str; if (set->submission_backend_capabilities == NULL) { subm_client->backend_capabilities = SMTP_CAPABILITY_8BITMIME; return; } subm_client->backend_capabilities = SMTP_CAPABILITY_NONE; str = t_strsplit_spaces(set->submission_backend_capabilities, " ,"); for (; *str != NULL; str++) { enum smtp_capability cap = smtp_capability_find_by_name(*str); if (cap == SMTP_CAPABILITY_NONE) { i_warning("Unknown SMTP capability in submission_backend_capabilities: " "%s", *str); continue; } subm_client->backend_capabilities |= cap; } /* Make sure CHUNKING support is always enabled when BINARYMIME is enabled by explicit configuration. */ if (HAS_ALL_BITS(subm_client->backend_capabilities, SMTP_CAPABILITY_BINARYMIME)) { subm_client->backend_capabilities |= SMTP_CAPABILITY_CHUNKING; } } static int submission_login_start_tls(void *conn_ctx, struct istream **input, struct ostream **output) { struct submission_client *subm_client = conn_ctx; struct client *client = &subm_client->common; client->starttls = TRUE; if (client_init_ssl(client) < 0) { client_notify_disconnect(client, CLIENT_DISCONNECT_INTERNAL_ERROR, "TLS initialization failed."); client_destroy(client, "Disconnected: TLS initialization failed."); return -1; } login_refresh_proctitle(); *input = client->input; *output = client->output; return 0; } static struct client *submission_client_alloc(pool_t pool) { struct submission_client *subm_client; subm_client = p_new(pool, struct submission_client, 1); return &subm_client->common; } static void submission_client_create(struct client *client, void **other_sets) { static const char *const xclient_extensions[] = { "FORWARD", NULL }; struct submission_client *subm_client = container_of(client, struct submission_client, common); struct smtp_server_settings smtp_set; subm_client->set = other_sets[0]; client_parse_backend_capabilities(subm_client); i_zero(&smtp_set); smtp_set.capabilities = SMTP_CAPABILITY_SIZE | SMTP_CAPABILITY_ENHANCEDSTATUSCODES | SMTP_CAPABILITY_AUTH | SMTP_CAPABILITY_XCLIENT; if (client_is_tls_enabled(client)) smtp_set.capabilities |= SMTP_CAPABILITY_STARTTLS; smtp_set.hostname = subm_client->set->hostname; smtp_set.login_greeting = client->set->login_greeting; smtp_set.tls_required = !client->secured && (strcmp(client->ssl_set->ssl, "required") == 0); smtp_set.xclient_extensions = xclient_extensions; smtp_set.command_limits.max_parameters_size = LOGIN_MAX_INBUF_SIZE; smtp_set.command_limits.max_auth_size = LOGIN_MAX_AUTH_BUF_SIZE; smtp_set.debug = client->set->auth_debug; subm_client->conn = smtp_server_connection_create_from_streams( smtp_server, client->input, client->output, &client->real_remote_ip, client->real_remote_port, &smtp_set, &smtp_callbacks, subm_client); } static void submission_client_destroy(struct client *client) { struct submission_client *subm_client = container_of(client, struct submission_client, common); if (subm_client->conn != NULL) smtp_server_connection_close(&subm_client->conn, NULL); i_free_and_null(subm_client->proxy_xclient); } static void submission_client_notify_auth_ready(struct client *client) { struct submission_client *subm_client = container_of(client, struct submission_client, common); client->banner_sent = TRUE; smtp_server_connection_start(subm_client->conn); } static void submission_client_notify_disconnect(struct client *_client, enum client_disconnect_reason reason, const char *text) { struct submission_client *client = container_of(_client, struct submission_client, common); struct smtp_server_connection *conn; conn = client->conn; client->conn = NULL; if (conn != NULL) { switch (reason) { case CLIENT_DISCONNECT_TIMEOUT: smtp_server_connection_terminate(&conn, "4.4.2", text); break; case CLIENT_DISCONNECT_SYSTEM_SHUTDOWN: smtp_server_connection_terminate(&conn, "4.3.2", text); break; case CLIENT_DISCONNECT_INTERNAL_ERROR: default: smtp_server_connection_terminate(&conn, "4.0.0", text); break; } } } static void client_connection_cmd_xclient(void *context, struct smtp_server_cmd_ctx *cmd, struct smtp_proxy_data *data) { unsigned int i; struct submission_client *client = context; if (data->source_ip.family != 0) client->common.ip = data->source_ip; if (data->source_port != 0) client->common.remote_port = data->source_port; if (data->ttl_plus_1 > 0) client->common.proxy_ttl = data->ttl_plus_1 - 1; if (data->session != NULL) { client->common.session_id = p_strdup(client->common.pool, data->session); } for (i = 0; i < data->extra_fields_count; i++) { const char *name = data->extra_fields[i].name; const char *value = data->extra_fields[i].value; if (strcasecmp(name, "FORWARD") == 0) { size_t value_len = strlen(value); if (client->common.forward_fields != NULL) { str_truncate(client->common.forward_fields, 0); } else { client->common.forward_fields = str_new( client->common.preproxy_pool, MAX_BASE64_DECODED_SIZE(value_len)); if (base64_decode(value, value_len, NULL, client->common.forward_fields) < 0) { smtp_server_reply(cmd, 501, "5.5.4", "Invalid FORWARD parameter"); } } } } } static void client_connection_disconnect(void *context, const char *reason) { struct submission_client *client = context; client->pending_auth = NULL; client_disconnect(&client->common, reason, !client->common.login_success); } static void client_connection_free(void *context) { struct submission_client *client = context; if (client->conn == NULL) return; client->conn = NULL; client_destroy(&client->common, NULL); } static bool client_connection_is_trusted(void *context) { struct submission_client *client = context; return client->common.trusted; } static void submission_login_die(void) { /* do nothing. submission connections typically die pretty quick anyway. */ } static void submission_login_preinit(void) { login_set_roots = submission_login_setting_roots; } static void submission_login_init(void) { struct smtp_server_settings smtp_server_set; /* override the default login_die() */ master_service_set_die_callback(master_service, submission_login_die); /* initialize SMTP server */ i_zero(&smtp_server_set); smtp_server_set.protocol = SMTP_PROTOCOL_SMTP; smtp_server_set.max_pipelined_commands = 5; smtp_server_set.max_bad_commands = CLIENT_MAX_BAD_COMMANDS; smtp_server_set.reason_code_module = "submission"; /* Pre-auth state is always logged either as GREETING or READY. It's not very useful. */ smtp_server_set.no_state_in_reason = TRUE; smtp_server = smtp_server_init(&smtp_server_set); smtp_server_command_override(smtp_server, "MAIL", cmd_mail, SMTP_SERVER_CMD_FLAG_PREAUTH); } static void submission_login_deinit(void) { clients_destroy_all(); smtp_server_deinit(&smtp_server); } static const struct smtp_server_callbacks smtp_callbacks = { .conn_cmd_helo = cmd_helo, .conn_start_tls = submission_login_start_tls, .conn_cmd_auth = cmd_auth, .conn_cmd_auth_continue = cmd_auth_continue, .conn_cmd_xclient = client_connection_cmd_xclient, .conn_disconnect = client_connection_disconnect, .conn_free = client_connection_free, .conn_is_trusted = client_connection_is_trusted }; static struct client_vfuncs submission_client_vfuncs = { .alloc = submission_client_alloc, .create = submission_client_create, .destroy = submission_client_destroy, .notify_auth_ready = submission_client_notify_auth_ready, .notify_disconnect = submission_client_notify_disconnect, .auth_send_challenge = submission_client_auth_send_challenge, .auth_result = submission_client_auth_result, .proxy_reset = submission_proxy_reset, .proxy_parse_line = submission_proxy_parse_line, .proxy_failed = submission_proxy_failed, .proxy_get_state = submission_proxy_get_state, }; static struct login_binary submission_login_binary = { .protocol = "submission", .process_name = "submission-login", .default_port = 587, .event_category = { .name = "submission", }, .client_vfuncs = &submission_client_vfuncs, .preinit = submission_login_preinit, .init = submission_login_init, .deinit = submission_login_deinit, .sasl_support_final_reply = FALSE, .anonymous_login_acceptable = FALSE, }; int main(int argc, char *argv[]) { return login_binary_run(&submission_login_binary, argc, argv); } dovecot-2.3.21.1/src/imap-hibernate/0000755000000000000000000000000014656633640014041 500000000000000dovecot-2.3.21.1/src/imap-hibernate/imap-master-connection.c0000644000000000000000000000710414656633576020513 00000000000000/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "connection.h" #include "imap-master-connection.h" #define IMAP_MASTER_CONNECTION_TIMEOUT_MSECS 30000 struct imap_master_connection { struct connection conn; struct timeout *to; imap_master_connection_send_callback_t *send_callback; imap_master_connection_read_callback_t *read_callback; void *context; }; static struct connection_list *master_clients; static void imap_master_connection_timeout(struct imap_master_connection *conn) { e_error(conn->conn.event, "Timeout communicating with %s (version %sreceived)", conn->conn.name, conn->conn.version_received ? "" : "not "); imap_master_connection_deinit(&conn); } int imap_master_connection_init(const char *path, imap_master_connection_send_callback_t *send_callback, imap_master_connection_read_callback_t *read_callback, void *context, struct imap_master_connection **conn_r, const char **error_r) { struct imap_master_connection *conn; conn = i_new(struct imap_master_connection, 1); conn->send_callback = send_callback; conn->read_callback = read_callback; conn->context = context; connection_init_client_unix(master_clients, &conn->conn, path); if (connection_client_connect(&conn->conn) < 0) { int ret = errno == EAGAIN ? 0 : -1; *error_r = t_strdup_printf( "net_connect_unix(%s) failed: %m", path); connection_deinit(&conn->conn); i_free(conn); return ret; } conn->to = timeout_add(IMAP_MASTER_CONNECTION_TIMEOUT_MSECS, imap_master_connection_timeout, conn); *conn_r = conn; return 1; } static void imap_master_read_callback(struct imap_master_connection **_conn, const char *line) { struct imap_master_connection *conn = *_conn; imap_master_connection_read_callback_t *read_callback = conn->read_callback; *_conn = NULL; conn->read_callback = NULL; read_callback(conn->context, line); /* connection is destroyed now */ } void imap_master_connection_deinit(struct imap_master_connection **_conn) { imap_master_read_callback(_conn, t_strdup_printf( "-%s", connection_disconnect_reason(&(*_conn)->conn))); } void imap_master_connection_free(struct imap_master_connection **_conn) { struct imap_master_connection *conn = *_conn; *_conn = NULL; timeout_remove(&conn->to); connection_deinit(&conn->conn); i_free(conn); } static void imap_master_client_destroy(struct connection *_conn) { struct imap_master_connection *conn = (struct imap_master_connection *)_conn; imap_master_connection_deinit(&conn); } static int imap_master_client_input_line(struct connection *_conn, const char *line) { struct imap_master_connection *conn = (struct imap_master_connection *)_conn; if (!_conn->version_received) { if (connection_input_line_default(_conn, line) < 0) return -1; conn->send_callback(conn->context, _conn->output); return 1; } else { imap_master_read_callback(&conn, line); /* we're finished now with this connection - disconnect it */ return -1; } } static struct connection_settings client_set = { .service_name_in = "imap-master", .service_name_out = "imap-master", .major_version = 1, .minor_version = 0, .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, .client = TRUE }; static const struct connection_vfuncs client_vfuncs = { .destroy = imap_master_client_destroy, .input_line = imap_master_client_input_line }; void imap_master_connections_init(void) { master_clients = connection_list_init(&client_set, &client_vfuncs); } void imap_master_connections_deinit(void) { connection_list_deinit(&master_clients); } dovecot-2.3.21.1/src/imap-hibernate/imap-client.c0000644000000000000000000005675014656633576016354 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "ioloop.h" #include "fdpass.h" #include "hostpid.h" #include "connection.h" #include "iostream.h" #include "istream.h" #include "ostream.h" #include "llist.h" #include "priorityq.h" #include "base64.h" #include "str.h" #include "strescape.h" #include "time-util.h" #include "var-expand.h" #include "master-service.h" #include "master-service-settings.h" #include "imap-keepalive.h" #include "imap-master-connection.h" #include "imap-client.h" #include #define IMAP_MASTER_SOCKET_NAME "imap-master" /* we only need enough for "DONE\r\n IDLE\r\n" */ #define IMAP_MAX_INBUF 12 + 1 + 128 /* DONE\r\nIDLE\r\n + ' ' + */ #define IMAP_MAX_OUTBUF 1024 /* If client has sent input and we can't recreate imap process in this many seconds, disconnect the client. */ #define IMAP_CLIENT_MOVE_BACK_WITH_INPUT_TIMEOUT_SECS 10 /* If there's a change notification and we can't recreate imap process in this many seconds, disconnect the client. */ #define IMAP_CLIENT_MOVE_BACK_WITHOUT_INPUT_TIMEOUT_SECS (60*5) /* How often to try to unhibernate clients. */ #define IMAP_UNHIBERNATE_RETRY_MSECS 100 #define IMAP_CLIENT_BUFFER_FULL_ERROR "Client output buffer is full" #define IMAP_CLIENT_UNHIBERNATE_ERROR "Failed to unhibernate client" enum imap_client_input_state { IMAP_CLIENT_INPUT_STATE_UNKNOWN, IMAP_CLIENT_INPUT_STATE_BAD, IMAP_CLIENT_INPUT_STATE_DONE_LF, IMAP_CLIENT_INPUT_STATE_DONE_CRLF, IMAP_CLIENT_INPUT_STATE_DONEIDLE }; struct imap_client_notify { int fd; struct io *io; }; struct imap_client { struct priorityq_item item; struct imap_client *prev, *next; pool_t pool; struct event *event; struct imap_client_state state; ARRAY(struct imap_client_notify) notifys; time_t move_back_start; struct timeout *to_move_back; int fd; struct io *io; struct istream *input; struct ostream *output; struct timeout *to_keepalive; struct imap_master_connection *master_conn; struct ioloop_context *ioloop_ctx; const char *log_prefix; unsigned int next_read_threshold; bool bad_done, idle_done; bool unhibernate_queued; bool input_pending; bool shutdown_fd_on_destroy; }; static struct imap_client *imap_clients; static struct priorityq *unhibernate_queue; static struct timeout *to_unhibernate; static const char imap_still_here_text[] = "* OK Still here\r\n"; static struct event_category event_category_imap = { .name = "imap", }; static struct event_category event_category_imap_hibernate = { .name = "imap-hibernate", .parent = &event_category_imap, }; static void imap_client_stop(struct imap_client *client); void imap_client_destroy(struct imap_client **_client, const char *reason); static void imap_client_add_idle_keepalive_timeout(struct imap_client *client); static void imap_clients_unhibernate(void *context); static void imap_client_stop_notify_listening(struct imap_client *client); static void imap_client_disconnected(struct imap_client **_client) { struct imap_client *client = *_client; const char *reason; reason = io_stream_get_disconnect_reason(client->input, client->output); imap_client_destroy(_client, reason); } static void imap_client_unhibernate_failed(struct imap_client **_client, const char *error) { struct imap_client *client = *_client; struct timeval created; event_get_create_time(client->event, &created); struct event_passthrough *e = event_create_passthrough(client->event)-> set_name("imap_client_unhibernated")-> add_int("hibernation_usecs", timeval_diff_usecs(&ioloop_timeval, &created))-> add_str("error", error); e_error(e->event(), IMAP_CLIENT_UNHIBERNATE_ERROR": %s", error); imap_client_destroy(_client, IMAP_CLIENT_UNHIBERNATE_ERROR); } static void imap_client_parse_userdb_fields(struct imap_client *client, const char **auth_user_r) { const char *const *field; unsigned int i; *auth_user_r = NULL; if (client->state.userdb_fields == NULL) return; field = t_strsplit_tabescaped(client->state.userdb_fields); for (i = 0; field[i] != NULL; i++) { if (str_begins(field[i], "auth_user=")) *auth_user_r = field[i] + 10; } } static void imap_client_move_back_send_callback(void *context, struct ostream *output) { struct imap_client *client = context; const struct imap_client_state *state = &client->state; string_t *str = t_str_new(256); struct timeval created; const unsigned char *input_data; size_t input_size; ssize_t ret; str_append_tabescaped(str, state->username); event_get_create_time(client->event, &created); str_printfa(str, "\thibernation_started=%"PRIdTIME_T".%06u", created.tv_sec, (unsigned int)created.tv_usec); if (state->session_id != NULL) { str_append(str, "\tsession="); str_append_tabescaped(str, state->session_id); } if (state->session_created != 0) { str_printfa(str, "\tsession_created=%s", dec2str(state->session_created)); } if (state->tag != NULL) str_printfa(str, "\ttag=%s", client->state.tag); if (state->local_ip.family != 0) str_printfa(str, "\tlip=%s", net_ip2addr(&state->local_ip)); if (state->local_port != 0) str_printfa(str, "\tlport=%u", state->local_port); if (state->remote_ip.family != 0) str_printfa(str, "\trip=%s", net_ip2addr(&state->remote_ip)); if (state->remote_port != 0) str_printfa(str, "\trport=%u", state->remote_port); if (state->userdb_fields != NULL) { str_append(str, "\tuserdb_fields="); str_append_tabescaped(str, state->userdb_fields); } if (major(state->peer_dev) != 0 || minor(state->peer_dev) != 0) { str_printfa(str, "\tpeer_dev_major=%lu\tpeer_dev_minor=%lu", (unsigned long)major(state->peer_dev), (unsigned long)minor(state->peer_dev)); } if (state->peer_ino != 0) str_printfa(str, "\tpeer_ino=%llu", (unsigned long long)state->peer_ino); if (state->state_size > 0) { str_append(str, "\tstate="); base64_encode(state->state, state->state_size, str); } input_data = i_stream_get_data(client->input, &input_size); if (input_size > 0) { str_append(str, "\tclient_input="); base64_encode(input_data, input_size, str); } i_assert(o_stream_get_buffer_used_size(client->output) == 0); if (client->idle_done) { if (client->bad_done) str_append(str, "\tbad-done"); } else if (client->state.idle_cmd) { /* IDLE continues after sending changes */ str_append(str, "\tidle-continue"); } str_append_c(str, '\n'); /* send the fd first */ ret = fd_send(o_stream_get_fd(output), client->fd, str_data(str), 1); if (ret < 0) { const char *error = t_strdup_printf( "fd_send(%s) failed: %m", o_stream_get_name(output)); imap_client_unhibernate_failed(&client, error); return; } /* If unhibernation fails after this, shutdown() the fd to make sure the imap process won't later on finish unhibernation after all and cause confusion. */ client->shutdown_fd_on_destroy = TRUE; i_assert(ret > 0); o_stream_nsend(output, str_data(str) + 1, str_len(str) - 1); } static void imap_client_move_back_read_callback(void *context, const char *line) { struct imap_client *client = context; if (line[0] != '+') { /* failed - FIXME: retry later? */ imap_client_unhibernate_failed(&client, line+1); } else { client->shutdown_fd_on_destroy = FALSE; imap_client_destroy(&client, NULL); } } static bool imap_move_has_reached_timeout(struct imap_client *client) { int max_secs = client->input_pending ? IMAP_CLIENT_MOVE_BACK_WITH_INPUT_TIMEOUT_SECS : IMAP_CLIENT_MOVE_BACK_WITHOUT_INPUT_TIMEOUT_SECS; return client->move_back_start != 0 && ioloop_time - client->move_back_start > max_secs; } static bool imap_client_try_move_back(struct imap_client *client) { const struct master_service_settings *master_set; const char *path, *error; int ret; if (o_stream_get_buffer_used_size(client->output) > 0) { /* there is data buffered, so we have to disconnect you */ imap_client_destroy(&client, IMAP_CLIENT_BUFFER_FULL_ERROR); return TRUE; } master_set = master_service_settings_get(master_service); path = t_strconcat(master_set->base_dir, "/"IMAP_MASTER_SOCKET_NAME, NULL); ret = imap_master_connection_init(path, imap_client_move_back_send_callback, imap_client_move_back_read_callback, client, &client->master_conn, &error); if (ret > 0) { /* success */ imap_client_stop(client); return TRUE; } else if (ret < 0 || imap_move_has_reached_timeout(client)) { /* failed to connect to the imap-master socket */ imap_client_unhibernate_failed(&client, error); return TRUE; } e_debug(event_create_passthrough(client->event)-> set_name("imap_client_unhibernate_retried")-> add_str("error", error)->event(), "Unhibernation failed: %s - retrying", error); /* Stop listening for client's IOs while waiting for the next reconnection attempt. However if we got here because of an external notification keep waiting to see if client sends any IO, since that will cause the unhibernation to be aborted earlier. */ if (client->input_pending) io_remove(&client->io); imap_client_stop_notify_listening(client); return FALSE; } static void imap_client_move_back(struct imap_client *client) { if (imap_client_try_move_back(client)) return; /* imap-master socket is busy. retry in a while. */ if (client->move_back_start == 0) client->move_back_start = ioloop_time; if (!client->unhibernate_queued) { client->unhibernate_queued = TRUE; priorityq_add(unhibernate_queue, &client->item); } if (to_unhibernate == NULL) { to_unhibernate = timeout_add_short(IMAP_UNHIBERNATE_RETRY_MSECS, imap_clients_unhibernate, NULL); } } static enum imap_client_input_state imap_client_input_parse(const unsigned char *data, size_t size, const char **tag_r) { const unsigned char *tag_start, *tag_end; enum imap_client_input_state state = IMAP_CLIENT_INPUT_STATE_DONE_LF; /* skip over DONE[\r]\n */ if (i_memcasecmp(data, "DONE", I_MIN(size, 4)) != 0) return IMAP_CLIENT_INPUT_STATE_BAD; if (size <= 4) return IMAP_CLIENT_INPUT_STATE_UNKNOWN; data += 4; size -= 4; if (data[0] == '\r') { state = IMAP_CLIENT_INPUT_STATE_DONE_CRLF; data++; size--; } if (size == 0) return IMAP_CLIENT_INPUT_STATE_UNKNOWN; if (data[0] != '\n') return IMAP_CLIENT_INPUT_STATE_BAD; data++; size--; if (size == 0) return state; tag_start = data; /* skip over tag */ while(data[0] != ' ' && data[0] != '\r' && data[0] != '\t' ) { data++; size--; } tag_end = data; if (size == 0) return state; if (data[0] != ' ') return IMAP_CLIENT_INPUT_STATE_BAD; data++; size--; /* skip over IDLE[\r]\n - checking this assumes that the DONE and IDLE are sent in the same IP packet, otherwise we'll unnecessarily recreate the imap process and immediately resume IDLE there. if this becomes an issue we could add a small delay to the imap process creation and wait for the IDLE command during it. */ if (size <= 4 || i_memcasecmp(data, "IDLE", 4) != 0) return state; data += 4; size -= 4; if (data[0] == '\r') { data++; size--; } if (size == 1 && data[0] == '\n') { *tag_r = t_strdup_until(tag_start, tag_end); return IMAP_CLIENT_INPUT_STATE_DONEIDLE; } return state; } static void imap_client_input_idle_cmd(struct imap_client *client) { char *old_tag; const char *new_tag; const char *output; const unsigned char *data; size_t size; bool done = TRUE; int ret; /* we should read either DONE or disconnection. also handle if client sends DONE\nIDLE simply to recreate the IDLE. */ ret = i_stream_read_bytes(client->input, &data, &size, client->next_read_threshold + 1); if (size == 0) { if (ret < 0) imap_client_disconnected(&client); return; } client->next_read_threshold = 0; switch (imap_client_input_parse(data, size, &new_tag)) { case IMAP_CLIENT_INPUT_STATE_UNKNOWN: /* we haven't received a full DONE[\r]\n yet - wait */ client->next_read_threshold = size; return; case IMAP_CLIENT_INPUT_STATE_BAD: /* invalid input - return this to the imap process */ client->bad_done = TRUE; break; case IMAP_CLIENT_INPUT_STATE_DONE_LF: i_stream_skip(client->input, 4+1); break; case IMAP_CLIENT_INPUT_STATE_DONE_CRLF: i_stream_skip(client->input, 4+2); break; case IMAP_CLIENT_INPUT_STATE_DONEIDLE: /* we received DONE+IDLE, so the client simply wanted to notify us that it's still there. continue hibernation. */ old_tag = client->state.tag; client->state.tag = i_strdup(new_tag); output = t_strdup_printf("%s OK Idle completed.\r\n+ idling\r\n", old_tag); i_free(old_tag); ret = o_stream_flush(client->output); if (ret > 0) ret = o_stream_send_str(client->output, output); if (ret < 0) { imap_client_disconnected(&client); return; } if ((size_t)ret != strlen(output)) { /* disconnect */ imap_client_destroy(&client, IMAP_CLIENT_BUFFER_FULL_ERROR); return; } else { done = FALSE; i_stream_skip(client->input, size); } break; } if (done) { client->idle_done = TRUE; client->input_pending = TRUE; imap_client_move_back(client); } else imap_client_add_idle_keepalive_timeout(client); } static void imap_client_input_nonidle(struct imap_client *client) { if (i_stream_read(client->input) < 0) imap_client_disconnected(&client); else { client->input_pending = TRUE; imap_client_move_back(client); } } static void imap_client_input_notify(struct imap_client *client) { imap_client_move_back(client); } static void keepalive_timeout(struct imap_client *client) { ssize_t ret; /* do not send this if there is data buffered */ if ((ret = o_stream_flush(client->output)) < 0) { imap_client_disconnected(&client); return; } else if (ret == 0) return; ret = o_stream_send_str(client->output, imap_still_here_text); if (ret < 0) { imap_client_disconnected(&client); return; } /* ostream buffer size is definitely large enough for this text */ i_assert((size_t)ret == strlen(imap_still_here_text)); imap_client_add_idle_keepalive_timeout(client); } static void imap_client_add_idle_keepalive_timeout(struct imap_client *client) { unsigned int interval = client->state.imap_idle_notify_interval; if (interval == 0) return; interval = imap_keepalive_interval_msecs(client->state.username, &client->state.remote_ip, interval); timeout_remove(&client->to_keepalive); client->to_keepalive = timeout_add(interval, keepalive_timeout, client); } static const struct var_expand_table * imap_client_get_var_expand_table(struct imap_client *client) { const char *username = t_strcut(client->state.username, '@'); const char *domain = i_strchr_to_next(client->state.username, '@'); const char *local_ip = client->state.local_ip.family == 0 ? NULL : net_ip2addr(&client->state.local_ip); const char *remote_ip = client->state.remote_ip.family == 0 ? NULL : net_ip2addr(&client->state.remote_ip); const char *auth_user, *auth_username, *auth_domain; imap_client_parse_userdb_fields(client, &auth_user); if (auth_user == NULL) { auth_user = client->state.username; auth_username = username; auth_domain = domain; } else { auth_username = t_strcut(auth_user, '@'); auth_domain = i_strchr_to_next(auth_user, '@'); } const struct var_expand_table stack_tab[] = { { 'u', client->state.username, "user" }, { 'n', username, "username" }, { 'd', domain, "domain" }, { 's', "imap-hibernate", "service" }, { 'h', NULL /* we shouldn't need this */, "home" }, { 'l', local_ip, "lip" }, { 'r', remote_ip, "rip" }, { 'p', my_pid, "pid" }, { 'i', dec2str(client->state.uid), "uid" }, { '\0', dec2str(client->state.gid), "gid" }, { '\0', client->state.session_id, "session" }, { '\0', auth_user, "auth_user" }, { '\0', auth_username, "auth_username" }, { '\0', auth_domain, "auth_domain" }, /* aliases: */ { '\0', local_ip, "local_ip" }, { '\0', remote_ip, "remote_ip" }, /* NOTE: keep this synced with lib-storage's mail_user_var_expand_table() */ { '\0', NULL, NULL } }; struct var_expand_table *tab; tab = t_malloc_no0(sizeof(stack_tab)); memcpy(tab, stack_tab, sizeof(stack_tab)); return tab; } static int imap_client_var_expand_func_userdb(const char *data, void *context, const char **value_r, const char **error_r ATTR_UNUSED) { const char *const *fields = context; const char *field_name = t_strdup_printf("%s=",t_strcut(data, ':')); const char *default_value = i_strchr_to_next(data, ':'); const char *value = NULL; for(;*fields != NULL; fields++) { if (str_begins(*fields, field_name)) { value = *fields+strlen(field_name); break; } } *value_r = value != NULL ? value : default_value; return 1; } static void imap_client_io_activate_user(struct imap_client *client) { i_set_failure_prefix("%s", client->log_prefix); } static void imap_client_io_deactivate_user(struct imap_client *client ATTR_UNUSED) { i_set_failure_prefix("imap-hibernate: "); } static const char *imap_client_get_anvil_userip_ident(struct imap_client_state *state) { if (state->remote_ip.family == 0) return NULL; return t_strconcat(net_ip2addr(&state->remote_ip), "/", str_tabescape(state->username), NULL); } struct imap_client * imap_client_create(int fd, const struct imap_client_state *state) { const struct var_expand_func_table funcs[] = { { "userdb", imap_client_var_expand_func_userdb }, { NULL, NULL } }; struct imap_client *client; pool_t pool = pool_alloconly_create("imap client", 256); void *statebuf; const char *ident, *error; i_assert(state->username != NULL); i_assert(state->mail_log_prefix != NULL); fd_set_nonblock(fd, TRUE); /* it should already be, but be sure */ client = p_new(pool, struct imap_client, 1); client->pool = pool; client->fd = fd; client->input = i_stream_create_fd(fd, IMAP_MAX_INBUF); client->output = o_stream_create_fd(fd, IMAP_MAX_OUTBUF); client->state = *state; client->state.username = p_strdup(pool, state->username); client->state.session_id = p_strdup(pool, state->session_id); client->state.userdb_fields = p_strdup(pool, state->userdb_fields); client->state.stats = p_strdup(pool, state->stats); client->event = event_create(NULL); event_add_category(client->event, &event_category_imap_hibernate); event_add_str(client->event, "user", state->username); event_add_str(client->event, "session", state->session_id); if (state->mailbox_vname != NULL) event_add_str(client->event, "mailbox", state->mailbox_vname); if (state->local_ip.family != 0) event_add_str(client->event, "local_ip", net_ip2addr(&state->local_ip)); if (state->local_port != 0) event_add_int(client->event, "local_port", state->local_port); if (state->remote_ip.family != 0) event_add_str(client->event, "remote_ip", net_ip2addr(&state->remote_ip)); if (state->remote_port != 0) event_add_int(client->event, "remote_port", state->remote_port); if (state->state_size > 0) { client->state.state = statebuf = p_malloc(pool, state->state_size); memcpy(statebuf, state->state, state->state_size); client->state.state_size = state->state_size; } T_BEGIN { string_t *str; char **fields = p_strsplit_tabescaped(unsafe_data_stack_pool, client->state.userdb_fields); str = t_str_new(256); if (var_expand_with_funcs(str, state->mail_log_prefix, imap_client_get_var_expand_table(client), funcs, fields, &error) <= 0) { e_error(client->event, "Failed to expand mail_log_prefix=%s: %s", state->mail_log_prefix, error); } client->log_prefix = p_strdup(pool, str_c(str)); } T_END; ident = imap_client_get_anvil_userip_ident(&client->state); if (ident != NULL) { master_service_anvil_send(master_service, t_strconcat( "CONNECT\t", my_pid, "\timap/", ident, "\n", NULL)); client->state.anvil_sent = TRUE; } p_array_init(&client->notifys, pool, 2); DLLIST_PREPEND(&imap_clients, client); return client; } static void imap_client_stop_notify_listening(struct imap_client *client) { struct imap_client_notify *notify; array_foreach_modifiable(&client->notifys, notify) { io_remove(¬ify->io); i_close_fd(¬ify->fd); } } static void imap_client_stop(struct imap_client *client) { if (client->unhibernate_queued) { priorityq_remove(unhibernate_queue, &client->item); client->unhibernate_queued = FALSE; } io_remove(&client->io); timeout_remove(&client->to_keepalive); imap_client_stop_notify_listening(client); } void imap_client_destroy(struct imap_client **_client, const char *reason) { struct imap_client *client = *_client; *_client = NULL; if (reason != NULL) { /* the client input/output bytes don't count the DONE+IDLE by imap-hibernate, but that shouldn't matter much. */ e_info(client->event, "Disconnected: %s %s", reason, client->state.stats); } if (client->state.anvil_sent) { master_service_anvil_send(master_service, t_strconcat( "DISCONNECT\t", my_pid, "\timap/", imap_client_get_anvil_userip_ident(&client->state), "\n", NULL)); } if (client->master_conn != NULL) imap_master_connection_free(&client->master_conn); if (client->ioloop_ctx != NULL) { io_loop_context_remove_callbacks(client->ioloop_ctx, imap_client_io_activate_user, imap_client_io_deactivate_user, client); imap_client_io_deactivate_user(client); io_loop_context_unref(&client->ioloop_ctx); } if (client->state.tag != NULL) i_free(client->state.tag); if (client->shutdown_fd_on_destroy) { if (shutdown(client->fd, SHUT_RDWR) < 0) e_error(client->event, "shutdown() failed: %m"); } DLLIST_REMOVE(&imap_clients, client); imap_client_stop(client); i_stream_destroy(&client->input); o_stream_destroy(&client->output); i_close_fd(&client->fd); event_unref(&client->event); pool_unref(&client->pool); master_service_client_connection_destroyed(master_service); } void imap_client_add_notify_fd(struct imap_client *client, int fd) { struct imap_client_notify *notify; notify = array_append_space(&client->notifys); notify->fd = fd; } void imap_client_create_finish(struct imap_client *client) { struct imap_client_notify *notify; client->ioloop_ctx = io_loop_context_new(current_ioloop); io_loop_context_add_callbacks(client->ioloop_ctx, imap_client_io_activate_user, imap_client_io_deactivate_user, client); io_loop_context_switch(client->ioloop_ctx); if (client->state.idle_cmd) { client->io = io_add(client->fd, IO_READ, imap_client_input_idle_cmd, client); } else { client->io = io_add(client->fd, IO_READ, imap_client_input_nonidle, client); } imap_client_add_idle_keepalive_timeout(client); array_foreach_modifiable(&client->notifys, notify) { notify->io = io_add(notify->fd, IO_READ, imap_client_input_notify, client); } } static int client_unhibernate_cmp(const void *p1, const void *p2) { const struct imap_client *c1 = p1, *c2 = p2; time_t t1, t2; t1 = c1->move_back_start + (c1->input_pending ? IMAP_CLIENT_MOVE_BACK_WITH_INPUT_TIMEOUT_SECS : IMAP_CLIENT_MOVE_BACK_WITHOUT_INPUT_TIMEOUT_SECS); t2 = c2->move_back_start + (c2->input_pending ? IMAP_CLIENT_MOVE_BACK_WITH_INPUT_TIMEOUT_SECS : IMAP_CLIENT_MOVE_BACK_WITHOUT_INPUT_TIMEOUT_SECS); if (t1 < t2) return -1; if (t1 > t2) return 1; return 0; } static void imap_clients_unhibernate(void *context ATTR_UNUSED) { struct priorityq_item *item; while ((item = priorityq_peek(unhibernate_queue)) != NULL) { struct imap_client *client = (struct imap_client *)item; if (!imap_client_try_move_back(client)) return; } timeout_remove(&to_unhibernate); } void imap_clients_init(void) { unhibernate_queue = priorityq_init(client_unhibernate_cmp, 64); } void imap_clients_deinit(void) { while (imap_clients != NULL) { struct imap_client *client = imap_clients; imap_client_io_activate_user(client); imap_client_destroy(&client, "Shutting down"); } timeout_remove(&to_unhibernate); priorityq_deinit(&unhibernate_queue); } dovecot-2.3.21.1/src/imap-hibernate/imap-client.h0000644000000000000000000000162314656633576016346 00000000000000#ifndef IMAP_CLIENT_H #define IMAP_CLIENT_H #include "net.h" struct imap_client_state { /* required: */ const char *username, *mail_log_prefix; /* optional: */ const char *session_id, *mailbox_vname, *userdb_fields, *stats; struct ip_addr local_ip, remote_ip; in_port_t local_port, remote_port; time_t session_created; uid_t uid; gid_t gid; dev_t peer_dev; ino_t peer_ino; char *tag; const unsigned char *state; size_t state_size; unsigned int imap_idle_notify_interval; bool idle_cmd; bool have_notify_fd; bool anvil_sent; }; struct imap_client * imap_client_create(int fd, const struct imap_client_state *state); void imap_client_add_notify_fd(struct imap_client *client, int fd); void imap_client_create_finish(struct imap_client *client); void imap_client_destroy(struct imap_client **_client, const char *reason); void imap_clients_init(void); void imap_clients_deinit(void); #endif dovecot-2.3.21.1/src/imap-hibernate/imap-master-connection.h0000644000000000000000000000147714656633576020527 00000000000000#ifndef IMAP_MASTER_CONNECTION_H #define IMAP_MASTER_CONNECTION_H struct imap_master_connection; typedef void imap_master_connection_send_callback_t(void *context, struct ostream *output); typedef void imap_master_connection_read_callback_t(void *context, const char *reply); /* Returns 1 = success, 0 = retry later, -1 = error */ int imap_master_connection_init(const char *path, imap_master_connection_send_callback_t *send_callback, imap_master_connection_read_callback_t *read_callback, void *context, struct imap_master_connection **conn_r, const char **error_r); void imap_master_connection_deinit(struct imap_master_connection **conn); void imap_master_connection_free(struct imap_master_connection **conn); void imap_master_connections_init(void); void imap_master_connections_deinit(void); #endif dovecot-2.3.21.1/src/imap-hibernate/Makefile.in0000644000000000000000000006455714656633607016052 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ pkglibexec_PROGRAMS = imap-hibernate$(EXEEXT) subdir = src/imap-hibernate ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(pkglibexecdir)" PROGRAMS = $(pkglibexec_PROGRAMS) am_imap_hibernate_OBJECTS = imap-client.$(OBJEXT) \ imap-hibernate-client.$(OBJEXT) \ imap-hibernate-settings.$(OBJEXT) \ imap-master-connection.$(OBJEXT) main.$(OBJEXT) imap_hibernate_OBJECTS = $(am_imap_hibernate_OBJECTS) am__DEPENDENCIES_1 = AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/imap-client.Po \ ./$(DEPDIR)/imap-hibernate-client.Po \ ./$(DEPDIR)/imap-hibernate-settings.Po \ ./$(DEPDIR)/imap-master-connection.Po ./$(DEPDIR)/main.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(imap_hibernate_SOURCES) DIST_SOURCES = $(imap_hibernate_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac HEADERS = $(noinst_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) pkglibexecdir = $(libexecdir)/dovecot ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-imap \ $(BINARY_CFLAGS) imap_hibernate_LDADD = $(LIBDOVECOT) \ $(BINARY_LDFLAGS) imap_hibernate_DEPENDENCIES = $(LIBDOVECOT_DEPS) imap_hibernate_SOURCES = \ imap-client.c \ imap-hibernate-client.c \ imap-hibernate-settings.c \ imap-master-connection.c \ main.c noinst_HEADERS = \ imap-client.h \ imap-hibernate-client.h \ imap-master-connection.h all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/imap-hibernate/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/imap-hibernate/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files clean-pkglibexecPROGRAMS: @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list imap-hibernate$(EXEEXT): $(imap_hibernate_OBJECTS) $(imap_hibernate_DEPENDENCIES) $(EXTRA_imap_hibernate_DEPENDENCIES) @rm -f imap-hibernate$(EXEEXT) $(AM_V_CCLD)$(LINK) $(imap_hibernate_OBJECTS) $(imap_hibernate_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-hibernate-client.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-hibernate-settings.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-master-connection.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am check: check-am all-am: Makefile $(PROGRAMS) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkglibexecdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-pkglibexecPROGRAMS \ mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/imap-client.Po -rm -f ./$(DEPDIR)/imap-hibernate-client.Po -rm -f ./$(DEPDIR)/imap-hibernate-settings.Po -rm -f ./$(DEPDIR)/imap-master-connection.Po -rm -f ./$(DEPDIR)/main.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-pkglibexecPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/imap-client.Po -rm -f ./$(DEPDIR)/imap-hibernate-client.Po -rm -f ./$(DEPDIR)/imap-hibernate-settings.Po -rm -f ./$(DEPDIR)/imap-master-connection.Po -rm -f ./$(DEPDIR)/main.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkglibexecPROGRAMS .MAKE: install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ clean-generic clean-libtool clean-pkglibexecPROGRAMS \ cscopelist-am ctags ctags-am distclean distclean-compile \ distclean-generic distclean-libtool distclean-tags distdir dvi \ dvi-am html html-am info info-am install install-am \ install-data install-data-am install-dvi install-dvi-am \ install-exec install-exec-am install-html install-html-am \ install-info install-info-am install-man install-pdf \ install-pdf-am install-pkglibexecPROGRAMS install-ps \ install-ps-am install-strip installcheck installcheck-am \ installdirs maintainer-clean maintainer-clean-generic \ mostlyclean mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ uninstall-am uninstall-pkglibexecPROGRAMS .PRECIOUS: Makefile # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/imap-hibernate/imap-hibernate-settings.c0000644000000000000000000000236414656633576020665 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "settings-parser.h" #include "service-settings.h" #include #include /* */ static struct file_listener_settings imap_hibernate_unix_listeners_array[] = { { "imap-hibernate", 0660, "", "$default_internal_group" } }; static struct file_listener_settings *imap_hibernate_unix_listeners[] = { &imap_hibernate_unix_listeners_array[0] }; static buffer_t imap_hibernate_unix_listeners_buf = { { { imap_hibernate_unix_listeners, sizeof(imap_hibernate_unix_listeners) } } }; /* */ struct service_settings imap_hibernate_service_settings = { .name = "imap-hibernate", .protocol = "imap", .type = "", .executable = "imap-hibernate", .user = "$default_internal_user", .group = "", .privileged_group = "", .extra_groups = "", .chroot = "", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 0, .client_limit = 0, .service_count = 0, .idle_kill = 0, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &imap_hibernate_unix_listeners_buf, sizeof(imap_hibernate_unix_listeners[0]) } }, .fifo_listeners = ARRAY_INIT, .inet_listeners = ARRAY_INIT }; dovecot-2.3.21.1/src/imap-hibernate/imap-hibernate-client.c0000644000000000000000000002072214656633576020301 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "connection.h" #include "istream.h" #include "istream-unix.h" #include "ostream.h" #include "buffer.h" #include "base64.h" #include "strescape.h" #include "master-service.h" #include "imap-client.h" #include "imap-hibernate-client.h" struct imap_hibernate_client { struct connection conn; struct imap_client *imap_client; bool imap_client_created; bool finished; bool debug; }; struct imap_hibernate_input { /* input we've already read from the IMAP client. */ buffer_t *client_input; /* IMAP connection state */ buffer_t *state; }; static struct connection_list *hibernate_clients = NULL; static void imap_hibernate_client_destroy(struct connection *conn) { struct imap_hibernate_client *client = (struct imap_hibernate_client *)conn; if (!client->imap_client_created) master_service_client_connection_destroyed(master_service); else if (client->finished) imap_client_create_finish(client->imap_client); connection_deinit(conn); i_free(conn); } static int imap_hibernate_client_parse_input(const char *const *args, pool_t pool, struct imap_client_state *state_r, const char **error_r) { const char *key, *value; unsigned int peer_dev_major = 0, peer_dev_minor = 0; i_zero(state_r); if (args[0] == NULL) { *error_r = "Missing username in input"; return -1; } state_r->username = args[0]; args++; if (args[0] == NULL) { *error_r = "Missing mail_log_prefix in input"; return -1; } state_r->mail_log_prefix = args[0]; args++; for (; *args != NULL; args++) { value = strchr(*args, '='); if (value != NULL) key = t_strdup_until(*args, value++); else { key = *args; value = ""; } if (strcmp(key, "lip") == 0) { if (net_addr2ip(value, &state_r->local_ip) < 0) { *error_r = t_strdup_printf( "Invalid lip value: %s", value); return -1; } } else if (strcmp(key, "lport") == 0) { if (net_str2port(value, &state_r->local_port) < 0) { *error_r = t_strdup_printf( "Invalid lport value: %s", value); return -1; } } else if (strcmp(key, "rip") == 0) { if (net_addr2ip(value, &state_r->remote_ip) < 0) { *error_r = t_strdup_printf( "Invalid rip value: %s", value); return -1; } } else if (strcmp(key, "rport") == 0) { if (net_str2port(value, &state_r->remote_port) < 0) { *error_r = t_strdup_printf( "Invalid rport value: %s", value); return -1; } } else if (strcmp(key, "peer_dev_major") == 0) { if (str_to_uint(value, &peer_dev_major) < 0) { *error_r = t_strdup_printf( "Invalid peer_dev_major value: %s", value); return -1; } } else if (strcmp(key, "peer_dev_minor") == 0) { if (str_to_uint(value, &peer_dev_minor) < 0) { *error_r = t_strdup_printf( "Invalid peer_dev_minor value: %s", value); return -1; } } else if (strcmp(key, "peer_ino") == 0) { if (str_to_ino(value, &state_r->peer_ino) < 0) { *error_r = t_strdup_printf( "Invalid peer_ino value: %s", value); return -1; } } else if (strcmp(key, "uid") == 0) { if (str_to_uid(value, &state_r->uid) < 0) { *error_r = t_strdup_printf( "Invalid uid value: %s", value); return -1; } } else if (strcmp(key, "gid") == 0) { if (str_to_gid(value, &state_r->gid) < 0) { *error_r = t_strdup_printf( "Invalid gid value: %s", value); return -1; } } else if (strcmp(key, "stats") == 0) { state_r->stats = value; } else if (strcmp(key, "idle-cmd") == 0) { state_r->idle_cmd = TRUE; } else if (strcmp(key, "session") == 0) { state_r->session_id = value; } else if (strcmp(key, "mailbox") == 0) { state_r->mailbox_vname = value; } else if (strcmp(key, "session_created") == 0) { if (str_to_time(value, &state_r->session_created) < 0) { *error_r = t_strdup_printf( "Invalid session_created value: %s", value); return -1; } } else if (strcmp(key, "userdb_fields") == 0) { state_r->userdb_fields = value; } else if (strcmp(key, "notify_fd") == 0) { state_r->have_notify_fd = TRUE; } else if (strcmp(key, "idle_notify_interval") == 0) { if (str_to_uint(value, &state_r->imap_idle_notify_interval) < 0) { *error_r = t_strdup_printf( "Invalid idle_notify_interval value: %s", value); return -1; } } else if (strcmp(key, "tag") == 0) { state_r->tag = i_strdup(value); } else if (strcmp(key, "state") == 0) { buffer_t *state_buf; state_buf = buffer_create_dynamic(pool, 1024); if (base64_decode(value, strlen(value), NULL, state_buf) < 0) { *error_r = t_strdup_printf( "Invalid state base64 value: %s", value); return -1; } state_r->state = state_buf->data; state_r->state_size = state_buf->used; } } if (state_r->tag == NULL) { *error_r = "Missing tag"; return -1; } if (peer_dev_major != 0 || peer_dev_minor != 0) state_r->peer_dev = makedev(peer_dev_major, peer_dev_minor); return 0; } static int imap_hibernate_client_input_args(struct connection *conn, const char *const *args, int fd, pool_t pool) { struct imap_hibernate_client *client = (struct imap_hibernate_client *)conn; struct imap_client_state state; const char *error; if (imap_hibernate_client_parse_input(args, pool, &state, &error) < 0) { e_error(conn->event, "Failed to parse client input: %s", error); o_stream_nsend_str(conn->output, t_strdup_printf( "-Failed to parse client input: %s\n", error)); return -1; } client->imap_client = imap_client_create(fd, &state); /* the transferred imap client fd is now counted as the client. */ client->imap_client_created = TRUE; return state.have_notify_fd ? 0 : 1; } static int imap_hibernate_client_input_line(struct connection *conn, const char *line) { struct imap_hibernate_client *client = (struct imap_hibernate_client *)conn; int fd = -1, ret; if (!conn->version_received) { if (connection_handshake_args_default( conn, t_strsplit_tabescaped(line)) < 0) return -1; conn->version_received = TRUE; return 1; } if (client->finished) { e_error(conn->event, "Received unexpected line: %s", line); return -1; } if (client->imap_client == NULL) { char *const *args; pool_t pool; fd = i_stream_unix_get_read_fd(conn->input); if (fd == -1) { e_error(conn->event, "IMAP client fd not received"); return -1; } pool = pool_alloconly_create("client cmd", 1024); args = p_strsplit_tabescaped(pool, line); ret = imap_hibernate_client_input_args(conn, (const void *)args, fd, pool); if (ret >= 0 && client->debug) e_debug(conn->event, "Create client with input: %s", line); pool_unref(&pool); } else { fd = i_stream_unix_get_read_fd(conn->input); if (fd == -1) { e_error(conn->event, "IMAP notify fd not received (input: %s)", line); ret = -1; } else if (line[0] != '\0') { e_error(conn->event, "Expected empty notify fd line from client, but got: %s", line); o_stream_nsend_str(conn->output, "Expected empty notify fd line"); ret = -1; } else { imap_client_add_notify_fd(client->imap_client, fd); ret = 1; } } if (ret < 0) { if (client->imap_client != NULL) imap_client_destroy(&client->imap_client, NULL); i_close_fd(&fd); return -1; } else if (ret == 0) { /* still need to read another fd */ i_stream_unix_set_read_fd(conn->input); } else { /* finished - wait for disconnection from imap before finishing. this way the old imap process will have time to destroy itself before we have a chance to create another one. */ client->finished = TRUE; } o_stream_nsend_str(conn->output, "+\n"); return 1; } void imap_hibernate_client_create(int fd, bool debug) { struct imap_hibernate_client *client; client = i_new(struct imap_hibernate_client, 1); client->debug = debug; client->conn.unix_socket = TRUE; connection_init_server(hibernate_clients, &client->conn, "imap-hibernate", fd, fd); i_stream_unix_set_read_fd(client->conn.input); } static struct connection_settings client_set = { .service_name_in = "imap-hibernate", .service_name_out = "imap-hibernate", .major_version = 1, .minor_version = 0, .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, .client = FALSE }; static const struct connection_vfuncs client_vfuncs = { .destroy = imap_hibernate_client_destroy, .input_line = imap_hibernate_client_input_line }; void imap_hibernate_clients_init(void) { hibernate_clients = connection_list_init(&client_set, &client_vfuncs); } void imap_hibernate_clients_deinit(void) { connection_list_deinit(&hibernate_clients); } dovecot-2.3.21.1/src/imap-hibernate/main.c0000644000000000000000000000273514656633576015070 00000000000000/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "restrict-access.h" #include "master-service.h" #include "master-service-settings.h" #include "imap-client.h" #include "imap-hibernate-client.h" #include "imap-master-connection.h" static bool debug = FALSE; static void client_connected(struct master_service_connection *conn) { master_service_client_connection_accept(conn); imap_hibernate_client_create(conn->fd, debug); } int main(int argc, char *argv[]) { enum master_service_flags service_flags = MASTER_SERVICE_FLAG_UPDATE_PROCTITLE; const char *error; int c; master_service = master_service_init("imap-hibernate", service_flags, &argc, &argv, "D"); while ((c = master_getopt(master_service)) > 0) { switch (c) { case 'D': debug = TRUE; break; default: return FATAL_DEFAULT; } } if (master_service_settings_read_simple(master_service, NULL, &error) < 0) i_fatal("Error reading configuration: %s", error); master_service_init_log(master_service); restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL); restrict_access_allow_coredumps(TRUE); imap_clients_init(); imap_master_connections_init(); imap_hibernate_clients_init(); master_service_init_finish(master_service); master_service_run(master_service, client_connected); imap_master_connections_deinit(); imap_hibernate_clients_deinit(); imap_clients_deinit(); master_service_deinit(&master_service); return 0; } dovecot-2.3.21.1/src/imap-hibernate/Makefile.am0000644000000000000000000000110014656633576016015 00000000000000pkglibexecdir = $(libexecdir)/dovecot pkglibexec_PROGRAMS = imap-hibernate AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-imap \ $(BINARY_CFLAGS) imap_hibernate_LDADD = $(LIBDOVECOT) \ $(BINARY_LDFLAGS) imap_hibernate_DEPENDENCIES = $(LIBDOVECOT_DEPS) imap_hibernate_SOURCES = \ imap-client.c \ imap-hibernate-client.c \ imap-hibernate-settings.c \ imap-master-connection.c \ main.c noinst_HEADERS = \ imap-client.h \ imap-hibernate-client.h \ imap-master-connection.h dovecot-2.3.21.1/src/imap-hibernate/imap-hibernate-client.h0000644000000000000000000000032314656633576020301 00000000000000#ifndef IMAP_HIBERNATE_CLIENT_H #define IMAP_HIBERNATE_CLIENT_H void imap_hibernate_client_create(int fd, bool debug); void imap_hibernate_clients_init(void); void imap_hibernate_clients_deinit(void); #endif dovecot-2.3.21.1/src/Makefile.in0000644000000000000000000006166414656633607013161 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ pkglibexecdir = $(libexecdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ subdir = src ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = SOURCES = DIST_SOURCES = RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ ctags-recursive dvi-recursive html-recursive info-recursive \ install-data-recursive install-dvi-recursive \ install-exec-recursive install-html-recursive \ install-info-recursive install-pdf-recursive \ install-ps-recursive install-recursive installcheck-recursive \ installdirs-recursive pdf-recursive ps-recursive \ tags-recursive uninstall-recursive am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ distclean-recursive maintainer-clean-recursive am__recursive_targets = \ $(RECURSIVE_TARGETS) \ $(RECURSIVE_CLEAN_TARGETS) \ $(am__extra_recursive_targets) AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ distdir distdir-am am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags DIST_SUBDIRS = lib-test lib lib-settings lib-auth lib-dns lib-master \ lib-charset lib-ssl-iostream lib-dcrypt lib-dict lib-sasl \ lib-old-stats lib-http lib-fs lib-mail lib-program-client \ lib-smtp lib-imap lib-imap-storage lib-oauth2 lib-dict-extra \ lib-dovecot lib-ldap lib-lua lib-fts lib-imap-client \ lib-imap-urlauth lib-compression lib-index lib-storage lib-sql \ lib-otp lib-lda lib-dict-backend anvil auth dict dns indexer \ ipc master login-common imap-hibernate imap-login imap \ imap-urlauth pop3-login pop3 submission-login submission lda \ lmtp log config director replication util doveadm stats \ old-stats plugins am__DIST_COMMON = $(srcdir)/Makefile.in DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) am__relativize = \ dir0=`pwd`; \ sed_first='s,^\([^/]*\)/.*$$,\1,'; \ sed_rest='s,^[^/]*/*,,'; \ sed_last='s,^.*/\([^/]*\)$$,\1,'; \ sed_butlast='s,/*[^/]*$$,,'; \ while test -n "$$dir1"; do \ first=`echo "$$dir1" | sed -e "$$sed_first"`; \ if test "$$first" != "."; then \ if test "$$first" = ".."; then \ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ else \ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ if test "$$first2" = "$$first"; then \ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ else \ dir2="../$$dir2"; \ fi; \ dir0="$$dir0"/"$$first"; \ fi; \ fi; \ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ done; \ reldir="$$dir2" ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ @HAVE_LDAP_TRUE@LIB_LDAP = lib-ldap @HAVE_LUA_TRUE@LIB_LUA = lib-lua LIBDOVECOT_SUBDIRS = \ lib-test \ lib \ lib-settings \ lib-auth \ lib-dns \ lib-master \ lib-charset \ lib-ssl-iostream \ lib-dcrypt \ lib-dict \ lib-sasl \ lib-old-stats \ lib-http \ lib-fs \ lib-mail \ lib-program-client \ lib-smtp \ lib-imap \ lib-imap-storage \ lib-oauth2 SUBDIRS = \ $(LIBDOVECOT_SUBDIRS) \ lib-dict-extra \ lib-dovecot \ $(LIB_LDAP) \ $(LIB_LUA) \ lib-fts \ lib-imap-client \ lib-imap-urlauth \ lib-compression \ lib-index \ lib-storage \ lib-sql \ lib-otp \ lib-lda \ lib-dict-backend \ anvil \ auth \ dict \ dns \ indexer \ ipc \ master \ login-common \ imap-hibernate \ imap-login \ imap \ imap-urlauth \ pop3-login \ pop3 \ submission-login \ submission \ lda \ lmtp \ log \ config \ director \ replication \ util \ doveadm \ stats \ old-stats \ plugins all: all-recursive .SUFFIXES: $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs # This directory's subdirectories are mostly independent; you can cd # into them and run 'make' without going through this Makefile. # To change the values of 'make' variables: instead of editing Makefiles, # (1) if the variable is set in 'config.status', edit 'config.status' # (which will cause the Makefiles to be regenerated when you run 'make'); # (2) otherwise, pass the desired values on the 'make' command line. $(am__recursive_targets): @fail=; \ if $(am__make_keepgoing); then \ failcom='fail=yes'; \ else \ failcom='exit 1'; \ fi; \ dot_seen=no; \ target=`echo $@ | sed s/-recursive//`; \ case "$@" in \ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ *) list='$(SUBDIRS)' ;; \ esac; \ for subdir in $$list; do \ echo "Making $$target in $$subdir"; \ if test "$$subdir" = "."; then \ dot_seen=yes; \ local_target="$$target-am"; \ else \ local_target="$$target"; \ fi; \ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ || eval $$failcom; \ done; \ if test "$$dot_seen" = "no"; then \ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ fi; test -z "$$fail" ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-recursive TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ include_option=--etags-include; \ empty_fix=.; \ else \ include_option=--include; \ empty_fix=; \ fi; \ list='$(SUBDIRS)'; for subdir in $$list; do \ if test "$$subdir" = .; then :; else \ test ! -f $$subdir/TAGS || \ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ fi; \ done; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-recursive CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-recursive cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ if test "$$subdir" = .; then :; else \ $(am__make_dryrun) \ || test -d "$(distdir)/$$subdir" \ || $(MKDIR_P) "$(distdir)/$$subdir" \ || exit 1; \ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ $(am__relativize); \ new_distdir=$$reldir; \ dir1=$$subdir; dir2="$(top_distdir)"; \ $(am__relativize); \ new_top_distdir=$$reldir; \ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ ($(am__cd) $$subdir && \ $(MAKE) $(AM_MAKEFLAGS) \ top_distdir="$$new_top_distdir" \ distdir="$$new_distdir" \ am__remove_distdir=: \ am__skip_length_check=: \ am__skip_mode_fix=: \ distdir) \ || exit 1; \ fi; \ done check-am: all-am check: check-recursive all-am: Makefile installdirs: installdirs-recursive installdirs-am: install: install-recursive install-exec: install-exec-recursive install-data: install-data-recursive uninstall: uninstall-recursive install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-recursive install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-recursive clean-am: clean-generic clean-libtool mostlyclean-am distclean: distclean-recursive -rm -f Makefile distclean-am: clean-am distclean-generic distclean-tags dvi: dvi-recursive dvi-am: html: html-recursive html-am: info: info-recursive info-am: install-data-am: install-dvi: install-dvi-recursive install-dvi-am: install-exec-am: install-html: install-html-recursive install-html-am: install-info: install-info-recursive install-info-am: install-man: install-pdf: install-pdf-recursive install-pdf-am: install-ps: install-ps-recursive install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-recursive -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-recursive mostlyclean-am: mostlyclean-generic mostlyclean-libtool pdf: pdf-recursive pdf-am: ps: ps-recursive ps-am: uninstall-am: .MAKE: $(am__recursive_targets) install-am install-strip .PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \ check-am clean clean-generic clean-libtool cscopelist-am ctags \ ctags-am distclean distclean-generic distclean-libtool \ distclean-tags distdir dvi dvi-am html html-am info info-am \ install install-am install-data install-data-am install-dvi \ install-dvi-am install-exec install-exec-am install-html \ install-html-am install-info install-info-am install-man \ install-pdf install-pdf-am install-ps install-ps-am \ install-strip installcheck installcheck-am installdirs \ installdirs-am maintainer-clean maintainer-clean-generic \ mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \ ps ps-am tags tags-am uninstall uninstall-am .PRECIOUS: Makefile # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/anvil/0000755000000000000000000000000014656633640012265 500000000000000dovecot-2.3.21.1/src/anvil/penalty.h0000644000000000000000000000136214656633576014044 00000000000000#ifndef PENALTY_H #define PENALTY_H #define PENALTY_MAX_VALUE ((1 << 16)-1) struct penalty *penalty_init(void); void penalty_deinit(struct penalty **penalty); void penalty_set_expire_secs(struct penalty *penalty, unsigned int expire_secs); unsigned int penalty_get(struct penalty *penalty, const char *ident, time_t *last_penalty_r); /* if checksum is non-zero and it already exists for ident, the value is set to "value-1", otherwise it's set to "value". */ void penalty_inc(struct penalty *penalty, const char *ident, unsigned int checksum, unsigned int value); bool penalty_has_checksum(struct penalty *penalty, const char *ident, unsigned int checksum); void penalty_dump(struct penalty *penalty, struct ostream *output); #endif dovecot-2.3.21.1/src/anvil/Makefile.in0000644000000000000000000006661514656633607014273 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ pkglibexec_PROGRAMS = anvil$(EXEEXT) noinst_PROGRAMS = $(am__EXEEXT_1) subdir = src/anvil ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__EXEEXT_1 = test-penalty$(EXEEXT) am__installdirs = "$(DESTDIR)$(pkglibexecdir)" PROGRAMS = $(noinst_PROGRAMS) $(pkglibexec_PROGRAMS) am_anvil_OBJECTS = main.$(OBJEXT) anvil-connection.$(OBJEXT) \ anvil-settings.$(OBJEXT) connect-limit.$(OBJEXT) \ penalty.$(OBJEXT) anvil_OBJECTS = $(am_anvil_OBJECTS) am__DEPENDENCIES_1 = AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = am_test_penalty_OBJECTS = test-penalty.$(OBJEXT) test_penalty_OBJECTS = $(am_test_penalty_OBJECTS) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/anvil-connection.Po \ ./$(DEPDIR)/anvil-settings.Po ./$(DEPDIR)/connect-limit.Po \ ./$(DEPDIR)/main.Po ./$(DEPDIR)/penalty.Po \ ./$(DEPDIR)/test-penalty.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(anvil_SOURCES) $(test_penalty_SOURCES) DIST_SOURCES = $(anvil_SOURCES) $(test_penalty_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac HEADERS = $(noinst_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) pkglibexecdir = $(libexecdir)/dovecot ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-master \ $(BINARY_CFLAGS) anvil_LDADD = \ $(LIBDOVECOT) \ $(RAND_LIBS) \ $(BINARY_LDFLAGS) anvil_DEPENDENCIES = $(LIBDOVECOT_DEPS) anvil_SOURCES = \ main.c \ anvil-connection.c \ anvil-settings.c \ connect-limit.c \ penalty.c noinst_HEADERS = \ anvil-connection.h \ common.h \ connect-limit.h \ penalty.h test_programs = \ test-penalty test_libs = \ ../lib-test/libtest.la \ ../lib/liblib.la test_penalty_SOURCES = test-penalty.c test_penalty_LDADD = penalty.o $(test_libs) test_penalty_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs) all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/anvil/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/anvil/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): clean-noinstPROGRAMS: @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files clean-pkglibexecPROGRAMS: @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list anvil$(EXEEXT): $(anvil_OBJECTS) $(anvil_DEPENDENCIES) $(EXTRA_anvil_DEPENDENCIES) @rm -f anvil$(EXEEXT) $(AM_V_CCLD)$(LINK) $(anvil_OBJECTS) $(anvil_LDADD) $(LIBS) test-penalty$(EXEEXT): $(test_penalty_OBJECTS) $(test_penalty_DEPENDENCIES) $(EXTRA_test_penalty_DEPENDENCIES) @rm -f test-penalty$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_penalty_OBJECTS) $(test_penalty_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/anvil-connection.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/anvil-settings.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/connect-limit.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/penalty.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-penalty.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am $(MAKE) $(AM_MAKEFLAGS) check-local check: check-am all-am: Makefile $(PROGRAMS) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkglibexecdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \ clean-pkglibexecPROGRAMS mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/anvil-connection.Po -rm -f ./$(DEPDIR)/anvil-settings.Po -rm -f ./$(DEPDIR)/connect-limit.Po -rm -f ./$(DEPDIR)/main.Po -rm -f ./$(DEPDIR)/penalty.Po -rm -f ./$(DEPDIR)/test-penalty.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-pkglibexecPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/anvil-connection.Po -rm -f ./$(DEPDIR)/anvil-settings.Po -rm -f ./$(DEPDIR)/connect-limit.Po -rm -f ./$(DEPDIR)/main.Po -rm -f ./$(DEPDIR)/penalty.Po -rm -f ./$(DEPDIR)/test-penalty.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkglibexecPROGRAMS .MAKE: check-am install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ check-local clean clean-generic clean-libtool \ clean-noinstPROGRAMS clean-pkglibexecPROGRAMS cscopelist-am \ ctags ctags-am distclean distclean-compile distclean-generic \ distclean-libtool distclean-tags distdir dvi dvi-am html \ html-am info info-am install install-am install-data \ install-data-am install-dvi install-dvi-am install-exec \ install-exec-am install-html install-html-am install-info \ install-info-am install-man install-pdf install-pdf-am \ install-pkglibexecPROGRAMS install-ps install-ps-am \ install-strip installcheck installcheck-am installdirs \ maintainer-clean maintainer-clean-generic mostlyclean \ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ uninstall-pkglibexecPROGRAMS .PRECIOUS: Makefile check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/anvil/test-penalty.c0000644000000000000000000000320114656633576015006 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "penalty.h" #include "test-common.h" static void test_penalty_checksum(void) { struct penalty *penalty; struct ioloop *ioloop; time_t t; unsigned int i, j; test_begin("penalty"); ioloop = io_loop_create(); penalty = penalty_init(); test_assert(penalty_get(penalty, "foo", &t) == 0); for (i = 1; i <= 10; i++) { ioloop_time = 12345678 + i; penalty_inc(penalty, "foo", i, 5+i); for (j = I_MIN(1, i-1); j <= i; j++) { test_assert(penalty_get(penalty, "foo", &t) == 5+i); test_assert(t == (time_t)(12345678 + i)); test_assert(penalty_has_checksum(penalty, "foo", i)); } test_assert(penalty_get(penalty, "foo", &t) == 5+i); test_assert(t == (time_t)(12345678 + i)); test_assert(!penalty_has_checksum(penalty, "foo", j)); } test_assert(penalty_get(penalty, "foo2", &t) == 0); /* overflows checksum array */ ioloop_time = 12345678 + i; penalty_inc(penalty, "foo", i, 5 + i); penalty_inc(penalty, "foo", i, 5 + i); penalty_inc(penalty, "foo", 0, 5 + i); test_assert(penalty_get(penalty, "foo", &t) == 5+i); test_assert(t == (time_t)(12345678 + i)); test_assert(!penalty_has_checksum(penalty, "foo", 1)); for (j = 2; j <= i; j++) { test_assert(penalty_get(penalty, "foo", &t) == 5+i); test_assert(t == (time_t)(12345678 + i)); test_assert(penalty_has_checksum(penalty, "foo", i)); } penalty_deinit(&penalty); io_loop_destroy(&ioloop); test_end(); } int main(void) { static void (*const test_functions[])(void) = { test_penalty_checksum, NULL }; return test_run(test_functions); } dovecot-2.3.21.1/src/anvil/common.h0000644000000000000000000000024614656633576013660 00000000000000#ifndef COMMON_H #define COMMON_H #include "lib.h" extern struct connect_limit *connect_limit; extern struct penalty *penalty; extern bool anvil_restarted; #endif dovecot-2.3.21.1/src/anvil/connect-limit.h0000644000000000000000000000112514656633576015132 00000000000000#ifndef CONNECT_LIMIT_H #define CONNECT_LIMIT_H struct connect_limit *connect_limit_init(void); void connect_limit_deinit(struct connect_limit **limit); unsigned int connect_limit_lookup(struct connect_limit *limit, const char *ident); void connect_limit_connect(struct connect_limit *limit, pid_t pid, const char *ident); void connect_limit_disconnect(struct connect_limit *limit, pid_t pid, const char *ident); void connect_limit_disconnect_pid(struct connect_limit *limit, pid_t pid); void connect_limit_dump(struct connect_limit *limit, struct ostream *output); #endif dovecot-2.3.21.1/src/anvil/connect-limit.c0000644000000000000000000001166214656633576015134 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "common.h" #include "hash.h" #include "str.h" #include "strescape.h" #include "ostream.h" #include "connect-limit.h" struct ident_pid { /* ident string points to ident_hash keys */ const char *ident; pid_t pid; unsigned int refcount; }; struct connect_limit { /* ident => unsigned int refcount */ HASH_TABLE(char *, void *) ident_hash; /* struct ident_pid => struct ident_pid */ HASH_TABLE(struct ident_pid *, struct ident_pid *) ident_pid_hash; }; static void connect_limit_ident_hash_unref(struct connect_limit *limit, const char *ident); static unsigned int ident_pid_hash(const struct ident_pid *i) { return str_hash(i->ident) ^ i->pid; } static int ident_pid_cmp(const struct ident_pid *i1, const struct ident_pid *i2) { if (i1->pid < i2->pid) return -1; else if (i1->pid > i2->pid) return 1; else return strcmp(i1->ident, i2->ident); } struct connect_limit *connect_limit_init(void) { struct connect_limit *limit; limit = i_new(struct connect_limit, 1); hash_table_create(&limit->ident_hash, default_pool, 0, str_hash, strcmp); hash_table_create(&limit->ident_pid_hash, default_pool, 0, ident_pid_hash, ident_pid_cmp); return limit; } void connect_limit_deinit(struct connect_limit **_limit) { struct connect_limit *limit = *_limit; struct hash_iterate_context *iter; struct ident_pid *i, *value; iter = hash_table_iterate_init(limit->ident_pid_hash); while (hash_table_iterate(iter, limit->ident_pid_hash, &i, &value)) { hash_table_remove(limit->ident_pid_hash, i); for (; i->refcount > 0; i->refcount--) connect_limit_ident_hash_unref(limit, i->ident); i_free(i); } hash_table_iterate_deinit(&iter); *_limit = NULL; hash_table_destroy(&limit->ident_hash); hash_table_destroy(&limit->ident_pid_hash); i_free(limit); } unsigned int connect_limit_lookup(struct connect_limit *limit, const char *ident) { void *value; value = hash_table_lookup(limit->ident_hash, ident); return POINTER_CAST_TO(value, unsigned int); } void connect_limit_connect(struct connect_limit *limit, pid_t pid, const char *ident) { struct ident_pid *i, lookup_i; char *key; void *value; if (!hash_table_lookup_full(limit->ident_hash, ident, &key, &value)) { key = i_strdup(ident); value = POINTER_CAST(1); hash_table_insert(limit->ident_hash, key, value); } else { value = POINTER_CAST(POINTER_CAST_TO(value, unsigned int) + 1); hash_table_update(limit->ident_hash, key, value); } lookup_i.ident = ident; lookup_i.pid = pid; i = hash_table_lookup(limit->ident_pid_hash, &lookup_i); if (i == NULL) { i = i_new(struct ident_pid, 1); i->ident = key; i->pid = pid; i->refcount = 1; hash_table_insert(limit->ident_pid_hash, i, i); } else { i->refcount++; } } static void connect_limit_ident_hash_unref(struct connect_limit *limit, const char *ident) { char *key; void *value; unsigned int new_refcount; if (!hash_table_lookup_full(limit->ident_hash, ident, &key, &value)) i_panic("connect limit hash tables are inconsistent"); new_refcount = POINTER_CAST_TO(value, unsigned int) - 1; if (new_refcount > 0) { value = POINTER_CAST(new_refcount); hash_table_update(limit->ident_hash, key, value); } else { hash_table_remove(limit->ident_hash, key); i_free(key); } } void connect_limit_disconnect(struct connect_limit *limit, pid_t pid, const char *ident) { struct ident_pid *i, lookup_i; lookup_i.ident = ident; lookup_i.pid = pid; i = hash_table_lookup(limit->ident_pid_hash, &lookup_i); if (i == NULL) { i_error("connect limit: disconnection for unknown " "pid %s + ident %s", dec2str(pid), ident); return; } if (--i->refcount == 0) { hash_table_remove(limit->ident_pid_hash, i); i_free(i); } connect_limit_ident_hash_unref(limit, ident); } void connect_limit_disconnect_pid(struct connect_limit *limit, pid_t pid) { struct hash_iterate_context *iter; struct ident_pid *i, *value; /* this should happen rarely (or never), so this slow implementation should be fine. */ iter = hash_table_iterate_init(limit->ident_pid_hash); while (hash_table_iterate(iter, limit->ident_pid_hash, &i, &value)) { if (i->pid == pid) { hash_table_remove(limit->ident_pid_hash, i); for (; i->refcount > 0; i->refcount--) connect_limit_ident_hash_unref(limit, i->ident); i_free(i); } } hash_table_iterate_deinit(&iter); } void connect_limit_dump(struct connect_limit *limit, struct ostream *output) { struct hash_iterate_context *iter; struct ident_pid *i, *value; string_t *str = t_str_new(256); iter = hash_table_iterate_init(limit->ident_pid_hash); while (hash_table_iterate(iter, limit->ident_pid_hash, &i, &value)) { str_truncate(str, 0); str_append_tabescaped(str, i->ident); str_printfa(str, "\t%ld\t%u\n", (long)i->pid, i->refcount); if (o_stream_send(output, str_data(str), str_len(str)) < 0) break; } hash_table_iterate_deinit(&iter); o_stream_nsend(output, "\n", 1); } dovecot-2.3.21.1/src/anvil/main.c0000644000000000000000000000476614656633576013322 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "common.h" #include "array.h" #include "env-util.h" #include "fdpass.h" #include "ioloop.h" #include "restrict-access.h" #include "master-service.h" #include "master-service-settings.h" #include "master-interface.h" #include "connect-limit.h" #include "penalty.h" #include "anvil-connection.h" #include struct connect_limit *connect_limit; struct penalty *penalty; bool anvil_restarted; static struct io *log_fdpass_io; static void client_connected(struct master_service_connection *conn) { bool master = conn->listen_fd == MASTER_LISTEN_FD_FIRST; master_service_client_connection_accept(conn); (void)anvil_connection_create(conn->fd, master, conn->fifo); } static void ATTR_NULL(1) log_fdpass_input(void *context ATTR_UNUSED) { int fd; char c; ssize_t ret; /* master wants us to replace the log fd */ ret = fd_read(MASTER_ANVIL_LOG_FDPASS_FD, &c, 1, &fd); if (ret < 0) i_error("fd_read(log fd) failed: %m"); else if (ret == 0) { /* master died. lib-master should notice it soon. */ io_remove(&log_fdpass_io); } else { if (dup2(fd, STDERR_FILENO) < 0) i_fatal("dup2(fd_read log fd, stderr) failed: %m"); if (close(fd) < 0) i_error("close(fd_read log fd) failed: %m"); } } int main(int argc, char *argv[]) { const enum master_service_flags service_flags = MASTER_SERVICE_FLAG_DONT_SEND_STATS | MASTER_SERVICE_FLAG_UPDATE_PROCTITLE; const char *error; master_service = master_service_init("anvil", service_flags, &argc, &argv, ""); if (master_getopt(master_service) > 0) return FATAL_DEFAULT; if (master_service_settings_read_simple(master_service, NULL, &error) < 0) i_fatal("Error reading configuration: %s", error); master_service_init_log(master_service); restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL); restrict_access_allow_coredumps(TRUE); anvil_restarted = getenv("ANVIL_RESTARTED") != NULL; /* delay dying until all of our clients are gone */ master_service_set_die_with_master(master_service, FALSE); connect_limit = connect_limit_init(); penalty = penalty_init(); log_fdpass_io = io_add(MASTER_ANVIL_LOG_FDPASS_FD, IO_READ, log_fdpass_input, NULL); master_service_init_finish(master_service); master_service_run(master_service, client_connected); io_remove(&log_fdpass_io); penalty_deinit(&penalty); connect_limit_deinit(&connect_limit); anvil_connections_destroy_all(); master_service_deinit(&master_service); return 0; } dovecot-2.3.21.1/src/anvil/Makefile.am0000644000000000000000000000161014656633576014247 00000000000000pkglibexecdir = $(libexecdir)/dovecot pkglibexec_PROGRAMS = anvil AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-master \ $(BINARY_CFLAGS) anvil_LDADD = \ $(LIBDOVECOT) \ $(RAND_LIBS) \ $(BINARY_LDFLAGS) anvil_DEPENDENCIES = $(LIBDOVECOT_DEPS) anvil_SOURCES = \ main.c \ anvil-connection.c \ anvil-settings.c \ connect-limit.c \ penalty.c noinst_HEADERS = \ anvil-connection.h \ common.h \ connect-limit.h \ penalty.h test_programs = \ test-penalty noinst_PROGRAMS = $(test_programs) test_libs = \ ../lib-test/libtest.la \ ../lib/liblib.la test_penalty_SOURCES = test-penalty.c test_penalty_LDADD = penalty.o $(test_libs) test_penalty_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs) check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done dovecot-2.3.21.1/src/anvil/anvil-connection.c0000644000000000000000000001350614656633576015634 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "common.h" #include "llist.h" #include "istream.h" #include "ostream.h" #include "strescape.h" #include "master-service.h" #include "master-interface.h" #include "connect-limit.h" #include "penalty.h" #include "anvil-connection.h" #include #define MAX_INBUF_SIZE 1024 #define ANVIL_CLIENT_PROTOCOL_MAJOR_VERSION 1 #define ANVIL_CLIENT_PROTOCOL_MINOR_VERSION 0 struct anvil_connection { struct anvil_connection *prev, *next; int fd; struct istream *input; struct ostream *output; struct io *io; bool version_received:1; bool handshaked:1; bool master:1; bool fifo:1; }; static struct anvil_connection *anvil_connections = NULL; static const char *const * anvil_connection_next_line(struct anvil_connection *conn) { const char *line; line = i_stream_next_line(conn->input); return line == NULL ? NULL : t_strsplit_tabescaped(line); } static int anvil_connection_request(struct anvil_connection *conn, const char *const *args, const char **error_r) { const char *cmd = args[0]; unsigned int value, checksum; time_t stamp; pid_t pid; args++; if (strcmp(cmd, "CONNECT") == 0) { if (args[0] == NULL || args[1] == NULL) { *error_r = "CONNECT: Not enough parameters"; return -1; } if (str_to_pid(args[0], &pid) < 0) { *error_r = "CONNECT: Invalid pid"; return -1; } connect_limit_connect(connect_limit, pid, args[1]); } else if (strcmp(cmd, "DISCONNECT") == 0) { if (args[0] == NULL || args[1] == NULL) { *error_r = "DISCONNECT: Not enough parameters"; return -1; } if (str_to_pid(args[0], &pid) < 0) { *error_r = "DISCONNECT: Invalid pid"; return -1; } connect_limit_disconnect(connect_limit, pid, args[1]); } else if (strcmp(cmd, "CONNECT-DUMP") == 0) { connect_limit_dump(connect_limit, conn->output); } else if (strcmp(cmd, "KILL") == 0) { if (args[0] == NULL) { *error_r = "KILL: Not enough parameters"; return -1; } if (!conn->master) { *error_r = "KILL sent by a non-master connection"; return -1; } if (str_to_pid(args[0], &pid) < 0) { *error_r = "KILL: Invalid pid"; return -1; } connect_limit_disconnect_pid(connect_limit, pid); } else if (strcmp(cmd, "LOOKUP") == 0) { if (args[0] == NULL) { *error_r = "LOOKUP: Not enough parameters"; return -1; } if (conn->output == NULL) { *error_r = "LOOKUP on a FIFO, can't send reply"; return -1; } value = connect_limit_lookup(connect_limit, args[0]); o_stream_nsend_str(conn->output, t_strdup_printf("%u\n", value)); } else if (strcmp(cmd, "PENALTY-GET") == 0) { if (args[0] == NULL) { *error_r = "PENALTY-GET: Not enough parameters"; return -1; } value = penalty_get(penalty, args[0], &stamp); o_stream_nsend_str(conn->output, t_strdup_printf("%u %s\n", value, dec2str(stamp))); } else if (strcmp(cmd, "PENALTY-INC") == 0) { if (args[0] == NULL || args[1] == NULL || args[2] == NULL) { *error_r = "PENALTY-INC: Not enough parameters"; return -1; } if (str_to_uint(args[1], &checksum) < 0 || str_to_uint(args[2], &value) < 0 || value > PENALTY_MAX_VALUE || (value == 0 && checksum != 0)) { *error_r = "PENALTY-INC: Invalid parameters"; return -1; } penalty_inc(penalty, args[0], checksum, value); } else if (strcmp(cmd, "PENALTY-SET-EXPIRE-SECS") == 0) { if (args[0] == NULL || str_to_uint(args[0], &value) < 0) { *error_r = "PENALTY-SET-EXPIRE-SECS: " "Invalid parameters"; return -1; } penalty_set_expire_secs(penalty, value); } else if (strcmp(cmd, "PENALTY-DUMP") == 0) { penalty_dump(penalty, conn->output); } else { *error_r = t_strconcat("Unknown command: ", cmd, NULL); return -1; } return 0; } static void anvil_connection_input(struct anvil_connection *conn) { const char *line, *const *args, *error; switch (i_stream_read(conn->input)) { case -2: i_error("BUG: Anvil client connection sent too much data"); anvil_connection_destroy(conn); return; case -1: anvil_connection_destroy(conn); return; } if (!conn->version_received) { if ((line = i_stream_next_line(conn->input)) == NULL) return; if (!version_string_verify(line, "anvil", ANVIL_CLIENT_PROTOCOL_MAJOR_VERSION)) { if (anvil_restarted && (conn->master || conn->fifo)) { /* old pending data. ignore input until we get the handshake. */ anvil_connection_input(conn); return; } i_error("Anvil client not compatible with this server " "(mixed old and new binaries?) %s", line); anvil_connection_destroy(conn); return; } conn->version_received = TRUE; } while ((args = anvil_connection_next_line(conn)) != NULL) { if (args[0] != NULL) { if (anvil_connection_request(conn, args, &error) < 0) { i_error("Anvil client input error: %s", error); anvil_connection_destroy(conn); break; } } } } struct anvil_connection * anvil_connection_create(int fd, bool master, bool fifo) { struct anvil_connection *conn; conn = i_new(struct anvil_connection, 1); conn->fd = fd; conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE); if (!fifo) { conn->output = o_stream_create_fd(fd, SIZE_MAX); o_stream_set_no_error_handling(conn->output, TRUE); } conn->io = io_add(fd, IO_READ, anvil_connection_input, conn); conn->master = master; conn->fifo = fifo; DLLIST_PREPEND(&anvil_connections, conn); return conn; } void anvil_connection_destroy(struct anvil_connection *conn) { bool fifo = conn->fifo; DLLIST_REMOVE(&anvil_connections, conn); io_remove(&conn->io); i_stream_destroy(&conn->input); o_stream_destroy(&conn->output); if (close(conn->fd) < 0) i_error("close(anvil conn) failed: %m"); i_free(conn); if (!fifo) master_service_client_connection_destroyed(master_service); } void anvil_connections_destroy_all(void) { while (anvil_connections != NULL) anvil_connection_destroy(anvil_connections); } dovecot-2.3.21.1/src/anvil/anvil-settings.c0000644000000000000000000000231714656633576015333 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "settings-parser.h" #include "service-settings.h" #include /* */ static struct file_listener_settings anvil_unix_listeners_array[] = { { "anvil", 0600, "", "" }, { "anvil-auth-penalty", 0600, "", "" } }; static struct file_listener_settings *anvil_unix_listeners[] = { &anvil_unix_listeners_array[0], &anvil_unix_listeners_array[1] }; static buffer_t anvil_unix_listeners_buf = { { { anvil_unix_listeners, sizeof(anvil_unix_listeners) } } }; /* */ struct service_settings anvil_service_settings = { .name = "anvil", .protocol = "", .type = "anvil", .executable = "anvil", .user = "$default_internal_user", .group = "", .privileged_group = "", .extra_groups = "", .chroot = "empty", .drop_priv_before_exec = FALSE, .process_min_avail = 1, .process_limit = 1, .client_limit = 0, .service_count = 0, .idle_kill = UINT_MAX, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &anvil_unix_listeners_buf, sizeof(anvil_unix_listeners[0]) } }, .fifo_listeners = ARRAY_INIT, .inet_listeners = ARRAY_INIT, .process_limit_1 = TRUE }; dovecot-2.3.21.1/src/anvil/anvil-connection.h0000644000000000000000000000037314656633576015637 00000000000000#ifndef ANVIL_CONNECTION_H #define ANVIL_CONNECTION_H struct anvil_connection * anvil_connection_create(int fd, bool master, bool fifo); void anvil_connection_destroy(struct anvil_connection *conn); void anvil_connections_destroy_all(void); #endif dovecot-2.3.21.1/src/anvil/penalty.c0000644000000000000000000001476214656633576014047 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ /* The idea behind checksums is that the same username+password doesn't increase the penalty, because it's most likely a user with a misconfigured account. */ #include "lib.h" #include "ioloop.h" #include "hash.h" #include "str.h" #include "strescape.h" #include "llist.h" #include "ostream.h" #include "penalty.h" #include #define PENALTY_DEFAULT_EXPIRE_SECS (60*60) #define PENALTY_CHECKSUM_SAVE_COUNT #define CHECKSUM_VALUE_COUNT 2 #define CHECKSUM_VALUE_PTR_COUNT 10 #define LAST_UPDATE_BITS 15 struct penalty_rec { /* ordered by last_update */ struct penalty_rec *prev, *next; char *ident; unsigned int last_penalty; unsigned int penalty:16; unsigned int last_update:LAST_UPDATE_BITS; /* last_penalty + n */ bool checksum_is_pointer:1; /* we use value up to two different checksums. after that switch to pointer. */ union { unsigned int value[CHECKSUM_VALUE_COUNT]; unsigned int *value_ptr; } checksum; }; struct penalty { /* ident => penalty_rec */ HASH_TABLE(char *, struct penalty_rec *) hash; struct penalty_rec *oldest, *newest; unsigned int expire_secs; struct timeout *to; }; struct penalty *penalty_init(void) { struct penalty *penalty; penalty = i_new(struct penalty, 1); hash_table_create(&penalty->hash, default_pool, 0, str_hash, strcmp); penalty->expire_secs = PENALTY_DEFAULT_EXPIRE_SECS; return penalty; } static void penalty_rec_free(struct penalty *penalty, struct penalty_rec *rec) { DLLIST2_REMOVE(&penalty->oldest, &penalty->newest, rec); if (rec->checksum_is_pointer) i_free(rec->checksum.value_ptr); i_free(rec->ident); i_free(rec); } void penalty_deinit(struct penalty **_penalty) { struct penalty *penalty = *_penalty; *_penalty = NULL; while (penalty->oldest != NULL) penalty_rec_free(penalty, penalty->oldest); hash_table_destroy(&penalty->hash); timeout_remove(&penalty->to); i_free(penalty); } void penalty_set_expire_secs(struct penalty *penalty, unsigned int expire_secs) { penalty->expire_secs = expire_secs; } static bool penalty_bump_checksum(struct penalty_rec *rec, unsigned int checksum) { unsigned int *checksums; unsigned int i, count; if (!rec->checksum_is_pointer) { checksums = rec->checksum.value; count = CHECKSUM_VALUE_COUNT; } else { checksums = rec->checksum.value_ptr; count = CHECKSUM_VALUE_PTR_COUNT; } for (i = 0; i < count; i++) { if (checksums[i] == checksum) { if (i > 0) { memmove(checksums + 1, checksums, sizeof(checksums[0]) * i); checksums[0] = checksum; } return TRUE; } } return FALSE; } static void penalty_add_checksum(struct penalty_rec *rec, unsigned int checksum) { unsigned int *checksums; i_assert(checksum != 0); if (!rec->checksum_is_pointer) { if (rec->checksum.value[CHECKSUM_VALUE_COUNT-1] == 0) { memcpy(rec->checksum.value + 1, rec->checksum.value, sizeof(rec->checksum.value[0]) * (CHECKSUM_VALUE_COUNT-1)); rec->checksum.value[0] = checksum; return; } /* switch to using a pointer */ checksums = i_new(unsigned int, CHECKSUM_VALUE_PTR_COUNT); memcpy(checksums, rec->checksum.value, sizeof(checksums[0]) * CHECKSUM_VALUE_COUNT); rec->checksum.value_ptr = checksums; rec->checksum_is_pointer = TRUE; } memmove(rec->checksum.value_ptr + 1, rec->checksum.value_ptr, sizeof(rec->checksum.value_ptr[0]) * (CHECKSUM_VALUE_PTR_COUNT-1)); rec->checksum.value_ptr[0] = checksum; } unsigned int penalty_get(struct penalty *penalty, const char *ident, time_t *last_penalty_r) { struct penalty_rec *rec; rec = hash_table_lookup(penalty->hash, ident); if (rec == NULL) { *last_penalty_r = 0; return 0; } *last_penalty_r = rec->last_penalty; return rec->penalty; } static void penalty_timeout(struct penalty *penalty) { struct penalty_rec *rec; time_t rec_last_update, expire_time; unsigned int diff; timeout_remove(&penalty->to); expire_time = ioloop_time - penalty->expire_secs; while (penalty->oldest != NULL) { rec = penalty->oldest; rec_last_update = rec->last_penalty + rec->last_update; if (rec_last_update > expire_time) { diff = rec_last_update - expire_time; penalty->to = timeout_add(diff * 1000, penalty_timeout, penalty); break; } hash_table_remove(penalty->hash, rec->ident); penalty_rec_free(penalty, rec); } } void penalty_inc(struct penalty *penalty, const char *ident, unsigned int checksum, unsigned int value) { struct penalty_rec *rec; time_t diff; i_assert(value > 0 || checksum == 0); i_assert(value <= INT_MAX); rec = hash_table_lookup(penalty->hash, ident); if (rec == NULL) { rec = i_new(struct penalty_rec, 1); rec->ident = i_strdup(ident); hash_table_insert(penalty->hash, rec->ident, rec); } else { DLLIST2_REMOVE(&penalty->oldest, &penalty->newest, rec); } if (checksum == 0) { rec->penalty = value; rec->last_penalty = ioloop_time; } else { if (penalty_bump_checksum(rec, checksum)) rec->penalty = value - 1; else { penalty_add_checksum(rec, checksum); rec->penalty = value; rec->last_penalty = ioloop_time; } } diff = ioloop_time - rec->last_penalty; if (diff >= (1 << LAST_UPDATE_BITS)) { rec->last_update = (1 << LAST_UPDATE_BITS) - 1; rec->last_penalty = ioloop_time - rec->last_update; } else { rec->last_update = diff; } DLLIST2_APPEND(&penalty->oldest, &penalty->newest, rec); if (penalty->to == NULL) { penalty->to = timeout_add(penalty->expire_secs * 1000, penalty_timeout, penalty); } } bool penalty_has_checksum(struct penalty *penalty, const char *ident, unsigned int checksum) { struct penalty_rec *rec; const unsigned int *checksums; unsigned int i, count; rec = hash_table_lookup(penalty->hash, ident); if (rec == NULL) return FALSE; if (!rec->checksum_is_pointer) { checksums = rec->checksum.value; count = CHECKSUM_VALUE_COUNT; } else { checksums = rec->checksum.value_ptr; count = CHECKSUM_VALUE_PTR_COUNT; } for (i = 0; i < count; i++) { if (checksums[i] == checksum) return TRUE; } return FALSE; } void penalty_dump(struct penalty *penalty, struct ostream *output) { const struct penalty_rec *rec; string_t *str = t_str_new(256); for (rec = penalty->oldest; rec != NULL; rec = rec->next) { str_truncate(str, 0); str_append_tabescaped(str, rec->ident); str_printfa(str, "\t%u\t%u\t%u\n", rec->penalty, rec->last_penalty, rec->last_penalty + rec->last_update); if (o_stream_send(output, str_data(str), str_len(str)) < 0) break; } o_stream_nsend(output, "\n", 1); } dovecot-2.3.21.1/src/auth/0000755000000000000000000000000014656633640012115 500000000000000dovecot-2.3.21.1/src/auth/passdb-bsdauth.c0000644000000000000000000000441714656633576015123 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "passdb.h" #ifdef PASSDB_BSDAUTH #include "safe-memset.h" #include "auth-cache.h" #include "ipwd.h" #include "mycrypt.h" #include #include static void bsdauth_verify_plain(struct auth_request *request, const char *password, verify_plain_callback_t *callback) { struct passwd pw; const char *type; int result; e_debug(authdb_event(request), "lookup"); switch (i_getpwnam(request->fields.user, &pw)) { case -1: e_error(authdb_event(request), "getpwnam() failed: %m"); callback(PASSDB_RESULT_INTERNAL_FAILURE, request); return; case 0: auth_request_log_unknown_user(request, AUTH_SUBSYS_DB); callback(PASSDB_RESULT_USER_UNKNOWN, request); return; } /* check if the password is valid */ type = t_strdup_printf("auth-%s", request->fields.service); result = auth_userokay(request->fields.user, NULL, t_strdup_noconst(type), t_strdup_noconst(password)); /* clear the passwords from memory */ safe_memset(pw.pw_passwd, 0, strlen(pw.pw_passwd)); if (result == 0) { auth_request_log_password_mismatch(request, AUTH_SUBSYS_DB); callback(PASSDB_RESULT_PASSWORD_MISMATCH, request); return; } /* make sure we're using the username exactly as it's in the database */ auth_request_set_field(request, "user", pw.pw_name, NULL); callback(PASSDB_RESULT_OK, request); } static struct passdb_module * bsdauth_preinit(pool_t pool, const char *args) { struct passdb_module *module; module = p_new(pool, struct passdb_module, 1); module->default_pass_scheme = "PLAIN"; /* same reason as PAM */ module->blocking = TRUE; if (strcmp(args, "blocking=no") == 0) module->blocking = FALSE; else if (str_begins(args, "cache_key=")) module->default_cache_key = auth_cache_parse_key(pool, args + 10); else if (*args != '\0') i_fatal("passdb bsdauth: Unknown setting: %s", args); return module; } static void bsdauth_deinit(struct passdb_module *module ATTR_UNUSED) { endpwent(); } struct passdb_module_interface passdb_bsdauth = { "bsdauth", bsdauth_preinit, NULL, bsdauth_deinit, bsdauth_verify_plain, NULL, NULL }; #else struct passdb_module_interface passdb_bsdauth = { .name = "bsdauth" }; #endif dovecot-2.3.21.1/src/auth/password-scheme-md5crypt.c0000644000000000000000000000741414656633576017070 00000000000000/* * ---------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * wrote this file. As long as you retain this notice you * can do whatever you want with this stuff. If we meet some day, and you think * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp * ---------------------------------------------------------------------------- */ /* * Ported from FreeBSD to Linux, only minimal changes. --marekm */ /* * Adapted from shadow-19990607 by Tudor Bosman, tudorb@jm.nu */ #include "lib.h" #include "safe-memset.h" #include "str.h" #include "md5.h" #include "password-scheme.h" static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; static char magic[] = "$1$"; /* * This string is magic for * this algorithm. Having * it this way, we can get * get better later on */ static void to64(string_t *str, unsigned long v, int n) { while (--n >= 0) { str_append_c(str, itoa64[v&0x3f]); v >>= 6; } } /* * UNIX password * * Use MD5 for what it is best at... */ const char *password_generate_md5_crypt(const char *pw, const char *salt) { const char *sp,*ep; unsigned char final[MD5_RESULTLEN]; int sl,pl,i,j; struct md5_context ctx,ctx1; unsigned long l; string_t *passwd; size_t pw_len = strlen(pw); /* Refine the Salt first */ sp = salt; /* If it starts with the magic string, then skip that */ if (strncmp(sp, magic, sizeof(magic)-1) == 0) sp += sizeof(magic)-1; /* It stops at the first '$', max 8 chars */ for(ep=sp;*ep != '\0' && *ep != '$' && ep < (sp+8);ep++) continue; /* get the length of the true salt */ sl = ep - sp; md5_init(&ctx); /* The password first, since that is what is most unknown */ md5_update(&ctx,pw,pw_len); /* Then our magic string */ md5_update(&ctx,magic,sizeof(magic)-1); /* Then the raw salt */ md5_update(&ctx,sp,sl); /* Then just as many characters of the MD5(pw,salt,pw) */ md5_init(&ctx1); md5_update(&ctx1,pw,pw_len); md5_update(&ctx1,sp,sl); md5_update(&ctx1,pw,pw_len); md5_final(&ctx1,final); for(pl = pw_len; pl > 0; pl -= MD5_RESULTLEN) md5_update(&ctx,final,pl>MD5_RESULTLEN ? MD5_RESULTLEN : pl); /* Don't leave anything around in vm they could use. */ safe_memset(final, 0, sizeof(final)); /* Then something really weird... */ for (j=0,i = pw_len; i != 0; i >>= 1) if ((i&1) != 0) md5_update(&ctx, final+j, 1); else md5_update(&ctx, pw+j, 1); /* Now make the output string */ passwd = t_str_new(sl + 64); str_append(passwd, magic); str_append_data(passwd, sp, sl); str_append_c(passwd, '$'); md5_final(&ctx,final); /* * and now, just to make sure things don't run too fast * On a 60 Mhz Pentium this takes 34 msec, so you would * need 30 seconds to build a 1000 entry dictionary... */ for(i=0;i<1000;i++) { md5_init(&ctx1); if((i & 1) != 0) md5_update(&ctx1,pw,pw_len); else md5_update(&ctx1,final,MD5_RESULTLEN); if((i % 3) != 0) md5_update(&ctx1,sp,sl); if((i % 7) != 0) md5_update(&ctx1,pw,pw_len); if((i & 1) != 0) md5_update(&ctx1,final,MD5_RESULTLEN); else md5_update(&ctx1,pw,pw_len); md5_final(&ctx1,final); } l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(passwd,l,4); l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(passwd,l,4); l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(passwd,l,4); l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(passwd,l,4); l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(passwd,l,4); l = final[11] ; to64(passwd,l,2); /* Don't leave anything around in vm they could use. */ safe_memset(final, 0, sizeof(final)); return str_c(passwd); } dovecot-2.3.21.1/src/auth/test-username-filter.c0000644000000000000000000000351114656633576016270 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "auth-settings.h" #include "test-auth.h" #include "auth-request.h" void test_username_filter(void) { const struct { const char *filter; const char *input; bool accepted; } cases[] = { { "", "", TRUE }, { "*", "", TRUE }, { "", "testuser1", TRUE }, { "*", "testuser1", TRUE }, { "!*", "testuser1", FALSE }, { "!*", "", FALSE }, { "*@*", "", FALSE }, { "*@*", "@", TRUE }, { "!*@*", "@", FALSE }, { "!*@*", "", TRUE }, { "*@*", "testuser1", FALSE }, { "!*@*", "testuser1", TRUE }, { "*@*", "testuser1@testdomain", TRUE }, { "!*@*", "testuser1@testdomain", FALSE }, { "*@testdomain *@testdomain2", "testuser1@testdomain", TRUE }, { "*@testdomain *@testdomain2", "testuser1@testdomain2", TRUE }, { "*@testdomain *@testdomain2", "testuser1@testdomain3", FALSE }, { "!testuser@testdomain *@testdomain", "testuser@testdomain", FALSE }, { "!testuser@testdomain *@testdomain", "testuser2@testdomain", TRUE }, { "*@testdomain !testuser@testdomain !testuser2@testdomain", "testuser@testdomain", FALSE }, { "*@testdomain !testuser@testdomain !testuser2@testdomain", "testuser3@testdomain", TRUE }, { "!testuser@testdomain !testuser2@testdomain", "testuser", TRUE }, { "!testuser@testdomain !testuser2@testdomain", "testuser@testdomain", FALSE }, { "!testuser@testdomain *@testdomain !testuser2@testdomain", "testuser3@testdomain", TRUE }, { "!testuser@testdomain *@testdomain !testuser2@testdomain", "testuser@testdomain", FALSE }, }; test_begin("test username_filter"); for(size_t i = 0; i < N_ELEMENTS(cases); i++) { const char *const *filter = t_strsplit_spaces(cases[i].filter, " ,"); test_assert_idx(auth_request_username_accepted(filter, cases[i].input) == cases[i].accepted, i); } test_end(); } dovecot-2.3.21.1/src/auth/password-scheme.c0000644000000000000000000005673214656633576015332 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "hash.h" #include "base64.h" #include "hex-binary.h" #include "md4.h" #include "md5.h" #include "hmac.h" #include "hmac-cram-md5.h" #include "mycrypt.h" #include "randgen.h" #include "sha1.h" #include "sha2.h" #include "otp.h" #include "str.h" #include "password-scheme.h" static const char salt_chars[] = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; static HASH_TABLE(const char*, const struct password_scheme *) password_schemes; static const struct password_scheme * password_scheme_lookup_name(const char *name) { return hash_table_lookup(password_schemes, name); } /* Lookup scheme and encoding by given name. The encoding is taken from ".base64", ".b64" or ".hex" suffix if it exists, otherwise the default encoding is used. */ static const struct password_scheme * password_scheme_lookup(const char *name, enum password_encoding *encoding_r) { const struct password_scheme *scheme; const char *encoding = NULL; *encoding_r = PW_ENCODING_NONE; if ((encoding = strchr(name, '.')) != NULL) { name = t_strdup_until(name, encoding); encoding++; } scheme = password_scheme_lookup_name(name); if (scheme == NULL) return NULL; if (encoding == NULL) *encoding_r = scheme->default_encoding; else if (strcasecmp(encoding, "b64") == 0 || strcasecmp(encoding, "base64") == 0) *encoding_r = PW_ENCODING_BASE64; else if (strcasecmp(encoding, "hex") == 0) *encoding_r = PW_ENCODING_HEX; else { /* unknown encoding. treat as invalid scheme. */ return NULL; } return scheme; } int password_verify(const char *plaintext, const struct password_generate_params *params, const char *scheme, const unsigned char *raw_password, size_t size, const char **error_r) { const struct password_scheme *s; enum password_encoding encoding; const unsigned char *generated; size_t generated_size; int ret; s = password_scheme_lookup(scheme, &encoding); if (s == NULL) { *error_r = "Unknown password scheme"; return -1; } if (s->password_verify != NULL) { ret = s->password_verify(plaintext, params, raw_password, size, error_r); } else { /* generic verification handler: generate the password and compare it to the one in database */ s->password_generate(plaintext, params, &generated, &generated_size); ret = size != generated_size ? 0 : mem_equals_timing_safe(generated, raw_password, size) ? 1 : 0; } if (ret == 0) *error_r = AUTH_LOG_MSG_PASSWORD_MISMATCH; return ret; } const char *password_get_scheme(const char **password) { const char *p, *scheme; if (*password == NULL) return NULL; if (str_begins(*password, "$1$")) { /* $1$$[$] */ p = strchr(*password + 3, '$'); if (p != NULL) { /* stop at next '$' after password */ p = strchr(p+1, '$'); if (p != NULL) *password = t_strdup_until(*password, p); return "MD5-CRYPT"; } } if (**password != '{') return NULL; p = strchr(*password, '}'); if (p == NULL) return NULL; scheme = t_strdup_until(*password + 1, p); *password = p + 1; return scheme; } int password_decode(const char *password, const char *scheme, const unsigned char **raw_password_r, size_t *size_r, const char **error_r) { const struct password_scheme *s; enum password_encoding encoding; buffer_t *buf; size_t len; bool guessed_encoding; *error_r = NULL; s = password_scheme_lookup(scheme, &encoding); if (s == NULL) { *error_r = "Unknown scheme"; return 0; } len = strlen(password); if (encoding != PW_ENCODING_NONE && s->raw_password_len != 0 && strchr(scheme, '.') == NULL) { /* encoding not specified. we can guess quite well between base64 and hex encodings. the only problem is distinguishing 2 character strings, but there shouldn't be any that short raw_password_lens. */ encoding = len == s->raw_password_len * 2 ? PW_ENCODING_HEX : PW_ENCODING_BASE64; guessed_encoding = TRUE; } else { guessed_encoding = FALSE; } switch (encoding) { case PW_ENCODING_NONE: *raw_password_r = (const unsigned char *)password; *size_r = len; break; case PW_ENCODING_HEX: buf = t_buffer_create(len / 2 + 1); if (hex_to_binary(password, buf) == 0) { *raw_password_r = buf->data; *size_r = buf->used; break; } if (!guessed_encoding) { *error_r = "Input isn't valid HEX encoded data"; return -1; } /* check if it's base64-encoded after all. some input lengths produce matching hex and base64 encoded lengths. */ /* fall through */ case PW_ENCODING_BASE64: buf = t_buffer_create(MAX_BASE64_DECODED_SIZE(len)); if (base64_decode(password, len, NULL, buf) < 0) { *error_r = "Input isn't valid base64 encoded data"; return -1; } *raw_password_r = buf->data; *size_r = buf->used; break; } if (s->raw_password_len != *size_r && s->raw_password_len != 0) { /* password has invalid length */ *error_r = t_strdup_printf( "Input length isn't valid (%u instead of %u)", (unsigned int)*size_r, s->raw_password_len); return -1; } return 1; } bool password_generate(const char *plaintext, const struct password_generate_params *params, const char *scheme, const unsigned char **raw_password_r, size_t *size_r) { const struct password_scheme *s; enum password_encoding encoding; s = password_scheme_lookup(scheme, &encoding); if (s == NULL) return FALSE; s->password_generate(plaintext, params, raw_password_r, size_r); return TRUE; } bool password_generate_encoded(const char *plaintext, const struct password_generate_params *params, const char *scheme, const char **password_r) { const struct password_scheme *s; const unsigned char *raw_password; enum password_encoding encoding; string_t *str; size_t size; s = password_scheme_lookup(scheme, &encoding); if (s == NULL) return FALSE; s->password_generate(plaintext, params, &raw_password, &size); switch (encoding) { case PW_ENCODING_NONE: *password_r = t_strndup(raw_password, size); break; case PW_ENCODING_BASE64: str = t_str_new(MAX_BASE64_ENCODED_SIZE(size) + 1); base64_encode(raw_password, size, str); *password_r = str_c(str); break; case PW_ENCODING_HEX: *password_r = binary_to_hex(raw_password, size); break; } return TRUE; } const char *password_generate_salt(size_t len) { char *salt; salt = t_malloc_no0(len + 1); for (size_t i = 0; i < len; i++) salt[i] = salt_chars[i_rand_limit(sizeof(salt_chars) - 1)]; salt[len] = '\0'; return salt; } bool password_scheme_is_alias(const char *scheme1, const char *scheme2) { const struct password_scheme *s1 = NULL, *s2 = NULL; if (*scheme1 == '\0' || *scheme2 == '\0') return FALSE; scheme1 = t_strcut(scheme1, '.'); scheme2 = t_strcut(scheme2, '.'); if (strcasecmp(scheme1, scheme2) == 0) return TRUE; s1 = hash_table_lookup(password_schemes, scheme1); s2 = hash_table_lookup(password_schemes, scheme2); /* if they've the same generate function, they're equivalent */ return s1 != NULL && s2 != NULL && s1->password_generate == s2->password_generate; } const char * password_scheme_detect(const char *plain_password, const char *crypted_password, const struct password_generate_params *params) { struct hash_iterate_context *ctx; const char *key; const struct password_scheme *scheme; const unsigned char *raw_password; size_t raw_password_size; const char *error; ctx = hash_table_iterate_init(password_schemes); while (hash_table_iterate(ctx, password_schemes, &key, &scheme)) { if (password_decode(crypted_password, scheme->name, &raw_password, &raw_password_size, &error) <= 0) continue; if (password_verify(plain_password, params, scheme->name, raw_password, raw_password_size, &error) > 0) break; key = NULL; } hash_table_iterate_deinit(&ctx); return key; } int crypt_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char *raw_password, size_t size, const char **error_r) { const char *password, *crypted; if (size > 4 && raw_password[0] == '$' && raw_password[1] == '2' && raw_password[3] == '$') return password_verify(plaintext, params, "BLF-CRYPT", raw_password, size, error_r); if (size == 0) { /* the default mycrypt() handler would return match */ return 0; } password = t_strndup(raw_password, size); crypted = mycrypt(plaintext, password); if (crypted == NULL) { /* really shouldn't happen unless the system is broken */ *error_r = t_strdup_printf("crypt() failed: %m"); return -1; } return str_equals_timing_almost_safe(crypted, password) ? 1 : 0; } static int md5_verify(const char *plaintext, const struct password_generate_params *params, const unsigned char *raw_password, size_t size, const char **error_r) { const char *password, *str, *error; const unsigned char *md5_password; size_t md5_size; password = t_strndup(raw_password, size); if (str_begins(password, "$1$")) { /* MD5-CRYPT */ str = password_generate_md5_crypt(plaintext, password); return str_equals_timing_almost_safe(str, password) ? 1 : 0; } else if (password_decode(password, "PLAIN-MD5", &md5_password, &md5_size, &error) <= 0) { *error_r = "Not a valid MD5-CRYPT or PLAIN-MD5 password"; return -1; } else { return password_verify(plaintext, params, "PLAIN-MD5", md5_password, md5_size, error_r); } } static int md5_crypt_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char *raw_password, size_t size, const char **error_r ATTR_UNUSED) { const char *password, *str; password = t_strndup(raw_password, size); str = password_generate_md5_crypt(plaintext, password); return str_equals_timing_almost_safe(str, password) ? 1 : 0; } static void md5_crypt_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r) { const char *password; const char *salt; salt = password_generate_salt(8); password = password_generate_md5_crypt(plaintext, salt); *raw_password_r = (const unsigned char *)password; *size_r = strlen(password); } static void sha1_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r) { unsigned char *digest; digest = t_malloc_no0(SHA1_RESULTLEN); sha1_get_digest(plaintext, strlen(plaintext), digest); *raw_password_r = digest; *size_r = SHA1_RESULTLEN; } static void sha256_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r) { unsigned char *digest; digest = t_malloc_no0(SHA256_RESULTLEN); sha256_get_digest(plaintext, strlen(plaintext), digest); *raw_password_r = digest; *size_r = SHA256_RESULTLEN; } static void sha512_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r) { unsigned char *digest; digest = t_malloc_no0(SHA512_RESULTLEN); sha512_get_digest(plaintext, strlen(plaintext), digest); *raw_password_r = digest; *size_r = SHA512_RESULTLEN; } static void ssha_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r) { #define SSHA_SALT_LEN 4 unsigned char *digest, *salt; struct sha1_ctxt ctx; digest = t_malloc_no0(SHA1_RESULTLEN + SSHA_SALT_LEN); salt = digest + SHA1_RESULTLEN; random_fill(salt, SSHA_SALT_LEN); sha1_init(&ctx); sha1_loop(&ctx, plaintext, strlen(plaintext)); sha1_loop(&ctx, salt, SSHA_SALT_LEN); sha1_result(&ctx, digest); *raw_password_r = digest; *size_r = SHA1_RESULTLEN + SSHA_SALT_LEN; } static int ssha_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char *raw_password, size_t size, const char **error_r) { unsigned char sha1_digest[SHA1_RESULTLEN]; struct sha1_ctxt ctx; /* format: */ if (size <= SHA1_RESULTLEN) { *error_r = "SSHA password is too short"; return -1; } sha1_init(&ctx); sha1_loop(&ctx, plaintext, strlen(plaintext)); sha1_loop(&ctx, raw_password + SHA1_RESULTLEN, size - SHA1_RESULTLEN); sha1_result(&ctx, sha1_digest); return mem_equals_timing_safe(sha1_digest, raw_password, SHA1_RESULTLEN) ? 1 : 0; } static void ssha256_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r) { #define SSHA256_SALT_LEN 4 unsigned char *digest, *salt; struct sha256_ctx ctx; digest = t_malloc_no0(SHA256_RESULTLEN + SSHA256_SALT_LEN); salt = digest + SHA256_RESULTLEN; random_fill(salt, SSHA256_SALT_LEN); sha256_init(&ctx); sha256_loop(&ctx, plaintext, strlen(plaintext)); sha256_loop(&ctx, salt, SSHA256_SALT_LEN); sha256_result(&ctx, digest); *raw_password_r = digest; *size_r = SHA256_RESULTLEN + SSHA256_SALT_LEN; } static int ssha256_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char *raw_password, size_t size, const char **error_r) { unsigned char sha256_digest[SHA256_RESULTLEN]; struct sha256_ctx ctx; /* format: */ if (size <= SHA256_RESULTLEN) { *error_r = "SSHA256 password is too short"; return -1; } sha256_init(&ctx); sha256_loop(&ctx, plaintext, strlen(plaintext)); sha256_loop(&ctx, raw_password + SHA256_RESULTLEN, size - SHA256_RESULTLEN); sha256_result(&ctx, sha256_digest); return mem_equals_timing_safe(sha256_digest, raw_password, SHA256_RESULTLEN) ? 1 : 0; } static void ssha512_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r) { #define SSHA512_SALT_LEN 4 unsigned char *digest, *salt; struct sha512_ctx ctx; digest = t_malloc_no0(SHA512_RESULTLEN + SSHA512_SALT_LEN); salt = digest + SHA512_RESULTLEN; random_fill(salt, SSHA512_SALT_LEN); sha512_init(&ctx); sha512_loop(&ctx, plaintext, strlen(plaintext)); sha512_loop(&ctx, salt, SSHA512_SALT_LEN); sha512_result(&ctx, digest); *raw_password_r = digest; *size_r = SHA512_RESULTLEN + SSHA512_SALT_LEN; } static int ssha512_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char *raw_password, size_t size, const char **error_r) { unsigned char sha512_digest[SHA512_RESULTLEN]; struct sha512_ctx ctx; /* format: */ if (size <= SHA512_RESULTLEN) { *error_r = "SSHA512 password is too short"; return -1; } sha512_init(&ctx); sha512_loop(&ctx, plaintext, strlen(plaintext)); sha512_loop(&ctx, raw_password + SHA512_RESULTLEN, size - SHA512_RESULTLEN); sha512_result(&ctx, sha512_digest); return mem_equals_timing_safe(sha512_digest, raw_password, SHA512_RESULTLEN) ? 1 : 0; } static void smd5_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r) { #define SMD5_SALT_LEN 4 unsigned char *digest, *salt; struct md5_context ctx; digest = t_malloc_no0(MD5_RESULTLEN + SMD5_SALT_LEN); salt = digest + MD5_RESULTLEN; random_fill(salt, SMD5_SALT_LEN); md5_init(&ctx); md5_update(&ctx, plaintext, strlen(plaintext)); md5_update(&ctx, salt, SMD5_SALT_LEN); md5_final(&ctx, digest); *raw_password_r = digest; *size_r = MD5_RESULTLEN + SMD5_SALT_LEN; } static int smd5_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char *raw_password, size_t size, const char **error_r) { unsigned char md5_digest[MD5_RESULTLEN]; struct md5_context ctx; /* format: */ if (size <= MD5_RESULTLEN) { *error_r = "SMD5 password is too short"; return -1; } md5_init(&ctx); md5_update(&ctx, plaintext, strlen(plaintext)); md5_update(&ctx, raw_password + MD5_RESULTLEN, size - MD5_RESULTLEN); md5_final(&ctx, md5_digest); return mem_equals_timing_safe(md5_digest, raw_password, MD5_RESULTLEN) ? 1 : 0; } static void plain_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r) { *raw_password_r = (const unsigned char *)plaintext, *size_r = strlen(plaintext); } static int plain_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char *raw_password, size_t size, const char **error_r ATTR_UNUSED) { size_t plaintext_len = strlen(plaintext); if (plaintext_len != size) return 0; return mem_equals_timing_safe(plaintext, raw_password, size) ? 1 : 0; } static int plain_trunc_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char *raw_password, size_t size, const char **error_r) { size_t i, plaintext_len, trunc_len = 0; /* format: - */ for (i = 0; i < size; i++) { if (raw_password[i] >= '0' && raw_password[i] <= '9') trunc_len = trunc_len*10 + raw_password[i]-'0'; else break; } if (i == size || raw_password[i] != '-') { *error_r = "PLAIN-TRUNC missing length: prefix"; return -1; } i++; plaintext_len = strlen(plaintext); if (size-i == trunc_len && plaintext_len >= trunc_len) { /* possibly truncated password. allow the given password as long as the prefix matches. */ return mem_equals_timing_safe(raw_password+i, plaintext, trunc_len) ? 1 : 0; } return plaintext_len == size-i && mem_equals_timing_safe(raw_password+i, plaintext, plaintext_len) ? 1 : 0; } static void cram_md5_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r) { struct hmac_context ctx; unsigned char *context_digest; context_digest = t_malloc_no0(CRAM_MD5_CONTEXTLEN); hmac_init(&ctx, (const unsigned char *)plaintext, strlen(plaintext), &hash_method_md5); hmac_md5_get_cram_context(&ctx, context_digest); *raw_password_r = context_digest; *size_r = CRAM_MD5_CONTEXTLEN; } static void digest_md5_generate(const char *plaintext, const struct password_generate_params *params, const unsigned char **raw_password_r, size_t *size_r) { const char *realm, *str, *user; unsigned char *digest; if (params->user == NULL) i_fatal("digest_md5_generate(): username not given"); user = params->user; /* assume user@realm format for username. If user@domain is wanted in the username, allow also user@domain@realm. */ realm = strrchr(user, '@'); if (realm != NULL) { user = t_strdup_until(user, realm); realm++; } else { realm = ""; } /* user:realm:passwd */ digest = t_malloc_no0(MD5_RESULTLEN); str = t_strdup_printf("%s:%s:%s", user, realm, plaintext); md5_get_digest(str, strlen(str), digest); *raw_password_r = digest; *size_r = MD5_RESULTLEN; } static void plain_md4_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r) { unsigned char *digest; digest = t_malloc_no0(MD4_RESULTLEN); md4_get_digest(plaintext, strlen(plaintext), digest); *raw_password_r = digest; *size_r = MD4_RESULTLEN; } static void plain_md5_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r) { unsigned char *digest; digest = t_malloc_no0(MD5_RESULTLEN); md5_get_digest(plaintext, strlen(plaintext), digest); *raw_password_r = digest; *size_r = MD5_RESULTLEN; } static int otp_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char *raw_password, size_t size, const char **error_r) { const char *password, *generated; password = t_strndup(raw_password, size); if (password_generate_otp(plaintext, password, UINT_MAX, &generated) < 0) { *error_r = "Invalid OTP data in passdb"; return -1; } return strcasecmp(password, generated) == 0 ? 1 : 0; } static void otp_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r) { const char *password; if (password_generate_otp(plaintext, NULL, OTP_HASH_SHA1, &password) < 0) i_unreached(); *raw_password_r = (const unsigned char *)password; *size_r = strlen(password); } static const struct password_scheme builtin_schemes[] = { { "MD5", PW_ENCODING_NONE, 0, md5_verify, md5_crypt_generate }, { "MD5-CRYPT", PW_ENCODING_NONE, 0, md5_crypt_verify, md5_crypt_generate }, { "SHA", PW_ENCODING_BASE64, SHA1_RESULTLEN, NULL, sha1_generate }, { "SHA1", PW_ENCODING_BASE64, SHA1_RESULTLEN, NULL, sha1_generate }, { "SHA256", PW_ENCODING_BASE64, SHA256_RESULTLEN, NULL, sha256_generate }, { "SHA512", PW_ENCODING_BASE64, SHA512_RESULTLEN, NULL, sha512_generate }, { "SMD5", PW_ENCODING_BASE64, 0, smd5_verify, smd5_generate }, { "SSHA", PW_ENCODING_BASE64, 0, ssha_verify, ssha_generate }, { "SSHA256", PW_ENCODING_BASE64, 0, ssha256_verify, ssha256_generate }, { "SSHA512", PW_ENCODING_BASE64, 0, ssha512_verify, ssha512_generate }, { "PLAIN", PW_ENCODING_NONE, 0, plain_verify, plain_generate }, { "CLEAR", PW_ENCODING_NONE, 0, plain_verify, plain_generate }, { "CLEARTEXT", PW_ENCODING_NONE, 0, plain_verify, plain_generate }, { "PLAIN-TRUNC", PW_ENCODING_NONE, 0, plain_trunc_verify, plain_generate }, { "CRAM-MD5", PW_ENCODING_HEX, CRAM_MD5_CONTEXTLEN, NULL, cram_md5_generate }, { "SCRAM-SHA-1", PW_ENCODING_NONE, 0, scram_sha1_verify, scram_sha1_generate}, { "SCRAM-SHA-256", PW_ENCODING_NONE, 0, scram_sha256_verify, scram_sha256_generate}, { "HMAC-MD5", PW_ENCODING_HEX, CRAM_MD5_CONTEXTLEN, NULL, cram_md5_generate }, { "DIGEST-MD5", PW_ENCODING_HEX, MD5_RESULTLEN, NULL, digest_md5_generate }, { "PLAIN-MD4", PW_ENCODING_HEX, MD4_RESULTLEN, NULL, plain_md4_generate }, { "PLAIN-MD5", PW_ENCODING_HEX, MD5_RESULTLEN, NULL, plain_md5_generate }, { "LDAP-MD5", PW_ENCODING_BASE64, MD5_RESULTLEN, NULL, plain_md5_generate }, { "OTP", PW_ENCODING_NONE, 0, otp_verify, otp_generate }, { "PBKDF2", PW_ENCODING_NONE, 0, pbkdf2_verify, pbkdf2_generate }, }; void password_scheme_register(const struct password_scheme *scheme) { if (password_scheme_lookup_name(scheme->name) != NULL) { i_panic("password_scheme_register(%s): Already registered", scheme->name); } hash_table_insert(password_schemes, scheme->name, scheme); } void password_scheme_unregister(const struct password_scheme *scheme) { if (!hash_table_try_remove(password_schemes, scheme->name)) i_panic("password_scheme_unregister(%s): Not registered", scheme->name); } void password_schemes_get(ARRAY_TYPE(password_scheme_p) *schemes_r) { struct hash_iterate_context *ctx; const char *key; const struct password_scheme *scheme; ctx = hash_table_iterate_init(password_schemes); while(hash_table_iterate(ctx, password_schemes, &key, &scheme)) { array_push_back(schemes_r, &scheme); } hash_table_iterate_deinit(&ctx); } void password_schemes_init(void) { unsigned int i; hash_table_create(&password_schemes, default_pool, N_ELEMENTS(builtin_schemes)*2, strfastcase_hash, strcasecmp); for (i = 0; i < N_ELEMENTS(builtin_schemes); i++) password_scheme_register(&builtin_schemes[i]); password_scheme_register_crypt(); #ifdef HAVE_LIBSODIUM password_scheme_register_sodium(); #endif } void password_schemes_deinit(void) { hash_table_destroy(&password_schemes); } dovecot-2.3.21.1/src/auth/mech-login.c0000644000000000000000000000345014656633576014235 00000000000000/* * LOGIN authentication mechanism. * * Copyright (c) 2004 Andrey Panin * * This software is released under the MIT license. */ #include "auth-common.h" #include "mech.h" #include "passdb.h" #include "safe-memset.h" #include "mech-plain-common.h" static void mech_login_auth_continue(struct auth_request *request, const unsigned char *data, size_t data_size) { static const char prompt2[] = "Password:"; const char *username, *error; if (request->fields.user == NULL) { username = t_strndup(data, data_size); if (!auth_request_set_username(request, username, &error)) { e_info(request->mech_event, "%s", error); auth_request_fail(request); return; } auth_request_handler_reply_continue(request, prompt2, strlen(prompt2)); } else { char *pass = p_strndup(unsafe_data_stack_pool, data, data_size); auth_request_verify_plain(request, pass, plain_verify_callback); safe_memset(pass, 0, strlen(pass)); } } static void mech_login_auth_initial(struct auth_request *request, const unsigned char *data, size_t data_size) { static const char prompt1[] = "Username:"; if (data_size == 0) { auth_request_handler_reply_continue(request, prompt1, strlen(prompt1)); } else { mech_login_auth_continue(request, data, data_size); } } static struct auth_request *mech_login_auth_new(void) { struct auth_request *request; pool_t pool; pool = pool_alloconly_create(MEMPOOL_GROWING"login_auth_request", 2048); request = p_new(pool, struct auth_request, 1); request->pool = pool; return request; } const struct mech_module mech_login = { "LOGIN", .flags = MECH_SEC_PLAINTEXT, .passdb_need = MECH_PASSDB_NEED_VERIFY_PLAIN, mech_login_auth_new, mech_login_auth_initial, mech_login_auth_continue, mech_generic_auth_free }; dovecot-2.3.21.1/src/auth/passdb-lua.c0000644000000000000000000001301714656633576014246 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "passdb.h" #include "auth-cache.h" #if defined(BUILTIN_LUA) || defined(PLUGIN_BUILD) #include "db-lua.h" struct dlua_passdb_module { struct passdb_module module; struct dlua_script *script; const char *file; bool has_password_verify; }; static enum passdb_result passdb_lua_verify_password(struct dlua_passdb_module *module, struct auth_request *request, const char *password) { const char *error = NULL; enum passdb_result result = auth_lua_call_password_verify(module->script, request, password, &error); if (result == PASSDB_RESULT_PASSWORD_MISMATCH) { auth_request_log_password_mismatch(request, AUTH_SUBSYS_DB); } else if (result == PASSDB_RESULT_INTERNAL_FAILURE && error != NULL) { e_error(authdb_event(request), "passdb-lua: %s", error); } return result; } static enum passdb_result passdb_lua_lookup(struct auth_request *request, const char **scheme_r, const char **password_r) { const char *error = NULL; enum passdb_result result; struct dlua_passdb_module *module = (struct dlua_passdb_module *)request->passdb->passdb; *scheme_r = *password_r = NULL; result = auth_lua_call_passdb_lookup(module->script, request, scheme_r, password_r, &error); if (result == PASSDB_RESULT_INTERNAL_FAILURE && error != NULL) { e_error(authdb_event(request), "db-lua: %s", error); } else if (result != PASSDB_RESULT_OK) { /* skip next bit */ } else if (!auth_fields_exists(request->fields.extra_fields, "nopassword")) { if (*password_r == NULL || **password_r == '\0') { result = auth_request_password_missing(request); } else { if (*scheme_r == NULL) *scheme_r = request->passdb->passdb->default_pass_scheme; auth_request_set_field(request, "password", *password_r, *scheme_r); } } else if (*password_r != NULL && **password_r != '\0') { e_info(authdb_event(request), "nopassword given and password is not empty"); result = PASSDB_RESULT_PASSWORD_MISMATCH; } return result; } static void passdb_lua_lookup_credentials(struct auth_request *request, lookup_credentials_callback_t *callback) { const char *lua_password, *lua_scheme; enum passdb_result result = passdb_lua_lookup(request, &lua_scheme, &lua_password); passdb_handle_credentials(result, lua_password, lua_scheme, callback, request); } static void passdb_lua_verify_plain(struct auth_request *request, const char *password, verify_plain_callback_t *callback) { struct dlua_passdb_module *module = (struct dlua_passdb_module *)request->passdb->passdb; const char *lua_password, *lua_scheme; enum passdb_result result; if (module->has_password_verify) { result = passdb_lua_verify_password(module, request, password); } else { result = passdb_lua_lookup(request, &lua_scheme, &lua_password); if (result == PASSDB_RESULT_OK) { if (lua_scheme == NULL) lua_scheme = "PLAIN"; result = auth_request_password_verify(request, password, lua_password, lua_scheme, AUTH_SUBSYS_DB); } } callback(result, request); } static struct passdb_module * passdb_lua_preinit(pool_t pool, const char *args) { const char *cache_key = "%u"; const char *scheme = "PLAIN"; struct dlua_passdb_module *module; bool blocking = TRUE; module = p_new(pool, struct dlua_passdb_module, 1); const char *const *fields = t_strsplit_spaces(args, " "); while(*fields != NULL) { if (str_begins(*fields, "file=")) { module->file = p_strdup(pool, (*fields)+5); } else if (str_begins(*fields, "blocking=")) { const char *value = (*fields)+9; if (strcmp(value, "yes") == 0) { blocking = TRUE; } else if (strcmp(value, "no") == 0) { blocking = FALSE; } else { i_fatal("Invalid value %s. " "Field blocking must be yes or no", value); } } else if (str_begins(*fields, "cache_key=")) { if (*((*fields)+10) != '\0') cache_key = (*fields)+10; else /* explicitly disable auth caching for lua */ cache_key = NULL; } else if (str_begins(*fields, "scheme=")) { scheme = p_strdup(pool, (*fields)+7); } else { i_fatal("Unsupported parameter %s", *fields); } fields++; } if (module->file == NULL) i_fatal("passdb-lua: Missing mandatory file= parameter"); module->module.blocking = blocking; module->module.default_cache_key = auth_cache_parse_key(pool, cache_key); module->module.default_pass_scheme = scheme; return &module->module; } static void passdb_lua_init(struct passdb_module *_module) { struct dlua_passdb_module *module = (struct dlua_passdb_module *)_module; const char *error; if (dlua_script_create_file(module->file, &module->script, auth_event, &error) < 0 || auth_lua_script_init(module->script, &error) < 0) i_fatal("passdb-lua: initialization failed: %s", error); module->has_password_verify = dlua_script_has_function(module->script, AUTH_LUA_PASSWORD_VERIFY); } static void passdb_lua_deinit(struct passdb_module *_module) { struct dlua_passdb_module *module = (struct dlua_passdb_module *)_module; dlua_script_unref(&module->script); } #ifndef PLUGIN_BUILD struct passdb_module_interface passdb_lua = #else struct passdb_module_interface passdb_lua_plugin = #endif { "lua", passdb_lua_preinit, passdb_lua_init, passdb_lua_deinit, passdb_lua_verify_plain, passdb_lua_lookup_credentials, NULL }; #else struct passdb_module_interface passdb_lua = { .name = "lua" }; #endif dovecot-2.3.21.1/src/auth/passdb-cache.h0000644000000000000000000000115114656633576014531 00000000000000#ifndef PASSDB_CACHE_H #define PASSDB_CACHE_H #include "auth-cache.h" #include "passdb.h" extern struct auth_cache *passdb_cache; bool passdb_cache_verify_plain(struct auth_request *request, const char *key, const char *password, enum passdb_result *result_r, bool use_expired); bool passdb_cache_lookup_credentials(struct auth_request *request, const char *key, const char **password_r, const char **scheme_r, enum passdb_result *result_r, bool use_expired); void passdb_cache_init(const struct auth_settings *set); void passdb_cache_deinit(void); #endif dovecot-2.3.21.1/src/auth/password-scheme.h0000644000000000000000000001322714656633576015327 00000000000000#ifndef PASSWORD_SCHEME_H #define PASSWORD_SCHEME_H #define AUTH_LOG_MSG_PASSWORD_MISMATCH "Password mismatch" struct hash_method; enum password_encoding { PW_ENCODING_NONE, PW_ENCODING_BASE64, PW_ENCODING_HEX }; struct password_generate_params { const char *user; unsigned int rounds; }; struct password_scheme { const char *name; enum password_encoding default_encoding; /* If non-zero, this is the expected raw password length. It can be used to automatically detect encoding between hex and base64 encoded passwords. */ unsigned int raw_password_len; int (*password_verify)(const char *plaintext, const struct password_generate_params *params, const unsigned char *raw_password, size_t size, const char **error_r); void (*password_generate)(const char *plaintext, const struct password_generate_params *params, const unsigned char **raw_password_r, size_t *size_r); }; ARRAY_DEFINE_TYPE(password_scheme_p, const struct password_scheme *); void password_schemes_get(ARRAY_TYPE(password_scheme_p) *schemes_r); extern unsigned int password_scheme_encryption_rounds; /* Returns 1 = matched, 0 = didn't match, -1 = unknown scheme or invalid raw_password */ int password_verify(const char *plaintext, const struct password_generate_params *params, const char *scheme, const unsigned char *raw_password, size_t size, const char **error_r); /* Extracts scheme from password, or returns NULL if it isn't found. If auth_request is given, it's used for debug logging. */ const char *password_get_scheme(const char **password); /* Decode encoded (base64/hex) password to raw form. Returns 1 if ok, 0 if scheme is unknown, -1 if password is invalid. */ int password_decode(const char *password, const char *scheme, const unsigned char **raw_password_r, size_t *size_r, const char **error_r); /* Create password with wanted scheme out of plaintext password and username. Potential base64/hex directives are ignored in scheme. Returns FALSE if the scheme is unknown. */ bool password_generate(const char *plaintext, const struct password_generate_params *params, const char *scheme, const unsigned char **raw_password_r, size_t *size_r); /* Like above, but generate encoded passwords. If hex/base64 directive isn't specified in the scheme, the default encoding for the scheme is used. Returns FALSE if the scheme is unknown. */ bool password_generate_encoded(const char *plaintext, const struct password_generate_params *params, const char *scheme, const char **password_r); /* Returns TRUE if schemes are equivalent. */ bool password_scheme_is_alias(const char *scheme1, const char *scheme2); /* Try to detect in which scheme crypted password is. Returns the scheme name or NULL if nothing was found. */ const char * password_scheme_detect(const char *plain_password, const char *crypted_password, const struct password_generate_params *params); void password_scheme_register(const struct password_scheme *scheme); void password_scheme_unregister(const struct password_scheme *scheme); void password_schemes_init(void); void password_schemes_deinit(void); /* some password schemes/algorithms supports a variable number of encryption rounds. */ void password_set_encryption_rounds(unsigned int rounds); /* INTERNAL: */ const char *password_generate_salt(size_t len); const char *password_generate_md5_crypt(const char *pw, const char *salt); int password_generate_otp(const char *pw, const char *state_data, unsigned int algo, const char **result_r) ATTR_NULL(2); int crypt_verify(const char *plaintext, const struct password_generate_params *params, const unsigned char *raw_password, size_t size, const char **error_r); int scram_scheme_parse(const struct hash_method *hmethod, const char *name, const unsigned char *credentials, size_t size, unsigned int *iter_count_r, const char **salt_r, unsigned char stored_key_r[], unsigned char server_key_r[], const char **error_r); int scram_verify(const struct hash_method *hmethod, const char *scheme_name, const char *plaintext, const unsigned char *raw_password, size_t size, const char **error_r); void scram_generate(const struct hash_method *hmethod, const char *plaintext, const unsigned char **raw_password_r, size_t *size_r); int scram_sha1_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char *raw_password, size_t size, const char **error_r ATTR_UNUSED); void scram_sha1_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r); int scram_sha256_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char *raw_password, size_t size, const char **error_r); void scram_sha256_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r); void pbkdf2_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r); int pbkdf2_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char *raw_password, size_t size, const char **error_r); /* check which of the algorithms Blowfish, SHA-256 and SHA-512 are supported by the used libc's/glibc's crypt() */ void password_scheme_register_crypt(void); #ifdef HAVE_LIBSODIUM void password_scheme_register_sodium(void); #endif #endif dovecot-2.3.21.1/src/auth/auth-cache.h0000644000000000000000000000365414656633576014230 00000000000000#ifndef AUTH_CACHE_H #define AUTH_CACHE_H struct auth_cache_node { struct auth_cache_node *prev, *next; time_t created; /* Total number of bytes used by this node */ uint32_t alloc_size:31; /* TRUE if the user gave the correct password the last time. */ bool last_success:1; char data[]; /* key \0 value \0 */ }; struct auth_cache; struct auth_request; /* Parses all %x variables from query and compresses them into tab-separated list, so it can be used as a cache key. */ char *auth_cache_parse_key(pool_t pool, const char *query); /* Create a new cache. max_size specifies the maximum amount of memory in bytes to use for cache (it's not fully exact). ttl_secs specifies time to live for cache record, requests older than that are not used. neg_ttl_secs specifies the TTL for negative entries. */ struct auth_cache *auth_cache_new(size_t max_size, unsigned int ttl_secs, unsigned int neg_ttl_secs); void auth_cache_free(struct auth_cache **cache); /* Clear the cache. Returns how many entries were removed. */ unsigned int ATTR_NOWARN_UNUSED_RESULT auth_cache_clear(struct auth_cache *cache); unsigned int auth_cache_clear_users(struct auth_cache *cache, const char *const *usernames); /* Look key from cache. key should be the same string as returned by auth_cache_parse_key(). Returned node can't be used after any other auth_cache_*() calls. */ const char * auth_cache_lookup(struct auth_cache *cache, const struct auth_request *request, const char *key, struct auth_cache_node **node_r, bool *expired_r, bool *neg_expired_r); /* Insert key => value into cache. "" value means negative cache entry. */ void auth_cache_insert(struct auth_cache *cache, struct auth_request *request, const char *key, const char *value, bool last_success); /* Remove key from cache */ void auth_cache_remove(struct auth_cache *cache, const struct auth_request *request, const char *key); #endif dovecot-2.3.21.1/src/auth/mech-plain-common.c0000644000000000000000000000076414656633576015523 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "mech.h" #include "passdb.h" #include "mech-plain-common.h" void plain_verify_callback(enum passdb_result result, struct auth_request *request) { switch (result) { case PASSDB_RESULT_OK: auth_request_success(request, "", 0); break; case PASSDB_RESULT_INTERNAL_FAILURE: auth_request_internal_failure(request); break; default: auth_request_fail(request); break; } } dovecot-2.3.21.1/src/auth/passdb-sql.c0000644000000000000000000002210614656633576014263 00000000000000/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "passdb.h" #ifdef PASSDB_SQL #include "safe-memset.h" #include "password-scheme.h" #include "auth-cache.h" #include "db-sql.h" #include struct sql_passdb_module { struct passdb_module module; struct db_sql_connection *conn; }; struct passdb_sql_request { struct auth_request *auth_request; union { verify_plain_callback_t *verify_plain; lookup_credentials_callback_t *lookup_credentials; set_credentials_callback_t *set_credentials; } callback; }; static void sql_query_save_results(struct sql_result *result, struct passdb_sql_request *sql_request) { struct auth_request *auth_request = sql_request->auth_request; struct passdb_module *_module = auth_request->passdb->passdb; struct sql_passdb_module *module = (struct sql_passdb_module *)_module; unsigned int i, fields_count; const char *name, *value; fields_count = sql_result_get_fields_count(result); for (i = 0; i < fields_count; i++) { name = sql_result_get_field_name(result, i); value = sql_result_get_field_value(result, i); if (*name == '\0') ; else if (value == NULL) auth_request_set_null_field(auth_request, name); else { auth_request_set_field(auth_request, name, value, module->conn->set.default_pass_scheme); } } } static void sql_query_callback(struct sql_result *result, struct passdb_sql_request *sql_request) { struct auth_request *auth_request = sql_request->auth_request; struct passdb_module *_module = auth_request->passdb->passdb; struct sql_passdb_module *module = (struct sql_passdb_module *)_module; enum passdb_result passdb_result; const char *password, *scheme; int ret; passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; password = NULL; ret = sql_result_next_row(result); if (ret >= 0) db_sql_success(module->conn); if (ret < 0) { if (!module->conn->default_password_query) { e_error(authdb_event(auth_request), "Password query failed: %s", sql_result_get_error(result)); } else { e_error(authdb_event(auth_request), "Password query failed: %s " "(using built-in default password_query: %s)", sql_result_get_error(result), module->conn->set.password_query); } } else if (ret == 0) { auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB); passdb_result = PASSDB_RESULT_USER_UNKNOWN; } else { sql_query_save_results(result, sql_request); /* Note that we really want to check if the password field is found. Just checking if password is set isn't enough, because with proxies we might want to return NULL as password. */ if (sql_result_find_field(result, "password") < 0 && sql_result_find_field(result, "password_noscheme") < 0) { e_error(authdb_event(auth_request), "Password query must return a field named " "'password'"); } else if (sql_result_next_row(result) > 0) { e_error(authdb_event(auth_request), "Password query returned multiple matches"); } else if (auth_request->passdb_password == NULL && !auth_fields_exists(auth_request->fields.extra_fields, "nopassword")) { passdb_result = auth_request_password_missing(auth_request); } else { /* passdb_password may change on the way, so we'll need to strdup. */ password = t_strdup(auth_request->passdb_password); passdb_result = PASSDB_RESULT_OK; } } scheme = password_get_scheme(&password); /* auth_request_set_field() sets scheme */ i_assert(password == NULL || scheme != NULL); if (auth_request->wanted_credentials_scheme != NULL) { passdb_handle_credentials(passdb_result, password, scheme, sql_request->callback.lookup_credentials, auth_request); auth_request_unref(&auth_request); return; } /* verify plain */ if (password == NULL) { sql_request->callback.verify_plain(passdb_result, auth_request); auth_request_unref(&auth_request); return; } passdb_result = auth_request_password_verify(auth_request, auth_request->mech_password, password, scheme, AUTH_SUBSYS_DB); sql_request->callback.verify_plain(passdb_result, auth_request); auth_request_unref(&auth_request); } static const char * passdb_sql_escape(const char *str, const struct auth_request *auth_request) { struct passdb_module *_module = auth_request->passdb->passdb; struct sql_passdb_module *module = (struct sql_passdb_module *)_module; return sql_escape_string(module->conn->db, str); } static void sql_lookup_pass(struct passdb_sql_request *sql_request) { struct passdb_module *_module = sql_request->auth_request->passdb->passdb; struct sql_passdb_module *module = (struct sql_passdb_module *)_module; const char *query, *error; if (t_auth_request_var_expand(module->conn->set.password_query, sql_request->auth_request, passdb_sql_escape, &query, &error) <= 0) { e_debug(authdb_event(sql_request->auth_request), "Failed to expand password_query=%s: %s", module->conn->set.password_query, error); sql_request->callback.verify_plain(PASSDB_RESULT_INTERNAL_FAILURE, sql_request->auth_request); return; } e_debug(authdb_event(sql_request->auth_request), "query: %s", query); auth_request_ref(sql_request->auth_request); sql_query(module->conn->db, query, sql_query_callback, sql_request); } static void sql_verify_plain(struct auth_request *request, const char *password ATTR_UNUSED, verify_plain_callback_t *callback) { struct passdb_sql_request *sql_request; sql_request = p_new(request->pool, struct passdb_sql_request, 1); sql_request->auth_request = request; sql_request->callback.verify_plain = callback; sql_lookup_pass(sql_request); } static void sql_lookup_credentials(struct auth_request *request, lookup_credentials_callback_t *callback) { struct passdb_sql_request *sql_request; sql_request = p_new(request->pool, struct passdb_sql_request, 1); sql_request->auth_request = request; sql_request->callback.lookup_credentials = callback; sql_lookup_pass(sql_request); } static void sql_set_credentials_callback(const struct sql_commit_result *sql_result, struct passdb_sql_request *sql_request) { struct passdb_module *_module = sql_request->auth_request->passdb->passdb; struct sql_passdb_module *module = (struct sql_passdb_module *)_module; if (sql_result->error != NULL) { if (!module->conn->default_update_query) { auth_request_log_error(sql_request->auth_request, AUTH_SUBSYS_DB, "Set credentials query failed: %s", sql_result->error); } else { auth_request_log_error(sql_request->auth_request, AUTH_SUBSYS_DB, "Set credentials query failed: %s" "(using built-in default update_query: %s)", sql_result->error, module->conn->set.update_query); } } sql_request->callback. set_credentials(sql_result->error == NULL, sql_request->auth_request); i_free(sql_request); } static void sql_set_credentials(struct auth_request *request, const char *new_credentials, set_credentials_callback_t *callback) { struct sql_passdb_module *module = (struct sql_passdb_module *) request->passdb->passdb; struct sql_transaction_context *transaction; struct passdb_sql_request *sql_request; const char *query, *error; request->mech_password = p_strdup(request->pool, new_credentials); if (t_auth_request_var_expand(module->conn->set.update_query, request, passdb_sql_escape, &query, &error) <= 0) { e_error(authdb_event(request), "Failed to expand update_query=%s: %s", module->conn->set.update_query, error); callback(FALSE, request); return; } sql_request = i_new(struct passdb_sql_request, 1); sql_request->auth_request = request; sql_request->callback.set_credentials = callback; transaction = sql_transaction_begin(module->conn->db); sql_update(transaction, query); sql_transaction_commit(&transaction, sql_set_credentials_callback, sql_request); } static struct passdb_module * passdb_sql_preinit(pool_t pool, const char *args) { struct sql_passdb_module *module; struct db_sql_connection *conn; module = p_new(pool, struct sql_passdb_module, 1); module->conn = conn = db_sql_init(args, FALSE); module->module.default_cache_key = auth_cache_parse_key(pool, conn->set.password_query); module->module.default_pass_scheme = conn->set.default_pass_scheme; return &module->module; } static void passdb_sql_init(struct passdb_module *_module) { struct sql_passdb_module *module = (struct sql_passdb_module *)_module; enum sql_db_flags flags; flags = sql_get_flags(module->conn->db); module->module.blocking = (flags & SQL_DB_FLAG_BLOCKING) != 0; if (!module->module.blocking || worker) db_sql_connect(module->conn); db_sql_check_userdb_warning(module->conn); } static void passdb_sql_deinit(struct passdb_module *_module) { struct sql_passdb_module *module = (struct sql_passdb_module *)_module; db_sql_unref(&module->conn); } struct passdb_module_interface passdb_sql = { "sql", passdb_sql_preinit, passdb_sql_init, passdb_sql_deinit, sql_verify_plain, sql_lookup_credentials, sql_set_credentials }; #else struct passdb_module_interface passdb_sql = { .name = "sql" }; #endif dovecot-2.3.21.1/src/auth/mech-plain-common.h0000644000000000000000000000023414656633576015520 00000000000000#ifndef MECH_PLAIN_COMMON_H #define MECH_PLAIN_COMMON_H void plain_verify_callback(enum passdb_result result, struct auth_request *request); #endif dovecot-2.3.21.1/src/auth/db-lua.h0000644000000000000000000000202114656633576013355 00000000000000#ifndef DB_LUA_H #define DB_LUA_H 1 #include "dlua-script.h" #define DB_LUA_CACHE_KEY "%u" #define AUTH_LUA_PASSWORD_VERIFY "auth_password_verify" struct dlua_script; int auth_lua_script_init(struct dlua_script *script, const char **error_r); int auth_lua_call_password_verify(struct dlua_script *script, struct auth_request *req, const char *password, const char **error_r); enum passdb_result auth_lua_call_passdb_lookup(struct dlua_script *script, struct auth_request *req, const char **scheme_r, const char **password_r, const char **error_r); enum userdb_result auth_lua_call_userdb_lookup(struct dlua_script *script, struct auth_request *req, const char **error_r); struct userdb_iterate_context * auth_lua_call_userdb_iterate_init(struct dlua_script *script, struct auth_request *req, userdb_iter_callback_t *callback, void *context); void auth_lua_userdb_iterate_next(struct userdb_iterate_context *ctx); int auth_lua_userdb_iterate_deinit(struct userdb_iterate_context *ctx); #endif dovecot-2.3.21.1/src/auth/test-auth.h0000644000000000000000000000106514656633576014136 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #ifndef TEST_AUTH_H #define TEST_AUTH_H 1 #define AUTH_REQUEST_FIELDS_CONST #include "lib.h" #include "test-common.h" struct auth_passdb; extern struct auth_passdb_settings mock_passdb_set; void test_auth_request_var_expand(void); void test_auth_request_fields(void); void test_db_dict_parse_cache_key(void); void test_username_filter(void); void test_db_lua(void); struct auth_passdb *passdb_mock(void); void passdb_mock_mod_init(void); void passdb_mock_mod_deinit(void); #endif dovecot-2.3.21.1/src/auth/passdb-dict.c0000644000000000000000000001175414656633576014416 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "passdb.h" #include "array.h" #include "str.h" #include "var-expand.h" #include "dict.h" #include "password-scheme.h" #include "auth-cache.h" #include "db-dict.h" #include struct dict_passdb_module { struct passdb_module module; struct dict_connection *conn; }; struct passdb_dict_request { struct auth_request *auth_request; union { verify_plain_callback_t *verify_plain; lookup_credentials_callback_t *lookup_credentials; } callback; }; static int dict_query_save_results(struct auth_request *auth_request, struct dict_connection *conn, struct db_dict_value_iter *iter) { const char *key, *value, *error; while (db_dict_value_iter_next(iter, &key, &value)) { if (value != NULL) { auth_request_set_field(auth_request, key, value, conn->set.default_pass_scheme); } } if (db_dict_value_iter_deinit(&iter, &error) < 0) { e_error(authdb_event(auth_request), "%s", error); return -1; } return 0; } static enum passdb_result passdb_dict_lookup_key(struct auth_request *auth_request, struct dict_passdb_module *module) { struct db_dict_value_iter *iter; int ret; ret = db_dict_value_iter_init(module->conn, auth_request, &module->conn->set.passdb_fields, &module->conn->set.parsed_passdb_objects, &iter); if (ret < 0) return PASSDB_RESULT_INTERNAL_FAILURE; else if (ret == 0) { auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB); return PASSDB_RESULT_USER_UNKNOWN; } else { if (dict_query_save_results(auth_request, module->conn, iter) < 0) return PASSDB_RESULT_INTERNAL_FAILURE; if (auth_request->passdb_password == NULL && !auth_fields_exists(auth_request->fields.extra_fields, "nopassword")) { return auth_request_password_missing(auth_request); } else { return PASSDB_RESULT_OK; } } } static void passdb_dict_lookup_pass(struct passdb_dict_request *dict_request) { struct auth_request *auth_request = dict_request->auth_request; struct passdb_module *_module = auth_request->passdb->passdb; struct dict_passdb_module *module = (struct dict_passdb_module *)_module; const char *password = NULL, *scheme = NULL; enum passdb_result passdb_result; if (array_count(&module->conn->set.passdb_fields) == 0 && array_count(&module->conn->set.parsed_passdb_objects) == 0) { e_error(authdb_event(auth_request), "No passdb_objects or passdb_fields specified"); passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; } else { passdb_result = passdb_dict_lookup_key(auth_request, module); } if (passdb_result == PASSDB_RESULT_OK) { /* passdb_password may change on the way, so we'll need to strdup. */ password = t_strdup(auth_request->passdb_password); scheme = password_get_scheme(&password); /* auth_request_set_field() sets scheme */ i_assert(password == NULL || scheme != NULL); } if (auth_request->wanted_credentials_scheme != NULL) { passdb_handle_credentials(passdb_result, password, scheme, dict_request->callback.lookup_credentials, auth_request); } else { if (password != NULL) { passdb_result = auth_request_password_verify(auth_request, auth_request->mech_password, password, scheme, AUTH_SUBSYS_DB); } dict_request->callback.verify_plain(passdb_result, auth_request); } } static void dict_verify_plain(struct auth_request *request, const char *password ATTR_UNUSED, verify_plain_callback_t *callback) { struct passdb_dict_request *dict_request; dict_request = p_new(request->pool, struct passdb_dict_request, 1); dict_request->auth_request = request; dict_request->callback.verify_plain = callback; passdb_dict_lookup_pass(dict_request); } static void dict_lookup_credentials(struct auth_request *request, lookup_credentials_callback_t *callback) { struct passdb_dict_request *dict_request; dict_request = p_new(request->pool, struct passdb_dict_request, 1); dict_request->auth_request = request; dict_request->callback.lookup_credentials = callback; passdb_dict_lookup_pass(dict_request); } static struct passdb_module * passdb_dict_preinit(pool_t pool, const char *args) { struct dict_passdb_module *module; struct dict_connection *conn; module = p_new(pool, struct dict_passdb_module, 1); module->conn = conn = db_dict_init(args); module->module.blocking = TRUE; module->module.default_cache_key = auth_cache_parse_key(pool, db_dict_parse_cache_key(&conn->set.keys, &conn->set.passdb_fields, &conn->set.parsed_passdb_objects)); module->module.default_pass_scheme = conn->set.default_pass_scheme; return &module->module; } static void passdb_dict_deinit(struct passdb_module *_module) { struct dict_passdb_module *module = (struct dict_passdb_module *)_module; db_dict_unref(&module->conn); } struct passdb_module_interface passdb_dict = { "dict", passdb_dict_preinit, NULL, passdb_dict_deinit, dict_verify_plain, dict_lookup_credentials, NULL }; dovecot-2.3.21.1/src/auth/auth-master-connection.h0000644000000000000000000000224414656633576016607 00000000000000#ifndef AUTH_MASTER_CONNECTION_H #define AUTH_MASTER_CONNECTION_H struct stat; struct auth_stream_reply; struct auth_master_connection { struct auth_master_connection *prev, *next; struct auth *auth; struct event *event; int refcount; struct timeval create_time, handshake_time; int fd; char *path; struct istream *input; struct ostream *output; struct io *io; struct master_list_iter_ctx *iter_ctx; /* If non-zero, allow only USER lookups whose returned uid matches this uid. Don't allow LIST/PASS lookups. */ uid_t userdb_restricted_uid; bool version_received:1; bool destroyed:1; bool userdb_only:1; }; struct auth_master_connection * auth_master_connection_create(struct auth *auth, int fd, const char *path, const struct stat *socket_st, bool userdb_only) ATTR_NULL(4); void auth_master_connection_destroy(struct auth_master_connection **conn); void auth_master_connection_ref(struct auth_master_connection *conn); void auth_master_connection_unref(struct auth_master_connection **conn); void auth_master_request_callback(const char *reply, struct auth_master_connection *conn); void auth_master_connections_destroy_all(void); #endif dovecot-2.3.21.1/src/auth/test-libpassword.c0000644000000000000000000001243414656633576015523 00000000000000/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ #include "test-lib.h" #include "password-scheme.h" #ifdef HAVE_LIBSODIUM #include #endif static struct { const char *scheme_generated; const char *scheme_detected; } known_non_aliases[] = { { "MD5", "DES-CRYPT" }, { "MD5-CRYPT", "DES-CRYPT" }, { "ARGON2ID", "ARGON2I" }, }; /* some algorithms are detected as something other, because they are compatible but not considered aliases by dovecot. treat those here to avoid false errors. */ static bool schemes_are_known_non_alias(const char *generated, const char *detected) { for(size_t i = 0; i < N_ELEMENTS(known_non_aliases); i++) { if (strcmp(known_non_aliases[i].scheme_generated, generated) == 0 && strcmp(known_non_aliases[i].scheme_detected, detected) == 0) return TRUE; } return FALSE; } static void test_password_scheme(const char *scheme, const char *crypted, const char *plaintext) { struct password_generate_params params = { .user = "testuser1", .rounds = 0, }; const unsigned char *raw_password; size_t siz; const char *error, *scheme2; test_begin(t_strdup_printf("password scheme(%s)", scheme)); test_assert(strcmp(password_get_scheme(&crypted), scheme) == 0); test_assert(password_decode(crypted, scheme, &raw_password, &siz, &error) == 1); test_assert(password_verify(plaintext, ¶ms, scheme, raw_password, siz, &error) == 1); test_assert(password_generate_encoded(plaintext, ¶ms, scheme, &crypted)); crypted = t_strdup_printf("{%s}%s", scheme, crypted); test_assert(strcmp(password_get_scheme(&crypted), scheme) == 0); test_assert(password_decode(crypted, scheme, &raw_password, &siz, &error) == 1); test_assert(password_verify(plaintext, ¶ms, scheme, raw_password, siz, &error) == 1); scheme2 = password_scheme_detect(plaintext, crypted, ¶ms); test_assert(scheme2 != NULL && (password_scheme_is_alias(scheme, scheme2) || schemes_are_known_non_alias(scheme, scheme2))); test_end(); } static void test_password_failures(void) { const char *scheme = "PLAIN"; const char *crypted = "{PLAIN}invalid"; const char *plaintext = "test"; struct password_generate_params params = { .user = "testuser1", .rounds = 0, }; const unsigned char *raw_password; size_t siz; const char *error; test_begin("password scheme failures"); /* wrong password */ test_assert(strcmp(password_get_scheme(&crypted), scheme) == 0); test_assert(password_decode(crypted, scheme, &raw_password, &siz, &error) == 1); test_assert(password_verify(plaintext, ¶ms, scheme, raw_password, siz, &error) == 0); /* unknown scheme */ crypted = "{INVALID}invalid"; scheme = password_get_scheme(&crypted); test_assert(password_decode(crypted, scheme, &raw_password, &siz, &error) == 0); /* crypt with empty value */ test_assert(password_verify(plaintext, ¶ms, "CRYPT", NULL, 0, &error) == 0); test_end(); } static void test_password_schemes(void) { test_password_scheme("PLAIN", "{PLAIN}test", "test"); test_password_scheme("CRYPT", "{CRYPT}//EsnG9FLTKjo", "test"); test_password_scheme("PLAIN-MD4", "{PLAIN-MD4}db346d691d7acc4dc2625db19f9e3f52", "test"); test_password_scheme("MD5", "{MD5}$1$wmyrgRuV$kImF6.9MAFQNHe23kq5vI/", "test"); test_password_scheme("SHA1", "{SHA1}qUqP5cyxm6YcTAhz05Hph5gvu9M=", "test"); test_password_scheme("SMD5", "{SMD5}JTu1KRwptKZJg/RLd+6Vn5GUd0M=", "test"); test_password_scheme("LDAP-MD5", "{LDAP-MD5}CY9rzUYh03PK3k6DJie09g==", "test"); test_password_scheme("SHA256", "{SHA256}n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=", "test"); test_password_scheme("SHA512", "{SHA512}7iaw3Ur350mqGo7jwQrpkj9hiYB3Lkc/iBml1JQODbJ6wYX4oOHV+E+IvIh/1nsUNzLDBMxfqa2Ob1f1ACio/w==", "test"); test_password_scheme("SSHA", "{SSHA}H/zrDv8FXUu1JmwvVYijfrYEF34jVZcO", "test"); test_password_scheme("MD5-CRYPT", "{MD5-CRYPT}$1$GgvxyNz8$OjZhLh4P.gF1lxYEbLZ3e/", "test"); test_password_scheme("OTP", "{OTP}sha1 1024 ae6b49aa481f7233 f69fc7f98b8fbf54", "test"); test_password_scheme("PBKDF2", "{PBKDF2}$1$bUnT4Pl7yFtYX0KU$5000$50a83cafdc517b9f46519415e53c6a858908680a", "test"); test_password_scheme("CRAM-MD5", "{CRAM-MD5}e02d374fde0dc75a17a557039a3a5338c7743304777dccd376f332bee68d2cf6", "test"); test_password_scheme("DIGEST-MD5", "{DIGEST-MD5}77c1a8c437c9b08ba2f460fe5d58db5d", "test"); test_password_scheme("SCRAM-SHA-1", "{SCRAM-SHA-1}4096,GetyLXdBuHzf1FWf8SLz2Q==,NA/OqmF4hhrsrB9KR7po+dliTGM=,QBiURvQaE6H6qYTmeghDHLANBFQ=", "test"); test_password_scheme("SCRAM-SHA-256", "{SCRAM-SHA-256}4096,LfNGSFqiFykEZ1xDAYlnKQ==," "HACNf9CII7cMz3XjRy/Oh3Ae2LHApoDyNw74d3YtFws=," "AQH0j7Hf8J12g8eNBadvzlNB2am3PxgNwFCFd3RxEaw=", "test"); test_password_scheme("BLF-CRYPT", "{BLF-CRYPT}$2y$05$11ipvo5dR6CwkzwmhwM26OXgzXwhV2PyPuLV.Qi31ILcRcThQpEiW", "test"); #ifdef HAVE_LIBSODIUM test_password_scheme("ARGON2I", "{ARGON2I}$argon2i$v=19$m=32768,t=4,p=1$f2iuP4aUeNMrgu34fhOkkg$1XSZZMWlIs0zmE+snlUIcLADO3GXbA2O/hsQmmc317k", "test"); #ifdef crypto_pwhash_ALG_ARGON2ID13 test_password_scheme("ARGON2ID", "{ARGON2ID}$argon2id$v=19$m=65536,t=3,p=1$vBb99oJ12p3WAdYlaMHz1A$jtFOtbo/sYV9OSlTxDo/nVNq3uArHd5GJSEx0ty85Cc", "test"); #endif #endif } int main(void) { static void (*const test_functions[])(void) = { test_password_schemes, test_password_failures, NULL }; password_schemes_init(); return test_run(test_functions); } dovecot-2.3.21.1/src/auth/mech-dovecot-token.c0000644000000000000000000000466214656633576015714 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ /* Used internally by Dovecot processes to authenticate against each others (e.g. imap to imap-urlauth). See auth-token.c */ #include "auth-common.h" #include "mech.h" #include "safe-memset.h" #include "auth-token.h" static void mech_dovecot_token_auth_continue(struct auth_request *request, const unsigned char *data, size_t data_size) { const char *session_id, *username, *pid, *service, *error; char *auth_token; size_t i, len; int count; /* service \0 pid \0 username \0 session_id \0 auth_token */ service = (const char *) data; session_id = username = pid = auth_token = NULL; count = 0; for (i = 0; i < data_size; i++) { if (data[i] == '\0') { count++; i++; if (count == 1) pid = (const char *)data + i; else if (count == 2) username = (const char *)data + i; else if (count == 3) session_id = (const char *)data + i; else if (count == 4) { len = data_size - i; auth_token = p_strndup(unsafe_data_stack_pool, data+i, len); } else break; } } if (count != 4) { /* invalid input */ e_info(request->mech_event, "invalid input"); auth_request_fail(request); } else if (!auth_request_set_username(request, username, &error)) { /* invalid username */ e_info(request->mech_event, "%s", error); auth_request_fail(request); } else { const char *valid_token = auth_token_get(service, pid, request->fields.user, session_id); if (auth_token != NULL && str_equals_timing_almost_safe(auth_token, valid_token)) { request->passdb_success = TRUE; auth_request_set_field(request, "userdb_client_service", service, ""); auth_request_success(request, NULL, 0); } else { auth_request_fail(request); } } /* make sure it's cleared */ if (auth_token != NULL) safe_memset(auth_token, 0, strlen(auth_token)); } static struct auth_request *mech_dovecot_token_auth_new(void) { struct auth_request *request; pool_t pool; pool = pool_alloconly_create(MEMPOOL_GROWING"dovecot_token_auth_request", 512); request = p_new(pool, struct auth_request, 1); request->pool = pool; return request; } const struct mech_module mech_dovecot_token = { "DOVECOT-TOKEN", .flags = MECH_SEC_PRIVATE | MECH_SEC_ALLOW_NULS, .passdb_need = MECH_PASSDB_NEED_NOTHING, mech_dovecot_token_auth_new, mech_generic_auth_initial, mech_dovecot_token_auth_continue, mech_generic_auth_free }; dovecot-2.3.21.1/src/auth/auth-penalty.c0000644000000000000000000001072014656633576014624 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "net.h" #include "crc32.h" #include "master-service.h" #include "anvil-client.h" #include "auth-request.h" #include "auth-penalty.h" #include /* We don't want IPv6 hosts being able to flood our penalty tracking with tons of different IPs. */ #define PENALTY_IPV6_MASK_BITS 48 struct auth_penalty_request { struct auth_request *auth_request; struct anvil_client *client; auth_penalty_callback_t *callback; }; struct auth_penalty { struct anvil_client *client; bool disabled:1; }; struct auth_penalty *auth_penalty_init(const char *path) { struct auth_penalty *penalty; penalty = i_new(struct auth_penalty, 1); penalty->client = anvil_client_init(path, NULL, ANVIL_CLIENT_FLAG_HIDE_ENOENT); if (anvil_client_connect(penalty->client, TRUE) < 0) penalty->disabled = TRUE; else { anvil_client_cmd(penalty->client, t_strdup_printf( "PENALTY-SET-EXPIRE-SECS\t%u", AUTH_PENALTY_TIMEOUT)); } return penalty; } void auth_penalty_deinit(struct auth_penalty **_penalty) { struct auth_penalty *penalty = *_penalty; *_penalty = NULL; anvil_client_deinit(&penalty->client); i_free(penalty); } unsigned int auth_penalty_to_secs(unsigned int penalty) { unsigned int i, secs = AUTH_PENALTY_INIT_SECS; for (i = 0; i < penalty; i++) secs *= 2; return secs < AUTH_PENALTY_MAX_SECS ? secs : AUTH_PENALTY_MAX_SECS; } static void auth_penalty_anvil_callback(const char *reply, void *context) { struct auth_penalty_request *request = context; unsigned int penalty = 0; unsigned long last_penalty = 0; unsigned int secs, drop_penalty; if (reply == NULL) { /* internal failure. */ if (!anvil_client_is_connected(request->client)) { /* we probably didn't have permissions to reconnect back to anvil. need to restart ourself. */ master_service_stop(master_service); } } else if (sscanf(reply, "%u %lu", &penalty, &last_penalty) != 2) { e_error(request->auth_request->event, "Invalid PENALTY-GET reply: %s", reply); } else { if ((time_t)last_penalty > ioloop_time) { /* time moved backwards? */ last_penalty = ioloop_time; } /* update penalty. */ drop_penalty = AUTH_PENALTY_MAX_PENALTY; while (penalty > 0) { secs = auth_penalty_to_secs(drop_penalty); if (ioloop_time - last_penalty < secs) break; drop_penalty--; penalty--; } } request->callback(penalty, request->auth_request); auth_request_unref(&request->auth_request); i_free(request); } static const char * auth_penalty_get_ident(struct auth_request *auth_request) { struct ip_addr ip; ip = auth_request->fields.remote_ip; if (IPADDR_IS_V6(&ip)) { memset(ip.u.ip6.s6_addr + PENALTY_IPV6_MASK_BITS/CHAR_BIT, 0, sizeof(ip.u.ip6.s6_addr) - PENALTY_IPV6_MASK_BITS/CHAR_BIT); } return net_ip2addr(&ip); } void auth_penalty_lookup(struct auth_penalty *penalty, struct auth_request *auth_request, auth_penalty_callback_t *callback) { struct auth_penalty_request *request; const char *ident; ident = auth_penalty_get_ident(auth_request); if (penalty->disabled || ident == NULL || auth_request->fields.no_penalty) { callback(0, auth_request); return; } request = i_new(struct auth_penalty_request, 1); request->auth_request = auth_request; request->client = penalty->client; request->callback = callback; auth_request_ref(auth_request); T_BEGIN { anvil_client_query(penalty->client, t_strdup_printf("PENALTY-GET\t%s", ident), auth_penalty_anvil_callback, request); } T_END; } static unsigned int get_userpass_checksum(struct auth_request *auth_request) { return auth_request->mech_password == NULL ? 0 : crc32_str_more(crc32_str(auth_request->mech_password), auth_request->fields.user); } void auth_penalty_update(struct auth_penalty *penalty, struct auth_request *auth_request, unsigned int value) { const char *ident; ident = auth_penalty_get_ident(auth_request); if (penalty->disabled || ident == NULL || auth_request->fields.no_penalty) return; if (value > AUTH_PENALTY_MAX_PENALTY) { /* even if the actual value doesn't change, the last_change timestamp does. */ value = AUTH_PENALTY_MAX_PENALTY; } T_BEGIN { const char *cmd; unsigned int checksum; checksum = value == 0 ? 0 : get_userpass_checksum(auth_request); cmd = t_strdup_printf("PENALTY-INC\t%s\t%u\t%u", ident, checksum, value); anvil_client_cmd(penalty->client, cmd); } T_END; } dovecot-2.3.21.1/src/auth/password-scheme-crypt.c0000644000000000000000000001420514656633576016456 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "mycrypt.h" #include "password-scheme.h" #include "crypt-blowfish.h" #include "randgen.h" /* Lengths and limits for some crypt() algorithms. */ #define CRYPT_BLF_ROUNDS_DEFAULT 5 #define CRYPT_BLF_ROUNDS_MIN 4 #define CRYPT_BLF_ROUNDS_MAX 31 #define CRYPT_BLF_SALT_LEN 16 /* raw salt */ #define CRYPT_BLF_PREFIX_LEN (7+22+1) /* $2.$nn$ + salt */ #define CRYPT_BLF_BUFFER_LEN 128 #define CRYPT_BLF_PREFIX "$2y" #define CRYPT_SHA2_ROUNDS_DEFAULT 5000 #define CRYPT_SHA2_ROUNDS_MIN 1000 #define CRYPT_SHA2_ROUNDS_MAX 999999999 #define CRYPT_SHA2_SALT_LEN 16 static void crypt_generate_des(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r) { #define CRYPT_SALT_LEN 2 const char *password, *salt; salt = password_generate_salt(CRYPT_SALT_LEN); password = t_strdup(mycrypt(plaintext, salt)); *raw_password_r = (const unsigned char *)password; *size_r = strlen(password); } static void crypt_generate_blowfish(const char *plaintext, const struct password_generate_params *params, const unsigned char **raw_password_r, size_t *size_r) { char salt[CRYPT_BLF_SALT_LEN]; char password[CRYPT_BLF_BUFFER_LEN]; char magic_salt[CRYPT_BLF_PREFIX_LEN]; unsigned int rounds = params->rounds; if (rounds == 0) rounds = CRYPT_BLF_ROUNDS_DEFAULT; else if (rounds < CRYPT_BLF_ROUNDS_MIN) rounds = CRYPT_BLF_ROUNDS_MIN; else if (rounds > CRYPT_BLF_ROUNDS_MAX) rounds = CRYPT_BLF_ROUNDS_MAX; random_fill(salt, CRYPT_BLF_SALT_LEN); if (crypt_gensalt_blowfish_rn(CRYPT_BLF_PREFIX, rounds, salt, CRYPT_BLF_SALT_LEN, magic_salt, CRYPT_BLF_PREFIX_LEN) == NULL) i_fatal("crypt_gensalt_blowfish_rn failed: %m"); if (crypt_blowfish_rn(plaintext, magic_salt, password, CRYPT_BLF_BUFFER_LEN) == NULL) i_fatal("crypt_blowfish_rn failed: %m"); *raw_password_r = (const unsigned char *)t_strdup(password); *size_r = strlen(password); } static int crypt_verify_blowfish(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char *raw_password, size_t size, const char **error_r) { const char *password; const char *salt; char crypted[CRYPT_BLF_BUFFER_LEN]; if (size == 0) { /* the default mycrypt() handler would return match */ return 0; } password = t_strndup(raw_password, size); if (size < CRYPT_BLF_PREFIX_LEN || !str_begins(password, "$2") || password[2] < 'a' || password[2] > 'z' || password[3] != '$') { *error_r = "Password is not blowfish password"; return -1; } salt = t_strndup(password, CRYPT_BLF_PREFIX_LEN); if (crypt_blowfish_rn(plaintext, salt, crypted, CRYPT_BLF_BUFFER_LEN) == NULL) { /* really shouldn't happen unless the system is broken */ *error_r = t_strdup_printf("crypt_blowfish_rn failed: %m"); return -1; } return strcmp(crypted, password) == 0 ? 1 : 0; } static void crypt_generate_sha256(const char *plaintext, const struct password_generate_params *params, const unsigned char **raw_password_r, size_t *size_r) { const char *password, *salt, *magic_salt; unsigned int rounds = params->rounds; if (rounds == 0) rounds = CRYPT_SHA2_ROUNDS_DEFAULT; else if (rounds < CRYPT_SHA2_ROUNDS_MIN) rounds = CRYPT_SHA2_ROUNDS_MIN; else if (rounds > CRYPT_SHA2_ROUNDS_MAX) rounds = CRYPT_SHA2_ROUNDS_MAX; salt = password_generate_salt(CRYPT_SHA2_SALT_LEN); if (rounds == CRYPT_SHA2_ROUNDS_DEFAULT) magic_salt = t_strdup_printf("$5$%s", salt); else magic_salt = t_strdup_printf("$5$rounds=%u$%s", rounds, salt); password = t_strdup(mycrypt(plaintext, magic_salt)); *raw_password_r = (const unsigned char *)password; *size_r = strlen(password); } static void crypt_generate_sha512(const char *plaintext, const struct password_generate_params *params, const unsigned char **raw_password_r, size_t *size_r) { const char *password, *salt, *magic_salt; unsigned int rounds = params->rounds; if (rounds == 0) rounds = CRYPT_SHA2_ROUNDS_DEFAULT; else if (rounds < CRYPT_SHA2_ROUNDS_MIN) rounds = CRYPT_SHA2_ROUNDS_MIN; else if (rounds > CRYPT_SHA2_ROUNDS_MAX) rounds = CRYPT_SHA2_ROUNDS_MAX; salt = password_generate_salt(CRYPT_SHA2_SALT_LEN); if (rounds == CRYPT_SHA2_ROUNDS_DEFAULT) magic_salt = t_strdup_printf("$6$%s", salt); else magic_salt = t_strdup_printf("$6$rounds=%u$%s", rounds, salt); password = t_strdup(mycrypt(plaintext, magic_salt)); *raw_password_r = (const unsigned char *)password; *size_r = strlen(password); } /* keep in sync with the crypt_schemes struct below */ static const struct { const char *key; const char *salt; const char *expected; } sample[] = { { "08/15!test~4711", "JB", "JBOZ0DgmtucwE" }, { "08/15!test~4711", "$5$rounds=1000$0123456789abcdef", "$5$rounds=1000$0123456789abcdef$K/DksR0DT01hGc8g/kt" "9McEgrbFMKi9qrb1jehe7hn4" }, { "08/15!test~4711", "$6$rounds=1000$0123456789abcdef", "$6$rounds=1000$0123456789abcdef$ZIAd5WqfyLkpvsVCVUU1GrvqaZTq" "vhJoouxdSqJO71l9Ld3tVrfOatEjarhghvEYADkq//LpDnTeO90tcbtHR1" } }; /* keep in sync with the sample struct above */ static const struct password_scheme crypt_schemes[] = { { "DES-CRYPT", PW_ENCODING_NONE, 0, crypt_verify, crypt_generate_des }, { "SHA256-CRYPT", PW_ENCODING_NONE, 0, crypt_verify, crypt_generate_sha256 }, { "SHA512-CRYPT", PW_ENCODING_NONE, 0, crypt_verify, crypt_generate_sha512 } }; static const struct password_scheme blf_crypt_scheme = { "BLF-CRYPT", PW_ENCODING_NONE, 0, crypt_verify_blowfish, crypt_generate_blowfish }; static const struct password_scheme default_crypt_scheme = { "CRYPT", PW_ENCODING_NONE, 0, crypt_verify, crypt_generate_blowfish }; void password_scheme_register_crypt(void) { unsigned int i; const char *crypted; i_assert(N_ELEMENTS(crypt_schemes) == N_ELEMENTS(sample)); for (i = 0; i < N_ELEMENTS(crypt_schemes); i++) { crypted = mycrypt(sample[i].key, sample[i].salt); if (crypted != NULL && (strcmp(crypted, sample[i].expected) == 0)) password_scheme_register(&crypt_schemes[i]); } password_scheme_register(&blf_crypt_scheme); password_scheme_register(&default_crypt_scheme); } dovecot-2.3.21.1/src/auth/auth-policy.c0000644000000000000000000004361214656633576014455 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "net.h" #include "str.h" #include "istream.h" #include "ioloop.h" #include "base64.h" #include "hex-binary.h" #include "hash-method.h" #include "http-url.h" #include "http-client.h" #include "json-parser.h" #include "master-service.h" #include "master-service-ssl-settings.h" #include "auth-request.h" #include "auth-penalty.h" #include "auth-settings.h" #include "auth-policy.h" #include "auth-common.h" #include "iostream-ssl.h" #define AUTH_POLICY_DNS_SOCKET_PATH "dns-client" static struct http_client_settings http_client_set = { .dns_client_socket_path = AUTH_POLICY_DNS_SOCKET_PATH, .max_connect_attempts = 1, .max_idle_time_msecs = 10000, .max_parallel_connections = 100, .debug = 0, .user_agent = "dovecot/auth-policy-client" }; static char *auth_policy_json_template; static struct http_client *http_client; struct policy_lookup_ctx { pool_t pool; string_t *json; struct auth_request *request; struct http_client_request *http_request; struct json_parser *parser; const struct auth_settings *set; const char *url; bool expect_result; int result; const char *message; auth_policy_callback_t callback; void *callback_context; struct istream *payload; struct io *io; struct event *event; enum { POLICY_RESULT = 0, POLICY_RESULT_VALUE_STATUS, POLICY_RESULT_VALUE_MESSAGE } parse_state; bool parse_error; }; struct policy_template_keyvalue { const char *key; const char *value; }; static int auth_policy_attribute_comparator(const struct policy_template_keyvalue *a, const struct policy_template_keyvalue *b) { return strcmp(a->key, b->key); } static int auth_policy_strptrcmp(const char *a0, const char *a1, const char *b0, const char *b1) { i_assert(a0 <= a1 && b0 <= b1); return memcmp(a0, b0, I_MIN((a1-a0),(b1-b0))); } static void auth_policy_open_key(const char *key, string_t *template) { const char *ptr; while((ptr = strchr(key, '/')) != NULL) { str_append_c(template,'"'); json_append_escaped(template, t_strndup(key, (ptr-key))); str_append_c(template,'"'); str_append_c(template,':'); str_append_c(template,'{'); key = ptr+1; } } static void auth_policy_close_key(const char *key, string_t *template) { while((key = strchr(key, '/')) != NULL) { str_append_c(template,'}'); key++; } } static void auth_policy_open_and_close_to_key(const char *fromkey, const char *tokey, string_t *template) { const char *fptr,*tptr,*fdash,*tdash; fptr = strrchr(fromkey, '/'); tptr = strrchr(tokey, '/'); if (fptr == NULL && tptr == NULL) return; /* nothing to do */ if (fptr == NULL && tptr != NULL) { auth_policy_open_key(tokey, template); return; } if (fptr != NULL && tptr == NULL) { str_truncate(template, str_len(template)-1); auth_policy_close_key(fromkey, template); str_append_c(template, ','); return; } if (auth_policy_strptrcmp(fromkey, fptr, tokey, tptr) == 0) { /* nothing to do, again */ return; } fptr = fromkey; tptr = tokey; while (fptr != NULL && tptr != NULL) { fdash = strchr(fptr, '/'); tdash = strchr(tptr, '/'); if (fdash == NULL) { auth_policy_open_key(tptr, template); break; } if (tdash == NULL) { str_truncate(template, str_len(template)-1); auth_policy_close_key(fptr, template); str_append_c(template, ','); break; } if (auth_policy_strptrcmp(fptr, fdash, tptr, tdash) != 0) { str_truncate(template, str_len(template)-1); auth_policy_close_key(fptr, template); str_append_c(template, ','); auth_policy_open_key(tptr, template); break; } fptr = fdash+1; tptr = tdash+1; } } void auth_policy_init(void) { const struct master_service_ssl_settings *master_ssl_set = master_service_ssl_settings_get(master_service); struct ssl_iostream_settings ssl_set; i_zero(&ssl_set); http_client_set.request_absolute_timeout_msecs = global_auth_settings->policy_server_timeout_msecs; if (global_auth_settings->debug) http_client_set.debug = 1; master_service_ssl_client_settings_to_iostream_set(master_ssl_set, pool_datastack_create(), &ssl_set); http_client_set.ssl = &ssl_set; http_client_set.event_parent = auth_event; http_client = http_client_init(&http_client_set); /* prepare template */ ARRAY(struct policy_template_keyvalue) attribute_pairs; const struct policy_template_keyvalue *kvptr; string_t *template = t_str_new(64); const char **ptr; const char *key = NULL; const char **list = t_strsplit_spaces(global_auth_settings->policy_request_attributes, "= "); t_array_init(&attribute_pairs, 8); for(ptr = list; *ptr != NULL; ptr++) { struct policy_template_keyvalue pair; if (key == NULL) { key = *ptr; } else { pair.key = key; pair.value = *ptr; key = NULL; array_push_back(&attribute_pairs, &pair); } } if (key != NULL) { i_fatal("auth_policy_request_attributes contains invalid value"); } /* then we sort it */ array_sort(&attribute_pairs, auth_policy_attribute_comparator); /* and build a template string */ const char *prevkey = ""; array_foreach(&attribute_pairs, kvptr) { const char *kptr = strchr(kvptr->key, '/'); auth_policy_open_and_close_to_key(prevkey, kvptr->key, template); str_append_c(template,'"'); json_append_escaped(template, (kptr != NULL?kptr+1:kvptr->key)); str_append_c(template,'"'); str_append_c(template,':'); str_append_c(template,'"'); str_append(template,kvptr->value); str_append_c(template,'"'); str_append_c(template,','); prevkey = kvptr->key; } auth_policy_open_and_close_to_key(prevkey, "", template); str_truncate(template, str_len(template)-1); auth_policy_json_template = i_strdup(str_c(template)); if (global_auth_settings->policy_log_only) i_warning("auth-policy: Currently in log-only mode. Ignoring " "tarpit and disconnect instructions from policy server"); } void auth_policy_deinit(void) { if (http_client != NULL) http_client_deinit(&http_client); i_free(auth_policy_json_template); } static void auth_policy_log_result(struct policy_lookup_ctx *context) { const char *action; struct event_passthrough *e = event_create_passthrough(context->event)-> set_name("auth_policy_request_finished"); if (!context->expect_result) { e_debug(e->event(), "Policy report action finished"); return; } int result = context->result; e->add_int("policy_response", context->result); if (result < 0) action = "drop connection"; else if (context->result == 0) action = "continue"; else action = t_strdup_printf("tarpit %d second(s)", context->result); if (context->request->set->policy_log_only && result != 0) e_info(e->event(), "Policy check action '%s' ignored", action); else if (result != 0) e_info(e->event(), "Policy check action is %s", action); else e_debug(e->event(), "Policy check action is %s", action); } static void auth_policy_finish(struct policy_lookup_ctx *context) { if (context->parser != NULL) { const char *error ATTR_UNUSED; (void)json_parser_deinit(&context->parser, &error); } http_client_request_abort(&context->http_request); if (context->request != NULL) auth_request_unref(&context->request); event_unref(&context->event); pool_unref(&context->pool); } static void auth_policy_callback(struct policy_lookup_ctx *context) { if (context->callback != NULL) context->callback(context->result, context->callback_context); if (context->event != NULL) auth_policy_log_result(context); } static void auth_policy_parse_response(struct policy_lookup_ctx *context) { enum json_type type; const char *value; int ret; while((ret = json_parse_next(context->parser, &type, &value)) == 1) { if (context->parse_state == POLICY_RESULT) { if (type != JSON_TYPE_OBJECT_KEY) continue; else if (strcmp(value, "status") == 0) context->parse_state = POLICY_RESULT_VALUE_STATUS; else if (strcmp(value, "msg") == 0) context->parse_state = POLICY_RESULT_VALUE_MESSAGE; else continue; } else if (context->parse_state == POLICY_RESULT_VALUE_STATUS) { if (type != JSON_TYPE_NUMBER || str_to_int(value, &context->result) != 0) break; context->parse_state = POLICY_RESULT; } else if (context->parse_state == POLICY_RESULT_VALUE_MESSAGE) { if (type != JSON_TYPE_STRING) break; if (*value != '\0') context->message = p_strdup(context->pool, value); context->parse_state = POLICY_RESULT; } else { break; } } if (ret == 0 && !context->payload->eof) return; context->parse_error = TRUE; io_remove(&context->io); if (context->payload->stream_errno != 0) { e_error(context->event, "Error reading policy server result: %s", i_stream_get_error(context->payload)); } else if (ret == 0 && context->payload->eof) { e_error(context->event, "Policy server result was too short"); } else if (ret == 1) { e_error(context->event, "Policy server response was malformed"); } else { const char *error = "unknown"; if (json_parser_deinit(&context->parser, &error) != 0) e_error(context->event, "Policy server response JSON parse error: %s", error); else if (context->parse_state == POLICY_RESULT) context->parse_error = FALSE; } if (context->parse_error) { context->result = (context->set->policy_reject_on_fail ? -1 : 0); } context->request->policy_refusal = FALSE; if (context->result < 0) { if (context->message != NULL) { /* set message here */ e_debug(context->event, "Policy response %d with message: %s", context->result, context->message); auth_request_set_field(context->request, "reason", context->message, NULL); } context->request->policy_refusal = TRUE; } else { e_debug(context->event, "Policy response %d", context->result); } if (context->request->policy_refusal == TRUE && context->set->verbose == TRUE) { e_info(context->event, "Authentication failure due to policy server refusal%s%s", (context->message!=NULL?": ":""), (context->message!=NULL?context->message:"")); } auth_policy_callback(context); i_stream_unref(&context->payload); } static void auth_policy_process_response(const struct http_response *response, void *ctx) { struct policy_lookup_ctx *context = ctx; context->payload = response->payload; if ((response->status / 10) != 20) { e_error(context->event, "Policy server HTTP error: %s", http_response_get_message(response)); auth_policy_callback(context); return; } if (response->payload == NULL) { if (context->expect_result) e_error(context->event, "Policy server result was empty"); auth_policy_callback(context); return; } if (context->expect_result) { i_stream_ref(response->payload); context->io = io_add_istream(response->payload, auth_policy_parse_response, context); context->parser = json_parser_init(response->payload); auth_policy_parse_response(ctx); } else { auth_policy_callback(context); } } static void auth_policy_send_request(struct policy_lookup_ctx *context) { const char *error; struct http_url *url; auth_request_ref(context->request); if (http_url_parse(context->url, NULL, HTTP_URL_ALLOW_USERINFO_PART, context->pool, &url, &error) != 0) { e_error(context->event, "Could not parse url %s: %s", context->url, error); auth_policy_callback(context); auth_policy_finish(context); return; } context->http_request = http_client_request_url(http_client, "POST", url, auth_policy_process_response, (void*)context); http_client_request_set_destroy_callback(context->http_request, auth_policy_finish, context); http_client_request_add_header(context->http_request, "Content-Type", "application/json"); if (*context->set->policy_server_api_header != 0) { const char *ptr; if ((ptr = strstr(context->set->policy_server_api_header, ":")) != NULL) { const char *header = t_strcut(context->set->policy_server_api_header, ':'); http_client_request_add_header(context->http_request, header, ptr + 1); } else { http_client_request_add_header(context->http_request, "X-API-Key", context->set->policy_server_api_header); } } if (url->user != NULL) { /* allow empty password */ http_client_request_set_auth_simple(context->http_request, url->user, (url->password != NULL ? url->password : "")); } struct istream *is = i_stream_create_from_buffer(context->json); http_client_request_set_payload(context->http_request, is, FALSE); i_stream_unref(&is); http_client_request_submit(context->http_request); } static const char *auth_policy_escape_function(const char *string, const struct auth_request *auth_request ATTR_UNUSED) { string_t *tmp = t_str_new(64); json_append_escaped(tmp, string); return str_c(tmp); } static const struct var_expand_table *policy_get_var_expand_table(struct auth_request *auth_request, const char *hashed_password, const char *requested_username) { struct var_expand_table *table; unsigned int count = 2; table = auth_request_get_var_expand_table_full(auth_request, auth_request->fields.user, auth_policy_escape_function, &count); table[0].key = '\0'; table[0].long_key = "hashed_password"; table[0].value = hashed_password; table[1].key = '\0'; table[1].long_key = "requested_username"; table[1].value = requested_username; if (table[0].value != NULL) table[0].value = auth_policy_escape_function(table[0].value, auth_request); if (table[1].value != NULL) table[1].value = auth_policy_escape_function(table[1].value, auth_request); return table; } static void auth_policy_create_json(struct policy_lookup_ctx *context, const char *password, bool include_success) { const struct var_expand_table *var_table; context->json = str_new(context->pool, 64); unsigned char *ptr; const char *requested_username; const struct hash_method *digest = hash_method_lookup(context->set->policy_hash_mech); i_assert(digest != NULL); void *ctx = t_malloc_no0(digest->context_size); buffer_t *buffer = t_buffer_create(64); digest->init(ctx); digest->loop(ctx, context->set->policy_hash_nonce, strlen(context->set->policy_hash_nonce)); if (context->request->fields.requested_login_user != NULL) requested_username = context->request->fields.requested_login_user; else if (context->request->fields.user != NULL) requested_username = context->request->fields.user; else requested_username = ""; /* use +1 to make sure \0 gets included */ digest->loop(ctx, requested_username, strlen(requested_username)+1); if (password != NULL) digest->loop(ctx, password, strlen(password)); ptr = buffer_get_modifiable_data(buffer, NULL); digest->result(ctx, ptr); buffer_set_used_size(buffer, digest->digest_size); if (context->set->policy_hash_truncate > 0) { buffer_truncate_rshift_bits(buffer, context->set->policy_hash_truncate); } const char *hashed_password = binary_to_hex(buffer->data, buffer->used); str_append_c(context->json, '{'); var_table = policy_get_var_expand_table(context->request, hashed_password, requested_username); const char *error; if (auth_request_var_expand_with_table(context->json, auth_policy_json_template, context->request, var_table, auth_policy_escape_function, &error) <= 0) { e_error(context->event, "Failed to expand auth policy template: %s", error); } if (include_success) { str_append(context->json, ",\"success\":"); if (!context->request->failed && context->request->fields.successful && !context->request->internal_failure) str_append(context->json, "true"); else str_append(context->json, "false"); str_append(context->json, ",\"policy_reject\":"); str_append(context->json, context->request->policy_refusal ? "true" : "false"); } str_append(context->json, ",\"tls\":"); if (context->request->fields.secured == AUTH_REQUEST_SECURED_TLS) str_append(context->json, "true"); else str_append(context->json, "false"); str_append_c(context->json, '}'); e_debug(context->event, "Policy server request JSON: %s", str_c(context->json)); } static void auth_policy_url(struct policy_lookup_ctx *context, const char *command) { size_t len = strlen(context->set->policy_server_url); if (context->set->policy_server_url[len-1] == '&') context->url = p_strdup_printf(context->pool, "%scommand=%s", context->set->policy_server_url, command); else context->url = p_strdup_printf(context->pool, "%s?command=%s", context->set->policy_server_url, command); } static const char *auth_policy_get_prefix(struct auth_request *request) { string_t *str = t_str_new(256); auth_request_get_log_prefix(str, request, "policy"); return str_c(str); } void auth_policy_check(struct auth_request *request, const char *password, auth_policy_callback_t cb, void *context) { if (request->master != NULL || *(request->set->policy_server_url) == '\0') { cb(0, context); return; } pool_t pool = pool_alloconly_create("auth policy", 512); struct policy_lookup_ctx *ctx = p_new(pool, struct policy_lookup_ctx, 1); ctx->pool = pool; ctx->request = request; ctx->expect_result = TRUE; ctx->callback = cb; ctx->callback_context = context; ctx->set = request->set; ctx->event = event_create(request->event); event_add_str(ctx->event, "mode", "allow"); event_set_append_log_prefix(ctx->event, auth_policy_get_prefix(request)); auth_policy_url(ctx, "allow"); ctx->result = (ctx->set->policy_reject_on_fail ? -1 : 0); e_debug(ctx->event, "Policy request %s", ctx->url); T_BEGIN { auth_policy_create_json(ctx, password, FALSE); } T_END; auth_policy_send_request(ctx); } void auth_policy_report(struct auth_request *request) { if (request->master != NULL) return; if (*(request->set->policy_server_url) == '\0') return; pool_t pool = pool_alloconly_create("auth policy", 512); struct policy_lookup_ctx *ctx = p_new(pool, struct policy_lookup_ctx, 1); ctx->pool = pool; ctx->request = request; ctx->expect_result = FALSE; ctx->set = request->set; ctx->event = event_create(request->event); event_add_str(ctx->event, "mode", "report"); event_set_append_log_prefix(ctx->event, auth_policy_get_prefix(request)); auth_policy_url(ctx, "report"); e_debug(ctx->event, "Policy request %s", ctx->url); T_BEGIN { auth_policy_create_json(ctx, request->mech_password, TRUE); } T_END; auth_policy_send_request(ctx); } dovecot-2.3.21.1/src/auth/passdb-blocking.c0000644000000000000000000001137014656633576015255 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "str.h" #include "strescape.h" #include "auth-worker-server.h" #include "password-scheme.h" #include "passdb.h" #include "passdb-blocking.h" static void auth_worker_reply_parse_args(struct auth_request *request, const char *const *args) { if (**args != '\0') request->passdb_password = p_strdup(request->pool, *args); args++; if (*args != NULL) auth_request_set_fields(request, args, NULL); } enum passdb_result passdb_blocking_auth_worker_reply_parse(struct auth_request *request, const char *reply) { enum passdb_result ret; const char *const *args; args = t_strsplit_tabescaped(reply); if (strcmp(*args, "OK") == 0 && args[1] != NULL && args[2] != NULL) { /* OK \t user \t password [\t extra] */ if (args[1][0] != '\0') auth_request_set_field(request, "user", args[1], NULL); auth_worker_reply_parse_args(request, args + 2); return PASSDB_RESULT_OK; } if (strcmp(*args, "NEXT") == 0 && args[1] != NULL) { /* NEXT \t user [\t extra] */ if (args[1][0] != '\0') auth_request_set_field(request, "user", args[1], NULL); auth_worker_reply_parse_args(request, args + 1); return PASSDB_RESULT_NEXT; } if (strcmp(*args, "FAIL") == 0 && args[1] != NULL) { int result; /* FAIL \t result [\t user \t password [\t extra]] */ if (str_to_int(args[1], &result) < 0) { /* shouldn't happen */ } else { ret = (enum passdb_result)result; if (ret == PASSDB_RESULT_OK) { /* shouldn't happen */ } else if (args[2] == NULL) { /* internal failure most likely */ return ret; } else if (args[3] != NULL) { if (*args[2] != '\0') { auth_request_set_field(request, "user", args[2], NULL); } auth_worker_reply_parse_args(request, args + 3); return ret; } } } e_error(authdb_event(request), "Received invalid reply from worker: %s", reply); return PASSDB_RESULT_INTERNAL_FAILURE; } static bool verify_plain_callback(struct auth_worker_connection *conn ATTR_UNUSED, const char *reply, void *context) { struct auth_request *request = context; enum passdb_result result; result = passdb_blocking_auth_worker_reply_parse(request, reply); auth_request_verify_plain_callback(result, request); auth_request_unref(&request); return TRUE; } void passdb_blocking_verify_plain(struct auth_request *request) { string_t *str; str = t_str_new(128); str_printfa(str, "PASSV\t%u\t", request->passdb->passdb->id); str_append_tabescaped(str, request->mech_password); str_append_c(str, '\t'); auth_request_export(request, str); auth_request_ref(request); auth_worker_call(request->pool, request->fields.user, str_c(str), verify_plain_callback, request); } static bool lookup_credentials_callback(struct auth_worker_connection *conn ATTR_UNUSED, const char *reply, void *context) { struct auth_request *request = context; enum passdb_result result; const char *password = NULL, *scheme = NULL; result = passdb_blocking_auth_worker_reply_parse(request, reply); if (result == PASSDB_RESULT_OK && request->passdb_password != NULL) { password = request->passdb_password; scheme = password_get_scheme(&password); if (scheme == NULL) { e_error(authdb_event(request), "Received reply from worker without " "password scheme"); result = PASSDB_RESULT_INTERNAL_FAILURE; } } passdb_handle_credentials(result, password, scheme, auth_request_lookup_credentials_callback, request); auth_request_unref(&request); return TRUE; } void passdb_blocking_lookup_credentials(struct auth_request *request) { string_t *str; str = t_str_new(128); str_printfa(str, "PASSL\t%u\t", request->passdb->passdb->id); str_append_tabescaped(str, request->wanted_credentials_scheme); str_append_c(str, '\t'); auth_request_export(request, str); auth_request_ref(request); auth_worker_call(request->pool, request->fields.user, str_c(str), lookup_credentials_callback, request); } static bool set_credentials_callback(struct auth_worker_connection *conn ATTR_UNUSED, const char *reply, void *context) { struct auth_request *request = context; bool success; success = strcmp(reply, "OK") == 0 || str_begins(reply, "OK\t"); request->private_callback.set_credentials(success, request); auth_request_unref(&request); return TRUE; } void passdb_blocking_set_credentials(struct auth_request *request, const char *new_credentials) { string_t *str; str = t_str_new(128); str_printfa(str, "SETCRED\t%u\t", request->passdb->passdb->id); str_append_tabescaped(str, new_credentials); str_append_c(str, '\t'); auth_request_export(request, str); auth_request_ref(request); auth_worker_call(request->pool, request->fields.user, str_c(str), set_credentials_callback, request); } dovecot-2.3.21.1/src/auth/test-db-dict.c0000644000000000000000000000275414656633576014504 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "auth-settings.h" #include "test-auth.h" #include "array.h" #include "db-dict.h" void test_db_dict_parse_cache_key(void) { struct db_dict_key keys[] = { { "key0", "%d and %n", NULL, NULL, 0 }, { "key1", "%{foo}%r%{bar}", NULL, NULL, 0 }, { "key2", "%{test1}/path", NULL, NULL, 0 }, { "key3", "path2/%{test2}", NULL, NULL, 0 }, { "key4", "%{plop}", NULL, NULL, 0 }, { "key5", "%{unused}", NULL, NULL, 0 } }; struct db_dict_field fields[] = { { "name1", "hello %{dict:key0} %l and %{dict:key1}" }, { "name2", "%{dict:key2} also %{extra} plus" } }; const struct db_dict_key *objects[] = { &keys[3], &keys[4] }; buffer_t keybuf, fieldbuf, objectbuf; ARRAY_TYPE(db_dict_key) keyarr; ARRAY_TYPE(db_dict_field) fieldarr; ARRAY_TYPE(db_dict_key_p) objectarr; test_begin("db dict parse cache key"); buffer_create_from_const_data(&keybuf, keys, sizeof(keys)); buffer_create_from_const_data(&fieldbuf, fields, sizeof(fields)); buffer_create_from_const_data(&objectbuf, objects, sizeof(objects)); array_create_from_buffer(&keyarr, &keybuf, sizeof(keys[0])); array_create_from_buffer(&fieldarr, &fieldbuf, sizeof(fields[0])); array_create_from_buffer(&objectarr, &objectbuf, sizeof(objects[0])); test_assert(strcmp(db_dict_parse_cache_key(&keyarr, &fieldarr, &objectarr), "\t%d and %n\t%l\t%{foo}%r%{bar}\t%{test1}/path\t%{extra}\tpath2/%{test2}\t%{plop}") == 0); test_end(); } dovecot-2.3.21.1/src/auth/auth-request-fields.c0000644000000000000000000004145414656633576016114 00000000000000/* Copyright (c) 2002-2020 Dovecot authors, see the included COPYING file */ #define AUTH_REQUEST_FIELDS_CONST #include "auth-common.h" #include "str.h" #include "strescape.h" #include "str-sanitize.h" #include "auth-request.h" #include "userdb-template.h" void auth_request_fields_init(struct auth_request *request) { request->fields.extra_fields = auth_fields_init(request->pool); if (request->mech != NULL) { request->fields.mech_name = request->mech->mech_name; event_add_str(request->event, "mechanism", request->mech->mech_name); } /* Default to "insecure" until it's changed later */ event_add_str(request->event, "transport", "insecure"); } static void auth_str_add_keyvalue(string_t *dest, const char *key, const char *value) { str_append_c(dest, '\t'); str_append(dest, key); if (value != NULL) { str_append_c(dest, '='); str_append_tabescaped(dest, value); } } static void auth_request_export_fields(string_t *dest, struct auth_fields *auth_fields, const char *prefix) { const ARRAY_TYPE(auth_field) *fields = auth_fields_export(auth_fields); const struct auth_field *field; array_foreach(fields, field) { str_printfa(dest, "\t%s%s", prefix, field->key); if (field->value != NULL) { str_append_c(dest, '='); str_append_tabescaped(dest, field->value); } } } void auth_request_export(struct auth_request *request, string_t *dest) { const struct auth_request_fields *fields = &request->fields; str_append(dest, "user="); str_append_tabescaped(dest, fields->user); auth_str_add_keyvalue(dest, "service", fields->service); if (fields->master_user != NULL) auth_str_add_keyvalue(dest, "master-user", fields->master_user); auth_str_add_keyvalue(dest, "original-username", fields->original_username); if (fields->requested_login_user != NULL) { auth_str_add_keyvalue(dest, "requested-login-user", fields->requested_login_user); } if (fields->local_ip.family != 0) { auth_str_add_keyvalue(dest, "lip", net_ip2addr(&fields->local_ip)); } if (fields->remote_ip.family != 0) { auth_str_add_keyvalue(dest, "rip", net_ip2addr(&fields->remote_ip)); } if (fields->local_port != 0) str_printfa(dest, "\tlport=%u", fields->local_port); if (fields->remote_port != 0) str_printfa(dest, "\trport=%u", fields->remote_port); if (fields->real_local_ip.family != 0) { auth_str_add_keyvalue(dest, "real_lip", net_ip2addr(&fields->real_local_ip)); } if (fields->real_remote_ip.family != 0) { auth_str_add_keyvalue(dest, "real_rip", net_ip2addr(&fields->real_remote_ip)); } if (fields->real_local_port != 0) str_printfa(dest, "\treal_lport=%u", fields->real_local_port); if (fields->real_remote_port != 0) str_printfa(dest, "\treal_rport=%u", fields->real_remote_port); if (fields->local_name != 0) { str_append(dest, "\tlocal_name="); str_append_tabescaped(dest, fields->local_name); } if (fields->session_id != NULL) { str_append(dest, "\tsession="); str_append_tabescaped(dest, fields->session_id); } if (event_want_debug(request->event)) str_append(dest, "\tdebug"); switch (fields->secured) { case AUTH_REQUEST_SECURED_NONE: break; case AUTH_REQUEST_SECURED: str_append(dest, "\tsecured"); break; case AUTH_REQUEST_SECURED_TLS: str_append(dest, "\tsecured=tls"); break; default: break; } if (fields->skip_password_check) str_append(dest, "\tskip-password-check"); if (fields->delayed_credentials != NULL) str_append(dest, "\tdelayed-credentials"); if (fields->valid_client_cert) str_append(dest, "\tvalid-client-cert"); if (fields->no_penalty) str_append(dest, "\tno-penalty"); if (fields->successful) str_append(dest, "\tsuccessful"); if (fields->mech_name != NULL) auth_str_add_keyvalue(dest, "mech", fields->mech_name); if (fields->client_id != NULL) auth_str_add_keyvalue(dest, "client_id", fields->client_id); /* export passdb extra fields */ auth_request_export_fields(dest, fields->extra_fields, "passdb_"); /* export any userdb fields */ if (fields->userdb_reply != NULL) auth_request_export_fields(dest, fields->userdb_reply, "userdb_"); } bool auth_request_import_info(struct auth_request *request, const char *key, const char *value) { struct auth_request_fields *fields = &request->fields; struct event *event = request->event; i_assert(value != NULL); /* authentication and user lookups may set these */ if (strcmp(key, "service") == 0) { fields->service = p_strdup(request->pool, value); event_add_str(event, "service", value); } else if (strcmp(key, "lip") == 0) { if (net_addr2ip(value, &fields->local_ip) < 0) return TRUE; event_add_str(event, "local_ip", value); if (fields->real_local_ip.family == 0) auth_request_import_info(request, "real_lip", value); } else if (strcmp(key, "rip") == 0) { if (net_addr2ip(value, &fields->remote_ip) < 0) return TRUE; event_add_str(event, "remote_ip", value); if (fields->real_remote_ip.family == 0) auth_request_import_info(request, "real_rip", value); } else if (strcmp(key, "lport") == 0) { if (net_str2port(value, &fields->local_port) < 0) return TRUE; event_add_int(event, "local_port", fields->local_port); if (fields->real_local_port == 0) auth_request_import_info(request, "real_lport", value); } else if (strcmp(key, "rport") == 0) { if (net_str2port(value, &fields->remote_port) < 0) return TRUE; event_add_int(event, "remote_port", fields->remote_port); if (fields->real_remote_port == 0) auth_request_import_info(request, "real_rport", value); } else if (strcmp(key, "real_lip") == 0) { if (net_addr2ip(value, &fields->real_local_ip) == 0) event_add_str(event, "real_local_ip", value); } else if (strcmp(key, "real_rip") == 0) { if (net_addr2ip(value, &fields->real_remote_ip) == 0) event_add_str(event, "real_remote_ip", value); } else if (strcmp(key, "real_lport") == 0) { if (net_str2port(value, &fields->real_local_port) == 0) event_add_int(event, "real_local_port", fields->real_local_port); } else if (strcmp(key, "real_rport") == 0) { if (net_str2port(value, &fields->real_remote_port) == 0) event_add_int(event, "real_remote_port", fields->real_remote_port); } else if (strcmp(key, "local_name") == 0) { fields->local_name = p_strdup(request->pool, value); event_add_str(event, "local_name", value); } else if (strcmp(key, "session") == 0) { fields->session_id = p_strdup(request->pool, value); event_add_str(event, "session", value); } else if (strcmp(key, "debug") == 0) event_set_forced_debug(request->event, TRUE); else if (strcmp(key, "client_id") == 0) { fields->client_id = p_strdup(request->pool, value); event_add_str(event, "client_id", value); } else if (strcmp(key, "forward_fields") == 0) { auth_fields_import_prefixed(fields->extra_fields, "forward_", value, 0); /* make sure the forward_ fields aren't deleted by auth_fields_rollback() if the first passdb lookup fails. */ auth_fields_snapshot(fields->extra_fields); } else return FALSE; /* NOTE: keep in sync with auth_request_export() */ return TRUE; } bool auth_request_import_auth(struct auth_request *request, const char *key, const char *value) { struct auth_request_fields *fields = &request->fields; i_assert(value != NULL); if (auth_request_import_info(request, key, value)) return TRUE; /* auth client may set these */ if (strcmp(key, "secured") == 0) { if (strcmp(value, "tls") == 0) { fields->secured = AUTH_REQUEST_SECURED_TLS; event_add_str(request->event, "transport", "TLS"); } else { fields->secured = AUTH_REQUEST_SECURED; event_add_str(request->event, "transport", "trusted"); } } else if (strcmp(key, "final-resp-ok") == 0) fields->final_resp_ok = TRUE; else if (strcmp(key, "no-penalty") == 0) fields->no_penalty = TRUE; else if (strcmp(key, "valid-client-cert") == 0) fields->valid_client_cert = TRUE; else if (strcmp(key, "cert_username") == 0) { if (request->set->ssl_username_from_cert && *value != '\0') { /* get username from SSL certificate. it overrides the username given by the auth mechanism. */ auth_request_set_username_forced(request, value); fields->cert_username = TRUE; } } else { return FALSE; } return TRUE; } bool auth_request_import(struct auth_request *request, const char *key, const char *value) { struct auth_request_fields *fields = &request->fields; i_assert(value != NULL); if (auth_request_import_auth(request, key, value)) return TRUE; /* for communication between auth master and worker processes */ if (strcmp(key, "user") == 0) auth_request_set_username_forced(request, value); else if (strcmp(key, "master-user") == 0) { fields->master_user = p_strdup(request->pool, value); event_add_str(request->event, "master_user", value); } else if (strcmp(key, "original-username") == 0) { fields->original_username = p_strdup(request->pool, value); event_add_str(request->event, "original_user", value); } else if (strcmp(key, "requested-login-user") == 0) auth_request_set_login_username_forced(request, value); else if (strcmp(key, "successful") == 0) auth_request_set_auth_successful(request); else if (strcmp(key, "skip-password-check") == 0) auth_request_set_password_verified(request); else if (strcmp(key, "delayed-credentials") == 0) { /* just make passdb_handle_credentials() work identically in auth-worker as it does in auth-master. the worker shouldn't care about the actual contents of the credentials. */ fields->delayed_credentials = &uchar_nul; fields->delayed_credentials_size = 1; } else if (strcmp(key, "mech") == 0) { fields->mech_name = p_strdup(request->pool, value); event_add_str(request->event, "mechanism", value); } else if (str_begins(key, "passdb_")) auth_fields_add(fields->extra_fields, key+7, value, 0); else if (str_begins(key, "userdb_")) { if (fields->userdb_reply == NULL) auth_request_init_userdb_reply(request, FALSE); auth_fields_add(fields->userdb_reply, key+7, value, 0); } else return FALSE; return TRUE; } static int auth_request_fix_username(struct auth_request *request, const char **username, const char **error_r) { const struct auth_settings *set = request->set; unsigned char *p; char *user; if (*set->default_realm != '\0' && strchr(*username, '@') == NULL) { user = p_strconcat(unsafe_data_stack_pool, *username, "@", set->default_realm, NULL); } else { user = t_strdup_noconst(*username); } for (p = (unsigned char *)user; *p != '\0'; p++) { if (set->username_translation_map[*p & 0xff] != 0) *p = set->username_translation_map[*p & 0xff]; if (set->username_chars_map[*p & 0xff] == 0) { *error_r = t_strdup_printf( "Username character disallowed by auth_username_chars: " "0x%02x (username: %s)", *p, str_sanitize(*username, 128)); return -1; } } if (*set->username_format != '\0') { /* username format given, put it through variable expansion. we'll have to temporarily replace request->user to get %u to be the wanted username */ const char *error; string_t *dest; dest = t_str_new(256); unsigned int count = 0; const struct var_expand_table *table = auth_request_get_var_expand_table_full(request, user, NULL, &count); if (auth_request_var_expand_with_table(dest, set->username_format, request, table, NULL, &error) <= 0) { *error_r = t_strdup_printf( "Failed to expand username_format=%s: %s", set->username_format, error); } user = str_c_modifiable(dest); } if (user[0] == '\0') { /* Some PAM plugins go nuts with empty usernames */ *error_r = "Empty username"; return -1; } *username = user; return 0; } bool auth_request_set_username(struct auth_request *request, const char *username, const char **error_r) { const struct auth_settings *set = request->set; const char *p, *login_username = NULL; if (*set->master_user_separator != '\0' && !request->userdb_lookup) { /* check if the username contains a master user */ p = strchr(username, *set->master_user_separator); if (p != NULL) { /* it does, set it. */ login_username = t_strdup_until(username, p); /* username is the master user */ username = p + 1; } } if (request->fields.original_username == NULL) { /* the username may change later, but we need to use this username when verifying at least DIGEST-MD5 password. */ request->fields.original_username = p_strdup(request->pool, username); event_add_str(request->event, "original_user", request->fields.original_username); } if (request->fields.cert_username) { /* cert_username overrides the username given by authentication mechanism. but still do checks and translations to it. */ username = request->fields.user; } if (auth_request_fix_username(request, &username, error_r) < 0) { request->fields.user = NULL; event_field_clear(request->event, "user"); return FALSE; } auth_request_set_username_forced(request, username); if (request->fields.translated_username == NULL) { /* similar to original_username, but after translations */ request->fields.translated_username = request->fields.user; event_add_str(request->event, "translated_user", request->fields.translated_username); } request->user_changed_by_lookup = TRUE; if (login_username != NULL) { if (!auth_request_set_login_username(request, login_username, error_r)) return FALSE; } return TRUE; } void auth_request_set_username_forced(struct auth_request *request, const char *username) { i_assert(username != NULL); request->fields.user = p_strdup(request->pool, username); event_add_str(request->event, "user", request->fields.user); } void auth_request_set_login_username_forced(struct auth_request *request, const char *username) { i_assert(username != NULL); request->fields.requested_login_user = p_strdup(request->pool, username); event_add_str(request->event, "login_user", request->fields.requested_login_user); } bool auth_request_set_login_username(struct auth_request *request, const char *username, const char **error_r) { struct auth_passdb *master_passdb; if (username[0] == '\0') { *error_r = "Master user login attempted to use empty login username"; return FALSE; } if (strcmp(username, request->fields.user) == 0) { /* The usernames are the same, we don't really wish to log in as someone else */ return TRUE; } /* lookup request->user from masterdb first */ master_passdb = auth_request_get_auth(request)->masterdbs; if (master_passdb == NULL) { *error_r = "Master user login attempted without master passdbs"; return FALSE; } request->passdb = master_passdb; if (auth_request_fix_username(request, &username, error_r) < 0) { request->fields.requested_login_user = NULL; event_field_clear(request->event, "login_user"); return FALSE; } auth_request_set_login_username_forced(request, username); e_debug(request->event, "%sMaster user lookup for login: %s", auth_request_get_log_prefix_db(request), request->fields.requested_login_user); return TRUE; } void auth_request_master_user_login_finish(struct auth_request *request) { if (request->failed) return; /* master login successful. update user and master_user variables. */ e_info(authdb_event(request), "Master user logging in as %s", request->fields.requested_login_user); request->fields.master_user = request->fields.user; event_add_str(request->event, "master_user", request->fields.master_user); auth_request_set_username_forced(request, request->fields.requested_login_user); request->fields.translated_username = request->fields.requested_login_user; event_add_str(request->event, "translated_user", request->fields.translated_username); request->fields.requested_login_user = NULL; event_field_clear(request->event, "login_user"); } void auth_request_set_realm(struct auth_request *request, const char *realm) { i_assert(realm != NULL); request->fields.realm = p_strdup(request->pool, realm); event_add_str(request->event, "realm", request->fields.realm); } void auth_request_set_auth_successful(struct auth_request *request) { request->fields.successful = TRUE; } void auth_request_set_password_verified(struct auth_request *request) { request->fields.skip_password_check = TRUE; } void auth_request_init_userdb_reply(struct auth_request *request, bool add_default_fields) { const char *error; request->fields.userdb_reply = auth_fields_init(request->pool); if (add_default_fields) { if (userdb_template_export(request->userdb->default_fields_tmpl, request, &error) < 0) { e_error(authdb_event(request), "Failed to expand default_fields: %s", error); } } } void auth_request_set_delayed_credentials(struct auth_request *request, const unsigned char *credentials, size_t size) { request->fields.delayed_credentials = p_memdup(request->pool, credentials, size); request->fields.delayed_credentials_size = size; } dovecot-2.3.21.1/src/auth/auth-settings.h0000644000000000000000000000532714656633576015024 00000000000000#ifndef AUTH_SETTINGS_H #define AUTH_SETTINGS_H struct master_service; struct master_service_settings_output; struct auth_passdb_settings { const char *name; const char *driver; const char *args; const char *default_fields; const char *override_fields; const char *mechanisms; const char *username_filter; const char *skip; const char *result_success; const char *result_failure; const char *result_internalfail; bool deny; bool pass; /* deprecated, use result_success=continue instead */ bool master; const char *auth_verbose; }; struct auth_userdb_settings { const char *name; const char *driver; const char *args; const char *default_fields; const char *override_fields; const char *skip; const char *result_success; const char *result_failure; const char *result_internalfail; const char *auth_verbose; }; struct auth_settings { const char *mechanisms; const char *realms; const char *default_realm; uoff_t cache_size; unsigned int cache_ttl; unsigned int cache_negative_ttl; bool cache_verify_password_with_worker; const char *username_chars; const char *username_translation; const char *username_format; const char *master_user_separator; const char *anonymous_username; const char *krb5_keytab; const char *gssapi_hostname; const char *winbind_helper_path; const char *proxy_self; unsigned int failure_delay; const char *policy_server_url; const char *policy_server_api_header; unsigned int policy_server_timeout_msecs; const char *policy_hash_mech; const char *policy_hash_nonce; const char *policy_request_attributes; bool policy_reject_on_fail; bool policy_check_before_auth; bool policy_check_after_auth; bool policy_report_after_auth; bool policy_log_only; unsigned int policy_hash_truncate; bool stats; bool verbose, debug, debug_passwords; const char *verbose_passwords; bool ssl_require_client_cert; bool ssl_username_from_cert; bool use_winbind; unsigned int worker_max_count; /* settings that don't have auth_ prefix: */ ARRAY(struct auth_passdb_settings *) passdbs; ARRAY(struct auth_userdb_settings *) userdbs; const char *base_dir; const char *ssl_client_ca_dir; const char *ssl_client_ca_file; bool verbose_proctitle; unsigned int first_valid_uid; unsigned int last_valid_uid; unsigned int first_valid_gid; unsigned int last_valid_gid; /* generated: */ char username_chars_map[256]; char username_translation_map[256]; const char *const *realms_arr; const struct ip_addr *proxy_self_ips; }; extern const struct setting_parser_info auth_setting_parser_info; extern struct auth_settings *global_auth_settings; struct auth_settings * auth_settings_read(const char *service, pool_t pool, struct master_service_settings_output *output_r) ATTR_NULL(1); #endif dovecot-2.3.21.1/src/auth/userdb-template.c0000644000000000000000000000572714656633576015321 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "array.h" #include "str.h" #include "userdb.h" #include "userdb-template.h" struct userdb_template { ARRAY(const char *) args; }; struct userdb_template * userdb_template_build(pool_t pool, const char *userdb_name, const char *args) { struct userdb_template *tmpl; const char *const *tmp, *key, *value, *nonull_value; uid_t uid; gid_t gid; tmpl = p_new(pool, struct userdb_template, 1); tmp = t_strsplit_spaces(args, " "); p_array_init(&tmpl->args, pool, str_array_length(tmp)); for (; *tmp != NULL; tmp++) { value = strchr(*tmp, '='); if (value == NULL) key = *tmp; else key = t_strdup_until(*tmp, value++); if (*key == '\0') i_fatal("Invalid userdb template %s - key must not be empty", args); nonull_value = value == NULL ? "" : value; if (strcasecmp(key, "uid") == 0) { uid = userdb_parse_uid(NULL, nonull_value); if (uid == (uid_t)-1) { i_fatal("%s userdb: Invalid uid: %s", userdb_name, nonull_value); } value = dec2str(uid); } else if (strcasecmp(key, "gid") == 0) { gid = userdb_parse_gid(NULL, nonull_value); if (gid == (gid_t)-1) { i_fatal("%s userdb: Invalid gid: %s", userdb_name, nonull_value); } value = dec2str(gid); } else if (*key == '\0') { i_fatal("%s userdb: Empty key (=%s)", userdb_name, nonull_value); } key = p_strdup(pool, key); value = p_strdup(pool, value); array_push_back(&tmpl->args, &key); array_push_back(&tmpl->args, &value); } return tmpl; } int userdb_template_export(struct userdb_template *tmpl, struct auth_request *auth_request, const char **error_r) { const struct var_expand_table *table; string_t *str; const char *const *args, *value; unsigned int i, count; if (userdb_template_is_empty(tmpl)) return 0; str = t_str_new(256); table = auth_request_get_var_expand_table(auth_request, NULL); args = array_get(&tmpl->args, &count); i_assert((count % 2) == 0); for (i = 0; i < count; i += 2) { if (args[i+1] == NULL) value = ""; else { str_truncate(str, 0); if (auth_request_var_expand_with_table(str, args[i+1], auth_request, table, NULL, error_r) <= 0) return -1; value = str_c(str); } auth_request_set_userdb_field(auth_request, args[i], value); } return 0; } bool userdb_template_remove(struct userdb_template *tmpl, const char *key, const char **value_r) { const char *const *args; unsigned int i, count; args = array_get(&tmpl->args, &count); i_assert((count % 2) == 0); for (i = 0; i < count; i += 2) { if (strcmp(args[i], key) == 0) { *value_r = args[i+1]; array_delete(&tmpl->args, i, 2); return TRUE; } } return FALSE; } bool userdb_template_is_empty(struct userdb_template *tmpl) { return array_count(&tmpl->args) == 0; } const char *const *userdb_template_get_args(struct userdb_template *tmpl, unsigned int *count_r) { return array_get(&tmpl->args, count_r); } dovecot-2.3.21.1/src/auth/userdb-blocking.c0000644000000000000000000000727014656633576015271 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "str.h" #include "auth-worker-server.h" #include "userdb.h" #include "userdb-blocking.h" struct blocking_userdb_iterate_context { struct userdb_iterate_context ctx; struct auth_worker_connection *conn; bool next; bool destroyed; }; static bool user_callback(struct auth_worker_connection *conn ATTR_UNUSED, const char *reply, void *context) { struct auth_request *request = context; enum userdb_result result; const char *username, *args; if (str_begins(reply, "FAIL\t")) { result = USERDB_RESULT_INTERNAL_FAILURE; args = reply + 5; } else if (str_begins(reply, "NOTFOUND\t")) { result = USERDB_RESULT_USER_UNKNOWN; args = reply + 9; } else if (str_begins(reply, "OK\t")) { result = USERDB_RESULT_OK; username = reply + 3; args = strchr(username, '\t'); if (args == NULL) args = ""; else username = t_strdup_until(username, args++); if (username[0] != '\0' && strcmp(request->fields.user, username) != 0) { auth_request_set_username_forced(request, username); request->user_changed_by_lookup = TRUE; } } else { result = USERDB_RESULT_INTERNAL_FAILURE; e_error(authdb_event(request), "BUG: auth-worker sent invalid user reply"); args = ""; } if (*args != '\0') { auth_fields_import(request->fields.userdb_reply, args, 0); if (auth_fields_exists(request->fields.userdb_reply, "tempfail")) request->userdb_lookup_tempfailed = TRUE; } auth_request_userdb_callback(result, request); auth_request_unref(&request); return TRUE; } void userdb_blocking_lookup(struct auth_request *request) { string_t *str; str = t_str_new(128); str_printfa(str, "USER\t%u\t", request->userdb->userdb->id); auth_request_export(request, str); auth_request_ref(request); auth_worker_call(request->pool, request->fields.user, str_c(str), user_callback, request); } static bool iter_callback(struct auth_worker_connection *conn, const char *reply, void *context) { struct blocking_userdb_iterate_context *ctx = context; ctx->conn = conn; if (str_begins(reply, "*\t")) { if (ctx->destroyed) return TRUE; ctx->next = FALSE; ctx->ctx.callback(reply + 2, ctx->ctx.context); return ctx->next || ctx->destroyed; } if (strcmp(reply, "OK") != 0) ctx->ctx.failed = TRUE; if (!ctx->destroyed) ctx->ctx.callback(NULL, ctx->ctx.context); auth_request_unref(&ctx->ctx.auth_request); return TRUE; } struct userdb_iterate_context * userdb_blocking_iter_init(struct auth_request *request, userdb_iter_callback_t *callback, void *context) { struct blocking_userdb_iterate_context *ctx; string_t *str; str = t_str_new(128); str_printfa(str, "LIST\t%u\t", request->userdb->userdb->id); auth_request_export(request, str); ctx = p_new(request->pool, struct blocking_userdb_iterate_context, 1); ctx->ctx.auth_request = request; ctx->ctx.callback = callback; ctx->ctx.context = context; auth_request_ref(request); auth_worker_call(request->pool, "*", str_c(str), iter_callback, ctx); return &ctx->ctx; } void userdb_blocking_iter_next(struct userdb_iterate_context *_ctx) { struct blocking_userdb_iterate_context *ctx = (struct blocking_userdb_iterate_context *)_ctx; i_assert(ctx->conn != NULL); ctx->next = TRUE; auth_worker_server_resume_input(ctx->conn); } int userdb_blocking_iter_deinit(struct userdb_iterate_context **_ctx) { struct blocking_userdb_iterate_context *ctx = (struct blocking_userdb_iterate_context *)*_ctx; int ret = ctx->ctx.failed ? -1 : 0; *_ctx = NULL; /* iter_callback() may still be called */ ctx->destroyed = TRUE; if (ctx->conn != NULL) auth_worker_server_resume_input(ctx->conn); return ret; } dovecot-2.3.21.1/src/auth/mech-otp-common.c0000644000000000000000000000270214656633576015214 00000000000000/* * Common code for OTP authentication mechanisms. * * Copyright (c) 2006 Andrey Panin * * This software is released under the MIT license. */ #include "auth-common.h" #include "hash.h" #include "mech.h" #include "otp.h" #include "mech-otp-common.h" static HASH_TABLE(char *, struct auth_request *) otp_lock_table; void otp_lock_init(void) { if (hash_table_is_created(otp_lock_table)) return; hash_table_create(&otp_lock_table, default_pool, 128, strcase_hash, strcasecmp); } bool otp_try_lock(struct auth_request *auth_request) { if (hash_table_lookup(otp_lock_table, auth_request->fields.user) != NULL) return FALSE; hash_table_insert(otp_lock_table, auth_request->fields.user, auth_request); return TRUE; } void otp_unlock(struct auth_request *auth_request) { struct otp_auth_request *request = (struct otp_auth_request *)auth_request; if (!request->lock) return; hash_table_remove(otp_lock_table, auth_request->fields.user); request->lock = FALSE; } void otp_set_credentials_callback(bool success, struct auth_request *auth_request) { if (success) auth_request_success(auth_request, "", 0); else { auth_request_internal_failure(auth_request); otp_unlock(auth_request); } otp_unlock(auth_request); } void mech_otp_auth_free(struct auth_request *auth_request) { otp_unlock(auth_request); pool_unref(&auth_request->pool); } void mech_otp_deinit(void) { hash_table_destroy(&otp_lock_table); } dovecot-2.3.21.1/src/auth/auth-request-var-expand.c0000644000000000000000000002325014656633576016705 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "str.h" #include "strescape.h" #include "auth-request.h" struct auth_request_var_expand_ctx { const struct auth_request *auth_request; auth_request_escape_func_t *escape_func; }; const struct var_expand_table auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT+1] = { { 'u', NULL, "user" }, { 'n', NULL, "username" }, { 'd', NULL, "domain" }, { 's', NULL, "service" }, { 'h', NULL, "home" }, { 'l', NULL, "lip" }, { 'r', NULL, "rip" }, { 'p', NULL, "pid" }, { 'w', NULL, "password" }, { '!', NULL, NULL }, { 'm', NULL, "mech" }, { 'c', NULL, "secured" }, { 'a', NULL, "lport" }, { 'b', NULL, "rport" }, { 'k', NULL, "cert" }, { '\0', NULL, "login_user" }, { '\0', NULL, "login_username" }, { '\0', NULL, "login_domain" }, { '\0', NULL, "session" }, { '\0', NULL, "real_lip" }, { '\0', NULL, "real_rip" }, { '\0', NULL, "real_lport" }, { '\0', NULL, "real_rport" }, { '\0', NULL, "domain_first" }, { '\0', NULL, "domain_last" }, { '\0', NULL, "master_user" }, { '\0', NULL, "session_pid" }, { '\0', NULL, "orig_user" }, { '\0', NULL, "orig_username" }, { '\0', NULL, "orig_domain" }, { '\0', NULL, "auth_user" }, { '\0', NULL, "auth_username" }, { '\0', NULL, "auth_domain" }, { '\0', NULL, "local_name" }, { '\0', NULL, "client_id" }, /* aliases: */ { '\0', NULL, "local_ip" }, { '\0', NULL, "remote_ip" }, { '\0', NULL, "local_port" }, { '\0', NULL, "remote_port" }, { '\0', NULL, "real_local_ip" }, { '\0', NULL, "real_remote_ip" }, { '\0', NULL, "real_local_port" }, { '\0', NULL, "real_remote_port" }, { '\0', NULL, "mechanism" }, { '\0', NULL, "original_user" }, { '\0', NULL, "original_username" }, { '\0', NULL, "original_domain" }, /* be sure to update AUTH_REQUEST_VAR_TAB_COUNT */ { '\0', NULL, NULL } }; static const char * escape_none(const char *string, const struct auth_request *request ATTR_UNUSED) { return string; } const char * auth_request_str_escape(const char *string, const struct auth_request *request ATTR_UNUSED) { return str_escape(string); } struct var_expand_table * auth_request_get_var_expand_table_full(const struct auth_request *auth_request, const char *username, auth_request_escape_func_t *escape_func, unsigned int *count) { const struct auth_request_fields *fields = &auth_request->fields; const unsigned int auth_count = N_ELEMENTS(auth_request_var_expand_static_tab); struct var_expand_table *tab, *ret_tab; const char *orig_user, *auth_user; if (escape_func == NULL) escape_func = escape_none; /* keep the extra fields at the beginning. the last static_tab field contains the ending NULL-fields. */ tab = ret_tab = t_new(struct var_expand_table, MALLOC_ADD(*count, auth_count)); tab += *count; *count += auth_count; memcpy(tab, auth_request_var_expand_static_tab, auth_count * sizeof(*tab)); if (username == NULL) username = ""; tab[0].value = escape_func(username, auth_request); tab[1].value = escape_func(t_strcut(username, '@'), auth_request); tab[2].value = i_strchr_to_next(username, '@'); if (tab[2].value != NULL) tab[2].value = escape_func(tab[2].value, auth_request); tab[3].value = escape_func(fields->service, auth_request); /* tab[4] = we have no home dir */ if (fields->local_ip.family != 0) tab[5].value = tab[35].value = net_ip2addr(&fields->local_ip); if (fields->remote_ip.family != 0) tab[6].value = tab[36].value = net_ip2addr(&fields->remote_ip); tab[7].value = dec2str(auth_request->client_pid); if (auth_request->mech_password != NULL) { tab[8].value = escape_func(auth_request->mech_password, auth_request); } if (auth_request->userdb_lookup) { tab[9].value = auth_request->userdb == NULL ? "" : dec2str(auth_request->userdb->userdb->id); } else { tab[9].value = auth_request->passdb == NULL ? "" : dec2str(auth_request->passdb->passdb->id); } tab[10].value = tab[43].value = fields->mech_name == NULL ? "" : escape_func(fields->mech_name, auth_request); switch (fields->secured) { case AUTH_REQUEST_SECURED_NONE: tab[11].value = ""; break; case AUTH_REQUEST_SECURED: tab[11].value = "secured"; break; case AUTH_REQUEST_SECURED_TLS: tab[11].value = "TLS"; break; default: tab[11].value = ""; break; }; tab[12].value = tab[37].value = dec2str(fields->local_port); tab[13].value = tab[38].value = dec2str(fields->remote_port); tab[14].value = fields->valid_client_cert ? "valid" : ""; if (fields->requested_login_user != NULL) { const char *login_user = fields->requested_login_user; tab[15].value = escape_func(login_user, auth_request); tab[16].value = escape_func(t_strcut(login_user, '@'), auth_request); tab[17].value = i_strchr_to_next(login_user, '@'); if (tab[17].value != NULL) { tab[17].value = escape_func(tab[17].value, auth_request); } } tab[18].value = fields->session_id == NULL ? NULL : escape_func(fields->session_id, auth_request); if (fields->real_local_ip.family != 0) tab[19].value = tab[39].value = net_ip2addr(&fields->real_local_ip); if (fields->real_remote_ip.family != 0) tab[20].value = tab[40].value = net_ip2addr(&fields->real_remote_ip); tab[21].value = tab[41].value = dec2str(fields->real_local_port); tab[22].value = tab[42].value = dec2str(fields->real_remote_port); tab[23].value = i_strchr_to_next(username, '@'); if (tab[23].value != NULL) { tab[23].value = escape_func(t_strcut(tab[23].value, '@'), auth_request); } tab[24].value = strrchr(username, '@'); if (tab[24].value != NULL) tab[24].value = escape_func(tab[24].value+1, auth_request); tab[25].value = fields->master_user == NULL ? NULL : escape_func(fields->master_user, auth_request); tab[26].value = auth_request->session_pid == (pid_t)-1 ? NULL : dec2str(auth_request->session_pid); orig_user = fields->original_username != NULL ? fields->original_username : username; tab[27].value = tab[44].value = escape_func(orig_user, auth_request); tab[28].value = tab[45].value = escape_func(t_strcut(orig_user, '@'), auth_request); tab[29].value = tab[46].value = i_strchr_to_next(orig_user, '@'); if (tab[29].value != NULL) tab[29].value = tab[46].value = escape_func(tab[29].value, auth_request); if (fields->master_user != NULL) auth_user = fields->master_user; else auth_user = orig_user; tab[30].value = escape_func(auth_user, auth_request); tab[31].value = escape_func(t_strcut(auth_user, '@'), auth_request); tab[32].value = i_strchr_to_next(auth_user, '@'); if (tab[32].value != NULL) tab[32].value = escape_func(tab[32].value, auth_request); if (fields->local_name != NULL) tab[33].value = escape_func(fields->local_name, auth_request); if (fields->client_id != NULL) tab[34].value = escape_func(fields->client_id, auth_request); return ret_tab; } const struct var_expand_table * auth_request_get_var_expand_table(const struct auth_request *auth_request, auth_request_escape_func_t *escape_func) { unsigned int count = 0; return auth_request_get_var_expand_table_full(auth_request, auth_request->fields.user, escape_func, &count); } static const char *field_get_default(const char *data) { const char *p; p = strchr(data, ':'); if (p == NULL) return ""; else { /* default value given */ return p+1; } } static int auth_request_var_expand_func_passdb(const char *data, void *context, const char **value_r, const char **error_r ATTR_UNUSED) { struct auth_request_var_expand_ctx *ctx = context; const char *field_name = t_strcut(data, ':'); const char *value; value = auth_fields_find(ctx->auth_request->fields.extra_fields, field_name); *value_r = ctx->escape_func(value != NULL ? value : field_get_default(data), ctx->auth_request); return 1; } static int auth_request_var_expand_func_userdb(const char *data, void *context, const char **value_r, const char **error_r ATTR_UNUSED) { struct auth_request_var_expand_ctx *ctx = context; const char *field_name = t_strcut(data, ':'); const char *value; value = ctx->auth_request->fields.userdb_reply == NULL ? NULL : auth_fields_find(ctx->auth_request->fields.userdb_reply, field_name); *value_r = ctx->escape_func(value != NULL ? value : field_get_default(data), ctx->auth_request); return 1; } const struct var_expand_func_table auth_request_var_funcs_table[] = { { "passdb", auth_request_var_expand_func_passdb }, { "userdb", auth_request_var_expand_func_userdb }, { NULL, NULL } }; int auth_request_var_expand(string_t *dest, const char *str, const struct auth_request *auth_request, auth_request_escape_func_t *escape_func, const char **error_r) { return auth_request_var_expand_with_table(dest, str, auth_request, auth_request_get_var_expand_table(auth_request, escape_func), escape_func, error_r); } int auth_request_var_expand_with_table(string_t *dest, const char *str, const struct auth_request *auth_request, const struct var_expand_table *table, auth_request_escape_func_t *escape_func, const char **error_r) { struct auth_request_var_expand_ctx ctx; i_zero(&ctx); ctx.auth_request = auth_request; ctx.escape_func = escape_func == NULL ? escape_none : escape_func; return var_expand_with_funcs(dest, str, table, auth_request_var_funcs_table, &ctx, error_r); } int t_auth_request_var_expand(const char *str, const struct auth_request *auth_request ATTR_UNUSED, auth_request_escape_func_t *escape_func ATTR_UNUSED, const char **value_r, const char **error_r) { string_t *dest = t_str_new(128); int ret = auth_request_var_expand(dest, str, auth_request, escape_func, error_r); *value_r = str_c(dest); return ret; } dovecot-2.3.21.1/src/auth/passdb-passwd.c0000644000000000000000000000600714656633576014767 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "passdb.h" #ifdef PASSDB_PASSWD #include "safe-memset.h" #include "ipwd.h" #define PASSWD_CACHE_KEY "%u" #define PASSWD_PASS_SCHEME "CRYPT" static enum passdb_result passwd_lookup(struct auth_request *request, struct passwd *pw_r) { e_debug(authdb_event(request), "lookup"); switch (i_getpwnam(request->fields.user, pw_r)) { case -1: e_error(authdb_event(request), "getpwnam() failed: %m"); return PASSDB_RESULT_INTERNAL_FAILURE; case 0: auth_request_log_unknown_user(request, AUTH_SUBSYS_DB); return PASSDB_RESULT_USER_UNKNOWN; } if (!IS_VALID_PASSWD(pw_r->pw_passwd)) { e_info(authdb_event(request), "invalid password field '%s'", pw_r->pw_passwd); return PASSDB_RESULT_USER_DISABLED; } /* save the password so cache can use it */ auth_request_set_field(request, "password", pw_r->pw_passwd, PASSWD_PASS_SCHEME); return PASSDB_RESULT_OK; } static void passwd_verify_plain(struct auth_request *request, const char *password, verify_plain_callback_t *callback) { struct passwd pw; enum passdb_result res; res = passwd_lookup(request, &pw); if (res != PASSDB_RESULT_OK) { callback(res, request); return; } /* check if the password is valid */ res = auth_request_password_verify(request, password, pw.pw_passwd, PASSWD_PASS_SCHEME, AUTH_SUBSYS_DB); /* clear the passwords from memory */ safe_memset(pw.pw_passwd, 0, strlen(pw.pw_passwd)); if (res != PASSDB_RESULT_OK) { callback(res, request); return; } /* make sure we're using the username exactly as it's in the database */ auth_request_set_field(request, "user", pw.pw_name, NULL); callback(res, request); } static void passwd_lookup_credentials(struct auth_request *request, lookup_credentials_callback_t *callback) { struct passwd pw; enum passdb_result res; res = passwd_lookup(request, &pw); if (res != PASSDB_RESULT_OK) { callback(res, NULL, 0, request); return; } /* make sure we're using the username exactly as it's in the database */ auth_request_set_field(request, "user", pw.pw_name, NULL); passdb_handle_credentials(PASSDB_RESULT_OK, pw.pw_passwd, PASSWD_PASS_SCHEME, callback, request); } static struct passdb_module * passwd_preinit(pool_t pool, const char *args) { struct passdb_module *module; module = p_new(pool, struct passdb_module, 1); module->blocking = TRUE; if (strcmp(args, "blocking=no") == 0) module->blocking = FALSE; else if (*args != '\0') i_fatal("passdb passwd: Unknown setting: %s", args); module->default_cache_key = PASSWD_CACHE_KEY; module->default_pass_scheme = PASSWD_PASS_SCHEME; return module; } static void passwd_deinit(struct passdb_module *module ATTR_UNUSED) { endpwent(); } struct passdb_module_interface passdb_passwd = { "passwd", passwd_preinit, NULL, passwd_deinit, passwd_verify_plain, passwd_lookup_credentials, NULL }; #else struct passdb_module_interface passdb_passwd = { .name = "passwd" }; #endif dovecot-2.3.21.1/src/auth/auth-token.c0000644000000000000000000001107014656633576014267 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ /* Auth process maintains a random secret. Once a user authenticates the response to the REQUEST command from a master service is augmented with an auth_token value. This token is the SHA1 hash of the secret, the service name and the username of the user that just logged in. Using this token the service (e.g. imap) can login to another service (e.g. imap-urlauth) to gain access to resources that require additional privileges (e.g. another user's e-mail). */ #include "auth-common.h" #include "hex-binary.h" #include "hmac.h" #include "sha1.h" #include "randgen.h" #include "read-full.h" #include "write-full.h" #include "safe-memset.h" #include "auth-settings.h" #include "auth-token.h" #include #include #include #include #include #define AUTH_TOKEN_SECRET_LEN 32 #define AUTH_TOKEN_SECRET_FNAME "auth-token-secret.dat" static unsigned char auth_token_secret[AUTH_TOKEN_SECRET_LEN]; static int auth_token_read_secret(const char *path, unsigned char secret_r[AUTH_TOKEN_SECRET_LEN]) { struct stat st, lst; int fd, ret; fd = open(path, O_RDONLY); if (fd == -1) { if (errno != ENOENT) i_error("open(%s) failed: %m", path); return -1; } if (fstat(fd, &st) < 0) { i_error("fstat(%s) failed: %m", path); i_close_fd(&fd); return -1; } /* check secret len and file type */ if (st.st_size != AUTH_TOKEN_SECRET_LEN || !S_ISREG(st.st_mode)) { i_error("Corrupted token secret file: %s", path); i_close_fd(&fd); i_unlink(path); return -1; } /* verify that we're not dealing with a symbolic link */ if (lstat(path, &lst) < 0) { i_error("lstat(%s) failed: %m", path); i_close_fd(&fd); return -1; } /* check security parameters for compromise */ if ((st.st_mode & 07777) != 0600 || st.st_uid != geteuid() || st.st_nlink > 1 || !S_ISREG(lst.st_mode) || st.st_ino != lst.st_ino || !CMP_DEV_T(st.st_dev, lst.st_dev)) { i_error("Compromised token secret file: %s", path); i_close_fd(&fd); i_unlink(path); return -1; } /* FIXME: fail here to generate new secret if stored one is too old */ ret = read_full(fd, secret_r, AUTH_TOKEN_SECRET_LEN); if (ret < 0) i_error("read(%s) failed: %m", path); else if (ret == 0) { i_error("Token secret file unexpectedly shrank: %s", path); ret = -1; } if (close(fd) < 0) i_error("close(%s) failed: %m", path); e_debug(auth_event, "Read auth token secret from %s", path); return ret; } static int auth_token_write_secret(const char *path, const unsigned char secret[AUTH_TOKEN_SECRET_LEN]) { const char *temp_path; mode_t old_mask; int fd, ret; temp_path = t_strconcat(path, ".tmp", NULL); old_mask = umask(0); fd = open(temp_path, O_WRONLY | O_CREAT | O_TRUNC, 0600); umask(old_mask); if (fd == -1) { i_error("open(%s) failed: %m", temp_path); return -1; } ret = write_full(fd, secret, AUTH_TOKEN_SECRET_LEN); if (ret < 0) i_error("write(%s) failed: %m", temp_path); if (close(fd) < 0) { i_error("close(%s) failed: %m", temp_path); ret = -1; } if (ret < 0) { i_unlink(temp_path); return -1; } if (rename(temp_path, path) < 0) { i_error("rename(%s, %s) failed: %m", temp_path, path); i_unlink(temp_path); return -1; } e_debug(auth_event, "Wrote new auth token secret to %s", path); return 0; } void auth_token_init(void) { const char *secret_path = t_strconcat(global_auth_settings->base_dir, "/", AUTH_TOKEN_SECRET_FNAME, NULL); if (auth_token_read_secret(secret_path, auth_token_secret) < 0) { random_fill(auth_token_secret, sizeof(auth_token_secret)); if (auth_token_write_secret(secret_path, auth_token_secret) < 0) { i_error("Failed to write auth token secret file; " "returned tokens will be invalid once auth restarts"); } } } void auth_token_deinit(void) { /* not very useful, but we do it anyway */ safe_memset(auth_token_secret, 0, sizeof(auth_token_secret)); } const char *auth_token_get(const char *service, const char *session_pid, const char *username, const char *session_id) { struct hmac_context ctx; unsigned char result[SHA1_RESULTLEN]; hmac_init(&ctx, (const unsigned char*)username, strlen(username), &hash_method_sha1); hmac_update(&ctx, session_pid, strlen(session_pid)); if (session_id != NULL && *session_id != '\0') hmac_update(&ctx, session_id, strlen(session_id)); hmac_update(&ctx, service, strlen(service)); hmac_update(&ctx, auth_token_secret, sizeof(auth_token_secret)); hmac_final(&ctx, result); return binary_to_hex(result, sizeof(result)); } dovecot-2.3.21.1/src/auth/db-checkpassword.h0000644000000000000000000000157014656633576015444 00000000000000#ifndef CHECKPASSWORD_COMMON_H #define CHECKPASSWORD_COMMON_H #include "auth-request.h" enum db_checkpassword_status { DB_CHECKPASSWORD_STATUS_INTERNAL_FAILURE = -1, /* auth unsuccessful / user not found */ DB_CHECKPASSWORD_STATUS_FAILURE = 0, DB_CHECKPASSWORD_STATUS_OK = 1 }; typedef void db_checkpassword_callback_t(struct auth_request *request, enum db_checkpassword_status status, const char *const *extra_fields, void (*request_callback)()); struct db_checkpassword * db_checkpassword_init(const char *checkpassword_path, const char *checkpassword_reply_path); void db_checkpassword_deinit(struct db_checkpassword **db); void db_checkpassword_call(struct db_checkpassword *db, struct auth_request *request, const char *auth_password, db_checkpassword_callback_t *callback, void (*request_callback)()) ATTR_NULL(3); #endif dovecot-2.3.21.1/src/auth/mech-digest-md5-private.h0000644000000000000000000000133614656633576016545 00000000000000#ifndef MECH_DIGEST_MD5_PRIVATE_H #define MECH_DIGEST_MD5_PRIVATE_H #include "auth-request.h" enum qop_option { QOP_AUTH = 0x01, /* authenticate */ QOP_AUTH_INT = 0x02, /* + integrity protection, not supported yet */ QOP_AUTH_CONF = 0x04, /* + encryption, not supported yet */ QOP_COUNT = 3 }; struct digest_auth_request { struct auth_request auth_request; pool_t pool; /* requested: */ char *nonce; enum qop_option qop; /* received: */ char *username; char *cnonce; char *nonce_count; char *qop_value; char *digest_uri; /* may be NULL */ char *authzid; /* may be NULL, authorization ID */ unsigned char response[32]; unsigned long maxbuf; bool nonce_found:1; /* final reply: */ char *rspauth; }; #endif dovecot-2.3.21.1/src/auth/auth-request.h0000644000000000000000000003641414656633576014655 00000000000000#ifndef AUTH_REQUEST_H #define AUTH_REQUEST_H #ifndef AUTH_REQUEST_FIELDS_CONST # define AUTH_REQUEST_FIELDS_CONST const #endif #include "array.h" #include "net.h" #include "var-expand.h" #include "mech.h" #include "userdb.h" #include "passdb.h" #include "auth-request-var-expand.h" #include "password-scheme.h" #define AUTH_REQUEST_USER_KEY_IGNORE " " struct auth_client_connection; enum auth_request_state { AUTH_REQUEST_STATE_NEW, AUTH_REQUEST_STATE_PASSDB, AUTH_REQUEST_STATE_MECH_CONTINUE, AUTH_REQUEST_STATE_FINISHED, AUTH_REQUEST_STATE_USERDB, AUTH_REQUEST_STATE_MAX }; enum auth_request_secured { AUTH_REQUEST_SECURED_NONE, AUTH_REQUEST_SECURED, AUTH_REQUEST_SECURED_TLS, }; enum auth_request_cache_result { AUTH_REQUEST_CACHE_NONE, AUTH_REQUEST_CACHE_MISS, AUTH_REQUEST_CACHE_HIT, }; /* All auth request fields are exported to auth-worker process. */ struct auth_request_fields { /* user contains the user who is being authenticated. When master user is logging in as someone else, it gets more complicated. Initially user is set to master's username and the requested_login_user is set to destination username. After masterdb has validated user as a valid master user, master_user is set to user and user is set to requested_login_user. */ char *user, *requested_login_user, *master_user; /* original_username contains the username exactly as given by the client. this is needed at least with DIGEST-MD5 for password verification. however with master logins the master username has been dropped from it. */ const char *original_username; /* the username after doing all internal translations, but before being changed by a db lookup */ const char *translated_username; /* realm for the request, may be specified by some auth mechanisms */ const char *realm; const char *service, *mech_name, *session_id, *local_name, *client_id; struct ip_addr local_ip, remote_ip, real_local_ip, real_remote_ip; in_port_t local_port, remote_port, real_local_port, real_remote_port; /* extra_fields are returned in authentication reply. Fields prefixed with "userdb_" are automatically placed to userdb_reply instead. */ struct auth_fields *extra_fields; /* the whole userdb result reply */ struct auth_fields *userdb_reply; /* Credentials from the first successful passdb lookup. These are used as the final credentials, unless overridden by later passdb lookups. Note that the requests in auth-worker processes see these only as 1 byte sized \0 strings. */ const unsigned char *delayed_credentials; size_t delayed_credentials_size; enum auth_request_secured secured; /* Authentication was successfully finished, including policy checks and such. There may still be some final delay or final SASL response. */ bool successful:1; /* Password was verified successfully by a passdb. The following passdbs shouldn't attempt to verify the password again. Note that this differs from passdb_success, which may be set to FALSE due to the result_* rules. */ bool skip_password_check:1; /* flags received from auth client: */ bool final_resp_ok:1; bool no_penalty:1; bool valid_client_cert:1; bool cert_username:1; }; struct auth_request { int refcount; pool_t pool; struct event *event; struct event *mech_event; ARRAY(struct event *) authdb_event; enum auth_request_state state; char *mech_password; /* set if verify_plain() is called */ char *passdb_password; /* set after password lookup if successful */ struct auth_request_proxy_dns_lookup_ctx *dns_lookup_ctx; /* The final result of passdb lookup (delayed due to asynchronous proxy DNS lookups) */ enum passdb_result passdb_result; const struct mech_module *mech; const struct auth_settings *set; struct auth_passdb *passdb; struct auth_userdb *userdb; struct stats *stats; /* passdb lookups have a handler, userdb lookups don't */ struct auth_request_handler *handler; struct auth_master_connection *master; /* FIXME: Remove this once mech-oauth2 correctly does the processing */ const char *openid_config_url; unsigned int connect_uid; unsigned int client_pid; unsigned int id; time_t last_access; time_t delay_until; pid_t session_pid; /* These are const for most of the code, so they don't try to modify the fields directly. Only auth-request-fields.c and unit tests have the fields writable. This way it's more difficult to make them out-of-sync with events. */ AUTH_REQUEST_FIELDS_CONST struct auth_request_fields fields; struct timeout *to_abort, *to_penalty; unsigned int policy_penalty; unsigned int last_penalty; size_t initial_response_len; const unsigned char *initial_response; union { verify_plain_callback_t *verify_plain; lookup_credentials_callback_t *lookup_credentials; set_credentials_callback_t *set_credentials; userdb_callback_t *userdb; } private_callback; /* Used by passdb's credentials lookup to determine which scheme is wanted by the caller. For example CRAM-MD5 SASL mechanism wants CRAM-MD5 scheme for passwords. When doing a PASS lookup (without authenticating), this is set to "" to imply that caller accepts any kind of credentials. After the credentials lookup is finished, this is set to the scheme that was actually received. Otherwise, this is kept as NULL. */ const char *wanted_credentials_scheme; void *context; enum auth_request_cache_result passdb_cache_result; enum auth_request_cache_result userdb_cache_result; /* this is a lookup on auth socket (not login socket). skip any proxying stuff if enabled. */ bool auth_only:1; /* we're doing a userdb lookup now (we may have done passdb lookup earlier) */ bool userdb_lookup:1; /* DIGEST-MD5 kludge */ bool domain_is_realm:1; bool request_auth_token:1; /* success/failure states: */ bool failed:1; /* overrides any other success */ bool internal_failure:1; bool passdbs_seen_user_unknown:1; bool passdbs_seen_internal_failure:1; bool userdbs_seen_internal_failure:1; /* current state: */ bool handler_pending_reply:1; bool accept_cont_input:1; bool prefer_plain_credentials:1; bool in_delayed_failure_queue:1; bool removed_from_handler:1; bool snapshot_have_userdb_prefetch_set:1; /* username was changed by this passdb/userdb lookup. Used by auth-workers to determine whether to send back a changed username. */ bool user_changed_by_lookup:1; /* each passdb lookup can update the current success-status using the result_* rules. the authentication succeeds only if this is TRUE at the end. mechanisms that don't require passdb, but do a passdb lookup anyway (e.g. GSSAPI) need to set this to TRUE by default. */ bool passdb_success:1; /* userdb equivalent of passdb_success */ bool userdb_success:1; /* the last userdb lookup failed either due to "tempfail" extra field or because one of the returned uid/gid fields couldn't be translated to a number */ bool userdb_lookup_tempfailed:1; /* userdb_* fields have been set by the passdb lookup, userdb prefetch will work. */ bool userdb_prefetch_set:1; bool stats_sent:1; bool policy_refusal:1; bool policy_processed:1; bool event_finished_sent:1; /* ... mechanism specific data ... */ }; typedef void auth_request_proxy_cb_t(bool success, struct auth_request *); extern unsigned int auth_request_state_count[AUTH_REQUEST_STATE_MAX]; extern const char auth_default_subsystems[2]; #define AUTH_SUBSYS_DB &auth_default_subsystems[0] #define AUTH_SUBSYS_MECH &auth_default_subsystems[1] struct auth_request * auth_request_new(const struct mech_module *mech, struct event *parent_event); struct auth_request *auth_request_new_dummy(struct event *parent_event); void auth_request_init(struct auth_request *request); struct auth *auth_request_get_auth(struct auth_request *request); void auth_request_set_state(struct auth_request *request, enum auth_request_state state); void auth_request_ref(struct auth_request *request); void auth_request_unref(struct auth_request **request); void auth_request_success(struct auth_request *request, const void *data, size_t data_size); void auth_request_fail_with_reply(struct auth_request *request, const void *final_data, size_t final_data_size); void auth_request_fail(struct auth_request *request); void auth_request_internal_failure(struct auth_request *request); void auth_request_export(struct auth_request *request, string_t *dest); bool auth_request_import(struct auth_request *request, const char *key, const char *value); bool auth_request_import_info(struct auth_request *request, const char *key, const char *value); bool auth_request_import_auth(struct auth_request *request, const char *key, const char *value); bool auth_request_import_master(struct auth_request *request, const char *key, const char *value); void auth_request_initial(struct auth_request *request); void auth_request_continue(struct auth_request *request, const unsigned char *data, size_t data_size); void auth_request_verify_plain(struct auth_request *request, const char *password, verify_plain_callback_t *callback); void auth_request_lookup_credentials(struct auth_request *request, const char *scheme, lookup_credentials_callback_t *callback); void auth_request_lookup_user(struct auth_request *request, userdb_callback_t *callback); bool auth_request_set_username(struct auth_request *request, const char *username, const char **error_r); /* Change the username without any translations or checks. */ void auth_request_set_username_forced(struct auth_request *request, const char *username); bool auth_request_set_login_username(struct auth_request *request, const char *username, const char **error_r); /* Change the login username without any translations or checks. */ void auth_request_set_login_username_forced(struct auth_request *request, const char *username); void auth_request_set_realm(struct auth_request *request, const char *realm); /* Request was fully successfully authenticated, including policy checks etc. */ void auth_request_set_auth_successful(struct auth_request *request); /* Password was successfully verified by a passdb. */ void auth_request_set_password_verified(struct auth_request *request); /* Save credentials from a successful passdb lookup. */ void auth_request_set_delayed_credentials(struct auth_request *request, const unsigned char *credentials, size_t size); void auth_request_set_field(struct auth_request *request, const char *name, const char *value, const char *default_scheme) ATTR_NULL(4); void auth_request_set_null_field(struct auth_request *request, const char *name); void auth_request_set_field_keyvalue(struct auth_request *request, const char *field, const char *default_scheme) ATTR_NULL(3); void auth_request_set_fields(struct auth_request *request, const char *const *fields, const char *default_scheme) ATTR_NULL(3); void auth_request_init_userdb_reply(struct auth_request *request, bool add_default_fields); void auth_request_set_userdb_field(struct auth_request *request, const char *name, const char *value); void auth_request_set_userdb_field_values(struct auth_request *request, const char *name, const char *const *values); /* returns -1 = failed, 0 = callback is called later, 1 = finished */ int auth_request_proxy_finish(struct auth_request *request, auth_request_proxy_cb_t *callback); void auth_request_proxy_finish_failure(struct auth_request *request); void auth_request_log_password_mismatch(struct auth_request *request, const char *subsystem); enum passdb_result auth_request_password_verify(struct auth_request *request, const char *plain_password, const char *crypted_password, const char *scheme, const char *subsystem) ATTR_WARN_UNUSED_RESULT; enum passdb_result auth_request_password_verify_log(struct auth_request *request, const char *plain_password, const char *crypted_password, const char *scheme, const char *subsystem, bool log_password_mismatch) ATTR_WARN_UNUSED_RESULT; enum passdb_result auth_request_password_missing(struct auth_request *request); void auth_request_get_log_prefix(string_t *str, struct auth_request *auth_request, const char *subsystem); void auth_request_log_debug(struct auth_request *auth_request, const char *subsystem, const char *format, ...) ATTR_FORMAT(3, 4); void auth_request_log_info(struct auth_request *auth_request, const char *subsystem, const char *format, ...) ATTR_FORMAT(3, 4); void auth_request_log_warning(struct auth_request *auth_request, const char *subsystem, const char *format, ...) ATTR_FORMAT(3, 4); void auth_request_log_error(struct auth_request *auth_request, const char *subsystem, const char *format, ...) ATTR_FORMAT(3, 4); void auth_request_log_unknown_user(struct auth_request *auth_request, const char *subsystem); void auth_request_log_login_failure(struct auth_request *request, const char *subsystem, const char *message); void auth_request_verify_plain_callback_finish(enum passdb_result result, struct auth_request *request); void auth_request_verify_plain_callback(enum passdb_result result, struct auth_request *request); void auth_request_lookup_credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *request); void auth_request_set_credentials(struct auth_request *request, const char *scheme, const char *data, set_credentials_callback_t *callback); void auth_request_userdb_callback(enum userdb_result result, struct auth_request *request); void auth_request_default_verify_plain_continue(struct auth_request *request, verify_plain_callback_t *callback); void auth_request_refresh_last_access(struct auth_request *request); void auth_str_append(string_t *dest, const char *key, const char *value); bool auth_request_username_accepted(const char *const *filter, const char *username); struct event_passthrough * auth_request_finished_event(struct auth_request *request, struct event *event); void auth_request_log_finished(struct auth_request *request); void auth_request_master_user_login_finish(struct auth_request *request); const char *auth_request_get_log_prefix_db(struct auth_request *auth_request); void auth_request_fields_init(struct auth_request *request); void auth_request_passdb_lookup_begin(struct auth_request *request); void auth_request_passdb_lookup_end(struct auth_request *request, enum passdb_result result); void auth_request_userdb_lookup_begin(struct auth_request *request); void auth_request_userdb_lookup_end(struct auth_request *request, enum userdb_result result); /* Fetches the current authdb event, this is done because some lookups can recurse into new lookups, requiring new event, which will be returned here. */ static inline struct event *authdb_event(const struct auth_request *request) { if (array_count(&request->authdb_event) == 0) return request->event; struct event **e = array_back_modifiable(&request->authdb_event); return *e; } #endif dovecot-2.3.21.1/src/auth/userdb.c0000644000000000000000000001474614656633576013511 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "array.h" #include "ipwd.h" #include "auth-worker-server.h" #include "userdb.h" static ARRAY(struct userdb_module_interface *) userdb_interfaces; static ARRAY(struct userdb_module *) userdb_modules; static const struct userdb_module_interface userdb_iface_deinit = { .name = "deinit" }; static struct userdb_module_interface *userdb_interface_find(const char *name) { struct userdb_module_interface *iface; array_foreach_elem(&userdb_interfaces, iface) { if (strcmp(iface->name, name) == 0) return iface; } return NULL; } void userdb_register_module(struct userdb_module_interface *iface) { struct userdb_module_interface *old_iface; old_iface = userdb_interface_find(iface->name); if (old_iface != NULL && old_iface->lookup == NULL) { /* replacing a "support not compiled in" userdb */ userdb_unregister_module(old_iface); } else if (old_iface != NULL) { i_panic("userdb_register_module(%s): Already registered", iface->name); } array_push_back(&userdb_interfaces, &iface); } void userdb_unregister_module(struct userdb_module_interface *iface) { struct userdb_module_interface *const *ifaces; unsigned int idx; array_foreach(&userdb_interfaces, ifaces) { if (*ifaces == iface) { idx = array_foreach_idx(&userdb_interfaces, ifaces); array_delete(&userdb_interfaces, idx, 1); return; } } i_panic("userdb_unregister_module(%s): Not registered", iface->name); } uid_t userdb_parse_uid(struct auth_request *request, const char *str) { struct passwd pw; uid_t uid; if (str == NULL) return (uid_t)-1; if (str_to_uid(str, &uid) == 0) return uid; switch (i_getpwnam(str, &pw)) { case -1: e_error(request == NULL ? auth_event : authdb_event(request), "getpwnam() failed: %m"); return (uid_t)-1; case 0: e_error(request == NULL ? auth_event : authdb_event(request), "Invalid UID value '%s'", str); return (uid_t)-1; default: return pw.pw_uid; } } gid_t userdb_parse_gid(struct auth_request *request, const char *str) { struct group gr; gid_t gid; if (str == NULL) return (gid_t)-1; if (str_to_gid(str, &gid) == 0) return gid; switch (i_getgrnam(str, &gr)) { case -1: e_error(request == NULL ? auth_event : authdb_event(request), "getgrnam() failed: %m"); return (gid_t)-1; case 0: e_error(request == NULL ? auth_event : authdb_event(request), "Invalid GID value '%s'", str); return (gid_t)-1; default: return gr.gr_gid; } } static struct userdb_module * userdb_find(const char *driver, const char *args, unsigned int *idx_r) { struct userdb_module *const *userdbs; unsigned int i, count; userdbs = array_get(&userdb_modules, &count); for (i = 0; i < count; i++) { if (strcmp(userdbs[i]->iface->name, driver) == 0 && strcmp(userdbs[i]->args, args) == 0) { *idx_r = i; return userdbs[i]; } } return NULL; } struct userdb_module * userdb_preinit(pool_t pool, const struct auth_userdb_settings *set) { static unsigned int auth_userdb_id = 0; struct userdb_module_interface *iface; struct userdb_module *userdb; unsigned int idx; iface = userdb_interface_find(set->driver); if (iface == NULL || iface->lookup == NULL) { /* maybe it's a plugin. try to load it. */ auth_module_load(t_strconcat("authdb_", set->driver, NULL)); iface = userdb_interface_find(set->driver); } if (iface == NULL) i_fatal("Unknown userdb driver '%s'", set->driver); if (iface->lookup == NULL) { i_fatal("Support not compiled in for userdb driver '%s'", set->driver); } if (iface->preinit == NULL && iface->init == NULL && *set->args != '\0') { i_fatal("userdb %s: No args are supported: %s", set->driver, set->args); } userdb = userdb_find(set->driver, set->args, &idx); if (userdb != NULL) return userdb; if (iface->preinit == NULL) userdb = p_new(pool, struct userdb_module, 1); else userdb = iface->preinit(pool, set->args); userdb->id = ++auth_userdb_id; userdb->iface = iface; userdb->args = p_strdup(pool, set->args); array_push_back(&userdb_modules, &userdb); return userdb; } void userdb_init(struct userdb_module *userdb) { if (userdb->iface->init != NULL && userdb->init_refcount == 0) userdb->iface->init(userdb); userdb->init_refcount++; } void userdb_deinit(struct userdb_module *userdb) { unsigned int idx; i_assert(userdb->init_refcount > 0); if (--userdb->init_refcount > 0) return; if (userdb_find(userdb->iface->name, userdb->args, &idx) == NULL) i_unreached(); array_delete(&userdb_modules, idx, 1); if (userdb->iface->deinit != NULL) userdb->iface->deinit(userdb); /* make sure userdb isn't accessed again */ userdb->iface = &userdb_iface_deinit; } void userdbs_generate_md5(unsigned char md5[STATIC_ARRAY MD5_RESULTLEN]) { struct md5_context ctx; struct userdb_module *const *userdbs; unsigned int i, count; md5_init(&ctx); userdbs = array_get(&userdb_modules, &count); for (i = 0; i < count; i++) { md5_update(&ctx, &userdbs[i]->id, sizeof(userdbs[i]->id)); md5_update(&ctx, userdbs[i]->iface->name, strlen(userdbs[i]->iface->name)); md5_update(&ctx, userdbs[i]->args, strlen(userdbs[i]->args)); } md5_final(&ctx, md5); } const char *userdb_result_to_string(enum userdb_result result) { switch (result) { case USERDB_RESULT_INTERNAL_FAILURE: return "internal_failure"; case USERDB_RESULT_USER_UNKNOWN: return "user_unknown"; case USERDB_RESULT_OK: return "ok"; } i_unreached(); } extern struct userdb_module_interface userdb_prefetch; extern struct userdb_module_interface userdb_static; extern struct userdb_module_interface userdb_passwd; extern struct userdb_module_interface userdb_passwd_file; extern struct userdb_module_interface userdb_ldap; extern struct userdb_module_interface userdb_sql; extern struct userdb_module_interface userdb_checkpassword; extern struct userdb_module_interface userdb_dict; #ifdef HAVE_LUA extern struct userdb_module_interface userdb_lua; #endif void userdbs_init(void) { i_array_init(&userdb_interfaces, 16); i_array_init(&userdb_modules, 16); userdb_register_module(&userdb_passwd); userdb_register_module(&userdb_passwd_file); userdb_register_module(&userdb_prefetch); userdb_register_module(&userdb_static); userdb_register_module(&userdb_ldap); userdb_register_module(&userdb_sql); userdb_register_module(&userdb_checkpassword); userdb_register_module(&userdb_dict); #ifdef HAVE_LUA userdb_register_module(&userdb_lua); #endif } void userdbs_deinit(void) { array_free(&userdb_modules); array_free(&userdb_interfaces); } dovecot-2.3.21.1/src/auth/crypt-blowfish.c0000644000000000000000000007703314656633576015177 00000000000000/* * The crypt_blowfish homepage is: * * http://www.openwall.com/crypt/ * * This code comes from John the Ripper password cracker, with reentrant * and crypt(3) interfaces added, but optimizations specific to password * cracking removed. * * Written by Solar Designer in 1998-2014. * No copyright is claimed, and the software is hereby placed in the public * domain. In case this attempt to disclaim copyright and place the software * in the public domain is deemed null and void, then the software is * Copyright (c) 1998-2014 Solar Designer and it is hereby released to the * general public under the following terms: * * Redistribution and use in source and binary forms, with or without * modification, are permitted. * * There's ABSOLUTELY NO WARRANTY, express or implied. * * It is my intent that you should be able to use this on your system, * as part of a software package, or anywhere else to improve security, * ensure compatibility, or for any other purpose. I would appreciate * it if you give credit where it is due and keep your modifications in * the public domain as well, but I don't require that in order to let * you place this code and any modifications you make under a license * of your choice. * * This implementation is fully compatible with OpenBSD's bcrypt.c for prefix * "$2b$", originally by Niels Provos , and it uses * some of his ideas. The password hashing algorithm was designed by David * Mazieres . For information on the level of * compatibility for bcrypt hash prefixes other than "$2b$", please refer to * the comments in BF_set_key() below and to the included crypt(3) man page. * * There's a paper on the algorithm that explains its design decisions: * * http://www.usenix.org/events/usenix99/provos.html * * Some of the tricks in BF_ROUND might be inspired by Eric Young's * Blowfish library (I can't be sure if I would think of something if I * hadn't seen his code). * * 2017-10-10 - Adapted for dovecot code by Aki Tuomi */ #include "lib.h" #include #ifndef __set_errno #define __set_errno(val) errno = (val) #endif /* Just to make sure the prototypes match the actual definitions */ #include "crypt-blowfish.h" #ifdef __i386__ #define BF_SCALE 1 #elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__) #define BF_SCALE 1 #else #define BF_SCALE 0 #endif typedef unsigned int BF_word; typedef signed int BF_word_signed; /* Number of Blowfish rounds, this is also hardcoded into a few places */ #define BF_N 16 typedef BF_word BF_key[BF_N + 2]; typedef struct { BF_word S[4][0x100]; BF_key P; } BF_ctx; /* * Magic IV for 64 Blowfish encryptions that we do at the end. * The string is "OrpheanBeholderScryDoubt" on big-endian. */ static BF_word BF_magic_w[6] = { 0x4F727068, 0x65616E42, 0x65686F6C, 0x64657253, 0x63727944, 0x6F756274 }; /* * P-box and S-box tables initialized with digits of Pi. */ static BF_ctx BF_init_state = { { { 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a }, { 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 }, { 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 }, { 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 } }, { 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b } }; static unsigned char BF_itoa64[64 + 1] = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; static unsigned char BF_atoi64[0x60] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64, 64, 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, 64, 64, 64, 64, 64, 64, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 64, 64, 64, 64, 64 }; #define BF_safe_atoi64(dst, src) \ { \ tmp = (unsigned char)(src); \ if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ tmp = BF_atoi64[tmp]; \ if (tmp > 63) return -1; \ (dst) = tmp; \ } static int BF_decode(BF_word *dst, const char *src, size_t size) { unsigned char *dptr = (unsigned char *)dst; unsigned char *end = dptr + size; const unsigned char *sptr = (const unsigned char *)src; unsigned int tmp, c1, c2, c3, c4; do { BF_safe_atoi64(c1, *sptr++); BF_safe_atoi64(c2, *sptr++); *dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4); if (dptr >= end) break; BF_safe_atoi64(c3, *sptr++); *dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2); if (dptr >= end) break; BF_safe_atoi64(c4, *sptr++); *dptr++ = ((c3 & 0x03) << 6) | c4; } while (dptr < end); return 0; } static void BF_encode(char *dst, const BF_word *src, size_t size) { const unsigned char *sptr = (const unsigned char *)src; const unsigned char *end = sptr + size; unsigned char *dptr = (unsigned char *)dst; unsigned int c1, c2; do { c1 = *sptr++; *dptr++ = BF_itoa64[c1 >> 2]; c1 = (c1 & 0x03) << 4; if (sptr >= end) { *dptr++ = BF_itoa64[c1]; break; } c2 = *sptr++; c1 |= c2 >> 4; *dptr++ = BF_itoa64[c1]; c1 = (c2 & 0x0f) << 2; if (sptr >= end) { *dptr++ = BF_itoa64[c1]; break; } c2 = *sptr++; c1 |= c2 >> 6; *dptr++ = BF_itoa64[c1]; *dptr++ = BF_itoa64[c2 & 0x3f]; } while (sptr < end); } static void ATTR_UNSIGNED_WRAPS BF_swap(BF_word *x, int count) { static int endianness_check = 1; char *is_little_endian = (char *)&endianness_check; BF_word tmp; if (*is_little_endian != 0) do { tmp = *x; tmp = (tmp << 16) | (tmp >> 16); *x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF); } while (--count > 0); } #if BF_SCALE /* Architectures which can shift addresses left by 2 bits with no extra cost */ #define BF_ROUND(L, R, N) \ tmp1 = L & 0xFF; \ tmp2 = L >> 8; \ tmp2 &= 0xFF; \ tmp3 = L >> 16; \ tmp3 &= 0xFF; \ tmp4 = L >> 24; \ tmp1 = data.ctx.S[3][tmp1]; \ tmp2 = data.ctx.S[2][tmp2]; \ tmp3 = data.ctx.S[1][tmp3]; \ tmp3 += data.ctx.S[0][tmp4]; \ tmp3 ^= tmp2; \ R ^= data.ctx.P[N + 1]; \ tmp3 += tmp1; \ R ^= tmp3; #else /* Architectures with no complicated addressing modes supported */ #define BF_INDEX(S, i) \ (*((BF_word *)(((unsigned char *)S) + (i)))) #define BF_ROUND(L, R, N) \ tmp1 = L & 0xFF; \ tmp1 <<= 2; \ tmp2 = L >> 6; \ tmp2 &= 0x3FC; \ tmp3 = L >> 14; \ tmp3 &= 0x3FC; \ tmp4 = L >> 22; \ tmp4 &= 0x3FC; \ tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \ tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \ tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \ tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \ tmp3 ^= tmp2; \ R ^= data.ctx.P[N + 1]; \ tmp3 += tmp1; \ R ^= tmp3; #endif /* * Encrypt one block, BF_N is hardcoded here. */ #define BF_ENCRYPT \ L ^= data.ctx.P[0]; \ BF_ROUND(L, R, 0); \ BF_ROUND(R, L, 1); \ BF_ROUND(L, R, 2); \ BF_ROUND(R, L, 3); \ BF_ROUND(L, R, 4); \ BF_ROUND(R, L, 5); \ BF_ROUND(L, R, 6); \ BF_ROUND(R, L, 7); \ BF_ROUND(L, R, 8); \ BF_ROUND(R, L, 9); \ BF_ROUND(L, R, 10); \ BF_ROUND(R, L, 11); \ BF_ROUND(L, R, 12); \ BF_ROUND(R, L, 13); \ BF_ROUND(L, R, 14); \ BF_ROUND(R, L, 15); \ tmp4 = R; \ R = L; \ L = tmp4 ^ data.ctx.P[BF_N + 1]; #define BF_body() \ L = R = 0; \ ptr = data.ctx.P; \ do { \ ptr += 2; \ BF_ENCRYPT; \ *(ptr - 2) = L; \ *(ptr - 1) = R; \ } while (ptr < &data.ctx.P[BF_N + 2]); \ \ ptr = data.ctx.S[0]; \ do { \ ptr += 2; \ BF_ENCRYPT; \ *(ptr - 2) = L; \ *(ptr - 1) = R; \ } while (ptr < &data.ctx.S[3][0xFF]); static void ATTR_UNSIGNED_WRAPS BF_set_key(const char *key, BF_key expanded, BF_key initial, unsigned char flags) { const char *ptr = key; unsigned int bug, i, j; BF_word safety, sign, diff, tmp[2]; /* * There was a sign extension bug in older revisions of this function. While * we would have liked to simply fix the bug and move on, we have to provide * a backwards compatibility feature (essentially the bug) for some systems and * a safety measure for some others. The latter is needed because for certain * multiple inputs to the buggy algorithm there exist easily found inputs to * the correct algorithm that produce the same hash. Thus, we optionally * deviate from the correct algorithm just enough to avoid such collisions. * While the bug itself affected the majority of passwords containing * characters with the 8th bit set (although only a percentage of those in a * collision-producing way), the anti-collision safety measure affects * only a subset of passwords containing the '\xff' character (not even all of * those passwords, just some of them). This character is not found in valid * UTF-8 sequences and is rarely used in popular 8-bit character encodings. * Thus, the safety measure is unlikely to cause much annoyance, and is a * reasonable tradeoff to use when authenticating against existing hashes that * are not reliably known to have been computed with the correct algorithm. * * We use an approach that tries to minimize side-channel leaks of password * information - that is, we mostly use fixed-cost bitwise operations instead * of branches or table lookups. (One conditional branch based on password * length remains. It is not part of the bug aftermath, though, and is * difficult and possibly unreasonable to avoid given the use of C strings by * the caller, which results in similar timing leaks anyway.) * * For actual implementation, we set an array index in the variable "bug" * (0 means no bug, 1 means sign extension bug emulation) and a flag in the * variable "safety" (bit 16 is set when the safety measure is requested). * Valid combinations of settings are: * * Prefix "$2a$": bug = 0, safety = 0x10000 * Prefix "$2b$": bug = 0, safety = 0 * Prefix "$2x$": bug = 1, safety = 0 * Prefix "$2y$": bug = 0, safety = 0 */ bug = (unsigned int)flags & 1; safety = ((BF_word)flags & 2) << 15; sign = diff = 0; for (i = 0; i < BF_N + 2; i++) { tmp[0] = tmp[1] = 0; for (j = 0; j < 4; j++) { tmp[0] <<= 8; tmp[0] |= (unsigned char)*ptr; /* correct */ tmp[1] <<= 8; tmp[1] |= (BF_word)(signed char)*ptr; /* bug */ /* * Sign extension in the first char has no effect - nothing to overwrite yet, * and those extra 24 bits will be fully shifted out of the 32-bit word. For * chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign * extension in tmp[1] occurs. Once this flag is set, it remains set. */ if (j != 0) sign |= tmp[1] & 0x80; if (*ptr == '\0') ptr = key; else ptr++; } diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */ expanded[i] = tmp[bug]; initial[i] = BF_init_state.P[i] ^ tmp[bug]; } /* * At this point, "diff" is zero iff the correct and buggy algorithms produced * exactly the same result. If so and if "sign" is non-zero, which indicates * that there was a non-benign sign extension, this means that we have a * collision between the correctly computed hash for this password and a set of * passwords that could be supplied to the buggy algorithm. Our safety measure * is meant to protect from such many-buggy to one-correct collisions, by * deviating from the correct algorithm in such cases. Let's check for this. */ diff |= diff >> 16; /* still zero iff exact match */ diff &= 0xffff; /* ditto */ diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */ sign <<= 9; /* move the non-benign sign extension flag to bit 16 */ sign &= ~diff & safety; /* action needed? */ /* * If we have determined that we need to deviate from the correct algorithm, * flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but * let's stick to it now. It came out of the approach we used above, and it's * not any worse than any other choice we could make.) * * It is crucial that we don't do the same to the expanded key used in the main * Eksblowfish loop. By doing it to only one of these two, we deviate from a * state that could be directly specified by a password to the buggy algorithm * (and to the fully correct one as well, but that's a side-effect). */ initial[0] ^= sign; } static const unsigned char flags_by_subtype[26] = {2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0}; static char * ATTR_NO_SANITIZE_UNDEFINED ATTR_NO_SANITIZE_INTEGER ATTR_NO_SANITIZE_IMPLICIT_CONVERSION BF_crypt(const char *key, const char *setting, char *output, size_t size, BF_word min) { struct { BF_ctx ctx; BF_key expanded_key; union { BF_word salt[4]; BF_word output[6]; } binary; } data; BF_word L, R; BF_word tmp1, tmp2, tmp3, tmp4; BF_word *ptr; BF_word count; int i; if (size < 7 + 22 + 31 + 1) { __set_errno(ERANGE); return NULL; } if (setting[0] != '$' || setting[1] != '2' || setting[2] < 'a' || setting[2] > 'z' || flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] == 0 || setting[3] != '$' || setting[4] < '0' || setting[4] > '3' || setting[5] < '0' || setting[5] > '9' || (setting[4] == '3' && setting[5] > '1') || setting[6] != '$') { __set_errno(EINVAL); return NULL; } count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0')); if (count < min || BF_decode(data.binary.salt, &setting[7], 16) != 0) { __set_errno(EINVAL); return NULL; } BF_swap(data.binary.salt, 4); BF_set_key(key, data.expanded_key, data.ctx.P, flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']); memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S)); L = R = 0; for (i = 0; i < BF_N + 2; i += 2) { L ^= data.binary.salt[i & 2]; R ^= data.binary.salt[(i & 2) + 1]; BF_ENCRYPT; data.ctx.P[i] = L; data.ctx.P[i + 1] = R; } ptr = data.ctx.S[0]; do { ptr += 4; L ^= data.binary.salt[(BF_N + 2) & 3]; R ^= data.binary.salt[(BF_N + 3) & 3]; BF_ENCRYPT; *(ptr - 4) = L; *(ptr - 3) = R; L ^= data.binary.salt[(BF_N + 4) & 3]; R ^= data.binary.salt[(BF_N + 5) & 3]; BF_ENCRYPT; *(ptr - 2) = L; *(ptr - 1) = R; } while (ptr < &data.ctx.S[3][0xFF]); do { bool done; for (i = 0; i < BF_N + 2; i += 2) { data.ctx.P[i] ^= data.expanded_key[i]; data.ctx.P[i + 1] ^= data.expanded_key[i + 1]; } done = FALSE; do { BF_body(); if (done) break; done = TRUE; tmp1 = data.binary.salt[0]; tmp2 = data.binary.salt[1]; tmp3 = data.binary.salt[2]; tmp4 = data.binary.salt[3]; for (i = 0; i < BF_N; i += 4) { data.ctx.P[i] ^= tmp1; data.ctx.P[i + 1] ^= tmp2; data.ctx.P[i + 2] ^= tmp3; data.ctx.P[i + 3] ^= tmp4; } data.ctx.P[16] ^= tmp1; data.ctx.P[17] ^= tmp2; } while (TRUE); } while (--count > 0); for (i = 0; i < 6; i += 2) { L = BF_magic_w[i]; R = BF_magic_w[i + 1]; count = 64; do { BF_ENCRYPT; } while (--count > 0); data.binary.output[i] = L; data.binary.output[i + 1] = R; } memcpy(output, setting, 7 + 22 - 1); output[7 + 22 - 1] = BF_itoa64[(int) BF_atoi64[(int)setting[7 + 22 - 1] - 0x20] & 0x30]; /* This has to be bug-compatible with the original implementation, so * only encode 23 of the 24 bytes. :-) */ BF_swap(data.binary.output, 6); BF_encode(&output[7 + 22], data.binary.output, 23); output[7 + 22 + 31] = '\0'; return output; } int crypt_output_magic(const char *setting, char *output, size_t size) { if (size < 3) return -1; output[0] = '*'; output[1] = '0'; output[2] = '\0'; if (setting[0] == '*' && setting[1] == '0') output[1] = '1'; return 0; } /* * Please preserve the runtime self-test. It serves two purposes at once: * * 1. We really can't afford the risk of producing incompatible hashes e.g. * when there's something like gcc bug 26587 again, whereas an application or * library integrating this code might not also integrate our external tests or * it might not run them after every build. Even if it does, the miscompile * might only occur on the production build, but not on a testing build (such * as because of different optimization settings). It is painful to recover * from incorrectly-computed hashes - merely fixing whatever broke is not * enough. Thus, a proactive measure like this self-test is needed. * * 2. We don't want to leave sensitive data from our actual password hash * computation on the stack or in registers. Previous revisions of the code * would do explicit cleanups, but simply running the self-test after hash * computation is more reliable. * * The performance cost of this quick self-test is around 0.6% at the "$2a$08" * setting. */ char *crypt_blowfish_rn(const char *key, const char *setting, char *output, size_t size) { const char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8"; const char *test_setting = "$2a$00$abcdefghijklmnopqrstuu"; static const char * const test_hashes[2] = {"i1D709vfamulimlGcq0qq3UvuUasvEa\0\x55", /* 'a', 'b', 'y' */ "VUrPmXD6q/nVSSp7pNDhCR9071IfIRe\0\x55"}; /* 'x' */ const char *test_hash = test_hashes[0]; char *retval; const char *p; int save_errno; bool ok; struct { char s[7 + 22 + 1]; char o[7 + 22 + 31 + 1 + 1 + 1]; } buf; /* Hash the supplied password */ crypt_output_magic(setting, output, size); retval = BF_crypt(key, setting, output, size, 16); save_errno = errno; /* * Do a quick self-test. It is important that we make both calls to BF_crypt() * from the same scope such that they likely use the same stack locations, * which makes the second call overwrite the first call's sensitive data on the * stack and makes it more likely that any alignment related issues would be * detected by the self-test. */ memcpy(buf.s, test_setting, sizeof(buf.s)); if (retval != NULL) { unsigned int flags = flags_by_subtype[ (unsigned int)(unsigned char)setting[2] - 'a']; test_hash = test_hashes[flags & 1]; buf.s[2] = setting[2]; } memset(buf.o, 0x55, sizeof(buf.o)); buf.o[sizeof(buf.o) - 1] = 0; p = BF_crypt(test_key, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1); ok = (p == buf.o && memcmp(p, buf.s, 7 + 22) == 0 && memcmp(p + (7 + 22), test_hash, 31 + 1 + 1 + 1) == 0); { const char *k = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"; BF_key ae, ai, ye, yi; BF_set_key(k, ae, ai, 2); /* $2a$ */ BF_set_key(k, ye, yi, 4); /* $2y$ */ ai[0] ^= 0x10000; /* undo the safety (for comparison) */ ok = ok && ai[0] == 0xdb9c59bc && ye[17] == 0x33343500 && memcmp(ae, ye, sizeof(ae)) == 0 && memcmp(ai, yi, sizeof(ai)) == 0; } __set_errno(save_errno); if (ok) return retval; i_unreached(); } char *crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, const char *input, size_t size, char *output, size_t output_size) { if (size < 16 || output_size < 7 + 22 + 1 || (count > 0 && (count < 4 || count > 31)) || prefix[0] != '$' || prefix[1] != '2' || (prefix[2] != 'a' && prefix[2] != 'b' && prefix[2] != 'y')) { if (output_size > 0) output[0] = '\0'; __set_errno((output_size < 7 + 22 + 1) ? ERANGE : EINVAL); return NULL; } if (count == 0) count = 5; output[0] = '$'; output[1] = '2'; output[2] = prefix[2]; output[3] = '$'; output[4] = '0' + count / 10; output[5] = '0' + count % 10; output[6] = '$'; BF_encode(&output[7], (const BF_word *)input, 16); output[7 + 22] = '\0'; return output; } dovecot-2.3.21.1/src/auth/password-scheme-otp.c0000644000000000000000000000172514656633576016122 00000000000000/* * OTP password scheme. * * Copyright (c) 2006 Andrey Panin * * This software is released under the MIT license. */ #include "lib.h" #include "hex-binary.h" #include "password-scheme.h" #include "randgen.h" #include "otp.h" int password_generate_otp(const char *pw, const char *state_data, unsigned int algo, const char **result_r) { struct otp_state state; if (state_data != NULL) { if (otp_parse_dbentry(state_data, &state) != 0) return -1; } else { /* Generate new OTP credentials from plaintext */ unsigned char random_data[OTP_MAX_SEED_LEN / 2]; const char *random_hex; random_fill(random_data, sizeof(random_data)); random_hex = binary_to_hex(random_data, sizeof(random_data)); if (i_strocpy(state.seed, random_hex, sizeof(state.seed)) < 0) i_unreached(); state.seq = 1024; state.algo = algo; } otp_hash(state.algo, state.seed, pw, state.seq, state.hash); *result_r = otp_print_dbentry(&state); return 0; } dovecot-2.3.21.1/src/auth/checkpassword-reply.c0000644000000000000000000000611114656633576016201 00000000000000/* simple checkpassword wrapper to send userdb data back to dovecot-auth */ #include "lib.h" #include "str.h" #include "strescape.h" #include "write-full.h" #include int main(void) { string_t *str; const char *user, *home, *authorized, *orig_uid_env; const char *extra_env, *key, *value, *const *tmp; bool uid_found = FALSE, gid_found = FALSE; uid_t orig_uid; lib_init(); str = t_str_new(1024); orig_uid_env = getenv("ORIG_UID"); if (orig_uid_env == NULL || str_to_uid(orig_uid_env, &orig_uid) < 0) orig_uid = (uid_t)-1; /* ORIG_UID should have the auth process's UID that forked us. if the checkpassword changed the UID, this could be a security hole because the UID's other processes can ptrace this process and write any kind of a reply to fd 4. so we can run only if: a) INSECURE_SETUID environment is set. b) process isn't ptraceable (this binary is setuid/setgid) c) checkpassword didn't actually change the UID (but used userdb_uid instead) */ if (getenv("INSECURE_SETUID") == NULL && (orig_uid == (uid_t)-1 || orig_uid != getuid()) && getuid() == geteuid() && getgid() == getegid()) { if (orig_uid_env == NULL) { i_error("checkpassword: ORIG_UID environment was dropped by checkpassword. " "Can't verify if we're safe to run. See " "http://wiki2.dovecot.org/AuthDatabase/CheckPassword#Security"); } else { i_error("checkpassword: The checkpassword couldn't be run securely. See " "http://wiki2.dovecot.org/AuthDatabase/CheckPassword#Security"); } return 111; } user = getenv("USER"); if (user != NULL) { if (strchr(user, '\t') != NULL) { i_error("checkpassword: USER contains TAB"); return 1; } str_printfa(str, "user="); str_append_tabescaped(str, user); str_append_c(str, '\t'); } home = getenv("HOME"); if (home != NULL) { if (strchr(home, '\t') != NULL) { i_error("checkpassword: HOME contains TAB"); return 1; } str_printfa(str, "userdb_home="); str_append_tabescaped(str, home); str_append_c(str, '\t'); } extra_env = getenv("EXTRA"); if (extra_env != NULL) { for (tmp = t_strsplit(extra_env, " "); *tmp != NULL; tmp++) { value = getenv(*tmp); if (value != NULL) { key = t_str_lcase(*tmp); if (strcmp(key, "userdb_uid") == 0) uid_found = TRUE; else if (strcmp(key, "userdb_gid") == 0) gid_found = TRUE; str_append_tabescaped(str, key); str_append_c(str, '='); str_append_tabescaped(str, value); str_append_c(str, '\t'); } } } if (!uid_found) str_printfa(str, "userdb_uid=%s\t", dec2str(getuid())); if (!gid_found) str_printfa(str, "userdb_gid=%s\t", dec2str(getgid())); i_assert(str_len(str) > 0); if (write_full(4, str_data(str), str_len(str)) < 0) { i_error("checkpassword: write_full() failed: %m"); lib_exit(111); } authorized = getenv("AUTHORIZED"); if (authorized == NULL) { /* authentication */ return 0; } else if (strcmp(authorized, "2") == 0) { /* successful passdb/userdb lookup */ return 2; } else { i_error("checkpassword: Script doesn't support passdb/userdb lookup"); return 111; } } dovecot-2.3.21.1/src/auth/mech-cram-md5.c0000644000000000000000000001115014656633576014526 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ /* CRAM-MD5 SASL authentication, see RFC-2195 Joshua Goodall */ #include "auth-common.h" #include "ioloop.h" #include "buffer.h" #include "hex-binary.h" #include "hmac-cram-md5.h" #include "hmac.h" #include "md5.h" #include "randgen.h" #include "mech.h" #include "passdb.h" #include "hostpid.h" #include struct cram_auth_request { struct auth_request auth_request; pool_t pool; /* requested: */ char *challenge; /* received: */ char *username; char *response; unsigned long maxbuf; }; static const char *get_cram_challenge(void) { unsigned char buf[17]; size_t i; random_fill(buf, sizeof(buf)-1); for (i = 0; i < sizeof(buf)-1; i++) buf[i] = (buf[i] % 10) + '0'; buf[sizeof(buf)-1] = '\0'; return t_strdup_printf("<%s.%s@%s>", (const char *)buf, dec2str(ioloop_time), my_hostname); } static bool verify_credentials(struct cram_auth_request *request, const unsigned char *credentials, size_t size) { unsigned char digest[MD5_RESULTLEN]; struct hmac_context ctx; const char *response_hex; if (size != CRAM_MD5_CONTEXTLEN) { e_error(request->auth_request.mech_event, "invalid credentials length"); return FALSE; } hmac_init(&ctx, NULL, 0, &hash_method_md5); hmac_md5_set_cram_context(&ctx, credentials); hmac_update(&ctx, request->challenge, strlen(request->challenge)); hmac_final(&ctx, digest); response_hex = binary_to_hex(digest, sizeof(digest)); if (!mem_equals_timing_safe(response_hex, request->response, sizeof(digest)*2)) { e_info(request->auth_request.mech_event, AUTH_LOG_MSG_PASSWORD_MISMATCH); return FALSE; } return TRUE; } static bool parse_cram_response(struct cram_auth_request *request, const unsigned char *data, size_t size, const char **error_r) { size_t i, space; *error_r = NULL; /* SPACE . Username may contain spaces, so assume the rightmost space is the response separator. */ for (i = space = 0; i < size; i++) { if (data[i] == '\0') { *error_r = "NULs in response"; return FALSE; } if (data[i] == ' ') space = i; } if (space == 0) { *error_r = "missing digest"; return FALSE; } request->username = p_strndup(request->pool, data, space); space++; request->response = p_strndup(request->pool, data + space, size - space); return TRUE; } static void credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *auth_request) { struct cram_auth_request *request = (struct cram_auth_request *)auth_request; switch (result) { case PASSDB_RESULT_OK: if (verify_credentials(request, credentials, size)) auth_request_success(auth_request, "", 0); else auth_request_fail(auth_request); break; case PASSDB_RESULT_INTERNAL_FAILURE: auth_request_internal_failure(auth_request); break; default: auth_request_fail(auth_request); break; } } static void mech_cram_md5_auth_continue(struct auth_request *auth_request, const unsigned char *data, size_t data_size) { struct cram_auth_request *request = (struct cram_auth_request *)auth_request; const char *error; if (parse_cram_response(request, data, data_size, &error)) { if (auth_request_set_username(auth_request, request->username, &error)) { auth_request_lookup_credentials(auth_request, "CRAM-MD5", credentials_callback); return; } } if (error == NULL) error = "authentication failed"; e_info(auth_request->mech_event, "%s", error); auth_request_fail(auth_request); } static void mech_cram_md5_auth_initial(struct auth_request *auth_request, const unsigned char *data ATTR_UNUSED, size_t data_size ATTR_UNUSED) { struct cram_auth_request *request = (struct cram_auth_request *)auth_request; request->challenge = p_strdup(request->pool, get_cram_challenge()); auth_request_handler_reply_continue(auth_request, request->challenge, strlen(request->challenge)); } static struct auth_request *mech_cram_md5_auth_new(void) { struct cram_auth_request *request; pool_t pool; pool = pool_alloconly_create(MEMPOOL_GROWING"cram_md5_auth_request", 2048); request = p_new(pool, struct cram_auth_request, 1); request->pool = pool; request->auth_request.pool = pool; return &request->auth_request; } const struct mech_module mech_cram_md5 = { "CRAM-MD5", .flags = MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE, .passdb_need = MECH_PASSDB_NEED_VERIFY_RESPONSE, mech_cram_md5_auth_new, mech_cram_md5_auth_initial, mech_cram_md5_auth_continue, mech_generic_auth_free }; dovecot-2.3.21.1/src/auth/db-ldap.c0000644000000000000000000015247714656633576013534 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #if defined(BUILTIN_LDAP) || defined(PLUGIN_BUILD) #include "net.h" #include "ioloop.h" #include "array.h" #include "hash.h" #include "aqueue.h" #include "str.h" #include "time-util.h" #include "env-util.h" #include "var-expand.h" #include "settings.h" #include "userdb.h" #include "db-ldap.h" #include #include #define HAVE_LDAP_SASL #ifdef HAVE_SASL_SASL_H # include #elif defined (HAVE_SASL_H) # include #else # undef HAVE_LDAP_SASL #endif #ifdef LDAP_OPT_X_TLS # define OPENLDAP_TLS_OPTIONS #endif #if !defined(SASL_VERSION_MAJOR) || SASL_VERSION_MAJOR < 2 # undef HAVE_LDAP_SASL #endif #ifndef LDAP_SASL_QUIET # define LDAP_SASL_QUIET 0 /* Doesn't exist in Solaris LDAP */ #endif /* Older versions may require calling ldap_result() twice */ #if LDAP_VENDOR_VERSION <= 20112 # define OPENLDAP_ASYNC_WORKAROUND #endif /* Solaris LDAP library doesn't have LDAP_OPT_SUCCESS */ #ifndef LDAP_OPT_SUCCESS # define LDAP_OPT_SUCCESS LDAP_SUCCESS #endif #define DB_LDAP_REQUEST_MAX_ATTEMPT_COUNT 3 static const char *LDAP_ESCAPE_CHARS = "*,\\#+<>;\"()= "; struct db_ldap_result { int refcount; LDAPMessage *msg; }; struct db_ldap_value { const char **values; bool used; }; struct db_ldap_result_iterate_context { pool_t pool; struct ldap_request *ldap_request; const ARRAY_TYPE(ldap_field) *attr_map; unsigned int attr_idx; /* attribute name => value */ HASH_TABLE(char *, struct db_ldap_value *) ldap_attrs; const char *val_1_arr[2]; string_t *var, *debug; bool skip_null_values; bool iter_dn_values; LDAPMessage *ldap_msg; LDAP *ld; }; struct db_ldap_sasl_bind_context { const char *authcid; const char *passwd; const char *realm; const char *authzid; }; #define DEF_STR(name) DEF_STRUCT_STR(name, ldap_settings) #define DEF_INT(name) DEF_STRUCT_INT(name, ldap_settings) #define DEF_BOOL(name) DEF_STRUCT_BOOL(name, ldap_settings) static struct setting_def setting_defs[] = { DEF_STR(hosts), DEF_STR(uris), DEF_STR(dn), DEF_STR(dnpass), DEF_BOOL(auth_bind), DEF_STR(auth_bind_userdn), DEF_BOOL(tls), DEF_BOOL(sasl_bind), DEF_STR(sasl_mech), DEF_STR(sasl_realm), DEF_STR(sasl_authz_id), DEF_STR(tls_ca_cert_file), DEF_STR(tls_ca_cert_dir), DEF_STR(tls_cert_file), DEF_STR(tls_key_file), DEF_STR(tls_cipher_suite), DEF_STR(tls_require_cert), DEF_STR(deref), DEF_STR(scope), DEF_STR(base), DEF_INT(ldap_version), DEF_STR(debug_level), DEF_STR(ldaprc_path), DEF_STR(user_attrs), DEF_STR(user_filter), DEF_STR(pass_attrs), DEF_STR(pass_filter), DEF_STR(iterate_attrs), DEF_STR(iterate_filter), DEF_STR(default_pass_scheme), DEF_BOOL(userdb_warning_disable), DEF_BOOL(blocking), { 0, NULL, 0 } }; static struct ldap_settings default_ldap_settings = { .hosts = NULL, .uris = NULL, .dn = NULL, .dnpass = NULL, .auth_bind = FALSE, .auth_bind_userdn = NULL, .tls = FALSE, .sasl_bind = FALSE, .sasl_mech = NULL, .sasl_realm = NULL, .sasl_authz_id = NULL, .tls_ca_cert_file = NULL, .tls_ca_cert_dir = NULL, .tls_cert_file = NULL, .tls_key_file = NULL, .tls_cipher_suite = NULL, .tls_require_cert = NULL, .deref = "never", .scope = "subtree", .base = NULL, .ldap_version = 3, .debug_level = "0", .ldaprc_path = "", .user_attrs = "homeDirectory=home,uidNumber=uid,gidNumber=gid", .user_filter = "(&(objectClass=posixAccount)(uid=%u))", .pass_attrs = "uid=user,userPassword=password", .pass_filter = "(&(objectClass=posixAccount)(uid=%u))", .iterate_attrs = "uid=user", .iterate_filter = "(objectClass=posixAccount)", .default_pass_scheme = "crypt", .userdb_warning_disable = FALSE, .blocking = FALSE }; static struct ldap_connection *ldap_connections = NULL; static int db_ldap_bind(struct ldap_connection *conn); static void db_ldap_conn_close(struct ldap_connection *conn); struct db_ldap_result_iterate_context * db_ldap_result_iterate_init_full(struct ldap_connection *conn, struct ldap_request_search *ldap_request, LDAPMessage *res, bool skip_null_values, bool iter_dn_values); static bool db_ldap_abort_requests(struct ldap_connection *conn, unsigned int max_count, unsigned int timeout_secs, bool error, const char *reason); static void db_ldap_request_free(struct ldap_request *request); static int deref2str(const char *str, int *ref_r) { if (strcasecmp(str, "never") == 0) *ref_r = LDAP_DEREF_NEVER; else if (strcasecmp(str, "searching") == 0) *ref_r = LDAP_DEREF_SEARCHING; else if (strcasecmp(str, "finding") == 0) *ref_r = LDAP_DEREF_FINDING; else if (strcasecmp(str, "always") == 0) *ref_r = LDAP_DEREF_ALWAYS; else return -1; return 0; } static int scope2str(const char *str, int *scope_r) { if (strcasecmp(str, "base") == 0) *scope_r = LDAP_SCOPE_BASE; else if (strcasecmp(str, "onelevel") == 0) *scope_r = LDAP_SCOPE_ONELEVEL; else if (strcasecmp(str, "subtree") == 0) *scope_r = LDAP_SCOPE_SUBTREE; else return -1; return 0; } #ifdef OPENLDAP_TLS_OPTIONS static int tls_require_cert2str(const char *str, int *value_r) { if (strcasecmp(str, "never") == 0) *value_r = LDAP_OPT_X_TLS_NEVER; else if (strcasecmp(str, "hard") == 0) *value_r = LDAP_OPT_X_TLS_HARD; else if (strcasecmp(str, "demand") == 0) *value_r = LDAP_OPT_X_TLS_DEMAND; else if (strcasecmp(str, "allow") == 0) *value_r = LDAP_OPT_X_TLS_ALLOW; else if (strcasecmp(str, "try") == 0) *value_r = LDAP_OPT_X_TLS_TRY; else return -1; return 0; } #endif static int ldap_get_errno(struct ldap_connection *conn) { int ret, err; ret = ldap_get_option(conn->ld, LDAP_OPT_ERROR_NUMBER, (void *) &err); if (ret != LDAP_SUCCESS) { e_error(conn->event, "Can't get error number: %s", ldap_err2string(ret)); return LDAP_UNAVAILABLE; } return err; } const char *ldap_get_error(struct ldap_connection *conn) { const char *ret; char *str = NULL; ret = ldap_err2string(ldap_get_errno(conn)); ldap_get_option(conn->ld, LDAP_OPT_ERROR_STRING, (void *)&str); if (str != NULL) { ret = t_strconcat(ret, ", ", str, NULL); ldap_memfree(str); } ldap_set_option(conn->ld, LDAP_OPT_ERROR_STRING, NULL); return ret; } static void ldap_conn_reconnect(struct ldap_connection *conn) { db_ldap_conn_close(conn); if (db_ldap_connect(conn) < 0) db_ldap_conn_close(conn); } static int ldap_handle_error(struct ldap_connection *conn) { int err = ldap_get_errno(conn); switch (err) { case LDAP_SUCCESS: i_unreached(); case LDAP_SIZELIMIT_EXCEEDED: case LDAP_TIMELIMIT_EXCEEDED: case LDAP_NO_SUCH_ATTRIBUTE: case LDAP_UNDEFINED_TYPE: case LDAP_INAPPROPRIATE_MATCHING: case LDAP_CONSTRAINT_VIOLATION: case LDAP_TYPE_OR_VALUE_EXISTS: case LDAP_INVALID_SYNTAX: case LDAP_NO_SUCH_OBJECT: case LDAP_ALIAS_PROBLEM: case LDAP_INVALID_DN_SYNTAX: case LDAP_IS_LEAF: case LDAP_ALIAS_DEREF_PROBLEM: case LDAP_FILTER_ERROR: /* invalid input */ return -1; case LDAP_SERVER_DOWN: case LDAP_TIMEOUT: case LDAP_UNAVAILABLE: case LDAP_BUSY: #ifdef LDAP_CONNECT_ERROR case LDAP_CONNECT_ERROR: #endif case LDAP_LOCAL_ERROR: case LDAP_INVALID_CREDENTIALS: case LDAP_OPERATIONS_ERROR: default: /* connection problems */ ldap_conn_reconnect(conn); return 0; } } static int db_ldap_request_bind(struct ldap_connection *conn, struct ldap_request *request) { struct ldap_request_bind *brequest = (struct ldap_request_bind *)request; i_assert(request->type == LDAP_REQUEST_TYPE_BIND); i_assert(request->msgid == -1); i_assert(conn->conn_state == LDAP_CONN_STATE_BOUND_AUTH || conn->conn_state == LDAP_CONN_STATE_BOUND_DEFAULT); i_assert(conn->pending_count == 0); request->msgid = ldap_bind(conn->ld, brequest->dn, request->auth_request->mech_password, LDAP_AUTH_SIMPLE); if (request->msgid == -1) { e_error(authdb_event(request->auth_request), "ldap_bind(%s) failed: %s", brequest->dn, ldap_get_error(conn)); if (ldap_handle_error(conn) < 0) { /* broken request, remove it */ return 0; } return -1; } conn->conn_state = LDAP_CONN_STATE_BINDING; return 1; } static int db_ldap_request_search(struct ldap_connection *conn, struct ldap_request *request) { struct ldap_request_search *srequest = (struct ldap_request_search *)request; i_assert(conn->conn_state == LDAP_CONN_STATE_BOUND_DEFAULT); i_assert(request->msgid == -1); request->msgid = ldap_search(conn->ld, *srequest->base == '\0' ? NULL : srequest->base, conn->set.ldap_scope, srequest->filter, srequest->attributes, 0); if (request->msgid == -1) { e_error(authdb_event(request->auth_request), "ldap_search(%s) parsing failed: %s", srequest->filter, ldap_get_error(conn)); if (ldap_handle_error(conn) < 0) { /* broken request, remove it */ return 0; } return -1; } return 1; } static bool db_ldap_request_queue_next(struct ldap_connection *conn) { struct ldap_request *request; int ret = -1; /* connecting may call db_ldap_connect_finish(), which gets us back here. so do the connection before checking the request queue. */ if (db_ldap_connect(conn) < 0) return FALSE; if (conn->pending_count == aqueue_count(conn->request_queue)) { /* no non-pending requests */ return FALSE; } if (conn->pending_count > DB_LDAP_MAX_PENDING_REQUESTS) { /* wait until server has replied to some requests */ return FALSE; } request = array_idx_elem(&conn->request_array, aqueue_idx(conn->request_queue, conn->pending_count)); if (conn->pending_count > 0 && request->type == LDAP_REQUEST_TYPE_BIND) { /* we can't do binds until all existing requests are finished */ return FALSE; } switch (conn->conn_state) { case LDAP_CONN_STATE_DISCONNECTED: case LDAP_CONN_STATE_BINDING: /* wait until we're in bound state */ return FALSE; case LDAP_CONN_STATE_BOUND_AUTH: if (request->type == LDAP_REQUEST_TYPE_BIND) break; /* bind to default dn first */ i_assert(conn->pending_count == 0); (void)db_ldap_bind(conn); return FALSE; case LDAP_CONN_STATE_BOUND_DEFAULT: /* we can do anything in this state */ break; } if (request->send_count >= DB_LDAP_REQUEST_MAX_ATTEMPT_COUNT) { /* Enough many times retried. Server just keeps disconnecting whenever attempting to send the request. */ ret = 0; } else { /* clear away any partial results saved before reconnecting */ db_ldap_request_free(request); switch (request->type) { case LDAP_REQUEST_TYPE_BIND: ret = db_ldap_request_bind(conn, request); break; case LDAP_REQUEST_TYPE_SEARCH: ret = db_ldap_request_search(conn, request); break; } } if (ret > 0) { /* success */ i_assert(request->msgid != -1); request->send_count++; conn->pending_count++; return TRUE; } else if (ret < 0) { /* disconnected */ return FALSE; } else { /* broken request, remove from queue */ aqueue_delete_tail(conn->request_queue); request->callback(conn, request, NULL); return TRUE; } } static void db_ldap_check_hanging(struct ldap_connection *conn) { struct ldap_request *first_request; unsigned int count; time_t secs_diff; count = aqueue_count(conn->request_queue); if (count == 0) return; first_request = array_idx_elem(&conn->request_array, aqueue_idx(conn->request_queue, 0)); secs_diff = ioloop_time - first_request->create_time; if (secs_diff > DB_LDAP_REQUEST_LOST_TIMEOUT_SECS) { db_ldap_abort_requests(conn, UINT_MAX, 0, TRUE, "LDAP connection appears to be hanging"); ldap_conn_reconnect(conn); } } void db_ldap_request(struct ldap_connection *conn, struct ldap_request *request) { i_assert(request->auth_request != NULL); request->msgid = -1; request->create_time = ioloop_time; db_ldap_check_hanging(conn); aqueue_append(conn->request_queue, &request); (void)db_ldap_request_queue_next(conn); } static int db_ldap_connect_finish(struct ldap_connection *conn, int ret) { if (ret == LDAP_SERVER_DOWN) { e_error(conn->event, "Can't connect to server: %s", conn->set.uris != NULL ? conn->set.uris : conn->set.hosts); return -1; } if (ret != LDAP_SUCCESS) { e_error(conn->event, "binding failed (dn %s): %s", conn->set.dn == NULL ? "(none)" : conn->set.dn, ldap_get_error(conn)); return -1; } timeout_remove(&conn->to); conn->conn_state = LDAP_CONN_STATE_BOUND_DEFAULT; while (db_ldap_request_queue_next(conn)) ; return 0; } static void db_ldap_default_bind_finished(struct ldap_connection *conn, struct db_ldap_result *res) { int ret; i_assert(conn->pending_count == 0); conn->default_bind_msgid = -1; ret = ldap_result2error(conn->ld, res->msg, FALSE); if (db_ldap_connect_finish(conn, ret) < 0) { /* lost connection, close it */ db_ldap_conn_close(conn); } } static bool db_ldap_abort_requests(struct ldap_connection *conn, unsigned int max_count, unsigned int timeout_secs, bool error, const char *reason) { struct ldap_request *request; time_t diff; bool aborts = FALSE; while (aqueue_count(conn->request_queue) > 0 && max_count > 0) { request = array_idx_elem(&conn->request_array, aqueue_idx(conn->request_queue, 0)); diff = ioloop_time - request->create_time; if (diff < (time_t)timeout_secs) break; /* timed out, abort */ aqueue_delete_tail(conn->request_queue); if (request->msgid != -1) { i_assert(conn->pending_count > 0); conn->pending_count--; } if (error) { e_error(authdb_event(request->auth_request), "%s", reason); } else { e_info(authdb_event(request->auth_request), "%s", reason); } request->callback(conn, request, NULL); max_count--; aborts = TRUE; } return aborts; } static struct ldap_request * db_ldap_find_request(struct ldap_connection *conn, int msgid, unsigned int *idx_r) { struct ldap_request *const *requests, *request = NULL; unsigned int i, count; count = aqueue_count(conn->request_queue); if (count == 0) return NULL; requests = array_front(&conn->request_array); for (i = 0; i < count; i++) { request = requests[aqueue_idx(conn->request_queue, i)]; if (request->msgid == msgid) { *idx_r = i; return request; } if (request->msgid == -1) break; } return NULL; } static int db_ldap_fields_get_dn(struct ldap_connection *conn, struct ldap_request_search *request, struct db_ldap_result *res) { struct auth_request *auth_request = request->request.auth_request; struct ldap_request_named_result *named_res; struct db_ldap_result_iterate_context *ldap_iter; const char *name, *const *values; ldap_iter = db_ldap_result_iterate_init_full(conn, request, res->msg, TRUE, TRUE); while (db_ldap_result_iterate_next(ldap_iter, &name, &values)) { if (values[1] != NULL) { e_warning(authdb_event(auth_request), "Multiple values found for '%s', " "using value '%s'", name, values[0]); } array_foreach_modifiable(&request->named_results, named_res) { if (strcmp(named_res->field->name, name) != 0) continue; /* In future we could also support LDAP URLs here */ named_res->dn = p_strdup(auth_request->pool, values[0]); } } db_ldap_result_iterate_deinit(&ldap_iter); return 0; } struct ldap_field_find_subquery_context { ARRAY_TYPE(string) attr_names; const char *name; }; static int db_ldap_field_subquery_find(const char *data, void *context, const char **value_r, const char **error_r ATTR_UNUSED) { struct ldap_field_find_subquery_context *ctx = context; char *ldap_attr; const char *p; if (*data != '\0') { data = t_strcut(data, ':'); p = strchr(data, '@'); if (p != NULL && strcmp(p+1, ctx->name) == 0) { ldap_attr = p_strdup_until(unsafe_data_stack_pool, data, p); array_push_back(&ctx->attr_names, &ldap_attr); } } *value_r = NULL; return 1; } static int ldap_request_send_subquery(struct ldap_connection *conn, struct ldap_request_search *request, struct ldap_request_named_result *named_res) { const struct ldap_field *field; const char *p, *error; char *name; struct auth_request *auth_request = request->request.auth_request; struct ldap_field_find_subquery_context ctx; const struct var_expand_table *table = auth_request_get_var_expand_table(auth_request, NULL); const struct var_expand_func_table *ptr; struct var_expand_func_table *ftable; string_t *tmp_str = t_str_new(64); ARRAY(struct var_expand_func_table) var_funcs_table; t_array_init(&var_funcs_table, 8); for(ptr = auth_request_var_funcs_table; ptr->key != NULL; ptr++) { array_push_back(&var_funcs_table, ptr); } ftable = array_append_space(&var_funcs_table); ftable->key = "ldap"; ftable->func = db_ldap_field_subquery_find; ftable = array_append_space(&var_funcs_table); ftable->key = "ldap_ptr"; ftable->func = db_ldap_field_subquery_find; array_append_zero(&var_funcs_table); i_zero(&ctx); t_array_init(&ctx.attr_names, 8); ctx.name = named_res->field->name; /* get the attributes names into array (ldapAttr@name -> ldapAttr) */ array_foreach(request->attr_map, field) { if (field->ldap_attr_name[0] == '\0') { str_truncate(tmp_str, 0); if (var_expand_with_funcs(tmp_str, field->value, table, array_front(&var_funcs_table), &ctx, &error) <= 0) { e_error(authdb_event(auth_request), "Failed to expand subquery %s: %s", field->value, error); return -1; } } else { p = strchr(field->ldap_attr_name, '@'); if (p != NULL && strcmp(p+1, named_res->field->name) == 0) { name = p_strdup_until(unsafe_data_stack_pool, field->ldap_attr_name, p); array_push_back(&ctx.attr_names, &name); } } } array_append_zero(&ctx.attr_names); request->request.msgid = ldap_search(conn->ld, named_res->dn, LDAP_SCOPE_BASE, NULL, array_front_modifiable(&ctx.attr_names), 0); if (request->request.msgid == -1) { e_error(authdb_event(auth_request), "ldap_search(dn=%s) failed: %s", named_res->dn, ldap_get_error(conn)); return -1; } return 0; } static int db_ldap_search_save_result(struct ldap_request_search *request, struct db_ldap_result *res) { struct ldap_request_named_result *named_res; if (!array_is_created(&request->named_results)) { if (request->result != NULL) return -1; request->result = res; } else { named_res = array_idx_modifiable(&request->named_results, request->name_idx); if (named_res->result != NULL) return -1; named_res->result = res; } res->refcount++; return 0; } static int db_ldap_search_next_subsearch(struct ldap_connection *conn, struct ldap_request_search *request, struct db_ldap_result *res) { struct ldap_request_named_result *named_res; const struct ldap_field *field; if (request->result != NULL) res = request->result; if (!array_is_created(&request->named_results)) { /* see if we need to do more LDAP queries */ p_array_init(&request->named_results, request->request.auth_request->pool, 2); array_foreach(request->attr_map, field) { if (!field->value_is_dn) continue; named_res = array_append_space(&request->named_results); named_res->field = field; } if (db_ldap_fields_get_dn(conn, request, res) < 0) return -1; } else { request->name_idx++; } while (request->name_idx < array_count(&request->named_results)) { /* send the next LDAP query */ named_res = array_idx_modifiable(&request->named_results, request->name_idx); if (named_res->dn != NULL) { if (ldap_request_send_subquery(conn, request, named_res) < 0) return -1; return 1; } /* dn field wasn't returned, skip this */ request->name_idx++; } return 0; } static bool db_ldap_handle_request_result(struct ldap_connection *conn, struct ldap_request *request, unsigned int idx, struct db_ldap_result *res) { struct ldap_request_search *srequest = NULL; const struct ldap_request_named_result *named_res; int ret; bool final_result; i_assert(conn->pending_count > 0); if (request->type == LDAP_REQUEST_TYPE_BIND) { i_assert(conn->conn_state == LDAP_CONN_STATE_BINDING); i_assert(conn->pending_count == 1); conn->conn_state = LDAP_CONN_STATE_BOUND_AUTH; } else { srequest = (struct ldap_request_search *)request; switch (ldap_msgtype(res->msg)) { case LDAP_RES_SEARCH_ENTRY: case LDAP_RES_SEARCH_RESULT: break; case LDAP_RES_SEARCH_REFERENCE: /* we're going to ignore this */ return FALSE; default: e_error(conn->event, "Reply with unexpected type %d", ldap_msgtype(res->msg)); return TRUE; } } if (ldap_msgtype(res->msg) == LDAP_RES_SEARCH_ENTRY) { ret = LDAP_SUCCESS; final_result = FALSE; } else { final_result = TRUE; ret = ldap_result2error(conn->ld, res->msg, 0); } /* LDAP_NO_SUCH_OBJECT is returned for nonexistent base */ if (ret != LDAP_SUCCESS && ret != LDAP_NO_SUCH_OBJECT && request->type == LDAP_REQUEST_TYPE_SEARCH) { /* handle search failures here */ struct ldap_request_search *srequest = (struct ldap_request_search *)request; if (!array_is_created(&srequest->named_results)) { e_error(authdb_event(request->auth_request), "ldap_search(base=%s filter=%s) failed: %s", srequest->base, srequest->filter, ldap_err2string(ret)); } else { named_res = array_idx(&srequest->named_results, srequest->name_idx); e_error(authdb_event(request->auth_request), "ldap_search(base=%s) failed: %s", named_res->dn, ldap_err2string(ret)); } res = NULL; } if (ret == LDAP_SUCCESS && srequest != NULL && !srequest->multi_entry) { /* expand any @results */ if (!final_result) { if (db_ldap_search_save_result(srequest, res) < 0) { e_error(authdb_event(request->auth_request), "LDAP search returned multiple entries"); res = NULL; } else { /* wait for finish */ return FALSE; } } else { ret = db_ldap_search_next_subsearch(conn, srequest, res); if (ret > 0) { /* more LDAP queries left */ return FALSE; } if (ret < 0) res = NULL; } } if (res == NULL && !final_result) { /* wait for the final reply */ request->failed = TRUE; return TRUE; } if (request->failed) res = NULL; if (final_result) { conn->pending_count--; aqueue_delete(conn->request_queue, idx); } T_BEGIN { if (res != NULL && srequest != NULL && srequest->result != NULL) request->callback(conn, request, srequest->result->msg); request->callback(conn, request, res == NULL ? NULL : res->msg); } T_END; if (idx > 0) { /* see if there are timed out requests */ if (db_ldap_abort_requests(conn, idx, DB_LDAP_REQUEST_LOST_TIMEOUT_SECS, TRUE, "Request lost")) ldap_conn_reconnect(conn); } return TRUE; } static void db_ldap_result_unref(struct db_ldap_result **_res) { struct db_ldap_result *res = *_res; *_res = NULL; i_assert(res->refcount > 0); if (--res->refcount == 0) { ldap_msgfree(res->msg); i_free(res); } } static void db_ldap_request_free(struct ldap_request *request) { if (request->type == LDAP_REQUEST_TYPE_SEARCH) { struct ldap_request_search *srequest = (struct ldap_request_search *)request; struct ldap_request_named_result *named_res; if (srequest->result != NULL) db_ldap_result_unref(&srequest->result); if (array_is_created(&srequest->named_results)) { array_foreach_modifiable(&srequest->named_results, named_res) { if (named_res->result != NULL) db_ldap_result_unref(&named_res->result); } array_free(&srequest->named_results); srequest->name_idx = 0; } } } static void db_ldap_handle_result(struct ldap_connection *conn, struct db_ldap_result *res) { struct auth_request *auth_request; struct ldap_request *request; unsigned int idx; int msgid; msgid = ldap_msgid(res->msg); if (msgid == conn->default_bind_msgid) { db_ldap_default_bind_finished(conn, res); return; } request = db_ldap_find_request(conn, msgid, &idx); if (request == NULL) { e_error(conn->event, "Reply with unknown msgid %d", msgid); ldap_conn_reconnect(conn); return; } /* request is allocated from auth_request's pool */ auth_request = request->auth_request; auth_request_ref(auth_request); if (db_ldap_handle_request_result(conn, request, idx, res)) db_ldap_request_free(request); auth_request_unref(&auth_request); } static void ldap_input(struct ldap_connection *conn) { struct timeval timeout; struct db_ldap_result *res; LDAPMessage *msg; time_t prev_reply_diff; int ret; do { if (conn->ld == NULL) return; i_zero(&timeout); ret = ldap_result(conn->ld, LDAP_RES_ANY, 0, &timeout, &msg); #ifdef OPENLDAP_ASYNC_WORKAROUND if (ret == 0) { /* try again, there may be another in buffer */ ret = ldap_result(conn->ld, LDAP_RES_ANY, 0, &timeout, &msg); } #endif if (ret <= 0) break; res = i_new(struct db_ldap_result, 1); res->refcount = 1; res->msg = msg; db_ldap_handle_result(conn, res); db_ldap_result_unref(&res); } while (conn->io != NULL); prev_reply_diff = ioloop_time - conn->last_reply_stamp; conn->last_reply_stamp = ioloop_time; if (ret > 0) { /* input disabled, continue once it's enabled */ i_assert(conn->io == NULL); } else if (ret == 0) { /* send more requests */ while (db_ldap_request_queue_next(conn)) ; } else if (ldap_get_errno(conn) != LDAP_SERVER_DOWN) { e_error(conn->event, "ldap_result() failed: %s", ldap_get_error(conn)); ldap_conn_reconnect(conn); } else if (aqueue_count(conn->request_queue) > 0 || prev_reply_diff < DB_LDAP_IDLE_RECONNECT_SECS) { e_error(conn->event, "Connection lost to LDAP server, reconnecting"); ldap_conn_reconnect(conn); } else { /* server probably disconnected an idle connection. don't reconnect until the next request comes. */ db_ldap_conn_close(conn); } } #ifdef HAVE_LDAP_SASL static int sasl_interact(LDAP *ld ATTR_UNUSED, unsigned flags ATTR_UNUSED, void *defaults, void *interact) { struct db_ldap_sasl_bind_context *context = defaults; sasl_interact_t *in; const char *str; for (in = interact; in->id != SASL_CB_LIST_END; in++) { switch (in->id) { case SASL_CB_GETREALM: str = context->realm; break; case SASL_CB_AUTHNAME: str = context->authcid; break; case SASL_CB_USER: str = context->authzid; break; case SASL_CB_PASS: str = context->passwd; break; default: str = NULL; break; } if (str != NULL) { in->len = strlen(str); in->result = str; } } return LDAP_SUCCESS; } #endif static void ldap_connection_timeout(struct ldap_connection *conn) { i_assert(conn->conn_state == LDAP_CONN_STATE_BINDING); e_error(conn->event, "Initial binding to LDAP server timed out"); db_ldap_conn_close(conn); } #ifdef HAVE_LDAP_SASL static int db_ldap_bind_sasl(struct ldap_connection *conn) { struct db_ldap_sasl_bind_context context; int ret; i_zero(&context); context.authcid = conn->set.dn; context.passwd = conn->set.dnpass; context.realm = conn->set.sasl_realm; context.authzid = conn->set.sasl_authz_id; /* There doesn't seem to be a way to do SASL binding asynchronously.. */ ret = ldap_sasl_interactive_bind_s(conn->ld, NULL, conn->set.sasl_mech, NULL, NULL, LDAP_SASL_QUIET, sasl_interact, &context); if (db_ldap_connect_finish(conn, ret) < 0) return -1; conn->conn_state = LDAP_CONN_STATE_BOUND_DEFAULT; return 0; } #else static int db_ldap_bind_sasl(struct ldap_connection *conn ATTR_UNUSED) { i_unreached(); /* already checked at init */ return -1; } #endif static int db_ldap_bind_simple(struct ldap_connection *conn) { int msgid; i_assert(conn->conn_state != LDAP_CONN_STATE_BINDING); i_assert(conn->default_bind_msgid == -1); i_assert(conn->pending_count == 0); msgid = ldap_bind(conn->ld, conn->set.dn, conn->set.dnpass, LDAP_AUTH_SIMPLE); if (msgid == -1) { i_assert(ldap_get_errno(conn) != LDAP_SUCCESS); if (db_ldap_connect_finish(conn, ldap_get_errno(conn)) < 0) { /* lost connection, close it */ db_ldap_conn_close(conn); } return -1; } conn->conn_state = LDAP_CONN_STATE_BINDING; conn->default_bind_msgid = msgid; timeout_remove(&conn->to); conn->to = timeout_add(DB_LDAP_REQUEST_LOST_TIMEOUT_SECS*1000, ldap_connection_timeout, conn); return 0; } static int db_ldap_bind(struct ldap_connection *conn) { if (conn->set.sasl_bind) { if (db_ldap_bind_sasl(conn) < 0) return -1; } else { if (db_ldap_bind_simple(conn) < 0) return -1; } return 0; } static void db_ldap_get_fd(struct ldap_connection *conn) { int ret; /* get the connection's fd */ ret = ldap_get_option(conn->ld, LDAP_OPT_DESC, (void *)&conn->fd); if (ret != LDAP_SUCCESS) { i_fatal("LDAP %s: Can't get connection fd: %s", conn->config_path, ldap_err2string(ret)); } if (conn->fd <= STDERR_FILENO) { /* Solaris LDAP library seems to be broken */ i_fatal("LDAP %s: Buggy LDAP library returned wrong fd: %d", conn->config_path, conn->fd); } i_assert(conn->fd != -1); net_set_nonblock(conn->fd, TRUE); } static void ATTR_NULL(1) db_ldap_set_opt(struct ldap_connection *conn, LDAP *ld, int opt, const void *value, const char *optname, const char *value_str) { int ret; ret = ldap_set_option(ld, opt, value); if (ret != LDAP_SUCCESS) { i_fatal("LDAP %s: Can't set option %s to %s: %s", conn->config_path, optname, value_str, ldap_err2string(ret)); } } static void ATTR_NULL(1) db_ldap_set_opt_str(struct ldap_connection *conn, LDAP *ld, int opt, const char *value, const char *optname) { if (value != NULL) db_ldap_set_opt(conn, ld, opt, value, optname, value); } static void db_ldap_set_tls_options(struct ldap_connection *conn) { #ifdef OPENLDAP_TLS_OPTIONS db_ldap_set_opt_str(conn, NULL, LDAP_OPT_X_TLS_CACERTFILE, conn->set.tls_ca_cert_file, "tls_ca_cert_file"); db_ldap_set_opt_str(conn, NULL, LDAP_OPT_X_TLS_CACERTDIR, conn->set.tls_ca_cert_dir, "tls_ca_cert_dir"); db_ldap_set_opt_str(conn, NULL, LDAP_OPT_X_TLS_CERTFILE, conn->set.tls_cert_file, "tls_cert_file"); db_ldap_set_opt_str(conn, NULL, LDAP_OPT_X_TLS_KEYFILE, conn->set.tls_key_file, "tls_key_file"); db_ldap_set_opt_str(conn, NULL, LDAP_OPT_X_TLS_CIPHER_SUITE, conn->set.tls_cipher_suite, "tls_cipher_suite"); if (conn->set.tls_require_cert != NULL) { db_ldap_set_opt(conn, NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &conn->set.ldap_tls_require_cert_parsed, "tls_require_cert", conn->set.tls_require_cert); } #else if (conn->set.tls_ca_cert_file != NULL || conn->set.tls_ca_cert_dir != NULL || conn->set.tls_cert_file != NULL || conn->set.tls_key_file != NULL || conn->set.tls_cipher_suite != NULL) { i_fatal("LDAP %s: tls_* settings aren't supported by your LDAP library - they must not be set", conn->config_path); } #endif } static void db_ldap_set_options(struct ldap_connection *conn) { unsigned int ldap_version; int value; #ifdef LDAP_OPT_NETWORK_TIMEOUT struct timeval tv; int ret; tv.tv_sec = DB_LDAP_CONNECT_TIMEOUT_SECS; tv.tv_usec = 0; ret = ldap_set_option(conn->ld, LDAP_OPT_NETWORK_TIMEOUT, &tv); if (ret != LDAP_SUCCESS) { i_fatal("LDAP %s: Can't set network-timeout: %s", conn->config_path, ldap_err2string(ret)); } #endif db_ldap_set_opt(conn, conn->ld, LDAP_OPT_DEREF, &conn->set.ldap_deref, "deref", conn->set.deref); #ifdef LDAP_OPT_DEBUG_LEVEL if (str_to_int(conn->set.debug_level, &value) >= 0 && value != 0) { db_ldap_set_opt(conn, NULL, LDAP_OPT_DEBUG_LEVEL, &value, "debug_level", conn->set.debug_level); event_set_forced_debug(conn->event, TRUE); } #endif ldap_version = conn->set.ldap_version; db_ldap_set_opt(conn, conn->ld, LDAP_OPT_PROTOCOL_VERSION, &ldap_version, "protocol_version", dec2str(ldap_version)); db_ldap_set_tls_options(conn); } static void db_ldap_init_ld(struct ldap_connection *conn) { int ret; if (conn->set.uris != NULL) { #ifdef LDAP_HAVE_INITIALIZE ret = ldap_initialize(&conn->ld, conn->set.uris); if (ret != LDAP_SUCCESS) { i_fatal("LDAP %s: ldap_initialize() failed with uris %s: %s", conn->config_path, conn->set.uris, ldap_err2string(ret)); } #else i_unreached(); /* already checked at init */ #endif } else { conn->ld = ldap_init(conn->set.hosts, LDAP_PORT); if (conn->ld == NULL) { i_fatal("LDAP %s: ldap_init() failed with hosts: %s", conn->config_path, conn->set.hosts); } } db_ldap_set_options(conn); } int db_ldap_connect(struct ldap_connection *conn) { struct timeval start, end; int ret; if (conn->conn_state != LDAP_CONN_STATE_DISCONNECTED) return 0; i_gettimeofday(&start); i_assert(conn->pending_count == 0); if (conn->delayed_connect) { conn->delayed_connect = FALSE; timeout_remove(&conn->to); } if (conn->ld == NULL) db_ldap_init_ld(conn); if (conn->set.tls) { #ifdef LDAP_HAVE_START_TLS_S ret = ldap_start_tls_s(conn->ld, NULL, NULL); if (ret != LDAP_SUCCESS) { if (ret == LDAP_OPERATIONS_ERROR && conn->set.uris != NULL && str_begins(conn->set.uris, "ldaps:")) { i_fatal("LDAP %s: Don't use both tls=yes " "and ldaps URI", conn->config_path); } e_error(conn->event, "ldap_start_tls_s() failed: %s", ldap_err2string(ret)); return -1; } #else i_unreached(); /* already checked at init */ #endif } if (db_ldap_bind(conn) < 0) return -1; i_gettimeofday(&end); int msecs = timeval_diff_msecs(&end, &start); e_debug(conn->event, "LDAP initialization took %d msecs", msecs); db_ldap_get_fd(conn); conn->io = io_add(conn->fd, IO_READ, ldap_input, conn); return 0; } static void db_ldap_connect_callback(struct ldap_connection *conn) { i_assert(conn->conn_state == LDAP_CONN_STATE_DISCONNECTED); (void)db_ldap_connect(conn); } void db_ldap_connect_delayed(struct ldap_connection *conn) { if (conn->delayed_connect) return; conn->delayed_connect = TRUE; i_assert(conn->to == NULL); conn->to = timeout_add_short(0, db_ldap_connect_callback, conn); } void db_ldap_enable_input(struct ldap_connection *conn, bool enable) { if (!enable) { io_remove(&conn->io); } else { if (conn->io == NULL && conn->fd != -1) { conn->io = io_add(conn->fd, IO_READ, ldap_input, conn); ldap_input(conn); } } } static void db_ldap_disconnect_timeout(struct ldap_connection *conn) { db_ldap_abort_requests(conn, UINT_MAX, DB_LDAP_REQUEST_DISCONNECT_TIMEOUT_SECS, FALSE, "Aborting (timeout), we're not connected to LDAP server"); if (aqueue_count(conn->request_queue) == 0) { /* no requests left, remove this timeout handler */ timeout_remove(&conn->to); } } static void db_ldap_conn_close(struct ldap_connection *conn) { struct ldap_request *const *requests, *request; unsigned int i; conn->conn_state = LDAP_CONN_STATE_DISCONNECTED; conn->delayed_connect = FALSE; conn->default_bind_msgid = -1; timeout_remove(&conn->to); if (conn->pending_count != 0) { requests = array_front(&conn->request_array); for (i = 0; i < conn->pending_count; i++) { request = requests[aqueue_idx(conn->request_queue, i)]; i_assert(request->msgid != -1); request->msgid = -1; } conn->pending_count = 0; } if (conn->ld != NULL) { ldap_unbind(conn->ld); conn->ld = NULL; } conn->fd = -1; /* the fd may have already been closed before ldap_unbind(), so we'll have to use io_remove_closed(). */ io_remove_closed(&conn->io); if (aqueue_count(conn->request_queue) > 0) { conn->to = timeout_add(DB_LDAP_REQUEST_DISCONNECT_TIMEOUT_SECS * 1000/2, db_ldap_disconnect_timeout, conn); } } struct ldap_field_find_context { ARRAY_TYPE(string) attr_names; pool_t pool; }; static int db_ldap_field_find(const char *data, void *context, const char **value_r, const char **error_r ATTR_UNUSED) { struct ldap_field_find_context *ctx = context; char *ldap_attr; if (*data != '\0') { ldap_attr = p_strdup(ctx->pool, t_strcut(data, ':')); if (strchr(ldap_attr, '@') == NULL) array_push_back(&ctx->attr_names, &ldap_attr); } *value_r = NULL; return 1; } void db_ldap_set_attrs(struct ldap_connection *conn, const char *attrlist, char ***attr_names_r, ARRAY_TYPE(ldap_field) *attr_map, const char *skip_attr) { static struct var_expand_func_table var_funcs_table[] = { { "ldap", db_ldap_field_find }, { "ldap_ptr", db_ldap_field_find }, { NULL, NULL } }; struct ldap_field_find_context ctx; struct ldap_field *field; string_t *tmp_str; const char *const *attr, *attr_data, *p, *error; char *ldap_attr, *name, *templ; unsigned int i; if (*attrlist == '\0') return; attr = t_strsplit_spaces(attrlist, ","); tmp_str = t_str_new(128); ctx.pool = conn->pool; p_array_init(&ctx.attr_names, conn->pool, 16); for (i = 0; attr[i] != NULL; i++) { /* allow spaces here so "foo=1, bar=2" works */ attr_data = attr[i]; while (*attr_data == ' ') attr_data++; p = strchr(attr_data, '='); if (p == NULL) ldap_attr = name = p_strdup(conn->pool, attr_data); else if (attr_data[0] == '@') { ldap_attr = ""; name = p_strdup(conn->pool, attr_data); } else { ldap_attr = p_strdup_until(conn->pool, attr_data, p); name = p_strdup(conn->pool, p + 1); } templ = strchr(name, '='); if (templ == NULL) { if (*ldap_attr == '\0') { /* =foo static value */ templ = ""; } } else { *templ++ = '\0'; str_truncate(tmp_str, 0); if (var_expand_with_funcs(tmp_str, templ, NULL, var_funcs_table, &ctx, &error) <= 0) { /* This var_expand_with_funcs call fills the * ldap_field_find_context in ctx, but the * resulting string_t is not used, and the * return value or error_r is not checked since * it gives errors for non-ldap variable * expansions. */ } if (strchr(templ, '%') == NULL) { /* backwards compatibility: attr=name=prefix means same as attr=name=prefix%$ when %vars are missing */ templ = p_strconcat(conn->pool, templ, "%$", NULL); } } if (*name == '\0') e_error(conn->event, "Invalid attrs entry: %s", attr_data); else if (skip_attr == NULL || strcmp(skip_attr, name) != 0) { field = array_append_space(attr_map); if (name[0] == '@') { /* @name=ldapField */ name++; field->value_is_dn = TRUE; } else if (name[0] == '!' && name == ldap_attr) { /* !ldapAttr */ name = ""; i_assert(ldap_attr[0] == '!'); ldap_attr++; field->skip = TRUE; } field->name = name; field->value = templ; field->ldap_attr_name = ldap_attr; if (*ldap_attr != '\0' && strchr(ldap_attr, '@') == NULL) { /* root request's attribute */ array_push_back(&ctx.attr_names, &ldap_attr); } } } array_append_zero(&ctx.attr_names); *attr_names_r = array_front_modifiable(&ctx.attr_names); } static const struct var_expand_table * db_ldap_value_get_var_expand_table(struct auth_request *auth_request, const char *ldap_value) { struct var_expand_table *table; unsigned int count = 1; table = auth_request_get_var_expand_table_full(auth_request, auth_request->fields.user, NULL, &count); table[0].key = '$'; table[0].value = ldap_value; return table; } #define IS_LDAP_ESCAPED_CHAR(c) \ ((((unsigned char)(c)) & 0x80) != 0 || strchr(LDAP_ESCAPE_CHARS, (c)) != NULL) const char *ldap_escape(const char *str, const struct auth_request *auth_request ATTR_UNUSED) { string_t *ret = NULL; for (const char *p = str; *p != '\0'; p++) { if (IS_LDAP_ESCAPED_CHAR(*p)) { if (ret == NULL) { ret = t_str_new((size_t) (p - str) + 64); str_append_data(ret, str, (size_t) (p - str)); } str_printfa(ret, "\\%02X", (unsigned char)*p); } else if (ret != NULL) str_append_c(ret, *p); } return ret == NULL ? str : str_c(ret); } static bool ldap_field_hide_password(struct db_ldap_result_iterate_context *ctx, const char *attr) { const struct ldap_field *field; if (ctx->ldap_request->auth_request->set->debug_passwords) return FALSE; array_foreach(ctx->attr_map, field) { if (strcmp(field->ldap_attr_name, attr) == 0) { if (strcmp(field->name, "password") == 0 || strcmp(field->name, "password_noscheme") == 0) return TRUE; } } return FALSE; } static void get_ldap_fields(struct db_ldap_result_iterate_context *ctx, struct ldap_connection *conn, LDAPMessage *entry, const char *suffix) { struct db_ldap_value *ldap_value; char *attr, **vals; unsigned int i, count; BerElement *ber; attr = ldap_first_attribute(conn->ld, entry, &ber); while (attr != NULL) { vals = ldap_get_values(conn->ld, entry, attr); ldap_value = p_new(ctx->pool, struct db_ldap_value, 1); if (vals == NULL) { ldap_value->values = p_new(ctx->pool, const char *, 1); count = 0; } else { for (count = 0; vals[count] != NULL; count++) ; } ldap_value->values = p_new(ctx->pool, const char *, count + 1); for (i = 0; i < count; i++) ldap_value->values[i] = p_strdup(ctx->pool, vals[i]); if (ctx->debug != NULL) { str_printfa(ctx->debug, " %s%s=", attr, suffix); if (count == 0) str_append(ctx->debug, ""); else if (ldap_field_hide_password(ctx, attr)) str_append(ctx->debug, PASSWORD_HIDDEN_STR); else { str_append(ctx->debug, ldap_value->values[0]); for (i = 1; i < count; i++) { str_printfa(ctx->debug, ",%s", ldap_value->values[0]); } } } hash_table_insert(ctx->ldap_attrs, p_strconcat(ctx->pool, attr, suffix, NULL), ldap_value); ldap_value_free(vals); ldap_memfree(attr); attr = ldap_next_attribute(conn->ld, entry, ber); } ber_free(ber, 0); } struct db_ldap_result_iterate_context * db_ldap_result_iterate_init_full(struct ldap_connection *conn, struct ldap_request_search *ldap_request, LDAPMessage *res, bool skip_null_values, bool iter_dn_values) { struct db_ldap_result_iterate_context *ctx; const struct ldap_request_named_result *named_res; const char *suffix; pool_t pool; pool = pool_alloconly_create(MEMPOOL_GROWING"ldap result iter", 1024); ctx = p_new(pool, struct db_ldap_result_iterate_context, 1); ctx->pool = pool; ctx->ldap_request = &ldap_request->request; ctx->attr_map = ldap_request->attr_map; ctx->skip_null_values = skip_null_values; ctx->iter_dn_values = iter_dn_values; hash_table_create(&ctx->ldap_attrs, pool, 0, strcase_hash, strcasecmp); ctx->var = str_new(ctx->pool, 256); if (event_want_debug(ctx->ldap_request->auth_request->event)) ctx->debug = t_str_new(256); ctx->ldap_msg = res; ctx->ld = conn->ld; get_ldap_fields(ctx, conn, res, ""); if (array_is_created(&ldap_request->named_results)) { array_foreach(&ldap_request->named_results, named_res) { suffix = t_strdup_printf("@%s", named_res->field->name); if (named_res->result != NULL) { get_ldap_fields(ctx, conn, named_res->result->msg, suffix); } } } return ctx; } struct db_ldap_result_iterate_context * db_ldap_result_iterate_init(struct ldap_connection *conn, struct ldap_request_search *ldap_request, LDAPMessage *res, bool skip_null_values) { return db_ldap_result_iterate_init_full(conn, ldap_request, res, skip_null_values, FALSE); } static const char *db_ldap_field_get_default(const char *data) { const char *p; p = i_strchr_to_next(data, ':'); if (p == NULL) return ""; else { /* default value given */ return p; } } static int db_ldap_field_expand(const char *data, void *context, const char **value_r, const char **error_r ATTR_UNUSED) { struct db_ldap_result_iterate_context *ctx = context; struct db_ldap_value *ldap_value; const char *field_name = t_strcut(data, ':'); ldap_value = hash_table_lookup(ctx->ldap_attrs, field_name); if (ldap_value == NULL) { /* requested ldap attribute wasn't returned at all */ if (ctx->debug != NULL) str_printfa(ctx->debug, "; %s missing", field_name); *value_r = db_ldap_field_get_default(data); return 1; } ldap_value->used = TRUE; if (ldap_value->values[0] == NULL) { /* no value for ldap attribute */ *value_r = db_ldap_field_get_default(data); return 1; } if (ldap_value->values[1] != NULL) { e_warning(authdb_event(ctx->ldap_request->auth_request), "Multiple values found for '%s', using value '%s'", field_name, ldap_value->values[0]); } *value_r = ldap_value->values[0]; return 1; } static int db_ldap_field_ptr_expand(const char *data, void *context, const char **value_r, const char **error_r) { struct db_ldap_result_iterate_context *ctx = context; const char *field_name, *suffix; suffix = strchr(t_strcut(data, ':'), '@'); if (db_ldap_field_expand(data, ctx, &field_name, error_r) <= 0) i_unreached(); if (field_name[0] == '\0') { *value_r = ""; return 1; } field_name = t_strconcat(field_name, suffix, NULL); return db_ldap_field_expand(field_name, ctx, value_r, error_r); } static int db_ldap_field_dn_expand(const char *data ATTR_UNUSED, void *context ATTR_UNUSED, const char **value_r, const char **error_r ATTR_UNUSED) { struct db_ldap_result_iterate_context *ctx = context; char *dn = ldap_get_dn(ctx->ld, ctx->ldap_msg); *value_r = t_strdup(dn); ldap_memfree(dn); return 1; } static struct var_expand_func_table ldap_var_funcs_table[] = { { "ldap", db_ldap_field_expand }, { "ldap_ptr", db_ldap_field_ptr_expand }, { "ldap_dn", db_ldap_field_dn_expand }, { NULL, NULL } }; static const char *const * db_ldap_result_return_value(struct db_ldap_result_iterate_context *ctx, const struct ldap_field *field, struct db_ldap_value *ldap_value) { const struct var_expand_table *var_table; const char *const *values, *error; if (ldap_value != NULL) values = ldap_value->values; else { /* LDAP attribute doesn't exist */ ctx->val_1_arr[0] = NULL; values = ctx->val_1_arr; } if (field->value == NULL) { /* use the LDAP attribute's value */ } else { /* template */ if (values[0] == NULL && *field->ldap_attr_name != '\0') { /* ldapAttr=key=template%$, but ldapAttr doesn't exist. */ return values; } if (values[0] != NULL && values[1] != NULL) { e_warning(authdb_event(ctx->ldap_request->auth_request), "Multiple values found for '%s', " "using value '%s'", field->name, values[0]); } /* do this lookup separately for each expansion, because: 1) the values are allocated from data stack 2) if "user" field is updated, we want %u/%n/%d updated (and less importantly the same for other variables) */ var_table = db_ldap_value_get_var_expand_table( ctx->ldap_request->auth_request, values[0]); if (var_expand_with_funcs(ctx->var, field->value, var_table, ldap_var_funcs_table, ctx, &error) <= 0) { e_warning(authdb_event(ctx->ldap_request->auth_request), "Failed to expand template %s: %s", field->value, error); } ctx->val_1_arr[0] = str_c(ctx->var); values = ctx->val_1_arr; } return values; } bool db_ldap_result_iterate_next(struct db_ldap_result_iterate_context *ctx, const char **name_r, const char *const **values_r) { const struct var_expand_table *tab; const struct ldap_field *field; struct db_ldap_value *ldap_value; unsigned int pos; const char *error; do { if (ctx->attr_idx == array_count(ctx->attr_map)) return FALSE; field = array_idx(ctx->attr_map, ctx->attr_idx++); } while (field->value_is_dn != ctx->iter_dn_values || field->skip); ldap_value = *field->ldap_attr_name == '\0' ? NULL : hash_table_lookup(ctx->ldap_attrs, field->ldap_attr_name); if (ldap_value != NULL) ldap_value->used = TRUE; else if (ctx->debug != NULL && *field->ldap_attr_name != '\0') str_printfa(ctx->debug, "; %s missing", field->ldap_attr_name); str_truncate(ctx->var, 0); *values_r = db_ldap_result_return_value(ctx, field, ldap_value); if (strchr(field->name, '%') == NULL) *name_r = field->name; else { /* expand %variables also for LDAP name fields. we'll use the same ctx->var, which may already contain the value. */ str_append_c(ctx->var, '\0'); pos = str_len(ctx->var); tab = auth_request_get_var_expand_table( ctx->ldap_request->auth_request, NULL); if (var_expand_with_funcs(ctx->var, field->name, tab, ldap_var_funcs_table, ctx, &error) <= 0) { e_warning(authdb_event(ctx->ldap_request->auth_request), "Failed to expand %s: %s", field->name, error); } *name_r = str_c(ctx->var) + pos; } if (ctx->skip_null_values && (*values_r)[0] == NULL) { /* no values. don't confuse the caller with this reply. */ return db_ldap_result_iterate_next(ctx, name_r, values_r); } return TRUE; } static void db_ldap_result_finish_debug(struct db_ldap_result_iterate_context *ctx) { struct hash_iterate_context *iter; char *name; struct db_ldap_value *value; unsigned int unused_count = 0; size_t orig_len; if (ctx->ldap_request->result_logged) return; orig_len = str_len(ctx->debug); if (orig_len == 0) { e_debug(authdb_event(ctx->ldap_request->auth_request), "no fields returned by the server"); return; } str_append(ctx->debug, "; "); iter = hash_table_iterate_init(ctx->ldap_attrs); while (hash_table_iterate(iter, ctx->ldap_attrs, &name, &value)) { if (!value->used) { str_printfa(ctx->debug, "%s,", name); unused_count++; } } hash_table_iterate_deinit(&iter); if (unused_count == 0) str_truncate(ctx->debug, orig_len); else { str_truncate(ctx->debug, str_len(ctx->debug)-1); str_append(ctx->debug, " unused"); } e_debug(authdb_event(ctx->ldap_request->auth_request), "result: %s", str_c(ctx->debug) + 1); ctx->ldap_request->result_logged = TRUE; } void db_ldap_result_iterate_deinit(struct db_ldap_result_iterate_context **_ctx) { struct db_ldap_result_iterate_context *ctx = *_ctx; *_ctx = NULL; if (ctx->debug != NULL) db_ldap_result_finish_debug(ctx); hash_table_destroy(&ctx->ldap_attrs); pool_unref(&ctx->pool); } static const char *parse_setting(const char *key, const char *value, struct ldap_connection *conn) { return parse_setting_from_defs(conn->pool, setting_defs, &conn->set, key, value); } static struct ldap_connection *ldap_conn_find(const char *config_path) { struct ldap_connection *conn; for (conn = ldap_connections; conn != NULL; conn = conn->next) { if (strcmp(conn->config_path, config_path) == 0) return conn; } return NULL; } struct ldap_connection *db_ldap_init(const char *config_path, bool userdb) { struct ldap_connection *conn; const char *str, *error; pool_t pool; /* see if it already exists */ conn = ldap_conn_find(config_path); if (conn != NULL) { if (userdb) conn->userdb_used = TRUE; conn->refcount++; return conn; } if (*config_path == '\0') i_fatal("LDAP: Configuration file path not given"); pool = pool_alloconly_create("ldap_connection", 1024); conn = p_new(pool, struct ldap_connection, 1); conn->pool = pool; conn->refcount = 1; conn->userdb_used = userdb; conn->conn_state = LDAP_CONN_STATE_DISCONNECTED; conn->default_bind_msgid = -1; conn->fd = -1; conn->config_path = p_strdup(pool, config_path); conn->set = default_ldap_settings; if (!settings_read_nosection(config_path, parse_setting, conn, &error)) i_fatal("ldap %s: %s", config_path, error); if (conn->set.base == NULL) i_fatal("LDAP %s: No base given", config_path); if (conn->set.uris == NULL && conn->set.hosts == NULL) i_fatal("LDAP %s: No uris or hosts set", config_path); #ifndef LDAP_HAVE_INITIALIZE if (conn->set.uris != NULL) { i_fatal("LDAP %s: uris set, but Dovecot compiled without support for LDAP uris " "(ldap_initialize() not supported by LDAP library)", config_path); } #endif #ifndef LDAP_HAVE_START_TLS_S if (conn->set.tls) i_fatal("LDAP %s: tls=yes, but your LDAP library doesn't support TLS", config_path); #endif #ifndef HAVE_LDAP_SASL if (conn->set.sasl_bind) i_fatal("LDAP %s: sasl_bind=yes but no SASL support compiled in", conn->config_path); #endif if (conn->set.ldap_version < 3) { if (conn->set.sasl_bind) i_fatal("LDAP %s: sasl_bind=yes requires ldap_version=3", config_path); if (conn->set.tls) i_fatal("LDAP %s: tls=yes requires ldap_version=3", config_path); } #ifdef OPENLDAP_TLS_OPTIONS if (conn->set.tls_require_cert != NULL) { if (tls_require_cert2str(conn->set.tls_require_cert, &conn->set.ldap_tls_require_cert_parsed) < 0) i_fatal("LDAP %s: Unknown tls_require_cert value '%s'", config_path, conn->set.tls_require_cert); } #endif if (*conn->set.ldaprc_path != '\0') { str = getenv("LDAPRC"); if (str != NULL && strcmp(str, conn->set.ldaprc_path) != 0) { i_fatal("LDAP %s: Multiple different ldaprc_path " "settings not allowed (%s and %s)", config_path, str, conn->set.ldaprc_path); } env_put("LDAPRC", conn->set.ldaprc_path); } if (deref2str(conn->set.deref, &conn->set.ldap_deref) < 0) i_fatal("LDAP %s: Unknown deref option '%s'", config_path, conn->set.deref); if (scope2str(conn->set.scope, &conn->set.ldap_scope) < 0) i_fatal("LDAP %s: Unknown scope option '%s'", config_path, conn->set.scope); conn->event = event_create(auth_event); event_set_append_log_prefix(conn->event, t_strdup_printf( "ldap(%s): ", conn->config_path)); i_array_init(&conn->request_array, 512); conn->request_queue = aqueue_init(&conn->request_array.arr); conn->next = ldap_connections; ldap_connections = conn; db_ldap_init_ld(conn); return conn; } void db_ldap_unref(struct ldap_connection **_conn) { struct ldap_connection *conn = *_conn; struct ldap_connection **p; *_conn = NULL; i_assert(conn->refcount >= 0); if (--conn->refcount > 0) return; for (p = &ldap_connections; *p != NULL; p = &(*p)->next) { if (*p == conn) { *p = conn->next; break; } } db_ldap_abort_requests(conn, UINT_MAX, 0, FALSE, "Shutting down"); i_assert(conn->pending_count == 0); db_ldap_conn_close(conn); i_assert(conn->to == NULL); array_free(&conn->request_array); aqueue_deinit(&conn->request_queue); event_unref(&conn->event); pool_unref(&conn->pool); } #ifndef BUILTIN_LDAP /* Building a plugin */ extern struct passdb_module_interface passdb_ldap_plugin; extern struct userdb_module_interface userdb_ldap_plugin; void authdb_ldap_init(void); void authdb_ldap_deinit(void); void authdb_ldap_init(void) { passdb_register_module(&passdb_ldap_plugin); userdb_register_module(&userdb_ldap_plugin); } void authdb_ldap_deinit(void) { passdb_unregister_module(&passdb_ldap_plugin); userdb_unregister_module(&userdb_ldap_plugin); } #endif #endif dovecot-2.3.21.1/src/auth/Makefile.in0000644000000000000000000030530314656633607014111 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ @AUTH_LUA_PLUGIN_TRUE@@HAVE_LUA_TRUE@am__append_1 = libauthdb_lua.la @AUTH_LUA_PLUGIN_FALSE@@HAVE_LUA_TRUE@am__append_2 = $(LIBDOVECOT_LUA) @AUTH_LUA_PLUGIN_FALSE@@HAVE_LUA_TRUE@am__append_3 = $(LUA_LIBS) pkglibexec_PROGRAMS = auth$(EXEEXT) checkpassword-reply$(EXEEXT) noinst_PROGRAMS = $(am__EXEEXT_1) subdir = src/auth ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ $(pkginc_lib_HEADERS) $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__EXEEXT_1 = test-libpassword$(EXEEXT) test-auth-cache$(EXEEXT) \ test-auth$(EXEEXT) test-mech$(EXEEXT) am__installdirs = "$(DESTDIR)$(pkglibexecdir)" \ "$(DESTDIR)$(auth_moduledir)" "$(DESTDIR)$(stats_moduledir)" \ "$(DESTDIR)$(pkginc_libdir)" PROGRAMS = $(noinst_PROGRAMS) $(pkglibexec_PROGRAMS) am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ *) f=$$p;; \ esac; am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; am__install_max = 40 am__nobase_strip_setup = \ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` am__nobase_strip = \ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" am__nobase_list = $(am__nobase_strip_setup); \ for p in $$list; do echo "$$p $$p"; done | \ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ if (++n[$$2] == $(am__install_max)) \ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ END { for (dir in files) print dir, files[dir] }' am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' am__uninstall_files_from_dir = { \ test -z "$$files" \ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ $(am__cd) "$$dir" && rm -f $$files; }; \ } LTLIBRARIES = $(auth_module_LTLIBRARIES) $(noinst_LTLIBRARIES) \ $(stats_module_LTLIBRARIES) libauth_la_LIBADD = am__objects_1 = db-ldap.lo passdb-ldap.lo userdb-ldap.lo am__objects_2 = db-lua.lo passdb-lua.lo userdb-lua.lo am_libauth_la_OBJECTS = auth.lo auth-cache.lo \ auth-client-connection.lo auth-master-connection.lo \ auth-policy.lo mech-otp-common.lo mech-plain-common.lo \ auth-penalty.lo auth-request.lo auth-request-fields.lo \ auth-request-handler.lo auth-request-stats.lo \ auth-request-var-expand.lo auth-settings.lo auth-fields.lo \ auth-token.lo auth-worker-client.lo auth-worker-server.lo \ db-checkpassword.lo db-dict.lo db-dict-cache-key.lo \ db-oauth2.lo db-sql.lo db-passwd-file.lo mech.lo \ mech-anonymous.lo mech-plain.lo mech-login.lo mech-cram-md5.lo \ mech-digest-md5.lo mech-external.lo mech-gssapi.lo mech-otp.lo \ mech-scram.lo mech-apop.lo mech-winbind.lo \ mech-dovecot-token.lo mech-oauth2.lo passdb.lo \ passdb-blocking.lo passdb-bsdauth.lo passdb-cache.lo \ passdb-checkpassword.lo passdb-dict.lo passdb-oauth2.lo \ passdb-passwd.lo passdb-passwd-file.lo passdb-pam.lo \ passdb-shadow.lo passdb-sql.lo passdb-static.lo \ passdb-template.lo userdb.lo userdb-blocking.lo \ userdb-checkpassword.lo userdb-dict.lo userdb-passwd.lo \ userdb-passwd-file.lo userdb-prefetch.lo userdb-static.lo \ userdb-sql.lo userdb-template.lo $(am__objects_1) \ $(am__objects_2) libauth_la_OBJECTS = $(am_libauth_la_OBJECTS) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = am__DEPENDENCIES_1 = libauthdb_imap_la_DEPENDENCIES = ../lib-imap-client/libimap_client.la \ $(am__DEPENDENCIES_1) am_libauthdb_imap_la_OBJECTS = libauthdb_imap_la-passdb-imap.lo libauthdb_imap_la_OBJECTS = $(am_libauthdb_imap_la_OBJECTS) libauthdb_imap_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(libauthdb_imap_la_LDFLAGS) $(LDFLAGS) \ -o $@ @LDAP_PLUGIN_TRUE@libauthdb_ldap_la_DEPENDENCIES = \ @LDAP_PLUGIN_TRUE@ $(am__DEPENDENCIES_1) am__libauthdb_ldap_la_SOURCES_DIST = db-ldap.c passdb-ldap.c \ userdb-ldap.c am__objects_3 = libauthdb_ldap_la-db-ldap.lo \ libauthdb_ldap_la-passdb-ldap.lo \ libauthdb_ldap_la-userdb-ldap.lo @LDAP_PLUGIN_TRUE@am_libauthdb_ldap_la_OBJECTS = $(am__objects_3) libauthdb_ldap_la_OBJECTS = $(am_libauthdb_ldap_la_OBJECTS) libauthdb_ldap_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(libauthdb_ldap_la_LDFLAGS) $(LDFLAGS) \ -o $@ @LDAP_PLUGIN_TRUE@am_libauthdb_ldap_la_rpath = -rpath \ @LDAP_PLUGIN_TRUE@ $(auth_moduledir) @AUTH_LUA_PLUGIN_TRUE@libauthdb_lua_la_DEPENDENCIES = \ @AUTH_LUA_PLUGIN_TRUE@ $(am__DEPENDENCIES_1) am__libauthdb_lua_la_SOURCES_DIST = db-lua.c passdb-lua.c userdb-lua.c am__objects_4 = libauthdb_lua_la-db-lua.lo \ libauthdb_lua_la-passdb-lua.lo libauthdb_lua_la-userdb-lua.lo @AUTH_LUA_PLUGIN_TRUE@am_libauthdb_lua_la_OBJECTS = $(am__objects_4) libauthdb_lua_la_OBJECTS = $(am_libauthdb_lua_la_OBJECTS) libauthdb_lua_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(libauthdb_lua_la_LDFLAGS) $(LDFLAGS) \ -o $@ @AUTH_LUA_PLUGIN_TRUE@@HAVE_LUA_TRUE@am_libauthdb_lua_la_rpath = \ @AUTH_LUA_PLUGIN_TRUE@@HAVE_LUA_TRUE@ -rpath $(auth_moduledir) @GSSAPI_PLUGIN_TRUE@libmech_gssapi_la_DEPENDENCIES = \ @GSSAPI_PLUGIN_TRUE@ $(am__DEPENDENCIES_1) am__libmech_gssapi_la_SOURCES_DIST = mech-gssapi.c @GSSAPI_PLUGIN_TRUE@am_libmech_gssapi_la_OBJECTS = \ @GSSAPI_PLUGIN_TRUE@ libmech_gssapi_la-mech-gssapi.lo libmech_gssapi_la_OBJECTS = $(am_libmech_gssapi_la_OBJECTS) libmech_gssapi_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(libmech_gssapi_la_LDFLAGS) $(LDFLAGS) \ -o $@ @GSSAPI_PLUGIN_TRUE@am_libmech_gssapi_la_rpath = -rpath \ @GSSAPI_PLUGIN_TRUE@ $(auth_moduledir) libpassword_la_LIBADD = am_libpassword_la_OBJECTS = libpassword_la-crypt-blowfish.lo \ libpassword_la-mycrypt.lo libpassword_la-password-scheme.lo \ libpassword_la-password-scheme-crypt.lo \ libpassword_la-password-scheme-md5crypt.lo \ libpassword_la-password-scheme-scram.lo \ libpassword_la-password-scheme-otp.lo \ libpassword_la-password-scheme-pbkdf2.lo \ libpassword_la-password-scheme-sodium.lo libpassword_la_OBJECTS = $(am_libpassword_la_OBJECTS) libpassword_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(libpassword_la_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o \ $@ am_libstats_auth_la_OBJECTS = auth-stats.lo libstats_auth_la_OBJECTS = $(am_libstats_auth_la_OBJECTS) libstats_auth_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(AM_CFLAGS) $(CFLAGS) $(libstats_auth_la_LDFLAGS) $(LDFLAGS) \ -o $@ am_auth_OBJECTS = auth-main.$(OBJEXT) auth_OBJECTS = $(am_auth_OBJECTS) @AUTH_LUA_PLUGIN_FALSE@@HAVE_LUA_TRUE@am__DEPENDENCIES_2 = \ @AUTH_LUA_PLUGIN_FALSE@@HAVE_LUA_TRUE@ $(am__DEPENDENCIES_1) am__DEPENDENCIES_3 = $(am__DEPENDENCIES_2) am__DEPENDENCIES_4 = libauth.la libstats_auth.la libpassword.la \ ../lib-otp/libotp.la $(am__DEPENDENCIES_3) \ $(am__DEPENDENCIES_1) auth_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(auth_LDFLAGS) $(LDFLAGS) -o $@ checkpassword_reply_SOURCES = checkpassword-reply.c checkpassword_reply_OBJECTS = \ checkpassword_reply-checkpassword-reply.$(OBJEXT) am_test_auth_OBJECTS = test-auth-request-var-expand.$(OBJEXT) \ test-auth-request-fields.$(OBJEXT) \ test-username-filter.$(OBJEXT) test-db-dict.$(OBJEXT) \ test-lua.$(OBJEXT) test-mock.$(OBJEXT) test-main.$(OBJEXT) test_auth_OBJECTS = $(am_test_auth_OBJECTS) am_test_auth_cache_OBJECTS = test_auth_cache-auth-cache.$(OBJEXT) \ test_auth_cache-test-auth-cache.$(OBJEXT) test_auth_cache_OBJECTS = $(am_test_auth_cache_OBJECTS) am_test_libpassword_OBJECTS = \ test_libpassword-test-libpassword.$(OBJEXT) test_libpassword_OBJECTS = $(am_test_libpassword_OBJECTS) am_test_mech_OBJECTS = test-mock.$(OBJEXT) test-mech.$(OBJEXT) test_mech_OBJECTS = $(am_test_mech_OBJECTS) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/auth-cache.Plo \ ./$(DEPDIR)/auth-client-connection.Plo \ ./$(DEPDIR)/auth-fields.Plo ./$(DEPDIR)/auth-main.Po \ ./$(DEPDIR)/auth-master-connection.Plo \ ./$(DEPDIR)/auth-penalty.Plo ./$(DEPDIR)/auth-policy.Plo \ ./$(DEPDIR)/auth-request-fields.Plo \ ./$(DEPDIR)/auth-request-handler.Plo \ ./$(DEPDIR)/auth-request-stats.Plo \ ./$(DEPDIR)/auth-request-var-expand.Plo \ ./$(DEPDIR)/auth-request.Plo ./$(DEPDIR)/auth-settings.Plo \ ./$(DEPDIR)/auth-stats.Plo ./$(DEPDIR)/auth-token.Plo \ ./$(DEPDIR)/auth-worker-client.Plo \ ./$(DEPDIR)/auth-worker-server.Plo ./$(DEPDIR)/auth.Plo \ ./$(DEPDIR)/checkpassword_reply-checkpassword-reply.Po \ ./$(DEPDIR)/db-checkpassword.Plo \ ./$(DEPDIR)/db-dict-cache-key.Plo ./$(DEPDIR)/db-dict.Plo \ ./$(DEPDIR)/db-ldap.Plo ./$(DEPDIR)/db-lua.Plo \ ./$(DEPDIR)/db-oauth2.Plo ./$(DEPDIR)/db-passwd-file.Plo \ ./$(DEPDIR)/db-sql.Plo \ ./$(DEPDIR)/libauthdb_imap_la-passdb-imap.Plo \ ./$(DEPDIR)/libauthdb_ldap_la-db-ldap.Plo \ ./$(DEPDIR)/libauthdb_ldap_la-passdb-ldap.Plo \ ./$(DEPDIR)/libauthdb_ldap_la-userdb-ldap.Plo \ ./$(DEPDIR)/libauthdb_lua_la-db-lua.Plo \ ./$(DEPDIR)/libauthdb_lua_la-passdb-lua.Plo \ ./$(DEPDIR)/libauthdb_lua_la-userdb-lua.Plo \ ./$(DEPDIR)/libmech_gssapi_la-mech-gssapi.Plo \ ./$(DEPDIR)/libpassword_la-crypt-blowfish.Plo \ ./$(DEPDIR)/libpassword_la-mycrypt.Plo \ ./$(DEPDIR)/libpassword_la-password-scheme-crypt.Plo \ ./$(DEPDIR)/libpassword_la-password-scheme-md5crypt.Plo \ ./$(DEPDIR)/libpassword_la-password-scheme-otp.Plo \ ./$(DEPDIR)/libpassword_la-password-scheme-pbkdf2.Plo \ ./$(DEPDIR)/libpassword_la-password-scheme-scram.Plo \ ./$(DEPDIR)/libpassword_la-password-scheme-sodium.Plo \ ./$(DEPDIR)/libpassword_la-password-scheme.Plo \ ./$(DEPDIR)/mech-anonymous.Plo ./$(DEPDIR)/mech-apop.Plo \ ./$(DEPDIR)/mech-cram-md5.Plo ./$(DEPDIR)/mech-digest-md5.Plo \ ./$(DEPDIR)/mech-dovecot-token.Plo \ ./$(DEPDIR)/mech-external.Plo ./$(DEPDIR)/mech-gssapi.Plo \ ./$(DEPDIR)/mech-login.Plo ./$(DEPDIR)/mech-oauth2.Plo \ ./$(DEPDIR)/mech-otp-common.Plo ./$(DEPDIR)/mech-otp.Plo \ ./$(DEPDIR)/mech-plain-common.Plo ./$(DEPDIR)/mech-plain.Plo \ ./$(DEPDIR)/mech-scram.Plo ./$(DEPDIR)/mech-winbind.Plo \ ./$(DEPDIR)/mech.Plo ./$(DEPDIR)/passdb-blocking.Plo \ ./$(DEPDIR)/passdb-bsdauth.Plo ./$(DEPDIR)/passdb-cache.Plo \ ./$(DEPDIR)/passdb-checkpassword.Plo \ ./$(DEPDIR)/passdb-dict.Plo ./$(DEPDIR)/passdb-ldap.Plo \ ./$(DEPDIR)/passdb-lua.Plo ./$(DEPDIR)/passdb-oauth2.Plo \ ./$(DEPDIR)/passdb-pam.Plo ./$(DEPDIR)/passdb-passwd-file.Plo \ ./$(DEPDIR)/passdb-passwd.Plo ./$(DEPDIR)/passdb-shadow.Plo \ ./$(DEPDIR)/passdb-sql.Plo ./$(DEPDIR)/passdb-static.Plo \ ./$(DEPDIR)/passdb-template.Plo ./$(DEPDIR)/passdb.Plo \ ./$(DEPDIR)/test-auth-request-fields.Po \ ./$(DEPDIR)/test-auth-request-var-expand.Po \ ./$(DEPDIR)/test-db-dict.Po ./$(DEPDIR)/test-lua.Po \ ./$(DEPDIR)/test-main.Po ./$(DEPDIR)/test-mech.Po \ ./$(DEPDIR)/test-mock.Po ./$(DEPDIR)/test-username-filter.Po \ ./$(DEPDIR)/test_auth_cache-auth-cache.Po \ ./$(DEPDIR)/test_auth_cache-test-auth-cache.Po \ ./$(DEPDIR)/test_libpassword-test-libpassword.Po \ ./$(DEPDIR)/userdb-blocking.Plo \ ./$(DEPDIR)/userdb-checkpassword.Plo \ ./$(DEPDIR)/userdb-dict.Plo ./$(DEPDIR)/userdb-ldap.Plo \ ./$(DEPDIR)/userdb-lua.Plo ./$(DEPDIR)/userdb-passwd-file.Plo \ ./$(DEPDIR)/userdb-passwd.Plo ./$(DEPDIR)/userdb-prefetch.Plo \ ./$(DEPDIR)/userdb-sql.Plo ./$(DEPDIR)/userdb-static.Plo \ ./$(DEPDIR)/userdb-template.Plo ./$(DEPDIR)/userdb.Plo am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(libauth_la_SOURCES) $(libauthdb_imap_la_SOURCES) \ $(libauthdb_ldap_la_SOURCES) $(libauthdb_lua_la_SOURCES) \ $(libmech_gssapi_la_SOURCES) $(libpassword_la_SOURCES) \ $(libstats_auth_la_SOURCES) $(auth_SOURCES) \ checkpassword-reply.c $(test_auth_SOURCES) \ $(test_auth_cache_SOURCES) $(test_libpassword_SOURCES) \ $(test_mech_SOURCES) DIST_SOURCES = $(libauth_la_SOURCES) $(libauthdb_imap_la_SOURCES) \ $(am__libauthdb_ldap_la_SOURCES_DIST) \ $(am__libauthdb_lua_la_SOURCES_DIST) \ $(am__libmech_gssapi_la_SOURCES_DIST) \ $(libpassword_la_SOURCES) $(libstats_auth_la_SOURCES) \ $(auth_SOURCES) checkpassword-reply.c $(test_auth_SOURCES) \ $(test_auth_cache_SOURCES) $(test_libpassword_SOURCES) \ $(test_mech_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) pkglibexecdir = $(libexecdir)/dovecot ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ # automake seems to force making this unconditional.. NOPLUGIN_LDFLAGS = OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ noinst_LTLIBRARIES = libpassword.la libauth.la auth_moduledir = $(moduledir)/auth @GSSAPI_PLUGIN_TRUE@GSSAPI_LIB = libmech_gssapi.la @LDAP_PLUGIN_TRUE@LDAP_LIB = libauthdb_ldap.la LUA_LIB = $(am__append_1) AUTH_LUA_LIBS = $(am__append_2) AUTH_LUA_LDADD = $(am__append_3) auth_module_LTLIBRARIES = \ $(GSSAPI_LIB) \ $(LDAP_LIB) \ $(LUA_LIB) \ libauthdb_imap.la AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-dict \ -I$(top_srcdir)/src/lib-dns \ -I$(top_srcdir)/src/lib-http \ -I$(top_srcdir)/src/lib-sql \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-old-stats \ -I$(top_srcdir)/src/lib-otp \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-oauth2 \ -I$(top_srcdir)/src/lib-ssl-iostream \ -I$(top_srcdir)/src/lib-lua \ -I$(top_srcdir)/src/lib-dcrypt \ -DAUTH_MODULE_DIR=\""$(auth_moduledir)"\" \ -DPKG_LIBEXECDIR=\""$(pkglibexecdir)"\" \ -DPKG_RUNDIR=\""$(rundir)"\" \ -DSYSCONFDIR=\""$(sysconfdir)/dovecot"\" \ $(LUA_CFLAGS) \ $(AUTH_CFLAGS) auth_LDFLAGS = -export-dynamic libpassword_la_SOURCES = \ crypt-blowfish.c \ mycrypt.c \ password-scheme.c \ password-scheme-crypt.c \ password-scheme-md5crypt.c \ password-scheme-scram.c \ password-scheme-otp.c \ password-scheme-pbkdf2.c \ password-scheme-sodium.c libpassword_la_CFLAGS = $(AM_CPPFLAGS) $(LIBSODIUM_CFLAGS) auth_libs = \ libauth.la \ libstats_auth.la \ libpassword.la \ ../lib-otp/libotp.la \ $(AUTH_LUA_LIBS) \ $(LIBDOVECOT_SQL) auth_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS) auth_LDADD = $(auth_libs) $(LIBDOVECOT) $(AUTH_LIBS) $(BINARY_LDFLAGS) $(AUTH_LUA_LDADD) auth_DEPENDENCIES = $(auth_libs) $(LIBDOVECOT_DEPS) auth_SOURCES = main.c ldap_sources = db-ldap.c passdb-ldap.c userdb-ldap.c lua_sources = db-lua.c passdb-lua.c userdb-lua.c libauth_la_DEPENDENCIES = $(LIBDOVECOT_DEPS) libauth_la_SOURCES = \ auth.c \ auth-cache.c \ auth-client-connection.c \ auth-master-connection.c \ auth-policy.c \ mech-otp-common.c \ mech-plain-common.c \ auth-penalty.c \ auth-request.c \ auth-request-fields.c \ auth-request-handler.c \ auth-request-stats.c \ auth-request-var-expand.c \ auth-settings.c \ auth-fields.c \ auth-token.c \ auth-worker-client.c \ auth-worker-server.c \ db-checkpassword.c \ db-dict.c \ db-dict-cache-key.c \ db-oauth2.c \ db-sql.c \ db-passwd-file.c \ mech.c \ mech-anonymous.c \ mech-plain.c \ mech-login.c \ mech-cram-md5.c \ mech-digest-md5.c \ mech-external.c \ mech-gssapi.c \ mech-otp.c \ mech-scram.c \ mech-apop.c \ mech-winbind.c \ mech-dovecot-token.c \ mech-oauth2.c \ passdb.c \ passdb-blocking.c \ passdb-bsdauth.c \ passdb-cache.c \ passdb-checkpassword.c \ passdb-dict.c \ passdb-oauth2.c \ passdb-passwd.c \ passdb-passwd-file.c \ passdb-pam.c \ passdb-shadow.c \ passdb-sql.c \ passdb-static.c \ passdb-template.c \ userdb.c \ userdb-blocking.c \ userdb-checkpassword.c \ userdb-dict.c \ userdb-passwd.c \ userdb-passwd-file.c \ userdb-prefetch.c \ userdb-static.c \ userdb-sql.c \ userdb-template.c \ $(ldap_sources) \ $(lua_sources) headers = \ auth.h \ auth-cache.h \ auth-client-connection.h \ auth-common.h \ auth-master-connection.h \ mech-otp-common.h \ mech-plain-common.h \ mech-digest-md5-private.h \ mech-scram.h \ auth-penalty.h \ auth-policy.h \ auth-request.h \ auth-request-handler.h \ auth-request-handler-private.h \ auth-request-stats.h \ auth-request-var-expand.h \ auth-settings.h \ auth-stats.h \ auth-fields.h \ auth-token.h \ auth-worker-client.h \ auth-worker-server.h \ db-dict.h \ db-ldap.h \ db-sql.h \ db-passwd-file.h \ db-checkpassword.h \ db-oauth2.h \ mech.h \ mycrypt.h \ passdb.h \ passdb-blocking.h \ passdb-cache.h \ passdb-template.h \ password-scheme.h \ userdb.h \ userdb-blocking.h \ userdb-template.h @GSSAPI_PLUGIN_TRUE@libmech_gssapi_la_LDFLAGS = -module -avoid-version @GSSAPI_PLUGIN_TRUE@libmech_gssapi_la_LIBADD = $(KRB5_LIBS) @GSSAPI_PLUGIN_TRUE@libmech_gssapi_la_CPPFLAGS = $(AM_CPPFLAGS) $(KRB5_CFLAGS) -DPLUGIN_BUILD @GSSAPI_PLUGIN_TRUE@libmech_gssapi_la_SOURCES = mech-gssapi.c @LDAP_PLUGIN_TRUE@libauthdb_ldap_la_LDFLAGS = -module -avoid-version @LDAP_PLUGIN_TRUE@libauthdb_ldap_la_LIBADD = $(LDAP_LIBS) @LDAP_PLUGIN_TRUE@libauthdb_ldap_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD @LDAP_PLUGIN_TRUE@libauthdb_ldap_la_SOURCES = $(ldap_sources) @AUTH_LUA_PLUGIN_TRUE@libauthdb_lua_la_LDFLAGS = -module -avoid-version @AUTH_LUA_PLUGIN_TRUE@libauthdb_lua_la_LIBADD = $(LIBDOVECOT_LUA) @AUTH_LUA_PLUGIN_TRUE@libauthdb_lua_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD @AUTH_LUA_PLUGIN_TRUE@libauthdb_lua_la_SOURCES = $(lua_sources) libauthdb_imap_la_LDFLAGS = -module -avoid-version libauthdb_imap_la_LIBADD = \ ../lib-imap-client/libimap_client.la \ $(LIBDOVECOT) libauthdb_imap_la_CPPFLAGS = \ $(AM_CPPFLAGS) \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-imap-client libauthdb_imap_la_SOURCES = passdb-imap.c pkginc_libdir = $(pkgincludedir) pkginc_lib_HEADERS = $(headers) checkpassword_reply_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS) checkpassword_reply_LDADD = $(LIBDOVECOT) $(BINARY_LDFLAGS) checkpassword_reply_DEPENDENCIES = $(LIBDOVECOT_DEPS) checkpassword_reply_sources = \ checkpassword-reply.c stats_moduledir = $(moduledir)/old-stats stats_module_LTLIBRARIES = libstats_auth.la libstats_auth_la_LDFLAGS = -module -avoid-version libstats_auth_la_LIBADD = $(LIBDOVECOT) libstats_auth_la_DEPENDENCIES = $(LIBDOVECOT_DEPS) libstats_auth_la_SOURCES = auth-stats.c test_programs = \ test-libpassword \ test-auth-cache \ test-auth \ test-mech noinst_HEADERS = test-auth.h crypt-blowfish.h db-lua.h test_libs = \ ../lib-dovecot/libdovecot.la test_libpassword_SOURCES = test-libpassword.c test_libpassword_LDADD = \ libpassword.la \ ../lib-otp/libotp.la \ $(CRYPT_LIBS) \ $(LIBDOVECOT_SQL) \ $(LIBSODIUM_LIBS) \ $(test_libs) \ $(BINARY_LDFLAGS) test_libpassword_DEPENDENCIES = libpassword.la test_libpassword_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS) test_auth_cache_SOURCES = auth-cache.c test-auth-cache.c test_auth_cache_LDADD = $(test_libs) test_auth_cache_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs) # this is needed to force auth-cache.c recompilation test_auth_cache_CPPFLAGS = $(AM_CPPFLAGS) test_auth_SOURCES = \ test-auth-request-var-expand.c \ test-auth-request-fields.c \ test-username-filter.c \ test-db-dict.c \ test-lua.c \ test-mock.c \ test-main.c test_auth_LDADD = $(test_libs) $(auth_libs) $(AUTH_LIBS) $(LUA_LIBS) test_auth_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs) test_mech_SOURCES = \ test-mock.c \ test-mech.c test_mech_LDADD = $(test_libs) $(auth_libs) $(AUTH_LIBS) $(LUA_LIBS) test_mech_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs) all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/auth/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/auth/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): clean-noinstPROGRAMS: @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) @$(NORMAL_INSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ || test -f $$p1 \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ } \ ; done uninstall-pkglibexecPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files clean-pkglibexecPROGRAMS: @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list install-auth_moduleLTLIBRARIES: $(auth_module_LTLIBRARIES) @$(NORMAL_INSTALL) @list='$(auth_module_LTLIBRARIES)'; test -n "$(auth_moduledir)" || list=; \ list2=; for p in $$list; do \ if test -f $$p; then \ list2="$$list2 $$p"; \ else :; fi; \ done; \ test -z "$$list2" || { \ echo " $(MKDIR_P) '$(DESTDIR)$(auth_moduledir)'"; \ $(MKDIR_P) "$(DESTDIR)$(auth_moduledir)" || exit 1; \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(auth_moduledir)'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(auth_moduledir)"; \ } uninstall-auth_moduleLTLIBRARIES: @$(NORMAL_UNINSTALL) @list='$(auth_module_LTLIBRARIES)'; test -n "$(auth_moduledir)" || list=; \ for p in $$list; do \ $(am__strip_dir) \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(auth_moduledir)/$$f'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(auth_moduledir)/$$f"; \ done clean-auth_moduleLTLIBRARIES: -test -z "$(auth_module_LTLIBRARIES)" || rm -f $(auth_module_LTLIBRARIES) @list='$(auth_module_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } clean-noinstLTLIBRARIES: -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) @list='$(noinst_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } install-stats_moduleLTLIBRARIES: $(stats_module_LTLIBRARIES) @$(NORMAL_INSTALL) @list='$(stats_module_LTLIBRARIES)'; test -n "$(stats_moduledir)" || list=; \ list2=; for p in $$list; do \ if test -f $$p; then \ list2="$$list2 $$p"; \ else :; fi; \ done; \ test -z "$$list2" || { \ echo " $(MKDIR_P) '$(DESTDIR)$(stats_moduledir)'"; \ $(MKDIR_P) "$(DESTDIR)$(stats_moduledir)" || exit 1; \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(stats_moduledir)'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(stats_moduledir)"; \ } uninstall-stats_moduleLTLIBRARIES: @$(NORMAL_UNINSTALL) @list='$(stats_module_LTLIBRARIES)'; test -n "$(stats_moduledir)" || list=; \ for p in $$list; do \ $(am__strip_dir) \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(stats_moduledir)/$$f'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(stats_moduledir)/$$f"; \ done clean-stats_moduleLTLIBRARIES: -test -z "$(stats_module_LTLIBRARIES)" || rm -f $(stats_module_LTLIBRARIES) @list='$(stats_module_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } libauth.la: $(libauth_la_OBJECTS) $(libauth_la_DEPENDENCIES) $(EXTRA_libauth_la_DEPENDENCIES) $(AM_V_CCLD)$(LINK) $(libauth_la_OBJECTS) $(libauth_la_LIBADD) $(LIBS) libauthdb_imap.la: $(libauthdb_imap_la_OBJECTS) $(libauthdb_imap_la_DEPENDENCIES) $(EXTRA_libauthdb_imap_la_DEPENDENCIES) $(AM_V_CCLD)$(libauthdb_imap_la_LINK) -rpath $(auth_moduledir) $(libauthdb_imap_la_OBJECTS) $(libauthdb_imap_la_LIBADD) $(LIBS) libauthdb_ldap.la: $(libauthdb_ldap_la_OBJECTS) $(libauthdb_ldap_la_DEPENDENCIES) $(EXTRA_libauthdb_ldap_la_DEPENDENCIES) $(AM_V_CCLD)$(libauthdb_ldap_la_LINK) $(am_libauthdb_ldap_la_rpath) $(libauthdb_ldap_la_OBJECTS) $(libauthdb_ldap_la_LIBADD) $(LIBS) libauthdb_lua.la: $(libauthdb_lua_la_OBJECTS) $(libauthdb_lua_la_DEPENDENCIES) $(EXTRA_libauthdb_lua_la_DEPENDENCIES) $(AM_V_CCLD)$(libauthdb_lua_la_LINK) $(am_libauthdb_lua_la_rpath) $(libauthdb_lua_la_OBJECTS) $(libauthdb_lua_la_LIBADD) $(LIBS) libmech_gssapi.la: $(libmech_gssapi_la_OBJECTS) $(libmech_gssapi_la_DEPENDENCIES) $(EXTRA_libmech_gssapi_la_DEPENDENCIES) $(AM_V_CCLD)$(libmech_gssapi_la_LINK) $(am_libmech_gssapi_la_rpath) $(libmech_gssapi_la_OBJECTS) $(libmech_gssapi_la_LIBADD) $(LIBS) libpassword.la: $(libpassword_la_OBJECTS) $(libpassword_la_DEPENDENCIES) $(EXTRA_libpassword_la_DEPENDENCIES) $(AM_V_CCLD)$(libpassword_la_LINK) $(libpassword_la_OBJECTS) $(libpassword_la_LIBADD) $(LIBS) libstats_auth.la: $(libstats_auth_la_OBJECTS) $(libstats_auth_la_DEPENDENCIES) $(EXTRA_libstats_auth_la_DEPENDENCIES) $(AM_V_CCLD)$(libstats_auth_la_LINK) -rpath $(stats_moduledir) $(libstats_auth_la_OBJECTS) $(libstats_auth_la_LIBADD) $(LIBS) auth$(EXEEXT): $(auth_OBJECTS) $(auth_DEPENDENCIES) $(EXTRA_auth_DEPENDENCIES) @rm -f auth$(EXEEXT) $(AM_V_CCLD)$(auth_LINK) $(auth_OBJECTS) $(auth_LDADD) $(LIBS) checkpassword-reply$(EXEEXT): $(checkpassword_reply_OBJECTS) $(checkpassword_reply_DEPENDENCIES) $(EXTRA_checkpassword_reply_DEPENDENCIES) @rm -f checkpassword-reply$(EXEEXT) $(AM_V_CCLD)$(LINK) $(checkpassword_reply_OBJECTS) $(checkpassword_reply_LDADD) $(LIBS) test-auth$(EXEEXT): $(test_auth_OBJECTS) $(test_auth_DEPENDENCIES) $(EXTRA_test_auth_DEPENDENCIES) @rm -f test-auth$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_auth_OBJECTS) $(test_auth_LDADD) $(LIBS) test-auth-cache$(EXEEXT): $(test_auth_cache_OBJECTS) $(test_auth_cache_DEPENDENCIES) $(EXTRA_test_auth_cache_DEPENDENCIES) @rm -f test-auth-cache$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_auth_cache_OBJECTS) $(test_auth_cache_LDADD) $(LIBS) test-libpassword$(EXEEXT): $(test_libpassword_OBJECTS) $(test_libpassword_DEPENDENCIES) $(EXTRA_test_libpassword_DEPENDENCIES) @rm -f test-libpassword$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_libpassword_OBJECTS) $(test_libpassword_LDADD) $(LIBS) test-mech$(EXEEXT): $(test_mech_OBJECTS) $(test_mech_DEPENDENCIES) $(EXTRA_test_mech_DEPENDENCIES) @rm -f test-mech$(EXEEXT) $(AM_V_CCLD)$(LINK) $(test_mech_OBJECTS) $(test_mech_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-cache.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-client-connection.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-fields.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-master-connection.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-penalty.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-policy.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-request-fields.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-request-handler.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-request-stats.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-request-var-expand.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-request.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-settings.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-stats.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-token.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-worker-client.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth-worker-server.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/checkpassword_reply-checkpassword-reply.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-checkpassword.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-dict-cache-key.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-dict.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-ldap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-lua.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-oauth2.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-passwd-file.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db-sql.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libauthdb_imap_la-passdb-imap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libauthdb_ldap_la-db-ldap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libauthdb_ldap_la-passdb-ldap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libauthdb_ldap_la-userdb-ldap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libauthdb_lua_la-db-lua.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libauthdb_lua_la-passdb-lua.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libauthdb_lua_la-userdb-lua.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmech_gssapi_la-mech-gssapi.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpassword_la-crypt-blowfish.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpassword_la-mycrypt.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpassword_la-password-scheme-crypt.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpassword_la-password-scheme-md5crypt.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpassword_la-password-scheme-otp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpassword_la-password-scheme-pbkdf2.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpassword_la-password-scheme-scram.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpassword_la-password-scheme-sodium.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpassword_la-password-scheme.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-anonymous.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-apop.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-cram-md5.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-digest-md5.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-dovecot-token.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-external.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-gssapi.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-login.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-oauth2.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-otp-common.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-otp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-plain-common.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-plain.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-scram.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-winbind.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-blocking.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-bsdauth.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-cache.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-checkpassword.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-dict.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-ldap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-lua.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-oauth2.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-pam.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-passwd-file.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-passwd.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-shadow.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-sql.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-static.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-template.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-auth-request-fields.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-auth-request-var-expand.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-db-dict.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-lua.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-main.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-mech.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-mock.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-username-filter.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_auth_cache-auth-cache.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_auth_cache-test-auth-cache.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_libpassword-test-libpassword.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-blocking.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-checkpassword.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-dict.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-ldap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-lua.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-passwd-file.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-passwd.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-prefetch.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-sql.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-static.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb-template.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/userdb.Plo@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< libauthdb_imap_la-passdb-imap.lo: passdb-imap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_imap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libauthdb_imap_la-passdb-imap.lo -MD -MP -MF $(DEPDIR)/libauthdb_imap_la-passdb-imap.Tpo -c -o libauthdb_imap_la-passdb-imap.lo `test -f 'passdb-imap.c' || echo '$(srcdir)/'`passdb-imap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libauthdb_imap_la-passdb-imap.Tpo $(DEPDIR)/libauthdb_imap_la-passdb-imap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='passdb-imap.c' object='libauthdb_imap_la-passdb-imap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_imap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libauthdb_imap_la-passdb-imap.lo `test -f 'passdb-imap.c' || echo '$(srcdir)/'`passdb-imap.c libauthdb_ldap_la-db-ldap.lo: db-ldap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libauthdb_ldap_la-db-ldap.lo -MD -MP -MF $(DEPDIR)/libauthdb_ldap_la-db-ldap.Tpo -c -o libauthdb_ldap_la-db-ldap.lo `test -f 'db-ldap.c' || echo '$(srcdir)/'`db-ldap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libauthdb_ldap_la-db-ldap.Tpo $(DEPDIR)/libauthdb_ldap_la-db-ldap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='db-ldap.c' object='libauthdb_ldap_la-db-ldap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libauthdb_ldap_la-db-ldap.lo `test -f 'db-ldap.c' || echo '$(srcdir)/'`db-ldap.c libauthdb_ldap_la-passdb-ldap.lo: passdb-ldap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libauthdb_ldap_la-passdb-ldap.lo -MD -MP -MF $(DEPDIR)/libauthdb_ldap_la-passdb-ldap.Tpo -c -o libauthdb_ldap_la-passdb-ldap.lo `test -f 'passdb-ldap.c' || echo '$(srcdir)/'`passdb-ldap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libauthdb_ldap_la-passdb-ldap.Tpo $(DEPDIR)/libauthdb_ldap_la-passdb-ldap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='passdb-ldap.c' object='libauthdb_ldap_la-passdb-ldap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libauthdb_ldap_la-passdb-ldap.lo `test -f 'passdb-ldap.c' || echo '$(srcdir)/'`passdb-ldap.c libauthdb_ldap_la-userdb-ldap.lo: userdb-ldap.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libauthdb_ldap_la-userdb-ldap.lo -MD -MP -MF $(DEPDIR)/libauthdb_ldap_la-userdb-ldap.Tpo -c -o libauthdb_ldap_la-userdb-ldap.lo `test -f 'userdb-ldap.c' || echo '$(srcdir)/'`userdb-ldap.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libauthdb_ldap_la-userdb-ldap.Tpo $(DEPDIR)/libauthdb_ldap_la-userdb-ldap.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='userdb-ldap.c' object='libauthdb_ldap_la-userdb-ldap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_ldap_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libauthdb_ldap_la-userdb-ldap.lo `test -f 'userdb-ldap.c' || echo '$(srcdir)/'`userdb-ldap.c libauthdb_lua_la-db-lua.lo: db-lua.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_lua_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libauthdb_lua_la-db-lua.lo -MD -MP -MF $(DEPDIR)/libauthdb_lua_la-db-lua.Tpo -c -o libauthdb_lua_la-db-lua.lo `test -f 'db-lua.c' || echo '$(srcdir)/'`db-lua.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libauthdb_lua_la-db-lua.Tpo $(DEPDIR)/libauthdb_lua_la-db-lua.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='db-lua.c' object='libauthdb_lua_la-db-lua.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_lua_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libauthdb_lua_la-db-lua.lo `test -f 'db-lua.c' || echo '$(srcdir)/'`db-lua.c libauthdb_lua_la-passdb-lua.lo: passdb-lua.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_lua_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libauthdb_lua_la-passdb-lua.lo -MD -MP -MF $(DEPDIR)/libauthdb_lua_la-passdb-lua.Tpo -c -o libauthdb_lua_la-passdb-lua.lo `test -f 'passdb-lua.c' || echo '$(srcdir)/'`passdb-lua.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libauthdb_lua_la-passdb-lua.Tpo $(DEPDIR)/libauthdb_lua_la-passdb-lua.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='passdb-lua.c' object='libauthdb_lua_la-passdb-lua.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_lua_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libauthdb_lua_la-passdb-lua.lo `test -f 'passdb-lua.c' || echo '$(srcdir)/'`passdb-lua.c libauthdb_lua_la-userdb-lua.lo: userdb-lua.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_lua_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libauthdb_lua_la-userdb-lua.lo -MD -MP -MF $(DEPDIR)/libauthdb_lua_la-userdb-lua.Tpo -c -o libauthdb_lua_la-userdb-lua.lo `test -f 'userdb-lua.c' || echo '$(srcdir)/'`userdb-lua.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libauthdb_lua_la-userdb-lua.Tpo $(DEPDIR)/libauthdb_lua_la-userdb-lua.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='userdb-lua.c' object='libauthdb_lua_la-userdb-lua.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libauthdb_lua_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libauthdb_lua_la-userdb-lua.lo `test -f 'userdb-lua.c' || echo '$(srcdir)/'`userdb-lua.c libmech_gssapi_la-mech-gssapi.lo: mech-gssapi.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmech_gssapi_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libmech_gssapi_la-mech-gssapi.lo -MD -MP -MF $(DEPDIR)/libmech_gssapi_la-mech-gssapi.Tpo -c -o libmech_gssapi_la-mech-gssapi.lo `test -f 'mech-gssapi.c' || echo '$(srcdir)/'`mech-gssapi.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmech_gssapi_la-mech-gssapi.Tpo $(DEPDIR)/libmech_gssapi_la-mech-gssapi.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mech-gssapi.c' object='libmech_gssapi_la-mech-gssapi.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmech_gssapi_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libmech_gssapi_la-mech-gssapi.lo `test -f 'mech-gssapi.c' || echo '$(srcdir)/'`mech-gssapi.c libpassword_la-crypt-blowfish.lo: crypt-blowfish.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -MT libpassword_la-crypt-blowfish.lo -MD -MP -MF $(DEPDIR)/libpassword_la-crypt-blowfish.Tpo -c -o libpassword_la-crypt-blowfish.lo `test -f 'crypt-blowfish.c' || echo '$(srcdir)/'`crypt-blowfish.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpassword_la-crypt-blowfish.Tpo $(DEPDIR)/libpassword_la-crypt-blowfish.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='crypt-blowfish.c' object='libpassword_la-crypt-blowfish.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -c -o libpassword_la-crypt-blowfish.lo `test -f 'crypt-blowfish.c' || echo '$(srcdir)/'`crypt-blowfish.c libpassword_la-mycrypt.lo: mycrypt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -MT libpassword_la-mycrypt.lo -MD -MP -MF $(DEPDIR)/libpassword_la-mycrypt.Tpo -c -o libpassword_la-mycrypt.lo `test -f 'mycrypt.c' || echo '$(srcdir)/'`mycrypt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpassword_la-mycrypt.Tpo $(DEPDIR)/libpassword_la-mycrypt.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mycrypt.c' object='libpassword_la-mycrypt.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -c -o libpassword_la-mycrypt.lo `test -f 'mycrypt.c' || echo '$(srcdir)/'`mycrypt.c libpassword_la-password-scheme.lo: password-scheme.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -MT libpassword_la-password-scheme.lo -MD -MP -MF $(DEPDIR)/libpassword_la-password-scheme.Tpo -c -o libpassword_la-password-scheme.lo `test -f 'password-scheme.c' || echo '$(srcdir)/'`password-scheme.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpassword_la-password-scheme.Tpo $(DEPDIR)/libpassword_la-password-scheme.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='password-scheme.c' object='libpassword_la-password-scheme.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -c -o libpassword_la-password-scheme.lo `test -f 'password-scheme.c' || echo '$(srcdir)/'`password-scheme.c libpassword_la-password-scheme-crypt.lo: password-scheme-crypt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -MT libpassword_la-password-scheme-crypt.lo -MD -MP -MF $(DEPDIR)/libpassword_la-password-scheme-crypt.Tpo -c -o libpassword_la-password-scheme-crypt.lo `test -f 'password-scheme-crypt.c' || echo '$(srcdir)/'`password-scheme-crypt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpassword_la-password-scheme-crypt.Tpo $(DEPDIR)/libpassword_la-password-scheme-crypt.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='password-scheme-crypt.c' object='libpassword_la-password-scheme-crypt.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -c -o libpassword_la-password-scheme-crypt.lo `test -f 'password-scheme-crypt.c' || echo '$(srcdir)/'`password-scheme-crypt.c libpassword_la-password-scheme-md5crypt.lo: password-scheme-md5crypt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -MT libpassword_la-password-scheme-md5crypt.lo -MD -MP -MF $(DEPDIR)/libpassword_la-password-scheme-md5crypt.Tpo -c -o libpassword_la-password-scheme-md5crypt.lo `test -f 'password-scheme-md5crypt.c' || echo '$(srcdir)/'`password-scheme-md5crypt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpassword_la-password-scheme-md5crypt.Tpo $(DEPDIR)/libpassword_la-password-scheme-md5crypt.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='password-scheme-md5crypt.c' object='libpassword_la-password-scheme-md5crypt.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -c -o libpassword_la-password-scheme-md5crypt.lo `test -f 'password-scheme-md5crypt.c' || echo '$(srcdir)/'`password-scheme-md5crypt.c libpassword_la-password-scheme-scram.lo: password-scheme-scram.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -MT libpassword_la-password-scheme-scram.lo -MD -MP -MF $(DEPDIR)/libpassword_la-password-scheme-scram.Tpo -c -o libpassword_la-password-scheme-scram.lo `test -f 'password-scheme-scram.c' || echo '$(srcdir)/'`password-scheme-scram.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpassword_la-password-scheme-scram.Tpo $(DEPDIR)/libpassword_la-password-scheme-scram.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='password-scheme-scram.c' object='libpassword_la-password-scheme-scram.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -c -o libpassword_la-password-scheme-scram.lo `test -f 'password-scheme-scram.c' || echo '$(srcdir)/'`password-scheme-scram.c libpassword_la-password-scheme-otp.lo: password-scheme-otp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -MT libpassword_la-password-scheme-otp.lo -MD -MP -MF $(DEPDIR)/libpassword_la-password-scheme-otp.Tpo -c -o libpassword_la-password-scheme-otp.lo `test -f 'password-scheme-otp.c' || echo '$(srcdir)/'`password-scheme-otp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpassword_la-password-scheme-otp.Tpo $(DEPDIR)/libpassword_la-password-scheme-otp.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='password-scheme-otp.c' object='libpassword_la-password-scheme-otp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -c -o libpassword_la-password-scheme-otp.lo `test -f 'password-scheme-otp.c' || echo '$(srcdir)/'`password-scheme-otp.c libpassword_la-password-scheme-pbkdf2.lo: password-scheme-pbkdf2.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -MT libpassword_la-password-scheme-pbkdf2.lo -MD -MP -MF $(DEPDIR)/libpassword_la-password-scheme-pbkdf2.Tpo -c -o libpassword_la-password-scheme-pbkdf2.lo `test -f 'password-scheme-pbkdf2.c' || echo '$(srcdir)/'`password-scheme-pbkdf2.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpassword_la-password-scheme-pbkdf2.Tpo $(DEPDIR)/libpassword_la-password-scheme-pbkdf2.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='password-scheme-pbkdf2.c' object='libpassword_la-password-scheme-pbkdf2.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -c -o libpassword_la-password-scheme-pbkdf2.lo `test -f 'password-scheme-pbkdf2.c' || echo '$(srcdir)/'`password-scheme-pbkdf2.c libpassword_la-password-scheme-sodium.lo: password-scheme-sodium.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -MT libpassword_la-password-scheme-sodium.lo -MD -MP -MF $(DEPDIR)/libpassword_la-password-scheme-sodium.Tpo -c -o libpassword_la-password-scheme-sodium.lo `test -f 'password-scheme-sodium.c' || echo '$(srcdir)/'`password-scheme-sodium.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpassword_la-password-scheme-sodium.Tpo $(DEPDIR)/libpassword_la-password-scheme-sodium.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='password-scheme-sodium.c' object='libpassword_la-password-scheme-sodium.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libpassword_la_CFLAGS) $(CFLAGS) -c -o libpassword_la-password-scheme-sodium.lo `test -f 'password-scheme-sodium.c' || echo '$(srcdir)/'`password-scheme-sodium.c auth-main.o: main.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(auth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT auth-main.o -MD -MP -MF $(DEPDIR)/auth-main.Tpo -c -o auth-main.o `test -f 'main.c' || echo '$(srcdir)/'`main.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/auth-main.Tpo $(DEPDIR)/auth-main.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='main.c' object='auth-main.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(auth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o auth-main.o `test -f 'main.c' || echo '$(srcdir)/'`main.c auth-main.obj: main.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(auth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT auth-main.obj -MD -MP -MF $(DEPDIR)/auth-main.Tpo -c -o auth-main.obj `if test -f 'main.c'; then $(CYGPATH_W) 'main.c'; else $(CYGPATH_W) '$(srcdir)/main.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/auth-main.Tpo $(DEPDIR)/auth-main.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='main.c' object='auth-main.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(auth_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o auth-main.obj `if test -f 'main.c'; then $(CYGPATH_W) 'main.c'; else $(CYGPATH_W) '$(srcdir)/main.c'; fi` checkpassword_reply-checkpassword-reply.o: checkpassword-reply.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(checkpassword_reply_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT checkpassword_reply-checkpassword-reply.o -MD -MP -MF $(DEPDIR)/checkpassword_reply-checkpassword-reply.Tpo -c -o checkpassword_reply-checkpassword-reply.o `test -f 'checkpassword-reply.c' || echo '$(srcdir)/'`checkpassword-reply.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/checkpassword_reply-checkpassword-reply.Tpo $(DEPDIR)/checkpassword_reply-checkpassword-reply.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='checkpassword-reply.c' object='checkpassword_reply-checkpassword-reply.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(checkpassword_reply_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o checkpassword_reply-checkpassword-reply.o `test -f 'checkpassword-reply.c' || echo '$(srcdir)/'`checkpassword-reply.c checkpassword_reply-checkpassword-reply.obj: checkpassword-reply.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(checkpassword_reply_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT checkpassword_reply-checkpassword-reply.obj -MD -MP -MF $(DEPDIR)/checkpassword_reply-checkpassword-reply.Tpo -c -o checkpassword_reply-checkpassword-reply.obj `if test -f 'checkpassword-reply.c'; then $(CYGPATH_W) 'checkpassword-reply.c'; else $(CYGPATH_W) '$(srcdir)/checkpassword-reply.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/checkpassword_reply-checkpassword-reply.Tpo $(DEPDIR)/checkpassword_reply-checkpassword-reply.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='checkpassword-reply.c' object='checkpassword_reply-checkpassword-reply.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(checkpassword_reply_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o checkpassword_reply-checkpassword-reply.obj `if test -f 'checkpassword-reply.c'; then $(CYGPATH_W) 'checkpassword-reply.c'; else $(CYGPATH_W) '$(srcdir)/checkpassword-reply.c'; fi` test_auth_cache-auth-cache.o: auth-cache.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_auth_cache_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_auth_cache-auth-cache.o -MD -MP -MF $(DEPDIR)/test_auth_cache-auth-cache.Tpo -c -o test_auth_cache-auth-cache.o `test -f 'auth-cache.c' || echo '$(srcdir)/'`auth-cache.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_auth_cache-auth-cache.Tpo $(DEPDIR)/test_auth_cache-auth-cache.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='auth-cache.c' object='test_auth_cache-auth-cache.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_auth_cache_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_auth_cache-auth-cache.o `test -f 'auth-cache.c' || echo '$(srcdir)/'`auth-cache.c test_auth_cache-auth-cache.obj: auth-cache.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_auth_cache_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_auth_cache-auth-cache.obj -MD -MP -MF $(DEPDIR)/test_auth_cache-auth-cache.Tpo -c -o test_auth_cache-auth-cache.obj `if test -f 'auth-cache.c'; then $(CYGPATH_W) 'auth-cache.c'; else $(CYGPATH_W) '$(srcdir)/auth-cache.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_auth_cache-auth-cache.Tpo $(DEPDIR)/test_auth_cache-auth-cache.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='auth-cache.c' object='test_auth_cache-auth-cache.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_auth_cache_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_auth_cache-auth-cache.obj `if test -f 'auth-cache.c'; then $(CYGPATH_W) 'auth-cache.c'; else $(CYGPATH_W) '$(srcdir)/auth-cache.c'; fi` test_auth_cache-test-auth-cache.o: test-auth-cache.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_auth_cache_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_auth_cache-test-auth-cache.o -MD -MP -MF $(DEPDIR)/test_auth_cache-test-auth-cache.Tpo -c -o test_auth_cache-test-auth-cache.o `test -f 'test-auth-cache.c' || echo '$(srcdir)/'`test-auth-cache.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_auth_cache-test-auth-cache.Tpo $(DEPDIR)/test_auth_cache-test-auth-cache.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-auth-cache.c' object='test_auth_cache-test-auth-cache.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_auth_cache_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_auth_cache-test-auth-cache.o `test -f 'test-auth-cache.c' || echo '$(srcdir)/'`test-auth-cache.c test_auth_cache-test-auth-cache.obj: test-auth-cache.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_auth_cache_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_auth_cache-test-auth-cache.obj -MD -MP -MF $(DEPDIR)/test_auth_cache-test-auth-cache.Tpo -c -o test_auth_cache-test-auth-cache.obj `if test -f 'test-auth-cache.c'; then $(CYGPATH_W) 'test-auth-cache.c'; else $(CYGPATH_W) '$(srcdir)/test-auth-cache.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_auth_cache-test-auth-cache.Tpo $(DEPDIR)/test_auth_cache-test-auth-cache.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-auth-cache.c' object='test_auth_cache-test-auth-cache.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_auth_cache_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_auth_cache-test-auth-cache.obj `if test -f 'test-auth-cache.c'; then $(CYGPATH_W) 'test-auth-cache.c'; else $(CYGPATH_W) '$(srcdir)/test-auth-cache.c'; fi` test_libpassword-test-libpassword.o: test-libpassword.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_libpassword_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_libpassword-test-libpassword.o -MD -MP -MF $(DEPDIR)/test_libpassword-test-libpassword.Tpo -c -o test_libpassword-test-libpassword.o `test -f 'test-libpassword.c' || echo '$(srcdir)/'`test-libpassword.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_libpassword-test-libpassword.Tpo $(DEPDIR)/test_libpassword-test-libpassword.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-libpassword.c' object='test_libpassword-test-libpassword.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_libpassword_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_libpassword-test-libpassword.o `test -f 'test-libpassword.c' || echo '$(srcdir)/'`test-libpassword.c test_libpassword-test-libpassword.obj: test-libpassword.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_libpassword_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT test_libpassword-test-libpassword.obj -MD -MP -MF $(DEPDIR)/test_libpassword-test-libpassword.Tpo -c -o test_libpassword-test-libpassword.obj `if test -f 'test-libpassword.c'; then $(CYGPATH_W) 'test-libpassword.c'; else $(CYGPATH_W) '$(srcdir)/test-libpassword.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_libpassword-test-libpassword.Tpo $(DEPDIR)/test_libpassword-test-libpassword.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-libpassword.c' object='test_libpassword-test-libpassword.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(test_libpassword_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o test_libpassword-test-libpassword.obj `if test -f 'test-libpassword.c'; then $(CYGPATH_W) 'test-libpassword.c'; else $(CYGPATH_W) '$(srcdir)/test-libpassword.c'; fi` mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) @$(NORMAL_INSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ done uninstall-pkginc_libHEADERS: @$(NORMAL_UNINSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am $(MAKE) $(AM_MAKEFLAGS) check-local check: check-am all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(auth_moduledir)" "$(DESTDIR)$(stats_moduledir)" "$(DESTDIR)$(pkginc_libdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-auth_moduleLTLIBRARIES clean-generic clean-libtool \ clean-noinstLTLIBRARIES clean-noinstPROGRAMS \ clean-pkglibexecPROGRAMS clean-stats_moduleLTLIBRARIES \ mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/auth-cache.Plo -rm -f ./$(DEPDIR)/auth-client-connection.Plo -rm -f ./$(DEPDIR)/auth-fields.Plo -rm -f ./$(DEPDIR)/auth-main.Po -rm -f ./$(DEPDIR)/auth-master-connection.Plo -rm -f ./$(DEPDIR)/auth-penalty.Plo -rm -f ./$(DEPDIR)/auth-policy.Plo -rm -f ./$(DEPDIR)/auth-request-fields.Plo -rm -f ./$(DEPDIR)/auth-request-handler.Plo -rm -f ./$(DEPDIR)/auth-request-stats.Plo -rm -f ./$(DEPDIR)/auth-request-var-expand.Plo -rm -f ./$(DEPDIR)/auth-request.Plo -rm -f ./$(DEPDIR)/auth-settings.Plo -rm -f ./$(DEPDIR)/auth-stats.Plo -rm -f ./$(DEPDIR)/auth-token.Plo -rm -f ./$(DEPDIR)/auth-worker-client.Plo -rm -f ./$(DEPDIR)/auth-worker-server.Plo -rm -f ./$(DEPDIR)/auth.Plo -rm -f ./$(DEPDIR)/checkpassword_reply-checkpassword-reply.Po -rm -f ./$(DEPDIR)/db-checkpassword.Plo -rm -f ./$(DEPDIR)/db-dict-cache-key.Plo -rm -f ./$(DEPDIR)/db-dict.Plo -rm -f ./$(DEPDIR)/db-ldap.Plo -rm -f ./$(DEPDIR)/db-lua.Plo -rm -f ./$(DEPDIR)/db-oauth2.Plo -rm -f ./$(DEPDIR)/db-passwd-file.Plo -rm -f ./$(DEPDIR)/db-sql.Plo -rm -f ./$(DEPDIR)/libauthdb_imap_la-passdb-imap.Plo -rm -f ./$(DEPDIR)/libauthdb_ldap_la-db-ldap.Plo -rm -f ./$(DEPDIR)/libauthdb_ldap_la-passdb-ldap.Plo -rm -f ./$(DEPDIR)/libauthdb_ldap_la-userdb-ldap.Plo -rm -f ./$(DEPDIR)/libauthdb_lua_la-db-lua.Plo -rm -f ./$(DEPDIR)/libauthdb_lua_la-passdb-lua.Plo -rm -f ./$(DEPDIR)/libauthdb_lua_la-userdb-lua.Plo -rm -f ./$(DEPDIR)/libmech_gssapi_la-mech-gssapi.Plo -rm -f ./$(DEPDIR)/libpassword_la-crypt-blowfish.Plo -rm -f ./$(DEPDIR)/libpassword_la-mycrypt.Plo -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-crypt.Plo -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-md5crypt.Plo -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-otp.Plo -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-pbkdf2.Plo -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-scram.Plo -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-sodium.Plo -rm -f ./$(DEPDIR)/libpassword_la-password-scheme.Plo -rm -f ./$(DEPDIR)/mech-anonymous.Plo -rm -f ./$(DEPDIR)/mech-apop.Plo -rm -f ./$(DEPDIR)/mech-cram-md5.Plo -rm -f ./$(DEPDIR)/mech-digest-md5.Plo -rm -f ./$(DEPDIR)/mech-dovecot-token.Plo -rm -f ./$(DEPDIR)/mech-external.Plo -rm -f ./$(DEPDIR)/mech-gssapi.Plo -rm -f ./$(DEPDIR)/mech-login.Plo -rm -f ./$(DEPDIR)/mech-oauth2.Plo -rm -f ./$(DEPDIR)/mech-otp-common.Plo -rm -f ./$(DEPDIR)/mech-otp.Plo -rm -f ./$(DEPDIR)/mech-plain-common.Plo -rm -f ./$(DEPDIR)/mech-plain.Plo -rm -f ./$(DEPDIR)/mech-scram.Plo -rm -f ./$(DEPDIR)/mech-winbind.Plo -rm -f ./$(DEPDIR)/mech.Plo -rm -f ./$(DEPDIR)/passdb-blocking.Plo -rm -f ./$(DEPDIR)/passdb-bsdauth.Plo -rm -f ./$(DEPDIR)/passdb-cache.Plo -rm -f ./$(DEPDIR)/passdb-checkpassword.Plo -rm -f ./$(DEPDIR)/passdb-dict.Plo -rm -f ./$(DEPDIR)/passdb-ldap.Plo -rm -f ./$(DEPDIR)/passdb-lua.Plo -rm -f ./$(DEPDIR)/passdb-oauth2.Plo -rm -f ./$(DEPDIR)/passdb-pam.Plo -rm -f ./$(DEPDIR)/passdb-passwd-file.Plo -rm -f ./$(DEPDIR)/passdb-passwd.Plo -rm -f ./$(DEPDIR)/passdb-shadow.Plo -rm -f ./$(DEPDIR)/passdb-sql.Plo -rm -f ./$(DEPDIR)/passdb-static.Plo -rm -f ./$(DEPDIR)/passdb-template.Plo -rm -f ./$(DEPDIR)/passdb.Plo -rm -f ./$(DEPDIR)/test-auth-request-fields.Po -rm -f ./$(DEPDIR)/test-auth-request-var-expand.Po -rm -f ./$(DEPDIR)/test-db-dict.Po -rm -f ./$(DEPDIR)/test-lua.Po -rm -f ./$(DEPDIR)/test-main.Po -rm -f ./$(DEPDIR)/test-mech.Po -rm -f ./$(DEPDIR)/test-mock.Po -rm -f ./$(DEPDIR)/test-username-filter.Po -rm -f ./$(DEPDIR)/test_auth_cache-auth-cache.Po -rm -f ./$(DEPDIR)/test_auth_cache-test-auth-cache.Po -rm -f ./$(DEPDIR)/test_libpassword-test-libpassword.Po -rm -f ./$(DEPDIR)/userdb-blocking.Plo -rm -f ./$(DEPDIR)/userdb-checkpassword.Plo -rm -f ./$(DEPDIR)/userdb-dict.Plo -rm -f ./$(DEPDIR)/userdb-ldap.Plo -rm -f ./$(DEPDIR)/userdb-lua.Plo -rm -f ./$(DEPDIR)/userdb-passwd-file.Plo -rm -f ./$(DEPDIR)/userdb-passwd.Plo -rm -f ./$(DEPDIR)/userdb-prefetch.Plo -rm -f ./$(DEPDIR)/userdb-sql.Plo -rm -f ./$(DEPDIR)/userdb-static.Plo -rm -f ./$(DEPDIR)/userdb-template.Plo -rm -f ./$(DEPDIR)/userdb.Plo -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-auth_moduleLTLIBRARIES \ install-pkginc_libHEADERS install-stats_moduleLTLIBRARIES install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-pkglibexecPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/auth-cache.Plo -rm -f ./$(DEPDIR)/auth-client-connection.Plo -rm -f ./$(DEPDIR)/auth-fields.Plo -rm -f ./$(DEPDIR)/auth-main.Po -rm -f ./$(DEPDIR)/auth-master-connection.Plo -rm -f ./$(DEPDIR)/auth-penalty.Plo -rm -f ./$(DEPDIR)/auth-policy.Plo -rm -f ./$(DEPDIR)/auth-request-fields.Plo -rm -f ./$(DEPDIR)/auth-request-handler.Plo -rm -f ./$(DEPDIR)/auth-request-stats.Plo -rm -f ./$(DEPDIR)/auth-request-var-expand.Plo -rm -f ./$(DEPDIR)/auth-request.Plo -rm -f ./$(DEPDIR)/auth-settings.Plo -rm -f ./$(DEPDIR)/auth-stats.Plo -rm -f ./$(DEPDIR)/auth-token.Plo -rm -f ./$(DEPDIR)/auth-worker-client.Plo -rm -f ./$(DEPDIR)/auth-worker-server.Plo -rm -f ./$(DEPDIR)/auth.Plo -rm -f ./$(DEPDIR)/checkpassword_reply-checkpassword-reply.Po -rm -f ./$(DEPDIR)/db-checkpassword.Plo -rm -f ./$(DEPDIR)/db-dict-cache-key.Plo -rm -f ./$(DEPDIR)/db-dict.Plo -rm -f ./$(DEPDIR)/db-ldap.Plo -rm -f ./$(DEPDIR)/db-lua.Plo -rm -f ./$(DEPDIR)/db-oauth2.Plo -rm -f ./$(DEPDIR)/db-passwd-file.Plo -rm -f ./$(DEPDIR)/db-sql.Plo -rm -f ./$(DEPDIR)/libauthdb_imap_la-passdb-imap.Plo -rm -f ./$(DEPDIR)/libauthdb_ldap_la-db-ldap.Plo -rm -f ./$(DEPDIR)/libauthdb_ldap_la-passdb-ldap.Plo -rm -f ./$(DEPDIR)/libauthdb_ldap_la-userdb-ldap.Plo -rm -f ./$(DEPDIR)/libauthdb_lua_la-db-lua.Plo -rm -f ./$(DEPDIR)/libauthdb_lua_la-passdb-lua.Plo -rm -f ./$(DEPDIR)/libauthdb_lua_la-userdb-lua.Plo -rm -f ./$(DEPDIR)/libmech_gssapi_la-mech-gssapi.Plo -rm -f ./$(DEPDIR)/libpassword_la-crypt-blowfish.Plo -rm -f ./$(DEPDIR)/libpassword_la-mycrypt.Plo -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-crypt.Plo -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-md5crypt.Plo -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-otp.Plo -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-pbkdf2.Plo -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-scram.Plo -rm -f ./$(DEPDIR)/libpassword_la-password-scheme-sodium.Plo -rm -f ./$(DEPDIR)/libpassword_la-password-scheme.Plo -rm -f ./$(DEPDIR)/mech-anonymous.Plo -rm -f ./$(DEPDIR)/mech-apop.Plo -rm -f ./$(DEPDIR)/mech-cram-md5.Plo -rm -f ./$(DEPDIR)/mech-digest-md5.Plo -rm -f ./$(DEPDIR)/mech-dovecot-token.Plo -rm -f ./$(DEPDIR)/mech-external.Plo -rm -f ./$(DEPDIR)/mech-gssapi.Plo -rm -f ./$(DEPDIR)/mech-login.Plo -rm -f ./$(DEPDIR)/mech-oauth2.Plo -rm -f ./$(DEPDIR)/mech-otp-common.Plo -rm -f ./$(DEPDIR)/mech-otp.Plo -rm -f ./$(DEPDIR)/mech-plain-common.Plo -rm -f ./$(DEPDIR)/mech-plain.Plo -rm -f ./$(DEPDIR)/mech-scram.Plo -rm -f ./$(DEPDIR)/mech-winbind.Plo -rm -f ./$(DEPDIR)/mech.Plo -rm -f ./$(DEPDIR)/passdb-blocking.Plo -rm -f ./$(DEPDIR)/passdb-bsdauth.Plo -rm -f ./$(DEPDIR)/passdb-cache.Plo -rm -f ./$(DEPDIR)/passdb-checkpassword.Plo -rm -f ./$(DEPDIR)/passdb-dict.Plo -rm -f ./$(DEPDIR)/passdb-ldap.Plo -rm -f ./$(DEPDIR)/passdb-lua.Plo -rm -f ./$(DEPDIR)/passdb-oauth2.Plo -rm -f ./$(DEPDIR)/passdb-pam.Plo -rm -f ./$(DEPDIR)/passdb-passwd-file.Plo -rm -f ./$(DEPDIR)/passdb-passwd.Plo -rm -f ./$(DEPDIR)/passdb-shadow.Plo -rm -f ./$(DEPDIR)/passdb-sql.Plo -rm -f ./$(DEPDIR)/passdb-static.Plo -rm -f ./$(DEPDIR)/passdb-template.Plo -rm -f ./$(DEPDIR)/passdb.Plo -rm -f ./$(DEPDIR)/test-auth-request-fields.Po -rm -f ./$(DEPDIR)/test-auth-request-var-expand.Po -rm -f ./$(DEPDIR)/test-db-dict.Po -rm -f ./$(DEPDIR)/test-lua.Po -rm -f ./$(DEPDIR)/test-main.Po -rm -f ./$(DEPDIR)/test-mech.Po -rm -f ./$(DEPDIR)/test-mock.Po -rm -f ./$(DEPDIR)/test-username-filter.Po -rm -f ./$(DEPDIR)/test_auth_cache-auth-cache.Po -rm -f ./$(DEPDIR)/test_auth_cache-test-auth-cache.Po -rm -f ./$(DEPDIR)/test_libpassword-test-libpassword.Po -rm -f ./$(DEPDIR)/userdb-blocking.Plo -rm -f ./$(DEPDIR)/userdb-checkpassword.Plo -rm -f ./$(DEPDIR)/userdb-dict.Plo -rm -f ./$(DEPDIR)/userdb-ldap.Plo -rm -f ./$(DEPDIR)/userdb-lua.Plo -rm -f ./$(DEPDIR)/userdb-passwd-file.Plo -rm -f ./$(DEPDIR)/userdb-passwd.Plo -rm -f ./$(DEPDIR)/userdb-prefetch.Plo -rm -f ./$(DEPDIR)/userdb-sql.Plo -rm -f ./$(DEPDIR)/userdb-static.Plo -rm -f ./$(DEPDIR)/userdb-template.Plo -rm -f ./$(DEPDIR)/userdb.Plo -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-auth_moduleLTLIBRARIES \ uninstall-pkginc_libHEADERS uninstall-pkglibexecPROGRAMS \ uninstall-stats_moduleLTLIBRARIES .MAKE: check-am install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ check-local clean clean-auth_moduleLTLIBRARIES clean-generic \ clean-libtool clean-noinstLTLIBRARIES clean-noinstPROGRAMS \ clean-pkglibexecPROGRAMS clean-stats_moduleLTLIBRARIES \ cscopelist-am ctags ctags-am distclean distclean-compile \ distclean-generic distclean-libtool distclean-tags distdir dvi \ dvi-am html html-am info info-am install install-am \ install-auth_moduleLTLIBRARIES install-data install-data-am \ install-dvi install-dvi-am install-exec install-exec-am \ install-html install-html-am install-info install-info-am \ install-man install-pdf install-pdf-am \ install-pkginc_libHEADERS install-pkglibexecPROGRAMS \ install-ps install-ps-am install-stats_moduleLTLIBRARIES \ install-strip installcheck installcheck-am installdirs \ maintainer-clean maintainer-clean-generic mostlyclean \ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ uninstall-auth_moduleLTLIBRARIES uninstall-pkginc_libHEADERS \ uninstall-pkglibexecPROGRAMS uninstall-stats_moduleLTLIBRARIES .PRECIOUS: Makefile check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/auth/db-dict.c0000644000000000000000000004220614656633576013523 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "array.h" #include "istream.h" #include "str.h" #include "json-parser.h" #include "settings.h" #include "dict.h" #include "auth-request.h" #include "auth-worker-client.h" #include "db-dict.h" #include enum dict_settings_section { DICT_SETTINGS_SECTION_ROOT = 0, DICT_SETTINGS_SECTION_KEY, DICT_SETTINGS_SECTION_PASSDB, DICT_SETTINGS_SECTION_USERDB }; struct dict_settings_parser_ctx { struct dict_connection *conn; enum dict_settings_section section; struct db_dict_key *cur_key; }; struct db_dict_iter_key { const struct db_dict_key *key; bool used; const char *value; }; struct db_dict_value_iter { pool_t pool; struct auth_request *auth_request; struct dict_connection *conn; const struct var_expand_table *var_expand_table; ARRAY(struct db_dict_iter_key) keys; const ARRAY_TYPE(db_dict_field) *fields; const ARRAY_TYPE(db_dict_key_p) *objects; unsigned int field_idx; unsigned int object_idx; struct json_parser *json_parser; string_t *tmpstr; const char *error; }; #define DEF_STR(name) DEF_STRUCT_STR(name, db_dict_settings) #define DEF_BOOL(name) DEF_STRUCT_BOOL(name, db_dict_settings) static struct setting_def setting_defs[] = { DEF_STR(uri), DEF_STR(default_pass_scheme), DEF_STR(iterate_prefix), DEF_BOOL(iterate_disable), DEF_STR(passdb_objects), DEF_STR(userdb_objects), { 0, NULL, 0 } }; static struct db_dict_settings default_dict_settings = { .uri = NULL, .default_pass_scheme = "MD5", .iterate_prefix = "", .iterate_disable = FALSE, .passdb_objects = "", .userdb_objects = "" }; #undef DEF_STR #define DEF_STR(name) DEF_STRUCT_STR(name, db_dict_key) static struct setting_def key_setting_defs[] = { DEF_STR(name), DEF_STR(key), DEF_STR(format), DEF_STR(default_value), { 0, NULL, 0 } }; static struct db_dict_key default_key_settings = { .name = NULL, .key = "", .format = "value", .default_value = NULL }; static struct dict_connection *connections = NULL; static struct dict_connection *dict_conn_find(const char *config_path) { struct dict_connection *conn; for (conn = connections; conn != NULL; conn = conn->next) { if (strcmp(conn->config_path, config_path) == 0) return conn; } return NULL; } static bool parse_obsolete_setting(const char *key, const char *value, struct dict_settings_parser_ctx *ctx, const char **error_r) { const struct db_dict_key *dbkey; if (strcmp(key, "password_key") == 0) { /* key passdb { key= format=json } passdb_objects = passdb */ ctx->cur_key = array_append_space(&ctx->conn->set.keys); *ctx->cur_key = default_key_settings; ctx->cur_key->name = "passdb"; ctx->cur_key->format = "json"; ctx->cur_key->parsed_format = DB_DICT_VALUE_FORMAT_JSON; ctx->cur_key->key = p_strdup(ctx->conn->pool, value); dbkey = ctx->cur_key; array_push_back(&ctx->conn->set.parsed_passdb_objects, &dbkey); return TRUE; } if (strcmp(key, "user_key") == 0) { /* key userdb { key= format=json } userdb_objects = userdb */ ctx->cur_key = array_append_space(&ctx->conn->set.keys); *ctx->cur_key = default_key_settings; ctx->cur_key->name = "userdb"; ctx->cur_key->format = "json"; ctx->cur_key->parsed_format = DB_DICT_VALUE_FORMAT_JSON; ctx->cur_key->key = p_strdup(ctx->conn->pool, value); dbkey = ctx->cur_key; array_push_back(&ctx->conn->set.parsed_userdb_objects, &dbkey); return TRUE; } if (strcmp(key, "value_format") == 0) { if (strcmp(value, "json") == 0) return TRUE; *error_r = "Deprecated value_format must be 'json'"; return FALSE; } return FALSE; } static const char *parse_setting(const char *key, const char *value, struct dict_settings_parser_ctx *ctx) { struct db_dict_field *field; const char *error = NULL; switch (ctx->section) { case DICT_SETTINGS_SECTION_ROOT: if (parse_obsolete_setting(key, value, ctx, &error)) return NULL; if (error != NULL) return error; return parse_setting_from_defs(ctx->conn->pool, setting_defs, &ctx->conn->set, key, value); case DICT_SETTINGS_SECTION_KEY: return parse_setting_from_defs(ctx->conn->pool, key_setting_defs, ctx->cur_key, key, value); case DICT_SETTINGS_SECTION_PASSDB: field = array_append_space(&ctx->conn->set.passdb_fields); field->name = p_strdup(ctx->conn->pool, key); field->value = p_strdup(ctx->conn->pool, value); return NULL; case DICT_SETTINGS_SECTION_USERDB: field = array_append_space(&ctx->conn->set.userdb_fields); field->name = p_strdup(ctx->conn->pool, key); field->value = p_strdup(ctx->conn->pool, value); return NULL; } i_unreached(); } static bool parse_section(const char *type, const char *name, struct dict_settings_parser_ctx *ctx, const char **errormsg) { if (type == NULL) { ctx->section = DICT_SETTINGS_SECTION_ROOT; if (ctx->cur_key != NULL) { if (strcmp(ctx->cur_key->format, "value") == 0) { ctx->cur_key->parsed_format = DB_DICT_VALUE_FORMAT_VALUE; } else if (strcmp(ctx->cur_key->format, "json") == 0) { ctx->cur_key->parsed_format = DB_DICT_VALUE_FORMAT_JSON; } else { *errormsg = t_strconcat("Unknown key format: ", ctx->cur_key->format, NULL); return FALSE; } } ctx->cur_key = NULL; return TRUE; } if (ctx->section != DICT_SETTINGS_SECTION_ROOT) { *errormsg = "Nested sections not supported"; return FALSE; } if (strcmp(type, "key") == 0) { if (name == NULL) { *errormsg = "Key section is missing name"; return FALSE; } if (strchr(name, '.') != NULL) { *errormsg = "Key section names must not contain '.'"; return FALSE; } ctx->section = DICT_SETTINGS_SECTION_KEY; ctx->cur_key = array_append_space(&ctx->conn->set.keys); *ctx->cur_key = default_key_settings; ctx->cur_key->name = p_strdup(ctx->conn->pool, name); return TRUE; } if (strcmp(type, "passdb_fields") == 0) { ctx->section = DICT_SETTINGS_SECTION_PASSDB; return TRUE; } if (strcmp(type, "userdb_fields") == 0) { ctx->section = DICT_SETTINGS_SECTION_USERDB; return TRUE; } *errormsg = "Unknown section"; return FALSE; } static void db_dict_settings_parse(struct db_dict_settings *set) { const struct db_dict_key *key; const char *const *tmp; tmp = t_strsplit_spaces(set->passdb_objects, " "); for (; *tmp != NULL; tmp++) { key = db_dict_set_key_find(&set->keys, *tmp); if (key == NULL) { i_fatal("dict: passdb_objects refers to key %s, " "which doesn't exist", *tmp); } if (key->parsed_format == DB_DICT_VALUE_FORMAT_VALUE) { i_fatal("dict: passdb_objects refers to key %s, " "but it's in value-only format", *tmp); } array_push_back(&set->parsed_passdb_objects, &key); } tmp = t_strsplit_spaces(set->userdb_objects, " "); for (; *tmp != NULL; tmp++) { key = db_dict_set_key_find(&set->keys, *tmp); if (key == NULL) { i_fatal("dict: userdb_objects refers to key %s, " "which doesn't exist", *tmp); } if (key->parsed_format == DB_DICT_VALUE_FORMAT_VALUE) { i_fatal("dict: userdb_objects refers to key %s, " "but it's in value-only format", *tmp); } array_push_back(&set->parsed_userdb_objects, &key); } } struct dict_connection *db_dict_init(const char *config_path) { struct dict_settings dict_set; struct dict_settings_parser_ctx ctx; struct dict_connection *conn; const char *error; pool_t pool; conn = dict_conn_find(config_path); if (conn != NULL) { conn->refcount++; return conn; } if (*config_path == '\0') i_fatal("dict: Configuration file path not given"); pool = pool_alloconly_create("dict_connection", 1024); conn = p_new(pool, struct dict_connection, 1); conn->pool = pool; conn->refcount = 1; conn->config_path = p_strdup(pool, config_path); conn->set = default_dict_settings; p_array_init(&conn->set.keys, pool, 8); p_array_init(&conn->set.passdb_fields, pool, 8); p_array_init(&conn->set.userdb_fields, pool, 8); p_array_init(&conn->set.parsed_passdb_objects, pool, 2); p_array_init(&conn->set.parsed_userdb_objects, pool, 2); i_zero(&ctx); ctx.conn = conn; if (!settings_read(config_path, NULL, parse_setting, parse_section, &ctx, &error)) i_fatal("dict %s: %s", config_path, error); db_dict_settings_parse(&conn->set); if (conn->set.uri == NULL) i_fatal("dict %s: Empty uri setting", config_path); i_zero(&dict_set); dict_set.base_dir = global_auth_settings->base_dir; dict_set.event_parent = auth_event; if (dict_init(conn->set.uri, &dict_set, &conn->dict, &error) < 0) i_fatal("dict %s: Failed to init dict: %s", config_path, error); conn->next = connections; connections = conn; return conn; } void db_dict_unref(struct dict_connection **_conn) { struct dict_connection *conn = *_conn; *_conn = NULL; if (--conn->refcount > 0) return; dict_deinit(&conn->dict); pool_unref(&conn->pool); } static struct db_dict_iter_key * db_dict_iter_find_key(struct db_dict_value_iter *iter, const char *name) { struct db_dict_iter_key *key; array_foreach_modifiable(&iter->keys, key) { if (strcmp(key->key->name, name) == 0) return key; } return NULL; } static void db_dict_iter_find_used_keys(struct db_dict_value_iter *iter) { const struct db_dict_field *field; struct db_dict_iter_key *key; const char *p, *name; unsigned int idx, size; array_foreach(iter->fields, field) { for (p = field->value; *p != '\0'; ) { if (*p != '%') { p++; continue; } var_get_key_range(++p, &idx, &size); if (size == 0) { /* broken %variable ending too early */ break; } p += idx; if (size > 5 && memcmp(p, "dict:", 5) == 0) { name = t_strcut(t_strndup(p+5, size-5), ':'); key = db_dict_iter_find_key(iter, name); if (key != NULL) key->used = TRUE; } p += size; } } } static void db_dict_iter_find_used_objects(struct db_dict_value_iter *iter) { const struct db_dict_key *dict_key; struct db_dict_iter_key *key; array_foreach_elem(iter->objects, dict_key) { key = db_dict_iter_find_key(iter, dict_key->name); i_assert(key != NULL); /* checked at init */ i_assert(key->key->parsed_format != DB_DICT_VALUE_FORMAT_VALUE); key->used = TRUE; } } static int db_dict_iter_key_cmp(const struct db_dict_iter_key *k1, const struct db_dict_iter_key *k2) { return null_strcmp(k1->key->default_value, k2->key->default_value); } static int db_dict_iter_lookup_key_values(struct db_dict_value_iter *iter) { struct db_dict_iter_key *key; string_t *path; const char *error; int ret; /* sort the keys so that we'll first lookup the keys without default value. if their lookup fails, the user doesn't exist. */ array_sort(&iter->keys, db_dict_iter_key_cmp); path = t_str_new(128); str_append(path, DICT_PATH_SHARED); struct dict_op_settings set = { .username = iter->auth_request->fields.user, }; array_foreach_modifiable(&iter->keys, key) { if (!key->used) continue; str_truncate(path, strlen(DICT_PATH_SHARED)); str_append(path, key->key->key); ret = dict_lookup(iter->conn->dict, &set, iter->pool, str_c(path), &key->value, &error); if (ret > 0) { e_debug(authdb_event(iter->auth_request), "Lookup: %s = %s", str_c(path), key->value); } else if (ret < 0) { e_error(authdb_event(iter->auth_request), "Failed to lookup key %s: %s", str_c(path), error); return -1; } else if (key->key->default_value != NULL) { e_debug(authdb_event(iter->auth_request), "Lookup: %s not found, using default value %s", str_c(path), key->key->default_value); key->value = key->key->default_value; } else { return 0; } } return 1; } int db_dict_value_iter_init(struct dict_connection *conn, struct auth_request *auth_request, const ARRAY_TYPE(db_dict_field) *fields, const ARRAY_TYPE(db_dict_key_p) *objects, struct db_dict_value_iter **iter_r) { struct db_dict_value_iter *iter; struct db_dict_iter_key *iterkey; const struct db_dict_key *key; pool_t pool; int ret; pool = pool_alloconly_create(MEMPOOL_GROWING"auth dict lookup", 1024); iter = p_new(pool, struct db_dict_value_iter, 1); iter->pool = pool; iter->conn = conn; iter->fields = fields; iter->objects = objects; iter->tmpstr = str_new(pool, 128); iter->auth_request = auth_request; iter->var_expand_table = auth_request_get_var_expand_table(auth_request, NULL); /* figure out what keys we need to lookup, and lookup them */ p_array_init(&iter->keys, pool, array_count(&conn->set.keys)); array_foreach(&conn->set.keys, key) { iterkey = array_append_space(&iter->keys); struct db_dict_key *new_key = p_new(iter->pool, struct db_dict_key, 1); memcpy(new_key, key, sizeof(struct db_dict_key)); string_t *expanded_key = str_new(iter->pool, strlen(key->key)); const char *error; if (auth_request_var_expand_with_table(expanded_key, key->key, auth_request, iter->var_expand_table, NULL, &error) <= 0) { e_error(authdb_event(iter->auth_request), "Failed to expand key %s: %s", key->key, error); pool_unref(&pool); return -1; } new_key->key = str_c(expanded_key); iterkey->key = new_key; } T_BEGIN { db_dict_iter_find_used_keys(iter); db_dict_iter_find_used_objects(iter); ret = db_dict_iter_lookup_key_values(iter); } T_END; if (ret <= 0) { pool_unref(&pool); return ret; } *iter_r = iter; return 1; } static bool db_dict_value_iter_json_next(struct db_dict_value_iter *iter, string_t *tmpstr, const char **key_r, const char **value_r) { enum json_type type; const char *value; if (json_parse_next(iter->json_parser, &type, &value) < 0) return FALSE; if (type != JSON_TYPE_OBJECT_KEY) { iter->error = "Object expected"; return FALSE; } if (*value == '\0') { iter->error = "Empty object key"; return FALSE; } str_truncate(tmpstr, 0); str_append(tmpstr, value); if (json_parse_next(iter->json_parser, &type, &value) < 0) { iter->error = "Missing value"; return FALSE; } if (type == JSON_TYPE_OBJECT) { iter->error = "Nested objects not supported"; return FALSE; } *key_r = str_c(tmpstr); *value_r = value; return TRUE; } static void db_dict_value_iter_json_init(struct db_dict_value_iter *iter, const char *data) { struct istream *input; i_assert(iter->json_parser == NULL); input = i_stream_create_from_data(data, strlen(data)); iter->json_parser = json_parser_init(input); i_stream_unref(&input); } static bool db_dict_value_iter_object_next(struct db_dict_value_iter *iter, const char **key_r, const char **value_r) { const struct db_dict_key *dict_key; struct db_dict_iter_key *key; if (iter->json_parser != NULL) return db_dict_value_iter_json_next(iter, iter->tmpstr, key_r, value_r); if (iter->object_idx == array_count(iter->objects)) return FALSE; dict_key = array_idx_elem(iter->objects, iter->object_idx); key = db_dict_iter_find_key(iter, dict_key->name); i_assert(key != NULL); /* checked at init */ switch (key->key->parsed_format) { case DB_DICT_VALUE_FORMAT_VALUE: i_unreached(); case DB_DICT_VALUE_FORMAT_JSON: db_dict_value_iter_json_init(iter, key->value); return db_dict_value_iter_json_next(iter, iter->tmpstr, key_r, value_r); } i_unreached(); } static int db_dict_field_find(const char *data, void *context, const char **value_r, const char **error_r ATTR_UNUSED) { struct db_dict_value_iter *iter = context; struct db_dict_iter_key *key; const char *name, *value, *dotname = strchr(data, '.'); string_t *tmpstr; *value_r = NULL; if (dotname != NULL) data = t_strdup_until(data, dotname++); key = db_dict_iter_find_key(iter, data); if (key == NULL) return 1; switch (key->key->parsed_format) { case DB_DICT_VALUE_FORMAT_VALUE: *value_r = dotname != NULL ? NULL : (key->value == NULL ? "" : key->value); return 1; case DB_DICT_VALUE_FORMAT_JSON: if (dotname == NULL) return 1; db_dict_value_iter_json_init(iter, key->value); *value_r = ""; tmpstr = t_str_new(64); while (db_dict_value_iter_json_next(iter, tmpstr, &name, &value)) { if (strcmp(name, dotname) == 0) { *value_r = t_strdup(value); break; } } (void)json_parser_deinit(&iter->json_parser, &iter->error); return 1; } i_unreached(); } bool db_dict_value_iter_next(struct db_dict_value_iter *iter, const char **key_r, const char **value_r) { static struct var_expand_func_table var_funcs_table[] = { { "dict", db_dict_field_find }, { NULL, NULL } }; const struct db_dict_field *field; const char *error; if (iter->field_idx == array_count(iter->fields)) return db_dict_value_iter_object_next(iter, key_r, value_r); field = array_idx(iter->fields, iter->field_idx++); str_truncate(iter->tmpstr, 0); if (var_expand_with_funcs(iter->tmpstr, field->value, iter->var_expand_table, var_funcs_table, iter, &error) <= 0) { iter->error = p_strdup_printf(iter->pool, "Failed to expand %s=%s: %s", field->name, field->value, error); return FALSE; } *key_r = field->name; *value_r = str_c(iter->tmpstr); return TRUE; } int db_dict_value_iter_deinit(struct db_dict_value_iter **_iter, const char **error_r) { struct db_dict_value_iter *iter = *_iter; *_iter = NULL; *error_r = iter->error; if (iter->json_parser != NULL) { if (json_parser_deinit(&iter->json_parser, &iter->error) < 0 && *error_r == NULL) *error_r = iter->error; } pool_unref(&iter->pool); return *error_r != NULL ? -1 : 0; } dovecot-2.3.21.1/src/auth/db-checkpassword.c0000644000000000000000000004001414656633576015433 00000000000000/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #if defined(PASSDB_CHECKPASSWORD) || defined(USERDB_CHECKPASSWORD) #include "lib-signals.h" #include "array.h" #include "buffer.h" #include "str.h" #include "ioloop.h" #include "hash.h" #include "execv-const.h" #include "env-util.h" #include "safe-memset.h" #include "strescape.h" #include "child-wait.h" #include "db-checkpassword.h" #include #include #define CHECKPASSWORD_MAX_REQUEST_LEN 512 struct chkpw_auth_request { struct db_checkpassword *db; struct auth_request *request; char *auth_password; db_checkpassword_callback_t *callback; void (*request_callback)(); pid_t pid; int fd_out, fd_in; struct io *io_out, *io_in; string_t *input_buf; size_t output_pos, output_len; int exit_status; bool exited:1; }; struct db_checkpassword { char *checkpassword_path, *checkpassword_reply_path; HASH_TABLE(void *, struct chkpw_auth_request *) clients; struct child_wait *child_wait; }; static void env_put_extra_fields(const ARRAY_TYPE(auth_field) *extra_fields) { const struct auth_field *field; const char *key, *value; array_foreach(extra_fields, field) { key = t_str_ucase(field->key); value = field->value != NULL ? field->value : "1"; env_put(key, value); } } static void checkpassword_request_close(struct chkpw_auth_request *request) { io_remove(&request->io_in); io_remove(&request->io_out); i_close_fd(&request->fd_in); i_close_fd(&request->fd_out); } static void checkpassword_request_free(struct chkpw_auth_request **_request) { struct chkpw_auth_request *request = *_request; *_request = NULL; if (!request->exited) { hash_table_remove(request->db->clients, POINTER_CAST(request->pid)); child_wait_remove_pid(request->db->child_wait, request->pid); } checkpassword_request_close(request); if (request->auth_password != NULL) { safe_memset(request->auth_password, 0, strlen(request->auth_password)); i_free(request->auth_password); } auth_request_unref(&request->request); str_free(&request->input_buf); i_free(request); } static void checkpassword_finish(struct chkpw_auth_request **_request, enum db_checkpassword_status status) { struct chkpw_auth_request *request = *_request; const char *const *extra_fields; *_request = NULL; extra_fields = t_strsplit_tabescaped(str_c(request->input_buf)); request->callback(request->request, status, extra_fields, request->request_callback); checkpassword_request_free(&request); } static void checkpassword_internal_failure(struct chkpw_auth_request **request) { checkpassword_finish(request, DB_CHECKPASSWORD_STATUS_INTERNAL_FAILURE); } static void checkpassword_request_finish_auth(struct chkpw_auth_request *request) { switch (request->exit_status) { /* standard checkpassword exit codes: */ case 1: e_info(authdb_event(request->request), "Login failed (status=%d)", request->exit_status); checkpassword_finish(&request, DB_CHECKPASSWORD_STATUS_FAILURE); break; case 0: if (request->input_buf->used == 0) { e_error(authdb_event(request->request), "Received no input"); checkpassword_internal_failure(&request); break; } checkpassword_finish(&request, DB_CHECKPASSWORD_STATUS_OK); break; case 2: /* checkpassword is called with wrong parameters? unlikely */ e_error(authdb_event(request->request), "Child %s exited with status 2 (tried to use " "userdb-only checkpassword program for passdb?)", dec2str(request->pid)); checkpassword_internal_failure(&request); break; case 111: /* temporary problem, treat as internal error */ default: /* whatever error.. */ e_error(authdb_event(request->request), "Child %s exited with status %d", dec2str(request->pid), request->exit_status); checkpassword_internal_failure(&request); break; } } static void checkpassword_request_finish_lookup(struct chkpw_auth_request *request) { switch (request->exit_status) { case 3: /* User does not exist. */ e_info(authdb_event(request->request), "User unknown"); checkpassword_finish(&request, DB_CHECKPASSWORD_STATUS_FAILURE); break; case 2: /* This is intentionally not 0. checkpassword-reply exits with 2 on success when AUTHORIZED is set. */ if (request->input_buf->used == 0) { e_error(authdb_event(request->request), "Received no input"); checkpassword_internal_failure(&request); break; } checkpassword_finish(&request, DB_CHECKPASSWORD_STATUS_OK); break; default: /* whatever error... */ e_error(authdb_event(request->request), "Child %s exited with status %d", dec2str(request->pid), request->exit_status); checkpassword_internal_failure(&request); break; } } static void checkpassword_request_half_finish(struct chkpw_auth_request *request) { /* the process must have exited, and the input fd must have closed */ if (!request->exited || request->fd_in != -1) return; if (request->auth_password != NULL) checkpassword_request_finish_auth(request); else checkpassword_request_finish_lookup(request); } static void env_put_auth_vars(struct auth_request *request) { const struct var_expand_table *tab; unsigned int i; tab = auth_request_get_var_expand_table(request, NULL); for (i = 0; tab[i].key != '\0' || tab[i].long_key != NULL; i++) { /* avoid keeping passwords in environment .. just in case an attacker might find it from there. environment is no longer world-readable in modern OSes, but maybe the attacker could be running with the same UID. of course then the attacker could usually ptrace() the process, except that is disabled on some secured systems. so, although I find it highly unlikely anyone could actually attack Dovecot this way in a real system, be safe just in case. besides, lets try to keep at least minimally compatible with the checkpassword API. */ if (tab[i].long_key != NULL && tab[i].value != NULL && strcasecmp(tab[i].long_key, "password") != 0) { env_put(t_strdup_printf("AUTH_%s", t_str_ucase(tab[i].long_key)), tab[i].value); } } } static void checkpassword_setup_env(struct auth_request *request) { const struct auth_request_fields *fields = &request->fields; /* Besides passing the standard username and password in a pipe, also pass some other possibly interesting information via environment. Use UCSPI names for local/remote IPs. */ env_put("PROTO", "TCP"); /* UCSPI */ env_put("ORIG_UID", dec2str(getuid())); env_put("SERVICE", fields->service); if (fields->local_ip.family != 0) { env_put("TCPLOCALIP", net_ip2addr(&fields->local_ip)); /* FIXME: for backwards compatibility only, remove some day */ env_put("LOCAL_IP", net_ip2addr(&fields->local_ip)); } if (fields->remote_ip.family != 0) { env_put("TCPREMOTEIP", net_ip2addr(&fields->remote_ip)); /* FIXME: for backwards compatibility only, remove some day */ env_put("REMOTE_IP", net_ip2addr(&fields->remote_ip)); } if (fields->local_port != 0) env_put("TCPLOCALPORT", dec2str(fields->local_port)); if (fields->remote_port != 0) env_put("TCPREMOTEPORT", dec2str(fields->remote_port)); if (fields->master_user != NULL) env_put("MASTER_USER", fields->master_user); if (!auth_fields_is_empty(fields->extra_fields)) { const ARRAY_TYPE(auth_field) *extra_fields = auth_fields_export(fields->extra_fields); /* extra fields could come from master db */ env_put_extra_fields(extra_fields); } env_put_auth_vars(request); } static const char * checkpassword_get_cmd(struct auth_request *request, const char *args, const char *checkpassword_reply_path) { string_t *str; const char *error; str = t_str_new(256); if (auth_request_var_expand(str, args, request, NULL, &error) <= 0) { e_error(authdb_event(request), "Failed to expand checkpassword_path=%s: %s", args, error); } return t_strconcat(str_c(str), " ", checkpassword_reply_path, NULL); } static void checkpassword_child_input(struct chkpw_auth_request *request) { unsigned char buf[1024]; ssize_t ret; ret = read(request->fd_in, buf, sizeof(buf)); if (ret > 0) { str_append_data(request->input_buf, buf, ret); return; } if (ret < 0) { e_error(authdb_event(request->request), "read() failed: %m"); checkpassword_internal_failure(&request); } else if (memchr(str_data(request->input_buf), '\0', str_len(request->input_buf)) != NULL) { e_error(authdb_event(request->request), "NUL characters in checkpassword reply"); checkpassword_internal_failure(&request); } else if (strchr(str_c(request->input_buf), '\n') != NULL) { e_error(authdb_event(request->request), "LF characters in checkpassword reply"); checkpassword_internal_failure(&request); } else { e_debug(authdb_event(request->request), "Received input: %s", str_c(request->input_buf)); checkpassword_request_close(request); checkpassword_request_half_finish(request); } } static void checkpassword_child_output(struct chkpw_auth_request *request) { /* Send: username \0 password \0 timestamp \0. Must be 512 bytes or less. The "timestamp" parameter is actually useful only for APOP authentication. We don't support it, so keep it empty */ struct auth_request *auth_request = request->request; buffer_t *buf; const unsigned char *data; size_t size; ssize_t ret; buf = t_buffer_create(CHECKPASSWORD_MAX_REQUEST_LEN); buffer_append(buf, auth_request->fields.user, strlen(auth_request->fields.user)+1); if (request->auth_password != NULL) { buffer_append(buf, request->auth_password, strlen(request->auth_password)+1); } else { buffer_append_c(buf, '\0'); } buffer_append_c(buf, '\0'); data = buffer_get_data(buf, &size); i_assert(size == request->output_len); /* already checked this */ i_assert(size <= CHECKPASSWORD_MAX_REQUEST_LEN); ret = write(request->fd_out, data + request->output_pos, size - request->output_pos); if (ret <= 0) { if (ret < 0) { e_error(authdb_event(request->request), "write() failed: %m"); } else { e_error(authdb_event(request->request), "write() returned 0"); } checkpassword_internal_failure(&request); return; } request->output_pos += ret; if (request->output_pos < size) return; /* finished sending the data */ io_remove(&request->io_out); if (close(request->fd_out) < 0) e_error(authdb_event(request->request), "close() failed: %m"); request->fd_out = -1; } static void ATTR_NORETURN checkpassword_exec(struct db_checkpassword *db, struct auth_request *request, int fd_in, int fd_out, bool authenticate) { const char *cmd, *const *args; /* fd 3 is used to send the username+password for the script fd 4 is used to communicate with checkpassword-reply */ if (dup2(fd_out, 3) < 0 || dup2(fd_in, 4) < 0) { e_error(authdb_event(request), "dup2() failed: %m"); lib_exit(111); } if (!authenticate) { /* We want to retrieve passdb/userdb data and don't do authorization, so we need to signalize the checkpassword program that the password shall be ignored by setting AUTHORIZED. This needs a special checkpassword program which knows how to handle this. */ env_put("AUTHORIZED", "1"); if (request->wanted_credentials_scheme != NULL) { /* passdb credentials lookup */ env_put("CREDENTIALS_LOOKUP", "1"); env_put("SCHEME", request->wanted_credentials_scheme); } } checkpassword_setup_env(request); cmd = checkpassword_get_cmd(request, db->checkpassword_path, db->checkpassword_reply_path); e_debug(authdb_event(request), "execute: %s", cmd); /* very simple argument splitting. */ args = t_strsplit(cmd, " "); execv_const(args[0], args); } static void sigchld_handler(const struct child_wait_status *status, struct db_checkpassword *db) { struct chkpw_auth_request *request = hash_table_lookup(db->clients, POINTER_CAST(status->pid)); i_assert(request != NULL); hash_table_remove(db->clients, POINTER_CAST(status->pid)); request->exited = TRUE; if (WIFSIGNALED(status->status)) { e_error(authdb_event(request->request), "Child %s died with signal %d", dec2str(status->pid), WTERMSIG(status->status)); checkpassword_internal_failure(&request); } else if (WIFEXITED(status->status)) { request->exit_status = WEXITSTATUS(status->status); e_debug(authdb_event(request->request), "exit_status=%d", request->exit_status); checkpassword_request_half_finish(request); } else { /* shouldn't happen */ e_debug(authdb_event(request->request), "Child %s exited with status=%d", dec2str(status->pid), status->status); checkpassword_internal_failure(&request); } } void db_checkpassword_call(struct db_checkpassword *db, struct auth_request *request, const char *auth_password, db_checkpassword_callback_t *callback, void (*request_callback)()) { struct chkpw_auth_request *chkpw_auth_request; size_t output_len; int fd_in[2], fd_out[2]; pid_t pid; /* \0 \0 timestamp \0 */ output_len = strlen(request->fields.user) + 3; if (auth_password != NULL) output_len += strlen(auth_password); if (output_len > CHECKPASSWORD_MAX_REQUEST_LEN) { e_info(authdb_event(request), "Username+password combination too long (%zu bytes)", output_len); callback(request, DB_CHECKPASSWORD_STATUS_FAILURE, NULL, request_callback); return; } fd_in[0] = -1; if (pipe(fd_in) < 0 || pipe(fd_out) < 0) { e_error(authdb_event(request), "pipe() failed: %m"); if (fd_in[0] != -1) { i_close_fd(&fd_in[0]); i_close_fd(&fd_in[1]); } callback(request, DB_CHECKPASSWORD_STATUS_INTERNAL_FAILURE, NULL, request_callback); return; } pid = fork(); if (pid == -1) { e_error(authdb_event(request), "fork() failed: %m"); i_close_fd(&fd_in[0]); i_close_fd(&fd_in[1]); i_close_fd(&fd_out[0]); i_close_fd(&fd_out[1]); callback(request, DB_CHECKPASSWORD_STATUS_INTERNAL_FAILURE, NULL, request_callback); return; } if (pid == 0) { /* child */ i_close_fd(&fd_in[0]); i_close_fd(&fd_out[1]); checkpassword_exec(db, request, fd_in[1], fd_out[0], auth_password != NULL); /* not reached */ } if (close(fd_in[1]) < 0) { e_error(authdb_event(request), "close(fd_in[1]) failed: %m"); } if (close(fd_out[0]) < 0) { e_error(authdb_event(request), "close(fd_out[0]) failed: %m"); } auth_request_ref(request); chkpw_auth_request = i_new(struct chkpw_auth_request, 1); chkpw_auth_request->db = db; chkpw_auth_request->pid = pid; chkpw_auth_request->fd_in = fd_in[0]; chkpw_auth_request->fd_out = fd_out[1]; chkpw_auth_request->auth_password = i_strdup(auth_password); chkpw_auth_request->request = request; chkpw_auth_request->output_len = output_len; chkpw_auth_request->input_buf = str_new(default_pool, 256); chkpw_auth_request->callback = callback; chkpw_auth_request->request_callback = request_callback; chkpw_auth_request->io_in = io_add(fd_in[0], IO_READ, checkpassword_child_input, chkpw_auth_request); chkpw_auth_request->io_out = io_add(fd_out[1], IO_WRITE, checkpassword_child_output, chkpw_auth_request); hash_table_insert(db->clients, POINTER_CAST(pid), chkpw_auth_request); child_wait_add_pid(db->child_wait, pid); } struct db_checkpassword * db_checkpassword_init(const char *checkpassword_path, const char *checkpassword_reply_path) { struct db_checkpassword *db; db = i_new(struct db_checkpassword, 1); db->checkpassword_path = i_strdup(checkpassword_path); db->checkpassword_reply_path = i_strdup(checkpassword_reply_path); hash_table_create_direct(&db->clients, default_pool, 0); db->child_wait = child_wait_new_with_pid((pid_t)-1, sigchld_handler, db); return db; } void db_checkpassword_deinit(struct db_checkpassword **_db) { struct db_checkpassword *db = *_db; struct hash_iterate_context *iter; void *key; struct chkpw_auth_request *request; *_db = NULL; iter = hash_table_iterate_init(db->clients); while (hash_table_iterate(iter, db->clients, &key, &request)) checkpassword_internal_failure(&request); hash_table_iterate_deinit(&iter); child_wait_free(&db->child_wait); hash_table_destroy(&db->clients); i_free(db->checkpassword_reply_path); i_free(db->checkpassword_path); i_free(db); } #endif dovecot-2.3.21.1/src/auth/auth-token.h0000644000000000000000000000035514656633576014300 00000000000000#ifndef AUTH_TOKEN_H #define AUTH_TOKEN_H void auth_token_init(void); void auth_token_deinit(void); const char *auth_token_get(const char *service, const char *session_pid, const char *username, const char *session_id); #endif dovecot-2.3.21.1/src/auth/passdb.c0000644000000000000000000002450014656633576013466 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "array.h" #include "password-scheme.h" #include "auth-worker-server.h" #include "passdb.h" static ARRAY(struct passdb_module_interface *) passdb_interfaces; static ARRAY(struct passdb_module *) passdb_modules; static const struct passdb_module_interface passdb_iface_deinit = { .name = "deinit" }; static struct passdb_module_interface *passdb_interface_find(const char *name) { struct passdb_module_interface *iface; array_foreach_elem(&passdb_interfaces, iface) { if (strcmp(iface->name, name) == 0) return iface; } return NULL; } void passdb_register_module(struct passdb_module_interface *iface) { struct passdb_module_interface *old_iface; old_iface = passdb_interface_find(iface->name); if (old_iface != NULL && old_iface->verify_plain == NULL) { /* replacing a "support not compiled in" passdb */ passdb_unregister_module(old_iface); } else if (old_iface != NULL) { i_panic("passdb_register_module(%s): Already registered", iface->name); } array_push_back(&passdb_interfaces, &iface); } void passdb_unregister_module(struct passdb_module_interface *iface) { struct passdb_module_interface *const *ifaces; unsigned int idx; array_foreach(&passdb_interfaces, ifaces) { if (*ifaces == iface) { idx = array_foreach_idx(&passdb_interfaces, ifaces); array_delete(&passdb_interfaces, idx, 1); return; } } i_panic("passdb_unregister_module(%s): Not registered", iface->name); } bool passdb_get_credentials(struct auth_request *auth_request, const char *input, const char *input_scheme, const unsigned char **credentials_r, size_t *size_r) { const char *wanted_scheme = auth_request->wanted_credentials_scheme; const char *plaintext, *error; int ret; struct password_generate_params pwd_gen_params; if (auth_request->prefer_plain_credentials && password_scheme_is_alias(input_scheme, "PLAIN")) { /* we've a plaintext scheme and we prefer to get it instead of converting it to the fallback scheme */ wanted_scheme = ""; } ret = password_decode(input, input_scheme, credentials_r, size_r, &error); if (ret <= 0) { if (ret < 0) { e_error(authdb_event(auth_request), "Password data is not valid for scheme %s: %s", input_scheme, error); } else { e_error(authdb_event(auth_request), "Unknown scheme %s", input_scheme); } return FALSE; } if (*wanted_scheme == '\0') { /* anything goes. change the wanted_credentials_scheme to what we actually got, so blocking passdbs work. */ auth_request->wanted_credentials_scheme = p_strdup(auth_request->pool, t_strcut(input_scheme, '.')); return TRUE; } if (!password_scheme_is_alias(input_scheme, wanted_scheme)) { if (!password_scheme_is_alias(input_scheme, "PLAIN")) { const char *error = t_strdup_printf( "Requested %s scheme, but we have only %s", wanted_scheme, input_scheme); if (auth_request->set->debug_passwords) { error = t_strdup_printf("%s (input: %s)", error, input); } e_info(authdb_event(auth_request), "%s", error); return FALSE; } /* we can generate anything out of plaintext passwords */ plaintext = t_strndup(*credentials_r, *size_r); i_zero(&pwd_gen_params); pwd_gen_params.user = auth_request->fields.original_username; if (!auth_request->domain_is_realm && strchr(pwd_gen_params.user, '@') != NULL) { /* domain must not be used as realm. add the @realm. */ pwd_gen_params.user = t_strconcat(pwd_gen_params.user, "@", auth_request->fields.realm, NULL); } if (auth_request->set->debug_passwords) { e_debug(authdb_event(auth_request), "Generating %s from user '%s', password '%s'", wanted_scheme, pwd_gen_params.user, plaintext); } if (!password_generate(plaintext, &pwd_gen_params, wanted_scheme, credentials_r, size_r)) { e_error(authdb_event(auth_request), "Requested unknown scheme %s", wanted_scheme); return FALSE; } } return TRUE; } void passdb_handle_credentials(enum passdb_result result, const char *password, const char *scheme, lookup_credentials_callback_t *callback, struct auth_request *auth_request) { const unsigned char *credentials = NULL; size_t size = 0; if (result != PASSDB_RESULT_OK) { callback(result, NULL, 0, auth_request); return; } else if (auth_fields_exists(auth_request->fields.extra_fields, "noauthenticate")) { callback(PASSDB_RESULT_NEXT, NULL, 0, auth_request); return; } if (password != NULL) { if (!passdb_get_credentials(auth_request, password, scheme, &credentials, &size)) result = PASSDB_RESULT_SCHEME_NOT_AVAILABLE; } else if (*auth_request->wanted_credentials_scheme == '\0') { /* We're doing a passdb lookup (not authenticating). Pass through a NULL password without an error. */ } else if (auth_request->fields.delayed_credentials != NULL) { /* We already have valid credentials from an earlier passdb lookup. auth_request_lookup_credentials_finish() will use them. */ } else { e_info(authdb_event(auth_request), "Requested %s scheme, but we have a NULL password", auth_request->wanted_credentials_scheme); result = PASSDB_RESULT_SCHEME_NOT_AVAILABLE; } callback(result, credentials, size, auth_request); } static struct passdb_module * passdb_find(const char *driver, const char *args, unsigned int *idx_r) { struct passdb_module *const *passdbs; unsigned int i, count; passdbs = array_get(&passdb_modules, &count); for (i = 0; i < count; i++) { if (strcmp(passdbs[i]->iface.name, driver) == 0 && strcmp(passdbs[i]->args, args) == 0) { *idx_r = i; return passdbs[i]; } } return NULL; } struct passdb_module * passdb_preinit(pool_t pool, const struct auth_passdb_settings *set) { static unsigned int auth_passdb_id = 0; struct passdb_module_interface *iface; struct passdb_module *passdb; unsigned int idx; iface = passdb_interface_find(set->driver); if (iface == NULL || iface->verify_plain == NULL) { /* maybe it's a plugin. try to load it. */ auth_module_load(t_strconcat("authdb_", set->driver, NULL)); iface = passdb_interface_find(set->driver); } if (iface == NULL) i_fatal("Unknown passdb driver '%s'", set->driver); if (iface->verify_plain == NULL) { i_fatal("Support not compiled in for passdb driver '%s'", set->driver); } if (iface->preinit == NULL && iface->init == NULL && *set->args != '\0') { i_fatal("passdb %s: No args are supported: %s", set->driver, set->args); } passdb = passdb_find(set->driver, set->args, &idx); if (passdb != NULL) return passdb; if (iface->preinit == NULL) passdb = p_new(pool, struct passdb_module, 1); else passdb = iface->preinit(pool, set->args); passdb->id = ++auth_passdb_id; passdb->iface = *iface; passdb->args = p_strdup(pool, set->args); if (*set->mechanisms == '\0') { passdb->mechanisms = NULL; } else if (strcasecmp(set->mechanisms, "none") == 0) { passdb->mechanisms = (const char *const[]){NULL}; } else { passdb->mechanisms = (const char* const*)p_strsplit_spaces(pool, set->mechanisms, " ,"); } if (*set->username_filter == '\0') { passdb->username_filter = NULL; } else { passdb->username_filter = (const char* const*)p_strsplit_spaces(pool, set->username_filter, " ,"); } array_push_back(&passdb_modules, &passdb); return passdb; } void passdb_init(struct passdb_module *passdb) { if (passdb->iface.init != NULL && passdb->init_refcount == 0) passdb->iface.init(passdb); passdb->init_refcount++; } void passdb_deinit(struct passdb_module *passdb) { unsigned int idx; i_assert(passdb->init_refcount > 0); if (--passdb->init_refcount > 0) return; if (passdb_find(passdb->iface.name, passdb->args, &idx) == NULL) i_unreached(); array_delete(&passdb_modules, idx, 1); if (passdb->iface.deinit != NULL) passdb->iface.deinit(passdb); /* make sure passdb isn't accessed again */ passdb->iface = passdb_iface_deinit; } void passdbs_generate_md5(unsigned char md5[STATIC_ARRAY MD5_RESULTLEN]) { struct md5_context ctx; struct passdb_module *const *passdbs; unsigned int i, count; md5_init(&ctx); passdbs = array_get(&passdb_modules, &count); for (i = 0; i < count; i++) { md5_update(&ctx, &passdbs[i]->id, sizeof(passdbs[i]->id)); md5_update(&ctx, passdbs[i]->iface.name, strlen(passdbs[i]->iface.name)); md5_update(&ctx, passdbs[i]->args, strlen(passdbs[i]->args)); } md5_final(&ctx, md5); } const char * passdb_result_to_string(enum passdb_result result) { switch (result) { case PASSDB_RESULT_INTERNAL_FAILURE: return "internal_failure"; case PASSDB_RESULT_SCHEME_NOT_AVAILABLE: return "scheme_not_available"; case PASSDB_RESULT_USER_UNKNOWN: return "user_unknown"; case PASSDB_RESULT_USER_DISABLED: return "user_disabled"; case PASSDB_RESULT_PASS_EXPIRED: return "pass_expired"; case PASSDB_RESULT_NEXT: return "next"; case PASSDB_RESULT_PASSWORD_MISMATCH: return "password_mismatch"; case PASSDB_RESULT_OK: return "ok"; } i_unreached(); } extern struct passdb_module_interface passdb_passwd; extern struct passdb_module_interface passdb_bsdauth; extern struct passdb_module_interface passdb_dict; #ifdef HAVE_LUA extern struct passdb_module_interface passdb_lua; #endif extern struct passdb_module_interface passdb_shadow; extern struct passdb_module_interface passdb_passwd_file; extern struct passdb_module_interface passdb_pam; extern struct passdb_module_interface passdb_checkpassword; extern struct passdb_module_interface passdb_ldap; extern struct passdb_module_interface passdb_sql; extern struct passdb_module_interface passdb_static; extern struct passdb_module_interface passdb_oauth2; void passdbs_init(void) { i_array_init(&passdb_interfaces, 16); i_array_init(&passdb_modules, 16); passdb_register_module(&passdb_passwd); passdb_register_module(&passdb_bsdauth); passdb_register_module(&passdb_dict); #ifdef HAVE_LUA passdb_register_module(&passdb_lua); #endif passdb_register_module(&passdb_passwd_file); passdb_register_module(&passdb_pam); passdb_register_module(&passdb_checkpassword); passdb_register_module(&passdb_shadow); passdb_register_module(&passdb_ldap); passdb_register_module(&passdb_sql); passdb_register_module(&passdb_static); passdb_register_module(&passdb_oauth2); } void passdbs_deinit(void) { array_free(&passdb_modules); array_free(&passdb_interfaces); } dovecot-2.3.21.1/src/auth/userdb-blocking.h0000644000000000000000000000062214656633576015270 00000000000000#ifndef USERDB_BLOCKING_H #define USERDB_BLOCKING_H void userdb_blocking_lookup(struct auth_request *request); struct userdb_iterate_context * userdb_blocking_iter_init(struct auth_request *request, userdb_iter_callback_t *callback, void *context); void userdb_blocking_iter_next(struct userdb_iterate_context *ctx); int userdb_blocking_iter_deinit(struct userdb_iterate_context **ctx); #endif dovecot-2.3.21.1/src/auth/test-auth-cache.c0000644000000000000000000000417714656633576015201 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "auth-request.h" #include "auth-cache.h" #include "test-common.h" const struct var_expand_table auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT + 1] = { /* these 3 must be in this order */ { 'u', NULL, "user" }, { 'n', NULL, "username" }, { 'd', NULL, "domain" }, { 'a', NULL, NULL }, { '\0', NULL, "longb" }, { 'c', NULL, "longc" }, { '\0', NULL, NULL } }; struct var_expand_table * auth_request_get_var_expand_table_full(const struct auth_request *auth_request ATTR_UNUSED, const char *username ATTR_UNUSED, auth_request_escape_func_t *escape_func ATTR_UNUSED, unsigned int *count ATTR_UNUSED) { i_unreached(); } int auth_request_var_expand_with_table(string_t *dest, const char *str, const struct auth_request *auth_request ATTR_UNUSED, const struct var_expand_table *table ATTR_UNUSED, auth_request_escape_func_t *escape_func ATTR_UNUSED, const char **error_r ATTR_UNUSED) { return var_expand(dest, str, auth_request_var_expand_static_tab, error_r); } static void test_auth_cache_parse_key(void) { static const struct { const char *in, *out; } tests[] = { { "%n@%d", "%u" }, { "%{username}@%{domain}", "%u" }, { "%n%d%u", "%u" }, { "%n", "%n" }, { "%d", "%d" }, { "%a%b%u", "%u\t%a\t%b" }, { "foo%5.5Mabar", "%a" }, { "foo%5.5M{longb}bar", "%{longb}" }, { "foo%5.5Mcbar", "%c" }, { "foo%5.5M{longc}bar", "%c" }, { "%a%b", "%a\t%b" }, { "%a%{longb}%a", "%a\t%{longb}" }, { "%{longc}%c", "%c" }, { "%c%a%{longc}%c", "%a\t%c" }, { "%a%{env:foo}%{env:foo}%a", "%a\t%{env:foo}\t%{env:foo}" } }; const char *cache_key; unsigned int i; test_begin("auth cache parse key"); for (i = 0; i < N_ELEMENTS(tests); i++) { cache_key = auth_cache_parse_key(pool_datastack_create(), tests[i].in); test_assert(strcmp(cache_key, tests[i].out) == 0); } test_end(); } int main(void) { static void (*const test_functions[])(void) = { test_auth_cache_parse_key, NULL }; return test_run(test_functions); } dovecot-2.3.21.1/src/auth/auth-policy.h0000644000000000000000000000052414656633576014455 00000000000000#ifndef AUTH_POLICY_H #define AUTH_POLICY_H typedef void (*auth_policy_callback_t)(int, void *); void auth_policy_check(struct auth_request *request, const char *password, auth_policy_callback_t cb, void *context); void auth_policy_report(struct auth_request *request); void auth_policy_init(void); void auth_policy_deinit(void); #endif dovecot-2.3.21.1/src/auth/passdb-oauth2.c0000644000000000000000000000443714656633576014675 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "passdb.h" #include "db-oauth2.h" struct oauth2_passdb_module { struct passdb_module module; struct db_oauth2 *db; }; static void oauth2_verify_plain_continue(struct db_oauth2_request *req, enum passdb_result result, const char *error, struct auth_request *request) { if (result == PASSDB_RESULT_INTERNAL_FAILURE) e_error(authdb_event(request), "oauth2 failed: %s", error); else if (result != PASSDB_RESULT_OK) e_info(authdb_event(request), "oauth2 failed: %s", error); else { auth_request_set_field(request, "token", req->token, "PLAIN"); } req->verify_callback(result, request); auth_request_unref(&request); } static void oauth2_verify_plain(struct auth_request *request, const char *password, verify_plain_callback_t *callback) { struct oauth2_passdb_module *module = (struct oauth2_passdb_module *)request->passdb->passdb; struct db_oauth2_request *req = p_new(request->pool, struct db_oauth2_request, 1); req->pool = request->pool; req->verify_callback = callback; auth_request_ref(request); db_oauth2_lookup(module->db, req, password, request, oauth2_verify_plain_continue, request); } static struct passdb_module * oauth2_preinit(pool_t pool, const char *args) { struct oauth2_passdb_module *module; module = p_new(pool, struct oauth2_passdb_module, 1); module->db = db_oauth2_init(args); module->module.default_pass_scheme = "PLAIN"; if (db_oauth2_uses_password_grant(module->db)) { module->module.default_cache_key = "%u"; } else { module->module.default_cache_key = "%u%w"; } return &module->module; } static void oauth2_deinit(struct passdb_module *passdb) { struct oauth2_passdb_module *module = (struct oauth2_passdb_module *)passdb; db_oauth2_unref(&module->db); } /* FIXME: Remove when oauth2 mech is fixed */ const char *passdb_oauth2_get_oidc_url(struct passdb_module *passdb) { struct oauth2_passdb_module *module = container_of(passdb, struct oauth2_passdb_module, module); if (module->db != NULL) return db_oauth2_get_openid_configuration_url(module->db); return NULL; } struct passdb_module_interface passdb_oauth2 = { "oauth2", oauth2_preinit, NULL, oauth2_deinit, oauth2_verify_plain, NULL, NULL }; dovecot-2.3.21.1/src/auth/test-auth-request-fields.c0000644000000000000000000001150514656633576017063 00000000000000/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ #include "test-auth.h" #include "str.h" #include "strescape.h" #include "auth-request.h" struct test_auth_request_field { const char *internal_name; const char *event_field; const char *value; }; static const struct test_auth_request_field auth_request_field_names[] = { /* use the order in auth_request_export() */ #define PREFIX "\t\r\n\001prefix-" { "user", "user", PREFIX"testuser" }, { "service", "service", PREFIX"testservice" }, { "master-user", "master_user", PREFIX"testmasteruser" }, { "original-username", "original_user", PREFIX"testoriguser" }, { "requested-login-user", "login_user", PREFIX"testloginuser" }, { "lip", "local_ip", "255.254.253.252" }, { "rip", "remote_ip", "155.154.153.152" }, { "lport", "local_port", "12" }, { "rport", "remote_port", "13" }, { "real_lip", "real_local_ip", "1.2.3.4" }, { "real_rip", "real_remote_ip", "5.6.7.8" }, { "real_lport", "real_local_port", "14" }, { "real_rport", "real_remote_port", "15" }, { "local_name", "local_name", PREFIX"testlocalname" }, { "session", "session", PREFIX"testsession" }, { "secured", NULL, "" }, { "skip-password-check", NULL, "" }, { "delayed-credentials", NULL, "" }, { "valid-client-cert", NULL, "" }, { "no-penalty", NULL, "" }, { "successful", NULL, "" }, { "mech", "mechanism", "TOKEN" }, { "client_id", "client_id", PREFIX"testclientid" }, { "passdb_extrafield1", NULL, PREFIX"extravalue1" }, { "passdb_extrafield2", NULL, PREFIX"extravalue2" }, { "userdb_uextrafield1", NULL, PREFIX"userextravalue1" }, { "userdb_uextrafield2", NULL, PREFIX"userextravalue2" }, }; static struct auth_request * test_auth_request_init(const struct mech_module *mech) { struct auth_request *request; pool_t pool = pool_alloconly_create("test auth request", 1024); request = p_new(pool, struct auth_request, 1); request->pool = pool; request->event = event_create(NULL); request->mech = mech; auth_request_fields_init(request); /* fill out fields that are always exported */ request->fields.user = "user"; request->fields.original_username = "user"; request->fields.service = "service"; return request; } static void test_auth_request_deinit(struct auth_request *request) { event_unref(&request->event); pool_unref(&request->pool); } static void test_auth_request_fields_list(void) { struct auth_request *request = test_auth_request_init(&mech_dovecot_token); string_t *exported = t_str_new(512); for (unsigned int i = 0; i < N_ELEMENTS(auth_request_field_names); i++) { const struct test_auth_request_field *test = &auth_request_field_names[i]; test_assert_idx(auth_request_import(request, test->internal_name, test->value), i); str_append(exported, test->internal_name); if (test->value[0] != '\0') { str_append_c(exported, '='); str_append_tabescaped(exported, test->value); } str_append_c(exported, '\t'); if (test->event_field != NULL) { const char *value = event_find_field_recursive_str(request->event, test->event_field); test_assert_idx(null_strcmp(value, test->value) == 0, i); } } str_truncate(exported, str_len(exported)-1); string_t *exported2 = t_str_new(512); auth_request_export(request, exported2); test_assert_strcmp(str_c(exported), str_c(exported2)); test_auth_request_deinit(request); } static bool test_auth_request_export_cmp(struct auth_request *request, const char *key, const char *value) { string_t *exported = t_str_new(128); str_append(exported, "user=user\tservice=service\toriginal-username=user\t"); str_append(exported, key); if (value[0] != '\0') { str_append_c(exported, '='); str_append_tabescaped(exported, value); } string_t *exported2 = t_str_new(128); auth_request_export(request, exported2); test_assert_strcmp(str_c(exported), str_c(exported2)); return strcmp(str_c(exported), str_c(exported2)) == 0; } static void test_auth_request_fields_secured(void) { struct auth_request *request = test_auth_request_init(NULL); test_assert(auth_request_import(request, "secured", "")); test_assert(test_auth_request_export_cmp(request, "secured", "")); test_assert(null_strcmp(event_find_field_recursive_str(request->event, "transport"), "trusted") == 0); test_assert(auth_request_import(request, "secured", "tls")); test_assert(test_auth_request_export_cmp(request, "secured", "tls")); test_assert(null_strcmp(event_find_field_recursive_str(request->event, "transport"), "TLS") == 0); test_assert(auth_request_import(request, "secured", "blah")); test_assert(test_auth_request_export_cmp(request, "secured", "")); test_assert(null_strcmp(event_find_field_recursive_str(request->event, "transport"), "trusted") == 0); test_auth_request_deinit(request); } void test_auth_request_fields(void) { test_begin("auth request fields"); test_auth_request_fields_list(); test_auth_request_fields_secured(); test_end(); } dovecot-2.3.21.1/src/auth/auth-fields.c0000644000000000000000000001275614656633576014431 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "array.h" #include "str.h" #include "strescape.h" #include "ostream.h" #include "auth-request.h" #include "auth-fields.h" struct auth_fields { pool_t pool; ARRAY_TYPE(auth_field) fields, snapshot_fields; unsigned int snapshot_idx; bool snapshotted; }; struct auth_fields *auth_fields_init(pool_t pool) { struct auth_fields *fields; fields = p_new(pool, struct auth_fields, 1); fields->pool = pool; return fields; } static void auth_fields_snapshot_preserve(struct auth_fields *fields) { if (!fields->snapshotted || array_is_created(&fields->snapshot_fields)) return; p_array_init(&fields->snapshot_fields, fields->pool, array_count(&fields->fields)); array_append_array(&fields->snapshot_fields, &fields->fields); } static bool auth_fields_find_idx(struct auth_fields *fields, const char *key, unsigned int *idx_r) { const struct auth_field *f; unsigned int i, count; if (!array_is_created(&fields->fields)) return FALSE; f = array_get(&fields->fields, &count); for (i = 0; i < count; i++) { if (strcmp(f[i].key, key) == 0) { *idx_r = i; return TRUE; } } return FALSE; } void auth_fields_add(struct auth_fields *fields, const char *key, const char *value, enum auth_field_flags flags) { struct auth_field *field; unsigned int idx; i_assert(*key != '\0'); i_assert(strchr(key, '\t') == NULL && strchr(key, '\n') == NULL); if (!auth_fields_find_idx(fields, key, &idx)) { if (!array_is_created(&fields->fields)) p_array_init(&fields->fields, fields->pool, 16); field = array_append_space(&fields->fields); field->key = p_strdup(fields->pool, key); } else { auth_fields_snapshot_preserve(fields); field = array_idx_modifiable(&fields->fields, idx); } field->value = p_strdup_empty(fields->pool, value); field->flags = flags | AUTH_FIELD_FLAG_CHANGED; } void auth_fields_remove(struct auth_fields *fields, const char *key) { unsigned int idx; if (auth_fields_find_idx(fields, key, &idx)) { auth_fields_snapshot_preserve(fields); array_delete(&fields->fields, idx, 1); } } const char *auth_fields_find(struct auth_fields *fields, const char *key) { const struct auth_field *field; unsigned int idx; if (!auth_fields_find_idx(fields, key, &idx)) return NULL; field = array_idx(&fields->fields, idx); return field->value == NULL ? "" : field->value; } bool auth_fields_exists(struct auth_fields *fields, const char *key) { return auth_fields_find(fields, key) != NULL; } void auth_fields_reset(struct auth_fields *fields) { if (array_is_created(&fields->fields)) { auth_fields_snapshot_preserve(fields); array_clear(&fields->fields); } } void auth_fields_import_prefixed(struct auth_fields *fields, const char *prefix, const char *str, enum auth_field_flags flags) { T_BEGIN { const char *const *arg = t_strsplit_tabescaped(str); const char *key, *value; for (; *arg != NULL; arg++) { value = strchr(*arg, '='); if (value == NULL) { key = *arg; value = NULL; } else { key = t_strdup_until(*arg, value++); if (*prefix != '\0') key = t_strconcat(prefix, key, NULL); } auth_fields_add(fields, key, value, flags); } } T_END; } void auth_fields_import(struct auth_fields *fields, const char *str, enum auth_field_flags flags) { auth_fields_import_prefixed(fields, "", str, flags); } const ARRAY_TYPE(auth_field) *auth_fields_export(struct auth_fields *fields) { if (!array_is_created(&fields->fields)) p_array_init(&fields->fields, fields->pool, 1); return &fields->fields; } void auth_fields_append(struct auth_fields *fields, string_t *dest, enum auth_field_flags flags_mask, enum auth_field_flags flags_result) { const struct auth_field *f; unsigned int i, count; bool first = TRUE; if (!array_is_created(&fields->fields)) return; f = array_get(&fields->fields, &count); for (i = 0; i < count; i++) { if ((f[i].flags & flags_mask) != flags_result) continue; if (first) first = FALSE; else str_append_c(dest, '\t'); str_append(dest, f[i].key); if (f[i].value != NULL) { str_append_c(dest, '='); str_append_tabescaped(dest, f[i].value); } } } bool auth_fields_is_empty(struct auth_fields *fields) { return fields == NULL || !array_is_created(&fields->fields) || array_count(&fields->fields) == 0; } void auth_fields_booleanize(struct auth_fields *fields, const char *key) { struct auth_field *field; unsigned int idx; if (auth_fields_find_idx(fields, key, &idx)) { field = array_idx_modifiable(&fields->fields, idx); field->value = NULL; } } void auth_fields_snapshot(struct auth_fields *fields) { struct auth_field *field; fields->snapshotted = TRUE; if (!array_is_created(&fields->fields)) return; if (!array_is_created(&fields->snapshot_fields)) { /* try to avoid creating this array */ fields->snapshot_idx = array_count(&fields->fields); } else { array_clear(&fields->snapshot_fields); array_append_array(&fields->snapshot_fields, &fields->fields); } array_foreach_modifiable(&fields->fields, field) field->flags &= ENUM_NEGATE(AUTH_FIELD_FLAG_CHANGED); } void auth_fields_rollback(struct auth_fields *fields) { if (array_is_created(&fields->snapshot_fields)) { array_clear(&fields->fields); array_append_array(&fields->fields, &fields->snapshot_fields); } else if (array_is_created(&fields->fields)) { array_delete(&fields->fields, fields->snapshot_idx, array_count(&fields->fields) - fields->snapshot_idx); } } dovecot-2.3.21.1/src/auth/auth-fields.h0000644000000000000000000000352714656633576014432 00000000000000#ifndef AUTH_FIELDS_H #define AUTH_FIELDS_H struct auth_request; enum auth_field_flags { /* This field is internal to auth process and won't be sent to client */ AUTH_FIELD_FLAG_HIDDEN = 0x01, /* Changed since last snapshot. Set/cleared automatically. */ AUTH_FIELD_FLAG_CHANGED = 0x02 }; struct auth_field { const char *key, *value; enum auth_field_flags flags; }; ARRAY_DEFINE_TYPE(auth_field, struct auth_field); struct auth_fields *auth_fields_init(pool_t pool); void auth_fields_add(struct auth_fields *fields, const char *key, const char *value, enum auth_field_flags flags) ATTR_NULL(3); void auth_fields_reset(struct auth_fields *fields); void auth_fields_remove(struct auth_fields *fields, const char *key); const char *auth_fields_find(struct auth_fields *fields, const char *key); bool auth_fields_exists(struct auth_fields *fields, const char *key); void auth_fields_import(struct auth_fields *fields, const char *str, enum auth_field_flags flags); void auth_fields_import_prefixed(struct auth_fields *fields, const char *prefix, const char *str, enum auth_field_flags flags); const ARRAY_TYPE(auth_field) *auth_fields_export(struct auth_fields *fields); /* Append fields where (flag & flags_mask) == flags_result. */ void auth_fields_append(struct auth_fields *fields, string_t *dest, enum auth_field_flags flags_mask, enum auth_field_flags flags_result); bool auth_fields_is_empty(struct auth_fields *fields); /* If the field exists, clear its value (so the exported string will be "key" instead of e.g. "key=y"). */ void auth_fields_booleanize(struct auth_fields *fields, const char *key); /* Remember the current fields. */ void auth_fields_snapshot(struct auth_fields *fields); /* Rollback to previous snapshot, or clear the fields if there isn't any. */ void auth_fields_rollback(struct auth_fields *fields); #endif dovecot-2.3.21.1/src/auth/db-passwd-file.h0000644000000000000000000000216314656633576015021 00000000000000#ifndef DB_PASSWD_FILE_H #define DB_PASSWD_FILE_H #include "hash.h" #define PASSWD_FILE_DEFAULT_USERNAME_FORMAT "%u" #define PASSWD_FILE_DEFAULT_SCHEME "CRYPT" struct passwd_user { uid_t uid; gid_t gid; char *home; char *password; char **extra_fields; }; struct passwd_file { struct db_passwd_file *db; pool_t pool; int refcount; struct event *event; time_t last_sync_time; char *path; time_t stamp; off_t size; int fd; HASH_TABLE(char *, struct passwd_user *) users; }; struct db_passwd_file { struct db_passwd_file *next; int refcount; struct event *event; char *path; HASH_TABLE(char *, struct passwd_file *) files; struct passwd_file *default_file; bool vars:1; bool userdb:1; bool userdb_warn_missing:1; }; int db_passwd_file_lookup(struct db_passwd_file *db, struct auth_request *request, const char *username_format, struct passwd_user **user_r); struct db_passwd_file * db_passwd_file_init(const char *path, bool userdb, bool debug); void db_passwd_file_parse(struct db_passwd_file *db); void db_passwd_file_unref(struct db_passwd_file **db); #endif dovecot-2.3.21.1/src/auth/mech-apop.c0000644000000000000000000001037714656633576014072 00000000000000/* * APOP (RFC-1460) authentication mechanism. * * Copyright (c) 2004 Andrey Panin * * This software is released under the MIT license. */ #include "auth-common.h" #include "mech.h" #include "passdb.h" #include "md5.h" #include "buffer.h" #include "auth-client-connection.h" #include "auth-master-connection.h" #include #include struct apop_auth_request { struct auth_request auth_request; pool_t pool; /* requested: */ char *challenge; /* received: */ unsigned char response_digest[16]; }; static bool verify_credentials(struct apop_auth_request *request, const unsigned char *credentials, size_t size) { unsigned char digest[16]; struct md5_context ctx; md5_init(&ctx); md5_update(&ctx, request->challenge, strlen(request->challenge)); md5_update(&ctx, credentials, size); md5_final(&ctx, digest); return mem_equals_timing_safe(digest, request->response_digest, 16); } static void apop_credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *auth_request) { struct apop_auth_request *request = (struct apop_auth_request *)auth_request; switch (result) { case PASSDB_RESULT_OK: if (verify_credentials(request, credentials, size)) auth_request_success(auth_request, "", 0); else auth_request_fail(auth_request); break; case PASSDB_RESULT_INTERNAL_FAILURE: auth_request_internal_failure(auth_request); break; default: auth_request_fail(auth_request); break; } } static void mech_apop_auth_initial(struct auth_request *auth_request, const unsigned char *data, size_t data_size) { struct apop_auth_request *request = (struct apop_auth_request *)auth_request; const unsigned char *tmp, *end, *username = NULL; unsigned long pid, connect_uid, timestamp; const char *error; /* pop3-login handles sending the challenge and getting the response. Our input here is: \0 \0 */ if (data_size == 0) { /* Should never happen */ e_info(auth_request->mech_event, "no initial response"); auth_request_fail(auth_request); return; } tmp = data; end = data + data_size; /* get the challenge */ while (tmp != end && *tmp != '\0') tmp++; request->challenge = p_strdup_until(request->pool, data, tmp); if (tmp != end) { /* get the username */ username = ++tmp; while (tmp != end && *tmp != '\0') tmp++; } else { /* should never happen */ e_info(auth_request->mech_event, "malformed data"); auth_request_fail(auth_request); return; } if (tmp + 1 + 16 != end) { /* Should never happen */ e_info(auth_request->mech_event, "malformed data"); auth_request_fail(auth_request); return; } memcpy(request->response_digest, tmp + 1, sizeof(request->response_digest)); /* the challenge must begin with trusted unique ID. we trust only ourself, so make sure it matches our connection specific UID which we told to client in handshake. Also require a timestamp which is later than this process's start time. */ if (sscanf(request->challenge, "<%lx.%lx.%lx.", &pid, &connect_uid, ×tamp) != 3 || connect_uid != auth_request->connect_uid || pid != (unsigned long)getpid() || (time_t)timestamp < process_start_time) { e_info(auth_request->mech_event, "invalid challenge"); auth_request_fail(auth_request); return; } if (!auth_request_set_username(auth_request, (const char *)username, &error)) { e_info(auth_request->mech_event, "%s", error); auth_request_fail(auth_request); return; } auth_request_lookup_credentials(auth_request, "PLAIN", apop_credentials_callback); } static struct auth_request *mech_apop_auth_new(void) { struct apop_auth_request *request; pool_t pool; pool = pool_alloconly_create(MEMPOOL_GROWING"apop_auth_request", 2048); request = p_new(pool, struct apop_auth_request, 1); request->pool = pool; request->auth_request.pool = pool; return &request->auth_request; } const struct mech_module mech_apop = { "APOP", .flags = MECH_SEC_PRIVATE | MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE | MECH_SEC_ALLOW_NULS, .passdb_need = MECH_PASSDB_NEED_VERIFY_RESPONSE, mech_apop_auth_new, mech_apop_auth_initial, NULL, mech_generic_auth_free }; dovecot-2.3.21.1/src/auth/auth-request-handler.h0000644000000000000000000000470614656633576016267 00000000000000#ifndef AUTH_REQUEST_HANDLER_H #define AUTH_REQUEST_HANDLER_H struct auth_request; struct auth_client_connection; struct auth_master_connection; struct auth_stream_reply; enum auth_client_result { AUTH_CLIENT_RESULT_CONTINUE = 1, AUTH_CLIENT_RESULT_SUCCESS, AUTH_CLIENT_RESULT_FAILURE }; typedef void auth_client_request_callback_t(const char *reply, struct auth_client_connection *conn); typedef void auth_master_request_callback_t(const char *reply, struct auth_master_connection *conn); typedef void auth_request_handler_reply_callback_t(struct auth_request *request, enum auth_client_result result, const void *auth_reply, size_t reply_size); typedef void auth_request_handler_reply_continue_callback_t(struct auth_request *request, const void *reply, size_t reply_size); struct auth_request_handler * auth_request_handler_create(bool token_auth, auth_client_request_callback_t *callback, struct auth_client_connection *conn, auth_master_request_callback_t *master_callback); void auth_request_handler_destroy(struct auth_request_handler **handler); void auth_request_handler_unref(struct auth_request_handler **handler); void auth_request_handler_abort_requests(struct auth_request_handler *handler); void auth_request_handler_set(struct auth_request_handler *handler, unsigned int connect_uid, unsigned int client_pid); bool auth_request_handler_auth_begin(struct auth_request_handler *handler, const char *args); bool auth_request_handler_auth_continue(struct auth_request_handler *handler, const char *args); void auth_request_handler_reply(struct auth_request *request, enum auth_client_result result, const void *reply, size_t reply_size); void auth_request_handler_reply_continue(struct auth_request *request, const void *reply, size_t reply_size); void auth_request_handler_abort(struct auth_request *request); unsigned int auth_request_handler_get_request_count(struct auth_request_handler *handler); bool auth_request_handler_master_request(struct auth_request_handler *handler, struct auth_master_connection *master, unsigned int id, unsigned int client_id, const char *const *params); void auth_request_handler_cancel_request(struct auth_request_handler *handler, unsigned int client_id); void auth_request_handler_flush_failures(bool flush_all); void auth_request_handler_init(void); void auth_request_handler_deinit(void); #endif dovecot-2.3.21.1/src/auth/passdb-cache.c0000644000000000000000000001371114656633576014531 00000000000000/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "str.h" #include "strescape.h" #include "restrict-process-size.h" #include "auth-request-stats.h" #include "auth-worker-server.h" #include "password-scheme.h" #include "passdb.h" #include "passdb-cache.h" #include "passdb-blocking.h" struct auth_cache *passdb_cache = NULL; static void passdb_cache_log_hit(struct auth_request *request, const char *value) { const char *p; if (!request->set->debug_passwords && *value != '\0' && *value != '\t') { /* hide the password */ p = strchr(value, '\t'); value = t_strconcat(PASSWORD_HIDDEN_STR, p, NULL); } e_debug(authdb_event(request), "cache hit: %s", value); } static bool passdb_cache_lookup(struct auth_request *request, const char *key, bool use_expired, struct auth_cache_node **node_r, const char **value_r, bool *neg_expired_r) { struct auth_stats *stats = auth_request_stats_get(request); const char *value; bool expired; request->passdb_cache_result = AUTH_REQUEST_CACHE_MISS; /* value = password \t ... */ value = auth_cache_lookup(passdb_cache, request, key, node_r, &expired, neg_expired_r); if (value == NULL || (expired && !use_expired)) { stats->auth_cache_miss_count++; e_debug(authdb_event(request), value == NULL ? "cache miss" : "cache expired"); return FALSE; } stats->auth_cache_hit_count++; passdb_cache_log_hit(request, value); request->passdb_cache_result = AUTH_REQUEST_CACHE_HIT; *value_r = value; return TRUE; } static bool passdb_cache_verify_plain_callback(struct auth_worker_connection *conn ATTR_UNUSED, const char *reply, void *context) { struct auth_request *request = context; enum passdb_result result; result = passdb_blocking_auth_worker_reply_parse(request, reply); if (result != PASSDB_RESULT_OK) auth_fields_rollback(request->fields.extra_fields); auth_request_verify_plain_callback_finish(result, request); auth_request_unref(&request); return TRUE; } bool passdb_cache_verify_plain(struct auth_request *request, const char *key, const char *password, enum passdb_result *result_r, bool use_expired) { const char *value, *cached_pw, *scheme, *const *list; struct auth_cache_node *node; enum passdb_result ret; bool neg_expired; if (passdb_cache == NULL || key == NULL) return FALSE; if (!passdb_cache_lookup(request, key, use_expired, &node, &value, &neg_expired)) return FALSE; if (*value == '\0') { /* negative cache entry */ auth_request_log_unknown_user(request, AUTH_SUBSYS_DB); *result_r = PASSDB_RESULT_USER_UNKNOWN; auth_request_verify_plain_callback_finish(*result_r, request); return TRUE; } list = t_strsplit_tabescaped(value); cached_pw = list[0]; if (*cached_pw == '\0') { /* NULL password */ e_info(authdb_event(request), "Cached NULL password access"); ret = PASSDB_RESULT_OK; } else if (request->set->cache_verify_password_with_worker) { string_t *str; str = t_str_new(128); str_printfa(str, "PASSW\t%u\t", request->passdb->passdb->id); str_append_tabescaped(str, password); str_append_c(str, '\t'); str_append_tabescaped(str, cached_pw); str_append_c(str, '\t'); auth_request_export(request, str); e_debug(authdb_event(request), "cache: " "validating password on worker"); auth_request_ref(request); /* Save the extra fields already here, and take a snapshot. If verification fails, roll back fields. */ auth_request_set_fields(request, list + 1, NULL); auth_fields_snapshot(request->fields.extra_fields); auth_worker_call(request->pool, request->fields.user, str_c(str), passdb_cache_verify_plain_callback, request); return TRUE; } else { scheme = password_get_scheme(&cached_pw); i_assert(scheme != NULL); ret = auth_request_password_verify_log(request, password, cached_pw, scheme, AUTH_SUBSYS_DB, !(node->last_success || neg_expired)); if (ret == PASSDB_RESULT_PASSWORD_MISMATCH && (node->last_success || neg_expired)) { /* a) the last authentication was successful. assume that the password was changed and cache is expired. b) negative TTL reached, use it for password mismatches too. */ node->last_success = FALSE; return FALSE; } } node->last_success = ret == PASSDB_RESULT_OK; /* save the extra_fields only after we know we're using the cached data */ auth_request_set_fields(request, list + 1, NULL); *result_r = ret; auth_request_verify_plain_callback_finish(*result_r, request); return TRUE; } bool passdb_cache_lookup_credentials(struct auth_request *request, const char *key, const char **password_r, const char **scheme_r, enum passdb_result *result_r, bool use_expired) { const char *value, *const *list; struct auth_cache_node *node; bool neg_expired; if (passdb_cache == NULL) return FALSE; if (!passdb_cache_lookup(request, key, use_expired, &node, &value, &neg_expired)) return FALSE; if (*value == '\0') { /* negative cache entry */ *result_r = PASSDB_RESULT_USER_UNKNOWN; *password_r = NULL; *scheme_r = NULL; return TRUE; } list = t_strsplit_tabescaped(value); auth_request_set_fields(request, list + 1, NULL); *result_r = PASSDB_RESULT_OK; *password_r = *list[0] == '\0' ? NULL : list[0]; *scheme_r = password_get_scheme(password_r); i_assert(*scheme_r != NULL || *password_r == NULL); return TRUE; } void passdb_cache_init(const struct auth_settings *set) { rlim_t limit; if (set->cache_size == 0 || set->cache_ttl == 0) return; if (restrict_get_process_size(&limit) == 0 && set->cache_size > (uoff_t)limit) { i_warning("auth_cache_size (%"PRIuUOFF_T"M) is higher than " "process VSZ limit (%"PRIuUOFF_T"M)", set->cache_size/1024/1024, (uoff_t)(limit/1024/1024)); } passdb_cache = auth_cache_new(set->cache_size, set->cache_ttl, set->cache_negative_ttl); } void passdb_cache_deinit(void) { if (passdb_cache != NULL) auth_cache_free(&passdb_cache); } dovecot-2.3.21.1/src/auth/mech-otp-common.h0000644000000000000000000000075314656633576015225 00000000000000#ifndef MECH_OTP_COMMON_H #define MECH_OTP_COMMON_H struct otp_auth_request { struct auth_request auth_request; pool_t pool; bool lock; struct otp_state state; }; void otp_lock_init(void); bool otp_try_lock(struct auth_request *auth_request); void otp_unlock(struct auth_request *auth_request); void otp_set_credentials_callback(bool success, struct auth_request *auth_request); void mech_otp_auth_free(struct auth_request *auth_request); void mech_otp_deinit(void); #endif dovecot-2.3.21.1/src/auth/auth-request-stats.c0000644000000000000000000000375114656633576016002 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "str.h" #include "strescape.h" #include "buffer.h" #include "base64.h" #include "stats.h" #include "stats-connection.h" #include "auth-stats.h" #include "auth-request.h" #include "auth-request-stats.h" #define USER_STATS_SOCKET_NAME "old-stats-user" static struct stats_connection *auth_stats_conn = NULL; static struct stats_item *auth_stats_item; struct auth_stats *auth_request_stats_get(struct auth_request *request) { if (request->stats == NULL) request->stats = stats_alloc(request->pool); return stats_fill_ptr(request->stats, auth_stats_item); } void auth_request_stats_add_tempfail(struct auth_request *request) { struct auth_stats *stats = auth_request_stats_get(request); stats->auth_db_tempfail_count++; } void auth_request_stats_send(struct auth_request *request) { string_t *str; buffer_t *buf; /* we'll send stats only when the request is finished. this reduces memory usage and is a bit simpler. auth requests are typically pretty short lived anyway. */ i_assert(!request->stats_sent); request->stats_sent = TRUE; if (request->stats == NULL) { /* nothing happened in this request - don't send it */ return; } if (!request->set->stats) return; buf = t_buffer_create(128); stats_export(buf, request->stats); str = t_str_new(256); str_append(str, "ADD-USER\t"); if (request->fields.user != NULL) str_append_tabescaped(str, request->fields.user); str_append_c(str, '\t'); str_append_tabescaped(str, request->fields.service); str_append_c(str, '\t'); base64_encode(buf->data, buf->used, str); str_append_c(str, '\n'); stats_connection_send(auth_stats_conn, str); } void auth_request_stats_init(void) { auth_stats_conn = stats_connection_create(USER_STATS_SOCKET_NAME); auth_stats_item = stats_register(&auth_stats_vfuncs); } void auth_request_stats_deinit(void) { stats_connection_unref(&auth_stats_conn); stats_unregister(&auth_stats_item); } dovecot-2.3.21.1/src/auth/passdb-static.c0000644000000000000000000000557214656633576014763 00000000000000/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "passdb.h" #include "passdb-template.h" #include "password-scheme.h" struct static_passdb_module { struct passdb_module module; struct passdb_template *tmpl; const char *static_password_tmpl; }; static enum passdb_result static_save_fields(struct auth_request *request, const char **password_r, const char **scheme_r) { struct static_passdb_module *module = (struct static_passdb_module *)request->passdb->passdb; const char *error; *password_r = NULL; *scheme_r = NULL; e_debug(authdb_event(request), "lookup"); if (passdb_template_export(module->tmpl, request, &error) < 0) { e_error(authdb_event(request), "Failed to expand template: %s", error); return PASSDB_RESULT_INTERNAL_FAILURE; } if (module->static_password_tmpl != NULL) { if (t_auth_request_var_expand(module->static_password_tmpl, request, NULL, password_r, &error) <= 0) { e_error(authdb_event(request), "Failed to expand password=%s: %s", module->static_password_tmpl, error); return PASSDB_RESULT_INTERNAL_FAILURE; } } else if (auth_fields_exists(request->fields.extra_fields, "nopassword")) { *password_r = ""; } else { return auth_request_password_missing(request); } *scheme_r = password_get_scheme(password_r); if (*scheme_r == NULL) *scheme_r = STATIC_PASS_SCHEME; auth_request_set_field(request, "password", *password_r, *scheme_r); return PASSDB_RESULT_OK; } static void static_verify_plain(struct auth_request *request, const char *password, verify_plain_callback_t *callback) { enum passdb_result result; const char *static_password; const char *static_scheme; result = static_save_fields(request, &static_password, &static_scheme); if (result != PASSDB_RESULT_OK) { callback(result, request); return; } result = auth_request_password_verify(request, password, static_password, static_scheme, AUTH_SUBSYS_DB); callback(result, request); } static void static_lookup_credentials(struct auth_request *request, lookup_credentials_callback_t *callback) { enum passdb_result result; const char *static_password; const char *static_scheme; result = static_save_fields(request, &static_password, &static_scheme); passdb_handle_credentials(result, static_password, static_scheme, callback, request); } static struct passdb_module * static_preinit(pool_t pool, const char *args) { struct static_passdb_module *module; const char *value; module = p_new(pool, struct static_passdb_module, 1); module->tmpl = passdb_template_build(pool, args); if (passdb_template_remove(module->tmpl, "password", &value)) module->static_password_tmpl = value; return &module->module; } struct passdb_module_interface passdb_static = { "static", static_preinit, NULL, NULL, static_verify_plain, static_lookup_credentials, NULL }; dovecot-2.3.21.1/src/auth/passdb-imap.c0000644000000000000000000001567414656633576014426 00000000000000/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "ioloop.h" #include "passdb.h" #include "str.h" #include "imap-resp-code.h" #include "imapc-client.h" #define IMAP_DEFAULT_PORT 143 #define IMAPS_DEFAULT_PORT 993 #define DNS_CLIENT_SOCKET_NAME "dns-client" struct imap_passdb_module { struct passdb_module module; struct imapc_client_settings set; bool set_have_vars; }; struct imap_auth_request { struct imapc_client *client; struct auth_request *auth_request; verify_plain_callback_t *verify_callback; struct timeout *to_free; }; static enum passdb_result passdb_imap_get_failure_result(const struct imapc_command_reply *reply) { const char *key = reply->resp_text_key; if (key == NULL) return PASSDB_RESULT_PASSWORD_MISMATCH; if (strcasecmp(key, IMAP_RESP_CODE_AUTHFAILED) == 0 || strcasecmp(key, IMAP_RESP_CODE_AUTHZFAILED) == 0) return PASSDB_RESULT_PASSWORD_MISMATCH; if (strcasecmp(key, IMAP_RESP_CODE_EXPIRED) == 0) return PASSDB_RESULT_PASS_EXPIRED; return PASSDB_RESULT_INTERNAL_FAILURE; } static void passdb_imap_login_free(struct imap_auth_request *request) { timeout_remove(&request->to_free); imapc_client_deinit(&request->client); auth_request_unref(&request->auth_request); } static void passdb_imap_login_callback(const struct imapc_command_reply *reply, void *context) { struct imap_auth_request *request = context; enum passdb_result result = PASSDB_RESULT_INTERNAL_FAILURE; switch (reply->state) { case IMAPC_COMMAND_STATE_OK: result = PASSDB_RESULT_OK; break; case IMAPC_COMMAND_STATE_NO: result = passdb_imap_get_failure_result(reply); e_info(authdb_event(request->auth_request), "%s", reply->text_full); break; case IMAPC_COMMAND_STATE_AUTH_FAILED: case IMAPC_COMMAND_STATE_BAD: case IMAPC_COMMAND_STATE_DISCONNECTED: e_error(authdb_event(request->auth_request), "%s", reply->text_full); break; } request->verify_callback(result, request->auth_request); /* imapc_client can't be freed in this callback, so do it in a separate callback. FIXME: remove this once imapc supports proper refcounting. */ request->to_free = timeout_add_short(0, passdb_imap_login_free, request); } static void passdb_imap_verify_plain(struct auth_request *auth_request, const char *password, verify_plain_callback_t *callback) { struct passdb_module *_module = auth_request->passdb->passdb; struct imap_passdb_module *module = (struct imap_passdb_module *)_module; struct imap_auth_request *request; struct imapc_client_settings set; const char *error; string_t *str; set = module->set; set.debug = event_want_debug(auth_request->event); set.dns_client_socket_path = t_strconcat(auth_request->set->base_dir, "/", DNS_CLIENT_SOCKET_NAME, NULL); set.password = password; set.max_idle_time = IMAPC_DEFAULT_MAX_IDLE_TIME; if (set.ssl_set.ca_dir == NULL) set.ssl_set.ca_dir = auth_request->set->ssl_client_ca_dir; if (set.ssl_set.ca_file == NULL) set.ssl_set.ca_file = auth_request->set->ssl_client_ca_file; if (module->set_have_vars) { str = t_str_new(128); if (auth_request_var_expand(str, set.username, auth_request, NULL, &error) <= 0) { e_error(authdb_event(auth_request), "Failed to expand username=%s: %s", set.username, error); callback(PASSDB_RESULT_INTERNAL_FAILURE, auth_request); return; } set.username = t_strdup(str_c(str)); str_truncate(str, 0); if (auth_request_var_expand(str, set.host, auth_request, NULL, &error) <= 0) { e_error(authdb_event(auth_request), "Failed to expand host=%s: %s", set.host, error); callback(PASSDB_RESULT_INTERNAL_FAILURE, auth_request); return; } set.host = t_strdup(str_c(str)); } e_debug(authdb_event(auth_request), "lookup host=%s port=%d", set.host, set.port); request = p_new(auth_request->pool, struct imap_auth_request, 1); request->client = imapc_client_init(&set, authdb_event(auth_request)); request->auth_request = auth_request; request->verify_callback = callback; auth_request_ref(auth_request); imapc_client_set_login_callback(request->client, passdb_imap_login_callback, request); imapc_client_login(request->client); } static struct passdb_module * passdb_imap_preinit(pool_t pool, const char *args) { struct imap_passdb_module *module; char **tmp; const char *key, *value; bool port_set = FALSE; module = p_new(pool, struct imap_passdb_module, 1); module->module.default_pass_scheme = "PLAIN"; module->set.port = IMAP_DEFAULT_PORT; module->set.ssl_mode = IMAPC_CLIENT_SSL_MODE_NONE; module->set.username = "%u"; module->set.rawlog_dir = ""; for (tmp = p_strsplit(pool, args, " "); *tmp != NULL; tmp++) { key = *tmp; value = strchr(key, '='); if (value == NULL) value = ""; else key = t_strdup_until(key, value++); if (strcmp(key, "host") == 0) module->set.host = value; else if (strcmp(key, "port") == 0) { if (net_str2port(value, &module->set.port) < 0) i_fatal("passdb imap: Invalid port: %s", value); port_set = TRUE; } else if (strcmp(key, "username") == 0) module->set.username = value; else if (strcmp(key, "ssl_ca_dir") == 0) module->set.ssl_set.ca_dir = value; else if (strcmp(key, "ssl_ca_file") == 0) module->set.ssl_set.ca_file = value; else if (strcmp(key, "rawlog_dir") == 0) module->set.rawlog_dir = value; else if (strcmp(key, "ssl") == 0) { if (strcmp(value, "imaps") == 0) { if (!port_set) module->set.port = IMAPS_DEFAULT_PORT; module->set.ssl_mode = IMAPC_CLIENT_SSL_MODE_IMMEDIATE; } else if (strcmp(value, "starttls") == 0) { module->set.ssl_mode = IMAPC_CLIENT_SSL_MODE_STARTTLS; } else { i_fatal("passdb imap: Invalid ssl mode: %s", value); } } else if (strcmp(key, "allow_invalid_cert") == 0) { if (strcmp(value, "yes") == 0) { module->set.ssl_set.allow_invalid_cert = TRUE; } else if (strcmp(value, "no") == 0) { module->set.ssl_set.allow_invalid_cert = FALSE; } else { i_fatal("passdb imap: Invalid allow_invalid_cert value: %s", value); } } else { i_fatal("passdb imap: Unknown parameter: %s", key); } } if (!module->set.ssl_set.allow_invalid_cert && module->set.ssl_mode != IMAPC_CLIENT_SSL_MODE_NONE) { if (module->set.ssl_set.ca_dir == NULL && module->set.ssl_set.ca_file == NULL) i_fatal("passdb imap: Cannot verify certificate without ssl_ca_dir or ssl_ca_file setting"); } if (module->set.host == NULL) i_fatal("passdb imap: Missing host parameter"); module->set_have_vars = strchr(module->set.username, '%') != NULL || strchr(module->set.host, '%') != NULL; return &module->module; } static struct passdb_module_interface passdb_imap_plugin = { "imap", passdb_imap_preinit, NULL, NULL, passdb_imap_verify_plain, NULL, NULL }; void authdb_imap_init(void); void authdb_imap_deinit(void); void authdb_imap_init(void) { passdb_register_module(&passdb_imap_plugin); } void authdb_imap_deinit(void) { passdb_unregister_module(&passdb_imap_plugin); } dovecot-2.3.21.1/src/auth/password-scheme-scram.c0000644000000000000000000001574114656633576016430 00000000000000/* * SCRAM-SHA-1 SASL authentication, see RFC-5802 * * Copyright (c) 2012 Florian Zeitz * * This software is released under the MIT license. */ #include "lib.h" #include "safe-memset.h" #include "base64.h" #include "buffer.h" #include "hmac.h" #include "randgen.h" #include "hash-method.h" #include "sha1.h" #include "sha2.h" #include "str.h" #include "password-scheme.h" /* SCRAM allowed iteration count range. RFC says it SHOULD be at least 4096 */ #define SCRAM_MIN_ITERATE_COUNT 4096 #define SCRAM_MAX_ITERATE_COUNT INT_MAX #define SCRAM_DEFAULT_ITERATE_COUNT 4096 static void Hi(const struct hash_method *hmethod, const unsigned char *str, size_t str_size, const unsigned char *salt, size_t salt_size, unsigned int i, unsigned char *result) { struct hmac_context ctx; unsigned char U[hmethod->digest_size]; unsigned int j, k; /* Calculate U1 */ hmac_init(&ctx, str, str_size, hmethod); hmac_update(&ctx, salt, salt_size); hmac_update(&ctx, "\0\0\0\1", 4); hmac_final(&ctx, U); memcpy(result, U, hmethod->digest_size); /* Calculate U2 to Ui and Hi */ for (j = 2; j <= i; j++) { hmac_init(&ctx, str, str_size, hmethod); hmac_update(&ctx, U, sizeof(U)); hmac_final(&ctx, U); for (k = 0; k < hmethod->digest_size; k++) result[k] ^= U[k]; } } int scram_scheme_parse(const struct hash_method *hmethod, const char *name, const unsigned char *credentials, size_t size, unsigned int *iter_count_r, const char **salt_r, unsigned char stored_key_r[], unsigned char server_key_r[], const char **error_r) { const char *const *fields; buffer_t *buf; /* password string format: iter,salt,stored_key,server_key */ fields = t_strsplit(t_strndup(credentials, size), ","); if (str_array_length(fields) != 4) { *error_r = t_strdup_printf( "Invalid %s passdb entry format", name); return -1; } if (str_to_uint(fields[0], iter_count_r) < 0 || *iter_count_r < SCRAM_MIN_ITERATE_COUNT || *iter_count_r > SCRAM_MAX_ITERATE_COUNT) { *error_r = t_strdup_printf( "Invalid %s iteration count in passdb", name); return -1; } *salt_r = fields[1]; buf = t_buffer_create(hmethod->digest_size); if (base64_decode(fields[2], strlen(fields[2]), NULL, buf) < 0 || buf->used != hmethod->digest_size) { *error_r = t_strdup_printf( "Invalid %s StoredKey in passdb", name); return -1; } memcpy(stored_key_r, buf->data, hmethod->digest_size); buffer_set_used_size(buf, 0); if (base64_decode(fields[3], strlen(fields[3]), NULL, buf) < 0 || buf->used != hmethod->digest_size) { *error_r = t_strdup_printf( "Invalid %s ServerKey in passdb", name); return -1; } memcpy(server_key_r, buf->data, hmethod->digest_size); return 0; } int scram_verify(const struct hash_method *hmethod, const char *scheme_name, const char *plaintext, const unsigned char *raw_password, size_t size, const char **error_r) { struct hmac_context ctx; const char *salt_base64; unsigned int iter_count; const unsigned char *salt; size_t salt_len; unsigned char salted_password[hmethod->digest_size]; unsigned char client_key[hmethod->digest_size]; unsigned char stored_key[hmethod->digest_size]; unsigned char calculated_stored_key[hmethod->digest_size]; unsigned char server_key[hmethod->digest_size]; int ret; if (scram_scheme_parse(hmethod, scheme_name, raw_password, size, &iter_count, &salt_base64, stored_key, server_key, error_r) < 0) return -1; salt = buffer_get_data(t_base64_decode_str(salt_base64), &salt_len); /* FIXME: credentials should be SASLprepped UTF8 data here */ Hi(hmethod, (const unsigned char *)plaintext, strlen(plaintext), salt, salt_len, iter_count, salted_password); /* Calculate ClientKey */ hmac_init(&ctx, salted_password, sizeof(salted_password), hmethod); hmac_update(&ctx, "Client Key", 10); hmac_final(&ctx, client_key); /* Calculate StoredKey */ hash_method_get_digest(hmethod, client_key, sizeof(client_key), calculated_stored_key); ret = mem_equals_timing_safe(stored_key, calculated_stored_key, sizeof(stored_key)) ? 1 : 0; safe_memset(salted_password, 0, sizeof(salted_password)); safe_memset(client_key, 0, sizeof(client_key)); safe_memset(stored_key, 0, sizeof(stored_key)); return ret; } void scram_generate(const struct hash_method *hmethod, const char *plaintext, const unsigned char **raw_password_r, size_t *size_r) { string_t *str; struct hmac_context ctx; unsigned char salt[16]; unsigned char salted_password[hmethod->digest_size]; unsigned char client_key[hmethod->digest_size]; unsigned char server_key[hmethod->digest_size]; unsigned char stored_key[hmethod->digest_size]; random_fill(salt, sizeof(salt)); str = t_str_new(MAX_BASE64_ENCODED_SIZE(sizeof(salt))); str_printfa(str, "%d,", SCRAM_DEFAULT_ITERATE_COUNT); base64_encode(salt, sizeof(salt), str); /* FIXME: credentials should be SASLprepped UTF8 data here */ Hi(hmethod, (const unsigned char *)plaintext, strlen(plaintext), salt, sizeof(salt), SCRAM_DEFAULT_ITERATE_COUNT, salted_password); /* Calculate ClientKey */ hmac_init(&ctx, salted_password, sizeof(salted_password), hmethod); hmac_update(&ctx, "Client Key", 10); hmac_final(&ctx, client_key); /* Calculate StoredKey */ hash_method_get_digest(hmethod, client_key, sizeof(client_key), stored_key); str_append_c(str, ','); base64_encode(stored_key, sizeof(stored_key), str); /* Calculate ServerKey */ hmac_init(&ctx, salted_password, sizeof(salted_password), hmethod); hmac_update(&ctx, "Server Key", 10); hmac_final(&ctx, server_key); str_append_c(str, ','); base64_encode(server_key, sizeof(server_key), str); safe_memset(salted_password, 0, sizeof(salted_password)); safe_memset(client_key, 0, sizeof(client_key)); safe_memset(server_key, 0, sizeof(server_key)); safe_memset(stored_key, 0, sizeof(stored_key)); *raw_password_r = (const unsigned char *)str_c(str); *size_r = str_len(str); } int scram_sha1_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char *raw_password, size_t size, const char **error_r) { return scram_verify(&hash_method_sha1, "SCRAM-SHA-1", plaintext, raw_password, size, error_r); } void scram_sha1_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r) { scram_generate(&hash_method_sha1, plaintext, raw_password_r, size_r); } int scram_sha256_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char *raw_password, size_t size, const char **error_r) { return scram_verify(&hash_method_sha256, "SCRAM-SHA-256", plaintext, raw_password, size, error_r); } void scram_sha256_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r) { scram_generate(&hash_method_sha256, plaintext, raw_password_r, size_r); } dovecot-2.3.21.1/src/auth/auth.h0000644000000000000000000000402214656633576013155 00000000000000#ifndef AUTH_H #define AUTH_H #include "auth-settings.h" #define PASSWORD_HIDDEN_STR "" ARRAY_DEFINE_TYPE(auth, struct auth *); extern ARRAY_TYPE(auth) auths; enum auth_passdb_skip { AUTH_PASSDB_SKIP_NEVER, AUTH_PASSDB_SKIP_AUTHENTICATED, AUTH_PASSDB_SKIP_UNAUTHENTICATED }; enum auth_userdb_skip { AUTH_USERDB_SKIP_NEVER, AUTH_USERDB_SKIP_FOUND, AUTH_USERDB_SKIP_NOTFOUND }; enum auth_db_rule { AUTH_DB_RULE_RETURN, AUTH_DB_RULE_RETURN_OK, AUTH_DB_RULE_RETURN_FAIL, AUTH_DB_RULE_CONTINUE, AUTH_DB_RULE_CONTINUE_OK, AUTH_DB_RULE_CONTINUE_FAIL }; struct auth_passdb { struct auth_passdb *next; const struct auth_passdb_settings *set; struct passdb_module *passdb; /* The caching key for this passdb, or NULL if caching isn't wanted. */ const char *cache_key; struct passdb_template *default_fields_tmpl; struct passdb_template *override_fields_tmpl; enum auth_passdb_skip skip; enum auth_db_rule result_success; enum auth_db_rule result_failure; enum auth_db_rule result_internalfail; }; struct auth_userdb { struct auth_userdb *next; const struct auth_userdb_settings *set; struct userdb_module *userdb; /* The caching key for this userdb, or NULL if caching isn't wanted. */ const char *cache_key; struct userdb_template *default_fields_tmpl; struct userdb_template *override_fields_tmpl; enum auth_userdb_skip skip; enum auth_db_rule result_success; enum auth_db_rule result_failure; enum auth_db_rule result_internalfail; }; struct auth { pool_t pool; const char *service; const struct auth_settings *set; const struct mechanisms_register *reg; struct auth_passdb *masterdbs; struct auth_passdb *passdbs; struct auth_userdb *userdbs; }; extern struct auth_penalty *auth_penalty; struct auth *auth_find_service(const char *name); struct auth *auth_default_service(void); void auths_preinit(const struct auth_settings *set, pool_t pool, const struct mechanisms_register *reg, const char *const *services); void auths_init(void); void auths_deinit(void); void auths_free(void); #endif dovecot-2.3.21.1/src/auth/auth-request.c0000644000000000000000000023060014656633576014641 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "ioloop.h" #include "buffer.h" #include "hash.h" #include "sha1.h" #include "hex-binary.h" #include "str.h" #include "array.h" #include "safe-memset.h" #include "str-sanitize.h" #include "strescape.h" #include "var-expand.h" #include "dns-lookup.h" #include "auth-cache.h" #include "auth-request.h" #include "auth-request-handler.h" #include "auth-request-handler-private.h" #include "auth-request-stats.h" #include "auth-client-connection.h" #include "auth-master-connection.h" #include "auth-policy.h" #include "passdb.h" #include "passdb-blocking.h" #include "passdb-cache.h" #include "passdb-template.h" #include "userdb-blocking.h" #include "userdb-template.h" #include "password-scheme.h" #include "wildcard-match.h" #include #define AUTH_SUBSYS_PROXY "proxy" #define AUTH_DNS_SOCKET_PATH "dns-client" #define AUTH_DNS_DEFAULT_TIMEOUT_MSECS (1000*10) #define AUTH_DNS_WARN_MSECS 500 #define AUTH_REQUEST_MAX_DELAY_SECS (60*5) #define CACHED_PASSWORD_SCHEME "SHA1" struct auth_request_proxy_dns_lookup_ctx { struct auth_request *request; auth_request_proxy_cb_t *callback; struct dns_lookup *dns_lookup; }; struct auth_policy_check_ctx { enum { AUTH_POLICY_CHECK_TYPE_PLAIN, AUTH_POLICY_CHECK_TYPE_LOOKUP, AUTH_POLICY_CHECK_TYPE_SUCCESS, } type; struct auth_request *request; buffer_t *success_data; verify_plain_callback_t *callback_plain; lookup_credentials_callback_t *callback_lookup; }; const char auth_default_subsystems[2]; unsigned int auth_request_state_count[AUTH_REQUEST_STATE_MAX]; static void get_log_identifier(string_t *str, struct auth_request *auth_request); static void auth_request_userdb_import(struct auth_request *request, const char *args); static void auth_request_lookup_credentials_policy_continue(struct auth_request *request, lookup_credentials_callback_t *callback); static void auth_request_policy_check_callback(int result, void *context); static const char *get_log_prefix_mech(struct auth_request *auth_request) { string_t *str = t_str_new(64); auth_request_get_log_prefix(str, auth_request, AUTH_SUBSYS_MECH); return str_c(str); } const char *auth_request_get_log_prefix_db(struct auth_request *auth_request) { string_t *str = t_str_new(64); auth_request_get_log_prefix(str, auth_request, AUTH_SUBSYS_DB); return str_c(str); } static struct event *get_request_event(struct auth_request *request, const char *subsystem) { if (subsystem == AUTH_SUBSYS_DB) return authdb_event(request); else if (subsystem == AUTH_SUBSYS_MECH) return request->mech_event; else return request->event; } static void auth_request_post_alloc_init(struct auth_request *request, struct event *parent_event) { enum log_type level; request->state = AUTH_REQUEST_STATE_NEW; auth_request_state_count[AUTH_REQUEST_STATE_NEW]++; request->refcount = 1; request->last_access = ioloop_time; request->session_pid = (pid_t)-1; request->set = global_auth_settings; request->event = event_create(parent_event); request->mech_event = event_create(request->event); auth_request_fields_init(request); level = request->set->verbose ? LOG_TYPE_INFO : LOG_TYPE_WARNING; event_set_min_log_level(request->event, level); event_set_min_log_level(request->mech_event, level); p_array_init(&request->authdb_event, request->pool, 2); event_set_log_prefix_callback(request->mech_event, FALSE, get_log_prefix_mech, request); event_set_forced_debug(request->event, request->set->debug); event_add_category(request->event, &event_category_auth); } struct auth_request * auth_request_new(const struct mech_module *mech, struct event *parent_event) { struct auth_request *request; request = mech->auth_new(); request->mech = mech; auth_request_post_alloc_init(request, parent_event); return request; } struct auth_request *auth_request_new_dummy(struct event *parent_event) { struct auth_request *request; pool_t pool; pool = pool_alloconly_create(MEMPOOL_GROWING"auth_request", 1024); request = p_new(pool, struct auth_request, 1); request->pool = pool; auth_request_post_alloc_init(request, parent_event); return request; } void auth_request_set_state(struct auth_request *request, enum auth_request_state state) { if (request->state == state) return; i_assert(request->to_penalty == NULL); i_assert(auth_request_state_count[request->state] > 0); auth_request_state_count[request->state]--; auth_request_state_count[state]++; request->state = state; auth_refresh_proctitle(); } void auth_request_init(struct auth_request *request) { struct auth *auth; auth = auth_request_get_auth(request); request->set = auth->set; request->passdb = auth->passdbs; request->userdb = auth->userdbs; } struct auth *auth_request_get_auth(struct auth_request *request) { return auth_find_service(request->fields.service); } void auth_request_success(struct auth_request *request, const void *data, size_t data_size) { i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE); if (!request->set->policy_check_after_auth) { struct auth_policy_check_ctx *ctx = p_new(request->pool, struct auth_policy_check_ctx, 1); ctx->success_data = buffer_create_dynamic(request->pool, 1); ctx->request = request; ctx->type = AUTH_POLICY_CHECK_TYPE_SUCCESS; auth_request_policy_check_callback(0, ctx); return; } /* perform second policy lookup here */ struct auth_policy_check_ctx *ctx = p_new(request->pool, struct auth_policy_check_ctx, 1); ctx->request = request; ctx->success_data = buffer_create_dynamic(request->pool, data_size); buffer_append(ctx->success_data, data, data_size); ctx->type = AUTH_POLICY_CHECK_TYPE_SUCCESS; auth_policy_check(request, request->mech_password, auth_request_policy_check_callback, ctx); } struct event_passthrough * auth_request_finished_event(struct auth_request *request, struct event *event) { struct event_passthrough *e = event_create_passthrough(event); if (request->failed) { if (request->internal_failure) { e->add_str("error", "internal failure"); } else { e->add_str("error", "authentication failed"); } } else if (request->fields.successful) { e->add_str("success", "yes"); } if (request->userdb_lookup) { return e; } if (request->policy_penalty > 0) e->add_int("policy_penalty", request->policy_penalty); if (request->policy_refusal) { e->add_str("policy_result", "refused"); } else if (request->policy_processed && request->policy_penalty > 0) { e->add_str("policy_result", "delayed"); e->add_int("policy_penalty", request->policy_penalty); } else if (request->policy_processed) { e->add_str("policy_result", "ok"); } return e; } void auth_request_log_finished(struct auth_request *request) { if (request->event_finished_sent) return; request->event_finished_sent = TRUE; string_t *str = t_str_new(64); auth_request_get_log_prefix(str, request, "auth"); struct event_passthrough *e = auth_request_finished_event(request, request->event)-> set_name("auth_request_finished"); e_debug(e->event(), "%sAuth request finished", str_c(str)); } static void auth_request_success_continue(struct auth_policy_check_ctx *ctx) { struct auth_request *request = ctx->request; struct auth_stats *stats; i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE); timeout_remove(&request->to_penalty); if (request->failed || !request->passdb_success) { /* password was valid, but some other check failed. */ auth_request_fail(request); return; } auth_request_set_auth_successful(request); /* log before delay */ auth_request_log_finished(request); if (request->delay_until > ioloop_time) { unsigned int delay_secs = request->delay_until - ioloop_time; request->to_penalty = timeout_add(delay_secs * 1000, auth_request_success_continue, ctx); return; } if (ctx->success_data->used > 0 && !request->fields.final_resp_ok) { /* we'll need one more SASL round, since client doesn't support the final SASL response */ auth_request_handler_reply_continue(request, ctx->success_data->data, ctx->success_data->used); return; } if (request->set->stats) { stats = auth_request_stats_get(request); stats->auth_success_count++; if (request->fields.master_user != NULL) stats->auth_master_success_count++; } auth_request_set_state(request, AUTH_REQUEST_STATE_FINISHED); auth_request_refresh_last_access(request); auth_request_handler_reply(request, AUTH_CLIENT_RESULT_SUCCESS, ctx->success_data->data, ctx->success_data->used); } void auth_request_fail_with_reply(struct auth_request *request, const void *final_data, size_t final_data_size) { struct auth_stats *stats; i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE); if (request->set->stats) { stats = auth_request_stats_get(request); stats->auth_failure_count++; } auth_request_set_state(request, AUTH_REQUEST_STATE_FINISHED); auth_request_refresh_last_access(request); auth_request_log_finished(request); auth_request_handler_reply(request, AUTH_CLIENT_RESULT_FAILURE, final_data, final_data_size); } void auth_request_fail(struct auth_request *request) { auth_request_fail_with_reply(request, "", 0); } void auth_request_internal_failure(struct auth_request *request) { request->internal_failure = TRUE; auth_request_fail(request); } void auth_request_ref(struct auth_request *request) { request->refcount++; } void auth_request_unref(struct auth_request **_request) { struct auth_request *request = *_request; *_request = NULL; i_assert(request->refcount > 0); if (--request->refcount > 0) return; i_assert(array_count(&request->authdb_event) == 0); if (request->handler_pending_reply) auth_request_handler_abort(request); event_unref(&request->mech_event); event_unref(&request->event); auth_request_stats_send(request); auth_request_state_count[request->state]--; auth_refresh_proctitle(); if (request->mech_password != NULL) { safe_memset(request->mech_password, 0, strlen(request->mech_password)); } if (request->dns_lookup_ctx != NULL) dns_lookup_abort(&request->dns_lookup_ctx->dns_lookup); timeout_remove(&request->to_abort); timeout_remove(&request->to_penalty); if (request->mech != NULL) request->mech->auth_free(request); else pool_unref(&request->pool); } bool auth_request_import_master(struct auth_request *request, const char *key, const char *value) { pid_t pid; i_assert(value != NULL); /* master request lookups may set these */ if (strcmp(key, "session_pid") == 0) { if (str_to_pid(value, &pid) == 0) request->session_pid = pid; } else if (strcmp(key, "request_auth_token") == 0) request->request_auth_token = TRUE; else return FALSE; return TRUE; } static bool auth_request_fail_on_nuls(struct auth_request *request, const unsigned char *data, size_t data_size) { if ((request->mech->flags & MECH_SEC_ALLOW_NULS) != 0) return FALSE; if (memchr(data, '\0', data_size) != NULL) { e_debug(request->mech_event, "Unexpected NUL in auth data"); auth_request_fail(request); return TRUE; } return FALSE; } void auth_request_initial(struct auth_request *request) { i_assert(request->state == AUTH_REQUEST_STATE_NEW); auth_request_set_state(request, AUTH_REQUEST_STATE_MECH_CONTINUE); if (auth_request_fail_on_nuls(request, request->initial_response, request->initial_response_len)) return; request->mech->auth_initial(request, request->initial_response, request->initial_response_len); } void auth_request_continue(struct auth_request *request, const unsigned char *data, size_t data_size) { i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE); if (request->fields.successful) { auth_request_success(request, "", 0); return; } if (auth_request_fail_on_nuls(request, data, data_size)) return; auth_request_refresh_last_access(request); request->mech->auth_continue(request, data, data_size); } static void auth_request_save_cache(struct auth_request *request, enum passdb_result result) { struct auth_passdb *passdb = request->passdb; const char *encoded_password; string_t *str; struct password_generate_params gen_params = { .user = request->fields.user, .rounds = 0 }; switch (result) { case PASSDB_RESULT_USER_UNKNOWN: case PASSDB_RESULT_PASSWORD_MISMATCH: case PASSDB_RESULT_OK: case PASSDB_RESULT_SCHEME_NOT_AVAILABLE: /* can be cached */ break; case PASSDB_RESULT_NEXT: case PASSDB_RESULT_USER_DISABLED: case PASSDB_RESULT_PASS_EXPIRED: /* FIXME: we can't cache this now, or cache lookup would return success. */ return; case PASSDB_RESULT_INTERNAL_FAILURE: i_unreached(); } if (passdb_cache == NULL || passdb->cache_key == NULL) return; if (result < 0) { /* lookup failed. */ if (result == PASSDB_RESULT_USER_UNKNOWN) { auth_cache_insert(passdb_cache, request, passdb->cache_key, "", FALSE); } return; } if (request->passdb_password == NULL && !auth_fields_exists(request->fields.extra_fields, "nopassword")) { /* passdb didn't provide the correct password */ if (result != PASSDB_RESULT_OK || request->mech_password == NULL) return; /* we can still cache valid password lookups though. strdup() it so that mech_password doesn't get cleared too early. */ if (!password_generate_encoded(request->mech_password, &gen_params, CACHED_PASSWORD_SCHEME, &encoded_password)) i_unreached(); request->passdb_password = p_strconcat(request->pool, "{"CACHED_PASSWORD_SCHEME"}", encoded_password, NULL); } /* save all except the currently given password in cache */ str = t_str_new(256); if (request->passdb_password != NULL) { if (*request->passdb_password != '{') { /* cached passwords must have a known scheme */ str_append_c(str, '{'); str_append(str, passdb->passdb->default_pass_scheme); str_append_c(str, '}'); } str_append_tabescaped(str, request->passdb_password); } if (!auth_fields_is_empty(request->fields.extra_fields)) { str_append_c(str, '\t'); /* add only those extra fields to cache that were set by this passdb lookup. the CHANGED flag does this, because we snapshotted the extra_fields before the current passdb lookup. */ auth_fields_append(request->fields.extra_fields, str, AUTH_FIELD_FLAG_CHANGED, AUTH_FIELD_FLAG_CHANGED); } auth_cache_insert(passdb_cache, request, passdb->cache_key, str_c(str), result == PASSDB_RESULT_OK); } static bool auth_request_mechanism_accepted(const char *const *mechs, const struct mech_module *mech) { /* no filter specified, anything goes */ if (mechs == NULL) return TRUE; /* request has no mechanism, see if none is accepted */ if (mech == NULL) return str_array_icase_find(mechs, "none"); /* check if request mechanism is accepted */ return str_array_icase_find(mechs, mech->mech_name); } /** Check if username is included in the filter. Logic is that if the username is not excluded by anything, and is included by something, it will be accepted. By default, all usernames are included, unless there is a inclusion item, when username will be excluded if there is no inclusion for it. Exclusions are denoted with a ! in front of the pattern. */ bool auth_request_username_accepted(const char *const *filter, const char *username) { bool have_includes = FALSE; bool matched_inc = FALSE; for(;*filter != NULL; filter++) { /* if filter has ! it means the pattern will be refused */ bool exclude = (**filter == '!'); if (!exclude) have_includes = TRUE; if (wildcard_match(username, (*filter)+(exclude?1:0))) { if (exclude) { return FALSE; } else { matched_inc = TRUE; } } } return matched_inc || !have_includes; } static bool auth_request_want_skip_passdb(struct auth_request *request, struct auth_passdb *passdb) { /* if mechanism is not supported, skip */ const char *const *mechs = passdb->passdb->mechanisms; const char *const *username_filter = passdb->passdb->username_filter; const char *username; username = request->fields.user; if (!auth_request_mechanism_accepted(mechs, request->mech)) { auth_request_log_debug(request, request->mech != NULL ? AUTH_SUBSYS_MECH : "none", "skipping passdb: mechanism filtered"); return TRUE; } if (passdb->passdb->username_filter != NULL && !auth_request_username_accepted(username_filter, username)) { auth_request_log_debug(request, request->mech != NULL ? AUTH_SUBSYS_MECH : "none", "skipping passdb: username filtered"); return TRUE; } /* skip_password_check basically specifies if authentication is finished */ bool authenticated = request->fields.skip_password_check; switch (passdb->skip) { case AUTH_PASSDB_SKIP_NEVER: return FALSE; case AUTH_PASSDB_SKIP_AUTHENTICATED: return authenticated; case AUTH_PASSDB_SKIP_UNAUTHENTICATED: return !authenticated; } i_unreached(); } static bool auth_request_want_skip_userdb(struct auth_request *request, struct auth_userdb *userdb) { switch (userdb->skip) { case AUTH_USERDB_SKIP_NEVER: return FALSE; case AUTH_USERDB_SKIP_FOUND: return request->userdb_success; case AUTH_USERDB_SKIP_NOTFOUND: return !request->userdb_success; } i_unreached(); } static const char * auth_request_cache_result_to_str(enum auth_request_cache_result result) { switch(result) { case AUTH_REQUEST_CACHE_NONE: return "none"; case AUTH_REQUEST_CACHE_HIT: return "hit"; case AUTH_REQUEST_CACHE_MISS: return "miss"; default: i_unreached(); } } void auth_request_passdb_lookup_begin(struct auth_request *request) { struct event *event; const char *name; i_assert(request->passdb != NULL); i_assert(!request->userdb_lookup); request->passdb_cache_result = AUTH_REQUEST_CACHE_NONE; name = (request->passdb->set->name[0] != '\0' ? request->passdb->set->name : request->passdb->passdb->iface.name); event = event_create(request->event); event_add_str(event, "passdb_id", dec2str(request->passdb->passdb->id)); event_add_str(event, "passdb_name", name); event_add_str(event, "passdb", request->passdb->passdb->iface.name); event_set_log_prefix_callback(event, FALSE, auth_request_get_log_prefix_db, request); /* check if we should enable verbose logging here */ if (*request->passdb->set->auth_verbose == 'y') event_set_min_log_level(event, LOG_TYPE_INFO); else if (*request->passdb->set->auth_verbose == 'n') event_set_min_log_level(event, LOG_TYPE_WARNING); e_debug(event_create_passthrough(event)-> set_name("auth_passdb_request_started")-> event(), "Performing passdb lookup"); array_push_back(&request->authdb_event, &event); } void auth_request_passdb_lookup_end(struct auth_request *request, enum passdb_result result) { i_assert(array_count(&request->authdb_event) > 0); struct event *event = authdb_event(request); struct event_passthrough *e = event_create_passthrough(event)-> set_name("auth_passdb_request_finished")-> add_str("result", passdb_result_to_string(result)); if (request->passdb_cache_result != AUTH_REQUEST_CACHE_NONE && request->set->cache_ttl != 0 && request->set->cache_size != 0) e->add_str("cache", auth_request_cache_result_to_str(request->passdb_cache_result)); e_debug(e->event(), "Finished passdb lookup"); event_unref(&event); array_pop_back(&request->authdb_event); } void auth_request_userdb_lookup_begin(struct auth_request *request) { struct event *event; const char *name; i_assert(request->userdb != NULL); i_assert(request->userdb_lookup); request->userdb_cache_result = AUTH_REQUEST_CACHE_NONE; name = (request->userdb->set->name[0] != '\0' ? request->userdb->set->name : request->userdb->userdb->iface->name); event = event_create(request->event); event_add_str(event, "userdb_id", dec2str(request->userdb->userdb->id)); event_add_str(event, "userdb_name", name); event_add_str(event, "userdb", request->userdb->userdb->iface->name); event_set_log_prefix_callback(event, FALSE, auth_request_get_log_prefix_db, request); /* check if we should enable verbose logging here*/ if (*request->userdb->set->auth_verbose == 'y') event_set_min_log_level(event, LOG_TYPE_INFO); else if (*request->userdb->set->auth_verbose == 'n') event_set_min_log_level(event, LOG_TYPE_WARNING); e_debug(event_create_passthrough(event)-> set_name("auth_userdb_request_started")-> event(), "Performing userdb lookup"); array_push_back(&request->authdb_event, &event); } void auth_request_userdb_lookup_end(struct auth_request *request, enum userdb_result result) { i_assert(array_count(&request->authdb_event) > 0); struct event *event = authdb_event(request); struct event_passthrough *e = event_create_passthrough(event)-> set_name("auth_userdb_request_finished")-> add_str("result", userdb_result_to_string(result)); if (request->userdb_cache_result != AUTH_REQUEST_CACHE_NONE && request->set->cache_ttl != 0 && request->set->cache_size != 0) e->add_str("cache", auth_request_cache_result_to_str(request->userdb_cache_result)); e_debug(e->event(), "Finished userdb lookup"); event_unref(&event); array_pop_back(&request->authdb_event); } static bool auth_request_handle_passdb_callback(enum passdb_result *result, struct auth_request *request) { struct auth_passdb *next_passdb; enum auth_db_rule result_rule; bool passdb_continue = FALSE; if (request->passdb_password != NULL) { safe_memset(request->passdb_password, 0, strlen(request->passdb_password)); } auth_request_passdb_lookup_end(request, *result); if (request->passdb->set->deny && *result != PASSDB_RESULT_USER_UNKNOWN) { /* deny passdb. we can get through this step only if the lookup returned that user doesn't exist in it. internal errors are fatal here. */ if (*result != PASSDB_RESULT_INTERNAL_FAILURE) { e_info(authdb_event(request), "User found from deny passdb"); *result = PASSDB_RESULT_USER_DISABLED; } return TRUE; } if (request->failed) { /* The passdb didn't fail, but something inside it failed (e.g. allow_nets mismatch). Make sure we'll fail this lookup, but reset the failure so the next passdb can succeed. */ if (*result == PASSDB_RESULT_OK) *result = PASSDB_RESULT_USER_UNKNOWN; request->failed = FALSE; } /* users that exist but can't log in are special. we don't try to match any of the success/failure rules to them. they'll always fail. */ switch (*result) { case PASSDB_RESULT_USER_DISABLED: return TRUE; case PASSDB_RESULT_PASS_EXPIRED: auth_request_set_field(request, "reason", "Password expired", NULL); return TRUE; case PASSDB_RESULT_OK: result_rule = request->passdb->result_success; break; case PASSDB_RESULT_INTERNAL_FAILURE: result_rule = request->passdb->result_internalfail; break; case PASSDB_RESULT_NEXT: e_debug(authdb_event(request), "Not performing authentication (noauthenticate set)"); result_rule = AUTH_DB_RULE_CONTINUE; break; case PASSDB_RESULT_SCHEME_NOT_AVAILABLE: case PASSDB_RESULT_USER_UNKNOWN: case PASSDB_RESULT_PASSWORD_MISMATCH: default: result_rule = request->passdb->result_failure; break; } switch (result_rule) { case AUTH_DB_RULE_RETURN: break; case AUTH_DB_RULE_RETURN_OK: request->passdb_success = TRUE; break; case AUTH_DB_RULE_RETURN_FAIL: request->passdb_success = FALSE; break; case AUTH_DB_RULE_CONTINUE: passdb_continue = TRUE; if (*result == PASSDB_RESULT_OK) { /* password was successfully verified. don't bother checking it again. */ auth_request_set_password_verified(request); } break; case AUTH_DB_RULE_CONTINUE_OK: passdb_continue = TRUE; request->passdb_success = TRUE; /* password was successfully verified. don't bother checking it again. */ auth_request_set_password_verified(request); break; case AUTH_DB_RULE_CONTINUE_FAIL: passdb_continue = TRUE; request->passdb_success = FALSE; break; } /* nopassword check is specific to a single passdb and shouldn't leak to the next one. we already added it to cache. */ auth_fields_remove(request->fields.extra_fields, "nopassword"); auth_fields_remove(request->fields.extra_fields, "noauthenticate"); if (request->fields.requested_login_user != NULL && *result == PASSDB_RESULT_OK) { auth_request_master_user_login_finish(request); /* if the passdb lookup continues, it continues with non-master passdbs for the requested_login_user. */ next_passdb = auth_request_get_auth(request)->passdbs; } else { next_passdb = request->passdb->next; } while (next_passdb != NULL && auth_request_want_skip_passdb(request, next_passdb)) next_passdb = next_passdb->next; if (*result == PASSDB_RESULT_OK || *result == PASSDB_RESULT_NEXT) { /* this passdb lookup succeeded, preserve its extra fields */ auth_fields_snapshot(request->fields.extra_fields); request->snapshot_have_userdb_prefetch_set = request->userdb_prefetch_set; if (request->fields.userdb_reply != NULL) auth_fields_snapshot(request->fields.userdb_reply); } else { /* this passdb lookup failed, remove any extra fields it set */ auth_fields_rollback(request->fields.extra_fields); if (request->fields.userdb_reply != NULL) { auth_fields_rollback(request->fields.userdb_reply); request->userdb_prefetch_set = request->snapshot_have_userdb_prefetch_set; } } if (passdb_continue && next_passdb != NULL) { /* try next passdb. */ request->passdb = next_passdb; request->passdb_password = NULL; if (*result == PASSDB_RESULT_USER_UNKNOWN) { /* remember that we did at least one successful passdb lookup */ request->passdbs_seen_user_unknown = TRUE; } else if (*result == PASSDB_RESULT_INTERNAL_FAILURE) { /* remember that we have had an internal failure. at the end return internal failure if we couldn't successfully login. */ request->passdbs_seen_internal_failure = TRUE; } return FALSE; } else if (*result == PASSDB_RESULT_NEXT) { /* admin forgot to put proper passdb last */ e_error(request->event, "%sLast passdb had noauthenticate field, cannot authenticate user", auth_request_get_log_prefix_db(request)); *result = PASSDB_RESULT_INTERNAL_FAILURE; } else if (request->passdb_success) { /* either this or a previous passdb lookup succeeded. */ *result = PASSDB_RESULT_OK; } else if (request->passdbs_seen_internal_failure) { /* last passdb lookup returned internal failure. it may have had the correct password, so return internal failure instead of plain failure. */ *result = PASSDB_RESULT_INTERNAL_FAILURE; } return TRUE; } void auth_request_verify_plain_callback_finish(enum passdb_result result, struct auth_request *request) { const char *error; if (passdb_template_export(request->passdb->override_fields_tmpl, request, &error) < 0) { e_error(authdb_event(request), "Failed to expand override_fields: %s", error); result = PASSDB_RESULT_INTERNAL_FAILURE; } if (!auth_request_handle_passdb_callback(&result, request)) { /* try next passdb */ auth_request_verify_plain(request, request->mech_password, request->private_callback.verify_plain); } else { auth_request_ref(request); request->passdb_result = result; request->private_callback.verify_plain(request->passdb_result, request); auth_request_unref(&request); } } void auth_request_verify_plain_callback(enum passdb_result result, struct auth_request *request) { struct auth_passdb *passdb = request->passdb; i_assert(request->state == AUTH_REQUEST_STATE_PASSDB); auth_request_set_state(request, AUTH_REQUEST_STATE_MECH_CONTINUE); if (result == PASSDB_RESULT_OK && auth_fields_exists(request->fields.extra_fields, "noauthenticate")) result = PASSDB_RESULT_NEXT; if (result != PASSDB_RESULT_INTERNAL_FAILURE) auth_request_save_cache(request, result); else { /* lookup failed. if we're looking here only because the request was expired in cache, fallback to using cached expired record. */ const char *cache_key = passdb->cache_key; auth_request_stats_add_tempfail(request); if (passdb_cache_verify_plain(request, cache_key, request->mech_password, &result, TRUE)) { e_info(authdb_event(request), "Falling back to expired data from cache"); return; } } auth_request_verify_plain_callback_finish(result, request); } static bool password_has_illegal_chars(const char *password) { for (; *password != '\0'; password++) { switch (*password) { case '\001': case '\t': case '\r': case '\n': /* these characters have a special meaning in internal protocols, make sure the password doesn't accidentally get there unescaped. */ return TRUE; } } return FALSE; } static bool auth_request_is_disabled_master_user(struct auth_request *request) { if (request->fields.requested_login_user == NULL || request->passdb != NULL) return FALSE; /* no masterdbs, master logins not supported */ e_info(request->mech_event, "Attempted master login with no master passdbs " "(trying to log in as user: %s)", request->fields.requested_login_user); return TRUE; } static void auth_request_policy_penalty_finish(void *context) { struct auth_policy_check_ctx *ctx = context; timeout_remove(&ctx->request->to_penalty); i_assert(ctx->request->state == AUTH_REQUEST_STATE_MECH_CONTINUE); switch(ctx->type) { case AUTH_POLICY_CHECK_TYPE_PLAIN: ctx->request->handler->verify_plain_continue_callback(ctx->request, ctx->callback_plain); return; case AUTH_POLICY_CHECK_TYPE_LOOKUP: auth_request_lookup_credentials_policy_continue(ctx->request, ctx->callback_lookup); return; case AUTH_POLICY_CHECK_TYPE_SUCCESS: auth_request_success_continue(ctx); return; default: i_unreached(); } } static void auth_request_policy_check_callback(int result, void *context) { struct auth_policy_check_ctx *ctx = context; ctx->request->policy_processed = TRUE; /* It's possible that multiple policy lookups return a penalty. Sum them all up to the event. */ ctx->request->policy_penalty += result < 0 ? 0 : result; if (ctx->request->set->policy_log_only && result != 0) { auth_request_policy_penalty_finish(context); return; } if (result < 0) { /* fail it right here and now */ auth_request_fail(ctx->request); } else if (ctx->type != AUTH_POLICY_CHECK_TYPE_SUCCESS && result > 0 && !ctx->request->fields.no_penalty) { ctx->request->to_penalty = timeout_add(result * 1000, auth_request_policy_penalty_finish, context); } else { auth_request_policy_penalty_finish(context); } } void auth_request_verify_plain(struct auth_request *request, const char *password, verify_plain_callback_t *callback) { struct auth_policy_check_ctx *ctx; i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE); if (request->mech_password == NULL) request->mech_password = p_strdup(request->pool, password); else i_assert(request->mech_password == password); request->user_changed_by_lookup = FALSE; if (request->policy_processed || !request->set->policy_check_before_auth) { request->handler->verify_plain_continue_callback(request, callback); } else { ctx = p_new(request->pool, struct auth_policy_check_ctx, 1); ctx->request = request; ctx->callback_plain = callback; ctx->type = AUTH_POLICY_CHECK_TYPE_PLAIN; auth_policy_check(request, request->mech_password, auth_request_policy_check_callback, ctx); } } void auth_request_default_verify_plain_continue(struct auth_request *request, verify_plain_callback_t *callback) { struct auth_passdb *passdb; enum passdb_result result; const char *cache_key, *error; const char *password = request->mech_password; i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE); if (auth_request_is_disabled_master_user(request)) { callback(PASSDB_RESULT_USER_UNKNOWN, request); return; } if (password_has_illegal_chars(password)) { e_info(authdb_event(request), "Attempted login with password having illegal chars"); callback(PASSDB_RESULT_USER_UNKNOWN, request); return; } passdb = request->passdb; while (passdb != NULL && auth_request_want_skip_passdb(request, passdb)) passdb = passdb->next; request->passdb = passdb; if (passdb == NULL) { auth_request_log_error(request, request->mech != NULL ? AUTH_SUBSYS_MECH : "none", "All password databases were skipped"); callback(PASSDB_RESULT_INTERNAL_FAILURE, request); return; } auth_request_passdb_lookup_begin(request); request->private_callback.verify_plain = callback; cache_key = passdb_cache == NULL ? NULL : passdb->cache_key; if (passdb_cache_verify_plain(request, cache_key, password, &result, FALSE)) { return; } auth_request_set_state(request, AUTH_REQUEST_STATE_PASSDB); /* In case this request had already done a credentials lookup (is it even possible?), make sure wanted_credentials_scheme is cleared so passdbs don't think we're doing a credentials lookup. */ request->wanted_credentials_scheme = NULL; if (passdb->passdb->iface.verify_plain == NULL) { /* we're deinitializing and just want to get rid of this request */ auth_request_verify_plain_callback( PASSDB_RESULT_INTERNAL_FAILURE, request); } else if (passdb->passdb->blocking) { passdb_blocking_verify_plain(request); } else if (passdb_template_export(passdb->default_fields_tmpl, request, &error) < 0) { e_error(authdb_event(request), "Failed to expand default_fields: %s", error); auth_request_verify_plain_callback( PASSDB_RESULT_INTERNAL_FAILURE, request); } else { passdb->passdb->iface.verify_plain(request, password, auth_request_verify_plain_callback); } } static void auth_request_lookup_credentials_finish(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *request) { const char *error; if (passdb_template_export(request->passdb->override_fields_tmpl, request, &error) < 0) { e_error(authdb_event(request), "Failed to expand override_fields: %s", error); result = PASSDB_RESULT_INTERNAL_FAILURE; } if (!auth_request_handle_passdb_callback(&result, request)) { /* try next passdb */ if (request->fields.skip_password_check && request->fields.delayed_credentials == NULL && size > 0) { /* passdb continue* rule after a successful lookup. remember these credentials and use them later on. */ auth_request_set_delayed_credentials(request, credentials, size); } auth_request_lookup_credentials(request, request->wanted_credentials_scheme, request->private_callback.lookup_credentials); } else { if (request->fields.delayed_credentials != NULL && size == 0) { /* we did multiple passdb lookups, but the last one didn't provide any credentials (e.g. just wanted to add some extra fields). so use the first passdb's credentials instead. */ credentials = request->fields.delayed_credentials; size = request->fields.delayed_credentials_size; } if (request->set->debug_passwords && result == PASSDB_RESULT_OK) { e_debug(authdb_event(request), "Credentials: %s", binary_to_hex(credentials, size)); } if (result == PASSDB_RESULT_SCHEME_NOT_AVAILABLE && request->passdbs_seen_user_unknown) { /* one of the passdbs accepted the scheme, but the user was unknown there */ result = PASSDB_RESULT_USER_UNKNOWN; } request->passdb_result = result; request->private_callback. lookup_credentials(result, credentials, size, request); } } void auth_request_lookup_credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *request) { struct auth_passdb *passdb = request->passdb; const char *cache_cred, *cache_scheme; i_assert(request->state == AUTH_REQUEST_STATE_PASSDB); auth_request_set_state(request, AUTH_REQUEST_STATE_MECH_CONTINUE); if (result == PASSDB_RESULT_OK && auth_fields_exists(request->fields.extra_fields, "noauthenticate")) result = PASSDB_RESULT_NEXT; if (result != PASSDB_RESULT_INTERNAL_FAILURE) auth_request_save_cache(request, result); else { /* lookup failed. if we're looking here only because the request was expired in cache, fallback to using cached expired record. */ const char *cache_key = passdb->cache_key; auth_request_stats_add_tempfail(request); if (passdb_cache_lookup_credentials(request, cache_key, &cache_cred, &cache_scheme, &result, TRUE)) { e_info(authdb_event(request), "Falling back to expired data from cache"); passdb_handle_credentials( result, cache_cred, cache_scheme, auth_request_lookup_credentials_finish, request); return; } } auth_request_lookup_credentials_finish(result, credentials, size, request); } void auth_request_lookup_credentials(struct auth_request *request, const char *scheme, lookup_credentials_callback_t *callback) { struct auth_policy_check_ctx *ctx; i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE); if (request->wanted_credentials_scheme == NULL) request->wanted_credentials_scheme = p_strdup(request->pool, scheme); request->user_changed_by_lookup = FALSE; if (request->policy_processed || !request->set->policy_check_before_auth) auth_request_lookup_credentials_policy_continue(request, callback); else { ctx = p_new(request->pool, struct auth_policy_check_ctx, 1); ctx->request = request; ctx->callback_lookup = callback; ctx->type = AUTH_POLICY_CHECK_TYPE_LOOKUP; auth_policy_check(request, ctx->request->mech_password, auth_request_policy_check_callback, ctx); } } static void auth_request_lookup_credentials_policy_continue(struct auth_request *request, lookup_credentials_callback_t *callback) { struct auth_passdb *passdb; const char *cache_key, *cache_cred, *cache_scheme, *error; enum passdb_result result; i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE); if (auth_request_is_disabled_master_user(request)) { callback(PASSDB_RESULT_USER_UNKNOWN, NULL, 0, request); return; } passdb = request->passdb; while (passdb != NULL && auth_request_want_skip_passdb(request, passdb)) passdb = passdb->next; request->passdb = passdb; if (passdb == NULL) { auth_request_log_error(request, request->mech != NULL ? AUTH_SUBSYS_MECH : "none", "All password databases were skipped"); callback(PASSDB_RESULT_INTERNAL_FAILURE, NULL, 0, request); return; } auth_request_passdb_lookup_begin(request); request->private_callback.lookup_credentials = callback; cache_key = passdb_cache == NULL ? NULL : passdb->cache_key; if (cache_key != NULL) { if (passdb_cache_lookup_credentials(request, cache_key, &cache_cred, &cache_scheme, &result, FALSE)) { request->passdb_cache_result = AUTH_REQUEST_CACHE_HIT; passdb_handle_credentials( result, cache_cred, cache_scheme, auth_request_lookup_credentials_finish, request); return; } else { request->passdb_cache_result = AUTH_REQUEST_CACHE_MISS; } } auth_request_set_state(request, AUTH_REQUEST_STATE_PASSDB); if (passdb->passdb->iface.lookup_credentials == NULL) { /* this passdb doesn't support credentials */ e_debug(authdb_event(request), "passdb doesn't support credential lookups"); auth_request_lookup_credentials_callback( PASSDB_RESULT_SCHEME_NOT_AVAILABLE, uchar_empty_ptr, 0, request); } else if (passdb->passdb->blocking) { passdb_blocking_lookup_credentials(request); } else if (passdb_template_export(passdb->default_fields_tmpl, request, &error) < 0) { e_error(authdb_event(request), "Failed to expand default_fields: %s", error); auth_request_lookup_credentials_callback( PASSDB_RESULT_INTERNAL_FAILURE, uchar_empty_ptr, 0, request); } else { passdb->passdb->iface.lookup_credentials(request, auth_request_lookup_credentials_callback); } } void auth_request_set_credentials(struct auth_request *request, const char *scheme, const char *data, set_credentials_callback_t *callback) { struct auth_passdb *passdb = request->passdb; const char *cache_key, *new_credentials; cache_key = passdb_cache == NULL ? NULL : passdb->cache_key; if (cache_key != NULL) auth_cache_remove(passdb_cache, request, cache_key); request->private_callback.set_credentials = callback; new_credentials = t_strdup_printf("{%s}%s", scheme, data); if (passdb->passdb->blocking) passdb_blocking_set_credentials(request, new_credentials); else if (passdb->passdb->iface.set_credentials != NULL) { passdb->passdb->iface.set_credentials(request, new_credentials, callback); } else { /* this passdb doesn't support credentials update */ callback(FALSE, request); } } static void auth_request_userdb_save_cache(struct auth_request *request, enum userdb_result result) { struct auth_userdb *userdb = request->userdb; string_t *str; const char *cache_value; if (passdb_cache == NULL || userdb->cache_key == NULL) return; if (result == USERDB_RESULT_USER_UNKNOWN) cache_value = ""; else { str = t_str_new(128); auth_fields_append(request->fields.userdb_reply, str, AUTH_FIELD_FLAG_CHANGED, AUTH_FIELD_FLAG_CHANGED); if (request->user_changed_by_lookup) { /* username was changed by passdb or userdb */ if (str_len(str) > 0) str_append_c(str, '\t'); str_append(str, "user="); str_append_tabescaped(str, request->fields.user); } if (str_len(str) == 0) { /* no userdb fields. but we can't save an empty string, since that means "user unknown". */ str_append(str, AUTH_REQUEST_USER_KEY_IGNORE); } cache_value = str_c(str); } /* last_success has no meaning with userdb */ auth_cache_insert(passdb_cache, request, userdb->cache_key, cache_value, FALSE); } static bool auth_request_lookup_user_cache(struct auth_request *request, const char *key, enum userdb_result *result_r, bool use_expired) { struct auth_stats *stats = auth_request_stats_get(request); const char *value; struct auth_cache_node *node; bool expired, neg_expired; value = auth_cache_lookup(passdb_cache, request, key, &node, &expired, &neg_expired); if (value == NULL || (expired && !use_expired)) { stats->auth_cache_miss_count++; request->userdb_cache_result = AUTH_REQUEST_CACHE_MISS; e_debug(request->event, value == NULL ? "%suserdb cache miss" : "%suserdb cache expired", auth_request_get_log_prefix_db(request)); return FALSE; } stats->auth_cache_hit_count++; request->userdb_cache_result = AUTH_REQUEST_CACHE_HIT; e_debug(request->event, "%suserdb cache hit: %s", auth_request_get_log_prefix_db(request), value); if (*value == '\0') { /* negative cache entry */ *result_r = USERDB_RESULT_USER_UNKNOWN; auth_request_init_userdb_reply(request, FALSE); return TRUE; } /* We want to preserve any userdb fields set by the earlier passdb lookup, so initialize userdb_reply only if it doesn't exist. Don't add userdb's default_fields, because the entire userdb part of the result comes from the cache. */ if (request->fields.userdb_reply == NULL) auth_request_init_userdb_reply(request, FALSE); auth_request_userdb_import(request, value); *result_r = USERDB_RESULT_OK; return TRUE; } void auth_request_userdb_callback(enum userdb_result result, struct auth_request *request) { struct auth_userdb *userdb = request->userdb; struct auth_userdb *next_userdb; enum auth_db_rule result_rule; const char *error; bool userdb_continue = FALSE; switch (result) { case USERDB_RESULT_OK: result_rule = userdb->result_success; break; case USERDB_RESULT_INTERNAL_FAILURE: auth_request_stats_add_tempfail(request); result_rule = userdb->result_internalfail; break; case USERDB_RESULT_USER_UNKNOWN: default: result_rule = userdb->result_failure; break; } switch (result_rule) { case AUTH_DB_RULE_RETURN: break; case AUTH_DB_RULE_RETURN_OK: request->userdb_success = TRUE; break; case AUTH_DB_RULE_RETURN_FAIL: request->userdb_success = FALSE; break; case AUTH_DB_RULE_CONTINUE: userdb_continue = TRUE; break; case AUTH_DB_RULE_CONTINUE_OK: userdb_continue = TRUE; request->userdb_success = TRUE; break; case AUTH_DB_RULE_CONTINUE_FAIL: userdb_continue = TRUE; request->userdb_success = FALSE; break; } auth_request_userdb_lookup_end(request, result); next_userdb = userdb->next; while (next_userdb != NULL && auth_request_want_skip_userdb(request, next_userdb)) next_userdb = next_userdb->next; if (userdb_continue && next_userdb != NULL) { /* try next userdb. */ if (result == USERDB_RESULT_INTERNAL_FAILURE) request->userdbs_seen_internal_failure = TRUE; if (result == USERDB_RESULT_OK) { /* this userdb lookup succeeded, preserve its extra fields */ if (userdb_template_export(userdb->override_fields_tmpl, request, &error) < 0) { e_error(request->event, "%sFailed to expand override_fields: %s", auth_request_get_log_prefix_db(request), error); request->private_callback.userdb( USERDB_RESULT_INTERNAL_FAILURE, request); return; } auth_fields_snapshot(request->fields.userdb_reply); } else { /* this userdb lookup failed, remove any extra fields it set */ auth_fields_rollback(request->fields.userdb_reply); } request->user_changed_by_lookup = FALSE; request->userdb = next_userdb; auth_request_lookup_user(request, request->private_callback.userdb); return; } if (request->userdb_success) { if (userdb_template_export(userdb->override_fields_tmpl, request, &error) < 0) { e_error(request->event, "%sFailed to expand override_fields: %s", auth_request_get_log_prefix_db(request), error); result = USERDB_RESULT_INTERNAL_FAILURE; } else { result = USERDB_RESULT_OK; } } else if (request->userdbs_seen_internal_failure || result == USERDB_RESULT_INTERNAL_FAILURE) { /* one of the userdb lookups failed. the user might have been in there, so this is an internal failure */ result = USERDB_RESULT_INTERNAL_FAILURE; } else if (request->client_pid != 0) { /* this was an actual login attempt, the user should have been found. */ if (auth_request_get_auth(request)->userdbs->next == NULL) { e_error(request->event, "%suser not found from userdb", auth_request_get_log_prefix_db(request)); } else { e_error(request->mech_event, "user not found from any userdbs"); } result = USERDB_RESULT_USER_UNKNOWN; } else { result = USERDB_RESULT_USER_UNKNOWN; } if (request->userdb_lookup_tempfailed) { /* no caching */ } else if (result != USERDB_RESULT_INTERNAL_FAILURE) { if (request->userdb_cache_result != AUTH_REQUEST_CACHE_HIT) auth_request_userdb_save_cache(request, result); } else if (passdb_cache != NULL && userdb->cache_key != NULL) { /* lookup failed. if we're looking here only because the request was expired in cache, fallback to using cached expired record. */ const char *cache_key = userdb->cache_key; if (auth_request_lookup_user_cache(request, cache_key, &result, TRUE)) { e_info(request->event, "%sFalling back to expired data from cache", auth_request_get_log_prefix_db(request)); } } request->private_callback.userdb(result, request); } void auth_request_lookup_user(struct auth_request *request, userdb_callback_t *callback) { struct auth_userdb *userdb = request->userdb; const char *cache_key, *error; request->private_callback.userdb = callback; request->user_changed_by_lookup = FALSE; request->userdb_lookup = TRUE; request->userdb_cache_result = AUTH_REQUEST_CACHE_NONE; if (request->fields.userdb_reply == NULL) auth_request_init_userdb_reply(request, TRUE); else { /* we still want to set default_fields. these override any existing fields set by previous userdbs (because if that is unwanted, ":protected" can be used). */ if (userdb_template_export(userdb->default_fields_tmpl, request, &error) < 0) { e_error(authdb_event(request), "Failed to expand default_fields: %s", error); auth_request_userdb_callback( USERDB_RESULT_INTERNAL_FAILURE, request); return; } } auth_request_userdb_lookup_begin(request); /* (for now) auth_cache is shared between passdb and userdb */ cache_key = passdb_cache == NULL ? NULL : userdb->cache_key; if (cache_key != NULL) { enum userdb_result result; if (auth_request_lookup_user_cache(request, cache_key, &result, FALSE)) { request->userdb_cache_result = AUTH_REQUEST_CACHE_HIT; auth_request_userdb_callback(result, request); return; } else { request->userdb_cache_result = AUTH_REQUEST_CACHE_MISS; } } if (userdb->userdb->iface->lookup == NULL) { /* we are deinitializing */ auth_request_userdb_callback(USERDB_RESULT_INTERNAL_FAILURE, request); } else if (userdb->userdb->blocking) userdb_blocking_lookup(request); else userdb->userdb->iface->lookup(request, auth_request_userdb_callback); } static void auth_request_validate_networks(struct auth_request *request, const char *name, const char *networks, const struct ip_addr *remote_ip) { const char *const *net; struct ip_addr net_ip; unsigned int bits; bool found = FALSE; for (net = t_strsplit_spaces(networks, ", "); *net != NULL; net++) { e_debug(authdb_event(request), "%s: Matching for network %s", name, *net); if (strcmp(*net, "local") == 0) { if (remote_ip->family == 0) { found = TRUE; break; } } else if (net_parse_range(*net, &net_ip, &bits) < 0) { e_info(authdb_event(request), "%s: Invalid network '%s'", name, *net); } else if (remote_ip->family != 0 && net_is_in_network(remote_ip, &net_ip, bits)) { found = TRUE; break; } } if (found) ; else if (remote_ip->family == 0) { e_info(authdb_event(request), "%s check failed: Remote IP not known and 'local' missing", name); } else { e_info(authdb_event(request), "%s check failed: IP %s not in allowed networks", name, net_ip2addr(remote_ip)); } if (!found) request->failed = TRUE; } static void auth_request_set_password(struct auth_request *request, const char *value, const char *default_scheme, bool noscheme) { if (request->passdb_password != NULL) { e_error(authdb_event(request), "Multiple password values not supported"); return; } /* if the password starts with '{' it most likely contains also '}'. check it anyway to make sure, because we assert-crash later if it doesn't exist. this could happen if plaintext passwords are used. */ if (*value == '{' && !noscheme && strchr(value, '}') != NULL) request->passdb_password = p_strdup(request->pool, value); else { i_assert(default_scheme != NULL); request->passdb_password = p_strdup_printf(request->pool, "{%s}%s", default_scheme, value); } } static const char * get_updated_username(const char *old_username, const char *name, const char *value) { const char *p; if (strcmp(name, "user") == 0) { /* replace the whole username */ return value; } p = strchr(old_username, '@'); if (strcmp(name, "username") == 0) { if (strchr(value, '@') != NULL) return value; /* preserve the current @domain */ return t_strconcat(value, p, NULL); } if (strcmp(name, "domain") == 0) { if (p == NULL) { /* add the domain */ return t_strconcat(old_username, "@", value, NULL); } else { /* replace the existing domain */ p = t_strdup_until(old_username, p + 1); return t_strconcat(p, value, NULL); } } return NULL; } static bool auth_request_try_update_username(struct auth_request *request, const char *name, const char *value) { const char *new_value; new_value = get_updated_username(request->fields.user, name, value); if (new_value == NULL) return FALSE; if (new_value[0] == '\0') { e_error(authdb_event(request), "username attempted to be changed to empty"); request->failed = TRUE; return TRUE; } if (strcmp(request->fields.user, new_value) != 0) { e_debug(authdb_event(request), "username changed %s -> %s", request->fields.user, new_value); auth_request_set_username_forced(request, new_value); request->user_changed_by_lookup = TRUE; } return TRUE; } static void auth_request_passdb_import(struct auth_request *request, const char *args, const char *key_prefix, const char *default_scheme) { const char *const *arg, *field; for (arg = t_strsplit(args, "\t"); *arg != NULL; arg++) { field = t_strconcat(key_prefix, *arg, NULL); auth_request_set_field_keyvalue(request, field, default_scheme); } } void auth_request_set_field(struct auth_request *request, const char *name, const char *value, const char *default_scheme) { size_t name_len = strlen(name); i_assert(*name != '\0'); i_assert(value != NULL); i_assert(request->passdb != NULL); if (name_len > 10 && strcmp(name+name_len-10, ":protected") == 0) { /* set this field only if it hasn't been set before */ name = t_strndup(name, name_len-10); if (auth_fields_exists(request->fields.extra_fields, name)) return; } else if (name_len > 7 && strcmp(name+name_len-7, ":remove") == 0) { /* remove this field entirely */ name = t_strndup(name, name_len-7); auth_fields_remove(request->fields.extra_fields, name); return; } if (strcmp(name, "password") == 0) { auth_request_set_password(request, value, default_scheme, FALSE); return; } if (strcmp(name, "password_noscheme") == 0) { auth_request_set_password(request, value, default_scheme, TRUE); return; } if (auth_request_try_update_username(request, name, value)) { /* don't change the original value so it gets saved correctly to cache. */ } else if (strcmp(name, "login_user") == 0) { auth_request_set_login_username_forced(request, value); } else if (strcmp(name, "allow_nets") == 0) { auth_request_validate_networks(request, name, value, &request->fields.remote_ip); } else if (strcmp(name, "fail") == 0) { request->failed = TRUE; } else if (strcmp(name, "delay_until") == 0) { time_t timestamp; unsigned int extra_secs = 0; const char *p; p = strchr(value, '+'); if (p != NULL) { value = t_strdup_until(value, p++); if (str_to_uint(p, &extra_secs) < 0) { e_error(authdb_event(request), "Invalid delay_until randomness number '%s'", p); request->failed = TRUE; } else { extra_secs = i_rand_limit(extra_secs); } } if (str_to_time(value, ×tamp) < 0) { e_error(authdb_event(request), "Invalid delay_until timestamp '%s'", value); request->failed = TRUE; } else if (timestamp <= ioloop_time) { /* no more delays */ } else if (timestamp - ioloop_time > AUTH_REQUEST_MAX_DELAY_SECS) { e_error(authdb_event(request), "delay_until timestamp %s is too much in the future, failing", value); request->failed = TRUE; } else { /* add randomness, but not too much of it */ timestamp += extra_secs; if (timestamp - ioloop_time > AUTH_REQUEST_MAX_DELAY_SECS) timestamp = ioloop_time + AUTH_REQUEST_MAX_DELAY_SECS; request->delay_until = timestamp; } } else if (strcmp(name, "allow_real_nets") == 0) { auth_request_validate_networks(request, name, value, &request->fields.real_remote_ip); } else if (str_begins(name, "userdb_")) { /* for prefetch userdb */ request->userdb_prefetch_set = TRUE; if (request->fields.userdb_reply == NULL) auth_request_init_userdb_reply(request, TRUE); if (strcmp(name, "userdb_userdb_import") == 0) { /* we can't put the whole userdb_userdb_import value to extra_cache_fields or it doesn't work properly. so handle this explicitly. */ auth_request_passdb_import(request, value, "userdb_", default_scheme); return; } auth_request_set_userdb_field(request, name + 7, value); } else if (strcmp(name, "noauthenticate") == 0) { /* add "nopassword" also so that passdbs won't try to verify the password. */ auth_fields_add(request->fields.extra_fields, name, value, 0); auth_fields_add(request->fields.extra_fields, "nopassword", NULL, 0); } else if (strcmp(name, "nopassword") == 0) { /* NULL password - anything goes */ const char *password = request->passdb_password; if (password != NULL && !auth_fields_exists(request->fields.extra_fields, "noauthenticate")) { (void)password_get_scheme(&password); if (*password != '\0') { e_error(authdb_event(request), "nopassword set but password is " "non-empty"); return; } } request->passdb_password = NULL; auth_fields_add(request->fields.extra_fields, name, value, 0); return; } else if (strcmp(name, "passdb_import") == 0) { auth_request_passdb_import(request, value, "", default_scheme); return; } else { /* these fields are returned to client */ auth_fields_add(request->fields.extra_fields, name, value, 0); return; } /* add the field unconditionally to extra_fields. this is required if a) auth cache is used, b) if we're a worker and we'll need to send this to the main auth process that can store it in the cache, c) for easily checking :protected fields' existence. */ auth_fields_add(request->fields.extra_fields, name, value, AUTH_FIELD_FLAG_HIDDEN); } void auth_request_set_null_field(struct auth_request *request, const char *name) { if (str_begins(name, "userdb_")) { /* make sure userdb prefetch is used even if all the fields were returned as NULL. */ request->userdb_prefetch_set = TRUE; } } void auth_request_set_field_keyvalue(struct auth_request *request, const char *field, const char *default_scheme) { const char *key, *value; value = strchr(field, '='); if (value == NULL) { key = field; value = ""; } else { key = t_strdup_until(field, value); value++; } auth_request_set_field(request, key, value, default_scheme); } void auth_request_set_fields(struct auth_request *request, const char *const *fields, const char *default_scheme) { for (; *fields != NULL; fields++) { if (**fields == '\0') continue; auth_request_set_field_keyvalue(request, *fields, default_scheme); } } static void auth_request_set_uidgid_file(struct auth_request *request, const char *path_template) { string_t *path; struct stat st; const char *error; path = t_str_new(256); if (auth_request_var_expand(path, path_template, request, NULL, &error) <= 0) { e_error(authdb_event(request), "Failed to expand uidgid_file=%s: %s", path_template, error); request->userdb_lookup_tempfailed = TRUE; } else if (stat(str_c(path), &st) < 0) { e_error(authdb_event(request), "stat(%s) failed: %m", str_c(path)); request->userdb_lookup_tempfailed = TRUE; } else { auth_fields_add(request->fields.userdb_reply, "uid", dec2str(st.st_uid), 0); auth_fields_add(request->fields.userdb_reply, "gid", dec2str(st.st_gid), 0); } } static void auth_request_userdb_import(struct auth_request *request, const char *args) { const char *key, *value, *const *arg; for (arg = t_strsplit(args, "\t"); *arg != NULL; arg++) { value = strchr(*arg, '='); if (value == NULL) { key = *arg; value = ""; } else { key = t_strdup_until(*arg, value); value++; } auth_request_set_userdb_field(request, key, value); } } void auth_request_set_userdb_field(struct auth_request *request, const char *name, const char *value) { size_t name_len = strlen(name); uid_t uid; gid_t gid; i_assert(value != NULL); if (name_len > 10 && strcmp(name+name_len-10, ":protected") == 0) { /* set this field only if it hasn't been set before */ name = t_strndup(name, name_len-10); if (auth_fields_exists(request->fields.userdb_reply, name)) return; } else if (name_len > 7 && strcmp(name+name_len-7, ":remove") == 0) { /* remove this field entirely */ name = t_strndup(name, name_len-7); auth_fields_remove(request->fields.userdb_reply, name); return; } if (strcmp(name, "uid") == 0) { uid = userdb_parse_uid(request, value); if (uid == (uid_t)-1) { request->userdb_lookup_tempfailed = TRUE; return; } value = dec2str(uid); } else if (strcmp(name, "gid") == 0) { gid = userdb_parse_gid(request, value); if (gid == (gid_t)-1) { request->userdb_lookup_tempfailed = TRUE; return; } value = dec2str(gid); } else if (strcmp(name, "tempfail") == 0) { request->userdb_lookup_tempfailed = TRUE; return; } else if (auth_request_try_update_username(request, name, value)) { return; } else if (strcmp(name, "uidgid_file") == 0) { auth_request_set_uidgid_file(request, value); return; } else if (strcmp(name, "userdb_import") == 0) { auth_request_userdb_import(request, value); return; } else if (strcmp(name, "system_user") == 0) { /* FIXME: the system_user is for backwards compatibility */ static bool warned = FALSE; if (!warned) { e_warning(authdb_event(request), "Replace system_user with system_groups_user"); warned = TRUE; } name = "system_groups_user"; } else if (strcmp(name, AUTH_REQUEST_USER_KEY_IGNORE) == 0) { return; } auth_fields_add(request->fields.userdb_reply, name, value, 0); } void auth_request_set_userdb_field_values(struct auth_request *request, const char *name, const char *const *values) { if (*values == NULL) return; if (strcmp(name, "gid") == 0) { /* convert gids to comma separated list */ string_t *value; gid_t gid; value = t_str_new(128); for (; *values != NULL; values++) { gid = userdb_parse_gid(request, *values); if (gid == (gid_t)-1) { request->userdb_lookup_tempfailed = TRUE; return; } if (str_len(value) > 0) str_append_c(value, ','); str_append(value, dec2str(gid)); } auth_fields_add(request->fields.userdb_reply, name, str_c(value), 0); } else { /* add only one */ if (values[1] != NULL) { e_warning(authdb_event(request), "Multiple values found for '%s', " "using value '%s'", name, *values); } auth_request_set_userdb_field(request, name, *values); } } static bool auth_request_proxy_is_self(struct auth_request *request) { const char *port = NULL; /* check if the port is the same */ port = auth_fields_find(request->fields.extra_fields, "port"); if (port != NULL && !str_uint_equals(port, request->fields.local_port)) return FALSE; /* don't check destuser. in some systems destuser is intentionally changed to proxied connections, but that shouldn't affect the proxying decision. it's unlikely any systems would actually want to proxy a connection to itself only to change the username, since it can already be done without proxying by changing the "user" field. */ return TRUE; } static bool auth_request_proxy_ip_is_self(struct auth_request *request, const struct ip_addr *ip) { unsigned int i; if (net_ip_compare(ip, &request->fields.real_local_ip)) return TRUE; for (i = 0; request->set->proxy_self_ips[i].family != 0; i++) { if (net_ip_compare(ip, &request->set->proxy_self_ips[i])) return TRUE; } return FALSE; } static void auth_request_proxy_finish_ip(struct auth_request *request, bool proxy_host_is_self) { const struct auth_request_fields *fields = &request->fields; if (!auth_fields_exists(fields->extra_fields, "proxy_maybe")) { /* proxying */ } else if (!proxy_host_is_self || !auth_request_proxy_is_self(request)) { /* proxy destination isn't ourself - proxy */ auth_fields_remove(fields->extra_fields, "proxy_maybe"); auth_fields_add(fields->extra_fields, "proxy", NULL, 0); } else { /* proxying to ourself - log in without proxying by dropping all the proxying fields. */ bool proxy_always = auth_fields_exists(fields->extra_fields, "proxy_always"); auth_request_proxy_finish_failure(request); if (proxy_always) { /* setup where "self" refers to the local director cluster, while "non-self" refers to remote clusters. we've matched self here, so add proxy field and let director fill the host. */ auth_fields_add(request->fields.extra_fields, "proxy", NULL, 0); } } } static void auth_request_proxy_dns_callback(const struct dns_lookup_result *result, struct auth_request_proxy_dns_lookup_ctx *ctx) { struct auth_request *request = ctx->request; const char *host; unsigned int i; bool proxy_host_is_self; request->dns_lookup_ctx = NULL; ctx->dns_lookup = NULL; host = auth_fields_find(request->fields.extra_fields, "host"); i_assert(host != NULL); if (result->ret != 0) { auth_request_log_error(request, AUTH_SUBSYS_PROXY, "DNS lookup for %s failed: %s", host, result->error); request->internal_failure = TRUE; auth_request_proxy_finish_failure(request); } else { if (result->msecs > AUTH_DNS_WARN_MSECS) { auth_request_log_warning(request, AUTH_SUBSYS_PROXY, "DNS lookup for %s took %u.%03u s", host, result->msecs/1000, result->msecs % 1000); } auth_fields_add(request->fields.extra_fields, "hostip", net_ip2addr(&result->ips[0]), 0); proxy_host_is_self = FALSE; for (i = 0; i < result->ips_count; i++) { if (auth_request_proxy_ip_is_self(request, &result->ips[i])) { proxy_host_is_self = TRUE; break; } } auth_request_proxy_finish_ip(request, proxy_host_is_self); } if (ctx->callback != NULL) ctx->callback(result->ret == 0, request); auth_request_unref(&request); } static int auth_request_proxy_host_lookup(struct auth_request *request, const char *host, auth_request_proxy_cb_t *callback) { struct auth_request_proxy_dns_lookup_ctx *ctx; struct dns_lookup_settings dns_set; const char *value; unsigned int secs; /* need to do dns lookup for the host */ i_zero(&dns_set); dns_set.dns_client_socket_path = AUTH_DNS_SOCKET_PATH; dns_set.timeout_msecs = AUTH_DNS_DEFAULT_TIMEOUT_MSECS; dns_set.event_parent = request->event; value = auth_fields_find(request->fields.extra_fields, "proxy_timeout"); if (value != NULL) { if (str_to_uint(value, &secs) < 0) { auth_request_log_error(request, AUTH_SUBSYS_PROXY, "Invalid proxy_timeout value: %s", value); } else { dns_set.timeout_msecs = secs*1000; } } ctx = p_new(request->pool, struct auth_request_proxy_dns_lookup_ctx, 1); ctx->request = request; auth_request_ref(request); request->dns_lookup_ctx = ctx; if (dns_lookup(host, &dns_set, auth_request_proxy_dns_callback, ctx, &ctx->dns_lookup) < 0) { /* failed early */ return -1; } ctx->callback = callback; return 0; } int auth_request_proxy_finish(struct auth_request *request, auth_request_proxy_cb_t *callback) { const char *host, *hostip; struct ip_addr ip; bool proxy_host_is_self; if (request->auth_only) return 1; if (!auth_fields_exists(request->fields.extra_fields, "proxy") && !auth_fields_exists(request->fields.extra_fields, "proxy_maybe")) return 1; host = auth_fields_find(request->fields.extra_fields, "host"); if (host == NULL) { /* director can set the host. give it access to lip and lport so it can also perform proxy_maybe internally */ proxy_host_is_self = FALSE; if (request->fields.local_ip.family != 0) { auth_fields_add(request->fields.extra_fields, "lip", net_ip2addr(&request->fields.local_ip), 0); } if (request->fields.local_port != 0) { auth_fields_add(request->fields.extra_fields, "lport", dec2str(request->fields.local_port), 0); } } else if (net_addr2ip(host, &ip) == 0) { proxy_host_is_self = auth_request_proxy_ip_is_self(request, &ip); } else { hostip = auth_fields_find(request->fields.extra_fields, "hostip"); if (hostip != NULL && net_addr2ip(hostip, &ip) < 0) { auth_request_log_error(request, AUTH_SUBSYS_PROXY, "Invalid hostip in passdb: %s", hostip); return -1; } if (hostip == NULL) { /* asynchronous host lookup */ return auth_request_proxy_host_lookup(request, host, callback); } proxy_host_is_self = auth_request_proxy_ip_is_self(request, &ip); } auth_request_proxy_finish_ip(request, proxy_host_is_self); return 1; } void auth_request_proxy_finish_failure(struct auth_request *request) { /* drop all proxying fields */ auth_fields_remove(request->fields.extra_fields, "proxy"); auth_fields_remove(request->fields.extra_fields, "proxy_maybe"); auth_fields_remove(request->fields.extra_fields, "proxy_always"); auth_fields_remove(request->fields.extra_fields, "host"); auth_fields_remove(request->fields.extra_fields, "port"); auth_fields_remove(request->fields.extra_fields, "destuser"); } static void log_password_failure(struct auth_request *request, const char *plain_password, const char *crypted_password, const char *scheme, const struct password_generate_params *params, const char *subsystem) { struct event *event = get_request_event(request, subsystem); static bool scheme_ok = FALSE; string_t *str = t_str_new(256); const char *working_scheme; str_printfa(str, "%s(%s) != '%s'", scheme, plain_password, crypted_password); if (!scheme_ok) { /* perhaps the scheme is wrong - see if we can find a working one */ working_scheme = password_scheme_detect(plain_password, crypted_password, params); if (working_scheme != NULL) { str_printfa(str, ", try %s scheme instead", working_scheme); } } e_debug(event, "%s", str_c(str)); } static void auth_request_append_password(struct auth_request *request, string_t *str) { const char *p, *log_type = request->set->verbose_passwords; unsigned int max_len = 1024; if (request->mech_password == NULL) return; p = strchr(log_type, ':'); if (p != NULL) { if (str_to_uint(p+1, &max_len) < 0) i_unreached(); log_type = t_strdup_until(log_type, p); } if (strcmp(log_type, "plain") == 0) { str_printfa(str, "(given password: %s)", t_strndup(request->mech_password, max_len)); } else if (strcmp(log_type, "sha1") == 0) { unsigned char sha1[SHA1_RESULTLEN]; sha1_get_digest(request->mech_password, strlen(request->mech_password), sha1); str_printfa(str, "(SHA1 of given password: %s)", t_strndup(binary_to_hex(sha1, sizeof(sha1)), max_len)); } else { i_unreached(); } } void auth_request_log_password_mismatch(struct auth_request *request, const char *subsystem) { auth_request_log_login_failure(request, subsystem, AUTH_LOG_MSG_PASSWORD_MISMATCH); } void auth_request_log_unknown_user(struct auth_request *request, const char *subsystem) { auth_request_log_login_failure(request, subsystem, "unknown user"); } void auth_request_log_login_failure(struct auth_request *request, const char *subsystem, const char *message) { struct event *event = get_request_event(request, subsystem); string_t *str; if (strcmp(request->set->verbose_passwords, "no") == 0) { e_info(event, "%s", message); return; } /* make sure this gets logged */ enum log_type orig_level = event_get_min_log_level(event); event_set_min_log_level(event, LOG_TYPE_INFO); str = t_str_new(128); str_append(str, message); str_append(str, " "); auth_request_append_password(request, str); if (request->userdb_lookup) { if (request->userdb->next != NULL) str_append(str, " - trying the next userdb"); } else { if (request->passdb->next != NULL) str_append(str, " - trying the next passdb"); } e_info(event, "%s", str_c(str)); event_set_min_log_level(event, orig_level); } enum passdb_result auth_request_password_verify(struct auth_request *request, const char *plain_password, const char *crypted_password, const char *scheme, const char *subsystem) { return auth_request_password_verify_log(request, plain_password, crypted_password, scheme, subsystem, TRUE); } enum passdb_result auth_request_password_verify_log(struct auth_request *request, const char *plain_password, const char *crypted_password, const char *scheme, const char *subsystem, bool log_password_mismatch) { enum passdb_result result; const unsigned char *raw_password; size_t raw_password_size; const char *error; int ret; struct password_generate_params gen_params = { .user = request->fields.original_username, .rounds = 0 }; if (request->fields.skip_password_check) { /* passdb continue* rule after a successful authentication */ return PASSDB_RESULT_OK; } if (request->passdb->set->deny) { /* this is a deny database, we don't care about the password */ return PASSDB_RESULT_PASSWORD_MISMATCH; } if (auth_fields_exists(request->fields.extra_fields, "nopassword")) { auth_request_log_debug(request, subsystem, "Allowing any password"); return PASSDB_RESULT_OK; } ret = password_decode(crypted_password, scheme, &raw_password, &raw_password_size, &error); if (ret <= 0) { if (ret < 0) { auth_request_log_error(request, subsystem, "Password data is not valid for scheme %s: %s", scheme, error); } else { auth_request_log_error(request, subsystem, "Unknown scheme %s", scheme); return PASSDB_RESULT_SCHEME_NOT_AVAILABLE; } return PASSDB_RESULT_INTERNAL_FAILURE; } /* Use original_username since it may be important for some password schemes (eg. digest-md5). Otherwise the username is used only for logging purposes. */ ret = password_verify(plain_password, &gen_params, scheme, raw_password, raw_password_size, &error); if (ret < 0) { const char *password_str = request->set->debug_passwords ? t_strdup_printf(" '%s'", crypted_password) : ""; auth_request_log_error(request, subsystem, "Invalid password%s in passdb: %s", password_str, error); result = PASSDB_RESULT_INTERNAL_FAILURE; } else if (ret == 0) { if (log_password_mismatch) auth_request_log_password_mismatch(request, subsystem); result = PASSDB_RESULT_PASSWORD_MISMATCH; } else { result = PASSDB_RESULT_OK; } if (ret <= 0 && request->set->debug_passwords) T_BEGIN { log_password_failure(request, plain_password, crypted_password, scheme, &gen_params, subsystem); } T_END; return result; } enum passdb_result auth_request_password_missing(struct auth_request *request) { if (request->fields.skip_password_check) { /* This passdb wasn't used for authentication */ return PASSDB_RESULT_OK; } e_info(authdb_event(request), "No password returned (and no nopassword)"); return PASSDB_RESULT_PASSWORD_MISMATCH; } void auth_request_get_log_prefix(string_t *str, struct auth_request *auth_request, const char *subsystem) { const char *name; if (subsystem == AUTH_SUBSYS_DB) { if (!auth_request->userdb_lookup) { i_assert(auth_request->passdb != NULL); name = auth_request->passdb->set->name[0] != '\0' ? auth_request->passdb->set->name : auth_request->passdb->passdb->iface.name; } else { i_assert(auth_request->userdb != NULL); name = auth_request->userdb->set->name[0] != '\0' ? auth_request->userdb->set->name : auth_request->userdb->userdb->iface->name; } } else if (subsystem == AUTH_SUBSYS_MECH) { i_assert(auth_request->mech != NULL); name = t_str_lcase(auth_request->mech->mech_name); } else { name = subsystem; } str_append(str, name); str_append_c(str, '('); get_log_identifier(str, auth_request); str_append(str, "): "); } #define MAX_LOG_USERNAME_LEN 64 static void get_log_identifier(string_t *str, struct auth_request *auth_request) { const char *ip; if (auth_request->fields.user == NULL) str_append(str, "?"); else str_sanitize_append(str, auth_request->fields.user, MAX_LOG_USERNAME_LEN); ip = net_ip2addr(&auth_request->fields.remote_ip); if (ip[0] != '\0') { str_append_c(str, ','); str_append(str, ip); } if (auth_request->fields.requested_login_user != NULL) str_append(str, ",master"); if (auth_request->fields.session_id != NULL) str_printfa(str, ",<%s>", auth_request->fields.session_id); } void auth_request_log_debug(struct auth_request *auth_request, const char *subsystem, const char *format, ...) { struct event *event = get_request_event(auth_request, subsystem); va_list va; va_start(va, format); T_BEGIN { string_t *str = t_str_new(128); str_vprintfa(str, format, va); e_debug(event, "%s", str_c(str)); } T_END; va_end(va); } void auth_request_log_info(struct auth_request *auth_request, const char *subsystem, const char *format, ...) { struct event *event = get_request_event(auth_request, subsystem); va_list va; va_start(va, format); T_BEGIN { string_t *str = t_str_new(128); str_vprintfa(str, format, va); e_info(event, "%s", str_c(str)); } T_END; va_end(va); } void auth_request_log_warning(struct auth_request *auth_request, const char *subsystem, const char *format, ...) { struct event *event = get_request_event(auth_request, subsystem); va_list va; va_start(va, format); T_BEGIN { string_t *str = t_str_new(128); str_vprintfa(str, format, va); e_warning(event, "%s", str_c(str)); } T_END; va_end(va); } void auth_request_log_error(struct auth_request *auth_request, const char *subsystem, const char *format, ...) { struct event *event = get_request_event(auth_request, subsystem); va_list va; va_start(va, format); T_BEGIN { string_t *str = t_str_new(128); str_vprintfa(str, format, va); e_error(event, "%s", str_c(str)); } T_END; va_end(va); } void auth_request_refresh_last_access(struct auth_request *request) { request->last_access = ioloop_time; if (request->to_abort != NULL) timeout_reset(request->to_abort); } dovecot-2.3.21.1/src/auth/auth-penalty.h0000644000000000000000000000163514656633576014636 00000000000000#ifndef AUTH_PENALTY_H #define AUTH_PENALTY_H struct auth_request; #define AUTH_PENALTY_INIT_SECS 2 #define AUTH_PENALTY_MAX_SECS 15 /* timeout specifies how long it takes for penalty to be irrelevant. */ #define AUTH_PENALTY_TIMEOUT \ (AUTH_PENALTY_INIT_SECS + 4 + 8 + AUTH_PENALTY_MAX_SECS) #define AUTH_PENALTY_MAX_PENALTY 4 /* If lookup failed, penalty and last_update are both zero */ typedef void auth_penalty_callback_t(unsigned int penalty, struct auth_request *request); struct auth_penalty *auth_penalty_init(const char *path); void auth_penalty_deinit(struct auth_penalty **penalty); unsigned int auth_penalty_to_secs(unsigned int penalty); void auth_penalty_lookup(struct auth_penalty *penalty, struct auth_request *auth_request, auth_penalty_callback_t *callback); void auth_penalty_update(struct auth_penalty *penalty, struct auth_request *auth_request, unsigned int value); #endif dovecot-2.3.21.1/src/auth/passdb.h0000644000000000000000000001001414656633576013466 00000000000000#ifndef PASSDB_H #define PASSDB_H #include "md5.h" #define IS_VALID_PASSWD(pass) \ ((pass)[0] != '\0' && (pass)[0] != '*' && (pass)[0] != '!') struct auth_request; struct auth_passdb_settings; enum passdb_result { PASSDB_RESULT_INTERNAL_FAILURE = -1, PASSDB_RESULT_SCHEME_NOT_AVAILABLE = -2, PASSDB_RESULT_USER_UNKNOWN = -3, PASSDB_RESULT_USER_DISABLED = -4, PASSDB_RESULT_PASS_EXPIRED = -5, PASSDB_RESULT_NEXT = -6, PASSDB_RESULT_PASSWORD_MISMATCH = 0, PASSDB_RESULT_OK = 1 }; typedef void verify_plain_callback_t(enum passdb_result result, struct auth_request *request); typedef void verify_plain_continue_callback_t(struct auth_request *request, verify_plain_callback_t *callback); typedef void lookup_credentials_callback_t(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *request); typedef void set_credentials_callback_t(bool success, struct auth_request *request); struct passdb_module_interface { const char *name; struct passdb_module *(*preinit)(pool_t pool, const char *args); void (*init)(struct passdb_module *module); void (*deinit)(struct passdb_module *module); /* Check if plaintext password matches */ void (*verify_plain)(struct auth_request *request, const char *password, verify_plain_callback_t *callback); /* Return authentication credentials, set in auth_request->credentials. */ void (*lookup_credentials)(struct auth_request *request, lookup_credentials_callback_t *callback); /* Update credentials */ void (*set_credentials)(struct auth_request *request, const char *new_credentials, set_credentials_callback_t *callback); }; struct passdb_module { const char *args; /* The default caching key for this module, or NULL if caching isn't wanted. This is updated by settings in auth_passdb. */ const char *default_cache_key; /* Default password scheme for this module. If default_cache_key is set, must not be NULL. */ const char *default_pass_scheme; /* Supported authentication mechanisms, NULL is all, [NULL] is none*/ const char *const *mechanisms; /* Username filter, NULL is no filter */ const char *const *username_filter; /* If blocking is set to TRUE, use child processes to access this passdb. */ bool blocking; /* id is used by blocking passdb to identify the passdb */ unsigned int id; /* number of time init() has been called */ int init_refcount; /* WARNING: avoid adding anything here that isn't based on args. if you do, you need to change passdb.c:passdb_find() also to avoid accidentally merging wrong passdbs. */ struct passdb_module_interface iface; }; const char *passdb_result_to_string(enum passdb_result result); /* Try to get credentials in wanted scheme (request->credentials_scheme) from given input. Returns FALSE if this wasn't possible (unknown scheme, conversion not possible or invalid credentials). If wanted scheme is "", the credentials are returned as-is without any checks. This is useful mostly just to see if there exist any credentials at all. */ bool passdb_get_credentials(struct auth_request *auth_request, const char *input, const char *input_scheme, const unsigned char **credentials_r, size_t *size_r); void passdb_handle_credentials(enum passdb_result result, const char *password, const char *scheme, lookup_credentials_callback_t *callback, struct auth_request *auth_request); struct passdb_module * passdb_preinit(pool_t pool, const struct auth_passdb_settings *set); void passdb_init(struct passdb_module *passdb); void passdb_deinit(struct passdb_module *passdb); void passdb_register_module(struct passdb_module_interface *iface); void passdb_unregister_module(struct passdb_module_interface *iface); void passdbs_generate_md5(unsigned char md5[STATIC_ARRAY MD5_RESULTLEN]); void passdbs_init(void); void passdbs_deinit(void); const char *passdb_oauth2_get_oidc_url(struct passdb_module *passdb); #include "auth-request.h" #endif dovecot-2.3.21.1/src/auth/passdb-ldap.c0000644000000000000000000003741214656633576014412 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "passdb.h" #if defined(PASSDB_LDAP) && (defined(BUILTIN_LDAP) || defined(PLUGIN_BUILD)) #include "ioloop.h" #include "array.h" #include "str.h" #include "password-scheme.h" #include "auth-cache.h" #include "db-ldap.h" #include struct ldap_passdb_module { struct passdb_module module; struct ldap_connection *conn; }; struct passdb_ldap_request { union { struct ldap_request ldap; struct ldap_request_search search; struct ldap_request_bind bind; } request; const char *dn; union { verify_plain_callback_t *verify_plain; lookup_credentials_callback_t *lookup_credentials; } callback; unsigned int entries; bool require_password; }; static void ldap_query_save_result(struct ldap_connection *conn, struct auth_request *auth_request, struct ldap_request_search *ldap_request, LDAPMessage *res) { struct db_ldap_result_iterate_context *ldap_iter; const char *name, *const *values; ldap_iter = db_ldap_result_iterate_init(conn, ldap_request, res, FALSE); while (db_ldap_result_iterate_next(ldap_iter, &name, &values)) { if (values[0] == NULL) { auth_request_set_null_field(auth_request, name); continue; } if (values[1] != NULL) { e_warning(authdb_event(auth_request), "Multiple values found for '%s', " "using value '%s'", name, values[0]); } auth_request_set_field(auth_request, name, values[0], conn->set.default_pass_scheme); } db_ldap_result_iterate_deinit(&ldap_iter); } static void ldap_lookup_finish(struct auth_request *auth_request, struct passdb_ldap_request *ldap_request, LDAPMessage *res) { enum passdb_result passdb_result; const char *password = NULL, *scheme; if (res == NULL) { passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; } else if (ldap_request->entries == 0) { passdb_result = PASSDB_RESULT_USER_UNKNOWN; auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB); } else if (ldap_request->entries > 1) { e_error(authdb_event(auth_request), "pass_filter matched multiple objects, aborting"); passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; } else if (auth_request->passdb_password == NULL && ldap_request->require_password && !auth_fields_exists(auth_request->fields.extra_fields, "nopassword")) { passdb_result = auth_request_password_missing(auth_request); } else { /* passdb_password may change on the way, so we'll need to strdup. */ password = t_strdup(auth_request->passdb_password); passdb_result = PASSDB_RESULT_OK; } scheme = password_get_scheme(&password); /* auth_request_set_field() sets scheme */ i_assert(password == NULL || scheme != NULL); if (auth_request->wanted_credentials_scheme != NULL) { passdb_handle_credentials(passdb_result, password, scheme, ldap_request->callback.lookup_credentials, auth_request); } else { if (password != NULL) { passdb_result = auth_request_password_verify(auth_request, auth_request->mech_password, password, scheme, AUTH_SUBSYS_DB); } ldap_request->callback.verify_plain(passdb_result, auth_request); } } static void ldap_lookup_pass_callback(struct ldap_connection *conn, struct ldap_request *request, LDAPMessage *res) { struct passdb_ldap_request *ldap_request = (struct passdb_ldap_request *)request; struct auth_request *auth_request = request->auth_request; if (res == NULL || ldap_msgtype(res) == LDAP_RES_SEARCH_RESULT) { ldap_lookup_finish(auth_request, ldap_request, res); auth_request_unref(&auth_request); return; } if (ldap_request->entries++ == 0) { /* first entry */ ldap_query_save_result(conn, auth_request, &ldap_request->request.search, res); } } static void ldap_auth_bind_callback(struct ldap_connection *conn, struct ldap_request *ldap_request, LDAPMessage *res) { struct passdb_ldap_request *passdb_ldap_request = (struct passdb_ldap_request *)ldap_request; struct auth_request *auth_request = ldap_request->auth_request; enum passdb_result passdb_result; int ret; passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; if (res != NULL) { ret = ldap_result2error(conn->ld, res, 0); if (ret == LDAP_SUCCESS) passdb_result = PASSDB_RESULT_OK; else if (ret == LDAP_INVALID_CREDENTIALS) { auth_request_log_login_failure(auth_request, AUTH_SUBSYS_DB, AUTH_LOG_MSG_PASSWORD_MISMATCH" (for LDAP bind)"); passdb_result = PASSDB_RESULT_PASSWORD_MISMATCH; } else if (ret == LDAP_NO_SUCH_OBJECT) { passdb_result = PASSDB_RESULT_USER_UNKNOWN; auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB); } else { e_error(authdb_event(auth_request), "ldap_bind() failed: %s", ldap_err2string(ret)); } } passdb_ldap_request->callback. verify_plain(passdb_result, auth_request); auth_request_unref(&auth_request); } static void ldap_auth_bind(struct ldap_connection *conn, struct ldap_request_bind *brequest) { struct passdb_ldap_request *passdb_ldap_request = (struct passdb_ldap_request *)brequest; struct auth_request *auth_request = brequest->request.auth_request; if (*auth_request->mech_password == '\0') { /* Assume that empty password fails. This is especially important with Windows 2003 AD, which always returns success with empty passwords. */ e_info(authdb_event(auth_request), "Login attempt with empty password"); passdb_ldap_request->callback. verify_plain(PASSDB_RESULT_PASSWORD_MISMATCH, auth_request); return; } brequest->request.callback = ldap_auth_bind_callback; db_ldap_request(conn, &brequest->request); } static void passdb_ldap_request_fail(struct passdb_ldap_request *request, enum passdb_result passdb_result) { struct auth_request *auth_request = request->request.ldap.auth_request; if (auth_request->wanted_credentials_scheme != NULL) { request->callback.lookup_credentials(passdb_result, NULL, 0, auth_request); } else { request->callback.verify_plain(passdb_result, auth_request); } auth_request_unref(&auth_request); } static void ldap_bind_lookup_dn_fail(struct auth_request *auth_request, struct passdb_ldap_request *request, LDAPMessage *res) { enum passdb_result passdb_result; if (res == NULL) passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; else if (request->entries == 0) { passdb_result = PASSDB_RESULT_USER_UNKNOWN; auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB); } else { i_assert(request->entries > 1); e_error(authdb_event(auth_request), "pass_filter matched multiple objects, aborting"); passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; } passdb_ldap_request_fail(request, passdb_result); } static void ldap_bind_lookup_dn_callback(struct ldap_connection *conn, struct ldap_request *ldap_request, LDAPMessage *res) { struct passdb_ldap_request *passdb_ldap_request = (struct passdb_ldap_request *)ldap_request; struct auth_request *auth_request = ldap_request->auth_request; struct passdb_ldap_request *brequest; char *dn; if (res != NULL && ldap_msgtype(res) == LDAP_RES_SEARCH_ENTRY) { if (passdb_ldap_request->entries++ > 0) { /* too many replies */ return; } /* first entry */ ldap_query_save_result(conn, auth_request, &passdb_ldap_request->request.search, res); /* save dn */ dn = ldap_get_dn(conn->ld, res); passdb_ldap_request->dn = p_strdup(auth_request->pool, dn); ldap_memfree(dn); } else if (res == NULL || passdb_ldap_request->entries != 1) { /* failure */ ldap_bind_lookup_dn_fail(auth_request, passdb_ldap_request, res); } else if (auth_request->fields.skip_password_check) { /* we've already verified that the password matched - we just wanted to get any extra fields */ passdb_ldap_request->callback. verify_plain(PASSDB_RESULT_OK, auth_request); auth_request_unref(&auth_request); } else { /* create a new bind request */ brequest = p_new(auth_request->pool, struct passdb_ldap_request, 1); brequest->dn = passdb_ldap_request->dn; brequest->callback = passdb_ldap_request->callback; brequest->request.bind.dn = brequest->dn; brequest->request.bind.request.type = LDAP_REQUEST_TYPE_BIND; brequest->request.bind.request.auth_request = auth_request; ldap_auth_bind(conn, &brequest->request.bind); } } static void ldap_lookup_pass(struct auth_request *auth_request, struct passdb_ldap_request *request, bool require_password) { struct passdb_module *_module = auth_request->passdb->passdb; struct ldap_passdb_module *module = (struct ldap_passdb_module *)_module; struct ldap_connection *conn = module->conn; struct ldap_request_search *srequest = &request->request.search; const char **attr_names = (const char **)conn->pass_attr_names; const char *error; string_t *str; request->require_password = require_password; srequest->request.type = LDAP_REQUEST_TYPE_SEARCH; str = t_str_new(512); if (auth_request_var_expand(str, conn->set.base, auth_request, ldap_escape, &error) <= 0) { e_error(authdb_event(auth_request), "Failed to expand base=%s: %s", conn->set.base, error); passdb_ldap_request_fail(request, PASSDB_RESULT_INTERNAL_FAILURE); return; } srequest->base = p_strdup(auth_request->pool, str_c(str)); str_truncate(str, 0); if (auth_request_var_expand(str, conn->set.pass_filter, auth_request, ldap_escape, &error) <= 0) { e_error(authdb_event(auth_request), "Failed to expand pass_filter=%s: %s", conn->set.pass_filter, error); passdb_ldap_request_fail(request, PASSDB_RESULT_INTERNAL_FAILURE); return; } srequest->filter = p_strdup(auth_request->pool, str_c(str)); srequest->attr_map = &conn->pass_attr_map; srequest->attributes = conn->pass_attr_names; e_debug(authdb_event(auth_request), "pass search: " "base=%s scope=%s filter=%s fields=%s", srequest->base, conn->set.scope, srequest->filter, attr_names == NULL ? "(all)" : t_strarray_join(attr_names, ",")); srequest->request.callback = ldap_lookup_pass_callback; db_ldap_request(conn, &srequest->request); } static void ldap_bind_lookup_dn(struct auth_request *auth_request, struct passdb_ldap_request *request) { struct passdb_module *_module = auth_request->passdb->passdb; struct ldap_passdb_module *module = (struct ldap_passdb_module *)_module; struct ldap_connection *conn = module->conn; struct ldap_request_search *srequest = &request->request.search; const char *error; string_t *str; srequest->request.type = LDAP_REQUEST_TYPE_SEARCH; str = t_str_new(512); if (auth_request_var_expand(str, conn->set.base, auth_request, ldap_escape, &error) <= 0) { e_error(authdb_event(auth_request), "Failed to expand base=%s: %s", conn->set.base, error); passdb_ldap_request_fail(request, PASSDB_RESULT_INTERNAL_FAILURE); return; } srequest->base = p_strdup(auth_request->pool, str_c(str)); str_truncate(str, 0); if (auth_request_var_expand(str, conn->set.pass_filter, auth_request, ldap_escape, &error) <= 0) { e_error(authdb_event(auth_request), "Failed to expand pass_filter=%s: %s", conn->set.pass_filter, error); passdb_ldap_request_fail(request, PASSDB_RESULT_INTERNAL_FAILURE); return; } srequest->filter = p_strdup(auth_request->pool, str_c(str)); /* we don't need the attributes to perform authentication, but they may contain some extra parameters. if a password is returned, it's just ignored. */ srequest->attr_map = &conn->pass_attr_map; srequest->attributes = conn->pass_attr_names; e_debug(authdb_event(auth_request), "bind search: base=%s filter=%s", srequest->base, srequest->filter); srequest->request.callback = ldap_bind_lookup_dn_callback; db_ldap_request(conn, &srequest->request); } static void ldap_verify_plain_auth_bind_userdn(struct auth_request *auth_request, struct passdb_ldap_request *request) { struct passdb_module *_module = auth_request->passdb->passdb; struct ldap_passdb_module *module = (struct ldap_passdb_module *)_module; struct ldap_connection *conn = module->conn; struct ldap_request_bind *brequest = &request->request.bind; string_t *dn; const char *error; brequest->request.type = LDAP_REQUEST_TYPE_BIND; dn = t_str_new(512); if (auth_request_var_expand(dn, conn->set.auth_bind_userdn, auth_request, ldap_escape, &error) <= 0) { e_error(authdb_event(auth_request), "Failed to expand auth_bind_userdn=%s: %s", conn->set.auth_bind_userdn, error); passdb_ldap_request_fail(request, PASSDB_RESULT_INTERNAL_FAILURE); return; } brequest->dn = p_strdup(auth_request->pool, str_c(dn)); ldap_auth_bind(conn, brequest); } static void ldap_verify_plain(struct auth_request *request, const char *password ATTR_UNUSED, verify_plain_callback_t *callback) { struct passdb_module *_module = request->passdb->passdb; struct ldap_passdb_module *module = (struct ldap_passdb_module *)_module; struct ldap_connection *conn = module->conn; struct passdb_ldap_request *ldap_request; /* reconnect if needed. this is also done by db_ldap_search(), but with auth binds we'll have to do it ourself */ if (db_ldap_connect(conn)< 0) { callback(PASSDB_RESULT_INTERNAL_FAILURE, request); return; } ldap_request = p_new(request->pool, struct passdb_ldap_request, 1); ldap_request->callback.verify_plain = callback; auth_request_ref(request); ldap_request->request.ldap.auth_request = request; if (!conn->set.auth_bind) ldap_lookup_pass(request, ldap_request, TRUE); else if (conn->set.auth_bind_userdn == NULL) ldap_bind_lookup_dn(request, ldap_request); else ldap_verify_plain_auth_bind_userdn(request, ldap_request); } static void ldap_lookup_credentials(struct auth_request *request, lookup_credentials_callback_t *callback) { struct passdb_module *_module = request->passdb->passdb; struct ldap_passdb_module *module = (struct ldap_passdb_module *)_module; struct passdb_ldap_request *ldap_request; bool require_password; ldap_request = p_new(request->pool, struct passdb_ldap_request, 1); ldap_request->callback.lookup_credentials = callback; auth_request_ref(request); ldap_request->request.ldap.auth_request = request; /* with auth_bind=yes we don't necessarily have a password. this will fail actual password credentials lookups, but it's fine for passdb lookups done by lmtp/doveadm */ require_password = !module->conn->set.auth_bind; ldap_lookup_pass(request, ldap_request, require_password); } static struct passdb_module * passdb_ldap_preinit(pool_t pool, const char *args) { struct ldap_passdb_module *module; struct ldap_connection *conn; module = p_new(pool, struct ldap_passdb_module, 1); module->conn = conn = db_ldap_init(args, FALSE); p_array_init(&conn->pass_attr_map, pool, 16); db_ldap_set_attrs(conn, conn->set.pass_attrs, &conn->pass_attr_names, &conn->pass_attr_map, conn->set.auth_bind ? "password" : NULL); module->module.blocking = conn->set.blocking; module->module.default_cache_key = auth_cache_parse_key(pool, t_strconcat(conn->set.base, conn->set.pass_attrs, conn->set.pass_filter, NULL)); module->module.default_pass_scheme = conn->set.default_pass_scheme; return &module->module; } static void passdb_ldap_init(struct passdb_module *_module) { struct ldap_passdb_module *module = (struct ldap_passdb_module *)_module; if (!module->module.blocking || worker) db_ldap_connect_delayed(module->conn); } static void passdb_ldap_deinit(struct passdb_module *_module) { struct ldap_passdb_module *module = (struct ldap_passdb_module *)_module; db_ldap_unref(&module->conn); } #ifndef PLUGIN_BUILD struct passdb_module_interface passdb_ldap = #else struct passdb_module_interface passdb_ldap_plugin = #endif { "ldap", passdb_ldap_preinit, passdb_ldap_init, passdb_ldap_deinit, ldap_verify_plain, ldap_lookup_credentials, NULL }; #else struct passdb_module_interface passdb_ldap = { .name = "ldap" }; #endif dovecot-2.3.21.1/src/auth/db-passwd-file.c0000644000000000000000000002675614656633576015032 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #if defined (USERDB_PASSWD_FILE) || defined(PASSDB_PASSWD_FILE) #include "userdb.h" #include "db-passwd-file.h" #include "array.h" #include "buffer.h" #include "istream.h" #include "hash.h" #include "str.h" #include "eacces-error.h" #include "ioloop.h" #include #include #include #include #define PARSE_TIME_STARTUP_WARN_SECS 60 #define PARSE_TIME_RELOAD_WARN_SECS 10 static struct db_passwd_file *passwd_files; static void ATTR_NULL(3) passwd_file_add(struct passwd_file *pw, const char *username, const char *pass, const char *const *args) { /* args = uid, gid, user info, home dir, shell, extra_fields */ struct passwd_user *pu; const char *extra_fields = NULL; char *user; size_t len; if (hash_table_lookup(pw->users, username) != NULL) { e_error(pw->event, "User %s exists more than once", username); return; } pu = p_new(pw->pool, struct passwd_user, 1); user = p_strdup(pw->pool, username); len = pass == NULL ? 0 : strlen(pass); if (len > 4 && pass[0] != '{' && pass[0] != '$' && pass[len-1] == ']' && pass[len-4] == '[') { /* password[type] - we're being libpam-pwdfile compatible here. it uses 13 = DES and 34 = MD5. For backwards compatibility with ourself, we have also 56 = Digest-MD5. */ int num = (pass[len-3] - '0') * 10 + (pass[len-2] - '0'); pass = t_strndup(pass, len-4); if (num == 34) { pu->password = p_strconcat(pw->pool, "{PLAIN-MD5}", pass, NULL); } else if (num == 56) { pu->password = p_strconcat(pw->pool, "{DIGEST-MD5}", pass, NULL); if (strlen(pu->password) != 32 + 12) { e_error(pw->event, "User %s " "has invalid password", username); return; } } else { pu->password = p_strconcat(pw->pool, "{CRYPT}", pass, NULL); } } else { pu->password = p_strdup(pw->pool, pass); } pu->uid = (uid_t)-1; pu->gid = (gid_t)-1; if (*args == NULL) ; else if (!pw->db->userdb || **args == '\0') { args++; } else { pu->uid = userdb_parse_uid(NULL, *args); if (pu->uid == 0 || pu->uid == (uid_t)-1) { e_error(pw->event, "User %s has invalid UID '%s'", username, *args); return; } args++; } if (*args == NULL) { if (pw->db->userdb_warn_missing) { e_error(pw->event, "User %s is missing userdb info", username); } /* don't allow userdb lookups */ pu->uid = 0; pu->gid = 0; } else if (!pw->db->userdb || **args == '\0') args++; else { pu->gid = userdb_parse_gid(NULL, *args); if (pu->gid == 0 || pu->gid == (gid_t)-1) { e_error(pw->event, "User %s has invalid GID '%s'", username, *args); return; } args++; } /* user info */ if (*args != NULL) args++; /* home */ if (*args != NULL) { if (pw->db->userdb) pu->home = p_strdup_empty(pw->pool, *args); args++; } /* shell */ if (*args != NULL) args++; if (*args != NULL && **args == '\0') { /* old format, this field is empty and next field may contain MAIL */ args++; if (*args != NULL && **args != '\0' && pw->db->userdb) { extra_fields = t_strconcat("userdb_mail=", t_strarray_join(args, ":"), NULL); } } else if (*args != NULL) { /* new format, contains a space separated list of extra fields */ extra_fields = t_strarray_join(args, ":"); } if (extra_fields != NULL) { pu->extra_fields = p_strsplit_spaces(pw->pool, extra_fields, " "); } hash_table_insert(pw->users, user, pu); } static struct passwd_file * passwd_file_new(struct db_passwd_file *db, const char *expanded_path) { struct passwd_file *pw; pw = i_new(struct passwd_file, 1); pw->db = db; pw->path = i_strdup(expanded_path); pw->fd = -1; pw->event = event_create(db->event); event_set_append_log_prefix(pw->event, t_strdup_printf("passwd-file %s:", pw->path)); if (hash_table_is_created(db->files)) hash_table_insert(db->files, pw->path, pw); return pw; } static int passwd_file_open(struct passwd_file *pw, bool startup, const char **error_r) { const char *no_args = NULL; struct istream *input; const char *line; struct stat st; time_t start_time, end_time; unsigned int time_secs; int fd; fd = open(pw->path, O_RDONLY); if (fd == -1) { if (errno == EACCES) *error_r = eacces_error_get("open", pw->path); else { *error_r = t_strdup_printf("open(%s) failed: %m", pw->path); } return -1; } if (fstat(fd, &st) != 0) { *error_r = t_strdup_printf("fstat(%s) failed: %m", pw->path); i_close_fd(&fd); return -1; } pw->fd = fd; pw->stamp = st.st_mtime; pw->size = st.st_size; pw->pool = pool_alloconly_create(MEMPOOL_GROWING"passwd_file", 10240); hash_table_create(&pw->users, pw->pool, 0, str_hash, strcmp); start_time = time(NULL); input = i_stream_create_fd(pw->fd, SIZE_MAX); i_stream_set_return_partial_line(input, TRUE); while ((line = i_stream_read_next_line(input)) != NULL) { if (*line == '\0' || *line == ':' || *line == '#') continue; /* no username or comment */ T_BEGIN { const char *const *args = t_strsplit(line, ":"); if (args[1] != NULL) { /* at least username+password */ passwd_file_add(pw, args[0], args[1], args+2); } else { /* only username */ passwd_file_add(pw, args[0], NULL, &no_args); } } T_END; } i_stream_destroy(&input); end_time = time(NULL); time_secs = end_time - start_time; if ((time_secs > PARSE_TIME_STARTUP_WARN_SECS && startup) || (time_secs > PARSE_TIME_RELOAD_WARN_SECS && !startup)) { e_warning(pw->event, "Reading %u users took %u secs", hash_table_count(pw->users), time_secs); } else { e_debug(pw->event, "Read %u users in %u secs", hash_table_count(pw->users), time_secs); } return 0; } static void passwd_file_close(struct passwd_file *pw) { i_close_fd_path(&pw->fd, pw->path); hash_table_destroy(&pw->users); pool_unref(&pw->pool); } static void passwd_file_free(struct passwd_file *pw) { if (hash_table_is_created(pw->db->files)) hash_table_remove(pw->db->files, pw->path); passwd_file_close(pw); event_unref(&pw->event); i_free(pw->path); i_free(pw); } static int passwd_file_sync(struct auth_request *request, struct passwd_file *pw) { struct stat st; const char *error; if (pw->last_sync_time == ioloop_time) return hash_table_is_created(pw->users) ? 1 : -1; pw->last_sync_time = ioloop_time; if (stat(pw->path, &st) < 0) { /* with variables don't give hard errors, or errors about nonexistent files */ int ret = -1; if (errno == EACCES) { e_error(authdb_event(request), "%s", eacces_error_get("stat", pw->path)); } else if (errno == ENOENT) { auth_request_log_info(request, "passwd-file", "missing passwd file: %s", pw->path); ret = 0; } else { e_error(authdb_event(request), "stat(%s) failed: %m", pw->path); } if (pw->db->default_file != pw) passwd_file_free(pw); return ret; } if (st.st_mtime != pw->stamp || st.st_size != pw->size) { passwd_file_close(pw); if (passwd_file_open(pw, FALSE, &error) < 0) { e_error(authdb_event(request), "%s", error); return -1; } } return 1; } static struct db_passwd_file *db_passwd_file_find(const char *path) { struct db_passwd_file *f; for (f = passwd_files; f != NULL; f = f->next) { if (strcmp(f->path, path) == 0) return f; } return NULL; } static void db_passwd_file_set_userdb(struct db_passwd_file *db) { db->userdb = TRUE; /* warn about missing userdb fields only when there aren't any other userdbs. */ db->userdb_warn_missing = array_is_created(&global_auth_settings->userdbs) && array_count(&global_auth_settings->userdbs) == 1; } struct db_passwd_file * db_passwd_file_init(const char *path, bool userdb, bool debug) { struct db_passwd_file *db; const char *p; bool percents = FALSE; db = db_passwd_file_find(path); if (db != NULL) { db->refcount++; if (userdb) db_passwd_file_set_userdb(db); return db; } db = i_new(struct db_passwd_file, 1); db->refcount = 1; if (userdb) db_passwd_file_set_userdb(db); db->event = event_create(auth_event); event_set_forced_debug(db->event, debug); for (p = path; *p != '\0'; p++) { if (*p == '%' && p[1] != '\0') { if (var_get_key(++p) == '%') percents = TRUE; else db->vars = TRUE; } } if (percents && !db->vars) { /* just extra escaped % chars. remove them. */ struct var_expand_table empty_table[1] = { { .key = '\0' }, }; string_t *dest; const char *error; dest = t_str_new(256); if (var_expand(dest, path, empty_table, &error) <= 0) i_unreached(); path = str_c(dest); } db->path = i_strdup(path); if (db->vars) { hash_table_create(&db->files, default_pool, 0, str_hash, strcmp); } else { db->default_file = passwd_file_new(db, path); } db->next = passwd_files; passwd_files = db; return db; } void db_passwd_file_parse(struct db_passwd_file *db) { const char *error; if (db->default_file != NULL && db->default_file->stamp == 0) { /* no variables, open the file immediately */ if (passwd_file_open(db->default_file, TRUE, &error) < 0) e_error(db->default_file->event, "%s", error); } } void db_passwd_file_unref(struct db_passwd_file **_db) { struct db_passwd_file *db = *_db; struct db_passwd_file **p; struct hash_iterate_context *iter; char *path; struct passwd_file *file; *_db = NULL; i_assert(db->refcount >= 0); if (--db->refcount > 0) return; for (p = &passwd_files; *p != NULL; p = &(*p)->next) { if (*p == db) { *p = db->next; break; } } if (db->default_file != NULL) passwd_file_free(db->default_file); else { iter = hash_table_iterate_init(db->files); while (hash_table_iterate(iter, db->files, &path, &file)) passwd_file_free(file); hash_table_iterate_deinit(&iter); hash_table_destroy(&db->files); } event_unref(&db->event); i_free(db->path); i_free(db); } static const char * path_fix(const char *path, const struct auth_request *auth_request ATTR_UNUSED) { const char *p; p = strchr(path, '/'); if (p == NULL) return path; /* most likely this is an invalid request. just cut off the '/' and everything after it. */ return t_strdup_until(path, p); } int db_passwd_file_lookup(struct db_passwd_file *db, struct auth_request *request, const char *username_format, struct passwd_user **user_r) { struct passwd_file *pw; string_t *username, *dest; const char *error; int ret; if (!db->vars) pw = db->default_file; else { dest = t_str_new(256); if (auth_request_var_expand(dest, db->path, request, path_fix, &error) <= 0) { e_error(authdb_event(request), "Failed to expand passwd-file path %s: %s", db->path, error); return -1; } pw = hash_table_lookup(db->files, str_c(dest)); if (pw == NULL) { /* doesn't exist yet. create lookup for it. */ pw = passwd_file_new(db, str_c(dest)); } } if ((ret = passwd_file_sync(request, pw)) <= 0) { /* pw may be freed now */ return ret; } username = t_str_new(256); if (auth_request_var_expand(username, username_format, request, auth_request_str_escape, &error) <= 0) { e_error(authdb_event(request), "Failed to expand username_format=%s: %s", username_format, error); return -1; } e_debug(authdb_event(request), "lookup: user=%s file=%s", str_c(username), pw->path); *user_r = hash_table_lookup(pw->users, str_c(username)); if (*user_r == NULL) { auth_request_log_unknown_user(request, AUTH_SUBSYS_DB); return 0; } return 1; } #endif dovecot-2.3.21.1/src/auth/mech-scram.h0000644000000000000000000000044014656633576014233 00000000000000#ifndef MECH_SCRAM_H #define MECH_SCRAM_H struct auth_request * mech_scram_auth_new(const struct hash_method *hash_method, const char *password_scheme); void mech_scram_auth_continue(struct auth_request *auth_request, const unsigned char *data, size_t data_size); #endif dovecot-2.3.21.1/src/auth/mech.h0000644000000000000000000000440014656633576013130 00000000000000#ifndef MECH_H #define MECH_H #include "auth-client-interface.h" struct auth_settings; struct auth_request; #include "auth-request.h" #include "auth-request-handler.h" /* Used only for string sanitization. */ #define MAX_MECH_NAME_LEN 64 enum mech_passdb_need { /* Mechanism doesn't need a passdb at all */ MECH_PASSDB_NEED_NOTHING = 0, /* Mechanism just needs to verify a given plaintext password */ MECH_PASSDB_NEED_VERIFY_PLAIN, /* Mechanism needs to verify a given challenge+response combination, i.e. there is only a single response from client. (Currently implemented the same as _LOOKUP_CREDENTIALS) */ MECH_PASSDB_NEED_VERIFY_RESPONSE, /* Mechanism needs to look up credentials with appropriate scheme */ MECH_PASSDB_NEED_LOOKUP_CREDENTIALS, /* Mechanism needs to look up credentials and also modify them */ MECH_PASSDB_NEED_SET_CREDENTIALS }; struct mech_module { const char *mech_name; enum mech_security_flags flags; enum mech_passdb_need passdb_need; struct auth_request *(*auth_new)(void); void (*auth_initial)(struct auth_request *request, const unsigned char *data, size_t data_size); void (*auth_continue)(struct auth_request *request, const unsigned char *data, size_t data_size); void (*auth_free)(struct auth_request *request); }; struct mech_module_list { struct mech_module_list *next; struct mech_module module; }; struct mechanisms_register { pool_t pool; const struct auth_settings *set; struct mech_module_list *modules; buffer_t *handshake; }; extern const struct mech_module mech_dovecot_token; void mech_register_module(const struct mech_module *module); void mech_unregister_module(const struct mech_module *module); const struct mech_module *mech_module_find(const char *name); void mech_generic_auth_initial(struct auth_request *request, const unsigned char *data, size_t data_size); void mech_generic_auth_free(struct auth_request *request); struct mechanisms_register * mech_register_init(const struct auth_settings *set); void mech_register_deinit(struct mechanisms_register **reg); const struct mech_module * mech_register_find(const struct mechanisms_register *reg, const char *name); void mech_init(const struct auth_settings *set); void mech_deinit(const struct auth_settings *set); #endif dovecot-2.3.21.1/src/auth/auth-stats.c0000644000000000000000000000557714656633576014324 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "stats.h" #include "stats-parser.h" #include "auth-stats.h" static struct stats_parser_field auth_stats_fields[] = { #define E(parsename, name, type) { parsename, offsetof(struct auth_stats, name), sizeof(((struct auth_stats *)0)->name), type } #define EN(parsename, name) E(parsename, name, STATS_PARSER_TYPE_UINT) EN("auth_successes", auth_success_count), EN("auth_master_successes", auth_master_success_count), EN("auth_failures", auth_failure_count), EN("auth_db_tempfails", auth_db_tempfail_count), EN("auth_cache_hits", auth_cache_hit_count), EN("auth_cache_misses", auth_cache_miss_count) }; static size_t auth_stats_alloc_size(void) { return sizeof(struct auth_stats); } static unsigned int auth_stats_field_count(void) { return N_ELEMENTS(auth_stats_fields); } static const char *auth_stats_field_name(unsigned int n) { i_assert(n < N_ELEMENTS(auth_stats_fields)); return auth_stats_fields[n].name; } static void auth_stats_field_value(string_t *str, const struct stats *stats, unsigned int n) { i_assert(n < N_ELEMENTS(auth_stats_fields)); stats_parser_value(str, &auth_stats_fields[n], stats); } static bool auth_stats_diff(const struct stats *stats1, const struct stats *stats2, struct stats *diff_stats_r, const char **error_r) { return stats_parser_diff(auth_stats_fields, N_ELEMENTS(auth_stats_fields), stats1, stats2, diff_stats_r, error_r); } static void auth_stats_add(struct stats *dest, const struct stats *src) { stats_parser_add(auth_stats_fields, N_ELEMENTS(auth_stats_fields), dest, src); } static bool auth_stats_have_changed(const struct stats *_prev, const struct stats *_cur) { return memcmp(_prev, _cur, sizeof(struct auth_stats)) != 0; } static void auth_stats_export(buffer_t *buf, const struct stats *_stats) { const struct auth_stats *stats = (const struct auth_stats *)_stats; buffer_append(buf, stats, sizeof(*stats)); } static bool auth_stats_import(const unsigned char *data, size_t size, size_t *pos_r, struct stats *_stats, const char **error_r) { struct auth_stats *stats = (struct auth_stats *)_stats; if (size < sizeof(*stats)) { *error_r = "auth_stats too small"; return FALSE; } memcpy(stats, data, sizeof(*stats)); *pos_r = sizeof(*stats); return TRUE; } const struct stats_vfuncs auth_stats_vfuncs = { "auth", auth_stats_alloc_size, auth_stats_field_count, auth_stats_field_name, auth_stats_field_value, auth_stats_diff, auth_stats_add, auth_stats_have_changed, auth_stats_export, auth_stats_import }; /* for the stats_auth plugin: */ void stats_auth_init(void); void stats_auth_deinit(void); static struct stats_item *auth_stats_item; void stats_auth_init(void) { auth_stats_item = stats_register(&auth_stats_vfuncs); } void stats_auth_deinit(void) { stats_unregister(&auth_stats_item); } dovecot-2.3.21.1/src/auth/test-mock.c0000644000000000000000000000477714656633576014136 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "test-auth.h" #include "auth-common.h" #include "passdb.h" struct auth_penalty *auth_penalty; time_t process_start_time; bool worker, worker_restart_request; static struct passdb_module *mock_passdb_mod = NULL; static pool_t mock_pool; void auth_module_load(const char *names ATTR_UNUSED) { } void auth_refresh_proctitle(void) { } static void passdb_mock_init(struct passdb_module *module ATTR_UNUSED) { } static void passdb_mock_deinit(struct passdb_module *module ATTR_UNUSED) { } static void passdb_mock_verify_plain(struct auth_request *request, const char *password ATTR_UNUSED, verify_plain_callback_t *callback) { callback(PASSDB_RESULT_OK, request); } static void passdb_mock_lookup_credentials(struct auth_request *request, lookup_credentials_callback_t *callback) { passdb_handle_credentials(PASSDB_RESULT_OK, "password", "PLAIN", callback, request); } static struct passdb_module_interface mock_interface = { .name = "mock", .init = passdb_mock_init, .deinit = passdb_mock_deinit, .verify_plain = passdb_mock_verify_plain, .lookup_credentials = passdb_mock_lookup_credentials, }; struct auth_passdb_settings mock_passdb_set = { .name = "mock", .driver = "mock", .args = "", .default_fields = "", .override_fields = "", .mechanisms = "", .username_filter = "", .skip = "never", .result_success = "return-ok", .result_failure = "continue", .result_internalfail = "continue", .deny = FALSE, .pass = FALSE, .master = FALSE, .auth_verbose = "default" }; void passdb_mock_mod_init(void) { if (mock_passdb_mod != NULL) return; mock_pool = pool_allocfree_create("auth mock"); passdb_register_module(&mock_interface); struct auth_passdb_settings set = { .name = "mock", .driver = "mock", .args = "", .default_fields = "", .override_fields = "", .mechanisms = "", .username_filter = "", .skip = "never", .result_success = "return-ok", .result_failure = "continue", .result_internalfail = "continue", .deny = FALSE, .pass = FALSE, .master = FALSE, .auth_verbose = "default" }; mock_passdb_mod = passdb_preinit(mock_pool, &set); passdb_init(mock_passdb_mod); } void passdb_mock_mod_deinit(void) { passdb_deinit(mock_passdb_mod); passdb_unregister_module(&mock_interface); pool_unref(&mock_pool); } struct auth_passdb *passdb_mock(void) { struct auth_passdb *ret = i_new(struct auth_passdb, 1); ret->set = &mock_passdb_set; ret->passdb = mock_passdb_mod; return ret; } dovecot-2.3.21.1/src/auth/userdb-sql.c0000644000000000000000000002042114656633576014271 00000000000000/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "userdb.h" #ifdef USERDB_SQL #include "auth-cache.h" #include "db-sql.h" #include struct sql_userdb_module { struct userdb_module module; struct db_sql_connection *conn; }; struct userdb_sql_request { struct auth_request *auth_request; userdb_callback_t *callback; }; struct sql_userdb_iterate_context { struct userdb_iterate_context ctx; struct sql_result *result; bool freed:1; bool call_iter:1; }; static void userdb_sql_iterate_next(struct userdb_iterate_context *_ctx); static int userdb_sql_iterate_deinit(struct userdb_iterate_context *_ctx); static void sql_query_get_result(struct sql_result *result, struct auth_request *auth_request) { const char *name, *value; unsigned int i, fields_count; fields_count = sql_result_get_fields_count(result); for (i = 0; i < fields_count; i++) { name = sql_result_get_field_name(result, i); value = sql_result_get_field_value(result, i); if (*name != '\0' && value != NULL) { auth_request_set_userdb_field(auth_request, name, value); } } } static void sql_query_callback(struct sql_result *sql_result, struct userdb_sql_request *sql_request) { struct auth_request *auth_request = sql_request->auth_request; struct userdb_module *_module = auth_request->userdb->userdb; struct sql_userdb_module *module = (struct sql_userdb_module *)_module; enum userdb_result result = USERDB_RESULT_INTERNAL_FAILURE; int ret; ret = sql_result_next_row(sql_result); if (ret >= 0) db_sql_success(module->conn); if (ret < 0) { if (!module->conn->default_user_query) { e_error(authdb_event(auth_request), "User query failed: %s", sql_result_get_error(sql_result)); } else { e_error(authdb_event(auth_request), "User query failed: %s " "(using built-in default user_query: %s)", sql_result_get_error(sql_result), module->conn->set.user_query); } } else if (ret == 0) { result = USERDB_RESULT_USER_UNKNOWN; auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB); } else { sql_query_get_result(sql_result, auth_request); result = USERDB_RESULT_OK; } sql_request->callback(result, auth_request); auth_request_unref(&auth_request); i_free(sql_request); } static const char * userdb_sql_escape(const char *str, const struct auth_request *auth_request) { struct userdb_module *_module = auth_request->userdb->userdb; struct sql_userdb_module *module = (struct sql_userdb_module *)_module; return sql_escape_string(module->conn->db, str); } static void userdb_sql_lookup(struct auth_request *auth_request, userdb_callback_t *callback) { struct userdb_module *_module = auth_request->userdb->userdb; struct sql_userdb_module *module = (struct sql_userdb_module *)_module; struct userdb_sql_request *sql_request; const char *query, *error; if (t_auth_request_var_expand(module->conn->set.user_query, auth_request, userdb_sql_escape, &query, &error) <= 0) { e_error(authdb_event(auth_request), "Failed to expand user_query=%s: %s", module->conn->set.user_query, error); callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request); return; } auth_request_ref(auth_request); sql_request = i_new(struct userdb_sql_request, 1); sql_request->callback = callback; sql_request->auth_request = auth_request; e_debug(authdb_event(auth_request), "%s", query); sql_query(module->conn->db, query, sql_query_callback, sql_request); } static void sql_iter_query_callback(struct sql_result *sql_result, struct sql_userdb_iterate_context *ctx) { ctx->result = sql_result; sql_result_ref(sql_result); if (ctx->freed) (void)userdb_sql_iterate_deinit(&ctx->ctx); else if (ctx->call_iter) userdb_sql_iterate_next(&ctx->ctx); } static struct userdb_iterate_context * userdb_sql_iterate_init(struct auth_request *auth_request, userdb_iter_callback_t *callback, void *context) { struct userdb_module *_module = auth_request->userdb->userdb; struct sql_userdb_module *module = (struct sql_userdb_module *)_module; struct sql_userdb_iterate_context *ctx; const char *query, *error; if (t_auth_request_var_expand(module->conn->set.iterate_query, auth_request, userdb_sql_escape, &query, &error) <= 0) { e_error(authdb_event(auth_request), "Failed to expand iterate_query=%s: %s", module->conn->set.iterate_query, error); } ctx = i_new(struct sql_userdb_iterate_context, 1); ctx->ctx.auth_request = auth_request; ctx->ctx.callback = callback; ctx->ctx.context = context; auth_request_ref(auth_request); sql_query(module->conn->db, query, sql_iter_query_callback, ctx); e_debug(authdb_event(auth_request), "%s", query); return &ctx->ctx; } static int userdb_sql_iterate_get_user(struct sql_userdb_iterate_context *ctx, const char **user_r) { const char *domain; int idx; /* try user first */ idx = sql_result_find_field(ctx->result, "user"); if (idx == 0) { *user_r = sql_result_get_field_value(ctx->result, idx); return 0; } /* username [+ domain]? */ idx = sql_result_find_field(ctx->result, "username"); if (idx < 0) { /* no user or username, fail */ return -1; } *user_r = sql_result_get_field_value(ctx->result, idx); if (*user_r == NULL) return 0; domain = sql_result_find_field_value(ctx->result, "domain"); if (domain != NULL) *user_r = t_strconcat(*user_r, "@", domain, NULL); return 0; } static void userdb_sql_iterate_next(struct userdb_iterate_context *_ctx) { struct sql_userdb_iterate_context *ctx = (struct sql_userdb_iterate_context *)_ctx; struct userdb_module *_module = _ctx->auth_request->userdb->userdb; struct sql_userdb_module *module = (struct sql_userdb_module *)_module; const char *user; int ret; if (ctx->result == NULL) { /* query not finished yet */ ctx->call_iter = TRUE; return; } ret = sql_result_next_row(ctx->result); if (ret >= 0) db_sql_success(module->conn); if (ret > 0) { if (userdb_sql_iterate_get_user(ctx, &user) < 0) e_error(authdb_event(_ctx->auth_request), "sql: Iterate query didn't return 'user' field"); else if (user == NULL) e_error(authdb_event(_ctx->auth_request), "sql: Iterate query returned NULL user"); else { _ctx->callback(user, _ctx->context); return; } _ctx->failed = TRUE; } else if (ret < 0) { if (!module->conn->default_iterate_query) { e_error(authdb_event(_ctx->auth_request), "sql: Iterate query failed: %s", sql_result_get_error(ctx->result)); } else { e_error(authdb_event(_ctx->auth_request), "sql: Iterate query failed: %s " "(using built-in default iterate_query: %s)", sql_result_get_error(ctx->result), module->conn->set.iterate_query); } _ctx->failed = TRUE; } _ctx->callback(NULL, _ctx->context); } static int userdb_sql_iterate_deinit(struct userdb_iterate_context *_ctx) { struct sql_userdb_iterate_context *ctx = (struct sql_userdb_iterate_context *)_ctx; int ret = _ctx->failed ? -1 : 0; auth_request_unref(&_ctx->auth_request); if (ctx->result == NULL) { /* sql query hasn't finished yet */ ctx->freed = TRUE; } else { if (ctx->result != NULL) sql_result_unref(ctx->result); i_free(ctx); } return ret; } static struct userdb_module * userdb_sql_preinit(pool_t pool, const char *args) { struct sql_userdb_module *module; module = p_new(pool, struct sql_userdb_module, 1); module->conn = db_sql_init(args, TRUE); module->module.default_cache_key = auth_cache_parse_key(pool, module->conn->set.user_query); return &module->module; } static void userdb_sql_init(struct userdb_module *_module) { struct sql_userdb_module *module = (struct sql_userdb_module *)_module; enum sql_db_flags flags; flags = sql_get_flags(module->conn->db); _module->blocking = (flags & SQL_DB_FLAG_BLOCKING) != 0; if (!_module->blocking || worker) db_sql_connect(module->conn); } static void userdb_sql_deinit(struct userdb_module *_module) { struct sql_userdb_module *module = (struct sql_userdb_module *)_module; db_sql_unref(&module->conn); } struct userdb_module_interface userdb_sql = { "sql", userdb_sql_preinit, userdb_sql_init, userdb_sql_deinit, userdb_sql_lookup, userdb_sql_iterate_init, userdb_sql_iterate_next, userdb_sql_iterate_deinit }; #else struct userdb_module_interface userdb_sql = { .name = "sql" }; #endif dovecot-2.3.21.1/src/auth/db-lua.c0000644000000000000000000005115114656633576013360 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #if defined(BUILTIN_LUA) || defined(PLUGIN_BUILD) #include "llist.h" #include "istream.h" #include "array.h" #include "sha1.h" #include "hex-binary.h" #include "strescape.h" #include "auth.h" #include "passdb.h" #include "userdb.h" #include "auth-request.h" #include "userdb-template.h" #include "passdb-template.h" #include "password-scheme.h" #include "auth-request-var-expand.h" #define AUTH_LUA_PASSDB_LOOKUP "auth_passdb_lookup" #define AUTH_LUA_USERDB_LOOKUP "auth_userdb_lookup" #define AUTH_LUA_USERDB_ITERATE "auth_userdb_iterate" #define AUTH_LUA_DOVECOT_AUTH "dovecot_auth" #define AUTH_LUA_AUTH_REQUEST "auth_request*" #include "db-lua.h" #include "dlua-script-private.h" struct auth_lua_userdb_iterate_context { struct userdb_iterate_context ctx; pool_t pool; unsigned int idx; ARRAY_TYPE(const_string) users; }; static struct auth_request * auth_lua_check_auth_request(lua_State *L, int arg); static int auth_request_lua_do_var_expand(struct auth_request *req, const char *tpl, const char **value_r, const char **error_r) { const char *error; if (t_auth_request_var_expand(tpl, req, NULL, value_r, &error) < 0) { *error_r = t_strdup_printf("var_expand(%s) failed: %s", tpl, error); return -1; } return 0; } static int auth_request_lua_var_expand(lua_State *L) { struct auth_request *req = auth_lua_check_auth_request(L, 1); const char *tpl = luaL_checkstring(L, 2); const char *value, *error; if (auth_request_lua_do_var_expand(req, tpl, &value, &error) < 0) { return luaL_error(L, "%s", error); } else { lua_pushstring(L, value); } return 1; } static const char *const * auth_request_template_build(struct auth_request *req, const char *str, unsigned int *count_r) { if (req->userdb_lookup) { struct userdb_template *tpl = userdb_template_build(pool_datastack_create(), "lua", str); if (userdb_template_is_empty(tpl)) return NULL; return userdb_template_get_args(tpl, count_r); } else { struct passdb_template *tpl = passdb_template_build(pool_datastack_create(), str); if (passdb_template_is_empty(tpl)) return NULL; return passdb_template_get_args(tpl, count_r); } } static int auth_request_lua_response_from_template(lua_State *L) { struct auth_request *req = auth_lua_check_auth_request(L, 1); const char *tplstr = luaL_checkstring(L, 2); const char *error,*expanded; unsigned int count,i; const char *const *fields = auth_request_template_build(req, tplstr, &count); /* push new table to stack */ lua_newtable(L); if (fields == NULL) return 1; i_assert((count % 2) == 0); for(i = 0; i < count; i+=2) { const char *key = fields[i]; const char *value = fields[i+1]; if (value == NULL) { lua_pushnil(L); } else if (auth_request_lua_do_var_expand(req, value, &expanded, &error) < 0) { return luaL_error(L, "%s", error); } else { lua_pushstring(L, expanded); } lua_setfield(L, -2, key); } /* stack should be left with table */ return 1; } static int auth_request_lua_log_debug(lua_State *L) { if (global_auth_settings->debug) { struct auth_request *request = auth_lua_check_auth_request(L, 1); const char *msg = luaL_checkstring(L, 2); e_debug(authdb_event(request), "db-lua: %s", msg); } return 0; } static int auth_request_lua_log_info(lua_State *L) { struct auth_request *request = auth_lua_check_auth_request(L, 1); const char *msg = luaL_checkstring(L, 2); e_info(authdb_event(request), "db-lua: %s", msg); return 0; } static int auth_request_lua_log_warning(lua_State *L) { struct auth_request *request = auth_lua_check_auth_request(L, 1); const char *msg = luaL_checkstring(L, 2); e_warning(authdb_event(request), "db-lua: %s", msg); return 0; } static int auth_request_lua_log_error(lua_State *L) { struct auth_request *request = auth_lua_check_auth_request(L, 1); const char *msg = luaL_checkstring(L, 2); e_error(authdb_event(request), "db-lua: %s", msg); return 0; } static int auth_request_lua_passdb(lua_State *L) { struct auth_request *request = auth_lua_check_auth_request(L, 1); const char *key = luaL_checkstring(L, 2); lua_pop(L, 1); if (request->fields.extra_fields == NULL) { lua_pushnil(L); return 1; } lua_pushstring(L, auth_fields_find(request->fields.extra_fields, key)); return 1; } static int auth_request_lua_userdb(lua_State *L) { struct auth_request *request = auth_lua_check_auth_request(L, 1); const char *key = luaL_checkstring(L, 2); lua_pop(L, 1); if (request->fields.userdb_reply == NULL) { lua_pushnil(L); return 1; } lua_pushstring(L, auth_fields_find(request->fields.userdb_reply, key)); return 1; } static int auth_request_lua_password_verify(lua_State *L) { struct auth_request *request = auth_lua_check_auth_request(L, 1); const char *crypted_password = lua_tostring(L, 2); const char *scheme; const char *plain_password = lua_tostring(L, 3); const char *error = NULL; const unsigned char *raw_password = NULL; size_t raw_password_size; int ret; struct password_generate_params gen_params = { .user = request->fields.original_username, .rounds = 0 }; scheme = password_get_scheme(&crypted_password); if (scheme == NULL) scheme = "PLAIN"; ret = password_decode(crypted_password, scheme, &raw_password, &raw_password_size, &error); if (ret <= 0) { if (ret < 0) { error = t_strdup_printf("Password data is not valid for scheme %s: %s", scheme, error); } else { error = t_strdup_printf("Unknown scheme %s", scheme); } } else { /* Use original_username since it may be important for some password schemes (eg. digest-md5). */ ret = password_verify(plain_password, &gen_params, scheme, raw_password, raw_password_size, &error); } lua_pushnumber(L, ret); lua_pushstring(L, error); return 2; } static int auth_request_lua_event(lua_State *L) { struct auth_request *request = auth_lua_check_auth_request(L, 1); struct event *event = event_create(authdb_event(request)); dlua_push_event(L, event); event_unref(&event); return 1; } /* put all methods here */ static const luaL_Reg auth_request_methods[] ={ { "var_expand", auth_request_lua_var_expand }, { "response_from_template", auth_request_lua_response_from_template }, { "log_debug", auth_request_lua_log_debug }, { "log_info", auth_request_lua_log_info }, { "log_warning", auth_request_lua_log_warning }, { "log_error", auth_request_lua_log_error }, { "password_verify", auth_request_lua_password_verify }, { "event", auth_request_lua_event }, { NULL, NULL } }; static int auth_request_lua_index(lua_State *L) { struct auth_request *req = auth_lua_check_auth_request(L, 1); const char *key = luaL_checkstring(L, 2); lua_pop(L, 1); const struct var_expand_table *table = auth_request_get_var_expand_table(req, NULL); /* check if it's variable */ for(unsigned int i = 0; i < AUTH_REQUEST_VAR_TAB_COUNT; i++) { if (null_strcmp(table[i].long_key, key) == 0) { lua_pushstring(L, table[i].value); return 1; } } /* check if it's function, then */ const luaL_Reg *ptr = auth_request_methods; while(ptr->name != NULL) { if (null_strcmp(key, ptr->name) == 0) { lua_pushcfunction(L, ptr->func); return 1; } ptr++; } lua_pushstring(L, key); lua_rawget(L, 1); return 1; } static void auth_lua_push_auth_request(lua_State *L, struct auth_request *req) { luaL_checkstack(L, 4, "out of memory"); /* create a table for holding few things */ lua_createtable(L, 0, 3); luaL_setmetatable(L, AUTH_LUA_AUTH_REQUEST); lua_pushlightuserdata(L, req); lua_setfield(L, -2, "item"); lua_newtable(L); lua_pushlightuserdata(L, req); lua_setfield(L, -2, "item"); luaL_setmetatable(L, "passdb_"AUTH_LUA_AUTH_REQUEST); lua_setfield(L, -2, "passdb"); lua_newtable(L); lua_pushlightuserdata(L, req); lua_setfield(L, -2, "item"); luaL_setmetatable(L, "userdb_"AUTH_LUA_AUTH_REQUEST); lua_setfield(L, -2, "userdb"); lua_pushboolean(L, req->fields.skip_password_check); lua_setfield(L, -2, "skip_password_check"); #undef LUA_TABLE_SET_BOOL #define LUA_TABLE_SET_BOOL(field) \ lua_pushboolean(L, req->field); \ lua_setfield(L, -2, #field); LUA_TABLE_SET_BOOL(passdbs_seen_user_unknown); LUA_TABLE_SET_BOOL(passdbs_seen_internal_failure); LUA_TABLE_SET_BOOL(userdbs_seen_internal_failure); } static struct auth_request * auth_lua_check_auth_request(lua_State *L, int arg) { if (!lua_istable(L, arg)) { (void)luaL_error(L, "Bad argument #%d, expected %s got %s", arg, "auth_request", lua_typename(L, lua_type(L, arg))); } lua_pushstring(L, "item"); lua_rawget(L, arg); void *bp = (void*)lua_touserdata(L, -1); lua_pop(L, 1); return (struct auth_request*)bp; } static void auth_lua_auth_request_register(lua_State *L) { luaL_newmetatable(L, AUTH_LUA_AUTH_REQUEST); lua_pushcfunction(L, auth_request_lua_index); lua_setfield(L, -2, "__index"); lua_pop(L, 1); /* register passdb */ luaL_newmetatable(L, "passdb_"AUTH_LUA_AUTH_REQUEST); lua_pushcfunction(L, auth_request_lua_passdb); lua_setfield(L, -2, "__index"); lua_pop(L, 1); /* register userdb */ luaL_newmetatable(L, "userdb_"AUTH_LUA_AUTH_REQUEST); lua_pushcfunction(L, auth_request_lua_userdb); lua_setfield(L, -2, "__index"); lua_pop(L, 1); } static struct dlua_table_values auth_lua_dovecot_auth_values[] = { DLUA_TABLE_ENUM(PASSDB_RESULT_INTERNAL_FAILURE), DLUA_TABLE_ENUM(PASSDB_RESULT_SCHEME_NOT_AVAILABLE), DLUA_TABLE_ENUM(PASSDB_RESULT_USER_UNKNOWN), DLUA_TABLE_ENUM(PASSDB_RESULT_USER_DISABLED), DLUA_TABLE_ENUM(PASSDB_RESULT_PASS_EXPIRED), DLUA_TABLE_ENUM(PASSDB_RESULT_NEXT), DLUA_TABLE_ENUM(PASSDB_RESULT_PASSWORD_MISMATCH), DLUA_TABLE_ENUM(PASSDB_RESULT_OK), DLUA_TABLE_ENUM(USERDB_RESULT_INTERNAL_FAILURE), DLUA_TABLE_ENUM(USERDB_RESULT_USER_UNKNOWN), DLUA_TABLE_ENUM(USERDB_RESULT_OK), DLUA_TABLE_END }; static luaL_Reg auth_lua_dovecot_auth_methods[] = { { NULL, NULL } }; static void auth_lua_dovecot_auth_register(lua_State *L) { dlua_get_dovecot(L); /* Create new table for holding values */ lua_newtable(L); /* register constants */ dlua_set_members(L, auth_lua_dovecot_auth_values, -1); /* push new metatable to stack */ luaL_newmetatable(L, AUTH_LUA_DOVECOT_AUTH); /* this will register functions to the metatable itself */ luaL_setfuncs(L, auth_lua_dovecot_auth_methods, 0); /* point __index to self */ lua_pushvalue(L, -1); lua_setfield(L, -1, "__index"); /* set table's metatable, pops stack */ lua_setmetatable(L, -2); /* put this as "dovecot.auth" */ lua_setfield(L, -2, "auth"); /* pop dovecot */ lua_pop(L, 1); } int auth_lua_script_init(struct dlua_script *script, const char **error_r) { dlua_dovecot_register(script); auth_lua_dovecot_auth_register(script->L); auth_lua_auth_request_register(script->L); return dlua_script_init(script, error_r); } static int auth_lua_call_lookup(lua_State *L, const char *fn, struct auth_request *req, const char **error_r) { int err = 0; e_debug(authdb_event(req), "Calling %s", fn); /* call with auth request as parameter */ auth_lua_push_auth_request(L, req); if (dlua_pcall(L, fn, 1, 2, error_r) < 0) return -1; if (!lua_isnumber(L, -2)) { *error_r = t_strdup_printf("db-lua: %s(req) invalid return value " "(expected number got %s)", fn, luaL_typename(L, -2)); err = -1; } else if (!lua_isstring(L, -1) && !lua_istable(L, -1)) { *error_r = t_strdup_printf("db-lua: %s(req) invalid return value " "(expected string or table, got %s)", fn, luaL_typename(L, -1)); err = -1; } if (err != 0) { lua_pop(L, 2); lua_gc(L, LUA_GCCOLLECT, 0); i_assert(lua_gettop(L) == 0); return PASSDB_RESULT_INTERNAL_FAILURE; } return 0; } static void auth_lua_export_fields(struct auth_request *req, const char *str, const char **scheme_r, const char **password_r) { const char *const *fields = t_strsplit_spaces(str, " "); while(*fields != NULL) { const char *value = strchr(*fields, '='); const char *key; if (value == NULL) { key = *fields; value = ""; } else { key = t_strdup_until(*fields, value++); } if (password_r != NULL && strcmp(key, "password") == 0) { *scheme_r = password_get_scheme(&value); *password_r = value; } else if (req->userdb_lookup) { auth_request_set_userdb_field(req, key, value); } else { auth_request_set_field(req, key, value, STATIC_PASS_SCHEME); } fields++; } } static void auth_lua_export_table(lua_State *L, struct auth_request *req, const char **scheme_r, const char **password_r) { lua_pushvalue(L, -1); lua_pushnil(L); while (lua_next(L, -2) != 0) { const char *key = t_strdup(lua_tostring(L, -2)); if (*key == '\0') { e_warning(authdb_event(req), "db-lua: Field key cannot be empty - ignoring"); lua_pop(L, 1); continue; } if (strpbrk(key, "\t\n\r") != NULL) { e_warning(authdb_event(req), "db-lua: Field key cannot contain , or - ignoring"); lua_pop(L, 1); continue; } const char *value; int type = lua_type(L, -1); switch(type) { case LUA_TNUMBER: value = dec2str(lua_tointeger(L, -1)); break; case LUA_TBOOLEAN: value = lua_toboolean(L, -1) ? "yes" : "no"; break; case LUA_TSTRING: value = t_strdup(lua_tostring(L, -1)); break; case LUA_TNIL: value = ""; break; default: e_warning(authdb_event(req), "db-lua: '%s' has invalid value type %s - ignoring", key, lua_typename(L, -1)); value = NULL; } if (value == NULL) { /* do not add */ } else if (password_r != NULL && strcmp(key, "password") == 0) { *scheme_r = password_get_scheme(&value); *password_r = value; } else if (req->userdb_lookup) { auth_request_set_userdb_field(req, key, value); } else { auth_request_set_field(req, key, value, STATIC_PASS_SCHEME); } lua_pop(L, 1); } /* stack has key table passdb_result */ lua_pop(L, 3); lua_gc(L, LUA_GCCOLLECT, 0); i_assert(lua_gettop(L) == 0); } static enum userdb_result auth_lua_export_userdb_table(lua_State *L, struct auth_request *req, const char **error_r) { enum userdb_result ret = lua_tointeger(L, -2); if (ret != USERDB_RESULT_OK) { lua_pop(L, 2); lua_gc(L, LUA_GCCOLLECT, 0); *error_r = "userdb failed"; return ret; } auth_lua_export_table(L, req, NULL, NULL); return USERDB_RESULT_OK; } static enum passdb_result auth_lua_export_passdb_table(lua_State *L, struct auth_request *req, const char **scheme_r, const char **password_r, const char **error_r) { enum passdb_result ret = lua_tointeger(L, -2); if (ret != PASSDB_RESULT_OK) { lua_pop(L, 2); lua_gc(L, LUA_GCCOLLECT, 0); *error_r = "passb failed"; return ret; } auth_lua_export_table(L, req, scheme_r, password_r); return PASSDB_RESULT_OK; } static enum passdb_result auth_lua_call_lookup_finish(lua_State *L, struct auth_request *req, const char **scheme_r, const char **password_r, const char **error_r) { if (lua_istable(L, -1)) { return auth_lua_export_passdb_table(L, req, scheme_r, password_r, error_r); } enum passdb_result ret = lua_tointeger(L, -2); const char *str = t_strdup(lua_tostring(L, -1)); lua_pop(L, 2); lua_gc(L, LUA_GCCOLLECT, 0); /* stack should be empty now */ i_assert(lua_gettop(L) == 0); if (ret != PASSDB_RESULT_OK && ret != PASSDB_RESULT_NEXT) { *error_r = str; } else { auth_lua_export_fields(req, str, scheme_r, password_r); } if (scheme_r != NULL && *scheme_r == NULL) *scheme_r = "PLAIN"; return ret; } enum passdb_result auth_lua_call_password_verify(struct dlua_script *script, struct auth_request *req, const char *password, const char **error_r) { lua_State *L = script->L; int err = 0; e_debug(authdb_event(req), "Calling %s", AUTH_LUA_PASSWORD_VERIFY); /* call with auth request, password as parameters */ auth_lua_push_auth_request(L, req); lua_pushstring(L, password); if (dlua_pcall(L, AUTH_LUA_PASSWORD_VERIFY, 2, 2, error_r) < 0) return PASSDB_RESULT_INTERNAL_FAILURE; if (!lua_isnumber(L, -2)) { *error_r = t_strdup_printf("db-lua: %s invalid return value " "(expected number got %s)", AUTH_LUA_PASSWORD_VERIFY, luaL_typename(L, -2)); err = -1; } else if (!lua_isstring(L, -1) && !lua_istable(L, -1)) { *error_r = t_strdup_printf("db-lua: %s invalid return value " "(expected string or table, got %s)", AUTH_LUA_PASSWORD_VERIFY, luaL_typename(L, -1)); err = -1; } if (err != 0) { lua_pop(L, 2); lua_gc(L, LUA_GCCOLLECT, 0); i_assert(lua_gettop(L) == 0); return PASSDB_RESULT_INTERNAL_FAILURE; } return auth_lua_call_lookup_finish(L, req, NULL, NULL, error_r); } enum passdb_result auth_lua_call_passdb_lookup(struct dlua_script *script, struct auth_request *req, const char **scheme_r, const char **password_r, const char **error_r) { lua_State *L = script->L; *scheme_r = *password_r = NULL; if (auth_lua_call_lookup(L, AUTH_LUA_PASSDB_LOOKUP, req, error_r) < 0) { lua_gc(L, LUA_GCCOLLECT, 0); i_assert(lua_gettop(L) == 0); return PASSDB_RESULT_INTERNAL_FAILURE; } return auth_lua_call_lookup_finish(L, req, scheme_r, password_r, error_r); } enum userdb_result auth_lua_call_userdb_lookup(struct dlua_script *script, struct auth_request *req, const char **error_r) { lua_State *L = script->L; if (auth_lua_call_lookup(L, AUTH_LUA_USERDB_LOOKUP, req, error_r) < 0) { lua_gc(L, LUA_GCCOLLECT, 0); i_assert(lua_gettop(L) == 0); return USERDB_RESULT_INTERNAL_FAILURE; } if (lua_istable(L, -1)) return auth_lua_export_userdb_table(L, req, error_r); enum userdb_result ret = lua_tointeger(L, -2); const char *str = t_strdup(lua_tostring(L, -1)); lua_pop(L, 2); lua_gc(L, LUA_GCCOLLECT, 0); i_assert(lua_gettop(L) == 0); if (ret != USERDB_RESULT_OK) { *error_r = str; return ret; } auth_lua_export_fields(req, str, NULL, NULL); return USERDB_RESULT_OK; } struct userdb_iterate_context * auth_lua_call_userdb_iterate_init(struct dlua_script *script, struct auth_request *req, userdb_iter_callback_t *callback, void *context) { lua_State *L = script->L; pool_t pool = pool_alloconly_create(MEMPOOL_GROWING"lua userdb iterate", 128); struct auth_lua_userdb_iterate_context *actx = p_new(pool, struct auth_lua_userdb_iterate_context, 1); actx->pool = pool; actx->ctx.auth_request = req; actx->ctx.callback = callback; actx->ctx.context = context; if (!dlua_script_has_function(script, AUTH_LUA_USERDB_ITERATE)) { actx->ctx.failed = TRUE; return &actx->ctx; } e_debug(authdb_event(req), "Calling %s", AUTH_LUA_USERDB_ITERATE); const char *error; if (dlua_pcall(L, AUTH_LUA_USERDB_ITERATE, 0, 1, &error) < 0) { e_error(authdb_event(req), "db-lua: " AUTH_LUA_USERDB_ITERATE " failed: %s", error); actx->ctx.failed = TRUE; return &actx->ctx; } if (!lua_istable(L, -1)) { e_error(authdb_event(req), "db-lua: Cannot iterate, return value is not table"); actx->ctx.failed = TRUE; lua_pop(L, 1); lua_gc(L, LUA_GCCOLLECT, 0); i_assert(lua_gettop(L) == 0); return &actx->ctx; } p_array_init(&actx->users, pool, 8); /* stack is now table */ /* see lua_next documentation */ lua_pushnil(L); while (lua_next(L, -2) != 0) { /* stack is now value key table */ if (!lua_isstring(L, -1)) { e_error(authdb_event(req), "db-lua: Value is not string"); actx->ctx.failed = TRUE; lua_pop(L, 3); lua_gc(L, LUA_GCCOLLECT, 0); i_assert(lua_gettop(L) == 0); return &actx->ctx; } const char *str = p_strdup(pool, lua_tostring(L, -1)); array_push_back(&actx->users, &str); lua_pop(L, 1); /* stack is now key table */ } /* stack is now table */ lua_pop(L, 1); lua_gc(L, LUA_GCCOLLECT, 0); i_assert(lua_gettop(L) == 0); return &actx->ctx; } void auth_lua_userdb_iterate_next(struct userdb_iterate_context *ctx) { struct auth_lua_userdb_iterate_context *actx = container_of(ctx, struct auth_lua_userdb_iterate_context, ctx); if (ctx->failed || actx->idx >= array_count(&actx->users)) { ctx->callback(NULL, ctx->context); return; } const char *user = array_idx_elem(&actx->users, actx->idx++); ctx->callback(user, ctx->context); } int auth_lua_userdb_iterate_deinit(struct userdb_iterate_context *ctx) { struct auth_lua_userdb_iterate_context *actx = container_of(ctx, struct auth_lua_userdb_iterate_context, ctx); int ret = ctx->failed ? -1 : 0; pool_unref(&actx->pool); return ret; } #ifndef BUILTIN_LUA /* Building a plugin */ extern struct passdb_module_interface passdb_lua_plugin; extern struct userdb_module_interface userdb_lua_plugin; void authdb_lua_init(void); void authdb_lua_deinit(void); void authdb_lua_init(void) { passdb_register_module(&passdb_lua_plugin); userdb_register_module(&userdb_lua_plugin); } void authdb_lua_deinit(void) { passdb_unregister_module(&passdb_lua_plugin); userdb_unregister_module(&userdb_lua_plugin); } #endif #endif dovecot-2.3.21.1/src/auth/auth-worker-server.c0000644000000000000000000003341714656633576015775 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "ioloop.h" #include "array.h" #include "aqueue.h" #include "net.h" #include "istream.h" #include "ostream.h" #include "hex-binary.h" #include "str.h" #include "eacces-error.h" #include "auth-request.h" #include "auth-worker-client.h" #include "auth-worker-server.h" #include /* Initial lookup timeout */ #define AUTH_WORKER_LOOKUP_TIMEOUT_SECS 60 /* Timeout for multi-line replies, e.g. listing users. This should be a much higher value, because e.g. doveadm could be doing some long-running commands for the users. And because of buffering this timeout is for handling multiple users, not just one. */ #define AUTH_WORKER_RESUME_TIMEOUT_SECS (30*60) #define AUTH_WORKER_MAX_IDLE_SECS (60*5) #define AUTH_WORKER_ABORT_SECS 60 #define AUTH_WORKER_DELAY_WARN_SECS 3 #define AUTH_WORKER_DELAY_WARN_MIN_INTERVAL_SECS 300 struct auth_worker_request { unsigned int id; time_t created; const char *username; const char *data; auth_worker_callback_t *callback; void *context; }; struct auth_worker_connection { int fd; struct event *event; struct io *io; struct istream *input; struct ostream *output; struct timeout *to; struct auth_worker_request *request; unsigned int id_counter; bool received_error:1; bool restart:1; bool shutdown:1; bool timeout_pending_resume:1; bool resuming:1; }; static ARRAY(struct auth_worker_connection *) connections = ARRAY_INIT; static unsigned int idle_count = 0, auth_workers_with_errors = 0; static ARRAY(struct auth_worker_request *) worker_request_array; static struct aqueue *worker_request_queue; static time_t auth_worker_last_warn; static unsigned int auth_workers_throttle_count; static const char *worker_socket_path; static void worker_input(struct auth_worker_connection *conn); static void auth_worker_destroy(struct auth_worker_connection **conn, const char *reason, bool restart) ATTR_NULL(2); static void auth_worker_idle_timeout(struct auth_worker_connection *conn) { i_assert(conn->request == NULL); if (idle_count > 1) auth_worker_destroy(&conn, NULL, FALSE); else timeout_reset(conn->to); } static void auth_worker_call_timeout(struct auth_worker_connection *conn) { i_assert(conn->request != NULL); auth_worker_destroy(&conn, "Lookup timed out", TRUE); } static bool auth_worker_request_send(struct auth_worker_connection *conn, struct auth_worker_request *request) { struct const_iovec iov[3]; unsigned int age_secs = ioloop_time - request->created; i_assert(conn->to != NULL); if (age_secs >= AUTH_WORKER_ABORT_SECS) { e_error(conn->event, "Aborting auth request that was queued for %d secs, " "%d left in queue", age_secs, aqueue_count(worker_request_queue)); request->callback(conn, t_strdup_printf( "FAIL\t%d", PASSDB_RESULT_INTERNAL_FAILURE), request->context); return FALSE; } if (age_secs >= AUTH_WORKER_DELAY_WARN_SECS && ioloop_time - auth_worker_last_warn > AUTH_WORKER_DELAY_WARN_MIN_INTERVAL_SECS) { auth_worker_last_warn = ioloop_time; e_error(conn->event, "Auth request was queued for %d " "seconds, %d left in queue " "(see auth_worker_max_count)", age_secs, aqueue_count(worker_request_queue)); } request->id = ++conn->id_counter; iov[0].iov_base = t_strdup_printf("%d\t", request->id); iov[0].iov_len = strlen(iov[0].iov_base); iov[1].iov_base = request->data; iov[1].iov_len = strlen(request->data); iov[2].iov_base = "\n"; iov[2].iov_len = 1; o_stream_nsendv(conn->output, iov, 3); i_assert(conn->request == NULL); conn->request = request; timeout_remove(&conn->to); conn->to = timeout_add(AUTH_WORKER_LOOKUP_TIMEOUT_SECS * 1000, auth_worker_call_timeout, conn); idle_count--; return TRUE; } static void auth_worker_request_send_next(struct auth_worker_connection *conn) { struct auth_worker_request *request; do { if (aqueue_count(worker_request_queue) == 0) return; request = array_idx_elem(&worker_request_array, aqueue_idx(worker_request_queue, 0)); aqueue_delete_tail(worker_request_queue); } while (!auth_worker_request_send(conn, request)); } static void auth_worker_send_handshake(struct auth_worker_connection *conn) { string_t *str; unsigned char passdb_md5[MD5_RESULTLEN]; unsigned char userdb_md5[MD5_RESULTLEN]; str = t_str_new(128); str_printfa(str, "VERSION\tauth-worker\t%u\t%u\n", AUTH_WORKER_PROTOCOL_MAJOR_VERSION, AUTH_WORKER_PROTOCOL_MINOR_VERSION); passdbs_generate_md5(passdb_md5); userdbs_generate_md5(userdb_md5); str_append(str, "DBHASH\t"); binary_to_hex_append(str, passdb_md5, sizeof(passdb_md5)); str_append_c(str, '\t'); binary_to_hex_append(str, userdb_md5, sizeof(userdb_md5)); str_append_c(str, '\n'); o_stream_nsend(conn->output, str_data(str), str_len(str)); } static struct auth_worker_connection *auth_worker_create(void) { struct auth_worker_connection *conn; struct event *event; int fd; if (array_count(&connections) >= auth_workers_throttle_count) return NULL; event = event_create(auth_event); event_set_append_log_prefix(event, "auth-worker: "); fd = net_connect_unix_with_retries(worker_socket_path, 5000); if (fd == -1) { if (errno == EACCES) { e_error(event, "%s", eacces_error_get("net_connect_unix", worker_socket_path)); } else { e_error(event, "net_connect_unix(%s) failed: %m", worker_socket_path); } event_unref(&event); return NULL; } conn = i_new(struct auth_worker_connection, 1); conn->fd = fd; conn->input = i_stream_create_fd(fd, AUTH_WORKER_MAX_LINE_LENGTH); conn->output = o_stream_create_fd(fd, SIZE_MAX); o_stream_set_no_error_handling(conn->output, TRUE); conn->io = io_add(fd, IO_READ, worker_input, conn); conn->to = timeout_add(AUTH_WORKER_MAX_IDLE_SECS * 1000, auth_worker_idle_timeout, conn); conn->event = event; auth_worker_send_handshake(conn); idle_count++; array_push_back(&connections, &conn); return conn; } static void auth_worker_destroy(struct auth_worker_connection **_conn, const char *reason, bool restart) { struct auth_worker_connection *conn = *_conn; struct auth_worker_connection *const *conns; unsigned int idx; *_conn = NULL; if (conn->received_error) { i_assert(auth_workers_with_errors > 0); i_assert(auth_workers_with_errors <= array_count(&connections)); auth_workers_with_errors--; } array_foreach(&connections, conns) { if (*conns == conn) { idx = array_foreach_idx(&connections, conns); array_delete(&connections, idx, 1); break; } } if (conn->request == NULL) idle_count--; if (conn->request != NULL) { e_error(conn->event, "Aborted %s request for %s: %s", t_strcut(conn->request->data, '\t'), conn->request->username, reason); conn->request->callback(conn, t_strdup_printf( "FAIL\t%d", PASSDB_RESULT_INTERNAL_FAILURE), conn->request->context); } io_remove(&conn->io); i_stream_destroy(&conn->input); o_stream_destroy(&conn->output); timeout_remove(&conn->to); if (close(conn->fd) < 0) e_error(conn->event, "close() failed: %m"); event_unref(&conn->event); i_free(conn); if (idle_count == 0 && restart) { conn = auth_worker_create(); if (conn != NULL) auth_worker_request_send_next(conn); } } static struct auth_worker_connection *auth_worker_find_free(void) { struct auth_worker_connection *conn; if (idle_count == 0) return NULL; array_foreach_elem(&connections, conn) { if (conn->request == NULL) return conn; } i_unreached(); return NULL; } static bool auth_worker_request_handle(struct auth_worker_connection *conn, struct auth_worker_request *request, const char *line) { if (str_begins(line, "*\t")) { /* multi-line reply, not finished yet */ if (conn->resuming) timeout_reset(conn->to); else { conn->resuming = TRUE; timeout_remove(&conn->to); conn->to = timeout_add(AUTH_WORKER_RESUME_TIMEOUT_SECS * 1000, auth_worker_call_timeout, conn); } } else { conn->resuming = FALSE; conn->request = NULL; conn->timeout_pending_resume = FALSE; timeout_remove(&conn->to); conn->to = timeout_add(AUTH_WORKER_MAX_IDLE_SECS * 1000, auth_worker_idle_timeout, conn); idle_count++; } if (!request->callback(conn, line, request->context) && conn->io != NULL) { conn->timeout_pending_resume = FALSE; timeout_remove(&conn->to); io_remove(&conn->io); return FALSE; } return TRUE; } static bool auth_worker_error(struct auth_worker_connection *conn) { if (conn->received_error) return TRUE; conn->received_error = TRUE; auth_workers_with_errors++; i_assert(auth_workers_with_errors <= array_count(&connections)); if (auth_workers_with_errors == 1) { /* this is the only failing auth worker connection. don't create new ones until this one sends SUCCESS. */ auth_workers_throttle_count = array_count(&connections); return TRUE; } /* too many auth workers, reduce them */ i_assert(array_count(&connections) > 1); if (auth_workers_throttle_count >= array_count(&connections)) auth_workers_throttle_count = array_count(&connections)-1; else if (auth_workers_throttle_count > 1) auth_workers_throttle_count--; auth_worker_destroy(&conn, "Internal auth worker failure", FALSE); return FALSE; } static void auth_worker_success(struct auth_worker_connection *conn) { unsigned int max_count = global_auth_settings->worker_max_count; if (!conn->received_error) return; i_assert(auth_workers_with_errors > 0); i_assert(auth_workers_with_errors <= array_count(&connections)); auth_workers_with_errors--; if (auth_workers_with_errors == 0) { /* all workers are succeeding now, set the limit back to original. */ auth_workers_throttle_count = max_count; } else if (auth_workers_throttle_count < max_count) auth_workers_throttle_count++; conn->received_error = FALSE; } static void worker_input(struct auth_worker_connection *conn) { const char *line, *id_str; unsigned int id; switch (i_stream_read(conn->input)) { case 0: return; case -1: /* disconnected */ auth_worker_destroy(&conn, "Worker process died unexpectedly", TRUE); return; case -2: /* buffer full */ e_error(conn->event, "BUG: Auth worker sent us more than %d bytes", (int)AUTH_WORKER_MAX_LINE_LENGTH); auth_worker_destroy(&conn, "Worker is buggy", TRUE); return; } while ((line = i_stream_next_line(conn->input)) != NULL) { if (strcmp(line, "RESTART") == 0) { conn->restart = TRUE; continue; } if (strcmp(line, "SHUTDOWN") == 0) { conn->shutdown = TRUE; continue; } if (strcmp(line, "ERROR") == 0) { if (!auth_worker_error(conn)) return; continue; } if (strcmp(line, "SUCCESS") == 0) { auth_worker_success(conn); continue; } id_str = line; line = strchr(line, '\t'); if (line == NULL || str_to_uint(t_strdup_until(id_str, line), &id) < 0) continue; if (conn->request != NULL && id == conn->request->id) { if (!auth_worker_request_handle(conn, conn->request, line + 1)) break; } else { if (conn->request != NULL) { e_error(conn->event, "BUG: Worker sent reply with id %u, " "expected %u", id, conn->request->id); } else { e_error(conn->event, "BUG: Worker sent reply with id %u, " "none was expected", id); } auth_worker_destroy(&conn, "Worker is buggy", TRUE); return; } } if (conn->request != NULL) { /* there's still a pending request */ } else if (conn->restart) auth_worker_destroy(&conn, "Max requests limit", TRUE); else if (conn->shutdown) auth_worker_destroy(&conn, "Idle kill", FALSE); else auth_worker_request_send_next(conn); } static void worker_input_resume(struct auth_worker_connection *conn) { conn->timeout_pending_resume = FALSE; timeout_remove(&conn->to); conn->to = timeout_add(AUTH_WORKER_RESUME_TIMEOUT_SECS * 1000, auth_worker_call_timeout, conn); worker_input(conn); } void auth_worker_call(pool_t pool, const char *username, const char *data, auth_worker_callback_t *callback, void *context) { struct auth_worker_connection *conn; struct auth_worker_request *request; request = p_new(pool, struct auth_worker_request, 1); request->created = ioloop_time; request->username = p_strdup(pool, username); request->data = p_strdup(pool, data); request->callback = callback; request->context = context; if (aqueue_count(worker_request_queue) > 0) { /* requests are already being queued, no chance of finding/creating a worker */ conn = NULL; } else { conn = auth_worker_find_free(); if (conn == NULL) { /* no free connections, create a new one */ conn = auth_worker_create(); } } if (conn != NULL) { if (!auth_worker_request_send(conn, request)) i_unreached(); } else { /* reached the limit, queue the request */ aqueue_append(worker_request_queue, &request); } } void auth_worker_server_resume_input(struct auth_worker_connection *conn) { if (conn->request == NULL) { /* request was just finished, don't try to resume it */ return; } if (conn->io == NULL) conn->io = io_add(conn->fd, IO_READ, worker_input, conn); if (!conn->timeout_pending_resume) { conn->timeout_pending_resume = TRUE; timeout_remove(&conn->to); conn->to = timeout_add_short(0, worker_input_resume, conn); } } void auth_worker_server_init(void) { worker_socket_path = "auth-worker"; auth_workers_throttle_count = global_auth_settings->worker_max_count; i_assert(auth_workers_throttle_count > 0); i_array_init(&worker_request_array, 128); worker_request_queue = aqueue_init(&worker_request_array.arr); i_array_init(&connections, 16); } void auth_worker_server_deinit(void) { struct auth_worker_connection **connp, *conn; while (array_count(&connections) > 0) { connp = array_front_modifiable(&connections); conn = *connp; auth_worker_destroy(&conn, "Shutting down", FALSE); } array_free(&connections); aqueue_deinit(&worker_request_queue); array_free(&worker_request_array); } dovecot-2.3.21.1/src/auth/mycrypt.h0000644000000000000000000000033114656633576013722 00000000000000#ifndef MYCRYPT_H #define MYCRYPT_H /* A simple wrapper to crypt(). Problem with it is that it requires _XOPEN_SOURCE define which breaks other things. */ char *mycrypt(const char *key, const char *salt); #endif dovecot-2.3.21.1/src/auth/db-sql.h0000644000000000000000000000163014656633576013400 00000000000000#ifndef DB_SQL_H #define DB_SQL_H #include "sql-api.h" struct db_sql_settings { const char *driver; const char *connect; const char *password_query; const char *user_query; const char *update_query; const char *iterate_query; const char *default_pass_scheme; bool userdb_warning_disable; }; struct db_sql_connection { struct db_sql_connection *next; pool_t pool; int refcount; char *config_path; struct db_sql_settings set; struct sql_db *db; bool default_password_query:1; bool default_user_query:1; bool default_update_query:1; bool default_iterate_query:1; bool userdb_used:1; }; struct db_sql_connection *db_sql_init(const char *config_path, bool userdb); void db_sql_unref(struct db_sql_connection **conn); void db_sql_connect(struct db_sql_connection *conn); void db_sql_success(struct db_sql_connection *conn); void db_sql_check_userdb_warning(struct db_sql_connection *conn); #endif dovecot-2.3.21.1/src/auth/passdb-blocking.h0000644000000000000000000000063414656633576015263 00000000000000#ifndef PASSDB_BLOCKING_H #define PASSDB_BLOCKING_H enum passdb_result passdb_blocking_auth_worker_reply_parse(struct auth_request *request, const char *reply); void passdb_blocking_verify_plain(struct auth_request *request); void passdb_blocking_lookup_credentials(struct auth_request *request); void passdb_blocking_set_credentials(struct auth_request *request, const char *new_credentials); #endif dovecot-2.3.21.1/src/auth/userdb-prefetch.c0000644000000000000000000000254414656633576015300 00000000000000/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "userdb.h" #ifdef USERDB_PREFETCH #include "str.h" #include "var-expand.h" static void prefetch_lookup(struct auth_request *auth_request, userdb_callback_t *callback) { /* auth_request_set_field() should have already placed the userdb_* values to userdb_reply. */ if (!auth_request->userdb_prefetch_set) { if (auth_request_get_auth(auth_request)->userdbs->next == NULL) { /* no other userdbs */ if (auth_request->userdb_lookup) { e_error(authdb_event(auth_request), "userdb lookup not possible with only userdb prefetch"); } else { e_error(authdb_event(auth_request), "passdb didn't return userdb entries"); } callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request); return; } /* more userdbs, they may know the user */ e_debug(authdb_event(auth_request), "passdb didn't return userdb entries, " "trying the next userdb"); callback(USERDB_RESULT_USER_UNKNOWN, auth_request); return; } e_debug(authdb_event(auth_request), "success"); callback(USERDB_RESULT_OK, auth_request); } struct userdb_module_interface userdb_prefetch = { "prefetch", NULL, NULL, NULL, prefetch_lookup, NULL, NULL, NULL }; #else struct userdb_module_interface userdb_prefetch = { .name = "prefetch" }; #endif dovecot-2.3.21.1/src/auth/auth-request-handler-private.h0000644000000000000000000000124114656633576017726 00000000000000#ifndef AUTH_REQUEST_HANDLER_PRIVATE_H #define AUTH_REQUEST_HANDLER_PRIVATE_H struct auth_request; struct auth_client_connection; struct auth_request_handler { int refcount; pool_t pool; HASH_TABLE(void *, struct auth_request *) requests; unsigned int connect_uid, client_pid; auth_client_request_callback_t *callback; struct auth_client_connection *conn; auth_master_request_callback_t *master_callback; auth_request_handler_reply_callback_t *reply_callback; auth_request_handler_reply_continue_callback_t *reply_continue_callback; verify_plain_continue_callback_t *verify_plain_continue_callback; bool destroyed:1; bool token_auth:1; }; #endif dovecot-2.3.21.1/src/auth/userdb-dict.c0000644000000000000000000001270514656633576014423 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "userdb.h" #include "ioloop.h" #include "array.h" #include "str.h" #include "auth-cache.h" #include "db-dict.h" #include struct dict_userdb_module { struct userdb_module module; struct dict_connection *conn; }; struct dict_userdb_iterate_context { struct userdb_iterate_context ctx; userdb_callback_t *userdb_callback; const char *key_prefix; size_t key_prefix_len; struct dict_iterate_context *iter; }; static int dict_query_save_results(struct auth_request *auth_request, struct db_dict_value_iter *iter) { const char *key, *value, *error; while (db_dict_value_iter_next(iter, &key, &value)) { if (value != NULL) auth_request_set_userdb_field(auth_request, key, value); } if (db_dict_value_iter_deinit(&iter, &error) < 0) { e_error(authdb_event(auth_request), "%s", error); return -1; } return 0; } static void userdb_dict_lookup(struct auth_request *auth_request, userdb_callback_t *callback) { struct userdb_module *_module = auth_request->userdb->userdb; struct dict_userdb_module *module = (struct dict_userdb_module *)_module; struct db_dict_value_iter *iter; enum userdb_result userdb_result; int ret; if (array_count(&module->conn->set.userdb_fields) == 0 && array_count(&module->conn->set.parsed_userdb_objects) == 0) { e_error(authdb_event(auth_request), "No userdb_objects or userdb_fields specified"); callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request); return; } ret = db_dict_value_iter_init(module->conn, auth_request, &module->conn->set.userdb_fields, &module->conn->set.parsed_userdb_objects, &iter); if (ret < 0) userdb_result = USERDB_RESULT_INTERNAL_FAILURE; else if (ret == 0) { auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB); userdb_result = USERDB_RESULT_USER_UNKNOWN; } else { if (dict_query_save_results(auth_request, iter) < 0) userdb_result = USERDB_RESULT_INTERNAL_FAILURE; else userdb_result = USERDB_RESULT_OK; } callback(userdb_result, auth_request); } static struct userdb_iterate_context * userdb_dict_iterate_init(struct auth_request *auth_request, userdb_iter_callback_t *callback, void *context) { struct userdb_module *_module = auth_request->userdb->userdb; struct dict_userdb_module *module = (struct dict_userdb_module *)_module; struct dict_userdb_iterate_context *ctx; string_t *path; const char *error; ctx = i_new(struct dict_userdb_iterate_context, 1); ctx->ctx.auth_request = auth_request; ctx->ctx.callback = callback; ctx->ctx.context = context; auth_request_ref(auth_request); if (*module->conn->set.iterate_prefix == '\0') { if (!module->conn->set.iterate_disable) { e_error(authdb_event(auth_request), "iterate: iterate_prefix not set"); ctx->ctx.failed = TRUE; } return &ctx->ctx; } path = t_str_new(128); str_append(path, DICT_PATH_SHARED); if (auth_request_var_expand(path, module->conn->set.iterate_prefix, auth_request, NULL, &error) <= 0) { e_error(authdb_event(auth_request), "Failed to expand iterate_prefix=%s: %s", module->conn->set.iterate_prefix, error); ctx->ctx.failed = TRUE; return &ctx->ctx; } ctx->key_prefix = p_strdup(auth_request->pool, str_c(path)); ctx->key_prefix_len = strlen(ctx->key_prefix); struct dict_op_settings set = { .username = auth_request->fields.user, }; ctx->iter = dict_iterate_init(module->conn->dict, &set, ctx->key_prefix, 0); e_debug(authdb_event(auth_request), "iterate: prefix=%s", ctx->key_prefix); return &ctx->ctx; } static const char * userdb_dict_get_user(struct dict_userdb_iterate_context *ctx, const char *key) { i_assert(strncmp(key, ctx->key_prefix, ctx->key_prefix_len) == 0); return key + ctx->key_prefix_len; } static void userdb_dict_iterate_next(struct userdb_iterate_context *_ctx) { struct dict_userdb_iterate_context *ctx = (struct dict_userdb_iterate_context *)_ctx; const char *key, *value; if (ctx->iter != NULL && dict_iterate(ctx->iter, &key, &value)) _ctx->callback(userdb_dict_get_user(ctx, key), _ctx->context); else _ctx->callback(NULL, _ctx->context); } static int userdb_dict_iterate_deinit(struct userdb_iterate_context *_ctx) { struct dict_userdb_iterate_context *ctx = (struct dict_userdb_iterate_context *)_ctx; const char *error; int ret = _ctx->failed ? -1 : 0; if (dict_iterate_deinit(&ctx->iter, &error) < 0) { e_error(authdb_event(_ctx->auth_request), "dict_iterate(%s) failed: %s", ctx->key_prefix, error); ret = -1; } auth_request_unref(&ctx->ctx.auth_request); i_free(ctx); return ret; } static struct userdb_module * userdb_dict_preinit(pool_t pool, const char *args) { struct dict_userdb_module *module; struct dict_connection *conn; module = p_new(pool, struct dict_userdb_module, 1); module->conn = conn = db_dict_init(args); module->module.blocking = TRUE; module->module.default_cache_key = auth_cache_parse_key(pool, db_dict_parse_cache_key(&conn->set.keys, &conn->set.userdb_fields, &conn->set.parsed_userdb_objects)); return &module->module; } static void userdb_dict_deinit(struct userdb_module *_module) { struct dict_userdb_module *module = (struct dict_userdb_module *)_module; db_dict_unref(&module->conn); } struct userdb_module_interface userdb_dict = { "dict", userdb_dict_preinit, NULL, userdb_dict_deinit, userdb_dict_lookup, userdb_dict_iterate_init, userdb_dict_iterate_next, userdb_dict_iterate_deinit }; dovecot-2.3.21.1/src/auth/auth-request-var-expand.h0000644000000000000000000000323514656633576016713 00000000000000#ifndef AUTH_REQUEST_VAR_EXPAND_H #define AUTH_REQUEST_VAR_EXPAND_H typedef const char * auth_request_escape_func_t(const char *string, const struct auth_request *auth_request); #define AUTH_REQUEST_VAR_TAB_USER_IDX 0 #define AUTH_REQUEST_VAR_TAB_USERNAME_IDX 1 #define AUTH_REQUEST_VAR_TAB_DOMAIN_IDX 2 #define AUTH_REQUEST_VAR_TAB_COUNT 47 extern const struct var_expand_table auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT+1]; extern const struct var_expand_func_table auth_request_var_funcs_table[]; const struct var_expand_table * auth_request_get_var_expand_table(const struct auth_request *auth_request, auth_request_escape_func_t *escape_func) ATTR_NULL(2); struct var_expand_table * auth_request_get_var_expand_table_full(const struct auth_request *auth_request, const char *username, auth_request_escape_func_t *escape_func, unsigned int *count) ATTR_NULL(3); int auth_request_var_expand(string_t *dest, const char *str, const struct auth_request *auth_request, auth_request_escape_func_t *escape_func, const char **error_r); int auth_request_var_expand_with_table(string_t *dest, const char *str, const struct auth_request *auth_request, const struct var_expand_table *table, auth_request_escape_func_t *escape_func, const char **error_r); int t_auth_request_var_expand(const char *str, const struct auth_request *auth_request, auth_request_escape_func_t *escape_func, const char **value_r, const char **error_r); const char *auth_request_str_escape(const char *string, const struct auth_request *request); #endif dovecot-2.3.21.1/src/auth/db-ldap.h0000644000000000000000000001347614656633576013534 00000000000000#ifndef DB_LDAP_H #define DB_LDAP_H /* Functions like ldap_bind() have been deprecated in OpenLDAP 2.3 This define enables them until the code here can be refactored */ #define LDAP_DEPRECATED 1 /* Maximum number of pending requests before delaying new requests. */ #define DB_LDAP_MAX_PENDING_REQUESTS 8 /* connect() timeout to LDAP */ #define DB_LDAP_CONNECT_TIMEOUT_SECS 5 /* If LDAP connection is down, fail requests after waiting for this long. */ #define DB_LDAP_REQUEST_DISCONNECT_TIMEOUT_SECS 4 /* If request is still in queue after this many seconds and other requests have been replied, assume the request was lost and abort it. */ #define DB_LDAP_REQUEST_LOST_TIMEOUT_SECS 60 /* If server disconnects us, don't reconnect if no requests have been sent for this many seconds. */ #define DB_LDAP_IDLE_RECONNECT_SECS 60 #include struct auth_request; struct ldap_connection; struct ldap_request; typedef void db_search_callback_t(struct ldap_connection *conn, struct ldap_request *request, LDAPMessage *res); struct ldap_settings { const char *hosts; const char *uris; const char *dn; const char *dnpass; bool auth_bind; const char *auth_bind_userdn; bool tls; bool sasl_bind; const char *sasl_mech; const char *sasl_realm; const char *sasl_authz_id; const char *tls_ca_cert_file; const char *tls_ca_cert_dir; const char *tls_cert_file; const char *tls_key_file; const char *tls_cipher_suite; const char *tls_require_cert; const char *deref; const char *scope; const char *base; unsigned int ldap_version; const char *ldaprc_path; const char *debug_level; const char *user_attrs; const char *user_filter; const char *pass_attrs; const char *pass_filter; const char *iterate_attrs; const char *iterate_filter; const char *default_pass_scheme; bool userdb_warning_disable; /* deprecated for now at least */ bool blocking; /* ... */ int ldap_deref, ldap_scope, ldap_tls_require_cert_parsed; uid_t uid; gid_t gid; }; enum ldap_request_type { LDAP_REQUEST_TYPE_SEARCH, LDAP_REQUEST_TYPE_BIND }; struct ldap_field { /* Dovecot field name. */ const char *name; /* Field value template with %vars. NULL = same as LDAP value. */ const char *value; /* LDAP attribute name, or "" if this is a static field. */ const char *ldap_attr_name; /* LDAP value contains a DN, which is looked up and used for @name attributes. */ bool value_is_dn; /* This attribute is used internally only via %{ldap_ptr}, it shouldn't be returned in iteration. */ bool skip; }; ARRAY_DEFINE_TYPE(ldap_field, struct ldap_field); struct ldap_request { enum ldap_request_type type; /* msgid for sent requests, -1 if not sent */ int msgid; /* timestamp when request was created */ time_t create_time; /* Number of times this request has been sent to LDAP server. This increases when LDAP gets disconnected and reconnect send the request again. */ unsigned int send_count; bool failed:1; /* This is to prevent double logging the result */ bool result_logged:1; db_search_callback_t *callback; struct auth_request *auth_request; }; struct ldap_request_named_result { const struct ldap_field *field; const char *dn; struct db_ldap_result *result; }; struct ldap_request_search { struct ldap_request request; const char *base; const char *filter; char **attributes; /* points to pass_attr_names / user_attr_names */ const ARRAY_TYPE(ldap_field) *attr_map; struct db_ldap_result *result; ARRAY(struct ldap_request_named_result) named_results; unsigned int name_idx; bool multi_entry; }; struct ldap_request_bind { struct ldap_request request; const char *dn; }; enum ldap_connection_state { /* Not connected */ LDAP_CONN_STATE_DISCONNECTED, /* Binding - either to default dn or doing auth bind */ LDAP_CONN_STATE_BINDING, /* Bound to auth dn */ LDAP_CONN_STATE_BOUND_AUTH, /* Bound to default dn */ LDAP_CONN_STATE_BOUND_DEFAULT }; struct ldap_connection { struct ldap_connection *next; pool_t pool; int refcount; struct event *event; char *config_path; struct ldap_settings set; LDAP *ld; enum ldap_connection_state conn_state; int default_bind_msgid; int fd; struct io *io; struct timeout *to; /* Request queue contains sent requests at tail (msgid != -1) and queued requests at head (msgid == -1). */ struct aqueue *request_queue; ARRAY(struct ldap_request *) request_array; /* Number of messages in queue with msgid != -1 */ unsigned int pending_count; /* Timestamp when we last received a reply */ time_t last_reply_stamp; char **pass_attr_names, **user_attr_names, **iterate_attr_names; ARRAY_TYPE(ldap_field) pass_attr_map, user_attr_map, iterate_attr_map; bool userdb_used; bool delayed_connect; }; /* Send/queue request */ void db_ldap_request(struct ldap_connection *conn, struct ldap_request *request); void db_ldap_set_attrs(struct ldap_connection *conn, const char *attrlist, char ***attr_names_r, ARRAY_TYPE(ldap_field) *attr_map, const char *skip_attr) ATTR_NULL(5); struct ldap_connection *db_ldap_init(const char *config_path, bool userdb); void db_ldap_unref(struct ldap_connection **conn); int db_ldap_connect(struct ldap_connection *conn); void db_ldap_connect_delayed(struct ldap_connection *conn); void db_ldap_enable_input(struct ldap_connection *conn, bool enable); const char *ldap_escape(const char *str, const struct auth_request *auth_request); const char *ldap_get_error(struct ldap_connection *conn); struct db_ldap_result_iterate_context * db_ldap_result_iterate_init(struct ldap_connection *conn, struct ldap_request_search *ldap_request, LDAPMessage *res, bool skip_null_values); bool db_ldap_result_iterate_next(struct db_ldap_result_iterate_context *ctx, const char **name_r, const char *const **values_r); void db_ldap_result_iterate_deinit(struct db_ldap_result_iterate_context **ctx); #endif dovecot-2.3.21.1/src/auth/auth-request-stats.h0000644000000000000000000000060614656633576016003 00000000000000#ifndef AUTH_REQUEST_STATS_H #define AUTH_REQUEST_STATS_H #include "auth-stats.h" struct auth_request; struct auth_stats *auth_request_stats_get(struct auth_request *request); void auth_request_stats_add_tempfail(struct auth_request *request); void auth_request_stats_send(struct auth_request *request); void auth_request_stats_init(void); void auth_request_stats_deinit(void); #endif dovecot-2.3.21.1/src/auth/passdb-template.c0000644000000000000000000000453714656633576015307 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "array.h" #include "str.h" #include "passdb.h" #include "passdb-template.h" struct passdb_template { ARRAY(const char *) args; }; struct passdb_template *passdb_template_build(pool_t pool, const char *args) { struct passdb_template *tmpl; const char *const *tmp, *key, *value; tmpl = p_new(pool, struct passdb_template, 1); tmp = t_strsplit_spaces(args, " "); p_array_init(&tmpl->args, pool, str_array_length(tmp)); for (; *tmp != NULL; tmp++) { value = strchr(*tmp, '='); if (value == NULL) key = *tmp; else key = t_strdup_until(*tmp, value++); if (*key == '\0') i_fatal("Invalid passdb template %s - key must not be empty", args); key = p_strdup(pool, key); value = p_strdup(pool, value); array_push_back(&tmpl->args, &key); array_push_back(&tmpl->args, &value); } return tmpl; } int passdb_template_export(struct passdb_template *tmpl, struct auth_request *auth_request, const char **error_r) { const struct var_expand_table *table; string_t *str; const char *const *args, *value; unsigned int i, count; if (passdb_template_is_empty(tmpl)) return 0; str = t_str_new(256); table = auth_request_get_var_expand_table(auth_request, NULL); args = array_get(&tmpl->args, &count); i_assert((count % 2) == 0); for (i = 0; i < count; i += 2) { if (args[i+1] == NULL) value = ""; else { str_truncate(str, 0); if (auth_request_var_expand_with_table(str, args[i+1], auth_request, table, NULL, error_r) <= 0) return -1; value = str_c(str); } auth_request_set_field(auth_request, args[i], value, STATIC_PASS_SCHEME); } return 0; } bool passdb_template_remove(struct passdb_template *tmpl, const char *key, const char **value_r) { const char *const *args; unsigned int i, count; args = array_get(&tmpl->args, &count); i_assert((count % 2) == 0); for (i = 0; i < count; i += 2) { if (strcmp(args[i], key) == 0) { *value_r = args[i+1]; array_delete(&tmpl->args, i, 2); return TRUE; } } return FALSE; } bool passdb_template_is_empty(struct passdb_template *tmpl) { return array_count(&tmpl->args) == 0; } const char *const *passdb_template_get_args(struct passdb_template *tmpl, unsigned int *count_r) { return array_get(&tmpl->args, count_r); } dovecot-2.3.21.1/src/auth/userdb-checkpassword.c0000644000000000000000000000433214656633576016335 00000000000000/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "userdb.h" #ifdef USERDB_CHECKPASSWORD #include "db-checkpassword.h" struct checkpassword_userdb_module { struct userdb_module module; struct db_checkpassword *db; }; static void userdb_checkpassword_callback(struct auth_request *request, enum db_checkpassword_status status, const char *const *extra_fields, userdb_callback_t *callback) { unsigned int i; switch (status) { case DB_CHECKPASSWORD_STATUS_INTERNAL_FAILURE: callback(USERDB_RESULT_INTERNAL_FAILURE, request); break; case DB_CHECKPASSWORD_STATUS_FAILURE: callback(USERDB_RESULT_USER_UNKNOWN, request); break; case DB_CHECKPASSWORD_STATUS_OK: for (i = 0; extra_fields[i] != NULL; i++) { if (!str_begins(extra_fields[i], "userdb_")) continue; auth_request_set_field_keyvalue(request, extra_fields[i], NULL); } callback(USERDB_RESULT_OK, request); break; } } static void checkpassword_lookup(struct auth_request *request, userdb_callback_t *callback) { struct userdb_module *_module = request->userdb->userdb; struct checkpassword_userdb_module *module = (struct checkpassword_userdb_module *)_module; db_checkpassword_call(module->db, request, NULL, userdb_checkpassword_callback, callback); } static struct userdb_module * checkpassword_preinit(pool_t pool, const char *args) { struct checkpassword_userdb_module *module; const char *checkpassword_path = args; const char *checkpassword_reply_path = PKG_LIBEXECDIR"/checkpassword-reply"; module = p_new(pool, struct checkpassword_userdb_module, 1); module->db = db_checkpassword_init(checkpassword_path, checkpassword_reply_path); return &module->module; } static void checkpassword_deinit(struct userdb_module *_module) { struct checkpassword_userdb_module *module = (struct checkpassword_userdb_module *)_module; db_checkpassword_deinit(&module->db); } struct userdb_module_interface userdb_checkpassword = { "checkpassword", checkpassword_preinit, NULL, checkpassword_deinit, checkpassword_lookup, NULL, NULL, NULL }; #else struct userdb_module_interface userdb_checkpassword = { .name = "checkpassword" }; #endif dovecot-2.3.21.1/src/auth/userdb-template.h0000644000000000000000000000106514656633576015315 00000000000000#ifndef USERDB_TEMPLATE_H #define USERDB_TEMPLATE_H struct userdb_template * userdb_template_build(pool_t pool, const char *userdb_name, const char *args); int userdb_template_export(struct userdb_template *tmpl, struct auth_request *auth_request, const char **error_r); bool userdb_template_remove(struct userdb_template *tmpl, const char *key, const char **value_r); bool userdb_template_is_empty(struct userdb_template *tmpl); const char *const *userdb_template_get_args(struct userdb_template *tmpl, unsigned int *count_r); #endif dovecot-2.3.21.1/src/auth/userdb-passwd.c0000644000000000000000000001546414656633576015006 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "userdb.h" #ifdef USERDB_PASSWD #include "ioloop.h" #include "ipwd.h" #include "time-util.h" #include "userdb-template.h" #define USER_CACHE_KEY "%u" #define PASSWD_SLOW_WARN_MSECS (10*1000) #define PASSWD_SLOW_MASTER_WARN_MSECS 50 #define PASSDB_SLOW_MASTER_WARN_COUNT_INTERVAL 100 #define PASSDB_SLOW_MASTER_WARN_MIN_PERCENTAGE 5 struct passwd_userdb_module { struct userdb_module module; struct userdb_template *tmpl; unsigned int fast_count, slow_count; bool slow_warned:1; }; struct passwd_userdb_iterate_context { struct userdb_iterate_context ctx; struct passwd_userdb_iterate_context *next_waiting; }; static struct passwd_userdb_iterate_context *cur_userdb_iter = NULL; static struct timeout *cur_userdb_iter_to = NULL; static void passwd_check_warnings(struct auth_request *auth_request, struct passwd_userdb_module *module, const struct timeval *start_tv) { struct timeval end_tv; unsigned int msecs, percentage; i_gettimeofday(&end_tv); msecs = timeval_diff_msecs(&end_tv, start_tv); if (msecs >= PASSWD_SLOW_WARN_MSECS) { e_warning(authdb_event(auth_request), "Lookup for %s took %u secs", auth_request->fields.user, msecs/1000); return; } if (worker || module->slow_warned) return; if (msecs < PASSWD_SLOW_MASTER_WARN_MSECS) { module->fast_count++; return; } module->slow_count++; if (module->fast_count + module->slow_count < PASSDB_SLOW_MASTER_WARN_COUNT_INTERVAL) return; percentage = module->slow_count * 100 / (module->slow_count + module->fast_count); if (percentage < PASSDB_SLOW_MASTER_WARN_MIN_PERCENTAGE) { /* start from beginning */ module->slow_count = module->fast_count = 0; } else { e_warning(authdb_event(auth_request), "%u%% of last %u lookups took over " "%u milliseconds, " "you may want to set blocking=yes for userdb", percentage, PASSDB_SLOW_MASTER_WARN_COUNT_INTERVAL, PASSWD_SLOW_MASTER_WARN_MSECS); module->slow_warned = TRUE; } } static void passwd_lookup(struct auth_request *auth_request, userdb_callback_t *callback) { struct userdb_module *_module = auth_request->userdb->userdb; struct passwd_userdb_module *module = (struct passwd_userdb_module *)_module; struct passwd pw; struct timeval start_tv; const char *error; int ret; e_debug(authdb_event(auth_request), "lookup"); i_gettimeofday(&start_tv); ret = i_getpwnam(auth_request->fields.user, &pw); if (start_tv.tv_sec != 0) passwd_check_warnings(auth_request, module, &start_tv); switch (ret) { case -1: e_error(authdb_event(auth_request), "getpwnam() failed: %m"); callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request); return; case 0: auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB); callback(USERDB_RESULT_USER_UNKNOWN, auth_request); return; } auth_request_set_field(auth_request, "user", pw.pw_name, NULL); auth_request_set_userdb_field(auth_request, "system_groups_user", pw.pw_name); auth_request_set_userdb_field(auth_request, "uid", dec2str(pw.pw_uid)); auth_request_set_userdb_field(auth_request, "gid", dec2str(pw.pw_gid)); auth_request_set_userdb_field(auth_request, "home", pw.pw_dir); if (userdb_template_export(module->tmpl, auth_request, &error) < 0) { e_error(authdb_event(auth_request), "Failed to expand template: %s", error); callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request); } callback(USERDB_RESULT_OK, auth_request); } static struct userdb_iterate_context * passwd_iterate_init(struct auth_request *auth_request, userdb_iter_callback_t *callback, void *context) { struct passwd_userdb_iterate_context *ctx; ctx = i_new(struct passwd_userdb_iterate_context, 1); ctx->ctx.auth_request = auth_request; ctx->ctx.callback = callback; ctx->ctx.context = context; setpwent(); if (cur_userdb_iter == NULL) cur_userdb_iter = ctx; return &ctx->ctx; } static bool passwd_iterate_want_pw(struct passwd *pw, const struct auth_settings *set) { /* skip entries not in valid UID range. they're users for daemons and such. */ if (pw->pw_uid < (uid_t)set->first_valid_uid) return FALSE; if (pw->pw_uid > (uid_t)set->last_valid_uid && set->last_valid_uid != 0) return FALSE; if (pw->pw_gid < (gid_t)set->first_valid_gid) return FALSE; if (pw->pw_gid > (gid_t)set->last_valid_gid && set->last_valid_gid != 0) return FALSE; return TRUE; } static void passwd_iterate_next(struct userdb_iterate_context *_ctx) { struct passwd_userdb_iterate_context *ctx = (struct passwd_userdb_iterate_context *)_ctx; const struct auth_settings *set = _ctx->auth_request->set; struct passwd *pw; if (cur_userdb_iter != NULL && cur_userdb_iter != ctx) { /* we can't support concurrent userdb iteration. wait until the previous one is done */ ctx->next_waiting = cur_userdb_iter->next_waiting; cur_userdb_iter->next_waiting = ctx; return; } /* reset errno since it might have been set when we got here */ errno = 0; while ((pw = getpwent()) != NULL) { if (passwd_iterate_want_pw(pw, set)) { _ctx->callback(pw->pw_name, _ctx->context); return; } /* getpwent might set errno to something even if it returns non-NULL. */ errno = 0; } if (errno != 0) { e_error(authdb_event(_ctx->auth_request), "getpwent() failed: %m"); _ctx->failed = TRUE; } _ctx->callback(NULL, _ctx->context); } static void ATTR_NULL(1) passwd_iterate_next_timeout(void *context ATTR_UNUSED) { timeout_remove(&cur_userdb_iter_to); passwd_iterate_next(&cur_userdb_iter->ctx); } static int passwd_iterate_deinit(struct userdb_iterate_context *_ctx) { struct passwd_userdb_iterate_context *ctx = (struct passwd_userdb_iterate_context *)_ctx; int ret = _ctx->failed ? -1 : 0; cur_userdb_iter = ctx->next_waiting; i_free(ctx); if (cur_userdb_iter != NULL) { cur_userdb_iter_to = timeout_add(0, passwd_iterate_next_timeout, NULL); } endpwent(); return ret; } static struct userdb_module * passwd_passwd_preinit(pool_t pool, const char *args) { struct passwd_userdb_module *module; const char *value; module = p_new(pool, struct passwd_userdb_module, 1); module->module.default_cache_key = USER_CACHE_KEY; module->tmpl = userdb_template_build(pool, "passwd", args); module->module.blocking = TRUE; if (userdb_template_remove(module->tmpl, "blocking", &value)) module->module.blocking = strcasecmp(value, "yes") == 0; /* FIXME: backwards compatibility */ if (!userdb_template_is_empty(module->tmpl)) i_warning("userdb passwd: Move templates args to override_fields setting"); return &module->module; } struct userdb_module_interface userdb_passwd = { "passwd", passwd_passwd_preinit, NULL, NULL, passwd_lookup, passwd_iterate_init, passwd_iterate_next, passwd_iterate_deinit }; #else struct userdb_module_interface userdb_passwd = { .name = "passwd" }; #endif dovecot-2.3.21.1/src/auth/mech-oauth2.c0000644000000000000000000002142514656633576014331 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "safe-memset.h" #include "str.h" #include "mech.h" #include "passdb.h" #include "oauth2.h" #include "json-parser.h" #include struct oauth2_auth_request { struct auth_request auth; bool failed; }; static bool oauth2_find_oidc_url(struct auth_request *req, const char **url_r) { struct auth_passdb *db = req->passdb; if (req->openid_config_url != NULL) { *url_r = req->openid_config_url; return TRUE; } /* keep looking until you get a value */ for (; db != NULL; db = db->next) { if (strcmp(db->passdb->iface.name, "oauth2") == 0) { const char *url = passdb_oauth2_get_oidc_url(db->passdb); if (url == NULL || *url == '\0') continue; *url_r = url; return TRUE; } } return FALSE; } /* RFC5801 based unescaping */ static bool oauth2_unescape_username(const char *in, const char **username_r) { string_t *out; out = t_str_new(64); for (; *in != '\0'; in++) { if (in[0] == ',') return FALSE; if (in[0] == '=') { if (in[1] == '2' && in[2] == 'C') str_append_c(out, ','); else if (in[1] == '3' && in[2] == 'D') str_append_c(out, '='); else return FALSE; in += 2; } else { str_append_c(out, *in); } } *username_r = str_c(out); return TRUE; } static void oauth2_verify_callback(enum passdb_result result, const char *const *error_fields, struct auth_request *request) { const char *oidc_url; i_assert(result == PASSDB_RESULT_OK || error_fields != NULL); switch (result) { case PASSDB_RESULT_OK: auth_request_success(request, "", 0); break; case PASSDB_RESULT_INTERNAL_FAILURE: request->internal_failure = TRUE; /* fall through */ default: /* we could get new token after this */ if (request->mech_password != NULL) request->mech_password = NULL; string_t *error = t_str_new(64); str_append_c(error, '{'); for (unsigned int i = 0; error_fields[i] != NULL; i += 2) { i_assert(error_fields[i+1] != NULL); if (i > 0) str_append_c(error, ','); str_append_c(error, '"'); json_append_escaped(error, error_fields[i]); str_append(error, "\":\""); json_append_escaped(error, error_fields[i+1]); str_append_c(error, '"'); } /* FIXME: HORRIBLE HACK - REMOVE ME!!! It is because the mech has not been implemented properly that we need to pass the config url in this strange way. This **must** be removed from here and db-oauth2 once the validation result et al is handled here. */ if (oauth2_find_oidc_url(request, &oidc_url)) { if (str_len(error) > 0) str_append_c(error, ','); str_printfa(error, "\"openid-configuration\":\""); json_append_escaped(error, oidc_url); str_append_c(error, '"'); } str_append_c(error, '}'); auth_request_fail_with_reply(request, str_data(error), str_len(error)); break; } } static void mech_oauth2_verify_token(struct auth_request *request, const char *token, enum passdb_result result, verify_plain_callback_t callback) { i_assert(token != NULL); if (result != PASSDB_RESULT_OK) { request->passdb_result = result; request->failed = TRUE; } auth_request_verify_plain(request, token, callback); } static void xoauth2_verify_callback(enum passdb_result result, struct auth_request *request) { const char *const error_fields[] = { "status", "401", "schemes", "bearer", "scope", "mail", NULL }; oauth2_verify_callback(result, error_fields, request); } static void oauthbearer_verify_callback(enum passdb_result result, struct auth_request *request) { const char *error_fields[] = { "status", "invalid_token", NULL }; oauth2_verify_callback(result, error_fields, request); } /* Input syntax: user=Username^Aauth=Bearer token^A^A */ static void mech_xoauth2_auth_continue(struct auth_request *request, const unsigned char *data, size_t data_size) { /* split the data from ^A */ bool user_given = FALSE; const char *error; const char *token = NULL; const char *const *ptr; const char *username; const char *const *fields = t_strsplit(t_strndup(data, data_size), "\x01"); for(ptr = fields; *ptr != NULL; ptr++) { if (str_begins(*ptr, "user=")) { /* xoauth2 does not require unescaping because the data format does not contain anything to escape */ username = (*ptr)+5; user_given = TRUE; } else if (str_begins(*ptr, "auth=")) { const char *value = (*ptr)+5; if (strncasecmp(value, "bearer ", 7) == 0 && oauth2_valid_token(value+7)) { token = value+7; } else { e_info(request->mech_event, "Invalid continued data"); xoauth2_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH, request); return; } } /* do not fail on unexpected fields */ } if (user_given && !auth_request_set_username(request, username, &error)) { e_info(request->mech_event, "%s", error); xoauth2_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH, request); return; } if (user_given && token != NULL) mech_oauth2_verify_token(request, token, PASSDB_RESULT_OK, xoauth2_verify_callback); else { e_info(request->mech_event, "Username or token missing"); xoauth2_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH, request); } } /* Input syntax for data: gs2flag,a=username,^Afield=...^Afield=...^Aauth=Bearer token^A^A */ static void mech_oauthbearer_auth_continue(struct auth_request *request, const unsigned char *data, size_t data_size) { bool user_given = FALSE; const char *error; const char *username; const char *const *ptr; /* split the data from ^A */ const char **fields = t_strsplit(t_strndup(data, data_size), "\x01"); const char *token = NULL; /* ensure initial field is OK */ if (*fields == NULL || *(fields[0]) == '\0') { e_info(request->mech_event, "Invalid continued data"); oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH, request); return; } /* the first field is specified by RFC5801 as gs2-header */ for(ptr = t_strsplit_spaces(fields[0], ","); *ptr != NULL; ptr++) { switch(*ptr[0]) { case 'f': e_info(request->mech_event, "Client requested non-standard mechanism"); oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH, request); return; case 'p': /* channel binding is not supported */ e_info(request->mech_event, "Client requested and used channel-binding"); oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH, request); return; case 'n': case 'y': /* we don't need to use channel-binding */ continue; case 'a': /* authzid */ if ((*ptr)[1] != '=' || !oauth2_unescape_username((*ptr)+2, &username)) { e_info(request->mech_event, "Invalid username escaping"); oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH, request); return; } else { user_given = TRUE; } break; default: e_info(request->mech_event, "Invalid gs2-header in request"); oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH, request); return; } } for(ptr = fields; *ptr != NULL; ptr++) { if (str_begins(*ptr, "auth=")) { const char *value = (*ptr)+5; if (strncasecmp(value, "bearer ", 7) == 0 && oauth2_valid_token(value+7)) { token = value+7; } else { e_info(request->mech_event, "Invalid continued data"); oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH, request); return; } } /* do not fail on unexpected fields */ } if (user_given && !auth_request_set_username(request, username, &error)) { e_info(request->mech_event, "%s", error); oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH, request); return; } if (user_given && token != NULL) mech_oauth2_verify_token(request, token, PASSDB_RESULT_OK, oauthbearer_verify_callback); else { e_info(request->mech_event, "Missing username or token"); oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH, request); } } static struct auth_request *mech_oauth2_auth_new(void) { struct oauth2_auth_request *request; pool_t pool; pool = pool_alloconly_create(MEMPOOL_GROWING"oauth2_auth_request", 2048); request = p_new(pool, struct oauth2_auth_request, 1); request->auth.pool = pool; return &request->auth; } const struct mech_module mech_oauthbearer = { "OAUTHBEARER", /* while this does not transfer plaintext password, the token is still considered as password */ .flags = MECH_SEC_PLAINTEXT, .passdb_need = 0, mech_oauth2_auth_new, mech_generic_auth_initial, mech_oauthbearer_auth_continue, mech_generic_auth_free }; const struct mech_module mech_xoauth2 = { "XOAUTH2", .flags = MECH_SEC_PLAINTEXT, .passdb_need = 0, mech_oauth2_auth_new, mech_generic_auth_initial, mech_xoauth2_auth_continue, mech_generic_auth_free }; dovecot-2.3.21.1/src/auth/mech-external.c0000644000000000000000000000312214656633576014743 00000000000000/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "passdb.h" #include "mech.h" #include "mech-plain-common.h" static void mech_external_auth_continue(struct auth_request *request, const unsigned char *data, size_t data_size) { const char *authzid, *error; authzid = t_strndup(data, data_size); if (request->fields.user == NULL) { e_info(request->mech_event, "username not known"); auth_request_fail(request); return; } /* this call is done simply to put the username through translation settings */ if (!auth_request_set_username(request, "", &error)) { e_info(request->mech_event, "Invalid username"); auth_request_fail(request); return; } if (*authzid != '\0' && !auth_request_set_login_username(request, authzid, &error)) { /* invalid login username */ e_info(request->mech_event, "login user: %s", error); auth_request_fail(request); } else { auth_request_verify_plain(request, "", plain_verify_callback); } } static struct auth_request *mech_external_auth_new(void) { struct auth_request *request; pool_t pool; pool = pool_alloconly_create(MEMPOOL_GROWING"external_auth_request", 2048); request = p_new(pool, struct auth_request, 1); request->pool = pool; return request; } const struct mech_module mech_external = { "EXTERNAL", .flags = 0, .passdb_need = MECH_PASSDB_NEED_VERIFY_PLAIN, mech_external_auth_new, mech_generic_auth_initial, mech_external_auth_continue, mech_generic_auth_free }; dovecot-2.3.21.1/src/auth/passdb-template.h0000644000000000000000000000106714656633576015307 00000000000000#ifndef PASSDB_TEMPLATE_H #define PASSDB_TEMPLATE_H #define STATIC_PASS_SCHEME "PLAIN" struct passdb_template *passdb_template_build(pool_t pool, const char *args); int passdb_template_export(struct passdb_template *tmpl, struct auth_request *auth_request, const char **error_r); bool passdb_template_remove(struct passdb_template *tmpl, const char *key, const char **value_r); bool passdb_template_is_empty(struct passdb_template *tmpl); const char *const *passdb_template_get_args(struct passdb_template *tmpl, unsigned int *count_r); #endif dovecot-2.3.21.1/src/auth/auth.c0000644000000000000000000003040514656633576013154 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "array.h" #include "settings-parser.h" #include "master-service-settings.h" #include "mech.h" #include "userdb.h" #include "passdb.h" #include "passdb-template.h" #include "userdb-template.h" #include "auth.h" struct event *auth_event; struct event_category event_category_auth = { .name = "auth", }; static const struct auth_userdb_settings userdb_dummy_set = { .name = "", .driver = "static", .args = "", .default_fields = "", .override_fields = "", .skip = "never", .result_success = "return-ok", .result_failure = "continue", .result_internalfail = "continue", .auth_verbose = "default", }; ARRAY_TYPE(auth) auths; static enum auth_passdb_skip auth_passdb_skip_parse(const char *str) { if (strcmp(str, "never") == 0) return AUTH_PASSDB_SKIP_NEVER; if (strcmp(str, "authenticated") == 0) return AUTH_PASSDB_SKIP_AUTHENTICATED; if (strcmp(str, "unauthenticated") == 0) return AUTH_PASSDB_SKIP_UNAUTHENTICATED; i_unreached(); } static enum auth_userdb_skip auth_userdb_skip_parse(const char *str) { if (strcmp(str, "never") == 0) return AUTH_USERDB_SKIP_NEVER; if (strcmp(str, "found") == 0) return AUTH_USERDB_SKIP_FOUND; if (strcmp(str, "notfound") == 0) return AUTH_USERDB_SKIP_NOTFOUND; i_unreached(); } static enum auth_db_rule auth_db_rule_parse(const char *str) { if (strcmp(str, "return") == 0) return AUTH_DB_RULE_RETURN; if (strcmp(str, "return-ok") == 0) return AUTH_DB_RULE_RETURN_OK; if (strcmp(str, "return-fail") == 0) return AUTH_DB_RULE_RETURN_FAIL; if (strcmp(str, "continue") == 0) return AUTH_DB_RULE_CONTINUE; if (strcmp(str, "continue-ok") == 0) return AUTH_DB_RULE_CONTINUE_OK; if (strcmp(str, "continue-fail") == 0) return AUTH_DB_RULE_CONTINUE_FAIL; i_unreached(); } static void auth_passdb_preinit(struct auth *auth, const struct auth_passdb_settings *set, struct auth_passdb **passdbs) { struct auth_passdb *auth_passdb, **dest; auth_passdb = p_new(auth->pool, struct auth_passdb, 1); auth_passdb->set = set; auth_passdb->skip = auth_passdb_skip_parse(set->skip); auth_passdb->result_success = auth_db_rule_parse(set->result_success); auth_passdb->result_failure = auth_db_rule_parse(set->result_failure); auth_passdb->result_internalfail = auth_db_rule_parse(set->result_internalfail); auth_passdb->default_fields_tmpl = passdb_template_build(auth->pool, set->default_fields); auth_passdb->override_fields_tmpl = passdb_template_build(auth->pool, set->override_fields); /* for backwards compatibility: */ if (set->pass) auth_passdb->result_success = AUTH_DB_RULE_CONTINUE; for (dest = passdbs; *dest != NULL; dest = &(*dest)->next) ; *dest = auth_passdb; auth_passdb->passdb = passdb_preinit(auth->pool, set); /* make sure any %variables in default_fields exist in cache_key */ if (auth_passdb->passdb->default_cache_key != NULL) { auth_passdb->cache_key = p_strconcat(auth->pool, auth_passdb->passdb->default_cache_key, set->default_fields, NULL); } else { auth_passdb->cache_key = NULL; } } static void auth_userdb_preinit(struct auth *auth, const struct auth_userdb_settings *set) { struct auth_userdb *auth_userdb, **dest; auth_userdb = p_new(auth->pool, struct auth_userdb, 1); auth_userdb->set = set; auth_userdb->skip = auth_userdb_skip_parse(set->skip); auth_userdb->result_success = auth_db_rule_parse(set->result_success); auth_userdb->result_failure = auth_db_rule_parse(set->result_failure); auth_userdb->result_internalfail = auth_db_rule_parse(set->result_internalfail); auth_userdb->default_fields_tmpl = userdb_template_build(auth->pool, set->driver, set->default_fields); auth_userdb->override_fields_tmpl = userdb_template_build(auth->pool, set->driver, set->override_fields); for (dest = &auth->userdbs; *dest != NULL; dest = &(*dest)->next) ; *dest = auth_userdb; auth_userdb->userdb = userdb_preinit(auth->pool, set); /* make sure any %variables in default_fields exist in cache_key */ if (auth_userdb->userdb->default_cache_key != NULL) { auth_userdb->cache_key = p_strconcat(auth->pool, auth_userdb->userdb->default_cache_key, set->default_fields, NULL); } else { auth_userdb->cache_key = NULL; } } static bool auth_passdb_list_have_verify_plain(const struct auth *auth) { const struct auth_passdb *passdb; for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next) { if (passdb->passdb->iface.verify_plain != NULL) return TRUE; } return FALSE; } static bool auth_passdb_list_have_lookup_credentials(const struct auth *auth) { const struct auth_passdb *passdb; for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next) { if (passdb->passdb->iface.lookup_credentials != NULL) return TRUE; } return FALSE; } static bool auth_passdb_list_have_set_credentials(const struct auth *auth) { const struct auth_passdb *passdb; for (passdb = auth->masterdbs; passdb != NULL; passdb = passdb->next) { if (passdb->passdb->iface.set_credentials != NULL) return TRUE; } for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next) { if (passdb->passdb->iface.set_credentials != NULL) return TRUE; } return FALSE; } static bool auth_mech_verify_passdb(const struct auth *auth, const struct mech_module_list *list) { switch (list->module.passdb_need) { case MECH_PASSDB_NEED_NOTHING: break; case MECH_PASSDB_NEED_VERIFY_PLAIN: if (!auth_passdb_list_have_verify_plain(auth)) return FALSE; break; case MECH_PASSDB_NEED_VERIFY_RESPONSE: case MECH_PASSDB_NEED_LOOKUP_CREDENTIALS: if (!auth_passdb_list_have_lookup_credentials(auth)) return FALSE; break; case MECH_PASSDB_NEED_SET_CREDENTIALS: if (!auth_passdb_list_have_lookup_credentials(auth)) return FALSE; if (!auth_passdb_list_have_set_credentials(auth)) return FALSE; break; } return TRUE; } static void auth_mech_list_verify_passdb(const struct auth *auth) { const struct mech_module_list *list; for (list = auth->reg->modules; list != NULL; list = list->next) { if (!auth_mech_verify_passdb(auth, list)) break; } if (list != NULL) { if (auth->passdbs == NULL) { i_fatal("No passdbs specified in configuration file. " "%s mechanism needs one", list->module.mech_name); } i_fatal("%s mechanism can't be supported with given passdbs", list->module.mech_name); } } static struct auth * ATTR_NULL(2) auth_preinit(const struct auth_settings *set, const char *service, pool_t pool, const struct mechanisms_register *reg) { struct auth_passdb_settings *const *passdbs; struct auth_userdb_settings *const *userdbs; struct auth *auth; unsigned int i, count, db_count, passdb_count, last_passdb = 0; auth = p_new(pool, struct auth, 1); auth->pool = pool; auth->service = p_strdup(pool, service); auth->set = set; auth->reg = reg; if (array_is_created(&set->passdbs)) passdbs = array_get(&set->passdbs, &db_count); else { passdbs = NULL; db_count = 0; } /* initialize passdbs first and count them */ for (passdb_count = 0, i = 0; i < db_count; i++) { if (passdbs[i]->master) continue; /* passdb { skip=unauthenticated } as the first passdb doesn't make sense, since user is never authenticated at that point. skip over them silently. */ if (auth->passdbs == NULL && auth_passdb_skip_parse(passdbs[i]->skip) == AUTH_PASSDB_SKIP_UNAUTHENTICATED) continue; auth_passdb_preinit(auth, passdbs[i], &auth->passdbs); passdb_count++; last_passdb = i; } if (passdb_count != 0 && passdbs[last_passdb]->pass) i_fatal("Last passdb can't have pass=yes"); for (i = 0; i < db_count; i++) { if (!passdbs[i]->master) continue; /* skip skip=unauthenticated, as explained above */ if (auth->masterdbs == NULL && auth_passdb_skip_parse(passdbs[i]->skip) == AUTH_PASSDB_SKIP_UNAUTHENTICATED) continue; if (passdbs[i]->deny) i_fatal("Master passdb can't have deny=yes"); if (passdbs[i]->pass && passdb_count == 0) { i_fatal("Master passdb can't have pass=yes " "if there are no passdbs"); } auth_passdb_preinit(auth, passdbs[i], &auth->masterdbs); } if (array_is_created(&set->userdbs)) { userdbs = array_get(&set->userdbs, &count); for (i = 0; i < count; i++) auth_userdb_preinit(auth, userdbs[i]); } if (auth->userdbs == NULL) { /* use a dummy userdb static. */ auth_userdb_preinit(auth, &userdb_dummy_set); } return auth; } static void auth_passdb_init(struct auth_passdb *passdb) { passdb_init(passdb->passdb); i_assert(passdb->passdb->default_pass_scheme != NULL || passdb->cache_key == NULL); } static void auth_init(struct auth *auth) { struct auth_passdb *passdb; struct auth_userdb *userdb; for (passdb = auth->masterdbs; passdb != NULL; passdb = passdb->next) auth_passdb_init(passdb); for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next) auth_passdb_init(passdb); for (userdb = auth->userdbs; userdb != NULL; userdb = userdb->next) userdb_init(userdb->userdb); } static void auth_deinit(struct auth *auth) { struct auth_passdb *passdb; struct auth_userdb *userdb; for (passdb = auth->masterdbs; passdb != NULL; passdb = passdb->next) passdb_deinit(passdb->passdb); for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next) passdb_deinit(passdb->passdb); for (userdb = auth->userdbs; userdb != NULL; userdb = userdb->next) userdb_deinit(userdb->userdb); } struct auth *auth_find_service(const char *name) { struct auth *const *a; unsigned int i, count; a = array_get(&auths, &count); if (name != NULL) { for (i = 1; i < count; i++) { if (strcmp(a[i]->service, name) == 0) return a[i]; } /* not found. maybe we can instead find a !service */ for (i = 1; i < count; i++) { if (a[i]->service[0] == '!' && strcmp(a[i]->service + 1, name) != 0) return a[i]; } } return a[0]; } struct auth *auth_default_service(void) { struct auth *const *a; unsigned int count; a = array_get(&auths, &count); return a[0]; } void auths_preinit(const struct auth_settings *set, pool_t pool, const struct mechanisms_register *reg, const char *const *services) { struct master_service_settings_output set_output; const struct auth_settings *service_set; struct auth *auth; unsigned int i; const char *not_service = NULL; bool check_default = TRUE; auth_event = event_create(NULL); event_set_forced_debug(auth_event, set->debug); event_add_category(auth_event, &event_category_auth); i_array_init(&auths, 8); auth = auth_preinit(set, NULL, pool, reg); array_push_back(&auths, &auth); for (i = 0; services[i] != NULL; i++) { if (services[i][0] == '!') { if (not_service != NULL) { i_fatal("Can't have multiple protocol " "!services (seen %s and %s)", not_service, services[i]); } not_service = services[i]; } service_set = auth_settings_read(services[i], pool, &set_output); auth = auth_preinit(service_set, services[i], pool, reg); array_push_back(&auths, &auth); } if (not_service != NULL && str_array_find(services, not_service+1)) check_default = FALSE; array_foreach_elem(&auths, auth) { if (auth->service != NULL || check_default) auth_mech_list_verify_passdb(auth); } } void auths_init(void) { struct auth *auth; /* sanity checks */ i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_USER_IDX].key == 'u'); i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_USERNAME_IDX].key == 'n'); i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_DOMAIN_IDX].key == 'd'); i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT].key == '\0' && auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT].long_key == NULL); i_assert(auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT-1].key != '\0' || auth_request_var_expand_static_tab[AUTH_REQUEST_VAR_TAB_COUNT-1].long_key != NULL); array_foreach_elem(&auths, auth) auth_init(auth); } void auths_deinit(void) { struct auth *auth; array_foreach_elem(&auths, auth) auth_deinit(auth); event_unref(&auth_event); } void auths_free(void) { struct auth **auth; unsigned int i, count; /* deinit in reverse order, because modules have been allocated by the first auth pool that used them */ auth = array_get_modifiable(&auths, &count); for (i = count; i > 0; i--) pool_unref(&auth[i-1]->pool); array_free(&auths); } dovecot-2.3.21.1/src/auth/mycrypt.c0000644000000000000000000000106614656633576013723 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #define _XOPEN_SOURCE 4 #define _XOPEN_SOURCE_EXTENDED 1 /* 1 needed for AIX */ #ifndef _AIX # define _XOPEN_VERSION 4 /* breaks AIX */ #endif #define _XPG4_2 #ifdef CRYPT_USE_XPG6 # define _XPG6 /* Some Solaris versions require this, some break with this */ #endif #include #ifdef HAVE_CRYPT_H # include #endif #include "mycrypt.h" char *mycrypt(const char *key, const char *salt) { return crypt(key, salt); } dovecot-2.3.21.1/src/auth/passdb-passwd-file.c0000644000000000000000000001240714656633576015705 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "passdb.h" #ifdef PASSDB_PASSWD_FILE #include "str.h" #include "auth-cache.h" #include "password-scheme.h" #include "db-passwd-file.h" struct passwd_file_passdb_module { struct passdb_module module; struct db_passwd_file *pwf; const char *username_format; }; static int passwd_file_add_extra_fields(struct auth_request *request, char *const *fields) { string_t *str = t_str_new(512); const struct var_expand_table *table; const char *key, *value, *error; unsigned int i; table = auth_request_get_var_expand_table(request, NULL); for (i = 0; fields[i] != NULL; i++) { value = strchr(fields[i], '='); if (value != NULL) { key = t_strdup_until(fields[i], value); str_truncate(str, 0); if (auth_request_var_expand_with_table(str, value + 1, request, table, NULL, &error) <= 0) { e_error(authdb_event(request), "Failed to expand extra field %s: %s", fields[i], error); return -1; } value = str_c(str); } else { key = fields[i]; value = ""; } auth_request_set_field(request, key, value, NULL); } return 0; } static int passwd_file_save_results(struct auth_request *request, const struct passwd_user *pu, const char **crypted_pass_r, const char **scheme_r) { *crypted_pass_r = pu->password != NULL ? pu->password : ""; *scheme_r = password_get_scheme(crypted_pass_r); if (*scheme_r == NULL) *scheme_r = request->passdb->passdb->default_pass_scheme; /* save the password so cache can use it */ auth_request_set_field(request, "password", *crypted_pass_r, *scheme_r); if (pu->extra_fields != NULL) { if (passwd_file_add_extra_fields(request, pu->extra_fields) < 0) return -1; } return 0; } static void passwd_file_verify_plain(struct auth_request *request, const char *password, verify_plain_callback_t *callback) { struct passdb_module *_module = request->passdb->passdb; struct passwd_file_passdb_module *module = (struct passwd_file_passdb_module *)_module; struct passwd_user *pu; const char *scheme, *crypted_pass; enum passdb_result result; int ret; ret = db_passwd_file_lookup(module->pwf, request, module->username_format, &pu); if (ret <= 0) { callback(ret < 0 ? PASSDB_RESULT_INTERNAL_FAILURE : PASSDB_RESULT_USER_UNKNOWN, request); return; } if (passwd_file_save_results(request, pu, &crypted_pass, &scheme) < 0) { callback(PASSDB_RESULT_INTERNAL_FAILURE, request); return; } result = auth_request_password_verify(request, password, crypted_pass, scheme, AUTH_SUBSYS_DB); callback(result, request); } static void passwd_file_lookup_credentials(struct auth_request *request, lookup_credentials_callback_t *callback) { struct passdb_module *_module = request->passdb->passdb; struct passwd_file_passdb_module *module = (struct passwd_file_passdb_module *)_module; struct passwd_user *pu; const char *crypted_pass, *scheme; int ret; ret = db_passwd_file_lookup(module->pwf, request, module->username_format, &pu); if (ret <= 0) { callback(ret < 0 ? PASSDB_RESULT_INTERNAL_FAILURE : PASSDB_RESULT_USER_UNKNOWN, NULL, 0, request); return; } if (passwd_file_save_results(request, pu, &crypted_pass, &scheme) < 0) { callback(PASSDB_RESULT_INTERNAL_FAILURE, NULL, 0, request); return; } passdb_handle_credentials(PASSDB_RESULT_OK, crypted_pass, scheme, callback, request); } static struct passdb_module * passwd_file_preinit(pool_t pool, const char *args) { struct passwd_file_passdb_module *module; const char *scheme = PASSWD_FILE_DEFAULT_SCHEME; const char *format = PASSWD_FILE_DEFAULT_USERNAME_FORMAT; const char *key, *value; while (*args != '\0') { if (*args == '/') break; key = args; value = strchr(key, '='); if (value == NULL) { value = ""; args = strchr(key, ' '); } else { key = t_strdup_until(key, value); args = strchr(++value, ' '); if (args != NULL) value = t_strdup_until(value, args); } if (args == NULL) args = ""; else args++; if (strcmp(key, "scheme") == 0) scheme = p_strdup(pool, value); else if (strcmp(key, "username_format") == 0) format = p_strdup(pool, value); else i_fatal("passdb passwd-file: Unknown setting: %s", key); } if (*args == '\0') i_fatal("passdb passwd-file: Missing args"); module = p_new(pool, struct passwd_file_passdb_module, 1); module->pwf = db_passwd_file_init(args, FALSE, global_auth_settings->debug); module->username_format = format; module->module.default_pass_scheme = scheme; return &module->module; } static void passwd_file_init(struct passdb_module *_module) { struct passwd_file_passdb_module *module = (struct passwd_file_passdb_module *)_module; db_passwd_file_parse(module->pwf); } static void passwd_file_deinit(struct passdb_module *_module) { struct passwd_file_passdb_module *module = (struct passwd_file_passdb_module *)_module; db_passwd_file_unref(&module->pwf); } struct passdb_module_interface passdb_passwd_file = { "passwd-file", passwd_file_preinit, passwd_file_init, passwd_file_deinit, passwd_file_verify_plain, passwd_file_lookup_credentials, NULL }; #else struct passdb_module_interface passdb_passwd_file = { .name = "passwd-file" }; #endif dovecot-2.3.21.1/src/auth/mech-scram.c0000644000000000000000000003517514656633576014243 00000000000000/* * SCRAM-SHA-1 SASL authentication, see RFC-5802 * * Copyright (c) 2011-2016 Florian Zeitz * * This software is released under the MIT license. */ #include #include "auth-common.h" #include "base64.h" #include "buffer.h" #include "hmac.h" #include "sha1.h" #include "sha2.h" #include "randgen.h" #include "safe-memset.h" #include "str.h" #include "strfuncs.h" #include "strnum.h" #include "password-scheme.h" #include "mech.h" #include "mech-scram.h" /* s-nonce length */ #define SCRAM_SERVER_NONCE_LEN 64 struct scram_auth_request { struct auth_request auth_request; pool_t pool; const struct hash_method *hash_method; const char *password_scheme; /* sent: */ const char *server_first_message; const char *snonce; /* received: */ const char *gs2_header; const char *cnonce; const char *client_first_message_bare; const char *client_final_message_without_proof; buffer_t *proof; /* stored */ unsigned char *stored_key; unsigned char *server_key; }; static const char * get_scram_server_first(struct scram_auth_request *request, int iter, const char *salt) { unsigned char snonce[SCRAM_SERVER_NONCE_LEN+1]; string_t *str; size_t i; /* RFC 5802, Section 7: server-first-message = [reserved-mext ","] nonce "," salt "," iteration-count ["," extensions] nonce = "r=" c-nonce [s-nonce] salt = "s=" base64 iteration-count = "i=" posit-number ;; A positive number. */ random_fill(snonce, sizeof(snonce)-1); /* make sure snonce is printable and does not contain ',' */ for (i = 0; i < sizeof(snonce)-1; i++) { snonce[i] = (snonce[i] % ('~' - '!')) + '!'; if (snonce[i] == ',') snonce[i] = '~'; } snonce[sizeof(snonce)-1] = '\0'; request->snonce = p_strndup(request->pool, snonce, sizeof(snonce)); str = t_str_new(32 + strlen(request->cnonce) + sizeof(snonce) + strlen(salt)); str_printfa(str, "r=%s%s,s=%s,i=%d", request->cnonce, request->snonce, salt, iter); return str_c(str); } static const char *get_scram_server_final(struct scram_auth_request *request) { const struct hash_method *hmethod = request->hash_method; struct hmac_context ctx; const char *auth_message; unsigned char server_signature[hmethod->digest_size]; string_t *str; /* RFC 5802, Section 3: AuthMessage := client-first-message-bare + "," + server-first-message + "," + client-final-message-without-proof ServerSignature := HMAC(ServerKey, AuthMessage) */ auth_message = t_strconcat(request->client_first_message_bare, ",", request->server_first_message, ",", request->client_final_message_without_proof, NULL); hmac_init(&ctx, request->server_key, hmethod->digest_size, hmethod); hmac_update(&ctx, auth_message, strlen(auth_message)); hmac_final(&ctx, server_signature); /* RFC 5802, Section 7: server-final-message = (server-error / verifier) ["," extensions] verifier = "v=" base64 ;; base-64 encoded ServerSignature. */ str = t_str_new(2 + MAX_BASE64_ENCODED_SIZE(sizeof(server_signature))); str_append(str, "v="); base64_encode(server_signature, sizeof(server_signature), str); return str_c(str); } static const char *scram_unescape_username(const char *in) { string_t *out; /* RFC 5802, Section 5.1: The characters ',' or '=' in usernames are sent as '=2C' and '=3D' respectively. If the server receives a username that contains '=' not followed by either '2C' or '3D', then the server MUST fail the authentication. */ out = t_str_new(64); for (; *in != '\0'; in++) { i_assert(in[0] != ','); /* strsplit should have caught this */ if (in[0] == '=') { if (in[1] == '2' && in[2] == 'C') str_append_c(out, ','); else if (in[1] == '3' && in[2] == 'D') str_append_c(out, '='); else return NULL; in += 2; } else { str_append_c(out, *in); } } return str_c(out); } static bool parse_scram_client_first(struct scram_auth_request *request, const unsigned char *data, size_t size, const char **error_r) { const char *login_username = NULL; const char *data_cstr, *p; const char *gs2_header, *gs2_cbind_flag, *authzid; const char *cfm_bare, *username, *nonce; const char *const *fields; data_cstr = gs2_header = t_strndup(data, size); /* RFC 5802, Section 7: client-first-message = gs2-header client-first-message-bare gs2-header = gs2-cbind-flag "," [ authzid ] "," client-first-message-bare = [reserved-mext ","] username "," nonce ["," extensions] extensions = attr-val *("," attr-val) ;; All extensions are optional, ;; i.e., unrecognized attributes ;; not defined in this document ;; MUST be ignored. attr-val = ALPHA "=" value */ p = strchr(data_cstr, ','); if (p == NULL) { *error_r = "Invalid initial client message: " "Missing first ',' in GS2 header"; return FALSE; } gs2_cbind_flag = t_strdup_until(data_cstr, p); data_cstr = p + 1; p = strchr(data_cstr, ','); if (p == NULL) { *error_r = "Invalid initial client message: " "Missing second ',' in GS2 header"; return FALSE; } authzid = t_strdup_until(data_cstr, p); gs2_header = t_strdup_until(gs2_header, p + 1); cfm_bare = p + 1; fields = t_strsplit(cfm_bare, ","); if (str_array_length(fields) < 2) { *error_r = "Invalid initial client message: " "Missing nonce field"; return FALSE; } username = fields[0]; nonce = fields[1]; /* gs2-cbind-flag = ("p=" cb-name) / "n" / "y" */ switch (gs2_cbind_flag[0]) { case 'p': *error_r = "Channel binding not supported"; return FALSE; case 'y': case 'n': break; default: *error_r = "Invalid GS2 header"; return FALSE; } /* authzid = "a=" saslname ;; Protocol specific. */ if (authzid[0] == '\0') ; else if (authzid[0] == 'a' && authzid[1] == '=') { /* Unescape authzid */ login_username = scram_unescape_username(authzid + 2); if (login_username == NULL) { *error_r = "authzid escaping is invalid"; return FALSE; } } else { *error_r = "Invalid authzid field"; return FALSE; } /* reserved-mext = "m=" 1*(value-char) */ if (username[0] == 'm') { *error_r = "Mandatory extension(s) not supported"; return FALSE; } /* username = "n=" saslname */ if (username[0] == 'n' && username[1] == '=') { /* Unescape username */ username = scram_unescape_username(username + 2); if (username == NULL) { *error_r = "Username escaping is invalid"; return FALSE; } if (!auth_request_set_username(&request->auth_request, username, error_r)) return FALSE; } else { *error_r = "Invalid username field"; return FALSE; } if (login_username != NULL) { if (!auth_request_set_login_username(&request->auth_request, login_username, error_r)) return FALSE; } /* nonce = "r=" c-nonce [s-nonce] */ if (nonce[0] == 'r' && nonce[1] == '=') request->cnonce = p_strdup(request->pool, nonce+2); else { *error_r = "Invalid client nonce"; return FALSE; } request->gs2_header = p_strdup(request->pool, gs2_header); request->client_first_message_bare = p_strdup(request->pool, cfm_bare); return TRUE; } static bool verify_credentials(struct scram_auth_request *request) { const struct hash_method *hmethod = request->hash_method; struct hmac_context ctx; const char *auth_message; unsigned char client_key[hmethod->digest_size]; unsigned char client_signature[hmethod->digest_size]; unsigned char stored_key[hmethod->digest_size]; size_t i; /* RFC 5802, Section 3: AuthMessage := client-first-message-bare + "," + server-first-message + "," + client-final-message-without-proof ClientSignature := HMAC(StoredKey, AuthMessage) */ auth_message = t_strconcat(request->client_first_message_bare, ",", request->server_first_message, ",", request->client_final_message_without_proof, NULL); hmac_init(&ctx, request->stored_key, hmethod->digest_size, hmethod); hmac_update(&ctx, auth_message, strlen(auth_message)); hmac_final(&ctx, client_signature); /* ClientProof := ClientKey XOR ClientSignature */ const unsigned char *proof_data = request->proof->data; for (i = 0; i < sizeof(client_signature); i++) client_key[i] = proof_data[i] ^ client_signature[i]; /* StoredKey := H(ClientKey) */ hash_method_get_digest(hmethod, client_key, sizeof(client_key), stored_key); safe_memset(client_key, 0, sizeof(client_key)); safe_memset(client_signature, 0, sizeof(client_signature)); return mem_equals_timing_safe(stored_key, request->stored_key, sizeof(stored_key)); } static void credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *auth_request) { struct scram_auth_request *request = (struct scram_auth_request *)auth_request; const char *salt, *error; unsigned int iter_count; switch (result) { case PASSDB_RESULT_OK: if (scram_scheme_parse(request->hash_method, request->password_scheme, credentials, size, &iter_count, &salt, request->stored_key, request->server_key, &error) < 0) { e_info(auth_request->mech_event, "%s", error); auth_request_fail(auth_request); break; } request->server_first_message = p_strdup(request->pool, get_scram_server_first(request, iter_count, salt)); auth_request_handler_reply_continue(auth_request, request->server_first_message, strlen(request->server_first_message)); break; case PASSDB_RESULT_INTERNAL_FAILURE: auth_request_internal_failure(auth_request); break; default: auth_request_fail(auth_request); break; } } static bool parse_scram_client_final(struct scram_auth_request *request, const unsigned char *data, size_t size, const char **error_r) { const struct hash_method *hmethod = request->hash_method; const char **fields, *cbind_input, *nonce_str; unsigned int field_count; string_t *str; /* RFC 5802, Section 7: client-final-message-without-proof = channel-binding "," nonce ["," extensions] client-final-message = client-final-message-without-proof "," proof */ fields = t_strsplit(t_strndup(data, size), ","); field_count = str_array_length(fields); if (field_count < 3) { *error_r = "Invalid final client message"; return FALSE; } /* channel-binding = "c=" base64 ;; base64 encoding of cbind-input. cbind-data = 1*OCTET cbind-input = gs2-header [ cbind-data ] ;; cbind-data MUST be present for ;; gs2-cbind-flag of "p" and MUST be absent ;; for "y" or "n". */ cbind_input = request->gs2_header; str = t_str_new(2 + MAX_BASE64_ENCODED_SIZE(strlen(cbind_input))); str_append(str, "c="); base64_encode(cbind_input, strlen(cbind_input), str); if (strcmp(fields[0], str_c(str)) != 0) { *error_r = "Invalid channel binding data"; return FALSE; } /* nonce = "r=" c-nonce [s-nonce] ;; Second part provided by server. c-nonce = printable s-nonce = printable */ nonce_str = t_strconcat("r=", request->cnonce, request->snonce, NULL); if (strcmp(fields[1], nonce_str) != 0) { *error_r = "Wrong nonce"; return FALSE; } /* proof = "p=" base64 */ if (fields[field_count-1][0] == 'p') { size_t len = strlen(&fields[field_count-1][2]); request->proof = buffer_create_dynamic(request->pool, MAX_BASE64_DECODED_SIZE(len)); if (base64_decode(&fields[field_count-1][2], len, NULL, request->proof) < 0) { *error_r = "Invalid base64 encoding"; return FALSE; } if (request->proof->used != hmethod->digest_size) { *error_r = "Invalid ClientProof length"; return FALSE; } } else { *error_r = "Invalid ClientProof"; return FALSE; } (void)str_array_remove(fields, fields[field_count-1]); request->client_final_message_without_proof = p_strdup(request->pool, t_strarray_join(fields, ",")); return TRUE; } void mech_scram_auth_continue(struct auth_request *auth_request, const unsigned char *data, size_t data_size) { struct scram_auth_request *request = (struct scram_auth_request *)auth_request; const char *error = NULL; const char *server_final_message; size_t len; if (request->client_first_message_bare == NULL) { /* Received client-first-message */ if (parse_scram_client_first(request, data, data_size, &error)) { auth_request_lookup_credentials( &request->auth_request, request->password_scheme, credentials_callback); return; } } else { /* Received client-final-message */ if (parse_scram_client_final(request, data, data_size, &error)) { if (!verify_credentials(request)) { e_info(auth_request->mech_event, AUTH_LOG_MSG_PASSWORD_MISMATCH); } else { server_final_message = get_scram_server_final(request); len = strlen(server_final_message); auth_request_success(auth_request, server_final_message, len); return; } } } if (error != NULL) e_info(auth_request->mech_event, "%s", error); auth_request_fail(auth_request); } struct auth_request * mech_scram_auth_new(const struct hash_method *hash_method, const char *password_scheme) { struct scram_auth_request *request; pool_t pool; pool = pool_alloconly_create(MEMPOOL_GROWING"scram_auth_request", 2048); request = p_new(pool, struct scram_auth_request, 1); request->pool = pool; request->hash_method = hash_method; request->password_scheme = password_scheme; request->stored_key = p_malloc(pool, hash_method->digest_size); request->server_key = p_malloc(pool, hash_method->digest_size); request->auth_request.pool = pool; return &request->auth_request; } static struct auth_request *mech_scram_sha1_auth_new(void) { return mech_scram_auth_new(&hash_method_sha1, "SCRAM-SHA-1"); } static struct auth_request *mech_scram_sha256_auth_new(void) { return mech_scram_auth_new(&hash_method_sha256, "SCRAM-SHA-256"); } const struct mech_module mech_scram_sha1 = { "SCRAM-SHA-1", .flags = MECH_SEC_MUTUAL_AUTH, .passdb_need = MECH_PASSDB_NEED_LOOKUP_CREDENTIALS, mech_scram_sha1_auth_new, mech_generic_auth_initial, mech_scram_auth_continue, mech_generic_auth_free }; const struct mech_module mech_scram_sha256 = { "SCRAM-SHA-256", .flags = MECH_SEC_MUTUAL_AUTH, .passdb_need = MECH_PASSDB_NEED_LOOKUP_CREDENTIALS, mech_scram_sha256_auth_new, mech_generic_auth_initial, mech_scram_auth_continue, mech_generic_auth_free }; dovecot-2.3.21.1/src/auth/mech-otp.c0000644000000000000000000001372414656633576013734 00000000000000/* * One-Time-Password (RFC 2444) authentication mechanism. * * Copyright (c) 2006 Andrey Panin * * This software is released under the MIT license. */ #include "auth-common.h" #include "safe-memset.h" #include "hash.h" #include "mech.h" #include "passdb.h" #include "hex-binary.h" #include "otp.h" #include "mech-otp-common.h" static void otp_send_challenge(struct auth_request *auth_request, const unsigned char *credentials, size_t size) { struct otp_auth_request *request = (struct otp_auth_request *)auth_request; const char *answer; if (otp_parse_dbentry(t_strndup(credentials, size), &request->state) != 0) { e_error(request->auth_request.mech_event, "invalid OTP data in passdb"); auth_request_fail(auth_request); return; } if (--request->state.seq < 1) { e_error(request->auth_request.mech_event, "sequence number < 1"); auth_request_fail(auth_request); return; } request->lock = otp_try_lock(auth_request); if (!request->lock) { e_error(request->auth_request.mech_event, "user is locked, race attack?"); auth_request_fail(auth_request); return; } answer = p_strdup_printf(request->pool, "otp-%s %u %s ext", digest_name(request->state.algo), request->state.seq, request->state.seed); auth_request_handler_reply_continue(auth_request, answer, strlen(answer)); } static void otp_credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *auth_request) { switch (result) { case PASSDB_RESULT_OK: otp_send_challenge(auth_request, credentials, size); break; case PASSDB_RESULT_INTERNAL_FAILURE: auth_request_internal_failure(auth_request); break; default: auth_request_fail(auth_request); break; } } static void mech_otp_auth_phase1(struct auth_request *auth_request, const unsigned char *data, size_t data_size) { struct otp_auth_request *request = (struct otp_auth_request *)auth_request; const char *authenid, *error; size_t i, count; /* authorization ID \0 authentication ID FIXME: we'll ignore authorization ID for now. */ authenid = NULL; count = 0; for (i = 0; i < data_size; i++) { if (data[i] == '\0') { if (++count == 1) authenid = (const char *) data + i + 1; } } if (count != 1) { e_error(request->auth_request.mech_event, "invalid input"); auth_request_fail(auth_request); return; } if (!auth_request_set_username(auth_request, authenid, &error)) { e_info(auth_request->mech_event, "%s", error); auth_request_fail(auth_request); return; } auth_request_lookup_credentials(auth_request, "OTP", otp_credentials_callback); } static void mech_otp_verify(struct auth_request *auth_request, const char *data, bool hex) { struct otp_auth_request *request = (struct otp_auth_request *)auth_request; struct otp_state *state = &request->state; unsigned char hash[OTP_HASH_SIZE], cur_hash[OTP_HASH_SIZE]; int ret; ret = otp_parse_response(data, hash, hex); if (ret < 0) { e_error(request->auth_request.mech_event, "invalid response"); auth_request_fail(auth_request); otp_unlock(auth_request); return; } otp_next_hash(state->algo, hash, cur_hash); ret = memcmp(cur_hash, state->hash, OTP_HASH_SIZE); if (ret != 0) { auth_request_fail(auth_request); otp_unlock(auth_request); return; } memcpy(state->hash, hash, sizeof(state->hash)); auth_request_set_credentials(auth_request, "OTP", otp_print_dbentry(state), otp_set_credentials_callback); } static void mech_otp_verify_init(struct auth_request *auth_request, const char *data, bool hex) { struct otp_auth_request *request = (struct otp_auth_request *)auth_request; struct otp_state new_state; unsigned char hash[OTP_HASH_SIZE], cur_hash[OTP_HASH_SIZE]; const char *error; int ret; ret = otp_parse_init_response(data, &new_state, cur_hash, hex, &error); if (ret < 0) { e_error(request->auth_request.mech_event, "invalid init response, %s", error); auth_request_fail(auth_request); otp_unlock(auth_request); return; } otp_next_hash(request->state.algo, cur_hash, hash); ret = memcmp(hash, request->state.hash, OTP_HASH_SIZE); if (ret != 0) { auth_request_fail(auth_request); otp_unlock(auth_request); return; } auth_request_set_credentials(auth_request, "OTP", otp_print_dbentry(&new_state), otp_set_credentials_callback); } static void mech_otp_auth_phase2(struct auth_request *auth_request, const unsigned char *data, size_t data_size) { const char *str = t_strndup(data, data_size); if (str_begins(str, "hex:")) { mech_otp_verify(auth_request, str + 4, TRUE); } else if (str_begins(str, "word:")) { mech_otp_verify(auth_request, str + 5, FALSE); } else if (str_begins(str, "init-hex:")) { mech_otp_verify_init(auth_request, str + 9, TRUE); } else if (str_begins(str, "init-word:")) { mech_otp_verify_init(auth_request, str + 10, FALSE); } else { e_error(auth_request->mech_event, "unsupported response type"); auth_request_fail(auth_request); otp_unlock(auth_request); } } static void mech_otp_auth_continue(struct auth_request *auth_request, const unsigned char *data, size_t data_size) { if (auth_request->fields.user == NULL) { mech_otp_auth_phase1(auth_request, data, data_size); } else { mech_otp_auth_phase2(auth_request, data, data_size); } } static struct auth_request *mech_otp_auth_new(void) { struct otp_auth_request *request; pool_t pool; otp_lock_init(); pool = pool_alloconly_create(MEMPOOL_GROWING"otp_auth_request", 2048); request = p_new(pool, struct otp_auth_request, 1); request->pool = pool; request->lock = FALSE; request->auth_request.refcount = 1; request->auth_request.pool = pool; return &request->auth_request; } const struct mech_module mech_otp = { "OTP", .flags = MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE | MECH_SEC_ALLOW_NULS, .passdb_need = MECH_PASSDB_NEED_SET_CREDENTIALS, mech_otp_auth_new, mech_generic_auth_initial, mech_otp_auth_continue, mech_otp_auth_free }; dovecot-2.3.21.1/src/auth/userdb-ldap.c0000644000000000000000000002367314656633576014426 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "userdb.h" #if defined(USERDB_LDAP) && (defined(BUILTIN_LDAP) || defined(PLUGIN_BUILD)) #include "ioloop.h" #include "array.h" #include "str.h" #include "auth-cache.h" #include "db-ldap.h" #include struct ldap_userdb_module { struct userdb_module module; struct ldap_connection *conn; }; struct userdb_ldap_request { struct ldap_request_search request; userdb_callback_t *userdb_callback; unsigned int entries; }; struct userdb_iter_ldap_request { struct ldap_request_search request; struct ldap_userdb_iterate_context *ctx; userdb_callback_t *userdb_callback; }; struct ldap_userdb_iterate_context { struct userdb_iterate_context ctx; struct userdb_iter_ldap_request request; pool_t pool; struct ldap_connection *conn; bool continued, in_callback, deinitialized; }; static void ldap_query_get_result(struct ldap_connection *conn, struct auth_request *auth_request, struct ldap_request_search *ldap_request, LDAPMessage *res) { struct db_ldap_result_iterate_context *ldap_iter; const char *name, *const *values; ldap_iter = db_ldap_result_iterate_init(conn, ldap_request, res, TRUE); while (db_ldap_result_iterate_next(ldap_iter, &name, &values)) { auth_request_set_userdb_field_values(auth_request, name, values); } db_ldap_result_iterate_deinit(&ldap_iter); } static void userdb_ldap_lookup_finish(struct auth_request *auth_request, struct userdb_ldap_request *urequest, LDAPMessage *res) { enum userdb_result result = USERDB_RESULT_INTERNAL_FAILURE; if (res == NULL) { result = USERDB_RESULT_INTERNAL_FAILURE; } else if (urequest->entries == 0) { result = USERDB_RESULT_USER_UNKNOWN; auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB); } else if (urequest->entries > 1) { e_error(authdb_event(auth_request), "user_filter matched multiple objects, aborting"); result = USERDB_RESULT_INTERNAL_FAILURE; } else { result = USERDB_RESULT_OK; } urequest->userdb_callback(result, auth_request); } static void userdb_ldap_lookup_callback(struct ldap_connection *conn, struct ldap_request *request, LDAPMessage *res) { struct userdb_ldap_request *urequest = (struct userdb_ldap_request *) request; struct auth_request *auth_request = urequest->request.request.auth_request; if (res == NULL || ldap_msgtype(res) == LDAP_RES_SEARCH_RESULT) { userdb_ldap_lookup_finish(auth_request, urequest, res); auth_request_unref(&auth_request); return; } if (urequest->entries++ == 0) { /* first entry */ ldap_query_get_result(conn, auth_request, &urequest->request, res); } } static void userdb_ldap_lookup(struct auth_request *auth_request, userdb_callback_t *callback) { struct userdb_module *_module = auth_request->userdb->userdb; struct ldap_userdb_module *module = (struct ldap_userdb_module *)_module; struct ldap_connection *conn = module->conn; const char **attr_names = (const char **)conn->user_attr_names; struct userdb_ldap_request *request; const char *error; string_t *str; auth_request_ref(auth_request); request = p_new(auth_request->pool, struct userdb_ldap_request, 1); request->userdb_callback = callback; str = t_str_new(512); if (auth_request_var_expand(str, conn->set.base, auth_request, ldap_escape, &error) <= 0) { e_error(authdb_event(auth_request), "Failed to expand base=%s: %s", conn->set.base, error); callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request); return; } request->request.base = p_strdup(auth_request->pool, str_c(str)); str_truncate(str, 0); if (auth_request_var_expand(str, conn->set.user_filter, auth_request, ldap_escape, &error) <= 0) { e_error(authdb_event(auth_request), "Failed to expand user_filter=%s: %s", conn->set.user_filter, error); callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request); return; } request->request.filter = p_strdup(auth_request->pool, str_c(str)); request->request.attr_map = &conn->user_attr_map; request->request.attributes = conn->user_attr_names; e_debug(authdb_event(auth_request), "user search: " "base=%s scope=%s filter=%s fields=%s", request->request.base, conn->set.scope, request->request.filter, attr_names == NULL ? "(all)" : t_strarray_join(attr_names, ",")); request->request.request.auth_request = auth_request; request->request.request.callback = userdb_ldap_lookup_callback; db_ldap_request(conn, &request->request.request); } static void userdb_ldap_iterate_callback(struct ldap_connection *conn, struct ldap_request *request, LDAPMessage *res) { struct userdb_iter_ldap_request *urequest = (struct userdb_iter_ldap_request *)request; struct ldap_userdb_iterate_context *ctx = urequest->ctx; struct db_ldap_result_iterate_context *ldap_iter; const char *name, *const *values; if (res == NULL || ldap_msgtype(res) == LDAP_RES_SEARCH_RESULT) { if (res == NULL) ctx->ctx.failed = TRUE; if (!ctx->deinitialized) ctx->ctx.callback(NULL, ctx->ctx.context); auth_request_unref(&request->auth_request); return; } if (ctx->deinitialized) return; /* the iteration can take a while. reset the request's create time so it won't be aborted while it's still running */ request->create_time = ioloop_time; ctx->in_callback = TRUE; ldap_iter = db_ldap_result_iterate_init(conn, &urequest->request, res, TRUE); while (db_ldap_result_iterate_next(ldap_iter, &name, &values)) { if (strcmp(name, "user") != 0) { e_warning(authdb_event(request->auth_request), "iterate: " "Ignoring field not named 'user': %s", name); continue; } for (; *values != NULL; values++) { ctx->continued = FALSE; ctx->ctx.callback(*values, ctx->ctx.context); } } db_ldap_result_iterate_deinit(&ldap_iter); if (!ctx->continued) db_ldap_enable_input(conn, FALSE); ctx->in_callback = FALSE; } static struct userdb_iterate_context * userdb_ldap_iterate_init(struct auth_request *auth_request, userdb_iter_callback_t *callback, void *context) { struct userdb_module *_module = auth_request->userdb->userdb; struct ldap_userdb_module *module = (struct ldap_userdb_module *)_module; struct ldap_connection *conn = module->conn; struct ldap_userdb_iterate_context *ctx; struct userdb_iter_ldap_request *request; const char **attr_names = (const char **)conn->iterate_attr_names; const char *error; string_t *str; ctx = p_new(auth_request->pool, struct ldap_userdb_iterate_context, 1); ctx->ctx.auth_request = auth_request; ctx->ctx.callback = callback; ctx->ctx.context = context; ctx->conn = conn; request = &ctx->request; request->ctx = ctx; auth_request_ref(auth_request); request->request.request.auth_request = auth_request; str = t_str_new(512); if (auth_request_var_expand(str, conn->set.base, auth_request, ldap_escape, &error) <= 0) { e_error(authdb_event(auth_request), "Failed to expand base=%s: %s", conn->set.base, error); ctx->ctx.failed = TRUE; } request->request.base = p_strdup(auth_request->pool, str_c(str)); str_truncate(str, 0); if (auth_request_var_expand(str, conn->set.iterate_filter, auth_request, ldap_escape, &error) <= 0) { e_error(authdb_event(auth_request), "Failed to expand iterate_filter=%s: %s", conn->set.iterate_filter, error); ctx->ctx.failed = TRUE; } request->request.filter = p_strdup(auth_request->pool, str_c(str)); request->request.attr_map = &conn->iterate_attr_map; request->request.attributes = conn->iterate_attr_names; request->request.multi_entry = TRUE; e_debug(auth_request->event, "ldap: iterate: base=%s scope=%s filter=%s fields=%s", request->request.base, conn->set.scope, request->request.filter, attr_names == NULL ? "(all)" : t_strarray_join(attr_names, ",")); request->request.request.callback = userdb_ldap_iterate_callback; db_ldap_request(conn, &request->request.request); return &ctx->ctx; } static void userdb_ldap_iterate_next(struct userdb_iterate_context *_ctx) { struct ldap_userdb_iterate_context *ctx = (struct ldap_userdb_iterate_context *)_ctx; ctx->continued = TRUE; if (!ctx->in_callback) db_ldap_enable_input(ctx->conn, TRUE); } static int userdb_ldap_iterate_deinit(struct userdb_iterate_context *_ctx) { struct ldap_userdb_iterate_context *ctx = (struct ldap_userdb_iterate_context *)_ctx; int ret = _ctx->failed ? -1 : 0; db_ldap_enable_input(ctx->conn, TRUE); ctx->deinitialized = TRUE; return ret; } static struct userdb_module * userdb_ldap_preinit(pool_t pool, const char *args) { struct ldap_userdb_module *module; struct ldap_connection *conn; module = p_new(pool, struct ldap_userdb_module, 1); module->conn = conn = db_ldap_init(args, TRUE); p_array_init(&conn->user_attr_map, pool, 16); p_array_init(&conn->iterate_attr_map, pool, 16); db_ldap_set_attrs(conn, conn->set.user_attrs, &conn->user_attr_names, &conn->user_attr_map, NULL); db_ldap_set_attrs(conn, conn->set.iterate_attrs, &conn->iterate_attr_names, &conn->iterate_attr_map, NULL); module->module.blocking = conn->set.blocking; module->module.default_cache_key = auth_cache_parse_key(pool, t_strconcat(conn->set.base, conn->set.user_attrs, conn->set.user_filter, NULL)); return &module->module; } static void userdb_ldap_init(struct userdb_module *_module) { struct ldap_userdb_module *module = (struct ldap_userdb_module *)_module; if (!module->module.blocking || worker) db_ldap_connect_delayed(module->conn); } static void userdb_ldap_deinit(struct userdb_module *_module) { struct ldap_userdb_module *module = (struct ldap_userdb_module *)_module; db_ldap_unref(&module->conn); } #ifndef PLUGIN_BUILD struct userdb_module_interface userdb_ldap = #else struct userdb_module_interface userdb_ldap_plugin = #endif { "ldap", userdb_ldap_preinit, userdb_ldap_init, userdb_ldap_deinit, userdb_ldap_lookup, userdb_ldap_iterate_init, userdb_ldap_iterate_next, userdb_ldap_iterate_deinit }; #else struct userdb_module_interface userdb_ldap = { .name = "ldap" }; #endif dovecot-2.3.21.1/src/auth/auth-client-connection.h0000644000000000000000000000154014656633576016570 00000000000000#ifndef AUTH_CLIENT_CONNECTION_H #define AUTH_CLIENT_CONNECTION_H #include "master-auth.h" struct auth_client_connection { struct auth_client_connection *prev, *next; struct auth *auth; struct event *event; int refcount; int fd; struct io *io; struct istream *input; struct ostream *output; unsigned int version_minor; unsigned int pid; unsigned int connect_uid; uint8_t cookie[MASTER_AUTH_COOKIE_SIZE]; struct auth_request_handler *request_handler; bool login_requests:1; bool version_received:1; bool token_auth:1; }; void auth_client_connection_create(struct auth *auth, int fd, bool login_requests, bool token_auth); void auth_client_connection_destroy(struct auth_client_connection **conn); struct auth_client_connection * auth_client_connection_lookup(unsigned int pid); void auth_client_connections_destroy_all(void); #endif dovecot-2.3.21.1/src/auth/test-mech.c0000644000000000000000000004552714656633576014117 00000000000000/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ #include "test-auth.h" #include "auth.h" #include "str.h" #include "auth-common.h" #include "auth-request.h" #include "auth-request-handler-private.h" #include "auth-settings.h" #include "mech-digest-md5-private.h" #include "otp.h" #include "mech-otp-common.h" #include "settings-parser.h" #include "password-scheme.h" #include "auth-token.h" #include #include #define UCHAR_LEN(str) (const unsigned char *)(str), sizeof(str)-1 extern const struct mech_module mech_anonymous; extern const struct mech_module mech_apop; extern const struct mech_module mech_cram_md5; extern const struct mech_module mech_digest_md5; extern const struct mech_module mech_dovecot_token; extern const struct mech_module mech_external; extern const struct mech_module mech_login; extern const struct mech_module mech_oauthbearer; extern const struct mech_module mech_otp; extern const struct mech_module mech_plain; extern const struct mech_module mech_scram_sha1; extern const struct mech_module mech_scram_sha256; extern const struct mech_module mech_xoauth2; static struct auth_settings set; static struct mechanisms_register *mech_reg; struct test_case { const struct mech_module *mech; const unsigned char *in; size_t len; const char *username; const char *expect_error; bool success; bool set_username_before_test; bool set_cert_username; }; static void verify_plain_continue_mock_callback(struct auth_request *request, verify_plain_callback_t *callback) { request->passdb_success = TRUE; callback(PASSDB_RESULT_OK, request); } static void request_handler_reply_mock_callback(struct auth_request *request, enum auth_client_result result, const void *auth_reply ATTR_UNUSED, size_t reply_size ATTR_UNUSED) { request->failed = result != AUTH_CLIENT_RESULT_SUCCESS; if (request->passdb_result == PASSDB_RESULT_OK) request->failed = FALSE; else if (request->mech == &mech_otp) { if (null_strcmp(request->fields.user, "otp_phase_2") == 0) request->failed = FALSE; } else if (request->mech == &mech_oauthbearer) { } }; static void request_handler_reply_continue_mock_callback(struct auth_request *request, const void *reply, size_t reply_size) { request->context = p_strndup(request->pool, reply, reply_size); } static void auth_client_request_mock_callback(const char *reply ATTR_UNUSED, struct auth_client_connection *conn ATTR_UNUSED) { } static void test_mechs_init(void) { const char *const services[] = {NULL}; process_start_time = time(NULL); /* Copy default settings */ set = *(const struct auth_settings *)auth_setting_parser_info.defaults; global_auth_settings = &set; global_auth_settings->base_dir = "."; memset((&set)->username_chars_map, 1, sizeof((&set)->username_chars_map)); set.username_format = ""; t_array_init(&set.passdbs, 2); struct auth_passdb_settings *mock_set = t_new(struct auth_passdb_settings, 1); *mock_set = mock_passdb_set; array_push_back(&set.passdbs, &mock_set); mock_set = t_new(struct auth_passdb_settings, 1); *mock_set = mock_passdb_set; mock_set->master = TRUE; array_push_back(&set.passdbs, &mock_set); t_array_init(&set.userdbs, 1); /* Disable stats */ set.stats = FALSE; /* For tests of digest-md5. */ set.realms_arr = t_strsplit_spaces("example.com ", " "); /* For tests of mech-anonymous. */ set.anonymous_username = "anonuser"; mech_init(global_auth_settings); mech_reg = mech_register_init(global_auth_settings); passdbs_init(); userdbs_init(); passdb_mock_mod_init(); password_schemes_init(); auths_preinit(&set, pool_datastack_create(), mech_reg, services); auths_init(); auth_token_init(); } static void test_mech_prepare_request(struct auth_request **request_r, const struct mech_module *mech, struct auth_request_handler *handler, unsigned int running_test, const struct test_case *test_case) { global_auth_settings->ssl_username_from_cert = test_case->set_cert_username; struct auth *auth = auth_default_service(); struct auth_request *request = auth_request_new(mech, NULL); request->handler = handler; request->id = running_test+1; request->mech_password = NULL; request->state = AUTH_REQUEST_STATE_NEW; request->set = global_auth_settings; request->connect_uid = running_test; request->passdb = auth->passdbs; request->userdb = auth->userdbs; handler->refcount = 1; auth_fields_add(request->fields.extra_fields, "nodelay", "", 0); auth_request_ref(request); auth_request_state_count[AUTH_REQUEST_STATE_NEW] = 1; if (test_case->set_username_before_test || test_case->set_cert_username) request->fields.user = p_strdup(request->pool, test_case->username); if (test_case->set_cert_username) request->fields.cert_username = TRUE; *request_r = request; } static void test_mech_handle_challenge(struct auth_request *request, const unsigned char *in, size_t in_len, unsigned int running_test, bool expected_success) { string_t *out = t_str_new(16); str_append_data(out, in, in_len); const char *challenge = request->context; if (request->mech == &mech_login) { /* We do not care about any specific password just give * the username input as password also in case it's wanted. */ if (expected_success) test_assert_strcmp_idx(challenge, "Password:", running_test); else test_assert_strcmp_idx(challenge, "Username:", running_test); } else if (request->mech == &mech_cram_md5 && *in != '\0') { str_truncate(out, 0); str_append(out, "testuser b913a602c7eda7a495b4e6e7334d3890"); } else if (request->mech == &mech_digest_md5) { struct digest_auth_request *digest_request = (struct digest_auth_request *) request; digest_request->nonce = "OA6MG9tEQGm2hh"; } auth_request_continue(request, out->data, out->used); } static inline const unsigned char * test_mech_construct_apop_challenge(unsigned int connect_uid, size_t *len_r) { string_t *apop_challenge = t_str_new(128); str_printfa(apop_challenge,"<%lx.%lx.%"PRIxTIME_T".", (unsigned long)getpid(), (unsigned long)connect_uid, process_start_time+10); str_append_data(apop_challenge, "\0testuser\0responseoflen16-", 26); *len_r = apop_challenge->used; return apop_challenge->data; } static void test_mechs(void) { static struct auth_request_handler handler = { .callback = auth_client_request_mock_callback, .reply_callback = request_handler_reply_mock_callback, .reply_continue_callback = request_handler_reply_continue_mock_callback, .verify_plain_continue_callback = verify_plain_continue_mock_callback, }; static struct test_case tests[] = { /* Expected to be successful */ {&mech_anonymous, UCHAR_LEN("\0any \0 bad \0 content"), "anonuser", NULL, TRUE, FALSE, FALSE}, {&mech_apop, NULL, 0, "testuser", NULL, TRUE, FALSE, FALSE}, {&mech_cram_md5, UCHAR_LEN("testuser b913a602c7eda7a495b4e6e7334d3890"), "testuser", NULL, TRUE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("username=\"testuser@example.com\",realm=\"example.com\",nonce=\"OA6MG9tEQGm2hh\",cnonce=\"OA6MHXh6VqTrRk\",nc=00000001,digest-uriresponse=d388dad90d4bbd760a152321f2143af7,qop=\"auth\""), "testuser@example.com", NULL,TRUE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("username=\"testuser@example.com\",realm=\"example.com\",nonce=\"OA6MG9tEQGm2hh\",cnonce=\"OA6MHXh6VqTrRk\",nc=00000001,digest-uriresponse=d388dad90d4bbd760a152321f2143af7,qop=\"auth\",authzid=\"masteruser\""), "testuser@example.com", NULL, TRUE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("username=\"test\xc3\xbaser@example.com\",realm=\"example.com\",nonce=\"OA6MG9tEQGm2hh\",cnonce=\"OA6MHXh6VqTrRk\",nc=00000001,digest-uriresponse=d388dad90d4bbd760a152321f2143af7,qop=\"auth\",authzid=\"masteruser\""), "test\xc3\xbaser@example.com", NULL, TRUE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("username=\"test\xc3\xbaser@example.com\",realm=\"example.com\",nonce=\"OA6MG9tEQGm2hh\",cnonce=\"OA6MHXh6VqTrRk\",charset=\"utf-8\",cipher=unsupported,nc=00000001,digest-uri=imap/server.com,response=d388dad90d4bbd760a152321f2143af7,qop=\"auth\",authzid=\"masteruser\""), "test\xc3\xbaser@example.com", NULL, TRUE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("username=\"testuser\",realm=\"example.com\",nonce=\"OA6MG9tEQGm2hh\",cnonce=\"OA6MHXh6VqTrRk\",charset=\"utf-8\",cipher=unsupported,nc=00000001,digest-uri=imap/server.com,response=d388dad90d4bbd760a152321f2143af7,qop=\"auth\",authzid=\"masteruser\""), "testuser@example.com", NULL, TRUE, FALSE, FALSE}, {&mech_external, UCHAR_LEN(""), "testuser", NULL, TRUE, TRUE, TRUE}, {&mech_dovecot_token, NULL, 0, "testuser", NULL, TRUE, FALSE, FALSE}, {&mech_login, UCHAR_LEN("testuser"), "testuser", NULL, TRUE, FALSE, FALSE}, {&mech_plain, UCHAR_LEN("\0testuser\0testpass"), "testuser", NULL, TRUE, FALSE, FALSE}, {&mech_plain, UCHAR_LEN("normaluser\0masteruser\0masterpass"), "masteruser", NULL, TRUE, FALSE, FALSE}, {&mech_plain, UCHAR_LEN("normaluser\0normaluser\0masterpass"), "normaluser", NULL, TRUE, FALSE, FALSE}, {&mech_otp, UCHAR_LEN("hex:5Bf0 75d9 959d 036f"), "otp_phase_2", NULL, TRUE, TRUE, FALSE}, {&mech_otp, UCHAR_LEN("word:BOND FOGY DRAB NE RISE MART"), "otp_phase_2", NULL, TRUE, TRUE, FALSE}, {&mech_otp, UCHAR_LEN("init-hex:f6bd 6b33 89b8 7203:md5 499 ke6118:23d1 b253 5ae0 2b7e"), "otp_phase_2", NULL, TRUE, TRUE, FALSE}, {&mech_otp, UCHAR_LEN("init-word:END KERN BALM NICK EROS WAVY:md5 499 ke1235:BABY FAIN OILY NIL TIDY DADE"), "otp_phase_2", NULL , TRUE, TRUE, FALSE}, {&mech_oauthbearer, UCHAR_LEN("n,a=testuser,p=cHJvb2Y=,f=nonstandart\x01host=server\x01port=143\x01""auth=Bearer vF9dft4qmTc2Nvb3RlckBhbHRhdmlzdGEuY29tCg==\x01\x01"), "testuser", NULL, FALSE, TRUE, FALSE}, {&mech_scram_sha1, UCHAR_LEN("n,,n=testuser,r=rOprNGfwEbeRWgbNEkqO"), "testuser", NULL, TRUE, FALSE, FALSE}, {&mech_scram_sha256, UCHAR_LEN("n,,n=testuser,r=rOprNGfwEbeRWgbNEkqO"), "testuser", NULL, TRUE, FALSE, FALSE}, {&mech_xoauth2, UCHAR_LEN("user=testuser\x01""auth=Bearer vF9dft4qmTc2Nvb3RlckBhdHRhdmlzdGEuY29tCg==\x01\x01"), "testuser", NULL, TRUE, FALSE, FALSE}, /* Below tests are expected to fail */ /* Empty input tests*/ {&mech_apop, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_cram_md5, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_dovecot_token, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_external, UCHAR_LEN(""), "testuser", NULL, FALSE, TRUE, FALSE}, {&mech_external, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_login, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_otp, UCHAR_LEN(""), NULL, "invalid input", FALSE, FALSE, FALSE}, {&mech_otp, UCHAR_LEN(""), "testuser", "invalid input", FALSE, FALSE, FALSE}, {&mech_plain, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_oauthbearer, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_xoauth2, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_scram_sha1, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_scram_sha256, UCHAR_LEN(""), NULL, NULL, FALSE, FALSE, FALSE}, /* Bad input tests*/ {&mech_apop, UCHAR_LEN("1.1.1\0test\0user\0response"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_apop, UCHAR_LEN("1.1.1\0testuser\0tooshort"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_apop, UCHAR_LEN("1.1.1\0testuser\0responseoflen16-"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_apop, UCHAR_LEN("1.1.1"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_otp, UCHAR_LEN("somebody\0testuser"), "testuser", "otp(testuser): unsupported response type", FALSE, TRUE, FALSE}, {&mech_cram_md5, UCHAR_LEN("testuser\0response"), "testuser", NULL, FALSE, FALSE, FALSE}, {&mech_plain, UCHAR_LEN("testuser\0"), "testuser", NULL, FALSE, FALSE, FALSE}, /* Covering most of the digest md5 parsing */ {&mech_digest_md5, UCHAR_LEN("username=\"testuser@example.com\",realm=\"example.com\",cnonce=\"OA6MHXh6VqTrRk\",response=d388dad90d4bbd760a152321f2143af7,qop=\"auth\""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("realm=\"example.com\",cnonce=\"OA6MHXh6VqTrRk\",nonce=\"OA6MG9tEQGm2hh\""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("username=\"testuser@example.com\",realm=\"example.com\", nonce=\"OA6MG9tEQGm2hh\""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("qop=\"auth-int\""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("qop=\"auth-int\""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("qop=\"auth-conf\",\"cipher=rc4\""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("cnonce=\"OA6MHXh6VqTrRk\",cnonce=\"OA6MHXh6VqTrRk\""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("cnonce=\"\""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("nonce=\"not matching\""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("nc=00000001,nc=00000002"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("nc=NAN"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("nc=00000002"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("cipher=unsupported"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("digest-uri="), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("username=\"\""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("username=\"a\",username=\"b\""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("response=broken"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("maxbuf=32,maxbuf=1024"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("maxbuf=broken"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("authzid=\"somebody\",authzid=\"else\""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("authzid=\"\""), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("charset=unsupported"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_digest_md5, UCHAR_LEN("qop=unsupported"), NULL, NULL, FALSE, FALSE, FALSE}, /* Too much nuls */ {&mech_dovecot_token, UCHAR_LEN("service\0pid\0fail\0se\0ssion_id\0deadbeef"), NULL , NULL, FALSE, FALSE, FALSE}, {&mech_login, UCHAR_LEN("test user\0user"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_oauthbearer, UCHAR_LEN("n,a==testuser,\x01""auth=Bearer token\x01\x01"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_oauthbearer, UCHAR_LEN("n,a=testuser,f=non-standard\x01""auth=Bearer token\x01\x01"), "testuser", NULL, FALSE, FALSE, FALSE}, {&mech_oauthbearer, UCHAR_LEN("n,a=testuser\x01""auth=token\x01\x01"), "testuser", NULL, FALSE, FALSE, FALSE}, {&mech_xoauth2, UCHAR_LEN("testuser\x01auth=Bearer token\x01\x01"), NULL, NULL, FALSE, FALSE, FALSE}, /* does not start with [B|b]earer */ {&mech_xoauth2, UCHAR_LEN("user=testuser\x01""auth=token\x01\x01"), "testuser", NULL, FALSE, FALSE, FALSE}, /* Too much nuls */ {&mech_plain, UCHAR_LEN("\0fa\0il\0ing\0withthis"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_plain, UCHAR_LEN("failingwiththis"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_plain, UCHAR_LEN("failing\0withthis"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_otp, UCHAR_LEN("someb\0ody\0testuser"), NULL, "invalid input", FALSE, FALSE, FALSE}, /* phase 2 */ {&mech_otp, UCHAR_LEN("someb\0ody\0testuser"), "testuser", "otp(testuser): unsupported response type", FALSE, TRUE, FALSE}, {&mech_scram_sha1, UCHAR_LEN("c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts="), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_scram_sha1, UCHAR_LEN("iws0X8v3Bz2T0CJGbJQyF0X+HI4Ts=,,,,"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_scram_sha1, UCHAR_LEN("n,a=masteruser,,"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_scram_sha1, UCHAR_LEN("n,a==masteruser,,"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_scram_sha1, UCHAR_LEN("n,,m=testuser,,"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_scram_sha1, UCHAR_LEN("broken\0input"), NULL, NULL, FALSE, FALSE, FALSE}, {&mech_scram_sha256, UCHAR_LEN("broken\0input"), NULL, NULL, FALSE, FALSE, FALSE}, }; test_mechs_init(); string_t *d_token = t_str_new(32); str_append_data(d_token, UCHAR_LEN("service\0pid\0testuser\0session\0")); str_append(d_token, auth_token_get("service","pid","testuser","session")); for (unsigned int running_test = 0; running_test < N_ELEMENTS(tests); running_test++) T_BEGIN { struct test_case *test_case = &tests[running_test]; const struct mech_module *mech = test_case->mech; struct auth_request *request; const char *testname = t_strdup_printf("auth mech %s %d/%zu", mech->mech_name, running_test+1, N_ELEMENTS(tests)); test_begin(testname); test_mech_prepare_request(&request, mech, &handler, running_test, test_case); if (mech == &mech_apop && test_case->in == NULL) test_case->in = test_mech_construct_apop_challenge(request->connect_uid, &test_case->len); if (mech == &mech_dovecot_token && test_case->in == NULL) { test_case->in = d_token->data; test_case->len = d_token->used; } if (test_case->expect_error != NULL) test_expect_error_string(test_case->expect_error); request->state = AUTH_REQUEST_STATE_NEW; unsigned char *input_dup = test_case->len == 0 ? NULL : i_memdup(test_case->in, test_case->len); request->initial_response = input_dup; request->initial_response_len = test_case->len; auth_request_initial(request); const char *challenge = request->context; if (challenge != NULL) { test_mech_handle_challenge(request, test_case->in, test_case->len, running_test, test_case->success); } const char *username = request->fields.user; if (request->fields.master_user != NULL) username = request->fields.master_user; if (!test_case->set_username_before_test && test_case->success) { /* If the username was set by the test logic, do not * compare it as it does not give any additional * information */ test_assert_strcmp_idx(test_case->username, username, running_test); } else if (!test_case->set_username_before_test && !test_case->success) { /* If the username is not set by the testlogic and we * expect failure, verify that the mechanism failed by * checking that the username is not set */ test_assert_idx(username == NULL, running_test); } if (test_case->success) test_assert_idx(request->failed == FALSE, running_test); else test_assert_idx(request->failed == TRUE, running_test); event_unref(&request->event); event_unref(&request->mech_event); i_free(input_dup); mech->auth_free(request); test_end(); } T_END; mech_otp_deinit(); auths_deinit(); auth_token_deinit(); password_schemes_deinit(); passdb_mock_mod_deinit(); passdbs_deinit(); userdbs_deinit(); event_unref(&auth_event); mech_deinit(global_auth_settings); mech_register_deinit(&mech_reg); auths_free(); i_unlink("auth-token-secret.dat"); } int main(void) { static void (*const test_functions[])(void) = { test_mechs, NULL }; return test_run(test_functions); } dovecot-2.3.21.1/src/auth/mech.c0000644000000000000000000001544014656633576013131 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "ioloop.h" #include "mech.h" #include "str.h" #include "strfuncs.h" #include "passdb.h" #include static struct mech_module_list *mech_modules; void mech_register_module(const struct mech_module *module) { struct mech_module_list *list; i_assert(strcmp(module->mech_name, t_str_ucase(module->mech_name)) == 0); list = i_new(struct mech_module_list, 1); list->module = *module; list->next = mech_modules; mech_modules = list; } void mech_unregister_module(const struct mech_module *module) { struct mech_module_list **pos, *list; for (pos = &mech_modules; *pos != NULL; pos = &(*pos)->next) { if (strcmp((*pos)->module.mech_name, module->mech_name) == 0) { list = *pos; *pos = (*pos)->next; i_free(list); break; } } } const struct mech_module *mech_module_find(const char *name) { struct mech_module_list *list; name = t_str_ucase(name); for (list = mech_modules; list != NULL; list = list->next) { if (strcmp(list->module.mech_name, name) == 0) return &list->module; } return NULL; } void mech_generic_auth_initial(struct auth_request *request, const unsigned char *data, size_t data_size) { if (data == NULL) { auth_request_handler_reply_continue(request, uchar_empty_ptr, 0); } else { /* initial reply given, even if it was 0 bytes */ request->mech->auth_continue(request, data, data_size); } } void mech_generic_auth_free(struct auth_request *request) { pool_unref(&request->pool); } extern const struct mech_module mech_plain; extern const struct mech_module mech_login; extern const struct mech_module mech_apop; extern const struct mech_module mech_cram_md5; extern const struct mech_module mech_digest_md5; extern const struct mech_module mech_external; extern const struct mech_module mech_otp; extern const struct mech_module mech_scram_sha1; extern const struct mech_module mech_scram_sha256; extern const struct mech_module mech_anonymous; #ifdef HAVE_GSSAPI extern const struct mech_module mech_gssapi; #endif #ifdef HAVE_GSSAPI_SPNEGO extern const struct mech_module mech_gssapi_spnego; #endif extern const struct mech_module mech_winbind_ntlm; extern const struct mech_module mech_winbind_spnego; extern const struct mech_module mech_oauthbearer; extern const struct mech_module mech_xoauth2; static void mech_register_add(struct mechanisms_register *reg, const struct mech_module *mech) { struct mech_module_list *list; list = p_new(reg->pool, struct mech_module_list, 1); list->module = *mech; str_printfa(reg->handshake, "MECH\t%s", mech->mech_name); if ((mech->flags & MECH_SEC_PRIVATE) != 0) str_append(reg->handshake, "\tprivate"); if ((mech->flags & MECH_SEC_ANONYMOUS) != 0) str_append(reg->handshake, "\tanonymous"); if ((mech->flags & MECH_SEC_PLAINTEXT) != 0) str_append(reg->handshake, "\tplaintext"); if ((mech->flags & MECH_SEC_DICTIONARY) != 0) str_append(reg->handshake, "\tdictionary"); if ((mech->flags & MECH_SEC_ACTIVE) != 0) str_append(reg->handshake, "\tactive"); if ((mech->flags & MECH_SEC_FORWARD_SECRECY) != 0) str_append(reg->handshake, "\tforward-secrecy"); if ((mech->flags & MECH_SEC_MUTUAL_AUTH) != 0) str_append(reg->handshake, "\tmutual-auth"); str_append_c(reg->handshake, '\n'); list->next = reg->modules; reg->modules = list; } static const char *mech_get_plugin_name(const char *name) { string_t *str = t_str_new(32); str_append(str, "mech_"); for (; *name != '\0'; name++) { if (*name == '-') str_append_c(str, '_'); else str_append_c(str, i_tolower(*name)); } return str_c(str); } struct mechanisms_register * mech_register_init(const struct auth_settings *set) { struct mechanisms_register *reg; const struct mech_module *mech; const char *const *mechanisms; pool_t pool; pool = pool_alloconly_create("mechanisms register", 1024); reg = p_new(pool, struct mechanisms_register, 1); reg->pool = pool; reg->set = set; reg->handshake = str_new(pool, 512); mechanisms = t_strsplit_spaces(set->mechanisms, " "); for (; *mechanisms != NULL; mechanisms++) { const char *name = t_str_ucase(*mechanisms); if (strcmp(name, "ANONYMOUS") == 0) { if (*set->anonymous_username == '\0') { i_fatal("ANONYMOUS listed in mechanisms, " "but anonymous_username not set"); } } mech = mech_module_find(name); if (mech == NULL) { /* maybe it's a plugin. try to load it. */ auth_module_load(mech_get_plugin_name(name)); mech = mech_module_find(name); } if (mech == NULL) i_fatal("Unknown authentication mechanism '%s'", name); mech_register_add(reg, mech); } if (reg->modules == NULL) i_fatal("No authentication mechanisms configured"); return reg; } void mech_register_deinit(struct mechanisms_register **_reg) { struct mechanisms_register *reg = *_reg; *_reg = NULL; pool_unref(®->pool); } const struct mech_module * mech_register_find(const struct mechanisms_register *reg, const char *name) { const struct mech_module_list *list; name = t_str_ucase(name); for (list = reg->modules; list != NULL; list = list->next) { if (strcmp(list->module.mech_name, name) == 0) return &list->module; } return NULL; } void mech_init(const struct auth_settings *set) { mech_register_module(&mech_plain); mech_register_module(&mech_login); mech_register_module(&mech_apop); mech_register_module(&mech_cram_md5); mech_register_module(&mech_digest_md5); mech_register_module(&mech_external); if (set->use_winbind) { mech_register_module(&mech_winbind_ntlm); mech_register_module(&mech_winbind_spnego); } else { #if defined(HAVE_GSSAPI_SPNEGO) && defined(BUILTIN_GSSAPI) mech_register_module(&mech_gssapi_spnego); #endif } mech_register_module(&mech_otp); mech_register_module(&mech_scram_sha1); mech_register_module(&mech_scram_sha256); mech_register_module(&mech_anonymous); #ifdef BUILTIN_GSSAPI mech_register_module(&mech_gssapi); #endif mech_register_module(&mech_oauthbearer); mech_register_module(&mech_xoauth2); } void mech_deinit(const struct auth_settings *set) { mech_unregister_module(&mech_plain); mech_unregister_module(&mech_login); mech_unregister_module(&mech_apop); mech_unregister_module(&mech_cram_md5); mech_unregister_module(&mech_digest_md5); mech_unregister_module(&mech_external); if (set->use_winbind) { mech_unregister_module(&mech_winbind_ntlm); mech_unregister_module(&mech_winbind_spnego); } else { #if defined(HAVE_GSSAPI_SPNEGO) && defined(BUILTIN_GSSAPI) mech_unregister_module(&mech_gssapi_spnego); #endif } mech_unregister_module(&mech_otp); mech_unregister_module(&mech_scram_sha1); mech_unregister_module(&mech_scram_sha256); mech_unregister_module(&mech_anonymous); #ifdef BUILTIN_GSSAPI mech_unregister_module(&mech_gssapi); #endif mech_unregister_module(&mech_oauthbearer); mech_unregister_module(&mech_xoauth2); } dovecot-2.3.21.1/src/auth/mech-digest-md5.c0000644000000000000000000003357214656633576015077 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ /* Digest-MD5 SASL authentication, see RFC-2831 */ #include "auth-common.h" #include "base64.h" #include "buffer.h" #include "hex-binary.h" #include "mech-digest-md5-private.h" #include "md5.h" #include "randgen.h" #include "str.h" #include "str-sanitize.h" #include "mech.h" #include "passdb.h" #define MAX_REALM_LEN 64 /* Linear whitespace */ #define IS_LWS(c) ((c) == ' ' || (c) == '\t') static const char *qop_names[] = { "auth", "auth-int", "auth-conf" }; static string_t *get_digest_challenge(struct digest_auth_request *request) { const struct auth_settings *set = request->auth_request.set; buffer_t buf; string_t *str; const char *const *tmp; unsigned char nonce[16]; unsigned char nonce_base64[MAX_BASE64_ENCODED_SIZE(sizeof(nonce))+1]; int i; bool first_qop; /* realm="hostname" (multiple allowed) nonce="randomized data, at least 64bit" qop="auth,auth-int,auth-conf" maxbuf=number (with auth-int, auth-conf, defaults to 64k) charset="utf-8" (iso-8859-1 if it doesn't exist) algorithm="md5-sess" cipher="3des,des,rc4-40,rc4,rc4-56" (with auth-conf) */ /* get 128bit of random data as nonce */ random_fill(nonce, sizeof(nonce)); buffer_create_from_data(&buf, nonce_base64, sizeof(nonce_base64)); base64_encode(nonce, sizeof(nonce), &buf); buffer_append_c(&buf, '\0'); request->nonce = p_strdup(request->pool, buf.data); str = t_str_new(256); if (*set->realms_arr == NULL) { /* If no realms are given, at least Cyrus SASL client defaults to destination host name */ str_append(str, "realm=\"\","); } else { for (tmp = set->realms_arr; *tmp != NULL; tmp++) str_printfa(str, "realm=\"%s\",", *tmp); } str_printfa(str, "nonce=\"%s\",", request->nonce); str_append(str, "qop=\""); first_qop = TRUE; for (i = 0; i < QOP_COUNT; i++) { if ((request->qop & (1 << i)) != 0) { if (first_qop) first_qop = FALSE; else str_append_c(str, ','); str_append(str, qop_names[i]); } } str_append(str, "\","); str_append(str, "charset=\"utf-8\"," "algorithm=\"md5-sess\""); return str; } static bool verify_credentials(struct digest_auth_request *request, const unsigned char *credentials, size_t size) { struct md5_context ctx; unsigned char digest[MD5_RESULTLEN]; const char *a1_hex, *a2_hex, *response_hex; int i; /* get the MD5 password */ if (size != MD5_RESULTLEN) { e_error(request->auth_request.mech_event, "invalid credentials length"); return FALSE; } /* response = HEX( KD ( HEX(H(A1)), { nonce-value, ":" nc-value, ":", cnonce-value, ":", qop-value, ":", HEX(H(A2)) })) and if authzid is not empty: A1 = { H( { username-value, ":", realm-value, ":", passwd } ), ":", nonce-value, ":", cnonce-value, ":", authzid } else: A1 = { H( { username-value, ":", realm-value, ":", passwd } ), ":", nonce-value, ":", cnonce-value } If the "qop" directive's value is "auth", then A2 is: A2 = { "AUTHENTICATE:", digest-uri-value } If the "qop" value is "auth-int" or "auth-conf" then A2 is: A2 = { "AUTHENTICATE:", digest-uri-value, ":00000000000000000000000000000000" } */ /* A1 */ md5_init(&ctx); md5_update(&ctx, credentials, size); md5_update(&ctx, ":", 1); md5_update(&ctx, request->nonce, strlen(request->nonce)); md5_update(&ctx, ":", 1); md5_update(&ctx, request->cnonce, strlen(request->cnonce)); if (request->authzid != NULL) { md5_update(&ctx, ":", 1); md5_update(&ctx, request->authzid, strlen(request->authzid)); } md5_final(&ctx, digest); a1_hex = binary_to_hex(digest, 16); /* do it twice, first verify the user's response, the second is sent for client as a reply */ for (i = 0; i < 2; i++) { /* A2 */ md5_init(&ctx); if (i == 0) md5_update(&ctx, "AUTHENTICATE:", 13); else md5_update(&ctx, ":", 1); if (request->digest_uri != NULL) { md5_update(&ctx, request->digest_uri, strlen(request->digest_uri)); } if (request->qop == QOP_AUTH_INT || request->qop == QOP_AUTH_CONF) { md5_update(&ctx, ":00000000000000000000000000000000", 33); } md5_final(&ctx, digest); a2_hex = binary_to_hex(digest, 16); /* response */ md5_init(&ctx); md5_update(&ctx, a1_hex, 32); md5_update(&ctx, ":", 1); md5_update(&ctx, request->nonce, strlen(request->nonce)); md5_update(&ctx, ":", 1); md5_update(&ctx, request->nonce_count, strlen(request->nonce_count)); md5_update(&ctx, ":", 1); md5_update(&ctx, request->cnonce, strlen(request->cnonce)); md5_update(&ctx, ":", 1); md5_update(&ctx, request->qop_value, strlen(request->qop_value)); md5_update(&ctx, ":", 1); md5_update(&ctx, a2_hex, 32); md5_final(&ctx, digest); response_hex = binary_to_hex(digest, 16); if (i == 0) { /* verify response */ if (!mem_equals_timing_safe(response_hex, request->response, 32)) { auth_request_log_info(&request->auth_request, AUTH_SUBSYS_MECH, AUTH_LOG_MSG_PASSWORD_MISMATCH); return FALSE; } } else { request->rspauth = p_strconcat(request->pool, "rspauth=", response_hex, NULL); } } return TRUE; } static bool parse_next(char **data, char **key, char **value) { /* @UNSAFE */ char *p, *dest; p = *data; while (IS_LWS(*p)) p++; /* get key */ *key = p; while (*p != '\0' && *p != '=' && *p != ',') p++; if (*p != '=') { *data = p; return FALSE; } *value = p+1; /* skip trailing whitespace in key */ while (p > *data && IS_LWS(p[-1])) p--; *p = '\0'; /* get value */ p = *value; while (IS_LWS(*p)) p++; if (*p != '"') { while (*p != '\0' && *p != ',') p++; *data = p; /* If there is more to parse, ensure it won't get skipped because *p is set to NUL below */ if (**data != '\0') (*data)++; while (IS_LWS(p[-1])) p--; *p = '\0'; } else { /* quoted string */ *value = dest = ++p; while (*p != '\0' && *p != '"') { if (*p == '\\' && p[1] != '\0') p++; *dest++ = *p++; } *data = *p == '"' ? p+1 : p; *dest = '\0'; } return TRUE; } static bool auth_handle_response(struct digest_auth_request *request, char *key, char *value, const char **error) { unsigned int i; (void)str_lcase(key); if (strcmp(key, "realm") == 0) { if (request->auth_request.fields.realm == NULL && *value != '\0') auth_request_set_realm(&request->auth_request, value); return TRUE; } if (strcmp(key, "username") == 0) { if (request->username != NULL) { *error = "username must not exist more than once"; return FALSE; } if (*value == '\0') { *error = "empty username"; return FALSE; } request->username = p_strdup(request->pool, value); return TRUE; } if (strcmp(key, "nonce") == 0) { /* nonce must be same */ if (strcmp(value, request->nonce) != 0) { *error = "Invalid nonce"; return FALSE; } request->nonce_found = TRUE; return TRUE; } if (strcmp(key, "cnonce") == 0) { if (request->cnonce != NULL) { *error = "cnonce must not exist more than once"; return FALSE; } if (*value == '\0') { *error = "cnonce can't contain empty value"; return FALSE; } request->cnonce = p_strdup(request->pool, value); return TRUE; } if (strcmp(key, "nc") == 0) { unsigned int nc; if (request->nonce_count != NULL) { *error = "nonce-count must not exist more than once"; return FALSE; } if (str_to_uint(value, &nc) < 0) { *error = "nonce-count value invalid"; return FALSE; } if (nc != 1) { *error = "re-auth not supported currently"; return FALSE; } request->nonce_count = p_strdup(request->pool, value); return TRUE; } if (strcmp(key, "qop") == 0) { for (i = 0; i < QOP_COUNT; i++) { if (strcasecmp(qop_names[i], value) == 0) break; } if (i == QOP_COUNT) { *error = t_strdup_printf("Unknown QoP value: %s", str_sanitize(value, 32)); return FALSE; } request->qop &= (1 << i); if (request->qop == 0) { *error = "Nonallowed QoP requested"; return FALSE; } request->qop_value = p_strdup(request->pool, value); return TRUE; } if (strcmp(key, "digest-uri") == 0) { /* type / host / serv-name */ const char *const *uri = t_strsplit(value, "/"); if (uri[0] == NULL || uri[1] == NULL) { *error = "Invalid digest-uri"; return FALSE; } /* FIXME: RFC recommends that we verify the host/serv-type. But isn't the realm enough already? That'd be just extra configuration.. Maybe optionally list valid hosts in config file? */ request->digest_uri = p_strdup(request->pool, value); return TRUE; } if (strcmp(key, "maxbuf") == 0) { if (request->maxbuf != 0) { *error = "maxbuf must not exist more than once"; return FALSE; } if (str_to_ulong(value, &request->maxbuf) < 0 || request->maxbuf == 0) { *error = "Invalid maxbuf value"; return FALSE; } return TRUE; } if (strcmp(key, "charset") == 0) { if (strcasecmp(value, "utf-8") != 0) { *error = "Only utf-8 charset is allowed"; return FALSE; } return TRUE; } if (strcmp(key, "response") == 0) { if (strlen(value) != 32) { *error = "Invalid response value"; return FALSE; } memcpy(request->response, value, 32); return TRUE; } if (strcmp(key, "cipher") == 0) { /* not supported, ignore */ return TRUE; } if (strcmp(key, "authzid") == 0) { if (request->authzid != NULL) { *error = "authzid must not exist more than once"; return FALSE; } if (*value == '\0') { *error = "empty authzid"; return FALSE; } request->authzid = p_strdup(request->pool, value); return TRUE; } /* unknown key, ignore */ return TRUE; } static bool parse_digest_response(struct digest_auth_request *request, const unsigned char *data, size_t size, const char **error) { char *copy, *key, *value; bool failed; /* realm="realm" username="username" nonce="randomized data" cnonce="??" nc=00000001 qop="auth|auth-int|auth-conf" digest-uri="serv-type/host[/serv-name]" response=32 HEX digits maxbuf=number (with auth-int, auth-conf, defaults to 64k) charset="utf-8" (iso-8859-1 if it doesn't exist) cipher="cipher-value" authzid="authzid-value" */ *error = NULL; failed = FALSE; if (size == 0) { *error = "Client sent no input"; return FALSE; } /* treating response as NUL-terminated string also gets rid of all potential problems with NUL characters in strings. */ copy = t_strdup_noconst(t_strndup(data, size)); while (*copy != '\0') { if (parse_next(©, &key, &value)) { if (!auth_handle_response(request, key, value, error)) { failed = TRUE; break; } } if (*copy == ',') copy++; } if (!failed) { if (!request->nonce_found) { *error = "Missing nonce parameter"; failed = TRUE; } else if (request->cnonce == NULL) { *error = "Missing cnonce parameter"; failed = TRUE; } else if (request->username == NULL) { *error = "Missing username parameter"; failed = TRUE; } } if (request->nonce_count == NULL) request->nonce_count = p_strdup(request->pool, "00000001"); if (request->qop_value == NULL) request->qop_value = p_strdup(request->pool, "auth"); return !failed; } static void credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *auth_request) { struct digest_auth_request *request = (struct digest_auth_request *)auth_request; switch (result) { case PASSDB_RESULT_OK: if (!verify_credentials(request, credentials, size)) { auth_request_fail(auth_request); return; } auth_request_success(auth_request, request->rspauth, strlen(request->rspauth)); break; case PASSDB_RESULT_INTERNAL_FAILURE: auth_request_internal_failure(auth_request); break; default: auth_request_fail(auth_request); break; } } static void mech_digest_md5_auth_continue(struct auth_request *auth_request, const unsigned char *data, size_t data_size) { struct digest_auth_request *request = (struct digest_auth_request *)auth_request; const char *username, *error; if (parse_digest_response(request, data, data_size, &error)) { if (auth_request->fields.realm != NULL && strchr(request->username, '@') == NULL) { username = t_strconcat(request->username, "@", auth_request->fields.realm, NULL); auth_request->domain_is_realm = TRUE; } else { username = request->username; } if (auth_request_set_username(auth_request, username, &error) && (request->authzid == NULL || auth_request_set_login_username(auth_request, request->authzid, &error))) { auth_request_lookup_credentials(auth_request, "DIGEST-MD5", credentials_callback); return; } } if (error != NULL) e_info(auth_request->mech_event, "%s", error); auth_request_fail(auth_request); } static void mech_digest_md5_auth_initial(struct auth_request *auth_request, const unsigned char *data ATTR_UNUSED, size_t data_size ATTR_UNUSED) { struct digest_auth_request *request = (struct digest_auth_request *)auth_request; string_t *challenge; /* FIXME: there's no support for subsequent authentication */ challenge = get_digest_challenge(request); auth_request_handler_reply_continue(auth_request, str_data(challenge), str_len(challenge)); } static struct auth_request *mech_digest_md5_auth_new(void) { struct digest_auth_request *request; pool_t pool; pool = pool_alloconly_create(MEMPOOL_GROWING"digest_md5_auth_request", 2048); request = p_new(pool, struct digest_auth_request, 1); request->pool = pool; request->qop = QOP_AUTH; request->auth_request.pool = pool; return &request->auth_request; } const struct mech_module mech_digest_md5 = { "DIGEST-MD5", .flags = MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE | MECH_SEC_MUTUAL_AUTH, .passdb_need = MECH_PASSDB_NEED_LOOKUP_CREDENTIALS, mech_digest_md5_auth_new, mech_digest_md5_auth_initial, mech_digest_md5_auth_continue, mech_generic_auth_free }; dovecot-2.3.21.1/src/auth/password-scheme-sodium.c0000644000000000000000000000547214656633576016623 00000000000000/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "password-scheme.h" #ifdef HAVE_LIBSODIUM #include static void generate_argon2i(const char *plaintext, const struct password_generate_params *params, const unsigned char **raw_password_r, size_t *size_r) { unsigned long long rounds = params->rounds; size_t memlimit; char result[crypto_pwhash_STRBYTES]; if (rounds == 0) rounds = crypto_pwhash_argon2i_OPSLIMIT_INTERACTIVE; if (rounds >= crypto_pwhash_argon2i_OPSLIMIT_SENSITIVE) memlimit = crypto_pwhash_argon2i_MEMLIMIT_SENSITIVE; else if (rounds >= crypto_pwhash_argon2i_OPSLIMIT_MODERATE) memlimit = crypto_pwhash_argon2i_MEMLIMIT_MODERATE; else memlimit = crypto_pwhash_argon2i_MEMLIMIT_INTERACTIVE; if (crypto_pwhash_argon2i_str(result, plaintext, strlen(plaintext), rounds, memlimit) < 0) i_fatal("crypto_pwhash_argon2i_str failed: %m"); *raw_password_r = (const unsigned char*)t_strdup(result); *size_r = strlen(result); } #ifdef crypto_pwhash_ALG_ARGON2ID13 static void generate_argon2id(const char *plaintext, const struct password_generate_params *params, const unsigned char **raw_password_r, size_t *size_r) { unsigned long long rounds = params->rounds; size_t memlimit; char result[crypto_pwhash_argon2id_STRBYTES]; i_zero(&result); if (rounds == 0) rounds = crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE; if (rounds >= crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE) memlimit = crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE; else if (rounds >= crypto_pwhash_argon2id_OPSLIMIT_MODERATE) memlimit = crypto_pwhash_argon2id_MEMLIMIT_MODERATE; else memlimit = crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE; /* XXX: Bug in sodium-1.0.13, it expects rounds to be 3 */ if (rounds < 3) rounds = 3; if (crypto_pwhash_argon2id_str(result, plaintext, strlen(plaintext), rounds, memlimit) < 0) i_fatal("crypto_pwhash_argon2id_str failed: %m"); *raw_password_r = (const unsigned char*)t_strdup(result); *size_r = strlen(result); } #endif static int verify_argon2(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char *raw_password, size_t size, const char **error_r ATTR_UNUSED) { const char *passwd = t_strndup(raw_password, size); if (crypto_pwhash_str_verify(passwd, plaintext, strlen(plaintext)) < 0) return 0; return 1; } static const struct password_scheme sodium_schemes[] = { { "ARGON2I", PW_ENCODING_NONE, 0, verify_argon2, generate_argon2i }, #ifdef crypto_pwhash_ALG_ARGON2ID13 { "ARGON2ID", PW_ENCODING_NONE, 0, verify_argon2, generate_argon2id }, #endif }; void password_scheme_register_sodium(void) { if (sodium_init() != 0) i_fatal("sodium_init() failed"); for(size_t i = 0; i < N_ELEMENTS(sodium_schemes); i++) password_scheme_register(&sodium_schemes[i]); } #endif dovecot-2.3.21.1/src/auth/test-lua.c0000644000000000000000000001206414656633576013752 00000000000000/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ #include "test-auth.h" #ifdef BUILTIN_LUA #include "istream.h" #include "auth-settings.h" #include "auth-request.h" #include "auth-fields.h" #include "db-lua.h" static struct auth_settings test_lua_auth_set = { .master_user_separator = "", .default_realm = "", .username_format = "", }; static struct auth_request *test_db_lua_auth_request_new(void) { const char *error; struct auth_request *req = auth_request_new_dummy(NULL); req->set = global_auth_settings; struct event *event = event_create(req->event); array_push_back(&req->authdb_event, &event); req->passdb = passdb_mock(); test_assert(auth_request_set_username(req, "testuser", &error)); return req; } static void test_db_lua_auth_verify(void) { struct auth_request *req = test_db_lua_auth_request_new(); static const char *luascript = "function auth_password_verify(req, pass)\n" " req:log_debug(\"user \" .. req.user)\n" " if req:password_verify(\"{SHA256-CRYPT}$5$XtUywQCSjW0zAJgE$YjuPKQnsLuH4iE9kranZyy1lbil5IrRUfs7X6EyJyG1\", pass) then\n" " return dovecot.auth.PASSDB_RESULT_OK, {}\n" " end\n" "end\n"; const char *error = NULL; struct dlua_script *script = NULL; test_begin("auth db lua passdb_verify"); test_assert(dlua_script_create_string(luascript, &script, NULL, &error) == 0); if (script != NULL) { test_assert(auth_lua_script_init(script, &error) == 0); test_assert(auth_lua_call_password_verify(script, req, "password", &error) == 1); dlua_script_unref(&script); } if (error != NULL) { i_error("Test failed: %s", error); } i_free(req->passdb); auth_request_passdb_lookup_end(req, PASSDB_RESULT_OK); auth_request_unref(&req); test_end(); } static void test_db_lua_auth_lookup_numberish_value(void) { const char *scheme,*pass; struct auth_request *req = test_db_lua_auth_request_new(); static const char *luascript = "function auth_passdb_lookup(req)\n" " local fields = {}\n" " fields[\"user\"] = \"01234\"\n" " return dovecot.auth.PASSDB_RESULT_OK, fields\n" "end\n"; const char *error = NULL; struct dlua_script *script = NULL; test_begin("auth db lua passdb_lookup"); test_assert(dlua_script_create_string(luascript, &script, NULL, &error) == 0); if (script != NULL) { test_assert(auth_lua_script_init(script, &error) == 0); test_assert(auth_lua_call_passdb_lookup(script, req, &scheme, &pass, &error) == 1); test_assert(strcmp(req->fields.user, "01234") == 0); dlua_script_unref(&script); } if (error != NULL) { i_error("Test failed: %s", error); } i_free(req->passdb); auth_request_passdb_lookup_end(req, PASSDB_RESULT_OK); auth_request_unref(&req); test_end(); } static void test_db_lua_auth_lookup(void) { const char *scheme,*pass; struct auth_request *req = test_db_lua_auth_request_new(); static const char *luascript = "function auth_passdb_lookup(req)\n" " req:log_debug(\"user \" .. req.user)\n" " return dovecot.auth.PASSDB_RESULT_OK, req:var_expand(\"password=pass\")\n" "end\n"; const char *error = NULL; struct dlua_script *script = NULL; test_begin("auth db lua passdb_lookup"); test_assert(dlua_script_create_string(luascript, &script, NULL, &error) == 0); if (script != NULL) { test_assert(auth_lua_script_init(script, &error) == 0); test_assert(auth_lua_call_passdb_lookup(script, req, &scheme, &pass, &error) == 1); dlua_script_unref(&script); test_assert_strcmp(pass, "pass"); } if (error != NULL) { i_error("Test failed: %s", error); } i_free(req->passdb); auth_request_passdb_lookup_end(req, PASSDB_RESULT_OK); auth_request_unref(&req); test_end(); } static void test_db_lua_auth_lookup_bad_keyname(void) { const char *scheme,*pass; struct auth_request *req = test_db_lua_auth_request_new(); static const char *luascript = "function auth_passdb_lookup(req)\n" " req:log_debug(\"user \" .. req.user)\n" " return dovecot.auth.PASSDB_RESULT_OK, {" "[\"\"]=\"1\"," "[\"\\t\"]=\"2\"," "[\"\\n\"]=\"3\"," "[\"password\"]=\"pass\"" "}\n" "end\n"; const char *error = NULL; struct dlua_script *script = NULL; test_begin("auth db lua bad keyname"); test_assert(dlua_script_create_string(luascript, &script, NULL, &error) == 0); if (script != NULL) { test_assert(auth_lua_script_init(script, &error) == 0); test_expect_error_string_n_times("db-lua: Field key", 3); test_assert(auth_lua_call_passdb_lookup(script, req, &scheme, &pass, &error) == 1); dlua_script_unref(&script); test_assert_strcmp(pass, "pass"); const ARRAY_TYPE(auth_field) *fields = auth_fields_export(req->fields.extra_fields); test_assert(array_count(fields) == 0); } if (error != NULL) { i_error("Test failed: %s", error); } i_free(req->passdb); auth_request_passdb_lookup_end(req, PASSDB_RESULT_OK); auth_request_unref(&req); test_end(); } void test_db_lua(void) { memset(test_lua_auth_set.username_chars_map, 0xff, sizeof(test_lua_auth_set.username_chars_map)); global_auth_settings = &test_lua_auth_set; test_db_lua_auth_lookup(); test_db_lua_auth_lookup_numberish_value(); test_db_lua_auth_verify(); test_db_lua_auth_lookup_bad_keyname(); } #endif dovecot-2.3.21.1/src/auth/auth-common.h0000644000000000000000000000065114656633576014447 00000000000000#ifndef AUTH_COMMON_H #define AUTH_COMMON_H #include "lib.h" #include "auth.h" extern bool worker, worker_restart_request; extern time_t process_start_time; extern struct auth_penalty *auth_penalty; extern struct event_category event_category_auth; extern struct event *auth_event; void auth_refresh_proctitle(void); void auth_worker_refresh_proctitle(const char *state); void auth_module_load(const char *names); #endif dovecot-2.3.21.1/src/auth/db-oauth2.c0000644000000000000000000006603614656633576014011 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "array.h" #include "str.h" #include "strescape.h" #include "var-expand.h" #include "env-util.h" #include "var-expand.h" #include "settings.h" #include "oauth2.h" #include "http-client.h" #include "http-url.h" #include "iostream-ssl.h" #include "auth-request.h" #include "auth-settings.h" #include "passdb.h" #include "passdb-template.h" #include "llist.h" #include "db-oauth2.h" #include "dcrypt.h" #include "dict.h" #include struct passdb_oauth2_settings { /* tokeninfo endpoint, format https://endpoint/somewhere?token= */ const char *tokeninfo_url; /* password grant endpoint, format https://endpoint/somewhere */ const char *grant_url; /* introspection endpoint, format https://endpoint/somewhere */ const char *introspection_url; /* expected scope, optional */ const char *scope; /* mode of introspection, one of get, get-auth, post - get: append token to url - get-auth: send token with header Authorization: Bearer token - post: send token= as POST request */ const char *introspection_mode; /* normalization var-expand template for username, defaults to %Lu */ const char *username_format; /* name of username attribute to lookup, mandatory */ const char *username_attribute; /* name of account is active attribute, optional */ const char *active_attribute; /* expected active value for active attribute, optional */ const char *active_value; /* client identificator for oauth2 server */ const char *client_id; /* not really used, but have to present by oauth2 specs */ const char *client_secret; /* template to expand into passdb */ const char *pass_attrs; /* template to expand into key path, turns on local validation support */ const char *local_validation_key_dict; /* valid token issuers */ const char *issuers; /* The URL for a document following the OpenID Provider Configuration Information schema, see https://datatracker.ietf.org/doc/html/rfc7628#section-3.2.2 */ const char *openid_configuration_url; /* TLS options */ const char *tls_ca_cert_file; const char *tls_ca_cert_dir; const char *tls_cert_file; const char *tls_key_file; const char *tls_cipher_suite; /* HTTP rawlog directory */ const char *rawlog_dir; /* HTTP client options */ unsigned int timeout_msecs; unsigned int max_idle_time_msecs; unsigned int max_parallel_connections; unsigned int max_pipelined_requests; bool tls_allow_invalid_cert; bool debug; /* Should introspection be done even if not necessary */ bool force_introspection; /* Should we send service and local/remote endpoints as X-Dovecot-Auth headers */ bool send_auth_headers; bool use_grant_password; }; struct db_oauth2 { struct db_oauth2 *prev,*next; pool_t pool; const char *config_path; struct passdb_oauth2_settings set; struct http_client *client; struct passdb_template *tmpl; struct oauth2_settings oauth2_set; struct db_oauth2_request *head; unsigned int refcount; }; static struct db_oauth2 *db_oauth2_head = NULL; #undef DEF_STR #undef DEF_BOOL #undef DEF_INT #define DEF_STR(name) DEF_STRUCT_STR(name, passdb_oauth2_settings) #define DEF_BOOL(name) DEF_STRUCT_BOOL(name, passdb_oauth2_settings) #define DEF_INT(name) DEF_STRUCT_INT(name, passdb_oauth2_settings) static struct setting_def setting_defs[] = { DEF_STR(tokeninfo_url), DEF_STR(grant_url), DEF_STR(introspection_url), DEF_STR(scope), DEF_BOOL(force_introspection), DEF_STR(introspection_mode), DEF_STR(username_format), DEF_STR(username_attribute), DEF_STR(pass_attrs), DEF_STR(local_validation_key_dict), DEF_STR(active_attribute), DEF_STR(active_value), DEF_STR(client_id), DEF_STR(client_secret), DEF_STR(issuers), DEF_STR(openid_configuration_url), DEF_INT(timeout_msecs), DEF_INT(max_idle_time_msecs), DEF_INT(max_parallel_connections), DEF_INT(max_pipelined_requests), DEF_BOOL(send_auth_headers), DEF_BOOL(use_grant_password), DEF_STR(tls_ca_cert_file), DEF_STR(tls_ca_cert_dir), DEF_STR(tls_cert_file), DEF_STR(tls_key_file), DEF_STR(tls_cipher_suite), DEF_BOOL(tls_allow_invalid_cert), DEF_STR(rawlog_dir), DEF_BOOL(debug), { 0, NULL, 0 } }; static struct passdb_oauth2_settings default_oauth2_settings = { .tokeninfo_url = "", .grant_url = "", .introspection_url = "", .scope = "", .force_introspection = FALSE, .introspection_mode = "", .username_format = "%Lu", .username_attribute = "email", .active_attribute = "", .active_value = "", .client_id = "", .client_secret = "", .issuers = "", .openid_configuration_url = "", .pass_attrs = "", .local_validation_key_dict = "", .rawlog_dir = "", .timeout_msecs = 0, .max_idle_time_msecs = 60000, .max_parallel_connections = 10, .max_pipelined_requests = 1, .tls_ca_cert_file = NULL, .tls_ca_cert_dir = NULL, .tls_cert_file = NULL, .tls_key_file = NULL, .tls_cipher_suite = "HIGH:!SSLv2", .tls_allow_invalid_cert = FALSE, .send_auth_headers = FALSE, .use_grant_password = FALSE, .debug = FALSE, }; static const char *parse_setting(const char *key, const char *value, struct db_oauth2 *db) { return parse_setting_from_defs(db->pool, setting_defs, &db->set, key, value); } struct db_oauth2 *db_oauth2_init(const char *config_path) { struct db_oauth2 *db; const char *error; struct ssl_iostream_settings ssl_set; struct http_client_settings http_set; for(db = db_oauth2_head; db != NULL; db = db->next) { if (strcmp(db->config_path, config_path) == 0) { db->refcount++; return db; } } pool_t pool = pool_alloconly_create("db_oauth2", 128); db = p_new(pool, struct db_oauth2, 1); db->pool = pool; db->refcount = 1; db->config_path = p_strdup(pool, config_path); db->set = default_oauth2_settings; if (!settings_read_nosection(config_path, parse_setting, db, &error)) i_fatal("oauth2 %s: %s", config_path, error); db->tmpl = passdb_template_build(pool, db->set.pass_attrs); i_zero(&ssl_set); i_zero(&http_set); ssl_set.cipher_list = db->set.tls_cipher_suite; ssl_set.ca_file = db->set.tls_ca_cert_file; ssl_set.ca_dir = db->set.tls_ca_cert_dir; if (db->set.tls_cert_file != NULL && *db->set.tls_cert_file != '\0') { ssl_set.cert.cert = db->set.tls_cert_file; ssl_set.cert.key = db->set.tls_key_file; } ssl_set.prefer_server_ciphers = TRUE; ssl_set.allow_invalid_cert = db->set.tls_allow_invalid_cert; ssl_set.verbose = db->set.debug; ssl_set.verbose_invalid_cert = db->set.debug; http_set.ssl = &ssl_set; http_set.dns_client_socket_path = "dns-client"; http_set.user_agent = "dovecot-oauth2-passdb/" DOVECOT_VERSION; if (*db->set.local_validation_key_dict == '\0' && *db->set.tokeninfo_url == '\0' && (*db->set.grant_url == '\0' || *db->set.client_id == '\0') && *db->set.introspection_url == '\0') i_fatal("oauth2: Password grant, tokeninfo, introspection URL or " "validation key dictionary must be given"); if (*db->set.rawlog_dir != '\0') http_set.rawlog_dir = db->set.rawlog_dir; http_set.max_idle_time_msecs = db->set.max_idle_time_msecs; http_set.max_parallel_connections = db->set.max_parallel_connections; http_set.max_pipelined_requests = db->set.max_pipelined_requests; http_set.no_auto_redirect = FALSE; http_set.no_auto_retry = TRUE; http_set.debug = db->set.debug; http_set.event_parent = auth_event; db->client = http_client_init(&http_set); i_zero(&db->oauth2_set); db->oauth2_set.client = db->client; db->oauth2_set.tokeninfo_url = db->set.tokeninfo_url, db->oauth2_set.grant_url = db->set.grant_url, db->oauth2_set.introspection_url = db->set.introspection_url; db->oauth2_set.client_id = db->set.client_id; db->oauth2_set.client_secret = db->set.client_secret; db->oauth2_set.timeout_msecs = db->set.timeout_msecs; db->oauth2_set.send_auth_headers = db->set.send_auth_headers; db->oauth2_set.use_grant_password = db->set.use_grant_password; db->oauth2_set.scope = db->set.scope; if (*db->set.active_attribute == '\0' && *db->set.active_value != '\0') i_fatal("oauth2: Cannot have empty active_attribute is active_value is set"); if (*db->set.introspection_mode == '\0' || strcmp(db->set.introspection_mode, "auth") == 0) { db->oauth2_set.introspection_mode = INTROSPECTION_MODE_GET_AUTH; } else if (strcmp(db->set.introspection_mode, "get") == 0) { db->oauth2_set.introspection_mode = INTROSPECTION_MODE_GET; } else if (strcmp(db->set.introspection_mode, "post") == 0) { db->oauth2_set.introspection_mode = INTROSPECTION_MODE_POST; } else if (strcmp(db->set.introspection_mode, "local") == 0) { if (*db->set.local_validation_key_dict == '\0') i_fatal("oauth2: local_validation_key_dict is required " "for local introspection."); db->oauth2_set.introspection_mode = INTROSPECTION_MODE_LOCAL; } else { i_fatal("oauth2: Invalid value '%s' for introspection mode, must be on auth, get, post or local", db->set.introspection_mode); } if (db->oauth2_set.introspection_mode == INTROSPECTION_MODE_LOCAL) { struct dict_settings dict_set = { .base_dir = global_auth_settings->base_dir, .event_parent = auth_event, }; if (dict_init(db->set.local_validation_key_dict, &dict_set, &db->oauth2_set.key_dict, &error) < 0) i_fatal("Cannot initialize key dict: %s", error); /* failure to initialize dcrypt is not fatal - we can still validate HMAC based keys */ (void)dcrypt_initialize(NULL, NULL, NULL); /* initialize key cache */ db->oauth2_set.key_cache = oauth2_validation_key_cache_init(); } if (*db->set.issuers != '\0') db->oauth2_set.issuers = (const char *const *) p_strsplit_spaces(pool, db->set.issuers, " "); if (*db->set.openid_configuration_url != '\0') { struct http_url *parsed_url ATTR_UNUSED; if (http_url_parse(db->set.openid_configuration_url, NULL, 0, pool_datastack_create(), &parsed_url, &error) < 0) { i_fatal("Invalid openid_configuration_url: %s", error); } } DLLIST_PREPEND(&db_oauth2_head, db); return db; } void db_oauth2_ref(struct db_oauth2 *db) { i_assert(db->refcount > 0); db->refcount++; } void db_oauth2_unref(struct db_oauth2 **_db) { struct db_oauth2 *ptr, *db = *_db; i_assert(db->refcount > 0); if (--db->refcount > 0) return; for(ptr = db_oauth2_head; ptr != NULL; ptr = ptr->next) { if (ptr == db) { DLLIST_REMOVE(&db_oauth2_head, ptr); break; } } i_assert(ptr != NULL && ptr == db); /* make sure all requests are aborted */ while (db->head != NULL) oauth2_request_abort(&db->head->req); http_client_deinit(&db->client); if (db->oauth2_set.key_dict != NULL) dict_deinit(&db->oauth2_set.key_dict); oauth2_validation_key_cache_deinit(&db->oauth2_set.key_cache); pool_unref(&db->pool); } static void db_oauth2_add_openid_config_url(struct db_oauth2_request *req) { /* FIXME: HORRIBLE HACK - REMOVE ME!!! It is because the mech has not been implemented properly that we need to pass the config url in this strange way. This **must** be moved to mech-oauth2 once the validation result et al is handled there. */ req->auth_request->openid_config_url = p_strdup_empty(req->auth_request->pool, req->db->set.openid_configuration_url); } const char *db_oauth2_get_openid_configuration_url(const struct db_oauth2 *db) { return db->set.openid_configuration_url; } static bool db_oauth2_have_all_fields(struct db_oauth2_request *req) { unsigned int n,i; unsigned int size,idx; const char *const *args = passdb_template_get_args(req->db->tmpl, &n); if (req->fields == NULL) return FALSE; for(i=1;ifields, field+7)) return FALSE; ptr = ptr+size; } } } if (!auth_fields_exists(req->fields, req->db->set.username_attribute)) return FALSE; if (*req->db->set.active_attribute != '\0' && !auth_fields_exists(req->fields, req->db->set.active_attribute)) return FALSE; return TRUE; } static const char *field_get_default(const char *data) { const char *p; p = strchr(data, ':'); if (p == NULL) return ""; else { /* default value given */ return p+1; } } static int db_oauth2_var_expand_func_oauth2(const char *data, void *context, const char **value_r, const char **error_r ATTR_UNUSED) { struct db_oauth2_request *ctx = context; const char *field_name = t_strcut(data, ':'); const char *value = NULL; if (ctx->fields != NULL) value = auth_fields_find(ctx->fields, field_name); *value_r = value != NULL ? value : field_get_default(data); return 1; } static const char *escape_none(const char *value, const struct auth_request *req ATTR_UNUSED) { return value; } static const struct var_expand_table * db_oauth2_value_get_var_expand_table(struct auth_request *auth_request, const char *oauth2_value) { struct var_expand_table *table; unsigned int count = 1; table = auth_request_get_var_expand_table_full(auth_request, auth_request->fields.user, NULL, &count); table[0].key = '$'; table[0].value = oauth2_value; return table; } static bool db_oauth2_template_export(struct db_oauth2_request *req, enum passdb_result *result_r, const char **error_r) { /* var=$ expands into var=${oauth2:var} */ const struct var_expand_func_table funcs_table[] = { { "oauth2", db_oauth2_var_expand_func_oauth2 }, { NULL, NULL } }; string_t *dest; const char *const *args, *value, *error; struct passdb_template *tmpl = req->db->tmpl; unsigned int i, count; if (passdb_template_is_empty(tmpl)) return TRUE; dest = t_str_new(256); args = passdb_template_get_args(tmpl, &count); i_assert((count % 2) == 0); for (i = 0; i < count; i += 2) { if (args[i+1] == NULL) value = ""; else { str_truncate(dest, 0); const struct var_expand_table * table = db_oauth2_value_get_var_expand_table(req->auth_request, auth_fields_find(req->fields, args[i])); if (var_expand_with_funcs(dest, args[i+1], table, funcs_table, req, &error) < 0) { *error_r = t_strdup_printf( "var_expand(%s) failed: %s", args[i+1], error); *result_r = PASSDB_RESULT_INTERNAL_FAILURE; return FALSE; } value = str_c(dest); } auth_request_set_field(req->auth_request, args[i], value, STATIC_PASS_SCHEME); } return TRUE; } static void db_oauth2_fields_merge(struct db_oauth2_request *req, ARRAY_TYPE(oauth2_field) *fields) { const struct oauth2_field *field; if (req->fields == NULL) req->fields = auth_fields_init(req->pool); array_foreach(fields, field) { e_debug(authdb_event(req->auth_request), "Processing field %s", field->name); auth_fields_add(req->fields, field->name, field->value, 0); } } static const char * db_oauth2_field_find(const ARRAY_TYPE(oauth2_field) *fields, const char *name) { const struct oauth2_field *f; array_foreach(fields, f) { if (strcmp(f->name, name) == 0) return f->value; } return NULL; } static void db_oauth2_callback(struct db_oauth2_request *req, enum passdb_result result, const char *error_prefix, const char *error) { db_oauth2_lookup_callback_t *callback = req->callback; req->callback = NULL; i_assert(result == PASSDB_RESULT_OK || error != NULL); if (result != PASSDB_RESULT_OK) db_oauth2_add_openid_config_url(req); /* Successful lookups were logged by the caller. Failed lookups will be logged either with e_error() or e_info() by the callback. */ if (callback != NULL) { DLLIST_REMOVE(&req->db->head, req); if (result != PASSDB_RESULT_OK) error = t_strconcat(error_prefix, error, NULL); callback(req, result, error, req->context); } } static bool db_oauth2_validate_username(struct db_oauth2_request *req, enum passdb_result *result_r, const char **error_r) { const char *error; struct var_expand_table table[] = { { 'u', NULL, "user" }, { 'n', NULL, "username" }, { 'd', NULL, "domain" }, { '\0', NULL, NULL } }; const char *username_value = auth_fields_find(req->fields, req->db->set.username_attribute); if (username_value == NULL) { *result_r = PASSDB_RESULT_INTERNAL_FAILURE; *error_r = "No username returned"; return FALSE; } table[0].value = username_value; table[1].value = t_strcut(username_value, '@'); table[2].value = i_strchr_to_next(username_value, '@'); string_t *username_req = t_str_new(32); string_t *username_val = t_str_new(strlen(username_value)); if (auth_request_var_expand(username_req, req->db->set.username_format, req->auth_request, escape_none, &error) < 0 || var_expand(username_val, req->db->set.username_format, table, &error) < 0) { *error_r = t_strdup_printf("var_expand(%s) failed: %s", req->db->set.username_format, error); *result_r = PASSDB_RESULT_INTERNAL_FAILURE; return FALSE; } else if (!str_equals(username_req, username_val)) { *error_r = t_strdup_printf("Username '%s' did not match '%s'", str_c(username_req), str_c(username_val)); *result_r = PASSDB_RESULT_USER_UNKNOWN; return FALSE; } else { req->username = p_strdup(req->pool, str_c(username_val)); return TRUE; } } static bool db_oauth2_user_is_enabled(struct db_oauth2_request *req, enum passdb_result *result_r, const char **error_r) { if (*req->db->set.active_attribute == '\0' ) { e_debug(authdb_event(req->auth_request), "oauth2 active_attribute is not configured; skipping the check"); return TRUE; } const char *active_value = auth_fields_find(req->fields, req->db->set.active_attribute); if (active_value == NULL) { e_debug(authdb_event(req->auth_request), "oauth2 active_attribute \"%s\" is not present in the oauth2 server's response", req->db->set.active_attribute); *error_r = "Missing active_attribute from token"; *result_r = PASSDB_RESULT_PASSWORD_MISMATCH; return FALSE; } if (*req->db->set.active_value == '\0') { e_debug(authdb_event(req->auth_request), "oauth2 active_attribute \"%s\" present; skipping the check on value", req->db->set.active_attribute); return TRUE; } if (strcmp(req->db->set.active_value, active_value) != 0) { e_debug(authdb_event(req->auth_request), "oauth2 active_attribute check failed: expected %s=\"%s\" but got \"%s\"", req->db->set.active_attribute, req->db->set.active_value, active_value); *error_r = "Provided token is not valid"; *result_r = PASSDB_RESULT_PASSWORD_MISMATCH; return FALSE; } e_debug(authdb_event(req->auth_request), "oauth2 active_attribute check succeeded"); return TRUE; } static bool db_oauth2_token_in_scope(struct db_oauth2_request *req, enum passdb_result *result_r, const char **error_r) { if (*req->db->set.scope != '\0') { bool found = FALSE; const char *value = auth_fields_find(req->fields, "scope"); bool has_scope = value != NULL; if (!has_scope) value = auth_fields_find(req->fields, "aud"); e_debug(authdb_event(req->auth_request), "Token scope(s): %s", value); if (value != NULL) { const char **wanted_scopes = t_strsplit_spaces(req->db->set.scope, " "); const char *const *entries = has_scope ? t_strsplit_spaces(value, " ") : t_strsplit_tabescaped(value); for (; !found && *wanted_scopes != NULL; wanted_scopes++) found = str_array_find(entries, *wanted_scopes); } if (!found) { *error_r = t_strdup_printf("Token is not valid for scope '%s'", req->db->set.scope); *result_r = PASSDB_RESULT_USER_DISABLED; return FALSE; } } return TRUE; } static void db_oauth2_process_fields(struct db_oauth2_request *req, enum passdb_result *result_r, const char **error_r) { *error_r = NULL; if (db_oauth2_user_is_enabled(req, result_r, error_r) && db_oauth2_validate_username(req, result_r, error_r) && db_oauth2_token_in_scope(req, result_r, error_r) && db_oauth2_template_export(req, result_r, error_r)) { *result_r = PASSDB_RESULT_OK; } else { i_assert(*result_r != PASSDB_RESULT_OK && *error_r != NULL); } } static void db_oauth2_introspect_continue(struct oauth2_request_result *result, struct db_oauth2_request *req) { enum passdb_result passdb_result; const char *error; req->req = NULL; if (result->error != NULL) { /* fail here */ passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; error = result->error; } else { e_debug(authdb_event(req->auth_request), "Introspection succeeded"); db_oauth2_fields_merge(req, result->fields); db_oauth2_process_fields(req, &passdb_result, &error); } db_oauth2_callback(req, passdb_result, "Introspection failed: ", error); } static void db_oauth2_lookup_introspect(struct db_oauth2_request *req) { struct oauth2_request_input input; i_zero(&input); e_debug(authdb_event(req->auth_request), "Making introspection request to %s", req->db->set.introspection_url); input.token = req->token; input.local_ip = req->auth_request->fields.local_ip; input.local_port = req->auth_request->fields.local_port; input.remote_ip = req->auth_request->fields.remote_ip; input.remote_port = req->auth_request->fields.remote_port; input.real_local_ip = req->auth_request->fields.real_local_ip; input.real_local_port = req->auth_request->fields.real_local_port; input.real_remote_ip = req->auth_request->fields.real_remote_ip; input.real_remote_port = req->auth_request->fields.real_remote_port; input.service = req->auth_request->fields.service; req->req = oauth2_introspection_start(&req->db->oauth2_set, &input, db_oauth2_introspect_continue, req); } static void db_oauth2_local_validation(struct db_oauth2_request *req, const char *token) { bool is_jwt ATTR_UNUSED; const char *error = NULL; enum passdb_result passdb_result; ARRAY_TYPE(oauth2_field) fields; t_array_init(&fields, 8); if (oauth2_try_parse_jwt(&req->db->oauth2_set, token, &fields, &is_jwt, &error) < 0) { passdb_result = PASSDB_RESULT_PASSWORD_MISMATCH; } else { db_oauth2_fields_merge(req, &fields); db_oauth2_process_fields(req, &passdb_result, &error); } if (passdb_result == PASSDB_RESULT_OK) { e_debug(authdb_event(req->auth_request), "Local validation succeeded"); } db_oauth2_callback(req, passdb_result, "Local validation failed: ", error); } static void db_oauth2_lookup_continue_valid(struct db_oauth2_request *req, ARRAY_TYPE(oauth2_field) *fields, const char *error_prefix) { enum passdb_result passdb_result; const char *error; db_oauth2_fields_merge(req, fields); if (db_oauth2_have_all_fields(req) && !req->db->set.force_introspection) { /* pass */ } else if (req->db->oauth2_set.introspection_mode == INTROSPECTION_MODE_LOCAL) { e_debug(authdb_event(req->auth_request), "Attempting to locally validate token"); db_oauth2_local_validation(req, req->token); return; } else if (!db_oauth2_user_is_enabled(req, &passdb_result, &error)) { db_oauth2_callback(req, passdb_result, "Token is not valid: ", error); return; } else if (*req->db->set.introspection_url != '\0') { db_oauth2_lookup_introspect(req); return; } db_oauth2_process_fields(req, &passdb_result, &error); db_oauth2_callback(req, passdb_result, error_prefix, error); } static void db_oauth2_lookup_continue(struct oauth2_request_result *result, struct db_oauth2_request *req) { i_assert(req->token != NULL); req->req = NULL; if (result->error != NULL) { db_oauth2_callback(req, PASSDB_RESULT_INTERNAL_FAILURE, "Token validation failed: ", result->error); } else if (!result->valid) { db_oauth2_callback(req, PASSDB_RESULT_PASSWORD_MISMATCH, "Token validation failed: ", "Invalid token"); } else { e_debug(authdb_event(req->auth_request), "Token validation succeeded"); db_oauth2_lookup_continue_valid(req, result->fields, "Token validation failed: "); } } static void db_oauth2_lookup_passwd_grant(struct oauth2_request_result *result, struct db_oauth2_request *req) { enum passdb_result passdb_result; const char *token, *error; i_assert(req->token == NULL); req->req = NULL; if (result->valid) { e_debug(authdb_event(req->auth_request), "Password grant succeeded"); token = db_oauth2_field_find(result->fields, "access_token"); if (token == NULL) { db_oauth2_callback(req, PASSDB_RESULT_INTERNAL_FAILURE, "Password grant failed: ", "OAuth2 token missing from reply"); } else { req->token = p_strdup(req->pool, token); db_oauth2_lookup_continue_valid(req, result->fields, "Password grant failed: "); } return; } passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; if (result->error != NULL) error = result->error; else { passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; error = db_oauth2_field_find(result->fields, "error"); if (error == NULL) error = "OAuth2 server returned failure without error field"; else if (strcmp("invalid_grant", error) == 0) passdb_result = PASSDB_RESULT_PASSWORD_MISMATCH; } db_oauth2_callback(req, passdb_result, "Password grant failed: ", error); } #undef db_oauth2_lookup void db_oauth2_lookup(struct db_oauth2 *db, struct db_oauth2_request *req, const char *token, struct auth_request *request, db_oauth2_lookup_callback_t *callback, void *context) { struct oauth2_request_input input; i_zero(&input); req->db = db; req->token = p_strdup(req->pool, token); req->callback = callback; req->context = context; req->auth_request = request; input.token = token; input.local_ip = req->auth_request->fields.local_ip; input.local_port = req->auth_request->fields.local_port; input.remote_ip = req->auth_request->fields.remote_ip; input.remote_port = req->auth_request->fields.remote_port; input.real_local_ip = req->auth_request->fields.real_local_ip; input.real_local_port = req->auth_request->fields.real_local_port; input.real_remote_ip = req->auth_request->fields.real_remote_ip; input.real_remote_port = req->auth_request->fields.real_remote_port; input.service = req->auth_request->fields.service; if (db->oauth2_set.introspection_mode == INTROSPECTION_MODE_LOCAL && !db_oauth2_uses_password_grant(db)) { /* try to validate token locally */ e_debug(authdb_event(req->auth_request), "Attempting to locally validate token"); db_oauth2_local_validation(req, request->mech_password); return; } if (db->oauth2_set.use_grant_password) { e_debug(authdb_event(req->auth_request), "Making grant url request to %s", db->set.grant_url); /* There is no valid token until grant looks it up. */ req->token = NULL; req->req = oauth2_passwd_grant_start(&db->oauth2_set, &input, request->fields.user, request->mech_password, db_oauth2_lookup_passwd_grant, req); } else if (*db->oauth2_set.tokeninfo_url == '\0') { e_debug(authdb_event(req->auth_request), "Making introspection request to %s", db->set.introspection_url); req->req = oauth2_introspection_start(&req->db->oauth2_set, &input, db_oauth2_introspect_continue, req); } else { e_debug(authdb_event(req->auth_request), "Making token validation lookup to %s", db->oauth2_set.tokeninfo_url); req->req = oauth2_token_validation_start(&db->oauth2_set, &input, db_oauth2_lookup_continue, req); } i_assert(req->req != NULL); DLLIST_PREPEND(&db->head, req); } bool db_oauth2_uses_password_grant(const struct db_oauth2 *db) { return db->set.use_grant_password; } dovecot-2.3.21.1/src/auth/db-sql.c0000644000000000000000000001203014656633576013367 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #if defined(PASSDB_SQL) || defined(USERDB_SQL) #include "settings.h" #include "auth-request.h" #include "auth-worker-client.h" #include "db-sql.h" #include #define DEF_STR(name) DEF_STRUCT_STR(name, db_sql_settings) #define DEF_INT(name) DEF_STRUCT_INT(name, db_sql_settings) #define DEF_BOOL(name) DEF_STRUCT_BOOL(name, db_sql_settings) static struct setting_def setting_defs[] = { DEF_STR(driver), DEF_STR(connect), DEF_STR(password_query), DEF_STR(user_query), DEF_STR(update_query), DEF_STR(iterate_query), DEF_STR(default_pass_scheme), DEF_BOOL(userdb_warning_disable), { 0, NULL, 0 } }; static struct db_sql_settings default_db_sql_settings = { .driver = NULL, .connect = NULL, .password_query = "SELECT username, domain, password FROM users WHERE username = '%n' AND domain = '%d'", .user_query = "SELECT home, uid, gid FROM users WHERE username = '%n' AND domain = '%d'", .update_query = "UPDATE users SET password = '%w' WHERE username = '%n' AND domain = '%d'", .iterate_query = "SELECT username, domain FROM users", .default_pass_scheme = "MD5", .userdb_warning_disable = FALSE }; static struct db_sql_connection *connections = NULL; static struct db_sql_connection *sql_conn_find(const char *config_path) { struct db_sql_connection *conn; for (conn = connections; conn != NULL; conn = conn->next) { if (strcmp(conn->config_path, config_path) == 0) return conn; } return NULL; } static const char *parse_setting(const char *key, const char *value, struct db_sql_connection *conn) { return parse_setting_from_defs(conn->pool, setting_defs, &conn->set, key, value); } struct db_sql_connection *db_sql_init(const char *config_path, bool userdb) { struct db_sql_connection *conn; struct sql_settings set; const char *error; pool_t pool; conn = sql_conn_find(config_path); if (conn != NULL) { if (userdb) conn->userdb_used = TRUE; conn->refcount++; return conn; } if (*config_path == '\0') i_fatal("sql: Configuration file path not given"); pool = pool_alloconly_create("db_sql_connection", 1024); conn = p_new(pool, struct db_sql_connection, 1); conn->pool = pool; conn->userdb_used = userdb; conn->refcount = 1; conn->config_path = p_strdup(pool, config_path); conn->set = default_db_sql_settings; if (!settings_read_nosection(config_path, parse_setting, conn, &error)) i_fatal("sql %s: %s", config_path, error); if (conn->set.password_query == default_db_sql_settings.password_query) conn->default_password_query = TRUE; if (conn->set.user_query == default_db_sql_settings.user_query) conn->default_user_query = TRUE; if (conn->set.update_query == default_db_sql_settings.update_query) conn->default_update_query = TRUE; if (conn->set.iterate_query == default_db_sql_settings.iterate_query) conn->default_iterate_query = TRUE; if (conn->set.driver == NULL) { i_fatal("sql: driver not set in configuration file %s", config_path); } if (conn->set.connect == NULL) { i_fatal("sql: connect string not set in configuration file %s", config_path); } i_zero(&set); set.driver = conn->set.driver; set.connect_string = conn->set.connect; set.event_parent = auth_event; if (sql_init_full(&set, &conn->db, &error) < 0) { i_fatal("sql: %s", error); } conn->next = connections; connections = conn; return conn; } void db_sql_unref(struct db_sql_connection **_conn) { struct db_sql_connection *conn = *_conn; /* abort all pending auth requests before setting conn to NULL, so that callbacks can still access it */ sql_disconnect(conn->db); *_conn = NULL; if (--conn->refcount > 0) return; sql_unref(&conn->db); pool_unref(&conn->pool); } void db_sql_connect(struct db_sql_connection *conn) { if (sql_connect(conn->db) < 0 && worker) { /* auth worker's sql connection failed. we can't do anything useful until the connection works. there's no point in having tons of worker processes all logging failures, so tell the auth master to stop creating new workers (and maybe close old ones). this handling is especially useful if we reach the max. number of connections for sql server. */ auth_worker_client_send_error(); } } void db_sql_success(struct db_sql_connection *conn ATTR_UNUSED) { if (worker) auth_worker_client_send_success(); } void db_sql_check_userdb_warning(struct db_sql_connection *conn) { if (worker || conn->userdb_used || conn->set.userdb_warning_disable) return; if (strcmp(conn->set.user_query, default_db_sql_settings.user_query) != 0) { i_warning("sql: Ignoring changed user_query in %s, " "because userdb sql not used. " "(If this is intentional, set userdb_warning_disable=yes)", conn->config_path); } else if (strcmp(conn->set.iterate_query, default_db_sql_settings.iterate_query) != 0) { i_warning("sql: Ignoring changed iterate_query in %s, " "because userdb sql not used. " "(If this is intentional, set userdb_warning_disable=yes)", conn->config_path); } } #endif dovecot-2.3.21.1/src/auth/main.c0000644000000000000000000002432514656633576013143 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "array.h" #include "ioloop.h" #include "net.h" #include "lib-signals.h" #include "restrict-access.h" #include "child-wait.h" #include "sql-api.h" #include "module-dir.h" #include "randgen.h" #include "process-title.h" #include "settings-parser.h" #include "master-service.h" #include "master-service-settings.h" #include "master-interface.h" #include "dict.h" #include "password-scheme.h" #include "passdb-cache.h" #include "mech.h" #include "otp.h" #include "mech-otp-common.h" #include "auth.h" #include "auth-penalty.h" #include "auth-token.h" #include "auth-request-handler.h" #include "auth-request-stats.h" #include "auth-worker-server.h" #include "auth-worker-client.h" #include "auth-master-connection.h" #include "auth-client-connection.h" #include "auth-policy.h" #include #include #define AUTH_PENALTY_ANVIL_PATH "anvil-auth-penalty" enum auth_socket_type { AUTH_SOCKET_UNKNOWN = 0, AUTH_SOCKET_CLIENT, AUTH_SOCKET_LOGIN_CLIENT, AUTH_SOCKET_MASTER, AUTH_SOCKET_USERDB, AUTH_SOCKET_TOKEN, AUTH_SOCKET_TOKEN_LOGIN }; struct auth_socket_listener { enum auth_socket_type type; struct stat st; char *path; }; bool worker = FALSE, worker_restart_request = FALSE; time_t process_start_time; struct auth_penalty *auth_penalty; static pool_t auth_set_pool; static struct module *modules = NULL; static struct mechanisms_register *mech_reg; static ARRAY(struct auth_socket_listener) listeners; void auth_refresh_proctitle(void) { if (!global_auth_settings->verbose_proctitle || worker) return; process_title_set(t_strdup_printf( "[%u wait, %u passdb, %u userdb]", auth_request_state_count[AUTH_REQUEST_STATE_NEW] + auth_request_state_count[AUTH_REQUEST_STATE_MECH_CONTINUE] + auth_request_state_count[AUTH_REQUEST_STATE_FINISHED], auth_request_state_count[AUTH_REQUEST_STATE_PASSDB], auth_request_state_count[AUTH_REQUEST_STATE_USERDB])); } static const char *const *read_global_settings(void) { struct master_service_settings_output set_output; const char **services; unsigned int i, count; auth_set_pool = pool_alloconly_create("auth settings", 8192); global_auth_settings = auth_settings_read(NULL, auth_set_pool, &set_output); /* strdup() the service names, because they're allocated from set parser pool, and we'll later clear it. */ count = str_array_length(set_output.specific_services); services = p_new(auth_set_pool, const char *, count + 1); for (i = 0; i < count; i++) { services[i] = p_strdup(auth_set_pool, set_output.specific_services[i]); } return services; } static enum auth_socket_type auth_socket_type_get(const char *path) { const char *name, *suffix; name = strrchr(path, '/'); if (name == NULL) name = path; else name++; suffix = strrchr(name, '-'); if (suffix == NULL) suffix = name; else suffix++; if (strcmp(suffix, "login") == 0) return AUTH_SOCKET_LOGIN_CLIENT; else if (strcmp(suffix, "master") == 0) return AUTH_SOCKET_MASTER; else if (strcmp(suffix, "userdb") == 0) return AUTH_SOCKET_USERDB; else if (strcmp(suffix, "token") == 0) return AUTH_SOCKET_TOKEN; else if (strcmp(suffix, "tokenlogin") == 0) return AUTH_SOCKET_TOKEN_LOGIN; else return AUTH_SOCKET_CLIENT; } static void listeners_init(void) { unsigned int i, n; const char *path; i_array_init(&listeners, 8); n = master_service_get_socket_count(master_service); for (i = 0; i < n; i++) { int fd = MASTER_LISTEN_FD_FIRST + i; struct auth_socket_listener *l; l = array_idx_get_space(&listeners, fd); if (net_getunixname(fd, &path) < 0) { if (errno != ENOTSOCK) i_fatal("getunixname(%d) failed: %m", fd); /* not a unix socket, set its name and type lazily */ } else { l->type = auth_socket_type_get(path); l->path = i_strdup(path); if (l->type == AUTH_SOCKET_USERDB) { if (stat(path, &l->st) < 0) i_error("stat(%s) failed: %m", path); } } } } static bool auth_module_filter(const char *name, void *context ATTR_UNUSED) { if (str_begins(name, "authdb_") || str_begins(name, "mech_")) { /* this is lazily loaded */ return FALSE; } return TRUE; } static void main_preinit(void) { struct module_dir_load_settings mod_set; const char *const *services; /* Load built-in SQL drivers (if any) */ sql_drivers_init(); sql_drivers_register_all(); /* Initialize databases so their configuration files can be readable only by root. Also load all modules here. */ passdbs_init(); userdbs_init(); /* init schemes before plugins are loaded */ password_schemes_init(); services = read_global_settings(); i_zero(&mod_set); mod_set.abi_version = DOVECOT_ABI_VERSION; mod_set.require_init_funcs = TRUE; mod_set.debug = global_auth_settings->debug; mod_set.filter_callback = auth_module_filter; modules = module_dir_load(AUTH_MODULE_DIR, NULL, &mod_set); module_dir_init(modules); if (!worker) auth_penalty = auth_penalty_init(AUTH_PENALTY_ANVIL_PATH); auth_request_stats_init(); mech_init(global_auth_settings); mech_reg = mech_register_init(global_auth_settings); dict_drivers_register_builtin(); auths_preinit(global_auth_settings, auth_set_pool, mech_reg, services); listeners_init(); if (!worker) auth_token_init(); /* Password lookups etc. may require roots, allow it. */ restrict_access_by_env(RESTRICT_ACCESS_FLAG_ALLOW_ROOT, NULL); restrict_access_allow_coredumps(TRUE); } void auth_module_load(const char *names) { struct module_dir_load_settings mod_set; i_zero(&mod_set); mod_set.abi_version = DOVECOT_ABI_VERSION; mod_set.require_init_funcs = TRUE; mod_set.debug = global_auth_settings->debug; mod_set.ignore_missing = TRUE; modules = module_dir_load_missing(modules, AUTH_MODULE_DIR, names, &mod_set); module_dir_init(modules); } static void main_init(void) { process_start_time = ioloop_time; /* If auth caches aren't used, just ignore these signals */ lib_signals_ignore(SIGHUP, TRUE); lib_signals_ignore(SIGUSR2, TRUE); /* set proctitles before init()s, since they may set them to error */ auth_refresh_proctitle(); auth_worker_refresh_proctitle(""); child_wait_init(); auth_worker_server_init(); auths_init(); auth_request_handler_init(); auth_policy_init(); if (worker) { /* workers have only a single connection from the master auth process */ master_service_set_client_limit(master_service, 1); auth_worker_set_max_service_count( master_service_get_service_count(master_service)); /* make sure this process cycles if auth connection drops */ master_service_set_service_count(master_service, 1); } else { /* caching is handled only by the main auth process */ passdb_cache_init(global_auth_settings); } } static void main_deinit(void) { struct auth_socket_listener *l; if (auth_penalty != NULL) { /* cancel all pending anvil penalty lookups */ auth_penalty_deinit(&auth_penalty); } /* deinit auth workers, which aborts pending requests */ auth_worker_server_deinit(); /* deinit passdbs and userdbs. it aborts any pending async requests. */ auths_deinit(); /* flush pending requests */ auth_request_handler_deinit(); /* there are no more auth requests */ auths_free(); dict_drivers_unregister_builtin(); auth_token_deinit(); auth_client_connections_destroy_all(); auth_master_connections_destroy_all(); auth_worker_connections_destroy_all(); auth_policy_deinit(); mech_register_deinit(&mech_reg); mech_otp_deinit(); mech_deinit(global_auth_settings); /* allow modules to unregister their dbs/drivers/etc. before freeing the whole data structures containing them. */ module_dir_unload(&modules); userdbs_deinit(); passdbs_deinit(); passdb_cache_deinit(); password_schemes_deinit(); auth_request_stats_deinit(); sql_drivers_deinit(); child_wait_deinit(); array_foreach_modifiable(&listeners, l) i_free(l->path); array_free(&listeners); pool_unref(&auth_set_pool); } static void worker_connected(struct master_service_connection *conn) { if (auth_worker_has_client()) { i_error("Auth workers can handle only a single client"); return; } master_service_client_connection_accept(conn); (void)auth_worker_client_create(auth_default_service(), conn); } static void client_connected(struct master_service_connection *conn) { struct auth_socket_listener *l; struct auth *auth; l = array_idx_modifiable(&listeners, conn->listen_fd); if (l->type == AUTH_SOCKET_UNKNOWN) { /* first connection from inet socket, figure out its type from the listener name */ l->type = auth_socket_type_get(conn->name); l->path = i_strdup(conn->name); } auth = auth_default_service(); switch (l->type) { case AUTH_SOCKET_MASTER: (void)auth_master_connection_create(auth, conn->fd, l->path, NULL, FALSE); break; case AUTH_SOCKET_USERDB: (void)auth_master_connection_create(auth, conn->fd, l->path, &l->st, TRUE); break; case AUTH_SOCKET_LOGIN_CLIENT: auth_client_connection_create(auth, conn->fd, TRUE, FALSE); break; case AUTH_SOCKET_CLIENT: auth_client_connection_create(auth, conn->fd, FALSE, FALSE); break; case AUTH_SOCKET_TOKEN_LOGIN: auth_client_connection_create(auth, conn->fd, TRUE, TRUE); break; case AUTH_SOCKET_TOKEN: auth_client_connection_create(auth, conn->fd, FALSE, TRUE); break; default: i_unreached(); } master_service_client_connection_accept(conn); } static void auth_die(void) { if (!worker) { /* do nothing. auth clients should disconnect soon. */ } else { /* ask auth master to disconnect us */ auth_worker_client_send_shutdown(); } } int main(int argc, char *argv[]) { int c; enum master_service_flags service_flags = MASTER_SERVICE_FLAG_NO_SSL_INIT; master_service = master_service_init("auth", service_flags, &argc, &argv, "w"); master_service_init_log(master_service); while ((c = master_getopt(master_service)) > 0) { switch (c) { case 'w': master_service_init_log_with_pid(master_service); worker = TRUE; break; default: return FATAL_DEFAULT; } } main_preinit(); master_service_set_die_callback(master_service, auth_die); main_init(); master_service_init_finish(master_service); master_service_run(master_service, worker ? worker_connected : client_connected); main_deinit(); master_service_deinit(&master_service); return 0; } dovecot-2.3.21.1/src/auth/Makefile.am0000644000000000000000000001565014656633576014110 00000000000000noinst_LTLIBRARIES = libpassword.la libauth.la auth_moduledir = $(moduledir)/auth # automake seems to force making this unconditional.. NOPLUGIN_LDFLAGS = if GSSAPI_PLUGIN GSSAPI_LIB = libmech_gssapi.la endif if LDAP_PLUGIN LDAP_LIB = libauthdb_ldap.la endif LUA_LIB = AUTH_LUA_LIBS = AUTH_LUA_LDADD = if HAVE_LUA if AUTH_LUA_PLUGIN LUA_LIB += libauthdb_lua.la else AUTH_LUA_LIBS += $(LIBDOVECOT_LUA) AUTH_LUA_LDADD += $(LUA_LIBS) endif endif auth_module_LTLIBRARIES = \ $(GSSAPI_LIB) \ $(LDAP_LIB) \ $(LUA_LIB) \ libauthdb_imap.la pkglibexecdir = $(libexecdir)/dovecot pkglibexec_PROGRAMS = auth checkpassword-reply AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-dict \ -I$(top_srcdir)/src/lib-dns \ -I$(top_srcdir)/src/lib-http \ -I$(top_srcdir)/src/lib-sql \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-old-stats \ -I$(top_srcdir)/src/lib-otp \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-oauth2 \ -I$(top_srcdir)/src/lib-ssl-iostream \ -I$(top_srcdir)/src/lib-lua \ -I$(top_srcdir)/src/lib-dcrypt \ -DAUTH_MODULE_DIR=\""$(auth_moduledir)"\" \ -DPKG_LIBEXECDIR=\""$(pkglibexecdir)"\" \ -DPKG_RUNDIR=\""$(rundir)"\" \ -DSYSCONFDIR=\""$(sysconfdir)/dovecot"\" \ $(LUA_CFLAGS) \ $(AUTH_CFLAGS) auth_LDFLAGS = -export-dynamic libpassword_la_SOURCES = \ crypt-blowfish.c \ mycrypt.c \ password-scheme.c \ password-scheme-crypt.c \ password-scheme-md5crypt.c \ password-scheme-scram.c \ password-scheme-otp.c \ password-scheme-pbkdf2.c \ password-scheme-sodium.c libpassword_la_CFLAGS = $(AM_CPPFLAGS) $(LIBSODIUM_CFLAGS) auth_libs = \ libauth.la \ libstats_auth.la \ libpassword.la \ ../lib-otp/libotp.la \ $(AUTH_LUA_LIBS) \ $(LIBDOVECOT_SQL) auth_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS) auth_LDADD = $(auth_libs) $(LIBDOVECOT) $(AUTH_LIBS) $(BINARY_LDFLAGS) $(AUTH_LUA_LDADD) auth_DEPENDENCIES = $(auth_libs) $(LIBDOVECOT_DEPS) auth_SOURCES = main.c ldap_sources = db-ldap.c passdb-ldap.c userdb-ldap.c lua_sources = db-lua.c passdb-lua.c userdb-lua.c libauth_la_DEPENDENCIES = $(LIBDOVECOT_DEPS) libauth_la_SOURCES = \ auth.c \ auth-cache.c \ auth-client-connection.c \ auth-master-connection.c \ auth-policy.c \ mech-otp-common.c \ mech-plain-common.c \ auth-penalty.c \ auth-request.c \ auth-request-fields.c \ auth-request-handler.c \ auth-request-stats.c \ auth-request-var-expand.c \ auth-settings.c \ auth-fields.c \ auth-token.c \ auth-worker-client.c \ auth-worker-server.c \ db-checkpassword.c \ db-dict.c \ db-dict-cache-key.c \ db-oauth2.c \ db-sql.c \ db-passwd-file.c \ mech.c \ mech-anonymous.c \ mech-plain.c \ mech-login.c \ mech-cram-md5.c \ mech-digest-md5.c \ mech-external.c \ mech-gssapi.c \ mech-otp.c \ mech-scram.c \ mech-apop.c \ mech-winbind.c \ mech-dovecot-token.c \ mech-oauth2.c \ passdb.c \ passdb-blocking.c \ passdb-bsdauth.c \ passdb-cache.c \ passdb-checkpassword.c \ passdb-dict.c \ passdb-oauth2.c \ passdb-passwd.c \ passdb-passwd-file.c \ passdb-pam.c \ passdb-shadow.c \ passdb-sql.c \ passdb-static.c \ passdb-template.c \ userdb.c \ userdb-blocking.c \ userdb-checkpassword.c \ userdb-dict.c \ userdb-passwd.c \ userdb-passwd-file.c \ userdb-prefetch.c \ userdb-static.c \ userdb-sql.c \ userdb-template.c \ $(ldap_sources) \ $(lua_sources) headers = \ auth.h \ auth-cache.h \ auth-client-connection.h \ auth-common.h \ auth-master-connection.h \ mech-otp-common.h \ mech-plain-common.h \ mech-digest-md5-private.h \ mech-scram.h \ auth-penalty.h \ auth-policy.h \ auth-request.h \ auth-request-handler.h \ auth-request-handler-private.h \ auth-request-stats.h \ auth-request-var-expand.h \ auth-settings.h \ auth-stats.h \ auth-fields.h \ auth-token.h \ auth-worker-client.h \ auth-worker-server.h \ db-dict.h \ db-ldap.h \ db-sql.h \ db-passwd-file.h \ db-checkpassword.h \ db-oauth2.h \ mech.h \ mycrypt.h \ passdb.h \ passdb-blocking.h \ passdb-cache.h \ passdb-template.h \ password-scheme.h \ userdb.h \ userdb-blocking.h \ userdb-template.h if GSSAPI_PLUGIN libmech_gssapi_la_LDFLAGS = -module -avoid-version libmech_gssapi_la_LIBADD = $(KRB5_LIBS) libmech_gssapi_la_CPPFLAGS = $(AM_CPPFLAGS) $(KRB5_CFLAGS) -DPLUGIN_BUILD libmech_gssapi_la_SOURCES = mech-gssapi.c endif if LDAP_PLUGIN libauthdb_ldap_la_LDFLAGS = -module -avoid-version libauthdb_ldap_la_LIBADD = $(LDAP_LIBS) libauthdb_ldap_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD libauthdb_ldap_la_SOURCES = $(ldap_sources) endif if AUTH_LUA_PLUGIN libauthdb_lua_la_LDFLAGS = -module -avoid-version libauthdb_lua_la_LIBADD = $(LIBDOVECOT_LUA) libauthdb_lua_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD libauthdb_lua_la_SOURCES = $(lua_sources) endif libauthdb_imap_la_LDFLAGS = -module -avoid-version libauthdb_imap_la_LIBADD = \ ../lib-imap-client/libimap_client.la \ $(LIBDOVECOT) libauthdb_imap_la_CPPFLAGS = \ $(AM_CPPFLAGS) \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-imap-client libauthdb_imap_la_SOURCES = passdb-imap.c pkginc_libdir=$(pkgincludedir) pkginc_lib_HEADERS = $(headers) checkpassword_reply_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS) checkpassword_reply_LDADD = $(LIBDOVECOT) $(BINARY_LDFLAGS) checkpassword_reply_DEPENDENCIES = $(LIBDOVECOT_DEPS) checkpassword_reply_sources = \ checkpassword-reply.c stats_moduledir = $(moduledir)/old-stats stats_module_LTLIBRARIES = libstats_auth.la libstats_auth_la_LDFLAGS = -module -avoid-version libstats_auth_la_LIBADD = $(LIBDOVECOT) libstats_auth_la_DEPENDENCIES = $(LIBDOVECOT_DEPS) libstats_auth_la_SOURCES = auth-stats.c test_programs = \ test-libpassword \ test-auth-cache \ test-auth \ test-mech noinst_PROGRAMS = $(test_programs) noinst_HEADERS = test-auth.h crypt-blowfish.h db-lua.h test_libs = \ ../lib-dovecot/libdovecot.la test_libpassword_SOURCES = test-libpassword.c test_libpassword_LDADD = \ libpassword.la \ ../lib-otp/libotp.la \ $(CRYPT_LIBS) \ $(LIBDOVECOT_SQL) \ $(LIBSODIUM_LIBS) \ $(test_libs) \ $(BINARY_LDFLAGS) test_libpassword_DEPENDENCIES = libpassword.la test_libpassword_CPPFLAGS = $(AM_CPPFLAGS) $(BINARY_CFLAGS) test_auth_cache_SOURCES = auth-cache.c test-auth-cache.c test_auth_cache_LDADD = $(test_libs) test_auth_cache_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs) # this is needed to force auth-cache.c recompilation test_auth_cache_CPPFLAGS = $(AM_CPPFLAGS) test_auth_SOURCES = \ test-auth-request-var-expand.c \ test-auth-request-fields.c \ test-username-filter.c \ test-db-dict.c \ test-lua.c \ test-mock.c \ test-main.c test_auth_LDADD = $(test_libs) $(auth_libs) $(AUTH_LIBS) $(LUA_LIBS) test_auth_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs) test_mech_SOURCES = \ test-mock.c \ test-mech.c test_mech_LDADD = $(test_libs) $(auth_libs) $(AUTH_LIBS) $(LUA_LIBS) test_mech_DEPENDENCIES = $(pkglibexec_PROGRAMS) $(test_libs) check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done dovecot-2.3.21.1/src/auth/auth-worker-server.h0000644000000000000000000000104214656633576015767 00000000000000#ifndef AUTH_WORKER_SERVER_H #define AUTH_WORKER_SERVER_H struct auth_request; struct auth_stream_reply; struct auth_worker_connection; typedef bool auth_worker_callback_t(struct auth_worker_connection *conn, const char *reply, void *context); void auth_worker_call(pool_t pool, const char *username, const char *data, auth_worker_callback_t *callback, void *context); void auth_worker_server_resume_input(struct auth_worker_connection *conn); void auth_worker_server_init(void); void auth_worker_server_deinit(void); #endif dovecot-2.3.21.1/src/auth/passdb-checkpassword.c0000644000000000000000000001014714656633576016326 00000000000000/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "passdb.h" #ifdef PASSDB_CHECKPASSWORD #include "password-scheme.h" #include "db-checkpassword.h" struct checkpassword_passdb_module { struct passdb_module module; struct db_checkpassword *db; }; static void auth_checkpassword_callback(struct auth_request *request, enum db_checkpassword_status status, const char *const *extra_fields, verify_plain_callback_t *callback) { const char *scheme, *crypted_pass = NULL; unsigned int i; switch (status) { case DB_CHECKPASSWORD_STATUS_INTERNAL_FAILURE: callback(PASSDB_RESULT_INTERNAL_FAILURE, request); return; case DB_CHECKPASSWORD_STATUS_FAILURE: callback(PASSDB_RESULT_PASSWORD_MISMATCH, request); return; case DB_CHECKPASSWORD_STATUS_OK: break; } for (i = 0; extra_fields[i] != NULL; i++) { if (str_begins(extra_fields[i], "password=")) crypted_pass = extra_fields[i]+9; else if (extra_fields[i][0] != '\0') { auth_request_set_field_keyvalue(request, extra_fields[i], NULL); } } if (crypted_pass != NULL) { /* for cache */ scheme = password_get_scheme(&crypted_pass); if (scheme != NULL) { auth_request_set_field(request, "password", crypted_pass, scheme); } else { e_error(authdb_event(request), "password field returned without {scheme} prefix"); } } callback(PASSDB_RESULT_OK, request); } static void checkpassword_verify_plain(struct auth_request *request, const char *password, verify_plain_callback_t *callback) { struct passdb_module *_module = request->passdb->passdb; struct checkpassword_passdb_module *module = (struct checkpassword_passdb_module *)_module; db_checkpassword_call(module->db, request, password, auth_checkpassword_callback, callback); } static void credentials_checkpassword_callback(struct auth_request *request, enum db_checkpassword_status status, const char *const *extra_fields, lookup_credentials_callback_t *callback) { const char *scheme, *crypted_pass = NULL; unsigned int i; switch (status) { case DB_CHECKPASSWORD_STATUS_INTERNAL_FAILURE: callback(PASSDB_RESULT_INTERNAL_FAILURE, NULL, 0, request); return; case DB_CHECKPASSWORD_STATUS_FAILURE: callback(PASSDB_RESULT_USER_UNKNOWN, NULL, 0, request); return; case DB_CHECKPASSWORD_STATUS_OK: break; } for (i = 0; extra_fields[i] != NULL; i++) { if (str_begins(extra_fields[i], "password=")) crypted_pass = extra_fields[i]+9; else if (extra_fields[i][0] != '\0') { auth_request_set_field_keyvalue(request, extra_fields[i], NULL); } } scheme = password_get_scheme(&crypted_pass); if (scheme == NULL) scheme = request->wanted_credentials_scheme; passdb_handle_credentials(PASSDB_RESULT_OK, crypted_pass, scheme, callback, request); } static void checkpassword_lookup_credentials(struct auth_request *request, lookup_credentials_callback_t *callback) { struct passdb_module *_module = request->passdb->passdb; struct checkpassword_passdb_module *module = (struct checkpassword_passdb_module *)_module; db_checkpassword_call(module->db, request, NULL, credentials_checkpassword_callback, callback); } static struct passdb_module * checkpassword_preinit(pool_t pool, const char *args) { struct checkpassword_passdb_module *module; const char *checkpassword_path = args; const char *checkpassword_reply_path = PKG_LIBEXECDIR"/checkpassword-reply"; module = p_new(pool, struct checkpassword_passdb_module, 1); module->db = db_checkpassword_init(checkpassword_path, checkpassword_reply_path); return &module->module; } static void checkpassword_deinit(struct passdb_module *_module) { struct checkpassword_passdb_module *module = (struct checkpassword_passdb_module *)_module; db_checkpassword_deinit(&module->db); } struct passdb_module_interface passdb_checkpassword = { "checkpassword", checkpassword_preinit, NULL, checkpassword_deinit, checkpassword_verify_plain, checkpassword_lookup_credentials, NULL }; #else struct passdb_module_interface passdb_checkpassword = { .name = "checkpassword" }; #endif dovecot-2.3.21.1/src/auth/auth-cache.c0000644000000000000000000003064414656633576014222 00000000000000/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "lib-signals.h" #include "hash.h" #include "str.h" #include "strescape.h" #include "var-expand.h" #include "auth-request.h" #include "auth-cache.h" #include struct auth_cache { HASH_TABLE(char *, struct auth_cache_node *) hash; struct auth_cache_node *head, *tail; size_t max_size, size_left; unsigned int ttl_secs, neg_ttl_secs; unsigned int hit_count, miss_count; unsigned int pos_entries, neg_entries; unsigned long long pos_size, neg_size; }; static bool auth_request_var_expand_tab_find(const char *key, unsigned int size, unsigned int *idx_r) { const struct var_expand_table *tab = auth_request_var_expand_static_tab; unsigned int i; for (i = 0; tab[i].key != '\0' || tab[i].long_key != NULL; i++) { if (size == 1) { if (key[0] == tab[i].key) { *idx_r = i; return TRUE; } } else if (tab[i].long_key != NULL) { if (strncmp(key, tab[i].long_key, size) == 0 && tab[i].long_key[size] == '\0') { *idx_r = i; return TRUE; } } } return FALSE; } static void auth_cache_key_add_var(string_t *str, const char *data, unsigned int len) { if (str_len(str) > 0) str_append_c(str, '\t'); str_append_c(str, '%'); if (len == 1) str_append_c(str, data[0]); else { str_append_c(str, '{'); str_append_data(str, data, len); str_append_c(str, '}'); } } static void auth_cache_key_add_tab_idx(string_t *str, unsigned int i) { const struct var_expand_table *tab = &auth_request_var_expand_static_tab[i]; if (str_len(str) > 0) str_append_c(str, '\t'); str_append_c(str, '%'); if (tab->key != '\0') str_append_c(str, tab->key); else { str_append_c(str, '{'); str_append(str, tab->long_key); str_append_c(str, '}'); } } char *auth_cache_parse_key(pool_t pool, const char *query) { string_t *str; bool key_seen[AUTH_REQUEST_VAR_TAB_COUNT]; const char *extra_vars; unsigned int i, idx, size, tab_idx; memset(key_seen, 0, sizeof(key_seen)); str = t_str_new(32); for (; *query != '\0'; ) { if (*query != '%') { query++; continue; } var_get_key_range(++query, &idx, &size); if (size == 0) { /* broken %variable ending too early */ break; } query += idx; if (!auth_request_var_expand_tab_find(query, size, &tab_idx)) { /* just add the key. it would be nice to prevent duplicates here as well, but that's just too much trouble and probably very rare. */ auth_cache_key_add_var(str, query, size); } else { i_assert(tab_idx < N_ELEMENTS(key_seen)); key_seen[tab_idx] = TRUE; } query += size; } if (key_seen[AUTH_REQUEST_VAR_TAB_USERNAME_IDX] && key_seen[AUTH_REQUEST_VAR_TAB_DOMAIN_IDX]) { /* %n and %d both used -> replace with %u */ key_seen[AUTH_REQUEST_VAR_TAB_USER_IDX] = TRUE; key_seen[AUTH_REQUEST_VAR_TAB_USERNAME_IDX] = FALSE; key_seen[AUTH_REQUEST_VAR_TAB_DOMAIN_IDX] = FALSE; } /* we rely on these being at the beginning */ i_assert(AUTH_REQUEST_VAR_TAB_USER_IDX == 0); i_assert(AUTH_REQUEST_VAR_TAB_USERNAME_IDX == 1); i_assert(AUTH_REQUEST_VAR_TAB_DOMAIN_IDX == 2); extra_vars = t_strdup(str_c(str)); str_truncate(str, 0); for (i = 0; i < N_ELEMENTS(key_seen); i++) { if (key_seen[i]) auth_cache_key_add_tab_idx(str, i); } if (*extra_vars != '\0') { if (str_len(str) > 0) str_append_c(str, '\t'); str_append(str, extra_vars); } return p_strdup(pool, str_c(str)); } static void auth_cache_node_unlink(struct auth_cache *cache, struct auth_cache_node *node) { if (node->prev != NULL) node->prev->next = node->next; else { /* unlinking tail */ cache->tail = node->next; } if (node->next != NULL) node->next->prev = node->prev; else { /* unlinking head */ cache->head = node->prev; } } static void auth_cache_node_link_head(struct auth_cache *cache, struct auth_cache_node *node) { node->prev = cache->head; node->next = NULL; cache->head = node; if (node->prev != NULL) node->prev->next = node; else cache->tail = node; } static void auth_cache_node_destroy(struct auth_cache *cache, struct auth_cache_node *node) { char *key = node->data; auth_cache_node_unlink(cache, node); cache->size_left += node->alloc_size; hash_table_remove(cache->hash, key); i_free(node); } static void sig_auth_cache_clear(const siginfo_t *si ATTR_UNUSED, void *context) { struct auth_cache *cache = context; i_info("SIGHUP received, %u cache entries flushed", auth_cache_clear(cache)); } static void sig_auth_cache_stats(const siginfo_t *si ATTR_UNUSED, void *context) { struct auth_cache *cache = context; unsigned int total_count; size_t cache_used; total_count = cache->hit_count + cache->miss_count; i_info("Authentication cache hits %u/%u (%u%%)", cache->hit_count, total_count, total_count == 0 ? 100 : (cache->hit_count * 100 / total_count)); i_info("Authentication cache inserts: " "positive: %u entries %llu bytes, " "negative: %u entries %llu bytes", cache->pos_entries, cache->pos_size, cache->neg_entries, cache->neg_size); cache_used = cache->max_size - cache->size_left; i_info("Authentication cache current size: " "%zu bytes used of %zu bytes (%u%%)", cache_used, cache->max_size, (unsigned int)(cache_used * 100ULL / cache->max_size)); /* reset counters */ cache->hit_count = cache->miss_count = 0; cache->pos_entries = cache->neg_entries = 0; cache->pos_size = cache->neg_size = 0; } struct auth_cache *auth_cache_new(size_t max_size, unsigned int ttl_secs, unsigned int neg_ttl_secs ) { struct auth_cache *cache; cache = i_new(struct auth_cache, 1); hash_table_create(&cache->hash, default_pool, 0, str_hash, strcmp); cache->max_size = max_size; cache->size_left = max_size; cache->ttl_secs = ttl_secs; cache->neg_ttl_secs = neg_ttl_secs; lib_signals_set_handler(SIGHUP, LIBSIG_FLAGS_SAFE, sig_auth_cache_clear, cache); lib_signals_set_handler(SIGUSR2, LIBSIG_FLAGS_SAFE, sig_auth_cache_stats, cache); return cache; } void auth_cache_free(struct auth_cache **_cache) { struct auth_cache *cache = *_cache; *_cache = NULL; lib_signals_unset_handler(SIGHUP, sig_auth_cache_clear, cache); lib_signals_unset_handler(SIGUSR2, sig_auth_cache_stats, cache); auth_cache_clear(cache); hash_table_destroy(&cache->hash); i_free(cache); } unsigned int auth_cache_clear(struct auth_cache *cache) { unsigned int ret = hash_table_count(cache->hash); while (cache->tail != NULL) auth_cache_node_destroy(cache, cache->tail); hash_table_clear(cache->hash, FALSE); return ret; } static bool auth_cache_node_is_user(struct auth_cache_node *node, const char *username) { const char *data = node->data; size_t username_len; /* The cache nodes begin with "P"/"U", passdb/userdb ID, optional "+" master user, "\t" and then usually followed by the username. It's too much trouble to keep track of all the cache keys, so we'll just match it as if it was the username. If e.g. '%n' is used in the cache key instead of '%u', it means that cache entries can be removed only when @domain isn't in the username parameter. */ if (*data != 'P' && *data != 'U') return FALSE; data++; while (*data >= '0' && *data <= '9') data++; if (*data == '+') { /* skip over +master_user */ while (*data != '\t' && *data != '\0') data++; } if (*data != '\t') return FALSE; data++; username_len = strlen(username); return str_begins(data, username) && (data[username_len] == '\t' || data[username_len] == '\0'); } static bool auth_cache_node_is_one_of_users(struct auth_cache_node *node, const char *const *usernames) { unsigned int i; for (i = 0; usernames[i] != NULL; i++) { if (auth_cache_node_is_user(node, usernames[i])) return TRUE; } return FALSE; } unsigned int auth_cache_clear_users(struct auth_cache *cache, const char *const *usernames) { struct auth_cache_node *node, *next; unsigned int ret = 0; for (node = cache->tail; node != NULL; node = next) { next = node->next; if (auth_cache_node_is_one_of_users(node, usernames)) { auth_cache_node_destroy(cache, node); ret++; } } return ret; } static const char * auth_cache_escape(const char *string, const struct auth_request *auth_request ATTR_UNUSED) { /* cache key %variables are separated by tabs, make sure that there are no tabs in the string */ return str_tabescape(string); } static const char * auth_request_expand_cache_key(const struct auth_request *request, const char *key, const char *username) { static bool error_logged = FALSE; const char *error; /* Uniquely identify the request's passdb/userdb with the P/U prefix and by "%!", which expands to the passdb/userdb ID number. */ key = t_strconcat(request->userdb_lookup ? "U" : "P", "%!", request->fields.master_user == NULL ? "" : "+%{master_user}", "\t", key, NULL); /* It's fine to have unknown %variables in the cache key. For example db-ldap can have pass_attrs containing %{ldap:fields} which are used for output, not as part of the input needed for cache_key. Those could in theory be filtered out early in the cache_key, but that gets more problematic when it needs to support also filtering out e.g. %{sha256:ldap:fields}. */ string_t *value = t_str_new(128); unsigned int count = 0; const struct var_expand_table *table = auth_request_get_var_expand_table_full(request, username, auth_cache_escape, &count); if (auth_request_var_expand_with_table(value, key, request, table, auth_cache_escape, &error) < 0 && !error_logged) { error_logged = TRUE; e_error(authdb_event(request), "Failed to expand auth cache key %s: %s", key, error); } return str_c(value); } const char * auth_cache_lookup(struct auth_cache *cache, const struct auth_request *request, const char *key, struct auth_cache_node **node_r, bool *expired_r, bool *neg_expired_r) { struct auth_cache_node *node; const char *value; unsigned int ttl_secs; time_t now; *expired_r = FALSE; *neg_expired_r = FALSE; key = auth_request_expand_cache_key(request, key, request->fields.translated_username); node = hash_table_lookup(cache->hash, key); if (node == NULL) { cache->miss_count++; return NULL; } value = node->data + strlen(node->data) + 1; ttl_secs = *value == '\0' ? cache->neg_ttl_secs : cache->ttl_secs; now = time(NULL); if (node->created < now - (time_t)ttl_secs) { /* TTL expired */ cache->miss_count++; *expired_r = TRUE; } else { /* move to head */ if (node != cache->head) { auth_cache_node_unlink(cache, node); auth_cache_node_link_head(cache, node); } cache->hit_count++; } if (node->created < now - (time_t)cache->neg_ttl_secs) *neg_expired_r = TRUE; if (node_r != NULL) *node_r = node; return value; } void auth_cache_insert(struct auth_cache *cache, struct auth_request *request, const char *key, const char *value, bool last_success) { struct auth_cache_node *node; size_t data_size, alloc_size, key_len, value_len = strlen(value); char *hash_key; if (*value == '\0' && cache->neg_ttl_secs == 0) { /* we're not caching negative entries */ return; } key = auth_request_expand_cache_key(request, key, request->fields.translated_username); key_len = strlen(key); data_size = key_len + 1 + value_len + 1; alloc_size = sizeof(struct auth_cache_node) + data_size; /* make sure we have enough space */ while (cache->size_left < alloc_size && cache->tail != NULL) auth_cache_node_destroy(cache, cache->tail); node = hash_table_lookup(cache->hash, key); if (node != NULL) { /* key is already in cache (probably expired), remove it */ auth_cache_node_destroy(cache, node); } /* @UNSAFE */ node = i_malloc(alloc_size); node->created = time(NULL); node->alloc_size = alloc_size; node->last_success = last_success; memcpy(node->data, key, key_len); memcpy(node->data + key_len + 1, value, value_len); auth_cache_node_link_head(cache, node); cache->size_left -= alloc_size; hash_key = node->data; hash_table_insert(cache->hash, hash_key, node); if (*value != '\0') { cache->pos_entries++; cache->pos_size += alloc_size; } else { cache->neg_entries++; cache->neg_size += alloc_size; } } void auth_cache_remove(struct auth_cache *cache, const struct auth_request *request, const char *key) { struct auth_cache_node *node; key = auth_request_expand_cache_key(request, key, request->fields.user); node = hash_table_lookup(cache->hash, key); if (node == NULL) return; auth_cache_node_destroy(cache, node); } dovecot-2.3.21.1/src/auth/auth-client-connection.c0000644000000000000000000002720714656633576016573 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "net.h" #include "hex-binary.h" #include "hostpid.h" #include "llist.h" #include "str.h" #include "str-sanitize.h" #include "randgen.h" #include "safe-memset.h" #include "master-service.h" #include "mech.h" #include "auth-fields.h" #include "auth-request-handler.h" #include "auth-client-interface.h" #include "auth-client-connection.h" #include "auth-master-connection.h" #define OUTBUF_THROTTLE_SIZE (1024*50) #define AUTH_DEBUG_SENSITIVE_SUFFIX \ " (previous base64 data may contain sensitive data)" static void auth_client_disconnected(struct auth_client_connection **_conn); static void auth_client_connection_unref(struct auth_client_connection **_conn); static void auth_client_input(struct auth_client_connection *conn); static struct auth_client_connection *auth_client_connections; static const char *reply_line_hide_pass(const char *line) { string_t *newline; const char *p, *p2; if (strstr(line, "pass") == NULL) return line; newline = t_str_new(strlen(line)); const char *const *fields = t_strsplit(line, "\t"); while(*fields != NULL) { p = strstr(*fields, "pass"); p2 = strchr(*fields, '='); if (p == NULL || p2 == NULL || p2 < p) { str_append(newline, *fields); } else { /* include = */ str_append_data(newline, *fields, (p2 - *fields)+1); str_append(newline, PASSWORD_HIDDEN_STR); } str_append_c(newline, '\t'); fields++; } return str_c(newline); } static void auth_client_send(struct auth_client_connection *conn, const char *cmd) { struct const_iovec iov[2]; iov[0].iov_base = cmd; iov[0].iov_len = strlen(cmd); iov[1].iov_base = "\n"; iov[1].iov_len = 1; o_stream_nsendv(conn->output, iov, 2); if (o_stream_get_buffer_used_size(conn->output) >= OUTBUF_THROTTLE_SIZE) { /* stop reading new requests until client has read the pending replies. */ io_remove(&conn->io); } e_debug(conn->event, "client passdb out: %s", conn->auth->set->debug_passwords ? cmd : reply_line_hide_pass(cmd)); } static void auth_callback(const char *reply, struct auth_client_connection *conn) { if (reply == NULL) { /* handler destroyed */ auth_client_connection_unref(&conn); } else { auth_client_send(conn, reply); } } static bool auth_client_input_cpid(struct auth_client_connection *conn, const char *args) { struct auth_client_connection *old; unsigned int pid; i_assert(conn->pid == 0); if (str_to_uint(args, &pid) < 0 || pid == 0) { e_error(conn->event, "BUG: Authentication client said it's PID 0"); return FALSE; } if (conn->login_requests) old = auth_client_connection_lookup(pid); else { /* the client is only authenticating, not logging in. the PID isn't necessary, and since we allow authentication via TCP sockets the PIDs may conflict, so ignore them. */ old = NULL; pid = 0; } if (old != NULL) { /* already exists. it's possible that it just reconnected, see if the old connection is still there. */ i_assert(old != conn); if (i_stream_read(old->input) == -1) { auth_client_disconnected(&old); old = NULL; } } if (old != NULL) { e_error(conn->event, "BUG: Authentication client gave a PID " "%u of existing connection", pid); return FALSE; } /* handshake complete, we can now actually start serving requests */ conn->refcount++; conn->request_handler = auth_request_handler_create(conn->token_auth, auth_callback, conn, !conn->login_requests ? NULL : auth_master_request_callback); auth_request_handler_set(conn->request_handler, conn->connect_uid, pid); conn->pid = pid; e_debug(conn->event, "auth client connected (pid=%u)", conn->pid); return TRUE; } static int auth_client_output(struct auth_client_connection *conn) { if (o_stream_flush(conn->output) < 0) { auth_client_disconnected(&conn); return 1; } if (o_stream_get_buffer_used_size(conn->output) <= OUTBUF_THROTTLE_SIZE/3 && conn->io == NULL) { /* allow input again */ conn->io = io_add(conn->fd, IO_READ, auth_client_input, conn); } return 1; } static const char * auth_line_hide_pass(struct auth_client_connection *conn, const char *line) { const char *p, *p2; p = strstr(line, "\tresp="); if (p == NULL) return line; p += 6; if (conn->auth->set->debug_passwords) return t_strconcat(line, AUTH_DEBUG_SENSITIVE_SUFFIX, NULL); p2 = strchr(p, '\t'); return t_strconcat(t_strdup_until(line, p), PASSWORD_HIDDEN_STR, p2, NULL); } static const char * cont_line_hide_pass(struct auth_client_connection *conn, const char *line) { const char *p; if (conn->auth->set->debug_passwords) return t_strconcat(line, AUTH_DEBUG_SENSITIVE_SUFFIX, NULL); p = strchr(line, '\t'); if (p == NULL) return line; return t_strconcat(t_strdup_until(line, p), PASSWORD_HIDDEN_STR, NULL); } static bool auth_client_cancel(struct auth_client_connection *conn, const char *line) { unsigned int client_id; if (str_to_uint(line, &client_id) < 0) { e_error(conn->event, "BUG: Authentication client sent broken CANCEL"); return FALSE; } auth_request_handler_cancel_request(conn->request_handler, client_id); return TRUE; } static bool auth_client_handle_line(struct auth_client_connection *conn, const char *line) { if (str_begins(line, "AUTH\t")) { if (conn->auth->set->debug) { e_debug(conn->event, "client in: %s", auth_line_hide_pass(conn, line)); } return auth_request_handler_auth_begin(conn->request_handler, line + 5); } if (str_begins(line, "CONT\t")) { if (conn->auth->set->debug) { e_debug(conn->event, "client in: %s", cont_line_hide_pass(conn, line)); } return auth_request_handler_auth_continue(conn->request_handler, line + 5); } if (str_begins(line, "CANCEL\t")) { if (conn->auth->set->debug) e_debug(conn->event, "client in: %s", line); return auth_client_cancel(conn, line + 7); } e_error(conn->event, "BUG: Authentication client sent unknown command: %s", str_sanitize(line, 80)); return FALSE; } static void auth_client_input(struct auth_client_connection *conn) { char *line; bool ret; switch (i_stream_read(conn->input)) { case 0: return; case -1: /* disconnected */ auth_client_disconnected(&conn); return; case -2: /* buffer full */ e_error(conn->event, "BUG: Auth client %u sent us more than %d bytes", conn->pid, (int)AUTH_CLIENT_MAX_LINE_LENGTH); auth_client_connection_destroy(&conn); return; } while (conn->request_handler == NULL) { /* still handshaking */ line = i_stream_next_line(conn->input); if (line == NULL) return; if (!conn->version_received) { unsigned int vmajor, vminor; const char *p; /* split the version line */ if (!str_begins(line, "VERSION\t") || str_parse_uint(line + 8, &vmajor, &p) < 0 || *(p++) != '\t' || str_to_uint(p, &vminor) < 0) { e_error(conn->event, "Authentication client " "sent invalid VERSION line: %s", line); auth_client_connection_destroy(&conn); return; } /* make sure the major version matches */ if (vmajor != AUTH_MASTER_PROTOCOL_MAJOR_VERSION) { e_error(conn->event, "Authentication client " "not compatible with this server " "(mixed old and new binaries?)"); auth_client_connection_destroy(&conn); return; } conn->version_minor = vminor; conn->version_received = TRUE; continue; } if (str_begins(line, "CPID\t")) { if (!auth_client_input_cpid(conn, line + 5)) { auth_client_connection_destroy(&conn); return; } } else { e_error(conn->event, "BUG: Authentication client sent " "unknown handshake command: %s", str_sanitize(line, 80)); auth_client_connection_destroy(&conn); return; } } conn->refcount++; while ((line = i_stream_next_line(conn->input)) != NULL) { T_BEGIN { ret = auth_client_handle_line(conn, line); safe_memset(line, 0, strlen(line)); } T_END; if (!ret) { struct auth_client_connection *tmp_conn = conn; auth_client_connection_destroy(&tmp_conn); break; } } auth_client_connection_unref(&conn); } void auth_client_connection_create(struct auth *auth, int fd, bool login_requests, bool token_auth) { static unsigned int connect_uid_counter = 0; struct auth_client_connection *conn; const char *mechanisms; string_t *str; conn = i_new(struct auth_client_connection, 1); conn->auth = auth; conn->refcount = 1; conn->connect_uid = ++connect_uid_counter; conn->login_requests = login_requests; conn->token_auth = token_auth; conn->event = event_create(auth_event); event_set_forced_debug(conn->event, auth->set->debug); random_fill(conn->cookie, sizeof(conn->cookie)); conn->fd = fd; conn->input = i_stream_create_fd(fd, AUTH_CLIENT_MAX_LINE_LENGTH); conn->output = o_stream_create_fd(fd, SIZE_MAX); o_stream_set_no_error_handling(conn->output, TRUE); o_stream_set_flush_callback(conn->output, auth_client_output, conn); conn->io = io_add(fd, IO_READ, auth_client_input, conn); DLLIST_PREPEND(&auth_client_connections, conn); if (token_auth) { mechanisms = t_strconcat("MECH\t", mech_dovecot_token.mech_name, "\n", NULL); } else { mechanisms = str_c(auth->reg->handshake); } str = t_str_new(128); str_printfa(str, "VERSION\t%u\t%u\n%sSPID\t%s\nCUID\t%u\nCOOKIE\t", AUTH_CLIENT_PROTOCOL_MAJOR_VERSION, AUTH_CLIENT_PROTOCOL_MINOR_VERSION, mechanisms, my_pid, conn->connect_uid); binary_to_hex_append(str, conn->cookie, sizeof(conn->cookie)); str_append(str, "\nDONE\n"); if (o_stream_send(conn->output, str_data(str), str_len(str)) < 0) auth_client_disconnected(&conn); } void auth_client_connection_destroy(struct auth_client_connection **_conn) { struct auth_client_connection *conn = *_conn; *_conn = NULL; if (conn->fd == -1) return; DLLIST_REMOVE(&auth_client_connections, conn); i_stream_close(conn->input); o_stream_close(conn->output); io_remove(&conn->io); net_disconnect(conn->fd); conn->fd = -1; if (conn->request_handler != NULL) { auth_request_handler_abort_requests(conn->request_handler); auth_request_handler_destroy(&conn->request_handler); } master_service_client_connection_destroyed(master_service); auth_client_connection_unref(&conn); } static void auth_client_disconnected(struct auth_client_connection **_conn) { struct auth_client_connection *conn = *_conn; unsigned int request_count; int err; *_conn = NULL; if (conn->input->stream_errno != 0) err = conn->input->stream_errno; else if (conn->output->stream_errno != 0) err = conn->output->stream_errno; else err = 0; request_count = conn->request_handler == NULL ? 0 : auth_request_handler_get_request_count(conn->request_handler); if (request_count > 0) { e_error(conn->event, "auth client %u disconnected with %u " "pending requests: %s", conn->pid, request_count, err == 0 ? "EOF" : strerror(err)); } auth_client_connection_destroy(&conn); } static void auth_client_connection_unref(struct auth_client_connection **_conn) { struct auth_client_connection *conn = *_conn; *_conn = NULL; if (--conn->refcount > 0) return; event_unref(&conn->event); i_stream_unref(&conn->input); o_stream_unref(&conn->output); i_free(conn); } struct auth_client_connection * auth_client_connection_lookup(unsigned int pid) { struct auth_client_connection *conn; for (conn = auth_client_connections; conn != NULL; conn = conn->next) { if (conn->pid == pid) return conn; } return NULL; } void auth_client_connections_destroy_all(void) { struct auth_client_connection *conn; while (auth_client_connections != NULL) { conn = auth_client_connections; auth_client_connection_destroy(&conn); } } dovecot-2.3.21.1/src/auth/passdb-pam.c0000644000000000000000000002444514656633576014251 00000000000000/* Based on auth_pam.c from popa3d by Solar Designer . You're allowed to do whatever you like with this software (including re-distribution in source and/or binary form, with or without modification), provided that credit is given where it is due and any modified versions are marked as such. There's absolutely no warranty. */ #include "auth-common.h" #include "passdb.h" #ifdef PASSDB_PAM #include "lib-signals.h" #include "str.h" #include "net.h" #include "safe-memset.h" #include "auth-cache.h" #include #ifdef HAVE_SECURITY_PAM_APPL_H # include #elif defined(HAVE_PAM_PAM_APPL_H) # include #endif #if defined(sun) || defined(__sun__) || defined(_HPUX_SOURCE) # define pam_const #else # define pam_const const #endif typedef pam_const void *pam_item_t; #define PASSDB_PAM_DEFAULT_MAX_REQUESTS 100 struct pam_passdb_module { struct passdb_module module; const char *service_name, *pam_cache_key; unsigned int requests_left; bool pam_setcred:1; bool pam_session:1; bool failure_show_msg:1; }; struct pam_conv_context { struct auth_request *request; const char *pass; const char *failure_msg; }; static int pam_userpass_conv(int num_msg, pam_const struct pam_message **msg, struct pam_response **resp_r, void *appdata_ptr) { /* @UNSAFE */ struct pam_conv_context *ctx = appdata_ptr; struct passdb_module *_passdb = ctx->request->passdb->passdb; struct pam_passdb_module *passdb = (struct pam_passdb_module *)_passdb; struct pam_response *resp; char *string; int i; *resp_r = NULL; resp = calloc(num_msg, sizeof(struct pam_response)); if (resp == NULL) i_fatal_status(FATAL_OUTOFMEM, "Out of memory"); for (i = 0; i < num_msg; i++) { e_debug(authdb_event(ctx->request), "#%d/%d style=%d msg=%s", i+1, num_msg, msg[i]->msg_style, msg[i]->msg != NULL ? msg[i]->msg : ""); switch (msg[i]->msg_style) { case PAM_PROMPT_ECHO_ON: /* Assume we're asking for user. We might not ever get here because PAM already knows the user. */ string = strdup(ctx->request->fields.user); if (string == NULL) i_fatal_status(FATAL_OUTOFMEM, "Out of memory"); break; case PAM_PROMPT_ECHO_OFF: /* Assume we're asking for password */ if (passdb->failure_show_msg) ctx->failure_msg = t_strdup(msg[i]->msg); string = strdup(ctx->pass); if (string == NULL) i_fatal_status(FATAL_OUTOFMEM, "Out of memory"); break; case PAM_ERROR_MSG: case PAM_TEXT_INFO: string = NULL; break; default: while (--i >= 0) { if (resp[i].resp != NULL) { safe_memset(resp[i].resp, 0, strlen(resp[i].resp)); free(resp[i].resp); } } free(resp); return PAM_CONV_ERR; } resp[i].resp_retcode = PAM_SUCCESS; resp[i].resp = string; } *resp_r = resp; return PAM_SUCCESS; } static const char * pam_get_missing_service_file_path(const char *service ATTR_UNUSED) { #ifdef SUNPAM /* Uses /etc/pam.conf - we're not going to parse that */ return NULL; #else static bool service_checked = FALSE; const char *path; struct stat st; if (service_checked) { /* check and complain only once */ return NULL; } service_checked = TRUE; path = t_strdup_printf("/etc/pam.d/%s", service); if (stat(path, &st) < 0 && errno == ENOENT) { /* looks like it's missing. but before assuming that the system even uses /etc/pam.d, make sure that it exists. */ if (stat("/etc/pam.d", &st) == 0) return path; } /* exists or is unknown */ return NULL; #endif } static int try_pam_auth(struct auth_request *request, pam_handle_t *pamh, const char *service) { struct passdb_module *_module = request->passdb->passdb; struct pam_passdb_module *module = (struct pam_passdb_module *)_module; const char *path, *str; pam_item_t item; int status; if ((status = pam_authenticate(pamh, 0)) != PAM_SUCCESS) { path = pam_get_missing_service_file_path(service); switch (status) { case PAM_USER_UNKNOWN: str = "unknown user"; break; default: str = t_strconcat("pam_authenticate() failed: ", pam_strerror(pamh, status), NULL); break; } if (path != NULL) { /* log this as error, since it probably is */ str = t_strdup_printf("%s (%s missing?)", str, path); e_error(authdb_event(request), "%s", str); } else if (status == PAM_AUTH_ERR) { str = t_strconcat(str, " ("AUTH_LOG_MSG_PASSWORD_MISMATCH"?)", NULL); if (request->set->debug_passwords) { str = t_strconcat(str, " (given password: ", request->mech_password, ")", NULL); } e_info(authdb_event(request), "%s", str); } else { if (status == PAM_USER_UNKNOWN) auth_request_log_unknown_user(request, AUTH_SUBSYS_DB); else { e_info(authdb_event(request), "%s", str); } } return status; } #ifdef HAVE_PAM_SETCRED if (module->pam_setcred) { if ((status = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) { e_error(authdb_event(request), "pam_setcred() failed: %s", pam_strerror(pamh, status)); return status; } } #endif if ((status = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) { e_error(authdb_event(request), "pam_acct_mgmt() failed: %s", pam_strerror(pamh, status)); return status; } if (module->pam_session) { if ((status = pam_open_session(pamh, 0)) != PAM_SUCCESS) { e_error(authdb_event(request), "pam_open_session() failed: %s", pam_strerror(pamh, status)); return status; } if ((status = pam_close_session(pamh, 0)) != PAM_SUCCESS) { e_error(authdb_event(request), "pam_close_session() failed: %s", pam_strerror(pamh, status)); return status; } } status = pam_get_item(pamh, PAM_USER, &item); if (status != PAM_SUCCESS) { e_error(authdb_event(request), "pam_get_item(PAM_USER) failed: %s", pam_strerror(pamh, status)); return status; } auth_request_set_field(request, "user", item, NULL); return PAM_SUCCESS; } static void set_pam_items(struct auth_request *request, pam_handle_t *pamh) { const char *host; /* These shouldn't fail, and we don't really care if they do. */ host = net_ip2addr(&request->fields.remote_ip); if (host[0] != '\0') (void)pam_set_item(pamh, PAM_RHOST, host); (void)pam_set_item(pamh, PAM_RUSER, request->fields.user); /* TTY is needed by eg. pam_access module */ (void)pam_set_item(pamh, PAM_TTY, "dovecot"); } static enum passdb_result pam_verify_plain_call(struct auth_request *request, const char *service, const char *password) { pam_handle_t *pamh; struct pam_conv_context ctx; struct pam_conv conv; enum passdb_result result; int status, status2; conv.conv = pam_userpass_conv; conv.appdata_ptr = &ctx; i_zero(&ctx); ctx.request = request; ctx.pass = password; status = pam_start(service, request->fields.user, &conv, &pamh); if (status != PAM_SUCCESS) { e_error(authdb_event(request), "pam_start() failed: %s", pam_strerror(pamh, status)); return PASSDB_RESULT_INTERNAL_FAILURE; } set_pam_items(request, pamh); status = try_pam_auth(request, pamh, service); if ((status2 = pam_end(pamh, status)) != PAM_SUCCESS) { e_error(authdb_event(request), "pam_end() failed: %s", pam_strerror(pamh, status2)); return PASSDB_RESULT_INTERNAL_FAILURE; } switch (status) { case PAM_SUCCESS: result = PASSDB_RESULT_OK; break; case PAM_USER_UNKNOWN: result = PASSDB_RESULT_USER_UNKNOWN; break; case PAM_NEW_AUTHTOK_REQD: case PAM_ACCT_EXPIRED: result = PASSDB_RESULT_PASS_EXPIRED; break; default: result = PASSDB_RESULT_PASSWORD_MISMATCH; break; } if (result != PASSDB_RESULT_OK && ctx.failure_msg != NULL) { auth_request_set_field(request, "reason", ctx.failure_msg, NULL); } return result; } static void pam_verify_plain(struct auth_request *request, const char *password, verify_plain_callback_t *callback) { struct passdb_module *_module = request->passdb->passdb; struct pam_passdb_module *module = (struct pam_passdb_module *)_module; enum passdb_result result; const char *service, *error; if (module->requests_left > 0) { if (--module->requests_left == 0) worker_restart_request = TRUE; } if (t_auth_request_var_expand(module->service_name, request, NULL, &service, &error) <= 0) { e_debug(authdb_event(request), "Failed to expand service %s: %s", module->service_name, error); callback(PASSDB_RESULT_INTERNAL_FAILURE, request); return; } e_debug(authdb_event(request), "lookup service=%s", service); result = pam_verify_plain_call(request, service, password); callback(result, request); } static struct passdb_module * pam_preinit(pool_t pool, const char *args) { struct pam_passdb_module *module; const char *const *t_args; int i; module = p_new(pool, struct pam_passdb_module, 1); module->service_name = "dovecot"; /* we're caching the password by using directly the plaintext password given by the auth mechanism */ module->module.default_pass_scheme = "PLAIN"; module->module.blocking = TRUE; module->requests_left = PASSDB_PAM_DEFAULT_MAX_REQUESTS; t_args = t_strsplit_spaces(args, " "); for(i = 0; t_args[i] != NULL; i++) { /* -session for backwards compatibility */ if (strcmp(t_args[i], "-session") == 0 || strcmp(t_args[i], "session=yes") == 0) module->pam_session = TRUE; else if (strcmp(t_args[i], "setcred=yes") == 0) module->pam_setcred = TRUE; else if (str_begins(t_args[i], "cache_key=")) { module->module.default_cache_key = auth_cache_parse_key(pool, t_args[i] + 10); } else if (strcmp(t_args[i], "blocking=yes") == 0) { /* ignore, for backwards compatibility */ } else if (strcmp(t_args[i], "failure_show_msg=yes") == 0) { module->failure_show_msg = TRUE; } else if (strcmp(t_args[i], "*") == 0) { /* for backwards compatibility */ module->service_name = "%Ls"; } else if (str_begins(t_args[i], "max_requests=")) { if (str_to_uint(t_args[i] + 13, &module->requests_left) < 0) { i_error("pam: Invalid requests_left value: %s", t_args[i] + 13); } } else if (t_args[i+1] == NULL) { module->service_name = p_strdup(pool, t_args[i]); } else { i_fatal("pam: Unknown setting: %s", t_args[i]); } } return &module->module; } struct passdb_module_interface passdb_pam = { "pam", pam_preinit, NULL, NULL, pam_verify_plain, NULL, NULL }; #else struct passdb_module_interface passdb_pam = { .name = "pam" }; #endif dovecot-2.3.21.1/src/auth/db-dict.h0000644000000000000000000000414414656633576013527 00000000000000#ifndef DB_DICT_H #define DB_DICT_H #include "sql-api.h" struct auth_request; struct db_dict_value_iter; enum db_dict_value_format { DB_DICT_VALUE_FORMAT_VALUE = 0, DB_DICT_VALUE_FORMAT_JSON }; struct db_dict_key { const char *name; const char *key; const char *format; const char *default_value; enum db_dict_value_format parsed_format; }; ARRAY_DEFINE_TYPE(db_dict_key, struct db_dict_key); ARRAY_DEFINE_TYPE(db_dict_key_p, const struct db_dict_key *); struct db_dict_field { const char *name; const char *value; }; ARRAY_DEFINE_TYPE(db_dict_field, struct db_dict_field); struct db_dict_settings { const char *uri; const char *default_pass_scheme; const char *iterate_prefix; bool iterate_disable; ARRAY_TYPE(db_dict_key) keys; const char *passdb_objects; const char *userdb_objects; ARRAY_TYPE(db_dict_field) passdb_fields; ARRAY_TYPE(db_dict_field) userdb_fields; ARRAY_TYPE(db_dict_key_p) parsed_passdb_objects; ARRAY_TYPE(db_dict_key_p) parsed_userdb_objects; }; struct dict_connection { struct dict_connection *next; pool_t pool; int refcount; char *config_path; struct db_dict_settings set; struct dict *dict; }; struct dict_connection *db_dict_init(const char *config_path); void db_dict_unref(struct dict_connection **conn); /* Returns 1 if ok, 0 if a key without default_value wasn't returned ("user doesn't exist"), -1 if internal error */ int db_dict_value_iter_init(struct dict_connection *conn, struct auth_request *auth_request, const ARRAY_TYPE(db_dict_field) *fields, const ARRAY_TYPE(db_dict_key_p) *objects, struct db_dict_value_iter **iter_r); bool db_dict_value_iter_next(struct db_dict_value_iter *iter, const char **key_r, const char **value_r); int db_dict_value_iter_deinit(struct db_dict_value_iter **iter, const char **error_r); const char *db_dict_parse_cache_key(const ARRAY_TYPE(db_dict_key) *keys, const ARRAY_TYPE(db_dict_field) *fields, const ARRAY_TYPE(db_dict_key_p) *objects); /* private: */ const struct db_dict_key * db_dict_set_key_find(const ARRAY_TYPE(db_dict_key) *keys, const char *name); #endif dovecot-2.3.21.1/src/auth/auth-settings.c0000644000000000000000000003537614656633576015026 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "hash-method.h" #include "settings-parser.h" #include "master-service-private.h" #include "master-service-settings.h" #include "service-settings.h" #include "auth-settings.h" #include static bool auth_settings_check(void *_set, pool_t pool, const char **error_r); static bool auth_passdb_settings_check(void *_set, pool_t pool, const char **error_r); static bool auth_userdb_settings_check(void *_set, pool_t pool, const char **error_r); /* */ static struct file_listener_settings auth_unix_listeners_array[] = { { "login/login", 0666, "", "" }, { "token-login/tokenlogin", 0666, "", "" }, { "auth-login", 0600, "$default_internal_user", "" }, { "auth-client", 0600, "$default_internal_user", "" }, { "auth-userdb", 0666, "$default_internal_user", "" }, { "auth-master", 0600, "", "" } }; static struct file_listener_settings *auth_unix_listeners[] = { &auth_unix_listeners_array[0], &auth_unix_listeners_array[1], &auth_unix_listeners_array[2], &auth_unix_listeners_array[3], &auth_unix_listeners_array[4], &auth_unix_listeners_array[5] }; static buffer_t auth_unix_listeners_buf = { { { auth_unix_listeners, sizeof(auth_unix_listeners) } } }; /* */ struct service_settings auth_service_settings = { .name = "auth", .protocol = "", .type = "", .executable = "auth", .user = "$default_internal_user", .group = "", .privileged_group = "", .extra_groups = "", .chroot = "", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 1, .client_limit = 0, .service_count = 0, .idle_kill = 0, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &auth_unix_listeners_buf, sizeof(auth_unix_listeners[0]) } }, .fifo_listeners = ARRAY_INIT, .inet_listeners = ARRAY_INIT, .process_limit_1 = TRUE }; /* */ static struct file_listener_settings auth_worker_unix_listeners_array[] = { { "auth-worker", 0600, "$default_internal_user", "" } }; static struct file_listener_settings *auth_worker_unix_listeners[] = { &auth_worker_unix_listeners_array[0] }; static buffer_t auth_worker_unix_listeners_buf = { { { auth_worker_unix_listeners, sizeof(auth_worker_unix_listeners) } } }; /* */ struct service_settings auth_worker_service_settings = { .name = "auth-worker", .protocol = "", .type = "worker", .executable = "auth -w", .user = "", .group = "", .privileged_group = "", .extra_groups = "", .chroot = "", .drop_priv_before_exec = FALSE, .process_min_avail = 0, .process_limit = 0, .client_limit = 1, .service_count = 0, .idle_kill = 0, .vsz_limit = UOFF_T_MAX, .unix_listeners = { { &auth_worker_unix_listeners_buf, sizeof(auth_worker_unix_listeners[0]) } }, .fifo_listeners = ARRAY_INIT, .inet_listeners = ARRAY_INIT }; #undef DEF #define DEF(type, name) \ SETTING_DEFINE_STRUCT_##type(#name, name, struct auth_passdb_settings) static const struct setting_define auth_passdb_setting_defines[] = { DEF(STR, name), DEF(STR, driver), DEF(STR, args), DEF(STR, default_fields), DEF(STR, override_fields), DEF(STR, mechanisms), DEF(STR, username_filter), DEF(ENUM, skip), DEF(ENUM, result_success), DEF(ENUM, result_failure), DEF(ENUM, result_internalfail), DEF(BOOL, deny), DEF(BOOL, pass), DEF(BOOL, master), DEF(ENUM, auth_verbose), SETTING_DEFINE_LIST_END }; static const struct auth_passdb_settings auth_passdb_default_settings = { .name = "", .driver = "", .args = "", .default_fields = "", .override_fields = "", .mechanisms = "", .username_filter = "", .skip = "never:authenticated:unauthenticated", .result_success = "return-ok:return:return-fail:continue:continue-ok:continue-fail", .result_failure = "continue:return:return-ok:return-fail:continue-ok:continue-fail", .result_internalfail = "continue:return:return-ok:return-fail:continue-ok:continue-fail", .deny = FALSE, .pass = FALSE, .master = FALSE, .auth_verbose = "default:yes:no" }; const struct setting_parser_info auth_passdb_setting_parser_info = { .defines = auth_passdb_setting_defines, .defaults = &auth_passdb_default_settings, .type_offset = offsetof(struct auth_passdb_settings, name), .struct_size = sizeof(struct auth_passdb_settings), .parent_offset = SIZE_MAX, .parent = &auth_setting_parser_info, .check_func = auth_passdb_settings_check }; #undef DEF #define DEF(type, name) \ SETTING_DEFINE_STRUCT_##type(#name, name, struct auth_userdb_settings) static const struct setting_define auth_userdb_setting_defines[] = { DEF(STR, name), DEF(STR, driver), DEF(STR, args), DEF(STR, default_fields), DEF(STR, override_fields), DEF(ENUM, skip), DEF(ENUM, result_success), DEF(ENUM, result_failure), DEF(ENUM, result_internalfail), DEF(ENUM, auth_verbose), SETTING_DEFINE_LIST_END }; static const struct auth_userdb_settings auth_userdb_default_settings = { /* NOTE: when adding fields, update also auth.c:userdb_dummy_set */ .name = "", .driver = "", .args = "", .default_fields = "", .override_fields = "", .skip = "never:found:notfound", .result_success = "return-ok:return:return-fail:continue:continue-ok:continue-fail", .result_failure = "continue:return:return-ok:return-fail:continue-ok:continue-fail", .result_internalfail = "continue:return:return-ok:return-fail:continue-ok:continue-fail", .auth_verbose = "default:yes:no" }; const struct setting_parser_info auth_userdb_setting_parser_info = { .defines = auth_userdb_setting_defines, .defaults = &auth_userdb_default_settings, .type_offset = offsetof(struct auth_userdb_settings, name), .struct_size = sizeof(struct auth_userdb_settings), .parent_offset = SIZE_MAX, .parent = &auth_setting_parser_info, .check_func = auth_userdb_settings_check }; /* we're kind of kludging here to avoid "auth_" prefix in the struct fields */ #undef DEF #undef DEF_NOPREFIX #undef DEFLIST #define DEF(type, name) \ SETTING_DEFINE_STRUCT_##type("auth_"#name, name, struct auth_settings) #define DEF_NOPREFIX(type, name) \ SETTING_DEFINE_STRUCT_##type(#name, name, struct auth_settings) #define DEFLIST(field, name, defines) \ { .type = SET_DEFLIST, .key = name, \ .offset = offsetof(struct auth_settings, field), \ .list_info = defines } static const struct setting_define auth_setting_defines[] = { DEF(STR, mechanisms), DEF(STR, realms), DEF(STR, default_realm), DEF(SIZE, cache_size), DEF(TIME, cache_ttl), DEF(TIME, cache_negative_ttl), DEF(BOOL, cache_verify_password_with_worker), DEF(STR, username_chars), DEF(STR, username_translation), DEF(STR, username_format), DEF(STR, master_user_separator), DEF(STR, anonymous_username), DEF(STR, krb5_keytab), DEF(STR, gssapi_hostname), DEF(STR, winbind_helper_path), DEF(STR, proxy_self), DEF(TIME, failure_delay), DEF(STR, policy_server_url), DEF(STR, policy_server_api_header), DEF(UINT, policy_server_timeout_msecs), DEF(STR, policy_hash_mech), DEF(STR, policy_hash_nonce), DEF(STR, policy_request_attributes), DEF(BOOL, policy_reject_on_fail), DEF(BOOL, policy_check_before_auth), DEF(BOOL, policy_check_after_auth), DEF(BOOL, policy_report_after_auth), DEF(BOOL, policy_log_only), DEF(UINT, policy_hash_truncate), DEF(BOOL, stats), DEF(BOOL, verbose), DEF(BOOL, debug), DEF(BOOL, debug_passwords), DEF(STR, verbose_passwords), DEF(BOOL, ssl_require_client_cert), DEF(BOOL, ssl_username_from_cert), DEF(BOOL, use_winbind), DEF(UINT, worker_max_count), DEFLIST(passdbs, "passdb", &auth_passdb_setting_parser_info), DEFLIST(userdbs, "userdb", &auth_userdb_setting_parser_info), DEF_NOPREFIX(STR, base_dir), DEF_NOPREFIX(BOOL, verbose_proctitle), DEF_NOPREFIX(UINT, first_valid_uid), DEF_NOPREFIX(UINT, last_valid_uid), DEF_NOPREFIX(UINT, first_valid_gid), DEF_NOPREFIX(UINT, last_valid_gid), DEF_NOPREFIX(STR, ssl_client_ca_dir), DEF_NOPREFIX(STR, ssl_client_ca_file), SETTING_DEFINE_LIST_END }; static const struct auth_settings auth_default_settings = { .mechanisms = "plain", .realms = "", .default_realm = "", .cache_size = 0, .cache_ttl = 60*60, .cache_negative_ttl = 60*60, .cache_verify_password_with_worker = FALSE, .username_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@", .username_translation = "", .username_format = "%Lu", .master_user_separator = "", .anonymous_username = "anonymous", .krb5_keytab = "", .gssapi_hostname = "", .winbind_helper_path = "/usr/bin/ntlm_auth", .proxy_self = "", .failure_delay = 2, .policy_server_url = "", .policy_server_api_header = "", .policy_server_timeout_msecs = 2000, .policy_hash_mech = "sha256", .policy_hash_nonce = "", .policy_request_attributes = "login=%{requested_username} pwhash=%{hashed_password} remote=%{rip} device_id=%{client_id} protocol=%s session_id=%{session}", .policy_reject_on_fail = FALSE, .policy_check_before_auth = TRUE, .policy_check_after_auth = TRUE, .policy_report_after_auth = TRUE, .policy_log_only = FALSE, .policy_hash_truncate = 12, .stats = FALSE, .verbose = FALSE, .debug = FALSE, .debug_passwords = FALSE, .verbose_passwords = "no", .ssl_require_client_cert = FALSE, .ssl_username_from_cert = FALSE, .ssl_client_ca_dir = "", .ssl_client_ca_file = "", .use_winbind = FALSE, .worker_max_count = 30, .passdbs = ARRAY_INIT, .userdbs = ARRAY_INIT, .base_dir = PKG_RUNDIR, .verbose_proctitle = FALSE, .first_valid_uid = 500, .last_valid_uid = 0, .first_valid_gid = 1, .last_valid_gid = 0, }; const struct setting_parser_info auth_setting_parser_info = { .module_name = "auth", .defines = auth_setting_defines, .defaults = &auth_default_settings, .type_offset = SIZE_MAX, .struct_size = sizeof(struct auth_settings), .parent_offset = SIZE_MAX, .check_func = auth_settings_check }; /* */ static bool auth_settings_set_self_ips(struct auth_settings *set, pool_t pool, const char **error_r) { const char *const *tmp; ARRAY(struct ip_addr) ips_array; struct ip_addr *ips; unsigned int ips_count; int ret; if (*set->proxy_self == '\0') { set->proxy_self_ips = p_new(pool, struct ip_addr, 1); return TRUE; } p_array_init(&ips_array, pool, 4); tmp = t_strsplit_spaces(set->proxy_self, " "); for (; *tmp != NULL; tmp++) { ret = net_gethostbyname(*tmp, &ips, &ips_count); if (ret != 0) { *error_r = t_strdup_printf("auth_proxy_self_ips: " "gethostbyname(%s) failed: %s", *tmp, net_gethosterror(ret)); } array_append(&ips_array, ips, ips_count); } array_append_zero(&ips_array); set->proxy_self_ips = array_front(&ips_array); return TRUE; } static bool auth_verify_verbose_password(struct auth_settings *set, const char **error_r) { const char *p, *value = set->verbose_passwords; unsigned int num; p = strchr(value, ':'); if (p != NULL) { if (str_to_uint(p+1, &num) < 0 || num == 0) { *error_r = t_strdup_printf("auth_verbose_passwords: " "Invalid truncation number: '%s'", p+1); return FALSE; } value = t_strdup_until(value, p); } if (strcmp(value, "no") == 0) return TRUE; else if (strcmp(value, "plain") == 0) return TRUE; else if (strcmp(value, "sha1") == 0) return TRUE; else if (strcmp(value, "yes") == 0) { /* just use it as alias for "plain" */ set->verbose_passwords = "plain"; return TRUE; } else { *error_r = "auth_verbose_passwords: Invalid value"; return FALSE; } } static bool auth_settings_check(void *_set, pool_t pool, const char **error_r) { struct auth_settings *set = _set; const char *p; if (set->debug_passwords) set->debug = TRUE; if (set->debug) set->verbose = TRUE; if (set->worker_max_count == 0) { *error_r = "auth_worker_max_count must be above zero"; return FALSE; } if (set->cache_size > 0 && set->cache_size < 1024) { /* probably a configuration error. older versions used megabyte numbers */ *error_r = t_strdup_printf("auth_cache_size value is too small " "(%"PRIuUOFF_T" bytes)", set->cache_size); return FALSE; } if (!auth_verify_verbose_password(set, error_r)) return FALSE; if (*set->username_chars == '\0') { /* all chars are allowed */ memset(set->username_chars_map, 1, sizeof(set->username_chars_map)); } else { for (p = set->username_chars; *p != '\0'; p++) set->username_chars_map[(int)(uint8_t)*p] = 1; } if (*set->username_translation != '\0') { p = set->username_translation; for (; *p != '\0' && p[1] != '\0'; p += 2) set->username_translation_map[(int)(uint8_t)*p] = p[1]; } set->realms_arr = (const char *const *)p_strsplit_spaces(pool, set->realms, " "); if (*set->policy_server_url != '\0') { if (*set->policy_hash_nonce == '\0') { *error_r = "auth_policy_hash_nonce must be set when policy server is used"; return FALSE; } const struct hash_method *digest = hash_method_lookup(set->policy_hash_mech); if (digest == NULL) { *error_r = "invalid auth_policy_hash_mech given"; return FALSE; } if (set->policy_hash_truncate > 0 && set->policy_hash_truncate >= digest->digest_size*8) { *error_r = t_strdup_printf("policy_hash_truncate is not smaller than digest size (%u >= %u)", set->policy_hash_truncate, digest->digest_size*8); return FALSE; } } if (!auth_settings_set_self_ips(set, pool, error_r)) return FALSE; return TRUE; } static bool auth_passdb_settings_check(void *_set, pool_t pool ATTR_UNUSED, const char **error_r) { struct auth_passdb_settings *set = _set; if (set->driver == NULL || *set->driver == '\0') { *error_r = "passdb is missing driver"; return FALSE; } if (set->pass && strcmp(set->result_success, "return-ok") != 0) { *error_r = "Obsolete pass=yes setting mixed with non-default result_success"; return FALSE; } return TRUE; } static bool auth_userdb_settings_check(void *_set, pool_t pool ATTR_UNUSED, const char **error_r) { struct auth_userdb_settings *set = _set; if (set->driver == NULL || *set->driver == '\0') { *error_r = "userdb is missing driver"; return FALSE; } return TRUE; } /* */ struct auth_settings *global_auth_settings; struct auth_settings * auth_settings_read(const char *service, pool_t pool, struct master_service_settings_output *output_r) { static const struct setting_parser_info *set_roots[] = { &auth_setting_parser_info, NULL }; struct master_service_settings_input input; struct setting_parser_context *set_parser; const char *error; void **sets; i_zero(&input); input.roots = set_roots; input.module = "auth"; input.service = service; if (master_service_settings_read(master_service, &input, output_r, &error) < 0) i_fatal("Error reading configuration: %s", error); pool_ref(pool); set_parser = settings_parser_dup(master_service->set_parser, pool); if (!settings_parser_check(set_parser, pool, &error)) i_unreached(); sets = master_service_settings_parser_get_others(master_service, set_parser); settings_parser_deinit(&set_parser); return sets[0]; } dovecot-2.3.21.1/src/auth/auth-request-handler.c0000644000000000000000000007230714656633576016264 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "ioloop.h" #include "array.h" #include "aqueue.h" #include "base64.h" #include "hash.h" #include "net.h" #include "str.h" #include "strescape.h" #include "str-sanitize.h" #include "master-interface.h" #include "auth-penalty.h" #include "auth-request.h" #include "auth-token.h" #include "auth-client-connection.h" #include "auth-master-connection.h" #include "auth-request-handler.h" #include "auth-request-handler-private.h" #include "auth-policy.h" #define AUTH_FAILURE_DELAY_CHECK_MSECS 500 static ARRAY(struct auth_request *) auth_failures_arr; static struct aqueue *auth_failures; static struct timeout *to_auth_failures; static void auth_failure_timeout(void *context) ATTR_NULL(1); static void auth_request_handler_default_reply_callback(struct auth_request *request, enum auth_client_result result, const void *auth_reply, size_t reply_size); static void auth_request_handler_default_reply_continue(struct auth_request *request, const void *reply, size_t reply_size); struct auth_request_handler * auth_request_handler_create(bool token_auth, auth_client_request_callback_t *callback, struct auth_client_connection *conn, auth_master_request_callback_t *master_callback) { struct auth_request_handler *handler; pool_t pool; pool = pool_alloconly_create("auth request handler", 4096); handler = p_new(pool, struct auth_request_handler, 1); handler->refcount = 1; handler->pool = pool; hash_table_create_direct(&handler->requests, pool, 0); handler->callback = callback; handler->conn = conn; handler->master_callback = master_callback; handler->token_auth = token_auth; handler->reply_callback = auth_request_handler_default_reply_callback; handler->reply_continue_callback = auth_request_handler_default_reply_continue; handler->verify_plain_continue_callback = auth_request_default_verify_plain_continue; return handler; } unsigned int auth_request_handler_get_request_count(struct auth_request_handler *handler) { return hash_table_count(handler->requests); } void auth_request_handler_abort_requests(struct auth_request_handler *handler) { struct hash_iterate_context *iter; void *key; struct auth_request *auth_request; iter = hash_table_iterate_init(handler->requests); while (hash_table_iterate(iter, handler->requests, &key, &auth_request)) { switch (auth_request->state) { case AUTH_REQUEST_STATE_NEW: case AUTH_REQUEST_STATE_MECH_CONTINUE: case AUTH_REQUEST_STATE_FINISHED: auth_request->removed_from_handler = TRUE; auth_request_unref(&auth_request); hash_table_remove(handler->requests, key); break; case AUTH_REQUEST_STATE_PASSDB: case AUTH_REQUEST_STATE_USERDB: /* can't abort a pending passdb/userdb lookup */ break; case AUTH_REQUEST_STATE_MAX: i_unreached(); } } hash_table_iterate_deinit(&iter); } void auth_request_handler_unref(struct auth_request_handler **_handler) { struct auth_request_handler *handler = *_handler; *_handler = NULL; i_assert(handler->refcount > 0); if (--handler->refcount > 0) return; i_assert(hash_table_count(handler->requests) == 0); /* notify parent that we're done with all requests */ handler->callback(NULL, handler->conn); hash_table_destroy(&handler->requests); pool_unref(&handler->pool); } void auth_request_handler_destroy(struct auth_request_handler **_handler) { struct auth_request_handler *handler = *_handler; *_handler = NULL; i_assert(!handler->destroyed); handler->destroyed = TRUE; auth_request_handler_unref(&handler); } void auth_request_handler_set(struct auth_request_handler *handler, unsigned int connect_uid, unsigned int client_pid) { handler->connect_uid = connect_uid; handler->client_pid = client_pid; } static void auth_request_handler_remove(struct auth_request_handler *handler, struct auth_request *request) { i_assert(request->handler == handler); if (request->removed_from_handler) { /* already removed it */ return; } request->removed_from_handler = TRUE; /* if db lookup is stuck, this call doesn't actually free the auth request, so make sure we don't get back here. */ timeout_remove(&request->to_abort); hash_table_remove(handler->requests, POINTER_CAST(request->id)); auth_request_unref(&request); } static void auth_str_add_keyvalue(string_t *dest, const char *key, const char *value) { str_append_c(dest, '\t'); str_append(dest, key); str_append_c(dest, '='); str_append_tabescaped(dest, value); } static void auth_str_append_extra_fields(struct auth_request *request, string_t *dest) { const struct auth_request_fields *fields = &request->fields; if (!auth_fields_is_empty(fields->extra_fields)) { str_append_c(dest, '\t'); auth_fields_append(fields->extra_fields, dest, AUTH_FIELD_FLAG_HIDDEN, 0); } if (fields->original_username != NULL && null_strcmp(fields->original_username, fields->user) != 0 && !auth_fields_exists(fields->extra_fields, "original_user")) { auth_str_add_keyvalue(dest, "original_user", fields->original_username); } if (fields->master_user != NULL && !auth_fields_exists(fields->extra_fields, "auth_user")) auth_str_add_keyvalue(dest, "auth_user", fields->master_user); if (*request->set->anonymous_username != '\0' && null_strcmp(fields->user, request->set->anonymous_username) == 0) { /* this is an anonymous login, either via ANONYMOUS SASL mechanism or simply logging in as the anonymous user via another mechanism */ str_append(dest, "\tanonymous"); } if (!request->auth_only && auth_fields_exists(fields->extra_fields, "proxy")) { /* we're proxying */ if (!auth_fields_exists(fields->extra_fields, "pass") && request->mech_password != NULL) { /* send back the password that was sent by user (not the password in passdb). */ auth_str_add_keyvalue(dest, "pass", request->mech_password); } if (fields->master_user != NULL && !auth_fields_exists(fields->extra_fields, "master") && *fields->master_user != '\0') { /* the master username needs to be forwarded */ auth_str_add_keyvalue(dest, "master", fields->master_user); } } } static void auth_request_handle_failure(struct auth_request *request, const char *reply) { struct auth_request_handler *handler = request->handler; /* handle failure here */ auth_request_log_finished(request); if (request->in_delayed_failure_queue) { /* we came here from flush_failures() */ handler->callback(reply, handler->conn); return; } /* remove the request from requests-list */ auth_request_ref(request); auth_request_handler_remove(handler, request); if (request->set->policy_report_after_auth) auth_policy_report(request); if (auth_fields_exists(request->fields.extra_fields, "nodelay")) { /* passdb specifically requested not to delay the reply. */ handler->callback(reply, handler->conn); auth_request_unref(&request); return; } /* failure. don't announce it immediately to avoid a) timing attacks, b) flooding */ request->in_delayed_failure_queue = TRUE; handler->refcount++; if (auth_penalty != NULL) { auth_penalty_update(auth_penalty, request, request->last_penalty + 1); } auth_request_refresh_last_access(request); aqueue_append(auth_failures, &request); if (to_auth_failures == NULL) { to_auth_failures = timeout_add_short(AUTH_FAILURE_DELAY_CHECK_MSECS, auth_failure_timeout, NULL); } } static void auth_request_handler_reply_success_finish(struct auth_request *request) { struct auth_request_handler *handler = request->handler; string_t *str = t_str_new(128); auth_request_log_finished(request); if (request->last_penalty != 0 && auth_penalty != NULL) { /* reset penalty */ auth_penalty_update(auth_penalty, request, 0); } /* sanitize these fields, since the login code currently assumes they are exactly in this format. */ auth_fields_booleanize(request->fields.extra_fields, "nologin"); auth_fields_booleanize(request->fields.extra_fields, "proxy"); str_printfa(str, "OK\t%u\tuser=", request->id); str_append_tabescaped(str, request->fields.user); auth_str_append_extra_fields(request, str); if (request->set->policy_report_after_auth) auth_policy_report(request); if (handler->master_callback == NULL || auth_fields_exists(request->fields.extra_fields, "nologin") || auth_fields_exists(request->fields.extra_fields, "proxy")) { /* this request doesn't have to wait for master process to pick it up. delete it */ auth_request_handler_remove(handler, request); } handler->callback(str_c(str), handler->conn); } static void auth_request_handler_reply_failure_finish(struct auth_request *request) { const char *code = NULL; string_t *str = t_str_new(128); auth_fields_remove(request->fields.extra_fields, "nologin"); str_printfa(str, "FAIL\t%u", request->id); if (request->fields.user != NULL) auth_str_add_keyvalue(str, "user", request->fields.user); else if (request->fields.original_username != NULL) { auth_str_add_keyvalue(str, "user", request->fields.original_username); } if (request->internal_failure) { code = AUTH_CLIENT_FAIL_CODE_TEMPFAIL; } else if (request->fields.master_user != NULL) { /* authentication succeeded, but we can't log in as the wanted user */ code = AUTH_CLIENT_FAIL_CODE_AUTHZFAILED; } else { switch (request->passdb_result) { case PASSDB_RESULT_NEXT: case PASSDB_RESULT_INTERNAL_FAILURE: case PASSDB_RESULT_SCHEME_NOT_AVAILABLE: case PASSDB_RESULT_USER_UNKNOWN: case PASSDB_RESULT_PASSWORD_MISMATCH: case PASSDB_RESULT_OK: break; case PASSDB_RESULT_USER_DISABLED: code = AUTH_CLIENT_FAIL_CODE_USER_DISABLED; break; case PASSDB_RESULT_PASS_EXPIRED: code = AUTH_CLIENT_FAIL_CODE_PASS_EXPIRED; break; } } if (auth_fields_exists(request->fields.extra_fields, "nodelay")) { /* this is normally a hidden field, need to add it explicitly */ str_append(str, "\tnodelay"); } if (code != NULL) { str_append(str, "\tcode="); str_append(str, code); } auth_str_append_extra_fields(request, str); auth_request_handle_failure(request, str_c(str)); } static void auth_request_handler_proxy_callback(bool success, struct auth_request *request) { struct auth_request_handler *handler = request->handler; if (success) auth_request_handler_reply_success_finish(request); else auth_request_handler_reply_failure_finish(request); auth_request_handler_unref(&handler); } void auth_request_handler_reply(struct auth_request *request, enum auth_client_result result, const void *auth_reply, size_t reply_size) { struct auth_request_handler *handler = request->handler; request->handler_pending_reply = FALSE; handler->reply_callback(request, result, auth_reply, reply_size); } static void auth_request_handler_default_reply_callback(struct auth_request *request, enum auth_client_result result, const void *auth_reply, size_t reply_size) { struct auth_request_handler *handler = request->handler; string_t *str; int ret; if (handler->destroyed) { /* the client connection was already closed. we can't do anything but abort this request */ request->internal_failure = TRUE; result = AUTH_CLIENT_RESULT_FAILURE; /* make sure this request is set to finished state (it's not with result=continue) */ auth_request_set_state(request, AUTH_REQUEST_STATE_FINISHED); } switch (result) { case AUTH_CLIENT_RESULT_CONTINUE: str = t_str_new(16 + MAX_BASE64_ENCODED_SIZE(reply_size)); str_printfa(str, "CONT\t%u\t", request->id); base64_encode(auth_reply, reply_size, str); request->accept_cont_input = TRUE; handler->callback(str_c(str), handler->conn); break; case AUTH_CLIENT_RESULT_SUCCESS: if (reply_size > 0) { str = t_str_new(MAX_BASE64_ENCODED_SIZE(reply_size)); base64_encode(auth_reply, reply_size, str); auth_fields_add(request->fields.extra_fields, "resp", str_c(str), 0); } ret = auth_request_proxy_finish(request, auth_request_handler_proxy_callback); if (ret < 0) auth_request_handler_reply_failure_finish(request); else if (ret > 0) auth_request_handler_reply_success_finish(request); else return; break; case AUTH_CLIENT_RESULT_FAILURE: auth_request_proxy_finish_failure(request); if (reply_size > 0) { str = t_str_new(MAX_BASE64_ENCODED_SIZE(reply_size)); base64_encode(auth_reply, reply_size, str); auth_fields_add(request->fields.extra_fields, "resp", str_c(str), 0); } auth_request_handler_reply_failure_finish(request); break; } /* NOTE: request may be destroyed now */ auth_request_handler_unref(&handler); } void auth_request_handler_reply_continue(struct auth_request *request, const void *reply, size_t reply_size) { request->handler->reply_continue_callback(request, reply, reply_size); } static void auth_request_handler_default_reply_continue(struct auth_request *request, const void *reply, size_t reply_size) { auth_request_handler_reply(request, AUTH_CLIENT_RESULT_CONTINUE, reply, reply_size); } void auth_request_handler_abort(struct auth_request *request) { i_assert(request->handler_pending_reply); /* request destroyed while waiting for auth_request_penalty_finish() to be called. */ auth_request_handler_unref(&request->handler); } static void auth_request_handler_auth_fail_code(struct auth_request_handler *handler, struct auth_request *request, const char *fail_code, const char *reason) { string_t *str = t_str_new(128); e_info(request->mech_event, "%s", reason); str_printfa(str, "FAIL\t%u", request->id); if (*fail_code != '\0') { str_append(str, "\tcode="); str_append(str, fail_code); } str_append(str, "\treason="); str_append_tabescaped(str, reason); handler->callback(str_c(str), handler->conn); auth_request_handler_remove(handler, request); } static void auth_request_handler_auth_fail (struct auth_request_handler *handler, struct auth_request *request, const char *reason) { auth_request_handler_auth_fail_code(handler, request, "", reason); } static void auth_request_timeout(struct auth_request *request) { unsigned int secs = (unsigned int)(time(NULL) - request->last_access); if (request->state != AUTH_REQUEST_STATE_MECH_CONTINUE) { /* client's fault */ e_error(request->mech_event, "Request %u.%u timed out after %u secs, state=%d", request->handler->client_pid, request->id, secs, request->state); } else if (request->set->verbose) { e_info(request->mech_event, "Request timed out waiting for client to continue authentication " "(%u secs)", secs); } auth_request_handler_remove(request->handler, request); } static void auth_request_penalty_finish(struct auth_request *request) { timeout_remove(&request->to_penalty); auth_request_initial(request); } static void auth_penalty_callback(unsigned int penalty, struct auth_request *request) { unsigned int secs; request->last_penalty = penalty; if (penalty == 0) auth_request_initial(request); else { secs = auth_penalty_to_secs(penalty); request->to_penalty = timeout_add(secs * 1000, auth_request_penalty_finish, request); } } bool auth_request_handler_auth_begin(struct auth_request_handler *handler, const char *args) { const struct mech_module *mech; struct auth_request *request; const char *const *list, *name, *arg, *initial_resp; void *initial_resp_data; unsigned int id; buffer_t *buf; i_assert(!handler->destroyed); /* [...] */ list = t_strsplit_tabescaped(args); if (list[0] == NULL || list[1] == NULL || str_to_uint(list[0], &id) < 0 || id == 0) { e_error(handler->conn->event, "BUG: Authentication client %u " "sent broken AUTH request", handler->client_pid); return FALSE; } if (handler->token_auth) { mech = &mech_dovecot_token; if (strcmp(list[1], mech->mech_name) != 0) { /* unsupported mechanism */ e_error(handler->conn->event, "BUG: Authentication client %u requested invalid " "authentication mechanism %s (DOVECOT-TOKEN required)", handler->client_pid, str_sanitize(list[1], MAX_MECH_NAME_LEN)); return FALSE; } } else { struct auth *auth_default = auth_default_service(); mech = mech_register_find(auth_default->reg, list[1]); if (mech == NULL) { /* unsupported mechanism */ e_error(handler->conn->event, "BUG: Authentication client %u requested unsupported " "authentication mechanism %s", handler->client_pid, str_sanitize(list[1], MAX_MECH_NAME_LEN)); return FALSE; } } request = auth_request_new(mech, handler->conn->event); request->handler = handler; request->connect_uid = handler->connect_uid; request->client_pid = handler->client_pid; request->id = id; request->auth_only = handler->master_callback == NULL; /* parse optional parameters */ initial_resp = NULL; for (list += 2; *list != NULL; list++) { arg = strchr(*list, '='); if (arg == NULL) { name = *list; arg = ""; } else { name = t_strdup_until(*list, arg); arg++; } if (auth_request_import_auth(request, name, arg)) ; else if (strcmp(name, "resp") == 0) { initial_resp = arg; /* this must be the last parameter */ list++; break; } } if (*list != NULL) { e_error(handler->conn->event, "BUG: Authentication client %u " "sent AUTH parameters after 'resp'", handler->client_pid); auth_request_unref(&request); return FALSE; } if (request->fields.service == NULL) { e_error(handler->conn->event, "BUG: Authentication client %u " "didn't specify service in request", handler->client_pid); auth_request_unref(&request); return FALSE; } if (hash_table_lookup(handler->requests, POINTER_CAST(id)) != NULL) { e_error(handler->conn->event, "BUG: Authentication client %u " "sent a duplicate ID %u", handler->client_pid, id); auth_request_unref(&request); return FALSE; } auth_request_init(request); request->to_abort = timeout_add(MASTER_AUTH_SERVER_TIMEOUT_SECS * 1000, auth_request_timeout, request); hash_table_insert(handler->requests, POINTER_CAST(id), request); if (request->set->ssl_require_client_cert && !request->fields.valid_client_cert) { /* we fail without valid certificate */ auth_request_handler_auth_fail(handler, request, "Client didn't present valid SSL certificate"); return TRUE; } if (request->set->ssl_require_client_cert && request->set->ssl_username_from_cert && !request->fields.cert_username) { auth_request_handler_auth_fail(handler, request, "SSL certificate didn't contain username"); return TRUE; } /* Handle initial respose */ if (initial_resp == NULL) { /* No initial response */ request->initial_response = NULL; request->initial_response_len = 0; } else if (handler->conn->version_minor < 2 && *initial_resp == '\0') { /* Some authentication clients like Exim send and empty initial response field when it is in fact absent in the authentication command. This was allowed for older versions of the Dovecot authentication protocol. */ request->initial_response = NULL; request->initial_response_len = 0; } else if (*initial_resp == '\0' || strcmp(initial_resp, "=") == 0 ) { /* Empty initial response - Protocols that use SASL often use '=' to indicate an empty initial response; i.e., to distinguish it from an absent initial response. However, that should not be conveyed to the SASL layer (it is not even valid Base64); only the empty string should be passed on. Still, we recognize it here anyway, because we used to make the same mistake. */ request->initial_response = uchar_empty_ptr; request->initial_response_len = 0; } else { size_t len = strlen(initial_resp); /* Initial response encoded in Bas64 */ buf = t_buffer_create(MAX_BASE64_DECODED_SIZE(len)); if (base64_decode(initial_resp, len, NULL, buf) < 0) { auth_request_handler_auth_fail_code(handler, request, AUTH_CLIENT_FAIL_CODE_INVALID_BASE64, "Invalid base64 data in initial response"); return TRUE; } initial_resp_data = p_malloc(request->pool, I_MAX(buf->used, 1)); memcpy(initial_resp_data, buf->data, buf->used); request->initial_response = initial_resp_data; request->initial_response_len = buf->used; } /* handler is referenced until auth_request_handler_reply() is called. */ handler->refcount++; request->handler_pending_reply = TRUE; /* before we start authenticating, see if we need to wait first */ auth_penalty_lookup(auth_penalty, request, auth_penalty_callback); return TRUE; } bool auth_request_handler_auth_continue(struct auth_request_handler *handler, const char *args) { struct auth_request *request; const char *data; size_t data_len; buffer_t *buf; unsigned int id; data = strchr(args, '\t'); if (data == NULL || str_to_uint(t_strdup_until(args, data), &id) < 0) { e_error(handler->conn->event, "BUG: Authentication client sent broken CONT request"); return FALSE; } data++; request = hash_table_lookup(handler->requests, POINTER_CAST(id)); if (request == NULL) { const char *reply = t_strdup_printf( "FAIL\t%u\treason=Authentication request timed out", id); handler->callback(reply, handler->conn); return TRUE; } /* accept input only once after mechanism has sent a CONT reply */ if (!request->accept_cont_input) { auth_request_handler_auth_fail(handler, request, "Unexpected continuation"); return TRUE; } request->accept_cont_input = FALSE; data_len = strlen(data); buf = t_buffer_create(MAX_BASE64_DECODED_SIZE(data_len)); if (base64_decode(data, data_len, NULL, buf) < 0) { auth_request_handler_auth_fail_code(handler, request, AUTH_CLIENT_FAIL_CODE_INVALID_BASE64, "Invalid base64 data in continued response"); return TRUE; } /* handler is referenced until auth_request_handler_reply() is called. */ handler->refcount++; auth_request_continue(request, buf->data, buf->used); return TRUE; } static void auth_str_append_userdb_extra_fields(struct auth_request *request, string_t *dest) { str_append_c(dest, '\t'); auth_fields_append(request->fields.userdb_reply, dest, AUTH_FIELD_FLAG_HIDDEN, 0); if (request->fields.master_user != NULL && !auth_fields_exists(request->fields.userdb_reply, "master_user")) { auth_str_add_keyvalue(dest, "master_user", request->fields.master_user); } auth_str_add_keyvalue(dest, "auth_mech", request->mech->mech_name); if (*request->set->anonymous_username != '\0' && strcmp(request->fields.user, request->set->anonymous_username) == 0) { /* this is an anonymous login, either via ANONYMOUS SASL mechanism or simply logging in as the anonymous user via another mechanism */ str_append(dest, "\tanonymous"); } /* generate auth_token when master service provided session_pid */ if (request->request_auth_token && request->session_pid != (pid_t)-1) { const char *auth_token = auth_token_get(request->fields.service, dec2str(request->session_pid), request->fields.user, request->fields.session_id); auth_str_add_keyvalue(dest, "auth_token", auth_token); } if (request->fields.master_user != NULL) { auth_str_add_keyvalue(dest, "auth_user", request->fields.master_user); } else if (request->fields.original_username != NULL && strcmp(request->fields.original_username, request->fields.user) != 0) { auth_str_add_keyvalue(dest, "auth_user", request->fields.original_username); } } static void userdb_callback(enum userdb_result result, struct auth_request *request) { struct auth_request_handler *handler = request->handler; string_t *str; const char *value; i_assert(request->state == AUTH_REQUEST_STATE_USERDB); auth_request_set_state(request, AUTH_REQUEST_STATE_FINISHED); if (request->userdb_lookup_tempfailed) result = USERDB_RESULT_INTERNAL_FAILURE; str = t_str_new(128); switch (result) { case USERDB_RESULT_INTERNAL_FAILURE: str_printfa(str, "FAIL\t%u", request->id); if (request->userdb_lookup_tempfailed) { value = auth_fields_find(request->fields.userdb_reply, "reason"); if (value != NULL) auth_str_add_keyvalue(str, "reason", value); } break; case USERDB_RESULT_USER_UNKNOWN: str_printfa(str, "NOTFOUND\t%u", request->id); break; case USERDB_RESULT_OK: str_printfa(str, "USER\t%u\t", request->id); str_append_tabescaped(str, request->fields.user); auth_str_append_userdb_extra_fields(request, str); break; } handler->master_callback(str_c(str), request->master); auth_master_connection_unref(&request->master); auth_request_unref(&request); auth_request_handler_unref(&handler); } static bool auth_master_request_failed(struct auth_request_handler *handler, struct auth_master_connection *master, unsigned int id) { if (handler->master_callback == NULL) return FALSE; handler->master_callback(t_strdup_printf("FAIL\t%u", id), master); return TRUE; } bool auth_request_handler_master_request(struct auth_request_handler *handler, struct auth_master_connection *master, unsigned int id, unsigned int client_id, const char *const *params) { struct auth_request *request; struct net_unix_cred cred; request = hash_table_lookup(handler->requests, POINTER_CAST(client_id)); if (request == NULL) { e_error(master->event, "Master request %u.%u not found", handler->client_pid, client_id); return auth_master_request_failed(handler, master, id); } auth_request_ref(request); auth_request_handler_remove(handler, request); for (; *params != NULL; params++) { const char *name, *param = strchr(*params, '='); if (param == NULL) { name = *params; param = ""; } else { name = t_strdup_until(*params, param); param++; } (void)auth_request_import_master(request, name, param); } /* verify session pid if specified and possible */ if (request->session_pid != (pid_t)-1 && net_getunixcred(master->fd, &cred) == 0 && cred.pid != (pid_t)-1 && request->session_pid != cred.pid) { e_error(master->event, "Session pid %ld provided by master for request %u.%u " "did not match peer credentials (pid=%ld, uid=%ld)", (long)request->session_pid, handler->client_pid, client_id, (long)cred.pid, (long)cred.uid); return auth_master_request_failed(handler, master, id); } if (request->state != AUTH_REQUEST_STATE_FINISHED || !request->fields.successful) { e_error(master->event, "Master requested unfinished authentication request " "%u.%u", handler->client_pid, client_id); handler->master_callback(t_strdup_printf("FAIL\t%u", id), master); auth_request_unref(&request); } else { /* the request isn't being referenced anywhere anymore, so we can do a bit of kludging.. replace the request's old client_id with master's id. */ auth_request_set_state(request, AUTH_REQUEST_STATE_USERDB); request->id = id; request->master = master; /* master and handler are referenced until userdb_callback i s called. */ auth_master_connection_ref(master); handler->refcount++; auth_request_lookup_user(request, userdb_callback); } return TRUE; } void auth_request_handler_cancel_request(struct auth_request_handler *handler, unsigned int client_id) { struct auth_request *request; request = hash_table_lookup(handler->requests, POINTER_CAST(client_id)); if (request != NULL) auth_request_handler_remove(handler, request); } void auth_request_handler_flush_failures(bool flush_all) { struct auth_request **auth_requests, *auth_request; unsigned int i, j, count; time_t diff; count = aqueue_count(auth_failures); if (count == 0) { timeout_remove(&to_auth_failures); return; } auth_requests = array_front_modifiable(&auth_failures_arr); /* count the number of requests that we need to flush */ for (i = 0; i < count; i++) { auth_request = auth_requests[aqueue_idx(auth_failures, i)]; /* FIXME: assumes that failure_delay is always the same. */ diff = ioloop_time - auth_request->last_access; if (diff < (time_t)auth_request->set->failure_delay && !flush_all) break; } /* shuffle these requests to try to prevent any kind of timing attacks where attacker performs multiple requests in parallel and attempts to figure out results based on the order of replies. */ count = i; for (i = 0; i < count; i++) { j = random() % (count - i) + i; auth_request = auth_requests[aqueue_idx(auth_failures, i)]; /* swap i & j */ auth_requests[aqueue_idx(auth_failures, i)] = auth_requests[aqueue_idx(auth_failures, j)]; auth_requests[aqueue_idx(auth_failures, j)] = auth_request; } /* flush the requests */ for (i = 0; i < count; i++) { auth_request = auth_requests[aqueue_idx(auth_failures, 0)]; aqueue_delete_tail(auth_failures); i_assert(auth_request != NULL); i_assert(auth_request->state == AUTH_REQUEST_STATE_FINISHED); auth_request_handler_reply(auth_request, AUTH_CLIENT_RESULT_FAILURE, uchar_empty_ptr, 0); auth_request_unref(&auth_request); } } static void auth_failure_timeout(void *context ATTR_UNUSED) { auth_request_handler_flush_failures(FALSE); } void auth_request_handler_init(void) { i_array_init(&auth_failures_arr, 128); auth_failures = aqueue_init(&auth_failures_arr.arr); } void auth_request_handler_deinit(void) { auth_request_handler_flush_failures(TRUE); array_free(&auth_failures_arr); aqueue_deinit(&auth_failures); timeout_remove(&to_auth_failures); } dovecot-2.3.21.1/src/auth/passdb-shadow.c0000644000000000000000000000567514656633576014765 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "passdb.h" #ifdef PASSDB_SHADOW #include "safe-memset.h" #include #define SHADOW_CACHE_KEY "%u" #define SHADOW_PASS_SCHEME "CRYPT" static enum passdb_result shadow_lookup(struct auth_request *request, struct spwd **spw_r) { e_debug(authdb_event(request), "lookup"); *spw_r = getspnam(request->fields.user); if (*spw_r == NULL) { auth_request_log_unknown_user(request, AUTH_SUBSYS_DB); return PASSDB_RESULT_USER_UNKNOWN; } if (!IS_VALID_PASSWD((*spw_r)->sp_pwdp)) { e_info(authdb_event(request), "invalid password field"); return PASSDB_RESULT_USER_DISABLED; } /* save the password so cache can use it */ auth_request_set_field(request, "password", (*spw_r)->sp_pwdp, SHADOW_PASS_SCHEME); return PASSDB_RESULT_OK; } static void shadow_verify_plain(struct auth_request *request, const char *password, verify_plain_callback_t *callback) { struct spwd *spw; enum passdb_result res; int ret; res = shadow_lookup(request, &spw); if (res != PASSDB_RESULT_OK) { callback(res, request); return; } /* check if the password is valid */ ret = auth_request_password_verify(request, password, spw->sp_pwdp, SHADOW_PASS_SCHEME, AUTH_SUBSYS_DB); /* clear the passwords from memory */ safe_memset(spw->sp_pwdp, 0, strlen(spw->sp_pwdp)); if (ret <= 0) { callback(PASSDB_RESULT_PASSWORD_MISMATCH, request); return; } /* make sure we're using the username exactly as it's in the database */ auth_request_set_field(request, "user", spw->sp_namp, NULL); callback(PASSDB_RESULT_OK, request); } static void shadow_lookup_credentials(struct auth_request *request, lookup_credentials_callback_t *callback) { struct spwd *spw; enum passdb_result res; res = shadow_lookup(request, &spw); if (res != PASSDB_RESULT_OK) { callback(res, NULL, 0, request); return; } /* make sure we're using the username exactly as it's in the database */ auth_request_set_field(request, "user", spw->sp_namp, NULL); passdb_handle_credentials(PASSDB_RESULT_OK, spw->sp_pwdp, SHADOW_PASS_SCHEME, callback, request); } static struct passdb_module * shadow_preinit(pool_t pool, const char *args) { struct passdb_module *module; module = p_new(pool, struct passdb_module, 1); module->blocking = TRUE; if (strcmp(args, "blocking=no") == 0) module->blocking = FALSE; else if (*args != '\0') i_fatal("passdb shadow: Unknown setting: %s", args); module->default_cache_key = SHADOW_CACHE_KEY; module->default_pass_scheme = SHADOW_PASS_SCHEME; return module; } static void shadow_deinit(struct passdb_module *module ATTR_UNUSED) { endspent(); } struct passdb_module_interface passdb_shadow = { "shadow", shadow_preinit, NULL, shadow_deinit, shadow_verify_plain, shadow_lookup_credentials, NULL }; #else struct passdb_module_interface passdb_shadow = { .name = "shadow" }; #endif dovecot-2.3.21.1/src/auth/auth-stats.h0000644000000000000000000000050414656633576014312 00000000000000#ifndef AUTH_STATS_H #define AUTH_STATS_H struct auth_stats { uint32_t auth_success_count; uint32_t auth_master_success_count; uint32_t auth_failure_count; uint32_t auth_db_tempfail_count; uint32_t auth_cache_hit_count; uint32_t auth_cache_miss_count; }; extern const struct stats_vfuncs auth_stats_vfuncs; #endif dovecot-2.3.21.1/src/auth/auth-master-connection.c0000644000000000000000000005523214656633576016607 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "buffer.h" #include "hash.h" #include "llist.h" #include "str.h" #include "strescape.h" #include "str-sanitize.h" #include "time-util.h" #include "hostpid.h" #include "hex-binary.h" #include "ioloop.h" #include "net.h" #include "istream.h" #include "ostream.h" #include "ipwd.h" #include "master-service.h" #include "userdb.h" #include "userdb-blocking.h" #include "master-interface.h" #include "passdb-cache.h" #include "auth-request-handler.h" #include "auth-client-connection.h" #include "auth-master-connection.h" #include #define MAX_INBUF_SIZE 1024 #define MAX_OUTBUF_SIZE (1024*50) struct master_userdb_request { struct auth_master_connection *conn; struct auth_request *auth_request; }; struct master_list_iter_ctx { struct auth_master_connection *conn; struct userdb_iterate_context *iter; struct auth_request *auth_request; bool failed; }; static void master_input(struct auth_master_connection *conn); static struct auth_master_connection *auth_master_connections; static const char * auth_master_reply_hide_passwords(struct auth_master_connection *conn, const char *str) { char **args, *p, *p2; unsigned int i; if (conn->auth->set->debug_passwords) return str; /* hide all parameters that have "pass" in their key */ args = p_strsplit(pool_datastack_create(), str, "\t"); for (i = 0; args[i] != NULL; i++) { p = strstr(args[i], "pass"); p2 = strchr(args[i], '='); if (p != NULL && p < p2) { *p2 = '\0'; args[i] = p_strconcat(pool_datastack_create(), args[i], "=", NULL); } } return t_strarray_join((void *)args, "\t"); } void auth_master_request_callback(const char *reply, struct auth_master_connection *conn) { struct const_iovec iov[2]; e_debug(auth_event, "master userdb out: %s", auth_master_reply_hide_passwords(conn, reply)); iov[0].iov_base = reply; iov[0].iov_len = strlen(reply); iov[1].iov_base = "\n"; iov[1].iov_len = 1; o_stream_nsendv(conn->output, iov, 2); } static const char * auth_master_event_log_callback(struct auth_master_connection *conn, enum log_type log_type ATTR_UNUSED, const char *message) { string_t *str = t_str_new(128); str_printfa(str, "auth-master client: %s (created %d msecs ago", message, timeval_diff_msecs(&ioloop_timeval, &conn->create_time)); if (conn->handshake_time.tv_sec != 0) { str_printfa(str, ", handshake %d msecs ago", timeval_diff_msecs(&ioloop_timeval, &conn->create_time)); } str_append_c(str, ')'); return str_c(str); } static bool master_input_request(struct auth_master_connection *conn, const char *args) { struct auth_client_connection *client_conn; const char *const *list, *const *params; unsigned int id, client_pid, client_id; uint8_t cookie[MASTER_AUTH_COOKIE_SIZE]; buffer_t buf; /* [] */ list = t_strsplit_tabescaped(args); if (str_array_length(list) < 4 || str_to_uint(list[0], &id) < 0 || str_to_uint(list[1], &client_pid) < 0 || str_to_uint(list[2], &client_id) < 0) { e_error(conn->event, "BUG: Master sent broken REQUEST"); return FALSE; } buffer_create_from_data(&buf, cookie, sizeof(cookie)); if (hex_to_binary(list[3], &buf) < 0) { e_error(conn->event, "BUG: Master sent broken REQUEST cookie"); return FALSE; } params = list + 4; client_conn = auth_client_connection_lookup(client_pid); if (client_conn == NULL) { e_error(conn->event, "Master requested auth for nonexistent client %u", client_pid); o_stream_nsend_str(conn->output, t_strdup_printf("FAIL\t%u\n", id)); } else if (!mem_equals_timing_safe(client_conn->cookie, cookie, sizeof(cookie))) { e_error(conn->event, "Master requested auth for client %u with invalid cookie", client_pid); o_stream_nsend_str(conn->output, t_strdup_printf("FAIL\t%u\n", id)); } else if (!auth_request_handler_master_request( client_conn->request_handler, conn, id, client_id, params)) { e_error(conn->event, "Master requested auth for non-login client %u", client_pid); o_stream_nsend_str(conn->output, t_strdup_printf("FAIL\t%u\n", id)); } return TRUE; } static bool master_input_cache_flush(struct auth_master_connection *conn, const char *args) { const char *const *list; unsigned int count; /* [ [ [..]] */ list = t_strsplit_tabescaped(args); if (list[0] == NULL) { e_error(conn->event, "BUG: doveadm sent broken CACHE-FLUSH"); return FALSE; } if (passdb_cache == NULL) { /* cache disabled */ count = 0; } else if (list[1] == NULL) { /* flush the whole cache */ count = auth_cache_clear(passdb_cache); } else { count = auth_cache_clear_users(passdb_cache, list+1); } o_stream_nsend_str(conn->output, t_strdup_printf("OK\t%s\t%u\n", list[0], count)); return TRUE; } static int master_input_auth_request(struct auth_master_connection *conn, const char *args, const char *cmd, struct auth_request **request_r, const char **error_r) { struct auth_request *auth_request; const char *const *list, *name, *arg, *username; unsigned int id; /* [] */ list = t_strsplit_tabescaped(args); if (list[0] == NULL || list[1] == NULL || str_to_uint(list[0], &id) < 0) { e_error(conn->event, "BUG: Master sent broken %s", cmd); return -1; } auth_request = auth_request_new_dummy(auth_event); auth_request->id = id; auth_request->master = conn; auth_master_connection_ref(conn); username = list[1]; for (list += 2; *list != NULL; list++) { arg = strchr(*list, '='); if (arg == NULL) { name = *list; arg = ""; } else { name = t_strdup_until(*list, arg); arg++; } (void)auth_request_import_info(auth_request, name, arg); } if (auth_request->fields.service == NULL) { e_error(conn->event, "BUG: Master sent %s request without service", cmd); auth_request_unref(&auth_request); auth_master_connection_unref(&conn); return -1; } auth_request_init(auth_request); if (!auth_request_set_username(auth_request, username, error_r)) { *request_r = auth_request; return 0; } *request_r = auth_request; return 1; } static int user_verify_restricted_uid(struct auth_request *auth_request) { struct auth_master_connection *conn = auth_request->master; struct auth_fields *reply = auth_request->fields.userdb_reply; const char *value, *reason; uid_t uid; if (conn->userdb_restricted_uid == 0) return 0; value = auth_fields_find(reply, "uid"); if (value == NULL) reason = "userdb reply doesn't contain uid"; else if (str_to_uid(value, &uid) < 0) reason = "userdb reply contains invalid uid"; else if (uid != conn->userdb_restricted_uid) { reason = t_strdup_printf( "userdb uid (%s) doesn't match peer uid (%s)", dec2str(uid), dec2str(conn->userdb_restricted_uid)); } else { return 0; } auth_request_log_error(auth_request, "userdb", "client doesn't have lookup permissions for this user: %s " "(to bypass this check, set: service auth { unix_listener %s { mode=0777 } })", reason, conn->path); return -1; } static void user_callback(enum userdb_result result, struct auth_request *auth_request) { struct auth_master_connection *conn = auth_request->master; string_t *str; const char *value; if (auth_request->userdb_lookup_tempfailed) result = USERDB_RESULT_INTERNAL_FAILURE; if (result == USERDB_RESULT_OK) { if (user_verify_restricted_uid(auth_request) < 0) result = USERDB_RESULT_INTERNAL_FAILURE; } str = t_str_new(128); switch (result) { case USERDB_RESULT_INTERNAL_FAILURE: str_printfa(str, "FAIL\t%u", auth_request->id); if (auth_request->userdb_lookup_tempfailed) { value = auth_fields_find(auth_request->fields.userdb_reply, "reason"); if (value != NULL) str_printfa(str, "\treason=%s", value); } break; case USERDB_RESULT_USER_UNKNOWN: str_printfa(str, "NOTFOUND\t%u", auth_request->id); break; case USERDB_RESULT_OK: str_printfa(str, "USER\t%u\t", auth_request->id); str_append_tabescaped(str, auth_request->fields.user); str_append_c(str, '\t'); auth_fields_append(auth_request->fields.userdb_reply, str, AUTH_FIELD_FLAG_HIDDEN, 0); break; } e_debug(auth_event, "userdb out: %s", auth_master_reply_hide_passwords(conn, str_c(str))); str_append_c(str, '\n'); o_stream_nsend(conn->output, str_data(str), str_len(str)); auth_request_unref(&auth_request); auth_master_connection_unref(&conn); } static bool master_input_user(struct auth_master_connection *conn, const char *args) { struct auth_request *auth_request; const char *error; int ret; ret = master_input_auth_request(conn, args, "USER", &auth_request, &error); if (ret <= 0) { if (ret < 0) return FALSE; auth_request_log_info(auth_request, "userdb", "%s", error); user_callback(USERDB_RESULT_USER_UNKNOWN, auth_request); } else { auth_request_set_state(auth_request, AUTH_REQUEST_STATE_USERDB); auth_request_lookup_user(auth_request, user_callback); } return TRUE; } static void pass_callback_finish(struct auth_request *auth_request, enum passdb_result result) { struct auth_master_connection *conn = auth_request->master; string_t *str; str = t_str_new(128); switch (result) { case PASSDB_RESULT_OK: if (auth_request->failed || !auth_request->passdb_success) { str_printfa(str, "FAIL\t%u", auth_request->id); break; } str_printfa(str, "PASS\t%u\tuser=", auth_request->id); str_append_tabescaped(str, auth_request->fields.user); if (!auth_fields_is_empty(auth_request->fields.extra_fields)) { str_append_c(str, '\t'); auth_fields_append(auth_request->fields.extra_fields, str, AUTH_FIELD_FLAG_HIDDEN, 0); } break; case PASSDB_RESULT_USER_UNKNOWN: case PASSDB_RESULT_USER_DISABLED: case PASSDB_RESULT_PASS_EXPIRED: str_printfa(str, "NOTFOUND\t%u", auth_request->id); break; case PASSDB_RESULT_NEXT: case PASSDB_RESULT_PASSWORD_MISMATCH: case PASSDB_RESULT_INTERNAL_FAILURE: str_printfa(str, "FAIL\t%u", auth_request->id); break; case PASSDB_RESULT_SCHEME_NOT_AVAILABLE: str_printfa(str, "FAIL\t%u\treason=Configured passdbs don't support credentials lookups", auth_request->id); break; } e_debug(auth_event, "passdb out: %s", str_c(str)); str_append_c(str, '\n'); o_stream_nsend(conn->output, str_data(str), str_len(str)); auth_request_unref(&auth_request); auth_master_connection_unref(&conn); } static void auth_master_pass_proxy_finish(bool success, struct auth_request *auth_request) { pass_callback_finish(auth_request, success ? PASSDB_RESULT_OK : PASSDB_RESULT_INTERNAL_FAILURE); } static void pass_callback(enum passdb_result result, const unsigned char *credentials ATTR_UNUSED, size_t size ATTR_UNUSED, struct auth_request *auth_request) { int ret; if (result != PASSDB_RESULT_OK) auth_request_proxy_finish_failure(auth_request); else { ret = auth_request_proxy_finish(auth_request, auth_master_pass_proxy_finish); if (ret == 0) return; if (ret < 0) result = PASSDB_RESULT_INTERNAL_FAILURE; } pass_callback_finish(auth_request, result); } static const char *auth_restricted_reason(struct auth_master_connection *conn) { struct passwd pw; const char *namestr; if (i_getpwuid(conn->userdb_restricted_uid, &pw) <= 0) namestr = ""; else namestr = t_strdup_printf("(%s)", pw.pw_name); return t_strdup_printf("%s mode=0666, but not owned by UID %lu%s", conn->path, (unsigned long)conn->userdb_restricted_uid, namestr); } static bool master_input_pass(struct auth_master_connection *conn, const char *args) { struct auth_request *auth_request; const char *error; int ret; ret = master_input_auth_request(conn, args, "PASS", &auth_request, &error); if (ret <= 0) { if (ret < 0) return FALSE; auth_request_log_info(auth_request, "passdb", "%s", error); pass_callback(PASSDB_RESULT_USER_UNKNOWN, uchar_empty_ptr, 0, auth_request); } else if (conn->userdb_restricted_uid != 0) { /* no permissions to do this lookup */ auth_request_log_error(auth_request, "passdb", "Auth client doesn't have permissions to do " "a PASS lookup: %s", auth_restricted_reason(conn)); pass_callback(PASSDB_RESULT_INTERNAL_FAILURE, uchar_empty_ptr, 0, auth_request); } else { auth_request_set_state(auth_request, AUTH_REQUEST_STATE_MECH_CONTINUE); auth_request_lookup_credentials(auth_request, "", pass_callback); } return TRUE; } static void master_input_list_finish(struct master_list_iter_ctx *ctx) { i_assert(ctx->conn->iter_ctx == ctx); ctx->conn->iter_ctx = NULL; ctx->conn->io = io_add(ctx->conn->fd, IO_READ, master_input, ctx->conn); if (ctx->iter != NULL) (void)userdb_blocking_iter_deinit(&ctx->iter); o_stream_uncork(ctx->conn->output); o_stream_unset_flush_callback(ctx->conn->output); auth_request_unref(&ctx->auth_request); auth_master_connection_unref(&ctx->conn); i_free(ctx); } static int master_output_list(struct master_list_iter_ctx *ctx) { int ret; if ((ret = o_stream_flush(ctx->conn->output)) < 0) { master_input_list_finish(ctx); return 1; } if (ret > 0) { o_stream_cork(ctx->conn->output); userdb_blocking_iter_next(ctx->iter); } return 1; } static void master_input_list_callback(const char *user, void *context) { struct master_list_iter_ctx *ctx = context; struct auth_userdb *userdb = ctx->auth_request->userdb; int ret; if (user == NULL) { if (userdb_blocking_iter_deinit(&ctx->iter) < 0) ctx->failed = TRUE; do { userdb = userdb->next; } while (userdb != NULL && userdb->userdb->iface->iterate_init == NULL); if (userdb == NULL) { /* iteration is finished */ const char *str; str = t_strdup_printf("DONE\t%u\t%s\n", ctx->auth_request->id, ctx->failed ? "fail" : ""); o_stream_nsend_str(ctx->conn->output, str); master_input_list_finish(ctx); return; } /* continue iterating next userdb */ ctx->auth_request->userdb = userdb; ctx->iter = userdb_blocking_iter_init(ctx->auth_request, master_input_list_callback, ctx); return; } T_BEGIN { const char *str; str = t_strdup_printf("LIST\t%u\t%s\n", ctx->auth_request->id, str_tabescape(user)); ret = o_stream_send_str(ctx->conn->output, str); } T_END; if (o_stream_get_buffer_used_size(ctx->conn->output) >= MAX_OUTBUF_SIZE) ret = o_stream_flush(ctx->conn->output); if (ret < 0) { /* disconnected, don't bother finishing */ master_input_list_finish(ctx); return; } if (o_stream_get_buffer_used_size(ctx->conn->output) < MAX_OUTBUF_SIZE) userdb_blocking_iter_next(ctx->iter); else o_stream_uncork(ctx->conn->output); } static bool master_input_list(struct auth_master_connection *conn, const char *args) { struct auth_userdb *userdb = conn->auth->userdbs; struct auth_request *auth_request; struct master_list_iter_ctx *ctx; const char *str, *name, *arg, *const *list; unsigned int id; /* [] */ list = t_strsplit_tabescaped(args); if (list[0] == NULL || str_to_uint(list[0], &id) < 0) { e_error(conn->event, "BUG: Master sent broken LIST"); return FALSE; } list++; if (conn->iter_ctx != NULL) { e_error(conn->event, "Auth client is already iterating users"); str = t_strdup_printf("DONE\t%u\tfail\n", id); o_stream_nsend_str(conn->output, str); return TRUE; } if (conn->userdb_restricted_uid != 0) { e_error(conn->event, "Auth client doesn't have permissions to list users: %s", auth_restricted_reason(conn)); str = t_strdup_printf("DONE\t%u\tfail\n", id); o_stream_nsend_str(conn->output, str); return TRUE; } while (userdb != NULL && userdb->userdb->iface->iterate_init == NULL) userdb = userdb->next; if (userdb == NULL) { e_error(conn->event, "Trying to iterate users, but userdbs don't support it"); str = t_strdup_printf("DONE\t%u\tfail\n", id); o_stream_nsend_str(conn->output, str); return TRUE; } auth_request = auth_request_new_dummy(auth_event); auth_request->id = id; auth_request->master = conn; auth_master_connection_ref(conn); for (; *list != NULL; list++) { arg = strchr(*list, '='); if (arg == NULL) { name = *list; arg = ""; } else { name = t_strdup_until(*list, arg); arg++; } if (!auth_request_import_info(auth_request, name, arg) && strcmp(name, "user") == 0) { /* username mask */ auth_request_set_username_forced(auth_request, arg); } } /* rest of the code doesn't like NULL user or service */ if (auth_request->fields.user == NULL) auth_request_set_username_forced(auth_request, ""); if (auth_request->fields.service == NULL) { if (!auth_request_import(auth_request, "service", "")) i_unreached(); i_assert(auth_request->fields.service != NULL); } ctx = i_new(struct master_list_iter_ctx, 1); ctx->conn = conn; ctx->auth_request = auth_request; ctx->auth_request->userdb = userdb; io_remove(&conn->io); o_stream_cork(conn->output); o_stream_set_flush_callback(conn->output, master_output_list, ctx); ctx->iter = userdb_blocking_iter_init(auth_request, master_input_list_callback, ctx); conn->iter_ctx = ctx; return TRUE; } static bool auth_master_input_line(struct auth_master_connection *conn, const char *line) { e_debug(auth_event, "master in: %s", line); if (str_begins(line, "USER\t")) return master_input_user(conn, line + 5); if (str_begins(line, "LIST\t")) return master_input_list(conn, line + 5); if (str_begins(line, "PASS\t")) return master_input_pass(conn, line + 5); if (!conn->userdb_only) { i_assert(conn->userdb_restricted_uid == 0); if (str_begins(line, "REQUEST\t")) return master_input_request(conn, line + 8); if (str_begins(line, "CACHE-FLUSH\t")) return master_input_cache_flush(conn, line + 12); if (str_begins(line, "CPID\t")) { e_error(conn->event, "Authentication client trying to connect to " "master socket"); return FALSE; } } e_error(conn->event, "BUG: Unknown command in %s socket: %s", conn->userdb_only ? "userdb" : "master", str_sanitize(line, 80)); return FALSE; } static void master_input(struct auth_master_connection *conn) { char *line; bool ret; switch (i_stream_read(conn->input)) { case 0: return; case -1: /* disconnected */ auth_master_connection_destroy(&conn); return; case -2: /* buffer full */ e_error(conn->event, "BUG: Master sent us more than %d bytes", (int)MAX_INBUF_SIZE); auth_master_connection_destroy(&conn); return; } if (!conn->version_received) { line = i_stream_next_line(conn->input); if (line == NULL) return; /* make sure the major version matches */ if (!str_begins(line, "VERSION\t") || !str_uint_equals(t_strcut(line + 8, '\t'), AUTH_MASTER_PROTOCOL_MAJOR_VERSION)) { e_error(conn->event, "Master not compatible with this server " "(mixed old and new binaries?)"); auth_master_connection_destroy(&conn); return; } conn->version_received = TRUE; conn->handshake_time = ioloop_timeval; } while ((line = i_stream_next_line(conn->input)) != NULL) { T_BEGIN { ret = auth_master_input_line(conn, line); } T_END; if (!ret) { auth_master_connection_destroy(&conn); return; } } } static int master_output(struct auth_master_connection *conn) { if (o_stream_flush(conn->output) < 0) { /* transmit error, probably master died */ auth_master_connection_destroy(&conn); return 1; } if (conn->io == NULL && o_stream_get_buffer_used_size(conn->output) <= MAX_OUTBUF_SIZE/2) { /* allow input again */ conn->io = io_add(conn->fd, IO_READ, master_input, conn); } return 1; } static int auth_master_connection_set_permissions(struct auth_master_connection *conn, const struct stat *st) { struct net_unix_cred cred; if (st == NULL) return 0; /* figure out what permissions we want to give to this client */ if ((st->st_mode & 0777) != 0666) { /* permissions were already restricted by the socket permissions. also +x bit indicates that we shouldn't do any permission checks. */ return 0; } if (net_getunixcred(conn->fd, &cred) < 0) { e_error(conn->event, "userdb connection: Failed to get peer's credentials"); return -1; } if (cred.uid == st->st_uid || cred.gid == st->st_gid) { /* full permissions */ return 0; } else { /* restrict permissions: return only lookups whose returned uid matches the peer's uid */ conn->userdb_restricted_uid = cred.uid; return 0; } } struct auth_master_connection * auth_master_connection_create(struct auth *auth, int fd, const char *path, const struct stat *socket_st, bool userdb_only) { struct auth_master_connection *conn; const char *line; i_assert(path != NULL); conn = i_new(struct auth_master_connection, 1); conn->refcount = 1; conn->fd = fd; conn->create_time = ioloop_timeval; conn->path = i_strdup(path); conn->auth = auth; conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE); conn->output = o_stream_create_fd(fd, SIZE_MAX); o_stream_set_no_error_handling(conn->output, TRUE); o_stream_set_flush_callback(conn->output, master_output, conn); conn->io = io_add(fd, IO_READ, master_input, conn); conn->userdb_only = userdb_only; conn->event = event_create(auth_event); event_set_log_message_callback(conn->event, auth_master_event_log_callback, conn); line = t_strdup_printf("VERSION\t%u\t%u\nSPID\t%s\n", AUTH_MASTER_PROTOCOL_MAJOR_VERSION, AUTH_MASTER_PROTOCOL_MINOR_VERSION, my_pid); o_stream_nsend_str(conn->output, line); DLLIST_PREPEND(&auth_master_connections, conn); if (auth_master_connection_set_permissions(conn, socket_st) < 0) { auth_master_connection_destroy(&conn); return NULL; } return conn; } void auth_master_connection_destroy(struct auth_master_connection **_conn) { struct auth_master_connection *conn = *_conn; *_conn = NULL; if (conn->destroyed) return; conn->destroyed = TRUE; DLLIST_REMOVE(&auth_master_connections, conn); if (conn->iter_ctx != NULL) master_input_list_finish(conn->iter_ctx); i_stream_close(conn->input); o_stream_close(conn->output); io_remove(&conn->io); i_close_fd_path(&conn->fd, conn->path); master_service_client_connection_destroyed(master_service); auth_master_connection_unref(&conn); } void auth_master_connection_ref(struct auth_master_connection *conn) { i_assert(conn->refcount > 0); conn->refcount++; } void auth_master_connection_unref(struct auth_master_connection **_conn) { struct auth_master_connection *conn = *_conn; *_conn = NULL; i_assert(conn->refcount > 0); if (--conn->refcount > 0) return; i_stream_unref(&conn->input); o_stream_unref(&conn->output); event_unref(&conn->event); i_free(conn->path); i_free(conn); } void auth_master_connections_destroy_all(void) { struct auth_master_connection *conn; while (auth_master_connections != NULL) { conn = auth_master_connections; auth_master_connection_destroy(&conn); } } dovecot-2.3.21.1/src/auth/db-oauth2.h0000644000000000000000000000263114656633576014005 00000000000000#ifndef DB_OAUTH2_H #define DB_OAUTH2_H 1 struct db_oauth2; struct oauth2_request; struct db_oauth2_request; typedef void db_oauth2_lookup_callback_t(struct db_oauth2_request *request, enum passdb_result result, const char *error, void *context); struct db_oauth2_request { pool_t pool; struct db_oauth2_request *prev,*next; struct db_oauth2 *db; struct oauth2_request *req; /* username to match */ const char *username; /* token to use */ const char *token; struct auth_request *auth_request; struct auth_fields *fields; db_oauth2_lookup_callback_t *callback; void *context; verify_plain_callback_t *verify_callback; }; struct db_oauth2 *db_oauth2_init(const char *config_path); void db_oauth2_ref(struct db_oauth2 *); void db_oauth2_unref(struct db_oauth2 **); bool db_oauth2_uses_password_grant(const struct db_oauth2 *db); const char *db_oauth2_get_openid_configuration_url(const struct db_oauth2 *db); void db_oauth2_lookup(struct db_oauth2 *db, struct db_oauth2_request *req, const char *token, struct auth_request *request, db_oauth2_lookup_callback_t *callback, void *context); #define db_oauth2_lookup(db, req, token, request, callback, context) \ db_oauth2_lookup(db, req, token - \ CALLBACK_TYPECHECK(callback, void(*)(struct db_oauth2_request *, enum passdb_result, const char*, typeof(context))), \ request, (db_oauth2_lookup_callback_t*)callback, (void*)context) #endif dovecot-2.3.21.1/src/auth/test-auth-request-var-expand.c0000644000000000000000000002034514656633576017664 00000000000000/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ #include "test-auth.h" #include "str.h" #include "auth.h" #include "passdb.h" #include "userdb.h" #include "auth-request.h" static struct passdb_module test_passdb = { .id = 40 }; static struct userdb_module test_userdb = { .id = 41 }; static struct auth_passdb test_auth_passdb = { .passdb = &test_passdb }; static struct auth_userdb test_auth_userdb = { .userdb = &test_userdb }; static struct auth_request default_test_request = { .fields = { .user = "-user@+domain1@+domain2", .service = "-service", .local_ip = { .family = AF_INET }, .remote_ip = { .family = AF_INET }, .mech_name = "-mech", .secured = AUTH_REQUEST_SECURED, .local_port = 21, .remote_port = 210, .valid_client_cert = TRUE, .requested_login_user = "-loginuser@+logindomain1@+logindomain2", .session_id = "-session", .real_local_ip = { .family = AF_INET }, .real_remote_ip = { .family = AF_INET }, .real_local_port = 200, .real_remote_port = 201, .master_user = "-masteruser@-masterdomain1@-masterdomain2", .original_username = "-origuser@-origdomain1@-origdomain2", }, .client_pid = 54321, .mech_password = "-password", .session_pid = 5000, .passdb = &test_auth_passdb, .userdb = &test_auth_userdb }; static struct auth_request test_request; static struct auth_request empty_test_request = { .fields = { .user = "" } }; static const char * test_escape(const char *string, const struct auth_request *request) { char *dest; unsigned int i; test_assert(request == &test_request); dest = t_strdup_noconst(string); for (i = 0; dest[i] != '\0'; i++) { if (dest[i] == '-') dest[i] = '+'; } return dest; } static bool test_empty_request(string_t *str, const char *input) { const struct var_expand_table *tab = auth_request_get_var_expand_table(&empty_test_request, NULL); const char *error; str_truncate(str, 0); test_assert(var_expand(str, input, tab, &error) == 1); return strspn(str_c(str), "\n0") == str_len(str); } static void test_auth_request_var_expand_shortlong(void) { static const char *test_input_short = "%u\n%n\n%d\n%s\n%h\n%l\n%r\n%l\n%r\n%p\n%w\n%m\n%c\n" "%a\n%b\n%a\n%b\n%k\n"; static const char *test_input_long = "%{user}\n%{username}\n%{domain}\n%{service}\n%{home}\n" "%{lip}\n%{rip}\n%{local_ip}\n%{remote_ip}\n" "%{pid}\n%{password}\n%{mech}\n%{secured}\n" "%{lport}\n%{rport}\n%{local_port}\n%{remote_port}\n%{cert}\n"; static const char *test_output = /* %{home} is intentionally always expanding to empty */ "+user@+domain1@+domain2\n+user\n+domain1@+domain2\n+service\n\n" "7.91.205.21\n73.150.2.210\n7.91.205.21\n73.150.2.210\n" "54321\n+password\n+mech\nsecured\n" "21\n210\n21\n210\nvalid\n"; const struct var_expand_table *tab; string_t *str = t_str_new(256); const char *error; test_begin("auth request var expand short and long"); tab = auth_request_get_var_expand_table(&test_request, test_escape); test_assert(var_expand(str, test_input_short, tab, &error) == 1); test_assert(strcmp(str_c(str), test_output) == 0); str_truncate(str, 0); test_assert(var_expand(str, test_input_long, tab, &error) == 1); test_assert(strcmp(str_c(str), test_output) == 0); /* test with empty input that it won't crash */ test_assert(test_empty_request(str, test_input_short)); test_assert(test_empty_request(str, test_input_long)); test_end(); } static void test_auth_request_var_expand_flags(void) { static const char *test_input = "%!\n%{secured}\n%{cert}\n"; string_t *str = t_str_new(10); const char *error; test_begin("auth request var expand flags"); test_request.userdb_lookup = FALSE; test_request.fields.secured = AUTH_REQUEST_SECURED_NONE; test_request.fields.valid_client_cert = FALSE; test_assert(var_expand(str, test_input, auth_request_get_var_expand_table(&test_request, test_escape), &error) == 1); test_assert(strcmp(str_c(str), "40\n\n\n") == 0); test_request.userdb_lookup = TRUE; test_request.fields.secured = AUTH_REQUEST_SECURED; test_request.fields.valid_client_cert = TRUE; str_truncate(str, 0); test_assert(var_expand(str, test_input, auth_request_get_var_expand_table(&test_request, test_escape), &error) == 1); test_assert(strcmp(str_c(str), "41\nsecured\nvalid\n") == 0); test_assert(test_empty_request(str, test_input)); test_end(); } static void test_auth_request_var_expand_long(void) { static const char *test_input = "%{login_user}\n%{login_username}\n%{login_domain}\n%{session}\n" "%{real_lip}\n%{real_rip}\n%{real_lport}\n%{real_rport}\n" "%{real_local_ip}\n%{real_remote_ip}\n" "%{real_local_port}\n%{real_remote_port}\n" "%{master_user}\n%{session_pid}\n" "%{orig_user}\n%{orig_username}\n%{orig_domain}\n"; static const char *test_output = "+loginuser@+logindomain1@+logindomain2\n+loginuser\n+logindomain1@+logindomain2\n+session\n" "13.81.174.20\n13.81.174.21\n200\n201\n" "13.81.174.20\n13.81.174.21\n" "200\n201\n" "+masteruser@+masterdomain1@+masterdomain2\n5000\n" "+origuser@+origdomain1@+origdomain2\n+origuser\n+origdomain1@+origdomain2\n"; string_t *str = t_str_new(256); const char *error; test_begin("auth request var expand long-only"); test_assert(var_expand(str, test_input, auth_request_get_var_expand_table(&test_request, test_escape), &error) == 1); test_assert(strcmp(str_c(str), test_output) == 0); test_assert(test_empty_request(str, test_input)); test_end(); } static void test_auth_request_var_expand_usernames(void) { static const struct { const char *username, *output; } tests[] = { { "-foo", "+foo\n\n\n\n+foo" }, { "-foo@-domain", "+foo\n+domain\n+domain\n+domain\n+foo@+domain" }, { "-foo@-domain1@-domain2", "+foo\n+domain1@+domain2\n+domain1\n+domain2\n+foo@+domain1@+domain2" } }; static const char *test_input = "%{username}\n%{domain}\n%{domain_first}\n%{domain_last}\n%{user}"; string_t *str = t_str_new(64); const char *error; unsigned int i; test_begin("auth request var expand usernames"); for (i = 0; i < N_ELEMENTS(tests); i++) { test_request.fields.user = t_strdup_noconst(tests[i].username); str_truncate(str, 0); test_assert(var_expand(str, test_input, auth_request_get_var_expand_table(&test_request, test_escape), &error) == 1); test_assert_idx(strcmp(str_c(str), tests[i].output) == 0, i); } test_request.fields.user = default_test_request.fields.user; test_end(); } static void test_auth_request_var_expand_funcs(void) { pool_t pool; const char *value, *error; test_begin("auth request var expand funcs"); pool = pool_alloconly_create("test var expand funcs", 1024); test_request.fields.extra_fields = auth_fields_init(pool); test_request.fields.userdb_reply = auth_fields_init(pool); auth_fields_add(test_request.fields.extra_fields, "pkey1", "-pval1", 0); auth_fields_add(test_request.fields.extra_fields, "pkey2", "", 0); auth_fields_add(test_request.fields.userdb_reply, "ukey1", "-uval1", 0); auth_fields_add(test_request.fields.userdb_reply, "ukey2", "", 0); test_assert(t_auth_request_var_expand( "%{passdb:pkey1}\n%{passdb:pkey1:default1}\n" "%{passdb:pkey2}\n%{passdb:pkey2:default2}\n" "%{passdb:pkey3}\n%{passdb:pkey3:default3}\n" "%{passdb:ukey1}\n%{passdb:ukey1:default4}\n", &test_request, test_escape, &value, &error) == 1); test_assert(strcmp(value, "+pval1\n+pval1\n\n\n\ndefault3\n\ndefault4\n") == 0); test_assert(t_auth_request_var_expand( "%{userdb:ukey1}\n%{userdb:ukey1:default1}\n" "%{userdb:ukey2}\n%{userdb:ukey2:default2}\n" "%{userdb:ukey3}\n%{userdb:ukey3:default3}\n" "%{userdb:pkey1}\n%{userdb:pkey1:default4}\n", &test_request, test_escape, &value, &error) == 1); test_assert(strcmp(value, "+uval1\n+uval1\n\n\n\ndefault3\n\ndefault4\n") == 0); pool_unref(&pool); test_end(); } void test_auth_request_var_expand(void) { default_test_request.fields.local_ip.u.ip4.s_addr = htonl(123456789); default_test_request.fields.remote_ip.u.ip4.s_addr = htonl(1234567890); default_test_request.fields.real_local_ip.u.ip4.s_addr = htonl(223456788); default_test_request.fields.real_remote_ip.u.ip4.s_addr = htonl(223456789); test_request = default_test_request; test_auth_request_var_expand_shortlong(); test_auth_request_var_expand_flags(); test_auth_request_var_expand_long(); test_auth_request_var_expand_usernames(); test_auth_request_var_expand_funcs(); } dovecot-2.3.21.1/src/auth/test-main.c0000644000000000000000000000154514656633576014117 00000000000000/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "auth-settings.h" #include "test-common.h" #include "test-auth.h" #include "password-scheme.h" #include "passdb.h" int main(int argc, const char *argv[]) { const char *match = ""; int ret; static const struct named_test test_functions[] = { TEST_NAMED(test_auth_request_var_expand) TEST_NAMED(test_auth_request_fields) TEST_NAMED(test_db_dict_parse_cache_key) TEST_NAMED(test_username_filter) #if defined(BUILTIN_LUA) TEST_NAMED(test_db_lua) #endif { NULL, NULL } }; password_schemes_init(); passdbs_init(); passdb_mock_mod_init(); if (argc > 2 && strcasecmp(argv[1], "--match") == 0) match = argv[2]; ret = test_run_named(test_functions, match); passdb_mock_mod_deinit(); password_schemes_deinit(); passdbs_deinit(); return ret; } dovecot-2.3.21.1/src/auth/userdb-passwd-file.c0000644000000000000000000001431114656633576015711 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "userdb.h" #ifdef USERDB_PASSWD_FILE #include "istream.h" #include "str.h" #include "auth-cache.h" #include "db-passwd-file.h" #include #include struct passwd_file_userdb_iterate_context { struct userdb_iterate_context ctx; struct istream *input; char *path; bool skip_passdb_entries; }; struct passwd_file_userdb_module { struct userdb_module module; struct db_passwd_file *pwf; const char *username_format; }; static int passwd_file_add_extra_fields(struct auth_request *request, char *const *fields) { string_t *str = t_str_new(512); const struct var_expand_table *table; const char *key, *value, *error; unsigned int i; table = auth_request_get_var_expand_table(request, NULL); for (i = 0; fields[i] != NULL; i++) { if (!str_begins(fields[i], "userdb_")) continue; key = fields[i] + 7; value = strchr(key, '='); if (value != NULL) { key = t_strdup_until(key, value); str_truncate(str, 0); if (auth_request_var_expand_with_table(str, value + 1, request, table, NULL, &error) <= 0) { e_error(authdb_event(request), "Failed to expand extra field %s: %s", fields[i], error); return -1; } value = str_c(str); } else { value = ""; } auth_request_set_userdb_field(request, key, value); } return 0; } static void passwd_file_lookup(struct auth_request *auth_request, userdb_callback_t *callback) { struct userdb_module *_module = auth_request->userdb->userdb; struct passwd_file_userdb_module *module = (struct passwd_file_userdb_module *)_module; struct passwd_user *pu; int ret; ret = db_passwd_file_lookup(module->pwf, auth_request, module->username_format, &pu); if (ret <= 0 || pu->uid == 0) { callback(ret < 0 ? USERDB_RESULT_INTERNAL_FAILURE : USERDB_RESULT_USER_UNKNOWN, auth_request); return; } if (pu->uid != (uid_t)-1) { auth_request_set_userdb_field(auth_request, "uid", dec2str(pu->uid)); } if (pu->gid != (gid_t)-1) { auth_request_set_userdb_field(auth_request, "gid", dec2str(pu->gid)); } if (pu->home != NULL) auth_request_set_userdb_field(auth_request, "home", pu->home); if (pu->extra_fields != NULL && passwd_file_add_extra_fields(auth_request, pu->extra_fields) < 0) { callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request); return; } callback(USERDB_RESULT_OK, auth_request); } static struct userdb_iterate_context * passwd_file_iterate_init(struct auth_request *auth_request, userdb_iter_callback_t *callback, void *context) { struct userdb_module *_module = auth_request->userdb->userdb; struct passwd_file_userdb_module *module = (struct passwd_file_userdb_module *)_module; struct passwd_file_userdb_iterate_context *ctx; int fd; ctx = i_new(struct passwd_file_userdb_iterate_context, 1); ctx->ctx.auth_request = auth_request; ctx->ctx.callback = callback; ctx->ctx.context = context; ctx->skip_passdb_entries = !module->pwf->userdb_warn_missing; if (module->pwf->default_file == NULL) { e_error(authdb_event(auth_request), "passwd-file: User iteration isn't currently supported " "with %%variable paths"); ctx->ctx.failed = TRUE; return &ctx->ctx; } ctx->path = i_strdup(module->pwf->default_file->path); /* for now we support only a single passwd-file */ fd = open(ctx->path, O_RDONLY); if (fd == -1) { e_error(authdb_event(auth_request), "open(%s) failed: %m", ctx->path); ctx->ctx.failed = TRUE; } else { ctx->input = i_stream_create_fd_autoclose(&fd, SIZE_MAX); } return &ctx->ctx; } static void passwd_file_iterate_next(struct userdb_iterate_context *_ctx) { struct passwd_file_userdb_iterate_context *ctx = (struct passwd_file_userdb_iterate_context *)_ctx; const char *line, *p; if (ctx->input == NULL) line = NULL; else { while ((line = i_stream_read_next_line(ctx->input)) != NULL) { if (*line == '\0' || *line == ':' || *line == '#') continue; /* no username or comment */ if (ctx->skip_passdb_entries && ((p = i_strchr_to_next(line, ':')) == NULL || strchr(p, ':') == NULL)) { /* only passdb info */ continue; } break; } if (line == NULL && ctx->input->stream_errno != 0) { e_error(authdb_event(_ctx->auth_request), "read(%s) failed: %s", ctx->path, i_stream_get_error(ctx->input)); _ctx->failed = TRUE; } } if (line == NULL) _ctx->callback(NULL, _ctx->context); else T_BEGIN { _ctx->callback(t_strcut(line, ':'), _ctx->context); } T_END; } static int passwd_file_iterate_deinit(struct userdb_iterate_context *_ctx) { struct passwd_file_userdb_iterate_context *ctx = (struct passwd_file_userdb_iterate_context *)_ctx; int ret = _ctx->failed ? -1 : 0; i_stream_destroy(&ctx->input); i_free(ctx->path); i_free(ctx); return ret; } static struct userdb_module * passwd_file_preinit(pool_t pool, const char *args) { struct passwd_file_userdb_module *module; const char *format = PASSWD_FILE_DEFAULT_USERNAME_FORMAT; const char *p; if (str_begins(args, "username_format=")) { args += 16; p = strchr(args, ' '); if (p == NULL) { format = p_strdup(pool, args); args = ""; } else { format = p_strdup_until(pool, args, p); args = p + 1; } } if (*args == '\0') i_fatal("userdb passwd-file: Missing args"); module = p_new(pool, struct passwd_file_userdb_module, 1); module->pwf = db_passwd_file_init(args, TRUE, global_auth_settings->debug); module->username_format = format; return &module->module; } static void passwd_file_init(struct userdb_module *_module) { struct passwd_file_userdb_module *module = (struct passwd_file_userdb_module *)_module; db_passwd_file_parse(module->pwf); } static void passwd_file_deinit(struct userdb_module *_module) { struct passwd_file_userdb_module *module = (struct passwd_file_userdb_module *)_module; db_passwd_file_unref(&module->pwf); } struct userdb_module_interface userdb_passwd_file = { "passwd-file", passwd_file_preinit, passwd_file_init, passwd_file_deinit, passwd_file_lookup, passwd_file_iterate_init, passwd_file_iterate_next, passwd_file_iterate_deinit }; #else struct userdb_module_interface userdb_passwd_file = { .name = "passwd-file" }; #endif dovecot-2.3.21.1/src/auth/auth-worker-client.c0000644000000000000000000006553014656633576015746 00000000000000/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "base64.h" #include "connection.h" #include "ioloop.h" #include "net.h" #include "istream.h" #include "ostream.h" #include "hex-binary.h" #include "str.h" #include "strescape.h" #include "process-title.h" #include "master-service.h" #include "auth-request.h" #include "auth-worker-client.h" #define AUTH_WORKER_WARN_DISCONNECTED_LONG_CMD_SECS 30 #define OUTBUF_THROTTLE_SIZE (1024*10) #define CLIENT_STATE_HANDSHAKE "handshaking" #define CLIENT_STATE_ITER "iterating users" #define CLIENT_STATE_IDLE "idling" #define CLIENT_STATE_STOP "waiting for shutdown" static unsigned int auth_worker_max_service_count = 0; static unsigned int auth_worker_service_count = 0; struct auth_worker_client { struct connection conn; int refcount; struct auth *auth; struct event *event; time_t cmd_start; bool error_sent:1; bool destroyed:1; }; struct auth_worker_command { struct auth_worker_client *client; struct event *event; }; struct auth_worker_list_context { struct auth_worker_command *cmd; struct auth_worker_client *client; struct auth_request *auth_request; struct userdb_iterate_context *iter; bool sending, sent, done; }; static struct connection_list *clients = NULL; static bool auth_worker_client_error = FALSE; static int auth_worker_output(struct auth_worker_client *client); static void auth_worker_client_destroy(struct connection *conn); static void auth_worker_client_unref(struct auth_worker_client **_client); void auth_worker_set_max_service_count(unsigned int count) { auth_worker_max_service_count = count; } static struct auth_worker_client *auth_worker_get_client(void) { if (!auth_worker_has_client()) return NULL; struct auth_worker_client *client = container_of(clients->connections, struct auth_worker_client, conn); return client; } void auth_worker_refresh_proctitle(const char *state) { if (!global_auth_settings->verbose_proctitle || !worker) return; if (auth_worker_client_error) state = "error"; else if (!auth_worker_has_client()) state = "waiting for connection"; process_title_set(t_strdup_printf("worker: %s", state)); } static void auth_worker_client_check_throttle(struct auth_worker_client *client) { if (o_stream_get_buffer_used_size(client->conn.output) >= OUTBUF_THROTTLE_SIZE) { /* stop reading new requests until client has read the pending replies. */ connection_input_halt(&client->conn); } } static void auth_worker_request_finished_full(struct auth_worker_command *cmd, const char *error, bool log_as_error) { event_set_name(cmd->event, "auth_worker_request_finished"); if (error != NULL) { event_add_str(cmd->event, "error", error); if (log_as_error) e_error(cmd->event, "Finished: %s", error); else e_debug(cmd->event, "Finished: %s", error); } else { e_debug(cmd->event, "Finished"); } auth_worker_client_check_throttle(cmd->client); auth_worker_client_unref(&cmd->client); event_unref(&cmd->event); i_free(cmd); auth_worker_refresh_proctitle(CLIENT_STATE_IDLE); } static void auth_worker_request_finished(struct auth_worker_command *cmd, const char *error) { auth_worker_request_finished_full(cmd, error, FALSE); } static void auth_worker_request_finished_bug(struct auth_worker_command *cmd, const char *error) { auth_worker_request_finished_full(cmd, error, TRUE); } bool auth_worker_auth_request_new(struct auth_worker_command *cmd, unsigned int id, const char *const *args, struct auth_request **request_r) { struct auth_request *auth_request; const char *key, *value; auth_request = auth_request_new_dummy(cmd->event); cmd->client->refcount++; auth_request->context = cmd; auth_request->id = id; for (; *args != NULL; args++) { value = strchr(*args, '='); if (value == NULL) (void)auth_request_import(auth_request, *args, ""); else { key = t_strdup_until(*args, value++); (void)auth_request_import(auth_request, key, value); } } if (auth_request->fields.user == NULL || auth_request->fields.service == NULL) { auth_request_unref(&auth_request); return FALSE; } /* reset changed-fields, so we'll export only the ones that were changed by this lookup. */ auth_fields_snapshot(auth_request->fields.extra_fields); if (auth_request->fields.userdb_reply != NULL) auth_fields_snapshot(auth_request->fields.userdb_reply); auth_request_init(auth_request); *request_r = auth_request; return TRUE; } static void auth_worker_send_reply(struct auth_worker_client *client, struct auth_request *request, string_t *str) { time_t cmd_duration = time(NULL) - client->cmd_start; const char *p; if (worker_restart_request) o_stream_nsend_str(client->conn.output, "RESTART\n"); o_stream_nsend(client->conn.output, str_data(str), str_len(str)); if (o_stream_flush(client->conn.output) < 0 && request != NULL && cmd_duration > AUTH_WORKER_WARN_DISCONNECTED_LONG_CMD_SECS) { p = i_strchr_to_next(str_c(str), '\t'); p = p == NULL ? "BUG" : t_strcut(p, '\t'); e_warning(client->conn.event, "Auth master disconnected us while handling " "request for %s for %ld secs (result=%s)", request->fields.user, (long)cmd_duration, p); } } static void reply_append_extra_fields(string_t *str, struct auth_request *request) { if (!auth_fields_is_empty(request->fields.extra_fields)) { str_append_c(str, '\t'); /* export only the fields changed by this lookup, so the changed-flag gets preserved correctly on the master side as well. */ auth_fields_append(request->fields.extra_fields, str, AUTH_FIELD_FLAG_CHANGED, AUTH_FIELD_FLAG_CHANGED); } if (request->fields.userdb_reply != NULL && auth_fields_is_empty(request->fields.userdb_reply)) { /* all userdb_* fields had NULL values. we'll still need to tell this to the master */ str_append(str, "\tuserdb_"AUTH_REQUEST_USER_KEY_IGNORE); } } static void verify_plain_callback(enum passdb_result result, struct auth_request *request) { struct auth_worker_command *cmd = request->context; struct auth_worker_client *client = cmd->client; const char *error = NULL; string_t *str; if (request->failed && result == PASSDB_RESULT_OK) result = PASSDB_RESULT_PASSWORD_MISMATCH; str = t_str_new(128); str_printfa(str, "%u\t", request->id); if (result == PASSDB_RESULT_OK) if (auth_fields_exists(request->fields.extra_fields, "noauthenticate")) str_append(str, "NEXT"); else str_append(str, "OK"); else { str_printfa(str, "FAIL\t%d", result); error = passdb_result_to_string(result); } if (result != PASSDB_RESULT_INTERNAL_FAILURE) { str_append_c(str, '\t'); if (request->user_changed_by_lookup) str_append_tabescaped(str, request->fields.user); str_append_c(str, '\t'); if (request->passdb_password != NULL) str_append_tabescaped(str, request->passdb_password); reply_append_extra_fields(str, request); } str_append_c(str, '\n'); auth_worker_send_reply(client, request, str); auth_request_passdb_lookup_end(request, result); auth_worker_request_finished(cmd, error); auth_request_unref(&request); } static bool auth_worker_handle_passv(struct auth_worker_command *cmd, unsigned int id, const char *const *args, const char **error_r) { /* verify plaintext password */ struct auth_request *auth_request; struct auth_passdb *passdb; const char *password; unsigned int passdb_id; /* [] */ if (str_to_uint(args[0], &passdb_id) < 0 || args[1] == NULL) { *error_r = "BUG: Auth worker server sent us invalid PASSV"; return FALSE; } password = args[1]; if (!auth_worker_auth_request_new(cmd, id, args + 2, &auth_request)) { *error_r = "BUG: Auth worker server sent us invalid PASSV"; return FALSE; } auth_request->mech_password = p_strdup(auth_request->pool, password); passdb = auth_request->passdb; while (passdb != NULL && passdb->passdb->id != passdb_id) passdb = passdb->next; if (passdb == NULL) { /* could be a masterdb */ passdb = auth_request_get_auth(auth_request)->masterdbs; while (passdb != NULL && passdb->passdb->id != passdb_id) passdb = passdb->next; if (passdb == NULL) { *error_r = "BUG: PASSV had invalid passdb ID"; auth_request_unref(&auth_request); return FALSE; } } auth_request->passdb = passdb; auth_request_passdb_lookup_begin(auth_request); passdb->passdb->iface. verify_plain(auth_request, password, verify_plain_callback); return TRUE; } static bool auth_worker_handle_passw(struct auth_worker_command *cmd, unsigned int id, const char *const *args, const char **error_r) { struct auth_worker_client *client = cmd->client; struct auth_request *request; string_t *str; const char *password; const char *crypted, *scheme, *error; unsigned int passdb_id; enum passdb_result ret; if (str_to_uint(args[0], &passdb_id) < 0 || args[1] == NULL || args[2] == NULL) { *error_r = "BUG: Auth worker server sent us invalid PASSW"; return FALSE; } password = args[1]; crypted = args[2]; scheme = password_get_scheme(&crypted); if (scheme == NULL) { *error_r = "BUG: Auth worker server sent us invalid PASSW (scheme is NULL)"; return FALSE; } if (!auth_worker_auth_request_new(cmd, id, args + 3, &request)) { *error_r = "BUG: PASSW had missing parameters"; return FALSE; } request->mech_password = p_strdup(request->pool, password); ret = auth_request_password_verify(request, password, crypted, scheme, "cache"); str = t_str_new(128); str_printfa(str, "%u\t", request->id); if (ret == PASSDB_RESULT_OK) { str_printfa(str, "OK\t\t"); error = NULL; } else { str_printfa(str, "FAIL\t%d", ret); error = passdb_result_to_string(ret); } str_append_c(str, '\n'); auth_worker_send_reply(client, request, str); auth_worker_request_finished(cmd, error); auth_request_unref(&request); return TRUE; } static void lookup_credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *request) { struct auth_worker_command *cmd = request->context; struct auth_worker_client *client = cmd->client; string_t *str; if (request->failed && result == PASSDB_RESULT_OK) result = PASSDB_RESULT_PASSWORD_MISMATCH; str = t_str_new(128); str_printfa(str, "%u\t", request->id); if (result != PASSDB_RESULT_OK && result != PASSDB_RESULT_NEXT) str_printfa(str, "FAIL\t%d", result); else { if (result == PASSDB_RESULT_NEXT) str_append(str, "NEXT\t"); else str_append(str, "OK\t"); if (request->user_changed_by_lookup) str_append_tabescaped(str, request->fields.user); str_append_c(str, '\t'); if (request->wanted_credentials_scheme[0] != '\0') { str_printfa(str, "{%s.b64}", request->wanted_credentials_scheme); base64_encode(credentials, size, str); } else { i_assert(size == 0); } reply_append_extra_fields(str, request); } str_append_c(str, '\n'); auth_worker_send_reply(client, request, str); auth_request_passdb_lookup_end(request, result); auth_request_unref(&request); auth_worker_request_finished(cmd, NULL); } static bool auth_worker_handle_passl(struct auth_worker_command *cmd, unsigned int id, const char *const *args, const char **error_r) { /* lookup credentials */ struct auth_request *auth_request; const char *scheme; unsigned int passdb_id; /* [] */ if (str_to_uint(args[0], &passdb_id) < 0 || args[1] == NULL) { *error_r = "BUG: Auth worker server sent us invalid PASSL"; return FALSE; } scheme = args[1]; if (!auth_worker_auth_request_new(cmd, id, args + 2, &auth_request)) { *error_r = "BUG: PASSL had missing parameters"; return FALSE; } auth_request->wanted_credentials_scheme = p_strdup(auth_request->pool, scheme); while (auth_request->passdb->passdb->id != passdb_id) { auth_request->passdb = auth_request->passdb->next; if (auth_request->passdb == NULL) { *error_r = "BUG: PASSL had invalid passdb ID"; auth_request_unref(&auth_request); return FALSE; } } if (auth_request->passdb->passdb->iface.lookup_credentials == NULL) { *error_r = "BUG: PASSL lookup not supported by given passdb"; auth_request_unref(&auth_request); return FALSE; } auth_request->prefer_plain_credentials = TRUE; auth_request_passdb_lookup_begin(auth_request); auth_request->passdb->passdb->iface. lookup_credentials(auth_request, lookup_credentials_callback); return TRUE; } static void set_credentials_callback(bool success, struct auth_request *request) { struct auth_worker_command *cmd = request->context; struct auth_worker_client *client = cmd->client; string_t *str; str = t_str_new(64); str_printfa(str, "%u\t%s\n", request->id, success ? "OK" : "FAIL"); auth_worker_send_reply(client, request, str); auth_worker_request_finished(cmd, success ? NULL : "Failed to set credentials"); auth_request_unref(&request); } static bool auth_worker_handle_setcred(struct auth_worker_command *cmd, unsigned int id, const char *const *args, const char **error_r) { struct auth_request *auth_request; unsigned int passdb_id; const char *creds; /* [] */ if (str_to_uint(args[0], &passdb_id) < 0 || args[1] == NULL) { *error_r = "BUG: Auth worker server sent us invalid SETCRED"; return FALSE; } creds = args[1]; if (!auth_worker_auth_request_new(cmd, id, args + 2, &auth_request)) { *error_r = "BUG: SETCRED had missing parameters"; return FALSE; } while (auth_request->passdb->passdb->id != passdb_id) { auth_request->passdb = auth_request->passdb->next; if (auth_request->passdb == NULL) { *error_r = "BUG: SETCRED had invalid passdb ID"; auth_request_unref(&auth_request); return FALSE; } } auth_request->passdb->passdb->iface. set_credentials(auth_request, creds, set_credentials_callback); return TRUE; } static void lookup_user_callback(enum userdb_result result, struct auth_request *auth_request) { struct auth_worker_command *cmd = auth_request->context; struct auth_worker_client *client = cmd->client; const char *error; string_t *str; str = t_str_new(128); str_printfa(str, "%u\t", auth_request->id); switch (result) { case USERDB_RESULT_INTERNAL_FAILURE: str_append(str, "FAIL\t"); break; case USERDB_RESULT_USER_UNKNOWN: str_append(str, "NOTFOUND\t"); break; case USERDB_RESULT_OK: str_append(str, "OK\t"); if (auth_request->user_changed_by_lookup) str_append_tabescaped(str, auth_request->fields.user); str_append_c(str, '\t'); /* export only the fields changed by this lookup */ auth_fields_append(auth_request->fields.userdb_reply, str, AUTH_FIELD_FLAG_CHANGED, AUTH_FIELD_FLAG_CHANGED); if (auth_request->userdb_lookup_tempfailed) str_append(str, "\ttempfail"); break; } str_append_c(str, '\n'); auth_worker_send_reply(client, auth_request, str); auth_request_userdb_lookup_end(auth_request, result); error = result == USERDB_RESULT_OK ? NULL : userdb_result_to_string(result); auth_worker_request_finished(cmd, error); auth_request_unref(&auth_request); } static struct auth_userdb * auth_userdb_find_by_id(struct auth_userdb *userdbs, unsigned int id) { struct auth_userdb *db; for (db = userdbs; db != NULL; db = db->next) { if (db->userdb->id == id) return db; } return NULL; } static bool auth_worker_handle_user(struct auth_worker_command *cmd, unsigned int id, const char *const *args, const char **error_r) { /* lookup user */ struct auth_request *auth_request; unsigned int userdb_id; /* [] */ if (str_to_uint(args[0], &userdb_id) < 0) { *error_r = "BUG: Auth worker server sent us invalid USER"; return FALSE; } if (!auth_worker_auth_request_new(cmd, id, args + 1, &auth_request)) { *error_r = "BUG: USER had missing parameters"; return FALSE; } auth_request->userdb_lookup = TRUE; auth_request->userdb = auth_userdb_find_by_id(auth_request->userdb, userdb_id); if (auth_request->userdb == NULL) { *error_r = "BUG: USER had invalid userdb ID"; auth_request_unref(&auth_request); return FALSE; } if (auth_request->fields.userdb_reply == NULL) auth_request_init_userdb_reply(auth_request, TRUE); auth_request_userdb_lookup_begin(auth_request); auth_request->userdb->userdb->iface-> lookup(auth_request, lookup_user_callback); return TRUE; } static void auth_worker_client_idle_kill(struct connection *conn ATTR_UNUSED) { auth_worker_client_send_shutdown(); } static void list_iter_deinit(struct auth_worker_list_context *ctx) { struct auth_worker_command *cmd = ctx->cmd; struct auth_worker_client *client = ctx->client; const char *error = NULL; string_t *str; i_assert(client->conn.io == NULL); str = t_str_new(32); if (ctx->auth_request->userdb->userdb->iface-> iterate_deinit(ctx->iter) < 0) { error = "Iteration failed"; str_printfa(str, "%u\tFAIL\n", ctx->auth_request->id); } else str_printfa(str, "%u\tOK\n", ctx->auth_request->id); auth_worker_send_reply(client, NULL, str); connection_input_resume(&client->conn); o_stream_set_flush_callback(client->conn.output, auth_worker_output, client); auth_request_userdb_lookup_end(ctx->auth_request, USERDB_RESULT_OK); auth_worker_request_finished(cmd, error); auth_request_unref(&ctx->auth_request); i_free(ctx); } static void list_iter_callback(const char *user, void *context) { struct auth_worker_list_context *ctx = context; string_t *str; if (user == NULL) { if (ctx->sending) ctx->done = TRUE; else list_iter_deinit(ctx); return; } if (!ctx->sending) o_stream_cork(ctx->client->conn.output); T_BEGIN { str = t_str_new(128); str_printfa(str, "%u\t*\t%s\n", ctx->auth_request->id, user); o_stream_nsend(ctx->client->conn.output, str_data(str), str_len(str)); } T_END; if (ctx->sending) { /* avoid recursively looping to this same function */ ctx->sent = TRUE; return; } do { ctx->sending = TRUE; ctx->sent = FALSE; T_BEGIN { ctx->auth_request->userdb->userdb->iface-> iterate_next(ctx->iter); } T_END; if (o_stream_get_buffer_used_size(ctx->client->conn.output) > OUTBUF_THROTTLE_SIZE) { if (o_stream_flush(ctx->client->conn.output) < 0) { ctx->done = TRUE; break; } } } while (ctx->sent && o_stream_get_buffer_used_size(ctx->client->conn.output) <= OUTBUF_THROTTLE_SIZE); o_stream_uncork(ctx->client->conn.output); ctx->sending = FALSE; if (ctx->done) list_iter_deinit(ctx); else o_stream_set_flush_pending(ctx->client->conn.output, TRUE); } static int auth_worker_list_output(struct auth_worker_list_context *ctx) { int ret; if ((ret = o_stream_flush(ctx->client->conn.output)) < 0) { list_iter_deinit(ctx); return 1; } if (ret > 0) T_BEGIN { ctx->auth_request->userdb->userdb->iface-> iterate_next(ctx->iter); } T_END; return 1; } static bool auth_worker_handle_list(struct auth_worker_command *cmd, unsigned int id, const char *const *args, const char **error_r) { struct auth_worker_client *client = cmd->client; struct auth_worker_list_context *ctx; struct auth_userdb *userdb; unsigned int userdb_id; if (str_to_uint(args[0], &userdb_id) < 0) { *error_r = "BUG: Auth worker server sent us invalid LIST"; return FALSE; } userdb = auth_userdb_find_by_id(client->auth->userdbs, userdb_id); if (userdb == NULL) { *error_r = "BUG: LIST had invalid userdb ID"; return FALSE; } ctx = i_new(struct auth_worker_list_context, 1); ctx->cmd = cmd; ctx->client = client; if (!auth_worker_auth_request_new(cmd, id, args + 1, &ctx->auth_request)) { *error_r = "BUG: LIST had missing parameters"; i_free(ctx); return FALSE; } ctx->auth_request->userdb = userdb; connection_input_halt(&ctx->client->conn); o_stream_set_flush_callback(ctx->client->conn.output, auth_worker_list_output, ctx); ctx->auth_request->userdb_lookup = TRUE; auth_request_userdb_lookup_begin(ctx->auth_request); ctx->iter = ctx->auth_request->userdb->userdb->iface-> iterate_init(ctx->auth_request, list_iter_callback, ctx); ctx->auth_request->userdb->userdb->iface->iterate_next(ctx->iter); return TRUE; } static bool auth_worker_verify_db_hash(const char *passdb_hash, const char *userdb_hash) { string_t *str = t_str_new(MD5_RESULTLEN*2); unsigned char passdb_md5[MD5_RESULTLEN]; unsigned char userdb_md5[MD5_RESULTLEN]; passdbs_generate_md5(passdb_md5); userdbs_generate_md5(userdb_md5); binary_to_hex_append(str, passdb_md5, sizeof(passdb_md5)); if (strcmp(str_c(str), passdb_hash) != 0) return FALSE; str_truncate(str, 0); binary_to_hex_append(str, userdb_md5, sizeof(userdb_md5)); return strcmp(str_c(str), userdb_hash) == 0; }; static int auth_worker_client_handshake_args(struct connection *conn, const char *const *args) { if (!conn->version_received) { if (connection_handshake_args_default(conn, args) < 0) return -1; return 0; } if (str_array_length(args) < 3 || strcmp(args[0], "DBHASH") != 0) { e_error(conn->event, "BUG: Invalid input: %s", t_strarray_join(args, "\t")); return -1; } if (!auth_worker_verify_db_hash(args[1], args[2])) { e_error(conn->event, "Auth worker sees different passdbs/userdbs " "than auth server. Maybe config just changed " "and this goes away automatically?"); return -1; } return 1; } static int auth_worker_client_input_args(struct connection *conn, const char *const *args) { unsigned int id; bool ret = FALSE; const char *error = NULL; struct auth_worker_command *cmd; struct auth_worker_client *client = container_of(conn, struct auth_worker_client, conn); if (str_array_length(args) < 3 || str_to_uint(args[0], &id) < 0) { e_error(conn->event, "BUG: Invalid input: %s", t_strarray_join(args, "\t")); return -1; } io_loop_time_refresh(); cmd = i_new(struct auth_worker_command, 1); cmd->client = client; cmd->event = event_create(client->conn.event); event_add_category(cmd->event, &event_category_auth); event_add_str(cmd->event, "command", args[1]); event_add_int(cmd->event, "command_id", id); event_set_append_log_prefix(cmd->event, t_strdup_printf("auth-worker<%u>: ", id)); client->cmd_start = ioloop_time; client->refcount++; e_debug(cmd->event, "Handling %s request", args[1]); /* Check if we have reached service_count */ if (auth_worker_max_service_count > 0) { auth_worker_service_count++; if (auth_worker_service_count >= auth_worker_max_service_count) worker_restart_request = TRUE; } auth_worker_refresh_proctitle(args[1]); if (strcmp(args[1], "PASSV") == 0) ret = auth_worker_handle_passv(cmd, id, args + 2, &error); else if (strcmp(args[1], "PASSL") == 0) ret = auth_worker_handle_passl(cmd, id, args + 2, &error); else if (strcmp(args[1], "PASSW") == 0) ret = auth_worker_handle_passw(cmd, id, args + 2, &error); else if (strcmp(args[1], "SETCRED") == 0) ret = auth_worker_handle_setcred(cmd, id, args + 2, &error); else if (strcmp(args[1], "USER") == 0) ret = auth_worker_handle_user(cmd, id, args + 2, &error); else if (strcmp(args[1], "LIST") == 0) ret = auth_worker_handle_list(cmd, id, args + 2, &error); else { error = t_strdup_printf("BUG: Auth-worker received unknown command: %s", args[1]); } i_assert(ret || error != NULL); if (!ret) { auth_worker_request_finished_bug(cmd, error); return -1; } auth_worker_client_unref(&client); return 1; } static int auth_worker_output(struct auth_worker_client *client) { if (o_stream_flush(client->conn.output) < 0) { auth_worker_client_destroy(&client->conn); return 1; } if (o_stream_get_buffer_used_size(client->conn.output) <= OUTBUF_THROTTLE_SIZE/3 && client->conn.io == NULL) { /* allow input again */ connection_input_resume(&client->conn); } return 1; } static void auth_worker_client_unref(struct auth_worker_client **_client) { struct auth_worker_client *client = *_client; if (client == NULL) return; if (--client->refcount > 0) return; /* the connection should've been destroyed before getting here */ i_assert(client->destroyed); connection_deinit(&client->conn); i_free(client); } static void auth_worker_client_destroy(struct connection *conn) { struct auth_worker_client *client = container_of(conn, struct auth_worker_client, conn); i_assert(!client->destroyed); client->destroyed = TRUE; connection_input_halt(conn); i_stream_close(conn->input); o_stream_close(conn->output); net_disconnect(conn->fd_in); conn->fd_out = conn->fd_in = -1; auth_worker_client_unref(&client); master_service_client_connection_destroyed(master_service); } static const struct connection_vfuncs auth_worker_client_v = { .input_args = auth_worker_client_input_args, .handshake_args = auth_worker_client_handshake_args, .destroy = auth_worker_client_destroy, .idle_timeout = auth_worker_client_idle_kill, }; static const struct connection_settings auth_worker_client_set = { .service_name_in = "auth-worker", .service_name_out = "auth-worker", .major_version = AUTH_WORKER_PROTOCOL_MAJOR_VERSION, .minor_version = AUTH_WORKER_PROTOCOL_MINOR_VERSION, .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, /* we use throttling */ }; struct auth_worker_client * auth_worker_client_create(struct auth *auth, const struct master_service_connection *master_conn) { struct auth_worker_client *client; if (clients == NULL) clients = connection_list_init(&auth_worker_client_set, &auth_worker_client_v); client = i_new(struct auth_worker_client, 1); client->refcount = 1; client->auth = auth; client->conn.event_parent = auth_event; client->conn.input_idle_timeout_secs = master_service_get_idle_kill_secs(master_service); connection_init_server(clients, &client->conn, master_conn->name, master_conn->fd, master_conn->fd); auth_worker_refresh_proctitle(CLIENT_STATE_HANDSHAKE); if (auth_worker_client_error) auth_worker_client_send_error(); return client; } void auth_worker_client_send_error(void) { struct auth_worker_client *auth_worker_client = auth_worker_get_client(); auth_worker_client_error = TRUE; if (auth_worker_client != NULL && !auth_worker_client->error_sent) { o_stream_nsend_str(auth_worker_client->conn.output, "ERROR\n"); auth_worker_client->error_sent = TRUE; } auth_worker_refresh_proctitle(""); } void auth_worker_client_send_success(void) { struct auth_worker_client *auth_worker_client = auth_worker_get_client(); auth_worker_client_error = FALSE; if (auth_worker_client == NULL) return; if (auth_worker_client->error_sent) { o_stream_nsend_str(auth_worker_client->conn.output, "SUCCESS\n"); auth_worker_client->error_sent = FALSE; } if (auth_worker_client->conn.io != NULL) auth_worker_refresh_proctitle(CLIENT_STATE_IDLE); } void auth_worker_client_send_shutdown(void) { struct auth_worker_client *auth_worker_client = auth_worker_get_client(); if (auth_worker_client != NULL) o_stream_nsend_str(auth_worker_client->conn.output, "SHUTDOWN\n"); auth_worker_refresh_proctitle(CLIENT_STATE_STOP); } void auth_worker_connections_destroy_all(void) { if (clients == NULL) return; while (clients->connections != NULL) connection_deinit(clients->connections); connection_list_deinit(&clients); } bool auth_worker_has_client(void) { return clients != NULL && clients->connections_count > 0; } dovecot-2.3.21.1/src/auth/password-scheme-pbkdf2.c0000644000000000000000000000461314656633576016467 00000000000000/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "str.h" #include "password-scheme.h" #include "hex-binary.h" #include "hash-method.h" #include "pkcs5.h" #define PBKDF2_KEY_SIZE_SHA1 20 #define PBKDF2_GENERATE_SALT_LEN 16 #define PBKDF2_ROUNDS_DEFAULT 5000 static void pbkdf_run(const char *plaintext, const char *salt, unsigned int rounds, unsigned char key_r[PBKDF2_KEY_SIZE_SHA1]) { memset(key_r, 0, PBKDF2_KEY_SIZE_SHA1); buffer_t buf; buffer_create_from_data(&buf, key_r, PBKDF2_KEY_SIZE_SHA1); pkcs5_pbkdf(PKCS5_PBKDF2, hash_method_lookup("sha1"), (const unsigned char *)plaintext, strlen(plaintext), (const unsigned char *)salt, strlen(salt), rounds, PBKDF2_KEY_SIZE_SHA1, &buf); } void pbkdf2_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char **raw_password_r, size_t *size_r) { unsigned char key[PBKDF2_KEY_SIZE_SHA1]; const char *salt; string_t *str = t_str_new(64); unsigned int rounds = params->rounds; if (rounds == 0) rounds = PBKDF2_ROUNDS_DEFAULT; salt = password_generate_salt(PBKDF2_GENERATE_SALT_LEN); pbkdf_run(plaintext, salt, rounds, key); str_printfa(str, "$1$%s$%u$", salt, rounds); binary_to_hex_append(str, key, sizeof(key)); *raw_password_r = str_data(str); *size_r = str_len(str); } int pbkdf2_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED, const unsigned char *raw_password, size_t size, const char **error_r) { const char *const *fields; const char *salt; unsigned int rounds; unsigned char key1[PBKDF2_KEY_SIZE_SHA1], key2[PBKDF2_KEY_SIZE_SHA1]; buffer_t buf; /* $1$salt$rounds$hash */ if (size < 3 || memcmp(raw_password, "$1$", 3) != 0) { *error_r = "Invalid PBKDF2 passdb entry prefix"; return -1; } fields = t_strsplit(t_strndup(raw_password + 3, size - 3), "$"); salt = fields[0]; if (str_array_length(fields) != 3 || str_to_uint(fields[1], &rounds) < 0) { *error_r = "Invalid PBKDF2 passdb entry format"; return -1; } buffer_create_from_data(&buf, key1, sizeof(key1)); if (strlen(fields[2]) != sizeof(key1)*2 || hex_to_binary(fields[2], &buf) < 0) { *error_r = "PBKDF2 hash not 160bit hex-encoded"; return -1; } pbkdf_run(plaintext, salt, rounds, key2); return mem_equals_timing_safe(key1, key2, sizeof(key1)) ? 1 : 0; } dovecot-2.3.21.1/src/auth/mech-gssapi.c0000644000000000000000000005170414656633576014420 00000000000000/* * GSSAPI Module * * Copyright (c) 2005 Jelmer Vernooij * * Related standards: * - draft-ietf-sasl-gssapi-03 * - RFC2222 * * Some parts inspired by an older patch from Colin Walters * * This software is released under the MIT license. */ #include "auth-common.h" #include "env-util.h" #include "str.h" #include "str-sanitize.h" #include "hex-binary.h" #include "safe-memset.h" #include "mech.h" #include "passdb.h" #if defined(BUILTIN_GSSAPI) || defined(PLUGIN_BUILD) #ifndef HAVE___GSS_USEROK # define USE_KRB5_USEROK # include #endif #ifdef HAVE_GSSAPI_GSSAPI_H # include #elif defined (HAVE_GSSAPI_H) # include #endif #ifdef HAVE_GSSAPI_GSSAPI_KRB5_H # include #elif defined (HAVE_GSSAPI_KRB5_H) # include #else # undef USE_KRB5_USEROK #endif #ifdef HAVE_GSSAPI_GSSAPI_EXT_H # include #endif #define krb5_boolean2bool(X) ((X) != 0) /* Non-zero flags defined in RFC 2222 */ enum sasl_gssapi_qop { SASL_GSSAPI_QOP_UNSPECIFIED = 0x00, SASL_GSSAPI_QOP_AUTH_ONLY = 0x01, SASL_GSSAPI_QOP_AUTH_INT = 0x02, SASL_GSSAPI_QOP_AUTH_CONF = 0x04 }; struct gssapi_auth_request { struct auth_request auth_request; gss_ctx_id_t gss_ctx; gss_cred_id_t service_cred; enum { GSS_STATE_SEC_CONTEXT, GSS_STATE_WRAP, GSS_STATE_UNWRAP } sasl_gssapi_state; gss_name_t authn_name; gss_name_t authz_name; pool_t pool; }; static bool gssapi_initialized = FALSE; static gss_OID_desc mech_gssapi_krb5_oid = { 9, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; static int mech_gssapi_wrap(struct gssapi_auth_request *request, gss_buffer_desc inbuf); static void mech_gssapi_log_error(struct auth_request *request, OM_uint32 status_value, int status_type, const char *description) { OM_uint32 message_context = 0; OM_uint32 minor_status; gss_buffer_desc status_string; do { (void)gss_display_status(&minor_status, status_value, status_type, GSS_C_NO_OID, &message_context, &status_string); e_info(request->mech_event, "While %s: %s", description, str_sanitize(status_string.value, SIZE_MAX)); (void)gss_release_buffer(&minor_status, &status_string); } while (message_context != 0); } static void mech_gssapi_initialize(const struct auth_settings *set) { const char *path = set->krb5_keytab; if (*path != '\0') { /* environment may be used by Kerberos 5 library directly */ env_put("KRB5_KTNAME", path); #ifdef HAVE_GSSKRB5_REGISTER_ACCEPTOR_IDENTITY gsskrb5_register_acceptor_identity(path); #elif defined (HAVE_KRB5_GSS_REGISTER_ACCEPTOR_IDENTITY) krb5_gss_register_acceptor_identity(path); #endif } } static struct auth_request *mech_gssapi_auth_new(void) { struct gssapi_auth_request *request; pool_t pool; pool = pool_alloconly_create(MEMPOOL_GROWING"gssapi_auth_request", 2048); request = p_new(pool, struct gssapi_auth_request, 1); request->pool = pool; request->gss_ctx = GSS_C_NO_CONTEXT; request->auth_request.pool = pool; return &request->auth_request; } static OM_uint32 obtain_service_credentials(struct auth_request *request, gss_cred_id_t *ret_r) { OM_uint32 major_status, minor_status; string_t *principal_name; gss_buffer_desc inbuf; gss_name_t gss_principal; const char *service_name; if (!gssapi_initialized) { gssapi_initialized = TRUE; mech_gssapi_initialize(request->set); } if (strcmp(request->set->gssapi_hostname, "$ALL") == 0) { e_debug(request->mech_event, "Using all keytab entries"); *ret_r = GSS_C_NO_CREDENTIAL; return GSS_S_COMPLETE; } if (strcasecmp(request->fields.service, "POP3") == 0) { /* The standard POP3 service name with GSSAPI is called just "pop". */ service_name = "pop"; } else { service_name = t_str_lcase(request->fields.service); } principal_name = t_str_new(128); str_append(principal_name, service_name); str_append_c(principal_name, '@'); str_append(principal_name, request->set->gssapi_hostname); e_debug(request->mech_event, "Obtaining credentials for %s", str_c(principal_name)); inbuf.length = str_len(principal_name); inbuf.value = str_c_modifiable(principal_name); major_status = gss_import_name(&minor_status, &inbuf, GSS_C_NT_HOSTBASED_SERVICE, &gss_principal); str_free(&principal_name); if (GSS_ERROR(major_status) != 0) { mech_gssapi_log_error(request, major_status, GSS_C_GSS_CODE, "importing principal name"); return major_status; } major_status = gss_acquire_cred(&minor_status, gss_principal, 0, GSS_C_NULL_OID_SET, GSS_C_ACCEPT, ret_r, NULL, NULL); if (GSS_ERROR(major_status) != 0) { mech_gssapi_log_error(request, major_status, GSS_C_GSS_CODE, "acquiring service credentials"); mech_gssapi_log_error(request, minor_status, GSS_C_MECH_CODE, "acquiring service credentials"); return major_status; } gss_release_name(&minor_status, &gss_principal); return major_status; } static gss_name_t import_name(struct auth_request *request, void *str, size_t len) { OM_uint32 major_status, minor_status; gss_buffer_desc name_buf; gss_name_t name; name_buf.value = str; name_buf.length = len; major_status = gss_import_name(&minor_status, &name_buf, GSS_C_NO_OID, &name); if (GSS_ERROR(major_status) != 0) { mech_gssapi_log_error(request, major_status, GSS_C_GSS_CODE, "gss_import_name"); return GSS_C_NO_NAME; } return name; } static gss_name_t duplicate_name(struct auth_request *request, gss_name_t old) { OM_uint32 major_status, minor_status; gss_name_t new; major_status = gss_duplicate_name(&minor_status, old, &new); if (GSS_ERROR(major_status) != 0) { mech_gssapi_log_error(request, major_status, GSS_C_GSS_CODE, "gss_duplicate_name"); return GSS_C_NO_NAME; } return new; } static bool data_has_nuls(const void *data, size_t len) { const unsigned char *c = data; size_t i; /* apparently all names end with NUL? */ if (len > 0 && c[len-1] == '\0') len--; for (i = 0; i < len; i++) { if (c[i] == '\0') return TRUE; } return FALSE; } static int get_display_name(struct auth_request *auth_request, gss_name_t name, gss_OID *name_type_r, const char **display_name_r) { OM_uint32 major_status, minor_status; gss_buffer_desc buf; major_status = gss_display_name(&minor_status, name, &buf, name_type_r); if (major_status != GSS_S_COMPLETE) { mech_gssapi_log_error(auth_request, major_status, GSS_C_GSS_CODE, "gss_display_name"); return -1; } if (data_has_nuls(buf.value, buf.length)) { e_info(auth_request->mech_event, "authn_name has NULs"); return -1; } *display_name_r = t_strndup(buf.value, buf.length); (void)gss_release_buffer(&minor_status, &buf); return 0; } static bool mech_gssapi_oid_cmp(const gss_OID_desc *oid1, const gss_OID_desc *oid2) { return oid1->length == oid2->length && mem_equals_timing_safe(oid1->elements, oid2->elements, oid1->length); } static int mech_gssapi_sec_context(struct gssapi_auth_request *request, gss_buffer_desc inbuf) { struct auth_request *auth_request = &request->auth_request; OM_uint32 major_status, minor_status; gss_buffer_desc output_token; gss_OID name_type; gss_OID mech_type; const char *username, *error; int ret = 0; major_status = gss_accept_sec_context ( &minor_status, &request->gss_ctx, request->service_cred, &inbuf, GSS_C_NO_CHANNEL_BINDINGS, &request->authn_name, &mech_type, &output_token, NULL, /* ret_flags */ NULL, /* time_rec */ NULL /* delegated_cred_handle */ ); if (GSS_ERROR(major_status) != 0) { mech_gssapi_log_error(auth_request, major_status, GSS_C_GSS_CODE, "processing incoming data"); mech_gssapi_log_error(auth_request, minor_status, GSS_C_MECH_CODE, "processing incoming data"); return -1; } switch (major_status) { case GSS_S_COMPLETE: if (!mech_gssapi_oid_cmp(mech_type, &mech_gssapi_krb5_oid)) { e_info(auth_request->mech_event, "GSSAPI mechanism not Kerberos5"); ret = -1; } else if (get_display_name(auth_request, request->authn_name, &name_type, &username) < 0) ret = -1; else if (!auth_request_set_username(auth_request, username, &error)) { e_info(auth_request->mech_event, "authn_name: %s", error); ret = -1; } else { request->sasl_gssapi_state = GSS_STATE_WRAP; e_debug(auth_request->mech_event, "security context state completed."); } break; case GSS_S_CONTINUE_NEEDED: e_debug(auth_request->mech_event, "Processed incoming packet correctly, " "waiting for another."); break; default: e_error(auth_request->mech_event, "Received unexpected major status %d", major_status); break; } if (ret == 0) { if (output_token.length > 0) { auth_request_handler_reply_continue(auth_request, output_token.value, output_token.length); } else { /* If there is no output token, go straight to wrap, which is expecting an empty input token. */ ret = mech_gssapi_wrap(request, output_token); } } (void)gss_release_buffer(&minor_status, &output_token); return ret; } static int mech_gssapi_wrap(struct gssapi_auth_request *request, gss_buffer_desc inbuf) { OM_uint32 major_status, minor_status; gss_buffer_desc outbuf; unsigned char ret[4]; /* The client's return data should be empty here */ /* Only authentication, no integrity or confidentiality protection (yet?) */ ret[0] = (SASL_GSSAPI_QOP_UNSPECIFIED | SASL_GSSAPI_QOP_AUTH_ONLY); ret[1] = 0xFF; ret[2] = 0xFF; ret[3] = 0xFF; inbuf.length = 4; inbuf.value = ret; major_status = gss_wrap(&minor_status, request->gss_ctx, 0, GSS_C_QOP_DEFAULT, &inbuf, NULL, &outbuf); if (GSS_ERROR(major_status) != 0) { mech_gssapi_log_error(&request->auth_request, major_status, GSS_C_GSS_CODE, "sending security layer negotiation"); mech_gssapi_log_error(&request->auth_request, minor_status, GSS_C_MECH_CODE, "sending security layer negotiation"); return -1; } e_debug(request->auth_request.mech_event, "Negotiated security layer"); auth_request_handler_reply_continue(&request->auth_request, outbuf.value, outbuf.length); (void)gss_release_buffer(&minor_status, &outbuf); request->sasl_gssapi_state = GSS_STATE_UNWRAP; return 0; } #ifdef USE_KRB5_USEROK static bool k5_principal_is_authorized(struct auth_request *request, const char *name) { const char *value, *const *authorized_names, *const *tmp; value = auth_fields_find(request->fields.extra_fields, "k5principals"); if (value == NULL) return FALSE; authorized_names = t_strsplit_spaces(value, ","); for (tmp = authorized_names; *tmp != NULL; tmp++) { if (strcmp(*tmp, name) == 0) { e_debug(request->mech_event, "authorized by k5principals field: %s", name); return TRUE; } } return FALSE; } static bool mech_gssapi_krb5_userok(struct gssapi_auth_request *request, gss_name_t name, const char *login_user, bool check_name_type) { krb5_context ctx; krb5_principal princ; krb5_error_code krb5_err; gss_OID name_type; const char *princ_display_name; bool authorized = FALSE; /* Parse out the principal's username */ if (get_display_name(&request->auth_request, name, &name_type, &princ_display_name) < 0) return FALSE; if (!mech_gssapi_oid_cmp(name_type, GSS_KRB5_NT_PRINCIPAL_NAME) && check_name_type) { e_info(request->auth_request.mech_event, "OID not kerberos principal name"); return FALSE; } /* Init a krb5 context and parse the principal username */ krb5_err = krb5_init_context(&ctx); if (krb5_err != 0) { e_error(request->auth_request.mech_event, "krb5_init_context() failed: %d", (int)krb5_err); return FALSE; } krb5_err = krb5_parse_name(ctx, princ_display_name, &princ); if (krb5_err != 0) { /* writing the error string would be better, but we probably rarely get here and there doesn't seem to be a standard way of getting it */ e_info(request->auth_request.mech_event, "krb5_parse_name() failed: %d", (int)krb5_err); } else { /* See if the principal is in the list of authorized * principals for the user */ authorized = k5_principal_is_authorized(&request->auth_request, princ_display_name); /* See if the principal is authorized to act as the specified (UNIX) user */ if (!authorized) { authorized = krb5_boolean2bool(krb5_kuserok(ctx, princ, login_user)); } krb5_free_principal(ctx, princ); } krb5_free_context(ctx); return authorized; } #endif static int mech_gssapi_userok(struct gssapi_auth_request *request, const char *login_user) { struct auth_request *auth_request = &request->auth_request; OM_uint32 major_status, minor_status; int equal_authn_authz; #ifdef HAVE___GSS_USEROK int login_ok; #endif /* if authn and authz names equal, don't bother checking further. */ major_status = gss_compare_name(&minor_status, request->authn_name, request->authz_name, &equal_authn_authz); if (GSS_ERROR(major_status) != 0) { mech_gssapi_log_error(auth_request, major_status, GSS_C_GSS_CODE, "gss_compare_name failed"); return -1; } if (equal_authn_authz != 0) return 0; /* handle cross-realm authentication */ #ifdef HAVE___GSS_USEROK /* Solaris */ major_status = __gss_userok(&minor_status, request->authn_name, login_user, &login_ok); if (GSS_ERROR(major_status) != 0) { mech_gssapi_log_error(auth_request, major_status, GSS_C_GSS_CODE, "__gss_userok failed"); return -1; } if (login_ok == 0) { e_info(auth_request->mech_event, "User not authorized to log in as %s", login_user); return -1; } return 0; #elif defined(USE_KRB5_USEROK) if (!mech_gssapi_krb5_userok(request, request->authn_name, login_user, TRUE)) { e_info(auth_request->mech_event, "User not authorized to log in as %s", login_user); return -1; } return 0; #else e_info(auth_request->mech_event, "Cross-realm authentication not supported " "(authn_name=%s, authz_name=%s)", request->auth_request.fields.original_username, login_user); return -1; #endif } static void gssapi_credentials_callback(enum passdb_result result, const unsigned char *credentials ATTR_UNUSED, size_t size ATTR_UNUSED, struct auth_request *request) { struct gssapi_auth_request *gssapi_request = (struct gssapi_auth_request *)request; /* We don't care much whether the lookup succeeded or not because GSSAPI * does not strictly require a passdb. But if a passdb is configured, * now the k5principals field will have been filled in. */ switch (result) { case PASSDB_RESULT_INTERNAL_FAILURE: auth_request_internal_failure(request); return; case PASSDB_RESULT_USER_DISABLED: case PASSDB_RESULT_PASS_EXPIRED: /* user is explicitly disabled, don't allow it to log in */ auth_request_fail(request); return; case PASSDB_RESULT_NEXT: case PASSDB_RESULT_SCHEME_NOT_AVAILABLE: case PASSDB_RESULT_USER_UNKNOWN: case PASSDB_RESULT_PASSWORD_MISMATCH: case PASSDB_RESULT_OK: break; } if (mech_gssapi_userok(gssapi_request, request->fields.user) == 0) auth_request_success(request, NULL, 0); else auth_request_fail(request); } static int mech_gssapi_unwrap(struct gssapi_auth_request *request, gss_buffer_desc inbuf) { struct auth_request *auth_request = &request->auth_request; OM_uint32 major_status, minor_status; gss_buffer_desc outbuf; const char *login_user, *error; unsigned char *name; size_t name_len; major_status = gss_unwrap(&minor_status, request->gss_ctx, &inbuf, &outbuf, NULL, NULL); if (GSS_ERROR(major_status) != 0) { mech_gssapi_log_error(auth_request, major_status, GSS_C_GSS_CODE, "final negotiation: gss_unwrap"); return -1; } /* outbuf[0] contains bitmask for selected security layer, outbuf[1..3] contains maximum output_message size */ if (outbuf.length < 4) { e_error(auth_request->mech_event, "Invalid response length"); (void)gss_release_buffer(&minor_status, &outbuf); return -1; } if (outbuf.length > 4) { name = (unsigned char *)outbuf.value + 4; name_len = outbuf.length - 4; if (data_has_nuls(name, name_len)) { e_info(auth_request->mech_event, "authz_name has NULs"); (void)gss_release_buffer(&minor_status, &outbuf); return -1; } login_user = p_strndup(auth_request->pool, name, name_len); request->authz_name = import_name(auth_request, name, name_len); } else { request->authz_name = duplicate_name(auth_request, request->authn_name); if (get_display_name(auth_request, request->authz_name, NULL, &login_user) < 0) { (void)gss_release_buffer(&minor_status, &outbuf); return -1; } } if (request->authz_name == GSS_C_NO_NAME) { e_info(auth_request->mech_event, "no authz_name"); (void)gss_release_buffer(&minor_status, &outbuf); return -1; } /* Set username early, so that the credential lookup is for the * authorizing user. This means the username in subsequent log * messages will be the authorization name, not the authentication * name, which may mean that future log messages should be adjusted * to log the right thing. */ if (!auth_request_set_username(auth_request, login_user, &error)) { e_info(auth_request->mech_event, "authz_name: %s", error); (void)gss_release_buffer(&minor_status, &outbuf); return -1; } /* Continue in callback once auth_request is populated with passdb information. */ auth_request->passdb_success = TRUE; /* default to success */ auth_request_lookup_credentials(&request->auth_request, "", gssapi_credentials_callback); (void)gss_release_buffer(&minor_status, &outbuf); return 0; } static void mech_gssapi_auth_continue(struct auth_request *request, const unsigned char *data, size_t data_size) { struct gssapi_auth_request *gssapi_request = (struct gssapi_auth_request *)request; gss_buffer_desc inbuf; int ret = -1; inbuf.value = (void *)data; inbuf.length = data_size; switch (gssapi_request->sasl_gssapi_state) { case GSS_STATE_SEC_CONTEXT: ret = mech_gssapi_sec_context(gssapi_request, inbuf); break; case GSS_STATE_WRAP: ret = mech_gssapi_wrap(gssapi_request, inbuf); break; case GSS_STATE_UNWRAP: ret = mech_gssapi_unwrap(gssapi_request, inbuf); break; default: i_unreached(); } if (ret < 0) auth_request_fail(request); } static void mech_gssapi_auth_initial(struct auth_request *request, const unsigned char *data, size_t data_size) { struct gssapi_auth_request *gssapi_request = (struct gssapi_auth_request *)request; OM_uint32 major_status; major_status = obtain_service_credentials(request, &gssapi_request->service_cred); if (GSS_ERROR(major_status) != 0) { auth_request_internal_failure(request); return; } gssapi_request->authn_name = GSS_C_NO_NAME; gssapi_request->authz_name = GSS_C_NO_NAME; gssapi_request->sasl_gssapi_state = GSS_STATE_SEC_CONTEXT; if (data_size == 0) { /* The client should go first */ auth_request_handler_reply_continue(request, NULL, 0); } else { mech_gssapi_auth_continue(request, data, data_size); } } static void mech_gssapi_auth_free(struct auth_request *request) { struct gssapi_auth_request *gssapi_request = (struct gssapi_auth_request *)request; OM_uint32 minor_status; if (gssapi_request->gss_ctx != GSS_C_NO_CONTEXT) { (void)gss_delete_sec_context(&minor_status, &gssapi_request->gss_ctx, GSS_C_NO_BUFFER); } (void)gss_release_cred(&minor_status, &gssapi_request->service_cred); if (gssapi_request->authn_name != GSS_C_NO_NAME) { (void)gss_release_name(&minor_status, &gssapi_request->authn_name); } if (gssapi_request->authz_name != GSS_C_NO_NAME) { (void)gss_release_name(&minor_status, &gssapi_request->authz_name); } pool_unref(&request->pool); } const struct mech_module mech_gssapi = { "GSSAPI", .flags = MECH_SEC_ALLOW_NULS, .passdb_need = MECH_PASSDB_NEED_NOTHING, mech_gssapi_auth_new, mech_gssapi_auth_initial, mech_gssapi_auth_continue, mech_gssapi_auth_free }; /* MTI Kerberos v1.5+ and Heimdal v0.7+ supports SPNEGO for Kerberos tickets internally. Nothing else needs to be done here. Note however that this does not support SPNEGO when the only available credential is NTLM.. */ const struct mech_module mech_gssapi_spnego = { "GSS-SPNEGO", .flags = MECH_SEC_ALLOW_NULS, .passdb_need = MECH_PASSDB_NEED_NOTHING, mech_gssapi_auth_new, mech_gssapi_auth_initial, mech_gssapi_auth_continue, mech_gssapi_auth_free }; #ifndef BUILTIN_GSSAPI void mech_gssapi_init(void); void mech_gssapi_deinit(void); void mech_gssapi_init(void) { mech_register_module(&mech_gssapi); #ifdef HAVE_GSSAPI_SPNEGO /* load if we already didn't load it using winbind */ if (mech_module_find(mech_gssapi_spnego.mech_name) == NULL) mech_register_module(&mech_gssapi_spnego); #endif } void mech_gssapi_deinit(void) { #ifdef HAVE_GSSAPI_SPNEGO const struct mech_module *mech; mech = mech_module_find(mech_gssapi_spnego.mech_name); if (mech != NULL && mech->auth_new == mech_gssapi_auth_new) mech_unregister_module(&mech_gssapi_spnego); #endif mech_unregister_module(&mech_gssapi); } #endif #endif dovecot-2.3.21.1/src/auth/auth-worker-client.h0000644000000000000000000000157014656633576015745 00000000000000#ifndef AUTH_WORKER_CLIENT_H #define AUTH_WORKER_CLIENT_H #define AUTH_WORKER_PROTOCOL_MAJOR_VERSION 1 #define AUTH_WORKER_PROTOCOL_MINOR_VERSION 0 #define AUTH_WORKER_MAX_LINE_LENGTH 8192 struct master_service_connection; struct auth_worker_command; struct auth_worker_client * auth_worker_client_create(struct auth *auth, const struct master_service_connection *master_conn); bool auth_worker_auth_request_new(struct auth_worker_command *cmd, unsigned int id, const char *const *args, struct auth_request **request_r); bool auth_worker_has_client(void); void auth_worker_client_send_error(void); void auth_worker_client_send_success(void); void auth_worker_client_send_shutdown(void); void auth_worker_connections_destroy_all(void); /* Stop master service after this many requests. 0 is unlimited. */ void auth_worker_set_max_service_count(unsigned int count); #endif dovecot-2.3.21.1/src/auth/mech-plain.c0000644000000000000000000000442714656633576014235 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "safe-memset.h" #include "mech.h" #include "passdb.h" #include "mech-plain-common.h" static void mech_plain_auth_continue(struct auth_request *request, const unsigned char *data, size_t data_size) { const char *authid, *authenid, *error; char *pass; size_t i, len; int count; /* authorization ID \0 authentication ID \0 pass. */ authid = (const char *) data; authenid = NULL; pass = NULL; count = 0; for (i = 0; i < data_size; i++) { if (data[i] == '\0') { if (++count == 1) authenid = (const char *) data + i+1; else if (count == 2) { i++; len = data_size - i; pass = p_strndup(unsafe_data_stack_pool, data+i, len); } else break; } } if (count == 2 && authenid != NULL && strcmp(authid, authenid) == 0) { /* the login username isn't different */ authid = ""; } if (count != 2) { /* invalid input */ e_info(request->mech_event, "invalid input"); auth_request_fail(request); } else if (!auth_request_set_username(request, authenid, &error)) { /* invalid username */ e_info(request->mech_event, "%s", error); auth_request_fail(request); } else if (*authid != '\0' && !auth_request_set_login_username(request, authid, &error)) { /* invalid login username */ e_info(request->mech_event, "login user: %s", error); auth_request_fail(request); } else { auth_request_verify_plain(request, pass, plain_verify_callback); } /* make sure it's cleared */ if (pass != NULL) safe_memset(pass, 0, strlen(pass)); } static struct auth_request *mech_plain_auth_new(void) { struct auth_request *request; pool_t pool; pool = pool_alloconly_create(MEMPOOL_GROWING"plain_auth_request", 2048); request = p_new(pool, struct auth_request, 1); request->pool = pool; return request; } const struct mech_module mech_plain = { "PLAIN", .flags = MECH_SEC_PLAINTEXT | MECH_SEC_ALLOW_NULS, .passdb_need = MECH_PASSDB_NEED_VERIFY_PLAIN, mech_plain_auth_new, mech_generic_auth_initial, mech_plain_auth_continue, mech_generic_auth_free }; dovecot-2.3.21.1/src/auth/userdb.h0000644000000000000000000000504514656633576013506 00000000000000#ifndef USERDB_H #define USERDB_H #include "md5.h" #include "auth-fields.h" struct auth; struct auth_request; struct auth_userdb_settings; enum userdb_result { USERDB_RESULT_INTERNAL_FAILURE = -1, USERDB_RESULT_USER_UNKNOWN = -2, USERDB_RESULT_OK = 1 }; typedef void userdb_callback_t(enum userdb_result result, struct auth_request *request); /* user=NULL when there are no more users */ typedef void userdb_iter_callback_t(const char *user, void *context); struct userdb_module { const char *args; /* The default caching key for this module, or NULL if caching isn't wanted. This is updated by settings in auth_userdb. */ const char *default_cache_key; /* If blocking is set to TRUE, use child processes to access this userdb. */ bool blocking; /* id is used by blocking userdb to identify the userdb */ unsigned int id; /* number of time init() has been called */ int init_refcount; /* WARNING: avoid adding anything here that isn't based on args. if you do, you need to change userdb.c:userdb_find() also to avoid accidentally merging wrong userdbs. */ const struct userdb_module_interface *iface; }; struct userdb_iterate_context { struct auth_request *auth_request; userdb_iter_callback_t *callback; void *context; bool failed; }; struct userdb_module_interface { const char *name; struct userdb_module *(*preinit)(pool_t pool, const char *args); void (*init)(struct userdb_module *module); void (*deinit)(struct userdb_module *module); void (*lookup)(struct auth_request *auth_request, userdb_callback_t *callback); struct userdb_iterate_context * (*iterate_init)(struct auth_request *auth_request, userdb_iter_callback_t *callback, void *context); void (*iterate_next)(struct userdb_iterate_context *ctx); int (*iterate_deinit)(struct userdb_iterate_context *ctx); }; const char *userdb_result_to_string(enum userdb_result result); uid_t userdb_parse_uid(struct auth_request *request, const char *str) ATTR_NULL(1); gid_t userdb_parse_gid(struct auth_request *request, const char *str) ATTR_NULL(1); struct userdb_module * userdb_preinit(pool_t pool, const struct auth_userdb_settings *set); void userdb_init(struct userdb_module *userdb); void userdb_deinit(struct userdb_module *userdb); void userdb_register_module(struct userdb_module_interface *iface); void userdb_unregister_module(struct userdb_module_interface *iface); void userdbs_generate_md5(unsigned char md5[STATIC_ARRAY MD5_RESULTLEN]); void userdbs_init(void); void userdbs_deinit(void); #include "auth-request.h" #endif dovecot-2.3.21.1/src/auth/crypt-blowfish.h0000644000000000000000000000211014656633576015164 00000000000000/* * Written by Solar Designer in 2000-2011. * No copyright is claimed, and the software is hereby placed in the public * domain. In case this attempt to disclaim copyright and place the software * in the public domain is deemed null and void, then the software is * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the * general public under the following terms: * * Redistribution and use in source and binary forms, with or without * modification, are permitted. * * There's ABSOLUTELY NO WARRANTY, express or implied. * * See crypt_blowfish.c for more information. * * 2017-10-10 - Adapted for dovecot code by Aki Tuomi */ #ifndef CRYPT_BLOWFISH_H #define CRYPT_BLOWFISH_H extern int crypt_output_magic(const char *setting, char *output, size_t size); extern char *crypt_blowfish_rn(const char *key, const char *setting, char *output, size_t size); extern char *crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, const char *input, size_t size, char *output, size_t output_size); #endif dovecot-2.3.21.1/src/auth/mech-winbind.c0000644000000000000000000002167314656633576014566 00000000000000/* * NTLM and Negotiate authentication mechanisms, * using Samba winbind daemon * * Copyright (c) 2007 Dmitry Butskoy * * This software is released under the MIT license. */ #include "auth-common.h" #include "lib-signals.h" #include "mech.h" #include "str.h" #include "buffer.h" #include "base64.h" #include "execv-const.h" #include "istream.h" #include "ostream.h" #include #include #define DEFAULT_WINBIND_HELPER_PATH "/usr/bin/ntlm_auth" enum helper_result { HR_OK = 0, /* OK or continue */ HR_FAIL = -1, /* authentication failed */ HR_RESTART = -2 /* FAIL + try to restart helper */ }; struct winbind_helper { const char *param; pid_t pid; struct istream *in_pipe; struct ostream *out_pipe; }; struct winbind_auth_request { struct auth_request auth_request; struct winbind_helper *winbind; bool continued; }; static struct winbind_helper winbind_ntlm_context = { "--helper-protocol=squid-2.5-ntlmssp", -1, NULL, NULL }; static struct winbind_helper winbind_spnego_context = { "--helper-protocol=gss-spnego", -1, NULL, NULL }; static bool sigchld_handler_set = FALSE; static void winbind_helper_disconnect(struct winbind_helper *winbind) { i_stream_destroy(&winbind->in_pipe); o_stream_destroy(&winbind->out_pipe); } static void winbind_wait_pid(struct winbind_helper *winbind) { int status, ret; if (winbind->pid == -1) return; /* FIXME: use child-wait.h API */ if ((ret = waitpid(winbind->pid, &status, WNOHANG)) <= 0) { if (ret < 0 && errno != ECHILD && errno != EINTR) i_error("waitpid() failed: %m"); return; } if (WIFSIGNALED(status)) { i_error("winbind: ntlm_auth died with signal %d", WTERMSIG(status)); } else if (WIFEXITED(status)) { i_error("winbind: ntlm_auth exited with exit code %d", WEXITSTATUS(status)); } else { /* shouldn't happen */ i_error("winbind: ntlm_auth exited with status %d", status); } winbind->pid = -1; } static void sigchld_handler(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED) { winbind_wait_pid(&winbind_ntlm_context); winbind_wait_pid(&winbind_spnego_context); } static void winbind_helper_connect(const struct auth_settings *set, struct winbind_helper *winbind, struct event *event) { int infd[2], outfd[2]; pid_t pid; if (winbind->in_pipe != NULL || winbind->pid != -1) return; if (pipe(infd) < 0) { e_error(event, "pipe() failed: %m"); return; } if (pipe(outfd) < 0) { i_close_fd(&infd[0]); i_close_fd(&infd[1]); return; } pid = fork(); if (pid < 0) { e_error(event, "fork() failed: %m"); i_close_fd(&infd[0]); i_close_fd(&infd[1]); i_close_fd(&outfd[0]); i_close_fd(&outfd[1]); return; } if (pid == 0) { /* child */ const char *args[3]; i_close_fd(&infd[0]); i_close_fd(&outfd[1]); if (dup2(outfd[0], STDIN_FILENO) < 0 || dup2(infd[1], STDOUT_FILENO) < 0) i_fatal("dup2() failed: %m"); args[0] = set->winbind_helper_path; args[1] = winbind->param; args[2] = NULL; execv_const(args[0], args); } /* parent */ i_close_fd(&infd[1]); i_close_fd(&outfd[0]); winbind->pid = pid; winbind->in_pipe = i_stream_create_fd_autoclose(&infd[0], AUTH_CLIENT_MAX_LINE_LENGTH); winbind->out_pipe = o_stream_create_fd_autoclose(&outfd[1], SIZE_MAX); if (!sigchld_handler_set) { sigchld_handler_set = TRUE; lib_signals_set_handler(SIGCHLD, LIBSIG_FLAGS_SAFE, sigchld_handler, NULL); } } static enum helper_result do_auth_continue(struct auth_request *auth_request, const unsigned char *data, size_t data_size) { struct winbind_auth_request *request = (struct winbind_auth_request *)auth_request; struct istream *in_pipe = request->winbind->in_pipe; string_t *str; char *answer; const char **token; bool gss_spnego = request->winbind == &winbind_spnego_context; if (request->winbind->in_pipe == NULL) return HR_RESTART; str = t_str_new(MAX_BASE64_ENCODED_SIZE(data_size + 1) + 4); str_printfa(str, "%s ", request->continued ? "KK" : "YR"); base64_encode(data, data_size, str); str_append_c(str, '\n'); if (o_stream_send(request->winbind->out_pipe, str_data(str), str_len(str)) < 0 || o_stream_flush(request->winbind->out_pipe) < 0) { e_error(auth_request->mech_event, "write(out_pipe) failed: %s", o_stream_get_error(request->winbind->out_pipe)); return HR_RESTART; } request->continued = FALSE; while ((answer = i_stream_read_next_line(in_pipe)) == NULL) { if (in_pipe->stream_errno != 0 || in_pipe->eof) break; } if (answer == NULL) { if (in_pipe->stream_errno != 0) { e_error(auth_request->mech_event, "read(in_pipe) failed: %m"); } else { e_error(auth_request->mech_event, "read(in_pipe) failed: " "unexpected end of file"); } return HR_RESTART; } token = t_strsplit_spaces(answer, " "); if (token[0] == NULL || (token[1] == NULL && strcmp(token[0], "BH") != 0) || (gss_spnego && (token[1] == NULL || token[2] == NULL))) { e_error(auth_request->mech_event, "Invalid input from helper: %s", answer); return HR_RESTART; } /* * NTLM: * The child's reply contains 2 parts: * - The code: TT, AF or NA * - The argument: * For TT it's the blob to send to the client, coded in base64 * For AF it's user or DOMAIN\user * For NA it's the NT error code * * GSS-SPNEGO: * The child's reply contains 3 parts: * - The code: TT, AF or NA * - The blob to send to the client, coded in base64 * - The argument: * For TT it's a dummy '*' * For AF it's DOMAIN\user * For NA it's the NT error code */ if (strcmp(token[0], "TT") == 0) { buffer_t *buf; i_assert(token[1] != NULL); buf = t_base64_decode_str(token[1]); auth_request_handler_reply_continue(auth_request, buf->data, buf->used); request->continued = TRUE; return HR_OK; } else if (strcmp(token[0], "NA") == 0) { const char *error = gss_spnego ? token[2] : token[1]; e_info(auth_request->mech_event, "user not authenticated: %s", error); return HR_FAIL; } else if (strcmp(token[0], "AF") == 0) { const char *user, *p, *error; user = t_strarray_join(gss_spnego ? token+2 : token+1, " "); i_assert(user != NULL); p = strchr(user, '\\'); if (p != NULL) { /* change "DOMAIN\user" to uniform style "user@DOMAIN" */ user = t_strconcat(p+1, "@", t_strdup_until(user, p), NULL); } if (!auth_request_set_username(auth_request, user, &error)) { e_info(auth_request->mech_event, "%s", error); return HR_FAIL; } request->auth_request.passdb_success = TRUE; if (gss_spnego && strcmp(token[1], "*") != 0) { buffer_t *buf; buf = t_base64_decode_str(token[1]); auth_request_success(&request->auth_request, buf->data, buf->used); } else { auth_request_success(&request->auth_request, "", 0); } return HR_OK; } else if (strcmp(token[0], "BH") == 0) { e_info(auth_request->mech_event, "ntlm_auth reports broken helper: %s", token[1] != NULL ? token[1] : ""); return HR_RESTART; } else { e_error(auth_request->mech_event, "Invalid input from helper: %s", answer); return HR_RESTART; } } static void mech_winbind_auth_initial(struct auth_request *auth_request, const unsigned char *data, size_t data_size) { struct winbind_auth_request *request = (struct winbind_auth_request *)auth_request; winbind_helper_connect(auth_request->set, request->winbind, auth_request->event); mech_generic_auth_initial(auth_request, data, data_size); } static void mech_winbind_auth_continue(struct auth_request *auth_request, const unsigned char *data, size_t data_size) { struct winbind_auth_request *request = (struct winbind_auth_request *)auth_request; enum helper_result res; res = do_auth_continue(auth_request, data, data_size); if (res != HR_OK) { if (res == HR_RESTART) winbind_helper_disconnect(request->winbind); auth_request_fail(auth_request); } } static struct auth_request *do_auth_new(struct winbind_helper *winbind) { struct winbind_auth_request *request; pool_t pool; pool = pool_alloconly_create(MEMPOOL_GROWING"winbind_auth_request", 2048); request = p_new(pool, struct winbind_auth_request, 1); request->auth_request.pool = pool; request->winbind = winbind; return &request->auth_request; } static struct auth_request *mech_winbind_ntlm_auth_new(void) { return do_auth_new(&winbind_ntlm_context); } static struct auth_request *mech_winbind_spnego_auth_new(void) { return do_auth_new(&winbind_spnego_context); } const struct mech_module mech_winbind_ntlm = { "NTLM", .flags = MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE | MECH_SEC_ALLOW_NULS, .passdb_need = MECH_PASSDB_NEED_NOTHING, mech_winbind_ntlm_auth_new, mech_winbind_auth_initial, mech_winbind_auth_continue, mech_generic_auth_free }; const struct mech_module mech_winbind_spnego = { "GSS-SPNEGO", .flags = 0, .passdb_need = MECH_PASSDB_NEED_NOTHING, mech_winbind_spnego_auth_new, mech_winbind_auth_initial, mech_winbind_auth_continue, mech_generic_auth_free }; dovecot-2.3.21.1/src/auth/db-dict-cache-key.c0000644000000000000000000000271714656633576015355 00000000000000/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "array.h" #include "str.h" #include "var-expand.h" #include "db-dict.h" const struct db_dict_key * db_dict_set_key_find(const ARRAY_TYPE(db_dict_key) *keys, const char *name) { const struct db_dict_key *key; array_foreach(keys, key) { if (strcmp(key->name, name) == 0) return key; } return NULL; } const char * db_dict_parse_cache_key(const ARRAY_TYPE(db_dict_key) *keys, const ARRAY_TYPE(db_dict_field) *fields, const ARRAY_TYPE(db_dict_key_p) *objects) { const struct db_dict_field *field; const struct db_dict_key *key; const char *p, *name; unsigned int idx, size; string_t *str = t_str_new(128); array_foreach(fields, field) { for (p = field->value; *p != '\0'; ) { if (*p != '%') { p++; continue; } var_get_key_range(++p, &idx, &size); if (size == 0) { /* broken %variable ending too early */ break; } p += idx; if (size > 5 && memcmp(p, "dict:", 5) == 0) { name = t_strcut(t_strndup(p+5, size-5), ':'); key = db_dict_set_key_find(keys, name); if (key != NULL) str_printfa(str, "\t%s", key->key); } else if (size == 1) { str_printfa(str, "\t%%%c", p[0]); } else { str_append(str, "\t%{"); str_append_data(str, p, size); str_append_c(str, '}'); } p += size; } } array_foreach_elem(objects, key) str_printfa(str, "\t%s", key->key); return str_c(str); } dovecot-2.3.21.1/src/auth/userdb-static.c0000644000000000000000000000756714656633576015001 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "array.h" #include "str.h" #include "var-expand.h" #include "userdb.h" #include "userdb-template.h" struct static_context { userdb_callback_t *callback, *old_callback; void *old_context; }; struct static_userdb_module { struct userdb_module module; struct userdb_template *tmpl; bool allow_all_users:1; }; static void static_lookup_real(struct auth_request *auth_request, userdb_callback_t *callback) { struct userdb_module *_module = auth_request->userdb->userdb; struct static_userdb_module *module = (struct static_userdb_module *)_module; const char *error; if (userdb_template_export(module->tmpl, auth_request, &error) < 0) { e_error(authdb_event(auth_request), "Failed to expand template: %s", error); callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request); } callback(USERDB_RESULT_OK, auth_request); } static void static_credentials_callback(enum passdb_result result, const unsigned char *credentials ATTR_UNUSED, size_t size ATTR_UNUSED, struct auth_request *auth_request) { struct static_context *ctx = auth_request->context; auth_request->userdb_lookup = TRUE; auth_request->private_callback.userdb = ctx->old_callback; auth_request->context = ctx->old_context; auth_request_set_state(auth_request, AUTH_REQUEST_STATE_USERDB); switch (result) { case PASSDB_RESULT_OK: static_lookup_real(auth_request, ctx->callback); break; case PASSDB_RESULT_USER_UNKNOWN: case PASSDB_RESULT_USER_DISABLED: case PASSDB_RESULT_PASS_EXPIRED: ctx->callback(USERDB_RESULT_USER_UNKNOWN, auth_request); break; case PASSDB_RESULT_SCHEME_NOT_AVAILABLE: e_error(authdb_event(auth_request), "passdb doesn't support lookups, " "can't verify user's existence"); /* fall through */ default: ctx->callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request); break; } i_free(ctx); } static void static_lookup(struct auth_request *auth_request, userdb_callback_t *callback) { struct userdb_module *_module = auth_request->userdb->userdb; struct static_userdb_module *module = (struct static_userdb_module *)_module; struct static_context *ctx; if (!auth_request->fields.successful && !module->allow_all_users) { /* this is a userdb-only lookup. we need to know if this users exists or not. use a passdb lookup to do that. if the passdb doesn't support returning credentials, this will of course fail.. */ ctx = i_new(struct static_context, 1); ctx->old_callback = auth_request->private_callback.userdb; ctx->old_context = auth_request->context; ctx->callback = callback; i_assert(auth_request->state == AUTH_REQUEST_STATE_USERDB); auth_request_set_state(auth_request, AUTH_REQUEST_STATE_MECH_CONTINUE); auth_request->context = ctx; if (auth_request->passdb != NULL) { /* kludge: temporarily work as if we weren't doing a userdb lookup. this is to get auth cache to use passdb caching instead of userdb caching. */ auth_request->userdb_lookup = FALSE; auth_request_lookup_credentials(auth_request, "", static_credentials_callback); } else { static_credentials_callback( PASSDB_RESULT_SCHEME_NOT_AVAILABLE, uchar_empty_ptr, 0, auth_request); } } else { static_lookup_real(auth_request, callback); } } static struct userdb_module * static_preinit(pool_t pool, const char *args) { struct static_userdb_module *module; const char *value; module = p_new(pool, struct static_userdb_module, 1); module->tmpl = userdb_template_build(pool, "static", args); if (userdb_template_remove(module->tmpl, "allow_all_users", &value)) { module->allow_all_users = value == NULL || strcasecmp(value, "yes") == 0; } return &module->module; } struct userdb_module_interface userdb_static = { "static", static_preinit, NULL, NULL, static_lookup, NULL, NULL, NULL }; dovecot-2.3.21.1/src/auth/mech-anonymous.c0000644000000000000000000000235514656633576015160 00000000000000/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "mech.h" static void mech_anonymous_auth_continue(struct auth_request *request, const unsigned char *data, size_t data_size) { i_assert(*request->set->anonymous_username != '\0'); if (request->set->verbose) { /* temporarily set the user to the one that was given, so that the log message goes right */ auth_request_set_username_forced(request, t_strndup(data, data_size)); e_info(request->mech_event, "login"); } auth_request_set_username_forced(request, request->set->anonymous_username); request->passdb_success = TRUE; auth_request_success(request, "", 0); } static struct auth_request *mech_anonymous_auth_new(void) { struct auth_request *request; pool_t pool; pool = pool_alloconly_create(MEMPOOL_GROWING"anonymous_auth_request", 2048); request = p_new(pool, struct auth_request, 1); request->pool = pool; return request; } const struct mech_module mech_anonymous = { "ANONYMOUS", .flags = MECH_SEC_ANONYMOUS | MECH_SEC_ALLOW_NULS, .passdb_need = MECH_PASSDB_NEED_NOTHING, mech_anonymous_auth_new, mech_generic_auth_initial, mech_anonymous_auth_continue, mech_generic_auth_free }; dovecot-2.3.21.1/src/auth/userdb-lua.c0000644000000000000000000000706314656633576014262 00000000000000/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "auth-common.h" #include "userdb.h" #include "auth-cache.h" #if defined(BUILTIN_LUA) || defined(PLUGIN_BUILD) #include "db-lua.h" struct dlua_userdb_module { struct userdb_module module; struct dlua_script *script; const char *file; }; static void userdb_lua_lookup(struct auth_request *auth_request, userdb_callback_t *callback) { struct userdb_module *_module = auth_request->userdb->userdb; struct dlua_userdb_module *module = (struct dlua_userdb_module *)_module; const char *error; enum userdb_result result = auth_lua_call_userdb_lookup(module->script, auth_request, &error); if (result == USERDB_RESULT_INTERNAL_FAILURE) e_error(authdb_event(auth_request), "userdb-lua: %s", error); callback(result, auth_request); } static struct userdb_module * userdb_lua_preinit(pool_t pool, const char *args) { struct dlua_userdb_module *module; const char *cache_key = "%u"; bool blocking = TRUE; module = p_new(pool, struct dlua_userdb_module, 1); const char *const *fields = t_strsplit_spaces(args, " "); while(*fields != NULL) { if (str_begins(*fields, "file=")) { module->file = p_strdup(pool, (*fields)+5); } else if (str_begins(*fields, "blocking=")) { const char *value = (*fields)+9; if (strcmp(value, "yes") == 0) { blocking = TRUE; } else if (strcmp(value, "no") == 0) { blocking = FALSE; } else { i_fatal("Invalid value %s. " "Field blocking must be yes or no", value); } } else if (str_begins(*fields, "cache_key=")) { if (*((*fields)+10) != '\0') cache_key = (*fields)+10; else /* explicitly disable auth caching for lua */ cache_key = NULL; } else { i_fatal("Unsupported parameter %s", *fields); } fields++; } if (module->file == NULL) i_fatal("userdb-lua: Missing mandatory file= parameter"); module->module.blocking = blocking; if (cache_key != NULL) { module->module.default_cache_key = auth_cache_parse_key(pool, cache_key); } return &module->module; } static void userdb_lua_init(struct userdb_module *_module) { struct dlua_userdb_module *module = (struct dlua_userdb_module *)_module; const char *error; if (dlua_script_create_file(module->file, &module->script, auth_event, &error) < 0 || auth_lua_script_init(module->script, &error) < 0) i_fatal("userdb-lua: initialization failed: %s", error); } static void userdb_lua_deinit(struct userdb_module *_module) { struct dlua_userdb_module *module = (struct dlua_userdb_module *)_module; dlua_script_unref(&module->script); } static struct userdb_iterate_context * userdb_lua_iterate_init(struct auth_request *auth_request, userdb_iter_callback_t *callback, void *context) { struct userdb_module *_module = auth_request->userdb->userdb; struct dlua_userdb_module *module = (struct dlua_userdb_module *)_module; return auth_lua_call_userdb_iterate_init(module->script, auth_request, callback, context); } static void userdb_lua_iterate_next(struct userdb_iterate_context *ctx) { auth_lua_userdb_iterate_next(ctx); } static int userdb_lua_iterate_deinit(struct userdb_iterate_context *ctx) { return auth_lua_userdb_iterate_deinit(ctx); } #ifndef PLUGIN_BUILD struct userdb_module_interface userdb_lua = #else struct userdb_module_interface userdb_lua_plugin = #endif { "lua", userdb_lua_preinit, userdb_lua_init, userdb_lua_deinit, userdb_lua_lookup, userdb_lua_iterate_init, userdb_lua_iterate_next, userdb_lua_iterate_deinit }; #else struct userdb_module_interface userdb_lua = { .name = "lua" }; #endif dovecot-2.3.21.1/src/lib-dcrypt/0000755000000000000000000000000014656633636013232 500000000000000dovecot-2.3.21.1/src/lib-dcrypt/dcrypt.h0000644000000000000000000003141314656633576014635 00000000000000#ifndef DCRYPT_H #define DCRYPT_H 1 #include "array.h" struct dcrypt_context_symmetric; struct dcrypt_context_hmac; struct dcrypt_public_key; struct dcrypt_private_key; struct dcrypt_keypair { struct dcrypt_public_key *pub; struct dcrypt_private_key *priv; }; enum dcrypt_sym_mode { DCRYPT_MODE_ENCRYPT, DCRYPT_MODE_DECRYPT }; enum dcrypt_key_type { DCRYPT_KEY_RSA = 0x1, DCRYPT_KEY_EC = 0x2 }; /** * dovecot key format: * version version-specific data * v1: version tab nid tab raw ec private key (in hex) * v2: version colon algorithm oid colon private-or-public-key-only (in hex) */ enum dcrypt_key_format { DCRYPT_FORMAT_PEM, DCRYPT_FORMAT_DOVECOT, DCRYPT_FORMAT_JWK, /* JSON Web Key (JWK) [RFC7517] */ }; enum dcrypt_key_encryption_type { DCRYPT_KEY_ENCRYPTION_TYPE_NONE, DCRYPT_KEY_ENCRYPTION_TYPE_KEY, DCRYPT_KEY_ENCRYPTION_TYPE_PASSWORD }; enum dcrypt_key_version { DCRYPT_KEY_VERSION_1, DCRYPT_KEY_VERSION_2, DCRYPT_KEY_VERSION_NA /* not applicable, PEM key */ }; enum dcrypt_key_kind { DCRYPT_KEY_KIND_PUBLIC, DCRYPT_KEY_KIND_PRIVATE }; enum dcrypt_key_usage { DCRYPT_KEY_USAGE_NONE, DCRYPT_KEY_USAGE_ENCRYPT, DCRYPT_KEY_USAGE_SIGN, }; enum dcrypt_signature_format { DCRYPT_SIGNATURE_FORMAT_DSS, DCRYPT_SIGNATURE_FORMAT_X962, }; /* this parameter makes sense with RSA only default for RSA means either PSS (sign/verify) or OAEP (encrypt/decrypt). for ECDSA default can be used. */ enum dcrypt_padding { DCRYPT_PADDING_DEFAULT, DCRYPT_PADDING_RSA_PKCS1_PSS, DCRYPT_PADDING_RSA_PKCS1_OAEP, DCRYPT_PADDING_RSA_PKCS1, /* for compatibility use only */ DCRYPT_PADDING_RSA_NO, }; struct dcrypt_settings { /* OpenSSL engine to use */ const char *crypto_device; /* Look for backends in this directory */ const char *module_dir; }; struct dcrypt_raw_key { const void *parameter; size_t len; }; ARRAY_DEFINE_TYPE(dcrypt_raw_key, struct dcrypt_raw_key); /** * load and initialize dcrypt backend, use either openssl or gnutls */ bool dcrypt_initialize(const char *backend, const struct dcrypt_settings *set, const char **error_r); /** * Returns TRUE if dcrypt has been initialized. */ bool dcrypt_is_initialized(void); /** * deinitialize dcrypt. * * NOTE: Do not call this function if you are going to use dcrypt later on. * Deinitializing the library using this will not allow it to be reinitialized * when using OpenSSL. */ void dcrypt_deinitialize(void); /** * create symmetric context */ bool dcrypt_ctx_sym_create(const char *algorithm, enum dcrypt_sym_mode mode, struct dcrypt_context_symmetric **ctx_r, const char **error_r); /** * destroy symmetric context and free memory */ void dcrypt_ctx_sym_destroy(struct dcrypt_context_symmetric **ctx); /** * key and IV manipulation functions */ void dcrypt_ctx_sym_set_key(struct dcrypt_context_symmetric *ctx, const unsigned char *key, size_t key_len); void dcrypt_ctx_sym_set_iv(struct dcrypt_context_symmetric *ctx, const unsigned char *iv, size_t iv_len); void dcrypt_ctx_sym_set_key_iv_random(struct dcrypt_context_symmetric *ctx); bool dcrypt_ctx_sym_get_key(struct dcrypt_context_symmetric *ctx, buffer_t *key); bool dcrypt_ctx_sym_get_iv(struct dcrypt_context_symmetric *ctx, buffer_t *iv); /** * turn padding on/off (default: on) */ void dcrypt_ctx_sym_set_padding(struct dcrypt_context_symmetric *ctx, bool padding); /** * authentication data manipulation (use with GCM only) */ void dcrypt_ctx_sym_set_aad(struct dcrypt_context_symmetric *ctx, const unsigned char *aad, size_t aad_len); bool dcrypt_ctx_sym_get_aad(struct dcrypt_context_symmetric *ctx, buffer_t *aad); /** * result tag from aead (use with GCM only) */ void dcrypt_ctx_sym_set_tag(struct dcrypt_context_symmetric *ctx, const unsigned char *tag, size_t tag_len); bool dcrypt_ctx_sym_get_tag(struct dcrypt_context_symmetric *ctx, buffer_t *tag); /* get various lengths */ unsigned int dcrypt_ctx_sym_get_key_length(struct dcrypt_context_symmetric *ctx); unsigned int dcrypt_ctx_sym_get_iv_length(struct dcrypt_context_symmetric *ctx); unsigned int dcrypt_ctx_sym_get_block_size(struct dcrypt_context_symmetric *ctx); /** * initialize crypto */ bool dcrypt_ctx_sym_init(struct dcrypt_context_symmetric *ctx, const char **error_r); /** * update with data */ bool dcrypt_ctx_sym_update(struct dcrypt_context_symmetric *ctx, const unsigned char *data, size_t data_len, buffer_t *result, const char **error_r); /** * perform final step (may or may not emit data) */ bool dcrypt_ctx_sym_final(struct dcrypt_context_symmetric *ctx, buffer_t *result, const char **error_r); /** * create HMAC context, algorithm is digest algorithm */ bool dcrypt_ctx_hmac_create(const char *algorithm, struct dcrypt_context_hmac **ctx_r, const char **error_r); /** * destroy HMAC context and free memory */ void dcrypt_ctx_hmac_destroy(struct dcrypt_context_hmac **ctx); /** * hmac key manipulation */ void dcrypt_ctx_hmac_set_key(struct dcrypt_context_hmac *ctx, const unsigned char *key, size_t key_len); bool dcrypt_ctx_hmac_get_key(struct dcrypt_context_hmac *ctx, buffer_t *key); void dcrypt_ctx_hmac_set_key_random(struct dcrypt_context_hmac *ctx); /** * get digest length for HMAC */ unsigned int dcrypt_ctx_hmac_get_digest_length(struct dcrypt_context_hmac *ctx); /** * initialize hmac */ bool dcrypt_ctx_hmac_init(struct dcrypt_context_hmac *ctx, const char **error_r); /** * update hmac context with data */ bool dcrypt_ctx_hmac_update(struct dcrypt_context_hmac *ctx, const unsigned char *data, size_t data_len, const char **error_r); /** * perform final rounds and retrieve result */ bool dcrypt_ctx_hmac_final(struct dcrypt_context_hmac *ctx, buffer_t *result, const char **error_r); /** * Elliptic Curve based Diffie-Heffman shared secret derivation */ bool dcrypt_ecdh_derive_secret(struct dcrypt_private_key *priv_key, struct dcrypt_public_key *pub_key, buffer_t *shared_secret, const char **error_r); /** * Helpers for DCRYPT file format */ bool dcrypt_ecdh_derive_secret_local(struct dcrypt_private_key *local_key, buffer_t *R, buffer_t *S, const char **error_r); bool dcrypt_ecdh_derive_secret_peer(struct dcrypt_public_key *peer_key, buffer_t *R, buffer_t *S, const char **error_r); /** Signature functions algorithm is name of digest algorithm to use, such as SHA256. both RSA and EC keys are supported. */ /* returns false on error, true on success */ bool dcrypt_sign(struct dcrypt_private_key *key, const char *algorithm, enum dcrypt_signature_format format, const void *data, size_t data_len, buffer_t *signature_r, enum dcrypt_padding padding, const char **error_r); /* check valid_r for signature validity false return means it wasn't able to verify it for other reasons */ bool dcrypt_verify(struct dcrypt_public_key *key, const char *algorithm, enum dcrypt_signature_format format, const void *data, size_t data_len, const unsigned char *signature, size_t signature_len, bool *valid_r, enum dcrypt_padding padding, const char **error_r); /** * generate cryptographic data from password and salt. Use 1000-10000 for rounds. */ bool dcrypt_pbkdf2(const unsigned char *password, size_t password_len, const unsigned char *salt, size_t salt_len, const char *hash, unsigned int rounds, buffer_t *result, unsigned int result_len, const char **error_r); bool dcrypt_keypair_generate(struct dcrypt_keypair *pair_r, enum dcrypt_key_type kind, unsigned int bits, const char *curve, const char **error_r); /** * load loads key structure from external format. * store stores key structure into external format. * * you can provide either PASSWORD or ENC_KEY, not both. */ bool dcrypt_key_load_private(struct dcrypt_private_key **key_r, const char *data, const char *password, struct dcrypt_private_key *dec_key, const char **error_r); bool dcrypt_key_load_public(struct dcrypt_public_key **key_r, const char *data, const char **error_r); /** * When encrypting with public key, the cipher parameter here must begin with * ecdh-, for example ecdh-aes-256-ctr. An example of a valid cipher for * encrypting with password would be aes-256-ctr. */ bool dcrypt_key_store_private(struct dcrypt_private_key *key, enum dcrypt_key_format format, const char *cipher, buffer_t *destination, const char *password, struct dcrypt_public_key *enc_key, const char **error_r); bool dcrypt_key_store_public(struct dcrypt_public_key *key, enum dcrypt_key_format format, buffer_t *destination, const char **error_r); void dcrypt_key_convert_private_to_public(struct dcrypt_private_key *priv_key, struct dcrypt_public_key **pub_key_r); void dcrypt_keypair_unref(struct dcrypt_keypair *keypair); void dcrypt_key_ref_public(struct dcrypt_public_key *key); void dcrypt_key_ref_private(struct dcrypt_private_key *key); void dcrypt_key_unref_public(struct dcrypt_public_key **key); void dcrypt_key_unref_private(struct dcrypt_private_key **key); enum dcrypt_key_type dcrypt_key_type_private(struct dcrypt_private_key *key); enum dcrypt_key_type dcrypt_key_type_public(struct dcrypt_public_key *key); /* return digest of key */ bool dcrypt_key_id_public(struct dcrypt_public_key *key, const char *algorithm, buffer_t *result, const char **error_r); /* return SHA1 sum of key */ bool dcrypt_key_id_public_old(struct dcrypt_public_key *key, buffer_t *result, const char **error_r); /* return digest of key */ bool dcrypt_key_id_private(struct dcrypt_private_key *key, const char *algorithm, buffer_t *result, const char **error_r); /* return SHA1 sum of key */ bool dcrypt_key_id_private_old(struct dcrypt_private_key *key, buffer_t *result, const char **error_r); /* return raw private key: Only ECC supported currently returns OID bytes and private key in bigendian bytes */ bool dcrypt_key_store_private_raw(struct dcrypt_private_key *key, pool_t pool, enum dcrypt_key_type *key_type_r, ARRAY_TYPE(dcrypt_raw_key) *keys_r, const char **error_r); /* return raw public key Only ECC supported currently returns OID bytes and public key in compressed form (z||x) */ bool dcrypt_key_store_public_raw(struct dcrypt_public_key *key, pool_t pool, enum dcrypt_key_type *key_type_r, ARRAY_TYPE(dcrypt_raw_key) *keys_r, const char **error_r); /* load raw private key: Only ECC supported currently expects OID bytes and private key in bigendian bytes */ bool dcrypt_key_load_private_raw(struct dcrypt_private_key **key_r, enum dcrypt_key_type key_type, const ARRAY_TYPE(dcrypt_raw_key) *keys, const char **error_r); /* load raw public key Only ECC supported currently expects OID bytes and public key bytes. */ bool dcrypt_key_load_public_raw(struct dcrypt_public_key **key_r, enum dcrypt_key_type key_type, const ARRAY_TYPE(dcrypt_raw_key) *keys, const char **error_r); /* for ECC only - return textual name or OID of used curve */ bool dcrypt_key_get_curve_public(struct dcrypt_public_key *key, const char **curve_r, const char **error_r); bool dcrypt_key_string_get_info(const char *key_data, enum dcrypt_key_format *format_r, enum dcrypt_key_version *version_r, enum dcrypt_key_kind *kind_r, enum dcrypt_key_encryption_type *encryption_type_r, const char **encryption_key_hash_r, const char **key_hash_r, const char **error_r); /* Get/Set key identifier, this is optional opaque string identifying the key. */ const char *dcrypt_key_get_id_public(struct dcrypt_public_key *key); const char *dcrypt_key_get_id_private(struct dcrypt_private_key *key); void dcrypt_key_set_id_public(struct dcrypt_public_key *key, const char *id); void dcrypt_key_set_id_private(struct dcrypt_private_key *key, const char *id); /* Get/Set key usage, optional. Defaults to NONE */ enum dcrypt_key_usage dcrypt_key_get_usage_public(struct dcrypt_public_key *key); enum dcrypt_key_usage dcrypt_key_get_usage_private(struct dcrypt_private_key *key); void dcrypt_key_set_usage_public(struct dcrypt_public_key *key, enum dcrypt_key_usage usage); void dcrypt_key_set_usage_private(struct dcrypt_private_key *key, enum dcrypt_key_usage usage); /* RSA stuff */ bool dcrypt_rsa_encrypt(struct dcrypt_public_key *key, const unsigned char *data, size_t data_len, buffer_t *result, enum dcrypt_padding padding, const char **error_r); bool dcrypt_rsa_decrypt(struct dcrypt_private_key *key, const unsigned char *data, size_t data_len, buffer_t *result, enum dcrypt_padding padding, const char **error_r); /* OID stuff */ const char *dcrypt_oid2name(const unsigned char *oid, size_t oid_len, const char **error_r); bool dcrypt_name2oid(const char *name, buffer_t *oid, const char **error_r); #endif dovecot-2.3.21.1/src/lib-dcrypt/dcrypt-iostream.h0000644000000000000000000000062614656633576016460 00000000000000#ifndef DCRYPT_IOSTREAM_H #define DCRYPT_IOSTREAM_H 1 static const unsigned char IOSTREAM_CRYPT_MAGIC[] = {'C','R','Y','P','T','E','D','\x03','\x07'}; #define IOSTREAM_CRYPT_VERSION 2 #define IOSTREAM_TAG_SIZE 16 enum io_stream_encrypt_flags { IO_STREAM_ENC_INTEGRITY_HMAC = 0x1, IO_STREAM_ENC_INTEGRITY_AEAD = 0x2, IO_STREAM_ENC_INTEGRITY_NONE = 0x4, IO_STREAM_ENC_VERSION_1 = 0x8, }; #endif dovecot-2.3.21.1/src/lib-dcrypt/istream-decrypt.h0000644000000000000000000000310114656633576016435 00000000000000#ifndef ISTREAM_DECRYPT_H #define ISTREAM_DECRYPT_H struct dcrypt_private_key; struct dcrypt_context_symmetric; enum decrypt_istream_format { DECRYPT_FORMAT_V1, DECRYPT_FORMAT_V2 }; /* Look for a private key for a specified public key digest and set it to priv_key_r. Returns 1 if ok, 0 if key doesn't exist, -1 on internal error. Note that the private key will be unreferenced when the istream is destroyed. If the callback is returning a persistent key, it must reference the key first. (This is required, because otherwise a key newly created by the callback couldn't be automatically freed.) */ typedef int i_stream_decrypt_get_key_callback_t(const char *pubkey_digest, struct dcrypt_private_key **priv_key_r, const char **error_r, void *context); struct istream * i_stream_create_decrypt(struct istream *input, struct dcrypt_private_key *priv_key); /* create stream for reading plain encrypted data with no header or MAC. do not call dcrypt_ctx_sym_init */ struct istream * i_stream_create_sym_decrypt(struct istream *input, struct dcrypt_context_symmetric *ctx); /* Decrypt the istream. When a private key is needed, the callback will be called. This allows using multiple private keys for different mails. */ struct istream * i_stream_create_decrypt_callback(struct istream *input, i_stream_decrypt_get_key_callback_t *callback, void *context); enum decrypt_istream_format i_stream_encrypt_get_format(const struct istream *input); enum io_stream_encrypt_flags i_stream_encrypt_get_flags(const struct istream *input); #endif dovecot-2.3.21.1/src/lib-dcrypt/test-stream.c0000644000000000000000000004354214656633576015601 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "str.h" #include "dcrypt.h" #include "dcrypt-iostream.h" #include "ostream.h" #include "ostream-encrypt.h" #include "istream.h" #include "istream-decrypt.h" #include "istream-hash.h" #include "istream-base64.h" #include "randgen.h" #include "hash-method.h" #include "test-common.h" #include "hex-binary.h" #include #include #include static const char key_v1_priv[] = "-----BEGIN PRIVATE KEY-----\n" "MIGpAgEAMBAGByqGSM49AgEGBSuBBAAjBIGRMIGOAgEBBEGz2V2VMi/5s+Z+GJh7\n" "4WfqZjZUpqqm+NJWojm6BbrZMY+9ZComlTGVcUZ007acFxV93oMmrfmtRUb5ynrb\n" "MRFskKFGA0QAAwHrAJc8TvyPzspOoz6UH1C1YRmaUVm8tsLu2d0dYtZeOKJUl52J\n" "4o8MKIg+ce4q0mTNFrhj+glKj29ppWti6JGAQA==\n" "-----END PRIVATE KEY-----"; static const char key_v1_pub[] = "-----BEGIN PUBLIC KEY-----\n" "MFgwEAYHKoZIzj0CAQYFK4EEACMDRAADAesAlzxO/I/Oyk6jPpQfULVhGZpRWby2\n" "wu7Z3R1i1l44olSXnYnijwwoiD5x7irSZM0WuGP6CUqPb2mla2LokYBA\n" "-----END PUBLIC KEY-----"; static const char key_v2_priv[] = "-----BEGIN PRIVATE KEY-----\n" "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgtuQJA+uboZWVwgHc\n" "DciyVdrovAPwlMqshDK3s78IDDuhRANCAAQm0VEdzLB9PtD0HA8JK1zifWnj8M00\n" "FQzedfp9SQsWyA8dzs5/NFR5MTe6Xbh/ndKEs1zZH3vZ4FlNrilZc0st\n" "-----END PRIVATE KEY-----"; static const char key_v2_pub[] = "-----BEGIN PUBLIC KEY-----\n" "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJtFRHcywfT7Q9BwPCStc4n1p4/DN\n" "NBUM3nX6fUkLFsgPHc7OfzRUeTE3ul24f53ShLNc2R972eBZTa4pWXNLLQ==\n" "-----END PUBLIC KEY-----"; static const char test_sample_v1_hash[] = "1d7cc2cc1f1983f76241cc42389911e88590ad58cf9d54cafeb5b198d3723dd1"; static const char test_sample_v1_short_hash[] = "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"; static const char test_sample_v2_hash[] = "2e31218656dd34db65b321688bf418dee4ee785e99eb9c21e0d29b4af27a863e"; static struct dcrypt_keypair test_v1_kp; static struct dcrypt_keypair test_v2_kp; static void test_static_v1_input(void) { ssize_t siz; const struct hash_method *hash = hash_method_lookup("sha256"); unsigned char hash_ctx[hash->context_size]; unsigned char hash_dgst[hash->digest_size]; hash->init(hash_ctx); test_begin("test_static_v1_input"); struct istream *is_1 = i_stream_create_file(DCRYPT_SRC_DIR"/sample-v1.asc", IO_BLOCK_SIZE); struct istream *is_2 = i_stream_create_base64_decoder(is_1); i_stream_unref(&is_1); struct istream *is_3 = i_stream_create_decrypt(is_2, test_v1_kp.priv); i_stream_unref(&is_2); struct istream *is_4 = i_stream_create_hash(is_3, hash, hash_ctx); i_stream_unref(&is_3); while((siz = i_stream_read(is_4))>0) { i_stream_skip(is_4, siz); } if (is_4->stream_errno != 0) i_debug("error: %s", i_stream_get_error(is_4)); test_assert(is_4->stream_errno == 0); i_stream_unref(&is_4); hash->result(hash_ctx, hash_dgst); test_assert(strcmp(test_sample_v1_hash, binary_to_hex(hash_dgst, sizeof(hash_dgst))) == 0); test_end(); } static void test_static_v1_input_short(void) { ssize_t siz; const struct hash_method *hash = hash_method_lookup("sha256"); unsigned char hash_ctx[hash->context_size]; unsigned char hash_dgst[hash->digest_size]; hash->init(hash_ctx); test_begin("test_static_v1_input_short"); struct istream *is_1 = i_stream_create_file(DCRYPT_SRC_DIR"/sample-v1_short.asc", IO_BLOCK_SIZE); struct istream *is_2 = i_stream_create_base64_decoder(is_1); i_stream_unref(&is_1); struct istream *is_3 = i_stream_create_decrypt(is_2, test_v1_kp.priv); i_stream_unref(&is_2); struct istream *is_4 = i_stream_create_hash(is_3, hash, hash_ctx); i_stream_unref(&is_3); while((siz = i_stream_read(is_4))>0) { i_stream_skip(is_4, siz); } if (is_4->stream_errno != 0) i_debug("error: %s", i_stream_get_error(is_4)); test_assert(is_4->stream_errno == 0); i_stream_unref(&is_4); hash->result(hash_ctx, hash_dgst); test_assert(strcmp(test_sample_v1_short_hash, binary_to_hex(hash_dgst, sizeof(hash_dgst))) == 0); test_end(); } static void test_static_v2_input(void) { test_begin("test_static_v2_input"); ssize_t amt; const struct hash_method *hash = hash_method_lookup("sha256"); unsigned char hash_ctx[hash->context_size]; unsigned char hash_dgst[hash->digest_size]; hash->init(hash_ctx); struct istream *is_1 = i_stream_create_file(DCRYPT_SRC_DIR"/sample-v2.asc", IO_BLOCK_SIZE); struct istream *is_2 = i_stream_create_base64_decoder(is_1); i_stream_unref(&is_1); struct istream *is_3 = i_stream_create_decrypt(is_2, test_v2_kp.priv); i_stream_unref(&is_2); struct istream *is_4 = i_stream_create_hash(is_3, hash, hash_ctx); i_stream_unref(&is_3); while((amt = i_stream_read(is_4))>0) { i_stream_skip(is_4, amt); } if (is_4->stream_errno != 0) i_debug("error: %s", i_stream_get_error(is_4)); test_assert(is_4->stream_errno == 0); i_stream_unref(&is_4); hash->result(hash_ctx, hash_dgst); test_assert(strcmp(test_sample_v2_hash, binary_to_hex(hash_dgst, sizeof(hash_dgst))) == 0); test_end(); /** this code is left here to show how the sample file is created struct istream *is = i_stream_create_file("../lib-fts/udhr_fra.txt", 8192); struct istream *is_2 = i_stream_create_hash(is, hash, hash_ctx); int fd = open("sample-v2.bin", O_CREAT|O_TRUNC|O_WRONLY, S_IRWXU); struct ostream *os = o_stream_create_fd_file(fd, 0, TRUE); struct ostream *os_2 = o_stream_create_encrypt(os, "aes-256-gcm-sha256", test_v2_kp.pub, IO_STREAM_ENC_INTEGRITY_AEAD); const unsigned char *ptr; size_t siz; while(i_stream_read_data(is_2, &ptr, &siz, 0)>0) { o_stream_nsend(os_2, ptr, siz); i_stream_skip(is_2, siz); } i_assert(o_stream_finish(os_2) > 0); o_stream_close(os_2); i_stream_close(is_2); hash->result(hash_ctx, hash_dgst); printf("%s\n", binary_to_hex(hash_dgst, sizeof(hash_dgst))); */ } static void test_write_read_v1(void) { test_begin("test_write_read_v1"); unsigned char payload[IO_BLOCK_SIZE]; const unsigned char *ptr; size_t pos = 0, siz; random_fill(payload, IO_BLOCK_SIZE); buffer_t *buf = buffer_create_dynamic(default_pool, sizeof(payload)); struct ostream *os = o_stream_create_buffer(buf); struct ostream *os_2 = o_stream_create_encrypt(os, "", test_v2_kp.pub, IO_STREAM_ENC_VERSION_1); o_stream_nsend(os_2, payload, sizeof(payload)); if (os_2->stream_errno != 0) i_debug("error: %s", o_stream_get_error(os_2)); test_assert(os_2->stream_errno == 0); test_assert(o_stream_finish(os_2) > 0); test_assert(os_2->stream_errno == 0); o_stream_unref(&os); o_stream_unref(&os_2); struct istream *is = test_istream_create_data(buf->data, buf->used); struct istream *is_2 = i_stream_create_decrypt(is, test_v2_kp.priv); size_t offset = 0; test_istream_set_allow_eof(is, FALSE); test_istream_set_size(is, 0); while(i_stream_read_data(is_2, &ptr, &siz, 0)>=0) { if (offset == buf->used) test_istream_set_allow_eof(is, TRUE); else test_istream_set_size(is, ++offset); test_assert_idx(pos + siz <= sizeof(payload), pos); if (pos + siz > sizeof(payload)) break; test_assert_idx(siz == 0 || memcmp(ptr, payload + pos, siz) == 0, pos); i_stream_skip(is_2, siz); pos += siz; } test_assert(is_2->stream_errno == 0); i_stream_unref(&is); i_stream_unref(&is_2); buffer_free(&buf); test_end(); } static void test_write_read_v1_short(void) { test_begin("test_write_read_v1_short"); unsigned char payload[1]; const unsigned char *ptr; size_t pos = 0, siz; random_fill(payload, 1); buffer_t *buf = buffer_create_dynamic(default_pool, 64); struct ostream *os = o_stream_create_buffer(buf); struct ostream *os_2 = o_stream_create_encrypt(os, "", test_v2_kp.pub, IO_STREAM_ENC_VERSION_1); o_stream_nsend(os_2, payload, sizeof(payload)); if (os_2->stream_errno != 0) i_debug("error: %s", o_stream_get_error(os_2)); test_assert(os_2->stream_errno == 0); test_assert(o_stream_finish(os_2) > 0); test_assert(os_2->stream_errno == 0); o_stream_unref(&os); o_stream_unref(&os_2); struct istream *is = test_istream_create_data(buf->data, buf->used); struct istream *is_2 = i_stream_create_decrypt(is, test_v2_kp.priv); size_t offset = 0; test_istream_set_allow_eof(is, FALSE); test_istream_set_size(is, 0); while(i_stream_read_data(is_2, &ptr, &siz, 0)>=0) { if (offset == buf->used) test_istream_set_allow_eof(is, TRUE); else test_istream_set_size(is, ++offset); test_assert_idx(pos + siz <= sizeof(payload), pos); if (siz > sizeof(payload) || pos + siz > sizeof(payload)) break; test_assert_idx(siz == 0 || memcmp(ptr, payload + pos, siz) == 0, pos); i_stream_skip(is_2, siz); pos += siz; } test_assert(is_2->stream_errno == 0); i_stream_unref(&is); i_stream_unref(&is_2); buffer_free(&buf); test_end(); } static void test_write_read_v1_empty(void) { const unsigned char *ptr; size_t siz; test_begin("test_write_read_v1_empty"); buffer_t *buf = buffer_create_dynamic(default_pool, 64); struct ostream *os = o_stream_create_buffer(buf); struct ostream *os_2 = o_stream_create_encrypt(os, "", test_v1_kp.pub, IO_STREAM_ENC_VERSION_1); test_assert(o_stream_finish(os_2) > 0); if (os_2->stream_errno != 0) i_debug("error: %s", o_stream_get_error(os_2)); o_stream_unref(&os); o_stream_unref(&os_2); /* this should've been enough */ struct istream *is = test_istream_create_data(buf->data, buf->used); struct istream *is_2 = i_stream_create_decrypt(is, test_v1_kp.priv); /* read should not fail */ test_istream_set_allow_eof(is, FALSE); test_istream_set_size(is, 0); size_t offset = 0; ssize_t ret; while ((ret = i_stream_read_data(is_2, &ptr, &siz, 0)) >= 0) { test_assert(ret == 0); if (offset == buf->used) test_istream_set_allow_eof(is, TRUE); else test_istream_set_size(is, ++offset); }; test_assert(is_2->stream_errno == 0); if (is_2->stream_errno != 0) i_debug("error: %s", i_stream_get_error(is_2)); i_stream_unref(&is); i_stream_unref(&is_2); buffer_free(&buf); test_end(); } static void test_write_read_v2(void) { test_begin("test_write_read_v2"); unsigned char payload[IO_BLOCK_SIZE*10]; const unsigned char *ptr; size_t pos = 0, siz; random_fill(payload, IO_BLOCK_SIZE*10); buffer_t *buf = buffer_create_dynamic(default_pool, sizeof(payload)); struct ostream *os = o_stream_create_buffer(buf); struct ostream *os_2 = o_stream_create_encrypt(os, "aes-256-gcm-sha256", test_v1_kp.pub, IO_STREAM_ENC_INTEGRITY_AEAD); o_stream_nsend(os_2, payload, sizeof(payload)); test_assert(o_stream_finish(os_2) > 0); if (os_2->stream_errno != 0) i_debug("error: %s", o_stream_get_error(os_2)); o_stream_unref(&os); o_stream_unref(&os_2); struct istream *is = test_istream_create_data(buf->data, buf->used); /* test regression where read fails due to incorrect behaviour when buffer is full before going to decrypt code */ i_stream_set_max_buffer_size(is, 8192); test_assert(i_stream_read(is) > 0); struct istream *is_2 = i_stream_create_decrypt(is, test_v1_kp.priv); size_t offset = 0; test_istream_set_size(is, 0); test_istream_set_allow_eof(is, FALSE); while(i_stream_read_data(is_2, &ptr, &siz, 0)>=0) { if (offset == buf->used) test_istream_set_allow_eof(is, TRUE); else test_istream_set_size(is, ++offset); test_assert_idx(pos + siz <= sizeof(payload), pos); if (pos + siz > sizeof(payload)) break; test_assert_idx(siz == 0 || memcmp(ptr, payload + pos, siz) == 0, pos); i_stream_skip(is_2, siz); pos += siz; } test_assert(is_2->stream_errno == 0); if (is_2->stream_errno != 0) i_debug("error: %s", i_stream_get_error(is_2)); /* test seeking */ for (size_t i = sizeof(payload)-100; i > 100; i -= 100) { i_stream_seek(is_2, i); test_assert_idx(i_stream_read_data(is_2, &ptr, &siz, 0) == 1, i); test_assert_idx(memcmp(ptr, payload + i, siz) == 0, i); } i_stream_seek(is_2, 0); test_assert(i_stream_read_data(is_2, &ptr, &siz, 0) == 1); test_assert(memcmp(ptr, payload, siz) == 0); i_stream_unref(&is); i_stream_unref(&is_2); buffer_free(&buf); test_end(); } static void test_write_read_v2_short(void) { test_begin("test_write_read_v2_short"); unsigned char payload[1]; const unsigned char *ptr; size_t pos = 0, siz; random_fill(payload, 1); buffer_t *buf = buffer_create_dynamic(default_pool, 64); struct ostream *os = o_stream_create_buffer(buf); struct ostream *os_2 = o_stream_create_encrypt(os, "aes-256-gcm-sha256", test_v1_kp.pub, IO_STREAM_ENC_INTEGRITY_AEAD); o_stream_nsend(os_2, payload, sizeof(payload)); test_assert(o_stream_finish(os_2) > 0); if (os_2->stream_errno != 0) i_debug("error: %s", o_stream_get_error(os_2)); o_stream_unref(&os); o_stream_unref(&os_2); struct istream *is = test_istream_create_data(buf->data, buf->used); struct istream *is_2 = i_stream_create_decrypt(is, test_v1_kp.priv); size_t offset = 0; test_istream_set_allow_eof(is, FALSE); test_istream_set_size(is, 0); while(i_stream_read_data(is_2, &ptr, &siz, 0)>=0) { if (offset == buf->used) test_istream_set_allow_eof(is, TRUE); test_istream_set_size(is, ++offset); test_assert_idx(pos + siz <= sizeof(payload), pos); if (siz > sizeof(payload) || pos + siz > sizeof(payload)) break; test_assert_idx(siz == 0 || memcmp(ptr, payload + pos, siz) == 0, pos); i_stream_skip(is_2, siz); pos += siz; } test_assert(is_2->stream_errno == 0); if (is_2->stream_errno != 0) i_debug("error: %s", i_stream_get_error(is_2)); i_stream_unref(&is); i_stream_unref(&is_2); buffer_free(&buf); test_end(); } static void test_write_read_v2_empty(void) { const unsigned char *ptr; size_t siz; test_begin("test_write_read_v2_empty"); buffer_t *buf = buffer_create_dynamic(default_pool, 64); struct ostream *os = o_stream_create_buffer(buf); struct ostream *os_2 = o_stream_create_encrypt(os, "aes-256-gcm-sha256", test_v1_kp.pub, IO_STREAM_ENC_INTEGRITY_AEAD); test_assert(o_stream_finish(os_2) > 0); if (os_2->stream_errno != 0) i_debug("error: %s", o_stream_get_error(os_2)); o_stream_unref(&os); o_stream_unref(&os_2); /* this should've been enough */ struct istream *is = test_istream_create_data(buf->data, buf->used); struct istream *is_2 = i_stream_create_decrypt(is, test_v1_kp.priv); /* read should not fail */ size_t offset = 0; test_istream_set_allow_eof(is, FALSE); test_istream_set_size(is, 0); ssize_t ret; while ((ret = i_stream_read_data(is_2, &ptr, &siz, 0)) >= 0) { test_assert(ret == 0); if (offset == buf->used) test_istream_set_allow_eof(is, TRUE); test_istream_set_size(is, ++offset); }; test_assert(is_2->stream_errno == 0); if (is_2->stream_errno != 0) i_debug("error: %s", i_stream_get_error(is_2)); i_stream_unref(&is); i_stream_unref(&is_2); buffer_free(&buf); test_end(); } static int no_op_cb(const char *digest ATTR_UNUSED, struct dcrypt_private_key **priv_key_r ATTR_UNUSED, const char **error_r ATTR_UNUSED, void *context ATTR_UNUSED) { return 0; } static void test_read_0_to_400_byte_garbage(void) { test_begin("test_read_0_to_100_byte_garbage"); char data[512]; memset(data, 0, sizeof(data)); for (size_t s = 0; s <= 400; ++s) { struct istream *is = test_istream_create_data(data, s); struct istream *ds = i_stream_create_decrypt_callback(is, no_op_cb, NULL); test_istream_set_size(is, 0); test_istream_set_allow_eof(is, FALSE); ssize_t siz = 0; for (size_t offset = 0; offset <= s && siz == 0; offset++) { if (offset == s) test_istream_set_allow_eof(is, TRUE); test_istream_set_size(is, offset); siz = i_stream_read(ds); } test_assert_idx(siz < 0, s); i_stream_unref(&ds); i_stream_unref(&is); } test_end(); } static void test_read_large_header(void) { test_begin("test_read_large_header"); struct istream *is = test_istream_create_data(IOSTREAM_CRYPT_MAGIC, sizeof(IOSTREAM_CRYPT_MAGIC)); struct istream *ds = i_stream_create_decrypt_callback(is, no_op_cb, NULL); test_istream_set_allow_eof(is, FALSE); test_istream_set_max_buffer_size(is, sizeof(IOSTREAM_CRYPT_MAGIC)); test_assert(i_stream_read(ds) == -1); i_stream_unref(&ds); i_stream_unref(&is); test_end(); } static void test_read_increment(void) { test_begin("test_read_increment"); ssize_t amt, total, i; struct istream *is_1 = i_stream_create_file( DCRYPT_SRC_DIR"/sample-v2.asc", IO_BLOCK_SIZE); struct istream *is_2 = i_stream_create_base64_decoder(is_1); i_stream_unref(&is_1); struct istream *is_3 = i_stream_create_decrypt(is_2, test_v2_kp.priv); i_stream_unref(&is_2); total = 0; i = 500; i_stream_set_max_buffer_size(is_3, i); while((amt = i_stream_read(is_3)) > 0) { total += amt; i_stream_set_max_buffer_size(is_3, ++i); } /* make sure snapshotting works: */ i_stream_skip(is_3, 1); test_assert(i_stream_read(is_3) == -1); test_assert(total == 13534); test_assert(is_3->stream_errno == 0); test_assert(is_3->eof); i_stream_unref(&is_3); test_end(); } static void test_free_keys() { dcrypt_key_unref_private(&test_v1_kp.priv); dcrypt_key_unref_public(&test_v1_kp.pub); dcrypt_key_unref_private(&test_v2_kp.priv); dcrypt_key_unref_public(&test_v2_kp.pub); } int main(void) { struct dcrypt_settings set = { .module_dir = ".libs" }; const char *error; if (!dcrypt_initialize(NULL, &set, &error)) { i_error("No functional dcrypt backend found - " "skipping tests: %s", error); return 0; } test_assert(dcrypt_key_load_private(&test_v1_kp.priv, key_v1_priv, NULL, NULL, NULL)); test_assert(dcrypt_key_load_public(&test_v1_kp.pub, key_v1_pub, NULL)); test_assert(dcrypt_key_load_private(&test_v2_kp.priv, key_v2_priv, NULL, NULL, NULL)); test_assert(dcrypt_key_load_public(&test_v2_kp.pub, key_v2_pub, NULL)); static void (*const test_functions[])(void) = { test_static_v1_input, test_static_v1_input_short, test_static_v2_input, test_read_increment, test_write_read_v1, test_write_read_v1_short, test_write_read_v1_empty, test_write_read_v2, test_write_read_v2_short, test_write_read_v2_empty, test_free_keys, test_read_0_to_400_byte_garbage, test_read_large_header, NULL }; return test_run(test_functions); } dovecot-2.3.21.1/src/lib-dcrypt/istream-decrypt.c0000644000000000000000000007463614656633576016455 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "randgen.h" #include "safe-memset.h" #include "hash-method.h" #include "sha2.h" #include "memarea.h" #include "dcrypt.h" #include "istream.h" #include "istream-decrypt.h" #include "istream-private.h" #include "dcrypt-iostream.h" #include "hex-binary.h" #include #define ISTREAM_DECRYPT_READ_FIRST 15 struct decrypt_istream_snapshot { struct istream_snapshot snapshot; struct decrypt_istream *dstream; buffer_t *buf; }; struct decrypt_istream { struct istream_private istream; buffer_t *buf; bool symmetric; i_stream_decrypt_get_key_callback_t *key_callback; void *key_context; struct dcrypt_private_key *priv_key; bool initialized; bool finalized; bool use_mac; bool snapshot_pending; uoff_t ftr, pos; enum io_stream_encrypt_flags flags; /* original iv, in case seeking is done, future feature */ unsigned char *iv; struct dcrypt_context_symmetric *ctx_sym; struct dcrypt_context_hmac *ctx_mac; enum decrypt_istream_format format; }; static void i_stream_decrypt_reset(struct decrypt_istream *dstream) { dstream->finalized = FALSE; dstream->use_mac = FALSE; dstream->ftr = 0; dstream->pos = 0; dstream->flags = 0; if (!dstream->symmetric) { dstream->initialized = FALSE; if (dstream->ctx_sym != NULL) dcrypt_ctx_sym_destroy(&dstream->ctx_sym); if (dstream->ctx_mac != NULL) dcrypt_ctx_hmac_destroy(&dstream->ctx_mac); } i_free(dstream->iv); dstream->format = DECRYPT_FORMAT_V1; } enum decrypt_istream_format i_stream_encrypt_get_format(const struct istream *input) { return ((const struct decrypt_istream*)input->real_stream)->format; } enum io_stream_encrypt_flags i_stream_encrypt_get_flags(const struct istream *input) { return ((const struct decrypt_istream*)input->real_stream)->flags; } static ssize_t i_stream_decrypt_read_header_v1(struct decrypt_istream *stream, const unsigned char *data, size_t mlen) { const char *error = NULL; size_t keydata_len = 0; uint16_t len; int ec, i = 0; const unsigned char *digest_pos = NULL, *key_digest_pos = NULL, *key_ct_pos = NULL; size_t digest_len = 0, key_ct_len = 0, key_digest_size = 0; buffer_t ephemeral_key; buffer_t *secret = t_buffer_create(256); buffer_t *key = t_buffer_create(256); if (mlen < 2) return 0; keydata_len = be16_to_cpu_unaligned(data); if (mlen-2 < keydata_len) { /* try to read more */ return 0; } data+=2; mlen-=2; while (i < 4 && mlen > 2) { memcpy(&len, data, 2); len = ntohs(len); if (len == 0 || len > mlen-2) break; data += 2; mlen -= 2; switch(i++) { case 0: buffer_create_from_const_data(&ephemeral_key, data, len); break; case 1: /* public key id */ digest_pos = data; digest_len = len; break; case 2: /* encryption key digest */ key_digest_pos = data; key_digest_size = len; break; case 3: /* encrypted key data */ key_ct_pos = data; key_ct_len = len; break; } data += len; mlen -= len; } if (i < 4) { io_stream_set_error(&stream->istream.iostream, "Invalid or corrupted header"); /* was it consumed? */ stream->istream.istream.stream_errno = mlen > 2 ? EINVAL : EPIPE; return -1; } /* we don't have a private key */ if (stream->priv_key == NULL) { /* see if we can get one */ if (stream->key_callback != NULL) { const char *key_id = binary_to_hex(digest_pos, digest_len); int ret = stream->key_callback(key_id, &stream->priv_key, &error, stream->key_context); if (ret < 0) { io_stream_set_error(&stream->istream.iostream, "Private key not available: %s", error); return -1; } if (ret == 0) { io_stream_set_error(&stream->istream.iostream, "Private key not available"); return -1; } } else { io_stream_set_error(&stream->istream.iostream, "Private key not available"); return -1; } } buffer_t *check = t_buffer_create(32); if (!dcrypt_key_id_private_old(stream->priv_key, check, &error)) { io_stream_set_error(&stream->istream.iostream, "Cannot get public key hash: %s", error); return -1; } else { if (memcmp(digest_pos, check->data, I_MIN(digest_len,check->used)) != 0) { io_stream_set_error(&stream->istream.iostream, "Private key not available"); return -1; } } /* derive shared secret */ if (!dcrypt_ecdh_derive_secret_local(stream->priv_key, &ephemeral_key, secret, &error)) { io_stream_set_error(&stream->istream.iostream, "Cannot perform ECDH: %s", error); return -1; } /* run it thru SHA256 once */ const struct hash_method *hash = &hash_method_sha256; unsigned char hctx[hash->context_size]; unsigned char hres[hash->digest_size]; hash->init(hctx); hash->loop(hctx, secret->data, secret->used); hash->result(hctx, hres); safe_memset(buffer_get_modifiable_data(secret, 0), 0, secret->used); /* NB! The old code was broken and used this kind of IV - it is not correct, but we need to stay compatible with old data */ /* use it to decrypt the actual encryption key */ struct dcrypt_context_symmetric *dctx; if (!dcrypt_ctx_sym_create("aes-256-ctr", DCRYPT_MODE_DECRYPT, &dctx, &error)) { io_stream_set_error(&stream->istream.iostream, "Key decryption error: %s", error); return -1; } ec = 0; dcrypt_ctx_sym_set_iv(dctx, (const unsigned char*) "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0", 16); dcrypt_ctx_sym_set_key(dctx, hres, hash->digest_size); if (!dcrypt_ctx_sym_init(dctx, &error) || !dcrypt_ctx_sym_update(dctx, key_ct_pos, key_ct_len, key, &error) || !dcrypt_ctx_sym_final(dctx, key, &error)) { io_stream_set_error(&stream->istream.iostream, "Key decryption error: %s", error); ec = -1; } dcrypt_ctx_sym_destroy(&dctx); if (ec != 0) { io_stream_set_error(&stream->istream.iostream, "Key decryption error: %s", error); return -1; } /* see if we got the correct key */ hash->init(hctx); hash->loop(hctx, key->data, key->used); hash->result(hctx, hres); if (key_digest_size != sizeof(hres)) { io_stream_set_error(&stream->istream.iostream, "Key decryption error: " "invalid digest length"); return -1; } if (memcmp(hres, key_digest_pos, sizeof(hres)) != 0) { io_stream_set_error(&stream->istream.iostream, "Key decryption error: " "decrypted key is invalid"); return -1; } /* prime context with key */ if (!dcrypt_ctx_sym_create("aes-256-ctr", DCRYPT_MODE_DECRYPT, &stream->ctx_sym, &error)) { io_stream_set_error(&stream->istream.iostream, "Decryption context create error: %s", error); return -1; } /* Again, old code used this IV, so we have to use it too */ dcrypt_ctx_sym_set_iv(stream->ctx_sym, (const unsigned char*) "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0", 16); dcrypt_ctx_sym_set_key(stream->ctx_sym, key->data, key->used); safe_memset(buffer_get_modifiable_data(key, 0), 0, key->used); if (!dcrypt_ctx_sym_init(stream->ctx_sym, &error)) { io_stream_set_error(&stream->istream.iostream, "Decryption init error: %s", error); return -1; } stream->use_mac = FALSE; stream->initialized = TRUE; /* now we are ready to decrypt stream */ return sizeof(IOSTREAM_CRYPT_MAGIC) + 1 + 2 + keydata_len; } static bool get_msb32(const unsigned char **_data, const unsigned char *end, uint32_t *num_r) { const unsigned char *data = *_data; if (end-data < 4) return FALSE; *num_r = be32_to_cpu_unaligned(data); *_data += 4; return TRUE; } static bool i_stream_decrypt_der(const unsigned char **_data, const unsigned char *end, const char **str_r) { const unsigned char *data = *_data; unsigned int len; if (end-data < 2) return FALSE; /* get us DER encoded length */ if ((data[1] & 0x80) != 0) { /* two byte length */ if (end-data < 3) return FALSE; len = ((data[1] & 0x7f) << 8) + data[2] + 3; } else { len = data[1] + 2; } if ((size_t)(end-data) < len) return FALSE; *str_r = dcrypt_oid2name(data, len, NULL); *_data += len; return TRUE; } static ssize_t i_stream_decrypt_key(struct decrypt_istream *stream, const char *malg, unsigned int rounds, const unsigned char *data, const unsigned char *end, buffer_t *key, size_t key_len) { const char *error; enum dcrypt_key_type ktype; int keys; bool have_key = FALSE; unsigned char dgst[32]; uint32_t val; buffer_t buf; if (data == end) return 0; keys = *data++; /* if we have a key, prefab the digest */ if (stream->priv_key != NULL) { buffer_create_from_data(&buf, dgst, sizeof(dgst)); if (!dcrypt_key_id_private(stream->priv_key, "sha256", &buf, &error)) { io_stream_set_error(&stream->istream.iostream, "Decryption error: " "dcrypt_key_id_private failed: %s", error); return -1; } } else if (stream->key_callback == NULL) { io_stream_set_error(&stream->istream.iostream, "Decryption error: " "no private key available"); return -1; } /* for each key */ for(;keys>0;keys--) { if ((size_t)(end-data) < 1 + (ssize_t)sizeof(dgst)) return 0; ktype = *data++; if (stream->priv_key != NULL) { /* see if key matches to the one we have */ if (memcmp(dgst, data, sizeof(dgst)) == 0) { have_key = TRUE; break; } } else if (stream->key_callback != NULL) { const char *hexdgst = /* digest length */ binary_to_hex(data, sizeof(dgst)); if (stream->priv_key != NULL) dcrypt_key_unref_private(&stream->priv_key); /* hope you going to give us right key.. */ int ret = stream->key_callback(hexdgst, &stream->priv_key, &error, stream->key_context); if (ret < 0) { io_stream_set_error(&stream->istream.iostream, "Private key not available: " "%s", error); return -1; } if (ret > 0) { have_key = TRUE; break; } } data += sizeof(dgst); /* wasn't correct key, skip over some data */ if (!get_msb32(&data, end, &val) || !get_msb32(&data, end, &val)) return 0; } /* didn't find matching key */ if (!have_key) { io_stream_set_error(&stream->istream.iostream, "Decryption error: " "no private key available"); return -1; } data += sizeof(dgst); const unsigned char *ephemeral_key; uint32_t ep_key_len; const unsigned char *encrypted_key; uint32_t eklen; const unsigned char *ekhash; uint32_t ekhash_len; /* read ephemeral key (can be missing for RSA) */ if (!get_msb32(&data, end, &ep_key_len) || (size_t)(end-data) < ep_key_len) return 0; ephemeral_key = data; data += ep_key_len; /* read encrypted key */ if (!get_msb32(&data, end, &eklen) || (size_t)(end-data) < eklen) return 0; encrypted_key = data; data += eklen; /* read key data hash */ if (!get_msb32(&data, end, &ekhash_len) || (size_t)(end-data) < ekhash_len) return 0; ekhash = data; data += ekhash_len; /* decrypt the seed */ if (ktype == DCRYPT_KEY_RSA) { if (!dcrypt_rsa_decrypt(stream->priv_key, encrypted_key, eklen, key, DCRYPT_PADDING_RSA_PKCS1_OAEP, &error)) { io_stream_set_error(&stream->istream.iostream, "key decryption error: %s", error); return -1; } } else if (ktype == DCRYPT_KEY_EC) { /* perform ECDHE */ buffer_t *temp_key = t_buffer_create(256); buffer_t *secret = t_buffer_create(256); buffer_t peer_key; buffer_create_from_const_data(&peer_key, ephemeral_key, ep_key_len); if (!dcrypt_ecdh_derive_secret_local(stream->priv_key, &peer_key, secret, &error)) { io_stream_set_error(&stream->istream.iostream, "Key decryption error: corrupted header"); return -1; } /* use shared secret and peer key to generate decryption key, AES-256-CBC has 32 byte key and 16 byte IV */ if (!dcrypt_pbkdf2(secret->data, secret->used, peer_key.data, peer_key.used, malg, rounds, temp_key, 32+16, &error)) { safe_memset(buffer_get_modifiable_data(secret, 0), 0, secret->used); io_stream_set_error(&stream->istream.iostream, "Key decryption error: %s", error); return -1; } safe_memset(buffer_get_modifiable_data(secret, 0), 0, secret->used); if (temp_key->used != 32+16) { safe_memset(buffer_get_modifiable_data(temp_key, 0), 0, temp_key->used); io_stream_set_error(&stream->istream.iostream, "Cannot perform key decryption: " "invalid temporary key"); return -1; } struct dcrypt_context_symmetric *dctx; if (!dcrypt_ctx_sym_create("AES-256-CBC", DCRYPT_MODE_DECRYPT, &dctx, &error)) { safe_memset(buffer_get_modifiable_data(temp_key, 0), 0, temp_key->used); io_stream_set_error(&stream->istream.iostream, "Key decryption error: %s", error); return -1; } const unsigned char *ptr = temp_key->data; /* we use ephemeral_key for IV */ dcrypt_ctx_sym_set_key(dctx, ptr, 32); dcrypt_ctx_sym_set_iv(dctx, ptr+32, 16); safe_memset(buffer_get_modifiable_data(temp_key, 0), 0, temp_key->used); int ec = 0; if (!dcrypt_ctx_sym_init(dctx, &error) || !dcrypt_ctx_sym_update(dctx, encrypted_key, eklen, key, &error) || !dcrypt_ctx_sym_final(dctx, key, &error)) { io_stream_set_error(&stream->istream.iostream, "Cannot perform key decryption: %s", error); ec = -1; } if (key->used != key_len) { io_stream_set_error(&stream->istream.iostream, "Cannot perform key decryption: " "invalid key length"); ec = -1; } dcrypt_ctx_sym_destroy(&dctx); if (ec != 0) return ec; } else { io_stream_set_error(&stream->istream.iostream, "Decryption error: " "unsupported key type 0x%02x", ktype); return -1; } /* make sure we were able to decrypt the encrypted key correctly */ const struct hash_method *hash = hash_method_lookup(t_str_lcase(malg)); if (hash == NULL) { safe_memset(buffer_get_modifiable_data(key, 0), 0, key->used); io_stream_set_error(&stream->istream.iostream, "Decryption error: " "unsupported hash algorithm: %s", malg); return -1; } unsigned char hctx[hash->context_size]; unsigned char hres[hash->digest_size]; hash->init(hctx); hash->loop(hctx, key->data, key->used); hash->result(hctx, hres); for(int i = 1; i < 2049; i++) { uint32_t i_msb = cpu32_to_be(i); hash->init(hctx); hash->loop(hctx, hres, sizeof(hres)); hash->loop(hctx, &i_msb, sizeof(i_msb)); hash->result(hctx, hres); } /* do the comparison */ if (memcmp(ekhash, hres, I_MIN(ekhash_len, sizeof(hres))) != 0) { safe_memset(buffer_get_modifiable_data(key, 0), 0, key->used); io_stream_set_error(&stream->istream.iostream, "Decryption error: " "corrupted header ekhash"); return -1; } return 1; } static int i_stream_decrypt_header_contents(struct decrypt_istream *stream, const unsigned char *data, size_t size) { const unsigned char *end = data + size; bool failed = FALSE; /* read cipher OID */ const char *calg; if (!i_stream_decrypt_der(&data, end, &calg)) return 0; if (calg == NULL || !dcrypt_ctx_sym_create(calg, DCRYPT_MODE_DECRYPT, &stream->ctx_sym, NULL)) { io_stream_set_error(&stream->istream.iostream, "Decryption error: " "unsupported/invalid cipher: %s", calg); return -1; } /* read MAC oid (MAC is used for PBKDF2 and key data digest, too) */ const char *malg; if (!i_stream_decrypt_der(&data, end, &malg)) return 0; if (malg == NULL || !dcrypt_ctx_hmac_create(malg, &stream->ctx_mac, NULL)) { io_stream_set_error(&stream->istream.iostream, "Decryption error: " "unsupported/invalid MAC algorithm: %s", malg); return -1; } /* read rounds (for PBKDF2) */ uint32_t rounds; if (!get_msb32(&data, end, &rounds)) return 0; /* read key data length */ uint32_t kdlen; if (!get_msb32(&data, end, &kdlen)) return 0; size_t tagsize; if ((stream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC) { tagsize = IOSTREAM_TAG_SIZE; } else if ((stream->flags & IO_STREAM_ENC_INTEGRITY_AEAD) == IO_STREAM_ENC_INTEGRITY_AEAD) { tagsize = IOSTREAM_TAG_SIZE; } else { tagsize = 0; } /* how much key data we should be getting */ size_t kl = dcrypt_ctx_sym_get_key_length(stream->ctx_sym) + dcrypt_ctx_sym_get_iv_length(stream->ctx_sym) + tagsize; buffer_t *keydata = t_buffer_create(kl); /* try to decrypt the keydata with a private key */ int ret; if ((ret = i_stream_decrypt_key(stream, malg, rounds, data, end, keydata, kl)) <= 0) return ret; /* oh, it worked! */ const unsigned char *ptr = keydata->data; if (keydata->used != kl) { /* but returned wrong amount of data */ io_stream_set_error(&stream->istream.iostream, "Key decryption error: " "Key data length mismatch"); return -1; } /* prime contexts */ dcrypt_ctx_sym_set_key(stream->ctx_sym, ptr, dcrypt_ctx_sym_get_key_length(stream->ctx_sym)); ptr += dcrypt_ctx_sym_get_key_length(stream->ctx_sym); dcrypt_ctx_sym_set_iv(stream->ctx_sym, ptr, dcrypt_ctx_sym_get_iv_length(stream->ctx_sym)); stream->iv = i_malloc(dcrypt_ctx_sym_get_iv_length(stream->ctx_sym)); memcpy(stream->iv, ptr, dcrypt_ctx_sym_get_iv_length(stream->ctx_sym)); ptr += dcrypt_ctx_sym_get_iv_length(stream->ctx_sym); /* based on the chosen MAC, initialize HMAC or AEAD */ if ((stream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC) { const char *error; dcrypt_ctx_hmac_set_key(stream->ctx_mac, ptr, tagsize); if (!dcrypt_ctx_hmac_init(stream->ctx_mac, &error)) { io_stream_set_error(&stream->istream.iostream, "MAC error: %s", error); stream->istream.istream.stream_errno = EINVAL; failed = TRUE; } stream->ftr = dcrypt_ctx_hmac_get_digest_length(stream->ctx_mac); stream->use_mac = TRUE; } else if ((stream->flags & IO_STREAM_ENC_INTEGRITY_AEAD) == IO_STREAM_ENC_INTEGRITY_AEAD) { dcrypt_ctx_sym_set_aad(stream->ctx_sym, ptr, tagsize); stream->ftr = tagsize; stream->use_mac = TRUE; } else { stream->use_mac = FALSE; } /* destroy private key data */ safe_memset(buffer_get_modifiable_data(keydata, 0), 0, keydata->used); buffer_set_used_size(keydata, 0); return failed ? -1 : 1; } static ssize_t i_stream_decrypt_read_header(struct decrypt_istream *stream, const unsigned char *data, size_t mlen) { const char *error; const unsigned char *end = data + mlen; /* check magic */ if (mlen < sizeof(IOSTREAM_CRYPT_MAGIC)) return 0; if (memcmp(data, IOSTREAM_CRYPT_MAGIC, sizeof(IOSTREAM_CRYPT_MAGIC)) != 0) { io_stream_set_error(&stream->istream.iostream, "Stream is not encrypted (invalid magic)"); stream->istream.istream.stream_errno = EINVAL; return -1; } data += sizeof(IOSTREAM_CRYPT_MAGIC); if (data >= end) return 0; /* read more? */ /* check version */ if (*data == '\x01') { stream->format = DECRYPT_FORMAT_V1; return i_stream_decrypt_read_header_v1(stream, data+1, end - (data+1)); } else if (*data != '\x02') { io_stream_set_error(&stream->istream.iostream, "Unsupported encrypted data 0x%02x", *data); return -1; } stream->format = DECRYPT_FORMAT_V2; data++; /* read flags */ uint32_t flags; if (!get_msb32(&data, end, &flags)) return 0; stream->flags = flags; /* get the total length of header */ uint32_t hdr_len; if (!get_msb32(&data, end, &hdr_len)) return 0; /* do not forget stream format */ if ((size_t)(end-data)+1 < hdr_len) return 0; int ret; if ((ret = i_stream_decrypt_header_contents(stream, data, hdr_len)) < 0) return -1; else if (ret == 0) { io_stream_set_error(&stream->istream.iostream, "Decryption error: truncate header length"); stream->istream.istream.stream_errno = EPIPE; return -1; } stream->initialized = TRUE; /* if it all went well, try to initialize decryption context */ if (!dcrypt_ctx_sym_init(stream->ctx_sym, &error)) { io_stream_set_error(&stream->istream.iostream, "Decryption init error: %s", error); return -1; } return hdr_len; } static void i_stream_decrypt_realloc_buf_if_needed(struct decrypt_istream *dstream) { if (!dstream->snapshot_pending) return; /* buf exists in a snapshot. Leave it be and create a copy of it that we modify. */ buffer_t *old_buf = dstream->buf; dstream->buf = buffer_create_dynamic(default_pool, I_MAX(512, old_buf->used)); buffer_append(dstream->buf, old_buf->data, old_buf->used); dstream->snapshot_pending = FALSE; dstream->istream.buffer = dstream->buf->data; } static ssize_t i_stream_decrypt_read(struct istream_private *stream) { struct decrypt_istream *dstream = (struct decrypt_istream *)stream; const unsigned char *data; size_t size, decrypt_size; const char *error = NULL; int ret; bool check_mac = FALSE; /* not if it's broken */ if (stream->istream.stream_errno != 0) return -1; i_stream_decrypt_realloc_buf_if_needed(dstream); for (;;) { /* remove skipped data from buffer */ if (stream->skip > 0) { i_assert(stream->skip <= dstream->buf->used); buffer_delete(dstream->buf, 0, stream->skip); stream->pos -= stream->skip; stream->skip = 0; } stream->buffer = dstream->buf->data; i_assert(stream->pos <= dstream->buf->used); if (stream->pos >= dstream->istream.max_buffer_size) { /* stream buffer still at maximum */ return -2; } /* if something is already decrypted, return as much of it as we can */ if (dstream->initialized && dstream->buf->used > 0) { size_t new_pos, bytes; /* only return up to max_buffer_size bytes, even when buffer actually has more, as not to confuse the caller */ if (dstream->buf->used <= dstream->istream.max_buffer_size) { new_pos = dstream->buf->used; if (dstream->finalized) stream->istream.eof = TRUE; } else { new_pos = dstream->istream.max_buffer_size; } bytes = new_pos - stream->pos; stream->pos = new_pos; if (bytes > 0) return (ssize_t)bytes; } if (dstream->finalized) { /* all data decrypted */ stream->istream.eof = TRUE; return -1; } /* need to read more input */ ret = i_stream_read_memarea(stream->parent); if (ret == 0) return ret; data = i_stream_get_data(stream->parent, &size); if (ret == -1 && (size == 0 || stream->parent->stream_errno != 0)) { stream->istream.stream_errno = stream->parent->stream_errno; /* file was empty */ if (!dstream->initialized && size == 0 && stream->parent->eof) { stream->istream.eof = TRUE; return -1; } if (stream->istream.stream_errno != 0) return -1; if (!dstream->initialized) { io_stream_set_error(&stream->iostream, "Decryption error: %s", "Input truncated in decryption header"); stream->istream.stream_errno = EPIPE; return -1; } /* final block */ if (dcrypt_ctx_sym_final(dstream->ctx_sym, dstream->buf, &error)) { dstream->finalized = TRUE; continue; } io_stream_set_error(&stream->iostream, "MAC error: %s", error); stream->istream.stream_errno = EINVAL; return -1; } if (!dstream->initialized) { ssize_t hret; if ((hret=i_stream_decrypt_read_header( dstream, data, size)) <= 0) { if (hret < 0) { if (stream->istream.stream_errno == 0) /* assume temporary failure */ stream->istream.stream_errno = EIO; return -1; } if (hret == 0 && stream->parent->eof) { /* not encrypted by us */ stream->istream.stream_errno = EPIPE; io_stream_set_error(&stream->iostream, "Truncated header"); return -1; } } if (hret == 0) { /* see if we can get more data */ if (ret == -2) { stream->istream.stream_errno = EINVAL; io_stream_set_error(&stream->iostream, "Header too large " "(more than %zu bytes)", size); return -1; } continue; } else { /* clean up buffer */ safe_memset(buffer_get_modifiable_data(dstream->buf, 0), 0, dstream->buf->used); buffer_set_used_size(dstream->buf, 0); i_stream_skip(stream->parent, hret); } data = i_stream_get_data(stream->parent, &size); } decrypt_size = size; if (dstream->use_mac) { if (stream->parent->eof) { if (decrypt_size < dstream->ftr) { io_stream_set_error(&stream->iostream, "Decryption error: " "footer is longer than data"); stream->istream.stream_errno = EINVAL; return -1; } check_mac = TRUE; } else { /* ignore footer's length of data until we reach EOF */ size -= dstream->ftr; } decrypt_size -= dstream->ftr; if ((dstream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC) { if (!dcrypt_ctx_hmac_update(dstream->ctx_mac, data, decrypt_size, &error)) { io_stream_set_error(&stream->iostream, "MAC error: %s", error); stream->istream.stream_errno = EINVAL; return -1; } } } if (check_mac) { if ((dstream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC) { unsigned char dgst[dcrypt_ctx_hmac_get_digest_length(dstream->ctx_mac)]; buffer_t db; buffer_create_from_data(&db, dgst, sizeof(dgst)); if (!dcrypt_ctx_hmac_final(dstream->ctx_mac, &db, &error)) { io_stream_set_error(&stream->iostream, "Cannot verify MAC: %s", error); stream->istream.stream_errno = EINVAL; return -1; } if (memcmp(dgst, data + decrypt_size, dcrypt_ctx_hmac_get_digest_length(dstream->ctx_mac)) != 0) { io_stream_set_error(&stream->iostream, "Cannot verify MAC: mismatch"); stream->istream.stream_errno = EINVAL; return -1; } } else if ((dstream->flags & IO_STREAM_ENC_INTEGRITY_AEAD) == IO_STREAM_ENC_INTEGRITY_AEAD) { dcrypt_ctx_sym_set_tag(dstream->ctx_sym, data + decrypt_size, dstream->ftr); } } if (!dcrypt_ctx_sym_update(dstream->ctx_sym, data, decrypt_size, dstream->buf, &error)) { io_stream_set_error(&stream->iostream, "Decryption error: %s", error); stream->istream.stream_errno = EINVAL; return -1; } i_stream_skip(stream->parent, size); } } static void i_stream_decrypt_seek(struct istream_private *stream, uoff_t v_offset, bool mark ATTR_UNUSED) { struct decrypt_istream *dstream = (struct decrypt_istream *)stream; i_stream_decrypt_realloc_buf_if_needed(dstream); if (i_stream_nonseekable_try_seek(stream, v_offset)) return; /* have to seek backwards - reset crypt state and retry */ i_stream_decrypt_reset(dstream); if (!i_stream_nonseekable_try_seek(stream, v_offset)) i_unreached(); } static void i_stream_decrypt_close(struct iostream_private *stream, bool close_parent) { struct decrypt_istream *dstream = (struct decrypt_istream *)stream; if (close_parent) i_stream_close(dstream->istream.parent); } static void i_stream_decrypt_snapshot_free(struct istream_snapshot *_snapshot) { struct decrypt_istream_snapshot *snapshot = container_of(_snapshot, struct decrypt_istream_snapshot, snapshot); if (snapshot->dstream->buf != snapshot->buf) buffer_free(&snapshot->buf); else { i_assert(snapshot->dstream->snapshot_pending); snapshot->dstream->snapshot_pending = FALSE; } i_free(snapshot); } static struct istream_snapshot * i_stream_decrypt_snapshot(struct istream_private *stream, struct istream_snapshot *prev_snapshot) { struct decrypt_istream *dstream = (struct decrypt_istream *)stream; struct decrypt_istream_snapshot *snapshot; if (stream->buffer != dstream->buf->data) { /* reading body */ return i_stream_default_snapshot(stream, prev_snapshot); } /* snapshot the header buffer */ snapshot = i_new(struct decrypt_istream_snapshot, 1); snapshot->dstream = dstream; snapshot->buf = dstream->buf; snapshot->snapshot.free = i_stream_decrypt_snapshot_free; snapshot->snapshot.prev_snapshot = prev_snapshot; dstream->snapshot_pending = TRUE; return &snapshot->snapshot; } static void i_stream_decrypt_destroy(struct iostream_private *stream) { struct decrypt_istream *dstream = (struct decrypt_istream *)stream; if (!dstream->snapshot_pending) buffer_free(&dstream->buf); else { /* Clear buf to make sure i_stream_decrypt_snapshot_free() frees it. */ dstream->buf = NULL; } if (dstream->iv != NULL) i_free_and_null(dstream->iv); if (dstream->ctx_sym != NULL) dcrypt_ctx_sym_destroy(&dstream->ctx_sym); if (dstream->ctx_mac != NULL) dcrypt_ctx_hmac_destroy(&dstream->ctx_mac); if (dstream->priv_key != NULL) dcrypt_key_unref_private(&dstream->priv_key); i_stream_unref(&dstream->istream.parent); } static struct decrypt_istream * i_stream_create_decrypt_common(struct istream *input) { struct decrypt_istream *dstream; i_assert(input->real_stream->max_buffer_size > 0); dstream = i_new(struct decrypt_istream, 1); dstream->istream.max_buffer_size = input->real_stream->max_buffer_size; dstream->istream.read = i_stream_decrypt_read; dstream->istream.snapshot = i_stream_decrypt_snapshot; if (input->seekable) dstream->istream.seek = i_stream_decrypt_seek; dstream->istream.iostream.close = i_stream_decrypt_close; dstream->istream.iostream.destroy = i_stream_decrypt_destroy; dstream->istream.istream.readable_fd = FALSE; dstream->istream.istream.blocking = input->blocking; dstream->istream.istream.seekable = input->seekable; dstream->buf = buffer_create_dynamic(default_pool, 512); (void)i_stream_create(&dstream->istream, input, i_stream_get_fd(input), 0); return dstream; } struct istream * i_stream_create_decrypt(struct istream *input, struct dcrypt_private_key *priv_key) { struct decrypt_istream *dstream; dstream = i_stream_create_decrypt_common(input); dcrypt_key_ref_private(priv_key); dstream->priv_key = priv_key; return &dstream->istream.istream; } struct istream * i_stream_create_sym_decrypt(struct istream *input, struct dcrypt_context_symmetric *ctx) { const char *error; int ec; struct decrypt_istream *dstream; dstream = i_stream_create_decrypt_common(input); dstream->use_mac = FALSE; dstream->initialized = TRUE; dstream->symmetric = TRUE; if (!dcrypt_ctx_sym_init(ctx, &error)) ec = -1; else ec = 0; dstream->ctx_sym = ctx; if (ec != 0) { io_stream_set_error(&dstream->istream.iostream, "Cannot initialize decryption: %s", error); dstream->istream.istream.stream_errno = EIO; }; return &dstream->istream.istream; } struct istream * i_stream_create_decrypt_callback(struct istream *input, i_stream_decrypt_get_key_callback_t *callback, void *context) { struct decrypt_istream *dstream; i_assert(callback != NULL); dstream = i_stream_create_decrypt_common(input); dstream->key_callback = callback; dstream->key_context = context; return &dstream->istream.istream; } dovecot-2.3.21.1/src/lib-dcrypt/Makefile.in0000644000000000000000000015545214656633610015223 00000000000000# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ pkglibexecdir = $(libexecdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ @BUILD_DCRYPT_OPENSSL_TRUE@am__append_1 = libdcrypt_openssl.la noinst_PROGRAMS = $(am__EXEEXT_1) subdir = src/lib-dcrypt ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ $(top_srcdir)/m4/flexible_array_member.m4 \ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/m4/pr_set_dumpable.m4 \ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ $(top_srcdir)/m4/typeof_dev_t.m4 \ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ $(top_srcdir)/m4/want_apparmor.m4 \ $(top_srcdir)/m4/want_bsdauth.m4 \ $(top_srcdir)/m4/want_bzlib.m4 \ $(top_srcdir)/m4/want_cassandra.m4 \ $(top_srcdir)/m4/want_cdb.m4 \ $(top_srcdir)/m4/want_checkpassword.m4 \ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ $(top_srcdir)/m4/want_prefetch.m4 \ $(top_srcdir)/m4/want_shadow.m4 \ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ $(top_srcdir)/m4/want_sqlite.m4 \ $(top_srcdir)/m4/want_stemmer.m4 \ $(top_srcdir)/m4/want_systemd.m4 \ $(top_srcdir)/m4/want_textcat.m4 \ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \ $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__EXEEXT_1 = test-crypto$(EXEEXT) test-stream$(EXEEXT) PROGRAMS = $(noinst_PROGRAMS) am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ *) f=$$p;; \ esac; am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; am__install_max = 40 am__nobase_strip_setup = \ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` am__nobase_strip = \ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" am__nobase_list = $(am__nobase_strip_setup); \ for p in $$list; do echo "$$p $$p"; done | \ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ if (++n[$$2] == $(am__install_max)) \ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ END { for (dir in files) print dir, files[dir] }' am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' am__uninstall_files_from_dir = { \ test -z "$$files" \ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ $(am__cd) "$$dir" && rm -f $$files; }; \ } am__installdirs = "$(DESTDIR)$(pkglibdir)" \ "$(DESTDIR)$(pkginc_libdir)" LTLIBRARIES = $(noinst_LTLIBRARIES) $(pkglib_LTLIBRARIES) libdcrypt_la_LIBADD = am_libdcrypt_la_OBJECTS = libdcrypt_la-dcrypt.lo \ libdcrypt_la-istream-decrypt.lo \ libdcrypt_la-ostream-encrypt.lo libdcrypt_la_OBJECTS = $(am_libdcrypt_la_OBJECTS) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent am__v_lt_1 = libdcrypt_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(libdcrypt_la_CFLAGS) \ $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ am__DEPENDENCIES_1 = am__libdcrypt_openssl_la_SOURCES_DIST = dcrypt-openssl.c @BUILD_DCRYPT_OPENSSL_TRUE@am_libdcrypt_openssl_la_OBJECTS = libdcrypt_openssl_la-dcrypt-openssl.lo libdcrypt_openssl_la_OBJECTS = $(am_libdcrypt_openssl_la_OBJECTS) libdcrypt_openssl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(libdcrypt_openssl_la_CFLAGS) $(CFLAGS) \ $(libdcrypt_openssl_la_LDFLAGS) $(LDFLAGS) -o $@ @BUILD_DCRYPT_OPENSSL_TRUE@am_libdcrypt_openssl_la_rpath = -rpath \ @BUILD_DCRYPT_OPENSSL_TRUE@ $(pkglibdir) am__objects_1 = test_crypto-dcrypt.$(OBJEXT) \ test_crypto-istream-decrypt.$(OBJEXT) \ test_crypto-ostream-encrypt.$(OBJEXT) am_test_crypto_OBJECTS = $(am__objects_1) \ test_crypto-test-crypto.$(OBJEXT) test_crypto_OBJECTS = $(am_test_crypto_OBJECTS) am__DEPENDENCIES_2 = $(LIBDOVECOT_TEST_DEPS) $(am__DEPENDENCIES_1) test_crypto_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(test_crypto_CFLAGS) \ $(CFLAGS) $(test_crypto_LDFLAGS) $(LDFLAGS) -o $@ am__objects_2 = test_stream-dcrypt.$(OBJEXT) \ test_stream-istream-decrypt.$(OBJEXT) \ test_stream-ostream-encrypt.$(OBJEXT) am_test_stream_OBJECTS = $(am__objects_2) \ test_stream-test-stream.$(OBJEXT) test_stream_OBJECTS = $(am_test_stream_OBJECTS) test_stream_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(test_stream_CFLAGS) \ $(CFLAGS) $(test_stream_LDFLAGS) $(LDFLAGS) -o $@ AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/libdcrypt_la-dcrypt.Plo \ ./$(DEPDIR)/libdcrypt_la-istream-decrypt.Plo \ ./$(DEPDIR)/libdcrypt_la-ostream-encrypt.Plo \ ./$(DEPDIR)/libdcrypt_openssl_la-dcrypt-openssl.Plo \ ./$(DEPDIR)/test_crypto-dcrypt.Po \ ./$(DEPDIR)/test_crypto-istream-decrypt.Po \ ./$(DEPDIR)/test_crypto-ostream-encrypt.Po \ ./$(DEPDIR)/test_crypto-test-crypto.Po \ ./$(DEPDIR)/test_stream-dcrypt.Po \ ./$(DEPDIR)/test_stream-istream-decrypt.Po \ ./$(DEPDIR)/test_stream-ostream-encrypt.Po \ ./$(DEPDIR)/test_stream-test-stream.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(libdcrypt_la_SOURCES) $(libdcrypt_openssl_la_SOURCES) \ $(test_crypto_SOURCES) $(test_stream_SOURCES) DIST_SOURCES = $(libdcrypt_la_SOURCES) \ $(am__libdcrypt_openssl_la_SOURCES_DIST) \ $(test_crypto_SOURCES) $(test_stream_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac HEADERS = $(pkginc_lib_HEADERS) am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` ETAGS = etags CTAGS = ctags am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTH_CFLAGS = @AUTH_CFLAGS@ AUTH_LIBS = @AUTH_LIBS@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ BINARY_CFLAGS = @BINARY_CFLAGS@ BINARY_LDFLAGS = @BINARY_LDFLAGS@ BISON = @BISON@ CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ CASSANDRA_LIBS = @CASSANDRA_LIBS@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CDB_LIBS = @CDB_LIBS@ CFLAGS = @CFLAGS@ CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ CLUCENE_LIBS = @CLUCENE_LIBS@ COMPRESS_LIBS = @COMPRESS_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CRYPT_LIBS = @CRYPT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DICT_LIBS = @DICT_LIBS@ DLLIB = @DLLIB@ DLLTOOL = @DLLTOOL@ DSYMUTIL = @DSYMUTIL@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ FGREP = @FGREP@ FLEX = @FLEX@ FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KRB5CONFIG = @KRB5CONFIG@ KRB5_CFLAGS = @KRB5_CFLAGS@ KRB5_LIBS = @KRB5_LIBS@ LD = @LD@ LDAP_LIBS = @LDAP_LIBS@ LDFLAGS = @LDFLAGS@ LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ LIBCAP = @LIBCAP@ LIBDOVECOT = @LIBDOVECOT@ LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ LIBICONV = @LIBICONV@ LIBICU_CFLAGS = @LIBICU_CFLAGS@ LIBICU_LIBS = @LIBICU_LIBS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ LIBTOOL = @LIBTOOL@ LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ LIBWRAP_LIBS = @LIBWRAP_LIBS@ LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ LIPO = @LIPO@ LN_S = @LN_S@ LTLIBICONV = @LTLIBICONV@ LTLIBOBJS = @LTLIBOBJS@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ LUA_CFLAGS = @LUA_CFLAGS@ LUA_LIBS = @LUA_LIBS@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ MKDIR_P = @MKDIR_P@ MODULE_LIBS = @MODULE_LIBS@ MODULE_SUFFIX = @MODULE_SUFFIX@ MYSQL_CFLAGS = @MYSQL_CFLAGS@ MYSQL_CONFIG = @MYSQL_CONFIG@ MYSQL_LIBS = @MYSQL_LIBS@ NM = @NM@ NMEDIT = @NMEDIT@ NOPLUGIN_LDFLAGS = OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PANDOC = @PANDOC@ PATH_SEPARATOR = @PATH_SEPARATOR@ PGSQL_CFLAGS = @PGSQL_CFLAGS@ PGSQL_LIBS = @PGSQL_LIBS@ PG_CONFIG = @PG_CONFIG@ PIE_CFLAGS = @PIE_CFLAGS@ PIE_LDFLAGS = @PIE_LDFLAGS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ QUOTA_LIBS = @QUOTA_LIBS@ RANLIB = @RANLIB@ RELRO_LDFLAGS = @RELRO_LDFLAGS@ RPCGEN = @RPCGEN@ RUN_TEST = @RUN_TEST@ SED = @SED@ SETTING_FILES = @SETTING_FILES@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SQLITE_CFLAGS = @SQLITE_CFLAGS@ SQLITE_LIBS = @SQLITE_LIBS@ SQL_CFLAGS = @SQL_CFLAGS@ SQL_LIBS = @SQL_LIBS@ SSL_CFLAGS = @SSL_CFLAGS@ SSL_LIBS = @SSL_LIBS@ STRIP = @STRIP@ SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ SYSTEMD_LIBS = @SYSTEMD_LIBS@ VALGRIND = @VALGRIND@ VERSION = @VERSION@ ZSTD_CFLAGS = @ZSTD_CFLAGS@ ZSTD_LIBS = @ZSTD_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_AR = @ac_ct_AR@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ dict_drivers = @dict_drivers@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ moduledir = @moduledir@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ rundir = @rundir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sql_drivers = @sql_drivers@ srcdir = @srcdir@ ssldir = @ssldir@ statedir = @statedir@ sysconfdir = @sysconfdir@ systemdservicetype = @systemdservicetype@ systemdsystemunitdir = @systemdsystemunitdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ noinst_LTLIBRARIES = libdcrypt.la pkglib_LTLIBRARIES = $(am__append_1) AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-ssl-iostream \ -DDCRYPT_MODULE_DIR=\"$(pkglibdir)\" libdcrypt_la_SOURCES = \ dcrypt.c \ istream-decrypt.c \ ostream-encrypt.c libdcrypt_la_CFLAGS = $(AM_CPPFLAGS) \ -DDCRYPT_MODULE_DIR=\"$(pkglibdir)\" @BUILD_DCRYPT_OPENSSL_TRUE@libdcrypt_openssl_la_SOURCES = dcrypt-openssl.c @BUILD_DCRYPT_OPENSSL_TRUE@libdcrypt_openssl_la_LDFLAGS = -module -avoid-version ../lib-ssl-iostream/libssl_iostream_openssl.la @BUILD_DCRYPT_OPENSSL_TRUE@libdcrypt_openssl_la_LIBADD = $(SSL_LIBS) @BUILD_DCRYPT_OPENSSL_TRUE@libdcrypt_openssl_la_DEPENDENCIES = ../lib-ssl-iostream/libssl_iostream_openssl.la @BUILD_DCRYPT_OPENSSL_TRUE@libdcrypt_openssl_la_CFLAGS = $(AM_CPPFLAGS) \ @BUILD_DCRYPT_OPENSSL_TRUE@ $(SSL_CFLAGS) headers = \ dcrypt.h \ dcrypt-iostream.h \ dcrypt-private.h \ ostream-encrypt.h \ istream-decrypt.h pkginc_libdir = $(pkgincludedir) pkginc_lib_HEADERS = $(headers) EXTRA_DIST = \ sample-v1.asc \ sample-v1_short.asc \ sample-v2.asc test_programs = test-crypto test-stream LIBDOVECOT_TEST_DEPS = \ ../lib-ssl-iostream/libssl_iostream.la \ ../lib-test/libtest.la \ ../lib/liblib.la LIBDOVECOT_TEST = \ $(LIBDOVECOT_TEST_DEPS) \ $(MODULE_LIBS) test_crypto_LDADD = $(LIBDOVECOT_TEST) test_crypto_DEPENDENCIES = $(LIBDOVECOT_TEST_DEPS) @HAVE_WHOLE_ARCHIVE_TRUE@test_crypto_LDFLAGS = -Wl,$(LD_WHOLE_ARCHIVE),../lib-ssl-iostream/.libs/libssl_iostream.a,$(LD_NO_WHOLE_ARCHIVE) test_crypto_CFLAGS = $(AM_CPPFLAGS) -DDCRYPT_SRC_DIR=\"$(top_srcdir)/src/lib-dcrypt\" test_crypto_SOURCES = $(libdcrypt_la_SOURCES) test-crypto.c test_stream_LDADD = $(LIBDOVECOT_TEST) test_stream_DEPENDENCIES = $(LIBDOVECOT_TEST_DEPS) @HAVE_WHOLE_ARCHIVE_TRUE@test_stream_LDFLAGS = -Wl,$(LD_WHOLE_ARCHIVE),../lib-ssl-iostream/.libs/libssl_iostream.a,$(LD_NO_WHOLE_ARCHIVE) test_stream_CFLAGS = $(AM_CPPFLAGS) -DDCRYPT_SRC_DIR=\"$(top_srcdir)/src/lib-dcrypt\" test_stream_SOURCES = $(libdcrypt_la_SOURCES) test-stream.c all: all-am .SUFFIXES: .SUFFIXES: .c .lo .o .obj $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-dcrypt/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/lib-dcrypt/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): clean-noinstPROGRAMS: @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ echo " rm -f" $$list; \ rm -f $$list || exit $$?; \ test -n "$(EXEEXT)" || exit 0; \ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list clean-noinstLTLIBRARIES: -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) @list='$(noinst_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } install-pkglibLTLIBRARIES: $(pkglib_LTLIBRARIES) @$(NORMAL_INSTALL) @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ list2=; for p in $$list; do \ if test -f $$p; then \ list2="$$list2 $$p"; \ else :; fi; \ done; \ test -z "$$list2" || { \ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkglibdir)" || exit 1; \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(pkglibdir)'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(pkglibdir)"; \ } uninstall-pkglibLTLIBRARIES: @$(NORMAL_UNINSTALL) @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \ for p in $$list; do \ $(am__strip_dir) \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(pkglibdir)/$$f'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(pkglibdir)/$$f"; \ done clean-pkglibLTLIBRARIES: -test -z "$(pkglib_LTLIBRARIES)" || rm -f $(pkglib_LTLIBRARIES) @list='$(pkglib_LTLIBRARIES)'; \ locs=`for p in $$list; do echo $$p; done | \ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ sort -u`; \ test -z "$$locs" || { \ echo rm -f $${locs}; \ rm -f $${locs}; \ } libdcrypt.la: $(libdcrypt_la_OBJECTS) $(libdcrypt_la_DEPENDENCIES) $(EXTRA_libdcrypt_la_DEPENDENCIES) $(AM_V_CCLD)$(libdcrypt_la_LINK) $(libdcrypt_la_OBJECTS) $(libdcrypt_la_LIBADD) $(LIBS) libdcrypt_openssl.la: $(libdcrypt_openssl_la_OBJECTS) $(libdcrypt_openssl_la_DEPENDENCIES) $(EXTRA_libdcrypt_openssl_la_DEPENDENCIES) $(AM_V_CCLD)$(libdcrypt_openssl_la_LINK) $(am_libdcrypt_openssl_la_rpath) $(libdcrypt_openssl_la_OBJECTS) $(libdcrypt_openssl_la_LIBADD) $(LIBS) test-crypto$(EXEEXT): $(test_crypto_OBJECTS) $(test_crypto_DEPENDENCIES) $(EXTRA_test_crypto_DEPENDENCIES) @rm -f test-crypto$(EXEEXT) $(AM_V_CCLD)$(test_crypto_LINK) $(test_crypto_OBJECTS) $(test_crypto_LDADD) $(LIBS) test-stream$(EXEEXT): $(test_stream_OBJECTS) $(test_stream_DEPENDENCIES) $(EXTRA_test_stream_DEPENDENCIES) @rm -f test-stream$(EXEEXT) $(AM_V_CCLD)$(test_stream_LINK) $(test_stream_OBJECTS) $(test_stream_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdcrypt_la-dcrypt.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdcrypt_la-istream-decrypt.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdcrypt_la-ostream-encrypt.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdcrypt_openssl_la-dcrypt-openssl.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_crypto-dcrypt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_crypto-istream-decrypt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_crypto-ostream-encrypt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_crypto-test-crypto.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_stream-dcrypt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_stream-istream-decrypt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_stream-ostream-encrypt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_stream-test-stream.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @echo '# dummy' >$@-t && $(am__mv) $@-t $@ am--depfiles: $(am__depfiles_remade) .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: @am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< libdcrypt_la-dcrypt.lo: dcrypt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libdcrypt_la_CFLAGS) $(CFLAGS) -MT libdcrypt_la-dcrypt.lo -MD -MP -MF $(DEPDIR)/libdcrypt_la-dcrypt.Tpo -c -o libdcrypt_la-dcrypt.lo `test -f 'dcrypt.c' || echo '$(srcdir)/'`dcrypt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdcrypt_la-dcrypt.Tpo $(DEPDIR)/libdcrypt_la-dcrypt.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dcrypt.c' object='libdcrypt_la-dcrypt.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libdcrypt_la_CFLAGS) $(CFLAGS) -c -o libdcrypt_la-dcrypt.lo `test -f 'dcrypt.c' || echo '$(srcdir)/'`dcrypt.c libdcrypt_la-istream-decrypt.lo: istream-decrypt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libdcrypt_la_CFLAGS) $(CFLAGS) -MT libdcrypt_la-istream-decrypt.lo -MD -MP -MF $(DEPDIR)/libdcrypt_la-istream-decrypt.Tpo -c -o libdcrypt_la-istream-decrypt.lo `test -f 'istream-decrypt.c' || echo '$(srcdir)/'`istream-decrypt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdcrypt_la-istream-decrypt.Tpo $(DEPDIR)/libdcrypt_la-istream-decrypt.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='istream-decrypt.c' object='libdcrypt_la-istream-decrypt.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libdcrypt_la_CFLAGS) $(CFLAGS) -c -o libdcrypt_la-istream-decrypt.lo `test -f 'istream-decrypt.c' || echo '$(srcdir)/'`istream-decrypt.c libdcrypt_la-ostream-encrypt.lo: ostream-encrypt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libdcrypt_la_CFLAGS) $(CFLAGS) -MT libdcrypt_la-ostream-encrypt.lo -MD -MP -MF $(DEPDIR)/libdcrypt_la-ostream-encrypt.Tpo -c -o libdcrypt_la-ostream-encrypt.lo `test -f 'ostream-encrypt.c' || echo '$(srcdir)/'`ostream-encrypt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdcrypt_la-ostream-encrypt.Tpo $(DEPDIR)/libdcrypt_la-ostream-encrypt.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ostream-encrypt.c' object='libdcrypt_la-ostream-encrypt.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libdcrypt_la_CFLAGS) $(CFLAGS) -c -o libdcrypt_la-ostream-encrypt.lo `test -f 'ostream-encrypt.c' || echo '$(srcdir)/'`ostream-encrypt.c libdcrypt_openssl_la-dcrypt-openssl.lo: dcrypt-openssl.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libdcrypt_openssl_la_CFLAGS) $(CFLAGS) -MT libdcrypt_openssl_la-dcrypt-openssl.lo -MD -MP -MF $(DEPDIR)/libdcrypt_openssl_la-dcrypt-openssl.Tpo -c -o libdcrypt_openssl_la-dcrypt-openssl.lo `test -f 'dcrypt-openssl.c' || echo '$(srcdir)/'`dcrypt-openssl.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdcrypt_openssl_la-dcrypt-openssl.Tpo $(DEPDIR)/libdcrypt_openssl_la-dcrypt-openssl.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dcrypt-openssl.c' object='libdcrypt_openssl_la-dcrypt-openssl.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libdcrypt_openssl_la_CFLAGS) $(CFLAGS) -c -o libdcrypt_openssl_la-dcrypt-openssl.lo `test -f 'dcrypt-openssl.c' || echo '$(srcdir)/'`dcrypt-openssl.c test_crypto-dcrypt.o: dcrypt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_crypto_CFLAGS) $(CFLAGS) -MT test_crypto-dcrypt.o -MD -MP -MF $(DEPDIR)/test_crypto-dcrypt.Tpo -c -o test_crypto-dcrypt.o `test -f 'dcrypt.c' || echo '$(srcdir)/'`dcrypt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_crypto-dcrypt.Tpo $(DEPDIR)/test_crypto-dcrypt.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dcrypt.c' object='test_crypto-dcrypt.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_crypto_CFLAGS) $(CFLAGS) -c -o test_crypto-dcrypt.o `test -f 'dcrypt.c' || echo '$(srcdir)/'`dcrypt.c test_crypto-dcrypt.obj: dcrypt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_crypto_CFLAGS) $(CFLAGS) -MT test_crypto-dcrypt.obj -MD -MP -MF $(DEPDIR)/test_crypto-dcrypt.Tpo -c -o test_crypto-dcrypt.obj `if test -f 'dcrypt.c'; then $(CYGPATH_W) 'dcrypt.c'; else $(CYGPATH_W) '$(srcdir)/dcrypt.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_crypto-dcrypt.Tpo $(DEPDIR)/test_crypto-dcrypt.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dcrypt.c' object='test_crypto-dcrypt.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_crypto_CFLAGS) $(CFLAGS) -c -o test_crypto-dcrypt.obj `if test -f 'dcrypt.c'; then $(CYGPATH_W) 'dcrypt.c'; else $(CYGPATH_W) '$(srcdir)/dcrypt.c'; fi` test_crypto-istream-decrypt.o: istream-decrypt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_crypto_CFLAGS) $(CFLAGS) -MT test_crypto-istream-decrypt.o -MD -MP -MF $(DEPDIR)/test_crypto-istream-decrypt.Tpo -c -o test_crypto-istream-decrypt.o `test -f 'istream-decrypt.c' || echo '$(srcdir)/'`istream-decrypt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_crypto-istream-decrypt.Tpo $(DEPDIR)/test_crypto-istream-decrypt.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='istream-decrypt.c' object='test_crypto-istream-decrypt.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_crypto_CFLAGS) $(CFLAGS) -c -o test_crypto-istream-decrypt.o `test -f 'istream-decrypt.c' || echo '$(srcdir)/'`istream-decrypt.c test_crypto-istream-decrypt.obj: istream-decrypt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_crypto_CFLAGS) $(CFLAGS) -MT test_crypto-istream-decrypt.obj -MD -MP -MF $(DEPDIR)/test_crypto-istream-decrypt.Tpo -c -o test_crypto-istream-decrypt.obj `if test -f 'istream-decrypt.c'; then $(CYGPATH_W) 'istream-decrypt.c'; else $(CYGPATH_W) '$(srcdir)/istream-decrypt.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_crypto-istream-decrypt.Tpo $(DEPDIR)/test_crypto-istream-decrypt.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='istream-decrypt.c' object='test_crypto-istream-decrypt.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_crypto_CFLAGS) $(CFLAGS) -c -o test_crypto-istream-decrypt.obj `if test -f 'istream-decrypt.c'; then $(CYGPATH_W) 'istream-decrypt.c'; else $(CYGPATH_W) '$(srcdir)/istream-decrypt.c'; fi` test_crypto-ostream-encrypt.o: ostream-encrypt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_crypto_CFLAGS) $(CFLAGS) -MT test_crypto-ostream-encrypt.o -MD -MP -MF $(DEPDIR)/test_crypto-ostream-encrypt.Tpo -c -o test_crypto-ostream-encrypt.o `test -f 'ostream-encrypt.c' || echo '$(srcdir)/'`ostream-encrypt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_crypto-ostream-encrypt.Tpo $(DEPDIR)/test_crypto-ostream-encrypt.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ostream-encrypt.c' object='test_crypto-ostream-encrypt.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_crypto_CFLAGS) $(CFLAGS) -c -o test_crypto-ostream-encrypt.o `test -f 'ostream-encrypt.c' || echo '$(srcdir)/'`ostream-encrypt.c test_crypto-ostream-encrypt.obj: ostream-encrypt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_crypto_CFLAGS) $(CFLAGS) -MT test_crypto-ostream-encrypt.obj -MD -MP -MF $(DEPDIR)/test_crypto-ostream-encrypt.Tpo -c -o test_crypto-ostream-encrypt.obj `if test -f 'ostream-encrypt.c'; then $(CYGPATH_W) 'ostream-encrypt.c'; else $(CYGPATH_W) '$(srcdir)/ostream-encrypt.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_crypto-ostream-encrypt.Tpo $(DEPDIR)/test_crypto-ostream-encrypt.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ostream-encrypt.c' object='test_crypto-ostream-encrypt.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_crypto_CFLAGS) $(CFLAGS) -c -o test_crypto-ostream-encrypt.obj `if test -f 'ostream-encrypt.c'; then $(CYGPATH_W) 'ostream-encrypt.c'; else $(CYGPATH_W) '$(srcdir)/ostream-encrypt.c'; fi` test_crypto-test-crypto.o: test-crypto.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_crypto_CFLAGS) $(CFLAGS) -MT test_crypto-test-crypto.o -MD -MP -MF $(DEPDIR)/test_crypto-test-crypto.Tpo -c -o test_crypto-test-crypto.o `test -f 'test-crypto.c' || echo '$(srcdir)/'`test-crypto.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_crypto-test-crypto.Tpo $(DEPDIR)/test_crypto-test-crypto.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-crypto.c' object='test_crypto-test-crypto.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_crypto_CFLAGS) $(CFLAGS) -c -o test_crypto-test-crypto.o `test -f 'test-crypto.c' || echo '$(srcdir)/'`test-crypto.c test_crypto-test-crypto.obj: test-crypto.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_crypto_CFLAGS) $(CFLAGS) -MT test_crypto-test-crypto.obj -MD -MP -MF $(DEPDIR)/test_crypto-test-crypto.Tpo -c -o test_crypto-test-crypto.obj `if test -f 'test-crypto.c'; then $(CYGPATH_W) 'test-crypto.c'; else $(CYGPATH_W) '$(srcdir)/test-crypto.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_crypto-test-crypto.Tpo $(DEPDIR)/test_crypto-test-crypto.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-crypto.c' object='test_crypto-test-crypto.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_crypto_CFLAGS) $(CFLAGS) -c -o test_crypto-test-crypto.obj `if test -f 'test-crypto.c'; then $(CYGPATH_W) 'test-crypto.c'; else $(CYGPATH_W) '$(srcdir)/test-crypto.c'; fi` test_stream-dcrypt.o: dcrypt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_stream_CFLAGS) $(CFLAGS) -MT test_stream-dcrypt.o -MD -MP -MF $(DEPDIR)/test_stream-dcrypt.Tpo -c -o test_stream-dcrypt.o `test -f 'dcrypt.c' || echo '$(srcdir)/'`dcrypt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_stream-dcrypt.Tpo $(DEPDIR)/test_stream-dcrypt.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dcrypt.c' object='test_stream-dcrypt.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_stream_CFLAGS) $(CFLAGS) -c -o test_stream-dcrypt.o `test -f 'dcrypt.c' || echo '$(srcdir)/'`dcrypt.c test_stream-dcrypt.obj: dcrypt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_stream_CFLAGS) $(CFLAGS) -MT test_stream-dcrypt.obj -MD -MP -MF $(DEPDIR)/test_stream-dcrypt.Tpo -c -o test_stream-dcrypt.obj `if test -f 'dcrypt.c'; then $(CYGPATH_W) 'dcrypt.c'; else $(CYGPATH_W) '$(srcdir)/dcrypt.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_stream-dcrypt.Tpo $(DEPDIR)/test_stream-dcrypt.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dcrypt.c' object='test_stream-dcrypt.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_stream_CFLAGS) $(CFLAGS) -c -o test_stream-dcrypt.obj `if test -f 'dcrypt.c'; then $(CYGPATH_W) 'dcrypt.c'; else $(CYGPATH_W) '$(srcdir)/dcrypt.c'; fi` test_stream-istream-decrypt.o: istream-decrypt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_stream_CFLAGS) $(CFLAGS) -MT test_stream-istream-decrypt.o -MD -MP -MF $(DEPDIR)/test_stream-istream-decrypt.Tpo -c -o test_stream-istream-decrypt.o `test -f 'istream-decrypt.c' || echo '$(srcdir)/'`istream-decrypt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_stream-istream-decrypt.Tpo $(DEPDIR)/test_stream-istream-decrypt.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='istream-decrypt.c' object='test_stream-istream-decrypt.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_stream_CFLAGS) $(CFLAGS) -c -o test_stream-istream-decrypt.o `test -f 'istream-decrypt.c' || echo '$(srcdir)/'`istream-decrypt.c test_stream-istream-decrypt.obj: istream-decrypt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_stream_CFLAGS) $(CFLAGS) -MT test_stream-istream-decrypt.obj -MD -MP -MF $(DEPDIR)/test_stream-istream-decrypt.Tpo -c -o test_stream-istream-decrypt.obj `if test -f 'istream-decrypt.c'; then $(CYGPATH_W) 'istream-decrypt.c'; else $(CYGPATH_W) '$(srcdir)/istream-decrypt.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_stream-istream-decrypt.Tpo $(DEPDIR)/test_stream-istream-decrypt.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='istream-decrypt.c' object='test_stream-istream-decrypt.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_stream_CFLAGS) $(CFLAGS) -c -o test_stream-istream-decrypt.obj `if test -f 'istream-decrypt.c'; then $(CYGPATH_W) 'istream-decrypt.c'; else $(CYGPATH_W) '$(srcdir)/istream-decrypt.c'; fi` test_stream-ostream-encrypt.o: ostream-encrypt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_stream_CFLAGS) $(CFLAGS) -MT test_stream-ostream-encrypt.o -MD -MP -MF $(DEPDIR)/test_stream-ostream-encrypt.Tpo -c -o test_stream-ostream-encrypt.o `test -f 'ostream-encrypt.c' || echo '$(srcdir)/'`ostream-encrypt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_stream-ostream-encrypt.Tpo $(DEPDIR)/test_stream-ostream-encrypt.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ostream-encrypt.c' object='test_stream-ostream-encrypt.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_stream_CFLAGS) $(CFLAGS) -c -o test_stream-ostream-encrypt.o `test -f 'ostream-encrypt.c' || echo '$(srcdir)/'`ostream-encrypt.c test_stream-ostream-encrypt.obj: ostream-encrypt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_stream_CFLAGS) $(CFLAGS) -MT test_stream-ostream-encrypt.obj -MD -MP -MF $(DEPDIR)/test_stream-ostream-encrypt.Tpo -c -o test_stream-ostream-encrypt.obj `if test -f 'ostream-encrypt.c'; then $(CYGPATH_W) 'ostream-encrypt.c'; else $(CYGPATH_W) '$(srcdir)/ostream-encrypt.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_stream-ostream-encrypt.Tpo $(DEPDIR)/test_stream-ostream-encrypt.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ostream-encrypt.c' object='test_stream-ostream-encrypt.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_stream_CFLAGS) $(CFLAGS) -c -o test_stream-ostream-encrypt.obj `if test -f 'ostream-encrypt.c'; then $(CYGPATH_W) 'ostream-encrypt.c'; else $(CYGPATH_W) '$(srcdir)/ostream-encrypt.c'; fi` test_stream-test-stream.o: test-stream.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_stream_CFLAGS) $(CFLAGS) -MT test_stream-test-stream.o -MD -MP -MF $(DEPDIR)/test_stream-test-stream.Tpo -c -o test_stream-test-stream.o `test -f 'test-stream.c' || echo '$(srcdir)/'`test-stream.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_stream-test-stream.Tpo $(DEPDIR)/test_stream-test-stream.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-stream.c' object='test_stream-test-stream.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_stream_CFLAGS) $(CFLAGS) -c -o test_stream-test-stream.o `test -f 'test-stream.c' || echo '$(srcdir)/'`test-stream.c test_stream-test-stream.obj: test-stream.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_stream_CFLAGS) $(CFLAGS) -MT test_stream-test-stream.obj -MD -MP -MF $(DEPDIR)/test_stream-test-stream.Tpo -c -o test_stream-test-stream.obj `if test -f 'test-stream.c'; then $(CYGPATH_W) 'test-stream.c'; else $(CYGPATH_W) '$(srcdir)/test-stream.c'; fi` @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/test_stream-test-stream.Tpo $(DEPDIR)/test_stream-test-stream.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='test-stream.c' object='test_stream-test-stream.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_stream_CFLAGS) $(CFLAGS) -c -o test_stream-test-stream.obj `if test -f 'test-stream.c'; then $(CYGPATH_W) 'test-stream.c'; else $(CYGPATH_W) '$(srcdir)/test-stream.c'; fi` mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) @$(NORMAL_INSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ done | $(am__base_list) | \ while read files; do \ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ done uninstall-pkginc_libHEADERS: @$(NORMAL_UNINSTALL) @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am $(MAKE) $(AM_MAKEFLAGS) check-local check: check-am all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(pkglibdir)" "$(DESTDIR)$(pkginc_libdir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ clean-noinstPROGRAMS clean-pkglibLTLIBRARIES mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/libdcrypt_la-dcrypt.Plo -rm -f ./$(DEPDIR)/libdcrypt_la-istream-decrypt.Plo -rm -f ./$(DEPDIR)/libdcrypt_la-ostream-encrypt.Plo -rm -f ./$(DEPDIR)/libdcrypt_openssl_la-dcrypt-openssl.Plo -rm -f ./$(DEPDIR)/test_crypto-dcrypt.Po -rm -f ./$(DEPDIR)/test_crypto-istream-decrypt.Po -rm -f ./$(DEPDIR)/test_crypto-ostream-encrypt.Po -rm -f ./$(DEPDIR)/test_crypto-test-crypto.Po -rm -f ./$(DEPDIR)/test_stream-dcrypt.Po -rm -f ./$(DEPDIR)/test_stream-istream-decrypt.Po -rm -f ./$(DEPDIR)/test_stream-ostream-encrypt.Po -rm -f ./$(DEPDIR)/test_stream-test-stream.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-pkginc_libHEADERS install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-pkglibLTLIBRARIES install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/libdcrypt_la-dcrypt.Plo -rm -f ./$(DEPDIR)/libdcrypt_la-istream-decrypt.Plo -rm -f ./$(DEPDIR)/libdcrypt_la-ostream-encrypt.Plo -rm -f ./$(DEPDIR)/libdcrypt_openssl_la-dcrypt-openssl.Plo -rm -f ./$(DEPDIR)/test_crypto-dcrypt.Po -rm -f ./$(DEPDIR)/test_crypto-istream-decrypt.Po -rm -f ./$(DEPDIR)/test_crypto-ostream-encrypt.Po -rm -f ./$(DEPDIR)/test_crypto-test-crypto.Po -rm -f ./$(DEPDIR)/test_stream-dcrypt.Po -rm -f ./$(DEPDIR)/test_stream-istream-decrypt.Po -rm -f ./$(DEPDIR)/test_stream-ostream-encrypt.Po -rm -f ./$(DEPDIR)/test_stream-test-stream.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-pkginc_libHEADERS uninstall-pkglibLTLIBRARIES .MAKE: check-am install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ check-local clean clean-generic clean-libtool \ clean-noinstLTLIBRARIES clean-noinstPROGRAMS \ clean-pkglibLTLIBRARIES cscopelist-am ctags ctags-am distclean \ distclean-compile distclean-generic distclean-libtool \ distclean-tags distdir dvi dvi-am html html-am info info-am \ install install-am install-data install-data-am install-dvi \ install-dvi-am install-exec install-exec-am install-html \ install-html-am install-info install-info-am install-man \ install-pdf install-pdf-am install-pkginc_libHEADERS \ install-pkglibLTLIBRARIES install-ps install-ps-am \ install-strip installcheck installcheck-am installdirs \ maintainer-clean maintainer-clean-generic mostlyclean \ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ uninstall-pkginc_libHEADERS uninstall-pkglibLTLIBRARIES .PRECIOUS: Makefile check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: dovecot-2.3.21.1/src/lib-dcrypt/dcrypt.c0000644000000000000000000004377714656633576014650 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "module-dir.h" #include "dcrypt.h" #include "istream.h" #include "json-tree.h" #include "dcrypt-private.h" static struct module *dcrypt_module = NULL; static struct dcrypt_vfs *dcrypt_vfs = NULL; static const struct dcrypt_settings dcrypt_default_set = { .module_dir = DCRYPT_MODULE_DIR, }; bool dcrypt_initialize(const char *backend, const struct dcrypt_settings *set, const char **error_r) { struct module_dir_load_settings mod_set; const char *error; if (dcrypt_vfs != NULL) { return TRUE; } if (backend == NULL) backend = "openssl"; /* default for now */ if (set == NULL) set = &dcrypt_default_set; const char *implementation = t_strconcat("dcrypt_",backend,NULL); i_zero(&mod_set); mod_set.abi_version = DOVECOT_ABI_VERSION; mod_set.require_init_funcs = TRUE; if (module_dir_try_load_missing(&dcrypt_module, set->module_dir, implementation, &mod_set, &error) < 0) { if (error_r != NULL) *error_r = error; return FALSE; } module_dir_init(dcrypt_module); i_assert(dcrypt_vfs != NULL); if (dcrypt_vfs->initialize != NULL) { if (!dcrypt_vfs->initialize(set, error_r)) { dcrypt_deinitialize(); return FALSE; } } /* Destroy SSL module after(most of) the others. Especially lib-fs backends may still want to access SSL module in their own atexit-callbacks. */ lib_atexit_priority(dcrypt_deinitialize, LIB_ATEXIT_PRIORITY_LOW); return TRUE; } void dcrypt_deinitialize(void) { module_dir_unload(&dcrypt_module); dcrypt_vfs = NULL; } void dcrypt_set_vfs(struct dcrypt_vfs *vfs) { dcrypt_vfs = vfs; } bool dcrypt_is_initialized(void) { return dcrypt_vfs != NULL; } bool dcrypt_ctx_sym_create(const char *algorithm, enum dcrypt_sym_mode mode, struct dcrypt_context_symmetric **ctx_r, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ctx_sym_create(algorithm, mode, ctx_r, error_r); } void dcrypt_ctx_sym_destroy(struct dcrypt_context_symmetric **ctx) { i_assert(dcrypt_vfs != NULL); dcrypt_vfs->ctx_sym_destroy(ctx); } void dcrypt_ctx_sym_set_key(struct dcrypt_context_symmetric *ctx, const unsigned char *key, size_t key_len) { i_assert(dcrypt_vfs != NULL); dcrypt_vfs->ctx_sym_set_key(ctx, key, key_len); } void dcrypt_ctx_sym_set_iv(struct dcrypt_context_symmetric *ctx, const unsigned char *iv, size_t iv_len) { i_assert(dcrypt_vfs != NULL); dcrypt_vfs->ctx_sym_set_iv(ctx, iv, iv_len); } void dcrypt_ctx_sym_set_key_iv_random(struct dcrypt_context_symmetric *ctx) { i_assert(dcrypt_vfs != NULL); dcrypt_vfs->ctx_sym_set_key_iv_random(ctx); } bool dcrypt_ctx_sym_get_key(struct dcrypt_context_symmetric *ctx, buffer_t *key) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ctx_sym_get_key(ctx, key); } bool dcrypt_ctx_sym_get_iv(struct dcrypt_context_symmetric *ctx, buffer_t *iv) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ctx_sym_get_iv(ctx, iv); } unsigned int dcrypt_ctx_sym_get_key_length(struct dcrypt_context_symmetric *ctx) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ctx_sym_get_key_length(ctx); } unsigned int dcrypt_ctx_sym_get_iv_length(struct dcrypt_context_symmetric *ctx) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ctx_sym_get_iv_length(ctx); } void dcrypt_ctx_sym_set_aad(struct dcrypt_context_symmetric *ctx, const unsigned char *aad, size_t aad_len) { i_assert(dcrypt_vfs != NULL); dcrypt_vfs->ctx_sym_set_aad(ctx, aad, aad_len); } bool dcrypt_ctx_sym_get_aad(struct dcrypt_context_symmetric *ctx, buffer_t *aad) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ctx_sym_get_aad(ctx, aad); } void dcrypt_ctx_sym_set_tag(struct dcrypt_context_symmetric *ctx, const unsigned char *tag, size_t tag_len) { i_assert(dcrypt_vfs != NULL); dcrypt_vfs->ctx_sym_set_tag(ctx, tag, tag_len); } bool dcrypt_ctx_sym_get_tag(struct dcrypt_context_symmetric *ctx, buffer_t *tag) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ctx_sym_get_tag(ctx, tag); } unsigned int dcrypt_ctx_sym_get_block_size(struct dcrypt_context_symmetric *ctx) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ctx_sym_get_block_size(ctx); } bool dcrypt_ctx_sym_init(struct dcrypt_context_symmetric *ctx, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ctx_sym_init(ctx, error_r); } bool dcrypt_ctx_sym_update(struct dcrypt_context_symmetric *ctx, const unsigned char *data, size_t data_len, buffer_t *result, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ctx_sym_update(ctx, data, data_len, result, error_r); } bool dcrypt_ctx_sym_final(struct dcrypt_context_symmetric *ctx, buffer_t *result, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ctx_sym_final(ctx, result, error_r); } void dcrypt_ctx_sym_set_padding(struct dcrypt_context_symmetric *ctx, bool padding) { i_assert(dcrypt_vfs != NULL); dcrypt_vfs->ctx_sym_set_padding(ctx, padding); } bool dcrypt_ctx_hmac_create(const char *algorithm, struct dcrypt_context_hmac **ctx_r, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ctx_hmac_create(algorithm, ctx_r, error_r); } void dcrypt_ctx_hmac_destroy(struct dcrypt_context_hmac **ctx) { i_assert(dcrypt_vfs != NULL); dcrypt_vfs->ctx_hmac_destroy(ctx); } void dcrypt_ctx_hmac_set_key(struct dcrypt_context_hmac *ctx, const unsigned char *key, size_t key_len) { i_assert(dcrypt_vfs != NULL); dcrypt_vfs->ctx_hmac_set_key(ctx, key, key_len); } bool dcrypt_ctx_hmac_get_key(struct dcrypt_context_hmac *ctx, buffer_t *key) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ctx_hmac_get_key(ctx, key); } void dcrypt_ctx_hmac_set_key_random(struct dcrypt_context_hmac *ctx) { i_assert(dcrypt_vfs != NULL); dcrypt_vfs->ctx_hmac_set_key_random(ctx); } unsigned int dcrypt_ctx_hmac_get_digest_length(struct dcrypt_context_hmac *ctx) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ctx_hmac_get_digest_length(ctx); } bool dcrypt_ctx_hmac_init(struct dcrypt_context_hmac *ctx, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ctx_hmac_init(ctx, error_r); } bool dcrypt_ctx_hmac_update(struct dcrypt_context_hmac *ctx, const unsigned char *data, size_t data_len, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ctx_hmac_update(ctx, data, data_len, error_r); } bool dcrypt_ctx_hmac_final(struct dcrypt_context_hmac *ctx, buffer_t *result, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ctx_hmac_final(ctx, result, error_r); } bool dcrypt_ecdh_derive_secret(struct dcrypt_private_key *local_key, struct dcrypt_public_key *pub_key, buffer_t *shared_secret, const char **error_r) { i_assert(dcrypt_vfs != NULL); if (dcrypt_vfs->ecdh_derive_secret == NULL) { *error_r = "Not implemented"; return FALSE; } return dcrypt_vfs->ecdh_derive_secret(local_key, pub_key, shared_secret, error_r); } bool dcrypt_ecdh_derive_secret_local(struct dcrypt_private_key *local_key, buffer_t *R, buffer_t *S, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ecdh_derive_secret_local(local_key, R, S, error_r); } bool dcrypt_ecdh_derive_secret_peer(struct dcrypt_public_key *peer_key, buffer_t *R, buffer_t *S, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->ecdh_derive_secret_peer(peer_key, R, S, error_r); } bool dcrypt_pbkdf2(const unsigned char *password, size_t password_len, const unsigned char *salt, size_t salt_len, const char *hash, unsigned int rounds, buffer_t *result, unsigned int result_len, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->pbkdf2(password, password_len, salt, salt_len, hash, rounds, result, result_len, error_r); } bool dcrypt_keypair_generate(struct dcrypt_keypair *pair_r, enum dcrypt_key_type kind, unsigned int bits, const char *curve, const char **error_r) { i_assert(dcrypt_vfs != NULL); i_zero(pair_r); return dcrypt_vfs->generate_keypair(pair_r, kind, bits, curve, error_r); } bool dcrypt_key_load_private(struct dcrypt_private_key **key_r, const char *data, const char *password, struct dcrypt_private_key *dec_key, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->load_private_key(key_r, data, password, dec_key, error_r); } bool dcrypt_key_load_public(struct dcrypt_public_key **key_r, const char *data, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->load_public_key(key_r, data, error_r); } bool dcrypt_key_store_private(struct dcrypt_private_key *key, enum dcrypt_key_format format, const char *cipher, buffer_t *destination, const char *password, struct dcrypt_public_key *enc_key, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->store_private_key(key, format, cipher, destination, password, enc_key, error_r); } bool dcrypt_key_store_public(struct dcrypt_public_key *key, enum dcrypt_key_format format, buffer_t *destination, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->store_public_key(key, format, destination, error_r); } void dcrypt_key_convert_private_to_public(struct dcrypt_private_key *priv_key, struct dcrypt_public_key **pub_key_r) { i_assert(dcrypt_vfs != NULL); dcrypt_vfs->private_to_public_key(priv_key, pub_key_r); } bool dcrypt_key_string_get_info(const char *key_data, enum dcrypt_key_format *format_r, enum dcrypt_key_version *version_r, enum dcrypt_key_kind *kind_r, enum dcrypt_key_encryption_type *encryption_type_r, const char **encryption_key_hash_r, const char **key_hash_r, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs-> key_string_get_info(key_data, format_r, version_r, kind_r, encryption_type_r, encryption_key_hash_r, key_hash_r, error_r); } enum dcrypt_key_type dcrypt_key_type_private(struct dcrypt_private_key *key) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->private_key_type(key); } enum dcrypt_key_type dcrypt_key_type_public(struct dcrypt_public_key *key) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->public_key_type(key); } bool dcrypt_key_id_public(struct dcrypt_public_key *key, const char *algorithm, buffer_t *result, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->public_key_id(key, algorithm, result, error_r); } bool dcrypt_key_id_public_old(struct dcrypt_public_key *key, buffer_t *result, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->public_key_id_old(key, result, error_r); } bool dcrypt_key_id_private(struct dcrypt_private_key *key, const char *algorithm, buffer_t *result, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->private_key_id(key, algorithm, result, error_r); } bool dcrypt_key_id_private_old(struct dcrypt_private_key *key, buffer_t *result, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->private_key_id_old(key, result, error_r); } void dcrypt_keypair_unref(struct dcrypt_keypair *keypair) { i_assert(dcrypt_vfs != NULL); dcrypt_vfs->unref_keypair(keypair); } void dcrypt_key_ref_public(struct dcrypt_public_key *key) { i_assert(dcrypt_vfs != NULL); dcrypt_vfs->ref_public_key(key); } void dcrypt_key_ref_private(struct dcrypt_private_key *key) { i_assert(dcrypt_vfs != NULL); dcrypt_vfs->ref_private_key(key); } void dcrypt_key_unref_public(struct dcrypt_public_key **key) { i_assert(dcrypt_vfs != NULL); dcrypt_vfs->unref_public_key(key); } void dcrypt_key_unref_private(struct dcrypt_private_key **key) { i_assert(dcrypt_vfs != NULL); dcrypt_vfs->unref_private_key(key); } bool dcrypt_rsa_encrypt(struct dcrypt_public_key *key, const unsigned char *data, size_t data_len, buffer_t *result, enum dcrypt_padding padding, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->rsa_encrypt(key, data, data_len, result, padding, error_r); } bool dcrypt_rsa_decrypt(struct dcrypt_private_key *key, const unsigned char *data, size_t data_len, buffer_t *result, enum dcrypt_padding padding, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->rsa_decrypt(key, data, data_len, result, padding, error_r); } const char *dcrypt_oid2name(const unsigned char *oid, size_t oid_len, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->oid2name(oid, oid_len, error_r); } bool dcrypt_name2oid(const char *name, buffer_t *oid, const char **error_r) { i_assert(dcrypt_vfs != NULL); return dcrypt_vfs->name2oid(name, oid, error_r); } bool dcrypt_key_store_private_raw(struct dcrypt_private_key *key, pool_t pool, enum dcrypt_key_type *key_type_r, ARRAY_TYPE(dcrypt_raw_key) *keys_r, const char **error_r) { i_assert(dcrypt_vfs != NULL); if (dcrypt_vfs->key_store_private_raw == NULL) { *error_r = "Not implemented"; return FALSE; } return dcrypt_vfs->key_store_private_raw(key, pool, key_type_r, keys_r, error_r); } bool dcrypt_key_store_public_raw(struct dcrypt_public_key *key, pool_t pool, enum dcrypt_key_type *key_type_r, ARRAY_TYPE(dcrypt_raw_key) *keys_r, const char **error_r) { i_assert(dcrypt_vfs != NULL); if (dcrypt_vfs->key_store_public_raw == NULL) { *error_r = "Not implemented"; return FALSE; } return dcrypt_vfs->key_store_public_raw(key, pool, key_type_r, keys_r, error_r); } bool dcrypt_key_load_private_raw(struct dcrypt_private_key **key_r, enum dcrypt_key_type key_type, const ARRAY_TYPE(dcrypt_raw_key) *keys, const char **error_r) { i_assert(dcrypt_vfs != NULL); if (dcrypt_vfs->key_load_private_raw == NULL) { *error_r = "Not implemented"; return FALSE; } return dcrypt_vfs->key_load_private_raw(key_r, key_type, keys, error_r); } bool dcrypt_key_load_public_raw(struct dcrypt_public_key **key_r, enum dcrypt_key_type key_type, const ARRAY_TYPE(dcrypt_raw_key) *keys, const char **error_r) { i_assert(dcrypt_vfs != NULL); if (dcrypt_vfs->key_load_public_raw == NULL) { *error_r = "Not implemented"; return FALSE; } return dcrypt_vfs->key_load_public_raw(key_r, key_type, keys, error_r); } bool dcrypt_key_get_curve_public(struct dcrypt_public_key *key, const char **curve_r, const char **error_r) { i_assert(dcrypt_vfs != NULL); if (dcrypt_vfs->key_get_curve_public == NULL) { *error_r = "Not implemented"; return FALSE; } return dcrypt_vfs->key_get_curve_public(key, curve_r, error_r); } const char *dcrypt_key_get_id_public(struct dcrypt_public_key *key) { i_assert(dcrypt_vfs != NULL); if (dcrypt_vfs->key_get_id_public == NULL) return NULL; return dcrypt_vfs->key_get_id_public(key); } const char *dcrypt_key_get_id_private(struct dcrypt_private_key *key) { i_assert(dcrypt_vfs != NULL); if (dcrypt_vfs->key_get_id_private == NULL) return NULL; return dcrypt_vfs->key_get_id_private(key); } void dcrypt_key_set_id_public(struct dcrypt_public_key *key, const char *id) { i_assert(dcrypt_vfs != NULL); if (dcrypt_vfs->key_set_id_public == NULL) return; dcrypt_vfs->key_set_id_public(key, id); } void dcrypt_key_set_id_private(struct dcrypt_private_key *key, const char *id) { i_assert(dcrypt_vfs != NULL); if (dcrypt_vfs->key_set_id_private == NULL) return; dcrypt_vfs->key_set_id_private(key, id); } enum dcrypt_key_usage dcrypt_key_get_usage_public(struct dcrypt_public_key *key) { i_assert(dcrypt_vfs != NULL); if (dcrypt_vfs->key_get_usage_public == NULL) return DCRYPT_KEY_USAGE_NONE; return dcrypt_vfs->key_get_usage_public(key); } enum dcrypt_key_usage dcrypt_key_get_usage_private(struct dcrypt_private_key *key) { i_assert(dcrypt_vfs != NULL); if (dcrypt_vfs->key_get_usage_private == NULL) return DCRYPT_KEY_USAGE_NONE; return dcrypt_vfs->key_get_usage_private(key); } void dcrypt_key_set_usage_public(struct dcrypt_public_key *key, enum dcrypt_key_usage usage) { i_assert(dcrypt_vfs != NULL); if (dcrypt_vfs->key_set_usage_public == NULL) return; dcrypt_vfs->key_set_usage_public(key, usage); } void dcrypt_key_set_usage_private(struct dcrypt_private_key *key, enum dcrypt_key_usage usage) { i_assert(dcrypt_vfs != NULL); if (dcrypt_vfs->key_set_usage_private == NULL) return; dcrypt_vfs->key_set_usage_private(key, usage); } bool dcrypt_sign(struct dcrypt_private_key *key, const char *algorithm, enum dcrypt_signature_format format, const void *data, size_t data_len, buffer_t *signature_r, enum dcrypt_padding padding, const char **error_r) { i_assert(dcrypt_vfs != NULL); if (dcrypt_vfs->sign == NULL) { *error_r = "Not implemented"; return FALSE; } return dcrypt_vfs->sign(key, algorithm, format, data, data_len, signature_r, padding, error_r); } bool dcrypt_verify(struct dcrypt_public_key *key, const char *algorithm, enum dcrypt_signature_format format, const void *data, size_t data_len, const unsigned char *signature, size_t signature_len, bool *valid_r, enum dcrypt_padding padding, const char **error_r) { i_assert(dcrypt_vfs != NULL); if (dcrypt_vfs->verify == NULL) { *error_r = "Not implemented"; return FALSE; } return dcrypt_vfs->verify(key, algorithm, format, data, data_len, signature, signature_len, valid_r, padding, error_r); } int parse_jwk_key(const char *key_data, struct json_tree **tree_r, const char **error_r) { struct istream *is = i_stream_create_from_data(key_data, strlen(key_data)); struct json_parser *parser = json_parser_init(is); struct json_tree *tree = json_tree_init(); const char *error; enum json_type type; const char *value; int ret; i_stream_unref(&is); while ((ret = json_parse_next(parser, &type, &value)) == 1) json_tree_append(tree, type, value); i_assert(ret == -1); if (json_parser_deinit(&parser, &error) != 0) { json_tree_deinit(&tree); *error_r = error; if (error == NULL) *error_r = "Truncated JSON"; return -1; } *tree_r = tree; return 0; } dovecot-2.3.21.1/src/lib-dcrypt/ostream-encrypt.h0000644000000000000000000000132514656633576016463 00000000000000#ifndef OSTREAM_ENCRYPT_H #define OSTREAM_ENCRYPT_H struct dcrypt_public_key; struct dcrypt_context_symmetric; /** * algorithm is in form AES-256-CBC-SHA1, recommended * AES-256-GCM-SHA256 * * Algorithms (both crypto and digest) *MUST* have OID to use it. * */ struct ostream * o_stream_create_encrypt(struct ostream *output, const char *algorithm, struct dcrypt_public_key *box_pub, enum io_stream_encrypt_flags flags); /* create context for performing encryption with preset crypto context. do not call ctx_sym_init. no header or mac is written, just plain crypto data. */ struct ostream * o_stream_create_sym_encrypt(struct ostream *output, struct dcrypt_context_symmetric *ctx); #endif dovecot-2.3.21.1/src/lib-dcrypt/sample-v2.asc0000644000000000000000000004433314656633576015462 00000000000000Q1JZUFRFRAMHAgAAAAIAAADfBglghkgBZQMEAS4GCWCGSAFlAwQCAQAACAAAAACv AQKrE9JRl23tq1RrZzVOdniCF0DdU0t0nChX9mv2K7rd/QAAACEDwvjlcRkV+Zrl JQIYgGVPawwFMOUC/ezeeEhcxKeS7MEAAABA2/Y3MaAjsrRPX6pwn2K3Cd8fUZcz S27nzQpUedF2Snt5neblmGVSSwJjZCbnmK91SHmb6TMxflQUntMvjfna0AAAACCD ICFyviOkFxwR4KhIlPV5v5CIINPw5PYmg7m1JcJoAHQSyJAiN7SnaDrV/DZws6zu /Pe6tI6yjY8bIZyVw9sOIhL1UHR8z+RVsn6wwuHqHJhl32isMG2u3I4Q7pY8o/or xcfJZj/HimYKmBc/1Wg6RSLQLIMEg496hFVHBJ4RC+rifhlf88j4s2Txyn+fO9al oqa4Z2hMuW/ad1NlieWNr54/6xbeZbpP17KayiYQPz9CFChAD+L2Rxg3DeJARWBY ywaPmy/NaoNU4+Cllbc6zfI0uvZySe+cYOYnKBi7G3ZuENdmHkSA/JWX1BjGnRdc TRG1vIdit8rL1MGBc3zvKqT10h+ZOOedCEVBi6KXOZAe9FM9DK80CnHjOr9BGNtq mM9ag1sWDAMPvlCK/1+/0A/CyBebjYqoeVF09Q/TrPbNSzdB/AF+wlS/uS+W3ope Ny5+vHz3iO6pKF0gnepFfyk0Eav+5efnOS8Mr3FhQCWIBm7jBCpmyIUlIZ+oE+D3 2qmNyjMpaYB6WcXplQXbK4yTL1kty5BNP++92yuOA22CTZgIy+k0ZK1N6yScnzG4 Bya1KBlbBGOT92/HYqfm4YZ8hjJ/aCBK36UfDnsTIOHx/dm4jUCaGo7MOAmOfTxJ mSnzNyXclbo2+MNK3YFLarkcT9XI848ojnAWOpZ1x/062oXIILH3Q/PSYXeRlzhQ yWNCXU3b0liyo7vl+l/jRSYlhPGvSO5ZUoDJD/KHk6xrZI68pZ0oogm8ywSgMlpQ VYjTTdaUAUEcueC/n46WTITSvIEf4RfdXjE/r7MStZfd0ypofNJDo9H/xQRWctzq Go9Ta2jtmOTFcy1JAuUbYGe6SyG7MlOAEprLnwl8gji4uXCb2yD4pYTmDbRwcvbi Xldcbzi7XIA3cbeDWJRiBsUh5OkbKKgK9UpbYLuZyya4H6mohVfC/bDFPlSezbzX z3WGNwNRY+mDIPCDXP4qgomiQ0SkK8gmPZY+q8YNePgB3CryNmea4Bw5waAJ6DHY 66D9jL37IFjG/mOXqwIinwDvxdoWI8SjwqwFzUsjRnUe9zkIBAV3+KKU7pkDrLe2 1K9ZvDhrVvHgYPOOkQ0UxEvkb2QamLlAV+1Py68kKKouVcwF3KTvq1bJRMlfLEFp f2d/agN92yMac1jCC42hUDYNZrGMBTtco+ZqOJJ5xZouYDTiLP9lgHfO9sHZ+9pk Nf39SlnDfsYeRhOTAlGCYVEkEGDxWU+5CTZRHDTZYXU26a0Pyd82dE47QHqGikqk sFIqtIaxuJEXDva5gmtBgDf9u1xSUe4r6CXtymT+zOjp2Zj9+lu9ggB+hpymgeRL CjzTk2lr6cx9n63k0U8QFRTz5N2yNvubD4mv6RkqTY8xf+q2WDD/aX61uImw/ltC EtYJfxKHRHAeXJDlSN9DLZ3+lhfVmEyw6MdeeQH2hpvAKTZeXcPuMB2PRwB8+vkK LgFNneRn1Tuztirs9sPtnErgaDEyNGF/o8xrDAQ1mnzKR9tWAPtXKh119snDps4N wwiYGkT3pAsxDoh4LiJWOyks4PRKX+1Ly1BGCPoRX3/t/QaPmN61vXSHnwUQVCJ8 kaxyqOl43eQbKCdw/+EfyPyyp7I7w4IShUe7g4RtHbxltNcNvQTyztxdkYDWiFQ0 C5aDmaqtaYFB/lqOFKJNC+SOCrleSqUYnJj/PWf+jZuZ11zjI0XAlXVY1mbxjEG8 RqlU4HAtcUa78AACc+vBJ6HD3KydpKB+R1kBCw9VTWiNx5o2TmS0uw3uimzBrkWE VM2QntF438DJl0d5fGWeG89KqOFM10sDyNDQ+tjserDyaVKHBuErDC/C2BI7YFA2 63mfRTn4Yt7HltFch3MRFo9x3iRLrkqRuww2kCvZTauC9YxqZgeuD5XYDrRwbx2A WkF8bL55jnODO/RWxOthSv5z2OTWwqCCw8/SE6IAIRdGnIH5nu6uoK90NfxsN/iG c6VjqY8HXY1mCQwdTFKmY1gsGLVgyKPbugF6LMOjEvy6mN3GiJvd+Ncs5/5QPVeh gakujGRmkwQ4PMGn56mU7MvKoJ1jq4GPV3DDjSgTwxxJn0GUKihTnslR+lI9mJXD +riKQAzT6POjfvWnQ9bfhVmPrGU/rPAAzi02jUXwXCXPfDUBqiD9yixGCGF2gHeW rXFqQvDYU0DuetOePprlC+IfqgMJHTM7Eepqi0Kx+vygH4wnu/JJKcSmCte/z5lH iVM3c4SZVMnBWhfhN+TWnuFoPuUGVgisLePoi1NsAzbsh5GbvImLgVVNbSAZmWCc nbnh1VXU0MNF5Ovyx6XAWUeHnhosIuLP/zQR/ne0QgOYUxE61dJ5iRnE1oA2hrJm UeISJ6QKzJtf690EtqnSq8qzjOLsOlvNgf6O030vc9Wgj2PqSK93Ai5kNb5r2iyX 0y/JIJaEPN5mjS4VVZDgcQoo6tRQWF9+iGYgPK3vGqeAOLrorw7UG0FsvkNqm2US Kqi6/65jEdmp975vuRwa4o0zCfuWc9OZ8UZTkREmouc3fEkvaHoOKJm/O68E6aHU WlpgYvLWLWychCWMqr4tOBAarTz+UZGI6wtORYTQWXPbfl4YtmJ7fxuykNtkLC1/ Jw+MHZ1WmA1csvalfYvcY/rvNDlkdpM7iPeKLpAMspyWueXiYl5cCEJ7R4eexs+p 71juVPhv1UvBtR6vcxjd9PryFCeMpJDvLUwtNq2taRXTFFa4pz3+zl7zWA5gqIag HSd0khr00aF8fUQhTfSVUg6qYo/FOAeinJMfSsyXnJ/R7WOtZmi7UkuC63zaA4nr HXXtrefd434tHUuWQP+76Fwmp62tlPFlfiMqNRipIRUi2JLgRXws7A7DH9msSvAz IHD9lxR7KECQWkJUhGhTOWCTUeNeVdyeAZdLD+bGkicvaQlFfyaqxm0XCqdJEviU PngsGLaqgEqfMGUkOObd4W4MlRw6X+77pkQf/l1FktSm4Ixo7qMxDz0En7irYvlj BDCsVyig0MM853I8cYoMHkd33HAnMITS2LkFfjlwUFh91rZlBQawFt5JWAbx0UaR /dJOHkxEhyX6yW5+PWMxazPyiVLAqxErsOspJXlUI77zucII5REDHnRL5qo0+9x8 sR4a9AyUch+oOgFoHMTKeNEMoFtVfNmpeyc3TCZmPQFNFb4KT+jrpQuH8ohREdGh /73Np9ufGBAeGqOhkIWNGD+VTZqFATANkbRJWzGSKoG53W4XFrYhXEx0/MmHy3Uo a9pvN/nspChjMncoe0x9ZwO4S/Zxr0JWuikFzvUfEqVck3R99Bcd5dD67qHx8Wih s7m/Q3RxLS+dU/1fPa2SA/tVGC0OciYmr7vDZArsUscpYehtMqbpgaQPDJI07x64 ilQiNg2ZtFmTh8VHQIQyf8zdE9jtFhKJwrT/M/MN1st5v0dfBbohz7Q7TGDct9Fu 4+JwdpeZK+dm0yxNT+gEhQOAuK5+rBX5wsr1kZ5nohg0nrGAQ3Kl2z+QV4u1Xnr+ d4W7TBpsy/ugq4ZV9/T/p3DiObPl/kjjjipsJCHN5wpRGr7YeDWUbKRq/GNqKNrW 6CpyyYnMFK6PfP85u0poDXlW2DX72/s+bf0pOJIHjRl3BoKhhQpQbQADb/3aV0Ov /3/RKN7zN8Z2ctmOmbCeFpp/Mren56kM/TjnTZs0WLwesShcAY0JNCQ2QcZRFHkr 8SQjlgVPOVVdP1835X2muQgnbyoQ7VNAplA3xsVlvtmOQRD10LOAMF/8s+EF0yZB keieB84j7eHZ9u2763pCMNAokhQ9FcGskEOqM/iNmTtiS0xL4NFfRlKXb7zZcpVT bqLg1mi5c2PLLJls2dw5gfgz6TNcnBSaDn2zhnFll4kuZqxhz5i/2RfPQgicMlV7 /3MDEhTzm3qVhf+S380qatNN94JK0QAn37oKZiK+GTHs69L82YbT9ILMKNXipT9t iViUHzZnYrffayW2+vLWFfjkvkJDrawxcVZ52RuXq0S5qjWmVrkK2prv97PTpNOx H1W4ap+AFQqGgQkoQjqZXTKEr2DIIhg4Ntt/kqeEMLUdS26lKFBSx81JOZivBmby NXIFpNw6FG0RdjAoTt17+ka2hQqk1tya2zIbL+ppQBwJblLx6tEKTfD11QDunsMS 8mM1K6zw1Js2BYiMwpwlN5knRXPzy4oC2iWG5dan2IxW2Fd82zIMLPbeCe6PkwMV FEhaR5l8GYrpYHIgK2JaZm183sOAsav4RDsmc8bio/crrGnSu1B/apMuf79xqXvx ygwRi4t0iborisRsVJveG7+IVf9I5ZHT+WqeEhkhh+lJ4QlRN0JfxuX/cy8I8Pd1 0oHztq+Eyx551a4zgrVl3nDuAZDlByMZlXZCQwwAGMIOCmBNp07WmZ58XZOfQ6Dy rQiHY2gYYI6wGwy/aiiy7BzR0/JGTfs2o1EfHwuI+95D8gyjbDX9VlngNmbpfisK 9mOx2cLBc2aOIu4mYFmpnL7G5gUOz6M2G8MDX0J/NraMcvFSlCT6KyuMzmBnSIHW mmI1k0xKZ+rPxmad8arpZ37KXsKQaRmhYo0WgDSABGnW5lE/9ZJxOj92fgywFLB0 a58uyxF8M/XagJAE6teU4A63xxkiWNj2ZCG9/tndCrzAIe8rMqNbWD15x7tw5LlD GZI+DWVstLyNvaXOhF+Nt+oMwEtMVqxZCO4/1I1zBIS9ginTaw8lE76oWGkwbnP7 5LZb4+BJqPyvaIrHijZR3l/qbK9r0cWdOHjNjKYFPEouz2vuYd4Mh5+wlHcoixw4 SpQ3ZQMBJCfanZXAhcLUYsSp0fmXPmmdPnQ7U6cuqlcurJhQt0Ks8LCKS/cytezK 89q1XTrJ75etdwGfuCMRCInU+lqIvzxZiOHrn8Nxcg48HOvgfo6r9dnmwa//7Lgz jfGJgI222B6/KXeTPyqDhbJP0/gc09tqsmTYMKw/w9wdu3ehbu1lSqALx76O9SgB cl+HDfHNgTt5Yd3BpGbEgG2G+Iin2v1DPXTu/cMYynXmb5X78CPcU9iVc7JbHFVa kepWqFdqRrUuZ2u92N7fm2DgqcBcTex5VRLnN+pAT8VZM8nJIRGgUl0YXY1C5ilN lcQm0jHI0StOrtr6TcFmw6Mru3v+xygWy185js+Y9KZaglmuF7iTsmBfAYfWW8hj 8Rx9jL8Gi54uxel9sL0y4lSA3veX8eo7k3euvGKKE0eSuHYq0XAKAMJgW4DeRmQU iilg6h1AEdbTDxAlkwwr0bKMcZtsidLX6dxIe+zZj6Hck224ms29Sbid/RLFGqxb h97dTz+YwK3dyC5LhD/2txVCEPhBZW3x5sBwbORzllI7WcqB+i/ILlqJwAJ5bd8q x0NP6+ZiepeTSUpeZAL/Z3N49bIO1QNDItvjYxQAixpliMoDlQZPPzPRWtZB/jkV BeUjeiAzD5gcQ8h1EsaRtoESEI/QX/e/kfU0YtW+jYrt0l3GS9rs5IBzod6Z/Igh 9wSZ8JIjnUhEwlibTTdr5xk+K07u6LlhFs3Ho8lzz9bS+YxqOfp/htfe3Mt2MWhl MCCMl1mLVGUyK63znvWeki6TATrqnqokxST1BMKA0rSRxRpzJIbMcAkn5cFLbyZt nP962O8+C8qgTvH+SIB/st+9YtqpluN/gOIzb5tcpI2bwo3N7nZos/uJwYOybh8Z CQgDJSJqBatkldu0ZG0i226k/APO5MkyKQflyxaOvIL/f79i3tSYDIECPKNf1Txx oXkL21hHPbflnpbbw1Bd8Qc9Cjr2Ku8H4Gy7xSE1k+KNkJQv4VZ7nZX/35X/hAS9 Wxk/BtVh8jJQ6BOsB/6nS1i9FIrQOAl1XmksvRzXBRdxUnRSiqa6ctGPJF2/Pvcz IkceEsdrFLrG9twoLP8XEYlJ06F/aLCROhIGGuy5MtMuF8kSWvDu6m5bZVARm2V0 VvtcuEb7OlOwKA/vsC771zEGqnojpIYuRywLu7W1fS0xbwWMFzrAHHadgRlW+MAA DaKbPtXMPjyx5euHkCk0Fx+gkqK7N7MJQzakbcrhojKIR/1lZdi/lq16hc0f4TJV OUZETZL1UqY2q/RU8kdDySa3sLc8RqqKB7mwNswQQL4lMX4PlF2FgbdOKCJusjFT 3BEA0/7YKwfV5x3h/OU/dUPSGykpb7zcZBVmJcWIzPy08xTevNb7L/Efgamfrn2q InzZVdp9pa5FhajpbZ+bCAQ2xnM56ZBV7o4Fw7YaXVP+yJ6EdJyFyfalHmWcIfFQ M/CtTNK2Z3y+01+YxrQJ9EcK+v6+kjyDMsWTfJgWgCyn2dzQ5STip0Dd33wXj8+h 2xep/nQ8ez1O3AUrUNi196dtmlEk5XjyIbGnCKvEvqu6z3GjSy+Rdf12B25h+XBm FdW00pdTkscmhm8gNbvVQBR0B/9GfNCE55zRPOrZPo/2GduZwr8ZfhgBDP/945LZ Vij8eiOftznUt9vxsxpCOu+JUA1vrbspnA1R4qw9+GLgotW6Jkh3tuHa543lfDmm BNFNssANVkwXWkqlSUNBEudfW+om9lQbb7hxxI2t8azNldYOt6vcRU1ZzGTj7BW3 usUXzcywoAyEXoPYK++64BUPIYcOhyps/OMuyFbu0zANrLsSwUjB58iYNPGSo09G VFCrhS+q5l7KEeyfUu07DB5rw+DNDiB/mhOvtysjbo49nBb+ccFtfP7FtlFuv/In itPR5p46km49q1RfnrrHFw3dy0fHWpw744ivLAVUhcLB0FZpwQ0rxe5Hi3VvWIiL ZEiZHHuw1os1sG+TirapFDjOefnYRDZSEJQTNjGHI8Js3jzgWZX41SU2bnf7eaNp nM5o3O/ZwdN6K92J4jvRD5W0UfmCWabQoLkJ/pkWb9lc7CipkkUUaZdXDwKk7vBd SMuvttpQJv4iCUkJNpCiRyYIfw/hUgGOHNpSZGuUQRtk3HzrEo6wdsQf2WY1tZfD 4q9607pzDGWzRFR0KDPKfrjk7/Q/2hpm2JGMHmg8dd4V5JQXljqyuffA/c8yrlPj KFQHEFrg9CI2oA3UUNgpDaKxLr/ucuA9bjMI5APs37Eyk5jO5fDvh5XKDTXZUzaW 9Nt9FMH+FAHXIGTf3URvYJZ39hUS+Gngv1BDPNpkH4XtBytlLDeETaxyEjPIMkJZ eT0v+g8jVpHk85qJTxbuqZG13u610ZDmyPoPRAqfdFsNAJub8OpJLj4YzRl36G4g un5TG5TE1v0T2fRkWW8fZxNiHzblirx45dXF+xaB+LfElQWr/V+AFaWiKfy3KpP8 G83FWASAMlnK5RQf+OVYeIAihKGoPGygbMsfpn6E2BUXK+E8Hz6sQMfFxqllGPgk /f87pzQAOpAbPgR+/XjGD3YGlA3zAFMwl3rCBo4TtJ9wH35vEJmeaRHx5YL1uvVA S+/krsYMKQ1hjQKDi0Hlyx66sl1rIYCSytjSS9Ae6lb2rPSqspRyShpJL1S+Q5al 3urZKhrurGiJmKmZrpZomjnKr6l045Fs6XR/nmaLXBf0j3y5LJ5OsWZ6Rwzfajk7 n4pdaotHgs1LdQdm06bHUIeFwgUBno5LqeC+DXZhbV5I+b1I78H8BUXDPv88dPiL 66wGx+Ha6M8sm335ydnJL02cDFOJGywdKc1XilXPQP/c6EYUrYVbDQXebtgDac78 aw+8vMFy0DeKtlKfEBtm44eG3BNQI+jnZbg6TQtkp3noD5OYFjjWPLtbKBPgYU8b JvTOE/MTblHC34Kn5rlanArdLgMH31FLbr1o18gRQNws6Jyxv3+eFIA6umvSXiFk PNNMDFqTvJBJtlMhhhH1kVItZvzol8bJDd7ed4ZQie7fH3ukt1E3iZMW3fRgqomq ZDb4GOWAKfpKrWwH+c6JXPO5+1URwjpTmrZj7wPubtJMMFDbtoTBNJFAaOMwlSMb z94AP8kd2gr/pREl7Ih+vJoyruVNJybKjZenidQlAjbhNn6hBIUSGDyC4mKLhnD6 EA1DTxYjZupajP68DC+S/bAO7k9veQ2IWTdbFqkjCC6pUkxFp6DuM+ADZdbR7Llm nWUjb5CLLwNuuE+CMFFYUrK8BdnC1uaqb6fQiEo42u9lAkX4pRIB6TVnTtLa25ub CUWpSWpu6bEP1nIf9YuNPIxZt8YAA97HX3fSKY6PcRp7QDP9Gu2kRenZRfeHpoYz bkruh6jWXIMhtZiWO4+qpFW2AQBro8KSrnNxjpruxy/DxbbexJt7LNv8ZnlaIKCc S5Bv9WHfxUk6tJ03+CqxDqsRHy3QkzwnzXLnNnRUiNKi/fID1YrbSbPvnYJwDWi9 Dc2eDOdWJck71nnJSWP6nRcskNxbNnNnJ4MNXKmbGlJ1G+2F5zpqnO8O9VbMb5S9 9vE8dW2LFQ/7QzCqakL4X1oKzjEyZ/LOaYgH/KtSwEZWpeI/v6w41AVOTRgvkQYY kP//kDFPr1WepWqZ6dkq0JrpudTkddFoIJmRvxRFYFkBwd7KthKOzI3kZlB6gQss /wO04e8Wv/iGoNJJN6SxapJpe7Hhx7GljesOTSM0Vd6t7NgELWUtwZ0SGpoMZAup HIKT1tAt1XsorC51jzksaEYCxUK/Ceb/B3y9xYpD+97BwZsHheAG3yq05FItHKZB njzqBM8fvKR4GyGRN2HGcybet+FEDyg1Tl3kpjgbqagqCmqwZdGuo6XwW65NpODg TIIqaXudEOCo6VUcA68S0ZfIF7KaBzAinQy8Qw4m7WlzQa9R7peterNTeMDj9fBk 6daJNJ//AUwcDSUa1pfL4OlkjmquE1JYal2crA++XuTzRKIQA6HmYt2QEZjqNyG1 S0j1xhFgebNEmwyqRnwrhnWIZNJpltZE9iGfeEXi0QtEZn4/A2fNaDF1R/WIUVfA M0tdSZz9rL+Q6UxsfFdlTszcTN7P0z8XM1IO9q5HMgZ7FgT/FozDhbYW6cNN8Haa +wYL3foW8CV5NWxUbYD+OZejHQTpbqTkYGvumSjPWtlqUhTWJ1NZRa5Y6tH2vwou Lh3IA43DSYVvhDcZeVwi3C9HMDRPkr0ZkLQYfpL4jqeE30Wodg0JWbihDnNscCLq Cl2BLU/o9PydlgRkq6vWNXtRIBoA3yaMytFu4+sHIrhGkxXwpmnP9bOgjSeNqmJe V07r3FTso7cnHqOQUHbRmsogEMALUegTpDUE0+mWyrdmKRvkbhm2Rj0MLrhpZjqv E2RngcJ3pWVX3Ph7oVPfdRWwMlL7lAgSTY2LHwVdUjKPL93wX+mY+ZnMWNgw619A ix/bNX68Ufeqhp0mUVrJg2gdhlYiRLDocaVZdoB3/M1ENLbbAT9NyQGp5uP2wCZE deE3ttI8CNRq4HQ+BzHo/bQiflG+GwcX+3rKn9m0JA6Jx3FlsMh+a+mKhILoj/VO sy/nzz9Mzf0nh0SFkwv+mH9qTc+paX33zV4TQeyR/zRDs7/vGHR5VQsKArHkUY9c D/c1HjXlbIlX5MHGRv126XdEVFd0FAxdZBRw/R+21MG3FZ9QMvslWcQxYiOovXmP DXyiNA10jzFZwGPSVfoh2FlfcIHnNUdaa/2oY0vVmUcSpU4CRS/cTC1+9X6CHLNv Nok1Hcy7MowgPEmqpIIOTI5vyXDr9PtKU9z65fvYEhj+dLlBvK1QCLl1TBPNg8PU MJLFcQlO9Owwe1GbWCoqJ4+3Z5O41jIckkjkrASGuwxiihVNuAMrmIocPlmsdOIW DikNtSNX+gLb3BPEjVrt7aZkotdvzqD/ZPZdcTN/g5/LG01ShUWBhMjMnw4eW+eF AZy8xMymZFeD3BrGEY7UNwpsYlYDJq00FRwz6pRw21oZkQwSxjLUnwi9M7pNJ00K cddZHf8XDUiTRvYxVvC+piVb5bPzilIHHhbG3RdVU4RPjeQdfvE4zffD81n81az1 mioDUSdGcPJM/fyBFnIkQmfF12m4bheGQuoXWr801LpBmy+x0W89tSWycvPY5gTi 6Ua/novyfnTrYTWaDG0I+tX/3HWS+FXDj/HSCMv3kalxYrAOBDQoYTasdJlARVwU aGeDcgel9yCmRd9PlPGUX1HQ/IA2fLVkkZYV8Lo0K3x2EYJ1cmInm+c91CztpIb3 2Nlejn1lg0CnvR4sVZRh+TYe65eMw4sFWWIJDt6Ad2fxdP/SBCJa9rUbbWV/K9xc zxU/GdO5uWOiGdYm7Zf/8gPBRWozxw0zi1DrZNGrRCkPNY95MYHSTD0GsFy29ySu ROy5KEaXXxNzrqWhG90sQu43A31OermYR2G0ERbl7gSFoOhI7WusYwW2Me19tySp 9O5rS1gJieFJkpAKO2mVMGw1p1b6U4snyWYW4+gVfVanYhJ1RLof6rzgKhazMfIs HRkwh4+Snjup+3Uu29+R3p/nHJ9/cgMAKgT64Ll/VZrf+krym2DirZr3EbnAb4HW r60Hm8cR64WjTBmSXSgy17CWPPtTuEqwqU1TLNDMuSeLT6EQujEcHqYwdjLju3JQ Fcfz205gLjy+MsfuiiZznyTIANaSBPvyC3ExJ5Kagvq2pq/W3rURNV79vJinjOeu qa/EPiRIddNSMF2yJ/7VFTsHvJaz03y4AGth/FQxQSDk5F94B0iGEGeRKbSOjaKq wucfsur3OHmeDGmAdRrB24grkSP3Pnj+AVj9aWQxlmWGLBCnuGK3iqy2jw7+2ekA PLSBTN/YuilrOF3Cgv1bwgYH4FIS389LMnYyzovDlrRFWjchzSmaGZqD0QKXyjmd evpyQsUBBlvZiRHjRgjhlML/J88hFCtFehLuxuzqw9JSY8Bhauevw7rwKhrw4ZHB 2GxF5IXrRuar9U3ofI+4vg+THR0WnHpFIjNpH8zamKK+vD8IkBy6v4RuNCtLzvM0 ulvJmYe+Ep5E2ZP5Y7pTkSiHUAtlH8PKqTwOu51W3GdQPDNR7q31BSpEJS4T/fTl CtyiYXd+onkhzutkyBeOSYBtH8GpLzYPc6tiontupT0trC6bJzhm6Hj7JtEhVx4u 80PRd7ZnhuLIQ8K5Vc3uDXjoJ7G5xCr7pOk8rQsXAzuVTn3djUZDj94AOEgXXo3M 0TdfgH/ZOsF1nCkxQ41cVqAyG3801mx7vxkKfeqA9Is4lIHVMLt7JuT9BYVxI2Wd O3f2oxGaYjJdA+GYxM2Xug+9F9ZfmsU+6/urCvvw8mw8oW27gEN+iMrwVrB057Fz s5O2mWG1xuFdoiOjLaGaKOYoi621w4mRJjZuTLv75QdsTOlXdcTCvqsTZEcU0xdJ 6DR2Gcz8OlA0jEbFc78W8746gjATcevEqtVr+XZEYlTGR5sJ/CRNtD44FCTewIrl /7Z30d8SKw3aTm0JFgBV3Ki2Pgct0Yz3JSpMd/ELMtPD9zNcLrWj+iKQ9ztEvRlg eMl+7LgtadAjQcJDXxIrceKvahh9auGI1kqO4f2o0klw5iGXXWMid1dTH6GqtFrh JME0R9oCwUIrcFoXPFq+9uvRPUCJQzS/wa+BUsenEF2i1Bos6gtxfI8EoglUpLPq IhwM/cAuuGJdPDd2762dBNMZNtcwI5/AfxLFv/dJ//E562YpSvcnis6hw+DJphGx 3NJ9EIeNIuv5K58mGXDaBgh07pk8flKtk5OAX9kT1MXzCYhjkI7V+m2O9OHoV1BN ylktKz7p6a4TjpFfci5dWUkcSqq5g0W28I5155iFe1LI+P7OU1Be2dpldSkl/OjJ Z5YKQg2hhj4Ik4fbahD+/nXCUWzoJzI1T/Fszx2h20OsnRrCDwucSz4MpyXd7afK DxOjRQJrK9NqZOXiGy2DFzvpsQrqvu1LXcRGDFI8FX69th5ea0nyInIrcwRcJcqV V4mgrTMMgaXjAbfH3OLARNnwwCv+CMlxk+Y+zmlOqbMgwJmfgG+H1TVDOnbuYNPA o7P7oYOKNuCwi6sXl5I6CUray0WNSxW/ySz7J86krQD8KVUsCOlyUI+tJ1WLO7yN zes699oPUNEXg2rxv60F7k7/FQzwvoNrTqpiMU95nrc4D1WIWtZNqHRhgcEF5g6L lZWVJsdj6SfA+yRzZ9KZ4CMcJt9bGg06qXVfIFbhlycM/83ickP0fC4uQkrkdu2S QwXkfsWFE/PNMAnUoW1ctgP6VD2Gha2gXXNzgD9WZOM6b30ZjrIxr518q9xDVWfX uYNjlUVZEhwL1yC8RSslS+AvMjFmFiHphQbtk8YO4/hq26uBtK5TTNKNhH/9Ep7e iQ3veRS/XsIpI3FaS9ZHJTdKA+ZiZpTgUW8HGWLVCswIP1IFfprbrpcsGpEWz9wm vVKtwVxh1i58Y6DqZv18lWgdsxJ+26cZOJT8aPjRBha+Wx/eqY60e4KHN1BYd4Rd 1mcGM5XPQht4d9LjpuoaolgZwWgqH0SpUpEgD+nbq85BQk4oBXO332JF/RofErJs mqUncAoo/VJx/FsqZVJo4XDo3SOueOKYIyrL26MJQ9H864L2gxq9hZmrzYW2iv9n l5w9t+f70hsk69i2Se9Sz32WudbHDcnR7SaGvCKHKogAvd/7jryVA/ZgcLlpgMcZ GB2CjurQZuBe4OXvwmJxeKwB1d98in2sM5t8LhwKEvi8JDvi5Xy+z84ZmWsULT+H qmQeszsimFbeWPRi7LT2rm+/AwUNUY0XoAVSPiludjR+f4ld3CZydPs5ccXm4tGy JcYffvDtyIyYMCQjBM/zPi+qKTwyeIEwLZcfOwXeDpKCdtSOxBNfKJgupEWPoj0c T+1C+AomgQNLd5kfBc3/Y5v3hHH5eAMiieejHt7yYYQcTbok5OfJEsl/ZsG0JBLO TKTKiUGLVgYekiSkpYZaxe8Yof22ZUDLYUszfuMt/Kul0EdXxkFT3EYWQSe+7db4 lj2+AAI9ExtqkaOLn/JVcoCDRvIB2iRhCA+u01eluYoQaRww5/qEWU31BsSC2cKe BimZyOX2pMULVZP9g0RSdj68J6415F2Q9nma0GdagvQKDDqWqqWwYhi/nZvVQVRd rtOIaKvjRXw2I699eKTmLOvzY9s0Sm2jMeCb+QZEAnnUlWgraez9PHy83eV4PBw3 9gyQdM2zipIWzf9j9hYRyfSt4myxSWKctgo8r7C/9DJYbRFVeG7zixnkm6yu5kmC +fTxuaybximy8znEW16Uz9toAOQBhYEuS48P2/whExCPUgCqns8OeCm272Evf+Ki ybgAOkZfRRXbU6YNqC3gM4sZMk2u0RBLj6nz+Quw/BMddiIJoIQjF5JJIL+yZEWd FDTlbmxKHVadU+4F5g2Z7XRw5Nh4lOY0iov2n5Q4S8f5ISyJUm+UsYFptHqKo73Q CGIGu6G1uvr4IZMUAN9T6M34oOV94q3pateKV0NbqpkAlt7iOSXgi4ueOTHnPKPN /4dp9YsbxQhYgVUJbWhxf+x/Z7iwgn2JPEEfkONrOmgepvR9APrcHCIv31rzMS3S YnVfaAGnqqrHBYsNHiraBeUKNZLgefTtQBxfuZkrtmqvrG6FeS9AX7l5IVYLq+jq +IEY3S9vsr26hABtCKgszVxtVXnQrCitYaN4F+ssfvmLuHPn2xHXkeMGrf1728mc 6pDHVR8CVi8u+3Og606F5xfcp2qkfuzCoS2AyvRg5uSUbRJdmkfA/WVerjOUwmPb ImWzb84L83McI8qMGrjSi1MNfYiWQFB9Qqs7gOm2ixdiYNqqS6X/7DPrXtdWoiLF M511CGQTPWdIr18snnupLdLzdX+iFzmD9uatc41hhZNyyTpqr8OmYHEwQq2WdyPK r2AWXk8nJBzBNNqIZURz0zousFA8sgCk7mma0CDNj+cM1v03ekXrxM+GAP1RUBqL Em7VrPpSkJtMpWV+LuaDhHyh9XesVedOHICSdazc0qe4ZeLdDDJ9F3kCOqslOFLs A9zFFt99hfWyR62iKVT/fcDWLO0Jof7lTF+z/G1GzWyMm8jAeR5mEeJVi8BpQqSN 7yI1SdHr5qFrJe9Bbd5wS5pQctxtg8zItYRdXOy9OAo4W7OrGOSYhXAHheSE1yIC yLgXl0yex4fl89UEOUUhK2Sk2H0VcoZBwY0gG8Ynt9h8VQqe5MQhMG/bJDHck+5E i9gpSN/J7WvFCWVdnFbgthdRih+mLJ9/NIeHy3FJFnQ47OxHevEmsYTIBqjj6AS/ Y+W9L7QKOCiAl7s6e8sNBL6HLR3gtIxQ4McSfe5fmmbzvRSRyF6NVHf2FI3qrPjq LFdwZUwCaMWLJzeUcxe2Vdvl69mfK/sAM8Cm1cElQ2YAWAAta4xyTYbz+M3DskeP rkLTJE4aakesoHYhFQ54Uz9JDLd0BzaQhYB2KpQfF+d6pVeGbOlRKbz49GRJ+Cc6 FYt1c4KZa3Vrb6XBqCirknJbd4S8jCIoc7DdSzaob5ahwULUrKOvghbEvADeIvd0 b6sjeSbH8RDt51pelrSLAoFe4B8AbPiMshNKVBmuStZRUQ3/sEnuLkKsgDHDp98Y H8CJrticAQ+qJd972qikimSminlUFkXwSrG9Zg7IOmm3WdU+3UaftiSMft5lIHhb fimhrDyXDkiGjHb7D7piPansmoyvnDngdhOTcrLXFXQfnlvrfa2j99v9+l+JfBwN waJ/c9xcfLyAbb9nkrVUjIS69OdZPhGVf87y5Ny32EhsqM5IE8EbpG1E22YvtQqq nuqeY4eLT/ngJDIL6zD3XVzhxUrko/QrHu71rccW+XX8NnA7lEfZRjtxkzPwqSiE iajmTrcCQJTDqyPxSTUpnESPQUegVfgF25yghBkWbdWKCf3t3LZxozV1IcSRu445 nU8wKXkmKYnmFQDAe6VzPipmkctgpW4zaHddP7Lm0qhiWkWlB1seRkd7bAG1uQaZ GKYpPZ8g50VEQhNP/lqU5FQ3q0bRibcIdkjXNnwFlPWBFa2LfTlXDmX/dE6HLbUB x+2l4KB/EIUfQPwtleC/7oscZoZbOSUi0pBfda2CXUDo5afB9xwtUaFV1Ncl6u6B ThPW4SFB6oaExfUCSlQ8dLYtqrluF5p/u9mTCdXNavxpz39bRsPk0zzWIdJ7e6Fg T/Slk1+pHqShrMUYacjon3jkVi3dhzMUwZBIuVfnA0ZaUwNEPrW4nj9HgqFAlSOT HWwrVrRaKIubV7v7ra9rCb+8kjQOwxDO2uma+CarAf6IvL+Qn1OhWa+Gw0lE6N6g zF6RXe+zoOk+wAjI9HR0LDYP2Wx76E9N0CUMvJS6gNS5Xyan17rzRWd2CJ4KrEGK ZSXbqtDX6HzZ9C0blC7d8IYhVq2Jog8AzljkTHb+Uy8PbWnLKtWIARgCa0IwPray v5BPDuXyXo3hZOVMDQvZ2oVXk4XRPuf8GkmWqlNSY5klyat+GxJWxZSAfQkEC2EJ JgVGHqoPOlJc0AZg/gYqweP3OzNqqS+4EAFMByw4VAAJOArzwRP98r26mO10iucQ 8jCQ+zGg1r49naP/orOAwPf0ovGe+nsy5ZAaz323mVVkJXcNFZFjMl7ZLg5RrmyS IKQmSET2KqA8EOJGb6G00wqGf2gp36bQktgI4byi2Oj/bKr3TLLLrB8AD8KxaPiv U4AHX1EM450LL6/lwoA2SbMy96Cg8yqT6heIDU/oP+mKlTYi9gxK+MkDy8TrU2vN ZC8o8Hrux5w3zIc6pzODQ62x35Uk6bRNGaAEDav74JK2sX6SXlsBLy2Ke0rOwfFk t9+/GoMcytmojQVn5TLSnwNRDRD/2rNGIPFvbG4XbmiQgoreyWTUp2EZXYp8Abkv bk9r23Mbx39NS+qL46l0vH0XGKFP5yXsGrON3h5Rdr2ASjKP8uJ4ztL+MRaVe7ly hM3t8FRAxct8R38glEBXYxEj6tmQVwRb7Y9sgNvA1HbiysqLRBrIRgl6QCYKwd30 ov/rWUgv5McPh6y8XXwiOermElFYCa2y3cR4rY4ERGRkV+HndqAMNSgvw5i8Y0Yj f1ysnL5Uiip9Olgd7q/syvAD1X/N+JpytgWkmN0fTQD9vL2wwBTsHODwrooatW/h aG2sKTjuFW/wJ73DY6eJ0DfJfDo3lo85jbrVMc75jCupTsfD7yPRhcdyIAOe8sRc DvjjOR33Wz4CxYoV9feVSbVuyucS7tDSGYWvorYViNPrcKRNVFSIUI+nfySWuhsF SetqFoWFZhQRTAJ4m7KqfWfaq2lDiB9LCwBCmqdMjAAn8Pwj1WQWJeQ55FbdWgCh e+OY2cc9G7YpAnYeDvrPFL25xiaGkZIGZZsBfGZgUBkSp0gEJIhsoP+ZGB/7xR4D pwMEpo13quAB5A0M/MQ1PEKwlw5T+wlBd3ndM99VYDAvBGoKGA2IHJgz5MXQ9kbV idc+3ECGAVKnIa/4kUs/pEMkX8nuU8lkrzi3RJ34iIoeOc0KoGkYRZsJ1woSAlRT JNtbwSlEvihbMgLYf9ChwvGkIeQYqujcnWv+eb3IShRxNqZqfso/Y1ng3JFsmb4m Xq2wimE8gsv1LPXhxZU36z+b3uyLSjwQgfdcpZ8OxTd1BQzFVP5NcLgYlcoPBWbM BY8Sw6J+2goXbVgkMPhqyvKPOfrs7ozTKZ5KEpiKhNnuOLEJaBWolUk0AFgGCseA O0kYu2cz0gq9iLH1rV5q324DitsTAcDaSnODuQ6A0VarVvJse60gsPNcIfMYseCQ FY4OEjlth+dGYt55ULEzOieaVan1U6rm0uczBo7usbLSDYpKkqiONxZchZaiL6b8 ltLfTAiQWo8aWloR1Q5enVkQhN0KexhIa2bdCpuCfdtDUAOteNka+6nspU7FH3YU BF/GdP+nLLnI/jYj9VqcLbN+XflvgakXHLzn57Ik3jtFsZYpfREL3XjOb0ZodIFm lAExZIy1bawdHSRahf0esxpGtswZrHlfyP8LwpAdazIQyYqxD2+R7oD1fsKXC7uG hvYairIOJOZ3mmEtIG0HkYNxTH8d1EuJHtXXcFWRboCnMqL5KaqQUdXRdisPLeJp P0dMiGVJAL8dN4A9BBz1q73e0HI/Y+OzNymNL2lwQUWKPrO2glgP9Cr/7NOOsbYU LWeeVz+jqItS8Kf6HXsiN3KVUsAwPk3g1I/IG+Z7ADHOiwayntk3lufWWROVo0Io 5A92tAlAXxwFJjkWAAPbCvjxJF98Z5KKt+8cP1VIaX25qHxsGfmX1FFgiCUDN0XI KyWpeuacpI8Qnyj9G3U3gqWhBAYkNne1EP4TwFA8Q9E8fKV9NgSA73kTMdqSnXyh 48OoSW7nWA9D188korO46qM8k54q2NTmoiEz5Q27U0t8/gnGzJ+h3jJjWI7kbUTr nUgJaChsce/xgCqI24c6y/Ypnjc2Is6l2ja8siIXhJehFS653J3TX6D3FwZZdw4r XID568OB5pb8xWiwoahLPsuHdclN7sZY6eb09TDXLK7hixezX0ntwJY9VVQUzWvP c8oUUuS4/F89+fcwNJl+1Yd5dEj8Z158ImSd8i3CxX8/ErnmJeaP5En+e/PJ+fYu nATo1VWUlyhD6lpjBbMs1HGELvFj/8TlBT/n5frmMvOx7TfkcCtYrA/EAuvV5DeO LE0iFX2UHVV4WaGLIXLE+woDMvsSckdtBsbxcSdYl+1tsAy0ok/OLdjYssseWj2e KJDL6XEC5IjTiO41UcwpJN0W5TU4aUcGLBbr8fO1wUkPrthezj2bGYlHeRIKbONb joQ1SXYqXzeokgJo1iEsS0xWJ+6TKSBgnn8QMLD/hY1SkcuadTdowUG2RTubhQts RfH4YgAHeiEXItKiCKLvmmuK3FFplRN9mtf4f+SJgS9I2tMfqokgMAUyVRdJ7NO5 k0gW2JRPg+qL3PXY5JQvsIfPATxSBHldSnYE+iLctZ+0hQRan0b93oQPpdED4xHR PqfcEOB0y1sfJfU1gYLU8+PgCcQdQhMGegMu8gM3cU00fa5nd7GCTtzD8ZtMAyR1 HW0MjzxbzleQuks41t5N4xyZBbC/nYU2dtkFgkOQ8OQVT/YOUlQwOWrWM4LozvXq g9p6Jfx+zW6C8i+uSRrniGNbD3PddWT1U/Myn8nbyvSvXjysL7hPxuHyjZmHmhad b9NmVcROFHOKXlLWVMbimXXZTbeqm9ouduKVTmWfZSq86YaIeIfQMJW7iER7 dovecot-2.3.21.1/src/lib-dcrypt/test-crypto.c0000644000000000000000000012102014656633576015612 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "buffer.h" #include "str.h" #include "dcrypt.h" #include "dcrypt-iostream.h" #include "ostream.h" #include "ostream-encrypt.h" #include "istream.h" #include "iostream-temp.h" #include "randgen.h" #include "test-common.h" #include "hex-binary.h" #include "json-parser.h" #include #include #include static void test_cipher_test_vectors(void) { static const struct { const char *key; const char *iv; const char *pt; const char *ct; } vectors[] = { { "2b7e151628aed2a6abf7158809cf4f3c", "000102030405060708090a0b0c0d0e0f", "6bc1bee22e409f96e93d7e117393172a", "7649abac8119b246cee98e9b12e9197d" }, { "2b7e151628aed2a6abf7158809cf4f3c", "7649ABAC8119B246CEE98E9B12E9197D", "ae2d8a571e03ac9c9eb76fac45af8e51", "5086cb9b507219ee95db113a917678b2" } }; test_begin("test_cipher_test_vectors"); buffer_t *key,*iv,*pt,*ct,*res_enc,*res_dec; key = t_buffer_create(16); iv = t_buffer_create(16); pt = t_buffer_create(16); ct = t_buffer_create(16); res_enc = t_buffer_create(32); res_dec = t_buffer_create(32); for(size_t i = 0; i < N_ELEMENTS(vectors); i++) { struct dcrypt_context_symmetric *ctx; buffer_set_used_size(key, 0); buffer_set_used_size(iv, 0); buffer_set_used_size(pt, 0); buffer_set_used_size(ct, 0); buffer_set_used_size(res_enc, 0); buffer_set_used_size(res_dec, 0); hex_to_binary(vectors[i].key, key); hex_to_binary(vectors[i].iv, iv); hex_to_binary(vectors[i].pt, pt); hex_to_binary(vectors[i].ct, ct); if (!dcrypt_ctx_sym_create("AES-128-CBC", DCRYPT_MODE_ENCRYPT, &ctx, NULL)) { test_assert_failed("dcrypt_ctx_sym_create", __FILE__, __LINE__-1); continue; } dcrypt_ctx_sym_set_padding(ctx, FALSE); dcrypt_ctx_sym_set_key(ctx, key->data, key->used); dcrypt_ctx_sym_set_iv(ctx, iv->data, iv->used); test_assert_idx(dcrypt_ctx_sym_init(ctx, NULL), i); test_assert_idx(dcrypt_ctx_sym_update(ctx, pt->data, pt->used, res_enc, NULL), i); test_assert_idx(dcrypt_ctx_sym_final(ctx, res_enc, NULL), i); test_assert_idx(buffer_cmp(ct, res_enc), i); dcrypt_ctx_sym_destroy(&ctx); if (!dcrypt_ctx_sym_create("AES-128-CBC", DCRYPT_MODE_DECRYPT, &ctx, NULL)) { test_assert_failed("dcrypt_ctx_sym_create", __FILE__, __LINE__-1); continue; } dcrypt_ctx_sym_set_padding(ctx, FALSE); dcrypt_ctx_sym_set_key(ctx, key->data, key->used); dcrypt_ctx_sym_set_iv(ctx, iv->data, iv->used); test_assert_idx(dcrypt_ctx_sym_init(ctx, NULL), i); test_assert_idx(dcrypt_ctx_sym_update(ctx, res_enc->data, res_enc->used, res_dec, NULL), i); test_assert_idx(dcrypt_ctx_sym_final(ctx, res_dec, NULL), i); test_assert_idx(buffer_cmp(pt, res_dec), i); dcrypt_ctx_sym_destroy(&ctx); } test_end(); } static void test_cipher_aead_test_vectors(void) { struct dcrypt_context_symmetric *ctx; const char *error = NULL; test_begin("test_cipher_aead_test_vectors"); if (!dcrypt_ctx_sym_create("aes-128-gcm", DCRYPT_MODE_ENCRYPT, &ctx, &error)) { test_assert_failed("dcrypt_ctx_sym_create", __FILE__, __LINE__-1); return; } buffer_t *key, *iv, *aad, *pt, *ct, *tag, *tag_res, *res; key = t_buffer_create(16); iv = t_buffer_create(16); aad = t_buffer_create(16); pt = t_buffer_create(16); ct = t_buffer_create(16); tag = t_buffer_create(16); res = t_buffer_create(16); tag_res = t_buffer_create(16); hex_to_binary("feffe9928665731c6d6a8f9467308308", key); hex_to_binary("cafebabefacedbaddecaf888", iv); hex_to_binary("d9313225f88406e5a55909c5aff5269a" "86a7a9531534f7da2e4c303d8a318a72" "1c3c0c95956809532fcf0e2449a6b525" "b16aedf5aa0de657ba637b391aafd255", pt); hex_to_binary("42831ec2217774244b7221b784d0d49c" "e3aa212f2c02a4e035c17e2329aca12e" "21d514b25466931c7d8f6a5aac84aa05" "1ba30b396a0aac973d58e091473f5985", ct); hex_to_binary("4d5c2af327cd64a62cf35abd2ba6fab4", tag); dcrypt_ctx_sym_set_key(ctx, key->data, key->used); dcrypt_ctx_sym_set_iv(ctx, iv->data, iv->used); dcrypt_ctx_sym_set_aad(ctx, aad->data, aad->used); test_assert(dcrypt_ctx_sym_init(ctx, &error)); test_assert(dcrypt_ctx_sym_update(ctx, pt->data, pt->used, res, &error)); test_assert(dcrypt_ctx_sym_final(ctx, res, &error)); test_assert(dcrypt_ctx_sym_get_tag(ctx, tag_res)); test_assert(buffer_cmp(ct, res) == TRUE); test_assert(buffer_cmp(tag, tag_res) == TRUE); dcrypt_ctx_sym_destroy(&ctx); if (!dcrypt_ctx_sym_create("aes-128-gcm", DCRYPT_MODE_DECRYPT, &ctx, &error)) { test_assert_failed("dcrypt_ctx_sym_create", __FILE__, __LINE__-1); } else { buffer_set_used_size(res, 0); dcrypt_ctx_sym_set_key(ctx, key->data, key->used); dcrypt_ctx_sym_set_iv(ctx, iv->data, iv->used); dcrypt_ctx_sym_set_aad(ctx, aad->data, aad->used); dcrypt_ctx_sym_set_tag(ctx, tag->data, tag->used); test_assert(dcrypt_ctx_sym_init(ctx, &error)); test_assert(dcrypt_ctx_sym_update(ctx, ct->data, ct->used, res, &error)); test_assert(dcrypt_ctx_sym_final(ctx, res, &error)); test_assert(buffer_cmp(pt, res) == TRUE); dcrypt_ctx_sym_destroy(&ctx); } test_end(); } static void test_hmac_test_vectors(void) { test_begin("test_hmac_test_vectors"); buffer_t *pt, *ct, *key, *res; pt = t_buffer_create(50); key = t_buffer_create(20); ct = t_buffer_create(32); res = t_buffer_create(32); hex_to_binary("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", key); hex_to_binary("dddddddddddddddddddddddddddddddddddddddddddddddddd" "dddddddddddddddddddddddddddddddddddddddddddddddddd", pt); hex_to_binary("773ea91e36800e46854db8ebd09181a7" "2959098b3ef8c122d9635514ced565fe", res); struct dcrypt_context_hmac *hctx; if (!dcrypt_ctx_hmac_create("sha256", &hctx, NULL)) { test_assert_failed("dcrypt_ctx_hmac_create", __FILE__, __LINE__-1); } else { dcrypt_ctx_hmac_set_key(hctx, key->data, key->used); test_assert(dcrypt_ctx_hmac_init(hctx, NULL)); test_assert(dcrypt_ctx_hmac_update(hctx, pt->data, pt->used, NULL)); test_assert(dcrypt_ctx_hmac_final(hctx, ct, NULL)); test_assert(buffer_cmp(ct, res)); dcrypt_ctx_hmac_destroy(&hctx); } test_end(); } static void test_load_v1_keys(void) { test_begin("test_load_v1_keys"); const char *error = NULL; const char *data1 = "1\t716\t1\t0567e6bf9579813ae967314423b0fceb14bda24" "749303923de9a9bb9370e0026f995901a57e63113eeb2baf0c" "940e978d00686cbb52bd5014bc318563375876255\t0300E46" "DA2125427BE968EB3B649910CDC4C405E5FFDE18D433A97CAB" "FEE28CEEFAE9EE356C792004FFB80981D67E741B8CC036A342" "35A8D2E1F98D1658CFC963D07EB\td0cfaca5d335f9edc41c8" "4bb47465184cb0e2ec3931bebfcea4dd433615e77a0\t7c9a1" "039ea2e4fed73e81dd3ffc3fa22ea4a28352939adde7bf8ea8" "58b00fa4f"; enum dcrypt_key_format format; enum dcrypt_key_version version; enum dcrypt_key_kind kind; enum dcrypt_key_encryption_type encryption_type; const char *encryption_key_hash = NULL; const char *key_hash = NULL; bool ret = dcrypt_key_string_get_info(data1, &format, &version, &kind, &encryption_type, &encryption_key_hash, &key_hash, &error); test_assert(ret == TRUE); test_assert(error == NULL); test_assert(format == DCRYPT_FORMAT_DOVECOT); test_assert(version == DCRYPT_KEY_VERSION_1); test_assert(kind == DCRYPT_KEY_KIND_PRIVATE); test_assert(encryption_type == DCRYPT_KEY_ENCRYPTION_TYPE_KEY); test_assert(strcmp(encryption_key_hash, "d0cfaca5d335f9edc41c84bb47465184" "cb0e2ec3931bebfcea4dd433615e77a0") == 0); test_assert(strcmp(key_hash, "7c9a1039ea2e4fed73e81dd3ffc3fa22" "ea4a28352939adde7bf8ea858b00fa4f") == 0); const char* data2 = "1\t716\t0301EB00973C4EFC8FCECA4EA33E941F50B561199A" "5159BCB6C2EED9DD1D62D65E38A254979D89E28F0C28883E71" "EE2AD264CD16B863FA094A8F6F69A56B62E8918040\t7c9a10" "39ea2e4fed73e81dd3ffc3fa22ea4a28352939adde7bf8ea85" "8b00fa4f"; error = NULL; encryption_key_hash = NULL; key_hash = NULL; ret = dcrypt_key_string_get_info(data2, &format, &version, &kind, &encryption_type, &encryption_key_hash, &key_hash, &error); test_assert(ret == TRUE); test_assert(error == NULL); test_assert(format == DCRYPT_FORMAT_DOVECOT); test_assert(version == DCRYPT_KEY_VERSION_1); test_assert(kind == DCRYPT_KEY_KIND_PUBLIC); test_assert(encryption_type == DCRYPT_KEY_ENCRYPTION_TYPE_NONE); test_assert(encryption_key_hash == NULL); test_assert(strcmp(key_hash, "7c9a1039ea2e4fed73e81dd3ffc3fa22" "ea4a28352939adde7bf8ea858b00fa4f") == 0); /* This is the key that should be able to decrypt key1 */ const char *data3 = "1\t716\t0\t048FD04FD3612B22D32790C592CF21CEF417EFD" "2EA34AE5F688FA5B51BED29E05A308B68DA78E16E90B47A11E" "133BD9A208A2894FD01B0BEE865CE339EA3FB17AC\td0cfaca" "5d335f9edc41c84bb47465184cb0e2ec3931bebfcea4dd4336" "15e77a0"; error = NULL; encryption_key_hash = NULL; key_hash = NULL; ret = dcrypt_key_string_get_info(data3, &format, &version, &kind, &encryption_type, &encryption_key_hash, &key_hash, &error); test_assert(ret == TRUE); test_assert(error == NULL); test_assert(format == DCRYPT_FORMAT_DOVECOT); test_assert(version == DCRYPT_KEY_VERSION_1); test_assert(kind == DCRYPT_KEY_KIND_PRIVATE); test_assert(encryption_type == DCRYPT_KEY_ENCRYPTION_TYPE_NONE); test_assert(encryption_key_hash == NULL); test_assert(strcmp(key_hash, "d0cfaca5d335f9edc41c84bb47465184" "cb0e2ec3931bebfcea4dd433615e77a0") == 0); /* key3's key_hash should and does match key1's encryption_key_hash */ struct dcrypt_private_key *pkey = NULL; struct dcrypt_private_key *pkey2 = NULL; pkey = NULL; error = NULL; ret = dcrypt_key_load_private(&pkey2, data3, NULL, NULL, &error); test_assert(ret == TRUE); test_assert(error == NULL); ret = dcrypt_key_load_private(&pkey, data1, NULL, pkey2, &error); test_assert(ret == TRUE); test_assert(error == NULL); dcrypt_key_unref_private(&pkey2); dcrypt_key_unref_private(&pkey); test_end(); } static void test_load_v1_key(void) { test_begin("test_load_v1_key"); buffer_t *key_1 = t_buffer_create(128); struct dcrypt_private_key *pkey = NULL, *pkey2 = NULL; const char *error = NULL; test_assert(dcrypt_key_load_private(&pkey, "1\t716\t0\t048FD04FD3612B22D32790C592CF21CEF417EFD" "2EA34AE5F688FA5B51BED29E05A308B68DA78E16E90B47A11E" "133BD9A208A2894FD01B0BEE865CE339EA3FB17AC\td0cfaca" "5d335f9edc41c84bb47465184cb0e2ec3931bebfcea4dd4336" "15e77a0", NULL, NULL, &error)); if (pkey != NULL) { buffer_set_used_size(key_1, 0); /* check that key_id matches */ struct dcrypt_public_key *pubkey = NULL; dcrypt_key_convert_private_to_public(pkey, &pubkey); test_assert(dcrypt_key_store_public(pubkey, DCRYPT_FORMAT_DOVECOT, key_1, NULL)); buffer_set_used_size(key_1, 0); dcrypt_key_id_public(pubkey, "sha256", key_1, &error); test_assert(strcmp("792caad4d38c9eb2134a0cbc844eae38" "6116de096a0ccafc98479825fc99b6a1", binary_to_hex(key_1->data, key_1->used)) == 0); dcrypt_key_unref_public(&pubkey); pkey2 = NULL; test_assert(dcrypt_key_load_private(&pkey2, "1\t716\t1\t0567e6bf9579813ae967314423b0fceb14" "bda24749303923de9a9bb9370e0026f995901a57e6311" "3eeb2baf0c940e978d00686cbb52bd5014bc318563375" "876255\t0300E46DA2125427BE968EB3B649910CDC4C4" "05E5FFDE18D433A97CABFEE28CEEFAE9EE356C792004F" "FB80981D67E741B8CC036A34235A8D2E1F98D1658CFC9" "63D07EB\td0cfaca5d335f9edc41c84bb47465184cb0e" "2ec3931bebfcea4dd433615e77a0\t7c9a1039ea2e4fe" "d73e81dd3ffc3fa22ea4a28352939adde7bf8ea858b00" "fa4f", NULL, pkey, &error)); if (pkey2 != NULL) { buffer_set_used_size(key_1, 0); /* check that key_id matches */ struct dcrypt_public_key *pubkey = NULL; dcrypt_key_convert_private_to_public(pkey2, &pubkey); test_assert(dcrypt_key_store_public(pubkey, DCRYPT_FORMAT_DOVECOT, key_1, NULL)); buffer_set_used_size(key_1, 0); test_assert(dcrypt_key_id_public_old(pubkey, key_1, &error)); test_assert(strcmp( "7c9a1039ea2e4fed73e81dd3ffc3fa22" "ea4a28352939adde7bf8ea858b00fa4f", binary_to_hex(key_1->data, key_1->used)) == 0); dcrypt_key_unref_public(&pubkey); dcrypt_key_unref_private(&pkey2); } dcrypt_key_unref_private(&pkey); } test_end(); } static void test_load_v1_public_key(void) { test_begin("test_load_v1_public_key"); const char* data1 = "1\t716\t030131D8A5FD5167947A0AE9CB112ADED652665463" "5AA5887051EE2364414B60FF32EBA8FA0BBE9485DBDE8794BB" "BCB44BBFC0D662A4287A848BA570D4E5E45A11FE0F\td0cfac" "a5d335f9edc41c84bb47465184cb0e2ec3931bebfcea4dd433" "615e77a0"; const char* error = NULL; const char* key_hash = NULL; const char* encryption_key_hash = NULL; enum dcrypt_key_format format; enum dcrypt_key_version version; enum dcrypt_key_kind kind; enum dcrypt_key_encryption_type encryption_type; bool ret = dcrypt_key_string_get_info(data1, &format, &version, &kind, &encryption_type, &encryption_key_hash, &key_hash, &error); test_assert(ret == TRUE); test_assert(error == NULL); test_assert(format == DCRYPT_FORMAT_DOVECOT); test_assert(version == DCRYPT_KEY_VERSION_1); test_assert(kind == DCRYPT_KEY_KIND_PUBLIC); test_assert(encryption_type == DCRYPT_KEY_ENCRYPTION_TYPE_NONE); test_assert(key_hash != NULL && strcmp(key_hash, "d0cfaca5d335f9edc41c84bb47465184" "cb0e2ec3931bebfcea4dd433615e77a0") == 0); test_assert(encryption_key_hash == NULL); struct dcrypt_public_key *pub_key = NULL; ret = dcrypt_key_load_public(&pub_key, data1, &error); test_assert(ret == TRUE); test_assert(error == NULL); test_assert(dcrypt_key_type_public(pub_key) == DCRYPT_KEY_EC); dcrypt_key_unref_public(&pub_key); test_assert(pub_key == NULL); test_end(); } static void test_load_v2_key(void) { const char *keys[] = { "-----BEGIN PRIVATE KEY-----\n" "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgtu" "QJA+uboZWVwgHc\n" "DciyVdrovAPwlMqshDK3s78IDDuhRANCAAQm0VEdzLB9PtD0HA" "8JK1zifWnj8M00\n" "FQzedfp9SQsWyA8dzs5/NFR5MTe6Xbh/ndKEs1zZH3vZ4FlNri" "lZc0st\n" "-----END PRIVATE KEY-----\n", "2:1.2.840.10045.3.1.7:0:0000002100b6e40903eb9ba195" "95c201dc0dc8b255dae8bc03f094caac8432b7b3bf080c3b:a" "b13d251976dedab546b67354e7678821740dd534b749c2857f" "66bf62bbaddfd", "2:1.2.840.10045.3.1.7:2:aes-256-ctr:483bd74fd3d917" "63:sha256:2048:d44ae35d3af7a2febcb15cde0c3693e7ed9" "8595665ed655a97fa918d346d5c661a6e2339f4:ab13d25197" "6dedab546b67354e7678821740dd534b749c2857f66bf62bba" "ddfd", "2:1.2.840.10045.3.1.7:1:aes-256-ctr:2574c10be28a4c" "09:sha256:2048:a750ec9dea91999f108f943485a20f273f4" "0f75c37fc9bcccdedda514c8243e550d69ce1bd:02237a199d" "7d945aa6492275a02881071eceec5749caf2485da8c64fb601" "229098:ab13d251976dedab546b67354e7678821740dd534b7" "49c2857f66bf62bbaddfd:ab13d251976dedab546b67354e76" "78821740dd534b749c2857f66bf62bbaddfd" }; test_begin("test_load_v2_key"); const char *error = NULL; buffer_t *tmp = buffer_create_dynamic(default_pool, 256); struct dcrypt_private_key *priv,*priv2; test_assert_idx(dcrypt_key_load_private(&priv2, keys[0], NULL, NULL, &error), 0); test_assert_idx(dcrypt_key_store_private(priv2, DCRYPT_FORMAT_PEM, NULL, tmp, NULL, NULL, &error), 0); test_assert_idx(strcmp(str_c(tmp), keys[0])==0, 0); buffer_set_used_size(tmp, 0); test_assert_idx(dcrypt_key_load_private(&priv, keys[1], NULL, NULL, &error), 1); test_assert_idx(dcrypt_key_store_private(priv, DCRYPT_FORMAT_DOVECOT, NULL, tmp, NULL, NULL, &error), 1); test_assert_idx(strcmp(str_c(tmp), keys[1])==0, 1); buffer_set_used_size(tmp, 0); dcrypt_key_unref_private(&priv); test_assert_idx(dcrypt_key_load_private(&priv, keys[2], "This Is Sparta", NULL, &error), 2); test_assert_idx(dcrypt_key_store_private(priv, DCRYPT_FORMAT_DOVECOT, "aes-256-ctr", tmp, "This Is Sparta", NULL, &error), 2); buffer_set_used_size(tmp, 0); dcrypt_key_unref_private(&priv); struct dcrypt_public_key *pub = NULL; dcrypt_key_convert_private_to_public(priv2, &pub); test_assert_idx(dcrypt_key_load_private(&priv, keys[3], NULL, priv2, &error), 3); test_assert_idx(dcrypt_key_store_private(priv, DCRYPT_FORMAT_DOVECOT, "ecdh-aes-256-ctr", tmp, NULL, pub, &error), 3); buffer_set_used_size(tmp, 0); dcrypt_key_unref_private(&priv2); dcrypt_key_unref_private(&priv); dcrypt_key_unref_public(&pub); buffer_free(&tmp); if (error != NULL) error = NULL; test_end(); } static void test_load_v2_public_key(void) { struct dcrypt_public_key *pub = NULL; const char *error; test_begin("test_load_v2_public_key"); const char *key = "2:3058301006072a8648ce3d020106052b810400230344000" "301c50954e734dd8b410a607764a7057065a45510da52f2c6" "e28e0cb353b9c389fa8cb786943ae991fce9befed78fb162f" "bbc615415f06af06c8cc80c37f4e94ff6c7:185a721254278" "2e239111f9c19d126ad55b18ddaf4883d66afe8d9627c3607" "d8"; test_assert(dcrypt_key_load_public(&pub, key, &error)); buffer_t *tmp = buffer_create_dynamic(default_pool, 256); if (pub != NULL) { test_assert(dcrypt_key_store_public(pub, DCRYPT_FORMAT_DOVECOT, tmp, &error)); test_assert(strcmp(key, str_c(tmp))==0); buffer_free(&tmp); dcrypt_key_unref_public(&pub); } test_end(); } static void test_get_info_v2_key(void) { test_begin("test_get_info_v2_key"); const char *key = "2:305e301006072a8648ce3d020106052b81040026034a0002" "03fcc90034fa03d6fb79a0fc8b3b43c3398f68e76029307360" "cdcb9e27bb7e84b3c19dfb7244763bc4d442d216f09b7b7945" "ed9d182f3156550e9ee30b237a0217dbf79d28975f31:86706" "b69d1f640011a65d26a42f2ba20a619173644e1cc7475eb1d9" "0966e84dc"; enum dcrypt_key_format format; enum dcrypt_key_version version = DCRYPT_KEY_VERSION_NA; enum dcrypt_key_kind kind; enum dcrypt_key_encryption_type encryption_type; const char *encryption_key_hash = NULL; const char *key_hash = NULL; const char *error = NULL; test_assert(dcrypt_key_string_get_info(key, &format, &version, &kind, &encryption_type, &encryption_key_hash, &key_hash, &error)); test_assert(error == NULL); test_assert(format == DCRYPT_FORMAT_DOVECOT); test_assert(version == DCRYPT_KEY_VERSION_2); test_assert(kind == DCRYPT_KEY_KIND_PUBLIC); test_assert(encryption_type == DCRYPT_KEY_ENCRYPTION_TYPE_NONE); test_assert(encryption_key_hash == NULL); test_assert(key_hash != NULL && strcmp(key_hash, "86706b69d1f640011a65d26a42f2ba20" "a619173644e1cc7475eb1d90966e84dc") == 0); test_end(); } static void test_gen_and_get_info_rsa_pem(void) { test_begin("test_gen_and_get_info_rsa_pem"); const char *error = NULL; bool ret = FALSE; struct dcrypt_keypair pair; string_t* buf = str_new(default_pool, 4096); ret = dcrypt_keypair_generate(&pair, DCRYPT_KEY_RSA, 1024, NULL, NULL); test_assert(ret == TRUE); /* test public key */ enum dcrypt_key_format format; enum dcrypt_key_version version; enum dcrypt_key_kind kind; enum dcrypt_key_encryption_type encryption_type; const char *encryption_key_hash; const char *key_hash; ret = dcrypt_key_store_public(pair.pub, DCRYPT_FORMAT_PEM, buf, &error); test_assert(ret == TRUE); ret = dcrypt_key_string_get_info(str_c(buf), &format, &version, &kind, &encryption_type, &encryption_key_hash, &key_hash, &error); test_assert(ret == TRUE); test_assert(format == DCRYPT_FORMAT_PEM); test_assert(version == DCRYPT_KEY_VERSION_NA); test_assert(kind == DCRYPT_KEY_KIND_PUBLIC); test_assert(encryption_type == DCRYPT_KEY_ENCRYPTION_TYPE_NONE); test_assert(encryption_key_hash == NULL); test_assert(key_hash == NULL); /* test private key */ buffer_set_used_size(buf, 0); ret = dcrypt_key_store_private(pair.priv, DCRYPT_FORMAT_PEM, NULL, buf, NULL, NULL, &error); test_assert(ret == TRUE); ret = dcrypt_key_string_get_info(str_c(buf), &format, &version, &kind, &encryption_type, &encryption_key_hash, &key_hash, &error); test_assert(ret == TRUE); test_assert(format == DCRYPT_FORMAT_PEM); test_assert(version == DCRYPT_KEY_VERSION_NA); test_assert(kind == DCRYPT_KEY_KIND_PRIVATE); test_assert(encryption_type == DCRYPT_KEY_ENCRYPTION_TYPE_NONE); test_assert(encryption_key_hash == NULL); test_assert(key_hash == NULL); dcrypt_keypair_unref(&pair); buffer_free(&buf); test_end(); } static void test_get_info_rsa_private_key(void) { test_begin("test_get_info_rsa_private_key"); const char *key = "-----BEGIN RSA PRIVATE KEY-----\n" "MIICXQIBAAKBgQC89q02I9NezBLQ+otn5XLYE7S+GsKUz59ogr45DA/6MI9jey0W\n" "56SeWQ1FJD1vDhAx/TRBMfOmhcIPsBjc5sakYOawPdoiqLjOIlO+iHwnbbmLuMsq\n" "ue09vgvZsKjuTr2F5DOFQY43Bq/Nd+4bjHJItdOM58+xwA2I/8vDbtI8jwIDAQAB\n" "AoGBAJCUrTMfdjqyKjN7f+6ewKBTc5eBIiB6O53ba3B6qj7jqNKVDIrZ8jq2KFEe\n" "yWKPgBS/h5vafHKNJU6bjmp2qMUJPB7PTA876eDo0cq9PplUqihiTlXJFwNQYtF+\n" "o27To5t25+5qdSAj657+lQfFT9Xn9fzYHDmotURxH10FgFkBAkEA+7Ny6lBTeb3W\n" "LnP0UPfPzQLilEr8u81PLWe69RGtsEaMQHGpHOl4e+bvvVYbG1cgxwxI1m01uR9r\n" "qpD3qLUdrQJBAMAw6UvN8R+opYTZzwqK7Nliil2QZMPmXM04SV1iFq26NM60w2Fm\n" "HqOOh0EbpSWsFtIgxJFWoZOtrguxqCJuUqsCQF3EoXf3StHczhDqM8eCOpD2lTCH\n" "qxXPy8JvlW+9EUbNUWykq0rRE4idJQ0VKe4KjHR6+Buh/dSkhvi5Hvpj1tUCQHRv\n" "LWeXZLVhXqWVrzEb6VHpuRnmGKX2MdLCfu/sNQEbBlMUgCnJzFYaSybOsMaZ81lq\n" "MKw8Z7coSYEcKFhzrfECQQD7l+4Bhy8Zuz6VoGGIZwIhxkJrImBFmaUwx8N6jg20\n" "sgDRYwCoGkGd7B8uIHZLJoWzSSutHiu5i5PYUy5VT1yT\n" "-----END RSA PRIVATE KEY-----\n"; const char *error = NULL; test_assert(!dcrypt_key_string_get_info(key, NULL, NULL, NULL, NULL, NULL, NULL, &error)); test_assert(error != NULL && strstr(error, "pkey") != NULL); test_end(); } static void test_get_info_invalid_keys(void) { test_begin("test_get_info_invalid_keys"); const char *key = "1:716:030131D8A5FD5167947A0AE9CB112ADED6526654635A" "A5887051EE2364414B60FF32EBA8FA0BBE9485DBDE8794BBBC" "B44BBFC0D662A4287A848BA570D4E5E45A11FE0F:d0cfaca5d" "335f9edc41c84bb47465184cb0e2ec3931bebfcea4dd433615" "e77a0"; const char *error = NULL; test_assert(dcrypt_key_string_get_info(key, NULL, NULL, NULL, NULL, NULL, NULL, &error) == FALSE); test_assert(error != NULL && strstr(error, "tab") != NULL); key = "2\t305e301006072a8648ce3d020106052b81040026034a000" "203fcc90034fa03d6fb79a0fc8b3b43c3398f68e7602930736" "0cdcb9e27bb7e84b3c19dfb7244763bc4d442d216f09b7b794" "5ed9d182f3156550e9ee30b237a0217dbf79d28975f31\t867" "06b69d1f640011a65d26a42f2ba20a619173644e1cc7475eb1" "d90966e84dc"; error = NULL; test_assert(dcrypt_key_string_get_info(key, NULL, NULL, NULL, NULL, NULL, NULL, &error) == FALSE); test_assert(error != NULL && strstr(error, "colon") != NULL); key = "2"; error = NULL; test_assert(dcrypt_key_string_get_info(key, NULL, NULL, NULL, NULL, NULL, NULL, &error) == FALSE); test_assert(error != NULL && strstr(error, "Unknown") != NULL); test_end(); } static void test_get_info_key_encrypted(void) { test_begin("test_get_info_key_encrypted"); struct dcrypt_keypair p1, p2; const char *error = NULL; bool ret = dcrypt_keypair_generate(&p1, DCRYPT_KEY_EC, 0, "secp521r1", &error); test_assert(ret == TRUE); ret = dcrypt_keypair_generate(&p2, DCRYPT_KEY_EC, 0, "secp521r1", &error); test_assert(ret == TRUE); string_t* buf = t_str_new(4096); buffer_set_used_size(buf, 0); ret = dcrypt_key_store_private(p1.priv, DCRYPT_FORMAT_DOVECOT, "ecdh-aes-256-ctr", buf, NULL, p2.pub, &error); test_assert(ret == TRUE); enum dcrypt_key_format format; enum dcrypt_key_version version; enum dcrypt_key_kind kind; enum dcrypt_key_encryption_type enc_type; const char *enc_hash; const char *key_hash; ret = dcrypt_key_string_get_info(str_c(buf), &format, &version, &kind, &enc_type, &enc_hash, &key_hash, &error); test_assert(ret == TRUE); test_assert(format == DCRYPT_FORMAT_DOVECOT); test_assert(version == DCRYPT_KEY_VERSION_2); test_assert(kind == DCRYPT_KEY_KIND_PRIVATE); test_assert(enc_type == DCRYPT_KEY_ENCRYPTION_TYPE_KEY); test_assert(enc_hash != NULL); test_assert(key_hash != NULL); dcrypt_keypair_unref(&p1); dcrypt_keypair_unref(&p2); test_end(); } static void test_get_info_pw_encrypted(void) { test_begin("test_get_info_pw_encrypted"); struct dcrypt_keypair p1; i_zero(&p1); const char *error; bool ret = dcrypt_keypair_generate(&p1, DCRYPT_KEY_EC, 0, "secp521r1", &error); test_assert(ret == TRUE); string_t* buf = t_str_new(4096); ret = dcrypt_key_store_private(p1.priv, DCRYPT_FORMAT_DOVECOT, "aes-256-ctr", buf, "pw", NULL, &error); test_assert(ret == TRUE); enum dcrypt_key_format format; enum dcrypt_key_version version; enum dcrypt_key_kind kind; enum dcrypt_key_encryption_type enc_type; const char *enc_hash; const char *key_hash; ret = dcrypt_key_string_get_info(str_c(buf), &format, &version, &kind, &enc_type, &enc_hash, &key_hash, &error); test_assert(ret == TRUE); test_assert(format == DCRYPT_FORMAT_DOVECOT); test_assert(version == DCRYPT_KEY_VERSION_2); test_assert(kind == DCRYPT_KEY_KIND_PRIVATE); test_assert(enc_type == DCRYPT_KEY_ENCRYPTION_TYPE_PASSWORD); test_assert(enc_hash == NULL); test_assert(key_hash != NULL); dcrypt_keypair_unref(&p1); test_end(); } static void test_password_change(void) { test_begin("test_password_change"); const char *pw1 = "first password"; struct dcrypt_keypair orig; const char *error = NULL; bool ret = dcrypt_keypair_generate(&orig, DCRYPT_KEY_EC, 0, "secp521r1", &error); test_assert(ret == TRUE); string_t *buf = t_str_new(4096); ret = dcrypt_key_store_private(orig.priv, DCRYPT_FORMAT_DOVECOT, "aes-256-ctr", buf, pw1, NULL, &error); test_assert(ret == TRUE); /* load the pw-encrypted key */ struct dcrypt_private_key *k1_priv = NULL; ret = dcrypt_key_load_private(&k1_priv, str_c(buf), pw1, NULL, &error); test_assert(ret == TRUE); /* encrypt a key with the pw-encrypted key k1 */ struct dcrypt_keypair k2; ret = dcrypt_keypair_generate(&k2, DCRYPT_KEY_EC, 0, "secp521r1", &error); test_assert(ret == TRUE); string_t *buf2 = t_str_new(4096); struct dcrypt_public_key *k1_pub = NULL; dcrypt_key_convert_private_to_public(k1_priv, &k1_pub); ret = dcrypt_key_store_private(k2.priv, DCRYPT_FORMAT_DOVECOT, "ecdh-aes-256-ctr", buf2, NULL, k1_pub, &error); test_assert(ret == TRUE); /* change the password */ const char *pw2 = "second password"; string_t *buf3 = t_str_new(4096); /* encrypt k1 with pw2 */ ret = dcrypt_key_store_private(k1_priv, DCRYPT_FORMAT_DOVECOT, "aes-256-ctr", buf3, pw2, NULL, &error); test_assert(ret == TRUE); /* load the pw2 encrypted key */ struct dcrypt_private_key *k2_priv = NULL; ret = dcrypt_key_load_private(&k2_priv, str_c(buf3), pw2, NULL, &error); test_assert(ret == TRUE); /* load the key that was encrypted with pw1 using the pw2 encrypted key */ struct dcrypt_private_key *k3_priv = NULL; ret = dcrypt_key_load_private(&k3_priv, str_c(buf2), NULL, k2_priv, &error); test_assert(ret == TRUE); dcrypt_key_unref_private(&k1_priv); dcrypt_key_unref_public(&k1_pub); dcrypt_key_unref_private(&k2_priv); dcrypt_key_unref_private(&k3_priv); dcrypt_keypair_unref(&orig); dcrypt_keypair_unref(&k2); test_end(); } static void test_load_invalid_keys(void) { test_begin("test_load_invalid_keys"); const char *error = NULL; const char *key = "1:716:0301EB00973C4EFC8FCECA4EA33E941F50B561199A51" "59BCB6C2EED9DD1D62D65E38A254979D89E28F0C28883E71EE" "2AD264CD16B863FA094A8F6F69A56B62E8918040:7c9a1039e" "a2e4fed73e81dd3ffc3fa22ea4a28352939adde7bf8ea858b0" "0fa4f"; struct dcrypt_public_key *pub_key = NULL; bool ret = dcrypt_key_load_public(&pub_key, key, &error); test_assert(ret == FALSE); test_assert(error != NULL); error = NULL; key = "2:305e301006072a8648ce3d020106052b81040026034a0002" "03fcc90034fa03d6fb79a0fc8b3b43c3398f68e76029307360" "cdcb9e27bb7e84b3c19dfb7244763bc4d442d216f09b7b7945" "ed9d182f3156550e9ee30b237a0217dbf79d28975f31:86706" "b69d1f640011a65d26a42f2ba20a619173644e1cc7475eb1d9" "0966e84dc"; struct dcrypt_private_key *priv_key = NULL; ret = dcrypt_key_load_private(&priv_key, key, NULL, NULL, &error); test_assert(ret == FALSE); test_assert(error != NULL); test_end(); } static void test_raw_keys(void) { test_begin("test_raw_keys"); ARRAY_TYPE(dcrypt_raw_key) priv_key; ARRAY_TYPE(dcrypt_raw_key) pub_key; pool_t pool = pool_datastack_create(); enum dcrypt_key_type t; p_array_init(&priv_key, pool, 2); p_array_init(&pub_key, pool, 2); /* generate ECC key */ struct dcrypt_keypair pair; i_assert(dcrypt_keypair_generate(&pair, DCRYPT_KEY_EC, 0, "prime256v1", NULL)); /* store it */ test_assert(dcrypt_key_store_private_raw(pair.priv, pool, &t, &priv_key, NULL)); test_assert(dcrypt_key_store_public_raw(pair.pub, pool, &t, &pub_key, NULL)); dcrypt_keypair_unref(&pair); /* load it */ test_assert(dcrypt_key_load_private_raw(&pair.priv, t, &priv_key, NULL)); test_assert(dcrypt_key_load_public_raw(&pair.pub, t, &pub_key, NULL)); dcrypt_keypair_unref(&pair); /* test load known raw private key */ const char *curve = "prime256v1"; const unsigned char priv_key_data[] = { 0x16, 0x9e, 0x62, 0x36, 0xaf, 0x9c, 0xae, 0x0e, 0x71, 0xda, 0xf2, 0x63, 0xe2, 0xe0, 0x5d, 0xf1, 0xd5, 0x35, 0x8c, 0x2b, 0x68, 0xf0, 0x2a, 0x69, 0xc4, 0x5d, 0x3d, 0x1c, 0xde, 0xa1, 0x9b, 0xd3 }; /* create buffers */ struct dcrypt_raw_key *item; ARRAY_TYPE(dcrypt_raw_key) static_key; t_array_init(&static_key, 2); /* Add OID */ buffer_t *buf = t_buffer_create(32); test_assert(dcrypt_name2oid(curve, buf, NULL)); item = array_append_space(&static_key); item->parameter = buf->data; item->len = buf->used; /* Add key data */ item = array_append_space(&static_key); item->parameter = priv_key_data; item->len = sizeof(priv_key_data); /* Try load it */ test_assert(dcrypt_key_load_private_raw(&pair.priv, t, &static_key, NULL)); /* See what we got */ buf = t_buffer_create(128); test_assert(dcrypt_key_store_private(pair.priv, DCRYPT_FORMAT_DOVECOT, NULL, buf, NULL, NULL, NULL)); test_assert_strcmp(str_c(buf), "2:1.2.840.10045.3.1.7:0:00000020169e6236af9cae0e71d" "af263e2e05df1d5358c2b68f02a69c45d3d1cdea19bd3:21d11" "6b7b3e5c52e81f0437a10b0116cfafc467fb1b96e48926d0216" "68fc1bea"); /* try to load public key, too */ const unsigned char pub_key_data[] = { 0x04, 0xe8, 0x7c, 0x6d, 0xa0, 0x29, 0xfe, 0x5d, 0x16, 0x1a, 0xd6, 0x6a, 0xc6, 0x1c, 0x78, 0x8a, 0x36, 0x0f, 0xfb, 0x64, 0xe7, 0x7f, 0x58, 0x13, 0xb3, 0x80, 0x1f, 0x99, 0x45, 0xee, 0xa9, 0x4a, 0xe2, 0xde, 0xf3, 0x88, 0xc6, 0x37, 0x72, 0x7f, 0xbe, 0x97, 0x02, 0x94, 0xb2, 0x21, 0x60, 0xa4, 0x98, 0x4e, 0xfb, 0x46, 0x19, 0x61, 0x4c, 0xc5, 0xe1, 0x9f, 0xe9, 0xb2, 0xd2, 0x4d, 0xae, 0x83, 0x4b }; array_clear(&static_key); /* Add OID */ buf = t_buffer_create(32); test_assert(dcrypt_name2oid(curve, buf, NULL)); item = array_append_space(&static_key); item->parameter = buf->data; item->len = buf->used; /* Add key data */ item = array_append_space(&static_key); item->parameter = pub_key_data; item->len = sizeof(pub_key_data); /* See what we got */ test_assert(dcrypt_key_load_public_raw(&pair.pub, t, &static_key, NULL)); buf = t_buffer_create(128); test_assert(dcrypt_key_store_public(pair.pub, DCRYPT_FORMAT_DOVECOT, buf, NULL)); test_assert_strcmp(str_c(buf), "2:3039301306072a8648ce3d020106082a8648ce3d03010703220003e87c6d" "a029fe5d161ad66ac61c788a360ffb64e77f5813b3801f9945eea94ae2:21d" "116b7b3e5c52e81f0437a10b0116cfafc467fb1b96e48926d021668fc1bea"); dcrypt_keypair_unref(&pair); test_end(); } static void test_sign_verify_rsa(void) { const char *error = NULL; bool valid; struct dcrypt_private_key *priv_key = NULL; struct dcrypt_public_key *pub_key = NULL; buffer_t *signature = buffer_create_dynamic(pool_datastack_create(), 128); const char *data = "signed data"; test_begin("sign and verify (rsa)"); const char *key = "-----BEGIN PRIVATE KEY-----\n" "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALz2rTYj017MEtD6\n" "i2flctgTtL4awpTPn2iCvjkMD/owj2N7LRbnpJ5ZDUUkPW8OEDH9NEEx86aFwg+w\n" "GNzmxqRg5rA92iKouM4iU76IfCdtuYu4yyq57T2+C9mwqO5OvYXkM4VBjjcGr813\n" "7huMcki104znz7HADYj/y8Nu0jyPAgMBAAECgYEAkJStMx92OrIqM3t/7p7AoFNz\n" "l4EiIHo7ndtrcHqqPuOo0pUMitnyOrYoUR7JYo+AFL+Hm9p8co0lTpuOanaoxQk8\n" "Hs9MDzvp4OjRyr0+mVSqKGJOVckXA1Bi0X6jbtOjm3bn7mp1ICPrnv6VB8VP1ef1\n" "/NgcOai1RHEfXQWAWQECQQD7s3LqUFN5vdYuc/RQ98/NAuKUSvy7zU8tZ7r1Ea2w\n" "RoxAcakc6Xh75u+9VhsbVyDHDEjWbTW5H2uqkPeotR2tAkEAwDDpS83xH6ilhNnP\n" "Cors2WKKXZBkw+ZczThJXWIWrbo0zrTDYWYeo46HQRulJawW0iDEkVahk62uC7Go\n" "Im5SqwJAXcShd/dK0dzOEOozx4I6kPaVMIerFc/Lwm+Vb70RRs1RbKSrStETiJ0l\n" "DRUp7gqMdHr4G6H91KSG+Lke+mPW1QJAdG8tZ5dktWFepZWvMRvpUem5GeYYpfYx\n" "0sJ+7+w1ARsGUxSAKcnMVhpLJs6wxpnzWWowrDxntyhJgRwoWHOt8QJBAPuX7gGH\n" "Lxm7PpWgYYhnAiHGQmsiYEWZpTDHw3qODbSyANFjAKgaQZ3sHy4gdksmhbNJK60e\n" "K7mLk9hTLlVPXJM=\n" "-----END PRIVATE KEY-----"; test_assert(dcrypt_key_load_private(&priv_key, key, NULL, NULL, &error)); if (priv_key == NULL) i_fatal("%s", error); dcrypt_key_convert_private_to_public(priv_key, &pub_key); test_assert(dcrypt_sign(priv_key, "sha256", DCRYPT_SIGNATURE_FORMAT_DSS, data, strlen(data), signature, 0, &error)); /* verify signature */ test_assert(dcrypt_verify(pub_key, "sha256", DCRYPT_SIGNATURE_FORMAT_DSS, data, strlen(data), signature->data, signature->used, &valid, 0, &error) && valid); dcrypt_key_unref_public(&pub_key); dcrypt_key_unref_private(&priv_key); test_end(); } static void test_sign_verify_ecdsa(void) { const char *error = NULL; bool valid; struct dcrypt_private_key *priv_key = NULL; struct dcrypt_public_key *pub_key = NULL; buffer_t *signature = buffer_create_dynamic(pool_datastack_create(), 128); const char *data = "signed data"; test_begin("sign and verify (ecdsa)"); const char *key = "-----BEGIN PRIVATE KEY-----\n" "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgZ4AMMyJ9XDl5lKM2\n" "vusbT1OQ6VzBWBkB3/4syovaKtyhRANCAAQHTR+6L2qMh5fdcMZF+Y1rctBsq8Oy\n" "7jZ4uV+MiuaoGNQ5sTxlcv6ETX/XrEDq4S/DUhFKzQ6u9VXYZImvRCT1\n" "-----END PRIVATE KEY-----"; test_assert(dcrypt_key_load_private(&priv_key, key, NULL, NULL, &error)); if (priv_key == NULL) i_fatal("%s", error); dcrypt_key_convert_private_to_public(priv_key, &pub_key); test_assert(dcrypt_sign(priv_key, "sha256", DCRYPT_SIGNATURE_FORMAT_DSS, data, strlen(data), signature, 0, &error)); /* verify signature */ test_assert(dcrypt_verify(pub_key, "sha256", DCRYPT_SIGNATURE_FORMAT_DSS, data, strlen(data), signature->data, signature->used, &valid, 0, &error) && valid); dcrypt_key_unref_public(&pub_key); dcrypt_key_unref_private(&priv_key); test_end(); } static void test_static_verify_ecdsa(void) { test_begin("static verify (ecdsa)"); const char *input = "hello, world"; const char *priv_key_pem = "-----BEGIN PRIVATE KEY-----\n" "MGcCAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcETTBLAgEBBCC25AkD65uhlZXCAdwN\n" "yLJV2ui8A/CUyqyEMrezvwgMO6EkAyIAAybRUR3MsH0+0PQcDwkrXOJ9aePwzTQV\n" "DN51+n1JCxbI\n" "-----END PRIVATE KEY-----"; const char *pub_key_pem = "-----BEGIN PUBLIC KEY-----\n" "MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgADJtFRHcywfT7Q9BwPCStc4n1p4/DN\n" "NBUM3nX6fUkLFsg=\n" "-----END PUBLIC KEY-----"; const unsigned char sig[] = { 0x30,0x45,0x02,0x20,0x2c,0x76,0x20,0x5e,0xfc,0xa6,0x9e,0x16, 0x44,0xb3,0xbc,0xbf,0xcc,0x43,0xc1,0x08,0x76,0x4a,0xe8,0x60, 0xc5,0x9b,0x99,0x20,0x5b,0x44,0x33,0x5c,0x38,0x84,0x63,0xcb, 0x02,0x21,0x00,0xa3,0x67,0xed,0x57,0xbf,0x59,0x46,0xb7,0x0c, 0x7b,0xec,0x4f,0x78,0x14,0xec,0xfa,0x8d,0xa2,0x85,0x48,0xea, 0xe1,0xaf,0x9e,0xbf,0x04,0xac,0x0e,0x41,0xfe,0x84,0x0e }; struct dcrypt_keypair pair; bool valid; const char *error; i_zero(&pair); /* static key test */ test_assert(dcrypt_key_load_public(&pair.pub, pub_key_pem, NULL)); test_assert(dcrypt_key_load_private(&pair.priv, priv_key_pem, NULL, NULL, NULL)); /* validate signature */ test_assert(dcrypt_verify(pair.pub, "sha256", DCRYPT_SIGNATURE_FORMAT_DSS, input, strlen(input), sig, sizeof(sig), &valid, 0, &error) && valid == TRUE); dcrypt_keypair_unref(&pair); test_end(); } static void test_jwk_keys(void) { /* Make sure this matches what comes out from store private */ const char *jwk_key_json = "{\"kty\":\"EC\"," "\"crv\":\"P-256\"," "\"x\":\"Kp0Y4-Wpt-D9t_2XenFIj0LmvaZByLG69yOisek4aMI\"," "\"y\":\"wjEPB5BhH5SRPw1cCN5grWrLCphrW19fCFR8p7c9O5o\"," "\"use\":\"sig\"," "\"kid\":\"123\"," "\"d\":\"Po2z9rs86J2Qb_xWprr4idsWNPlgKf3G8-mftnE2ync\"}"; /* Acquired using another tool */ const char *pem_key = "-----BEGIN PUBLIC KEY-----\n" "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKp0Y4+Wpt+D9t/2XenFIj0LmvaZB\n" "yLG69yOisek4aMLCMQ8HkGEflJE/DVwI3mCtassKmGtbX18IVHyntz07mg==\n" "-----END PUBLIC KEY-----"; test_begin("test_jwk_keys"); struct dcrypt_keypair pair; buffer_t *pem = t_buffer_create(256); i_zero(&pair); test_assert(dcrypt_key_load_public(&pair.pub, jwk_key_json, NULL)); test_assert(dcrypt_key_load_private(&pair.priv, jwk_key_json, NULL, NULL, NULL)); /* test accessors */ test_assert_strcmp(dcrypt_key_get_id_public(pair.pub), "123"); test_assert(dcrypt_key_get_usage_public(pair.pub) == DCRYPT_KEY_USAGE_SIGN); /* make sure we got the right key */ test_assert(dcrypt_key_store_public(pair.pub, DCRYPT_FORMAT_PEM, pem, NULL)); test_assert_strcmp(str_c(pem), pem_key); str_truncate(pem, 0); test_assert(dcrypt_key_store_private(pair.priv, DCRYPT_FORMAT_JWK, NULL, pem, NULL, NULL, NULL)); test_assert_strcmp(str_c(pem), jwk_key_json); dcrypt_keypair_unref(&pair); test_end(); } static void test_static_verify_rsa(void) { const char *error = NULL; bool valid; struct dcrypt_public_key *pub_key = NULL; test_begin("static verify (rsa)"); const char *data = "test signature input\n"; const unsigned char sig[] = { 0x6f,0x1b,0xfb,0xdd,0xdb,0xb1,0xcd,0x6f,0xf1,0x1b, 0xb8,0xad,0x71,0x75,0x6c,0x87,0x22,0x11,0xe4,0xc3, 0xe7,0xca,0x15,0x04,0xda,0x98,0xab,0x07,0x27,0xcc, 0x5a,0x4d,0xab,0xac,0x37,0x7a,0xff,0xd2,0xdf,0x37, 0x58,0x37,0x53,0x46,0xd5,0x6d,0x9d,0x73,0x83,0x90, 0xea,0x5e,0x2c,0xc7,0x51,0x9e,0xc4,0xda,0xc5,0x7d, 0xa5,0xcd,0xb7,0xd7,0x41,0x23,0x6d,0xb9,0x6d,0xe0, 0x99,0xa1,0x63,0x6b,0x60,0x5f,0x15,0x5b,0xda,0x21, 0x17,0x4c,0x37,0x68,0x67,0x7f,0x8e,0x02,0x93,0xd2, 0x86,0xdd,0xe5,0xa7,0xc3,0xd9,0x93,0x8b,0x0c,0x56, 0x1d,0x5c,0x60,0x63,0x3e,0x8b,0xbe,0x1f,0xb2,0xe7, 0x7f,0xe5,0x66,0x6f,0xcd,0x2b,0x0c,0x02,0x2a,0x12, 0x96,0x86,0x66,0x00,0xff,0x12,0x8a,0x79 }; const char *key = "-----BEGIN PUBLIC KEY-----\n" "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC89q02I9NezBLQ+otn5XLYE7S+\n" "GsKUz59ogr45DA/6MI9jey0W56SeWQ1FJD1vDhAx/TRBMfOmhcIPsBjc5sakYOaw\n" "PdoiqLjOIlO+iHwnbbmLuMsque09vgvZsKjuTr2F5DOFQY43Bq/Nd+4bjHJItdOM\n" "58+xwA2I/8vDbtI8jwIDAQAB\n" "-----END PUBLIC KEY-----"; test_assert(dcrypt_key_load_public(&pub_key, key, &error)); if (pub_key == NULL) i_fatal("%s", error); test_assert(dcrypt_verify(pub_key, "sha256", DCRYPT_SIGNATURE_FORMAT_DSS, data, strlen(data), sig, sizeof(sig), &valid, DCRYPT_PADDING_RSA_PKCS1, &error) && valid); dcrypt_key_unref_public(&pub_key); test_end(); } /* Sample values from RFC8292 */ static void test_static_verify_ecdsa_x962(void) { const char *error = NULL; bool valid; struct dcrypt_public_key *pub_key = NULL; test_begin("static verify (ecdsa x9.62)"); const char *data = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL3B1c" "2guZXhhbXBsZS5uZXQiLCJleHAiOjE0NTM1MjM3NjgsInN1YiI6Im1haWx0bzp" "wdXNoQGV4YW1wbGUuY29tIn0"; const unsigned char sig[] = { 0x8b,0x70,0x98,0x6f,0xbb,0x78,0xc5,0xfc,0x42,0x0e,0xab, 0xa9,0xb4,0x53,0x9e,0xa4,0x2f,0x46,0x02,0xef,0xc7,0x2c, 0x69,0x0c,0x94,0xcb,0x82,0x19,0x22,0xb6,0xae,0x98,0x94, 0x7e,0x72,0xbd,0xa2,0x31,0x70,0x0d,0x76,0xf5,0x26,0xb1, 0x2b,0xb6,0x6c,0xac,0x6b,0x33,0x63,0x8e,0xf5,0xb6,0x2f, 0xd3,0xa4,0x49,0x21,0xf3,0xbe,0x80,0xf5,0xa0 }; const char *key = "-----BEGIN PUBLIC KEY-----\n" "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDUfHPKLVFQzVvnCPGyfucbECzPDa\n" "7rWbXriLcysAjEcXpgrmHhINiJz51G5T9EI8J8Dlqr2iNLCTljYSYKUE+w==\n" "-----END PUBLIC KEY-----"; test_assert(dcrypt_key_load_public(&pub_key, key, &error)); if (pub_key == NULL) i_fatal("%s", error); test_assert(dcrypt_verify(pub_key, "sha256", DCRYPT_SIGNATURE_FORMAT_X962, data, strlen(data), sig, sizeof(sig), &valid, DCRYPT_PADDING_RSA_PKCS1, &error) && valid); dcrypt_key_unref_public(&pub_key); test_end(); } int main(void) { struct dcrypt_settings set = { .module_dir = ".libs" }; const char *error; if (!dcrypt_initialize(NULL, &set, &error)) { i_error("No functional dcrypt backend found - " "skipping tests: %s", error); return 0; } static void (*const test_functions[])(void) = { test_cipher_test_vectors, test_cipher_aead_test_vectors, test_hmac_test_vectors, test_load_v1_keys, test_load_v1_key, test_load_v1_public_key, test_load_v2_key, test_load_v2_public_key, test_get_info_v2_key, test_gen_and_get_info_rsa_pem, test_get_info_rsa_private_key, test_get_info_invalid_keys, test_get_info_key_encrypted, test_get_info_pw_encrypted, test_password_change, test_load_invalid_keys, test_raw_keys, test_jwk_keys, test_sign_verify_rsa, test_sign_verify_ecdsa, test_static_verify_ecdsa, test_static_verify_rsa, test_static_verify_ecdsa_x962, NULL }; int ret = test_run(test_functions); dcrypt_deinitialize(); return ret; } dovecot-2.3.21.1/src/lib-dcrypt/dcrypt-private.h0000644000000000000000000002123614656633576016307 00000000000000#ifndef DCRYPT_PRIVATE_H #define DCRYPT_PRIVATE_H #define DCRYPT_DOVECOT_KEY_ENCRYPT_HASH "sha256" #define DCRYPT_DOVECOT_KEY_ENCRYPT_ROUNDS 2048 #define DCRYPT_DOVECOT_KEY_ENCRYPT_NONE 0 #define DCRYPT_DOVECOT_KEY_ENCRYPT_PK 1 #define DCRYPT_DOVECOT_KEY_ENCRYPT_PASSWORD 2 struct dcrypt_vfs { bool (*initialize)(const struct dcrypt_settings *set, const char **error_r); bool (*ctx_sym_create)(const char *algorithm, enum dcrypt_sym_mode mode, struct dcrypt_context_symmetric **ctx_r, const char **error_r); void (*ctx_sym_destroy)(struct dcrypt_context_symmetric **ctx); void (*ctx_sym_set_key)(struct dcrypt_context_symmetric *ctx, const unsigned char *key, size_t key_len); void (*ctx_sym_set_iv)(struct dcrypt_context_symmetric *ctx, const unsigned char *iv, size_t iv_len); void (*ctx_sym_set_key_iv_random)(struct dcrypt_context_symmetric *ctx); void (*ctx_sym_set_padding)(struct dcrypt_context_symmetric *ctx, bool padding); bool (*ctx_sym_get_key)(struct dcrypt_context_symmetric *ctx, buffer_t *key); bool (*ctx_sym_get_iv)(struct dcrypt_context_symmetric *ctx, buffer_t *iv); void (*ctx_sym_set_aad)(struct dcrypt_context_symmetric *ctx, const unsigned char *aad, size_t aad_len); bool (*ctx_sym_get_aad)(struct dcrypt_context_symmetric *ctx, buffer_t *aad); void (*ctx_sym_set_tag)(struct dcrypt_context_symmetric *ctx, const unsigned char *tag, size_t tag_len); bool (*ctx_sym_get_tag)(struct dcrypt_context_symmetric *ctx, buffer_t *tag); unsigned int (*ctx_sym_get_key_length)( struct dcrypt_context_symmetric *ctx); unsigned int (*ctx_sym_get_iv_length)( struct dcrypt_context_symmetric *ctx); unsigned int (*ctx_sym_get_block_size)( struct dcrypt_context_symmetric *ctx); bool (*ctx_sym_init)(struct dcrypt_context_symmetric *ctx, const char **error_r); bool (*ctx_sym_update)(struct dcrypt_context_symmetric *ctx, const unsigned char *data, size_t data_len, buffer_t *result, const char **error_r); bool (*ctx_sym_final)(struct dcrypt_context_symmetric *ctx, buffer_t *result, const char **error_r); bool (*ctx_hmac_create)(const char *algorithm, struct dcrypt_context_hmac **ctx_r, const char **error_r); void (*ctx_hmac_destroy)(struct dcrypt_context_hmac **ctx); void (*ctx_hmac_set_key)(struct dcrypt_context_hmac *ctx, const unsigned char *key, size_t key_len); bool (*ctx_hmac_get_key)(struct dcrypt_context_hmac *ctx, buffer_t *key); unsigned int (*ctx_hmac_get_digest_length)( struct dcrypt_context_hmac *ctx); void (*ctx_hmac_set_key_random)(struct dcrypt_context_hmac *ctx); bool (*ctx_hmac_init)(struct dcrypt_context_hmac *ctx, const char **error_r); bool (*ctx_hmac_update)(struct dcrypt_context_hmac *ctx, const unsigned char *data, size_t data_len, const char **error_r); bool (*ctx_hmac_final)(struct dcrypt_context_hmac *ctx, buffer_t *result, const char **error_r); bool (*ecdh_derive_secret_local)(struct dcrypt_private_key *local_key, buffer_t *R, buffer_t *S, const char **error_r); bool (*ecdh_derive_secret_peer)(struct dcrypt_public_key *peer_key, buffer_t *R, buffer_t *S, const char **error_r); bool (*pbkdf2)(const unsigned char *password, size_t password_len, const unsigned char *salt, size_t salt_len, const char *hash, unsigned int rounds, buffer_t *result, unsigned int result_len, const char **error_r); bool (*generate_keypair)(struct dcrypt_keypair *pair_r, enum dcrypt_key_type kind, unsigned int bits, const char *curve, const char **error_r); bool (*load_private_key)(struct dcrypt_private_key **key_r, const char *data, const char *password, struct dcrypt_private_key *dec_key, const char **error_r); bool (*load_public_key)(struct dcrypt_public_key **key_r, const char *data, const char **error_r); bool (*store_private_key)(struct dcrypt_private_key *key, enum dcrypt_key_format format, const char *cipher, buffer_t *destination, const char *password, struct dcrypt_public_key *enc_key, const char **error_r); bool (*store_public_key)(struct dcrypt_public_key *key, enum dcrypt_key_format format, buffer_t *destination, const char **error_r); void (*private_to_public_key)(struct dcrypt_private_key *priv_key, struct dcrypt_public_key **pub_key_r); bool (*key_string_get_info)( const char *key_data, enum dcrypt_key_format *format_r, enum dcrypt_key_version *version_r, enum dcrypt_key_kind *kind_r, enum dcrypt_key_encryption_type *encryption_type_r, const char **encryption_key_hash_r, const char **key_hash_r, const char **error_r); void (*unref_keypair)(struct dcrypt_keypair *keypair); void (*unref_public_key)(struct dcrypt_public_key **key); void (*unref_private_key)(struct dcrypt_private_key **key); void (*ref_public_key)(struct dcrypt_public_key *key); void (*ref_private_key)(struct dcrypt_private_key *key); bool (*rsa_encrypt)(struct dcrypt_public_key *key, const unsigned char *data, size_t data_len, buffer_t *result, enum dcrypt_padding padding, const char **error_r); bool (*rsa_decrypt)(struct dcrypt_private_key *key, const unsigned char *data, size_t data_len, buffer_t *result, enum dcrypt_padding padding, const char **error_r); const char *(*oid2name)(const unsigned char *oid, size_t oid_len, const char **error_r); bool (*name2oid)(const char *name, buffer_t *oid, const char **error_r); enum dcrypt_key_type (*private_key_type)(struct dcrypt_private_key *key); enum dcrypt_key_type (*public_key_type)(struct dcrypt_public_key *key); bool (*public_key_id)(struct dcrypt_public_key *key, const char *algorithm, buffer_t *result, const char **error_r); bool (*public_key_id_old)(struct dcrypt_public_key *key, buffer_t *result, const char **error_r); bool (*private_key_id)(struct dcrypt_private_key *key, const char *algorithm, buffer_t *result, const char **error_r); bool (*private_key_id_old)(struct dcrypt_private_key *key, buffer_t *result, const char **error_r); bool (*key_store_private_raw)(struct dcrypt_private_key *key, pool_t pool, enum dcrypt_key_type *key_type_r, ARRAY_TYPE(dcrypt_raw_key) *keys_r, const char **error_r); bool (*key_store_public_raw)(struct dcrypt_public_key *key, pool_t pool, enum dcrypt_key_type *key_type_r, ARRAY_TYPE(dcrypt_raw_key) *keys_r, const char **error_r); bool (*key_load_private_raw)(struct dcrypt_private_key **key_r, enum dcrypt_key_type key_type, const ARRAY_TYPE(dcrypt_raw_key) *keys, const char **error_r); bool (*key_load_public_raw)(struct dcrypt_public_key **key_r, enum dcrypt_key_type key_type, const ARRAY_TYPE(dcrypt_raw_key) *keys, const char **error_r); bool (*key_get_curve_public)(struct dcrypt_public_key *key, const char **curve_r, const char **error_r); const char *(*key_get_id_public)(struct dcrypt_public_key *key); const char *(*key_get_id_private)(struct dcrypt_private_key *key); void (*key_set_id_public)(struct dcrypt_public_key *key, const char *id); void (*key_set_id_private)(struct dcrypt_private_key *key, const char *id); enum dcrypt_key_usage (*key_get_usage_public)(struct dcrypt_public_key *key); enum dcrypt_key_usage (*key_get_usage_private)(struct dcrypt_private_key *key); void (*key_set_usage_public)(struct dcrypt_public_key *key, enum dcrypt_key_usage usage); void (*key_set_usage_private)(struct dcrypt_private_key *key, enum dcrypt_key_usage usage); bool (*sign)(struct dcrypt_private_key *key, const char *algorithm, enum dcrypt_signature_format format, const void *data, size_t data_len, buffer_t *signature_r, enum dcrypt_padding padding, const char **error_r); bool (*verify)(struct dcrypt_public_key *key, const char *algorithm, enum dcrypt_signature_format format, const void *data, size_t data_len, const unsigned char *signature, size_t signature_len, bool *valid_r, enum dcrypt_padding padding, const char **error_r); bool (*ecdh_derive_secret)(struct dcrypt_private_key *priv_key, struct dcrypt_public_key *pub_key, buffer_t *shared_secret, const char **error_r); }; void dcrypt_set_vfs(struct dcrypt_vfs *vfs); void dcrypt_openssl_init(struct module *module ATTR_UNUSED); void dcrypt_gnutls_init(struct module *module ATTR_UNUSED); void dcrypt_openssl_deinit(void); void dcrypt_gnutls_deinit(void); int parse_jwk_key(const char *key_data, struct json_tree **tree_r, const char **error_r); #endif dovecot-2.3.21.1/src/lib-dcrypt/ostream-encrypt.c0000644000000000000000000005331114656633576016460 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "randgen.h" #include "dcrypt-iostream.h" #include "ostream-encrypt.h" #include "ostream-private.h" #include "hash-method.h" #include "sha2.h" #include "safe-memset.h" #include "dcrypt.h" #include /* file struct dcrypt_public_key syntax * magic (14 bytes) * version (1 bytes) * flags (4 bytes) * size of header (4 bytes) * sha1 of key id (20 bytes) * cipher oid * mac oid * rounds (4 bytes) * key data size (4 bytes) * key data * cipher data * mac data (mac specific bytes) */ #define IO_STREAM_ENCRYPT_SEED_SIZE 32 #define IO_STREAM_ENCRYPT_ROUNDS 2048 struct encrypt_ostream { struct ostream_private ostream; struct dcrypt_context_symmetric *ctx_sym; struct dcrypt_context_hmac *ctx_mac; enum io_stream_encrypt_flags flags; struct dcrypt_public_key *pub; unsigned char *key_data; size_t key_data_len; buffer_t *cipher_oid; buffer_t *mac_oid; size_t block_size; bool finalized; bool failed; bool prefix_written; }; static int o_stream_encrypt_send(struct encrypt_ostream *stream, const unsigned char *data, size_t size) { ssize_t ec; ec = o_stream_send(stream->ostream.parent, data, size); if (ec == (ssize_t)size) return 0; else if (ec < 0) { o_stream_copy_error_from_parent(&stream->ostream); return -1; } else { io_stream_set_error(&stream->ostream.iostream, "ostream-encrypt: " "Unexpectedly short write to parent stream"); stream->ostream.ostream.stream_errno = EINVAL; return -1; } } static int o_stream_encrypt_send_header_v1(struct encrypt_ostream *stream) { unsigned char c; unsigned short s; i_assert(!stream->prefix_written); stream->prefix_written = TRUE; buffer_t *values = t_buffer_create(256); buffer_append(values, IOSTREAM_CRYPT_MAGIC, sizeof(IOSTREAM_CRYPT_MAGIC)); /* version */ c = 1; buffer_append(values, &c, 1); /* key data length */ s = htons(stream->key_data_len); buffer_append(values, &s, 2); /* then write key data */ buffer_append(values, stream->key_data, stream->key_data_len); i_free_and_null(stream->key_data); /* then send it to stream */ return o_stream_encrypt_send(stream, values->data, values->used); } static int o_stream_encrypt_send_header_v2(struct encrypt_ostream *stream) { unsigned char c; unsigned int i; i_assert(!stream->prefix_written); stream->prefix_written = TRUE; buffer_t *values = t_buffer_create(256); buffer_append(values, IOSTREAM_CRYPT_MAGIC, sizeof(IOSTREAM_CRYPT_MAGIC)); c = 2; buffer_append(values, &c, 1); i = cpu32_to_be(stream->flags); buffer_append(values, &i, 4); /* store total length of header 9 = version + flags + length 8 = rounds + key data length */ i = cpu32_to_be(sizeof(IOSTREAM_CRYPT_MAGIC) + 9 + stream->cipher_oid->used + stream->mac_oid->used + 8 + stream->key_data_len); buffer_append(values, &i, 4); buffer_append_buf(values, stream->cipher_oid, 0, SIZE_MAX); buffer_append_buf(values, stream->mac_oid, 0, SIZE_MAX); i = cpu32_to_be(IO_STREAM_ENCRYPT_ROUNDS); buffer_append(values, &i, 4); i = cpu32_to_be(stream->key_data_len); buffer_append(values, &i, 4); buffer_append(values, stream->key_data, stream->key_data_len); i_free_and_null(stream->key_data); return o_stream_encrypt_send(stream, values->data, values->used); } static int o_stream_encrypt_keydata_create_v1(struct encrypt_ostream *stream) { buffer_t *encrypted_key, *ephemeral_key, *secret, *res, buf; const char *error = NULL; const struct hash_method *hash = &hash_method_sha256; /* various temporary buffers */ unsigned char seed[IO_STREAM_ENCRYPT_SEED_SIZE]; unsigned char pkhash[hash->digest_size]; unsigned char ekhash[hash->digest_size]; unsigned char hres[hash->digest_size]; unsigned char hctx[hash->context_size]; /* hash the public key first */ buffer_create_from_data(&buf, pkhash, sizeof(pkhash)); if (!dcrypt_key_id_public_old(stream->pub, &buf, &error)) { io_stream_set_error(&stream->ostream.iostream, "Key hash failed: %s", error); return -1; } /* hash the key base */ hash->init(hctx); hash->loop(hctx, seed, sizeof(seed)); hash->result(hctx, ekhash); ephemeral_key = t_buffer_create(256); encrypted_key = t_buffer_create(256); secret = t_buffer_create(256); if (!dcrypt_ecdh_derive_secret_peer(stream->pub, ephemeral_key, secret, &error)) { io_stream_set_error(&stream->ostream.iostream, "Cannot perform ECDH: %s", error); return -1; } /* hash the secret data */ hash->init(hctx); hash->loop(hctx, secret->data, secret->used); hash->result(hctx, hres); safe_memset(buffer_get_modifiable_data(secret, 0), 0, secret->used); /* use it to encrypt the actual encryption key */ struct dcrypt_context_symmetric *dctx; if (!dcrypt_ctx_sym_create("aes-256-ctr", DCRYPT_MODE_ENCRYPT, &dctx, &error)) { io_stream_set_error(&stream->ostream.iostream, "Key encryption error: %s", error); return -1; } random_fill(seed, sizeof(seed)); hash->init(hctx); hash->loop(hctx, seed, sizeof(seed)); hash->result(hctx, ekhash); int ec = 0; /* NB! The old code was broken and used this kind of IV - it is not correct, but we need to stay compatible with old data */ dcrypt_ctx_sym_set_iv(dctx, (const unsigned char*) "\x00\x00\x00\x00\x00\x00\x00\x00" "\x00\x00\x00\x00\x00\x00\x00\x00", 16); dcrypt_ctx_sym_set_key(dctx, hres, sizeof(hres)); if (!dcrypt_ctx_sym_init(dctx, &error) || !dcrypt_ctx_sym_update(dctx, seed, sizeof(seed), encrypted_key, &error) || !dcrypt_ctx_sym_final(dctx, encrypted_key, &error)) { ec = -1; } dcrypt_ctx_sym_destroy(&dctx); if (ec != 0) { safe_memset(seed, 0, sizeof(seed)); io_stream_set_error(&stream->ostream.iostream, "Key encryption error: %s", error); return -1; } /* same as above */ dcrypt_ctx_sym_set_iv(stream->ctx_sym, (const unsigned char*) "\x00\x00\x00\x00\x00\x00\x00\x00" "\x00\x00\x00\x00\x00\x00\x00\x00", 16); dcrypt_ctx_sym_set_key(stream->ctx_sym, seed, sizeof(seed)); safe_memset(seed, 0, sizeof(seed)); if (!dcrypt_ctx_sym_init(stream->ctx_sym, &error)) { io_stream_set_error(&stream->ostream.iostream, "Encryption init error: %s", error); return -1; } res = buffer_create_dynamic(default_pool, 256); /* ephemeral key */ unsigned short s; s = htons(ephemeral_key->used); buffer_append(res, &s, 2); buffer_append(res, ephemeral_key->data, ephemeral_key->used); /* public key hash */ s = htons(sizeof(pkhash)); buffer_append(res, &s, 2); buffer_append(res, pkhash, sizeof(pkhash)); /* encrypted key hash */ s = htons(sizeof(ekhash)); buffer_append(res, &s, 2); buffer_append(res, ekhash, sizeof(ekhash)); /* encrypted key */ s = htons(encrypted_key->used); buffer_append(res, &s, 2); buffer_append(res, encrypted_key->data, encrypted_key->used); stream->key_data_len = res->used; stream->key_data = buffer_free_without_data(&res); return 0; } static int o_stream_encrypt_key_for_pubkey_v2(struct encrypt_ostream *stream, const char *malg, const unsigned char *key, size_t key_len, struct dcrypt_public_key *pubkey, buffer_t *res) { enum dcrypt_key_type ktype; const char *error; buffer_t *encrypted_key, *ephemeral_key, *temp_key; ephemeral_key = t_buffer_create(256); encrypted_key = t_buffer_create(256); temp_key = t_buffer_create(48); ktype = dcrypt_key_type_public(pubkey); if (ktype == DCRYPT_KEY_RSA) { /* encrypt key as R (as we don't need DH with RSA)*/ if (!dcrypt_rsa_encrypt(pubkey, key, key_len, encrypted_key, DCRYPT_PADDING_RSA_PKCS1_OAEP, &error)) { io_stream_set_error(&stream->ostream.iostream, "Cannot encrypt key data: %s", error); return -1; } } else if (ktype == DCRYPT_KEY_EC) { /* R = our ephemeral public key */ buffer_t *secret = t_buffer_create(256); /* derive ephemeral key and shared secret */ if (!dcrypt_ecdh_derive_secret_peer(pubkey, ephemeral_key, secret, &error)) { io_stream_set_error(&stream->ostream.iostream, "Cannot perform ECDH: %s", error); return -1; } /* use shared secret and ephemeral key to generate encryption key/iv */ if (!dcrypt_pbkdf2(secret->data, secret->used, ephemeral_key->data, ephemeral_key->used, malg, IO_STREAM_ENCRYPT_ROUNDS, temp_key, 48, &error)) { safe_memset(buffer_get_modifiable_data(secret, 0), 0, secret->used); io_stream_set_error(&stream->ostream.iostream, "Cannot perform key encryption: %s", error); } safe_memset(buffer_get_modifiable_data(secret, 0), 0, secret->used); /* encrypt key with shared secret */ struct dcrypt_context_symmetric *dctx; if (!dcrypt_ctx_sym_create("AES-256-CBC", DCRYPT_MODE_ENCRYPT, &dctx, &error)) { safe_memset(buffer_get_modifiable_data(temp_key, 0), 0, temp_key->used); io_stream_set_error(&stream->ostream.iostream, "Cannot perform key encryption: %s", error); return -1; } const unsigned char *ptr = temp_key->data; i_assert(temp_key->used == 48); dcrypt_ctx_sym_set_key(dctx, ptr, 32); dcrypt_ctx_sym_set_iv(dctx, ptr+32, 16); safe_memset(buffer_get_modifiable_data(temp_key, 0), 0, temp_key->used); int ec = 0; if (!dcrypt_ctx_sym_init(dctx, &error) || !dcrypt_ctx_sym_update(dctx, key, key_len, encrypted_key, &error) || !dcrypt_ctx_sym_final(dctx, encrypted_key, &error)) { io_stream_set_error(&stream->ostream.iostream, "Cannot perform key encryption: %s", error); ec = -1; } dcrypt_ctx_sym_destroy(&dctx); if (ec != 0) return ec; } else { io_stream_set_error(&stream->ostream.iostream, "Unsupported key type"); return -1; } /* store key type */ char kt = ktype; buffer_append(res, &kt, 1); /* store hash of public key as ID */ dcrypt_key_id_public(stream->pub, "sha256", res, NULL); /* store ephemeral key (if present) */ unsigned int val = cpu32_to_be(ephemeral_key->used); buffer_append(res, &val, 4); buffer_append_buf(res, ephemeral_key, 0, SIZE_MAX); /* store encrypted key */ val = cpu32_to_be(encrypted_key->used); buffer_append(res, &val, 4); buffer_append_buf(res, encrypted_key, 0, SIZE_MAX); return 0; } static int o_stream_encrypt_keydata_create_v2(struct encrypt_ostream *stream, const char *malg) { const struct hash_method *hash = hash_method_lookup(malg); const char *error; size_t tagsize; const unsigned char *ptr; size_t kl; unsigned int val; buffer_t *keydata, *res; if (hash == NULL) { io_stream_set_error(&stream->ostream.iostream, "Encryption init error: " "Hash algorithm '%s' not supported", malg); return -1; } /* key data length for internal use */ if ((stream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC) { tagsize = IOSTREAM_TAG_SIZE; } else if ((stream->flags & IO_STREAM_ENC_INTEGRITY_AEAD) == IO_STREAM_ENC_INTEGRITY_AEAD) { tagsize = IOSTREAM_TAG_SIZE; } else { /* do not include MAC */ tagsize = 0; } /* generate keydata length of random data for key/iv/mac */ kl = dcrypt_ctx_sym_get_key_length(stream->ctx_sym) + dcrypt_ctx_sym_get_iv_length(stream->ctx_sym) + tagsize; keydata = t_buffer_create(kl); random_fill(buffer_append_space_unsafe(keydata, kl), kl); buffer_set_used_size(keydata, kl); ptr = keydata->data; res = buffer_create_dynamic(default_pool, 256); /* store number of public key(s) */ buffer_append(res, "\1", 1); /* one key for now */ /* we can do multiple keys at this point, but do it only once now */ if (o_stream_encrypt_key_for_pubkey_v2(stream, malg, ptr, kl, stream->pub, res) != 0) { buffer_free(&res); return -1; } /* create hash of the key data */ unsigned char hctx[hash->context_size]; unsigned char hres[hash->digest_size]; hash->init(hctx); hash->loop(hctx, ptr, kl); hash->result(hctx, hres); for(int i = 1; i < 2049; i++) { uint32_t i_msb = cpu32_to_be(i); hash->init(hctx); hash->loop(hctx, hres, sizeof(hres)); hash->loop(hctx, &i_msb, sizeof(i_msb)); hash->result(hctx, hres); } /* store key data hash */ val = cpu32_to_be(sizeof(hres)); buffer_append(res, &val, 4); buffer_append(res, hres, sizeof(hres)); /* pick up key data that goes into stream */ stream->key_data_len = res->used; stream->key_data = buffer_free_without_data(&res); /* prime contexts */ dcrypt_ctx_sym_set_key(stream->ctx_sym, ptr, dcrypt_ctx_sym_get_key_length(stream->ctx_sym)); ptr += dcrypt_ctx_sym_get_key_length(stream->ctx_sym); dcrypt_ctx_sym_set_iv(stream->ctx_sym, ptr, dcrypt_ctx_sym_get_iv_length(stream->ctx_sym)); ptr += dcrypt_ctx_sym_get_iv_length(stream->ctx_sym); if ((stream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC) { dcrypt_ctx_hmac_set_key(stream->ctx_mac, ptr, tagsize); dcrypt_ctx_hmac_init(stream->ctx_mac, &error); } else if ((stream->flags & IO_STREAM_ENC_INTEGRITY_AEAD) == IO_STREAM_ENC_INTEGRITY_AEAD) { dcrypt_ctx_sym_set_aad(stream->ctx_sym, ptr, tagsize); } /* clear out private key data */ safe_memset(buffer_get_modifiable_data(keydata, 0), 0, keydata->used); if (!dcrypt_ctx_sym_init(stream->ctx_sym, &error)) { io_stream_set_error(&stream->ostream.iostream, "Encryption init error: %s", error); return -1; } return 0; } static ssize_t o_stream_encrypt_sendv(struct ostream_private *stream, const struct const_iovec *iov, unsigned int iov_count) { struct encrypt_ostream *estream = (struct encrypt_ostream *)stream; const char *error; ssize_t ec,total = 0; /* not if finalized */ i_assert(!estream->finalized); /* write prefix */ if (!estream->prefix_written) { T_BEGIN { if ((estream->flags & IO_STREAM_ENC_VERSION_1) == IO_STREAM_ENC_VERSION_1) ec = o_stream_encrypt_send_header_v1(estream); else ec = o_stream_encrypt_send_header_v2(estream); } T_END; if (ec < 0) { return -1; } } /* buffer for encrypted data */ unsigned char ciphertext[IO_BLOCK_SIZE]; buffer_t buf; buffer_create_from_data(&buf, ciphertext, sizeof(ciphertext)); /* encrypt & send all blocks of data at max ciphertext buffer's length */ for(unsigned int i = 0; i < iov_count; i++) { size_t bl, off = 0, len = iov[i].iov_len; const unsigned char *ptr = iov[i].iov_base; while(len > 0) { buffer_set_used_size(&buf, 0); /* update can emite twice the size of input */ bl = I_MIN(sizeof(ciphertext)/2, len); if (!dcrypt_ctx_sym_update(estream->ctx_sym, ptr + off, bl, &buf, &error)) { io_stream_set_error(&stream->iostream, "Encryption failure: %s", error); return -1; } if ((estream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC) { /* update mac */ if (!dcrypt_ctx_hmac_update(estream->ctx_mac, buf.data, buf.used, &error)) { io_stream_set_error(&stream->iostream, "MAC failure: %s", error); return -1; } } /* hopefully upstream can accommodate */ if (o_stream_encrypt_send(estream, buf.data, buf.used) < 0) { return -1; } len -= bl; off += bl; total += bl; } } stream->ostream.offset += total; return total; } static int o_stream_encrypt_finalize(struct ostream_private *stream) { const char *error; struct encrypt_ostream *estream = (struct encrypt_ostream *)stream; if (estream->finalized) { /* we've already flushed the encrypted output. */ return 0; } estream->finalized = TRUE; /* if nothing was written, we are done */ if (!estream->prefix_written) return 0; /* acquire last block */ buffer_t *buf = t_buffer_create( dcrypt_ctx_sym_get_block_size(estream->ctx_sym)); if (!dcrypt_ctx_sym_final(estream->ctx_sym, buf, &error)) { io_stream_set_error(&estream->ostream.iostream, "Encryption failure: %s", error); return -1; } /* sometimes final does not emit anything */ if (buf->used > 0) { /* update mac */ if (((estream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC)) { if (!dcrypt_ctx_hmac_update(estream->ctx_mac, buf->data, buf->used, &error)) { io_stream_set_error(&estream->ostream.iostream, "MAC failure: %s", error); return -1; } } if (o_stream_encrypt_send(estream, buf->data, buf->used) < 0) { return -1; } } /* write last mac bytes */ buffer_set_used_size(buf, 0); if ((estream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC) { if (!dcrypt_ctx_hmac_final(estream->ctx_mac, buf, &error)) { io_stream_set_error(&estream->ostream.iostream, "MAC failure: %s", error); return -1; } } else if ((estream->flags & IO_STREAM_ENC_INTEGRITY_AEAD) == IO_STREAM_ENC_INTEGRITY_AEAD) { dcrypt_ctx_sym_get_tag(estream->ctx_sym, buf); i_assert(buf->used > 0); } if (buf->used > 0 && o_stream_encrypt_send(estream, buf->data, buf->used) < 0) { return -1; } return 0; } static int o_stream_encrypt_flush(struct ostream_private *stream) { struct encrypt_ostream *estream = (struct encrypt_ostream *)stream; if (stream->finished && estream->ctx_sym != NULL && !estream->finalized) { if (o_stream_encrypt_finalize(&estream->ostream) < 0) return -1; } return o_stream_flush_parent(stream); } static void o_stream_encrypt_close(struct iostream_private *stream, bool close_parent) { struct encrypt_ostream *estream = (struct encrypt_ostream *)stream; i_assert(estream->finalized || estream->ctx_sym == NULL || estream->ostream.ostream.stream_errno != 0); if (close_parent) o_stream_close(estream->ostream.parent); } static void o_stream_encrypt_destroy(struct iostream_private *stream) { struct encrypt_ostream *estream = (struct encrypt_ostream *)stream; /* release resources */ if (estream->ctx_sym != NULL) dcrypt_ctx_sym_destroy(&estream->ctx_sym); if (estream->ctx_mac != NULL) dcrypt_ctx_hmac_destroy(&estream->ctx_mac); if (estream->key_data != NULL) i_free(estream->key_data); if (estream->cipher_oid != NULL) buffer_free(&estream->cipher_oid); if (estream->mac_oid != NULL) buffer_free(&estream->mac_oid); if (estream->pub != NULL) dcrypt_key_unref_public(&estream->pub); o_stream_unref(&estream->ostream.parent); } static int o_stream_encrypt_init(struct encrypt_ostream *estream, const char *algorithm) { const char *error; char *calg, *malg; if ((estream->flags & IO_STREAM_ENC_VERSION_1) == IO_STREAM_ENC_VERSION_1) { if (!dcrypt_ctx_sym_create("AES-256-CTR", DCRYPT_MODE_ENCRYPT, &estream->ctx_sym, &error)) { io_stream_set_error(&estream->ostream.iostream, "Cannot create ostream-encrypt: %s", error); return -1; } /* disable MAC */ estream->flags |= IO_STREAM_ENC_INTEGRITY_NONE; /* then do keying */ return o_stream_encrypt_keydata_create_v1(estream); } else { calg = t_strdup_noconst(algorithm); malg = strrchr(calg, '-'); if (malg == NULL) { io_stream_set_error(&estream->ostream.iostream, "Invalid algorithm " "(must be cipher-mac)"); return -1; } (*malg++) = '\0'; if (!dcrypt_ctx_sym_create(calg, DCRYPT_MODE_ENCRYPT, &estream->ctx_sym, &error)) { io_stream_set_error(&estream->ostream.iostream, "Cannot create ostream-encrypt: %s", error); return -1; } /* create cipher and mac context, take note of OIDs */ estream->cipher_oid = buffer_create_dynamic(default_pool, 12); estream->block_size = dcrypt_ctx_sym_get_block_size(estream->ctx_sym); if (!dcrypt_name2oid(calg, estream->cipher_oid, &error)) { io_stream_set_error(&estream->ostream.iostream, "Cannot create ostream-encrypt: %s", error); return -1; } /* mac context is optional */ if ((estream->flags & IO_STREAM_ENC_INTEGRITY_HMAC) == IO_STREAM_ENC_INTEGRITY_HMAC) { if (!dcrypt_ctx_hmac_create(malg, &estream->ctx_mac, &error)) { io_stream_set_error(&estream->ostream.iostream, "Cannot create ostream-encrypt: %s", error); return -1; } } estream->mac_oid = buffer_create_dynamic(default_pool, 12); if (!dcrypt_name2oid(malg, estream->mac_oid, &error)) { io_stream_set_error(&estream->ostream.iostream, "Cannot create ostream-encrypt: %s", error); return -1; } /* MAC algorithm is used for PBKDF2 and keydata hashing */ return o_stream_encrypt_keydata_create_v2(estream, malg); } } static struct encrypt_ostream * o_stream_create_encrypt_common(enum io_stream_encrypt_flags flags) { struct encrypt_ostream *estream; estream = i_new(struct encrypt_ostream, 1); estream->ostream.sendv = o_stream_encrypt_sendv; estream->ostream.flush = o_stream_encrypt_flush; estream->ostream.iostream.close = o_stream_encrypt_close; estream->ostream.iostream.destroy = o_stream_encrypt_destroy; estream->flags = flags; return estream; } struct ostream * o_stream_create_encrypt(struct ostream *output, const char *algorithm, struct dcrypt_public_key *box_pub, enum io_stream_encrypt_flags flags) { struct encrypt_ostream *estream = o_stream_create_encrypt_common(flags); int ec; dcrypt_key_ref_public(box_pub); estream->pub = box_pub; T_BEGIN { ec = o_stream_encrypt_init(estream, algorithm); } T_END; struct ostream *os = o_stream_create(&estream->ostream, output, o_stream_get_fd(output)); if (ec != 0) { os->stream_errno = EINVAL; } return os; } struct ostream * o_stream_create_sym_encrypt(struct ostream *output, struct dcrypt_context_symmetric *ctx) { struct encrypt_ostream *estream = o_stream_create_encrypt_common(IO_STREAM_ENC_INTEGRITY_NONE); const char *error; int ec; estream->prefix_written = TRUE; if (!dcrypt_ctx_sym_init(estream->ctx_sym, &error)) ec = -1; else ec = 0; estream->ctx_sym = ctx; struct ostream *os = o_stream_create(&estream->ostream, output, o_stream_get_fd(output)); if (ec != 0) { io_stream_set_error(&estream->ostream.iostream, "Could not initialize stream: %s", error); os->stream_errno = EINVAL; } return os; } dovecot-2.3.21.1/src/lib-dcrypt/Makefile.am0000644000000000000000000000414114656633576015211 00000000000000noinst_LTLIBRARIES = libdcrypt.la pkglib_LTLIBRARIES = NOPLUGIN_LDFLAGS= AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-ssl-iostream \ -DDCRYPT_MODULE_DIR=\"$(pkglibdir)\" libdcrypt_la_SOURCES = \ dcrypt.c \ istream-decrypt.c \ ostream-encrypt.c libdcrypt_la_CFLAGS = $(AM_CPPFLAGS) \ -DDCRYPT_MODULE_DIR=\"$(pkglibdir)\" if BUILD_DCRYPT_OPENSSL pkglib_LTLIBRARIES += libdcrypt_openssl.la libdcrypt_openssl_la_SOURCES = dcrypt-openssl.c libdcrypt_openssl_la_LDFLAGS = -module -avoid-version ../lib-ssl-iostream/libssl_iostream_openssl.la libdcrypt_openssl_la_LIBADD = $(SSL_LIBS) libdcrypt_openssl_la_DEPENDENCIES = ../lib-ssl-iostream/libssl_iostream_openssl.la libdcrypt_openssl_la_CFLAGS = $(AM_CPPFLAGS) \ $(SSL_CFLAGS) endif headers = \ dcrypt.h \ dcrypt-iostream.h \ dcrypt-private.h \ ostream-encrypt.h \ istream-decrypt.h pkginc_libdir=$(pkgincludedir) pkginc_lib_HEADERS = $(headers) EXTRA_DIST = \ sample-v1.asc \ sample-v1_short.asc \ sample-v2.asc test_programs = test-crypto test-stream noinst_PROGRAMS = $(test_programs) check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ done LIBDOVECOT_TEST_DEPS = \ ../lib-ssl-iostream/libssl_iostream.la \ ../lib-test/libtest.la \ ../lib/liblib.la LIBDOVECOT_TEST = \ $(LIBDOVECOT_TEST_DEPS) \ $(MODULE_LIBS) test_crypto_LDADD = $(LIBDOVECOT_TEST) test_crypto_DEPENDENCIES = $(LIBDOVECOT_TEST_DEPS) if HAVE_WHOLE_ARCHIVE test_crypto_LDFLAGS = -Wl,$(LD_WHOLE_ARCHIVE),../lib-ssl-iostream/.libs/libssl_iostream.a,$(LD_NO_WHOLE_ARCHIVE) endif test_crypto_CFLAGS = $(AM_CPPFLAGS) -DDCRYPT_SRC_DIR=\"$(top_srcdir)/src/lib-dcrypt\" test_crypto_SOURCES = $(libdcrypt_la_SOURCES) test-crypto.c test_stream_LDADD = $(LIBDOVECOT_TEST) test_stream_DEPENDENCIES = $(LIBDOVECOT_TEST_DEPS) if HAVE_WHOLE_ARCHIVE test_stream_LDFLAGS = -Wl,$(LD_WHOLE_ARCHIVE),../lib-ssl-iostream/.libs/libssl_iostream.a,$(LD_NO_WHOLE_ARCHIVE) endif test_stream_CFLAGS = $(AM_CPPFLAGS) -DDCRYPT_SRC_DIR=\"$(top_srcdir)/src/lib-dcrypt\" test_stream_SOURCES = $(libdcrypt_la_SOURCES) test-stream.c dovecot-2.3.21.1/src/lib-dcrypt/sample-v1_short.asc0000644000000000000000000000040014656633576016663 00000000000000Q1JZUFRFRAMHAQCtAEMDAA+5INzRNr5OAicv3XI4jh//ufjZN9yYr7mElHKNGY+D D2ziqHhPKVra6JzBzZvfySntDnDvdLAomafpDVlESMlkACB8mhA56i5P7XPoHdP/ w/oi6kooNSk5rd57+OqFiwD6TwAgu4C6eZWV+Spojlk8eOAw784ySgMHK8gDrXhA Jwg34GcAIPX4RmqXh+7QTAQWtGNHQvjqSygBxSUYkQ3KfPLMymKvAACMLy6/ dovecot-2.3.21.1/src/lib-dcrypt/sample-v1.asc0000644000000000000000000000602414656633576015454 00000000000000Q1JZUFRFRAMHAQCtAEMCAMyKuGO/j3TPoXzRJ39THa1oGDChmueqRVFlR3qnIWcd Mt15zp+juTJzwfxKDNsgdIfFleIbuuo1AX1TgimaVfb8ACB8mhA56i5P7XPoHdP/ w/oi6kooNSk5rd57+OqFiwD6TwAgWi/IHZ3tFmaohetUkFowgcrYwMh9HR9iOXg6 QdIDnqMAIIrC9OcLyuMzUp18LpVKZLg6QaJEsjrepBatgkqRDgBKAAD7tnI+Rjjg rNZ5UHYvjA1xsrEhfbyx8X6Vb+em++p+aE+I92pBrqV/XIeR1er1oNX3nxZwEnL4 UwhavZOMw7Qna0o4bop4PfK65HqnFTmgaiNDBMdE/CFaxSRlI0PLc0jhqxoEYU/d 0hrtPcpQMq0sCxBbHqcnDF1xAK2hAZU12BH+JoV4bI1k1MMn1xAcXxiVdtSO5NE8 E5fyaMfvTq2zIZtqQY09arrd0DaQ8o/L2dIV6jQVNZLxbRFWVayoWloT/YVSlHhi w5YHJetffXO02mllj3Mxr5aIfCmpZbrcWMfrF88ksI6HyQOrTHKS+Y95+VFLbxy4 +BWFGKV4zUGUwrfEDOpxIAZbBHsABjV82NS2TEZltu/ki3EhlwC8Hy+oyqn+LZN5 LCmbt/maMI0EJU6cNCUCQM8Rq2Xv7xP/DrC3A0y7gj3pT44dY0dMj76gBApKO4Qw rLcBgY9qycXHtUwvtg/QZJrb2n2AB7h0+B3LgVm8P12l1KAFS2ykugBWUVJSuUjK 5fjTk4EDIaCm0rhs2hNty9OtBkBuQBolkzxHtqp9/+QhIWJEtNtQEdnwN8DtdlNH /p69sZvkDhmyqSuByCodwVhkPZf5d/oGVUvE73btlAJcl8NZMXDqgKHfT4U5OF6Y eSXUIz9oiM2Wy1CVqrA2JdFGQ4Hcbf76IP462+gGefOd62atFfvjGGIb+Okyrmab jNxn/7wtUw42MXIzoAk+GQsOgo77rH075mILWqp0OuAyRTKmsZTP3FaDb4SxQk3K pw3N7HiNIiDW1wd5BLJE73qxKr+JC8GLs/s+JpfVb+lxzXAKXpEZTGFd/zphF1Kf J+aBCF+UB5Lq+QYoHfKSJzRb24PScAVs1VrV8nJlQvxMZW6Rd4ofNcsMjY+b1pBx bv74+oACEXC/F1jpp3tMTTLOLN8/hNq0J1mE3uOYRAaNK1jaQQAoa4rNxeY/2vBw mod8/Erc9M0M4B3VHVfRJ4F8MAx3b8if3oiicLu0OJ16snGHM7BGp2pOEPRVAg1Q +70zDctuSV6HwPYSAXaIw2LPrbsQmq1Lq/JivHYRwjUDBFAaXkPv2WFEfUsXEtmw UzUPLgWxZM8MsPDEI510SzRd3OlBUjuS96+/9n/Qv1D5DBnrDH9gUwUiPr9V+rMM kmNaAQpTKpCNpevCH/F18rQnUi/PJKY37q8pF/lO0OW7mzS+CxCvFr+aWxZ2kV84 GwUrUuSdHa1Z/0BY/UTZn1BAi6bOudiWHH5loJBldreSJ2K2/iwMpLUUEuQ+TJJ8 BvsWeOLMqEpDIxuuYERCUqHO8EmtvsSPKcIeS7ZZpvkRlRIWCuKgnaHSymT87Eq1 SJskTkAWd1iGvQI78tM8KwT2KDmf7Qs3RUCiSynT/H1OmVQqwVn/5c9wFjV+0PRA KFZZDEMvwy5tYpJ1Y0nYuUUMOlA8l11rpKqAIcRj0V256uXoj5cBnTsGZAOFDBqx pBLmGFgz1havj8RsgqtJfCkkh+Y8l9xszAC98/FGYOtmKpP4sXXM3LASkhi2FU4C OH/4tUsoh3tMCendx36s1UmliE94BiNuJMUAqSh9cLCnW/Uiw0bzqV8GOJLDk/A3 XMWrERuA2Jn7qQ9e9qmYarc8r6JjWUuxECZ04d12wNCcDF7hWxFYLbwTk/ZUIM5D 1ZsdOPUQDf7gjEW4gQoOK7pQWifJa56ZSSFurLoL5ae+3dPwUu8HNQLvwmx4paSe 01shQd36MXcRZ7BRVp0GqNrviuAtXacSxx1GIO9rh7RtyGGs1grsQ07P6Evpk0k/ 1WY48cE+xWU5SH4JwxMZ3vbDguMY/cnp2VhuzZguJ4iIFKg5RMVShrSkZQcWwH7e 7JVu7hOe3bWp1KeVG41IsOFpo0Jfpegtgf4r1hYih02Q54UNIFf5G3IRsbC1pjtj ALYteCLe9oa+7lIAVDWgmq/NqWLsi4dtlz97TG8XApIFZ6Prr7KG8N+RFTmouXYT QH6FuF5XvJ0TqIgIkdSzbuaNmN47E6PQoDAuRJ4X9ahYpF1xC4ecnAaI+rlaparF Z1OOtwYVQ1Hthc7wp+205aK6ujZyU6a9MrGXGZklRQdigCd+EOs0kWy8t+bcmk6K 9aXEJyGMBtcgQDGWZZJer5w8aUKj6SDp9y7X1kyAuhh8DFvNKMgR3vs4wnvsjr1W cYxH8RvAVXj76Xjvvgeg3jcJbcr0mPwB0SrcpQ+88mF64x+nrKsXmz9E8wBgOMY6 4qXXb27YUehAGN81jTZRN8lShl20fnPZKd5U1sqIhevXNUrTVjxVNff0fHRnOOb9 sVD3vdQmfbXvB3nyQLKQ+Wcw/WPqa0But5KEXFjnaXPORcEWEpYvWOgGPegmrTgh P4ZVscRdxozvd0sKvpLNMd/EiNLBaYft+4Yo38WZYkZzNJT7LhW41pbQyK1cmZiU COisSP2rrus6iKCNwaGPMZJNCCdQN862DSTaa0IZSEeSRpfBfA2UevSsfX0uTgyb B4Az1u4QJv9fQp8oAnnpH7YHhW43V/YTVuwLLF7pqSl2J2gNcLFfGuJVftRp6xJg mkfXOxoJ0y5CQU7pwQZ/F6WxuWBX1m7MNE/OfI9l9ODj0uAgKn1e+DzH1VXZi8ls lenQuepkt7zqXG5dRRhW+FSlJy1oo9oPcf2bZhAYMty3mh9F4Ils3SSCE6T6cU0A yYCNrq8iKyq5V1jdC9haN5NmF9yNiIxYWdbigcBTzR5+AZuNe7aJSXu0qvQJR9s0 d/l7J6LsH25I/zsIV/0OHcrvMUEf2WzU3Q== dovecot-2.3.21.1/src/lib-dcrypt/dcrypt-openssl.c0000644000000000000000000031354714656633576016324 00000000000000/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "base64.h" #include "str.h" #include "hex-binary.h" #include "safe-memset.h" #include "randgen.h" #include "array.h" #include "module-dir.h" #include "istream.h" #include "json-tree.h" #include "dovecot-openssl-common.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "dcrypt.h" #include "dcrypt-private.h" /** key format documentation: ========================= v1 key ------ algo id = openssl NID enctype = 0 = none, 1 = ecdhe, 2 = password key id = sha256(hex encoded public point) public key ---------- 1algo idpublic point private key ----------- - enctype none 1algo id0private pointkey id - enctype ecdh (algorithm AES-256-CTR, key = SHA256(shared secret), IV = \0\0\0...) 1algo id1private pointephemeral public keyencryption key idkey id - enctype password (algorithm AES-256-CTR, key = PBKDF2(SHA1, 16, password, salt), IV = \0\0\0...) 1algo id2private pointsaltkey id v2 key ------ algo oid = ASN1 OID of key algorithm (RSA or EC curve) enctype = 0 = none, 1 = ecdhe, 2 = password key id = SHA256(i2d_PUBKEY) public key ---------- 2HEX(i2d_PUBKEY)key id - enctype none 2key algo oid0(RSA = i2d_PrivateKey, EC=Private Point)key id - enctype ecdh, key,iv = PBKDF2(hash algo, rounds, shared secret, salt) 2key algo oid1symmetric algo namesalthash algoroundsE(RSA = i2d_PrivateKey, EC=Private Point)ephemeral public keyencryption key idkey id - enctype password, key,iv = PBKDF2(hash algo, rounds, password, salt) 2key algo oid1symmetric algo namesalthash algoroundsE(RSA = i2d_PrivateKey, EC=Private Point)key id **/ #ifndef HAVE_EVP_PKEY_get0 #define EVP_PKEY_get0_EC_KEY(x) x->pkey.ec #define EVP_PKEY_get0_RSA(x) x->pkey.rsa #endif #ifndef HAVE_OBJ_LENGTH #define OBJ_length(o) ((o)->length) #endif #ifndef HAVE_EVP_MD_CTX_NEW # define EVP_MD_CTX_new() EVP_MD_CTX_create() # define EVP_MD_CTX_free(ctx) EVP_MD_CTX_destroy(ctx) #endif #ifndef HAVE_HMAC_CTX_NEW # define HMAC_Init_ex(ctx, key, key_len, md, impl) \ HMAC_Init_ex(&(ctx), key, key_len, md, impl) # define HMAC_Update(ctx, data, len) HMAC_Update(&(ctx), data, len) # define HMAC_Final(ctx, md, len) HMAC_Final(&(ctx), md, len) # define HMAC_CTX_free(ctx) HMAC_cleanup(&(ctx)) #else # define HMAC_CTX_free(ctx) \ STMT_START { HMAC_CTX_free(ctx); (ctx) = NULL; } STMT_END #endif /* Not always present */ #ifndef HAVE_BN_SECURE_NEW # define BN_secure_new BN_new #endif /* openssl manual says this is OK */ #define OID_TEXT_MAX_LEN 80 #define t_base64url_decode_str(x) t_base64url_decode_str(BASE64_DECODE_FLAG_IGNORE_PADDING, (x)) struct dcrypt_context_symmetric { pool_t pool; const EVP_CIPHER *cipher; EVP_CIPHER_CTX *ctx; unsigned char *key; unsigned char *iv; unsigned char *aad; size_t aad_len; unsigned char *tag; size_t tag_len; int padding; int mode; }; struct dcrypt_context_hmac { pool_t pool; const EVP_MD *md; #ifdef HAVE_HMAC_CTX_NEW HMAC_CTX *ctx; #else HMAC_CTX ctx; #endif unsigned char *key; size_t klen; }; struct dcrypt_public_key { EVP_PKEY *key; unsigned int ref; enum dcrypt_key_usage usage; char *key_id; }; struct dcrypt_private_key { EVP_PKEY *key; unsigned int ref; enum dcrypt_key_usage usage; char *key_id; }; #define DCRYPT_SET_ERROR(error) STMT_START { if (error_r != NULL) *error_r = (error); } STMT_END static bool dcrypt_openssl_public_key_id(struct dcrypt_public_key *key, const char *algorithm, buffer_t *result, const char **error_r); static bool dcrypt_openssl_public_key_id_old(struct dcrypt_public_key *key, buffer_t *result, const char **error_r); static bool dcrypt_openssl_private_key_id(struct dcrypt_private_key *key, const char *algorithm, buffer_t *result, const char **error_r); static bool dcrypt_openssl_private_key_id_old(struct dcrypt_private_key *key, buffer_t *result, const char **error_r); static void dcrypt_openssl_private_to_public_key(struct dcrypt_private_key *priv_key, struct dcrypt_public_key **pub_key_r); static void dcrypt_openssl_unref_private_key(struct dcrypt_private_key **key); static void dcrypt_openssl_unref_public_key(struct dcrypt_public_key **key); static bool dcrypt_openssl_rsa_decrypt(struct dcrypt_private_key *key, const unsigned char *data, size_t data_len, buffer_t *result, enum dcrypt_padding padding, const char **error_r); static bool dcrypt_openssl_key_string_get_info(const char *key_data, enum dcrypt_key_format *format_r, enum dcrypt_key_version *version_r, enum dcrypt_key_kind *kind_r, enum dcrypt_key_encryption_type *encryption_type_r, const char **encryption_key_hash_r, const char **key_hash_r, const char **error_r); #ifndef HAVE_EC_GROUP_order_bits static int EC_GROUP_order_bits(const EC_GROUP *grp) { int bits; BIGNUM *bn = BN_new(); (void)EC_GROUP_get_order(grp, bn, NULL); bits = BN_num_bits(bn); BN_free(bn); return bits; } #endif static bool dcrypt_openssl_error(const char **error_r) { unsigned long ec; if (error_r == NULL) { /* caller is not really interested */ return FALSE; } ec = ERR_get_error(); DCRYPT_SET_ERROR(t_strdup_printf("%s", ERR_error_string(ec, NULL))); return FALSE; } static int dcrypt_openssl_padding_mode(enum dcrypt_padding padding, bool sig, const char **error_r) { switch (padding) { case DCRYPT_PADDING_DEFAULT: if (sig) return RSA_PKCS1_PSS_PADDING; else return RSA_PKCS1_OAEP_PADDING; case DCRYPT_PADDING_RSA_PKCS1_OAEP: return RSA_PKCS1_OAEP_PADDING; case DCRYPT_PADDING_RSA_PKCS1_PSS: return RSA_PKCS1_PSS_PADDING; case DCRYPT_PADDING_RSA_PKCS1: return RSA_PKCS1_PADDING; case DCRYPT_PADDING_RSA_NO: return RSA_NO_PADDING; default: DCRYPT_SET_ERROR("Unsupported padding mode"); return -1; } i_unreached(); } static bool dcrypt_openssl_initialize(const struct dcrypt_settings *set, const char **error_r) { if (set->crypto_device != NULL && set->crypto_device[0] != '\0') { if (dovecot_openssl_common_global_set_engine( set->crypto_device, error_r) <= 0) return FALSE; } return TRUE; } /* legacy function for old formats that generates hex encoded point from EC public key */ static char *ec_key_get_pub_point_hex(const EC_KEY *key) { const EC_POINT *p; const EC_GROUP *g; p = EC_KEY_get0_public_key(key); g = EC_KEY_get0_group(key); return EC_POINT_point2hex(g, p, POINT_CONVERSION_COMPRESSED, NULL); } static bool dcrypt_openssl_ctx_sym_create(const char *algorithm, enum dcrypt_sym_mode mode, struct dcrypt_context_symmetric **ctx_r, const char **error_r) { struct dcrypt_context_symmetric *ctx; pool_t pool; const EVP_CIPHER *cipher; cipher = EVP_get_cipherbyname(algorithm); if (cipher == NULL) { DCRYPT_SET_ERROR(t_strdup_printf("Invalid cipher %s", algorithm)); return FALSE; } /* allocate context */ pool = pool_alloconly_create("dcrypt openssl", 1024); ctx = p_new(pool, struct dcrypt_context_symmetric, 1); ctx->pool = pool; ctx->cipher = cipher; ctx->padding = 1; ctx->mode = (mode == DCRYPT_MODE_ENCRYPT ? 1 : 0); *ctx_r = ctx; return TRUE; } static void dcrypt_openssl_ctx_sym_destroy(struct dcrypt_context_symmetric **ctx) { pool_t pool = (*ctx)->pool; if ((*ctx)->ctx != NULL) EVP_CIPHER_CTX_free((*ctx)->ctx); pool_unref(&pool); *ctx = NULL; } static void dcrypt_openssl_ctx_sym_set_key(struct dcrypt_context_symmetric *ctx, const unsigned char *key, size_t key_len) { if (ctx->key != NULL) p_free(ctx->pool, ctx->key); ctx->key = p_malloc(ctx->pool, EVP_CIPHER_key_length(ctx->cipher)); memcpy(ctx->key, key, I_MIN(key_len, (size_t)EVP_CIPHER_key_length(ctx->cipher))); } static void dcrypt_openssl_ctx_sym_set_iv(struct dcrypt_context_symmetric *ctx, const unsigned char *iv, size_t iv_len) { if(ctx->iv != NULL) p_free(ctx->pool, ctx->iv); ctx->iv = p_malloc(ctx->pool, EVP_CIPHER_iv_length(ctx->cipher)); memcpy(ctx->iv, iv, I_MIN(iv_len, (size_t)EVP_CIPHER_iv_length(ctx->cipher))); } static void dcrypt_openssl_ctx_sym_set_key_iv_random(struct dcrypt_context_symmetric *ctx) { if(ctx->key != NULL) p_free(ctx->pool, ctx->key); if(ctx->iv != NULL) p_free(ctx->pool, ctx->iv); ctx->key = p_malloc(ctx->pool, EVP_CIPHER_key_length(ctx->cipher)); random_fill(ctx->key, EVP_CIPHER_key_length(ctx->cipher)); ctx->iv = p_malloc(ctx->pool, EVP_CIPHER_iv_length(ctx->cipher)); random_fill(ctx->iv, EVP_CIPHER_iv_length(ctx->cipher)); } static void dcrypt_openssl_ctx_sym_set_padding(struct dcrypt_context_symmetric *ctx, bool padding) { ctx->padding = (padding?1:0); } static bool dcrypt_openssl_ctx_sym_get_key(struct dcrypt_context_symmetric *ctx, buffer_t *key) { if(ctx->key == NULL) return FALSE; buffer_append(key, ctx->key, EVP_CIPHER_key_length(ctx->cipher)); return TRUE; } static bool dcrypt_openssl_ctx_sym_get_iv(struct dcrypt_context_symmetric *ctx, buffer_t *iv) { if(ctx->iv == NULL) return FALSE; buffer_append(iv, ctx->iv, EVP_CIPHER_iv_length(ctx->cipher)); return TRUE; } static void dcrypt_openssl_ctx_sym_set_aad(struct dcrypt_context_symmetric *ctx, const unsigned char *aad, size_t aad_len) { if (ctx->aad != NULL) p_free(ctx->pool, ctx->aad); /* allow empty aad */ ctx->aad = p_malloc(ctx->pool, I_MAX(1,aad_len)); memcpy(ctx->aad, aad, aad_len); ctx->aad_len = aad_len; } static bool dcrypt_openssl_ctx_sym_get_aad(struct dcrypt_context_symmetric *ctx, buffer_t *aad) { if (ctx->aad == NULL) return FALSE; buffer_append(aad, ctx->aad, ctx->aad_len); return TRUE; } static void dcrypt_openssl_ctx_sym_set_tag(struct dcrypt_context_symmetric *ctx, const unsigned char *tag, size_t tag_len) { if (ctx->tag != NULL) p_free(ctx->pool, ctx->tag); /* unlike aad, tag cannot be empty */ ctx->tag = p_malloc(ctx->pool, tag_len); memcpy(ctx->tag, tag, tag_len); ctx->tag_len = tag_len; } static bool dcrypt_openssl_ctx_sym_get_tag(struct dcrypt_context_symmetric *ctx, buffer_t *tag) { if (ctx->tag == NULL) return FALSE; buffer_append(tag, ctx->tag, ctx->tag_len); return TRUE; } static unsigned int dcrypt_openssl_ctx_sym_get_key_length(struct dcrypt_context_symmetric *ctx) { return EVP_CIPHER_key_length(ctx->cipher); } static unsigned int dcrypt_openssl_ctx_sym_get_iv_length(struct dcrypt_context_symmetric *ctx) { return EVP_CIPHER_iv_length(ctx->cipher); } static unsigned int dcrypt_openssl_ctx_sym_get_block_size(struct dcrypt_context_symmetric *ctx) { return EVP_CIPHER_block_size(ctx->cipher); } static bool dcrypt_openssl_ctx_sym_init(struct dcrypt_context_symmetric *ctx, const char **error_r) { int ec; int len; i_assert(ctx->key != NULL); i_assert(ctx->iv != NULL); i_assert(ctx->ctx == NULL); if((ctx->ctx = EVP_CIPHER_CTX_new()) == NULL) return dcrypt_openssl_error(error_r); ec = EVP_CipherInit_ex(ctx->ctx, ctx->cipher, NULL, ctx->key, ctx->iv, ctx->mode); if (ec != 1) return dcrypt_openssl_error(error_r); EVP_CIPHER_CTX_set_padding(ctx->ctx, ctx->padding); len = 0; if (ctx->aad != NULL) { ec = EVP_CipherUpdate(ctx->ctx, NULL, &len, ctx->aad, ctx->aad_len); } if (ec != 1) return dcrypt_openssl_error(error_r); return TRUE; } static bool dcrypt_openssl_ctx_sym_update(struct dcrypt_context_symmetric *ctx, const unsigned char *data, size_t data_len, buffer_t *result, const char **error_r) { const size_t block_size = (size_t)EVP_CIPHER_block_size(ctx->cipher); size_t buf_used = result->used; unsigned char *buf; int outl; i_assert(ctx->ctx != NULL); /* From `man 3 evp_cipherupdate`: EVP_EncryptUpdate() encrypts inl bytes from the buffer in and writes the encrypted version to out. This function can be called multiple times to encrypt successive blocks of data. The amount of data written depends on the block alignment of the encrypted data: as a result the amount of data written may be anything from zero bytes to (inl + cipher_block_size - 1) so out should contain sufficient room. The actual number of bytes written is placed in outl. */ buf = buffer_append_space_unsafe(result, data_len + block_size); outl = 0; if (EVP_CipherUpdate (ctx->ctx, buf, &outl, data, data_len) != 1) return dcrypt_openssl_error(error_r); buffer_set_used_size(result, buf_used + outl); return TRUE; } static bool dcrypt_openssl_ctx_sym_final(struct dcrypt_context_symmetric *ctx, buffer_t *result, const char **error_r) { const size_t block_size = (size_t)EVP_CIPHER_block_size(ctx->cipher); size_t buf_used = result->used; unsigned char *buf; int outl; int ec; i_assert(ctx->ctx != NULL); /* From `man 3 evp_cipherupdate`: If padding is enabled (the default) then EVP_EncryptFinal_ex() encrypts the "final" data, that is any data that remains in a partial block. It uses standard block padding (aka PKCS padding). The encrypted final data is written to out which should have sufficient space for one cipher block. The number of bytes written is placed in outl. After this function is called the encryption operation is finished and no further calls to EVP_EncryptUpdate() should be made. */ buf = buffer_append_space_unsafe(result, block_size); outl = 0; /* when **DECRYPTING** set expected tag */ if (ctx->mode == 0 && ctx->tag != NULL) { ec = EVP_CIPHER_CTX_ctrl(ctx->ctx, EVP_CTRL_GCM_SET_TAG, ctx->tag_len, ctx->tag); } else { ec = 1; } if (ec == 1) ec = EVP_CipherFinal_ex(ctx->ctx, buf, &outl); if (ec == 1) { buffer_set_used_size(result, buf_used + outl); /* when **ENCRYPTING** recover tag */ if (ctx->mode == 1 && ctx->aad != NULL) { /* tag should be NULL here */ i_assert(ctx->tag == NULL); /* openssl claims taglen is always 16, go figure .. */ ctx->tag = p_malloc(ctx->pool, EVP_GCM_TLS_TAG_LEN); ec = EVP_CIPHER_CTX_ctrl(ctx->ctx, EVP_CTRL_GCM_GET_TAG, EVP_GCM_TLS_TAG_LEN, ctx->tag); ctx->tag_len = EVP_GCM_TLS_TAG_LEN; } } if (ec == 0) DCRYPT_SET_ERROR("data authentication failed"); else if (ec < 0) dcrypt_openssl_error(error_r); EVP_CIPHER_CTX_free(ctx->ctx); ctx->ctx = NULL; return (ec == 1); } static bool dcrypt_openssl_ctx_hmac_create(const char *algorithm, struct dcrypt_context_hmac **ctx_r, const char **error_r) { struct dcrypt_context_hmac *ctx; pool_t pool; const EVP_MD *md; md = EVP_get_digestbyname(algorithm); if(md == NULL) { DCRYPT_SET_ERROR(t_strdup_printf("Invalid digest %s", algorithm)); return FALSE; } /* allocate context */ pool = pool_alloconly_create("dcrypt openssl", 1024); ctx = p_new(pool, struct dcrypt_context_hmac, 1); ctx->pool = pool; ctx->md = md; *ctx_r = ctx; return TRUE; } static void dcrypt_openssl_ctx_hmac_destroy(struct dcrypt_context_hmac **ctx) { pool_t pool = (*ctx)->pool; HMAC_CTX_free((*ctx)->ctx); pool_unref(&pool); *ctx = NULL; } static void dcrypt_openssl_ctx_hmac_set_key(struct dcrypt_context_hmac *ctx, const unsigned char *key, size_t key_len) { if (ctx->key != NULL) p_free(ctx->pool, ctx->key); ctx->klen = I_MIN(key_len, HMAC_MAX_MD_CBLOCK); ctx->key = p_malloc(ctx->pool, ctx->klen); memcpy(ctx->key, key, ctx->klen); } static bool dcrypt_openssl_ctx_hmac_get_key(struct dcrypt_context_hmac *ctx, buffer_t *key) { if (ctx->key == NULL) return FALSE; buffer_append(key, ctx->key, ctx->klen); return TRUE; } static void dcrypt_openssl_ctx_hmac_set_key_random(struct dcrypt_context_hmac *ctx) { ctx->klen = HMAC_MAX_MD_CBLOCK; ctx->key = p_malloc(ctx->pool, ctx->klen); random_fill(ctx->key, ctx->klen); } static unsigned int dcrypt_openssl_ctx_hmac_get_digest_length(struct dcrypt_context_hmac *ctx) { return EVP_MD_size(ctx->md); } static bool dcrypt_openssl_ctx_hmac_init(struct dcrypt_context_hmac *ctx, const char **error_r) { int ec; i_assert(ctx->md != NULL); #ifdef HAVE_HMAC_CTX_NEW ctx->ctx = HMAC_CTX_new(); if (ctx->ctx == NULL) return dcrypt_openssl_error(error_r); #endif ec = HMAC_Init_ex(ctx->ctx, ctx->key, ctx->klen, ctx->md, NULL); if (ec != 1) return dcrypt_openssl_error(error_r); return TRUE; } static bool dcrypt_openssl_ctx_hmac_update(struct dcrypt_context_hmac *ctx, const unsigned char *data, size_t data_len, const char **error_r) { int ec; ec = HMAC_Update(ctx->ctx, data, data_len); if (ec != 1) return dcrypt_openssl_error(error_r); return TRUE; } static bool dcrypt_openssl_ctx_hmac_final(struct dcrypt_context_hmac *ctx, buffer_t *result, const char **error_r) { int ec; unsigned char buf[HMAC_MAX_MD_CBLOCK]; unsigned int outl; ec = HMAC_Final(ctx->ctx, buf, &outl); HMAC_CTX_free(ctx->ctx); if (ec == 1) buffer_append(result, buf, outl); else return dcrypt_openssl_error(error_r); return TRUE; } static bool dcrypt_openssl_generate_ec_key(int nid, EVP_PKEY **key, const char **error_r) { EVP_PKEY_CTX *pctx; EVP_PKEY_CTX *ctx; EVP_PKEY *params = NULL; /* generate parameters for EC */ pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); if (pctx == NULL || EVP_PKEY_paramgen_init(pctx) < 1 || EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, nid) < 1 || EVP_PKEY_paramgen(pctx, ¶ms) < 1) { dcrypt_openssl_error(error_r); EVP_PKEY_CTX_free(pctx); return FALSE; } /* generate key from parameters */ ctx = EVP_PKEY_CTX_new(params, NULL); if (ctx == NULL || EVP_PKEY_keygen_init(ctx) < 1 || EVP_PKEY_keygen(ctx, key) < 1) { dcrypt_openssl_error(error_r); EVP_PKEY_free(params); EVP_PKEY_CTX_free(pctx); EVP_PKEY_CTX_free(ctx); return FALSE; } EVP_PKEY_free(params); EVP_PKEY_CTX_free(pctx); EVP_PKEY_CTX_free(ctx); EC_KEY_set_asn1_flag(EVP_PKEY_get0_EC_KEY((*key)), OPENSSL_EC_NAMED_CURVE); return TRUE; } static bool dcrypt_openssl_generate_rsa_key(int bits, EVP_PKEY **key, const char **error_r) { i_assert(bits >= 256); int ec = 0; EVP_PKEY_CTX *ctx; ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); if (ctx == NULL || EVP_PKEY_keygen_init(ctx) < 1 || EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) < 1 || EVP_PKEY_keygen(ctx, key) < 1) { dcrypt_openssl_error(error_r); ec = -1; } EVP_PKEY_CTX_free(ctx); return ec == 0; } static bool dcrypt_openssl_ecdh_derive_secret(struct dcrypt_private_key *priv_key, struct dcrypt_public_key *pub_key, buffer_t *shared_secret, const char **error_r) { /* initialize */ EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(priv_key->key, NULL); if (pctx == NULL || EVP_PKEY_derive_init(pctx) != 1 || EVP_PKEY_derive_set_peer(pctx, pub_key->key) != 1) { EVP_PKEY_CTX_free(pctx); return dcrypt_openssl_error(error_r); } /* derive */ size_t len; if (EVP_PKEY_derive(pctx, NULL, &len) != 1) { EVP_PKEY_CTX_free(pctx); return dcrypt_openssl_error(error_r); } unsigned char buf[len]; if (EVP_PKEY_derive(pctx, buf, &len) != 1) { EVP_PKEY_CTX_free(pctx); return dcrypt_openssl_error(error_r); } EVP_PKEY_CTX_free(pctx); buffer_append(shared_secret, buf, len); return TRUE; } static bool dcrypt_openssl_ecdh_derive_secret_local(struct dcrypt_private_key *local_key, buffer_t *R, buffer_t *S, const char **error_r) { bool ret; i_assert(local_key != NULL && local_key->key != NULL); EVP_PKEY *local = local_key->key; BN_CTX *bn_ctx = BN_CTX_new(); if (bn_ctx == NULL) return dcrypt_openssl_error(error_r); const EC_GROUP *grp = EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(local)); EC_POINT *pub = EC_POINT_new(grp); /* convert ephemeral key data EC point */ if (pub == NULL || EC_POINT_oct2point(grp, pub, R->data, R->used, bn_ctx) != 1) { EC_POINT_free(pub); BN_CTX_free(bn_ctx); return dcrypt_openssl_error(error_r); } EC_KEY *ec_key = EC_KEY_new(); /* convert point to public key */ int ec = 0; if (ec_key == NULL || EC_KEY_set_group(ec_key, grp) != 1 || EC_KEY_set_public_key(ec_key, pub) != 1) ec = -1; else EC_POINT_free(pub); BN_CTX_free(bn_ctx); /* make sure it looks like a valid key */ if (ec == -1 || EC_KEY_check_key(ec_key) != 1) { EC_KEY_free(ec_key); return dcrypt_openssl_error(error_r); } EVP_PKEY *peer = EVP_PKEY_new(); if (peer == NULL) { EC_KEY_free(ec_key); return dcrypt_openssl_error(error_r); } EVP_PKEY_set1_EC_KEY(peer, ec_key); EC_KEY_free(ec_key); struct dcrypt_public_key pub_key; i_zero(&pub_key); pub_key.key = peer; ret = dcrypt_openssl_ecdh_derive_secret(local_key, &pub_key, S, error_r); EVP_PKEY_free(peer); return ret; } static bool dcrypt_openssl_ecdh_derive_secret_peer(struct dcrypt_public_key *peer_key, buffer_t *R, buffer_t *S, const char **error_r) { i_assert(peer_key != NULL && peer_key->key != NULL); bool ret; /* ensure peer_key is EC key */ EVP_PKEY *local = NULL; EVP_PKEY *peer = peer_key->key; if (EVP_PKEY_base_id(peer) != EVP_PKEY_EC) { DCRYPT_SET_ERROR("Only ECC key can be used"); return FALSE; } /* generate another key from same group */ int nid = EC_GROUP_get_curve_name( EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(peer))); if (!dcrypt_openssl_generate_ec_key(nid, &local, error_r)) return FALSE; struct dcrypt_private_key priv_key; i_zero(&priv_key); priv_key.key = local; if (!(ret = dcrypt_openssl_ecdh_derive_secret(&priv_key, peer_key, S, error_r))) { EVP_PKEY_free(local); return FALSE; } /* get ephemeral key (=R) */ BN_CTX *bn_ctx = BN_CTX_new(); const EC_POINT *pub = EC_KEY_get0_public_key(EVP_PKEY_get0_EC_KEY(local)); const EC_GROUP *grp = EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(local)); size_t len = EC_POINT_point2oct(grp, pub, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, bn_ctx); unsigned char R_buf[len]; EC_POINT_point2oct(grp, pub, POINT_CONVERSION_UNCOMPRESSED, R_buf, len, bn_ctx); BN_CTX_free(bn_ctx); buffer_append(R, R_buf, len); EVP_PKEY_free(local); return ret; } static bool dcrypt_openssl_pbkdf2(const unsigned char *password, size_t password_len, const unsigned char *salt, size_t salt_len, const char *hash, unsigned int rounds, buffer_t *result, unsigned int result_len, const char **error_r) { int ret; i_assert(rounds > 0); i_assert(result_len > 0); i_assert(result != NULL); /* determine MD */ const EVP_MD* md = EVP_get_digestbyname(hash); if (md == NULL) { DCRYPT_SET_ERROR(t_strdup_printf("Invalid digest %s", hash)); return FALSE; } unsigned char buffer[result_len]; if ((ret = PKCS5_PBKDF2_HMAC((const char*)password, password_len, salt, salt_len, rounds, md, result_len, buffer)) == 1) { buffer_append(result, buffer, result_len); } if (ret != 1) return dcrypt_openssl_error(error_r); return TRUE; } static bool dcrypt_openssl_generate_keypair(struct dcrypt_keypair *pair_r, enum dcrypt_key_type kind, unsigned int bits, const char *curve, const char **error_r) { EVP_PKEY *pkey = NULL; i_assert(pair_r != NULL); i_zero(pair_r); if (kind == DCRYPT_KEY_RSA) { if (dcrypt_openssl_generate_rsa_key(bits, &pkey, error_r)) { pair_r->priv = i_new(struct dcrypt_private_key, 1); pair_r->priv->key = pkey; pair_r->priv->ref++; pair_r->pub = NULL; dcrypt_openssl_private_to_public_key(pair_r->priv, &pair_r->pub); return TRUE; } else { return dcrypt_openssl_error(error_r); } } else if (kind == DCRYPT_KEY_EC) { int nid = OBJ_sn2nid(curve); if (nid == NID_undef) { DCRYPT_SET_ERROR(t_strdup_printf("Unknown EC curve %s", curve)); return FALSE; } if (dcrypt_openssl_generate_ec_key(nid, &pkey, error_r)) { pair_r->priv = i_new(struct dcrypt_private_key, 1); pair_r->priv->key = pkey; pair_r->priv->ref++; pair_r->pub = NULL; dcrypt_openssl_private_to_public_key(pair_r->priv, &pair_r->pub); return TRUE; } else { return dcrypt_openssl_error(error_r); } } DCRYPT_SET_ERROR("Key type not supported in this build"); return FALSE; } static bool dcrypt_openssl_decrypt_point_v1(buffer_t *data, buffer_t *key, BIGNUM **point_r, const char **error_r) { struct dcrypt_context_symmetric *dctx; buffer_t *tmp = t_buffer_create(64); if (!dcrypt_openssl_ctx_sym_create("aes-256-ctr", DCRYPT_MODE_DECRYPT, &dctx, error_r)) { return FALSE; } /* v1 KEYS have all-zero IV - have to use it ourselves too */ dcrypt_openssl_ctx_sym_set_iv(dctx, (const unsigned char*) "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0", 16); dcrypt_openssl_ctx_sym_set_key(dctx, key->data, key->used); if (!dcrypt_openssl_ctx_sym_init(dctx, error_r) || !dcrypt_openssl_ctx_sym_update(dctx, data->data, data->used, tmp, error_r) || !dcrypt_openssl_ctx_sym_final(dctx, tmp, error_r)) { dcrypt_openssl_ctx_sym_destroy(&dctx); return FALSE; } dcrypt_openssl_ctx_sym_destroy(&dctx); *point_r = BN_bin2bn(tmp->data, tmp->used, NULL); safe_memset(buffer_get_modifiable_data(tmp, NULL), 0,tmp->used); buffer_set_used_size(key, 0); if (*point_r == NULL) return dcrypt_openssl_error(error_r); return TRUE; } static bool dcrypt_openssl_decrypt_point_ec_v1(struct dcrypt_private_key *dec_key, const char *data_hex, const char *peer_key_hex, BIGNUM **point_r, const char **error_r) { buffer_t *peer_key, *data, key, *secret; bool res; data = t_buffer_create(128); peer_key = t_buffer_create(64); hex_to_binary(data_hex, data); hex_to_binary(peer_key_hex, peer_key); secret = t_buffer_create(64); if (!dcrypt_openssl_ecdh_derive_secret_local(dec_key, peer_key, secret, error_r)) return FALSE; /* run it thru SHA256 once */ unsigned char digest[SHA256_DIGEST_LENGTH]; SHA256(secret->data, secret->used, digest); safe_memset(buffer_get_modifiable_data(secret, NULL), 0, secret->used); buffer_set_used_size(secret, 0); buffer_create_from_const_data(&key, digest, SHA256_DIGEST_LENGTH); /* then use this as key */ res = dcrypt_openssl_decrypt_point_v1(data, &key, point_r, error_r); memset(digest, 0, sizeof(digest)); safe_memset(digest, 0, SHA256_DIGEST_LENGTH); return res; } static bool dcrypt_openssl_decrypt_point_password_v1(const char *data_hex, const char *password_hex, const char *salt_hex, BIGNUM **point_r, const char **error_r) { buffer_t *salt, *data, *password, *key; data = t_buffer_create(128); salt = t_buffer_create(16); password = t_buffer_create(32); key = t_buffer_create(32); hex_to_binary(data_hex, data); hex_to_binary(salt_hex, salt); hex_to_binary(password_hex, password); /* aes-256-ctr uses 32 byte key, and v1 uses all-zero IV */ if (!dcrypt_openssl_pbkdf2(password->data, password->used, salt->data, salt->used, "sha256", 16, key, 32, error_r)) return FALSE; return dcrypt_openssl_decrypt_point_v1(data, key, point_r, error_r); } static bool dcrypt_openssl_load_private_key_dovecot_v1(struct dcrypt_private_key **key_r, int len, const char **input, const char *password, struct dcrypt_private_key *dec_key, const char **error_r) { int nid, ec, enctype; BIGNUM *point = NULL; if (str_to_int(input[1], &nid) != 0) { DCRYPT_SET_ERROR("Corrupted data"); return FALSE; } if (str_to_int(input[2], &enctype) != 0) { DCRYPT_SET_ERROR("Corrupted data"); return FALSE; } /* decode and optionally decipher private key value */ if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_NONE) { point = BN_secure_new(); if (point == NULL || BN_hex2bn(&point, input[3]) < 1) { BN_free(point); return dcrypt_openssl_error(error_r); } } else if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PASSWORD) { /* by password */ if (password == NULL) { DCRYPT_SET_ERROR("password missing"); return FALSE; } const char *enc_priv_pt = input[3]; const char *salt = input[4]; if (!dcrypt_openssl_decrypt_point_password_v1( enc_priv_pt, password, salt, &point, error_r)) { return FALSE; } } else if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PK) { /* by key */ if (dec_key == NULL) { DCRYPT_SET_ERROR("decrypt key missing"); return FALSE; } const char *enc_priv_pt = input[3]; const char *peer_key = input[4]; if (!dcrypt_openssl_decrypt_point_ec_v1( dec_key, enc_priv_pt, peer_key, &point, error_r)) { return FALSE; } } else { DCRYPT_SET_ERROR("Invalid key data"); return FALSE; } EC_KEY *eckey = EC_KEY_new_by_curve_name(nid); if (eckey == NULL) return dcrypt_openssl_error(error_r); /* assign private key */ BN_CTX *bnctx = BN_CTX_new(); if (bnctx == NULL) { EC_KEY_free(eckey); return dcrypt_openssl_error(error_r); } EC_KEY_set_private_key(eckey, point); EC_KEY_precompute_mult(eckey, bnctx); EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE); EC_POINT *pub = EC_POINT_new(EC_KEY_get0_group(eckey)); if (pub == NULL) { EC_KEY_free(eckey); BN_CTX_free(bnctx); return dcrypt_openssl_error(error_r); } /* calculate public key */ ec = EC_POINT_mul(EC_KEY_get0_group(eckey), pub, point, NULL, NULL, bnctx); EC_KEY_set_public_key(eckey, pub); BN_free(point); EC_POINT_free(pub); BN_CTX_free(bnctx); /* make sure it looks OK and is correct */ if (ec == 1 && EC_KEY_check_key(eckey) == 1) { unsigned char digest[SHA256_DIGEST_LENGTH]; /* validate that the key was loaded correctly */ char *id = ec_key_get_pub_point_hex(eckey); if (id == NULL) { EC_KEY_free(eckey); return dcrypt_openssl_error(error_r); } SHA256((unsigned char*)id, strlen(id), digest); OPENSSL_free(id); const char *digest_hex = binary_to_hex(digest, SHA256_DIGEST_LENGTH); if (strcmp(digest_hex, input[len-1]) != 0) { DCRYPT_SET_ERROR("Key id mismatch after load"); EC_KEY_free(eckey); return FALSE; } EVP_PKEY *key = EVP_PKEY_new(); if (key == NULL) { EC_KEY_free(eckey); return dcrypt_openssl_error(error_r); } EVP_PKEY_set1_EC_KEY(key, eckey); EC_KEY_free(eckey); *key_r = i_new(struct dcrypt_private_key, 1); (*key_r)->key = key; (*key_r)->ref++; return TRUE; } EC_KEY_free(eckey); return dcrypt_openssl_error(error_r); } /* encrypt/decrypt private keys */ static bool dcrypt_openssl_cipher_key_dovecot_v2(const char *cipher, enum dcrypt_sym_mode mode, buffer_t *input, buffer_t *secret, buffer_t *salt, const char *digalgo, unsigned int rounds, buffer_t *result_r, const char **error_r) { struct dcrypt_context_symmetric *dctx; bool res; if (!dcrypt_openssl_ctx_sym_create(cipher, mode, &dctx, error_r)) { return FALSE; } /* generate encryption key/iv based on secret/salt */ buffer_t *key_data = t_buffer_create(128); res = dcrypt_openssl_pbkdf2(secret->data, secret->used, salt->data, salt->used, digalgo, rounds, key_data, dcrypt_openssl_ctx_sym_get_key_length(dctx) + dcrypt_openssl_ctx_sym_get_iv_length(dctx), error_r); if (!res) { dcrypt_openssl_ctx_sym_destroy(&dctx); return FALSE; } buffer_t *tmp = t_buffer_create(128); const unsigned char *kd = buffer_free_without_data(&key_data); /* perform ciphering */ dcrypt_openssl_ctx_sym_set_key(dctx, kd, dcrypt_openssl_ctx_sym_get_key_length(dctx)); dcrypt_openssl_ctx_sym_set_iv(dctx, kd + dcrypt_openssl_ctx_sym_get_key_length(dctx), dcrypt_openssl_ctx_sym_get_iv_length(dctx)); if (!dcrypt_openssl_ctx_sym_init(dctx, error_r) || !dcrypt_openssl_ctx_sym_update(dctx, input->data, input->used, tmp, error_r) || !dcrypt_openssl_ctx_sym_final(dctx, tmp, error_r)) { res = FALSE; } else { /* provide result if succeeded */ buffer_append_buf(result_r, tmp, 0, SIZE_MAX); res = TRUE; } /* and ensure no data leaks */ safe_memset(buffer_get_modifiable_data(tmp, NULL), 0, tmp->used); dcrypt_openssl_ctx_sym_destroy(&dctx); return res; } static bool dcrypt_openssl_load_private_key_dovecot_v2(struct dcrypt_private_key **key_r, int len, const char **input, const char *password, struct dcrypt_private_key *dec_key, const char **error_r) { int enctype; buffer_t *key_data = t_buffer_create(256); /* check for encryption type */ if (str_to_int(input[2], &enctype) != 0) { DCRYPT_SET_ERROR("Corrupted data"); return FALSE; } if (enctype < 0 || enctype > 2) { DCRYPT_SET_ERROR("Corrupted data"); return FALSE; } /* match encryption type to field counts */ if ((enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_NONE && len != 5) || (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PASSWORD && len != 9) || (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PK && len != 11)) { DCRYPT_SET_ERROR("Corrupted data"); return FALSE; } /* get key type */ int nid = OBJ_txt2nid(input[1]); if (nid == NID_undef) return dcrypt_openssl_error(error_r); /* decode and possibly decipher private key value */ if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_NONE) { if (hex_to_binary(input[3], key_data) != 0) { DCRYPT_SET_ERROR("Corrupted data"); } } else if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PK) { if (dec_key == NULL) { DCRYPT_SET_ERROR("decrypt key missing"); return FALSE; } unsigned int rounds; struct dcrypt_public_key *pubkey = NULL; if (str_to_uint(input[6], &rounds) != 0) { DCRYPT_SET_ERROR("Corrupted data"); return FALSE; } buffer_t *data = t_buffer_create(128); /* check that we have correct decryption key */ dcrypt_openssl_private_to_public_key(dec_key, &pubkey); if (!dcrypt_openssl_public_key_id(pubkey, "sha256", data, error_r)) { dcrypt_openssl_unref_public_key(&pubkey); return FALSE; } dcrypt_openssl_unref_public_key(&pubkey); if (strcmp(binary_to_hex(data->data, data->used), input[9]) != 0) { DCRYPT_SET_ERROR("No private key available"); return FALSE; } buffer_t *salt, *peer_key, *secret; salt = t_buffer_create(strlen(input[4])/2); peer_key = t_buffer_create(strlen(input[8])/2); secret = t_buffer_create(128); buffer_set_used_size(data, 0); hex_to_binary(input[4], salt); hex_to_binary(input[8], peer_key); hex_to_binary(input[7], data); /* get us secret value to use for key/iv generation */ if (EVP_PKEY_base_id((EVP_PKEY*)dec_key) == EVP_PKEY_RSA) { if (!dcrypt_openssl_rsa_decrypt(dec_key, peer_key->data, peer_key->used, secret, DCRYPT_PADDING_RSA_PKCS1_OAEP, error_r)) return FALSE; } else { /* perform ECDH */ if (!dcrypt_openssl_ecdh_derive_secret_local( dec_key, peer_key, secret, error_r)) return FALSE; } /* decrypt key */ if (!dcrypt_openssl_cipher_key_dovecot_v2(input[3], DCRYPT_MODE_DECRYPT, data, secret, salt, input[5], rounds, key_data, error_r)) { return FALSE; } } else if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PASSWORD) { if (password == NULL) { DCRYPT_SET_ERROR("password missing"); return FALSE; } unsigned int rounds; if (str_to_uint(input[6], &rounds) != 0) { DCRYPT_SET_ERROR("Corrupted data"); return FALSE; } buffer_t *salt, secret, *data; salt = t_buffer_create(strlen(input[4])/2); buffer_create_from_const_data(&secret, password, strlen(password)); data = t_buffer_create(strlen(input[7])/2); if (hex_to_binary(input[4], salt) != 0 || hex_to_binary(input[7], data) != 0) { DCRYPT_SET_ERROR("Corrupted data"); return FALSE; } if (!dcrypt_openssl_cipher_key_dovecot_v2(input[3], DCRYPT_MODE_DECRYPT, data, &secret, salt, input[5], rounds, key_data, error_r)) { return FALSE; } } /* decode actual key */ if (EVP_PKEY_type(nid) == EVP_PKEY_RSA) { RSA *rsa = RSA_new(); const unsigned char *ptr = buffer_get_data(key_data, NULL); if (rsa == NULL || d2i_RSAPrivateKey(&rsa, &ptr, key_data->used) == NULL || RSA_check_key(rsa) != 1) { safe_memset(buffer_get_modifiable_data(key_data, NULL), 0, key_data->used); RSA_free(rsa); return dcrypt_openssl_error(error_r); } safe_memset(buffer_get_modifiable_data(key_data, NULL), 0, key_data->used); buffer_set_used_size(key_data, 0); EVP_PKEY *pkey = EVP_PKEY_new(); if (pkey == NULL) { RSA_free(rsa); return dcrypt_openssl_error(error_r); } EVP_PKEY_set1_RSA(pkey, rsa); RSA_free(rsa); *key_r = i_new(struct dcrypt_private_key, 1); (*key_r)->key = pkey; (*key_r)->ref++; } else { int ec; BIGNUM *point = BN_secure_new(); if (point == NULL || BN_mpi2bn(key_data->data, key_data->used, point) == NULL) { safe_memset(buffer_get_modifiable_data(key_data, NULL), 0, key_data->used); BN_free(point); return dcrypt_openssl_error(error_r); } EC_KEY *eckey = EC_KEY_new_by_curve_name(nid); safe_memset(buffer_get_modifiable_data(key_data, NULL), 0, key_data->used); buffer_set_used_size(key_data, 0); BN_CTX *bnctx = BN_CTX_new(); if (eckey == NULL || bnctx == NULL) { BN_free(point); EC_KEY_free(eckey); BN_CTX_free(bnctx); return dcrypt_openssl_error(error_r); } EC_KEY_set_private_key(eckey, point); EC_KEY_precompute_mult(eckey, bnctx); EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE); EC_POINT *pub = EC_POINT_new(EC_KEY_get0_group(eckey)); if (pub == NULL) ec = -1; else { /* calculate public key */ ec = EC_POINT_mul(EC_KEY_get0_group(eckey), pub, point, NULL, NULL, bnctx); EC_KEY_set_public_key(eckey, pub); EC_POINT_free(pub); } BN_free(point); BN_CTX_free(bnctx); /* make sure the EC key is valid */ EVP_PKEY *key = EVP_PKEY_new(); if (ec == 1 && key != NULL && EC_KEY_check_key(eckey) == 1) { EVP_PKEY_set1_EC_KEY(key, eckey); EC_KEY_free(eckey); *key_r = i_new(struct dcrypt_private_key, 1); (*key_r)->key = key; (*key_r)->ref++; } else { EVP_PKEY_free(key); EC_KEY_free(eckey); return dcrypt_openssl_error(error_r); } } /* finally compare key to key id */ dcrypt_openssl_private_key_id(*key_r, "sha256", key_data, NULL); if (strcmp(binary_to_hex(key_data->data, key_data->used), input[len-1]) != 0) { dcrypt_openssl_unref_private_key(key_r); DCRYPT_SET_ERROR("Key id mismatch after load"); return FALSE; } return TRUE; } /* JWK Parameter names defined at https://www.iana.org/assignments/jose/jose.xhtml */ static const struct jwk_to_ssl_map_entry { const char *jwk_curve; int nid; } jwk_to_ssl_curves[] = { /* See https://tools.ietf.org/search/rfc8422#appendix-A */ { .jwk_curve = "P-256", .nid = NID_X9_62_prime256v1 }, { .jwk_curve = "P-384", .nid = NID_secp384r1 }, { .jwk_curve = "P-521", .nid = NID_secp521r1 }, { .jwk_curve = NULL, .nid = 0 } }; static const char *key_usage_to_jwk_use(enum dcrypt_key_usage usage) { switch(usage) { case DCRYPT_KEY_USAGE_NONE: return NULL; case DCRYPT_KEY_USAGE_ENCRYPT: return "enc"; case DCRYPT_KEY_USAGE_SIGN: return "sig"; }; i_unreached(); } static enum dcrypt_key_usage jwk_use_to_key_usage(const char *use) { if (strcmp(use, "enc") == 0) return DCRYPT_KEY_USAGE_ENCRYPT; if (strcmp(use, "sig") == 0) return DCRYPT_KEY_USAGE_SIGN; return DCRYPT_KEY_USAGE_NONE; } static int jwk_curve_to_nid(const char *curve) { /* use static mapping table to get correct input for OpenSSL */ const struct jwk_to_ssl_map_entry *entry = jwk_to_ssl_curves; for (;entry->jwk_curve != NULL;entry++) if (strcmp(curve, entry->jwk_curve) == 0) return entry->nid; return 0; } static const char *nid_to_jwk_curve(int nid) { const struct jwk_to_ssl_map_entry *entry = jwk_to_ssl_curves; for (;entry->jwk_curve != NULL;entry++) if (nid == entry->nid) return entry->jwk_curve; return NULL; } /* Loads both public and private key */ static bool load_jwk_ec_key(EVP_PKEY **key_r, bool want_private_key, const struct json_tree_node *root, const char *password ATTR_UNUSED, struct dcrypt_private_key *dec_key ATTR_UNUSED, const char **error_r) { i_assert(password == NULL && dec_key == NULL); const char *crv, *x, *y, *d; const struct json_tree_node *node; if ((node = json_tree_find_key(root, "crv")) == NULL || (crv = json_tree_get_value_str(node)) == NULL) { DCRYPT_SET_ERROR("Missing crv parameter"); return FALSE; } if ((node = json_tree_find_key(root, "x")) == NULL || (x = json_tree_get_value_str(node)) == NULL) { DCRYPT_SET_ERROR("Missing x parameter"); return FALSE; } if ((node = json_tree_find_key(root, "y")) == NULL || (y = json_tree_get_value_str(node)) == NULL) { DCRYPT_SET_ERROR("Missing y parameter"); return FALSE; } if ((node = json_tree_find_key(root, "d")) == NULL || (d = json_tree_get_value_str(node)) == NULL) { if (want_private_key) { DCRYPT_SET_ERROR("Missing d parameter"); return FALSE; } } /* base64 decode x and y */ buffer_t *bx = t_base64url_decode_str(x); buffer_t *by = t_base64url_decode_str(y); /* determine NID */ int nid = jwk_curve_to_nid(crv); if (nid == 0) { DCRYPT_SET_ERROR(t_strdup_printf("Unsupported curve: %s", crv)); return FALSE; } /* create key */ EC_KEY *ec_key = EC_KEY_new_by_curve_name(nid); if (ec_key == NULL) { DCRYPT_SET_ERROR("Cannot allocate memory"); return FALSE; } BIGNUM *px = BN_new(); BIGNUM *py = BN_new(); if (BN_bin2bn(bx->data, bx->used, px) == NULL || BN_bin2bn(by->data, by->used, py) == NULL) { EC_KEY_free(ec_key); BN_free(px); BN_free(py); return dcrypt_openssl_error(error_r); } int ret = EC_KEY_set_public_key_affine_coordinates(ec_key, px, py); BN_free(px); BN_free(py); if (ret != 1) { EC_KEY_free(ec_key); return dcrypt_openssl_error(error_r); } /* FIXME: Support decryption */ if (want_private_key) { buffer_t *bd = t_base64url_decode_str(d); BIGNUM *pd = BN_secure_new(); if (BN_bin2bn(bd->data, bd->used, pd) == NULL) { EC_KEY_free(ec_key); return dcrypt_openssl_error(error_r); } ret = EC_KEY_set_private_key(ec_key, pd); BN_free(pd); if (ret != 1) { EC_KEY_free(ec_key); return dcrypt_openssl_error(error_r); } } if (EC_KEY_check_key(ec_key) != 1) { EC_KEY_free(ec_key); return dcrypt_openssl_error(error_r); } EC_KEY_precompute_mult(ec_key, NULL); EC_KEY_set_asn1_flag(ec_key, OPENSSL_EC_NAMED_CURVE); /* return as EVP_PKEY */ EVP_PKEY *pkey = EVP_PKEY_new(); EVP_PKEY_set1_EC_KEY(pkey, ec_key); EC_KEY_free(ec_key); *key_r = pkey; return TRUE; } /* RSA helpers */ #if !defined(HAVE_RSA_SET0_KEY) static int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) { if (n == NULL || e == NULL) { RSAerr(0, ERR_R_PASSED_NULL_PARAMETER); return 0; } BN_free(r->n); r->n = n; BN_free(r->e); r->e = e; BN_free(r->d); r->d = d; return 1; } #endif #if !defined(HAVE_RSA_SET0_FACTORS) static int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q) { if (p == NULL || q == NULL) { RSAerr(0, ERR_R_PASSED_NULL_PARAMETER); return 0; } BN_free(r->p); r->p = p; BN_free(r->q); r->q = q; return 1; } #endif #if !defined(HAVE_RSA_SET0_CRT_PARAMS) static int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp) { if (dmp1 == NULL || dmq1 == NULL || iqmp == NULL) { RSAerr(0, ERR_R_PASSED_NULL_PARAMETER); return 0; } BN_free(r->dmp1); r->dmp1 = dmp1; BN_free(r->dmq1); r->dmq1 = dmq1; BN_free(r->iqmp); r->iqmp = iqmp; return 1; } #endif /* Loads both public and private key */ static bool load_jwk_rsa_key(EVP_PKEY **key_r, bool want_private_key, const struct json_tree_node *root, const char *password ATTR_UNUSED, struct dcrypt_private_key *dec_key ATTR_UNUSED, const char **error_r) { const char *n, *e, *d = NULL, *p = NULL, *q = NULL, *dp = NULL; const char *dq = NULL, *qi = NULL; const struct json_tree_node *node; /* n and e must be present */ if ((node = json_tree_find_key(root, "n")) == NULL || (n = json_tree_get_value_str(node)) == NULL) { DCRYPT_SET_ERROR("Missing n parameter"); return FALSE; } if ((node = json_tree_find_key(root, "e")) == NULL || (e = json_tree_get_value_str(node)) == NULL) { DCRYPT_SET_ERROR("Missing e parameter"); return FALSE; } if (want_private_key) { if ((node = json_tree_find_key(root, "d")) == NULL || (d = json_tree_get_value_str(node)) == NULL) { DCRYPT_SET_ERROR("Missing d parameter"); return FALSE; } if ((node = json_tree_find_key(root, "p")) == NULL || (p = json_tree_get_value_str(node)) == NULL) { DCRYPT_SET_ERROR("Missing p parameter"); return FALSE; } if ((node = json_tree_find_key(root, "q")) == NULL || (q = json_tree_get_value_str(node)) == NULL) { DCRYPT_SET_ERROR("Missing q parameter"); return FALSE; } if ((node = json_tree_find_key(root, "dp")) == NULL || (dp = json_tree_get_value_str(node)) == NULL) { DCRYPT_SET_ERROR("Missing dp parameter"); return FALSE; } if ((node = json_tree_find_key(root, "dq")) == NULL || (dq = json_tree_get_value_str(node)) == NULL) { DCRYPT_SET_ERROR("Missing dq parameter"); return FALSE; } if ((node = json_tree_find_key(root, "qi")) == NULL || (qi = json_tree_get_value_str(node)) == NULL) { DCRYPT_SET_ERROR("Missing qi parameter"); return FALSE; } } /* convert into BIGNUMs */ BIGNUM *pn, *pe, *pd, *pp, *pq, *pdp, *pdq, *pqi; buffer_t *bn = t_base64url_decode_str(n); buffer_t *be = t_base64url_decode_str(e); if (want_private_key) { pd = BN_secure_new(); buffer_t *bd = t_base64url_decode_str(d); if (BN_bin2bn(bd->data, bd->used, pd) == NULL) { BN_free(pd); return dcrypt_openssl_error(error_r); } } else { pd = NULL; } pn = BN_new(); pe = BN_new(); if (BN_bin2bn(bn->data, bn->used, pn) == NULL || BN_bin2bn(be->data, be->used, pe) == NULL) { if (pd != NULL) BN_free(pd); BN_free(pn); BN_free(pe); return dcrypt_openssl_error(error_r); } RSA *rsa_key = RSA_new(); if (rsa_key == NULL) { if (pd != NULL) BN_free(pd); BN_free(pn); BN_free(pe); return dcrypt_openssl_error(error_r); } if (RSA_set0_key(rsa_key, pn, pe, pd) != 1) { if (pd != NULL) BN_free(pd); BN_free(pn); BN_free(pe); RSA_free(rsa_key); return dcrypt_openssl_error(error_r); } if (want_private_key) { pp = BN_secure_new(); pq = BN_secure_new(); pdp = BN_secure_new(); pdq = BN_secure_new(); pqi = BN_secure_new(); buffer_t *bp = t_base64url_decode_str(p); buffer_t *bq = t_base64url_decode_str(q); buffer_t *bdp = t_base64url_decode_str(dp); buffer_t *bdq = t_base64url_decode_str(dq); buffer_t *bqi = t_base64url_decode_str(qi); if (BN_bin2bn(bp->data, bp->used, pp) == NULL || BN_bin2bn(bq->data, bq->used, pq) == NULL || BN_bin2bn(bdp->data, bdp->used, pdp) == NULL || BN_bin2bn(bdq->data, bdq->used, pdq) == NULL || BN_bin2bn(bqi->data, bqi->used, pqi) == NULL || RSA_set0_factors(rsa_key, pp, pq) != 1) { RSA_free(rsa_key); BN_free(pp); BN_free(pq); BN_free(pdp); BN_free(pdq); BN_free(pqi); return dcrypt_openssl_error(error_r); } else if (RSA_set0_crt_params(rsa_key, pdp, pdq, pqi) != 1) { RSA_free(rsa_key); BN_free(pdp); BN_free(pdq); BN_free(pqi); return dcrypt_openssl_error(error_r); } } /* return as EVP_PKEY */ EVP_PKEY *pkey = EVP_PKEY_new(); EVP_PKEY_set1_RSA(pkey, rsa_key); RSA_free(rsa_key); *key_r = pkey; return TRUE; } static bool dcrypt_openssl_load_private_key_jwk(struct dcrypt_private_key **key_r, const char *data, const char *password, struct dcrypt_private_key *dec_key, const char **error_r) { const char *kty; const char *error; const struct json_tree_node *root, *node; struct json_tree *key_tree; EVP_PKEY *pkey; bool ret; if (parse_jwk_key(data, &key_tree, &error) != 0) { DCRYPT_SET_ERROR(t_strdup_printf("Cannot load JWK private key: %s", error)); return FALSE; } root = json_tree_root(key_tree); /* check key type */ if ((node = json_tree_find_key(root, "kty")) == NULL) { DCRYPT_SET_ERROR("Cannot load JWK private key: no kty parameter"); json_tree_deinit(&key_tree); return FALSE; } kty = json_tree_get_value_str(node); if (null_strcmp(kty, "EC") == 0) { ret = load_jwk_ec_key(&pkey, TRUE, root, password, dec_key, &error); } else if (strcmp(kty, "RSA") == 0) { ret = load_jwk_rsa_key(&pkey, TRUE, root, password, dec_key, &error); } else { error = "Unsupported key type"; ret = FALSE; } i_assert(ret || error != NULL); if (!ret) DCRYPT_SET_ERROR(t_strdup_printf("Cannot load JWK private key: %s", error)); else if (ret) { *key_r = i_new(struct dcrypt_private_key, 1); (*key_r)->key = pkey; (*key_r)->ref++; /* check if kid is present */ if ((node = json_tree_find_key(root, "kid")) != NULL) (*key_r)->key_id = i_strdup_empty(json_tree_get_value_str(node)); /* check if use is present */ if ((node = json_tree_find_key(root, "use")) != NULL) (*key_r)->usage = jwk_use_to_key_usage(json_tree_get_value_str(node)); } json_tree_deinit(&key_tree); return ret; } static bool dcrypt_openssl_load_public_key_jwk(struct dcrypt_public_key **key_r, const char *data, const char **error_r) { const char *kty; const char *error; const struct json_tree_node *root, *node; struct json_tree *key_tree; EVP_PKEY *pkey; bool ret; if (parse_jwk_key(data, &key_tree, &error) != 0) { DCRYPT_SET_ERROR(t_strdup_printf("Cannot load JWK public key: %s", error)); return FALSE; } root = json_tree_root(key_tree); /* check key type */ if ((node = json_tree_find_key(root, "kty")) == NULL) { DCRYPT_SET_ERROR("Cannot load JWK public key: no kty parameter"); json_tree_deinit(&key_tree); return FALSE; } kty = json_tree_get_value_str(node); if (null_strcmp(kty, "EC") == 0) { ret = load_jwk_ec_key(&pkey, FALSE, root, NULL, NULL, &error); } else if (strcmp(kty, "RSA") == 0) { ret = load_jwk_rsa_key(&pkey, FALSE, root, NULL, NULL, &error); } else { error = "Unsupported key type"; ret = FALSE; } i_assert(ret || error != NULL); if (!ret) DCRYPT_SET_ERROR(t_strdup_printf("Cannot load JWK public key: %s", error)); else if (ret) { *key_r = i_new(struct dcrypt_public_key, 1); (*key_r)->key = pkey; (*key_r)->ref++; /* check if kid is present */ if ((node = json_tree_find_key(root, "kid")) != NULL) (*key_r)->key_id = i_strdup_empty(json_tree_get_value_str(node)); /* check if use is present */ if ((node = json_tree_find_key(root, "use")) != NULL) (*key_r)->usage = jwk_use_to_key_usage(json_tree_get_value_str(node)); } json_tree_deinit(&key_tree); return ret; } static int bn2base64url(const BIGNUM *bn, string_t *dest) { int len = BN_num_bytes(bn); unsigned char *data = t_malloc_no0(len); if (BN_bn2bin(bn, data) != len) return -1; base64url_encode(BASE64_ENCODE_FLAG_NO_PADDING, SIZE_MAX, data, len, dest); return 0; } /* FIXME: Add encryption support */ /* FIXME: Add support for 'algo' field */ static bool store_jwk_ec_key(EVP_PKEY *pkey, bool is_private_key, enum dcrypt_key_usage usage, const char *key_id, const char *cipher ATTR_UNUSED, const char *password ATTR_UNUSED, struct dcrypt_public_key *enc_key ATTR_UNUSED, string_t *dest, const char **error_r) { i_assert(cipher == NULL && password == NULL && enc_key == NULL); string_t *temp = t_str_new(256); const EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(pkey); i_assert(ec_key != NULL); int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec_key)); const EC_POINT *public_point = EC_KEY_get0_public_key(ec_key); BIGNUM *x, *y; x = BN_new(); y = BN_new(); if (EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ec_key), public_point, x, y, NULL) != 1) { BN_free(x); BN_free(y); return dcrypt_openssl_error(error_r); } const char *curve = nid_to_jwk_curve(nid); const char *use = key_usage_to_jwk_use(usage); str_printfa(temp, "{\"kty\":\"EC\",\"crv\":\"%s\"", curve); str_append(temp, ",\"x\":\""); bn2base64url(x, temp); str_append(temp, "\",\"y\":\""); bn2base64url(y, temp); if (use != NULL) { str_append(temp, "\",\"use\":\""); json_append_escaped(temp, use); } if (key_id != NULL) { str_append(temp, "\",\"kid\":\""); json_append_escaped(temp, key_id); } BN_free(x); BN_free(y); if (is_private_key) { const BIGNUM *d = EC_KEY_get0_private_key(ec_key); if (d == NULL) { DCRYPT_SET_ERROR("No private key available"); return FALSE; } str_append(temp, "\",\"d\":\""); bn2base64url(d, temp); } str_append(temp, "\"}"); str_append_str(dest, temp); return TRUE; } /* FIXME: Add RSA support */ static bool store_jwk_key(EVP_PKEY *pkey, bool is_private_key, enum dcrypt_key_usage usage, const char *key_id, const char *cipher, const char *password, struct dcrypt_public_key *enc_key, string_t *dest, const char **error_r) { i_assert(cipher == NULL && password == NULL && enc_key == NULL); if (EVP_PKEY_base_id(pkey) == EVP_PKEY_EC) { return store_jwk_ec_key(pkey, is_private_key, usage, key_id, cipher, password, enc_key, dest, error_r); } DCRYPT_SET_ERROR("Unsupported key type"); return FALSE; } static bool dcrypt_openssl_load_private_key_dovecot(struct dcrypt_private_key **key_r, const char *data, const char *password, struct dcrypt_private_key *key, enum dcrypt_key_version version, const char **error_r) { const char **input = t_strsplit(data, ":\t"); size_t len = str_array_length(input); switch (version) { case DCRYPT_KEY_VERSION_1: return dcrypt_openssl_load_private_key_dovecot_v1( key_r, len, input, password, key, error_r); case DCRYPT_KEY_VERSION_2: return dcrypt_openssl_load_private_key_dovecot_v2( key_r, len, input, password, key, error_r); case DCRYPT_KEY_VERSION_NA: i_unreached(); } return FALSE; } static bool dcrypt_openssl_load_public_key_dovecot_v1(struct dcrypt_public_key **key_r, int len, const char **input, const char **error_r) { int nid; if (str_to_int(input[1], &nid) != 0) { DCRYPT_SET_ERROR("Corrupted data"); return FALSE; } EC_KEY *eckey = EC_KEY_new_by_curve_name(nid); if (eckey == NULL) { dcrypt_openssl_error(error_r); return FALSE; } EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE); BN_CTX *bnctx = BN_CTX_new(); EC_POINT *point = EC_POINT_new(EC_KEY_get0_group(eckey)); if (bnctx == NULL || point == NULL || EC_POINT_hex2point(EC_KEY_get0_group(eckey), input[2], point, bnctx) == NULL) { BN_CTX_free(bnctx); EC_KEY_free(eckey); EC_POINT_free(point); dcrypt_openssl_error(error_r); return FALSE; } BN_CTX_free(bnctx); EC_KEY_set_public_key(eckey, point); EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE); EC_POINT_free(point); if (EC_KEY_check_key(eckey) == 1) { EVP_PKEY *key = EVP_PKEY_new(); EVP_PKEY_set1_EC_KEY(key, eckey); EC_KEY_free(eckey); /* make sure digest matches */ buffer_t *dgst = t_buffer_create(32); struct dcrypt_public_key tmp; i_zero(&tmp); tmp.key = key; dcrypt_openssl_public_key_id_old(&tmp, dgst, NULL); if (strcmp(binary_to_hex(dgst->data, dgst->used), input[len-1]) != 0) { DCRYPT_SET_ERROR("Key id mismatch after load"); EVP_PKEY_free(key); return FALSE; } *key_r = i_new(struct dcrypt_public_key, 1); (*key_r)->key = key; (*key_r)->ref++; return TRUE; } dcrypt_openssl_error(error_r); return FALSE; } static bool dcrypt_openssl_load_public_key_dovecot_v2(struct dcrypt_public_key **key_r, int len, const char **input, const char **error_r) { buffer_t tmp; size_t keylen = strlen(input[1])/2; unsigned char keybuf[keylen]; const unsigned char *ptr; buffer_create_from_data(&tmp, keybuf, keylen); hex_to_binary(input[1], &tmp); ptr = keybuf; EVP_PKEY *pkey = EVP_PKEY_new(); if (pkey == NULL || d2i_PUBKEY(&pkey, &ptr, keylen)==NULL) { EVP_PKEY_free(pkey); dcrypt_openssl_error(error_r); return FALSE; } /* make sure digest matches */ buffer_t *dgst = t_buffer_create(32); struct dcrypt_public_key tmpkey; i_zero(&tmpkey); tmpkey.key = pkey; dcrypt_openssl_public_key_id(&tmpkey, "sha256", dgst, NULL); if (strcmp(binary_to_hex(dgst->data, dgst->used), input[len-1]) != 0) { DCRYPT_SET_ERROR("Key id mismatch after load"); EVP_PKEY_free(pkey); return FALSE; } *key_r = i_new(struct dcrypt_public_key, 1); (*key_r)->key = pkey; (*key_r)->ref++; return TRUE; } static bool dcrypt_openssl_load_public_key_dovecot(struct dcrypt_public_key **key_r, const char *data, enum dcrypt_key_version version, const char **error_r) { const char **input = t_strsplit(data, ":\t"); size_t len = str_array_length(input); switch (version) { case DCRYPT_KEY_VERSION_1: return dcrypt_openssl_load_public_key_dovecot_v1( key_r, len, input, error_r); break; case DCRYPT_KEY_VERSION_2: return dcrypt_openssl_load_public_key_dovecot_v2( key_r, len, input, error_r); break; case DCRYPT_KEY_VERSION_NA: i_unreached(); } return FALSE; } static bool dcrypt_openssl_encrypt_private_key_dovecot(buffer_t *key, int enctype, const char *cipher, const char *password, struct dcrypt_public_key *enc_key, buffer_t *destination, const char **error_r) { bool res; unsigned char *ptr; unsigned char salt[8]; buffer_t *peer_key = t_buffer_create(128); buffer_t *secret = t_buffer_create(128); cipher = t_str_lcase(cipher); str_append(destination, cipher); str_append_c(destination, ':'); random_fill(salt, sizeof(salt)); binary_to_hex_append(destination, salt, sizeof(salt)); buffer_t saltbuf; buffer_create_from_const_data(&saltbuf, salt, sizeof(salt)); /* so we don't have to make new version if we ever upgrade these */ str_append(destination, t_strdup_printf(":%s:%d:", DCRYPT_DOVECOT_KEY_ENCRYPT_HASH, DCRYPT_DOVECOT_KEY_ENCRYPT_ROUNDS)); if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PK) { if (EVP_PKEY_base_id(enc_key->key) == EVP_PKEY_RSA) { size_t used = buffer_get_used_size(secret); /* peer key, in this case, is encrypted secret, which is 16 bytes of data */ ptr = buffer_append_space_unsafe(secret, 16); random_fill(ptr, 16); buffer_set_used_size(secret, used+16); if (!dcrypt_rsa_encrypt(enc_key, secret->data, secret->used, peer_key, DCRYPT_PADDING_RSA_PKCS1_OAEP, error_r)) { return FALSE; } } else if (EVP_PKEY_base_id(enc_key->key) == EVP_PKEY_EC) { /* generate secret by ECDHE */ if (!dcrypt_openssl_ecdh_derive_secret_peer( enc_key, peer_key, secret, error_r)) { return FALSE; } } else { /* Loading the key should have failed */ i_unreached(); } /* add encryption key id, reuse peer_key buffer */ } else if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PASSWORD) { str_append(secret, password); } /* encrypt key using secret and salt */ buffer_t *tmp = t_buffer_create(128); res = dcrypt_openssl_cipher_key_dovecot_v2(cipher, DCRYPT_MODE_ENCRYPT, key, secret, &saltbuf, DCRYPT_DOVECOT_KEY_ENCRYPT_HASH, DCRYPT_DOVECOT_KEY_ENCRYPT_ROUNDS, tmp, error_r); safe_memset(buffer_get_modifiable_data(secret, NULL), 0, secret->used); binary_to_hex_append(destination, tmp->data, tmp->used); /* some additional fields or private key version */ if (enctype == DCRYPT_DOVECOT_KEY_ENCRYPT_PK) { str_append_c(destination, ':'); /* for RSA, this is the actual encrypted secret */ binary_to_hex_append(destination, peer_key->data, peer_key->used); str_append_c(destination, ':'); buffer_set_used_size(peer_key, 0); if (!dcrypt_openssl_public_key_id(enc_key, "sha256", peer_key, error_r)) return FALSE; binary_to_hex_append(destination, peer_key->data, peer_key->used); } return res; } static bool dcrypt_openssl_store_private_key_dovecot(struct dcrypt_private_key *key, const char *cipher, buffer_t *destination, const char *password, struct dcrypt_public_key *enc_key, const char **error_r) { size_t dest_used = buffer_get_used_size(destination); const char *cipher2 = NULL; EVP_PKEY *pkey = key->key; char objtxt[OID_TEXT_MAX_LEN]; ASN1_OBJECT *obj; if (EVP_PKEY_base_id(pkey) == EVP_PKEY_EC) { /* because otherwise we get wrong nid */ obj = OBJ_nid2obj(EC_GROUP_get_curve_name( EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(pkey)))); EC_KEY_set_conv_form(EVP_PKEY_get0_EC_KEY(pkey), POINT_CONVERSION_COMPRESSED); } else { obj = OBJ_nid2obj(EVP_PKEY_id(pkey)); } int enctype = DCRYPT_KEY_ENCRYPTION_TYPE_NONE; int len = OBJ_obj2txt(objtxt, sizeof(objtxt), obj, 1); if (len < 1) return dcrypt_openssl_error(error_r); if (len > (int)sizeof(objtxt)) { DCRYPT_SET_ERROR("Object identifier too long"); return FALSE; } buffer_t *buf = t_buffer_create(256); /* convert key to private key value */ if (EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA) { unsigned char *ptr; RSA *rsa = EVP_PKEY_get0_RSA(pkey); int len = i2d_RSAPrivateKey(rsa, &ptr); if (len < 1) return dcrypt_openssl_error(error_r); buffer_append(buf, ptr, len); } else if (EVP_PKEY_base_id(pkey) == EVP_PKEY_EC) { unsigned char *ptr; EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pkey); const BIGNUM *pk = EC_KEY_get0_private_key(eckey); /* serialize to MPI which is portable */ int len = BN_bn2mpi(pk, NULL); ptr = buffer_append_space_unsafe(buf, len); BN_bn2mpi(pk, ptr); } else { /* Loading the key should have failed */ i_unreached(); } /* see if we want ECDH based or password based encryption */ if (cipher != NULL && strncasecmp(cipher, "ecdh-", 5) == 0) { i_assert(enc_key != NULL); i_assert(password == NULL); enctype = DCRYPT_DOVECOT_KEY_ENCRYPT_PK; cipher2 = cipher+5; } else if (cipher != NULL) { i_assert(enc_key == NULL); i_assert(password != NULL); enctype = DCRYPT_DOVECOT_KEY_ENCRYPT_PASSWORD; cipher2 = cipher; } else if (enctype == DCRYPT_KEY_ENCRYPTION_TYPE_NONE) { i_assert(enc_key == NULL && password == NULL); } /* put in OID and encryption type */ str_append(destination, t_strdup_printf("2:%s:%d:", objtxt, enctype)); /* perform encryption if desired */ if (enctype != DCRYPT_KEY_ENCRYPTION_TYPE_NONE) { if (!dcrypt_openssl_encrypt_private_key_dovecot(buf, enctype, cipher2, password, enc_key, destination, error_r)) { buffer_set_used_size(destination, dest_used); return FALSE; } } else { binary_to_hex_append(destination, buf->data, buf->used); } /* append public key id */ str_append_c(destination, ':'); buffer_set_used_size(buf, 0); bool res = dcrypt_openssl_private_key_id(key, "sha256", buf, error_r); binary_to_hex_append(destination, buf->data, buf->used); if (!res) { /* well, that didn't end well */ buffer_set_used_size(destination, dest_used); return FALSE; } return TRUE; } static bool dcrypt_openssl_store_public_key_dovecot(struct dcrypt_public_key *key, buffer_t *destination, const char **error_r) { EVP_PKEY *pubkey = key->key; unsigned char *tmp = NULL; size_t dest_used = buffer_get_used_size(destination); if (EVP_PKEY_base_id(pubkey) == EVP_PKEY_EC) EC_KEY_set_conv_form(EVP_PKEY_get0_EC_KEY(pubkey), POINT_CONVERSION_COMPRESSED); int rv = i2d_PUBKEY(pubkey, &tmp); if (tmp == NULL) return dcrypt_openssl_error(error_r); /* then store it */ str_append_c(destination, '2'); str_append_c(destination, ':'); binary_to_hex_append(destination, tmp, rv); OPENSSL_free(tmp); /* append public key ID */ str_append_c(destination, ':'); buffer_t *buf = t_buffer_create(32); bool res = dcrypt_openssl_public_key_id(key, "sha256", buf, error_r); if (!res) { buffer_set_used_size(destination, dest_used); return FALSE; } str_append(destination, binary_to_hex(buf->data, buf->used)); return TRUE; } static bool dcrypt_openssl_load_private_key(struct dcrypt_private_key **key_r, const char *data, const char *password, struct dcrypt_private_key *dec_key, const char **error_r) { i_assert(key_r != NULL); enum dcrypt_key_format format; enum dcrypt_key_version version; enum dcrypt_key_kind kind; if (!dcrypt_openssl_key_string_get_info(data, &format, &version, &kind, NULL, NULL, NULL, error_r)) { return FALSE; } if (kind != DCRYPT_KEY_KIND_PRIVATE) { DCRYPT_SET_ERROR("key is not private"); return FALSE; } if (format == DCRYPT_FORMAT_JWK) return dcrypt_openssl_load_private_key_jwk(key_r, data, password, dec_key, error_r); if (format == DCRYPT_FORMAT_DOVECOT) return dcrypt_openssl_load_private_key_dovecot(key_r, data, password, dec_key, version, error_r); EVP_PKEY *key = NULL, *key2; BIO *key_in = BIO_new_mem_buf((void*)data, strlen(data)); key = EVP_PKEY_new(); key2 = PEM_read_bio_PrivateKey(key_in, &key, NULL, (void*)password); BIO_vfree(key_in); if (key2 == NULL) { EVP_PKEY_free(key); return dcrypt_openssl_error(error_r); } if (EVP_PKEY_base_id(key) == EVP_PKEY_EC) { EC_KEY_set_asn1_flag(EVP_PKEY_get0_EC_KEY(key), OPENSSL_EC_NAMED_CURVE); } *key_r = i_new(struct dcrypt_private_key, 1); (*key_r)->key = key; (*key_r)->ref++; return TRUE; } static bool dcrypt_openssl_load_public_key(struct dcrypt_public_key **key_r, const char *data, const char **error_r) { enum dcrypt_key_format format; enum dcrypt_key_version version; enum dcrypt_key_kind kind; i_assert(key_r != NULL); if (!dcrypt_openssl_key_string_get_info(data, &format, &version, &kind, NULL, NULL, NULL, error_r)) { return FALSE; } /* JWK private keys can be loaded as public */ if (kind != DCRYPT_KEY_KIND_PUBLIC && format != DCRYPT_FORMAT_JWK) { DCRYPT_SET_ERROR("key is not public"); return FALSE; } if (format == DCRYPT_FORMAT_JWK) return dcrypt_openssl_load_public_key_jwk(key_r, data, error_r); if (format == DCRYPT_FORMAT_DOVECOT) return dcrypt_openssl_load_public_key_dovecot(key_r, data, version, error_r); EVP_PKEY *key = NULL; BIO *key_in = BIO_new_mem_buf((void*)data, strlen(data)); if (key_in == NULL) return dcrypt_openssl_error(error_r); key = PEM_read_bio_PUBKEY(key_in, &key, NULL, NULL); if (BIO_reset(key_in) <= 0) i_unreached(); if (key == NULL) { /* ec keys are bother */ /* read the header */ char buf[27]; /* begin public key */ if (BIO_gets(key_in, buf, sizeof(buf)) != 1) { BIO_vfree(key_in); return dcrypt_openssl_error(error_r); } if (strcmp(buf, "-----BEGIN PUBLIC KEY-----") != 0) { DCRYPT_SET_ERROR("Missing public key header"); return FALSE; } BIO *b64 = BIO_new(BIO_f_base64()); if (b64 == NULL) { BIO_vfree(key_in); return dcrypt_openssl_error(error_r); } EC_KEY *eckey = d2i_EC_PUBKEY_bio(b64, NULL); if (eckey != NULL) { EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE); key = EVP_PKEY_new(); if (key != NULL) EVP_PKEY_set1_EC_KEY(key, eckey); EC_KEY_free(eckey); } } BIO_vfree(key_in); if (key == NULL) return dcrypt_openssl_error(error_r); *key_r = i_new(struct dcrypt_public_key, 1); (*key_r)->key = key; (*key_r)->ref++; return TRUE; } static bool dcrypt_openssl_store_private_key(struct dcrypt_private_key *key, enum dcrypt_key_format format, const char *cipher, buffer_t *destination, const char *password, struct dcrypt_public_key *enc_key, const char **error_r) { i_assert(key != NULL && key->key != NULL); int ec; if (format == DCRYPT_FORMAT_DOVECOT) { bool ret; ret = dcrypt_openssl_store_private_key_dovecot( key, cipher, destination, password, enc_key, error_r); return ret; } EVP_PKEY *pkey = key->key; if (format == DCRYPT_FORMAT_JWK) { bool ret; ret = store_jwk_key(pkey, TRUE, key->usage, key->key_id, cipher, password, enc_key, destination, error_r); return ret; } if (EVP_PKEY_base_id(pkey) == EVP_PKEY_EC) EC_KEY_set_conv_form(EVP_PKEY_get0_EC_KEY(pkey), POINT_CONVERSION_UNCOMPRESSED); BIO *key_out = BIO_new(BIO_s_mem()); if (key_out == NULL) return dcrypt_openssl_error(error_r); const EVP_CIPHER *algo = NULL; if (cipher != NULL) { algo = EVP_get_cipherbyname(cipher); if (algo == NULL) { DCRYPT_SET_ERROR(t_strdup_printf("Invalid cipher %s", cipher)); return FALSE; } } ec = PEM_write_bio_PrivateKey(key_out, pkey, algo, NULL, 0, NULL, (void*)password); if (BIO_flush(key_out) <= 0) ec = -1; if (ec != 1) { BIO_vfree(key_out); return dcrypt_openssl_error(error_r); } long bs; char *buf; bs = BIO_get_mem_data(key_out, &buf); buffer_append(destination, buf, bs); BIO_vfree(key_out); return TRUE; } static bool dcrypt_openssl_store_public_key(struct dcrypt_public_key *key, enum dcrypt_key_format format, buffer_t *destination, const char **error_r) { int ec; i_assert(key != NULL && key->key != NULL); if (format == DCRYPT_FORMAT_DOVECOT) { return dcrypt_openssl_store_public_key_dovecot(key, destination, error_r); } EVP_PKEY *pkey = key->key; if (format == DCRYPT_FORMAT_JWK) { bool ret; ret = store_jwk_key(pkey, FALSE, key->usage, key->key_id, NULL, NULL, NULL, destination, error_r); return ret; } if (EVP_PKEY_base_id(pkey) == EVP_PKEY_EC) EC_KEY_set_conv_form(EVP_PKEY_get0_EC_KEY(pkey), POINT_CONVERSION_UNCOMPRESSED); BIO *key_out = BIO_new(BIO_s_mem()); if (key_out == NULL) return dcrypt_openssl_error(error_r); BIO *b64; if (EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA) ec = PEM_write_bio_PUBKEY(key_out, pkey); else if ((b64 = BIO_new(BIO_f_base64())) == NULL) ec = -1; else { (void)BIO_puts(key_out, "-----BEGIN PUBLIC KEY-----\n"); (void)BIO_push(b64, key_out); ec = i2d_EC_PUBKEY_bio(b64, EVP_PKEY_get0_EC_KEY(pkey)); if (BIO_flush(b64) <= 0) ec = -1; (void)BIO_pop(b64); BIO_vfree(b64); if (BIO_puts(key_out, "-----END PUBLIC KEY-----") <= 0) ec = -1; } if (ec != 1) { BIO_vfree(key_out); return dcrypt_openssl_error(error_r); } long bs; char *buf; bs = BIO_get_mem_data(key_out, &buf); buffer_append(destination, buf, bs); BIO_vfree(key_out); return TRUE; } static void dcrypt_openssl_private_to_public_key(struct dcrypt_private_key *priv_key, struct dcrypt_public_key **pub_key_r) { i_assert(priv_key != NULL && pub_key_r != NULL); EVP_PKEY *pkey = priv_key->key; EVP_PKEY *pk; pk = EVP_PKEY_new(); i_assert(pk != NULL); /* we shouldn't get malloc() failures */ if (EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA) { RSA *rsa = RSAPublicKey_dup(EVP_PKEY_get0_RSA(pkey)); EVP_PKEY_set1_RSA(pk, rsa); RSA_free(rsa); } else if (EVP_PKEY_base_id(pkey) == EVP_PKEY_EC) { EC_KEY* eck = EVP_PKEY_get1_EC_KEY(pkey); EC_KEY_set_asn1_flag(eck, OPENSSL_EC_NAMED_CURVE); EVP_PKEY_set1_EC_KEY(pk, eck); EC_KEY_free(eck); } else { /* Loading the key should have failed */ i_unreached(); } *pub_key_r = i_new(struct dcrypt_public_key, 1); (*pub_key_r)->key = pk; (*pub_key_r)->ref++; } static bool dcrypt_openssl_key_string_get_info( const char *key_data, enum dcrypt_key_format *format_r, enum dcrypt_key_version *version_r, enum dcrypt_key_kind *kind_r, enum dcrypt_key_encryption_type *encryption_type_r, const char **encryption_key_hash_r, const char **key_hash_r, const char **error_r) { enum dcrypt_key_format format = DCRYPT_FORMAT_PEM; enum dcrypt_key_version version = DCRYPT_KEY_VERSION_NA; enum dcrypt_key_encryption_type encryption_type = DCRYPT_KEY_ENCRYPTION_TYPE_NONE; enum dcrypt_key_kind kind = DCRYPT_KEY_KIND_PUBLIC; char *encryption_key_hash = NULL; char *key_hash = NULL; i_assert(key_data != NULL); /* is it PEM key */ if (str_begins(key_data, "-----BEGIN ")) { format = DCRYPT_FORMAT_PEM; version = DCRYPT_KEY_VERSION_NA; key_data += 11; if (str_begins(key_data, "RSA ")) { DCRYPT_SET_ERROR("RSA private key format not supported, convert it to PKEY format with openssl pkey"); return FALSE; } if (str_begins(key_data, "ENCRYPTED ")) { encryption_type = DCRYPT_KEY_ENCRYPTION_TYPE_PASSWORD; key_data += 10; } if (str_begins(key_data, "PRIVATE KEY-----")) kind = DCRYPT_KEY_KIND_PRIVATE; else if (str_begins(key_data, "PUBLIC KEY-----")) kind = DCRYPT_KEY_KIND_PUBLIC; else { DCRYPT_SET_ERROR("Unknown/invalid PEM key type"); return FALSE; } } else if (*key_data == '{') { /* possibly a JWK key */ format = DCRYPT_FORMAT_JWK; version = DCRYPT_KEY_VERSION_NA; struct json_tree *tree; const struct json_tree_node *root, *node; const char *value, *error; if (parse_jwk_key(key_data, &tree, &error) != 0) { DCRYPT_SET_ERROR("Unknown/invalid key data"); return FALSE; } /* determine key type */ root = json_tree_root(tree); if ((node = json_tree_find_key(root, "kty")) == NULL || (value = json_tree_get_value_str(node)) == NULL) { json_tree_deinit(&tree); DCRYPT_SET_ERROR("Invalid JWK key: Missing kty parameter"); return FALSE; } else if (strcmp(value, "RSA") == 0) { if (json_tree_find_key(root, "d") != NULL) kind = DCRYPT_KEY_KIND_PRIVATE; else kind = DCRYPT_KEY_KIND_PUBLIC; } else if (strcmp(value, "EC") == 0) { if (json_tree_find_key(root, "d") != NULL) kind = DCRYPT_KEY_KIND_PRIVATE; else kind = DCRYPT_KEY_KIND_PUBLIC; } else { json_tree_deinit(&tree); DCRYPT_SET_ERROR("Unsupported JWK key type"); return FALSE; } json_tree_deinit(&tree); } else { if (str_begins(key_data, "1:")) { DCRYPT_SET_ERROR("Dovecot v1 key format uses tab to separate fields"); return FALSE; } else if (str_begins(key_data, "2\t")) { DCRYPT_SET_ERROR("Dovecot v2 key format uses colon to separate fields"); return FALSE; } const char **fields = t_strsplit(key_data, ":\t"); int nfields = str_array_length(fields); if (nfields < 2) { DCRYPT_SET_ERROR("Unknown key format"); return FALSE; } format = DCRYPT_FORMAT_DOVECOT; /* field 1 - version */ if (strcmp(fields[0], "1") == 0) { version = DCRYPT_KEY_VERSION_1; if (nfields == 4) { kind = DCRYPT_KEY_KIND_PUBLIC; } else if (nfields == 5 && strcmp(fields[2],"0") == 0) { kind = DCRYPT_KEY_KIND_PRIVATE; encryption_type = DCRYPT_KEY_ENCRYPTION_TYPE_NONE; } else if (nfields == 6 && strcmp(fields[2],"2") == 0) { kind = DCRYPT_KEY_KIND_PRIVATE; encryption_type = DCRYPT_KEY_ENCRYPTION_TYPE_PASSWORD; } else if (nfields == 7 && strcmp(fields[2],"1") == 0) { kind = DCRYPT_KEY_KIND_PRIVATE; encryption_type = DCRYPT_KEY_ENCRYPTION_TYPE_KEY; if (encryption_key_hash_r != NULL) encryption_key_hash = i_strdup(fields[nfields-2]); } else { DCRYPT_SET_ERROR("Invalid dovecot v1 encoding"); return FALSE; } } else if (strcmp(fields[0], "2") == 0) { version = DCRYPT_KEY_VERSION_2; if (nfields == 3) { kind = DCRYPT_KEY_KIND_PUBLIC; } else if (nfields == 5 && strcmp(fields[2],"0") == 0) { kind = DCRYPT_KEY_KIND_PRIVATE; encryption_type = DCRYPT_KEY_ENCRYPTION_TYPE_NONE; } else if (nfields == 9 && strcmp(fields[2],"2") == 0) { kind = DCRYPT_KEY_KIND_PRIVATE; encryption_type = DCRYPT_KEY_ENCRYPTION_TYPE_PASSWORD; } else if (nfields == 11 && strcmp(fields[2],"1") == 0) { kind = DCRYPT_KEY_KIND_PRIVATE; encryption_type = DCRYPT_KEY_ENCRYPTION_TYPE_KEY; if (encryption_key_hash_r != NULL) encryption_key_hash = i_strdup(fields[nfields-2]); } else { DCRYPT_SET_ERROR("Invalid dovecot v2 encoding"); return FALSE; } } else { DCRYPT_SET_ERROR("Invalid dovecot key version"); return FALSE; } /* last field is always key hash */ if (key_hash_r != NULL) key_hash = i_strdup(fields[nfields-1]); } if (format_r != NULL) *format_r = format; if (version_r != NULL) *version_r = version; if (encryption_type_r != NULL) *encryption_type_r = encryption_type; if (encryption_key_hash_r != NULL) { *encryption_key_hash_r = t_strdup(encryption_key_hash); i_free(encryption_key_hash); } if (kind_r != NULL) *kind_r = kind; if (key_hash_r != NULL) { *key_hash_r = t_strdup(key_hash); i_free(key_hash); } return TRUE; } static void dcrypt_openssl_ref_public_key(struct dcrypt_public_key *key) { i_assert(key != NULL && key->ref > 0); key->ref++; } static void dcrypt_openssl_ref_private_key(struct dcrypt_private_key *key) { i_assert(key != NULL && key->ref > 0); key->ref++; } static void dcrypt_openssl_unref_public_key(struct dcrypt_public_key **key) { i_assert(key != NULL); struct dcrypt_public_key *_key = *key; if (_key == NULL) return; i_assert(_key->ref > 0); *key = NULL; if (--_key->ref > 0) return; EVP_PKEY_free(_key->key); i_free(_key->key_id); i_free(_key); } static void dcrypt_openssl_unref_private_key(struct dcrypt_private_key **key) { i_assert(key != NULL); struct dcrypt_private_key *_key = *key; if (_key == NULL) return; i_assert(_key->ref > 0); *key = NULL; if (--_key->ref > 0) return; EVP_PKEY_free(_key->key); i_free(_key->key_id); i_free(_key); } static void dcrypt_openssl_unref_keypair(struct dcrypt_keypair *keypair) { i_assert(keypair != NULL); dcrypt_openssl_unref_public_key(&keypair->pub); dcrypt_openssl_unref_private_key(&keypair->priv); } static bool dcrypt_openssl_rsa_encrypt(struct dcrypt_public_key *key, const unsigned char *data, size_t data_len, buffer_t *result, enum dcrypt_padding padding, const char **error_r) { i_assert(key != NULL && key->key != NULL); int ec, pad = dcrypt_openssl_padding_mode(padding, FALSE, error_r); if (pad == -1) return FALSE; EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(key->key, NULL); size_t outl = EVP_PKEY_size(key->key); unsigned char buf[outl]; if (ctx == NULL || EVP_PKEY_encrypt_init(ctx) < 1 || EVP_PKEY_CTX_set_rsa_padding(ctx, pad) < 1 || EVP_PKEY_encrypt(ctx, buf, &outl, data, data_len) < 1) { dcrypt_openssl_error(error_r); ec = -1; } else { buffer_append(result, buf, outl); ec = 0; } EVP_PKEY_CTX_free(ctx); return ec == 0; } static bool dcrypt_openssl_rsa_decrypt(struct dcrypt_private_key *key, const unsigned char *data, size_t data_len, buffer_t *result, enum dcrypt_padding padding, const char **error_r) { i_assert(key != NULL && key->key != NULL); int ec, pad = dcrypt_openssl_padding_mode(padding, FALSE, error_r); if (pad == -1) return FALSE; EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(key->key, NULL); size_t outl = EVP_PKEY_size(key->key); unsigned char buf[outl]; if (ctx == NULL || EVP_PKEY_decrypt_init(ctx) < 1 || EVP_PKEY_CTX_set_rsa_padding(ctx, pad) < 1 || EVP_PKEY_decrypt(ctx, buf, &outl, data, data_len) < 1) { dcrypt_openssl_error(error_r); ec = -1; } else { buffer_append(result, buf, outl); ec = 0; } EVP_PKEY_CTX_free(ctx); return ec == 0; } static const char * dcrypt_openssl_oid2name(const unsigned char *oid, size_t oid_len, const char **error_r) { const char *name; i_assert(oid != NULL); ASN1_OBJECT *obj = d2i_ASN1_OBJECT(NULL, &oid, oid_len); if (obj == NULL) { dcrypt_openssl_error(error_r); return NULL; } name = OBJ_nid2sn(OBJ_obj2nid(obj)); ASN1_OBJECT_free(obj); return name; } static bool dcrypt_openssl_name2oid(const char *name, buffer_t *oid, const char **error_r) { i_assert(name != NULL); ASN1_OBJECT *obj = OBJ_txt2obj(name, 0); if (obj == NULL) return dcrypt_openssl_error(error_r); size_t len = OBJ_length(obj); if (len == 0) { DCRYPT_SET_ERROR("Object has no OID assigned"); return FALSE; } len = i2d_ASN1_OBJECT(obj, NULL); unsigned char *bufptr = buffer_append_space_unsafe(oid, len); i2d_ASN1_OBJECT(obj, &bufptr); ASN1_OBJECT_free(obj); if (bufptr != NULL) { return TRUE; } return dcrypt_openssl_error(error_r); } static enum dcrypt_key_type dcrypt_openssl_private_key_type(struct dcrypt_private_key *key) { i_assert(key != NULL && key->key != NULL); EVP_PKEY *priv = key->key; if (EVP_PKEY_base_id(priv) == EVP_PKEY_RSA) return DCRYPT_KEY_RSA; else if (EVP_PKEY_base_id(priv) == EVP_PKEY_EC) return DCRYPT_KEY_EC; else i_unreached(); } static enum dcrypt_key_type dcrypt_openssl_public_key_type(struct dcrypt_public_key *key) { i_assert(key != NULL && key->key != NULL); EVP_PKEY *pub = key->key; if (EVP_PKEY_base_id(pub) == EVP_PKEY_RSA) return DCRYPT_KEY_RSA; else if (EVP_PKEY_base_id(pub) == EVP_PKEY_EC) return DCRYPT_KEY_EC; else i_unreached(); } /** this is the v1 old legacy way of doing key id's **/ static bool dcrypt_openssl_public_key_id_old(struct dcrypt_public_key *key, buffer_t *result, const char **error_r) { unsigned char buf[SHA256_DIGEST_LENGTH]; i_assert(key != NULL && key->key != NULL); EVP_PKEY *pub = key->key; if (EVP_PKEY_base_id(pub) != EVP_PKEY_EC) { DCRYPT_SET_ERROR("Only EC key supported"); return FALSE; } char *pub_pt_hex = ec_key_get_pub_point_hex(EVP_PKEY_get0_EC_KEY(pub)); if (pub_pt_hex == NULL) return dcrypt_openssl_error(error_r); /* digest this */ SHA256((const unsigned char*)pub_pt_hex, strlen(pub_pt_hex), buf); buffer_append(result, buf, SHA256_DIGEST_LENGTH); OPENSSL_free(pub_pt_hex); return TRUE; } static bool dcrypt_openssl_private_key_id_old(struct dcrypt_private_key *key, buffer_t *result, const char **error_r) { unsigned char buf[SHA256_DIGEST_LENGTH]; i_assert(key != NULL && key->key != NULL); EVP_PKEY *priv = key->key; if (EVP_PKEY_base_id(priv) != EVP_PKEY_EC) { DCRYPT_SET_ERROR("Only EC key supported"); return FALSE; } char *pub_pt_hex = ec_key_get_pub_point_hex(EVP_PKEY_get0_EC_KEY(priv)); if (pub_pt_hex == NULL) return dcrypt_openssl_error(error_r); /* digest this */ SHA256((const unsigned char*)pub_pt_hex, strlen(pub_pt_hex), buf); buffer_append(result, buf, SHA256_DIGEST_LENGTH); OPENSSL_free(pub_pt_hex); return TRUE; } /** this is the new which uses H(der formatted public key) **/ static bool dcrypt_openssl_public_key_id_evp(EVP_PKEY *key, const EVP_MD *md, buffer_t *result, const char **error_r) { bool res = FALSE; unsigned char buf[EVP_MD_size(md)], *ptr; if (EVP_PKEY_base_id(key) == EVP_PKEY_EC) { EC_KEY_set_conv_form(EVP_PKEY_get0_EC_KEY(key), POINT_CONVERSION_COMPRESSED); } BIO *b = BIO_new(BIO_s_mem()); if (b == NULL || i2d_PUBKEY_bio(b, key) < 1) { BIO_vfree(b); return dcrypt_openssl_error(error_r); } long len = BIO_get_mem_data(b, &ptr); unsigned int hlen = sizeof(buf); /* then hash it */ EVP_MD_CTX *ctx = EVP_MD_CTX_new(); if (ctx == NULL || EVP_DigestInit_ex(ctx, md, NULL) < 1 || EVP_DigestUpdate(ctx, (const unsigned char*)ptr, len) < 1 || EVP_DigestFinal_ex(ctx, buf, &hlen) < 1) { res = dcrypt_openssl_error(error_r); } else { buffer_append(result, buf, hlen); res = TRUE; } EVP_MD_CTX_free(ctx); BIO_vfree(b); return res; } static bool dcrypt_openssl_public_key_id(struct dcrypt_public_key *key, const char *algorithm, buffer_t *result, const char **error_r) { const EVP_MD *md = EVP_get_digestbyname(algorithm); i_assert(key != NULL && key->key != NULL); EVP_PKEY *pub = key->key; if (md == NULL) { DCRYPT_SET_ERROR(t_strdup_printf("Unknown cipher %s", algorithm)); return FALSE; } return dcrypt_openssl_public_key_id_evp(pub, md, result, error_r); } static bool dcrypt_openssl_private_key_id(struct dcrypt_private_key *key, const char *algorithm, buffer_t *result, const char **error_r) { const EVP_MD *md = EVP_get_digestbyname(algorithm); i_assert(key != NULL && key->key != NULL); EVP_PKEY *priv = key->key; if (md == NULL) { DCRYPT_SET_ERROR(t_strdup_printf("Unknown cipher %s", algorithm)); return FALSE; } return dcrypt_openssl_public_key_id_evp(priv, md, result, error_r); } static bool dcrypt_openssl_digest(const char *algorithm, const void *data, size_t data_len, buffer_t *digest_r, const char **error_r) { bool ret; EVP_MD_CTX *mdctx; const EVP_MD *md = EVP_get_digestbyname(algorithm); if (md == NULL) return dcrypt_openssl_error(error_r); unsigned int md_size = EVP_MD_size(md); if ((mdctx = EVP_MD_CTX_create()) == NULL) return dcrypt_openssl_error(error_r); unsigned char *buf = buffer_append_space_unsafe(digest_r, md_size); if (EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL) != 1 || EVP_DigestUpdate(mdctx, data, data_len) != 1 || EVP_DigestFinal_ex(mdctx, buf, &md_size) != 1) { ret = dcrypt_openssl_error(error_r); } else { ret = TRUE; } EVP_MD_CTX_free(mdctx); return ret; } #ifndef HAVE_ECDSA_SIG_GET0 static void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) { i_assert(sig != NULL); *pr = sig->r; *ps = sig->s; } #endif #ifndef HAVE_ECDSA_SIG_SET0 static int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s) { if (sig == NULL || r == NULL || s == NULL) { ECDSAerr(0, ERR_R_PASSED_NULL_PARAMETER); return 0; } BN_free(sig->r); sig->r = r; BN_free(sig->s); sig->s = s; return 1; } #endif static bool dcrypt_openssl_sign_ecdsa(struct dcrypt_private_key *key, const char *algorithm, const void *data, size_t data_len, buffer_t *signature_r, const char **error_r) { EVP_PKEY *pkey = key->key; EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(pkey); bool ret; int rs_len = EC_GROUP_order_bits(EC_KEY_get0_group(ec_key)) / 8; /* digest data */ buffer_t *digest = t_buffer_create(64); if (!dcrypt_openssl_digest(algorithm, data, data_len, digest, error_r)) return FALSE; /* sign data */ ECDSA_SIG *ec_sig; if ((ec_sig = ECDSA_do_sign(digest->data, digest->used, ec_key)) == NULL) return dcrypt_openssl_error(error_r); /* export signature */ const BIGNUM *r; const BIGNUM *s; ECDSA_SIG_get0(ec_sig, &r, &s); int r_len = BN_num_bytes(r); i_assert(rs_len >= r_len); /* write r */ unsigned char *buf = buffer_append_space_unsafe(signature_r, rs_len); if (BN_bn2bin(r, buf + (rs_len - r_len)) != r_len) { ret = dcrypt_openssl_error(error_r); } else { buf = buffer_append_space_unsafe(signature_r, rs_len); int s_len = BN_num_bytes(s); i_assert(rs_len >= s_len); if (BN_bn2bin(s, buf + (rs_len - s_len)) != s_len) { ret = dcrypt_openssl_error(error_r); } else { ret = TRUE; } } ECDSA_SIG_free(ec_sig); return ret; } static bool dcrypt_openssl_sign(struct dcrypt_private_key *key, const char *algorithm, enum dcrypt_signature_format format, const void *data, size_t data_len, buffer_t *signature_r, enum dcrypt_padding padding, const char **error_r) { switch (format) { case DCRYPT_SIGNATURE_FORMAT_DSS: break; case DCRYPT_SIGNATURE_FORMAT_X962: if (EVP_PKEY_base_id(key->key) == EVP_PKEY_RSA) { DCRYPT_SET_ERROR("Format does not support RSA"); return FALSE; } return dcrypt_openssl_sign_ecdsa(key, algorithm, data, data_len, signature_r, error_r); default: i_unreached(); } EVP_PKEY_CTX *pctx = NULL; EVP_MD_CTX *dctx; bool ret; const EVP_MD *md = EVP_get_digestbyname(algorithm); size_t siglen; int pad = dcrypt_openssl_padding_mode(padding, TRUE, error_r); if (pad == -1) return FALSE; if (md == NULL) { DCRYPT_SET_ERROR(t_strdup_printf("Unknown digest %s", algorithm)); return FALSE; } dctx = EVP_MD_CTX_create(); /* NB! Padding is set only on RSA signatures ECDSA signatures use whatever is default */ if (EVP_DigestSignInit(dctx, &pctx, md, NULL, key->key) != 1 || (EVP_PKEY_base_id(key->key) == EVP_PKEY_RSA && EVP_PKEY_CTX_set_rsa_padding(pctx, pad) != 1) || EVP_DigestSignUpdate(dctx, data, data_len) != 1 || EVP_DigestSignFinal(dctx, NULL, &siglen) != 1) { ret = dcrypt_openssl_error(error_r); } else { i_assert(siglen > 0); /* @UNSAFE */ unsigned char *buf = buffer_append_space_unsafe(signature_r, siglen); if (EVP_DigestSignFinal(dctx, buf, &siglen) != 1) { ret = dcrypt_openssl_error(error_r); } else { buffer_set_used_size(signature_r, siglen); ret = TRUE; } } EVP_MD_CTX_destroy(dctx); return ret; } static bool dcrypt_openssl_verify_ecdsa(struct dcrypt_public_key *key, const char *algorithm, const void *data, size_t data_len, const unsigned char *signature, size_t signature_len, bool *valid_r, const char **error_r) { if ((signature_len % 2) != 0) { DCRYPT_SET_ERROR("Truncated signature"); return FALSE; } EVP_PKEY *pkey = key->key; EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(pkey); int ec; /* digest data */ buffer_t *digest = t_buffer_create(64); if (!dcrypt_openssl_digest(algorithm, data, data_len, digest, error_r)) return FALSE; BIGNUM *r = BN_new(); BIGNUM *s = BN_new(); /* attempt to decode BIGNUMs */ if (BN_bin2bn(signature, signature_len / 2, r) == NULL) { BN_free(r); BN_free(s); return dcrypt_openssl_error(error_r); } /* then next */ if (BN_bin2bn(CONST_PTR_OFFSET(signature, signature_len / 2), signature_len / 2, s) == NULL) { BN_free(r); BN_free(s); return dcrypt_openssl_error(error_r); } /* reconstruct signature */ ECDSA_SIG *ec_sig = ECDSA_SIG_new(); ECDSA_SIG_set0(ec_sig, r, s); /* verify it */ ec = ECDSA_do_verify(digest->data, digest->used, ec_sig, ec_key); ECDSA_SIG_free(ec_sig); if (ec == 1) { *valid_r = TRUE; } else if (ec == 0) { *valid_r = FALSE; } else { return dcrypt_openssl_error(error_r); } return TRUE; } static bool dcrypt_openssl_verify(struct dcrypt_public_key *key, const char *algorithm, enum dcrypt_signature_format format, const void *data, size_t data_len, const unsigned char *signature, size_t signature_len, bool *valid_r, enum dcrypt_padding padding, const char **error_r) { switch (format) { case DCRYPT_SIGNATURE_FORMAT_DSS: break; case DCRYPT_SIGNATURE_FORMAT_X962: if (EVP_PKEY_base_id(key->key) == EVP_PKEY_RSA) { DCRYPT_SET_ERROR("Format does not support RSA"); return FALSE; } return dcrypt_openssl_verify_ecdsa(key, algorithm, data, data_len, signature, signature_len, valid_r, error_r); default: i_unreached(); } EVP_PKEY_CTX *pctx = NULL; EVP_MD_CTX *dctx; bool ret; const EVP_MD *md = EVP_get_digestbyname(algorithm); int rc, pad = dcrypt_openssl_padding_mode(padding, TRUE, error_r); if (pad == -1) return FALSE; if (md == NULL) { DCRYPT_SET_ERROR(t_strdup_printf("Unknown digest %s", algorithm)); return FALSE; } dctx = EVP_MD_CTX_create(); /* NB! Padding is set only on RSA signatures ECDSA signatures use whatever is default */ if (EVP_DigestVerifyInit(dctx, &pctx, md, NULL, key->key) != 1 || (EVP_PKEY_base_id(key->key) == EVP_PKEY_RSA && EVP_PKEY_CTX_set_rsa_padding(pctx, pad) != 1) || EVP_DigestVerifyUpdate(dctx, data, data_len) != 1 || (rc = EVP_DigestVerifyFinal(dctx, signature, signature_len)) < 0) { ret = dcrypt_openssl_error(error_r); } else { /* return code 1 means valid signature, otherwise invalid */ *valid_r = (rc == 1); ret = TRUE; } EVP_MD_CTX_destroy(dctx); return ret; } static bool dcrypt_openssl_key_store_private_raw(struct dcrypt_private_key *key, pool_t pool, enum dcrypt_key_type *type_r, ARRAY_TYPE(dcrypt_raw_key) *keys_r, const char **error_r) { i_assert(key != NULL && key->key != NULL); i_assert(array_is_created(keys_r)); EVP_PKEY *priv = key->key; ARRAY_TYPE(dcrypt_raw_key) keys; t_array_init(&keys, 2); if (EVP_PKEY_base_id(priv) == EVP_PKEY_RSA) { DCRYPT_SET_ERROR("Not implemented"); return FALSE; } else if (EVP_PKEY_base_id(priv) == EVP_PKEY_EC) { /* store OID */ EC_KEY *key = EVP_PKEY_get0_EC_KEY(priv); EC_KEY_set_conv_form(key, POINT_CONVERSION_UNCOMPRESSED); int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(key)); ASN1_OBJECT *obj = OBJ_nid2obj(nid); int len = OBJ_length(obj); if (len == 0) { DCRYPT_SET_ERROR("Object has no OID assigned"); return FALSE; } len = i2d_ASN1_OBJECT(obj, NULL); unsigned char *bufptr = p_malloc(pool, len); struct dcrypt_raw_key *item = array_append_space(&keys); item->parameter = bufptr; item->len = i2d_ASN1_OBJECT(obj, &bufptr); ASN1_OBJECT_free(obj); /* store private key */ const BIGNUM *b = EC_KEY_get0_private_key(key); len = BN_num_bytes(b); item = array_append_space(&keys); bufptr = p_malloc(pool, len); if (BN_bn2bin(b, bufptr) < len) return dcrypt_openssl_error(error_r); item->parameter = bufptr; item->len = len; *type_r = DCRYPT_KEY_EC; } else { DCRYPT_SET_ERROR("Key type unsupported"); return FALSE; } array_append_array(keys_r, &keys); return TRUE; } static bool dcrypt_openssl_key_store_public_raw(struct dcrypt_public_key *key, pool_t pool, enum dcrypt_key_type *type_r, ARRAY_TYPE(dcrypt_raw_key) *keys_r, const char **error_r) { i_assert(key != NULL && key->key != NULL); EVP_PKEY *pub = key->key; ARRAY_TYPE(dcrypt_raw_key) keys; t_array_init(&keys, 2); if (EVP_PKEY_base_id(pub) == EVP_PKEY_RSA) { DCRYPT_SET_ERROR("Not implemented"); return FALSE; } else if (EVP_PKEY_base_id(pub) == EVP_PKEY_EC) { /* store OID */ EC_KEY *key = EVP_PKEY_get0_EC_KEY(pub); EC_KEY_set_conv_form(key, POINT_CONVERSION_UNCOMPRESSED); int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(key)); ASN1_OBJECT *obj = OBJ_nid2obj(nid); int len = OBJ_length(obj); if (len == 0) { DCRYPT_SET_ERROR("Object has no OID assigned"); return FALSE; } len = i2d_ASN1_OBJECT(obj, NULL); unsigned char *bufptr = p_malloc(pool, len); struct dcrypt_raw_key *item = array_append_space(&keys); item->parameter = bufptr; item->len = i2d_ASN1_OBJECT(obj, &bufptr); ASN1_OBJECT_free(obj); /* store public key */ const EC_POINT *point = EC_KEY_get0_public_key(key); len = EC_POINT_point2oct(EC_KEY_get0_group(key), point, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL); bufptr = p_malloc(pool, len); item = array_append_space(&keys); item->parameter = bufptr; item->len = len; if (EC_POINT_point2oct(EC_KEY_get0_group(key), point, POINT_CONVERSION_UNCOMPRESSED, bufptr, len, NULL) < (unsigned int)len) return dcrypt_openssl_error(error_r); *type_r = DCRYPT_KEY_EC; } else { DCRYPT_SET_ERROR("Key type unsupported"); return FALSE; } array_append_array(keys_r, &keys); return TRUE; } static bool dcrypt_openssl_key_load_private_raw(struct dcrypt_private_key **key_r, enum dcrypt_key_type type, const ARRAY_TYPE(dcrypt_raw_key) *keys, const char **error_r) { int ec; i_assert(keys != NULL && array_is_created(keys) && array_count(keys) > 1); const struct dcrypt_raw_key *item; if (type == DCRYPT_KEY_RSA) { DCRYPT_SET_ERROR("Not implemented"); return FALSE; } else if (type == DCRYPT_KEY_EC) { /* get curve */ if (array_count(keys) < 2) { DCRYPT_SET_ERROR("Invalid parameters"); return FALSE; } item = array_idx(keys, 0); const unsigned char *oid = item->parameter; ASN1_OBJECT *obj = d2i_ASN1_OBJECT(NULL, &oid, item->len); if (obj == NULL) return dcrypt_openssl_error(error_r); int nid = OBJ_obj2nid(obj); ASN1_OBJECT_free(obj); /* load private point */ item = array_idx(keys, 1); BIGNUM *bn = BN_secure_new(); if (BN_bin2bn(item->parameter, item->len, bn) == NULL) { BN_free(bn); return dcrypt_openssl_error(error_r); } /* setup a key */ EC_KEY *key = EC_KEY_new_by_curve_name(nid); ec = EC_KEY_set_private_key(key, bn); BN_free(bn); if (ec != 1) { EC_KEY_free(key); return dcrypt_openssl_error(error_r); } /* calculate & assign public key */ EC_POINT *pub = EC_POINT_new(EC_KEY_get0_group(key)); if (pub == NULL) { EC_KEY_free(key); return dcrypt_openssl_error(error_r); } /* calculate public key */ ec = EC_POINT_mul(EC_KEY_get0_group(key), pub, EC_KEY_get0_private_key(key), NULL, NULL, NULL); if (ec == 1) ec = EC_KEY_set_public_key(key, pub); EC_POINT_free(pub); /* check the key */ if (ec != 1 || EC_KEY_check_key(key) != 1) { EC_KEY_free(key); return dcrypt_openssl_error(error_r); } EC_KEY_set_asn1_flag(key, OPENSSL_EC_NAMED_CURVE); EVP_PKEY *pkey = EVP_PKEY_new(); EVP_PKEY_set1_EC_KEY(pkey, key); EC_KEY_free(key); *key_r = i_new(struct dcrypt_private_key, 1); (*key_r)->key = pkey; (*key_r)->ref++; return TRUE; } else { DCRYPT_SET_ERROR("Key type unsupported"); } return FALSE; } static bool dcrypt_openssl_key_load_public_raw(struct dcrypt_public_key **key_r, enum dcrypt_key_type type, const ARRAY_TYPE(dcrypt_raw_key) *keys, const char **error_r) { int ec; i_assert(keys != NULL && array_is_created(keys) && array_count(keys) > 1); const struct dcrypt_raw_key *item; if (type == DCRYPT_KEY_RSA) { DCRYPT_SET_ERROR("Not implemented"); return FALSE; } else if (type == DCRYPT_KEY_EC) { /* get curve */ if (array_count(keys) < 2) { DCRYPT_SET_ERROR("Invalid parameters"); return FALSE; } item = array_idx(keys, 0); const unsigned char *oid = item->parameter; ASN1_OBJECT *obj = d2i_ASN1_OBJECT(NULL, &oid, item->len); if (obj == NULL) { dcrypt_openssl_error(error_r); return FALSE; } int nid = OBJ_obj2nid(obj); ASN1_OBJECT_free(obj); /* set group */ EC_GROUP *group = EC_GROUP_new_by_curve_name(nid); if (group == NULL) { dcrypt_openssl_error(error_r); return FALSE; } /* load point */ item = array_idx(keys, 1); EC_POINT *point = EC_POINT_new(group); if (EC_POINT_oct2point(group, point, item->parameter, item->len, NULL) != 1) { EC_POINT_free(point); EC_GROUP_free(group); return dcrypt_openssl_error(error_r); } EC_KEY *key = EC_KEY_new(); ec = EC_KEY_set_group(key, group); if (ec == 1) ec = EC_KEY_set_public_key(key, point); EC_POINT_free(point); EC_GROUP_free(group); if (ec != 1 || EC_KEY_check_key(key) != 1) { EC_KEY_free(key); return dcrypt_openssl_error(error_r); } EC_KEY_precompute_mult(key, NULL); EC_KEY_set_asn1_flag(key, OPENSSL_EC_NAMED_CURVE); EVP_PKEY *pkey = EVP_PKEY_new(); EVP_PKEY_set1_EC_KEY(pkey, key); EC_KEY_free(key); *key_r = i_new(struct dcrypt_public_key, 1); (*key_r)->key = pkey; (*key_r)->ref++; return TRUE; } else { DCRYPT_SET_ERROR("Key type unsupported"); } return FALSE; } static bool dcrypt_openssl_key_get_curve_public(struct dcrypt_public_key *key, const char **curve_r, const char **error_r) { EVP_PKEY *pkey = key->key; char objtxt[OID_TEXT_MAX_LEN]; if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) { DCRYPT_SET_ERROR("Unsupported key type"); return FALSE; } ASN1_OBJECT *obj = OBJ_nid2obj(EC_GROUP_get_curve_name( EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(pkey)))); int len = OBJ_obj2txt(objtxt, sizeof(objtxt), obj, 1); ASN1_OBJECT_free(obj); if (len < 1) { return dcrypt_openssl_error(error_r); } else if ((unsigned int)len > sizeof(objtxt)) { DCRYPT_SET_ERROR("Object name too long"); return FALSE; } *curve_r = t_strndup(objtxt, len); return TRUE; } static const char * dcrypt_openssl_key_get_id_public(struct dcrypt_public_key *key) { return key->key_id; } static const char * dcrypt_openssl_key_get_id_private(struct dcrypt_private_key *key) { return key->key_id; } static void dcrypt_openssl_key_set_id_public(struct dcrypt_public_key *key, const char *id) { i_free(key->key_id); key->key_id = i_strdup_empty(id); } static void dcrypt_openssl_key_set_id_private(struct dcrypt_private_key *key, const char *id) { i_free(key->key_id); key->key_id = i_strdup_empty(id); } static enum dcrypt_key_usage dcrypt_openssl_key_get_usage_public(struct dcrypt_public_key *key) { return key->usage; } static enum dcrypt_key_usage dcrypt_openssl_key_get_usage_private(struct dcrypt_private_key *key) { return key->usage; } static void dcrypt_openssl_key_set_usage_public(struct dcrypt_public_key *key, enum dcrypt_key_usage usage) { key->usage = usage; } static void dcrypt_openssl_key_set_usage_private(struct dcrypt_private_key *key, enum dcrypt_key_usage usage) { key->usage = usage; } static struct dcrypt_vfs dcrypt_openssl_vfs = { .initialize = dcrypt_openssl_initialize, .ctx_sym_create = dcrypt_openssl_ctx_sym_create, .ctx_sym_destroy = dcrypt_openssl_ctx_sym_destroy, .ctx_sym_set_key = dcrypt_openssl_ctx_sym_set_key, .ctx_sym_set_iv = dcrypt_openssl_ctx_sym_set_iv, .ctx_sym_set_key_iv_random = dcrypt_openssl_ctx_sym_set_key_iv_random, .ctx_sym_set_padding = dcrypt_openssl_ctx_sym_set_padding, .ctx_sym_get_key = dcrypt_openssl_ctx_sym_get_key, .ctx_sym_get_iv = dcrypt_openssl_ctx_sym_get_iv, .ctx_sym_set_aad = dcrypt_openssl_ctx_sym_set_aad, .ctx_sym_get_aad = dcrypt_openssl_ctx_sym_get_aad, .ctx_sym_set_tag = dcrypt_openssl_ctx_sym_set_tag, .ctx_sym_get_tag = dcrypt_openssl_ctx_sym_get_tag, .ctx_sym_get_key_length = dcrypt_openssl_ctx_sym_get_key_length, .ctx_sym_get_iv_length = dcrypt_openssl_ctx_sym_get_iv_length, .ctx_sym_get_block_size = dcrypt_openssl_ctx_sym_get_block_size, .ctx_sym_init = dcrypt_openssl_ctx_sym_init, .ctx_sym_update = dcrypt_openssl_ctx_sym_update, .ctx_sym_final = dcrypt_openssl_ctx_sym_final, .ctx_hmac_create = dcrypt_openssl_ctx_hmac_create, .ctx_hmac_destroy = dcrypt_openssl_ctx_hmac_destroy, .ctx_hmac_set_key = dcrypt_openssl_ctx_hmac_set_key, .ctx_hmac_set_key_random = dcrypt_openssl_ctx_hmac_set_key_random, .ctx_hmac_get_digest_length = dcrypt_openssl_ctx_hmac_get_digest_length, .ctx_hmac_get_key = dcrypt_openssl_ctx_hmac_get_key, .ctx_hmac_init = dcrypt_openssl_ctx_hmac_init, .ctx_hmac_update = dcrypt_openssl_ctx_hmac_update, .ctx_hmac_final = dcrypt_openssl_ctx_hmac_final, .ecdh_derive_secret_local = dcrypt_openssl_ecdh_derive_secret_local, .ecdh_derive_secret_peer = dcrypt_openssl_ecdh_derive_secret_peer, .pbkdf2 = dcrypt_openssl_pbkdf2, .generate_keypair = dcrypt_openssl_generate_keypair, .load_private_key = dcrypt_openssl_load_private_key, .load_public_key = dcrypt_openssl_load_public_key, .store_private_key = dcrypt_openssl_store_private_key, .store_public_key = dcrypt_openssl_store_public_key, .private_to_public_key = dcrypt_openssl_private_to_public_key, .key_string_get_info = dcrypt_openssl_key_string_get_info, .unref_keypair = dcrypt_openssl_unref_keypair, .unref_public_key = dcrypt_openssl_unref_public_key, .unref_private_key = dcrypt_openssl_unref_private_key, .ref_public_key = dcrypt_openssl_ref_public_key, .ref_private_key = dcrypt_openssl_ref_private_key, .rsa_encrypt = dcrypt_openssl_rsa_encrypt, .rsa_decrypt = dcrypt_openssl_rsa_decrypt, .oid2name = dcrypt_openssl_oid2name, .name2oid = dcrypt_openssl_name2oid, .private_key_type = dcrypt_openssl_private_key_type, .public_key_type = dcrypt_openssl_public_key_type, .public_key_id = dcrypt_openssl_public_key_id, .public_key_id_old = dcrypt_openssl_public_key_id_old, .private_key_id = dcrypt_openssl_private_key_id, .private_key_id_old = dcrypt_openssl_private_key_id_old, .key_store_private_raw = dcrypt_openssl_key_store_private_raw, .key_store_public_raw = dcrypt_openssl_key_store_public_raw, .key_load_private_raw = dcrypt_openssl_key_load_private_raw, .key_load_public_raw = dcrypt_openssl_key_load_public_raw, .key_get_curve_public = dcrypt_openssl_key_get_curve_public, .key_get_id_public = dcrypt_openssl_key_get_id_public, .key_get_id_private = dcrypt_openssl_key_get_id_private, .key_set_id_public = dcrypt_openssl_key_set_id_public, .key_set_id_private = dcrypt_openssl_key_set_id_private, .key_get_usage_public = dcrypt_openssl_key_get_usage_public, .key_get_usage_private = dcrypt_openssl_key_get_usage_private, .key_set_usage_public = dcrypt_openssl_key_set_usage_public, .key_set_usage_private = dcrypt_openssl_key_set_usage_private, .sign = dcrypt_openssl_sign, .verify = dcrypt_openssl_verify, .ecdh_derive_secret = dcrypt_openssl_ecdh_derive_secret, }; void dcrypt_openssl_init(struct module *module ATTR_UNUSED) { dovecot_openssl_common_global_ref(); dcrypt_set_vfs(&dcrypt_openssl_vfs); } void dcrypt_openssl_deinit(void) { dovecot_openssl_common_global_unref(); } dovecot-2.3.21.1/src/util/0000755000000000000000000000000014656633640012131 500000000000000dovecot-2.3.21.1/src/util/health-check.sh0000755000000000000000000000165714656633576014751 00000000000000#!/bin/sh # # Copyright (c) 2019 Dovecot authors, see the included COPYING file */ # # This script is intended to be called by the script service and to be # connected to a socket using a service configuration like this: # executable = script -p /path/to/health-check.sh # # This simple example merely answers "PONG\n" if the input is "PING\n". It # stops waiting for input after $timeout which causes the process to die # which causes the script module to close the socket. Inputs and outputs # can be written to STDIN and STDOUT, they are duplicated file-descriptors # if called from the script service. timeout=10 # timeout the read via trap for POSIX shell compatibility trap "exit 0" QUIT { sleep $timeout kill -3 $$ 2>/dev/null } & read -r input exit_code=$? cleaned_input=$(echo ${input} | sed "s/[^a-zA-Z0-9]//g") if [ ${exit_code} -eq 0 ] && [ "${cleaned_input}" = "PING" ];then echo "PONG" fi # always exit successful exit 0 dovecot-2.3.21.1/src/util/test-fs.c0000644000000000000000000002526214656633576013621 00000000000000/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "guid.h" #include "llist.h" #include "master-service.h" #include "dict.h" #include "fs-api.h" #define DEFAULT_MAX_PARALLEL_OPS 30 #define DEFAULT_FILES_COUNT 100 /* Allow +-10% difference in number of files */ #define FILES_COUNT_APPROX 0.9 struct test_read { struct test_read *prev, *next; struct test_ctx *ctx; struct fs_file *file; struct istream *input; struct io *io; }; struct test_write { struct test_write *prev, *next; struct test_ctx *ctx; struct fs_file *file; }; struct test_delete { struct test_delete *prev, *next; struct test_ctx *ctx; struct fs_file *file; }; struct test_iter { struct test_iter *prev, *next; struct test_ctx *ctx; pool_t pool; ARRAY_TYPE(const_string) files; struct fs_iter *iter; bool object_ids; }; struct test_ctx { struct fs *fs; const char *prefix; bool sync_only, async_only; unsigned int files_count; unsigned int max_parallel_ops; pool_t files_pool; ARRAY_TYPE(const_string) files; struct timeout *to; struct test_read *reads; struct test_write *writes; struct test_delete *deletes; struct test_iter *iters; unsigned int running_op_count; unsigned int total_reads, total_writes, total_deletes, total_iters; }; static struct ioloop *root_ioloop; static void test_more(struct test_ctx *ctx); static void test_read_callback(struct test_read *r); static bool test_want_async(struct test_ctx *ctx) { if (ctx->async_only) return TRUE; if (ctx->sync_only) return FALSE; return (i_rand_limit(2) == 0); } static void test_op_finish(struct test_ctx *ctx) { i_assert(ctx->running_op_count > 0); ctx->running_op_count--; if (ctx->to == NULL) ctx->to = timeout_add_short_to(root_ioloop, 0, test_more, ctx); } static void test_write_finish(struct test_write *w) { DLLIST_REMOVE(&w->ctx->writes, w); fs_file_deinit(&w->file); w->ctx->total_writes++; test_op_finish(w->ctx); i_free(w); } static void test_write_callback(struct test_write *w) { int ret; ret = fs_write_stream_finish_async(w->file); if (ret < 0) { i_error("fs_write_stream() failed: %s", fs_file_last_error(w->file)); } if (ret != 0) test_write_finish(w); } static void test_next_op_write(struct test_ctx *ctx) { struct test_write *w; struct istream *input, *input2; struct ostream *output; const char *path; w = i_new(struct test_write, 1); w->ctx = ctx; DLLIST_PREPEND(&ctx->writes, w); path = t_strconcat(ctx->prefix, guid_generate(), NULL); w->file = fs_file_init(ctx->fs, path, FS_OPEN_MODE_REPLACE | (test_want_async(ctx) ? FS_OPEN_FLAG_ASYNC : 0)); input = i_stream_create_file("/dev/urandom", IO_BLOCK_SIZE); input2 = i_stream_create_limit(input, i_rand_limit(1024*1024)); output = fs_write_stream(w->file); switch (o_stream_send_istream(output, input2)) { case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: break; case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: i_unreached(); case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: i_fatal("read(/dev/urandom) failed: %s", i_stream_get_error(input)); case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: i_fatal("write() failed: %s", o_stream_get_error(output)); } i_stream_unref(&input); i_stream_unref(&input2); int ret = fs_write_stream_finish(w->file, &output); if (ret < 0) { i_error("fs_write_stream(%s) failed: %s", path, fs_file_last_error(w->file)); } if (ret == 0) fs_file_set_async_callback(w->file, test_write_callback, w); else test_write_finish(w); } static void test_iter_finish(struct test_iter *i) { const char *error; DLLIST_REMOVE(&i->ctx->iters, i); if (fs_iter_deinit(&i->iter, &error) < 0) { i_error("fs_iter_deinit() failed: %s", error); pool_unref(&i->pool); } else { pool_unref(&i->ctx->files_pool); i->ctx->files_pool = i->pool; i->ctx->files = i->files; } i->ctx->total_iters++; test_op_finish(i->ctx); i_free(i); } static void test_iter_callback(struct test_iter *i) { const char *fname; while ((fname = fs_iter_next(i->iter)) != NULL) { if (i->object_ids) { /* skip object ID */ fname = strchr(fname, '/'); i_assert(fname != NULL); fname++; } fname = p_strdup(i->pool, fname); array_push_back(&i->files, &fname); } if (!fs_iter_have_more(i->iter)) test_iter_finish(i); } static void test_next_op_iter(struct test_ctx *ctx) { struct test_iter *i; i = i_new(struct test_iter, 1); i->ctx = ctx; i->pool = pool_alloconly_create(MEMPOOL_GROWING"test iter", 256); p_array_init(&i->files, i->pool, 16); DLLIST_PREPEND(&ctx->iters, i); i->object_ids = (fs_get_properties(ctx->fs) & FS_PROPERTY_OBJECTIDS) == 0 ? FALSE : (i_rand_limit(2) == 0); i->iter = fs_iter_init(ctx->fs, ctx->prefix, (test_want_async(ctx) ? FS_ITER_FLAG_ASYNC : 0) | (i->object_ids ? FS_ITER_FLAG_OBJECTIDS : 0)); fs_iter_set_async_callback(i->iter, test_iter_callback, i); test_iter_callback(i); } static void test_read_finish(struct test_read *r) { DLLIST_REMOVE(&r->ctx->reads, r); io_remove(&r->io); i_stream_unref(&r->input); fs_file_deinit(&r->file); r->ctx->total_reads++; test_op_finish(r->ctx); i_free(r); } static void test_read_callback(struct test_read *r) { const unsigned char *data; size_t size; int ret; while ((ret = i_stream_read_more(r->input, &data, &size)) > 0) i_stream_skip(r->input, size); if (ret == 0) { if (r->io == NULL) { r->io = io_add_istream(r->input, test_read_callback, r); fs_file_set_async_callback(r->file, test_read_callback, r); } return; } i_assert(ret == -1); if (r->input->stream_errno != 0 && r->input->stream_errno != ENOENT) i_error("read() failed: %s", i_stream_get_error(r->input)); test_read_finish(r); } static void test_next_read(struct test_ctx *ctx) { struct test_read *r; r = i_new(struct test_read, 1); r->ctx = ctx; DLLIST_PREPEND(&ctx->reads, r); const char *const *fnamep = array_idx(&ctx->files, i_rand_limit(array_count(&ctx->files))); const char *path = t_strconcat(ctx->prefix, *fnamep, NULL); r->file = fs_file_init(ctx->fs, path, FS_OPEN_MODE_READONLY | (test_want_async(ctx) ? FS_OPEN_FLAG_ASYNC : 0)); r->input = fs_read_stream(r->file, IO_BLOCK_SIZE); test_read_callback(r); } static void test_delete_finish(struct test_delete *d) { DLLIST_REMOVE(&d->ctx->deletes, d); fs_file_deinit(&d->file); d->ctx->total_deletes++; test_op_finish(d->ctx); i_free(d); } static void test_delete_callback(struct test_delete *d) { int ret = fs_delete(d->file); if (ret < 0 && errno != ENOENT) { if (errno == EAGAIN) return; i_error("fs_delete() failed: %s", fs_file_last_error(d->file)); } test_delete_finish(d); } static void test_next_delete(struct test_ctx *ctx) { struct test_delete *d; d = i_new(struct test_delete, 1); d->ctx = ctx; DLLIST_PREPEND(&ctx->deletes, d); const char *const *fnamep = array_idx(&ctx->files, i_rand_limit(array_count(&ctx->files))); const char *path = t_strconcat(ctx->prefix, *fnamep, NULL); d->file = fs_file_init(ctx->fs, path, FS_OPEN_MODE_READONLY | (test_want_async(ctx) ? FS_OPEN_FLAG_ASYNC : 0)); int ret = fs_delete(d->file); if (ret < 0 && errno == EAGAIN) { fs_file_set_async_callback(d->file, test_delete_callback, d); return; } if (ret < 0 && errno != ENOENT) i_error("fs_delete() failed: %s", fs_file_last_error(d->file)); test_delete_finish(d); } static void test_next_op(struct test_ctx *ctx) { switch (i_rand_limit(4)) { case 0: if (array_is_created(&ctx->files) && array_count(&ctx->files) * FILES_COUNT_APPROX > ctx->files_count) break; ctx->running_op_count++; test_next_op_write(ctx); break; case 1: ctx->running_op_count++; test_next_op_iter(ctx); break; case 2: if (!array_is_created(&ctx->files) || array_count(&ctx->files) == 0) break; ctx->running_op_count++; test_next_read(ctx); break; case 3: if (!array_is_created(&ctx->files) || array_count(&ctx->files) / FILES_COUNT_APPROX < ctx->files_count) break; ctx->running_op_count++; test_next_delete(ctx); break; } } static void test_more(struct test_ctx *ctx) { timeout_remove(&ctx->to); /* note that all operations may be synchronous */ for (unsigned int i = ctx->running_op_count; i < ctx->max_parallel_ops; i++) test_next_op(ctx); if (ctx->to == NULL) { ctx->to = timeout_add(ctx->running_op_count == 0 ? 0 : i_rand_limit(100), test_more, ctx); } } static void stats_output(struct test_ctx *ctx) { printf("%u iters, %u reads, %u writes, %u deletes\n", ctx->total_iters, ctx->total_reads, ctx->total_writes, ctx->total_deletes); } int main(int argc, char *argv[]) { struct fs_settings set; struct test_ctx ctx; const char *error; unsigned int timeout_secs = 0; struct timeout *to_stop = NULL; int c; i_zero(&ctx); ctx.max_parallel_ops = DEFAULT_MAX_PARALLEL_OPS; ctx.files_count = DEFAULT_FILES_COUNT; i_zero(&set); set.base_dir = PKG_RUNDIR; master_service = master_service_init("test-fs", MASTER_SERVICE_FLAG_STANDALONE, &argc, &argv, "Daf:p:st:u:"); while ((c = master_getopt(master_service)) > 0) { switch (c) { case 'D': set.debug = TRUE; break; case 'a': ctx.async_only = TRUE; break; case 'f': if (str_to_uint(optarg, &ctx.files_count) < 0) i_fatal("Invalid -f parameter: %s", optarg); break; case 'p': if (str_to_uint(optarg, &ctx.max_parallel_ops) < 0) i_fatal("Invalid -p parameter: %s", optarg); break; case 's': ctx.sync_only = TRUE; break; case 'u': set.username = optarg; break; case 't': if (str_to_uint(optarg, &timeout_secs) < 0) i_fatal("Invalid -t parameter: %s", optarg); break; default: return FATAL_DEFAULT; } } argc -= optind; argv += optind; if (argc != 3) i_fatal("Usage: [-a|-s] [-D] [-f ] [-p ] [-t ] [-u ] "); master_service_init_finish(master_service); dict_drivers_register_builtin(); if (fs_init(argv[0], argv[1], &set, &ctx.fs, &error) < 0) i_fatal("fs_init() failed: %s", error); ctx.prefix = argv[2]; root_ioloop = current_ioloop; test_more(&ctx); if (timeout_secs != 0) to_stop = timeout_add(timeout_secs*1000, io_loop_stop, current_ioloop); struct timeout *to_stats = timeout_add(1000, stats_output, &ctx); io_loop_run(current_ioloop); timeout_remove(&to_stats); timeout_remove(&to_stop); while (ctx.writes != NULL) test_write_finish(ctx.writes); while (ctx.iters != NULL) test_iter_finish(ctx.iters); while (ctx.reads != NULL) test_read_finish(ctx.reads); while (ctx.deletes != NULL) test_delete_finish(ctx.deletes); stats_output(&ctx); timeout_remove(&ctx.to); fs_deinit(&ctx.fs); master_service_deinit(&master_service); } dovecot-2.3.21.1/src/util/dovecot-sysreport0000755000000000000000000001347314656633576015532 00000000000000#!/usr/bin/env bash set -eu dest="dovecot-sysreport-$(uname -n)-$(date +'%s').tar.gz" conf_flag="" binary="" core="" copy_files="" keep_temp=0 PARAMS="" SRTEMP="`mktemp -d`" if test "x$SRTEMP" = x; then echo "Could not create temp directory" exit 1 fi while (( "$#" )); do case "$1" in -d|--destination) if [ "$#" -lt "2" ] ; then echo "Usage: $0 $1 " exit 1 fi dest=$2 shift 2 ;; -c|--config) if [ "$#" -lt "2" ] ; then echo "Usage: $0 $1 " exit 1 fi conf_flag="-c $2" shift 2 ;; -o|--core) gdb=`which gdb` if [ "$gdb" = "" ]; then echo "gdb not found" exit 1 fi if [[ "$#" -lt 2 ]] ; then echo "Usage: $0 $1 [] [...]" exit 1 fi while [[ "$#" -ge 2 ]]; do # see if binary parameter is specified binary=$2 if ! [ -r "$binary" ]; then echo "$binary not readable" exit 1 fi binary_info=$(file "$binary") if echo "$binary_info" | grep "core file.*execfn: '" >/dev/null; then # no binary specified - detect it binary=$(echo "$binary_info" | sed "s;^.*execfn: '\([^\']\+\)'.*$;\1;") if ! [ -r "$binary" ]; then echo "Detected binary path '$binary' from core file, but it is not readable" exit 1 fi echo "Core file was detected to be generated by $binary" else shift fi core=$2 shift if ! [ -s "$core" ]; then echo "$core not found or it is empty" exit 1 fi echo "gathering core file dependencies..." core_files=$((echo "info shared"; sleep 1) | $gdb $binary $core | grep '^0x.*/' | sed 's,^[^/]*,,') copy_files="$copy_files $binary $core_files" cp $core $SRTEMP done shift ;; -k|--keeptemp) keep_temp=1 shift ;; -h|--help) echo -e "dovecot-sysreport \t[-h|--help] [-o|--core [binary] core [...]] [-d|--destination dest] \t\t\t[-k|--keeptemp] -- utility to gather information from the current \t\t\tsystem to be reported for dovecot bug fixes." echo "" echo -e "where:" echo "" echo -e "\t-h, --help\t\tShow the contents of this help." echo -e "\t-d, --destination\tThe file location which the report archive should be put to. \t\t\t\tThe default value is dovecot-sysreport--.tar.gz" echo -e "\t-c, --config\t\tSpecify the root configuration file of dovecot." echo -e "\t-o, --core\t\tInclude an specific core file along with its dependencies." echo -e "\t-k, --keeptemp\t\tDo not remove temp files at the end." exit 0 ;; --) shift break ;; -*|--*=) echo "Error: Unsupported flag $1" >&2 exit 1 ;; *) PARAMS="$PARAMS $1" shift ;; esac done eval set -- "$PARAMS" mkdir $SRTEMP/conf doveconf $conf_flag -n > $SRTEMP/conf/dovecot.conf unwrap_and_hide_pass () { files=`grep -zPo 'dict\s*{[^}]*}' $1 | grep -zPo '.*=.*:\K(.*)' | tr '\0' '\n'` files="$files `grep -zPo 'args\s*=\s*\K(.*)' $1 | tr '\0' '\n'`" for cf in $files; do if [ -r "$cf" ]; then if [[ ! -z `grep -vhIE '^([^:]*:){6}[^:]*$' $cf` ]]; then unwrap_and_hide_pass $cf mkdir -p $SRTEMP/conf"$(dirname "$cf")" if [[ -x "$(command -v python)" ]]; then python <